# Playback và vé

> **TL;DR** — Để phát một tập, backend của bạn xin `POST /playback` một **vé**: token cho một tập trên một thiết bị, hiệu lực 30 phút. Gửi hai giá trị thiết bị lấy từ Player SDK, `viewer_ref` ổn định của riêng bạn, và `"intent": "play"` khi người xem bắt đầu một tập. Một lần gọi trả về tối đa 3 vé (tập đó và hai tập kế). Dùng lại vé cho cùng người xem, thiết bị và tập là được, cho tới 5 phút trước khi vé hết hạn.

## Khi nào xin vé
- **Khi người xem bắt đầu một tập**: luôn có một request với `"intent": "play"` cho tập đó. Chính request này được tính là lượt xem.
- **Xin trước** cho một hoặc hai tập kế, với `"intent": "prefetch"`, trong cùng request (`next` hoặc `episodes`). Prefetch không phải lượt xem.
- **Thêm một lần** khi Player SDK báo lỗi, kể cả khi vé hết hạn.

Không bao giờ xin vé cho cả bộ phim, và không bao giờ cho tập mà paywall của bạn chưa mở khoá: Wandura kiểm tra **giấy phép** của bạn, không kiểm tra người xem của bạn. Luật về thời hạn, dùng lại và lô nằm chung một chỗ: [Hành vi chuẩn của API · Vé playback](https://docs.wandura.ai/vi/v1/developers/reference/behaviour#tickets).

## Xác định tập
| Cách | Trường | Ví dụ |
|---|---|---|
| Theo episode ID | `episode_id` | `"episode_id": "b2a4d6e8-1c3f-4a5b-8d7e-9f0a1b2c3d4e"` |
| Theo phim và số tập | `film_ref` (film ID hoặc slug) và `episode_no` (số JSON, bắt đầu từ 1) | `"film_ref": "the-heiress-returns", "episode_no": 3` |

Nếu gửi cả hai, `episode_id` được dùng. Dùng cùng một cách mỗi lần cho cùng một tập: việc dùng lại vé so khớp tập đúng như bạn đã gửi.

## Gửi giá trị thiết bị
Các tập được cấp phép đều đã mã hoá, và chỉ Player SDK giải mã được. Trước mỗi request, app đọc hai giá trị từ SDK và gửi về backend của bạn:

| Trường | Là gì |
|---|---|
| `union_info` | Device ID do Player SDK tạo ra. |
| `drm_kek` | Device public key do Player SDK tạo ra. Private key tương ứng không bao giờ rời khỏi thiết bị. |

- Gửi cả hai hoặc không gửi cái nào. Chỉ gửi một: `400` `invalid_request`.
- Gửi đúng output thật của SDK. Một giá trị bị từ chối bằng `400` `invalid_device_info` khi, sau khi bỏ khoảng trắng hai đầu, nó ngắn hơn 8 ký tự hoặc là `(null)`, `null`, `nil`, `undefined`, `nan`, `<null>` hay `none` (hoa thường đều tính). `fields` nêu tên giá trị sai.
- Không gửi cái nào, với tập đã mã hoá: `409` `sdk_required`.

Cách đọc chúng trong app: [Player SDK](https://docs.wandura.ai/vi/v1/developers/player-sdk).

## Vé chứa gì
| Trường | Mô tả |
|---|---|
| `vid` | Video ID để tải trong Player SDK. |
| `play_auth_token` | Token cho Player SDK. Đây là secret: xem [Bảo mật](https://docs.wandura.ai/vi/v1/developers/concepts/security#tickets-and-links). |
| `expires_at` | Thời điểm hết hạn (ISO 8601, UTC). |
| `protection` | `private-encrypted` với các tập được cấp phép. |
| `subtitles` | Các file WebVTT, mỗi ngôn ngữ một file; có thể rỗng. Xem [Phụ đề](https://docs.wandura.ai/vi/v1/developers/guides/subtitles). |

**Kiểm tra `protection`** ở mọi vé. Chỉ bán tập trả phí khi giá trị là `private-encrypted`. Giá trị khác: đừng phát, và báo cho Wandura.

Một lô trả về `{ "items": [...] }` theo thứ tự trong request, mỗi phần tử có `status` riêng. Mọi trường: [Xin playback](https://docs.wandura.ai/vi/v1/developers/reference/playback).

## `intent`
| Giá trị | Ý nghĩa | Tính là lượt xem |
|---|---|---|
| `play` | Người xem vừa bắt đầu tập. | Có |
| `prefetch` | App tải tập trước. | Không |
| thiếu hoặc giá trị khác | Ghi nhận là không xác định, không bao giờ bị từ chối. | Có |

Khi gần chạm rate limit, vé `prefetch` bị từ chối trước (`429` kèm `"reason": "prefetch_shed"`) để vé `play` vẫn chạy đều. Prefetch bị từ chối không phải lỗi với người xem: xin lại tập đó với `"intent": "play"` khi người xem tới tập. Xem [Rate limit](https://docs.wandura.ai/vi/v1/developers/concepts/rate-limits#graceful-overload).

## `viewer_ref`
Một chuỗi opaque tuỳ chọn của bạn, tối đa 256 ký tự, được lưu cùng vé để tra cứu khi hỗ trợ. Hãy làm nó **ổn định theo người xem và thiết bị** và không chứa dữ liệu cá nhân, ví dụ HMAC của user ID cộng hash của install ID. Có nó, Wandura có thể trả lại đúng vé cũ cho request lặp lại thay vì cấp (và tính) vé mới. Quyền riêng tư và thời gian lưu: [Bảo mật](https://docs.wandura.ai/vi/v1/developers/concepts/security#viewer-data).

## Hết hạn và khôi phục
Vé sống 30 phút (`expires_at`). Khi Player SDK báo lỗi trong lúc phát, kể cả khi vé hết hạn:

1. Xin **một** vé mới và tải lại player tại vị trí hiện tại.
2. Nếu request đó thất bại, ánh xạ `error_code` của nó sang thông báo cho người xem ([Lỗi](https://docs.wandura.ai/vi/v1/developers/concepts/errors#viewer-messages)).

Đừng rẽ nhánh theo mã lỗi riêng của Player SDK (chúng đổi giữa các phiên bản) và không bao giờ retry theo vòng lặp. Khi app quay lại từ nền mà vé còn dưới một phút, hãy thay vé trước khi phát tiếp.

## Liên quan
- [Luồng playback và paywall](https://docs.wandura.ai/vi/v1/developers/guides/playback-flow): endpoint phía backend, kèm code.
- [Phát mượt khi tải cao](https://docs.wandura.ai/vi/v1/developers/guides/smooth-playback)
- [Xin playback](https://docs.wandura.ai/vi/v1/developers/reference/playback)
