hypnagaga/src/components/ScrollyVideo/js/ScrollyVideo.ts
2025-06-04 19:32:37 +05:30

966 lines
29 KiB
TypeScript

import { UAParser } from 'ua-parser-js';
import videoDecoder from './videoDecoder';
import { debounce, isScrollPositionAtTarget, map } from './utils';
import { createComponentState, type ScrollyVideoState } from './state.svelte';
interface ScrollyVideoArgs {
src?: string;
scrollyVideoContainer: HTMLElement | string;
objectFit?: string;
sticky?: boolean;
full?: boolean;
trackScroll?: boolean;
lockScroll?: boolean;
transitionSpeed?: number;
frameThreshold?: number;
useWebCodecs?: boolean;
onReady?: () => void;
onChange?: (percentage?: number) => void;
debug?: boolean;
autoplay?: boolean;
}
interface TransitionOptions {
jump: boolean;
transitionSpeed?: number;
easing?: ((progress: number) => number) | null;
autoplay?: boolean;
}
/**
* ScrollyVideo class for scroll-driven or programmatic video playback with Svelte integration.
*/
class ScrollyVideo {
/**
* The container element for the video or canvas.
* @type {HTMLElement | null}
*/
container: HTMLElement | null;
/**
* The original container argument (element or string ID).
* @type {Element | string | undefined}
*/
scrollyVideoContainer: Element | string | undefined;
/**
* Video source URL.
* @type {string}
*/
src: string;
/**
* Speed of transitions.
* @type {number}
*/
transitionSpeed: number;
/**
* Threshold for frame transitions.
* @type {number}
*/
frameThreshold: number;
/**
* Whether to use WebCodecs for decoding.
* @type {boolean}
*/
useWebCodecs: boolean;
/**
* CSS object-fit property for video/canvas.
* @type {string}
*/
objectFit: string;
/**
* Whether to use sticky positioning.
* @type {boolean}
*/
sticky: boolean;
/**
* Whether to track scroll position.
* @type {boolean}
*/
trackScroll: boolean;
/**
* Callback when ready.
* @type {() => void}
*/
onReady: () => void;
/**
* Callback on scroll percentage change.
* @type {(percentage?: number) => void}
*/
onChange: (percentage?: number) => void;
/**
* Enable debug logging.
* @type {boolean}
*/
debug: boolean;
/**
* Enable autoplay.
* @type {boolean}
*/
autoplay: boolean;
/**
* The HTML video element.
* @type {HTMLVideoElement | undefined}
*/
video: HTMLVideoElement | undefined;
/**
* Current scroll/video percentage (0-1).
* @type {number}
*/
videoPercentage: number;
/**
* True if browser is Safari.
* @type {boolean}
*/
isSafari: boolean;
/**
* Current video time in seconds.
* @type {number}
*/
currentTime: number;
/**
* Target video time in seconds.
* @type {number}
*/
targetTime: number;
/**
* Canvas for rendering frames (if using WebCodecs).
* @type {HTMLCanvasElement | null}
*/
canvas: HTMLCanvasElement | null;
/**
* 2D context for the canvas.
* @type {CanvasRenderingContext2D | null}
*/
context: CanvasRenderingContext2D | null;
/**
* Decoded video frames (if using WebCodecs).
* @type {ImageBitmap[] | null}
*/
frames: ImageBitmap[] | null;
/**
* Video frame rate.
* @type {number}
*/
frameRate: number;
/**
* Target scroll position in pixels, if set.
* @type {number | null}
*/
targetScrollPosition: number | null = null;
/**
* Current frame index (if using WebCodecs).
* @type {number}
*/
currentFrame: number;
/**
* True if using WebCodecs for decoding.
* @type {boolean}
*/
usingWebCodecs: boolean;
/**
* Total video duration in seconds.
* @type {number}
*/
totalTime: number;
/**
* RequestAnimationFrame ID for transitions.
* @type {number | null}
*/
transitioningRaf: number | null;
/**
* State object for component-level state.
* @type {ScrollyVideoState}
*/
componentState: ScrollyVideoState;
/**
* Function to update scroll percentage (set in constructor).
* @type {((jump: boolean) => void) | undefined}
*/
updateScrollPercentage: ((jump: boolean) => void) | undefined;
/**
* Function to handle resize events (set in constructor).
* @type {(() => void) | undefined}
*/
resize: (() => void) | undefined;
/**
* Creates a new ScrollyVideo instance.
* @param {ScrollyVideoArgs} args - The arguments for initialization.
*/
constructor({
src = 'https://scrollyvideo.js.org/goldengate.mp4',
scrollyVideoContainer,
objectFit = 'cover',
sticky = true,
full = true,
trackScroll = true,
lockScroll = true,
transitionSpeed = 8,
frameThreshold = 0.1,
useWebCodecs = true,
onReady = () => {},
onChange = (_percentage?: number) => {},
debug = false,
autoplay = false,
}: ScrollyVideoArgs) {
this.src = src;
this.scrollyVideoContainer = scrollyVideoContainer;
this.objectFit = objectFit;
this.sticky = sticky;
this.trackScroll = trackScroll;
this.transitionSpeed = transitionSpeed;
this.frameThreshold = frameThreshold;
this.useWebCodecs = useWebCodecs;
this.onReady = onReady;
this.onChange = onChange;
this.debug = debug;
this.autoplay = autoplay;
this.videoPercentage = 0;
this.isSafari = false;
this.currentTime = 0;
this.targetTime = 0;
this.canvas = null;
this.context = null;
this.container = null;
this.frames = null;
this.frameRate = 0;
this.currentTime = 0; // Saves the currentTime of the video, synced with this.video.currentTime
this.targetTime = 0; // The target time before a transition happens
this.canvas = null; // The canvas for drawing the frames decoded by webCodecs
this.context = null; // The canvas context
this.frames = []; // The frames decoded by webCodecs
this.frameRate = 0; // Calculation of frameRate so we know which frame to paint
this.currentFrame = 0;
this.videoPercentage = 0;
this.usingWebCodecs = false; // Whether we are using webCodecs
this.totalTime = 0; // The total time of the video, used for calculating percentage
this.transitioningRaf = null;
this.componentState = createComponentState();
// Make sure that we have a DOM
if (typeof document !== 'object') {
console.error('ScrollyVideo must be initiated in a DOM context');
return;
}
// Make sure the basic arguments are set for scrollyvideo
if (!scrollyVideoContainer) {
console.error('scrollyVideoContainer must be a valid DOM object');
return;
}
if (!src) {
console.error('Must provide valid video src to ScrollyVideo');
return;
}
// Save the container. If the container is a string we get the element
if (scrollyVideoContainer && scrollyVideoContainer instanceof HTMLElement)
this.container = scrollyVideoContainer;
// otherwise it should better be an element
else if (typeof scrollyVideoContainer === 'string') {
this.container = document.getElementById(scrollyVideoContainer) || null;
if (!this.container)
throw new Error('scrollyVideoContainer must be a valid DOM object');
} else {
throw new Error('scrollyVideoContainer must be a valid DOM object');
}
// Create the initial video object. Even if we are going to use webcodecs,
// we start with a paused video object
this.video = document.createElement('video');
this.video.src = src;
this.video.preload = 'auto';
this.video.tabIndex = 0;
this.video.preload = 'auto';
this.video.playsInline = true;
this.video.muted = true;
this.video.pause();
this.video.load();
this.video.addEventListener(
'canplaythrough',
() => {
this.onReady();
if (this.autoplay && !this.useWebCodecs) {
this.autoplayScroll();
}
},
{ once: true }
);
// Start the video percentage at 0
this.videoPercentage = 0;
// Adds the video to the container
this.container.appendChild(this.video);
// Setting CSS properties for sticky
if (sticky) {
this.container.style.display = 'block';
this.container.style.position = 'sticky';
this.container.style.top = '0';
}
// Setting CSS properties for full
if (full) {
this.container.style.width = '100%';
this.container.style.height = '100svh';
this.container.style.overflow = 'hidden';
}
// Setting CSS properties for cover
if (objectFit) this.setCoverStyle(this.video);
// Detect webkit (safari), because webkit requires special attention
const browserEngine = new UAParser().getEngine();
this.isSafari = browserEngine.name === 'WebKit';
if (debug && this.isSafari) console.info('Safari browser detected');
const debouncedScroll = debounce(() => {
window.requestAnimationFrame(() => {
this.setScrollPercent(this.videoPercentage);
});
}, 100);
// Add scroll listener for responding to scroll position
this.updateScrollPercentage = (jump = false) => {
// Used for internally setting the scroll percentage based on built-in listeners
let containerBoundingClientRect;
if (
this.container &&
this.container.parentNode &&
(this.container.parentNode as Element).getBoundingClientRect
) {
containerBoundingClientRect = (
this.container.parentNode as Element
).getBoundingClientRect();
} else {
if (this.debug) {
console.error(
'ScrollyVideo: container or parentNode is null or invalid.'
);
}
return;
}
// Calculate the current scroll percent of the video
let scrollPercent =
-containerBoundingClientRect.top /
(containerBoundingClientRect.height - window.innerHeight);
// if autplay, trim the playing time to last locked video position
if (this.componentState.autoplayProgress > 0) {
scrollPercent = map(
scrollPercent,
0,
1,
this.componentState.autoplayProgress,
1
);
}
if (this.debug) {
console.info('ScrollyVideo scrolled to', scrollPercent);
}
// toggle autoplaying state on manual intervention
if (this.componentState.isAutoPlaying && this.frames) {
if (this.debug) console.warn('Stopping autoplay due to manual scroll');
if (this.usingWebCodecs) {
this.componentState.autoplayProgress = parseFloat(
(this.currentFrame / this.frames.length).toFixed(4)
);
} else {
this.componentState.autoplayProgress = parseFloat(
(this.currentTime / this.totalTime).toFixed(4)
);
}
this.componentState.isAutoPlaying = false;
}
this.videoPercentage = scrollPercent;
if (this.targetScrollPosition == null) {
this.setTargetTimePercent(scrollPercent, { jump });
this.onChange(scrollPercent);
} else if (isScrollPositionAtTarget(this.targetScrollPosition)) {
this.targetScrollPosition = null;
} else if (lockScroll && this.targetScrollPosition != null) {
debouncedScroll();
}
this.updateDebugInfo();
};
// Add our event listeners for handling changes to the window or scroll
if (this.trackScroll) {
window.addEventListener('scroll', () => {
if (this.updateScrollPercentage) {
this.updateScrollPercentage(false);
}
});
// Set the initial scroll percentage
this.video.addEventListener(
'loadedmetadata',
() => {
if (this.updateScrollPercentage) {
this.updateScrollPercentage(true);
}
if (this.video) {
this.totalTime = this.video.duration;
}
this.setCoverStyle(this.canvas || this.video);
},
{ once: true }
);
} else {
this.video.addEventListener(
'loadedmetadata',
() => {
this.setTargetTimePercent(0, { jump: true });
if (this.video) {
this.totalTime = this.video.duration;
}
this.setCoverStyle(this.canvas || this.video);
},
{ once: true }
);
}
// Add resize function
this.resize = () => {
if (this.debug) console.info('ScrollyVideo resizing...');
// On resize, we need to reset the cover style
if (this.objectFit) this.setCoverStyle(this.canvas || this.video);
// Then repaint the canvas, if we are in useWebcodecs
this.paintCanvasFrame(Math.floor(this.currentTime * this.frameRate));
};
window.addEventListener('resize', this.resize);
this.video.addEventListener('progress', this.resize);
// Calls decode video to attempt webcodecs method
this.decodeVideo();
this.updateDebugInfo();
}
/**
* Sets the currentTime of the video as a specified percentage of its total duration.
*
* @param percentage - The percentage of the video duration to set as the current time.
* @param options - Configuration options for adjusting the video playback.
* - autoplay: boolean - If true, the video will start playing immediately after setting the percentage. Default is false.
* - jump: boolean - If true, the video currentTime will jump directly to the specified percentage. If false, the change will be animated over time.
* - transitionSpeed: number - Defines the speed of the transition when `jump` is false. Represents the duration of the transition in milliseconds. Default is 8.
* - easing: (progress: number) => number - A function that defines the easing curve for the transition. It takes the progress ratio (a number between 0 and 1) as an argument and returns the eased value, affecting the playback speed during the transition.
*/
setVideoPercentage(
percentage: number,
options: TransitionOptions = { jump: false, transitionSpeed: 8 }
): void {
// Early termination if the video percentage is already at the percentage that is intended.
if (this.videoPercentage === percentage) return;
if (this.transitioningRaf) {
window.cancelAnimationFrame(this.transitioningRaf);
}
this.videoPercentage = percentage;
this.onChange(percentage);
if (this.trackScroll && !options.autoplay) {
this.setScrollPercent(percentage);
}
this.setTargetTimePercent(percentage, options);
}
/**
* Sets the style of the video or canvas to "cover" its container.
* @param {HTMLElement | HTMLCanvasElement | undefined} el - The element to style.
*/
setCoverStyle(el: HTMLElement | HTMLCanvasElement | undefined): void {
if (!el) {
if (this.debug) console.warn('No element to set cover style on');
return;
}
if (this.objectFit) {
el.style.position = 'absolute';
el.style.top = '50%';
el.style.left = '50%';
el.style.transform = 'translate(-50%, -50%)';
// el.style.minWidth = '101%';
// el.style.minHeight = '101%';
// Gets the width and height of the container
const { width: containerWidth, height: containerHeight } =
this.container?.getBoundingClientRect() || { width: 0, height: 0 };
let width = 0,
height = 0;
if (el instanceof HTMLVideoElement) {
width = el.videoWidth;
height = el.videoHeight;
} else if (el instanceof HTMLCanvasElement) {
width = el.width;
height = el.height;
}
if (this.debug)
console.info('Container dimensions:', [
containerWidth,
containerHeight,
]);
if (this.debug) console.info('Element dimensions:', [width, height]);
// Determines which axis needs to be 100% and which needs to be scaled
if (this.objectFit == 'cover') {
if (containerWidth / containerHeight > width / height) {
el.style.width = '100%';
el.style.height = 'auto';
} else {
el.style.height = '100%';
el.style.width = 'auto';
}
} else if (this.objectFit == 'contain') {
if (containerWidth / containerHeight > width / height) {
el.style.height = '100%';
el.style.width = 'auto';
} else {
el.style.width = '100%';
el.style.height = 'auto';
}
}
}
}
/**
* Uses webCodecs to decode the video into frames.
* @returns {Promise<void>} Resolves when decoding is complete.
*/
async decodeVideo(): Promise<void> {
if (!this.useWebCodecs) {
if (this.debug)
console.warn('Cannot perform video decode: `useWebCodes` disabled');
return;
}
if (!this.src) {
if (this.debug)
console.warn('Cannot perform video decode: no `src` found');
return;
}
try {
await videoDecoder(
this.src,
(frame) => {
this.frames?.push(frame);
},
this.debug
).then((codec) => {
this.usingWebCodecs = true;
if (typeof codec == 'string') {
this.componentState.framesData.codec = codec;
}
});
} catch (error) {
if (this.debug)
console.error('Error encountered while decoding video', error);
// Remove all decoded frames if a failure happens during decoding
this.frames = [];
// Force a video reload when videoDecoder fails
this.video?.load();
}
// If no frames, something went wrong
if (this.frames?.length === 0) {
if (this.debug) console.error('No frames were received from webCodecs');
this.onReady();
return;
}
// Calculate the frameRate based on number of frames and the duration
this.frameRate =
this.frames && this.video ? this.frames.length / this.video.duration : 0;
if (this.debug)
console.info(
'Received',
this.frames?.length,
'frames. Video frame rate:',
this.frameRate
);
// Remove the video and add the canvas
this.canvas = document.createElement('canvas');
this.context = this.canvas.getContext('2d');
// Hide the video and add the canvas to the container
if (this.video) {
this.video.style.display = 'none';
}
if (this.container) {
this.container.appendChild(this.canvas);
}
if (this.objectFit) this.setCoverStyle(this.canvas);
// Paint our first frame
this.paintCanvasFrame(Math.floor(this.currentTime * this.frameRate));
this.onReady();
if (this.autoplay) this.autoplayScroll();
}
/**
* Paints the frame to the canvas.
* @param {number} frameNum - The frame index to paint.
*/
paintCanvasFrame(frameNum: number): void {
if (!this.frames) {
if (this.debug) console.warn('No frames available to paint');
return;
}
// Get the frame and paint it to the canvas
const currFrame = this.frames[frameNum];
this.currentFrame = frameNum;
if (!this.canvas || !currFrame) {
return;
}
if (this.debug) {
console.info('Painting frame', frameNum);
}
// Make sure the canvas is scaled properly, similar to setCoverStyle
this.canvas.width = currFrame.width;
this.canvas.height = currFrame.height;
const { width, height } = this.container?.getBoundingClientRect() || {
width: 0,
height: 0,
};
if (this.objectFit == 'cover') {
if (width / height > currFrame.width / currFrame.height) {
this.canvas.style.width = '100%';
this.canvas.style.height = 'auto';
} else {
this.canvas.style.height = '100%';
this.canvas.style.width = 'auto';
}
} else if (this.objectFit == 'contain') {
if (width / height > currFrame.width / currFrame.height) {
this.canvas.style.height = '100%';
this.canvas.style.width = 'auto';
} else {
this.canvas.style.width = '100%';
this.canvas.style.height = 'auto';
}
}
// Draw the frame to the canvas context
if (!this.context) {
if (this.debug) console.warn('No canvas context available to paint');
return;
}
this.context.drawImage(currFrame, 0, 0, currFrame.width, currFrame.height);
this.updateDebugInfo();
}
/**
* Transitions the video or the canvas to the proper frame.
*
* @param options - Configuration options for adjusting the video playback.
* - jump: boolean - If true, the video currentTime will jump directly to the specified percentage. If false, the change will be animated over time.
* - transitionSpeed: number - Defines the speed of the transition when `jump` is false. Represents the duration of the transition in milliseconds. Default is 8.
* - easing: (progress: number) => number - A function that defines the easing curve for the transition. It takes the progress ratio (a number between 0 and 1) as an argument and returns the eased value, affecting the playback speed during the transition.
*/
transitionToTargetTime({
jump,
transitionSpeed = this.transitionSpeed,
easing = null,
}: TransitionOptions) {
if (!this.video) {
console.warn('No video found');
return;
}
if (this.debug) {
console.info(
'Transitioning targetTime:',
this.targetTime,
'currentTime:',
this.currentTime
);
}
const diff = this.targetTime - this.currentTime;
const distance = Math.abs(diff);
const duration = distance * 1000;
const isForwardTransition = diff > 0;
const tick = ({
startCurrentTime,
startTimestamp,
timestamp,
}: {
startCurrentTime: number;
startTimestamp: number;
timestamp: number;
}) => {
if (!this.video) {
console.warn('No video found during transition tick');
return;
}
const progress = (timestamp - startTimestamp) / duration;
// if frameThreshold is too low to catch condition Math.abs(this.targetTime - this.currentTime) < this.frameThreshold
const hasPassedThreshold =
isForwardTransition ?
this.currentTime >= this.targetTime
: this.currentTime <= this.targetTime;
if (this.componentState.isAutoPlaying) {
this.componentState.autoplayProgress =
this.currentTime / this.totalTime;
}
// If we are already close enough to our target, pause the video and return.
// This is the base case of the recursive function
if (
isNaN(this.targetTime) ||
// If the currentTime is already close enough to the targetTime
Math.abs(this.targetTime - this.currentTime) < this.frameThreshold ||
hasPassedThreshold
) {
this.video?.pause();
if (this.transitioningRaf) {
cancelAnimationFrame(this.transitioningRaf);
this.transitioningRaf = null;
}
return;
}
// Make sure we don't go out of time bounds
if (this.targetTime > this.video.duration)
this.targetTime = this.video.duration;
if (this.targetTime < 0) this.targetTime = 0;
// How far forward we need to transition
const transitionForward = this.targetTime - this.currentTime;
const easedProgress =
easing && Number.isFinite(progress) ? easing(progress) : 0;
const easedCurrentTime =
isForwardTransition ?
startCurrentTime +
easedProgress * Math.abs(distance) * transitionSpeed
: startCurrentTime -
easedProgress * Math.abs(distance) * transitionSpeed;
if (this.canvas) {
if (jump) {
// If jump, we go directly to the frame
this.currentTime = this.targetTime;
} else if (easedProgress) {
this.currentTime = easedCurrentTime;
} else {
this.currentTime += transitionForward / (256 / transitionSpeed);
}
this.paintCanvasFrame(Math.floor(this.currentTime * this.frameRate));
} else if (jump || this.isSafari || !isForwardTransition) {
// We can't use a negative playbackRate, so if the video needs to go backwards,
// We have to use the inefficient method of modifying currentTime rapidly to
// get an effect.
this.video.pause();
if (easedProgress) {
this.currentTime = easedCurrentTime;
} else {
this.currentTime += transitionForward / (64 / transitionSpeed);
}
// If jump, we go directly to the frame
if (jump) {
this.currentTime = this.targetTime;
}
this.video.currentTime = this.currentTime;
} else {
// Otherwise, we play the video and adjust the playbackRate to get a smoother
// animation effect.
const playbackRate = Math.max(
Math.min(transitionForward * 4, transitionSpeed, 16),
1
);
if (this.debug)
console.info('ScrollyVideo playbackRate:', playbackRate);
if (!isNaN(playbackRate)) {
this.video.playbackRate = playbackRate;
this.video.play();
}
// Set the currentTime to the video's currentTime
this.currentTime = this.video.currentTime;
}
// Recursively calls ourselves until the animation is done.
if (typeof requestAnimationFrame === 'function') {
this.transitioningRaf = requestAnimationFrame((currentTimestamp) =>
tick({
startCurrentTime,
startTimestamp,
timestamp: currentTimestamp,
})
);
}
};
if (typeof requestAnimationFrame === 'function') {
this.transitioningRaf = requestAnimationFrame((startTimestamp) => {
tick({
startCurrentTime: this.currentTime,
startTimestamp,
timestamp: startTimestamp,
});
});
}
}
/**
* Sets the currentTime of the video as a specified percentage of its total duration.
*
* @param percentage - The percentage of the video duration to set as the current time.
* @param options - Configuration options for adjusting the video playback.
* - jump: boolean - If true, the video currentTime will jump directly to the specified percentage. If false, the change will be animated over time.
* - transitionSpeed: number - Defines the speed of the transition when `jump` is false. Represents the duration of the transition in milliseconds. Default is 8.
* - easing: (progress: number) => number - A function that defines the easing curve for the transition. It takes the progress ratio (a number between 0 and 1) as an argument and returns the eased value, affecting the playback speed during the transition.
*/
setTargetTimePercent(
percentage: number,
options: TransitionOptions = { jump: false, transitionSpeed: 8 }
) {
const targetDuration =
this.frames?.length && this.frameRate ?
this.frames.length / this.frameRate
: this.video?.duration || 0;
// The time we want to transition to
this.targetTime = Math.max(Math.min(percentage, 1), 0) * targetDuration;
// If we are close enough, return early
if (
!options.jump &&
Math.abs(this.currentTime - this.targetTime) < this.frameThreshold
)
return;
// Play the video if we are in video mode
if (!this.canvas && !this.video?.paused) this.video?.play();
this.transitionToTargetTime(options);
}
/**
* Simulate trackScroll programmatically (scrolls on page by percentage of video).
* @param {number} percentage - The percentage of the video to scroll to.
*/
setScrollPercent(percentage: number) {
if (!this.trackScroll) {
console.warn('`setScrollPercent` requires enabled `trackScroll`');
return;
}
const parent = this.container?.parentNode;
let top = 0,
height = 0;
if (parent && parent instanceof Element) {
const rect = parent.getBoundingClientRect();
top = rect.top;
height = rect.height;
}
const startPoint = top + window.pageYOffset;
const containerHeightInViewport = height - window.innerHeight;
const targetPosition = startPoint + containerHeightInViewport * percentage;
if (isScrollPositionAtTarget(targetPosition)) {
this.targetScrollPosition = null;
} else {
window.scrollTo({ top: targetPosition, behavior: 'smooth' });
this.targetScrollPosition = targetPosition;
}
}
/**
* Call to destroy this ScrollyVideo object.
*/
destroy() {
if (this.debug) console.info('Destroying ScrollyVideo');
if (this.trackScroll && this.updateScrollPercentage)
window.removeEventListener('scroll', () => this.updateScrollPercentage);
if (this.resize) {
window.removeEventListener('resize', this.resize);
}
// Clear component
if (this.container) this.container.innerHTML = '';
}
/**
* Autoplay the video by scrolling to the end.
*/
autoplayScroll() {
this.setVideoPercentage(1, {
jump: false,
transitionSpeed: this.totalTime * 0.1,
easing: (i) => i,
autoplay: true,
});
this.componentState.isAutoPlaying = true;
}
/**
* Updates debug information in the component state.
*/
updateDebugInfo() {
this.componentState.generalData.src = this.src;
this.componentState.generalData.videoPercentage = parseFloat(
this.videoPercentage.toFixed(4)
);
this.componentState.generalData.frameRate = parseFloat(
this.frameRate.toFixed(2)
);
this.componentState.generalData.currentTime = parseFloat(
this.currentTime.toFixed(4)
);
this.componentState.generalData.totalTime = parseFloat(
this.totalTime.toFixed(4)
);
this.componentState.usingWebCodecs = this.usingWebCodecs;
this.componentState.framesData.currentFrame = this.currentFrame;
this.componentState.framesData.totalFrames = this.frames?.length || 0;
}
}
export default ScrollyVideo;