capacitor-plugin-playlist 0.11.2 → 0.13.0

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.
Files changed (53) hide show
  1. package/README.md +133 -127
  2. package/android/build.gradle +31 -18
  3. package/android/gradle/wrapper/gradle-wrapper.properties +1 -1
  4. package/android/src/main/java/org/dwbn/plugins/playlist/AudioMediaItemFactory.java +96 -0
  5. package/android/src/main/java/org/dwbn/plugins/playlist/PlaylistPlugin.kt +3 -3
  6. package/android/src/main/java/org/dwbn/plugins/playlist/RmxAudioPlayer.java +145 -188
  7. package/android/src/main/java/org/dwbn/plugins/playlist/RmxPlaybackErrorMapper.kt +26 -0
  8. package/android/src/main/java/org/dwbn/plugins/playlist/data/AudioTrack.kt +28 -28
  9. package/android/src/main/java/org/dwbn/plugins/playlist/handoff/VideoPlayerBridge.kt +102 -0
  10. package/android/src/main/java/org/dwbn/plugins/playlist/manager/PlaybackProgress.kt +8 -0
  11. package/android/src/main/java/org/dwbn/plugins/playlist/manager/PlaylistManager.kt +418 -183
  12. package/android/src/main/java/org/dwbn/plugins/playlist/manager/PlaylistPlaybackPolicy.kt +98 -0
  13. package/android/src/main/java/org/dwbn/plugins/playlist/manager/RmxPlaybackState.kt +11 -0
  14. package/android/src/main/java/org/dwbn/plugins/playlist/playlist/AudioPlaylistHandler.java +107 -211
  15. package/android/src/main/java/org/dwbn/plugins/playlist/service/GlideBitmapLoader.kt +121 -0
  16. package/android/src/main/java/org/dwbn/plugins/playlist/service/HandoffForwardingPlayer.kt +185 -0
  17. package/android/src/main/java/org/dwbn/plugins/playlist/service/MediaNotificationPolicy.kt +253 -0
  18. package/android/src/main/java/org/dwbn/plugins/playlist/service/MediaService.kt +260 -68
  19. package/android/src/main/java/org/dwbn/plugins/playlist/service/PlaylistMediaSessionCallback.kt +39 -0
  20. package/android/src/test/java/org/dwbn/plugins/playlist/AudioMediaItemFactoryMimeTest.kt +98 -0
  21. package/android/src/test/java/org/dwbn/plugins/playlist/AudioMediaItemFactoryTest.java +71 -0
  22. package/android/src/test/java/org/dwbn/plugins/playlist/data/AudioTrackTest.kt +53 -0
  23. package/android/src/test/java/org/dwbn/plugins/playlist/handoff/VideoPlayerBridgeTest.kt +84 -0
  24. package/android/src/test/java/org/dwbn/plugins/playlist/manager/PlaylistPlaybackPolicyTest.kt +128 -0
  25. package/android/src/test/java/org/dwbn/plugins/playlist/playlist/AudioPlaylistHandlerTest.kt +145 -0
  26. package/android/src/test/java/org/dwbn/plugins/playlist/service/GlideBitmapLoaderTest.kt +76 -0
  27. package/android/src/test/java/org/dwbn/plugins/playlist/service/HandoffForwardingPlayerTest.kt +282 -0
  28. package/android/src/test/java/org/dwbn/plugins/playlist/service/MediaNotificationPolicyTest.kt +285 -0
  29. package/android/src/test/java/org/dwbn/plugins/playlist/service/VideoHandoffNotificationCommandsTest.kt +54 -0
  30. package/android/src/test/java/org/dwbn/plugins/playlist/service/VideoHandoffNotificationPolicyTest.kt +47 -0
  31. package/dist/docs.json +12 -846
  32. package/dist/esm/RmxAudioPlayer.d.ts +2 -2
  33. package/dist/esm/RmxAudioPlayer.js.map +1 -1
  34. package/dist/esm/definitions.d.ts +7 -7
  35. package/dist/esm/interfaces.d.ts +7 -1
  36. package/dist/esm/utils.d.ts +1 -1
  37. package/dist/esm/utils.js +2 -2
  38. package/dist/esm/utils.js.map +1 -1
  39. package/dist/esm/web.d.ts +3 -3
  40. package/dist/esm/web.js +5 -5
  41. package/dist/esm/web.js.map +1 -1
  42. package/dist/plugin.cjs.js +7 -7
  43. package/dist/plugin.cjs.js.map +1 -1
  44. package/dist/plugin.js +7 -7
  45. package/dist/plugin.js.map +1 -1
  46. package/ios/Sources/PlaylistPlugin/AVBidirectionalQueuePlayer.swift +45 -6
  47. package/ios/Sources/PlaylistPlugin/AudioTrack.swift +15 -2
  48. package/ios/Tests/PlaylistPluginTests/PositionResumeTests.swift +82 -0
  49. package/package.json +15 -19
  50. package/android/src/main/java/org/dwbn/plugins/playlist/notification/PlaylistNotificationProvider.kt +0 -26
  51. package/android/src/main/java/org/dwbn/plugins/playlist/playlist/AudioApi.kt +0 -114
  52. package/android/src/main/java/org/dwbn/plugins/playlist/playlist/BaseMediaApi.kt +0 -36
  53. package/android/src/main/java/org/dwbn/plugins/playlist/service/MediaImageProvider.kt +0 -83
@@ -1,32 +1,40 @@
1
1
  package org.dwbn.plugins.playlist.data
2
2
 
3
- import com.devbrackets.android.playlistcore.annotation.SupportedMediaType
4
- import com.devbrackets.android.playlistcore.api.PlaylistItem
5
- import com.devbrackets.android.playlistcore.manager.BasePlaylistManager
6
3
  import org.json.JSONException
7
4
  import org.json.JSONObject
8
5
  import java.util.concurrent.atomic.AtomicLong
9
6
 
10
- class AudioTrack (private val config: JSONObject) : PlaylistItem {
7
+ class AudioTrack(private val config: JSONObject) {
11
8
  companion object {
12
9
  private val nextPlaylistId = AtomicLong(1)
13
10
  }
14
11
 
15
- override val id: Long = nextPlaylistId.getAndIncrement()
12
+ val id: Long = nextPlaylistId.getAndIncrement()
16
13
 
17
14
  var bufferPercentFloat = 0f
18
15
  set(buff) {
19
16
  // There is a bug in MediaProgress where if bufferPercent == 100 it sets bufferPercentFloat
20
17
  // to 100 instead of to 1.
21
- field = Math.min(Math.max(bufferPercentFloat, buff), 1f)
18
+ field = minOf(maxOf(bufferPercentFloat, buff), 1f)
22
19
  }
23
20
  var bufferPercent = 0
24
21
  set(buff) {
25
- field = Math.max(bufferPercent, buff)
22
+ field = maxOf(bufferPercent, buff)
26
23
  }
27
24
  var duration: Long = 0
28
25
  set(dur) {
29
- field = Math.max(0, dur)
26
+ field = maxOf(0, dur)
27
+ }
28
+
29
+ /**
30
+ * Last known playback position for this track, in ms. Seeded from the JS-supplied
31
+ * `startPosition` (seconds) at construction, and kept fresh by [org.dwbn.plugins.playlist.manager.PlaylistManager]
32
+ * whenever the player skips away from this track, so a later skip back within the same
33
+ * session resumes correctly instead of restarting at 0.
34
+ */
35
+ var startPositionMs: Long = (config.optDouble("startPosition", 0.0) * 1000.0).toLong()
36
+ set(value) {
37
+ field = maxOf(0, value)
30
38
  }
31
39
 
32
40
  fun toDict(): JSONObject {
@@ -39,7 +47,7 @@ class AudioTrack (private val config: JSONObject) : PlaylistItem {
39
47
  info.put("artist", artist)
40
48
  info.put("album", album)
41
49
  info.put("title", title)
42
- } catch (e: JSONException) {
50
+ } catch (_: JSONException) {
43
51
  // I can think of no reason this would ever fail
44
52
  }
45
53
  return info
@@ -56,40 +64,32 @@ class AudioTrack (private val config: JSONObject) : PlaylistItem {
56
64
  } else trackId
57
65
  }
58
66
 
59
- // Would really like to set this to true once the cache has it...
60
- override val downloaded: Boolean
61
- get() = false // Would really like to set this to true once the cache has it...
67
+ val downloaded: Boolean
68
+ get() = false
62
69
 
63
- // ... at which point we can return a value here.
64
- override val downloadedMediaUri: String?
65
- get() = null // ... at which point we can return a value here.
70
+ val downloadedMediaUri: String?
71
+ get() = null
66
72
 
67
- @get:SupportedMediaType
68
- override val mediaType: Int
69
- get() = BasePlaylistManager.AUDIO
70
-
71
- override val mediaUrl: String
73
+ val mediaUrl: String
72
74
  get() = config.optString("assetUrl", "")
73
75
 
74
- // we should have a good default here.
75
- override val thumbnailUrl: String?
76
+ val thumbnailUrl: String?
76
77
  get() {
77
78
  val albumArt = config.optString("albumArt")
78
79
  return if (albumArt == "") {
79
80
  null
80
- } else albumArt // we should have a good default here.
81
+ } else albumArt
81
82
  }
82
83
 
83
- override val artworkUrl: String?
84
+ val artworkUrl: String?
84
85
  get() = thumbnailUrl
85
86
 
86
- override val title: String
87
+ val title: String
87
88
  get() = config.optString("title")
88
89
 
89
- override val album: String
90
+ val album: String
90
91
  get() = config.optString("album")
91
92
 
92
- override val artist: String
93
+ val artist: String
93
94
  get() = config.optString("artist")
94
-
95
95
  }
@@ -0,0 +1,102 @@
1
+ package org.dwbn.plugins.playlist.handoff
2
+
3
+ import androidx.annotation.OptIn
4
+ import androidx.media3.common.Player
5
+ import androidx.media3.common.util.UnstableApi
6
+ import org.dwbn.plugins.playlist.service.MediaService
7
+ import java.util.concurrent.CopyOnWriteArrayList
8
+
9
+ /**
10
+ * Process-local link from the video plugin ExoPlayer to the playlist [MediaService] session.
11
+ * Video attaches via reflection so the video module does not depend on the playlist module.
12
+ */
13
+ @OptIn(UnstableApi::class)
14
+ object VideoPlayerBridge {
15
+ @Volatile
16
+ private var activePlayer: Player? = null
17
+
18
+ private var playbackListener: Player.Listener? = null
19
+
20
+ private val playbackChangedListeners = CopyOnWriteArrayList<() -> Unit>()
21
+
22
+ @JvmStatic
23
+ fun attach(player: Player) {
24
+ detach(player)
25
+ activePlayer = player
26
+ val listener = object : Player.Listener {
27
+ override fun onEvents(player: Player, events: Player.Events) {
28
+ if (events.containsAny(
29
+ Player.EVENT_PLAY_WHEN_READY_CHANGED,
30
+ Player.EVENT_PLAYBACK_STATE_CHANGED,
31
+ Player.EVENT_IS_PLAYING_CHANGED,
32
+ Player.EVENT_MEDIA_METADATA_CHANGED,
33
+ Player.EVENT_TIMELINE_CHANGED,
34
+ Player.EVENT_POSITION_DISCONTINUITY
35
+ )
36
+ ) {
37
+ notifyPlaybackChanged()
38
+ }
39
+ }
40
+ }
41
+ playbackListener = listener
42
+ player.addListener(listener)
43
+ notifyPlaybackChanged()
44
+ }
45
+
46
+ @JvmStatic
47
+ fun detach(player: Player?) {
48
+ if (player == null) {
49
+ detachInternal()
50
+ notifyPlaybackChanged()
51
+ return
52
+ }
53
+ if (activePlayer !== player) {
54
+ return
55
+ }
56
+ detachInternal()
57
+ notifyPlaybackChanged()
58
+ }
59
+
60
+ @JvmStatic
61
+ fun activePlayer(): Player? = activePlayer
62
+
63
+ @JvmStatic
64
+ fun hasActivePlayer(): Boolean = activePlayer != null
65
+
66
+ @JvmStatic
67
+ fun addPlaybackChangedListener(listener: () -> Unit) {
68
+ playbackChangedListeners.addIfAbsent(listener)
69
+ }
70
+
71
+ @JvmStatic
72
+ fun removePlaybackChangedListener(listener: () -> Unit) {
73
+ playbackChangedListeners.remove(listener)
74
+ }
75
+
76
+ @JvmStatic
77
+ fun requestNotificationRefresh() {
78
+ MediaService.instance?.triggerNotificationUpdate()
79
+ }
80
+
81
+ /** Test-only reset. */
82
+ @JvmStatic
83
+ internal fun clearForTests() {
84
+ detachInternal()
85
+ playbackChangedListeners.clear()
86
+ }
87
+
88
+ private fun notifyPlaybackChanged() {
89
+ playbackChangedListeners.forEach { it() }
90
+ requestNotificationRefresh()
91
+ }
92
+
93
+ private fun detachInternal() {
94
+ val player = activePlayer
95
+ val listener = playbackListener
96
+ if (player != null && listener != null) {
97
+ player.removeListener(listener)
98
+ }
99
+ activePlayer = null
100
+ playbackListener = null
101
+ }
102
+ }
@@ -0,0 +1,8 @@
1
+ package org.dwbn.plugins.playlist.manager
2
+
3
+ data class PlaybackProgress(
4
+ val position: Long,
5
+ val duration: Long,
6
+ val bufferPercent: Int,
7
+ val bufferPercentFloat: Float
8
+ )