Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Sign in / Register
Toggle navigation
phpdts
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Locked Files
Issues
0
Issues
0
List
Boards
Labels
Service Desk
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Security & Compliance
Security & Compliance
Dependency List
License Compliance
Packages
Packages
List
Container Registry
Analytics
Analytics
CI / CD
Code Review
Insights
Issues
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Nemo Ma
phpdts
Commits
a93080eb
Commit
a93080eb
authored
Jun 14, 2025
by
Nemo Ma
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Revert
parent
85d98d58
Changes
3
Hide whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
27 additions
and
369 deletions
+27
-369
doc/etc/20250614_bgm_playback_fix.txt
doc/etc/20250614_bgm_playback_fix.txt
+0
-120
include/game.func.php
include/game.func.php
+5
-65
include/game20130526.js
include/game20130526.js
+22
-184
No files found.
doc/etc/20250614_bgm_playback_fix.txt
deleted
100644 → 0
View file @
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. 长时间运行稳定性测试
include/game.func.php
View file @
a93080eb
...
@@ -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;
}
}
...
...
include/game20130526.js
View file @
a93080eb
...
@@ -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
)
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment