通过janus
的源码的html
文件以及相应的js
文件我们可以参考官方的demo
,在上文服务端的部署中最后我们可以进行在线使用。
只有demo
肯定是不够的,而且使用的是jquery
和bootStrap
,改起来也特别麻烦。
因此我们可以参考janus 的 jsAPI
将需要将功能抽离到vue
中,这里因为我使用到的是videoroom
,因此代码也是以videoroom
为例。
首先我们需要安装janus
需要的依赖
npm i janus-gateway
npm i webrtc-adapter
<template>
<div>
<button @click="start">Startbutton>
<button @click="stop">Stopbutton>
<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); // 需要拉流的 发布者id
const start = () => {
// 浏览器不支持webRTC
if (!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);
}
});
}
连接会话前需要打开服务端http-server
发布的在线demo
中的videoroom
,加入并且开始推送,前端才能接收到流,否则videoroom
里面是空的没有流,前端也就拉不到数据了。
const attachToStreamingPlugin = (janus) => {
// 创建一个新的会话
janus.attach({
// 指定插件为videoroom, 需要使用streaming则进行切换, 插件不同代码也不同, 需要另外编写逻辑和处理回调。
plugin: "janus.plugin.videoroom",
// 指定房间号, 默认1234
room,
// 成功连接时的回调, 保存插件句柄, getParticipant获取房间内所有发布者的列表
success: function (pluginHandle) {
pluginHandler.value = pluginHandle;
getParticipant()
},
// 接收到信息时的回调
// 由于需要建立webrtc双向连接, 因此需要接收jsep信息并createAnswer回复本地描述来维持webrtc的连接
// 具体逻辑在handleSDP
onmessage: 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) {}
});
}
const startStream = (selectedStream) => {
// 获取到发布者的id
var selectedStreamId = selectedStream?.["id"];
if (selectedStreamId === undefined || selectedStreamId === null) {
return console.log("::: No selected stream :::");
}
// 将id存到userId中
userId.value = selectedStreamId
// 订阅流video和audio
pluginHandler.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
});
}
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();
}
<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); // 需要拉流的发布者id
const start = () => {
init()
}
const stop = () => {
stopStream()
}
const init = () => {
// 浏览器不支持webRTC
if (!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) => {
// 获取到发布者的id
var selectedStreamId = selectedStream?.["id"];
if (selectedStreamId === undefined || selectedStreamId === null) {
return console.log("::: No selected stream :::");
}
userId.value = selectedStreamId
// 订阅流video和audio
pluginHandler.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>