实现一起听(基于底层能力)
更新时间: 2024/08/14 14:11:32
本文介绍如何基于 RTC,在您的 App 中添加一起听场景,实现房主和观众一起听音乐。
方案架构
开发环境要求
开发环境要求如下:
环境要求 | 说明 |
---|---|
JDK 版本 | 1.8.0 及以上版 |
Android API 版本 | API 21 及以上版本 |
Android Studio 版本 | 5.0 及以上版本 |
CPU架构 | ARM 64、ARMV7 |
IDE | Android Studio |
其他 | 依赖 Androidx,不支持 support 库。 Android 系统 5.0 及以上版本的真机。 |
前提条件
API 时序图
sequenceDiagram
participant playerA as NERTC SDK
participant hostClientA as 房主A
participant app_server as 一起听Server
participant neroom_server as NIM_Server
participant audienceB as 连麦观众B
participant playerB as NERTC SDK
Note over playerA, playerB: 单人场景
hostClientA ->> app_server: 创建房间、加入房间
hostClientA ->> app_server: 点歌(已下载)
app_server -->> hostClientA: 点歌信息
app_server -->> hostClientA: 歌单变化
hostClientA ->> app_server: 上报歌曲ready
app_server ->> app_server: 判断房间人数和上报ready人数
app_server ->> hostClientA: 下发开始播放
hostClientA ->> playerA: 播放
hostClientA ->> hostClientA: 刷新歌词
audienceB ->> app_server: 加入房间
Note over playerA, playerB: 单人场景暂停、恢复
hostClientA ->> app_server: 暂停
app_server -->> hostClientA: 下发暂停播放
hostClientA ->> playerA: 暂停
hostClientA ->> app_server: 恢复
app_server -->> hostClientA: 下发开始播放
hostClientA ->> playerA: 恢复
Note over playerA, playerB: 单人场景切歌
hostClientA ->> app_server: 切歌
app_server -->> hostClientA: 下发切歌,包含下一首歌信息
hostClientA ->> app_server: 上报下一首歌ready
app_server -->> hostClientA: 下发开始播放
hostClientA ->> playerA: 播放
Note over playerA, playerB: 观众上麦
audienceB ->> app_server: 上麦(主播抱麦)
audienceB ->> app_server: 获取当前播放歌曲
audienceB ->> app_server: 获取已点歌曲列表
app_server -->> audienceB: 当前播放歌曲信息
app_server -->> audienceB: 当前已点歌曲列表
audienceB ->> audienceB: 下载当前播放歌曲
audienceB ->> audienceB: 当前歌曲下载完成
audienceB ->> audienceB: 预下载歌曲列表的其他歌曲
audienceB ->> neroom_server: 获取播放进度
neroom_server ->> hostClientA: 下发消息通知房主需要同步播放进度给连麦观众
hostClientA ->> neroom_server: 通知当前播放进度
neroom_server -->> audienceB: 通知当前播放进度
audienceB ->> audienceB: 对齐播放进度
audienceB ->> playerB: seekTo
audienceB ->> audienceB: 刷新歌词
sequenceDiagram
participant playerA as NERTC SDK
participant hostClientA as 房主A
participant app_server as 一起听Server
participant neroom_server as NIM_Server
participant audienceB as 连麦观众B
participant playerB as NERTC SDK
Note over playerA, playerB: 同步一起听歌曲状态(暂停、恢复)
hostClientA->>app_server: 歌曲播放控制,暂停or恢复
audienceB->>app_server: 歌曲播放控制,暂停or恢复
app_server-->>hostClientA: 下发当前播放歌曲状态
app_server-->>audienceB: 下发当前播放歌曲状态
hostClientA->>playerA: 暂停or恢复
audienceB->>playerB: 暂停or恢复
Note over playerA, playerB: 同步一起听歌曲状态(换歌)
hostClientA->>app_server: 上报换歌动作
app_server-->>hostClientA: 下发换歌消息
app_server-->>audienceB: 下发换歌消息
alt AB均已下载待播放歌曲
hostClientA->>app_server: 上报待播歌曲ready
audienceB->>app_server: 上报待播歌曲ready
app_server->>app_server: 判断房间人数和上报ready人数
app_server-->>hostClientA: 下发开始播放
app_server-->>audienceB: 下发开始播放
else AB至少有一人未下载待播歌曲
audienceB->>audienceB: 先切到待播歌曲状态,同时下载歌曲,状态为下载中
hostClientA->>hostClientA: 先切到待播歌曲状态,同时下载歌曲,状态为下载中
hostClientA->>app_server: 上报待播歌曲ready
audienceB->>app_server: 上报待播歌曲ready
app_server-->>hostClientA: 下发开始播放
app_server-->>audienceB: 下发开始播放
hostClientA->>playerA: 播放新歌
audienceB->>playerB: 播放新歌
end
Note over playerA, playerB: 同步一起听歌曲进度
hostClientA->>hostClientA: 拖动进度条
hostClientA->>playerA: seekTo
hostClientA->>hostClientA: 刷新歌词
hostClientA->>neroom_server: 通知播放进度(NERoom点对点通知自定义消息)
neroom_server-->>audienceB: 通知播放进度 <br> (NERoom点对点通知自定义消息)
audienceB->>audienceB: 对齐播放进度
audienceB->>playerB: seekTo
audienceB->>audienceB: 刷新歌词
房间管理
-
房主创建一起听房间。
客户端通过业务服务器提供的restful api创建房间。
-
房主和连麦观众分别调用
joinChannel
接口加入RTC房间,分别调用enterChatRoom
接口加入聊天室。一起听房间只允许两个用户同时在线,如果房间中已经存在两个用户,则第三个用户无法加入。
-
房主和连麦观众分别调用
setChannelProfile
接口,设置房间场景为直播场景(LIVE_BROADCASTING)。 -
房主和连麦观众分别调用
setAudioProfile
接口,设置音频profile
类型为HighQualityStereo
,设置scenario
为MUSIC
。 -
连麦观众调用业务服务器接口(需自行实现)离开一起听房间,同时调用
leaveChannel
接口离开RTC房间,调用exitChatRoom
接口离开聊天室。 -
房主调用业务服务器接口(需自行实现)结束一起听房间,同时调用
leaveChannel
接口离开RTC房间,调用exitChatRoom
接口离开聊天室。
示例代码
// 房主调用自己的业务服务器restful api创建房间。
// 房主或者观众加入房间:
主播和观众加入RTC
NERtcEx.getInstance().joinChannel(token,channelName,uid);
主播和观众加入聊天室
EnterChatRoomData enterChatRoomData = new EnterChatRoomData(chatroomId);
NIMClient.getService(ChatRoomService.class).enterChatRoom(enterChatRoomData).setCallback(new RequestCallback<EnterChatRoomResultData>() {
@Override
public void onSuccess(EnterChatRoomResultData result) {
}
@Override
public void onFailed(int code) {
}
@Override
public void onException(Throwable exception) {
}
});
// 设置房间场景为直播场景
NERtcEx.getInstance().setChannelProfile(NERtcConstants.RTCChannelProfile.LIVE_BROADCASTING);
// 设置音频类型
NERtcEx.getInstance().setAudioProfile(NERtcConstants.AudioProfile.HIGH_QUALITY_STEREO, NERtcConstants.AudioScenario.MUSIC);
// 观众离开房间
观众调用业务服务器接口离开房间,同时离开RTC房间和聊天室
a、离开RTC房间
NERtcEx.getInstance().leaveChannel();
b、离开聊天室
NIMClient.getService(ChatRoomService.class).exitChatRoom(chatroomId);
// 房主关闭房间
房主调用业务服务器接口关闭房间,同时离开RTC房间和聊天室
a、离开RTC房间
NERtcEx.getInstance().leaveChannel();
b、离开聊天室
NIMClient.getService(ChatRoomService.class).exitChatRoom(chatroomId);
实现一起听
步骤1 初始化曲库 SDK
-
调用
NECopyrightedMedia.getInstance()
接口创建版权音乐对象。 示例代码如下:NECopyrightedMedia copyRight = NECopyrightedMedia.getInstance();
-
调用
NECopyrightedMedia.initialize
接口初始化组件。在调用 SDK 初始化接口之前,需要先为应用下的每一位用户生成user账号(account),同时获取对应的 Token。Token 鉴权相关步骤,请参考 曲库动态 Token 鉴权 即可,下文不再赘述。示例代码如下:
NECopyrightedMedia copyRight = NECopyrightedMedia.getInstance(); HashMap<String, Object> extras = new HashMap<>(); copyRight.initialize( context, appkey, token, user, //请传入您的应用账号体系中的账号ID,用于数据统计 extras, new NECopyrightedMedia.Callback<Unit>() { @Override public void success(@Nullable Unit info) { Toast.makeText( context, "init CopyrightedMedia success", Toast.LENGTH_LONG) .show(); } @Override public void error(int code, @Nullable String msg) { Toast.makeText( context, "init CopyrightedMedia fail", Toast.LENGTH_LONG) .show(); } } );
-
调用
NECopyrightedMedia.setSongScene
接口,指定音乐场景为听歌的场景。版权曲库支持听歌场景和 K 歌场景,您需要在初始化曲库 SDK 后指定对应的场景。
// 设置听歌场景 NECopyrightedMedia.getInstance().setSongScene(SongScene.TYPE_LISTENING_TO_MUSIC);
-
调用
NECopyrightedMedia.setEventHandler
接口注册事件通知回调。当您的曲库 Token 过期时,会触发
onTokenExpired
回调。此时,您需要参见曲库动态 Token 鉴权生成新的 Token,并调用renewToken
更新 Token 后才能继续调用 NECopyrightedMedia SDK 的API。示例代码如下:
NECopyrightedMedia.getInstance().setEventHandler(new NECopyrightedEventHandler() { @Override public void onTokenExpired() { // token过期监听回调 } });
-
调用
NECopyrightedMedia.renewToken
接口更新Token。示例代码如下:
NECopyrightedMedia.getInstance().renewToken(token);
初始化操作完毕,如果能正常调用以下接口,表示初始化成功。
步骤2 获取歌曲列表
房主和连麦观众可以通过搜索、请求歌曲列表、榜单三种方式获取歌曲。
- 通过搜索获取歌曲
调用 NECopyrightedMedia.searchSong
接口获取搜索的歌曲列表和歌曲的song ID。
参数 | 类型 | 描述 |
---|---|---|
keyword | String | 搜索的关键字。 |
channel | Int | 版权渠道,默认不传则包含所有签约渠道。
|
pageNum | Int | 页码。 默认值为 0。 |
pageSize | Int | 每页显示的行数,默认值为 20。 |
callback | Callback | 回调 |
- 通过请求歌曲列表获得歌曲
调用 NECopyrightedMedia.getSongList
接口获取歌曲列表和歌曲的song ID。
- 通过榜单获取歌曲
调用 getHotSongList
接口获取推荐歌单的歌曲 song ID。
示例代码如下:
// 搜索歌曲
NECopyrightedMedia.getInstance().searchSong("keyword", null, 1, 20, new NECopyrightedMedia.Callback<List<NECopyrightedSong>>() {
@Override
public void success(@Nullable List<NECopyrightedSong> info) {
}
@Override
public void error(int code, @Nullable String msg) {
}
});
// 获取歌曲列表
NECopyrightedMedia.getInstance().getSongList(null, null, 1, 20, new NECopyrightedMedia.Callback<List<NECopyrightedSong>>() {
@Override
public void success(@Nullable List<NECopyrightedSong> info) {
}
@Override
public void error(int code, @Nullable String msg) {
}
});
// 获取热门歌曲列表
NECopyrightedMedia.getInstance().getHotSongList(NECopyrightedHotType.HOTTYPE_DEFAULT, NECopyrightedHotDimension.HOTDIMENSION_APPLICATION, null, 1, 20, new NECopyrightedMedia.Callback<List<NECopyrightedHotSong>>() {
@Override
public void success(@Nullable List<NECopyrightedHotSong> info) {
}
@Override
public void error(int code, @Nullable String msg) {
}
});
步骤3 点歌并下载歌曲
调用 NECopyrightedMedia.preloadSong
接口预加载歌曲,包括原唱、歌词和MIDI。
示例代码如下:
NECopyrightedMedia.getInstance().preloadSong(songId, channel, new NESongPreloadCallback() {
@Override
public void onPreloadStart(String songId, int channel) {
}
@Override
public void onPreloadProgress(String songId, int channel, float progress) {
}
@Override
public void onPreloadComplete(String songId, int channel, int errorCode, String msg) {
}
});
步骤4 房主播放歌曲
调用 NERtcEx.playEffect
播放歌曲。
示例代码如下:
NERtcCreateAudioEffectOption neRtcCreateAudioEffectOption = new NERtcCreateAudioEffectOption();
neRtcCreateAudioEffectOption.path="";// 音乐文件路径
neRtcCreateAudioEffectOption.loopCount=1;
neRtcCreateAudioEffectOption.sendEnabled=true;
neRtcCreateAudioEffectOption.sendVolume=100;
neRtcCreateAudioEffectOption.playbackEnabled=true;
neRtcCreateAudioEffectOption.playbackVolume=100;
neRtcCreateAudioEffectOption.startTimestamp=0;
neRtcCreateAudioEffectOption.progressInterval=100;
neRtcCreateAudioEffectOption.sendWithAudioType= NERtcAudioStreamType.kNERtcAudioStreamTypeSub;
NERtcEx.getInstance().playEffect(effectId,neRtcCreateAudioEffectOption);
步骤5 观众上麦
观众加入房间后,自动上麦并获取歌曲播放信息。
- 观众加入房间后,主播把观众抱上麦位。
- 观众调用业务服务器接口获取当前播放歌曲列表。
- 观众调用
NECopyrightedMedia.preloadSong
接口预加载歌曲。
// 观众进入房间,主播抱麦,观众自动上麦 (具体业务逻辑需自行实现)
// 调用业务服务器获取当前播放歌曲和歌曲列表(需自行实现)
// 预加载当前播放歌曲,在加载完后通过点对点自定义消息向主播询问当前播放进度
String songId="your songId";
int channel=1;
if (NECopyrightedMedia.getInstance().isSongPreloaded(songId,channel)){
// 向主播询问当前歌曲播放进度
String roomId = "50";
String customStr = "{\"commandId\":10001,\"msg\":\"\"}";// 可以设置自定义消息
ChatRoomMessage message = ChatRoomMessageBuilder.createChatRoomTextMessage(roomId, customStr);
//设置定向发送的账户列表
List<String> accountList = new ArrayList<>();
accountList.add("targetAccount");
message.setToAccounts(accountList);
// 发送聊天室消息
NIMClient.getService(ChatRoomService.class).sendMessage(message, false);
}else {
NECopyrightedMedia.getInstance().preloadSong(songId, channel, new NESongPreloadCallback() {
@Override
public void onPreloadStart(String songId, int channel) {
}
@Override
public void onPreloadProgress(String songId, int channel, float progress) {
}
@Override
public void onPreloadComplete(String songId, int channel, int errorCode, String msg) {
// 向主播询问当前歌曲播放进度
String roomId = "50";
String customStr = "{\"commandId\":10001,\"msg\":\"\"}";// 可以设置自定义消息
ChatRoomMessage message = ChatRoomMessageBuilder.createChatRoomTextMessage(roomId, customStr);
//设置定向发送的账户列表
List<String> accountList = new ArrayList<>();
accountList.add("targetAccount");
message.setToAccounts(accountList);
// 发送聊天室消息
NIMClient.getService(ChatRoomService.class).sendMessage(message, false);
}
});
步骤6 同步一起听的歌曲状态和进度
-
房主通过
ChatRoomService.sendMessage
发送定向消息,将播放进度同步给观众。 -
观众调用
ChatRoomService.sendMessage
接口获取歌曲播放进度。
// 监听来自主播的自定义消息
Observer<List<ChatRoomMessage>> incomingChatRoomMsg = new Observer<List<ChatRoomMessage>>() {
@Override
public void onEvent(List<ChatRoomMessage> messages) {
// 处理新收到的消息
for (ChatRoomMessage message : messages) {
if (message.getMsgType() == MsgTypeEnum.text) {
String customStr = message.getContent();
// 自定义消息内容,通过解析获取主播的播放进度
NERtcCreateAudioEffectOption neRtcCreateAudioEffectOption = new NERtcCreateAudioEffectOption();
neRtcCreateAudioEffectOption.path="";// 音乐文件路径
neRtcCreateAudioEffectOption.loopCount=1;
neRtcCreateAudioEffectOption.sendEnabled=true;
neRtcCreateAudioEffectOption.sendVolume=100;
neRtcCreateAudioEffectOption.playbackEnabled=true;
neRtcCreateAudioEffectOption.playbackVolume=100;
neRtcCreateAudioEffectOption.startTimestamp=30;//主播播放进度
neRtcCreateAudioEffectOption.progressInterval=100;
neRtcCreateAudioEffectOption.sendWithAudioType= NERtcAudioStreamType.kNERtcAudioStreamTypeSub;
NERtcEx.getInstance().playEffect(effectId,neRtcCreateAudioEffectOption);
}
}
}
};
NIMClient.getService(ChatRoomServiceObserver.class).observeReceiveMessage(incomingChatRoomMsg, true);
// 向主播询问当前歌曲播放进度
String roomId = "50";
String customStr = "{\"commandId\":10001,\"msg\":\"\"}";// 可以设置自定义消息
ChatRoomMessage message = ChatRoomMessageBuilder.createChatRoomTextMessage(roomId, customStr);
//设置定向发送的账户列表
List<String> accountList = new ArrayList<>();
accountList.add("targetAccount");
message.setToAccounts(accountList);
// 发送聊天室消息
NIMClient.getService(ChatRoomService.class).sendMessage(message, false);
进阶功能
暂停或恢复歌曲播放
// 暂停歌曲播放
NERtcEx.getInstance().pauseEffect(effectId);
// 恢复歌曲播放
NERtcEx.getInstance().resumeEffect(effectId);
切歌
// 停止播放上一首歌
NERtcEx.getInstance().stopEffect(effectId);
// 播放下一首歌
NERtcCreateAudioEffectOption neRtcCreateAudioEffectOption = new NERtcCreateAudioEffectOption();
neRtcCreateAudioEffectOption.path="";// 音乐文件路径
neRtcCreateAudioEffectOption.loopCount=1;
neRtcCreateAudioEffectOption.sendEnabled=true;
neRtcCreateAudioEffectOption.sendVolume=100;
neRtcCreateAudioEffectOption.playbackEnabled=true;
neRtcCreateAudioEffectOption.playbackVolume=100;
neRtcCreateAudioEffectOption.startTimestamp=0;
neRtcCreateAudioEffectOption.progressInterval=100;
neRtcCreateAudioEffectOption.sendWithAudioType= NERtcAudioStreamType.kNERtcAudioStreamTypeSub;
NERtcEx.getInstance().playEffect(effectId,neRtcCreateAudioEffectOption);