@shopify/react-native-skia
Advanced tools
Comparing version 1.3.2 to 1.3.3
@@ -8,8 +8,14 @@ "use strict"; | ||
var _typeddash = require("../../../renderer/typeddash"); | ||
const size = (width = 0, height = 0) => ({ | ||
width, | ||
height | ||
}); | ||
const size = (width = 0, height = 0) => { | ||
"worklet"; | ||
return { | ||
width, | ||
height | ||
}; | ||
}; | ||
exports.size = size; | ||
const rect2rect = (src, dst) => { | ||
"worklet"; | ||
const scaleX = dst.width / src.width; | ||
@@ -30,28 +36,2 @@ const scaleY = dst.height / src.height; | ||
exports.rect2rect = rect2rect; | ||
const fitRects = (fit, rect, { | ||
x, | ||
y, | ||
width, | ||
height | ||
}) => { | ||
const sizes = applyBoxFit(fit, { | ||
width: rect.width, | ||
height: rect.height | ||
}, { | ||
width, | ||
height | ||
}); | ||
const src = inscribe(sizes.src, rect); | ||
const dst = inscribe(sizes.dst, { | ||
x, | ||
y, | ||
width, | ||
height | ||
}); | ||
return { | ||
src, | ||
dst | ||
}; | ||
}; | ||
exports.fitRects = fitRects; | ||
const inscribe = ({ | ||
@@ -61,2 +41,4 @@ width, | ||
}, rect) => { | ||
"worklet"; | ||
const halfWidthDelta = (rect.width - width) / 2.0; | ||
@@ -72,2 +54,4 @@ const halfHeightDelta = (rect.height - height) / 2.0; | ||
const applyBoxFit = (fit, input, output) => { | ||
"worklet"; | ||
let src = size(), | ||
@@ -133,2 +117,30 @@ dst = size(); | ||
}; | ||
const fitRects = (fit, rect, { | ||
x, | ||
y, | ||
width, | ||
height | ||
}) => { | ||
"worklet"; | ||
const sizes = applyBoxFit(fit, { | ||
width: rect.width, | ||
height: rect.height | ||
}, { | ||
width, | ||
height | ||
}); | ||
const src = inscribe(sizes.src, rect); | ||
const dst = inscribe(sizes.dst, { | ||
x, | ||
y, | ||
width, | ||
height | ||
}); | ||
return { | ||
src, | ||
dst | ||
}; | ||
}; | ||
exports.fitRects = fitRects; | ||
//# sourceMappingURL=Fitting.js.map |
@@ -0,9 +1,21 @@ | ||
import type { SharedValue } from "react-native-reanimated"; | ||
import type { SkImage } from "../../skia/types"; | ||
import { type PlaybackOptions } from "./video"; | ||
type Animated<T> = SharedValue<T> | T; | ||
interface PlaybackOptions { | ||
looping: Animated<boolean>; | ||
paused: Animated<boolean>; | ||
seek: Animated<number | null>; | ||
volume: Animated<number>; | ||
} | ||
export declare const useVideo: (source: string | null, userOptions?: Partial<PlaybackOptions>) => { | ||
currentFrame: import("react-native-reanimated").SharedValue<SkImage | null>; | ||
currentTime: import("react-native-reanimated").SharedValue<number>; | ||
currentFrame: SharedValue<SkImage | null>; | ||
currentTime: SharedValue<number>; | ||
duration: number; | ||
framerate: number; | ||
rotationInDegrees: number; | ||
rotation: import("../../skia/types").VideoRotation; | ||
size: { | ||
width: number; | ||
height: number; | ||
}; | ||
}; | ||
export {}; |
@@ -9,11 +9,26 @@ "use strict"; | ||
var _Skia = require("../../skia/Skia"); | ||
var _Platform = require("../../Platform"); | ||
var _ReanimatedProxy = _interopRequireDefault(require("./ReanimatedProxy")); | ||
var _video = require("./video"); | ||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } | ||
const setFrame = (video, currentFrame) => { | ||
"worklet"; | ||
const img = video.nextImage(); | ||
if (img) { | ||
if (currentFrame.value) { | ||
currentFrame.value.dispose(); | ||
} | ||
if (_Platform.Platform.OS === "android") { | ||
currentFrame.value = img.makeNonTextureImage(); | ||
} else { | ||
currentFrame.value = img; | ||
} | ||
} | ||
}; | ||
const defaultOptions = { | ||
playbackSpeed: 1, | ||
looping: true, | ||
paused: false, | ||
seek: null, | ||
currentTime: 0 | ||
currentTime: 0, | ||
volume: 0 | ||
}; | ||
@@ -33,3 +48,3 @@ const useOption = value => { | ||
const useVideo = (source, userOptions) => { | ||
var _userOptions$paused, _userOptions$looping, _userOptions$seek, _userOptions$playback; | ||
var _userOptions$paused, _userOptions$looping, _userOptions$seek, _userOptions$volume; | ||
const video = (0, _react.useMemo)(() => source ? _Skia.Skia.Video(source) : null, [source]); | ||
@@ -39,3 +54,3 @@ const isPaused = useOption((_userOptions$paused = userOptions === null || userOptions === void 0 ? void 0 : userOptions.paused) !== null && _userOptions$paused !== void 0 ? _userOptions$paused : defaultOptions.paused); | ||
const seek = useOption((_userOptions$seek = userOptions === null || userOptions === void 0 ? void 0 : userOptions.seek) !== null && _userOptions$seek !== void 0 ? _userOptions$seek : defaultOptions.seek); | ||
const playbackSpeed = useOption((_userOptions$playback = userOptions === null || userOptions === void 0 ? void 0 : userOptions.playbackSpeed) !== null && _userOptions$playback !== void 0 ? _userOptions$playback : defaultOptions.playbackSpeed); | ||
const volume = useOption((_userOptions$volume = userOptions === null || userOptions === void 0 ? void 0 : userOptions.volume) !== null && _userOptions$volume !== void 0 ? _userOptions$volume : defaultOptions.volume); | ||
const currentFrame = _ReanimatedProxy.default.useSharedValue(null); | ||
@@ -52,12 +67,58 @@ const currentTime = _ReanimatedProxy.default.useSharedValue(0); | ||
}, [video]); | ||
const rotationInDegrees = (0, _react.useMemo)(() => { | ||
var _video$getRotationInD; | ||
return (_video$getRotationInD = video === null || video === void 0 ? void 0 : video.getRotationInDegrees()) !== null && _video$getRotationInD !== void 0 ? _video$getRotationInD : 0; | ||
const size = (0, _react.useMemo)(() => { | ||
var _video$size; | ||
return (_video$size = video === null || video === void 0 ? void 0 : video.size()) !== null && _video$size !== void 0 ? _video$size : { | ||
width: 0, | ||
height: 0 | ||
}; | ||
}, [video]); | ||
const rotation = (0, _react.useMemo)(() => { | ||
var _video$rotation; | ||
return (_video$rotation = video === null || video === void 0 ? void 0 : video.rotation()) !== null && _video$rotation !== void 0 ? _video$rotation : 0; | ||
}, [video]); | ||
const frameDuration = 1000 / framerate; | ||
const currentFrameDuration = Math.floor(frameDuration); | ||
_ReanimatedProxy.default.useAnimatedReaction(() => isPaused.value, paused => { | ||
if (paused) { | ||
video === null || video === void 0 || video.pause(); | ||
} else { | ||
lastTimestamp.value = -1; | ||
video === null || video === void 0 || video.play(); | ||
} | ||
}); | ||
_ReanimatedProxy.default.useAnimatedReaction(() => seek.value, value => { | ||
if (value !== null) { | ||
video === null || video === void 0 || video.seek(value); | ||
currentTime.value = value; | ||
seek.value = null; | ||
} | ||
}); | ||
_ReanimatedProxy.default.useAnimatedReaction(() => volume.value, value => { | ||
video === null || video === void 0 || video.setVolume(value); | ||
}); | ||
_ReanimatedProxy.default.useFrameCallback(frameInfo => { | ||
(0, _video.processVideoState)(video, duration, framerate, frameInfo.timestamp, { | ||
paused: isPaused.value, | ||
looping: looping.value, | ||
playbackSpeed: playbackSpeed.value | ||
}, currentTime, currentFrame, lastTimestamp, seek); | ||
"worklet"; | ||
if (!video) { | ||
return; | ||
} | ||
if (isPaused.value) { | ||
return; | ||
} | ||
const currentTimestamp = frameInfo.timestamp; | ||
if (lastTimestamp.value === -1) { | ||
lastTimestamp.value = currentTimestamp; | ||
} | ||
const delta = currentTimestamp - lastTimestamp.value; | ||
const isOver = currentTime.value + delta > duration; | ||
if (isOver && looping.value) { | ||
seek.value = 0; | ||
currentTime.value = seek.value; | ||
lastTimestamp.value = currentTimestamp; | ||
} | ||
if (delta >= currentFrameDuration && !isOver) { | ||
setFrame(video, currentFrame); | ||
currentTime.value += delta; | ||
lastTimestamp.value = currentTimestamp; | ||
} | ||
}); | ||
@@ -75,3 +136,4 @@ (0, _react.useEffect)(() => { | ||
framerate, | ||
rotationInDegrees | ||
rotation, | ||
size | ||
}; | ||
@@ -78,0 +140,0 @@ }; |
import type { ReactNode } from "react"; | ||
import React from "react"; | ||
import type { Fit } from "../../../dom/nodes"; | ||
import type { SkRect } from "../../../skia/types"; | ||
import type { SkRect, Transforms3d } from "../../../skia/types"; | ||
interface FitProps { | ||
@@ -11,12 +11,4 @@ fit?: Fit; | ||
} | ||
export declare const fitbox: (fit: Fit, src: SkRect, dst: SkRect) => [{ | ||
translateX: number; | ||
}, { | ||
translateY: number; | ||
}, { | ||
scaleX: number; | ||
}, { | ||
scaleY: number; | ||
}]; | ||
export declare const fitbox: (fit: Fit, src: SkRect, dst: SkRect, rotation?: 0 | 90 | 180 | 270) => Transforms3d; | ||
export declare const FitBox: ({ fit, src, dst, children }: FitProps) => React.JSX.Element; | ||
export {}; |
@@ -12,5 +12,34 @@ "use strict"; | ||
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; } | ||
const fitbox = (fit, src, dst) => { | ||
const rects = (0, _nodes.fitRects)(fit, src, dst); | ||
return (0, _nodes.rect2rect)(rects.src, rects.dst); | ||
const fitbox = (fit, src, dst, rotation = 0) => { | ||
"worklet"; | ||
const rects = (0, _nodes.fitRects)(fit, rotation === 90 || rotation === 270 ? { | ||
x: 0, | ||
y: 0, | ||
width: src.height, | ||
height: src.width | ||
} : src, dst); | ||
const result = (0, _nodes.rect2rect)(rects.src, rects.dst); | ||
if (rotation === 90) { | ||
return [...result, { | ||
translate: [src.height, 0] | ||
}, { | ||
rotate: Math.PI / 2 | ||
}]; | ||
} | ||
if (rotation === 180) { | ||
return [...result, { | ||
translate: [src.width, src.height] | ||
}, { | ||
rotate: Math.PI | ||
}]; | ||
} | ||
if (rotation === 270) { | ||
return [...result, { | ||
translate: [0, src.width] | ||
}, { | ||
rotate: -Math.PI / 2 | ||
}]; | ||
} | ||
return result; | ||
}; | ||
@@ -17,0 +46,0 @@ exports.fitbox = fitbox; |
@@ -9,4 +9,8 @@ "use strict"; | ||
var _types = require("../types"); | ||
const processTransform2d = transforms => (0, _types.processTransform)(_Skia.Skia.Matrix(), transforms); | ||
const processTransform2d = transforms => { | ||
"worklet"; | ||
return (0, _types.processTransform)(_Skia.Skia.Matrix(), transforms); | ||
}; | ||
exports.processTransform2d = processTransform2d; | ||
//# sourceMappingURL=Matrix.js.map |
@@ -11,2 +11,4 @@ "use strict"; | ||
const processTransform = (m, transforms) => { | ||
"worklet"; | ||
const m3 = (0, _Matrix.processTransform3d)(transforms); | ||
@@ -13,0 +15,0 @@ m.concat(m3); |
import type { SkImage } from "../Image"; | ||
import type { SkJSIInstance } from "../JsiInstance"; | ||
export type VideoRotation = 0 | 90 | 180 | 270; | ||
export interface Video extends SkJSIInstance<"Video"> { | ||
@@ -8,3 +9,10 @@ duration(): number; | ||
seek(time: number): void; | ||
getRotationInDegrees(): number; | ||
rotation(): VideoRotation; | ||
size(): { | ||
width: number; | ||
height: number; | ||
}; | ||
pause(): void; | ||
play(): void; | ||
setVolume(volume: number): void; | ||
} |
import { exhaustiveCheck } from "../../../renderer/typeddash"; | ||
export const size = (width = 0, height = 0) => ({ | ||
width, | ||
height | ||
}); | ||
export const size = (width = 0, height = 0) => { | ||
"worklet"; | ||
return { | ||
width, | ||
height | ||
}; | ||
}; | ||
export const rect2rect = (src, dst) => { | ||
"worklet"; | ||
const scaleX = dst.width / src.width; | ||
@@ -21,27 +27,2 @@ const scaleY = dst.height / src.height; | ||
}; | ||
export const fitRects = (fit, rect, { | ||
x, | ||
y, | ||
width, | ||
height | ||
}) => { | ||
const sizes = applyBoxFit(fit, { | ||
width: rect.width, | ||
height: rect.height | ||
}, { | ||
width, | ||
height | ||
}); | ||
const src = inscribe(sizes.src, rect); | ||
const dst = inscribe(sizes.dst, { | ||
x, | ||
y, | ||
width, | ||
height | ||
}); | ||
return { | ||
src, | ||
dst | ||
}; | ||
}; | ||
const inscribe = ({ | ||
@@ -51,2 +32,4 @@ width, | ||
}, rect) => { | ||
"worklet"; | ||
const halfWidthDelta = (rect.width - width) / 2.0; | ||
@@ -62,2 +45,4 @@ const halfHeightDelta = (rect.height - height) / 2.0; | ||
const applyBoxFit = (fit, input, output) => { | ||
"worklet"; | ||
let src = size(), | ||
@@ -123,2 +108,29 @@ dst = size(); | ||
}; | ||
export const fitRects = (fit, rect, { | ||
x, | ||
y, | ||
width, | ||
height | ||
}) => { | ||
"worklet"; | ||
const sizes = applyBoxFit(fit, { | ||
width: rect.width, | ||
height: rect.height | ||
}, { | ||
width, | ||
height | ||
}); | ||
const src = inscribe(sizes.src, rect); | ||
const dst = inscribe(sizes.dst, { | ||
x, | ||
y, | ||
width, | ||
height | ||
}); | ||
return { | ||
src, | ||
dst | ||
}; | ||
}; | ||
//# sourceMappingURL=Fitting.js.map |
@@ -0,9 +1,21 @@ | ||
import type { SharedValue } from "react-native-reanimated"; | ||
import type { SkImage } from "../../skia/types"; | ||
import { type PlaybackOptions } from "./video"; | ||
type Animated<T> = SharedValue<T> | T; | ||
interface PlaybackOptions { | ||
looping: Animated<boolean>; | ||
paused: Animated<boolean>; | ||
seek: Animated<number | null>; | ||
volume: Animated<number>; | ||
} | ||
export declare const useVideo: (source: string | null, userOptions?: Partial<PlaybackOptions>) => { | ||
currentFrame: import("react-native-reanimated").SharedValue<SkImage | null>; | ||
currentTime: import("react-native-reanimated").SharedValue<number>; | ||
currentFrame: SharedValue<SkImage | null>; | ||
currentTime: SharedValue<number>; | ||
duration: number; | ||
framerate: number; | ||
rotationInDegrees: number; | ||
rotation: import("../../skia/types").VideoRotation; | ||
size: { | ||
width: number; | ||
height: number; | ||
}; | ||
}; | ||
export {}; |
import { useEffect, useMemo } from "react"; | ||
import { Skia } from "../../skia/Skia"; | ||
import { Platform } from "../../Platform"; | ||
import Rea from "./ReanimatedProxy"; | ||
import { processVideoState } from "./video"; | ||
const setFrame = (video, currentFrame) => { | ||
"worklet"; | ||
const img = video.nextImage(); | ||
if (img) { | ||
if (currentFrame.value) { | ||
currentFrame.value.dispose(); | ||
} | ||
if (Platform.OS === "android") { | ||
currentFrame.value = img.makeNonTextureImage(); | ||
} else { | ||
currentFrame.value = img; | ||
} | ||
} | ||
}; | ||
const defaultOptions = { | ||
playbackSpeed: 1, | ||
looping: true, | ||
paused: false, | ||
seek: null, | ||
currentTime: 0 | ||
currentTime: 0, | ||
volume: 0 | ||
}; | ||
@@ -25,3 +40,3 @@ const useOption = value => { | ||
export const useVideo = (source, userOptions) => { | ||
var _userOptions$paused, _userOptions$looping, _userOptions$seek, _userOptions$playback; | ||
var _userOptions$paused, _userOptions$looping, _userOptions$seek, _userOptions$volume; | ||
const video = useMemo(() => source ? Skia.Video(source) : null, [source]); | ||
@@ -31,3 +46,3 @@ const isPaused = useOption((_userOptions$paused = userOptions === null || userOptions === void 0 ? void 0 : userOptions.paused) !== null && _userOptions$paused !== void 0 ? _userOptions$paused : defaultOptions.paused); | ||
const seek = useOption((_userOptions$seek = userOptions === null || userOptions === void 0 ? void 0 : userOptions.seek) !== null && _userOptions$seek !== void 0 ? _userOptions$seek : defaultOptions.seek); | ||
const playbackSpeed = useOption((_userOptions$playback = userOptions === null || userOptions === void 0 ? void 0 : userOptions.playbackSpeed) !== null && _userOptions$playback !== void 0 ? _userOptions$playback : defaultOptions.playbackSpeed); | ||
const volume = useOption((_userOptions$volume = userOptions === null || userOptions === void 0 ? void 0 : userOptions.volume) !== null && _userOptions$volume !== void 0 ? _userOptions$volume : defaultOptions.volume); | ||
const currentFrame = Rea.useSharedValue(null); | ||
@@ -44,12 +59,58 @@ const currentTime = Rea.useSharedValue(0); | ||
}, [video]); | ||
const rotationInDegrees = useMemo(() => { | ||
var _video$getRotationInD; | ||
return (_video$getRotationInD = video === null || video === void 0 ? void 0 : video.getRotationInDegrees()) !== null && _video$getRotationInD !== void 0 ? _video$getRotationInD : 0; | ||
const size = useMemo(() => { | ||
var _video$size; | ||
return (_video$size = video === null || video === void 0 ? void 0 : video.size()) !== null && _video$size !== void 0 ? _video$size : { | ||
width: 0, | ||
height: 0 | ||
}; | ||
}, [video]); | ||
const rotation = useMemo(() => { | ||
var _video$rotation; | ||
return (_video$rotation = video === null || video === void 0 ? void 0 : video.rotation()) !== null && _video$rotation !== void 0 ? _video$rotation : 0; | ||
}, [video]); | ||
const frameDuration = 1000 / framerate; | ||
const currentFrameDuration = Math.floor(frameDuration); | ||
Rea.useAnimatedReaction(() => isPaused.value, paused => { | ||
if (paused) { | ||
video === null || video === void 0 || video.pause(); | ||
} else { | ||
lastTimestamp.value = -1; | ||
video === null || video === void 0 || video.play(); | ||
} | ||
}); | ||
Rea.useAnimatedReaction(() => seek.value, value => { | ||
if (value !== null) { | ||
video === null || video === void 0 || video.seek(value); | ||
currentTime.value = value; | ||
seek.value = null; | ||
} | ||
}); | ||
Rea.useAnimatedReaction(() => volume.value, value => { | ||
video === null || video === void 0 || video.setVolume(value); | ||
}); | ||
Rea.useFrameCallback(frameInfo => { | ||
processVideoState(video, duration, framerate, frameInfo.timestamp, { | ||
paused: isPaused.value, | ||
looping: looping.value, | ||
playbackSpeed: playbackSpeed.value | ||
}, currentTime, currentFrame, lastTimestamp, seek); | ||
"worklet"; | ||
if (!video) { | ||
return; | ||
} | ||
if (isPaused.value) { | ||
return; | ||
} | ||
const currentTimestamp = frameInfo.timestamp; | ||
if (lastTimestamp.value === -1) { | ||
lastTimestamp.value = currentTimestamp; | ||
} | ||
const delta = currentTimestamp - lastTimestamp.value; | ||
const isOver = currentTime.value + delta > duration; | ||
if (isOver && looping.value) { | ||
seek.value = 0; | ||
currentTime.value = seek.value; | ||
lastTimestamp.value = currentTimestamp; | ||
} | ||
if (delta >= currentFrameDuration && !isOver) { | ||
setFrame(video, currentFrame); | ||
currentTime.value += delta; | ||
lastTimestamp.value = currentTimestamp; | ||
} | ||
}); | ||
@@ -67,5 +128,6 @@ useEffect(() => { | ||
framerate, | ||
rotationInDegrees | ||
rotation, | ||
size | ||
}; | ||
}; | ||
//# sourceMappingURL=useVideo.js.map |
import type { ReactNode } from "react"; | ||
import React from "react"; | ||
import type { Fit } from "../../../dom/nodes"; | ||
import type { SkRect } from "../../../skia/types"; | ||
import type { SkRect, Transforms3d } from "../../../skia/types"; | ||
interface FitProps { | ||
@@ -11,12 +11,4 @@ fit?: Fit; | ||
} | ||
export declare const fitbox: (fit: Fit, src: SkRect, dst: SkRect) => [{ | ||
translateX: number; | ||
}, { | ||
translateY: number; | ||
}, { | ||
scaleX: number; | ||
}, { | ||
scaleY: number; | ||
}]; | ||
export declare const fitbox: (fit: Fit, src: SkRect, dst: SkRect, rotation?: 0 | 90 | 180 | 270) => Transforms3d; | ||
export declare const FitBox: ({ fit, src, dst, children }: FitProps) => React.JSX.Element; | ||
export {}; |
import React, { useMemo } from "react"; | ||
import { fitRects, rect2rect } from "../../../dom/nodes"; | ||
import { Group } from "../Group"; | ||
export const fitbox = (fit, src, dst) => { | ||
const rects = fitRects(fit, src, dst); | ||
return rect2rect(rects.src, rects.dst); | ||
export const fitbox = (fit, src, dst, rotation = 0) => { | ||
"worklet"; | ||
const rects = fitRects(fit, rotation === 90 || rotation === 270 ? { | ||
x: 0, | ||
y: 0, | ||
width: src.height, | ||
height: src.width | ||
} : src, dst); | ||
const result = rect2rect(rects.src, rects.dst); | ||
if (rotation === 90) { | ||
return [...result, { | ||
translate: [src.height, 0] | ||
}, { | ||
rotate: Math.PI / 2 | ||
}]; | ||
} | ||
if (rotation === 180) { | ||
return [...result, { | ||
translate: [src.width, src.height] | ||
}, { | ||
rotate: Math.PI | ||
}]; | ||
} | ||
if (rotation === 270) { | ||
return [...result, { | ||
translate: [0, src.width] | ||
}, { | ||
rotate: -Math.PI / 2 | ||
}]; | ||
} | ||
return result; | ||
}; | ||
@@ -8,0 +37,0 @@ export const FitBox = ({ |
import { Skia } from "../Skia"; | ||
import { processTransform } from "../types"; | ||
export const processTransform2d = transforms => processTransform(Skia.Matrix(), transforms); | ||
export const processTransform2d = transforms => { | ||
"worklet"; | ||
return processTransform(Skia.Matrix(), transforms); | ||
}; | ||
//# sourceMappingURL=Matrix.js.map |
import { processTransform3d } from "./Matrix4"; | ||
export const isMatrix = obj => obj !== null && obj.__typename__ === "Matrix"; | ||
export const processTransform = (m, transforms) => { | ||
"worklet"; | ||
const m3 = processTransform3d(transforms); | ||
@@ -5,0 +7,0 @@ m.concat(m3); |
import type { SkImage } from "../Image"; | ||
import type { SkJSIInstance } from "../JsiInstance"; | ||
export type VideoRotation = 0 | 90 | 180 | 270; | ||
export interface Video extends SkJSIInstance<"Video"> { | ||
@@ -8,3 +9,10 @@ duration(): number; | ||
seek(time: number): void; | ||
getRotationInDegrees(): number; | ||
rotation(): VideoRotation; | ||
size(): { | ||
width: number; | ||
height: number; | ||
}; | ||
pause(): void; | ||
play(): void; | ||
setVolume(volume: number): void; | ||
} |
@@ -0,9 +1,21 @@ | ||
import type { SharedValue } from "react-native-reanimated"; | ||
import type { SkImage } from "../../skia/types"; | ||
import { type PlaybackOptions } from "./video"; | ||
type Animated<T> = SharedValue<T> | T; | ||
interface PlaybackOptions { | ||
looping: Animated<boolean>; | ||
paused: Animated<boolean>; | ||
seek: Animated<number | null>; | ||
volume: Animated<number>; | ||
} | ||
export declare const useVideo: (source: string | null, userOptions?: Partial<PlaybackOptions>) => { | ||
currentFrame: import("react-native-reanimated").SharedValue<SkImage | null>; | ||
currentTime: import("react-native-reanimated").SharedValue<number>; | ||
currentFrame: SharedValue<SkImage | null>; | ||
currentTime: SharedValue<number>; | ||
duration: number; | ||
framerate: number; | ||
rotationInDegrees: number; | ||
rotation: import("../../skia/types").VideoRotation; | ||
size: { | ||
width: number; | ||
height: number; | ||
}; | ||
}; | ||
export {}; |
import type { ReactNode } from "react"; | ||
import React from "react"; | ||
import type { Fit } from "../../../dom/nodes"; | ||
import type { SkRect } from "../../../skia/types"; | ||
import type { SkRect, Transforms3d } from "../../../skia/types"; | ||
interface FitProps { | ||
@@ -11,12 +11,4 @@ fit?: Fit; | ||
} | ||
export declare const fitbox: (fit: Fit, src: SkRect, dst: SkRect) => [{ | ||
translateX: number; | ||
}, { | ||
translateY: number; | ||
}, { | ||
scaleX: number; | ||
}, { | ||
scaleY: number; | ||
}]; | ||
export declare const fitbox: (fit: Fit, src: SkRect, dst: SkRect, rotation?: 0 | 90 | 180 | 270) => Transforms3d; | ||
export declare const FitBox: ({ fit, src, dst, children }: FitProps) => React.JSX.Element; | ||
export {}; |
import type { SkImage } from "../Image"; | ||
import type { SkJSIInstance } from "../JsiInstance"; | ||
export type VideoRotation = 0 | 90 | 180 | 270; | ||
export interface Video extends SkJSIInstance<"Video"> { | ||
@@ -8,3 +9,10 @@ duration(): number; | ||
seek(time: number): void; | ||
getRotationInDegrees(): number; | ||
rotation(): VideoRotation; | ||
size(): { | ||
width: number; | ||
height: number; | ||
}; | ||
pause(): void; | ||
play(): void; | ||
setVolume(volume: number): void; | ||
} |
@@ -10,3 +10,3 @@ { | ||
"title": "React Native Skia", | ||
"version": "1.3.2", | ||
"version": "1.3.3", | ||
"description": "High-performance React Native Graphics using Skia", | ||
@@ -13,0 +13,0 @@ "main": "lib/module/index.js", |
@@ -10,3 +10,6 @@ import { exhaustiveCheck } from "../../../renderer/typeddash"; | ||
export const size = (width = 0, height = 0) => ({ width, height }); | ||
export const size = (width = 0, height = 0) => { | ||
"worklet"; | ||
return { width, height }; | ||
}; | ||
@@ -22,2 +25,3 @@ export const rect2rect = ( | ||
] => { | ||
"worklet"; | ||
const scaleX = dst.width / src.width; | ||
@@ -30,22 +34,2 @@ const scaleY = dst.height / src.height; | ||
export const fitRects = ( | ||
fit: Fit, | ||
rect: SkRect, | ||
{ x, y, width, height }: SkRect | ||
) => { | ||
const sizes = applyBoxFit( | ||
fit, | ||
{ width: rect.width, height: rect.height }, | ||
{ width, height } | ||
); | ||
const src = inscribe(sizes.src, rect); | ||
const dst = inscribe(sizes.dst, { | ||
x, | ||
y, | ||
width, | ||
height, | ||
}); | ||
return { src, dst }; | ||
}; | ||
const inscribe = ( | ||
@@ -55,2 +39,3 @@ { width, height }: Size, | ||
) => { | ||
"worklet"; | ||
const halfWidthDelta = (rect.width - width) / 2.0; | ||
@@ -67,2 +52,3 @@ const halfHeightDelta = (rect.height - height) / 2.0; | ||
const applyBoxFit = (fit: Fit, input: Size, output: Size) => { | ||
"worklet"; | ||
let src = size(), | ||
@@ -130,1 +116,22 @@ dst = size(); | ||
}; | ||
export const fitRects = ( | ||
fit: Fit, | ||
rect: SkRect, | ||
{ x, y, width, height }: SkRect | ||
) => { | ||
"worklet"; | ||
const sizes = applyBoxFit( | ||
fit, | ||
{ width: rect.width, height: rect.height }, | ||
{ width, height } | ||
); | ||
const src = inscribe(sizes.src, rect); | ||
const dst = inscribe(sizes.dst, { | ||
x, | ||
y, | ||
width, | ||
height, | ||
}); | ||
return { src, dst }; | ||
}; |
@@ -1,2 +0,2 @@ | ||
import { type FrameInfo } from "react-native-reanimated"; | ||
import type { SharedValue, FrameInfo } from "react-native-reanimated"; | ||
import { useEffect, useMemo } from "react"; | ||
@@ -6,12 +6,31 @@ | ||
import type { SkImage, Video } from "../../skia/types"; | ||
import { Platform } from "../../Platform"; | ||
import Rea from "./ReanimatedProxy"; | ||
import { | ||
processVideoState, | ||
type Animated, | ||
type PlaybackOptions, | ||
} from "./video"; | ||
type Animated<T> = SharedValue<T> | T; | ||
interface PlaybackOptions { | ||
looping: Animated<boolean>; | ||
paused: Animated<boolean>; | ||
seek: Animated<number | null>; | ||
volume: Animated<number>; | ||
} | ||
const setFrame = (video: Video, currentFrame: SharedValue<SkImage | null>) => { | ||
"worklet"; | ||
const img = video.nextImage(); | ||
if (img) { | ||
if (currentFrame.value) { | ||
currentFrame.value.dispose(); | ||
} | ||
if (Platform.OS === "android") { | ||
currentFrame.value = img.makeNonTextureImage(); | ||
} else { | ||
currentFrame.value = img; | ||
} | ||
} | ||
}; | ||
const defaultOptions = { | ||
playbackSpeed: 1, | ||
looping: true, | ||
@@ -21,2 +40,3 @@ paused: false, | ||
currentTime: 0, | ||
volume: 0, | ||
}; | ||
@@ -46,5 +66,3 @@ | ||
const seek = useOption(userOptions?.seek ?? defaultOptions.seek); | ||
const playbackSpeed = useOption( | ||
userOptions?.playbackSpeed ?? defaultOptions.playbackSpeed | ||
); | ||
const volume = useOption(userOptions?.volume ?? defaultOptions.volume); | ||
const currentFrame = Rea.useSharedValue<null | SkImage>(null); | ||
@@ -55,22 +73,58 @@ const currentTime = Rea.useSharedValue(0); | ||
const framerate = useMemo(() => video?.framerate() ?? 0, [video]); | ||
const rotationInDegrees = useMemo( | ||
() => video?.getRotationInDegrees() ?? 0, | ||
[video] | ||
const size = useMemo(() => video?.size() ?? { width: 0, height: 0 }, [video]); | ||
const rotation = useMemo(() => video?.rotation() ?? 0, [video]); | ||
const frameDuration = 1000 / framerate; | ||
const currentFrameDuration = Math.floor(frameDuration); | ||
Rea.useAnimatedReaction( | ||
() => isPaused.value, | ||
(paused) => { | ||
if (paused) { | ||
video?.pause(); | ||
} else { | ||
lastTimestamp.value = -1; | ||
video?.play(); | ||
} | ||
} | ||
); | ||
Rea.useAnimatedReaction( | ||
() => seek.value, | ||
(value) => { | ||
if (value !== null) { | ||
video?.seek(value); | ||
currentTime.value = value; | ||
seek.value = null; | ||
} | ||
} | ||
); | ||
Rea.useAnimatedReaction( | ||
() => volume.value, | ||
(value) => { | ||
video?.setVolume(value); | ||
} | ||
); | ||
Rea.useFrameCallback((frameInfo: FrameInfo) => { | ||
processVideoState( | ||
video, | ||
duration, | ||
framerate, | ||
frameInfo.timestamp, | ||
{ | ||
paused: isPaused.value, | ||
looping: looping.value, | ||
playbackSpeed: playbackSpeed.value, | ||
}, | ||
currentTime, | ||
currentFrame, | ||
lastTimestamp, | ||
seek | ||
); | ||
"worklet"; | ||
if (!video) { | ||
return; | ||
} | ||
if (isPaused.value) { | ||
return; | ||
} | ||
const currentTimestamp = frameInfo.timestamp; | ||
if (lastTimestamp.value === -1) { | ||
lastTimestamp.value = currentTimestamp; | ||
} | ||
const delta = currentTimestamp - lastTimestamp.value; | ||
const isOver = currentTime.value + delta > duration; | ||
if (isOver && looping.value) { | ||
seek.value = 0; | ||
currentTime.value = seek.value; | ||
lastTimestamp.value = currentTimestamp; | ||
} | ||
if (delta >= currentFrameDuration && !isOver) { | ||
setFrame(video, currentFrame); | ||
currentTime.value += delta; | ||
lastTimestamp.value = currentTimestamp; | ||
} | ||
}); | ||
@@ -85,3 +139,10 @@ | ||
return { currentFrame, currentTime, duration, framerate, rotationInDegrees }; | ||
return { | ||
currentFrame, | ||
currentTime, | ||
duration, | ||
framerate, | ||
rotation, | ||
size, | ||
}; | ||
}; |
@@ -5,3 +5,5 @@ import { Skia } from "../Skia"; | ||
export const processTransform2d = (transforms: Transforms3d) => | ||
processTransform(Skia.Matrix(), transforms); | ||
export const processTransform2d = (transforms: Transforms3d) => { | ||
"worklet"; | ||
return processTransform(Skia.Matrix(), transforms); | ||
}; |
@@ -33,2 +33,3 @@ import type { SkJSIInstance } from "./JsiInstance"; | ||
) => { | ||
"worklet"; | ||
const m3 = processTransform3d(transforms); | ||
@@ -35,0 +36,0 @@ m.concat(m3); |
import type { SkImage } from "../Image"; | ||
import type { SkJSIInstance } from "../JsiInstance"; | ||
export type VideoRotation = 0 | 90 | 180 | 270; | ||
export interface Video extends SkJSIInstance<"Video"> { | ||
@@ -9,3 +11,7 @@ duration(): number; | ||
seek(time: number): void; | ||
getRotationInDegrees(): number; | ||
rotation(): VideoRotation; | ||
size(): { width: number; height: number }; | ||
pause(): void; | ||
play(): void; | ||
setVolume(volume: number): void; | ||
} |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
224182424
55989
3361