feat: also collect track position in ms, allows pause handling

This commit is contained in:
2026-05-24 07:29:26 +02:00
parent 698605d7a9
commit b5bc01fac2
4 changed files with 58 additions and 18 deletions

View File

@@ -232,13 +232,17 @@ fun NowPlayingRoute(
LaunchedEffect(collectRunData, playback) { LaunchedEffect(collectRunData, playback) {
if (!collectRunData) { if (!collectRunData) {
collector.setAcceptSamples(false) collector.setCollectingEnabled(false)
return@LaunchedEffect return@LaunchedEffect
} }
val service = playback ?: return@LaunchedEffect val service = playback ?: run {
collector.setCollectingEnabled(false)
return@LaunchedEffect
}
collector.setPlaybackPositionMsProvider { service.getPlaybackPositionMs() }
collector.setCollectingEnabled(true)
var lastTrackId: String? = null var lastTrackId: String? = null
service.uiState.collect { state -> service.uiState.collect { state ->
collector.setAcceptSamples(state.isPlaying)
val trackId = state.currentTrackId val trackId = state.currentTrackId
if (trackId != null && trackId != lastTrackId) { if (trackId != null && trackId != lastTrackId) {
collector.markSongStart() collector.markSongStart()

View File

@@ -26,7 +26,7 @@ class RunDataCollector(
private val handlerThread = HandlerThread("RunDataCollect").apply { start() } private val handlerThread = HandlerThread("RunDataCollect").apply { start() }
private val handler = Handler(handlerThread.looper) private val handler = Handler(handlerThread.looper)
private val accelBuffer = mutableListOf<RunDataSample>() private val accelBuffer = mutableListOf<RunAccelSample>()
private val gyroBuffer = mutableListOf<RunDataSample>() private val gyroBuffer = mutableListOf<RunDataSample>()
private val gpsBuffer = mutableListOf<RunGpsSample>() private val gpsBuffer = mutableListOf<RunGpsSample>()
@@ -34,7 +34,10 @@ class RunDataCollector(
private var songStartElapsedRealtimeNanos: Long? = null private var songStartElapsedRealtimeNanos: Long? = null
@Volatile @Volatile
private var acceptSamples = false private var collectingEnabled = false
@Volatile
private var playbackPositionMsProvider: () -> Long = { 0L }
private var sensorsRegistered = false private var sensorsRegistered = false
private var locationRegistered = false private var locationRegistered = false
@@ -42,24 +45,32 @@ class RunDataCollector(
private val sensorListener = private val sensorListener =
object : SensorEventListener { object : SensorEventListener {
override fun onSensorChanged(event: SensorEvent) { override fun onSensorChanged(event: SensorEvent) {
if (!acceptSamples) return if (!collectingEnabled) return
val timestamp = relativeTimestampNanos(event.timestamp) ?: return val timestamp = relativeTimestampNanos(event.timestamp) ?: return
when (event.sensor.type) {
Sensor.TYPE_ACCELEROMETER -> {
val sample =
RunAccelSample(
timestampNanos = timestamp,
positionMs = playbackPositionMsProvider(),
values = floatArrayOf(event.values[0], event.values[1], event.values[2]),
)
synchronized(accelBuffer) {
accelBuffer.add(sample)
}
}
Sensor.TYPE_GYROSCOPE -> {
val sample = val sample =
RunDataSample( RunDataSample(
timestampNanos = timestamp, timestampNanos = timestamp,
values = floatArrayOf(event.values[0], event.values[1], event.values[2]), values = floatArrayOf(event.values[0], event.values[1], event.values[2]),
) )
when (event.sensor.type) {
Sensor.TYPE_ACCELEROMETER ->
synchronized(accelBuffer) {
accelBuffer.add(sample)
}
Sensor.TYPE_GYROSCOPE ->
synchronized(gyroBuffer) { synchronized(gyroBuffer) {
gyroBuffer.add(sample) gyroBuffer.add(sample)
} }
} }
} }
}
override fun onAccuracyChanged( override fun onAccuracyChanged(
sensor: Sensor?, sensor: Sensor?,
@@ -69,7 +80,7 @@ class RunDataCollector(
private val locationListener = private val locationListener =
LocationListener { location -> LocationListener { location ->
if (!acceptSamples) return@LocationListener if (!collectingEnabled) return@LocationListener
recordGpsLocation(location) recordGpsLocation(location)
} }
@@ -148,8 +159,12 @@ class RunDataCollector(
songStartElapsedRealtimeNanos = null songStartElapsedRealtimeNanos = null
} }
fun setAcceptSamples(accept: Boolean) { fun setCollectingEnabled(enabled: Boolean) {
acceptSamples = accept collectingEnabled = enabled
}
fun setPlaybackPositionMsProvider(provider: () -> Long) {
playbackPositionMsProvider = provider
} }
fun snapshotAndClear(): RunTrackDataSnapshot = fun snapshotAndClear(): RunTrackDataSnapshot =

View File

@@ -49,7 +49,7 @@ object RunDataStorage {
val jsonString = val jsonString =
JSONObject() JSONObject()
.apply { .apply {
put("data", samplesToJsonArray(snapshot.accelerometer)) put("data", accelToJsonArray(snapshot.accelerometer))
put("gyro", samplesToJsonArray(snapshot.gyroscope)) put("gyro", samplesToJsonArray(snapshot.gyroscope))
put("gps", gpsToJsonArray(snapshot.gps)) put("gps", gpsToJsonArray(snapshot.gps))
put("meta", metaContentUri) put("meta", metaContentUri)
@@ -65,6 +65,27 @@ object RunDataStorage {
} }
} }
private fun accelToJsonArray(samples: List<RunAccelSample>): JSONArray {
val array = JSONArray()
for (sample in samples) {
array.put(
JSONObject().apply {
put("timestamp", sample.timestampNanos)
put("positionMs", sample.positionMs)
put(
"values",
JSONArray().apply {
for (v in sample.values) {
put(v.toDouble())
}
},
)
},
)
}
return array
}
private fun samplesToJsonArray(samples: List<RunDataSample>): JSONArray { private fun samplesToJsonArray(samples: List<RunDataSample>): JSONArray {
val array = JSONArray() val array = JSONArray()
for (sample in samples) { for (sample in samples) {

View File

@@ -8,7 +8,7 @@ data class RunGpsSample(
) )
data class RunTrackDataSnapshot( data class RunTrackDataSnapshot(
val accelerometer: List<RunDataSample>, val accelerometer: List<RunAccelSample>,
val gyroscope: List<RunDataSample>, val gyroscope: List<RunDataSample>,
val gps: List<RunGpsSample>, val gps: List<RunGpsSample>,
) { ) {