capacitor-plugin-playlist 0.13.0 → 0.13.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/android/src/main/java/org/dwbn/plugins/playlist/manager/PlaylistManager.kt +9 -0
- package/android/src/main/java/org/dwbn/plugins/playlist/service/HandoffForwardingPlayer.kt +21 -0
- package/android/src/main/java/org/dwbn/plugins/playlist/service/MediaService.kt +30 -0
- package/android/src/test/java/org/dwbn/plugins/playlist/service/HandoffForwardingPlayerTest.kt +51 -0
- package/package.json +1 -1
|
@@ -216,6 +216,7 @@ class PlaylistManager(private val application: Application) {
|
|
|
216
216
|
}
|
|
217
217
|
|
|
218
218
|
beginPlayback(seekStart, options.startPaused)
|
|
219
|
+
mediaServiceRef.get()?.refreshSkipAvailability()
|
|
219
220
|
}
|
|
220
221
|
|
|
221
222
|
fun addItem(item: AudioTrack?, index: Int = -1) {
|
|
@@ -441,6 +442,13 @@ class PlaylistManager(private val application: Application) {
|
|
|
441
442
|
return
|
|
442
443
|
}
|
|
443
444
|
val index = currentPosition.coerceIn(0, audioTracks.size - 1)
|
|
445
|
+
// Snapshot the position of whatever track is being switched away from — covers the
|
|
446
|
+
// JS "tap a different playlist item" path (playTrackById), not just native skip, so a
|
|
447
|
+
// later native skip back to that track resumes correctly regardless of how it was left.
|
|
448
|
+
val fromIndex = exoPlayer.currentMediaItemIndex
|
|
449
|
+
if (fromIndex != index) {
|
|
450
|
+
audioTracks.getOrNull(fromIndex)?.startPositionMs = exoPlayer.currentPosition
|
|
451
|
+
}
|
|
444
452
|
currentPosition = index
|
|
445
453
|
exoPlayer.repeatMode = PlaylistPlaybackPolicy.repeatMode(loop, audioTracks.size)
|
|
446
454
|
exoPlayer.seekTo(index, seekPosition)
|
|
@@ -546,6 +554,7 @@ class PlaylistManager(private val application: Application) {
|
|
|
546
554
|
isNextAvailable,
|
|
547
555
|
isPreviousAvailable
|
|
548
556
|
)
|
|
557
|
+
mediaServiceRef.get()?.refreshSkipAvailability()
|
|
549
558
|
}
|
|
550
559
|
|
|
551
560
|
override fun onPositionDiscontinuity(
|
|
@@ -123,6 +123,27 @@ internal class HandoffForwardingPlayer(
|
|
|
123
123
|
onSkipToPrevious?.invoke() ?: super.seekToPreviousMediaItem()
|
|
124
124
|
}
|
|
125
125
|
|
|
126
|
+
/**
|
|
127
|
+
* The system media notification and hardware media buttons invoke this — Player's "smart"
|
|
128
|
+
* seek, not [seekToNextMediaItem] — so without this override they fall through to
|
|
129
|
+
* [ForwardingPlayer]'s default (the wrapped ExoPlayer's own seekToNext), bypassing
|
|
130
|
+
* [onSkipToNext] and its saved-position resume entirely.
|
|
131
|
+
*/
|
|
132
|
+
override fun seekToNext() {
|
|
133
|
+
if (videoPlayer() != null) {
|
|
134
|
+
return
|
|
135
|
+
}
|
|
136
|
+
onSkipToNext?.invoke() ?: super.seekToNext()
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/** See [seekToNext] — the system-control counterpart of [seekToPreviousMediaItem]. */
|
|
140
|
+
override fun seekToPrevious() {
|
|
141
|
+
if (videoPlayer() != null) {
|
|
142
|
+
return
|
|
143
|
+
}
|
|
144
|
+
onSkipToPrevious?.invoke() ?: super.seekToPrevious()
|
|
145
|
+
}
|
|
146
|
+
|
|
126
147
|
override fun getAvailableCommands(): Player.Commands {
|
|
127
148
|
val previous = previousAvailable
|
|
128
149
|
val next = nextAvailable
|
|
@@ -219,6 +219,36 @@ class MediaService : MediaSessionService() {
|
|
|
219
219
|
triggerNotificationUpdate()
|
|
220
220
|
}
|
|
221
221
|
|
|
222
|
+
/**
|
|
223
|
+
* Re-grants the media notification controller's skip commands to match current queue state.
|
|
224
|
+
* [PlaylistMediaSessionCallback.onConnect] only runs once per connection (typically before any
|
|
225
|
+
* track is loaded), so without this the system notification and hardware media buttons stay
|
|
226
|
+
* permanently frozen at whatever previous/next availability existed at connect time.
|
|
227
|
+
*/
|
|
228
|
+
fun refreshSkipAvailability() {
|
|
229
|
+
val session = mediaSession ?: return
|
|
230
|
+
val controller = session.mediaNotificationControllerInfo ?: return
|
|
231
|
+
val retain = playlistManager.videoHandoffForegroundRetain
|
|
232
|
+
val videoAttached = VideoPlayerBridge.hasActivePlayer()
|
|
233
|
+
session.setAvailableCommands(
|
|
234
|
+
controller,
|
|
235
|
+
MediaSession.ConnectionResult.DEFAULT_SESSION_COMMANDS,
|
|
236
|
+
MediaNotificationPolicy.playerCommandsForMediaNotification(
|
|
237
|
+
MediaSession.ConnectionResult.DEFAULT_PLAYER_COMMANDS,
|
|
238
|
+
MediaNotificationPolicy.mediaNotificationSkipPreviousEnabled(
|
|
239
|
+
playlistManager.isPreviousAvailable,
|
|
240
|
+
retain,
|
|
241
|
+
videoAttached
|
|
242
|
+
),
|
|
243
|
+
MediaNotificationPolicy.mediaNotificationSkipNextEnabled(
|
|
244
|
+
playlistManager.isNextAvailable,
|
|
245
|
+
retain,
|
|
246
|
+
videoAttached
|
|
247
|
+
)
|
|
248
|
+
)
|
|
249
|
+
)
|
|
250
|
+
}
|
|
251
|
+
|
|
222
252
|
/** Re-reads `options.icon` after `setOptions` so a running service picks up the app's icon. */
|
|
223
253
|
fun applyNotificationIcon() {
|
|
224
254
|
notificationProvider?.setSmallIcon(smallIconRes())
|
package/android/src/test/java/org/dwbn/plugins/playlist/service/HandoffForwardingPlayerTest.kt
CHANGED
|
@@ -75,6 +75,44 @@ class HandoffForwardingPlayerTest {
|
|
|
75
75
|
assertEquals(0, recording.seekToPreviousCount)
|
|
76
76
|
}
|
|
77
77
|
|
|
78
|
+
/**
|
|
79
|
+
* seekToNext/seekToPrevious (not the MediaItem variants) are what the system media
|
|
80
|
+
* notification and hardware media buttons actually invoke — regression coverage for the
|
|
81
|
+
* bug where these fell through to the wrapped player's own default and bypassed the
|
|
82
|
+
* playlist's skip/position-resume logic entirely.
|
|
83
|
+
*/
|
|
84
|
+
@Test
|
|
85
|
+
fun seekToNext_delegatesToCallback() {
|
|
86
|
+
var skipNextCount = 0
|
|
87
|
+
val recording = RecordingPlayer()
|
|
88
|
+
val forwarding = HandoffForwardingPlayer(
|
|
89
|
+
recording.proxy,
|
|
90
|
+
retain = { false },
|
|
91
|
+
onSkipToNext = { skipNextCount++ }
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
forwarding.seekToNext()
|
|
95
|
+
|
|
96
|
+
assertEquals(1, skipNextCount)
|
|
97
|
+
assertEquals(0, recording.seekToNextSmartCount)
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
@Test
|
|
101
|
+
fun seekToPrevious_delegatesToCallback() {
|
|
102
|
+
var skipPreviousCount = 0
|
|
103
|
+
val recording = RecordingPlayer()
|
|
104
|
+
val forwarding = HandoffForwardingPlayer(
|
|
105
|
+
recording.proxy,
|
|
106
|
+
retain = { false },
|
|
107
|
+
onSkipToPrevious = { skipPreviousCount++ }
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
forwarding.seekToPrevious()
|
|
111
|
+
|
|
112
|
+
assertEquals(1, skipPreviousCount)
|
|
113
|
+
assertEquals(0, recording.seekToPreviousSmartCount)
|
|
114
|
+
}
|
|
115
|
+
|
|
78
116
|
@Test
|
|
79
117
|
fun retainWithVideoAttached_playReachesVideoPlayer() {
|
|
80
118
|
val audio = RecordingPlayer()
|
|
@@ -153,10 +191,13 @@ class HandoffForwardingPlayerTest {
|
|
|
153
191
|
|
|
154
192
|
forwarding.seekToNextMediaItem()
|
|
155
193
|
forwarding.seekToPreviousMediaItem()
|
|
194
|
+
forwarding.seekToNext()
|
|
195
|
+
forwarding.seekToPrevious()
|
|
156
196
|
|
|
157
197
|
assertEquals(0, skipNextCount)
|
|
158
198
|
assertEquals(0, skipPreviousCount)
|
|
159
199
|
assertEquals(0, video.seekToNextCount)
|
|
200
|
+
assertEquals(0, video.seekToNextSmartCount)
|
|
160
201
|
}
|
|
161
202
|
|
|
162
203
|
@Test
|
|
@@ -224,6 +265,8 @@ class HandoffForwardingPlayerTest {
|
|
|
224
265
|
var pauseCount = 0
|
|
225
266
|
var seekToNextCount = 0
|
|
226
267
|
var seekToPreviousCount = 0
|
|
268
|
+
var seekToNextSmartCount = 0
|
|
269
|
+
var seekToPreviousSmartCount = 0
|
|
227
270
|
val seekToPositionMsCalls = mutableListOf<Long>()
|
|
228
271
|
val setPlayWhenReadyCalls = mutableListOf<Boolean>()
|
|
229
272
|
val proxy: Player = Proxy.newProxyInstance(
|
|
@@ -260,6 +303,14 @@ class HandoffForwardingPlayerTest {
|
|
|
260
303
|
seekToPreviousCount++
|
|
261
304
|
null
|
|
262
305
|
}
|
|
306
|
+
"seekToNext" -> {
|
|
307
|
+
seekToNextSmartCount++
|
|
308
|
+
null
|
|
309
|
+
}
|
|
310
|
+
"seekToPrevious" -> {
|
|
311
|
+
seekToPreviousSmartCount++
|
|
312
|
+
null
|
|
313
|
+
}
|
|
263
314
|
"isPlaying", "getIsPlaying" -> isPlayingValue
|
|
264
315
|
"isPlayWhenReady", "getPlayWhenReady" -> playWhenReady
|
|
265
316
|
"getPlaybackState" -> playbackState
|