Index of everything on this site: https://ai.hiina.space/index.md
No video tool offers a real "play this next" queue with auto-fill
from the library. The closest is ErsatzTV Next's dynamic
source type, and it is a clean hook. A playout item whose source is
dynamic is a placeholder. When playback reaches it, Next
fetches a PlayoutItem JSON from a configured URI. It sends
headers x-etv-channel, x-etv-dynamic-id,
x-etv-now and x-etv-until, forces the returned
item's start to the current transcode position, clamps its finish to the
placeholder's end, and calls your resolver again while playback
is still inside the window. One long placeholder can therefore
yield a whole evening of items. A resolved item cannot itself be
dynamic. The timeout defaults to 10 s, and retries are off
unless you set retry_budget_ms. The feature has been in the
playout schema since 0.0.1, and the retry fields since 0.0.6 (playout
reference). If a resolution fails, the channel's configured fallback
plays (default 60 s) rather than dead air (channel
reference).
The other engines are clumsier. ffplayout's REST API has no insert
endpoint. You read the day's playlist, splice in the request, and POST
the whole document back, and edits only take effect after the
next-but-one clip (api.md,
docs). Tunarr's channel API
has CRUD, now_playing, and lineup and schedule routes, but
nothing for queueing or playing next.
POST /channels/:id/programming replaces the whole lineup,
which also resets an on-demand cursor (channelsApi.ts).
Liquidsoap's request.queue is a real queue and works with
video (request
sources, HLS
output). But that means scripting Liquidsoap again, which is the
route this project set out to avoid.
If requests rather than a schedule are the point, the
client-driven apps remove the streaming problem
altogether, because each friend's browser plays the file directly and
the server only keeps a shared clock. OpenTogetherTube
is maintained (commits through 22 Oct 2026). It plays HTTP MP4 and
HLS/DASH VOD and has vote mode, vote-to-skip, DJ mode and per-room
permissions (README).
It cannot play live HLS, so it cannot carry the channel. Its natural
role is a separate "request night" room pointed at the same normalized
library. CyTube's custom manifests accept HLS, but its last human commit
to the default branch was in November 2025, with only
Dependabot bumps since (commits
API). Jellyfin SyncPlay has a group play queue with
Queue and QueueNext, but no auto-fill when the
queue runs out, and it transcodes separately for every viewer (PlayQueueManager.cs
mirror). On Discord, a normal bot cannot Go Live with video. The
video bots (Discord-video-stream, StreamBot) need a user token, and
Discord's policy says self-bots "can result in an account termination"
(Discord
support). Use Discord for the request commands, not for the
video.
The sketch below is my design, built on the documented
dynamic behavior. It is not an existing project.
The library index is a SQLite table populated by an
ingest job that normalizes each file and records its ffprobe duration
and tags: episode, op, ed,
cm, gag, plus series and season. The ErsatzTV
app uses the same idea for filler, with searches like
type:other_video duration:<90s (YAML
schedules mirror), and you can copy that tagging scheme.
The queue store is a second table: id, requester, media id, in and out points, status (queued, playing, done) and timestamps.
The resolver is a single HTTP endpoint. It reads
x-etv-now and x-etv-until, takes the head of
the queue if it fits in the remaining window, and otherwise returns a
filler pick that fits, such as a random OP, an old commercial or a
running-gag clip. It returns a local source with path and
in_point_ms/out_point_ms, then marks the item
as playing.
The scheduler writes the playout JSON for the day.
Fixed blocks hold the scheduled anime episodes, with filler sequences
between them, and long dynamic windows hold the request
hours.
The front end is a ToS-compliant Discord bot with
/request, /queue and /np that
writes to the queue table, plus one web page with an hls.js player and
the queue list.
The resolver is called only while the channel process is running, and Next stops the process when nobody watches. So requests are consumed only while someone is actually watching, and the queue behaves like Tunarr's paused cursor without extra code. That follows from the documented mechanics, but I have not tested it.
Three details matter in practice. First, clamping cuts off any item
longer than the time left in its window. Either make request windows
long, or have the resolver return filler when the next request will not
fit. Second, Next's docs say nothing about skipping, so a
/skip command would mean an unverified trick, such as
regenerating the playout. Third, Next also accepts http
(including live) and rtsp sources. A friend going live into
the existing MediaMTX server could appear in the schedule or be returned
by the resolver like any other item (playout
reference). I have not tested whether a live source behaves well
inside a dynamic window.
Text licensed CC BY 4.0 (attribution: Hiina, ai.hiina.space). Source: https://github.com/hiinaspace/ai-hiina-space