@@ -41,7 +41,7 @@ @interface FLTVideoPlayer : NSObject <FlutterTexture, FlutterStreamHandler>
4141@property (readonly , nonatomic ) AVPlayerItemVideoOutput* videoOutput;
4242@property (readonly , nonatomic ) CVDisplayLinkRef displayLink;
4343@property (readonly , nonatomic ) FLTFrameUpdater* frameUpdater;
44- @property (readonly , nonatomic ) CVPixelBufferRef _Nullable frame ;
44+ @property (readonly , nonatomic ) CVPixelBufferRef _Nullable lastValidFrame ;
4545@property (nonatomic ) FlutterEventChannel* eventChannel;
4646@property (nonatomic ) FlutterEventSink eventSink;
4747@property (nonatomic ) CGAffineTransform preferredTransform;
@@ -170,10 +170,12 @@ - (void)notifyIfFrameAvailable {
170170 if (!_playerItem || _playerItem.status != AVPlayerItemStatusReadyToPlay || ![_videoOutput hasNewPixelBufferForItemTime: outputItemTime]) {
171171 return ;
172172 } 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+ }
177179 }
178180 [_frameUpdater notifyFrameAvailable ];
179181 }
@@ -185,9 +187,7 @@ static CVReturn OnDisplayLink(CVDisplayLinkRef CV_NONNULL displayLink,
185187 CVOptionFlags* CV_NONNULL flagsOut,
186188 void * CV_NULLABLE displayLinkContext) {
187189 __weak FLTVideoPlayer* video_player = (__bridge FLTVideoPlayer*)(displayLinkContext);
188- dispatch_async (dispatch_get_main_queue (), ^{
189- [video_player notifyIfFrameAvailable ];
190- });
190+ [video_player notifyIfFrameAvailable ];
191191 return kCVReturnSuccess ;
192192}
193193
@@ -403,9 +403,7 @@ - (void)seekTo:(int)location {
403403 [_player seekToTime: CMTimeMake (location, 1000 )
404404 toleranceBefore: kCMTimeZero
405405 toleranceAfter: kCMTimeZero ];
406- dispatch_async (dispatch_get_main_queue (), ^{
407- [self notifyIfFrameAvailable ];
408- });
406+ [self notifyIfFrameAvailable ];
409407}
410408
411409- (void )setIsLooping : (bool )isLooping {
@@ -441,11 +439,45 @@ - (void)setPlaybackSpeed:(double)speed {
441439}
442440
443441- (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;
446480 }
447- CVBufferRetain (_frame);
448- return _frame;
449481}
450482
451483- (void )onTextureUnregistered : (NSObject <FlutterTexture>*)texture {
@@ -476,7 +508,7 @@ - (FlutterError* _Nullable)onListenWithArguments:(id _Nullable)arguments
476508// / so the channel is going to die or is already dead.
477509- (void )disposeSansEventChannel {
478510 _disposed = true ;
479- CVBufferRelease (_frame );
511+ CVBufferRelease (_lastValidFrame );
480512 [self stopDisplayLink ];
481513 [[_player currentItem ] removeObserver: self forKeyPath: @" status" context: statusContext];
482514 [[_player currentItem ] removeObserver: self
0 commit comments