






curl -X POST \\http://service-mqk0mc83-1257411467.bj.apigw.tencentcs.com/release/register \\-H 'Content-Type: application/json' \\-H 'Cache-control: no-cache' \\-d '{"requestId": "test-regisiter-service","action": "Register","registerRequest": {"operateUin": <operateUin>,"userName": <customedName>,"cosConfig": {"secretId": <CosConfig.secretId>,"secretKey": <CosConfig.secretKey>,"bucket": <CosConfig.bucket>,"region": <CosConfig.region>}}}'
{"requestId": "test-regisiter-service","registerInfo": {"tmpContentId": <tmpContentId>,"tmpSecretId": <tmpSecretId>,"tmpSecretKey": <tmpSecretKey>,"apiGateSecretId": <apiGateSecretId>,"apiGateSecretKey": <apiGateSecretKey>,"demoCosPath": "UIN_demo/run_musicBeat.py","usageDescription": "请从COS桶[CosConfig.bucket]中下载python版本demo文件[UIN_demo/run_musicBeat.py], 替换demo中的输入文件后,执行python run_musicBeat.py","message": "注册成功,感谢注册","createdAt": <createdAt>,"updatedAt": <updatedAt>}}
demoCosPath 目录下生成一个以音乐鼓点识别能力为例的 python 版本的可执行 demo,请在有网络的环境下,执行命令 python run_musicBeat.py 验证。dependencies {// TRTC 精简版 SDK, 包含 TRTC 和直播播放两项功能, 体积小巧implementation 'com.tencent.liteav:LiteAVSDK_TRTC:latest.release'// TRTC 全功能版 SDK, 另含直播、短视频、点播等多项功能, 体积略大// implementation 'com.tencent.liteav:LiteAVSDK_Professional:latest.release'}
defaultConfig {ndk {abiFilters "armeabi-v7a", "arm64-v8a"}}
<uses-permission android:name="android.permission.INTERNET" /><uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /><uses-permission android:name="android.permission.ACCESS_WIFI_STATE" /><uses-permission android:name="android.permission.RECORD_AUDIO" /><uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" /><uses-permission android:name="android.permission.BLUETOOTH" />
targetSdkVersion 为 31 或者目标设备涉及到 Android 12 及更高系统版本,官方要求需要在代码中动态申请 android.permission.BLUETOOTH_CONNECT 权限,以正常使用蓝牙功能,具体信息请参见 蓝牙权限。-keep class com.tencent.** { *; }

// 创建 TRTC SDK 实例(单例模式)TRTCCloud mTRTCCloud = TRTCCloud.sharedInstance(context);// 设置事件监听器mTRTCCloud.addListener(trtcSdkListener);// 来自 SDK 的各类事件通知(比如:错误码,警告码,音视频状态参数等)private TRTCCloudListener trtcSdkListener = new TRTCCloudListener() {@Overridepublic void onError(int errCode, String errMsg, Bundle extraInfo) {Log.d(TAG, errCode + errMsg);}@Overridepublic void onWarning(int warningCode, String warningMsg, Bundle extraInfo) {Log.d(TAG, warningCode + warningMsg);}};// 移除事件监听器mTRTCCloud.removeListener(trtcSdkListener);// 销毁 TRTC SDK 实例(单例模式)TRTCCloud.destroySharedInstance();
public void enterRoom(String roomId, String userId) {TRTCCloudDef.TRTCParams params = new TRTCCloudDef.TRTCParams();// 以字符串房间号为例params.strRoomId = roomId;params.userId = userId;// 从业务后台获取到的 UserSigparams.userSig = getUserSig(userId);// 替换成您的 SDKAppIDparams.sdkAppId = SDKAppID;// 建议均以观众角色进房params.role = TRTCCloudDef.TRTCRoleAudience;// 进房场景须选择 LIVEmTRTCCloud.enterRoom(params, TRTCCloudDef.TRTC_APP_SCENE_LIVE);}
TRTC_APP_SCENE_LIVE。// 进房结果事件回调@Overridepublic void onEnterRoom(long result) {if (result > 0) {// result 代表加入房间所消耗的时间(毫秒)Log.d(TAG, "Enter room succeed");// 开启补黑帧的实验性接口mTRTCCloud.callExperimentalAPI("{\\"api\\":\\"enableBlackStream\\",\\"params\\": {\\"enable\\":true}}");} else {// result 代表进房失败的错误码Log.d(TAG, "Enter room failed");}}
// 切换为主播角色mTRTCCloud.switchRole(TRTCCloudDef.TRTCRoleAnchor);// 切换角色事件回调@Overridepublic void onSwitchRole(int errCode, String errMsg) {if (errCode == TXLiteAVCode.ERR_NULL) {// 设置媒体音量类型mTRTCCloud.setSystemVolumeType(TRTCCloudDef.TRTCSystemVolumeTypeMedia);// 上行本地音频流,设置音质mTRTCCloud.startLocalAudio(TRTCCloudDef.TRTC_AUDIO_QUALITY_MUSIC);}}
// 获取音频特效管理类TXAudioEffectManager mTXAudioEffectManager = mTRTCCloud.getAudioEffectManager();// originMusicId: 自定义原唱音乐标识;originMusicUrl: 原唱音乐资源地址TXAudioEffectManager.AudioMusicParam originMusicParam = new TXAudioEffectManager.AudioMusicParam(originMusicId, originMusicUrl);// 是否将原唱发布到远端(否则仅本地播放)originMusicParam.publish = true;// accompMusicId: 自定义伴奏音乐标识;accompMusicUrl: 伴奏音乐资源地址TXAudioEffectManager.AudioMusicParam accompMusicParam = new TXAudioEffectManager.AudioMusicParam(accompMusicId, accompMusicUrl);// 是否将伴奏发布到远端(否则仅本地播放)accompMusicParam.publish = true;// 开始播放原唱音乐mTXAudioEffectManager.startPlayMusic(originMusicParam);// 开始播放伴奏音乐mTXAudioEffectManager.startPlayMusic(accompMusicParam);// 切换至原唱音乐mTXAudioEffectManager.setMusicPlayoutVolume(originMusicId, 100);mTXAudioEffectManager.setMusicPlayoutVolume(accompMusicId, 0);mTXAudioEffectManager.setMusicPublishVolume(originMusicId, 100);mTXAudioEffectManager.setMusicPublishVolume(accompMusicId, 0);// 切换至伴奏音乐mTXAudioEffectManager.setMusicPlayoutVolume(originMusicId, 0);mTXAudioEffectManager.setMusicPlayoutVolume(accompMusicId, 100);mTXAudioEffectManager.setMusicPublishVolume(originMusicId, 0);mTXAudioEffectManager.setMusicPublishVolume(accompMusicId, 100);
mTXAudioEffectManager.setMusicObserver(musicId, new TXAudioEffectManager.TXMusicPlayObserver() {@Overridepublic void onStart(int id, int errCode) {// 音乐开始播放}@Overridepublic void onPlayProgress(int id, long curPtsMs, long durationMs) {// 根据最新进度和本地歌词进度误差,判断是否需要 seek// 通过发送 SEI 消息传递歌曲进度try {JSONObject jsonObject = new JSONObject();jsonObject.put("musicId", id);jsonObject.put("progress", curPtsMs);jsonObject.put("duration", durationMs);mTRTCCloud.sendSEIMsg(jsonObject.toString().getBytes(), 1);} catch (JSONException e) {e.printStackTrace();}}@Overridepublic void onComplete(int id, int errCode) {// 音乐播放完成}});
// 切换为观众角色mTRTCCloud.switchRole(TRTCCloudDef.TRTCRoleAudience);// 切换角色事件回调@Overridepublic void onSwitchRole(int errCode, String errMsg) {if (errCode == TXLiteAVCode.ERR_NULL) {// 停止播放伴奏音乐mTRTCCloud.getAudioEffectManager().stopPlayMusic(musicId);// 停止本地音频的采集和发布mTRTCCloud.stopLocalAudio();}}// 退出房间mTRTCCloud.exitRoom();// 退出房间事件回调@Overridepublic void onExitRoom(int reason) {if (reason == 0) {Log.d(TAG, "主动调用 exitRoom 退出房间");} else if (reason == 1) {Log.d(TAG, "被服务器踢出当前房间");} else if (reason == 2) {Log.d(TAG, "当前房间整个被解散");}}
onExitRoom 回调通知到您。enterRoom 或切换到其他音视频 SDK,请等待 onExitRoom 回调到来后再执行相关操作。否则可能会遇到例如摄像头、麦克风设备被强占等各种异常问题。public void enterRoom(String roomId, String userId) {TRTCCloudDef.TRTCParams params = new TRTCCloudDef.TRTCParams();// 以字符串房间号为例params.strRoomId = roomId;params.userId = userId;// 从业务后台获取到的 UserSigparams.userSig = getUserSig(userId);// 替换成您的 SDKAppIDparams.sdkAppId = SDKAppID;// 建议均以观众角色进房params.role = TRTCCloudDef.TRTCRoleAudience;// 进房场景须选择 LIVEmTRTCCloud.enterRoom(params, TRTCCloudDef.TRTC_APP_SCENE_LIVE);}// 进房结果事件回调@Overridepublic void onEnterRoom(long result) {if (result > 0) {// result 代表加入房间所消耗的时间(毫秒)Log.d(TAG, "Enter room succeed");} else {// result 代表进房失败的错误码Log.d(TAG, "Enter room failed");}}
TRTC_APP_SCENE_LIVE。@Overridepublic void onUserVideoAvailable(String userId, boolean available) {if (available) {mTRTCCloud.startRemoteView(userId, null);} else {mTRTCCloud.stopRemoteView(userId);}}@Overridepublic void onRecvSEIMsg(String userId, byte[] data) {String result = new String(data);try {JSONObject jsonObject = new JSONObject(result);int musicId = jsonObject.getInt("musicId");long progress = jsonObject.getLong("progress");long duration = jsonObject.getLong("duration");} catch (JSONException e) {e.printStackTrace();}...// TODO 更新歌词控件逻辑:// 根据接收到的最新进度和本地歌词进度误差,判断是否需要 seek 歌词控件...}
// 退出房间mTRTCCloud.exitRoom();// 退出房间事件回调@Overridepublic void onExitRoom(int reason) {if (reason == 0) {Log.d(TAG, "主动调用 exitRoom 退出房间");} else if (reason == 1) {Log.d(TAG, "被服务器踢出当前房间");} else if (reason == 2) {Log.d(TAG, "当前房间整个被解散");}}
// 创建 TRTCCloud 主实例(人声实例)TRTCCloud mTRTCCloud = TRTCCloud.sharedInstance(context);// 创建 TRTCCloud 子实例(音乐实例)TRTCCloud subCloud = mTRTCCloud.createSubCloud();// 主实例(人声实例)进房TRTCCloudDef.TRTCParams params = new TRTCCloudDef.TRTCParams();params.sdkAppId = SDKAppId;params.userId = UserId;params.userSig = UserSig;params.role = TRTCCloudDef.TRTCRoleAnchor;params.strRoomId = RoomId;mTRTCCloud.enterRoom(params, TRTCCloudDef.TRTC_APP_SCENE_LIVE);// 子实例开启手动订阅模式,默认不订阅远端流subCloud.setDefaultStreamRecvMode(false, false);// 子实例(音乐实例)进房TRTCCloudDef.TRTCParams bgmParams = new TRTCCloudDef.TRTCParams();bgmParams.sdkAppId = SDKAppId;// 子实例用户名不能与房间内其他用户重复bgmParams.userId = UserId + "_bgm";bgmParams.userSig = UserSig;bgmParams.role = TRTCCloudDef.TRTCRoleAnchor;bgmParams.strRoomId = RoomId;subCloud.enterRoom(bgmParams, TRTCCloudDef.TRTC_APP_SCENE_LIVE);
// 主实例进房结果事件回调@Overridepublic void onEnterRoom(long result) {if (result > 0) {// 主实例取消订阅子实例发布的音乐流mTRTCCloud.muteRemoteAudio(UserId + "_bgm", true);// 主实例开启补黑帧的实验性接口mTRTCCloud.callExperimentalAPI("{\\"api\\":\\"enableBlackStream\\",\\"params\\": {\\"enable\\":true}}");// 主实例开启合唱模式的实验性接口mTRTCCloud.callExperimentalAPI("{\\"api\\":\\"enableChorus\\",\\"params\\":{\\"enable\\":true,\\"audioSource\\":0}}");// 主实例开启低延时模式的实验性接口mTRTCCloud.callExperimentalAPI("{\\"api\\":\\"setLowLatencyModeEnabled\\",\\"params\\":{\\"enable\\":true}}");// 主实例启用音量大小回调mTRTCCloud.enableAudioVolumeEvaluation(300, false);// 主实例设置全程媒体音量类型mTRTCCloud.setSystemVolumeType(TRTCCloudDef.TRTCSystemVolumeTypeMedia);// 主实例采集和发布本地音频,同时设置音质mTRTCCloud.startLocalAudio(TRTCCloudDef.TRTC_AUDIO_QUALITY_MUSIC);} else {// result 代表进房失败的错误码Log.d(TAG, "Enter room failed");}}// 子实例进房结果事件回调@Overridepublic void onEnterRoom(long result) {if (result > 0) {// 子实例开启合唱模式的实验性接口subCloud.callExperimentalAPI("{\\"api\\":\\"enableChorus\\",\\"params\\":{\\"enable\\":true,\\"audioSource\\":1}}");// 子实例开启低延时模式的实验性接口subCloud.callExperimentalAPI("{\\"api\\":\\"setLowLatencyModeEnabled\\",\\"params\\":{\\"enable\\":true}}");// 子实例设置全程媒体音量类型subCloud.setSystemVolumeType(TRTCCloudDef.TRTCSystemVolumeTypeMedia);// 子实例设置音质subCloud.setAudioQuality(TRTCCloudDef.TRTC_AUDIO_QUALITY_MUSIC);} else {// result 代表进房失败的错误码Log.d(TAG, "Enter room failed");}}
audioSource 参数的不同。private void startPublishMediaToRoom(String roomId, String userId) {// 创建 TRTCPublishTarget 对象TRTCCloudDef.TRTCPublishTarget target = new TRTCCloudDef.TRTCPublishTarget();// 混流后回推到房间target.mode = TRTCCloudDef.TRTC_PublishMixStream_ToRoom;target.mixStreamIdentity.strRoomId = roomId;// 混流机器人用户名不能与房间内其他用户重复target.mixStreamIdentity.userId = userId + "_robot";// 设置转码后的音频流的编码参数(可自定义)TRTCCloudDef.TRTCStreamEncoderParam trtcStreamEncoderParam = new TRTCCloudDef.TRTCStreamEncoderParam();trtcStreamEncoderParam.audioEncodedChannelNum = 2;trtcStreamEncoderParam.audioEncodedKbps = 64;trtcStreamEncoderParam.audioEncodedCodecType = 2;trtcStreamEncoderParam.audioEncodedSampleRate = 48000;// 设置转码后的视频流的编码参数(混入黑帧必填)trtcStreamEncoderParam.videoEncodedFPS = 15;trtcStreamEncoderParam.videoEncodedGOP = 3;trtcStreamEncoderParam.videoEncodedKbps = 30;trtcStreamEncoderParam.videoEncodedWidth = 64;trtcStreamEncoderParam.videoEncodedHeight = 64;// 设置音频混流参数TRTCCloudDef.TRTCStreamMixingConfig trtcStreamMixingConfig = new TRTCCloudDef.TRTCStreamMixingConfig();// 默认情况下填空值即可,代表会混合房间中的所有音频trtcStreamMixingConfig.audioMixUserList = null;// 配置视频混流模板(混入黑帧必填)TRTCCloudDef.TRTCVideoLayout videoLayout = new TRTCCloudDef.TRTCVideoLayout();trtcStreamMixingConfig.videoLayoutList.add(videoLayout);// 开始混流回推mTRTCCloud.startPublishMediaStream(target, trtcStreamEncoderParam, trtcStreamMixingConfig);}
TXLiveBase.setListener(new TXLiveBaseListener() {@Overridepublic void onUpdateNetworkTime(int errCode, String errMsg) {super.onUpdateNetworkTime(errCode, errMsg);// errCode 0: 校时成功且偏差在30ms以内;1: 校时成功但偏差可能在30ms以上;-1: 校时失败if (errCode == 0) {// 校时成功,获取 NTP 时间戳long ntpTime = TXLiveBase.getNetworkTimestamp();} else {// 校时失败,可尝试重新校时TXLiveBase.updateNetworkTime();}}});TXLiveBase.updateNetworkTime();
Timer mTimer = new Timer();mTimer.schedule(new TimerTask() {@Overridepublic void run() {try {JSONObject jsonObject = new JSONObject();jsonObject.put("cmd", "startChorus");// 约定合唱开始时间: 当前 NTP 时间 + 延迟播放时间(例如3秒)jsonObject.put("startPlayMusicTS", TXLiveBase.getNetworkTimestamp() + 3000);jsonObject.put("musicId", musicId);jsonObject.put("musicDuration", subCloud.getAudioEffectManager().getMusicDurationInMS(originMusicUri));mTRTCCloud.sendCustomCmdMsg(1, jsonObject.toString().getBytes(), false, false);} catch (JSONException e) {e.printStackTrace();}}}, 0, 1000);
// 获取音频特效管理类TXAudioEffectManager mTXAudioEffectManager = subCloud.getAudioEffectManager();// originMusicId: 自定义原唱音乐标识;originMusicUrl: 原唱音乐资源地址TXAudioEffectManager.AudioMusicParam originMusicParam = new TXAudioEffectManager.AudioMusicParam(originMusicId, originMusicUrl);// 将原唱音乐发布到远端originMusicParam.publish = true;// 音乐开始播放的时间点(毫秒)originMusicParam.startTimeMS = 0;// accompMusicId: 自定义伴奏音乐标识;accompMusicUrl: 伴奏音乐资源地址TXAudioEffectManager.AudioMusicParam accompMusicParam = new TXAudioEffectManager.AudioMusicParam(accompMusicId, accompMusicUrl);// 将伴奏音乐发布到远端accompMusicParam.publish = true;// 音乐开始播放的时间点(毫秒)accompMusicParam.startTimeMS = 0;// 预加载原唱音乐mTXAudioEffectManager.preloadMusic(originMusicParam);// 预加载伴奏音乐mTXAudioEffectManager.preloadMusic(accompMusicParam);// 延迟播放时间(例如3秒)后开始播放原唱音乐mTXAudioEffectManager.startPlayMusic(originMusicParam);// 延迟播放时间(例如3秒)后开始播放伴奏音乐mTXAudioEffectManager.startPlayMusic(accompMusicParam);// 切换至原唱音乐mTXAudioEffectManager.setMusicPlayoutVolume(originMusicId, 100);mTXAudioEffectManager.setMusicPlayoutVolume(accompMusicId, 0);mTXAudioEffectManager.setMusicPublishVolume(originMusicId, 100);mTXAudioEffectManager.setMusicPublishVolume(accompMusicId, 0);// 切换至伴奏音乐mTXAudioEffectManager.setMusicPlayoutVolume(originMusicId, 0);mTXAudioEffectManager.setMusicPlayoutVolume(accompMusicId, 100);mTXAudioEffectManager.setMusicPublishVolume(originMusicId, 0);mTXAudioEffectManager.setMusicPublishVolume(accompMusicId, 100);
// 约定的合唱开始时间long mStartPlayMusicTs = jsonObject.getLong("startPlayMusicTS");// 当前伴奏音乐的实际播放进度long currentProgress = subCloud.getAudioEffectManager().getMusicCurrentPosInMS(musicId);// 当前伴奏音乐的理想播放进度long estimatedProgress = TXLiveBase.getNetworkTimestamp() - mStartPlayMusicTs;// 当进度差超过50ms,进行修正if (estimatedProgress >= 0 && Math.abs(currentProgress - estimatedProgress) > 50) {subCloud.getAudioEffectManager().seekMusicToPosInMS(musicId, (int) estimatedProgress);}
mTXAudioEffectManager.setMusicObserver(musicId, new TXAudioEffectManager.TXMusicPlayObserver() {@Overridepublic void onStart(int id, int errCode) {// 音乐开始播放}@Overridepublic void onPlayProgress(int id, long curPtsMs, long durationMs) {// 根据最新进度和本地歌词进度误差,判断是否需要 seek// 通过发送 SEI 消息传递歌曲进度try {JSONObject jsonObject = new JSONObject();jsonObject.put("musicId", id);jsonObject.put("progress", curPtsMs);jsonObject.put("duration", durationMs);mTRTCCloud.sendSEIMsg(jsonObject.toString().getBytes(), 1);} catch (JSONException e) {e.printStackTrace();}}@Overridepublic void onComplete(int id, int errCode) {// 音乐播放完成}});
// 子实例关闭合唱模式的实验性接口subCloud.callExperimentalAPI("{\\"api\\":\\"enableChorus\\",\\"params\\":{\\"enable\\":false,\\"audioSource\\":1}}");// 子实例关闭低延时模式的实验性接口subCloud.callExperimentalAPI("{\\"api\\":\\"setLowLatencyModeEnabled\\",\\"params\\":{\\"enable\\":false}}");// 子实例切换为观众角色subCloud.switchRole(TRTCCloudDef.TRTCRoleAudience);// 子实例停止播放伴奏音乐subCloud.getAudioEffectManager().stopPlayMusic(musicId);// 子实例退出房间subCloud.exitRoom();// 主实例关闭补黑帧的实验性接口mTRTCCloud.callExperimentalAPI("{\\"api\\":\\"enableBlackStream\\",\\"params\\": {\\"enable\\":false}}");// 主实例关闭合唱模式的实验性接口mTRTCCloud.callExperimentalAPI("{\\"api\\":\\"enableChorus\\",\\"params\\":{\\"enable\\":false,\\"audioSource\\":0}}");// 主实例关闭低延时模式的实验性接口mTRTCCloud.callExperimentalAPI("{\\"api\\":\\"setLowLatencyModeEnabled\\",\\"params\\":{\\"enable\\":false}}");// 主实例切换为观众角色mTRTCCloud.switchRole(TRTCCloudDef.TRTCRoleAudience);// 主实例停止本地音频采集和发布mTRTCCloud.stopLocalAudio();// 主实例退出房间mTRTCCloud.exitRoom();
public void enterRoom(String roomId, String userId) {TRTCCloudDef.TRTCParams params = new TRTCCloudDef.TRTCParams();// 以字符串房间号为例params.strRoomId = roomId;params.userId = userId;// 从业务后台获取到的 UserSigparams.userSig = getUserSig(userId);// 替换成您的 SDKAppIDparams.sdkAppId = SDKAppID;// 示例以观众角色进房params.role = TRTCCloudDef.TRTCRoleAudience;// 进房场景须选择 LIVEmTRTCCloud.enterRoom(params, TRTCCloudDef.TRTC_APP_SCENE_LIVE);}// 进房结果事件回调@Overridepublic void onEnterRoom(long result) {if (result > 0) {// result 代表加入房间所消耗的时间(毫秒)Log.d(TAG, "Enter room succeed");} else {// result 代表进房失败的错误码Log.d(TAG, "Enter room failed");}}
// 切换为主播角色mTRTCCloud.switchRole(TRTCCloudDef.TRTCRoleAnchor);// 切换角色事件回调@Overridepublic void onSwitchRole(int errCode, String errMsg) {if (errCode == TXLiteAVCode.ERR_NULL) {// 取消订阅主唱子实例发布的音乐流mTRTCCloud.muteRemoteAudio(mBgmUserId, true);// 开启合唱模式的实验性接口mTRTCCloud.callExperimentalAPI("{\\"api\\":\\"enableChorus\\",\\"params\\":{\\"enable\\":true,\\"audioSource\\":0}}");// 开启低延时模式的实验性接口mTRTCCloud.callExperimentalAPI("{\\"api\\":\\"setLowLatencyModeEnabled\\",\\"params\\":{\\"enable\\":true}}");// 设置媒体音量类型mTRTCCloud.setSystemVolumeType(TRTCCloudDef.TRTCSystemVolumeTypeMedia);// 上行本地音频流,设置音质mTRTCCloud.startLocalAudio(TRTCCloudDef.TRTC_AUDIO_QUALITY_MUSIC);}}
TXLiveBase.setListener(new TXLiveBaseListener() {@Overridepublic void onUpdateNetworkTime(int errCode, String errMsg) {super.onUpdateNetworkTime(errCode, errMsg);// errCode 0: 校时成功且偏差在30ms以内;1: 校时成功但偏差可能在30ms以上;-1: 校时失败if (errCode == 0) {// 校时成功,获取 NTP 时间戳long ntpTime = TXLiveBase.getNetworkTimestamp();} else {// 校时失败,可尝试重新校时TXLiveBase.updateNetworkTime();}}});TXLiveBase.updateNetworkTime();
@Overridepublic void onRecvCustomCmdMsg(String userId, int cmdID, int seq, byte[] message) {try {JSONObject json = new JSONObject(new String(message, "UTF-8"));// 匹配合唱信令if (json.getString("cmd").equals("startChorus")) {long startPlayMusicTs = json.getLong("startPlayMusicTS");int musicId = json.getInt("musicId");long musicDuration = json.getLong("musicDuration");// 约定合唱时间和当前时间差值long delayMs = startPlayMusicTs - TXLiveBase.getNetworkTimestamp();}} catch (JSONException e) {e.printStackTrace();}}
if (delayMs > 0) { // 合唱未开始// 开始预加载音乐preloadMusic(musicId, 0L);// 延迟 delayMs 后开始播放音乐startPlayMusic(musicId, 0L);} else if (Math.abs(delayMs) < musicDuration) { // 合唱进行中// 开始播放时间: 时间差值绝对值 + 预加载延迟(例如400ms)long startTimeMS = Math.abs(delayMs) + 400;// 开始预加载音乐preloadMusic(musicId, startTimeMS);// 预加载延迟(例如400ms)后开始播放音乐startPlayMusic(musicId, startTimeMS);} else { // 合唱已结束// 不允许加入合唱}// 预加载音乐public void preloadMusic(int musicId, long startTimeMS) {// musicId: 从合唱信令获取;musicUrl: 对应的音乐资源地址TXAudioEffectManager.AudioMusicParam musicParam = newTXAudioEffectManager.AudioMusicParam(musicId, musicUrl);// 仅本地播放音乐musicParam.publish = false;// 音乐开始播放的时间点(毫秒)musicParam.startTimeMS = startTimeMS;mTRTCCloud.getAudioEffectManager().preloadMusic(musicParam);}// 开始播放音乐public void startPlayMusic(int musicId, long startTimeMS) {// musicId: 从合唱信令获取;musicUrl: 对应的音乐资源地址TXAudioEffectManager.AudioMusicParam musicParam = newTXAudioEffectManager.AudioMusicParam(musicId, musicUrl);// 仅本地播放音乐musicParam.publish = false;// 音乐开始播放的时间点(毫秒)musicParam.startTimeMS = startTimeMS;mTRTCCloud.getAudioEffectManager().startPlayMusic(musicParam);}
delayMs 可判断当前合唱状态,不同状态下的 startPlayMusic 延迟调用需要开发者自行实现。// 约定的合唱开始时间long mStartPlayMusicTs = jsonObject.getLong("startPlayMusicTS");// 当前伴奏音乐的实际播放进度long currentProgress = mTRTCCloud.getAudioEffectManager().getMusicCurrentPosInMS(musicId);// 当前伴奏音乐的理想播放进度long estimatedProgress = TXLiveBase.getNetworkTimestamp() - mStartPlayMusicTs;// 当进度差超过50ms,进行修正if (estimatedProgress >= 0 && Math.abs(currentProgress - estimatedProgress) > 50) {mTRTCCloud.getAudioEffectManager().seekMusicToPosInMS(musicId, (int) estimatedProgress);}
mTXAudioEffectManager.setMusicObserver(musicId, new TXAudioEffectManager.TXMusicPlayObserver() {@Overridepublic void onStart(int id, int errCode) {// 音乐开始播放}@Overridepublic void onPlayProgress(int id, long curPtsMs, long durationMs) {// TODO 更新歌词控件逻辑:// 根据最新进度和本地歌词进度误差,判断是否需要 seek 歌词控件}@Overridepublic void onComplete(int id, int errCode) {// 音乐播放完成}});
// 关闭合唱模式的实验性接口mTRTCCloud.callExperimentalAPI("{\\"api\\":\\"enableChorus\\",\\"params\\":{\\"enable\\":false,\\"audioSource\\":0}}");// 关闭低延时模式的实验性接口mTRTCCloud.callExperimentalAPI("{\\"api\\":\\"setLowLatencyModeEnabled\\",\\"params\\":{\\"enable\\":false}}");// 切换为观众角色mTRTCCloud.switchRole(TRTCCloudDef.TRTCRoleAudience);// 停止播放伴奏音乐mTRTCCloud.getAudioEffectManager().stopPlayMusic(musicId);// 停止本地音频采集和发布mTRTCCloud.stopLocalAudio();// 退出房间mTRTCCloud.exitRoom();
public void enterRoom(String roomId, String userId) {TRTCCloudDef.TRTCParams params = new TRTCCloudDef.TRTCParams();// 以字符串房间号为例params.strRoomId = roomId;params.userId = userId;// 从业务后台获取到的 UserSigparams.userSig = getUserSig(userId);// 替换成您的 SDKAppIDparams.sdkAppId = SDKAppID;// 建议均以观众角色进房params.role = TRTCCloudDef.TRTCRoleAudience;// 进房场景须选择 LIVEmTRTCCloud.enterRoom(params, TRTCCloudDef.TRTC_APP_SCENE_LIVE);}// 进房结果事件回调@Overridepublic void onEnterRoom(long result) {if (result > 0) {// result 代表加入房间所消耗的时间(毫秒)Log.d(TAG, "Enter room succeed");} else {// result 代表进房失败的错误码Log.d(TAG, "Enter room failed");}}
TRTC_APP_SCENE_LIVE。@Overridepublic void onUserVideoAvailable(String userId, boolean available) {if (available) {mTRTCCloud.startRemoteView(userId, null);} else {mTRTCCloud.stopRemoteView(userId);}}@Overridepublic void onRecvSEIMsg(String userId, byte[] data) {String result = new String(data);try {JSONObject jsonObject = new JSONObject(result);int musicId = jsonObject.getInt("musicId");long progress = jsonObject.getLong("progress");long duration = jsonObject.getLong("duration");} catch (JSONException e) {e.printStackTrace();}...// TODO 更新歌词控件逻辑:// 根据接收到的最新进度和本地歌词进度误差,判断是否需要 seek 歌词控件...}
// 退出房间mTRTCCloud.exitRoom();// 退出房间事件回调@Overridepublic void onExitRoom(int reason) {if (reason == 0) {Log.d(TAG, "主动调用 exitRoom 退出房间");} else if (reason == 1) {Log.d(TAG, "被服务器踢出当前房间");} else if (reason == 2) {Log.d(TAG, "当前房间整个被解散");}}
{"action": "CreateJob","secretId": "{secretId}","secretKey": "{secretKey}","createJobRequest": {"customId": "{customId}","callback": "{callback}","inputs": [{ "url": "{url}" }],"outputs": [{"contentId": "{contentId}","destination": "{destination}","inputSelectors": [0],"smartContentDescriptor": {"outputPrefix": "{outputPrefix}","vocalScore": {"standardAudio": {"midi": {"url":"{url}"},"standardWav": {"url":"{url}"},"alignWav": {"url":"{url}"}}}}}]}}
{"requestId": "ac004192-110b-46e3-ade8-4e449df84d60","createJobResponse": {"job": {"id": "13f342e4-6866-450e-b44e-3151431c578b","state": 1,"customId": "{customId}","callback": "{callback}","inputs": [ { "url": "{url}" } ],"outputs": [{"contentId": "{contentId}","destination": "{destination}","inputSelectors": [ 0 ],"smartContentDescriptor": {"outputPrefix": "{outputPrefix}","vocalScore": {"standardAudio": {"midi": {"url":"{url}"},"standardWav": {"url":"{url}"},"alignWav": {"url":"{url}"}}}}}],"timing": {"createdAt": "1603432763000","startedAt": "0","completedAt": "0"}}}}
{"action": "GetJob","secretId": "{secretId}","secretKey": "{secretKey}","getJobRequest": {"id": "{id}"}}
{"requestId": "c9845a99-34e3-4b0f-80f5-f0a2a0ee8896","getJobResponse": {"job": {"id": "a95e9d74-6602-4405-a3fc-6408a76bcc98","state": 3,"customId": "{customId}","callback": "{callback}","timing": {"createdAt": "1610513575000","startedAt": "1610513575000","completedAt": "1610513618000"},"inputs": [{ "url": "{url}" }],"outputs": [{"contentId": "{contentId}","destination": "{destination}","inputSelectors": [0],"smartContentDescriptor": {"outputPrefix": "{outputPrefix}","vocalScore": {"standardAudio": {"midi": {"url":"{url}"},"standardWav": {"url":"{url}"},"alignWav": {"url":"{url}"}}}},"smartContentResult": {"vocalScore": "out.json"}}]}}}
@Overridepublic void onUserVoiceVolume(ArrayList<TRTCCloudDef.TRTCVolumeInfo> userVolumes, int totalVolume) {super.onUserVoiceVolume(userVolumes, totalVolume);if (userVolumes != null && userVolumes.size() > 0) {// 用于保存麦上用户对应的音量值HashMap<String, Integer> volumesMap = new HashMap<>();for (TRTCCloudDef.TRTCVolumeInfo user : userVolumes) {// 可以设置适当的音量阈值if (user.volume > 10) {volumesMap.put(user.userId, user.volume);}}Gson gson = new Gson();String body = gson.toJson(volumesMap);// 通过 SEI 消息发送麦上用户音量集合mTRTCCloud.sendSEIMsg(body.getBytes(), 1);}}@Overridepublic void onRecvSEIMsg(String userId, byte[] data) {Gson gson = new Gson();HashMap<String, Integer> volumesMap = new HashMap<>();try {String message = new String(data, "UTF-8");volumesMap = gson.fromJson(message, volumesMap.getClass());for (String userId : volumesMap.keySet()) {// 打印所有麦上用户单流的音量大小Log.i(userId, String.valueOf(volumesMap.get(userId)));}} catch (UnsupportedEncodingException e) {e.printStackTrace();}}
onNetworkQuality 来实时统计本地及远端用户的网络质量,该回调每隔2秒抛出一次。private class TRTCCloudImplListener extends TRTCCloudListener {@Overridepublic void onNetworkQuality(TRTCCloudDef.TRTCQuality localQuality,ArrayList<TRTCCloudDef.TRTCQuality> remoteQuality) {// localQuality userId 为空,代表本地用户网络质量评估结果// remoteQuality 代表远端用户网络质量评估结果,其结果受远端和本地共同影响switch (localQuality.quality) {case TRTCCloudDef.TRTC_QUALITY_Excellent:Log.i(TAG, "当前网络非常好");break;case TRTCCloudDef.TRTC_QUALITY_Good:Log.i(TAG, "当前网络比较好");break;case TRTCCloudDef.TRTC_QUALITY_Poor:Log.i(TAG, "当前网络一般");break;case TRTCCloudDef.TRTC_QUALITY_Bad:Log.i(TAG, "当前网络较差");break;case TRTCCloudDef.TRTC_QUALITY_Vbad:Log.i(TAG, "当前网络很差");break;case TRTCCloudDef.TRTC_QUALITY_Down:Log.i(TAG, "当前网络不满足 TRTC 最低要求");break;default:Log.i(TAG, "未定义");break;}}}

TRTCParams 中传入 privateMapKey 参数才可以成功进房。因此如果您线上有使用此 SDKAppID 的用户,请不要轻易开启此功能。TRTCCloudDef.TRTCParams mTRTCParams = new TRTCCloudDef.TRTCParams();mTRTCParams.sdkAppId = SDKAPPID;mTRTCParams.userId = mUserId;mTRTCParams.strRoomId = mRoomId;// 从业务后台获取到的 UserSigmTRTCParams.userSig = getUserSig();// 从业务后台获取到的 PrivateMapKeymTRTCParams.privateMapKey = getPrivateMapKey();mTRTCParams.role = TRTCCloudDef.TRTCRoleAudience;mTRTCCloud.enterRoom(mTRTCParams, TRTCCloudDef.TRTC_APP_SCENE_LIVE);
// 从业务后台获取到最新的 PrivateMapKey 传入切换角色接口mTRTCCloud.switchRole(TRTCCloudDef.TRTCRoleAnchor, getPrivateMapKey());
枚举 | 取值 | 描述 |
ERR_TRTC_INVALID_USER_SIG | -3320 | 进房参数 userSig 不正确,请检查 TRTCParams.userSig 是否为空。 |
ERR_TRTC_USER_SIG_CHECK_FAILED | -100018 | UserSig 校验失败,请检查参数 TRTCParams.userSig 是否填写正确或已经过期。 |
枚举 | 取值 | 描述 |
ERR_TRTC_CONNECT_SERVER_TIMEOUT | -3308 | 请求进房超时,请检查是否断网或者是否开启 VPN,您也可以切换4G进行测试。 |
ERR_TRTC_INVALID_SDK_APPID | -3317 | 进房参数 sdkAppId 错误,请检查 TRTCParams.sdkAppId 是否为空 |
ERR_TRTC_INVALID_ROOM_ID | -3318 | 进房参数 roomId 错误,请检查 TRTCParams.roomId 或 TRTCParams.strRoomId 是否为空,注意 roomId 和 strRoomId 不可混用。 |
ERR_TRTC_INVALID_USER_ID | -3319 | 进房参数 userId 不正确,请检查 TRTCParams.userId 是否为空。 |
ERR_TRTC_ENTER_ROOM_REFUSED | -3340 | 进房请求被拒绝,请检查是否连续调用 enterRoom 进入相同 Id 的房间。 |
枚举 | 取值 | 描述 |
ERR_MIC_START_FAIL | -1302 | 打开麦克风失败,例如在 Windows 或 Mac 设备,麦克风的配置程序(驱动程序)异常,禁用后重新启用设备,或者重启机器,或者更新配置程序。 |
ERR_SPEAKER_START_FAIL | -1321 | 打开扬声器失败,例如在 Windows 或 Mac 设备,扬声器的配置程序(驱动程序)异常,禁用后重新启用设备,或者重启机器,或者更新配置程序。 |
ERR_MIC_OCCUPY | -1319 | 麦克风正在被占用中,例如移动设备正在通话时,打开麦克风会失败。 |
// 开启耳返mTRTCCloud.getAudioEffectManager().enableVoiceEarMonitor(true);// 设置耳返音量mTRTCCloud.getAudioEffectManager().setVoiceEarMonitorVolume(int volume);
setSystemAudioKitEnabled 开启硬件耳返来改善耳返延迟过高的问题。硬件耳返性能较好,且延迟较低;软件耳返延迟较高,但兼容性较好。目前,对于华为和 VIVO 设备,SDK 默认使用硬件耳返,其他设备默认使用软件耳返。如果硬件耳返存在兼容性问题,可以 联系我们 配置强制使用软件耳返。
public static void copyAssetsToFile(Context context, String name) {// 应用程序自身目录下的 files 目录String savePath = ContextCompat.getExternalFilesDirs(context, null)[0].getAbsolutePath();// 应用程序自身目录下的 cache 目录// String savePath = getApplication().getExternalCacheDir().getAbsolutePath();// 应用程序私有存储目录下的 files 目录// String savePath = getApplication().getFilesDir().getAbsolutePath();String filename = savePath + "/" + name;File dir = new File(savePath);// 如果目录不存在,创建这个目录if (!dir.exists()) {dir.mkdir();}try {if (!(new File(filename)).exists()) {InputStream is = context.getResources().getAssets().open(name);FileOutputStream fos = new FileOutputStream(filename);byte[] buffer = new byte[1024];int count = 0;while ((count = is.read(buffer)) > 0) {fos.write(buffer, 0, count);}fos.close();is.close();}} catch (Exception e) {e.printStackTrace();}}
android:requestLegacyExternalStorage="true"。该属性只在 targetSdkVersion 为29(Android 10)的应用上生效,更高版本 targetSdkVersion 的应用仍建议您使用应用的私有或外部存储路径。MANAGE_EXTERNAL_STORAGE 权限:<manifest ...><!-- This is the permission itself --><uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" /><application ...>...</application></manifest>
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {if (!Environment.isExternalStorageManager()) {Intent intent = new Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION);Uri uri = Uri.fromParts("package", getPackageName(), null);intent.setData(uri);startActivity(intent);}} else {// For Android versions less than Android 11, you can use the old permissions modelActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, REQUEST_CODE);}
mTRTCCloud.getAudioEffectManager().preloadMusic(musicParam);
// 设置某一首背景音乐的本地播放音量的大小mTRTCCloud.getAudioEffectManager().setMusicPlayoutVolume(musicID, volume);// 设置某一首背景音乐的远端播放音量的大小mTRTCCloud.getAudioEffectManager().setMusicPublishVolume(musicID, volume);// 设置所有背景音乐的本地音量和远端音量的大小mTRTCCloud.getAudioEffectManager().setAllMusicVolume(volume);// 设置人声采集音量的大小mTRTCCloud.getAudioEffectManager().setVoiceCaptureVolume(volume);
文档反馈