实现一起听(基于底层能力)

更新时间: 2024/08/14 14:11:32

本文介绍如何基于 RTC,在您的 App 中添加一起听场景,实现房主和观众一起听音乐。

方案架构

一起听的架构-RTC.png

开发环境要求

开发环境要求如下:

环境要求 说明
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: 刷新歌词

房间管理

  1. 房主创建一起听房间。

    客户端通过业务服务器提供的restful api创建房间。

  2. 房主和连麦观众分别调用 joinChannel 接口加入RTC房间,分别调用enterChatRoom 接口加入聊天室。

    一起听房间只允许两个用户同时在线,如果房间中已经存在两个用户,则第三个用户无法加入。

  3. 房主和连麦观众分别调用 setChannelProfile 接口,设置房间场景为直播场景(LIVE_BROADCASTING)。

  4. 房主和连麦观众分别调用 setAudioProfile 接口,设置音频 profile 类型为 HighQualityStereo,设置 scenarioMUSIC

  5. 连麦观众调用业务服务器接口(需自行实现)离开一起听房间,同时调用leaveChannel接口离开RTC房间,调用exitChatRoom接口离开聊天室。

  6. 房主调用业务服务器接口(需自行实现)结束一起听房间,同时调用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

  1. 调用 NECopyrightedMedia.getInstance() 接口创建版权音乐对象。 示例代码如下:

    NECopyrightedMedia copyRight = NECopyrightedMedia.getInstance();
    
  2. 调用 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();
            }
        }
    );
    
  3. 调用 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过期监听回调
        }
    });
    
  5. 调用 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 房主播放歌曲

调用 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 观众上麦

观众加入房间后,自动上麦并获取歌曲播放信息。

  1. 观众加入房间后,主播把观众抱上麦位。
  2. 观众调用业务服务器接口获取当前播放歌曲列表。
  3. 观众调用 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 同步一起听的歌曲状态和进度

  1. 房主通过 ChatRoomService.sendMessage 发送定向消息,将播放进度同步给观众。

  2. 观众调用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);
此文档是否对你有帮助?
有帮助
去反馈
  • 方案架构
  • 开发环境要求
  • 前提条件
  • API 时序图
  • 房间管理
  • 实现一起听
  • 步骤1 初始化曲库 SDK
  • 步骤2 获取歌曲列表
  • 步骤3 点歌并下载歌曲
  • 步骤4 房主播放歌曲
  • 步骤5 观众上麦
  • 步骤6 同步一起听的歌曲状态和进度
  • 进阶功能
  • 暂停或恢复歌曲播放
  • 切歌