1 Commits

Author SHA1 Message Date
hehh
532cbafd8e feat(audio): 重构音频播放功能以支持iframe隔离播放
- 将音频播放器移至独立的iframe中以提升性能和隔离性
- 实现主页面与iframe之间的postMessage通信机制
- 添加音频播放状态同步和UI更新逻辑
- 支持自动播放控制和用户交互检测
- 实现播放状态持久化存储和恢复功能
- 优化移动端音频控制体验
- 添加版权信息更新和国际化支持
2025-11-30 16:42:00 +08:00
5 changed files with 405 additions and 32 deletions

View File

@@ -301,7 +301,8 @@
<button id="fab-music" class="fab-item" tabindex="0"><i class="ri-music-2-line"></i><span class="fab-text">Play</span></button> <button id="fab-music" class="fab-item" tabindex="0"><i class="ri-music-2-line"></i><span class="fab-text">Play</span></button>
</div> </div>
</div> </div>
<audio id="site-audio" class="site-audio" src="data/至少做一件离谱的事-Kiri T_compressed.mp3" loop preload="none"></audio> <!-- 隐藏的音频播放iframe -->
<iframe id="audio-player-iframe" src="audio-player.html" style="display: none;"></iframe>
</main> </main>
<!-- 微信弹窗 --> <!-- 微信弹窗 -->
@@ -318,7 +319,7 @@
</div> </div>
<footer class="site-footer"> <footer class="site-footer">
<p>&copy; 2018 <span id="currentYear"></span> Honesty. All rights reserved. <a class="icp" href="https://beian.miit.gov.cn/" target="_blank">湘ICP备20014902号</a> Powered By <a href="https://pages.edgeone.ai/" target="_blank"> Tencent EdgeOne </a></p> <p>Copyright &copy; 2018 <span id="currentYear"></span> Honesty. All rights reserved. <a class="icp" href="https://beian.miit.gov.cn/" target="_blank">湘ICP备20014902号</a> Powered By <a href="https://pages.edgeone.ai/" target="_blank"> Tencent EdgeOne </a></p>
<script> <script>
document.getElementById("currentYear").textContent = ' - ' + new Date().getFullYear(); document.getElementById("currentYear").textContent = ' - ' + new Date().getFullYear();
</script> </script>

208
audio-player.html Normal file
View File

@@ -0,0 +1,208 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Audio Player</title>
<style>
body {
margin: 0;
padding: 0;
background: transparent;
}
</style>
</head>
<body>
<audio id="audio-player" controls autoplay style="display: none;"></audio>
<script>
const audio = document.getElementById('audio-player');
let currentSrc = '';
let isStateBroadcasting = false;
// 监听来自主页面的消息
window.addEventListener('message', function(event) {
// 允许来自同域的所有窗口的消息
if (event.origin !== window.location.origin) return;
const data = event.data;
switch (data.action) {
case 'play':
// 如果当前正在播放,则暂停;否则播放
if (audio.paused) {
audio.play().catch(e => console.error('播放失败:', e));
} else {
audio.pause();
}
// 发送状态更新回所有可能的窗口
broadcastState();
break;
case 'pause':
if (!audio.paused) {
audio.pause();
}
broadcastState();
break;
case 'setTrack':
if (currentSrc !== data.src) {
currentSrc = data.src;
audio.src = data.src;
audio.load();
// 自动播放新曲目
setTimeout(() => {
audio.play().catch(e => console.error('播放失败:', e));
broadcastState();
}, 100);
}
break;
case 'setVolume':
audio.volume = data.volume;
broadcastState();
break;
case 'getCurrentState':
// 向请求方发送当前状态
event.source.postMessage({
action: 'currentState',
playing: !audio.paused,
src: audio.src,
volume: audio.volume,
currentTime: audio.currentTime,
duration: audio.duration || 0
}, event.origin);
break;
}
});
// 广播状态到所有可能监听的窗口
function broadcastState() {
if (isStateBroadcasting) return; // 防止状态广播循环
isStateBroadcasting = true;
window.parent.postMessage({
action: 'stateChange',
playing: !audio.paused,
src: audio.src,
volume: audio.volume,
currentTime: audio.currentTime,
duration: audio.duration || 0
}, '*');
// 保存播放状态
savePlaybackState();
setTimeout(() => {
isStateBroadcasting = false;
}, 10);
}
// 监听音频事件并通知主页面
audio.addEventListener('play', function() {
broadcastState();
});
audio.addEventListener('pause', function() {
broadcastState();
});
audio.addEventListener('ended', function() {
window.parent.postMessage({
action: 'trackEnded'
}, '*');
});
// 页面加载完成后通知主页面
window.addEventListener('load', function() {
// 恢复之前保存的播放状态
restorePlaybackState();
window.parent.postMessage({
action: 'playerReady'
}, '*');
});
// 页面可见性变化时处理
document.addEventListener('visibilitychange', function() {
if (!document.hidden) {
// 页面变为可见时,广播当前状态
setTimeout(broadcastState, 100);
}
});
// 页面即将卸载时保存播放状态
window.addEventListener('beforeunload', function() {
savePlaybackState();
});
// 保存播放状态到 sessionStorage
function savePlaybackState() {
try {
sessionStorage.setItem('audioState', JSON.stringify({
src: audio.src,
currentTime: audio.currentTime,
playing: !audio.paused,
volume: audio.volume,
timestamp: Date.now()
}));
} catch (e) {
console.error('保存音频状态失败:', e);
}
}
// 从 sessionStorage 恢复播放状态
function restorePlaybackState() {
try {
const savedState = sessionStorage.getItem('audioState');
if (savedState) {
const state = JSON.parse(savedState);
// 如果状态保存时间不超过1小时则恢复播放
if (Date.now() - state.timestamp < 3600000) {
currentSrc = state.src;
audio.src = state.src;
audio.volume = state.volume !== undefined ? state.volume : 1.0;
audio.currentTime = state.currentTime || 0;
// 确保在用户交互后才尝试播放
if (state.playing) {
// 检查是否已经有用户交互
if (document.hasFocus()) {
// 稍微延迟播放,确保一切准备就绪
setTimeout(() => {
audio.play().catch(e => {
console.error('恢复播放失败:', e);
// 如果自动播放失败,等待用户交互后再播放
const tryPlayOnInteraction = () => {
audio.play().catch(console.error);
document.removeEventListener('click', tryPlayOnInteraction);
document.removeEventListener('touchstart', tryPlayOnInteraction);
};
document.addEventListener('click', tryPlayOnInteraction, { once: true });
document.addEventListener('touchstart', tryPlayOnInteraction, { once: true });
});
}, 300);
} else {
// 等待用户交互后再播放
const tryPlayOnInteraction = () => {
audio.play().catch(console.error);
document.removeEventListener('click', tryPlayOnInteraction);
document.removeEventListener('touchstart', tryPlayOnInteraction);
};
document.addEventListener('click', tryPlayOnInteraction, { once: true });
document.addEventListener('touchstart', tryPlayOnInteraction, { once: true });
}
}
}
}
} catch (e) {
console.error('恢复音频状态失败:', e);
}
}
</script>
</body>
</html>

View File

@@ -413,5 +413,26 @@
}() }()
}({id: SiteConfig.analytics.tencent.id, ck: SiteConfig.analytics.tencent.ck}); }({id: SiteConfig.analytics.tencent.id, ck: SiteConfig.analytics.tencent.ck});
</script> </script>
<!-- 隐藏的音频播放iframe -->
<iframe id="audio-player-iframe" src="audio-player.html" style="display: none;"></iframe>
<script>
// 音频控制逻辑
let audioIframe = document.getElementById('audio-player-iframe');
// 监听来自iframe的消息
window.addEventListener('message', function(event) {
// 确保消息来自我们的iframe
if (event.source !== audioIframe.contentWindow) return;
const data = event.data;
if (data.playing && data.action === 'playerReady') {
// iframe准备就绪设置初始音频
audioIframe.contentWindow.postMessage({
action: 'setTrack',
src: 'data/至少做一件离谱的事-Kiri T_compressed.mp3'
}, '*');
}
});
</script>
</body> </body>
</html> </html>

View File

@@ -534,6 +534,9 @@ class DataManager {
=========================== */ =========================== */
class UIManager { class UIManager {
constructor() { constructor() {
this.userInteracted = false;
this.audioPlayer = null;
this.audioIframe = null;
this.initTechCloud(); this.initTechCloud();
this.initModal(); this.initModal();
this.initArtalk(); this.initArtalk();
@@ -1080,10 +1083,33 @@ class UIManager {
const theme = getStoredTheme(); const theme = getStoredTheme();
fLang.querySelector('.fab-text').textContent = lang === 'zh' ? 'English' : '中文'; fLang.querySelector('.fab-text').textContent = lang === 'zh' ? 'English' : '中文';
fTheme.querySelector('.fab-text').textContent = theme === 'night' ? 'Day' : 'Night'; fTheme.querySelector('.fab-text').textContent = theme === 'night' ? 'Day' : 'Night';
const playing = (this.audio && !this.audio.paused); // 音频播放状态需要通过iframe通信获取
fMusic.querySelector('.fab-text').textContent = lang === 'zh' ? (playing ? '暂停' : '播放') : (playing ? 'Pause' : 'Play'); const audioIframe = document.getElementById('audio-player-iframe');
if (audioIframe && audioIframe.contentWindow) {
// 请求当前播放状态
audioIframe.contentWindow.postMessage({
action: 'getCurrentState'
}, '*');
}
}); });
}; };
// 监听来自iframe的音频状态更新
window.addEventListener('message', (event) => {
const audioIframe = document.getElementById('audio-player-iframe');
if (!audioIframe || event.source !== audioIframe.contentWindow) return;
const data = event.data;
if (data.action === 'currentState') {
const fMusic = document.getElementById('fab-music');
if (fMusic) {
const lang = getStoredLanguage();
fMusic.querySelector('.fab-text').textContent =
lang === 'zh' ? (data.playing ? '暂停' : '播放') : (data.playing ? 'Pause' : 'Play');
}
}
});
main.addEventListener('click', () => { main.addEventListener('click', () => {
menu.classList.toggle('open'); menu.classList.toggle('open');
main.setAttribute('aria-expanded', menu.classList.contains('open') ? 'true' : 'false'); main.setAttribute('aria-expanded', menu.classList.contains('open') ? 'true' : 'false');
@@ -1101,17 +1127,15 @@ class UIManager {
requestAnimationFrame(updateLabels); requestAnimationFrame(updateLabels);
}); });
fMusic.addEventListener('click', () => { fMusic.addEventListener('click', () => {
if (this.audio) { const audioIframe = document.getElementById('audio-player-iframe');
if (this.audio.paused) { if (audioIframe && audioIframe.contentWindow) {
this.audio.play().catch(() => { // 直接发送播放指令让iframe内部处理播放/暂停切换
}); audioIframe.contentWindow.postMessage({
// 清除暂停时间记录,允许下次自动播放 action: 'play'
this.clearMusicPauseTime(); }, '*');
} else {
this.audio.pause(); // 记录用户操作
// 记录暂停时间 this.setMusicPauseTime(); // 记录暂停时间
this.setMusicPauseTime();
}
} }
// 延迟更新标签以避免阻塞 // 延迟更新标签以避免阻塞
requestAnimationFrame(updateLabels); requestAnimationFrame(updateLabels);
@@ -1122,33 +1146,33 @@ class UIManager {
initAudio() { initAudio() {
const el = document.getElementById('site-audio'); // 获取已存在的iframe或创建新的
if (!el) return; let audioIframe = document.getElementById('audio-player-iframe');
this.audio = el; if (!audioIframe) {
this.audio.loop = true; audioIframe = document.createElement('iframe');
audioIframe.src = 'audio-player.html';
audioIframe.style.display = 'none';
audioIframe.id = 'audio-player-iframe';
document.body.appendChild(audioIframe);
}
this.audioIframe = audioIframe;
// 页面加载完成后根据条件决定是否播放
window.addEventListener('load', () => { const autoPlayer = () => {
// 检查是否在24小时内用户暂停过音乐 // 检查是否在24小时内用户暂停过音乐
const shouldRemainPaused = this.shouldMusicRemainPaused(); const shouldRemainPaused = this.shouldMusicRemainPaused();
// 如果不应该保持暂停状态,则尝试播放 // 如果不应该保持暂停状态,则尝试播放
if (!shouldRemainPaused) { if (!shouldRemainPaused) {
this.audio.autoplay = true;
// 添加用户交互检查,避免浏览器阻止自动播放 // 添加用户交互检查,避免浏览器阻止自动播放
const attemptAutoplay = () => { const attemptAutoplay = () => {
// 检查是否已有用户交互 // 检查是否已有用户交互
if (this.userInteracted) { if (this.userInteracted) {
this.audio.play().catch(() => { this.playAudio();
// 静默处理播放失败
console.error('Failed to play audio.');
});
} else { } else {
// 添加一次性用户交互监听器 // 添加一次性用户交互监听器
const enableAudio = () => { const enableAudio = () => {
this.userInteracted = true; this.userInteracted = true;
this.audio.play().catch(() => { this.playAudio();
console.error('Failed to play audio.');
});
document.removeEventListener('click', enableAudio); document.removeEventListener('click', enableAudio);
document.removeEventListener('touchstart', enableAudio); document.removeEventListener('touchstart', enableAudio);
document.removeEventListener('keydown', enableAudio); document.removeEventListener('keydown', enableAudio);
@@ -1161,9 +1185,86 @@ class UIManager {
document.addEventListener('mousemove', enableAudio, { once: true }); document.addEventListener('mousemove', enableAudio, { once: true });
} }
}; };
requestAnimationFrame(attemptAutoplay); setTimeout(attemptAutoplay, 500); // 稍微延迟以确保iframe加载完成
}
}
// 监听iframe发来的消息
const handleMessage = (event) => {
// 确保消息来自我们的iframe
if (event.source !== this.audioIframe.contentWindow) return;
const data = event.data;
switch (data.action) {
case 'playerReady':
// iframe准备就绪设置初始音频
this.setAudioTrack('data/至少做一件离谱的事-Kiri T_compressed.mp3');
autoPlayer();
break;
case 'stateChange':
// 音频状态改变更新UI
this.updateAudioUI(data.playing);
break;
case 'trackEnded':
// 曲目结束
this.updateAudioUI(false);
break;
case 'currentState':
// 当前状态响应
this.updateAudioUI(data.playing);
if (!data.playing) {
autoPlayer();
}
break;
}
};
window.addEventListener('message', handleMessage);
}
// 播放音频
playAudio() {
const audioIframe = document.getElementById('audio-player-iframe');
if (audioIframe && audioIframe.contentWindow) {
audioIframe.contentWindow.postMessage({
action: 'play'
}, '*');
}
}
// 暂停音频
pauseAudio() {
const audioIframe = document.getElementById('audio-player-iframe');
if (audioIframe && audioIframe.contentWindow) {
audioIframe.contentWindow.postMessage({
action: 'pause'
}, '*');
}
}
// 设置音频曲目
setAudioTrack(src) {
const audioIframe = document.getElementById('audio-player-iframe');
if (audioIframe && audioIframe.contentWindow) {
audioIframe.contentWindow.postMessage({
action: 'setTrack',
src: src
}, '*');
}
}
// 更新音频UI状态
updateAudioUI(playing) {
// 更新移动端fab按钮的文本
const fMusic = document.getElementById('fab-music');
if (fMusic) {
const lang = getStoredLanguage();
fMusic.querySelector('.fab-text').textContent =
lang === 'zh' ? (playing ? '暂停' : '播放') : (playing ? 'Pause' : 'Play');
} }
});
} }
updateCustomStyles(container, theme) { updateCustomStyles(container, theme) {

View File

@@ -0,0 +1,42 @@
// audio-service-worker.js
// Service Worker for background audio playback
self.addEventListener('install', event => {
self.skipWaiting();
});
self.addEventListener('activate', event => {
event.waitUntil(self.clients.claim());
});
// 监听来自主页面的消息
self.addEventListener('message', async event => {
const client = event.source;
const data = event.data;
switch (data.action) {
case 'play':
// 这里只是示例实际上Service Worker无法直接播放音频
// 我们需要采用另一种方式实现
break;
case 'pause':
break;
case 'setTrack':
break;
}
});
// 使用 Broadcast Channel API 在页面间通信
const broadcastChannel = new BroadcastChannel('audio-control');
broadcastChannel.addEventListener('message', event => {
const data = event.data;
// 将消息转发给所有客户端
self.clients.matchAll().then(clients => {
clients.forEach(client => {
client.postMessage(data);
});
});
});