Musica su iPhone e iPad: il volume lì è di sola lettura, la dissolvenza non arrivava mai a zero e il brano vecchio restava sotto quello nuovo (organo e paese insieme nella Pieve). Il livello si tiene a parte e lo zero diventa muto; test che simula iOS
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018ks2K99uqCZjAxvy7Rdj1R
This commit is contained in:
+16
-6
@@ -19,6 +19,12 @@ export function trackFor(story, { screen, scene, phase }) {
|
|||||||
|
|
||||||
const FADE = 1.2;
|
const FADE = 1.2;
|
||||||
|
|
||||||
|
// Dove il volume non si può cambiare (iOS), almeno il muto e lo zero funzionano: `muted` lì si rispetta.
|
||||||
|
function setLevel(a) {
|
||||||
|
a.volume = a.level;
|
||||||
|
a.muted = a.level === 0;
|
||||||
|
}
|
||||||
|
|
||||||
export class Music {
|
export class Music {
|
||||||
constructor({ muted = false, volume = 0.6 } = {}) {
|
constructor({ muted = false, volume = 0.6 } = {}) {
|
||||||
this.muted = muted;
|
this.muted = muted;
|
||||||
@@ -46,22 +52,26 @@ export class Music {
|
|||||||
if (this.current) this.old.push(this.current.audio);
|
if (this.current) this.old.push(this.current.audio);
|
||||||
this.current = src ? { src, audio: this.start(src) } : null;
|
this.current = src ? { src, audio: this.start(src) } : null;
|
||||||
}
|
}
|
||||||
// Il brano in corso sale (o scende) verso il volume scelto; quelli vecchi sfumano fino a zero.
|
// Il brano in corso sale (o scende) verso il volume scelto; quelli vecchi sfumano fino a zero, e lì si fermano.
|
||||||
|
// Il livello si tiene in `level`, non si rilegge da audio.volume: su iPhone e iPad quello è di sola lettura e
|
||||||
|
// resta sempre 1, e un brano vecchio non arrivava mai a zero (in chiesa l'organo suonava insieme al paese).
|
||||||
const step = dt / FADE, target = this.muted || this.hold ? 0 : this.volume * this.duck;
|
const step = dt / FADE, target = this.muted || this.hold ? 0 : this.volume * this.duck;
|
||||||
if (this.current) {
|
if (this.current) {
|
||||||
const a = this.current.audio;
|
const a = this.current.audio;
|
||||||
a.volume = a.volume < target ? Math.min(target, a.volume + step) : Math.max(target, a.volume - step);
|
a.level = a.level < target ? Math.min(target, a.level + step) : Math.max(target, a.level - step);
|
||||||
if (this.hold && a.volume === 0 && !a.paused) a.pause();
|
setLevel(a);
|
||||||
|
if (this.hold && a.level === 0 && !a.paused) a.pause();
|
||||||
else if (!this.hold && !this.away && a.paused && a !== this.blocked) a.play().catch(() => { this.blocked = a; });
|
else if (!this.hold && !this.away && a.paused && a !== this.blocked) a.play().catch(() => { this.blocked = a; });
|
||||||
}
|
}
|
||||||
for (const a of this.old) a.volume = Math.max(0, a.volume - step);
|
for (const a of this.old) { a.level = Math.max(0, a.level - step); setLevel(a); }
|
||||||
this.old = this.old.filter(a => a.volume > 0 || (a.pause(), false));
|
this.old = this.old.filter(a => a.level > 0 || (a.pause(), false));
|
||||||
}
|
}
|
||||||
|
|
||||||
start(src) {
|
start(src) {
|
||||||
const audio = new Audio(versioned(src));
|
const audio = new Audio(versioned(src));
|
||||||
audio.loop = true;
|
audio.loop = true;
|
||||||
audio.volume = 0;
|
audio.level = 0;
|
||||||
|
setLevel(audio);
|
||||||
audio.play().catch(() => { this.blocked = audio; }); // senza permesso si riprova al primo gesto
|
audio.play().catch(() => { this.blocked = audio; }); // senza permesso si riprova al primo gesto
|
||||||
return audio;
|
return audio;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -83,3 +83,20 @@ test('passando a un\'altra app la musica si ferma subito, e tornando riparte', (
|
|||||||
m.update('b.mp3', 0.1);
|
m.update('b.mp3', 0.1);
|
||||||
assert.ok(now.paused, 'con la pausa aperta resta ferma');
|
assert.ok(now.paused, 'con la pausa aperta resta ferma');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('su iPhone (volume di sola lettura, sempre 1) il brano vecchio si ferma lo stesso: niente organo sopra il paese', () => {
|
||||||
|
globalThis.Audio = class { constructor(src) { this.src = src; this.paused = true; this.muted = false; }
|
||||||
|
get volume() { return 1; } set volume(v) {}
|
||||||
|
play() { this.paused = false; return Promise.resolve(); } pause() { this.paused = true; } };
|
||||||
|
const m = new Music({ volume: 0.6 });
|
||||||
|
for (let i = 0; i < 20; i++) m.update('paese.mp3', 0.1);
|
||||||
|
const paese = m.current.audio;
|
||||||
|
for (let i = 0; i < 20; i++) m.update('pieve.mp3', 0.1);
|
||||||
|
assert.ok(paese.paused, 'il brano del paese, sfumato, si è fermato');
|
||||||
|
assert.deepEqual(m.old, []);
|
||||||
|
assert.ok(!m.current.audio.paused && !m.current.audio.muted, 'suona solo l\'organo');
|
||||||
|
m.muted = true;
|
||||||
|
for (let i = 0; i < 20; i++) m.update('pieve.mp3', 0.1);
|
||||||
|
assert.ok(m.current.audio.muted, 'il muto funziona anche dove il volume no');
|
||||||
|
delete globalThis.Audio;
|
||||||
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user