Skip to content

Commit ef0e9a7

Browse files
committed
fix android playback + audio storing
1 parent ae0ee12 commit ef0e9a7

3 files changed

Lines changed: 103 additions & 75 deletions

File tree

src/lib/components/BottomBar.svelte

Lines changed: 1 addition & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,6 @@
2424
2525
let artwork = $state(null as string | null);
2626
let progress = $state(0);
27-
let volume = $state(100);
28-
let lastVolume = $state(100);
2927
let duration = $state(0);
3028
let paused = $state(true);
3129
let fillPercent = $state(0);
@@ -69,17 +67,6 @@
6967
fillPercent = duration > 0 ? Math.min(100, Math.max(0, (progress / duration) * 100)) : 0;
7068
});
7169
72-
$effect(() => {
73-
volume = $audioPlayer.volume ?? volume;
74-
});
75-
76-
$effect(() => {
77-
if (!browser) return;
78-
if (volume === lastVolume) return;
79-
lastVolume = volume;
80-
setVolumeLevel(volume);
81-
});
82-
8370
function formatTime(seconds: number) {
8471
if (!Number.isFinite(seconds)) return '0:00';
8572
const mins = Math.floor(seconds / 60);
@@ -273,7 +260,7 @@
273260
<Slider
274261
min={0}
275262
max={100}
276-
bind:value={volume}
263+
bind:value={() => $audioPlayer.volume ?? 100, (v) => setVolumeLevel(v)}
277264
step={1}
278265
showValue
279266
format={(n) => `${n}%`}

src/lib/player.ts

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,34 @@ import { UserManager } from './api/UserManager';
2323
import { SERVER } from '$lib/api/server';
2424

2525
let mediaSessionInitialized = false;
26+
let currentAudioUrl: string | null = null;
27+
28+
const AUDIO_MIME: Record<string, string> = {
29+
mp3: 'audio/mpeg',
30+
mpeg: 'audio/mpeg',
31+
mpga: 'audio/mpeg',
32+
m4a: 'audio/mp4',
33+
mp4: 'audio/mp4',
34+
aac: 'audio/aac',
35+
wav: 'audio/wav',
36+
wave: 'audio/wav',
37+
flac: 'audio/flac',
38+
ogg: 'audio/ogg',
39+
oga: 'audio/ogg',
40+
opus: 'audio/ogg',
41+
webm: 'audio/webm'
42+
};
43+
44+
function audioMimeType(ext: string) {
45+
const key = ext.toLowerCase().replace(/^\./, '');
46+
return AUDIO_MIME[key] ?? `audio/${key}`;
47+
}
48+
49+
function revokeCurrentAudioUrl() {
50+
if (!currentAudioUrl) return;
51+
URL.revokeObjectURL(currentAudioUrl);
52+
currentAudioUrl = null;
53+
}
2654

2755
function initMediaSession() {
2856
if (!browser || mediaSessionInitialized || !('mediaSession' in navigator)) return;
@@ -239,15 +267,17 @@ async function playAtIndex(index: number) {
239267
const buffer = await OPFS.getSong(song);
240268
if (!buffer) return;
241269
const arrayBuffer = await buffer.arrayBuffer();
242-
const blob = new Blob([arrayBuffer], { type: `audio/${song.ext}` });
270+
const blob = new Blob([arrayBuffer], { type: audioMimeType(song.ext) });
243271
const audioUrl = URL.createObjectURL(blob);
244272
curTime.set(0);
245273
setCurTime.set(0);
246274
audioPlayer.update((value) => {
247275
const audio = value.audio ?? (browser ? new Audio() : null);
248276
if (audio) {
277+
audio.pause();
278+
revokeCurrentAudioUrl();
279+
currentAudioUrl = audioUrl;
249280
audio.src = audioUrl;
250-
audio.currentTime = 0;
251281
audio.play();
252282
audio.addEventListener('loadedmetadata', () => {
253283
updateMediaSessionPositionState(audio);

src/lib/store.ts

Lines changed: 70 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -54,11 +54,21 @@ hideTips.subscribe((value) => {
5454
}
5555
});
5656
export const isSmallDevice = writable(false);
57+
function storedVolume() {
58+
if (!browser) return 100;
59+
const parsed = parseInt(localStorage.getItem('volume') ?? '100', 10);
60+
return Number.isFinite(parsed) ? Math.min(100, Math.max(0, parsed)) : 100;
61+
}
62+
const initialVolume = storedVolume();
63+
const initialAudio = browser ? new Audio() : null;
64+
if (initialAudio) {
65+
initialAudio.volume = initialVolume / 100;
66+
}
5767
export const audioPlayer = writable({
58-
audio: browser ? new Audio() : null,
68+
audio: initialAudio,
5969
onEnded: () => {},
6070
playing: false,
61-
volume: 100,
71+
volume: initialVolume,
6272
currentTime: 0,
6373
changeVolume: false
6474
});
@@ -88,6 +98,7 @@ export const recentlyPlayedManager = {
8898

8999
let endedHandler: ((this: HTMLAudioElement, ev: Event) => void) | null = null;
90100
let durationChangeHandler: ((this: HTMLAudioElement, ev: Event) => void) | null = null;
101+
let listenersBoundTo: HTMLAudioElement | null = null;
91102

92103
export const currentDuration = derived(audioPlayer, ($audioPlayer) => {
93104
return $audioPlayer.audio?.duration ?? 0;
@@ -97,71 +108,71 @@ let currentTime = 0;
97108
let lastListenTime = 0;
98109
let lastListenSongId = '';
99110

100-
audioPlayer.subscribe((value) => {
101-
if (browser) {
102-
if (value.audio instanceof HTMLAudioElement) {
103-
if (!value.changeVolume) {
104-
if (endedHandler) {
105-
value.audio.removeEventListener('ended', endedHandler);
106-
endedHandler = null;
107-
}
108-
109-
if (value.onEnded) {
110-
endedHandler = () => {
111-
value.onEnded();
112-
};
113-
value.audio.addEventListener('ended', endedHandler);
114-
}
111+
function bindAudioListeners(audio: HTMLAudioElement) {
112+
if (listenersBoundTo === audio) return;
115113

116-
value.audio.ontimeupdate = () => {
117-
currentTime = value.audio?.currentTime ?? 0;
118-
if (value.playing) {
119-
curTime.set(value.audio?.currentTime ?? currentTime);
120-
setCurTime.set(value.audio?.currentTime ?? currentTime);
121-
}
122-
const song = get(activeSong);
123-
const state = get(queueState);
124-
if (value.playing && song?.id) {
125-
if (song.id !== lastListenSongId) {
126-
lastListenSongId = song.id;
127-
lastListenTime = value.audio?.currentTime ?? 0;
128-
} else {
129-
const nextTime = value.audio?.currentTime ?? 0;
130-
const delta = nextTime - lastListenTime;
131-
lastListenTime = nextTime;
132-
if (delta > 0 && delta <= 2.5) {
133-
statsManager.recordListeningSeconds(song, state.source, delta);
134-
}
135-
}
136-
}
137-
};
138-
139-
if (durationChangeHandler) {
140-
value.audio.removeEventListener('durationchange', durationChangeHandler);
141-
durationChangeHandler = null;
142-
}
114+
if (listenersBoundTo) {
115+
if (endedHandler) {
116+
listenersBoundTo.removeEventListener('ended', endedHandler);
117+
endedHandler = null;
118+
}
119+
listenersBoundTo.ontimeupdate = null;
120+
if (durationChangeHandler) {
121+
listenersBoundTo.removeEventListener('durationchange', durationChangeHandler);
122+
durationChangeHandler = null;
123+
}
124+
}
143125

144-
durationChangeHandler = () => {
145-
audioPlayer.update((state) => ({ ...state }));
146-
};
147-
value.audio.addEventListener('durationchange', durationChangeHandler);
126+
listenersBoundTo = audio;
148127

149-
if (value.audio instanceof HTMLAudioElement && value.currentTime !== undefined) {
150-
value.audio.currentTime = value.currentTime;
151-
}
128+
endedHandler = () => {
129+
get(audioPlayer).onEnded();
130+
};
131+
audio.addEventListener('ended', endedHandler);
132+
133+
audio.ontimeupdate = () => {
134+
const player = get(audioPlayer);
135+
currentTime = player.audio?.currentTime ?? 0;
136+
if (player.playing) {
137+
curTime.set(player.audio?.currentTime ?? currentTime);
138+
setCurTime.set(player.audio?.currentTime ?? currentTime);
139+
}
140+
const song = get(activeSong);
141+
const state = get(queueState);
142+
if (player.playing && song?.id) {
143+
if (song.id !== lastListenSongId) {
144+
lastListenSongId = song.id;
145+
lastListenTime = player.audio?.currentTime ?? 0;
152146
} else {
153-
if (value.audio instanceof HTMLAudioElement && value.volume !== undefined) {
154-
value.audio.volume = value.volume / 100;
155-
localStorage.setItem('volume', value.volume.toString());
156-
value.changeVolume = false;
157-
return;
158-
} else {
159-
value.changeVolume = false;
160-
return;
147+
const nextTime = player.audio?.currentTime ?? 0;
148+
const delta = nextTime - lastListenTime;
149+
lastListenTime = nextTime;
150+
if (delta > 0 && delta <= 2.5) {
151+
statsManager.recordListeningSeconds(song, state.source, delta);
161152
}
162153
}
163154
}
155+
};
156+
157+
durationChangeHandler = () => {
158+
audioPlayer.update((state) => ({ ...state }));
159+
};
160+
audio.addEventListener('durationchange', durationChangeHandler);
161+
}
162+
163+
audioPlayer.subscribe((value) => {
164+
if (!browser || !(value.audio instanceof HTMLAudioElement)) return;
165+
166+
if (value.changeVolume) {
167+
if (value.volume !== undefined) {
168+
value.audio.volume = value.volume / 100;
169+
localStorage.setItem('volume', value.volume.toString());
170+
}
171+
value.changeVolume = false;
172+
return;
164173
}
174+
175+
bindAudioListeners(value.audio);
165176
});
166177
function createTitle() {
167178
const { subscribe, set } = writable('');

0 commit comments

Comments
 (0)