@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
@@ -48,6 +48,7 @@ add_library(
48
48
  "${PROJECT_SOURCE_DIR}/cpp/rnskia-android/SkiaOpenGLSurfaceFactory.cpp"
49
49
  "${PROJECT_SOURCE_DIR}/cpp/rnskia-android/GrAHardwareBufferUtils.cpp"
50
50
  "${PROJECT_SOURCE_DIR}/cpp/rnskia-android/AHardwareBufferUtils.cpp"
51
+ "${PROJECT_SOURCE_DIR}/cpp/rnskia-android/RNSkAndroidVideo.cpp"
51
52
 
52
53
  "${PROJECT_SOURCE_DIR}/../cpp/jsi/JsiHostObject.cpp"
53
54
  "${PROJECT_SOURCE_DIR}/../cpp/jsi/JsiValue.cpp"
@@ -75,6 +75,29 @@ TSelf JniPlatformContext::initHybrid(jni::alias_ref<jhybridobject> jThis,
75
75
  return makeCxxInstance(jThis, pixelDensity);
76
76
  }
77
77
 
78
+ jni::global_ref<jobject>
79
+ JniPlatformContext::createVideo(const std::string &url) {
80
+ jni::ThreadScope ts; // Manages JNI thread attachment/detachment
81
+
82
+ // Get the JNI environment
83
+ JNIEnv *env = facebook::jni::Environment::current();
84
+
85
+ // Convert std::string to jstring
86
+ jstring jUrl = env->NewStringUTF(url.c_str());
87
+
88
+ // Get the method ID for the createVideo method
89
+ // Replace "Lcom/yourpackage/RNSkVideo;" with the actual return type
90
+ // descriptor
91
+ static auto method =
92
+ javaPart_->getClass()->getMethod<jobject(jstring)>("createVideo");
93
+
94
+ // Call the method and receive a local reference to the video object
95
+ auto videoObject = method(javaPart_.get(), jUrl);
96
+ // Clean up the jstring local reference
97
+ auto result = jni::make_global(videoObject);
98
+ return result;
99
+ }
100
+
78
101
  sk_sp<SkImage> JniPlatformContext::takeScreenshotFromViewTag(size_t tag) {
79
102
  // Call the java method for creating a view screenshot as a bitmap:
80
103
  auto env = jni::Environment::current();
@@ -50,6 +50,8 @@ public:
50
50
  _onNotifyDrawLoop = callback;
51
51
  }
52
52
 
53
+ jni::global_ref<jobject> createVideo(const std::string &url);
54
+
53
55
  private:
54
56
  friend HybridBase;
55
57
  jni::global_ref<JniPlatformContext::javaobject> javaPart_;
@@ -11,6 +11,7 @@
11
11
 
12
12
  #include "AHardwareBufferUtils.h"
13
13
  #include "JniPlatformContext.h"
14
+ #include "RNSkAndroidVideo.h"
14
15
  #include "RNSkPlatformContext.h"
15
16
  #include "SkiaOpenGLSurfaceFactory.h"
16
17
 
@@ -57,6 +58,11 @@ public:
57
58
  return SkiaOpenGLSurfaceFactory::makeImageFromHardwareBuffer(buffer);
58
59
  }
59
60
 
61
+ std::shared_ptr<RNSkVideo> createVideo(const std::string &url) override {
62
+ auto jniVideo = _jniPlatformContext->createVideo(url);
63
+ return std::make_shared<RNSkAndroidVideo>(jniVideo);
64
+ }
65
+
60
66
  void releaseNativeBuffer(uint64_t pointer) override {
61
67
  #if __ANDROID_API__ >= 26
62
68
  AHardwareBuffer *buffer = reinterpret_cast<AHardwareBuffer *>(pointer);
@@ -0,0 +1,92 @@
1
+ #include <memory>
2
+ #include <string>
3
+
4
+ #if __ANDROID_API__ >= 26
5
+ #include <android/hardware_buffer_jni.h>
6
+ #endif
7
+
8
+ #pragma clang diagnostic push
9
+ #pragma clang diagnostic ignored "-Wdocumentation"
10
+
11
+ #include "include/core/SkImage.h"
12
+
13
+ #pragma clang diagnostic pop
14
+
15
+ #include "RNSkAndroidVideo.h"
16
+ #include "SkiaOpenGLSurfaceFactory.h"
17
+
18
+ namespace RNSkia {
19
+
20
+ namespace jsi = facebook::jsi;
21
+ namespace jni = facebook::jni;
22
+
23
+ RNSkAndroidVideo::RNSkAndroidVideo(jni::global_ref<jobject> jniVideo)
24
+ : _jniVideo(jniVideo) {
25
+ #if __ANDROID_API__ < 26
26
+ throw std::runtime_error("Skia Videos are only support on API 26 and above");
27
+ #endif
28
+ }
29
+
30
+ RNSkAndroidVideo::~RNSkAndroidVideo() {}
31
+
32
+ sk_sp<SkImage> RNSkAndroidVideo::nextImage(double *timeStamp) {
33
+ #if __ANDROID_API__ >= 26
34
+ JNIEnv *env = facebook::jni::Environment::current();
35
+ // Get the Java class and method ID
36
+ jclass cls = env->GetObjectClass(_jniVideo.get());
37
+ jmethodID mid =
38
+ env->GetMethodID(cls, "nextImage", "()Landroid/hardware/HardwareBuffer;");
39
+ if (mid == nullptr) {
40
+ // Method not found, handle error
41
+ RNSkLogger::logToConsole("nextImage method not found");
42
+ return nullptr;
43
+ }
44
+ // Call the Java method
45
+ jobject jHardwareBuffer = env->CallObjectMethod(_jniVideo.get(), mid);
46
+ if (jHardwareBuffer == nullptr) {
47
+ RNSkLogger::logToConsole("Buffer not found");
48
+ // Null return, handle error
49
+ return nullptr;
50
+ }
51
+ // Convert jobject to AHardwareBuffer
52
+ AHardwareBuffer *buffer =
53
+ AHardwareBuffer_fromHardwareBuffer(env, jHardwareBuffer);
54
+ return SkiaOpenGLSurfaceFactory::makeImageFromHardwareBuffer(buffer);
55
+ #else
56
+ return nullptr;
57
+ #endif
58
+ }
59
+
60
+ double RNSkAndroidVideo::duration() {
61
+ JNIEnv *env = facebook::jni::Environment::current();
62
+ jclass cls = env->GetObjectClass(_jniVideo.get());
63
+ jmethodID mid = env->GetMethodID(cls, "getDuration", "()D");
64
+ if (!mid) {
65
+ RNSkLogger::logToConsole("getDuration method not found");
66
+ return 0.0;
67
+ }
68
+ return env->CallDoubleMethod(_jniVideo.get(), mid);
69
+ }
70
+ double RNSkAndroidVideo::framerate() {
71
+ JNIEnv *env = facebook::jni::Environment::current();
72
+ jclass cls = env->GetObjectClass(_jniVideo.get());
73
+ jmethodID mid = env->GetMethodID(cls, "getFrameRate", "()D");
74
+ if (!mid) {
75
+ RNSkLogger::logToConsole("getFrameRate method not found");
76
+ return 0.0;
77
+ }
78
+ return env->CallDoubleMethod(_jniVideo.get(), mid);
79
+ }
80
+
81
+ void RNSkAndroidVideo::seek(double timestamp) {
82
+ JNIEnv *env = facebook::jni::Environment::current();
83
+ jclass cls = env->GetObjectClass(_jniVideo.get());
84
+ jmethodID mid = env->GetMethodID(cls, "seek", "(J)V");
85
+ if (!mid) {
86
+ RNSkLogger::logToConsole("seek method not found");
87
+ return;
88
+ }
89
+ env->CallVoidMethod(_jniVideo.get(), mid, static_cast<jlong>(timestamp));
90
+ }
91
+
92
+ } // namespace RNSkia
@@ -0,0 +1,36 @@
1
+ #pragma once
2
+
3
+ #include <string>
4
+
5
+ #include <fbjni/fbjni.h>
6
+ #include <jni.h>
7
+ #include <jsi/jsi.h>
8
+
9
+ #pragma clang diagnostic push
10
+ #pragma clang diagnostic ignored "-Wdocumentation"
11
+
12
+ #include "include/core/SkImage.h"
13
+
14
+ #pragma clang diagnostic pop
15
+
16
+ #include "RNSkVideo.h"
17
+
18
+ namespace RNSkia {
19
+
20
+ namespace jsi = facebook::jsi;
21
+ namespace jni = facebook::jni;
22
+
23
+ class RNSkAndroidVideo : public RNSkVideo {
24
+ private:
25
+ jni::global_ref<jobject> _jniVideo;
26
+
27
+ public:
28
+ explicit RNSkAndroidVideo(jni::global_ref<jobject> jniVideo);
29
+ ~RNSkAndroidVideo();
30
+ sk_sp<SkImage> nextImage(double *timeStamp = nullptr) override;
31
+ double duration() override;
32
+ double framerate() override;
33
+ void seek(double timestamp) override;
34
+ };
35
+
36
+ } // namespace RNSkia
@@ -16,16 +16,13 @@ namespace RNSkia {
16
16
  thread_local SkiaOpenGLContext ThreadContextHolder::ThreadSkiaOpenGLContext;
17
17
 
18
18
  sk_sp<SkImage>
19
- SkiaOpenGLSurfaceFactory::makeImageFromHardwareBuffer(void *buffer) {
19
+ SkiaOpenGLSurfaceFactory::makeImageFromHardwareBuffer(void *buffer,
20
+ bool requireKnownFormat) {
20
21
  #if __ANDROID_API__ >= 26
21
22
  // Setup OpenGL and Skia:
22
23
  if (!SkiaOpenGLHelper::createSkiaDirectContextIfNecessary(
23
- &ThreadContextHolder::ThreadSkiaOpenGLContext)) {
24
-
25
- RNSkLogger::logToConsole(
26
- "Could not create Skia Surface from native window / surface. "
27
- "Failed creating Skia Direct Context");
28
- return nullptr;
24
+ &ThreadContextHolder::ThreadSkiaOpenGLContext)) [[unlikely]] {
25
+ throw std::runtime_error("Failed to create Skia Context for this Thread!");
29
26
  }
30
27
  const AHardwareBuffer *hardwareBuffer =
31
28
  static_cast<AHardwareBuffer *>(buffer);
@@ -35,23 +32,46 @@ SkiaOpenGLSurfaceFactory::makeImageFromHardwareBuffer(void *buffer) {
35
32
 
36
33
  AHardwareBuffer_Desc description;
37
34
  AHardwareBuffer_describe(hardwareBuffer, &description);
38
- if (description.format != AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM) {
39
- throw std::runtime_error("AHardwareBuffer has unknown format (" +
40
- std::to_string(description.format) +
41
- ") - cannot convert to SkImage!");
35
+ GrBackendFormat format;
36
+ switch (description.format) {
37
+ // TODO: find out if we can detect, which graphic buffers support
38
+ // GR_GL_TEXTURE_2D
39
+ case AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM:
40
+ format = GrBackendFormats::MakeGL(GR_GL_RGBA8, GR_GL_TEXTURE_EXTERNAL);
41
+ case AHARDWAREBUFFER_FORMAT_R16G16B16A16_FLOAT:
42
+ format = GrBackendFormats::MakeGL(GR_GL_RGBA16F, GR_GL_TEXTURE_EXTERNAL);
43
+ case AHARDWAREBUFFER_FORMAT_R5G6B5_UNORM:
44
+ format = GrBackendFormats::MakeGL(GR_GL_RGB565, GR_GL_TEXTURE_EXTERNAL);
45
+ case AHARDWAREBUFFER_FORMAT_R10G10B10A2_UNORM:
46
+ format = GrBackendFormats::MakeGL(GR_GL_RGB10_A2, GR_GL_TEXTURE_EXTERNAL);
47
+ case AHARDWAREBUFFER_FORMAT_R8G8B8_UNORM:
48
+ format = GrBackendFormats::MakeGL(GR_GL_RGB8, GR_GL_TEXTURE_EXTERNAL);
49
+ #if __ANDROID_API__ >= 33
50
+ case AHARDWAREBUFFER_FORMAT_R8_UNORM:
51
+ format = GrBackendFormats::MakeGL(GR_GL_R8, GR_GL_TEXTURE_EXTERNAL);
52
+ #endif
53
+ default:
54
+ if (requireKnownFormat) {
55
+ format = GrBackendFormat();
56
+ } else {
57
+ format = GrBackendFormats::MakeGL(GR_GL_RGBA8, GR_GL_TEXTURE_EXTERNAL);
58
+ }
42
59
  }
43
- GrBackendFormat format =
44
- GrBackendFormats::MakeGL(GR_GL_RGBA8, GR_GL_TEXTURE_EXTERNAL);
45
60
 
46
61
  auto backendTex = MakeGLBackendTexture(
47
62
  ThreadContextHolder::ThreadSkiaOpenGLContext.directContext.get(),
48
63
  const_cast<AHardwareBuffer *>(hardwareBuffer), description.width,
49
64
  description.height, &deleteImageProc, &updateImageProc, &deleteImageCtx,
50
65
  false, format, false);
66
+ if (!backendTex.isValid()) {
67
+ RNSkLogger::logToConsole(
68
+ "Failed to convert HardwareBuffer to OpenGL Texture!");
69
+ return nullptr;
70
+ }
51
71
  sk_sp<SkImage> image = SkImages::BorrowTextureFrom(
52
72
  ThreadContextHolder::ThreadSkiaOpenGLContext.directContext.get(),
53
73
  backendTex, kTopLeft_GrSurfaceOrigin, kRGBA_8888_SkColorType,
54
- kOpaque_SkAlphaType, nullptr);
74
+ kOpaque_SkAlphaType, nullptr, deleteImageProc, deleteImageCtx);
55
75
  return image;
56
76
  #else
57
77
  throw std::runtime_error(
@@ -149,7 +149,8 @@ public:
149
149
  */
150
150
  static sk_sp<SkSurface> makeOffscreenSurface(int width, int height);
151
151
 
152
- static sk_sp<SkImage> makeImageFromHardwareBuffer(void *buffer);
152
+ static sk_sp<SkImage>
153
+ makeImageFromHardwareBuffer(void *buffer, bool requireKnownFormat = false);
153
154
 
154
155
  /**
155
156
  * Creates a windowed Skia Surface holder.
@@ -38,6 +38,11 @@ public class PlatformContext {
38
38
  mHybridData = initHybrid(reactContext.getResources().getDisplayMetrics().density);
39
39
  }
40
40
 
41
+ @DoNotStrip
42
+ public Object createVideo(String url) {
43
+ return new RNSkVideo(mContext, url);
44
+ }
45
+
41
46
  private byte[] getStreamAsBytes(InputStream is) throws IOException {
42
47
  ByteArrayOutputStream buffer = new ByteArrayOutputStream();
43
48
  int nRead;
@@ -0,0 +1,185 @@
1
+ package com.shopify.reactnative.skia;
2
+
3
+ import android.content.Context;
4
+ import android.graphics.ImageFormat;
5
+ import android.hardware.HardwareBuffer;
6
+ import android.media.Image;
7
+ import android.media.ImageReader;
8
+ import android.media.MediaCodec;
9
+ import android.media.MediaExtractor;
10
+ import android.media.MediaFormat;
11
+ import android.net.Uri;
12
+ import android.os.Build;
13
+ import android.view.Surface;
14
+
15
+ import androidx.annotation.RequiresApi;
16
+
17
+ import com.facebook.jni.annotations.DoNotStrip;
18
+
19
+ import java.io.IOException;
20
+ import java.nio.ByteBuffer;
21
+
22
+ public class RNSkVideo {
23
+ private final Uri uri;
24
+ private final Context context;
25
+
26
+ private MediaExtractor extractor;
27
+ private MediaCodec decoder;
28
+ private ImageReader imageReader;
29
+ private Surface outputSurface;
30
+ private double durationMs;
31
+ private double frameRate;
32
+
33
+ RNSkVideo(Context context, String localUri) {
34
+ this.uri = Uri.parse(localUri);
35
+ this.context = context;
36
+ this.initializeReader();
37
+ }
38
+
39
+ private void initializeReader() {
40
+ extractor = new MediaExtractor();
41
+ try {
42
+ extractor.setDataSource(context, this.uri, null);
43
+ int trackIndex = selectVideoTrack(extractor);
44
+ if (trackIndex < 0) {
45
+ throw new RuntimeException("No video track found in " + this.uri);
46
+ }
47
+ extractor.selectTrack(trackIndex);
48
+ MediaFormat format = extractor.getTrackFormat(trackIndex);
49
+ // Retrieve and store video properties
50
+ if (format.containsKey(MediaFormat.KEY_DURATION)) {
51
+ durationMs = format.getLong(MediaFormat.KEY_DURATION) / 1000; // Convert microseconds to milliseconds
52
+ }
53
+ if (format.containsKey(MediaFormat.KEY_FRAME_RATE)) {
54
+ frameRate = format.getInteger(MediaFormat.KEY_FRAME_RATE);
55
+ }
56
+ int width = format.getInteger(MediaFormat.KEY_WIDTH);
57
+ int height = format.getInteger(MediaFormat.KEY_HEIGHT);
58
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
59
+ imageReader = ImageReader.newInstance(
60
+ width,
61
+ height,
62
+ ImageFormat.PRIVATE,
63
+ 2,
64
+ HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE
65
+ );
66
+ } else {
67
+ imageReader = ImageReader.newInstance(width, height,
68
+ ImageFormat.PRIVATE, 2);
69
+ }
70
+ outputSurface = imageReader.getSurface();
71
+
72
+ // Create a decoder for the format
73
+ String mime = format.getString(MediaFormat.KEY_MIME);
74
+ decoder = MediaCodec.createDecoderByType(mime);
75
+
76
+ // Note: Use an output Surface for rendering if necessary, otherwise handle buffers
77
+ decoder.configure(format, outputSurface, null, 0);
78
+ decoder.start();
79
+ } catch (IOException e) {
80
+ throw new RuntimeException("Failed to initialize extractor or decoder", e);
81
+ }
82
+ }
83
+
84
+ @DoNotStrip
85
+ public double getDuration() {
86
+ return durationMs;
87
+ }
88
+
89
+ @DoNotStrip
90
+
91
+ public double getFrameRate() {
92
+ return frameRate;
93
+ }
94
+
95
+ @DoNotStrip
96
+ public HardwareBuffer nextImage() {
97
+ if (!decoderOutputAvailable()) {
98
+ decodeFrame();
99
+ }
100
+
101
+ Image image = imageReader.acquireLatestImage();
102
+ if (image != null) {
103
+ HardwareBuffer hardwareBuffer = image.getHardwareBuffer();
104
+ image.close(); // Make sure to close the Image to free up the buffer
105
+ return hardwareBuffer;
106
+ }
107
+ return null;
108
+ }
109
+
110
+ @DoNotStrip
111
+ public void seek(long timestamp) {
112
+ // Seek to the closest sync frame at or before the specified time
113
+ extractor.seekTo(timestamp * 1000, MediaExtractor.SEEK_TO_PREVIOUS_SYNC);
114
+ // Flush the codec to reset internal state and buffers
115
+ if (decoder != null) {
116
+ decoder.flush();
117
+ }
118
+ }
119
+
120
+ private int selectVideoTrack(MediaExtractor extractor) {
121
+ int numTracks = extractor.getTrackCount();
122
+ for (int i = 0; i < numTracks; i++) {
123
+ MediaFormat format = extractor.getTrackFormat(i);
124
+ String mime = format.getString(MediaFormat.KEY_MIME);
125
+ if (mime.startsWith("video/")) {
126
+ return i;
127
+ }
128
+ }
129
+ return -1;
130
+ }
131
+
132
+ private boolean decoderOutputAvailable() {
133
+ MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
134
+ int outputBufferId = decoder.dequeueOutputBuffer(info, 0);
135
+ if (outputBufferId >= 0) {
136
+ // If a buffer is available, release it immediately back since we are just checking
137
+ decoder.releaseOutputBuffer(outputBufferId, true);
138
+ return true;
139
+ }
140
+ return false;
141
+ }
142
+
143
+ private void decodeFrame() {
144
+ MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
145
+ long timeoutUs = 10000;
146
+ boolean isEOS = false;
147
+
148
+ int inputBufferId = decoder.dequeueInputBuffer(timeoutUs);
149
+ if (inputBufferId >= 0) {
150
+ ByteBuffer inputBuffer = decoder.getInputBuffer(inputBufferId);
151
+ int sampleSize = extractor.readSampleData(inputBuffer, 0);
152
+ if (sampleSize < 0) {
153
+ // End of stream, make sure to send this information to the decoder
154
+ decoder.queueInputBuffer(inputBufferId, 0, 0, 0L, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
155
+ isEOS = true;
156
+ } else {
157
+ long presentationTimeUs = extractor.getSampleTime();
158
+ decoder.queueInputBuffer(inputBufferId, 0, sampleSize, presentationTimeUs, 0);
159
+ extractor.advance();
160
+ }
161
+ }
162
+
163
+ int outputBufferId = decoder.dequeueOutputBuffer(info, timeoutUs);
164
+ if (outputBufferId >= 0) {
165
+ // If we have a valid buffer, release it to make it available to the ImageReader's surface
166
+ decoder.releaseOutputBuffer(outputBufferId, true);
167
+
168
+ if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
169
+ isEOS = true;
170
+ }
171
+ }
172
+ }
173
+
174
+ public void release() {
175
+ if (decoder != null) {
176
+ decoder.stop();
177
+ decoder.release();
178
+ decoder = null;
179
+ }
180
+ if (extractor != null) {
181
+ extractor.release();
182
+ extractor = null;
183
+ }
184
+ }
185
+ }
@@ -50,6 +50,7 @@
50
50
  #include "JsiSkTypefaceFactory.h"
51
51
  #include "JsiSkTypefaceFontProviderFactory.h"
52
52
  #include "JsiSkVertices.h"
53
+ #include "JsiVideo.h"
53
54
 
54
55
  namespace RNSkia {
55
56
 
@@ -67,6 +68,7 @@ public:
67
68
  // We create the system font manager eagerly since it has proven to be too
68
69
  // slow to do it on demand
69
70
  JsiSkFontMgrFactory::getFontMgr(getContext());
71
+ installFunction("Video", JsiVideo::createCtor(context));
70
72
  installFunction("Font", JsiSkFont::createCtor(context));
71
73
  installFunction("Paint", JsiSkPaint::createCtor(context));
72
74
  installFunction("RSXform", JsiSkRSXform::createCtor(context));
@@ -29,11 +29,11 @@ public:
29
29
 
30
30
  JSI_HOST_FUNCTION(MakeImageFromNativeBuffer) {
31
31
  jsi::BigInt pointer = arguments[0].asBigInt(runtime);
32
- const uintptr_t platformBufferPointer = pointer.asUint64(runtime);
33
- void *rawPointer = reinterpret_cast<void *>(platformBufferPointer);
32
+ const uintptr_t nativeBufferPointer = pointer.asUint64(runtime);
33
+ void *rawPointer = reinterpret_cast<void *>(nativeBufferPointer);
34
34
  auto image = getContext()->makeImageFromNativeBuffer(rawPointer);
35
35
  if (image == nullptr) {
36
- throw std::runtime_error("Failed to convert PlatformBuffer to SkImage!");
36
+ throw std::runtime_error("Failed to convert NativeBuffer to SkImage!");
37
37
  }
38
38
  return jsi::Object::createFromHostObject(
39
39
  runtime, std::make_shared<JsiSkImage>(getContext(), std::move(image)));
@@ -0,0 +1,87 @@
1
+ #pragma once
2
+
3
+ #include <memory>
4
+ #include <numeric>
5
+ #include <utility>
6
+ #include <vector>
7
+
8
+ #include "JsiSkHostObjects.h"
9
+ #include "RNSkLog.h"
10
+ #include <jsi/jsi.h>
11
+
12
+ #include "JsiSkPaint.h"
13
+ #include "JsiSkPoint.h"
14
+ #include "JsiSkRect.h"
15
+ #include "JsiSkTypeface.h"
16
+
17
+ #include "RNSkVideo.h"
18
+
19
+ #pragma clang diagnostic push
20
+ #pragma clang diagnostic ignored "-Wdocumentation"
21
+
22
+ #include "include/core/SkFont.h"
23
+ #include "include/core/SkFontMetrics.h"
24
+
25
+ #pragma clang diagnostic pop
26
+
27
+ namespace RNSkia {
28
+
29
+ namespace jsi = facebook::jsi;
30
+
31
+ class JsiVideo : public JsiSkWrappingSharedPtrHostObject<RNSkVideo> {
32
+ public:
33
+ EXPORT_JSI_API_TYPENAME(JsiVideo, Video)
34
+
35
+ JSI_HOST_FUNCTION(nextImage) {
36
+ double timestamp = 0;
37
+ auto video = getObject();
38
+ auto image = video->nextImage(&timestamp);
39
+ if (!image) {
40
+ return jsi::Value::null();
41
+ }
42
+ return jsi::Object::createFromHostObject(
43
+ runtime, std::make_shared<JsiSkImage>(getContext(), std::move(image)));
44
+ }
45
+
46
+ JSI_HOST_FUNCTION(duration) { return getObject()->duration(); }
47
+
48
+ JSI_HOST_FUNCTION(framerate) { return getObject()->framerate(); }
49
+
50
+ JSI_HOST_FUNCTION(seek) {
51
+ double timestamp = arguments[0].asNumber();
52
+ getObject()->seek(timestamp);
53
+ return jsi::Value::undefined();
54
+ }
55
+
56
+ JSI_EXPORT_FUNCTIONS(JSI_EXPORT_FUNC(JsiVideo, nextImage),
57
+ JSI_EXPORT_FUNC(JsiVideo, duration),
58
+ JSI_EXPORT_FUNC(JsiVideo, framerate),
59
+ JSI_EXPORT_FUNC(JsiVideo, seek),
60
+ JSI_EXPORT_FUNC(JsiVideo, dispose))
61
+
62
+ JsiVideo(std::shared_ptr<RNSkPlatformContext> context,
63
+ std::shared_ptr<RNSkVideo> video)
64
+ : JsiSkWrappingSharedPtrHostObject(std::move(context), std::move(video)) {
65
+ }
66
+
67
+ /**
68
+ * Creates the function for construction a new instance of the SkFont
69
+ * wrapper
70
+ * @param context Platform context
71
+ * @return A function for creating a new host object wrapper for the SkFont
72
+ * class
73
+ */
74
+ static const jsi::HostFunctionType
75
+ createCtor(std::shared_ptr<RNSkPlatformContext> context) {
76
+ return JSI_HOST_FUNCTION_LAMBDA {
77
+ auto url = arguments[0].asString(runtime).utf8(runtime);
78
+ auto video = context->createVideo(url);
79
+ // Return the newly constructed object
80
+ return jsi::Object::createFromHostObject(
81
+ runtime,
82
+ std::make_shared<JsiVideo>(std::move(context), std::move(video)));
83
+ };
84
+ }
85
+ };
86
+
87
+ } // namespace RNSkia
@@ -10,6 +10,7 @@
10
10
  #include <utility>
11
11
 
12
12
  #include "RNSkDispatchQueue.h"
13
+ #include "RNSkVideo.h"
13
14
 
14
15
  #pragma clang diagnostic push
15
16
  #pragma clang diagnostic ignored "-Wdocumentation"
@@ -137,7 +138,7 @@ public:
137
138
  * Creates an image from a native buffer.
138
139
  * - On iOS, this is a `CVPixelBufferRef`
139
140
  * - On Android, this is a `AHardwareBuffer*`
140
- * @param buffer The native platform buffer.
141
+ * @param buffer The native buffer.
141
142
  * @return sk_sp<SkImage>
142
143
  */
143
144
  virtual sk_sp<SkImage> makeImageFromNativeBuffer(void *buffer) = 0;
@@ -146,6 +147,8 @@ public:
146
147
 
147
148
  virtual uint64_t makeNativeBuffer(sk_sp<SkImage> image) = 0;
148
149
 
150
+ virtual std::shared_ptr<RNSkVideo> createVideo(const std::string &url) = 0;
151
+
149
152
  /**
150
153
  * Return the Platform specific font manager
151
154
  */
@@ -0,0 +1,23 @@
1
+ #pragma once
2
+
3
+ #include <string>
4
+
5
+ #pragma clang diagnostic push
6
+ #pragma clang diagnostic ignored "-Wdocumentation"
7
+
8
+ #include "include/core/SkImage.h"
9
+
10
+ #pragma clang diagnostic pop
11
+
12
+ namespace RNSkia {
13
+
14
+ class RNSkVideo {
15
+ public:
16
+ virtual ~RNSkVideo() = default;
17
+ virtual sk_sp<SkImage> nextImage(double *timeStamp = nullptr) = 0;
18
+ virtual double duration() = 0;
19
+ virtual double framerate() = 0;
20
+ virtual void seek(double timestamp) = 0;
21
+ };
22
+
23
+ } // namespace RNSkia
@@ -65,6 +65,8 @@ public:
65
65
 
66
66
  void releaseNativeBuffer(uint64_t pointer) override;
67
67
 
68
+ std::shared_ptr<RNSkVideo> createVideo(const std::string &url) override;
69
+
68
70
  virtual void performStreamOperation(
69
71
  const std::string &sourceUri,
70
72
  const std::function<void(std::unique_ptr<SkStreamAsset>)> &op) override;