Skip to content

Commit 3431c2d

Browse files
committed
Add PlayerService with ExoPlayer and MediaSession support
1 parent 9d6e9c0 commit 3431c2d

1 file changed

Lines changed: 133 additions & 0 deletions

File tree

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
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

Comments
 (0)