383 lines
10 KiB
JavaScript
383 lines
10 KiB
JavaScript
( function() {
|
|
function formatTime( totalSeconds ) {
|
|
if ( ! Number.isFinite( totalSeconds ) || totalSeconds < 0 ) {
|
|
return '0:00';
|
|
}
|
|
|
|
const minutes = Math.floor( totalSeconds / 60 );
|
|
const seconds = Math.floor( totalSeconds % 60 );
|
|
return minutes + ':' + String( seconds ).padStart( 2, '0' );
|
|
}
|
|
|
|
function initPlayers( root ) {
|
|
root.querySelectorAll( '[data-map-player]' ).forEach( initPlayer );
|
|
}
|
|
|
|
function initPlayer( root ) {
|
|
if ( root.dataset.mapInitialized === 'true' ) {
|
|
return;
|
|
}
|
|
|
|
const audio = root.querySelector( '[data-map-audio]' );
|
|
const toggle = root.querySelector( '[data-map-toggle]' );
|
|
const previousButton = root.querySelector( '[data-map-previous]' );
|
|
const nextButton = root.querySelector( '[data-map-next]' );
|
|
const repeatButton = root.querySelector( '[data-map-repeat]' );
|
|
const progress = root.querySelector( '[data-map-progress]' );
|
|
const currentTime = root.querySelector( '[data-map-current]' );
|
|
const duration = root.querySelector( '[data-map-duration]' );
|
|
const title = root.querySelector( '[data-map-title]' );
|
|
const coverWrap = root.querySelector( '.map-player__cover-wrap' );
|
|
const cover = root.querySelector( '[data-map-cover]' );
|
|
const toggleText = root.querySelector( '.map-player__toggle-text' );
|
|
const editorNotice = root.querySelector( '[data-map-editor-notice]' );
|
|
const isEditorPreview = root.dataset.mapEditorPreview === 'true';
|
|
const showCoverSetting = root.dataset.mapShowCoverSetting === 'true';
|
|
const trackedHashes = new Set();
|
|
let isRepeatEnabled = root.dataset.mapRepeatEnabled === 'true';
|
|
let currentIndex = Number( root.dataset.mapCurrentIndex || 0 );
|
|
let playlist = [];
|
|
|
|
if ( ! audio || ! toggle || ! progress || ! currentTime || ! duration ) {
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const parsedPlaylist = JSON.parse( root.dataset.mapPlaylist || '[]' );
|
|
|
|
if ( Array.isArray( parsedPlaylist ) ) {
|
|
playlist = parsedPlaylist.filter( function( track ) {
|
|
return track && track.src;
|
|
} );
|
|
}
|
|
} catch ( error ) {
|
|
playlist = [];
|
|
}
|
|
|
|
if ( ! playlist.length && root.dataset.trackSrc ) {
|
|
playlist = [
|
|
{
|
|
src: root.dataset.trackSrc,
|
|
title: root.dataset.trackTitle || 'Untitled Track',
|
|
image: cover && cover.getAttribute( 'src' ) ? cover.getAttribute( 'src' ) : '',
|
|
hash: root.dataset.trackHash || '',
|
|
nonce: root.dataset.trackNonce || ''
|
|
}
|
|
];
|
|
}
|
|
|
|
if ( ! playlist.length ) {
|
|
return;
|
|
}
|
|
|
|
currentIndex = Math.max( 0, Math.min( currentIndex, playlist.length - 1 ) );
|
|
root.dataset.mapInitialized = 'true';
|
|
|
|
function getCurrentTrack() {
|
|
return playlist[ currentIndex ] || playlist[ 0 ];
|
|
}
|
|
|
|
function setEditorNotice( message ) {
|
|
if ( ! editorNotice ) {
|
|
return;
|
|
}
|
|
|
|
editorNotice.textContent = message;
|
|
}
|
|
|
|
function syncTrackDataset( track ) {
|
|
root.dataset.mapCurrentIndex = String( currentIndex );
|
|
root.dataset.trackSrc = track.src || '';
|
|
root.dataset.trackTitle = track.title || '';
|
|
root.dataset.trackHash = track.hash || '';
|
|
root.dataset.trackNonce = track.nonce || '';
|
|
}
|
|
|
|
function updateCoverUI( track ) {
|
|
const shouldShowCover = showCoverSetting && !! track.image;
|
|
|
|
root.classList.toggle( 'map-player--no-cover', ! shouldShowCover );
|
|
root.dataset.mapHasCover = shouldShowCover ? 'true' : 'false';
|
|
|
|
if ( ! coverWrap || ! cover ) {
|
|
return;
|
|
}
|
|
|
|
coverWrap.style.display = shouldShowCover ? '' : 'none';
|
|
|
|
if ( shouldShowCover ) {
|
|
cover.src = track.image;
|
|
cover.alt = track.title || 'Audio cover';
|
|
}
|
|
}
|
|
|
|
function updateTransportState() {
|
|
const atFirstTrack = currentIndex === 0;
|
|
const atLastTrack = currentIndex === playlist.length - 1;
|
|
|
|
if ( previousButton ) {
|
|
previousButton.disabled = atFirstTrack;
|
|
}
|
|
|
|
if ( nextButton ) {
|
|
nextButton.disabled = atLastTrack;
|
|
}
|
|
|
|
if ( repeatButton ) {
|
|
repeatButton.classList.toggle( 'is-active', isRepeatEnabled );
|
|
repeatButton.setAttribute( 'aria-pressed', isRepeatEnabled ? 'true' : 'false' );
|
|
}
|
|
}
|
|
|
|
function updateProgressUI() {
|
|
if ( ! Number.isFinite( audio.duration ) || audio.duration <= 0 ) {
|
|
progress.value = 0;
|
|
duration.textContent = '0:00';
|
|
currentTime.textContent = '0:00';
|
|
progress.style.setProperty( '--map-progress-percent', '0%' );
|
|
return;
|
|
}
|
|
|
|
const percent = ( audio.currentTime / audio.duration ) * 100;
|
|
progress.value = percent;
|
|
progress.style.setProperty( '--map-progress-percent', percent + '%' );
|
|
currentTime.textContent = formatTime( audio.currentTime );
|
|
duration.textContent = formatTime( audio.duration );
|
|
}
|
|
|
|
function setPlayingState( isPlaying ) {
|
|
root.classList.toggle( 'is-playing', isPlaying );
|
|
toggle.setAttribute( 'aria-pressed', isPlaying ? 'true' : 'false' );
|
|
toggle.setAttribute( 'aria-label', isPlaying ? 'Pause audio' : 'Play audio' );
|
|
toggleText.textContent = isPlaying ? 'Pause' : 'Play';
|
|
}
|
|
|
|
function applyTrack( options ) {
|
|
const track = getCurrentTrack();
|
|
const autoplay = !! ( options && options.autoplay );
|
|
|
|
if ( title ) {
|
|
title.textContent = track.title || 'Untitled Track';
|
|
}
|
|
|
|
updateCoverUI( track );
|
|
updateTransportState();
|
|
syncTrackDataset( track );
|
|
|
|
if ( audio.getAttribute( 'src' ) !== track.src ) {
|
|
audio.setAttribute( 'src', track.src );
|
|
}
|
|
|
|
if ( audio.src !== track.src ) {
|
|
audio.src = track.src;
|
|
}
|
|
|
|
audio.currentTime = 0;
|
|
audio.load();
|
|
updateProgressUI();
|
|
|
|
if ( autoplay ) {
|
|
audio.play().catch( function() {
|
|
if ( isEditorPreview ) {
|
|
setEditorNotice( 'Playback preview is limited in the editor. Test full playback on the frontend.' );
|
|
}
|
|
} );
|
|
}
|
|
}
|
|
|
|
function ensureAudioLoaded() {
|
|
const track = getCurrentTrack();
|
|
|
|
if ( ! track || ! track.src ) {
|
|
return;
|
|
}
|
|
|
|
if ( audio.getAttribute( 'src' ) !== track.src ) {
|
|
applyTrack( { autoplay: false } );
|
|
return;
|
|
}
|
|
|
|
if ( audio.networkState === HTMLMediaElement.NETWORK_EMPTY || ! Number.isFinite( audio.duration ) || audio.duration <= 0 ) {
|
|
try {
|
|
audio.load();
|
|
} catch ( error ) {
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
function trackPlay() {
|
|
const track = getCurrentTrack();
|
|
|
|
if ( ! track || ! track.src || ! track.hash || ! track.nonce || ! root.dataset.restEndpoint || trackedHashes.has( track.hash ) ) {
|
|
return;
|
|
}
|
|
|
|
trackedHashes.add( track.hash );
|
|
|
|
window.fetch( root.dataset.restEndpoint, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json'
|
|
},
|
|
body: JSON.stringify( {
|
|
src: track.src,
|
|
title: track.title || '',
|
|
hash: track.hash,
|
|
nonce: track.nonce
|
|
} ),
|
|
credentials: 'same-origin'
|
|
} ).catch( function() {
|
|
return null;
|
|
} );
|
|
}
|
|
|
|
function switchTrack( nextIndex, options ) {
|
|
if ( nextIndex < 0 || nextIndex >= playlist.length || nextIndex === currentIndex ) {
|
|
return;
|
|
}
|
|
|
|
currentIndex = nextIndex;
|
|
applyTrack( options );
|
|
}
|
|
|
|
toggle.addEventListener( 'click', function() {
|
|
ensureAudioLoaded();
|
|
|
|
if ( audio.paused ) {
|
|
audio.play().catch( function() {
|
|
if ( isEditorPreview ) {
|
|
setEditorNotice( 'Playback preview is limited in the editor. Test full playback on the frontend.' );
|
|
}
|
|
} );
|
|
return;
|
|
}
|
|
|
|
audio.pause();
|
|
} );
|
|
|
|
if ( previousButton ) {
|
|
previousButton.addEventListener( 'click', function() {
|
|
if ( previousButton.disabled ) {
|
|
return;
|
|
}
|
|
|
|
if ( audio.currentTime > 5 ) {
|
|
audio.currentTime = 0;
|
|
updateProgressUI();
|
|
return;
|
|
}
|
|
|
|
switchTrack( currentIndex - 1, { autoplay: ! audio.paused } );
|
|
} );
|
|
}
|
|
|
|
if ( nextButton ) {
|
|
nextButton.addEventListener( 'click', function() {
|
|
if ( nextButton.disabled ) {
|
|
return;
|
|
}
|
|
|
|
switchTrack( currentIndex + 1, { autoplay: ! audio.paused } );
|
|
} );
|
|
}
|
|
|
|
if ( repeatButton ) {
|
|
repeatButton.addEventListener( 'click', function() {
|
|
isRepeatEnabled = ! isRepeatEnabled;
|
|
root.dataset.mapRepeatEnabled = isRepeatEnabled ? 'true' : 'false';
|
|
updateTransportState();
|
|
} );
|
|
}
|
|
|
|
audio.addEventListener( 'play', function() {
|
|
setPlayingState( true );
|
|
trackPlay();
|
|
} );
|
|
|
|
audio.addEventListener( 'pause', function() {
|
|
setPlayingState( false );
|
|
} );
|
|
|
|
audio.addEventListener( 'loadedmetadata', updateProgressUI );
|
|
audio.addEventListener( 'durationchange', updateProgressUI );
|
|
audio.addEventListener( 'canplay', updateProgressUI );
|
|
audio.addEventListener( 'timeupdate', updateProgressUI );
|
|
audio.addEventListener( 'error', function() {
|
|
updateProgressUI();
|
|
setEditorNotice( 'Unable to load audio preview. Confirm the URL points directly to an audio file.' );
|
|
} );
|
|
audio.addEventListener( 'ended', function() {
|
|
if ( isRepeatEnabled ) {
|
|
audio.currentTime = 0;
|
|
audio.play().catch( function() {
|
|
return null;
|
|
} );
|
|
return;
|
|
}
|
|
|
|
if ( currentIndex < playlist.length - 1 ) {
|
|
switchTrack( currentIndex + 1, { autoplay: true } );
|
|
return;
|
|
}
|
|
|
|
setPlayingState( false );
|
|
audio.currentTime = 0;
|
|
updateProgressUI();
|
|
} );
|
|
|
|
progress.addEventListener( 'input', function() {
|
|
if ( ! Number.isFinite( audio.duration ) || audio.duration <= 0 ) {
|
|
return;
|
|
}
|
|
|
|
const targetTime = ( Number( progress.value ) / 100 ) * audio.duration;
|
|
audio.currentTime = targetTime;
|
|
updateProgressUI();
|
|
} );
|
|
|
|
applyTrack( { autoplay: false } );
|
|
}
|
|
|
|
function observePlayerMounts() {
|
|
if ( ! document.body || typeof MutationObserver === 'undefined' ) {
|
|
return;
|
|
}
|
|
|
|
const observer = new MutationObserver( function( mutations ) {
|
|
mutations.forEach( function( mutation ) {
|
|
mutation.addedNodes.forEach( function( node ) {
|
|
if ( ! node || node.nodeType !== 1 ) {
|
|
return;
|
|
}
|
|
|
|
if ( node.matches && node.matches( '[data-map-player]' ) ) {
|
|
initPlayer( node );
|
|
return;
|
|
}
|
|
|
|
if ( node.querySelectorAll ) {
|
|
initPlayers( node );
|
|
}
|
|
} );
|
|
} );
|
|
} );
|
|
|
|
observer.observe( document.body, {
|
|
childList: true,
|
|
subtree: true
|
|
} );
|
|
}
|
|
|
|
function boot() {
|
|
initPlayers( document );
|
|
observePlayerMounts();
|
|
}
|
|
|
|
if ( document.readyState === 'loading' ) {
|
|
document.addEventListener( 'DOMContentLoaded', boot );
|
|
} else {
|
|
boot();
|
|
}
|
|
} )();
|