Skip to content

Latest commit

 

History

History
79 lines (61 loc) · 5.43 KB

File metadata and controls

79 lines (61 loc) · 5.43 KB

Architecture

Vì sao thiết kế thế này. Schema chính xác xem spec/contract.yaml; lớp proxy xem docs/proxy.md.

Triết lý: static-first / CDN-as-database

Đẩy mọi logic về build-time (ingest) + client; CDN chỉ serve byte tĩnh. Không app server trên đường đọc → scale theo GB, không theo user; chi phí ≈ chỉ storage. Một ngoại lệ hẹp: proxy edge cho byte phim ở host cần CORS/Range — vẫn edge-only, không phải app server đọc data.

Các bất biến

  1. Write ⟂ Read — bộ ghi (crawler) và bộ đọc (client) gặp nhau CHỈ qua file tĩnh R2.
  2. Data ⟂ Blob — R2 = data + token (gd:/r2:/b2:/src:); byte phim ở host khác, đổi host không đụng data.
  3. Mutable ⟂ Immutable — chỉ pointer nhỏ tươi (SWR); payload *.v{rev} immutable, cache mãi.
  4. Direct ⟂ Proxiedr2/b2 thẳng; gd/src qua proxy /p/*. Data /v1/*.json không bao giờ qua proxy.

Thành phần

Lớp Chọn Server?
Data R2 custom domain (10GB free ≈ hàng chục nghìn title) không
CDN Cloudflare Cache Rules không
Frontend Astro static (Pages) không
Ingestion GitHub Actions cron (transcode/sub) ephemeral
Blob video R2 / B2 / Drive (1 file/tập hoặc HLS) không
Proxy video Worker / Pages Fn (/p/* — CORS/Range/confirm) edge-only
Watch-state IndexedDB không

Data model — title → parts (CHỈ video)

title (phim) có 1..N parts (tập). kind = movie (phim lẻ, ~1 part) | series (phim bộ, N part). Mỗi part trỏ 1 blob video qua token + (tuỳ chọn) phụ đề rời.

Payload theo profile (discriminated kind)

  • video-mp4 (phim đơn giản): 1 file/tập; <video> seek bằng HTTP Range native. Host gd/src → qua proxy giữ 206.
  • video-hls (phim ABR): master playlist + segment immutable — mọi file tĩnh, CDN ăn khớp; ingest nặng hơn (transcode đa bitrate). Nên đặt trên R2/B2 phát thẳng.
  • subtitles: .vtt/.srt rời, đa ngôn ngữ, gắn <track> (host ngoài cần proxy cho CORS).

Điểm chung: index nhẹ ở R2 ⟂ blob immutable ở host khác. Range vào 1 file phục vụ "seek video"; host khó tính thì proxy chuẩn hoá Range/CORS.

Pointer + immutability (vì sao update vẫn cache mạnh)

  • Payload lớn *.v{rev} immutable → cache 1 năm.
  • head.json = pointer ~50B, SWR 30s → cho biết rev hiện tại.
  • Player: head (tươi) → meta.v{rev} (cache mãi). Tập mới hiện ~30s.
  • Part list chunk hoá: chunk mới nhất versioned, chunk cũ immutable → thêm tập không ghi lại chunk cũ.

Lớp proxy (tâm điểm staticine)

r2/b2 phát thẳng. gd/src qua /p/<token>: forward Range → giữ 206; gắn CORS; bóc Drive confirm-token; cache cứng từng byte-range; giấu origin. Đầy đủ ở docs/proxy.md. resolveBlob() trong contract.ts quyết định thẳng-hay-proxy ở client.

Denormalization

feed/search/genre/status entry là CatalogEntry tự chứa {title, cover, year, status, genres[], rating, latest} → list view 1 fetch là render. Detail view mới fetch record đầy đủ.

Lọc & tìm kiếm (không server)

Mỗi facet = 1 listing tĩnh dựng sẵn (kiểu Hitomi nozomi). Vì entry mang đủ facet, client refine tại chỗ.

Truy vấn Cách
Theo tên tải search/index.v{N} (CatalogEntry[]) → lọc substring/fuzzy in-memory
Theo thể loại GET genre/{slug}/{page}
Theo tình trạng GET status/{status}/{page} hoặc lọc client-side
Theo năm / kind lọc client-side trên .year/.kind (filterEntries())
Kết hợp AND tải facet NHỎ NHẤT → lọc client-side trên .genres/.status/.year/.title
Sắp xếp precompute biến thể (latest/popular/rating) hoặc sort client-side

Catalog lớn: mỗi facet = nozomi id-list (sorted); client tải các id-list cần rồi INTERSECT; resolve id → CatalogEntry. Không product explosion, không server.

Resilience

Thành phần CHẾT Đọc/Duyệt Xem phim Nội dung mới
GitHub Actions (ingest) OK OK dừng tới khi hồi
R2 origin (blip) OK (stale-if-error) OK (blob cache) -
Drive (quota/ban) OK WARN: phim chưa cache lỗi -
proxy OK (data thẳng R2) WARN: chỉ phim gd/src; r2/b2 vẫn xem được -
Cloudflare DOWN (hiếm, SLA cao) DOWN -

Không app server trên đường đọc → không SPOF cổ điển. Crawler & proxy thay/sập không kéo sập read path data.

SEO + UI/UX

SEO không cần prerender (Google render JS). Ưu tiên UI/UX player: seek nhanh, đổi chất lượng (HLS), chọn phụ đề, nhớ tiến độ (IndexedDB), dark mode, responsive. Bổ sung nhẹ <title>/OG + sitemap.xml (sinh lúc ingest) cho social card.

Chi phí

R2 10GB data free (egress $0) · Pages free · Worker proxy 100k/ngày free ($5/tháng = 10M). MVP read-only ≈ $0; chi phí tăng = storage video ở host bạn chọn.

Giới hạn

Cập nhật trễ ~TTL pointer (30s) · không real-time · comment/social cần lớp dynamic · Drive = MVP (quota/ban), chuyển R2/B2 khi nghiêm túc.