Skip to content

Commit a1d46cb

Browse files
Factor out display link code for easier use
1 parent 8dde36b commit a1d46cb

File tree

5 files changed

+195
-83
lines changed

5 files changed

+195
-83
lines changed
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
#import <Foundation/Foundation.h>
6+
7+
// A cross-platform display link abstraction.
8+
@interface FVPDisplayLink : NSObject
9+
10+
/**
11+
* Whether the display link is currently running (i.e., firing events).
12+
*
13+
* Defaults to NO.
14+
*/
15+
@property(nonatomic, assign) BOOL running;
16+
17+
/**
18+
* Initializes a display link that calls the given callback when fired.
19+
*
20+
* The display link starts paused, so must be started, by setting 'running' to YES, before the callback will fire.
21+
*/
22+
- (instancetype)initWithCallback:(void (^)(void))callback NS_DESIGNATED_INITIALIZER;
23+
24+
- (instancetype)init NS_UNAVAILABLE;
25+
26+
@end

packages/video_player/video_player_avfoundation/darwin/Classes/FVPVideoPlayerPlugin.m

Lines changed: 29 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
#import <GLKit/GLKit.h>
1010

1111
#import "AVAssetTrackUtils.h"
12+
#import "FVPDisplayLink.h"
1213
#import "messages.g.h"
1314

1415
#if !__has_feature(objc_arc)
@@ -22,9 +23,12 @@ @interface FVPFrameUpdater : NSObject
2223
@property(nonatomic, weak) AVPlayerItemVideoOutput *videoOutput;
2324
// The last time that has been validated as avaliable according to hasNewPixelBufferForItemTime:.
2425
@property(nonatomic, assign) CMTime lastKnownAvailableTime;
25-
#if TARGET_OS_IOS
26-
- (void)onDisplayLink:(CADisplayLink *)link;
27-
#endif
26+
// If YES, the engine is informed that a new texture is available any time the display link
27+
// callback is fired, regardless of the videoOutput state.
28+
//
29+
// TODO(stuartmorgan): Investigate removing this; it exists only to preserve existing iOS behavior
30+
// while implementing macOS, but iOS should very likely be doing the check as well.
31+
@property(nonatomic, assign) BOOL skipBufferAvailabilityCheck;
2832
@end
2933

3034
@implementation FVPFrameUpdater
@@ -36,35 +40,24 @@ - (FVPFrameUpdater *)initWithRegistry:(NSObject<FlutterTextureRegistry> *)regist
3640
return self;
3741
}
3842

39-
#if TARGET_OS_IOS
40-
- (void)onDisplayLink:(CADisplayLink *)link {
41-
// TODO(stuartmorgan): Investigate switching this to displayLinkFired; iOS may also benefit from
42-
// the availability check there.
43-
[_registry textureFrameAvailable:_textureId];
44-
}
45-
#endif
46-
4743
- (void)displayLinkFired {
48-
// Only report a new frame if one is actually available.
49-
CMTime outputItemTime = [self.videoOutput itemTimeForHostTime:CACurrentMediaTime()];
50-
if ([self.videoOutput hasNewPixelBufferForItemTime:outputItemTime]) {
51-
_lastKnownAvailableTime = outputItemTime;
44+
// Only report a new frame if one is actually available, or the check is being skipped.
45+
BOOL reportFrame = NO;
46+
if (self.skipBufferAvailabilityCheck) {
47+
reportFrame = YES;
48+
} else {
49+
CMTime outputItemTime = [self.videoOutput itemTimeForHostTime:CACurrentMediaTime()];
50+
if ([self.videoOutput hasNewPixelBufferForItemTime:outputItemTime]) {
51+
_lastKnownAvailableTime = outputItemTime;
52+
reportFrame = YES;
53+
}
54+
}
55+
if (reportFrame) {
5256
[_registry textureFrameAvailable:_textureId];
5357
}
5458
}
5559
@end
5660

57-
#if TARGET_OS_OSX
58-
static CVReturn DisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeStamp *now,
59-
const CVTimeStamp *outputTime, CVOptionFlags flagsIn,
60-
CVOptionFlags *flagsOut, void *displayLinkSource) {
61-
// Trigger the main-thread dispatch queue, to drive a frame update check.
62-
__weak dispatch_source_t source = (__bridge dispatch_source_t)displayLinkSource;
63-
dispatch_source_merge_data(source, 1);
64-
return kCVReturnSuccess;
65-
}
66-
#endif
67-
6861
@interface FVPDefaultPlayerFactory : NSObject <FVPPlayerFactory>
6962
@end
7063

@@ -96,16 +89,7 @@ @interface FVPVideoPlayer : NSObject <FlutterTexture, FlutterStreamHandler>
9689
@property(nonatomic) BOOL isLooping;
9790
@property(nonatomic, readonly) BOOL isInitialized;
9891
@property(nonatomic) FVPFrameUpdater *frameUpdater;
99-
// TODO(stuartmorgan): Extract and abstract the display link to remove all the display-link-related
100-
// ifdefs from this file.
101-
#if TARGET_OS_OSX
102-
// The display link to trigger frame reads from the video player.
103-
@property(nonatomic, assign) CVDisplayLinkRef displayLink;
104-
// A dispatch source to move display link callbacks to the main thread.
105-
@property(nonatomic, strong) dispatch_source_t displayLinkSource;
106-
#else
107-
@property(nonatomic) CADisplayLink *displayLink;
108-
#endif
92+
@property(nonatomic) FVPDisplayLink *displayLink;
10993

11094
- (instancetype)initWithURL:(NSURL *)url
11195
frameUpdater:(FVPFrameUpdater *)frameUpdater
@@ -255,27 +239,15 @@ - (void)createVideoOutputAndDisplayLink:(FVPFrameUpdater *)frameUpdater {
255239
};
256240
_videoOutput = [[AVPlayerItemVideoOutput alloc] initWithPixelBufferAttributes:pixBuffAttributes];
257241

258-
#if TARGET_OS_OSX
242+
259243
frameUpdater.videoOutput = _videoOutput;
260-
// Create and start the main-thread dispatch queue to drive frameUpdater.
261-
self.displayLinkSource =
262-
dispatch_source_create(DISPATCH_SOURCE_TYPE_DATA_ADD, 0, 0, dispatch_get_main_queue());
263-
dispatch_source_set_event_handler(self.displayLinkSource, ^() {
264-
@autoreleasepool {
265-
[frameUpdater displayLinkFired];
266-
}
267-
});
268-
dispatch_resume(self.displayLinkSource);
269-
if (CVDisplayLinkCreateWithActiveCGDisplays(&_displayLink) == kCVReturnSuccess) {
270-
CVDisplayLinkSetOutputCallback(_displayLink, &DisplayLinkCallback,
271-
(__bridge void *)(self.displayLinkSource));
272-
}
273-
#else
274-
_displayLink = [CADisplayLink displayLinkWithTarget:frameUpdater
275-
selector:@selector(onDisplayLink:)];
276-
[_displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
277-
_displayLink.paused = YES;
244+
#if TARGET_OS_IOS
245+
// See TODO on this property in FVPFrameUpdater.
246+
frameUpdater.skipBufferAvailabilityCheck = YES;
278247
#endif
248+
self.displayLink = [[FVPDisplayLink alloc] initWithCallback:^() {
249+
[frameUpdater displayLinkFired];
250+
}];
279251
}
280252

281253
- (instancetype)initWithURL:(NSURL *)url
@@ -428,23 +400,7 @@ - (void)updatePlayingState {
428400
} else {
429401
[_player pause];
430402
}
431-
#if TARGET_OS_OSX
432-
if (_displayLink) {
433-
if (_isPlaying) {
434-
NSScreen *screen = self.registrar.view.window.screen;
435-
if (screen) {
436-
CGDirectDisplayID viewDisplayID =
437-
(CGDirectDisplayID)[screen.deviceDescription[@"NSScreenNumber"] unsignedIntegerValue];
438-
CVDisplayLinkSetCurrentCGDisplay(_displayLink, viewDisplayID);
439-
}
440-
CVDisplayLinkStart(_displayLink);
441-
} else {
442-
CVDisplayLinkStop(_displayLink);
443-
}
444-
}
445-
#else
446-
_displayLink.paused = !_isPlaying;
447-
#endif
403+
_displayLink.running = _isPlaying;
448404
}
449405

450406
- (void)setupEventSinkIfReadyToPlay {
@@ -615,16 +571,7 @@ - (void)disposeSansEventChannel {
615571

616572
_disposed = YES;
617573
[_playerLayer removeFromSuperlayer];
618-
#if TARGET_OS_OSX
619-
if (_displayLink) {
620-
CVDisplayLinkStop(_displayLink);
621-
CVDisplayLinkRelease(_displayLink);
622-
_displayLink = NULL;
623-
}
624-
dispatch_source_cancel(_displayLinkSource);
625-
#else
626-
[_displayLink invalidate];
627-
#endif
574+
_displayLink = nil;
628575
[self removeKeyValueObservers];
629576

630577
[self.player replaceCurrentItemWithPlayerItem:nil];
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
#import "../FVPDisplayLink.h"
6+
7+
#import <CoreAnimation/CoreAnimation.h>
8+
#import <Foundation/Foundation.h>
9+
10+
/** A proxy object to act as a CADisplayLink target, to avoid retain loops. */
11+
@interface FVPDisplayLinkTarget : NSObject
12+
@property(nonatomic) (void (^)(void))callback;
13+
14+
/** Initializes a target object that runs the given callback when onDisplayLink: is called. */
15+
- (instancetype)initWithCallback:(void (^)(void))callback;
16+
17+
/** Method to be called when a CADisplayLink fires. */
18+
- (void)onDisplayLink:(CADisplayLink *)link;
19+
@end
20+
21+
@implementation FVPDisplayLinkTarget
22+
- (instancetype)initWithCallback:(void (^)(void))callback {
23+
self = [super init];
24+
if (self) {
25+
_callback = callback;
26+
}
27+
return self;
28+
}
29+
30+
- (void)onDisplayLink:(CADisplayLink *)link {
31+
self.callback();
32+
}
33+
@end
34+
35+
#pragma mark -
36+
37+
@interface FVPDisplayLink()
38+
// The underlying display link implementation.
39+
@property(nonatomic) CADisplayLink *displayLink;
40+
@property(nonatomic) FVPDisplayLinkTarget *target;
41+
@end
42+
43+
@implementation FVPDisplayLink
44+
45+
- (instancetype)initWithCallback:(void (^)(void))callback {
46+
self = [super init];
47+
if (self) {
48+
_target = [[FVPDisplayLinkTarget alloc] initWithCallback:callback];
49+
_displayLink = [CADisplayLink displayLinkWithTarget:_target
50+
selector:@selector(onDisplayLink:)];
51+
[_displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
52+
_displayLink.paused = YES;
53+
}
54+
return self;
55+
}
56+
57+
- (void)dealloc {
58+
[_displayLink invalidate];
59+
}
60+
61+
- (void)setRunning:(BOOL)running {
62+
self.displayLink.paused = !running;
63+
}
64+
65+
@end
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
#import "../FVPDisplayLink.h"
6+
7+
#import <CoreVideo/CoreVideo.h>
8+
#import <Foundation/Foundation.h>
9+
10+
@interface FVPDisplayLink()
11+
// The underlying display link implementation.
12+
@property(nonatomic, assign) CVDisplayLinkRef displayLink;
13+
// A dispatch source to move display link callbacks to the main thread.
14+
@property(nonatomic, strong) dispatch_source_t displayLinkSource;
15+
@end
16+
17+
static CVReturn DisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeStamp *now,
18+
const CVTimeStamp *outputTime, CVOptionFlags flagsIn,
19+
CVOptionFlags *flagsOut, void *displayLinkSource) {
20+
// Trigger the main-thread dispatch queue, to drive the callback there.
21+
__weak dispatch_source_t source = (__bridge dispatch_source_t)displayLinkSource;
22+
dispatch_source_merge_data(source, 1);
23+
return kCVReturnSuccess;
24+
}
25+
26+
@implementation FVPDisplayLink
27+
28+
- (instancetype)initWithCallback:(void (^)(void))callback {
29+
self = [super init];
30+
if (self) {
31+
// Create and start the main-thread dispatch queue to drive frameUpdater.
32+
_displayLinkSource =
33+
dispatch_source_create(DISPATCH_SOURCE_TYPE_DATA_ADD, 0, 0, dispatch_get_main_queue());
34+
dispatch_source_set_event_handler(_displayLinkSource, ^() {
35+
@autoreleasepool {
36+
callback();
37+
}
38+
});
39+
dispatch_resume(_displayLinkSource);
40+
if (CVDisplayLinkCreateWithActiveCGDisplays(&_displayLink) == kCVReturnSuccess) {
41+
CVDisplayLinkSetOutputCallback(_displayLink, &DisplayLinkCallback,
42+
(__bridge void *)(_displayLinkSource));
43+
}
44+
}
45+
return self;
46+
}
47+
48+
- (void)dealloc {
49+
CVDisplayLinkStop(_displayLink);
50+
CVDisplayLinkRelease(_displayLink);
51+
_displayLink = NULL;
52+
53+
dispatch_source_cancel(_displayLinkSource);
54+
}
55+
56+
- (void)setRunning:(BOOL)running {
57+
if (running) {
58+
// TODO(stuartmorgan): Move this to init + a screen change listener; this won't correctly
59+
// handle windows being dragged to another screen until the next pause/play cycle.
60+
NSScreen *screen = self.registrar.view.window.screen;
61+
if (screen) {
62+
CGDirectDisplayID viewDisplayID =
63+
(CGDirectDisplayID)[screen.deviceDescription[@"NSScreenNumber"] unsignedIntegerValue];
64+
CVDisplayLinkSetCurrentCGDisplay(self.displayLink, viewDisplayID);
65+
}
66+
CVDisplayLinkStart(self.displayLink);
67+
} else {
68+
CVDisplayLinkStop(self.displayLink);
69+
}
70+
}
71+
72+
@end

packages/video_player/video_player_avfoundation/darwin/video_player_avfoundation.podspec

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,9 @@ Downloaded by pub (not CocoaPods).
1414
s.author = { 'Flutter Dev Team' => '[email protected]' }
1515
s.source = { :http => 'https://github.com/flutter/packages/tree/main/packages/video_player/video_player_avfoundation' }
1616
s.documentation_url = 'https://pub.dev/packages/video_player'
17-
s.source_files = 'Classes/**/*'
17+
s.source_files = 'Classes/*'
18+
s.ios.source_files = 'Classes/ios/*'
19+
s.osx.source_files = 'Classes/macos/*'
1820
s.public_header_files = 'Classes/**/*.h'
1921
s.ios.dependency 'Flutter'
2022
s.osx.dependency 'FlutterMacOS'

0 commit comments

Comments
 (0)