This commit is contained in:
Vaica committed 2026-06-25 19:35:38 +08:00
1 parent 3a8db8fb12
commit adce55986d
2 files changed
+390 -352

No files matched your search

+237 -347
View File
@@ -8,281 +8,277 @@
// 确保所有音频控件互斥播放,PJAX 切换时保存进度
// ============================================================
var AudioManager = {
currentPlaying: null,
currentType: null,
savedStates: {},
// 全局音乐播放器 - PJAX 持久化
globalAudio: null,
globalMusicSrc: null,
globalMusicCard: null,
// Session storage key
STORAGE_KEY: 'amigo_music_state',
// 持久化 AudioManager 对象本身(防止脚本重载时丢失方法)
// 第一次加载:定义 AudioManager
// 后续加载(PJAX 切换):合并新方法到已存在的对象
if (!window.AudioManager) {
window.AudioManager = {};
}
// 获取当前页面的存储 key(基于 URL path)
_getPageKey: function() {
return window.location.pathname;
},
// 用一个 IIFE 来定义 AudioManager 的方法
(function() {
var AM = window.AudioManager;
init: function() {
// 创建全局音频元素
if (!this.globalAudio) {
this.globalAudio = new Audio();
this.globalAudio.preload = 'metadata';
// 监听全局音频事件
this.globalAudio.addEventListener('timeupdate', this._onTimeUpdate.bind(this));
this.globalAudio.addEventListener('ended', this._onEnded.bind(this));
this.globalAudio.addEventListener('loadedmetadata', this._onLoadedMetadata.bind(this));
// 一次性定义所有方法
AM.getAudioState = function() {
if (!window.__amigoAudioState) {
window.__amigoAudioState = {
audio: null,
src: null,
isPlaying: false,
currentTime: 0,
attached: false
};
}
},
return window.__amigoAudioState;
};
// 保存当前播放状态到 sessionStorage(按页面保存)
saveToSession: function() {
if (!this.globalAudio || !this.globalMusicSrc) return;
AM._getAudio = function() {
return this.getAudioState().audio;
};
var pageKey = this._getPageKey();
var allStates = this._loadAllStates();
AM._getSrc = function() {
return this.getAudioState().src;
};
allStates[pageKey] = {
src: this.globalMusicSrc,
currentTime: this.globalAudio.currentTime,
isPlaying: !this.globalAudio.paused,
AM._setSrc = function(src) {
this.getAudioState().src = src;
};
AM.init = function() {
var state = this.getAudioState();
if (!state.audio) {
state.audio = new Audio();
state.audio.preload = 'metadata';
if (!state.attached) {
state.audio.style.display = 'none';
state.audio.setAttribute('data-amigo-music', 'true');
document.body.appendChild(state.audio);
state.attached = true;
}
var self = this;
state.audio.addEventListener('timeupdate', function() { self._onTimeUpdate(); });
state.audio.addEventListener('ended', function() { self._onEnded(); });
state.audio.addEventListener('loadedmetadata', function() { self._onLoadedMetadata(); });
}
};
AM.saveToSession = function() {
var audio = this._getAudio();
var src = this._getSrc();
console.log('[AudioManager] saveToSession called, audio:', !!audio, 'src:', src, 'isPlaying:', audio ? !audio.paused : false);
if (!audio || !src) {
console.log('[AudioManager] saveToSession skipped - no audio or src');
return;
}
var state = {
src: src,
currentTime: audio.currentTime,
isPlaying: !audio.paused,
name: this.globalMusicCard ? this.globalMusicCard.dataset.name : '',
artist: this.globalMusicCard ? this.globalMusicCard.dataset.artist : ''
artist: this.globalMusicCard ? this.globalMusicCard.dataset.artist : '',
savedAt: Date.now()
};
console.log('[AudioManager] saving state:', JSON.stringify(state));
try {
sessionStorage.setItem(this.STORAGE_KEY, JSON.stringify(allStates));
sessionStorage.setItem(this.STORAGE_KEY, JSON.stringify(state));
} catch (e) {
console.warn('Failed to save music state:', e);
}
},
};
// 加载所有页面的状态
_loadAllStates: function() {
AM.restoreFromSession = function() {
try {
var stored = sessionStorage.getItem(this.STORAGE_KEY);
if (!stored) return {};
if (!stored) return null;
return JSON.parse(stored);
} catch (e) {
return {};
return null;
}
},
};
// 从 sessionStorage 恢复当前页面的播放状态
restoreFromSession: function() {
var pageKey = this._getPageKey();
var allStates = this._loadAllStates();
var state = allStates[pageKey];
if (!state || !state.src) return null;
return state;
},
// 清除当前页面的播放状态
clearSession: function() {
var pageKey = this._getPageKey();
var allStates = this._loadAllStates();
delete allStates[pageKey];
try {
sessionStorage.setItem(this.STORAGE_KEY, JSON.stringify(allStates));
} catch (e) {}
},
// 重置播放器到初始状态(刷新页面/非 PJAX 导航时调用)
reset: function() {
if (this.globalAudio) {
this.globalAudio.pause();
this.globalAudio.currentTime = 0;
this.globalAudio.src = '';
}
this.globalMusicSrc = null;
this.globalMusicCard = null;
AM.clearSession = function() {
sessionStorage.removeItem(this.STORAGE_KEY);
},
};
// 恢复播放状态 - 在 PJAX 切换后调用
restorePlayingState: function() {
AM.reset = function() {
console.log('[AudioManager] reset called');
var audio = this._getAudio();
if (audio) {
audio.pause();
audio.currentTime = 0;
audio.src = '';
}
this._setSrc(null);
console.log('[AudioManager] reset done, src:', this._getSrc());
this.globalMusicCard = null;
this.clearSession();
};
AM.restorePlayingState = function() {
var state = this.restoreFromSession();
if (!state || !state.src) {
// 当前页面没有保存的状态,保持当前播放状态(继续播放)
if (!state || !state.src) return;
var audio = this._getAudio();
var currentSrc = this._getSrc();
if (currentSrc === state.src && audio && audio.src) {
this._syncCardUI(state.src, state.isPlaying);
return;
}
if (audio && !audio.paused && currentSrc !== state.src) {
audio.pause();
}
this._setSrc(state.src);
if (!audio.src || audio.src !== state.src) {
audio.src = state.src;
}
var targetCard = this._findCardBySrc(state.src);
if (targetCard) {
this.globalMusicCard = targetCard;
this._updateUI(targetCard, state.isPlaying);
}
var self = this;
// 找到对应的音乐卡片
var cards = document.querySelectorAll('.amigo-music-card');
var targetCard = null;
for (var i = 0; i < cards.length; i++) {
var card = cards[i];
if (card.dataset.src === state.src) {
targetCard = card;
break;
var doRestore = function() {
if (state.currentTime > 0 && state.currentTime < audio.duration) {
audio.currentTime = state.currentTime;
}
}
if (!targetCard) {
// 当前页面没有这个音乐卡片,保持当前播放状态(继续播放)
return;
}
// 设置全局音频
this.globalMusicSrc = state.src;
this.globalMusicCard = targetCard;
this.globalAudio.src = state.src;
// 更新 UI(恢复原来的播放状态)
this._updateUI(targetCard, state.isPlaying);
// 等待元数据加载完成
this.globalAudio.addEventListener('loadedmetadata', function onLoaded() {
self.globalAudio.removeEventListener('loadedmetadata', onLoaded);
if (state.currentTime > 0 && state.currentTime < self.globalAudio.duration) {
self.globalAudio.currentTime = state.currentTime;
}
self._onTimeUpdate();
// 如果之前在播放,继续播放
if (state.isPlaying && self.globalAudio.paused) {
self.globalAudio.play().catch(function() {});
if (state.isPlaying && audio.paused) {
audio.play().catch(function() {});
}
});
};
if (this.globalAudio.readyState >= 1) {
if (state.currentTime > 0 && state.currentTime < this.globalAudio.duration) {
this.globalAudio.currentTime = state.currentTime;
}
this._onTimeUpdate();
// 如果之前在播放,继续播放
if (state.isPlaying && this.globalAudio.paused) {
this.globalAudio.play().catch(function() {});
if (audio.readyState >= 1) {
doRestore();
} else {
audio.addEventListener('loadedmetadata', function onLoaded() {
audio.removeEventListener('loadedmetadata', onLoaded);
doRestore();
});
}
};
AM._findCardBySrc = function(src) {
var cards = document.querySelectorAll('.amigo-music-card');
for (var i = 0; i < cards.length; i++) {
if (cards[i].dataset.src === src) {
return cards[i];
}
}
},
return null;
};
// 播放指定音乐卡片
playMusic: function(card) {
AM._syncCardUI = function(src, isPlaying) {
var card = this._findCardBySrc(src);
if (card) {
this.globalMusicCard = card;
this._updateUI(card, isPlaying);
this._onTimeUpdate();
}
};
AM.playMusic = function(card) {
if (!card) return;
var audioSrc = card.dataset.src;
if (!audioSrc) return;
// 如果是同一首歌,切换播放/暂停
if (this.globalMusicSrc === audioSrc && this.globalAudio) {
if (this.globalAudio.paused) {
this.globalAudio.play().catch(function() {});
console.log('[AudioManager] playMusic called:', audioSrc);
var audio = this._getAudio();
var currentSrc = this._getSrc();
if (currentSrc === audioSrc && audio) {
console.log('[AudioManager] Same song, toggling play/pause');
if (audio.paused) {
audio.play().catch(function() {});
this.globalMusicCard = card;
this._updateUI(card, true);
} else {
this.globalAudio.pause();
audio.pause();
this._updateUI(card, false);
}
return;
}
// 暂停其他所有音频
this.pauseAll();
// 设置新的音频源
this.globalMusicSrc = audioSrc;
this._setSrc(audioSrc);
console.log('[AudioManager] set src:', audioSrc, 'state.src:', this._getSrc());
this.globalMusicCard = card;
this.globalAudio.src = audioSrc;
// 更新 UI
audio.src = audioSrc;
this._updateUI(card, true);
// 尝试播放
var self = this;
this.globalAudio.play().then(function() {
// 保存状态
audio.play().then(function() {
console.log('[AudioManager] Playback started, saving session');
self.saveToSession();
}).catch(function(err) {
console.warn('Play failed:', err);
});
},
};
// 跳转到指定时间
seekTo: function(time) {
if (this.globalAudio) {
this.globalAudio.currentTime = time;
AM.seekTo = function(time) {
var audio = this._getAudio();
if (audio) {
audio.currentTime = time;
this.saveToSession();
}
},
};
// 更新时间回调
_onTimeUpdate: function() {
if (!this.globalAudio || !this.globalMusicCard) return;
// 如果正在拖动进度条,跳过 UI 更新
if (this.globalAudio.__seeking) return;
var currentTime = this.globalAudio.currentTime;
var duration = this.globalAudio.duration;
AM._onTimeUpdate = function() {
var audio = this._getAudio();
if (!audio || !this.globalMusicCard) return;
if (audio.__seeking) return;
var currentTime = audio.currentTime;
var duration = audio.duration;
if (isNaN(duration) || duration === 0) return;
// 更新进度条
var progress = (currentTime / duration) * 100;
this.globalMusicCard.style.setProperty('--music-progress', progress + '%');
// 更新时间显示
var currentTimeEl = this.globalMusicCard.querySelector('.amigo-music-card__time--current');
if (currentTimeEl) {
currentTimeEl.textContent = formatTime(currentTime);
}
if (currentTimeEl) currentTimeEl.textContent = formatTime(currentTime);
// 更新总时长(如果还没更新过)
var durationTimeEl = this.globalMusicCard.querySelector('.amigo-music-card__time--duration');
if (durationTimeEl && durationTimeEl.textContent === '--:--') {
durationTimeEl.textContent = formatTime(duration);
}
// 定期保存状态(每5秒)
if (Math.floor(currentTime) % 5 === 0) {
this.saveToSession();
}
},
};
// 加载完成回调
_onLoadedMetadata: function() {
if (!this.globalAudio || !this.globalMusicCard) return;
var duration = this.globalAudio.duration;
// 更新总时长显示
AM._onLoadedMetadata = function() {
var audio = this._getAudio();
if (!audio || !this.globalMusicCard) return;
var duration = audio.duration;
var durationTimeEl = this.globalMusicCard.querySelector('.amigo-music-card__time--duration');
if (durationTimeEl) {
durationTimeEl.textContent = formatTime(duration);
}
},
if (durationTimeEl) durationTimeEl.textContent = formatTime(duration);
};
// 播放结束回调
_onEnded: function() {
AM._onEnded = function() {
if (this.globalMusicCard) {
this._updateUI(this.globalMusicCard, false);
}
this.globalMusicCard = null;
this._setSrc(null);
this.clearSession();
},
};
// 更新 UI 状态
_updateUI: function(card, isPlaying) {
AM._updateUI = function(card, isPlaying) {
if (!card) return;
card.setAttribute('data-playing', isPlaying ? 'true' : 'false');
var playBtn = card.querySelector('.amigo-music-card__play');
if (playBtn) {
playBtn.setAttribute('aria-pressed', isPlaying ? 'true' : 'false');
}
if (playBtn) playBtn.setAttribute('aria-pressed', isPlaying ? 'true' : 'false');
if (isPlaying) {
this.currentPlaying = card;
@@ -291,178 +287,52 @@ var AudioManager = {
this.currentPlaying = null;
this.currentType = null;
}
},
};
pauseAll: function() {
// Pause all voice bubbles
AM.pauseAll = function() {
document.querySelectorAll('.amigo-voice-bubble.is-playing').forEach(function(bubble) {
if (bubble.__audio) {
try {
bubble.__audio.pause();
bubble.__audio.currentTime = 0;
} catch (e) {}
try { bubble.__audio.pause(); bubble.__audio.currentTime = 0; } catch (e) {}
}
bubble.classList.remove('is-playing');
});
// Pause global music
if (this.globalAudio && !this.globalAudio.paused) {
this.globalAudio.pause();
var audio = this._getAudio();
if (audio && !audio.paused) {
audio.pause();
if (this.globalMusicCard) {
this._updateUI(this.globalMusicCard, 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) {}
}
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) {
AM.registerPlay = function(element, type) {
if (this.currentPlaying && this.currentPlaying !== element) {
this.pauseAll();
// 触发其他播放的暂停
}
this.currentPlaying = element;
this.currentType = type;
},
};
saveState: function() {
var states = [];
// 初始化基本字段
if (typeof AM.currentPlaying === 'undefined') AM.currentPlaying = null;
if (typeof AM.currentType === 'undefined') AM.currentType = null;
if (typeof AM.savedStates === 'undefined') AM.savedStates = {};
if (typeof AM.STORAGE_KEY === 'undefined') AM.STORAGE_KEY = 'amigo_music_state';
})();
// 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
});
}
});
// 保留对 window.AudioManager 的引用
var AudioManager = window.AudioManager;
// 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
@@ -671,22 +541,25 @@ function initMusicCards() {
// Seek - 使用 AudioManager
if (seekInput) {
seekInput.addEventListener('input', function() {
if (AudioManager.globalAudio && AudioManager.globalAudio.duration) {
var time = (parseFloat(seekInput.value) / 1000) * AudioManager.globalAudio.duration;
var audio = AudioManager._getAudio();
if (audio && audio.duration) {
var time = (parseFloat(seekInput.value) / 1000) * audio.duration;
AudioManager.seekTo(time);
}
});
// 拖动时暂停更新
seekInput.addEventListener('mousedown', function() {
if (AudioManager.globalAudio) {
AudioManager.globalAudio.__seeking = true;
var audio = AudioManager._getAudio();
if (audio) {
audio.__seeking = true;
}
});
seekInput.addEventListener('mouseup', function() {
if (AudioManager.globalAudio) {
AudioManager.globalAudio.__seeking = false;
var audio = AudioManager._getAudio();
if (audio) {
audio.__seeking = false;
}
});
}
@@ -758,7 +631,15 @@ function cleanupAllShortcodes() {
// Initialize all shortcodes
// ============================================================
// 防止 initAllShortcodes 在 PJAX 切换时被重复调用
var __shortcodesInitLock = false;
function initAllShortcodes() {
console.log('[AudioManager] initAllShortcodes called, lock:', __shortcodesInitLock);
// 防止重复初始化
if (__shortcodesInitLock) return;
__shortcodesInitLock = true;
// 初始化 AudioManager(创建全局音频元素)
AudioManager.init();
@@ -766,17 +647,21 @@ function initAllShortcodes() {
initVoiceBubbles();
initMusicCards();
// 检查是否是 PJAX 导航
if (window.__isPjaxNavigation) {
// PJAX 导航:恢复播放状态(进度和播放状态)
// 检查 sessionStorage 中是否有保存的播放状态
// 如果有,恢复播放(无论是 PJAX 切换还是脚本重新加载)
// 如果没有,重置播放器(首次加载或刷新页面)
var savedState = AudioManager.restoreFromSession();
console.log('[AudioManager] initAllShortcodes - savedState:', savedState ? JSON.stringify(savedState) : null);
if (savedState && savedState.src) {
setTimeout(function() {
AudioManager.restorePlayingState();
// 延迟释放锁,避免短时间内重复触发
setTimeout(function() { __shortcodesInitLock = false; }, 500);
}, 100);
// 重置标志
window.__isPjaxNavigation = false;
} else {
// 非 PJAX 导航(刷新页面/跳转):重置播放器到初始状态
// 没有保存的状态,重置播放器到初始状态
AudioManager.reset();
__shortcodesInitLock = false;
}
}
@@ -796,8 +681,13 @@ document.addEventListener('pjax:beforeSend', function() {
window.__isPjaxNavigation = true;
cleanupAllShortcodes();
});
document.addEventListener('pjax:complete', initAllShortcodes);
document.addEventListener('pjax:success', initAllShortcodes);
document.addEventListener('pjax:complete', function() {
initAllShortcodes();
});
document.addEventListener('pjax:success', function() {
// pjax:success 在 pjax:complete 之前触发,已经被锁过滤
initAllShortcodes();
});
// Also listen for custom events that some PJAX libraries fire
document.addEventListener('page:loaded', initAllShortcodes);