一、功能概览
·多端人声和音乐同步对齐: 基于 NTP 时间戳对齐技术,确保跨设备间人声与伴奏的精准同步,有效解决网络抖动带来的音画不同步问题。
·实时打分与音准曲线:曲库歌曲可实时展示标准音高与用户个人音准曲线,唱歌得分同步广播给全房观众。本地导入歌曲暂不支持打分和音准显示。
·毫秒级歌词滚动:支持 VTT、LRC 格式歌词,随音乐进度精准滚动。
·多样化音乐源支持: 全面支持在线曲库点播与本地音乐(MP3/M4A)上传播放。
三、步骤 1. 开通服务
参考 开通服务 文档开通「体验版」或「大规模直播版」套餐。
四、步骤 2. 代码集成
参考 准备工作 接入 TUILiveKit。
六、布局选择与接入
我们提供了两种不同交互的布局模式,你可以根据直播间的业务侧重点(例如:专业 K 歌房、社交娱乐房)选择最合适的集成方案。
标准 KTV 模式悬浮球模式 KTV 接入该模式主要通过 KTVView 实现,视图通常固定置于房间顶部。它提供了完整的歌词滚动、音高曲线评分以及播放控制功能。你可以根据业务需求,通过代码动态构建的方式将该组件集成到应用中。方式1:通过代码动态构建对于使用 Swift 语言开发的工程,集成逻辑如下。import AtomicXimport AtomicXCoreimport UIKitimport SnapKitclass ViewController: UIViewController { //替换为你真实的类名 private let liveID: String private let isOwner: Bool // 1. 初始化 KaraokeManager private lazy var karaokeManager: KaraokeManager = { let manager = KaraokeManager(roomId: liveID) //roomId:房间唯一标识 return manager }() // 2. 创建 KtvView private lazy var ktvView: KtvView = { let ktvView = KtvView( karaokeManager: karaokeManager, isOwner: isOwner, //isOwner: 是否是房主 isKTV: true ) return ktvView }() init(liveID: String) { self.liveID = liveID self.isOwner = LiveListStore.shared.state.value.currentLive.liveOwner.userID == LoginStore.shared.state.value.loginUserInfo?.userID super.init(nibName: nil, bundle: nil) } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func viewDidLoad() { super.viewDidLoad() setupKaraokeView() setupKaraoke() } // 3. 添加 KtvView 到视图 private func setupKaraokeView() { view.addSubview(ktvView) // 替换为你真实的布局 ktvView.snp.makeConstraints { make in make.left.right.equalToSuperview() make.bottom.equalTo(view.safeAreaLayoutGuide.snp.bottom).offset(-20) make.height.equalTo(150) } } // 4. 初始化 Karaoke 功能 private func setupKaraoke() { karaokeManager.show() }}该模式主要通过 KTVView 实现,支持手动拖拽。提供了歌词滚动、音准打分曲线及播放控制功能。你可以根据业务需求,通过代码动态构建的方式将该组件集成到应用中。方式1:通过代码动态构建对于使用 Swift 语言开发的工程,集成逻辑如下。import AtomicXimport AtomicXCoreimport UIKitimport SnapKitclass ViewController: UIViewController { //替换为你真实的类名 private let liveID: String private let isOwner: Bool // 1. 初始化 KaraokeManager private lazy var karaokeManager: KaraokeManager = { let manager = KaraokeManager(roomId: liveID) //roomId:房间唯一标识 return manager }() // 2. 创建 KtvView private lazy var ktvView: KtvView = { let ktvView = KtvView( karaokeManager: karaokeManager, isOwner: isOwner, //isOwner: 是否是房主 isKTV: false ) return ktvView }() init(liveID: String) { self.liveID = liveID self.isOwner = LiveListStore.shared.state.value.currentLive.liveOwner.userID == LoginStore.shared.state.value.loginUserInfo?.userID super.init(nibName: nil, bundle: nil) } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func viewDidLoad() { super.viewDidLoad() setupKaraokeView() setupKaraoke() } // 3. 添加 KtvView 到视图 private func setupKaraokeView() { view.addSubview(ktvView) //替换为你真实的布局 ktvView.snp.makeConstraints { make in make.trailing.equalToSuperview().inset(20.scale375()) make.top.equalTo(view.safeAreaLayoutGuide.snp.top).offset(100) make.height.equalTo(150) } } // 4. 初始化 Karaoke 功能 private func setupKaraoke() { karaokeManager.show() }}
七、添加音乐
Karaoke 组件提供了灵活的数据源适配机制,支持集成本地音乐资源或商业版权曲库。你可以通过继承 MusicSourceService 类来实现不同场景下的音乐加载逻辑。
场景一:添加本地音乐
若你希望播放应用内置或本地音乐文件,请在你的工程中修改或重写LocalMusicCatalogServiceImpl类。该类继承自 MusicCatalogService基类。在本地音乐模式下,LocalMusicCatalogServiceImpl 核心承担以下职责:
管理音乐列表:负责检索本地资源,并将其映射为组件可识别的 MusicInfo 标准列表。
注意:说明:
注意:实时评分与音准检测功能需配合版权曲库的标准音轨数据实现。因此,本地音乐目前仅支持歌词展示与同步播放,不支持打分和计算用户的实时音准。
步骤一:导入音乐资源文件到AtomicX.bundle
1. 在 Xcode 中,找到 AtomicX 工程目录。
2. 将你的音乐资源文件添加到 AtomicX/Resources/KTVResource/目录下。
3. 确保文件包含以下类型:
·原唱文件:例如 houlai_original.mp3
·伴奏文件:例如 houlai_accompany.mp3
·歌词文件:例如 houlai.vtt
4. 在AtomicX.podspec中,确保资源文件被正确打包:
s.resource_bundles = { 'AtomicXBundle' => ['Resources/**/*.{xcassets,json,png,xcstrings,vtt,mp3,json,flac,gif}']}
5. 修改完成后,回到你的.xcworkspace文件路径下重新执行以下命令使配置生效:
pod install
步骤二:在AppDelegate 中初始化KaraokeConfig。
在使用 Karaoke 组件前,你需要在 AppDelegate.swift 中配置你的SDKAppID 和SecretKey:
import UIKitimport AtomicX@mainclass AppDelegate: UIResponder, UIApplicationDelegate { func initKaraokeConfig() { // 配置你的腾讯云 SDKAppID 和 SecretKey KaraokeConfig.shared.updateConfig(SDKAPPID: Int32(SDKAPPID), SECRETKEY: SECRETKEY) } func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { // 初始化 KaraokeConfig(必须在使用 Karaoke 组件前调用) initKaraokeConfig() return true }}
步骤三:实现LocalMusicCatalogServiceImpl类。
import Foundationclass LocalMusicCatalogServiceImpl: MusicCatalogService { /** * 步骤 1:获取本地歌曲列表 * 将本地文件路径封装为 MusicInfo 对象并返回,确保在调用此方法前,相关 mp3/vtt 等文件已导入到 AtomicX.bundle 中。 * 在 Xcode 中,找到 `AtomicX` 工程目录,并将你的音乐资源文件添加到 `AtomicX/Resources/KTVResource/` 目录下 * 执行pod install */ func getSongList(completion: @escaping ([MusicInfo]) -> Void) { // 1. 配置 MusicInfo 核心字段。 // musicId: 歌曲唯一标识;musicName: 歌曲名称;artist: 歌手名列表。 let demoMusic = MusicInfo( musicId: "local_demo_01", // LiveKit中,本地音乐的musicId前缀是local_demo. musicName: "", // 真实的音乐名 artist: "", // 真实的歌手列表 duration: 0, // 真实的音乐持续时间 coverUrl: "", // 真实的歌曲封面 accompanyUrl: atomicXBundle.path(forResource: "houlai_accompany", ofType: "mp3") ?? "", // 对应伴奏文件的 URL originalUrl: atomicXBundle.path(forResource: "houlai_original", ofType: "mp3") ?? "", // 对应原唱文件的 URL lyricUrl: atomicXBundle.path(forResource: "houlai", ofType: "vtt") ?? "", isOriginal: true, hasRating: false ) // 2. 构建列表并通过回调返回。 let musicList = [demoMusic] completion(musicList) } /** * 步骤 2:生成音乐鉴权token * 本地集成音乐暂不需要实现 */ public func queryPlayToken(musicId: String, liveID: String, callback: QueryPlayTokenCallBack) { }}
场景二:添加曲库音乐
如果你已购买版权曲库服务(例如 音速达曲库),可以通过继承 MusicCatalogService 实现云端资源的集成。请在工程中实现MusicCatalogServiceImpl类。在曲库模式下,该类核心承担以下两项职责:
·管理云端列表:对接业务后台或腾讯云接口,获取在线歌曲元数据并映射为组件可识别的 MusicInfo 标准列表。
·获取播放凭证:针对受版权保护的数字音乐,需根据 musicId 向服务器换取 playToken 及 License 授权,这是在线流媒体正常解密播放的核心凭证。
步骤一:在 AppDelegate 中初始化 KaraokeConfig。
在使用 Karaoke 组件前,你需要在 AppDelegate.swift 中配置你的SDKAppID 和 SecretKey并注册网络音乐服务:
import UIKitimport AtomicX@mainclass AppDelegate: UIResponder, UIApplicationDelegate { func initKaraokeConfig() { // 1. 初始化 KaraokeConfig(必需) KaraokeConfig.shared.updateConfig(SDKAPPID: Int32(SDKAPPID), SECRETKEY: SECRETKEY) // 2. 注册网络音乐服务 let networkMusicSource = MusicCatalogServiceImpl() MusicCatalogServiceManager.shared.setService(networkMusicSource) } func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { initKaraokeConfig() return true }}
步骤二:实现MusicCatalogServiceImpl 类。
import Foundationimport AtomicX/** * 网络音乐数据源实现类 * 负责从版权曲库获取在线音乐列表,并处理播放鉴权逻辑 */public class MusicCatalogServiceImpl: MusicCatalogService { // 版权曲库 License 配置 private let copyrightedLicenseKey = "your_license_key" //替换为你真实的曲库(音速达)LicenseKey private let copyrightedLicenseUrl = "your_license_url" //替换为你真实的曲库(音速达)LicenseURL public init() {} /** * 步骤 1:获取版权曲库歌曲列表 * 通过网络请求从版权曲库中检索指定标签的音乐列表 */ public func getSongList(completion: @escaping ([MusicInfo]) -> Void) { // 示例:此处模拟网络请求成功后构建的在线歌曲对象 // 实际使用时,你需要替换为真实的网络请求逻辑,从后端服务获取歌曲列表 let onlineMusic = MusicInfo( musicId: "online_music_001", // 在线音乐ID musicName: "示例歌曲", // 歌曲名称 artist: "示例歌手", // 歌手名称 duration: 240, // 歌曲时长(秒) coverUrl: "https://example.com/cover.jpg", // 封面图片URL accompanyUrl: "https://example.com/accompany.m3u8", // 伴奏URL originalUrl: "https://example.com/original.m3u8", // 原唱URL lyricUrl: "https://example.com/lyric.vtt", // 歌词URL isOriginal: true, hasRating: true ) let musicList = [onlineMusic] completion(musicList) } /** * 步骤 2:获取播放凭证 * 向后端服务请求 playToken,用于授权音乐播放 */ public func queryPlayToken(musicId: String, liveID: String, callback: QueryPlayTokenCallBack) { // 示例:此处模拟网络请求成功后返回的播放凭证 // 实际使用时,你需要替换为真实的网络请求逻辑,从后端服务获取 playToken let playToken = "your_play_token_from_server" callback.onSuccess( musicId: musicId, playToken: playToken, copyrightedLicenseKey: self.copyrightedLicenseKey, copyrightedLicenseUrl: self.copyrightedLicenseUrl ) }}
九、歌词组件(LyricView)
LyricView 是 Karaoke 组件中负责文本表现的核心视图。它能够根据音乐播放进度,实现毫秒级的歌词平滑滚动与逐字颜色填充,为演唱者提供精准的节奏指引。
组件能力:
1. 高精度对齐:支持 VTT、LRC 等标准歌词格式,确保歌词的逐字染色进度与伴奏精准同步。
2. 自适应排版:内置长句拆行与缩减逻辑,当歌词过长时会自动进行排版优化,防止 UI 溢出。
3. 双行渲染模式:默认采用“当前行+下一行”的双行显示策略,帮助演唱者提前预判歌词内容。
十、自定义歌词 UI 样式
为了适配不同风格的直播间布局,你可以通过以下方式来修改组件样式。
1. 设置歌词颜色。你可以分别设置第一行(当前行)的两种状态颜色,以及第二行(等待行)的颜色。
// 修改 LyricCell 的 configure 方法,项目使用了自定义 UIColor 扩展 UIColor(_hex: String)func configure(with line: LyricLine, isCurrentLine: Bool, progress: Int, alignment: NSTextAlignment) { karaokeLyricLabel.text = line.text if isCurrentLine { // 当前行:白色底色 karaokeLyricLabel.textColor = .white updateProgress(progress) } else { // 等待行:浅灰色 karaokeLyricLabel.textColor = .lightGray karaokeLyricLabel.progress = 0 karaokeLyricLabel.setNeedsDisplay() }}// 修改 KaraokeLyricLabel 的 draw 方法中的高亮色override func draw(_ rect: CGRect) { // … 绘制底色文字 … if progress > 0 { // 修改这里的颜色,设置高亮色 let attributes: [NSAttributedString.Key: Any] = [ .font: font, .foregroundColor: UIColor("00ABD6"), // 修改为你需要的高亮色 .paragraphStyle: style ] // … 绘制高亮文字 … }}
2. 调整字体与间距你可以为“当前正在演唱行”和“后续等待行”分别设置不同的字体大小,以增强视觉层级感。
// 在 LyricCell 的 configure 方法中修改字体func configure(with line: LyricLine, isCurrentLine: Bool, progress: Int, alignment: NSTextAlignment) { if alignment == .center { // KTV 模式 karaokeLyricLabel.font = isCurrentLine ? UIFont(name: "PingFangSC-Semibold", size: 18) ?? .systemFont(ofSize: 18, weight: .semibold) : UIFont(name: "PingFangSC-Regular", size: 12) ?? .systemFont(ofSize: 12, weight: .regular) } else { // 直播模式 karaokeLyricLabel.font = isCurrentLine ? UIFont(name: "PingFangSC-Medium", size: 14) ?? .systemFont(ofSize: 14, weight: .medium) : UIFont(name: "PingFangSC-Regular", size: 10) ?? .systemFont(ofSize: 10, weight: .regular) } // … 其他配置 …}private func lyricRowStyle(for isKTV: Bool, row: Int) -> LyricRowStyle { if isKTV { if row == 0 { //当前行 return LyricRowStyle(labelHeight: 21, top: 3, bottom: 3) } else { return LyricRowStyle(labelHeight: 17, top: 5, bottom: 5) } } else { if row == 0 { //当前行 return LyricRowStyle(labelHeight: 18, top: 4, bottom: 0) } else { return LyricRowStyle(labelHeight: 14, top: 4, bottom: 4) } }}
3. 变换对齐方式。根据 UI 设计需求,你可以在LyricView中灵活切换歌词的水平对齐模式。
// 对齐方式由初始化时的 isKTV 参数决定,在 cellForRowAt 方法中应用。func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { // … 其他配置 … let lineIndex = currentLineIndex + indexPath.row guard lineIndex < lyricsData.count else { return cell } let line = lyricsData[lineIndex == -1 ? 0 : lineIndex] let isCurrentLine = indexPath.row == 0 // isKTV 为 true 时居中对齐,false 时右对齐 cell.configure(with: line, isCurrentLine: isCurrentLine, progress: currentPlaybackProgress, alignment: isKTV ? .center : .right) return cell}
4. 实时驱动进度LyricView 的状态由进度数据驱动。你只需将音乐播放器返回的毫秒级进度注入组件,即可实现丝滑的滚动效果。
// 在进度监听器中调用,驱动歌词染色与滚动lyricsView.updateLyrics(progress: currentProgressMs)
十一、音准组件 (PitchView)
PitchView 是 Karaoke 组件中实现专业级打分功能的核心视图。它通过波形化的视觉反馈,将演唱者的实时音高(Pitch)与歌曲标准音轨进行对比,为用户提供极具趣味性与挑战性的“音准校对”体验。
组件能力:
1. 实时音准反馈:毫秒级呈现演唱者的音高动态,通过移动的“音准粒子”实时反馈音准偏移情况。
2. 标准基准线对比:自动加载歌曲的标准音高数据,形成可视化的音高参考矩形块,方便演唱者找准音调。
3. 视觉命中反馈:当演唱音高进入标准范围内时,组件会自动触发高亮染色与“蝴蝶点缀”等动效,强化演唱成就感。
4. 多端状态同步:支持将房主的演唱得分与音高曲线实时广播给全房观众,实现跨端的视觉同步。
十二、自定义音准 UI 样式
PitchView 提供了深度的自定义配置接口,允许开发者根据直播间的视觉调性,调整曲线的外观、命中判定规则以及动画效果。
1. 开启/关闭打分模式:你可以动态控制打分功能的开关。关闭后,组件将不再显示评分标签。
// 开启打分显示逻辑pitchView.setScoreComponentVisible(true)
2. 设置标准音轨数据:在歌曲开始播放前,需要将标准音高序列注入组件,用于生成参考背景。pitchList 数据来自合唱回调 onChorusMusicLoadSucceed 中的 pitchList 字段。
// 注入来自 TXChorusMusicPlayer 的标准音高列表pitchView.setStandardPitchModels(standardPitchModels: pitchModels)
3. 实时驱动播放进度:PitchView 需要实时进度来驱动音轨的横向滚动。
// 在进度监听器中调用,驱动音轨随音乐进度平滑滚动pitchView.setCurrentSongProgress(progress: currentProgressMs)
4. 注入演唱者当前音高:将人声检测到的实时音高注入组件,用于绘制当前演唱的粒子位置。
// 注入演唱者当前的实时音高值(通常为 0-100)pitchView.setCurrentPitch(pitch: userPitch)
5. 实时更新分数:当打分逻辑计算出最新得分时,可调用此接口更新界面顶部的分数标签。
// 设置当前实时得分,视图会自动更新分数的冒泡显示pitchView.setScore("85")
6. 核心配置项:通过PitchViewConfig 配置接口,你可以自定义标准音轨、命中高亮以及背景分割线的颜色等设置。
// 创建配置对象let config = PitchViewConfig()// 配置颜色config.standardRectColor = UIColor.white.withAlphaComponent(0.7) // 标准音轨颜色config.hitRectColor = UIColor("FF6A4C") // 命中填充颜色config.pitchIndicatorColor = .white // 引导粒子颜色config.verticalLineColor = UIColor.white.withAlphaComponent(0.45) // 竖线颜色config.scoreTextColor = UIColor("FF6A4C") // 分数文本颜色// 配置时间相关config.timeElapsedOnScreen = 1175 // 竖线左侧表示的时间(ms)config.timeToPlayOnScreen = 2750 // 竖线右侧表示的时间(ms)config.estimatedCallInterval = 20 // 进度更新间隔(ms)// 配置音高范围config.pitchNum = 20 // 音高等级数(影响垂直分辨率)config.maxPitch = 90 // 最大音高值config.minPitch = 5 // 最小音高值// 应用配置pitchView.setConfig(config: config)
十四、合唱相关
TXChorusMusicPlayer 是合唱功能的核心控制单元,封装在 TRTC SDK 中。该组件基于 NTP 时间戳对齐技术,确保跨设备间人声与伴奏的精准同步,并提供了完整的音乐播放控制能力 。
注意:TXChorusMusicPlayer 主要负责音频流的加载、同步与播放控制 。音准检测与实时评分功能是由版权曲库实现的。
十五、核心控制方法
| 方法名 | 说明 | 调用权限 |
| start() | 开始播放合唱音乐。 | 仅限主唱 |
| stop() | 停止合唱播放。 | 仅限主唱 |
| pause() | 暂停合唱播放。 | 仅限主唱 |
| resume() | 恢复合唱播放。 | 仅限主唱 |
| seek(_ timestampMs: Int64) | 跳转到指定播放进度。 | 仅限主唱 |
| setChorusRole(_ role: TXChorusRole,trtcParamsForPlayer: TRTCParams) | 设置合唱角色(例如主唱、副唱、观众等)。 | 所有角色 |
| switch(_ track: TXChorusMusicTrack) | 切换原唱与伴奏。主唱设置会同步影响观众。 | 主唱/副唱 |
十六、音乐加载与配置
| 方法名 | 说明 |
| loadMusic(_ params: TXChorusCopyrightedMusicParams) | 加载版权曲库音乐。 |
| loadExternalMusic(_ params: TXChorusExternalMusicParams) | 加载本地或自定义 URL 音乐文件。 |
| setPublishVolume(_ volume: Int32) | 设置主唱推流到远端的 BGM 音量 (0-100)。 |
| setPlayoutVolume(_ volume: Int32) | 设置本地播放的 BGM 音量 (0-100)。 |
十七、事件回调 (ITXChorusPlayerListener)
| 回调方法 | 触发时机 |
| onChorusStarted() | 合唱正式开始播放时触发。 |
| onMusicProgressUpdated(_ progressMs: Int64, durationMs: Int64) | 毫秒级播放进度更新。 |
| onVoicePitchUpdated(_ pitch: Int32, hasVoice: Bool, progressMs: Int64) | 演唱音高实时更新(-1 表示间奏/无人声)。 |
| onVoiceScoreUpdated(_ currentScore: Int32, averageScore: Int32, currentLine: Int32) | 每一句歌词唱完后的实时得分反馈。 |
| onChorusRequireLoadMusic(_ musicId: String) | 主唱发起点播后,通知其他角色同步加载歌曲。 |
十八、点歌台相关
TUISongListManager 负责管理直播间的点唱队列,包括添加歌曲、删除歌曲、歌曲置顶及自动切歌逻辑。
| 方法名 | 说明 |
| addSong(songList , onSuccess, onError) | 将歌曲添加至待播列表。 |
| removeSong(songIdList , onSuccess, onError) | 从队列中移除指定歌曲。 |
| setNextSong(targetSongId, onSuccess, onError) | 置顶某首歌曲,使其成为下一首播放的曲目。 |
| playNextSong(onSuccess , onError) | 立即切换至下一首歌曲。 |
| getWaitingList(cursor, count, onSuccess, onError) | 分页获取当前待播列表。 |
| getPlayedList(cursor, count, onSuccess, onError) | 分页获取已播放的历史列表。 |
20、是否有推荐的曲库供应商?
国内建议优先选择音速达曲库。它具备丰富的正版曲目授权,且与组件的音准打分系统深度适配。你可以访问 音速达帮助中心 获取更多接入支持。
21、为什么演唱时没有音高曲线或实时评分?
音准检测与打分功能高度依赖版权曲库(例如音速达)提供的标准音高基准文件。如果你使用的是本地音乐(External Source),由于缺少标准的参考数据,系统将无法触发 onVoicePitchUpdated(音高更新)和 onVoiceScoreUpdated(分数更新)回调。
22、如何解决合唱时人声被伴奏掩盖的问题?
你可以使用 setPublishVolume 接口动态调整推流中伴奏的音量(建议范围 30-60),同时通过 TRTC SDK 的 setAudioCaptureVolume 调高人声采集音量 。此外,建议主唱使用 Music 音质模式(TRTCAudioQualityMusic)以获得更好的音频效果 。