@nativescript/canvas-media 3.0.0-alpha.9 → 3.0.0-beta.2
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/index.windows.d.ts +12 -0
- package/VideoFrame/index.windows.js +98 -0
- package/VideoFrame/index.windows.js.map +1 -0
- package/audio/index.windows.d.ts +44 -0
- package/audio/index.windows.js +158 -0
- package/audio/index.windows.js.map +1 -0
- package/package.json +5 -3
- package/platforms/android/canvas_media.aar +0 -0
- package/platforms/android/java/org/nativescript/canvas/media/VideoHelper.java +66 -2
- package/platforms/ios/src/NSCVideoHelper+Internal.h +4 -0
- package/platforms/ios/src/NSCVideoHelper.h +2 -0
- package/platforms/ios/src/NSCVideoHelper.m +70 -16
- package/platforms/windows/arm64/canvasmedia.node +0 -0
- package/platforms/windows/plugin.props +2 -0
- package/platforms/windows/plugin.targets +33 -0
- package/platforms/windows/x64/canvasmedia.node +0 -0
- package/player-windows.d.ts +79 -0
- package/player-windows.js +302 -0
- package/player-windows.js.map +1 -0
- package/video/index.android.js +2 -1
- package/video/index.android.js.map +1 -1
- package/video/index.ios.d.ts +1 -1
- package/video/index.ios.js +17 -4
- package/video/index.ios.js.map +1 -1
- package/video/index.windows.d.ts +80 -0
- package/video/index.windows.js +372 -0
- package/video/index.windows.js.map +1 -0
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { VideoFrameBase, VideoColorSpace, DOMRectReadOnly, VideoFrameInit, VideoFrameBufferInit, VideoFrameCopyToOptions, PlaneLayout, RectInit, VideoPixelFormat, VideoColorSpaceInit, _INTERNAL } from './common';
|
|
2
|
+
export { VideoColorSpace, DOMRectReadOnly, _INTERNAL };
|
|
3
|
+
export type { VideoPixelFormat, VideoFrameInit, VideoFrameBufferInit, VideoFrameCopyToOptions, PlaneLayout, RectInit, VideoColorSpaceInit };
|
|
4
|
+
export declare class VideoFrame extends VideoFrameBase {
|
|
5
|
+
constructor(source: ArrayBuffer | ArrayBufferView, init: VideoFrameBufferInit);
|
|
6
|
+
constructor(source: object, init?: VideoFrameInit);
|
|
7
|
+
static fromVideo(source: any, init?: VideoFrameInit): VideoFrame | null;
|
|
8
|
+
private static _dataFromBuffer;
|
|
9
|
+
private static _dataFromVideoSource;
|
|
10
|
+
private static _assertBufferInit;
|
|
11
|
+
protected _doClone(): VideoFrame;
|
|
12
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { VideoFrameBase, VideoColorSpace, DOMRectReadOnly, _INTERNAL, bgraToRgba } from './common';
|
|
2
|
+
export { VideoColorSpace, DOMRectReadOnly, _INTERNAL };
|
|
3
|
+
export class VideoFrame extends VideoFrameBase {
|
|
4
|
+
constructor(source, init) {
|
|
5
|
+
if (source === _INTERNAL) {
|
|
6
|
+
super(init);
|
|
7
|
+
return;
|
|
8
|
+
}
|
|
9
|
+
if (source instanceof ArrayBuffer || ArrayBuffer.isView(source)) {
|
|
10
|
+
super(VideoFrame._dataFromBuffer(source, init));
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
super(VideoFrame._dataFromVideoSource(source, init));
|
|
14
|
+
}
|
|
15
|
+
static fromVideo(source, init) {
|
|
16
|
+
try {
|
|
17
|
+
const data = VideoFrame._dataFromVideoSource(source, init);
|
|
18
|
+
// Return null when no pixel data is available yet (no frame decoded).
|
|
19
|
+
// Callers can treat null as "not ready" and fall back to drawImage.
|
|
20
|
+
if (!data.pixelData)
|
|
21
|
+
return null;
|
|
22
|
+
return new VideoFrame(_INTERNAL, data);
|
|
23
|
+
}
|
|
24
|
+
catch (err) {
|
|
25
|
+
console.error('[VideoFrame] fromVideo:', err?.message ?? err);
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
static _dataFromBuffer(source, init) {
|
|
30
|
+
VideoFrame._assertBufferInit(init);
|
|
31
|
+
const buf = ArrayBuffer.isView(source) ? new Uint8Array(source.buffer, source.byteOffset, source.byteLength) : new Uint8Array(source);
|
|
32
|
+
const pixelData = init.format === 'BGRA' || init.format === 'BGRX' ? bgraToRgba(buf) : new Uint8Array(buf);
|
|
33
|
+
return {
|
|
34
|
+
pixelData,
|
|
35
|
+
codedWidth: init.codedWidth,
|
|
36
|
+
codedHeight: init.codedHeight,
|
|
37
|
+
displayWidth: init.displayWidth ?? init.codedWidth,
|
|
38
|
+
displayHeight: init.displayHeight ?? init.codedHeight,
|
|
39
|
+
timestamp: init.timestamp,
|
|
40
|
+
duration: init.duration ?? null,
|
|
41
|
+
format: 'RGBA',
|
|
42
|
+
colorSpace: init.colorSpace,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
static _dataFromVideoSource(source, init) {
|
|
46
|
+
const video = source?._video ?? source;
|
|
47
|
+
if (typeof video?._currentFrame !== 'function') {
|
|
48
|
+
throw new TypeError('[VideoFrame] Source must be an HTMLVideoElement or a canvas-media Video instance');
|
|
49
|
+
}
|
|
50
|
+
const timestampUs = typeof init?.timestamp === 'number' ? init.timestamp : Math.round(video.currentTime * 1000000);
|
|
51
|
+
// A copy: the video hands the same buffer to every reader until its next frame.
|
|
52
|
+
const frame = video._currentFrame();
|
|
53
|
+
const pixelData = frame ? new Uint8Array(frame.pixels) : null;
|
|
54
|
+
const codedWidth = frame?.width ?? video.videoWidth ?? 0;
|
|
55
|
+
const codedHeight = frame?.height ?? video.videoHeight ?? 0;
|
|
56
|
+
return {
|
|
57
|
+
pixelData,
|
|
58
|
+
codedWidth,
|
|
59
|
+
codedHeight,
|
|
60
|
+
displayWidth: init?.displayWidth ?? codedWidth,
|
|
61
|
+
displayHeight: init?.displayHeight ?? codedHeight,
|
|
62
|
+
timestamp: timestampUs,
|
|
63
|
+
duration: init?.duration ?? null,
|
|
64
|
+
format: 'RGBA',
|
|
65
|
+
colorSpace: init?.colorSpace,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
static _assertBufferInit(init) {
|
|
69
|
+
if (!init?.format) {
|
|
70
|
+
throw new TypeError('VideoFrame: init.format is required for the buffer constructor');
|
|
71
|
+
}
|
|
72
|
+
if (!init?.codedWidth || init.codedWidth <= 0) {
|
|
73
|
+
throw new TypeError('VideoFrame: init.codedWidth must be a positive integer');
|
|
74
|
+
}
|
|
75
|
+
if (!init?.codedHeight || init.codedHeight <= 0) {
|
|
76
|
+
throw new TypeError('VideoFrame: init.codedHeight must be a positive integer');
|
|
77
|
+
}
|
|
78
|
+
if (typeof init?.timestamp !== 'number' || !isFinite(init.timestamp)) {
|
|
79
|
+
throw new TypeError('VideoFrame: init.timestamp must be a finite number (microseconds)');
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
_doClone() {
|
|
83
|
+
const pixelCopy = this._pixelData ? new Uint8Array(this._pixelData) : null;
|
|
84
|
+
const data = {
|
|
85
|
+
pixelData: pixelCopy,
|
|
86
|
+
codedWidth: this.codedWidth,
|
|
87
|
+
codedHeight: this.codedHeight,
|
|
88
|
+
displayWidth: this.displayWidth,
|
|
89
|
+
displayHeight: this.displayHeight,
|
|
90
|
+
timestamp: this.timestamp,
|
|
91
|
+
duration: this.duration,
|
|
92
|
+
format: this.format,
|
|
93
|
+
colorSpace: this.colorSpace.toJSON(),
|
|
94
|
+
};
|
|
95
|
+
return new VideoFrame(_INTERNAL, data);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
//# sourceMappingURL=index.windows.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.windows.js","sourceRoot":"","sources":["../../../../packages/canvas-media/VideoFrame/index.windows.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { AudioBase } from './common';
|
|
2
|
+
import { Source } from '../source';
|
|
3
|
+
import { WindowsMediaPlayer } from '../player-windows';
|
|
4
|
+
export declare class Audio extends AudioBase {
|
|
5
|
+
_media: WindowsMediaPlayer;
|
|
6
|
+
_sourceView: Source[];
|
|
7
|
+
private _sources;
|
|
8
|
+
_isCustom: boolean;
|
|
9
|
+
private _controls;
|
|
10
|
+
private _grid;
|
|
11
|
+
private _element;
|
|
12
|
+
constructor();
|
|
13
|
+
static createCustomView(): Audio;
|
|
14
|
+
get _player(): any;
|
|
15
|
+
createNativeView(): any;
|
|
16
|
+
disposeNativeView(): void;
|
|
17
|
+
private _syncControls;
|
|
18
|
+
get readyState(): number;
|
|
19
|
+
canPlayType(type: string): "" | "maybe";
|
|
20
|
+
_addChildFromBuilder(name: string, value: any): void;
|
|
21
|
+
onLoaded(): void;
|
|
22
|
+
_onLoadError(): boolean;
|
|
23
|
+
get duration(): number;
|
|
24
|
+
get currentTime(): number;
|
|
25
|
+
set currentTime(value: number);
|
|
26
|
+
get muted(): boolean;
|
|
27
|
+
set muted(value: boolean);
|
|
28
|
+
get volume(): number;
|
|
29
|
+
set volume(value: number);
|
|
30
|
+
get paused(): boolean;
|
|
31
|
+
get src(): string;
|
|
32
|
+
set src(value: string);
|
|
33
|
+
load(): void;
|
|
34
|
+
play(): Promise<void>;
|
|
35
|
+
pause(): void;
|
|
36
|
+
get controls(): boolean;
|
|
37
|
+
set controls(value: boolean);
|
|
38
|
+
get autoplay(): boolean;
|
|
39
|
+
set autoplay(value: boolean);
|
|
40
|
+
get loop(): boolean;
|
|
41
|
+
set loop(value: boolean);
|
|
42
|
+
attachAudioContextTap(): any;
|
|
43
|
+
detachAudioContextTap(): void;
|
|
44
|
+
}
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import { booleanConverter } from '@nativescript/core';
|
|
2
|
+
import { AudioBase } from './common';
|
|
3
|
+
import { Source } from '../source';
|
|
4
|
+
import { WindowsMediaPlayer, canPlayType } from '../player-windows';
|
|
5
|
+
export class Audio extends AudioBase {
|
|
6
|
+
constructor() {
|
|
7
|
+
super();
|
|
8
|
+
this._sourceView = [];
|
|
9
|
+
this._sources = [];
|
|
10
|
+
this._isCustom = false;
|
|
11
|
+
this._controls = false;
|
|
12
|
+
this._media = new WindowsMediaPlayer(this, false);
|
|
13
|
+
}
|
|
14
|
+
static createCustomView() {
|
|
15
|
+
const audio = new Audio();
|
|
16
|
+
audio._isCustom = true;
|
|
17
|
+
audio.width = 300;
|
|
18
|
+
audio.height = 40;
|
|
19
|
+
return audio;
|
|
20
|
+
}
|
|
21
|
+
get _player() {
|
|
22
|
+
return this._media.player;
|
|
23
|
+
}
|
|
24
|
+
createNativeView() {
|
|
25
|
+
this._grid = new Microsoft.UI.Xaml.Controls.Grid();
|
|
26
|
+
this._syncControls();
|
|
27
|
+
return this._grid;
|
|
28
|
+
}
|
|
29
|
+
disposeNativeView() {
|
|
30
|
+
try {
|
|
31
|
+
this._element?.SetMediaPlayer(null);
|
|
32
|
+
}
|
|
33
|
+
catch (e) { }
|
|
34
|
+
this._grid = this._element = null;
|
|
35
|
+
super.disposeNativeView();
|
|
36
|
+
}
|
|
37
|
+
_syncControls() {
|
|
38
|
+
const grid = this._grid;
|
|
39
|
+
if (!grid) {
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
if (this._controls && !this._element) {
|
|
43
|
+
const element = new Microsoft.UI.Xaml.Controls.MediaPlayerElement();
|
|
44
|
+
element.SetMediaPlayer(this._player);
|
|
45
|
+
element.AreTransportControlsEnabled = true;
|
|
46
|
+
try {
|
|
47
|
+
const controls = element.TransportControls;
|
|
48
|
+
controls.IsCompact = true;
|
|
49
|
+
controls.IsFullWindowButtonVisible = false;
|
|
50
|
+
controls.IsZoomButtonVisible = false;
|
|
51
|
+
}
|
|
52
|
+
catch (e) { }
|
|
53
|
+
grid.Children.Append(element);
|
|
54
|
+
this._element = element;
|
|
55
|
+
}
|
|
56
|
+
else if (!this._controls && this._element) {
|
|
57
|
+
const element = this._element;
|
|
58
|
+
this._element = null;
|
|
59
|
+
try {
|
|
60
|
+
element.SetMediaPlayer(null);
|
|
61
|
+
grid.Children.Clear();
|
|
62
|
+
}
|
|
63
|
+
catch (e) { }
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
get readyState() {
|
|
67
|
+
return this._media.readyState;
|
|
68
|
+
}
|
|
69
|
+
canPlayType(type) {
|
|
70
|
+
return canPlayType(type);
|
|
71
|
+
}
|
|
72
|
+
_addChildFromBuilder(name, value) {
|
|
73
|
+
if (value instanceof Source) {
|
|
74
|
+
this._sourceView.push(value);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
onLoaded() {
|
|
78
|
+
super.onLoaded();
|
|
79
|
+
if (!this._media.src) {
|
|
80
|
+
this._sources = this._sourceView.filter((item) => item.src && (!item.type || canPlayType(item.type) !== '')).map((item) => item.src);
|
|
81
|
+
this._onLoadError();
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
_onLoadError() {
|
|
85
|
+
const next = this._sources.shift();
|
|
86
|
+
if (next === undefined) {
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
89
|
+
this._media.src = next;
|
|
90
|
+
return true;
|
|
91
|
+
}
|
|
92
|
+
get duration() {
|
|
93
|
+
return this._media.duration;
|
|
94
|
+
}
|
|
95
|
+
get currentTime() {
|
|
96
|
+
return this._media.currentTime;
|
|
97
|
+
}
|
|
98
|
+
set currentTime(value) {
|
|
99
|
+
this._media.currentTime = value;
|
|
100
|
+
}
|
|
101
|
+
get muted() {
|
|
102
|
+
return this._media.muted;
|
|
103
|
+
}
|
|
104
|
+
set muted(value) {
|
|
105
|
+
this._media.muted = booleanConverter(value);
|
|
106
|
+
}
|
|
107
|
+
get volume() {
|
|
108
|
+
return this._media.volume;
|
|
109
|
+
}
|
|
110
|
+
set volume(value) {
|
|
111
|
+
this._media.volume = value;
|
|
112
|
+
}
|
|
113
|
+
get paused() {
|
|
114
|
+
return this._media.paused;
|
|
115
|
+
}
|
|
116
|
+
get src() {
|
|
117
|
+
return this._media.src;
|
|
118
|
+
}
|
|
119
|
+
set src(value) {
|
|
120
|
+
this._sources = [];
|
|
121
|
+
this._media.src = value;
|
|
122
|
+
}
|
|
123
|
+
load() {
|
|
124
|
+
this._media.load();
|
|
125
|
+
}
|
|
126
|
+
play() {
|
|
127
|
+
return this._media.play();
|
|
128
|
+
}
|
|
129
|
+
pause() {
|
|
130
|
+
this._media.pause();
|
|
131
|
+
}
|
|
132
|
+
get controls() {
|
|
133
|
+
return this._controls;
|
|
134
|
+
}
|
|
135
|
+
set controls(value) {
|
|
136
|
+
this._controls = booleanConverter(value);
|
|
137
|
+
this._syncControls();
|
|
138
|
+
}
|
|
139
|
+
get autoplay() {
|
|
140
|
+
return this._media.autoplay;
|
|
141
|
+
}
|
|
142
|
+
set autoplay(value) {
|
|
143
|
+
this._media.autoplay = booleanConverter(value);
|
|
144
|
+
}
|
|
145
|
+
get loop() {
|
|
146
|
+
return this._media.loop;
|
|
147
|
+
}
|
|
148
|
+
set loop(value) {
|
|
149
|
+
this._media.loop = booleanConverter(value);
|
|
150
|
+
}
|
|
151
|
+
attachAudioContextTap() {
|
|
152
|
+
return this._media.attachAudioTap();
|
|
153
|
+
}
|
|
154
|
+
detachAudioContextTap() {
|
|
155
|
+
this._media.detachAudioTap();
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
//# sourceMappingURL=index.windows.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.windows.js","sourceRoot":"","sources":["../../../../packages/canvas-media/audio/index.windows.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nativescript/canvas-media",
|
|
3
|
-
"version": "3.0.0-
|
|
3
|
+
"version": "3.0.0-beta.2",
|
|
4
4
|
"description": "Canvas media",
|
|
5
5
|
"main": "index",
|
|
6
6
|
"typings": "index.d.ts",
|
|
7
7
|
"nativescript": {
|
|
8
8
|
"platforms": {
|
|
9
9
|
"ios": "6.0.0",
|
|
10
|
-
"android": "6.0.0"
|
|
10
|
+
"android": "6.0.0",
|
|
11
|
+
"windows": "9.1.0"
|
|
11
12
|
}
|
|
12
13
|
},
|
|
13
14
|
"repository": {
|
|
@@ -19,7 +20,8 @@
|
|
|
19
20
|
"JavaScript",
|
|
20
21
|
"TypeScript",
|
|
21
22
|
"iOS",
|
|
22
|
-
"Android"
|
|
23
|
+
"Android",
|
|
24
|
+
"Windows"
|
|
23
25
|
],
|
|
24
26
|
"author": {
|
|
25
27
|
"name": "NativeScript",
|
|
Binary file
|
|
@@ -751,9 +751,10 @@ public class VideoHelper implements Player.Listener, SurfaceTexture.OnFrameAvail
|
|
|
751
751
|
try {
|
|
752
752
|
// PRIVATE, not RGBA_8888: a hardware decoder refuses to convert and fails with
|
|
753
753
|
// "producer output buffer format ... doesn't match the ImageReader's".
|
|
754
|
+
// A threaded 2D canvas holds a frame until the GPU has read it.
|
|
754
755
|
_imageReader = ImageReader.newInstance(
|
|
755
756
|
size[0], size[1],
|
|
756
|
-
ImageFormat.PRIVATE,
|
|
757
|
+
ImageFormat.PRIVATE, 3,
|
|
757
758
|
android.hardware.HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE);
|
|
758
759
|
_imageReader.setOnImageAvailableListener(reader -> {
|
|
759
760
|
_hasFrame = true;
|
|
@@ -785,6 +786,11 @@ public class VideoHelper implements Player.Listener, SurfaceTexture.OnFrameAvail
|
|
|
785
786
|
}
|
|
786
787
|
|
|
787
788
|
public GPUFrame getCurrentGPUFrame() {
|
|
789
|
+
return getCurrentGPUFrame(true);
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
/** @param requireImportable RGB only, which Vulkan samples directly; GL takes YCbCr too. */
|
|
793
|
+
public GPUFrame getCurrentGPUFrame(boolean requireImportable) {
|
|
788
794
|
if (!supportsGPUFrames()) return null;
|
|
789
795
|
|
|
790
796
|
if (_imageReader == null && _surfaceOwner == SurfaceOwner.NONE) {
|
|
@@ -804,7 +810,7 @@ public class VideoHelper implements Player.Listener, SurfaceTexture.OnFrameAvail
|
|
|
804
810
|
return null;
|
|
805
811
|
}
|
|
806
812
|
|
|
807
|
-
if (!isImportableFormat(buffer.getFormat())) {
|
|
813
|
+
if (requireImportable && !isImportableFormat(buffer.getFormat())) {
|
|
808
814
|
// Give up for this player and hand the surface back to the upload path.
|
|
809
815
|
android.util.Log.d("JS", "getCurrentGPUFrame: decoder format "
|
|
810
816
|
+ buffer.getFormat() + " cannot be imported; using the upload path");
|
|
@@ -1029,9 +1035,13 @@ public class VideoHelper implements Player.Listener, SurfaceTexture.OnFrameAvail
|
|
|
1029
1035
|
// On some devices onVideoSizeChanged only fires after the first rendered frame,
|
|
1030
1036
|
// so we must give ExoPlayer a surface first to break the deadlock.
|
|
1031
1037
|
if (backendType == 2) ensureGL2DSurface(context);
|
|
1038
|
+
if (backendType == HARDWARE_BUFFER_2D && _imageReader == null && _surfaceOwner == SurfaceOwner.NONE) setupGpuSurface();
|
|
1032
1039
|
float w = (float) _videoWidth;
|
|
1033
1040
|
float h = (float) _videoHeight;
|
|
1034
1041
|
if (w <= 0 || h <= 0) return false;
|
|
1042
|
+
if (backendType == HARDWARE_BUFFER_2D) {
|
|
1043
|
+
return drawFrame2DHardwareBuffer(context, 0, 0, w, h, dx, dy, w, h);
|
|
1044
|
+
}
|
|
1035
1045
|
if (backendType == 2) {
|
|
1036
1046
|
return drawFrame2DGL(context, 0, 0, w, h, dx, dy, w, h);
|
|
1037
1047
|
}
|
|
@@ -1041,9 +1051,13 @@ public class VideoHelper implements Player.Listener, SurfaceTexture.OnFrameAvail
|
|
|
1041
1051
|
public boolean drawVideoFrame2D(int backendType, long context, float dx, float dy, float dw, float dh) {
|
|
1042
1052
|
if (context == 0) return false;
|
|
1043
1053
|
if (backendType == 2) ensureGL2DSurface(context);
|
|
1054
|
+
if (backendType == HARDWARE_BUFFER_2D && _imageReader == null && _surfaceOwner == SurfaceOwner.NONE) setupGpuSurface();
|
|
1044
1055
|
float w = (float) _videoWidth;
|
|
1045
1056
|
float h = (float) _videoHeight;
|
|
1046
1057
|
if (w <= 0 || h <= 0) return false;
|
|
1058
|
+
if (backendType == HARDWARE_BUFFER_2D) {
|
|
1059
|
+
return drawFrame2DHardwareBuffer(context, 0, 0, w, h, dx, dy, dw, dh);
|
|
1060
|
+
}
|
|
1047
1061
|
if (backendType == 2) {
|
|
1048
1062
|
return drawFrame2DGL(context, 0, 0, w, h, dx, dy, dw, dh);
|
|
1049
1063
|
}
|
|
@@ -1053,7 +1067,11 @@ public class VideoHelper implements Player.Listener, SurfaceTexture.OnFrameAvail
|
|
|
1053
1067
|
public boolean drawVideoFrame2D(int backendType, long context, float sx, float sy, float sw, float sh, float dx, float dy, float dw, float dh) {
|
|
1054
1068
|
if (context == 0) return false;
|
|
1055
1069
|
if (backendType == 2) ensureGL2DSurface(context);
|
|
1070
|
+
if (backendType == HARDWARE_BUFFER_2D && _imageReader == null && _surfaceOwner == SurfaceOwner.NONE) setupGpuSurface();
|
|
1056
1071
|
if (_videoWidth <= 0 || _videoHeight <= 0) return false;
|
|
1072
|
+
if (backendType == HARDWARE_BUFFER_2D) {
|
|
1073
|
+
return drawFrame2DHardwareBuffer(context, sx, sy, sw, sh, dx, dy, dw, dh);
|
|
1074
|
+
}
|
|
1057
1075
|
if (backendType == 2) {
|
|
1058
1076
|
return drawFrame2DGL(context, sx, sy, sw, sh, dx, dy, dw, dh);
|
|
1059
1077
|
}
|
|
@@ -1061,6 +1079,52 @@ public class VideoHelper implements Player.Listener, SurfaceTexture.OnFrameAvail
|
|
|
1061
1079
|
}
|
|
1062
1080
|
|
|
1063
1081
|
|
|
1082
|
+
/** A threaded 2D canvas, which rasterizes off this thread. */
|
|
1083
|
+
public static final int HARDWARE_BUFFER_2D = 5;
|
|
1084
|
+
|
|
1085
|
+
private static Method _drawHardwareBuffer;
|
|
1086
|
+
private static boolean _drawHardwareBufferResolved = false;
|
|
1087
|
+
|
|
1088
|
+
private static Method drawHardwareBufferMethod() {
|
|
1089
|
+
if (_drawHardwareBufferResolved) return _drawHardwareBuffer;
|
|
1090
|
+
_drawHardwareBufferResolved = true;
|
|
1091
|
+
try {
|
|
1092
|
+
Class<?> utils = Class.forName("org.nativescript.canvas.Utils");
|
|
1093
|
+
_drawHardwareBuffer = utils.getMethod("drawVideoFrameHardwareBuffer",
|
|
1094
|
+
long.class, android.hardware.HardwareBuffer.class, Object.class, int.class, int.class,
|
|
1095
|
+
float.class, float.class, float.class, float.class,
|
|
1096
|
+
float.class, float.class, float.class, float.class);
|
|
1097
|
+
} catch (Throwable t) {
|
|
1098
|
+
if (IS_DEBUG) android.util.Log.d("JS", "drawVideoFrameHardwareBuffer unavailable: " + t);
|
|
1099
|
+
_drawHardwareBuffer = null;
|
|
1100
|
+
}
|
|
1101
|
+
return _drawHardwareBuffer;
|
|
1102
|
+
}
|
|
1103
|
+
|
|
1104
|
+
private boolean drawFrame2DHardwareBuffer(long context, float sx, float sy, float sw, float sh, float dx, float dy, float dw, float dh) {
|
|
1105
|
+
Method draw = drawHardwareBufferMethod();
|
|
1106
|
+
if (draw == null || !supportsGPUFrames()) {
|
|
1107
|
+
return drawFrame2DBitmap(context, sx, sy, sw, sh, dx, dy, dw, dh);
|
|
1108
|
+
}
|
|
1109
|
+
GPUFrame frame = getCurrentGPUFrame(false);
|
|
1110
|
+
if (frame == null) {
|
|
1111
|
+
// No new frame, or the GPU path gave up and handed the surface back.
|
|
1112
|
+
return _surfaceOwner == SurfaceOwner.GPU ? false : drawFrame2DBitmap(context, sx, sy, sw, sh, dx, dy, dw, dh);
|
|
1113
|
+
}
|
|
1114
|
+
boolean taken = false;
|
|
1115
|
+
try {
|
|
1116
|
+
Object result = draw.invoke(null, context, frame._buffer, frame, frame.getWidth(), frame.getHeight(),
|
|
1117
|
+
sx, sy, sw, sh, dx, dy, dw, dh);
|
|
1118
|
+
taken = result instanceof Boolean && (Boolean) result;
|
|
1119
|
+
} catch (Throwable t) {
|
|
1120
|
+
if (IS_DEBUG) android.util.Log.d("JS", "drawFrame2DHardwareBuffer: " + t);
|
|
1121
|
+
}
|
|
1122
|
+
if (!taken) {
|
|
1123
|
+
frame.close();
|
|
1124
|
+
}
|
|
1125
|
+
return taken;
|
|
1126
|
+
}
|
|
1127
|
+
|
|
1064
1128
|
private void ensureGL2DSurface(long context) {
|
|
1065
1129
|
if (_glInitFailed || _glSt != null) return;
|
|
1066
1130
|
if (IS_DEBUG) android.util.Log.d("JS", "ensureGL2DSurface: context=" + context);
|
|
@@ -23,4 +23,8 @@
|
|
|
23
23
|
@property (nonatomic, assign) BOOL inForeground;
|
|
24
24
|
@property (nonatomic, assign) BOOL isLoop;
|
|
25
25
|
@property (nonatomic, assign) BOOL loadedDataFired;
|
|
26
|
+
@property (nonatomic, assign) BOOL firstFrameReady;
|
|
27
|
+
@property (nonatomic, assign) BOOL waitingForFirstFrame;
|
|
28
|
+
/// Bumped per first-frame wait so a stale wait stops.
|
|
29
|
+
@property (nonatomic, assign) NSUInteger frameWaitGeneration;
|
|
26
30
|
@end
|
|
@@ -30,6 +30,8 @@
|
|
|
30
30
|
@property (nonatomic, readonly) BOOL isInForeground;
|
|
31
31
|
@property (nonatomic, readonly) NSCPlayerState state;
|
|
32
32
|
@property (nonatomic, readonly) NSCPlayerReadyState readyState;
|
|
33
|
+
/// A decoded frame exists for the current source.
|
|
34
|
+
@property (nonatomic, readonly) BOOL firstFrameReady;
|
|
33
35
|
@property (nonatomic, readonly) CGSize videoSize;
|
|
34
36
|
@property (nonatomic, strong, readonly) AVPlayerItemVideoOutput *assetOutput;
|
|
35
37
|
@property (nonatomic, weak) id<NSCVideoHelperListener> listener;
|
|
@@ -81,6 +81,7 @@ static NSArray<AVAssetTrack *> *NSCTracksWithMediaType(AVAsset *asset, AVMediaTy
|
|
|
81
81
|
_currentSrc = src;
|
|
82
82
|
_readyState = NSCPlayerReadyStateHaveNothing;
|
|
83
83
|
self.loadedDataFired = NO;
|
|
84
|
+
self.firstFrameReady = NO;
|
|
84
85
|
|
|
85
86
|
if (!src) return;
|
|
86
87
|
|
|
@@ -191,6 +192,8 @@ static NSArray<AVAssetTrack *> *NSCTracksWithMediaType(AVAsset *asset, AVMediaTy
|
|
|
191
192
|
self->_videoSize = [NSCTracksWithMediaType(self.asset, AVMediaTypeVideo) firstObject].naturalSize;
|
|
192
193
|
[self.player.currentItem addOutput:self.assetOutput];
|
|
193
194
|
self.readyState = NSCPlayerReadyStateHaveCurrentData;
|
|
195
|
+
// The output is only attached now, so check again.
|
|
196
|
+
[self _scheduleFirstFrameCheck];
|
|
194
197
|
}
|
|
195
198
|
}
|
|
196
199
|
}
|
|
@@ -202,25 +205,60 @@ static NSArray<AVAssetTrack *> *NSCTracksWithMediaType(AVAsset *asset, AVMediaTy
|
|
|
202
205
|
- (void)_checkForFirstFrameWithAttempts:(int)attemptsRemaining delayMs:(int)delayMs {
|
|
203
206
|
if (self.loadedDataFired || attemptsRemaining <= 0) return;
|
|
204
207
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
buffer = [self.assetOutput copyPixelBufferForItemTime:current itemTimeForDisplay:NULL];
|
|
208
|
+
if ([self _hasDecodedFrame]) {
|
|
209
|
+
[self _fireLoadedData];
|
|
210
|
+
return;
|
|
209
211
|
}
|
|
210
212
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
213
|
+
__weak typeof(self) weakSelf = self;
|
|
214
|
+
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayMs * NSEC_PER_MSEC)), dispatch_get_main_queue(), ^{
|
|
215
|
+
[weakSelf _checkForFirstFrameWithAttempts:attemptsRemaining - 1 delayMs:delayMs];
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/// Non-consuming: `copyPixelBufferForItemTime:` would steal the first frame from whoever draws it.
|
|
220
|
+
- (BOOL)_hasDecodedFrame {
|
|
221
|
+
if (self.firstFrameReady) return YES;
|
|
222
|
+
if (!self.assetOutput) return NO;
|
|
223
|
+
if ([self.assetOutput hasNewPixelBufferForItemTime:self.player.currentTime]) {
|
|
224
|
+
self.firstFrameReady = YES;
|
|
225
|
+
}
|
|
226
|
+
return self.firstFrameReady;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
- (void)_fireLoadedData {
|
|
230
|
+
if (self.loadedDataFired) return;
|
|
231
|
+
self.loadedDataFired = YES;
|
|
232
|
+
dispatch_async(dispatch_get_main_queue(), ^{
|
|
233
|
+
if ([self.listener respondsToSelector:@selector(onLoadedData)]) {
|
|
234
|
+
[self.listener onLoadedData];
|
|
235
|
+
}
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/// Reports `playing` (resolving `play()`) once a frame is decoded, as on the web. Audio-only
|
|
240
|
+
/// reports at once; after ~10 s it reports anyway so `play()` cannot hang.
|
|
241
|
+
- (void)_notifyPlayingWhenFrameReady:(int)attemptsRemaining generation:(NSUInteger)generation {
|
|
242
|
+
if (generation != self.frameWaitGeneration) return;
|
|
243
|
+
if (self.state != NSCPlayerStatePlaying) {
|
|
244
|
+
self.waitingForFirstFrame = NO;
|
|
219
245
|
return;
|
|
220
246
|
}
|
|
247
|
+
self.waitingForFirstFrame = YES;
|
|
221
248
|
|
|
222
|
-
|
|
223
|
-
|
|
249
|
+
BOOL audioOnly = self.currentItem != nil && CGSizeEqualToSize(self.videoSize, CGSizeZero);
|
|
250
|
+
if ([self _hasDecodedFrame] || audioOnly || attemptsRemaining <= 0) {
|
|
251
|
+
self.waitingForFirstFrame = NO;
|
|
252
|
+
if (self.firstFrameReady) {
|
|
253
|
+
[self _fireLoadedData];
|
|
254
|
+
}
|
|
255
|
+
[self.listener onStateChange:self.state];
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
__weak typeof(self) weakSelf = self;
|
|
260
|
+
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(16 * NSEC_PER_MSEC)), dispatch_get_main_queue(), ^{
|
|
261
|
+
[weakSelf _notifyPlayingWhenFrameReady:attemptsRemaining - 1 generation:generation];
|
|
224
262
|
});
|
|
225
263
|
}
|
|
226
264
|
|
|
@@ -295,11 +333,27 @@ static NSArray<AVAssetTrack *> *NSCTracksWithMediaType(AVAsset *asset, AVMediaTy
|
|
|
295
333
|
}
|
|
296
334
|
|
|
297
335
|
- (void)play {
|
|
298
|
-
if (self.state == NSCPlayerStatePlaying)
|
|
336
|
+
if (self.state == NSCPlayerStatePlaying) {
|
|
337
|
+
// Autoplay started but has not reported playing yet.
|
|
338
|
+
if (!self.firstFrameReady && !self.waitingForFirstFrame) {
|
|
339
|
+
[self _startWaitingForFrame];
|
|
340
|
+
}
|
|
341
|
+
return;
|
|
342
|
+
}
|
|
299
343
|
[self addTimeObserver];
|
|
300
344
|
[self.player play];
|
|
301
345
|
self.state = NSCPlayerStatePlaying;
|
|
302
|
-
[self
|
|
346
|
+
[self _startWaitingForFrame];
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
/// Deferred a turn, like the web's queued `play()` resolution, so an immediate `pause()` wins.
|
|
350
|
+
- (void)_startWaitingForFrame {
|
|
351
|
+
NSUInteger generation = ++self.frameWaitGeneration;
|
|
352
|
+
self.waitingForFirstFrame = YES;
|
|
353
|
+
__weak typeof(self) weakSelf = self;
|
|
354
|
+
dispatch_async(dispatch_get_main_queue(), ^{
|
|
355
|
+
[weakSelf _notifyPlayingWhenFrameReady:600 generation:generation];
|
|
356
|
+
});
|
|
303
357
|
}
|
|
304
358
|
|
|
305
359
|
- (void)addTimeObserver {
|
|
Binary file
|