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 @@
+
+
+
+
+