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

更新时间: 2023/08/01 03:41:50

本文介绍如何基于 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. 房主和连麦观众分别调用 joinChannelWithToken 接口加入RTC房间,分别调用enterChatRoom 接口加入聊天室。

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

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

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

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

  6. 房主调用业务服务器接口(需自行实现)结束一起听房间,同时调用leaveChannel接口离开RTC房间,调用exitChatRoom接口离开聊天室。

示例代码

// 房主调用自己的业务服务器restful api创建房间。
// 房主或者观众加入房间:
          主播和观众加入RTC
             let ret = NERtcEngine.shared().joinChannel(withToken: rtcToken,
                                               channelName: channel,
                                               myUid: rtcUid) { error, channelId, elapesd, uid in
    }
       主播和观众加入聊天室
    let request = NIMChatroomEnterRequest()
    request.roomId = roomId
    request.roomNickname = nickName
    ChatroomProvider.shared.enterChatroom(request: request) { error, chatroom, member in
    }

        // 设置房间场景为直播场景
         NERtcEngine.shared()
      .setChannelProfile(NERtcChannelProfileType(rawValue: liveBroadcasting.rawValue))

        // 设置音频类型 
        NERtcEngine.shared().setAudioProfile(.highQuality, scenario: .music)
   // 观众离开房间
   观众调用业务服务器接口离开房间,同时离开RTC房间和聊天室
    a、离开RTC房间
    NERtcEx.getInstance().leaveChannel();
    b、离开聊天室
    NIMSDK.shared().chatroomManager.exitChatroom(roomId, completion: completion)
   // 房主关闭房间
   房主调用业务服务器接口关闭房间,同时离开RTC房间和聊天室 
    a、离开RTC房间
    NERtcEx.getInstance().leaveChannel();
    b、离开聊天室
    NIMSDK.shared().chatroomManager.exitChatroom(roomId, completion: completion)

实现一起听

步骤1 初始化曲库 SDK

  1. 调用 NECopyrightedMedia getInstance 接口创建版权音乐对象。

    NECopyrightedMedia * copyRight = [NECopyrightedMedia getInstance];
    
  2. 调用 initialize 接口初始化组件。

/// 初始化 NECopyrightedMedia
/// @param appkey appkey
/// @param token token
/// @param userUuid userUuid
/// @param extras 填入Nil
/// @param callback 异步回调 NSError 为Nil 则成功
- (void)initialize:(NSString *_Nonnull)appkey
            token:(NSString *_Nonnull)token
          userUuid:(NSString *_Nullable)userUuid
            extras:(NSDictionary *_Nullable)extras
          callback:(void (^)(NSError *_Nullable error))callback;
  1. 调用 NECopyrightedMedia.setSongScene 接口,指定音乐场景为听歌的场景。

    版权曲库支持听歌场景和 K 歌场景,您需要在初始化曲库 SDK 后指定对应的场景。

    // 设置听歌场景
    NECopyrightedMedia.getInstance().setSongScene(TYPE_LISTENING_TO_MUSIC)
    
  2. 调用 setEventHandler 接口注册事件通知回调。

    当您的曲库 Token 过期时,会触发 onTokenExpired 回调。此时,您需要参见曲库动态 Token 鉴权生成新的 Token,并调用 renewToken 更新 Token 后才能继续调用 NECopyrightedMedia SDK 的API。

    //设置动态Token过期代理
    [[NECopyrightedMedia getInstance] setEventHandler:self];
    //回调如下
    - (void)onTokenExpired {
    // Token过期
    //此处需要申请新的realTimeToken
    }
    
  3. 调用 renewToken 接口更新 Token。

    [[NECopyrightedMedia getInstance]       renewToken:copyrightedToken];
    

    初始化操作完毕,如果能正常调用以下接口,表示初始化成功。

步骤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", channel: 1, pageNum: 1,
                  pageSize: 20) { songList, error in
        
      }

// 获取歌曲列表
NECopyrightedMedia.getInstance()
      .getSongList(nil, channel: 1, pageNum: 1,
                   pageSize: 20) { songList, error in
        
      }

// 获取热门歌曲列表
[[NECopyrightedMedia getInstance] getHotSongList:HOTTYPE_DEFAULT channel:@1 hotDimension:HOTDIMENSION_PLATFORM pageNum:@0 pageSize:@20 callback:^(NSArray<NECopyrightedHotSong *> * _Nonnull songList, NSError * _Nonnull error) {
        if (error) {
            NSLog(@"获取歌曲列表失败");
        }else{
            NSLog(@"获取歌曲列表成功");
            for (NECopyrightedSong *songItem in songList) {
                //遍历数据
                NSLog(@"songDta --- %@",songItem);
            }
        }
    }];

步骤3 点歌并下载歌曲

调用 NECopyrightedMedia.preloadSong 接口预加载歌曲,包括原唱、歌词和MIDI。

示例代码如下:

//遵循协议:
<NESongPreloadProtocol>
//请求示例:
[[NECopyrightedMedia getInstance] preloadSong:songModel.songId channel:CLOUD_MUSIC observe:self];

//请求回调:
//开始下载回调
-(void)onPreloadStart:(NSString *)songId channel:(SongChannel)channel{
    NSLog(@"onPreloadStart -- songId = %@",songId);
}
//下载进度回调
-(void)onPreloadProgress:(NSString *)songId channel:(SongChannel)channel progress:(float)progress{
    NSLog(@"onPreloadProgress -- songId = %@ ; progress = %.2f",songId,progress);
}
//下载失败/完成回调
-(void)onPreloadComplete:(NSString *)songId channel:(SongChannel)channel error:(NSError * _Nullable)error{
    if(error){
        NSLog(@"onPreloadComplete error reason:%@",error.description);
    }else{
        NSLog(@"onPreloadComplete")
    }
}

步骤4 房主播放歌曲

调用 playEffectWitdId 播放歌曲。

示例代码如下:

var path:String = "路径"
/// 如果使用版权SDK,可通过以下方法获取
/// NECopyrightedMedia.getInstance().getSongURI(songId, channel: channel, songResType: songResType)

 let option = NERtcCreateAudioEffectOption()
    option.path = path
    option.loopCount = 1
    option.sendEnabled = false
    option.playbackEnabled = true
    option.sendVolume = 100
    option.playbackVolume = 100
    option.progressInterval = 100
    option.sendWithAudioType = .main
      
    option.startTimeStamp = 1000
    var effectId = 10001;///自定义播放通道ID,需要rong'yi
NERtcEngine.shared().playEffectWitdId(effectId, effectOption: option.convertToRTC())

步骤5 观众上麦

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

  1. 观众加入房间后,主播把观众抱上麦位。
  2. 观众调用业务服务器接口获取当前播放歌曲列表。
  3. 观众调用 NECopyrightedMedia.preloadSong 接口预加载歌曲。
// 观众进入房间,主播抱麦,观众自动上麦 (具体业务逻辑需自行实现)
// 调用业务服务器获取当前播放歌曲和歌曲列表(需自行实现)
// 预加载当前播放歌曲,在加载完后通过点对点自定义消息向主播询问当前播放进度
String songId="your songId";
int channel=1;
if (NECopyrightedMedia.getInstance().isSongPreloaded(songId,channel)){
  // 向主播询问当前歌曲播放进度

    let m = NIMMessage()
    m.text = "{\"commandId\":10001,\"msg\":\"\"}"// 可以设置自定义消息
    m.toAccIds = [String]
    // 自定义扩展
    m.remoteExt = [fromAccountId: fromId, toAccIds: group]
    let session = NIMSession(roomId, type: .chatroom)
    let session = NIMSession(roomId, type: .chatroom)
     NIMSDK.shared().chatManager.send(message, to: session) { error in
    }

}else {
  NECopyrightedMedia.getInstance().preloadSong(songId, channel: channel, observe: self)
}
//下载完成回调
  - (void)onPreloadComplete:(NSString *)songId
                            channel:(SongChannel)channel
                              error:(NSError *)error {
                                // 向主播询问当前歌曲播放进度
     let m = NIMMessage()
    m.text = "{\"commandId\":10001,\"msg\":\"\"}"// 可以设置自定义消息
    m.toAccIds = [String]
    // 自定义扩展
    m.remoteExt = [fromAccountId: fromId, toAccIds: group]
    let session = NIMSession(roomId, type: .chatroom)
    let session = NIMSession(roomId, type: .chatroom)
     NIMSDK.shared().chatManager.send(message, to: session) { error in
    }
  }

步骤6 同步一起听的歌曲状态和进度

  1. 房主通过 NIMSDK.shared().chatManager.send 发送定向消息,将播放进度同步给观众。
  2. 观众调用NIMSDK.shared().chatManager.send 接口获取歌曲播放进度。
NIMSDK.shared().chatManager.add(self)

 // 收到消息回调
  func onRecvMessages(_ messages: [NIMMessage]) {
    print("@@#🏷️ onRecvMessages success:", messages)
    if let caseModel = lastCaseModel {
      NEHawkManager.shared().sendMessage(caseModel)
    }
      for message in messages {
          if message.messageType == .text{
              var string = message.text
              ///json 转 dic
              let draft = string?.data(using: String.Encoding.utf8)
              let d = try? JSONSerialization.jsonObject(with: draft!, options: .mutableContainers)
              let dic = d as! NSDictionary
              guard let commandId = dic["commandId"] else{
                  return
              }
              if (commandId as AnyObject).intValue == 10001{
                  let m = NIMMessage()
                     m.text = "{\"commandId\": 10002,\"progress\" : 1000}"// 可以设置自定义消息,1000 为播放进度,单位是ms
                     m.toAccIds = [String]
                     let session = NIMSession(roomId, type: .chatroom)
                     let session = NIMSession(roomId, type: .chatroom)
                      NIMSDK.shared().chatManager.send(message, to: session) { error in
                     }
          }else if (commandId as AnyObject).intValue == 10002{
                  //seek
                  
              NERtcEngine.shared().setEffectPositionWithId(effectId, position: dic["progress"])
              }
          }
      }
  }

进阶功能

暂停或恢复歌曲播放

// 暂停歌曲播放
  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 同步一起听的歌曲状态和进度
  • 进阶功能
  • 暂停或恢复歌曲播放
  • 切歌