Skip to content

Commit

Permalink
feat/player: Added player connection state handler and service binding
Browse files Browse the repository at this point in the history
fix/player: Player not removing media items after being unbound

Signed-off-by: Gabriel Fontán <gabilessto@gmail.com>
  • Loading branch information
BobbyESP committed Apr 20, 2024
1 parent b73736b commit 872ef96
Show file tree
Hide file tree
Showing 6 changed files with 96 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import androidx.media3.common.util.UnstableApi
import androidx.media3.exoplayer.ExoPlayer
import androidx.media3.exoplayer.trackselection.DefaultTrackSelector
import androidx.media3.session.MediaSession
import com.bobbyesp.mediaplayer.service.ConnectionHandler
import com.bobbyesp.mediaplayer.service.MediaServiceHandler
import com.bobbyesp.mediaplayer.service.notifications.MediaNotificationManager
import dagger.Module
Expand Down Expand Up @@ -61,4 +62,7 @@ object MediaPlayerModule {
player = player
)

@Provides
@Singleton
fun provideConnectionHandler(): ConnectionHandler = ConnectionHandler()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package com.bobbyesp.mediaplayer.service

import android.util.Log
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.update

sealed class ConnectionState {
data object Disconnected : ConnectionState()
data class Connected(val serviceHandler: MediaServiceHandler) : ConnectionState()
}

class ConnectionHandler {
private val _connectionState = MutableStateFlow<ConnectionState>(ConnectionState.Disconnected)
val connectionState: StateFlow<ConnectionState> = _connectionState

init {
connectionState.onEach { newState ->
Log.d("ConnectionHandler", "Connection state changed: $newState")
}.launchIn(GlobalScope)
}

fun connect(serviceHandler: MediaServiceHandler) {
_connectionState.update {
ConnectionState.Connected(serviceHandler)
}
}

fun disconnect() {
_connectionState.update {
ConnectionState.Disconnected
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,15 @@ class MediaplayerService : MediaSessionService() {
@Inject
lateinit var notificationManager: MediaNotificationManager

@Inject
lateinit var connectionHandler: ConnectionHandler

@Inject
lateinit var mediaServiceHandler: MediaServiceHandler

@UnstableApi
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
connectionHandler.connect(mediaServiceHandler)
notificationManager.startNotificationService(
mediaSessionService = this,
mediaSession = mediaSession
Expand All @@ -30,15 +37,17 @@ class MediaplayerService : MediaSessionService() {
}

override fun onDestroy() {
super.onDestroy()
connectionHandler.disconnect()
mediaSession.run {
release()
if (player.playbackState != Player.STATE_IDLE) {
player.seekTo(0)
player.clearMediaItems()
player.playWhenReady = false
player.stop()
}
}
super.onDestroy()
}

override fun onGetSession(controllerInfo: MediaSession.ControllerInfo): MediaSession {
Expand Down
39 changes: 36 additions & 3 deletions app/src/main/java/com/bobbyesp/metadator/MainActivity.kt
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
package com.bobbyesp.metadator

import android.content.ComponentName
import android.content.Intent
import android.content.ServiceConnection
import android.os.Bundle
import android.os.IBinder
import android.util.Log
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
Expand All @@ -10,20 +14,26 @@ import androidx.compose.material3.windowsizeclass.calculateWindowSizeClass
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
import androidx.core.view.ViewCompat
import androidx.core.view.WindowCompat
import com.bobbyesp.mediaplayer.service.ConnectionHandler
import com.bobbyesp.mediaplayer.service.MediaplayerService
import com.bobbyesp.metadator.presentation.Navigator
import com.bobbyesp.metadator.presentation.common.AppLocalSettingsProvider
import com.bobbyesp.metadator.presentation.common.LocalDarkTheme
import com.bobbyesp.metadator.presentation.theme.MetadatorTheme
import dagger.hilt.android.AndroidEntryPoint
import setupFirebase
import javax.inject.Inject

@AndroidEntryPoint
class MainActivity : ComponentActivity() {
private var isMusicPlayerServiceStarted = false

@Inject
lateinit var connectionHandler: ConnectionHandler

@OptIn(ExperimentalMaterial3WindowSizeClassApi::class)
override fun onCreate(savedInstanceState: Bundle?) {
Log.i("MainActivity", "onCreate")
installSplashScreen()
enableEdgeToEdge()
super.onCreate(savedInstanceState)
Expand All @@ -37,7 +47,7 @@ class MainActivity : ComponentActivity() {
startMediaPlayerService()
setContent {
val windowSizeClass = calculateWindowSizeClass(this)
AppLocalSettingsProvider(windowSizeClass.widthSizeClass) {
AppLocalSettingsProvider(windowSizeClass.widthSizeClass, connectionHandler) {
MetadatorTheme(
darkTheme = LocalDarkTheme.current.isDarkTheme(),
isHighContrastModeEnabled = LocalDarkTheme.current.isHighContrastModeEnabled,
Expand All @@ -51,16 +61,39 @@ class MainActivity : ComponentActivity() {
override fun onDestroy() {
super.onDestroy()
stopService(Intent(this, MediaplayerService::class.java))
unbindService(serviceConnection)
isMusicPlayerServiceStarted = false
}

fun startMediaPlayerService() {
private fun startMediaPlayerService() {
val intent = Intent(this, MediaplayerService::class.java)
if (!isMusicPlayerServiceStarted) {
isMusicPlayerServiceStarted = true
startService(Intent(this, MediaplayerService::class.java))
startService(intent)
bindService(intent, serviceConnection, BIND_AUTO_CREATE)
}
}

private var serviceConnection = object : ServiceConnection {
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
Log.i(
"MainActivity",
"The Music Service is connected. Updating the connection handler."
)
val binder = service as MediaplayerService.MusicBinder
connectionHandler.connect(binder.service.mediaServiceHandler)
}

override fun onServiceDisconnected(name: ComponentName?) {
Log.i(
"MainActivity",
"The Music Service has been disconnected. Detaching the connection handler."
)
connectionHandler.disconnect()
}
}


companion object {
private lateinit var activity: MainActivity
fun getActivity(): MainActivity {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import androidx.navigation.compose.rememberNavController
import coil.ImageLoader
import coil.disk.DiskCache
import coil.memory.MemoryCache
import com.bobbyesp.mediaplayer.service.ConnectionHandler
import com.bobbyesp.metadator.App.Companion.context
import com.bobbyesp.utilities.DarkThemePreference
import com.bobbyesp.utilities.Theme.paletteStyles
Expand All @@ -46,11 +47,14 @@ val LocalWindowWidthState =
val LocalSnackbarHostState = compositionLocalOf<SnackbarHostState> { error("No snackbar host state provided") }
val LocalDrawerState =
compositionLocalOf<DrawerState> { error("No Drawer State has been provided") }
val LocalMediaplayerConnection =
compositionLocalOf<ConnectionHandler> { error("No Media Player Service Connection handler has been provided") }

@OptIn(ExperimentalMaterialNavigationApi::class)
@Composable
fun AppLocalSettingsProvider(
windowWidthSize: WindowWidthSizeClass,
playerConnectionHandler: ConnectionHandler,
content: @Composable () -> Unit
) {
val appSettingsState = AppSettingsStateFlow.collectAsStateWithLifecycle().value
Expand Down Expand Up @@ -95,7 +99,8 @@ fun AppLocalSettingsProvider(
LocalOrientation provides config.orientation,
LocalSnackbarHostState provides snackbarHostState,
LocalCoilImageLoader provides imageLoader,
LocalDrawerState provides drawerState
LocalDrawerState provides drawerState,
LocalMediaplayerConnection provides playerConnectionHandler,
) {
content() //The content of the app
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import androidx.media3.common.MediaItem
import androidx.media3.common.MediaMetadata
import com.bobbyesp.mediaplayer.service.ConnectionHandler
import com.bobbyesp.mediaplayer.service.MediaServiceHandler
import com.bobbyesp.mediaplayer.service.MediaState
import com.bobbyesp.mediaplayer.service.PlayerEvent
Expand All @@ -25,7 +26,8 @@ import javax.inject.Inject
@HiltViewModel
class MediaplayerViewModel @Inject constructor(
@ApplicationContext private val applicationContext: Context,
private val serviceHandler: MediaServiceHandler
private val serviceHandler: MediaServiceHandler,
private val connectionHandler: ConnectionHandler
) : ViewModel() {
private val mutableMediaplayerPageState = MutableStateFlow(MediaplayerPageState())
val pageViewState = mutableMediaplayerPageState.asStateFlow()
Expand Down

0 comments on commit 872ef96

Please sign in to comment.