Fer que YouTube reprodueixi
Un sol lloc com a bateria de proves
Triar un únic lloc i fer que funcioni és una manera barata d'esbrinar què li falta a un motor, perquè un lloc gran ho fa servir tot. YouTube va resultar que necessitava Media Source Extensions, un backend de GStreamer capaç d'acceptar-les, un arranjament en com es llegeixen els timestamps de WebM, una propietat del DOM del 1997, un SVG que es redibuixi quan canvia el seu contingut, i un IntersectionObserver que es pugui reconnectar. Cap d'aquestes sis coses no va de vídeo tret de les dues primeres.
Media Source Extensions
MediaSource, SourceBuffer i SourceBufferList estan implementats, i URL.createObjectURL ja accepta un MediaSource. Un element de mitjans la font del qual és una URL així s'enganxa al MediaSource i reprodueix el que s'afegeix als seus source buffers.
Aquest enganxament canvia d'on surt l'estat del mateix element. Amb un MediaSource enganxat, l'element pren les seves metadades i la seva durada de la font i no del reproductor, i deriva readyState dels rangs buffered al voltant de la posició de reproducció — que és l'única definició que té sentit quan els bytes arriben a trossos i el tros que hi ha sota el capçal és el que decideix si es pot continuar.
Un pipeline per source buffer
El backend de GStreamer accepta source buffers mitjançant un reproductor creat amb StreamType::MediaSource. Cada source buffer fa demux del que se li afegeix amb un pipeline propi i guarda els frames codificats per pista; aquests frames guardats són d'on surten els rangs buffered, de manera que els rangs que llegeix l'script són un fet sobre el que s'ha analitzat i no un recompte de bytes rebuts.
La reproducció s'alimenta d'aquests frames per un appsrc per pista, dins d'un element nou, servomediasourcesrc. Després d'un seek comença al keyframe anterior a la posició demanada, perquè començar a la posició mateixa donaria al descodificador un frame que depèn d'un altre que no ha vist. S'accepten MP4 fragmentat i WebM.
El canvi de qualitat que ho aturava tot
Canviar de qualitat a YouTube li fa enganxar-se a un MediaSource nou i afegir des de la posició de reproducció actual. Dues coses diferents feien que allò fallés.
La primera és a matroskademux, que mapeja el primer cluster que rep al temps zero del seu segment. Alimentat amb un fragment de WebM que comença onze minuts endins del mitjà, guardava aquells frames com si comencessin al principi. Els rangs buffered deien aleshores que les dades eren al zero, no hi havia res buffered al voltant del capçal, i l'element esperava per sempre uns bytes que ja li havien donat. Els timestamps dels frames de WebM es prenen ara tal com vénen.
La segona és més simple: changeType a un altre contenidor es rebutjava de pla. Un lloc que canvia de WebM a MP4 fragmentat a mitja reproducció — que és el que és un canvi de qualitat entre còdecs — no tenia on anar.
preload, per fi obeït
Un element de mitjans es descarregava el recurs sencer tan bon punt tenia una font, digués el que digués preload, i mentre ho feia playbin en guardava una segona còpia en un fitxer temporal. En una pàgina amb una dotzena de vídeos això són dotze descàrregues completes que ningú no ha demanat, per duplicat.
preload decideix ara, fins que alguna cosa demani reproduir de debò. Amb none, no es descarrega res i no es crea cap reproductor. Amb metadata, que és el valor per defecte que suggereix l'especificació, la descàrrega s'atura tan bon punt hi ha el primer frame, networkState passa a NETWORK_IDLE i salta suspend. Una crida a play() ho anul·la tot, que és justament el punt: la pista descriu què fer abans que l'usuari decideixi, no un límit del que passa després.
Quatre bugs que no anaven de mitjans
document.all està implementat, cosa que el 2026 sona a broma i no ho és. El sanejador polymer-resin de YouTube compara valors contra document.all, i amb la propietat indefinida cada binding indefinit coincidia i es reemplaçava per la cadena zClosurez. HTMLAllCollection emula undefined — JSCLASS_EMULATES_UNDEFINED a la seva classe de proxy, així que typeof dóna "undefined" i l'objecte és fals — cosa que manté funcionant els sniffs d'IE antic i alhora dóna a la comparació un objecte real per trobar.
La imatge d'un element <svg> es rasteritza del marcatge del seu subarbre i es guarda fins que canvia l'element mateix o la seva llista de fills. Un canvi més avall — l'atribut d d'un path, per exemple — deixava la imatge vella a la pantalla. Per això el botó de play no passava mai a pausa, molt després que la reproducció ja funcionés.
Un IntersectionObserver es treu del seu document quan es desconnecta o perd el seu últim objectiu, però es quedava marcat com a connectat, així que observar un objectiu després no el tornava a posar i no informava de res des d'aquell moment, sense dir-ho. YouTube desconnecta i reconnecta l'observer amb què carrega imatges.
I un <svg> en línia es rasteritzava amb una escala diferent a cada eix per omplir la seva caixa, com es fa amb una imatge. La seva caixa és un viewport: el dibuix s'escala ara de manera uniforme i es centra, que és el que fa preserveAspectRatio per defecte. Els altres valors de l'atribut continuen sense mirar-se.
I els arranjaments de layout que hi ha a sota
Una pàgina com YouTube exercita prou CSS per trobar una altra classe de bug, dels que no són la funcionalitat de ningú i el problema de tothom.
mask es construeix ara com clips quan es pot. Una capa de màscara la imatge de la qual és opaca a tot arreu mostra l'element dins de la caixa que anomena el seu mask-clip i l'oculta fora, de manera que compondre capes així és pura geometria: un rectangle arrodonit, o un amb un altre més petit retallat amb un clip ClipOut. Això cobreix la manera habitual de dibuixar una vora amb degradat, on es componen dos degradats de color pla per obrir un forat.
backdrop-filter es retallava només a un rectangle, perquè WebRender en retalla un així, de manera que una caixa amb border-radius filtrava el seu fons fins a les cantonades quadrades. Els radis reben ara un clip propi a sobre de la cadena heretada.
Tres més, cadascun d'una línia i amb cua llarga. El recorregut cap amunt per l'arbre pla buscant un bloc contenidor no passava d'un ancestre sense estil i sense caixa — demanava un altre cop el pare del mateix node i no acabava mai. El layout incremental reconstruïa només la caixa quan un restyle feia que una caixa en flux passés a posicionada absolutament o flotada, així que la caixa vella mantenia el seu lloc en el flux; ara es reconstrueix el pare. I un element tret de l'arbre mentre estava en hover es quedava com a objectiu del hover, així que el moviment de ratolí següent pujava des d'un node desconnectat, no arribava mai als ancestres que continuaven a l'arbre, i els deixava amb :hover i sense mouseleave.
Dos sobre :has() i <svg>, que es van trobar entre ells. Stylo calcula de quins elements depèn un selector relatiu, però això només està cablejat per a Gecko, així que un estil que venia de :has() es quedava com estava després de la primera passada. Un canvi en els fills d'un node, o en un atribut que no sigui style, reestilitza ara els ancestres d'on ha passat — més gruixut del que Stylo podria i encara barat, perquè un element l'estil del qual surt igual atura el recorregut. I serialitzar un element <svg> llegia el color que hereta amb Element::style, que demana un reflow; executant-se dins de Window::reflow mentre aquest atenia els resultats d'un, aquell reflow imbricat trobava el mateix element pendent i recursava fins a desbordar la pila.
El que continua sense reproduir-se
El suport de còdecs és el que donin els plugins de GStreamer de la màquina, cosa que vol dir que un build pot passar un canPlayType i després fallar amb el stream de debò. Els mitjans xifrats no hi són gens — no hi ha EME, així que el que hi hagi darrere de DRM no és qüestió de bugs.
preserveAspectRatio es llegeix com el seu valor per defecte i res més. I res d'això no fa ràpid el lloc: els frames passen per un pipeline de demux per source buffer, que és correcte i no és el mateix que eficient.