A self-hosted streaming aggregator that searches movieffm, 777tv, and dramasq simultaneously, validates HLS sources, and plays them in-browser — no ads, no redirects.
- Unified search across movieffm.net, 777tv.ai, and dramasq.io in one query
- Three providers — movieffm & 777tv (movies + TV), dramasq (TV dramas, multiple CDN sources per episode)
- Strict stream validation — checks HTTP status and reads m3u8 content (
#EXTM3U+ segment tags); HTML error pages no longer pass as valid - In-browser HLS playback via hls.js, with automatic fallback to the server proxy if direct play fails
- m3u8 proxy that rewrites segment and
#EXT-X-KEYURLs through the backend to bypass CORS and hotlinking - Poster proxy to bypass hotlink protection on cover images
- LRU caching for search results, detail pages, stream checks, and media-type detection
- Fast search timeouts — search requests time out at 8 s (configurable), detail/stream fetches at 20 s; slow providers never block fast ones
- Traditional Chinese / English UI, language switch does not interrupt playback
docker compose up --build| Service | URL |
|---|---|
| Frontend | http://localhost:8080 |
| Backend | http://localhost:8787/api/health |
# Backend (auto-reloads on file change)
cd server && npm install && npm run dev
# Frontend (Vite HMR)
cd frontend && npm install && npm run dev
# → http://localhost:5173 (proxies /api to localhost:8787)browser
└── React SPA (Vite)
└── /api/* ──► Express server
├── /api/search scrape & cache search results
├── /api/item scrape detail page (streams / seasons / episodes)
├── /api/episodes episode list for a season URL
├── /api/sources check & filter streams for one episode
├── /api/check-sources batch check raw stream list (movies)
├── /api/stream HLS proxy + m3u8 URL rewriting
└── /api/poster image proxy
| File | Role |
|---|---|
src/index.js |
Express routes |
src/stream.js |
Stream checker, HLS proxy, poster proxy |
src/cache.js |
Four LRU caches (search 5 min, detail 10 min, streamCheck 3 min, mediaType 10 min) |
src/providers/movieffm.js |
Scraper for movieffm.net |
src/providers/seventv.js |
Scraper for 777tv.ai |
src/providers/dramasq.js |
Scraper for dramasq.io (TV only, uses /drq/ JSON API for streams) |
src/providers/index.js |
Provider registry |
src/utils/http.js |
Shared fetchText / fetchJson with browser UA |
Adding a provider: implement { search, getItem, getEpisodes, getEpisodeStreams } and register it in src/providers/index.js.
Single App.jsx component. UI flow:
- Search → grouped poster grid per provider (skeleton shimmer while loading); each provider resolves independently
- Click poster → detail panel opens (poster + title on left, player on right)
- Select season → episode list loads
- Select episode → sources checked and listed as compact pills (green dot = direct, orange = proxy)
- Source selected → HLS plays; on fatal error auto-falls back to proxy URL
| Provider | Movies | TV | Stream method |
|---|---|---|---|
| movieffm | ✓ | ✓ | Scrapes embedded JSON from detail/episode pages |
| 777tv | ✓ | ✓ | Scrapes var player_* JSON from play pages |
| dramasq | — | ✓ | /drq/{id}/{ep} JSON API returns multiple CDN sources |
Standalone CLI tools in PoC/ for scraping without the web UI:
pip install requests beautifulsoup4
python3 PoC/movieffm_cli.py
python3 PoC/777tv_cli.py
python3 PoC/dramasq_cli.pyAll scripts apply the same strict m3u8 validation as the backend (reads first 4 KB, checks #EXTM3U + segment tags).
- Docker ≥ 26 — for the compose setup
- Node.js ≥ 22 — for local dev
- Python ≥ 3.11 +
requests+beautifulsoup4— for the PoC scripts only