本文聚焦 RTMP 协议的最精华的内容,接进行实际操做 Buffer 的练习和协议的学习。
RTMP 是什么
RTMP 全称便是 Real-Time Messaging Protocol。顾名思义就是用来做为实时通讯的一种协议。该协议是 Adobe 搞出来的。主要是用来传递音视频流的。它经过一种自定义的协议,来完成对指定直播流的播放和相关的操做。和现行的直播流相比,RTMP 主要的特色就是高效,这里,我就很少费口舌了。咱们先来了解一下 RTMP 是如何进行握手的。
RTMP 握手
RTMP 是基于 TCP 三次握手以后的,因此,RTMP 不是和 TCP 一个 level 的。它自己是基于 TCP 的可靠性链接。RTMP 握手的方式如图:
(C 表明 Client,S 表明 Server)
它主要是经过两端的字段内容协商,来完成可信度认证的。基本过程以下:
-
client: 客户端须要发 3 个包。C0,C1,C2
-
server: 服务端也须要发一样 3 个包。 S0,S1,S2。
【2】 服务端在接受到 C0,发送 S0,S1 包。也能够等到接受到 C1 以后再一块儿发送,C1 包的等待不是必须的。
【3】客户端接受到 S1/S0 包后,发送 C2 包。
【4】服务端接受到 C2 包后,返回 S2 包,而且此时握手已经完成。
不过,在实际应用中,并非严格按照上面的来。由于 RTMP 并非强安全性的协议,因此,S2/C2 包只须要 C1/S1 中的内容,就能够完成内容的拼接。
接下来,咱们来具体看看 C/S 012 包分别表明什么。
C0 && S0
C0 和 S0 其实区别不大,我这里主要讲解一下 C0,就差很少了。首先,C0 的长度为 1B。它的主要工做是肯定 RTMP 的版本号。
C1 && S1
C1/S1 长度为 1536B。主要目的是确保握手的惟一性。格式为:
C2 && S2
C2/S2 的长度也是 1536B。至关于就是 S1/C1 的响应值。上图也简单说明了就是,对应 C1/S1 的 Copy 值,不过第二个字段有区别。基本格式为:
这里须要说起的是,RTMP 默认都是使用 Big-Endian 进行写入和读取,除非强调对某个字段使用 Little-Endian 字节序。
上面握手协议的顺序也是根据其中相关的字段来进行制定的。这样,看起来很容易啊哈,可是,咱们并不只仅停留在了解,而是要真正的了解,接下来,咱们来实现一下,若是经过 Buffer 来进行 3 次握手。这里,咱们做为 Client 端来进行请求的发起,假设 Server 端是按照标准进行发送便可。
Buffer 实操握手
咱们使用 Buffer 实操主要涉及两块,一个块是 request server 的搭建,还有一块是 Buffer 的拼接。
Request Server 搭建
这里的 Server 是直接使用底层的 TCP 链接。
const client = new net.Socket();
client.connect({
port: 1935,
host: "6721.myqcloud.com"},
()=>{
console.log("connected");
});
client.on('data',(data)=>{
client.write('hello');
});复制代码
不过,为了更好的进行实际演练,咱们经过 EventEmitter 的方式,来作一个筛选器。这里,咱们使用
mitt 模块来作代理。
const Emitter = require('mitt')();复制代码
而后,咱们只要分析的就是将要接受到的 S0/1/2 包。根据上面的字节包图,能够清楚的知道包里面的详细内容。这里,为了简单起见,咱们排除其余协议的包头,只是针对 RTMP 里面的包。并且,咱们针对的只有 3 种包,S0/1/2。为了达到这种目的,咱们须要在 data 时间中,加上相应的钩子才行。
这里,咱们借用 Now 直播的 RTMP 流来进行相关的 RTMP 直播讲解。
Buffer 操做
Server 的搭建其实上网搜一搜,应该均可以搜索出来。关键点在于,如何针对 RTMP 的实操握手进行 encode/decode。因此,这里,咱们针对上述操做,来主要讲解一下。
咱们主要的工做量在于如何构造出 C0/1/2。根据上面格式的描述,你们应该能够清楚的知道 C0/1/2 里面的格式分别有啥。
好比,C1 中的 time 和 random,其实并非必须字段,因此,为了简单起见,咱们能够默认设为 0。具体代码以下:
class C {
constructor() {
this.time;
this.random;
}
C0() {
let buf = Buffer.alloc(1);
buf[0] = 3;
return buf;
}
C1() {
let buf = Buffer.alloc(1536);
return buf;
}
/**
* write C2 package
* @param {Number} time the 4B Number of time
* @param {Buffer} random 1528 byte
*/
produceC2(){
let buf = Buffer.alloc(1536);
// leave empty value as origin time
buf.writeUInt32BE(this.time, 4);
this.random.copy(buf,8,0,1528);
return buf;
}
get getC01(){
return Buffer.concat([this.C0(),this.C1()]);
}
get C2(){
return this.produceC2();
}
}复制代码
接下来,咱们来看一下,结合 server 完成的 RTMP 客户端服务。
const Client = new net.Socket();
const RTMP_C = new C();
Client.connect({
port: 1935,
host: "6721.liveplay.myqcloud.com"
}, () => {
console.log('connected')
Client.write(RTMP_C.getC01);
});
Client.on('data',res=>{
if(!res){
console.warn('received empty Buffer ' + res);
return;
}
// start to decode res package
if(!RTMP_C.S0 && res.length>0){
RTMP_C.S0 = res.readUInt8(0);
res = res.slice(1);
}
if(!RTMP_C.S1 && res.length>=1536){
RTMP_C.time = res.readUInt32BE(0);
RTMP_C.random = res.slice(8,1536);
RTMP_C.S1 = true;
res = res.slice(1536);
console.log('send C2');
Client.write(RTMP_C.C2);
}
if(!RTMP_C.S2 && res.length >= 1536){
RTMP_C.S2 = true;
res = res.slice(1536);
}
})复制代码
RTMP 基本架构
RTMP 整个内容,除了握手,其实剩下的就是一些列围绕 type id 的 message。为了让你们更清楚的看到整个架构,这里简单陈列了一份框架:
在 Message 下的 3 个一级子 Item 就是咱们如今将要大体讲解的内容。
能够看到上面全部的 item 都有一个共同的父 Item–Message。它的基本结构为:
下面,咱们先了解一下 Header 和不一样 typeID 的内容:
Header
RTMP 中的 Header 分为 Basic Header 和 Message Header。须要注意,他们二者并非独立的,而是相互联系。Message Header 的结构由 Basic Header 的内容来决定。
Basic Header
BH(基础头部)主要是定义了该 chunk stream ID 和 chunk type。须要注意的是,BH 是变长度的,即,它的长度范围是 1-3B。怎么讲呢?就是根据不一样的 chunk stream ID 来决定具体的长度。CS ID(Chunk Stream ID)自己的支持的范围为 <= 65597 ,差很少为 22bit。固然,为了节省这 3B 的内容。 Adobe 搞了一个比较绕的理论,即,经过以下格式中的 CS ID 来肯定:
0 1 2 3 4 5 6 7
+-+-+-+-+-+-+-+-+
|fmt| cs id |
+-+-+-+-+-+-+-+-+复制代码
即,经过 2-7 bit 位来肯定整个 BH 的长度。怎么肯定呢?
RTMP 规定,CS ID 的 0,1,2 为保留字,你在设置 CS ID 的时候只能从 3 开始。
0 1
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|fmt| 0 | cs id - 64 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+复制代码
注意上面的 cs id - 64。这个表明的就是,你经过切割第二个 byte 时,是将获得的值加上 64。即:2th byte + 64 = CS ID
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|fmt| 1 | cs id - 64 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+复制代码
固然,后面 CS ID 的计算方法也是最后的结果加上 64。
0 1 2 3 4 5 6 7
+-+-+-+-+-+-+-+-+
|fmt| cs id |
+-+-+-+-+-+-+-+-+复制代码
最后强调一下,由于 RTMP 规定,CS ID 的 0,1,2 为保留字,因此,0,1,2 不做为 CS ID。综上所述,CS ID 的起始位为 3(并不表明它是 3 个 Stream)。
上面我并无提到 fmt 字段,这实际上是用来定义 Message Header 的。
Message Header
根据前面 BH 中 fmt 字段的定义,能够分为 4 种 MH(Message Header)。或者说,就是一种 MH 格式会存在从繁到简 4 种:
当 fmt 为 0 时,MH 的长度为 11B。该类型的 MH 必需要流的开头部分,这包括当进行快退或者点播时从新获取的流。该结构的总体格式以下:
也就是说,当 fmt 为 0 时,其格式是一个完整的 MH。
-
timestamp 是绝对时间戳。用来表明当前流编码。
-
message length: 3B, 发送 message 的长度。
-
type id: 1B
-
stream id: 4B, 发送 message stream id 的值。是 little-endian 写入格式!
当 fmt 为 1 时,MH 的长度为 7B。该类型的 MH 不带 msg stream id。msg stream id 由前面一个 package 决定。该数值主要由前一个 fmt 为 0 的 MH 决定。该类型的 MH 一般放在 fmt 为 0 以后。
当 fmt 为 2 时,MH 的长度为 3B。该类型的 MH 只包括一个 timestamp delta 字段。其它的信息都是依照前面一个其余类型 MH 决定的。
当 fmt 为 3时,这其实 RTMP 里面就没有了 MH。官方定义,该类型主要所有都是 payload 的 chunk,其 Header 信息和第一个非 type:3 的头一致。由于这主要用于 chunk 中,这些 chunk 都是从一个包里面切割出来的,因此除了第一个 chunk 外,其它的 chunk 均可以采用这种格式。当 fmt 为 3时,计算它的 timestamp 须要注意几点,若是前面一个 chunk 里面存在 timestrameDelta,那么计算 fmt 为 3 的 chunk 时,就直接相加,若是没有,则是使用前一个 chunk 的 timestamp 来进行相加,用代码表示为:
prevChunk.timeStamp += prevChunk.timeStampDelta || prevChunk.timeStamp;复制代码
不过,当 fmt: 3 的状况通常很难遇到。由于,他要求前面几个包必须存在 fmt 为 0/1/2 的状况。
Message Body
上面说的主要是 Message Header 的公用部分,可是,对于具体的 RTMP Message 来讲,里面的 type 会针对不一样的业务场景有不一样的格式。Message 所有内容如上图所示:
这里,咱们根据流程图的一级子 item 来展开讲解。
PCM
PCM 全称为:Protocol Control Messages(协议控制消息)。主要使用来沟通 RTMP 初始状态的相关链接信息,好比,windows size,chunk size 等。
PCM 中一共有 5 种不一样的 Message 类型,是根据 Header 中的 type ID 决定的,范围是 1~6 (不包括 4)。另外,PCM 在构造的时候须要注意,它 Heaer 中的 message stream id 和 chunk stream id 须要设置为固定值:
-
message stream ID 为 0
-
chunk stream ID 为 2
Set Chunk Size(1)
看名字你们应该都能猜到这类信息是用来干啥的。该类型的 PCM 就是用来设置 server 和 client 之间正式传输信息的 chunk 的大小,type ID 为 1。那这有啥用呢?
SCS(Set Chunk Size) 是针对正式发送数据而进行数据大小的发送限制。通常默认为 128B。不过,若是 server 以为过小了,想发送更大的包给你,好比 132B,那么 server 就须要给你发送一个 SCS,告知你,接下来“我发送给你的数据大小是 132B”。
Abort Message(2)
该类 PCM 是用来告诉 client,丢弃指定的 stream 中,已经加载到一半或者还未加载完成的 Chunk Message。它须要指定一个 chunk stream ID。
Acknowledgement(3)
该协议信息其实就是一个 ACK 包,在实际使用是并无用到,它主要是用来做为一个 ACK 包,来表示两次 ACK 间,接收端所能接收的最大字节数。
Window Acknowledgement Size(5)
这是用来协商发送包的大小的。这个和上面的 chunk size 不一样,这里主要针对的是客户端可接受的最大数据包的值,而 chunk size 是指每次发送的包的大小。也能够叫作 window size。通常电脑设置的大小都是 500000B。
Set Peer Bandwidth(6)
这是 PCM 中,最后一个包。他作的工做主要是根据网速来改变发送包的大小。它的格式和 WAS 相似,不事后面带上了一个 Type 用来标明当前带宽限制算法。当一方接收到该信息后,若是设置的 window size 和前面的 WAS 不一致,须要返回一个 WAS 来进行显示改变。
-
0: Hard,表示当前带宽须要和当前设置的 window size 匹配
-
1: Soft,将当前宽带设置为该信息定义的 window size,或者已经生效的 window size。主要取决于谁的 window size 更小
-
2: Dynamic,若是前一个 Limit Type 为 Hard 那么,继续使用 Hard 为基准,不然忽略该次协议信息。
UCM
全称为:User Control Message(用户控制信息)。它的 Type ID 只能为 4。它主要是发送一些对视频的控制信息。其发送的条件也有必定的限制:
-
msg stream ID 为 0
-
chunk stream ID 为 2
UCM 根据 Event Type 的不一样,对流进行不一样的设置。它的 Event Type 一共有 6 种格式 Stream Begin(0),Stream EOF(1),StreamDry(2),SetBuffer Length(3),StreamIs Recorded(4),PingRequest(6),PingResponse(7)。
这里,根据重要性划分,只介绍 Begin,EOF,SetBuffer Length 这 3 种。
-
Stream EOF: Event Type 为 1。它经常出如今,当音视频流已经所有传输完时。 Event Data 为 4B,用来表示已经发送完音视频流的 Stream ID(实际没啥用)。
-
Set Buffer Length: Event Type 为 3。它主要是为了通知服务端,每毫秒用来接收流中 Buffer 的大小。Event Data 的前 4B 表示 stream ID,后面 4B 表示每毫秒 Buffer 的大小。一般为 3000ms
OK 剩下就是 Command Msg 里面的内容了。
Command Msg
Command Msg 里面的内容,其 type id 涵盖了 8~22 之间的值。具体内容,能够参考下表:
须要注意,为何有些选项里面有两个 id,这主要和 AMF 版本选择有关。第一个 ID 表示 AMF0 的编解码方式,第二个 ID 表示 AMF3 的编解码方式。 其中比较重要的是 command Msg,video,audio 这 3 个 Msg。为了让你们更好的理解 RTMP 流的解析,这里,先讲解一下 video 和 audio 两个 Msg。
Video Msg
由于 RTMP 是 Adobe 开发的。理所固然,内部的使用格式确定是 FLV 格式。不过,这和没说同样。由于,FLV 格式内部有不少的 tag 和相关的描述信息。那么,RTMP 是怎么解决的呢?是直接传一整个 FLV 文件,还自定义协议来分段传输 FLV Tag 呢?
这个其实很好回答,由于 RTMP 协议是一个长链接,若是是传整个 FLV 文件,根本不必用到这个,并且,RTMP 最经常使用在直播当中。直播中的视频都是分段播放的。综上所述,RTMP 是根据本身的自定义协议来分段传输 FLV Tag 的。那具体的协议是啥呢?
这个在 RTMP 官方文档中其实也没有给出。它只是告诉咱们 Video Msg 的 type ID 是 9 而已。
由于,RTMP 只是一个传输工具,里面传什么仍是由具体的流生成框架来决定的。因此,这里,我选择了一个很是具备表明性的 RTMP 直播流来进行讲解。
经过 wireshark 抓包,能够捕获到如下的 RTMP 包数据:
这里须要说起一点,由于 RTMP 是主动将 Video 和 Audio 分开传输,因此,它须要交叉发布 Video 和 Audio,以保证音视频的同步。那么具体每一个 Video Data 里面的数据都是同样的吗?
若是看 Tag 的话,他们传输的都是 VideoData Tag。先看一下 FLV VideoData Tag 的内容:
这是 FLV Video 的协议格式。但,遇到第一个字段 FrameType 的时候,咱们就可能懵逼了,这 TM 有 5 种状况,难道 RTMP 会给你 5 种不一样的包吗?
答案是,有可能,可是,很大状况下,咱们只须要支持 1/2 便可。由于,视频中最重要的是 I 帧,它对应的 FrameType 就是 1。而 B/P 则是剩下的 2。咱们只要针对 1/2 进行软解,便可实现视频全部信息的获取。
因此,在 RTMP 中,也主要(或者大部分)都是传输上面两种 FrameType。咱们经过实际抓包来说解一下。
这是 KeyFrame 的包,注意 Buffer 开头的 17 数字。你们能够找到上面的 FrameType 对应找一找,看结果是否是一致的:
这是 Inter-frame 的包。同上,你们也能够对比一下:
Audio Tag
Aduio Tag 也是和 Video Tag 同样的蜜汁数据。经过观察 FLV Audio Tag 的内容:
上面这些字段全是相关的配置值,换句话说,你必须实现知道这些值才行。这里,RTMP 发送 Audio Tag 和 Video Tag 有点不一样。由于 Audio Tag 已经不可能再细分为 Config Tag,因此,RTMP 会直接传递 上面的 audio Tag 内容。详细能够参考抓包内容:
由于 Audio 和 Video 是分开发送的。因此,在后期进行拼接的时候,须要注意二者的同步。说道这里,顺便补充一下,音视频同步的相关知识点。
音视频同步
主要过程变量参考就是 timeStamp 和 duration。由于,这里主要是作直播的,推荐你们采用第二种方法,以 Video 为准。由于,在实际开发中,会遇到 MP4 文件生成时,必需要求第一帧为 keyframe,这就形成了,以 Audio 为参考的,会遇到两个变量的问题。一个是 timeStamp 一个是 keyframe。固然,解决办法也是有的,就是检查最后一个拼接的 Buffer 是否是 Keyframe,而后判断是否移到下一次同步处理。
这里,我简单的说一下,以 Video 为准的同步方法。以 Video 同步,不须要管第一帧是否是 keyframe,也不须要关心 Audio 里面的数据,由于,Audio 数据是很是简单的 AAC 数据。下面咱们经过伪代码来讲明一下:
// known condition
video.timeStamp && video.perDuration && video.wholeDuration
audio.timeStamp && audio.perDuration
// start
refDuration = video.timeStamp + video.wholeDuration
delta = refDuration - audio.timeStamp
audioCount = Math.round(delta/audio.perDuration);
audDemuxArr = this._tmpArr.splice(0,audioCount);
// begin to demux
this._remuxVideo(vidDemuxArr);
this._remuxAudio(audDemuxArr);复制代码
上面算法能够避免判断 Aduio 和 Video timeStamp 的比较,保证 Video 一直在 Audio 前面并相差不远。下面,咱们回到 RTMP 内容。来看看 Command Msg 里面的内容。
Command Msg
Command Msg 是 RTMP 里面的一个主要信息传递工具。经常用在 RTMP 前期和后期处理。Command Msg 是经过 AMF 的格式进行传输的(其实就是相似 JSON 的二进制编码规则)。Command Msg 主要分为 net connect 和 net stream 两大块。它的交流方式是双向的,即,你发送一次 net connect 或者 stream 以后,另一端都必须返回一个 _result 或者 _error 以表示收到信息。详细结构能够参考下图:
里面的 _result 和 _error 会穿插在每一个包中进行讲解。
NetConnection
netConnection 能够分为 4 种 Msg,connect,call,createStream,close。
connect 是客户端向 Server 端发送播放请求的。里面的字段内容有:
-
Command Name[String]: 默认为 connect。表示信息名称
-
Transaction ID[Number]: 默认为 1。
-
Command Object: 键值对的形式存放相关信息。
-
Optional: 可选值。通常没有
那,Command Object 里面又能够存放些什么内容呢?
-
app[String]: 服务端链接应用的名字。这个主要根据你的 RTMP 服务器设定来设置。好比:live。
-
flashver[String]: Flash Player 的版本号。通常根据本身设备上的型号来肯定便可。也能够设置为默认值:LNX 9,0,124,2。
-
tcUrl[String]: 服务端的 URL 地址。简单来讲,就是 protocol://host/path。好比:rtmp://6521.liveplay.myqcloud.com/live。
-
fpad[Boolean]: 表示是否使用代理。通常为 false。
-
audioCodecs[Number]: 客户端支持的音频解码。后续会介绍。默承认以设置为 4071
-
videoCodecs[Number]: 客户端支持的视频解码。有自定义的标准。默承认以设置为 252
-
videoFunction[Number]: 代表在服务端上调用那种特别的视频函数。默承认以设置为 1
简单来讲,Command Object 就是起到 RTMP Route 的做用。用来请求特定的资源路径。实际数据,能够参考抓包结果:
上面具体的取值主要是根据 rtmp 官方文档来决定。若是懒得查,能够直接使用上面的取值。上面的内容是兼容性比较高的值。当该包成功发送时,另一端须要获得一个返回包来响应,具体格式为:
-
Command Name[String]: 为 _result 或者 _error。
-
Transaction ID[Number]: 默认为 1。
-
Command Object: 键值对的形式存放相关信息。
-
Information[Object]: 键值对的形式,来描述相关的 response 信息。里面存在的字段有:level,code,description
connect 包发送的位置,主要是在 RTMP 握手结束以后。以下:
call 包主要做用是用来远程执行接收端的程序(RPC, remote procedure calls)。不过,在我解 RTMP 的过程当中,并无实际用到过。这里简单介绍一下格式。它的内容和 connect 相似:
-
Procedure Name[String]: 调用处理程序的名字。
-
Transaction ID[Number]: 若是想要有返回,则咱们须要制定一个 id。不然为 0。
-
Command Object: 键值对的形式存放相关信息。AMF0/3
-
Optional: 可选值。通常没有
Command Object 里面的内容主要是针对程序,设置相关的调用参数。由于内容不固定,这里就不介绍了。
call 通常是须要有 response 来代表,远端程序是否执行,以及是否执行成功等。返回的格式为:
-
Command Name[String]: 根据 call 中 Command Object 参数来决定的。
-
Transaction ID[Number]: 若是想要有返回,则咱们须要制定一个 id。不然为 0。
-
Command Object: 键值对的形式存放相关信息。AMF0/3
-
Response[Object]: 响应的结果值
createStream 包只是用来告诉服务端,咱们如今要建立一个 channel 开始进行流的交流了。格式和内容都不复杂:
-
Procedure Name[String]: 调用处理程序的名字。
-
Transaction ID[Number]: 本身制定一个。通常能够设为 2
-
Command Object: 键值对的形式存放相关信息。AMF0/3
当成功后,服务端会返回一个 _result 或者 _error 包来讲明接收成功,详细内容为:
-
Command Name[String]: 根据 call 中 Command Object 参数来决定的。
-
Transaction ID[Number]: 若是想要有返回,则咱们须要制定一个 id。不然为 0。
-
Command Object: 键值对的形式存放相关信息。AMF0/3。通常为 Null
-
Stream ID: 返回的 stream ID 值。
下面,咱们来看一下 RTMP 中第二个比较重要的 command msg – netStream msg。
NetStream Msg
NetStream 里面的 Msg 有不少,但在直播流中,比较重要的只有 play 包。因此,这里咱们着重介绍一下 play 包。
play 包主要是用来告诉 Server 正式播放音视频流。并且,因为 RTMP 自然是作多流分发的。若是遇到网络出现相应的波动,客户端能够根据网络条件屡次调用 play 命令,来切换不一样模式的流。
-
Command Name[String]: 根据 call 中 Command Object 参数来决定的。
-
Transaction ID[Number]: 默认为 0。也能够设置为其余值
-
Command Object: 不须要该字段,在该命令中,默认设为 Null
-
Stream Name[String]: 用来指定播放的视频流文件。由于,RTMP 天生是支持 FLV 的,因此针对 FLV 文件来讲,并不须要加额外的标识,只须要写明文件名便可。好比:
StreamName: '6721_75994f92ce868a0cd3cc84600a97f75c'复制代码
-
不过,若是想要支持其它的文件,那么则须要额外的表示。固然,音频和视频须要不一样的支持:
-
若是是播放音频文件,好比 mp3,那么则须要额外的前缀标识符-mp3。例如:mp3:6721_75994f9。
-
若是涉及到视频文件的话,不只须要前缀,还须要后缀。好比播放的是 MP4 文件,则标识为:mp4:6721_75994f9.mp4。
-
start[Number][seconds]: 这个字段其实有点意思。它能够分为 3 类来说解:-2,-1,>=0。
-
-2: 若是是该标识符,服务端会首先寻找是否有对应的 liveStream。没有的话,就找 record_stream。若是尚未的,此次请求会暂时挂起,直到获取到下一次 live_stream。
-
-1: 只有 live_stream 才会播放。
-
=0: 至关于就是 seek video。它会直接找到 record_stream,而且根据该字段的值来肯定播放开始时间。若是没有的话,则播放 list 中的下一个 video。
-
duration[Number][seconds]: 用来设置播放时长的。它里面也有几个参数须要讲解一下,-1,0,>0。
-
-1: 会一直播放到 live_stream 或者 record_stream 结束。
-
0: 会播放一段一段的 frame。通常不用。
-
0: 会直接播放指定 duration 以内的流。若是超出,则会播放指定时间段内容的 record_stream。
整个 play 包内容就已经介绍完了。咱们能够看看实际的 play 抓包结果:
那 play 包是在那个环节发送,发送完以后需不须要对应的 _result 包呢?
play 包比较特殊,它是不须要 _result 回包的。由于,一旦 play 包成功接收后。server 端会直接开始进行 streamBegin 的操做。
到这里,后续就能够开始正式接收 video 和 audio 的 stream。
了解
网易云信,来自网易核心架构的通讯与视频云服务。
网易云信(NeteaseYunXin)是集网易18年IM以及音视频技术打造的PaaS服务产品,来自网易核心技术架构的通讯与视频云服务,稳定易用且功能全面,致力于提供全球领先的技术能力和场景化解决方案。开发者经过集成客户端SDK和云端OPEN API,便可快速实现包含IM、音视频通话、直播、点播、互动白板、短信等功能。