实时播放示例 (WebSocket API)
本示例用Vue 3 + TypeScript + Vite框架构造一个简单的实时音视频播放应用,演示调用WebSocket API
的一般使用方法。
阅读本文的同时,可结合查看示例页面:ws-example-1-live(请使用Chrome/Firefox/Edge等浏览器打开),
及其代码:ws-example-live-1.zip。
本文使用 strm-js
库中的 StrmWsApi
类来与流媒体服务的 WebSocket API
端点进行交互。
基本流程
- 首先客户端要连接到GT-Streaming流媒体服务的 WebStock API 端点
- 调用登录
StrmWsApi.login()
API,完成登录 - 调用 打开实时音视频播放
StrmWsApi.liveOpen()
接口 - 等待流媒体已准备好的通知
- 得到媒体可用通知后,用媒体播放器打开接口返回的媒体地址,即开始播放
- 要关闭播放时
- 关闭播放器
- 调用
StrmWsApi.releaseStrmReq()
接口
以下详述本示例通过连接测试服务器进行测试的各个步骤:
连接终端
首先,需要准备一台符合JT/T 1076标准的终端,记下终端的通讯标识号(即协议中所说的终端手机号)。将终端的服务器地址设为:n11.gratour.info
,端口号设为: 7510
。
如果终端设置了ACC ON
才响应音视频指令的话,则终端应接上ACC ON
信号线。
等待终端上线
启动终端后,接下来我们就是等待终端上线。可到 https://wx.gratour.info:3001/ 查看是否已经上线。如下图示,我们在在线终端
列表中看到终端8888000001
,表示该终端识别号为8888000001
的终端已经上线。
测试服务器上有两台模拟终端,终端识别号分别为8888000001
和8888000002
。我们也可以不使用真实终端,使用模拟终端来进行测试。本例直接使用模拟终端来进行测试。
连接WebSocket API端点,并登录
终端上线后,我们就可以通过WebSocket API来对终端进行操作。本示例所使用的测试服务器的WebSocket API 端点地址为: wss://n11.gratour.info:7211/ws2 。
API操作的第一步,构造连接参数(主要是API端点地址)和构造 StrmWsApi
对象;构造完 StrmWsApi
,紧接着添加媒体状态通知监听器,再调用登录接口 StrmWsApi.login()
:
/**
* 连接服务器并登录
*/
function login() {
// 连接配置
const config = new StrmWsApiConfig();
config.wsApiUrl = wsUrl.value; // wsApiUrl 是必要参数
connectState.value = ConnStates.connecting;
// 如果此前连接过,则断开之前的连接
if (strmWsApi) {
strmWsApi.close()
}
// 创建 StrmWsApi 对象
strmWsApi = new StrmWsApi(config);
// 添加 媒体状态变更通知 监听器
strmWsApi.addStrmNotifListener(onStrmNotif);
// 登录
infoLog(`尝试登录到: ${config.wsApiUrl}`);
strmWsApi.login(username, password)
.then(
// login API 登录成功后,返回 GnssLoginResult 对象
(_: GnssLoginResult) => {
connectState.value = ConnStates.connected;
infoLog('登录成功');
loggedIn.value = true;
}
)
.catch((err) => {
connectState.value = ConnStates.unConnected;
errLog('登录时遇到错误:' + err.toString());
alert(err.toString());
});
}
调用打开实时音视频接口
登录成功并连接websocket后,再调用 StrmWsApi.liveOpen()
接口,请求打开实时音视频:
/**
* 打开实时音视频
*/
function liveOpen() {
if (connectState.value !== ConnStates.connected || !strmWsApi) {
const message = '请先登录';
alert(message);
return
}
if (simNo.value.length === 0) {
alert('请输入终端识别号');
return;
}
// 准备参数
const params = new GnssOpenLiveParams();
params.simNo = simNo.value; // 终端识别号
params.channel = channel.value; // 通道号
params.dataType = selectedDataType.value; // 数据类型
params.codeStream = selectedCodeStrm.value; // 码流代码
params.async = true; // 采用异步方式
params.proto = StrmConsts.PROTO__FLV; // 使用 FLV 格式
// 调用打开实时音视频接口
infoLog('尝试打开实时音视频:strm/liveOpen');
strmWsApi.liveOpen(params)
.then(
// liveOpen 接口成功后,返回结果
(reply: ApiReply<GnssOpenStrmResult>) => {
openStrmResult = reply.data![0];
// 得到请求ID(reqId)后,保存起来,后续关闭通道时要用到
reqId.value = openStrmResult.reqId;
stateText.value = '流已经创建';
infoLog(`流已经创建:reqId=${openStrmResult.reqId}`);
// 此时还不一定能直接播放,要检查返回结果的 `ready` 属性。
// 如果`ready` == true(流已经准备好),且播放器尚未创建,则创建播放器并播放
if (openStrmResult.ready && !player) {
infoLog(`流已经准备好:${params.simNo}/${params.channel}`)
createPlayerAndLoad(openStrmResult); // 创建播放器并播放
}
}
)
.catch((err: any) => {
const msg = `打开实时音视频播放时遇到错误:${err.toString()}`;
errLog(msg);
alert(msg);
});
}
StrmWsApi.liveOpen()
是一个异步函数,成功时会返回一个结果。得到成功结果后,要判断结果的 ready
属性,只有 GnssOpenStrmResult.ready
为 true
才可以开始播放。
如果ready
为 false
,则要等待媒体状态变更通知(见下)。大多数情况下,返回结果的 ready
属性 为 false
。只有当该通道之前已经有其它用户(或会话)
打开了媒体流的情况下,ready
才为 true
。
媒体状态变更通知的处理
我们在前文创建 StrmWsApi
对象后,通过StrmWsApi.addStrmNotifListener()
调用添加了一个媒体状态变更通知处理器(回调函数)。在这个处理器里,
我们主要关注 StrmNotif.ACT__strmReady
通知,当收到已经准备好的通知后,如果之前还没有播放(liveOpen
返回的ready
为false
时),则创建播放器并开始播放:
/**
* 收到 流媒体状态通知 时的处理
* @param {StrmNotif} strmNotif 通知
*/
function onStrmNotif(strmNotif: StrmNotif) {
switch (strmNotif.act) {
case StrmNotif.ACT__strmReady:
if (strmNotif.simNo === simNo.value && strmNotif.chan === channel.value && !player) {
infoLog(`流已经准备好:${strmNotif.simNo}/${strmNotif.chan}`)
createPlayerAndLoad(strmNotif);
}
break;
case StrmNotif.ACT__cmdSent:
infoLog(`指令已经下发到:${strmNotif.simNo}/${strmNotif.chan}`);
break;
case StrmNotif.ACT__cmdFailed:
errLog(`指令失败:${strmNotif.simNo}/${strmNotif.chan}`);
break;
}
}
创建播放器并播放
liveOpen()
接口返回后,我们就得到服务器预分配的播放地址,但要在返回结果的ready
为true
或收到 StrmNotif.ACT__strmReady
通知时,才开始播放。
播放器可以提前创建,也可以到真正需要播放时才创建。为简单起见,我们这里采用真正需要播放时才创建的策略,代码很简单:
/**
* 播放器对象
*/
let player: PlayerWrapper | null = null;
/**
* 创建播放器并加载音视频
* @param n 创建参数。GnssOpenStrmResult 或 StrmNotif 类型
*/
function createPlayerAndLoad(n: GnssOpenStrmResult | StrmNotif) {
// video 元素
const mediaElmt = videoElmtRef.value as HTMLMediaElement;
player = PlayerWrapper.createPlayer(
playerContainer, // 回调接口
mediaElmt, // 媒体元素
StrmConsts.PROTO__FLV, // 播放的客户端协议(格式)
n.mediaTyp!, // 媒体类型(`av`, `v`, `a`之一)
n.playUrl! // 播放地址
);
// 加载并播放
player.load();
}
这里,使用strm-js
包中提供的简单播放器封装(PlayerWrapper
),该封装从各个具体播放器中抽象出基本的播放器操作,支持FLV/HLS两种客户端协议(格式)。
使用时需要向PlayerWrapper.createPlayer()
方法提供一个回调处理对象,这个回调处理对象继承 PlayerContainer
接口,提供几个相关的回调方法,供播放器封装在相应的情况下调用。
客户端需要实现该回调接口,以下是示例程序的简单实现:
/**
* 播放器回调接口
*/
const playerContainer = new PlayerContainer(
// id() 方法,播放器的ID,如一个页面内有多个时,用此ID区分。主要用于调试。
() => 'test',
// isOpenRequested() 回调方法,返回是否正在请求打开流媒体或已经打开流媒体
() => !!openStrmResult,
// onPlayerError() 回调方法,当 PlayerWrapper 发生错误时,此方法被 PlayerWrapper 调用。实现类通常在此方法中在UI上提示用户。
(err: string) => {
errLog(`播放时遇到错误:${err.toString()}`);
},
// onPlaying() 回调方法,当开始播放时,此方法被 PlayerWrapper 调用。
() => {
infoLog('开始播放');
},
// onClose() 回调方法,当播放正常或异常结束时,此方法 PlayerWrapper 调用。实现类通常在此方法中做一些清理工作。
() => {
// nop
}
);
结束播放
结束播放时,可先关闭播放器,然后调用关闭媒体请求接口 StrmWsApi.releaseStrmReq()
,这样整个播放过程就结束了。
/**
* 销毁播放器并释放流请求
*/
function stop() {
// 关闭、销毁播放器
if (player) {
player.stop();
player = null;
}
// 释放流请求
if (strmWsApi && loggedIn.value && reqId.value) {
strmWsApi.releaseStrmReq(reqId.value);
reqId.value = undefined;
}
stateText.value = '已经关闭';
}