7
7
<div class =" uno-relative" >
8
8
<Component
9
9
:is =" mediaElementType"
10
- v-show =" mediaElementType === 'video' && videoContainerRef"
10
+ v-show =" playbackManager.isVideo.value && videoContainerRef"
11
11
ref =" mediaElementRef"
12
12
:poster =" String(posterUrl)"
13
13
autoplay
14
14
crossorigin
15
15
playsinline
16
16
:loop =" playbackManager.isRepeatingOnce.value"
17
- class =" uno-h-full uno-max-h-100vh"
18
17
:class =" {
19
18
'uno-object-fill uno-w-screen': playerElement.state.value.isStretched,
19
+ 'uno-h-full uno-max-h-100vh': playbackManager.isVideo.value
20
20
}"
21
21
@loadeddata =" onLoadedData" >
22
22
<track
37
37
<script setup lang="ts">
38
38
import Hls , { ErrorTypes , Events , type ErrorData } from ' hls.js' ;
39
39
import HlsWorkerUrl from ' hls.js/dist/hls.worker.js?url' ;
40
- import { computed , nextTick , watch } from ' vue' ;
40
+ import { computed , nextTick , onScopeDispose , watch } from ' vue' ;
41
41
import { useTranslation } from ' i18next-vue' ;
42
42
import { isNil } from ' @jellyfin-vue/shared/validation' ;
43
+ import { PromiseQueue } from ' @jellyfin-vue/shared/promises' ;
43
44
import { useSnackbar } from ' #/composables/use-snackbar' ;
44
45
import {
45
46
mediaElementRef ,
@@ -51,7 +52,7 @@ import { getImageInfo } from '#/utils/images';
51
52
import { subtitleSettings } from ' #/store/settings/subtitle' ;
52
53
53
54
const { t } = useTranslation ();
54
- let busyWebAudio = false ;
55
+ const webAudioQueue = new PromiseQueue () ;
55
56
const hls = Hls .isSupported ()
56
57
? new Hls ({
57
58
testBandwidth: false ,
@@ -90,55 +91,29 @@ function detachHls(): void {
90
91
* Suspends WebAudio when no playback is in place
91
92
*/
92
93
async function detachWebAudio(): Promise <void > {
93
- if (mediaWebAudio .context .state === ' running' && ! busyWebAudio ) {
94
- busyWebAudio = true ;
95
-
96
- try {
97
- if (mediaWebAudio .gainNode ) {
98
- mediaWebAudio .gainNode .gain .setValueAtTime (mediaWebAudio .gainNode .gain .value , mediaWebAudio .context .currentTime );
99
- mediaWebAudio .gainNode .gain .exponentialRampToValueAtTime (0.0001 , mediaWebAudio .context .currentTime + 1.5 );
100
- await nextTick ();
101
- await new Promise (resolve => globalThis .setTimeout (resolve ));
102
- mediaWebAudio .gainNode .disconnect ();
103
- mediaWebAudio .gainNode = undefined ;
104
- }
105
-
106
- if (mediaWebAudio .sourceNode ) {
107
- mediaWebAudio .sourceNode .disconnect ();
108
- mediaWebAudio .sourceNode = undefined ;
109
- }
94
+ const { context, sourceNode } = mediaWebAudio ;
110
95
111
- await mediaWebAudio .context .suspend ();
112
- } catch {} finally {
113
- busyWebAudio = false ;
96
+ if (context .value ) {
97
+ if (sourceNode .value ) {
98
+ sourceNode .value .disconnect ();
99
+ sourceNode .value = undefined ;
114
100
}
101
+
102
+ await context .value .close ();
103
+ context .value = undefined ;
115
104
}
116
105
}
117
106
118
107
/**
119
108
* Resumes WebAudio when playback is in place
120
109
*/
121
110
async function attachWebAudio(el : HTMLMediaElement ): Promise <void > {
122
- if (mediaWebAudio .context .state === ' suspended' && ! busyWebAudio ) {
123
- busyWebAudio = true ;
111
+ const { context, sourceNode } = mediaWebAudio ;
124
112
125
- try {
126
- await mediaWebAudio .context .resume ();
127
-
128
- mediaWebAudio .sourceNode = mediaWebAudio .context .createMediaElementSource (el );
129
- mediaWebAudio .sourceNode .connect (mediaWebAudio .context .destination );
130
-
131
- /**
132
- * The gain node is to avoid cracks when stopping playback or switching really fast between tracks
133
- */
134
- mediaWebAudio .gainNode = mediaWebAudio .context .createGain ();
135
- mediaWebAudio .gainNode .connect (mediaWebAudio .context .destination );
136
- mediaWebAudio .gainNode .gain .setValueAtTime (mediaWebAudio .gainNode .gain .value , mediaWebAudio .context .currentTime );
137
- mediaWebAudio .gainNode .gain .exponentialRampToValueAtTime (1 , mediaWebAudio .context .currentTime + 1.5 );
138
- } catch {} finally {
139
- busyWebAudio = false ;
140
- }
141
- }
113
+ context .value = new AudioContext ();
114
+ sourceNode .value = context .value .createMediaElementSource (el );
115
+ await context .value .resume ();
116
+ sourceNode .value .connect (context .value .destination );
142
117
}
143
118
144
119
/**
@@ -188,17 +163,19 @@ function onHlsEror(_event: typeof Hls.Events.ERROR, data: ErrorData): void {
188
163
}
189
164
}
190
165
191
- watch (mediaElementRef , async () => {
166
+ watch (mediaElementRef , () => {
192
167
detachHls ();
193
- await detachWebAudio ();
168
+ void webAudioQueue . add (() => detachWebAudio () );
194
169
195
170
if (mediaElementRef .value ) {
196
- if (mediaElementType . value === ' video ' && hls ) {
171
+ if (playbackManager . isVideo . value && hls ) {
197
172
hls .attachMedia (mediaElementRef .value );
198
173
hls .on (Events .ERROR , onHlsEror );
199
174
}
200
175
201
- await attachWebAudio (mediaElementRef .value );
176
+ if (playbackManager .isAudio .value ) {
177
+ void webAudioQueue .add (() => attachWebAudio (mediaElementRef .value ! ));
178
+ }
202
179
}
203
180
});
204
181
@@ -238,4 +215,10 @@ watch(playbackManager.currentSourceUrl,
238
215
}
239
216
}
240
217
);
218
+
219
+ onScopeDispose (() => {
220
+ detachHls ();
221
+ hls ?.destroy ();
222
+ void detachWebAudio ();
223
+ });
241
224
</script >
0 commit comments