From bcbae1cd2b5621de47cb488f026296210c3c9273 Mon Sep 17 00:00:00 2001 From: Bobby Sudekum Date: Wed, 18 Oct 2017 14:21:15 -0700 Subject: [PATCH 1/6] Prefetch audio instructions --- MapboxNavigation/PollyVoiceController.swift | 107 +++++++++++++++----- 1 file changed, 82 insertions(+), 25 deletions(-) diff --git a/MapboxNavigation/PollyVoiceController.swift b/MapboxNavigation/PollyVoiceController.swift index 37c8e67c7c4..af41ed2db14 100644 --- a/MapboxNavigation/PollyVoiceController.swift +++ b/MapboxNavigation/PollyVoiceController.swift @@ -36,6 +36,11 @@ public class PollyVoiceController: RouteVoiceController { let sessionConfiguration = URLSessionConfiguration.default var urlSession: URLSession + var cacheURLSession: URLSession + var cachePollyTask: URLSessionDataTask? + + var spokenInstructionsForRoute: [String: Data] = [:] + public init(identityPoolId: String) { self.identityPoolId = identityPoolId @@ -45,6 +50,7 @@ public class PollyVoiceController: RouteVoiceController { sessionConfiguration.timeoutIntervalForRequest = timeoutIntervalForRequest; urlSession = URLSession(configuration: sessionConfiguration) + cacheURLSession = URLSession(configuration: URLSessionConfiguration.default) super.init() } @@ -57,14 +63,25 @@ public class PollyVoiceController: RouteVoiceController { pollyTask?.cancel() audioPlayer?.stop() + startAnnouncementTimer() + + guard spokenInstructionsForRoute[instruction] == nil else { + sayInStruction(for: spokenInstructionsForRoute[instruction]!) + return + } speak(instruction, error: nil) - startAnnouncementTimer() + + if let upcomingStep = routeProgresss.currentLegProgress.upComingStep, let instructions = upcomingStep.instructionsSpokenAlongStep { + for instruction in instructions { + guard spokenInstructionsForRoute[instruction.ssmlText] == nil else { continue } + + cacheSpokenInstruction(instruction: instruction.ssmlText) + } + } } - override func speak(_ text: String, error: String?) { - assert(!text.isEmpty) - + func pollyURL(for instruction: String) -> AWSPollySynthesizeSpeechURLBuilderRequest { let input = AWSPollySynthesizeSpeechURLBuilderRequest() input.textType = .ssml input.outputFormat = .mp3 @@ -108,15 +125,22 @@ public class PollyVoiceController: RouteVoiceController { case ("tr", _): input.voiceId = .filiz default: - callSuperSpeak(fallbackText, error: "Voice \(langCode)-\(countryCode) not found") - return + input.voiceId = .joanna } if let voiceId = globalVoiceId { input.voiceId = voiceId } - input.text = text + input.text = instruction + + return input + } + + override func speak(_ text: String, error: String?) { + assert(!text.isEmpty) + + let input = pollyURL(for: text) let builder = AWSPollySynthesizeSpeechURLBuilder.default().getPreSignedURL(input) builder.continueWith { [weak self] (awsTask: AWSTask) -> Any? in @@ -172,29 +196,62 @@ public class PollyVoiceController: RouteVoiceController { return } - do { - strongSelf.audioPlayer = try AVAudioPlayer(data: data) - let prepared = strongSelf.audioPlayer?.prepareToPlay() ?? false + strongSelf.sayInStruction(for: data) + } + + pollyTask?.resume() + } + + func cacheSpokenInstruction(instruction: String) { + + let pollyRequestURL = pollyURL(for: instruction) + + let builder = AWSPollySynthesizeSpeechURLBuilder.default().getPreSignedURL(pollyRequestURL) + builder.continueWith { [weak self] (awsTask: AWSTask) -> Any? in + guard let strongSelf = self else { + return nil + } + + guard let url = awsTask.result else { return nil } + + strongSelf.cachePollyTask = strongSelf.cacheURLSession.dataTask(with: url as URL) { (data, response, error) in - guard prepared else { - strongSelf.callSuperSpeak(strongSelf.fallbackText, error: "Audio player failed to prepare") - return + if let error = error { + print(error.localizedDescription) } - DispatchQueue.main.async { - strongSelf.audioPlayer?.delegate = self - let played = strongSelf.audioPlayer?.play() ?? false - - guard played else { - strongSelf.callSuperSpeak(strongSelf.fallbackText, error: "Audio player failed to play") - return - } + if let data = data { + strongSelf.spokenInstructionsForRoute[instruction] = data } - } catch let error as NSError { - strongSelf.callSuperSpeak(strongSelf.fallbackText, error: error.localizedDescription) } + + strongSelf.cachePollyTask?.resume() + + return nil + } + } + + func sayInStruction(for data: Data) { + do { + audioPlayer = try AVAudioPlayer(data: data) + let prepared = audioPlayer?.prepareToPlay() ?? false + + guard prepared else { + callSuperSpeak(fallbackText, error: "Audio player failed to prepare") + return + } + + DispatchQueue.main.async { + self.audioPlayer?.delegate = self + let played = self.audioPlayer?.play() ?? false + + guard played else { + self.callSuperSpeak(self.fallbackText, error: "Audio player failed to play") + return + } + } + } catch let error as NSError { + callSuperSpeak(fallbackText, error: error.localizedDescription) } - - pollyTask?.resume() } } From 5c6704588fe7bcc59ccb3e7d574598fdb3d77237 Mon Sep 17 00:00:00 2001 From: Bobby Sudekum Date: Wed, 18 Oct 2017 14:44:58 -0700 Subject: [PATCH 2/6] Always try caching --- MapboxNavigation/PollyVoiceController.swift | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/MapboxNavigation/PollyVoiceController.swift b/MapboxNavigation/PollyVoiceController.swift index af41ed2db14..e0c87b6cf73 100644 --- a/MapboxNavigation/PollyVoiceController.swift +++ b/MapboxNavigation/PollyVoiceController.swift @@ -65,13 +65,6 @@ public class PollyVoiceController: RouteVoiceController { audioPlayer?.stop() startAnnouncementTimer() - guard spokenInstructionsForRoute[instruction] == nil else { - sayInStruction(for: spokenInstructionsForRoute[instruction]!) - return - } - - speak(instruction, error: nil) - if let upcomingStep = routeProgresss.currentLegProgress.upComingStep, let instructions = upcomingStep.instructionsSpokenAlongStep { for instruction in instructions { guard spokenInstructionsForRoute[instruction.ssmlText] == nil else { continue } @@ -79,6 +72,13 @@ public class PollyVoiceController: RouteVoiceController { cacheSpokenInstruction(instruction: instruction.ssmlText) } } + + guard spokenInstructionsForRoute[instruction] == nil else { + sayInStruction(for: spokenInstructionsForRoute[instruction]!) + return + } + + speak(instruction, error: nil) } func pollyURL(for instruction: String) -> AWSPollySynthesizeSpeechURLBuilderRequest { From 3589f3b7746db1ddfd404a477a08696e2e2046c6 Mon Sep 17 00:00:00 2001 From: Bobby Sudekum Date: Wed, 18 Oct 2017 15:09:00 -0700 Subject: [PATCH 3/6] Cache ahead 3 --- MapboxNavigation/PollyVoiceController.swift | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/MapboxNavigation/PollyVoiceController.swift b/MapboxNavigation/PollyVoiceController.swift index e0c87b6cf73..5486cd51fe5 100644 --- a/MapboxNavigation/PollyVoiceController.swift +++ b/MapboxNavigation/PollyVoiceController.swift @@ -31,6 +31,11 @@ public class PollyVoiceController: RouteVoiceController { */ public var timeoutIntervalForRequest:TimeInterval = 2 + /** + Number of steps ahead of the current step to cache spoken instructions. + */ + public var stepsAheadToCache: Int = 3 + var pollyTask: URLSessionDataTask? let sessionConfiguration = URLSessionConfiguration.default @@ -65,7 +70,12 @@ public class PollyVoiceController: RouteVoiceController { audioPlayer?.stop() startAnnouncementTimer() - if let upcomingStep = routeProgresss.currentLegProgress.upComingStep, let instructions = upcomingStep.instructionsSpokenAlongStep { + for (stepIndex, step) in routeProgresss.currentLegProgress.leg.steps.suffix(from: routeProgresss.currentLegProgress.stepIndex).enumerated() { + let adjustedStepIndex = stepIndex + routeProgresss.currentLegProgress.stepIndex + + guard adjustedStepIndex < routeProgresss.currentLegProgress.stepIndex + 3 else { continue } + guard let instructions = step.instructionsSpokenAlongStep else { continue } + for instruction in instructions { guard spokenInstructionsForRoute[instruction.ssmlText] == nil else { continue } @@ -73,6 +83,7 @@ public class PollyVoiceController: RouteVoiceController { } } + guard spokenInstructionsForRoute[instruction] == nil else { sayInStruction(for: spokenInstructionsForRoute[instruction]!) return From fe1d02c039f31378daf3e4b6f6a19bc767b74258 Mon Sep 17 00:00:00 2001 From: Bobby Sudekum Date: Thu, 19 Oct 2017 12:20:24 -0700 Subject: [PATCH 4/6] Fix --- MapboxNavigation/PollyVoiceController.swift | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/MapboxNavigation/PollyVoiceController.swift b/MapboxNavigation/PollyVoiceController.swift index 5486cd51fe5..5be1567cb29 100644 --- a/MapboxNavigation/PollyVoiceController.swift +++ b/MapboxNavigation/PollyVoiceController.swift @@ -73,7 +73,7 @@ public class PollyVoiceController: RouteVoiceController { for (stepIndex, step) in routeProgresss.currentLegProgress.leg.steps.suffix(from: routeProgresss.currentLegProgress.stepIndex).enumerated() { let adjustedStepIndex = stepIndex + routeProgresss.currentLegProgress.stepIndex - guard adjustedStepIndex < routeProgresss.currentLegProgress.stepIndex + 3 else { continue } + guard adjustedStepIndex < routeProgresss.currentLegProgress.stepIndex + stepsAheadToCache else { continue } guard let instructions = step.instructionsSpokenAlongStep else { continue } for instruction in instructions { @@ -85,7 +85,7 @@ public class PollyVoiceController: RouteVoiceController { guard spokenInstructionsForRoute[instruction] == nil else { - sayInStruction(for: spokenInstructionsForRoute[instruction]!) + sayInstruction(for: spokenInstructionsForRoute[instruction]!) return } @@ -207,7 +207,7 @@ public class PollyVoiceController: RouteVoiceController { return } - strongSelf.sayInStruction(for: data) + strongSelf.sayInstruction(for: data) } pollyTask?.resume() @@ -242,7 +242,7 @@ public class PollyVoiceController: RouteVoiceController { } } - func sayInStruction(for data: Data) { + func sayInstruction(for data: Data) { do { audioPlayer = try AVAudioPlayer(data: data) let prepared = audioPlayer?.prepareToPlay() ?? false From d44083e2875aacf7e4168b222c5f25c0790cd21d Mon Sep 17 00:00:00 2001 From: Bobby Sudekum Date: Thu, 19 Oct 2017 13:23:33 -0700 Subject: [PATCH 5/6] play --- MapboxNavigation/PollyVoiceController.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/MapboxNavigation/PollyVoiceController.swift b/MapboxNavigation/PollyVoiceController.swift index 5be1567cb29..8d34bf8b57e 100644 --- a/MapboxNavigation/PollyVoiceController.swift +++ b/MapboxNavigation/PollyVoiceController.swift @@ -85,7 +85,7 @@ public class PollyVoiceController: RouteVoiceController { guard spokenInstructionsForRoute[instruction] == nil else { - sayInstruction(for: spokenInstructionsForRoute[instruction]!) + play(spokenInstructionsForRoute[instruction]!) return } @@ -207,7 +207,7 @@ public class PollyVoiceController: RouteVoiceController { return } - strongSelf.sayInstruction(for: data) + strongSelf.play(data) } pollyTask?.resume() @@ -242,7 +242,7 @@ public class PollyVoiceController: RouteVoiceController { } } - func sayInstruction(for data: Data) { + func play(_ data: Data) { do { audioPlayer = try AVAudioPlayer(data: data) let prepared = audioPlayer?.prepareToPlay() ?? false From 984944ffaec2c0e5364a2055a769d78f9829edb7 Mon Sep 17 00:00:00 2001 From: Fredrik Karlsson Date: Thu, 19 Oct 2017 13:56:33 -0700 Subject: [PATCH 6/6] Play data from a background queue --- MapboxNavigation/PollyVoiceController.swift | 25 +++++++++++---------- 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/MapboxNavigation/PollyVoiceController.swift b/MapboxNavigation/PollyVoiceController.swift index 8d34bf8b57e..1ca57777f26 100644 --- a/MapboxNavigation/PollyVoiceController.swift +++ b/MapboxNavigation/PollyVoiceController.swift @@ -243,16 +243,16 @@ public class PollyVoiceController: RouteVoiceController { } func play(_ data: Data) { - do { - audioPlayer = try AVAudioPlayer(data: data) - let prepared = audioPlayer?.prepareToPlay() ?? false - - guard prepared else { - callSuperSpeak(fallbackText, error: "Audio player failed to prepare") - return - } - - DispatchQueue.main.async { + DispatchQueue.global(qos: .userInitiated).async { + do { + self.audioPlayer = try AVAudioPlayer(data: data) + let prepared = self.audioPlayer?.prepareToPlay() ?? false + + guard prepared else { + self.callSuperSpeak(self.fallbackText, error: "Audio player failed to prepare") + return + } + self.audioPlayer?.delegate = self let played = self.audioPlayer?.play() ?? false @@ -260,9 +260,10 @@ public class PollyVoiceController: RouteVoiceController { self.callSuperSpeak(self.fallbackText, error: "Audio player failed to play") return } + + } catch let error as NSError { + self.callSuperSpeak(self.fallbackText, error: error.localizedDescription) } - } catch let error as NSError { - callSuperSpeak(fallbackText, error: error.localizedDescription) } } }