diff --git a/audio_enhancer.js b/audio_enhancer.js new file mode 100644 index 0000000..ddc768e --- /dev/null +++ b/audio_enhancer.js @@ -0,0 +1,112 @@ +class AudioEnhancer { + constructor(videoElement) { + if (!window.AudioContext) { + this.isSupported = false; + return; + } + this.isSupported = true; + this.isEnabled = false; + this.videoElement = videoElement; + this.audioContext = new AudioContext(); + this.sourceNode = this.audioContext.createMediaElementSource(this.videoElement); + + this.preamp = this.audioContext.createGain(); + this.compressor = this.audioContext.createDynamicsCompressor(); + this.bandFrequencies = [60, 170, 310, 600, 1000, 3000, 6000, 12000, 14000, 16000]; + this.filters = this.bandFrequencies.map(freq => { + const filter = this.audioContext.createBiquadFilter(); + filter.type = 'peaking'; + filter.frequency.value = freq; + filter.Q.value = 1.41; + filter.gain.value = 0; + return filter; + }); + + this.connectNodes(); + } + + connectNodes() { + this.sourceNode.disconnect(); + if (this.isEnabled) { + let lastNode = this.preamp; + this.sourceNode.connect(this.preamp); + this.filters.forEach(filter => { + lastNode.connect(filter); + lastNode = filter; + }); + lastNode.connect(this.compressor); + this.compressor.connect(this.audioContext.destination); + } else { + this.sourceNode.connect(this.audioContext.destination); + } + } + + toggle(state) { + this.isEnabled = state; + this.connectNodes(); + } + + setCompressor(settings) { + if (!this.isSupported || !settings) return; + this.compressor.threshold.value = settings.threshold || -24; + this.compressor.knee.value = settings.knee || 30; + this.compressor.ratio.value = settings.ratio || 12; + this.compressor.attack.value = settings.attack || 0.003; + this.compressor.release.value = settings.release || 0.25; + } + + changeGain(bandIndex, gainValue) { + if (!this.isSupported || bandIndex < 0 || bandIndex >= this.filters.length) return; + this.filters[bandIndex].gain.value = gainValue; + } + + changePreamp(gainValue) { + if (!this.isSupported) return; + const linearValue = Math.pow(10, gainValue / 20); + this.preamp.gain.value = linearValue; + } + + applySettings(settings) { + if (!this.isSupported || !settings) return; + + this.toggle(settings.enabled); + + if (typeof settings.preamp === 'number') { + this.changePreamp(settings.preamp); + } + + if (Array.isArray(settings.bands)) { + settings.bands.forEach((gain, index) => { + this.changeGain(index, gain); + }); + } + + if (settings.compressor) { + this.setCompressor(settings.compressor); + } else { + this.setCompressor({}); + } + } + + getSettings() { + if (!this.isSupported) return { enabled: false, preamp: 0, bands: new Array(10).fill(0), customPresets: [] }; + const preampDB = 20 * Math.log10(this.preamp.gain.value); + const bandGains = this.filters.map(filter => filter.gain.value); + return { enabled: this.isEnabled, preamp: preampDB, bands: bandGains }; + } + + destroy() { + if (!this.isSupported) return; + try { + this.sourceNode.disconnect(); + this.preamp.disconnect(); + this.compressor.disconnect(); + this.filters.forEach(filter => filter.disconnect()); + if (this.audioContext.state !== 'closed') { + this.audioContext.close(); + } + } catch (e) { + console.error("Error al destruir AudioEnhancer:", e); + } + } +} \ No newline at end of file diff --git a/css/eq_panel.css b/css/eq_panel.css new file mode 100644 index 0000000..bc97a33 --- /dev/null +++ b/css/eq_panel.css @@ -0,0 +1,220 @@ +.eq-panel { + position: absolute; + bottom: calc(var(--shaka-controls-height, 60px) + 15px); + right: 15px; + width: 450px; + max-width: 90vw; + background-color: rgba(var(--rgb-bg-tertiary), 0.95); + backdrop-filter: blur(10px); + -webkit-backdrop-filter: blur(10px); + border: 1px solid var(--border-color); + border-radius: var(--radius-md); + box-shadow: 0 5px 20px var(--shadow-color); + z-index: 20; + padding: 1rem; + display: flex; + flex-direction: column; + gap: 1rem; + opacity: 0; + transform: translateY(20px) scale(0.95); + transition: opacity 0.2s ease-out, transform 0.2s ease-out; + pointer-events: none; +} + +.eq-panel.open { + opacity: 1; + transform: translateY(0) scale(1); + pointer-events: auto; +} + +.eq-header { + display: flex; + justify-content: space-between; + align-items: center; + padding-bottom: 0.75rem; + border-bottom: 1px solid var(--border-color); +} + +.eq-header strong { + font-size: 1rem; + font-weight: 600; + color: var(--text-primary); +} + +.eq-band-container { + display: flex; + justify-content: space-around; + align-items: center; + gap: 5px; + padding: 10px 5px; +} + +.eq-band { + display: flex; + flex-direction: column; + align-items: center; + flex: 1; + min-width: 30px; +} + +.eq-slider-wrapper { + width: 25px; + height: 130px; + position: relative; + display: flex; + justify-content: center; + align-items: center; + margin-bottom: 8px; + background-color: var(--bg-element); + border-radius: var(--radius-lg); + border: 1px solid var(--border-color); +} + +input[type="range"].eq-slider { + -webkit-appearance: none; + appearance: none; + background: transparent; + cursor: pointer; + width: 110px; + transform: rotate(-90deg); +} + +input[type="range"].eq-slider::-webkit-slider-runnable-track { + height: 6px; + background: linear-gradient(to right, var(--accent-secondary) 0%, var(--accent-primary) 100%); + border-radius: 3px; +} + +input[type="range"].eq-slider::-moz-range-track { + height: 6px; + background: linear-gradient(to right, var(--accent-secondary) 0%, var(--accent-primary) 100%); + border-radius: 3px; +} + +input[type="range"].eq-slider::-webkit-slider-thumb { + -webkit-appearance: none; + appearance: none; + margin-top: -7px; + width: 20px; + height: 20px; + border-radius: 50%; + background: #ffffff; + border: 2px solid var(--bg-primary); + box-shadow: 0 0 5px rgba(0,0,0,0.5); + transition: transform 0.1s ease; +} + +input[type="range"].eq-slider::-moz-range-thumb { + width: 20px; + height: 20px; + border-radius: 50%; + background: #ffffff; + border: 2px solid var(--bg-primary); + box-shadow: 0 0 5px rgba(0,0,0,0.5); + transition: transform 0.1s ease; +} + +input[type="range"].eq-slider:active::-webkit-slider-thumb { + transform: scale(1.1); +} +input[type="range"].eq-slider:active::-moz-range-thumb { + transform: scale(1.1); +} + +.eq-band label { + font-size: 0.7rem; + color: var(--text-secondary); + margin-bottom: 4px; + font-weight: 500; +} + +.eq-band .eq-value { + font-size: 0.75rem; + color: var(--text-primary); + background-color: var(--bg-element); + padding: 2px 6px; + border-radius: var(--radius-sm); + min-width: 35px; + text-align: center; + border: 1px solid var(--border-color); +} + +.preamp-band label, +.preamp-band .eq-value { + font-weight: 600; + color: var(--accent-primary); +} + +.eq-controls-container { + display: flex; + flex-direction: column; + gap: 0.75rem; + padding-top: 0.75rem; + border-top: 1px solid var(--border-color); +} + +.eq-static-presets, .eq-custom-presets { + display: flex; + gap: 0.5rem; + justify-content: center; + align-items: center; +} + +.eq-custom-presets { + flex-grow: 1; +} + +.eq-custom-presets .form-select { + flex-grow: 1; + font-size: 0.8rem; + height: calc(1.5em + 0.5rem + 2px); + padding-top: 0.25rem; + padding-bottom: 0.25rem; +} + +.eq-custom-presets .btn-control { + flex-shrink: 0; +} + +.eq-panel .switch { + position: relative; + display: inline-block; + width: 38px; + height: 22px; +} +.eq-panel .switch input { + opacity: 0; + width: 0; + height: 0; +} +.eq-slider-toggle { + position: absolute; + cursor: pointer; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: #ccc; + transition: .4s; + border-radius: 22px; +} +.eq-slider-toggle:before { + position: absolute; + content: ""; + height: 16px; + width: 16px; + left: 3px; + bottom: 3px; + background-color: white; + transition: .4s; + border-radius: 50%; +} +.eq-panel input:checked + .eq-slider-toggle { + background-color: var(--accent-primary); +} +.eq-panel input:focus + .eq-slider-toggle { + box-shadow: 0 0 1px var(--accent-primary); +} +.eq-panel input:checked + .eq-slider-toggle:before { + transform: translateX(16px); +} \ No newline at end of file diff --git a/manifest.json b/manifest.json index 9666548..fc69f2a 100644 --- a/manifest.json +++ b/manifest.json @@ -50,6 +50,7 @@ "settings_manager.js", "db_manager.js", "m3u_utils.js", + "audio_enhancer.js", "shaka_handler.js", "xtream_handler.js", "xcodec_handler.js", @@ -78,7 +79,8 @@ "css/generic_modals.css", "css/components.css", "css/responsive.css", - "css/editor.css" + "css/editor.css", + "css/eq_panel.css" ], "matches": [ "chrome-extension://*/*" diff --git a/player.html b/player.html index 9fec71c..451de66 100644 --- a/player.html +++ b/player.html @@ -24,9 +24,9 @@ + -
@@ -163,6 +163,35 @@
+ +