Assista várias lives (Twitch/Kick) ao mesmo tempo, num grid ajustável, com posição de cada player salva e a configuração inteira compartilhável por link.
Ferramentas de multi-stream em geral ou fixam o layout, ou perdem a posição de cada player a cada reload, ou não deixam compartilhar a tela montada com outra pessoa. O multistream resolve os três ao mesmo tempo:
?streams=twitch:canal,kick:canal2) — copiar o
link e mandar pra alguém reproduz a mesma tela.npm install
npm run dev # http://localhost:5173npm run build # gera dist/
npm run preview # serve o build localmenteVia Docker (build multi-stage + nginx servindo estático):
docker compose up --build # http://localhost:5174cols e o layout recalcula linhas, largura
e altura de cada player mantendo 16:9, considerando o tamanho real da viewport
(use-grid-layout.ts).dnd-kit).React.memo + key estável por platform:channel + CSS Grid, sem tocar a ordem no DOM —
ver RENDER-PLAYERS.md).Aplicação client-side pura (SPA), sem backend — build estático servido por nginx.
Duas fontes de verdade coexistem de propósito: a URL decide o quê está na tela (pra ser
compartilhável), o localStorage decide onde cada coisa está (pra sobreviver a reload sem
poluir o link). A reconciliação entre as duas é o único ponto realmente não-trivial do projeto —
detalhado no deep-dive linkado acima.
React 19 · TypeScript · Vite · TanStack Router · Zustand (+persist) · dnd-kit · Tailwind v4 · shadcn/radix-ui · react-hook-form + zod · Biome (lint/format) · Docker + nginx (deploy estático)
TanStack Router (estado principal via search params)
└─ routes/index.tsx
├─ useGridLayout → calcula cols/rows/tamanho do player a partir da viewport
├─ useLoadStreams → sincroniza streams da URL com a ordem de slot persistida
├─ useGridStore (zustand+persist) → posição de cada stream no grid (localStorage)
├─ useHistoryStore (zustand+persist) → streams removidas (localStorage)
└─ DndContext (dnd-kit) → swap de posição ao arrastar
└─ Slot × N
└─ PlayerContainer (memo) → TwitchPlayer | KickPlayer (iframe)src/
├── routes/ TanStack Router — rota única, orquestra grid + drag-and-drop
├── components/ UI: layout, slots, sidebar/histórico, top bar, players/
├── hooks/ use-grid-layout, use-load-streams, use-stream-manager, use-window-size
├── store/ zustand: streams (em memória), grid (slotOrder), history — os dois
│ últimos persistidos em localStorage
├── utils/ parse-stream (parse/serialização do parâmetro `streams` da URL)
└── @types/ Stream, STREAM_OPTION