-
-## Por qué
-
-Quería poner delay en mi propio directo y me puse a buscar. La opción más cuidada que encontré fue [InstantDelay](https://instant-delay.com/), que es de pago. Prefería tener algo que pudiera construir desde cero, entender de punta a punta y adaptar a mi setup, así que escribí esto.
+
-Cuando ya lo tenía hecho, las piezas que de verdad quería eran: una activación en dos fases (para que el momento de salir al aire con delay sea imperceptible en el reproductor del destino), varios destinos simultáneos, un dock para OBS y un overlay de estadísticas que se mete como browser-source.
+### Un retardo (delay) de stream sin cortes para OBS.
-InstantClone es un proyecto independiente, sin afiliación ni respaldo de InstantDelay ni de sus desarrolladores.
+Una señal entra. Un delay con buffer que **armas**, **activas** y **cortas** al vuelo, repartido a todas las plataformas a la vez. Libre y de código abierto.
-
-
+
-
-
Binario
1.3 MB
-
RSS inactivo
~9 MB
-
Hilos
1 tokio + 1 bandeja
-
Deps en runtime
tokio, bytes, ureq
-
Tests
282 / 282
-
+
-
-
-
+
+
+
-## Cómo funciona
-
-
-
-
+## Inicio rápido
-
+El primer arranque abre un asistente que te guía por todo esto. Los pasos de abajo son lo mismo a mano.
-**Dos fases por diseño.** **Armas** un buffer (tamaño en segundos). InstantClone empieza a rellenarlo desde el feed de OBS sin afectar todavía a lo que sale al aire. Cuando está lleno pasa de BUFFERING a ARMED y pulsas **Activar** cuando quieras. La transición es instantánea en pantalla: el lector cambia de la cola en vivo a una posición N segundos atrás.
+**1 · Ejecútalo**
+
+```text
+Descarga instantclone.exe → doble clic.
+El panel abre en http://127.0.0.1:7799
+```
+
+Esa es toda la instalación. Un icono queda en la bandeja mientras corre; clic derecho para el panel, el dock, un **Corte** de un clic, o **Salir**. Cerrar la pestaña no mata el proxy, solo Salir lo hace.
+
+Al primer arranque, Windows SmartScreen puede decir "editor desconocido" porque la build aún no está firmada. Pulsa **Más información → Ejecutar de todas formas**, o compárala con el `SHA256SUMS.txt` de la release.
-**Cortar es el mismo truco al revés.** Pulsas **Cortar**, el lector busca el IDR más cercano a la cola en vivo, reescribe los timestamps para que sigan siendo monótonos desde donde el reproductor del destino piensa que está el "ahora", y reanuda. Sin reconexión, sin nuevo handshake.
+**2 · Apunta OBS aquí**
+
+Pulsa **Registrar con OBS** en el panel, reinicia OBS una vez y en **Ajustes → Emisión** elige:
+
+```text
+Servicio: InstantClone
+Clave: main (vale cualquier texto)
+```
+
+El modo multipista "Auto" funciona de fábrica. Tus claves reales van en la pestaña **Destinos**, no en OBS.
+
+¿Prefieres manual? Servicio Personalizado, Servidor rtmp://127.0.0.1:1935/live, Clave main.
-```mermaid
-flowchart LR
- obs([OBS]) --> ic
- subgraph ic[InstantClone]
- direction TB
- ring[/ring buffer en disco índice de IDR en memoria, búsqueda O log n/]
- cut[lector con cortes alineado a IDR, timestamps monótonos]
- ring --> cut
- end
- ic --> tw([Twitch])
- ic --> yt([YouTube])
- ic --> kk([Kick])
- ic --> rs([Restream])
- ic --> any([RTMP personalizado])
-```
+**3 · Arma, activa, corta**
-> [!NOTE]
-> El buffer vive en disco por defecto (`./instantclone.buf`, 500 MB ≈ 11 minutos a 6 Mbps, ≈ 6 min 50 s a 10 Mbps), fuera de RAM porque puede llegar a varios cientos de MB. Lo único en RAM es el índice de IDR, alrededor de 1 MB para 10 minutos a 60 fps. El archivo se borra al cerrar la app de forma limpia, así que no se acumula entre sesiones. La UI se niega a armar un delay mayor de lo que el buffer puede aguantar al bitrate actual, con un "necesita ≥ N MB" explícito - sin stalls silenciosos.
+
+
+
+
+
+
1
Escribe un retardo (p. ej. 15s) y pulsa Armar. El buffer se prellena desde la señal en directo sin tocar lo que sale.
+
2
Cuando ponga ARMED, pulsa Activar. El cambio a diferido es instantáneo en pantalla, sin reconexión ni corte.
+
3
Corta para volver a directo cuando quieras, o ⏱ Cortar cuando esto salga para autocortar justo cuando tu reacción llega a los espectadores. Sin contar el retardo de cabeza.
+
+
+> [!IMPORTANT]
+> El Firewall de Windows preguntará al primer arranque porque el proxy escucha en :1935 (RTMP) y :7799 (web). Permítelo solo en **redes privadas**.
+
+> [!WARNING]
+> Solo Windows 10/11. macOS y Linux no están soportados, probados ni empaquetados.
-## Instalación
-
-```text
-1. Descarga instantclone.exe
-2. Doble clic
-3. El panel se abre en http://127.0.0.1:7799
-```
+## Por qué
-Ya está. Mientras corre se queda un icono en la bandeja del sistema. Clic derecho para abrir el panel, el dock de OBS, un **Cortar delay** al vuelo, o **Quit**. Cerrar la pestaña del navegador no mata el proxy; solo Quit lo hace.
+Quería un buffer de retardo para mi propio stream y me puse a buscar. La opción pulida que encontré fue [InstantDelay](https://instant-delay.com/), que es de pago. Prefería algo que pudiera reconstruir desde cero, entender de punta a punta y adaptar a mi setup, así que lo escribí yo.
-> [!IMPORTANT]
-> El Firewall de Windows preguntará en el primer arranque porque el proxy escucha en :1935 (RTMP) y :7799 (web). Permítelo solo en **Redes privadas**.
+Una vez existía, acabaron dentro las partes que de verdad quería: un armado/activado real de dos fases (para que el momento de salir con retardo sea **sin cortes** en el reproductor del destino), varios destinos de salida a la vez (así hace también de herramienta de multistream / simulcast gratuita, una alternativa autoalojada a Restream), un dock de OBS y un overlay de estadísticas que puedes soltar como fuente de navegador.
-> [!WARNING]
-> Solo Windows 10/11. macOS y Linux no están soportados, no están probados y no están empaquetados.
+InstantClone es un proyecto independiente, no afiliado ni respaldado por InstantDelay ni sus desarrolladores.
-## Configuración de OBS
+## Funciones
-
-
-En OBS, ve a **Ajustes → Emisión** y cambia:
+
-```diff
-- Servicio: Twitch (o lo que tuvieras)
-- Servidor: auto
-- Stream Key:
-+ Servicio: InstantClone
-+ Servidor: auto
-+ Stream Key: live (no importa kek)
-```
-Nota: InstantClone tiene un setup interactivo la primera vez que lo uses!
+**🎯 Multistream a cada plataforma**
+Haz simulcast de una señal de OBS a Twitch, YouTube, Kick y RTMP personalizado a la vez, una alternativa gratuita y autoalojada a Restream. Activa cada uno por separado y mira el bitrate por destino en vivo. Un **sink de prueba local** emite a un receptor diminuto en tu PC para ensayar armar/activar/cortar sin clave y sin que nada salga de tu máquina.
-Pulsa **Iniciar transmisión**. La cápsula OBS de InstantClone se pone verde. Tus claves reales de Twitch/YouTube/Kick van en la pestaña **Destinos** de InstantClone, no en OBS. OBS solo habla con InstantClone.
-Seguramente desaparezca tu chat de twitch en OBS porque OBS detecta que no "vas a transmitir en Twitch", añade el panel manualmente.
-
+
-
-
1
Pon un delay (p. ej. 15s) → Armar.
-
2
Mira cómo se llena el buffer. Cuando indique ARMED, pulsa Activar.
-
3
Cortar delay cuando quieras para volver al directo - o pulsa ⏱ Cortar cuando esto salga justo al acabar tu reacción de fin de partida, e InstantClone corta solo cuando ese momento ha llegado a tus viewers. Sin contar el delay de cabeza.
-
+**⏱ Corte programado seguro**
+**Cortar cuando esto salga** marca el borde en directo y autocorta cuando ha llegado a los espectadores en todos los destinos. Ideal para reacciones de final de partida sin hacer cuentas de retardo.
-> [!TIP]
-> Haz fan-out de un único feed de OBS a varios destinos a la vez. Añade Twitch, YouTube y un endpoint RTMP personalizado, activa cada uno por separado y mira su bitrate en vivo por destino. También hay un destino **Local test sink**: InstantClone lanza su propio receptor diminuto en tu PC y emite hacia él, para ensayar armar/activar/cortar de punta a punta - sin stream key, sin que nada salga de tu máquina, y con un enlace **Watch output** que muestra exactamente lo que recibiría una plataforma.
+
+
+
+
-> [!TIP]
-> **Vertical gratis.** Activa el **Formato Dual** de Twitch (Enhanced Broadcasting) en OBS y pon el **Formato de stream** de cualquier destino que no sea Twitch en **Vertical**: InstantClone reutiliza el lienzo 9:16 que OBS ya genera para Twitch y lo envía a YouTube Shorts, Kick móvil o cualquier RTMP personalizado, sin codificación extra. El vertical solo fluye mientras el Formato Dual está activo; hasta entonces el destino muestra "Esperando Formato Dual" y nada más se ve afectado. (Twitch gestiona ambos lienzos por su cuenta, así que ahí la opción se oculta.)
+**📱 Vertical (9:16) gratis**
+Activa **Formato Dual** de Twitch (Enhanced Broadcasting) y pon el formato de cualquier destino no-Twitch en **Vertical**. InstantClone reutiliza el lienzo 9:16 que OBS ya crea para Twitch y lo envía a YouTube Shorts, Kick móvil o TikTok, sin codificación extra.
-> [!TIP]
-> **Audio VOD de Twitch + enrutado anti-copyright.** Manten la música en tu directo pero fuera de la grabación: un clic en **System → Behavior → Twitch VOD audio unlocker** deja un pequeño script de OBS que añade una segunda pista de audio (OBS bloquea su pista VOD al servicio "Twitch", así que en el servicio InstantClone no está disponible). Luego, por destino, elige una **Pista de audio** - Twitch se queda con **ambas**, envía la pista limpia **Pista 2** a YouTube para esquivar el copyright, o la pista en vivo **Pista 1** a Kick. Necesario en OBS 32.2+; en OBS antiguo aún sirve el checkbox de pista VOD integrado.
+
+
+
+**🎚 Audio de VOD + ruteo sin copyright**
+Mantén la música en directo pero fuera de la grabación. Un clic añade una segunda pista de audio (con un script de OBS); luego, por destino, eliges la **pista de audio**: Twitch se queda **Ambas**, envías la **Pista 2** limpia a YouTube para esquivar copyright, o la **Pista 1** a Kick.
-
+
+
-
+**📡 Passthrough de Enhanced Broadcasting**
+Cuando OBS pasa a multipista "Auto", InstantClone hace de proxy de la config de Twitch, rutea al endpoint IVS de la sesión y reenvía cada SPS/PPS por pista fielmente para que se encienda la escalera de transcodificado. Los destinos no-Twitch reciben una sola pista limpia y aplanada.
-
+
+
-## Dock y overlays
+**🔒 Salida RTMPS (Kick + cualquier `rtmps://`)**
+El socket de salida sube a TLS para URLs `rtmps://`, reutilizando el schannel de Windows ya enlazado, sin una segunda pila TLS. Kick es una plataforma de pega-tu-URL-de-servidor en el asistente, con la ruta `/app` añadida automáticamente.
-
+
+
-
+
-
+**🎛 Dock de OBS + overlays sin código**
+Un dock de control 280×340 vive dentro de OBS para no hacer alt-tab a media partida. La pestaña **Overlay** es un Studio: elige un overlay de estadísticas ya hecho, copia su URL, suéltalo en OBS como fuente de navegador, o rediséñalo en vivo.
-
+
+
+**⚡ Ajuste de retardo en vivo**
+Rearma o ajusta el retardo arriba/abajo sin desarmar primero, expuesto como un control **↻ Ajustar a Ns** de un solo valor. Consciente de la capacidad: rechaza un retardo que el buffer no aguanta y te dice exactamente cuántos MB necesita.
-### Dock de OBS
+
+
+
-Añade un browser-dock personalizado en OBS apuntando a:
+> [!TIP]
+> **Registro de OBS de un clic.** El asistente puede añadir una entrada "InstantClone" al desplegable de Servicio de OBS por ti (escribe `services.json` con un `.bak` antes, se refresca al cambiar el puerto y avisa "cierra OBS primero" cuando el archivo está bloqueado).
-
http://127.0.0.1:7799/dock
+
-Un panel de 280×340 con el indicador, los controles de armar/activar/desarmar/cortar y el estado en vivo. Vive dentro de OBS para no cambiar de pestaña en mitad de una partida.
+
-### Overlays como browser-source
+## Cómo funciona
-La pestaña **Overlay** es un Studio sin código. Elige un overlay ya hecho, copia su URL y métela en OBS - o abre cualquiera en el Studio para rediseñarlo (colores por estado, widgets, animaciones) y darle a **Save** o **Save as new**.
+
+
+
-
http://127.0.0.1:7799/overlay/whisper.html
+**Dos fases por diseño.** **Armas** un buffer (un tamaño objetivo en segundos). InstantClone lo prellena desde la señal de OBS sin tocar lo que sale. Cuando se llena pulsas **Activar**, y el cambio a diferido es instantáneo en pantalla: el reproductor solo salta del borde en directo a un punto N segundos atrás.
-¿Sin configurar? Los estilos rápidos de siempre siguen funcionando desde la URL: /overlay?style=corner&lang=es(`minimal · corner · strip · focus · broadcast · ticker`, idiomas `en · es · pt · fr · de`)
+
+
-O suelta cualquier `.html` en `./overlays/` y se sirve en `/overlay/tu-archivo.html`.
+**Cortar es el mismo truco al revés.** Pulsas **Cortar**, InstantClone se alinea con el fotograma clave más cercano al borde en directo, ajusta las marcas de tiempo para que sigan contando hacia delante sin saltos, y continúa. Sin reconexión, sin fotograma negro, sin corte visible.
+> [!NOTE]
+> El buffer del retardo vive en disco y se reinicia cada vez que cierras la app, así que nada se acumula entre sesiones. Pide más delay del que cabe y la app te dice exactamente cuánto necesita en vez de atascarse.
+
@@ -220,34 +223,34 @@ O suelta cualquier `.html` en `./overlays/` y se sirve en `/overlay/tu-archivo.h
-| | Endpoint | Cuerpo | Acción |
+| | Endpoint | Cuerpo | Qué hace |
|:---|---|---|---|
-| POST | `/arm` | `ms=15000` | Empieza a llenar un buffer de 15 s. No sale al aire todavía. |
-| POST | `/activate` | | Activa el delay armado. 409 si el buffer no está listo. |
-| POST | `/disarm` | | Cancela el armado. Descarta el buffer sin salir al aire. |
-| POST | `/stop` | | Corta el delay y vuelve al directo. |
-| POST | `/cut-after` | | Marca el borde en vivo; auto-corta cuando haya salido en todos los destinos (409 si no hay delay activo). |
-| POST | `/cut-after/cancel` | | Descarta un corte programado pendiente sin cortar. |
-| POST | `/delay` | `ms=NNN` | Atajo: arma y activa en cuanto esté listo. |
-| GET | `/state` | | JSON instantáneo del estado. |
-| GET | `/events` | | Flujo SSE del JSON de estado, solo push. |
+| POST | `/arm` | `ms=15000` | Empieza a llenar un buffer de 15 s. Aún no sale. |
+| POST | `/activate` | | Activa el retardo armado. 409 si no está listo. |
+| POST | `/disarm` | | Cancela el armado, descarta el buffer sin salir. |
+| POST | `/stop` | | Vuelve a directo (igual que el botón **Cortar**). |
+| POST | `/cut-after` | | Marca el borde en directo; autocorta cuando sale en todos. |
+| POST | `/cut-after/cancel` | | Descarta un corte programado pendiente. |
+| POST | `/delay` | `ms=NNN` | De un tiro: arma y autoactiva en cuanto esté listo. |
+| GET | `/state` | | Instantánea JSON puntual. |
+| GET | `/events` | | Flujo de estado JSON por server-sent events. Solo push. |
-**Receta Stream Deck**
+**Receta de Stream Deck**
La acción **Web Request** habla POST form-encoded por defecto.
```text
-URL: http://127.0.0.1:7799/arm
-Método: POST
-Cuerpo: ms=15000
+URL: http://127.0.0.1:7799/arm
+Método: POST
+Cuerpo: ms=15000
```
-Armado de un botón. Añade `/activate` y `/stop` a un segundo y tercer botón y tienes control completo del delay desde tu deck.
+Armado de un botón. Añade `/activate` y `/stop` a otros dos botones para control total desde tu deck.
@@ -277,9 +280,33 @@ Armado de un botón. Añade `/activate` y `/stop` a un segundo y tercer botón y
-## Compilar
+## Por dentro
+
+
+Interioridades de RTMP + Enhanced Broadcasting
+
+
+
+- **Handshake RTMP con paridad total con OBS.** `connect` lleva la misma bolsa de capacidades de códec que envía librtmp (`audioCodecs=3191`, `videoCodecs=252`, `videoFunction=1`), el `fourCcList` de Enhanced-RTMP (AVC / HEVC / AV1 / VP9 / Opus / AC-3 / FLAC), `Set Chunk Size` antes de connect, `FCUnpublish → deleteStream` al cerrar, y RTMP Acknowledgement (BYTES_READ_REPORT) al umbral ventana/10 declarado por el par, en entrada y salida.
+- **Passthrough de Enhanced Broadcasting a Twitch.** Cuando OBS pasa a multipista "Auto" hacemos de proxy de `GetClientConfiguration` de Twitch, ruteamos la salida al endpoint IVS asignado a la sesión y reenviamos cada SPS/PPS por pista fielmente para que se encienda la escalera de transcodificado sin importar el nivel de cuenta. Los destinos no-Twitch reciben la pista primaria horizontal por defecto; las etiquetas de escalera con `TrackId != 0` se descartan para evitar la tormenta de varios frames por PTS que hace caer el decodificador de YouTube. Los cortes de EB caen en el IDR de la pista primaria (no en el keyframe del peldaño que gane el `partition_point`) para que el decodificador del destino siempre tenga su ancla.
+- **Selección del lienzo vertical (9:16).** El lienzo vertical se identifica decodificando el SPS de cada pista por orientación (retrato, mayor área) en vez de confiar en el JSON privado de sesión de Twitch, y se autocorrige según se activa/desactiva Formato Dual.
+- **Audio de VOD de Twitch, desbloqueado en el servicio InstantClone.** OBS ata su pista de VOD al servicio llamado literalmente "Twitch" (`ServiceSupportsVodTrack == {"Twitch"}`), así que está bloqueada en el servicio InstantClone. Un pequeño script de OBS incluido (`optional-vod-unlocker.lua`, descargado desde el panel) engancha el mismo segundo codificador de audio que usaría la propia pista de VOD de OBS, sin la restricción. Su lector de formato coincide byte a byte con el `flv_packet_audio_ex` de OBS (`AudioPacketType` en el byte 0, `TrackId` en el byte 6). OBS 32.2+ necesita el script; OBS anterior puede usar la casilla de VOD Track integrada (escribimos `EnableCustomServerVodTrack` en el `user.ini` de OBS 32, con `global.ini` como respaldo).
+- **Ruteo de audio por destino.** Las pistas no seleccionadas se descartan y la elegida se aplana a una etiqueta de una sola pista estándar (AAC reescrito al `0xAF` legado), espejando el `flatten_multitrack_video` del lado de vídeo. Si la pista elegida no se está enviando, cae a la pista en directo en vez de quedarse en silencio.
+
+
+
+
+Buffer, compilación y cobertura de tests
+
+
+
+
+
RSS inactivo
~9 MB
Hilos
1 tokio + 1 bandeja
Deps runtime
tokio, bytes, ureq
Tests
282 / 282
+
+
+**Buffer.** En disco por defecto (`./instantclone.buf`, 500 MB ≈ 11 min a 6 Mbps, ≈ 6 min 50 s a 10 Mbps), fuera de la RAM porque puede llegar a cientos de MB. Lo único en RAM es el índice de IDR, ~1 MB para 10 minutos a 60 fps. El archivo se reinicia en cada apagado limpio, así que nada se acumula entre sesiones, y la interfaz se niega a armar un retardo mayor del que cabe, con un motivo explícito "necesita ≥ N MB".
-Rust 1.74+ estable.
+**Compilar.** Rust 1.74+ estable. Sin npm, sin submódulos, sin SDKs de plataforma.
```powershell
git clone
@@ -288,9 +315,13 @@ cargo build --release
.\target\release\instantclone.exe
```
-Sin npm, sin submódulos, sin SDK de plataforma. El HTML del panel se minifica y comprime con gzip en tiempo de compilación desde `build.rs` (usa `flate2`, solo build-dep) y se embebe en el binario; en runtime se sirve con `Content-Encoding: gzip`.
+El HTML del panel se minifica + gzipea en tiempo de compilación con `build.rs` (`flate2`, solo build) y se incrusta en el binario; en ejecución se sirve con `Content-Encoding: gzip`. El script opcional de VOD para OBS también va incrustado y se entrega al navegador como descarga "Guardar como", así que siempre coincide con el binario en ejecución y no necesita red.
+
+**E/S de disco síncrona en la ruta caliente de escritura al anillo, por elección.** La escritura con buffer aterriza en la caché de páginas del SO en microsegundos y el kernel vacía en segundo plano, así que la caché de páginas ya es el buffer asíncrono; el índice y los bytes avanzan bajo un solo lock para que un lector nunca vea una etiqueta cuyos bytes aún no están en disco.
-`cargo test --release` cubre la máquina de estados (`arm → preparing → ready → active → cut`), detección de IDR en AVC + Enhanced RTMP, codec AMF0 incluyendo Strict Array (la `fourCcList` de Enhanced-RTMP) + guardia de recursión, round-trip de settings, evicción del ring-buffer con protección de lecturas en vuelo, parsing HTTP, política CSRF, pre-flight de puertos, la negociación de contenido `accepts_gzip`, la caché de cabeceras de secuencia por-pista de Enhanced Broadcasting + selección de tags consciente del TrackId, el audio multi-pista de Enhanced-RTMP (pista de VOD de Twitch) más el enrutado de pista de audio por destino (aplanar cualquier pista a un feed single-track limpio), el filtro de IDR de pista primaria para que los cortes con EB no glitcheen las escaleras, el parseo de orientación del SPS para la selección de lienzo vertical (9:16), la resolución de `user.ini` / `global.ini` en OBS 32, el parcheado de `services.json` de OBS, el parser de releases de GitHub + comparador SemVer-ish para el chequeo de actualizaciones, la implementación propia de SHA-256 (vectores NIST), el lector/escritor del flujo de chunks RTMP (cabeceras fmt 0-3, timestamps extendidos, fragmentación entre chunks, control Set-Chunk-Size / Window-Ack en banda, y guardias ante entrada malformada), la máquina de estados del corte programado ("cortar cuando esto salga"), y la descarga + verificación de checksum + intercambio del exe en disco de la auto-actualización. 282 tests, todos en verde.
+**Tests.** `cargo test --release` cubre la máquina de estados (`arm → preparing → ready → active → cut`), detección de IDR de AVC + Enhanced-RTMP, AMF0 (incluido Strict Array + guardia de recursión), round-trip de settings, expulsión del buffer en anillo con protección de lecturas en vuelo, parseo HTTP, política CSRF, pre-flight de puerto, negociación de contenido, caché de cabeceras de secuencia por pista de Enhanced Broadcasting + selección de etiquetas por TrackId, audio multipista + ruteo por destino, parseo de orientación SPS para la selección vertical, el parcheador de `services.json`, el parser del check de actualizaciones, el SHA-256 hecho a mano (vectores NIST), el lector/escritor de chunk-stream RTMP, la máquina del corte programado, y la descarga de autoactualización + verificación de checksum + intercambio del exe. **282 tests, todos en verde.**
+
+
@@ -298,35 +329,79 @@ Sin npm, sin submódulos, sin SDK de plataforma. El HTML del panel se minifica y
## Estado
-**Listo para uso diario en Windows.** Lo uso en mis propios directos, y cada vez más streamers lo usan a diario también. CI corre fmt + clippy (con `-D warnings`) + 282 tests en cada push, y un tag dispara la build + publicación automática de la release con su `SHA256SUMS.txt` al lado (todavía no hay certificado de firma de código, así que el sistema operativo puede avisar en el primer lanzamiento).
-
-**Lo que está sólido**
-
-- La máquina de estados `arm → activate → cut` en dos fases, con cortes alineados a IDR y reescritura monótona de timestamps. La pieza por la que empecé este proyecto.
-- **Ajuste de delay en vivo**: re-armar o cambiar el delay arriba/abajo sin desarmar primero. El backend ya lo soportaba; el panel ahora lo expone como un valor escrito + CTA "↻ Adjust ↑/↓ to Ns".
-- **Handshake RTMP a la altura de OBS.** `connect` lleva el mismo paquete de capacidades de códec que envía librtmp (`audioCodecs=3191`, `videoCodecs=252`, `videoFunction=1`), la `fourCcList` de Enhanced-RTMP (AVC / HEVC / AV1 / VP9 / Opus / AC-3 / FLAC), `Set Chunk Size` antes del connect, `FCUnpublish → deleteStream` al cerrar, y Acknowledgement RTMP (BYTES_READ_REPORT) cruzando el umbral window/10 declarado por el peer en ingest y egress.
-- **Passthrough de Enhanced Broadcasting a Twitch.** Cuando OBS activa multi-track "Auto" proxyamos la `GetClientConfiguration` de Twitch, enrutamos el egress al endpoint IVS asignado para la sesión, y reenviamos cada SPS/PPS por pista bit a bit para que la escalera transcodificada se ilumine sin depender del tier de la cuenta. Los destinos no-Twitch reciben la pista primaria horizontal por defecto - los tags de escalera multi-track con `TrackId != 0` se descartan para evitar la avalancha de múltiples frames por PTS que rompía el decoder de YouTube. Los cortes con EB aterrizan en el IDR de la pista primaria (no en el de la escalera que toque ganar el partition_point), así el decoder del destino siempre tiene su ancla.
-- **Salida vertical (9:16) para destinos que no son Twitch.** Pon el **Formato de stream** de un destino YouTube / Kick / personalizado en **Vertical** y reenviará el lienzo vertical del Formato Dual de Twitch en lugar del horizontal, aplanado a un feed single-track que esas plataformas aceptan de forma nativa (YouTube Shorts, Kick móvil, TikTok). El lienzo vertical se identifica decodificando la orientación del SPS de cada pista (vertical, mayor área) en vez de depender del JSON privado de sesión de Twitch, y se auto-corrige según el Formato Dual se activa/desactiva. Sólo fluye mientras el Formato Dual / Enhanced Broadcasting está activo en OBS; si no, la tarjeta del destino muestra "Esperando Formato Dual" y nada más se ve afectado. Se oculta para Twitch, que lleva ambos lienzos de forma nativa.
-- **Pista VOD de Twitch, desbloqueada en el servicio InstantClone.** OBS fija su pista VOD al servicio llamado literalmente "Twitch" (la puerta es `ServiceSupportsVodTrack == {"Twitch"}`), así que queda bloqueada mientras usas el servicio InstantClone. Un pequeño script de OBS incluido (`optional-vod-unlocker.lua`, descarga de un clic desde el dashboard directa a la carpeta de scripts de OBS) engancha el mismo segundo encoder de audio que usaría la pista VOD nativa de OBS - sin la puerta - de modo que OBS envía tanto la pista en vivo (TrackId 0 en cable) como la pista VOD (TrackId 1). InstantClone las reenvía a Twitch bit a bit; su lector de formato coincide byte a byte con el `flv_packet_audio_ex` de OBS (`AudioPacketType` en el byte 0, `TrackId` en el byte 6). **OBS 32.2+ necesita el script** (esa versión bloqueó la pista VOD nativa a servicios Custom); en OBS antiguo sigue funcionando el checkbox clásico - escribimos `EnableCustomServerVodTrack` en el `user.ini` de OBS 32 (con fallback a `global.ini`) para desbloquearlo, opcionalmente combinado con EB vía el botón de un clic **"Set up VOD + EB"** o un **acceso directo de escritorio**. Live + VOD funcionan junto con los cortes de delay.
-- **Enrutado de pista de audio por destino.** Cuando OBS envía una segunda pista de audio (por el script desbloqueador o Enhanced Broadcasting), cada destino elige qué recibe: Twitch se queda con **ambas** (audio VOD), mientras envías sólo la pista limpia **Pista 2** a YouTube para esquivar el copyright, o sólo la pista en vivo **Pista 1** a Kick. Las pistas no elegidas se descartan y la elegida se aplana a un tag single-track estándar (AAC reescrito a legacy `0xAF`, la forma que todo ingest acepta), replicando el `flatten_multitrack_video` del lado de vídeo. Si la pista elegida no se está enviando, cae de vuelta a la pista en vivo en lugar de quedarse en silencio. Sólo Twitch consume una segunda pista de audio, así que las demás plataformas reciben siempre exactamente una.
-- **Registro de servicio en OBS con un click.** El primer paso del wizard añade una entrada "InstantClone" al desplegable de servicios de OBS (escribe `services.json` con `.bak` previo; se refresca si cambia el puerto; surfacea "cierra OBS primero" cuando el fichero está bloqueado).
-- Egress multi-destino con reconexión + bitrate por destino.
-- **Egress RTMPS (Kick y cualquier destino `rtmps://`).** El socket de egress sube a TLS de forma transparente para URLs `rtmps://`, reutilizando el schannel de Windows ya enlazado para el cliente HTTPS, así que no se enlaza una segunda pila TLS (rustls + ring habrían añadido ~1,4 MB). Kick es una plataforma de pega-tu-URL-de-servidor en el wizard y el formulario de destino, con la ruta `/app` añadida automáticamente cuando falta.
-- **UI consciente de la capacidad del buffer**: pista en vivo "X MB → máx Ys de delay a N Mbps", se niega a armar un delay mayor de lo que cabe con una razón explícita "necesita ≥ N MB".
-- **Avisos por plataforma**: riesgo de fallo de decodificación en móvil por encima de 8 Mbps en Twitch Source-Only, reglas de ingesta AWS IVS de Kick (CBR + keyframe de 2 s; los B-frames en realidad van bien en su RTMP de baja latencia), enlaces directos al dashboard de claves de cada plataforma - todo expuesto en el wizard y el formulario de destino para no aprender cada gotcha en directo.
-- Icono de bandeja con estado en vivo + corte de un click, pre-flight de puertos que identifica el proceso conflictivo por PID + exe.
-- Cobertura de tests sobre la máquina de estados, detección IDR (AVC + Enhanced RTMP + flatten multi-track), codec AMF0 incluyendo Strict Array, evicción del ring con protección de lecturas en vuelo, y la promoción del wrap de timestamps que evita el bug de los 49,7 días.
-
-**Lo que sigue siendo áspero, siendo honesto**
-
-- **Solo Windows.** macOS / Linux no están probados ni empaquetados. Varios módulos (bandeja, pre-flight de puertos, sampler de RSS) tienen rutas específicas de Windows que necesitarían implementación paralela.
-- **Directos de varias horas, ya con rodaje real.** Directos de varias horas a diario entre un grupo creciente de streamers, con el supervisor + keepalive + acks diseñados para sesiones indefinidas. Aún conviene alguna sesión larga de estrés según crece la base de usuarios.
-- **I/O de disco sin async en el hot-path** para el append del ring. La page cache lo absorbe a tasas típicas de stream, pero un stall por flush podría congelar otras tareas. `spawn_blocking` está en la lista para v0.2.
-- **Un puñado de `unwrap()` sobre locks.** Está bien porque `panic = "abort"` impide que una condición de poison se propague, pero sigue en la lista de limpieza.
-- **Servidor HTTP escrito a mano.** Binario más pequeño que con `hyper`, pero ahora me toca cargar con toda la superficie de CVEs HTTP. Vale la pena reevaluarlo si la superficie crece.
+**Listo para uso diario en Windows.** Lo uso en mis propios streams, y un grupo creciente de streamers lo corre a diario también. CI ejecuta fmt + clippy (`-D warnings`) + 282 tests en cada push, y un commit etiquetado compila y publica una release con un `SHA256SUMS.txt` al lado (todavía sin certificado de firma de código, así que el SO puede avisar al primer arranque).
+
+**Lo áspero, con honestidad**
+
+- **Solo Windows por ahora.** Varios módulos (bandeja, pre-flight de puerto, muestreo de RSS) usan rutas específicas de Windows. Hay una versión para Linux (incluida una variante headless/terminal para servidor) en la hoja de ruta; macOS aún no está planeado.
+- **La escalera de transcodificado no está garantizada sin EB.** Solo los Twitch Partner tienen slot de transcodificado siempre; el resto se queda en Source-Only, donde algunos decodificadores por hardware fallan por encima de ~8 Mbps (es el comportamiento de asignación de Twitch, no del proxy). Para una escalera garantizada usa Enhanced Broadcasting; si no, mantén el bitrate cerca de ~6000 Kbps.
+- **Un puñado de `unwrap()` sobre guards de lock.** Seguro porque `panic = "abort"` hace que una condición de poison no pueda propagarse, pero sigue en la lista de limpieza.
+- **Servidor HTTP hecho a mano.** Binario más pequeño que con `hyper`, pero soy dueño de toda la superficie HTTP. A revisar si crece.
> [!WARNING]
-> Esto es un proyecto personal que uso yo mismo, no un producto de empresa. Si emites esports pagados, valídalo contra tu propio pipeline antes de confiar en él.
+> Esto es un proyecto hobby que uso yo mismo, no un producto de proveedor. Si emites esports de pago, valídalo contra tu propio pipeline antes de confiar en él una noche de torneo.
+
+
+
+
+
+## Preguntas frecuentes
+
+
+¿Emitir a varias plataformas baja mi calidad?
+
+
+
+No. InstantClone reenvía la señal ya codificada de OBS a cada destino sin recodificar, así que todas las plataformas reciben la misma calidad que produjo OBS. Lo que sí consume es **ancho de banda de subida**: cada destino recibe el bitrate completo, así que tres destinos necesitan unas tres veces la subida. El panel muestra un aviso de "cuello de botella de subida" y sugiere mantener el bitrate de OBS por debajo del ~80% de tu subida.
+
+
+
+
+¿El delay se aplica a todas las plataformas a la vez?
+
+
+
+Sí. El buffer está antes del reparto, así que cada destino reproduce desde la misma posición retardada. Armar, activar y cortar les afectan a todos juntos.
+
+
+
+
+¿Cuánto delay puedo poner?
+
+
+
+El que aguante el buffer. El archivo de 500 MB por defecto son unos 11 minutos a 6 Mbps (menos a bitrates más altos). Puedes hacerlo más grande; el panel rechaza un delay que el buffer no aguanta y te dice cuántos MB necesita, así que nunca se atasca en silencio.
+
+
+
+
+¿Recodifica o toca mi vídeo?
+
+
+
+No. El vídeo pasa tal cual, bit a bit. Lo único que se reescribe es el contenedor de audio cuando ruteas una pista concreta a un destino (el AAC se reescribe a la etiqueta legada que acepta cualquier ingest); las muestras de audio no se tocan.
+
+
+
+
+¿Dónde van mis claves de stream reales?
+
+
+
+En la pestaña **Destinos** de InstantClone, nunca en OBS. OBS solo apunta a InstantClone con un servicio y una clave desechable; InstantClone guarda la clave real de cada plataforma y reparte tu señal a todas. Así tus claves están en un solo sitio y activas o desactivas destinos sin tocar OBS.
+
+**Tus claves nunca salen de tu PC.** InstantClone no tiene servidores propios ni telemetría: las claves se guardan localmente en tu máquina y solo se envían a los servidores de ingest de las plataformas a las que elijas emitir. Ejecutar la app no nos envía nada; nunca vemos tus claves, tu stream ni ninguna otra cosa. Es de código abierto, así que puedes verificarlo tú mismo.
+
+
+
+
+¿Funciona en macOS o Linux?
+
+
+
+Windows 10/11 por ahora. Hay una **versión para Linux en la hoja de ruta**, incluida una variante headless/terminal para servidores sin escritorio. macOS aún no está planeado. Algunas partes (bandeja, pre-flight de puerto, muestreo de memoria) usan código específico de Windows que primero hay que portar.
+
+
@@ -341,7 +416,7 @@ Sin npm, sin submódulos, sin SDK de plataforma. El HTML del panel se minifica y
-Sígueme en directo mientras construyo esto, o cuéntame qué tal te va con la app. Bugs y propuestas → [Issues](https://github.com/Soulhackzlol/InstantClone/issues) y [Discussions](https://github.com/Soulhackzlol/InstantClone/discussions).
+Pásate mientras lo construyo en directo, o charlemos del proyecto. Reportes de bugs e ideas → [Issues](https://github.com/Soulhackzlol/InstantClone/issues) y [Discussions](https://github.com/Soulhackzlol/InstantClone/discussions).
@@ -349,6 +424,6 @@ Sígueme en directo mientras construyo esto, o cuéntame qué tal te va con la a
## Licencia
-[GPL-3.0](LICENSE). Puedes usarlo, modificarlo y correrlo en el directo que quieras. Si distribuyes una versión modificada (incluyendo un fork "Pro", un instalador empaquetado con extras o un front-end de pago), tu código fuente tiene que publicarse bajo la misma licencia, en abierto. Construí esto como alternativa gratis porque la quería para mí; la GPL es lo que hace que los forks sigan siendo libres también.
+[GPL-3.0](LICENSE). Úsalo, modifícalo, córrelo en el stream que quieras. Si distribuyes una versión modificada (incluido un fork "Pro", un instalador con extras, o un front-end de pago), tu código fuente tiene que salir bajo la misma licencia, públicamente. Construí esto como alternativa libre porque quería una para mí; GPL es lo que mantiene libres los forks también.
Hecho por [s1moscs](https://s1moscs.dev).
diff --git a/README.md b/README.md
index 69002f8..546cc5e 100644
--- a/README.md
+++ b/README.md
@@ -5,13 +5,16 @@
-
+
-
+
+
+
+
+
-
@@ -25,191 +28,191 @@
-
-
-
-
-
-
-
-## Why
-
-I wanted a delay buffer for my own stream and went looking. The polished option I found was [InstantDelay](https://instant-delay.com/), which is paid. I'd rather have something I could rebuild from scratch, understand end-to-end, and adapt to my setup, so I wrote this instead.
+
-Once it existed, the parts I'd actually wanted ended up in: a real two-phase arm/activate (so the moment you go live with delay is **zero glitch** on the destination player), multiple egress destinations at once, an OBS browser-dock, and a stats overlay you can drop in as a browser-source.
+### A zero-glitch stream delay for OBS.
-InstantClone is an independent project, not affiliated with or endorsed by InstantDelay or its developers.
+One feed in. A buffered delay you **arm**, **activate**, and **cut** on the fly, fanned out to every platform at once. Free and open source.
-
-
+
-
-
Binary
1.3 MB
-
Idle RSS
~9 MB
-
Threads
1 tokio + 1 tray
-
Runtime deps
tokio, bytes, ureq
-
Tests
282 / 282
-
+
-
-
-
+
+
+
-## How it works
+## Quickstart
-
-
-
-
-
+First launch opens a setup wizard that walks you through all of this. The steps below are the same thing by hand.
-**Two-phase by design.** You **arm** a buffer (target size in seconds). InstantClone pre-fills it from the live OBS feed without affecting what's going out. Once it's full, the state moves from BUFFERING to ARMED and you hit **Activate** when you're ready. The transition is instant on screen: the reader just swaps from the live tail to a position N seconds back in the ring.
+**1 · Run it**
+
+```text
+Download instantclone.exe → double-click.
+Dashboard opens at http://127.0.0.1:7799
+```
+
+That's the whole install. A tray icon sits in the systray while it runs; right-click for the dashboard, dock, one-click **Cut**, or **Quit**. Closing the tab doesn't kill the proxy, only Quit does.
+
+First launch, Windows SmartScreen may say "unknown publisher" because the build isn't code-signed yet. Click **More info → Run anyway**, or check it against the `SHA256SUMS.txt` on the release.
-**Cutting is the same trick in reverse.** You hit **Cut**, the reader seeks to the nearest IDR near the live tail, rewrites timestamps so they continue monotonically from where the destination player thinks "now" is, and resumes. No re-handshake, no reconnect, no glitch.
+**2 · Point OBS at it**
+
+Click **Register with OBS** in the dashboard, restart OBS once, then in **Settings → Stream** pick:
+
+```text
+Service: InstantClone
+Key: main (any string works)
+```
+
+Multi-track "Auto" works out of the box. Your real platform keys go into the **Destinations** tab, not OBS.
+
+Prefer manual? Service Custom, Server rtmp://127.0.0.1:1935/live, Key main.
-```mermaid
-flowchart LR
- obs([OBS]) --> ic
- subgraph ic[InstantClone]
- direction TB
- ring[/disk-backed ring buffer in-memory IDR index, O log n seek/]
- cut[cut-aware reader IDR-aligned, monotonic timestamps]
- ring --> cut
- end
- ic --> tw([Twitch])
- ic --> yt([YouTube])
- ic --> kk([Kick])
- ic --> rs([Restream])
- ic --> any([custom RTMP])
-```
+**3 · Arm, activate, cut**
-> [!NOTE]
-> The buffer is on disk by default (`./instantclone.buf`, 500 MB ≈ 11 minutes at 6 Mbps, ≈ 6 min 50 s at 10 Mbps), kept off RAM because it can be hundreds of MB. The only thing in RAM is the IDR index, about 1 MB for 10 minutes at 60 fps. The file is reset on every clean shutdown, so it doesn't accumulate between sessions. The UI refuses to arm a delay larger than the buffer can hold at the current bitrate, with an explicit "needs ≥ N MB" reason - no silent stalls.
+
+
+
+
+
+
1
Type a delay (e.g. 15s) and hit Arm. The buffer pre-fills from the live feed without touching what's going out.
+
2
When it reads ARMED, hit Activate. The switch to delayed is instant on screen, no reconnect, no glitch.
+
3
Cut to snap back to live any time, or ⏱ Cut after this airs to auto-cut the moment your reaction reaches viewers. No counting the delay in your head.
+
+
+> [!IMPORTANT]
+> Windows Firewall prompts on first launch because the proxy listens on :1935 (RTMP) and :7799 (web). Allow it on **Private networks** only.
+
+> [!WARNING]
+> Windows 10/11 only. macOS and Linux are not supported, tested, or packaged.
-## Install
-
-```text
-1. Download instantclone.exe
-2. Double-click it
-3. Dashboard opens at http://127.0.0.1:7799
-```
+## Why
-That's the whole install. A tray icon sits in the systray while it's running. Right-click it for the dashboard, the OBS dock, a one-click **Cut delay**, or **Quit**. Closing the browser tab doesn't kill the proxy; only Quit does.
+I wanted a delay buffer for my own stream and went looking. The polished option I found was [InstantDelay](https://instant-delay.com/), which is paid. I'd rather have something I could rebuild from scratch, understand end-to-end, and adapt to my setup, so I wrote this instead.
-> [!IMPORTANT]
-> Windows Firewall will prompt on first launch because the proxy listens on :1935 (RTMP) and :7799 (web). Allow it on **Private networks** only.
+Once it existed, the parts I'd actually wanted ended up in it: a real two-phase arm/activate (so the moment you go live with delay is **zero glitch** on the destination player), multiple egress destinations at once (so it doubles as a free multistream / simulcast tool, a self-hosted alternative to Restream), an OBS browser-dock, and a stats overlay you can drop in as a browser-source.
-> [!WARNING]
-> Windows 10/11 only. macOS and Linux are not supported, not tested, and not packaged.
+InstantClone is an independent project, not affiliated with or endorsed by InstantDelay or its developers.
-## OBS setup
+## Features
-
+
-In OBS, go to **Settings → Stream** and change:
+**🎯 Multistream to every platform**
+Simulcast one OBS feed to Twitch, YouTube, Kick, and custom RTMP at once, a free self-hosted alternative to Restream. Toggle each independently, watch per-destination bitrate live. A **Local test sink** streams to a tiny receiver on your PC so you can rehearse arm/activate/cut with no key and nothing leaving your machine.
-```diff
-- Service: Twitch (or whatever you had)
-- Server: auto
-- Stream Key:
-+ Service: InstantClone
-+ Server: auto
-+ Stream Key: live (doesn't matter heh)
-```
-Note: Instatclone has a built in first time wizard to guide you!
+
+
-Click **Start Streaming**. The OBS pill in InstantClone turns green. Your real Twitch/YouTube/Kick keys go into InstantClone's **Destinations** tab, not OBS. OBS only ever talks to InstantClone.
+**⏱ Scheduled safe cut**
+**Cut after this airs** marks the live edge and auto-cuts once it has reached viewers on every destination. Perfect for match-end reactions without doing delay math in your head.
-
-
-
-
1
Type a delay (e.g. 15s) → Arm.
-
2
Watch the buffer fill. When it says ARMED, hit Activate.
-
3
Cut delay at any time to snap back to live - or hit ⏱ Cut after this airs right when your match reaction ends, and InstantClone auto-cuts once that moment has reached your viewers. No counting the delay in your head.
-
+
+
+
-> [!TIP]
-> Fan out one OBS feed to several destinations at once. Add Twitch, YouTube, and a custom RTMP endpoint, toggle each on independently, watch their per-destination bitrate live. There is also a **Local test sink** destination: InstantClone spawns its own tiny receiver on your PC and streams to it, so you can rehearse arm/activate/cut end to end - stream key not needed, nothing leaves your machine, and a **Watch output** link shows exactly what a platform would receive.
+**📱 Vertical (9:16) for free**
+Turn on Twitch **Dual Format** (Enhanced Broadcasting) and set any non-Twitch destination's format to **Vertical**. InstantClone reuses the 9:16 canvas OBS already makes for Twitch and sends it to YouTube Shorts, Kick mobile, or TikTok, with no extra encoding.
-> [!TIP]
-> **Go vertical for free.** Turn on Twitch **Dual Format** (Enhanced Broadcasting) in OBS and set any non-Twitch destination's **Stream format** to **Vertical** - InstantClone reuses the 9:16 canvas OBS is already making for Twitch and sends it to YouTube Shorts, Kick mobile, or any custom RTMP target, with no extra encoding. Vertical only flows while Dual Format is on; until then the destination shows "Waiting for Dual Format" and nothing else is affected. (Twitch handles both canvases itself, so the option is hidden there.)
+
+
-> [!TIP]
-> **Twitch VOD audio + copyright-safe routing.** Keep music in your live stream but out of the recording: one click on **System → Behavior → Twitch VOD audio unlocker** drops a tiny OBS script that adds a second audio track (OBS otherwise locks its VOD Track to the "Twitch" service, so it's unavailable on the InstantClone service). Then, per destination, pick an **Audio track** - Twitch keeps **Both**, send the clean **Track 2** to YouTube to dodge copyright, or the live **Track 1** to Kick. Needed on OBS 32.2+; older OBS can still use the built-in VOD Track checkbox.
+**🎚 VOD audio + copyright-safe routing**
+Keep music live but out of the recording. One click adds a second audio track (via a bundled OBS script); then per destination pick an **Audio track**, Twitch keeps **Both**, send the clean **Track 2** to YouTube to dodge copyright, or **Track 1** to Kick.
-
+
+
-
+**📡 Enhanced Broadcasting passthrough**
+When OBS goes multi-track "Auto", InstantClone proxies Twitch's config, routes to the session IVS endpoint, and forwards every per-track SPS/PPS bit-faithfully so the transcode ladder lights up. Non-Twitch destinations get a clean flattened single track.
-
+
+
-## Dock and overlays
+**🔒 RTMPS egress (Kick + any `rtmps://`)**
+The egress socket upgrades to TLS for `rtmps://` URLs, reusing the Windows schannel already linked, so no second TLS stack. Kick is a paste-your-Server-URL platform in the wizard, with the `/app` path added automatically.
-
+
+
-
+
-
+**🎛 OBS dock + no-code overlays**
+A 280×340 control dock lives inside OBS so you're not alt-tabbing mid-match. The **Overlay** tab is a Studio: pick a ready-made stats overlay, copy its URL, drop it into OBS as a browser-source, or redesign it live.
-
+
-### OBS browser-dock
+**⚡ Live delay adjustment**
+Re-arm or nudge the delay up/down without disarming first, exposed as a single typed-value **↻ Adjust to Ns** control. Capacity-aware: it refuses a delay the buffer can't hold and tells you exactly how many MB it needs.
-Add a custom dock in OBS pointing at:
+
+
+
-
http://127.0.0.1:7799/dock
+> [!TIP]
+> **One-click OBS registration.** The setup wizard can add an "InstantClone" entry to OBS's Service dropdown for you (writes `services.json` with a `.bak` first, refreshes on port change, and warns "close OBS first" when the file is locked).
-A 280×340 panel with the readout, arm / activate / disarm / cut controls, and live status. Lives inside OBS so you're not alt-tabbing mid-match.
+
-### Browser-source overlays
+
-The **Overlay** tab is a no-code Studio. Pick a ready-made overlay, copy its URL, and drop it into OBS - or open any in the Studio to redesign it (per-state colours, widgets, animations) and **Save** or **Save as new**.
+## How it works
-
http://127.0.0.1:7799/overlay/whisper.html
+
+
+
-No setup? The older quick styles still work straight from a URL: /overlay?style=corner&lang=es(`minimal · corner · strip · focus · broadcast · ticker`, langs `en · es · pt · fr · de`)
+**Two-phase by design.** You **arm** a buffer (a target size in seconds). InstantClone pre-fills it from the live OBS feed without touching what's going out. Once it's full you hit **Activate**, and the switch to delayed is instant on screen: the player just jumps from the live edge to a point N seconds back.
-Or drop any `.html` into `./overlays/` and it's served at `/overlay/your-file.html`.
+
+
+
+**Cutting is the same trick in reverse.** You hit **Cut**, InstantClone lines up on the nearest keyframe near the live edge, fixes the timestamps so they keep counting forward smoothly, and resumes. No reconnect, no black frame, no glitch.
+> [!NOTE]
+> The delay buffer lives on disk and resets every time you close the app, so nothing piles up between sessions. Ask for more delay than it can hold and the app tells you exactly what it needs instead of stalling.
+
@@ -222,12 +225,12 @@ Or drop any `.html` into `./overlays/` and it's served at `/overlay/your-file.ht
| | Endpoint | Body | What it does |
|:---|---|---|---|
-| POST | `/arm` | `ms=15000` | Start filling a 15 s buffer. Does not go live yet. |
-| POST | `/activate` | | Activate the armed delay. 409 if the buffer isn't ready. |
-| POST | `/disarm` | | Cancel arming. Drop the buffer without going live. |
-| POST | `/stop` | | Cut the delay, return to live. |
-| POST | `/cut-after` | | Mark the live edge; auto-cut once it has aired on every destination (409 if no delay is active). |
-| POST | `/cut-after/cancel` | | Drop a pending scheduled cut without cutting. |
+| POST | `/arm` | `ms=15000` | Start filling a 15 s buffer. Not live yet. |
+| POST | `/activate` | | Activate the armed delay. 409 if not ready. |
+| POST | `/disarm` | | Cancel arming, drop the buffer without going live. |
+| POST | `/stop` | | Cut back to live (same as the **Cut** button). |
+| POST | `/cut-after` | | Mark the live edge; auto-cut once it airs everywhere. |
+| POST | `/cut-after/cancel` | | Drop a pending scheduled cut. |
| POST | `/delay` | `ms=NNN` | One-shot: arm, auto-activate as soon as ready. |
| GET | `/state` | | One-shot JSON snapshot. |
| GET | `/events` | | Server-sent stream of state JSON. Push-only. |
@@ -247,7 +250,7 @@ Method: POST
Body: ms=15000
```
-One-button arming. Add `/activate` and `/stop` to a second and third button and you have full delay control from your deck.
+One-button arming. Add `/activate` and `/stop` to two more buttons for full delay control from your deck.
@@ -277,9 +280,33 @@ One-button arming. Add `/activate` and `/stop` to a second and third button and
-## Build
+## Under the hood
+
+
+RTMP + Enhanced Broadcasting internals
+
+
+
+- **Full OBS-parity RTMP handshake.** `connect` carries the same codec-capability bag librtmp ships (`audioCodecs=3191`, `videoCodecs=252`, `videoFunction=1`), the Enhanced-RTMP `fourCcList` (AVC / HEVC / AV1 / VP9 / Opus / AC-3 / FLAC), `Set Chunk Size` before connect, `FCUnpublish → deleteStream` on shutdown, and RTMP Acknowledgement (BYTES_READ_REPORT) at the peer-declared window/10 threshold on both ingest and egress.
+- **Enhanced Broadcasting passthrough to Twitch.** When OBS hits multi-track "Auto" we proxy Twitch's `GetClientConfiguration`, route egress to the session-allocated IVS endpoint, and forward every per-track SPS/PPS bit-faithfully so the transcoded ladder lights up regardless of account tier. Non-Twitch destinations get the horizontal primary track by default; ladder tags with `TrackId != 0` are dropped to avoid the multi-frame-per-PTS storm that crashes YouTube's decoder. EB cuts land on the primary track's IDR (not whichever ladder rung's keyframe wins the `partition_point`) so the destination decoder always has its anchor.
+- **Vertical (9:16) canvas selection.** The vertical canvas is identified by decoding each track's SPS for orientation (portrait, largest area) rather than trusting Twitch's private session JSON, and it self-heals as Dual Format toggles on/off.
+- **Twitch VOD audio, unlocked on the InstantClone service.** OBS hardcodes its VOD Track to the service literally named "Twitch" (`ServiceSupportsVodTrack == {"Twitch"}`), so it's locked on the InstantClone service. A tiny bundled OBS script (`optional-vod-unlocker.lua`, downloaded from the dashboard) attaches the same second audio encoder OBS's own VOD Track would, without the gate. Its wire-format reader matches OBS's `flv_packet_audio_ex` byte-for-byte (`AudioPacketType` in byte 0, `TrackId` at byte 6). OBS 32.2+ needs the script; older OBS can still use the built-in VOD Track checkbox (we write `EnableCustomServerVodTrack` to OBS 32's `user.ini`, falling back to `global.ini`).
+- **Per-destination audio routing.** Non-selected tracks are dropped and the chosen one is flattened to a standard single-track tag (AAC rewritten to legacy `0xAF`), mirroring the video-side `flatten_multitrack_video`. If the chosen track isn't being sent, it falls back to the live track rather than going silent.
-Rust 1.74+ stable.
+
+
+
+Buffer, build, and test coverage
+
+
+
+
+
Idle RSS
~9 MB
Threads
1 tokio + 1 tray
Runtime deps
tokio, bytes, ureq
Tests
282 / 282
+
+
+**Buffer.** Disk-backed by default (`./instantclone.buf`, 500 MB ≈ 11 min at 6 Mbps, ≈ 6 min 50 s at 10 Mbps), kept off RAM because it can run to hundreds of MB. The only thing in RAM is the IDR index, ~1 MB for 10 minutes at 60 fps. The file resets on every clean shutdown, so nothing accumulates between sessions, and the UI refuses to arm a delay larger than the buffer can hold, with an explicit "needs ≥ N MB" reason.
+
+**Build.** Rust 1.74+ stable. No npm, no submodules, no platform SDKs.
```powershell
git clone
@@ -288,9 +315,13 @@ cargo build --release
.\target\release\instantclone.exe
```
-No npm. No submodules. No platform SDKs. The dashboard HTML is minified + gzipped at build time by `build.rs` (uses `flate2`, build-only) and embedded into the binary; at runtime it's served with `Content-Encoding: gzip`.
+The dashboard HTML is minified + gzipped at build time by `build.rs` (`flate2`, build-only) and embedded into the binary; at runtime it's served with `Content-Encoding: gzip`. The optional VOD-unlocker OBS script is embedded too and handed to the browser as a Save-As download, so it always matches the running binary and needs no network.
+
+**Sync disk I/O on the ring-append hot path, by choice.** The buffered write lands in the OS page cache in microseconds and the kernel flushes in the background, so the page cache is already the async buffer; the index and the bytes advance under one lock so a reader never sees a tag whose bytes aren't on disk yet.
-`cargo test --release` covers the state machine (`arm → preparing → ready → active → cut`), AVC + Enhanced RTMP IDR detection, AMF0 codec including Strict Array (Enhanced-RTMP `fourCcList`) + recursion guard, settings round-trip, ring-buffer eviction with in-flight-read protection, HTTP parsing, CSRF policy, port pre-flight, `accepts_gzip` content negotiation, Enhanced Broadcasting per-track seq-header cache + TrackId-aware tag selection, Enhanced-RTMP multi-track audio (Twitch's VOD audio track) plus per-destination audio-track routing (flatten any track to a clean single-track feed), primary-track IDR gate so EB cuts don't pixel-glitch ladder rungs, SPS orientation parsing for vertical-canvas (9:16) selection, OBS 32 `user.ini` / legacy `global.ini` path resolution, the OBS services.json patcher, the GitHub releases update-check parser + SemVer-ish comparator, the hand-rolled SHA-256 (NIST vectors), the RTMP chunk-stream reader/writer (fmt 0-3 headers, extended timestamps, cross-chunk fragmentation, in-band Set-Chunk-Size / Window-Ack control, and malformed-input guards), the scheduled safe-cut ("cut after this airs") state machine, and the self-update download + checksum-verify + on-disk exe swap. 282 tests, all green.
+**Tests.** `cargo test --release` covers the state machine (`arm → preparing → ready → active → cut`), AVC + Enhanced-RTMP IDR detection, AMF0 (including Strict Array + recursion guard), settings round-trip, ring-buffer eviction with in-flight-read protection, HTTP parsing, CSRF policy, port pre-flight, content negotiation, Enhanced Broadcasting per-track seq-header cache + TrackId-aware tag selection, multi-track audio + per-destination routing, SPS orientation parsing for vertical selection, the OBS `services.json` patcher, the update-check parser, the hand-rolled SHA-256 (NIST vectors), the RTMP chunk-stream reader/writer, the scheduled safe-cut state machine, and the self-update download + checksum-verify + exe swap. **282 tests, all green.**
+
+
@@ -298,33 +329,14 @@ No npm. No submodules. No platform SDKs. The dashboard HTML is minified + gzippe
## Status
-**Daily-driver ready on Windows.** I use it on my own streams, and a growing group of streamers now run it daily too. CI runs fmt + clippy (with `-D warnings`) + 282 tests on every push, and a tagged commit auto-builds + publishes a release artifact with a `SHA256SUMS.txt` checksum file alongside (no code-signing certificate yet, so the OS may warn on first launch).
-
-**What's solid**
-
-- The two-phase `arm → activate → cut` state machine, with IDR-aligned cuts and monotonic timestamp rewrites. The thing that would have made me build this if it didn't exist.
-- **Live delay adjustment**: re-arm or adjust the delay up / down without disarming first. Backend already supported it; the cockpit now exposes it as a single typed-value + "↻ Adjust ↑/↓ to Ns" CTA.
-- **Full OBS-parity RTMP handshake.** `connect` carries the same codec-capability bag librtmp ships (`audioCodecs=3191`, `videoCodecs=252`, `videoFunction=1`), the Enhanced-RTMP `fourCcList` (AVC / HEVC / AV1 / VP9 / Opus / AC-3 / FLAC), `Set Chunk Size` before connect, `FCUnpublish → deleteStream` on shutdown, and RTMP Acknowledgement (BYTES_READ_REPORT) at the peer-declared window/10 threshold on both ingest and egress.
-- **Enhanced Broadcasting passthrough to Twitch.** When OBS hits multi-track "Auto" we proxy Twitch's `GetClientConfiguration`, route egress to the session-allocated IVS endpoint, and forward every per-track SPS/PPS bit-faithfully so the transcoded ladder lights up regardless of account tier. Non-Twitch destinations get the horizontal primary track by default - multi-track ladder tags with `TrackId != 0` are dropped to avoid the multi-frame-per-PTS storm that crashes YouTube's decoder. EB cuts land on the primary track's IDR (not whichever ladder rung's keyframe happens to win the partition_point) so the destination decoder always has its anchor.
-- **Vertical (9:16) output for non-Twitch destinations.** Set a YouTube / Kick / custom destination's **Stream format** to **Vertical** and it forwards Twitch Dual Format's vertical canvas instead of the horizontal one, flattened to a standard single-track feed those platforms accept natively (YouTube Shorts, Kick mobile, TikTok). The vertical canvas is identified by decoding each track's SPS for orientation (portrait, largest area) rather than relying on Twitch's private session JSON, and it self-heals as Dual Format toggles on/off. It only flows while Twitch Dual Format / Enhanced Broadcasting is on in OBS; otherwise the destination card shows "Waiting for Dual Format" and nothing else is affected. Hidden for Twitch, which carries both canvases natively.
-- **Twitch VOD audio track, unlocked on the InstantClone service.** OBS hardcodes its VOD Track to the service literally named "Twitch" (the gate is `ServiceSupportsVodTrack == {"Twitch"}`), so it's locked while you use the InstantClone service. A tiny bundled OBS script (`optional-vod-unlocker.lua`, one-click download from the dashboard straight into OBS's scripts folder) attaches the same second audio encoder OBS's own VOD Track would - without the gate - so OBS sends both the live track (wire TrackId 0) and the VOD track (TrackId 1). InstantClone forwards them to Twitch bit-faithfully; its wire-format reader matches OBS's `flv_packet_audio_ex` byte-for-byte (`AudioPacketType` in byte 0, `TrackId` at byte 6). **OBS 32.2+ needs the script** (that release locked the built-in VOD Track to Custom services); on older OBS the classic checkbox path still works - we write `EnableCustomServerVodTrack` to OBS 32's `user.ini` (falling back to `global.ini`) to unlock it, optionally paired with EB via the one-click **"Set up VOD + EB"** launcher or a cold-start **desktop shortcut**. Live + VOD audio works alongside delay cuts.
-- **Per-destination audio track routing.** Once OBS sends a second audio track (via the unlocker script or Enhanced Broadcasting), each destination chooses what it gets: Twitch keeps **both** (VOD audio), while you send only the clean **Track 2** to YouTube to dodge copyright, or only the live **Track 1** to Kick. Non-selected tracks are dropped and the chosen one is flattened to a standard single-track tag (AAC rewritten to legacy `0xAF`, the form every ingest accepts), mirroring the video-side `flatten_multitrack_video`. If the chosen track isn't being sent, it falls back to the live track rather than going silent. Only Twitch consumes a second audio track, so every other platform always receives exactly one.
-- **One-click OBS service registration.** The wizard's primary onboarding path adds an "InstantClone" entry to OBS's Service dropdown (writes `services.json` with a `.bak` first; refreshes on port change; surfaces "close OBS first" when the file is locked).
-- Multi-destination egress with per-destination reconnect + bitrate stats.
-- **RTMPS egress (Kick + any `rtmps://` destination).** The egress socket transparently upgrades to TLS for `rtmps://` URLs, reusing the Windows schannel already linked for the HTTPS client, so there's no second TLS stack (rustls + ring would have added ~1.4 MB). Kick is a paste-your-Server-URL platform in the wizard and destination form, with the `/app` path added automatically when it's missing.
-- **Capacity-aware buffer UI**: live "X MB → max Ys delay at N Mbps" hint, refuses to arm a delay larger than the buffer can hold with an explicit "needs ≥ N MB" reason.
-- **Platform-specific warnings**: Twitch mobile-decoder risk above 8 Mbps under Source-Only, Kick's AWS IVS ingest rules (CBR + 2 s keyframe; B-frames actually fine on its low-latency RTMP), per-platform stream-key dashboard links - all surfaced in the wizard / destination form so streamers don't have to learn each platform's gotchas the hard way.
-- Tray icon with live status + one-click cut, port-conflict pre-flight that names the offending process by PID + exe.
-- Test coverage covers the state machine, AVC + Enhanced RTMP IDR detection + multi-track flatten, AMF0 codec including Strict Array, ring eviction with in-flight-read protection, and the timestamp-wrap promotion that prevents the 49.7-day bug.
+**Daily-driver ready on Windows.** I use it on my own streams, and a growing group of streamers now run it daily too. CI runs fmt + clippy (`-D warnings`) + 282 tests on every push, and a tagged commit auto-builds and publishes a release with a `SHA256SUMS.txt` alongside (no code-signing certificate yet, so the OS may warn on first launch).
**What's rough, honestly**
-- **Windows only.** macOS / Linux aren't tested or packaged. Several modules (tray, port pre-flight, RSS sampler) have Windows-specific code paths that need parallel implementations.
-- **Multi-hour streams, real mileage now.** Multi-hour daily-driver runs across a growing set of streamers, with the supervisor + keepalive + ack logic built for indefinite sessions. Still worth periodic long soak runs as the user base scales.
-- **Transcoded ladder isn't guaranteed without EB.** Only Twitch Partners get a transcode slot every time. Affiliates get one opportunistically (capacity-dependent, and far less likely above ~6 Mbps); everyone else stays Source-Only. Above ~8 Mbps under Source-Only, some viewers' hardware decoders fail with Error #1000. This is Twitch's allocation behaviour, not the proxy: OBS's native Twitch preset auto-caps bitrate to stay transcode-eligible, but the Custom-server path (which InstantClone has to be) skips that cap. For a guaranteed ladder use Enhanced Broadcasting (or the VOD+EB Launch button); otherwise keep bitrate near ~6000 Kbps.
-- **Sync disk I/O on the ring-append hot path, by choice.** The buffered write lands in the OS page cache in microseconds and the kernel flushes in the background, so the page cache is already acting as the async buffer, and the index and the bytes advance under one lock so a reader never sees a tag whose bytes aren't on disk yet. The tail risk is a writeback stall under memory or slow-disk pressure, which on the current-thread runtime would briefly freeze egress too, not just ingest. Moving the write off-thread adds per-tag overhead and reopens a write-vs-index consistency window, so it isn't worth it for typical hardware. If low-end disks ever become a priority the real lever is the runtime (separate the disk-blocking ingest from egress), not async writes. A someday-maybe I'll revisit, not a blocker.
-- **A handful of `unwrap()` on lock guards.** Fine because `panic = "abort"` means a poison condition can't propagate, but still on the cleanup list.
-- **Hand-rolled HTTP server.** Smaller binary than `hyper`, but I now own the entire HTTP CVE surface. Worth re-evaluating if the surface grows.
+- **Windows only today.** Several modules (tray, port pre-flight, RSS sampler) use Windows-specific paths. A Linux build (including a headless/terminal server version) is on the roadmap; macOS isn't planned yet.
+- **Transcoded ladder isn't guaranteed without EB.** Only Twitch Partners get a transcode slot every time; everyone else stays Source-Only, where some hardware decoders fail above ~8 Mbps (Twitch's allocation behaviour, not the proxy). For a guaranteed ladder use Enhanced Broadcasting; otherwise keep bitrate near ~6000 Kbps.
+- **A handful of `unwrap()` on lock guards.** Safe because `panic = "abort"` means a poison condition can't propagate, but still on the cleanup list.
+- **Hand-rolled HTTP server.** Smaller binary than `hyper`, but I own the entire HTTP surface. Worth re-evaluating if it grows.
> [!WARNING]
> This is a hobby project I use myself, not a vendor product. If you stream paid esports, validate it against your own pipeline before trusting it on a tournament night.
@@ -333,6 +345,68 @@ No npm. No submodules. No platform SDKs. The dashboard HTML is minified + gzippe
+## FAQ
+
+
+Does streaming to several platforms lower my quality?
+
+
+
+No. InstantClone forwards the exact encoded feed from OBS to each destination without re-encoding, so every platform gets the same quality OBS produced. What it does use is **upload bandwidth**: each destination receives the full bitrate, so three destinations need roughly three times the upload. The dashboard shows an "Upload bottleneck" warning and suggests keeping your OBS bitrate under ~80% of your upload.
+
+
+
+
+Is the delay applied to every platform at once?
+
+
+
+Yes. The buffer sits before the fan-out, so every destination plays from the same delayed position. Arm, activate, and cut affect all of them together.
+
+
+
+
+How much delay can I set?
+
+
+
+As much as the buffer holds. The default 500 MB file is about 11 minutes at 6 Mbps (less at higher bitrates). You can make it bigger; the dashboard refuses a delay the buffer can't hold and tells you exactly how many MB it needs, so it never stalls silently.
+
+
+
+
+Does it re-encode or touch my video?
+
+
+
+No. Video is passed through bit-for-bit. The only rewriting happens on the audio container when you route a specific track to a destination (AAC is rewritten to the legacy tag every ingest accepts); the audio samples themselves are untouched.
+
+
+
+
+Where do my real stream keys go?
+
+
+
+In InstantClone's **Destinations** tab, never in OBS. OBS only ever points at InstantClone with one service and a throwaway key; InstantClone holds each platform's real key and fans your feed out to them. So your keys live in one place, and you toggle destinations on and off without touching OBS.
+
+**Your keys never leave your PC.** InstantClone has no servers of its own and no telemetry: keys are stored locally on your machine and only ever sent to the platform ingest servers you choose to stream to. Running the app sends us nothing, we never see your keys, your stream, or anything else. It's open source, so you can verify that yourself.
+
+
+
+
+Does it run on macOS or Linux?
+
+
+
+Windows 10/11 today. A **Linux build is on the roadmap**, including a headless/terminal server version for boxes with no desktop. macOS isn't planned yet. A few parts (tray, port pre-flight, memory sampling) use Windows-specific code that needs porting first.
+
+
+
+
+
+
+
## Connect
@@ -350,6 +424,6 @@ Catch me streaming while building this, or just chat about the project. Bug repo
## License
-[GPL-3.0](LICENSE). You can use it, modify it, run it on whatever stream you like. If you distribute a modified version (including a "Pro" fork, a bundled installer with extras, or a paid front-end), your source has to ship under the same license, publicly. I built this as a free alternative because I wanted one for myself; GPL is what keeps forks free too.
+[GPL-3.0](LICENSE). Use it, modify it, run it on whatever stream you like. If you distribute a modified version (including a "Pro" fork, a bundled installer, or a paid front-end), your source has to ship under the same license, publicly. I built this as a free alternative because I wanted one for myself; GPL is what keeps forks free too.
Built by [s1moscs](https://s1moscs.dev).
diff --git a/docs/icon.ico b/docs/icon.ico
new file mode 100644
index 0000000..a9f60af
Binary files /dev/null and b/docs/icon.ico differ
diff --git a/docs/icon.png b/docs/icon.png
new file mode 100644
index 0000000..cae66fb
Binary files /dev/null and b/docs/icon.png differ
diff --git a/docs/pipeline.svg b/docs/pipeline.svg
new file mode 100644
index 0000000..ab34eae
--- /dev/null
+++ b/docs/pipeline.svg
@@ -0,0 +1,84 @@
+
diff --git a/docs/states.svg b/docs/states.svg
index 0828848..ee174cc 100644
--- a/docs/states.svg
+++ b/docs/states.svg
@@ -1,122 +1,116 @@
-