feature 切换文章音乐继续播放
This commit is contained in:
1 parent
5651d494ba
commit
4c5dc5074f
1 file changed
+320
-77
@@ -12,6 +12,278 @@ var AudioManager = {
|
|||||||
currentPlaying: null,
|
currentPlaying: null,
|
||||||
currentType: null,
|
currentType: null,
|
||||||
savedStates: {},
|
savedStates: {},
|
||||||
|
// 全局音乐播放器 - PJAX 持久化
|
||||||
|
globalAudio: null,
|
||||||
|
globalMusicSrc: null,
|
||||||
|
globalMusicCard: null,
|
||||||
|
// Session storage key
|
||||||
|
STORAGE_KEY: 'amigo_music_state',
|
||||||
|
|
||||||
|
// 获取当前页面的存储 key(基于 URL path)
|
||||||
|
_getPageKey: function() {
|
||||||
|
return window.location.pathname;
|
||||||
|
},
|
||||||
|
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// 保存当前播放状态到 sessionStorage(按页面保存)
|
||||||
|
saveToSession: function() {
|
||||||
|
if (!this.globalAudio || !this.globalMusicSrc) return;
|
||||||
|
|
||||||
|
var pageKey = this._getPageKey();
|
||||||
|
var allStates = this._loadAllStates();
|
||||||
|
|
||||||
|
allStates[pageKey] = {
|
||||||
|
src: this.globalMusicSrc,
|
||||||
|
currentTime: this.globalAudio.currentTime,
|
||||||
|
isPlaying: !this.globalAudio.paused,
|
||||||
|
name: this.globalMusicCard ? this.globalMusicCard.dataset.name : '',
|
||||||
|
artist: this.globalMusicCard ? this.globalMusicCard.dataset.artist : ''
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
sessionStorage.setItem(this.STORAGE_KEY, JSON.stringify(allStates));
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('Failed to save music state:', e);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// 加载所有页面的状态
|
||||||
|
_loadAllStates: function() {
|
||||||
|
try {
|
||||||
|
var stored = sessionStorage.getItem(this.STORAGE_KEY);
|
||||||
|
if (!stored) return {};
|
||||||
|
return JSON.parse(stored);
|
||||||
|
} catch (e) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// 从 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 切换后调用
|
||||||
|
restorePlayingState: function() {
|
||||||
|
var state = this.restoreFromSession();
|
||||||
|
if (!state || !state.src) {
|
||||||
|
// 当前页面没有保存的状态,暂停播放
|
||||||
|
if (this.globalAudio && !this.globalAudio.paused) {
|
||||||
|
this.globalAudio.pause();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!targetCard) {
|
||||||
|
// 当前页面没有这个音乐卡片,暂停播放
|
||||||
|
if (this.globalAudio && !this.globalAudio.paused) {
|
||||||
|
this.globalAudio.pause();
|
||||||
|
}
|
||||||
|
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.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.play().catch(function() {});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// 播放指定音乐卡片
|
||||||
|
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() {});
|
||||||
|
this.globalMusicCard = card;
|
||||||
|
this._updateUI(card, true);
|
||||||
|
} else {
|
||||||
|
this.globalAudio.pause();
|
||||||
|
this._updateUI(card, false);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 暂停其他所有音频
|
||||||
|
this.pauseAll();
|
||||||
|
|
||||||
|
// 设置新的音频源
|
||||||
|
this.globalMusicSrc = audioSrc;
|
||||||
|
this.globalMusicCard = card;
|
||||||
|
this.globalAudio.src = audioSrc;
|
||||||
|
|
||||||
|
// 更新 UI
|
||||||
|
this._updateUI(card, true);
|
||||||
|
|
||||||
|
// 尝试播放
|
||||||
|
var self = this;
|
||||||
|
this.globalAudio.play().then(function() {
|
||||||
|
// 保存状态
|
||||||
|
self.saveToSession();
|
||||||
|
}).catch(function(err) {
|
||||||
|
console.warn('Play failed:', err);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
// 跳转到指定时间
|
||||||
|
seekTo: function(time) {
|
||||||
|
if (this.globalAudio) {
|
||||||
|
this.globalAudio.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;
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新总时长(如果还没更新过)
|
||||||
|
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;
|
||||||
|
|
||||||
|
// 更新总时长显示
|
||||||
|
var durationTimeEl = this.globalMusicCard.querySelector('.amigo-music-card__time--duration');
|
||||||
|
if (durationTimeEl) {
|
||||||
|
durationTimeEl.textContent = formatTime(duration);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// 播放结束回调
|
||||||
|
_onEnded: function() {
|
||||||
|
if (this.globalMusicCard) {
|
||||||
|
this._updateUI(this.globalMusicCard, false);
|
||||||
|
}
|
||||||
|
this.globalMusicCard = null;
|
||||||
|
this.clearSession();
|
||||||
|
},
|
||||||
|
|
||||||
|
// 更新 UI 状态
|
||||||
|
_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 (isPlaying) {
|
||||||
|
this.currentPlaying = card;
|
||||||
|
this.currentType = 'music';
|
||||||
|
} else if (this.globalMusicCard === card) {
|
||||||
|
this.currentPlaying = null;
|
||||||
|
this.currentType = null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
pauseAll: function() {
|
pauseAll: function() {
|
||||||
// Pause all voice bubbles
|
// Pause all voice bubbles
|
||||||
@@ -25,15 +297,13 @@ var AudioManager = {
|
|||||||
bubble.classList.remove('is-playing');
|
bubble.classList.remove('is-playing');
|
||||||
});
|
});
|
||||||
|
|
||||||
// Pause all music cards
|
// Pause global music
|
||||||
document.querySelectorAll('.amigo-music-card[data-playing="true"]').forEach(function(card) {
|
if (this.globalAudio && !this.globalAudio.paused) {
|
||||||
if (card.__audio) {
|
this.globalAudio.pause();
|
||||||
try { card.__audio.pause(); } catch (e) {}
|
if (this.globalMusicCard) {
|
||||||
|
this._updateUI(this.globalMusicCard, false);
|
||||||
}
|
}
|
||||||
card.setAttribute('data-playing', 'false');
|
}
|
||||||
var playBtn = card.querySelector('.amigo-music-card__play');
|
|
||||||
if (playBtn) playBtn.setAttribute('aria-pressed', 'false');
|
|
||||||
});
|
|
||||||
|
|
||||||
// Pause all live photos
|
// Pause all live photos
|
||||||
document.querySelectorAll('.amigo-livephoto.is-playing').forEach(function(livePhoto) {
|
document.querySelectorAll('.amigo-livephoto.is-playing').forEach(function(livePhoto) {
|
||||||
@@ -355,7 +625,8 @@ function initVoiceBubbles() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// Music Card
|
// Music Card - PJAX 持久化版本
|
||||||
|
// 使用全局 AudioManager 管理音乐播放
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
function initMusicCards() {
|
function initMusicCards() {
|
||||||
@@ -368,80 +639,49 @@ function initMusicCards() {
|
|||||||
var seekInput = card.querySelector('.amigo-music-card__seek');
|
var seekInput = card.querySelector('.amigo-music-card__seek');
|
||||||
var currentTime = card.querySelector('.amigo-music-card__time--current');
|
var currentTime = card.querySelector('.amigo-music-card__time--current');
|
||||||
var durationTime = card.querySelector('.amigo-music-card__time--duration');
|
var durationTime = card.querySelector('.amigo-music-card__time--duration');
|
||||||
var audio = null;
|
|
||||||
var isPlaying = false;
|
|
||||||
|
|
||||||
if (!audioSrc) return;
|
if (!audioSrc) return;
|
||||||
|
|
||||||
audio = new Audio(audioSrc);
|
// 不再创建单独的 Audio 对象,使用全局 AudioManager
|
||||||
card.__audio = audio;
|
// card.__audio = null;
|
||||||
|
|
||||||
// Load metadata
|
// 检查是否应该显示播放状态
|
||||||
audio.addEventListener('loadedmetadata', function() {
|
// (由 AudioManager 在 restorePlayingState 中处理)
|
||||||
var duration = formatTime(audio.duration);
|
|
||||||
if (durationTime) durationTime.textContent = duration;
|
|
||||||
});
|
|
||||||
|
|
||||||
// Update progress
|
// Play/Pause - 使用 AudioManager
|
||||||
audio.addEventListener('timeupdate', function() {
|
function togglePlay(e) {
|
||||||
if (currentTime) currentTime.textContent = formatTime(audio.currentTime);
|
if (e) e.stopPropagation();
|
||||||
if (seekInput && audio.duration) {
|
AudioManager.playMusic(card);
|
||||||
var progress = (audio.currentTime / audio.duration) * 100;
|
|
||||||
seekInput.value = (audio.currentTime / audio.duration * 1000).toString();
|
|
||||||
card.style.setProperty('--music-progress', progress + '%');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Play/Pause
|
|
||||||
function togglePlay() {
|
|
||||||
if (isPlaying) {
|
|
||||||
audio.pause();
|
|
||||||
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 {
|
|
||||||
AudioManager.registerPlay(card, 'music');
|
|
||||||
audio.play();
|
|
||||||
card.setAttribute('data-playing', 'true');
|
|
||||||
if (playBtn) playBtn.setAttribute('aria-pressed', 'true');
|
|
||||||
isPlaying = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (playBtn) {
|
if (playBtn) {
|
||||||
playBtn.addEventListener('click', function(e) {
|
playBtn.addEventListener('click', togglePlay);
|
||||||
e.stopPropagation();
|
|
||||||
togglePlay();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
card.addEventListener('click', togglePlay);
|
card.addEventListener('click', togglePlay);
|
||||||
|
|
||||||
// Seek
|
// Seek - 使用 AudioManager
|
||||||
if (seekInput) {
|
if (seekInput) {
|
||||||
seekInput.addEventListener('input', function() {
|
seekInput.addEventListener('input', function() {
|
||||||
if (audio.duration) {
|
if (AudioManager.globalAudio && AudioManager.globalAudio.duration) {
|
||||||
var progress = (parseFloat(seekInput.value) / 1000) * 100;
|
var time = (parseFloat(seekInput.value) / 1000) * AudioManager.globalAudio.duration;
|
||||||
audio.currentTime = (parseFloat(seekInput.value) / 1000) * audio.duration;
|
AudioManager.seekTo(time);
|
||||||
card.style.setProperty('--music-progress', progress + '%');
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 拖动时暂停更新
|
||||||
|
seekInput.addEventListener('mousedown', function() {
|
||||||
|
if (AudioManager.globalAudio) {
|
||||||
|
AudioManager.globalAudio.__seeking = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
seekInput.addEventListener('mouseup', function() {
|
||||||
|
if (AudioManager.globalAudio) {
|
||||||
|
AudioManager.globalAudio.__seeking = false;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ended
|
|
||||||
audio.addEventListener('ended', function() {
|
|
||||||
card.setAttribute('data-playing', 'false');
|
|
||||||
if (playBtn) playBtn.setAttribute('aria-pressed', 'false');
|
|
||||||
isPlaying = false;
|
|
||||||
if (AudioManager.currentPlaying === card) {
|
|
||||||
AudioManager.currentPlaying = null;
|
|
||||||
AudioManager.currentType = null;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -495,15 +735,15 @@ function cleanupLivePhotos() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function cleanupAllShortcodes() {
|
function cleanupAllShortcodes() {
|
||||||
// Save playback state before cleanup
|
// 保存当前播放状态到 sessionStorage(音乐不中断)
|
||||||
AudioManager.saveState();
|
AudioManager.saveToSession();
|
||||||
|
|
||||||
|
// 只清理语音气泡和实况照片(音乐由全局 AudioManager 管理)
|
||||||
cleanupVoiceBubbles();
|
cleanupVoiceBubbles();
|
||||||
cleanupMusicCards();
|
|
||||||
cleanupLivePhotos();
|
cleanupLivePhotos();
|
||||||
|
|
||||||
AudioManager.currentPlaying = null;
|
// 不再清理音乐卡片,因为音乐由全局 AudioManager 管理
|
||||||
AudioManager.currentType = null;
|
// cleanupMusicCards(); // 已移除,音乐在 PJAX 切换时继续播放
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
@@ -511,14 +751,17 @@ function cleanupAllShortcodes() {
|
|||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
function initAllShortcodes() {
|
function initAllShortcodes() {
|
||||||
|
// 初始化 AudioManager(创建全局音频元素,恢复播放状态)
|
||||||
|
AudioManager.init();
|
||||||
|
|
||||||
initLivePhotoShortcodes();
|
initLivePhotoShortcodes();
|
||||||
initVoiceBubbles();
|
initVoiceBubbles();
|
||||||
initMusicCards();
|
initMusicCards();
|
||||||
|
|
||||||
// Restore playback state after initialization
|
// 恢复 PJAX 切换前的播放状态
|
||||||
setTimeout(function() {
|
setTimeout(function() {
|
||||||
AudioManager.restoreState();
|
AudioManager.restorePlayingState();
|
||||||
}, 50);
|
}, 100);
|
||||||
}
|
}
|
||||||
|
|
||||||
// DOMContentLoaded
|
// DOMContentLoaded
|
||||||
|
|||||||
Reference in new issue
Block a user