mirror of
https://github.com/GrakovNe/lissen-android.git
synced 2026-08-02 11:29:52 -04:00
player screen
This commit is contained in:
@@ -8,8 +8,6 @@ import android.os.Looper
|
||||
import androidx.annotation.OptIn
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.media3.common.MediaItem
|
||||
import androidx.media3.common.MediaMetadata
|
||||
import androidx.media3.common.Player
|
||||
import androidx.media3.common.Timeline
|
||||
import androidx.media3.common.util.UnstableApi
|
||||
@@ -21,23 +19,26 @@ import com.google.common.util.concurrent.MoreExecutors
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import org.grakovne.lissen.domain.DetailedBook
|
||||
import org.grakovne.lissen.player.service.AudioPlayerService
|
||||
import org.grakovne.lissen.repository.ServerMediaRepository
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
@OptIn(UnstableApi::class)
|
||||
class MediaRepository
|
||||
@Inject constructor(
|
||||
@ApplicationContext private val context: Context,
|
||||
private val serverMediaRepository: ServerMediaRepository
|
||||
@ApplicationContext private val context: Context
|
||||
) {
|
||||
|
||||
private val sessionToken =
|
||||
SessionToken(context, ComponentName(context, AudioPlayerService::class.java))
|
||||
private lateinit var mediaController: MediaController
|
||||
|
||||
private val token = SessionToken(
|
||||
context,
|
||||
ComponentName(context, AudioPlayerService::class.java)
|
||||
)
|
||||
|
||||
val _isPlaying = MutableLiveData(false)
|
||||
val _currentPosition = MutableLiveData<Long>()
|
||||
val _currentMediaItemIndex = MutableLiveData<Int>()
|
||||
|
||||
private val handler = Handler(Looper.getMainLooper())
|
||||
|
||||
@@ -50,7 +51,7 @@ class MediaRepository
|
||||
}
|
||||
|
||||
init {
|
||||
val controllerBuilder = MediaController.Builder(context, sessionToken)
|
||||
val controllerBuilder = MediaController.Builder(context, token)
|
||||
val futureController = controllerBuilder.buildAsync()
|
||||
|
||||
Futures.addCallback(
|
||||
@@ -62,6 +63,7 @@ class MediaRepository
|
||||
controller.addListener(object : Player.Listener {
|
||||
override fun onPlaybackStateChanged(playbackState: Int) {
|
||||
_isPlaying.postValue(playbackState == Player.STATE_READY && controller.isPlaying)
|
||||
_currentMediaItemIndex.postValue(mediaController.currentMediaItemIndex)
|
||||
|
||||
when (controller.isPlaying) {
|
||||
true -> startUpdatingProgress()
|
||||
@@ -82,13 +84,19 @@ class MediaRepository
|
||||
oldPosition: Player.PositionInfo,
|
||||
newPosition: Player.PositionInfo,
|
||||
reason: Int
|
||||
) = _currentPosition.postValue(mediaController.currentPosition)
|
||||
) {
|
||||
_currentMediaItemIndex.postValue(mediaController.currentMediaItemIndex)
|
||||
_currentPosition.postValue(mediaController.currentPosition)
|
||||
}
|
||||
|
||||
|
||||
override fun onTimelineChanged(
|
||||
timeline: Timeline,
|
||||
reason: Int
|
||||
) = _currentPosition.postValue(mediaController.currentPosition)
|
||||
) {
|
||||
_currentMediaItemIndex.postValue(mediaController.currentMediaItemIndex)
|
||||
_currentPosition.postValue(mediaController.currentPosition)
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
@@ -101,7 +109,6 @@ class MediaRepository
|
||||
}
|
||||
|
||||
|
||||
@OptIn(UnstableApi::class)
|
||||
fun playAudio(book: DetailedBook) {
|
||||
val intent = Intent(context, AudioPlayerService::class.java).apply {
|
||||
action = AudioPlayerService.ACTION_START_FOREGROUND
|
||||
@@ -121,11 +128,48 @@ class MediaRepository
|
||||
ContextCompat.startForegroundService(context, intent)
|
||||
}
|
||||
|
||||
fun startUpdatingProgress() {
|
||||
fun nextTrack() {
|
||||
val nextIndex = mediaController.currentMediaItemIndex + 1
|
||||
val timeline = mediaController.currentTimeline
|
||||
|
||||
if (nextIndex < timeline.windowCount) {
|
||||
mediaController.seekTo(nextIndex, 0)
|
||||
}
|
||||
}
|
||||
|
||||
fun setTrack(index: Int) {
|
||||
val timeline = mediaController.currentTimeline
|
||||
|
||||
if (index < timeline.windowCount && index >= 0) {
|
||||
mediaController.seekTo(index, 0)
|
||||
}
|
||||
}
|
||||
|
||||
fun seekTo(position: Float) {
|
||||
stopUpdatingProgress()
|
||||
|
||||
val duration = mediaController.duration
|
||||
if (duration > 0) {
|
||||
val newPosition = (1000 * position).toLong()
|
||||
mediaController.seekTo(newPosition)
|
||||
}
|
||||
|
||||
startUpdatingProgress()
|
||||
}
|
||||
|
||||
fun previousTrack() {
|
||||
val previousIndex = mediaController.currentMediaItemIndex - 1
|
||||
|
||||
if (previousIndex > 0) {
|
||||
mediaController.seekTo(previousIndex, 0)
|
||||
}
|
||||
}
|
||||
|
||||
private fun startUpdatingProgress() {
|
||||
handler.post(updateProgressAction)
|
||||
}
|
||||
|
||||
fun stopUpdatingProgress() {
|
||||
private fun stopUpdatingProgress() {
|
||||
handler.removeCallbacks(updateProgressAction)
|
||||
}
|
||||
}
|
||||
@@ -23,8 +23,13 @@ import androidx.compose.material3.Slider
|
||||
import androidx.compose.material3.SliderDefaults
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.livedata.observeAsState
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
@@ -43,8 +48,16 @@ fun TrackControlComposable(
|
||||
val book by viewModel.book.observeAsState()
|
||||
val chapters = book?.chapters ?: emptyList()
|
||||
|
||||
var sliderPosition by remember { mutableStateOf(0f) }
|
||||
var isDragging by remember { mutableStateOf(false) }
|
||||
val duration = book?.chapters?.get(currentTrackIndex)?.duration?.toFloat() ?: 0f
|
||||
|
||||
val draggableTrackProgress by remember {
|
||||
derivedStateOf {
|
||||
if (!isDragging) currentPosition.toFloat() else sliderPosition
|
||||
}
|
||||
}
|
||||
|
||||
Column(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
@@ -52,8 +65,15 @@ fun TrackControlComposable(
|
||||
) {
|
||||
|
||||
Slider(
|
||||
value = currentPosition.toFloat(),
|
||||
onValueChange = { newPosition -> viewModel.seekTo(newPosition) },
|
||||
value = draggableTrackProgress,
|
||||
onValueChange = { newPosition ->
|
||||
sliderPosition = newPosition
|
||||
isDragging = true
|
||||
},
|
||||
onValueChangeFinished = {
|
||||
viewModel.seekTo(sliderPosition)
|
||||
isDragging = false
|
||||
},
|
||||
valueRange = 0f..(duration),
|
||||
modifier = Modifier
|
||||
.height(36.dp)
|
||||
|
||||
@@ -26,10 +26,9 @@ class PlayerViewModel @Inject constructor(
|
||||
val playingQueueExpanded = _playingQueueExpanded
|
||||
|
||||
val isPlaying = mediaRepository._isPlaying
|
||||
|
||||
val currentPosition = mediaRepository._currentPosition
|
||||
|
||||
private val _currentTrackIndex = MutableLiveData(0)
|
||||
private val _currentTrackIndex = mediaRepository._currentMediaItemIndex
|
||||
val currentTrackIndex: LiveData<Int> = _currentTrackIndex
|
||||
|
||||
fun togglePlayingQueue() {
|
||||
@@ -62,19 +61,19 @@ class PlayerViewModel @Inject constructor(
|
||||
}
|
||||
|
||||
fun seekTo(position: Float) {
|
||||
//_currentPosition.value = position
|
||||
mediaRepository.seekTo(position)
|
||||
}
|
||||
|
||||
fun setChapter(index: Int) {
|
||||
_currentTrackIndex.value = index
|
||||
mediaRepository.setTrack(index)
|
||||
}
|
||||
|
||||
fun nextTrack() {
|
||||
_currentTrackIndex.value = _currentTrackIndex.value?.plus(1)
|
||||
mediaRepository.nextTrack()
|
||||
}
|
||||
|
||||
fun previousTrack() {
|
||||
_currentTrackIndex.value = _currentTrackIndex.value?.minus(1)
|
||||
mediaRepository.previousTrack()
|
||||
}
|
||||
|
||||
fun togglePlayPause() {
|
||||
|
||||
Reference in New Issue
Block a user