JavaScript: Mejorando el volumen de medios HTML con Web Audio API

Introducción

Existen ocasiones en las que navegamos por páginas web cuyo volumen de reproducción de medios es notablemente bajo. En lugar de ajustar el volumen del sistema o usar herramientas externas, podemos emplear una solución basada en scripts para usuarios avanzados que permite aumentar el volumen de elementos multimedia en la página.

Características clave

  • Rango de ajuste de volumen: 0% a 666%
  • Uso de Web Audio API para amplificación de audio
  • Interfaz de usuario minimalista y funcional
  • Compatibilidad con elementos dentro de iframes
  • Soporte para múltiples plataformas de video
  • Configuración personalizada por sitio web
  • Panel de control en tiempo real

Limitaciones técnicas

Debido a las restricciones del estándar Web Audio API, un elemento multimedia solo puede ser procesado por un único contexto de audio. Esto significa que si otro script o la propia página ya está utilizando createMediaElementSource()>, nuestro script no podrá aplicarse.

Implementación técnica

El script utiliza únicamente JavaScript nativo sin dependencias externas, lo que garantiza compatibilidad con la mayoría de navegadores moderons (Chrome, Firefox, Edge).

Estructura principle del script

// ==UserScript==
// @name         Amplificador de volumen web
// @namespace    https://github.com/Stareven233
// @version      1.6.2
// @description  Mejora el volumen de elementos multimedia en páginas web
// @match        *://*/*
// @grant        GM_registerMenuCommand
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_notification
// @run-at       document-end
// ==/UserScript==

(function () {
  'use strict';

  if (window._n_volumAmpInitialized) {
    return;
  }
  window._n_volumAmpInitialized = true;

  const MAX_AMP_FACTOR = 6.66;
  const STEP_AMP = 0.01;

  // Funciones de utilidad
  function escapeHTML(str) {
    return str.replace(/[&<>"']/g, m => ({
      '&': '&', '<': '<', '>': '>', '"': '"', "'": '''
    }[m]));
  }

  // Gestión de configuración
  function loadConfig() {
    return GM_getValue('ampConfig', { rules: [], active: true });
  }

  function saveConfig(config) {
    GM_setValue('ampConfig', config);
  }

  function getSiteSettings() {
    return GM_getValue('ampSettings', {});
  }

  function saveSettings(settings) {
    GM_setValue('ampSettings', settings);
  }

  // Lógica de coincidencia de URLs
  function matchUrl() {
    const config = loadConfig();
    if (!config.active || !config.rules?.length) {
      return { matched: false };
    }

    const currentUrl = window.location.href;
    for (const pattern of config.rules) {
      try {
        if (new RegExp(pattern).test(currentUrl)) {
          return { matched: true, rule: pattern };
        }
      } catch (e) {
        if (currentUrl.includes(pattern)) {
          return { matched: true, rule: pattern };
        }
      }
    }
    return { matched: false };
  }

  // Panel de configuración
  function openSettingsPanel() {
    // Implementación del panel de configuración
  }

  // Registro de menú contextual
  function registerMenu() {
    // Implementación del menú de usuario
  }

  // Funcionalidad principal
  let audioContext, gainControl;

  function initializeAmplifier(videoElement, volumeLevel) {
    try {
      if (videoElement._ampBound) {
        return true;
      }
      
      const AudioContext = window.AudioContext || window.webkitAudioContext;
      audioContext = new AudioContext();
      const source = audioContext.createMediaElementSource(videoElement);
      gainControl = audioContext.createGain();
      gainControl.gain.value = volumeLevel;
      
      source.connect(gainControl).connect(audioContext.destination);
      videoElement._ampBound = true;

      // Resumir contexto de audio si está suspendido
      const resumeAudio = () => {
        if (audioContext.state === 'suspended') audioContext.resume();
      };
      
      window.addEventListener('mousedown', resumeAudio, { once: true });
      window.addEventListener('keydown', resumeAudio, { once: true });
      
      return true;
    } catch (error) {
      console.warn('Fallo al inicializar el amplificador:', error);
      return false;
    }
  }

  function createControlPanel(settings, rule) {
    // Implementación del panel de control
  }

  // Lógica de inicio
  const isMainFrame = window.self === window.top;
  if (isMainFrame) registerMenu();

  const { matched, rule } = matchUrl();
  if (!matched && isMainFrame) return;

  const siteSettings = getSiteSettings();
  const currentSettings = (rule && siteSettings[rule]) || { selector: 'video', volume: 1.0 };

  if (isMainFrame) {
    createControlPanel(currentSettings, rule);
    const statusTitle = document.getElementById('ampStatusTitle');

    // Escuchar mensajes de iframes
    window.addEventListener('message', (e) => {
      if (e.data?.type === '_ampReady' && statusTitle) {
        statusTitle.innerText = 'Amplificador de volumen';
      }
    });

    // Buscar elementos de video
    function findVideoElement() {
      const video = document.querySelector(currentSettings.selector);
      if (video) {
        const success = initializeAmplifier(video, currentSettings.volume);
        if (statusTitle) {
          statusTitle.innerText = success ? 'Amplificador de volumen' : 'Fallo en inicialización - conflicto con otro script';
        }
        return success;
      }
      return false;
    }

    // Buscar video periódicamente
    let attemptCount = 0;
    const searchInterval = setInterval(() => {
      if (findVideoElement() || attemptCount++ > 10) {
        clearInterval(searchInterval);
      }
    }, 1000);

    // Comunicación con iframes
    setTimeout(() => {
      for (const frame of document.querySelectorAll('iframe')) {
        try {
          frame.contentWindow.postMessage({
            type: '_ampInit',
            volume: currentSettings.volume,
            selector: currentSettings.selector
          }, '*');
        } catch (_) { }
      }
    }, 2000);

  } else {
    // Lógica para elementos dentro de iframes
    window.addEventListener('message', (e) => {
      if (e.source !== window.parent) return;
      
      if (e.data?.type === '_ampInit') {
        const video = document.querySelector(e.data.selector || 'video');
        if (video && initializeAmplifier(video, e.data.volume)) {
          window.parent.postMessage({ type: '_ampReady' }, '*');
        }
      } else if (e.data?.type === '_ampVolume' && gainControl) {
        gainControl.gain.value = e.data.volume;
      }
    });
  }
})();

Función básica de amplificación

Para casos más simples, podemos usar esta versión compacta:

function amplifyMedia(mediaElement, factor) {
  const context = new (window.AudioContext || window.webkitAudioContext);
  const result = {
    context: context,
    source: context.createMediaElementSource(mediaElement),
    gain: context.createGain(),
    media: mediaElement,
    amplify: function (factor) { this.gain.gain.value = factor },
    getLevel: function () { return this.gain.gain.value }
  };
  
  result.source.connect(result.gain);
  result.gain.connect(context.destination);
  result.amplify(factor);
  return result;
}

Uso: En la consola del navegador, tras seleccionar un elemento multimedia:

amplifyMedia(elem, 6)

Uso del script

  1. Instalar el script desde Greasy Fork
  2. Visitar una página con contenido multimedia
  3. Usar el menú contextual del script para:
    • Añadir/quitar reglas para la página actual
    • Gestionar reglas de coincidencia
  4. Usar el panel de control para:
    • Ajustar el factor de amplificación en tiempo real
    • Configurar selectores personalizados para elemnetos de video
    • Guardar configuraciones específicas por sitio

Ejemplos de reglas de coincidencia

  • URL específica: https://ejemplo.tv/257254-3-4.html
  • Todas las páginas de Bilibili: https?://www.bilibili.com/video/.*
  • Páginas de YouTube: https?://www.youtube.com/watch?v=.*
  • Todas las páginas de un dominio: https?://dominio.com/.*

Etiquetas: JavaScript web-audio-api UserScript Tampermonkey html5-video

Publicado el 9-12 12:00