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.
- package/README.md +133 -127
- package/android/build.gradle +31 -18
- package/android/gradle/wrapper/gradle-wrapper.properties +1 -1
- package/android/src/main/java/org/dwbn/plugins/playlist/AudioMediaItemFactory.java +96 -0
- package/android/src/main/java/org/dwbn/plugins/playlist/PlaylistPlugin.kt +3 -3
- package/android/src/main/java/org/dwbn/plugins/playlist/RmxAudioPlayer.java +145 -188
- package/android/src/main/java/org/dwbn/plugins/playlist/RmxPlaybackErrorMapper.kt +26 -0
- package/android/src/main/java/org/dwbn/plugins/playlist/data/AudioTrack.kt +28 -28
- package/android/src/main/java/org/dwbn/plugins/playlist/handoff/VideoPlayerBridge.kt +102 -0
- package/android/src/main/java/org/dwbn/plugins/playlist/manager/PlaybackProgress.kt +8 -0
- package/android/src/main/java/org/dwbn/plugins/playlist/manager/PlaylistManager.kt +418 -183
- package/android/src/main/java/org/dwbn/plugins/playlist/manager/PlaylistPlaybackPolicy.kt +98 -0
- package/android/src/main/java/org/dwbn/plugins/playlist/manager/RmxPlaybackState.kt +11 -0
- package/android/src/main/java/org/dwbn/plugins/playlist/playlist/AudioPlaylistHandler.java +107 -211
- package/android/src/main/java/org/dwbn/plugins/playlist/service/GlideBitmapLoader.kt +121 -0
- package/android/src/main/java/org/dwbn/plugins/playlist/service/HandoffForwardingPlayer.kt +185 -0
- package/android/src/main/java/org/dwbn/plugins/playlist/service/MediaNotificationPolicy.kt +253 -0
- package/android/src/main/java/org/dwbn/plugins/playlist/service/MediaService.kt +260 -68
- package/android/src/main/java/org/dwbn/plugins/playlist/service/PlaylistMediaSessionCallback.kt +39 -0
- package/android/src/test/java/org/dwbn/plugins/playlist/AudioMediaItemFactoryMimeTest.kt +98 -0
- package/android/src/test/java/org/dwbn/plugins/playlist/AudioMediaItemFactoryTest.java +71 -0
- package/android/src/test/java/org/dwbn/plugins/playlist/data/AudioTrackTest.kt +53 -0
- package/android/src/test/java/org/dwbn/plugins/playlist/handoff/VideoPlayerBridgeTest.kt +84 -0
- package/android/src/test/java/org/dwbn/plugins/playlist/manager/PlaylistPlaybackPolicyTest.kt +128 -0
- package/android/src/test/java/org/dwbn/plugins/playlist/playlist/AudioPlaylistHandlerTest.kt +145 -0
- package/android/src/test/java/org/dwbn/plugins/playlist/service/GlideBitmapLoaderTest.kt +76 -0
- package/android/src/test/java/org/dwbn/plugins/playlist/service/HandoffForwardingPlayerTest.kt +282 -0
- package/android/src/test/java/org/dwbn/plugins/playlist/service/MediaNotificationPolicyTest.kt +285 -0
- package/android/src/test/java/org/dwbn/plugins/playlist/service/VideoHandoffNotificationCommandsTest.kt +54 -0
- package/android/src/test/java/org/dwbn/plugins/playlist/service/VideoHandoffNotificationPolicyTest.kt +47 -0
- package/dist/docs.json +12 -846
- package/dist/esm/RmxAudioPlayer.d.ts +2 -2
- package/dist/esm/RmxAudioPlayer.js.map +1 -1
- package/dist/esm/definitions.d.ts +7 -7
- package/dist/esm/interfaces.d.ts +7 -1
- package/dist/esm/utils.d.ts +1 -1
- package/dist/esm/utils.js +2 -2
- package/dist/esm/utils.js.map +1 -1
- package/dist/esm/web.d.ts +3 -3
- package/dist/esm/web.js +5 -5
- package/dist/esm/web.js.map +1 -1
- package/dist/plugin.cjs.js +7 -7
- package/dist/plugin.cjs.js.map +1 -1
- package/dist/plugin.js +7 -7
- package/dist/plugin.js.map +1 -1
- package/ios/Sources/PlaylistPlugin/AVBidirectionalQueuePlayer.swift +45 -6
- package/ios/Sources/PlaylistPlugin/AudioTrack.swift +15 -2
- package/ios/Tests/PlaylistPluginTests/PositionResumeTests.swift +82 -0
- package/package.json +15 -19
- package/android/src/main/java/org/dwbn/plugins/playlist/notification/PlaylistNotificationProvider.kt +0 -26
- package/android/src/main/java/org/dwbn/plugins/playlist/playlist/AudioApi.kt +0 -114
- package/android/src/main/java/org/dwbn/plugins/playlist/playlist/BaseMediaApi.kt +0 -36
- 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
|
|
7
|
+
class AudioTrack(private val config: JSONObject) {
|
|
11
8
|
companion object {
|
|
12
9
|
private val nextPlaylistId = AtomicLong(1)
|
|
13
10
|
}
|
|
14
11
|
|
|
15
|
-
|
|
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 =
|
|
18
|
+
field = minOf(maxOf(bufferPercentFloat, buff), 1f)
|
|
22
19
|
}
|
|
23
20
|
var bufferPercent = 0
|
|
24
21
|
set(buff) {
|
|
25
|
-
field =
|
|
22
|
+
field = maxOf(bufferPercent, buff)
|
|
26
23
|
}
|
|
27
24
|
var duration: Long = 0
|
|
28
25
|
set(dur) {
|
|
29
|
-
field =
|
|
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 (
|
|
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
|
-
|
|
60
|
-
|
|
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
|
-
|
|
64
|
-
|
|
65
|
-
get() = null // ... at which point we can return a value here.
|
|
70
|
+
val downloadedMediaUri: String?
|
|
71
|
+
get() = null
|
|
66
72
|
|
|
67
|
-
|
|
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
|
-
|
|
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
|
|
81
|
+
} else albumArt
|
|
81
82
|
}
|
|
82
83
|
|
|
83
|
-
|
|
84
|
+
val artworkUrl: String?
|
|
84
85
|
get() = thumbnailUrl
|
|
85
86
|
|
|
86
|
-
|
|
87
|
+
val title: String
|
|
87
88
|
get() = config.optString("title")
|
|
88
89
|
|
|
89
|
-
|
|
90
|
+
val album: String
|
|
90
91
|
get() = config.optString("album")
|
|
91
92
|
|
|
92
|
-
|
|
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
|
+
}
|