gioser

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…

TOML 1572Shell 203Rust 109Markdown 993728 Definitions

⎇ 928d5eb119a399f636254f407d9b372be55887d6 recipes/atuq/extensions/medios/fondo.js JavaScript · 121 lines · 4.5 KB History Who touched what raw

atuq: un vídeo se abre en el reproductor de la distro, no en una pestaña 928d5eb11 · Sergio ·

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");