为降低开发者的集成难度,声网为 K 歌房场景提供了场景化 API。场景化 API 封装了声网音视频 SDK 的 API,并提供了 K 歌业务常见的功能,例如,对主唱和伴唱进行 NTP 时间同步。你只需要调用一个场景化 API 即可实现通过多个音视频 SDK 的 API 完成的复杂代码逻辑,从而更轻松实现 K 歌场景。声网在 GitHub 上提供 KTV 场景化 API 的源码文件 KTVApi 和 KTVApiImpl。
本文介绍如何使用 KTV 场景化 API 实现点歌、独唱、合唱等基础业务功能。
开始前,请确保你的开发环境满足以下条件:
参考一下步骤来配置项目:
本节介绍如何实现点歌功能。点歌指用户通过浏览榜单或搜索关键词选定想唱的正版音乐,然后下载播放音乐。用户需要在唱歌前进行点歌。
下图展示点歌的 API 调用时序图:
调用 sharedEngineWithAppId 初始化 AgoraRtcEngineKit。请确保 AgoraRtcEngineKit 的生命周期大于 KTV API 模块的生命周期。
// 初始化 AgoraRtcEngineKit
// self.RTCkit 是定义的 AgoraRtcEngineKit 全局变量
self.RTCkit = [AgoraRtcEngineKit sharedEngineWithAppId:<Your_Agora_Appid> delegate:self];
// 在独唱场景下,将音频应用场景设为 AgoraAudioScenarioGameStreaming,在合唱场景下设为 AgoraAudioScenarioChorus
[self.RTCkit setAudioScenario:AgoraAudioScenarioGameStreaming];
[self.RTCkit setAudioProfile:AgoraAudioProfileMusicHighQuality];
[self.RTCkit setChannelProfile:AgoraChannelProfileLiveBroadcasting];
创建 KTV API 实例,并初始化 API 模块。调用 addEventHandler 注册 KTV API 事件。调用 KTV API 模块的 API 前,请确保已初始化 KTV API 实例。
 // 创建 KTVApiConfig 实例
KTVApiConfig *apiConfig = [[KTVApiConfig alloc] initWithAppId:@"<Your Agora Appid>"
                                                    rtmToken:@"<Your Agora Rtm Token>"
                                                      engine:self.RTCkit
                                                channelName:@"<Your Channel Name>"
                                                   localUid:@"<Your Uid>"
                                          chorusChannelName:@"<Your Chorus Channel Name>"
                                        chorusChannelToken:@"<Your Agora Chorus Token>"
                                                     // 设置 K 歌的场景。
                                                      type:KTVTypeNormal
                                               // 设置可缓存的音乐资源数量,最多不能超过 50。
                                               maxCacheSize:10
                                               // 设置音乐资源类型,默认为声网音乐内容中心提供的版权音乐。
                                                musicType:loadMusicTypeMcc
                                                // 设置是否开启调试模式,默认为 false。
                                                isDebugMode: false];
self.ktvApi = [[KTVApiImpl alloc] initWithConfig:apiConfig];
// 更新数据流 ID。一个频道中最多只能创建 5 个数据流。 
[self.ktvApi renewInnerDataStreamId];
// 注册 KTVAPI 事件。
[self.ktvApi addEventHandlerWithKtvApiEventHandler:self];
KTVApiDelegate 下的 getMusicContentCenter 获取音乐内容中心实例,再调用 KTVApiDelegate 下的 renewToken 来更新 Token。KTVApiDelegate 下的 renewToken 来更新 Token。AgoraRtcEngineDelegate 类下的 tokenPrivilegeWillExpire 回调,你需要调用 AgoraRtcEngineKit 下的 renewToken 来更新 Token。通过关键词搜索或音乐榜单获取歌曲列表。
// 通过关键词搜索歌曲
[self.ktvApi fetchMusicChartsWithCompletion:^(NSString *requestId , AgoraMusicContentCenterStatusCode code , NSArray<AgoraMusicChartInfo *> *info) {
    }];
- (void)loadSearchDataWithKeyWord:(NSString *)keyWord {
    NSDictionary *dict = @{
        @"pitchType": @(1),
        @"needLyric": @(YES) // 用来过滤没有歌词的歌曲
    };
    NSString *extra = [NSString convertToJsonData:dict];
    [self.ktvApi searchMusicWithKeyword:keyWord ?: @""
                                   page:self.page
                               pageSize:50
                             jsonOption:extra
                             completion:^(NSString * _Nonnull, AgoraMusicContentCenterStatusCode, AgoraMusicCollection * _Nonnull) {
                                 // 结果返回
                             }];
}
// 用音乐榜单获取歌曲
- (void)loadDataWithIndex:(NSInteger)pageType {
    NSArray *chartIds = @[@(3), @(4), @(2), @(6)];
    NSInteger chartId = [[chartIds objectAtIndex:MIN(pageType - 1, chartIds.count - 1)] intValue];
    NSDictionary *dict = @{
        @"pitchType": @(1),
        @"needLyric": @(YES) // 用来过滤没有歌词的歌曲
    };
    NSString *extra = [NSString convertToJsonData:dict];
    [self.ktvApi searchMusicWithMusicChartId:chartId
                                        page:self.page
                                    pageSize:20
                                  jsonOption:extra
                                  completion:^(NSString * _Nonnull, AgoraMusicContentCenterStatusCode, AgoraMusicCollection * _Nonnull) {
                                      // 结果返回
                                  }];
}
jsonOption 字段来筛选有副歌片段、支持打分的音乐资源。jsonOption 字段的具体说明见 searchMusic[1/2] 及 searchMusic[2/2] 的 API 文档。调用 loadMusic 加载歌曲。该方法中你需要传入歌曲编号并进行歌曲加载配置。歌曲加载结果会通过 IMusicLoadStateListener 接口类下的回调通知你。在调用 loadMusic 时,你需要传入下列参数:
songCode:歌曲编号,可参考步骤 2获取。如果你需要播放歌曲的副歌片段,需要先调用 getInternalSongCode 方法为该片段创建一个编号,作为该片段的唯一标识。当你获取到该片段的编号后,调用 loadMusic 并将获取到的副歌片段编号传入 songCode 参数来加载该副歌片段。
config:歌曲加载配置,包含下列参数:
autoPlay:歌曲加载完成后是否自动播放。
true:自动播放歌曲,此时用户角色会自动切换为独唱者。
false:不自动播放歌曲。歌曲加载成功后如果听众需要开始独唱或加入合唱,则需要在 onMusicLoadSuccess 回调内调用 switchSingerRole 切换为对应的角色然后调用 startSing 播放歌曲。
autoPlay 必须设为 NO。mode:歌曲加载的模式,默认为加载歌曲及歌词。
mainSingerUid:独唱者的用户 ID。
IMusicLoadStateListener:监听歌曲加载状态的协议。
// 示例代码为用户点歌后需要开始独唱的场景,你可以选择下列任意一个方案开始独唱。
// 方案 1:autoPlay 设置为 YES, 歌曲加载成功后会自动将用户角色切换为独唱者(SoloSinger)并开始播放歌曲。
// mode 设为 KTVLoadMusicModeLoadLrcMusicAndLrc,同时加载歌曲和歌词。
KTVSongConfiguration *songConfig = [[KTVSongConfiguration alloc] init];
songConfig.autoPlay = YES;
songConfig.mode = KTVLoadMusicModeLoadLrcMusicAndLrc;
songConfig.mainSingerUid = mainSingerUid;
[self.ktvApi loadMusicWithSongCode:songCode
                            config:songConfig
             onMusicLoadStateListener:self];
// 歌曲加载进度回调
- (void)onMusicLoadProgressWithSongCode:(NSInteger)songCode
                                percent:(NSInteger)percent
                                 status:(AgoraMusicContentCenterPreloadStatus)status
                                    msg:(NSString *)msg
                               lyricUrl:(NSString *)lyricUrl {
}
// 方案 2:autoPlay 设置为 NO 时, 歌曲加载成功后不会自动播放。
KTVSongConfiguration* songConfig = [[KTVSongConfiguration alloc] init];
songConfig.autoPlay = NO;
// 同时加载歌曲和歌词。
songConfig.mode = KTVLoadMusicModeLoadMusicAndLrc;
songConfig.mainSingerUid = mainSingerUid;
// 歌曲加载失败的原因。
- (void)onMusicLoadFailWithSongCode:(NSInteger)songCode reason:(enum KTVLoadSongFailReason)reason {
}
// 歌曲加载成功的回调,在此回调内调用 switchSingerRole 切换角色,调用 startSing 开始播放歌曲。
- (void)onMusicLoadSuccessWithSongCode:(NSInteger)songCode lyricUrl:(NSString * _Nonnull)lyricUrl {
    // 切换角色
    [weakSelf.ktvApi switchSingerRoleWithNewRole:KTVSingRoleSoloSinger
                               onSwitchRoleState:^(KTVSwitchRoleState state, KTVSwitchRoleFailReason reason) {
        // 切换角色状态回调。
    }];
    // 开始播放歌曲。
    [weakSelf.ktvApi startSingWithSongCode:songCode startPos:0];
}
[self.ktvApi loadMusicWithSongCode:songCode config:songConfig onMusicLoadStateListener:self];
// 歌曲加载进度回调。
- (void)onMusicLoadProgressWithSongCode:(NSInteger)songCode
                                percent:(NSInteger)percent
                                 status:(AgoraMusicContentCenterPreloadStatus)status
                                    msg:(NSString *)msg
                               lyricUrl:(NSString *)lyricUrl {
}
KTV API 初始化时默认用户角色为听众,如果需要开始独唱或加入合唱,需要调用 switchSingerRole 来切换至相应的角色。 KTV API 内部会根据角色的切换来控制演唱过程中音乐播放器的播放、同步,以及订阅和发布音频流的行为。你还可以通过 onSingerRoleChanged 回调来获取角色切换的相关信息。
请参考下图来进行角色切换:
[self.ktvApi switchSingerRoleWithNewRole:role
                   // 通过闭包 onSwitchRoleState 返回切换角色的状态和失败原因。
                   onSwitchRoleState:^(KTVSwitchRoleState state, KTVSwitchRoleFailReason reason) {
    if (state == KTVSwitchRoleStateFail && reason != KTVSwitchRoleFailReasonNoPermission) {
        return;
    }
}];
// 角色已切换回调。
- (void)onSingerRoleChangedWithOldRole:(KTVSingRole)oldRole newRole:(KTVSingRole)newRole {
}
歌曲播放时,音乐播放器会通过 onMusicPlayerStateChanged 回调向业务层通知歌曲播放状态改变。收到 onMusicPlayerStateChanged(AgoraMediaPlayerStateOpenCompleted) 回调后,你可以使用 seekSing、pauseSing、resumeSing 等方法控制播放器。
onMusicPlayerStateChanged 回调获取远端播放器的状态。// 跳转到指定时间播放歌曲
[self.ktvApi seekSing: time];
退出 K 歌场景时,你需要调用 cleanCache 释放 KTV API 模块内的资源和取消注册事件回调。请确保 AgoraRtcEngineKit 的生命周期大于 KTV API 模块。释放资源时,请在释放 KTV API 模块之后再释放 AgoraRtcEngineKit。
[self.ktvApi cleanCache];
本节介绍如何实现独唱功能。用户点歌后,可以开始独唱,K 歌房内的听众都可以听到其演唱。房间内想与独唱者连麦语聊的听众可以上麦。
独唱场景下存在两种角色:
下图展示独唱的 API 调用时序图:
调用 joinChannelByToken 让独唱者加入频道。为保证高音质,声网推荐你在加入频道前调用 SetAudioProfile 将音频编码属性设为 AgoraAudioProfileMusicHighQuality,调用 setAudioScenario 将音频场景设为 AgoraAudioScenarioGameStreaming。
// 加入频道
[self.rtcKit joinChannelByToken:<YOUR TOKEN>
                    channelId:<YOUR CHANNEL NAME>
                           info:nil
                            uid:<YOUR UID>
                   // 媒体选项,详见第 5 步操作。
                   mediaOptions:mediaOption
                   joinSuccess:nil];
调用 loadMusic 加载歌曲。在调用该方法时,如果你将 autoPlay 设为 YES,歌曲加载完成后会自动播放,用户角色会自动设为独唱者(SoloSinger)。如果设置为 NO,则需在 onMusicLoadSuccess 回调内调用 switchSingerRole 将用户角色设为 SoloSinger,然后调用 startSing 才可以播放歌曲。
// 方案 1:加载歌曲后自动播放。
KTVSongConfiguration* songConfig = [[KTVSongConfiguration alloc] init];
songConfig.autoPlay = YES;
songConfig.mode = KTVLoadMusicModeLoadMusicAndLrc;
songConfig.mainSingerUid = mainSingerUid;
[self.ktvApi loadMusicWithSongCode:songCode config:songConfig onMusicLoadStateListener:self];
// 歌曲加载失败。
- (void)onMusicLoadFailWithSongCode:(NSInteger)songCode reason:(enum KTVLoadSongFailReason)reason {
}
// 歌曲加载成功。
- (void)onMusicLoadSuccessWithSongCode:(NSInteger)songCode lyricUrl:(NSString * _Nonnull)lyricUrl {
}
// 歌曲加载进度回调。
- (void)onMusicLoadProgressWithSongCode:(NSInteger)songCode
                               percent:(NSInteger)percent
                                status:(AgoraMusicContentCenterPreloadStatus)status
                                   msg:(NSString *)msg
                              lyricUrl:(NSString *)lyricUrl {
}
// 方案 2:autoPlay 设置为 NO 时, 歌曲加载成功后不会自动播放。
KTVSongConfiguration* songConfig = [[KTVSongConfiguration alloc] init];
songConfig.autoPlay = NO;
// 同时加载歌曲和歌词。
songConfig.mode = KTVLoadMusicModeLoadMusicAndLrc;
songConfig.mainSingerUid = mainSingerUid;
// 歌曲加载失败的原因。
- (void)onMusicLoadFailWithSongCode:(NSInteger)songCode reason:(enum KTVLoadSongFailReason)reason {
}
// 歌曲加载成功的回调,在此回调内调用 switchSingerRole 切换角色,调用 startSing 开始播放歌曲。
- (void)onMusicLoadSuccessWithSongCode:(NSInteger)songCode lyricUrl:(NSString * _Nonnull)lyricUrl {
    // 切换角色
    [weakSelf.ktvApi switchSingerRoleWithNewRole:KTVSingRoleSoloSinger
                               onSwitchRoleState:^(KTVSwitchRoleState state, KTVSwitchRoleFailReason reason) {
        // 切换角色状态回调。
    }];
    // 开始播放歌曲。
    [weakSelf.ktvApi startSingWithSongCode:songCode startPos:0];
}
[self.ktvApi loadMusicWithSongCode:songCode config:songConfig onMusicLoadStateListener:self];
// 歌曲加载进度回调。
- (void)onMusicLoadProgressWithSongCode:(NSInteger)songCode
                                percent:(NSInteger)percent
                                 status:(AgoraMusicContentCenterPreloadStatus)status
                                    msg:(NSString *)msg
                               lyricUrl:(NSString *)lyricUrl {
}
当歌曲播放完成或切歌后,你需要调用 switchSingerRole 将用户的角色切回听众。
[self.ktvApi switchSingerRoleWithNewRole:KTVSingRoleAudience
                      onSwitchRoleState:^(KTVSwitchRoleState state, KTVSwitchRoleFailReason reason) {
                      }];
独唱者停止唱歌或希望暂时关闭麦克风时,可以调用 adjustRecordingSignalVolume,将音频采集信号音量设置为 0。
[self.RTCKit adjustRecordingSignalVolume:0];
通过 updateChannelWithMediaOptions 方法在主播加入频道后更新频道媒体选项,例如是否开启本地音频采集,是否发布本地音频流等。
AgoraRtcChannelMediaOptions* options = [AgoraRtcChannelMediaOptions new];
// 发布本地麦克风流。
options.publishMicrophoneTrack = YES;
// 启用音频采集和播放。
options.enableAudioRecordingOrPlayout = YES;
// 设置角色为主播。
options.clientRoleType = AgoraClientRoleBroadcaster;
// 更新媒体选项。
[self.RTCKit updateChannelWithMediaOptions:options];
调用 joinChannelByToken 让听众加入频道。
// 加入频道
[self.rtcKit joinChannelByToken:<YOUR TOKEN>
                    channelId:<YOUR CHANNEL NAME>
                           info:nil
                            uid:<YOUR UID>
                   // 媒体选项,详见第 3 步操作。
                   mediaOptions:mediaOption
                   joinSuccess:nil];
调用 loadMusic 加载歌词。听众加入频道后,默认订阅独唱者发布的音频合流,即独唱者人声和音乐混合的音频流,因此只需要加载歌词。
KTVSongConfiguration* songConfig = [[KTVSongConfiguration alloc] init];
// 用户角色为听众,autoPlay 必须设为 NO。
songConfig.autoPlay = NO;
// 仅加载歌词。
songConfig.mode = KTVLoadMusicModeLoadLrcOnly;
songConfig.mainSingerUid = mainSingerUid;
[self.ktvApi loadMusicWithSongCode:songCode config:songConfig onMusicLoadStateListener:self];
// 歌曲加载失败回调。
- (void)onMusicLoadFailWithSongCode:(NSInteger)songCode reason:(enum KTVLoadSongFailReason)reason {
}
// 歌曲加载成功回调。
- (void)onMusicLoadSuccessWithSongCode:(NSInteger)songCode lyricUrl:(NSString * _Nonnull)lyricUrl {
}
// 歌曲加载进度回调。
- (void)onMusicLoadProgressWithSongCode:(NSInteger)songCode
                               percent:(NSInteger)percent
                                status:(AgoraMusicContentCenterPreloadStatus)status
                                   msg:(NSString *)msg
                              lyricUrl:(NSString *)lyricUrl {
}
通过 updateChannelWithMediaOptions 方法在听众加入频道后更新频道媒体选项,例如是否开启本地音频采集,是否发布本地音频流等。
听众的用户角色为 AgoraClientRoleAudience,因此无法在频道内发布音频流。如果听众想上麦与独唱者语聊,需要将用户角色修改为 AgoraClientRoleBroadcaster。修改角色后,SDK 默认发布该连麦听众的音频流,独唱者和其他听众都能听到连麦听众的声音。
// 对需要上麦聊天的听众更新媒体选项。
AgoraRtcChannelMediaOptions* options = [AgoraRtcChannelMediaOptions new];
// 发布本地麦克风流。
options.publishMicrophoneTrack = YES;
// 启用音频采集和播放。
options.enableAudioRecordingOrPlayout = YES;
// 设置角色为主播。
options.clientRoleType = AgoraClientRoleBroadcaster;
// 更新媒体选项。
[self.RTCKit updateChannelWithMediaOptions:options];
// 对未上麦的听众更新媒体选项。
AgoraRtcChannelMediaOptions* options = [AgoraRtcChannelMediaOptions new];
// 不发布本地麦克风流。
options.publishMicrophoneTrack = NO;
// 启用音频采集和播放。
options.enableAudioRecordingOrPlayout = YES;
// 设置角色为观众。
options.clientRoleType = AgoraClientRoleAudience;
// 更新媒体选项。
[self.RTCKit updateChannelWithMediaOptions:options];
本节介绍如何实现合唱功能。独唱者点歌开唱,伴唱加入后独唱者成为领唱,K 歌房内的听众都可以听到合唱。房间内想与领唱或伴唱连麦语聊的听众可以上麦。
合唱场景下存在四种角色:
下图展示合唱的 API 调用时序图:
调用 joinChannelByToken 加入频道。为保证高音质,声网推荐你在加入频道前调用 SetAudioProfile 将音频编码属性设为 AgoraAudioProfileMusicHighQuality,调用 setAudioScenario 将音频场景设为 AgoraAudioScenarioGameStreaming。
// 加入频道
[self.rtcKit joinChannelByToken:<YOUR TOKEN>
                    channelId:<YOUR CHANNEL NAME>
                           info:nil
                            uid:<YOUR UID>
                   // 媒体选项,详见第 4 步操作。
                   mediaOptions:mediaOption
                   joinSuccess:nil];
调用 loadMusic 加载歌曲。在调用该方法时,如果你将 autoPlay 设为 YES,歌曲加载完成后会自动播放,用户角色会自动设为独唱者(SoloSinger)。如果设置为 NO,则需在 onMusicLoadSuccess 回调内调用 switchSingerRole 将用户角色设为 SoloSinger,然后调用 startSing 才可以播放歌曲。
// 方案 1:加载歌曲后自动播放。
KTVSongConfiguration* songConfig = [[KTVSongConfiguration alloc] init];
songConfig.autoPlay = YES;
songConfig.mode = KTVLoadMusicModeLoadMusicAndLrc;
songConfig.mainSingerUid = mainSingerUid;
[self.ktvApi loadMusicWithSongCode:songCode config:songConfig onMusicLoadStateListener:self];
// 歌曲加载失败。
- (void)onMusicLoadFailWithSongCode:(NSInteger)songCode reason:(enum KTVLoadSongFailReason)reason {
}
// 歌曲加载成功。
- (void)onMusicLoadSuccessWithSongCode:(NSInteger)songCode lyricUrl:(NSString * _Nonnull)lyricUrl {
}
// 歌曲加载进度回调。
- (void)onMusicLoadProgressWithSongCode:(NSInteger)songCode
                               percent:(NSInteger)percent
                                status:(AgoraMusicContentCenterPreloadStatus)status
                                   msg:(NSString *)msg
                              lyricUrl:(NSString *)lyricUrl {
}
// 方案 2:autoPlay 设置为 NO 时, 歌曲加载成功后不会自动播放。
KTVSongConfiguration* songConfig = [[KTVSongConfiguration alloc] init];
songConfig.autoPlay = NO;
// 同时加载歌曲和歌词。
songConfig.mode = KTVLoadMusicModeLoadMusicAndLrc;
songConfig.mainSingerUid = mainSingerUid;
// 歌曲加载失败的原因。
- (void)onMusicLoadFailWithSongCode:(NSInteger)songCode reason:(enum KTVLoadSongFailReason)reason {
}
// 歌曲加载成功的回调,在此回调内调用 switchSingerRole 切换角色,调用 startSing 开始播放歌曲。
- (void)onMusicLoadSuccessWithSongCode:(NSInteger)songCode lyricUrl:(NSString * _Nonnull)lyricUrl {
    // 切换角色
    [weakSelf.ktvApi switchSingerRoleWithNewRole:KTVSingRoleSoloSinger
                               onSwitchRoleState:^(KTVSwitchRoleState state, KTVSwitchRoleFailReason reason) {
        // 切换角色状态回调。
    }];
    // 开始播放歌曲。
    [weakSelf.ktvApi startSingWithSongCode:songCode startPos:0];
}
[self.ktvApi loadMusicWithSongCode:songCode config:songConfig onMusicLoadStateListener:self];
// 歌曲加载进度回调。
- (void)onMusicLoadProgressWithSongCode:(NSInteger)songCode
                                percent:(NSInteger)percent
                                 status:(AgoraMusicContentCenterPreloadStatus)status
                                    msg:(NSString *)msg
                               lyricUrl:(NSString *)lyricUrl {
}
有人加入合唱成为伴唱时,独唱者需将其用户角色切换为领唱;最后一个伴唱者退出合唱时,领唱需将其用户角色切回独唱者;歌曲结束后,需将用户角色切为听众。
// 听众加入合唱成为伴唱,独唱者切换角色为领唱。
[self.ktvApi switchSingerRoleWithNewRole:KTVSingRoleLeadSinger
                               onSwitchRoleState:^(KTVSwitchRoleState state, KTVSwitchRoleFailReason reason) {
        }];
// 最后一个伴唱退出合唱,领唱切换角色为独唱。
[self.ktvApi switchSingerRoleWithNewRole:KTVSingRoleSoloSinger
                               onSwitchRoleState:^(KTVSwitchRoleState state, KTVSwitchRoleFailReason reason) {
        }];
// 歌曲结束后,将角色切换为听众。
[self.ktvApi switchSingerRoleWithNewRole:KTVSingRoleAudience
                               onSwitchRoleState:^(KTVSwitchRoleState state, KTVSwitchRoleFailReason reason) {
        }];
通过 updateChannelWithMediaOptions 方法在主播加入频道后更新频道媒体选项,例如是否开启本地音频采集,是否发布本地音频流等。
AgoraRtcChannelMediaOptions* options = [AgoraRtcChannelMediaOptions new];
// 发布本地麦克风流
options.publishMicrophoneTrack = YES;
// 启用音频采集和播放
options.enableAudioRecordingOrPlayout = YES;
// 设置角色为主播
options.clientRoleType = AgoraClientRoleBroadcaster;
// 更新媒体选项
[self.RTCKit updateChannelWithMediaOptions:options];
调用 joinChannelByToken 让伴唱加入频道。
// 加入频道
[self.rtcKit joinChannelByToken:<YOUR TOKEN>
                    channelId:<YOUR CHANNEL NAME>
                          info:nil
                           uid:<YOUR UID>
                 // 媒体选项,详见第 4 步操作。
                 mediaOptions:mediaOption
                 joinSuccess:nil];
听众加入合唱成为伴唱前,需调用 loadMusic 加载歌曲。歌曲加载完成后,调用 switchSingerRole 将用户角色设为伴唱(CoSinger)。
KTVSongConfiguration* songConfig = [[KTVSongConfiguration alloc] init];
// 不开启自动播放。
songConfig.autoPlay = NO;
// 仅加载歌曲。
songConfig.mode = KTVLoadMusicModeLoadMusicOnly;
songConfig.mainSingerUid = mainSingerUid;
[self.ktvApi loadMusicWithSongCode:songCode config:songConfig onMusicLoadStateListener:self];
// 歌曲加载失败。
- (void)onMusicLoadFailWithSongCode:(NSInteger)songCode reason:(enum KTVLoadSongFailReason)reason {
}
// 歌曲加载成功。
- (void)onMusicLoadSuccessWithSongCode:(NSInteger)songCode lyricUrl:(NSString * _Nonnull)lyricUrl {
    // 切换角色为伴唱。
    [weakSelf.ktvApi switchSingerRoleWithNewRole:KTVSingRoleCoSinger
                               onSwitchRoleState:^(KTVSwitchRoleState state, KTVSwitchRoleFailReason reason) {
        }];
}
当歌曲结束或伴唱想要中途退出合唱时,调用 switchSingerRole 将用户角色切换为听众。
[self.ktvApi switchSingerRoleWithNewRole:KTVSingRoleAudience
                      onSwitchRoleState:^(KTVSwitchRoleState state, KTVSwitchRoleFailReason reason) {
}];
通过 updateChannelWithMediaOptions 方法在伴唱加入频道后更新频道媒体选项,例如是否开启本地音频采集,是否发布本地音频流等。
AgoraRtcChannelMediaOptions* options = [AgoraRtcChannelMediaOptions new];
// 发布本地麦克风流
options.publishMicrophoneTrack = YES;
// 启用音频采集和播放
options.enableAudioRecordingOrPlayout = YES;
// 设置角色为主播
options.clientRoleType = AgoraClientRoleBroadcaster;
// 更新媒体选项
[self.RTCKit updateChannelWithMediaOptions:options];
调用 joinChannelByToken 让听众加入频道。
// 加入频道
[self.rtcKit joinChannelByToken:<YOUR TOKEN>
                    channelId:<YOUR CHANNEL NAME>
                           info:nil
                            uid:<YOUR UID>
                   // 媒体选项,详见第 xxx。
                   mediaOptions:mediaOption
                   joinSuccess:nil];
调用 loadMusic 加载歌词。听众加入频道后,默认订阅领唱人声和音乐混合的音频流,默认订阅伴唱人声,因此只需要加载歌词。
KTVSongConfiguration* songConfig = [[KTVSongConfiguration alloc] init];
// 用户角色为听众,autoPlay 必须设为 NO。
songConfig.autoPlay = NO;
// 仅加载歌词。
songConfig.mode = KTVLoadMusicModeLoadLrcOnly;
songConfig.mainSingerUid = mainSingerUid;
[self.ktvApi loadMusicWithSongCode:songCode config:songConfig onMusicLoadStateListener:self];
// 加载失败。
- (void)onMusicLoadFailWithSongCode:(NSInteger)songCode reason:(enum KTVLoadSongFailReason)reason {
}
// 加载成功。
- (void)onMusicLoadSuccessWithSongCode:(NSInteger)songCode lyricUrl:(NSString * _Nonnull)lyricUrl {
}
// 报告加载进度。
- (void)onMusicLoadProgressWithSongCode:(NSInteger)songCode
                                percent:(NSInteger)percent
                                 status:(AgoraMusicContentCenterPreloadStatus)status
                                    msg:(NSString *)msg
                               lyricUrl:(NSString *)lyricUrl {
}
通过 updateChannelWithMediaOptions 方法在听众加入频道后更新频道媒体选项,例如是否开启本地音频采集,是否发布本地音频流等。
听众的用户角色为 AgoraClientRoleAudience,因此无法在频道内发布音频流。如果听众想上麦与主唱/伴唱语聊,需要将用户角色修改为 AgoraClientRoleBroadcaster。修改角色后,SDK 默认发布该连麦听众的音频流,主唱、伴唱、其他听众都能听到连麦听众的声音。
// 对需要上麦聊天的听众更新媒体选项。
AgoraRtcChannelMediaOptions* options = [AgoraRtcChannelMediaOptions new];
// 发布本地麦克风流。
options.publishMicrophoneTrack = YES;
// 启用音频采集和播放。
options.enableAudioRecordingOrPlayout = YES;
// 设置角色为主播。
options.clientRoleType = AgoraClientRoleBroadcaster;
// 更新媒体选项。
[self.RTCKit updateChannelWithMediaOptions:options];
// 对未上麦的听众更新媒体选项。
AgoraRtcChannelMediaOptions* options = [AgoraRtcChannelMediaOptions new];
// 不发布本地麦克风流。
options.publishMicrophoneTrack = NO;
// 启用音频采集和播放。
options.enableAudioRecordingOrPlayout = YES;
// 设置角色为观众。
options.clientRoleType = AgoraClientRoleAudience;
// 更新媒体选项。
[self.RTCKit updateChannelWithMediaOptions:options];
声网基于独唱推出抢唱功能,本节介绍如何实现副歌片段抢唱功能。
默认由房主播放副歌片段(播放的实现逻辑同独唱),听众可以上麦进行抢唱,抢到麦后房主停止播放,抢到麦的人播放副歌片段并进行独唱,K 歌房内的听众都可以听到其演唱。
抢唱场景下存在两种角色:
独唱者:加入频道,加载并播放歌曲。KTV API 模块内部控制音乐播放器播放音乐,发布音乐到远端,将音乐播放进度同步到远端,让歌词组件进入歌词滚动状态等逻辑。
听众:加入频道,加载歌词。KTV API 模块内部控制听众订阅独唱者的人声和音乐的音频合流,同步独唱者的音乐播放进度,让歌词组件进入歌词滚动状态等逻辑。如果听众需要上麦聊天,可以更新媒体选项。
下图展示抢唱的 API 调用时序图:
与独唱方案相比,抢唱方案中仅需在 KTVApiConfig 中将 KTVType 设为 SingBattle(抢唱场景),独唱者实现和听众实现也仅在加载及播放这一步骤有差异。因此,你可以参考独唱方案来实现独唱者和听众逻辑。
在播放副歌片段之前,你需要先调用 getInternalSongCode 方法为该片段创建一个编号,作为该片段的唯一标识。当你获取到该片段的编号后,调用 loadMusic 并将获取到的副歌片段编号传入 songCode 参数来加载该副歌片段。
抢唱场景下的播放和演唱的音乐资源为副歌片段,你可以参考下列步骤来获取副歌片段的总分:
通过 onHighPartTime 回调获取副歌片段开始和结束时间。
调用歌词组件中的 parseLyricData 方法解析歌词文件数据,获取歌词数据模型。
通过歌词的行数来计算副歌片段的总分,示例代码如下:
  - (void)dealWithBattleSong:(LyricModel *)lyricsModel {
 // 确保传入的歌词模型不为空
 if (!lyricsModel) {
     return;
 }
 // 初始化行数计数器
 NSInteger lineCount = 0;
 // 遍历歌词数据模型中的每一行歌词
 for (NSInteger index = 0; index < lyricsModel.lines.count; index++) {
     // 获取当前歌词的行数
     LyricLine *line = lyricsModel.lines[index];
     // 检查当前歌词行的开始时间是否处于副歌片段开始和结束时间的范围内
     if (line.beginTime >= highStartTime && highEndTime >= line.beginTime + line.duration) {
         // 打印匹配的行信息,并增加行数计数
         NSLog(@"%ld -> s: %f, e: %f", (long)index, line.beginTime, line.beginTime + line.duration);
         lineCount++;
     }
 }
 // 总分=行数*100
 localTotalScore = lineCount * 100;
}
本文集成步骤中使用如下 API: