-
Notifications
You must be signed in to change notification settings - Fork 3.6k
[video_player] Improve macOS frame management #5078
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
8dde36b
a1d46cb
eba10b9
b24e792
1a489e5
419859d
109ca02
1a59cc1
c9ca774
b17b65a
8295980
08d7497
8f7dcbe
b6a55a0
9ce3e8c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
- Loading branch information
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,26 @@ | ||
| // Copyright 2013 The Flutter Authors. All rights reserved. | ||
| // Use of this source code is governed by a BSD-style license that can be | ||
| // found in the LICENSE file. | ||
|
|
||
| #import <Foundation/Foundation.h> | ||
|
|
||
| // A cross-platform display link abstraction. | ||
| @interface FVPDisplayLink : NSObject | ||
|
|
||
| /** | ||
| * Whether the display link is currently running (i.e., firing events). | ||
| * | ||
| * Defaults to NO. | ||
| */ | ||
| @property(nonatomic, assign) BOOL running; | ||
|
|
||
| /** | ||
| * Initializes a display link that calls the given callback when fired. | ||
| * | ||
| * The display link starts paused, so must be started, by setting 'running' to YES, before the callback will fire. | ||
| */ | ||
| - (instancetype)initWithCallback:(void (^)(void))callback NS_DESIGNATED_INITIALIZER; | ||
|
|
||
| - (instancetype)init NS_UNAVAILABLE; | ||
|
|
||
| @end |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -9,6 +9,7 @@ | |
| #import <GLKit/GLKit.h> | ||
|
|
||
| #import "AVAssetTrackUtils.h" | ||
| #import "FVPDisplayLink.h" | ||
| #import "messages.g.h" | ||
|
|
||
| #if !__has_feature(objc_arc) | ||
|
|
@@ -22,9 +23,12 @@ @interface FVPFrameUpdater : NSObject | |
| @property(nonatomic, weak) AVPlayerItemVideoOutput *videoOutput; | ||
| // The last time that has been validated as avaliable according to hasNewPixelBufferForItemTime:. | ||
| @property(nonatomic, assign) CMTime lastKnownAvailableTime; | ||
| #if TARGET_OS_IOS | ||
| - (void)onDisplayLink:(CADisplayLink *)link; | ||
| #endif | ||
| // If YES, the engine is informed that a new texture is available any time the display link | ||
| // callback is fired, regardless of the videoOutput state. | ||
| // | ||
| // TODO(stuartmorgan): Investigate removing this; it exists only to preserve existing iOS behavior | ||
| // while implementing macOS, but iOS should very likely be doing the check as well. | ||
| @property(nonatomic, assign) BOOL skipBufferAvailabilityCheck; | ||
| @end | ||
|
|
||
| @implementation FVPFrameUpdater | ||
|
|
@@ -36,35 +40,24 @@ - (FVPFrameUpdater *)initWithRegistry:(NSObject<FlutterTextureRegistry> *)regist | |
| return self; | ||
| } | ||
|
|
||
| #if TARGET_OS_IOS | ||
| - (void)onDisplayLink:(CADisplayLink *)link { | ||
| // TODO(stuartmorgan): Investigate switching this to displayLinkFired; iOS may also benefit from | ||
| // the availability check there. | ||
| [_registry textureFrameAvailable:_textureId]; | ||
| } | ||
| #endif | ||
|
|
||
| - (void)displayLinkFired { | ||
| // Only report a new frame if one is actually available. | ||
| CMTime outputItemTime = [self.videoOutput itemTimeForHostTime:CACurrentMediaTime()]; | ||
| if ([self.videoOutput hasNewPixelBufferForItemTime:outputItemTime]) { | ||
| _lastKnownAvailableTime = outputItemTime; | ||
| // Only report a new frame if one is actually available, or the check is being skipped. | ||
| BOOL reportFrame = NO; | ||
| if (self.skipBufferAvailabilityCheck) { | ||
| reportFrame = YES; | ||
| } else { | ||
| CMTime outputItemTime = [self.videoOutput itemTimeForHostTime:CACurrentMediaTime()]; | ||
| if ([self.videoOutput hasNewPixelBufferForItemTime:outputItemTime]) { | ||
| _lastKnownAvailableTime = outputItemTime; | ||
| reportFrame = YES; | ||
| } | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Reset in else?
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Why would we reset |
||
| } | ||
| if (reportFrame) { | ||
| [_registry textureFrameAvailable:_textureId]; | ||
| } | ||
| } | ||
| @end | ||
|
|
||
| #if TARGET_OS_OSX | ||
| static CVReturn DisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeStamp *now, | ||
| const CVTimeStamp *outputTime, CVOptionFlags flagsIn, | ||
| CVOptionFlags *flagsOut, void *displayLinkSource) { | ||
| // Trigger the main-thread dispatch queue, to drive a frame update check. | ||
| __weak dispatch_source_t source = (__bridge dispatch_source_t)displayLinkSource; | ||
| dispatch_source_merge_data(source, 1); | ||
| return kCVReturnSuccess; | ||
| } | ||
| #endif | ||
|
|
||
| @interface FVPDefaultPlayerFactory : NSObject <FVPPlayerFactory> | ||
| @end | ||
|
|
||
|
|
@@ -96,16 +89,7 @@ @interface FVPVideoPlayer : NSObject <FlutterTexture, FlutterStreamHandler> | |
| @property(nonatomic) BOOL isLooping; | ||
| @property(nonatomic, readonly) BOOL isInitialized; | ||
| @property(nonatomic) FVPFrameUpdater *frameUpdater; | ||
| // TODO(stuartmorgan): Extract and abstract the display link to remove all the display-link-related | ||
| // ifdefs from this file. | ||
| #if TARGET_OS_OSX | ||
| // The display link to trigger frame reads from the video player. | ||
| @property(nonatomic, assign) CVDisplayLinkRef displayLink; | ||
| // A dispatch source to move display link callbacks to the main thread. | ||
| @property(nonatomic, strong) dispatch_source_t displayLinkSource; | ||
| #else | ||
| @property(nonatomic) CADisplayLink *displayLink; | ||
| #endif | ||
| @property(nonatomic) FVPDisplayLink *displayLink; | ||
|
|
||
| - (instancetype)initWithURL:(NSURL *)url | ||
| frameUpdater:(FVPFrameUpdater *)frameUpdater | ||
|
|
@@ -255,27 +239,15 @@ - (void)createVideoOutputAndDisplayLink:(FVPFrameUpdater *)frameUpdater { | |
| }; | ||
| _videoOutput = [[AVPlayerItemVideoOutput alloc] initWithPixelBufferAttributes:pixBuffAttributes]; | ||
|
|
||
| #if TARGET_OS_OSX | ||
|
|
||
| frameUpdater.videoOutput = _videoOutput; | ||
| // Create and start the main-thread dispatch queue to drive frameUpdater. | ||
| self.displayLinkSource = | ||
| dispatch_source_create(DISPATCH_SOURCE_TYPE_DATA_ADD, 0, 0, dispatch_get_main_queue()); | ||
| dispatch_source_set_event_handler(self.displayLinkSource, ^() { | ||
| @autoreleasepool { | ||
| [frameUpdater displayLinkFired]; | ||
| } | ||
| }); | ||
| dispatch_resume(self.displayLinkSource); | ||
| if (CVDisplayLinkCreateWithActiveCGDisplays(&_displayLink) == kCVReturnSuccess) { | ||
| CVDisplayLinkSetOutputCallback(_displayLink, &DisplayLinkCallback, | ||
| (__bridge void *)(self.displayLinkSource)); | ||
| } | ||
| #else | ||
| _displayLink = [CADisplayLink displayLinkWithTarget:frameUpdater | ||
| selector:@selector(onDisplayLink:)]; | ||
| [_displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes]; | ||
| _displayLink.paused = YES; | ||
| #if TARGET_OS_IOS | ||
| // See TODO on this property in FVPFrameUpdater. | ||
| frameUpdater.skipBufferAvailabilityCheck = YES; | ||
| #endif | ||
| self.displayLink = [[FVPDisplayLink alloc] initWithCallback:^() { | ||
| [frameUpdater displayLinkFired]; | ||
| }]; | ||
| } | ||
|
|
||
| - (instancetype)initWithURL:(NSURL *)url | ||
|
|
@@ -428,23 +400,7 @@ - (void)updatePlayingState { | |
| } else { | ||
| [_player pause]; | ||
| } | ||
| #if TARGET_OS_OSX | ||
| if (_displayLink) { | ||
| if (_isPlaying) { | ||
| NSScreen *screen = self.registrar.view.window.screen; | ||
| if (screen) { | ||
| CGDirectDisplayID viewDisplayID = | ||
| (CGDirectDisplayID)[screen.deviceDescription[@"NSScreenNumber"] unsignedIntegerValue]; | ||
| CVDisplayLinkSetCurrentCGDisplay(_displayLink, viewDisplayID); | ||
| } | ||
| CVDisplayLinkStart(_displayLink); | ||
| } else { | ||
| CVDisplayLinkStop(_displayLink); | ||
| } | ||
| } | ||
| #else | ||
| _displayLink.paused = !_isPlaying; | ||
| #endif | ||
| _displayLink.running = _isPlaying; | ||
| } | ||
|
|
||
| - (void)setupEventSinkIfReadyToPlay { | ||
|
|
@@ -615,16 +571,7 @@ - (void)disposeSansEventChannel { | |
|
|
||
| _disposed = YES; | ||
| [_playerLayer removeFromSuperlayer]; | ||
| #if TARGET_OS_OSX | ||
| if (_displayLink) { | ||
| CVDisplayLinkStop(_displayLink); | ||
| CVDisplayLinkRelease(_displayLink); | ||
| _displayLink = NULL; | ||
| } | ||
| dispatch_source_cancel(_displayLinkSource); | ||
| #else | ||
| [_displayLink invalidate]; | ||
| #endif | ||
| _displayLink = nil; | ||
| [self removeKeyValueObservers]; | ||
|
|
||
| [self.player replaceCurrentItemWithPlayerItem:nil]; | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,65 @@ | ||
| // Copyright 2013 The Flutter Authors. All rights reserved. | ||
| // Use of this source code is governed by a BSD-style license that can be | ||
| // found in the LICENSE file. | ||
|
|
||
| #import "../FVPDisplayLink.h" | ||
|
|
||
| #import <CoreAnimation/CoreAnimation.h> | ||
| #import <Foundation/Foundation.h> | ||
|
|
||
| /** A proxy object to act as a CADisplayLink target, to avoid retain loops. */ | ||
stuartmorgan-g marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| @interface FVPDisplayLinkTarget : NSObject | ||
| @property(nonatomic) (void (^)(void))callback; | ||
|
|
||
| /** Initializes a target object that runs the given callback when onDisplayLink: is called. */ | ||
| - (instancetype)initWithCallback:(void (^)(void))callback; | ||
|
|
||
| /** Method to be called when a CADisplayLink fires. */ | ||
| - (void)onDisplayLink:(CADisplayLink *)link; | ||
| @end | ||
|
|
||
| @implementation FVPDisplayLinkTarget | ||
| - (instancetype)initWithCallback:(void (^)(void))callback { | ||
| self = [super init]; | ||
| if (self) { | ||
| _callback = callback; | ||
| } | ||
| return self; | ||
| } | ||
|
|
||
| - (void)onDisplayLink:(CADisplayLink *)link { | ||
| self.callback(); | ||
| } | ||
| @end | ||
|
|
||
| #pragma mark - | ||
|
|
||
| @interface FVPDisplayLink() | ||
| // The underlying display link implementation. | ||
| @property(nonatomic) CADisplayLink *displayLink; | ||
| @property(nonatomic) FVPDisplayLinkTarget *target; | ||
| @end | ||
|
|
||
| @implementation FVPDisplayLink | ||
|
|
||
| - (instancetype)initWithCallback:(void (^)(void))callback { | ||
| self = [super init]; | ||
| if (self) { | ||
| _target = [[FVPDisplayLinkTarget alloc] initWithCallback:callback]; | ||
| _displayLink = [CADisplayLink displayLinkWithTarget:_target | ||
| selector:@selector(onDisplayLink:)]; | ||
| [_displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes]; | ||
| _displayLink.paused = YES; | ||
| } | ||
| return self; | ||
| } | ||
|
|
||
| - (void)dealloc { | ||
| [_displayLink invalidate]; | ||
| } | ||
|
|
||
| - (void)setRunning:(BOOL)running { | ||
| self.displayLink.paused = !running; | ||
stuartmorgan-g marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| } | ||
|
|
||
| @end | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,72 @@ | ||
| // Copyright 2013 The Flutter Authors. All rights reserved. | ||
| // Use of this source code is governed by a BSD-style license that can be | ||
| // found in the LICENSE file. | ||
|
|
||
| #import "../FVPDisplayLink.h" | ||
|
|
||
| #import <CoreVideo/CoreVideo.h> | ||
| #import <Foundation/Foundation.h> | ||
|
|
||
| @interface FVPDisplayLink() | ||
| // The underlying display link implementation. | ||
| @property(nonatomic, assign) CVDisplayLinkRef displayLink; | ||
| // A dispatch source to move display link callbacks to the main thread. | ||
| @property(nonatomic, strong) dispatch_source_t displayLinkSource; | ||
| @end | ||
|
|
||
| static CVReturn DisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeStamp *now, | ||
| const CVTimeStamp *outputTime, CVOptionFlags flagsIn, | ||
| CVOptionFlags *flagsOut, void *displayLinkSource) { | ||
| // Trigger the main-thread dispatch queue, to drive the callback there. | ||
| __weak dispatch_source_t source = (__bridge dispatch_source_t)displayLinkSource; | ||
| dispatch_source_merge_data(source, 1); | ||
| return kCVReturnSuccess; | ||
| } | ||
|
|
||
| @implementation FVPDisplayLink | ||
|
|
||
| - (instancetype)initWithCallback:(void (^)(void))callback { | ||
| self = [super init]; | ||
| if (self) { | ||
| // Create and start the main-thread dispatch queue to drive frameUpdater. | ||
| _displayLinkSource = | ||
| dispatch_source_create(DISPATCH_SOURCE_TYPE_DATA_ADD, 0, 0, dispatch_get_main_queue()); | ||
| dispatch_source_set_event_handler(_displayLinkSource, ^() { | ||
| @autoreleasepool { | ||
| callback(); | ||
| } | ||
| }); | ||
| dispatch_resume(_displayLinkSource); | ||
| if (CVDisplayLinkCreateWithActiveCGDisplays(&_displayLink) == kCVReturnSuccess) { | ||
| CVDisplayLinkSetOutputCallback(_displayLink, &DisplayLinkCallback, | ||
| (__bridge void *)(_displayLinkSource)); | ||
| } | ||
| } | ||
| return self; | ||
| } | ||
|
|
||
| - (void)dealloc { | ||
| CVDisplayLinkStop(_displayLink); | ||
| CVDisplayLinkRelease(_displayLink); | ||
| _displayLink = NULL; | ||
|
|
||
| dispatch_source_cancel(_displayLinkSource); | ||
| } | ||
|
|
||
| - (void)setRunning:(BOOL)running { | ||
| if (running) { | ||
| // TODO(stuartmorgan): Move this to init + a screen change listener; this won't correctly | ||
| // handle windows being dragged to another screen until the next pause/play cycle. | ||
| NSScreen *screen = self.registrar.view.window.screen; | ||
| if (screen) { | ||
| CGDirectDisplayID viewDisplayID = | ||
| (CGDirectDisplayID)[screen.deviceDescription[@"NSScreenNumber"] unsignedIntegerValue]; | ||
| CVDisplayLinkSetCurrentCGDisplay(self.displayLink, viewDisplayID); | ||
| } | ||
| CVDisplayLinkStart(self.displayLink); | ||
| } else { | ||
| CVDisplayLinkStop(self.displayLink); | ||
| } | ||
| } | ||
|
|
||
| @end |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -14,7 +14,9 @@ Downloaded by pub (not CocoaPods). | |
| s.author = { 'Flutter Dev Team' => '[email protected]' } | ||
| s.source = { :http => 'https://github.com/flutter/packages/tree/main/packages/video_player/video_player_avfoundation' } | ||
| s.documentation_url = 'https://pub.dev/packages/video_player' | ||
| s.source_files = 'Classes/**/*' | ||
| s.source_files = 'Classes/*' | ||
| s.ios.source_files = 'Classes/ios/*' | ||
| s.osx.source_files = 'Classes/macos/*' | ||
| s.public_header_files = 'Classes/**/*.h' | ||
| s.ios.dependency 'Flutter' | ||
| s.osx.dependency 'FlutterMacOS' | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.