Skip to content

Commit 68328c2

Browse files
committed
Add a ReplayHistorySession
1 parent 2ecee1c commit 68328c2

File tree

15 files changed

+828
-149
lines changed

15 files changed

+828
-149
lines changed
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
- Introduced `ReplayHistorySession` and `ReplayHistorySessionOptions` to simplify the implementation for replaying history files. History can also be enabled with `MapboxTripStarter.enableReplayHistory()`. This can replay large history files in a memory efficient way.

examples/src/main/java/com/mapbox/navigation/examples/MainActivity.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ import androidx.core.app.ActivityCompat
1111
import androidx.core.content.ContextCompat
1212
import androidx.recyclerview.widget.LinearLayoutManager
1313
import com.mapbox.android.core.permissions.PermissionsListener
14+
import com.mapbox.common.LogConfiguration
15+
import com.mapbox.common.LoggingLevel
1416
import com.mapbox.navigation.examples.core.IndependentRouteGenerationActivity
1517
import com.mapbox.navigation.examples.core.MapboxBuildingHighlightActivity
1618
import com.mapbox.navigation.examples.core.MapboxCustomStyleActivity
@@ -39,6 +41,7 @@ class MainActivity : AppCompatActivity(), PermissionsListener {
3941

4042
override fun onCreate(savedInstanceState: Bundle?) {
4143
super.onCreate(savedInstanceState)
44+
LogConfiguration.setLoggingLevel(LoggingLevel.DEBUG)
4245
binding = LayoutActivityMainBinding.inflate(layoutInflater)
4346
setContentView(binding.root)
4447

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

Lines changed: 17 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import android.content.res.Configuration
66
import android.content.res.Resources
77
import android.location.Location
88
import android.os.Bundle
9-
import android.view.View
109
import android.widget.Button
1110
import android.widget.SeekBar
1211
import androidx.appcompat.app.AppCompatActivity
@@ -23,11 +22,8 @@ import com.mapbox.maps.plugin.locationcomponent.location
2322
import com.mapbox.navigation.base.ExperimentalPreviewMapboxNavigationAPI
2423
import com.mapbox.navigation.base.options.NavigationOptions
2524
import com.mapbox.navigation.core.MapboxNavigation
26-
import com.mapbox.navigation.core.MapboxNavigationProvider
2725
import com.mapbox.navigation.core.directions.session.RoutesObserver
28-
import com.mapbox.navigation.core.replay.MapboxReplayer
29-
import com.mapbox.navigation.core.replay.history.ReplayEventBase
30-
import com.mapbox.navigation.core.replay.history.ReplaySetNavigationRoute
26+
import com.mapbox.navigation.core.replay.history.ReplayHistorySession
3127
import com.mapbox.navigation.core.trip.session.LocationMatcherResult
3228
import com.mapbox.navigation.core.trip.session.LocationObserver
3329
import com.mapbox.navigation.core.trip.session.RouteProgressObserver
@@ -50,21 +46,21 @@ import com.mapbox.navigation.ui.maps.route.line.model.MapboxRouteLineOptions
5046
import com.mapbox.navigation.ui.maps.route.line.model.RouteLine
5147
import com.mapbox.navigation.ui.maps.route.line.model.RouteLineColorResources
5248
import com.mapbox.navigation.ui.maps.route.line.model.RouteLineResources
49+
import com.mapbox.navigation.utils.internal.logI
5350
import kotlinx.coroutines.CoroutineScope
5451
import kotlinx.coroutines.Dispatchers
5552
import kotlinx.coroutines.Job
5653
import kotlinx.coroutines.launch
57-
import java.util.Collections
5854

5955
private const val DEFAULT_INITIAL_ZOOM = 15.0
6056

57+
@OptIn(ExperimentalPreviewMapboxNavigationAPI::class)
6158
class ReplayHistoryActivity : AppCompatActivity() {
6259

6360
private var loadNavigationJob: Job? = null
6461
private val navigationLocationProvider = NavigationLocationProvider()
6562
private lateinit var historyFileLoader: HistoryFileLoader
6663
private lateinit var mapboxNavigation: MapboxNavigation
67-
private lateinit var mapboxReplayer: MapboxReplayer
6864
private lateinit var locationComponent: LocationComponentPlugin
6965
private lateinit var navigationCamera: NavigationCamera
7066
private lateinit var viewportDataSource: MapboxNavigationViewportDataSource
@@ -103,6 +99,7 @@ class ReplayHistoryActivity : AppCompatActivity() {
10399
40.0 * pixelDensity
104100
)
105101
}
102+
private val replayHistorySession = ReplayHistorySession()
106103

107104
private val initialCameraOptions: CameraOptions? = CameraOptions.Builder()
108105
.zoom(DEFAULT_INITIAL_ZOOM)
@@ -163,7 +160,7 @@ class ReplayHistoryActivity : AppCompatActivity() {
163160
super.onDestroy()
164161
routeLineApi.cancel()
165162
routeLineView.cancel()
166-
mapboxReplayer.finish()
163+
replayHistorySession.onDetached(mapboxNavigation)
167164
mapboxNavigation.onDestroy()
168165
if (::locationComponent.isInitialized) {
169166
locationComponent.removeOnIndicatorPositionChangedListener(onPositionChangedListener)
@@ -212,15 +209,16 @@ class ReplayHistoryActivity : AppCompatActivity() {
212209
viewportDataSource.onLocationChanged(locationMatcherResult.enhancedLocation)
213210
viewportDataSource.evaluate()
214211
if (!isLocationInitialized) {
212+
logI("ReplayHistoryActivity") {
213+
"onNewLocationMatcherResult initialize location"
214+
}
215215
isLocationInitialized = true
216-
val instantTransition = NavigationCameraTransitionOptions.Builder()
217-
.maxDuration(0)
218-
.build()
219-
navigationCamera.requestNavigationCameraToOverview(
220-
stateTransitionOptions = instantTransition,
216+
navigationCamera.requestNavigationCameraToFollowing(
217+
stateTransitionOptions = NavigationCameraTransitionOptions.Builder()
218+
.maxDuration(0)
219+
.build(),
221220
)
222221
}
223-
224222
navigationLocationProvider.changePosition(
225223
locationMatcherResult.enhancedLocation,
226224
locationMatcherResult.keyPoints,
@@ -308,21 +306,12 @@ class ReplayHistoryActivity : AppCompatActivity() {
308306
@SuppressLint("MissingPermission")
309307
private fun initNavigation() {
310308
historyFileLoader = HistoryFileLoader()
311-
mapboxNavigation = MapboxNavigationProvider.create(
309+
mapboxNavigation = MapboxNavigation(
312310
NavigationOptions.Builder(this)
313311
.accessToken(Utils.getMapboxAccessToken(this))
314312
.build()
315313
)
316-
startReplayTripSession()
317-
}
318-
319-
/**
320-
* This is showcasing a new way to replay rides at runtime.
321-
*/
322-
@OptIn(ExperimentalPreviewMapboxNavigationAPI::class)
323-
private fun startReplayTripSession() {
324-
mapboxReplayer = mapboxNavigation.mapboxReplayer
325-
mapboxNavigation.startReplayTripSession()
314+
replayHistorySession.onAttached(mapboxNavigation)
326315
}
327316

328317
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
@@ -336,24 +325,9 @@ class ReplayHistoryActivity : AppCompatActivity() {
336325
@SuppressLint("MissingPermission")
337326
private fun handleHistoryFileSelected() {
338327
loadNavigationJob = CoroutineScope(Dispatchers.Main).launch {
339-
val events = historyFileLoader
340-
.loadReplayHistory(this@ReplayHistoryActivity)
341-
mapboxReplayer.clearEvents()
342-
mapboxReplayer.pushEvents(events)
343-
binding.playReplay.visibility = View.VISIBLE
344-
mapboxNavigation.resetTripSession()
345-
mapboxNavigation.setRoutes(emptyList())
328+
val historyReader = historyFileLoader.loadReplayHistory(this@ReplayHistoryActivity)
329+
replayHistorySession.setHistoryFile(historyReader.filePath)
346330
isLocationInitialized = false
347-
mapboxReplayer.playFirstLocation()
348-
}
349-
}
350-
351-
@SuppressLint("SetTextI18n")
352-
private fun updateReplayStatus(playbackEvents: List<ReplayEventBase>) {
353-
playbackEvents.lastOrNull()?.eventTimestamp?.let {
354-
val currentSecond = mapboxReplayer.eventSeconds(it).toInt()
355-
val durationSecond = mapboxReplayer.durationSeconds().toInt()
356-
binding.playerStatus.text = "$currentSecond:$durationSecond"
357331
}
358332
}
359333

@@ -368,7 +342,7 @@ class ReplayHistoryActivity : AppCompatActivity() {
368342
binding.seekBar.setOnSeekBarChangeListener(
369343
object : SeekBar.OnSeekBarChangeListener {
370344
override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {
371-
mapboxReplayer.playbackSpeed(progress.toDouble())
345+
mapboxNavigation.mapboxReplayer.playbackSpeed(progress.toDouble())
372346
binding.seekBarText.text = getString(
373347
R.string.replay_playback_speed_seekbar,
374348
progress
@@ -379,26 +353,5 @@ class ReplayHistoryActivity : AppCompatActivity() {
379353
override fun onStopTrackingTouch(seekBar: SeekBar) {}
380354
}
381355
)
382-
383-
binding.playReplay.setOnClickListener {
384-
mapboxReplayer.play()
385-
binding.playReplay.visibility = View.GONE
386-
navigationCamera.requestNavigationCameraToFollowing()
387-
}
388-
389-
mapboxReplayer.registerObserver { events ->
390-
updateReplayStatus(events)
391-
events.forEach {
392-
when (it) {
393-
is ReplaySetNavigationRoute -> setRoute(it)
394-
}
395-
}
396-
}
397-
}
398-
399-
private fun setRoute(replaySetRoute: ReplaySetNavigationRoute) {
400-
replaySetRoute.route?.let { directionRoute ->
401-
mapboxNavigation.setNavigationRoutes(Collections.singletonList(directionRoute))
402-
}
403356
}
404357
}

examples/src/main/java/com/mapbox/navigation/examples/core/replay/HistoryFileLoader.kt

Lines changed: 3 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -3,46 +3,28 @@ package com.mapbox.navigation.examples.core.replay
33
import android.annotation.SuppressLint
44
import android.content.Context
55
import com.mapbox.navigation.core.history.MapboxHistoryReader
6-
import com.mapbox.navigation.core.replay.history.ReplayEventBase
7-
import com.mapbox.navigation.core.replay.history.ReplayHistoryMapper
8-
import com.mapbox.navigation.core.replay.history.ReplaySetNavigationRoute
96
import kotlinx.coroutines.Dispatchers
107
import kotlinx.coroutines.withContext
118

129
class HistoryFileLoader {
13-
private val replayHistoryMapper = ReplayHistoryMapper.Builder().setRouteMapper {
14-
ReplaySetNavigationRoute.Builder(eventTimestamp = it.eventTimestamp)
15-
.route(it.navigationRoute)
16-
.build()
17-
}.build()
1810
private val historyFilesDirectory = HistoryFilesDirectory()
1911

2012
@SuppressLint("MissingPermission")
2113
suspend fun loadReplayHistory(
2214
context: Context
23-
): List<ReplayEventBase> = withContext(Dispatchers.IO) {
24-
loadSelectedHistory() ?: loadDefaultReplayHistory(context)
15+
): MapboxHistoryReader = withContext(Dispatchers.IO) {
16+
HistoryFilesActivity.selectedHistory ?: loadDefaultReplayHistory(context)
2517
}
2618

27-
private suspend fun loadSelectedHistory(): List<ReplayEventBase>? =
28-
withContext(Dispatchers.IO) {
29-
HistoryFilesActivity.selectedHistory?.asSequence()?.mapNotNull { historyEvent ->
30-
replayHistoryMapper.mapToReplayEvent(historyEvent)
31-
}?.toList()
32-
}
33-
3419
private suspend fun loadDefaultReplayHistory(
3520
context: Context
36-
): List<ReplayEventBase> = withContext(Dispatchers.IO) {
21+
): MapboxHistoryReader = withContext(Dispatchers.IO) {
3722
val fileName = "replay-history-activity.json"
3823
val inputStream = context.assets.open(fileName)
3924
val outputFile = historyFilesDirectory.outputFile(context, fileName)
4025
outputFile.outputStream().use { fileOut ->
4126
inputStream.copyTo(fileOut)
4227
}
4328
MapboxHistoryReader(outputFile.absolutePath)
44-
.asSequence()
45-
.mapNotNull { replayHistoryMapper.mapToReplayEvent(it) }
46-
.toList()
4729
}
4830
}

examples/src/main/res/layout/activity_replay_history_layout.xml

Lines changed: 2 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -34,23 +34,11 @@
3434
android:text="@string/select_history"
3535
/>
3636

37-
<androidx.appcompat.widget.AppCompatButton
38-
android:id="@+id/playReplay"
39-
android:layout_width="0dp"
40-
android:layout_height="wrap_content"
41-
app:layout_constraintEnd_toEndOf="parent"
42-
app:layout_constraintStart_toStartOf="parent"
43-
app:layout_constraintBottom_toBottomOf="parent"
44-
android:background="@color/colorPrimary"
45-
android:text="@string/play_history"
46-
android:textColor="@android:color/white"
47-
/>
48-
4937
<com.google.android.material.card.MaterialCardView
5038
android:layout_width="match_parent"
5139
android:layout_height="wrap_content"
5240
android:theme="@style/Theme.MaterialComponents.Light"
53-
app:layout_constraintBottom_toTopOf="@id/playReplay"
41+
app:layout_constraintBottom_toBottomOf="parent"
5442
app:cardElevation="3dp"
5543
app:cardUseCompatPadding="true">
5644

@@ -71,17 +59,10 @@
7159
android:id="@+id/seekBar"
7260
android:layout_width="match_parent"
7361
android:layout_height="wrap_content"
74-
android:paddingBottom="6dp"
62+
android:paddingBottom="30dp"
7563
android:paddingTop="6dp"
7664
/>
7765

78-
<TextView
79-
android:id="@+id/playerStatus"
80-
android:layout_width="match_parent"
81-
android:layout_height="wrap_content"
82-
android:padding="6dp"
83-
android:text="Paused"/>
84-
8566
</LinearLayout>
8667

8768
</com.google.android.material.card.MaterialCardView>

libnavigation-core/api/current.txt

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -529,6 +529,9 @@ package com.mapbox.navigation.core.replay.history {
529529
property public final Double? time;
530530
}
531531

532+
public final class ReplayEventLocationMapperKt {
533+
}
534+
532535
public final class ReplayEventUpdateLocation implements com.mapbox.navigation.core.replay.history.ReplayEventBase {
533536
ctor public ReplayEventUpdateLocation(@com.google.gson.annotations.SerializedName("event_timestamp") double eventTimestamp, @com.google.gson.annotations.SerializedName("location") com.mapbox.navigation.core.replay.history.ReplayEventLocation location);
534537
method public double component1();
@@ -570,6 +573,33 @@ package com.mapbox.navigation.core.replay.history {
570573
method public com.mapbox.navigation.core.replay.history.ReplayHistoryMapper.Builder statusMapper(com.mapbox.navigation.core.replay.history.ReplayHistoryEventMapper<com.mapbox.navigation.core.history.model.HistoryEventGetStatus>? statusMapper);
571574
}
572575

576+
@com.mapbox.navigation.base.ExperimentalPreviewMapboxNavigationAPI public final class ReplayHistorySession implements com.mapbox.navigation.core.lifecycle.MapboxNavigationObserver {
577+
ctor public ReplayHistorySession();
578+
method public kotlinx.coroutines.flow.StateFlow<com.mapbox.navigation.core.replay.history.ReplayHistorySessionOptions> getOptions();
579+
method public void onAttached(com.mapbox.navigation.core.MapboxNavigation mapboxNavigation);
580+
method public void onDetached(com.mapbox.navigation.core.MapboxNavigation mapboxNavigation);
581+
method public void setHistoryFile(String absolutePath);
582+
method public void setOptions(com.mapbox.navigation.core.replay.history.ReplayHistorySessionOptions options);
583+
}
584+
585+
@com.mapbox.navigation.base.ExperimentalPreviewMapboxNavigationAPI public final class ReplayHistorySessionOptions {
586+
method public boolean getEnableSetRoute();
587+
method public String? getFilePath();
588+
method public com.mapbox.navigation.core.replay.history.ReplayHistoryMapper getReplayHistoryMapper();
589+
method public com.mapbox.navigation.core.replay.history.ReplayHistorySessionOptions.Builder toBuilder();
590+
property public final boolean enableSetRoute;
591+
property public final String? filePath;
592+
property public final com.mapbox.navigation.core.replay.history.ReplayHistoryMapper replayHistoryMapper;
593+
}
594+
595+
@com.mapbox.navigation.base.ExperimentalPreviewMapboxNavigationAPI public static final class ReplayHistorySessionOptions.Builder {
596+
ctor public ReplayHistorySessionOptions.Builder();
597+
method public com.mapbox.navigation.core.replay.history.ReplayHistorySessionOptions build();
598+
method public com.mapbox.navigation.core.replay.history.ReplayHistorySessionOptions.Builder enableSetRoute(boolean enableSetRoute);
599+
method public com.mapbox.navigation.core.replay.history.ReplayHistorySessionOptions.Builder filePath(String? filePath);
600+
method public com.mapbox.navigation.core.replay.history.ReplayHistorySessionOptions.Builder replayHistoryMapper(com.mapbox.navigation.core.replay.history.ReplayHistoryMapper replayHistoryMapper);
601+
}
602+
573603
public final class ReplaySetNavigationRoute implements com.mapbox.navigation.core.replay.history.ReplayEventBase {
574604
method public double getEventTimestamp();
575605
method public com.mapbox.navigation.base.route.NavigationRoute? getRoute();
@@ -1027,8 +1057,10 @@ package com.mapbox.navigation.core.trip {
10271057
@com.mapbox.navigation.base.ExperimentalPreviewMapboxNavigationAPI public final class MapboxTripStarter implements com.mapbox.navigation.core.lifecycle.MapboxNavigationObserver {
10281058
method public static com.mapbox.navigation.core.trip.MapboxTripStarter create();
10291059
method public com.mapbox.navigation.core.trip.MapboxTripStarter enableMapMatching();
1060+
method public com.mapbox.navigation.core.trip.MapboxTripStarter enableReplayHistory(com.mapbox.navigation.core.replay.history.ReplayHistorySessionOptions? options = null);
10301061
method public com.mapbox.navigation.core.trip.MapboxTripStarter enableReplayRoute(com.mapbox.navigation.core.replay.route.ReplayRouteSessionOptions? options = null);
10311062
method public static com.mapbox.navigation.core.trip.MapboxTripStarter getRegisteredInstance();
1063+
method public com.mapbox.navigation.core.replay.history.ReplayHistorySessionOptions getReplayHistorySessionOptions();
10321064
method public com.mapbox.navigation.core.replay.route.ReplayRouteSessionOptions getReplayRouteSessionOptions();
10331065
method public void onAttached(com.mapbox.navigation.core.MapboxNavigation mapboxNavigation);
10341066
method public void onDetached(com.mapbox.navigation.core.MapboxNavigation mapboxNavigation);
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package com.mapbox.navigation.core.history
2+
3+
import androidx.annotation.VisibleForTesting
4+
5+
@VisibleForTesting
6+
internal object MapboxHistoryReaderProvider {
7+
fun create(filePath: String) = MapboxHistoryReader(filePath)
8+
}

0 commit comments

Comments
 (0)