PDF 朗读的逐词回调会在同一页内持续触发高亮,原实现每次都调用翻页入口,使用户在朗读过程中反复看到页面转场。现仅在目标页变化时定位,并对朗读产生的跨页定位禁用动画,保留高亮刷新而不重复翻页。\n\nEPUB/TXT 此前只提供内容源,缺少将朗读进度回写到阅读器的会话层,无法从当前阅读位置开始朗读、随内容跨页及显示临时高亮。新增 EPUB 朗读会话,使用 href 与 UTF-16 偏移恢复位置;文本重排 EPUB 与 TXT 按实际页面定位并高亮,Web EPUB 则按章节安全定位以避免词级回调导致跳页。URL 阅读器同时提供可选会话入口,图片型阅读器保持不可朗读。\n\n已执行 ReadViewDemo 的 Debug Simulator 构建,结果为 BUILD SUCCEEDED。
62 lines
2.4 KiB
Swift
62 lines
2.4 KiB
Swift
import Foundation
|
|
import RDSpeechReaderView
|
|
|
|
/// A ready-to-use PDF speech session. It owns the controller delegate so page
|
|
/// navigation and temporary sentence highlighting stay synchronized.
|
|
@MainActor
|
|
public final class RDPDFSpeechSession: NSObject, RDSpeechReaderControllerDelegate {
|
|
public let controller: RDSpeechReaderController
|
|
public let contentProvider: RDPDFSpeechContentProvider
|
|
public var onStateChange: ((RDSpeechReaderState) -> Void)?
|
|
|
|
private weak var reader: RDPDFReaderViewController?
|
|
|
|
init(reader: RDPDFReaderViewController, configuration: RDSpeechReaderConfiguration) {
|
|
self.reader = reader
|
|
contentProvider = reader.makeSpeechContentProvider()
|
|
controller = RDSpeechReaderController(contentProvider: contentProvider, configuration: configuration)
|
|
super.init()
|
|
controller.delegate = self
|
|
}
|
|
|
|
public func start(from pageIndex: Int = 0) async throws {
|
|
let location = RDSpeechLocation(
|
|
bookIdentifier: contentProvider.speechBookDescriptor().identifier,
|
|
resourceIdentifier: String(max(0, pageIndex))
|
|
)
|
|
try await controller.start(from: location)
|
|
}
|
|
|
|
public func pause() { controller.pause() }
|
|
public func resume() { controller.resume() }
|
|
|
|
public func stop() {
|
|
controller.stop()
|
|
reader?.clearSpeechHighlight()
|
|
}
|
|
|
|
public func speechReaderController(_ controller: RDSpeechReaderController, didChange state: RDSpeechReaderState) {
|
|
if case .finished = state { reader?.clearSpeechHighlight() }
|
|
if case .idle = state { reader?.clearSpeechHighlight() }
|
|
onStateChange?(state)
|
|
}
|
|
|
|
public func speechReaderController(_ controller: RDSpeechReaderController, willSpeak range: RDSpeechSpokenRange) {
|
|
guard let pageIndex = Int(range.unit.location.resourceIdentifier) else { return }
|
|
reader?.showSpeechHighlight(
|
|
pageIndex: pageIndex,
|
|
normalizedRects: contentProvider.normalizedRects(for: range),
|
|
// 朗读跨页时只需同步定位,不能把词级高亮变成一次页面转场。
|
|
animated: false
|
|
)
|
|
}
|
|
}
|
|
|
|
public extension RDPDFReaderViewController {
|
|
func makeSpeechSession(
|
|
configuration: RDSpeechReaderConfiguration = .default
|
|
) -> RDPDFSpeechSession {
|
|
RDPDFSpeechSession(reader: self, configuration: configuration)
|
|
}
|
|
}
|