实现一起听(基于 NERoom)
更新时间: 2024/08/14 14:11:32
本文介绍如何基于 NERoom 房间组件,在您的 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 及以上版本的真机。 |
前提条件
示例项目源码
一起听的示例项目源码地址请参见 语聊房 NEChatroom 的示例项目源码。
示例项目的目录结构和跑通示例项目的步骤请参见跑通示例项目。
API 时序图
sequenceDiagram
participant playerA as NERoom SDK
participant hostClientA as 房主A
participant app_server as 一起听Server
participant neroom_server as NERoom_Server
participant audienceB as 连麦观众B
participant playerB as NERoom 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: 刷新歌词
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 NERoom SDK
participant hostClientA as 房主A
participant app_server as 一起听Server
participant neroom_server as NERoom_Server
participant audienceB as 连麦观众B
participant playerB as NERoom 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: 通知播放进度 <br> (NERoom点对点自定义消息)
neroom_server ->> audienceB: 通知播放进度 <br> (NERoom点对点自定义消息)
audienceB ->> audienceB: 对齐播放进度
audienceB ->> playerB: seekTo
audienceB ->> audienceB:刷新歌词
房间管理
-
房主创建一起听房间。
您可以通过自己的业务服务器调用 NERoom Server 的接口去创建房间,客户端通过业务服务器提供的restful api创建房间。
-
房主和连麦观众分别调用
joinRoom
接口加入一起听房间。加入房间成功后,房间内用户触发onMemberJoinRoom
回调。一起听房间只允许两个用户同时在线,如果房间中已经存在两个用户,则第三个用户无法加入。
-
房主和连麦观众分别调用
setChannelProfile
接口,设置房间场景为直播场景(LIVE_BROADCASTING)。 -
房主和连麦观众分别调用
setLocalAudioProfile
接口,设置音频profile
类型为HighQualityStereo
,设置scenario
为MUSIC
。 -
连麦观众调用
leaveRoom
接口离开一起听房间。 -
房主调用
endRoom
接口结束一起听房间。
示例代码
// 房主调用自己的业务服务器restful api创建房间。
// 房主或者观众加入房间:
String roomUuid="123";
String userName="userName";
String avatar="your avatar";
String role="host";// host为房主,audience为观众
NERoomKit.getInstance().getRoomService().joinRoom(new NEJoinRoomParams(roomUuid,userName,avatar,role,null,new HashMap<>()), new NEJoinRoomOptions(), new NECallback2<NERoomContext>() {
@Override
public void onSuccess(@Nullable NERoomContext roomContext) {
// 设置为直播场景
roomContext.getRtcController().setChannelProfile(
NERoomRtcChannelProfile.liveBroadcasting
);
// 设置音频类型
roomContext.getRtcController().setLocalAudioProfile(
NERoomRtcAudioProfile.HIGH_QUALITY_STEREO,
NERoomRtcAudioScenario.MUSIC
);
// 加入rtc
roomContext.getRtcController().joinRtcChannel(null);
// 加入聊天室
roomContext.getChatController().joinChatroom(null);
}
@Override
public void onError(int code, @Nullable String message) {
super.onError(code, message);
}
});
// 观众离开房间
roomContext.leaveRoom(null);
// 房主结束房间
roomContext.endRoom(false,null);
实现一起听
步骤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);
4.调用 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 版权渠道,默认不传则包含所有签约渠道。 - 1:网易云音乐
- 2: 咪咕
- 3: HIFIVE
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 房主播放歌曲
调用 NERoomRtcController.playEffect
播放歌曲。
示例代码如下:
String path="";// 音乐文件路径
int loopCount=1;
boolean sendEnabled=false;
int sendVolume=100;
boolean playbackEnabled=true;
int playbackVolume=100;
long startTimestamp= 0;
long progressInterval=100;
NERoomRtcAudioStreamType sendWithAudioType=NERoomRtcAudioStreamType.NERtcAudioStreamTypeSub;
roomContext.getRtcController().playEffect(effectId,new NERoomCreateAudioEffectOption(path,loopCount,sendEnabled,sendVolume,playbackEnabled,playbackVolume,startTimestamp,progressInterval,sendWithAudioType));
步骤5 观众上麦
观众加入房间后,自动上麦并获取歌曲播放信息。
- 观众加入房间后,主播调用
NESeatController.sendSeatInvitation
接口邀请观众上麦。上麦成功后,房间内所有成员收到onSeatInvitationAccepted
的回调和onSeatListChanged
的回调。 - 观众调用业务服务器接口获取当前播放歌曲列表。
- 观众调用
NECopyrightedMedia.preloadSong
接口预加载歌曲。
// 观众进入房间,主播抱麦,观众自动上麦
NERoomContext roomContext = NERoomKit.getInstance().getRoomService().getRoomContext(roomUuid);
if (roomContext!=null){
roomContext.getSeatController().sendSeatInvitation(index, "account", new NECallback2<Unit>() {
@Override
public void onSuccess(@Nullable Unit data) {
super.onSuccess(data);
}
@Override
public void onError(int code, @Nullable String message) {
super.onError(code, message);
}
});
}
// 调用业务服务器获取当前播放歌曲和歌曲列表(需自行实现)
// 预加载当前播放歌曲,在加载完后通过点对点自定义消息向主播询问当前播放进度
String songId="your songId";
int channel=1;
if (NECopyrightedMedia.getInstance().isSongPreloaded(songId,channel)){
// 向主播询问当前歌曲播放进度
NERoomKit.instance.getService(NEMessageChannelService.class).sendCustomMessage(roomUuid,anchorUserUuid,10001,"",null);
}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) {
// 向主播询问当前歌曲播放进度
NERoomKit.instance.getService(NEMessageChannelService.class).sendCustomMessage(roomUuid,anchorUserUuid,10001,"",null);
}
});
步骤6 同步一起听的歌曲状态和进度
- 房主通过
NEMessageChannelService.sendCustomMessage
发送定向消息,将播放进度同步给观众。 - 观众调用
NEMessageChannelService.sendCustomMessage
接口获取歌曲播放进度。
// 监听来自主播的自定义消息
NERoomKit.instance.getService(NEMessageChannelService.class).addMessageChannelListener(new NEMessageChannelListener() {
@Override
public void onReceiveCustomMessage(@NonNull NECustomMessage message) {
if (message.getCommandId()==10002){
// 接收到来自主播播放进度消息,假设commandId=10002
String data = message.getData(); // 可定义为json格式
// 自定义消息内容,通过解析获取主播的播放进度
String path="";// 音乐文件路径
int loopCount=1;
boolean sendEnabled=false;
int sendVolume=100;
boolean playbackEnabled=true;
int playbackVolume=100;
long startTimestamp= 30;// 主播播放进度
long progressInterval=100;
NERoomRtcAudioStreamType sendWithAudioType=NERoomRtcAudioStreamType.NERtcAudioStreamTypeSub;
roomContext.getRtcController().playEffect(effectId,new NERoomCreateAudioEffectOption(path,loopCount,sendEnabled,sendVolume,playbackEnabled,playbackVolume,startTimestamp,progressInterval,sendWithAudioType));
}else if(commandId=10001){
// 回复当前播放进度给对方
String data="d";
NERoomKit.instance.getService(NEMessageChannelService.class).sendCustomMessage(roomUuid,anotherUserUuid,10002,data,null);
}
}
});
// 向主播询问当前歌曲播放进度
NERoomKit.instance.getService(NEMessageChannelService.class).sendCustomMessage(roomUuid,anchorUserUuid,10001,"",null);
进阶功能
暂停或恢复歌曲播放
// 暂停歌曲播放
roomContext.getRtcController().pauseEffect(effectId);
// 恢复歌曲播放
roomContext.getRtcController().resumeEffect(effectId);
切歌
// 停止播放上一首歌
roomContext.getRtcController().stopEffect(effectId);
// 播放下一首歌
String path="";// 音乐文件路径
int loopCount=1;
boolean sendEnabled=false;
int sendVolume=100;
boolean playbackEnabled=true;
int playbackVolume=100;
long startTimestamp= 0;
long progressInterval=100;
NERoomRtcAudioStreamType sendWithAudioType=NERoomRtcAudioStreamType.NERtcAudioStreamTypeSub;
roomContext.getRtcController().playEffect(effectId,new NERoomCreateAudioEffectOption(path,loopCount,sendEnabled,sendVolume,playbackEnabled,playbackVolume,startTimestamp,progressInterval,sendWithAudioType));