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. 发现协议

设备发现采用DLNA的设备发现机制。Client加入组播组并发送SSDP M-SEARCH包到组播地址239.255.255.250:1900端口,也可以直接对当前局域网的广播地址(如192.168.1.255)直接发送SSDP M-SEARCH广播包。参考M-SEARCH包数据如下:

M-SEARCH * HTTP/1.1
MX: 2
ST: upnp:rootdevice
MAN: "ssdp:discover"
User-Agent: UPnP/1.0 DLNADOC/1.50 JdPlay/1.0.0
Connection: close
Host: 239.255.255.250:1900

Client监听到SSDP NOTIFY包,解析包如果含有EXT字段并该字段含有JDPLAY关键字,则发现背景音系统,如果版本号大于XXX,则可以通过本协议来控制。 参考NOTIFY包数据如下:

NOTIFY * HTTP/1.1
Host: 239.255.255.250:1900
Location: http://192.168.1.154:1500/
Cache-Control: max-age=100
Server: UPnP/1.0 DLNADOC/1.50 EglPlay/1.0.0
BOOTID.UPNP.ORG: 1490004021
EXT: JDPLAY/2.1.1
NTS: ssdp:alive
USN: uuid:10000003816::urn:schemas-upnp-org:device:MediaRenderer:1
NT: urn:schemas-upnp-org:device:MediaRenderer:1

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:简单音乐元数据数组
110 MEDIA_PLAY_LOCAL_SONG C->S 播放本地歌曲 s0:简单音乐元数据数组 i1:开始播放索引 无参数
111 MEDIA_SWITCH_PLAY_MODE C->S 切换播放模式,如果是电台类,会提示不支持电台类模式切换 无参数
112 MEDIA_GET_SCENE_MUSICS C->S 获取所有场景音乐 s0: 简单音乐元数据数组,场景音乐有效字段只有songId, songTitle
113 MEDIA_PLAY_SCENE_MUSIC C->S 播放场景音乐 i1:场景音乐id,即元数据中的songId字段 无参数
114 MEDIA_PLAY_LOCAL_ONE_SONG C->S 播放本地单首歌曲,单曲不循环 s0:仅一个简单音乐元数据,非数组 无参数
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:外部音频
155 MEDIA_REPORT_PROGRESS S->C 反馈当前播放进度 s0: 当前时间(秒):总时间(秒)
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等
205 MEDIA_SET_AUDIO_SYNC C->S 设置为同播或分区模式 无参数
206 MEDIA_SET_AUDIO_SWITCH_CHANNEL C->S 设置分区 i1:通道分区(1:分区1, 2:分区2) 无参数
207 MEDIA_GET_AUDIO_SYNC C->S 获取当前模式 i1:同播状态(1:同步,0:分区)
208 MEDIA_GET_AUDIO_SWITCH_CHANNEL C->S 获取当前分区 i1: 通道分区(1:分区1,2:分区2)
209 MEDIA_AUDIO_SYNC S->C 监听当前模式 i1:同播状态(1:同步,0:分区) Client无需回复
210 MEDIA_AUDIO_SWITCH_CHANNEL S->C 监听当前分区状态 i1: 通道分区(1:分区1,2:分区2) Client无需回复
211 MEDIA_SET_VOICE_CHANNEL1 C->S 设置分区一的音量 i1: 音量值(0~100) 无参数
212 MEDIA_SET_VOICE_CHANNEL2 C->S 设置分区二的音量 i1: 音量值(0~100) 无参数
213 MEDIA_GET_VOICE_CHANNEL S->C 反馈当前分区音量 s0: 音量(":")
0:冒号前面的*为分区一的音量
1:后面为分区二的音量
2:范围0-100
Client无需回复
214 MEDIA_GET_VOICE_CHANNEL1 C->S 获取分区一的音量 无参数 i1: 音量值(0~100)
215 MEDIA_GET_VOICE_CHANNEL2 C->S 获取分区二的音量 无参数 i1: 音量值(0~100)
216 MEDIA_CHECK_DUAL C->S 查看机器是否是双音源的 i1: 音乐主机类型(1表示是双音源,0不是双音源)
元数据
{
  "playState": 1,
  "singer": "刘德华",
  "songId": "548408",
  "songTitle": "爱你一万年",
  "songUrl": "http://www.xxx.com/1.mp3",
  "volume": 80
}
简单音乐元数据
{
  "songId": "548408",
  "songTitle": "爱你一万年",
}

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. 常量定义

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

public class JSSSConstant {
    public static final int VERSION = 1;

    //JSSS协议命令,type字段
    public static final int CONNECT = 1;
    public static final int CONNACK = 2;
    public static final int PUBLISH = 3;
    public static final int PUBACK = 4;
    public static final int PINGREQ = 12;
    public static final int PINGRESP = 13;
    public static final int DISCONNECT = 14;

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

    //JSSS协议媒体状态反馈命令
    public static final int MEDIA_REPORT_METADATA = 150;
    public static final int MEDIA_REPORT_PlAY_STATE = 151;
    public static final int MEDIA_REPORT_VOLUME = 152;
    public static final int MEDIA_REPORT_PLAY_MODE = 153;

    //JSSS协议设备控制命令
    public static final int DEVICE_POWER_ON = 200;
    public static final int DEVICE_POWER_OFF = 201;
    public static final int DEVICE_POWER_REBOOT = 202;
    public static final int DEVICE_GET_POWER_STATUS = 203;
    //JSSS协议切换双音源通道
    public static final int MEDIA_SET_AUDIO_SYNC = 205; //设置为同播或分区
    public static final int MEDIA_SET_AUDIO_SWITCH_CHANNEL = 206; //设置为通道1或通道2 

    //获取当前状态
    public static final int MEDIA_GET_AUDIO_SYNC = 207; //获取当前是同播还是分区
    public static final int MEDIA_GET_AUDIO_SWITCH_CHANNEL = 208; //获取当前通道

    //JSSS协议切换双音源通道监听结果返回
    public static final int MEDIA_AUDIO_SYNC = 209; // 1同播,0分区
    public static final int MEDIA_AUDIO_SWITCH_CHANNEL = 210; //通道1,2

    //JSSS调节分区音量
    public static final int MEDIA_SET_VOICE_CHANNEL1 = 211;//分区一音量
    public static final int MEDIA_SET_VOICE_CHANNEL2 = 212;//分区二音量

    //JSSS主机分区音量改变返回
    public static final int MEDIA_GET_VOICE_CHANNEL = 213;//主机分区音量改变反馈

    //获取分区音量的指令                                                          
    public static final int MEDIA_GET_VOICE_CHANNEL1 = 214;//获取分区一音量
    public static final int MEDIA_GET_VOICE_CHANNEL2 = 215;//获取分区二音量

    public static final int MEDIA_CHECK_DUAL = 216;//查看机器是否是双音源的
    //JSSS 状态码
    public static final int FAIL = -1;
    public static final int OK = 0;
}

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":112,"seq":1,"type":3}

播放场景音乐

i1为要播放的场景音乐的ID

{"i0":113,"i1":"142884","seq":1,"type":3}

切换音源

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

想播放本地歌曲使用上面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消息是否收到,如果客户端检测到通信链路异常,则需要跟服务端重连。

另附cmd命令方式查看测试

JSSS协议测试 cmd 输入 telnet + 主机ip + 固定端口 8000 电脑和主机在同一个局域网 如 telnet 192.168.1.210 8000

{"type":1,"i0":1,"i1":240}    发送请求连接
{"type":3,"i0":100,"seq":1}  获取元数据
{"type":3,"i0":101,"seq":1}  播放
{"type":3,"i0":102,"seq":1}  暂停
{"type":3,"i0":103,"seq":1}  下一首
{"type":3,"i0":104,"seq":1}  上一首
{"type":3,"i0":105,"i1":20,"seq":1}  跳播
{"type":3,"i0":106,"seq":1}  获取播放进度
{"type":3,"i0":107,"i1":10,"seq":1}  设置音量 i1表示设置的值
{"type":3,"i0":108,"seq":1}     向主机查询音量
{"type":3,"i0":109,"seq":1}     获取所有本地歌曲信息
{"type":3,"i0":110,"seq":1,"i1":0,"s0":"[{\"songId\":\"40\",\"songTitle\":\"DreamVillage_GuZheng-pre\"}]"}     播放本地歌曲信息
{"type":3,"i0":111,"seq":1}  切换播放模式
{"type":3,"i0":112,"seq":1}    获取场景音乐/不含推荐的电台歌单 
{"type":3,"i0":113,"i1":47999835,"seq":1}  播放指定收藏歌单,i1歌单id
{"type":3,"i0":114,"seq":1,"i1":0,"s0":"{\"songId\":\"40\",\"songTitle\":\"DreamVillage_GuZheng-pre\"}"}   播放本地单首歌曲,单曲不循环
{"type":3,"i0":115,"seq":1}  获取当前模式,单曲不循环 4
{"type":3,"i0":118,"seq":1,"s0":"/storage/emulated/0/Music/DreamVillage_GuZheng-pre.mp3"} 播放提示音
{"type":3,"i0":119,"seq":1}  获取当前的音源
{"type":3,"i0":120,"s0":"bt","seq":1}  切换音源
{"type":3,"i0":200,"seq":1} 开屏
{"type":3,"i0":201,"seq":1} 关屏
{"type":3,"i0":202,"seq":1} 重启
{"type":3,"i0":203,"seq":1} 获取开机状态
{"type":3,"i0":204,"seq":1} 获取设备信息
{"type":3,"i0":205,"seq":1} 设置同播或分区
{"type":3,"i0":206,"i1":2,"seq":1} 设置分区
{"type":3,"i0":207,"seq":1}  获取当前同播还是分区模式
{"type":3,"i0":211,"i1":30,"seq":1} 设置分区一音量
{"type":3,"i0":212,"i1":50,"seq":1} 设置分区二音量
{"type":3,"i0":214,"i1":30,"seq":1} 获取分区一音量
{"type":3,"i0":215,"i1":30,"seq":1} 获取分区二音量

results matching ""

    No results matching ""