# 合唱 (/zh-CN/solutions/online-ktv/ktv-scenario/build/implementation/chorus)

> For AI agents: see the complete documentation index at [llms.txt](/llms.txt).

<_PlatformTabsGroup>
<_PlatformProcessedMarker groupMode="structured" canonicalPlatform="android" platform="android" />
本文介绍如何实现合唱。在此之前，请确保你已参考[实现文档](/zh-CN/solutions/online-ktv/ktv-scenario/get-started/integrate-ktvapi)集成 KTV API。

## 方案介绍 [#方案介绍]

合唱场景下存在三种角色：

- **主唱**：在没有伴唱加入、伴唱中途退出后，演唱的人的用户角色为独唱者。独唱者开始唱歌后，如有人加入合唱，独唱则成为合唱中主唱。主唱需要加入主频道，加载并播放歌曲，发布麦克风采集的音频流。此外，主唱还需要加入合唱子频道将人声同步给伴唱。KTV API 模块内部控制音乐播放器播放音乐，同步主唱的音乐播放进度，让歌词组件进入歌词滚动状态等逻辑。
- **伴唱**：独唱者开始唱歌后，如有人加入合唱，则其成为合唱中的伴唱。伴唱需要加入合唱子频道，加载并播放歌曲，发布麦克风采集的音频流。KTV API 模块内部控制音乐播放器播放音乐，同步主唱的音乐播放进度，让歌词组件进入歌词滚动状态等逻辑。
- **听众**：加入主频道，加载歌词。KTV API 模块内部控制听众订阅主唱的人声和音乐的音频合流，订阅伴唱的人声，同步主唱的音乐播放进度，让歌词组件进入歌词滚动状态等逻辑。如果普通听众需要上麦聊天，可以更新媒体选项。

下图为合唱方案的技术原理图：

<DocsImage alt="合唱" src="https://web-cdn.agora.io/docs-portal/img/online-ktv/chorus.png" displayWidth="80%" />

## 主唱实现 [#主唱实现]

### 1. 加入频道 [#1-加入频道]

调用 [`joinChannel`](/zh-CN/api-reference/rtc/android/channel#api_irtcengine_joinchannel2) 加入频道。为保证高音质，声网推荐你在加入频道前调用 [`setAudioProfile`](/zh-CN/api-reference/rtc/android/audio/audio-basic#api_irtcengine_setaudioprofile2) 将音频编码属性设为 `MUSIC_HIGH_QUALITY`，将音频场景设为 `AUDIO_SCENARIO_GAME_STREAMING`。

```kotlin
// 加入频道
mRtcEngine.joinChannel(
    <Your_Rtc_Token>,
    <Your_Channel_Name>,
    <Your_Uid>,
    // 媒体选项详见第 4 步操作
    channelMediaOption
)
```

:::info[信息]
为同步频道内的歌词进度等状态信息，你需要在成功加入频道后调用 `renewInnerDataStreamId` 来创建数据流通道。如果退出频道后再次加入频道，你需要在加入频道成功后重新调用该方法来创建数据流通道。一个频道中最多只能创建 **5 个**数据流通道。
:::

### 2. 加载及播放歌曲 [#2-加载及播放歌曲]

调用 `loadMusic` 加载歌曲。在调用该方法后需调用 `switchSingerRole` 将用户角色设为独唱（`SoloSinger`），然后调用 `startSing` 才可以播放歌曲。

:::warning[注意]
如需加载声网提供的版权音乐，请参考[获取版权音乐](/zh-CN/solutions/online-ktv/ktv-scenario/build/extend-karaoke/get-music)。
:::

```kotlin

// 加载歌曲
ktvApiProtocol.loadMusic(
    songCode,
    KTVLoadMusicConfiguration("<SongId>", "<MainSingerUid>", KTVLoadMusicMode.LOAD_MUSIC_AND_LRC),
    object : IMusicLoadStateListener {
        override fun onMusicLoadSuccess(songCode: Long, lyricUrl: String) {
            // 加载成功后，将用户角色设为独唱者
            ktvApiProtocol.switchSingerRole(SoloSinger)
            // 开始播放
            ktvApiProtocol.startSing(songCode, 0)
        }
        // 加载失败
        override fun onMusicLoadFail(songCode: Long, reason: KTVLoadSongFailReason) {}

        // 报告加载进度
        override fun onMusicLoadProgress(songCode: Long, percent: Int, status: MusicLoadStatus, msg: String?, lyricUrl: String?) {}
    }
)
```

### 3. 切换角色 [#3-切换角色]

有人加入合唱成为伴唱时，独唱者需将其用户角色切换为主唱；最后一个伴唱者退出合唱时，主唱需将其用户角色切回独唱者；歌曲结束后，需将用户角色切为听众。

```kotlin

// 听众加入合唱成为伴唱，独唱者切换角色为主唱
ktvApiProtocol.switchSingerRole(KTVSingRole.LeadSinger, null);
// 最后一个伴唱退出合唱，主唱切换角色为独唱
ktvApiProtocol.switchSingerRole(KTVSingRole.SoloSinger, null);
// 歌曲结束后，将角色切换为听众
ktvApiProtocol.switchSingerRole(KTVSingRole.Audience, null);
```

### 4. 根据角色更新媒体选项 [#4-根据角色更新媒体选项]

通过 [`updateChannelMediaOptions`](/zh-CN/api-reference/rtc/android/channel#api_irtcengine_updatechannelmediaoptions) 方法在加入频道后更新频道媒体选项，例如是否开启本地音频采集，是否发布本地音频流等。

```kotlin
val channelMediaOption = ChannelMediaOptions()
// 发布本地麦克风流
channelMediaOption.publishMicrophoneTrack = true
// 启用音频采集和播放
channelMediaOption.enableAudioRecordingOrPlayout = true
// 设置角色为主播
channelMediaOption.clientRoleType = CLIENT_ROLE_BROADCASTER
// 更新媒体选项
mRtcEngine.updateChannelMediaOptions(channelMediaOption)
```

## 伴唱实现 [#伴唱实现]

### 1. 加入频道 [#1-加入频道-1]

调用 [`joinChannel`](/zh-CN/api-reference/rtc/android/channel#api_irtcengine_joinchannel2) 让伴唱加入频道。为保证高音质，声网推荐你在加入频道前调用 [`setAudioProfile`](/zh-CN/api-reference/rtc/android/audio/audio-basic#api_irtcengine_setaudioprofile2) 将音频编码属性设为 `MUSIC_HIGH_QUALITY`，将音频场景设为 `AUDIO_SCENARIO_GAME_STREAMING`。

```kotlin
// 加入频道
mRtcEngine.joinChannel(
    <Your_Rtc_Token>,
    <Your_Channel_Name>,
    <Your_Uid>,
    // 媒体选项详见第 4 步操作
    channelMediaOption
)
```

### 2. 加载歌曲 [#2-加载歌曲]

听众加入合唱成为伴唱前，需调用 `loadMusic` 加载歌曲。歌曲加载完成后，调用 `switchSingerRole` 将用户角色设为伴唱（`CoSinger`）。

```kotlin

// 加载歌曲
ktvApiProtocol.loadMusic(songCode, KTVLoadMusicConfiguration("<SongId>", "<MainSingerUid>", KTVLoadMusicMode.LOAD_MUSIC_ONLY), object: IMusicLoadStateListener {
    // 歌曲加载成功
    override fun onMusicLoadSuccess(songCode: Long, lyricUrl: String) {
        // 将用户角色切换为伴唱
        ktvApiProtocol.switchSingerRole(KTVSingRole.CoSinger, null)
    }
    // 歌曲加载失败
    override fun onMusicLoadFail(songCode: Long, reason: KTVLoadSongFailReason) {}
    // 报告加载进度
    override fun onMusicLoadProgress(songCode: Long, percent: Int, status: MusicLoadStatus, msg: String?, lyricUrl: String?)
})
```

### 3. 切换角色 [#3-切换角色-1]

当歌曲结束或伴唱想要中途退出合唱时，调用 `switchSingerRole` 将用户角色切换为听众。

```kotlin
ktvApiProtocol.switchSingerRole(KTVSingRole.Audience, null);
```

### 4. 根据角色更新媒体选项 [#4-根据角色更新媒体选项-1]

通过 [`updateChannelMediaOptions`](/zh-CN/api-reference/rtc/android/channel#api_irtcengine_updatechannelmediaoptions) 方法在伴唱加入频道后更新频道媒体选项，例如是否开启本地音频采集，是否发布本地音频流等。

```kotlin
val channelMediaOption = ChannelMediaOptions()
// 发布本地麦克风流
channelMediaOption.publishMicrophoneTrack = true
// 启用音频采集和播放
channelMediaOption.enableAudioRecordingOrPlayout = true
// 设置角色为主播
channelMediaOption.clientRoleType = CLIENT_ROLE_BROADCASTER
// 更新媒体选项
mRtcEngine.updateChannelMediaOptions(channelMediaOption)
```

## 听众实现 [#听众实现]

### 1. 加入频道 [#1-加入频道-2]

调用 [`joinChannel`](/zh-CN/api-reference/rtc/android/channel#api_irtcengine_joinchannel2) 让听众加入频道，订阅音频流。为保证高音质，声网推荐你在加入频道前调用 [`setAudioProfile`](/zh-CN/api-reference/rtc/android/audio/audio-basic#api_irtcengine_setaudioprofile2) 将音频编码属性设为 `MUSIC_HIGH_QUALITY`，将音频场景设为 `AUDIO_SCENARIO_GAME_STREAMING`。

```kotlin
// 加入频道
mRtcEngine.joinChannel(
    <Your_Rtc_Token>,
    <Your_Channel_Name>,
    <Your_Uid>,
    // 媒体选项详见第 3 步操作
    channelMediaOption
)
```

### 2. 加载歌词 [#2-加载歌词]

调用 `loadMusic` 加载歌词。听众加入频道后，默认订阅主唱人声和音乐混合的音频流，默认订阅伴唱人声。

```kotlin
// 听众角色仅需加载歌词
ktvApiProtocol.loadMusic(songCode, KTVLoadMusicConfiguration("<SongId>", "<MainSingerUid>", KTVLoadMusicMode.LOAD_LRC_ONLY), object: IMusicLoadStateListener {
    // 加载成功
    override fun onMusicLoadSuccess(songCode: Long, lyricUrl: String) {}
    // 加载失败
    override fun onMusicLoadFail(songCode: Long, reason: KTVLoadSongFailReason) {}
    // 报告加载进度
    override fun onMusicLoadProgress(songCode: Long, percent: Int, status: MusicLoadStatus, msg: String?, lyricUrl: String?)
})
```

### 3. 根据角色更新媒体选项 [#3-根据角色更新媒体选项]

通过 [`updateChannelMediaOptions`](/zh-CN/api-reference/rtc/android/channel#api_irtcengine_updatechannelmediaoptions) 方法在听众加入频道后更新频道媒体选项，例如是否开启本地音频采集，是否发布本地音频流等。

听众的用户角色为 `CLIENT_ROLE_AUDIENCE`，因此无法在频道内发布音频流。如果听众想上麦与演唱者语聊，需要将用户角色修改为 `CLIENT_ROLE_BROADCASTER`。修改角色后，SDK 默认发布该连麦听众的音频流，主唱、伴唱、其他听众都能听到连麦听众的声音。

```kotlin
// 对需要上麦聊天的听众更新媒体选项
val channelMediaOption = ChannelMediaOptions()
// 发布本地麦克风流
channelMediaOption.publishMicrophoneTrack = true
// 启用音频采集和播放
channelMediaOption.enableAudioRecordingOrPlayout = true
// 设置角色为主播
channelMediaOption.clientRoleType = CLIENT_ROLE_BROADCASTER
// 更新媒体选项
mRtcEngine.updateChannelMediaOptions(channelMediaOption)


// 对未上麦的听众更新媒体选项
val channelMediaOption = ChannelMediaOptions()
// 不发布本地麦克风流
channelMediaOption.publishMicrophoneTrack = false
// 启用音频采集和播放
channelMediaOption.enableAudioRecordingOrPlayout = true
// 设置角色为观众
channelMediaOption.clientRoleType = CLIENT_ROLE_AUDIENCE
// 更新媒体选项
mRtcEngine.updateChannelMediaOptions(channelMediaOption)
```

下图展示合唱的 API 调用时序图：

<DocsImage alt="合唱时序图" src="https://web-cdn.agora.io/docs-portal/img/online-ktv/chorus.android.svg" displayWidth="60%" />

## API 参考 [#api-参考]

本文集成步骤中使用如下 API：

- [K 歌房场景化 API](/zh-CN/api-reference/online-ktv)
- [实时互动 API](/zh-CN/api-reference/online-ktv)

## 相关文档 [#相关文档]

- [快速集成 KTV API](/zh-CN/solutions/online-ktv/ktv-scenario/get-started/integrate-ktvapi)
- [独唱](/zh-CN/solutions/online-ktv/ktv-scenario/build/implementation/solo)
- [合唱](/zh-CN/solutions/online-ktv/ktv-scenario/build/implementation/chorus)
- [抢唱](/zh-CN/solutions/online-ktv/ktv-scenario/build/implementation/gaming)
- [接唱](/zh-CN/solutions/online-ktv/ktv-scenario/build/implementation/relay)
- [歌词打分组件](/zh-CN/api-reference/online-ktv)
<_PlatformProcessedMarker close="true" />

<_PlatformProcessedMarker groupMode="structured" canonicalPlatform="android" platform="ios" />
本文介绍如何实现合唱。在此之前，请确保你已参考[实现文档](/zh-CN/solutions/online-ktv/ktv-scenario/get-started/integrate-ktvapi)集成 KTV API。

## 方案介绍 [#方案介绍-1]

合唱场景下存在三种角色：

- **主唱**：在没有伴唱加入、伴唱中途退出后，演唱的人的用户角色为独唱者。独唱者开始唱歌后，如有人加入合唱，独唱则成为合唱中的主唱。主唱者需要加入主频道，加载并播放歌曲，发布麦克风采集的音频流。此外，主唱还需要加入合唱子频道将人声同步给伴唱。KTV API 模块内部控制音乐播放器播放音乐，同步主唱的音乐播放进度，让歌词组件进入歌词滚动状态等逻辑。
- **伴唱**：独唱者开始唱歌后，如有人加入合唱，则其成为合唱中的伴唱。伴唱需要加入合唱子频道，加载并播放歌曲，发布麦克风采集的音频流。KTV API 模块内部控制音乐播放器播放音乐，同步主唱的音乐播放进度，让歌词组件进入歌词滚动状态等逻辑。
- **听众**：加入主频道，加载歌词。KTV API 模块内部控制听众订阅主唱的人声和音乐的音频合流，订阅伴唱的人声，同步主唱的音乐播放进度，让歌词组件进入歌词滚动状态等逻辑。如果普通听众需要上麦聊天，可以更新媒体选项。

下图为合唱方案的技术原理图：

<DocsImage alt="合唱" src="https://web-cdn.agora.io/docs-portal/img/online-ktv/chorus.png" displayWidth="80%" />

## 主唱实现 [#主唱实现-1]

### 1. 加入频道 [#1-加入频道-3]

调用 [`joinChannelByToken`](/zh-CN/api-reference/rtc/ios/channel#api_irtcengine_joinchannel2) 加入频道。为保证高音质，声网推荐你在加入频道前调用 [`setAudioProfile`](/zh-CN/api-reference/rtc/ios/audio/audio-basic#api_irtcengine_setaudioprofile2) 将音频编码属性设为 `AgoraAudioProfileMusicHighQuality`，调用 [`setAudioScenario`](/zh-CN/api-reference/rtc/ios/audio/audio-basic#api_irtcengine_setaudioscenario) 将音频场景设为 `AgoraAudioScenarioGameStreaming`。

```objc
// 加入频道
[self.rtcKit joinChannelByToken:<YOUR TOKEN>
                      channelId:<YOUR CHANNEL NAME>
                           info:nil
                            uid:<YOUR UID>
                   // 媒体选项，详见第 4 步操作
                   mediaOptions:mediaOption
                    joinSuccess:nil];
```

:::info[信息]
为同步频道内的歌词进度等状态信息，你需要在成功加入频道后调用 `renewInnerDataStreamId` 来创建数据流通道。如果退出频道后再次加入频道，你需要在加入频道成功后重新调用该方法来创建数据流通道。一个频道中最多只能创建 **5 个**数据流通道。
:::

### 2. 加载及播放歌曲 [#2-加载及播放歌曲-1]

调用 `loadMusic` 加载歌曲。在调用该方法后需调用 `switchSingerRole` 将用户角色设为独唱（`SoloSinger`），然后调用 `startSing` 才可以播放歌曲。

:::warning[注意]
如需加载声网提供的版权音乐，请参考[获取版权音乐](/zh-CN/solutions/online-ktv/ktv-scenario/build/extend-karaoke/get-music)。
:::

```objc

KTVSongConfiguration *songConfig = [[KTVSongConfiguration alloc] init];
// 同时加载歌曲和歌词
songCode.songIdentifier = songIdentifier;
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 {
}
```

### 3. 切换角色 [#3-切换角色-2]

有人加入合唱成为伴唱时，独唱者需将其用户角色切换为主唱；最后一个伴唱者退出合唱时，主唱需将其用户角色切回独唱者；歌曲结束后，需将用户角色切为听众。

```objc
// 听众加入合唱成为伴唱，独唱者切换角色为主唱
[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) {
                      }];
```

### 4. 根据角色更新媒体选项 [#4-根据角色更新媒体选项-2]

通过 [`updateChannelWithMediaOptions`](/zh-CN/api-reference/rtc/ios/channel#api_irtcengine_updatechannelmediaoptions) 方法在主播加入频道后更新频道媒体选项，例如是否开启本地音频采集，是否发布本地音频流等。

```objc
AgoraRtcChannelMediaOptions* options = [AgoraRtcChannelMediaOptions new];
// 发布本地麦克风流
options.publishMicrophoneTrack = YES;
// 启用音频采集和播放
options.enableAudioRecordingOrPlayout = YES;
// 设置角色为主播
options.clientRoleType = AgoraClientRoleBroadcaster;
// 更新媒体选项
[self.RTCKit updateChannelWithMediaOptions:options];
```

## 伴唱实现 [#伴唱实现-1]

### 1. 加入频道 [#1-加入频道-4]

调用 [`joinChannelByToken`](/zh-CN/api-reference/rtc/ios/channel#api_irtcengine_joinchannel2) 让伴唱加入频道。

```objc
// 加入频道
[self.rtcKit joinChannelByToken:<YOUR TOKEN>
                      channelId:<YOUR CHANNEL NAME>
                           info:nil
                            uid:<YOUR UID>
                 // 媒体选项，详见第 4 步操作
                   mediaOptions:mediaOption
                    joinSuccess:nil];
```

### 2. 加载歌曲 [#2-加载歌曲-1]

听众加入合唱成为伴唱前，需调用 `loadMusic` 加载歌曲。歌曲加载完成后，调用 `switchSingerRole` 将用户角色设为伴唱（`CoSinger`）。

```objc
KTVSongConfiguration* songConfig = [[KTVSongConfiguration alloc] init];
// 仅加载歌曲
songCode.songIdentifier = songIdentifier;
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){
                  }];
}
```

### 3. 切换角色 [#3-切换角色-3]

当歌曲结束或伴唱想要中途退出合唱时，调用 `switchSingerRole` 将用户角色切换为听众。

```objc
[self.ktvApi switchSingerRoleWithNewRole:KTVSingRoleAudience
                      onSwitchRoleState:^(KTVSwitchRoleState state, KTVSwitchRoleFailReason reason) {
                      }];
```

### 4. 根据角色更新媒体选项 [#4-根据角色更新媒体选项-3]

通过 [`updateChannelWithMediaOptions`](/zh-CN/api-reference/rtc/ios/channel#api_irtcengine_updatechannelmediaoptions) 方法在伴唱加入频道后更新频道媒体选项，例如是否开启本地音频采集，是否发布本地音频流等。

```objc
AgoraRtcChannelMediaOptions* options = [AgoraRtcChannelMediaOptions new];
// 发布本地麦克风流
options.publishMicrophoneTrack = YES;
// 启用音频采集和播放
options.enableAudioRecordingOrPlayout = YES;
// 设置角色为主播
options.clientRoleType = AgoraClientRoleBroadcaster;
// 更新媒体选项
[self.RTCKit updateChannelWithMediaOptions:options];
```

## 听众实现 [#听众实现-1]

### 1. 加入频道 [#1-加入频道-5]

调用 [`joinChannelByToken`](/zh-CN/api-reference/rtc/ios/channel#api_irtcengine_joinchannel2) 让听众加入频道。

```objc
// 加入频道
[self.rtcKit joinChannelByToken:<YOUR TOKEN>
                      channelId:<YOUR CHANNEL NAME>
                           info:nil
                            uid:<YOUR UID>
                   // 媒体选项，详见步骤 3
                   mediaOptions:mediaOption
                    joinSuccess:nil];
```

### 2. 加载歌词 [#2-加载歌词-1]

调用 `loadMusic` 加载歌词。听众加入频道后，默认订阅主唱人声和音乐混合的音频流，默认订阅伴唱人声，因此只需要加载歌词。

```objc
KTVSongConfiguration* songConfig = [[KTVSongConfiguration alloc] init];
// 仅加载歌词
songCode.songIdentifier = songIdentifier;
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 {
}
```

### 3. 根据角色更新媒体选项 [#3-根据角色更新媒体选项-1]

通过 [`updateChannelWithMediaOptions`](/zh-CN/api-reference/rtc/ios/channel#api_irtcengine_updatechannelmediaoptions) 方法在听众加入频道后更新频道媒体选项，例如是否开启本地音频采集，是否发布本地音频流等。

听众的用户角色为 `AgoraClientRoleAudience`，因此无法在频道内发布音频流。如果听众想上麦与主唱/伴唱语聊，需要将用户角色修改为 `AgoraClientRoleBroadcaster`。修改角色后，SDK 默认发布该连麦听众的音频流，主唱、伴唱、其他听众都能听到连麦听众的声音。

```objc
// 对需要上麦聊天的听众更新媒体选项
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];
```

下图展示合唱的 API 调用时序图：

<DocsImage alt="合唱时序图" src="https://web-cdn.agora.io/docs-portal/img/online-ktv/chorus.ios.svg" displayWidth="60%" />

## API 参考 [#api-参考-1]

本文集成步骤中使用如下 API：

- [K 歌房场景化 API](/zh-CN/api-reference/online-ktv)
- [实时互动 API](/zh-CN/api-reference/online-ktv)

## 相关文档 [#相关文档-1]

- [快速集成 KTV API](/zh-CN/solutions/online-ktv/ktv-scenario/get-started/integrate-ktvapi)
- [独唱](/zh-CN/solutions/online-ktv/ktv-scenario/build/implementation/solo)
- [大合唱](/zh-CN/solutions/online-ktv/ktv-scenario/build/implementation/grand-chorus)
- [抢唱](/zh-CN/solutions/online-ktv/ktv-scenario/build/implementation/gaming)
- [接唱](/zh-CN/solutions/online-ktv/ktv-scenario/build/implementation/relay)
- [歌词打分组件](/zh-CN/api-reference/online-ktv)
<_PlatformProcessedMarker close="true" />
</_PlatformTabsGroup>
