Website phát nhạc từ URL TikTok: dán link → hệ thống tải & extract audio chất lượng tốt nhất TikTok cung cấp → lưu cache → phát trên trình player.
Dự án Next.js (App Router, TypeScript) làm cả frontend lẫn API. Phần backend
(validate URL, cache, media processor, API routes) nằm ở thư mục lib/ và
app/api/.
- Node.js 18/20+
- ffmpeg — dùng để tách audio (yt-dlp gọi nội bộ)
- yt-dlp — tải metadata + audio từ TikTok
⚠️ Lưu ý pháp lý: tải & phát lại nội dung TikTok có thể vi phạm bản quyền và Điều khoản dịch vụ của TikTok tuỳ khu vực. Chỉ dùng cho mục đích cá nhân / thử nghiệm. Spec thuần tuý kỹ thuật; người dùng chịu trách nhiệm tuân thủ luật.
- Cài ffmpeg:
# macOS brew install ffmpeg # Ubuntu / Debian sudo apt-get install -y ffmpeg
- Cài yt-dlp kèm
curl_cffi(bắt buộc — TikTok hiện yêu cầu impersonation, thiếucurl_cffisẽ báoRequested format is not available):Kiểm tra:pip install yt-dlp curl_cffi # hoặc trong một venv: # python3 -m venv .venv && .venv/bin/pip install yt-dlp curl_cffi # rồi set YTDLP_PATH=.venv/bin/yt-dlp
yt-dlp --versionphải chạy được và không báoimpersonate target is not available. - Cài dependencies & chạy dev:
npm install npm run dev
- Mở http://localhost:3000
| Biến | Mặc định | Ý nghĩa |
|---|---|---|
CACHE_DIR |
./cache |
Thư mục lưu file audio đã xử lý. Cần ổ đĩa bền. |
CACHE_TTL_DAYS |
7 |
Xoá file cũ hơn số ngày này. |
CACHE_MAX_GB |
5 |
Khi tổng dung lượng vượt mức, xoá theo LRU. |
YTDLP_PATH |
yt-dlp |
Đường dẫn binary yt-dlp (phải có curl_cffi để impersonate TikTok). |
DB_PATH |
./data/tikplay.json |
File dữ liệu thư viện, báo cáo và danh sách chặn. |
ADMIN_TOKEN |
Không có | Token bắt buộc để truy cập API quản trị bản quyền. |
Ví dụ:
export CACHE_DIR=/data/cache
export CACHE_TTL_DAYS=7
export CACHE_MAX_GB=5
export YTDLP_PATH=yt-dlp
export ADMIN_TOKEN='thay-bang-token-dai-ngau-nhien'
npm run devTrên Fly.io, đặt token bằng secret, không ghi trực tiếp vào fly.toml:
fly secrets set ADMIN_TOKEN='thay-bang-token-dai-ngau-nhien'Màn hình xử lý báo cáo nằm tại /admin/copyright. Token chỉ được giữ trong
sessionStorage của tab quản trị.
Body: { "url": "https://www.tiktok.com/@user/video/123" }
- 200:
{ "ok": true, "data": { "audioUrl": "/api/audio/<key>", "title", "author", "cover", "duration" } } - 400:
{ "ok": false, "error": "..." }
Các request trùng URL (cùng cacheKey) được gộp chung một job (debounce) để không chạy yt-dlp nhiều lần.
Stream file audio .m4a (Content-Type audio/mp4). 404 nếu thiếu.
docker build -t craw-music .
docker run -d --name craw-music \
-p 3000:3000 \
-v craw-cache:/app/cache \
craw-musicHoặc dùng docker-compose:
services:
app:
build: .
ports:
- "3000:3000"
volumes:
- cache:/app/cache
environment:
- CACHE_TTL_DAYS=7
- CACHE_MAX_GB=5
volumes:
cache:Deploy trên host có ổ đĩa bền và chạy được binary: Railway / Render /
Fly.io / Docker VPS. Mount volume cho thư mục cache (CACHE_DIR).
Không dùng Vercel serverless cho MVP: filesystem read-only, timeout ngắn, và khó chạy binary yt-dlp/ffmpeg. Có thể cân nhắc sau này nếu tách riêng worker.
- Cài Fly CLI:
brew install flyctl(hoặc xem https://fly.io/docs/hands-on/install/). - Đăng nhập:
fly auth login. - (Tùy chọn) Đổi tên app trong
fly.toml(app = "...") cho unique. - Tạo volume cho cache (chỉ làm 1 lần):
fly volumes create craw_cache --region sin --size 3
- Deploy:
fly deploy
- Mở app:
fly open(hoặcfly apps open).
Lưu ý:
- App cấu hình auto-stop khi rảnh để tiết kiến free allowance; request đầu sau khi wake sẽ chậm ~20–30s (đang tải lại + xử lý TikTok).
- Volume
craw_cachegắn tại/app/cache→ cache audio bền, không mất khi restart (quan trọng vì mỗi lần xử lý TikTok tốn thời gian tải). - yt-dlp nằm trong image; để luôn mới chạy
fly ssh consolerồiyt-dlp -U, hoặc thêm cron trong container. - Free tier: 3 máy 256MB-shared + 3GB volume. Nếu cần RAM hơn, sửa
memorytrongfly.toml(tốn thêm trong giới hạn tài khoản).
Để yt-dlp luôn mới (TikTok hay đổi API), chạy định kỳ:
yt-dlp -UTrong container có thể thêm cron hoặc chạy trong startup script.
lib/
tiktok/validate.ts # validate + normalize URL, cacheKey = sha256(url)
cache/index.ts # FileCacheStore: get/set/evictOld (TTL + LRU)
media/processor.ts # MediaProcessor: yt-dlp + ffmpeg, debounce
app/api/process/route.ts # POST: nhận URL → cache/process → JSON
app/api/audio/[key]/route.ts # GET: stream file audio
Thiết kế tách module: sau này đổi sang Redis + BullMQ (Hướng B) hoặc
R2/S3 + CDN (Hướng C) chỉ cần thay implementation của CacheStore /
MediaProcessor mà không đổi code gọi.