diff --git a/VoidLink/Stream/VideoDecoderRenderer.h b/VoidLink/Stream/VideoDecoderRenderer.h index 1b1adbb0..afc36a47 100644 --- a/VoidLink/Stream/VideoDecoderRenderer.h +++ b/VoidLink/Stream/VideoDecoderRenderer.h @@ -30,6 +30,7 @@ - (void)safeCopyMetricsTo:(PlotMetrics *)dst from:(PlotMetrics *)src; - (void)getAllStats:(video_stats_t *)stats; - (void)optimizeRefreshRate; +- (void)resetFramePacing; - (int)submitDecodeBuffer:(unsigned char *)data length:(int)length diff --git a/VoidLink/Stream/VideoDecoderRenderer.m b/VoidLink/Stream/VideoDecoderRenderer.m index 13353731..a97d0a77 100644 --- a/VoidLink/Stream/VideoDecoderRenderer.m +++ b/VoidLink/Stream/VideoDecoderRenderer.m @@ -64,7 +64,7 @@ extern int ff_isom_write_av1c(AVIOContext *pb, const uint8_t *buf, int size, _displayLayer.videoGravity = AVLayerVideoGravityResize; [_view.layer addSublayer:_displayLayer]; } - + // Ensure the AVSampleBufferDisplayLayer is sized to preserve the aspect ratio // of the video stream. We used to use AVLayerVideoGravityResizeAspect, but that // respects the PAR encoded in the SPS which causes our computed video-relative @@ -76,13 +76,13 @@ extern int ff_isom_write_av1c(AVIOContext *pb, const uint8_t *buf, int size, } else { videoSize = CGSizeMake(_view.bounds.size.width, _view.bounds.size.width / _streamAspectRatio); } - + [CATransaction begin]; [CATransaction setDisableActions:YES]; _displayLayer.position = CGPointMake(CGRectGetMidX(_view.bounds), CGRectGetMidY(_view.bounds)); _displayLayer.bounds = CGRectMake(0, 0, videoSize.width, videoSize.height); [CATransaction commit]; - + // Hide the layer until we get an IDR frame. This ensures we // can see the loading progress label as the stream is starting. _displayLayer.hidden = YES; @@ -91,13 +91,15 @@ extern int ff_isom_write_av1c(AVIOContext *pb, const uint8_t *buf, int size, CFRelease(_formatDesc); _formatDesc = nil; } - + if (_formatDescImageBuffer != nil) { CFRelease(_formatDescImageBuffer); _formatDescImageBuffer = nil; } - + if (_decompressionSession != nil){ + VTDecompressionSessionWaitForAsynchronousFrames(_decompressionSession); + VTDecompressionSessionInvalidate(_decompressionSession); CFRelease(_decompressionSession); _decompressionSession = nil; @@ -1129,4 +1131,26 @@ int DrSubmitDecodeUnit(PDECODE_UNIT decodeUnit); } } +- (void)resetFramePacing { + // Ensure this only runs for the AVSampleBuffer rendering backend and that the display link exists. + if (_renderingBackend == RENDER_AVSB && _displayLink) { + Log(LOG_I, @"Frame pacing is being reset to %d FPS...", self->_frameRate); + + // Toggling the paused state can help re-engage the display link with the + // run loop correctly after the app resumes from a background state like PiP. + _displayLink.paused = YES; + + // Re-apply the desired frame rate range. This is the critical hint for ProMotion + // that may have been lost or ignored during the PiP transition. + if (@available(iOS 15.0, *)) { + _displayLink.preferredFrameRateRange = CAFrameRateRangeMake(self->_frameRate, self->_frameRate, self->_frameRate); + } else { + _displayLink.preferredFramesPerSecond = self->_frameRate; + } + + // Resume the display link immediately. + _displayLink.paused = NO; + } +} + @end diff --git a/VoidLink/ViewControllers/StreamFrameViewController.m b/VoidLink/ViewControllers/StreamFrameViewController.m index aa722afd..1c4e44dd 100644 --- a/VoidLink/ViewControllers/StreamFrameViewController.m +++ b/VoidLink/ViewControllers/StreamFrameViewController.m @@ -95,6 +95,10 @@ - (void)pictureInPictureControllerWillStartPictureInPicture:(AVPictureInPictureController *)pictureInPictureController { _streamView.hidden = YES; + if (self.imguiView) { + self.imguiView.mtkView.hidden = YES; + Log(LOG_I, @"Hiding ImGui view for PiP start."); + } } - (void)pictureInPictureControllerDidStartPictureInPicture:(AVPictureInPictureController *)pictureInPictureController { @@ -110,15 +114,23 @@ - (void)pictureInPictureControllerDidStopPictureInPicture:(AVPictureInPictureController *)pictureInPictureController { _streamView.hidden = NO; + if (self.imguiView) { + self.imguiView.mtkView.hidden = NO; + Log(LOG_I, @"Showing ImGui view after PiP stop."); + } + if (!_isRestoringFromPiP) { [self returnToMainFrame]; } - _isRestoringFromPiP = NO; } - (void)pictureInPictureController:(AVPictureInPictureController *)pictureInPictureController restoreUserInterfaceForPictureInPictureStopWithCompletionHandler:(void (^)(BOOL restored))completionHandler { _isRestoringFromPiP = YES; _streamView.hidden = NO; + if (self.imguiView) { + self.imguiView.mtkView.hidden = NO; + Log(LOG_I, @"Showing ImGui view for PiP restore."); + } completionHandler(YES); } @@ -343,6 +355,13 @@ userInfo:nil repeats:_settings.statsOverlayEnabled]; + if (self.metalViewController && self.metalViewController.view.superview) { + [self.view bringSubviewToFront:self.metalViewController.view]; + } + if (self.imguiView && self.imguiView.mtkView.superview) { + [self.view bringSubviewToFront:self.imguiView.mtkView]; + } + NSLog(@"frameview gestures: %d", (uint32_t)[self.view.gestureRecognizers count]); NSLog(@"streamview gestures: %d", (uint32_t)[_streamView.gestureRecognizers count]); } @@ -909,19 +928,23 @@ } - (void)applicationDidBecomeActive:(NSNotification *)notification { - // Stop the background timer, since we're foregrounded again + // Stop the background timer if (_inactivityTimer != nil) { Log(LOG_I, @"Stopping inactivity timer after becoming active again"); [_inactivityTimer invalidate]; _inactivityTimer = nil; } + + // Check if we were in PiP if (self.pipController && self.pipController.isPictureInPictureActive) { [self.pipController stopPictureInPicture]; } - //self.pipController = nil; - //[self setupPiPControllerWithRenderer:self->_streamMan.videoRenderer]; - //self->_streamMan.videoRenderer. + if (_isRestoringFromPiP) { + [self->_streamMan.videoRenderer resetFramePacing]; + } + + _isRestoringFromPiP = NO; } // This fires when the home button is pressed @@ -1365,6 +1388,11 @@ - (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id)coordinator { [super viewWillTransitionToSize:size withTransitionCoordinator:coordinator]; + if (_isRestoringFromPiP) { + Log(LOG_I, @"View size changed during PiP restore, skipping redundant reconfiguration."); + return; + } + Log(LOG_I, @"View size changed, terminating stream"); double delayInSeconds = 0.2;