Bản lưu ngày / Snapshot of 2026-10-16 · Xem bản mới nhất / See the latest version
WanduraDocsBeta
BetaDocs đang ở bản beta và còn thay đổi. Thấy chỗ sai, khó hiểu hoặc còn thiếu? Hãy cho chúng tôi biết.
Bạn đang đọc một bản tài liệu cũ: v1 · Sang bản mới nhất (v2)
Hướng dẫn

Đồng bộ open video

Markdown
Trên trang này

TL;DR — Server của bạn quét mọi trang của GET /videos bằng key của app, giữ toàn bộ kết quả, và phục vụ link từ đó cho tới refresh_before. Chỉ dừng khi next_cursor === null, không bao giờ dừng vì một trang ngắn. Retry 429/503; nếu một lượt quét hỏng, tiếp tục phục vụ danh sách trước đó.

Trước khi bắt đầu

  • Mỗi app một key, có videos:read, gắn với app đó (Open video).
  • Đồng bộ theo trường nào: id, không bao giờ theo title.

Lượt quét

  1. Gọi GET /videos?limit=200, rồi gọi lại với cursor=<next_cursor> (URL-encode, giữ nguyên) cho tới khi next_cursor là null. Một trang ngắn hay rỗng không có nghĩa là đã hết: các video chưa sẵn sàng phát bị bỏ qua.
  2. Đặt khoá dòng theo id. Lưu cặp order_code ↔ id một lần nếu bạn ghép với một tệp đơn hàng; không bao giờ ghép theo title (tên có thể trùng).
  3. Lấy refresh_before sớm nhất trong mọi trang: các trang trước hết hạn trước.
  4. Chỉ thay danh sách app của bạn thấy khi lượt quét đã hoàn tất. Nếu lượt quét hỏng, tiếp tục phục vụ danh sách trước đó (link của nó vẫn hợp lệ tới khi chính nó hết hạn) và thử lại sau một phút.
  5. Quét lại trước refresh_before sớm nhất.

Code

/** Every page or an exception (429/503/network are retried inside `call`). Never replace your list with a partial sweep. */
export async function sweep(o: CallOptions = {}): Promise<{ rows: OpenVideo[]; refreshAt: Date }> {
  const rows = new Map<string, OpenVideo>();
  let cursor: string | null = null;
  let refreshAt: Date | null = null;
  for (let page = 0; page < 10_000; page++) {
    const qs: string = `limit=200${cursor ? `&cursor=${encodeURIComponent(cursor)}` : ''}`;
    const { data }: { data: Page } = await call<Page>('GET', `/api/delivery/v1/videos?${qs}`, { policy: BACKGROUND, ...o });
    for (const r of data.rows) rows.set(r.id, r); // key by `id`, never by title
    const rb = new Date(data.refresh_before); // earlier pages expire first: keep the EARLIEST deadline
    if (!refreshAt || rb < refreshAt) refreshAt = rb;
    cursor = data.next_cursor;
    if (cursor === null) return { rows: [...rows.values()], refreshAt }; // a short or empty page is NOT the end
  }
  throw new Error('open video sweep did not end');
}
let current: { rows: OpenVideo[]; refreshAt: Date } | null = null;

/** Keeps `current` fresh. On failure keeps serving the previous list and tries again in a minute. */
export async function refreshLoop(o: CallOptions = {}): Promise<void> {
  let delay = 60_000;
  try {
    current = await sweep(o);
    delay = Math.max(60_000, current.refreshAt.getTime() - Date.now());
  } catch {
    // previous links stay valid until their own expiry
  }
  setTimeout(() => void refreshLoop(o), delay).unref();
}
/** What your app receives: links from your server's cache. Apps never store them. */
export const currentRows = (): OpenVideo[] => current?.rows ?? [];

Retry diễn ra bên trong call, theo chính sách retry chuẩn. CI của docs chạy code này với một API giả có trang đầu trả 429, để chứng minh lượt quét không kết thúc với danh sách rỗng.

Các trường

Mọi trường của một dòng: Liệt kê open video. Những trường hay làm người ta vấp:

  • mp4.p720 có thể là null: không có bản 720p. Đừng thay bằng p480.
  • order_code luôn có mặt và có thể là null.
  • difficulty và kcal_est hiện luôn là null.

Xong khi

  • Lượt quét có trang đầu trả 429 vẫn kết thúc với đầy đủ danh sách.
  • App lấy link từ server của bạn, không bao giờ lấy từ bộ nhớ lưu trữ.
  • Danh sách được làm mới trước refresh_before sớm nhất.

Cập nhật 05/10/2026 · Lịch sử thay đổi (1)

↑↓ chọnEnter mởEsc đóng

Gửi góp ý

Góp ý được gửi thẳng tới đội Wandura, kèm địa chỉ trang này.

0 / 4000