Skip to content

Commit 5925cd5

Browse files
fix: use Web Audio API GainNode for volume control on iOS
iOS WebKit makes Audio.volume read-only, so the volume slider had no effect on Brave/Safari mobile. Route audio through AudioContext with GainNode to control volume on all platforms. Also sync slider value to server so it persists across category switches. Amp-Thread-ID: https://ampcode.com/threads/T-019cd35e-b9e2-720f-8a40-15326918e4cb Co-authored-by: Amp <amp@ampcode.com>
1 parent 598d7ff commit 5925cd5

File tree

1 file changed

+41
-18
lines changed

1 file changed

+41
-18
lines changed

lib/event_horizon_web/live/jukebox_live.ex

Lines changed: 41 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -278,45 +278,64 @@ defmodule EventHorizonWeb.JukeboxLive do
278278
<script :type={Phoenix.LiveView.ColocatedHook} name=".JukeboxPanel">
279279
export default {
280280
mounted() {
281-
this._audios = {};
281+
this._tracks = {};
282+
this._ctx = null;
282283
this._wasHidden = this.el.style.display === 'none';
283284
284285
this.el.addEventListener("jukebox:credit_click", e => {
285286
e.stopPropagation();
286287
});
287288
289+
const getCtx = () => {
290+
if (!this._ctx) this._ctx = new (window.AudioContext || window.webkitAudioContext)();
291+
return this._ctx;
292+
};
293+
294+
const getVolume = () => {
295+
const slider = this.el.querySelector('#jukebox-volume-slider');
296+
return slider ? parseInt(slider.value, 10) / 100 : 0.7;
297+
};
298+
288299
this.handleEvent("jukebox:play", ({id, url, loop}) => {
289-
if (this._audios[id]) {
290-
this._audios[id].pause();
291-
delete this._audios[id];
300+
if (this._tracks[id]) {
301+
this._tracks[id].audio.pause();
302+
this._tracks[id].source.disconnect();
303+
delete this._tracks[id];
292304
}
293-
const slider = this.el.querySelector('#jukebox-volume-slider');
294-
const vol = slider ? parseInt(slider.value, 10) / 100 : 0.7;
305+
const ctx = getCtx();
295306
const audio = new Audio(url);
307+
audio.crossOrigin = "anonymous";
296308
audio.preload = "metadata";
297309
audio.loop = !!loop;
298-
audio.volume = vol;
310+
const source = ctx.createMediaElementSource(audio);
311+
const gain = ctx.createGain();
312+
gain.gain.value = getVolume();
313+
source.connect(gain);
314+
gain.connect(ctx.destination);
299315
if (!loop) {
300316
audio.addEventListener("ended", () => {
301-
delete this._audios[id];
317+
source.disconnect();
318+
delete this._tracks[id];
302319
this.pushEvent("track_ended", {id: id});
303320
});
304321
}
305322
audio.play().catch(() => {});
306-
this._audios[id] = audio;
323+
this._tracks[id] = {audio, source, gain};
307324
});
308325
309326
this.handleEvent("jukebox:stop", ({id}) => {
310-
if (this._audios[id]) {
311-
this._audios[id].pause();
312-
delete this._audios[id];
327+
if (this._tracks[id]) {
328+
this._tracks[id].audio.pause();
329+
this._tracks[id].source.disconnect();
330+
delete this._tracks[id];
313331
}
314332
});
315333
316334
this.handleEvent("jukebox:stop_all", () => {
317-
Object.keys(this._audios).forEach(id => {
318-
this._audios[id].pause();
319-
delete this._audios[id];
335+
Object.keys(this._tracks).forEach(id => {
336+
this._tracks[id].audio.pause();
337+
this._tracks[id].source.disconnect();
338+
delete this._tracks[id];
320339
});
321340
});
322341
@@ -325,7 +344,7 @@ defmodule EventHorizonWeb.JukeboxLive do
325344
const onVolume = () => {
326345
const vol = parseInt(slider.value, 10) / 100;
327346
slider.style.setProperty("--vol", (vol * 100) + "%");
328-
Object.values(this._audios).forEach(a => a.volume = vol);
347+
Object.values(this._tracks).forEach(t => t.gain.gain.value = vol);
329348
this.pushEvent("set_volume", {volume: parseInt(slider.value, 10)});
330349
};
331350
slider.addEventListener("input", onVolume);
@@ -337,8 +356,12 @@ defmodule EventHorizonWeb.JukeboxLive do
337356
this._wasHidden = this.el.style.display === 'none';
338357
},
339358
destroyed() {
340-
Object.values(this._audios).forEach(a => a.pause());
341-
this._audios = {};
359+
Object.values(this._tracks).forEach(t => {
360+
t.audio.pause();
361+
if (t.source) t.source.disconnect();
362+
});
363+
this._tracks = {};
364+
if (this._ctx) this._ctx.close();
342365
}
343366
}
344367
</script>

0 commit comments

Comments
 (0)