From 6954fc0990be65468682676318f1e18c313c4151 Mon Sep 17 00:00:00 2001 From: Christian Schabesberger Date: Thu, 26 Sep 2024 00:45:00 +0200 Subject: [PATCH] make notification customizable --- .idea/icon.svg | 21 ++++++ misc/icon.svg | 63 ++++++++++++++++++ misc/tinny_CoolS.svg | 62 ++++++++++++++++++ misc/tiny_icon.svg | 64 +++++++++++++++++++ .../java/net/newpipe/newplayer/NewPlayer.kt | 2 + .../net/newpipe/newplayer/NewPlayerImpl.kt | 5 ++ .../newplayer/service/MediaNotification.kt | 51 +++++++++++++++ .../newplayer/service/NewPlayerService.kt | 42 +++++++++++- .../newplayer/ui/audioplayer/BottomUI.kt | 2 - .../res/drawable/new_player_tiny_icon.xml | 26 ++++++++ new-player/src/main/res/values/strings.xml | 2 + .../newplayer/testapp/NewPlayerComponent.kt | 14 ++-- .../src/main/res/drawable/tinny_cools.xml | 5 ++ 13 files changed, 349 insertions(+), 10 deletions(-) create mode 100644 misc/icon.svg create mode 100644 misc/tinny_CoolS.svg create mode 100644 misc/tiny_icon.svg create mode 100644 new-player/src/main/java/net/newpipe/newplayer/service/MediaNotification.kt create mode 100644 new-player/src/main/res/drawable/new_player_tiny_icon.xml create mode 100644 test-app/src/main/res/drawable/tinny_cools.xml diff --git a/.idea/icon.svg b/.idea/icon.svg index e2cd577..5b985eb 100644 --- a/.idea/icon.svg +++ b/.idea/icon.svg @@ -1,3 +1,24 @@ + + + diff --git a/misc/icon.svg b/misc/icon.svg new file mode 100644 index 0000000..7c5960c --- /dev/null +++ b/misc/icon.svg @@ -0,0 +1,63 @@ + + + + + + diff --git a/misc/tinny_CoolS.svg b/misc/tinny_CoolS.svg new file mode 100644 index 0000000..83eed06 --- /dev/null +++ b/misc/tinny_CoolS.svg @@ -0,0 +1,62 @@ + + + + diff --git a/misc/tiny_icon.svg b/misc/tiny_icon.svg new file mode 100644 index 0000000..6072c87 --- /dev/null +++ b/misc/tiny_icon.svg @@ -0,0 +1,64 @@ + + + + + + + diff --git a/new-player/src/main/java/net/newpipe/newplayer/NewPlayer.kt b/new-player/src/main/java/net/newpipe/newplayer/NewPlayer.kt index ebe619f..bad5b80 100644 --- a/new-player/src/main/java/net/newpipe/newplayer/NewPlayer.kt +++ b/new-player/src/main/java/net/newpipe/newplayer/NewPlayer.kt @@ -20,6 +20,7 @@ package net.newpipe.newplayer +import androidx.core.graphics.drawable.IconCompat import androidx.media3.common.MediaItem import androidx.media3.common.Player import kotlinx.coroutines.flow.MutableStateFlow @@ -54,6 +55,7 @@ interface NewPlayer { val preferredVideoVariants: List val preferredAudioVariants: List val preferredStreamLanguage: List + val notificationIcon: IconCompat val exoPlayer: StateFlow var playWhenReady: Boolean diff --git a/new-player/src/main/java/net/newpipe/newplayer/NewPlayerImpl.kt b/new-player/src/main/java/net/newpipe/newplayer/NewPlayerImpl.kt index 6f94599..9a39d0b 100644 --- a/new-player/src/main/java/net/newpipe/newplayer/NewPlayerImpl.kt +++ b/new-player/src/main/java/net/newpipe/newplayer/NewPlayerImpl.kt @@ -24,6 +24,7 @@ import android.app.Application import android.content.ComponentName import android.util.Log import androidx.annotation.OptIn +import androidx.core.graphics.drawable.IconCompat import androidx.media3.common.AudioAttributes import androidx.media3.common.C import androidx.media3.common.MediaItem @@ -63,6 +64,10 @@ class NewPlayerImpl( override val preferredStreamLanguage: List = emptyList(), override val preferredAudioVariants: List = emptyList(), val httpDataSourceFactory: HttpDataSource.Factory = DefaultHttpDataSource.Factory(), + override val notificationIcon: IconCompat = IconCompat.createWithResource( + app, + R.drawable.new_player_tiny_icon + ), ) : NewPlayer { private val mutableExoPlayer = MutableStateFlow(null) diff --git a/new-player/src/main/java/net/newpipe/newplayer/service/MediaNotification.kt b/new-player/src/main/java/net/newpipe/newplayer/service/MediaNotification.kt new file mode 100644 index 0000000..d7aa95d --- /dev/null +++ b/new-player/src/main/java/net/newpipe/newplayer/service/MediaNotification.kt @@ -0,0 +1,51 @@ +package net.newpipe.newplayer.service + +import android.app.Notification +import android.app.NotificationChannel +import android.app.NotificationManager +import android.os.Build +import androidx.annotation.OptIn +import androidx.core.app.NotificationCompat +import androidx.core.graphics.drawable.IconCompat +import androidx.media3.common.util.UnstableApi +import androidx.media3.session.MediaSession +import androidx.media3.session.MediaStyleNotificationHelper +import net.newpipe.newplayer.R + +const val NEW_PLAYER_MEDIA_NOTIFICATION_ID = 17480 +const val NEW_PLAYER_MEDIA_NOTIFICATION_CHANNEL_NAME = "Player" + +@OptIn(UnstableApi::class) +fun createNewPlayerNotification( + service: NewPlayerService, + session: MediaSession, + notificationManager: NotificationManager, + notificationIcon: IconCompat +): Notification { + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + notificationManager.createNotificationChannel( + NotificationChannel( + NEW_PLAYER_MEDIA_NOTIFICATION_CHANNEL_NAME, + NEW_PLAYER_MEDIA_NOTIFICATION_CHANNEL_NAME, + NotificationManager.IMPORTANCE_LOW + ) + ) + } + + + val notificationBuilder = + NotificationCompat.Builder(service, NEW_PLAYER_MEDIA_NOTIFICATION_CHANNEL_NAME) + .setContentTitle(service.resources.getString(R.string.new_player_name)) + .setContentText(service.resources.getString(R.string.playing_in_background)) + .setStyle(MediaStyleNotificationHelper.MediaStyle(session)) + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + notificationBuilder.setSmallIcon(notificationIcon) + } else { + notificationBuilder + .setSmallIcon(R.drawable.new_player_tiny_icon) + } + + return notificationBuilder.build() +} \ No newline at end of file diff --git a/new-player/src/main/java/net/newpipe/newplayer/service/NewPlayerService.kt b/new-player/src/main/java/net/newpipe/newplayer/service/NewPlayerService.kt index d972970..61a104b 100644 --- a/new-player/src/main/java/net/newpipe/newplayer/service/NewPlayerService.kt +++ b/new-player/src/main/java/net/newpipe/newplayer/service/NewPlayerService.kt @@ -21,16 +21,21 @@ package net.newpipe.newplayer.service +import android.app.NotificationManager +import android.content.Context import android.content.Intent import android.os.Bundle import android.util.Log import androidx.annotation.OptIn import androidx.media3.common.util.UnstableApi +import androidx.media3.session.CommandButton +import androidx.media3.session.MediaNotification import androidx.media3.session.MediaSession import androidx.media3.session.MediaSessionService import androidx.media3.session.SessionCommand import androidx.media3.session.SessionError import androidx.media3.session.SessionResult +import com.google.common.collect.ImmutableList import com.google.common.util.concurrent.Futures import com.google.common.util.concurrent.ListenableFuture import dagger.hilt.android.AndroidEntryPoint @@ -59,9 +64,41 @@ class NewPlayerService : MediaSessionService() { override fun onCreate() { super.onCreate() + setMediaNotificationProvider(object : MediaNotification.Provider { + override fun createNotification( + mediaSession: MediaSession, + customLayout: ImmutableList, + actionFactory: MediaNotification.ActionFactory, + onNotificationChangedCallback: MediaNotification.Provider.Callback + ): MediaNotification { + + val notification = createNewPlayerNotification( + service = this@NewPlayerService, + session = mediaSession, + notificationManager = getSystemService( + Context.NOTIFICATION_SERVICE + ) as NotificationManager, + notificationIcon = newPlayer.notificationIcon + ) + + return MediaNotification(NEW_PLAYER_MEDIA_NOTIFICATION_ID, notification) + } + + override fun handleCustomCommand( + session: MediaSession, + action: String, + extras: Bundle + ): Boolean { + println("gurken cought custom MediaNotification action: ${action}") + return false + } + + }) + + customCommands = buildCustomCommandList(this) - if(newPlayer.exoPlayer.value != null) { + if (newPlayer.exoPlayer.value != null) { mediaSession = MediaSession.Builder(this, newPlayer.exoPlayer.value!!) .setCallback(object : MediaSession.Callback { override fun onConnect( @@ -135,8 +172,7 @@ class NewPlayerService : MediaSessionService() { override fun onTaskRemoved(rootIntent: Intent?) { // Check if the player is not ready to play or there are no items in the media queue - if (!(newPlayer.exoPlayer.value?.playWhenReady - ?: false) || newPlayer.playlist.value.size == 0 + if (newPlayer.exoPlayer.value?.playWhenReady != true || newPlayer.playlist.value.size == 0 ) { // Stop the service stopSelf() diff --git a/new-player/src/main/java/net/newpipe/newplayer/ui/audioplayer/BottomUI.kt b/new-player/src/main/java/net/newpipe/newplayer/ui/audioplayer/BottomUI.kt index 4fd2435..a9f5dda 100644 --- a/new-player/src/main/java/net/newpipe/newplayer/ui/audioplayer/BottomUI.kt +++ b/new-player/src/main/java/net/newpipe/newplayer/ui/audioplayer/BottomUI.kt @@ -18,8 +18,6 @@ * along with NewPlayer. If not, see . */ - - package net.newpipe.newplayer.ui.audioplayer import android.app.Activity diff --git a/new-player/src/main/res/drawable/new_player_tiny_icon.xml b/new-player/src/main/res/drawable/new_player_tiny_icon.xml new file mode 100644 index 0000000..c71a3e4 --- /dev/null +++ b/new-player/src/main/res/drawable/new_player_tiny_icon.xml @@ -0,0 +1,26 @@ + + + + + + + diff --git a/new-player/src/main/res/values/strings.xml b/new-player/src/main/res/values/strings.xml index e19b926..5f526dd 100644 --- a/new-player/src/main/res/values/strings.xml +++ b/new-player/src/main/res/values/strings.xml @@ -19,6 +19,7 @@ --> + NewPlayer NewPlayer Fullscreen Open in browser Share timestamp @@ -59,4 +60,5 @@ Switch to details view Switch to fullscreen video mode Picture in picture + Playing in the background… \ No newline at end of file diff --git a/test-app/src/main/java/net/newpipe/newplayer/testapp/NewPlayerComponent.kt b/test-app/src/main/java/net/newpipe/newplayer/testapp/NewPlayerComponent.kt index ab18fbb..46200bc 100644 --- a/test-app/src/main/java/net/newpipe/newplayer/testapp/NewPlayerComponent.kt +++ b/test-app/src/main/java/net/newpipe/newplayer/testapp/NewPlayerComponent.kt @@ -22,6 +22,7 @@ package net.newpipe.newplayer.testapp import android.app.Application import android.util.Log +import androidx.core.graphics.drawable.IconCompat import dagger.Module import dagger.Provides import dagger.hilt.InstallIn @@ -32,17 +33,20 @@ import net.newpipe.newplayer.NewPlayerImpl import javax.inject.Singleton - @Module @InstallIn(SingletonComponent::class) object NewPlayerComponent { @Provides @Singleton - fun provideNewPlayer(app: Application) : NewPlayer { - val player = NewPlayerImpl(app, TestMediaRepository(app)) - if(app is NewPlayerApp) { + fun provideNewPlayer(app: Application): NewPlayer { + val player = NewPlayerImpl( + app, + TestMediaRepository(app), + notificationIcon = IconCompat.createWithResource(app, R.drawable.tinny_cools) + ) + if (app is NewPlayerApp) { app.appScope.launch { - while(true) { + while (true) { player.errorFlow.collect { e -> Log.e("NewPlayerException", e.stackTraceToString()) } diff --git a/test-app/src/main/res/drawable/tinny_cools.xml b/test-app/src/main/res/drawable/tinny_cools.xml new file mode 100644 index 0000000..59fd870 --- /dev/null +++ b/test-app/src/main/res/drawable/tinny_cools.xml @@ -0,0 +1,5 @@ + + + + +