/
githubmirror
/
gutenberg
Обзор
Документация
Войти
/
githubmirror
/
gutenberg
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
trunk
packages/block-library/src/utils/waveform-player.js
272 строки
8 KB
Marco Ciampini
ESLint: Replace strict config with bulk suppressions (#81248)
07 авг 2026, 14:34
Не верифицирован
07 авг 2026, 14:34
7f43eaf
Код
Авторство
О чём код?
import { useEffect, useRef } from '@wordpress/element'; import { useEvent, useRefEffect } from '@wordpress/compose'; import { __, _x } from '@wordpress/i18n'; import { applyWaveformPlayerStyles, initWaveformPlayer, setupPlayButtonArtwork, updateSeekControlLabel, } from './waveform-utils'; /** * Update the metadata of a WaveformPlayer element to reflect current props. * * `loadTrack()` owns full track swaps, but it also resets the audio element. * This keeps same-source metadata edits lightweight in the editor. * * @param {Object} player - The waveform player. * @param {Object} metadata - The track metadata. * @param {string} metadata.title - The track title. * @param {string} metadata.artist - The artist name. * @param {string} metadata.image - The track image URL. * @param {string} metadata.imageAlt - The track image alt text. * @param {boolean} showPlayButtonArtwork - Whether to show artwork on the play button. */ function updatePlayerMetadata( player, { title, artist, image, imageAlt }, showPlayButtonArtwork ) { const { instance, container } = player; const playerArtwork = showPlayButtonArtwork ? undefined : image; if ( instance.titleEl ) { instance.titleEl.textContent = title ?? ''; } updateSeekControlLabel( instance, title || __( 'Seek' ) ); if ( typeof instance.syncArtist === 'function' ) { instance.syncArtist( artist || '' ); } else if ( instance.artistEl ) { instance.artistEl.textContent = artist ?? ''; instance.artistEl.style.display = artist ? '' : 'none'; } if ( typeof instance.syncArtwork === 'function' ) { instance.syncArtwork( playerArtwork || null, playerArtwork ? imageAlt || '' : '' ); } else if ( instance.artworkEl && playerArtwork ) { instance.artworkEl.src = playerArtwork; instance.artworkEl.alt = imageAlt || ''; } if ( showPlayButtonArtwork ) { setupPlayButtonArtwork( container, image ); } } /** * A reusable WaveformPlayer component for the block editor. * * Renders an audio waveform visualization with play/pause controls. * Automatically inherits colors from the parent block's text color. * * @param {Object} props - Component props. * @param {string} props.src - The audio file URL. * @param {string} props.title - The track title. * @param {string} props.artist - The artist name. * @param {string} props.image - The track image URL. * @param {string} props.imageAlt - The track image alt text. * @param {string} props.color - The waveform color. * @param {string} props.gradient - The waveform gradient. * @param {string} props.backgroundColor - The waveform background color. * @param {string} props.backgroundGradient - The waveform background gradient. * @param {string} props.textColor - The player text color. * @param {string} props.waveformStyle - Waveform style (bars, mirror, line, blocks, dots, seekbar). * @param {Function} props.onEnded - Callback when the track finishes playing. * @param {boolean} props.showPlayButtonArtwork - Whether to show artwork on the play button. * @return {Element} The WaveformPlayer element. */ export function WaveformPlayer( { src, title, artist, image, imageAlt, color, gradient, backgroundColor, backgroundGradient, textColor, waveformStyle, onEnded, showPlayButtonArtwork = false, } ) { // Store onEnded in a stable callback so it doesn't need to be a useRefEffect dependency. // The callback changes reference on every render (its dependency chain // includes an unstable array), which would cause useRefEffect to destroy // and recreate the entire player on every re-render, making it disappear // during editor resizes. const onEndedEvent = useEvent( onEnded ); // Ref for the WaveformPlayer instance const playerRef = useRef(); // WaveformPlayer needs an audio source on init, but the source may change // throughout its lifetime. const hasSrc = !! src; // Combined props ref for `initWaveformPlayer`, which is called // asynchronously after this component mounts. const metadataRef = useRef( { src, title, artist, image, imageAlt } ); const stylesRef = useRef( { color, gradient, backgroundColor, backgroundGradient, textColor, } ); useEffect( () => { metadataRef.current = { src, title, artist, image, imageAlt }; }, [ src, title, artist, image, imageAlt ] ); useEffect( () => { stylesRef.current = { color, gradient, backgroundColor, backgroundGradient, textColor, }; }, [ color, gradient, backgroundColor, backgroundGradient, textColor ] ); useEffect( () => { if ( playerRef.current?.container ) { applyWaveformPlayerStyles( playerRef.current.container, { backgroundColor, backgroundGradient, textColor, playButtonColor: showPlayButtonArtwork ? undefined : color, playButtonGradient: showPlayButtonArtwork ? undefined : gradient, } ); } }, [ backgroundColor, backgroundGradient, color, gradient, showPlayButtonArtwork, textColor, ] ); const ref = useRefEffect( ( element ) => { if ( ! hasSrc ) { return; } let cancelled = false; let playerDestroy; function init() { if ( cancelled ) { return; } const player = initWaveformPlayer( element, { src: metadataRef.current.src, title: metadataRef.current.title, artist: metadataRef.current.artist, image: metadataRef.current.image, imageAlt: metadataRef.current.imageAlt, waveformColor: stylesRef.current.color, waveformGradient: stylesRef.current.gradient, backgroundColor: stylesRef.current.backgroundColor, backgroundGradient: stylesRef.current.backgroundGradient, textColor: stylesRef.current.textColor, waveformStyle, labels: { seek: __( 'Seek' ), /* translators: %1$s: current audio time, %2$s: total audio duration. */ seekValueText: _x( '%1$s of %2$s', 'audio current time of total duration' ), }, onEnded: () => onEndedEvent?.(), showPlayButtonArtwork, } ); playerRef.current = player; const { destroy } = player; playerDestroy = destroy; } // Defer initialization so the element inherits the correct // text color, which is used to derive waveform colors. In the // editor iframe, theme styles (CSS custom properties) are // injected dynamically, so getComputedStyle may return the // default black on first render. // Using a requestAnimationFrame loop isn't sufficient to solve the issue. // TODO - find a better option than a setTimeout, so we're not relying on an arbitrary number. const timeoutId = setTimeout( init, 100 ); return () => { cancelled = true; clearTimeout( timeoutId ); playerRef.current = undefined; playerDestroy?.(); }; }, [ onEndedEvent, hasSrc, waveformStyle, color, gradient, textColor, showPlayButtonArtwork, ] ); useEffect( () => { if ( playerRef.current?.instance ) { const player = playerRef.current; if ( player ) { updatePlayerMetadata( player, { title, artist, image, imageAlt, }, showPlayButtonArtwork ); } } }, [ title, artist, image, imageAlt, showPlayButtonArtwork ] ); useEffect( () => { if ( src && playerRef.current?.instance ) { const wasPlaying = playerRef.current.instance.isPlaying; const promise = playerRef.current.instance.loadTrack( src, metadataRef.current.title, metadataRef.current.artist, { artwork: showPlayButtonArtwork ? undefined : metadataRef.current.image, artworkAlt: showPlayButtonArtwork ? '' : metadataRef.current.imageAlt, } ); promise.then( () => { if ( showPlayButtonArtwork && playerRef.current?.container ) { setupPlayButtonArtwork( playerRef.current.container, metadataRef.current.image ); } if ( ! wasPlaying ) { playerRef.current?.instance.pause(); } } ); } }, [ src, showPlayButtonArtwork ] ); return <div ref={ ref } className="wp-block-playlist__waveform-player" />; }