UNPKG

@remotion/studio

Version:

APIs for interacting with the Remotion Studio

130 lines (129 loc) 5.2 kB
"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;