Saltar a contenido

El manifiesto

kino-plugin.json, máximo 16 KB:

{
  "id": "archive-org",
  "name": "Internet Archive",
  "version": "1.0.0",
  "apiVersion": 1,
  "entry": "plugin.js",
  "description": "Películas de dominio público y televisión clásica de archive.org",
  "author": "kinotvapp",
  "homepage": "https://github.com/kinotvapp/kino-plugin-archive",
  "hosts": ["archive.org", "*.archive.org"],
  "capabilities": ["search", "home", "browse", "episodes", "resolve"],
  "color": "#E0A030",
  "icon": "icon.png"
}

Si se rompe una regla de las de abajo, Kino se niega a instalar el plugin y muestra un mensaje en español que nombra el campo.

Campo Regla
id Obligatorio. ^[a-z0-9][a-z0-9-]{1,39}$ (de 2 a 40 letras minúsculas, dígitos o guiones, sin empezar por guion). No puede ser live, local, unknown, plugin, own, subtitle-keys ni subtitle-prefs (nombres propios de Kino; las versiones anteriores reservan además otros ids: si una dice "El id … está reservado por Kino", escoge otro). Es la identidad del plugin: nunca lo cambies cuando ya haya gente que lo instaló.
name Obligatorio. De 1 a 40 caracteres.
version Obligatorio. MAJOR.MINOR.PATCH y nada más (sin -beta, sin +build), cada número de hasta 6 dígitos y sin ceros a la izquierda.
apiVersion Obligatorio. De 1 a 7. Un número más alto del que Kino soporta se rechaza con "Este plugin necesita una versión más nueva de Kino". Declara el número más bajo que tenga lo que usas, para que tu plugin también corra en versiones viejas de Kino: 2 para download, drm, insecureHttp, "hosts": [] o ítems live; 3 para channels/liveStreamHosts; 4 para un ajuste list, streamHosts o secrets; 5 solo para un plugin firmado (Kino 0.9.45+); 6 (Kino 0.9.50+) para cualquier cosa de la lista de apiVersion 6: secretos con tipo o más grandes, migrate, scopedSearch, streams firmados por petición, section/status/action en los ajustes, debug, telemetry, section, categories, theme, userMessage, entradas adult, canales en filas de Inicio, las llamadas de pares de llaves de kino.crypto, la capacidad meta, "browser": true con kino.browser.capture (y "browser": "pages" con kino.browser.page también), y las copias con etiqueta y perezosas; 7 (Kino 0.9.51+) para las capacidades tracking y segments.
entry Obligatorio. Ruta relativa del archivo JavaScript: solo letras, dígitos, ., _, - y /, sin .., máximo 200 caracteres, termina en .js. El archivo pesa máximo 1 MB. Escribe "plugin.js", nunca "./plugin.js": Kino 0.9.45 y anteriores rechazan un ./ al principio (mira la advertencia más abajo).
signature Opcional, desde apiVersion 5: { "authorKey": "<64 hex>", "value": "<128 hex>" }, la escribe node sdk/seal.mjs --sign: tu firma sobre el archivo de entrada. Necesita Kino 0.9.45+. Mira Plugins firmados. Por debajo de apiVersion 5 se ignora.
hosts Obligatorio. Al menos 1 entrada, sin tope máximo desde Kino 0.9.45 (solo la acota el manifiesto de 16 KB). Kino 0.9.44 y anteriores rechazan más de 20, así que con más de 20 hosts el kit avisa "Más de 20 hosts: Kino 0.9.44 o anterior rechaza este plugin; necesita Kino 0.9.45 o superior". Desde apiVersion 2 puede estar vacío, [], cuando el plugin tiene un ajuste de tipo url: mira Los servidores propios de la persona); cada una es un nombre DNS en minúsculas (archive.org), *. más un nombre DNS (*.archive.org) o (solo apiVersion 2) un objeto { "host": "…", "insecureHttp": true } (abajo). Solo nombres de host: sin esquema, puerto ni ruta. Nada de * solo, nada de direcciones IP, nada de localhost, nada que termine en .local, .lan, .internal, .localhost o .home.arpa, y por lo menos un punto. *.x cubre solo los subdominios, no x mismo: si necesitas los dos, pon los dos. Los hosts que la persona aprueba después, uno por uno, mientras tu plugin corre (Un host que se te olvidó) no cuentan contra el manifiesto.
capabilities Obligatorio. Un subconjunto de search, home, browse, episodes, resolve, download, drm, channels, migrate, scopedSearch, meta, subtitles, tracking, segments. Debe incluir resolve y al menos uno de search o home. search, home, browse, episodes y resolve tienen que ser, cada una, una función exportada del archivo de entrada, o la instalación falla con "El plugin no carga: le falta ...". download y drm necesitan apiVersion: 2 y son banderas declarativas — la app actúa sobre ellas, no tu código, así que no hay nada más que exportar; declarar una muestra su línea de consentimiento ("Puede descargar videos para verlos sin conexión" / "Reproduce video protegido (DRM)") y pide aprobación otra vez en una actualización que la agregue. download les da descargas sin conexión a tus títulos (mira Descargas); drm le permite a un Stream llevar una licencia Widevine (mira Un stream protegido con Widevine). channels necesita apiVersion: 3 y los exports liveCategories y liveChannels (mira Canales en la pestaña En vivo). migrate necesita apiVersion: 6 y el export migrate; declararla muestra "Revisar lo que tienes guardado (biblioteca, historial, favoritos) para pasarlo a este plugin" y pide aprobación otra vez en una actualización que la agregue (mira Pasar lo guardado). scopedSearch necesita apiVersion: 6 y search (si no, se rechaza con "La capacidad \"scopedSearch\" necesita también \"search\""); no exporta nada propio: tu search recibe within cuando la persona busca dentro de un "Ver más" (mira Buscar dentro de un "Ver más"). meta necesita apiVersion: 6 y el export meta, sin línea de consentimiento (mira Describir otros títulos). subtitles necesita el export subtitles; ["subtitles"] sola es un proveedor de subtítulos; un plugin que declara solo subtitles, tracking y/o segments (un proveedor de subtítulos, de seguimiento, de segmentos o una mezcla) es la única excepción a "resolve y search o home" (mira Subtítulos para cualquier título). tracking necesita apiVersion: 7 y el export track; declararla muestra una línea en rojo con tus hosts ("Le contará a seenr.app qué ves y cuándo lo terminas") y pide aprobación otra vez en una actualización que la agregue, aunque Kino apruebe otras actualizaciones por su cuenta (mira Contarle a un servicio de seguimiento qué ve la persona). segments necesita apiVersion: 7 y el export segments; declararla muestra "Agrega el botón para saltar la intro y los créditos", no en rojo y sin aprobación propia (mira Dónde están la intro y los créditos).
settings Opcional. Lo que la persona llena en la pantalla "Configurar" de tu plugin: mira abajo.
permissions Opcional. Una lista de nombres de la lista cerrada de contract.json. La lista está vacía en esta versión: cualquier nombre se rechaza con "permiso desconocido: …". Existe para que una versión futura pueda agregar permisos (cada uno visible en la pantalla de consentimiento) sin un apiVersion nuevo.
color Opcional, #RRGGBB: el acento de la pestaña y los chips de tu plugin. Por defecto, un color neutro.
icon Opcional, ruta relativa a un .png cuadrado de máximo 128 KB. Un ícono que falta o que pesa demasiado se omite sin que falle la instalación.
discoverable Opcional, true o false (por defecto true), en cualquier apiVersion. false deja el plugin por fuera de la búsqueda de la comunidad de Kino (mira Hazte encontrar); la gente igual puede instalarlo escribiendo su dirección. Cualquier otro valor se rechaza con "El campo \"discoverable\" debe ser true o false".
categories Opcional, en cualquier apiVersion: lo que ofrece tu plugin, para los chips de categoría de la tienda de plugins de Kino (Recomendados, "De la comunidad", "Elige tus fuentes"). Una lista sin repetidos de movies, series, anime, live, radio, subtitles, utilities, adult. Si está, reemplaza lo que Kino adivina por tus capacidades (channels es live, episodes es series, todo lo que lista y reproduce es movies); el chip de un plugin adult solo se ve mientras el código +18 de la persona está desbloqueado. Cualquier otro valor se rechaza con "El campo \"categories\" debe ser una lista sin repetidos de: movies, series, anime, live, radio, subtitles, utilities, adult". No es lo mismo que el export categories() (mosaicos dentro de Categorías de Kino, mira Sección, categorías y colores).
browser Opcional, true, false o "pages", desde apiVersion 6; por debajo se ignora. true deja que el plugin abra páginas en una vista web oculta en el aparato con kino.browser.capture (en resolve), en rojo como "Puede abrir páginas web ocultas para encontrar el video"; "pages" agrega kino.browser.page, como "Puede abrir páginas web ocultas para mostrar contenido y encontrar el video". Una actualización que lo agrega, o que pasa de true a "pages", espera aprobación de nuevo; el resolve de un plugin aprobado tiene 75 s. Mira Navegador oculto. Cualquier otro valor se rechaza con "El campo \"browser\" debe ser true, false o \"pages\"".
debug Opcional, true o false (por defecto false), desde apiVersion 6; por debajo se ignora. Desde Kino 0.9.50 todo plugin instalado tiene un interruptor "Modo debug" en su pestaña de Ajustes (errores en pantalla y un "Registro" que la persona puede copiar o compartirte); este campo solo lo deja encendido de entrada. Sin él arranca apagado y cada persona lo enciende cuando quiere mandarte un reporte. Cuando la persona lo toca, manda su decisión (validate.mjs te dice qué significa true). Mira Registro y telemetría. Cualquier otro valor se rechaza con "El campo \"debug\" debe ser true o false".
telemetry Opcional, true, false o "verbose" (por defecto false), desde apiVersion 6; por debajo se ignora. Pide compartir las líneas de diagnóstico de tu plugin con el registro de errores de Kino cuando una llamada falla, venga de donde venga el plugin, y enciende kino.log.report. Se muestra en la hoja de consentimiento y una actualización que lo declara por primera vez espera su aprobación. Por ahora las líneas de todo plugin que lo declara se envían siempre (todavía no hay interruptor; una versión posterior de Kino agrega uno por aparato, encendido por defecto). Mira Registro y telemetría. Cualquier otro valor se rechaza con "El campo \"telemetry\" debe ser true, false o \"verbose\"".
section Opcional, { "label": "…" } (de 1 a 20 caracteres), desde apiVersion 6; por debajo se ignora. Le da a tu plugin su propia sección y exige el export section: mira Sección, categorías y colores.
theme Opcional, un objeto, desde apiVersion 6; por debajo se ignora. Hasta cinco colores #RRGGBB: accent, onAccent, background, surface, highlight; cualquier otra clave se rechaza con "El campo \"theme\" tiene un color desconocido". El manifiesto solo revisa el formato; las protecciones de lectura corren cuando Kino usa los colores (Tus colores).
fetchHosts No es para tu plugin: Kino escribe "fetchHosts": "any" en los manifiestos que arma cuando convierte un scraper de Nuvio, y solo lo respeta en esos (después de que la persona lo aprueba, en rojo), para que el kino.fetch de un scraper convertido llegue a cualquier host público (mira Scrapers de Nuvio). En un plugin escrito a mano se ignora: tu kino.fetch sigue limitado a tus hosts, y sdk/validate.mjs avisa "fetchHosts solo tiene efecto en plugins convertidos desde Nuvio; en tu plugin se ignora". Desde apiVersion: 4 su único valor es "any"; cualquier otro se rechaza con "El campo \"fetchHosts\" solo admite \"any\"". Por debajo de apiVersion 4 se ignora.
description, author, homepage Textos opcionales. Se les quitan los espacios de los extremos y se cortan a 300, 60 y 200 caracteres. Kino muestra el nombre, el autor, la versión y la descripción cuando le pregunta a la persona si quiere instalar.

Las demás claves se ignoran. hosts cumple tres funciones: es lo que la persona aprueba, es el único conjunto de sitios a los que llega kino.fetch, y es el conjunto en el que deben estar las URL de un Stream: el video, sus subtítulos, sus audioTracks y la licenseUrl de un bloque drm (además del servidor propio de la persona).

Hay un campo más, liveStreamHosts, que solo se lee con "apiVersion": 3 y solo en plugins con la capacidad channels: mira Canales desde cualquier servidor. Los ítems en vivo (apiVersion 2) y la pestaña En vivo (apiVersion 3) tienen su propia página, Canales en vivo.

Rutas en entry e icon: sin ./ al principio

Escribe plugin.js, nunca ./plugin.js

entry e icon son rutas relativas al manifiesto. Kino 0.9.45 y anteriores rechazan un ./ al principio: la instalación falla con El campo "entry" debe ser una ruta relativa a un archivo .js (o lo mismo para "icon"), y en la app solo parece que el plugin "no se instala". Un plugin generado con IA escribió "./plugin.js" y falló en unas 35 instalaciones. Kino 0.9.46 y posteriores aceptan un ./ al principio y lo quitan, pero todavía hay gente con versiones más viejas, así que escribe siempre "plugin.js" e "icon.png", sin ./ (una carpeta sí vale: "src/plugin.js"). El validate.mjs del kit lo rechaza con: Quita el "./" del campo "entry" (por ejemplo "plugin.js"): Kino 0.9.45 y anteriores no instalan el plugin con "./".

Reproducir desde cualquier servidor (streamHosts, apiVersion 4)

Algunas fuentes sirven el video desde CDNs cuyos dominios no puedes listar (cambian, o están en TLDs sueltos, que una entrada *.xyz nunca cubre). Con "apiVersion": 4 un plugin puede agregar:

"apiVersion": 4,
"streamHosts": "any"

"any" es el único valor y no hace falta ninguna capacidad; un manifiesto más viejo ignora el campo. Deja que lo que el plugin reproduce esté en cualquier host público, por http o https:

  • una película o un episodio: exactamente la regla del permiso amplio de video -- la misma regla, pedida por ti de entrada en vez de concedida por la persona. En el reproductor, la url que devuelve resolve, todo lo que nombra su manifiesto, cada salto de redirección y los subtitles y audioTracks que devuelves pueden estar en cualquier host público, y nunca se pregunta por un host de video. Una descarga de esa película o episodio sigue la misma regla;
  • un canal en vivo: la regla de liveStreamHosts: "any" (el stream, su manifiesto y sus redirecciones; tus subtitles y audioTracks siguen en tus hosts).

No cambia nada más: kino.fetch (y por tanto todo secreto sellado), las imágenes y los servidores de licencia DRM siguen en los hosts que declaraste, y las direcciones locales o privadas (y los nombres públicos que resuelven dentro de la red de la casa) siguen rechazadas. La pantalla de consentimiento lo muestra en rojo ("Puede reproducir video desde cualquier servidor que indique"), y una actualización que lo agrega espera a que la persona apruebe de nuevo. Si puedes, lista los dominios reales: la gente confía más en una lista corta.

fetchHosts no es para ti

Los plugins que Kino arma por su cuenta a partir de un scraper de Nuvio (Scrapers de Nuvio) llevan un campo más, "fetchHosts": "any", que deja que su kino.fetch llegue a cualquier servidor público. Kino lo respeta solo en esas instalaciones convertidas. En un plugin que escribes tú, "any" se acepta y se ignora (la pantalla de consentimiento no lo muestra y kino.fetch sigue en tus hosts), y cualquier otro valor se rechaza. Declara tus hosts.

Secretos sellados (apiVersion 4)

Un plugin que trae una clave fija (un token de API metido en el cliente del propio sitio, un secreto por cliente que es del autor) puede sellarla en vez de escribirla en texto plano en el manifiesto:

node sdk/seal.mjs --repo owner/repo --name apiKey

(owner/repo/ruta para un plugin que vive en una subcarpeta.) --repo sigue las mismas reglas que la dirección desde la que la gente instala: se quitan un / final y un .git, pero una URL (https://github.com/...) y un @ref se rechazan en vez de adivinar. El valor se lee de un prompt oculto o por stdin -- nunca como argumento de la línea de comandos, que quedaría en el historial de la terminal. Debe tener de 1 a 4.096 bytes (UTF-8) -- hasta 8.192 con "apiVersion": 6; la herramienta imprime una línea, kino-sealed:v1:..., para pegar en el manifiesto:

"apiVersion": 4,
"secrets": { "apiKey": "kino-sealed:v1:AbC123..." }
  • Hasta 16 secretos; cada nombre cumple ^[A-Za-z][A-Za-z0-9_]{0,31}$. secrets necesita "apiVersion": 4; por debajo el campo se ignora (el plugin se instala sin secretos y kino.secret lanza error con cualquier nombre), y un Kino demasiado viejo para apiVersion 4 rechaza toda la instalación con "Este plugin necesita una versión más nueva de Kino".
  • Un sello queda atado al repositorio (y subcarpeta) que le pasaste a seal.mjs, en minúsculas, nunca a un ref. Al instalar y en cada actualización, Kino abre cada sello una vez contra la dirección desde la que la persona instala, solo para comprobar que es de ahí; cada ejecución del plugin los vuelve a abrir, en memoria, solo para esa ejecución. Un sello hecho para otro repositorio, ruta o nombre, o uno dañado, se rechaza con "Los datos sellados de este plugin no son para este repositorio o están dañados"; una versión que no puede abrir sellos los rechaza con "Este Kino no puede abrir datos sellados".
  • Solo desde la rama principal, nunca con un @ref explícito. GitHub sirve cualquier commit alcanzable en la red de forks de un repositorio -- el de un fork o el de un pull request -- por la dirección del repositorio padre, y no solo con un SHA evidente: un prefijo hexadecimal corto o un ref de git-describe resuelven igual. Así, owner/repo@<lo-que-sea> puede ser el manifiesto de otra persona, con sus propios hosts, mientras el sello sigue diciendo owner/repo. Un plugin con secretos que se instala o actualiza con cualquier @ref explícito -- rama, etiqueta o commit -- se rechaza con "Los datos sellados solo funcionan si instalas el plugin desde su rama principal, sin @rama", y una ejecución desde una dirección así no recibe secretos.
  • Un sello confía en el nombre del repositorio: si su dueño cambia de nombre o se borra y otra persona registra ese nombre, su repositorio abre tus sellos. Vuelve a sellar para el nombre nuevo, y cambia el valor si el viejo valía la pena protegerlo.
  • Declarar cualquier secreto agrega "Usa datos sellados por su autor" a la hoja de consentimiento; una actualización que trae secretos a un plugin que no tenía pide aprobación otra vez, igual que un host nuevo. Agregar, cambiar o quitar un secreto en un plugin que ya declaraba alguno no la pide.

Llaves de cifrado con tipo (apiVersion 6)

Un valor sellado que se usa como llave de kino.crypto.encrypt/decrypt se puede declarar como llave, para que Kino lea sus bytes con una codificación fijada en el manifiesto en vez del keyEncoding que pase el código. Eso es lo que deja que una llave sellada sirva también para des-ede3-* (una llave sellada sin tipo sigue siendo solo para AES):

node sdk/seal.mjs --repo owner/repo --name portalKey --use cipher-key --encoding hex

imprime una línea JSON para pegar como valor del secreto:

"apiVersion": 6,
"secrets": { "portalKey": { "seal": "kino-sealed:v1:...", "use": "cipher-key", "encoding": "hex" } }
  • use tiene que ser "cipher-key"; encoding es "hex" o "base64"; no se permite ningún otro campo.
  • El valor, leído con esa codificación, tiene que dar una llave de 16, 24 o 32 bytes: seal.mjs rechaza cualquier otra cosa, y Kino rechaza la instalación con "El secreto "portalKey" debe ser una clave de 16, 24 o 32 bytes".
  • kino.secret("portalKey") sirve como la key completa de cualquier encrypt/decrypt; el keyEncoding que pases se ignora. Se rechaza, con "no se puede usar un dato sellado aquí", como llave de HMAC, como entrada de pbkdf2, en cualquier parte de data/iv/aad, y en cualquier parte de una petición de kino.fetch (la URL, los nombres o valores de los headers, un cuerpo de texto, JSON o formulario): una llave con tipo es solo para kino.crypto y nunca sale por la red.
  • Por debajo de apiVersion 6 un objeto aquí no es un sello: el manifiesto se rechaza.
  • El kit de Node simula todo esto a partir del valor en claro de .kino-secrets.json; los bytes de la llave, en hex (mayúsculas o minúsculas) o base64, también se tapan en todo lo que devuelve un servidor.

Qué protege y qué no. Esto es ofuscación, no secreto: la clave privada que abre un sello va dentro de cada copia de Kino. Sellar un valor lo saca de tu manifiesto y del historial de tu repositorio; no impide que alguien desarme Kino y abra el sello por su cuenta, igual que no impide que el sitio al que llamas vea el valor en claro de su lado. No selles un valor que ya es público (una clave que ya está en el JavaScript del reproductor de ese sitio no gana nada sellada en el tuyo), y nunca selles las credenciales de la persona: esas van en un ajuste de tipo password.

Cómo se usa. kino.secret(name) devuelve un marcador, no el valor; Kino cambia el marcador por el valor real solo dentro de kino.fetch, hacia los hosts de tu manifiesto por https, y tapa el valor en todo lo que vuelve a tu código. Las reglas completas (dónde se cambia el marcador, kino.crypto, el tapado) están en La API kino; cómo probarlo con el kit de Node, en Probar en local.

Ajustes

settings es una lista de máximo 12 entradas que guardan un valor (más, desde apiVersion 6, máximo 16 que solo muestran o hacen algo: mira Formulario de ajustes). Cada una se vuelve un campo en la pantalla "Configurar" del plugin (Ajustes ▸ Plugins, y desde Kino 0.9.50 también la propia pestaña de tu plugin en Ajustes), y tu código lee su valor con kino.config.get(key):

"settings": [
  { "key": "server", "label": "Servidor", "type": "url", "required": true, "hint": "http://192.168.1.10:8096" },
  { "key": "user", "label": "Usuario", "type": "text", "required": true },
  { "key": "password", "label": "Contraseña", "type": "password", "required": true },
  { "key": "quality", "label": "Calidad", "type": "select", "default": "hd",
    "options": [{ "value": "hd", "label": "Alta" }, { "value": "sd", "label": "Normal" }] },
  { "key": "subs", "label": "Subtítulos", "type": "toggle", "default": true }
]
type valor puede ser required puede tener default valor más largo
text texto sí sí 500 caracteres
url texto sí no (usa hint para un ejemplo) 2.048 caracteres
password texto sí sí 500 caracteres
toggle true / false no (siempre tiene valor) sí —
select uno de los valores de options no (siempre tiene valor) sí —
list una lista de entradas, cada una un objeto con los fields de la lista sí no —
section ninguno (apiVersion 6) no (no guarda valor) no —
status ninguno (apiVersion 6) no (no guarda valor) no —
action ninguno (apiVersion 6) no (no guarda valor) no —
  • key cumple ^[a-z][a-zA-Z0-9_]{0,31}$ y no se repite; label tiene de 1 a 40 caracteres; hint (el ejemplo que sale debajo del campo), máximo 80.
  • select necesita options (de 1 a 20, cada una con un value y un label de máximo 40 caracteres); su default tiene que ser uno de los valores. El default de un toggle es true o false.
  • list (apiVersion 4) es una lista que la persona arma con un botón "Agregar": cada entrada es una línea de texto con un botón "Editar", y el diálogo para agregar o editar muestra los fields de la lista (de 1 a 4, cada uno con key, label, un type text o url, y opcionalmente hint y required; sin default). max (de 1 a 50, 20 por defecto) limita las entradas. kino.config.get(key) devuelve un arreglo de objetos { [field.key]: string }, sin espacios sobrantes y sin las entradas totalmente vacías; una lista vacía es undefined. Una lista required necesita al menos una entrada. Los campos url de las entradas se vuelven hosts a los que tu plugin puede llegar, igual que un ajuste url (así que hosts puede ser []).
    { "key": "sources", "label": "Direcciones", "type": "list", "max": 30,
      "fields": [ { "key": "url", "label": "Dirección", "type": "url", "required": true },
                  { "key": "category", "label": "Categoría", "type": "text" } ] }
    
  • Un ajuste url no tiene default: un servidor que escribe la persona se vuelve un host al que tu plugin puede llegar, así que solo la persona puede escogerlo. Un manifiesto con default en un ajuste url se rechaza; pon una dirección de ejemplo en hint.
  • Un ajuste required sin valor frena cualquier llamada a tu plugin antes de que corra: el plugin muestra "Falta configurar", no se le piden sus filas de Inicio, y todo lo que la persona abra de él dice "Configura <name> en Ajustes ▸ Plugins" con un botón a esa pantalla.
  • Las contraseñas se guardan cifradas en el dispositivo. Tu código puede leerlas (tiene que enviarlas), y por eso la pantalla de consentimiento dice "Este plugin usa tu usuario y contraseña". Kino nunca escribe un ajuste en su log; no lo hagas tú tampoco.
  • Cambiar cualquier ajuste cierra el sandbox de tu plugin y borra sus cookies y sus filas de Inicio guardadas, así que la siguiente llamada arranca una sesión nueva con los valores nuevos. kino.storage no se borra: si guardas un token ahí, ponle como clave el usuario y el servidor a los que pertenece (el recetario lo hace).
  • Desinstalar borra los ajustes, contraseñas incluidas.
  • Desde apiVersion 6 el formulario también puede mostrar estados, botones de acción y validar antes de guardar, y los ajustes viajan entre los aparatos de la persona: mira Formulario de ajustes.

Los servidores propios de la persona

Un ajuste url es la forma en que un plugin habla con un servidor que no está en internet: un servidor multimedia en la casa, por ejemplo. El servidor que escribe la persona se vuelve un host más al que tu plugin puede llegar, exactamente como se escribió: su esquema (aquí se permite http, porque los servidores caseros casi nunca tienen certificado), su host y su puerto. Nada más de esa máquina o de esa red se permite, sus redirecciones solo pueden ir al mismo servidor o a tus hosts declarados, y las URL de tus streams y de tus imágenes pueden apuntar a él. La pantalla de consentimiento avisa "Se conectará a los servidores que escribas en su configuración", y Ajustes lista a qué llega cada plugin ("Se conectará a: …").

Un plugin que solo llega a ese servidor (nunca llama a un sitio propio) declara "hosts": [] desde "apiVersion": 2, siempre que tenga al menos un ajuste url: la pantalla de consentimiento entonces no lista ningún host, solo la línea sobre los servidores que escribe la persona, y Ajustes dice "Se conectará solo a los servidores que escribas en su configuración" hasta que se escriba uno. Un hosts vacío sin ajuste url se rechaza (El campo "hosts" solo puede estar vacío si el plugin tiene un ajuste de tipo "url"), y con "apiVersion": 1 se rechaza como siempre. (Kino tampoco lista nunca un host bajo el dominio reservado .invalid, el comodín que usaban manifiestos viejos.)

Solo cuentan el esquema, el host y el puerto: cualquier ruta de ese servidor es alcanzable, y kino.config.get devuelve el valor tal como se escribió. Kino rechaza, con un mensaje debajo del campo, un valor que no sea una URL http/https, o cuyo host sea localhost, una dirección de loopback (127.0.0.1, ::1), una link-local (169.254.x.x, fe80::) o 0.0.0.0. Las direcciones de la red de la persona (192.168.x.x, 10.x.x.x, un nombre .local) sí se permiten: ese es el punto.

Declarar un host inseguro (apiVersion 2)

Una entrada de hosts también puede ser un objeto, para un sitio tuyo que no tiene certificado:

"hosts": ["archive.org", { "host": "cdn.example.org", "insecureHttp": true }]

Esto necesita "apiVersion": 2. insecureHttp: true es lo único que puede llevar además de host, y marca los únicos hosts declarados (no el servidor propio de la persona, de arriba) que se permiten por http plano: kino.fetch, la url de un Stream, sus subtitles, sus audioTracks y la licenseUrl de un bloque drm aceptan http://cdn.example.org/… cuando se declara así, y cada salto de redirección se juzga con la misma regla. Todos los demás hosts declarados siguen siendo solo https, https sigue funcionando en el inseguro, y el host se compara exacto: sub.cdn.example.org no queda cubierto. Siguen aplicando las mismas reglas que para un texto simple (nombre DNS público, sin *, sin IP, nada privado ni de LAN; un nombre que resuelve dentro de la red de la persona igual se rechaza) más una: sin comodín *. — un host inseguro se nombra exacto. La pantalla de consentimiento lo muestra en rojo, "Conexión sin cifrar con cdn.example.org", y una actualización que marque un host así por primera vez espera aprobación, igual que un host nuevo. Mira Un sitio tuyo sin certificado.

Descargas (apiVersion 2)

Declara "download" en capabilities (con "apiVersion": 2) y Kino ofrece tus títulos para verlos sin conexión: "Descargar" en la página de información y "Guardar en el dispositivo" en la biblioteca, en celulares (Kino nunca descarga en un televisor). No hay nada más que exportar. Cuando la persona guarda un título, Kino llama a tu resolve(ref) en el momento en que la descarga de verdad arranca, igual que al reproducir, y guarda el Stream como un solo archivo, con tus headers en cada petición, por el mismo filtro de hosts que el reproductor usa con ese stream: https en tus hosts o el servidor propio de la persona -- o cualquier host público cuando tu manifiesto tiene streamHosts: "any" o la persona le dio a tu plugin el permiso amplio de video --, cada salto de redirección revisado, nunca la red de la casa. Una descarga nunca pregunta por un host. Un servidor que el filtro rechaza termina la descarga para siempre (sin "Reintentar": no es un problema de red), con una frase que nombra el servidor; cuando es uno por el que reproducir habría preguntado, la frase dice que reproduzcas el título una vez para aprobarlo, y después una descarga nueva funciona. Tus subtitles se guardan al lado. Los audioTracks no se guardan: la copia sin conexión solo tiene el audio que va dentro del archivo de video, así que una fuente que dobla con pistas aparte se oye con su audio principal cuando está sin conexión.

Qué se descarga y qué no:

  • Un archivo progresivo (mp4, mkv, webm, ts, …) se descarga. El archivo guardado toma su extensión de tu mime cuando lo das, si no de la URL, y si no mp4; el reproductor igual mira los bytes.
  • Un stream HLS bajo demanda (.m3u8, un mime mpegurl, o una respuesta que resulta ser una playlist) también se descarga, guardado como un solo archivo: los segmentos MPEG-TS quedan en un .ts y los fMP4 (EXT-X-MAP) en un .mp4. De una playlist maestra Kino toma la variante más alta hasta 1080p cuyo audio va dentro del video; se manejan las llaves AES-128 y los rangos de bytes, y tus headers van en las playlists, la llave y cada segmento. En un EXT-X-DISCONTINUITY los segmentos se guardan tal cual (los tiempos arrancan de nuevo ahí y el reproductor los sigue; adelantar justo en el empalme puede caer un poco corrido), salvo que ahí cambie el formato del video o del audio (por ejemplo H.264 → HEVC, o una pista que aparece o desaparece): eso se rechaza como abajo. Las pistas de metadatos (ID3, SCTE-35, datos privados) no cuentan como cambio, y los mismos formatos que llegan con otros números de pista (PID) después del empalme se reescriben con los del primer segmento, así que el .ts guardado se reproduce hasta el final. Un reintento retoma en el primer segmento que falta cuando recibe el mismo contenido (la misma variante, los mismos primeros bytes), aunque venga de otro CDN; un contenido distinto arranca de cero.
  • Lo que no se puede guardar termina como "Este video no se puede descargar", un estado final sin "Reintentar" (se negaría igual) que la persona solo puede quitar, y el archivo parcial se borra: un manifiesto DASH o Smooth (.mpd, application/dash+xml, …), una playlist HLS en vivo (sin EXT-X-ENDLIST), SAMPLE-AES o cualquier llave DRM, una llave que no tiene 16 bytes o que no descifra, un segmento vacío (pedido 3 veces antes), una maestra en la que toda variante de video necesita una pista de audio aparte (Kino no guarda un video mudo), un stream con DRM y un canal en vivo (un canal en vivo ni siquiera muestra el botón de descarga, tampoco en un plugin que declara channels y download a la vez). Los subtítulos que vienen dentro de la playlist no se guardan (tus subtitles sí). No hay una llamada aparte de "resolve para descargar": si tu fuente ofrece DASH y también un archivo o HLS, prefiere esos, o acepta que esos títulos se reproducen pero no se descargan.
  • La cola descarga un título a la vez, así que un ref puede esperar un rato antes de que se llame a resolve: guarda en él algo estable y busca el enlace fresco dentro de resolve (como se recomienda en Contrato). Un reintento retoma el archivo parcial aunque tu URL haya cambiado. Un resolve de la cola que se pasa del tiempo hace fallar solo esa descarga: no cuenta para los tres tiempos agotados seguidos que apagan tu plugin ("No responde"); eso solo lo cuentan las llamadas hechas para la persona que está en pantalla.
  • Un plugin desactivado, esperando sus ajustes o desinstalado no descarga nada: sus títulos no muestran el botón de descarga, y un título que ya estaba en cola falla con "Este plugin ya no puede descargar videos". Los archivos ya descargados se siguen reproduciendo sin conexión y se pueden quitar en Descargas, pase lo que pase después con el plugin.

Declarar download muestra "Puede descargar videos para verlos sin conexión" en la hoja de consentimiento, y una actualización que lo declare por primera vez espera la aprobación de la persona (Publicar).