# 独唱 (/zh-CN/solutions/online-ktv/ktv-scenario/build/implementation/solo)

> 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。

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

用户点歌后，可以开始独唱，K 歌房内的听众都可以听到其演唱。房间内想与独唱者连麦语聊的听众可以上麦。

独唱场景下存在两种角色：

- **独唱者**：加入频道，加载并播放歌曲。KTV API 模块内部控制音乐播放器播放音乐，发布音乐到远端，将音乐播放进度同步到远端，让歌词组件进入歌词滚动状态等逻辑。
- **听众**：加入频道，加载歌词。KTV API 模块内部控制听众订阅独唱者的人声和音乐的音频合流，同步独唱者的音乐播放进度，让歌词组件进入歌词滚动状态等逻辑。如果普通听众需要上麦聊天，可以更新媒体选项。

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

<DocsImage alt="独唱" src="https://web-cdn.agora.io/docs-portal/img/online-ktv/solo.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>,
    // 媒体选项详见第 5 步操作
    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(false, "<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-歌曲结束]

当歌曲播放完成或切歌后，你需要调用 `switchSingerRole` 将用户的角色切回听众。

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

### 4. 关闭麦克风 [#4-关闭麦克风]

独唱者停止唱歌或希望暂时关闭麦克风时，可以调用 `muteMic(true)`。

```kotlin
ktvApiProtocol.muteMic(true)
```

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

通过 [`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>,
    // 媒体选项详见第 3 步操作
    channelMediaOption
)
```

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

调用 `loadMusic` 加载歌词。听众加入频道后，默认订阅独唱者发布的音频合流，即独唱者人声和音乐混合的音频流，因此听众仅需加载歌词。

```kotlin
// songId 为歌曲的唯一标识
// mainSingerUid 为独唱者的 UID
// KTVLoadMusicMode.LOAD_LRC_ONLY 听众只需要加载歌词
ktvApiProtocol.loadMusic(songId, KTVLoadMusicConfiguration(false, 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/solo.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/chorus)
- [抢唱方案](/zh-CN/solutions/online-ktv/ktv-scenario/build/implementation/gaming)
- [接唱方案](/zh-CN/solutions/online-ktv/ktv-scenario/build/implementation/relay)
- [歌词打分组件](/zh-CN/solutions/online-ktv/ktv-scenario/build/extend-karaoke/lyrics-scoring)
<_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 模块内部控制听众订阅独唱者的人声和音乐的音频合流，同步独唱者的音乐播放进度，让歌词组件进入歌词滚动状态等逻辑。如果普通听众需要上麦聊天，可以更新媒体选项。

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

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

## 独唱者实现 [#独唱者实现-1]

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

调用 [`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>
                   // 媒体选项，详见第 5 步操作
                   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];
// 同时加载歌曲和歌词
songConfig.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-歌曲结束-1]

当歌曲播放完成或切歌后，你需要调用 `switchSingerRole` 将用户的角色切回听众。

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

### 4. 关闭麦克风 [#4-关闭麦克风-1]

独唱者停止唱歌或希望暂时关闭麦克风时，可以调用 `muteMic(true)`。

```objc
[self.ktvApi muteMicWithMuteStatus: true/false];
```

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

通过 [`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-加入频道-3]

调用 [`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];
// 仅加载歌词
songConfig.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/solo.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/chorus)
- [抢唱方案](/zh-CN/solutions/online-ktv/ktv-scenario/build/implementation/gaming)
- [接唱方案](/zh-CN/solutions/online-ktv/ktv-scenario/build/implementation/relay)
- [歌词打分组件](/zh-CN/solutions/online-ktv/ktv-scenario/build/extend-karaoke/lyrics-scoring)
<_PlatformProcessedMarker close="true" />
</_PlatformTabsGroup>
