JSMpeg es un decodificador de video MPEG1 y audio MP2 escrito en JavaScript, que incluye un desmultiplexor MPEG-TS, decodificadores de video y audio, renderizadores WebGL y Canvas2D, y salida de sonido WebAudio. Su diseño permite una reproducción eficiente en navegadores modernos, con carga estática vía Ajax y transmisión de baja latencia (alrededor de 50ms) mediante WebSockets. En este artículo, exploraremos cómo refactorizar JSMpeg para reemplazar el patrón de callbacks tradicional por un enfoque más limpio y mantenible basado en Promise y Async/Await.
Limitaciones del Patrón de Callbacks en JSMpeg
El código actual de JSMpeg depende en gran medida de callbacks para manejar operaciones asíncronas, lo que puede conducir a una estructura anidada difícil de gestionar, conocida como "infierno de callbacks". Por ejemplo, en el módulo de carga de WASM, se utiliza un callback para notificar la finalización:
// Ejemplo original de carga de WASM con callback
WASM.prototype.cargarDesdeArchivo = function(url, callback) {
if (callback) {
this.callbacksDeInicializacion.push(callback);
}
// ... lógica de carga
}
De manera similar, la funcionalidad de desbloqueo de audio en WebAudio también sigue este patrón:
// Ejemplo original de desbloqueo de audio con callback
WebAudioOut.prototype.desbloquear = function(callback) {
// ... lógica de desbloqueo
if (callback) {
callback();
}
}
Este enfoque incrementa la complejidad y reduce la legibilidad, especialmente al combinar múltiples operaciones asíncronas.
Refactorización hacia Promise/Async
La modernización de JSMpeg implica convertir funciones con callbacks en funciones que devuelvan Promises, permitiendo el uso de async/await para un flujo de control más claro. Esto se centra en encapsular la lógica asíncrona de manera declarativa.
Adaptación de la Carga de Módulos WASM
En lugar de depender de un callback, la función de carga puede retornar una Promise que se resuelve al completarse la inicialización:
// Refactorización: carga de WASM con Promise
async function inicializarWasm(url) {
return new Promise((resolver, rechazar) => {
// Lógica de carga simulada
setTimeout(() => {
console.log('Módulo WASM cargado');
resolver();
}, 100);
});
}
// Uso con async/await
async function main() {
try {
await inicializarWasm('decodificador.wasm');
console.log('Carga completada');
} catch (error) {
console.error('Error en carga de WASM:', error);
}
}
Refactorización del Desbloqueo de Audio
La funcionalidad de audio se adapta para devolver una Promise, simplificando la secuencia de operaciones:
// Refactorización: desbloqueo de audio con Promise
async function desbloquearAudio(salida) {
return new Promise((resolver) => {
// Simulación de desbloqueo
setTimeout(() => {
console.log('Audio desbloqueado');
resolver();
}, 50);
});
}
// Uso integrado
async function reproducirContenido() {
const salidaAudio = new WebAudioOut();
await desbloquearAudio(salidaAudio);
// Iniciar reproducción
}
Actualización de la API del Reproductor
El rerpoductor central de JSMpeg puede rediseñarse para ofrecer una interfaz basada en Promise, facilitando la creación y control asincrónico:
// Refactorización: creación de reproductor con Promise
const CrearReproductor = async (fuente, opciones) => {
return new Promise((resolver, rechazar) => {
// Lógica de inicialización
const reproductor = {
canvas: document.createElement('canvas'),
iniciar: () => console.log('Reproducción iniciada'),
// ... más métodos
};
setTimeout(() => resolver(reproductor), 150);
});
};
// Ejemplo de uso
async function setup() {
try {
const player = await CrearReproductor('video.ts', { bucle: true });
player.iniciar();
document.body.appendChild(player.canvas);
} catch (error) {
console.error('Fallo al crear reproductor:', error);
}
}
Ventajas de la Modernización
La adopción de Promise/Async en JSMpeg ofrece beneficios significativos:
- Legibilidad mejorada: El código asíncrono se asemeja al flujo síncrono, reduciendo la complejidad anidada.
- Manejo de errores centralizado: Con try/catch, se capturan excepciones de manera uniforme en toda la cadena asíncrona.
- Control de flujo avanzado: Facilita el uso de Promise.all o Promise.race para coordinar operaciones paralelas.
- Integración con ecosistemas modernos: Compatibilidad con librerías y frameworks que priorizan patrones asíncronos modernos.
Implementación Práctica
Para integrar la versión refactorizada de JSMpeg, se pueden seguir estos pasos genéricos:
// Ejemplo de módulo refactorizado
import { DecodificadorVideo } from './modulo-refactorizado.js';
async function ejecutarDecodificacion() {
try {
const decodificador = new DecodificadorVideo();
await decodificador.cargarRecursos('datos.ts');
decodificador.reproducir();
} catch (error) {
console.error('Error en decodificación:', error);
}
}