diff --git a/packages/video_player/video_player_avfoundation/CHANGELOG.md b/packages/video_player/video_player_avfoundation/CHANGELOG.md index af4f99d5632..8ef74b3c441 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.7 + +* Forces tone-mapping to SDR on iOS to prevent washed-out HDR video playback. + ## 2.9.6 * Adds a Swift version to fix a potential build issue when using CocoaPods. 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 2dbef61b2f4..24fcc1ca38f 100644 --- a/packages/video_player/video_player_avfoundation/darwin/RunnerTests/TestClasses.swift +++ b/packages/video_player/video_player_avfoundation/darwin/RunnerTests/TestClasses.swift @@ -200,6 +200,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 @@ -233,7 +234,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 7d776258c4c..5d0784d636a 100644 --- a/packages/video_player/video_player_avfoundation/darwin/RunnerTests/VideoPlayerTests.swift +++ b/packages/video_player/video_player_avfoundation/darwin/RunnerTests/VideoPlayerTests.swift @@ -755,6 +755,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_objc/FVPAVFactory.m b/packages/video_player/video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation_objc/FVPAVFactory.m index 4053bd0dbaa..d87a7c46c1b 100644 --- a/packages/video_player/video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation_objc/FVPAVFactory.m +++ b/packages/video_player/video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation_objc/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_objc/FVPVideoPlayer.m b/packages/video_player/video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation_objc/FVPVideoPlayer.m index 04ce60e6a98..7f3fa3f2f77 100644 --- a/packages/video_player/video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation_objc/FVPVideoPlayer.m +++ b/packages/video_player/video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation_objc/FVPVideoPlayer.m @@ -138,12 +138,20 @@ - (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. + // See https://github.com/flutter/flutter/issues/91241 + 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_objc/include/video_player_avfoundation_objc/FVPAVFactory.h b/packages/video_player/video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation_objc/include/video_player_avfoundation_objc/FVPAVFactory.h index 90d4fa3ed1b..6e80b8a5b67 100644 --- a/packages/video_player/video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation_objc/include/video_player_avfoundation_objc/FVPAVFactory.h +++ b/packages/video_player/video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation_objc/include/video_player_avfoundation_objc/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 9b462f0376f..b60381dc3c8 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.6 +version: 2.9.7 environment: sdk: ^3.10.0