@nativescript/canvas-media 2.0.12 → 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.
Files changed (34) hide show
  1. package/audio/common.d.ts +29 -3
  2. package/audio/common.js +90 -0
  3. package/audio/common.js.map +1 -1
  4. package/audio/index.android.d.ts +32 -1
  5. package/audio/index.android.js +219 -1
  6. package/audio/index.android.js.map +1 -1
  7. package/audio/index.ios.d.ts +27 -4
  8. package/audio/index.ios.js +183 -142
  9. package/audio/index.ios.js.map +1 -1
  10. package/common.d.ts +1 -0
  11. package/common.js +3 -0
  12. package/common.js.map +1 -1
  13. package/package.json +1 -1
  14. package/platforms/android/canvas_media.aar +0 -0
  15. package/platforms/android/java/org/nativescript/canvas/media/AudioHelper.java +338 -0
  16. package/platforms/android/java/org/nativescript/canvas/media/AudioView.java +125 -0
  17. package/platforms/android/java/org/nativescript/canvas/media/SimpleCacheProvider.java +27 -0
  18. package/platforms/android/java/org/nativescript/canvas/media/VideoHelper.java +60 -5
  19. package/platforms/ios/src/NSCAudioHelper.h +47 -0
  20. package/platforms/ios/src/NSCAudioHelper.m +713 -0
  21. package/platforms/ios/src/NSCAudioView.h +20 -0
  22. package/platforms/ios/src/NSCAudioView.m +270 -0
  23. package/platforms/ios/src/NSCCanvasMediaUmbrella.h +3 -1
  24. package/platforms/ios/src/NSCVideoHelper.h +4 -0
  25. package/platforms/ios/src/NSCVideoHelper.m +63 -5
  26. package/video/common.d.ts +3 -0
  27. package/video/common.js +3 -0
  28. package/video/common.js.map +1 -1
  29. package/video/index.android.d.ts +2 -1
  30. package/video/index.android.js +41 -8
  31. package/video/index.android.js.map +1 -1
  32. package/video/index.ios.d.ts +1 -0
  33. package/video/index.ios.js +26 -1
  34. 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
+ }
@@ -60,6 +60,9 @@ import androidx.media3.exoplayer.ExoPlayer;
60
60
  import androidx.media3.exoplayer.source.DefaultMediaSourceFactory;
61
61
  import androidx.media3.ui.PlayerView;
62
62
 
63
+ import android.media.MediaCodecInfo;
64
+ import android.media.MediaCodecList;
65
+
63
66
  import org.nativescript.canvas_media.R;
64
67
 
65
68
  @UnstableApi
@@ -80,7 +83,7 @@ public class VideoHelper implements Player.Listener, SurfaceTexture.OnFrameAvail
80
83
  TextureView _textureView;
81
84
  private DefaultDataSource.Factory _dsf;
82
85
 
83
- private static SimpleCache _cache;
86
+ // SimpleCache is provided centrally to avoid duplicate instances locking the same folder.
84
87
  private LeastRecentlyUsedCacheEvictor _leastRecentlyUsedCacheEvictor;
85
88
  private StandaloneDatabaseProvider _exoDatabaseProvider;
86
89
  private static final long _exoPlayerCacheSize = 100 * 1024 * 1024;
@@ -89,6 +92,8 @@ public class VideoHelper implements Player.Listener, SurfaceTexture.OnFrameAvail
89
92
  boolean _isCustom = false;
90
93
  boolean _playing = false;
91
94
  private final AtomicBoolean _loadedDataFired = new AtomicBoolean(false);
95
+ private boolean _canPlayFired = false;
96
+ private boolean _canPlayThroughFired = false;
92
97
  Timer _timer;
93
98
  SurfaceTexture _st;
94
99
  Surface _surface;
@@ -158,6 +163,10 @@ public class VideoHelper implements Player.Listener, SurfaceTexture.OnFrameAvail
158
163
 
159
164
  void onLoadedData();
160
165
 
166
+ void onCanPlay();
167
+
168
+ void onCanPlayThrough();
169
+
161
170
  void onError(String message);
162
171
  }
163
172
 
@@ -282,14 +291,14 @@ public class VideoHelper implements Player.Listener, SurfaceTexture.OnFrameAvail
282
291
  _leastRecentlyUsedCacheEvictor = new LeastRecentlyUsedCacheEvictor(VideoHelper._exoPlayerCacheSize);
283
292
  String packageName = context.getPackageName();
284
293
  _exoDatabaseProvider = new StandaloneDatabaseProvider(context);
285
- if (_cache == null) {
286
- _cache = new SimpleCache(cacheDir, _leastRecentlyUsedCacheEvictor, _exoDatabaseProvider);
287
- }
294
+ SimpleCache cache = SimpleCacheProvider.getInstance(cacheDir, _leastRecentlyUsedCacheEvictor, _exoDatabaseProvider);
288
295
  DefaultHttpDataSource.Factory factory = new DefaultHttpDataSource.Factory();
289
296
  factory.setUserAgent(Util.getUserAgent(context, packageName));
290
297
  _dsf = new DefaultDataSource.Factory(context, factory);
291
298
  _cdsf = new CacheDataSource.Factory();
292
- _cdsf.setCache(_cache);
299
+ if (cache != null) {
300
+ _cdsf.setCache(cache);
301
+ }
293
302
  _cdsf.setUpstreamDataSourceFactory(_dsf);
294
303
  _msf = new DefaultMediaSourceFactory(_cdsf);
295
304
 
@@ -1003,6 +1012,8 @@ public class VideoHelper implements Player.Listener, SurfaceTexture.OnFrameAvail
1003
1012
  public void setSrc(String value) {
1004
1013
  this._src = value;
1005
1014
  this._loadedDataFired.set(false);
1015
+ this._canPlayFired = false;
1016
+ this._canPlayThroughFired = false;
1006
1017
  // Video dimensions will change — release the ImageReader so setupBitmapSurface()
1007
1018
  // recreates it at the new size once onVideoSizeChanged fires.
1008
1019
  if (_surfaceOwner == SurfaceOwner.BITMAP) {
@@ -1066,6 +1077,34 @@ public class VideoHelper implements Player.Listener, SurfaceTexture.OnFrameAvail
1066
1077
  }
1067
1078
  }
1068
1079
 
1080
+ public String canPlayType(String type) {
1081
+ if (type == null || type.trim().isEmpty()) return "";
1082
+ String mime = type;
1083
+ int sc = mime.indexOf(';');
1084
+ if (sc >= 0) mime = mime.substring(0, sc).trim();
1085
+ String t = mime.toLowerCase();
1086
+ try {
1087
+ if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) {
1088
+ MediaCodecList mcl = new MediaCodecList(MediaCodecList.ALL_CODECS);
1089
+ MediaCodecInfo[] infos = mcl.getCodecInfos();
1090
+ for (MediaCodecInfo info : infos) {
1091
+ if (info.isEncoder()) continue;
1092
+ String[] types = info.getSupportedTypes();
1093
+ for (String s : types) {
1094
+ if (s.equalsIgnoreCase(t)) {
1095
+ return "probably";
1096
+ }
1097
+ }
1098
+ }
1099
+ }
1100
+ } catch (Throwable e) {}
1101
+
1102
+ if (t.contains("mp4") || t.contains("mpeg") || t.contains("h264") || t.contains("video/mp4")) return "probably";
1103
+ if (t.contains("webm") || t.contains("vp8") || t.contains("vp9") || t.contains("video/webm")) return "maybe";
1104
+ if (t.contains("ogg") || t.contains("theora") || t.contains("video/ogg")) return "maybe";
1105
+ return "";
1106
+ }
1107
+
1069
1108
 
1070
1109
  public int getWidth() {
1071
1110
  int width = this._container.getWidth();
@@ -1164,11 +1203,27 @@ public class VideoHelper implements Player.Listener, SurfaceTexture.OnFrameAvail
1164
1203
  public void onPlaybackStateChanged(@Player.State int playbackState) {
1165
1204
  if (playbackState == Player.STATE_READY) {
1166
1205
  this._readyState = 2; // HAVE_CURRENT_DATA
1206
+ if (!this._canPlayFired) {
1207
+ this._canPlayFired = true;
1208
+ if (this._callback != null) {
1209
+ this._callback.onCanPlay();
1210
+ }
1211
+ }
1212
+
1167
1213
  if (_loadedDataFired.compareAndSet(false, true)) {
1168
1214
  if (this._callback != null) {
1169
1215
  this._callback.onLoadedData();
1170
1216
  }
1171
1217
  }
1218
+
1219
+ long duration = this._player.getDuration();
1220
+ long buffered = this._player.getBufferedPosition();
1221
+ if (duration > 0 && buffered >= duration - 1000 && !this._canPlayThroughFired) {
1222
+ this._canPlayThroughFired = true;
1223
+ if (this._callback != null) {
1224
+ this._callback.onCanPlayThrough();
1225
+ }
1226
+ }
1172
1227
  }
1173
1228
  }
1174
1229
 
@@ -0,0 +1,47 @@
1
+ //
2
+ // NSCAudioHelper.h
3
+ // CanvasMedia
4
+ //
5
+ //
6
+ #import <Foundation/Foundation.h>
7
+ #import <UIKit/UIKit.h>
8
+ #import <AVFoundation/AVFoundation.h>
9
+ #import "NSCPlayerState.h"
10
+
11
+ @protocol NSCAudioHelperListener <NSObject>
12
+ - (void)onTimeUpdate:(double)seconds;
13
+ - (void)onStateChange:(NSCPlayerState)state;
14
+ - (void)onLoadedData;
15
+ - (void)onError:(NSString *)message;
16
+ - (void)onCanPlay;
17
+ - (void)onCanPlayThrough;
18
+ @end
19
+
20
+ @interface NSCAudioHelper : NSObject
21
+
22
+ @property (nonatomic, strong, readonly) AVPlayer *player;
23
+ @property (nonatomic, strong, readonly) AVPlayerItem *currentItem;
24
+ @property (nonatomic, readonly) NSCPlayerState state;
25
+ @property (nonatomic, readonly) NSCPlayerReadyState readyState;
26
+ @property (nonatomic, weak) id<NSCAudioHelperListener> listener;
27
+ @property (nonatomic) BOOL loop;
28
+ @property (nonatomic) BOOL autoplay;
29
+ @property (nonatomic) BOOL muted;
30
+ @property (nonatomic) BOOL controls;
31
+ @property (nonatomic, strong, readonly) UIView *view;
32
+ @property (nonatomic, copy) NSString *src;
33
+ @property (nonatomic, readonly) double duration;
34
+ @property (nonatomic) double currentTime;
35
+
36
+ - (void)play;
37
+ - (void)pause;
38
+ - (void)load;
39
+
40
+ - (NSString *)canPlayType:(NSString *)type;
41
+
42
+ - (void)setPlayIconName:(NSString *)name;
43
+ - (void)setPauseIconName:(NSString *)name;
44
+ - (void)setIconTintHex:(NSString *)hex;
45
+ - (void)setIconTintColor:(UIColor *)color;
46
+
47
+ @end