@remotion/studio
Version:
APIs for interacting with the Remotion Studio
130 lines (129 loc) • 5.2 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.AudioWaveform = void 0;
const jsx_runtime_1 = require("react/jsx-runtime");
const react_1 = require("react");
const remotion_1 = require("remotion");
const colors_1 = require("../helpers/colors");
const timeline_layout_1 = require("../helpers/timeline-layout");
const draw_peaks_1 = require("./draw-peaks");
const load_waveform_peaks_1 = require("./load-waveform-peaks");
const container = {
display: 'flex',
flexDirection: 'row',
alignItems: 'center',
position: 'absolute',
inset: 0,
};
const errorMessage = {
fontSize: 13,
paddingTop: 6,
paddingBottom: 6,
paddingLeft: 12,
paddingRight: 12,
alignSelf: 'flex-start',
maxWidth: 450,
opacity: 0.75,
};
const waveformCanvasStyle = {
pointerEvents: 'none',
};
const volumeCanvasStyle = {
position: 'absolute',
};
const AudioWaveform = ({ src, startFrom, durationInFrames, visualizationWidth, volume, doesVolumeChange, playbackRate, }) => {
const [peaks, setPeaks] = (0, react_1.useState)(null);
const [error, setError] = (0, react_1.useState)(null);
const vidConf = remotion_1.Internals.useUnsafeVideoConfig();
if (vidConf === null) {
throw new Error('Expected video config');
}
const containerRef = (0, react_1.useRef)(null);
const waveformCanvas = (0, react_1.useRef)(null);
const volumeCanvas = (0, react_1.useRef)(null);
(0, react_1.useEffect)(() => {
const controller = new AbortController();
setError(null);
(0, load_waveform_peaks_1.loadWaveformPeaks)(src, controller.signal)
.then((p) => {
if (!controller.signal.aborted) {
setPeaks(p);
}
})
.catch((err) => {
if (!controller.signal.aborted) {
setError(err);
}
});
return () => controller.abort();
}, [src]);
const portionPeaks = (0, react_1.useMemo)(() => {
if (!peaks || peaks.length === 0) {
return null;
}
const startTimeInSeconds = startFrom / vidConf.fps;
const durationInSeconds = (durationInFrames / vidConf.fps) * playbackRate;
const startPeakIndex = Math.floor(startTimeInSeconds * load_waveform_peaks_1.TARGET_SAMPLE_RATE);
const endPeakIndex = Math.ceil((startTimeInSeconds + durationInSeconds) * load_waveform_peaks_1.TARGET_SAMPLE_RATE);
return peaks.slice(Math.max(0, startPeakIndex), Math.min(peaks.length, endPeakIndex));
}, [peaks, startFrom, durationInFrames, vidConf.fps, playbackRate]);
(0, react_1.useEffect)(() => {
const { current: canvasElement } = waveformCanvas;
const { current: containerElement } = containerRef;
if (!canvasElement ||
!containerElement ||
!portionPeaks ||
portionPeaks.length === 0) {
return;
}
const h = containerElement.clientHeight;
const w = Math.ceil(visualizationWidth);
canvasElement.width = w;
canvasElement.height = h;
const vol = typeof volume === 'number' ? volume : 1;
(0, draw_peaks_1.drawBars)(canvasElement, portionPeaks, 'rgba(255, 255, 255, 0.6)', vol, w);
}, [portionPeaks, visualizationWidth, volume]);
(0, react_1.useEffect)(() => {
const { current: volumeCanvasElement } = volumeCanvas;
const { current: containerElement } = containerRef;
if (!volumeCanvasElement || !containerElement) {
return;
}
const h = containerElement.clientHeight;
const context = volumeCanvasElement.getContext('2d');
if (!context) {
return;
}
volumeCanvasElement.width = Math.ceil(visualizationWidth);
volumeCanvasElement.height = h;
context.clearRect(0, 0, visualizationWidth, h);
if (!doesVolumeChange || typeof volume === 'number') {
return;
}
const volumes = volume.split(',').map((v) => Number(v));
context.beginPath();
context.moveTo(0, h);
volumes.forEach((v, index) => {
const x = (index / (volumes.length - 1)) * visualizationWidth;
const y = (1 - v) * (h - timeline_layout_1.TIMELINE_BORDER * 2) + 1;
if (index === 0) {
context.moveTo(x, y);
}
else {
context.lineTo(x, y);
}
});
context.strokeStyle = colors_1.LIGHT_TRANSPARENT;
context.stroke();
}, [visualizationWidth, volume, doesVolumeChange]);
if (error) {
return (jsx_runtime_1.jsx("div", { style: container, children: jsx_runtime_1.jsx("div", { style: errorMessage, children: "No waveform available. Audio might not support CORS." }) }));
}
if (!peaks) {
return null;
}
return (jsx_runtime_1.jsxs("div", { ref: containerRef, style: container, children: [
jsx_runtime_1.jsx("canvas", { ref: waveformCanvas, style: waveformCanvasStyle }), jsx_runtime_1.jsx("canvas", { ref: volumeCanvas, style: volumeCanvasStyle })
] }));
};
exports.AudioWaveform = AudioWaveform;