@shopify/react-native-skia 1.2.2 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (91) hide show
  1. package/android/CMakeLists.txt +1 -0
  2. package/android/cpp/jni/JniPlatformContext.cpp +23 -0
  3. package/android/cpp/jni/include/JniPlatformContext.h +2 -0
  4. package/android/cpp/rnskia-android/RNSkAndroidPlatformContext.h +6 -0
  5. package/android/cpp/rnskia-android/RNSkAndroidVideo.cpp +92 -0
  6. package/android/cpp/rnskia-android/RNSkAndroidVideo.h +36 -0
  7. package/android/cpp/rnskia-android/SkiaOpenGLSurfaceFactory.cpp +34 -14
  8. package/android/cpp/rnskia-android/SkiaOpenGLSurfaceFactory.h +2 -1
  9. package/android/src/main/java/com/shopify/reactnative/skia/PlatformContext.java +5 -0
  10. package/android/src/main/java/com/shopify/reactnative/skia/RNSkVideo.java +185 -0
  11. package/cpp/api/JsiSkApi.h +2 -0
  12. package/cpp/api/JsiSkImageFactory.h +3 -3
  13. package/cpp/api/JsiVideo.h +87 -0
  14. package/cpp/rnskia/RNSkPlatformContext.h +4 -1
  15. package/cpp/rnskia/RNSkVideo.h +23 -0
  16. package/ios/RNSkia-iOS/RNSkiOSPlatformContext.h +2 -0
  17. package/ios/RNSkia-iOS/RNSkiOSPlatformContext.mm +7 -1
  18. package/ios/RNSkia-iOS/RNSkiOSVideo.h +40 -0
  19. package/ios/RNSkia-iOS/RNSkiOSVideo.mm +119 -0
  20. package/ios/RNSkia-iOS/RNSkiOSView.mm +37 -0
  21. package/ios/RNSkia-iOS/SkiaMetalSurfaceFactory.mm +2 -2
  22. package/lib/commonjs/external/reanimated/index.d.ts +1 -0
  23. package/lib/commonjs/external/reanimated/index.js +11 -0
  24. package/lib/commonjs/external/reanimated/index.js.map +1 -1
  25. package/lib/commonjs/external/reanimated/useVideo.d.ts +10 -0
  26. package/lib/commonjs/external/reanimated/useVideo.js +93 -0
  27. package/lib/commonjs/external/reanimated/useVideo.js.map +1 -0
  28. package/lib/commonjs/mock/index.js +2 -1
  29. package/lib/commonjs/mock/index.js.map +1 -1
  30. package/lib/commonjs/skia/types/Image/ImageFactory.d.ts +3 -3
  31. package/lib/commonjs/skia/types/Image/ImageFactory.js.map +1 -1
  32. package/lib/commonjs/skia/types/NativeBuffer/NativeBufferFactory.d.ts +2 -2
  33. package/lib/commonjs/skia/types/NativeBuffer/NativeBufferFactory.js.map +1 -1
  34. package/lib/commonjs/skia/types/Skia.d.ts +2 -0
  35. package/lib/commonjs/skia/types/Skia.js.map +1 -1
  36. package/lib/commonjs/skia/types/Video/Video.d.ts +8 -0
  37. package/lib/commonjs/skia/types/Video/Video.js +6 -0
  38. package/lib/commonjs/skia/types/Video/Video.js.map +1 -0
  39. package/lib/commonjs/skia/types/Video/index.d.ts +1 -0
  40. package/lib/commonjs/skia/types/Video/index.js +17 -0
  41. package/lib/commonjs/skia/types/Video/index.js.map +1 -0
  42. package/lib/commonjs/skia/web/JsiSkNativeBufferFactory.d.ts +1 -1
  43. package/lib/commonjs/skia/web/JsiSkNativeBufferFactory.js +1 -1
  44. package/lib/commonjs/skia/web/JsiSkNativeBufferFactory.js.map +1 -1
  45. package/lib/commonjs/skia/web/JsiSkia.js +4 -1
  46. package/lib/commonjs/skia/web/JsiSkia.js.map +1 -1
  47. package/lib/module/external/reanimated/index.d.ts +1 -0
  48. package/lib/module/external/reanimated/index.js +1 -0
  49. package/lib/module/external/reanimated/index.js.map +1 -1
  50. package/lib/module/external/reanimated/useVideo.d.ts +10 -0
  51. package/lib/module/external/reanimated/useVideo.js +85 -0
  52. package/lib/module/external/reanimated/useVideo.js.map +1 -0
  53. package/lib/module/mock/index.js +2 -1
  54. package/lib/module/mock/index.js.map +1 -1
  55. package/lib/module/skia/types/Image/ImageFactory.d.ts +3 -3
  56. package/lib/module/skia/types/Image/ImageFactory.js.map +1 -1
  57. package/lib/module/skia/types/NativeBuffer/NativeBufferFactory.d.ts +2 -2
  58. package/lib/module/skia/types/NativeBuffer/NativeBufferFactory.js.map +1 -1
  59. package/lib/module/skia/types/Skia.d.ts +2 -0
  60. package/lib/module/skia/types/Skia.js.map +1 -1
  61. package/lib/module/skia/types/Video/Video.d.ts +8 -0
  62. package/lib/module/skia/types/Video/Video.js +2 -0
  63. package/lib/module/skia/types/Video/Video.js.map +1 -0
  64. package/lib/module/skia/types/Video/index.d.ts +1 -0
  65. package/lib/module/skia/types/Video/index.js +2 -0
  66. package/lib/module/skia/types/Video/index.js.map +1 -0
  67. package/lib/module/skia/web/JsiSkNativeBufferFactory.d.ts +1 -1
  68. package/lib/module/skia/web/JsiSkNativeBufferFactory.js +1 -1
  69. package/lib/module/skia/web/JsiSkNativeBufferFactory.js.map +1 -1
  70. package/lib/module/skia/web/JsiSkia.js +4 -1
  71. package/lib/module/skia/web/JsiSkia.js.map +1 -1
  72. package/lib/typescript/src/external/reanimated/index.d.ts +1 -0
  73. package/lib/typescript/src/external/reanimated/useVideo.d.ts +10 -0
  74. package/lib/typescript/src/skia/types/Image/ImageFactory.d.ts +3 -3
  75. package/lib/typescript/src/skia/types/NativeBuffer/NativeBufferFactory.d.ts +2 -2
  76. package/lib/typescript/src/skia/types/Skia.d.ts +2 -0
  77. package/lib/typescript/src/skia/types/Video/Video.d.ts +8 -0
  78. package/lib/typescript/src/skia/types/Video/index.d.ts +1 -0
  79. package/lib/typescript/src/skia/web/JsiSkNativeBufferFactory.d.ts +1 -1
  80. package/package.json +1 -1
  81. package/scripts/setup-canvaskit.js +1 -1
  82. package/src/external/reanimated/index.ts +1 -0
  83. package/src/external/reanimated/useVideo.ts +115 -0
  84. package/src/mock/index.ts +1 -0
  85. package/src/skia/types/Image/ImageFactory.ts +3 -3
  86. package/src/skia/types/NativeBuffer/NativeBufferFactory.ts +2 -2
  87. package/src/skia/types/Skia.ts +2 -0
  88. package/src/skia/types/Video/Video.ts +9 -0
  89. package/src/skia/types/Video/index.ts +1 -0
  90. package/src/skia/web/JsiSkNativeBufferFactory.ts +1 -1
  91. package/src/skia/web/JsiSkia.ts +3 -0
@@ -0,0 +1,115 @@
1
+ import {
2
+ runOnUI,
3
+ useSharedValue,
4
+ type FrameInfo,
5
+ type SharedValue,
6
+ } from "react-native-reanimated";
7
+ import { useCallback, useEffect, useMemo } from "react";
8
+
9
+ import { Skia } from "../../skia/Skia";
10
+ import type { SkImage } from "../../skia/types";
11
+ import { Platform } from "../../Platform";
12
+
13
+ import Rea from "./ReanimatedProxy";
14
+
15
+ type Animated<T> = SharedValue<T> | T;
16
+
17
+ export interface PlaybackOptions {
18
+ playbackSpeed: Animated<number>;
19
+ looping: Animated<boolean>;
20
+ paused: Animated<boolean>;
21
+ }
22
+
23
+ const defaultOptions = {
24
+ playbackSpeed: 1,
25
+ looping: true,
26
+ paused: false,
27
+ };
28
+
29
+ const useOption = <T>(value: Animated<T>) => {
30
+ "worklet";
31
+ // TODO: only create defaultValue is needed (via makeMutable)
32
+ const defaultValue = useSharedValue(
33
+ Rea.isSharedValue(value) ? value.value : value
34
+ );
35
+ return Rea.isSharedValue(value) ? value : defaultValue;
36
+ };
37
+
38
+ export const useVideo = (
39
+ source: string | null,
40
+ userOptions?: Partial<PlaybackOptions>
41
+ ) => {
42
+ const video = useMemo(() => (source ? Skia.Video(source) : null), [source]);
43
+ const isPaused = useOption(userOptions?.paused ?? defaultOptions.paused);
44
+ const looping = useOption(userOptions?.looping ?? defaultOptions.looping);
45
+ const playbackSpeed = useOption(
46
+ userOptions?.playbackSpeed ?? defaultOptions.playbackSpeed
47
+ );
48
+ const currentFrame = Rea.useSharedValue<null | SkImage>(null);
49
+ const lastTimestamp = Rea.useSharedValue(-1);
50
+ const startTimestamp = Rea.useSharedValue(-1);
51
+
52
+ const framerate = useMemo(() => (video ? video.framerate() : -1), [video]);
53
+ const duration = useMemo(() => (video ? video.duration() : -1), [video]);
54
+ const frameDuration = useMemo(
55
+ () => (framerate > 0 ? 1000 / framerate : -1),
56
+ [framerate]
57
+ );
58
+ const disposeVideo = useCallback(() => {
59
+ "worklet";
60
+ video?.dispose();
61
+ }, [video]);
62
+
63
+ Rea.useFrameCallback((frameInfo: FrameInfo) => {
64
+ if (!video) {
65
+ return;
66
+ }
67
+ if (isPaused.value && lastTimestamp.value !== -1) {
68
+ return;
69
+ }
70
+ const { timestamp } = frameInfo;
71
+
72
+ // Initialize start timestamp
73
+ if (startTimestamp.value === -1) {
74
+ startTimestamp.value = timestamp;
75
+ }
76
+
77
+ // Calculate the current time in the video
78
+ const currentTimestamp = timestamp - startTimestamp.value;
79
+
80
+ // Handle looping
81
+ if (currentTimestamp > duration && looping.value) {
82
+ video.seek(0);
83
+ startTimestamp.value = timestamp;
84
+ }
85
+
86
+ // Update frame only if the elapsed time since last update is greater than the frame duration
87
+ const currentFrameDuration = frameDuration / playbackSpeed.value;
88
+ if (
89
+ lastTimestamp.value === -1 ||
90
+ timestamp - lastTimestamp.value >= currentFrameDuration
91
+ ) {
92
+ const img = video.nextImage();
93
+ if (img) {
94
+ if (currentFrame.value) {
95
+ currentFrame.value.dispose();
96
+ }
97
+ if (Platform.OS === "android") {
98
+ currentFrame.value = img.makeNonTextureImage();
99
+ } else {
100
+ currentFrame.value = img;
101
+ }
102
+ }
103
+ lastTimestamp.value = timestamp;
104
+ }
105
+ });
106
+
107
+ useEffect(() => {
108
+ return () => {
109
+ // TODO: should video simply be a shared value instead?
110
+ runOnUI(disposeVideo)();
111
+ };
112
+ }, [disposeVideo, video]);
113
+
114
+ return currentFrame;
115
+ };
package/src/mock/index.ts CHANGED
@@ -46,5 +46,6 @@ export const Mock = (CanvasKit: CanvasKit) => {
46
46
  useTypeface: () => null,
47
47
  useImage: () => null,
48
48
  useSVG: () => null,
49
+ useVideo: () => null,
49
50
  };
50
51
  };
@@ -54,7 +54,7 @@ export interface ImageFactory {
54
54
 
55
55
  /**
56
56
  * Return an Image backed by a given native buffer.
57
- * The platform buffer must be a valid owning reference.
57
+ * The native buffer must be a valid owning reference.
58
58
  *
59
59
  * For instance, this API is used by
60
60
  * [react-native-vision-camera](https://github.com/mrousavy/react-native-vision-camera)
@@ -62,9 +62,9 @@ export interface ImageFactory {
62
62
  *
63
63
  * - On Android; This is an `AHardwareBuffer*`
64
64
  * - On iOS, this is a `CVPixelBufferRef`
65
- * @param nativeBuffer A strong `uintptr_t` pointer to the native platform buffer
65
+ * @param nativeBuffer A strong `uintptr_t` pointer to the native buffer
66
66
  * @throws Throws an error if the Image could not be created, for example when the given
67
- * platform buffer is invalid.
67
+ * native buffer is invalid.
68
68
  */
69
69
  MakeImageFromNativeBuffer: (nativeBuffer: NativeBuffer) => SkImage;
70
70
 
@@ -32,7 +32,7 @@ export interface NativeBufferFactory {
32
32
  */
33
33
  MakeFromImage: (image: SkImage) => NativeBuffer;
34
34
  /**
35
- * Release a platform buffer that was created with `MakeFromImage`.
35
+ * Release a native buffer that was created with `MakeFromImage`.
36
36
  */
37
- Release: (platformBuffer: NativeBuffer) => void;
37
+ Release: (nativeBuffer: NativeBuffer) => void;
38
38
  }
@@ -30,6 +30,7 @@ import type { Color, SkColor } from "./Color";
30
30
  import type { TypefaceFontProviderFactory } from "./Paragraph/TypefaceFontProviderFactory";
31
31
  import type { AnimatedImageFactory } from "./AnimatedImage";
32
32
  import type { ParagraphBuilderFactory } from "./Paragraph/ParagraphBuilder";
33
+ import type { Video } from "./Video";
33
34
  import type { NativeBufferFactory } from "./NativeBuffer";
34
35
 
35
36
  /**
@@ -95,5 +96,6 @@ export interface Skia {
95
96
  TextBlob: TextBlobFactory;
96
97
  Surface: SurfaceFactory;
97
98
  ParagraphBuilder: ParagraphBuilderFactory;
99
+ Video: (url: string) => Video;
98
100
  NativeBuffer: NativeBufferFactory;
99
101
  }
@@ -0,0 +1,9 @@
1
+ import type { SkImage } from "../Image";
2
+ import type { SkJSIInstance } from "../JsiInstance";
3
+
4
+ export interface Video extends SkJSIInstance<"Video"> {
5
+ duration(): number;
6
+ framerate(): number;
7
+ nextImage(): SkImage | null;
8
+ seek(time: number): void;
9
+ }
@@ -0,0 +1 @@
1
+ export * from "./Video";
@@ -29,7 +29,7 @@ export class JsiSkNativeBufferFactory
29
29
  return canvas;
30
30
  }
31
31
 
32
- Release(_platformBuffer: NativeBuffer) {
32
+ Release(_nativeBuffer: NativeBuffer) {
33
33
  // it's a noop on Web
34
34
  }
35
35
  }
@@ -127,4 +127,7 @@ export const JsiSkApi = (CanvasKit: CanvasKit): Skia => ({
127
127
  FontMgr: new JsiSkFontMgrFactory(CanvasKit),
128
128
  ParagraphBuilder: new JsiSkParagraphBuilderFactory(CanvasKit),
129
129
  NativeBuffer: new JsiSkNativeBufferFactory(CanvasKit),
130
+ Video: (_localUri: string) => {
131
+ throw new Error("Not implemented on React Native Web");
132
+ },
130
133
  });