FIX: try to fix application volume control on linux
This commit is contained in:
parent
bb93edefde
commit
02cf092904
4 changed files with 46 additions and 19 deletions
|
|
@ -81,12 +81,14 @@ class AudioContextManager {
|
||||||
this.analyser = null;
|
this.analyser = null;
|
||||||
this.filters = [];
|
this.filters = [];
|
||||||
this.outputNode = null;
|
this.outputNode = null;
|
||||||
|
this.volumeNode = null;
|
||||||
this.isInitialized = false;
|
this.isInitialized = false;
|
||||||
this.isEQEnabled = false;
|
this.isEQEnabled = false;
|
||||||
this.isMonoAudioEnabled = false;
|
this.isMonoAudioEnabled = false;
|
||||||
this.monoMergerNode = null;
|
this.monoMergerNode = null;
|
||||||
this.currentGains = new Array(16).fill(0);
|
this.currentGains = new Array(16).fill(0);
|
||||||
this.audio = null;
|
this.audio = null;
|
||||||
|
this.currentVolume = 1.0;
|
||||||
|
|
||||||
// Callbacks for audio graph changes (for visualizers like Butterchurn)
|
// Callbacks for audio graph changes (for visualizers like Butterchurn)
|
||||||
this._graphChangeCallbacks = [];
|
this._graphChangeCallbacks = [];
|
||||||
|
|
@ -170,6 +172,10 @@ class AudioContextManager {
|
||||||
this.outputNode = this.audioContext.createGain();
|
this.outputNode = this.audioContext.createGain();
|
||||||
this.outputNode.gain.value = 1;
|
this.outputNode.gain.value = 1;
|
||||||
|
|
||||||
|
// Create volume node
|
||||||
|
this.volumeNode = this.audioContext.createGain();
|
||||||
|
this.volumeNode.gain.value = this.currentVolume;
|
||||||
|
|
||||||
// Create mono audio merger node
|
// Create mono audio merger node
|
||||||
this.monoMergerNode = this.audioContext.createChannelMerger(2);
|
this.monoMergerNode = this.audioContext.createChannelMerger(2);
|
||||||
|
|
||||||
|
|
@ -199,6 +205,11 @@ class AudioContextManager {
|
||||||
// Disconnect everything first
|
// Disconnect everything first
|
||||||
this.source.disconnect();
|
this.source.disconnect();
|
||||||
this.outputNode.disconnect();
|
this.outputNode.disconnect();
|
||||||
|
if (this.volumeNode) {
|
||||||
|
this.volumeNode.disconnect();
|
||||||
|
}
|
||||||
|
this.analyser.disconnect();
|
||||||
|
|
||||||
if (this.monoMergerNode) {
|
if (this.monoMergerNode) {
|
||||||
try {
|
try {
|
||||||
this.monoMergerNode.disconnect();
|
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;
|
let lastNode = this.source;
|
||||||
|
|
||||||
// Apply mono audio if enabled
|
// Apply mono audio if enabled
|
||||||
|
|
@ -234,15 +238,17 @@ class AudioContextManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.isEQEnabled && this.filters.length > 0) {
|
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]);
|
lastNode.connect(this.filters[0]);
|
||||||
this.outputNode.connect(this.analyser);
|
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');
|
console.log('[AudioContext] EQ connected');
|
||||||
} else {
|
} else {
|
||||||
// EQ disabled: lastNode -> analyser -> destination
|
// EQ disabled: lastNode -> analyser -> volume -> destination
|
||||||
lastNode.connect(this.analyser);
|
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');
|
console.log('[AudioContext] EQ bypassed');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -313,6 +319,18 @@ class AudioContextManager {
|
||||||
return this.isInitialized;
|
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
|
* Toggle EQ on/off
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -66,11 +66,6 @@ export function initializePlayerEvents(player, audioPlayer, scrobbler, ui) {
|
||||||
scrobbler.updateNowPlaying(player.currentTrack);
|
scrobbler.updateNowPlaying(player.currentTrack);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Resume AudioContext for waveform on mobile (iOS)
|
|
||||||
if (waveformGenerator.audioContext.state === 'suspended') {
|
|
||||||
waveformGenerator.audioContext.resume();
|
|
||||||
}
|
|
||||||
|
|
||||||
updateWaveform();
|
updateWaveform();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
14
js/player.js
14
js/player.js
|
|
@ -111,9 +111,18 @@ export class Player {
|
||||||
// Calculate effective volume
|
// Calculate effective volume
|
||||||
const effectiveVolume = curvedVolume * scale;
|
const effectiveVolume = curvedVolume * scale;
|
||||||
|
|
||||||
// Apply to audio element
|
// 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));
|
this.audio.volume = Math.max(0, Math.min(1, effectiveVolume));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
applyAudioEffects() {
|
applyAudioEffects() {
|
||||||
const speed = audioEffectsSettings.getSpeed();
|
const speed = audioEffectsSettings.getSpeed();
|
||||||
|
|
@ -213,6 +222,7 @@ export class Player {
|
||||||
// Must happen before audio.play() or audio won't route through Web Audio
|
// Must happen before audio.play() or audio won't route through Web Audio
|
||||||
if (!audioContextManager.isReady()) {
|
if (!audioContextManager.isReady()) {
|
||||||
audioContextManager.init(this.audio);
|
audioContextManager.init(this.audio);
|
||||||
|
this.applyReplayGain();
|
||||||
}
|
}
|
||||||
await audioContextManager.resume();
|
await audioContextManager.resume();
|
||||||
|
|
||||||
|
|
@ -233,6 +243,7 @@ export class Player {
|
||||||
// Ensure audio context is active for iOS lock screen controls
|
// Ensure audio context is active for iOS lock screen controls
|
||||||
if (!audioContextManager.isReady()) {
|
if (!audioContextManager.isReady()) {
|
||||||
audioContextManager.init(this.audio);
|
audioContextManager.init(this.audio);
|
||||||
|
this.applyReplayGain();
|
||||||
}
|
}
|
||||||
await audioContextManager.resume();
|
await audioContextManager.resume();
|
||||||
this.playPrev();
|
this.playPrev();
|
||||||
|
|
@ -242,6 +253,7 @@ export class Player {
|
||||||
// Ensure audio context is active for iOS lock screen controls
|
// Ensure audio context is active for iOS lock screen controls
|
||||||
if (!audioContextManager.isReady()) {
|
if (!audioContextManager.isReady()) {
|
||||||
audioContextManager.init(this.audio);
|
audioContextManager.init(this.audio);
|
||||||
|
this.applyReplayGain();
|
||||||
}
|
}
|
||||||
await audioContextManager.resume();
|
await audioContextManager.resume();
|
||||||
this.playNext();
|
this.playNext();
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,9 @@
|
||||||
|
|
||||||
export class WaveformGenerator {
|
export class WaveformGenerator {
|
||||||
constructor() {
|
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();
|
this.cache = new Map();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue