diff --git a/packages/video_player/video_player_avfoundation/CHANGELOG.md b/packages/video_player/video_player_avfoundation/CHANGELOG.md index 1508c0bbfe22..a6e14b8ee319 100644 --- a/packages/video_player/video_player_avfoundation/CHANGELOG.md +++ b/packages/video_player/video_player_avfoundation/CHANGELOG.md @@ -1,3 +1,7 @@ +## 2.9.5 + +* Fixes washed-out HDR video playback on iOS by declaring BT.709 color properties on the video output so AVFoundation tone-maps HDR sources into the Flutter texture. + ## 2.9.4 * Ensures that the display link does not continue requesting frames after a player is disposed. diff --git a/packages/video_player/video_player_avfoundation/darwin/RunnerTests/TestClasses.swift b/packages/video_player/video_player_avfoundation/darwin/RunnerTests/TestClasses.swift index 3856995b855c..1bb988b1b7e2 100644 --- a/packages/video_player/video_player_avfoundation/darwin/RunnerTests/TestClasses.swift +++ b/packages/video_player/video_player_avfoundation/darwin/RunnerTests/TestClasses.swift @@ -198,6 +198,7 @@ final class StubFVPAVFactory: NSObject, FVPAVFactory { let player: AVPlayer let playerItem: FVPAVPlayerItem let pixelBufferSource: FVPPixelBufferSource? + private(set) var lastOutputSettings: [String: Any]? #if os(iOS) var audioSession: FVPAVAudioSession #endif @@ -231,7 +232,8 @@ final class StubFVPAVFactory: NSObject, FVPAVFactory { return self.player } - func videoOutput(pixelBufferAttributes attributes: [String: Any]) -> FVPPixelBufferSource { + func videoOutput(outputSettings: [String: Any]) -> FVPPixelBufferSource { + lastOutputSettings = outputSettings return pixelBufferSource ?? TestPixelBufferSource() } diff --git a/packages/video_player/video_player_avfoundation/darwin/RunnerTests/VideoPlayerTests.swift b/packages/video_player/video_player_avfoundation/darwin/RunnerTests/VideoPlayerTests.swift index c965a70a8430..b64477914963 100644 --- a/packages/video_player/video_player_avfoundation/darwin/RunnerTests/VideoPlayerTests.swift +++ b/packages/video_player/video_player_avfoundation/darwin/RunnerTests/VideoPlayerTests.swift @@ -802,6 +802,28 @@ private let hlsAudioTestURI = } } + @Test func videoOutputIsConfiguredWithBT709ColorProperties() throws { + let item = StubPlayerItem() + let stubAVFactory = StubFVPAVFactory(player: nil, playerItem: item, pixelBufferSource: nil) + let stubViewProvider = StubViewProvider() + let _ = FVPVideoPlayer( + playerItem: item, avFactory: stubAVFactory, viewProvider: stubViewProvider) + + // BT.709 color properties are required so AVFoundation tone-maps HDR sources + // into the Flutter texture. Without them HDR samples arrive unconverted and + // render washed out. See flutter/flutter#91241. + let settings = try #require(stubAVFactory.lastOutputSettings) + let colorProperties = try #require( + settings[AVVideoColorPropertiesKey] as? [String: Any]) + #expect( + colorProperties[AVVideoColorPrimariesKey] as? String == AVVideoColorPrimaries_ITU_R_709_2) + #expect( + colorProperties[AVVideoTransferFunctionKey] as? String + == AVVideoTransferFunction_ITU_R_709_2) + #expect( + colorProperties[AVVideoYCbCrMatrixKey] as? String == AVVideoYCbCrMatrix_ITU_R_709_2) + } + // MARK: - Helper Methods /// Creates a plugin with the given dependencies, and default stubs for any that aren't provided, diff --git a/packages/video_player/video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation/FVPAVFactory.m b/packages/video_player/video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation/FVPAVFactory.m index 4cee990adb1f..907072928d39 100644 --- a/packages/video_player/video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation/FVPAVFactory.m +++ b/packages/video_player/video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation/FVPAVFactory.m @@ -84,10 +84,10 @@ @interface FVPDefaultAVPlayerItemVideoOutput : NSObject @end @implementation FVPDefaultAVPlayerItemVideoOutput -- (instancetype)initWithPixelBufferAttributes:(NSDictionary *)attributes { +- (instancetype)initWithOutputSettings:(NSDictionary *)outputSettings { self = [super init]; if (self) { - _videoOutput = [[AVPlayerItemVideoOutput alloc] initWithPixelBufferAttributes:attributes]; + _videoOutput = [[AVPlayerItemVideoOutput alloc] initWithOutputSettings:outputSettings]; } return self; } @@ -150,9 +150,9 @@ - (AVPlayer *)playerWithPlayerItem:(NSObject *)playerItem { return [AVPlayer playerWithPlayerItem:((FVPDefaultAVPlayerItem *)playerItem).playerItem]; } -- (NSObject *)videoOutputWithPixelBufferAttributes: - (NSDictionary *)attributes { - return [[FVPDefaultAVPlayerItemVideoOutput alloc] initWithPixelBufferAttributes:attributes]; +- (NSObject *)videoOutputWithOutputSettings: + (NSDictionary *)outputSettings { + return [[FVPDefaultAVPlayerItemVideoOutput alloc] initWithOutputSettings:outputSettings]; } #if TARGET_OS_IOS diff --git a/packages/video_player/video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation/FVPVideoPlayer.m b/packages/video_player/video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation/FVPVideoPlayer.m index 2270120378d5..45e972137804 100644 --- a/packages/video_player/video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation/FVPVideoPlayer.m +++ b/packages/video_player/video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation/FVPVideoPlayer.m @@ -138,12 +138,19 @@ - (instancetype)initWithPlayerItem:(NSObject *)item _player = [avFactory playerWithPlayerItem:item]; _player.actionAtItemEnd = AVPlayerActionAtItemEndNone; - // Configure output. - NSDictionary *pixBuffAttributes = @{ + // Configure output. AVVideoColorPropertiesKey must be declared on the output settings (not on + // pixel buffer attributes, where AVFoundation silently ignores it) so that HDR sources are + // tone-mapped to BT.709 SDR for the Flutter texture. + NSDictionary *outputSettings = @{ + AVVideoColorPropertiesKey : @{ + AVVideoColorPrimariesKey : AVVideoColorPrimaries_ITU_R_709_2, + AVVideoTransferFunctionKey : AVVideoTransferFunction_ITU_R_709_2, + AVVideoYCbCrMatrixKey : AVVideoYCbCrMatrix_ITU_R_709_2, + }, (id)kCVPixelBufferPixelFormatTypeKey : @(kCVPixelFormatType_32BGRA), - (id)kCVPixelBufferIOSurfacePropertiesKey : @{} + (id)kCVPixelBufferIOSurfacePropertiesKey : @{}, }; - _pixelBufferSource = [avFactory videoOutputWithPixelBufferAttributes:pixBuffAttributes]; + _pixelBufferSource = [avFactory videoOutputWithOutputSettings:outputSettings]; [asset loadValuesAsynchronouslyForKeys:@[ @"tracks" ] completionHandler:assetCompletionHandler]; diff --git a/packages/video_player/video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation/include/video_player_avfoundation/FVPAVFactory.h b/packages/video_player/video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation/include/video_player_avfoundation/FVPAVFactory.h index 90d4fa3ed1b9..6e80b8a5b67f 100644 --- a/packages/video_player/video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation/include/video_player_avfoundation/FVPAVFactory.h +++ b/packages/video_player/video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation/include/video_player_avfoundation/FVPAVFactory.h @@ -94,10 +94,12 @@ NS_ASSUME_NONNULL_BEGIN /// Creates and returns an AVPlayer instance with the specified player item. - (AVPlayer *)playerWithPlayerItem:(NSObject *)playerItem; -/// Creates and returns a wrapped AVPlayerItemVideoOutput instance with the specified pixel buffer -/// attributes. -- (NSObject *)videoOutputWithPixelBufferAttributes: - (NSDictionary *)attributes; +/// Creates and returns a wrapped AVPlayerItemVideoOutput instance with the specified output +/// settings. The dictionary may contain both video output keys (e.g. AVVideoColorPropertiesKey) +/// and CVPixelBuffer attribute keys (e.g. kCVPixelBufferPixelFormatTypeKey), as accepted by +/// -[AVPlayerItemVideoOutput initWithOutputSettings:]. +- (NSObject *)videoOutputWithOutputSettings: + (NSDictionary *)outputSettings NS_SWIFT_NAME(videoOutput(outputSettings:)); #if TARGET_OS_IOS /// Returns the AVAudioSession shared instance, wrapped in the protocol. diff --git a/packages/video_player/video_player_avfoundation/pubspec.yaml b/packages/video_player/video_player_avfoundation/pubspec.yaml index f14fefb73326..f7c5c9edf5e3 100644 --- a/packages/video_player/video_player_avfoundation/pubspec.yaml +++ b/packages/video_player/video_player_avfoundation/pubspec.yaml @@ -2,7 +2,7 @@ name: video_player_avfoundation description: iOS and macOS implementation of the video_player plugin. repository: https://github.com/flutter/packages/tree/main/packages/video_player/video_player_avfoundation issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+video_player%22 -version: 2.9.4 +version: 2.9.5 environment: sdk: ^3.10.0