@@ -41,7 +41,7 @@ @interface FLTVideoPlayer : NSObject <FlutterTexture, FlutterStreamHandler>
41
41
@property (readonly , nonatomic ) AVPlayerItemVideoOutput* videoOutput;
42
42
@property (readonly , nonatomic ) CVDisplayLinkRef displayLink;
43
43
@property (readonly , nonatomic ) FLTFrameUpdater* frameUpdater;
44
- @property (readonly , nonatomic ) CVPixelBufferRef _Nullable frame ;
44
+ @property (readonly , nonatomic ) CVPixelBufferRef _Nullable lastValidFrame ;
45
45
@property (nonatomic ) FlutterEventChannel* eventChannel;
46
46
@property (nonatomic ) FlutterEventSink eventSink;
47
47
@property (nonatomic ) CGAffineTransform preferredTransform;
@@ -170,10 +170,12 @@ - (void)notifyIfFrameAvailable {
170
170
if (!_playerItem || _playerItem.status != AVPlayerItemStatusReadyToPlay || ![_videoOutput hasNewPixelBufferForItemTime: outputItemTime]) {
171
171
return ;
172
172
} else {
173
- CVBufferRelease (_frame);
174
- _frame = [_videoOutput copyPixelBufferForItemTime: outputItemTime itemTimeForDisplay: NULL ];
175
- if (_frame == NULL ) {
176
- NSLog (@" copyPixelBufferForItemTime returned NULL" );
173
+ CVPixelBufferRef pixelBuffer = [_videoOutput copyPixelBufferForItemTime: outputItemTime itemTimeForDisplay: NULL ];
174
+ if (pixelBuffer != NULL ) {
175
+ @synchronized (self) {
176
+ CVBufferRelease (_lastValidFrame);
177
+ _lastValidFrame = pixelBuffer;
178
+ }
177
179
}
178
180
[_frameUpdater notifyFrameAvailable ];
179
181
}
@@ -185,9 +187,7 @@ static CVReturn OnDisplayLink(CVDisplayLinkRef CV_NONNULL displayLink,
185
187
CVOptionFlags* CV_NONNULL flagsOut,
186
188
void * CV_NULLABLE displayLinkContext) {
187
189
__weak FLTVideoPlayer* video_player = (__bridge FLTVideoPlayer*)(displayLinkContext);
188
- dispatch_async (dispatch_get_main_queue (), ^{
189
- [video_player notifyIfFrameAvailable ];
190
- });
190
+ [video_player notifyIfFrameAvailable ];
191
191
return kCVReturnSuccess ;
192
192
}
193
193
@@ -403,9 +403,7 @@ - (void)seekTo:(int)location {
403
403
[_player seekToTime: CMTimeMake (location, 1000 )
404
404
toleranceBefore: kCMTimeZero
405
405
toleranceAfter: kCMTimeZero ];
406
- dispatch_async (dispatch_get_main_queue (), ^{
407
- [self notifyIfFrameAvailable ];
408
- });
406
+ [self notifyIfFrameAvailable ];
409
407
}
410
408
411
409
- (void )setIsLooping : (bool )isLooping {
@@ -441,11 +439,45 @@ - (void)setPlaybackSpeed:(double)speed {
441
439
}
442
440
443
441
- (CVPixelBufferRef)copyPixelBuffer {
444
- if (_frame == NULL ) {
445
- NSLog (@" Returning NULL from copyPixelBuffer" );
442
+ // Creates a memcpy of the last valid frame.
443
+ //
444
+ // Unlike on iOS, the macOS embedder does show the last frame when
445
+ // we return NULL from `copyPixelBuffer`.
446
+
447
+ if (_lastValidFrame == NULL ) {
448
+ return NULL ;
449
+ }
450
+
451
+ @synchronized (self) {
452
+ CVPixelBufferLockBaseAddress (_lastValidFrame, kCVPixelBufferLock_ReadOnly );
453
+ int bufferWidth = (int )CVPixelBufferGetWidth (_lastValidFrame);
454
+ int bufferHeight = (int )CVPixelBufferGetHeight (_lastValidFrame);
455
+ size_t bytesPerRow = CVPixelBufferGetBytesPerRow (_lastValidFrame);
456
+ uint8_t *baseAddress = CVPixelBufferGetBaseAddress (_lastValidFrame);
457
+
458
+ if (baseAddress == NULL ) {
459
+ NSLog (@" ----> baseadress is NULL" );
460
+ return NULL ;
461
+ }
462
+
463
+ NSDictionary * pixBuffAttributes = @{
464
+ (id )kCVPixelBufferPixelFormatTypeKey : @(kCVPixelFormatType_32BGRA ),
465
+ (id )kCVPixelBufferIOSurfacePropertiesKey : @{},
466
+ (id )kCVPixelBufferOpenGLCompatibilityKey : @YES ,
467
+ (id )kCVPixelBufferMetalCompatibilityKey : @YES ,
468
+ };
469
+
470
+ CVPixelBufferRef pixelBufferCopy = NULL ;
471
+ CVPixelBufferCreate (kCFAllocatorDefault , bufferWidth, bufferHeight,kCVPixelFormatType_32BGRA ,
472
+ CFBridgingRetain (pixBuffAttributes), &pixelBufferCopy);
473
+ CVPixelBufferLockBaseAddress (pixelBufferCopy, 0 );
474
+ uint8_t *copyBaseAddress = CVPixelBufferGetBaseAddress (pixelBufferCopy);
475
+ memcpy (copyBaseAddress, baseAddress, bufferHeight * bytesPerRow);
476
+
477
+ CVPixelBufferUnlockBaseAddress (_lastValidFrame, kCVPixelBufferLock_ReadOnly );
478
+ CVPixelBufferUnlockBaseAddress (pixelBufferCopy, 0 );
479
+ return pixelBufferCopy;
446
480
}
447
- CVBufferRetain (_frame);
448
- return _frame;
449
481
}
450
482
451
483
- (void )onTextureUnregistered : (NSObject <FlutterTexture>*)texture {
@@ -476,7 +508,7 @@ - (FlutterError* _Nullable)onListenWithArguments:(id _Nullable)arguments
476
508
// / so the channel is going to die or is already dead.
477
509
- (void )disposeSansEventChannel {
478
510
_disposed = true ;
479
- CVBufferRelease (_frame );
511
+ CVBufferRelease (_lastValidFrame );
480
512
[self stopDisplayLink ];
481
513
[[_player currentItem ] removeObserver: self forKeyPath: @" status" context: statusContext];
482
514
[[_player currentItem ] removeObserver: self
0 commit comments