跳到主要内容
版本:4.0.0

实时播放示例 (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 端点进行交互。

基本流程

live-sequence.png

  1. 首先客户端要连接到GT-Streaming流媒体服务的 WebStock API 端点
  2. 调用登录 StrmWsApi.login() API,完成登录
  3. 调用 打开实时音视频播放 StrmWsApi.liveOpen() 接口
  4. 等待流媒体已准备好的通知
  5. 得到媒体可用通知后,用媒体播放器打开接口返回的媒体地址,即开始播放
  6. 要关闭播放时
    • 关闭播放器
    • 调用 StrmWsApi.releaseStrmReq() 接口

以下详述本示例通过连接测试服务器进行测试的各个步骤:

连接终端

首先,需要准备一台符合JT/T 1076标准的终端,记下终端的通讯标识号(即协议中所说的终端手机号)。将终端的服务器地址设为:n11.gratour.info,端口号设为: 7510。 如果终端设置了ACC ON才响应音视频指令的话,则终端应接上ACC ON信号线。

等待终端上线

启动终端后,接下来我们就是等待终端上线。可到 https://wx.gratour.info:3001/ 查看是否已经上线。如下图示,我们在在线终端列表中看到终端8888000001,表示该终端识别号为8888000001的终端已经上线。

term-online.png

测试服务器上有两台模拟终端,终端识别号分别为88880000018888000002。我们也可以不使用真实终端,使用模拟终端来进行测试。本例直接使用模拟终端来进行测试。

连接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.readytrue 才可以开始播放。 如果readyfalse,则要等待媒体状态变更通知(见下)。大多数情况下,返回结果的 ready 属性 为 false。只有当该通道之前已经有其它用户(或会话) 打开了媒体流的情况下,ready 才为 true

媒体状态变更通知的处理

我们在前文创建 StrmWsApi 对象后,通过StrmWsApi.addStrmNotifListener()调用添加了一个媒体状态变更通知处理器(回调函数)。在这个处理器里, 我们主要关注 StrmNotif.ACT__strmReady 通知,当收到已经准备好的通知后,如果之前还没有播放(liveOpen返回的readyfalse时),则创建播放器并开始播放:

/**
* 收到 流媒体状态通知 时的处理
* @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()接口返回后,我们就得到服务器预分配的播放地址,但要在返回结果的readytrue或收到 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 = '已经关闭';
}