实时字幕
与对话式智能体进行实时互动时,你可能需要实时字幕显示你与智能体的对话内容。本文介绍如何在你的 App 中实现实时字幕功能。
技术原理
声网提供开源的字幕处理模块,你只需要将模块集成到项目中,并调用模块的 API 即可快速实现实时字幕功能。以下为字幕模块的工作流程图,其中:
- App 是你的项目,负责调用字幕处理模块的 API 获取字幕内容,并显示在 App UI 上。
- 字幕处理模块是声网提供的开源模块,负责获取并解析字幕原始数据,之后将更新的字幕内容通过回调传给 App。
- SD-RTN™ 是 Software Defined Real-time Network 的缩写,即软件定义实时网,这是声网自建的底层实时传输网络,负责传输字幕原始数据。
前提条件
开始前,请确保已参考实现对话式智能体实现了与 AI 智能体对话互动的基本逻辑。
实现方法
本节介绍如何通过字幕处理模块,获取原始字幕内容并最终显示在 App 的 UI 上。
集成字幕处理模块
- Android
- iOS
- Web
将 ConverationSubtitleController.kt
和 MessageParser.kt
文件拷贝到你的项目中,并在后续调用模块 API 前引入模块。
将 ConversationSubtitleController.swift
和 MessageParser.swift
文件拷贝到你的项目中,并在后续调用模块 API 前引入模块。
将 message.ts
文件拷贝到你自己的项目中,并在后续调用模块 API 前引入模块。
实现字幕数据回调接口
- Android
- iOS
- Web
将你实现的的字幕 UI 模块继承自 IConversationSubtitleCallback
接口, 并实现 onSubtitleUpdated
方法, 在该方法内处理字幕渲染至 UI 的逻辑。
class CovMessageListView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : LinearLayout(context, attrs, defStyleAttr), IConversationSubtitleCallback {
override fun onSubtitleUpdated(subtitle: SubtitleMessage) {
// 这里实现 UI 渲染逻辑
}
}
将你实现的的字幕 UI 模块实现 ConversationSubtitleDelegate
协议, 并实现 onSubtitleUpdated
方法, 在该方法内处理字幕渲染至 UI 的逻辑。
extension ChatViewController: ConversationSubtitleDelegate {
public func onSubtitleUpdated(subtitle: SubtitleMessage) {
// 这里实现 UI 渲染逻辑
}
}
将你实现的字幕 UI 模块接收 MessageEngine
的字幕消息列表,并实现一个简单的组件来展示这些消息:
示例代码通过 window.addEventListener("message")
监听 MessageEngine
内部通过 window.postMessage
发送的字幕数据。对于复杂应用,声网建议使用 Redux 或其他状态管理工具来更有效地管理这些消息数据。
const ChatHistory = () => {
const [chatHistory, setChatHistory] = useState<IMessageListItem[]>([]);
useEffect(() => {
const getChatHistoryFromEvent = (event: MessageEvent) => {
const { data } = event;
if (data.type === "message") {
setChatHistory(data?.data?.chatHistory || []);
}
};
window.addEventListener("message", getChatHistoryFromEvent);
return () => {
window.removeEventListener("message", getChatHistoryFromEvent);
};
}, []);
return (
<>
{chatHistory.map((message, index) => (
<div key={`${message.uid}-${message.turn_id}`}>
{message.uid}: {message.text}
</div>
))}
</>
);
};
创建字幕处理模块实例
- Android
- iOS
- Web
进入通话页面时,创建 ConversationSubtitleController
实例,内部会自行监听字幕消息回调, 并将字幕信息通过 IConversationSubtitleCallback
的 onSubtitleUpdated
回调传给你的 UI。
override fun onCreate(savedInstanceState: Bundle?) {
val subRenderController = ConversationSubtitleController(
SubtitleRenderConfig(
rtcEngine = rtcEngine,
SubtitleRenderMode.word,
mBinding?.messageListView
)
)
}
进入通话页面时,创建 ConversationSubtitleController
实例,内部会自行监听字幕消息回调,并将字幕信息通过 ConversationSubtitleDelegate
的 onSubtitleUpdated
回调传给你的 UI。
let subRenderConfig = SubtitleRenderConfig(rtcEngine: rtcEngine, renderMode: .words, delegate: self)
subRenderController.setupWithConfig(subRenderConfig)
加入 RTC 频道前,创建 MessageEngine
实例,并传入 AgoraRTC 客户端、模式和回调函数。
import AgoraRTC, { IAgoraRTCClient } from "agora-rtc-sdk-ng";
class RtcEngine {
private client: IAgoraRTCClient;
private messageEngine: MessageEngine | null = null;
constructor() {
// 使用 rtc 模式和 vp8 编码器创建 AgoraRTC 客户端
this.client = AgoraRTC.createClient({ mode: "rtc", codec: "vp8" });
}
public joinChannel() {
// 创建 MessageEngine 实例,并传入 AgoraRTC 客户端、模式和回调函数
this.messageEngine = new MessageEngine(
this.client,
EMessageEngineMode.AUTO,
(chatHistory) => {
// 控制台打印 chatHistory
console.log("chatHistory", chatHistory);
// 发送 chatHistory 到 web 页面, 建议使用Redux或者其他状态管理工具
// 这里使用 window.postMessage 发送消息作为示例
window.postMessage({
type: "message",
chatHistory,
});
}
);
this.client.join("***", "****", "****", "****");
}
}
释放资源
- Android
- iOS
- Web
每次通话结束时调用 reset
方法, 清理缓存。离开通话页面时, 调用 release
方法释放资源。
subRenderController.reset()
subRenderController.release()
每次通话结束时调用 reset
方法, 清理缓存。
subRenderController.reset()
离开通话页面或结束对话时, 调用 cleanup
方法释放资源。
this.messageEngine.clearup()
参考信息
示例项目
声网提供了开源的实时字幕示例项目供你参考,你可以前往下载或查看其中的源代码。
API 参考
本节提供各平台字幕模块的 API 参考文档。
- Android
- iOS
- Web
ConversationSubtitleController
字幕处理控制器。
class ConversationSubtitleController(
private val config: SubtitleRenderConfig
)
config
:字幕渲染配置,详见SubtitleRenderConfig
。
SubtitleRenderConfig
字幕渲染配置。
data class SubtitleRenderConfig (
val rtcEngine: RtcEngine,
val renderMode: SubtitleRenderMode?,
val callback: IConversationSubtitleCallback?
)
rtcEngine
:声网RtcEngine
实例,详见create
。renderMode
:字幕渲染模式,详见SubtitleRenderMode
。callback
:接收字幕内容更新的回调接口,详见IConversationSubtitleCallback
。
SubtitleRenderMode
字幕渲染模式。
enum class SubtitleRenderMode {
Text,
Word
}
Text
:逐句渲染模式,即回调接收的字幕内容将全量渲染到 UI 上。Word
:逐字渲染模式,即回调接收的字幕内容将逐字渲染到 UI 上。
使用逐字渲染模式 (Word
) 要求你选择的 TTS 供应商支持逐字输出,否则将自动回退到逐句渲染模式 (Text
)。
IConversationSubtitleCallback
字幕内容更新事件的回调接口。
interface IConversationSubtitleCallback {
fun onSubtitleUpdated(subtitle: SubtitleMessage)
}
onSubtitleUpdated
:字幕更新回调。subtitle
:更新后的字幕消息,详见SubtitleMessage
。
SubtitleMessage
字幕消息。
data class SubtitleMessage(
val turnId: Long,
val userId: Int,
val text: String,
var status: SubtitleStatus
)
turnId
:对话轮次的标识符。用户和智能体的一轮对话对应一个turnId
,并遵循以下规则:turnId = 0
时,text
为智能体的欢迎语,且不存在用户的字幕。turnId ≥ 1
时,text
为该轮次用户或智能体的字幕。你需要根据userId
将用户的字幕显示在智能体的字幕之前,之后轮次+1,重复该流程。
turnId
不保证以严格顺序递增的形式回调。如果遇到乱序情况,你需要自行实现排序逻辑。
userId
:本条字幕消息关联的用户 ID。当前版本0
表示用户,非0
表示智能体的 ID。text
:字幕文本内容。status
:字幕的当前状态,详见SubtitleStatus
。
SubtitleStatus
字幕状态。你可以根据状态做特殊的 UI 处理,例如在字幕句尾显示已打断标识。
enum class SubtitleStatus {
Progress,
End,
Interrupted
}
Progress
:字幕仍在生成中,即用户或智能体的话还没说完。End
:字幕已正常完成,即用户或智能体的话已说完。Interrupted
:字幕在完成前被打断,即用户主动打断智能体回复的场景。
ConversationSubtitleController
字幕处理控制器。
@objc public class ConversationSubtitleController: NSObject {
@objc public func setupWithConfig(_ config: SubtitleRenderConfig)
@objc public func reset()
}
setupWithConfig(_ config:)
:设置字幕渲染配置。config
:字幕渲染配置,详见SubtitleRenderConfig
。
reset()
:清理缓存。
SubtitleRenderConfig
字幕渲染配置。
@objc public class SubtitleRenderConfig: NSObject {
let rtcEngine: AgoraRtcEngineKit
let renderMode: SubtitleRenderMode
let delegate: ConversationSubtitleDelegate?
@objc public init(rtcEngine: AgoraRtcEngineKit, renderMode: SubtitleRenderMode, delegate: ConversationSubtitleDelegate?) {
self.rtcEngine = rtcEngine
self.renderMode = renderMode
self.delegate = delegate
}
}
rtcEngine
:声网AgoraRtcEngineKit
实例,详见sharedEngine
。renderMode
:字幕渲染模式,详见SubtitleRenderMode
。delegate
:接收字幕内容更新事件的回调协议,详见ConversationSubtitleDelegate
。init(rtcEngine:renderMode:delegate:)
:初始化字幕渲染配置。
SubtitleRenderMode
字幕渲染模式。
@objc public enum SubtitleRenderMode: Int {
case words = 0
case text = 1
}
words
:逐字渲染模式,即回调接收的字幕内容将逐字渲染到 UI 上。text
:逐句渲染模式,即回调接收的字幕内容将全量渲染到 UI 上。
使用逐字渲染模式 (words
) 要求你选择的 TTS 供应商支持逐字输出,否则将自动回退到逐句渲染模式 (text
)。
ConversationSubtitleDelegate
字幕内容更新事件的回调协议。
@objc public protocol ConversationSubtitleDelegate: AnyObject {
@objc func onSubtitleUpdated(subtitle: SubtitleMessage)
@objc optional func onDebugLog(_ txt: String)
}
ConversationSubtitleDelegate
:字幕更新回调协议。onSubtitleUpdated
:字幕更新回调。subtitle
:更新后的字幕消息,详见SubtitleMessage
。
onDebugLog
:调试日志回调。txt
:调试日志内容。
SubtitleMessage
字幕消息。
@objc public class SubtitleMessage: NSObject {
let turnId: Int
let userId: UInt
let text: String
var status: SubtitleStatus
init(turnId: Int, userId: UInt, text: String, status: SubtitleStatus) {
self.turnId = turnId
self.userId = userId
self.text = text
self.status = status
}
}
turnId
:对话轮次的标识符。用户和智能体的一轮对话对应一个turnId
,并遵循以下规则:turnId = 0
时,text
为智能体的欢迎语,且不存在用户的字幕。turnId ≥ 1
时,text
为该轮次用户或智能体的字幕。你需要根据userId
将用户的字幕显示在智能体的字幕之前,之后轮次+1,重复该流程。
turnId
不保证以严格顺序递增的形式回调。如果遇到乱序情况,你需要自行实现排序逻辑。
userId
:本条字幕消息关联的用户 ID。当前版本0
表示用户,非0
表示智能体的 ID。text
:字幕文本内容。status
:字幕的当前状态,详见SubtitleStatus
。init(turnId:userId:text:status:)
:初始化字幕消息。
SubtitleStatus
字幕状态。你可以根据状态做特殊的 UI 处理,例如在字幕句尾显示已打断标识。
enum SubtitleStatus: Int {
case inprogress = 0
case end = 1
case interrupt = 2
}
progress
:字幕仍在生成中,即用户或智能体的话还没说完。end
:字幕已正常完成,即用户或智能体的话已说完。interrupted
:字幕在完成前被打断,即用户主动打断智能体回复的场景。
MessageEngine
字幕处理引擎。
class MessageEngine (
rtcEngine: rtcEngine,
renderMode?: EMessageEngineMode,
callback?: (messageList: IMessageListItem[]) => void
)
rtcEngine
:声网 RTC 引擎实例,详见createClient
。renderMode
:字幕渲染模式,详见EMessageEngineMode
。默认为EMessageEngineMode.AUTO
。callback
:接收字幕内容更新的回调函数,参数为IMessageListItem[]
类型的消息列表,详见IMessageListItem
。
EMessageEngineMode
字幕渲染模式。
enum EMessageEngineMode {
TEXT = 'text',
WORD = 'word',
AUTO = 'auto',
}
TEXT
:逐句渲染模式,即回调接收的字幕内容将全量渲染到 UI 上。WORD
:逐字渲染模式,即回调接收的字幕内容将逐字渲染到 UI 上。AUTO
:自动模式,即根据 TTS 供应商支持的渲染模式自动选择。
使用逐字渲染模式 (WORD
) 要求你选择的 TTS 供应商支持逐字输出,否则将自动回退到逐句渲染模式 (TEXT
)。
IMessageListItem
消息列表项。
interface IMessageListItem {
uid: number
turn_id: number
text: string
status: EMessageStatus
}
uid
:本条字幕消息关联的用户 ID。当前版本0
表示用户,非0
表示智能体的 ID。turn_id
:对话轮次的标识符。用户和智能体的一轮对话对应一个turn_id
,并遵循以下规则:turn_id = 0
时,text
为智能体的欢迎语,且不存在用户的字幕。turn_id ≥ 1
时,text
为该轮次用户或智能体的字幕。你需要根据uid
将用户的字幕显示在智能体的字幕之前,之后轮次+1,重复该流程。
turn_id
不保证以严格顺序递增的形式回调。如果遇到乱序情况,你需要自行实现排序逻辑。
text
:字幕文本内容。status
:字幕的当前状态,详见EMessageStatus
。
EMessageStatus
消息状态。
enum EMessageStatus {
IN_PROGRESS = 0,
END = 1,
INTERRUPTED = 2,
}
IN_PROGRESS
:字幕仍在生成中,即用户或智能体的话还没说完。END
:字幕已正常完成,即用户或智能体的话已说完。INTERRUPTED
:字幕在完成前被打断,即用户主动打断智能体回复的场景。