feat(web): level playback by the user's normalization preference (M464 #4999)
release / govulncheck (push) Successful in 18s
release / web (push) Successful in 1m13s
release / go (push) Successful in 1m29s
release / integration (push) Successful in 4m26s
release / android (push) Successful in 5m20s
release / Build signed APK (releases and dev) (push) Successful in 5m31s
release / Attach APK to the Release (tag releases only) (push) Skipped
release / Build + push container image (push) Successful in 32s
release / Verify release artifacts (tag releases only) (push) Skipped
release / govulncheck (push) Successful in 18s
release / web (push) Successful in 1m13s
release / go (push) Successful in 1m29s
release / integration (push) Successful in 4m26s
release / android (push) Successful in 5m20s
release / Build signed APK (releases and dev) (push) Successful in 5m31s
release / Attach APK to the Release (tag releases only) (push) Skipped
release / Build + push container image (push) Successful in 32s
release / Verify release artifacts (tag releases only) (push) Skipped
Cuts go through element.volume. Boosts route the element through a Web Audio GainNode and a DynamicsCompressor (a -1 dBFS limiter in limiter mode, a pass-through otherwise), built only when a track wants a boost and only once an AudioContext is confirmed running; iOS never gets the graph. Auto mode takes album gain when a queue neighbour is from the same album in track order. Gains are fetched for the next 50 tracks as the queue moves, with a 10s deadline. The prefetch element is untouched. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { api } from './client';
|
||||
import { api, apiFetch } from './client';
|
||||
|
||||
// Loudness normalization preference (M464 #4998). One per user, stored on
|
||||
// the server so the web player, the Android app and casts agree.
|
||||
@@ -40,3 +40,23 @@ export function getNormalization(): Promise<NormalizationPrefs> {
|
||||
export function putNormalization(p: NormalizationPrefs): Promise<NormalizationPrefs> {
|
||||
return api.put<NormalizationPrefs>('/api/me/normalization', p);
|
||||
}
|
||||
|
||||
// Per-track gains (#4997): dB to the -18 LUFS ReplayGain reference and linear
|
||||
// peaks. A field is absent until the server has measured it.
|
||||
export type ReplayGain = {
|
||||
track_gain?: number;
|
||||
track_peak?: number;
|
||||
album_gain?: number;
|
||||
album_peak?: number;
|
||||
};
|
||||
|
||||
/**
|
||||
* Gains for up to 200 tracks; an id missing from items has none yet. Given
|
||||
* a deadline: the player waits on it, and a request that never answers
|
||||
* would leave those tracks unleveled for the rest of the session.
|
||||
*/
|
||||
export function getReplayGain(ids: string[]): Promise<{ items: Record<string, ReplayGain> }> {
|
||||
return apiFetch(`/api/tracks/replay-gain?ids=${ids.map(encodeURIComponent).join(',')}`, {
|
||||
signal: AbortSignal.timeout(10_000)
|
||||
}) as Promise<{ items: Record<string, ReplayGain> }>;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user