* notes / open-source
Contribuir a Video.js: mi código en el reproductor de medio internet
— Mis pull requests en Video.js como alexrqs: la arquitectura de componentes, el sistema de plugins, la etiqueta del open source y la emoción de ver tu código en un reproductor que usa medio mundo.
Contexto: ¿qué es Video.js y por qué importa?
Video.js es una librería de código abierto, distribuida bajo licencia Apache 2.0, que envuelve el elemento <video> nativo con una API consistente y extensible. Su valor no está sólo en los controles bonitos, sino en abstraer un panorama disperso (reproducción nativa en navegadores modernos, HLS y DASH mediante extensiones, fallbacks en plataformas que no soportan reproducción adaptativa) bajo un único contrato. Quien escribe contra la API de Video.js no necesita saber si por debajo el medio se sirve por MediaSource Extensions o por reproducción nativa de Safari; recibe los mismos eventos, los mismos métodos, las mismas garantías.
La arquitectura sobre la que se apoya es elegante a fuerza de simple: Component, Tech, Middleware y Plugin.
- Component es la unidad visual y semántica básica; todo lo que se ve en el reproductor es un componente (el
Playermismo, laControlBar, elPlayButton, elProgressControl) y todos heredan de la misma clase base con métodosel(),addChild(),on(),off(),dispose(). - Tech abstrae la tecnología de reproducción subyacente;
Html5es la implementación canónica, pero el proyecto admite techs alternativas para escenarios específicos. - Middleware permite interceptar y transformar peticiones entre el reproductor y el tech, útil para inyectar lógica de DRM, telemetría o reescritura de fuentes.
- Plugin es el contrato público para extender el reproductor sin tocar su núcleo; basta con registrar una función mediante
videojs.registerPlugin('miPlugin', fn)para que cualquier instancia dePlayeradquiera la capacidad correspondiente.
El árbol de componentes, de un vistazo
Esa jerarquía es la que un plugin debe conocer si pretende añadir un control nuevo o modificar uno existente. Los hijos se gestionan mediante addChild() y se posicionan en orden de inserción, con la posibilidad de fijar index para colocarse en un punto preciso de la ControlBar.
Implementación: un plugin mínimo que añade un control
Mi primera contribución no fue al núcleo, sino un plugin externo, videojs-snapback-button, que añadía un botón para retroceder diez segundos con una sola pulsación y veinte si se mantenía. El esqueleto del plugin, en TypeScript, es el siguiente:
import videojs from 'video.js';
import type Player from 'video.js/dist/types/player';
const Button = videojs.getComponent('Button') as any;
class SnapBackButton extends Button {{
constructor(player: Player, options?: Record<string, unknown>) {{
super(player, options);
this.controlText('Retroceder 10 segundos');
this.addClass('vjs-snapback-button');
}}
buildCSSClass(): string {{
return `vjs-icon-replay-10 ${{super.buildCSSClass()}}`;
}}
handleClick(): void {{
const t = this.player().currentTime() ?? 0;
this.player().currentTime(Math.max(0, t - 10));
}}
}}
videojs.registerComponent('SnapBackButton', SnapBackButton);
function snapBack(this: Player, options: {{ index?: number }} = {{}}) {{
this.ready(() => {{
const controlBar = this.getChild('ControlBar');
controlBar?.addChild('SnapBackButton', {{}}, options.index ?? 1);
}});
}}
videojs.registerPlugin('snapBack', snapBack);
export default snapBack;El patrón es el habitual: extiendes una clase base existente (Button en este caso), la registras como componente, y expones una función que se inscribe como plugin. Al invocar player.snapBack({ index: 2 }), el botón aparece como tercer hijo de la ControlBar. La belleza de la API está en que el resto del plumbing (ciclo de vida, theming, accesibilidad) viene heredado. Por defecto, Button ya responde a teclado, anuncia su texto a lectores de pantalla y respeta tabindex.
El flujo de contribución: de la issue al merge
Mi primer pull request al repositorio principal fue, irónicamente, una corrección de un caso límite en el manejo del foco al ocultar la ControlBar con inactivityTimeout. Detecté que, tras ese ocultamiento, un usuario con teclado quedaba enfocado en un control no visible. La secuencia que aprendí a seguir es la que recomienda cualquier proyecto sano:
- Abrir una issue descriptiva, con pasos de reproducción, navegador y versión exacta, y un gist o
codesandboxque demuestre el problema. - Esperar la conversación inicial: a menudo, un mantenedor responde para confirmar el bug, descartarlo como esperado, o redirigirlo a un componente distinto. Saltarse esta etapa y enviar directamente un PR especulativo es una falta de etiqueta común y casi siempre contraproducente.
- Diseñar el cambio respetando el estilo del proyecto: en Video.js eso significa preservar la compatibilidad de la API pública, escribir tests en
karma+QUnit, no introducir dependencias nuevas sin justificación, y respetar la convención de commit messages del proyecto. - Abrir el PR enlazando la issue, con descripción clara del qué, del por qué y del cómo (los tres son distintos), y una sección breve de trade-offs considerados.
- Atender la revisión con humildad operativa: aceptar sugerencias razonables, defender con argumentos las que no comparta uno, y aprender que un mantenedor que pide cinco cambios pequeños te está enseñando dónde está el listón.
El PR concreto que recuerdo con cariño tardó tres semanas en cerrarse, pasó por dos rondas de revisión, exigió que añadiera un test que reprodujera el caso y, finalmente, se mezcló con un mensaje breve del mantenedor que decía «Nice catch». Aquella frase compensó todas las revisiones.
Disciplina de semver y la responsabilidad compartida
Una de las lecciones que más se me grabó es la atención obsesiva con la que un proyecto maduro vigila el versionado semántico. Cualquier cambio que pudiera, en el escenario más imaginativo, alterar el comportamiento observable de la API pública se considera breaking y se aplaza para la próxima mayor. Esa disciplina, que de fuera parece exagerada, es lo que permite que miles de aplicaciones integren Video.js y lo actualicen sin pánico. Como contribuyente uno aprende a preguntarse, antes de proponer un cambio: ¿esto rompería el día de alguien que actualiza un patch?
Los correos con José Valim
No todo lo que me enseñó el open source pasó por un pull request. Por esa misma época intercambié varios correos con José Valim (ex core de Rails, creador de Elixir y una de las cabezas del ecosistema Phoenix). Le escribí esperando, como mucho, una respuesta de dos líneas al cabo de unas semanas; recibí respuestas rápidas, concretas y con más contexto del que pedí, de alguien que mantiene proyectos usados por miles de equipos y aun así se toma en serio el correo de un desconocido.
De ese intercambio me quedó la misma lección que me dejaron los mantenedores de Video.js, vista desde otro ángulo: la gente que sostiene las herramientas que usamos está mucho más dispuesta a conversar de lo que uno cree. Si escribes con contexto, con el trabajo previo hecho y yendo al grano, la puerta casi siempre se abre.
Hallazgos
De aquellas contribuciones me quedan tres aprendizajes muy concretos:
- La arquitectura de un buen framework enseña a programar. Pasar tardes leyendo cómo Video.js compone, dispone y limpia sus componentes me cambió la manera de diseñar mis propias librerías.
- El open source es, ante todo, conversación. El código es lo de menos; lo difícil es comunicar bien una idea, escuchar la objeción y modificar el rumbo sin orgullo. Quien sólo sabe escribir parches acaba abandonando contribuciones a medio cocer.
- Un PR mezclado en un proyecto grande no es un logro de vanidad, sino una responsabilidad. Tu código pasa a formar parte de algo más grande que tú, y conviene tener la madurez de mantenerlo, responder a las issues derivadas y, si llega el caso, deprecarlo con elegancia.