完善首页权限申请流程与队列播报体验。
对齐 Android 无权限强制弹窗、角色下拉与申请页交互,补充 UIButton configuration 适配,并增强景区排队 TTS 与相关单元测试。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@ -304,7 +304,15 @@ final class ScenicQueueFeatureTests: XCTestCase {
|
||||
let tokenProvider = MockAliyunNLSTokenProvider()
|
||||
let bridge = MockAliyunNuiTTSBridge()
|
||||
let player = MockAliyunPCMStreamingPlayer()
|
||||
let defaults = UserDefaults(suiteName: "ScenicQueueFeatureTests.onlineTTS")!
|
||||
defaults.removePersistentDomain(forName: "ScenicQueueFeatureTests.onlineTTS")
|
||||
let store = AppStore(defaults: defaults)
|
||||
store.session.userId = "u1"
|
||||
store.session.accountType = .unknown("photog")
|
||||
store.session.currentScenicId = 11
|
||||
store.scenicQueue.useOfflineTTS = false
|
||||
let manager = AliyunNLSTTSManager(
|
||||
appStore: store,
|
||||
tokenProvider: tokenProvider,
|
||||
bridge: bridge,
|
||||
streamingPlayer: player
|
||||
@ -321,6 +329,58 @@ final class ScenicQueueFeatureTests: XCTestCase {
|
||||
XCTAssertEqual(Array(player.events.suffix(3)), ["start:16000", "write:4", "drain"])
|
||||
}
|
||||
|
||||
func testAliyunTTSManagerInitializesOfflineEngineWithBundledVoice() async throws {
|
||||
let api = MockScenicQueueAPI()
|
||||
let tokenProvider = MockAliyunNLSTokenProvider()
|
||||
let bridge = MockAliyunNuiTTSBridge()
|
||||
bridge.paramValues["model_sample_rate"] = "24000"
|
||||
let player = MockAliyunPCMStreamingPlayer()
|
||||
let defaults = UserDefaults(suiteName: "ScenicQueueFeatureTests.nativeOfflineTTS")!
|
||||
defaults.removePersistentDomain(forName: "ScenicQueueFeatureTests.nativeOfflineTTS")
|
||||
let store = AppStore(defaults: defaults)
|
||||
store.session.userId = "u1"
|
||||
store.session.accountType = .unknown("photog")
|
||||
store.session.currentScenicId = 11
|
||||
store.scenicQueue.useOfflineTTS = true
|
||||
|
||||
let workspace = FileManager.default.temporaryDirectory
|
||||
.appendingPathComponent("ScenicQueueFeatureTests-\(UUID().uuidString)", isDirectory: true)
|
||||
let voiceDirectory = workspace.appendingPathComponent("tts/voices", isDirectory: true)
|
||||
try FileManager.default.createDirectory(at: voiceDirectory, withIntermediateDirectories: true)
|
||||
try Data([0]).write(to: workspace.appendingPathComponent("tts/languagedata_embedded.bin"))
|
||||
try Data([0]).write(to: workspace.appendingPathComponent("tts/parameter.cfg"))
|
||||
try Data([0]).write(to: voiceDirectory.appendingPathComponent("aijia"))
|
||||
defer { try? FileManager.default.removeItem(at: workspace) }
|
||||
|
||||
let manager = AliyunNLSTTSManager(
|
||||
appStore: store,
|
||||
tokenProvider: tokenProvider,
|
||||
bridge: bridge,
|
||||
streamingPlayer: player,
|
||||
workspacePathProvider: { workspace.path }
|
||||
)
|
||||
|
||||
XCTAssertTrue(manager.supportsOfflineTTS)
|
||||
await manager.prepareQueueTTSEngine(api: api)
|
||||
let ok = await manager.speakPlainTextOnce("请到拍摄区域")
|
||||
|
||||
XCTAssertTrue(ok)
|
||||
let ticketData = try XCTUnwrap(bridge.initializedTickets.last?.data(using: .utf8))
|
||||
let ticket = try XCTUnwrap(JSONSerialization.jsonObject(with: ticketData) as? [String: String])
|
||||
XCTAssertEqual(ticket["app_key"], "gfWimmOuTku1AbIV")
|
||||
XCTAssertEqual(ticket["sdk_code"], "software_nls_tts_offline_standard")
|
||||
XCTAssertEqual(ticket["mode_type"], "0")
|
||||
XCTAssertEqual(ticket["workspace"], workspace.path)
|
||||
XCTAssertEqual(ticket["ak_id"], "ak")
|
||||
XCTAssertEqual(ticket["sts_token"], "security-token")
|
||||
XCTAssertTrue(bridge.events.contains { $0.hasPrefix("set:extend_font_name=") && $0.hasSuffix("/tts/voices/aijia") })
|
||||
XCTAssertTrue(bridge.events.contains("set:encode_type=pcm"))
|
||||
XCTAssertTrue(bridge.events.contains("set:sample_rate=24000"))
|
||||
XCTAssertTrue(bridge.events.contains("set:speed_level=1.0"))
|
||||
XCTAssertFalse(bridge.events.contains("set:mode_type=2"))
|
||||
XCTAssertEqual(Array(player.events.suffix(3)), ["start:24000", "write:4", "drain"])
|
||||
}
|
||||
|
||||
func testSettingsOfflineTTSSwitchReportsUnavailable() {
|
||||
let defaults = UserDefaults(suiteName: "ScenicQueueFeatureTests.offlineTTS")!
|
||||
defaults.removePersistentDomain(forName: "ScenicQueueFeatureTests.offlineTTS")
|
||||
@ -471,10 +531,13 @@ private final class MockAliyunNLSTokenProvider: AliyunNLSTokenProviding {
|
||||
private final class MockAliyunNuiTTSBridge: AliyunNuiTTSBridging {
|
||||
weak var delegate: AliyunNuiTTSBridgeDelegate?
|
||||
var sdkAvailable = true
|
||||
var paramValues: [String: String] = [:]
|
||||
private(set) var events: [String] = []
|
||||
private(set) var initializedTickets: [String] = []
|
||||
private let callbackBridge = AliyunNuiTTSBridge()
|
||||
|
||||
func initialize(ticket: String, logLevel: Int, saveLog: Bool) -> Int {
|
||||
initializedTickets.append(ticket)
|
||||
events.append("initialize:\(ticket.contains("mock-nls-token"))")
|
||||
return 0
|
||||
}
|
||||
@ -507,7 +570,7 @@ private final class MockAliyunNuiTTSBridge: AliyunNuiTTSBridging {
|
||||
}
|
||||
|
||||
func paramValue(for key: String) -> String? {
|
||||
nil
|
||||
paramValues[key]
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user