I am using the latest version of Exoplayer (2.16.1). I create my foreground service notification using the code below but it comes with a notification sound. How can I prevent this sound?
override fun onCreate() {
super.onCreate()
playerNotificationManager = PlayerNotificationManager
.Builder(this, 9998, NOTIFICATION_CHANNEL_ID)
.setMediaDescriptionAdapter(PlayerNotificationAdapter())
.setChannelImportance(IMPORTANCE_HIGH)
.setNotificationListener(object : PlayerNotificationManager.NotificationListener {
override fun onNotificationPosted(
notificationId: Int,
notification: Notification,
ongoing: Boolean
) {
super.onNotificationPosted(notificationId, notification, ongoing)
if (ongoing) {
startForeground(notificationId, notification)
}
}
override fun onNotificationCancelled(
notificationId: Int,
dismissedByUser: Boolean
) {
super.onNotificationCancelled(notificationId, dismissedByUser)
stopSelf()
stopForeground(false)
}
})
.setChannelNameResourceId(R.string.NotificationChannelName)
.setChannelDescriptionResourceId(R.string.NotificationChannelDescription)
.build()
playerNotificationManager.setSmallIcon(R.drawable.ic_play)
playerNotificationManager.setPriority(PRIORITY_MAX)
playerNotificationManager.setPlayer(musicPlayerManager.exoPlayer)
mediaSessionCompat = MediaSessionCompat(this, MEDIA_SESSION_TAG)
mediaSessionCompat.isActive = true
playerNotificationManager.setMediaSessionToken(mediaSessionCompat.sessionToken)
mediaSessionConnector = MediaSessionConnector(mediaSessionCompat)
val timelineQueueNavigator = object : TimelineQueueNavigator(mediaSessionCompat) {
override fun getMediaDescription(
player: Player,
windowIndex: Int
): MediaDescriptionCompat {
return MediaDescriptionCompat.Builder().build()
}
}
mediaSessionConnector.setQueueNavigator(timelineQueueNavigator)
mediaSessionConnector.setPlayer(musicPlayerManager.exoPlayer)
}
Please note that I want to use PlayerNotificationManager of Exoplayer to create the notification.
you can set : playerNotificationManager.setPriority(PRIORITY_DEFAULT) to make silent notification in this case
Related
I build a web radio player with Media3 1.0.0-beta03. I use the sample code from
Developers page.
It's generated a media notification automatically but I don't know how to add Title and sub title to this.
Here is my media service:
class PlaybackService : MediaSessionService(), MediaSession.Callback {
private object LC {
lateinit var exoPlayer: ExoPlayer
lateinit var mediaSession: MediaSession
}
override fun onCreate() {
super.onCreate()
log("----------------------------- MediaSessionService, onCreate")
LC.exoPlayer = ExoPlayer.Builder(this).build()
LC.exoPlayer.addListener(ExoListener())
LC.exoPlayer.setAudioAttributes(AudioAttributes.Builder().setContentType(AUDIO_CONTENT_TYPE_MUSIC).setUsage(USAGE_MEDIA).build(),true)
LC.mediaSession = MediaSession.Builder(this, LC.exoPlayer).setCallback(this).build()
}
override fun onGetSession(controllerInfo: MediaSession.ControllerInfo): MediaSession = LC.mediaSession
override fun onAddMediaItems(mediaSession: MediaSession, controller: MediaSession.ControllerInfo, mediaItems: MutableList<MediaItem>): ListenableFuture<MutableList<MediaItem>> {
val updatedMediaItems = mediaItems.map { it.buildUpon().setUri(it.mediaId).build() }.toMutableList()
return Futures.immediateFuture(updatedMediaItems)
}
override fun onDestroy() {
log("----------------------------- MediaSessionService, onDestroy")
LC.exoPlayer.stop()
LC.exoPlayer.release()
LC.mediaSession.release()
super.onDestroy()
exitProcess(0)
}
}
I tryed the onUpdateNotification
Yesss, thank you TG. Kahsay
Notification manager is not needed.
class PlaybackService : MediaSessionService(), MediaSession.Callback {
private object LC {
lateinit var exoPlayer: ExoPlayer
lateinit var mediaSession: MediaSession
}
#SuppressLint("UnsafeOptInUsageError")
override fun onCreate() {
super.onCreate()
log("----------------------------- MediaSessionService, onCreate")
LC.exoPlayer = ExoPlayer.Builder(this).build()
LC.exoPlayer.addListener(BackgroundService())
LC.exoPlayer.setAudioAttributes(AudioAttributes.Builder().setContentType(AUDIO_CONTENT_TYPE_MUSIC).setUsage(USAGE_MEDIA).build(),true)
LC.mediaSession = MediaSession.Builder(this, LC.exoPlayer).setCallback(this).build()
setMediaNotificationProvider(object : MediaNotification.Provider{
override fun createNotification(
mediaSession: MediaSession,
customLayout: ImmutableList<CommandButton>,
actionFactory: MediaNotification.ActionFactory,
onNotificationChangedCallback: MediaNotification.Provider.Callback
): MediaNotification {
// This run every time when I press buttons on notification bar:
return updateNotification(mediaSession)
}
override fun handleCustomCommand(session: MediaSession, action: String, extras: Bundle): Boolean { return false }
})
}
#SuppressLint("UnsafeOptInUsageError")
private fun updateNotification(session: MediaSession): MediaNotification {
val notify = NotificationCompat.Builder(this,"Radio")
.setSmallIcon(R.drawable.ic_launcher_foreground)
// This is globally changed every time when
// I add a new MediaItem from background service
.setContentTitle(GL.MEDIA.radio)
.setContentText(GL.MEDIA.artist)
.setStyle(MediaStyleNotificationHelper.MediaStyle(session))
.build()
return MediaNotification(1, notify)
}
override fun onGetSession(controllerInfo: MediaSession.ControllerInfo): MediaSession = LC.mediaSession
override fun onAddMediaItems(mediaSession: MediaSession, controller: MediaSession.ControllerInfo, mediaItems: MutableList<MediaItem>): ListenableFuture<MutableList<MediaItem>> {
val updatedMediaItems = mediaItems.map { it.buildUpon().setUri(it.mediaId).build() }.toMutableList()
return Futures.immediateFuture(updatedMediaItems)
}
override fun onDestroy() {
log("----------------------------- MediaSessionService, onDestroy")
LC.exoPlayer.stop()
LC.exoPlayer.release()
LC.mediaSession.release()
super.onDestroy()
}
}
Update :
There is also another way, which I found out does the Job better.
In onCreate() function of MediaSessionService, We can set a MediaNotificationProvider like so.
private lateinit var nBuilder: NotificationCompat.Builder
override fun onCreate(){
super.onCreate()
// init notificationCompat.Builder before setting the MediaNotificationProvider
this.setMediaNotificationProvider(object : MediaNotification.Provider{
override fun createNotification(
mediaSession: MediaSession,// this is the session we pass to style
customLayout: ImmutableList<CommandButton>,
actionFactory: MediaNotification.ActionFactory,
onNotificationChangedCallback: MediaNotification.Provider.Callback
): MediaNotification {
createNotification(mediaSession)
// notification should be created before you return here
return MediaNotification(NOTIFICATION_ID,nBuilder.build())
}
override fun handleCustomCommand(
session: MediaSession,
action: String,
extras: Bundle
): Boolean {
TODO("Not yet implemented")
}
})
}
fun createNotification(session: MediaSession) {
val notificationManager: NotificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
notificationManager.createNotificationChannel(NotificationChannel(notification_id,"Channel", NotificationManager.IMPORTANCE_LOW))
// NotificationCompat.Builder here.
nBuilder = NotificationCompat.Builder(this,notification_id)
// Text can be set here
// but I believe setting MediaMetaData to MediaSession would be enough.
// I havent tested it deeply yet but did display artist from session
.setSmallIcon(R.drawable.your_drawable)
.setContentTitle("your Content title")
.setContentText("your content text")
// set session here
.setStyle(MediaStyleNotificationHelper.MediaStyle(session))
// we don build.
}
and finally if you want to update notification info yourself
you can do so by calling a function like this..
private fun updateNotification(/*parameter*/){
nBuilder.setContentTitle("text")
nBuilder.setContentText("subtext")
nManager.notify(NOTIFICATION_ID,nBuilder.build())
}
Turns out, its very simple.
There is a method to override in MediaService class called onUpdateNotification(). it provides the media session for us.
so we can override it and create our own NotificationCompat
// Override this method in your service
override fun onUpdateNotification(session: MediaSession) {
createNotification(session) //calling method where we create notification
}
and in our createNotification() method we create the notification and set its style with a MediaStyleHelper.MediaStyle() set the session parameter there
like in the following.
and create the notification as always
fun createNotification(session: MediaSession) {
val notificationManager: NotificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
notificationManager.createNotificationChannel(NotificationChannel(notification_id,"Channel", NotificationManager.IMPORTANCE_LOW))
// NotificationCompat here.
val notificationCompat = NotificationCompat.Builder(this,notification_id)
// Text can be set here
// but I believe setting MediaMetaData to MediaSession would be enough.
// I havent tested it deeply yet but did display artist from session
.setSmallIcon(R.drawable.your_drawable)
.setContentTitle("your Content title")
.setContentText("your content text")
// set session here
.setStyle(MediaStyleNotificationHelper.MediaStyle(session))
.build()
notificationManager.notify(1,notificationCompat)
}
I hope this helps and isn't too late.
Edit:
and another cleaner option is to just Create MediaItem wit desired MediaMetaData and add it to ExoPlayer. If source is Hls, try not adding title to MediaMetaData.
I'm creating a countdown timer and I want to notify the user all the time that the countdown timer runs. Therefore I have created a service which is started by a ViewModel. I use Hilt for dependency injection as I want to inject the service into the ViewModel. Additionally the UI library is jetpack compose. Following is my approach.
This is my service.
#AndroidEntryPoint
class TimerService: Service(){
override fun onBind(p0: Intent?): IBinder? = null
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
Notifications.createNotification(applicationContext)
return super.onStartCommand(intent, flags, startId)
}
}
This is how notifications are created.
object Notifications {
private var notificationId = UUID.randomUUID().hashCode()
fun createNotification(context: Context){
val notification = NotificationCompat.Builder(context, "ChannelId")
.setSmallIcon(R.drawable.ic_launcher_background)
.setContentTitle("Timer started")
.setContentText("Timer started and running...")
.build()
NotificationManagerCompat.from(context).notify(notificationId, notification)
}
fun createNotificationChannel(context: Context){
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O){
val name = "timerNotifyChannel"
val description = "Timer Notification Channel"
val importance = NotificationManager.IMPORTANCE_HIGH
// The notification channel
val channel = NotificationChannel("ChannelId", name, importance).apply {
description
}
val notificationManager : NotificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
notificationManager.createNotificationChannel(channel)
}
}
}
And a manager for starting and stopping the service.
class TimerServiceManager #Inject constructor(
#ApplicationContext private val applicationContext: Context,
){
private val serviceIntent = Intent(applicationContext, TimerService::class.java)
fun startTimerService(){
ContextCompat.startForegroundService(applicationContext, serviceIntent)
}
fun stopTimerService(){
applicationContext.stopService(serviceIntent)
}
}
The Application.
#HiltAndroidApp
class TimerApp: Application(){}
The TimerViewModel, which starts the service whenever the service is started.
#HiltViewModel
class TimerViewModel #Inject constructor(
private val timerServiceManager: TimerServiceManager,
): ViewModel() {
//...
fun startcountDown(){
//...
countDownTimer = object : CountDownTimer(...){...}
countDownTimer?.start()
timerServiceManage.startTimerService()
}
private fun cancelTimer(){
countDownTimer?.cancel()
_isRunning.postValue(false)
timerServiceManager.stopTimerService()
}
}
And the MainActivity
#AndroidEntryPoint
class MainActivity : ComponentActivity() {
// private val timerViewModel by viewModels<TimerViewModel>()
private val timeViewModel by viewModels<TimeViewModel>()
// val timeViewModel: TimeViewModel = ViewModelProvider(checkNotNull(LocalViewModelStoreOwner.current))[TimeViewModel::class.java]
override fun onCreate(savedInstanceState: Bundle?) {
//...
val timerViewModel = hiltViewModel<TimerViewModel>()
callUI(timerViewModel = timerViewModel, timeViewModel, localConfig = LocalConfiguration.current)
}
}
The problem is when I debug I can see that it calls the ContextCompact.startForegrounService(...) withing theTimerServiceManager.startTimerService() function. But it doesn't start the service. I say it doesn't start because when I put a break point in the onCreated(...) method in the TimerService it's never reached. Why is this failing? What's wrong?
After weeks of trial-and-errors I could show notifications from my app. I was being dumb at the first place for not allowing the application to access notifications in the emulator. So if you're also having this issue make sure your application has access to the notifications.
However I have also made some changes. I don't know whether the app working because of these changes or not.
I removed the Notificationobject and then add that functionality to the TimerService.
#AndroidEntryPoint
class TimerService: Service() {
private lateinit var notificationManager: NotificationManagerCompat
private lateinit var notification: Notification
override fun onCreate() {
// the notification channel creates when the Service is created
super.onCreate()
notificationManager = NotificationManagerCompat.from(this)
createNotificationChannel()
}
#RequiresApi(Build.VERSION_CODES.N)
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
// when the service starts, a notification will be created
notification = NotificationCompat.Builder(this, TIMER_SERVICE_NOTIFICATION_CHANNEL_ID)
.setContentTitle("Timer service")
.setContentText("Timer running")
.setSmallIcon(R.drawable.ic_launcher_background)
.setOngoing(true) // an ongoing notification means can't dismiss by the user.
.setOnlyAlertOnce(true)
.build()
startForeground(TIMER_SERVICE_NOTIFICATION_ID, notification)
return START_STICKY
}
private fun createNotificationChannel(){
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O){
val serviceChannel = NotificationChannel(
TIMER_SERVICE_NOTIFICATION_CHANNEL_ID,
getString(R.string.app_name),
NotificationManager.IMPORTANCE_HIGH
)
notificationManager.createNotificationChannel(serviceChannel)
}
}
#RequiresApi(Build.VERSION_CODES.N)
override fun onDestroy() {
super.onDestroy()
stopForeground(STOP_FOREGROUND_REMOVE)
}
override fun onBind(p0: Intent?): IBinder? = null // We don't need a binder
companion object {
public const val TIMER_SERVICE_NOTIFICATION_CHANNEL_ID = "TimerServiceChannel"
public const val TIMER_SERVICE_NOTIFICATION_ID = 69
}
}
Currently, I need a bound (Music)Service, because I need to interact with it. But I also want it to not be stopped, even when all components have unbound themselves.
My service code:
class ServicePlayer : LifecycleService() {
var mediaPlayer: MediaPlayer? = null
var notificationManager: NotificationManager? = null
var notificationBuilder: NotificationCompat.Builder? = null
private val mBinder: IBinder = PlayerBinder()
private val NOTIFICATION_ID = 1111
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
super.onStartCommand(intent, flags, startId)
return START_REDELIVER_INTENT
}
inner class PlayerBinder : Binder() {
val service: ServicePlayer
get() = this#ServicePlayer
}
override fun onBind(intent: Intent): IBinder? {
super.onBind(intent)
return mBinder
}
override fun onCreate() {
super.onCreate()
mediaPlayer = MediaPlayer()
mediaPlayer!!.setOnCompletionListener(this)
mediaPlayer!!.setOnBufferingUpdateListener(this)
mediaPlayer!!.setOnErrorListener(this)
val filter = IntentFilter()
filter.addAction(TelephonyManager.ACTION_PHONE_STATE_CHANGED)
filter.addAction(Intent.ACTION_SCREEN_ON)
registerReceiver(receiver, filter)
}
override fun onDestroy() {
super.onDestroy()
mediaPlayer!!.reset()
mediaPlayer!!.release()
Log.i("DESTROY SERVICE", "destroy")
unregisterReceiver(receiver)
}
fun play(trackIndex: Int, tracks: ArrayList<Track>?) {
...
val intent = Intent(BUFFERING)
this#ServicePlayer.sendBroadcast(intent)
}
fun pause() {
if (mediaPlayer!!.isPlaying) {
mediaPlayer!!.pause()
PlayerLiveData.isPlaying.value = false
val intent = Intent(UPDATE_UI)
this#ServicePlayer.sendBroadcast(intent)
//Show notification
CoroutineScope(Dispatchers.Default).launch {
showNotification()
}
}
}
private fun hideNotification() {
notificationManager!!.cancel(NOTIFICATION_ID)
stopForeground(true)
}
private fun showNotification() {
notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
...
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val CHANNEL_ID = "controls_channel_id"
val CHANNEL_NAME = "Play tracks"
val channel = NotificationChannel(CHANNEL_ID,CHANNEL_NAME, NotificationManager.IMPORTANCE_LOW)
...
val mMediaSession = MediaSessionCompat(applicationContext, getString(R.string.app_name))
mMediaSession.setFlags(
MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS or
MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS
)
notificationManager!!.createNotificationChannel(channel)
notificationBuilder = NotificationCompat.Builder(applicationContext)
.setChannelId(CHANNEL_ID)
.setContentText(artistText)
.setContentTitle(track.title)
...
} else {
notificationBuilder = NotificationCompat.Builder(applicationContext)
...
notificationBuilder!!
.setSmallIcon(R.drawable.ic_notification)
.setContentIntent(contentIntent)
.setCustomContentView(remoteSmallViews)
.setCustomBigContentView(remoteViews)
}
CoroutineScope(Dispatchers.Default).launch {
val notification = notificationBuilder!!.build()
startForeground(NOTIFICATION_ID, notification)
val notificationTarget = NotificationTarget(
applicationContext
, R.id.imgThumb, remoteViews
, notification, NOTIFICATION_ID
)
...
lifecycleScope.launch {
val request = ImageRequest.Builder(applicationContext)
.data(thumb)
.error(R.drawable.placeholder_song)
.placeholder(R.drawable.placeholder_song)
.build()
val drawable = imageLoader.execute(request).drawable
val bitmap = (drawable as BitmapDrawable).bitmap
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
notificationBuilder!!.setLargeIcon(bitmap)
val notification = notificationBuilder!!.build()
notificationManager!!.notify(NOTIFICATION_ID,notification)
//Start Foreground service
startForeground(NOTIFICATION_ID, notification)
}
}
}
}
}
Manifest file declaration:
<service android:name=".services.ServicePlayer" android:enabled="true" android:exported="true"/>
Using service in activity
class MainActivity : AppCompatActivity() {
lateinit var binding: MainActivityBinding
private lateinit var audioPlayerService: ServicePlayer
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val intent = Intent(this, ServicePlayer::class.java)
bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE)
binding = DataBindingUtil.setContentView(this, R.layout.main_activity)
binding.lifecycleOwner = this
binding.viewmodel = mainViewModel
}
private val serviceConnection: ServiceConnection = object : ServiceConnection {
override fun onServiceDisconnected(name: ComponentName) {
// audioPlayerService = null;
}
override fun onServiceConnected(name: ComponentName, service: IBinder) {
audioPlayerService = (service as ServicePlayer.PlayerBinder).service
if (audioPlayerService.trackIndex !== -1) {
//updatePlaybackUI()
}
}
}
}
How can I keep my service running in background even after activity destroyed. I refer few threads of StackOverflow but they are not helpful.
Use Service instead LifecycleService as parent class.
Add partial wake lock start and stop calls to onCreate and onDestroy methods respectively.
private val powerManager
get() = (this.getSystemService(Context.POWER_SERVICE) as PowerManager)
private var wakeLock: PowerManager.WakeLock? = null
private fun startWakeLock() {
if (wakeLock == null) {
wakeLock = powerManager.newWakeLock(
PowerManager.PARTIAL_WAKE_LOCK,
"${packageName}:wakeLock"
)
wakeLock?.acquire()
}
}
private fun stopWakeLock() {
if (wakeLock?.isHeld == true) {
wakeLock?.release()
wakeLock = null
}
}
Add the following tag to service at mainfest
android:foregroundServiceType="mediaPlayback"
You should start service as foreground from the activity
A bound service stops once every client unbinds from it, and that happens automatically when the client (your Activity) is destroyed
If your client is still bound to a service when your app destroys the client, destruction causes the client to unbind. It is better practice to unbind the client as soon as it is done interacting with the service. Doing so allows the idle service to shut down.
If you want the service to just keep running, a Started Service will do that. You can still bind to it, but it won't stop until you explicitly tell it to stop and there are no clients bound.
Honestly though, if you're making some kind of media player, you'll probably want to use the MediaBrowserServiceCompat framework. It allows you to create a service that plays nice with MediaBrowser (which does the binding, among other things) and lets you use a MediaSession to get a media notification with controls and all that.
A few links about that stuff:
MediaBrowserServiceCompat and the modern media playback app by Ian Lake from the Android team
Background Audio in Android With MediaSessionCompat - Java but gets into a lot of the nonsense you'll have to wrangle
Developer docs about building media apps - a few sections here and it's all kinda spread out, I feel like the other links give a better overview
If you don't care about any of that then startService/startForegroundService (or ContextCompat#startForegroundService) will get you a service that just runs, but those links might give you some pointers about stuff
I'm creating an audiobook player, and I'm using MediaSessionCompat related classes to handle notifications. My code is heavily inspired by the android-MediaBrowserService samples ( https://github.com/googlearchive/android-MediaBrowserService ), and i'm not quite understanding it all for the moment ( the createContentIntent espicially )
Here is my simple class in charge of building notifications from a bookPlayer providing metadata and playbackstate data
class PlayerNotificationManager(val playerService: PlayerService) {
val CHANNEL_ID = "pylvain.gamma"
val NOTIFICATION_ID = 412
private val REQUEST_CODE = 501
val notificationManager =
playerService.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
private val playAction: NotificationCompat.Action =
NotificationCompat.Action (
android.R.drawable.ic_media_pause,
"PAUSE",
buildMediaButtonPendingIntent(
playerService,
PlaybackStateCompat.ACTION_PAUSE
)
)
fun getNotification(bookPlayer: BookPlayer): Notification {
if (isAndroidOOrHigher()) createChannel()
val style = androidx.media.app.NotificationCompat.MediaStyle()
.setMediaSession(playerService.sessionToken)
.setShowCancelButton(true)
.setShowActionsInCompactView(0)
.setCancelButtonIntent(
buildMediaButtonPendingIntent(
playerService,
PlaybackStateCompat.ACTION_STOP
)
)
val builder = NotificationCompat.Builder(playerService, CHANNEL_ID)
.addAction(playAction)
.setStyle(style)
.setSmallIcon(R.drawable.ic_music_rest_quarter)
.setContentIntent(createContentIntent())
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
return builder.build()
}
private fun createContentIntent(): PendingIntent { //TODO: Understand that
Timber.i("Creating Intent")
val openUI = Intent(playerService, MainActivity::class.java)
openUI.flags = Intent.FLAG_ACTIVITY_SINGLE_TOP
return PendingIntent.getActivity(
playerService, 0, openUI, PendingIntent.FLAG_UPDATE_CURRENT
)
}
The notification is showing perfectly fine with metadata
Here is my MediaBrowserService handling the media session, where I registered the callbacks. The bookplayer is constructed and injected with Koin. :
class PlayerService : MediaBrowserServiceCompat() {
private lateinit var playerNotificationManager: PlayerNotificationManager
lateinit var session: MediaSessionCompat
private val bookPlayer: BookPlayer by inject()
override fun onCreate() {
super.onCreate()
session = MediaSessionCompat(this, "MusicService")
session.apply {
setFlags(
MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS or
MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS
)
setPlaybackState(bookPlayer.playbackState)
setMetadata(bookPlayer.getMetadata())
setCallback(callbacks)
setActive(true)
}
setSessionToken(session.sessionToken)
playerNotificationManager = PlayerNotificationManager(this)
val notification = playerNotificationManager.getNotification(bookPlayer)
startForeground(playerNotificationManager.NOTIFICATION_ID, notification)
}
override fun onTaskRemoved(rootIntent: Intent?) { //TODO
super.onTaskRemoved(rootIntent)
stopSelf()
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
return super.onStartCommand(intent, flags, startId)
}
override fun onGetRoot(
clientPackageName: String,
clientUid: Int,
rootHints: Bundle?
): BrowserRoot? {
return BrowserRoot("root", null)
}
override fun onLoadChildren(
parentId: String, result: Result<MutableList<MediaBrowserCompat.MediaItem>>
) {
result.sendResult(null);
}
override fun onDestroy() {
session.release()
}
val callbacks = object : MediaSessionCompat.Callback() {
override fun onCommand(command: String?, extras: Bundle?, cb: ResultReceiver?) {
Timber.i("Test")
super.onCommand(command, extras, cb)
}
override fun onPrepare() {
Timber.i("Preparing")
}
override fun onPlay() {
Timber.i("Playing")
bookPlayer.pause()
}
override fun onPause() {
Timber.i("Pausing")
bookPlayer.pause()
}
override fun onSkipToNext() {}
override fun onSkipToPrevious() {}
override fun onStop() {}
override fun onSeekTo(pos: Long) {}
override fun onMediaButtonEvent(mediaButtonIntent: Intent): Boolean = true
}
}
Then the service is started from the main activity with
startService(Intent(mainContext, PlayerService::class.java))
I also added this to my Android manifest
<service android:name=".playerservice.PlayerService">
<intent-filter>
<action android:name="android.media.browse.MediaBrowserService" />
</intent-filter>
</service>
<receiver android:name="androidx.media.session.MediaButtonReceiver">
<intent-filter>
<action android:name="android.intent.action.MEDIA_BUTTON" />
</intent-filter>
</receiver>
None of the callbacks are called whenever I push the button. when i do, the app log the following text :
D/MediaBrowserCompat: Connecting to a MediaBrowserService.
and nothing happens ...
I've searched the entire internet and I'm completely clueless, but it's surely something simple. Can someone help me ? Thank you very much in advance <3
The callback worked ... Just not the way intended. It turns out that the play action button was calling
override fun onMediaButtonEvent(mediaButtonIntent: Intent): Boolean = true
I deleted the function, and ... It works ...
Thank you for your attention !
If you want to get media button, you have to play something.
Try to play dummy audio, when your service is started
// play dummy audio
AudioTrack at = new AudioTrack(AudioManager.STREAM_MUSIC, 48000, AudioFormat.CHANNEL_OUT_STEREO, AudioFormat.ENCODING_PCM_16BIT,
AudioTrack.getMinBufferSize(48000, AudioFormat.CHANNEL_OUT_STEREO, AudioFormat.ENCODING_PCM_16BIT), AudioTrack.MODE_STREAM);
at.play();
// a little sleep
at.stop();
at.release();
https://stackoverflow.com/a/50678833/9891730 - this is my answer before
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")
}
}
})