实现一起听(基于NERoom 房间组件)

更新时间: 2023/08/30 06:04:31

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

方案架构

一起听的架构.png

开发环境要求

开发环境要求如下:

环境要求 说明
iOS 版本 9.0 及以上
CPU 架构 ARM64、ARMV7
IDE XCode
其他 安装 CocoaPods。

前提条件

示例项目源码

一起听的示例项目源码地址请参见 一起听 NEListenTogether 示例项目源码

示例项目的目录结构和跑通示例项目的步骤请参见跑通示例项目

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:刷新歌词

房间管理

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

    您可以通过自己的业务服务器调用 NERoom Server 的接口去创建房间,客户端通过业务服务器提供的restful api创建房间。

  2. 房主和连麦观众分别调用 joinRoom 接口加入一起听房间。加入房间成功后,房间内用户触发 onMemberJoinRoom 回调。

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

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

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

  5. 连麦观众调用 leaveRoom 接口离开一起听房间。

  6. 房主调用 endRoom 接口结束一起听房间。

示例代码

// 房主调用自己的业务服务器restful api创建房间。
// 房主或者观众加入房间:

    // 进入房间
    let joinParams = NEJoinRoomParams()
    joinParams.roomUuid = "123"
    joinParams.userName = "userName"
    joinParams.role = "host" // host为房主,audience为观众
    let joinOptions = NEJoinRoomOptions()
    NERoomKit.shared().roomService.joinRoom(params: joinParams,
                                            options: joinOptions) { [weak self] joinCode, joinMsg, context in
      guard let context = context else {
        
        )
        callback?(joinCode, joinMsg, nil)
        return
      }
      self.roomContext = context
      context.addRoomListener(listener: self)
      context.seatController.addSeatListener(self)
      // 加入chatroom、rtc
       // 加入rtc
     context.rtcController.joinRtcChannel { rtcCode, rtcMsg, _ in
      guard rtcCode == 0 else {
        // 加入rtc 失败,离开房间
        context.leaveRoom()
        callback?(rtcCode, rtcMsg, nil)
        return
      }
      // 加入聊天室
      context.chatController.joinChatroom { chatCode, chatMsg, _ in
        guard chatCode == 0 else {
          // 加入聊天室失败,离开房间
          context.leaveRoom()
          callback?(chatCode, chatMsg, nil)
          return
        }
        callback?(chatCode, nil, nil)
      }
    }
    }

 // 观众离开房间
 self.roomContext.leaveRoom(null);
 // 房主结束房间
 self.roomContext.endRoom(false,null);

实现一起听

步骤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 房主播放歌曲

调用 NERoomRtcController.playEffect 播放歌曲。

示例代码如下:

var originPath:String = 本地歌曲路径 
//如果使用版权SDK 可通过以下方法获取路径
//NECopyrightedMedia.getInstance()
      .getSongURI(songId, channel: channel, songResType: songResType)
     
  if (originPath.length > 0) {
    let option = NERoomCreateAudioEffectOption()
    option.path = originPath
    option.sendEnabled = false
    option.playbackEnabled = true
    option.sendVolume = 100
    option.playbackVolume = 100
    option.progressInterval = 100
    NSInteger code =
    NERoomContext roomContext = NERoomKit.getInstance().getRoomService().getRoomContext(roomUuid);
          let code = roomContext.rtcController.playEffect(
        effectId: effectId,
        option: option
      )
        

步骤5 观众上麦

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

  1. 观众加入房间后,主播调用 NESeatController.sendSeatInvitation 接口邀请观众上麦。上麦成功后,房间内所有成员收到 onSeatInvitationAccepted 的回调和 onSeatListChanged 的回调。
  2. 观众调用业务服务器接口获取当前播放歌曲列表。
  3. 观众调用 NECopyrightedMedia.preloadSong 接口预加载歌曲。
// 观众进入房间,主播抱麦,观众自动上麦
NERoomContext roomContext = NERoomKit.getInstance().getRoomService().getRoomContext(roomUuid);
if (roomContext!=null){
        roomContext!.seatController.sendSeatInvitation(seatIndex,
                                                          userUuid: account) { code, msg, _ in
        if code == 0 {
          
        } else {
        }
        callback?(code, msg, nil)
      }
}
// 调用业务服务器获取当前播放歌曲和歌曲列表(需自行实现)
// 预加载当前播放歌曲,在加载完后通过点对点自定义消息向主播询问当前播放进度
String songId="your songId";
int channel=1;
if (NECopyrightedMedia.getInstance().isSongPreloaded(songId,channel)){
  // 向主播询问当前歌曲播放进度
  {
      NERoomKit.shared().messageChannelService
        .sendCustomMessage(roomUuid: roomUuid,
                           userUuid: anotherUserUuid,
                           commandId: 10001,
                           data: null) { code, msg, _ in
        if code == 0 {
          
        } else {
        }
        callback?(code, msg, nil)
      }
      }
}else {
  NECopyrightedMedia.getInstance().preloadSong(songId, channel: channel, observe: self)
}

//下载完成回调
  - (void)onPreloadComplete:(NSString *)songId
                            channel:(SongChannel)channel
                              error:(NSError *)error {
                                // 向主播询问当前歌曲播放进度
    [[NEVoiceRoomKit getInstance] sendCustomMessage:anotherUserUuid
                                              commandId:10001
                                                   data:""
                                               callback:nil];
  }

    

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

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

  2. 观众调用 NEMessageChannelService.sendCustomMessage 接口获取歌曲播放进度。

    
// 监听来自主播的自定义消息
NERoomKit.shared().messageChannelService.addMessageChannelListener(listener: self)

  /// 接收自定义消息
  func onReceiveCustomMessage(message: NECustomMessage) {
    guard let dic = message.data.toDictionary() else { return }
    guard roomContext != nil else { return }
    if message.commandId == 10001 {
      //接收到获取进度消息
      ///发送播放进度

      NERoomKit.shared().messageChannelService
        .sendCustomMessage(roomUuid: roomUuid,
                           userUuid: anotherUserUuid,
                           commandId: 10002,
                           data: data) { code, msg, _ in
        if code == 0 {
          //成功
        } else {
          //失败
        }
      }


    } else if message.commandId == 10002 {
      //接受到播放进度消息
        //seek
      roomContext?.rtcController.setEffectPosition(effectId:EffectId, postion: position)
    } 
  }

进阶功能

暂停或恢复歌曲播放

// 暂停歌曲播放
roomContext.getRtcController().pauseEffect(effectId);
// 恢复歌曲播放
roomContext.getRtcController().resumeEffect(effectId);

切歌

// 停止播放上一首歌
roomContext.getRtcController().stopEffect(effectId);
// 播放下一首歌
let option = NERoomCreateAudioEffectOption()
    option.path = "";// 音乐文件路径
    option.loopCount = 1
    option.sendEnabled = false
    option.playbackEnabled = true
    option.sendVolume = 100
    option.playbackVolume = 100
    option.progressInterval = 100
    option
      .sendWithAudioType = NERoomAudioStreamType(rawValue: sendWithAudioType.rawValue) ?? .main

 self.roomContext!.rtcController.playEffect(
        effectId: effectId,
        option: option
      )

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