@nativescript/canvas-media 2.0.11 → 2.0.14
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/VideoFrame/common.d.ts +122 -0
- package/VideoFrame/common.js +173 -0
- package/VideoFrame/common.js.map +1 -0
- package/VideoFrame/index.android.d.ts +12 -0
- package/VideoFrame/index.android.js +113 -0
- package/VideoFrame/index.android.js.map +1 -0
- package/VideoFrame/index.ios.d.ts +12 -0
- package/VideoFrame/index.ios.js +122 -0
- package/VideoFrame/index.ios.js.map +1 -0
- package/audio/common.d.ts +29 -3
- package/audio/common.js +90 -0
- package/audio/common.js.map +1 -1
- package/audio/index.android.d.ts +32 -1
- package/audio/index.android.js +219 -1
- package/audio/index.android.js.map +1 -1
- package/audio/index.ios.d.ts +27 -4
- package/audio/index.ios.js +183 -142
- package/audio/index.ios.js.map +1 -1
- package/common.d.ts +1 -0
- package/common.js +3 -0
- package/common.js.map +1 -1
- package/index.d.ts +2 -0
- package/index.js +1 -0
- package/index.js.map +1 -1
- package/package.json +1 -1
- package/platforms/android/canvas_media.aar +0 -0
- package/platforms/android/java/org/nativescript/canvas/media/AudioHelper.java +338 -0
- package/platforms/android/java/org/nativescript/canvas/media/AudioView.java +125 -0
- package/platforms/android/java/org/nativescript/canvas/media/SimpleCacheProvider.java +27 -0
- package/platforms/android/java/org/nativescript/canvas/media/VideoHelper.java +204 -5
- package/platforms/ios/src/NSCAudioHelper.h +47 -0
- package/platforms/ios/src/NSCAudioHelper.m +713 -0
- package/platforms/ios/src/NSCAudioView.h +20 -0
- package/platforms/ios/src/NSCAudioView.m +270 -0
- package/platforms/ios/src/NSCCanvasMediaUmbrella.h +3 -1
- package/platforms/ios/src/NSCVideoHelper.h +6 -0
- package/platforms/ios/src/NSCVideoHelper.m +111 -4
- package/video/common.d.ts +4 -0
- package/video/common.js +3 -0
- package/video/common.js.map +1 -1
- package/video/index.android.d.ts +3 -1
- package/video/index.android.js +44 -8
- package/video/index.android.js.map +1 -1
- package/video/index.ios.d.ts +2 -0
- package/video/index.ios.js +28 -0
- package/video/index.ios.js.map +1 -1
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
package org.nativescript.canvas.media;
|
|
2
|
+
|
|
3
|
+
import android.content.Context;
|
|
4
|
+
import android.net.Uri;
|
|
5
|
+
import android.view.View;
|
|
6
|
+
import android.widget.LinearLayout;
|
|
7
|
+
import android.media.MediaCodecInfo;
|
|
8
|
+
import android.media.MediaCodecList;
|
|
9
|
+
import android.os.Handler;
|
|
10
|
+
import android.os.Looper;
|
|
11
|
+
import java.io.File;
|
|
12
|
+
import java.util.Collections;
|
|
13
|
+
import java.util.Timer;
|
|
14
|
+
import java.util.TimerTask;
|
|
15
|
+
import java.util.concurrent.atomic.AtomicBoolean;
|
|
16
|
+
|
|
17
|
+
import androidx.media3.common.MediaItem;
|
|
18
|
+
import androidx.media3.common.PlaybackException;
|
|
19
|
+
import androidx.media3.common.AudioAttributes;
|
|
20
|
+
import androidx.media3.common.Player;
|
|
21
|
+
import androidx.media3.common.util.UnstableApi;
|
|
22
|
+
import androidx.media3.common.util.Util;
|
|
23
|
+
import androidx.media3.datasource.DefaultDataSource;
|
|
24
|
+
import androidx.media3.datasource.DefaultHttpDataSource;
|
|
25
|
+
import androidx.media3.datasource.cache.CacheDataSource;
|
|
26
|
+
import androidx.media3.datasource.cache.LeastRecentlyUsedCacheEvictor;
|
|
27
|
+
import androidx.media3.datasource.cache.SimpleCache;
|
|
28
|
+
import androidx.media3.database.StandaloneDatabaseProvider;
|
|
29
|
+
import androidx.media3.exoplayer.DefaultLoadControl;
|
|
30
|
+
import androidx.media3.exoplayer.ExoPlayer;
|
|
31
|
+
import androidx.media3.exoplayer.source.DefaultMediaSourceFactory;
|
|
32
|
+
|
|
33
|
+
@UnstableApi
|
|
34
|
+
public class AudioHelper implements Player.Listener {
|
|
35
|
+
private ExoPlayer _player;
|
|
36
|
+
private DefaultDataSource.Factory _dsf;
|
|
37
|
+
private CacheDataSource.Factory _cdsf;
|
|
38
|
+
private DefaultMediaSourceFactory _msf;
|
|
39
|
+
private boolean _autoplay;
|
|
40
|
+
private boolean _loop;
|
|
41
|
+
private String _src;
|
|
42
|
+
private Timer _timer;
|
|
43
|
+
private AtomicBoolean _loadedDataFired = new AtomicBoolean(false);
|
|
44
|
+
private boolean _canPlayFired = false;
|
|
45
|
+
private boolean _canPlayThroughFired = false;
|
|
46
|
+
private Callback _callback;
|
|
47
|
+
private LeastRecentlyUsedCacheEvictor _leastRecentlyUsedCacheEvictor;
|
|
48
|
+
private StandaloneDatabaseProvider _exoDatabaseProvider;
|
|
49
|
+
private static final long _exoPlayerCacheSize = 100 * 1024 * 1024;
|
|
50
|
+
private boolean _playing = false;
|
|
51
|
+
private AudioView _audioView;
|
|
52
|
+
private LinearLayout _container;
|
|
53
|
+
|
|
54
|
+
public interface Callback {
|
|
55
|
+
void onDurationChange(long duration);
|
|
56
|
+
void onPlaying();
|
|
57
|
+
void onCurrentTimeChanged(long time);
|
|
58
|
+
void onLoadedData();
|
|
59
|
+
void onCanPlay();
|
|
60
|
+
void onCanPlayThrough();
|
|
61
|
+
void onError(String message);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
public void setCallback(Callback callback) {
|
|
65
|
+
this._callback = callback;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
Handler handler;
|
|
69
|
+
|
|
70
|
+
public AudioHelper(Context context, String cacheRoot) {
|
|
71
|
+
File cacheDir = new File(cacheRoot, "MEDIA_PLAYER_CACHE");
|
|
72
|
+
if (!cacheDir.exists()) {
|
|
73
|
+
cacheDir.mkdirs();
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
Looper looper1 = Looper.myLooper();
|
|
77
|
+
if (looper1 == null) {
|
|
78
|
+
Looper.prepare();
|
|
79
|
+
looper1 = Looper.myLooper();
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
handler = new Handler(looper1);
|
|
83
|
+
|
|
84
|
+
_leastRecentlyUsedCacheEvictor = new LeastRecentlyUsedCacheEvictor(_exoPlayerCacheSize);
|
|
85
|
+
String packageName = context.getPackageName();
|
|
86
|
+
_exoDatabaseProvider = new StandaloneDatabaseProvider(context);
|
|
87
|
+
SimpleCache cache = SimpleCacheProvider.getInstance(cacheDir, _leastRecentlyUsedCacheEvictor, _exoDatabaseProvider);
|
|
88
|
+
DefaultHttpDataSource.Factory factory = new DefaultHttpDataSource.Factory();
|
|
89
|
+
factory.setUserAgent(Util.getUserAgent(context, packageName));
|
|
90
|
+
_dsf = new DefaultDataSource.Factory(context, factory);
|
|
91
|
+
_cdsf = new CacheDataSource.Factory();
|
|
92
|
+
if (cache != null) {
|
|
93
|
+
_cdsf.setCache(cache);
|
|
94
|
+
}
|
|
95
|
+
_cdsf.setUpstreamDataSourceFactory(_dsf);
|
|
96
|
+
_msf = new DefaultMediaSourceFactory(_cdsf);
|
|
97
|
+
|
|
98
|
+
ExoPlayer.Builder builder = new ExoPlayer.Builder(context);
|
|
99
|
+
builder.setMediaSourceFactory(_msf);
|
|
100
|
+
|
|
101
|
+
DefaultLoadControl.Builder loadControl = new DefaultLoadControl.Builder();
|
|
102
|
+
loadControl.setBufferDurationsMs(500, DefaultLoadControl.DEFAULT_MAX_BUFFER_MS, 500, 500);
|
|
103
|
+
builder.setLoadControl(loadControl.build());
|
|
104
|
+
_player = builder.build();
|
|
105
|
+
_player.addListener(this);
|
|
106
|
+
_player.setAudioAttributes(AudioAttributes.DEFAULT, true);
|
|
107
|
+
|
|
108
|
+
_container = new LinearLayout(context);
|
|
109
|
+
_container.setOrientation(LinearLayout.VERTICAL);
|
|
110
|
+
_audioView = new AudioView(context, this);
|
|
111
|
+
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT);
|
|
112
|
+
_container.addView(_audioView, params);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
private void _setSrc(String value) {
|
|
116
|
+
try {
|
|
117
|
+
this._player.setMediaItems(Collections.singletonList(MediaItem.fromUri(Uri.parse(value))), true);
|
|
118
|
+
this._player.prepare();
|
|
119
|
+
if (this._autoplay) {
|
|
120
|
+
this._player.setPlayWhenReady(true);
|
|
121
|
+
}
|
|
122
|
+
} catch (Exception e) {}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
@Override
|
|
126
|
+
public void onIsPlayingChanged(boolean isPlaying) {
|
|
127
|
+
long duration = this._player.getDuration();
|
|
128
|
+
if (this._callback != null) {
|
|
129
|
+
this._callback.onDurationChange(duration);
|
|
130
|
+
}
|
|
131
|
+
this._playing = isPlaying;
|
|
132
|
+
if (isPlaying) {
|
|
133
|
+
if (this._callback != null) {
|
|
134
|
+
this._callback.onPlaying();
|
|
135
|
+
this._callback.onLoadedData();
|
|
136
|
+
}
|
|
137
|
+
if (this._audioView != null) {
|
|
138
|
+
this._audioView.setPlaying(true);
|
|
139
|
+
this._audioView.setDuration(duration);
|
|
140
|
+
}
|
|
141
|
+
if (_timer != null) {
|
|
142
|
+
_timer.cancel();
|
|
143
|
+
_timer = null;
|
|
144
|
+
}
|
|
145
|
+
_timer = new Timer();
|
|
146
|
+
AudioHelper that = this;
|
|
147
|
+
_timer.schedule(new TimerTask() {
|
|
148
|
+
@Override
|
|
149
|
+
public void run() {
|
|
150
|
+
handler.post(() -> {
|
|
151
|
+
long current = _player.getCurrentPosition();
|
|
152
|
+
if (that._callback != null) {
|
|
153
|
+
that._callback.onCurrentTimeChanged(current);
|
|
154
|
+
}
|
|
155
|
+
if (that._audioView != null) {
|
|
156
|
+
that._audioView.setCurrentTime(current);
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
}, 0, 1000);
|
|
161
|
+
} else {
|
|
162
|
+
if (_timer != null) {
|
|
163
|
+
_timer.cancel();
|
|
164
|
+
_timer = null;
|
|
165
|
+
}
|
|
166
|
+
if (this._callback != null) {
|
|
167
|
+
this._callback.onCurrentTimeChanged(this._player.getCurrentPosition());
|
|
168
|
+
}
|
|
169
|
+
if (this._audioView != null) {
|
|
170
|
+
this._audioView.setPlaying(false);
|
|
171
|
+
this._audioView.setCurrentTime(this._player.getCurrentPosition());
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
@Override
|
|
177
|
+
public void onPlayerError(PlaybackException error) {
|
|
178
|
+
if (this._callback != null) {
|
|
179
|
+
this._callback.onError(error == null ? "Playback error" : error.getMessage());
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
@Override
|
|
184
|
+
public void onEvents(Player player, Player.Events events) { }
|
|
185
|
+
|
|
186
|
+
@Override
|
|
187
|
+
public void onPlaybackStateChanged(@Player.State int playbackState) {
|
|
188
|
+
if (playbackState == Player.STATE_READY) {
|
|
189
|
+
if (!this._canPlayFired) {
|
|
190
|
+
this._canPlayFired = true;
|
|
191
|
+
if (this._callback != null) {
|
|
192
|
+
this._callback.onCanPlay();
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
if (this._loadedDataFired.compareAndSet(false, true)) {
|
|
197
|
+
if (this._callback != null) {
|
|
198
|
+
this._callback.onLoadedData();
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
long duration = this._player.getDuration();
|
|
203
|
+
long buffered = this._player.getBufferedPosition();
|
|
204
|
+
if (this._audioView != null) {
|
|
205
|
+
this._audioView.setDuration(duration);
|
|
206
|
+
this._audioView.setCurrentTime(this._player.getCurrentPosition());
|
|
207
|
+
}
|
|
208
|
+
if (duration > 0 && buffered >= duration - 1000 && !this._canPlayThroughFired) {
|
|
209
|
+
this._canPlayThroughFired = true;
|
|
210
|
+
if (this._callback != null) {
|
|
211
|
+
this._callback.onCanPlayThrough();
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
public void play() {
|
|
218
|
+
try {
|
|
219
|
+
this._player.setPlayWhenReady(true);
|
|
220
|
+
this._player.play();
|
|
221
|
+
} catch (Exception e) { }
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
public void pause() {
|
|
225
|
+
try {
|
|
226
|
+
this._player.setPlayWhenReady(false);
|
|
227
|
+
this._player.pause();
|
|
228
|
+
} catch (Exception e) { }
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
public boolean getMuted() {
|
|
232
|
+
return this._player.getVolume() == 0f;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
public void setMuted(boolean value) {
|
|
236
|
+
this._player.setVolume(value ? 0f : 1f);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
public long getDuration() {
|
|
240
|
+
return this._player.getDuration();
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
public double getCurrentTime() {
|
|
244
|
+
return this._player.getCurrentPosition() / 1000.0;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
public void setCurrentTime(double value) {
|
|
248
|
+
this._player.seekTo((long) (value * 1000.0));
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
public String getSrc() {
|
|
252
|
+
return this._src;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
public LinearLayout getContainer() {
|
|
256
|
+
return this._container;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
public boolean getControls() {
|
|
260
|
+
return this._container != null && this._container.getVisibility() == View.VISIBLE;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
public void setControls(boolean value) {
|
|
264
|
+
if (this._container != null) {
|
|
265
|
+
this._container.setVisibility(value ? View.VISIBLE : View.GONE);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
public boolean isPlaying() {
|
|
270
|
+
return this._playing;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
public void setSrc(String value) {
|
|
274
|
+
this._src = value;
|
|
275
|
+
this._loadedDataFired.set(false);
|
|
276
|
+
this._canPlayFired = false;
|
|
277
|
+
this._canPlayThroughFired = false;
|
|
278
|
+
this._setSrc(value);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
public void load() {
|
|
282
|
+
this.pause();
|
|
283
|
+
if (_timer != null) { _timer.cancel(); _timer = null; }
|
|
284
|
+
this._loadedDataFired.set(false);
|
|
285
|
+
if (this._src != null && !this._src.isEmpty()) {
|
|
286
|
+
this._setSrc(this._src);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
public boolean getAutoplay() {
|
|
291
|
+
return this._autoplay;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
public void setAutoplay(boolean value) {
|
|
295
|
+
this._autoplay = value;
|
|
296
|
+
this._player.setPlayWhenReady(value);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
public boolean getLoop() {
|
|
300
|
+
return this._loop;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
public void setLoop(boolean value) {
|
|
304
|
+
this._loop = value;
|
|
305
|
+
if (value) {
|
|
306
|
+
this._player.setRepeatMode(Player.REPEAT_MODE_ALL);
|
|
307
|
+
} else {
|
|
308
|
+
this._player.setRepeatMode(Player.REPEAT_MODE_OFF);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
public String canPlayType(String type) {
|
|
313
|
+
if (type == null || type.trim().isEmpty()) return "";
|
|
314
|
+
String mime = type;
|
|
315
|
+
int sc = mime.indexOf(';');
|
|
316
|
+
if (sc >= 0) mime = mime.substring(0, sc).trim();
|
|
317
|
+
String t = mime.toLowerCase();
|
|
318
|
+
try {
|
|
319
|
+
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) {
|
|
320
|
+
MediaCodecList mcl = new MediaCodecList(MediaCodecList.ALL_CODECS);
|
|
321
|
+
MediaCodecInfo[] infos = mcl.getCodecInfos();
|
|
322
|
+
for (MediaCodecInfo info : infos) {
|
|
323
|
+
if (info.isEncoder()) continue;
|
|
324
|
+
String[] types = info.getSupportedTypes();
|
|
325
|
+
for (String s : types) {
|
|
326
|
+
if (s.equalsIgnoreCase(t)) {
|
|
327
|
+
return "probably";
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
} catch (Throwable e) {}
|
|
333
|
+
|
|
334
|
+
if (t.contains("mp3") || t.contains("mpeg") || t.contains("aac") || t.contains("wav") || t.contains("m4a") || t.contains("flac")) return "probably";
|
|
335
|
+
if (t.contains("ogg") || t.contains("opus") || t.contains("webm")) return "maybe";
|
|
336
|
+
return "";
|
|
337
|
+
}
|
|
338
|
+
}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
package org.nativescript.canvas.media;
|
|
2
|
+
|
|
3
|
+
import android.content.Context;
|
|
4
|
+
import android.widget.LinearLayout;
|
|
5
|
+
import android.widget.ImageButton;
|
|
6
|
+
import android.widget.SeekBar;
|
|
7
|
+
import android.widget.TextView;
|
|
8
|
+
import android.view.View;
|
|
9
|
+
import android.view.Gravity;
|
|
10
|
+
import android.util.TypedValue;
|
|
11
|
+
import android.view.ViewGroup.LayoutParams;
|
|
12
|
+
import java.lang.ref.WeakReference;
|
|
13
|
+
|
|
14
|
+
public class AudioView extends LinearLayout {
|
|
15
|
+
private ImageButton _playButton;
|
|
16
|
+
private SeekBar _seekBar;
|
|
17
|
+
private TextView _timeLabel;
|
|
18
|
+
private long _durationMs = 0;
|
|
19
|
+
private WeakReference<AudioHelper> _helperRef;
|
|
20
|
+
private boolean _isSeeking = false;
|
|
21
|
+
|
|
22
|
+
public AudioView(Context context, AudioHelper helper) {
|
|
23
|
+
super(context);
|
|
24
|
+
this._helperRef = new WeakReference<>(helper);
|
|
25
|
+
|
|
26
|
+
setOrientation(HORIZONTAL);
|
|
27
|
+
setGravity(Gravity.CENTER_VERTICAL);
|
|
28
|
+
int pad = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 8, getResources().getDisplayMetrics());
|
|
29
|
+
setPadding(pad, pad, pad, pad);
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
int minWidthPx = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 300, getResources().getDisplayMetrics());
|
|
33
|
+
int minHeightPx = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 40, getResources().getDisplayMetrics());
|
|
34
|
+
setMinimumWidth(minWidthPx);
|
|
35
|
+
setMinimumHeight(minHeightPx);
|
|
36
|
+
|
|
37
|
+
_playButton = new ImageButton(context);
|
|
38
|
+
_playButton.setImageResource(android.R.drawable.ic_media_play);
|
|
39
|
+
_playButton.setBackgroundResource(0);
|
|
40
|
+
int iconSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 40, getResources().getDisplayMetrics());
|
|
41
|
+
LayoutParams btnParams = new LayoutParams(iconSize, iconSize);
|
|
42
|
+
addView(_playButton, btnParams);
|
|
43
|
+
|
|
44
|
+
_seekBar = new SeekBar(context);
|
|
45
|
+
LayoutParams sbParams = new LayoutParams(0, LayoutParams.WRAP_CONTENT, 1f);
|
|
46
|
+
sbParams.leftMargin = pad;
|
|
47
|
+
sbParams.rightMargin = pad;
|
|
48
|
+
addView(_seekBar, sbParams);
|
|
49
|
+
_seekBar.setEnabled(false);
|
|
50
|
+
|
|
51
|
+
_timeLabel = new TextView(context);
|
|
52
|
+
_timeLabel.setText("0:00 / 0:00");
|
|
53
|
+
addView(_timeLabel, new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
|
|
54
|
+
|
|
55
|
+
_playButton.setOnClickListener(new OnClickListener() {
|
|
56
|
+
@Override
|
|
57
|
+
public void onClick(View v) {
|
|
58
|
+
AudioHelper h = _helperRef.get();
|
|
59
|
+
if (h == null) return;
|
|
60
|
+
boolean playing = h.isPlaying();
|
|
61
|
+
if (playing) {
|
|
62
|
+
h.pause();
|
|
63
|
+
} else {
|
|
64
|
+
h.play();
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
_seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
|
|
70
|
+
@Override
|
|
71
|
+
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
|
|
72
|
+
if (fromUser) {
|
|
73
|
+
updateTimeLabel(progress);
|
|
74
|
+
AudioHelper h = _helperRef.get();
|
|
75
|
+
if (h == null) return;
|
|
76
|
+
float seconds = progress / 1000.0f;
|
|
77
|
+
h.setCurrentTime(seconds);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
@Override
|
|
82
|
+
public void onStartTrackingTouch(SeekBar seekBar) {
|
|
83
|
+
_isSeeking = true;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
@Override
|
|
87
|
+
public void onStopTrackingTouch(SeekBar seekBar) {
|
|
88
|
+
_isSeeking = false;
|
|
89
|
+
AudioHelper h = _helperRef.get();
|
|
90
|
+
if (h == null) return;
|
|
91
|
+
float seconds = seekBar.getProgress() / 1000.0f;
|
|
92
|
+
h.setCurrentTime(seconds);
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
public void setPlaying(boolean playing) {
|
|
98
|
+
_playButton.setImageResource(playing ? android.R.drawable.ic_media_pause : android.R.drawable.ic_media_play);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
public void setDuration(long durationMs) {
|
|
102
|
+
this._durationMs = durationMs;
|
|
103
|
+
if (durationMs > 0) {
|
|
104
|
+
_seekBar.setMax((int)Math.max(durationMs, 1000));
|
|
105
|
+
}
|
|
106
|
+
updateTimeLabel((int)_seekBar.getProgress());
|
|
107
|
+
_seekBar.setEnabled(durationMs > 0);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
public void setCurrentTime(long ms) {
|
|
111
|
+
if (_isSeeking) return;
|
|
112
|
+
_seekBar.setProgress((int)ms);
|
|
113
|
+
updateTimeLabel((int)ms);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
private void updateTimeLabel(int ms) {
|
|
117
|
+
int totalSec = ms / 1000;
|
|
118
|
+
int mins = totalSec / 60;
|
|
119
|
+
int secs = totalSec % 60;
|
|
120
|
+
int durationSec = (int)(this._durationMs / 1000);
|
|
121
|
+
int dmins = durationSec / 60;
|
|
122
|
+
int dsecs = durationSec % 60;
|
|
123
|
+
_timeLabel.setText(String.format("%d:%02d / %d:%02d", mins, secs, dmins, dsecs));
|
|
124
|
+
}
|
|
125
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
package org.nativescript.canvas.media;
|
|
2
|
+
|
|
3
|
+
import android.util.Log;
|
|
4
|
+
import java.io.File;
|
|
5
|
+
|
|
6
|
+
import androidx.media3.datasource.cache.SimpleCache;
|
|
7
|
+
import androidx.media3.datasource.cache.LeastRecentlyUsedCacheEvictor;
|
|
8
|
+
import androidx.media3.database.DatabaseProvider;
|
|
9
|
+
|
|
10
|
+
public class SimpleCacheProvider {
|
|
11
|
+
private static SimpleCache instance = null;
|
|
12
|
+
|
|
13
|
+
public synchronized static SimpleCache getInstance(File cacheDir, LeastRecentlyUsedCacheEvictor evictor, DatabaseProvider dbProvider) {
|
|
14
|
+
if (instance == null) {
|
|
15
|
+
try {
|
|
16
|
+
instance = new SimpleCache(cacheDir, evictor, dbProvider);
|
|
17
|
+
} catch (IllegalStateException e) {
|
|
18
|
+
Log.w("SimpleCacheProvider", "SimpleCache already exists for dir: " + cacheDir + " - " + e.getMessage());
|
|
19
|
+
instance = null;
|
|
20
|
+
} catch (Exception e) {
|
|
21
|
+
Log.w("SimpleCacheProvider", "Failed to create SimpleCache: " + e.getMessage());
|
|
22
|
+
instance = null;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return instance;
|
|
26
|
+
}
|
|
27
|
+
}
|