@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.
- package/android/CMakeLists.txt +1 -0
- package/android/cpp/jni/JniPlatformContext.cpp +23 -0
- package/android/cpp/jni/include/JniPlatformContext.h +2 -0
- package/android/cpp/rnskia-android/RNSkAndroidPlatformContext.h +6 -0
- package/android/cpp/rnskia-android/RNSkAndroidVideo.cpp +92 -0
- package/android/cpp/rnskia-android/RNSkAndroidVideo.h +36 -0
- package/android/cpp/rnskia-android/SkiaOpenGLSurfaceFactory.cpp +34 -14
- package/android/cpp/rnskia-android/SkiaOpenGLSurfaceFactory.h +2 -1
- package/android/src/main/java/com/shopify/reactnative/skia/PlatformContext.java +5 -0
- package/android/src/main/java/com/shopify/reactnative/skia/RNSkVideo.java +185 -0
- package/cpp/api/JsiSkApi.h +2 -0
- package/cpp/api/JsiSkImageFactory.h +3 -3
- package/cpp/api/JsiVideo.h +87 -0
- package/cpp/rnskia/RNSkPlatformContext.h +4 -1
- package/cpp/rnskia/RNSkVideo.h +23 -0
- package/ios/RNSkia-iOS/RNSkiOSPlatformContext.h +2 -0
- package/ios/RNSkia-iOS/RNSkiOSPlatformContext.mm +7 -1
- package/ios/RNSkia-iOS/RNSkiOSVideo.h +40 -0
- package/ios/RNSkia-iOS/RNSkiOSVideo.mm +119 -0
- package/ios/RNSkia-iOS/RNSkiOSView.mm +37 -0
- package/ios/RNSkia-iOS/SkiaMetalSurfaceFactory.mm +2 -2
- package/lib/commonjs/external/reanimated/index.d.ts +1 -0
- package/lib/commonjs/external/reanimated/index.js +11 -0
- package/lib/commonjs/external/reanimated/index.js.map +1 -1
- package/lib/commonjs/external/reanimated/useVideo.d.ts +10 -0
- package/lib/commonjs/external/reanimated/useVideo.js +93 -0
- package/lib/commonjs/external/reanimated/useVideo.js.map +1 -0
- package/lib/commonjs/mock/index.js +2 -1
- package/lib/commonjs/mock/index.js.map +1 -1
- package/lib/commonjs/skia/types/Image/ImageFactory.d.ts +3 -3
- package/lib/commonjs/skia/types/Image/ImageFactory.js.map +1 -1
- package/lib/commonjs/skia/types/NativeBuffer/NativeBufferFactory.d.ts +2 -2
- package/lib/commonjs/skia/types/NativeBuffer/NativeBufferFactory.js.map +1 -1
- package/lib/commonjs/skia/types/Skia.d.ts +2 -0
- package/lib/commonjs/skia/types/Skia.js.map +1 -1
- package/lib/commonjs/skia/types/Video/Video.d.ts +8 -0
- package/lib/commonjs/skia/types/Video/Video.js +6 -0
- package/lib/commonjs/skia/types/Video/Video.js.map +1 -0
- package/lib/commonjs/skia/types/Video/index.d.ts +1 -0
- package/lib/commonjs/skia/types/Video/index.js +17 -0
- package/lib/commonjs/skia/types/Video/index.js.map +1 -0
- package/lib/commonjs/skia/web/JsiSkNativeBufferFactory.d.ts +1 -1
- package/lib/commonjs/skia/web/JsiSkNativeBufferFactory.js +1 -1
- package/lib/commonjs/skia/web/JsiSkNativeBufferFactory.js.map +1 -1
- package/lib/commonjs/skia/web/JsiSkia.js +4 -1
- package/lib/commonjs/skia/web/JsiSkia.js.map +1 -1
- package/lib/module/external/reanimated/index.d.ts +1 -0
- package/lib/module/external/reanimated/index.js +1 -0
- package/lib/module/external/reanimated/index.js.map +1 -1
- package/lib/module/external/reanimated/useVideo.d.ts +10 -0
- package/lib/module/external/reanimated/useVideo.js +85 -0
- package/lib/module/external/reanimated/useVideo.js.map +1 -0
- package/lib/module/mock/index.js +2 -1
- package/lib/module/mock/index.js.map +1 -1
- package/lib/module/skia/types/Image/ImageFactory.d.ts +3 -3
- package/lib/module/skia/types/Image/ImageFactory.js.map +1 -1
- package/lib/module/skia/types/NativeBuffer/NativeBufferFactory.d.ts +2 -2
- package/lib/module/skia/types/NativeBuffer/NativeBufferFactory.js.map +1 -1
- package/lib/module/skia/types/Skia.d.ts +2 -0
- package/lib/module/skia/types/Skia.js.map +1 -1
- package/lib/module/skia/types/Video/Video.d.ts +8 -0
- package/lib/module/skia/types/Video/Video.js +2 -0
- package/lib/module/skia/types/Video/Video.js.map +1 -0
- package/lib/module/skia/types/Video/index.d.ts +1 -0
- package/lib/module/skia/types/Video/index.js +2 -0
- package/lib/module/skia/types/Video/index.js.map +1 -0
- package/lib/module/skia/web/JsiSkNativeBufferFactory.d.ts +1 -1
- package/lib/module/skia/web/JsiSkNativeBufferFactory.js +1 -1
- package/lib/module/skia/web/JsiSkNativeBufferFactory.js.map +1 -1
- package/lib/module/skia/web/JsiSkia.js +4 -1
- package/lib/module/skia/web/JsiSkia.js.map +1 -1
- package/lib/typescript/src/external/reanimated/index.d.ts +1 -0
- package/lib/typescript/src/external/reanimated/useVideo.d.ts +10 -0
- package/lib/typescript/src/skia/types/Image/ImageFactory.d.ts +3 -3
- package/lib/typescript/src/skia/types/NativeBuffer/NativeBufferFactory.d.ts +2 -2
- package/lib/typescript/src/skia/types/Skia.d.ts +2 -0
- package/lib/typescript/src/skia/types/Video/Video.d.ts +8 -0
- package/lib/typescript/src/skia/types/Video/index.d.ts +1 -0
- package/lib/typescript/src/skia/web/JsiSkNativeBufferFactory.d.ts +1 -1
- package/package.json +1 -1
- package/scripts/setup-canvaskit.js +1 -1
- package/src/external/reanimated/index.ts +1 -0
- package/src/external/reanimated/useVideo.ts +115 -0
- package/src/mock/index.ts +1 -0
- package/src/skia/types/Image/ImageFactory.ts +3 -3
- package/src/skia/types/NativeBuffer/NativeBufferFactory.ts +2 -2
- package/src/skia/types/Skia.ts +2 -0
- package/src/skia/types/Video/Video.ts +9 -0
- package/src/skia/types/Video/index.ts +1 -0
- package/src/skia/web/JsiSkNativeBufferFactory.ts +1 -1
- package/src/skia/web/JsiSkia.ts +3 -0
package/android/CMakeLists.txt
CHANGED
@@ -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();
|
@@ -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
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
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>
|
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
|
+
}
|
package/cpp/api/JsiSkApi.h
CHANGED
@@ -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
|
33
|
-
void *rawPointer = reinterpret_cast<void *>(
|
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
|
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(×tamp);
|
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
|
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;
|