# DCP-Cine Player: handoff to Cinema Catcher and other TMS developers

13 October 2026. Implementation build **4.2.18 beta**. See the [device metadata handoff](DCP_CINE_PLAYER_DEVICE_INFO_HANDOFF.md) for identity, running components and explicit screen declarations. This extends the installed 4.2.14 API. Read [the implementation specification](DCP_CINE_PLAYER_TMS_PROGRAMMING_API_SPEC.md), [qualification](tms-qualification.md) and [build manifest](tms-build-manifest.json). The signed Debian package is published through the distribution server; installation and commissioning on `10.30.1.71` are separate operations. Query the actual player's version and capabilities before using additions.

## Programming sequence

1. Authenticate using existing `/api/auth/login` and its session cookie, or the configured bearer/API key. Read `/api/player/v1/capabilities`, `/identity`, `/status`, `/ingest/sources` and `/ingest/staging`.
2. Render the entire SPL, with actual CPL metadata and finite BLACK/live durations. Save `/shows` using `validation_mode:"deferred"` when negotiated and the operator programs with dependency warnings. Persist returned show ID/revision and every occurrence ID. Default strict mode still rejects missing CPLs/cues; an unknown validation mode is an error.
3. Read the global `/schedule` ETag. Book with `/schedule`, or atomically replace an owned window with `PUT /schedule` and that ETag. Send owner application/installation/session/render/correlation metadata. Persist exact booking IDs. Missing dependencies produce `ready:false`; they do not erase the booking.
4. Independently queue every unique missing package with `/ingest/remote`, using the player-visible commissioned source and package-relative path. CPLs in the same package can share one job. Different packages need separate jobs. Keep successful booking and ingest results when another operation fails.
5. Poll `/jobs/{id}`. Remote native jobs cover transfer **and integrity/indexing**. Re-read CPL completeness and validate the same show/booking after content or key arrival. Completed ingest does not mean a future KDM is usable.
6. Call `/shows/{id}/validate` for each booked start. Optionally queue `/shows/{id}/preflight` with the saved revision and poll its job. `state:completed` means the check finished; inspect `result.ready`, `result.readiness`, `result.issues` and current-time facts. Future encrypted qualification remains pending where the native engine cannot prove future key usability. Never display “Good to go” for pending/unknown checks.
7. After render changes, save a new show and conditionally replace only eligible owned future bookings, retaining their IDs. A duration mismatch requires rerendering; arrival must not silently change durations, cues or neighbouring bookings.
8. Collect operational audit with the native NDJSON request/poll/download contract. Actual playback and cue outcomes are separate from programming success. Respect per-screen enablement and retain one player's failure independently of others.

## Native API audit and implemented messages

Paths below are relative to `/api/player/v1`. Existing fields remain; additions are negotiated. Administrator source configuration is exposed natively. Ordinary programming, transfer, monitoring and recovery require no UI-only call.

| Area | Calls | Delivered behaviour |
| --- | --- | --- |
| Discovery | `GET /capabilities`, `/documentation`, `/schemas/{name}`, `/tms/specification`, `/tms/handoff` | Version/licence/role-dependent flags; complete downloadable contracts |
| Status and identity | `GET /status`, `/identity`, `/identity/recipient-certificate` | Fresh scheduler, job totals, space, trusted-time health and recipient identity |
| Inventory | `GET /content/cpls`, `/content/cpls/{id}`, `/content/kdms`, `/content/kdms/{id}`, `/content/packages` | Individual metadata, CPL dependencies/use, UUID filters, optional snapshot pagination |
| Keys | `POST /content/kdms`, `DELETE /content/kdms/{id}` | Raw XML up to 2 MiB, retained digest/identity, durable import; protected deletion; no decrypted key API |
| Integrity/deletion | `POST /content/cpls/{id}/check`, `DELETE /content/packages/{id}` | Durable jobs; shared assets/active/future protection; per-CPL physical deletion is not advertised |
| Show publication | `GET /shows`, `/shows/{id}`, `POST /shows`, `DELETE /shows/{id}` | Strict/deferred, exact occurrences and declared metadata, persistence/readiness, conditional edits and active/scheduled protection |
| Qualification | `POST /shows/{id}/validate`, `/shows/{id}/preflight` | Every item interval; quick checks versus asynchronous current-time native facts/future limitations |
| Booking | `GET /schedule`, `POST /schedule`, `PUT /schedule`, `PATCH /schedule/{id}`, `DELETE /schedule/{id}` | Global ETag, atomic owned replacement, stable moves, readiness and explicit authorized overlaps |
| Scheduler | `GET /scheduler`, `PUT /scheduler` | Authoritative enabled state and established manual interlocks |
| Automation | `GET /automation/cues`, `/automation/macros`; `POST /automation/cues/ensure`; `POST /automation/{cues|macros}/{id}/execute` | Actual names/IDs/types and configured/empty flags; explicit default creation, durable request replay, audit outcomes |
| Transport | `POST /playback/select`, `/play`, `/pause`, `/resume`, `/stop`, `/seek`, `/previous`, `/next`, `/jump`, `/unload`; `GET /commands/{id}` | Existing accepted/terminal command contract, occurrence-based jump and capability/interlock checks |
| Ingest | `POST /ingest/local`, `/ingest/remote`; `GET /ingest/sources`, `/ingest/staging` | Independent push and pull; prompt durable queue acceptance, protected/redacted source configuration |
| Commissioning | `POST /ingest/sources`, `PUT /ingest/sources/{id}`, `DELETE /ingest/sources/{id}` | Admin-only source management; existing queued jobs keep frozen credentials |
| Jobs | `GET /jobs`, `/jobs/{id}`; `POST /jobs/{id}/cancel`, `/retry`, `/priority` | Filtered fenced pages, active totals, cancellation confirmation, linked retries, conditional queued priority |
| Reconciliation | `GET /requests/{idempotency_key}` | Caller-scoped digest/state/redacted recorded result; pending versus interrupted/indeterminate claims |
| Audit | `POST /audit/exports`, `GET /audit/exports/{id}`, `/documents/{document_id}`, `/audit/journal`, `/audit/schemas/{name}`, `/audit/documentation`; `PUT /audit/retention` | Durable identities, range/sequence fences, asynchronous immutable authenticated downloads and health/retention |

Machine contracts: `player-api.openapi.json`, `tms-programming.schema.json`, `audit-api.openapi.json`, `audit-event.schema.json`, `audit-export.schema.json`. The first two use `/schemas`; audit contracts also retain `/audit/schemas`.

## Scheduler and transport

In 4.2.15, native manual `/playback/*` requests return 409 `SCHEDULER_LOCKED` while scheduling is enabled. Barco Select/Play/Pause/Resume/Stop/Clear return a nonzero result in Scheduled mode. Programming, ingest, status and validation remain available. An authorized operator must explicitly switch to manual mode before ordinary transport; the TMS must not toggle the scheduler merely to test or bypass the interlock. Barco's explicitly named EmergencyStop remains available as the emergency exception. The internal scheduler starts performances independently and never runs two shows concurrently. This supersedes older extended-control documentation that permitted TMS controls while scheduling remained enabled.

## Show and cue payload

The test CPL in `audit-fixtures/test-dcp.zip` is five seconds, 120 frames at 24/1. This example can be saved before that harmless CPL is ingested. It is **not** a duration claim for either incident trailer/advert.

```json
{
  "name": "Catcher qualification",
  "validation_mode": "deferred",
  "owner_application": "cinema-catcher",
  "owner_installation_id": "cinema-installation-uuid",
  "external_session_id": "qualification-session-1",
  "render_hash": "hash-of-the-complete-render",
  "correlation_id": "publish-operation-1",
  "items": [
    {
      "kind": "cpl",
      "cpl_id": "b315fa99-8732-4b98-afb1-014ab679b2b3",
      "content_title_text": "D-Cine harmless audit test",
      "content_kind": "test",
      "duration_frames": 120,
      "edit_rate_num": 24,
      "edit_rate_den": 1,
      "duration_in_milliseconds": 5000,
      "cues": []
    }
  ]
}
```

Each returned CPL occurrence includes `resolved_catalogue_metadata` when present, separately from the declared title, frame/rate and duration. Catalogue enrichment does not rewrite the programmed duration or revision.

Creation omits show ID. Updating requires its existing ID and `If-Match` revision. Do not send filesystem paths/package roots or resolved read-only metadata as declarations. The returned `ownership` object contains the persisted fields and verified `owner_principal`. Fields are supplied flat in mutation bodies; each is at most 256 characters. Owner application and installation must be paired.

BLACK uses `kind:"black"` and positive `duration_in_milliseconds`; event streams additionally require `event_stream_id`. SDI uses the configured player input, with optional `source_id:"configured_sdi"`. An unknown SDI source can be retained in deferred mode but blocks readiness. Live availability at the actual future performance is always an explicit warning, not a guarantee. Maximum show duration is one year.

Cue shape: `name`, `target_type:"cue"|"macro"`, exact `target_name`, `reference:"start"|"end"`, `offset_in_milliseconds`. Start offsets run after item start; end offsets run before item end. End 60000 means 60 seconds before end. Without reference, retain legacy signed offsets. Bounds include both item endpoints. Missing or ambiguous targets warn and are skipped with a failed audit event; they do not block media playback, including when `optional` is false or omitted. Legacy target IDs resolve by ID before the runtime name fallback. Empty configured cues succeed as `no_action`; dispatched devices without feedback are `unverified`. Empty defaults are created only by the explicit ensure operation/button.

Readiness includes all dependency issues with zero-based `item_index`, stable `item_id`, CPL UUID/title, cue name, severity, message and remediation. `DURATION_MISMATCH` includes declared/resolved milliseconds. The declared timeline and revision remain unchanged after arrival. Generation hashes identify catalogue, keys, automation, recipient and clock health. There is no reusable qualification cache; a change during deeper checks returns pending/stale and playback rechecks dependencies.

## Booking and conflicts

`POST /schedule` takes `playlist_id`, `start_at_utc` and flat ownership metadata; omit booking ID. Use explicit RFC3339 timezone; offsets normalize to UTC and responses use UTC. Times must satisfy `schedule.grid_minutes` (default five minutes), including fractional seconds. Duration comes from the saved programmed show.

`PUT /schedule` requires `from`, `through`, ordered `items`, global `If-Match`, and top-level `owner_application`/`owner_installation_id` to scope Catcher replacement. Items have `id` when retaining/moving a booking, `playlist_id`, `start_at_utc` and applicable ownership/session/render metadata. The window selects booking starts; outside bookings crossing its boundary still participate in conflicts. Other owners/manual bookings within a scoped window are retained. Do not include them as Catcher-owned items.

`PATCH /schedule/{id}` moves the same booking with `start_at_utc`; its metadata/lifecycle is preserved. It does not change the referenced show; use conditional window replacement for a new rendered show. Pass `If-Match` on moves/removals as well. For deletion of an owned show or booking, supply `owner_application` and `owner_installation_id` as query parameters matching the stored owner, plus the relevant ETag. A local schedule edit clears ownership and changes the revision; it cannot be reclaimed by name/time.

Default overlaps return 409 `SCHEDULE_CONFLICT` with affected entries. Explicit `conflict_policy:"allow"` and a 1–1024-character `override_reason` require `schedule.override` (manager/admin; separately checked from schedule.edit). Persisted `override_by` is the authenticated principal. Allow bypasses overlap refusal only; stale revisions, invalid data, unknown ownership, active/processed entries and licensing still fail. Returned conflicts identify booking IDs, show IDs and intervals. The response carries the new global revision.

Runtime is deterministic: UTC start then booking ID; skip if another performance is active; never interrupt it automatically. More than one minute late is skipped. Refusals are durable/audited. The whole programmed show is checked at start; unavailable CPLs are never silently removed or replaced by BLACK.

## Native remote ingest and commissioning

Preferred request:

```json
{
  "asset_type": "dcp",
  "source_id": "returned-commissioned-source-id",
  "relative_path": "all/Harmless_Test_DCP/",
  "cpl_ids": ["b315fa99-8732-4b98-afb1-014ab679b2b3"],
  "label": "Qualification content",
  "priority": 50
}
```

Alternatively replace source_id/relative_path with `external_storage_url:"ftp://cinema-storage-host:21/all/Package/"` and optional `source_credentials:{"username":"…","password":"…"}`. URLs must have no userinfo/query/fragment. Source forms are exclusive; no path traversal, CR/LF/NUL or backslash. The first native pull transport is FTP/DCP only. Non-admin request-scoped endpoints/roots must match a commissioned source. Additional protocols and source scan jobs are not advertised.

Send `Idempotency-Key`; 202 has `job_id`, `state`, `status_url` and `coalesced`. An equivalent active source/package/CPL-set request returns the same job. Source configuration is frozen and encrypted for restart even if its profile changes. Job polling never exposes credentials or secret references. A linked retry retains the source-manifest fence and partial transfer. Changed metadata fails `SOURCE_CHANGED`; it needs a deliberate new source operation, not an automatic retry with changed arguments. Shared verified assets are reused. Transfer and integrity phases remain separate in status; an imported VF can still be incomplete until its OV arrives. Re-read requested CPLs after completed ingest.

Administrators can create a source natively with name, host, port, username, password, base_path and asset_type; use the returned source ID. For push staging, authenticate an actual FTP session, upload a small disposable file to the advertised FTP-visible root, and verify its corresponding **player-local** path/permissions. Configure Catcher ftp_host/port/credentials/ftp_path/dcp_staging_path from that verified mapping. A reachable TCP 2121 alone proves none of these. Check space, complete package bytes, `/ingest/local`, terminal job and `complete:true` for every CPL/dependency. Never send a Catcher-local path to the player-local endpoint.

## Polling, recovery and inventory

`GET /jobs?kind=…&state=…&active=true&limit=100&cursor=…` supports limits 1–1000 and opaque continuation. Response has items, next_cursor, complete, sampled_at_utc and aggregate totals. Order is creation time/ID descending. The cursor freezes the creation fence and filters; states remain live, so start a fresh active query each monitoring cycle and inspect totals. Do not interpret a page's empty items as an idle queue or change filters mid-pagination. Native states are queued/running/completed/failed/cancelled, plus cancelling during a running cancellation. Terminal display normalizes stored succeeded to completed.

Jobs expose phase, bytes (null when unknown), priority, queue_position (null outside queue), timestamps, result/code/retryable, retry_of and a job-control revision/ETag. Cancel acknowledgment is not proof a running transfer stopped. Retry accepts failed/cancelled controlled kinds, returns a new linked ID, and preserves original evidence. Priority is 0–100, higher first; requires the current job ETag and queued state. Supported controlled kinds are enumerated in capabilities; package deletion and arbitrary hardware/transport jobs cannot be retried/cancelled through these controls. Deletion is a distinct permission-controlled operation.

Jobs/requests have persistent retention. Explicit administrator terminal-history pruning leaves a tombstone: native GET returns 410 `JOB_EXPIRED`; unknown IDs return 404. Cancelling/queued/running jobs are not history. `GET /requests/{URL-encoded-key}` is principal-scoped and returns digest/state/status/redacted result. A current claim is pending; a process interrupted before recording its response is indeterminate. Reconcile read-back/resources before changing keys. Exactly repeated failed keys replay the old failure even after repair.

CPL/KDM/package lists preserve unpaginated legacy access. Supplying limit creates a one-hour immutable, principal-scoped inventory snapshot; continue with next_cursor and identical filters. UUID/cpl_id/message_id filters apply where relevant. Expired snapshots return 409 `INVENTORY_SNAPSHOT_EXPIRED`; restart the list. Individual KDM detail contains identity, recipient, windows, state/error and digest, never decrypted keys.

Package deletion protects active/booked CPLs and shared dependent assets, reports affected IDs, queues physical removal, and rechecks protection in the worker. It refuses unmanaged/root paths. Individual CPL deletion is unsupported because shared package ownership cannot be safely inferred from one row. KDM deletion conservatively protects any active/booked CPL it covers.

## Barco compatibility and limits

The bundled vendor SMS 1.x WSDL is the wire authority, with **53 advertised operations** including existing show/schedule/catalogue/transport and the added ingest queue/check/audit operations. `GET /sms_1?wsdl` has actual schemas and SOAP 1.1/1.2 bindings. Operations require Login session or native bearer authorization and their permissions; enable emulation explicitly. Native targets remain type dcp-cine-player/dcp-cine/catcher-player and use the native API.

`AddIngestJob` uses jobToAdd fields **Id, Title, ExternalStorageUrl, ItemId, ItemType**, returning unsigned AddIngestJobResult and jobId. Only ItemType CPL is implemented for pull; use AddKdm for keys. Legacy URL userinfo is translated directly into protected credentials and never echoed. AddIngestJobs queues independent jobs; a later failure does not roll back earlier accepted jobs. Reconcile GetIngestJobList and reuse the original per-job Id after an ambiguous/bulk failure. GetIngestJobList includes the durable native job; AbortIngestJobs requests cancellation; SwitchToIngestJob prioritizes queued work without preemption; BlockIngest/UnblockIngest persist queue pause. GetCplCheckOptions/StartCplCheck/CancelCplCheck/GetCplCheckStatus expose integrity checks. Unsupported options/hardware/types fail explicitly. SOAP AddShow remains strict; the deferred extension is native only.

For older clients requiring vendor ports, configure the service environment (private local/systemd environment file):

```ini
DCP_PLAYER_BARCO_HTTPS_ADDR=0.0.0.0:43758
DCP_PLAYER_BARCO_TLS_CERT=/etc/dcp-player/barco-server.crt
DCP_PLAYER_BARCO_TLS_KEY=/etc/dcp-player/barco-server.key
DCP_PLAYER_BARCO_FTP_ADDR=0.0.0.0:43750
DCP_PLAYER_BARCO_FTP_USERNAME=<dedicated SOAP account name>
DCP_PLAYER_BARCO_FTP_PASSWORD=<configured legacy export password>
DCP_PLAYER_BARCO_EXPORT_DIR=/opt/dcp-player-app/db/barco-audit
```

No passwords are assigned by this document. Commission TLS trust, matching SOAP account name, explicit FTP credentials and passive data connectivity. The FTP listener currently selects IPv4 passive data ports from the OS ephemeral range on the control interface; permit that range on the cinema network. Fixed passive port ranges and NAT address translation are not advertised. FTP is read-only and separate from staging. The fixed-credential legacy Catcher adapter needs configuration changes if it cannot use the commissioned account. These listeners are opt-in; simply enabling SOAP on 8080 does not satisfy a fixed-port HTTPS/FTP client.

ExportSecureLog(startDate,endDate) returns zero for queued acceptance, not completion. GetSecureLogExportStatus has unsigned result and **State, Error, StartTime, EndTime, DocumentCount**, in that order. States: None/Running/Completed/Failed/Canceled. Errors: 0 success, 1 export/storage/coverage failure, 2 competing transaction busy, 3 expired status. Native range restrictions apply (maximum 24 elapsed hours, no unavailable history/future end).

Completed FTP files are `smseclogs_0.xml`, `smseclogs_1.xml`, etc.; empty completion has zero files. XML uses the Catcher LogReport/LogRecordElement parser namespace and embeds **dcine:CanonicalJSON** to preserve every JSON field/identity/outcome. It is unsigned operational XML, not a qualified SMPTE security report. Current legacy Catcher import does not preserve the extension; prefer native NDJSON.

Only the configured authenticated account can export/download. The global transaction is busy until all documents are successfully downloaded, or expires; active FTP reads block replacement. Consumers sharing an account must serialize export → poll → download, including retries. Do not expect old global filenames/status to refer to an earlier transaction after starting a new one. Unknown/failed/expired/uncompleted files cannot be downloaded. Files/native journal remain independent. The delivered loopback tests cover TLS/SOAP login, asynchronous export, authenticated passive FTP, repeated bytes and competing access. Actual Catcher legacy-client, production TLS/firewall and multiple-file commissioning are separate qualification gates. `audit.barco_compatible_exports` and legacy hardware qualification remain false until that acceptance.

## Migration and outstanding qualification

Schema **11** extends the prior audit migration (schema 10) with schedule metadata, durable job controls, inventory snapshots and terminal-job tombstones. Playlist XML stores declared/rate/deferred/occurrence/ownership/optional-cue/live-source extensions. Existing shows retain legacy signed-offset behaviour and reload without reposting. Existing FTP passwords migrate into encrypted protected storage and list/update responses do not expose them. Back up database, `<database>.secrets/` including its key, SPL directory and audit identity anchor together. Losing the key makes frozen-source recovery fail visibly; do not replace it to “repair” jobs. Restart requeues supported interrupted work, completes pending cancellation, and resumes or explicitly fails native/legacy audit work.

The supplied test DCP and empty cue are harmless fixtures. Automated source/loopback tests are recorded in tms-qualification.md. Actual Catcher application changes are still client work: translation metadata, negotiated deferred/override fields, source commissioning, independent jobs, exact ownership/read-back, durable replay/reconciliation, readiness UI and full audit storage/import. The Catcher repository was not present here. Production encrypted show/key usability, live SDI, power-loss/storage-pressure, actual incident packages, and the real older TMS client must be qualified on the cinema installation. Do not advertise joint workflow qualification from route presence or these fixtures.


## 4.2.17 ingest monitoring and table update

See [the ingest/table handoff](DCP_CINE_PLAYER_INGEST_TABLES_HANDOFF.md) for safe additive job fields, paginated monitoring, native actions, phase counters, observed playback dates, schema 12 migration and qualification screenshots. Existing native and Barco wire paths remain compatible.
