From 5051d54e02988f7e1d8cdd2b691a6faf6f6ecf02 Mon Sep 17 00:00:00 2001 From: Hameed Chishti Date: Thu, 9 May 2024 18:12:44 +0300 Subject: [PATCH 1/6] Update dependencies - updated libs and AGP --- app/build.gradle.kts | 40 ++++++++++++------------ build.gradle.kts | 8 ++--- gradle/wrapper/gradle-wrapper.properties | 2 +- player-service/build.gradle.kts | 14 ++++----- 4 files changed, 32 insertions(+), 32 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 8fc8419..d3e55f0 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -5,8 +5,6 @@ plugins { id("dagger.hilt.android.plugin") } -var composeVersion: String = "1.5.4" - android { namespace = "com.rcudev.simplemediaplayer" compileSdk = 34 @@ -41,7 +39,7 @@ android { compose = true } composeOptions { - kotlinCompilerExtensionVersion = "1.4.4" + kotlinCompilerExtensionVersion = "1.5.13" } packagingOptions { resources.excludes.add("META-INF/{AL2.0,LGPL2.1}") @@ -51,33 +49,35 @@ android { dependencies { implementation(project(":player-service")) - implementation("androidx.core:core-ktx:1.12.0") - implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.6.2") + implementation("androidx.core:core-ktx:1.13.1") + implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.7.0") // Compose - implementation("androidx.activity:activity-compose:1.8.1") - implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.6.2") - implementation("androidx.lifecycle:lifecycle-runtime-compose:2.6.2") - implementation("androidx.compose.ui:ui:1.5.4") - implementation("androidx.compose.ui:ui-tooling-preview:$composeVersion") - implementation("androidx.compose.material3:material3:1.1.2") - implementation("androidx.navigation:navigation-compose:2.7.5") - implementation("androidx.hilt:hilt-navigation-compose:1.1.0") + implementation(platform("androidx.compose:compose-bom:2024.05.00")) + implementation("androidx.compose.ui:ui") + implementation("androidx.compose.ui:ui-tooling-preview") + implementation("androidx.compose.material3:material3") + + implementation("androidx.activity:activity-compose:1.9.0") + implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.7.0") + implementation("androidx.lifecycle:lifecycle-runtime-compose:2.7.0") + implementation("androidx.navigation:navigation-compose:2.7.7") + implementation("androidx.hilt:hilt-navigation-compose:1.2.0") // Hilt - implementation("com.google.dagger:hilt-android:2.46.1") - kapt("com.google.dagger:hilt-compiler:2.46.1") + implementation("com.google.dagger:hilt-android:2.51.1") + kapt("com.google.dagger:hilt-compiler:2.51.1") // Media3 - implementation("androidx.media3:media3-session:1.2.0") + implementation("androidx.media3:media3-session:1.3.1") // Coil - implementation("io.coil-kt:coil-compose:2.4.0") + implementation("io.coil-kt:coil-compose:2.6.0") testImplementation("junit:junit:4.13.2") androidTestImplementation("androidx.test.ext:junit:1.1.5") androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1") - androidTestImplementation("androidx.compose.ui:ui-test-junit4:$composeVersion") - debugImplementation("androidx.compose.ui:ui-tooling:$composeVersion") - debugImplementation("androidx.compose.ui:ui-test-manifest:$composeVersion") + androidTestImplementation("androidx.compose.ui:ui-test-junit4") + debugImplementation("androidx.compose.ui:ui-tooling") + debugImplementation("androidx.compose.ui:ui-test-manifest") } \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts index b8bcbfc..7567d1e 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,11 +1,11 @@ buildscript { dependencies { - classpath("com.google.dagger:hilt-android-gradle-plugin:2.46.1") + classpath("com.google.dagger:hilt-android-gradle-plugin:2.51.1") } } plugins { - id("com.android.application") version "8.1.1" apply false - id("com.android.library") version "8.1.1" apply false - id("org.jetbrains.kotlin.android") version "1.8.10" apply false + id("com.android.application") version "8.4.0" apply false + id("com.android.library") version "8.4.0" apply false + id("org.jetbrains.kotlin.android") version "1.9.23" apply false } \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index d26eeb8..03b65bb 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ #Fri Mar 24 10:06:16 CET 2023 distributionBase=GRADLE_USER_HOME -distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip distributionPath=wrapper/dists zipStorePath=wrapper/dists zipStoreBase=GRADLE_USER_HOME diff --git a/player-service/build.gradle.kts b/player-service/build.gradle.kts index c77f6f1..0054903 100644 --- a/player-service/build.gradle.kts +++ b/player-service/build.gradle.kts @@ -33,19 +33,19 @@ android { dependencies { - implementation("androidx.core:core-ktx:1.12.0") + implementation("androidx.core:core-ktx:1.13.1") implementation("androidx.appcompat:appcompat:1.6.1") implementation("androidx.legacy:legacy-support-v4:1.0.0") // Needed MediaSessionCompat.Token - implementation("com.google.android.material:material:1.10.0") + implementation("com.google.android.material:material:1.12.0") // Hilt - implementation("com.google.dagger:hilt-android:2.46.1") - kapt("com.google.dagger:hilt-compiler:2.46.1") + implementation("com.google.dagger:hilt-android:2.51.1") + kapt("com.google.dagger:hilt-compiler:2.51.1") // Media3 - implementation("androidx.media3:media3-exoplayer:1.2.0") - implementation("androidx.media3:media3-ui:1.2.0") - implementation("androidx.media3:media3-session:1.2.0") + implementation("androidx.media3:media3-exoplayer:1.3.1") + implementation("androidx.media3:media3-ui:1.3.1") + implementation("androidx.media3:media3-session:1.3.1") // Glide implementation("com.github.bumptech.glide:glide:4.15.1") From d76375bf9dd37ac40c3d41a7a84c2f190995e0e1 Mon Sep 17 00:00:00 2001 From: Hameed Chishti Date: Thu, 9 May 2024 19:00:59 +0300 Subject: [PATCH 2/6] refactor --- app/src/main/AndroidManifest.xml | 5 - .../common/ui/SimpleMediaActivity.kt | 20 +-- .../common/ui/SimpleMediaViewModel.kt | 30 ++-- .../simplemediaplayer/main/MainScreen.kt | 5 - player-service/src/main/AndroidManifest.xml | 14 ++ .../player_service/di/SimpleMediaModule.kt | 64 ++----- .../service/SimpleMediaService.kt | 80 +++++++-- .../service/SimpleMediaServiceHandler.kt | 170 ++++++++++++++---- .../SimpleMediaNotificationManager.kt | 10 +- 9 files changed, 254 insertions(+), 144 deletions(-) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index f9ac715..7317f1c 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -3,8 +3,6 @@ xmlns:tools="http://schemas.android.com/tools"> - - - \ No newline at end of file diff --git a/app/src/main/java/com/rcudev/simplemediaplayer/common/ui/SimpleMediaActivity.kt b/app/src/main/java/com/rcudev/simplemediaplayer/common/ui/SimpleMediaActivity.kt index 8f28962..42015e8 100644 --- a/app/src/main/java/com/rcudev/simplemediaplayer/common/ui/SimpleMediaActivity.kt +++ b/app/src/main/java/com/rcudev/simplemediaplayer/common/ui/SimpleMediaActivity.kt @@ -1,13 +1,17 @@ package com.rcudev.simplemediaplayer.common.ui +import android.content.ComponentName import android.content.Intent import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.activity.viewModels +import androidx.media3.session.MediaController +import androidx.media3.session.SessionToken import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable import androidx.navigation.compose.rememberNavController +import com.google.common.util.concurrent.MoreExecutors import com.rcudev.player_service.service.SimpleMediaService import com.rcudev.simplemediaplayer.common.ui.theme.SimpleMediaPlayerTheme import com.rcudev.simplemediaplayer.main.SimpleMediaScreen @@ -18,7 +22,6 @@ import dagger.hilt.android.AndroidEntryPoint class SimpleMediaActivity : ComponentActivity() { private val viewModel: SimpleMediaViewModel by viewModels() - private var isServiceRunning = false override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -32,7 +35,6 @@ class SimpleMediaActivity : ComponentActivity() { SimpleMediaScreen( vm = viewModel, navController = navController, - startService = ::startService ) } composable(Destination.Secondary.route) { @@ -42,18 +44,4 @@ class SimpleMediaActivity : ComponentActivity() { } } } - - override fun onDestroy() { - super.onDestroy() - stopService(Intent(this, SimpleMediaService::class.java)) - isServiceRunning = false - } - - private fun startService() { - if (!isServiceRunning) { - val intent = Intent(this, SimpleMediaService::class.java) - startForegroundService(intent) - isServiceRunning = true - } - } } \ No newline at end of file diff --git a/app/src/main/java/com/rcudev/simplemediaplayer/common/ui/SimpleMediaViewModel.kt b/app/src/main/java/com/rcudev/simplemediaplayer/common/ui/SimpleMediaViewModel.kt index d1f633d..8496cfa 100644 --- a/app/src/main/java/com/rcudev/simplemediaplayer/common/ui/SimpleMediaViewModel.kt +++ b/app/src/main/java/com/rcudev/simplemediaplayer/common/ui/SimpleMediaViewModel.kt @@ -35,13 +35,19 @@ class SimpleMediaViewModel @Inject constructor( val uiState = _uiState.asStateFlow() init { + simpleMediaServiceHandler.connect( + callBack = { connected -> + if (connected) { + println("VM.init - simpleMediaServiceHandler is connected. Load data") + loadData() + } + } + ) viewModelScope.launch { - loadData() - simpleMediaServiceHandler.simpleMediaState.collect { mediaState -> when (mediaState) { - is SimpleMediaState.Buffering -> calculateProgressValues(mediaState.progress) SimpleMediaState.Initial -> _uiState.value = UIState.Initial + is SimpleMediaState.Buffering -> calculateProgressValues(mediaState.progress) is SimpleMediaState.Playing -> isPlaying = mediaState.isPlaying is SimpleMediaState.Progress -> calculateProgressValues(mediaState.progress) is SimpleMediaState.Ready -> { @@ -54,9 +60,8 @@ class SimpleMediaViewModel @Inject constructor( } override fun onCleared() { - viewModelScope.launch { - simpleMediaServiceHandler.onPlayerEvent(PlayerEvent.Stop) - } + println("VM.onCleared") + simpleMediaServiceHandler.release() } fun onUIEvent(uiEvent: UIEvent) = viewModelScope.launch { @@ -117,17 +122,16 @@ class SimpleMediaViewModel @Inject constructor( simpleMediaServiceHandler.addMediaItem(mediaItem) //simpleMediaServiceHandler.addMediaItemList(mediaItemList) } - } sealed class UIEvent { - object PlayPause : UIEvent() - object Backward : UIEvent() - object Forward : UIEvent() + data object PlayPause : UIEvent() + data object Backward : UIEvent() + data object Forward : UIEvent() data class UpdateProgress(val newProgress: Float) : UIEvent() } sealed class UIState { - object Initial : UIState() - object Ready : UIState() -} \ No newline at end of file + data object Initial : UIState() + data object Ready : UIState() +} diff --git a/app/src/main/java/com/rcudev/simplemediaplayer/main/MainScreen.kt b/app/src/main/java/com/rcudev/simplemediaplayer/main/MainScreen.kt index d312d4f..81ce073 100644 --- a/app/src/main/java/com/rcudev/simplemediaplayer/main/MainScreen.kt +++ b/app/src/main/java/com/rcudev/simplemediaplayer/main/MainScreen.kt @@ -22,7 +22,6 @@ import com.rcudev.simplemediaplayer.common.ui.components.SimpleMediaPlayerUI internal fun SimpleMediaScreen( vm: SimpleMediaViewModel, navController: NavController, - startService: () -> Unit, ) { val state = vm.uiState.collectAsStateWithLifecycle() @@ -38,10 +37,6 @@ internal fun SimpleMediaScreen( .align(Alignment.Center) ) is UIState.Ready -> { - LaunchedEffect(true) { // This is only call first time - startService() - } - ReadyContent(vm = vm, navController = navController) } } diff --git a/player-service/src/main/AndroidManifest.xml b/player-service/src/main/AndroidManifest.xml index a5918e6..d110442 100644 --- a/player-service/src/main/AndroidManifest.xml +++ b/player-service/src/main/AndroidManifest.xml @@ -1,4 +1,18 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/player-service/src/main/java/com/rcudev/player_service/di/SimpleMediaModule.kt b/player-service/src/main/java/com/rcudev/player_service/di/SimpleMediaModule.kt index 22d4779..392131f 100644 --- a/player-service/src/main/java/com/rcudev/player_service/di/SimpleMediaModule.kt +++ b/player-service/src/main/java/com/rcudev/player_service/di/SimpleMediaModule.kt @@ -1,72 +1,28 @@ package com.rcudev.player_service.di import android.content.Context -import androidx.media3.common.AudioAttributes -import androidx.media3.common.C -import androidx.media3.common.util.UnstableApi -import androidx.media3.exoplayer.ExoPlayer -import androidx.media3.exoplayer.trackselection.DefaultTrackSelector -import androidx.media3.session.MediaSession import com.rcudev.player_service.service.SimpleMediaServiceHandler -import com.rcudev.player_service.service.notification.SimpleMediaNotificationManager import dagger.Module import dagger.Provides -import dagger.Reusable import dagger.hilt.InstallIn import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.components.SingletonComponent +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.SupervisorJob import javax.inject.Singleton @Module @InstallIn(SingletonComponent::class) class SimpleMediaModule { - - @Provides - @Singleton - fun provideAudioAttributes(): AudioAttributes = - AudioAttributes.Builder() - .setContentType(C.AUDIO_CONTENT_TYPE_MOVIE) - .setUsage(C.USAGE_MEDIA) - .build() - - @Provides - @Singleton - @UnstableApi - fun providePlayer( - @ApplicationContext context: Context, - audioAttributes: AudioAttributes - ): ExoPlayer = - ExoPlayer.Builder(context) - .setAudioAttributes(audioAttributes, true) - .setHandleAudioBecomingNoisy(true) - .setTrackSelector(DefaultTrackSelector(context)) - .build() - - @Provides - @Singleton - fun provideNotificationManager( - @ApplicationContext context: Context, - player: ExoPlayer - ): SimpleMediaNotificationManager = - SimpleMediaNotificationManager( - context = context, - player = player - ) - - @Provides - @Singleton - fun provideMediaSession( - @ApplicationContext context: Context, - player: ExoPlayer - ): MediaSession = - MediaSession.Builder(context, player).build() - @Provides @Singleton fun provideServiceHandler( - player: ExoPlayer - ): SimpleMediaServiceHandler = - SimpleMediaServiceHandler( - player = player + @ApplicationContext appContext: Context, + ): SimpleMediaServiceHandler { + return SimpleMediaServiceHandler( + appContext = appContext, + scope = CoroutineScope(SupervisorJob() + Dispatchers.Main), ) -} \ No newline at end of file + } +} diff --git a/player-service/src/main/java/com/rcudev/player_service/service/SimpleMediaService.kt b/player-service/src/main/java/com/rcudev/player_service/service/SimpleMediaService.kt index 14334f1..69ba710 100644 --- a/player-service/src/main/java/com/rcudev/player_service/service/SimpleMediaService.kt +++ b/player-service/src/main/java/com/rcudev/player_service/service/SimpleMediaService.kt @@ -1,45 +1,101 @@ package com.rcudev.player_service.service +import android.content.Context import android.content.Intent +import androidx.annotation.OptIn +import androidx.media3.common.AudioAttributes +import androidx.media3.common.C import androidx.media3.common.Player import androidx.media3.common.util.UnstableApi +import androidx.media3.exoplayer.ExoPlayer +import androidx.media3.exoplayer.trackselection.DefaultTrackSelector import androidx.media3.session.MediaSession import androidx.media3.session.MediaSessionService import com.rcudev.player_service.service.notification.SimpleMediaNotificationManager import dagger.hilt.android.AndroidEntryPoint +import dagger.hilt.android.qualifiers.ApplicationContext import javax.inject.Inject @AndroidEntryPoint class SimpleMediaService : MediaSessionService() { @Inject - lateinit var mediaSession: MediaSession + @ApplicationContext + lateinit var appContext: Context - @Inject - lateinit var notificationManager: SimpleMediaNotificationManager + private var mediaSession: MediaSession? = null + private var notificationManager: SimpleMediaNotificationManager? = null + + @OptIn(UnstableApi::class) + override fun onCreate() { + println("SimpleMediaService.onCreate") + super.onCreate() + val audioAttributes = AudioAttributes.Builder() + .setContentType(C.AUDIO_CONTENT_TYPE_MOVIE) + .setUsage(C.USAGE_MEDIA) + .build() + + val player = ExoPlayer.Builder(appContext) + .setAudioAttributes(audioAttributes, true) + .setHandleAudioBecomingNoisy(true) + .setTrackSelector(DefaultTrackSelector(appContext)) + .build() + + notificationManager = SimpleMediaNotificationManager( + context = appContext, + player = player + ) + + mediaSession = MediaSession.Builder(appContext, player).build() + } @UnstableApi override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { - notificationManager.startNotificationService( - mediaSessionService = this, - mediaSession = mediaSession - ) + println("SimpleMediaService.onStartCommand") + val session = mediaSession + if (session != null) { + println("SimpleMediaService.onStartCommand - startNotificationService") + notificationManager?.startNotificationService( + mediaSessionService = this, + mediaSession = session + ) + } return super.onStartCommand(intent, flags, startId) } + // The user dismissed the app from the recent tasks + override fun onTaskRemoved(rootIntent: Intent?) { + println("SimpleMediaService.onTaskRemoved") + val player = mediaSession?.player + if (player == null || + !player.playWhenReady || + player.mediaItemCount == 0 || + player.playbackState == Player.STATE_ENDED + ) { + println("SimpleMediaService.onTaskRemoved - stopSelf") + // Stop the service if not playing, continue playing in the background otherwise. + stopSelf() + } + } + override fun onDestroy() { - super.onDestroy() - mediaSession.run { - release() + println("SimpleMediaService.onDestroy") + mediaSession?.run { if (player.playbackState != Player.STATE_IDLE) { player.seekTo(0) player.playWhenReady = false player.stop() } + println("SimpleMediaService.onDestroy - release player and session") + player.release() + release() + mediaSession = null + notificationManager = null } + super.onDestroy() } - override fun onGetSession(controllerInfo: MediaSession.ControllerInfo): MediaSession = + override fun onGetSession(controllerInfo: MediaSession.ControllerInfo): MediaSession? = mediaSession -} \ No newline at end of file +} diff --git a/player-service/src/main/java/com/rcudev/player_service/service/SimpleMediaServiceHandler.kt b/player-service/src/main/java/com/rcudev/player_service/service/SimpleMediaServiceHandler.kt index 1608698..53c6aa5 100644 --- a/player-service/src/main/java/com/rcudev/player_service/service/SimpleMediaServiceHandler.kt +++ b/player-service/src/main/java/com/rcudev/player_service/service/SimpleMediaServiceHandler.kt @@ -1,102 +1,206 @@ package com.rcudev.player_service.service import android.annotation.SuppressLint +import android.app.ActivityManager +import android.app.Service +import android.content.ComponentName +import android.content.Context +import android.content.Intent import androidx.media3.common.MediaItem import androidx.media3.common.Player import androidx.media3.exoplayer.ExoPlayer +import androidx.media3.session.MediaController +import androidx.media3.session.SessionToken +import com.google.common.util.concurrent.ListenableFuture +import com.google.common.util.concurrent.MoreExecutors import kotlinx.coroutines.* import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.update import javax.inject.Inject class SimpleMediaServiceHandler @Inject constructor( - private val player: ExoPlayer + private val appContext: Context, + private val scope: CoroutineScope, ) : Player.Listener { private val _simpleMediaState = MutableStateFlow(SimpleMediaState.Initial) val simpleMediaState = _simpleMediaState.asStateFlow() + private var controllerFuture: ListenableFuture? = null + private var controller: MediaController? = null private var job: Job? = null init { - player.addListener(this) - job = Job() + if (!appContext.isServiceRunning(SimpleMediaService::class.java)) { + println("SimpleMediaServiceHandler.init service not running, start") + Intent(appContext, SimpleMediaService::class.java).also { + appContext.startForegroundService(it) + } + } + } + + fun connect( + callBack: (Boolean) -> Unit = {}, + isRetry: Boolean = false, + ) { + if (controller?.isConnected == true) { + println("SimpleMediaServiceHandler.connect - already connected") + _simpleMediaState.update { + SimpleMediaState.Ready(controller?.duration ?: 0) + } + //_simpleMediaState.update { + // SimpleMediaState.Playing(controller?.isPlaying ?: false) + //} + startProgressUpdate() + return + } + + println("SimpleMediaServiceHandler.connect") + kotlin.runCatching { + val sessionToken = SessionToken( + appContext, + ComponentName(appContext, SimpleMediaService::class.java) + ) + controllerFuture = MediaController.Builder(appContext, sessionToken).buildAsync() + controllerFuture?.addListener( + { + println("SimpleMediaServiceHandler.connect - controllerFuture.get") + controller = controllerFuture?.get() + controller?.addListener(this@SimpleMediaServiceHandler) + callBack(controller?.isConnected ?: false) + }, + MoreExecutors.directExecutor() + ) + }.onSuccess { + println("SimpleMediaServiceHandler.connect got handle to controller") + }.onFailure { + println("SimpleMediaServiceHandler.connect failed to get handle to controller. retry. err ${it.message}") + if (!isRetry) { + connect(isRetry = true) + } else { + callBack(false) + } + } + } + + fun release(stopPlayback: Boolean = false) { + println("SimpleMediaServiceHandler.release called, cleanup") + controller ?: return + stopProgressUpdate() + controllerFuture?.cancel(true) + controllerFuture = null + controller?.release() + controller = null + if (stopPlayback && appContext.isServiceRunning(SimpleMediaService::class.java)) { + println("SimpleMediaServiceHandler.release stop service") + appContext.stopService(Intent(appContext, SimpleMediaService::class.java)) + } } fun addMediaItem(mediaItem: MediaItem) { - player.setMediaItem(mediaItem) - player.prepare() + println("SimpleMediaServiceHandler.addMediaItem (controller connected: ${controller?.isConnected})") + controller?.setMediaItem(mediaItem) + controller?.prepare() } fun addMediaItemList(mediaItemList: List) { - player.setMediaItems(mediaItemList) - player.prepare() + println("SimpleMediaServiceHandler.addMediaItemList (controller connected: ${controller?.isConnected})") + controller?.setMediaItems(mediaItemList) + controller?.prepare() } suspend fun onPlayerEvent(playerEvent: PlayerEvent) { + println("SimpleMediaServiceHandler.onPlayerEvent $playerEvent") + val controller = this.controller ?: return when (playerEvent) { - PlayerEvent.Backward -> player.seekBack() - PlayerEvent.Forward -> player.seekForward() + PlayerEvent.Backward -> controller.seekBack() + PlayerEvent.Forward -> controller.seekForward() + PlayerEvent.Stop -> stopProgressUpdate() PlayerEvent.PlayPause -> { - if (player.isPlaying) { - player.pause() + if (controller.isPlaying) { + controller.pause() stopProgressUpdate() } else { - player.play() - _simpleMediaState.value = SimpleMediaState.Playing(isPlaying = true) + controller.play() + _simpleMediaState.update { + SimpleMediaState.Playing(isPlaying = true) + } startProgressUpdate() } } - PlayerEvent.Stop -> stopProgressUpdate() - is PlayerEvent.UpdateProgress -> player.seekTo((player.duration * playerEvent.newProgress).toLong()) + is PlayerEvent.UpdateProgress -> { + controller.seekTo((controller.duration * playerEvent.newProgress).toLong()) + } } } @SuppressLint("SwitchIntDef") override fun onPlaybackStateChanged(playbackState: Int) { + println("SimpleMediaServiceHandler.onPlaybackStateChanged $playbackState") + val controller = this.controller ?: return when (playbackState) { - ExoPlayer.STATE_BUFFERING -> _simpleMediaState.value = - SimpleMediaState.Buffering(player.currentPosition) - ExoPlayer.STATE_READY -> _simpleMediaState.value = - SimpleMediaState.Ready(player.duration) + ExoPlayer.STATE_BUFFERING -> { + _simpleMediaState.update { + SimpleMediaState.Buffering(controller.currentPosition) + } + } + ExoPlayer.STATE_READY -> { + _simpleMediaState.update { + SimpleMediaState.Ready(controller.duration) + } + } } } @OptIn(DelicateCoroutinesApi::class) override fun onIsPlayingChanged(isPlaying: Boolean) { - _simpleMediaState.value = SimpleMediaState.Playing(isPlaying = isPlaying) + println("SimpleMediaServiceHandler.onIsPlayingChanged $isPlaying") + _simpleMediaState.update { SimpleMediaState.Playing(isPlaying = isPlaying) } if (isPlaying) { - GlobalScope.launch(Dispatchers.Main) { - startProgressUpdate() - } + startProgressUpdate() } else { stopProgressUpdate() } } - private suspend fun startProgressUpdate() = job.run { - while (true) { - delay(500) - _simpleMediaState.value = SimpleMediaState.Progress(player.currentPosition) + private fun startProgressUpdate() { + job?.cancel() + job = scope.launch { + while (true) { + delay(500) + _simpleMediaState.update { + SimpleMediaState.Progress(controller?.currentPosition ?: 0) + } + } } } private fun stopProgressUpdate() { job?.cancel() - _simpleMediaState.value = SimpleMediaState.Playing(isPlaying = false) + job = null + _simpleMediaState.update { SimpleMediaState.Playing(isPlaying = false) } + } + + private fun Context.isServiceRunning(serviceClass: Class) = try { + (getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager) + .getRunningServices(Int.MAX_VALUE) + .any { it.service.className == serviceClass.name } + } catch (e: Exception) { + false } } sealed class PlayerEvent { - object PlayPause : PlayerEvent() - object Backward : PlayerEvent() - object Forward : PlayerEvent() - object Stop : PlayerEvent() + data object PlayPause : PlayerEvent() + data object Backward : PlayerEvent() + data object Forward : PlayerEvent() + data object Stop : PlayerEvent() data class UpdateProgress(val newProgress: Float) : PlayerEvent() } sealed class SimpleMediaState { - object Initial : SimpleMediaState() + data object Initial : SimpleMediaState() data class Ready(val duration: Long) : SimpleMediaState() data class Progress(val progress: Long) : SimpleMediaState() data class Buffering(val progress: Long) : SimpleMediaState() diff --git a/player-service/src/main/java/com/rcudev/player_service/service/notification/SimpleMediaNotificationManager.kt b/player-service/src/main/java/com/rcudev/player_service/service/notification/SimpleMediaNotificationManager.kt index 57025b5..b72c5ee 100644 --- a/player-service/src/main/java/com/rcudev/player_service/service/notification/SimpleMediaNotificationManager.kt +++ b/player-service/src/main/java/com/rcudev/player_service/service/notification/SimpleMediaNotificationManager.kt @@ -6,22 +6,20 @@ import android.app.NotificationManager import android.content.Context import androidx.core.app.NotificationCompat import androidx.core.app.NotificationManagerCompat +import androidx.media3.common.Player import androidx.media3.common.util.UnstableApi -import androidx.media3.exoplayer.ExoPlayer import androidx.media3.session.MediaSession import androidx.media3.session.MediaSessionService import androidx.media3.ui.PlayerNotificationManager import com.rcudev.player_service.R -import dagger.hilt.android.qualifiers.ApplicationContext -import javax.inject.Inject private const val NOTIFICATION_ID = 200 private const val NOTIFICATION_CHANNEL_NAME = "notification channel 1" private const val NOTIFICATION_CHANNEL_ID = "notification channel id 1" -class SimpleMediaNotificationManager @Inject constructor( - @ApplicationContext private val context: Context, - private val player: ExoPlayer +class SimpleMediaNotificationManager( + private val context: Context, + private val player: Player, ) { private var notificationManager: NotificationManagerCompat = From aa207b2108f4a11308c266bea144af4c96c446df Mon Sep 17 00:00:00 2001 From: Hameed Chishti Date: Fri, 10 May 2024 08:50:41 +0300 Subject: [PATCH 3/6] small ui side fixes --- .../common/ui/SimpleMediaViewModel.kt | 23 ++++++++---- .../player_service/di/SimpleMediaModule.kt | 6 ++-- .../service/SimpleMediaServiceHandler.kt | 36 +++++++------------ 3 files changed, 33 insertions(+), 32 deletions(-) diff --git a/app/src/main/java/com/rcudev/simplemediaplayer/common/ui/SimpleMediaViewModel.kt b/app/src/main/java/com/rcudev/simplemediaplayer/common/ui/SimpleMediaViewModel.kt index 8496cfa..3c69d5a 100644 --- a/app/src/main/java/com/rcudev/simplemediaplayer/common/ui/SimpleMediaViewModel.kt +++ b/app/src/main/java/com/rcudev/simplemediaplayer/common/ui/SimpleMediaViewModel.kt @@ -1,6 +1,8 @@ package com.rcudev.simplemediaplayer.common.ui import android.net.Uri +import androidx.compose.runtime.mutableFloatStateOf +import androidx.compose.runtime.mutableLongStateOf import androidx.compose.runtime.mutableStateOf import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel @@ -26,8 +28,8 @@ class SimpleMediaViewModel @Inject constructor( savedStateHandle: SavedStateHandle ) : ViewModel() { - var duration by savedStateHandle.saveable { mutableStateOf(0L) } - var progress by savedStateHandle.saveable { mutableStateOf(0f) } + var duration by savedStateHandle.saveable { mutableLongStateOf(0L) } + var progress by savedStateHandle.saveable { mutableFloatStateOf(0f) } var progressString by savedStateHandle.saveable { mutableStateOf("00:00") } var isPlaying by savedStateHandle.saveable { mutableStateOf(false) } @@ -36,13 +38,22 @@ class SimpleMediaViewModel @Inject constructor( init { simpleMediaServiceHandler.connect( - callBack = { connected -> - if (connected) { - println("VM.init - simpleMediaServiceHandler is connected. Load data") - loadData() + callBack = { connected, playing -> + when { + connected && playing -> { + println("VM.init - simpleMediaServiceHandler is connected and is playing") + } + connected && !playing -> { + println("VM.init - simpleMediaServiceHandler is connected. Load data") + loadData() + } + else -> { + println("VM.init - simpleMediaServiceHandler failed to connect") + } } } ) + viewModelScope.launch { simpleMediaServiceHandler.simpleMediaState.collect { mediaState -> when (mediaState) { diff --git a/player-service/src/main/java/com/rcudev/player_service/di/SimpleMediaModule.kt b/player-service/src/main/java/com/rcudev/player_service/di/SimpleMediaModule.kt index 392131f..f1dc722 100644 --- a/player-service/src/main/java/com/rcudev/player_service/di/SimpleMediaModule.kt +++ b/player-service/src/main/java/com/rcudev/player_service/di/SimpleMediaModule.kt @@ -5,7 +5,9 @@ import com.rcudev.player_service.service.SimpleMediaServiceHandler import dagger.Module import dagger.Provides import dagger.hilt.InstallIn +import dagger.hilt.android.components.ViewModelComponent import dagger.hilt.android.qualifiers.ApplicationContext +import dagger.hilt.android.scopes.ViewModelScoped import dagger.hilt.components.SingletonComponent import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers @@ -13,10 +15,10 @@ import kotlinx.coroutines.SupervisorJob import javax.inject.Singleton @Module -@InstallIn(SingletonComponent::class) +@InstallIn(ViewModelComponent::class) class SimpleMediaModule { @Provides - @Singleton + @ViewModelScoped fun provideServiceHandler( @ApplicationContext appContext: Context, ): SimpleMediaServiceHandler { diff --git a/player-service/src/main/java/com/rcudev/player_service/service/SimpleMediaServiceHandler.kt b/player-service/src/main/java/com/rcudev/player_service/service/SimpleMediaServiceHandler.kt index 53c6aa5..2d63161 100644 --- a/player-service/src/main/java/com/rcudev/player_service/service/SimpleMediaServiceHandler.kt +++ b/player-service/src/main/java/com/rcudev/player_service/service/SimpleMediaServiceHandler.kt @@ -41,21 +41,8 @@ class SimpleMediaServiceHandler @Inject constructor( } fun connect( - callBack: (Boolean) -> Unit = {}, - isRetry: Boolean = false, + callBack: (Boolean, Boolean) -> Unit = { _, _ -> }, ) { - if (controller?.isConnected == true) { - println("SimpleMediaServiceHandler.connect - already connected") - _simpleMediaState.update { - SimpleMediaState.Ready(controller?.duration ?: 0) - } - //_simpleMediaState.update { - // SimpleMediaState.Playing(controller?.isPlaying ?: false) - //} - startProgressUpdate() - return - } - println("SimpleMediaServiceHandler.connect") kotlin.runCatching { val sessionToken = SessionToken( @@ -68,19 +55,21 @@ class SimpleMediaServiceHandler @Inject constructor( println("SimpleMediaServiceHandler.connect - controllerFuture.get") controller = controllerFuture?.get() controller?.addListener(this@SimpleMediaServiceHandler) - callBack(controller?.isConnected ?: false) + val connected = controller?.isConnected ?: false + val playing = controller?.isPlaying ?: false + callBack(connected, playing) + _simpleMediaState.update { + SimpleMediaState.Ready(controller?.duration ?: 0) + } + onIsPlayingChanged(playing) }, MoreExecutors.directExecutor() ) }.onSuccess { - println("SimpleMediaServiceHandler.connect got handle to controller") + println("SimpleMediaServiceHandler.connect session token created") }.onFailure { - println("SimpleMediaServiceHandler.connect failed to get handle to controller. retry. err ${it.message}") - if (!isRetry) { - connect(isRetry = true) - } else { - callBack(false) - } + println("SimpleMediaServiceHandler.connect failed to get handle to controller. err ${it.message}") + callBack(false, false) } } @@ -110,7 +99,7 @@ class SimpleMediaServiceHandler @Inject constructor( controller?.prepare() } - suspend fun onPlayerEvent(playerEvent: PlayerEvent) { + fun onPlayerEvent(playerEvent: PlayerEvent) { println("SimpleMediaServiceHandler.onPlayerEvent $playerEvent") val controller = this.controller ?: return when (playerEvent) { @@ -153,7 +142,6 @@ class SimpleMediaServiceHandler @Inject constructor( } } - @OptIn(DelicateCoroutinesApi::class) override fun onIsPlayingChanged(isPlaying: Boolean) { println("SimpleMediaServiceHandler.onIsPlayingChanged $isPlaying") _simpleMediaState.update { SimpleMediaState.Playing(isPlaying = isPlaying) } From a6bd1aa87e1e77c37c3ff5691a2b3e8775de0cd6 Mon Sep 17 00:00:00 2001 From: Hameed Chishti Date: Fri, 10 May 2024 10:55:54 +0300 Subject: [PATCH 4/6] use kotlinx-coroutines-guava --- player-service/build.gradle.kts | 3 + .../service/SimpleMediaServiceHandler.kt | 59 +++++++++---------- 2 files changed, 30 insertions(+), 32 deletions(-) diff --git a/player-service/build.gradle.kts b/player-service/build.gradle.kts index 0054903..479e28f 100644 --- a/player-service/build.gradle.kts +++ b/player-service/build.gradle.kts @@ -50,6 +50,9 @@ dependencies { // Glide implementation("com.github.bumptech.glide:glide:4.15.1") + + implementation("org.jetbrains.kotlinx:kotlinx-coroutines-guava:1.8.0") + testImplementation("junit:junit:4.13.2") androidTestImplementation("androidx.test.ext:junit:1.1.5") androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1") diff --git a/player-service/src/main/java/com/rcudev/player_service/service/SimpleMediaServiceHandler.kt b/player-service/src/main/java/com/rcudev/player_service/service/SimpleMediaServiceHandler.kt index 2d63161..c3ab15a 100644 --- a/player-service/src/main/java/com/rcudev/player_service/service/SimpleMediaServiceHandler.kt +++ b/player-service/src/main/java/com/rcudev/player_service/service/SimpleMediaServiceHandler.kt @@ -11,12 +11,11 @@ import androidx.media3.common.Player import androidx.media3.exoplayer.ExoPlayer import androidx.media3.session.MediaController import androidx.media3.session.SessionToken -import com.google.common.util.concurrent.ListenableFuture -import com.google.common.util.concurrent.MoreExecutors import kotlinx.coroutines.* import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.update +import kotlinx.coroutines.guava.await import javax.inject.Inject class SimpleMediaServiceHandler @Inject constructor( @@ -27,7 +26,6 @@ class SimpleMediaServiceHandler @Inject constructor( private val _simpleMediaState = MutableStateFlow(SimpleMediaState.Initial) val simpleMediaState = _simpleMediaState.asStateFlow() - private var controllerFuture: ListenableFuture? = null private var controller: MediaController? = null private var job: Job? = null @@ -43,42 +41,39 @@ class SimpleMediaServiceHandler @Inject constructor( fun connect( callBack: (Boolean, Boolean) -> Unit = { _, _ -> }, ) { - println("SimpleMediaServiceHandler.connect") - kotlin.runCatching { - val sessionToken = SessionToken( - appContext, - ComponentName(appContext, SimpleMediaService::class.java) - ) - controllerFuture = MediaController.Builder(appContext, sessionToken).buildAsync() - controllerFuture?.addListener( - { - println("SimpleMediaServiceHandler.connect - controllerFuture.get") - controller = controllerFuture?.get() - controller?.addListener(this@SimpleMediaServiceHandler) - val connected = controller?.isConnected ?: false - val playing = controller?.isPlaying ?: false - callBack(connected, playing) - _simpleMediaState.update { - SimpleMediaState.Ready(controller?.duration ?: 0) - } - onIsPlayingChanged(playing) - }, - MoreExecutors.directExecutor() - ) - }.onSuccess { - println("SimpleMediaServiceHandler.connect session token created") - }.onFailure { - println("SimpleMediaServiceHandler.connect failed to get handle to controller. err ${it.message}") - callBack(false, false) + job?.cancel() + job = scope.launch { + println("SimpleMediaServiceHandler.connect") + kotlin.runCatching { + val sessionToken = SessionToken( + appContext, + ComponentName(appContext, SimpleMediaService::class.java) + ) + controller = MediaController.Builder(appContext, sessionToken) + .buildAsync() + .await() + controller?.addListener(this@SimpleMediaServiceHandler) + val connected = controller?.isConnected ?: false + val playing = controller?.isPlaying ?: false + callBack(connected, playing) + _simpleMediaState.update { + SimpleMediaState.Ready(controller?.duration ?: 0) + } + onIsPlayingChanged(playing) + }.onSuccess { + println("SimpleMediaServiceHandler.connect session token created") + }.onFailure { + println("SimpleMediaServiceHandler.connect failed to get handle to controller. err ${it.message}") + callBack(false, false) + } } } fun release(stopPlayback: Boolean = false) { println("SimpleMediaServiceHandler.release called, cleanup") controller ?: return + job?.cancel() stopProgressUpdate() - controllerFuture?.cancel(true) - controllerFuture = null controller?.release() controller = null if (stopPlayback && appContext.isServiceRunning(SimpleMediaService::class.java)) { From a44d852930e79dfb5456929402f507f3895fcc2d Mon Sep 17 00:00:00 2001 From: Hameed Chishti Date: Fri, 10 May 2024 11:34:49 +0300 Subject: [PATCH 5/6] use default notifications provided by media service --- .../common/ui/SimpleMediaViewModel.kt | 2 +- .../player_service/service/SimpleMediaService.kt | 16 ++++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/app/src/main/java/com/rcudev/simplemediaplayer/common/ui/SimpleMediaViewModel.kt b/app/src/main/java/com/rcudev/simplemediaplayer/common/ui/SimpleMediaViewModel.kt index 3c69d5a..1ef7c06 100644 --- a/app/src/main/java/com/rcudev/simplemediaplayer/common/ui/SimpleMediaViewModel.kt +++ b/app/src/main/java/com/rcudev/simplemediaplayer/common/ui/SimpleMediaViewModel.kt @@ -111,7 +111,7 @@ class SimpleMediaViewModel @Inject constructor( .setFolderType(MediaMetadata.FOLDER_TYPE_ALBUMS) .setArtworkUri(Uri.parse("https://i.pinimg.com/736x/4b/02/1f/4b021f002b90ab163ef41aaaaa17c7a4.jpg")) .setAlbumTitle("SoundHelix") - .setDisplayTitle("Song 1") + .setTitle("Song 1") .build() ).build() diff --git a/player-service/src/main/java/com/rcudev/player_service/service/SimpleMediaService.kt b/player-service/src/main/java/com/rcudev/player_service/service/SimpleMediaService.kt index 69ba710..00644b5 100644 --- a/player-service/src/main/java/com/rcudev/player_service/service/SimpleMediaService.kt +++ b/player-service/src/main/java/com/rcudev/player_service/service/SimpleMediaService.kt @@ -24,7 +24,7 @@ class SimpleMediaService : MediaSessionService() { lateinit var appContext: Context private var mediaSession: MediaSession? = null - private var notificationManager: SimpleMediaNotificationManager? = null + //private var notificationManager: SimpleMediaNotificationManager? = null @OptIn(UnstableApi::class) override fun onCreate() { @@ -41,15 +41,15 @@ class SimpleMediaService : MediaSessionService() { .setTrackSelector(DefaultTrackSelector(appContext)) .build() - notificationManager = SimpleMediaNotificationManager( - context = appContext, - player = player - ) + //notificationManager = SimpleMediaNotificationManager( + // context = appContext, + // player = player + //) mediaSession = MediaSession.Builder(appContext, player).build() } - @UnstableApi + /*@UnstableApi override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { println("SimpleMediaService.onStartCommand") val session = mediaSession @@ -62,7 +62,7 @@ class SimpleMediaService : MediaSessionService() { } return super.onStartCommand(intent, flags, startId) - } + }*/ // The user dismissed the app from the recent tasks override fun onTaskRemoved(rootIntent: Intent?) { @@ -91,7 +91,7 @@ class SimpleMediaService : MediaSessionService() { player.release() release() mediaSession = null - notificationManager = null + //notificationManager = null } super.onDestroy() } From 0807be116e1ea02864c3a9cbfd872ab68186ca4d Mon Sep 17 00:00:00 2001 From: Hameed Chishti Date: Wed, 2 Oct 2024 14:57:53 +0300 Subject: [PATCH 6/6] updates --- app/build.gradle.kts | 28 +++++++++---------- .../common/ui/Destination.kt | 4 +-- .../common/ui/components/PlayerBar.kt | 17 ++++++----- build.gradle.kts | 8 +++--- gradle/wrapper/gradle-wrapper.properties | 2 +- player-service/build.gradle.kts | 2 +- .../service/SimpleMediaService.kt | 1 - 7 files changed, 32 insertions(+), 30 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index d3e55f0..bc119df 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -7,12 +7,12 @@ plugins { android { namespace = "com.rcudev.simplemediaplayer" - compileSdk = 34 + compileSdk = 35 defaultConfig { applicationId = "com.rcudev.simplemediaplayer" minSdk = 26 - targetSdk = 34 + targetSdk = 35 versionCode = 1 versionName = "1.0" @@ -50,33 +50,33 @@ dependencies { implementation(project(":player-service")) implementation("androidx.core:core-ktx:1.13.1") - implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.7.0") + implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.8.6") // Compose - implementation(platform("androidx.compose:compose-bom:2024.05.00")) + implementation(platform("androidx.compose:compose-bom:2024.09.02")) implementation("androidx.compose.ui:ui") implementation("androidx.compose.ui:ui-tooling-preview") implementation("androidx.compose.material3:material3") - implementation("androidx.activity:activity-compose:1.9.0") - implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.7.0") - implementation("androidx.lifecycle:lifecycle-runtime-compose:2.7.0") - implementation("androidx.navigation:navigation-compose:2.7.7") + implementation("androidx.activity:activity-compose:1.9.2") + implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.8.6") + implementation("androidx.lifecycle:lifecycle-runtime-compose:2.8.6") + implementation("androidx.navigation:navigation-compose:2.8.1") implementation("androidx.hilt:hilt-navigation-compose:1.2.0") // Hilt - implementation("com.google.dagger:hilt-android:2.51.1") - kapt("com.google.dagger:hilt-compiler:2.51.1") + implementation("com.google.dagger:hilt-android:2.52") + kapt("com.google.dagger:hilt-compiler:2.52") // Media3 - implementation("androidx.media3:media3-session:1.3.1") + implementation("androidx.media3:media3-session:1.4.1") // Coil - implementation("io.coil-kt:coil-compose:2.6.0") + implementation("io.coil-kt:coil-compose:2.7.0") testImplementation("junit:junit:4.13.2") - androidTestImplementation("androidx.test.ext:junit:1.1.5") - androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1") + androidTestImplementation("androidx.test.ext:junit:1.2.1") + androidTestImplementation("androidx.test.espresso:espresso-core:3.6.1") androidTestImplementation("androidx.compose.ui:ui-test-junit4") debugImplementation("androidx.compose.ui:ui-tooling") debugImplementation("androidx.compose.ui:ui-test-manifest") diff --git a/app/src/main/java/com/rcudev/simplemediaplayer/common/ui/Destination.kt b/app/src/main/java/com/rcudev/simplemediaplayer/common/ui/Destination.kt index 542fe39..31c1273 100644 --- a/app/src/main/java/com/rcudev/simplemediaplayer/common/ui/Destination.kt +++ b/app/src/main/java/com/rcudev/simplemediaplayer/common/ui/Destination.kt @@ -1,6 +1,6 @@ package com.rcudev.simplemediaplayer.common.ui sealed class Destination(val route: String) { - object Main: Destination("main") - object Secondary: Destination("secondary") + data object Main: Destination("main") + data object Secondary: Destination("secondary") } \ No newline at end of file diff --git a/app/src/main/java/com/rcudev/simplemediaplayer/common/ui/components/PlayerBar.kt b/app/src/main/java/com/rcudev/simplemediaplayer/common/ui/components/PlayerBar.kt index 8763a7c..9c05421 100644 --- a/app/src/main/java/com/rcudev/simplemediaplayer/common/ui/components/PlayerBar.kt +++ b/app/src/main/java/com/rcudev/simplemediaplayer/common/ui/components/PlayerBar.kt @@ -4,8 +4,11 @@ import androidx.compose.foundation.layout.* import androidx.compose.material3.Slider import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import com.rcudev.simplemediaplayer.common.ui.UIEvent @@ -17,21 +20,21 @@ internal fun PlayerBar( progressString: String, onUiEvent: (UIEvent) -> Unit ) { - val newProgressValue = remember { mutableStateOf(0f) } - val useNewProgressValue = remember { mutableStateOf(false) } + var newProgressValue by remember { mutableFloatStateOf(0f) } + var useNewProgressValue by remember { mutableStateOf(false) } Column( modifier = Modifier.fillMaxWidth() ) { Slider( - value = if (useNewProgressValue.value) newProgressValue.value else progress, + value = if (useNewProgressValue) newProgressValue else progress, onValueChange = { newValue -> - useNewProgressValue.value = true - newProgressValue.value = newValue + useNewProgressValue = true + newProgressValue = newValue onUiEvent(UIEvent.UpdateProgress(newProgress = newValue)) }, onValueChangeFinished = { - useNewProgressValue.value = false + useNewProgressValue = false }, modifier = Modifier .padding(horizontal = 8.dp) @@ -46,4 +49,4 @@ internal fun PlayerBar( Text(text = durationString) } } -} \ No newline at end of file +} diff --git a/build.gradle.kts b/build.gradle.kts index 7567d1e..bf9c9f8 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,11 +1,11 @@ buildscript { dependencies { - classpath("com.google.dagger:hilt-android-gradle-plugin:2.51.1") + classpath("com.google.dagger:hilt-android-gradle-plugin:2.52") } } plugins { - id("com.android.application") version "8.4.0" apply false - id("com.android.library") version "8.4.0" apply false - id("org.jetbrains.kotlin.android") version "1.9.23" apply false + id("com.android.application") version "8.6.1" apply false + id("com.android.library") version "8.6.1" apply false + id("org.jetbrains.kotlin.android") version "2.0.20" apply false } \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 03b65bb..b9d5e99 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ #Fri Mar 24 10:06:16 CET 2023 distributionBase=GRADLE_USER_HOME -distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip distributionPath=wrapper/dists zipStorePath=wrapper/dists zipStoreBase=GRADLE_USER_HOME diff --git a/player-service/build.gradle.kts b/player-service/build.gradle.kts index 479e28f..6fac940 100644 --- a/player-service/build.gradle.kts +++ b/player-service/build.gradle.kts @@ -51,7 +51,7 @@ dependencies { implementation("com.github.bumptech.glide:glide:4.15.1") - implementation("org.jetbrains.kotlinx:kotlinx-coroutines-guava:1.8.0") + implementation("org.jetbrains.kotlinx:kotlinx-coroutines-guava:1.8.1") testImplementation("junit:junit:4.13.2") androidTestImplementation("androidx.test.ext:junit:1.1.5") diff --git a/player-service/src/main/java/com/rcudev/player_service/service/SimpleMediaService.kt b/player-service/src/main/java/com/rcudev/player_service/service/SimpleMediaService.kt index 00644b5..a46561b 100644 --- a/player-service/src/main/java/com/rcudev/player_service/service/SimpleMediaService.kt +++ b/player-service/src/main/java/com/rcudev/player_service/service/SimpleMediaService.kt @@ -11,7 +11,6 @@ import androidx.media3.exoplayer.ExoPlayer import androidx.media3.exoplayer.trackselection.DefaultTrackSelector import androidx.media3.session.MediaSession import androidx.media3.session.MediaSessionService -import com.rcudev.player_service.service.notification.SimpleMediaNotificationManager import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.qualifiers.ApplicationContext import javax.inject.Inject