sergio/takana
> Una distribución Linux construida con un laboratorio funcional y hermético en el > sótano, y una terminal mutable, clásica y anárquica en el piso de arriba — diseñada > desde el suelo para que una IA programadora entienda, modifique y co…
⎇ 928d5eb119a399f636254f407d9b372be55887d6
recipes/atuq/extensions/medios/fondo.js
History
Who touched what
raw
atuq: un vídeo se abre en el reproductor de la distro, no en una pestaña
1"use strict";
2// medios — el vídeo no se ve en una pestaña (SDD 26 §6.6), v1.
3//
4// Cuando una navegación de PRIMER NIVEL resulta ser un medio —el servidor contesta
5// `Content-Type: video/*` o `audio/*`—, la navegación se cancela y la URL se le pasa
6// al host, que abre el reproductor de la distro. El navegador vuelve a ser un
7// navegador: no un reproductor a medias metido en una pestaña.
8//
9// ── POR QUÉ SÓLO EL PRIMER NIVEL, Y NO LOS `<video>` DE UNA PÁGINA ────────────
10// Un `<video>` embebido es parte de la página: sacarlo de ahí rompería el sitio que
11// lo puso. Lo que esta extensión cambia es el caso en que el navegador NO estaba
12// pintando una página, sino haciendo de reproductor: seguiste un enlace a un `.mp4`.
13// Es una regla estrecha a propósito — las reglas anchas en el camino de cada
14// petición son las que terminan rompiendo la web de alguien.
15//
16// ── LA REGLA DE ORO: SI NO HAY REPRODUCTOR, NO SE CANCELA NADA ────────────────
17// La decisión de cancelar es SÍNCRONA y la respuesta del host llega después, así que
18// no se puede saber en el momento si el reproductor arrancó. Se cancela de forma
19// optimista **sólo si el puerto está vivo**, y si el host contesta que no pudo, la
20// URL se vuelve a abrir en el navegador. Quedarse sin vídeo y sin pestaña sería el
21// peor resultado posible, y es el que se consigue si uno confía en que salió bien.
22
23const HOST = "puriy_costura";
24
25// Los tipos que se sacan del navegador. Deliberadamente cortos: `application/*` no
26// entra —hay demasiadas cosas que se sirven así— y los manifiestos de streaming
27// tampoco por ahora, porque el reproductor los pide con otras cabeceras.
28const MEDIOS = ["video/", "audio/"];
29
30let puerto = null;
31// url → tabId, para poder devolver al navegador lo que el host no pudo abrir.
32const enVuelo = new Map();
33
34function di(s) {
35 try { dump("MEDIOS " + s + "\n"); } catch (e) {}
36}
37
38function conectar() {
39 try {
40 puerto = browser.runtime.connectNative(HOST);
41 puerto.onMessage.addListener(alRecibir);
42 puerto.onDisconnect.addListener(p => {
43 di("DESCONECTADO " + (p.error ? p.error.message : "sin error"));
44 puerto = null;
45 });
46 di("CONECTADO " + HOST);
47 } catch (e) {
48 di("SIN HOST " + e.message);
49 puerto = null;
50 }
51}
52
53function alRecibir(r) {
54 if (!r || r.verb !== "media.open") {
55 if (r && r.ok === false) { fracaso(r); }
56 return;
57 }
58 if (r.ok === true && r.player) {
59 di("ABIERTO " + r.player.program + " pid=" + r.player.pid + " " + r.player.url);
60 enVuelo.delete(r.player.url);
61 return;
62 }
63 fracaso(r);
64}
65
66// El host no pudo. La navegación ya está cancelada, así que hay que devolverla: sin
67// esto el usuario se queda sin vídeo Y sin pestaña.
68function fracaso(r) {
69 di("SIN REPRODUCTOR " + (r && r.error ? r.error : "sin error"));
70 for (const [url, tabId] of enVuelo) {
71 enVuelo.delete(url);
72 try {
73 // `?atuq-medios=no` marca la reapertura para no volver a cancelarla: sin esa
74 // marca esto sería un bucle —cancelar, reabrir, cancelar— con la pestaña
75 // parpadeando y sin vídeo.
76 const vuelta = url + (url.includes("?") ? "&" : "?") + "atuq-medios=no";
77 browser.tabs.update(tabId, { url: vuelta });
78 di("VUELVE AL NAVEGADOR " + vuelta);
79 } catch (e) {
80 di("NO PUDE DEVOLVERLA " + e.message);
81 }
82 }
83}
84
85function esMedio(cabeceras) {
86 for (const h of cabeceras || []) {
87 if (h.name.toLowerCase() === "content-type") {
88 const v = (h.value || "").toLowerCase();
89 return MEDIOS.some(m => v.startsWith(m));
90 }
91 }
92 return false;
93}
94
95browser.webRequest.onHeadersReceived.addListener(
96 d => {
97 try {
98 if (d.url.includes("atuq-medios=no")) { return {}; } // ya volvió una vez
99 if (!esMedio(d.responseHeaders)) { return {}; }
100 if (!puerto) { conectar(); }
101 if (!puerto) {
102 // Sin host no se cancela: mejor un vídeo en una pestaña que ningún vídeo.
103 di("SIN HOST, no se cancela " + d.url);
104 return {};
105 }
106 di("MEDIO " + d.url);
107 enVuelo.set(d.url, d.tabId);
108 puerto.postMessage({ verb: "media.open", url: d.url });
109 di("CANCELADA la navegación " + d.url);
110 return { cancel: true };
111 } catch (e) {
112 di("FALLÓ el filtro " + e.message);
113 return {};
114 }
115 },
116 { urls: ["http://*/*", "https://*/*"], types: ["main_frame"] },
117 ["blocking", "responseHeaders"]
118);
119
120conectar();
121di("LISTA v1 saca del navegador sólo la navegación de primer nivel");