@shopify/react-native-skia 1.2.2 → 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
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;