Declare a Player

A Player is one unit of equipment: a screen, the outputs its sound goes to, the GPU that draws, and the controllers that drive it. This guide declares one for a machine that is connected to a television or a monitor, reads its status until the screen is present, and proves it with one Play. At the end, the unit shows its idle screen and a remote drives it.

You need:

1. Confirm the machine publishes the hardware

The scheduler places every pod of a Player on the one machine that owns all of its devices, so start with what that machine publishes. The display operator publishes one device per connector, and the audio operator one per output:

kubectl get resourceslice <node>-display.liken.sh -o yaml
kubectl get displays
kubectl get sinks

The monitor you want must appear as a Display, and the outputs you want as Sinks on that node. A connector with no monitor publishes with a disconnected taint, and a claim on it parks.

A monitor that is switched to another input sends no hotplug event, and the display operator polls nothing. If the Display for a connected monitor is missing, switch the monitor to this machine’s input, and it appears within seconds.

2. Write the Player

Declare the unit in the namespace its Remotes are in. Name each device by class, and narrow the class with a selector where the machine has more than one of a kind:

kubectl apply -f - <<EOF
apiVersion: media.liken.sh/v1alpha1
kind: Player
metadata:
  name: den
  namespace: media
spec:
  zone: den
  displayName: Den Television
  display:
    class: display-output
    displayName: Television
    selector: |
      has(device.attributes["display.liken.sh"].model) &&
      device.attributes["display.liken.sh"].model == "LG TV"
  render:
    class: gpu-render
  sinks:
    - class: audio-output
      displayName: Television Speakers
      selector: |
        has(device.attributes["audio.liken.sh"].sink) &&
        device.attributes["audio.liken.sh"].connectionType == "hdmi"
  remotes:
    - name: den-remote
      displayName: Den Remote
EOF

The selectors are the same CEL a hand-written claim would use, over the attributes each hardware operator publishes. Guard an attribute that comes from the monitor with has(), because it is absent on an empty connector, and a selector that reads a missing attribute fails the whole allocation. The display operator’s claim guide and the audio operator’s claim guide list the attributes and the selectors that survive a re-cabling.

render is required for video. mpv decodes and draws through the GPU, and a Player with a display and no render node plays nothing. Omit display and render together for a unit that plays sound alone. Players describes every field.

3. Read the status

The operator creates a persistent claim for the screen and a pod that draws the idle screen, named <player>-idle:

kubectl get player den
NAME   ZONE   ACTIVITY   PLAY   IDLE                          SCREEN    AGE
den    den    Idle              media.liken.sh/idle-screen    Present   40s

SCREEN is the reason of the Screen condition. Present means the screen’s Display reports a panel on its connector, and the idle screen is on it. The condition is absent until the claim resolves once, so an empty column right after the apply is normal. Read the whole status for the screen and the outputs the claim resolved to:

kubectl get player den -o jsonpath='{.status.screen}{"\n"}{.status.sinks}{"\n"}'

status.sinks fills after the first Play, because the operator reads the outputs from an allocated playback claim.

4. When the idle pod stays Pending

A Pending idle pod means the claim on the screen did not allocate. The Screen condition says why:

A selector that reads an attribute without has() fails on every device that lacks it. If every candidate lacks that attribute, the resulting claim matches no device.

A pod whose events say Insufficient memory or Insufficient cpu has a different cause: the machine that has the screen has too little memory or cpu left for the pod’s requests. Every pod of a Player must run on that machine, so the scheduler cannot place it on another one. At the defaults, the idle pod requests 96Mi of memory and 5m of cpu. A playback pod requests 592Mi and 100m in all: 432Mi and 80m for the player, 144Mi and 10m for the display, and 16Mi and 10m for the command sidecar. The idle pod keeps running under a film, so a machine must hold both pods at once: about 688Mi and 105m. Free memory on the machine, lower the requests as the install guide shows, or connect the screen to a machine with more.

kubectl describe pod den-idle

5. Prove it plays

Create a Play that names the unit, with one file the machine can reach:

kubectl apply -f - <<EOF
apiVersion: media.liken.sh/v1alpha1
kind: Play
metadata:
  name: den-check
  namespace: media
spec:
  players: [den]
  items:
    - uri: nfs://nas/media/movies/Example (2019)/Example.mkv
EOF

The Player’s ACTIVITY goes to Starting, then Playing, and the film is on the screen with its sound on the outputs you named. Press a button on the remote to confirm it drives the unit. Delete the Play to stop, and the idle screen returns:

kubectl delete play den-check

A Play that stays Starting has a pod that did not schedule, and its events name the device that did not allocate. A uri scheme the operator does not know fails the Play before any pod exists. Plays describes the item forms.

Choose what the screen does when idle

The operator’s own idle screen is the default. Three fields on spec.idle change it, and each one overrides the default MediaPreferences on its own:

Set the defaults once for every unit in the cluster’s MediaPreferences , and set a field on a Player only where that unit differs.