# avengine **Repository Path**: tubp/avengine ## Basic Information - **Project Name**: avengine - **Description**: JanusAVEngine是一款流媒体信令服务器,具有传输信令,管理流媒体服务器的功能。支持多人音视频接入,视频会议,视频直播,负载均衡,实时监控等。 - **Primary Language**: Unknown - **License**: Apache-2.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 4 - **Created**: 2024-08-28 - **Last Updated**: 2024-08-28 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # **JanusAVEngine** ## SDK ### 1.创建房间 接口路径 ``` /create_room ``` 简要描述 创建房间 请求类型 HTTP POST 请求参数 ``` /** * createRoomBody可选参数: * String description 房间的描述信息 * Boolean is_is_private : true | false(是否是私有房间? 如果创建的是私有房间,则无法通过list指令进行查看) * String secret : <可选项,操作房间所需的密码,如果设置了,则像做销毁房间这样的操作时你要带上它才行> * String pin : <可选项,加入会议房间的密码> * Boolean require_pvtid : true | false(是否订阅音视频流时,需要提供一个与发布者相关的有效_id, 默认为false) * Integer publishers : <房间内发布者的最大数>(例如,一个视频会议可以有6个发布者,而广播只有一个,默认= 3) * String bitrate : <房间里发布者发送数据的最大比特率>(例如128000) * Integer fir_freq : <向发布者发送FIR指令的频率>(0 =禁用) * String audiocodec : opus | g722 | pcmu | pcma | isac32 | isac16(发布者可以使用的音频编解码器列表,默认为opus。编码器按优先顺序以逗号分隔) * String videocodec : vp8 | vp9 | h264 | av1 | h265(发布者可以使用的视频编解码器列表,默认为vp8。可以按优先级顺序用逗号分隔,例如,vp9,vp8,h264) * String vp9_profile : VP9首选的profile("2" 表示 "profile-id= 2" ) * String h264_profile : H.264首选的profile("42e01f" 表示 "profile-level-id= 42e01f" ) * Boolean opus_fec : true | false(是否使用带内FEC;仅适用于Opus,默认为false) * Boolean video_svc : true | false(是否启用SVC支持;仅适用于VP9,默认为false) * Boolean audiolevel_ext : true | false(对于发布者是否使用RTP扩展ssrc-audio-level?默认为 true) * Boolean audiolevel_event : true | false(是否将audiolevel事件发送给其他用户) * Integer audio_active_packets : 100(音频保活包个数,默认值= 100,2秒) * Integer audio_level_average : 25(音频音量级别的平均值,127 =静音,0 ='太大声',默认= 25) * Boolean videoorient_ext = false : true | false(发布者是否使用RTP扩展video-orientation? 默认= true) * Boolean playoutdelay_ext : true | false(发布者是否使用RTP扩展playout-delay? 默认= true) * Boolean transport_wide_cc_ext : true | false(发布者是否使用RTP扩展 transport-wide-cc? 默认= true) * Boolean record : true | false(该房间是否启录制?默认= false) * String rec_dir : <启用录制后,录制文件存放的目录> * Boolean lock_record : true | false(是否锁定录制状态? 默认= false) * Boolean notify_joining : true | false(可选,当有新的参与方加入房音后,是否通知房间里的所有参与者?Videoroom插件默认仅通知发布者,启用此功能可能会导致额外的通知传输。该功能与require_pvtid一起启用时,对管理员管理仅收听的参与者特别有用。默认= false) * Boolean require_e2ee : true | false(是否启用端到端加密? 默认= false) * Boolean permanent : * String allowed : [可选,用户加入房间的token数组] * Boolean is_public : 是否公开 * 例子: { "createRoomBody":{ "room":999991, "is_":true, "pin":"123456", "publishers":9 }, "userId":"stephen"//请求用户ID(后期可能使用token代替) } */ ``` ### 2.加入房间 接口路径 ``` /join_pub ``` 简要描述 加入房间,并获得其他参与者信息,开始监听加入信息 请求类型 HTTP POST 请求参数 ``` /** * MAIN_PARAMETER: * String ptype : 加入类型publisher or subscriber * String pin : 密码 * String display : 加入者身份标识 * Long feed : 当作为subscriber加入时候,需要提供feed来订阅音视频 * String token : "" * EXAMPLE: { "joinBody":{ "pin":"123456", "room":999991, "display":"stephen" }, "userId":"stephen" } */ ``` 返回示例值 ```json { "handle_id": 7678119295883122, "sender": 7678119295883122, "janus": "event", "plugindata": { "plugin": "janus.plugin.videoroom", "data": { "videoroom": "joined", "attendees": [ //参与者信息,未发布视频,前端收到该信息可以处理UI逻辑 { "display": "738549C2B8684B3A", "id": 7512956477763016000 } ], "description": "Room 98206", "publishers": [//发布者信息,前端收到该信息可以开始订阅该用户 { "audio_codec": "opus", "display": "DEF73FE092F8CB33", "id": 8018577415440172000, "remote": true, "simulcast": true, "talking": false, "video_codec": "vp8" } ], "private_id": 3124855494, "id": 185502716393694000, "room": 98206 } }, "session_id": 5527847335467878, "transaction": "MAn7Um2DRG7l" } ``` ### 3.发送Offer 接口路径 ``` /offer ``` 简要描述 发送offer,并协商视频信息 请求类型 HTTP POST 请求参数 ``` /** * MAIN_PARAMETER: * Boolean audio : 是否发送音频 * Boolean video : 是否发送视频 * Integer pin : 密码 * Integer substream : substream(0-2):数字越大分辨率越高 * Integer temporal : temporal(0-2):数字越大帧率越高 * Integer spatial_layer : , * Integer temporal_layer : * Integer bitrate : 最大码率,不设置着为无上限 * Boolean restart : 重新协商offer * boolean keyframe : * boolean record : , * String filename : ", * String display : ", * String audio_active_packets : ", * String audio_level_average : ", * Integer mid : , * boolean send : , * EXAMPLE: { "configureBody":{ "audio":true, "pin":"123456", "request":"configure", "video":true }, "jsep":{ "sdp":"v=0\r\no=- 8960238441194795609...", "type":"offer" }, "userId":"stephen" } */ ``` 返回示例值 ```json { "sender": 7678119295883122, "jsep": { "type": "answer", "sdp": "v=0\r\no=- 4673851998710969533 2 IN IP4 58.39.96.197\r\n..." }, "janus": "event", "plugindata": { "plugin": "janus.plugin.videoroom", "data": { "configured": "ok", "video_codec": "vp8", "videoroom": "event", "audio_codec": "opus", "room": 98206 } }, "session_id": 5527847335467878, "transaction": "RNdvWKVjiJ1s" } ``` ### 4.订阅房间 接口路径 ``` /join_sub ``` 简要描述 订阅房间内的用户音视频数据。 请求类型 HTTP POST 请求参数 ``` /** * MAIN_PARAMETER: * String pin : 密码 * String token : "" * EXAMPLE: { "joinBody":{ "pin":"123456" }, "userId":"stephen", "targetUserId":"targetUserId" } */ ``` 返回示例值 ```json { "handle_id": 1458642848551551, "sender": 1458642848551551, "jsep": { "type": "offer", "sdp": "v=0\r\no=- 1642731969770272 1 IN IP4 58.39.96.197\r\ns=VideoRoom..." }, "janus": "event", "plugindata": { "plugin": "janus.plugin.videoroom", "data": { "videoroom": "attached", "display": "854D2A35F4096D6D", "id": 2022894204078739700, "room": 98206 } }, "session_id": 5527847335467878, "transaction": "7jAI4eI6CTt5" } ``` ### 5.发送candidate 接口路径 ``` /trickle ``` 简要描述 发送candidate 请求类型 HTTP POST 请求参数 ``` /** { candidate":"{ "candidate":"candidate:547260449 1 udp 2122260223 10.0.2.15 38953 typ host generation 0 ufrag kT2l network-id 3 network-cost 10", "pin":"123456", "sdpMLineIndex":0, "sdpMid":"audio" }", "userId":"stephen", "targetUserId":"targetUserId"//如果是发布流的candidate,targetUserId和userId相同,如果是订阅流的candidate,targetUserId和订阅用户一致。 } */ ``` 返回示例值 ```json {"janus":"ack"} ``` ### 6.离开房间 接口路径 ``` /leave ``` 简要描述 离开房间,不接收房间内通知 请求类型 HTTP POST 请求参数 ``` /** { "userId":"stephen" } */ ``` 返回示例值 ```json { "sender": 6328109649291273, "janus": "event", "plugindata": { "plugin": "janus.plugin.videoroom", "data": { "videoroom": "event", "leaving": "ok", "room": 98206 } }, "session_id": 3003539431406435, "transaction": "0FvQbeXe7W7R" } ``` ### 7.发送心跳 接口路径 ``` /keepalive ``` 简要描述 发送心跳,维持音视频连接 请求类型 HTTP POST 请求参数 ``` /** { "userId":"stephen" } */ ``` 返回示例值 ```json {"janus":"ack"} ``` ### 8.开始观看订阅 接口路径 ``` /start ``` 简要描述 开始观看订阅 请求类型 HTTP POST 请求参数 ``` /** { "jsep":{ "sdp":"v=0\r\no=- 3949792525869817657...", "type":"answer" }, "userId":"stephen", "targetUserId":"Alice"//订阅用户id } */ ``` 返回示例值 ```json { "sender": 1458642848551551, "janus": "event", "plugindata": { "plugin": "janus.plugin.videoroom", "data": { "videoroom": "event", "display": "854D2A35F4096D6D", "started": "ok", "room": 98206 } }, "session_id": 5527847335467878, "transaction": "Qp2lPMxBxUwE" } ``` ### 9.修改观看属性 接口路径 ``` /start ``` 简要描述 修改观看的属性,利于分辨率,帧率 请求类型 HTTP POST 请求参数 ``` /** * MAIN_PARAMETER: * Integer pin : 密码 * Integer substream : substream(0-2):数字越大分辨率越高 * Integer temporal : temporal(0-2):数字越大帧率越高 * Integer spatial_layer : , * Integer temporal_layer : * * EXAMPLE: { "configureBody":{ "substream":0, "temporal":1 }, "targetUserId":"targetUserId",//订阅用户id "userId":"stephen" } */ ``` 返回示例值 ```json { "sender": 3298027441055507, "janus": "event", "plugindata": { "plugin": "janus.plugin.videoroom", "data": { "videoroom": "event", "room": 98206, "substream": 0 } }, "session_id": 1475814366237898 } ``` ### 10.开始直播 接口路径 ``` /start_live ``` 简要描述 加入完房间之后,可以选择开始直播 请求类型 HTTP POST 请求参数 ``` /** { "userId":"stephen" } */ ``` 返回示例值 ```json {"janus":"ack"} ``` ### 11.暂停直播 接口路径 ``` /stop_live ``` 简要描述 开始直播之后,可以选择暂停直播 请求类型 HTTP POST 请求参数 ``` /** { "userId":"stephen" } */ ``` 返回示例值 ```json {"janus":"ack"} ``` ### 12.观看直播 接口路径 ``` /watch_live ``` 简要描述 选择房间,观看直播 请求类型 HTTP POST 请求参数 ``` /** { "userId":"stephen" "roomId":123456 } */ ``` 返回示例值 ``` { "sender": 6978937607200697, "jsep": { "type": "offer", "sdp": "v=0\r\no=- 1642735494686100 1 IN IP4 58.39.96.197\r\ns=Mountpoint 140319\r\nt=0 0\r\na=group:BUNDLE audio video\r\na=msid-semantic: WMS janus\r\nm=audio 9 UDP/TLS/RTP/SAVPF 111\r\nc=IN ..." }, "janus": "event", "plugindata": { "plugin": "janus.plugin.streaming", "data": { "result": { "status": "preparing" }, "streaming": "event" } }, "session_id": 1179807261471032, "transaction": "mxQdAynKPm3T" } ``` ``` janus: #日志功能是否开启 is_log: true #日志级别 1-5 分别对应ERROR,WARN,INFO,DEBUG,TRACE 日志级别0不输出日志 log_level: 5 #每经过10s检测janus服务器存活情况 check : false #每个janus服务器最大容纳多少人 max_participant: 10 is_share_stream: false #分配策略,1.同房间优先分配同一个服务器 2.优先分配空闲服务器 3.优先根据IM分配服务器(未实现) 4.优先根据地域分配服务器(未实现) #超过最大容纳人数之后,分配空闲服务器。 load_distribution_strategy: 1 #janus rtp转发端口范围 min_port: 20000 max_port: 30000 ``` ## Q&A ### **Q:什么是JanusAVEngine**? A:JanusAVEngine是一款流媒体信令服务器,具有传输信令,管理流媒体服务器的功能。支持多人音视频接入,视频会议,视频直播,负载均衡,实时监控等。 顾名思义JanusAVEngine是为了整合多个Janus服务器而提供的一个公共入口,为了支持多人音视频,视频会议等功能需要使用大量的流媒体服务器(Janus),而Janus本身并不是原生支持分布式部署的,所以我们在此之上,构建了JanusAVEngine来作为管理,分配,调度Janus服务器的工具。 JanusAVEngine并不提供流媒体能力,它只是帮助用户,无感的连接到Janus服务器上,或者调度Janus服务器进行流媒体数据转发。 对于客户端来说,客户端连接的永远是JanusAVEngine,但是最终提供服务的是数不胜数的Janus服务器。 ### **Q:JanusAVEngine发展过程?** A: **2021年** - 8月20号:apollo在sc-server项目,写下了第一句基于rabbitmq发布订阅的音视频信令demo - 8月27号:看到第一帧来着对方的画面 - 8月30号:单机版多人音视频功能基本完成 - 9月8日:单机版支持**simulcast**功能 - 9月23日:单机版->分布式改造正式开始 - 9月29日:**分布式改造**基本完成 - 10月14日:移除客户端临时分布式订阅信令。 - 10月26日:分布式音视频首屏时间优化(开启**关键帧**支持) - 11月03日:修复级联转发亮屏时间不稳定问题(添加**rtcp**支持) - 11月10日:**音视频信令模块从IM服务独立出来** - 11月16日:移除客户端临时分布式转发信令,至此客户端不需要添加额外信令即可支持分布式功能。 - 11月17日:分布式流转发支持**simulcast**功能。 - 11月26日:添加自动发现,移除Janus服务器功能 - 11月29日:添加统一**日志**管理 - 12月01日:添加Echart基础**监控**功能 - 12月17日:添加**redis分布式锁**,保证数据一致性 **2021年** - 1月12日:sdk开发 - 1月17日:新增基础**直播功能**完成 - 1月20日:WebSocket,HTTP SDK接口开发完毕 ### **Q:JanusAVEngine包括什么?** JanusAVEngine包括下面三个模块: | 模块 | 说明 | 备注 | | ------------------------------------------------------------ | ------------ | ------------------------------------ | | [av-sdk](http://gitlab.gms.com/Stephen.qiu/janusavengine/-/tree/sdk/av-sdk) | sdk | 基于http接口的音视频sdk调用,集成使用 | | [avengine-server](http://gitlab.gms.com/Stephen.qiu/janusavengine/-/tree/sdk/avengine-server) | server端源码 | 音视频服务主体,需启动运行 | | [common](http://gitlab.gms.com/Stephen.qiu/janusavengine/-/tree/sdk/common) | 通用模块代码 | 基础类 | 具体的功能参考备注 ### **Q:JanusAVEngine的架构是怎么样的?** ![image-20220120162927251](https://gmsresource.oss-cn-shanghai.aliyuncs.com/image-20220120162927251.png?versionId=CAEQLBiBgIDHucDa8xciIGFkNmVmYjVhYjc3MjQyYmY4MzQ1ZWJiYjgwM2E0ODA2) 建议使用nginx做JanusAVEngine负载均衡,这样客户端访问的入口都是固定的。每一台JanusAVEngine都绑定同一个rabbitmq,而所有的Janus服务器也是绑定同一个rabbitmq。JanusAVEngine需要IM服务来返回异步消息。需要用户自己配置IM服务器,或者使用JanusAVEngine上简单的websocket功能。 ### Q:JanusAVEngine如何让用户的流穿梭于不同的Janus服务器之间实现分布式? A:Janus视频会议的实现关键是音视频流的上传(发布)和下载(订阅)。 在单机版中,由Janus内置房间概念帮助用户管理上传(发布)流和下载(订阅)流。我们通过**join**信令,分别以发布者的身份和订阅者的身份加入一个房间之后,就可以自由的发布自己的流在这个房间,并且可以供其他人订阅,自己也可以任意订阅该房间内的任何一个人的流。 在分布式中,用户可能会被分配到不同的Janus服务器上,而Janus内置的房间概念,并没有办法对其他Janus服务器上的流进行管理。 于是我们为了实现不同的Janus服务器之间实现分布式级联,我们必须要完成下面的内容: 1. **重写房间逻辑,自己管理每一个发布者和订阅者** 2. **重写通知逻辑,让非同一个Janus服务器上的同房间用户也可以接收到所有通知(如加入,离开)** 3. **让发布者的音视频流可以从一个Janus服务器到另一个Janus服务器** 4. **让Janus服务器接收外部的音视频流** 5. **让用户订阅不同服务器上的用户时,同订阅同服务器用户一样具有相同的参数和返回值** ### **Q:JanusAVEngine的房间管理,和janus的房间管理是不是一个东西?** A:不是一个东西。 Janus的房间管理仅局限于单台Janus服务器,在room里面的也是连接到同一台服务器上的用户,但是JanusAVEngine是更上一层的管理,它的房间管理是最全的。 例:JanusAVEngine维护一个房间(**9999**),里面有三个用户**A**,**B**,**C**。他们分别连接到**一号Janus**服务器和**二号Janus**服务器上,对于**一号Janus**服务器来说,它可能只有**A**,**B**两个用户的信息,对于**二号Janus**服务器来说,它就只有**C**一个用户的信息。 而所有的信息通知都是根据Janus内置房间的用户来进行通知的,于是我们需要在JanusAVEngine里对这些通知进行进一步的处理。 ### **Q:如何快速使用JanusAVEngine?** A:我们为刚接触JanusAVEngine的同学准备了一个简易的web端源码,可以快速实现多人会议,视频直播功能。 可以参照demo慢慢熟悉音视频接口以及使用。 | 仓库 | 说明 | 备注 | | -------------------------------------------------------- | --------- | --------------------------------------- | | [webchat](https://gitee.com/stephen-qiu/webchat.git) | web端源码 | 可以很方便地进行二次开发,熟悉音视频接口 | ### **Q:JanusAVEngine是否支持分布式部署?** A:当然。JanusAVEngine采用redis统一缓存,每一个JanusAVEngine实例都是无状态的,它的数据交互都是通过访问redis集群完成,只要所有的JanusAVEngine实例都配置同一个redis集群,那么它们就是天然的分布式系统。 ### **Q:JanusAVEngine提供了哪些类型的访问接口?** A:长连接和短连接都有。你可以使用HTTP接口,或者websocket连接等来发起你的请求(后续会添加更多的方式)。 调用HTTP接口会马上返回同步消息,但是一些异步消息需要配置长连接方式获取,如果集成了我们的IM服务使用,那么会从IM服务器的消息通道返回异步消息。 调用websocket连接使用send方法发送信令,通过action指定请求路径,通过监听onmessage方法获取返回值。 ### **Q:JanusAVEngine可以支持多少Janus服务器?** A:取决于rabbitmq的吞吐量。由于JanusAVEngine采用rabbitmq通过不同队列的方式与Janus服务器进行通信,那么Janus服务器的数量就取决于rabbitmq或者rabbitmq集群消息的吞吐量。 ``` #janus rabbitmq 配置 janus.rabbitmq_host: 47.100.62.xx rabbitmq_port: 5672 rabbitmq_username: guest rabbitmq_password: guest rabbitmq_exchange: janus-exchange rabbitmq_virtualHost: / ``` ### **Q:JanusAVEngine的Janus分配策略有哪些?** A:1.同房间优先分配同一个服务器 2.优先分配空闲服务器 3.优先根据IM分配服务器(未实现) 4.优先根据地域分配服务器(未实现) ### **Q:JanusAVEngine是否支持动态扩容Janus?** A:支持。JanusAVEngine配置里面并没有详细配置Janus服务器的地址,取而代之的是配置了rabbitmq服务器。当你启动的Janus服务器绑定的rabbitmq地址与JanusAVEngine一致(**详情请参考Janus服务器配置**),那么就会被JanusAVEngine所监听到,进而添加到JanusAVEngine管理当中。相反,如果绑定rabbitmq的Janus服务器失去连接,JanusAVEngine也会相应的移除该Janus节点。 **PS:请勿在运行过程中人为的手动关闭Janus节点,可能会导致用户失去视频连接,可以通过监控系统查看Janus节点使用情况** ### **Q:JanusAVEngine中每一个房间支持多少个人?** A:按照市场上普遍的设计,我们每一个房间的人数限定为9人,当然可以结合业务情况进行修改。 ### **Q:JanusAVEngine中每一个Janus节点支持多少个人?** A:按照服务器100M带宽,上传下载100KB/s计算,如果是2个人进行音视频通话,单台服务器可以支持 : $$ 100M = 12800KB/s $$ $$ 12800KB/s / 100KB/s = 128人 $$ 按照服务器100M带宽,上传下载100KB/s计算,如果是9个人进行音视频通话,单台服务器可以支持 : $$ 12800KB/s / 800KB/s = 16人 $$ 由于Janus是SFU框架流媒体服务器,所以单Janus节点支持人数会根据参加音视频人数的不同而不同,差距也会比较明显。 所以在分配策略中,我们可以先按同房间优先分配同一个服务器,当所有Janus服务器都至少被一个房间占用时,可以使用优先分配空闲服务器策略,分配人数较少的Janus服务器,当然,细节部分还可以根据业务进行修改。 ### **Q:JanusAVEngine的一定要使用IM功能吗?** A:是的。JanusAVEngine存在一些异步通知类型的消息,需要集成基础的IM功能才能实现,否则就会缺少关键信令导致无法正常工作,例如最基础的websocket。当然JanusAVEngine也非常方便二次开发来实现基础IM功能,例如MQTT等。你一定要保证前端直接或者间接和JanusAVEngine保持长连接通道,你可以自己编写一个专业的IM服务器,来调用JanusAVEngine的sdk,同时编写IM sdk供JanusAVEngine异步消息通知使用,我们的项目就是这样做的,这样做的好处就是不需要同时为IM服务和JanusAVEngine保持长连接。 ``` #im通知接口 im: admin_url: http://gms-im:18080 admin_secret: 123456 ``` ### **Q:JanusAVEngine如何保持缓存数据正确性?** A:使用redisson分布式锁来保证部分业务不会在高并发的情况下发生业务数据错误问题。 ### **Q:JanusAVEngine日志功能?** A:JanusAVEngine支持动态修改输出日志级别,就算是线上环境也可以有效的发现和定位问题。 ### **Q:JanusAVEngine邮件提醒功能?** A:JanusAVEngine支持邮件提醒功能,配置好自己的通知邮箱之后,当janus服务器发生故障的时候,可以尽快通知到维护人员。 ``` #公司系统邮箱 email: emailName: xxxx@xxxx.com emailPassword: xxxx #target邮箱 toEmail: xxxx@qq.com #信息title格式 avEngineTitle : avEngine提示服务 #是否发送邮件 send : true ``` ### **Q:JanusAVEngine是否支持docker部署?** A:当然支持 ### **Q:我们需要自己部署什么?** A:1.rabbitmq服务器 2.Janus服务器 3.redis 4.IM服务器(如果有)5.nginx服务器(如果有多个JanusAVEngine时做负载均衡) ### **Q:如何快速部署Janus服务器?** A:需要到你需要部署Janus的服务器上下载脚本janus.sh,并运行脚本,简单的来说就是下面这样 ```shell wget https://gmsresource.oss-cn-shanghai.aliyuncs.com/janus.sh sudo sh janus.sh 服务器唯一标识 rabbitmq服务器地址 ``` ### **Q:如何快速启动JanusAVEngine?** 确保安装jdk1.8及以上环境,确保安装maven,确保安装redis及rabbitmq,修改JanusAVEngine中redis和rabbitmq相关配置。 使用mvn 进行包导入,最后使用idea运行或者mvn package之后使用java -jar命令运行。 ### **Q:JanusAVEngine的优势在哪里?** A:JanusAVEngine服务器对客户端隐藏了流媒体服务器的一些细节,例如session_id,handle_id等来自Janus系统里面的参数,这些参数统一由JanusAVEngine进行管理,方便了客户端的开发工作,对于后续更换流媒体服务器,例如弃用Janus,更换自行研发的流媒体服务器,都可以保持入参和出参都不会随着更换服务器而发生很大的变动,使客户端不需要,或者仅需要少量代码即可完成适配。 JanusAVEngine完善了单一Janus服务器不存在的功能,例如级联,数据转发等,让无数Janus服务器在协调工作中发挥出更强大的表现力。 JanusAVEngine提供了一个简单易用的SDK接口,参考Webchat demo,前端工程师仅需要像实现原生webrtc一样,完成一个视频会议的搭建。