1. JdPlaySS协议

1.1. 使用场景

使用网络协议控制背景音主机,支持局域网发现和控制背景音主机,开放本地和场景音乐资源及控制,主要用于Linux/RTOS的网关。

1.2. 概述

JdPlaySS是JdPlay Server Socket的缩写,参考了mqtt、DLNA协议的思想,是基于TCP协议的简易Server Socket,可用于非Android/iOS系统(如Linux网关)下的局域网背景音系统控制,并可支持多个客户端同时连接。

1.3. 交互图

JdPlaySS

1.4. 条件&约束

C/S传输数据格式采用json, 且以换行符作为消息分割符。因此不允许消息中存在实际的换行符,但可以存在转义换行符。

1.5. 发现协议

设备发现采用标准的mDNS协议。使用如下命令发现JdPlaySS设备, 服务类型为_jdplayss._tcp, JdPlaySS服务的ip地址为 192.168.1.67, 端口号为8000

$ avahi-browse -a
+   eno1 IPv4 SmartSpeaker H7[1115]                         _jdplayss._tcp       local

$ avahi-browse -r _jdplayss._tcp
+   eno1 IPv4 SmartSpeaker H7[1115]                         _jdplayss._tcp       local
=   eno1 IPv4 SmartSpeaker H7[1115]                         _jdplayss._tcp       local
   hostname = [\040none\041.local]
   address = [192.168.1.67]
   port = [8000]
   txt = ["name=BGM-3588" "id=fe0064a3f0f2b5d73588"]

1.6. 控制协议

1.6.1. 消息格式

C/S直接数据传递采用如下字段,注意seq=0保留给服务器端消息的主动反馈。

{
    "type": [int][必选]控制包类型,
    "seq": [int][可选] 控制包序号,
    "s0": [string][可选] 字符串参数0,
    "s1": [string][可选] 字符串参数1,
    "i0": [int][可选] 整形参数0,
    "i1": [int)[可选] 整形参数1,
}

1.6.2. 消息类型

消息包类型有如下几种

type类型值 传输方向 描述
1 C->S CONNECT: 连接请求
2 S->C CONNACK: 连接应答
3 C->S
S->C
PUBLISH: 发布消息
4 C->S
S->C
PUBACK: 发布消息确认
12 C->S PINGREQ: 心跳请求
13 S->C PINGRESP: 心跳反馈
14 C->S DISCONNECT: 断开请求

1.6.3. 消息描述

CONNECT

i0: version 客户端协议版本,本协议为1
i1: keepalive 心跳包时间,单位秒,范围(10~600秒),建议值300, 客户端每隔240秒,发一次心跳包或者传输一次数据。

CONNACK

i0: version 服务器端协议版本,本协议为1
i1: 0:成功 -1:失败
s0: 反馈的消息字符串,用于调试

PUBLISH

用于C/S之间消息传递,目前支持2种消息,控制消息(C->S)和状态主动(S->C)反馈的消息。Client发送消息给Server时,Server会回复PUBACK消息;Server主动反馈消息给Client时,Client端无需回复PUBACK消息。

seq: [必选] 消息序号,从1开始递增(0:保留给Server消息主动发送)
i0: [必选] cmd命令,支持的命令及参数设置如下。

命令值 命令名 方向 功能 请求参数 PubAck回复参数
100 MEDIA_GET_METADATA C->S 获取元数据 s0:参考元数据
101 MEDIA_PLAY C->S 播放 无参数
102 MEDIA_PAUSE C->S 暂停 无参数
103 MEDIA_NEXT C->S 下一首 无参数
104 MEDIA_PREV C->S 上一首 无参数
105 MEDIA_SEEK C->S 跳播 i1: 跳播位置,单位秒 无参数
106 MEDIA_GET_POSITION C->S 获取播放位置 s0: 当前时间(秒):总时间(秒)
107 MEDIA_SET_VOLUME C->S 设置音量 i1: 音量值(0~100) 无参数
108 MEDIA_GET_VOLUME C->S 获取音量 i1: 音量值(0~100)
109 MEDIA_GET_ALL_LOCAL_MEDIA C->S 获取所有本地歌曲信息 s0:简单音乐元数据数组
111 MEDIA_SWITCH_PLAY_MODE C->S 切换播放模式,如果是电台类,会提示不支持电台类模式切换 无参数
115 MEDIA_GET_PLAY_MODE C->S 获取当前播放模式
i1: 播放模式
0:重复所有
1:单曲循环
2:随机播放
116 MEDIA_PLAY_TTS C->S 用语音播放TTS文本 s0:TTS文本 无参数
118 MEDIA_PLAY_HINT_PATH C->S 播放提示音 s0:完整的提示音路径 无参数
119 MEDIA_GET_AUDIO_SOURCE C->S 获取当前的音源 s0: 当前的音源
120 MEDIA_SET_AUDIO_SOURCE C->S 切换音源 s0:目标音源 无参数
150 MEDIA_REPORT_METADATA S->C 反馈元数据 s0:参考元数据 Client无需回复
151 MEDIA_REPORT_PlAY_STATE S->C 反馈播放状态 i1: 播放状态
0:暂停
1:正在播放
2:缓冲结束
Client无需回复
152 MEDIA_REPORT_VOLUME S->C 反馈音量 i1:音量值(0~100) Client无需回复
153 MEDIA_REPORT_PLAY_MODE S->C 反馈当前的播放模式 i1: 播放模式
0:重复所有
1:单曲循环
2:随机播放
3:顺序播放
Client无需回复
154 MEDIA_REPORT_AUDIO_SOURCE S->C 反馈当前的音源 s0:当前音源
sdcard:本地
bt:蓝牙
online:在线
auxin:外部音频
160 MEDIA_GET_SONGLIST S->C 获取歌单
s0.type: 请求歌曲json参数
0:最近播放列表
1:我的最爱
2:我的歌单
100:当前播放歌单
s0:音乐或歌单元数据数组
161 MEDIA_PLAY_SONGLIST C->S 播放歌单 s0:音乐元数据
i1:播放模式
无参数
200 DEVICE_POWER_ON C->S 开屏 无参数
201 DEVICE_POWER_OFF C->S 关屏 无参数
202 DEVICE_POWER_REBOOT C->S 重启 无参数
203 DEVICE_GET_POWER_STATUS C->S 获取开机状态 i1: 0表示关机,1表示开机
204 DEVICE_GET_INFO C->S 获取设备信息 s0:小可主机信息
uuid、name、version等
元数据
{
  "playState": 1,
  "singer": "刘德华",
  "songId": "548408",
  "songTitle": "爱你一万年",
  "songUrl": "http://www.xxx.com/1.mp3",
  "volume": 80
}
音乐元数据
{
  "songId": "548408",
  "songTitle": "爱你一万年",
  "singer": "刘德华",
  "source": "migu",
  "type":2 //歌单或歌曲类型
}

PUBACK

i0: 跟PUBLISH cmd命令值一致
i1,s0: [可选],如果需要反馈消息

回复参数如上表格

PINGREQ

无参数

PINGRESP

无参数

DISCONNECT

无参数

1.7. 集成&开发

1.7.1. Client端流程

  1. 通过DLNA设备发现发现背景音主机设备
  2. 建立socket链接,TCP连接到设备IP地址的8000端口, 开始socket收发数据,并以换行符作为包分隔符。
  3. 发送CONNECT请求,参考发送数据 {"type":1,"i0":1,"i1":240} 等待Server端反馈连接成功。 参考接收到的数据{"i0":1,"i1":0,"s0":"OK","seq":0,"type":2}
  4. 如果连接成功,发送 PUBLISH/MEDIA_GET_METADATA 请求,获取元数据。 参考发送数据{"type":3,"i0":100,"seq":1}
  5. 根据播放状态决定是否周期性读取播放位置,如果 s0.playState=1, 发送获取播放位置请求,参考发送数据 {"type":3,"i0":106,"seq":1}
  6. 客户端需要在KeepAlive时间周期内发送数据给服务端以维持长连接,否则服务器端超时会主动断开连接。客户端也需要检测PINGREQ/PINGRESP消息或ACK消息是否收到,如果客户端检测到通信链路异常,则需要跟服务端重连。
  7. 接收线程可能监听到元数据、播放状态以及音量改变的消息。

1.8. 测试&调试方法

  1. 进入背景音系统 “设置>网络设置”, 查看ip地址,通过telnet连接背景音系统
telnet ip地址 8000
  1. 根据如上"Client端流程",在telnet终端通过命令测试交互
    => telnet 192.168.1.154 8000
    Trying 192.168.1.154...
    Connected to 192.168.1.154.
    Escape character is '^]'.
    {"type":1,"i0":1,"i1":240}                
    {"i0":1,"i1":0,"s0":"OK","seq":0,"type":2}
    {"type":3,"i0":101,"seq":1}
    {"i0":101,"i1":0,"seq":1,"type":4}
    {"i0":150,"i1":0,"s0":"{\"playState\":0,\"singer\":\"Brooke White\",\"songId\":\"1552954\",\"songTitle\":\"Let It Be\",\"songUrl\":\"http://mr3.doubanio.com/87b89194e8858151bf1375eb17c96878/0/fm/song/p1552954_128k.mp3\",\"volume\":40}","seq":0,"type":3}
    {"i0":151,"i1":2,"seq":0,"type":3}
    {"type":3,"i0":102,"seq":1}
    {"i0":151,"i1":0,"seq":0,"type":3}
    {"i0":102,"i1":0,"seq":1,"type":4}
    
    发送”连接请求“命令
    {"type":1,"i0":1,"i1":240}
    
    背景音系统返回
    {"i0":1,"i1":0,"s0":"OK","seq":0,"type":2}
    
    发送”播放“命令
    {"type":3,"i0":101,"seq":1}
    
    背景音系统返回
    {"i0":101,"i1":0,"seq":1,"type":4}
    {"i0":150,"i1":0,"s0":"{\"playState\":0,\"singer\":\"Brooke White\",\"songId\":\"1552954\",\"songTitle\":\"Let It Be\",\"songUrl\":\"http://mr3.doubanio.com/87b89194e8858151bf1375eb17c96878/0/fm/song/p1552954_128k.mp3\",\"volume\":40}","seq":0,"type":3}
    {"i0":151,"i1":2,"seq":0,"type":3}
    
    发送”暂停“命令
    {"type":3,"i0":102,"seq":1}
    
    背景音系统返回
    {"i0":151,"i1":0,"seq":0,"type":3}
    {"i0":102,"i1":0,"seq":1,"type":4}
    

    1.9. 参考

1.9.1. 常量定义

为简化开发者工作量,常量定义如下:

//JSSS协议命令
#define CONNECT  1
#define CONNACK  2
#define PUBLISH  3
#define PUBACK  4
#define PINGREQ  12
#define PINGRESP  13
#define DISCONNECT  14

//JSSS协议媒体控制命令
#define MEDIA_GET_METADATA  100
#define MEDIA_PLAY  101
#define MEDIA_PAUSE  102
#define MEDIA_NEXT  103
#define MEDIA_PREV  104
#define MEDIA_SEEK  105
#define MEDIA_GET_POSITION  106
#define MEDIA_SET_VOLUME  107
#define MEDIA_GET_VOLUME  108
#define MEDIA_GET_ALL_LOCAL_MEDIA  109
#define MEDIA_PLAY_LOCAL_SONG  110
#define MEDIA_SWITCH_PLAY_MODE  111
#define MEDIA_GET_SCENE_MUSICS  112
#define MEDIA_PLAY_SCENE_MUSIC  113
#define MEDIA_PLAY_ONCE_LOCAL_SONG  114
#define MEDIA_GET_PLAY_MODE  115
#define MEDIA_PLAY_TTS  116
#define MEDIA_PLAY_HINT  117
#define MEDIA_PLAY_HINT_PATH  118
#define MEDIA_GET_AUDIO_SOURCE  119
#define MEDIA_SET_AUDIO_SOURCE  120

//JSSS协议媒体状态反馈命令
#define MEDIA_REPORT_METADATA  150
#define MEDIA_REPORT_PLAY_STATE  151
#define MEDIA_REPORT_VOLUME  152
#define MEDIA_REPORT_PLAY_MODE  153
#define MEDIA_REPORT_AUDIO_SOURCE  154
#define MEDIA_REPORT_PROGRESS  155

#define MEDIA_GET_SONGLIST 160
#define MEDIA_PLAY_SONGLIST 161

//JSSS协议设备控制命令
#define DEVICE_POWER_ON  200
#define DEVICE_POWER_OFF  201
#define DEVICE_POWER_REBOOT  202
#define DEVICE_GET_POWER_STATUS  203
#define DEVICE_GET_INFO  204

1.9.2. 常用指令示例

连接命令

telnet 10.0.0.26 8000
telnet 192.168.1.176 8000
{"type":1,"i0":109,"i1":240}

获取所有本地歌曲

{"type":3,"i0":109,"seq":1}

播放本地歌曲

注意:在播放本地歌曲前必须先发获取所有本地歌曲的指令。s0:简单音乐元数据数组 ,i1:开始播放索引

{"i0":110,"i1":0,"s0":"[{\"songId\":\"40\",\"songTitle\":\"DreamVillage_GuZheng-pre\"},{\"songId\":\"101\",\"songTitle\":\"A.I.N.Y.-[爱你]\"}]","seq":1,"type":3}

播放本地单首歌曲,单曲不循环

注意:s0此时为单个音乐元数据json字符串

{"i0":114,"i1":0,"s0":"{\"songId\":\"40\",\"songTitle\":\"DreamVillage_GuZheng-pre\"}","seq":1,"type":3}

获取当前的音源

注意:s0参数有sdcard 本地、bt 蓝牙、online 在线、auxin 外部音频四种

{"i0":119,"seq":1,"type":3}
{"i0":119,"i1":0,"s0":"sdcard","seq":1,"type":4}

获取我的歌单列表

{"i0":160,"i1":1,"seq":1,"type":3, "s0":"{\"type\":2}"}

播放歌单

{"i0":161,"i1":1,"seq":1,"type":3,"s0":"{\"songTitle\":\"测试\",\"songId\":\"261aba01-760f-47\",\"singer\":\"\",\"source\":\"\",\"type\":2}"}

获取当前播放列表

{"i0":160,"seq":1,"type":3, "s0":"{\"type\":100}"}

播放当前播放列表某首歌曲

注意:i1为播放第几首歌曲

{"i0":161,"i1":1,"seq":1,"type":3,"s0":"{\"type\":100}"}

切换音源

注意:切换完本地或在线音源后要发送播放指令才能播放歌曲

想播放本地歌曲使用上面109(获取本地歌曲)和110(播放本地歌曲)指令结合实现

想随机播放在线歌曲可以使用120(切到在线音源)和 101(播放歌曲)指令结合实现

C -> S 
{"i0":120,"s0":"online","seq":1,"type":3}
{"i0":120,"s0":"bt","seq":1,"type":3}
{"i0":120,"s0":"sdcard","seq":1,"type":3}
{"i0":120,"s0":"auxin","seq":1,"type":3}

S -> C 
{"i0":120,"i1":0,"seq":1,"type":4}
i1值为0表切换音源成功,-1表切换音源失败

播放

{"i0":101,"seq":1,"type":3}

获取当前音乐元数据

C -> S
{"type":3,"i0":100,"seq":1}

S -> C
{"i0":100,"i1":0,"s0":"{\"playState\":1,\"singer\":\"Harry Styles\",\"songId\":\"003luGpS16qZgi\",\"songTitle\":\"Sweet Creature\",\"songUrl\":\"http://dl.stream.qqmusic.qq.com/C400003luGpS16qZgi.m4a?vkey=E3EDE3145DFD5364AA8DE7315B218A9A963C25BA76AD47D7760486319CB9A3980125E46BD1A19BF0FBC94D2099B32DD27B0B104FF236EBE5&guid=5358354936&fromtag=30\",\"volume\":7}","seq":1,"type":4}

切换播放模式

{"type":3,"i0":111,"seq":4}

1.10. FAQ

E:开关屏操作仅针对于有屏设备
Q:为什么Server端Socket连接会主动断开

A:请检查是否在KeepAlive时间内发送过PING或者PUBLISH命令

Q:为什么Client发布消息不成功

A:请检查seq字段是否设置为非0值,并主动递增

Q:Window 使用 PuTTY 进行测试
A: 配置连接ip和端口
发送连接请求{"type":1,"i0":1,"i1":240}

发送连接请求

Q:获取当前播放的歌曲信息策略

A:Socket连接上之后,只需要发送一次命令 MEDIA_GET_METADATA 获取当前播放的歌曲信息,只要Socket连接一直不断,以后都通过MEDIA_REPORT_PlAY_STATE主动反馈歌曲信息即可。

Q:换行符用哪个

A:换行符统一使用 \n

Q:怎么获取小可主机设备ID

A:发现设备的时候,notify包里面包含设备ID

Q:播放模式有哪些

A:
0 列表循环 REPEAT_ALL
1 单曲循环 REPEAT_ONE
2 随机播放 SHUFFLE
3 顺序播放 ORDER

Q:哪些信息发生改变服务端会主动上报

A:小可主机端的歌曲、音量、播放状态、播放模式发生改变后,会主动上报新状态

Q:怎么获取播放进度

A:当前播放进度,要C主动向S查询,指令:MEDIA_GET_POSITION

Q:客户端发M-SEARCH包,server并不会立即应答

A:SSDP包是组播包,存在被丢包的可能。每次发送search包的时候,可以发多个

Q:如果网络不好的情况下,小可在什么情况下会主动断开连接,有没有重连机制

A:JSSS 小可作为服务器端,不会根据网络状况来决定断开。会重连,客户端需要在KeepAlive时间周期内发送数据给服务端以维持长连接,否则服务器端超时会主动断开连接。客户端也需要检测PINGREQ/PINGRESP消息或ACK消息是否收到,如果客户端检测到通信链路异常,则需要跟服务端重连。

results matching ""

    No results matching ""