A Player is one named unit of equipment: a lone speaker, a TV with its built-in speakers, a TV with a receiver. The spec selects the unit’s devices out of what the hardware operators publish, with the same CEL selectors a hand-written ResourceClaim would use. Between runs, the operator holds one claim on the unit’s display for the idle screen. It claims the other devices only while a Play runs on it.

The resource is namespaced, and everything a Player becomes is created in its namespace: the claims, the playback pod, and the Play that names it, so RBAC on the namespace covers the set.

apiVersion: media.liken.sh/v1alpha1
kind: Player
metadata:
  name: studio
  namespace: media
spec:
  zone: studio
  displayName: Studio Lab
  display:
    class: display
    displayName: Portable Screen
  render:
    class: gpu-render
  sinks:
    - class: audio-output
      displayName: Built-in Speakers
  remotes:
    - name: studio-gamepad
      displayName: Studio Controller
  idle:
    fadeAfterSeconds: 600

The class names here are the cluster’s own vocabulary: consumer DeviceClass objects are yours to create, and each hardware operator’s manual gives the YAML for its class.

A Player is one named unit of equipment. A Play names a Player to run media on it, and the media operator turns the Player into device claims: the display’s claim stands between runs, for the idle screen, and the other devices are claimed only while a Play runs.

spec

The devices that form the unit, each selected from what a hardware operator publishes. All of a Player’s devices must be reachable from one machine, because a Play becomes one pod and the scheduler places that pod on the machine that owns every claimed device. A Player whose devices span machines produces Plays that stay Pending. The spec must select a display or at least one sink; render alone plays nothing.

Field Type Required Description
zone string no The area this Player is in, such as living-room. A word for grouping and display; nothing acts on it yet.
displayName string no The human name of this unit, the one the idle screen and later ambient surfaces show in place of the object name, such as Studio Lab. Omit it, and the idle screen falls back to the object name.
display object no The display this Player shows video on. Omit it for an audio-only Player.
sinks []object no The audio outputs this Player plays sound through.
render object no The GPU render node the player program decodes and draws with. Omit it only for an audio-only Player; mpv needs a GPU to put video on a display.
control object no The panel’s DDC/CI control device, an opt-in. The idle pod holds it, and its sidecar darkens the panel after idle.offAfterSeconds and lights it on a press. A panel that refuses DDC/CI publishes no control device, so omit this there.
remotes []object no The controllers this unit owns, each naming a Remote in the same namespace. The Play’s pod builds one translator sidecar per entry.
audioLanguages []string no A per-Player override of the audio language order; omit it to inherit the default MediaPreferences.
subtitleLanguages []string no A per-Player override of the subtitle language order; omit it to inherit the default MediaPreferences.
subtitles string no A per-Player override of when subtitles show; omit it to inherit the default MediaPreferences. One of: on, off, auto.
idle object no This unit’s idle screen policy. Each field overrides the default MediaPreferences on its own.

spec.display

The display this Player shows video on. Omit it for an audio-only Player.

Field Type Required Description
class string yes The DeviceClass the claim allocates through. Consumer classes are the cluster owner’s vocabulary; each hardware operator’s manual gives the YAML for its class.
displayName string no The human name of this selection, the one the idle screen shows in its parts list, such as Portable Screen. Omit it, and the idle screen falls back to the DeviceClass name.
selector string no A CEL expression over device.attributes, the same expression a hand-written claim would carry. Omitted, the class alone chooses, which fits a class that already names one kind of device.
parameters object no Opaque configuration for the driver that prepares the device, carried onto the claim unread. The display operator’s manual documents its parameters, such as mode and brightness.

spec.display.parameters

Opaque configuration for the driver that prepares the device, carried onto the claim unread. The display operator’s manual documents its parameters, such as mode and brightness.

Field Type Required Description
driver string yes The driver the parameters are for, such as display.liken.sh.
values object no The parameters themselves. The driver defines them; this operator carries them.

spec.sinks[]

The audio outputs this Player plays sound through.

Field Type Required Description
class string yes The DeviceClass the claim allocates through. Consumer classes are the cluster owner’s vocabulary; each hardware operator’s manual gives the YAML for its class.
displayName string no The human name of this selection, the one the idle screen shows in its parts list, such as Built-in Speakers. Omit it, and the idle screen falls back to the DeviceClass name.
selector string no A CEL expression over device.attributes, the same expression a hand-written claim would carry.
parameters object no Opaque configuration for the driver that prepares the device, carried onto the claim unread. The audio operator’s manual documents its parameters, such as codec.

spec.sinks[].parameters

Opaque configuration for the driver that prepares the device, carried onto the claim unread. The audio operator’s manual documents its parameters, such as codec.

Field Type Required Description
driver string yes The driver the parameters are for, such as audio.liken.sh.
values object no The parameters themselves. The driver defines them; this operator carries them.

spec.render

The GPU render node the player program decodes and draws with. Omit it only for an audio-only Player; mpv needs a GPU to put video on a display.

Field Type Required Description
class string yes The DeviceClass the claim allocates through. A render class usually needs no selector, because it already names one kind of device.
displayName string no The human name of this selection, the one the idle screen shows in its parts list. Omit it, and the idle screen falls back to the DeviceClass name.
selector string no A CEL expression over device.attributes, for a machine with more than one GPU.

spec.control

The panel’s DDC/CI control device, an opt-in. The idle pod holds it, and its sidecar darkens the panel after idle.offAfterSeconds and lights it on a press. A panel that refuses DDC/CI publishes no control device, so omit this there.

Field Type Required Description
class string yes The cluster’s control DeviceClass. A constraint ties the device to the display selection’s own panel, so no selector is needed to name the screen twice.
displayName string no The human name of this selection, shown where the idle screen lists the unit’s parts.
selector string no A CEL expression over device.attributes that narrows which control device the claim takes. Usually omitted: the constraint already ties it to the display’s panel.
parameters object no Opaque driver configuration for the control selection, passed through on the claim.

spec.control.parameters

Opaque driver configuration for the control selection, passed through on the claim.

Field Type Required Description
driver string yes The driver these parameters are for.
values object no The parameters themselves, defined by the driver.

spec.remotes[]

The controllers this unit owns, each naming a Remote in the same namespace. The Play’s pod builds one translator sidecar per entry.

Field Type Required Description
name string yes The Remote this unit owns, by name, in this namespace.
displayName string no The human name of this controller, the one the idle screen shows in its parts list, such as Studio Dualsense Controller. Omit it, and the idle screen falls back to name.
keymap string no A per-unit Keymap override for this controller on this unit, by name. Set it, and this controller maps this way here and its Remote’s own way elsewhere. Empty, the unit reads the Remote’s own Keymap.

spec.idle

This unit’s idle screen policy. Each field overrides the default MediaPreferences on its own.

Field Type Required Description
fadeAfterSeconds integer no Seconds of quiet before the idle screen fades to black. Zero disables the automatic fade; omit it to inherit the default MediaPreferences.
offAfterSeconds integer no Seconds of quiet before the panel itself goes dark, at least fadeAfterSeconds. Zero or unset means the panel never goes dark on its own. It acts only on a Player that states a control device.
offMode string no What the off window writes: backlight, the default, writes the backlight to zero and always wakes over DDC; power writes DPM off, which is deeper and which some panels never answer from. State power only for a panel that woke from it in a drill. One of: backlight, power.

status

What plays on this Player now, written only by the media operator. It is derived from the Plays that name the Player, so it is empty until one does.

Field Type Required Description
activity string no Whether the Player performs a run now. Playing is a Play running on it, Starting is a Play whose pod has not begun, and Idle is no Play at all. One of: Playing, Starting, Idle.
play string no The name of the Play on this Player, in the same namespace. Empty while the Player is Idle.
panel string no The panel state the idle sidecar last actuated: On, BacklightOff, Off, or Unresponsive. Empty until a sidecar with a control device reports one.

On the bus

The players tree describes the equipment, with or without a running Play. See the media bus for the rules every topic follows.

Topic Writer Retained Carries
players/{namespace}/{name}/status the operator yes the unit’s name, activity, and parts
players/{namespace}/{name}/volume the operator and the pods yes the listening level
players/{namespace}/{name}/panel the idle pod yes the panel state
players/{namespace}/{name}/commands the operator no a command for the idle pod

status

What a screen would show about one unit: its name, what it is doing, the Play it runs, and its parts with the presence of each. The operator is the only writer, so an idle pod that just started draws the live state the broker already holds, with no request to the operator.

{
  "displayName": "Studio Lab",
  "activity": "Playing",
  "play": {"name": "dune", "title": "Dune"},
  "components": [
    {"name": "Portable Screen", "kind": "display"},
    {"name": "Built-in Speakers", "kind": "sink"},
    {"name": "Studio Controller", "kind": "remote", "connected": true}
  ]
}

activity is the same word the Kubernetes status carries. play is present while a run starts or plays: name is the object a person finds with kubectl, and title is the one line a screen draws. A component’s kind is display, sink, or remote, and only a remote carries connected, because a wired screen reports no presence.

volume

The unit’s listening level and its muted flag:

{"level": 40, "muted": false}

Both fields are always written, so a reader never needs a default for a missing key. The level runs 0 to 100, and 100 is unity, the player’s own default and the cap. Every pod for the unit subscribes and applies what it reads, so the unit plays at the one level the topic holds. The operator writes it when it seeds a unit or applies a Play’s starting volume, and the pod that handles a volume or mute press writes the result back. A published level outside 0 to 100 is clamped to the range.

panel

What the idle sidecar last actuated on the unit’s screen:

{"state": "On"}

The states are the four the Player status carries: On, BacklightOff, Off, and Unresponsive. The sidecar holds no API credentials, so the operator folds this topic into status.panel.

commands

The operator’s channel to the Player’s idle pod. It carries {"action": "re-present"} when a Play ends, and the idle sidecar recreates the idle surface. A controller never sends it, and it carries none of the media actions a Play’s commands topic accepts.