I'm trying to run exoplayer in a foreground service (not a MediaBrowserServiceCompat).
Here is my service -
#AndroidEntryPoint
class PodcastPlayerService: Service() {
#Inject
lateinit var dataSourceFactory: DefaultDataSourceFactory
#Inject
lateinit var exoPlayer: SimpleExoPlayer
lateinit var podcastNotificationManager: PodcastNotificationManager
lateinit var podcast: Podcast
override fun onDestroy() {
super.onDestroy()
exoPlayer.release()
}
override fun onTaskRemoved(rootIntent: Intent?) {
super.onTaskRemoved(rootIntent)
exoPlayer.stop()
exoPlayer.release()
}
override fun onBind(p0: Intent?): IBinder? = null
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
Log.i("Pit stop", "2")
val b = intent!!.getBundleExtra("test")
if (b != null) {
Log.i("Pit stop", "3")
podcast = b.getParcelable<Podcast>(ArgumentKeyAndValues.KEY_PODCAST)!!
}
val mediaSource = buildMediaSource(Uri.parse("https://something.etc/file.mp3"))
if (mediaSource != null) {
exoPlayer.prepare(mediaSource)
exoPlayer.playWhenReady = true
podcastNotificationManager =
PodcastNotificationManager(
this,
PodcastPlayerNotificationListener(this)
)
podcastNotificationManager.showNotification(exoPlayer)
}
return START_STICKY
}
private fun buildMediaSource(uri: Uri): MediaSource? {
return ProgressiveMediaSource.Factory(dataSourceFactory)
.createMediaSource(uri)
}
}
Notification Listener -
class PodcastPlayerNotificationListener(private val podcastPlayerService: PodcastPlayerService):
PlayerNotificationManager.NotificationListener {
override fun onNotificationPosted(
notificationId: Int,
notification: Notification,
ongoing: Boolean) {
super.onNotificationPosted(notificationId, notification, ongoing)
podcastPlayerService.apply {
if(ongoing) {
ContextCompat.startForegroundService(this,
Intent(applicationContext, this::class.java))
startForeground(OtherConstants.PODCAST_NOTIFICATION_ID, notification)
}
}
}
override fun onNotificationCancelled(notificationId: Int, dismissedByUser: Boolean) {
super.onNotificationCancelled(notificationId, dismissedByUser)
podcastPlayerService.apply {
stopForeground(true)
stopSelf()
}
}
}
Podcast Notification Manager -
class PodcastNotificationManager(private val context: Context,
notificationListener: PlayerNotificationManager.NotificationListener) {
private val notificationManager: PlayerNotificationManager
init {
notificationManager = PlayerNotificationManager.createWithNotificationChannel(
context,
OtherConstants.NOTIFICATION_CHANNEL_ID,
R.string.notification_channel_name,
R.string.notification_channel_description,
OtherConstants.PODCAST_NOTIFICATION_ID,
DescriptionAdapter(),
notificationListener
).apply {
setSmallIcon(R.drawable.exo_icon_play)
}
}
fun showNotification(player: Player) {
notificationManager.setPlayer(player)
}
private inner class DescriptionAdapter : PlayerNotificationManager.MediaDescriptionAdapter {
override fun getCurrentContentTitle(player: Player): String {
val window = player.currentWindowIndex
return "Title"
}
override fun getCurrentContentText(player: Player): String? {
val window = player.currentWindowIndex
return "Description"
}
override fun getCurrentLargeIcon(
player: Player,
callback: BitmapCallback
): Bitmap? = null
override fun createCurrentContentIntent(player: Player): PendingIntent? {
val window = player.currentWindowIndex
return null
}
}
}
Here is how I start the service -
val intent = Intent(context, PodcastPlayerService::class.java)
val serviceBundle = Bundle()
serviceBundle.putParcelable("test", podcast)
intent.putExtra(ArgumentKeyAndValues.KEY_PODCAST, serviceBundle)
context?.let { Util.startForegroundService(it, intent) }
However, when I do this onStartCommand keeps getting called (I'm assuming that the OS keeps killing my service for some reason and START_STICKY forces it to start again) and nothing happens.
If I place the media, notification manager and listener code in onCreate the service works fine.
Where am I going wrong?
Turns out I was starting the foreground service twice -
ContextCompat.startForegroundService(this,
Intent(applicationContext, this::class.java))
startForeground(OtherConstants.PODCAST_NOTIFICATION_ID, notification)
Related
I am getting issue regarding foreground service is that some time foreground being killed by os so should i resolve Service class issue or move to workmanager . i have completed the application and only getting a single os issue suggestion required thanks .
// this is the service class
class SmsService : Service() {
private var wakeLock: PowerManager.WakeLock? = null
private lateinit var smsScheduler : SmsScdeduler
private var isServiceStarted = false
private var notificationBuilder : NotificationCompat.Builder?=null
private var serviceScope = CoroutineScope(Dispatchers.Main)
private var notificationManager : NotificationManager?=null
fun isServiceRunning():Boolean{
return isServiceStarted }
companion object{
const val MESSAGE_ID: String ="messageId"
const val USER_ID:String ="userId"
const val DELIVERED: String ="sb.app.messageschedular.sms_schedulers.DELIVERYKEY"
const val ADD_SERVICE = "sb.spp.message_scheduler.add"
const val DELETE_SERVICE ="sb.spp.message_scheduler.delete"
const val Add_kEY ="ADD_KEY"
const val SENT ="sb.app.messageschedular.sms_schedulers.SENT"
private const val channelId = "default_notification_channel_id"
private const val NOTIFICATION_ID =1995L
}
/************* Delivery BroaddCast ENd **************/
override fun onCreate() {
super.onCreate()
smsScheduler = SmsScdeduler.getInstance(this )
val defaultSoundUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION)
notificationBuilder = NotificationCompat.Builder(this, channelId)
.setSmallIcon(sb.app.messageschedular.R.mipmap.ic_launcher)
.setContentTitle("Message Scheduled")
// .setContentIntent(pendingIntent)
.setAutoCancel(true)
}
#RequiresApi(Build.VERSION_CODES.O)
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
showNotification(NOTIFICATION_ID)
wakeLock =
(getSystemService(Context.POWER_SERVICE) as PowerManager).run {
newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "EndlessService::lock").apply {
acquire()
}
}
println("intent ${intent }")
isServiceStarted =true
if(intent!=null) {
val action = intent?.action
val sms = intent?.getParcelableExtra<Sms>(Add_kEY)
schedule(sms!!)
}else{
serviceScope.launch(Dispatchers.IO) {
smsScheduler.reSchedule()
}
}
return START_STICKY }
#RequiresApi(Build.VERSION_CODES.O)
private fun showNotification(messageid:Long ) {
notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val channel = NotificationChannel(channelId, "test1", NotificationManager.IMPORTANCE_DEFAULT)
channel.description = "test otificaiton"
notificationManager?.createNotificationChannel(channel)
}
this.startForeground(messageid.toInt() ,notificationBuilder!!.build())
}
override fun onBind(intent: Intent?): IBinder? {
return LocalBinder() }
inner class LocalBinder : Binder(){
fun getService(): SmsService = this#SmsService
}
override fun onDestroy() {
super.onDestroy()
isServiceStarted =false
if( serviceScope.isActive){
println("Service Scope is cancelled ")
serviceScope.cancel() }
}
}
fun finish() {
println("service finished ")
println("Stop self")
println("Stop foreground service")
stopForeground(true )
println("service state false ")
isServiceStarted =false
stopSelf()
}
}
// calling service
fun scheduleService(sms: Sms) {
if(!mService.isServiceRunning()){
val intent = Intent(this , SmsService::class.java)
intent.action = SmsService.ADD_SERVICE
intent.putExtra(SmsService.Add_kEY, sms)
ContextCompat.startForegroundService(this.applicationContext,intent)
}
/// Permission of Service
<service android:name=".service.SmsService"
android:foregroundServiceType="dataSync"
/>
Hello friends. I use this code inside the service for the pedometer service and I will
encounter the following error. Thank you for your help.
*error
android.app.RemoteServiceException: Context.startForegroundService() did not then call
Service.startForeground()
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2220)
at android.os.Handler.dispatchMessage(Handler.java:109)
at android.os.Looper.loop(Looper.java:166)
at android.app.ActivityThread.main(ActivityThread.java:7555)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:469)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:963)
class MyService : Service(), SensorEventListener {
private var sensorManager: SensorManager? = null
private var running = false
private var totalStep = 0f
private var previousTotalStep = 0f
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
try {
running = true
val stepSensor = sensorManager?.getDefaultSensor(Sensor.TYPE_STEP_COUNTER)
sensorManager?.registerListener(this, stepSensor, SensorManager.SENSOR_DELAY_UI)
} catch (e: Exception) {
}
return START_STICKY
}
override fun onBind(p0: Intent?): IBinder? {
return null
}
override fun onSensorChanged(event: SensorEvent?) {
if (running) {
totalStep = event!!.values[0]
val currentSteps = totalStep.toInt() - previousTotalStep.toInt()
Constant.editor(this).putFloat(STEPNUMBER, previousTotalStep).apply()
}
}
override fun onAccuracyChanged(p0: Sensor?, p1: Int) {
}
override fun stopService(name: Intent?): Boolean {
return super.stopService(name)
}
override fun onDestroy() {
val intent = Intent(this, MyPhoneReciver::class.java)
sendBroadcast(intent)
super.onDestroy()
}
}
The reason for this crash is :
-> From Android 9 Pie if your service does not call startForeground within 5 seconds after it has been started with the command startForegroundService ... then it produces an ANR + Crash.
Solution : After starting the service with startForegroundService, call startForeground() in the service’s onCreate method.
Android O you can set the background limitation as below;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
context!!.startForegroundService(Intent(context, MyService::class.java))
} else {
context!!.startService(Intent(context, MyService::class.java))
}
I am trying to download a video for offline playing in exoplayer, but I don't know how to listen for onDownloadComplete. In the exoplayer docs they say DownloadService is a wrap around android DownloadManager so I try to listen for DownloadManager.ACTION_DOWNLOAD_COMPLETE broadcast but it's not working, actually this is my first time using exoplayer.
Download Service
class MediaDownloadService : DownloadService(
C.DOWNLOAD_NOTIFICATION_ID, DEFAULT_FOREGROUND_NOTIFICATION_UPDATE_INTERVAL,
C.CHANNEL_ID, R.string.channel_name, R.string.channel_description
) {
override fun onCreate() {
registerReceiver(onComplete, IntentFilter(ACTION_DOWNLOAD_COMPLETE))
super.onCreate()
}
override fun onDestroy() {
unregisterReceiver(onComplete)
super.onDestroy()
}
override fun getDownloadManager(): DownloadManager {
return DownloadUtil.getDownloadManager(this)
}
override fun getForegroundNotification(downloads: MutableList<Download>): Notification {
val intent = Intent(this, MainActivity::class.java)
val pendingIntent = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)
val notificationHelper = DownloadNotificationHelper(this, C.CHANNEL_ID)
return notificationHelper.buildProgressNotification(
R.drawable.ic_notification,
pendingIntent,
"simple message",
downloads
)
}
override fun getScheduler(): Scheduler? {
return null
}
val onComplete: BroadcastReceiver = object : BroadcastReceiver() {
override fun onReceive(ctxt: Context?, intent: Intent?) {
toast("Download COmpleted")
}
}
}
You can compare bytesDownloaded and contentLength to check if it's finish downloading.
downloadManager.addListener(object : DownloadManager.Listener {
override fun onDownloadChanged(downloadManager: DownloadManager, download: Download) {
if (download.bytesDownloaded == download.contentLength) {
Log.d("Completed")
}
}
})
I am trying to implement ExoPlayer's Notification Manager, it works pretty well but I do not want to show fast rewind and fast forward buttons. I checked documentation but can not find a way to hide these button. Is there any tricky way to hide them?
Here is my code
private fun initListener() {
val playerNotificationManager: PlayerNotificationManager
val notificationId = 1234
val mediaDescriptionAdapter = object : PlayerNotificationManager.MediaDescriptionAdapter {
override fun getCurrentSubText(player: Player?): String {
return "Sub text"
}
override fun getCurrentContentTitle(player: Player): String {
return "Title"
}
override fun createCurrentContentIntent(player: Player): PendingIntent? {
return null
}
override fun getCurrentContentText(player: Player): String {
return "ContentText"
}
override fun getCurrentLargeIcon(
player: Player,
callback: PlayerNotificationManager.BitmapCallback
): Bitmap? {
return null
}
}
playerNotificationManager = PlayerNotificationManager.createWithNotificationChannel(
context,
"My_channel_id",
R.string.app_name,
notificationId,
mediaDescriptionAdapter,
object : PlayerNotificationManager.NotificationListener {
override fun onNotificationPosted(notificationId: Int, notification: Notification, ongoing: Boolean) {}
override fun onNotificationCancelled(notificationId: Int, dismissedByUser: Boolean) {}
})
playerNotificationManager.setUseNavigationActions(false)
playerNotificationManager.setUseNavigationActionsInCompactView(false)
playerNotificationManager.setVisibility(View.VISIBLE)
playerNotificationManager.setPlayer(mPlayer)
}
You can set rewindIncrementMs and fastForwardIncrementMs to 0 to hide the buttons.
The link to the JavaDoc you posted above explaines this: https://exoplayer.dev/doc/reference/com/google/android/exoplayer2/ui/PlayerNotificationManager.html
playerNotificationManager.setRewindIncrementMs(0);
playerNotificationManager.setFastForwardIncrementMs(0);
You can do this in ExoPlayer 2.15.0 -
playerNotificationManager.setUseFastForwardAction(false)
playerNotificationManager.setUseFastForwardActionInCompactView(false)
playerNotificationManager.setUseRewindAction(false)
playerNotificationManager.setUseRewindActionInCompactView(false)
I have an app that uses Service class to perform task in foreground.
This service also contains a Handler object to run same function multiple times. I want to change attributes in my activity_main.xml while functions are running in Service. For example when function calculates something in Service the result prints in TextView.
How it would be correct access activity_main's objects to retrieve and change their values and attributes?
Here is what I have:
MainActivity.kt:
class MainActivity : AppCompatActivity() {
private var notificationManager: NotificationManager? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
buttonStart.setOnClickListener{
buttonStart.isEnabled = false
buttonStop.isEnabled = true
IdListener.startService(this, "Foreground Service is running...")
}
}
}
IdListener.kt:
class IdListener : Service() {
private val CHANNEL_ID = "ForegroundService Kotlin"
private lateinit var mainHandler: Handler
private lateinit var mRunnable: Runnable
companion object {
fun startService(context: Context, message: String) {
val startIntent = Intent(context, IdListener::class.java)
startIntent.putExtra("inputExtra", message)
ContextCompat.startForegroundService(context, startIntent)
}
fun stopService(context: Context) {
val stopIntent = Intent(context, IdListener::class.java)
context.stopService(stopIntent)
}
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
mainHandler = Handler()
mRunnable = Runnable { showRandomNumber(tm) }
mainHandler.postDelayed(mRunnable, 1000)
val input = intent?.getStringExtra("inputExtra")
createNotificationChannel()
val notificationIntent = Intent(this, MainActivity::class.java)
val pendingIntent = PendingIntent.getActivity(
this,
0, notificationIntent, 0
)
val notification = NotificationCompat.Builder(this, CHANNEL_ID)
.setContentTitle("Foreground Service Kotlin Example")
.setContentText(input)
.setSmallIcon(R.drawable.ic_notofication)
.setContentIntent(pendingIntent)
.build()
startForeground(1, notification)
return START_NOT_STICKY
}
override fun onBind(intent: Intent): IBinder? {
return null
}
override fun onDestroy() {
super.onDestroy()
mainHandler.removeCallbacks(mRunnable)
}
private fun createNotificationChannel() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val serviceChannel = NotificationChannel(CHANNEL_ID, "Foreground Service Channel",
NotificationManager.IMPORTANCE_DEFAULT)
val manager = getSystemService(NotificationManager::class.java)
manager!!.createNotificationChannel(serviceChannel)
}
}
/// function in which I want elements from activity_main.xml to be changed
fun showRandomNumber(manager: TelephonyManager){
myTextView.text = "Working..."
mainHandler.postDelayed(mRunnable, 1000)
}
}
Here's how I'd probably handle your case. I don't know exactly what you're doing, but I'm just having the text view show "Working..." when it starts the service until there's an ID available. I haven't tested this and haven't worked with services in a long time, so you might want other input.
object IdServiceData {
val id = MutableLiveData<String>()
}
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
//...
myTextView.text = "Working..." // should really use string resource here.
IdServiceData.id.observe(this) {
myTextView.text = it.value
}
}
}
When an Activity or Fragment observes a LiveData, they automatically stop observing when they are destroyed, so they are not leaked. So your Activity can be destroyed and recreated multiple times while the Service is running and it will keep getting the proper updates.
class IdListener : Service() {
//...
private fun broadcastNewId(id: String){
mainHandler.post {
IdServiceData.id.value = id
}
}
}
If you want better encapsulation, I suppose you could abstract out the MutableLiveData by creating a separate IdServiceDataProvider that has the MutableLiveData and is used by the service, and the IdServiceData would reference the data like this: val id: LiveData<String> = IdServiceDataProvider.id