# 快速集成 PaaS 方案 (/zh-CN/solutions/online-ktv/online-ktv-sdk/get-started/karaoke)

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

<_PlatformTabsGroup>
<_PlatformProcessedMarker groupMode="structured" canonicalPlatform="android" platform="android" />
:::info[信息]
本文介绍如何通过声网实时互动 SDK v4.x 在你的 Android 项目里实现在线 K 歌房的主要功能。如果你想简化集成步骤，请参考[场景化 API 解决方案](/zh-CN/solutions/online-ktv/ktv-scenario/get-started/integrate-ktvapi)。
:::

## 技术原理 [#技术原理]

在线 K 歌房内用户角色说明如下：

| 角色     | 描述                                                         |
| :------- | :----------------------------------------------------------- |
| 房主     | 歌房创建者。可以收、发音频流。                               |
|独唱|在没有伴唱加入、伴唱中途退出后，演唱的人的用户角色为独唱者。|
| 主唱     | 如果有伴唱加入合唱，独唱成为主唱，可以收、发音频流。 |
| 伴唱   | 独唱开始唱歌后，如有人加入合唱，则其成为合唱中的伴唱，可以收、发音频流。 |
| 听众     | 进入歌房的倾听者，只能接收音频流。                           |

实现在线 K 歌房场景中不同角色流程如下：

### 房主实现流程 [#房主实现流程]

1. 房主通过第三方云服务发起指令创建歌房，创建成功后调用 `joinChannel` 方法加入歌房。
   - 加入歌房前，调用 `create [2/2]` 方法创建并初始化 `RtcEngine` 实例，并将实例的音频场景设置为合唱（`CHORUS`）。
   - 调用 `setClientRole` 方法将用户角色设置为主播（ `BROADCASTER`）。
2. 房主自动上麦成为连麦主播，可以点歌成为主唱，并邀请其他连麦主播进行合唱；也可以接受主唱的邀请，成为合唱者。
3. 房主可以通过第三方云服务对连麦主播发起下麦指令，同时房间内所有用户会收到该连麦主播的下麦信息并更新房间内麦位信息。
4. 房主离开歌房后，房间对象自动销毁，其他成员自动离开歌房。

### 主唱实现流程 [#主唱实现流程]

下图展示在线 K 歌房场景的主唱端的实现流程：

![主唱实现流程](https://web-cdn.agora.io/docs-portal/img/online-ktv/sdk-solo.png)

1. 用户选择歌房，调用 `joinChannel` 方法加入歌房。
   加入歌房前，调用 `create [2/2]` 方法创建并初始化 `RtcEngine` 实例，并将实例的音频场景设置为合唱（`CHORUS`）。

2. 用户通过第三方云服务发起上麦指令，并调用 `setClientRole` 方法将用户角色设置为主播（ `BROADCASTER`）。上麦后成为连麦主播，此时可以与房间内其他连麦主播进行语音互动。同时房间内所有用户收到主播端的上麦信息并更新房间内麦位信息。
3. 用户可以点歌，从[声网内容中心](/zh-CN/api-reference/online-ktv)获取歌单，并下载歌曲文件和歌词文件，成为主唱。
4. 主唱可以独唱，也可以发起合唱邀请指令，邀请房间内其他连麦主播一起合唱歌曲。
5. 主唱调用 `IMediaPlayer` 类的方法播放歌曲，并伴随歌曲唱歌。如需切换原唱或伴奏模式，可以调用 `setAudioDualMonoMode` 方法。同时，主唱调用 `joinChannelEx` 方法，实现合唱。
6. 主唱在唱歌的同时，调用 `sendStreamMessage` 方法将当前歌曲播放进度同步到房间内所有用户。合唱者和听众可以结合歌词组件实现歌词同步显示，并自定义歌词界面，详见[歌词组件](/zh-CN/solutions/online-ktv/online-ktv-sdk/build/extend-karaoke/lyrics-scoring)。
7. 主唱通过第三方云服务发起下麦指令，并调用 `leaveChannel` 方法离开歌房。

### 合唱者实现流程 [#合唱者实现流程]

下图展示在线 K 歌房场景的合唱者的实现流程：

![伴唱实现流程](https://web-cdn.agora.io/docs-portal/img/online-ktv/sdk-chorus.png)

1. 用户选择歌房，调用 `joinChannel` 方法加入歌房。
   加入歌房前，调用 `create [2/2]` 方法创建并初始化 `RtcEngine` 实例，并将实例的音频场景设置为合唱（`CHORUS`）。

2. 用户通过第三方云服务发起上麦指令，并调用 `setClientRole` 方法将用户角色设置为主播（ `BROADCASTER`）。上麦后成为连麦主播，此时可以与房间内其他连麦主播进行语音互动。同时房间内所有用户收到主播端的上麦信息并更新房间内麦位信息。
3. 主唱点歌并发出合唱邀请指令后，连麦主播可以接受主唱的邀请，并下载歌曲文件和歌词文件，成为合唱者。
4. 合唱者调用 `IMediaPlayer` 类的方法播放本地歌曲，并伴随歌曲与主唱一起唱歌，并调用 `muteRemoteAudioStream` 方法静音主唱的歌曲。
5. 合唱者通过 `onStreamMessage` 回调接收主唱发送的数据流，实现歌曲同步。同时结合歌词组件实现歌词同步显示，并自定义歌词界面，详见[歌词组件](/zh-CN/solutions/online-ktv/online-ktv-sdk/build/extend-karaoke/lyrics-scoring)。
6. 合唱者通过第三方云服务发起下麦指令，并调用 `leaveChannel` 方法离开歌房。

### 听众实现流程 [#听众实现流程]

1. 用户选择歌房，调用 `joinChannel` 方法加入歌房。

   - 加入歌房前，调用 `create [2/2]` 方法创建并初始化 `RtcEngine` 实例，并将实例的音频场景设置为合唱（`CHORUS`）。
   - 调用 `setClientRole` 方法将用户角色设置为听众（ `AUDIENCE`）。

1. 主播上麦成为主唱后，听众通过第三方云服务同步房间内麦位信息。

2. 主唱发起指令设置当前歌曲 ID，听众接收主唱指令并更新当前歌曲 ID。主唱唱歌时，听众接收主唱的音频流。

   听众如需上麦，需要再次调用 `setClientRole` 方法将用户角色设为 `BROADCASTER`，才可以在房间内发布音频流。

3. 听众通过第三方云服务发起获取当前歌词指令，并下载歌词。同时结合 `onStreamMessage` 回调和歌词组件，实时同步歌词进度。

4. 听众调用 `leaveChannel` 方法离开歌房。

## 前提条件 [#前提条件]

开始前，请确保你的开发环境满足以下条件：

- Android Studio 4.0.0 或以上版本。
- Android 4.1 或以上版本的设备。声网推荐使用真机，部分模拟机可能无法支持本项目的全部功能。
- [Python](https://www.python.org/) 3.X。
- 第三方云服务[命令行工具](https://docs.leancloud.cn/sdk/engine/cli/)。
- 有效的声网[开发者账号](https://sso.shengwang.cn/cn/v4/signup/with-sms)。
- 有效的声网项目，获取项目的 App ID 以及一个 RTM Token。详情请参考使用 RTM Token 鉴权。

:::info[信息]
在线 K 歌房仅使用 RTM Token 进行鉴权，不会产生云信令服务费用。
:::

- 如果你的网络环境部署了防火墙，请参考[应用企业防火墙限制](/zh-CN/realtime-media/rtc/build/setup-and-access/firewall)以正常使用声网服务。

## 项目配置 [#项目配置]

根据下表提供的链接，下载对应平台的 SDK 并集成到你的项目中，并联系 sales@shengwang.cn 开通内容中心服务。

| 产品 | SDK 下载 | 集成文档 |
| --- | --- | --- |
| 声网实时互动 SDK v4.x | [声网实时互动 SDK v4.x ](/zh-CN/realtime-media/rtc/reference/downloads) | [实现纯语音互动](/zh-CN/realtime-media/rtc/build/audio/audio-quick-start) |
| 第三方云服务 | [第三方云服务 SDK](https://leancloud.cn/docs/sdk_down.html) | [karaoke-android-markdown-1-2-2 details](#table-slot-karaoke-android-markdown-1-2-2) |
| 声网歌词打分组件 | [源码](https://github.com/AgoraIO-Community/LyricsView/tree/main) | [歌词组件](/zh-CN/solutions/online-ktv/online-ktv-sdk/build/extend-karaoke/lyrics-scoring) |

<a id="table-slot-karaoke-android-markdown-1-2-2"></a>

**karaoke-android-markdown-1-2-2 details**

[SDK 安装指南](https://leancloud.cn/docs/start.html)

:::warning[注意]
你可以联系声网技术支持获取第三方云服务测试环境，或自行集成第三方云服务。
:::

## 实现在线 K 歌 [#实现在线-k-歌]

本节介绍如何实现在线 K 歌。

### 初始设置 [#初始设置]

加入歌房前，调用以下方法进行初始设置：

| API            | 实现功能                                                     |
| :------------- | :----------------------------------------------------------- |
| `create` [2/2] | 调用其他 API 之前，需要调用该方法创建并初始化 `RtcEngine`，并将 `RtcEngineConfig` 中的 `mAudioScenario` 设置为 `AUDIO_SCENARIO_CHORUS (7)`。 |

示例代码如下：

```java
RtcEngineConfig config = new RtcEngineConfig();
config.mContext = mContext;
config.mAppId = appid;
config.mEventHandler = mIRtcEngineEventHandler;
config.mChannelProfile = Constants.CHANNEL_PROFILE_LIVE_BROADCASTING;
config.mAudioScenario = Constants.AUDIO_SCENARIO_CHORUS;
mRtcEngine = RtcEngine.create(config);
```

### 加入/离开歌房 [#加入离开歌房]

调用以下方法加入或离开歌房：

| API                           | 实现功能                                                     |
| :---------------------------- | :----------------------------------------------------------- |
| `joinChannel`                 | 加入歌房。用户加入歌房后才能接收或发布音频流。               |
| `updateChannelMediaOptions`   | 加入歌房后更新媒体选项。                                     |
| `leaveChannel`                | 离开歌房。房主离开歌房后，房间对象自动销毁，其他成员会自动离开歌房。 |
| `joinChannelEx`               | 合唱场景下，主唱需要调用 `joinChannel` 发布麦克风采集音频流，调用 `joinChannelEx` 发布播放器音频流。两次调用必须设置不同的 `uid`。 |
| `updateChannelMediaOptionsEx` | 合唱场景下，主唱加入另一个频道后更新媒体选项。               |
| `leaveChannelEx`              | 合唱场景下，主唱停止合唱后离开频道。                         |

独唱场景下，主唱发布播放器音频流和麦克风采集音频流，示例代码如下：

```java
mediaPlayer = engine.createMediaPlayer();
mediaPlayer.registerPlayerObserver(this);
mediaPlayer.open(url, 0);
ChannelMediaOptions options = new ChannelMediaOptions();
options.autoSubscribeVideo = true;
options.autoSubscribeAudio = true;
options.publishScreenCaptureVideo = false;
options.publishCameraTrack = false;
// 发布麦克风采集音频流
options.publishMicrophoneTrack = true;
options.enableAudioRecordingOrPlayout = true;
options.publishMediaPlayerId = mediaPlayer.getMediaPlayerId();
// 发布播放器音频流
options.publishMediaPlayerAudioTrack = true;
engine.updateChannelMediaOptions(options);
```

合唱场景下，主唱调用 `joinChannelEx` 发布另一路音频流，示例代码如下：

```java
mediaPlayer = engine.createMediaPlayer();
mediaPlayer.registerPlayerObserver(this);
mediaPlayer.open(url, 0);
ChannelMediaOptions mediaOptions = new ChannelMediaOptions();
mediaOptions.autoSubscribeAudio = false;
mediaOptions.autoSubscribeVideo = false;
mediaOptions.publishMicrophoneTrack = false;
mediaOptions.publishCameraTrack = false;
mediaOptions.publishMediaPlayerId = mediaPlayer.getMediaPlayerId();
// 发布播放器音频流
mediaOptions.publishMediaPlayerAudioTrack = true;
mediaOptions.enableAudioRecordingOrPlayout = false;
mediaOptions.clientRoleType = Constants.CLIENT_ROLE_BROADCASTER;
rtcConnection.channelId = channel2;
rtcConnection.localUid = channel2Uid;
int ret = engine.joinChannelEx(null, rtcConnection, mediaOptions, iRtcEngineEventHandler2);
```

### 上麦/下麦 [#上麦下麦]

调用以下方法进行上麦或下麦：

| API | 实现功能 |
| --- | --- |
| `setClientRole` | [karaoke-android-markdown-2-1-1 details](#table-slot-karaoke-android-markdown-2-1-1) |

<a id="table-slot-karaoke-android-markdown-2-1-1"></a>

**karaoke-android-markdown-2-1-1 details**

设置用户角色。
加入歌房时，需要将主唱的用户角色设为 `BROADCASTER`、听众的用户角色设为 `AUDIENCE`。听众成功上麦后，需要先调用该方法将用户角色切换为 `BROADCASTER`，才能在房间里发布音频流。

### 音频相关 [#音频相关]

主唱调用以下方法设置音频流：

| API                           | 实现功能                       |
| :---------------------------- | :----------------------------- |
| `setAudioProfile` [2/2]       | 设置音频编码属性。             |
| `muteLocalAudioStream`        | 主唱可以关闭或开启本地麦克风。 |
| `adjustRecordingSignalVolume` | 调节人声音量。                 |

### 播放器相关 [#播放器相关]

参考[获取版权音乐](/zh-CN/solutions/online-ktv/online-ktv-sdk/build/extend-karaoke/get-music)来创建并初始化音乐播放器，获取并加载版权音乐。

当你无需再使用音乐播放器时，你需要：

1. 调用 `IAgoraMusicPlayer` 类下的 `destroy` 销毁音乐播放器；
2. 调用 `IAgoraMusicContentCenter` 类下的 `destroy` 销毁音乐内容中心实例；
3. 调用 `RtcEngine` 类下的 `destroy` 销毁引擎。

   ```java
   void destroy() {
   musicPlayer.unRegisterPlayerObserver(mediaPlayerObserver);
   musicPlayer.destroy();
   musicPlayer = null;

   mcc.unregisterEventHandler();
   IAgoraMusicContentCenter.destroy();

   contentCenterConfiguration.eventHandler = null;
   contentCenterConfiguration = null;
   mcc = null;

   rtcEngine.destroy();
   rtcEngine = null;
   }
   ```

你可以参考[版权音乐 API 文档](/zh-CN/api-reference/rtc/android/play/drm)了解更多详细信息，音乐播放器的参数调节、推流设置、事件回调等请参考[媒体播放器 API 文档](/zh-CN/api-reference/rtc/android/play/mediaplayer/mediaplayer-initialize)。

### 歌曲/歌词同步 [#歌曲歌词同步]

| API                      | 实现功能                                                     |
| :----------------------- | :----------------------------------------------------------- |
| `createDataStream` [2/2] | 主唱创建数据流。用于同步歌曲播放进度。                       |
| `sendStreamMessage`      | 主唱发送数据流到歌房内所有用户。                             |
| `onStreamMessage`        | 歌房内所有用户接收主唱发送的数据流，结合歌词控件实现歌词进度的同步更新。 |

示例代码如下：

```java
// 以下代码发送接收的 json 格式仅为示例格式，具体协议格式根据你的具体项目而定。
DataStreamConfig cfg = new DataStreamConfig();
cfg.syncWithAudio = true;
cfg.ordered = true;
mStreamId = getRtcEngine().createDataStream(cfg);
private void sendSyncLrc(String lrcId, long duration, long time) {
    Map<String, Object> msg = new HashMap<>();
    msg.put("cmd", "setLrcTime");
    msg.put("lrcId", lrcId);
    msg.put("duration", duration);
    msg.put("time", time);//ms
    JSONObject jsonMsg = new JSONObject(msg);
    int streamId = RoomManager.Instance(mContext).getStreamId();
    int ret = RoomManager.Instance(mContext).getRtcEngine().sendStreamMessage(streamId, jsonMsg.toString().getBytes());
    if (ret < 0) {
        mLogger.e("sendSyncLrc() sendStreamMessage called returned: ret = [%s]", ret);
    }
}
@Override
public void onStreamMessage(int uid, int streamId, byte[] data) {
    JSONObject jsonMsg;
        String strMsg = new String(data);
        jsonMsg = new JSONObject(strMsg);
        if (jsonMsg.getString("cmd").equals("setLrcTime")) {
            long position = jsonMsg.getLong("time");
            if (position == 0) {
                mHandler.obtainMessage(ACTION_ON_RECEIVED_PLAY, uid).sendToTarget();
            } else if (position == -1) {
                mHandler.obtainMessage(ACTION_ON_RECEIVED_PAUSE, uid).sendToTarget();
            } else {
                Bundle bundle = new Bundle();
                bundle.putInt("uid", uid);
                bundle.putLong("time", position);
                Message message = Message.obtain(mHandler, ACTION_ON_RECEIVED_SYNC_TIME);
                message.setData(bundle);
                message.sendToTarget();
            }
        }
}
```

### 原唱及伴奏切换 [#原唱及伴奏切换]

| 音乐资源类型 | 切换原唱/伴奏方式 |
| --- | --- |
| 左声道伴奏、右声道原唱的单音轨歌曲 | [karaoke-android-markdown-3-1-1 details](#table-slot-karaoke-android-markdown-3-1-1) |
| 多音轨的歌曲 | [karaoke-android-markdown-3-2-1 details](#table-slot-karaoke-android-markdown-3-2-1) |

<a id="table-slot-karaoke-android-markdown-3-1-1"></a>

**karaoke-android-markdown-3-1-1 details**

切换至原唱：调用 `setAudioDualMonoMode`，将 `mode` 设为 `AUDIO_DUAL_MONO_R`(2)。
切换至伴奏：调用 `setAudioDualMonoMode`，将 `mode` 设为 `AUDIO_DUAL_MONO_L`(1)。

:::info[信息]
必须设置只播放左声道或右声道，否则会因为左、右声道音源不同导致音质变差。
:::

<a id="table-slot-karaoke-android-markdown-3-2-1"></a>

**karaoke-android-markdown-3-2-1 details**

切换至原唱：调用 `selectAudioTrack` 将 `index` 设为 `0`。
切换至伴奏：调用 `selectAudioTrack` 将 `index` 设为 `1`。

### 附加功能 [#附加功能]

#### 人声音效 [#人声音效]

调用 `setAudioEffectPreset` 方法，在不改变原声的性别特征的前提下，设置人声音效。设置音效后，频道内所有用户都能听到该效果。详见[设置人声效果](/zh-CN/realtime-media/rtc/build/audio/voice-changer)。

#### 耳返 [#耳返]

调用 `enableInEarMonitoring` [2/2] 方法开启主唱的耳返功能。

#### 歌唱评分 [#歌唱评分]

调用 `enableAudioVolumeIndication` 方法开启歌唱评分功能，并通过 `onAudioVolumeIndication` 回调，结合声网歌词组件实现歌唱评分效果，详见[歌词打分组件](/zh-CN/solutions/online-ktv/online-ktv-sdk/build/extend-karaoke/lyrics-scoring)。

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

- [RESTful API](/zh-CN/api-reference/online-ktv)
- [歌词打分组件](/zh-CN/solutions/online-ktv/online-ktv-sdk/build/extend-karaoke/lyrics-scoring)
- [实时互动 API 参考](/zh-CN/api-reference/online-ktv)
<_PlatformProcessedMarker close="true" />

<_PlatformProcessedMarker groupMode="structured" canonicalPlatform="android" platform="ios" />
本文介绍如何通过声网实时互动 SDK 在你的 iOS 项目里实现在线 K 歌房的主要功能。
:::info[信息]
如果你想简化集成步骤，请参考[场景化 API 解决方案](/zh-CN/solutions/online-ktv/ktv-scenario/get-started/integrate-ktvapi)。
:::

## 技术原理 [#技术原理-1]

在线 K 歌房内用户角色说明如下：

| 角色     | 描述                                                         |
| :------- | :----------------------------------------------------------- |
| 房主     | 歌房创建者。可以收、发音频流。                               |
| 连麦主播 | 进入歌房后，通过上麦成为连麦主播，可以收、发音频流。         |
| 主唱     | 连麦主播点歌后进行排麦演唱，正在演唱者成为主唱，可以收、发音频流。 |
| 合唱者   | 主唱发起合唱后，连麦主播接受邀请，成为合唱者，可以收、发音频流。 |
| 听众     | 进入歌房的倾听者，只能接收音频流。                           |

实现在线 K 歌房场景中不同角色流程如下：

### 房主实现流程 [#房主实现流程-1]

1. 房主通过第三方云服务发起指令创建歌房，创建成功后调用 `joinChannelByToken` 方法加入歌房。
   - 加入歌房前，调用 `sharedEngineWithConfig` 方法创建并初始化 `AgoraRtcEngineKit` 实例，并将实例的音频场景设置为合唱（`AgoraAudioScenarioChorus`）。
   - 调用 `setClientRole` 方法将用户角色设置为主播（ `AgoraClientRoleBroadcaster`）。
2. 房主自动上麦成为连麦主播，可以点歌成为主唱，并邀请其他连麦主播进行合唱；也可以接受主唱的邀请，成为合唱者。
3. 房主可以通过第三方云服务对连麦主播发起下麦指令，同时房间内所有用户会收到该连麦主播的下麦信息并更新房间内麦位信息。
4. 房主离开歌房后，房间对象自动销毁，其他成员自动离开歌房。

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

参考下图实现在线 K 歌房场景的主唱端流程：

![主唱实现流程](https://web-cdn.agora.io/docs-portal/img/online-ktv/sdk-solo.png)

1. 用户选择歌房，调用 `joinChannelByToken` 方法加入歌房。
   加入歌房前，调用 `sharedEngineWithConfig` 方法创建并初始化 `AgoraRtcEngineKit` 实例，并将实例的音频场景设置为合唱（`AgoraAudioScenarioChorus`）。

2. 用户通过第三方云服务发起上麦指令，并调用 `setClientRole` 方法将用户角色设置为主播（ `AgoraClientRoleBroadcaster`）。上麦后成为连麦主播，此时可以与房间内其他连麦主播进行语音互动。同时房间内所有用户收到主播端的上麦信息并更新房间内麦位信息。
3. 用户可以点歌，从[声网内容中心](/zh-CN/api-reference/online-ktv)获取歌单，并下载歌曲文件和歌词文件，成为主唱。
4. 主唱可以独唱，也可以发起合唱邀请指令，邀请房间内其他连麦主播一起合唱歌曲。
5. 主唱调用 `AgoraRtcMediaPlayerProtocol` 类的方法播放歌曲，并伴随歌曲唱歌。如需切换原唱或伴奏模式，可以调用 `setAudioDualMonoMode` 方法。同时，主唱调用 `joinChannelExByToken` 方法，实现合唱。
6. 主唱在唱歌的同时，调用 `sendStreamMessage` 方法将当前歌曲播放进度同步到房间内所有用户。合唱者和听众可以结合歌词组件实现歌词同步显示，并自定义歌词界面，详见[歌词打分组件](/zh-CN/solutions/online-ktv/online-ktv-sdk/build/extend-karaoke/lyrics-scoring)。
7. 主唱通过第三方云服务发起下麦指令，并调用 `leaveChannel` 方法离开歌房。

### 合唱者实现流程 [#合唱者实现流程-1]

下图展示在线 K 歌房场景的合唱者实现流程：

![伴唱实现流程](https://web-cdn.agora.io/docs-portal/img/online-ktv/sdk-chorus.png)

1. 用户选择歌房，调用 `joinChannelByToken` 方法加入歌房。
   加入歌房前，调用 `sharedEngineWithConfig` 方法创建并初始化 `AgoraRtcEngineKit` 实例，并将实例的音频场景设置为合唱（`AgoraAudioScenarioChorus`）。

2. 用户通过第三方云服务发起上麦指令，并调用 `setClientRole` 方法将用户角色设置为主播（ `AgoraClientRoleBroadcaster`）。上麦后成为连麦主播，此时可以与房间内其他连麦主播进行语音互动。同时房间内所有用户收到主播端的上麦信息并更新房间内麦位信息。
3. 主唱点歌并发出合唱邀请指令后，连麦主播可以接受主唱的邀请，并下载歌曲文件和歌词文件，成为合唱者。
4. 合唱者调用 `AgoraRtcMediaPlayerProtocol` 类的方法播放本地歌曲，并伴随歌曲与主唱一起唱歌，并调用 `muteRemoteAudioStream` 方法静音主唱的歌曲。
5. 合唱者通过 `receiveStreamMessageFromUid` 回调接收主唱发送的数据流，实现歌曲同步。同时结合歌词组件实现歌词同步显示，并自定义歌词界面，详见[歌词打分组件](/zh-CN/solutions/online-ktv/online-ktv-sdk/build/extend-karaoke/lyrics-scoring)。
6. 合唱者通过第三方云服务发起下麦指令，并调用 `leaveChannel` 方法离开歌房。

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

1. 用户选择歌房，调用 `joinChannelByToken` 方法加入歌房。

   - 加入歌房前，调用 `sharedEngineWithConfig` 方法创建并初始化 `AgoraRtcEngineKit` 实例，并将实例的音频场景设置为合唱（`AgoraAudioScenarioChorus`）。
   - 调用 `setClientRole` 方法将用户角色设置为听众（ `AgoraClientRoleAudience`）。

1. 主播上麦成为主唱后，听众通过第三方云服务同步房间内麦位信息。

2. 主唱发起指令设置当前歌曲 ID，听众接收主唱指令并更新当前歌曲 ID。主唱唱歌时，听众接收主唱的音频流。

   听众如需上麦，需要再次调用 `setClientRole` 方法将用户角色设为 `AgoraClientRoleBroadcaster`，才可以在房间内发布音频流。

3. 听众通过第三方云服务发起获取当前歌词指令，并下载歌词。同时结合 `receiveStreamMessageFromUid` 回调和歌词组件，实时同步歌词进度。

4. 听众调用 `leaveChannel` 方法离开歌房。

## 前提条件 [#前提条件-1]

开始前，请确保你的开发环境满足以下条件：

- Xcode 12.0 或以上版本。
- 已安装 [CocoaPods](https://guides.cocoapods.org/using/getting-started.html#getting-started)。
- iOS 11.0 或以上版本的设备。部分模拟机可能无法支持本项目的全部功能，所以推荐使用真机。
- [Python](https://www.python.org/) 3.X。
- 第三方云服务[命令行工具](https://leancloud.cn/docs/leanengine_cli.html)。
- 有效的声网[开发者账号](https://sso.shengwang.cn/cn/v4/signup/with-sms)。
- 有效的声网项目，获取项目的 App ID 以及一个 RTM Token。详情请参考使用 RTM Token 鉴权。

:::info[信息]
在线 K 歌房仅使用 RTM Token 进行鉴权，不会产生云信令服务费用。
:::

- 如果你的网络环境部署了防火墙，请参考[应用企业防火墙限制](/zh-CN/realtime-media/rtc/build/setup-and-access/firewall)以正常使用声网服务。

## 项目配置 [#项目配置-1]

根据下表提供的链接，下载对应平台的 SDK 并集成到你的项目中，并联系 sales@shengwang.cn 开通内容中心服务。
| 产品 | SDK 下载 | 集成文档 |
| --- | --- | --- |
| 声网实时互动 SDK v4.x | [声网实时互动 SDK v4.x](/zh-CN/realtime-media/rtc/reference/downloads) | [实现纯语音互动](/zh-CN/realtime-media/rtc/build/audio/audio-quick-start) |
| 第三方云服务 | [第三方云服务 SDK](https://leancloud.cn/docs/sdk_down.html) | [karaoke-ios-markdown-1-2-2 details](#table-slot-karaoke-ios-markdown-1-2-2) |
| 声网歌词组件 | [源码](https://github.com/AgoraIO-Community/LrcView-iOS/tree/main) | [歌词打分组件](/zh-CN/solutions/online-ktv/online-ktv-sdk/build/extend-karaoke/lyrics-scoring) |

<a id="table-slot-karaoke-ios-markdown-1-2-2"></a>

**karaoke-ios-markdown-1-2-2 details**

[SDK 安装指南](https://leancloud.cn/docs/start.html)

:::info[信息]
你可以联系声网技术支持获取第三方云服务测试环境，或自行集成第三方云服务。
:::

## 实现在线 K 歌 [#实现在线-k-歌-1]

本节介绍如何实现在线 K 歌。

### 初始设置 [#初始设置-1]

加入歌房前，调用以下方法进行初始设置：

| API                      | 实现功能                                                     |
| :----------------------- | :----------------------------------------------------------- |
| `sharedEngineWithConfig` | 调用其他 API 之前，需要调用该方法创建并初始化 `AgoraRtcEngineKit`，并将 `AgoraRtcEngineConfig` 中的 `audioScenario` 设置为 `AgoraAudioScenarioChorus(7)`。 |

```objc tab="Objective-C" tabGroup="init"
AgoraRtcEngineConfig *config = [AgoraRtcEngineConfig new];
config.appId = appId;
config.audioScenario = AgoraAudioScenarioChorus;
config.channelProfile = AgoraChannelProfileLiveBroadcasting;
AgoraRtcEngineKit *rtcEngine = [AgoraRtcEngineKit sharedEngineWithConfig:config delegate:self];
```

```swift tab="Swift" tabGroup="init"
let config = AgoraRtcEngineConfig();
config.appId = BuildConfig.AppId
config.audioScenario = .chorus
config.channelProfile = .liveBroadcasting
rtcEngine = AgoraRtcEngineKit.sharedEngine(with: config, delegate: self)
```

### 加入/离开歌房 [#加入离开歌房-1]

调用以下方法加入或离开歌房：

| API                               | 实现功能                                                     |
| :-------------------------------- | :----------------------------------------------------------- |
| `joinChannelByToken`              | 加入歌房。用户加入歌房后才能接收或发布音频流。               |
| `updateChannelWithMediaOptions`   | 加入歌房后更新媒体选项。                                     |
| `leaveChannel`                    | 离开歌房。房主离开歌房后，房间对象自动销毁，其他成员会自动离开歌房。 |
| `joinChannelExByToken`            | 合唱场景下，主唱需要调用 `joinChannelByToken` 发布麦克风采集的音频流，调用 `joinChannelExByToken` 发布播放器的音频流。两次调用必须设置不同的 `uid`。 |
| `updateChannelExWithMediaOptions` | 合唱场景下，主唱加入另一个频道后更新媒体选项。               |
| `leaveChannelEx`                  | 合唱场景下，主唱停止合唱后离开频道。                         |

独唱场景下，主唱发布播放器音频流和麦克风采集音频流，示例代码如下：

```objc tab="Objective-C" tabGroup="solo"
id<AgoraRtcMediaPlayerProtocol> rtcMediaPlayer = [rtcEngine createMediaPlayerWithDelegate:self];
AgoraRtcChannelMediaOptions *option = [AgoraRtcChannelMediaOptions new];
option.publishMicrophoneTrack = YES;
option.publishMediaPlayerAudioTrack = YES;
option.enableAudioRecordingOrPlayout = YES;
option.publishMediaPlayerId = [rtcMediaPlayer getMediaPlayerId];
NSInteger ret = [rtcEngine joinChannelByToken:nil channelId:channelId uid:uid mediaOptions:option joinSuccess:nil];
```

```swift tab="Swift" tabGroup="solo"
rtcMediaPlayer = rtcEngine.createMediaPlayer(with: self)
let option = AgoraRtcChannelMediaOptions()
option.publishMicrophoneTrack = .of(true)
option.publishMediaPlayerAudioTrack = .of(true)
option.enableAudioRecordingOrPlayout = .of(true)
option.publishMediaPlayerId = rtcMediaPlayer.getMediaPlayerId()
let ret = rtcEngine.joinChannel(byToken: nil, channelId: channelId, uid: uid, mediaOptions: option)
```

合唱场景下，主唱调用 `joinChannelExByToken` 发布另一路音频流，示例代码如下：

```objc tab="Objective-C" tabGroup="chorus"
id<AgoraRtcMediaPlayerProtocol> rtcMediaPlayer = [rtcEngine createMediaPlayerWithDelegate:self];
AgoraRtcChannelMediaOptions *option = [AgoraRtcChannelMediaOptions new];
option.autoSubscribeAudio = NO;
option.autoSubscribeVideo = NO;
option.publishMicrophoneTrack = NO;
option.publishMediaPlayerId = [rtcMediaPlayer getMediaPlayerId];
/// 发布播放器音频流
option.publishMediaPlayerAudioTrack = YES;
option.enableAudioRecordingOrPlayout = NO;
AgoraRtcConnection *connection = [AgoraRtcConnection new];
connection.channelId = @"";
connection.localUid = 0;

NSInteger ret = [rtcEngine joinChannelExByToken:@"" connection:connection delegate:self mediaOptions:option joinSuccess:nil];
```

```swift tab="Swift" tabGroup="chorus"
rtcMediaPlayer = rtcEngine.createMediaPlayer(with: self)
rtcMediaPlayer.open(url, 0);
let option = AgoraRtcChannelMediaOptions()
option.autoSubscribeAudio = AgoraRtcBoolOptional.of(false)
option.autoSubscribeVideo = AgoraRtcBoolOptional.of(false)
option.publishMicrophoneTrack = .of(false)
option.publishMediaPlayerId = rtcMediaPlayer.getMediaPlayerId();
// 发布播放器音频流
option.publishMediaPlayerAudioTrack = .of(true)
option.enableAudioRecordingOrPlayout = .of(false)
let connection = AgoraRtcConnection()
connection.channelId = ""
connection.localUid = 0

int ret = rtcEngine?.joinChannelEx(byToken: nil, connection: connection, delegate: self, mediaOptions: mediaOption, joinSuccess: nil)
```

### 上麦/下麦 [#上麦下麦-1]

调用以下方法进行上麦或下麦：

| API | 实现功能 |
| --- | --- |
| `setClientRole` | [karaoke-ios-markdown-2-1-1 details](#table-slot-karaoke-ios-markdown-2-1-1) |

<a id="table-slot-karaoke-ios-markdown-2-1-1"></a>

**karaoke-ios-markdown-2-1-1 details**

设置用户角色。加入歌房时，需要将主唱的用户角色设为 `AgoraClientRoleBroadcaster`、听众的用户角色设为 `AgoraClientRoleAudience`。听众成功上麦后，需要先调用该方法将用户角色切换为 `AgoraClientRoleBroadcaster`，才能在房间里发布音频流。

### 音频相关 [#音频相关-1]

主唱调用以下方法设置音频流：

| API                           | 实现功能                       |
| :---------------------------- | :----------------------------- |
| `setAudioProfile` [2/2]       | 设置音频编码属性。             |
| `muteLocalAudioStream`        | 主唱可以关闭或开启本地麦克风。 |
| `adjustRecordingSignalVolume` | 调节人声音量。                 |

### 播放器相关 [#播放器相关-1]

参考[获取版权音乐](/zh-CN/solutions/online-ktv/online-ktv-sdk/build/extend-karaoke/get-music)来创建并初始化音乐播放器，获取并加载版权音乐。

当你无需再使用音乐播放器时，你需要：

1. 调用 `destroyMediaPlayer` 销毁音乐播放器；
2. 调用 `AgoraMusicContentCenter` 下的 `destroy` 销毁音乐内容中心对象；
3. 调用 `AgoraRtcEngineKit` 的 `destroy` 销毁引擎。

```objc
- (void)destroy {
    [self.mcc destroyMusicPlayer:self.player];
    self.player = nil;

    [self.mcc registerEventDelegate:nil];
    [AgoraMusicContentCenter destroy];
    self.mcc = nil;

    [AgoraRtcEngineKit destroy];
    self.agoraKit = nil;
}
```

你可以参考[版权音乐 API 文档](/zh-CN/api-reference/rtc/ios/play/drm)了解更多详细信息，音乐播放器的参数调节、推流设置、事件回调等请参考[内置媒体播放器 API 文档](/zh-CN/api-reference/rtc/ios/play/mediaplayer/mediaplayer-initialize)。

### 歌曲/歌词同步 [#歌曲歌词同步-1]

| API                           | 实现功能                                                     |
| :---------------------------- | :----------------------------------------------------------- |
| `createDataStream` [2/2]      | 主唱创建数据流。用于同步歌曲播放进度。                       |
| `sendStreamMessage`           | 主唱发送数据流到歌房内所有用户。                             |
| `receiveStreamMessageFromUid` | 歌房内所有用户接收主唱发送的数据流，结合歌词组件实现歌词进度的同步更新。 |

示例代码如下：

```objc tab="Objective-C" tabGroup="lyrics"
AgoraDataStreamConfig *config = [AgoraDataStreamConfig new];
config.ordered = NO;
config.syncWithAudio = NO;
NSInteger dataStreamId = -1;
int ret = [rtcEngine createDataStream:&dataStreamId config:config];
if (ret < 0) {
    NSLog(@"createDataStream error");
}
ret = [rtcEngine sendStreamMessage:dataStreamId data:[NSData new]];
if (ret < 0) {
    NSLog(@"sendStreamMessage error");
}

/// receiveStreamMessage in remote
- (void)rtcEngine:(AgoraRtcEngineKit * _Nonnull)engine receiveStreamMessageFromUid:(NSUInteger)uid streamId:(NSInteger)streamId data:(NSData * _Nonnull)data {
}
```

```swift tab="Swift" tabGroup="lyrics"
let config = AgoraDataStreamConfig()
config.ordered = false
config.syncWithAudio = false
var ret = rtc.createDataStream(&dataStreamId, config: config)
if ret < 0 {
    print("createDataStream error")
}

let ret = rtcEngine.sendStreamMessage(streamId, data: jsonData)
if code < 0 {
    print("sendRtcStreamMessage error")
}

func rtcEngine(_: AgoraRtcEngineKit, receiveStreamMessageFromUid uid: UInt, streamId: Int, data: Data) {
}
```

### 原唱及伴奏切换 [#原唱及伴奏切换-1]

| 音乐资源类型 | 切换原唱/伴奏方式 |
| --- | --- |
| 左声道伴奏、右声道原唱的单音轨歌曲 | [karaoke-ios-markdown-3-1-1 details](#table-slot-karaoke-ios-markdown-3-1-1) |
| 多音轨的歌曲（第一个音轨为原唱，第二个音轨为伴奏） | [karaoke-ios-markdown-3-2-1 details](#table-slot-karaoke-ios-markdown-3-2-1) |

<a id="table-slot-karaoke-ios-markdown-3-1-1"></a>

**karaoke-ios-markdown-3-1-1 details**

切换至原唱：调用 `setAudioDualMonoMode`，将 `mode` 设为 `AgoraAudioDualMonoR`(2)。
切换至伴奏：调用 `setAudioDualMonoMode`，将 `mode` 设为 `AgoraAudioDualMonoL`(1)。

:::warning[注意]
必须设置只播放左声道或右声道，否则会因为左、右声道音源不同导致音质变差。
:::

<a id="table-slot-karaoke-ios-markdown-3-2-1"></a>

**karaoke-ios-markdown-3-2-1 details**

切换至原唱：调用 `selectAudioTrack` 将 `index` 设为 `0`。
切换至伴奏：调用 `selectAudioTrack` 将 `index` 设为 `1`。

### 附加功能 [#附加功能-1]

#### 人声音效 [#人声音效-1]

调用 `setAudioEffectPreset` 方法，在不改变原声的性别特征的前提下，设置人声音效。设置音效后，频道内所有用户都能听到该效果。详见[设置人声效果](/zh-CN/realtime-media/rtc/build/audio/voice-changer)。

#### 耳返 [#耳返-1]

调用 `enableInEarMonitoring` [2/2] 方法开启主唱的耳返功能。

#### 歌唱评分 [#歌唱评分-1]

调用 `enableAudioVolumeIndication` 方法开启歌唱评分功能，并通过 `reportAudioVolumeIndicationOfSpeakers` 回调，结合声网歌词组件实现歌唱评分效果，详见[歌词打分组件](/zh-CN/solutions/online-ktv/online-ktv-sdk/build/extend-karaoke/lyrics-scoring)。

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

- [RESTful API](/zh-CN/api-reference/online-ktv)
- [歌词打分组件](/zh-CN/solutions/online-ktv/online-ktv-sdk/build/extend-karaoke/lyrics-scoring)
- [实时互动 API 参考](/zh-CN/api-reference/online-ktv)
<_PlatformProcessedMarker close="true" />
</_PlatformTabsGroup>
