# 一、接入流程
# 1.获取WXGameJetPack实例,并设置相应回调
// 一般在MonoBehaviour子类的Start()回调里执行以下代码
void Start()
{
...
WXGameJetPack mJetPackEngine = WXGameJetPack.GetEngine();;
// 必须实现的回调
mJetPackEngine.livePush.OnCheckSupportResult += OnCheckSupportResult;
mJetPackEngine.livePush.OnStartLiveEvent += OnStartLive;
mJetPackEngine.livePush.OnStopLiveEvent += OnStopLive;
mJetPackEngine.livePush.OnOpenUrlEvent += OnOpenUrl;
// 扩展事件回调
mJetPackEngine.livePush.OnExtraEvent += OnExtra;
//挂件启动关闭事件回调
mJetPackEngine.livePush.OnLiveWidgetLoadedEvent += OnWebViewLoadedEvent;
mJetPackEngine.livePush.OnLiveWidgetClosedEvent += OnWebViewClosedEvent;
// MSDK 还需要再实现以下2个回调
mJetPackEngine.livePush.OnRequireAuthorizeEvent += OnRequireAuthorize;
mJetPackEngine.livePush.OnStartChannelLiveEvent += OnStartChannelLive;
...
}
# 2.直播入口检测(在游戏登录操作后执行)
// 注册检测结果回调
string userInfo = "{\"openid\":\"" + mOpenId + "\",\"appid\":\"" + mWxAppId + "\",\"renderer\":\"" + SystemInfo.graphicsDeviceName +"\"}";
mJetPackEngine.livePush.OnCheckSupportResult += OnCheckSupportResult; // 可以在登录注销时执行反注OnCheckSupportResult
mJetPackEngine.livePush.CheckSupport(userInfo, mTestEnv);
# 3.处理OnCheckSupportResult回调结果
void OnCheckSupportResult(int result, string message) {
// result == 0或1时,显示直播入口,并调用Init接口
if (result == 0||result == 1) {
// 初始化sdk
mJetPackEngine.Init(gameName, appId, ilinkAppId);
// 显示直播入口
mLiveBtn.SetActive(true);
}
}
# 4.拉起挂件直播
//点击一键直播按钮
mLiveBtn.GetComponent<Button>().onClick.AddListener(() =>
{
//支持通过“StartChannelLive”跳转视频号,需要保证MSDK版本>=3.3.26
mJetPackEngine.livePush.EnableStartChannelLive(true);
// 建议设置SDK内部处理麦克风权限
mJetPackEngine.livePush.AuthorizeInnerAudio(true);
//如果没有接入第三方语音组件(如GVoice),可以启用SDK内部的麦克风采集
mJetPackEngine.livePush.SwitchInnerAudioRecord(mInnerRecord);
//拉起直播挂件
mJetPackEngine.livePush.LoadLiveWebView();
});
# 5.设置直播事件轮训
void Update()
{
...
if (mJetPackEngine != null)
{
//设置直播事件轮询
mJetPackEngine.Poll();
}
}
# 6.处理打开链接事件
void OnOpenUrl(string url, int screenType, bool isFullScreen, bool isBrowser)
{
//MSDK V5
MSDKWebView.OpenUrl(url, (MSDKWebViewOrientation)screenType, isFullScreen,true, "",isBrowser);
//MSDK V3
// WGOpenUrl(liveMsg.url, (MSDKWebViewOrientation)liveMsg.screenType, liveMsg.isFullScreen);
// 没有MSDK的情况,请使用游戏内置WebView或者使用系统浏览器
...
}
# 7.处理开播事件
void OnStartLive(bool isAudioPermissionGranted)
{
// 执行挂载WXGameLiveEvent组件
if (_wxgameliveObject != null)
{
GameObject.Destroy(_wxgameliveObject);
}
_wxgameliveObject = new GameObject(_gameObjectName);
_wxgameliveObject.AddComponent<WXGameLiveEvent>();
// 接入GVoice的游戏,开始直播时,需要设置GVoice加入语音房间,专门用于采集主播语音
if (isAudioPermissionGranted) {
mVoiceEngine.invoke(4, 1, 0, null); // 启用OnRecordingData回调,务必在同意麦克风权限后调用
mVoiceEngine.SetMode(Mode.RealTime);
mVoiceEngine.EnableMultiRoom(true);
// 加入直播专用的语音房间,mLiveRoomName命名可以是前缀+时戳,确保每个房间名都是唯一的
mVoiceEngine.JoinTeamRoom(mLiveRoomName, 5000);
} else {
// 如果没有设置EnableInnerAudioPermissionAuth(true), 此时需要申请麦克风权限,等用户同意麦克风权限后再调用
// mVoiceEngine.invoke(4, 1, 0, null)
// 如果已经设置了EnableInnerAudioPermissionAuth(true),说明用于拒绝了麦克风权限,需要提示用户无法采集主播语音
}
}
# 8.处理关播事件
void OnStopLive()
{
// 卸载WXGameLiveEvent组件
if (_wxgameliveObject != null)
{
GameObject.Destroy(_wxgameliveObject);
_wxgameliveObject = null;
}
// 停止直播时,退出GVoice语音房间
if (mAudioPermissionGranted)
{
mVoiceEngine.invoke(4, 0, 0, null); // 禁用OnRecordingData回调
mVoiceEngine.EnableRoomMicrophone(mLiveRoomName, false);
mVoiceEngine.QuitRoom(mLiveRoomName, 5000);
}
}
# 9.扩展事件回调
void OnExtra(string param)
{
var dict = Json.Deserialize(param) as Dictionary<string,object>;
string type = dict["type"].ToString();
if (type.Equals("micPermission"))
{
// 隐私合规需要,在弹出权限框时需对为什么申请该权限做出说明
// 显示权限说明提示, 在OnStartLive回调中关闭
}
//接入了活动页面需实现
else if(type.Equals("finishLoadActivity")){
//发送玩家信息
}
}
# 10.处理视频号授权事件(接入MSDK的游戏需要处理)
void OnRequireAuthorize()
{
// MSDK V5
MSDKLogin.ChannelPermissionAuth(MSDKChannel.WeChat,"snsapi_channels_livestream");
// 老接口
// MSDKLogin.Login(MSDKChannel.WeChat,"snsapi_channels_livestream");
// MSDK V3
// WGPlatform.Instance.WGChannelPermissionAuth(ePlatform.ePlatform_WeixinVideoLive, "snsapi_channels_livestream");
}
# 11.处理跳转微信事件(接入MSDK的游戏需要处理)
void OnStartChannelLive(string liveJsonInfo)
{
// MSDK V5
var reqInfo = new MSDKFriendReqInfo
{
Type = (int) FriendReqType.Friend_REQ_WX_CHANNEL_START_LIVE,
ExtraJson = liveJsonInfo,
};
MSDKFriend.SendMessage(reqInfo, MSDKChannel.WeChat);
// MSDK V3
//WGPlatform.Instance.WGSendToWXChannelStartLive(extraJson,null);
}
# 12.微信传入信息处理(接入MSDK的游戏需要处理)
- 监听游戏被第三方应用拉起时,传递过来的参数,确认是被微信视频号拉起时,调用SetMessageFromWeChat接口将相关参数传给直播SDK。
MSDKLogin.LoginBaseRetEvent += OnLoginBaseRetEvent;
void OnLoginBaseRetEvent(MSDKBaseRet baseRet)
{
if (baseRet.MethodNameId == (int) MSDKMethodNameID.MSDK_CHANNEL_PERMISSION_AUTH)
{
// 视频号授权同意的事件
var dict = Json.Deserialize(baseRet.ExtraJson) as Dictionary<string,object>;
string tdiAuthBufferBase64 = dict["tdiAuthBuffer"].ToString();
byte[] tdiAuthBuffer = System.Convert.FromBase64String(tdiAuthBufferBase64);
mJetPackEngine.livePush.AuthorizeFinish(baseRet.RetCode, tdiAuthBuffer);
}
else if (baseRet.MethodNameId == (int)MSDKMethodNameID.MSDK_LOGIN_WAKEUP)
{
// 游戏被唤醒
var dict = Json.Deserialize(baseRet.ExtraJson) as Dictionary<string,object>;
string _params = dict["params"].ToString();
var wxDict = Json.Deserialize(_params) as Dictionary<string,object>;
#if UNITY_ANDROID
mMsgFromWx = wxDict["_wxappextendobject_extInfo"].ToString();
#elif UNITY_IOS
mMsgFromWx = wxDict["messageExt"].ToString();
#endif
// 确认是被微信拉起
if (mMsgFromWx.Contains("WeChatLive_ShiPinHao"))
{
// 将微信的参数设置给SDK
mJetPackEngine.livePush.SetMessageFromWeChat(mMsgFromWx);
}
}
......
}
# 13.游戏生命周期埋点
//游戏退出
void OnApplicationQuit()
{
Debug.Log("====OnApplicationQuit");
WXGameJetPack.GetEngine().AppWillTerminate();
}
//游戏切到前后台等情况(个别安卓机型会出现回调问题,安卓机型推荐在安卓Activity生命周期进行回调)
void OnApplicationPause(bool pauseStatus)
{
Debug.Log("OnApplicationPause pauseStatus:" + pauseStatus);
if (pauseStatus)
{
WXGameJetPack.GetEngine().AppDidEnterBackground();
}
else
{
WXGameJetPack.GetEngine().AppWillEnterForeground();
}
}
- Android端还需要在游戏的Activity中埋点如下接口
import com.tencent.wx.gamelive.WxGameLifeCycle;
public class MainActivity extends UnityPlayerActivity {
...
@Override
public void onResume() {
super.onResume();
...
WxGameLifeCycle.onResume();
}
@Override
public void onPause() {
super.onPause();
...
WxGameLifeCycle.onPause();
}
@Override
public void onDestroy() {
super.onDestroy();
WxGameLifeCycle.onDestroy();
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
...
WxGameLifeCycle.onActivityResult(requestCode,resultCode, data);
}
@Override
public void onRequestPermissionsResult(int arg0, String[] arg1, int[] arg2)
{
super.onRequestPermissionsResult(arg0, arg1, arg2);
...
WxGameLifeCycle.onRequestPermissionsResult(arg0, arg1, arg2);
}
@Override
public void onWindowFocusChanged(boolean hasFocus)
{
super.onWindowFocusChanged(hasFocus);
...
WxGameLifeCycle.onWindowFocusChanged(hasFocus);
}
}
# 14.GVoice 语音采集
// mVoiceEngine 为GVoice实例:GCloudVoice.GetEngine()
// 设置队友语音回调
mVoiceEngine.OnPlayingData += OnPlayingData;
// invoke(5, 1, 1, null)必须在Init之前调用,作用为开启OnPlayingData回调
mVoiceEngine.invoke(5, 1, 1, null);
mLiveEngine.LogToSDK("调用【GVoiceEngine invoke(5, 1, 1, null)】"); //一键开播SDK v1.3.4才支持,若无LogToSDK接口则可删除此行代码
mVoiceEngine.Init();
// 设置主播语音回调
mVoiceEngine.OnRecording += OnRecordingData;
// 设置进房结果回调
mVoiceEngine.OnJoinRoomCompleteEvent += OnJoinRoomCompleteEvent;
// 设置退房结果回调
mVoiceEngine.OnQuitRoomCompleteEvent += OnQuitRoomCompleteEvent;
// 设置实时语音模式
mVoiceEngine.SetMode(Mode.RealTime);
// 队友语音回调函数
[AOT.MonoPInvokeCallback(typeof(IGCloudVoiceEngine.PlayingDataHandler))]
static void OnPlayingData(IntPtr buf, int len, int nSampleRate, int nChannel)
{
// 发送队友语音
if (isLiving)
{
mLiveEngine.OnGVoiceTeamPcmS16(buf, len, nSampleRate, nChannel, false);
}
}
// 主播语音回调函数
[AOT.MonoPInvokeCallback(typeof(IGCloudVoiceEngine.RecordingDataHandler))]
static void OnRecordingData(IntPtr buf, int len) {
// 发送主播语音
if (isLiving)
{
mLiveEngine.OnGVoicePcmS16(buf, len, 16000, 1, false);
}
}
// 进房结果回调函数
void OnJoinRoomCompleteEvent(CompleteCode code, string roomName, int memberID)
{
roomName = roomName.Trim('\0');
if (code == CompleteCode.JoinRoomSucc)
{
// 进房成功后,打开麦克风
mVoiceEngine.OpenMic();
if (roomName.Equals(mLiveRoomName)) {
// 进入直播语音房间,打开扬声器,用于播放背景音乐
mVoiceEngine.OpenSpeaker();
// 发送gvoice注册信息及房间信息,用于在后台往指定的语音房间注入背景音乐
string gvoice_notify = "{\"type\": \"gvoice_notify\", \"room_name\": \"" + roomName + "\", \"game_id\": \"" + mGVoiceId + "\", \"game_key\": \"" + mGVoiceKey + "\"}";
mLiveEngine.PostGameMessage(gvoice_notify);
}
}
}
// 退房结果回调函数
void OnQuitRoomCompleteEvent(CompleteCode code, string roomName, int memberID)
{
roomName = roomName.Trim('\0');
if (code == CompleteCode.QuitRoomSucc)
{
if (noRoom)
{
// 全部语音房间都退出后,关闭麦克风和扬声器
// 游戏可能加入了多个语音房间
mVoiceEngine.CloseMic();
mVoiceEngine.CloseSpeaker();
}
}
}
# 15.微信OpenSDK 相关逻辑
对于接入了微信OpenSDK的外部游戏,需要再原生平台做如下修改:
- Android
// Step 1 : 传入WXAPI句柄
api = WXAPIFactory.createWXAPI(this, Constants.WX_APP_ID, true);
api.registerApp(Constants.WX_APP_ID);
WxApiHelper.sharedInstance().setWxApi(api);
// Step 2: 处理微信返回的票据
// 在WXEntryAcivity的onResp回调中加入:
@Override
public void onResp(BaseResp resp) {
...
if (resp.getType() == ConstantsAPI.COMMAND_LAUNCH_WX_SEND_TDI_AUTH) {
SendTdiAuth.Resp authResp = (SendTdiAuth.Resp)resp;
// 完成视频号授权登录
WXLivePush.sharedInstance().authorizeFinish(authResp.errCode, authResp.tdiAuthBuffer);
}
finish();
}
// 处理微信传递过来的开播票据
@Override
public void onReq(BaseReq req) {
switch (req.getType()) {
.....
case ConstantsAPI.COMMAND_SHOWMESSAGE_FROM_WX:
ShowMessageFromWX.Req wxReq = (ShowMessageFromWX.Req) req;
WXMediaMessage wxMsg = wxReq.message;
WXAppExtendObject obj = (WXAppExtendObject) wxMsg.mediaObject;
if (obj.extInfo.contains("WeChatLive_ShiPinHao")) {
// 实现微信拉起游戏自动开播
WXLivePush.sharedInstance().onMessageFromWeChat(obj.extInfo);
}
finish();
break;
.....
default:
break;
}
finish();
}
- iOS
// Step 1 : 设置WXApiDelegate
// 在实现WXApiDelegate的类里设置
[WXGameLiveAdapterBridge shareInstance].delegate = self;
// Step 2 : 处理微信传递过来的数据
-(void) onReq:(BaseReq*)req {
if ([req isKindOfClass:[LaunchFromWXReq class]]) {
LaunchFromWXReq *launchFromWXReq = (LaunchFromWXReq*)req;
NSString* messageExt = launchFromWXReq.message.messageExt;
if ([messageExt containsString:@"WeChatLive_ShiPinHao"]) {
// 实现微信拉起游戏自动开播
[[WXGameJetPack getJetPackEngine].livePush onMessageFromWeChat:messageExt];
}
}
}
-(void) onResp:(BaseResp*)resp {
....
if ([resp isKindOfClass:[SendTdiAuthResp class]]) {
// 完成视频号授权登录
SendTdiAuthResp *authResp = (SendTdiAuthResp *)resp;
[[WXGameJetPack getJetPackEngine].livePush authorizeFinish:authResp.errCode tdiAuthBuffer:authResp.tdiAuthBuffer];
}
}
注意事项
- 使用Unity内置音频引擎时,启用需要在Player Settings --> Other Settings --> Scripting Define Symbols中添加UNITY_AUDIO_CAPTURE,如果Unity中的AudioListener是全局的,并不是每个Scene放置一个独立的AudioListener的话,则需要再添加UNITY_GLOBAL_AUDIO_LISTENER。
- 使用Fmod for Unity音频引擎时,启用需要在Player Settings --> Other Settings --> Scripting Define Symbols中添加FMOD_AUDIO_CAPTURE。
- 使用Wwise情况下,sdk提供了wwise的录音插件:WGLive,游戏侧需将该插件挂在到wwise的工程的master总线上后,重新build sound bank资源。
- GVoice的时序需要特别注意:
mVoiceEngine.invoke(4, 1, 0, null) 必须在麦克风权限同意后再调用 mVoiceEngine.invoke(5, 1, 1, null) 必须在mVoiceEngine.Init()之前调用
- 游戏注销当前登录的帐号时需要关闭当前的直播,调用如下接口:
mLiveEngine.PostGameMessage("{\"type\": \"finishLive\"}");
# 二、一键开播完整流程图
# 三、相关文档
- WXGameJetpack接入QA文档
https://docs.qq.com/doc/DRlh6RkhBUXpNdlVL?_t=1697005219736
- WXGamejetpack API文档
https://docs.qq.com/doc/DRnpxa3NlSUd6TXpi