@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,487 @@
1
+ package org.dwbn.plugins.playlist;
2
+
3
+ import android.util.Log;
4
+
5
+ import androidx.annotation.NonNull;
6
+ import androidx.annotation.Nullable;
7
+ import androidx.annotation.OptIn;
8
+ import androidx.media3.common.util.UnstableApi;
9
+ import androidx.media3.exoplayer.ExoPlaybackException;
10
+
11
+ import com.devbrackets.android.exomedia.listener.OnErrorListener;
12
+ import com.devbrackets.android.playlistcore.data.MediaProgress;
13
+ import com.devbrackets.android.playlistcore.data.PlaybackState;
14
+ import com.devbrackets.android.playlistcore.data.PlaylistItemChange;
15
+ import com.devbrackets.android.playlistcore.listener.PlaybackStatusListener;
16
+ import com.devbrackets.android.playlistcore.listener.PlaylistListener;
17
+ import com.devbrackets.android.playlistcore.listener.ProgressListener;
18
+
19
+ import org.dwbn.plugins.playlist.data.AudioTrack;
20
+ import org.dwbn.plugins.playlist.manager.MediaControlsListener;
21
+ import org.dwbn.plugins.playlist.manager.Options;
22
+ import org.dwbn.plugins.playlist.manager.PlaylistManager;
23
+ import org.json.JSONException;
24
+ import org.json.JSONObject;
25
+
26
+ /**
27
+ * The implementation of this player borrows from ExoMedia's demo example
28
+ * and utilizes heavily those classes, basically because that is "the" way
29
+ * to actually use ExoMedia.
30
+ */
31
+ public class RmxAudioPlayer implements PlaybackStatusListener<AudioTrack>,
32
+ PlaylistListener<AudioTrack>, ProgressListener, OnErrorListener, MediaControlsListener {
33
+
34
+ public static String TAG = "PlaylistRmxAudioPlayer";
35
+
36
+ // PlaylistCore requires this but we don't use it
37
+ // It would be used to switch between playlists. I guess we could
38
+ // support that in the future, might be cool.
39
+ private static final int PLAYLIST_ID = 32;
40
+ private PlaylistManager playlistManager;
41
+ private final OnStatusReportListener statusListener;
42
+
43
+ private int lastBufferPercent = 0;
44
+ private long lastDuration = 0;
45
+ private boolean trackLoaded = false;
46
+ private boolean resetStreamOnPause = true;
47
+ private String pendingSelectionTrackId = null;
48
+ private boolean suppressSelectionPlaybackEvents = false;
49
+ private final App app;
50
+
51
+ public RmxAudioPlayer(@NonNull OnStatusReportListener statusListener, App context) {
52
+ // AudioPlayerPlugin and RmxAudioPlayer are separate classes in order to increase
53
+ // the portability of this code.
54
+ // Because AudioPlayerPlugin itself holds a strong reference to this class,
55
+ // we can hold a strong reference to this shared callback. Normally not a good idea
56
+ // but these two objects will always live together (And the plugin couldn't function
57
+ // at all if this one gets garbage collected).
58
+ this.statusListener = statusListener;
59
+ this.app = context;
60
+
61
+ app.resetPlaylistManager();
62
+ getPlaylistManager();
63
+ playlistManager.setId(PLAYLIST_ID);
64
+ playlistManager.setPlaybackStatusListener(this);
65
+ playlistManager.setOnErrorListener(this);
66
+ playlistManager.setMediaControlsListener(this);
67
+ }
68
+
69
+ public PlaylistManager getPlaylistManager() {
70
+ playlistManager = app.getPlaylistManager();
71
+ return playlistManager;
72
+ }
73
+
74
+ public boolean getResetStreamOnPause() {
75
+ return resetStreamOnPause;
76
+ }
77
+
78
+ public void setResetStreamOnPause(boolean val) {
79
+ resetStreamOnPause = val;
80
+ getPlaylistManager().setResetStreamOnPause(getResetStreamOnPause());
81
+ }
82
+
83
+ public void setOptions(JSONObject val) {
84
+ Options options = new Options(app, val);
85
+ getPlaylistManager().setOptions(options);
86
+ }
87
+
88
+ public void prepareForTrackSelection(@Nullable String trackId) {
89
+ pendingSelectionTrackId = trackId;
90
+ suppressSelectionPlaybackEvents = trackId != null;
91
+ }
92
+
93
+ public void clearTrackSelectionSuppression() {
94
+ pendingSelectionTrackId = null;
95
+ suppressSelectionPlaybackEvents = false;
96
+ }
97
+
98
+ private boolean isSuppressingSelection(@Nullable AudioTrack item) {
99
+ return suppressSelectionPlaybackEvents
100
+ && pendingSelectionTrackId != null
101
+ && item != null
102
+ && pendingSelectionTrackId.equals(item.getTrackId());
103
+ }
104
+
105
+ public float getVolume() {
106
+ return (getVolumeLeft() + getVolumeRight()) / 2f;
107
+ }
108
+
109
+ public float getVolumeLeft() {
110
+ return playlistManager.getVolumeLeft();
111
+ }
112
+
113
+ public float getVolumeRight() {
114
+ return playlistManager.getVolumeRight();
115
+ }
116
+
117
+ public void setVolume(float both) {
118
+ setVolume(both, both);
119
+ }
120
+
121
+ public void setVolume(float left, float right) {
122
+ playlistManager.setVolume(left, right);
123
+ }
124
+
125
+ public void onCompletion(AudioTrack item) {
126
+ if (item != null) {
127
+ String trackId = item.getTrackId();
128
+ JSONObject trackStatus = getPlayerStatus(item);
129
+ onStatus(RmxAudioStatusMessage.RMXSTATUS_COMPLETED, trackId, trackStatus);
130
+ }
131
+
132
+ if (!playlistManager.isNextAvailable()) {
133
+ onStatus(RmxAudioStatusMessage.RMXSTATUS_PLAYLIST_COMPLETED, "INVALID", null);
134
+ }
135
+ }
136
+
137
+ @Override
138
+ public void onPrevious(AudioTrack currentItem, int currentIndex) {
139
+ JSONObject param = new JSONObject();
140
+ String trackId = currentItem == null ? "NONE" : currentItem.getTrackId();
141
+
142
+ try {
143
+ param.put("currentIndex", currentIndex);
144
+ param.put("currentItem", currentItem != null ? currentItem.toDict() : null);
145
+ } catch (JSONException e) {
146
+ Log.i(TAG, "Error generating onPrevious status message: " + e.toString());
147
+ }
148
+
149
+ onStatus(RmxAudioStatusMessage.RMX_STATUS_SKIP_BACK, trackId, param);
150
+ }
151
+
152
+ @Override
153
+ public void onNext(AudioTrack currentItem, int currentIndex) {
154
+ JSONObject param = new JSONObject();
155
+ String trackId = currentItem == null ? "NONE" : currentItem.getTrackId();
156
+
157
+ try {
158
+ param.put("currentIndex", currentIndex);
159
+ param.put("currentItem", currentItem != null ? currentItem.toDict() : null);
160
+ } catch (JSONException e) {
161
+ Log.i(TAG, "Error generating onNext status message: " + e.toString());
162
+ }
163
+ onStatus(RmxAudioStatusMessage.RMX_STATUS_SKIP_FORWARD, trackId, param);
164
+ }
165
+
166
+ @OptIn(markerClass = UnstableApi.class) @Override
167
+ public boolean onError(Exception e) {
168
+ String errorMsg = e.toString();
169
+ RmxAudioErrorType errorType = RmxAudioErrorType.RMXERR_NONE_SUPPORTED;
170
+
171
+ if (e instanceof ExoPlaybackException) {
172
+ switch (((ExoPlaybackException) e).type) {
173
+ case ExoPlaybackException.TYPE_SOURCE:
174
+ errorMsg = "ExoPlaybackException.TYPE_SOURCE: " + ((ExoPlaybackException) e).getSourceException().getMessage();
175
+ break;
176
+ case ExoPlaybackException.TYPE_RENDERER:
177
+ errorType = RmxAudioErrorType.RMXERR_DECODE;
178
+ errorMsg = "ExoPlaybackException.TYPE_RENDERER: " + ((ExoPlaybackException) e).getRendererException().getMessage();
179
+ break;
180
+ case ExoPlaybackException.TYPE_UNEXPECTED:
181
+ errorType = RmxAudioErrorType.RMXERR_DECODE;
182
+ errorMsg = "ExoPlaybackException.TYPE_UNEXPECTED: " + ((ExoPlaybackException) e).getUnexpectedException().getMessage();
183
+ break;
184
+ }
185
+ }
186
+
187
+ AudioTrack errorItem = playlistManager.getCurrentErrorTrack();
188
+ String trackId = errorItem != null ? errorItem.getTrackId() : "INVALID";
189
+
190
+ Log.i(TAG, "Error playing audio track: [" + trackId + "]: " + errorMsg);
191
+ onError(errorType, trackId, errorMsg);
192
+ playlistManager.setCurrentErrorTrack(null);
193
+ return true;
194
+ }
195
+
196
+ @Override
197
+ public void onMediaPlaybackStarted(AudioTrack item, long currentPosition, long duration) {
198
+ Log.i(TAG, "onMediaPlaybackStarted: ==> " + item.getTitle() + ": " + currentPosition + "," + duration);
199
+ // this is the first place that valid duration is seen. Immediately before, we get the PLAYING status change,
200
+ // and before that, it announces PREPARING twice and all values are 0.
201
+ // Problem is, this method is only called if playback is already in progress when the track changes,
202
+ // which is useless in most cases. So, these values are actually handled in onProgressUpdated.
203
+ }
204
+
205
+ @Override
206
+ public void onItemPlaybackEnded(AudioTrack item) {
207
+ if (item != null) {
208
+ String trackId = item.getTrackId();
209
+ JSONObject trackStatus = getPlayerStatus(item);
210
+ onStatus(RmxAudioStatusMessage.RMXSTATUS_STOPPED, trackId, trackStatus);
211
+ }
212
+ }
213
+
214
+ @Override
215
+ public void onPlaylistEnded() {
216
+ Log.i(TAG, "onPlaylistEnded");
217
+ playlistManager.setShouldStopPlaylist(false);
218
+ }
219
+
220
+ @Override
221
+ public boolean onPlaylistItemChanged(@Nullable AudioTrack currentItem, boolean hasNext, boolean hasPrevious) {
222
+ JSONObject info = new JSONObject();
223
+ String trackId = currentItem == null ? "NONE" : currentItem.getTrackId();
224
+ try {
225
+ info.put("currentItem", currentItem != null ? currentItem.toDict() : null);
226
+ info.put("currentIndex", playlistManager.getCurrentPosition());
227
+ info.put("isAtEnd", !hasNext);
228
+ info.put("isAtBeginning", !hasPrevious);
229
+ info.put("hasNext", hasNext);
230
+ info.put("hasPrevious", hasPrevious);
231
+ } catch (JSONException e) {
232
+ Log.e(TAG, "Error creating onPlaylistItemChanged message: " + e.toString());
233
+ }
234
+
235
+ lastDuration = 0;
236
+ lastBufferPercent = 0;
237
+ trackLoaded = false;
238
+
239
+ onStatus(RmxAudioStatusMessage.RMXSTATUS_TRACK_CHANGED, trackId, info);
240
+ return true;
241
+ }
242
+
243
+ @Override
244
+ public boolean onPlaybackStateChanged(@NonNull PlaybackState playbackState) {
245
+ // in testing, I saw PREPARING, then PLAYING, and buffering happened
246
+ // during PLAYING. Tapping play/pause toggles PLAYING and PAUSED
247
+ // sending a seek command produces SEEKING here
248
+ // RETRIEVING is never sent.
249
+
250
+ AudioTrack currentItem = playlistManager.getCurrentItem();
251
+ JSONObject trackStatus = getPlayerStatus(currentItem);
252
+ boolean suppressSelection = isSuppressingSelection(currentItem);
253
+ Log.i("onPlaybackStateChanged", playbackState.toString() + ", " + trackStatus.toString() + ", " + currentItem);
254
+
255
+ switch (playbackState) {
256
+ case STOPPED:
257
+ if (suppressSelection) {
258
+ clearTrackSelectionSuppression();
259
+ break;
260
+ }
261
+ onStatus(RmxAudioStatusMessage.RMXSTATUS_STOPPED, "INVALID", null);
262
+ break;
263
+
264
+ case RETRIEVING: // these are all loading states
265
+ case PREPARING: {
266
+ if (currentItem != null && currentItem.getTrackId() != null) {
267
+ onStatus(RmxAudioStatusMessage.RMXSTATUS_LOADING, currentItem.getTrackId(), trackStatus);
268
+ }
269
+ break;
270
+ }
271
+ case SEEKING: {
272
+ MediaProgress progress = playlistManager.getCurrentProgress();
273
+ if (currentItem != null && currentItem.getTrackId() != null && progress != null) {
274
+ JSONObject info = new JSONObject();
275
+ try {
276
+ info.put("position", progress.getPosition() / 1000f);
277
+ onStatus(RmxAudioStatusMessage.RMXSTATUS_SEEK, currentItem.getTrackId(), info);
278
+ } catch (JSONException e) {
279
+ Log.e(TAG, "Error generating seeking status message: " + e.toString());
280
+ }
281
+ }
282
+ break;
283
+ }
284
+ case PLAYING:
285
+ if (suppressSelection) {
286
+ break;
287
+ }
288
+ if (currentItem != null && currentItem.getTrackId() != null) {
289
+ // Can also check here that duration == 0, because that is what happens on the first PLAYING invokation.
290
+ // We'll leave this for now.
291
+ if (!trackLoaded) {
292
+ onStatus(RmxAudioStatusMessage.RMXSTATUS_CANPLAY, currentItem.getTrackId(), trackStatus);
293
+ trackLoaded = true;
294
+ }
295
+ onStatus(RmxAudioStatusMessage.RMXSTATUS_PLAYING, currentItem.getTrackId(), trackStatus);
296
+ }
297
+ break;
298
+ case PAUSED:
299
+ if (suppressSelection) {
300
+ clearTrackSelectionSuppression();
301
+ break;
302
+ }
303
+ if (currentItem != null && currentItem.getTrackId() != null) {
304
+ onStatus(RmxAudioStatusMessage.RMXSTATUS_PAUSE, currentItem.getTrackId(), trackStatus);
305
+ }
306
+ break;
307
+ // we'll handle error in the listener. ExoMedia only raises this in the case of catastrophic player failure.
308
+ case ERROR:
309
+ if (suppressSelection) {
310
+ clearTrackSelectionSuppression();
311
+ }
312
+ default:
313
+ break;
314
+ }
315
+
316
+ return true;
317
+ }
318
+
319
+ @Override
320
+ public boolean onProgressUpdated(@NonNull MediaProgress progress) {
321
+ // Order matters here. We must update the item's duration and buffer before pulling the track status,
322
+ // because those values are adjusted to account for the buffering-reset in ExoPlayer.
323
+ AudioTrack currentItem = playlistManager.getCurrentItem();
324
+ PlaybackState playbackState = playlistManager.getCurrentPlaybackState();
325
+
326
+ if (currentItem != null) { // I mean, this call makes no sense otherwise..
327
+ currentItem.setDuration(progress.getDuration());
328
+ currentItem.setBufferPercent(progress.getBufferPercent());
329
+ currentItem.setBufferPercentFloat(progress.getBufferPercentFloat());
330
+
331
+ JSONObject trackStatus = getPlayerStatus(currentItem);
332
+ boolean suppressSelection = isSuppressingSelection(currentItem);
333
+
334
+ if (progress.getBufferPercent() != lastBufferPercent) {
335
+ if (progress.getBufferPercent() >= 100f) {
336
+ // Unlike iOS this will get raised continuously.
337
+ // Extracting the source event from playlistcore would be really hard.
338
+ // The gate above should do the trick.
339
+ onStatus(RmxAudioStatusMessage.RMXSTATUS_LOADED, currentItem.getTrackId(), trackStatus);
340
+ }
341
+
342
+ if (!trackLoaded) {
343
+ onStatus(RmxAudioStatusMessage.RMXSTATUS_CANPLAY, currentItem.getTrackId(), trackStatus);
344
+ trackLoaded = true;
345
+ }
346
+
347
+ onStatus(RmxAudioStatusMessage.RMXSTATUS_BUFFERING, currentItem.getTrackId(), trackStatus);
348
+ lastBufferPercent = progress.getBufferPercent();
349
+ }
350
+
351
+ if (lastDuration != progress.getDuration() && progress.getDuration() > 0) {
352
+ onStatus(RmxAudioStatusMessage.RMXSTATUS_DURATION, currentItem.getTrackId(), trackStatus);
353
+ lastDuration = progress.getDuration();
354
+ }
355
+
356
+ if (!suppressSelection
357
+ && (playbackState == PlaybackState.PLAYING || playbackState == PlaybackState.SEEKING)) {
358
+ onStatus(RmxAudioStatusMessage.RMXSTATUS_PLAYBACK_POSITION, currentItem.getTrackId(), trackStatus);
359
+ }
360
+ }
361
+
362
+ return true;
363
+ }
364
+
365
+ public JSONObject getPlayerStatus(@Nullable AudioTrack statusItem) {
366
+ // TODO: Make this its own object.
367
+ AudioTrack currentItem = statusItem != null ? statusItem : playlistManager.getCurrentItem();
368
+ PlaybackState playbackState = playlistManager.getCurrentPlaybackState();
369
+ MediaProgress progress = playlistManager.getCurrentProgress();
370
+
371
+ String status = "unknown";
372
+ switch (playbackState) {
373
+ case STOPPED: {
374
+ status = "stopped";
375
+ break;
376
+ }
377
+ case ERROR: {
378
+ status = "error";
379
+ break;
380
+ }
381
+ case RETRIEVING:
382
+ case SEEKING: // { status = "seeking"; break; } // seeking === loading
383
+ case PREPARING: {
384
+ status = "loading";
385
+ break;
386
+ }
387
+ case PLAYING: {
388
+ status = "playing";
389
+ break;
390
+ }
391
+ case PAUSED: {
392
+ status = "paused";
393
+ break;
394
+ }
395
+ default:
396
+ break;
397
+ }
398
+
399
+ String trackId = "";
400
+ boolean isStream = false;
401
+ float bufferPercentFloat = 0;
402
+ int bufferPercent = 0;
403
+ long duration = 0;
404
+ long position = 0;
405
+
406
+ // The media players hold onto their current playback position between songs,
407
+ // despite my efforts to reset it. So we will just filter out this state.
408
+ if (progress != null && playbackState != PlaybackState.RETRIEVING && playbackState != PlaybackState.PREPARING) {
409
+ position = progress.getPosition();
410
+ }
411
+
412
+ // the position and duration vals are in milliseconds.
413
+ if (currentItem != null) {
414
+ isStream = currentItem.isStream();
415
+ trackId = currentItem.getTrackId();
416
+ bufferPercentFloat = currentItem.getBufferPercentFloat(); // progress.
417
+ bufferPercent = currentItem.getBufferPercent(); // progress.
418
+ duration = currentItem.getDuration(); // progress.
419
+ }
420
+
421
+ JSONObject trackStatus = new JSONObject();
422
+ try {
423
+ trackStatus.put("trackId", trackId);
424
+ trackStatus.put("isStream", isStream);
425
+ trackStatus.put("currentIndex", playlistManager.getCurrentPosition());
426
+ trackStatus.put("status", status);
427
+ trackStatus.put("currentPosition", position / 1000.0);
428
+ trackStatus.put("duration", duration / 1000.0);
429
+ trackStatus.put("playbackPercent", duration > 0 ? (((double) position / duration) * 100.0) : 0);
430
+ trackStatus.put("bufferPercent", bufferPercent);
431
+ trackStatus.put("bufferStart", 0.0);
432
+ trackStatus.put("bufferEnd", (bufferPercentFloat * duration) / 1000.0);
433
+ } catch (JSONException e) {
434
+ Log.e(TAG, "Error generating player status: " + e.toString());
435
+ }
436
+
437
+ return trackStatus;
438
+ }
439
+
440
+ public void pause() {
441
+ Log.i(TAG, "Pausing, removing event listeners");
442
+ removePlaylistListeners();
443
+ }
444
+
445
+ public void resume() {
446
+ Log.i(TAG, "Resumed, wiring up event listeners");
447
+ getPlaylistManager();
448
+ registerPlaylistListeners();
449
+ //Makes sure to retrieve the current playback information
450
+ updateCurrentPlaybackInformation();
451
+ }
452
+
453
+ private void updateCurrentPlaybackInformation() {
454
+ PlaylistItemChange<AudioTrack> itemChange = playlistManager.getCurrentItemChange();
455
+ if (itemChange != null) {
456
+ onPlaylistItemChanged(itemChange.getCurrentItem(), itemChange.getHasNext(), itemChange.getHasPrevious());
457
+ }
458
+
459
+ PlaybackState currentPlaybackState = playlistManager.getCurrentPlaybackState();
460
+ if (currentPlaybackState != PlaybackState.STOPPED) {
461
+ onPlaybackStateChanged(currentPlaybackState);
462
+ }
463
+
464
+ MediaProgress mediaProgress = playlistManager.getCurrentProgress();
465
+ if (mediaProgress != null) {
466
+ onProgressUpdated(mediaProgress);
467
+ }
468
+ }
469
+
470
+ private void registerPlaylistListeners() {
471
+ playlistManager.registerPlaylistListener(this);
472
+ playlistManager.registerProgressListener(this);
473
+ }
474
+
475
+ private void removePlaylistListeners() {
476
+ playlistManager.unRegisterPlaylistListener(this);
477
+ playlistManager.unRegisterProgressListener(this);
478
+ }
479
+
480
+ private void onError(RmxAudioErrorType errorCode, String trackId, String message) {
481
+ statusListener.onError(errorCode, trackId, message);
482
+ }
483
+
484
+ private void onStatus(RmxAudioStatusMessage what, String trackId, JSONObject param) {
485
+ statusListener.onStatus(what, trackId, param);
486
+ }
487
+ }
@@ -0,0 +1,35 @@
1
+ package org.dwbn.plugins.playlist;
2
+
3
+ public enum RmxAudioStatusMessage {
4
+ RMXSTATUS_NONE(0),
5
+ RMXSTATUS_REGISTER(1),
6
+ RMXSTATUS_INIT(2),
7
+ RMXSTATUS_ERROR(5),
8
+
9
+ RMXSTATUS_LOADING(10),
10
+ RMXSTATUS_CANPLAY(11),
11
+ RMXSTATUS_LOADED(15),
12
+ RMXSTATUS_STALLED(20),
13
+ RMXSTATUS_BUFFERING(25),
14
+ RMXSTATUS_PLAYING(30),
15
+ RMXSTATUS_PAUSE(35),
16
+ RMXSTATUS_PLAYBACK_POSITION(40),
17
+ RMXSTATUS_SEEK(45),
18
+ RMXSTATUS_COMPLETED(50),
19
+ RMXSTATUS_DURATION(55),
20
+ RMXSTATUS_STOPPED(60),
21
+
22
+ RMX_STATUS_SKIP_FORWARD(90),
23
+ RMX_STATUS_SKIP_BACK(95),
24
+ RMXSTATUS_TRACK_CHANGED(100),
25
+ RMXSTATUS_PLAYLIST_COMPLETED(105),
26
+ RMXSTATUS_ITEM_ADDED(110),
27
+ RMXSTATUS_ITEM_REMOVED(115),
28
+ RMXSTATUS_PLAYLIST_CLEARED(120),
29
+
30
+ RMXSTATUS_VIEWDISAPPEAR(200); // just for testing
31
+
32
+ private final int id;
33
+ RmxAudioStatusMessage(int id) { this.id = id; }
34
+ public int getValue() { return id; }
35
+ };
@@ -0,0 +1,42 @@
1
+ package org.dwbn.plugins.playlist;
2
+
3
+ public interface RmxConstants {
4
+ String DOCUMENTS_SCHEME_PREFIX = "documents://";
5
+ String HTTP_SCHEME_PREFIX = "http://";
6
+ String HTTPS_SCHEME_PREFIX = "https://";
7
+ String CDVFILE_PREFIX = "cdvfile://";
8
+
9
+ // Playlist item management
10
+ String SET_OPTIONS = "setOptions";
11
+ String INITIALIZE = "initialize";
12
+ String SET_PLAYLIST_ITEMS = "setPlaylistItems";
13
+ String ADD_PLAYLIST_ITEM = "addItem";
14
+ String ADD_PLAYLIST_ITEMS = "addAllItems";
15
+ String REMOVE_PLAYLIST_ITEM = "removeItem";
16
+ String REMOVE_PLAYLIST_ITEMS = "removeItems";
17
+ String CLEAR_PLAYLIST_ITEMS = "clearAllItems";
18
+
19
+ // Playback
20
+ String PLAY = "play";
21
+ String PLAY_BY_INDEX = "playTrackByIndex";
22
+ String PLAY_BY_ID = "playTrackById";
23
+ String SELECT_BY_INDEX = "selectTrackByIndex";
24
+ String SELECT_BY_ID = "selectTrackById";
25
+ String PAUSE = "pause";
26
+ String SKIP_FORWARD = "skipForward";
27
+ String SKIP_BACK = "skipBack";
28
+ String SEEK = "seekTo";
29
+ String SEEK_TO_QUEUE_POSITION = "seekToQueuePosition";
30
+ String SET_PLAYBACK_RATE = "setPlaybackRate";
31
+ String SET_PLAYBACK_VOLUME = "setPlaybackVolume";
32
+ String SET_LOOP_ALL = "setLoopAll";
33
+
34
+ // Getters, should almost always be unneeded since the status is continually reported.
35
+ String GET_PLAYBACK_RATE = "getPlaybackRate";
36
+ String GET_PLAYBACK_VOLUME = "getPlaybackVolume";
37
+ String GET_PLAYBACK_POSITION = "getPlaybackPosition";
38
+ String GET_BUFFER_STATUS = "getCurrentBuffer";
39
+ String GET_QUEUE_POSITION = "getQueuePosition";
40
+
41
+ String RELEASE = "release";
42
+ }
@@ -0,0 +1,12 @@
1
+ package org.dwbn.plugins.playlist;
2
+
3
+ public class TrackRemovalItem {
4
+ public int trackIndex = -1;
5
+ public String trackId = "";
6
+
7
+ TrackRemovalItem(int index, String id) {
8
+ trackIndex = index;
9
+ trackId = id;
10
+ }
11
+ }
12
+
@@ -0,0 +1,94 @@
1
+ package org.dwbn.plugins.playlist.data
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
+ import org.json.JSONException
7
+ import org.json.JSONObject
8
+
9
+ class AudioTrack (private val config: JSONObject) : PlaylistItem {
10
+ var bufferPercentFloat = 0f
11
+ set(buff) {
12
+ // There is a bug in MediaProgress where if bufferPercent == 100 it sets bufferPercentFloat
13
+ // to 100 instead of to 1.
14
+ field = Math.min(Math.max(bufferPercentFloat, buff), 1f)
15
+ }
16
+ var bufferPercent = 0
17
+ set(buff) {
18
+ field = Math.max(bufferPercent, buff)
19
+ }
20
+ var duration: Long = 0
21
+ set(dur) {
22
+ field = Math.max(0, dur)
23
+ }
24
+
25
+ fun toDict(): JSONObject {
26
+ val info = JSONObject()
27
+ try {
28
+ info.put("trackId", trackId)
29
+ info.put("isStream", isStream)
30
+ info.put("assetUrl", mediaUrl)
31
+ info.put("albumArt", thumbnailUrl)
32
+ info.put("artist", artist)
33
+ info.put("album", album)
34
+ info.put("title", title)
35
+ } catch (e: JSONException) {
36
+ // I can think of no reason this would ever fail
37
+ }
38
+ return info
39
+ }
40
+
41
+ override val id: Long
42
+ get() =
43
+ if (trackId == null) {
44
+ 0
45
+ } else trackId.hashCode().toLong()
46
+
47
+ val isStream: Boolean
48
+ get() = config.optBoolean("isStream", false)
49
+
50
+ val trackId: String?
51
+ get() {
52
+ val trackId = config.optString("trackId")
53
+ return if (trackId == "") {
54
+ null
55
+ } else trackId
56
+ }
57
+
58
+ // Would really like to set this to true once the cache has it...
59
+ override val downloaded: Boolean
60
+ get() = false // Would really like to set this to true once the cache has it...
61
+
62
+ // ... at which point we can return a value here.
63
+ override val downloadedMediaUri: String?
64
+ get() = null // ... at which point we can return a value here.
65
+
66
+ @get:SupportedMediaType
67
+ override val mediaType: Int
68
+ get() = BasePlaylistManager.AUDIO
69
+
70
+ override val mediaUrl: String
71
+ get() = config.optString("assetUrl", "")
72
+
73
+ // we should have a good default here.
74
+ override val thumbnailUrl: String?
75
+ get() {
76
+ val albumArt = config.optString("albumArt")
77
+ return if (albumArt == "") {
78
+ null
79
+ } else albumArt // we should have a good default here.
80
+ }
81
+
82
+ override val artworkUrl: String?
83
+ get() = thumbnailUrl
84
+
85
+ override val title: String
86
+ get() = config.optString("title")
87
+
88
+ override val album: String
89
+ get() = config.optString("album")
90
+
91
+ override val artist: String
92
+ get() = config.optString("artist")
93
+
94
+ }
@@ -0,0 +1,13 @@
1
+ package org.dwbn.plugins.playlist.manager
2
+
3
+ import org.dwbn.plugins.playlist.data.AudioTrack
4
+
5
+ /*
6
+ * Interface to enable the PlaylistManager to send these events out.
7
+ * We could add more like play/pause/toggle/stop, but right now there
8
+ * are other ways to get all the other information.
9
+ */
10
+ interface MediaControlsListener {
11
+ fun onNext(currentItem: AudioTrack?, currentIndex: Int)
12
+ fun onPrevious(currentItem: AudioTrack?, currentIndex: Int)
13
+ }