fix(player): keep HDR/Dolby Vision through zoom on iOS and tvOS

Nonzero mpv video-zoom flips vo_avfoundation into a per-frame Core
Image re-render that destroys HDR/DV passthrough - DV frames render
near-black on tvOS (verified on Apple TV 4K, DV P7->8.1 content:
panel luma mean 0.0 zoomed vs 87-103 unzoomed at locked exposure).

Zoom now scales the AVSampleBufferDisplayLayer itself (a
sublayerTransform on the container is ignored by the video plane)
via the existing Player.setVideoZoom seam, and VideoFilterManager
pins the mpv property to 0 on backends with native zoom. The layer
tree at 100% stays identical to before: clipping engages only while
zoomed, and updateFrame sizes the layer via bounds/position, which
frame= decomposes to anyway.

macOS keeps the property path (gpu-next zooms losslessly in-shader);
Android is untouched.
This commit is contained in:
edde746
2026-08-10 14:17:40 +02:00
parent f2ef15806a
commit a0269c6feb
8 changed files with 106 additions and 7 deletions
+6 -2
View File
@@ -326,8 +326,12 @@ abstract class Player {
/// here and scale via `panscan`/`video-aspect-override` properties instead.
Future<void> setBoxFitMode(int mode);
/// Apply custom zoom to the native video layer. No-op on mpv backends,
/// which zoom via the `video-zoom` property.
/// Apply custom zoom to the native video layer.
///
/// ExoPlayer scales its frame layout; iOS/tvOS scale the AVFoundation video
/// container (mpv's `video-zoom` would force vo_avfoundation's Core Image
/// path and destroy HDR/Dolby Vision passthrough). Other mpv backends are a
/// no-op here and zoom via the `video-zoom` property.
Future<void> setVideoZoom(double scale);
/// Aggregated native playback stats (codecs, dimensions, dropped frames…).
+1 -1
View File
@@ -1100,7 +1100,7 @@ abstract class PlayerBase with PlayerStreamControllersMixin implements Player {
Future<void> setBoxFitMode(int mode) async {}
@override
// ignore: no-empty-block - base no-op, mpv zooms via the video-zoom property
// ignore: no-empty-block - base no-op, non-Apple mpv zooms via the video-zoom property
Future<void> setVideoZoom(double scale) async {}
@override
+11
View File
@@ -1013,6 +1013,17 @@ class PlayerNative extends PlayerBase {
}
}
/// iOS/tvOS scale the native video container instead of mpv's `video-zoom`:
/// on the avfoundation VO a nonzero zoom re-renders every frame through
/// Core Image, which destroys HDR/Dolby Vision passthrough (DV renders
/// near-black on tvOS). macOS keeps the property path — gpu-next zooms
/// losslessly in-shader.
@override
Future<void> setVideoZoom(double scale) async {
if (_nativeCoreUnavailable || audioOnly || !Platform.isIOS || !initialized) return;
await invoke('setVideoZoom', {'scale': scale});
}
@override
Future<bool> setVideoFrameRate(
double fps,