From 3240d25963160c34a2944201b772052204af78ba Mon Sep 17 00:00:00 2001 From: andannn Date: Fri, 29 Nov 2024 18:05:08 +0900 Subject: [PATCH] temp --- .../melodify/util/ComposeMultiplatform.kt | 1 + .../andannn/melodify/MelodifyDesktopApp.kt | 30 ++- ...ediaControllerRepositoryImpl.android.kt.kt | 2 +- .../repository/MediaControllerRepository.kt | 2 +- .../repository/UserPreferenceRepository.kt | 3 - .../UserPreferenceRepositoryImpl.kt | 4 - .../fake/FakeMediaControllerRepositoryImpl.kt | 2 +- .../core/data/di/DataModule.desktop.kt | 8 +- .../MediaControllerRepository.desktop.kt | 69 ++++++ .../PlayerStateMonitoryRepository.desktop.kt | 52 ++++ .../core/database/dao/MediaLibraryDao.kt | 3 + core/player/build.gradle.kts | 4 + .../core/player/di/PlayerModule.android.kt | 2 +- .../melodify/core/player/di/PlayerModule.kt | 4 +- .../melodify/core/player/PlayerImpl.kt | 234 ++++++++++++++++++ .../andannn/melodify/core/player/VlcPlayer.kt | 47 ++++ .../core/player/di/PlayerModule.desktop.kt | 8 +- .../syncer/MediaLibraryScanner.desktop.kt | 1 - .../syncer/util/extractTagFromAudioFile.kt | 1 + .../core/syncer/util/generateHashKey.kt | 15 +- .../syncer/util/scanAllLibraryAudioFile.kt | 2 +- .../melodify/core/syncer/util/UtilTest.kt | 17 +- .../melodify/feature/home/HomeScreen.kt | 171 ++----------- .../melodify/feature/home/HomeViewModel.kt | 18 +- .../feature/player/PlayerAreaViewModel.kt | 2 +- .../melodify/feature/player/PlayerSector.kt | 40 ++- gradle/libs.versions.toml | 2 + 27 files changed, 517 insertions(+), 227 deletions(-) create mode 100644 core/data/src/desktopMain/kotlin/com/andannn/melodify/core/data/repository/MediaControllerRepository.desktop.kt create mode 100644 core/data/src/desktopMain/kotlin/com/andannn/melodify/core/data/repository/PlayerStateMonitoryRepository.desktop.kt create mode 100644 core/player/src/desktopMain/kotlin/com/andannn/melodify/core/player/PlayerImpl.kt create mode 100644 core/player/src/desktopMain/kotlin/com/andannn/melodify/core/player/VlcPlayer.kt diff --git a/build-logic/android-plugins/src/main/kotlin/com/andanana/melodify/util/ComposeMultiplatform.kt b/build-logic/android-plugins/src/main/kotlin/com/andanana/melodify/util/ComposeMultiplatform.kt index fdc9d9ee..ae9317ea 100644 --- a/build-logic/android-plugins/src/main/kotlin/com/andanana/melodify/util/ComposeMultiplatform.kt +++ b/build-logic/android-plugins/src/main/kotlin/com/andanana/melodify/util/ComposeMultiplatform.kt @@ -27,6 +27,7 @@ fun Project.configureComposeMultiplatform( commonMain.dependencies { implementation(compose.runtime) implementation(compose.foundation) + implementation(compose.material) implementation(compose.material3) implementation(compose.animation) implementation(compose.ui) diff --git a/composeApp/src/desktopMain/kotlin/com/andannn/melodify/MelodifyDesktopApp.kt b/composeApp/src/desktopMain/kotlin/com/andannn/melodify/MelodifyDesktopApp.kt index b1e1899e..9875f427 100644 --- a/composeApp/src/desktopMain/kotlin/com/andannn/melodify/MelodifyDesktopApp.kt +++ b/composeApp/src/desktopMain/kotlin/com/andannn/melodify/MelodifyDesktopApp.kt @@ -3,6 +3,8 @@ package com.andannn.melodify import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Surface import androidx.compose.material3.VerticalDivider @@ -11,6 +13,7 @@ import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp import androidx.compose.ui.window.MenuBar import androidx.compose.ui.window.Window import com.andannn.melodify.core.data.model.AudioItemModel @@ -47,26 +50,27 @@ fun MelodifyDeskTopApp() { fun MainWindowContent( modifier: Modifier = Modifier, ) { - Row(modifier = modifier.fillMaxSize()) { - LeftSidePaneSector( - modifier = Modifier.weight(1f) - ) - - VerticalDivider() + Column( + modifier = Modifier.fillMaxSize(), + ) { - Column( - modifier = Modifier.weight(2f), - ) { - TabWithContentSector( + Row(modifier = modifier.weight(1f)) { + LeftSidePaneSector( modifier = Modifier.weight(1f) ) - HorizontalDivider() + VerticalDivider() - PlayerSector( - modifier = Modifier + TabWithContentSector( + modifier = Modifier.weight(2f) ) } + + HorizontalDivider() + + PlayerSector( + modifier = Modifier + ) } } diff --git a/core/data/src/androidMain/kotlin/com/andannn/melodify/core/data/repository/MediaControllerRepositoryImpl.android.kt.kt b/core/data/src/androidMain/kotlin/com/andannn/melodify/core/data/repository/MediaControllerRepositoryImpl.android.kt.kt index 02c29280..5cd06fee 100644 --- a/core/data/src/androidMain/kotlin/com/andannn/melodify/core/data/repository/MediaControllerRepositoryImpl.android.kt.kt +++ b/core/data/src/androidMain/kotlin/com/andannn/melodify/core/data/repository/MediaControllerRepositoryImpl.android.kt.kt @@ -23,7 +23,7 @@ internal class MediaControllerRepositoryImpl( private val mediaBrowser get() = mediaBrowserManager.mediaBrowser - override val duration: Long + override val currentDuration: Long get() = mediaBrowser.duration override fun playMediaList(mediaList: List, index: Int) { diff --git a/core/data/src/commonMain/kotlin/com/andannn/melodify/core/data/repository/MediaControllerRepository.kt b/core/data/src/commonMain/kotlin/com/andannn/melodify/core/data/repository/MediaControllerRepository.kt index 8a755bee..8ae6bc7d 100644 --- a/core/data/src/commonMain/kotlin/com/andannn/melodify/core/data/repository/MediaControllerRepository.kt +++ b/core/data/src/commonMain/kotlin/com/andannn/melodify/core/data/repository/MediaControllerRepository.kt @@ -6,7 +6,7 @@ import kotlinx.coroutines.flow.Flow import kotlin.time.Duration interface MediaControllerRepository { - val duration: Long? + val currentDuration: Long? fun playMediaList(mediaList: List, index: Int) diff --git a/core/data/src/commonMain/kotlin/com/andannn/melodify/core/data/repository/UserPreferenceRepository.kt b/core/data/src/commonMain/kotlin/com/andannn/melodify/core/data/repository/UserPreferenceRepository.kt index 5d78d7ad..14304de7 100644 --- a/core/data/src/commonMain/kotlin/com/andannn/melodify/core/data/repository/UserPreferenceRepository.kt +++ b/core/data/src/commonMain/kotlin/com/andannn/melodify/core/data/repository/UserPreferenceRepository.kt @@ -1,7 +1,6 @@ package com.andannn.melodify.core.data.repository import com.andannn.melodify.core.data.model.CustomTab -import com.andannn.melodify.core.data.model.MediaPreviewMode import com.andannn.melodify.core.data.model.UserSetting import kotlinx.coroutines.flow.Flow @@ -10,7 +9,5 @@ interface UserPreferenceRepository { val currentCustomTabsFlow: Flow> - suspend fun setPreviewMode(previewMode: MediaPreviewMode) - suspend fun updateCurrentCustomTabs(currentCustomTabs: List) } diff --git a/core/data/src/commonMain/kotlin/com/andannn/melodify/core/data/repository/UserPreferenceRepositoryImpl.kt b/core/data/src/commonMain/kotlin/com/andannn/melodify/core/data/repository/UserPreferenceRepositoryImpl.kt index 19ba77e7..5a259705 100644 --- a/core/data/src/commonMain/kotlin/com/andannn/melodify/core/data/repository/UserPreferenceRepositoryImpl.kt +++ b/core/data/src/commonMain/kotlin/com/andannn/melodify/core/data/repository/UserPreferenceRepositoryImpl.kt @@ -37,10 +37,6 @@ class UserPreferenceRepositoryImpl( .getCustomTabsFlow() .map { it.mapToCustomTabModel() } - override suspend fun setPreviewMode(previewMode: MediaPreviewMode) { - preferences.setMediaPreviewMode(previewMode.toIntValue()) - } - override suspend fun updateCurrentCustomTabs(currentCustomTabs: List) { userDataDao.clearAndInsertCustomTabs( currentCustomTabs.map { it.toEntity() } diff --git a/core/data/src/commonMain/kotlin/com/andannn/melodify/core/data/repository/fake/FakeMediaControllerRepositoryImpl.kt b/core/data/src/commonMain/kotlin/com/andannn/melodify/core/data/repository/fake/FakeMediaControllerRepositoryImpl.kt index cccea0dc..d30ffcdb 100644 --- a/core/data/src/commonMain/kotlin/com/andannn/melodify/core/data/repository/fake/FakeMediaControllerRepositoryImpl.kt +++ b/core/data/src/commonMain/kotlin/com/andannn/melodify/core/data/repository/fake/FakeMediaControllerRepositoryImpl.kt @@ -12,7 +12,7 @@ private const val TAG = "MediaControllerRepository" internal class FakeMediaControllerRepositoryImpl( ) : MediaControllerRepository { - override val duration: Long? + override val currentDuration: Long? get() = 0L override fun playMediaList(mediaList: List, index: Int) { diff --git a/core/data/src/desktopMain/kotlin/com/andannn/melodify/core/data/di/DataModule.desktop.kt b/core/data/src/desktopMain/kotlin/com/andannn/melodify/core/data/di/DataModule.desktop.kt index f5b4d898..7232c843 100644 --- a/core/data/src/desktopMain/kotlin/com/andannn/melodify/core/data/di/DataModule.desktop.kt +++ b/core/data/src/desktopMain/kotlin/com/andannn/melodify/core/data/di/DataModule.desktop.kt @@ -1,19 +1,19 @@ package com.andannn.melodify.core.data.di import com.andannn.melodify.core.data.repository.MediaControllerRepository +import com.andannn.melodify.core.data.repository.MediaControllerRepositoryImpl import com.andannn.melodify.core.data.repository.PlayerStateMonitoryRepository -import com.andannn.melodify.core.data.repository.fake.FakeMediaControllerRepositoryImpl -import com.andannn.melodify.core.data.repository.fake.FakePlayerStateMonitoryRepositoryImpl +import com.andannn.melodify.core.data.repository.PlayerStateMonitoryRepositoryImpl import org.koin.core.module.Module import org.koin.core.module.dsl.singleOf import org.koin.dsl.bind import org.koin.dsl.module internal actual val platformDataModule: Module = module { - singleOf(::FakeMediaControllerRepositoryImpl).bind( + singleOf(::MediaControllerRepositoryImpl).bind( MediaControllerRepository::class ) - singleOf(::FakePlayerStateMonitoryRepositoryImpl).bind( + singleOf(::PlayerStateMonitoryRepositoryImpl).bind( PlayerStateMonitoryRepository::class ) } diff --git a/core/data/src/desktopMain/kotlin/com/andannn/melodify/core/data/repository/MediaControllerRepository.desktop.kt b/core/data/src/desktopMain/kotlin/com/andannn/melodify/core/data/repository/MediaControllerRepository.desktop.kt new file mode 100644 index 00000000..c6136064 --- /dev/null +++ b/core/data/src/desktopMain/kotlin/com/andannn/melodify/core/data/repository/MediaControllerRepository.desktop.kt @@ -0,0 +1,69 @@ +package com.andannn.melodify.core.data.repository + +import com.andannn.melodify.core.data.model.AudioItemModel +import com.andannn.melodify.core.data.model.PlayMode +import com.andannn.melodify.core.player.VlcPlayer +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flowOf +import kotlin.time.Duration + +internal class MediaControllerRepositoryImpl( + private val vlcPlayer: VlcPlayer +) : MediaControllerRepository { + override val currentDuration: Long + get() = vlcPlayer.currentDurationMs + + override fun playMediaList(mediaList: List, index: Int) { + vlcPlayer.playMediaList( + mediaList.map { it.source }, + index + ) + } + + override fun seekToNext() = vlcPlayer.seekToNext() + + override fun seekToPrevious() = vlcPlayer.seekToPrevious() + + override fun seekMediaItem(mediaItemIndex: Int, positionMs: Long) = + vlcPlayer.seekMediaItem(mediaItemIndex, positionMs) + + override fun seekToTime(time: Long) = vlcPlayer.seekToTime(time) + + override fun setPlayMode(mode: PlayMode) { + } + + override fun setShuffleModeEnabled(enable: Boolean) { + } + + override fun play() = vlcPlayer.play() + + override fun pause() = vlcPlayer.pause() + + override fun addMediaItems(index: Int, mediaItems: List) = + vlcPlayer.addMediaItems( + index = index, + mrls = mediaItems.map { it.source }, + ) + + override fun moveMediaItem(from: Int, to: Int) = vlcPlayer.moveMediaItem(from, to) + + override fun removeMediaItem(index: Int) = vlcPlayer.removeMediaItem(index) + + override fun isCounting(): Boolean { + return false + } + + override fun observeIsCounting(): Flow { + return flowOf(false) + } + + override fun observeRemainTime(): Flow { + return flowOf() + } + + override fun startSleepTimer(duration: Duration) { + } + + override fun cancelSleepTimer() { + } +} \ No newline at end of file diff --git a/core/data/src/desktopMain/kotlin/com/andannn/melodify/core/data/repository/PlayerStateMonitoryRepository.desktop.kt b/core/data/src/desktopMain/kotlin/com/andannn/melodify/core/data/repository/PlayerStateMonitoryRepository.desktop.kt new file mode 100644 index 00000000..0db6872a --- /dev/null +++ b/core/data/src/desktopMain/kotlin/com/andannn/melodify/core/data/repository/PlayerStateMonitoryRepository.desktop.kt @@ -0,0 +1,52 @@ +package com.andannn.melodify.core.data.repository + +import com.andannn.melodify.core.data.model.AudioItemModel +import com.andannn.melodify.core.data.model.PlayMode +import com.andannn.melodify.core.data.util.toAppItem +import com.andannn.melodify.core.database.dao.MediaLibraryDao +import com.andannn.melodify.core.player.VlcPlayer +import com.andannn.melodify.core.player.PlayerState +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.map + +internal class PlayerStateMonitoryRepositoryImpl( + private val vlcPlayer: VlcPlayer, + private val libraryDao: MediaLibraryDao +) : PlayerStateMonitoryRepository { + override val currentPositionMs: Long = vlcPlayer.currentPositionMs + + override val playingIndexInQueue: Int = vlcPlayer.playingIndexInQueue + + override val playListQueue: List = emptyList() + + override val playingMediaStateFlow: Flow = vlcPlayer.observePlayingMediaMrl() + .map { mrl -> + if (mrl == null) { + return@map null + } + libraryDao.getMediaByMediaIds(listOf(mrl.hashCode().toLong().toString())).firstOrNull()?.toAppItem() + } + + override val playListQueueStateFlow: Flow> = flowOf(emptyList()) + + override fun observeIsShuffle(): StateFlow { + return MutableStateFlow(false) + } + + override val playMode: PlayMode + get() = PlayMode.REPEAT_ALL + + override fun observePlayMode() = flowOf(PlayMode.REPEAT_ALL) + + override fun observeIsPlaying(): Flow = vlcPlayer.observePlayerState() + .map { + it is PlayerState.Playing + } + .distinctUntilChanged() + + override fun observeProgressFactor(): Flow = vlcPlayer.observeProgressFactor() +} \ No newline at end of file diff --git a/core/database/src/commonMain/kotlin/com/andannn/melodify/core/database/dao/MediaLibraryDao.kt b/core/database/src/commonMain/kotlin/com/andannn/melodify/core/database/dao/MediaLibraryDao.kt index c7bdf8da..f2bee384 100644 --- a/core/database/src/commonMain/kotlin/com/andannn/melodify/core/database/dao/MediaLibraryDao.kt +++ b/core/database/src/commonMain/kotlin/com/andannn/melodify/core/database/dao/MediaLibraryDao.kt @@ -94,6 +94,9 @@ interface MediaLibraryDao { @Query("SELECT * FROM ${Tables.LIBRARY_MEDIA} WHERE ${MediaColumns.ID} IN (:mediaIds)") suspend fun getMediaByMediaIds(mediaIds: List): List + @Query("SELECT * FROM ${Tables.LIBRARY_MEDIA} WHERE ${MediaColumns.ID} IN (:mediaIds)") + fun getMediaByMediaIdsFlow(mediaIds: List): Flow> + @Transaction suspend fun clearAndInsertLibrary( albums: List, diff --git a/core/player/build.gradle.kts b/core/player/build.gradle.kts index 604a8fc8..db055ea8 100644 --- a/core/player/build.gradle.kts +++ b/core/player/build.gradle.kts @@ -20,5 +20,9 @@ kotlin { implementation(libs.androidx.media3.exoplayer) implementation(libs.androidx.media3.session) } + + desktopMain.dependencies { + implementation(libs.vlcj) + } } } diff --git a/core/player/src/androidMain/kotlin/com/andannn/melodify/core/player/di/PlayerModule.android.kt b/core/player/src/androidMain/kotlin/com/andannn/melodify/core/player/di/PlayerModule.android.kt index 4b56e668..ad535ee3 100644 --- a/core/player/src/androidMain/kotlin/com/andannn/melodify/core/player/di/PlayerModule.android.kt +++ b/core/player/src/androidMain/kotlin/com/andannn/melodify/core/player/di/PlayerModule.android.kt @@ -9,7 +9,7 @@ import org.koin.core.module.dsl.singleOf import org.koin.dsl.bind import org.koin.dsl.module -internal actual val platformPlayerModule: Module = module { +internal actual val platformVlcPlayerModule: Module = module { singleOf(::PlayerWrapperImpl).bind(PlayerWrapper::class) singleOf(::MediaBrowserManagerImpl).bind(MediaBrowserManager::class) } diff --git a/core/player/src/commonMain/kotlin/com/andannn/melodify/core/player/di/PlayerModule.kt b/core/player/src/commonMain/kotlin/com/andannn/melodify/core/player/di/PlayerModule.kt index c975f0da..643f875a 100644 --- a/core/player/src/commonMain/kotlin/com/andannn/melodify/core/player/di/PlayerModule.kt +++ b/core/player/src/commonMain/kotlin/com/andannn/melodify/core/player/di/PlayerModule.kt @@ -8,8 +8,8 @@ import org.koin.dsl.bind import org.koin.dsl.module val playerModule : Module = module { - includes(platformPlayerModule) + includes(platformVlcPlayerModule) singleOf(::SleepTimerControllerImpl).bind(SleepTimerController::class) } -internal expect val platformPlayerModule : Module +internal expect val platformVlcPlayerModule : Module diff --git a/core/player/src/desktopMain/kotlin/com/andannn/melodify/core/player/PlayerImpl.kt b/core/player/src/desktopMain/kotlin/com/andannn/melodify/core/player/PlayerImpl.kt new file mode 100644 index 00000000..a19ef495 --- /dev/null +++ b/core/player/src/desktopMain/kotlin/com/andannn/melodify/core/player/PlayerImpl.kt @@ -0,0 +1,234 @@ +package com.andannn.melodify.core.player + +import io.github.aakira.napier.Napier +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Job +import kotlinx.coroutines.asCoroutineDispatcher +import kotlinx.coroutines.cancel +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.getAndUpdate +import kotlinx.coroutines.launch +import uk.co.caprica.vlcj.media.MediaRef +import uk.co.caprica.vlcj.medialist.MediaList +import uk.co.caprica.vlcj.medialist.MediaListEventAdapter +import uk.co.caprica.vlcj.player.base.MediaPlayer +import uk.co.caprica.vlcj.player.base.MediaPlayerEventAdapter +import uk.co.caprica.vlcj.player.component.AudioListPlayerComponent +import java.util.concurrent.Executors + +private const val TAG = "PlayerImpl" + +internal class PlayerImpl : VlcPlayer, CoroutineScope { + + private val nativeApiDispatcher = Executors.newFixedThreadPool(1).asCoroutineDispatcher() + override val coroutineContext = nativeApiDispatcher + Job() + + private val mediaPlayerComponent = AudioListPlayerComponent() + + private val mediaPlayerApi: uk.co.caprica.vlcj.player.base.MediaApi + get() = mediaPlayerComponent.mediaPlayer().media()!! + + private val mediaListApi: uk.co.caprica.vlcj.medialist.MediaApi + get() = mediaPlayerComponent.mediaListPlayer().list().media()!! + + private val listControlsApi: uk.co.caprica.vlcj.player.list.ControlsApi + get() = mediaPlayerComponent.mediaListPlayer().controls()!! + + private val playControlsApi: uk.co.caprica.vlcj.player.base.ControlsApi + get() = mediaPlayerComponent.mediaPlayer().controls()!! + + private val playEventApi: uk.co.caprica.vlcj.player.base.EventApi + get() = mediaPlayerComponent.mediaPlayer().events()!! + + private val listEventApi: uk.co.caprica.vlcj.medialist.EventApi + get() = mediaPlayerComponent.mediaListPlayer().list().events() + + private val _playerStateFlow = MutableStateFlow(PlayerState.Idle) + private val _currentPositionFlow = MutableStateFlow(0F) + private val _playingDurationFlow = MutableStateFlow(0L) + private val _playingMrlFlow = MutableStateFlow(null) + private val _playListFlow = MutableStateFlow>(emptyList()) + + private val currentPosition: Long = + (_currentPositionFlow.value * _playingDurationFlow.value).toLong() + + private val playerEventLister = object : MediaPlayerEventAdapter() { + override fun mediaChanged(mediaPlayer: MediaPlayer, media: MediaRef) { + launch { + val newMedia = media.newMedia().info() + _playingMrlFlow.value = newMedia.mrl() + Napier.d(tag = TAG) { "media changed duration: ${newMedia.duration()}. thread ${Thread.currentThread()}" } + } + } + + override fun lengthChanged(mediaPlayer: MediaPlayer?, newLength: Long) { + launch { + Napier.d(tag = TAG) { "lengthChanged duration: ${newLength}. thread ${Thread.currentThread()}" } + _playingDurationFlow.value = newLength + } + } + + override fun paused(mediaPlayer: MediaPlayer?) { + Napier.d(tag = TAG) { "paused. thread ${Thread.currentThread()}" } + + _playerStateFlow.value = PlayerState.Paused(currentPosition) + } + + override fun playing(mediaPlayer: MediaPlayer?) { + Napier.d(tag = TAG) { "playing. thread ${Thread.currentThread()}" } + + _playerStateFlow.value = PlayerState.Playing(currentPosition) + } + + override fun buffering(mediaPlayer: MediaPlayer?, newCache: Float) { +// _playerStateFlow.value = PlayerState.Buffering(currentPosition) + } + + override fun finished(mediaPlayer: MediaPlayer?) { + Napier.d(tag = TAG) { "finished. thread ${Thread.currentThread()}" } + } + + override fun positionChanged(mediaPlayer: MediaPlayer?, newPosition: Float) { + _currentPositionFlow.value = newPosition + + _playerStateFlow.getAndUpdate { old -> + when (old) { + is PlayerState.Error, + is PlayerState.PlayBackEnd, + PlayerState.Idle -> old + + is PlayerState.Buffering -> PlayerState.Paused(currentPosition) + is PlayerState.Paused -> PlayerState.Paused(currentPosition) + is PlayerState.Playing -> PlayerState.Playing(currentPosition) + } + } + } + } + + private val mediaListEvent = object : MediaListEventAdapter() { + override fun mediaListItemAdded(mediaList: MediaList, item: MediaRef, index: Int) { + Napier.d(tag = TAG) { "mediaListItemAdded index $index. thread ${Thread.currentThread()}" } + + launch { + _playListFlow.value = mediaList.media().mrls() + } + } + + override fun mediaListItemDeleted(mediaList: MediaList, item: MediaRef, index: Int) { + Napier.d(tag = TAG) { "mediaListItemDeleted index $index. thread ${Thread.currentThread()}" } + launch { + _playListFlow.value = mediaList.media().mrls() + } + } + } + + + init { + playEventApi.addMediaPlayerEventListener(playerEventLister) + listEventApi.addMediaListEventListener(mediaListEvent) + } + + override fun playMediaList(mediaList: List, index: Int) { + launch { + mediaListApi.clear() + + mediaList.forEach { + mediaListApi.add(it) + } + + mediaPlayerApi.play(mediaList[index]) + } + } + + override fun observePlayerState() = _playerStateFlow + + override val currentPositionMs: Long get() = currentPosition + + override val currentDurationMs: Long get() = _playingDurationFlow.value + + override val playingIndexInQueue: Int + get() = _playListFlow.value.indexOf(_playingMrlFlow.value) + + override val playList: List + get() = _playListFlow.value + + override fun seekToNext() { + launch { + listControlsApi.playNext() + } + } + + override fun seekToPrevious() { + launch { + listControlsApi.playPrevious() + } + } + + override fun seekMediaItem(mediaItemIndex: Int, positionMs: Long) { + launch { + listControlsApi.play(mediaItemIndex) + playControlsApi.setPosition(positionMs.toFloat().div(currentDurationMs)) + } + } + + override fun seekToTime(time: Long) { + launch { + Napier.d(tag = TAG) { "seekToTime $time. thread ${Thread.currentThread()}" } + playControlsApi.setPosition(time.toFloat().div(currentDurationMs)) + } + } + + override fun setShuffleModeEnabled(enable: Boolean) { + } + + override fun play() { + launch { + Napier.d(tag = TAG) { "play. thread ${Thread.currentThread()}" } + playControlsApi.play() + } + } + + override fun pause() { + launch { + Napier.d(tag = TAG) { "pause. thread ${Thread.currentThread()}" } + playControlsApi.pause() + } + } + + override fun addMediaItems(index: Int, mrls: List) { + launch { + mrls.forEachIndexed { i, mrl -> + mediaListApi.insert(index + i, mrl) + } + } + } + + override fun moveMediaItem(from: Int, to: Int) { + launch { + val fromMrl = mediaListApi.mrl(from) + mediaListApi.remove(from) + mediaListApi.insert(to, fromMrl) + } + } + + override fun removeMediaItem(index: Int) { + launch { + mediaListApi.remove(index) + } + } + + override fun observePlayListQueue() = _playListFlow + + override fun observePlayingMediaMrl() = _playingMrlFlow + + override fun observeProgressFactor() = _currentPositionFlow + + override fun release() { + nativeApiDispatcher.close() + nativeApiDispatcher.cancel() + + playEventApi.removeMediaPlayerEventListener(playerEventLister) + listEventApi.removeMediaListEventListener(mediaListEvent) + mediaPlayerComponent.release() + } +} \ No newline at end of file diff --git a/core/player/src/desktopMain/kotlin/com/andannn/melodify/core/player/VlcPlayer.kt b/core/player/src/desktopMain/kotlin/com/andannn/melodify/core/player/VlcPlayer.kt new file mode 100644 index 00000000..4b6239db --- /dev/null +++ b/core/player/src/desktopMain/kotlin/com/andannn/melodify/core/player/VlcPlayer.kt @@ -0,0 +1,47 @@ +package com.andannn.melodify.core.player + +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.StateFlow + +interface VlcPlayer { + val currentPositionMs: Long + + val currentDurationMs: Long + + val playingIndexInQueue: Int + + fun playMediaList(mediaList: List, index: Int) + + fun observePlayerState(): StateFlow + + fun observePlayListQueue(): Flow> + + fun observePlayingMediaMrl(): Flow + + fun observeProgressFactor(): Flow + + val playList: List + + fun seekToNext() + + fun seekToPrevious() + + fun seekMediaItem(mediaItemIndex: Int, positionMs: Long = 0) + + fun seekToTime(time: Long) + + fun setShuffleModeEnabled(enable: Boolean) + + fun play() + + fun pause() + + fun addMediaItems(index: Int, mrls: List) + + fun moveMediaItem(from: Int, to: Int) + + fun removeMediaItem(index: Int) + + fun release() +} + diff --git a/core/player/src/desktopMain/kotlin/com/andannn/melodify/core/player/di/PlayerModule.desktop.kt b/core/player/src/desktopMain/kotlin/com/andannn/melodify/core/player/di/PlayerModule.desktop.kt index 7c8120a0..ef7b68cf 100644 --- a/core/player/src/desktopMain/kotlin/com/andannn/melodify/core/player/di/PlayerModule.desktop.kt +++ b/core/player/src/desktopMain/kotlin/com/andannn/melodify/core/player/di/PlayerModule.desktop.kt @@ -1,8 +1,12 @@ package com.andannn.melodify.core.player.di +import com.andannn.melodify.core.player.VlcPlayer +import com.andannn.melodify.core.player.PlayerImpl import org.koin.core.module.Module +import org.koin.core.module.dsl.singleOf +import org.koin.dsl.bind import org.koin.dsl.module -internal actual val platformPlayerModule: Module = module { - +internal actual val platformVlcPlayerModule: Module = module { + singleOf(::PlayerImpl).bind(VlcPlayer::class) } diff --git a/core/syncer/src/desktopMain/kotlin/com/andannn/melodify/core/syncer/MediaLibraryScanner.desktop.kt b/core/syncer/src/desktopMain/kotlin/com/andannn/melodify/core/syncer/MediaLibraryScanner.desktop.kt index 593161b1..52a321c3 100644 --- a/core/syncer/src/desktopMain/kotlin/com/andannn/melodify/core/syncer/MediaLibraryScanner.desktop.kt +++ b/core/syncer/src/desktopMain/kotlin/com/andannn/melodify/core/syncer/MediaLibraryScanner.desktop.kt @@ -42,7 +42,6 @@ class MediaLibraryScannerImpl( // val libraryPathSet = userSettingPreferences.userDate.first().libraryPath val libraryPathSet = setOf( "/Volumes/PS2000/Music", - "/Users/jiangqn/Documents" ) // TODO: Scan file in worker thread. diff --git a/core/syncer/src/desktopMain/kotlin/com/andannn/melodify/core/syncer/util/extractTagFromAudioFile.kt b/core/syncer/src/desktopMain/kotlin/com/andannn/melodify/core/syncer/util/extractTagFromAudioFile.kt index fcc0df6f..14fd1dec 100644 --- a/core/syncer/src/desktopMain/kotlin/com/andannn/melodify/core/syncer/util/extractTagFromAudioFile.kt +++ b/core/syncer/src/desktopMain/kotlin/com/andannn/melodify/core/syncer/util/extractTagFromAudioFile.kt @@ -18,6 +18,7 @@ fun extractTagFromAudioFile(filePath: String): AudioData? { val audioFile = AudioFileIO.read(file) val tag = audioFile.tag +// TODO: cache error and set default for method getFirst AudioData( id = -1, // assign id later. albumId = -1, // assign id later. diff --git a/core/syncer/src/desktopMain/kotlin/com/andannn/melodify/core/syncer/util/generateHashKey.kt b/core/syncer/src/desktopMain/kotlin/com/andannn/melodify/core/syncer/util/generateHashKey.kt index 8f87bd96..eb0def0f 100644 --- a/core/syncer/src/desktopMain/kotlin/com/andannn/melodify/core/syncer/util/generateHashKey.kt +++ b/core/syncer/src/desktopMain/kotlin/com/andannn/melodify/core/syncer/util/generateHashKey.kt @@ -1,5 +1,16 @@ package com.andannn.melodify.core.syncer.util -fun generateHashKey(absolutePath: String, lastModifiedDate: Long): Long { - return (absolutePath + lastModifiedDate.toString()).hashCode().toLong() +import java.net.URLEncoder +import java.nio.file.Paths + +fun generateHashKey(absolutePath: String): Long { + return (toFileUrl(absolutePath)).hashCode().toLong() +} + +fun toFileUrl(path: String): String { + val file = Paths.get(path) + val encodedPath = file.joinToString("/") { + URLEncoder.encode(it.toString(), "UTF-8").replace("+", "%20") + } + return "file:///$encodedPath" } \ No newline at end of file diff --git a/core/syncer/src/desktopMain/kotlin/com/andannn/melodify/core/syncer/util/scanAllLibraryAudioFile.kt b/core/syncer/src/desktopMain/kotlin/com/andannn/melodify/core/syncer/util/scanAllLibraryAudioFile.kt index dcbe2029..6221bc25 100644 --- a/core/syncer/src/desktopMain/kotlin/com/andannn/melodify/core/syncer/util/scanAllLibraryAudioFile.kt +++ b/core/syncer/src/desktopMain/kotlin/com/andannn/melodify/core/syncer/util/scanAllLibraryAudioFile.kt @@ -33,5 +33,5 @@ data class PathWithLastModifyDate( val path: String, val lastModified: Long, ) { - val key = generateHashKey(path, lastModified) + val key = generateHashKey(path) } diff --git a/core/syncer/src/desktopTest/kotlin/com/andannn/melodify/core/syncer/util/UtilTest.kt b/core/syncer/src/desktopTest/kotlin/com/andannn/melodify/core/syncer/util/UtilTest.kt index 9d7068f8..8b59755b 100644 --- a/core/syncer/src/desktopTest/kotlin/com/andannn/melodify/core/syncer/util/UtilTest.kt +++ b/core/syncer/src/desktopTest/kotlin/com/andannn/melodify/core/syncer/util/UtilTest.kt @@ -1,6 +1,7 @@ package com.andannn.melodify.core.syncer.util import kotlin.test.Test +import kotlin.test.assertEquals import kotlin.test.assertFalse import kotlin.test.assertNotNull import kotlin.test.assertTrue @@ -26,11 +27,21 @@ class UtilTest { @Test fun scan_all_library_audio_file() { measureTime { - val audioData = scanAllLibraryAudioFile(setOf( - "src/desktopTest/" - )) + val audioData = scanAllLibraryAudioFile( + setOf( + "src/desktopTest/" + ) + ) println(audioData) assertNotNull(audioData) }.also { println("consumed time: $it") } } + + @Test + fun test_to_file_uri() { + assertEquals( + "file:///Volumes/PS2000/Music/2019.12.31%20%5BRDWL-0030%5D%20%E5%BD%81%20%5BC97%5D/%E5%87%8B%E5%8F%B6%E6%A3%95%20-%20%E5%BD%81.flac", + toFileUrl("/Volumes/PS2000/Music/2019.12.31 [RDWL-0030] 彁 [C97]/凋叶棕 - 彁.flac") + ) + } } \ No newline at end of file diff --git a/feature/home/src/commonMain/kotlin/com/andannn/melodify/feature/home/HomeScreen.kt b/feature/home/src/commonMain/kotlin/com/andannn/melodify/feature/home/HomeScreen.kt index 991997e0..4362c6f0 100644 --- a/feature/home/src/commonMain/kotlin/com/andannn/melodify/feature/home/HomeScreen.kt +++ b/feature/home/src/commonMain/kotlin/com/andannn/melodify/feature/home/HomeScreen.kt @@ -12,12 +12,6 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyListState -import androidx.compose.foundation.lazy.grid.GridCells -import androidx.compose.foundation.lazy.grid.GridItemSpan -import androidx.compose.foundation.lazy.grid.LazyGridState -import androidx.compose.foundation.lazy.grid.LazyVerticalGrid -import androidx.compose.foundation.lazy.grid.items -import androidx.compose.foundation.lazy.grid.rememberLazyGridState import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.material.icons.Icons @@ -43,23 +37,17 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.rememberUpdatedState import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.Modifier -import androidx.compose.ui.hapticfeedback.HapticFeedbackType import androidx.compose.ui.input.nestedscroll.nestedScroll -import androidx.compose.ui.platform.LocalHapticFeedback import androidx.compose.ui.unit.dp import com.andannn.melodify.core.data.model.AlbumItemModel import com.andannn.melodify.core.data.model.ArtistItemModel import com.andannn.melodify.core.data.model.AudioItemModel import com.andannn.melodify.core.data.model.GenreItemModel import com.andannn.melodify.core.data.model.MediaItemModel -import com.andannn.melodify.core.data.model.MediaPreviewMode import com.andannn.melodify.core.data.model.PlayListItemModel import com.andannn.melodify.core.data.model.browsableOrPlayable import com.andannn.melodify.core.data.model.key -import com.andannn.melodify.core.platform.Desktop -import com.andannn.melodify.core.platform.PlatformInfo import com.andannn.melodify.feature.common.component.ExtraPaddingBottom -import com.andannn.melodify.feature.common.component.LargePreviewCard import com.andannn.melodify.feature.common.component.ListTileItemView import com.andannn.melodify.feature.common.theme.MelodifyTheme import com.andannn.melodify.feature.common.util.getCategoryResource @@ -72,7 +60,6 @@ import melodify.feature.common.generated.resources.Res import melodify.feature.common.generated.resources.track_count import org.jetbrains.compose.resources.stringResource import org.jetbrains.compose.ui.tooling.preview.Preview -import org.koin.compose.getKoin import org.koin.compose.viewmodel.koinViewModel import org.koin.core.parameter.parametersOf import org.koin.core.scope.Scope @@ -198,110 +185,33 @@ fun TabWithContent( } val mediaItems by rememberUpdatedState(uiState.mediaItems) - val previewMode by rememberUpdatedState(uiState.previewMode) + val playingItem by rememberUpdatedState(uiState.playingItem) - when (previewMode) { - MediaPreviewMode.GRID_PREVIEW -> { - val gridLayoutState = - rememberSaveable(selectedIndex, saver = LazyGridState.Saver) { - LazyGridState() - } - LazyGridContent( - state = gridLayoutState, - modifier = - Modifier.fillMaxSize(), - layoutToggleButton = { - LayoutToggleButton( - previewMode = previewMode, - onClick = { - onEvent(HomeUiEvent.OnTogglePreviewMode) - } - ) - }, - mediaItems = mediaItems, - onClick = onMediaItemClick, - onLongPress = { - onEvent(HomeUiEvent.OnShowItemOption(it)) - } - ) + val listLayoutState = + rememberSaveable(selectedIndex, saver = LazyListState.Saver) { + LazyListState() } - - MediaPreviewMode.LIST_PREVIEW -> { - val listLayoutState = - rememberSaveable(selectedIndex, saver = LazyListState.Saver) { - LazyListState() - } - LazyListContent( - modifier = - Modifier.fillMaxSize(), - state = listLayoutState, - layoutToggleButton = { - LayoutToggleButton( - previewMode = previewMode, - onClick = { - onEvent(HomeUiEvent.OnTogglePreviewMode) - } - ) - }, - mediaItems = mediaItems, - onMusicItemClick = onMediaItemClick, - onShowMusicItemOption = { - onEvent(HomeUiEvent.OnShowItemOption(it)) - } - ) + LazyListContent( + modifier = + Modifier.fillMaxSize(), + state = listLayoutState, + mediaItems = mediaItems, + playingItem = playingItem, + onMusicItemClick = onMediaItemClick, + onShowMusicItemOption = { + onEvent(HomeUiEvent.OnShowItemOption(it)) } - } - } -} - -@Composable -private fun LazyGridContent( - mediaItems: ImmutableList, - modifier: Modifier = Modifier, - state: LazyGridState = rememberLazyGridState(), - layoutToggleButton: @Composable () -> Unit = {}, - onClick: (T) -> Unit = {}, - onLongPress: (T) -> Unit = {} -) { - val hapticFeedBack = LocalHapticFeedback.current - LazyVerticalGrid( - state = state, - modifier = modifier.fillMaxSize(), - columns = GridCells.Adaptive(180.dp), - ) { - item(span = { GridItemSpan(2) }) { layoutToggleButton() } - - items( - items = mediaItems, - key = { it.key }, - ) { item -> - LargePreviewCard( - modifier = Modifier - .padding(horizontal = 4.dp, vertical = 3.dp) - .animateItem(), - artCoverUri = item.artWorkUri, - title = item.name, - subTitle = subTitle(item), - onClick = { - onClick.invoke(item) - }, - onLongClick = { - hapticFeedBack.performHapticFeedback(HapticFeedbackType.LongPress) - onLongPress.invoke(item) - }, - ) - } - - item { ExtraPaddingBottom() } + ) } } @Composable private fun LazyListContent( + mediaItems: ImmutableList, modifier: Modifier = Modifier, + playingItem: AudioItemModel? = null, state: LazyListState = rememberLazyListState(), - layoutToggleButton: @Composable () -> Unit = {}, onMusicItemClick: (T) -> Unit = {}, onShowMusicItemOption: (T) -> Unit = {}, ) { @@ -310,22 +220,15 @@ private fun LazyListContent( modifier = modifier, contentPadding = PaddingValues(horizontal = 5.dp), ) { - item { - if (getKoin().get().platform !is Desktop) { - layoutToggleButton() - } - } items( items = mediaItems, key = { it.key }, ) { item -> ListTileItemView( modifier = - Modifier - .padding(vertical = 4.dp) - .animateItem(), + Modifier.animateItem(), playable = item.browsableOrPlayable, - isActive = false, + isActive = item.id == playingItem?.id, albumArtUri = item.artWorkUri, title = item.name, subTitle = subTitle(item), @@ -342,43 +245,6 @@ private fun LazyListContent( } } -@Composable -fun LayoutToggleButton( - previewMode: MediaPreviewMode, - modifier: Modifier = Modifier, - onClick: () -> Unit -) { - Row(modifier = modifier.fillMaxWidth()) { - Spacer(modifier = Modifier.weight(1f)) - - IconButton( - modifier = Modifier - .padding(end = 10.dp), - onClick = { - onClick() - } - ) { - when (previewMode) { - MediaPreviewMode.GRID_PREVIEW -> { - Icon( - Icons.AutoMirrored.Rounded.List, - contentDescription = "" - ) - } - - MediaPreviewMode.LIST_PREVIEW -> { - Icon( - Icons.Rounded.Apps, - contentDescription = "" - ) - } - } - } - } - -} - - @Composable private fun subTitle( model: MediaItemModel @@ -405,7 +271,6 @@ private fun HomeScreenPreview() { trackCount = 10 ) }.toImmutableList(), - previewMode = MediaPreviewMode.GRID_PREVIEW, ), ) } diff --git a/feature/home/src/commonMain/kotlin/com/andannn/melodify/feature/home/HomeViewModel.kt b/feature/home/src/commonMain/kotlin/com/andannn/melodify/feature/home/HomeViewModel.kt index 8bed5abe..0d1b93f2 100644 --- a/feature/home/src/commonMain/kotlin/com/andannn/melodify/feature/home/HomeViewModel.kt +++ b/feature/home/src/commonMain/kotlin/com/andannn/melodify/feature/home/HomeViewModel.kt @@ -4,7 +4,6 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.andannn.melodify.core.data.model.MediaItemModel import com.andannn.melodify.core.data.model.AudioItemModel -import com.andannn.melodify.core.data.model.MediaPreviewMode import com.andannn.melodify.core.data.Repository import com.andannn.melodify.core.data.model.CustomTab import com.andannn.melodify.feature.drawer.DrawerController @@ -35,7 +34,6 @@ sealed interface HomeUiEvent { data class OnSelectedCategoryChanged(val tabIndex: Int) : HomeUiEvent data class OnMusicItemClick(val mediaItem: AudioItemModel) : HomeUiEvent data class OnShowItemOption(val audioItemModel: MediaItemModel) : HomeUiEvent - data object OnTogglePreviewMode : HomeUiEvent } @OptIn(ExperimentalCoroutinesApi::class) @@ -46,6 +44,7 @@ class HomeViewModel( ) : ViewModel() { private val mediaControllerRepository = repository.mediaControllerRepository private val userPreferenceRepository = repository.userPreferenceRepository + private val playerStateMonitoryRepository = repository.playerStateMonitoryRepository private val playListRepository = repository.playListRepository private val playlistCreatedEventChannel = drawerController.playlistCreatedEventChannel @@ -111,13 +110,13 @@ class HomeViewModel( val state = combine( _tabStatusFlow, _mediaContentFlow, - _userSettingFlow, - ) { tabStatus, mediaContents, userSetting -> + playerStateMonitoryRepository.playingMediaStateFlow, + ) { tabStatus, mediaContents, playingItem -> HomeUiState( selectedIndex = tabStatus.selectedIndex, customTabList = tabStatus.customTabList, mediaItems = mediaContents.toImmutableList(), - previewMode = userSetting.mediaPreviewMode, + playingItem = playingItem ) } .stateIn(viewModelScope, SharingStarted.WhileSubscribed(), HomeUiState()) @@ -144,13 +143,6 @@ class HomeViewModel( is HomeUiEvent.OnSelectedCategoryChanged -> onSelectedCategoryChanged(event.tabIndex) is HomeUiEvent.OnMusicItemClick -> playMusic(event.mediaItem) is HomeUiEvent.OnShowItemOption -> onShowMusicItemOption(event.audioItemModel) - is HomeUiEvent.OnTogglePreviewMode -> onTogglePreviewMode() - } - } - - private fun onTogglePreviewMode() { - viewModelScope.launch { - userPreferenceRepository.setPreviewMode(state.value.previewMode.next()) } } @@ -218,7 +210,7 @@ data class HomeUiState( val selectedIndex: Int = 0, val customTabList: List = emptyList(), val mediaItems: ImmutableList = emptyList().toImmutableList(), - val previewMode: MediaPreviewMode = MediaPreviewMode.GRID_PREVIEW, + val playingItem: AudioItemModel? = null ) { val currentTab: CustomTab get() = customTabList[selectedIndex] diff --git a/feature/player/src/commonMain/kotlin/com/andannn/melodify/feature/player/PlayerAreaViewModel.kt b/feature/player/src/commonMain/kotlin/com/andannn/melodify/feature/player/PlayerAreaViewModel.kt index a2c4b9ef..c8b724d4 100644 --- a/feature/player/src/commonMain/kotlin/com/andannn/melodify/feature/player/PlayerAreaViewModel.kt +++ b/feature/player/src/commonMain/kotlin/com/andannn/melodify/feature/player/PlayerAreaViewModel.kt @@ -117,7 +117,7 @@ class PlayerStateViewModel( PlayerUiState.Active( lyric = lyric, mediaItem = interactingMusicItem, - duration = mediaControllerRepository.duration ?: 0L, + duration = mediaControllerRepository.currentDuration ?: 0L, playMode = state.playMode, isShuffle = state.isShuffle, isFavorite = isFavorite, diff --git a/feature/player/src/desktopMain/kotlin/com/andannn/melodify/feature/player/PlayerSector.kt b/feature/player/src/desktopMain/kotlin/com/andannn/melodify/feature/player/PlayerSector.kt index a6149ea7..5e31074a 100644 --- a/feature/player/src/desktopMain/kotlin/com/andannn/melodify/feature/player/PlayerSector.kt +++ b/feature/player/src/desktopMain/kotlin/com/andannn/melodify/feature/player/PlayerSector.kt @@ -2,15 +2,14 @@ package com.andannn.melodify.feature.player import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.IntrinsicSize import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.aspectRatio import androidx.compose.foundation.layout.fillMaxHeight -import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width +import androidx.compose.material.Slider import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.Pause import androidx.compose.material.icons.rounded.PlayArrow @@ -58,7 +57,11 @@ fun PlayerSector( onEvent = playerStateViewModel::onEvent, ) - PlayerUiState.Inactive -> Spacer(modifier = modifier) + PlayerUiState.Inactive -> PlayStateBar( + modifier = modifier, + coverUri = "", + activeMediaItem = AudioItemModel.DEFAULT, + ) } } @@ -80,13 +83,11 @@ fun PlayStateBar( onEvent: (PlayerUiEvent) -> Unit = {}, ) { Surface( - modifier = modifier , + modifier = modifier, ) { - Column( - Modifier.fillMaxWidth() - ) { + Column { Row( - modifier = Modifier.height(IntrinsicSize.Min), + modifier = Modifier.height(48.dp), verticalAlignment = Alignment.CenterVertically, ) { PlayInfoWithAlbumCover( @@ -108,18 +109,14 @@ fun PlayStateBar( Spacer(modifier = Modifier.weight(1f)) } -// Slider( -// modifier = Modifier -// .padding(horizontal = 36.dp) -// .height(24.dp) -// .requiredHeight(48.dp) -// .offset(y = 12.dp), -// value = progress, -// enabled = true, -// onValueChange = { -// onEvent(PlayerUiEvent.OnProgressChange(it)) -// }, -// ) + Slider( + modifier = Modifier.height(24.dp), + value = progress, + enabled = true, + onValueChange = { + onEvent(PlayerUiEvent.OnProgressChange(it)) + }, + ) } } } @@ -210,7 +207,8 @@ private fun PlayInfoWithAlbumCover( ) { Row( - modifier = modifier + modifier = modifier, + verticalAlignment = Alignment.CenterVertically ) { CircleBorderImage( modifier = Modifier diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 5347c27b..4d412ddf 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -38,6 +38,7 @@ appcompat = "1.7.0" material = "1.12.0" dependencyGraphGenerator = "0.8.0" uiDesktop = "1.7.0" +vlcj = "4.8.2" [libraries] androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "androidxCore" } @@ -104,6 +105,7 @@ androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-co androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" } material = { group = "com.google.android.material", name = "material", version.ref = "material" } androidx-ui-desktop = { group = "androidx.compose.ui", name = "ui-desktop", version.ref = "uiDesktop" } +vlcj = { module = "uk.co.caprica:vlcj", version.ref = "vlcj" } [plugins] android-application = { id = "com.android.application", version.ref = "androidGradlePlugin" }