微信小程序开发需要基于微信提供的开发者工具与 SDK。如果开发者对小程序开发流程不熟悉,建议先系统学习:微信小程序开发官方文档 。
由于微信官方文档比较详细,本文对小程序开发流程中的框架说明、API 调用、组件使用等,不再赘述,而是重点介绍如何使用 ZEGO SDK 开发出支持音视频直播的微信小程序。
SDK 集成指引详见:微信小程序 SDK集成指引
SDK 提供的 API 说明详见:微信小程序 SDK API 说明
小程序开发主要用到 web 开发知识( js、html、css )。
微信小程序中的推拉流功能,需要用到微信提供的 live-player
live-pusher
标签。其他的常规组件同原生 App 开发类似,此处不一一介绍。
live-player 是微信提供的支持实时音视频播放的组件,官方介绍详见:组件介绍
开发者创建组件成功后,需要在 js 文件中,调用 API 操作对应的组件来实现需求,官方 API 详见: API 说明
即构音视频云小程序中,创建 live-player 的演示源码如下:
@ZegoLive/pages/liveroom/room/room.wxml@
<live-player
autoplay
wx:if="{{item.playUrl}}"
id="{{item.streamID}}"
mode="RTC"
object-fit="fillCrop"
min-cache="0.1"
max-cache="0.3"
src="{{item.playUrl}}"
debug="{{pushConfig.showLog}}"
bindstatechange="onPlayStateChange"
bindnetstatus="onPlayNetStateChange"
binderror="error">
<cover-view class='character' style='padding: 0 5px;'>{{item.streamID}}</cover-view>
</live-player>
请注意:
live 模式主要用于直播类场景,比如赛事直播、在线教育、远程培训等等。该模式下,小程序内部的模块会优先保证观看体验的流畅,通过调整 min-cache 和 max-cache 属性,您可以调节观众(播放)端所感受到的时间延迟的大小,文档下面会详细介绍这两个参数。
RTC 则主要用于双向视频通话或多人视频通话场景,比如金融开会、在线客服、车险定损、培训会议 等等。在此模式下,对 min-cache 和 max-cache 的设置不会起作用,因为小程序内部会自动将延迟控制在一个很低的水平(500ms 左右)。
live-pusher 是微信提供的支持实时音视频录制的组件,官方介绍详见:组件介绍
开发者创建组件成功后,需要在 js 文件中,调用 API 操作对应的组件来实现需求,官方 API 详见:API 说明
即构音视频云小程序中,创建 live-pusher 的演示源码如下:
@ZegoLive/pages/liveroom/room/room.wxml@
<live-pusher
wx:if="{{pushUrl}}"
id="video-livePusher"
mode="RTC"
url="{{pushUrl}}"
min-bitrate="{{pushConfig.minBitrate}}"
max-bitrate="{{pushConfig.maxBitrate}}"
aspect="{{pushConfig.aspect}}"
beauty="{{pushConfig.isBeauty}}"
muted="{{pushConfig.isMute}}"
background-mute="true"
debug="{{pushConfig.showLog}}"
bindstatechange="onPushStateChange"
bindnetstatus="onPushNetStateChange">
<cover-view class='character' style='padding: 0 5px;'>{{isPublishing ? "我(" + publishStreamID + ")": ""}}</cover-view>
</live-pusher>
请注意:
SD、HD 和 FHD 主要用于直播类场景,比如赛事直播、在线教育、远程培训等等。SD、HD 和 FHD 分别对应三种默认的清晰度。该模式下,小程序会更加注重清晰度和观看的流畅性,不会过分强调低延迟,也不会为了延迟牺牲画质和流畅性。
RTC 则主要用于双向视频通话或多人视频通话场景,比如金融开会、在线客服、车险定损、培训会议 等等。该模式下,小程序会更加注重降低点到点的时延,也会优先保证声音的质量,在必要的时候会对画面清晰度和画面的流畅性进行一定的缩水。
详细讲解请参考 2.6 小节。
详细讲解请参考 2.6 小节。
@ZegoLive/pages/liveroom/room/room.js@
// 声明变量
var ZegoSDK = require("../../../js/jZego-wx-1.0.2.js");
var zg;
// 初始化实例
zg = new ZegoSDK.ZegoClient();
// 配置必要参数
zg.config({
appid: appid, // 必填,应用id,请从 ZEGO 控制台-https://console.zego.im 获取
idName: idName, // 必填,用户自定义id,全局唯一
nickName: nickName, // 必填,用户自定义昵称
remoteLogLevel: 1, // 上传日志最低级别,建议跟 logLevel 一致
logLevel: 1, // 日志级别,debug:0,info:1,warn:2,error:3,report:99,disable:100(数字越大,日志越少),建议选择 1
server: server // 必填,接入服务器地址,请从 ZEGO 控制台-https://console.zego.im 获取
logUrl: logUrl // 必填,logServer 地址,请从 ZEGO 控制台-https://console.zego.im 获取
});
登录 token 的获取详见后文 安全方案
中的 房间登录安全
小节。
即构小程序中演示源码片段如下:
@ZegoLive/pages/liveroom/room/room.js@
// 获取登录 token
getLoginToken: function () {
var self = this;
const requestTask = wx.request({
url: 'xxxx', // 该接口由开发者后台自行实现,开发者的 Token 从各自后台获取
data: {
app_id: self.data.appID,
id_name: self.data.userID,
},
header: {
'content-type': 'text/plain'
},
success: function (res) {
console.log(">>>[liveroom-room] get login token success. token is: " + res.data);
if (res.statusCode != 200) {
return;
}
zg.setUserStateUpdate(true);
self.loginRoom(res.data, self);
},
fail: function (e) {
console.log(">>>[liveroom-room] get login token fail, error is: ")
console.log(e);
}
});
},
登录房间成功是后续所有操作的前提。即构音视频云小程序中演示源码片段如下,仅供参考:
@ZegoLive/pages/liveroom/room/room.js@
zg.login(self.data.roomID, self.data.loginType == "anchor" ? 1 : 2, token, function (streamList) {
// 登录成功处理
console.log('>>>[liveroom-room] login succeeded');
}, function (err) {
// 登录失败处理
console.log('>>>[liveroom-room] login failed, error is: ', err);
});
登录错误码列表如下:
错误码 | 含义 |
---|---|
ZegoClient.Success | 成功 |
ZegoClient.Error.Param | 参数错误 |
ZegoClient.Error.Timeout | 超时 |
ZegoClient.Error.Network | 网络错误 |
ZegoClient.Error.Kickout | 用户被踢 |
ZegoClient.Error.Server | 服务返回错误 |
ZegoClient.Error.Unknown | 未知错误 |
单主播直播时,一个房间内仅有一个主播,主播不会与观众进行互动。
主播登录房间成功后,根据业务逻辑准备推流。使用 SDK 推流播放需要遵循如下步骤:
startPublishingStream
获取 streamID 对应的推流地址onStreamUrlUpdate
中获推流地址wx.createLivePusherContext
创建 live-pusher
,将步骤 3 中获取的推流地址设置为 live-pusher
的 url
,然后调用 live-pusher
的 start()
录制视频演示源码片段如下,仅供参考:
@ZegoLive/pages/liveroom/room/room.js@
// 1/2. 主播登录房间成功后触发推流,调用 SDK 的 startPublishingStream 获取 streamID 对应的推流地址
zg.login(self.data.roomID, self.data.loginType == "anchor" ? 1 : 2, token, function (streamList) {
// 主播登录成功即推流
if (self.data.loginType == 'anchor') {
console.log('>>>[liveroom-room] anchor startPublishingStream, publishStreamID: ' + self.data.publishStreamID);
zg.setPreferPublishSourceType(1); // 0:推流到 CDN,观众拉流延迟在 2 秒左右;1:推流到 ZEGO 服务器,延迟在 400ms 左右
zg.startPublishingStream(self.data.publishStreamID, '');
}
}, function (err) {
console.log('>>>[liveroom-room] login failed, error is: ', err);
});
// 3. 在 SDK 的回调 onStreamUrlUpdate 中获取推流地址
// type: {play: 0, publish: 1};
zg.onStreamUrlUpdate = function (streamid, url, type) {
console.log(">>>[liveroom-room] zg onStreamUrlUpdate, streamId: " + streamid + ', type: ' + (type == 0 ? 'play' : 'publish') + ', url: ' + url);
...
};
// 4. 调用微信小程序的 wx.createLivePusherContext 创建 live-pusher ,将步骤 3 中获取的推流流地址设置为 live-player 的 url,然后调用 live-pusher 的 start 录制视频
setPushUrl: function (url) {
console.log('>>>[liveroom-room] setPushUrl: ', url);
var self = this;
...
self.setData({
pushUrl: url,
pusherVideoContext : wx.createLivePusherContext("video-livePusher", self),
}, function () {
self.data.pusherVideoContext.stop();
self.data.pusherVideoContext.start();
});
},
观众登录房间成功后,根据业务逻辑准备拉流。使用 SDK 拉流播放需要遵循如下步骤:
startPlayingStream
获取 streamID 对应的播放地址onStreamUrlUpdate
中获取拉流地址wx.createLivePlayerContext
创建 live-player
,将步骤 3 中获取的推流地址设置为 live-player 的 src
,然后调用 live-player
的 play()
播放视频。此步骤也可以设置 live-player
为 autoplay
,此时播放器会自动播放,无需再手动调用 play()
。演示源码片段如下,仅供参考:
@ZegoLive/pages/liveroom/room/room.js@
// 1. 登录后拉流
zg.login(self.data.roomID, self.data.loginType == "anchor" ? 1 : 2, token, function (streamList) {
// 房间内已经有流,拉流
self.startPlayingStreamList(streamList);
}, function (err) {
console.log('>>>[liveroom-room] login failed, error is: ', err);
});
// 2. 通过 SDK 获取 streamID 对应的播放地址
startPlayingStreamList: function (streamList) {
var self = this;
...
// 设置拉流目标地址,可选,0:auto;1:从 bgp 拉流
zg.setPreferPlaySourceType(1);
// 获取每个 streamID 对应的拉流 url
var playStreamList = self.data.playStreamList;
for (var i = 0; i < streamList.length; i++) {
var streamID = streamList[i].stream_id;
// 调用 SDK 的 startPlayingStream 获取 streamID 对应的播放地址
zg.startPlayingStream(streamID);
}
},
// 3. 在 SDK 的回调 onStreamUrlUpdate 中获取拉流地址
// type: {play: 0, publish: 1};
zg.onStreamUrlUpdate = function (streamid, url, type) {
console.log(">>>[liveroom-room] zg onStreamUrlUpdate, streamId: " + streamid + ', type: ' + (type == 0 ? 'play' : 'publish') + ', url: ' + url);
...
};
// 4. 调用微信小程序的 wx.createLivePlayerContext 创建 live-player ,将步骤 3 中获取的拉流地址设置为 live-player 的 src,然后调用 live-player 的 play() 播放视频。此步骤也可以设置 live-player 为 autoplay,此时播放器会自动播放,无需再手动调用 play()
setPlayUrl: function (streamid, url, self) {
...
// 相同 streamid 的源不存在,创建新 player
if (!isStreamRepeated) {
streamInfo['streamID'] = streamid;
streamInfo['playUrl'] = url;
streamInfo['playContext'] = wx.createLivePlayerContext(streamid, self);
self.data.playStreamList.push(streamInfo);
}
...
self.setData({
playStreamList: self.data.playStreamList,
}, function(){});
},
微信小程序会在 live-player
和 live-pusher
的 bindstatechange
绑定的方法中通知出推拉流状态事件,开发者需要:
bindstatechange
绑定的回调函数中,调用 SDK 提供的 API updatePlayerState
将推流事件透传给 SDK onPlayStateUpdate
onPublishStateUpdate
回调中处理播推、拉流的开始、失败状态演示源码片段如下,仅供参考:
@ZegoLive/pages/liveroom/room/room.js@
// live-player 绑定的拉流事件
onPlayStateChange(e) {
// 透传拉流事件给 SDK,type 0 拉流
zg.updatePlayerState(e.currentTarget.id, e, 0);
},
// 服务端主动推过来的 流的播放状态, 视频播放状态通知;type: { start:0, stop:1};
zg.onPlayStateUpdate = function (updatedType, streamID) {
console.log(">>>[liveroom-room] zg onPlayStateUpdate, " + (updatedType == 0 ? 'start ' : 'stop ') + streamID);
};
// live-pusher 绑定推流事件
onPushStateChange(e) {
// 透传推流事件给 SDK,type 1 推流
zg.updatePlayerState(this.data.publishStreamID, e, 1);
},
// 推流后,服务器主动推过来的,流状态更新;type: { start: 0, stop: 1 },主动停止推流没有回调,其他情况均回调
zg.onPublishStateUpdate = function (type, streamid, error) {
console.log('>>>[liveroom-room] zg onPublishStateUpdate, streamid: ' + streamid + ', type: ' + (type == 0 ? 'start' : 'stop') + ', error: ' + error);
};
另外,小程序会在 live-player
和 live-pusher
的 bindnetstatus
绑定的方法中通知出推拉流网络事件,开发者也需要在对应的小程序回调中,调用 updatePlayerNetStatus
将推流事件透传给 SDK。
演示源码片段如下,仅供参考:
// live-player 绑定网络状态事件
onPlayNetStateChange(e) {
// 透传网络状态事件给 SDK,type 0 拉流
zg.updatePlayerNetStatus(e.currentTarget.id, e, 0);
},
// SDK 拉流网络质量回调
zg.onPlayQualityUpdate = function (streamId, streamQuality) {
...
},
// live-pusher 绑定网络状态事件
onPushNetStateChange(e) {
//透传网络状态事件给 SDK,type 1 推流
zg.updatePlayerNetStatus(this.data.publishStreamID, e, 1);
},
// SDK 推流网络质量回调
zg.onPublishQualityUpdate = function (streamId, streamQuality) {
...
},
停止推拉流,开发者需要:
stopPublishingStream(streamid)
stopPlayingStream(streamid)
清空推、拉流状态live-pusher
和 live-player
提供的 stop()
停止推、拉流请注意,上述第 1 点一定要处理,否则可能导致 SDK 状态异常!
演示源码片段如下,仅供参考:
// 停止拉流
zg.stopPlayingStream(this.data.playStreamList[i]['streamID']);
this.data.playStreamList[i]['playContext'] && this.data.playStreamList[i]['playContext'].stop();
// 停止推流
zg.stopPublishingStream(this.data.publishStreamID);
this.data.pusherVideoContext.stop();
多主播直播是主播与观众连麦,使观众也成为主播的互动功能。视频会议也可通过该模式实现。多主播直播较单主播直播多出了连麦信令交互过程。推流、拉流流程同单主播基本一致,本节不再赘述。
观众申请连麦交互的主要步骤是:
requestJoinLive
onRecvJoinLiveRequest
respondJoinLive
endJoinLive
主播邀请连麦交互的主要步骤是:
inviteJoinLive
onRecvJoinLiveRequest
respondJoinLive
endJoinLive
开发者可按需调用 SDK 连麦相关 API。即构音视频云小程序中演示源码片段如下,仅供参考:
@ZegoLive/pages/liveroom/room/room.js@
// 观众请求连麦
requestJoinLive: function () {
var self = this;
// 观众正在连麦时点击,则结束连麦
if (self.data.isPublishing) {
zg.endJoinLive(self.data.anchorID, function(result, userID, userName) {
console.log('>>>[liveroom-room] endJoinLive, result: ' + result);
}, null);
// 停止推流
zg.stopPublishingStream(this.data.publishStreamID);
this.setData({
isPublishing: false,
publishTitle : "未推流",
});
this.data.pusherVideoContext.stop();
return;
}
// 观众未连麦,点击开始推流
console.log('>>>[liveroom-room] audience requestJoinLive');
zg.requestJoinLive(this.data.anchorID, null, null, function(result, userID, userName) {
console.log('>>>[liveroom-room] requestJoinLive, result: ' + result);
if (result == false) {
wx.showToast({
title: '主播拒绝连麦',
icon: 'none',
duration: 2000
})
} else {
wx.showToast({
title: '主播同意连麦,准备推流',
icon: 'none',
duration: 2000
});
// 主播同意连麦后,观众开始推流
console.log('>>>[liveroom-room] startPublishingStream, userID: ' + userID + ', publishStreamID: ' + self.data.publishStreamID);
zg.setPreferPublishSourceType(1); // 0:推流到 CDN,观众拉流延迟在 2 秒左右;1:推流到 ZEGO 服务器,延迟在 400ms 左右
zg.startPublishingStream(self.data.publishStreamID, '');
}
},
// 收到连麦请求
zg.onRecvJoinLiveRequest = function(requestId, fromUserId, fromUsername, roomId) {
console.log('>>>[liveroom-room] onRecvJoinLiveRequest, roomId: ' + roomId + 'requestUserId: ' + fromUserId + ', requestUsername: ' + fromUsername);
var content = '观众 ' + fromUsername + ' 请求连麦,是否允许?';
wx.showModal({
title: '提示',
content: content,
success: function(res) {
if (res.confirm) {
console.log('>>>[liveroom-room] onRecvJoinLiveRequest accept join live');
zg.respondJoinLive(requestId, true); // true:同意;false:拒绝
} else if (res.cancel) {
console.log('>>>[liveroom-room] onRecvJoinLiveRequest refuse join live');
zg.respondJoinLive(requestId, false); // true:同意;false:拒绝
}
}
});
};
混流可以把多路直播的流根据配置信息,输出成一路流。开发者可按需调用 SDK 混流相关 API。演示源码片段如下,仅供参考:
//更新混流配置
updateMixStream: function () {
console.log('>>>[liveroom-room] updateMixStream');
var self = this;
if (self.data.loginType !== 'anchor') {
return;
}
var width = 360;
var height = 640;
var mixConfig = {};
//混流输出流ID
mixConfig.outputStreamId = "mix-" + self.data.publishStreamID;
//混流输出帧率
mixConfig.outputFps = 15;
//混流输出码率
mixConfig.outputBitrate = 800 * 1000;
//混流输出分辨率
mixConfig.outputWidth = width;
mixConfig.outputHeight = height;
//混流输入流信息
mixConfig.streamList = [];
var margin = 0;
if (self.data.isPublishing) {
var streamInfo = {
streamId: self.data.publishStreamID,
top: margin,
left: margin,
bottom: height - margin,
right: width - margin
};
mixConfig.streamList.push(streamInfo);
}
for (var i = 0; i < self.data.playStreamList.length; i++) {
var streamId = self.data.playStreamList[i].streamID;
var streamInfo = {
streamId: streamId,
top: 0,
left: 0,
bottom: 0,
right: 0
};
if (mixConfig.streamList.length == 0) {
streamInfo.bottom = height;
streamInfo.right = width;
}
else if (mixConfig.streamList.length == 1) {
streamInfo.top = parseInt(height * 2 / 3);
streamInfo.left = parseInt(width * 2 / 3);
streamInfo.bottom = height;
streamInfo.right = width;
}
else if (mixConfig.streamList.length == 2) {
streamInfo.top = parseInt(height * 2 / 3);
streamInfo.left = 0;
streamInfo.bottom = height;
streamInfo.right = parseInt(width / 3);
}
mixConfig.streamList.push(streamInfo);
}
zg.updateMixStream(mixConfig, function(mixStreamId, mixStreamInfo) {
console.log(">>>[liveroom-room] updateMixStream success");
var rtmpUrls = mixStreamInfo["rtmpUrls"];
for (var i = 0; i < rtmpUrls.length; i++) {
console.log(">>>[liveroom-room] updateMixStream mix Rtmp: " + rtmpUrls[i]);
}
wx.showModal({
title: '提示',
content: '混流成功',
showCancel: false
});
}, function(error, info) {
console.error(">>>[liveroom-room] updateMixStream error " + error.code);
if (info) {
for (var i = 0; i < info.length; i++) {
console.error(">>>[liveroom-room] input stream not exist " + info[i]);
}
}
wx.showModal({
title: '提示',
content: '混流失败',
showCancel: false
});
});
},
//停止混流
stopMixStream: function () {
console.log(">>>[liveroom-room] stopMixStream");
var self = this;
var mixConfig = {};
mixConfig.outputStreamId = "mix-" + self.data.publishStreamID;
zg.stopMixStream(mixConfig, undefined, undefined);
},
<live-pusher/>
切后台,会停止视频采集,但不会禁用音频采集。开发者可以通过 background-mute
属性来设置是否禁用音频采集。
<live-player/>
切后台,会停止视频播放,默认退后台静音。
请注意:
生成 Token 信息需要业务后台自行开发。
{
"ver": 1,
"hash": xxxxx,
"nonce": xxxxx,
"expired": xxxxx,
}
字段说明如下:
参数名 | 类型 | 说明 |
---|---|---|
ver | int | 版本号,填 1 即可 |
hash | String | hash = MD5(app_id+app_sign_32+id_name+nonce+expired) |
nonce | String | 随机串,需要保证同一 user_id 在失效时间内不重复,建议按 guid 生成 |
expired | int64 | 失效时间,unix_timestamp;单位:秒 |
对于 hash 计算中使用字段的更详细说明:
参数名 | 类型 | 说明 |
---|---|---|
app_id | int | ZEGO 分配给客户的 app_id,App 唯一标识 |
app_sign_32 | String | 通过 app_sign 运算获得。算法:剔除 app_sign 里的 "0x", "," 字符后,获取前面 32 字节即为 app_sign_32(具体算法可参考下述代码) |
id_name | String | 与初始化时config里设置的idName保持一致 |
nonce | String | 随机串,与 json 中的 nonce 一致 |
expired | int64 | 失效时间,与 json 中的 expired 一致 |
请注意:
login_token 传输过程中,会经过 base64 加密。
每次登录都要重新获取 login_token。
业务方开发的小程序需要和业务后台建立一种安全通讯和鉴权机制,业务方使用自有的账户体系或第三方认证体系的登录完成后,业务方小程序和业务后台交互获取该 login_token, AppSecret 是存储在业务后台的。
/**
* 拉流端获取登录token
* @param appId 即构分配的appId
* @param appSign 即构分配的appSign
* @param idName 这里的idname需要和websdk前端传入的idname一致,
// 否则校验失败(因为这里的idname是为了校验和前端传进来的idname是否一致)
* @return
*/
function getZeGouToken(appId, appSign, idName) {
let nonce = new Date ().getTime ().toString ();
let time = Math.floor (new Date ().getTime () / 1000 + 30 * 60);
let appSign32 = appSign.replace (/0x/g, '').replace (/,/g, '').substring (0, 32);
console.log ('appSign:' + time + ' ' + appSign32 + ' ' + nonce);
if (appSign32.length < 32) {
console.log ('private sign erro!!!!');
return null;
}
let sourece = md5 (appId + appSign32 + idName + nonce + time);
console.log ('hash:' + sourece);
let jsonStr = JSON.stringify({
'ver': 1,
'expired': time,
'nonce': nonce,
'hash': sourece
});
console.log ('json', jsonStr);
return Buffer.from(jsonStr).toString('base64');
}
func makeTokenSample(appid uint32, app_sign string, idname string, expired_add int64) (ret string, err error){
nonce := UniqueId()
expired := time.Now().Unix() + expired_add //单位:秒
app_sign = strings.Replace(app_sign, "0x","",-1)
app_sign = strings.Replace(app_sign, ",", "", -1)
if len(app_sign) < 32 {
return "", fmt.Errorf("app_sign wrong")
}
app_sign_32 := app_sign[0:32]
source := fmt.Sprintf("%d%s%s%s%d",appid,app_sign_32,idname,nonce,expired)
sum := GetMd5String(source)
token := tokenInfo{}
token.Ver = 1
token.Hash = sum
token.Nonce = nonce
token.Expired = expired
buf, err := json.Marshal(token)
if err != nil {
return "", err
}
encodeString := base64.StdEncoding.EncodeToString(buf)
return encodeString, nil
}
// 获取MD5加密
func GetMd5String(s string) string {
h := md5.New()
h.Write([]byte(s))
return hex.EncodeToString(h.Sum(nil))
}
public function getToken(int $app_id, string $app_sign, string $idname, int $expired_add)
{
$nonce = uniqid();
$expired = time() + $expired_add; //单位:秒
$app_sign = str_replace("0x", "", $app_sign);
$app_sign = str_replace(",", "", $app_sign);
if(strlen($app_sign) < 32) {
return false;
}
$app_sign_32 = substr($app_sign, 0, 32);
$source = $app_id.$app_sign_32.$idname.$nonce.$expired;
$sum = md5($source);
$tokenInfo = [
'ver' => 1,
'hash' => $sum,
'nonce' => $nonce,
'expired' => $expired,
];
$token = base64_encode(json_encode($tokenInfo));
return $token;
}
package demo;
import org.json.JSONObject;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Date;
import java.util.UUID;
import org.apache.commons.codec.binary.Base64;
public class ZegouUtils {
public static void main(String[] args) {
String appid = "0000000000"; //即构分配的appId
String appSign = "0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00"; //即构分配的appSign
String idName = "xxxxxxx"; //业务系统用户唯一标识
String Token = getZeGouToken(appid,appSign,idName);
System.out.println("--Token--:"+Token);
}
/**
* 拉流端获取登录token
* @param appId 即构分配的appId
* @param appSign 即构分配的appSign
* @param idName 业务系统用户唯一标识
* @return
*/
public static String getZeGouToken(String appId,String appSign,String idName){
String nonce= UUID.randomUUID().toString().replaceAll("-", "");
long time=new Date().getTime()/1000+30*60;
String appSign32=new String(appSign.replace("0x", "").replace(",", "").substring(0, 32));
System.out.println("appSign:"+time+" "+appSign32+" "+nonce);
if(appSign32.length()<32){
System.out.println("private sign erro!!!!");
return null;
}
String sourece= getPwd(appId+appSign32+idName+nonce+time);
System.out.println("hash:"+sourece);
JSONObject json=new JSONObject();
json.put("ver", 1);
json.put("hash", sourece);
json.put("nonce", nonce);
json.put("expired",time); //unix时间戳,单位为秒
org.apache.commons.codec.binary.Base64 base64 = new org.apache.commons.codec.binary.Base64();
System.out.println("json"+json.toString());
return base64.encodeAsString(json.toString().getBytes());
}
/**
* 获取MD5加密
* @param pwd 需要加密的字符串
* @return String字符串 加密后的字符串
*/
public static String getPwd(String pwd) {
try {
// 创建加密对象
MessageDigest digest = MessageDigest.getInstance("md5");
// 调用加密对象的方法,加密的动作已经完成
byte[] bs = digest.digest(pwd.getBytes());
// 接下来,我们要对加密后的结果,进行优化,按照mysql的优化思路走
// mysql的优化思路:
// 第一步,将数据全部转换成正数:
String hexString = "";
for (byte b : bs) {
// 第一步,将数据全部转换成正数:
int temp = b & 255;
// 第二步,将所有的数据转换成16进制的形式
// 注意:转换的时候注意if正数>=0&&<16,那么如果使用Integer.toHexString(),可能会造成缺少位数
// 因此,需要对temp进行判断
if (temp < 16 && temp >= 0) {
// 手动补上一个“0”
hexString = hexString + "0" + Integer.toHexString(temp);
} else {
hexString = hexString + Integer.toHexString(temp);
}
}
return hexString;
} catch (NoSuchAlgorithmException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return "";
}
}
onPublishStateUpdate
stateCode 含义如下:
stateCode | 信息 | 含义 | 说明 |
---|---|---|---|
0 | success | 成功 | —— |
-1 | failure | 没有拿到 URL 就开始推流 | 请检查网络是否正常,重新推流 |
11009 | dispatch err | 调度出错 | 请联系 ZEGO 技术支持 |
其他 4 位错误码均为小程序框架的报错,请参考:小程序官方文档
onPlayStateUpdate
stateCode 含义如下:
stateCode | 信息 | 含义 | 说明 |
---|---|---|---|
0 | success | 成功 | —— |
-1 | failure | 没有拿到 URL 就开始拉流 | 请检查网络是否正常,重新拉流 |
11009 | dispatch err | 调度出错 | 请联系 ZEGO 技术支持 |
其他 4 位错误码均为小程序框架的报错,请参考:小程序官方文档
解决方案:
请检查 初始化配置 ZegoClient.config 里面每个参数的类型是否正确,一般情况下都是类型不正确引起的问题。
原因:
观众角色不允许创建房间,也就是这个房间不存在。
解决方案:
1)在 ZegoClient.config 里面的 option.audienceCreateRoom 设置为 TRUE
2)在 ZegoClient.login 里面将 role 设置为 1,主播角色。
原因:计算 token 的时候,算法不对引起,可通过这个链接:http://sig-wstoken.zego.im/tokenindex 来验证是否正确:
首先验证 hash
字段是否有错,这里要注意 id_name
是 string 类型,不是数值类型; expired
是 uninx 的时间戳,不是北京时间。
login_token 由业务侧后台生成后,将 json 里面的字段经过 base64 加密后再下发给小程序端。
详情请参考: 4.1 房间登录安全
微信小程序提供设置清晰度的 mode,无法指定具体的分辨率,码率和帧率,具体可参考:
微信小程序的 LivePusherContext 里面有提供相关的接口,大家可参考:
切换摄像头的示例代码可参考:
ZEGO 分配给开发者的 URL(包含 HTTPS、WSS 协议;config配置方法中server,logUrl),需要在微信公众平台进行“合法域名”配置后,小程序才能正常访问。
微信后台配置地址:微信公众平台 -> 设置 -> 开发设置 -> 服务器域名
。
请开发者将 ZEGO 分配的请求域名,按照协议分类,填到指定的 request合法域名
或者 socket合法域名
中。例如:
联系我们
文档反馈