With startup.mode = optimistic, session.startup stays set for the whole
session lifetime on clear MPEG-TS sources. select_window's startup branch
returned before retain_published_predecessors, so the v3.3.153
three-target-duration retention never ran in that mode. A 3-segment
origin window was served unchanged (e.g. 17.96 s with target 7), both on
retained-session return entries and during steady playback.
Apply the retention after the startup branch's MIN_VISIBLE_SEGMENTS check.
The retention rules are unchanged: only contiguous Ready, renderable,
non-provisioning predecessors are added, never before the previously
rendered first segment, and the window is capped at
TARGET_VISIBLE_SEGMENTS.
* feat(watch): group added/removed contexts per watch entry
- watch entries: regex string or {pattern, on:
[channels|group_added|group_removed]}
- one playlist.watch.changed per refresh with WatchChange list;
playlist.groups.changed aliased
- Telegram/ntfy message length limits
- web UI: Mapping and Watch sub pages, new Mapping icon
- ClusterFlags on create_bitset!, bitflags removed
BREAKING CHANGE: playlist.watch.changed templates must use the new
watch.* fields
* feat(watch): group added/removed contexts per watch entry
- watch entries: regex string or {pattern, on:
[channels|group_added|group_removed]}
- one playlist.watch.changed per refresh with WatchChange list;
playlist.groups.changed aliased
- Telegram/ntfy message length limits
- web UI: Mapping and Watch sub pages, new Mapping icon
- ClusterFlags on create_bitset!, bitflags removed
BREAKING CHANGE: playlist.watch.changed templates must use the new
watch.* fields
* fix hls: require fresh origin window for new playback on retained sessions, count known-length revision bodies as completed
- New playback leases joining a retained shared session no longer start
on
a stale committed window. If the origin window was last confirmed more
than two target durations ago, admission requires a fresh commit
(StaleEntryWindow) using the strict pinned host and no recovery burst.
This prevents the forward jump after fast-start entry.
- RevisionResponseStream tracks bytes sent. A known-length body that is
dropped after its final byte is now recorded as completed and marks
segment completion instead of logging outcome=dropped.
- Add an end-to-end re-entry regression test covering progressive,
first_ready and conservative, plus unit tests for the commit
requirement and revision completion.
* - fix epg: adopt guide channel ID casing on matched live streams
Synchronize the stream's `epg_channel_id` to the exact casing of the
matched XMLTV guide channel. This fixes exact-key B+Tree lookups in short
EPG and client API responses when stream and guide IDs differ in case.
- fix frontend: hide column rail on empty tables
* - fix epg: adopt guide channel ID casing on matched live streams
Synchronize the stream's `epg_channel_id` to the exact casing of the
matched XMLTV guide channel. This fixes exact-key B+Tree lookups in short
EPG and client API responses when stream and guide IDs differ in case.
- fix frontend: hide column rail on empty tables
* fix hls close revision file at reader deadline
Move the progressive revision reader onto its own task and feed the
response stream through a bounded channel. At the deadline the watchdog
aborts the reader, so the file handle and revision pin are released
even if a stalled client never polls the stream again.
Each open revision file now holds its own pin, so GC cannot retire a
revision while a handle is open, including one held by an in-flight
blocking read.
* fix hls close revision file at reader deadline
Move the progressive revision reader onto its own task and feed the
response stream through a bounded channel. At the deadline the watchdog
aborts the reader, so the file handle and revision pin are released
even if a stalled client never polls the stream again.
Each open revision file now holds its own pin, so GC cannot retire a
revision while a handle is open, including one held by an in-flight
blocking read.
---------
Co-authored-by: euzu <euzu@proton.me>
* **New Features**
* Customize table columns by showing, hiding, and reordering them, with keyboard or pointer controls.
* Save layouts per account, restore defaults, and resolve conflicts when preferences change elsewhere. On installations without web authentication, settings are shared.
* CSV tables now support saved layouts for recognized table structures.
* **Bug Fixes**
* Improved pagination bounds and page-size validation.
* Settings requests now return clearer errors for oversized or unreadable request bodies, and recheck access after waiting for settings to become available.
* **Documentation**
* Added guidance for table customization and the settings API.
* fix(frontend): clean up websocket callbacks and reconnect timers on session expiry
* fix(frontend): clean up websocket callbacks and reconnect timers on session expiry
---------
Co-authored-by: euzu <euzu@proton.me>
* fix(m3u): use alias token for Flussonic catch-up archives
Catch-up archive URLs are built from the main account's live URL. On an
alias allocation the M3U URL index only knows live URLs, so the lookup
missed and the archive manifest was fetched with the main stream token.
DVR segments inherited it, and the provider attributed the playback to
the main account.
On an index miss for a Flussonic archive file, resolve the live file in
the same directory through the alias index (one lookup pass for all
candidates) and keep the requested archive file name with the alias
query.
Query-based catch-up templates (append mode) are not covered.
* fix(dvr): retry ffmpeg spawn while executable is busy
Spawning a just-written executable can fail with ETXTBSY when a
concurrently forked process still holds an inherited write descriptor
before its exec. This made run_recording_preempts_fake_ffmpeg_and_
preserves_window_semantics flaky on CI.
Retry the spawn up to five times with a short backoff on
ExecutableFileBusy; other errors fail immediately as before.
* fix(hls): resolve single-variant archive master in shared cache
Archive URLs that return a master playlist with one #EXT-X-STREA
child failed with segment_uri_without_extinf. The variant's media
playlist is now fetched and fed to the cache timeline.
- skip masters with several variants or separate #EXT-X-MEDIA tr
- send entry credentials only to the entry origin
- send master cookies only to the master origin
- merge master and variant session cookies by name
- keep the recovery binding on the entry URL
* **New Features**
* Added an EPG group view with channel counts, programme details, and channel search.
* Added timeline zooming and panning, recording controls for eligible upcoming programmes, and a resizable side panel with keyboard and pointer controls.
* **Improvements**
* EPG group and grid results support JSON or CBOR responses and show up to 1,000 channels per group.
* Channel filters support case-insensitive text and regular expressions; invalid patterns are rejected with an error.
feat(dvr)!: unify live and VOD/series recording into one recoverable DVR
Merge fix/merge_live_and_vod_recording into develop.
- One recording queue for scheduled live captures and VOD/series transfers,
with a shared transition graph, provider-slot scheduling, resumable HTTP
transfers (strong ETag / Last-Modified validation) and ffmpeg live capture.
- Crash-recoverable persistence: B+Tree repository with a journal-first
recovery history; one file can be shared by several library entries.
- Admission checks quota and disk on request, at transfer start (real size)
and before an entry attaches to a finished file; idempotent requests.
- REST is command-only; state reaches clients via owner-filtered WebSocket
snapshots.
- Deleting a recording removes the file once no entry holds it; removing an
entry keeps a finished file and cleans up orphaned partials.
- User ids derive from the configured username (web:/api:); the identity
registry is gone.
- Spanish locale added; ru/ar completed; locale parity test.
BREAKING CHANGE: `video.download` is now `video.recording` (nested
`recording` keys move up, `download_priority`/`recording_priority` become
`priority`); old configs fail to load with a migration hint.
BREAKING CHANGE: `/api/v1/file/download*` and `/file/record` are removed in
favour of `/api/v1/recording/*`.
BREAKING CHANGE: `download.*` and `recording.write` permissions are replaced
by `recording.read|create|manage|delete`; tokens from before the change must
re-authenticate.
BREAKING CHANGE: the old recording queue does not migrate; existing
`identity_registry.json` is ignored.
* fix(hls): return upstream status for HLS resources
- no TS fallback for failed segments, init and keys
- bounded capacity wait on pinned account (manifest 5s, media 2s)
- reject waiting requests after session end or account switch
- per-origin provider cookie jar, read at send time
- opt-in flussonic_hls_audio_tracks input option
* fix(hls): return upstream status for HLS resources
- no TS fallback for failed segments, init and keys
- bounded capacity wait on pinned account (manifest 5s, media 2s)
- reject waiting requests after session end or account switch
- per-origin provider cookie jar, read at send time
- opt-in flussonic_hls_audio_tracks input option
* fix(hls): return upstream status for HLS resources
- no TS fallback for failed segments, init and keys
- bounded capacity wait on pinned account (manifest 5s, media 2s)
- reject waiting requests after session end or account switch
- per-origin provider cookie jar, read at send time
- opt-in flussonic_hls_audio_tracks input option
* fix(hls): keep provider account across client IP changes
Wrap live HLS media playlists in a single-variant master whose variant
URI is a sealed owner token, so playlist refreshes from changing client
IPs keep their owner, lease and provider affinity. Expired sessions are
recreated from the token with the same access and admission checks.
- Classify HLS resources by playlist tag, not by .m3u8 extension
- Answer non-HLS 200 bodies as failed manifests (entry: unavailable,
refresh: 502)
- Pin child manifests and media URIs to the resolving provider account
- Add reverse_proxy.stream.hls_wrap_media_playlist (default true)
- Split AppState into app_state/ containers; move HTTP clients to core
* fix(hls): keep provider account across client IP changes
Wrap live HLS media playlists in a single-variant master whose variant
URI is a sealed owner token, so playlist refreshes from changing client
IPs keep their owner, lease and provider affinity. Expired sessions are
recreated from the token with the same access and admission checks.
- Classify HLS resources by playlist tag, not by .m3u8 extension
- Answer non-HLS 200 bodies as failed manifests (entry: unavailable,
refresh: 502)
- Pin child manifests and media URIs to the resolving provider account
- Add reverse_proxy.stream.hls_wrap_media_playlist (default true)
- Split AppState into app_state/ containers; move HTTP clients to core
* fix(hls): keep provider account across client IP changes
Wrap live HLS media playlists in a single-variant master whose variant
URI is a sealed owner token, so playlist refreshes from changing client
IPs keep their owner, lease and provider affinity. Expired sessions are
recreated from the token with the same access and admission checks.
- Classify HLS resources by playlist tag, not by .m3u8 extension
- Answer non-HLS 200 bodies as failed manifests (entry: unavailable,
refresh: 502)
- Pin child manifests and media URIs to the resolving provider account
- Add reverse_proxy.stream.hls_wrap_media_playlist (default true)
- Split AppState into app_state/ containers; move HTTP clients to core
* fix(hls): keep provider account across client IP changes
Wrap live HLS media playlists in a single-variant master whose variant
URI is a sealed owner token, so playlist refreshes from changing client
IPs keep their owner, lease and provider affinity. Expired sessions are
recreated from the token with the same access and admission checks.
- Classify HLS resources by playlist tag, not by .m3u8 extension
- Answer non-HLS 200 bodies as failed manifests (entry: unavailable,
refresh: 502)
- Pin child manifests and media URIs to the resolving provider account
- Add reverse_proxy.stream.hls_wrap_media_playlist (default true)
- Split AppState into app_state/ containers; move HTTP clients to core
* fix(hls): keep provider account across client IP changes
Wrap live HLS media playlists in a single-variant master whose variant
URI is a sealed owner token, so playlist refreshes from changing client
IPs keep their owner, lease and provider affinity. Expired sessions are
recreated from the token with the same access and admission checks.
- Classify HLS resources by playlist tag, not by .m3u8 extension
- Answer non-HLS 200 bodies as failed manifests (entry: unavailable,
refresh: 502)
- Pin child manifests and media URIs to the resolving provider account
- Add reverse_proxy.stream.hls_wrap_media_playlist (default true)
- Split AppState into app_state/ containers; move HTTP clients to core
---------
Co-authored-by: euzu <euzu@proton.me>
* fix(hls): keep provider affinity after reconnect lease expiry
- Remember the provider that last delivered media for reconnect-capable
playback (HLS, DASH, Catchup, VOD) beyond the lease window. The
preference holds no slot, never forces grace and falls back to the
lineup when the provider is full.
- End it on provider failure, preemption, kick, timeout or admin
terminate; stale cleanups are rejected by binding tag, request ID and
session token. Retry token claims stay bounded.
- Terminate and kick now release the HLS binding they acquired.
- Add reverse_proxy.stream.provider_affinity_ttl_secs (default 120,
max 86400) with Web UI field, docs and translations.
- Testkit: pause_millis steps, TTL overrides and the
m3u-hls-provider-affinity-after-lease-expiry scenario.
* fix(hls): keep provider affinity after reconnect lease expiry
- Remember the provider that last delivered media for reconnect-capable
playback (HLS, DASH, Catchup, VOD) beyond the lease window. The
preference holds no slot, never forces grace and falls back to the
lineup when the provider is full.
- End it on provider failure, preemption, kick, timeout or admin
terminate; stale cleanups are rejected by binding tag, request ID and
session token. Retry token claims stay bounded.
- Terminate and kick now release the HLS binding they acquired.
- Add reverse_proxy.stream.provider_affinity_ttl_secs (default 120,
max 86400) with Web UI field, docs and translations.
- Testkit: pause_millis steps, TTL overrides and the
m3u-hls-provider-affinity-after-lease-expiry scenario.
* fix(hls): keep provider affinity after reconnect lease expiry
- Remember the provider that last delivered media for reconnect-capable
playback (HLS, DASH, Catchup, VOD) beyond the lease window. The
preference holds no slot, never forces grace and falls back to the
lineup when the provider is full.
- End it on provider failure, preemption, kick, timeout or admin
terminate; stale cleanups are rejected by binding tag, request ID and
session token. Retry token claims stay bounded.
- Terminate and kick now release the HLS binding they acquired.
- Add reverse_proxy.stream.provider_affinity_ttl_secs (default 120,
max 86400) with Web UI field, docs and translations.
- Testkit: pause_millis steps, TTL overrides and the
m3u-hls-provider-affinity-after-lease-expiry scenario.
---------
Co-authored-by: euzu <euzu@proton.me>
- Store terminal account states in source.yml/CSV; keep Pending runtime-only
- On 401/403, only flag the account for the throttled expiry worker
- Lift stored bans on panel renewal or credential update
- Persist per file in one batch; back up only when content changes
- Use the first usable alias for playlists when the root is blocked
Co-authored-by: euzu <euzu@proton.me>
* fix(provider): exclude and persist banned/expired accounts
- Store terminal account states in source.yml/CSV; keep Pending runtime-only
- On 401/403, only flag the account for the throttled expiry worker
- Lift stored bans on panel renewal or credential update
- Persist per file in one batch; back up only when content changes
- Use the first usable alias for playlists when the root is blocked
* fix(provider): exclude and persist banned/expired accounts
- Store terminal account states in source.yml/CSV; keep Pending runtime-only
- On 401/403, only flag the account for the throttled expiry worker
- Lift stored bans on panel renewal or credential update
- Persist per file in one batch; back up only when content changes
- Use the first usable alias for playlists when the root is blocked