
# Put the media browser on a screen

The media browser is a native Wayland client that draws the namespace's
catalog on a screen, takes a remote's presses, and starts a `Play`.
It takes the place of `media-operator`'s idle screen on a `Player`.
This guide names it on a `Player` and describes what a person sees.

## 1. Hand the idle screen to the operator

A `Player`'s `spec.idle.controller` names the operator that draws its
screen when nothing plays. Set it to this operator's name:

    apiVersion: media.liken.sh/v1alpha1
    kind: Player
    metadata:
      name: living-room
      namespace: media
    spec:
      idle:
        controller: library.liken.sh/media-browser

`media-operator` resolves the name into `status.idle.controller`, and
this operator compares against that resolved value, never the spec.
The `Player` must be in the same namespace as the libraries it shows.
[Hand the idle screen to another controller](https://liken.sh/media/docs/guides/handing-the-idle-screen-to-another-controller/)
in the `media-operator` manual describes the delegation from its side.

## 2. What the operator runs

The operator creates a pod named `<player>-media-browser`, owned by
the `Player`, so deleting the `Player` deletes the pod. It has two
containers: the catalog agent as a native sidecar, and the browser.
The browser starts after the agent's startup probe passes.

The pod mounts every `Library` of the namespace read-only, whatever
the browser does. It mounts its own catalog claim and its own art
claim, where the browser keeps every piece of art it scaled. It uses
the existing `ResourceClaim` that `media-operator` holds for the
`Player`, so no second claim on the display is created.

    kubectl -n media get player living-room
    kubectl -n media get pod living-room-media-browser
    kubectl -n media logs living-room-media-browser -c browser

The browser's first log line reports the home page's open time:

    media-browser: the home page opened in 38.2 ms

After that, the browser prints one line for each press, each play
request, and each change the bus confirms. A line names a title by
its catalog id and never by its name, and it names a typed character
only as "a character":

    media-browser: press enter (KEY_OK): opened the movie page of movie:tmdb:1001 in media/films
    media-browser: play movie:tmdb:1001 in media/films from the start for person-a: sent a request for 1 file on liken/library/players/media/living-room/play
    media-browser: press enter (KEY_OK): asked to play movie:tmdb:1001
    media-browser: the player went from idle to starting

When the default `MediaPreferences` states `spec.timeZone`, the
browser draws its clock in that zone. Otherwise it runs on UTC.

## 3. How presses reach it

The `Player`'s `status.idle.bus` names the bus topics for the
`Player`'s remotes. The browser subscribes to each remote's events
and its retained focus mark, and answers a press only while the mark
names this `Player`. Starting a `Play` moves the mark to the `Play`'s
`Player`. A `Play` ending moves nothing, because the `Player` it names
is still there, showing its idle screen.

Over the bus, every key a remote sends reaches the browser under the
kernel's name, except volume, mute, and the cycle key. Those three are
handled before they reach the browser, and the browser draws the level
as a fading row. The level arrives on the `Player`'s volume topic. When
its payload carries `"indicator": "receiver"`, the receiver draws the
level on the TV, so the browser draws no row. A payload with no
`indicator` field means the browser draws the row. So a remote with a keyboard
types into search, and its home and search buttons work once a
`Keymap` names them. The same keys reach the browser from a keyboard
attached to the screen's machine.

## 4. The keys

| Key | Effect |
|---|---|
| arrows | move focus |
| enter | open the card, open an episode's row, or press a button or a mark of a title's page |
| back | close an episode's row, or pop to the screen before this one. On a row the page opened on, pop to the screen before this one |
| home | pop to the home page |
| search | open the search wall with the on-screen keyboard |
| a letter or a digit | open the search wall with that character typed |

Up from the top of any wall puts focus on the strip, the row across
the top that holds the clock and the search glass. Right from the last
column of a long wall puts focus on the rail. The rail is the bars at
the right edge, and they jump through the wall by year, decade,
letter, or season.
Enter on the rail's sort button cycles the wall's order.

## 5. The screens

The home page is the bottom of the stack. It draws a banner, a
continue-watching row, what was recently released and recently added,
a few strips chosen for the day, the libraries row, and every genre.
Selecting a library or a genre opens a wall of posters under a heading
with its count. A wall longer than eight rows draws the rail.

The continue-watching row belongs to the people at the screen. The
browser asks who is watching, and every play it requests records those
people. The row then reads the plays that named exactly them. A person
alone sees what they watched alone, and a family sees what the family
watched together. A night with one more person in the room still counts,
as long as that play continued from where the group had stopped. For
each series, set, and franchise those plays touch, the row offers the
next thing in its order, or the thing to resume. The card's second line
says why it is there: "Resume", "Next in" the series, the set, or the
franchise. A card that is next in a series or a set opens that title's
page. A card that is next in a franchise alone opens the franchise page
on that member.

A home card that names one episode opens the series' page with that
episode's row already open, and focus on its first button, Resume or
Play. The continue-watching row, the recently added row, and the banner
all open an episode this way, so the play is one press from the card.
Behind the row, the wall stands at that episode's season, with the
episode's still selected and scrolled into view, as if the person had
moved to the still and pressed select. Down from the row reaches the
marks, and down again lands on that still. Back from that row returns
to the home page in one press, because the wall under it was never on
the screen. Once focus leaves the row for the wall, back closes a row as
it does on any series page. A card that
names a series and no episode opens the page on its wall.

A play that stands at position 0 is not started. The row draws no card
for it, and its series, set, or franchise offers nothing after it, so
the whole container leaves the row. A cleared title writes that play,
and so does a title marked unplayed in Jellyfin. A play that starts
over and stops before its first position report reads the same way.

The answer to who is watching lasts until three hours pass with no
press. The browser keeps it on the bus, retained, so a screen pod that
restarts inside those hours draws the same room and asks nobody.
[The library bus](/docs/reference/bus/#who-is-watching) gives the
message.

A movie's page shows its art, its facts, its people, and the set or
franchise it is part of. Under the plot, the playback row holds Play,
or Resume and Start over for a film the people at the screen are in the
middle of, and Trailer where the film has a trailer file. Every button
of the row starts a play.

Under the playback row, a bar as wide as the text column shows how far
the people at the screen reached. Under the bar, the status line gives
where they stand, and the marks of
[section 6](#6-marking-a-title-watched-or-clearing-it) follow it:

| Where they stand | Status line |
|---|---|
| Not started | `Not started · 1h 39m` |
| In the middle of the film | `0:13:07 watched · 1h 26m remaining` |
| Finished | A check, then `Watched` |

The position has hours in a title an hour or longer, as in `0:13:07`,
and minutes and seconds in a shorter one, as in `21:40`. Down from the
playback row puts focus on the first mark, left and right move between
the marks, and up returns to the playback row. Down from the marks
moves on to the set strip, the franchise strips, or the cast. While a
mark has focus, the bar is twice as tall and the status is brighter.

A series' page shows its seasons as a wall of episode stills. Enter on
a still opens the episode's own row in the header, level with the
ratings line. The row holds the playback buttons of a movie's page
except Trailer, with the bar and the status line under it. Down from
the row puts focus on the marks, down from the marks returns to the
still, and back closes the row.

Pick up here follows the playback buttons on an episode's row. It marks
every earlier episode of the series watched and clears every later
episode, for the people at the screen, and plays the episode from the
beginning, as Play does, with the same next episode after it. The
earlier episodes are every episode before this one in season and
episode order, across seasons, and the later episodes are every episode
after it. The press marks the earlier ones the people at the screen
have not finished, a partly watched one included. It clears the later
ones they started or finished, as Clear progress on each row would, and
leaves the later ones they never started. It leaves the specials of
season 0 alone. The row shows the button only where the press changes
at least one episode: an earlier one is still to mark, or a later one
holds progress. So a special does not show it, and neither does an
episode with every earlier episode finished and no later one started.
Its glyph is a bar with a triangle that points away from it.
A person's page shows their credits and their biography.
A franchise's page draws its story order as one lane, with a line per
universe beside it. Over the first row of each era it draws a heading,
with the era's length beside its name. An era inside a wider one reads
as a smaller line under it. While the wall scrolls inside an era, one
line held over the cards names the eras around it, outer to inner. Left
and right jump an era at a time. The wall moves only when the row in
focus would leave the screen.

Search is a wall like any other. Every typed character rereads it.
The index is built in memory from titles, original titles, people's
names, episode titles, and descriptions, in that rank.

The on-screen keyboard is a grid over the wall. The arrows move across
it and enter types the focused cell. Down off the bottom row closes
the grid and puts focus on the first hit, which is how a remote with
no keyboard accepts the text. Back over the grid closes it and keeps
the text. Back over the wall with the grid closed clears the text, and
back over an empty search leaves the wall. Up from the first row of
hits puts focus on the strip, and enter there opens the grid again.

## 6. Marking a title watched or clearing it

The status line of a movie's page and of an episode's row holds the
marks that apply to the title, after the status. Each mark applies to everyone at the
screen, the same people the continue-watching row is read for, and it
takes effect at once, with no confirmation step.

| Mark | Where it shows | Effect |
|---|---|---|
| Mark watched | on a title they have not finished | The title counts as finished. A film leaves the continue-watching row. In a series, a set, or a franchise, the next title takes the card. |
| Clear progress | on a title they started or finished | The title counts as not started, and its whole series, set, or franchise leaves the row. |

The actions of a continue-watching card are on the page the card
opens. A film's card opens the film's page, and an episode's card
opens the series' page with that episode's row open.

Pick up here on an episode's row, in [section 5](#5-the-screens), marks
every earlier episode watched and clears every later one in one press.
The browser publishes one mark that lists those episodes, each with its
own mark, and the progress role writes a row for each of them. An
earlier episode with no duration in the audience's play or in the
catalog is left out, as a single watched mark on it sends nothing. A
later episode the press cleared never takes the series'
continue-watching card from the episodes the press marked watched, or
from the play of the picked episode.

The browser does not write the progress store. It publishes the mark
on the bus, retained, and the progress role writes it as one play at
the end of the title for watched, or at 0 for a clear. The mark states
the duration of the audience's play of the title where one exists, and
the catalog's running time where none does. A title with neither
sends no watched mark, and the browser logs a line that says so.

The screen shows the mark with no press, once the progress role has
recorded it. Each screen pod's progress agent names every play whose
row changed as the row arrives, and the browser then reads the
progress of the titles on its screen again. So every screen of the
namespace shows a mark pressed on another screen, and a position that
a `Play` records moves the bars, the status lines, and the
continue-watching row the same way. A change to a title the screen
does not show reads nothing more.

Neither mark deletes a play. The store keeps the earlier plays, but
the browser reads the newest play of a title, so after a clear the
screen offers no Resume and draws no position for the title. A later
play of the title replaces either mark.
[The library bus](/docs/reference/bus/#a-mark) gives the message, and
[a list of episodes](/docs/reference/bus/#a-list-of-episodes) gives the
form Pick up here publishes.

A play counts as finished when the time left is at or under a twentieth
of the work, and at or under five minutes, whichever leaves less time.
Where the credits marks of the title's file place the credits in the
second half of the work, the play counts as finished from the start of
those credits instead. The browser reads the marks from its catalog.
The jellyfin role writes a title played in Jellyfin by the same rule,
and [Keep progress with Jellyfin](/docs/guides/jellyfin/#3-what-crosses)
states it in full. Where the `Catalog` names a Jellyfin server, the
jellyfin role also sends each mark to Jellyfin.

## 7. Playback

Enter on a title resolves what to play from the browser's own copy of
the catalog. It then publishes the list on the bus, as a play request
on `liken/library/players/{namespace}/{player}/play`. The operator names
the topic on the browser container as `LIBRARY_PLAY_TOPIC`, and
[The library bus](/docs/reference/bus/#the-play-request) gives every field
of the request. The operator turns it into a `Play` for the `Player`,
with each item's path as a claim reference, and `media-operator` runs
it. The `Play` is named after the title, so `kubectl get plays` reads
like a listing. When the `Play` ends, the browser is shown again
on the page it left.

## Every screen shows every library

Nothing scopes a `Player`'s screen to a subset of the namespace's
libraries. A screen that should show fewer libraries needs a namespace
of its own. See
[The namespace is a boundary](/docs/guides/libraries/#the-namespace-is-a-boundary).

