1.概要
在Janus的众多插件中,有一个房间插件videoRoom,由于它实现视频会议sfu功能,通过改造能适合我们很多业务场景。而且在Janus众多的业务插件中VideoRoom
应该也是最复杂的一个,如果你们撑握了它,就轻而易举了解其他插件。
janus 提供了很多管理房间的api接口,包括房间的创建,修改,删除,转推等操作。学习这些api有助于了解janus房间管理,同时也可以学习房间管理需要提供哪些标准的api接口。
1.VideoRoom插件
VideoRoom
是Janus的一个插件,实现了一个SFU(Selective Forwarding Unit)型的音视频会议。如果你从数据转发的角度看,也可以把它认为是一个音视频路由器
。
VideoRoom
实现的音视频会议是基于发布/订阅
模式。每个参与方
都可以发布自己的实时音视频流,因此它可以实现几种不同的场景,比如泛娱乐化直播或多人的实时互动产品(如音视频会议、在线教育小班课等)。m/n 76
主handle是发布handle。考虑到此插件允许一个参与方
可以打开多个WebRTC PeerConnection
(如每个参与方
可以有1个用于推流的PeerConnection
和N个拉流的PeerConnection
),所以每个参与方
需要为订阅不同的流attach
到VideoRoom
插件几次(每attach
一次就会生成一个Handle
,每个Handle
就是一个上下文)。因此,对于每个参与方
至少要有一个Handle
用于管理与插件的关系(如加入一个房间,离开一个房间,静音/取消静音,发布,接收事件)。
每当参与方
需要订阅另一个参与方发布的音视频流时,它需要创建一个新的Handle
。新创建的Handle
在逻辑上属于“slave”Handle
,它不能像“master”Handle
一样可以做取消房间静音这样的操作。因此,从Handle
唯一目的是提供一个上下文,在该上下文中创建一个recvonly
类型的PeerConnection
来订阅发布者的音视频流。
通过上面的描述我们可以知道,主Handle用于管理,而从Handle用于订阅音视频流。
注意,现在WebRTC
已经实现了SSRC复用(Unified Plan),这意味着你可以使用相同的Janus Handle
和PeerConnection
同时接收多路音视频流。
2.房间参数
VideoRoom插件功能非常强大,也很灵活,它有很多的配置项,你可以通过conf/janus.plugin.videoroom.jcfg
来修改它们。当然Janus也支持动态API修改配置,如通过API创建房间等。
2.1 创建房间
要增加更多房间或修改现有房间信息,你可以向Janus发送下面格式的请求:
room- <唯一的房间ID>:{
description = 房间的描述信息
is_private = true | false(是否是私有房间? 如果创建的是私有房间,则无法通过list指令进行查看)
secret = <可选项,操作房间所需的密码,如果设置了,则像做销毁房间这样的操作时你要带上它才行>
PIN = <可选项,加入会议房间的密码>
require_pvtid = true | false(是否订阅音视频流时,需要提供一个与发布者相关的有效private_id, 默认为false)
publishers = <房间内发布者的最大数>(例如,一个视频会议可以有6个发布者,而广播只有一个,默认= 3)
bitrate = <房间里发布者发送数据的最大比特率>(例如128000)
fir_freq = <向发布者发送FIR指令的频率>(0 =禁用)
audiocodec = opus | g722 | pcmu | pcma | isac32 | isac16(发布者可以使用的音频编解码器列表,默认为opus。编码器按优先顺序以逗号分隔)
videocodec = vp8 | vp9 | h264 | av1 | h265(发布者可以使用的视频编解码器列表,默认为vp8。可以按优先级顺序用逗号分隔,例如,vp9,vp8,h264)
vp9_profile = VP9首选的profile("2" 表示 "profile-id = 2" )
h264_profile = H.264首选的profile("42e01f" 表示 "profile-level-id = 42e01f" )
opus_fec = true | false(是否使用带内FEC;仅适用于Opus,默认为false)
video_svc = true | false(是否启用SVC支持;仅适用于VP9,默认为false)
audiolevel_ext = true | false(对于发布者是否使用RTP扩展ssrc-audio-level?默认为 true)
audiolevel_event = true | false(是否将audiolevel事件发送给其他用户)
audio_active_packets = 100(音频保活包个数,默认值= 100,2秒)
audio_level_average = 25(音频音量级别的平均值,127 =静音,0 ='太大声',默认= 25)
videoorient_ext = true | false(发布者是否使用RTP扩展video-orientation? 默认= true)
playoutdelay_ext = true | false(发布者是否使用RTP扩展playout-delay? 默认= true)
transport_wide_cc_ext = true | false(发布者是否使用RTP扩展 transport-wide-cc? 默认= true)
record = true | false(该房间是否启录制?默认= false)
rec_dir = <启用录制后,录制文件存放的目录>
lock_record = true | false(是否锁定录制状态? 默认= false)
notify_joining = true | false(可选,当有新的参与方加入房音后,是否通知房间里的所有参与者?
Videoroom插件默认仅通知发布者,启用此功能可能会导致额外的通知传输。
该功能与require_pvtid一起启用时,对管理员管理仅收听的参与者特别有用。默认= false)
require_e2ee = true | false(是否启用端到端加密? 默认= false)
}
3. Video Room API接口
VideoRoom
插件支持很多API操作。这些API中,一些是同步请求,一些则是异步请求。但无论是同步还是异步请求,当遇到无效的JSON格式或无效的请求时,都使用同步进行错误响应。
接下来,我们首先看看都有那些同步请求API。create
,destroy
,edit
,exists
,list
,allowed
,kick
和listparticipants
是同步请求API。create
允许您动态创建一个新的音视频房间;edit
允许您动态编辑房间的属性(例如 修改PIN码);destroy
首先释放视频资源,然后踢除房间里的所有用户,最后销毁音视频房间;exists
检查指定的音视频房间是否存在;list
列出所有有效的音视频房间; listparticipants
列出指定房间中所有激活的参与者及其详细信息。
异步请求API有:join
,joinandconfigure
,configure
,publish
,unpublish
,start
,pause
,switch
和leave
。join
允许你加入指定的音视频房间;configure
可用于修改某些属性(例如,比特率范围);joinandconfigure
的含义是将前两个请求合并为一个请求(该请求仅适用于发布者);publish
发布媒体流给所有订阅者; unpublish
正好与publish
相反;start
允许你开始接收订阅的媒体流;pause
暂停发送媒体流;switch
更改指定PeerConnection
的媒体源(例如,你正在看A,现在改为看B),但无需为此创建新的Handle;leave
离开视频房间。
下面咱们对上面提到的API做一下详细分析,首先看一下create
API,它用于创建新的音视频房间,其格式如下:
{
"request":"create",
"room":<可选,房间ID。如果不填,则由插件随机生成>,
"permanent":<true | false,是否创建永久房间,默认= false>,
"description":"<可选,房间的名称>",
"secret":"<可选,编辑/销毁房间时用的密码>",
"pin":"<可选,加入房间的密码>",
"is_private":<true | false,是否是私有房间?如果是私有房间则不会出现在房间列表中>,
"allowed":[可选,用户加入房间的token数组],
...
}
上面的说明已经非常清楚了,这里我就不做简赘述了。
如果create
成功,则会返回created
响应,格式如下:
{
"videoroom":“created",
"room":<房间ID>,
"permanent":<是否是创建的永久房间?是则为true,否则为false>
}
注意,如果你请求创建一个永久房间,但
permanet
返回的是false,很可能是因为权限的问题导致的。
如果create
请求失败,则返回错误信息,格式如下:
{
"videoroom":"event",
"error_code":<错误码,每个错误码的含义需要看插件实现代码中的宏定义>,
"error":"<错误描述字符串>"
}
这里需要注意的是,所有请求的错误响应格式都与上面一样。
默认情况下,所有用户都可以创建房间,但你可以通过在VideoRoom
插件的配置文件中增加admin_key
项来限制此功能。此时,只有带了正确的admin_key
值的create
请求才能成功创建房间。你也可以选择将此功能扩展到RTP转发,只转发受信任的客户端的RTP包。
房间创建好后,您可以用edit
API编辑其中的部分(但不是全部)属性。edit
允许你修改房间描述,密码,PIN码以及是否为私有。但你将无法修改他的静态属性,例如房间ID,采样率,与扩展相关的内容等。如果你有兴趣更改ACL,还需要查看allowed
是否允许。
一个edit
请求格式如下:
{
"request":"edit",
"room":<房间ID>,
"secret":"<房间密码>",
"new_description":"<房间的新名称,可选>",
"new_secret":"<房间的新密码,可选>",
"new_pin":"<新PIN码,可选>",
"new_is_private":<true | false,房间是否为私有房间?>,
"new_require_pvtid":<true | false,房间是否要求订阅者提供private_id>,
"new_bitrate":<比特率>,
"new_fir_freq":<发送PLI请求关键帧的时间间隔>,
"new_publishers":<房间里发布者的最大数>,
"new_lock_record":<true | false,如否可以改变录制状态>,
"permanent":<true | false,该房间是否是永久房间?默认= false>
}
edit
请求成功,刚收到edited
响应:
{
"videoroom":"edited",
"room":<房间ID>
}
接下来我们来看看destroy
API,无论你是通过动态创建的还是静态创建的房间,均可使用destroy
销毁它,其格式如下:
{
"request":"destroy",
"room":<房间ID>,
"secret":"<房间密码>",
"permanent":<true | false,是否是永久房间,默认= false>
}
成功销毁房间后将收到destroyed
响应,其格式如下:
{
"videoroom":"destroyed",
"room":<房间ID>
}
销毁房间后,在房间内的所有参与者都会收到destroyed
事件,如下所示:
{
"videoroom":"destroyed",
"room":<房间ID>
}
Janus中还提供了exists
API,来检查房间是否存在,该请求的格式如下:
{
"request":"exists",
"room":<房间ID>
}
请求成功将收到success响应:
{
"videoroom":“success",
"room":<房间ID>,
“exists":<true | false 房间是否存在>
}
allowed
API可以打开/关闭对令牌的检测,它还可以增加/删除允许的用户,其请求格式如下:
{
"request":"allowed",
"secret":"<房间密码,如果已配置,则是必需的>",
"action":"enable | disable | add | remove",
"room":<房间ID>,
"allowed":[
//字符串数组
]
}
成功请求将返回success响应:
{
"videoroom":“success",
"room":<房间ID>,
“allowed":[
//更新后完整的令牌列表
]
}
如果你是房间管理员(即你创建了该房间并可以加密访问),则你可以使用kick
API踢除房间内的用户。
注意,这只会将用户踢出房间,但并不能阻止他们重新加入。要禁止他们加入,你需要先从授权用户列表中删除他们(请参阅allowed请求),然后再将其踢掉。
kick
请求的格式如下:
{
"request":"kick",
"secret":"<房间密码>",
"room":<房间ID>,
"id":<被踢用户ID>
}
请求成功将收到success响应:
{
"videoroom":"success",
}
你还可以通过list
API获取可用房间的列表(不包括配置或创建为私有的房间),其格式如下:
{
"request":“list"
}
请求成功将返回success响应,响应中会带有有效的房间列表:
{
"videoroom":“成功",
"rooms":[//房间对象数组
{// 第一个房间
"room":<房间ID>,
"description":"<房间名称>",
"pin_required":<true | false,是否需要输入PIN吗才能加入此房间>,
"max_publishers":<房间内发布者最大数量,>
"bitrate":<发布者使用的(通过REMB)比特率上限>,
"bitrate_cap":<true | false,上述上限是否可以动态更改?>,
"fir_freq":<发送PLI/FIR请求关键帧的时间间隔>,
"audiocodec":"<音频编解码器列表,每个编码器以逗号分隔>",
"videocodec":"<视频编解码器列表,每个编码器以逗号分隔>",
"record":<true | false,是否打开了录制功能>,
"record_dir":"<如果开启了录掉,.mjr文件保存的路径>",
"lock_record":<true | false,是否只能通过密码才能更改房间记录状态>,
"num_participants":<房间内参与人的个数>
},
//其他房间
]
}
当然,你要获取特定房间中的参与者列表,可以使用listparticipants
请求,其格式如下:
{
"request":"listparticipants",
"room":<房间ID>
}
请求成功将返回一个participants
响应:
{
"videoroom":"participants",
"room":<房间ID>,
“participants":[//参与者对象的数组
{//参与者#1
"id":<用户ID>,
"display":"<用户名;可选>",
"publisher":"<true | false,用户是否是房间的发布者>",
"talking":<true | false,用户是否可以说话(仅当使用音频级别时)>
},
//其他参与者
]
}
上面是Janus中的同步API。异步API都是与参与者
有关,即参与者
如何发布,订阅或管理他们正在发送或接收的媒体流。
4. VideoRoom 发布者
在VideoRoom中,发布者
是指那些能够在房间中发布媒体流
的参与者。
当你以发布者
的身份加入到房间里时,您应该发送join
请求,并且将ptype设置为publisher
。请求的具体格式如下:
{
"request":"join",
"ptype":"pbulisher",
"room":<房间ID>,
"id":<发布者ID;可选,如果缺少,将由插件选择>,
"display":"<发布者名称;可选>",
"token":"<邀请令牌,如果房间有ACL时需要该字段;可选>"
}
joid
请求成功将收到joined
事件,其中包含当前激活的发布者
列表,以及任选的参加者
列表。joined
事件格式如下:
{
"videoroom":"joined",
"room":<房间ID>,
"description":<房间名,如果有的话>,
"id":<用户ID>,
"private_id":<与参与者相关联的不同唯一ID;打算是私人的>,
“publishers":[
{
"id":<活动发布者#1的唯一ID>,
"display":"<发布者#1的名称,如果有的话>",
"audio_codec":"<发布者#1使用的音频编解码器,如果有的话>",
"video_codec":"<发布者#1使用的视频编解码器,如果有的话>",
"simulcast":"<如果发布者使用simulcast,则为true(仅VP8和H.264)>",
"talking":<true | false,发布者开启语音聊天(仅在使用音频级别的情况下)>,
},
//其他活跃的发布者
],
"attendees":[//仅当房间的notify_joining设置为TRUE时存在
{
"id":<与会者#1的唯一ID>,
"display":"<与会者#1的名称,如果有的话>"
},
//其他参加者
]
}
注意,如果房间中当前没有人,则发布者
列表为空。上面格式中的private_id
属性只有在用户订阅时才起作用。
对于房间里的订阅者来说,会收到event
通知。
{
"videoroom":"event",
"room":<房间ID>,
“joining":{
"id":<参与者ID>,
"display":"<参与者名称>"
}
}
如果你想成为发布者
,则发送publish
请求。该请求必须跟着一个JSEP SDP Offer
,用于协商新的PeerConnection
。插件会将其与房间配置进行匹配(例如,确保房间中使用协商的编解码器),并使用JSEP SDP answer
进行答复从而完成PeerConnection
的设置。建立PeerConnection
后,发布者立即处于活动状态,其他参与者就可以订阅它发布的流啦。
publish
请求格式如下:
{
"request":"publish",
"audio":<true | false,是否应该转发音频;默认为true>,
"video":<true | false,是否应该转发视频;默认为true>,
"data":<true | false,是否应该转发数据;默认为true>,
"audiocodec":"<在协商协议中首选的音频编解码器;可选>",
"videocodec":"<在协商协议中首选的视频编解码器;可选>",
"bitrate":<通过REMB返回的比特率上限;可选,如果存在则覆盖全局房间值>,
"record":<true | false,是否应该记录此发布者;可选>
"filename":"<录制文件名;可选>",
"display":"<用户名称;可选>",
"audio_level_average":"<音频音量平均值,此设置覆盖房间的audio_level_average;可选>",
"audio_active_packets":"<音频保活包数,此设置覆盖房间audio_active_packets;可选>
}
此请求应该与发布者的JSEP SDP Offer
一起提供,插件收到此消息后,将协商与之匹配的JSEP SDP Answer
。如果成功,configured
事件将被返回,其格式如下:
{
"videoroom":"event",
"configured":“ok"
}
该事件将与准备好的JSEP SDP Answer
一起发送给客户端。
你也可以用configure
请求代替publish
。两者的功能在发布
上是等效的,但从语义的角度来看,publish
是发布时要发送的正确消息。configure
请求也可以用于更新发布者会话的某些属性,在这种情况下,就不能用publish
请求了。
需要注意的是,如果用户已经发送过publish
了,再发送publish
将导致失败。
其实,您可以将join
和publish
两个API合并为一个API请求。比如你一开始以参与者
的身份加入,随后变为发布者
,这时你就可以将他们合并。你可以使用joinandconfigure
请求来做到这一点,该请求将这两个请求(join与publish)结合在一起。如果成功,则响应一个joined
事件,并且将JSEP SDP Answer
一起发送出去。
一旦PeerConnection
设置成功,且发布者处于激活状态,event
就会被发向房间中的所有参与者
。其格式如下所示:
{
"videoroom":"event",
"room":<房间ID>,
“publishes":[
{
"id":<新发布者的唯一ID>,
"display":"<新发布者的名称,如果有的话>",
"audio_codec":"<新布者使用的音频编解码器,如果有的话>",
"video_codec":"<新发布使用的视频编解码器,如果有的话>",
"simulcast":"<如果发布者使用simucast,则为true(仅VP8和H.264)>",
"talking":<true | false,发布者是否在讲话(仅在使用音频级别的情况下)>,
}
]
}
要停止发布并删除相关的PeerConnection
,可以使用该unpublish
请求:
{
"request":“unpublish"
}
当插件收到这条请求后,它会删除对应的PeerConnection
,并将发布者从活动列表中删除。如果成功,响应如下所示:
{
"videoroom":"event",
“unpublish":“ok"
}
当PeerConnection
删除后,插件还将向所有其他参与者
通知该流不再可用的消息:
{
"videoroom":"event",
"room":<房间ID>,
"unpublished":<发布者的ID>
}
注意,不光收到unpublish
消息会触发上面的事件通知,其实无论什么情况下,只要发布者
提供的流消失了(例如,句柄已关闭或用户失去连接),都会发同样的事件
。此外,你可以使用同一句柄的上下文多次执行发布
或取消发布
操作。
正如我们上面讲过的,你可以使用configure
请求调整发布者会话的某些属性。该请求的格式如下:
{
"request":"configure",
"audio":<true | false,取决于是否应该转发音频;默认为true>,
"video":<true | false,取决于是否应该转发视频;默认为true>,
"data":<true | false,取决于是否应该转发数据;默认为true>,
"bitrate":<比特率上限;可选,如果存在则覆盖全局房间值(除非设置了bitrate_cap)>,
"keyframe":<true | false,是否向发布者发送关键帧请求>,
"record":<true | false,是否开启录制;可选>
"filename":"<如果开启了录制,指明录制路径/文件;可选>",
"display":"<用户名称;可选>",
"audio_active_packets":"<音频保活包个数,audio_active_packets;可选>",
"audio_level_average":"<音频音量平均值,audio_level_average;可选>",
}
configure
基本上与publish
的属性相同。这就是为什么两个请求都可以用来开始发布的原因。如果configure
成功,则返回configured
事件,格式如下:
{
"videoroom":"event",
"configured":“ok"
}
当发送configure
请求RTP扩展ssrc-audio-level
时,如果audiolevel_event
设置为true ,则可能会向所有发布者发送一些临时事件。这些事件将具有以下格式:
{
"videoroom":<"talking"|"stopped-talking",是否发布者开始或停止发言>,
"room":<房间的唯一ID>,
"id":<发布者的唯一ID>,
"audio-level-dBov-avg":<音平音量的平均值,127 =静音,0 ='太大声'>
}
5. VideoRoom 级联
VideoRoom
插件的主要目的是从WebRTC源(发布者)获取媒体,并将其转发到WebRTC目的地(订阅者),但实际上存在几种方案,可以将媒体转发给外部(不一定与WebRTC兼容)组件。例如,用于媒体处理,外部录制,转码,级联等等。rtp_forward
顾名思义,就是将发布者发送的RTP包(普通或加密)实时转发到远程后端。
您可以使用rtp_forward
请求为现有发布者添加新的RTP转发器,其格式如下:
{
"request":"rtp_forward",
"room":<房间ID>,
"publisher_id":<发布者ID>,
"host":"<将RTP和数据包转发到的host主机IP地址>",
"host_family":"<ipv4 | ipv6,使用IPv4还是IPv6;默认情况下,无论我们得到什么>",
"audio_port":<音频RTP数据包转发到的端口>,
"audio_ssrc":<音频SSRC,用于流式传输;可选>
"audio_pt":<音频有效负载类型;可选>
"audio_rtcp_port":<接收方接收音频RTCP反馈端口;可选,当前未用于音频>,
"video_port":<将视频RTP数据包转发到的端口>,
"video_ssrc":<视频 SSRC;可选>
"video_pt":<视频有效载荷类型;可选>
"video_rtcp_port":<接收方接收视频RTCP反馈端口;可选>
"video_port_2":<如果simulcast,则视频第二个的RTP数据端口>,
"video_ssrc_2":<如果simulcast,则视频第二个的SSRC;可选>
"video_pt_2":<如果simulcast,则视频第二个的有效载荷类型;可选>
"video_port_3":<如果simulcast,则视频第三个RTP数据包端口>,
"video_ssrc_3":<如果simulcast,则视频第三个SSRC;可选>
"video_pt_3":<如果simulcast,则视频第三个的有效载荷类型;可选>
"data_port":<数据通道消息端口>,
"srtp_suite":<身份验证标签的长度(32或80);可选>
"srtp_crypto":"<用作加密的密钥(如SDES中的base64编码的密钥;可选>"
}
注意,如上所述,如果您配置了admin_key属性,则在请求中也需要提供它,否则未授权的请求将被拒绝。默认情况下,没有对rtp_forward进行限制。
如果请求成功则返回rtp_forward
响应,其中格式如下:
{
"videoroom":"rtp_forward",
"room":<房间ID>,
"publisher_id":<发布者ID>
"rtp_stream":{
"host":"<接收流的主机IP,如果未解析,则与请求相同>",
"audio":<音频RTP端口,与请求相同(如果已配置)>,
"audio_rtcp":<音频RTCP端口,与请求相同(如果已配置)>,
"audio_stream_id":<分配给音频RTP转发器的唯一数字ID,如果有的话,>
"video":<视频RTP端口,与请求相同(如果已配置)>,
"video_rtcp":<视频RTCP端口,如果配置,则与请求相同,>
"video_stream_id":<分配给主视频RTP转发器的唯一数字ID,如果有的话,>
"video_2":<第二个视频端口,与请求相同(如果已配置)>,
"video_stream_id_2":<分配给第二层视频RTP转发器的唯一数字ID,如果有的话,>
"video_3":<第三个视频端口,与请求相同(如果已配置)>,
"video_stream_id_3":<分配给第三个视频RTP转发器的唯一数字ID,如果有,>
"data":<数据端口,与请求相同(如果已配置)>,
"data_stream_id":<分配给数据通道消息转发器的唯一数字ID(如果有)>
}
}
要停止以前创建的RTP转发器,可以使用stop_rtp_forward
请求,其格式如下:
{
"request":"stop_rtp_forward",
"room":<房间ID>,
"publisher_id":<发布者ID>,
"stream_id":<RTP转发器ID>
}
请求成功,则返回stop_rtp_forward
响应:
{
"videoroom":"stop_rtp_forward",
"room":<房间ID>,
"publisher_id":<发布者ID,与请求相同,>
"stream_id":<流ID,与请求相同>
}
如果要获取特定房间中所有转发器的列表,可以使用listforwarders
请求,其格式如下:
{
"request":"listforwarders",
"room":<房间的唯一数字ID>,
"secret":"<房间密码;如果已配置,则是必需的>"
}
请求成功,则返回forwarders响应,其中包括RTP转发器列表:
{
"videoroom":"forwarders",
"room":<房间的唯一ID>,
"rtp_forwarders":[//具有RTP转发器的发布者数组
{//发布者#1
"publisher_id":<发布者#1的唯一数字ID>,
"rtp_forwarders":[// RTP转发器数组
{// RTP转发器#1
"audio_stream_id":<音频RTP转发器的唯一ID,如果有的话>,
"video_stream_id":<视频RTP转发器的唯一ID,如果有的话>,
"data_stream_id":<数据通道消息转发器的唯一ID(如果有)>,
"ip":"<接收端IP>",
"port":<接收端端口>,
"rtcp_port":<接收端RTCP端口,如果有的话>,
"ssrc":<转发器正在使用的SSRC,如果有的话>,
"pt":<转发器正在使用的有效负载类型>,
"substream":<视频子流,如果有>,
"srtp":<true | false,RTP流是否已加密>
},
//此发布者的其他转发器
],
},
//其他发布者
]
}
在会议进行期间启用或禁用录制,您可以使用enable_recording
请求,该请求的格式如下:
{
"request":"enable_recording",
"room":<房间ID>,
"secret":"<房间密码;如果已配置,则是必需的>"
"record":<true | false,是否自动记录此会议室中的参与者>,
}
注意,参与者通常也可以通过configure请求来更改自己的录制状态:这样做是为了获得最大的灵活性,您可能希望单独记录一些流,而不是全局或自动记录一些内容,到特定文件。就是说,如果你希望确保在启用全局录制后参与者不能停止其录制,或者在不应该录制该会议室的情况下启动它,那么您应该确保在创建会议室时使用lock_record属性,将其设置为true。这样,只有在提供了房间密码的情况下,才能更改录制状态,从而确保只有管理员才能执行此操作。
最后,您可以使用leave
请求离开会议室。如果您是会议室中的活动发布者,这也将隐式取消你的发布。该leave请求如下所示:
{
"request":"leave"
}
如果成功,响应将如下所示:
{
"videoroom":"event",
"leave":"ok"
}
其他参与者将收到”leave”事件,格式如下:
{
"videoroom":"event",
"room":<房间ID>,
"leave:<离开的参与者的唯一ID>
}
如果您是活跃的发布者,则其他用户也将收到相应的unpublish
事件,以通知他们该流不再可用。如果您只是潜伏而不是发布者,则其他参与者将仅收到”leave”事件。
VideoRoom 订阅者
订阅者在加入房间时,join请求的ptype属性应该设置为subscriber
,并指定要订阅的确切的媒体流。该请求的确切语法如下:
{
"request":"join",
"ptype":"subscriber",
"room":<房间ID>,
"feed":<发布者ID;强制性>,
"private_id":<发起此请求的用户ID;可选的,除非房间配置要求>
"close_pc":<true | false,发布者离开时是否应自动关闭PeerConnection;默认为true>,
"audio":<true | false,是否转发音频;默认为true>,
"video":<true | false,是否转发视频;默认为true>,
"data":<true | false,是否转发数据;默认为true>,
"offer_audio":<true | false; 是否应该协商音频;如果发布者的音频>,默认为true
"offer_video":<true | false; 是否应该协商视频;如果发布者的视频>,默认为true
"offer_data":<true | false; 是否应该协商数据通道;如果发布者的datachannels>为默认值,则为true
"substream":<启用了simulcast情况下,要接收的子流(0-2);可选>
"temporal":<启用simulcast情况下,要接收的时间层(0-2);可选>
"fallback":<多少时间(在我们这里,默认为250000)没有接收到数据包将使我们下降到下面的子流>,
"spatial_layer":<启用VP9-SVC时要接收的空间层(0-2);可选>
"temporal_layer":<启用VP9-SVC时要接收的时间层(0-2);可选>
}
如您所见,只要指定好要订阅的发布者ID,并在需要时指定好private_id
(订阅者ID),其它的都可以不设置。不过请求中的offer_audio
,offer_video
和offer_data
特别有意思,你可以通过它们订阅媒体的一个子集(音频\视频\数据)。
默认情况下,发送join
请求时会导致插件层创建SDP Offer
,用以协商发布者提供那些媒体。此外,如果发布者发布的是simulcast
或VP9 SVC
,那么你还可以订阅你感兴趣的子流,例如,获得最佳质量的中间质量。更有意思的是,你可以使用configure
请求随时动态更改这些设置。
上面的请求如果成功,将生成一个新的JSEP SDP Offer
,并伴随一个attached事件:
{
"videoroom":"attached",
"room":<房间ID>,
"feed":<发布者ID>,
"display":"<发布者的名称,如果有的话>"
}
在此阶段,为了完成PeerConnection
的设置,订阅者应将JSEP SDP Answer
发送回插件。此操作是通过start
请求来完成的,在这种情况下,请求必须与JSEP SDP Answer
相关联,但是不需要任何参数:
{
"request":“start"
}
如果成功,此请求将返回一个started事件:
{
"videoroom":"event",
"started":"ok"
}
完成此操作后,所需要做的就是等待WebRTC PeerConnection
建立成功。一旦PeerConnection
建立成功,Streaming插件就可以开始向订阅的观众转发媒体了。
注意,在需要重新协商(例如出于ICE重启目的)的情况下,您也可以使用我们刚经历的相同步骤(watch请求,然后插件创建JSEP Offer
,最后客户端发送start
请求和JSEP Answer
)。
作为订阅者
,您可以发送pause
临时暂停或发送start
恢复整个媒体的传送(在这种情况下,不附带任何JSEP SDP Answer)。因为上下文中已经有了相关信息,所以不需要重新进行协商。
{
"request":"pause"
}
{
"request":"start"
}
当然,它们会分别导致paused和started事件:
{
"videoroom":"event",
"paused":"ok"
}
{
"videoroom":"event",
"started":"ok"
}
configure
请求可以对订阅
做更多深入操作。该请求允许订阅者
动态更改与媒体订阅有关的某些属性,configure
请求的格式如下:
{
"request":"configure",
"audio":<true | false,是否应该转发音频;可选>
"video":<true | false,是否应该转发视频;可选>
"data":<true | false,是否转发数据;可选>
"substream":<启用simulcast情况下,要接收的子流(0-2);可选>
"temporal":<启用simulcast,要接收的时间层(0-2);可选>
"fallback":<多少时间(在我们这里,默认为250000)没有接收到数据包将使我们下降到下面的子流>,
"spatial_layer":<启用VP9-SVC时要接收的空间层(0-2);可选>
"temporal_layer":<启用VP9-SVC时要接收的时间层(0-2);可选>
"audio_level_average":"<如果提供,将覆盖此用户的房间audio_level_average;可选>",
"audio_active_packets":"<如果提供,将覆盖此用户的房间audio_active_packets;可选>
}
正如你所看到的audio,video和data属性可以用作媒体级的暂停/恢复功能,而pause与start只是简单地暂停/恢复所有数据流。
下面来说说switch
,switch 请求格式如下:
{
"request":"switch",
"feed":<要切换到的新发布者的唯一ID;强制性>,
"audio":<true | false,取决于是否应该中继音频;可选>
"video":<true | false,取决于是否应该中继视频;可选>
"data":<true | false,取决于是否应该中继数据通道消息;可选>
}
如果成功,您将退订之前的发布者,然后订阅新的发布者。确认切换成功的事件如下所示:
{
"videoroom":"event",
"switched":"ok",
"room":<房间ID>,
"id":<新发布者的唯一ID>
}
最后,要停止订阅并删除相关的PeerConnection,可以使用该leave请求。由于上下文是隐式的,因此不需要其他参数:
{
"request":"leave"
}
如果成功,该插件将尝试拆除PeerConnection,并发送回一个left事件:
{
"videoroom":"event",
"left":"ok",
}
小结
VideoRoom
插件是Janus的一个特别重要的插件,对于该插件的理解对于我们理解整个Janus有至关重要的意义。本文说细分析了VideoRoom
插件中所有的信令,大体上我们可以将它们人成两在类,一类是房间管理信令,另一类是用户信令。
这些信令设计的非常巧妙,对我们研发自己的SFU会议系统是一个很好的借鉴。
参考