A crappy website I vibe coded just to watch the hermitcraft + gamers outreach charity stream
  • JavaScript 63.9%
  • CSS 21.8%
  • HTML 6.5%
  • Nix 6.1%
  • Dockerfile 1.7%
Find a file
2026-04-26 02:03:15 -04:00
src Focus and Fullscreen modes fix 2026-04-12 15:44:30 -04:00
.dockerignore first commit 2026-04-12 13:18:41 -04:00
.gitignore first commit 2026-04-12 13:18:41 -04:00
docker-compose.yml first commit 2026-04-12 13:18:41 -04:00
Dockerfile first commit 2026-04-12 13:18:41 -04:00
flake.nix first commit 2026-04-12 13:18:41 -04:00
index.html Many fixes 2026-04-12 15:38:41 -04:00
package-lock.json Updating deps 2026-04-26 02:03:15 -04:00
package.json first commit 2026-04-12 13:18:41 -04:00
README.md Remove placeholder image 2026-04-12 13:20:12 -04:00
shell.nix first commit 2026-04-12 13:18:41 -04:00
vite.config.js first commit 2026-04-12 13:18:41 -04:00

MultiStream

Watch multiple Twitch and YouTube streams at the same time in a single browser tab.

Features

  • Multi-stream grid — watch Twitch channels and YouTube videos side-by-side
  • Drag to reorder — grab the ⠿ handle on any stream card and drag it into position
  • Per-stream audio controls — mute/unmute each stream individually
  • Solo mode — focus audio on one stream and silence the rest with a single click
  • Global mute / unmute — silence or restore all streams at once (M / U)
  • Streams start muted — new streams are added silently so you stay in control
  • Account sign-in — sign in to Twitch and YouTube so embedded players recognise your subscriptions and YouTube Premium (skips ads)
  • Profiles — save named groups of streams (e.g. "Valorant", "Hermitcraft") and switch between them instantly
  • Persistent state — open streams, layout, and profiles are saved to localStorage and restored on reload
  • Clear All — remove all active streams in one click
  • Flexible layouts — auto-fill grid or fixed 14 column layouts

Supported URL formats

Platform Example
Twitch channel twitch.tv/channelname
YouTube video youtube.com/watch?v=VIDEO_ID
YouTube short URL youtu.be/VIDEO_ID
YouTube live youtube.com/live/VIDEO_ID
YouTube embed youtube.com/embed/VIDEO_ID

Twitch embeds require the page to be served over HTTP/HTTPS — they will not work when opened as a local file:// URL.

Keyboard shortcuts

Key Action
A Open the Add Stream dialog
M Mute all streams
U Unmute all streams
Esc Close any open dialog

Getting started

Prerequisites

  • Node.js 18 or later (24 recommended)
  • npm 9 or later (bundled with Node.js)

Install and run

# 1. Install dependencies
npm install

# 2. Start the dev server (hot reload at http://localhost:5173)
npm run dev

Available npm scripts

Script Description
npm run dev Dev server with hot reload → http://localhost:5173
npm run build Production bundle → dist/
npm run preview Serve the production build → http://localhost:4173
npm start Build then serve (production preview)

Nix

Two Nix entry points are provided — use whichever matches your setup.

shell.nix — classic Nix (nix-shell)

Uses your system nixpkgs channel.

# Enter the dev environment (auto-installs npm deps on first run)
nix-shell

# Or run a single command without entering the shell
nix-shell --run "npm run build"

flake.nix — modern Nix (nix develop)

Pins nixpkgs to nixos-unstable for fully reproducible builds.

# Enter the dev environment
nix develop

# Or run a single command
nix develop --command npm run build

Both shells provide Node.js 24 (with npm) and print available commands on entry. If node_modules/ is absent they run npm install automatically.

Reproducible nix build

flake.nix also exposes a packages.default output that builds the dist/ folder hermetically via buildNpmPackage. Before using it you need to supply the correct npmDepsHash:

# 1. Make sure package-lock.json is up to date
npm install --package-lock-only

# 2. Compute the hash
nix-prefetch-npm-deps package-lock.json

# 3. Paste the printed hash into flake.nix → npmDepsHash = "sha256-...";

# 4. Build — outputs to ./result/
nix build

Docker

Quick start

# Build the image and start the container
docker compose up -d

# Tail logs
docker compose logs -f

# Stop and remove the container
docker compose down

The app will be available at http://localhost:3000.

Build and run manually

# Build the image
docker build -t multistream:latest .

# Run the container
docker run -d \
  --name multistream \
  -p 3000:3000 \
  --restart unless-stopped \
  multistream:latest

Image details

The Dockerfile uses a two-stage build to keep the final image small (~173 MB):

Stage Base image Purpose
builder node:24-alpine Install npm dependencies and run vite build
runner node:24-alpine Install serve, copy dist/, expose port 3000

The runner stage contains only the compiled static assets and the static file server — no source code, Vite, or dev tooling.

Changing the port

Edit docker-compose.yml and update the left side of the port mapping:

ports:
  - "8080:3000"   # now reachable at http://localhost:8080

Or pass it directly with docker run:

docker run -d -p 8080:3000 multistream:latest

Rebuilding after source changes

docker compose up -d --build

Project structure

multistream/
├── index.html          # HTML shell (markup only)
├── src/
│   ├── main.js         # Application logic (ES module)
│   └── style.css       # All styles
├── public/             # Static assets served as-is
├── dist/               # Production build output (generated)
├── package.json        # npm scripts and dependencies
├── vite.config.js      # Vite configuration
├── shell.nix           # Classic Nix dev shell
├── flake.nix           # Flake-based Nix dev shell + build
├── Dockerfile          # Two-stage Docker build
├── docker-compose.yml  # Docker Compose service definition
└── .dockerignore       # Files excluded from Docker build context

Dependencies

Package Role
SortableJS Drag-and-drop reordering of stream cards
Vite Dev server and production bundler
serve Static file server used in the Docker image