@shopify/react-native-skia 1.3.2 → 1.3.4
Sign up to get free protection for your applications and to get access to all the features.
- package/android/cpp/jni/JniPlatformContext.cpp +4 -3
- package/android/cpp/rnskia-android/RNSkAndroidVideo.cpp +59 -1
- package/android/cpp/rnskia-android/RNSkAndroidVideo.h +4 -0
- package/android/src/main/java/com/shopify/reactnative/skia/RNSkVideo.java +80 -7
- package/cpp/api/JsiVideo.h +32 -7
- package/cpp/rnskia/RNSkVideo.h +4 -0
- package/ios/RNSkia-iOS/RNSkiOSVideo.h +13 -4
- package/ios/RNSkia-iOS/RNSkiOSVideo.mm +65 -67
- package/lib/commonjs/dom/nodes/datatypes/Fitting.js +42 -30
- package/lib/commonjs/dom/nodes/datatypes/Fitting.js.map +1 -1
- package/lib/commonjs/external/reanimated/useVideo.d.ts +16 -4
- package/lib/commonjs/external/reanimated/useVideo.js +92 -17
- package/lib/commonjs/external/reanimated/useVideo.js.map +1 -1
- package/lib/commonjs/external/reanimated/useVideoLoading.d.ts +4 -0
- package/lib/commonjs/external/reanimated/useVideoLoading.js +27 -0
- package/lib/commonjs/external/reanimated/useVideoLoading.js.map +1 -0
- package/lib/commonjs/external/reanimated/useVideoLoading.web.d.ts +4 -0
- package/lib/commonjs/external/reanimated/useVideoLoading.web.js +20 -0
- package/lib/commonjs/external/reanimated/useVideoLoading.web.js.map +1 -0
- package/lib/commonjs/renderer/components/shapes/FitBox.d.ts +2 -10
- package/lib/commonjs/renderer/components/shapes/FitBox.js +32 -3
- package/lib/commonjs/renderer/components/shapes/FitBox.js.map +1 -1
- package/lib/commonjs/skia/core/Matrix.js +5 -1
- package/lib/commonjs/skia/core/Matrix.js.map +1 -1
- package/lib/commonjs/skia/types/Matrix.js +2 -0
- package/lib/commonjs/skia/types/Matrix.js.map +1 -1
- package/lib/commonjs/skia/types/NativeBuffer/NativeBufferFactory.d.ts +3 -1
- package/lib/commonjs/skia/types/NativeBuffer/NativeBufferFactory.js +4 -2
- package/lib/commonjs/skia/types/NativeBuffer/NativeBufferFactory.js.map +1 -1
- package/lib/commonjs/skia/types/Skia.d.ts +1 -1
- package/lib/commonjs/skia/types/Skia.js.map +1 -1
- package/lib/commonjs/skia/types/Video/Video.d.ts +9 -1
- package/lib/commonjs/skia/types/Video/Video.js.map +1 -1
- package/lib/commonjs/skia/web/CanvasKitWebGLBufferImpl.d.ts +9 -0
- package/lib/commonjs/skia/web/CanvasKitWebGLBufferImpl.js +30 -0
- package/lib/commonjs/skia/web/CanvasKitWebGLBufferImpl.js.map +1 -0
- package/lib/commonjs/skia/web/JsiSkImageFactory.js +8 -2
- package/lib/commonjs/skia/web/JsiSkImageFactory.js.map +1 -1
- package/lib/commonjs/skia/web/JsiSkia.js +2 -3
- package/lib/commonjs/skia/web/JsiSkia.js.map +1 -1
- package/lib/commonjs/skia/web/JsiVideo.d.ts +24 -0
- package/lib/commonjs/skia/web/JsiVideo.js +83 -0
- package/lib/commonjs/skia/web/JsiVideo.js.map +1 -0
- package/lib/commonjs/views/SkiaDomView.js +2 -0
- package/lib/commonjs/views/SkiaDomView.js.map +1 -1
- package/lib/commonjs/views/SkiaDomView.web.js +2 -0
- package/lib/commonjs/views/SkiaDomView.web.js.map +1 -1
- package/lib/commonjs/views/SkiaJSDomView.js +2 -0
- package/lib/commonjs/views/SkiaJSDomView.js.map +1 -1
- package/lib/module/dom/nodes/datatypes/Fitting.js +41 -29
- package/lib/module/dom/nodes/datatypes/Fitting.js.map +1 -1
- package/lib/module/external/reanimated/useVideo.d.ts +16 -4
- package/lib/module/external/reanimated/useVideo.js +92 -17
- package/lib/module/external/reanimated/useVideo.js.map +1 -1
- package/lib/module/external/reanimated/useVideoLoading.d.ts +4 -0
- package/lib/module/external/reanimated/useVideoLoading.js +20 -0
- package/lib/module/external/reanimated/useVideoLoading.js.map +1 -0
- package/lib/module/external/reanimated/useVideoLoading.web.d.ts +4 -0
- package/lib/module/external/reanimated/useVideoLoading.web.js +13 -0
- package/lib/module/external/reanimated/useVideoLoading.web.js.map +1 -0
- package/lib/module/renderer/components/shapes/FitBox.d.ts +2 -10
- package/lib/module/renderer/components/shapes/FitBox.js +32 -3
- package/lib/module/renderer/components/shapes/FitBox.js.map +1 -1
- package/lib/module/skia/core/Matrix.js +5 -1
- package/lib/module/skia/core/Matrix.js.map +1 -1
- package/lib/module/skia/types/Matrix.js +2 -0
- package/lib/module/skia/types/Matrix.js.map +1 -1
- package/lib/module/skia/types/NativeBuffer/NativeBufferFactory.d.ts +3 -1
- package/lib/module/skia/types/NativeBuffer/NativeBufferFactory.js +2 -1
- package/lib/module/skia/types/NativeBuffer/NativeBufferFactory.js.map +1 -1
- package/lib/module/skia/types/Skia.d.ts +1 -1
- package/lib/module/skia/types/Skia.js.map +1 -1
- package/lib/module/skia/types/Video/Video.d.ts +9 -1
- package/lib/module/skia/types/Video/Video.js.map +1 -1
- package/lib/module/skia/web/CanvasKitWebGLBufferImpl.d.ts +9 -0
- package/lib/module/skia/web/CanvasKitWebGLBufferImpl.js +23 -0
- package/lib/module/skia/web/CanvasKitWebGLBufferImpl.js.map +1 -0
- package/lib/module/skia/web/JsiSkImageFactory.js +9 -3
- package/lib/module/skia/web/JsiSkImageFactory.js.map +1 -1
- package/lib/module/skia/web/JsiSkia.js +2 -3
- package/lib/module/skia/web/JsiSkia.js.map +1 -1
- package/lib/module/skia/web/JsiVideo.d.ts +24 -0
- package/lib/module/skia/web/JsiVideo.js +75 -0
- package/lib/module/skia/web/JsiVideo.js.map +1 -0
- package/lib/module/views/SkiaDomView.js +2 -0
- package/lib/module/views/SkiaDomView.js.map +1 -1
- package/lib/module/views/SkiaDomView.web.js +2 -0
- package/lib/module/views/SkiaDomView.web.js.map +1 -1
- package/lib/module/views/SkiaJSDomView.js +2 -0
- package/lib/module/views/SkiaJSDomView.js.map +1 -1
- package/lib/typescript/src/external/reanimated/useVideo.d.ts +16 -4
- package/lib/typescript/src/external/reanimated/useVideoLoading.d.ts +4 -0
- package/lib/typescript/src/external/reanimated/useVideoLoading.web.d.ts +4 -0
- package/lib/typescript/src/renderer/components/shapes/FitBox.d.ts +2 -10
- package/lib/typescript/src/skia/types/NativeBuffer/NativeBufferFactory.d.ts +3 -1
- package/lib/typescript/src/skia/types/Skia.d.ts +1 -1
- package/lib/typescript/src/skia/types/Video/Video.d.ts +9 -1
- package/lib/typescript/src/skia/web/CanvasKitWebGLBufferImpl.d.ts +9 -0
- package/lib/typescript/src/skia/web/JsiVideo.d.ts +24 -0
- package/package.json +1 -1
- package/src/dom/nodes/datatypes/Fitting.ts +28 -21
- package/src/external/reanimated/useVideo.ts +108 -31
- package/src/external/reanimated/useVideoLoading.ts +28 -0
- package/src/external/reanimated/useVideoLoading.web.ts +17 -0
- package/src/renderer/components/shapes/FitBox.tsx +38 -4
- package/src/skia/core/Matrix.ts +4 -2
- package/src/skia/types/Matrix.ts +1 -0
- package/src/skia/types/NativeBuffer/NativeBufferFactory.ts +10 -2
- package/src/skia/types/Skia.ts +1 -1
- package/src/skia/types/Video/Video.ts +7 -1
- package/src/skia/web/CanvasKitWebGLBufferImpl.ts +22 -0
- package/src/skia/web/JsiSkImageFactory.ts +16 -3
- package/src/skia/web/JsiSkia.ts +2 -3
- package/src/skia/web/JsiVideo.ts +96 -0
- package/src/views/SkiaDomView.tsx +4 -0
- package/src/views/SkiaDomView.web.tsx +4 -0
- package/src/views/SkiaJSDomView.tsx +4 -0
- package/lib/commonjs/external/reanimated/video.d.ts +0 -16
- package/lib/commonjs/external/reanimated/video.js +0 -54
- package/lib/commonjs/external/reanimated/video.js.map +0 -1
- package/lib/module/external/reanimated/video.d.ts +0 -16
- package/lib/module/external/reanimated/video.js +0 -46
- package/lib/module/external/reanimated/video.js.map +0 -1
- package/lib/typescript/src/external/reanimated/video.d.ts +0 -16
- package/src/external/reanimated/video.ts +0 -82
@@ -1,22 +1,52 @@
|
|
1
|
-
import {
|
1
|
+
import type { SharedValue, FrameInfo } from "react-native-reanimated";
|
2
2
|
import { useEffect, useMemo } from "react";
|
3
3
|
|
4
|
-
import { Skia } from "../../skia/Skia";
|
5
4
|
import type { SkImage, Video } from "../../skia/types";
|
5
|
+
import { Platform } from "../../Platform";
|
6
6
|
|
7
7
|
import Rea from "./ReanimatedProxy";
|
8
|
-
import {
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
8
|
+
import { useVideoLoading } from "./useVideoLoading";
|
9
|
+
|
10
|
+
type Animated<T> = SharedValue<T> | T;
|
11
|
+
|
12
|
+
interface PlaybackOptions {
|
13
|
+
looping: Animated<boolean>;
|
14
|
+
paused: Animated<boolean>;
|
15
|
+
seek: Animated<number | null>;
|
16
|
+
volume: Animated<number>;
|
17
|
+
}
|
18
|
+
|
19
|
+
const copyFrameOnAndroid = (currentFrame: SharedValue<SkImage | null>) => {
|
20
|
+
"worklet";
|
21
|
+
// on android we need to copy the texture before it's invalidated
|
22
|
+
if (Platform.OS === "android") {
|
23
|
+
const tex = currentFrame.value;
|
24
|
+
if (tex) {
|
25
|
+
currentFrame.value = tex.makeNonTextureImage();
|
26
|
+
tex.dispose();
|
27
|
+
}
|
28
|
+
}
|
29
|
+
};
|
30
|
+
|
31
|
+
const setFrame = (video: Video, currentFrame: SharedValue<SkImage | null>) => {
|
32
|
+
"worklet";
|
33
|
+
const img = video.nextImage();
|
34
|
+
if (img) {
|
35
|
+
if (currentFrame.value) {
|
36
|
+
currentFrame.value.dispose();
|
37
|
+
}
|
38
|
+
currentFrame.value = img;
|
39
|
+
} else {
|
40
|
+
copyFrameOnAndroid(currentFrame);
|
41
|
+
}
|
42
|
+
};
|
13
43
|
|
14
44
|
const defaultOptions = {
|
15
|
-
playbackSpeed: 1,
|
16
45
|
looping: true,
|
17
46
|
paused: false,
|
18
47
|
seek: null,
|
19
48
|
currentTime: 0,
|
49
|
+
volume: 0,
|
20
50
|
};
|
21
51
|
|
22
52
|
const useOption = <T>(value: Animated<T>) => {
|
@@ -37,38 +67,78 @@ export const useVideo = (
|
|
37
67
|
source: string | null,
|
38
68
|
userOptions?: Partial<PlaybackOptions>
|
39
69
|
) => {
|
40
|
-
const video =
|
70
|
+
const video = useVideoLoading(source);
|
41
71
|
const isPaused = useOption(userOptions?.paused ?? defaultOptions.paused);
|
42
72
|
const looping = useOption(userOptions?.looping ?? defaultOptions.looping);
|
43
73
|
const seek = useOption(userOptions?.seek ?? defaultOptions.seek);
|
44
|
-
const
|
45
|
-
userOptions?.playbackSpeed ?? defaultOptions.playbackSpeed
|
46
|
-
);
|
74
|
+
const volume = useOption(userOptions?.volume ?? defaultOptions.volume);
|
47
75
|
const currentFrame = Rea.useSharedValue<null | SkImage>(null);
|
48
76
|
const currentTime = Rea.useSharedValue(0);
|
49
77
|
const lastTimestamp = Rea.useSharedValue(-1);
|
50
78
|
const duration = useMemo(() => video?.duration() ?? 0, [video]);
|
51
|
-
const framerate = useMemo(
|
52
|
-
|
53
|
-
() => video?.getRotationInDegrees() ?? 0,
|
79
|
+
const framerate = useMemo(
|
80
|
+
() => (Platform.OS === "web" ? -1 : video?.framerate() ?? 0),
|
54
81
|
[video]
|
55
82
|
);
|
83
|
+
const size = useMemo(() => video?.size() ?? { width: 0, height: 0 }, [video]);
|
84
|
+
const rotation = useMemo(() => video?.rotation() ?? 0, [video]);
|
85
|
+
const frameDuration = 1000 / framerate;
|
86
|
+
const currentFrameDuration = Math.floor(frameDuration);
|
87
|
+
Rea.useAnimatedReaction(
|
88
|
+
() => isPaused.value,
|
89
|
+
(paused) => {
|
90
|
+
if (paused) {
|
91
|
+
video?.pause();
|
92
|
+
} else {
|
93
|
+
lastTimestamp.value = -1;
|
94
|
+
video?.play();
|
95
|
+
}
|
96
|
+
}
|
97
|
+
);
|
98
|
+
Rea.useAnimatedReaction(
|
99
|
+
() => seek.value,
|
100
|
+
(value) => {
|
101
|
+
if (value !== null) {
|
102
|
+
copyFrameOnAndroid(currentFrame);
|
103
|
+
video?.seek(value);
|
104
|
+
currentTime.value = value;
|
105
|
+
seek.value = null;
|
106
|
+
}
|
107
|
+
}
|
108
|
+
);
|
109
|
+
Rea.useAnimatedReaction(
|
110
|
+
() => volume.value,
|
111
|
+
(value) => {
|
112
|
+
video?.setVolume(value);
|
113
|
+
}
|
114
|
+
);
|
56
115
|
Rea.useFrameCallback((frameInfo: FrameInfo) => {
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
)
|
116
|
+
"worklet";
|
117
|
+
if (!video) {
|
118
|
+
return;
|
119
|
+
}
|
120
|
+
if (isPaused.value) {
|
121
|
+
return;
|
122
|
+
}
|
123
|
+
const currentTimestamp = frameInfo.timestamp;
|
124
|
+
if (lastTimestamp.value === -1) {
|
125
|
+
lastTimestamp.value = currentTimestamp;
|
126
|
+
}
|
127
|
+
const delta = currentTimestamp - lastTimestamp.value;
|
128
|
+
|
129
|
+
const isOver = currentTime.value + delta > duration;
|
130
|
+
if (isOver && looping.value) {
|
131
|
+
seek.value = 0;
|
132
|
+
currentTime.value = seek.value;
|
133
|
+
lastTimestamp.value = currentTimestamp;
|
134
|
+
}
|
135
|
+
// On Web the framerate is uknown.
|
136
|
+
// This could be optimized by using requestVideoFrameCallback (Chrome only)
|
137
|
+
if ((delta >= currentFrameDuration && !isOver) || Platform.OS === "web") {
|
138
|
+
setFrame(video, currentFrame);
|
139
|
+
currentTime.value += delta;
|
140
|
+
lastTimestamp.value = currentTimestamp;
|
141
|
+
}
|
72
142
|
});
|
73
143
|
|
74
144
|
useEffect(() => {
|
@@ -78,5 +148,12 @@ export const useVideo = (
|
|
78
148
|
};
|
79
149
|
}, [video]);
|
80
150
|
|
81
|
-
return {
|
151
|
+
return {
|
152
|
+
currentFrame,
|
153
|
+
currentTime,
|
154
|
+
duration,
|
155
|
+
framerate,
|
156
|
+
rotation,
|
157
|
+
size,
|
158
|
+
};
|
82
159
|
};
|
@@ -0,0 +1,28 @@
|
|
1
|
+
import { useEffect, useState } from "react";
|
2
|
+
import {
|
3
|
+
createWorkletRuntime,
|
4
|
+
runOnJS,
|
5
|
+
runOnRuntime,
|
6
|
+
} from "react-native-reanimated";
|
7
|
+
|
8
|
+
import type { Video } from "../../skia/types";
|
9
|
+
import { Skia } from "../../skia";
|
10
|
+
|
11
|
+
const runtime = createWorkletRuntime("video-metadata-runtime");
|
12
|
+
|
13
|
+
type VideoSource = string | null;
|
14
|
+
|
15
|
+
export const useVideoLoading = (source: VideoSource) => {
|
16
|
+
const [video, setVideo] = useState<Video | null>(null);
|
17
|
+
const cb = (src: string) => {
|
18
|
+
"worklet";
|
19
|
+
const vid = Skia.Video(src) as Video;
|
20
|
+
runOnJS(setVideo)(vid);
|
21
|
+
};
|
22
|
+
useEffect(() => {
|
23
|
+
if (source) {
|
24
|
+
runOnRuntime(runtime, cb)(source);
|
25
|
+
}
|
26
|
+
}, [source]);
|
27
|
+
return video;
|
28
|
+
};
|
@@ -0,0 +1,17 @@
|
|
1
|
+
import { useEffect, useState } from "react";
|
2
|
+
|
3
|
+
import type { Video } from "../../skia/types";
|
4
|
+
import { Skia } from "../../skia";
|
5
|
+
|
6
|
+
type VideoSource = string | null;
|
7
|
+
|
8
|
+
export const useVideoLoading = (source: VideoSource) => {
|
9
|
+
const [video, setVideo] = useState<Video | null>(null);
|
10
|
+
useEffect(() => {
|
11
|
+
if (source) {
|
12
|
+
const vid = Skia.Video(source) as Promise<Video>;
|
13
|
+
vid.then((v) => setVideo(v));
|
14
|
+
}
|
15
|
+
}, [source]);
|
16
|
+
return video;
|
17
|
+
};
|
@@ -3,7 +3,7 @@ import React, { useMemo } from "react";
|
|
3
3
|
|
4
4
|
import type { Fit } from "../../../dom/nodes";
|
5
5
|
import { fitRects, rect2rect } from "../../../dom/nodes";
|
6
|
-
import type { SkRect } from "../../../skia/types";
|
6
|
+
import type { SkRect, Transforms3d } from "../../../skia/types";
|
7
7
|
import { Group } from "../Group";
|
8
8
|
|
9
9
|
interface FitProps {
|
@@ -13,9 +13,43 @@ interface FitProps {
|
|
13
13
|
children: ReactNode | ReactNode[];
|
14
14
|
}
|
15
15
|
|
16
|
-
export const fitbox = (
|
17
|
-
|
18
|
-
|
16
|
+
export const fitbox = (
|
17
|
+
fit: Fit,
|
18
|
+
src: SkRect,
|
19
|
+
dst: SkRect,
|
20
|
+
rotation: 0 | 90 | 180 | 270 = 0
|
21
|
+
) => {
|
22
|
+
"worklet";
|
23
|
+
const rects = fitRects(
|
24
|
+
fit,
|
25
|
+
rotation === 90 || rotation === 270
|
26
|
+
? { x: 0, y: 0, width: src.height, height: src.width }
|
27
|
+
: src,
|
28
|
+
dst
|
29
|
+
);
|
30
|
+
const result = rect2rect(rects.src, rects.dst);
|
31
|
+
if (rotation === 90) {
|
32
|
+
return [
|
33
|
+
...result,
|
34
|
+
{ translate: [src.height, 0] },
|
35
|
+
{ rotate: Math.PI / 2 },
|
36
|
+
] as Transforms3d;
|
37
|
+
}
|
38
|
+
if (rotation === 180) {
|
39
|
+
return [
|
40
|
+
...result,
|
41
|
+
{ translate: [src.width, src.height] },
|
42
|
+
{ rotate: Math.PI },
|
43
|
+
] as Transforms3d;
|
44
|
+
}
|
45
|
+
if (rotation === 270) {
|
46
|
+
return [
|
47
|
+
...result,
|
48
|
+
{ translate: [0, src.width] },
|
49
|
+
{ rotate: -Math.PI / 2 },
|
50
|
+
] as Transforms3d;
|
51
|
+
}
|
52
|
+
return result;
|
19
53
|
};
|
20
54
|
|
21
55
|
export const FitBox = ({ fit = "contain", src, dst, children }: FitProps) => {
|
package/src/skia/core/Matrix.ts
CHANGED
@@ -2,5 +2,7 @@ import { Skia } from "../Skia";
|
|
2
2
|
import type { Transforms3d } from "../types";
|
3
3
|
import { processTransform } from "../types";
|
4
4
|
|
5
|
-
export const processTransform2d = (transforms: Transforms3d) =>
|
6
|
-
|
5
|
+
export const processTransform2d = (transforms: Transforms3d) => {
|
6
|
+
"worklet";
|
7
|
+
return processTransform(Skia.Matrix(), transforms);
|
8
|
+
};
|
package/src/skia/types/Matrix.ts
CHANGED
@@ -1,7 +1,14 @@
|
|
1
1
|
import type { SkImage } from "../Image";
|
2
2
|
|
3
|
+
export abstract class CanvasKitWebGLBuffer {}
|
4
|
+
|
3
5
|
export type NativeBuffer<
|
4
|
-
T extends
|
6
|
+
T extends
|
7
|
+
| bigint
|
8
|
+
| ArrayBuffer
|
9
|
+
| CanvasImageSource
|
10
|
+
| CanvasKitWebGLBuffer
|
11
|
+
| unknown = unknown
|
5
12
|
> = T;
|
6
13
|
|
7
14
|
export type NativeBufferAddr = NativeBuffer<bigint>;
|
@@ -20,7 +27,8 @@ export const isNativeBufferWeb = (
|
|
20
27
|
buffer instanceof OffscreenCanvas ||
|
21
28
|
buffer instanceof VideoFrame ||
|
22
29
|
buffer instanceof HTMLImageElement ||
|
23
|
-
buffer instanceof SVGImageElement
|
30
|
+
buffer instanceof SVGImageElement ||
|
31
|
+
buffer instanceof CanvasKitWebGLBuffer;
|
24
32
|
|
25
33
|
export const isNativeBufferNode = (
|
26
34
|
buffer: NativeBuffer
|
package/src/skia/types/Skia.ts
CHANGED
@@ -1,10 +1,16 @@
|
|
1
1
|
import type { SkImage } from "../Image";
|
2
2
|
import type { SkJSIInstance } from "../JsiInstance";
|
3
3
|
|
4
|
+
export type VideoRotation = 0 | 90 | 180 | 270;
|
5
|
+
|
4
6
|
export interface Video extends SkJSIInstance<"Video"> {
|
5
7
|
duration(): number;
|
6
8
|
framerate(): number;
|
7
9
|
nextImage(): SkImage | null;
|
8
10
|
seek(time: number): void;
|
9
|
-
|
11
|
+
rotation(): VideoRotation;
|
12
|
+
size(): { width: number; height: number };
|
13
|
+
pause(): void;
|
14
|
+
play(): void;
|
15
|
+
setVolume(volume: number): void;
|
10
16
|
}
|
@@ -0,0 +1,22 @@
|
|
1
|
+
import type { Surface, TextureSource, Image } from "canvaskit-wasm";
|
2
|
+
|
3
|
+
import { CanvasKitWebGLBuffer } from "../types";
|
4
|
+
|
5
|
+
export class CanvasKitWebGLBufferImpl extends CanvasKitWebGLBuffer {
|
6
|
+
public image: Image | null = null;
|
7
|
+
|
8
|
+
constructor(public surface: Surface, private source: TextureSource) {
|
9
|
+
super();
|
10
|
+
}
|
11
|
+
|
12
|
+
toImage() {
|
13
|
+
if (this.image === null) {
|
14
|
+
this.image = this.surface.makeImageFromTextureSource(this.source);
|
15
|
+
}
|
16
|
+
if (this.image === null) {
|
17
|
+
throw new Error("Failed to create image from texture source");
|
18
|
+
}
|
19
|
+
this.surface.updateTextureFromSource(this.image, this.source);
|
20
|
+
return this.image;
|
21
|
+
}
|
22
|
+
}
|
@@ -1,6 +1,6 @@
|
|
1
1
|
import type { CanvasKit, Image } from "canvaskit-wasm";
|
2
2
|
|
3
|
-
import { isNativeBufferWeb } from "../types";
|
3
|
+
import { CanvasKitWebGLBuffer, isNativeBufferWeb } from "../types";
|
4
4
|
import type {
|
5
5
|
SkData,
|
6
6
|
ImageInfo,
|
@@ -13,6 +13,7 @@ import { Host, getEnum } from "./Host";
|
|
13
13
|
import { JsiSkImage } from "./JsiSkImage";
|
14
14
|
import { JsiSkData } from "./JsiSkData";
|
15
15
|
import type { JsiSkSurface } from "./JsiSkSurface";
|
16
|
+
import type { CanvasKitWebGLBufferImpl } from "./CanvasKitWebGLBufferImpl";
|
16
17
|
|
17
18
|
export class JsiSkImageFactory extends Host implements ImageFactory {
|
18
19
|
constructor(CanvasKit: CanvasKit) {
|
@@ -35,8 +36,20 @@ export class JsiSkImageFactory extends Host implements ImageFactory {
|
|
35
36
|
throw new Error("Invalid NativeBuffer");
|
36
37
|
}
|
37
38
|
if (!surface) {
|
38
|
-
|
39
|
-
|
39
|
+
let img: Image;
|
40
|
+
if (
|
41
|
+
buffer instanceof HTMLImageElement ||
|
42
|
+
buffer instanceof HTMLVideoElement ||
|
43
|
+
buffer instanceof ImageBitmap
|
44
|
+
) {
|
45
|
+
img = this.CanvasKit.MakeLazyImageFromTextureSource(buffer);
|
46
|
+
} else if (buffer instanceof CanvasKitWebGLBuffer) {
|
47
|
+
img = (
|
48
|
+
buffer as CanvasKitWebGLBuffer as CanvasKitWebGLBufferImpl
|
49
|
+
).toImage();
|
50
|
+
} else {
|
51
|
+
img = this.CanvasKit.MakeImageFromCanvasImageSource(buffer);
|
52
|
+
}
|
40
53
|
return new JsiSkImage(this.CanvasKit, img);
|
41
54
|
} else if (!image) {
|
42
55
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
package/src/skia/web/JsiSkia.ts
CHANGED
@@ -42,6 +42,7 @@ import { JsiSkFontMgrFactory } from "./JsiSkFontMgrFactory";
|
|
42
42
|
import { JsiSkAnimatedImageFactory } from "./JsiSkAnimatedImageFactory";
|
43
43
|
import { JsiSkParagraphBuilderFactory } from "./JsiSkParagraphBuilderFactory";
|
44
44
|
import { JsiSkNativeBufferFactory } from "./JsiSkNativeBufferFactory";
|
45
|
+
import { createVideo } from "./JsiVideo";
|
45
46
|
|
46
47
|
export const JsiSkApi = (CanvasKit: CanvasKit): Skia => ({
|
47
48
|
Point: (x: number, y: number) =>
|
@@ -127,7 +128,5 @@ export const JsiSkApi = (CanvasKit: CanvasKit): Skia => ({
|
|
127
128
|
FontMgr: new JsiSkFontMgrFactory(CanvasKit),
|
128
129
|
ParagraphBuilder: new JsiSkParagraphBuilderFactory(CanvasKit),
|
129
130
|
NativeBuffer: new JsiSkNativeBufferFactory(CanvasKit),
|
130
|
-
Video: (
|
131
|
-
throw new Error("Not implemented on React Native Web");
|
132
|
-
},
|
131
|
+
Video: createVideo.bind(null, CanvasKit),
|
133
132
|
});
|
@@ -0,0 +1,96 @@
|
|
1
|
+
import type { CanvasKit, Surface } from "canvaskit-wasm";
|
2
|
+
|
3
|
+
import type { CanvasKitWebGLBuffer, Video, ImageFactory } from "../types";
|
4
|
+
|
5
|
+
import { CanvasKitWebGLBufferImpl } from "./CanvasKitWebGLBufferImpl";
|
6
|
+
import { JsiSkImageFactory } from "./JsiSkImageFactory";
|
7
|
+
|
8
|
+
export const createVideo = async (
|
9
|
+
CanvasKit: CanvasKit,
|
10
|
+
url: string
|
11
|
+
): Promise<Video> => {
|
12
|
+
const video = document.createElement("video");
|
13
|
+
return new Promise((resolve, reject) => {
|
14
|
+
video.src = url;
|
15
|
+
video.style.display = "none";
|
16
|
+
video.crossOrigin = "anonymous";
|
17
|
+
video.volume = 0;
|
18
|
+
video.addEventListener("loadedmetadata", () => {
|
19
|
+
document.body.appendChild(video);
|
20
|
+
resolve(new JsiVideo(new JsiSkImageFactory(CanvasKit), video));
|
21
|
+
});
|
22
|
+
video.addEventListener("error", () => {
|
23
|
+
reject(new Error(`Failed to load video from URL: ${url}`));
|
24
|
+
});
|
25
|
+
});
|
26
|
+
};
|
27
|
+
|
28
|
+
export class JsiVideo implements Video {
|
29
|
+
__typename__ = "Video" as const;
|
30
|
+
|
31
|
+
private webglBuffer: CanvasKitWebGLBuffer | null = null;
|
32
|
+
|
33
|
+
constructor(
|
34
|
+
private ImageFactory: ImageFactory,
|
35
|
+
private videoElement: HTMLVideoElement
|
36
|
+
) {
|
37
|
+
document.body.appendChild(this.videoElement);
|
38
|
+
}
|
39
|
+
|
40
|
+
duration() {
|
41
|
+
return this.videoElement.duration * 1000;
|
42
|
+
}
|
43
|
+
|
44
|
+
framerate(): number {
|
45
|
+
throw new Error("Video.frame is not available on React Native Web");
|
46
|
+
}
|
47
|
+
|
48
|
+
setSurface(surface: Surface) {
|
49
|
+
// If we have the surface, we can use the WebGL buffer which is slightly faster
|
50
|
+
// This is because WebGL cannot be shared across contextes.
|
51
|
+
// This can be removed with WebGPU
|
52
|
+
this.webglBuffer = new CanvasKitWebGLBufferImpl(surface, this.videoElement);
|
53
|
+
}
|
54
|
+
|
55
|
+
nextImage() {
|
56
|
+
return this.ImageFactory.MakeImageFromNativeBuffer(
|
57
|
+
this.webglBuffer ? this.webglBuffer : this.videoElement
|
58
|
+
);
|
59
|
+
}
|
60
|
+
|
61
|
+
seek(time: number) {
|
62
|
+
if (isNaN(time)) {
|
63
|
+
throw new Error(`Invalid time: ${time}`);
|
64
|
+
}
|
65
|
+
this.videoElement.currentTime = time / 1000;
|
66
|
+
}
|
67
|
+
|
68
|
+
rotation() {
|
69
|
+
return 0 as const;
|
70
|
+
}
|
71
|
+
|
72
|
+
size() {
|
73
|
+
return {
|
74
|
+
width: this.videoElement.videoWidth,
|
75
|
+
height: this.videoElement.videoHeight,
|
76
|
+
};
|
77
|
+
}
|
78
|
+
|
79
|
+
pause() {
|
80
|
+
this.videoElement.pause();
|
81
|
+
}
|
82
|
+
|
83
|
+
play() {
|
84
|
+
this.videoElement.play();
|
85
|
+
}
|
86
|
+
|
87
|
+
setVolume(volume: number) {
|
88
|
+
this.videoElement.volume = volume;
|
89
|
+
}
|
90
|
+
|
91
|
+
dispose() {
|
92
|
+
if (this.videoElement.parentNode) {
|
93
|
+
this.videoElement.parentNode.removeChild(this.videoElement);
|
94
|
+
}
|
95
|
+
}
|
96
|
+
}
|
@@ -26,6 +26,10 @@ export class SkiaDomView extends React.Component<SkiaDomViewProps> {
|
|
26
26
|
}
|
27
27
|
if (onTouch) {
|
28
28
|
assertSkiaViewApi();
|
29
|
+
console.warn(
|
30
|
+
`The onTouch property is deprecated and will be removed in the next Skia release.
|
31
|
+
See: https://shopify.github.io/react-native-skia/docs/animations/gestures`
|
32
|
+
);
|
29
33
|
SkiaViewApi.setJsiProperty(this._nativeId, "onTouch", onTouch);
|
30
34
|
}
|
31
35
|
if (onSize) {
|
@@ -12,6 +12,10 @@ export class SkiaDomView extends SkiaBaseWebView<SkiaDomViewProps> {
|
|
12
12
|
|
13
13
|
protected renderInCanvas(canvas: SkCanvas, touches: TouchInfo[]): void {
|
14
14
|
if (this.props.onTouch) {
|
15
|
+
console.warn(
|
16
|
+
`The onTouch property is deprecated and will be removed in the next Skia release.
|
17
|
+
See: https://shopify.github.io/react-native-skia/docs/animations/gestures`
|
18
|
+
);
|
15
19
|
this.props.onTouch([touches]);
|
16
20
|
}
|
17
21
|
if (this.props.onSize) {
|
@@ -29,6 +29,10 @@ export class SkiaJSDomView extends React.Component<
|
|
29
29
|
}
|
30
30
|
if (onTouch) {
|
31
31
|
assertSkiaViewApi();
|
32
|
+
console.warn(
|
33
|
+
`The onTouch property is deprecated and will be removed in the next Skia release.
|
34
|
+
See: https://shopify.github.io/react-native-skia/docs/animations/gestures`
|
35
|
+
);
|
32
36
|
SkiaViewApi.setJsiProperty(this._nativeId, "onTouch", onTouch);
|
33
37
|
}
|
34
38
|
if (onSize) {
|
@@ -1,16 +0,0 @@
|
|
1
|
-
import type { SharedValue } from "react-native-reanimated";
|
2
|
-
import type { SkImage, Video } from "../../skia/types";
|
3
|
-
export type Animated<T> = SharedValue<T> | T;
|
4
|
-
export interface PlaybackOptions {
|
5
|
-
playbackSpeed: Animated<number>;
|
6
|
-
looping: Animated<boolean>;
|
7
|
-
paused: Animated<boolean>;
|
8
|
-
seek: Animated<number | null>;
|
9
|
-
}
|
10
|
-
type Materialized<T> = {
|
11
|
-
[K in keyof T]: T[K] extends Animated<infer U> ? U : T[K];
|
12
|
-
};
|
13
|
-
export type MaterializedPlaybackOptions = Materialized<Omit<PlaybackOptions, "seek">>;
|
14
|
-
export declare const setFrame: (video: Video, currentFrame: SharedValue<SkImage | null>) => void;
|
15
|
-
export declare const processVideoState: (video: Video | null, duration: number, framerate: number, currentTimestamp: number, options: Materialized<Omit<PlaybackOptions, "seek">>, currentTime: SharedValue<number>, currentFrame: SharedValue<SkImage | null>, lastTimestamp: SharedValue<number>, seek: SharedValue<number | null>) => void;
|
16
|
-
export {};
|
@@ -1,54 +0,0 @@
|
|
1
|
-
"use strict";
|
2
|
-
|
3
|
-
Object.defineProperty(exports, "__esModule", {
|
4
|
-
value: true
|
5
|
-
});
|
6
|
-
exports.setFrame = exports.processVideoState = void 0;
|
7
|
-
var _Platform = require("../../Platform");
|
8
|
-
const setFrame = (video, currentFrame) => {
|
9
|
-
"worklet";
|
10
|
-
|
11
|
-
const img = video.nextImage();
|
12
|
-
if (img) {
|
13
|
-
if (currentFrame.value) {
|
14
|
-
currentFrame.value.dispose();
|
15
|
-
}
|
16
|
-
if (_Platform.Platform.OS === "android") {
|
17
|
-
currentFrame.value = img.makeNonTextureImage();
|
18
|
-
} else {
|
19
|
-
currentFrame.value = img;
|
20
|
-
}
|
21
|
-
}
|
22
|
-
};
|
23
|
-
exports.setFrame = setFrame;
|
24
|
-
const processVideoState = (video, duration, framerate, currentTimestamp, options, currentTime, currentFrame, lastTimestamp, seek) => {
|
25
|
-
"worklet";
|
26
|
-
|
27
|
-
if (!video) {
|
28
|
-
return;
|
29
|
-
}
|
30
|
-
if (options.paused) {
|
31
|
-
return;
|
32
|
-
}
|
33
|
-
const delta = currentTimestamp - lastTimestamp.value;
|
34
|
-
const frameDuration = 1000 / framerate;
|
35
|
-
const currentFrameDuration = Math.floor(frameDuration / options.playbackSpeed);
|
36
|
-
if (currentTime.value + delta >= duration && options.looping) {
|
37
|
-
seek.value = 0;
|
38
|
-
}
|
39
|
-
if (seek.value !== null) {
|
40
|
-
video.seek(seek.value);
|
41
|
-
currentTime.value = seek.value;
|
42
|
-
setFrame(video, currentFrame);
|
43
|
-
lastTimestamp.value = currentTimestamp;
|
44
|
-
seek.value = null;
|
45
|
-
return;
|
46
|
-
}
|
47
|
-
if (delta >= currentFrameDuration) {
|
48
|
-
setFrame(video, currentFrame);
|
49
|
-
currentTime.value += delta;
|
50
|
-
lastTimestamp.value = currentTimestamp;
|
51
|
-
}
|
52
|
-
};
|
53
|
-
exports.processVideoState = processVideoState;
|
54
|
-
//# sourceMappingURL=video.js.map
|
@@ -1 +0,0 @@
|
|
1
|
-
{"version":3,"names":["_Platform","require","setFrame","video","currentFrame","img","nextImage","value","dispose","Platform","OS","makeNonTextureImage","exports","processVideoState","duration","framerate","currentTimestamp","options","currentTime","lastTimestamp","seek","paused","delta","frameDuration","currentFrameDuration","Math","floor","playbackSpeed","looping"],"sources":["video.ts"],"sourcesContent":["import type { SharedValue } from \"react-native-reanimated\";\n\nimport type { SkImage, Video } from \"../../skia/types\";\nimport { Platform } from \"../../Platform\";\n\nexport type Animated<T> = SharedValue<T> | T;\n\nexport interface PlaybackOptions {\n playbackSpeed: Animated<number>;\n looping: Animated<boolean>;\n paused: Animated<boolean>;\n seek: Animated<number | null>;\n}\n\ntype Materialized<T> = {\n [K in keyof T]: T[K] extends Animated<infer U> ? U : T[K];\n};\n\nexport type MaterializedPlaybackOptions = Materialized<\n Omit<PlaybackOptions, \"seek\">\n>;\n\nexport const setFrame = (\n video: Video,\n currentFrame: SharedValue<SkImage | null>\n) => {\n \"worklet\";\n const img = video.nextImage();\n if (img) {\n if (currentFrame.value) {\n currentFrame.value.dispose();\n }\n if (Platform.OS === \"android\") {\n currentFrame.value = img.makeNonTextureImage();\n } else {\n currentFrame.value = img;\n }\n }\n};\n\nexport const processVideoState = (\n video: Video | null,\n duration: number,\n framerate: number,\n currentTimestamp: number,\n options: Materialized<Omit<PlaybackOptions, \"seek\">>,\n currentTime: SharedValue<number>,\n currentFrame: SharedValue<SkImage | null>,\n lastTimestamp: SharedValue<number>,\n seek: SharedValue<number | null>\n) => {\n \"worklet\";\n if (!video) {\n return;\n }\n if (options.paused) {\n return;\n }\n const delta = currentTimestamp - lastTimestamp.value;\n\n const frameDuration = 1000 / framerate;\n const currentFrameDuration = Math.floor(\n frameDuration / options.playbackSpeed\n );\n if (currentTime.value + delta >= duration && options.looping) {\n seek.value = 0;\n }\n if (seek.value !== null) {\n video.seek(seek.value);\n currentTime.value = seek.value;\n setFrame(video, currentFrame);\n lastTimestamp.value = currentTimestamp;\n seek.value = null;\n return;\n }\n\n if (delta >= currentFrameDuration) {\n setFrame(video, currentFrame);\n currentTime.value += delta;\n lastTimestamp.value = currentTimestamp;\n }\n};\n"],"mappings":";;;;;;AAGA,IAAAA,SAAA,GAAAC,OAAA;AAmBO,MAAMC,QAAQ,GAAGA,CACtBC,KAAY,EACZC,YAAyC,KACtC;EACH,SAAS;;EACT,MAAMC,GAAG,GAAGF,KAAK,CAACG,SAAS,CAAC,CAAC;EAC7B,IAAID,GAAG,EAAE;IACP,IAAID,YAAY,CAACG,KAAK,EAAE;MACtBH,YAAY,CAACG,KAAK,CAACC,OAAO,CAAC,CAAC;IAC9B;IACA,IAAIC,kBAAQ,CAACC,EAAE,KAAK,SAAS,EAAE;MAC7BN,YAAY,CAACG,KAAK,GAAGF,GAAG,CAACM,mBAAmB,CAAC,CAAC;IAChD,CAAC,MAAM;MACLP,YAAY,CAACG,KAAK,GAAGF,GAAG;IAC1B;EACF;AACF,CAAC;AAACO,OAAA,CAAAV,QAAA,GAAAA,QAAA;AAEK,MAAMW,iBAAiB,GAAGA,CAC/BV,KAAmB,EACnBW,QAAgB,EAChBC,SAAiB,EACjBC,gBAAwB,EACxBC,OAAoD,EACpDC,WAAgC,EAChCd,YAAyC,EACzCe,aAAkC,EAClCC,IAAgC,KAC7B;EACH,SAAS;;EACT,IAAI,CAACjB,KAAK,EAAE;IACV;EACF;EACA,IAAIc,OAAO,CAACI,MAAM,EAAE;IAClB;EACF;EACA,MAAMC,KAAK,GAAGN,gBAAgB,GAAGG,aAAa,CAACZ,KAAK;EAEpD,MAAMgB,aAAa,GAAG,IAAI,GAAGR,SAAS;EACtC,MAAMS,oBAAoB,GAAGC,IAAI,CAACC,KAAK,CACrCH,aAAa,GAAGN,OAAO,CAACU,aAC1B,CAAC;EACD,IAAIT,WAAW,CAACX,KAAK,GAAGe,KAAK,IAAIR,QAAQ,IAAIG,OAAO,CAACW,OAAO,EAAE;IAC5DR,IAAI,CAACb,KAAK,GAAG,CAAC;EAChB;EACA,IAAIa,IAAI,CAACb,KAAK,KAAK,IAAI,EAAE;IACvBJ,KAAK,CAACiB,IAAI,CAACA,IAAI,CAACb,KAAK,CAAC;IACtBW,WAAW,CAACX,KAAK,GAAGa,IAAI,CAACb,KAAK;IAC9BL,QAAQ,CAACC,KAAK,EAAEC,YAAY,CAAC;IAC7Be,aAAa,CAACZ,KAAK,GAAGS,gBAAgB;IACtCI,IAAI,CAACb,KAAK,GAAG,IAAI;IACjB;EACF;EAEA,IAAIe,KAAK,IAAIE,oBAAoB,EAAE;IACjCtB,QAAQ,CAACC,KAAK,EAAEC,YAAY,CAAC;IAC7Bc,WAAW,CAACX,KAAK,IAAIe,KAAK;IAC1BH,aAAa,CAACZ,KAAK,GAAGS,gBAAgB;EACxC;AACF,CAAC;AAACJ,OAAA,CAAAC,iBAAA,GAAAA,iBAAA"}
|
@@ -1,16 +0,0 @@
|
|
1
|
-
import type { SharedValue } from "react-native-reanimated";
|
2
|
-
import type { SkImage, Video } from "../../skia/types";
|
3
|
-
export type Animated<T> = SharedValue<T> | T;
|
4
|
-
export interface PlaybackOptions {
|
5
|
-
playbackSpeed: Animated<number>;
|
6
|
-
looping: Animated<boolean>;
|
7
|
-
paused: Animated<boolean>;
|
8
|
-
seek: Animated<number | null>;
|
9
|
-
}
|
10
|
-
type Materialized<T> = {
|
11
|
-
[K in keyof T]: T[K] extends Animated<infer U> ? U : T[K];
|
12
|
-
};
|
13
|
-
export type MaterializedPlaybackOptions = Materialized<Omit<PlaybackOptions, "seek">>;
|
14
|
-
export declare const setFrame: (video: Video, currentFrame: SharedValue<SkImage | null>) => void;
|
15
|
-
export declare const processVideoState: (video: Video | null, duration: number, framerate: number, currentTimestamp: number, options: Materialized<Omit<PlaybackOptions, "seek">>, currentTime: SharedValue<number>, currentFrame: SharedValue<SkImage | null>, lastTimestamp: SharedValue<number>, seek: SharedValue<number | null>) => void;
|
16
|
-
export {};
|