The media bus

The resources declare what should exist. The components exchange reports, commands, button events, and state through one MQTT broker. The bus has a defined interface, like the CRDs. This page gives the rules every topic follows and lists every topic under the media operator’s tree, with its writer, its readers, and its payload. Each resource page links here for the rules and gives the payload shapes for its own topics.

Two operators meet on the broker. The media operator, its playback pods, each Remote’s pod, and each idle pod connect to it. The library operator runs its own tree on the same broker. Your program can connect too. A phone app, a Home Assistant instance, and a library application all join the same way, with a plain MQTT client and no Kubernetes credentials.

The broker is deploy/bus.yaml: one Mosquitto Deployment with its Service and ConfigMap, all named bus, beside the operator’s Deployment and never inside it. The two are separate so the operator restarts without dropping a message: a button press reaches mpv while the operator is down. The broker holds no storage volume, so a broker restart loses only the retained set. Each connected program republishes the retained state it owns when its session reconnects, and the next report from each running Play refills the rest within seconds.

The topic base

Every topic extends one base. The media operator’s base is liken/media by default, and the library operator’s is liken/library. Each operator stores its base as one string and passes it to every pod it creates. A whole tree moves together when a cluster chooses another base. The pages in this reference write topics without the base.

Retained state and events

State is retained and events are not. The broker retains the current value for a state topic and sends it to new subscribers. Event topics deliver individual button presses, commands, and requests to move focus without retaining them.

An empty retained payload clears a topic. The writer of a retained topic clears it that way when the object it described is gone, and a reader treats an empty payload as a cleared value, not as a live message. The media operator ignores an empty payload on a status, an availability, a panel, or a volume topic for that reason. It publishes those clears itself, and reading one back as a signal would act on a run or a unit that no longer exists.

An operator clears the topics of an object it owns on a finalizer, so the object is never gone from the API server while its topics still stand on the broker. The media operator’s finalizer on a Play is media.liken.sh/bus-topics. A sweep on every pass is the backstop behind it: it clears the topics of any run the pass’s own list of Plays does not hold, which is what finds a run whose clear the broker never received.

The topic names the object

The topic identifies the object; the payload does not. A Play’s namespace and name are segments of its topic path, while its report body contains only playback numbers. Parse the topic path to identify which object a message belongs to.

Availability

Retained state outlives the pod that wrote it, so a reader needs a signal that the writer is gone. The playback pod and the Remote’s pod each name an availability topic in their tree as the MQTT Last Will, with offline as the payload, and publish online there once connected. Both messages are retained. When a pod dies without a clean disconnect, the broker publishes the will, so retained state a dead pod left behind does not read as live. A reader that folds state from one of these trees reads the availability topic beside it.

The playback pod adds a space and its own pod UID after the word, and it puts the same UID on each report. A run’s new pod has the old pod’s name, and the broker can publish the old pod’s Last Will after the new pod is online, so the UID is how a reader tells the two pods apart.

The trees

Two operators publish trees on the broker. Each tree’s page lists its topics, and the resource pages give the payloads.

Tree Owner What it carries Page
liken/media the media operator runs, units, and controllers: the table below this page
liken/library the library operator library reports, catalog availability, and play requests The library bus

No device operator connects to the bus. The media operator writes a unit’s asks for its level, its power, and its input into the Receiver or the Sink that serves the unit, and reads back what the device reports there.

The media tree

Every topic under liken/media. The writer column names every program that publishes on the topic, the clear included. The readers column names every program that subscribes. The payload column links to the section of the resource page that gives the shape.

Pattern Writer Readers Retained Payload
plays/{namespace}/{name}/commands any program the playback pod no one named command
plays/{namespace}/{name}/status the playback pod; the operator clears it the operator yes the run’s report
plays/{namespace}/{name}/availability the playback pod and its Last Will; the operator clears it the operator yes online or offline, and the pod’s UID
players/{namespace}/{name}/status the operator the idle pod, or a delegate’s client yes the unit’s name, activity, Play, parts, and power mode
players/{namespace}/{name}/volume the operator the playback pod, the idle pod or a delegate’s client, and the operator yes the level, the muted flag, and who draws the indicator
players/{namespace}/{name}/volume/commands any program that is not a remote the operator no an ask for a step or a mute
players/{namespace}/{name}/panel the idle pod, or a delegate’s client; the operator clears it the operator yes the panel desire
players/{namespace}/{name}/power the idle pod or a delegate’s client, and the operator the operator, and the idle pod or a delegate’s client no an ask for the room’s power, or for the screen
players/{namespace}/{name}/commands the playback pod the idle pod, or a delegate’s client no the ask for the next work, or for home
remotes/{namespace}/{name}/events the Remote’s pod the playback pod, the idle pod or a delegate’s client, and the operator no one key event
remotes/{namespace}/{name}/keys the operator the Remote’s pod yes the compiled key table
remotes/{namespace}/{name}/codes the Remote’s pod the operator yes the declared code set
remotes/{namespace}/{name}/availability the Remote’s pod and its Last Will the operator yes online or offline
remotes/{namespace}/{name}/focus the operator the playback pod, the idle pod or a delegate’s client, and the operator yes the name of a Player, or empty
remotes/{namespace}/{name}/focus/cycle the holder of focus the operator no empty

The players commands topic has one writer. The playback pod publishes play-next when a person takes the up-next offer on the scrubber, and the client that wrote the Play reads it and creates the next Play. It publishes home when a person presses home during a film, just before the Play ends, and the client under the film reads that ask as a press of the home key. It publishes power the same way when a person presses power during a film, and power-off when a person presses a TV remote’s Power Off Function. The client holds that ask until the Player’s status reads Idle, and then answers it the way it answers the same press between films.

“The playback pod” in this table is its command sidecar, the one container that connects to the bus. “The idle pod” is the idle screen client the media operator ships. A delegate’s client, under another controller , reads and writes the same players and remotes topics from status.idle.bus.

What the operator reads

The operator subscribes to eleven filters, one per retained or event kind it folds into a status or a decision:

Filter What the operator does with it
plays/+/+/status folds each report from the run’s current pod into the Play’s status
plays/+/+/availability gates a retained report on a live sidecar, from the run’s current pod only
remotes/+/+/focus reads its own marks back after a restart, and publishes again only a mark a restarted broker lost
remotes/+/+/focus/cycle advances the mark to the next bound Player
remotes/+/+/events moves the level for a volume key, and asks the unit’s Receiver for the unit’s input
remotes/+/+/availability gates the declared codes on a live pod
remotes/+/+/codes subtracts the key table and reports status.unbound
players/+/+/panel overrides the screen’s Display from the desire
players/+/+/volume reads its own levels back, so after a restart it publishes no level the broker already holds
players/+/+/volume/commands moves the level the same as a volume key
players/+/+/power writes each power ask into the unit’s Receiver

Most presses never pass through the operator. A key event travels from the Remote’s pod to the playback pod or the idle client directly, gated on the retained focus mark, so a controller keeps working while the operator is down. The volume keys, the power asks, and the input asks are the exceptions, because a device operator acts on them through the Receiver or the Sink. They do nothing while no operator holds the lease.