Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Factor out display link code for easier use
  • Loading branch information
stuartmorgan-g committed Oct 5, 2023
commit a1d46cbab6723fda88586bedf9b8e2c82c8886b3
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
Expand Up @@ -9,6 +9,7 @@
#import <GLKit/GLKit.h>

#import "AVAssetTrackUtils.h"
#import "FVPDisplayLink.h"
#import "messages.g.h"

#if !__has_feature(objc_arc)
Expand All @@ -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
Expand All @@ -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;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reset in else?

else {
  _lastKnownAvailableTime = kCMTimeInvalid;
  reportFrame = NO;
}

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

reportFrame is a local initialized to NO, so there's no need to set it to NO.

Why would we reset _lastKnownAvailableTime? If we don't have a new time, then the last known time is still the last known time.

}
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

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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];
Expand Down
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. */
@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;
}

@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
Expand Up @@ -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'
Expand Down