iOS

最近更新时间:2026-01-26 12:02:55

我的收藏
Karaoke 组件是 TUILiveKit 的标准化 K 歌功能模块,可快速集成至视频直播语音房场景。组件支持本地与在线音乐接入,内置歌词滚动、音准检测、实时打分及多种音效设置。为用户提供低延迟、高保真的实时 K 歌体验。

功能概览

多端人声和音乐同步对齐: 基于 NTP 时间戳对齐技术,确保跨设备间人声与伴奏的精准同步,有效解决网络抖动带来的音画不同步问题。
实时打分与音准曲线:曲库歌曲可实时展示标准音高与用户个人音准曲线,唱歌得分同步广播给全房观众。本地导入歌曲暂不支持打分和音准显示。
毫秒级歌词滚动:支持 VTT、LRC 格式歌词,随音乐进度精准滚动。
多样化音乐源支持: 全面支持在线曲库点播与本地音乐(MP3/M4A)上传播放。


快速接入

步骤 1. 开通服务

参考 开通服务 文档开通「体验版」或「大规模直播版」套餐。

步骤 2. 代码集成

参考 准备工作 接入 TUILiveKit

步骤 3. 集成 KTV 功能

布局选择与接入

我们提供了两种不同交互的布局模式,您可以根据直播间的业务侧重点(例如:专业 K 歌房、社交娱乐房)选择最合适的集成方案。
标准 KTV 模式
悬浮球模式 KTV 接入
该模式主要通过 KTVView 实现,视图通常固定置于房间顶部。它提供了完整的歌词滚动、音高曲线评分以及播放控制功能。您可以根据业务需求,通过代码动态构建的方式将该组件集成到应用中。

方式1:通过代码动态构建
对于使用 Swift 语言开发的工程,集成逻辑如下。
import AtomicX
import AtomicXCore
import UIKit
import SnapKit

class 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 AtomicX
import AtomicXCore
import UIKit
import SnapKit

class 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 中配置您的SDKAppIDSecretKey
import UIKit
import AtomicX

@main
class 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 Foundation

class 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 向服务器换取 playTokenLicense 授权,这是在线流媒体正常解密播放的核心凭证。
步骤一:AppDelegate 中初始化 KaraokeConfig
在使用 Karaoke 组件前,您需要在 AppDelegate.swift 中配置您的SDKAppIDSecretKey并注册网络音乐服务:
import UIKit
import AtomicX

@main
class 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 Foundation
import 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
)
}
}

核心 UI 组件

歌词组件(LyricView)

LyricViewKaraoke 组件中负责文本表现的核心视图。它能够根据音乐播放进度,实现毫秒级的歌词平滑滚动与逐字颜色填充,为演唱者提供精准的节奏指引。

组件能力:
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)

API 参考

合唱相关

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)
分页获取已播放的历史列表。

常见问题

是否有推荐的曲库供应商?

国内建议优先选择音速达曲库。它具备丰富的正版曲目授权,且与组件的音准打分系统深度适配。您可以访问 音速达帮助中心 获取更多接入支持。

为什么演唱时没有音高曲线或实时评分?

音准检测与打分功能高度依赖版权曲库(例如音速达)提供的标准音高基准文件。如果您使用的是本地音乐(External Source),由于缺少标准的参考数据,系统将无法触发 onVoicePitchUpdated(音高更新)和 onVoiceScoreUpdated(分数更新)回调。

如何解决合唱时人声被伴奏掩盖的问题?

您可以使用 setPublishVolume 接口动态调整推流中伴奏的音量(建议范围 30-60),同时通过 TRTC SDK 的 setAudioCaptureVolume 调高人声采集音量 。此外,建议主唱使用 Music 音质模式(TRTCAudioQualityMusic)以获得更好的音频效果 。