1+ package org.newpipe.externalplayer
2+
3+ import android.app.Notification
4+ import android.app.PendingIntent
5+ import android.content.Context
6+ import android.content.Intent
7+ import android.os.Build
8+ import android.os.IBinder
9+ import androidx.lifecycle.LifecycleService
10+ import android.app.NotificationChannel
11+ import android.app.NotificationManager
12+ import androidx.media.app.NotificationCompat.MediaStyle
13+ import android.support.v4.media.session.MediaSessionCompat
14+ import android.support.v4.media.MediaMetadataCompat
15+ import android.support.v4.media.session.PlaybackStateCompat
16+ import com.google.android.exoplayer2.ExoPlayer
17+ import com.google.android.exoplayer2.MediaItem
18+
19+ class PlayerService : LifecycleService () {
20+
21+ companion object {
22+ const val CHANNEL_ID = " external_player_channel"
23+ const val NOTIF_ID = 1
24+
25+ const val ACTION_PLAY = " org.newpipe.externalplayer.action.PLAY"
26+ const val ACTION_PAUSE = " org.newpipe.externalplayer.action.PAUSE"
27+ const val ACTION_STOP = " org.newpipe.externalplayer.action.STOP"
28+ const val ACTION_SET_URI = " org.newpipe.externalplayer.action.SET_URI"
29+ const val EXTRA_URI = " uri"
30+ }
31+
32+ private lateinit var mediaSession: MediaSessionCompat
33+ private var player: ExoPlayer ? = null
34+ private lateinit var notificationManager: MediaNotificationManager
35+
36+ override fun onCreate () {
37+ super .onCreate()
38+ createChannel()
39+ mediaSession = MediaSessionCompat (this , " ExternalPlayerSession" ).apply { isActive = true }
40+ notificationManager = MediaNotificationManager (this )
41+ initializePlayer()
42+ }
43+
44+ private fun initializePlayer () {
45+ if (player == null ) {
46+ player = ExoPlayer .Builder (this ).build()
47+ player?.addListener(object : com.google.android.exoplayer2.Player .Listener {
48+ override fun onIsPlayingChanged (isPlaying : Boolean ) {
49+ updatePlaybackState()
50+ startForegroundIfNeeded()
51+ }
52+ })
53+ }
54+ }
55+
56+ private fun startForegroundIfNeeded () {
57+ val notification = notificationManager.buildNotification(player, mediaSession.sessionToken)
58+ if (player?.isPlaying == true ) {
59+ startForeground(NOTIF_ID , notification)
60+ } else {
61+ val nm = getSystemService(Context .NOTIFICATION_SERVICE ) as NotificationManager
62+ nm.notify(NOTIF_ID , notification)
63+ }
64+ }
65+
66+ private fun updatePlaybackState () {
67+ val state = if (player?.isPlaying == true ) PlaybackStateCompat .STATE_PLAYING else PlaybackStateCompat .STATE_PAUSED
68+ val pos = player?.currentPosition ? : 0L
69+ val playbackState = PlaybackStateCompat .Builder ()
70+ .setActions(
71+ PlaybackStateCompat .ACTION_PLAY or
72+ PlaybackStateCompat .ACTION_PAUSE or
73+ PlaybackStateCompat .ACTION_PLAY_PAUSE or
74+ PlaybackStateCompat .ACTION_SEEK_TO or
75+ PlaybackStateCompat .ACTION_STOP
76+ )
77+ .setState(state, pos, 1.0f )
78+ .build()
79+ mediaSession.setPlaybackState(playbackState)
80+ }
81+
82+ override fun onStartCommand (intent : Intent ? , flags : Int , startId : Int ): Int {
83+ intent?.action?.let { action ->
84+ when (action) {
85+ ACTION_PLAY -> player?.play()
86+ ACTION_PAUSE -> player?.pause()
87+ ACTION_STOP -> {
88+ player?.stop()
89+ stopForeground(true )
90+ stopSelf()
91+ }
92+ ACTION_SET_URI -> {
93+ val uri = intent.getStringExtra(EXTRA_URI )
94+ if (uri != null ) setMediaUri(uri)
95+ }
96+ }
97+ }
98+ startForegroundIfNeeded()
99+ return START_STICKY
100+ }
101+
102+ private fun setMediaUri (uri : String ) {
103+ initializePlayer()
104+ val mediaItem = MediaItem .fromUri(uri)
105+ player?.setMediaItem(mediaItem)
106+ player?.prepare()
107+ player?.play()
108+ val metadataBuilder = MediaMetadataCompat .Builder ()
109+ .putString(MediaMetadataCompat .METADATA_KEY_TITLE , uri)
110+ mediaSession.setMetadata(metadataBuilder.build())
111+ updatePlaybackState()
112+ }
113+
114+ override fun onDestroy () {
115+ player?.release()
116+ player = null
117+ mediaSession.release()
118+ super .onDestroy()
119+ }
120+
121+ override fun onBind (intent : Intent ): IBinder ? {
122+ super .onBind(intent)
123+ return null
124+ }
125+
126+ private fun createChannel () {
127+ if (Build .VERSION .SDK_INT >= Build .VERSION_CODES .O ) {
128+ val nm = getSystemService(NotificationManager ::class .java)
129+ val channel = NotificationChannel (CHANNEL_ID , " External Player" , NotificationManager .IMPORTANCE_LOW )
130+ nm.createNotificationChannel(channel)
131+ }
132+ }
133+ }
0 commit comments