@shopify/react-native-skia 2.4.17 → 2.4.19

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 (87) hide show
  1. package/android/cpp/rnskia-android/RNSkAndroidVideo.cpp +34 -0
  2. package/android/cpp/rnskia-android/RNSkAndroidVideo.h +3 -0
  3. package/android/src/main/java/com/shopify/reactnative/skia/RNSkVideo.java +72 -18
  4. package/android/src/main/java/com/shopify/reactnative/skia/SkiaBaseView.java +7 -7
  5. package/apple/RNSkAppleVideo.h +30 -3
  6. package/apple/RNSkAppleVideo.mm +172 -17
  7. package/cpp/api/JsiSkApi.h +15 -13
  8. package/cpp/api/JsiSkHostObjects.h +57 -3
  9. package/cpp/api/JsiSkImage.h +19 -5
  10. package/cpp/api/JsiSkPicture.h +19 -5
  11. package/cpp/api/JsiSkSurface.h +19 -5
  12. package/cpp/api/JsiVideo.h +15 -2
  13. package/cpp/api/recorder/Convertor.h +4 -2
  14. package/cpp/jsi2/EnumMapper.h +49 -34
  15. package/cpp/jsi2/JSIConverter.h +149 -99
  16. package/cpp/jsi2/NativeObject.h +23 -25
  17. package/cpp/jsi2/Promise.cpp +10 -6
  18. package/cpp/jsi2/Promise.h +9 -7
  19. package/cpp/rnskia/RNDawnContext.h +3 -8
  20. package/cpp/rnskia/RNSkManager.cpp +13 -7
  21. package/cpp/rnskia/RNSkVideo.h +3 -0
  22. package/cpp/rnwgpu/api/GPUAdapter.cpp +31 -32
  23. package/cpp/rnwgpu/api/GPUAdapter.h +1 -1
  24. package/cpp/rnwgpu/api/GPUBuffer.cpp +8 -8
  25. package/cpp/rnwgpu/api/GPUCommandEncoder.h +4 -4
  26. package/cpp/rnwgpu/api/GPUDevice.h +12 -12
  27. package/cpp/rnwgpu/api/GPUQueue.cpp +45 -44
  28. package/cpp/rnwgpu/api/GPUQueue.h +1 -1
  29. package/cpp/rnwgpu/api/GPURenderBundleEncoder.h +1 -1
  30. package/cpp/rnwgpu/api/GPURenderPassEncoder.h +1 -1
  31. package/cpp/rnwgpu/api/descriptors/GPUBindGroupEntry.h +1 -1
  32. package/cpp/rnwgpu/api/descriptors/GPUComputePipelineDescriptor.h +1 -1
  33. package/cpp/rnwgpu/api/descriptors/GPUImageCopyExternalImage.h +7 -6
  34. package/cpp/rnwgpu/api/descriptors/GPURenderPassDescriptor.h +1 -1
  35. package/cpp/rnwgpu/api/descriptors/GPURenderPipelineDescriptor.h +1 -1
  36. package/cpp/rnwgpu/api/descriptors/GPUVertexState.h +1 -1
  37. package/cpp/rnwgpu/async/AsyncRunner.cpp +2 -1
  38. package/cpp/rnwgpu/async/AsyncRunner.h +2 -1
  39. package/lib/commonjs/external/reanimated/useVideo.js +30 -31
  40. package/lib/commonjs/external/reanimated/useVideo.js.map +1 -1
  41. package/lib/commonjs/renderer/Offscreen.js +1 -0
  42. package/lib/commonjs/renderer/Offscreen.js.map +1 -1
  43. package/lib/commonjs/skia/types/Video/Video.d.ts +3 -0
  44. package/lib/commonjs/skia/types/Video/Video.js.map +1 -1
  45. package/lib/commonjs/skia/web/JsiVideo.d.ts +3 -0
  46. package/lib/commonjs/skia/web/JsiVideo.js +9 -0
  47. package/lib/commonjs/skia/web/JsiVideo.js.map +1 -1
  48. package/lib/commonjs/sksg/Container.web.js +1 -0
  49. package/lib/commonjs/sksg/Container.web.js.map +1 -1
  50. package/lib/commonjs/sksg/HostConfig.js +4 -1
  51. package/lib/commonjs/sksg/HostConfig.js.map +1 -1
  52. package/lib/commonjs/sksg/Reconciler.js +6 -6
  53. package/lib/commonjs/sksg/Reconciler.js.map +1 -1
  54. package/lib/commonjs/sksg/StaticContainer.js +1 -0
  55. package/lib/commonjs/sksg/StaticContainer.js.map +1 -1
  56. package/lib/module/external/reanimated/useVideo.js +30 -31
  57. package/lib/module/external/reanimated/useVideo.js.map +1 -1
  58. package/lib/module/renderer/Offscreen.js +1 -0
  59. package/lib/module/renderer/Offscreen.js.map +1 -1
  60. package/lib/module/skia/types/Video/Video.d.ts +3 -0
  61. package/lib/module/skia/types/Video/Video.js.map +1 -1
  62. package/lib/module/skia/web/JsiVideo.d.ts +3 -0
  63. package/lib/module/skia/web/JsiVideo.js +9 -0
  64. package/lib/module/skia/web/JsiVideo.js.map +1 -1
  65. package/lib/module/sksg/Container.web.js +1 -0
  66. package/lib/module/sksg/Container.web.js.map +1 -1
  67. package/lib/module/sksg/HostConfig.js +4 -1
  68. package/lib/module/sksg/HostConfig.js.map +1 -1
  69. package/lib/module/sksg/Reconciler.js +6 -6
  70. package/lib/module/sksg/Reconciler.js.map +1 -1
  71. package/lib/module/sksg/StaticContainer.js +1 -0
  72. package/lib/module/sksg/StaticContainer.js.map +1 -1
  73. package/lib/typescript/lib/commonjs/skia/web/JsiVideo.d.ts +3 -0
  74. package/lib/typescript/lib/commonjs/sksg/HostConfig.d.ts +2 -0
  75. package/lib/typescript/lib/module/skia/web/JsiVideo.d.ts +3 -0
  76. package/lib/typescript/lib/module/sksg/HostConfig.d.ts +2 -0
  77. package/lib/typescript/src/skia/types/Video/Video.d.ts +3 -0
  78. package/lib/typescript/src/skia/web/JsiVideo.d.ts +3 -0
  79. package/package.json +1 -1
  80. package/src/external/reanimated/useVideo.ts +32 -32
  81. package/src/renderer/Offscreen.tsx +1 -0
  82. package/src/skia/types/Video/Video.ts +3 -0
  83. package/src/skia/web/JsiVideo.ts +12 -0
  84. package/src/sksg/Container.web.ts +1 -0
  85. package/src/sksg/HostConfig.ts +4 -0
  86. package/src/sksg/Reconciler.ts +5 -6
  87. package/src/sksg/StaticContainer.ts +1 -0
@@ -44,13 +44,11 @@ const defaultOptions = {
44
44
  looping: true,
45
45
  paused: false,
46
46
  seek: null,
47
- currentTime: 0,
48
47
  volume: 0,
49
48
  };
50
49
 
51
50
  const useOption = <T>(value: MaybeAnimated<T>) => {
52
51
  "worklet";
53
- // TODO: only create defaultValue is needed (via makeMutable)
54
52
  const defaultValue = Rea.useSharedValue(
55
53
  Rea.isSharedValue(value) ? value.value : value
56
54
  );
@@ -75,7 +73,6 @@ export const useVideo = (
75
73
  const volume = useOption(userOptions?.volume ?? defaultOptions.volume);
76
74
  const currentFrame = Rea.useSharedValue<null | SkImage>(null);
77
75
  const currentTime = Rea.useSharedValue(0);
78
- const lastTimestamp = Rea.useSharedValue(-1);
79
76
  const duration = useMemo(() => video?.duration() ?? 0, [video]);
80
77
  const framerate = useMemo(
81
78
  () => (Platform.OS === "web" ? -1 : (video?.framerate() ?? 0)),
@@ -83,70 +80,73 @@ export const useVideo = (
83
80
  );
84
81
  const size = useMemo(() => video?.size() ?? { width: 0, height: 0 }, [video]);
85
82
  const rotation = useMemo(() => video?.rotation() ?? 0, [video]);
86
- const frameDuration = 1000 / framerate;
87
- const currentFrameDuration = Math.floor(frameDuration);
83
+
84
+ // Handle pause/play state changes
88
85
  Rea.useAnimatedReaction(
89
86
  () => isPaused.value,
90
87
  (paused) => {
91
88
  if (paused) {
92
89
  video?.pause();
93
90
  } else {
94
- lastTimestamp.value = -1;
95
91
  video?.play();
96
92
  }
97
93
  }
98
94
  );
95
+
96
+ // Handle seek
99
97
  Rea.useAnimatedReaction(
100
98
  () => seek.value,
101
99
  (value) => {
102
100
  if (value !== null) {
103
101
  video?.seek(value);
104
- currentTime.value = value;
105
102
  seek.value = null;
106
103
  }
107
104
  }
108
105
  );
106
+
107
+ // Handle volume changes
109
108
  Rea.useAnimatedReaction(
110
109
  () => volume.value,
111
110
  (value) => {
112
111
  video?.setVolume(value);
113
112
  }
114
113
  );
115
- Rea.useFrameCallback((frameInfo: FrameInfo) => {
114
+
115
+ // Handle looping changes
116
+ Rea.useAnimatedReaction(
117
+ () => looping.value,
118
+ (value) => {
119
+ video?.setLooping(value);
120
+ }
121
+ );
122
+
123
+ // Frame callback - simplified since native handles frame timing
124
+ Rea.useFrameCallback((_frameInfo: FrameInfo) => {
116
125
  "worklet";
117
126
  if (!video) {
118
127
  return;
119
128
  }
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
- }
129
+ // Update current time from native player
130
+ currentTime.value = video.currentTime();
131
+ // Get the latest frame (native handles timing via CADisplayLink/etc)
132
+ setFrame(video, currentFrame);
142
133
  });
143
134
 
135
+ // Apply initial state when video becomes available
144
136
  useEffect(() => {
137
+ if (video) {
138
+ video.setLooping(looping.value);
139
+ video.setVolume(volume.value);
140
+ if (isPaused.value) {
141
+ video.pause();
142
+ } else {
143
+ video.play();
144
+ }
145
+ }
145
146
  return () => {
146
- // TODO: should video simply be a shared value instead?
147
147
  Rea.runOnUI(disposeVideo)(video);
148
148
  };
149
- }, [video]);
149
+ }, [video, isPaused, looping, volume]);
150
150
 
151
151
  return {
152
152
  currentFrame,
@@ -20,6 +20,7 @@ export const drawAsPicture = async (element: ReactElement, bounds?: SkRect) => {
20
20
  await root.render(element);
21
21
  root.drawOnCanvas(canvas);
22
22
  const picture = recorder.finishRecordingAsPicture();
23
+ recorder.dispose();
23
24
  root.unmount();
24
25
  return picture;
25
26
  };
@@ -6,6 +6,7 @@ export type VideoRotation = 0 | 90 | 180 | 270;
6
6
  export interface Video extends SkJSIInstance<"Video"> {
7
7
  duration(): number;
8
8
  framerate(): number;
9
+ currentTime(): number;
9
10
  nextImage(): SkImage | null;
10
11
  seek(time: number): void;
11
12
  rotation(): VideoRotation;
@@ -13,4 +14,6 @@ export interface Video extends SkJSIInstance<"Video"> {
13
14
  pause(): void;
14
15
  play(): void;
15
16
  setVolume(volume: number): void;
17
+ setLooping(looping: boolean): void;
18
+ isPlaying(): boolean;
16
19
  }
@@ -46,6 +46,10 @@ export class JsiVideo implements Video {
46
46
  return throwNotImplementedOnRNWeb<number>();
47
47
  }
48
48
 
49
+ currentTime() {
50
+ return this.videoElement.currentTime * 1000;
51
+ }
52
+
49
53
  setSurface(surface: Surface) {
50
54
  // If we have the surface, we can use the WebGL buffer which is slightly faster
51
55
  // This is because WebGL cannot be shared across contextes.
@@ -89,6 +93,14 @@ export class JsiVideo implements Video {
89
93
  this.videoElement.volume = volume;
90
94
  }
91
95
 
96
+ setLooping(looping: boolean) {
97
+ this.videoElement.loop = looping;
98
+ }
99
+
100
+ isPlaying() {
101
+ return !this.videoElement.paused && !this.videoElement.ended;
102
+ }
103
+
92
104
  [Symbol.dispose]() {
93
105
  if (this.videoElement.parentNode) {
94
106
  this.videoElement.parentNode.removeChild(this.videoElement);
@@ -21,6 +21,7 @@ const drawOnscreen = (Skia: Skia, nativeId: number, recording: Recording) => {
21
21
  const ctx = createDrawingContext(Skia, recording.paintPool, canvas);
22
22
  replay(ctx, recording.commands);
23
23
  const picture = rec.finishRecordingAsPicture();
24
+ rec.dispose();
24
25
  //const end = performance.now();
25
26
  //console.log("Recording time: ", end - start);
26
27
  SkiaViewApi.setJsiProperty(nativeId, "picture", picture);
@@ -263,4 +263,8 @@ export const sksgHostConfig: SkiaHostConfig = {
263
263
  return DefaultEventPriority;
264
264
  },
265
265
  resetFormInstance() {},
266
+
267
+ // DefinitelyTyped is not up to date, these are needed for devtools
268
+ rendererVersion: "0.0.1",
269
+ rendererPackageName: "react-native-skia",
266
270
  };
@@ -13,11 +13,8 @@ import "./Elements";
13
13
 
14
14
  const skiaReconciler = ReactReconciler(sksgHostConfig);
15
15
 
16
- skiaReconciler.injectIntoDevTools({
17
- bundleType: 1,
18
- version: "0.0.1",
19
- rendererPackageName: "react-native-skia",
20
- });
16
+ // @ts-expect-error DefinitelyTyped is not up to date
17
+ skiaReconciler.injectIntoDevTools();
21
18
 
22
19
  export class SkiaSGRoot {
23
20
  private root: OpaqueRoot;
@@ -68,7 +65,9 @@ export class SkiaSGRoot {
68
65
  const recorder = this.Skia.PictureRecorder();
69
66
  const canvas = recorder.beginRecording();
70
67
  this.drawOnCanvas(canvas);
71
- return recorder.finishRecordingAsPicture();
68
+ const picture = recorder.finishRecordingAsPicture();
69
+ recorder.dispose();
70
+ return picture;
72
71
  }
73
72
 
74
73
  unmount() {
@@ -65,6 +65,7 @@ export class StaticContainer extends Container {
65
65
  const canvas = rec.beginRecording();
66
66
  this.drawOnCanvas(canvas);
67
67
  const picture = rec.finishRecordingAsPicture();
68
+ rec.dispose();
68
69
  SkiaViewApi.setJsiProperty(this.nativeId, "picture", picture);
69
70
  }
70
71
  }