FIX: try to fix application volume control on linux

This commit is contained in:
Julien Maille 2026-02-13 21:01:57 +01:00
parent bb93edefde
commit 02cf092904
4 changed files with 46 additions and 19 deletions

View file

@ -81,12 +81,14 @@ class AudioContextManager {
this.analyser = null;
this.filters = [];
this.outputNode = null;
this.volumeNode = null;
this.isInitialized = false;
this.isEQEnabled = false;
this.isMonoAudioEnabled = false;
this.monoMergerNode = null;
this.currentGains = new Array(16).fill(0);
this.audio = null;
this.currentVolume = 1.0;
// Callbacks for audio graph changes (for visualizers like Butterchurn)
this._graphChangeCallbacks = [];
@ -170,6 +172,10 @@ class AudioContextManager {
this.outputNode = this.audioContext.createGain();
this.outputNode.gain.value = 1;
// Create volume node
this.volumeNode = this.audioContext.createGain();
this.volumeNode.gain.value = this.currentVolume;
// Create mono audio merger node
this.monoMergerNode = this.audioContext.createChannelMerger(2);
@ -199,6 +205,11 @@ class AudioContextManager {
// Disconnect everything first
this.source.disconnect();
this.outputNode.disconnect();
if (this.volumeNode) {
this.volumeNode.disconnect();
}
this.analyser.disconnect();
if (this.monoMergerNode) {
try {
this.monoMergerNode.disconnect();
@ -207,13 +218,6 @@ class AudioContextManager {
}
}
// Only disconnect destination from analyser to preserve other taps (like Butterchurn)
try {
this.analyser.disconnect(this.audioContext.destination);
} catch {
// Ignore if not connected
}
let lastNode = this.source;
// Apply mono audio if enabled
@ -234,15 +238,17 @@ class AudioContextManager {
}
if (this.isEQEnabled && this.filters.length > 0) {
// EQ enabled: lastNode -> EQ filters -> output -> analyser -> destination
// EQ enabled: lastNode -> EQ filters -> output -> analyser -> volume -> destination
lastNode.connect(this.filters[0]);
this.outputNode.connect(this.analyser);
this.analyser.connect(this.audioContext.destination);
this.analyser.connect(this.volumeNode);
this.volumeNode.connect(this.audioContext.destination);
console.log('[AudioContext] EQ connected');
} else {
// EQ disabled: lastNode -> analyser -> destination
// EQ disabled: lastNode -> analyser -> volume -> destination
lastNode.connect(this.analyser);
this.analyser.connect(this.audioContext.destination);
this.analyser.connect(this.volumeNode);
this.volumeNode.connect(this.audioContext.destination);
console.log('[AudioContext] EQ bypassed');
}
@ -313,6 +319,18 @@ class AudioContextManager {
return this.isInitialized;
}
/**
* Set the volume level (0.0 to 1.0)
* @param {number} value - Volume level
*/
setVolume(value) {
this.currentVolume = Math.max(0, Math.min(1, value));
if (this.volumeNode && this.audioContext) {
const now = this.audioContext.currentTime;
this.volumeNode.gain.setTargetAtTime(this.currentVolume, now, 0.01);
}
}
/**
* Toggle EQ on/off
*/

View file

@ -66,11 +66,6 @@ export function initializePlayerEvents(player, audioPlayer, scrobbler, ui) {
scrobbler.updateNowPlaying(player.currentTrack);
}
// Resume AudioContext for waveform on mobile (iOS)
if (waveformGenerator.audioContext.state === 'suspended') {
waveformGenerator.audioContext.resume();
}
updateWaveform();
}

View file

@ -111,8 +111,17 @@ export class Player {
// Calculate effective volume
const effectiveVolume = curvedVolume * scale;
// Apply to audio element
this.audio.volume = Math.max(0, Math.min(1, effectiveVolume));
// Apply to audio element and/or Web Audio graph
if (audioContextManager.isReady()) {
// If Web Audio is active, we apply volume there for better compatibility
// Especially on Linux where audio.volume might not affect the Web Audio graph
// We set audio.volume to 1.0 to avoid double-reduction, or keep it synced?
// Some browsers require audio.volume to be set for system media controls to show volume
this.audio.volume = 1.0;
audioContextManager.setVolume(effectiveVolume);
} else {
this.audio.volume = Math.max(0, Math.min(1, effectiveVolume));
}
}
applyAudioEffects() {
@ -213,6 +222,7 @@ export class Player {
// Must happen before audio.play() or audio won't route through Web Audio
if (!audioContextManager.isReady()) {
audioContextManager.init(this.audio);
this.applyReplayGain();
}
await audioContextManager.resume();
@ -233,6 +243,7 @@ export class Player {
// Ensure audio context is active for iOS lock screen controls
if (!audioContextManager.isReady()) {
audioContextManager.init(this.audio);
this.applyReplayGain();
}
await audioContextManager.resume();
this.playPrev();
@ -242,6 +253,7 @@ export class Player {
// Ensure audio context is active for iOS lock screen controls
if (!audioContextManager.isReady()) {
audioContextManager.init(this.audio);
this.applyReplayGain();
}
await audioContextManager.resume();
this.playNext();

View file

@ -2,7 +2,9 @@
export class WaveformGenerator {
constructor() {
this.audioContext = new (window.AudioContext || window.webkitAudioContext)();
// Use OfflineAudioContext to prevent creating unnecessary OS audio streams
// decodeAudioData doesn't require a real-time AudioContext
this.audioContext = new (window.OfflineAudioContext || window.webkitOfflineAudioContext)(1, 1, 44100);
this.cache = new Map();
}