# CastCaster

[![CI](https://github.com/Lax/castcaster/actions/workflows/ci.yml/badge.svg)](https://github.com/Lax/castcaster/actions/workflows/ci.yml)
[![Gem](https://img.shields.io/gem/v/castcaster)](https://rubygems.org/gems/castcaster)
[![License](https://img.shields.io/github/license/Lax/castcaster)](LICENSE)
[![Ruby](https://img.shields.io/badge/Ruby-%3E%3D%203.0-red)](https://ruby-lang.org)

Unified live streaming management platform.

Manage RTMP/HLS streaming with Nginx, per-channel FFmpeg tasks, and Docker-based deployment.
Each channel spawns independent containers for relay, adaptive transcoding, snapshot, or test — isolated and scalable.

## Quick Start

```bash
# Install and initialize
gem install castcaster
castcaster init

# Add a test channel
castcaster channel add --name demo

# Generate compose.yml and start
castcaster deploy
docker compose up -d
```

Open `http://localhost:8080/channel/demo` — click play to see timestamp video.

### Source Types

```bash
# Test — internal test stream (default)
castcaster channel add --name demo

# Pull — relay from external RTMP source
castcaster channel add --name relay --source-type rtmp_pull --source rtmp://example.com/live/stream

# Pull + adaptive transcoding
castcaster channel add --name news \
  --source-type rtmp_pull \
  --source rtmp://example.com/live/stream \
  --transcode 720p,480p

# Pull + snapshot
castcaster channel add --name camera \
  --source-type http_pull \
  --source http://example.com/stream \
  --snapshot 5

# Push — external OBS/FFmpeg pushes to CastCaster
castcaster channel add --name mychannel --source-type rtmp_push
# OBS: rtmp://host:1935/live/mychannel
```

## Architecture

```
Browser───:80 ──→ Traefik (optional) ──→ nginx :8080
                   │                       ├── /hls/live/* → HLS segments
                   │                       ├── /api/*      → proxy → webui:8081
                   │                       ├── /           → proxy → webui:8081
                   │                       └── RTMP :1935  → RTMP ingest
                   │
                   └── :443 (HTTPS, via Let's Encrypt)

FFmpeg containers (per channel):
  test-<name>    → teststream.sh (internal test pattern)
  relay-<name>   → relay.sh (RTMP/HTTP pull relay)
  adaptive-<name>→ adaptive.sh (multi-bitrate transcode)
  snapshot-<name>→ snapshot.sh (periodic thumbnail)
```

## Requirements

- **Ruby** >= 3.0
- **Docker** + **Docker Compose** (v2)

### Docker Images

| Image | Source | Purpose |
|-------|--------|---------|
| `ghcr.io/lax/castcaster-nginx` | Local build | Nginx (RTMP + HLS + proxy) |
| `ghcr.io/lax/castcaster-webui` | Local build | Web management UI |
| `ghcr.io/lax/castcaster-ffmpeg` | Local build | FFmpeg relay/transcode/snapshot |
| `traefik:v3.0` | Docker Hub | Reverse proxy + SSL (optional) |

## Commands

### `init`

Initialize project in current directory.

```bash
castcaster init
```

Creates: `castcaster.yml`, `castcaster.token`, `channels/`, `hls/`, `logs/`, `data/`.

### `channel add`

Default is `test` — generates a timestamp video with zero external dependencies.

```bash
# Test — internal test stream
castcaster channel add --name demo

# Pull — relay from external RTMP source
castcaster channel add --name relay \
  --source-type rtmp_pull \
  --source rtmp://example.com/live/stream

# Pull — relay from HTTP source
castcaster channel add --name radio \
  --source-type http_pull \
  --source http://example.com/stream

# Pull + adaptive transcoding
castcaster channel add --name news \
  --source-type http_pull \
  --source http://example.com/stream \
  --transcode 720p,480p,360p

# Pull + snapshot
castcaster channel add --name camera \
  --source-type http_pull \
  --source http://example.com/stream \
  --snapshot 5

# Push — external OBS/FFmpeg pushes to CastCaster
castcaster channel add --name mychannel --source-type rtmp_push
# OBS: rtmp://host:1935/live/mychannel
```

### `channel list / start / stop / rm / update`

```bash
castcaster channel list
castcaster channel start news
castcaster channel stop news
castcaster channel rm radio
castcaster channel update news --source-type rtmp_pull --source rtmp://example.com/new
```

### `config`

Preview generated nginx config.

```bash
castcaster config
```

### `deploy`

Generate compose.yml (and optional Traefik config).

```bash
castcaster deploy                       # Docker Compose (default)
castcaster deploy --with-traefik        # + Traefik SSL
castcaster deploy --mode swarm          # Docker Swarm

```

### `doctor`

Check system environment.

```
Ruby           ruby 3.4.1
Docker         Docker version 28.4.0
Compose        Docker Compose version v2.39.3
nginx          available
ffmpeg         available
webui          available
config.yml     ok (engine: nginx-rtmp)
```

## Channel Tasks

Each channel spawns independent containers based on config:

| Task | Container Name | Trigger | Description |
|------|---------------|---------|-------------|
| Test | `castcaster-test-<name>` | `test` (default) | Internal timestamp video |
| Relay | `castcaster-relay-<name>` | `rtmp_pull` / `http_pull` / `srt` | Relay external source → nginx |
| Adaptive | `castcaster-adaptive-<name>` | `--transcode` | Multi-bitrate transcoding (replaces relay) |
| Snapshot | `castcaster-snapshot-<name>` | `--snapshot` | Periodic thumbnail capture |
| (none) | — | `rtmp_push` | OBS/FFmpeg pushes directly, no container |

Containers are tagged with Docker labels:

```yaml
labels:
  "castcaster/channel": "news"
  "castcaster/task": "adaptive"
```

### Transcode Presets

| Profile | Bitrate | Resolution | Audio |
|---------|---------|------------|-------|
| 360p | 512k | 640×360 | 64k |
| 480p | 1024k | 854×480 | 96k |
| 576p | 1248k | 1024×576 | 96k |
| 720p | 2048k | 1280×720 | 128k |
| 1080p | 3000k | 1920×1080 | 128k |

## Web Management UI

`http://localhost:8080`

- Dashboard — channel list with task status
- Channel page — hls.js player, RTMP/HLS connection info, click-to-copy
- REST API — `GET /api/channels`, `GET /api/status`

> Note: Start/stop actions are managed via CLI (`castcaster channel start/stop`) for security reasons.

## Configuration

```yaml
# castcaster.yml
engine: nginx-rtmp
hls_fragment: 6
hls_window: 60
domain: stream.example.com        # for Traefik SSL
acme_email: admin@example.com
enable_traefik: false
```

## Deployment Modes

### Docker Compose (default)

```bash
castcaster deploy
docker compose up -d
```

### Docker Compose + Traefik

```bash
castcaster deploy --with-traefik
docker compose up -d
```

Traefik provides Let's Encrypt HTTPS, sits in front of nginx.

### Docker Swarm

```bash
castcaster deploy --mode swarm
docker stack deploy -c docker-stack.yml castcaster
```

## Development

| Branch | Description |
|--------|-------------|
| `dev` | Active development |
| `main` | Product release branch |

### Docker-based development (recommended)

Only Docker needed — no Ruby installation required:

```bash
# Build CLI image
make cli-build

# Create and start a test project
make dev TARGET=/tmp/my-project

# View logs
make dev-logs TARGET=/tmp/my-project

# Stop all services
make dev-down TARGET=/tmp/my-project

# Run tests (unit + integration) in container
make test

# Quick syntax check in container
make syntax
```

### Build from source (Ruby only)

```bash
git clone https://github.com/Lax/castcaster.git
cd castcaster
gem build castcaster.gemspec
gem install castcaster-*.gem
```

### Test

```bash
make test                     # Run all tests in Docker container
make syntax                   # Ruby syntax check in Docker container
make check                    # Full: syntax + test + gem build
```

Or with Ruby locally:

```bash
rake test                     # Run all tests (unit)
rake syntax                   # Ruby syntax check
rake                          # Default: syntax + test
```

### Project structure

```
castcaster/
├── lib/castcaster/           ← Ruby source
│   ├── cli.rb                ← CLI commands (Thor)
│   ├── channel.rb            ← Channel model + CRUD
│   ├── channel_cli.rb        ← Channel subcommands
│   ├── config.rb             ← Configuration load/save
│   ├── deploy.rb             ← Deployment generator
│   ├── deploy/               ← Compose/Swarm deployers
│   ├── engines/              ← Streaming engine configs
│   ├── ffmpeg_profiles.rb    ← Transcode presets
│   └── version.rb            ← Version constant
├── templates/                ← ERB/Tera templates
│   ├── nginx-rtmp/           ← Nginx config template
│   └── deploy/               ← Deployment templates
├── docker/                   ← Docker image sources
│   ├── cli/                  ← CLI tool container
│   ├── nginx/
│   ├── ffmpeg/
│   └── webui/
├── bin/                      ← CLI entry point
├── test/                     ← Minitest suite
├── examples/README.md         ← Example documentation
└── examples/clock-demo/       ← CLI-generated project with clock source
```

## License

MIT

**Homepage:** [https://liulantao.com/castcaster](https://liulantao.com/castcaster)
