capacitor-plugin-playlist 0.1.19 → 0.1.23
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/PlaylistPlugin.kt +4 -8
- package/android/src/main/java/org/dwbn/plugins/playlist/RmxAudioPlayer.java +397 -385
- package/android/src/main/java/org/dwbn/plugins/playlist/manager/PlaylistManager.kt +17 -12
- package/android/src/main/java/org/dwbn/plugins/playlist/notification/PlaylistNotificationProvider.kt +5 -1
- package/android/src/main/java/org/dwbn/plugins/playlist/service/MediaService.kt +1 -2
- package/dist/docs.json +3 -21
- package/dist/esm/Constants.js.map +1 -1
- package/dist/esm/RmxAudioPlayer.d.ts +4 -10
- package/dist/esm/RmxAudioPlayer.js +19 -22
- package/dist/esm/RmxAudioPlayer.js.map +1 -1
- package/dist/esm/definitions.d.ts +5 -4
- package/dist/esm/index.d.ts +1 -1
- package/dist/esm/index.js +1 -1
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/plugin.js +1 -0
- package/dist/esm/plugin.js.map +1 -1
- package/dist/esm/utils.d.ts +1 -1
- package/dist/esm/utils.js.map +1 -1
- package/dist/esm/web.d.ts +3 -3
- package/dist/esm/web.js +32 -27
- package/dist/esm/web.js.map +1 -1
- package/dist/plugin.cjs.js +47 -44
- package/dist/plugin.cjs.js.map +1 -1
- package/dist/plugin.js +48 -45
- package/dist/plugin.js.map +1 -1
- package/ios/Plugin/AVBidirectionalQueuePlayer.swift +18 -4
- package/ios/Plugin/AudioTrack.swift +0 -1
- package/ios/Plugin/DispatchQueue.swift +3 -3
- package/ios/Plugin/RmxAudioPlayer.swift +38 -39
- package/package.json +1 -1
- package/android/.gitignore +0 -1
- package/android/.npmignore +0 -1
- package/ios/.DS_Store +0 -0
|
@@ -5,433 +5,445 @@ import android.util.Log;
|
|
|
5
5
|
import androidx.annotation.NonNull;
|
|
6
6
|
import androidx.annotation.Nullable;
|
|
7
7
|
|
|
8
|
-
import
|
|
9
|
-
import org.dwbn.plugins.playlist.manager.MediaControlsListener;
|
|
10
|
-
import org.dwbn.plugins.playlist.manager.Options;
|
|
11
|
-
import org.dwbn.plugins.playlist.manager.PlaylistManager;
|
|
12
|
-
import org.json.JSONException;
|
|
13
|
-
import org.json.JSONObject;
|
|
14
|
-
|
|
8
|
+
import com.devbrackets.android.exomedia.listener.OnErrorListener;
|
|
15
9
|
import com.devbrackets.android.playlistcore.data.MediaProgress;
|
|
16
10
|
import com.devbrackets.android.playlistcore.data.PlaybackState;
|
|
17
11
|
import com.devbrackets.android.playlistcore.data.PlaylistItemChange;
|
|
12
|
+
import com.devbrackets.android.playlistcore.listener.PlaybackStatusListener;
|
|
18
13
|
import com.devbrackets.android.playlistcore.listener.PlaylistListener;
|
|
19
14
|
import com.devbrackets.android.playlistcore.listener.ProgressListener;
|
|
20
|
-
import com.devbrackets.android.playlistcore.listener.PlaybackStatusListener;
|
|
21
|
-
import com.devbrackets.android.exomedia.listener.OnErrorListener;
|
|
22
15
|
import com.google.android.exoplayer2.ExoPlaybackException;
|
|
23
16
|
|
|
17
|
+
import org.dwbn.plugins.playlist.data.AudioTrack;
|
|
18
|
+
import org.dwbn.plugins.playlist.manager.MediaControlsListener;
|
|
19
|
+
import org.dwbn.plugins.playlist.manager.Options;
|
|
20
|
+
import org.dwbn.plugins.playlist.manager.PlaylistManager;
|
|
21
|
+
import org.json.JSONException;
|
|
22
|
+
import org.json.JSONObject;
|
|
23
|
+
|
|
24
24
|
/**
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
|
|
29
|
-
*/
|
|
25
|
+
* The implementation of this player borrows from ExoMedia's demo example
|
|
26
|
+
* and utilizes heavily those classes, basically because that is "the" way
|
|
27
|
+
* to actually use ExoMedia.
|
|
28
|
+
*/
|
|
30
29
|
public class RmxAudioPlayer implements PlaybackStatusListener<AudioTrack>,
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
public float getVolume() {
|
|
87
|
-
return (getVolumeLeft() + getVolumeRight()) / 2f;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
public float getVolumeLeft() {
|
|
91
|
-
return playlistManager.getVolumeLeft();
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
public float getVolumeRight() {
|
|
95
|
-
return playlistManager.getVolumeRight();
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
public void setVolume(float both) {
|
|
99
|
-
setVolume(both, both);
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
public void setVolume(float left, float right) {
|
|
103
|
-
playlistManager.setVolume(left, right);
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
@Override
|
|
108
|
-
public void onPrevious(AudioTrack currentItem, int currentIndex) {
|
|
109
|
-
JSONObject param = new JSONObject();
|
|
110
|
-
String trackId = currentItem == null ? "NONE" : currentItem.getTrackId();
|
|
111
|
-
|
|
112
|
-
try {
|
|
113
|
-
param.put("currentIndex", currentIndex);
|
|
114
|
-
param.put("currentItem", currentItem != null ? currentItem.toDict() : null);
|
|
115
|
-
} catch (JSONException e) {
|
|
116
|
-
Log.i(TAG, "Error generating onPrevious status message: " + e.toString());
|
|
30
|
+
PlaylistListener<AudioTrack>, ProgressListener, OnErrorListener, MediaControlsListener {
|
|
31
|
+
|
|
32
|
+
public static String TAG = "RmxAudioPlayer";
|
|
33
|
+
|
|
34
|
+
// PlaylistCore requires this but we don't use it
|
|
35
|
+
// It would be used to switch between playlists. I guess we could
|
|
36
|
+
// support that in the future, might be cool.
|
|
37
|
+
private static final int PLAYLIST_ID = 32;
|
|
38
|
+
private PlaylistManager playlistManager;
|
|
39
|
+
private final OnStatusReportListener statusListener;
|
|
40
|
+
|
|
41
|
+
private int lastBufferPercent = 0;
|
|
42
|
+
private boolean trackDuration = false;
|
|
43
|
+
private boolean trackLoaded = false;
|
|
44
|
+
private boolean resetStreamOnPause = true;
|
|
45
|
+
private final App app;
|
|
46
|
+
|
|
47
|
+
public RmxAudioPlayer(@NonNull OnStatusReportListener statusListener, App context) {
|
|
48
|
+
// AudioPlayerPlugin and RmxAudioPlayer are separate classes in order to increase
|
|
49
|
+
// the portability of this code.
|
|
50
|
+
// Because AudioPlayerPlugin itself holds a strong reference to this class,
|
|
51
|
+
// we can hold a strong reference to this shared callback. Normally not a good idea
|
|
52
|
+
// but these two objects will always live together (And the plugin couldn't function
|
|
53
|
+
// at all if this one gets garbage collected).
|
|
54
|
+
this.statusListener = statusListener;
|
|
55
|
+
this.app = context;
|
|
56
|
+
|
|
57
|
+
getPlaylistManager();
|
|
58
|
+
playlistManager.setId(PLAYLIST_ID);
|
|
59
|
+
playlistManager.setPlaybackStatusListener(this);
|
|
60
|
+
playlistManager.setOnErrorListener(this);
|
|
61
|
+
playlistManager.setMediaControlsListener(this);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
public PlaylistManager getPlaylistManager() {
|
|
65
|
+
playlistManager = app.getPlaylistManager();
|
|
66
|
+
return playlistManager;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
public boolean getResetStreamOnPause() {
|
|
70
|
+
return resetStreamOnPause;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
public void setResetStreamOnPause(boolean val) {
|
|
74
|
+
resetStreamOnPause = val;
|
|
75
|
+
getPlaylistManager().setResetStreamOnPause(getResetStreamOnPause());
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
public void setOptions(JSONObject val) {
|
|
79
|
+
Options options = new Options(app, val);
|
|
80
|
+
getPlaylistManager().setOptions(options);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
public float getVolume() {
|
|
84
|
+
return (getVolumeLeft() + getVolumeRight()) / 2f;
|
|
117
85
|
}
|
|
118
86
|
|
|
119
|
-
|
|
120
|
-
|
|
87
|
+
public float getVolumeLeft() {
|
|
88
|
+
return playlistManager.getVolumeLeft();
|
|
89
|
+
}
|
|
121
90
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
91
|
+
public float getVolumeRight() {
|
|
92
|
+
return playlistManager.getVolumeRight();
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
public void setVolume(float both) {
|
|
96
|
+
setVolume(both, both);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
public void setVolume(float left, float right) {
|
|
100
|
+
playlistManager.setVolume(left, right);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
@Override
|
|
105
|
+
public void onPrevious(AudioTrack currentItem, int currentIndex) {
|
|
106
|
+
JSONObject param = new JSONObject();
|
|
107
|
+
String trackId = currentItem == null ? "NONE" : currentItem.getTrackId();
|
|
108
|
+
|
|
109
|
+
try {
|
|
110
|
+
param.put("currentIndex", currentIndex);
|
|
111
|
+
param.put("currentItem", currentItem != null ? currentItem.toDict() : null);
|
|
112
|
+
} catch (JSONException e) {
|
|
113
|
+
Log.i(TAG, "Error generating onPrevious status message: " + e.toString());
|
|
114
|
+
}
|
|
126
115
|
|
|
127
|
-
|
|
128
|
-
param.put("currentIndex", currentIndex);
|
|
129
|
-
param.put("currentItem", currentItem != null ? currentItem.toDict() : null);
|
|
130
|
-
} catch (JSONException e) {
|
|
131
|
-
Log.i(TAG, "Error generating onNext status message: " + e.toString());
|
|
116
|
+
onStatus(RmxAudioStatusMessage.RMX_STATUS_SKIP_BACK, trackId, param);
|
|
132
117
|
}
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
@Override
|
|
167
|
-
public void onMediaPlaybackStarted(AudioTrack item, long currentPosition, long duration) {
|
|
168
|
-
Log.i(TAG, "onMediaPlaybackStarted: ==> " + item.getTitle() + ": " + currentPosition + "," + duration);
|
|
169
|
-
// this is the first place that valid duration is seen. Immediately before, we get the PLAYING status change,
|
|
170
|
-
// and before that, it announces PREPARING twice and all values are 0.
|
|
171
|
-
// Problem is, this method is only called if playback is already in progress when the track changes,
|
|
172
|
-
// which is useless in most cases. So, these values are actually handled in onProgressUpdated.
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
@Override
|
|
176
|
-
public void onItemPlaybackEnded(AudioTrack item) {
|
|
177
|
-
AudioTrack nextItem = playlistManager.getCurrentItem();
|
|
178
|
-
// String title = item != null ? item.getTitle() : "(null)";
|
|
179
|
-
// String currTitle = nextItem != null ? nextItem.getTitle() : "(null)";
|
|
180
|
-
// String currTrackId = nextItem != null ? nextItem.getTrackId() : null;
|
|
181
|
-
// Log.i(TAG, "onItemPlaybackEnded: ==> " + title + "," + trackId + " ==> next item: " + currTitle + "," + currTrackId);
|
|
182
|
-
|
|
183
|
-
if (item != null) {
|
|
184
|
-
String trackId = item.getTrackId();
|
|
185
|
-
JSONObject trackStatus = getPlayerStatus(item);
|
|
186
|
-
onStatus(RmxAudioStatusMessage.RMXSTATUS_COMPLETED, trackId, trackStatus);
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
if (nextItem == null) { // if (!playlistManager.isNextAvailable()) {
|
|
190
|
-
onStatus(RmxAudioStatusMessage.RMXSTATUS_PLAYLIST_COMPLETED, "INVALID", null);
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
@Override
|
|
195
|
-
public void onPlaylistEnded() {
|
|
196
|
-
Log.i(TAG, "onPlaylistEnded");
|
|
197
|
-
playlistManager.setShouldStopPlaylist(false);
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
@Override
|
|
201
|
-
public boolean onPlaylistItemChanged(@Nullable AudioTrack currentItem, boolean hasNext, boolean hasPrevious) {
|
|
202
|
-
JSONObject info = new JSONObject();
|
|
203
|
-
String trackId = currentItem == null ? "NONE" : currentItem.getTrackId();
|
|
204
|
-
try {
|
|
205
|
-
info.put("currentItem", currentItem != null ? currentItem.toDict() : null);
|
|
206
|
-
info.put("currentIndex", playlistManager.getCurrentPosition());
|
|
207
|
-
info.put("isAtEnd", !hasNext);
|
|
208
|
-
info.put("isAtBeginning", !hasPrevious);
|
|
209
|
-
info.put("hasNext", hasNext);
|
|
210
|
-
info.put("hasPrevious", hasPrevious);
|
|
211
|
-
} catch (JSONException e) {
|
|
212
|
-
Log.e(TAG, "Error creating onPlaylistItemChanged message: " + e.toString());
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
lastBufferPercent = 0;
|
|
216
|
-
trackDuration = false;
|
|
217
|
-
trackLoaded = false;
|
|
218
|
-
|
|
219
|
-
onStatus(RmxAudioStatusMessage.RMXSTATUS_TRACK_CHANGED, trackId, info);
|
|
220
|
-
return true;
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
@Override
|
|
224
|
-
public boolean onPlaybackStateChanged(@NonNull PlaybackState playbackState) {
|
|
225
|
-
// in testing, I saw PREPARING, then PLAYING, and buffering happened
|
|
226
|
-
// during PLAYING. Tapping play/pause toggles PLAYING and PAUSED
|
|
227
|
-
// sending a seek command produces SEEKING here
|
|
228
|
-
// RETRIEVING is never sent.
|
|
229
|
-
|
|
230
|
-
AudioTrack currentItem = playlistManager.getCurrentItem();
|
|
231
|
-
JSONObject trackStatus = getPlayerStatus(currentItem);
|
|
232
|
-
Log.i("AudioPlayerActiv/opsc", playbackState.toString() + ", " + trackStatus.toString() + ", " + currentItem);
|
|
233
|
-
|
|
234
|
-
switch (playbackState) {
|
|
235
|
-
case STOPPED:
|
|
236
|
-
onStatus(RmxAudioStatusMessage.RMXSTATUS_STOPPED, "INVALID", null);
|
|
237
|
-
break;
|
|
238
|
-
|
|
239
|
-
case RETRIEVING: // these are all loading states
|
|
240
|
-
case PREPARING: {
|
|
241
|
-
if (currentItem != null && currentItem.getTrackId() != null) {
|
|
242
|
-
onStatus(RmxAudioStatusMessage.RMXSTATUS_LOADING, currentItem.getTrackId(), trackStatus);
|
|
118
|
+
|
|
119
|
+
@Override
|
|
120
|
+
public void onNext(AudioTrack currentItem, int currentIndex) {
|
|
121
|
+
JSONObject param = new JSONObject();
|
|
122
|
+
String trackId = currentItem == null ? "NONE" : currentItem.getTrackId();
|
|
123
|
+
|
|
124
|
+
try {
|
|
125
|
+
param.put("currentIndex", currentIndex);
|
|
126
|
+
param.put("currentItem", currentItem != null ? currentItem.toDict() : null);
|
|
127
|
+
} catch (JSONException e) {
|
|
128
|
+
Log.i(TAG, "Error generating onNext status message: " + e.toString());
|
|
129
|
+
}
|
|
130
|
+
onStatus(RmxAudioStatusMessage.RMX_STATUS_SKIP_FORWARD, trackId, param);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
@Override
|
|
134
|
+
public boolean onError(Exception e) {
|
|
135
|
+
String errorMsg = e.toString();
|
|
136
|
+
RmxAudioErrorType errorType = RmxAudioErrorType.RMXERR_NONE_SUPPORTED;
|
|
137
|
+
|
|
138
|
+
if (e instanceof ExoPlaybackException) {
|
|
139
|
+
switch (((ExoPlaybackException) e).type) {
|
|
140
|
+
case ExoPlaybackException.TYPE_SOURCE:
|
|
141
|
+
errorMsg = "ExoPlaybackException.TYPE_SOURCE: " + ((ExoPlaybackException) e).getSourceException().getMessage();
|
|
142
|
+
break;
|
|
143
|
+
case ExoPlaybackException.TYPE_RENDERER:
|
|
144
|
+
errorType = RmxAudioErrorType.RMXERR_DECODE;
|
|
145
|
+
errorMsg = "ExoPlaybackException.TYPE_RENDERER: " + ((ExoPlaybackException) e).getRendererException().getMessage();
|
|
146
|
+
break;
|
|
147
|
+
case ExoPlaybackException.TYPE_UNEXPECTED:
|
|
148
|
+
errorType = RmxAudioErrorType.RMXERR_DECODE;
|
|
149
|
+
errorMsg = "ExoPlaybackException.TYPE_UNEXPECTED: " + ((ExoPlaybackException) e).getUnexpectedException().getMessage();
|
|
150
|
+
break;
|
|
243
151
|
}
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
AudioTrack errorItem = playlistManager.getCurrentErrorTrack();
|
|
155
|
+
String trackId = errorItem != null ? errorItem.getTrackId() : "INVALID";
|
|
156
|
+
|
|
157
|
+
Log.i(TAG, "Error playing audio track: [" + trackId + "]: " + errorMsg);
|
|
158
|
+
onError(errorType, trackId, errorMsg);
|
|
159
|
+
playlistManager.setCurrentErrorTrack(null);
|
|
160
|
+
return true;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
@Override
|
|
164
|
+
public void onMediaPlaybackStarted(AudioTrack item, long currentPosition, long duration) {
|
|
165
|
+
Log.i(TAG, "onMediaPlaybackStarted: ==> " + item.getTitle() + ": " + currentPosition + "," + duration);
|
|
166
|
+
// this is the first place that valid duration is seen. Immediately before, we get the PLAYING status change,
|
|
167
|
+
// and before that, it announces PREPARING twice and all values are 0.
|
|
168
|
+
// Problem is, this method is only called if playback is already in progress when the track changes,
|
|
169
|
+
// which is useless in most cases. So, these values are actually handled in onProgressUpdated.
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
@Override
|
|
173
|
+
public void onItemPlaybackEnded(AudioTrack item) {
|
|
174
|
+
AudioTrack nextItem = playlistManager.getCurrentItem();
|
|
175
|
+
// String title = item != null ? item.getTitle() : "(null)";
|
|
176
|
+
// String currTitle = nextItem != null ? nextItem.getTitle() : "(null)";
|
|
177
|
+
// String currTrackId = nextItem != null ? nextItem.getTrackId() : null;
|
|
178
|
+
// Log.i(TAG, "onItemPlaybackEnded: ==> " + title + "," + trackId + " ==> next item: " + currTitle + "," + currTrackId);
|
|
179
|
+
|
|
180
|
+
if (item != null) {
|
|
181
|
+
String trackId = item.getTrackId();
|
|
182
|
+
JSONObject trackStatus = getPlayerStatus(item);
|
|
183
|
+
onStatus(RmxAudioStatusMessage.RMXSTATUS_COMPLETED, trackId, trackStatus);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (nextItem == null) { // if (!playlistManager.isNextAvailable()) {
|
|
187
|
+
onStatus(RmxAudioStatusMessage.RMXSTATUS_PLAYLIST_COMPLETED, "INVALID", null);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
@Override
|
|
192
|
+
public void onPlaylistEnded() {
|
|
193
|
+
Log.i(TAG, "onPlaylistEnded");
|
|
194
|
+
playlistManager.setShouldStopPlaylist(false);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
@Override
|
|
198
|
+
public boolean onPlaylistItemChanged(@Nullable AudioTrack currentItem, boolean hasNext, boolean hasPrevious) {
|
|
199
|
+
JSONObject info = new JSONObject();
|
|
200
|
+
String trackId = currentItem == null ? "NONE" : currentItem.getTrackId();
|
|
201
|
+
try {
|
|
202
|
+
info.put("currentItem", currentItem != null ? currentItem.toDict() : null);
|
|
203
|
+
info.put("currentIndex", playlistManager.getCurrentPosition());
|
|
204
|
+
info.put("isAtEnd", !hasNext);
|
|
205
|
+
info.put("isAtBeginning", !hasPrevious);
|
|
206
|
+
info.put("hasNext", hasNext);
|
|
207
|
+
info.put("hasPrevious", hasPrevious);
|
|
208
|
+
} catch (JSONException e) {
|
|
209
|
+
Log.e(TAG, "Error creating onPlaylistItemChanged message: " + e.toString());
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
lastBufferPercent = 0;
|
|
213
|
+
trackDuration = false;
|
|
214
|
+
trackLoaded = false;
|
|
215
|
+
|
|
216
|
+
onStatus(RmxAudioStatusMessage.RMXSTATUS_TRACK_CHANGED, trackId, info);
|
|
217
|
+
return true;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
@Override
|
|
221
|
+
public boolean onPlaybackStateChanged(@NonNull PlaybackState playbackState) {
|
|
222
|
+
// in testing, I saw PREPARING, then PLAYING, and buffering happened
|
|
223
|
+
// during PLAYING. Tapping play/pause toggles PLAYING and PAUSED
|
|
224
|
+
// sending a seek command produces SEEKING here
|
|
225
|
+
// RETRIEVING is never sent.
|
|
226
|
+
|
|
227
|
+
AudioTrack currentItem = playlistManager.getCurrentItem();
|
|
228
|
+
JSONObject trackStatus = getPlayerStatus(currentItem);
|
|
229
|
+
Log.i("onPlaybackStateChanged", playbackState.toString() + ", " + trackStatus.toString() + ", " + currentItem);
|
|
230
|
+
|
|
231
|
+
switch (playbackState) {
|
|
232
|
+
case STOPPED:
|
|
233
|
+
onStatus(RmxAudioStatusMessage.RMXSTATUS_STOPPED, "INVALID", null);
|
|
234
|
+
break;
|
|
235
|
+
|
|
236
|
+
case RETRIEVING: // these are all loading states
|
|
237
|
+
case PREPARING: {
|
|
238
|
+
if (currentItem != null && currentItem.getTrackId() != null) {
|
|
239
|
+
onStatus(RmxAudioStatusMessage.RMXSTATUS_LOADING, currentItem.getTrackId(), trackStatus);
|
|
255
240
|
}
|
|
241
|
+
break;
|
|
256
242
|
}
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
243
|
+
case SEEKING: {
|
|
244
|
+
MediaProgress progress = playlistManager.getCurrentProgress();
|
|
245
|
+
if (currentItem != null && currentItem.getTrackId() != null && progress != null) {
|
|
246
|
+
JSONObject info = new JSONObject();
|
|
247
|
+
try {
|
|
248
|
+
info.put("position", progress.getPosition() / 1000f);
|
|
249
|
+
onStatus(RmxAudioStatusMessage.RMXSTATUS_SEEK, currentItem.getTrackId(), info);
|
|
250
|
+
} catch (JSONException e) {
|
|
251
|
+
Log.e(TAG, "Error generating seeking status message: " + e.toString());
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
break;
|
|
255
|
+
}
|
|
256
|
+
case PLAYING:
|
|
257
|
+
if (currentItem != null && currentItem.getTrackId() != null) {
|
|
258
|
+
// Can also check here that duration == 0, because that is what happens on the first PLAYING invokation.
|
|
259
|
+
// We'll leave this for now.
|
|
260
|
+
if (!trackLoaded) {
|
|
261
|
+
onStatus(RmxAudioStatusMessage.RMXSTATUS_CANPLAY, currentItem.getTrackId(), trackStatus);
|
|
262
|
+
trackLoaded = true;
|
|
263
|
+
}
|
|
264
|
+
onStatus(RmxAudioStatusMessage.RMXSTATUS_PLAYING, currentItem.getTrackId(), trackStatus);
|
|
265
|
+
}
|
|
266
|
+
break;
|
|
267
|
+
case PAUSED:
|
|
268
|
+
if (currentItem != null && currentItem.getTrackId() != null) {
|
|
269
|
+
onStatus(RmxAudioStatusMessage.RMXSTATUS_PAUSE, currentItem.getTrackId(), trackStatus);
|
|
270
|
+
}
|
|
271
|
+
break;
|
|
272
|
+
// we'll handle error in the listener. ExoMedia only raises this in the case of catastrophic player failure.
|
|
273
|
+
case ERROR:
|
|
274
|
+
default:
|
|
275
|
+
break;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
return true;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
@Override
|
|
282
|
+
public boolean onProgressUpdated(@NonNull MediaProgress progress) {
|
|
283
|
+
// Order matters here. We must update the item's duration and buffer before pulling the track status,
|
|
284
|
+
// because those values are adjusted to account for the buffering-reset in ExoPlayer.
|
|
285
|
+
AudioTrack currentItem = playlistManager.getCurrentItem();
|
|
286
|
+
PlaybackState playbackState = playlistManager.getCurrentPlaybackState();
|
|
287
|
+
|
|
288
|
+
if (currentItem != null) { // I mean, this call makes no sense otherwise..
|
|
289
|
+
currentItem.setDuration(progress.getDuration());
|
|
290
|
+
currentItem.setBufferPercent(progress.getBufferPercent());
|
|
291
|
+
currentItem.setBufferPercentFloat(progress.getBufferPercentFloat());
|
|
292
|
+
|
|
293
|
+
JSONObject trackStatus = getPlayerStatus(currentItem);
|
|
294
|
+
|
|
295
|
+
if (progress.getBufferPercent() != lastBufferPercent) {
|
|
296
|
+
if (progress.getBufferPercent() >= 100f) {
|
|
297
|
+
// Unlike iOS this will get raised continuously.
|
|
298
|
+
// Extracting the source event from playlistcore would be really hard.
|
|
299
|
+
// The gate above should do the trick.
|
|
300
|
+
onStatus(RmxAudioStatusMessage.RMXSTATUS_LOADED, currentItem.getTrackId(), trackStatus);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
if (!trackLoaded) {
|
|
264
304
|
onStatus(RmxAudioStatusMessage.RMXSTATUS_CANPLAY, currentItem.getTrackId(), trackStatus);
|
|
265
305
|
trackLoaded = true;
|
|
266
|
-
|
|
267
|
-
onStatus(RmxAudioStatusMessage.RMXSTATUS_PLAYING, currentItem.getTrackId(), trackStatus);
|
|
268
|
-
}
|
|
269
|
-
break;
|
|
270
|
-
case PAUSED:
|
|
271
|
-
if (currentItem != null && currentItem.getTrackId() != null) {
|
|
272
|
-
onStatus(RmxAudioStatusMessage.RMXSTATUS_PAUSE, currentItem.getTrackId(), trackStatus);
|
|
273
|
-
}
|
|
274
|
-
break;
|
|
275
|
-
// we'll handle error in the listener. ExoMedia only raises this in the case of catastrophic player failure.
|
|
276
|
-
case ERROR:
|
|
277
|
-
default:
|
|
278
|
-
break;
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
return true;
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
@Override
|
|
285
|
-
public boolean onProgressUpdated(@NonNull MediaProgress progress) {
|
|
286
|
-
// Order matters here. We must update the item's duration and buffer before pulling the track status,
|
|
287
|
-
// because those values are adjusted to account for the buffering-reset in ExoPlayer.
|
|
288
|
-
AudioTrack currentItem = playlistManager.getCurrentItem();
|
|
289
|
-
PlaybackState playbackState = playlistManager.getCurrentPlaybackState();
|
|
290
|
-
|
|
291
|
-
if (currentItem != null) { // I mean, this call makes no sense otherwise..
|
|
292
|
-
currentItem.setDuration(progress.getDuration());
|
|
293
|
-
currentItem.setBufferPercent(progress.getBufferPercent());
|
|
294
|
-
currentItem.setBufferPercentFloat(progress.getBufferPercentFloat());
|
|
306
|
+
}
|
|
295
307
|
|
|
296
|
-
|
|
308
|
+
if (!trackDuration && progress.getDuration() > 0) {
|
|
309
|
+
onStatus(RmxAudioStatusMessage.RMXSTATUS_DURATION, currentItem.getTrackId(), trackStatus);
|
|
310
|
+
trackDuration = true;
|
|
311
|
+
}
|
|
297
312
|
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
// Unlike iOS this will get raised continuously.
|
|
301
|
-
// Extracting the source event from playlistcore would be really hard.
|
|
302
|
-
// The gate above should do the trick.
|
|
303
|
-
onStatus(RmxAudioStatusMessage.RMXSTATUS_LOADED, currentItem.getTrackId(), trackStatus);
|
|
313
|
+
onStatus(RmxAudioStatusMessage.RMXSTATUS_BUFFERING, currentItem.getTrackId(), trackStatus);
|
|
314
|
+
lastBufferPercent = progress.getBufferPercent();
|
|
304
315
|
}
|
|
305
316
|
|
|
306
|
-
if
|
|
307
|
-
|
|
308
|
-
|
|
317
|
+
// dont send on prepare, if null
|
|
318
|
+
if (playbackState == PlaybackState.PLAYING || playbackState == PlaybackState.SEEKING
|
|
319
|
+
|| (playbackState == PlaybackState.PREPARING && progress.getDuration() == 0)) {
|
|
320
|
+
onStatus(RmxAudioStatusMessage.RMXSTATUS_PLAYBACK_POSITION, currentItem.getTrackId(), trackStatus);
|
|
309
321
|
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
return true;
|
|
325
|
+
}
|
|
310
326
|
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
327
|
+
public JSONObject getPlayerStatus(@Nullable AudioTrack statusItem) {
|
|
328
|
+
// TODO: Make this its own object.
|
|
329
|
+
AudioTrack currentItem = statusItem != null ? statusItem : playlistManager.getCurrentItem();
|
|
330
|
+
PlaybackState playbackState = playlistManager.getCurrentPlaybackState();
|
|
331
|
+
MediaProgress progress = playlistManager.getCurrentProgress();
|
|
332
|
+
|
|
333
|
+
String status = "unknown";
|
|
334
|
+
switch (playbackState) {
|
|
335
|
+
case STOPPED: {
|
|
336
|
+
status = "stopped";
|
|
337
|
+
break;
|
|
338
|
+
}
|
|
339
|
+
case ERROR: {
|
|
340
|
+
status = "error";
|
|
341
|
+
break;
|
|
342
|
+
}
|
|
343
|
+
case RETRIEVING:
|
|
344
|
+
case SEEKING: // { status = "seeking"; break; } // seeking === loading
|
|
345
|
+
case PREPARING: {
|
|
346
|
+
status = "loading";
|
|
347
|
+
break;
|
|
314
348
|
}
|
|
349
|
+
case PLAYING: {
|
|
350
|
+
status = "playing";
|
|
351
|
+
break;
|
|
352
|
+
}
|
|
353
|
+
case PAUSED: {
|
|
354
|
+
status = "paused";
|
|
355
|
+
break;
|
|
356
|
+
}
|
|
357
|
+
default:
|
|
358
|
+
break;
|
|
359
|
+
}
|
|
315
360
|
|
|
316
|
-
|
|
317
|
-
|
|
361
|
+
String trackId = "";
|
|
362
|
+
boolean isStream = false;
|
|
363
|
+
float bufferPercentFloat = 0;
|
|
364
|
+
int bufferPercent = 0;
|
|
365
|
+
long duration = 0;
|
|
366
|
+
long position = 0;
|
|
367
|
+
|
|
368
|
+
// The media players hold onto their current playback position between songs,
|
|
369
|
+
// despite my efforts to reset it. So we will just filter out this state.
|
|
370
|
+
if (progress != null) { // && !status.equals("loading")) {
|
|
371
|
+
position = progress.getPosition();
|
|
318
372
|
}
|
|
319
373
|
|
|
320
|
-
//
|
|
321
|
-
if (
|
|
322
|
-
|
|
323
|
-
|
|
374
|
+
// the position and duration vals are in milliseconds.
|
|
375
|
+
if (currentItem != null) {
|
|
376
|
+
isStream = currentItem.isStream();
|
|
377
|
+
trackId = currentItem.getTrackId();
|
|
378
|
+
bufferPercentFloat = currentItem.getBufferPercentFloat(); // progress.
|
|
379
|
+
bufferPercent = currentItem.getBufferPercent(); // progress.
|
|
380
|
+
duration = currentItem.getDuration(); // progress.
|
|
324
381
|
}
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
return true;
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
public JSONObject getPlayerStatus(@Nullable AudioTrack statusItem) {
|
|
331
|
-
// TODO: Make this its own object.
|
|
332
|
-
AudioTrack currentItem = statusItem != null ? statusItem : playlistManager.getCurrentItem();
|
|
333
|
-
PlaybackState playbackState = playlistManager.getCurrentPlaybackState();
|
|
334
|
-
MediaProgress progress = playlistManager.getCurrentProgress();
|
|
335
|
-
|
|
336
|
-
String status = "unknown";
|
|
337
|
-
switch (playbackState) {
|
|
338
|
-
case STOPPED: { status = "stopped"; break; }
|
|
339
|
-
case ERROR: { status = "error"; break; }
|
|
340
|
-
case RETRIEVING:
|
|
341
|
-
case SEEKING: // { status = "seeking"; break; } // seeking === loading
|
|
342
|
-
case PREPARING: { status = "loading"; break; }
|
|
343
|
-
case PLAYING: { status = "playing"; break; }
|
|
344
|
-
case PAUSED: { status = "paused"; break; }
|
|
345
|
-
default:
|
|
346
|
-
break;
|
|
347
|
-
}
|
|
348
382
|
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
383
|
+
JSONObject trackStatus = new JSONObject();
|
|
384
|
+
try {
|
|
385
|
+
trackStatus.put("trackId", trackId);
|
|
386
|
+
trackStatus.put("isStream", isStream);
|
|
387
|
+
trackStatus.put("currentIndex", playlistManager.getCurrentPosition());
|
|
388
|
+
trackStatus.put("status", status);
|
|
389
|
+
trackStatus.put("currentPosition", position / 1000.0);
|
|
390
|
+
trackStatus.put("duration", duration / 1000.0);
|
|
391
|
+
trackStatus.put("playbackPercent", duration > 0 ? (((double) position / duration) * 100.0) : 0);
|
|
392
|
+
trackStatus.put("bufferPercent", bufferPercent);
|
|
393
|
+
trackStatus.put("bufferStart", 0.0);
|
|
394
|
+
trackStatus.put("bufferEnd", (bufferPercentFloat * duration) / 1000.0);
|
|
395
|
+
} catch (JSONException e) {
|
|
396
|
+
Log.e(TAG, "Error generating player status: " + e.toString());
|
|
397
|
+
}
|
|
361
398
|
|
|
362
|
-
|
|
363
|
-
if (currentItem != null) {
|
|
364
|
-
isStream = currentItem.isStream();
|
|
365
|
-
trackId = currentItem.getTrackId();
|
|
366
|
-
bufferPercentFloat = currentItem.getBufferPercentFloat(); // progress.
|
|
367
|
-
bufferPercent = currentItem.getBufferPercent(); // progress.
|
|
368
|
-
duration = currentItem.getDuration(); // progress.
|
|
399
|
+
return trackStatus;
|
|
369
400
|
}
|
|
370
401
|
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
trackStatus.put("isStream", isStream);
|
|
375
|
-
trackStatus.put("currentIndex", playlistManager.getCurrentPosition());
|
|
376
|
-
trackStatus.put("status", status);
|
|
377
|
-
trackStatus.put("currentPosition", position / 1000.0);
|
|
378
|
-
trackStatus.put("duration", duration / 1000.0);
|
|
379
|
-
trackStatus.put("playbackPercent", duration > 0 ? (((double)position / duration) * 100.0) : 0);
|
|
380
|
-
trackStatus.put("bufferPercent", bufferPercent);
|
|
381
|
-
trackStatus.put("bufferStart", 0.0);
|
|
382
|
-
trackStatus.put("bufferEnd", (bufferPercentFloat * duration) / 1000.0);
|
|
383
|
-
} catch (JSONException e) {
|
|
384
|
-
Log.e(TAG, "Error generating player status: " + e.toString());
|
|
402
|
+
public void pause() {
|
|
403
|
+
Log.i(TAG, "Pausing, removing event listeners");
|
|
404
|
+
removePlaylistListeners();
|
|
385
405
|
}
|
|
386
406
|
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
public void resume() {
|
|
396
|
-
Log.i(TAG, "Resumed, wiring up event listeners");
|
|
397
|
-
getPlaylistManager();
|
|
398
|
-
registerPlaylistListeners();
|
|
399
|
-
//Makes sure to retrieve the current playback information
|
|
400
|
-
updateCurrentPlaybackInformation();
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
private void updateCurrentPlaybackInformation() {
|
|
404
|
-
PlaylistItemChange<AudioTrack> itemChange = playlistManager.getCurrentItemChange();
|
|
405
|
-
if (itemChange != null) {
|
|
406
|
-
onPlaylistItemChanged(itemChange.getCurrentItem(), itemChange.getHasNext(), itemChange.getHasPrevious());
|
|
407
|
+
public void resume() {
|
|
408
|
+
Log.i(TAG, "Resumed, wiring up event listeners");
|
|
409
|
+
getPlaylistManager();
|
|
410
|
+
registerPlaylistListeners();
|
|
411
|
+
//Makes sure to retrieve the current playback information
|
|
412
|
+
updateCurrentPlaybackInformation();
|
|
407
413
|
}
|
|
408
414
|
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
415
|
+
private void updateCurrentPlaybackInformation() {
|
|
416
|
+
PlaylistItemChange<AudioTrack> itemChange = playlistManager.getCurrentItemChange();
|
|
417
|
+
if (itemChange != null) {
|
|
418
|
+
onPlaylistItemChanged(itemChange.getCurrentItem(), itemChange.getHasNext(), itemChange.getHasPrevious());
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
PlaybackState currentPlaybackState = playlistManager.getCurrentPlaybackState();
|
|
422
|
+
if (currentPlaybackState != PlaybackState.STOPPED) {
|
|
423
|
+
onPlaybackStateChanged(currentPlaybackState);
|
|
424
|
+
}
|
|
413
425
|
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
426
|
+
MediaProgress mediaProgress = playlistManager.getCurrentProgress();
|
|
427
|
+
if (mediaProgress != null) {
|
|
428
|
+
onProgressUpdated(mediaProgress);
|
|
429
|
+
}
|
|
417
430
|
}
|
|
418
|
-
}
|
|
419
431
|
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
432
|
+
private void registerPlaylistListeners() {
|
|
433
|
+
playlistManager.registerPlaylistListener(this);
|
|
434
|
+
playlistManager.registerProgressListener(this);
|
|
435
|
+
}
|
|
424
436
|
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
437
|
+
private void removePlaylistListeners() {
|
|
438
|
+
playlistManager.unRegisterPlaylistListener(this);
|
|
439
|
+
playlistManager.unRegisterProgressListener(this);
|
|
440
|
+
}
|
|
429
441
|
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
442
|
+
private void onError(RmxAudioErrorType errorCode, String trackId, String message) {
|
|
443
|
+
statusListener.onError(errorCode, trackId, message);
|
|
444
|
+
}
|
|
433
445
|
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
446
|
+
private void onStatus(RmxAudioStatusMessage what, String trackId, JSONObject param) {
|
|
447
|
+
statusListener.onStatus(what, trackId, param);
|
|
448
|
+
}
|
|
437
449
|
}
|