feat(audio): 重构音频播放功能以支持iframe隔离播放
- 将音频播放器移至独立的iframe中以提升性能和隔离性 - 实现主页面与iframe之间的postMessage通信机制 - 添加音频播放状态同步和UI更新逻辑 - 支持自动播放控制和用户交互检测 - 实现播放状态持久化存储和恢复功能 - 优化移动端音频控制体验 - 添加版权信息更新和国际化支持
This commit is contained in:
208
audio-player.html
Normal file
208
audio-player.html
Normal 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>
|
||||
Reference in New Issue
Block a user