Skip to content

Commit 4fc1327

Browse files
committed
NAVAND-552: predownload voice instructions
1 parent 8322e43 commit 4fc1327

File tree

26 files changed

+1428
-299
lines changed

26 files changed

+1428
-299
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@ Mapbox welcomes participation and contributions from everyone.
2929
- Introduced `NavigationViewListener.onSpeedInfoClicked` that would be triggered when `MapboxSpeedInfoView` is clicked upon. [#6770](https://github.com/mapbox/mapbox-navigation-android/pull/6770)
3030
- Each newly instantiated MapboxRouteArrowView class will initialize the layers with the provided options on the first render call. Previously this would only be done if the layers hadn't already been initialized. [#6466](https://github.com/mapbox/mapbox-navigation-android/pull/6466)
3131
- Fixed an issue where the first voice instruction might have been played twice. [#6766](https://github.com/mapbox/mapbox-navigation-android/pull/6766)
32+
- Introduced `VoiceInstructionsDownloadTrigger` `MapboxSpeechAPI#generatePredownloaded` to use predownloaded voice instructions instead of downloading them on demand. Example usage can be found in the examples directory ( see `MapboxVoiceActivity`). [#6771](https://github.com/mapbox/mapbox-navigation-android/pull/6771)
33+
- Enabled voice instructions predownloading for those who use `MapboxAudioGuidance`. [#6771](https://github.com/mapbox/mapbox-navigation-android/pull/6771)
34+
- Fixed an issue where with low connectivity voice instruction might have been played too late for those who use `MapboxAudioGuidance`. If you use `MapboxSpeechAPI` directly, switch to voice instructions predownloading as described above if you encounter said issue. [#6771](https://github.com/mapbox/mapbox-navigation-android/pull/6771)
3235

3336
## Mapbox Navigation SDK 2.10.0-rc.1 - 16 December, 2022
3437
### Changelog

examples/src/main/java/com/mapbox/navigation/examples/core/MapboxVoiceActivity.kt

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import com.mapbox.maps.plugin.gestures.OnMapLongClickListener
2222
import com.mapbox.maps.plugin.gestures.gestures
2323
import com.mapbox.maps.plugin.locationcomponent.LocationComponentPlugin
2424
import com.mapbox.maps.plugin.locationcomponent.location
25+
import com.mapbox.navigation.base.ExperimentalPreviewMapboxNavigationAPI
2526
import com.mapbox.navigation.base.extensions.applyDefaultNavigationOptions
2627
import com.mapbox.navigation.base.extensions.applyLanguageAndVoiceUnitOptions
2728
import com.mapbox.navigation.base.options.NavigationOptions
@@ -60,6 +61,7 @@ import com.mapbox.navigation.ui.utils.internal.resource.ResourceLoadRequest
6061
import com.mapbox.navigation.ui.utils.internal.resource.ResourceLoaderFactory
6162
import com.mapbox.navigation.ui.voice.api.MapboxSpeechApi
6263
import com.mapbox.navigation.ui.voice.api.MapboxVoiceInstructionsPlayer
64+
import com.mapbox.navigation.ui.voice.api.VoiceInstructionsDownloadTrigger
6365
import com.mapbox.navigation.ui.voice.model.SpeechAnnouncement
6466
import com.mapbox.navigation.ui.voice.model.SpeechError
6567
import com.mapbox.navigation.ui.voice.model.SpeechValue
@@ -201,9 +203,14 @@ class MapboxVoiceActivity : AppCompatActivity(), OnMapLongClickListener {
201203
}
202204
}
203205

206+
@OptIn(ExperimentalPreviewMapboxNavigationAPI::class)
207+
private val voiceInstructionsDownloadTrigger by lazy {
208+
VoiceInstructionsDownloadTrigger(speechApi)
209+
}
210+
204211
private val voiceInstructionsObserver =
205212
VoiceInstructionsObserver { voiceInstructions -> // The data obtained must be used to generate the synthesized speech mp3 file.
206-
speechApi.generate(
213+
speechApi.generatePredownloaded(
207214
voiceInstructions,
208215
speechCallback
209216
)
@@ -384,35 +391,41 @@ class MapboxVoiceActivity : AppCompatActivity(), OnMapLongClickListener {
384391
init()
385392
}
386393

394+
@OptIn(ExperimentalPreviewMapboxNavigationAPI::class)
387395
override fun onStart() {
388396
super.onStart()
389397
if (::mapboxNavigation.isInitialized) {
390398
mapboxNavigation.registerRoutesObserver(routesObserver)
391399
mapboxNavigation.registerLocationObserver(locationObserver)
400+
mapboxNavigation.registerVoiceInstructionsTriggerObserver(voiceInstructionsDownloadTrigger)
392401
mapboxNavigation.registerRouteProgressObserver(routeProgressObserver)
393402
mapboxNavigation.registerRouteProgressObserver(replayProgressObserver)
394403
mapboxNavigation.registerVoiceInstructionsObserver(voiceInstructionsObserver)
395404
}
396405
ResourceLoaderFactory.getInstance().registerObserver(resourceLoadObserver)
397406
}
398407

408+
@OptIn(ExperimentalPreviewMapboxNavigationAPI::class)
399409
override fun onStop() {
400410
super.onStop()
401411
ResourceLoaderFactory.getInstance().unregisterObserver(resourceLoadObserver)
402412
mapboxNavigation.unregisterRoutesObserver(routesObserver)
413+
mapboxNavigation.registerVoiceInstructionsTriggerObserver(voiceInstructionsDownloadTrigger)
403414
mapboxNavigation.unregisterLocationObserver(locationObserver)
404415
mapboxNavigation.unregisterRouteProgressObserver(routeProgressObserver)
405416
mapboxNavigation.unregisterRouteProgressObserver(replayProgressObserver)
406417
mapboxNavigation.unregisterVoiceInstructionsObserver(voiceInstructionsObserver)
407418
}
408419

420+
@OptIn(ExperimentalPreviewMapboxNavigationAPI::class)
409421
override fun onDestroy() {
410422
super.onDestroy()
411423
routeLineApi.cancel()
412424
routeLineView.cancel()
413425
mapboxReplayer.finish()
414426
mapboxNavigation.onDestroy()
415427
speechApi.cancel()
428+
voiceInstructionsDownloadTrigger.destroy()
416429
voiceInstructionsPlayer.shutdown()
417430
}
418431

libnavigation-core/api/current.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ package com.mapbox.navigation.core {
5959
method @com.mapbox.navigation.base.ExperimentalPreviewMapboxNavigationAPI public void registerRoutesPreviewObserver(com.mapbox.navigation.core.preview.RoutesPreviewObserver observer);
6060
method public void registerTripSessionStateObserver(com.mapbox.navigation.core.trip.session.TripSessionStateObserver tripSessionStateObserver);
6161
method public void registerVoiceInstructionsObserver(com.mapbox.navigation.core.trip.session.VoiceInstructionsObserver voiceInstructionsObserver);
62+
method @com.mapbox.navigation.base.ExperimentalPreviewMapboxNavigationAPI public <T extends com.mapbox.navigation.core.directions.session.RoutesObserver & com.mapbox.navigation.core.trip.session.RouteProgressObserver> void registerVoiceInstructionsTriggerObserver(T observer);
6263
method public void requestAlternativeRoutes();
6364
method public void requestAlternativeRoutes(com.mapbox.navigation.core.routealternatives.NavigationRouteAlternativesRequestCallback? callback = null);
6465
method public void requestAlternativeRoutes(com.mapbox.navigation.core.routealternatives.RouteAlternativesRequestCallback callback);
@@ -102,6 +103,7 @@ package com.mapbox.navigation.core {
102103
method @com.mapbox.navigation.base.ExperimentalPreviewMapboxNavigationAPI public void unregisterRoutesPreviewObserver(com.mapbox.navigation.core.preview.RoutesPreviewObserver observer);
103104
method public void unregisterTripSessionStateObserver(com.mapbox.navigation.core.trip.session.TripSessionStateObserver tripSessionStateObserver);
104105
method public void unregisterVoiceInstructionsObserver(com.mapbox.navigation.core.trip.session.VoiceInstructionsObserver voiceInstructionsObserver);
106+
method @com.mapbox.navigation.base.ExperimentalPreviewMapboxNavigationAPI public <T extends com.mapbox.navigation.core.directions.session.RoutesObserver & com.mapbox.navigation.core.trip.session.RouteProgressObserver> void unregisterVoiceInstructionsTriggerObserver(T observer);
105107
property public final com.mapbox.navigator.Experimental experimental;
106108
property public final com.mapbox.navigation.core.trip.session.eh.GraphAccessor graphAccessor;
107109
property public final com.mapbox.navigation.core.history.MapboxHistoryRecorder historyRecorder;

libnavigation-core/src/main/java/com/mapbox/navigation/core/MapboxNavigation.kt

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1283,6 +1283,28 @@ class MapboxNavigation @VisibleForTesting internal constructor(
12831283
}
12841284
}
12851285

1286+
/**
1287+
* Subscribes voice instructions trigger observer for all the necessary updates.
1288+
*/
1289+
@ExperimentalPreviewMapboxNavigationAPI
1290+
fun <T> registerVoiceInstructionsTriggerObserver(
1291+
observer: T
1292+
) where T : RoutesObserver, T : RouteProgressObserver {
1293+
registerRoutesObserver(observer)
1294+
registerRouteProgressObserver(observer)
1295+
}
1296+
1297+
/**
1298+
* Unsubscribes voice instructions trigger observer from all the updates it was subsribed for.
1299+
*/
1300+
@ExperimentalPreviewMapboxNavigationAPI
1301+
fun <T> unregisterVoiceInstructionsTriggerObserver(
1302+
observer: T
1303+
) where T : RoutesObserver, T : RouteProgressObserver {
1304+
unregisterRoutesObserver(observer)
1305+
unregisterRouteProgressObserver(observer)
1306+
}
1307+
12861308
/**
12871309
* Unregisters [RoutesObserver].
12881310
*/

libnavigation-core/src/test/java/com/mapbox/navigation/core/MapboxNavigationTest.kt

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ import com.mapbox.navigation.core.trip.session.NativeSetRouteValue
3939
import com.mapbox.navigation.core.trip.session.NavigationSession
4040
import com.mapbox.navigation.core.trip.session.OffRouteObserver
4141
import com.mapbox.navigation.core.trip.session.RoadObjectsOnRouteObserver
42+
import com.mapbox.navigation.core.trip.session.RouteProgressObserver
4243
import com.mapbox.navigation.core.trip.session.TripSessionState
4344
import com.mapbox.navigation.core.trip.session.TripSessionStateObserver
4445
import com.mapbox.navigation.core.trip.session.createSetRouteResult
@@ -2019,6 +2020,42 @@ internal class MapboxNavigationTest : MapboxNavigationBaseTest() {
20192020
}
20202021
}
20212022

2023+
@Test
2024+
fun registerVoiceInstructionsTriggerObserver() {
2025+
val observer = TestVoiceInstructionsTriggerObserver()
2026+
createMapboxNavigation()
2027+
2028+
mapboxNavigation.registerVoiceInstructionsTriggerObserver(observer)
2029+
2030+
verify(exactly = 1) {
2031+
directionsSession.registerRoutesObserver(observer)
2032+
tripSession.registerRouteProgressObserver(observer)
2033+
}
2034+
}
2035+
2036+
@Test
2037+
fun unregisterVoiceInstructionsTriggerObserver() {
2038+
val observer = TestVoiceInstructionsTriggerObserver()
2039+
createMapboxNavigation()
2040+
mapboxNavigation.registerVoiceInstructionsTriggerObserver(observer)
2041+
2042+
mapboxNavigation.unregisterVoiceInstructionsTriggerObserver(observer)
2043+
2044+
verify(exactly = 1) {
2045+
directionsSession.unregisterRoutesObserver(observer)
2046+
tripSession.unregisterRouteProgressObserver(observer)
2047+
}
2048+
}
2049+
2050+
private class TestVoiceInstructionsTriggerObserver : RoutesObserver, RouteProgressObserver {
2051+
2052+
override fun onRouteProgressChanged(routeProgress: RouteProgress) {
2053+
}
2054+
2055+
override fun onRoutesChanged(result: RoutesUpdatedResult) {
2056+
}
2057+
}
2058+
20222059
private fun alternativeWithId(mockId: String): RouteAlternative {
20232060
val mockedRoute = mockk<RouteInterface> {
20242061
every { routeId } returns mockId
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,17 @@
11
package com.mapbox.navigation.utils.internal
22

3+
import java.util.concurrent.TimeUnit
4+
35
interface Time {
46
fun nanoTime(): Long
57
fun millis(): Long
8+
fun seconds(): Long
69

710
object SystemImpl : Time {
811
override fun nanoTime(): Long = System.nanoTime()
912

1013
override fun millis(): Long = System.currentTimeMillis()
14+
15+
override fun seconds(): Long = TimeUnit.MILLISECONDS.toSeconds(millis())
1116
}
1217
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package com.mapbox.navigation.utils.internal
2+
3+
import org.junit.Assert.assertTrue
4+
import org.junit.Test
5+
import kotlin.math.abs
6+
7+
class TimeTest {
8+
9+
@Test
10+
fun seconds() {
11+
val tolerance = 1
12+
val diff = abs(System.currentTimeMillis() / 1000 - Time.SystemImpl.seconds())
13+
assertTrue(diff < tolerance)
14+
}
15+
16+
@Test
17+
fun millis() {
18+
val tolerance = 100
19+
val diff = abs(System.currentTimeMillis() - Time.SystemImpl.millis())
20+
assertTrue(diff < tolerance)
21+
}
22+
23+
@Test
24+
fun nanoTime() {
25+
val tolerance = 100000000
26+
val diff = abs(System.nanoTime() - Time.SystemImpl.nanoTime())
27+
assertTrue(diff < tolerance)
28+
}
29+
}

libnavui-voice/api/current.txt

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ package com.mapbox.navigation.ui.voice.api {
6464
method public void cancel();
6565
method public void clean(com.mapbox.navigation.ui.voice.model.SpeechAnnouncement announcement);
6666
method public void generate(com.mapbox.api.directions.v5.models.VoiceInstructions voiceInstruction, com.mapbox.navigation.ui.base.util.MapboxNavigationConsumer<com.mapbox.bindgen.Expected<com.mapbox.navigation.ui.voice.model.SpeechError,com.mapbox.navigation.ui.voice.model.SpeechValue>> consumer);
67+
method public void generatePredownloaded(com.mapbox.api.directions.v5.models.VoiceInstructions voiceInstruction, com.mapbox.navigation.ui.base.util.MapboxNavigationConsumer<com.mapbox.bindgen.Expected<com.mapbox.navigation.ui.voice.model.SpeechError,com.mapbox.navigation.ui.voice.model.SpeechValue>> consumer);
6768
}
6869

6970
@UiThread public final class MapboxVoiceInstructionsPlayer {
@@ -84,6 +85,20 @@ package com.mapbox.navigation.ui.voice.api {
8485
method @kotlin.jvm.Throws(exceptionClasses=IllegalArgumentException::class) public void volume(com.mapbox.navigation.ui.voice.model.SpeechVolume state) throws java.lang.IllegalArgumentException;
8586
}
8687

88+
@com.mapbox.navigation.base.ExperimentalPreviewMapboxNavigationAPI public final class VoiceInstructionsDownloadTrigger implements com.mapbox.navigation.core.trip.session.RouteProgressObserver com.mapbox.navigation.core.directions.session.RoutesObserver {
89+
ctor public VoiceInstructionsDownloadTrigger(com.mapbox.navigation.ui.voice.api.MapboxSpeechApi speechApi);
90+
ctor public VoiceInstructionsDownloadTrigger(int observableTime, double timePercentageToTriggerAfter, com.mapbox.navigation.ui.voice.api.MapboxSpeechApi speechApi);
91+
method public void destroy();
92+
method public void onRouteProgressChanged(com.mapbox.navigation.base.trip.model.RouteProgress routeProgress);
93+
method public void onRoutesChanged(com.mapbox.navigation.core.directions.session.RoutesUpdatedResult result);
94+
field public static final com.mapbox.navigation.ui.voice.api.VoiceInstructionsDownloadTrigger.Companion Companion;
95+
field public static final int DEFAULT_OBSERVABLE_TIME_SECONDS = 180; // 0xb4
96+
field public static final double DEFAULT_TIME_PERCENTAGE_TO_TRIGGER_AFTER = 0.5;
97+
}
98+
99+
public static final class VoiceInstructionsDownloadTrigger.Companion {
100+
}
101+
87102
public abstract sealed class VoiceInstructionsPlayerAttributes {
88103
method protected abstract kotlin.jvm.functions.Function1<android.media.AudioFocusRequest.Builder,kotlin.Unit> configureAudioFocusRequestBuilder(com.mapbox.navigation.ui.voice.model.AudioFocusOwner owner);
89104
method protected abstract kotlin.jvm.functions.Function1<android.media.MediaPlayer,kotlin.Unit> configureMediaPlayer();

libnavui-voice/src/main/java/com/mapbox/navigation/ui/voice/api/MapboxAudioGuidance.kt

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package com.mapbox.navigation.ui.voice.api
22

33
import androidx.annotation.VisibleForTesting
44
import com.mapbox.api.directions.v5.models.VoiceInstructions
5+
import com.mapbox.navigation.base.ExperimentalPreviewMapboxNavigationAPI
56
import com.mapbox.navigation.core.MapboxNavigation
67
import com.mapbox.navigation.core.lifecycle.MapboxNavigationApp
78
import com.mapbox.navigation.core.lifecycle.MapboxNavigationObserver
@@ -41,6 +42,7 @@ internal constructor(
4142

4243
private var dataStoreOwner: NavigationDataStoreOwner? = null
4344
private var configOwner: NavigationConfigOwner? = null
45+
private var audioGuidanceVoice: MapboxAudioGuidanceVoice? = null
4446
private var mutedStateFlow = MutableStateFlow(false)
4547
private val internalStateFlow = MutableStateFlow(MapboxAudioGuidanceState())
4648
private val scope = CoroutineScope(SupervisorJob() + dispatcher)
@@ -72,6 +74,7 @@ internal constructor(
7274
*/
7375
override fun onDetached(mapboxNavigation: MapboxNavigation) {
7476
mapboxVoiceInstructions.unregisterObservers(mapboxNavigation)
77+
audioGuidanceVoice?.destroy()
7578
job?.cancel()
7679
job = null
7780
}
@@ -160,15 +163,25 @@ internal constructor(
160163
}
161164
}
162165

163-
private fun MapboxNavigation.audioGuidanceVoice(): Flow<MapboxAudioGuidanceVoice> =
164-
combine(
166+
@OptIn(ExperimentalPreviewMapboxNavigationAPI::class)
167+
private fun MapboxNavigation.audioGuidanceVoice(): Flow<MapboxAudioGuidanceVoice> {
168+
var trigger: VoiceInstructionsDownloadTrigger? = null
169+
return combine(
165170
mapboxVoiceInstructions.voiceLanguage(),
166171
configOwner!!.language(),
167172
) { voiceLanguage, deviceLanguage -> voiceLanguage ?: deviceLanguage }
168173
.distinctUntilChanged()
169174
.map { language ->
170-
audioGuidanceServices.mapboxAudioGuidanceVoice(this, language)
175+
audioGuidanceVoice?.destroy()
176+
trigger?.let { unregisterVoiceInstructionsTriggerObserver(it) }
177+
audioGuidanceServices.mapboxAudioGuidanceVoice(this, language).also {
178+
audioGuidanceVoice = it
179+
trigger = VoiceInstructionsDownloadTrigger(it.mapboxSpeechApi).also {
180+
registerVoiceInstructionsTriggerObserver(it)
181+
}
182+
}
171183
}
184+
}
172185

173186
private suspend fun restoreMutedState() {
174187
dataStoreOwner?.apply {

0 commit comments

Comments
 (0)