make notification customizable

This commit is contained in:
Christian Schabesberger 2024-09-26 00:45:00 +02:00
parent 07a3fcd001
commit 6954fc0990
13 changed files with 349 additions and 10 deletions

View File

@ -1,3 +1,24 @@
<!-- NewPlayer
@author Jaime López
Copyright (C) Jaime López 2023
NewPlayer is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
NewPlayer is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with NewPlayer. If not, see <http://www.gnu.org/licenses/>.
-->
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) --> <!-- Created with Inkscape (http://www.inkscape.org/) -->

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

63
misc/icon.svg Normal file
View File

@ -0,0 +1,63 @@
<!-- NewPlayer
@author Jaime López
Copyright (C) Jaime López 2023
NewPlayer is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
NewPlayer is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with NewPlayer. If not, see <http://www.gnu.org/licenses/>.
-->
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="67.199371mm"
height="77.619392mm"
viewBox="0 0 67.199371 77.619392"
version="1.1"
id="svg1"
xml:space="preserve"
inkscape:version="1.3.2 (091e20ef0f, 2023-11-25)"
sodipodi:docname="new_layer_logo.svg"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"><sodipodi:namedview
id="namedview1"
pagecolor="#ffffff"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
inkscape:document-units="mm"
inkscape:zoom="0.70710678"
inkscape:cx="258.09397"
inkscape:cy="560.73567"
inkscape:window-width="2560"
inkscape:window-height="1371"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="layer1" /><defs
id="defs1" /><g
inkscape:label="Ebene 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(-141.17539,-148.5)"><path
style="fill:#cd201f;fill-opacity:1"
d="m 141.19829,148.5 67.17647,38.80671 -67.19937,38.81268 z m 11.64414,64.44229 -0.0332,-33.91671 10.22231,28.05976 5.30542,-3.09852 0.006,-11.31872 9.85035,5.68473 9.52996,-5.50176 9.57514,-5.57648 c 0,0 -23.41311,-13.48704 -35.10557,-20.24054 l -0.11485,9.72966 0.0524,9.69903 -4.54436,-12.32713 -4.44857,-12.38662 -6.4004,-3.69238 -0.0197,58.49415 z m 15.49904,-27.35687 -9.2e-4,-7.95123 16.69358,9.67687 -6.87491,3.94834 z"
id="path1"
sodipodi:nodetypes="ccccccccccccccccccccccccc" /></g></svg>

62
misc/tinny_CoolS.svg Normal file
View File

@ -0,0 +1,62 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="48"
height="48"
viewBox="0 0 12.7 12.7"
version="1.1"
id="svg1"
xml:space="preserve"
inkscape:version="1.3.2 (091e20ef0f, 2023-11-25)"
sodipodi:docname="tinny_CoolS.svg"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"><sodipodi:namedview
id="namedview1"
pagecolor="#ffffff"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
inkscape:document-units="mm"
showgrid="false"
inkscape:zoom="8.5367867"
inkscape:cx="14.173951"
inkscape:cy="36.72342"
inkscape:window-width="1920"
inkscape:window-height="1011"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="layer1"
showguides="true"><inkscape:grid
type="axonomgrid"
id="grid2"
units="mm"
originx="0"
originy="0"
spacingx="0"
spacingy="1"
empcolor="#0099e5"
empopacity="0.30196078"
color="#0099e5"
opacity="0.14901961"
empspacing="5"
dotted="false"
gridanglex="30"
gridanglez="30"
visible="false" /></sodipodi:namedview><defs
id="defs1" /><g
inkscape:label="Ebene 1"
inkscape:groupmode="layer"
id="layer1"><g
id="g3"
transform="matrix(0.62929153,0,0,0.62929153,-12.850838,-69.079564)"><path
id="path3-0"
style="color:#000000;fill:#181818;-inkscape-stroke:none"
d="m 30.511833,111.63866 -4.792989,2.76573 v 5.53506 l 1.701705,0.98237 -1.701705,0.98237 v 3.41788 l 4.792989,2.76779 4.792989,-2.76779 v -5.533 l -1.702739,-0.98289 1.702739,-0.98391 v -3.41788 z m 0,1.06816 3.866947,2.23242 v 2.34972 l -1.702738,0.98237 -1.701188,-0.98237 v -1.61126 l -0.926042,-0.52917 v 2.67374 l 4.329968,2.5001 v 4.46691 l -3.866947,2.23242 -3.867465,-2.23242 v -2.34973 l 1.703256,-0.98236 1.701188,0.98236 v 1.89653 l 0.926042,0.52917 v -2.96106 l -4.330486,-2.50011 v -4.46484 z"
sodipodi:nodetypes="cccccccccccccccccccccccccccccccccc" /></g></g></svg>

After

Width:  |  Height:  |  Size: 2.4 KiB

64
misc/tiny_icon.svg Normal file
View File

@ -0,0 +1,64 @@
<!-- NewPlayer
@author Jaime López
Copyright (C) Jaime López 2023
NewPlayer is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
NewPlayer is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with NewPlayer. If not, see <http://www.gnu.org/licenses/>.
-->
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="48"
height="48"
viewBox="0 0 12.7 12.7"
version="1.1"
id="svg1"
xml:space="preserve"
inkscape:version="1.3.2 (091e20ef0f, 2023-11-25)"
sodipodi:docname="tiny_icon.svg"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"><sodipodi:namedview
id="namedview1"
pagecolor="#ffffff"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
inkscape:document-units="mm"
inkscape:zoom="11.313708"
inkscape:cx="4.5078059"
inkscape:cy="18.782525"
inkscape:window-width="1920"
inkscape:window-height="1011"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="layer1" /><defs
id="defs1" /><g
inkscape:label="Ebene 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(-141.17539,-148.5)"><path
style="fill:#000000;fill-opacity:1;stroke-width:0.14216"
d="m 143.16189,149.38364 9.54978,5.51674 -9.55304,5.5176 z m 1.65533,9.16109 -0.004,-4.82158 1.45321,3.98895 0.7542,-0.44048 7.5e-4,-1.60906 1.40033,0.80812 1.35477,-0.78212 1.3612,-0.79275 c 0,0 -3.3284,-1.91731 -4.99059,-2.87738 l -0.0164,1.38316 0.008,1.37881 -0.64602,-1.75241 -0.63242,-1.76088 -0.90987,-0.5249 -0.003,8.31549 z m 2.20332,-3.88904 -1.3e-4,-1.13035 2.37317,1.37566 -0.97734,0.5613 z"
id="path1"
sodipodi:nodetypes="ccccccccccccccccccccccccc" /></g></svg>

View File

@ -20,6 +20,7 @@
package net.newpipe.newplayer package net.newpipe.newplayer
import androidx.core.graphics.drawable.IconCompat
import androidx.media3.common.MediaItem import androidx.media3.common.MediaItem
import androidx.media3.common.Player import androidx.media3.common.Player
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
@ -54,6 +55,7 @@ interface NewPlayer {
val preferredVideoVariants: List<String> val preferredVideoVariants: List<String>
val preferredAudioVariants: List<String> val preferredAudioVariants: List<String>
val preferredStreamLanguage: List<String> val preferredStreamLanguage: List<String>
val notificationIcon: IconCompat
val exoPlayer: StateFlow<Player?> val exoPlayer: StateFlow<Player?>
var playWhenReady: Boolean var playWhenReady: Boolean

View File

@ -24,6 +24,7 @@ import android.app.Application
import android.content.ComponentName import android.content.ComponentName
import android.util.Log import android.util.Log
import androidx.annotation.OptIn import androidx.annotation.OptIn
import androidx.core.graphics.drawable.IconCompat
import androidx.media3.common.AudioAttributes import androidx.media3.common.AudioAttributes
import androidx.media3.common.C import androidx.media3.common.C
import androidx.media3.common.MediaItem import androidx.media3.common.MediaItem
@ -63,6 +64,10 @@ class NewPlayerImpl(
override val preferredStreamLanguage: List<String> = emptyList(), override val preferredStreamLanguage: List<String> = emptyList(),
override val preferredAudioVariants: List<String> = emptyList(), override val preferredAudioVariants: List<String> = emptyList(),
val httpDataSourceFactory: HttpDataSource.Factory = DefaultHttpDataSource.Factory(), val httpDataSourceFactory: HttpDataSource.Factory = DefaultHttpDataSource.Factory(),
override val notificationIcon: IconCompat = IconCompat.createWithResource(
app,
R.drawable.new_player_tiny_icon
),
) : NewPlayer { ) : NewPlayer {
private val mutableExoPlayer = MutableStateFlow<ExoPlayer?>(null) private val mutableExoPlayer = MutableStateFlow<ExoPlayer?>(null)

View File

@ -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()
}

View File

@ -21,16 +21,21 @@
package net.newpipe.newplayer.service package net.newpipe.newplayer.service
import android.app.NotificationManager
import android.content.Context
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.util.Log import android.util.Log
import androidx.annotation.OptIn import androidx.annotation.OptIn
import androidx.media3.common.util.UnstableApi 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.MediaSession
import androidx.media3.session.MediaSessionService import androidx.media3.session.MediaSessionService
import androidx.media3.session.SessionCommand import androidx.media3.session.SessionCommand
import androidx.media3.session.SessionError import androidx.media3.session.SessionError
import androidx.media3.session.SessionResult import androidx.media3.session.SessionResult
import com.google.common.collect.ImmutableList
import com.google.common.util.concurrent.Futures import com.google.common.util.concurrent.Futures
import com.google.common.util.concurrent.ListenableFuture import com.google.common.util.concurrent.ListenableFuture
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
@ -59,6 +64,38 @@ class NewPlayerService : MediaSessionService() {
override fun onCreate() { override fun onCreate() {
super.onCreate() super.onCreate()
setMediaNotificationProvider(object : MediaNotification.Provider {
override fun createNotification(
mediaSession: MediaSession,
customLayout: ImmutableList<CommandButton>,
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) customCommands = buildCustomCommandList(this)
if (newPlayer.exoPlayer.value != null) { if (newPlayer.exoPlayer.value != null) {
@ -135,8 +172,7 @@ class NewPlayerService : MediaSessionService() {
override fun onTaskRemoved(rootIntent: Intent?) { override fun onTaskRemoved(rootIntent: Intent?) {
// Check if the player is not ready to play or there are no items in the media queue // Check if the player is not ready to play or there are no items in the media queue
if (!(newPlayer.exoPlayer.value?.playWhenReady if (newPlayer.exoPlayer.value?.playWhenReady != true || newPlayer.playlist.value.size == 0
?: false) || newPlayer.playlist.value.size == 0
) { ) {
// Stop the service // Stop the service
stopSelf() stopSelf()

View File

@ -18,8 +18,6 @@
* along with NewPlayer. If not, see <http://www.gnu.org/licenses/>. * along with NewPlayer. If not, see <http://www.gnu.org/licenses/>.
*/ */
package net.newpipe.newplayer.ui.audioplayer package net.newpipe.newplayer.ui.audioplayer
import android.app.Activity import android.app.Activity

View File

@ -0,0 +1,26 @@
<!-- NewPlayer
@author Jaime López
@author Christian Schabesberger
Copyright (C) NewPipe e.V. 2024 <info (at) jaim3.com>
NewPlayer is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
NewPlayer is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with NewPlayer. If not, see <http://www.gnu.org/licenses/>.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:autoMirrored="true" android:height="48dp" android:viewportHeight="12.7" android:viewportWidth="12.7" android:width="48dp">
<path android:fillColor="#000000" android:pathData="m1.987,0.884 l9.55,5.517 -9.553,5.518zM3.642,10.045 L3.638,5.223 5.091,9.212 5.845,8.772 5.846,7.163 7.246,7.971 8.601,7.189 9.962,6.396c0,0 -3.328,-1.917 -4.991,-2.877l-0.016,1.383 0.008,1.379 -0.646,-1.752 -0.632,-1.761 -0.91,-0.525 -0.003,8.315zM5.845,6.156 L5.845,5.025 8.218,6.401 7.241,6.962z" android:strokeWidth="0.14216"/>
</vector>

View File

@ -19,6 +19,7 @@
--> -->
<resources> <resources>
<string name="new_player_name" translatable="false">NewPlayer</string>
<string name="video_player_fullscreen_activity">NewPlayer Fullscreen</string> <string name="video_player_fullscreen_activity">NewPlayer Fullscreen</string>
<string name="menu_item_open_in_browser">Open in browser</string> <string name="menu_item_open_in_browser">Open in browser</string>
<string name="menu_item_share_timestamp">Share timestamp</string> <string name="menu_item_share_timestamp">Share timestamp</string>
@ -59,4 +60,5 @@
<string name="details_view_button_description">Switch to details view</string> <string name="details_view_button_description">Switch to details view</string>
<string name="fullscreen_button_description">Switch to fullscreen video mode</string> <string name="fullscreen_button_description">Switch to fullscreen video mode</string>
<string name="pip_button_description">Picture in picture</string> <string name="pip_button_description">Picture in picture</string>
<string name="playing_in_background">Playing in the background…</string>
</resources> </resources>

View File

@ -22,6 +22,7 @@ package net.newpipe.newplayer.testapp
import android.app.Application import android.app.Application
import android.util.Log import android.util.Log
import androidx.core.graphics.drawable.IconCompat
import dagger.Module import dagger.Module
import dagger.Provides import dagger.Provides
import dagger.hilt.InstallIn import dagger.hilt.InstallIn
@ -32,14 +33,17 @@ import net.newpipe.newplayer.NewPlayerImpl
import javax.inject.Singleton import javax.inject.Singleton
@Module @Module
@InstallIn(SingletonComponent::class) @InstallIn(SingletonComponent::class)
object NewPlayerComponent { object NewPlayerComponent {
@Provides @Provides
@Singleton @Singleton
fun provideNewPlayer(app: Application): NewPlayer { fun provideNewPlayer(app: Application): NewPlayer {
val player = NewPlayerImpl(app, TestMediaRepository(app)) val player = NewPlayerImpl(
app,
TestMediaRepository(app),
notificationIcon = IconCompat.createWithResource(app, R.drawable.tinny_cools)
)
if (app is NewPlayerApp) { if (app is NewPlayerApp) {
app.appScope.launch { app.appScope.launch {
while (true) { while (true) {

View File

@ -0,0 +1,5 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:autoMirrored="true" android:height="48dp" android:viewportHeight="12.7" android:viewportWidth="12.7" android:width="48dp">
<path android:fillColor="#181818" android:pathData="m6.35,1.174 l-3.016,1.74l0,3.483l1.071,0.618 -1.071,0.618l0,2.151l3.016,1.742 3.016,-1.742l0,-3.482l-1.072,-0.619 1.072,-0.619l0,-2.151zM6.35,1.846 L8.783,3.251l0,1.479l-1.072,0.618 -1.071,-0.618l0,-1.014l-0.583,-0.333l0,1.683l2.725,1.573l0,2.811l-2.433,1.405 -2.434,-1.405l0,-1.479l1.072,-0.618 1.071,0.618l0,1.193l0.583,0.333l0,-1.863l-2.725,-1.573l0,-2.81z"/>
</vector>