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.