Commit Graph
50 Commits
Author SHA1 Message Date
edde746 e1ef52aba1 fix(android): fall back to software decoding for non-Main-profile AV1
4:4:4 (High profile) AV1 played as green or black video on Android TV
devices with AV1 hardware decoders: MediaCodec defines no profile
constants beyond Main, but decoders like the Fire Stick 4K Max (MTK)
and Amlogic C2 accept the stream at configure time and emit garbage or
nothing, so no error ever reached the player and no fallback ran.

libmpv v1.2.2 carries an FFmpeg patch that reads seq_profile from the
bitstream at av1_mediacodec init and refuses High/Professional streams,
letting mpv's hwdec probing fall through to dav1d software decoding.
Main-profile AV1 keeps hardware decoding.

close #2194
2026-08-31 13:29:13 +02:00
edde746 39ee089212 fix(audio): reload the mpv audio output when the HDMI route changes
TrueHD/Atmos passthrough with Frame Rate Matching enabled never engaged
the receiver on the mpv backend (Ugoos SK1 -> Denon AVR): the display
mode switch settles 2 s before the audio output opens, but some HDMI
chains are still renegotiating audio then, so the IEC 61937 track binds
a half-negotiated route and the sink never locks onto the MAT
bitstream - for the whole session. The same file with Frame Rate
Matching off, or on ExoPlayer, engages Atmos immediately: media3
rebuilds its sink when the audio device set changes, while mpv's
ao_audiotrack never revisited a track once created.

libmpv v1.2.1 polls AudioTrack.getRoutedDevice from the AO thread and
reloads the AO when the route changes - or vanishes and rebinds - under
a live IEC or raw passthrough track, reopening it against the settled
route. PCM tracks are exempt, and audiotrack-route-reload=no is the
escape hatch.

close #2190
2026-08-31 10:05:08 +02:00
edde746 10f9139ac6 fix(audio): bitstream Dolby/DTS as raw tracks on the mpv path
Audio Passthrough on 2.18.0 played EAC3 silently on a Shield in front of
a Dolby-Digital-only receiver: mpv's IEC 61937 AudioTrack bypasses the
platform's Dolby transcoder, so the pre-packed DD+ bitstream reached a
sink that cannot decode it, drained at full rate, and no failure was
observable app-side. Disabling passthrough lost surround instead, because
multichannel PCM collapses to stereo in the platform mixer before the
HDMI re-encode.

libmpv v1.2.0 unwraps the IEC bursts and feeds AC3, E-AC3 and the DTS
core to a raw ENCODING_AC3/E_AC3/DTS track - the transport ExoPlayer and
Kodi use - keeping the platform decoder/transcoder in the path, with the
IEC track as fallback and audiotrack-raw-passthrough=no as an escape
hatch. TrueHD and DTS-HD MA stay on the 8-channel IEC carrier.

The audio-spdif route probe now accepts a codec when the route takes its
raw track (the same direct-playback tiering media3 uses; encoding-only
below API 29) or one of the IEC shapes, matching the AO's transport
ladder. A dev-only PLEZY_LOCAL_MPV_AAR override allows testing locally
built fork AARs before a release is pinned.

close #2177
close #2179
2026-08-30 19:22:14 +02:00
edde746 8f893e7a67 fix(player): demux Android direct play with media3 again
The in-app FFmpeg container demuxer classified every DTS variant as
plain DTS (profile-blind MIME mapping at the demux boundary), so
DTS-HD MA lost its lossless identity downstream, and it ignored
container display-aspect-ratio overrides. media3's extractors get both
right.

Delete the FFmpeg demuxer (JNI, extractor, and its setting); ExoPlayer
direct play demuxes with media3's DefaultExtractorsFactory again, and
mpv - the Android default - reads container display dimensions itself.

close #2124
close #2115
2026-08-26 18:59:28 +02:00
edde746 a1c98b9d27 feat(player): render Android mpv video on the MediaCodec plane
Android mpv drew every frame through vo=gpu: an ImageReader/GLES copy
pinned to 8-bit RGB0 with a 100 ms timed wait per frame, which
truncated 10-bit and HDR output, raced acquireLatestImage, and
performed badly on low-end TVs.

Video now scans out on a SurfaceFlinger video plane through the fork's
vo=mediacodec: decoder buffers are queued to the surface at their
target PTS (av_mediacodec_release_buffer_at_time), so 10-bit and HDR
dataspaces reach the display untouched and frame pacing no longer
depends on GL vsync. Subtitles and OSD render on a sibling transparent
surface presented for the same PTS, frame-locked by construction.

The plane takes decoder buffers only, so software-decoded video stays
on a GL vo, with a chain-failure watchdog re-initializing the output
when the plane refuses a stream mid-session. The GL fallback is vo=gpu
- gpu-next breaks the Tegra GLES linker (#2010) - and GpuVoPolicy
selects gpu-next only when Dolby Vision RPU reshaping needs
libplacebo. AV1 film grain is applied in the decoder
(vd-lavc-film-grain=cpu) because the GL raster grain fallback is not
available on this path.

Pins libmpv-android v1.1.3, which carries the fork patches this path
rides on: the vo itself, BT2020-PQ EGL window surfaces, GLES direct
rendering treated as slow (H.264 Hi10P software decode on Tegra went
from 0.3x realtime with wrong colors to 1.0x with correct 10-bit
output), and multichannel IEC61937 for ao_audiotrack.

Verified on Shield Pro (Tegra/GLES), Pixel 7 (Mali), Box R 4K Plus
(Amlogic armv7), and Galaxy Z Fold3 (Adreno): 520 play/teardown cycles
and 3 hours of continuous 4K HDR playback; HDR engagement confirmed
via SurfaceFlinger dataspace BT2020_ITU_PQ.
2026-08-26 18:59:19 +02:00
edde746 75c095a5b2 feat(player): demux direct-played containers with ffmpeg on Android
Direct play on Android kept accumulating container patches: AVI with
XviD packed-bitstream timestamps and missing VOL csd played broken,
ASF/WMV and MPEG-PS/VOB had no media3 extractor at all, and MKV needed
a custom extractor stack for zlib-compressed subtitles, LOAS/LATM
audio, cueless seeking, and font attachments.

Demux progressive containers with libavformat behind media3's
extractor API. An AVIO bridge serves libavformat from the
ExtractorInput: every position divergence defers into a RESULT_SEEK
round trip, header reads replay from a block cache while
avformat_open_input restarts, and avformat_seek_file executes seeks
with bounded loader round trips. Packets feed media3's TrackOutputs,
so decoders, passthrough carriers, Dolby Vision RPU/EL conversion
(dvh1 codecs string surfaced from DOVI side data), and the subtitle
pipeline are untouched: embedded fonts feed AssHandler over JNI, ASS
reaches libass as per-sample dialogue, SRT/VTT render as cues, VobSub
maps through media3's VobsubParser, and LOAS/LATM AAC unwraps via
LatmTrackOutput.

Under the default Auto preference FFmpeg demuxes AVI, ASF/WMV,
MPEG-PS, and Matroska/WebM and sits behind media3's list as an
any-container fallback; MP4/TS keep media3's extractors. A playback
setting exposes auto / FFmpeg first / media3 only, and the JNI
load-failure path falls back to stock media3. The custom Matroska
extractor stack and its reflection keeps are retired.

Verified on Pixel 7 (API 36), Box R 4K Plus TV (API 34), and SHIELD
TV (API 30): instrumentation playback suites, the minified R8
reachability gate, and an on-device container matrix with forward and
backward seek landings plus visual subtitle checks.

close #2052
2026-08-23 01:22:24 +02:00
edde746 5a0b595466 fix(android): play MKV files with late track metadata
MKV files whose Tracks element follows media clusters could direct-play with audio but no video. Upgrade Media3 to 1.11.0 and cover the extractor regression.

close #1947
2026-08-15 19:08:00 +02:00
edde746 d19ec625cd fix(tv): remove the background Watch Next refresh
2.13.0's ShelfRefreshWorker boots a second headless FlutterEngine in
the app process to refresh the launcher row every six hours. Its
foreground guard is checked only once at worker start, so launching the
app during a run leaves two engines sharing a low-RAM TV for up to 90
seconds, and a failed run retries with backoff. Suspected of
destabilizing the compositor on the 32-bit TCL panel in #1862. The tvOS
Top Shelf live fetch is unaffected and stays.

The foreground sync pipeline keeps the row fresh while the app runs, as
before 2.13.0. Updated devices still carry the persisted periodic job,
which would wake the process once more only to fail instantiating the
deleted class; the package-replaced receiver now cancels it.
2026-08-10 22:30:51 +02:00
edde746 69fadc220d chore: clean up code comments 2026-08-10 20:28:41 +02:00
edde746 de76c0a515 feat(tv): refresh the Watch Next row without the app open
The Watch Next row previously only updated while the app was in the
foreground, so it drifted stale until the next launch. A WorkManager
periodic job (6h, network-connected, KEEP) now runs a headless Flutter
engine executing `systemShelfBackgroundMain`, which mirrors the
cold-start profile bind from cached tokens (never prompting for a PIN),
fetches Continue Watching through the existing multi-server aggregation,
and republishes the shelf through the normal Watch Next pipeline.

The job is armed by a committed foreground sync, cancelled when the
shelf is cleared, and re-armed after boot or app update only when
persisted shelf state exists. It skips entirely while a foreground
engine holds the shelf lifecycle lease, both to defer to the live app
and to avoid two engines sharing the database in one process. The Dart
isolate always reports completion over `backgroundSyncComplete`; the
worker hard-caps the run at 90 seconds and destroys the engine on the
main thread.
2026-08-09 10:59:30 +02:00
edde746 7ce5a443fd feat(automotive): read the vehicle's driver-distraction state
Android Automotive tells an app when the car requires distraction optimization,
and Plezy never asked. A monitor now watches CarUxRestrictions and publishes the
verdict over the existing platform channel, where a single Dart gate answers
whether playback may start.

The car service is reached through the lifecycle-listener overload rather than
Car.createCar(Context). That overload blocks its caller for up to five seconds
polling ServiceManager, and on car-service death it reaches killClient(), which
kills the hosting process for any context that is not an Activity or a Service -
a crash in a system component would take the app down with it. Head units on
Android 9 and 10 predate the listener, so a legacy ServiceConnection is used
there, with the same identity guard on reconnect.

A vehicle that has not answered yet counts as restricted, and one deadline is
spent resolving it rather than one per request, so a wedged car service delays
playback once instead of on every open.
2026-08-06 03:45:09 +02:00
edde746 8aa836d106 ci(android): gate name-based reachability on an R8-minified variant
R8 only ever ran on `release`, so every automated gate in this repository
exercised code the shipped APK does not contain. Reflective lookups, JNI
callbacks and native library loading can all break under shrinking while
`flutter test`, the Robolectric suites and `connectedDebugAndroidTest`
stay green — which is exactly how #1703 shipped, with the bundled FFmpeg
audio renderer shrunk out of release builds for TrueHD and DTS-HD.

Add a `minified` build type that inherits release's shrinker
configuration but stays debuggable and debug-signed, so it is an ordinary
test artifact and never a publishable one. Three integration details
took a run each to find: the Flutter plugin copies app build types into
every plugin module, so library-level shrinking deleted the plugin entry
points that only GeneratedPluginRegistrant references; the harness must
not be shrunk or the runner disappears; and androidx.test has to survive
in the app under test, or the runner cannot link its own supertype and
the run reports zero tests instead of failing.

Instrumentation still defaults to `debug`, because only one build type
can host androidTest and the existing playback suites drive media3
builder APIs the app never calls, which R8 shrinks legitimately. The new
reachability test opts into the minified variant instead and touches no
builder API, so the only keeps it depends on are the ones under test.
Emptying proguard-rules.pro was verified to fail it.
2026-07-28 20:13:40 +02:00
edde746 e251273322 feat(downloads): warn about Android background restrictions 2026-07-26 04:24:55 +02:00
edde746 54a002f9b7 fix(android): consolidate native media decoders 2026-07-24 03:56:40 +02:00
edde746 658da37b48 build: make toolchain inputs reproducible 2026-07-24 03:40:06 +02:00
edde746 822657972d fix(android): enforce minSdk compatibility 2026-07-13 17:00:50 +02:00
edde746 03651d6c73 fix(exoplayer): unwrap AAC-LATM (LOAS) audio in MKV direct streams
close #1521
2026-07-10 23:23:44 +02:00
edde746 c9dc15b1d8 fix(android): improve libass frame timing 2026-06-24 01:26:46 +02:00
edde746 e1c0df3bd7 fix(ci): resolve sanity check failures 2026-06-14 06:55:58 +02:00
edde746 c89c88647e fix(android): package libmpv's NDK r29 libc++_shared.so deterministically 2026-06-12 01:51:51 +02:00
edde746 1adb89c6b3 feat(android): frame-accurate libass pipeline + optimized native core 2026-06-12 01:45:19 +02:00
edde746 804bea8e9a fix(android): keep only the active dynamic HDR metadata in DV bitstreams
close #1296
2026-06-10 15:39:51 +02:00
edde746 024af35bf5 chore: add native formatting checks 2026-05-01 05:56:49 +02:00
edde746 48ba923f47 chore: bump mpvkit and libmpv-android 2026-04-21 10:39:24 +02:00
edde746 f96342bd81 chore: bump libass to fp-3
close #893
2026-04-20 11:55:21 +02:00
edde746 81bac6ee9b fix(exoplayer): libass performance 2026-04-19 23:01:40 +02:00
edde746 dec2e1c9fc fix: add media3 hls dependency
close #881
2026-04-17 04:42:46 +02:00
edde746 4b05ed79ea fix: bump libmpv-android to v1.0.6 2026-03-22 04:59:31 +01:00
edde746 4365e70a78 fix(android): libmpv failed init reinit 2026-03-21 16:51:25 +01:00
edde746 d355e07e10 refactor: switch to libmpv-android fork, fix ANR and track selection 2026-03-21 08:53:08 +01:00
edde746 82f0d46acc fix: update libass 2026-03-20 09:07:17 +01:00
edde746 fbf6fc988b chore: update libass-android 2026-03-19 19:52:05 +01:00
edde746 ad0623334c feat: approximate seeking for MKV files without Cues 2026-03-18 08:52:14 +01:00
edde746 9798486724 chore: bump cronet 2026-03-15 22:38:06 +01:00
edde746 af0de5c0a9 fix: switch from OkHttp to Cronet 2026-03-15 22:00:51 +01:00
edde746 d01628a262 fix(android): use OkHttp connection pooling to prevent high-bitrate stutter
Replace DefaultHttpDataSource with OkHttpDataSource for TCP connection
reuse on buffer refill. Increase buffer sizes for devices with >=1GB RAM.
2026-03-13 17:23:06 +01:00
edde746 af2ac2c993 fix: suppress ASS GL thread crash, update ass-media to 0.4.0 2026-03-10 06:41:43 +01:00
edde746 317d5dbdd9 feat: DV Profile 7→8.1 RPU conversion via libdovi
Convert Dolby Vision Profile 7 RPU NALUs to Profile 8.1 using libdovi
native bridge, preserving dynamic tone mapping on devices with DV8
decoders. Falls back to HEVC stripping when no DV8 decoder is present.

- Add JNI bridge (dovi_bridge.cpp) wrapping libdovi's RPU converter
- Download prebuilt libdovi from edde746/libdovi-builds releases
- DoviConvertingTrackOutput: DV81 mode converts RPUs, HEVC_STRIP strips
- Retry logic: if native DV7 decoding fails, auto-retry with conversion
- Performance overlay shows active DV conversion mode
2026-03-05 11:11:57 +01:00
edde746 229d6b24e1 feat: crash log reporting 2026-03-02 07:29:21 +01:00
edde746 2405cd179b fix: use forked libass-android with font buffering fix 2026-02-28 19:00:55 +01:00
edde746 637a3e9e37 chore: bump media3 1.5.1 → 1.9.2, drop unused media3-session 2026-02-20 03:32:38 +01:00
edde746 aae1b1c45c refactor: clean up release script and fastlane configs 2026-02-18 09:15:20 +01:00
edde746 0791764de9 feat: lower minSdk to 25 2026-02-13 14:00:24 +01:00
edde746 3d255f9920 fix(exoplayer): apply subtitle styling 2026-02-06 05:46:07 +01:00
edde746 ea35892502 feat: android tv watch next
close #276
2026-01-31 08:18:36 +01:00
edde746 943f7dab08 feat: exoplayer 2026-01-23 18:29:35 +01:00
edde746 e0fd77d7de android 2025-12-02 15:46:19 +01:00
edde746 f3e91ba789 fix(actions): android build 2025-10-22 20:01:56 +02:00
edde746 7f9cee4222 fix: unify version source 2025-10-22 18:30:25 +02:00
edde746 a6effe208b squash 2025-10-22 12:45:29 +02:00