房间配置文件
room-<unique room ID>: {
description = This is my awesome room
is_private = true|false (private rooms don't appear when you do a 'list' request)
secret = <optional password needed for manipulating (e.g. destroying) the room>
pin = <optional password needed for joining the room>
require_pvtid = true|false (whether subscriptions are required to provide a valid
a valid private_id to associate with a publisher, default=false)
publishers = <max number of concurrent senders> (e.g., 6 for a video
conference or 1 for a webinar, default=3)
bitrate = <max video bitrate for senders> (e.g., 128000)
fir_freq = <send a FIR to publishers every fir_freq seconds> (0=disable)
audiocodec = opus|g722|pcmu|pcma|isac32|isac16 (audio codec to force on publishers, default=opus
can be a comma separated list in order of preference, e.g., opus,pcmu)
videocodec = vp8|vp9|h264 (video codec to force on publishers, default=vp8
can be a comma separated list in order of preference, e.g., vp9,vp8,h264)
opus_fec = true|false (whether inband FEC must be negotiated; only works for Opus, default=false)
video_svc = true|false (whether SVC support must be enabled; only works for VP9, default=false)
audiolevel_ext = true|false (whether the ssrc-audio-level RTP extension must be
negotiated/used or not for new publishers, default=true)
audiolevel_event = true|false (whether to emit event to other users or not)
audio_active_packets = 100 (number of packets with audio level, default=100, 2 seconds)
audio_level_average = 25 (average value of audio level, 127=muted, 0='too loud', default=25)
videoorient_ext = true|false (whether the video-orientation RTP extension must be
negotiated/used or not for new publishers, default=true)
playoutdelay_ext = true|false (whether the playout-delay RTP extension must be
negotiated/used or not for new publishers, default=true)
transport_wide_cc_ext = true|false (whether the transport wide CC RTP extension must be
negotiated/used or not for new publishers, default=true)
record = true|false (whether this room should be recorded, default=false)
rec_dir = <folder where recordings should be stored, when enabled>
notify_joining = true|false (optional, whether to notify all participants when a new
participant joins the room. The Videoroom plugin by design only notifies
new feeds (publishers), and enabling this may result extra notification
traffic. This flag is particularly useful when enabled with \c require_pvtid
for admin to manage listening only participants. default=false)
}
API 分类
- 同步请求:直接在事务(
transacation
)的上下文里获取响应,即直接响应(transaction
绑定即可)- create:动态创建一个新的房间,作为使用配置文件的替代方法
- destroy:移除并销毁房间,同时剔除房间里的所有用户
- edit:动态修改一些房间属性
- exists:查询是否存在指定的房间
- list:列出所有可用的房间
- allowed:配置观众进入房间权限
- kick:管理员踢除用户操作
- listparticipants:列出所有指定房间中活跃(比如当前正在发布)的参与者和他们的详细信息
- 异步请求:在事件(
event
)通知中获取成功(success
)与失败(failure
)事件,先发一个ask后再发一个event
进行响应(必须使用handle_id
绑定)- join:加入指定的房间,并指定连接用于发布或观看音视频
- joinandconfigure:对于发布者,将前两个请求合成一个
- configure:修改一些参与者的设置,例如 bitrate cap
- publish:开始发送媒体并广播给其他参与者
- unpublish:停止发送媒体并广播给其他参与者
- start:开始接收来自发布者的媒体数据
- pause:停止传递媒体数据
- switch:修改媒体连接的来源
- leave:离开房间
Video Room
create - 创建房间
请求用 body
{
"request" : "create",
"room" : <unique numeric ID, optional, chosen by plugin if missing>,
"permanent" : <true|false, whether the room should be saved in the config file, default=false>,
"description" : "<pretty name of the room, optional>",
"secret" : "<password required to edit/destroy the room, optional>",
"pin" : "<password required to join the room, optional>",
"is_private" : <true|false, whether the room should appear in a list request>,
"allowed" : [ array of string tokens users can use to join this room, optional],
...
}
更多房间创建参数可参考房间配置文件——
conf/janus.plugin.videoroom.jcfg
成功响应 created
{
"videoroom" : "created",
"room" : <unique numeric ID>,
"permanent" : <true if saved to config file, false if not>
}
(通用)失败回调 event
{
"videoroom" : "event",
"error_code" : <numeric ID, check Macros below>,
"error" : "<error description as a string>"
}
Janus 中所有用户都可以创建房间,我们可以在插件中配置
admin_key
,只有在create
携带了正确的admin_key
才可以创建成功。
注意,您也可以选择将此功能扩展到RTP转发,以便只允许受信任的客户端使用此功能。
edit - 编辑房间
在房间被创建后,我们可以使用edit
编辑房间的属性(例如room description
、secret
、pin
、private or not
),但不能修改一些静态的属性(例如room ID
、sampling rate
)。
请求用 body
{
"request" : "edit",
"room" : <unique numeric ID of the room to edit>,
"secret" : "<room secret, mandatory if configured>",
"new_description" : "<new pretty name of the room, optional>",
"new_secret" : "<new password required to edit/destroy the room, optional>",
"new_pin" : "<new password required to join the room, optional>",
"new_is_private" : <true|false, whether the room should appear in a list request>,
"new_require_pvtid" : <true|false, whether the room should require private_id from subscribers>,
"new_bitrate" : <new bitrate cap to force on all publishers (except those with custom overrides)>,
"new_fir_freq" : <new period for regular PLI keyframe requests to publishers>,
"new_publishers" : <new cap on the number of concurrent active WebRTC publishers>,
"permanent" : <true|false, whether the room should be also removed from the config file, default=false>
}
成功响应 edited
{
"videoroom" : "edited",
"room" : <unique numeric ID>
}
destroy - 销毁房间
无论是静态还是动态的房间,都可以用destroy
进行销毁。
请求用 body
{
"request" : "destroy",
"room" : <unique numeric ID of the room to destroy>,
"secret" : "<room secret, mandatory if configured>",
"permanent" : <true|false, whether the room should be also removed from the config file, default=false>
}
成功响应 destoryed
{
"videoroom" : "destroyed",
"room" : <unique numeric ID>
}
房间用户接收的事件 event
用户可能在房间使用中销毁房间,其他观众会收到这个事件,获知房间被销毁
{
"videoroom" : "destroyed",
"room" : <unique numeric ID of the destroyed room>
}
exists - 房间是否存在
请求用 body
{
"request" : "exists",
"room" : <unique numeric ID of the room to check>
}
成功响应 - success
{
"videoroom" : "success",
"room" : <unique numeric ID>,
"exists" : <true|false>
}
allowed - 出入管理
用于配置是否启用token
验证,添加或移除允许进入房间的用户。
请求用 body
{
"request" : "allowed",
"secret" : "<room secret, mandatory if configured>",
"action" : "enable|disable|add|remove",
"room" : <unique numeric ID of the room to update>,
"allowed" : [
// Array of strings (tokens users might pass in "join", only for add|remove)
]
}
成功响应 success
{
"videoroom" : "success",
"room" : <unique numeric ID>,
"allowed" : [
// Updated, complete, list of allowed tokens (only for enable|add|remove)
]
}
kick - 踢出房间
房间的管理员可以通过kick
方法将指定参与者踢出房间。注意,只是能将他踢出房间,并不能阻止他再次进入。若想阻止他再次进入,则需要使用allowed
对他进行限制。
请求用 body
{
"request" : "kick",
"secret" : "<room secret, mandatory if configured>",
"room" : <unique numeric ID of the room>,
"id" : <unique numeric ID of the participant to kick>
}
成功响应 - success
{
"videoroom" : "success",
}
list - 房间列表
用于展示公开的所有房间
请求用 body
{
"request" : "list"
}
成功响应 - success
{
"videoroom" : "success",
"rooms" : [ // Array of room objects
{ // Room #1
"room" : <unique numeric ID>,
"description" : "<Name of the room>",
"pin_required" : <true|false, whether a PIN is required to join this room>,
"max_publishers" : <how many publishers can actually publish via WebRTC at the same time>,
"bitrate" : <bitrate cap that should be forced (via REMB) on all publishers by default>,
"bitrate_cap" : <true|false, whether the above cap should act as a limit to dynamic bitrate changes by publishers>,
"fir_freq" : <how often a keyframe request is sent via PLI/FIR to active publishers>,
"audiocodec" : "<comma separated list of allowed audio codecs>",
"videocodec" : "<comma separated list of allowed video codecs>",
"record" : <true|false, whether the room is being recorded>,
"record_dir" : "<if recording, the path where the .mjr files are being saved>",
"num_participants" : <count of the participants (publishers, active or not; not subscribers)>
},
// Other rooms
]
}
listparticipants
请求用 body
{
"request" : "listparticipants",
"room" : <unique numeric ID of the room>
}
成功响应 participants
{
"videoroom" : "participants",
"room" : <unique numeric ID of the room>,
"participants" : [ // Array of participant objects
{ // Participant #1
"id" : <unique numeric ID of the participant>,
"display" : "<display name of the participant, if any; optional>",
"talking" : <true|false, whether user is talking or not (only if audio levels are used)>,
"internal_audio_ssrc" : <audio SSRC used internally for this active publisher>,
"internal_video_ssrc" : <video SSRC used internally for this active publisher>
},
// Other participants
]
}
异步请求
角色梳理
- 发布者 Publisher
- 活跃的发布者 active publisher
- 不活跃的发布者 passive publisher
- 订阅者 Subscriber
VideoRoom Publishers
在音频房间中,发布者(Publisher)是将多媒体数据发布在房间中的参与者,他们可以成为feed
让其他用户进行订阅(subscribe)。
join - 加入房间
我们可以通过join
请求将指定的handle
指定为publisher
或subscriber
。
- 指定为
publisher
:Janus 会将指定用户添加到房间中的参与者列表(the list of participants
)中,此后,参与者可以接受到房间中关于handle
的一系列通知(包括流的可用与移除)。 - 指定为
subscriber
:
请求用 body
{
"request" : "join",
"ptype" : "publisher",
"room" : <unique ID of the room to join>,
"id" : <unique ID to register for the publisher; optional, will be chosen by the plugin if missing>,
"display" : "<display name for the publisher; optional>",
"token" : "<invitation token, in case the room has an ACL; optional>"
}
成功响应 joined
成功join
入房间后,将会接收到当前房间中活跃的publishers
的列表。
{
"videoroom" : "joined",
"room" : <room ID>,
"description" : <description of the room, if available>,
"id" : <unique ID of the participant>,
"private_id" : <a different unique ID associated to the participant; meant to be private>,
"publishers" : [
{
"id" : <unique ID of active publisher #1>,
"display" : "<display name of active publisher #1, if any>",
"audio_codec" : "<audio codec used by active publisher #1, if any>",
"video_codec" : "<video codec used by active publisher #1, if any>",
"simulcast" : "<true if the publisher uses simulcast (VP8 and H.264 only)>",
"talking" : <true|false, whether the publisher is talking or not (only if audio levels are used)>,
},
// Other active publishers
],
"attendees" : [ // Only present when notify_joining is set to TRUE for rooms
{
"id" : <unique ID of attendee #1>,
"display" : "<display name of attendee #1, if any>"
},
// Other attendees
]
}
当房间配置了
notify_joining
为TRUE
时,也会返回不推流的参与者。
private_id
:该属性用于订阅,便于插件对其进行关联。
关于不活跃的发布者进出通知
不活跃的发布者加入房间不会通知其他参与者,因为一个大房间中可能会有很多参与者,如果每一个进入离开都需要通知所有人的话会显得太过于繁琐。如果开发者它进出房间也通知所有人,那么需要将房间的notify_joining
配置为true
。通知的事件如下:
{
"videoroom" : "event",
"room" : <room ID>,
"joining" : {
"id" : <unique ID of the new participant>,
"display" : "<display name of the new participant, if any>"
}
}
publish - 发布(开始发布1)
发布者带着 Offer 向 Janus 进行协商
publish
请求必须携带着 JSEP SDP offer去协商一个新的 PeerConnection,即插件会检查编码器和码率是否是房间所支持的,之后将会回应一个 JSEP SDP acswer 来关闭循环并完成配置一个 PeerConnection。当指定的 PeerConnection 搭建好后,该指定的 publisher 才会变成一个活跃的 publisher,之后其他参与者才可以订阅它。
请求用 Body
{
"request" : "publish",
"audio" : <true|false, depending on whether or not audio should be relayed; true by default>,
"video" : <true|false, depending on whether or not video should be relayed; true by default>,
"data" : <true|false, depending on whether or not data should be relayed; true by default>,
"audiocodec" : "<audio codec to prefer among the negotiated ones; optional>",
"videocodec" : "<video codec to prefer among the negotiated ones; optional>",
"bitrate" : <bitrate cap to return via REMB; optional, overrides the global room value if present>,
"record" : <true|false, whether this publisher should be recorded or not; optional>,
"filename" : "<if recording, the base path/file to use for the recording files; optional>",
"display" : "<new display name to use in the room; optional>"
}
成功响应 event
Janus 会在准备好 Answer 后进行响应,该 event 中也会带着 JSEP SDP answer
{
"videoroom" : "event",
"configured" : "ok"
}
configure - 配置(开始发布2)
publish
和configure
在功能上时等价的,他们都有开始发布的功能。从语义上,publish
更加合适。但是configure
可以更新发布者会话的一些属性。总的来说configure
的功能更加强大,用它就没错了。
请求用 body
{
"request" : "configure",
"audio" : <true|false, depending on whether or not audio should be relayed; true by default>,
"video" : <true|false, depending on whether or not video should be relayed; true by default>,
"data" : <true|false, depending on whether or not data should be relayed; true by default>,
"bitrate" : <bitrate cap to return via REMB; optional, overrides the global room value if present (unless bitrate_cap is set)>,
"keyframe" : <true|false, whether we should send this publisher a keyframe request>,
"record" : <true|false, whether this publisher should be recorded or not; optional>,
"filename" : "<if recording, the base path/file to use for the recording files; optional>",
"display" : "<new display name to use in the room; optional>"
}
成功响应 event
{
"videoroom" : "event",
"configured" : "ok"
}
joinandconfigure - 加入并配置(加入并且发布)
我们可以通过使用joinandconfigure
在一个请求中进行加入和发布两个操作。
请求用 body
参考join
请求用的 body,并且携带 JSEP SDP offer
成功响应 - joined
成功响应将是一个joined
事件,并且携带 JSEP SDP answer。
发布成功后推送
当一个用户在房间中发布成功后,Janus 将会发送一个关于新对端的事件给所有参与者,事件如下:
{
"videoroom" : "event",
"room" : <room ID>,
"publishers" : [
{
"id" : <unique ID of the new publisher>,
"display" : "<display name of the new publisher, if any>",
"audio_codec" : "<audio codec used the new publisher, if any>",
"video_codec" : "<video codec used by the new publisher, if any>",
"simulcast" : "<true if the publisher uses simulcast (VP8 and H.264 only)>",
"talking" : <true|false, whether the publisher is talking or not (only if audio levels are used)>,
}
]
}
unpublish - 停止发布
unpublish
请求用于停止发布并且销毁相关的 PeerConnection,并且将发布者从活跃流列表中移除。
请求用 body
{
"request" : "unpublish"
}
成功响应 event
{
"videoroom" : "event",
"unpublished" : "ok"
}
其他成员通知 event
发布者停止订阅成功后,房间内的其他成员会通过下面这个通知得知此事
{
"videoroom" : "event",
"room" : <room ID>,
"unpublished" : <unique ID of the publisher who unpublished>
}
我们可以在同一个 handle 中多次发布和取消发布流
VideoRoom Subscribers
在 VideoRoom 中,订阅者(Subscriber)不是参与者,但是他可以准确接收指定发布者的媒体数据。通常发布者发布了媒体后,订阅者可以获取该流进行播放。当发布者取消发布后,订阅者 handle 也会随之被移除。除非给订阅者正确的信息,不然订阅者是不能单独存在的。
join - 加入房间
请求用 body
{
"request" : "join",
"ptype" : "subscriber",
"room" : <unique ID of the room to subscribe in>,
"feed" : <unique ID of the publisher to subscribe to; mandatory>,
"private_id" : <unique ID of the publisher that originated this request; optional, unless mandated by the room configuration>,
"close_pc" : <true|false, depending on whether or not the PeerConnection should be automatically closed when the publisher leaves; true by default>,
"audio" : <true|false, depending on whether or not audio should be relayed; true by default>,
"video" : <true|false, depending on whether or not video should be relayed; true by default>,
"data" : <true|false, depending on whether or not data should be relayed; true by default>,
"offer_audio" : <true|false; whether or not audio should be negotiated; true by default if the publisher has audio>,
"offer_video" : <true|false; whether or not video should be negotiated; true by default if the publisher has video>,
"offer_data" : <true|false; whether or not datachannels should be negotiated; true by default if the publisher has datachannels>,
"substream" : <substream to receive (0-2), in case simulcasting is enabled; optional>,
"temporal" : <temporal layers to receive (0-2), in case simulcasting is enabled; optional>,
"spatial_layer" : <spatial layer to receive (0-2), in case VP9-SVC is enabled; optional>,
"temporal_layer" : <temporal layers to receive (0-2), in case VP9-SVC is enabled; optional>
}
如果发布者对房间中的媒体进行订阅,则需要传入private_id
。
成功响应 attached(Offer)
订阅者成功加入房间并订阅指定feed
成功后,Janus 会给予一个attached
响应,并且在这个event
中携带 JSEP SDP offer,用于进行媒体协商。
{
"videoroom" : "attached",
"room" : <room ID>,
"feed" : <publisher ID>,
"display" : "<the display name of the publisher, if any>"
}
start - 开始订阅(Answer 对 attached 的订阅者响应)
订阅者接收到含有attach
的event
后,获取里面的 JSEP SDP offer 后,将其设置为远程 SDP 后,然后发送start
请求并携带 JSEP SDP answer。
请求用 body
{
"request" : "start"
}
成功响应 (event - started)
{
"videoroom" : "event",
"started" : "ok"
}
当订阅者与Janus媒体协商完毕后,双方就会建立一条新的 WebRTC PeerConnection。在这之后,Streaming插件将会开始将媒体转发到订阅者端。
Notice that the same exact steps we just went through (
watch
request, followed by JSEP offer by the plugin, followed bystart
request with JSEP answer by the viewer) is what you also use when renegotiations are needed, e.g., for the purpose of ICE restarts.
pause - 暂停订阅
我们可以通过pause
和start
请求对订阅媒体进行临时的暂停和恢复
此处的 start 不需要携带 JSEP SDP answer
请求用 body
{
"request" : "pause"
}
成功响应(event - paused)
{
"videoroom" : "event",
"paused" : "ok"
}
用于恢复的
start
其用法和上面是一致的,注意他不用携带 JSEP 即可。
configure - 配置
configure
请求允许动态修改订阅者对于媒体订阅的一些属性,比如配置音频和视频的启用与禁用,灵活设置订阅内容。
请求用 body
{
"request" : "configure",
"audio" : <true|false, depending on whether audio should be relayed or not; optional>,
"video" : <true|false, depending on whether video should be relayed or not; optional>,
"data" : <true|false, depending on whether datachannel messages should be relayed or not; optional>,
"substream" : <substream to receive (0-2), in case simulcasting is enabled; optional>,
"temporal" : <temporal layers to receive (0-2), in case simulcasting is enabled; optional>,
"spatial_layer" : <spatial layer to receive (0-2), in case VP9-SVC is enabled; optional>,
"temporal_layer" : <temporal layers to receive (0-2), in case VP9-SVC is enabled; optional>
}
-
audio
、video
和data
:可以在媒体层(media-level
)上进行暂停与恢复,对比pause
和start
直接作用所有音视频流而言,它更加灵活。 -
substream
和temporal
: Thesubstream
andtemporal
properties, instead, only make sense when the mountpoint is configured with video simulcasting support, and as such the viewer is interested in receiving a specific substream or temporal layer, rather than any other of the available ones. -
spatial_layer
和temporal_layer
:Thespatial_layer
andtemporal_layer
have exactly the same meaning, but within the context of VP9-SVC publishers, and will have no effect on subscriptions associated to regular publishers.
switch - 切换
switch
适用于已经与 Janus 成功建立 PeerConnection 的订阅者,通过这个请求完成订阅的切换,就像看电视切换频道一样。这样做的好处就是能复用一条连接,在切换订阅目标的时候不需要重新建立连接,消耗不必要的资源。但是他也有以下的限制:
- 不能只换音视频流的其中一个,要都换
- 两个目标发布者必须使用一样的配置,比如一样的编码器、一样的数据格式(不一样的话会导致延迟甚至直接黑屏没声音)
请求用 body
{
"request" : "switch",
"feed" : <unique ID of the new publisher to switch to; mandatory>,
"audio" : <true|false, depending on whether audio should be relayed or not; optional>,
"video" : <true|false, depending on whether video should be relayed or not; optional>,
"data" : <true|false, depending on whether datachannel messages should be relayed or not; optional>
}
成功响应 event
{
"videoroom" : "event",
"switched" : "ok",
"room" : <room ID>,
"id" : <unique ID of the new publisher>
}
leave - 离开房间
请求用 body
leave
请求用于停止订阅并销毁相关的 PeerConnection,由于上下文是隐式的,所以不需要其他参数:
{
"request" : "leave"
}
成功响应 - left event
{
"videoroom" : "event",
"left" : "ok",
}