@@ -15,6 +15,7 @@ import { Toast } from '@/core/toast'
1515import { Options } from ' ./index'
1616
1717const console = useScopedConsole (' 听视频' )
18+ const BLUR_AUTO_ENABLE_DELAY_MS = 30 * 1000
1819
1920export default Vue .extend ({
2021 components: {
@@ -26,6 +27,8 @@ export default Vue.extend({
2627 isAudioMode: false ,
2728 disabled: false ,
2829 settings ,
30+ blurTimer: null as ReturnType <typeof setTimeout > | null ,
31+ initialAutoEnableAttempted: false as boolean ,
2932 }
3033 },
3134 computed: {
@@ -37,14 +40,19 @@ export default Vue.extend({
3740 },
3841 },
3942 async mounted() {
40- videoChange (() => {
43+ videoChange (async () => {
4144 this .isAudioMode = false
42- if (this .settings .options .autoEnable ) {
45+ if (this .settings .options .autoEnable && ! this .initialAutoEnableAttempted ) {
46+ this .initialAutoEnableAttempted = true
47+ // Wait briefly for the player to settle after a no-refresh video change
48+ // before attempting to switch to audio mode, to avoid AbortError.
49+ await new Promise (r => setTimeout (r , 800 ))
4350 this .switchToAudioMode ()
4451 }
4552 })
4653
47- if (this .settings .options .autoEnable ) {
54+ if (this .settings .options .autoEnable && ! this .initialAutoEnableAttempted ) {
55+ this .initialAutoEnableAttempted = true
4856 await this .switchToAudioMode ()
4957 }
5058
@@ -53,8 +61,52 @@ export default Vue.extend({
5361 this .switchToAudioMode ()
5462 }
5563 })
64+
65+ if (this .settings .options .autoEnableOnBlur ) {
66+ this .setupBlurListener ()
67+ }
68+
69+ addComponentListener (' audioOnlyMode.autoEnableOnBlur' , (value : boolean ) => {
70+ if (value ) {
71+ this .setupBlurListener ()
72+ } else {
73+ this .teardownBlurListener ()
74+ }
75+ })
76+ },
77+ beforeDestroy() {
78+ this .teardownBlurListener ()
5679 },
5780 methods: {
81+ setupBlurListener() {
82+ // Remove first to prevent duplicate registrations if called multiple times.
83+ document .removeEventListener (' visibilitychange' , this .handleVisibilityChange )
84+ document .addEventListener (' visibilitychange' , this .handleVisibilityChange )
85+ },
86+ teardownBlurListener() {
87+ document .removeEventListener (' visibilitychange' , this .handleVisibilityChange )
88+ this .clearBlurTimer ()
89+ },
90+ handleVisibilityChange() {
91+ if (document .hidden ) {
92+ // Clear any existing timer before starting a new one to avoid orphaned timeouts.
93+ this .clearBlurTimer ()
94+ this .blurTimer = setTimeout (() => {
95+ // Re-check visibility to avoid switching modes if the page is visible again.
96+ if (document .hidden && ! this .isAudioMode ) {
97+ this .switchToAudioMode ()
98+ }
99+ }, BLUR_AUTO_ENABLE_DELAY_MS )
100+ } else {
101+ this .clearBlurTimer ()
102+ }
103+ },
104+ clearBlurTimer() {
105+ if (this .blurTimer !== null ) {
106+ clearTimeout (this .blurTimer )
107+ this .blurTimer = null
108+ }
109+ },
58110 async toggleAudioMode() {
59111 if (this .isAudioMode ) {
60112 Toast .info (' 请刷新页面以退出音频模式' , ' 听视频' , 2000 )
@@ -179,10 +231,24 @@ export default Vue.extend({
179231
180232 video .src = audioUrl
181233 video .load ()
182- await video .play ().catch ((err : Error ) => {
234+
235+ let playAborted = false
236+ await video .play ().catch ((err : DOMException ) => {
237+ if (err .name === ' AbortError' ) {
238+ // AbortError is expected when a no-refresh video navigation interrupts
239+ // the play() call before it can complete. This is not a real failure;
240+ // the next videoChange event will trigger another switch attempt.
241+ console .warn (' 播放被中止 (AbortError),可能由视频切换引起,将等待下次触发' )
242+ playAborted = true
243+ return
244+ }
183245 throw new Error (` 播放失败: ${err .message } ` )
184246 })
185247
248+ if (playAborted ) {
249+ return
250+ }
251+
186252 this .isAudioMode = true
187253 Toast .success (' 已切换到音频模式' , ' 听视频' , 2000 )
188254 console .log (' 已切换到音频模式' )
0 commit comments