SwiftUI解析WebVTT文件并实现音频同步交互转录技术问询
实现SwiftUI交互式转录文档(音频+WebVTT同步)
一、WebVTT解析实现
首先定义字幕数据模型,再编写本地VTT文件解析逻辑,处理时间戳和多行字幕场景:
import Foundation struct Caption: Identifiable { let id = UUID() let startTime: TimeInterval let endTime: TimeInterval let text: String } // 解析本地WebVTT文件 func parseWebVTT(from url: URL) -> [Caption] { var captions = [Caption]() guard let content = try? String(contentsOf: url) else { return captions } let lines = content.components(separatedBy: .newlines) var currentStart: TimeInterval? var currentEnd: TimeInterval? var currentText = "" for line in lines { let trimmed = line.trimmingCharacters(in: .whitespacesAndNewlines) // 跳过无关行(空行、WebVTT头部、注释) if trimmed.isEmpty || trimmed.starts(with: "WEBVTT") || trimmed.starts(with: "NOTE") { continue } // 匹配时间戳格式行 let timePattern = #"^(\d{2}:\d{2}:\d{2}\.\d{3}) --> (\d{2}:\d{2}:\d{2}\.\d{3})$"# if trimmed.range(of: timePattern, options: .regularExpression) != nil { // 保存上一段已收集好的字幕 if let start = currentStart, let end = currentEnd, !currentText.isEmpty { captions.append(Caption(startTime: start, endTime: end, text: currentText)) currentText = "" } // 解析时间戳为TimeInterval let timeParts = trimmed.components(separatedBy: " --> ") currentStart = parseTime(timeParts[0]) currentEnd = parseTime(timeParts[1]) } else { // 拼接多行字幕文本 currentText += (currentText.isEmpty ? "" : " ") + trimmed } } // 处理最后一段未保存的字幕 if let start = currentStart, let end = currentEnd, !currentText.isEmpty { captions.append(Caption(startTime: start, endTime: end, text: currentText)) } return captions } // 将VTT时间字符串转换为TimeInterval private func parseTime(_ timeStr: String) -> TimeInterval { let parts = timeStr.components(separatedBy: ":") guard parts.count == 3 else { return 0 } let hours = Double(parts[0]) ?? 0 let minutes = Double(parts[1]) ?? 0 let seconds = Double(parts[2]) ?? 0 return hours * 3600 + minutes * 60 + seconds }
二、SwiftUI视图与音频同步逻辑
使用AVPlayer处理音频播放,监听播放进度实现字幕高亮,同时支持点击字幕跳转对应音频时间:
import AVKit import SwiftUI struct TranscriptView: View { @State private var captions: [Caption] = [] @State private var player: AVPlayer? @State private var currentTime: TimeInterval = 0 @State private var highlightedCaptionId: UUID? // 保存时间观察者token,用于页面销毁时移除监听 private var timeObserverToken: Any? var body: some View { VStack(spacing: 16) { // 播放控制栏 HStack { Button(action: togglePlay) { Image(systemName: player?.timeControlStatus == .playing ? "pause.circle.fill" : "play.circle.fill") .font(.system(size: 44)) .foregroundColor(.blue) } Slider( value: $currentTime, in: 0...(player?.currentItem?.duration.seconds ?? 0), onEditingChanged: { isEditing in if !isEditing { player?.seek(to: CMTime(seconds: currentTime, preferredTimescale: 1000)) } } ) } .padding(.horizontal) // 转录文本展示区域 ScrollView { VStack(alignment: .leading, spacing: 12) { ForEach(captions) { caption in Text(caption.text) .padding(10) .background(highlightedCaptionId == caption.id ? Color.blue.opacity(0.2) : Color.clear) .cornerRadius(6) .onTapGesture { player?.seek(to: CMTime(seconds: caption.startTime, preferredTimescale: 1000)) } } } .padding() } } .onAppear { // 加载本地音频和VTT文件(替换为你的实际文件名) guard let audioURL = Bundle.main.url(forResource: "your-audio", withExtension: "mp3"), let vttURL = Bundle.main.url(forResource: "your-transcript", withExtension: "vtt") else { print("未找到本地音频或VTT文件") return } player = AVPlayer(url: audioURL) captions = parseWebVTT(from: vttURL) // 监听播放进度,0.1秒间隔平衡同步精度与性能 let interval = CMTime(seconds: 0.1, preferredTimescale: 1000) timeObserverToken = player?.addPeriodicTimeObserver(forInterval: interval, queue: .main) { [weak self] time in guard let self = self else { return } self.currentTime = time.seconds // 匹配当前播放时间对应的字幕 self.highlightedCaptionId = self.captions.first { self.currentTime >= $0.startTime && self.currentTime <= $0.endTime }?.id } } .onDisappear { player?.pause() if let token = timeObserverToken { player?.removeTimeObserver(token) } } } private func togglePlay() { guard let player = player else { return } player.timeControlStatus == .playing ? player.pause() : player.play() } }
三、关键注意事项
- 多行字幕处理:WebVTT允许单段字幕跨多行,解析时需将连续的文本行拼接成完整的字幕内容
- 时间精度:解析时间戳时保留毫秒级精度,避免音频与字幕出现同步偏移
- 性能优化:AVPlayer的时间监听间隔不要设置过小,0.1秒足够满足视觉同步需求
- 资源配置:确保MP3和VTT文件已添加到Xcode项目目标中(勾选
Copy items if needed)
内容的提问来源于stack exchange,提问作者Jason G
相关产品推荐
相关产品推荐

