Commit a93080eb authored by Nemo Ma's avatar Nemo Ma

Revert

parent 85d98d58
BGM播放卡顿问题修复记录
时间:2025-06-14
问题:播放新歌单时出现AbortError错误
== 问题分析 ==
1. Console错误信息:
- "The play() request was interrupted by a new load request"
- "The play() request was interrupted by a call to pause()"
- 错误发生在game20130526.js:583和game.php:1
2. 根本原因:
- changeBGM()函数中,load()和play()方法调用过于紧密
- 没有处理异步播放的Promise返回值
- 缺少对播放中断的错误处理
- 自动播放可能与用户交互冲突
== 解决方案 ==
1. 修改changeBGM()函数 (include/game20130526.js:566-618):
- 在加载新音频前先暂停当前播放
- 使用Promise处理异步播放请求
- 添加错误捕获和重试机制
- 添加控制台日志用于调试
2. 修改BGM初始化 (include/game.func.php:211-246):
- 移除autoplay属性,改为手动控制
- 添加DOMContentLoaded事件处理自动播放
- 添加beforeunload事件防止页面卸载时的播放错误
- 使用Promise处理自动播放的错误
3. 改进音量控制 (include/game20130526.js:553-576):
- 添加音频元素存在性检查
- 使用try-catch包装音量设置
- 同步更新nowbgmvolume元素
== 技术细节 ==
- 使用audioElement.pause()和currentTime=0确保完全停止
- Promise.then()和.catch()处理异步播放状态
- 100ms延迟重试机制避免快速连续操作冲突
- 控制台日志帮助调试播放状态
== 预期效果 ==
- 消除AbortError错误信息
- 改善歌单切换的流畅性
- 提供更好的错误恢复机制
- 保持音频播放的稳定性
== 第二次修复 (问题仍存在) ==
发现问题根源更深层:
1. 多个地方同时触发音频播放请求
2. DOMContentLoaded自动播放与changeBGM冲突
3. 浏览器的自动播放策略限制
4. 并发的load()和play()调用
== 彻底解决方案 ==
1. 实现全局BGM状态管理 (bgmPlayState):
- 防止并发changeBGM调用
- 跟踪当前播放Promise
- 处理待处理的切换请求
2. 移除自动播放,改为用户交互触发:
- 移除DOMContentLoaded中的自动播放
- 添加首次点击事件监听器
- 使用preload="metadata"预加载
3. 改进changeBGM函数:
- 添加并发保护机制
- 使用canplaythrough事件确保加载完成
- 实现请求队列处理
- 添加适当的延迟避免竞争条件
4. 优化音频元素初始化:
- 使用IIFE避免全局变量污染
- 添加初始化状态检查
- 改进事件监听器管理
== 技术改进 ==
- 状态机模式管理播放状态
- Promise链式处理避免中断
- 事件驱动的播放控制
- 用户交互检测机制
== 第三次修复 (全面重构) ==
实现了完整的BGM管理系统:
1. 创建全局BGM管理器 (window.BGMManager):
- 集中管理所有BGM播放状态
- 防止重复初始化和并发调用
- 实现请求队列和状态跟踪
- 提供统一的API接口
2. 重构changeBGM函数:
- 原函数改为调用BGMManager.changeBGM()
- 保持向后兼容性
- 简化调用逻辑
3. 改进BGM初始化:
- 添加唯一时间戳防止重复初始化
- 清理旧的事件监听器
- 优先使用BGMManager处理事件
4. 增强播放控制:
- 使用readyState检查音频加载状态
- 实现智能重试机制
- 添加详细的控制台日志
== 核心改进 ==
- 单例模式的BGM管理器
- 状态机驱动的播放控制
- 防重复初始化机制
- 智能的音频加载检测
- 完善的错误处理和恢复
== 测试建议 ==
1. 快速连续切换BGM测试
2. 页面刷新时的音频状态测试
3. 音量调节时的播放稳定性测试
4. 不同浏览器的兼容性测试
5. 用户首次交互后的自动播放测试
6. 并发播放请求处理测试
7. 长时间运行稳定性测试
...@@ -175,17 +175,9 @@ function init_bgm($force_update=0) ...@@ -175,17 +175,9 @@ function init_bgm($force_update=0)
} }
} }
# 防止重复初始化 - 只在真正需要时重载播放器
static $bgm_initialized = false;
if($bgm_initialized && !$force_update && $command != 'enter')
{
return '';
}
# 刷新页面或输入强制重载参数时,重载播放器 # 刷新页面或输入强制重载参数时,重载播放器
if($command == 'enter' || $force_update || !$bgm_initialized) if($command == 'enter' || $force_update)
{ {
$bgm_initialized = true;
$booklist = $bgmarr = Array(); $booklist = $bgmarr = Array();
# 为播放列表中的曲集关联对应BGM名、链接与种类 # 为播放列表中的曲集关联对应BGM名、链接与种类
foreach($clbpara['bgmbook'] as $book) foreach($clbpara['bgmbook'] as $book)
...@@ -216,69 +208,17 @@ function init_bgm($force_update=0) ...@@ -216,69 +208,17 @@ function init_bgm($force_update=0)
# 生成播放器与播放队列 太野蛮了……嘻嘻…… # 生成播放器与播放队列 太野蛮了……嘻嘻……
if(!empty($bgmlink) && !empty($bgmtype)) if(!empty($bgmlink) && !empty($bgmtype))
{ {
$bgm_timestamp = time() . rand(1000, 9999); // 唯一标识符
$bgmplayer = <<<EOT $bgmplayer = <<<EOT
<audio id="gamebgm" controls="1" preload="metadata" data-bgm-init="$bgm_timestamp"> <audio id="gamebgm" autoplay controls=1" onplay="$('gamebgm').volume=$('nowbgmvolume').innerHTML;">
<source id="gbgm" src="$bgmlink" type="$bgmtype"> <source id="gbgm" src="$bgmlink" type="$bgmtype">
</audio> </audio>
<div id="bgmlist">$json_bgmarr</div> <div id="bgmlist">$json_bgmarr</div>
<div id="nowbgm">0</div> <div id="nowbgm">0</div>
<div id="nowbgmvolume">$volume_r</div> <div id="nowbgmvolume">$volume_r</div>
<script> <script>
// 防止重复初始化BGM播放器 gamebgm.addEventListener('ended', function () {
(function() { changeBGM();
var currentTimestamp = '$bgm_timestamp'; }, false);
// 检查是否已经初始化过相同的实例
if (window.bgmInitTimestamp === currentTimestamp) {
console.log('BGM播放器已存在,跳过重复初始化');
return;
}
window.bgmInitTimestamp = currentTimestamp;
var audioElement = document.getElementById('gamebgm');
if (!audioElement) {
console.log('BGM音频元素不存在');
return;
}
// 清理之前的事件监听器
if (window.bgmEndedHandler) {
audioElement.removeEventListener('ended', window.bgmEndedHandler);
}
function initBGM() {
audioElement.volume = $volume_r;
// 创建新的事件处理器
window.bgmEndedHandler = function() {
if (window.BGMManager && typeof BGMManager.changeBGM === 'function') {
BGMManager.changeBGM();
} else if (typeof changeBGM === 'function') {
changeBGM();
}
};
// 监听音频结束事件
audioElement.addEventListener('ended', window.bgmEndedHandler, false);
console.log('BGM播放器已初始化 (ID: ' + currentTimestamp + ')');
}
// 页面加载完成后初始化
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initBGM);
} else {
initBGM();
}
// 防止页面卸载时的播放中断错误
window.addEventListener('beforeunload', function() {
if (audioElement) {
audioElement.pause();
}
});
})();
</script> </script>
EOT; EOT;
} }
......
...@@ -551,10 +551,7 @@ function AddMixElements(emix_arr) { ...@@ -551,10 +551,7 @@ function AddMixElements(emix_arr) {
} }
function changeVolume(cv){ function changeVolume(cv){
var audioElement = $('gamebgm'); var v = $('gamebgm').volume;
if (!audioElement) return;
var v = audioElement.volume;
v = v+cv; v = v+cv;
v = Math.min(1,v); v = Math.max(0,v); v = v.toFixed(2); v = Math.min(1,v); v = Math.max(0,v); v = v.toFixed(2);
Cookie.setCookie("volume",v, { Cookie.setCookie("volume",v, {
...@@ -562,189 +559,30 @@ function changeVolume(cv){ ...@@ -562,189 +559,30 @@ function changeVolume(cv){
path: "/", path: "/",
}); });
s = Math.round(v*100); s = Math.round(v*100);
$('gamebgm').volume = v;
// 安全地设置音量,避免在播放状态变化时出错 $('volume_num').innerHTML = s+'%';
try {
audioElement.volume = v;
$('nowbgmvolume').innerHTML = v;
if ($('volume_num')) {
$('volume_num').innerHTML = s+'%';
}
} catch (error) {
console.log('音量设置失败: ' + error.message);
}
} }
// 全局BGM管理器 function changeBGM(mode=1){
window.BGMManager = { var bgmlist = JSON.parse($('bgmlist').innerHTML);
isChanging: false, var nowbgm = Math.round($('nowbgm').innerHTML);
currentPromise: null, nowbgm = nowbgm + mode;
pendingChange: null, if(nowbgm < 0){
isPlaying: false, nowbgm = bgmlist.length - 1;
userInteracted: false, }else{
initialized: false, nowbgm = nowbgm % bgmlist.length;
// 初始化BGM管理器
init: function() {
if (this.initialized) return;
this.initialized = true;
// 在任何用户交互时尝试启动BGM
var self = this;
document.addEventListener('click', function() {
if (!self.userInteracted) {
self.startBGM();
}
}, { once: true });
console.log('BGM管理器已初始化');
},
// 启动BGM播放(需要用户交互)
startBGM: function() {
if (this.userInteracted) return;
this.userInteracted = true;
var audioElement = $('gamebgm');
if (audioElement && audioElement.paused) {
var self = this;
var playPromise = audioElement.play();
if (playPromise !== undefined) {
playPromise.then(function() {
self.isPlaying = true;
console.log('BGM开始播放');
}).catch(function(error) {
console.log('BGM启动失败: ' + error.message);
});
}
}
},
// 安全地切换BGM
changeBGM: function(mode) {
mode = mode || 1;
// 防止并发调用
if (this.isChanging) {
this.pendingChange = mode;
console.log('BGM正在切换中,将在完成后执行新的切换请求');
return;
}
this.isChanging = true;
var self = this;
try {
var bgmlist = JSON.parse($('bgmlist').innerHTML);
var nowbgm = Math.round($('nowbgm').innerHTML);
nowbgm = nowbgm + mode;
if(nowbgm < 0){
nowbgm = bgmlist.length - 1;
}else{
nowbgm = nowbgm % bgmlist.length;
}
var audioElement = $('gamebgm');
var sourceElement = $('gbgm');
if (!audioElement || !sourceElement) {
this.isChanging = false;
return;
}
// 取消当前的播放Promise
if (this.currentPromise) {
audioElement.pause();
this.currentPromise = null;
}
// 暂停当前播放并重置
audioElement.pause();
audioElement.currentTime = 0;
// 更新音频源
sourceElement.src = bgmlist[nowbgm].url;
sourceElement.type = bgmlist[nowbgm].type;
$('nowbgm').innerHTML = nowbgm;
Cookie.setCookie("nowbgmid",bgmlist[nowbgm].id, {
path: "/",
});
var v = audioElement.volume;
if ($('bgmname')) {
$('bgmname').innerHTML = bgmlist[nowbgm].name;
}
// 等待一小段时间确保pause()完成
setTimeout(function() {
// 加载新音频
audioElement.load();
audioElement.volume = v;
// 等待load完成后再播放
function tryPlay() {
if (audioElement.readyState >= 3) { // HAVE_FUTURE_DATA
// 使用Promise处理异步播放
self.currentPromise = audioElement.play();
if (self.currentPromise !== undefined) {
self.currentPromise.then(function() {
console.log('BGM播放成功: ' + bgmlist[nowbgm].name);
self.currentPromise = null;
self.isChanging = false;
self.isPlaying = true;
// 处理待处理的切换请求
if (self.pendingChange !== null) {
var pendingMode = self.pendingChange;
self.pendingChange = null;
setTimeout(function() {
self.changeBGM(pendingMode);
}, 100);
}
}).catch(function(error) {
console.log('BGM播放失败: ' + error.message);
self.currentPromise = null;
self.isChanging = false;
// 处理待处理的切换请求
if (self.pendingChange !== null) {
var pendingMode = self.pendingChange;
self.pendingChange = null;
setTimeout(function() {
self.changeBGM(pendingMode);
}, 100);
}
});
} else {
self.isChanging = false;
}
} else {
// 等待音频加载完成
setTimeout(tryPlay, 50);
}
}
tryPlay();
}, 100);
} catch (error) {
console.log('changeBGM错误: ' + error.message);
this.isChanging = false;
this.currentPromise = null;
}
}
};
// 初始化BGM管理器
BGMManager.init();
// 兼容性函数 - 调用新的BGM管理器
function changeBGM(mode) {
if (window.BGMManager) {
BGMManager.changeBGM(mode);
} else {
console.log('BGM管理器未初始化');
} }
$('gbgm').src = bgmlist[nowbgm].url;
$('gbgm').type = bgmlist[nowbgm].type;
$('nowbgm').innerHTML = nowbgm;
Cookie.setCookie("nowbgmid",bgmlist[nowbgm].id, {
path: "/",
});
var v = $('gamebgm').volume;
$('bgmname').innerHTML = bgmlist[nowbgm].name;
$('gamebgm').load();
$('gamebgm').volume = v;
$('gamebgm').play();
} }
function changePages(mode,cPages) function changePages(mode,cPages)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment