Reputation: 467
I am developing an audio player app that works well but I am facing one problem if I minimize the app it kill my foreground service. I don't know why it's happening can anyone suggest me any solution
MainActivity
import android.Manifest
import android.app.PendingIntent
import android.content.*
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.os.Handler
import android.os.IBinder
import android.support.v4.media.MediaDescriptionCompat
import android.support.v4.media.MediaMetadataCompat
import android.support.v4.media.session.MediaSessionCompat
import android.text.Html
import android.util.Log
import android.view.WindowManager
import android.widget.SeekBar
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.isVisible
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentTransaction
import androidx.localbroadcastmanager.content.LocalBroadcastManager
import com.brahmakumaris.fragment.*
import com.brahmakumaris.model.getDashboard.RecentSong
import com.brahmakumaris.service.MusicPlayerService
import com.brahmakumaris.service.MyService
import com.brahmakumaris.util.*
import com.google.android.exoplayer2.DefaultLoadControl
import com.google.android.exoplayer2.ExoPlaybackException
import com.google.android.exoplayer2.Player
import com.google.android.exoplayer2.SimpleExoPlayer
import com.google.android.exoplayer2.ext.mediasession.MediaSessionConnector
import com.google.android.exoplayer2.ext.mediasession.TimelineQueueNavigator
import com.google.android.exoplayer2.extractor.DefaultExtractorsFactory
import com.google.android.exoplayer2.source.ConcatenatingMediaSource
import com.google.android.exoplayer2.source.ProgressiveMediaSource
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector
import com.google.android.exoplayer2.ui.PlayerNotificationManager
import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory
import com.google.android.exoplayer2.util.Util
import kotlinx.android.synthetic.main.activity_main.*
import java.util.*
import kotlin.collections.ArrayList
class MainActivity : AppCompatActivity(), OnFragmentInteractionListener {
override fun onFragmentInteraction(screen: String, model: RecentSong, songList: ArrayList<RecentSong>, position: Int) {
MyLog.e(TAG, screen)
when (screen) {
getString(R.string.speakers) -> {
bottom_navigation.selectedItemId = R.id.nav_speaker
supportFragmentManager.switch(
newFrag = SpeakerPageFragment.newInstance(),
tag = getString(R.string.speakers)
)
}
getString(R.string.classes) -> {
bottom_navigation.selectedItemId = R.id.nav_class
supportFragmentManager.switch(
newFrag = ClassesPageFragment.newInstance(),
tag = getString(R.string.classes)
)
}
getString(R.string.songs) -> {
bottom_navigation.selectedItemId = R.id.nav_song
supportFragmentManager.switch(
newFrag = SongsPageFragment.newInstance(),
tag = getString(R.string.songs)
)
}
getString(R.string.play_song) -> {
if(!model.name.isNullOrBlank()) {
songPosition = position
mSongList = songList
MyLog.e(TAG, "======= ${getString(R.string.play_song)} ${model.name}")
isPlaying = false // to start new song
// play song
if (!lnPlayer.isVisible) {
lnPlayer.isVisible = true
}
titleTxt.setHtmlText(model.name)
if(mBound) {
startService()
mMusicPlayerService.playSong(mSongList,songPosition)
media_button.setImageResource(R.drawable.ic_pause_black_24dp)
}
setPlayPause(!mMusicPlayerService.isPlaying())
initSeekBar()
}
}
else -> {
}
}
}
private val TAG = javaClass.simpleName
private var isPlaying = false
private var mediaSource: ProgressiveMediaSource? = null
// notification
private lateinit var mediaSessionConnector: MediaSessionConnector
private lateinit var mediaSession: MediaSessionCompat
private lateinit var playerNotificationManager: PlayerNotificationManager
private var handler: Handler? = null
private val dashUrl = "http://www.panacherock.com/downloads/mp3/01_All_Tangled_Up.mp3"
var mSongList: ArrayList<RecentSong> = ArrayList()
var songPosition = 0
val MESSAGE_KEY = "message_key"
private lateinit var mMusicPlayerService:MusicPlayerService
private lateinit var mPlayer: SimpleExoPlayer
private var mBound = false
private val mServiceConnection: ServiceConnection = object : ServiceConnection {
override fun onServiceConnected(componentName: ComponentName, iBinder: IBinder) {
val binder = iBinder as MusicPlayerService.MyServiceBinder
mMusicPlayerService = binder.getService()
mPlayer = mMusicPlayerService.getPlayerInstance()!!
mPlayer.addListener(playerListener)
mBound = true
MyLog.e(TAG,"onServiceConnected: ")
}
override fun onServiceDisconnected(componentName: ComponentName) {
mBound = false
MyLog.e(TAG,"onServiceDisconnected: ") // calles only in rare case if service destroy unexpectedly
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
if (savedInstanceState == null) {
supportFragmentManager.switch(
newFrag = DashboardPageFragment.newInstance(),
tag = getString(R.string.home)
)
}
getIntentData()
bottom_navigation.setOnNavigationItemSelectedListener { item ->
hideKeybord()
when(item.itemId) {
R.id.nav_home -> {
checkFragment()
supportFragmentManager.switch(
newFrag = DashboardPageFragment.newInstance(),
tag = getString(R.string.home)
)
true
}
R.id.nav_song -> {
checkFragment()
supportFragmentManager.switch(
newFrag = SongsPageFragment.newInstance(),
tag = getString(R.string.songs)
)
true
}
R.id.nav_speaker -> {
checkFragment()
supportFragmentManager.switch(
newFrag = SpeakerPageFragment.newInstance(),
tag = getString(R.string.speakers)
)
true
}
R.id.nav_class -> {
checkFragment()
supportFragmentManager.switch(
newFrag = ClassesPageFragment.newInstance(),
tag = getString(R.string.classes)
)
true
}
R.id.nav_search -> {
checkFragment()
supportFragmentManager.switch(
newFrag = SearchPageFragment.newInstance(),
tag = getString(R.string.search)
)
true
}
else -> false
}
}
bottom_navigation.setOnNavigationItemReselectedListener { } //disable reselection tab
media_button.setOnClickListener {
// startService()
setPlayPause(!isPlaying)
}
imgClose.setOnClickListener {
isPlaying = true // to stop song
playerNotificationManager.setPlayer(null)
setPlayPause(!isPlaying)
if (lnPlayer.isVisible) {
lnPlayer.isVisible = false
}
}
}
private fun getIntentData() {
if (intent!=null && intent.hasExtra("internet") && !intent.getBooleanExtra("internet", false)) {
this.changeFragment(
DownloadPageFragment.newInstance(), Constants.DOWNLOAD_TAG
)
}
}
private fun checkFragment() {
val fragment = getSupportFragmentManager().findFragmentByTag(Constants.SUB_CATEGORY_TAG)
if (fragment != null) {
getSupportFragmentManager().beginTransaction().remove(fragment).commit()
}
val fragment2 = getSupportFragmentManager().findFragmentByTag(Constants.DOWNLOAD_TAG)
if (fragment2 != null) {
getSupportFragmentManager().beginTransaction().remove(fragment2).commit()
}
}
private val playerListener by lazy {
object : Player.EventListener {
override fun onPlayerError(error: ExoPlaybackException) {
super.onPlayerError(error)
//onError(error)
}
override fun onPlayerStateChanged(playWhenReady: Boolean, playbackState: Int) {
super.onPlayerStateChanged(playWhenReady, playbackState)
when (playbackState) {
Player.STATE_BUFFERING -> Log.e(TAG,"STATE_BUFFERING")
Player.STATE_ENDED -> Log.e(TAG,"STATE_ENDED")
Player.STATE_IDLE -> Log.e(TAG,"STATE_IDLE")
Player.STATE_READY -> {
// setPlayPause(playWhenReady)
isPlaying = playWhenReady
mPlayer.setPlayWhenReady(playWhenReady)
if (!isPlaying) {
media_button.setImageResource(R.drawable.ic_play_arrow_black_24dp)
} else {
setProgress()
media_button.setImageResource(R.drawable.ic_pause_black_24dp)
}
if (playWhenReady) {
Log.e(TAG, "PlaybackStatus.PLAYING")
} else {
Log.e(TAG, "PlaybackStatus.PAUSED")
}
titleTxt.setHtmlText(mSongList[mPlayer.currentWindowIndex].name)
MyLog.e(TAG, "====== " + mSongList[mPlayer.currentWindowIndex].name)
MyLog.e(TAG, "======111 " + mSongList[mPlayer.currentWindowIndex].descripation)
}
else -> Log.e(TAG, "PlaybackStatus.IDLE")
}
}
}
}
private fun startService() {
val myService = Intent(this, MusicPlayerService ::class.java)
Util.startForegroundService(this,myService)
}
private fun stopService() {
val myService = Intent(this, MusicPlayerService ::class.java)
stopService(myService)
}
private fun setPlayPause(play: Boolean) {
if(mBound) {
if(mMusicPlayerService.isPlaying()) {
mMusicPlayerService.pause()
media_button.setImageResource(R.drawable.ic_play_arrow_black_24dp)
} else {
startService()
playerNotificationManager = mMusicPlayerService.getNotificationInstance()!!
/* if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
startForegroundService(intent)
} else {
startService(intent)
}*/
mMusicPlayerService.play()
setProgress()
media_button.setImageResource(R.drawable.ic_pause_black_24dp)
}
}
/*isPlaying = play
exoPlayer.setPlayWhenReady(play)
if (!isPlaying) {
media_button.setImageResource(R.drawable.ic_play_arrow_black_24dp)
} else {
setProgress()
media_button.setImageResource(R.drawable.ic_pause_black_24dp)
}*/
}
private fun stringForTime(timeMs: Int): String {
val mFormatBuilder: StringBuilder
val mFormatter: Formatter
mFormatBuilder = StringBuilder()
mFormatter = Formatter(mFormatBuilder, Locale.getDefault())
val totalSeconds = timeMs / 1000
val seconds = totalSeconds % 60
val minutes = totalSeconds / 60 % 60
val hours = totalSeconds / 3600
mFormatBuilder.setLength(0)
return if (hours > 0) {
mFormatter.format("%d:%02d:%02d", hours, minutes, seconds).toString()
} else {
mFormatter.format("%02d:%02d", minutes, seconds).toString()
}
}
private fun setProgress() {
seekPlayerProgress.progress = 0
seekPlayerProgress.max = mPlayer.getDuration().toInt() / 1000
position.setText(stringForTime(mPlayer.getCurrentPosition().toInt()))
duration.setText(stringForTime(mPlayer.getDuration().toInt()))
if (handler == null) handler = Handler()
//Make sure you update Seekbar on UI thread
handler!!.post(object : Runnable {
override fun run() {
if (mPlayer != null && ::mPlayer.isInitialized && isPlaying) {
seekPlayerProgress.max = mPlayer.getDuration().toInt() / 1000
val mCurrentPosition = mPlayer.getCurrentPosition().toInt() / 1000
seekPlayerProgress.progress = mCurrentPosition
position.setText(stringForTime(mPlayer.getCurrentPosition().toInt()))
duration.setText(stringForTime(mPlayer.getDuration().toInt()))
handler!!.postDelayed(this, 1000)
}
}
})
}
private fun initSeekBar() {
seekPlayerProgress.requestFocus()
seekPlayerProgress.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener {
override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {
if (!fromUser) {
// We're not interested in programmatically generated changes to
// the progress bar's position.
return
}
mPlayer.seekTo((progress * 1000).toLong())
}
override fun onStartTrackingTouch(seekBar: SeekBar) {
}
override fun onStopTrackingTouch(seekBar: SeekBar) {
}
})
seekPlayerProgress.setMax(0)
seekPlayerProgress.setMax(mPlayer.getDuration().toInt() / 1000)
}
private var doubleBackToExitPressedOnce = false
override fun onBackPressed() {
val fragment = getSupportFragmentManager().findFragmentByTag(Constants.SUB_CATEGORY_TAG)
if (fragment != null) {
getSupportFragmentManager().beginTransaction().remove(fragment).commit()
return
}
val fragment2 = getSupportFragmentManager().findFragmentByTag(Constants.DOWNLOAD_TAG)
if (fragment2 != null) {
getSupportFragmentManager().beginTransaction().remove(fragment2).commit()
return
}
if (doubleBackToExitPressedOnce) {
super.onBackPressed()
return
}
this.doubleBackToExitPressedOnce = true
this.showSnackBarToast("Please click BACK again to exit")
Handler().postDelayed(Runnable { doubleBackToExitPressedOnce = false }, 2000)
}
override fun onDestroy() {
if(::playerNotificationManager.isInitialized) {
playerNotificationManager.setPlayer(null)
}
killPlayer()
// stopService()
MyLog.e(TAG," #### onDestroy #### ")
super.onDestroy()
}
override fun onStop() {
super.onStop()
if(mBound) {
unbindService(mServiceConnection)
mBound = false
}
LocalBroadcastManager.getInstance(this)
.unregisterReceiver(broadCastReceiver)
MyLog.e(TAG," #### onStop #### ")
}
private fun killPlayer() {
if (mPlayer != null) {
mPlayer.release()
mediaSource = null
}
MyLog.e(TAG,"#### killPlayer ####")
}
private fun getHtmlText(str:String):String {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
return (Html.fromHtml(str, Html.FROM_HTML_MODE_COMPACT)).toString()
} else {
return (Html.fromHtml(str)).toString()
}
}
private fun setMediaSession() {
mediaSession = MediaSessionCompat(this@MainActivity,"MEDIA_SESSION_TAG")
mediaSession.isActive = true
playerNotificationManager.setMediaSessionToken(mediaSession.sessionToken) //enhance media stye notification and provide artwork in lock screen
mediaSessionConnector = MediaSessionConnector(mediaSession)
//timeline is the internal representation of the pllaylist after the player has been prepared
mediaSessionConnector.setQueueNavigator(object : TimelineQueueNavigator(mediaSession){
override fun getMediaDescription(player: Player?, windowIndex: Int): MediaDescriptionCompat {
return getMediaDescriptionData(this@MainActivity, mSongList[windowIndex])
}
})
mediaSessionConnector.setPlayer(exoPlayer/*, null*/) //sync player wth media session
}
fun getMediaDescriptionData(context: Context, sample: RecentSong): MediaDescriptionCompat {
val extras = Bundle()
val options = BitmapFactory.Options()
options.inSampleSize = 8
// val bitmap = BitmapFactory.decodeFile(getBitmap(context, sample.bitmapResource), options)
val bitmap = context.getBitmap(R.drawable.bg)
extras.putParcelable(MediaMetadataCompat.METADATA_KEY_ALBUM_ART, bitmap)
extras.putParcelable(MediaMetadataCompat.METADATA_KEY_DISPLAY_ICON, bitmap)
return MediaDescriptionCompat.Builder()
.setMediaId(sample.song)
.setIconBitmap(bitmap)
.setTitle(sample.name)
.setDescription(sample.descripation)
.setExtras(extras)
.build()
}
override fun onStart() {
super.onStart()
bindService(Intent(this,MusicPlayerService::class.java),mServiceConnection,Context.BIND_AUTO_CREATE)
LocalBroadcastManager.getInstance(this)
.registerReceiver(broadCastReceiver, IntentFilter(MusicPlayerService().MUSIC_COMPLETED))
}
val broadCastReceiver = object : BroadcastReceiver() {
override fun onReceive(contxt: Context?, intent: Intent?) {
MyLog.e(TAG,"onReceive: ${intent?.getBooleanExtra(MESSAGE_KEY,false)}")
if(intent!!.getBooleanExtra(MESSAGE_KEY,false)) {
setPlayPause(intent.getBooleanExtra(MESSAGE_KEY,false))
if (intent.getBooleanExtra(MESSAGE_KEY,false)) {
Log.e(TAG, "PlaybackStatus.PLAYING")
} else {
Log.e(TAG, "PlaybackStatus.PAUSED")
}
titleTxt.setHtmlText(mSongList[mPlayer.currentWindowIndex].name)
MyLog.e(TAG,"====== "+mSongList[mPlayer.currentWindowIndex].name)
MyLog.e(TAG,"======111 "+mSongList[mPlayer.currentWindowIndex].descripation)
}
}
}
}
MusicPlayerService
import android.app.PendingIntent
import android.app.Service
import android.content.Context
import android.content.Intent
import android.graphics.Bitmap
import android.graphics.drawable.Drawable
import android.net.Uri
import android.os.Binder
import android.os.IBinder
import androidx.localbroadcastmanager.content.LocalBroadcastManager
import com.brahmakumaris.MainActivity
import com.brahmakumaris.R
import com.brahmakumaris.model.getDashboard.RecentSong
import com.brahmakumaris.util.MyLog
import com.brahmakumaris.util.getBitmap
import com.brahmakumaris.util.getPlaintextfromHtmlHtmlText
import com.bumptech.glide.Glide
import com.bumptech.glide.request.target.CustomTarget
import com.bumptech.glide.request.transition.Transition
import com.google.android.exoplayer2.Player
import com.google.android.exoplayer2.SimpleExoPlayer
import com.google.android.exoplayer2.extractor.DefaultExtractorsFactory
import com.google.android.exoplayer2.source.ConcatenatingMediaSource
import com.google.android.exoplayer2.source.ProgressiveMediaSource
import com.google.android.exoplayer2.ui.PlayerNotificationManager
import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory
import com.google.android.exoplayer2.util.Util
class MusicPlayerService : Service() {
private val TAG = javaClass.simpleName
private val mContext: Context = this
private val mBinder = MyServiceBinder()
val MUSIC_COMPLETED = "music completed"
val mPlayer: SimpleExoPlayer by lazy { SimpleExoPlayer.Builder(this).build() }
private lateinit var playerNotificationManager: PlayerNotificationManager
var mSongList: ArrayList<RecentSong> = ArrayList()
override fun onCreate() {
super.onCreate()
MyLog.d(TAG,"onCreate: ")
}
fun playSong(mSongList: ArrayList<RecentSong> = ArrayList(), position: Int = 0)/* mContext: Context = this*/ {
this.mSongList = mSongList
val dataSourceFactory = DefaultDataSourceFactory(mContext, Util.getUserAgent(mContext,getString(R.string.app_name)))
val concateMediaSource = ConcatenatingMediaSource()
for (i in mSongList) {
val mediaSource = ProgressiveMediaSource
.Factory(
DefaultDataSourceFactory(mContext, dataSourceFactory),
DefaultExtractorsFactory()
)
.createMediaSource(/*i.uri*/Uri.parse(i.musicFile))
concateMediaSource.addMediaSource(mediaSource)
}
mPlayer.prepare(concateMediaSource)
mPlayer.seekToDefaultPosition(position)
mPlayer.playWhenReady =true
setNotification()
}
private fun setNotification() {
playerNotificationManager = PlayerNotificationManager.createWithNotificationChannel(
this,"channel_id",R.string.channelName,R.string.channelDescription,11,
object : PlayerNotificationManager.MediaDescriptionAdapter{
override fun createCurrentContentIntent(player: Player): PendingIntent? {
val intent = Intent(mContext, MainActivity::class.java)
return PendingIntent.getActivity(mContext,0,intent, PendingIntent.FLAG_UPDATE_CURRENT)
}
override fun getCurrentContentText(player: Player): String? {
// return mSongList[player.currentWindowIndex].descripation // descrption
return mSongList[player.currentWindowIndex].descripation.getPlaintextfromHtmlHtmlText() // descrption
}
override fun getCurrentContentTitle(player: Player): String {
// return mSongList[player.currentWindowIndex].name // title
return mSongList[player.currentWindowIndex].name.getPlaintextfromHtmlHtmlText()
}
override fun getCurrentLargeIcon(
player: Player,
callback: PlayerNotificationManager.BitmapCallback
): Bitmap? {
return mContext.getBitmap(R.drawable.bg)
}
} ,object : PlayerNotificationManager.NotificationListener {
override fun onNotificationStarted(notificationId: Int, notification: Notification) {
startForeground(notificationId,notification)
}
override fun onNotificationPosted(notificationId: Int, notification: Notification, ongoing: Boolean) {
startForeground(notificationId,notification)
}
override fun onNotificationCancelled(notificationId: Int, dismissedByUser: Boolean) {
stopSelf()
}
override fun onNotificationCancelled(notificationId: Int) {
stopSelf()
}
}
)
//show hide button
playerNotificationManager.setUseStopAction(false) //stop song
playerNotificationManager.setRewindIncrementMs(0) //hide rewind button
playerNotificationManager.setFastForwardIncrementMs(0) //hide fast forward button
playerNotificationManager.setPlayer(mPlayer)
}
inner class MyServiceBinder : Binder() {
fun getService(): MusicPlayerService = this@MusicPlayerService
}
fun getPlayerInstance(): SimpleExoPlayer? {
return mPlayer
}
fun getNotificationInstance(): PlayerNotificationManager? {
return playerNotificationManager
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
MyLog.d(TAG,"onStartCommand: ")
return START_NOT_STICKY
}
override fun onBind(intent: Intent?): IBinder? {
MyLog.d(TAG,"onBind: ")
return mBinder
}
override fun onUnbind(intent: Intent?): Boolean {
MyLog.d(TAG,"onUnbind: ")
return true
}
override fun onRebind(intent: Intent?) {
MyLog.d(TAG,"onRebind: ")
super.onRebind(intent)
}
override fun onDestroy() {
MyLog.e(TAG,"onDestroy: ")
super.onDestroy()
mPlayer.release()
if(::playerNotificationManager.isInitialized) {
playerNotificationManager.setPlayer(null)
}
}
// public client method
fun isPlaying():Boolean {
return mPlayer.isPlaying
}
fun play() {
mPlayer.setPlayWhenReady(true)
}
fun pause() {
mPlayer.setPlayWhenReady(false)
}
}
if I minimize the app then it's printing unbind and then it directly going to onDestroy
why it's happening can anyone help me,
Any Help Would Be Highly Appreciated.
Upvotes: 0
Views: 883
Reputation: 1751
I experienced this issue because I set the player on notification manager to null on onStop which causes onNotificationCancelled to be called, you should check, if the notification was canceled by the user then you should stop the service, else leave it running.
override fun onNotificationCancelled(notificationId: Int, dismissedByUser:Boolean)
{
if (dismissedByUser) {
stopSelf()
}
}
Upvotes: 0
Reputation: 467
I just remove onStop unbind and unregister code and set in onDestroy and it's working for me.
Upvotes: 1
Reputation: 663
You should be using startForeground() with a notification, to Android service that never stops running.
startForeground(1, notification)
Please have a look at this. https://robertohuertas.com/2019/06/29/android_foreground_services/
I hope this will help you.
Upvotes: 0