Rindle.Streaming.Provider behaviour (Rindle v0.1.5)

Copy Markdown View Source

Behaviour contract for streaming providers (Phase 33 — promoted from v1.4 reserved shim).

A streaming provider implements asset-CRUD + signed-playback-URL + webhook-verify against an external streaming service (e.g. Mux). The dispatch surface that decides whether to call into a provider lives on Rindle.Delivery.streaming_url/3, NOT on this behaviour.

Security invariant 14

Implementations MUST NOT expose provider_asset_id (or any provider-internal identifier) in adopter-facing URLs, telemetry metadata, log lines, or inspect/2 output. Only the public-side playback_id (or its provider equivalent) crosses into URLs. Custom Inspect impls on persistence rows enforce redaction at the schema layer (see Rindle.Domain.MediaProviderAsset).

Callback discipline

Every callback returns an :ok-tuple or :error-tuple. No raises on the happy path. verify_webhook/3 returns a normalized provider_event map — provider structs (e.g. Mux structs) MUST NOT cross this boundary.

Summary

Types

Capability atom advertised by capabilities/0. Closed vocabulary lives in Rindle.Streaming.Capabilities.

Public-side playback identifier. Safe for URL embedding.

Provider-internal asset identifier. Treated as a secret; never exposed in adopter-facing paths.

Normalized webhook event surface. Provider-specific structs MUST be normalized into this shape by verify_webhook/3 before crossing into core.

Locked finite-state-machine vocabulary for media_provider_assets.state.

Callbacks

Capabilities advertised by this provider. Filtered against Rindle.Streaming.Capabilities.known/0 by safe/1.

Create a provider-side asset for source_url under profile. Returns the provider-internal identifier and (when known) initial playback ids.

OPTIONAL: Mint a direct-creator upload URL the browser can PUT to. Reserved for Phase 37 / v1.7; no v1.6 adapter implements this callback.

Delete the provider-side asset. Idempotent on :not_found.

Fetch the current provider-side state for provider_asset_id.

Mint a signed playback URL for playback_id under profile. Implementations MUST respect the profile's signed_url_ttl_seconds policy (no hidden defaults). Returns the v1.4-stable shape %{url, kind, mime}.

Verify a raw webhook payload against secrets. Returns a normalized provider_event map on success — provider-specific structs MUST be normalized before returning.

Types

capability()

@type capability() ::
  :signed_playback
  | :public_playback
  | :webhook_ingest
  | :server_push_ingest
  | :direct_creator_upload

Capability atom advertised by capabilities/0. Closed vocabulary lives in Rindle.Streaming.Capabilities.

playback_id()

@type playback_id() :: String.t()

Public-side playback identifier. Safe for URL embedding.

provider_asset_id()

@type provider_asset_id() :: String.t()

Provider-internal asset identifier. Treated as a secret; never exposed in adopter-facing paths.

provider_event()

@type provider_event() :: %{
  :type => atom(),
  :provider_asset_id => provider_asset_id() | nil,
  :playback_ids => [playback_id()],
  :state => provider_state() | nil,
  :occurred_at => DateTime.t() | nil,
  :raw => map(),
  optional(:upload_id) => String.t() | nil
}

Normalized webhook event surface. Provider-specific structs MUST be normalized into this shape by verify_webhook/3 before crossing into core.

state is nil when the webhook payload carries no recognized status (e.g. lifecycle events like video.asset.created that pre-date transcoding).

:upload_id is OPTIONAL and populated only by adapter typed branches that carry both an upload id and a provider asset id (e.g. Mux's video.upload.asset_created — see D-29 / D-30, added in Phase 35 as forward-compat for Phase 37 / direct-creator-upload).

provider_state()

@type provider_state() :: String.t()

Locked finite-state-machine vocabulary for media_provider_assets.state.

BL-04 alignment: the schema column is :string (see Rindle.Domain.MediaProviderAsset.@states), the FSM keys are strings (Rindle.Domain.ProviderAssetFSM.@allowed_transitions), and adapter implementations return strings (e.g. Rindle.Streaming.Provider.Mux.normalize_state/1). This typespec mirrors that surface — the closed set lives at the schema layer, not in the type system. Adopters MUST treat values as one of:

"pending" | "uploading" | "processing" | "ready" | "errored" | "deleted"

Callbacks

capabilities()

@callback capabilities() :: [capability()]

Capabilities advertised by this provider. Filtered against Rindle.Streaming.Capabilities.known/0 by safe/1.

create_asset(profile, source_url, opts)

@callback create_asset(profile :: module(), source_url :: String.t(), opts :: keyword()) ::
  {:ok,
   %{provider_asset_id: provider_asset_id(), playback_ids: [playback_id()]}}
  | {:error, term()}

Create a provider-side asset for source_url under profile. Returns the provider-internal identifier and (when known) initial playback ids.

create_direct_upload(profile, opts)

(optional)
@callback create_direct_upload(profile :: module(), opts :: keyword()) ::
  {:ok,
   %{
     upload_url: String.t(),
     upload_id: String.t(),
     provider_asset_id: provider_asset_id() | nil
   }}
  | {:error, term()}

OPTIONAL: Mint a direct-creator upload URL the browser can PUT to. Reserved for Phase 37 / v1.7; no v1.6 adapter implements this callback.

delete_asset(provider_asset_id)

@callback delete_asset(provider_asset_id()) :: :ok | {:error, term()}

Delete the provider-side asset. Idempotent on :not_found.

get_asset(provider_asset_id)

@callback get_asset(provider_asset_id()) ::
  {:ok, %{state: provider_state(), playback_ids: [playback_id()], raw: map()}}
  | {:error, term()}

Fetch the current provider-side state for provider_asset_id.

signed_playback_url(profile, playback_id, opts)

@callback signed_playback_url(profile :: module(), playback_id(), opts :: keyword()) ::
  {:ok, %{url: String.t(), kind: :hls, mime: String.t()}} | {:error, term()}

Mint a signed playback URL for playback_id under profile. Implementations MUST respect the profile's signed_url_ttl_seconds policy (no hidden defaults). Returns the v1.4-stable shape %{url, kind, mime}.

verify_webhook(raw_body, headers, secrets)

@callback verify_webhook(raw_body :: binary(), headers :: map(), secrets :: [String.t()]) ::
  {:ok, provider_event()} | {:error, term()}

Verify a raw webhook payload against secrets. Returns a normalized provider_event map on success — provider-specific structs MUST be normalized before returning.