diff --git a/themes/Ying/assets/js/shortcodes.js b/themes/Ying/assets/js/shortcodes.js index 77d24d4c..de1da604 100644 --- a/themes/Ying/assets/js/shortcodes.js +++ b/themes/Ying/assets/js/shortcodes.js @@ -3,6 +3,189 @@ * 移植自 Hugo-Theme-Amigo */ +// ============================================================ +// Global Audio Manager - 全局音频/视频管理器 +// 确保所有音频控件互斥播放,PJAX 切换时保存进度 +// ============================================================ + +var AudioManager = { + currentPlaying: null, + currentType: null, + savedStates: {}, + + pauseAll: function() { + // Pause all voice bubbles + document.querySelectorAll('.amigo-voice-bubble.is-playing').forEach(function(bubble) { + if (bubble.__audio) { + try { + bubble.__audio.pause(); + bubble.__audio.currentTime = 0; + } catch (e) {} + } + bubble.classList.remove('is-playing'); + }); + + // Pause all music cards + document.querySelectorAll('.amigo-music-card[data-playing="true"]').forEach(function(card) { + if (card.__audio) { + try { card.__audio.pause(); } catch (e) {} + } + card.setAttribute('data-playing', 'false'); + var playBtn = card.querySelector('.amigo-music-card__play'); + if (playBtn) playBtn.setAttribute('aria-pressed', 'false'); + }); + + // Pause all live photos + document.querySelectorAll('.amigo-livephoto.is-playing').forEach(function(livePhoto) { + var video = livePhoto.querySelector('.amigo-livephoto-video'); + if (video) { + try { video.pause(); video.currentTime = 0; } catch (e) {} + } + livePhoto.classList.remove('is-playing'); + }); + + this.currentPlaying = null; + this.currentType = null; + }, + + registerPlay: function(element, type) { + if (this.currentPlaying && this.currentPlaying !== element) { + this.pauseAll(); + } + this.currentPlaying = element; + this.currentType = type; + }, + + saveState: function() { + var states = []; + + // Save music card states + document.querySelectorAll('.amigo-music-card').forEach(function(card, index) { + var audio = card.__audio; + if (audio && audio.src) { + states.push({ + type: 'music', + src: card.dataset.src, + currentTime: audio.currentTime, + isPlaying: card.getAttribute('data-playing') === 'true', + index: index + }); + } + }); + + // Save live photo states + document.querySelectorAll('.amigo-livephoto').forEach(function(livePhoto, index) { + var video = livePhoto.querySelector('.amigo-livephoto-video'); + if (video && video.src) { + states.push({ + type: 'livephoto', + src: livePhoto.dataset.src, + currentTime: video.currentTime, + isPlaying: livePhoto.classList.contains('is-playing'), + index: index + }); + } + }); + + this.savedStates[window.location.pathname] = states; + }, + + restoreState: function() { + var path = window.location.pathname; + var states = this.savedStates[path]; + if (!states || states.length === 0) return; + + states.forEach(function(state) { + if (state.type === 'music') { + var cards = document.querySelectorAll('.amigo-music-card'); + var card = cards[state.index]; + if (card && card.__audio && card.dataset.src === state.src) { + var audio = card.__audio; + var playBtn = card.querySelector('.amigo-music-card__play'); + var seekInput = card.querySelector('.amigo-music-card__seek'); + var currentTimeEl = card.querySelector('.amigo-music-card__time--current'); + + audio.addEventListener('loadedmetadata', function onLoaded() { + audio.currentTime = state.currentTime; + audio.removeEventListener('loadedmetadata', onLoaded); + + if (currentTimeEl) { + currentTimeEl.textContent = formatTime(state.currentTime); + } + if (seekInput && audio.duration) { + var progress = (state.currentTime / audio.duration) * 100; + seekInput.value = (state.currentTime / audio.duration * 1000).toString(); + card.style.setProperty('--music-progress', progress + '%'); + } + + if (state.isPlaying) { + audio.play().catch(function() {}); + card.setAttribute('data-playing', 'true'); + if (playBtn) playBtn.setAttribute('aria-pressed', 'true'); + AudioManager.currentPlaying = card; + AudioManager.currentType = 'music'; + } + }); + + if (audio.readyState >= 1) { + audio.currentTime = state.currentTime; + if (currentTimeEl) { + currentTimeEl.textContent = formatTime(state.currentTime); + } + if (seekInput && audio.duration) { + var progress = (state.currentTime / audio.duration) * 100; + seekInput.value = (state.currentTime / audio.duration * 1000).toString(); + card.style.setProperty('--music-progress', progress + '%'); + } + if (state.isPlaying) { + audio.play().catch(function() {}); + card.setAttribute('data-playing', 'true'); + if (playBtn) playBtn.setAttribute('aria-pressed', 'true'); + AudioManager.currentPlaying = card; + AudioManager.currentType = 'music'; + } + } + } + } else if (state.type === 'livephoto') { + var photos = document.querySelectorAll('.amigo-livephoto'); + var livePhoto = photos[state.index]; + if (livePhoto && livePhoto.dataset.src === state.src) { + var video = livePhoto.querySelector('.amigo-livephoto-video'); + if (video) { + var src = livePhoto.dataset.src || ''; + if (src && !video.getAttribute('src')) { + video.setAttribute('src', src); + video.src = src; + } + + video.addEventListener('loadedmetadata', function onLoaded() { + video.currentTime = state.currentTime; + video.removeEventListener('loadedmetadata', onLoaded); + + if (state.isPlaying) { + video.play().catch(function() {}); + livePhoto.classList.add('is-playing'); + AudioManager.currentPlaying = livePhoto; + AudioManager.currentType = 'livephoto'; + } + }); + + if (video.readyState >= 1) { + video.currentTime = state.currentTime; + if (state.isPlaying) { + video.play().catch(function() {}); + livePhoto.classList.add('is-playing'); + AudioManager.currentPlaying = livePhoto; + AudioManager.currentType = 'livephoto'; + } + } + } + } + } + }); + } +}; + // ============================================================ // Live Photo // ============================================================ @@ -47,6 +230,7 @@ function initLivePhotoShortcodes() { function playVideo() { ensureLoaded(); + AudioManager.registerPlay(livePhoto, 'livephoto'); livePhoto.classList.add('is-playing'); try { video.play(); } catch (e) {} } @@ -54,6 +238,10 @@ function initLivePhotoShortcodes() { function stopVideo() { livePhoto.classList.remove('is-playing'); try { video.pause(); video.currentTime = 0; } catch (e) {} + if (AudioManager.currentPlaying === livePhoto) { + AudioManager.currentPlaying = null; + AudioManager.currentType = null; + } } function togglePlay() { @@ -94,6 +282,16 @@ function initLivePhotoShortcodes() { }); } + // Video ended + video.addEventListener('ended', function() { + livePhoto.classList.remove('is-playing'); + isUserToggled = false; + if (AudioManager.currentPlaying === livePhoto) { + AudioManager.currentPlaying = null; + AudioManager.currentType = null; + } + }); + // Initialize muted state setMuted(livePhoto.classList.contains('is-muted')); }); @@ -116,6 +314,7 @@ function initVoiceBubbles() { // Load audio duration if (audioSrc) { audio = new Audio(audioSrc); + bubble.__audio = audio; audio.addEventListener('loadedmetadata', function() { var duration = Math.round(audio.duration); if (durationSpan) { @@ -132,14 +331,12 @@ function initVoiceBubbles() { audio.currentTime = 0; bubble.classList.remove('is-playing'); isPlaying = false; + if (AudioManager.currentPlaying === bubble) { + AudioManager.currentPlaying = null; + AudioManager.currentType = null; + } } else { - // Stop other playing voices - document.querySelectorAll('.amigo-voice-bubble.is-playing').forEach(function(other) { - if (other !== bubble) { - other.classList.remove('is-playing'); - } - }); - + AudioManager.registerPlay(bubble, 'voice'); audio.play(); bubble.classList.add('is-playing'); isPlaying = true; @@ -149,6 +346,10 @@ function initVoiceBubbles() { audio.addEventListener('ended', function() { bubble.classList.remove('is-playing'); isPlaying = false; + if (AudioManager.currentPlaying === bubble) { + AudioManager.currentPlaying = null; + AudioManager.currentType = null; + } }); }); } @@ -198,20 +399,12 @@ function initMusicCards() { card.setAttribute('data-playing', 'false'); if (playBtn) playBtn.setAttribute('aria-pressed', 'false'); isPlaying = false; + if (AudioManager.currentPlaying === card) { + AudioManager.currentPlaying = null; + AudioManager.currentType = null; + } } else { - // Stop other playing music - document.querySelectorAll('.amigo-music-card[data-playing="true"]').forEach(function(other) { - if (other !== card) { - other.setAttribute('data-playing', 'false'); - var otherPlayBtn = other.querySelector('.amigo-music-card__play'); - if (otherPlayBtn) otherPlayBtn.setAttribute('aria-pressed', 'false'); - var otherAudio = other.__audio; - if (otherAudio) { - try { otherAudio.pause(); } catch(e) {} - } - } - }); - + AudioManager.registerPlay(card, 'music'); audio.play(); card.setAttribute('data-playing', 'true'); if (playBtn) playBtn.setAttribute('aria-pressed', 'true'); @@ -244,6 +437,10 @@ function initMusicCards() { card.setAttribute('data-playing', 'false'); if (playBtn) playBtn.setAttribute('aria-pressed', 'false'); isPlaying = false; + if (AudioManager.currentPlaying === card) { + AudioManager.currentPlaying = null; + AudioManager.currentType = null; + } }); }); } @@ -298,9 +495,15 @@ function cleanupLivePhotos() { } function cleanupAllShortcodes() { + // Save playback state before cleanup + AudioManager.saveState(); + cleanupVoiceBubbles(); cleanupMusicCards(); cleanupLivePhotos(); + + AudioManager.currentPlaying = null; + AudioManager.currentType = null; } // ============================================================ @@ -311,6 +514,11 @@ function initAllShortcodes() { initLivePhotoShortcodes(); initVoiceBubbles(); initMusicCards(); + + // Restore playback state after initialization + setTimeout(function() { + AudioManager.restoreState(); + }, 50); } // DOMContentLoaded