多种接入场景demo/实践
6.1、快速接入
6.1.1、公有化
import { Launcher } from "live-cat";
const appKey = "xxxxxxxxxxxxxxxxxxx";
const address = "https://app.3dcat.live";
const bootstrap = async () => {
try {
const launch = new Launcher({
baseOptions: {
address,
appKey,
startType: 1,
},
});
const player = document.querySelector("body");
//容器必须指定宽高,否则不显示
document.querySelector("body").style.width = "100vw";
document.querySelector("body").style.height = "100vh";
await launch.automata(player);
} catch (error) {
console.error(error);
}
};
window.addEventListener("DOMContentLoaded", () => {
if (
navigator.userAgent.includes("miniProgram") ||
navigator.userAgent.includes("MicroMessenger")
) {
//微信浏览器/微信小程序环境
document.addEventListener("WeixinJSBridgeReady", bootstrap, false);
} else {
bootstrap();
}
});
6.1.2、私有化
import { LauncherPrivate } from "live-cat";
const appKey = "xxxxxxxxxxxxxxxxxxx";
const address = "私有化服务器地址";
const bootstrap = async () => {
try {
const launch = new LauncherPrivate({
baseOptions: {
address,
appKey,
startType: 1,
},
});
const player = document.querySelector("body");
//容器必须指定宽高,否则不显示
document.querySelector("body").style.width = "100vw";
document.querySelector("body").style.height = "100vh";
await launch.automata(player);
} catch (error) {
console.error(error);
}
};
window.addEventListener("DOMContentLoaded", () => {
if (
navigator.userAgent.includes("miniProgram") ||
navigator.userAgent.includes("MicroMessenger")
) {
//微信浏览器/微信小程序环境
document.addEventListener("WeixinJSBridgeReady", bootstrap, false);
} else {
bootstrap();
}
});
6.2、错误代码使用说明
- 以公有化为例
- 错误代码说明文档
import { Launcher } from "live-cat";
const appKey = "xxxxxxxxxxxxxxxxxxx";
const address = "https://app.3dcat.live";
const bootstrap = async () => {
try {
const launch = new Launcher({
baseOptions: {
address,
appKey,
startType: 1,
},
});
const player = document.querySelector("body");
document.querySelector("body").style.width = "100vw";
document.querySelector("body").style.height = "100vh";
await launch.automata(player).catch((code) => {
//错误代码
console.error(code);
});
} catch (error) {
console.error(error);
}
};
window.addEventListener("DOMContentLoaded", () => {
if (
navigator.userAgent.includes("miniProgram") ||
navigator.userAgent.includes("MicroMessenger")
) {
//微信浏览器/微信小程序环境
document.addEventListener("WeixinJSBridgeReady", bootstrap, false);
} else {
bootstrap();
}
});
6.3、接入消息双向通信(ue4、unity)
import { Launcher } from 'live-cat'
const appKey = 'xxxxxxxxxxxxxxxxxxx'
const address = 'https://app.3dcat.live'
const bootstrap = async () => {
try {
const launch = new Launcher({
baseOptions: {
address,
appKey,
startType: 1,
},
extendOptions: {
onPlay: () => {
//web端->应用
window.connection.emitUIInteraction('text').then(res => {
console.log(res ? '发送成功' : '发送失败')
})
},
},
})
const player = document.querySelector('body')
document.querySelector('body').style.width = '100vw'
document.querySelector('body').style.height = '100vh'
await launch.automata(player)
const connection = launch.getConnection()
window.connection = connection
//应用->web端
connection.event.interaction.on(msg => {
console.log('收到应用发来的消息:', msg)
})
} catch (error) {
console.error(error)
}
}
window.addEventListener('DOMContentLoaded', () => {
if (
navigator.userAgent.includes('miniProgram') ||
navigator.userAgent.includes('MicroMessenger')
) {
//微信浏览器/微信小程序环境
document.addEventListener('WeixinJSBridgeReady', bootstrap, false)
} else {
bootstrap()
}
})
6.4、进入有效画面后初始化
import { Launcher } from "live-cat";
const appKey = "xxxxxxxxxxxxxxxxxxx";
const address = "https://app.3dcat.live";
const bootstrap = async () => {
try {
const launch = new Launcher({
baseOptions: {
address,
appKey,
startType: 1,
},
extendOptions: {
onPlay: () => {
// 初始化操作
},
},
});
const player = document.querySelector("body");
document.querySelector("body").style.width = "100vw";
document.querySelector("body").style.height = "100vh";
await launch.automata(player);
} catch (error) {
console.error(error);
}
};
window.addEventListener("DOMContentLoaded", () => {
if (
navigator.userAgent.includes("miniProgram") ||
navigator.userAgent.includes("MicroMessenger")
) {
//微信浏览器/微信小程序环境
document.addEventListener("WeixinJSBridgeReady", bootstrap, false);
} else {
bootstrap();
}
});
6.5、接入微信小程序
- 步骤
- 使用 jssdk 开发,打包成 html 部署到线上(生产环境需要域名解析)
- 微信小程序通过 web-view 嵌入 html 地址(域名链接)
- 开发 html 时候,微信(小程序)环境需要注意监听 WeixinJSBridgeReady 事件,然后开始对接 jssdk。
window.addEventListener("DOMContentLoaded", () => {
if (
navigator.userAgent.includes("miniProgram") ||
navigator.userAgent.includes("MicroMessenger")
) {
//微信浏览器/微信小程序环境
document.addEventListener("WeixinJSBridgeReady", bootstrap, false);
} else {
bootstrap();
}
});
6.6、投屏交互接入
- 投屏交互分为主控端、观看端(可被赋予临时操控权)
- 参考代码文件[点击下载]
- 时序图参考
6.7、替换加载页面各阶段提示
- 替换 "可视化服务启动中"
- 运行 await launch.automata(player)后,调用 launch.getPlayer(),获取 livePlayer,执行如下代码。
livePlayer.showLoadingText("xxxxxx");
- 替换 "可视化服务连接中"
- 运行 await launch.automata(player)后,调用 launch.getPlayer(),获取 livePlayer;调用 launch.getConnection(),获取 connection,执行如下代码。
const TEXT = "xxxxxx";
connection.event.connect.on(() => livePlayer.showLoadingText(TEXT));
connection.event.ready.on(() => livePlayer.showLoadingText(TEXT));
connection.event.receivedTrack.on(() => livePlayer.showLoadingText(TEXT));
connection.event.endCandidate.on(() => livePlayer.showLoadingText(TEXT));
- 替换 "连接成功,资源加载中" 或 "资源加载成功,请点击进入"
- 运行 await launch.automata(player)后,调用 launch.getPlayer(),获取 livePlayer;调用 launch.getConnection(),获取 connection,执行如下代码。
connection.event.dataChannelConnected.on(() =>
livePlayer.showLoadingText("xxxxxx")
);
- 替换 所有的异常报错
- 运行 await launch.automata(player)后,执行如下代码。
launch.destory("xxxx");
//or
livePlayer.showTextOverlay("xxxxxx");
6.8、去除/替换加载流程 UI 页面
- 挂载容器的时候(await launch.automata(player)),展示业务界面,z-index 属性大于 player 容器,等到 player 出现有效画面(onPlay)在出现有效画面后,在 onPlay 回调中把 业务界面去掉,参考代码如下。
- 注意自动播放问题:Autoplay policy in Chrome ,可能会出现资源加载中一直进不去,需要引导用户点击 player 元素再展示业务界面。(当用户操作系统为 iOS 时,需在
connection
的dataChannelConnected
event 被触发时执行launch
的getManualPlay()
方法来获取手动播放方法实例,再将其应用到所需业务场景。)
//以公有化为例
const launch = new Launcher({
baseOptions: {
address,
appKey,
startType: 1,
},
extendOptions: {
onPlay: () => {
//开始播放回调
//loadingEl 为业务界面容器
loadingEl.style.display = "none";
},
},
});
const player = document.querySelector("body");
document.querySelector("body").style.width = "100vw";
document.querySelector("body").style.height = "100vh";
await launch.automata(player);
let manualPlay;
connection.event.dataChannelConnected.on(() =>
{ manualPlay = luanch.getManualPlay()}
);
// 在所需业务场景执行如下方法
manualPlay();
6.9、接入多点触控需求(UE4)
6.10、浏览器直接接入 sdk
- 参考链接
- 需要引入 jssdk 的 umd 版本,以及相关依赖模块,具体见参考链接。
6.11、聚焦应用输入框,发送内容(复制粘贴)
- 运行 await launch.automata(player)后,调用 launch.getConnection(),获取 connection,执行如下代码。
//开发可以自定义检测到键盘的粘贴事件后执行
connection.send(new TextInput("test text").dumps());
6.12、调节码率
- 运行 await launch.automata(player)后,执行如下代码。
- 建议:用户的下行网络最好高于最高码率的 50%,体验最佳。
Connection.changeBandwidth(
8000, // 初始码率(8M)
10000, // 最高码率(10M)
5000 // 最低码率(5M)
);
//or
Connection.changeBandwidth(
10000 // 码率(均值)
);
6.13、获取连接状态具体说明
- 通过 launcher 的 toggleStatistics 方法可以切换统计数据开关,参考代码如下:
launcher.toggleStatistics();
window.setInterval(() => {
const {
fps,
bitrate,
packetLossRate,
latency,
averageJitterBufferDelay,
framesDecoded,
framesDropped,
framesReceived,
keyFramesDecoded,
} = launcher.report();
console.log(`
FPS: ${fps}
biterate: ${bitrate}kbps
latency: ${latency}ms
averageJitterBufferDelay: ${averageJitterBufferDelay}ms
packetLossRate: ${(packetLossRate * 100).toFixed(3)}%
`);
}, 1000);
6.14、RTCPeerConnection 、RTCDataChannel 状态说明
- RTCPeerConnection:通过 connection.pc 获取
//打印ICE的状态
console.log("RTCPeerConnection.connectionState", connection.pc.connectionState);
属性 | 说明 | 类型 |
---|---|---|
new | 表示至少有一个 ICE 连接(RTCIceTransport (en-US) 或 RTCDtlsTransport (en-US) 对象)处于 new 状态,并且没有连接处于以下状态: connecting、checking、failed、disconnected,或者这些连接都处于 closed 状态 | string |
connecting | 表示至少有一个 ICE 连接处于正在建立连接 | string |
connected | 表示每一个 ICE 连接要么正在使用(connected 或 completed 状态),要么已被关闭(closed 状态);并且,至少有一个连接处于 connected 或 completed 状态 | string |
disconnected | 表示至少有一个 ICE 连接处于 disconnected 状态,并且没有连接处于 failed、connecting 或 checking 状态。 | string |
failed | 表示至少有一个 ICE 连接处于 failed 的状态 | string |
closed | 表示 RTCPeerConnection 已关闭 | string |
- RTCDataChannel:通过 connection.dc 获取
//打印数据通道状态
console.log("RTCDataChannel.readyState", connection.dc.readyState);
属性 | 说明 | 类型 |
---|---|---|
connecting | 正在初始化连接,正在启动 | string |
open | 数据通道已经建立 | string |
closing | 正在关闭数据通道 | string |
closed | 数据通道已经关闭 | string |
6.15、WebRTC 调试参考
- 方法1:在任一浏览器标签地址打开 "chrome://webrtc-internals",即可看到 WebRTC 的完整调试数据
- 方法2:调用Launcher的showDashboard方法,显示仪表盘推流性能数据(注:空数据情况下记得调用launcher的toggleStatistics方法切换状态)
- 方法3:调用Launcher的exportLog方法,导出webrtc-internals统计数据(注:空数据情况下记得调用launcher的toggleStatistics方法切换状态)
6.16、键盘事件自定义
- 需求背景:应用需支持组合键输入
- 启动器初始化的时候取消挂载默认键盘事件,OnPlay 执行自定义的键盘事件。参考如下:
let connection, player, ele;
const launch = new Launcher({
baseOptions: { address, appKey, startType: 1 },
extendOptions: {
eventOption: {
enableKeyBoard: false,
},
onPlay: () => {
createKeyboardStream(ele);
},
},
});
await launch.automata(document.body);
connection = launch.getConnection();
player = launch.getPlayer();
ele = player.videoElement;
- 然后自定义键盘事件,比如实现组合键,参考代码如下:
function createKeyboardStream(target) {
//防止按到tab键切换dom
target.tabIndex = -1;
const onInteract = () => target.focus();
target.addEventListener("touchstart", onInteract, { passive: true });
target.addEventListener("mouseenter", onInteract);
target.addEventListener("click", onInteract);
target.addEventListener("keydown", (e) => {
e.preventDefault();
let capsLock = e.getModifierState("CapsLock");
let shift = e.getModifierState("Shift");
//includes " ~ ( ) _ { } : " < > ? "
let signalKeyCode = [186, 187, 188, 189, 190, 191, 192, 219, 220, 221, 222];
if (
(capsLock || shift) &&
(signalKeyCode.includes(e.keyCode) ||
(e.keyCode >= 65 && e.keyCode <= 90) ||
(e.keyCode >= 48 && e.keyCode <= 57))
) {
connection.send(new TextInput(e.key, true).dumps(), true);
return;
}
connection.send(Keyboard.fromKeyboardEvent(e, true).dumps(), true);
});
target.addEventListener("keyup", (e) => {
e.preventDefault();
connection.send(Keyboard.fromKeyboardEvent(e, false).dumps(), true);
});
}
6.17、自定义工具栏(实现上报功能)
- 可以自行新增工具栏,以上报功能为例
- 启动器初始化的时候指定上报栏目。参考如下:
const launch = new Launcher({
baseOptions: { address, appKey, startType: 1 },
extendOptions: {
toolOption: {
extendTools: [
{
icon: "data: image/png; base64xxxxxxxxx",
text: "上报错误",
platform: 3,
order: 0,
onClick: () => {
API.post("/api/feed/xxxxxx", { error: "xxxxxx" });
},
},
],
},
},
});
6.18、重置超时时间
- 需求背景:在不需要与应用交互的前提下,防止长时间未交互导致超时断连。
import { Launcher } from 'live-cat'
const appKey = 'xxxxxxxxxxxxxxxxxxx'
const address = 'https://app.3dcat.live'
const bootstrap = async () => {
try {
const launch = new Launcher({
baseOptions: {
address,
appKey,
startType: 1,
},
})
const player = document.querySelector('body')
document.querySelector('body').style.width = '100vw'
document.querySelector('body').style.height = '100vh'
await launch.automata(player)
const connection = launch.getConnection()
//重置超时时间
connection.send('', true)
} catch (error) {
console.error(error)
}
}
window.addEventListener('DOMContentLoaded', () => {
if (
navigator.userAgent.includes('miniProgram') ||
navigator.userAgent.includes('MicroMessenger')
) {
//微信浏览器/微信小程序环境
document.addEventListener('WeixinJSBridgeReady', bootstrap, false)
} else {
bootstrap()
}
})
6.19、移动端以及PC端中文输入方案
- 背景:无法直接输入中文以及移动端无法直接输入
- 前置条件:应用接入通信插件:UE应用通信/U3D应用通信
- 主要思路:当用户点击应用输入框,应用发送指令给web端,web端唤起输入框(自定义),输入完毕通过
TextInput
接口传输到云端。
- 当用户点击应用输入框,应用发送指令(比如"input_focus")给web端
- web端接收到"input_focus"指令,唤起自定义的输入框,示例图如下:
3.输入完毕通过TextInput
接口传输到云端
import { Launcher } from 'live-cat'
import { TextInput } from "ray-streaming" // "live-cat"生产依赖
const appKey = 'xxxxxxxxxxxxxxxxxxx'
const address = 'https://app.3dcat.live'
const bootstrap = async () => {
try {
const launch = new Launcher({
baseOptions: {
address,
appKey,
startType: 1,
},
})
const player = document.querySelector('body')
document.querySelector('body').style.width = '100vw'
document.querySelector('body').style.height = '100vh'
await launch.automata(player)
const connection = launch.getConnection()
//应用->web端
connection.event.interaction.on(msg => {
console.log('收到应用发来的消息:', msg)
if(msg === 'input_focus'){
// show input modal
}
})
} catch (error) {
console.error(error)
}
}
//文本传输事件
const sendText = () =>{
connection.send(new TextInput("input框输入的内容").dumps());
}
window.addEventListener('DOMContentLoaded', () => {
if (
navigator.userAgent.includes('miniProgram') ||
navigator.userAgent.includes('MicroMessenger')
) {
//微信浏览器/微信小程序环境
document.addEventListener('WeixinJSBridgeReady', bootstrap, false)
} else {
bootstrap()
}
})
6.20、上传资产文件并且获取文件同步状态(云端应用加载上传文件)
- 背景:需要通过web端上传资产文件并且保证能够实时加载文件
- 前置条件:
- 接入3dcat后端接口文档
- 接入raysync-sdk(web端镭速上传服务)
- 接入live-cat(web端3dcat推流服务),具体文档:live-cat接入文档
- 步骤:
在3dcat管理平台开启Access Key选项,获取AccessKey ID以及AccessKey Secret。
用户通过Java/NodeJS等后端服务接入3cat后端接口,首先需要鉴权签名,需要对接的接口有:
搭建上传页面(demo里面的pulic/index.html),接入raysync-sdk。
- 通过A接口获取上传地址相关
- 通过B接口获取用户名
- 通过C接口获取应用云端根目录以及appKey
- 通过D接口轮询上传文件的同步状态
- 通过E接口获取AccessKey ID
- 搭建推流页面(demo里面的pulic/player.html),接入live-cat。
- 完整demo(NodeJS/express注释版本)下载
6.21、自定义loading、loadingbar、toolbar图标
const loadingImage = document.createElement('img')
loadingImage.style.height = '200px'
loadingImage.style.marginBottom = '40px'
loadingImage.src = 'string'
const toolBarLogoImage = document.createElement('img')
toolBarLogoImage.style.height = '60px'
toolBarLogoImage.style.width = '60px'
toolBarLogoImage.style.borderRadius = '50%'
toolBarLogoImage.src = 'string'
const loadingBarImage = document.createElement('img')
toolBarLogoImage.style.width = '24px'
toolBarLogoImage.src = 'string'
const launch = new Launcher({
baseOptions: { address, appKey, startType: 1 },
extendOptions: {
loadingImage,
loadingBarImage,
toolBarLogoImage,
},
});
// or
const launch = new Launcher({
baseOptions: { address, appKey, startType: 1 },
extendOptions: {
loadingImage:"string",// url or base64
loadingBarImage:"string",// url or base64
toolBarLogoImage:"string",// url or base64
},
});
- 6.1、快速接入
- 6.1.1、公有化
- 6.1.2、私有化
- 6.2、错误代码使用说明
- 6.3、接入消息双向通信(ue4、unity)
- 6.4、进入有效画面后初始化
- 6.5、接入微信小程序
- 6.6、投屏交互接入
- 6.7、替换加载页面各阶段提示
- 6.8、去除/替换加载流程 UI 页面
- 6.9、接入多点触控需求(UE4)
- 6.10、浏览器直接接入 sdk
- 6.11、聚焦应用输入框,发送内容(复制粘贴)
- 6.12、调节码率
- 6.13、获取连接状态具体说明
- 6.14、RTCPeerConnection 、RTCDataChannel 状态说明
- 6.15、WebRTC 调试参考
- 6.16、键盘事件自定义
- 6.17、自定义工具栏(实现上报功能)
- 6.18、重置超时时间
- 6.19、移动端以及PC端中文输入方案
- 6.20、上传资产文件并且获取文件同步状态(云端应用加载上传文件)
- 6.21、自定义loading、loadingbar、toolbar图标