v1.8.1English

سجل التغييرات

All notable changes to PulseTV are documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). See

[Versioning](#versioning) below for how version numbers are chosen.

[Unreleased]

[1.8.1] - 2026-10-22

Fixed

search field instead skipped past it and landed on the global nav bar's search icon, bouncing

into the unrelated global Search screen. The field is now reliably reachable with the remote in

both screens, matching Live TV's already-working group search.

[1.8.0] - 2026-10-22

Added

category/genre rail (Movies, TV shows) filters that list itself — finding a group or category to

jump to — rather than the channels/titles inside one. Debounced, reachable with the remote, and

works with Arabic the same way the existing global Search screen does. The global Search screen

remains the way to find a channel, movie or show by name.

Fixed

running, Playlists' Back up/Restore mid-operation) looked the same as an enabled-but-unfocused

button, differing only by a slightly dimmer text color — easy to mistake for "just not focused

yet". Disabled now has its own flat, inert fill, clearly distinct from both the unfocused and

focused states.

successful add) created a second, third, etc. duplicate playlist row for the same account. The

Add/Save button now disables itself once the add succeeds, and adding is deduped at the data

layer too — an M3U playlist with the same URL, or an Xtream playlist with the same server and

username, updates the existing playlist instead of creating a new one.

[1.7.0] - 2026-10-21

Added

move it, OK or Back to drop it in place (same pattern as Favourites' reorder). "All",

Favourites and Recent stay pinned at the top and aren't reorderable. The custom order persists

across restarts and playlist refreshes — a refresh never resets it, and any group newly seen

after a refresh is appended at the bottom instead of disturbing positions already set. Applied

consistently in both the Live TV screen's group pane and the in-player channel drawer.

list itself, not just the channels within a selected group — A–Z sorts the groups too; Number

has no per-group meaning, so groups stay in provider order under it.

Fixed

truncated with "…" instead of being readable. Category rows now wrap to two lines (falling back

to a focused marquee only if a name still doesn't fit), and the rail no longer re-sorts

categories alphabetically — it now follows the provider's own category order, matching how Live

TV groups already worked.

Compose's `combinedClickable` long-click only ever fires from a pointer/touch hold, and the

platform's own long-press key flag isn't set consistently by every remote's driver either — the

press is now timed by hand with a coroutine, matching how a real held key behaves regardless of

the specific remote hardware.

behind the screen header instead of staying visible — the row being moved keeps focus throughout

the move, so Compose's normal "scroll the focused item into view" never re-triggered for it. The

row now explicitly scrolls itself into view on every move.

[1.6.0] - 2026-10-12

Added

playback (series episodes by default; a new Settings → Playback → "Show Skip Intro on movies

too" toggle extends it to movies, off by default). Focus it and press OK to seek forward by a

configurable amount — Settings → Playback → "Skip Intro length" (30/60/75/90s, default 75s). A

fixed forward seek, not a detected intro boundary. Auto-hides once the window elapses or once

pressed.

[1.5.3] - 2026-10-09

Fixed

a regression from 1.5.2's new buffer byte cap. Two compounding causes: (1) the live TV

controller's player listener was never detached when it called its own `stop()` — which happens

on essentially every Live TV grid scroll — so after any Live TV browsing that session, its

8-second "no bytes loaded" watchdog stayed wired to the shared player and could fire on a

movie's own ordinary rebuffer dip, calling a literal `stop()` on it; (2) 1.5.2's buffer byte cap

(48MB effective on this hardware) was too tight for higher-bitrate content, cutting the buffer

short of the configured duration target well before it should have. Fixed the listener leak, and

sized the buffer cap from actual remaining heap headroom instead of a flat guess. Also enabled

`android:largeHeap`, since this hardware's real heap ceiling (512MB) was well above what the app

was actually being granted (192MB) with no reason not to use it.

[1.5.2] - 2026-10-09

Fixed

crash recurred on 1.5.1 itself during completely normal single-movie playback. Found the actual

cause by pulling the device's own ANR traces: the playback buffer had no byte cap (only a

duration cap), so ExoPlayer's ~144MB default applied regardless of the "Buffer size" setting, on

top of the app's own baseline memory usage — together enough to exhaust the 192MB heap on this

hardware. The buffer is now capped relative to the device's actual available heap.

it compared each run's own progress against the whole library instead of tracking progress

across chained runs, so it kept re-enqueuing itself forever, including while something was

playing, contributing extra memory pressure on top of the buffer issue above. It now correctly

recognizes when a full pass is done.

[1.5.1] - 2026-10-09

Fixed

had been played earlier in the same session. Live TV and VOD share one app-wide player, and the

live controller only attached its own listener the first time it personally created that player

— if VOD created it first, live TV never heard `STATE_READY`, so its own 15-second watchdog kept

firing blind even while the stream was actually playing underneath. It now re-attaches its

listener on every call, the way the VOD controller already did.

traced to a genuine `OutOfMemoryError`. Switching titles quickly (skipping through episodes,

trying several movies) left every not-yet-finished manifest/segment fetch from whatever was just

playing running in the background instead of being cancelled, piling up buffered data across

repeated switches. VOD playback now cancels in-flight requests on every switch, matching how

live TV already handled this.

[1.5.0] - 2026-10-08

Added

the enriched movie/show library, so a chip only appears when a title carries it). Selecting one

or more combines with the text query: no text + a genre lists every title in that genre; text +

genre narrows further. Multiple genres OR together. D-pad navigable.

Changed

Recent-channels quick-zap row, so a favourited channel is reachable in one or two presses from

Home, matching the existing Recent-channels row's prominence. Live TV already put Favourites

first in its sidebar, so no change was needed there.

[1.4.0] - 2026-10-06

Added

own built-in default so existing behaviour is unchanged for anyone who never touches it). Also

surfaced in the in-player options menu (Menu key, or long-press OK) so it can be changed

mid-stream; Settings and the in-player menu read and write the same stored value, and a change

re-applies live without restarting the current stream.

Changed

re-prepares the source and seeks to the live edge with exponential backoff, within a ~45-60

second total wall-clock window, before showing the error card. A genuine hard error (host

unresolved, 403, 404 with no fallback left) still fails fast — only a transient stall gets the

longer patience.

to the physical bottom-right corner of the screen (regardless of RTL/LTR layout) and dropped the

spinning loader graphic in favour of text only, so it stays out of the way of the video during

background recovery.

Fixed

now-dead track-selection state into it on every single retry attempt (not just on an actual

Buffer size change), which could corrupt ExoPlayer's internal state badly enough to crash

playback with `ERROR_CODE_FAILED_RUNTIME_CHECK` a few retries into a stall. The buffer-control

delegate is now only rebuilt when the buffer size has actually changed.

~45-60s target, not real elapsed time, so a stream that failed slowly on each attempt (e.g. an

unresponsive host sitting on a connection until the read-timeout fired) could take well over a

minute to show the error card. The window now tracks wall-clock time from the first failure.

[1.3.1] - 2026-10-06

Fixed

or search result's details page — they now land back on the exact item that was open, with the

grid/list scrolled there, even thousands of items deep into the library. Root cause: Compose

Navigation disposes a screen's composition when navigating away and recomposes it fresh on

Back, so only `rememberSaveable` state survives the round trip; `MoviesScreen`/`ShowsScreen` had

an unconditional effect that force-focused the sidebar's first category on every re-entry

(including a Back-return), and `PosterGrid` tracked no "last focused" state to restore in the

first place — a plain `focusRestorer()` can't help either, since a virtualised grid's

off-screen target item isn't even composed yet. Fixed by having the grid/list scroll to the

remembered item's index before requesting focus on it; applied to Movies, TV shows, Search's

channel/movie/show/people sections, and Favourites.

Changed

the generic `app-release.apk`, so multiple release builds sitting side by side (or the VPS

download gate's upload step) are self-describing.

[1.3.0] - 2026-10-04

Added

queries TMDb's `/search/person` alongside the existing channel/movie/show search, showing each

match's circular photo and name. Selecting one opens the same actor page already used from a

detail page's cast row — bio plus a TMDb `/combined_credits` filmography grid — where an

in-library title opens its own details page and an out-of-library title shows a read-only

"Not in your library" card instead of a dead tap. An Arabic query (e.g. "عمر الشريف") requests

TMDb's Arabic-localised name for the match, confirmed on-device returning "عمر الشريف" rather

than "Omar Sharif". Results are capped at 15 and only fire once the query is 2+ characters, so

typing doesn't flood TMDb with a network request per keystroke on top of the existing 200 ms

debounce.

[1.2.1] - 2026-10-01

Fixed

one Composable for both content types, but its Up/Down and long-press-OK key handling branched

on `isEpisode`, giving each content type a different, inconsistent gesture scheme. Series:

long-press OK opened an unnavigable episode list (focus never moved off the underlying video

surface because the drawer's initial-focus `LaunchedEffect` was keyed only on the episode list's

content, which doesn't change between opens, so it silently failed to re-fire on a second open),

and Up/Down jumped to the next/previous episode. Movies: long-press OK correctly opened the

options sheet. Fix strips out all `isEpisode` branching from the key handlers so both content

types now share identical behaviour: long-press OK always opens the Audio/Subtitles/Aspect/Speed

options sheet; Up/Down always opens the quick-actions row and never changes the episode; episode

navigation moved to explicit Previous/Next overlay buttons and a new "Episodes" button that opens

the all-episodes drawer (reachable only via the overlay, not long-press OK); the drawer's

initial-focus effect now also keys on an open-counter so it reliably re-fires on every open, and

gained `.focusGroup()` on its season-chip row and episode list to match the proven focus pattern

used elsewhere (e.g. the channel drawer). Live TV was left unchanged — Up/Down still zaps

channels there, which is intentional and distinct from VOD/series.

now-removed Up/Down-to-advance behaviour; it now correctly points to the overlay's Next/Previous

buttons.

[1.2.0] - 2026-10-01

Added

(playlist URL + the title's own URL/name) so they survive a reimport's regenerated row ids —

previously only channel favourites and the TMDb API key were backed up, so restoring onto a

fresh install lost every movie/series favourite and left enrichment starting from zero.

Fixed

`fallbackToDestructiveMigration()`, which silently drops and recreates every table — including

playlists, favourites and the TMDb cache — whenever it meets an on-disk schema version it has no

registered migration for, with no confirmation or log. Switched to

`fallbackToDestructiveMigrationOnDowngrade()`: destructive recreation now only happens on a

genuine downgrade (an intentional, dev-only case); every other mismatch makes Room throw instead

of silently wiping, leaving the database file untouched.

missing or unreadable (decrypt fails closed by design), and had no database transaction, so a

failure partway through left a half-restored, inconsistent state. Restore now validates the

whole backup up front and fails loudly if credentials can't be decrypted, and wraps all the

metadata writes (playlists, credentials, channel favourites, locked groups, TMDb key) in one

transaction that rolls back completely on any failure. A local-file playlist (whose SAF URI

can't survive a backup/restore round trip) is now explicitly skipped and reported instead of

being silently mis-restored as an M3U playlist.

[1.1.1] - 2026-10-01

Fixed

firmware intercepts for its own quick-settings before the app ever sees it — the menu was

effectively unopenable on the real remote. Added a visible "Aspect" and "Options" button to both

the live and VOD player overlays, reachable with a plain D-pad press (Right on live TV, Down on

a VOD movie — both previously-unused directions in those contexts) as the primary path, plus a

long-press of OK/center as a secondary one. The underlying programmatic-focus bug behind the new

buttons not working at all on the first pass — a direct `requestFocus()` call made synchronously

inside the key-event handler itself, which Compose's focus system doesn't apply reentrantly

mid-dispatch — is also fixed, deferring the request to the next frame like every other

programmatic focus request in this codebase already does.

[1.1.0] - 2026-09-30

Added

marks it finished in WatchProgress and closes, returning focus to the exact item that launched

it — the hero or Continue Watching card on Home, or the played episode's row on a series'

details page — instead of a generic default. A series with a next episode still auto-plays it

as before; the new behaviour only applies when there's nothing left to play.

Watching row, and Recent Channels, with a confirmation dialog. Favourites and playlists are

untouched, and Home's rows empty immediately without restarting the app.

[1.0.1] - 2026-09-29

A device-reported follow-up pass: two of the fixes from 1.0.0 didn't hold on the signed release

build and were re-diagnosed and fixed properly (see each commit for what the earlier fix missed).

Fixed

the Home hero's auto-focus effect was living inside its own `LazyColumn` item slot and got

re-triggered whenever that item was disposed and recomposed (e.g. scrolled far enough

off-screen), silently stealing focus back from wherever the user had already navigated to.

Hoisted the hero's initial-focus effect above the `LazyColumn` so it runs at most once per

Home visit.

a modifier-ordering slip (`onFocusChanged` after `clickable` instead of before) left the focus

state permanently stuck at "unfocused". Reordered to match every other focus-ring card in the

app.

[1.0.0] - 2026-09-29

The first tagged release. Everything below shipped incrementally over milestones M0 through M5

and a series of post-M5 passes (NexTV UX comparison, search/order/genre fixes, look-and-feel

polish, TMDb enrichment rework, and a device-reported bug-fix pass) before this project adopted

version numbers — this section retroactively summarizes that whole history.

Added

in-app playlist import/editing, backup and restore, and a configurable auto-refresh interval.

live-switching channel drawer, ExoPlayer playback, zap, number-jump, error cards with retry, and

Xtream HLS→TS fallback. A recent-channels quick-zap row on Home.

virtualized Guide grid.

long-press context menu; unified search (channels, movies, TV shows) with voice input and a

browsable pre-typing state; catch-up playback; audio/subtitle/aspect/speed picker; parental lock

(PIN + per-group); picture-in-picture; QR pairing for playlists and the TMDb API key.

grids classified by provider category and TMDb genre, series → season → episode navigation, a

VOD/episode player (seek/resume/next-episode, all-episodes drawer, Resume/Start-over prompt),

and an external-player setting.

(filmography with in-library title matching), trailers, recommendations/similar rows, a manual

"report bad metadata" re-match flow, "Re-match all" (with a confirmation dialog) and "Match

everything now" (a one-time full pass), backed by a fast parallel enrichment engine (up to 40

requests/s, batched writes, per-title caching) and automatic accumulation after every playlist

refresh.

R8/ProGuard release build, signed APK.

Arabic fonts.

Changed

lists respect provider order by default, with a Settings sort toggle (provider/A–Z/number).

episode list; the TMDb API key moved to its own Settings page; enrichment progress reporting

made honest (a real ETA, no premature "Done" state).

Watching collapses to one card per movie/series; VOD movies are deduped by title on import.

panel treatment for overlays/drawer/dialogs/mini-guide, and consistent artwork box styling

(clip/border/shimmer/flat fallback) with TMDb poster art now showing in grids, rows and search,

not just detail pages.

Fixed

channel-drawer focus, D-pad navigation quirks).

glitches.

a starved rotation that never reached series titles), reporting progress dishonestly, and

"Re-match all" not actually using the fast enrichment engine.

a malformed Xtream `episode.info` payload or a bare-string `backdrop_path`.

restore losing credentials or duplicating playlists across a debug-to-release re-sign.

recombining the TMDb metadata stream on every row.

bug when linking between two titles of the same type).

current row.

Versioning

PulseTV follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html) (`MAJOR.MINOR.PATCH`)

starting from this release:

setting).

`versionCode` (in `app/build.gradle.kts`) is a plain integer that must increase on every release,

regardless of which part of the semver triple changed. `versionName` is the semver string shown on

the About screen. See [README.md](README.md#releasing) for the release checklist.