feat(recordings): перемотка записей + тайм-коды треков
1) Перемотка: записи эфира — сырой ADTS-AAC/MP3 без индексов, ExoPlayer считал их неперематываемыми (старт всегда с нуля). Включён CBR-seeking (DefaultExtractorsFactory.setConstantBitrateSeekingEnabled) — seek работает. 2) Тайм-коды треков: при записи фиксируются смены now-playing с offset от начала (модель TrackMarker, колонка markers в recordings, миграция v6, захват через NowPlayingRepository — свой поллинг, не зависит от экрана). В плеере записи — список «Треки в записи»: тайм-код + название, тап переходит к моменту, текущий трек подсвечен. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -2,9 +2,14 @@ package com.radiola.ui.recordings
|
||||
|
||||
import androidx.compose.animation.Crossfade
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
@@ -54,6 +59,7 @@ fun RecordingPlayerSheet(
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.verticalScroll(rememberScrollState())
|
||||
.padding(horizontal = 24.dp)
|
||||
.padding(bottom = 40.dp),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
@@ -202,6 +208,78 @@ fun RecordingPlayerSheet(
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Список треков записи с переходом по тайм-коду
|
||||
if (recording.markers.isNotEmpty()) {
|
||||
Spacer(modifier = Modifier.height(28.dp))
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Text(
|
||||
text = "Треки в записи",
|
||||
style = MaterialTheme.typography.titleSmall,
|
||||
color = colors.textPrimary,
|
||||
fontWeight = FontWeight.SemiBold
|
||||
)
|
||||
Text(
|
||||
text = "${recording.markers.size}",
|
||||
style = MaterialTheme.typography.labelMedium,
|
||||
color = colors.textMuted
|
||||
)
|
||||
}
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
// Индекс текущего трека: последняя метка, до которой уже дошло время
|
||||
val activeIndex = recording.markers.indexOfLast { positionMs >= it.offsetMs }
|
||||
recording.markers.forEachIndexed { index, marker ->
|
||||
MarkerRow(
|
||||
timecode = formatMs(marker.offsetMs),
|
||||
title = marker.title,
|
||||
active = index == activeIndex,
|
||||
onClick = {
|
||||
haptic.performHapticFeedback(HapticFeedbackType.LongPress)
|
||||
viewModel.seekTo(marker.offsetMs)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Строка трека в записи: тайм-код + название, тап → переход. */
|
||||
@Composable
|
||||
private fun MarkerRow(
|
||||
timecode: String,
|
||||
title: String,
|
||||
active: Boolean,
|
||||
onClick: () -> Unit
|
||||
) {
|
||||
val colors = RadiolaTheme.colors
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clip(RoundedCornerShape(12.dp))
|
||||
.background(if (active) colors.surface2 else androidx.compose.ui.graphics.Color.Transparent)
|
||||
.clickable(onClick = onClick)
|
||||
.padding(horizontal = 12.dp, vertical = 12.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(12.dp)
|
||||
) {
|
||||
Text(
|
||||
text = timecode,
|
||||
style = MaterialTheme.typography.labelMedium,
|
||||
color = if (active) colors.accent else colors.textMuted,
|
||||
fontWeight = FontWeight.Medium
|
||||
)
|
||||
Text(
|
||||
text = title.ifBlank { "—" },
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = if (active) colors.accent else colors.textPrimary,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
modifier = Modifier.weight(1f)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user