janus Web
1. 在线demo
通过janus
的源码的html
文件以及相应的js
文件我们可以参考官方的demo
,在上文服务端的部署中最后我们可以进行在线使用。
2. 模块开发
只有demo
肯定是不够的,而且使用的是jquery
和bootStrap
,改起来也特别麻烦。
因此我们可以参考janus 的 jsAPI
将需要将功能抽离到vue
中,这里因为我使用到的是videoroom
,因此代码也是以videoroom
为例。
2.1 安装依赖
首先我们需要安装janus
需要的依赖
npm i janus-gatewaynpm i webrtc-adapter
2.2 初始化
<template><div><button @click="start">Start</button><button @click="stop">Stop</button><video id="myVideo" style="width: 1280px;height: 720px;border: 1px solid #ccc;margin-top: 10px;" /></div>
</template>
import Janus from 'janus-gateway';
import adapter from 'webrtc-adapter'const server = "ws://xx.xx.xx.xx:8188"; // 这个是上文部署的服务端的IP地址
const videoId = "myVideo"; // template中用来接收流的video标签的id
const janus = ref(null); // 保存janus实例
const pluginHandler = ref(null); // 保存插件句柄, 许多交互操作都需要这个变量来操作
const room = 1234; // 连接的房间id, 默认在后端没改动的情况下为 1234
const userId = ref(null); // 需要拉流的 发布者idconst start = () => {// 浏览器不支持webRTCif (!Janus.isWebrtcSupported()) {return;}// 初始化并指定配置项Janus.init({debug: true,dependencies: Janus.useDefaultDependencies({adapter}),callback: () => startJanus()})
}const startJanus = () => {// 将janus实例保存起来janus.value = new Janus({// 指定上述的janus服务器ip连接server: server,success: function () {attachToStreamingPlugin(janus.value);}});
}
2.3 连接会话
连接会话前需要打开服务端http-server
发布的在线demo
中的videoroom
,加入并且开始推送,前端才能接收到流,否则videoroom
里面是空的没有流,前端也就拉不到数据了。
const attachToStreamingPlugin = (janus) => {// 创建一个新的会话janus.attach({// 指定插件为videoroom, 需要使用streaming则进行切换, 插件不同代码也不同, 需要另外编写逻辑和处理回调。plugin: "janus.plugin.videoroom",// 指定房间号, 默认1234room,// 成功连接时的回调, 保存插件句柄, getParticipant获取房间内所有发布者的列表success: function (pluginHandle) {pluginHandler.value = pluginHandle;getParticipant()},// 接收到信息时的回调// 由于需要建立webrtc双向连接, 因此需要接收jsep信息并createAnswer回复本地描述来维持webrtc的连接// 具体逻辑在handleSDPonmessage: function (msg, jsep) {handleSDP(jsep);},// 接收到远程的流时的处理onremotetrack: function (stream) {handleStream(stream);},// 清除信息时回调oncleanup: function () {}});
}// 获取房间内所有发布者的列表
const getParticipant = () => {pluginHandler.value.send({message: {request: "listparticipants",room,audio: true,video: true,},success: function (response) {// 成功回调中可以获取所有房间内发布者的列表const participants = response["participants"];// 选择第一个用户开始订阅 可以添加业务逻辑拉第几个或多个const firstPart = participants[0]startStream(firstPart)},error: function (error) {}});
}
2.4 订阅发布者的流
const startStream = (selectedStream) => {// 获取到发布者的idvar selectedStreamId = selectedStream?.["id"];if (selectedStreamId === undefined || selectedStreamId === null) {return console.log("::: No selected stream :::");}// 将id存到userId中userId.value = selectedStreamId// 订阅流video和audiopluginHandler.value.send({message: {request: "join",feed: selectedStreamId,room: room,ptype: "subscriber",audio: true,video: true,}})
}const handleSDP = (jsep) => {if (jsep !== undefined && jsep !== null) {// 处理jsep信息并创建回复,成功回复之后通过keepRTC回调保持会话pluginHandler.value.createAnswer({// 将发送过来的jsep信息返回jsep,// 由于只发送jsep信息所以将audioSend和videoSend设置为false,不传输媒体流media: {audioSend: false, videoSend: false},// 成功后执行keepRTC并将sdp参数传入success: keepRTC,error: function (error) {console.error("::: WebRTC error :::" + JSON.stringify(error));}});}
}const keepRTC = (sdp) => {// 发送start请求保持连接pluginHandler.value.send({message: {request: "start"},jsep: sdp});
}
2.5 流的处理
const handleStream = (stream) => {// 接收到远程流之后新建一个媒体流 将远程流加入轨道const streamRemote = new MediaStream();streamRemote.addTrack(stream);const videoElement = document.getElementById(videoId);// 在video标签显示内容videoElement.srcObject = streamRemote;videoElement.oncanplay = () => {videoElement.play()};
}const stopStream = () => {pluginHandler.value.send({message: {request: "stop"}});pluginHandler.value.hangup();
}
完整示例代码(vue3)
<template><div><button @click="start">Start</button><button @click="stop">Stop</button><video id="myVideo" style="width: 1280px;height: 720px;border: 1px solid #ccc;margin-top: 10px;"></video></div>
</template><script setup>
import {ref} from 'vue'import Janus from 'janus-gateway';
import adapter from 'webrtc-adapter'const server = "ws://xxx.xx.xxx.xx:8188";
const videoId = "myVideo"; // 接收流的video标签的id
const janus = ref(null); // 保存janus实例
const pluginHandler = ref(null); // 保存插件句柄, 许多交互操作都需要这个变量来操作
const room = "1234"; // 连接的房间id, 默认在后端没改动的情况下为 1234
const userId = ref(null); // 需要拉流的发布者idconst start = () => {init()
}const stop = () => {stopStream()
}const init = () => {// 浏览器不支持webRTCif (!Janus.isWebrtcSupported()) {console.error("::: No webrtc support :::");return;}// 初始化并指定配置项Janus.init({debug: true,dependencies: Janus.useDefaultDependencies({adapter}),callback: () => startJanus()})
}const startJanus = () => {// 创建一个新的会话janus.value = new Janus({server: server,success: function () {attachToStreamingPlugin(janus.value);},error: function (error) {console.log("::: error :::", error);},destroyed: function () {console.log("::: destroyed :::");}});
}const attachToStreamingPlugin = (janus) => {janus.attach({plugin: "janus.plugin.videoroom",room,// 成功连接时的回调, 保存插件句柄, 获取房间内所有发布者的列表success: function (pluginHandle) {pluginHandler.value = pluginHandle;getParticipant()},/*接收到信息时的回调,由于需要建立webrtc双向连接, 因此需要接收jsep信息并createAnswer回复本地描述来维持webrtc的连接,具体逻辑在handleSDP。*/onmessage: function (msg, jsep) {console.log(" ::: message :::", JSON.stringify(msg));console.log(" ::: jsep :::", jsep);handleSDP(jsep);},// 接收到远程的流时的处理onremotetrack: function (stream) {console.log(" ::: remote stream :::", stream);handleStream(stream);},oncleanup: function () {console.log(" ::: cleanup notification :::");},error: function (error) {console.log("::: Error attaching plugin :::" + error);},});
}const getParticipant = () => {pluginHandler.value.send({message: {request: "listparticipants",room: +room,audio: true,video: true,},success: function (response) {// 成功回调中可以获取所有房间内发布者的列表const participants = response["participants"];// 选择第一个用户开始订阅 默认只有一个流const firstPart = participants[0]startStream(firstPart)},error: function (error) {console.error("::: Error getting participant list :::", error);}});
}const startStream = (selectedStream) => {// 获取到发布者的idvar selectedStreamId = selectedStream?.["id"];if (selectedStreamId === undefined || selectedStreamId === null) {return console.log("::: No selected stream :::");}userId.value = selectedStreamId// 订阅流video和audiopluginHandler.value.send({message: {request: "join",feed: selectedStreamId,room: +room,ptype: "subscriber",audio: true,video: true,}})
}const handleSDP = (jsep) => {if (jsep !== undefined && jsep !== null) {pluginHandler.value.createAnswer({jsep,media: {audioSend: false, videoSend: false},success: keepRTC,error: function (error) {console.error("::: WebRTC error :::" + JSON.stringify(error));}});}
}const keepRTC = (sdp) => {console.log("::: sdp :::", sdp);pluginHandler.value.send({message: {request: "start"},jsep: sdp});
}const handleStream = (stream) => {const streamRemote = new MediaStream();streamRemote.addTrack(stream);const videoElement = document.getElementById(videoId);videoElement.srcObject = streamRemote;videoElement.oncanplay = () => {videoElement.play()};
}const stopStream = () => {pluginHandler.value.send({message: {request: "stop"}});pluginHandler.value.hangup();
}
</script>