diff --git a/packages/video_player/video_player_macos/macos/Classes/FLTVideoPlayerPlugin.m b/packages/video_player/video_player_macos/macos/Classes/FLTVideoPlayerPlugin.m index 58eca3777760..147fcbceadad 100644 --- a/packages/video_player/video_player_macos/macos/Classes/FLTVideoPlayerPlugin.m +++ b/packages/video_player/video_player_macos/macos/Classes/FLTVideoPlayerPlugin.m @@ -41,7 +41,7 @@ @interface FLTVideoPlayer : NSObject @property(readonly, nonatomic) AVPlayerItemVideoOutput* videoOutput; @property(readonly, nonatomic) CVDisplayLinkRef displayLink; @property(readonly, nonatomic) FLTFrameUpdater* frameUpdater; -@property(readonly, nonatomic) CVPixelBufferRef _Nullable frame; +@property(readonly, nonatomic) CVPixelBufferRef _Nullable lastValidFrame; @property(nonatomic) FlutterEventChannel* eventChannel; @property(nonatomic) FlutterEventSink eventSink; @property(nonatomic) CGAffineTransform preferredTransform; @@ -170,10 +170,10 @@ - (void)notifyIfFrameAvailable { if (!_playerItem || _playerItem.status != AVPlayerItemStatusReadyToPlay || ![_videoOutput hasNewPixelBufferForItemTime:outputItemTime]) { return; } else { - CVBufferRelease(_frame); - _frame = [_videoOutput copyPixelBufferForItemTime:outputItemTime itemTimeForDisplay:NULL]; - if (_frame == NULL) { - NSLog(@"copyPixelBufferForItemTime returned NULL"); + CVPixelBufferRef pixelBuffer = [_videoOutput copyPixelBufferForItemTime:outputItemTime itemTimeForDisplay:NULL]; + if (pixelBuffer != NULL) { + CVBufferRelease(_lastValidFrame); + _lastValidFrame = pixelBuffer; } [_frameUpdater notifyFrameAvailable]; } @@ -441,11 +441,32 @@ - (void)setPlaybackSpeed:(double)speed { } - (CVPixelBufferRef)copyPixelBuffer { - if (_frame == NULL) { - NSLog(@"Returning NULL from copyPixelBuffer"); - } - CVBufferRetain(_frame); - return _frame; + // Creates a memcpy of the last valid frame. + // + // Unlike on iOS, the macOS embedder does show the last frame when + // we return NULL from `copyPixelBuffer`. + CVPixelBufferLockBaseAddress(_lastValidFrame, 0); + int bufferWidth = (int)CVPixelBufferGetWidth(_lastValidFrame); + int bufferHeight = (int)CVPixelBufferGetHeight(_lastValidFrame); + size_t bytesPerRow = CVPixelBufferGetBytesPerRow(_lastValidFrame); + uint8_t *baseAddress = CVPixelBufferGetBaseAddress(_lastValidFrame); + NSDictionary* pixBuffAttributes = @{ + (id)kCVPixelBufferPixelFormatTypeKey : @(kCVPixelFormatType_32BGRA), + (id)kCVPixelBufferIOSurfacePropertiesKey : @{}, + (id)kCVPixelBufferOpenGLCompatibilityKey : @YES, + (id)kCVPixelBufferMetalCompatibilityKey : @YES, + }; + + CVPixelBufferRef pixelBufferCopy = NULL; + CVPixelBufferCreate(kCFAllocatorDefault, bufferWidth, bufferHeight,kCVPixelFormatType_32BGRA, + CFBridgingRetain(pixBuffAttributes), &pixelBufferCopy); + CVPixelBufferLockBaseAddress(pixelBufferCopy, 0); + uint8_t *copyBaseAddress = CVPixelBufferGetBaseAddress(pixelBufferCopy); + memcpy(copyBaseAddress, baseAddress, bufferHeight * bytesPerRow); + + CVPixelBufferUnlockBaseAddress(_lastValidFrame, 0); + CVPixelBufferUnlockBaseAddress(pixelBufferCopy, 0); + return pixelBufferCopy; } - (void)onTextureUnregistered:(NSObject*)texture { @@ -476,7 +497,7 @@ - (FlutterError* _Nullable)onListenWithArguments:(id _Nullable)arguments /// so the channel is going to die or is already dead. - (void)disposeSansEventChannel { _disposed = true; - CVBufferRelease(_frame); + CVBufferRelease(_lastValidFrame); [self stopDisplayLink]; [[_player currentItem] removeObserver:self forKeyPath:@"status" context:statusContext]; [[_player currentItem] removeObserver:self