I have been trying to play audio using exoplayer media3 lib in background. So, far I was able to play the audio and it is playing in background too but I have kept url to be a test url which is static. Now, I want to pass the url to my service class which I am not able to understand how to do.
Attaching my code files here..
class PlaybackService : MediaSessionService() {
// Create your Player and MediaSession in the onCreate lifecycle event
lateinit var player: Player
private var mediaSession: MediaSession? = null
override fun onCreate() {
super.onCreate()
player = ExoPlayer.Builder(this).build()
mediaSession = MediaSession.Builder(this, player).build()
player.prepare()
player.playWhenReady = true
player.seekTo(0, 0)
val mediaItem =
MediaItem.fromUri("https://www.learningcontainer.com/wp-content/uploads/2020/05/sample-mp4-file.mp4")
player.setMediaItem(mediaItem)
}
// Return a MediaSession to link with the MediaController that is making
// this request.
override fun onGetSession(controllerInfo: MediaSession.ControllerInfo): MediaSession? =
mediaSession
override fun onDestroy() {
mediaSession?.run {
player.release()
release()
mediaSession = null
}
}
}
Here is my fragment
class MusicPlayerFragment : BaseFragment() {
private lateinit var controllerFuture: ListenableFuture<MediaController>
private val controller: MediaController?
get() = if (controllerFuture.isDone) controllerFuture.get() else null
private val currentWindow = 0
var playerStopped = true
private val playbackPosition: Long = 0
override fun onStart() {
super.onStart()
playMedia()
}
private fun playMedia() {
val sessionToken = SessionToken(
requireContext(),
ComponentName(requireContext(), PlaybackService::class.java)
)
controllerFuture =
MediaController.Builder(requireContext(), sessionToken)
.buildAsync()
controllerFuture.addListener({
fragmentMusicPlayerBinding.playerView.player = controllerFuture.get()
fragmentMusicPlayerBinding.controls.player = controllerFuture.get()
}, MoreExecutors.directExecutor())
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
fragmentMusicPlayerBinding.imgPlay.setOnClickListener {
if (playerStopped) {
playerStopped = false
playMedia()
}
}
fragmentMusicPlayerBinding.imgStop.setOnClickListener {
stopMediaPlayer()
}
fragmentMusicPlayerBinding.imgPause.setOnClickListener {
fragmentMusicPlayerBinding.playerView.player?.pause()
}
fragmentMusicPlayerBinding.imgRewind.setOnClickListener {
fragmentMusicPlayerBinding.playerView.player?.seekTo(0)
}
}
private fun stopMediaPlayer() {
controllerFuture.get().stop()
controllerFuture.get().playWhenReady = false
MediaController.releaseFuture(controllerFuture)
playerStopped = true
fragmentMusicPlayerBinding.playerView.player = null
}
}
Problems I am facing
I want to set media uri dynamically which I will get from api. So, how to do that?
Using this code I get a mediapplayer notification in my notification center, when I play or pause from there music get stops or play but at the same time when I stop from notification and play from fragment music doesn't plays.
Whenever I stop music from fragment, then too music keeps playing in background and notification is still there
When I clear out the music notification from the notification center then also music keeps playing.
Please help me out with the solutions to the problem I facing if anyone have faced any. TIA
After many tries and lots of research, I found out the answer to my own question. Posting the answer as it can help someone out in the community finding the same answer. No hard feelings but after 3 days I am posting the answer to my own question but no one here came to help me out.
Note: Exoplayer(Mediaplayer) used in my code is media3 lib which is in beta mode. Code is tested in android 11 and android 12 and is working fine. So, go ahead and use it.
This youtube video helped me a lot with my answer.
I modified my code slightly as per the instructions in video and here is my final code.
class MusicPlayerActivity : BaseActivity() {
lateinit var musicPlayerBinding: ActivityMusicPlayerBinding
var player: Player? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
musicPlayerBinding = ActivityMusicPlayerBinding.inflate(layoutInflater)
setContentView(musicPlayerBinding.root)
bindMusicService()
}
override fun onBackPressed() {
unbindService(playerServiceConnection)
if (player?.isPlaying!!) {
player?.stop()
}
player?.release()
player = null
super.onBackPressed()
}
private fun bindMusicService() {
val intent = Intent(this, PlayerService::class.java)
bindService(intent, playerServiceConnection, Context.BIND_AUTO_CREATE)
}
private val playerServiceConnection = object : ServiceConnection {
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
val binder = service as PlayerService.ServiceBinder
player = binder.getPlayerService().player!!
player?.prepare()
player?.playWhenReady = false
player?.seekTo(0, 0)
val mediaMetaData = MediaMetadata.Builder()
.setAlbumTitle(MusicPlayerData.audioTitle)
.build()
val mediaItem =
MediaItem.Builder().setMediaMetadata(mediaMetaData)
.setUri(MusicPlayerData.audioUri)
.build()
player?.setMediaItem(mediaItem)
setPlayerControls()
}
override fun onServiceDisconnected(name: ComponentName?) {
TODO("Not yet implemented")
}
}
private fun setPlayerControls() {
musicPlayerBinding.playerView.player = player
musicPlayerBinding.controls.player = player
player?.addListener(object : Player.Listener {
override fun onPlaybackStateChanged(playbackState: Int) {
super.onPlaybackStateChanged(playbackState)
when (playbackState) {
Player.STATE_BUFFERING -> show(musicPlayerBinding.progressCircular)
Player.STATE_READY -> {
player?.playWhenReady = true
hide(musicPlayerBinding.progressCircular)
}
}
}
})
}
}
Here is my service class
class PlayerService : Service() {
private val iBinder = ServiceBinder()
var player: Player? = null
private var mediaSession: MediaSession? = null
lateinit var notificationManager: PlayerNotificationManager
inner class ServiceBinder : Binder() {
fun getPlayerService(): PlayerService = this@PlayerService
}
override fun onBind(intent: Intent?): IBinder {
return iBinder
}
override fun onCreate() {
super.onCreate()
player = ExoPlayer.Builder(this).build()
mediaSession =
MediaSession.Builder(this, player!!).setSessionActivity(pendingIntent()!!).setId(Random(5).toString())
.build()
notificationManager = PlayerNotificationManager.Builder(this, 111, "Music Channel")
.setChannelImportance(IMPORTANCE_HIGH)
.setSmallIconResourceId(R.drawable.music)
.setChannelDescriptionResourceId(R.string.app_name)
.setChannelNameResourceId(R.string.app_name)
.setMediaDescriptionAdapter(audioDescriptor)
.setNotificationListener(notificationListener)
.build()
notificationManager.setPlayer(player)
notificationManager.setPriority(PRIORITY_MAX)
notificationManager.setUseRewindAction(true)
notificationManager.setUseFastForwardAction(false)
notificationManager.setUsePreviousAction(false)
notificationManager.setUsePlayPauseActions(true)
}
override fun onDestroy() {
if (player?.isPlaying!!) {
player?.stop()
}
notificationManager.setPlayer(null)
player?.release()
player = null
stopForeground(true)
stopSelf()
super.onDestroy()
}
private val notificationListener = object : PlayerNotificationManager.NotificationListener {
override fun onNotificationCancelled(notificationId: Int, dismissedByUser: Boolean) {
super.onNotificationCancelled(notificationId, dismissedByUser)
stopForeground(true)
if (player?.isPlaying!!) {
player?.stop()
player?.release()
}
}
override fun onNotificationPosted(
notificationId: Int,
notification: Notification,
ongoing: Boolean
) {
super.onNotificationPosted(notificationId, notification, ongoing)
startForeground(notificationId, notification)
}
}
private val audioDescriptor = object : PlayerNotificationManager.MediaDescriptionAdapter {
override fun getCurrentContentTitle(player: Player): CharSequence {
return player.currentMediaItem?.mediaMetadata?.albumTitle!!
}
override fun createCurrentContentIntent(player: Player): PendingIntent? {
return pendingIntent()
}
override fun getCurrentContentText(player: Player): CharSequence? {
return ""
}
override fun getCurrentLargeIcon(
player: Player,
callback: PlayerNotificationManager.BitmapCallback
): Bitmap? {
val bitmapDrawable: BitmapDrawable =
ContextCompat.getDrawable(
applicationContext,
R.drawable.cma_logo_render
) as BitmapDrawable
return bitmapDrawable.bitmap
}
}
private fun pendingIntent(): PendingIntent? {
val intent = Intent(applicationContext, MusicPlayerActivity::class.java)
return PendingIntent.getActivity(
applicationContext,
0,
intent,
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)
}
Problems I am facing
I want to set media uri dynamically which I will get from api. So, how to do that? -----> No need of setting the media uri in service class, we can get instance of player from service class in our activity and do all the functionalities in activity class only
Using this code I get a mediapplayer notification in my notification center, when I play or pause from there music get stops or play but at the same time when I stop from notification and play from fragment music doesn't plays. ----->problem solved after modifying my service class
Whenever I stop music from fragment, then too music keeps playing in background and notification is still there ----->problem solved after modifying my service class
When I clear out the music notification from the notification center then also music keeps playing.
-----> for this I have set session activity to open activity from the current playing state
After implementing the things like above,