@mustafaj/capacitor-plugin-playlist 0.9.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 (90) hide show
  1. package/CapacitorPluginPlaylist.podspec +17 -0
  2. package/README.md +248 -0
  3. package/android/.project +34 -0
  4. package/android/build.gradle +69 -0
  5. package/android/gradle/wrapper/gradle-wrapper.jar +0 -0
  6. package/android/gradle/wrapper/gradle-wrapper.properties +7 -0
  7. package/android/gradle.properties +22 -0
  8. package/android/gradlew +251 -0
  9. package/android/gradlew.bat +94 -0
  10. package/android/proguard-rules.pro +21 -0
  11. package/android/settings.gradle +2 -0
  12. package/android/src/androidTest/java/com/getcapacitor/android/ExampleInstrumentedTest.java +26 -0
  13. package/android/src/main/AndroidManifest.xml +4 -0
  14. package/android/src/main/java/org/dwbn/plugins/playlist/App.kt +19 -0
  15. package/android/src/main/java/org/dwbn/plugins/playlist/FakeR.kt +39 -0
  16. package/android/src/main/java/org/dwbn/plugins/playlist/OnStatusCallback.kt +34 -0
  17. package/android/src/main/java/org/dwbn/plugins/playlist/OnStatusReportListener.java +7 -0
  18. package/android/src/main/java/org/dwbn/plugins/playlist/PlaylistItemOptions.java +52 -0
  19. package/android/src/main/java/org/dwbn/plugins/playlist/PlaylistPlugin.kt +447 -0
  20. package/android/src/main/java/org/dwbn/plugins/playlist/RmxAudioErrorType.java +13 -0
  21. package/android/src/main/java/org/dwbn/plugins/playlist/RmxAudioPlayer.java +487 -0
  22. package/android/src/main/java/org/dwbn/plugins/playlist/RmxAudioStatusMessage.java +35 -0
  23. package/android/src/main/java/org/dwbn/plugins/playlist/RmxConstants.java +42 -0
  24. package/android/src/main/java/org/dwbn/plugins/playlist/TrackRemovalItem.java +12 -0
  25. package/android/src/main/java/org/dwbn/plugins/playlist/data/AudioTrack.kt +94 -0
  26. package/android/src/main/java/org/dwbn/plugins/playlist/manager/MediaControlsListener.kt +13 -0
  27. package/android/src/main/java/org/dwbn/plugins/playlist/manager/Options.kt +77 -0
  28. package/android/src/main/java/org/dwbn/plugins/playlist/manager/PlaylistManager.kt +308 -0
  29. package/android/src/main/java/org/dwbn/plugins/playlist/notification/PlaylistNotificationProvider.kt +26 -0
  30. package/android/src/main/java/org/dwbn/plugins/playlist/playlist/AudioApi.kt +114 -0
  31. package/android/src/main/java/org/dwbn/plugins/playlist/playlist/AudioPlaylistHandler.java +146 -0
  32. package/android/src/main/java/org/dwbn/plugins/playlist/playlist/BaseMediaApi.kt +36 -0
  33. package/android/src/main/java/org/dwbn/plugins/playlist/service/MediaImageProvider.kt +83 -0
  34. package/android/src/main/java/org/dwbn/plugins/playlist/service/MediaService.kt +98 -0
  35. package/android/src/main/res/.gitkeep +0 -0
  36. package/android/src/main/res/drawable/ic_closed_caption_white_24dp.xml +9 -0
  37. package/android/src/main/res/drawable/ic_demo_icon_adaptive.xml +15 -0
  38. package/android/src/main/res/drawable/ic_launcher_background.xml +48 -0
  39. package/android/src/main/res/drawable/ic_launcher_foreground.xml +22 -0
  40. package/android/src/main/res/drawable/ic_notification_icon.png +0 -0
  41. package/android/src/main/res/layout/bridge_layout_main.xml +15 -0
  42. package/android/src/main/res/values/colors.xml +3 -0
  43. package/android/src/main/res/values/strings.xml +3 -0
  44. package/android/src/main/res/values/styles.xml +3 -0
  45. package/android/src/test/java/com/getcapacitor/ExampleUnitTest.java +18 -0
  46. package/dist/docs.json +2071 -0
  47. package/dist/esm/Constants.d.ts +164 -0
  48. package/dist/esm/Constants.js +175 -0
  49. package/dist/esm/Constants.js.map +1 -0
  50. package/dist/esm/RmxAudioPlayer.d.ts +181 -0
  51. package/dist/esm/RmxAudioPlayer.js +344 -0
  52. package/dist/esm/RmxAudioPlayer.js.map +1 -0
  53. package/dist/esm/definitions.d.ts +78 -0
  54. package/dist/esm/definitions.js +2 -0
  55. package/dist/esm/definitions.js.map +1 -0
  56. package/dist/esm/index.d.ts +5 -0
  57. package/dist/esm/index.js +6 -0
  58. package/dist/esm/index.js.map +1 -0
  59. package/dist/esm/interfaces.d.ts +246 -0
  60. package/dist/esm/interfaces.js +2 -0
  61. package/dist/esm/interfaces.js.map +1 -0
  62. package/dist/esm/plugin.d.ts +3 -0
  63. package/dist/esm/plugin.js +13 -0
  64. package/dist/esm/plugin.js.map +1 -0
  65. package/dist/esm/utils.d.ts +15 -0
  66. package/dist/esm/utils.js +48 -0
  67. package/dist/esm/utils.js.map +1 -0
  68. package/dist/esm/web.d.ts +54 -0
  69. package/dist/esm/web.js +409 -0
  70. package/dist/esm/web.js.map +1 -0
  71. package/dist/plugin.cjs.js +993 -0
  72. package/dist/plugin.cjs.js.map +1 -0
  73. package/dist/plugin.js +996 -0
  74. package/dist/plugin.js.map +1 -0
  75. package/ios/Plugin/AVBidirectionalQueuePlayer.swift +269 -0
  76. package/ios/Plugin/AudioTrack.swift +63 -0
  77. package/ios/Plugin/Constants.swift +39 -0
  78. package/ios/Plugin/DispatchQueue.swift +47 -0
  79. package/ios/Plugin/Info.plist +24 -0
  80. package/ios/Plugin/Plugin.h +10 -0
  81. package/ios/Plugin/Plugin.m +30 -0
  82. package/ios/Plugin/Plugin.swift +208 -0
  83. package/ios/Plugin/RmxAudioPlayer.swift +1150 -0
  84. package/ios/Plugin.xcodeproj/project.pbxproj +574 -0
  85. package/ios/Plugin.xcworkspace/contents.xcworkspacedata +10 -0
  86. package/ios/Plugin.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +8 -0
  87. package/ios/PluginTests/Info.plist +22 -0
  88. package/ios/PluginTests/PluginTests.swift +35 -0
  89. package/ios/Podfile +16 -0
  90. package/package.json +89 -0
@@ -0,0 +1,77 @@
1
+ /*
2
+ * Apache 2.0 License
3
+ *
4
+ * Copyright (c) Sebastian Katzer 2017
5
+ *
6
+ * This file contains Original Code and/or Modifications of Original Code
7
+ * as defined in and that are subject to the Apache License
8
+ * Version 2.0 (the 'License'). You may not use this file except in
9
+ * compliance with the License. Please obtain a copy of the License at
10
+ * http://opensource.org/licenses/Apache-2.0/ and read it before using this
11
+ * file.
12
+ *
13
+ * The Original Code and all software distributed under the License are
14
+ * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15
+ * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16
+ * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17
+ * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18
+ * Please see the License for the specific language governing rights and
19
+ * limitations under the License.
20
+ */
21
+ package org.dwbn.plugins.playlist.manager
22
+
23
+ import android.content.Context
24
+ import org.json.JSONObject
25
+
26
+ class Options {
27
+ /**
28
+ * Wrapped JSON object.
29
+ */
30
+ // The original JSON object
31
+ val dict: JSONObject
32
+
33
+ /**
34
+ * Application context.
35
+ */
36
+ // The application context
37
+ val context: Context
38
+
39
+ /**
40
+ * Constructor
41
+ *
42
+ * @param context The application context.
43
+ */
44
+ constructor(context: Context) {
45
+ this.context = context
46
+ dict = JSONObject()
47
+ }
48
+
49
+ /**
50
+ * Constructor
51
+ *
52
+ * @param context The application context.
53
+ * @param options The options dict map.
54
+ */
55
+ constructor(context: Context, options: JSONObject) {
56
+ this.context = context
57
+ dict = options
58
+ }
59
+
60
+ /**
61
+ * JSON object as string.
62
+ */
63
+ override fun toString(): String {
64
+ return dict.toString()
65
+ }
66
+
67
+ /**
68
+ * icon resource ID for the local notification.
69
+ */
70
+ val icon: String
71
+ get() = dict.optString("icon", DEFAULT_ICON)
72
+
73
+ companion object {
74
+ // Default icon path
75
+ private const val DEFAULT_ICON = "icon"
76
+ }
77
+ }
@@ -0,0 +1,308 @@
1
+ package org.dwbn.plugins.playlist.manager
2
+
3
+ import android.app.Application
4
+ import android.util.Log
5
+ import androidx.annotation.FloatRange
6
+ import androidx.annotation.IntRange
7
+ import com.devbrackets.android.exomedia.listener.OnErrorListener
8
+ import com.devbrackets.android.playlistcore.api.MediaPlayerApi
9
+ import com.devbrackets.android.playlistcore.manager.ListPlaylistManager
10
+ import org.dwbn.plugins.playlist.PlaylistItemOptions
11
+ import org.dwbn.plugins.playlist.TrackRemovalItem
12
+ import org.dwbn.plugins.playlist.data.AudioTrack
13
+ import org.dwbn.plugins.playlist.playlist.AudioApi
14
+ import org.dwbn.plugins.playlist.service.MediaService
15
+ import java.lang.ref.WeakReference
16
+ import java.util.*
17
+
18
+ /**
19
+ * A PlaylistManager that extends the [ListPlaylistManager] for use with the
20
+ * [MediaService] which extends [com.devbrackets.android.playlistcore.service.BasePlaylistService].
21
+ */
22
+ class PlaylistManager(application: Application) :
23
+ ListPlaylistManager<AudioTrack>(application, MediaService::class.java), OnErrorListener {
24
+ private val audioTracks: MutableList<AudioTrack> = ArrayList()
25
+ private var volumeLeft = 1.0f
26
+ private var volumeRight = 1.0f
27
+ private var playbackSpeed = 1.0f
28
+ var loop = false
29
+ var isShouldStopPlaylist = false
30
+ var currentErrorTrack: AudioTrack? = null
31
+
32
+ // Really need a way to propagate the settings through the app
33
+ var resetStreamOnPause = true
34
+ var options: Options
35
+ private var mediaControlsListener = WeakReference<MediaControlsListener?>(null)
36
+ private var errorListener = WeakReference<OnErrorListener?>(null)
37
+ private var currentMediaPlayer: WeakReference<MediaPlayerApi<AudioTrack>?>? =
38
+ WeakReference(null)
39
+
40
+ fun setOnErrorListener(listener: OnErrorListener?) {
41
+ errorListener = WeakReference(listener)
42
+ }
43
+
44
+ fun setMediaControlsListener(listener: MediaControlsListener?) {
45
+ mediaControlsListener = WeakReference(listener)
46
+ }
47
+
48
+ val isPlaying: Boolean
49
+ get() = playlistHandler != null && playlistHandler!!.currentMediaPlayer != null && playlistHandler!!.currentMediaPlayer!!.isPlaying
50
+
51
+ override fun onError(e: Exception?): Boolean {
52
+
53
+ if (e != null && errorListener.get() != null) {
54
+ Log.i(TAG, "onError: $e")
55
+ errorListener.get()!!.onError(e)
56
+ }
57
+ return true
58
+ }
59
+
60
+ /*
61
+ * isNextAvailable, getCurrentItem, and next() are overridden because there is
62
+ * a glaring bug in playlist core where when an item completes, isNextAvailable and
63
+ * getCurrentItem return wildly contradictory things, resulting in endless repeat
64
+ * of the last item in the playlist.
65
+ */
66
+ override val isNextAvailable: Boolean
67
+ get() {
68
+ if (itemCount <= 1) {
69
+ return false;
70
+ }
71
+ val isAtEnd = currentPosition + 1 >= itemCount
72
+ val isConstrained = currentPosition + 1 in 0 until itemCount
73
+ return if (isAtEnd) {
74
+ loop
75
+ } else isConstrained
76
+ }
77
+
78
+ override operator fun next(): AudioTrack? {
79
+ if (isNextAvailable) {
80
+ val isAtEnd = currentPosition + 1 >= itemCount
81
+ currentPosition = if (isAtEnd && loop) {
82
+ 0
83
+ } else {
84
+ (currentPosition + 1).coerceAtMost(itemCount)
85
+ }
86
+ } else {
87
+ if (loop) {
88
+ currentPosition = INVALID_POSITION
89
+ } else {
90
+ isShouldStopPlaylist = true
91
+ return null
92
+ }
93
+ }
94
+
95
+ return currentItem
96
+ }
97
+
98
+
99
+ /*
100
+ * List management
101
+ */
102
+ fun setAllItems(items: List<AudioTrack>?, options: PlaylistItemOptions) {
103
+ clearItems()
104
+ addAllItems(items)
105
+ currentPosition = 0
106
+ // If the options said to start from a specific position, do so.
107
+ var seekStart: Long = 0
108
+ if (options.playFromPosition > 0) {
109
+ seekStart = options.playFromPosition
110
+ } else if (options.retainPosition) {
111
+ val progress = currentProgress
112
+ if (progress != null) {
113
+ seekStart = progress.position
114
+ }
115
+ }
116
+
117
+ // If the options said to start from a specific id, do so.
118
+ var idStart: String? = null
119
+ if (options.playFromId != null) {
120
+ idStart = options.playFromId
121
+ }
122
+ if (idStart != null && "" != idStart) {
123
+ val code = idStart.hashCode()
124
+ setCurrentItem(code.toLong())
125
+ }
126
+
127
+ // We assume that if the playlist is fully loaded in one go,
128
+ // that the next thing to happen will be to play. So let's start
129
+ // paused, which will allow the player to pre-buffer until the
130
+ // user says Go.
131
+ beginPlayback(seekStart, options.startPaused)
132
+ }
133
+
134
+ fun addItem(item: AudioTrack?) {
135
+ if (item == null) {
136
+ return
137
+ }
138
+ val countBefore = audioTracks.size;
139
+ audioTracks.add(item)
140
+ items = audioTracks
141
+ if (countBefore == 0) {
142
+ currentPosition = 0
143
+ beginPlayback(1, true)
144
+ }
145
+ if (this.playlistHandler != null) {
146
+ this.playlistHandler!!.updateMediaControls()
147
+ }
148
+ }
149
+
150
+ fun addAllItems(its: List<AudioTrack>?) {
151
+ val currentItem = currentItem // may be null
152
+ audioTracks.addAll(its.orEmpty())
153
+ items =
154
+ audioTracks // not *strictly* needed since they share the reference, but for good measure..
155
+ currentPosition = audioTracks.indexOf(currentItem)
156
+ }
157
+
158
+ fun removeItem(index: Int, itemId: String): AudioTrack? {
159
+ val wasPlaying = isPlaying
160
+ if (playlistHandler != null) {
161
+ playlistHandler!!.pause(true)
162
+ }
163
+ var currentPosition = currentPosition
164
+ var foundItem: AudioTrack? = null
165
+ var removingCurrent = false
166
+
167
+ // Get the current playback position in milliseconds before removing items
168
+ val progress = currentProgress
169
+ val seekPosition: Long = if (progress != null) progress.position else 0
170
+
171
+ // If isPlaying is true, and currentItem is not null,
172
+ // that implies that currentItem is the currently playing item.
173
+ // If removingCurrent gets set to true, we are removing the currently playing item,
174
+ // and we need to restart playback once we do.
175
+ val resolvedIndex = resolveItemPosition(index, itemId)
176
+ if (resolvedIndex >= 0) {
177
+ foundItem = audioTracks[resolvedIndex]
178
+ if (foundItem == currentItem) {
179
+ removingCurrent = true
180
+ }
181
+ audioTracks.removeAt(resolvedIndex)
182
+ }
183
+ items = audioTracks
184
+ currentPosition = if (removingCurrent) currentPosition else audioTracks.indexOf(currentItem)
185
+ // If removing the current item, start from beginning (0), otherwise preserve playback position
186
+ val seekStart = if (removingCurrent) 0 else seekPosition
187
+ beginPlayback(seekStart, !wasPlaying)
188
+ if (this.playlistHandler != null) {
189
+ this.playlistHandler!!.updateMediaControls()
190
+ }
191
+ return foundItem
192
+ }
193
+
194
+ fun removeAllItems(its: ArrayList<TrackRemovalItem>): ArrayList<AudioTrack> {
195
+ val removedTracks = ArrayList<AudioTrack>()
196
+ val wasPlaying = isPlaying
197
+ if (playlistHandler != null) {
198
+ playlistHandler!!.pause(true)
199
+ }
200
+ var currentPosition = currentPosition
201
+ val currentItem = currentItem // may be null
202
+ var removingCurrent = false
203
+
204
+ // Get the current playback position in milliseconds before removing items
205
+ val progress = currentProgress
206
+ val seekPosition: Long = if (progress != null) progress.position else 0
207
+
208
+ for (item in its) {
209
+ val resolvedIndex = resolveItemPosition(item.trackIndex, item.trackId)
210
+ if (resolvedIndex >= 0) {
211
+ val foundItem = audioTracks[resolvedIndex]
212
+ if (foundItem == currentItem) {
213
+ removingCurrent = true
214
+ }
215
+ removedTracks.add(foundItem)
216
+ audioTracks.removeAt(resolvedIndex)
217
+ }
218
+ }
219
+ items = audioTracks
220
+ currentPosition = if (removingCurrent) currentPosition else audioTracks.indexOf(currentItem)
221
+ // If removing the current item, start from beginning (0), otherwise preserve playback position
222
+ val seekStart = if (removingCurrent) 0 else seekPosition
223
+ beginPlayback(seekStart, !wasPlaying)
224
+ return removedTracks
225
+ }
226
+
227
+ fun clearItems() {
228
+ playlistHandler?.stop()
229
+ audioTracks.clear()
230
+ items = audioTracks
231
+ currentPosition = INVALID_POSITION
232
+ }
233
+
234
+ fun getAllItems(): List<AudioTrack> {
235
+ return audioTracks.toList()
236
+ }
237
+
238
+ private fun resolveItemPosition(trackIndex: Int, trackId: String): Int {
239
+ var resolvedPosition = -1
240
+ if (trackIndex >= 0 && trackIndex < audioTracks.size) {
241
+ resolvedPosition = trackIndex
242
+ } else if ("" != trackId) {
243
+ val itemPos = getPositionForItem(trackId.hashCode().toLong())
244
+ if (itemPos != INVALID_POSITION) {
245
+ resolvedPosition = itemPos
246
+ }
247
+ }
248
+ return resolvedPosition
249
+ }
250
+
251
+ fun getVolumeLeft(): Float {
252
+ return volumeLeft
253
+ }
254
+
255
+ fun getVolumeRight(): Float {
256
+ return volumeRight
257
+ }
258
+
259
+ fun setVolume(
260
+ @FloatRange(from = 0.0, to = 1.0) left: Float,
261
+ @FloatRange(from = 0.0, to = 1.0) right: Float
262
+ ) {
263
+ volumeLeft = left
264
+ volumeRight = right
265
+ if (currentMediaPlayer != null && currentMediaPlayer!!.get() != null) {
266
+ Log.i("PlaylistManager", "setVolume completing with volume = $left")
267
+ currentMediaPlayer!!.get()!!.setVolume(volumeLeft, volumeRight)
268
+ }
269
+ }
270
+
271
+ fun getPlaybackSpeed(): Float {
272
+ return playbackSpeed
273
+ }
274
+
275
+ fun setPlaybackSpeed(@FloatRange(from = 0.0625, to = 16.0) speed: Float) {
276
+ val validSpeed = speed.coerceIn(0.0625f, 16.0f)
277
+ playbackSpeed = validSpeed
278
+ playlistHandler?.let { handler ->
279
+ handler.currentMediaPlayer?.let { mediaPlayer ->
280
+ if (mediaPlayer is AudioApi) {
281
+ Log.i(TAG, "setPlaybackSpeed completing with speed = $validSpeed")
282
+ mediaPlayer.setPlaybackSpeed(playbackSpeed)
283
+ }
284
+ }
285
+ }
286
+ }
287
+
288
+ fun beginPlayback(@IntRange(from = 0) seekPosition: Long, startPaused: Boolean) {
289
+ currentItem ?: return
290
+ super.play(seekPosition, startPaused)
291
+ try {
292
+ setVolume(volumeLeft, volumeRight)
293
+ setPlaybackSpeed(playbackSpeed)
294
+ } catch (e: Exception) {
295
+ Log.w(TAG, "beginPlayback: Error setting volume or playback speed: " + e.message)
296
+ }
297
+ }
298
+
299
+ companion object {
300
+ private const val TAG = "PlaylistManager"
301
+ }
302
+
303
+ init {
304
+ setParameters(audioTracks, 0)
305
+ options = Options(application.baseContext)
306
+ }
307
+
308
+ }
@@ -0,0 +1,26 @@
1
+ package org.dwbn.plugins.playlist.notification
2
+
3
+ import android.annotation.SuppressLint
4
+ import android.app.PendingIntent
5
+ import android.app.PendingIntent.FLAG_UPDATE_CURRENT
6
+ import android.app.PendingIntent.FLAG_IMMUTABLE
7
+ import android.content.Context
8
+ import android.content.Intent
9
+ import com.devbrackets.android.playlistcore.components.notification.DefaultPlaylistNotificationProvider
10
+
11
+ class PlaylistNotificationProvider(context: Context?) : DefaultPlaylistNotificationProvider(context!!) {
12
+ override val clickPendingIntent: PendingIntent?
13
+ @SuppressLint("UnspecifiedImmutableFlag")
14
+ get() {
15
+ val context = context
16
+ val pkgName = context.packageName
17
+ val intent = context
18
+ .packageManager
19
+ .getLaunchIntentForPackage(pkgName)
20
+ intent!!.addFlags(
21
+ Intent.FLAG_ACTIVITY_REORDER_TO_FRONT or Intent.FLAG_ACTIVITY_SINGLE_TOP)
22
+ return PendingIntent.getActivity(this.context,
23
+ 0, intent, FLAG_UPDATE_CURRENT or FLAG_IMMUTABLE
24
+ )
25
+ }
26
+ }
@@ -0,0 +1,114 @@
1
+
2
+ package org.dwbn.plugins.playlist.playlist
3
+
4
+ import android.content.Context
5
+ import android.media.AudioManager
6
+ import android.net.Uri
7
+ import android.os.PowerManager
8
+ import androidx.annotation.FloatRange
9
+ import androidx.annotation.IntRange
10
+ import androidx.annotation.OptIn
11
+ import androidx.media3.common.AudioAttributes
12
+ import androidx.media3.common.C
13
+ import androidx.media3.common.util.UnstableApi
14
+ import androidx.media3.exoplayer.util.EventLogger
15
+ import com.devbrackets.android.exomedia.AudioPlayer
16
+ import com.devbrackets.android.exomedia.listener.OnErrorListener
17
+ import com.devbrackets.android.playlistcore.manager.BasePlaylistManager
18
+ import org.dwbn.plugins.playlist.data.AudioTrack
19
+ import java.lang.ref.WeakReference
20
+ import java.util.concurrent.locks.ReentrantLock
21
+
22
+ @OptIn(UnstableApi::class)
23
+ class AudioApi(context: Context) : BaseMediaApi() {
24
+ private val audioPlayer: AudioPlayer = AudioPlayer(context.applicationContext)
25
+
26
+ private val errorListenersLock = ReentrantLock(true)
27
+ private val errorListeners = ArrayList<WeakReference<OnErrorListener>>()
28
+
29
+ override val isPlaying: Boolean
30
+ get() = audioPlayer.isPlaying
31
+
32
+ override val handlesOwnAudioFocus: Boolean
33
+ get() = false
34
+
35
+ override val currentPosition: Long
36
+ get() = if (prepared) audioPlayer.currentPosition else 0
37
+
38
+ override val duration: Long
39
+ get() = if (prepared) audioPlayer.duration else 0
40
+
41
+ override val bufferedPercent: Int
42
+ get() = bufferPercent
43
+
44
+ init {
45
+ audioPlayer.setOnErrorListener(this)
46
+ audioPlayer.setOnPreparedListener(this)
47
+ audioPlayer.setOnCompletionListener(this)
48
+ audioPlayer.setOnSeekCompletionListener(this)
49
+ audioPlayer.setOnBufferUpdateListener(this)
50
+
51
+ audioPlayer.setWakeLevel(PowerManager.PARTIAL_WAKE_LOCK)
52
+ audioPlayer.setAudioAttributes(getAudioAttributes(C.USAGE_MEDIA, C.AUDIO_CONTENT_TYPE_MUSIC))
53
+ audioPlayer.setAnalyticsListener(EventLogger())
54
+ }
55
+
56
+ override fun play() {
57
+ audioPlayer.start()
58
+ }
59
+
60
+ override fun pause() {
61
+ audioPlayer.pause()
62
+ }
63
+
64
+ override fun stop() {
65
+ audioPlayer.stop()
66
+ }
67
+
68
+ override fun reset() {
69
+ audioPlayer.reset()
70
+ }
71
+
72
+ override fun release() {
73
+ audioPlayer.release()
74
+ }
75
+
76
+ override fun setVolume(@FloatRange(from = 0.0, to = 1.0) left: Float, @FloatRange(from = 0.0, to = 1.0) right: Float) {
77
+ audioPlayer.volume = (left + right) / 2
78
+ }
79
+
80
+ override fun seekTo(@IntRange(from = 0L) milliseconds: Long) {
81
+ audioPlayer.seekTo(milliseconds.toInt().toLong())
82
+ }
83
+
84
+ override fun handlesItem(item: AudioTrack): Boolean {
85
+ return item.mediaType == BasePlaylistManager.AUDIO
86
+ }
87
+
88
+ override fun playItem(item: AudioTrack) {
89
+ try {
90
+ bufferPercent = 0
91
+ audioPlayer.setMedia(Uri.parse(if (item.downloaded) item.downloadedMediaUri else item.mediaUrl))
92
+ } catch (e: Exception) {
93
+ //Purposefully left blank
94
+ }
95
+ }
96
+
97
+ fun setPlaybackSpeed(@FloatRange(from = 0.0, to = 1.0) speed: Float) {
98
+ audioPlayer.setPlaybackSpeed(speed)
99
+ }
100
+
101
+ fun addErrorListener(listener: OnErrorListener) {
102
+ errorListenersLock.lock()
103
+ errorListeners.add(WeakReference<OnErrorListener>(listener))
104
+ errorListenersLock.unlock()
105
+ }
106
+
107
+ @Suppress("SameParameterValue")
108
+ private fun getAudioAttributes(@C.AudioUsage usage: Int, @C.AudioContentType contentType: Int): AudioAttributes {
109
+ return AudioAttributes.Builder()
110
+ .setUsage(usage)
111
+ .setContentType(contentType)
112
+ .build()
113
+ }
114
+ }
@@ -0,0 +1,146 @@
1
+ package org.dwbn.plugins.playlist.playlist;
2
+
3
+ import android.app.Service;
4
+ import android.content.Context;
5
+ import android.util.Log;
6
+
7
+ import androidx.annotation.Nullable;
8
+
9
+ import com.devbrackets.android.playlistcore.api.MediaPlayerApi;
10
+ import com.devbrackets.android.playlistcore.api.PlaylistItem;
11
+ import com.devbrackets.android.playlistcore.components.audiofocus.AudioFocusProvider;
12
+ import com.devbrackets.android.playlistcore.components.audiofocus.DefaultAudioFocusProvider;
13
+ import com.devbrackets.android.playlistcore.components.image.ImageProvider;
14
+ import com.devbrackets.android.playlistcore.components.mediacontrols.DefaultMediaControlsProvider;
15
+ import com.devbrackets.android.playlistcore.components.mediacontrols.MediaControlsProvider;
16
+ import com.devbrackets.android.playlistcore.components.mediasession.DefaultMediaSessionProvider;
17
+ import com.devbrackets.android.playlistcore.components.mediasession.MediaSessionProvider;
18
+ import com.devbrackets.android.playlistcore.components.playlisthandler.DefaultPlaylistHandler;
19
+ import com.devbrackets.android.playlistcore.manager.BasePlaylistManager;
20
+
21
+ import org.dwbn.plugins.playlist.RmxAudioPlayer;
22
+ import org.dwbn.plugins.playlist.data.AudioTrack;
23
+ import org.dwbn.plugins.playlist.manager.PlaylistManager;
24
+ import org.dwbn.plugins.playlist.notification.PlaylistNotificationProvider;
25
+ import org.jetbrains.annotations.NotNull;
26
+
27
+
28
+ public class AudioPlaylistHandler<I extends PlaylistItem, M extends BasePlaylistManager<I>>
29
+ extends DefaultPlaylistHandler<I, M> {
30
+
31
+ private static final String TAG = "PlaylistAudioPlaylistHandler";
32
+
33
+ AudioPlaylistHandler(
34
+ Context context,
35
+ Class<? extends Service> serviceClass,
36
+ M playlistManager,
37
+ ImageProvider<I> imageProvider,
38
+ com.devbrackets.android.playlistcore.components.notification.PlaylistNotificationProvider notificationProvider,
39
+ MediaSessionProvider mediaSessionProvider,
40
+ MediaControlsProvider mediaControlsProvider,
41
+ AudioFocusProvider<I> audioFocusProvider,
42
+ @Nullable Listener<I> listener
43
+ ) {
44
+ super(context, serviceClass, playlistManager, imageProvider, notificationProvider,
45
+ mediaSessionProvider, mediaControlsProvider, audioFocusProvider, listener);
46
+ // Lmao this entire class exists for the sake of this one line
47
+ // The default value is 30fps (e.g 33ms), which would overwhelm the Cordova webview with messages
48
+ // Ideally we could make this configurable.
49
+ getMediaProgressPoll().setProgressPollDelay(1000);
50
+ }
51
+
52
+ public void next() {
53
+ if (!getPlaylistManager().isNextAvailable()) {
54
+ return;
55
+ }
56
+ getPlaylistManager().next();
57
+ startItemPlayback(0, !this.isPlaying());
58
+ }
59
+
60
+ public void previous() {
61
+ if (!getPlaylistManager().isPreviousAvailable()) {
62
+ return;
63
+ }
64
+ getPlaylistManager().previous();
65
+ startItemPlayback(0, !this.isPlaying());
66
+ }
67
+
68
+ @Override
69
+ public void onPrepared(@NotNull MediaPlayerApi<I> mediaPlayer) {
70
+ super.onPrepared(mediaPlayer);
71
+ }
72
+
73
+ @Override
74
+ public boolean onError(@NotNull MediaPlayerApi<I> mediaPlayer) {
75
+ ((PlaylistManager)getPlaylistManager()).setCurrentErrorTrack((AudioTrack) getCurrentPlaylistItem());
76
+ int currentIndex = getPlaylistManager().getCurrentPosition();
77
+ int currentErrorCount = getSequentialErrors();
78
+
79
+ super.onError(mediaPlayer);
80
+ // Do not set startPaused to false if we are at the first item.
81
+ // For all other items, the user MUST have triggered playback;
82
+ // for item 0, they will never have done so at this point (since the tracks
83
+ // are auto-buffered when a list is loaded).
84
+ // This is a bit of a guess. What happens of tracks 2,3,4 are also broken?
85
+ // User has no network? The only way to be *certain* is to capture all user interaction
86
+ // input points and create a global flag "somewhere" that says the user has tried to play.
87
+ // Maintaining that would be a nightmare.
88
+ if (currentIndex > 0 && currentErrorCount <= 3) {
89
+ Log.e(TAG, "ListHandler error: setting startPaused to false");
90
+ setStartPaused(false);
91
+ }
92
+
93
+ return false;
94
+ }
95
+
96
+ @Override
97
+ public void onSeekComplete(MediaPlayerApi<I> mediaPlayer) {
98
+ Log.i(TAG, "onSeekComplete! " + mediaPlayer.getCurrentPosition());
99
+ getCurrentMediaProgress().update(mediaPlayer.getCurrentPosition(), mediaPlayer.getBufferedPercent(), mediaPlayer.getDuration());
100
+ super.onSeekComplete(mediaPlayer);
101
+ }
102
+
103
+ @Override
104
+ public void onCompletion(@NotNull MediaPlayerApi<I> mediaPlayer) {
105
+ ((RmxAudioPlayer)super.getPlaylistManager().getPlaybackStatusListener()).onCompletion((AudioTrack) getCurrentPlaylistItem());
106
+ Log.i("AudioPlaylistHandler", "onCompletion");
107
+ // This is called when a single item completes playback.
108
+ // For now, the superclass does the right thing, but we may need to override.
109
+ super.onCompletion(mediaPlayer);
110
+ }
111
+
112
+
113
+ public static class Builder<I extends PlaylistItem, M extends BasePlaylistManager<I>> {
114
+
115
+ Context context;
116
+ Class<? extends Service> serviceClass;
117
+ M playlistManager;
118
+ ImageProvider<I> imageProvider;
119
+
120
+ com.devbrackets.android.playlistcore.components.notification.PlaylistNotificationProvider notificationProvider = null;
121
+ MediaSessionProvider mediaSessionProvider = null;
122
+ MediaControlsProvider mediaControlsProvider = null;
123
+ AudioFocusProvider<I> audioFocusProvider = null;
124
+ Listener<I> listener;
125
+ public Builder(Context context, Class<? extends Service> serviceClass,
126
+ M playlistManager, ImageProvider<I> imageProvider, Listener<I> listener) {
127
+ this.context = context;
128
+ this.serviceClass = serviceClass;
129
+ this.playlistManager = playlistManager;
130
+ this.imageProvider = imageProvider;
131
+ this.listener = listener;
132
+ }
133
+
134
+ public AudioPlaylistHandler<I, M> build() {
135
+ return new AudioPlaylistHandler<>(context,
136
+ serviceClass,
137
+ playlistManager,
138
+ imageProvider,
139
+ notificationProvider != null ? notificationProvider : new PlaylistNotificationProvider(context),
140
+ mediaSessionProvider != null ? mediaSessionProvider : new DefaultMediaSessionProvider(context, serviceClass),
141
+ mediaControlsProvider != null ? mediaControlsProvider : new DefaultMediaControlsProvider(context),
142
+ audioFocusProvider != null ? audioFocusProvider : new DefaultAudioFocusProvider<I>(context),
143
+ listener);
144
+ }
145
+ }
146
+ }