跳到主要内容
版本:1.1.7

以异步模式打开实时播放

当应用要同时打开多个终端通道时,应该使用异步模式打开。本示例演示异步实时播放的一般编程模式。可结合示例页面:live-example-2(请使用Chrome/Firefox/Edge等浏览器打开),及其代码:live-example-2.zip进行本文的阅读。

本示例使用以下到库:

同、异步模式的主要差异

异步模式和同步模式的主要差异在于:

模式接口参数返回后码流是否已可用可确认终端不在线需要订阅WebSocket通知
同步async: false可确认可选
异步async: true不一定不可确认必需

以下逐一解释这些差异:

  1. 调用POST /strm/live/open接口打开通道时,我们需要传递一个true值作为async的参数,告诉服务端我们要使用异步模式:
        axios.post(apiUrlPrefix() + 'strm/live/open', {
'simNo': simNo(),
'channel': channelId(),
'dataType': dataType(),
'codeStream': codeStream(),
'proto': 0,
'async': true // <--- 异步模式
}, {
headers: {
'X-Auth-Token': token
}
}).then(function (resp) {
// ...
}).catch(function (err) {
// ...
});
  1. 异步模式的POST /strm/live/open在向媒体服务分配一个播放请求后、下发终端指令以前返回,而同步模式则在码流已经准备好或确认终端不在线时返回。 异步模式接口调用返回后,服务端继续进行终端交互,指示终端向媒体服务推流。故当POST /strm/live/open调用返回后,如果我们立即创建播放器并加载码流, 播放器通常处于等待码流的状态,除非通道已经有其他用户正在打开。
        axios.post(apiUrlPrefix() + 'strm/live/open', {
'simNo': simNo(),
'channel': channelId(),
'dataType': dataType(),
'codeStream': codeStream(),
'proto': 0,
'async': true
}, {
headers: {
'X-Auth-Token': token
}
}).then(function (resp) {
// ...

// 可以直接创建播放器并等待码流。播放器通常会处于等待码流的状态,除非通道已经有其他用户正在打开
createPlayerAndLoad(data.playUrl, data.mediaTyp);

// ...
}).catch(function (err) {
// ...
});
  1. 同步模式调用POST /strm/live/open时,服务端在完成终端交互后返回,当出现终端不在线时,接口返回-27: 没有可用的连接(终端不在线)的错误码。 而在异步模式下,服务端在进行终端交互之前返回,故而接口调用无法确认终端是否在线,此时需要通过websocket API来订阅相关通知来作相应的处理。
  2. 异步模式需要订阅WebSocket API的user/queue/strm消息来接收通道状态变化的相关通知,并作相应的处理,主要收到closed事件时,我们要做关闭播放器、停止保持定时器等工作:
                    // 判断事件类型并作相应的处理
switch (notif.act) {
// ...

case StrmNotif.ACT__strmClosed:
if (notif.closeCause !== StrmNotif.CLOSE_CAUSE__clientReq) {
errLog(closeReasonOf(notif.closeCause));
} else
infoLog('音视频通道已经关闭');

// 收到 `closed`事件通知时,关闭播放器
stop();
break;
}

订阅WebSocket通知

WebSocket接口地址

WebSocket地址可从HTTP接口地址中派生,只需将HTTP接口地址的https变为wss或将http变为ws,然后在后面加上/ws__token参数即可。如API地址为:

https://n11.gratour.info:8388/v1

则WebSocket接口地址为:

wss://n11.gratour.info:8388/v1/ws?__token=Ge4E1xNHSfW8NYa0VJe48A

上面地址示例中的__token参数的参数值为POST /login接口返回的authToken属性的值。

见示例代码中的connectWs()函数:

    /**
* 连接到websocket并监听 StrmNotif 事件
*/
function connectWs() {
// 从HTTP API地址中派生 websocket 接口地址
let url = apiUrlPrefix();
url = url.replace('https://', 'wss://').replace('http://', 'ws://');
if (!url.endsWith('/v1/'))
url += 'v1/';
url += 'ws?__token=' + token; // 用 `POST /login`接口返回的`authToken`作为`__token`参数
stomp = Stomp.client(url);

// ...
}

有关WebSocket的基本约定,请参考:基本约定

连接WebSocket的时机和重连

一旦登录成功,我们就可以连接WebSocket并订阅需要监听的事件:

        infoLog('登录到:' + apiUrlPrefix());
axios.post(apiUrlPrefix() + 'login', loginReq)
.then(function (resp) {
token = apiCheck(resp).data[0].authToken;

// 更新最后成功调用API的时间
lastApiTime = new Date().getTime();

// 连接websocket进行事件监听
connectWs();

// ...
})
.catch(function (err) {
// ...
});

如果WebSocket因网络原因断开时,WebSocket客户端的错误处理函数将被调用,我们可以在这个处理函数中进行一个延时的重连处理。 但在重连处理中有一点需要注意的是,WebSocket会话的有效性依赖于token的有效性。当token失效时(如长时间无HTTP API调用),WebSocket会话也会失效,WebSocket客户端也会发生错误,从而触发错误处理函数被调用。 因此,在WebSocket客户端的错误处理函数中,要先确定token的有效性,只在认为token仍有效的情况下才尝试重连。

由于token的失效主要原因是长时间无HTTP API调用,所以为了确定token有效性,可以引入一个记录最后一次成功调用HTTP API的时间的变量,每次成功调用API后,将变量设为当前时间;判断时,如果当前时间距离上次成功调用API时间超过10分钟(此时间值可配置),则认为token已经无效。

示例代码中的lastApiTime即为记录最后一次成功调用API的时间变量:

    /**
* 连接到websocket并监听 StrmNotif 事件
*/
function connectWs() {
// ...
stomp.connect(
// 连接参数,未使用
{},

// 连接成功回调
function (_) {
// ...
},

// 错误回调
function (err) {
const message = 'Websocket发生错误:' + err.toString();
errLog(message);
disconnectWs();
if (token) {
const sinceLastApiCall = new Date().getTime() - lastApiTime;
if (sinceLastApiCall < 10 * 60 * 1000) {
// 如果上次成功调用API是在10分钟前,则token依然是有效的,此种情况下我们重连websocket(5秒后)
setTimeout(() => connectWs(), 5000);
}
}
}
);
}

订阅流媒体状态通知

WebSocket通知的订阅地址不是固定地址,其中一个包含token占位符,实际使用时替换为POST /login接口返回的authToken的值。例如,流媒体状态通知的订阅地址模式为:

/user/{token}/queue/strm

假设登录后得到的authTokenGe4E1xNHSfW8NYa0VJe48A,那么订阅地址为:

/user/Ge4E1xNHSfW8NYa0VJe48A/queue/strm

代码中的相关部分为:

    /**
* 连接到websocket并监听 StrmNotif 事件
*/
function connectWs() {
// 从HTTP API地址中派生 websocket 接口地址
let url = apiUrlPrefix();
url = url.replace('https://', 'wss://').replace('http://', 'ws://');
if (!url.endsWith('/v1/'))
url += 'v1/';
url += 'ws?__token=' + token; // 用 `POST /login`接口返回的`authToken`作为`__token`参数
stomp = Stomp.client(url);

stomp.connect(
// 连接参数,未使用
{},

// 连接成功回调
function (_) {
stompConnected = true;

// 队列名称构成方式:/user/{token}/queue/strm
const queueName = '/user/' + token + '/queue/strm';

// 订阅 StrmNotif 事件
stomp.subscribe(queueName, (frame) => {
// 事件处理
// ...
});
},

// 错误回调
function (err) {
// ...
}
);
}