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"?>
<!-- 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
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<String>
val preferredAudioVariants: List<String>
val preferredStreamLanguage: List<String>
val notificationIcon: IconCompat
val exoPlayer: StateFlow<Player?>
var playWhenReady: Boolean

View File

@ -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<String> = emptyList(),
override val preferredAudioVariants: List<String> = 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<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
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<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)
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()

View File

@ -18,8 +18,6 @@
* along with NewPlayer. If not, see <http://www.gnu.org/licenses/>.
*/
package net.newpipe.newplayer.ui.audioplayer
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>
<string name="new_player_name" translatable="false">NewPlayer</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_share_timestamp">Share timestamp</string>
@ -59,4 +60,5 @@
<string name="details_view_button_description">Switch to details view</string>
<string name="fullscreen_button_description">Switch to fullscreen video mode</string>
<string name="pip_button_description">Picture in picture</string>
<string name="playing_in_background">Playing in the background…</string>
</resources>

View File

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

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>