- Add 31 missing settings_card_depth_* strings from upstream Card Depth Effect feature
- Fix remaining mixed Greek/English corruption artifacts (Όχι/no, partial word swaps)
- Translate newly surfaced untranslated strings in continue watching, debrid, trakt, and cloud library areas
- Bring values-el/strings.xml to full parity with the 1958-key English base
Co-authored-by: Nasos V. <nvasedis@gmail.com>
Add Android Now Playing support using Media3 MediaSession and media notifications.
* Publish title, subtitle, artwork, duration, position, and playback state
* Add play, pause, seek, rewind, and fast-forward controls
* Support lock screen, notification, headset, and external media controls
* Integrate with both ExoPlayer and libmpv backends
* Keep playback active in the background while the media session is running
* Clean up the media session, notification, and foreground service when playback ends
- Translate 18 strings that were missing from the Greek locale entirely (Sentry crash report dialogs, downloads folder actions).
- Fix ~257 strings that were corrupted by a bad find/replace pass, which left garbled mixed Greek/English text (e.g. Αποθήκευσηd, ΑναπαραγωγήER, Όχιt available).
- Translate ~275 additional strings that were left in English (mainly the Collections/TMDB editor, plugin manager, and debrid settings screens).
- Keep brand names, codec/technical identifiers, and pure format strings in English as is conventional.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Add Vietnamese (vi) as an available language option with complete string translations. Updates include Android locale configuration, comprehensive Vietnamese translation file with 1927 strings, and AppLanguage enum to support Vietnamese selection in the app settings.
The device-authorization poll for debrid providers (TorBox/Premiumize)
treated any thrown redeem request as a fatal error and stopped polling,
showing "Could not start sign-in". The only thrown error it tolerated was
iOS request cancellation (isCancelledHttpRequest matched NSURLErrorDomain
/ code=-999); on Android a thrown request fell through to Failed(null)
and terminated the loop.
On aggressive ROMs (OxygenOS, MIUI, ...) the OS severs a backgrounded
app's sockets and fails its DNS while the user is in the browser approving
the code, so the in-flight poll throws UnknownHostException/IOException.
That terminated the login even though approval succeeded — reproduced on
OnePlus 13 / Android 16 (issue #1409): browser shows "logged in", app shows
"Could not start sign-in".
Treat a thrown redeem as Pending and keep polling instead of failing. To
avoid polling forever when the failure is persistent (airplane mode,
captive portal) rather than a brief background blip, a monotonic-clock
watchdog (DEVICE_AUTH_MAX_POLL_DURATION) bounds the throwing path and
surfaces failure once it elapses; a response-bearing expiry still stops
the loop earlier via the Expired branch. This also removes the now-unused,
iOS-only isCancelledHttpRequest helper.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
SkipIntroRepository returned the result of the first provider that had
any data, so a provider with only a partial result (e.g. AniSkip with
just the intro) shadowed another provider that had the complete set
(e.g. IntroDB with intro + outro), silently dropping the outro.
Query IntroDB, Anime-Skip and AniSkip and merge their results by segment
category (opening / ending / recap), filling each category from the
highest-priority provider that has it (IntroDB, then Anime-Skip, then
AniSkip). The most complete set is always used and no provider shadows
another in either direction. The skipIntroEnabled gate and caching are
unchanged.
The skip lookup ran on the caller's composition-bound coroutine scope,
so navigating into the external player cancelled the in-flight network
request before it returned (and the cancelled lookup cached an empty
result, poisoning retries). Run resolution on a dedicated app scope so
it completes regardless of UI lifecycle.
Adds an opt-in External Player setting that hands resolved intro and
outro skip timestamps to external players via a skip_segments intent
extra, so a supporting player can auto-skip the opening and ending the
same way the internal player already does.
Resolution reuses the existing SkipIntroRepository (IntroDB, ARM,
AniSkip, Anime-Skip), so there is no new API, key, provider, or
dependency. The toggle is off by default and sits under Forward
Subtitles in the External Player settings. Resolution is bounded by a
timeout and only runs when the setting is on; players that do not read
the extra ignore it.
The LifecycleEventObserver was only capturing playWhenReady at the
time the effect ran. When ON_START fired later, it would use the old
value and resume playback even if the user had paused before leaving
the app.
Use rememberUpdatedState so we always read the live value.
Fixes#1080
When a metadata addon (e.g. aiometadata) supplies a genuine IMDb rating,
the TMDB enrichment on the details page was overwriting it with TMDB's
vote_average because the copy() preferred enrichment.rating over the
existing value. This made the IMDb placeholder display the TMDB score on
mobile, even though the addon provided IMDb ratings (the TV version was
already correct).
Invert the precedence so the addon-provided imdbRating is kept and TMDB's
vote_average is only used as a fallback when no rating was supplied.
Fixes#1377
- Rewrote all user-facing French strings from informal address (tu/ton/ta/
tes, 2nd-person-singular imperatives) to formal vouvoiement (vous/votre/
vos, 2nd-person-plural imperatives). 171 strings updated.
- Past participles and adjectives kept singular (politeness "vous"):
"Vous avez ete deconnecte", "Vous etes seul responsable".
- Setting behavior descriptions written in 3rd person ("Affiche les
badges...") left unchanged: they describe the option, not the user.
- Key parity unchanged, placeholders (%1$s, \n), HTML entities and French
typography (apostrophes ', non-breaking spaces) preserved.
- Add AudioLanguageOption.ORIGINAL to preferred audio language picker
(primary and secondary) with hint 'Requires TMDB enrichment'
- Resolve content original language from meta.language with fallback
to country-based inference (countryToLanguageCode mapping)
- Pass contentLanguage through PlayerLaunch, PlayerScreen, and
StreamLinkCache for reuse on subsequent playback sessions
- Add Device Language as default app language option (resets to system
locale instead of forcing English)
- Add all missing Polish translations (417 strings)
- Add LanguageSelectionOption.description support in settings dialogs
Long-press menu was blocked for debrid instant streams because
playableDirectUrl is null until resolved. Now the menu opens for
any stream that has a direct URL or is a debrid candidate.
Download and Copy Link actions now resolve the debrid stream URL
via DirectDebridPlaybackResolver before enqueuing/copying, matching
the same flow used by the player.
Store CatalogRoute.targetKind as CatalogTargetKind.name and convert it back in toCatalogTarget(). This keeps the typed route argument on a primitive String boundary so Compose Navigation does not require a custom enum NavType during graph construction.
The failure path now falls back to a message carrying the HTTP status
when the body has no detail/error and the raw body is blank, and the
large mylist fixture moves to TorboxCloudLibraryFixtures.kt so the
test file stays scannable.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Android httpRequestRaw capped response bodies at 1 MiB and appended a
truncation marker, corrupting large Torbox mylist JSON. The decode error
was swallowed, so the cloud library rendered as silently empty. Read the
full body like iOS/desktop already do, and surface undecodable Torbox
list responses as a provider error instead of an empty library.
Fixes#1216
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Prevents a Long overflow when the last AnimSkip segment carries Double.MAX_VALUE as its endTime sentinel. The seek target is now capped to playbackSnapshot.durationMs so ExoPlayer always receives a valid millisecond position.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The stop-scrobble path snapshots its inputs via
snapshotTraktScrobbleItemInputs() returning a TraktScrobbleItemInputs
holder instead of seven loose locals, so adding a field to the item
cannot silently miss the snapshot.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Addons that embed the torrent identity in the url field (torrent://<hash>[/<fileIdx>]) with a null infoHash were treated as direct HTTP streams: playableDirectUrl surfaced the raw torrent:// string to the player, isTorrentStream missed it so debrid never engaged, and p2pInfoHash could not extract a hash so the p2p engine never engaged. Equivalent of NuvioTV#2174: classify torrent:// URLs as torrents, never surface them as playable, extract a validated info hash (40-char hex or 32-char base32) and trailing file index for the p2p path, and build proper magnet URIs for debrid from the effective hash. torrent://null and other unextractable URLs yield a null hash and stay unselectable.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
streamType is now trimmed, lowercased, and blanked to null by a shared
normalizeStreamType helper at all four ingestion sites, and the player
derives a single normalized value for both its rebuild keys and MIME
inference, so values like " HLS " cannot cause key churn or missed
mappings. The helper lives in commonMain and is covered by commonTest
together with a parser round-trip for a padded uppercase type.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Plugin scrapers (and some non-standard addons) declare the manifest
format of their links via a "type" field ("hls", "dash") on the
stream object. PluginRuntime already parses it into
PluginRuntimeResult.type, but both toStreamItem conversions dropped
it, and PlayerEngine.android.kt built MediaItems with no MIME type,
leaving ExoPlayer to infer the format from the URL extension. For
HLS links whose URL hides the .m3u8 behind tokens or rewriters
(https://cdn.example.com/playlist?token=…), inference fails and
playback errors or stalls.
This change:
- Adds a streamType field to StreamItem, populated from stream.type
in StreamParser, MetaDetailsParser embedded streams, and both
PluginRuntimeResult.toStreamItem conversions (StreamsRepository and
PlayerStreamsRepository).
- Plumbs the value through PlayerLaunch / PlayerScreen /
PlatformPlayerSurface and the in-player source-switch paths.
Switches to P2P sentinels and downloaded local files reset the
value to null so a stale "hls" can never mislabel the next source.
streamType participates in the player rebuild keys so a source
change always rebuilds with the right MIME.
- Persists streamType in the Reuse Last Link cache (defaulted
serializable field, old payloads stay decodable) so a typed HLS
stream that played once keeps working through link reuse.
- In playbackMediaItemFromUrl, the declared type takes precedence
over response-header and URL-path inference, mapping "hls"/"m3u8" →
APPLICATION_M3U8, "dash"/"mpd" → APPLICATION_MPD,
"ss"/"smoothstreaming" → APPLICATION_SS. Unrecognized values
(including Stremio's content-type semantics like "movie"/"series")
fall through to the existing inference chain unchanged.
The official Stremio stream spec defines no top-level type field, so
the Stremio-addon parsing is best-effort; the reliable emitters are
plugin scrapers, whose results previously lost the field entirely.
Scopes down to #1244 only. #1246 (User-Agent override) is already
fixed in the current PlayerPlaybackNetworking.createHttpDataSourceFactory,
which skips setUserAgent when caller-supplied headers declare one.
Fixes#1244
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Persist pending debrid device-code authorization so returning from the browser after Android process death can continue polling the same session.
Recover cached profiles during auth rehydration instead of briefly dumping users to login after background kills.
Include debrid and downloads preferences in local account cleanup on sign-out.