@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.
Files changed (125) hide show
  1. package/android/cpp/jni/JniPlatformContext.cpp +4 -3
  2. package/android/cpp/rnskia-android/RNSkAndroidVideo.cpp +59 -1
  3. package/android/cpp/rnskia-android/RNSkAndroidVideo.h +4 -0
  4. package/android/src/main/java/com/shopify/reactnative/skia/RNSkVideo.java +80 -7
  5. package/cpp/api/JsiVideo.h +32 -7
  6. package/cpp/rnskia/RNSkVideo.h +4 -0
  7. package/ios/RNSkia-iOS/RNSkiOSVideo.h +13 -4
  8. package/ios/RNSkia-iOS/RNSkiOSVideo.mm +65 -67
  9. package/lib/commonjs/dom/nodes/datatypes/Fitting.js +42 -30
  10. package/lib/commonjs/dom/nodes/datatypes/Fitting.js.map +1 -1
  11. package/lib/commonjs/external/reanimated/useVideo.d.ts +16 -4
  12. package/lib/commonjs/external/reanimated/useVideo.js +92 -17
  13. package/lib/commonjs/external/reanimated/useVideo.js.map +1 -1
  14. package/lib/commonjs/external/reanimated/useVideoLoading.d.ts +4 -0
  15. package/lib/commonjs/external/reanimated/useVideoLoading.js +27 -0
  16. package/lib/commonjs/external/reanimated/useVideoLoading.js.map +1 -0
  17. package/lib/commonjs/external/reanimated/useVideoLoading.web.d.ts +4 -0
  18. package/lib/commonjs/external/reanimated/useVideoLoading.web.js +20 -0
  19. package/lib/commonjs/external/reanimated/useVideoLoading.web.js.map +1 -0
  20. package/lib/commonjs/renderer/components/shapes/FitBox.d.ts +2 -10
  21. package/lib/commonjs/renderer/components/shapes/FitBox.js +32 -3
  22. package/lib/commonjs/renderer/components/shapes/FitBox.js.map +1 -1
  23. package/lib/commonjs/skia/core/Matrix.js +5 -1
  24. package/lib/commonjs/skia/core/Matrix.js.map +1 -1
  25. package/lib/commonjs/skia/types/Matrix.js +2 -0
  26. package/lib/commonjs/skia/types/Matrix.js.map +1 -1
  27. package/lib/commonjs/skia/types/NativeBuffer/NativeBufferFactory.d.ts +3 -1
  28. package/lib/commonjs/skia/types/NativeBuffer/NativeBufferFactory.js +4 -2
  29. package/lib/commonjs/skia/types/NativeBuffer/NativeBufferFactory.js.map +1 -1
  30. package/lib/commonjs/skia/types/Skia.d.ts +1 -1
  31. package/lib/commonjs/skia/types/Skia.js.map +1 -1
  32. package/lib/commonjs/skia/types/Video/Video.d.ts +9 -1
  33. package/lib/commonjs/skia/types/Video/Video.js.map +1 -1
  34. package/lib/commonjs/skia/web/CanvasKitWebGLBufferImpl.d.ts +9 -0
  35. package/lib/commonjs/skia/web/CanvasKitWebGLBufferImpl.js +30 -0
  36. package/lib/commonjs/skia/web/CanvasKitWebGLBufferImpl.js.map +1 -0
  37. package/lib/commonjs/skia/web/JsiSkImageFactory.js +8 -2
  38. package/lib/commonjs/skia/web/JsiSkImageFactory.js.map +1 -1
  39. package/lib/commonjs/skia/web/JsiSkia.js +2 -3
  40. package/lib/commonjs/skia/web/JsiSkia.js.map +1 -1
  41. package/lib/commonjs/skia/web/JsiVideo.d.ts +24 -0
  42. package/lib/commonjs/skia/web/JsiVideo.js +83 -0
  43. package/lib/commonjs/skia/web/JsiVideo.js.map +1 -0
  44. package/lib/commonjs/views/SkiaDomView.js +2 -0
  45. package/lib/commonjs/views/SkiaDomView.js.map +1 -1
  46. package/lib/commonjs/views/SkiaDomView.web.js +2 -0
  47. package/lib/commonjs/views/SkiaDomView.web.js.map +1 -1
  48. package/lib/commonjs/views/SkiaJSDomView.js +2 -0
  49. package/lib/commonjs/views/SkiaJSDomView.js.map +1 -1
  50. package/lib/module/dom/nodes/datatypes/Fitting.js +41 -29
  51. package/lib/module/dom/nodes/datatypes/Fitting.js.map +1 -1
  52. package/lib/module/external/reanimated/useVideo.d.ts +16 -4
  53. package/lib/module/external/reanimated/useVideo.js +92 -17
  54. package/lib/module/external/reanimated/useVideo.js.map +1 -1
  55. package/lib/module/external/reanimated/useVideoLoading.d.ts +4 -0
  56. package/lib/module/external/reanimated/useVideoLoading.js +20 -0
  57. package/lib/module/external/reanimated/useVideoLoading.js.map +1 -0
  58. package/lib/module/external/reanimated/useVideoLoading.web.d.ts +4 -0
  59. package/lib/module/external/reanimated/useVideoLoading.web.js +13 -0
  60. package/lib/module/external/reanimated/useVideoLoading.web.js.map +1 -0
  61. package/lib/module/renderer/components/shapes/FitBox.d.ts +2 -10
  62. package/lib/module/renderer/components/shapes/FitBox.js +32 -3
  63. package/lib/module/renderer/components/shapes/FitBox.js.map +1 -1
  64. package/lib/module/skia/core/Matrix.js +5 -1
  65. package/lib/module/skia/core/Matrix.js.map +1 -1
  66. package/lib/module/skia/types/Matrix.js +2 -0
  67. package/lib/module/skia/types/Matrix.js.map +1 -1
  68. package/lib/module/skia/types/NativeBuffer/NativeBufferFactory.d.ts +3 -1
  69. package/lib/module/skia/types/NativeBuffer/NativeBufferFactory.js +2 -1
  70. package/lib/module/skia/types/NativeBuffer/NativeBufferFactory.js.map +1 -1
  71. package/lib/module/skia/types/Skia.d.ts +1 -1
  72. package/lib/module/skia/types/Skia.js.map +1 -1
  73. package/lib/module/skia/types/Video/Video.d.ts +9 -1
  74. package/lib/module/skia/types/Video/Video.js.map +1 -1
  75. package/lib/module/skia/web/CanvasKitWebGLBufferImpl.d.ts +9 -0
  76. package/lib/module/skia/web/CanvasKitWebGLBufferImpl.js +23 -0
  77. package/lib/module/skia/web/CanvasKitWebGLBufferImpl.js.map +1 -0
  78. package/lib/module/skia/web/JsiSkImageFactory.js +9 -3
  79. package/lib/module/skia/web/JsiSkImageFactory.js.map +1 -1
  80. package/lib/module/skia/web/JsiSkia.js +2 -3
  81. package/lib/module/skia/web/JsiSkia.js.map +1 -1
  82. package/lib/module/skia/web/JsiVideo.d.ts +24 -0
  83. package/lib/module/skia/web/JsiVideo.js +75 -0
  84. package/lib/module/skia/web/JsiVideo.js.map +1 -0
  85. package/lib/module/views/SkiaDomView.js +2 -0
  86. package/lib/module/views/SkiaDomView.js.map +1 -1
  87. package/lib/module/views/SkiaDomView.web.js +2 -0
  88. package/lib/module/views/SkiaDomView.web.js.map +1 -1
  89. package/lib/module/views/SkiaJSDomView.js +2 -0
  90. package/lib/module/views/SkiaJSDomView.js.map +1 -1
  91. package/lib/typescript/src/external/reanimated/useVideo.d.ts +16 -4
  92. package/lib/typescript/src/external/reanimated/useVideoLoading.d.ts +4 -0
  93. package/lib/typescript/src/external/reanimated/useVideoLoading.web.d.ts +4 -0
  94. package/lib/typescript/src/renderer/components/shapes/FitBox.d.ts +2 -10
  95. package/lib/typescript/src/skia/types/NativeBuffer/NativeBufferFactory.d.ts +3 -1
  96. package/lib/typescript/src/skia/types/Skia.d.ts +1 -1
  97. package/lib/typescript/src/skia/types/Video/Video.d.ts +9 -1
  98. package/lib/typescript/src/skia/web/CanvasKitWebGLBufferImpl.d.ts +9 -0
  99. package/lib/typescript/src/skia/web/JsiVideo.d.ts +24 -0
  100. package/package.json +1 -1
  101. package/src/dom/nodes/datatypes/Fitting.ts +28 -21
  102. package/src/external/reanimated/useVideo.ts +108 -31
  103. package/src/external/reanimated/useVideoLoading.ts +28 -0
  104. package/src/external/reanimated/useVideoLoading.web.ts +17 -0
  105. package/src/renderer/components/shapes/FitBox.tsx +38 -4
  106. package/src/skia/core/Matrix.ts +4 -2
  107. package/src/skia/types/Matrix.ts +1 -0
  108. package/src/skia/types/NativeBuffer/NativeBufferFactory.ts +10 -2
  109. package/src/skia/types/Skia.ts +1 -1
  110. package/src/skia/types/Video/Video.ts +7 -1
  111. package/src/skia/web/CanvasKitWebGLBufferImpl.ts +22 -0
  112. package/src/skia/web/JsiSkImageFactory.ts +16 -3
  113. package/src/skia/web/JsiSkia.ts +2 -3
  114. package/src/skia/web/JsiVideo.ts +96 -0
  115. package/src/views/SkiaDomView.tsx +4 -0
  116. package/src/views/SkiaDomView.web.tsx +4 -0
  117. package/src/views/SkiaJSDomView.tsx +4 -0
  118. package/lib/commonjs/external/reanimated/video.d.ts +0 -16
  119. package/lib/commonjs/external/reanimated/video.js +0 -54
  120. package/lib/commonjs/external/reanimated/video.js.map +0 -1
  121. package/lib/module/external/reanimated/video.d.ts +0 -16
  122. package/lib/module/external/reanimated/video.js +0 -46
  123. package/lib/module/external/reanimated/video.js.map +0 -1
  124. package/lib/typescript/src/external/reanimated/video.d.ts +0 -16
  125. package/src/external/reanimated/video.ts +0 -82
@@ -1,22 +1,52 @@
1
- import { type FrameInfo } from "react-native-reanimated";
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
- processVideoState,
10
- type Animated,
11
- type PlaybackOptions,
12
- } from "./video";
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 = useMemo(() => (source ? Skia.Video(source) : null), [source]);
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 playbackSpeed = useOption(
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(() => video?.framerate() ?? 0, [video]);
52
- const rotationInDegrees = useMemo(
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
- processVideoState(
58
- video,
59
- duration,
60
- framerate,
61
- frameInfo.timestamp,
62
- {
63
- paused: isPaused.value,
64
- looping: looping.value,
65
- playbackSpeed: playbackSpeed.value,
66
- },
67
- currentTime,
68
- currentFrame,
69
- lastTimestamp,
70
- seek
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 { currentFrame, currentTime, duration, framerate, rotationInDegrees };
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 = (fit: Fit, src: SkRect, dst: SkRect) => {
17
- const rects = fitRects(fit, src, dst);
18
- return rect2rect(rects.src, rects.dst);
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) => {
@@ -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
- processTransform(Skia.Matrix(), transforms);
5
+ export const processTransform2d = (transforms: Transforms3d) => {
6
+ "worklet";
7
+ return processTransform(Skia.Matrix(), transforms);
8
+ };
@@ -30,6 +30,7 @@ export const processTransform = <T extends SkMatrix | SkCanvas>(
30
30
  m: T,
31
31
  transforms: Transforms3d
32
32
  ) => {
33
+ "worklet";
33
34
  const m3 = processTransform3d(transforms);
34
35
  m.concat(m3);
35
36
  return m;
@@ -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 bigint | ArrayBuffer | CanvasImageSource | unknown = unknown
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
@@ -96,6 +96,6 @@ export interface Skia {
96
96
  TextBlob: TextBlobFactory;
97
97
  Surface: SurfaceFactory;
98
98
  ParagraphBuilder: ParagraphBuilderFactory;
99
- Video: (url: string) => Video;
99
+ Video: (url: string) => Promise<Video> | Video;
100
100
  NativeBuffer: NativeBufferFactory;
101
101
  }
@@ -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
- getRotationInDegrees(): number;
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
- // TODO: this is way to slow
39
- const img = this.CanvasKit.MakeImageFromCanvasImageSource(buffer);
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
@@ -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: (_localUri: string) => {
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 {};