@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.
Files changed (46) hide show
  1. package/VideoFrame/common.d.ts +122 -0
  2. package/VideoFrame/common.js +173 -0
  3. package/VideoFrame/common.js.map +1 -0
  4. package/VideoFrame/index.android.d.ts +12 -0
  5. package/VideoFrame/index.android.js +113 -0
  6. package/VideoFrame/index.android.js.map +1 -0
  7. package/VideoFrame/index.ios.d.ts +12 -0
  8. package/VideoFrame/index.ios.js +122 -0
  9. package/VideoFrame/index.ios.js.map +1 -0
  10. package/audio/common.d.ts +29 -3
  11. package/audio/common.js +90 -0
  12. package/audio/common.js.map +1 -1
  13. package/audio/index.android.d.ts +32 -1
  14. package/audio/index.android.js +219 -1
  15. package/audio/index.android.js.map +1 -1
  16. package/audio/index.ios.d.ts +27 -4
  17. package/audio/index.ios.js +183 -142
  18. package/audio/index.ios.js.map +1 -1
  19. package/common.d.ts +1 -0
  20. package/common.js +3 -0
  21. package/common.js.map +1 -1
  22. package/index.d.ts +2 -0
  23. package/index.js +1 -0
  24. package/index.js.map +1 -1
  25. package/package.json +1 -1
  26. package/platforms/android/canvas_media.aar +0 -0
  27. package/platforms/android/java/org/nativescript/canvas/media/AudioHelper.java +338 -0
  28. package/platforms/android/java/org/nativescript/canvas/media/AudioView.java +125 -0
  29. package/platforms/android/java/org/nativescript/canvas/media/SimpleCacheProvider.java +27 -0
  30. package/platforms/android/java/org/nativescript/canvas/media/VideoHelper.java +204 -5
  31. package/platforms/ios/src/NSCAudioHelper.h +47 -0
  32. package/platforms/ios/src/NSCAudioHelper.m +713 -0
  33. package/platforms/ios/src/NSCAudioView.h +20 -0
  34. package/platforms/ios/src/NSCAudioView.m +270 -0
  35. package/platforms/ios/src/NSCCanvasMediaUmbrella.h +3 -1
  36. package/platforms/ios/src/NSCVideoHelper.h +6 -0
  37. package/platforms/ios/src/NSCVideoHelper.m +111 -4
  38. package/video/common.d.ts +4 -0
  39. package/video/common.js +3 -0
  40. package/video/common.js.map +1 -1
  41. package/video/index.android.d.ts +3 -1
  42. package/video/index.android.js +44 -8
  43. package/video/index.android.js.map +1 -1
  44. package/video/index.ios.d.ts +2 -0
  45. package/video/index.ios.js +28 -0
  46. package/video/index.ios.js.map +1 -1
@@ -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;
@@ -141,6 +146,10 @@ public class VideoHelper implements Player.Listener, SurfaceTexture.OnFrameAvail
141
146
  // Reusable bitmap for getCurrentBitmap to avoid per-frame allocation
142
147
  private android.graphics.Bitmap _reusableBitmap;
143
148
 
149
+ // Dimensions of the most-recently returned getRgbaBytes() frame
150
+ private int _lastRgbaWidth = 0;
151
+ private int _lastRgbaHeight = 0;
152
+
144
153
  public interface Callback {
145
154
  void onDurationChange(long duration);
146
155
 
@@ -154,6 +163,10 @@ public class VideoHelper implements Player.Listener, SurfaceTexture.OnFrameAvail
154
163
 
155
164
  void onLoadedData();
156
165
 
166
+ void onCanPlay();
167
+
168
+ void onCanPlayThrough();
169
+
157
170
  void onError(String message);
158
171
  }
159
172
 
@@ -278,14 +291,14 @@ public class VideoHelper implements Player.Listener, SurfaceTexture.OnFrameAvail
278
291
  _leastRecentlyUsedCacheEvictor = new LeastRecentlyUsedCacheEvictor(VideoHelper._exoPlayerCacheSize);
279
292
  String packageName = context.getPackageName();
280
293
  _exoDatabaseProvider = new StandaloneDatabaseProvider(context);
281
- if (_cache == null) {
282
- _cache = new SimpleCache(cacheDir, _leastRecentlyUsedCacheEvictor, _exoDatabaseProvider);
283
- }
294
+ SimpleCache cache = SimpleCacheProvider.getInstance(cacheDir, _leastRecentlyUsedCacheEvictor, _exoDatabaseProvider);
284
295
  DefaultHttpDataSource.Factory factory = new DefaultHttpDataSource.Factory();
285
296
  factory.setUserAgent(Util.getUserAgent(context, packageName));
286
297
  _dsf = new DefaultDataSource.Factory(context, factory);
287
298
  _cdsf = new CacheDataSource.Factory();
288
- _cdsf.setCache(_cache);
299
+ if (cache != null) {
300
+ _cdsf.setCache(cache);
301
+ }
289
302
  _cdsf.setUpstreamDataSourceFactory(_dsf);
290
303
  _msf = new DefaultMediaSourceFactory(_cdsf);
291
304
 
@@ -696,6 +709,121 @@ public class VideoHelper implements Player.Listener, SurfaceTexture.OnFrameAvail
696
709
  return null;
697
710
  }
698
711
 
712
+ public java.nio.ByteBuffer getRgbaBuffer() {
713
+ if (_imageReader == null && _surfaceOwner == SurfaceOwner.NONE) {
714
+ setupBitmapSurface();
715
+ }
716
+
717
+ if (_imageReader != null) {
718
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
719
+ // ── API 33+: PRIVATE → HardwareBuffer → software Bitmap → RGBA ──────────
720
+ try (Image image = _imageReader.acquireLatestImage()) {
721
+ if (image == null) return null;
722
+ android.hardware.HardwareBuffer hb = image.getHardwareBuffer();
723
+ if (hb == null) return null;
724
+ android.graphics.Bitmap hwBmp = android.graphics.Bitmap.wrapHardwareBuffer(hb, null);
725
+ hb.close();
726
+ int w = hwBmp.getWidth(), h = hwBmp.getHeight();
727
+ android.graphics.Bitmap softBmp = android.graphics.Bitmap.createBitmap(
728
+ w, h, android.graphics.Bitmap.Config.ARGB_8888);
729
+ new android.graphics.Canvas(softBmp).drawBitmap(hwBmp, 0, 0, null);
730
+ hwBmp.recycle();
731
+
732
+ // copyPixelsToBuffer requires native byte order; on ARM (little-endian)
733
+ // ARGB_8888 int 0xAARRGGBB → bytes [B, G, R, A]. Swap B↔R → RGBA.
734
+ java.nio.ByteBuffer buf = java.nio.ByteBuffer
735
+ .allocateDirect(w * h * 4)
736
+ .order(java.nio.ByteOrder.nativeOrder());
737
+ softBmp.copyPixelsToBuffer(buf);
738
+ softBmp.recycle();
739
+
740
+ for (int i = 0, n = w * h * 4; i < n; i += 4) {
741
+ byte b = buf.get(i);
742
+ buf.put(i, buf.get(i + 2)); // R → position 0
743
+ buf.put(i + 2, b); // B → position 2
744
+ // G (i+1) and A (i+3) are already correct
745
+ }
746
+ buf.rewind();
747
+ _lastRgbaWidth = w;
748
+ _lastRgbaHeight = h;
749
+ return buf;
750
+ } catch (Exception e) {
751
+ if (IS_DEBUG) android.util.Log.d("JS", "getRgbaBuffer HW: " + e);
752
+ }
753
+ } else {
754
+ // API < 33: RGBA_8888 plane → direct buffer, strip row padding
755
+ try (Image image = _imageReader.acquireLatestImage()) {
756
+ if (image == null) return null;
757
+ Image.Plane plane = image.getPlanes()[0];
758
+ java.nio.ByteBuffer srcBuf = plane.getBuffer();
759
+ int w = image.getWidth();
760
+ int h = image.getHeight();
761
+ int pixelStride = plane.getPixelStride(); // typically 4 for RGBA_8888
762
+ int rowStride = plane.getRowStride(); // may include alignment padding
763
+ int rowBytes = w * 4;
764
+
765
+ java.nio.ByteBuffer dst = java.nio.ByteBuffer.allocateDirect(rowBytes * h);
766
+ if (pixelStride == 4 && rowStride == rowBytes) {
767
+ // No padding — single bulk copy
768
+ srcBuf.rewind();
769
+ dst.put(srcBuf);
770
+ } else {
771
+ // Padding present — strip it row by row using absolute-position reads,
772
+ // which work correctly when writing into a direct ByteBuffer on older APIs.
773
+ for (int row = 0; row < h; row++) {
774
+ for (int col = 0; col < w; col++) {
775
+ int srcOff = row * rowStride + col * pixelStride;
776
+ dst.put(srcBuf.get(srcOff));
777
+ dst.put(srcBuf.get(srcOff + 1));
778
+ dst.put(srcBuf.get(srcOff + 2));
779
+ dst.put(srcBuf.get(srcOff + 3));
780
+ }
781
+ }
782
+ }
783
+ dst.rewind();
784
+ _lastRgbaWidth = w;
785
+ _lastRgbaHeight = h;
786
+ return dst;
787
+ } catch (Exception e) {
788
+ if (IS_DEBUG) android.util.Log.d("JS", "getRgbaBuffer SW: " + e);
789
+ }
790
+ }
791
+ }
792
+
793
+ View surfaceView = this._playerView.getVideoSurfaceView();
794
+ if (surfaceView instanceof TextureView && ((TextureView) surfaceView).isAvailable()) {
795
+ TextureView tv = (TextureView) surfaceView;
796
+ int w = _videoWidth > 0 ? _videoWidth : tv.getWidth();
797
+ int h = _videoHeight > 0 ? _videoHeight : tv.getHeight();
798
+ if (w <= 0 || h <= 0) return null;
799
+ android.graphics.Bitmap bmp = android.graphics.Bitmap.createBitmap(
800
+ w, h, android.graphics.Bitmap.Config.ARGB_8888);
801
+ tv.getBitmap(bmp);
802
+
803
+ java.nio.ByteBuffer buf = java.nio.ByteBuffer
804
+ .allocateDirect(w * h * 4)
805
+ .order(java.nio.ByteOrder.nativeOrder());
806
+ bmp.copyPixelsToBuffer(buf);
807
+ bmp.recycle();
808
+
809
+ // BGRA → RGBA
810
+ for (int i = 0, n = w * h * 4; i < n; i += 4) {
811
+ byte b = buf.get(i);
812
+ buf.put(i, buf.get(i + 2));
813
+ buf.put(i + 2, b);
814
+ }
815
+ buf.rewind();
816
+ _lastRgbaWidth = w;
817
+ _lastRgbaHeight = h;
818
+ return buf;
819
+ }
820
+
821
+ return null;
822
+ }
823
+
824
+ public int getLastRgbaWidth() { return _lastRgbaWidth; }
825
+ public int getLastRgbaHeight() { return _lastRgbaHeight; }
826
+
699
827
  private boolean drawFrame2DBitmap(long context, float sx, float sy, float sw, float sh, float dx, float dy, float dw, float dh) {
700
828
  android.graphics.Bitmap bitmap = getCurrentBitmap();
701
829
  if (bitmap == null) return false;
@@ -884,6 +1012,8 @@ public class VideoHelper implements Player.Listener, SurfaceTexture.OnFrameAvail
884
1012
  public void setSrc(String value) {
885
1013
  this._src = value;
886
1014
  this._loadedDataFired.set(false);
1015
+ this._canPlayFired = false;
1016
+ this._canPlayThroughFired = false;
887
1017
  // Video dimensions will change — release the ImageReader so setupBitmapSurface()
888
1018
  // recreates it at the new size once onVideoSizeChanged fires.
889
1019
  if (_surfaceOwner == SurfaceOwner.BITMAP) {
@@ -893,6 +1023,31 @@ public class VideoHelper implements Player.Listener, SurfaceTexture.OnFrameAvail
893
1023
  this._setSrc(value);
894
1024
  }
895
1025
 
1026
+ public void load() {
1027
+ try {
1028
+ this.pause();
1029
+
1030
+ if (_timer != null) { _timer.cancel(); _timer.purge(); _timer = null; }
1031
+ if (_frameTimer != null) { _frameTimer.cancel(); _frameTimer.purge(); _frameTimer = null; }
1032
+
1033
+ if (_glSurface != null) { _glSurface.release(); _glSurface = null; }
1034
+ if (_glSt != null) { _glSt.release(); _glSt = null; }
1035
+
1036
+ if (_imageReader != null) { try { _imageReader.close(); } catch (Exception ignored) {} _imageReader = null; }
1037
+
1038
+ _surfaceOwner = SurfaceOwner.NONE;
1039
+ _hasFrame = false;
1040
+ _glHasFrame = false;
1041
+ _loadedDataFired.set(false);
1042
+
1043
+ if (this._src != null && !this._src.isEmpty()) {
1044
+ this._setSrc(this._src);
1045
+ }
1046
+ } catch (Exception e) {
1047
+ if (IS_DEBUG) android.util.Log.d("JS", "VideoHelper.load() error: " + e);
1048
+ }
1049
+ }
1050
+
896
1051
  public boolean getAutoplay() {
897
1052
  return this._autoplay;
898
1053
  }
@@ -922,6 +1077,34 @@ public class VideoHelper implements Player.Listener, SurfaceTexture.OnFrameAvail
922
1077
  }
923
1078
  }
924
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
+
925
1108
 
926
1109
  public int getWidth() {
927
1110
  int width = this._container.getWidth();
@@ -1020,11 +1203,27 @@ public class VideoHelper implements Player.Listener, SurfaceTexture.OnFrameAvail
1020
1203
  public void onPlaybackStateChanged(@Player.State int playbackState) {
1021
1204
  if (playbackState == Player.STATE_READY) {
1022
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
+
1023
1213
  if (_loadedDataFired.compareAndSet(false, true)) {
1024
1214
  if (this._callback != null) {
1025
1215
  this._callback.onLoadedData();
1026
1216
  }
1027
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
+ }
1028
1227
  }
1029
1228
  }
1030
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