@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
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|