diff --git a/composeApp/src/desktopMain/native/macos/player_bridge.mm b/composeApp/src/desktopMain/native/macos/player_bridge.mm index c10e5a0e..9753df3b 100644 --- a/composeApp/src/desktopMain/native/macos/player_bridge.mm +++ b/composeApp/src/desktopMain/native/macos/player_bridge.mm @@ -99,6 +99,7 @@ - (void)handleScriptMessage:(NSDictionary *)message; - (void)focusControlsWebViewIfNeeded; - (void)layoutNativeSubviews; +- (void)layoutControlsWebViewToBounds:(NSRect)bounds immediate:(BOOL)immediate; - (void)hostViewBoundsDidChange:(NSNotification *)notification; - (void)hostViewFrameDidChange:(NSNotification *)notification; - (void)windowWillEnterFullScreen:(NSNotification *)notification; @@ -1138,6 +1139,8 @@ static NSString *redactUrlsInText(NSString *text) { NSRect _lastAppliedNativeLayoutBounds; BOOL _lastAppliedNativeLayoutWasLiveResize; NSTimeInterval _lightweightResizeSettleUntil; + NSSize _lastControlsViewportNudgeSize; + NSTimeInterval _lastControlsViewportNudgeAt; } - (instancetype)initWithHostView:(NSView *)hostView @@ -1180,6 +1183,9 @@ static NSString *redactUrlsInText(NSString *text) { _webView = [[WKWebView alloc] initWithFrame:_hostView.bounds configuration:configuration]; _webView.autoresizingMask = NSViewWidthSizable | NSViewHeightSizable; _webView.wantsLayer = YES; + _webView.layerContentsRedrawPolicy = NSViewLayerContentsRedrawDuringViewResize; + _webView.layer.needsDisplayOnBoundsChange = YES; + _webView.layer.drawsAsynchronously = YES; [_webView setValue:@NO forKey:@"drawsBackground"]; [_hostView addSubview:_webView positioned:NSWindowAbove relativeTo:_videoView]; NSURL *controlsURL = [NSURL URLWithString:controlsUrl ?: @""]; @@ -1242,6 +1248,34 @@ static NSString *redactUrlsInText(NSString *text) { [_webView.window makeFirstResponder:_webView]; } +- (void)layoutControlsWebViewToBounds:(NSRect)bounds immediate:(BOOL)immediate { + if (!_webView) { + return; + } + + if (!NSEqualRects(_webView.frame, bounds)) { + _webView.frame = bounds; + } + if (!immediate) { + return; + } + + [_webView setNeedsLayout:YES]; + [_webView layoutSubtreeIfNeeded]; + [_webView setNeedsDisplay:YES]; + + NSTimeInterval now = NSDate.timeIntervalSinceReferenceDate; + BOOL sizeChanged = !NSEqualSizes(_lastControlsViewportNudgeSize, bounds.size); + if (!_controlsWebReady || !sizeChanged || now - _lastControlsViewportNudgeAt < 0.05) { + return; + } + + _lastControlsViewportNudgeSize = bounds.size; + _lastControlsViewportNudgeAt = now; + NSString *script = @"window.nuvioNativeViewportChanged ? window.nuvioNativeViewportChanged() : window.dispatchEvent(new Event('resize'));"; + [_webView evaluateJavaScript:script completionHandler:nil]; +} + - (void)layoutNativeSubviews { if (!_hostView) { return; @@ -1299,13 +1333,8 @@ static NSString *redactUrlsInText(NSString *text) { } } if (_webView) { - if (!NSEqualRects(_webView.frame, bounds)) { - _webView.frame = bounds; - } - if (!liveResize && !settlingFromResize) { - [_webView setNeedsLayout:YES]; - [_webView layoutSubtreeIfNeeded]; - } + BOOL immediateControlsLayout = _fullscreenTransitionActive || settlingFromResize || (!liveResize && !settlingFromResize); + [self layoutControlsWebViewToBounds:bounds immediate:immediateControlsLayout]; } [CATransaction commit]; @@ -1439,6 +1468,7 @@ static NSString *redactUrlsInText(NSString *text) { return; } _lightweightResizeSettleUntil = 0.0; + [self layoutControlsWebViewToBounds:_hostView.bounds immediate:YES]; [_videoView setFullscreenTransitionActive:NO]; [_videoView updateMetalLayerLayout]; NSString *reason = [NSString stringWithFormat:@"resize-settled/%@", timer.userInfo ?: @"unknown"]; diff --git a/composeApp/src/desktopMain/resources/player-ui/controls.css b/composeApp/src/desktopMain/resources/player-ui/controls.css index fcb07112..de86aea0 100644 --- a/composeApp/src/desktopMain/resources/player-ui/controls.css +++ b/composeApp/src/desktopMain/resources/player-ui/controls.css @@ -160,6 +160,12 @@ button:disabled { transition: opacity 160ms ease; } +.player.native-resizing, +.player.native-resizing * { + transition-duration: 1ms !important; + animation-duration: 1ms !important; +} + .player.chrome-hidden .top-gradient, .player.chrome-hidden .bottom-gradient, .player.chrome-hidden .metadata, diff --git a/composeApp/src/desktopMain/resources/player-ui/controls.js b/composeApp/src/desktopMain/resources/player-ui/controls.js index 24161843..85622c9f 100644 --- a/composeApp/src/desktopMain/resources/player-ui/controls.js +++ b/composeApp/src/desktopMain/resources/player-ui/controls.js @@ -332,6 +332,7 @@ let pauseMetadataEligibilityKey = ""; let chromeAutoHideTimer = 0; let chromeAutoHideKey = ""; let chromeAutoHideActivity = 0; +let nativeViewportTimer = 0; const prefersReducedMotion = window.matchMedia && window.matchMedia("(prefers-reduced-motion: reduce)").matches; const modalTransitionMs = prefersReducedMotion ? 1 : 240; @@ -1381,6 +1382,19 @@ const renderActiveModal = () => { if (activeModal === "p2pConsent") renderP2pConsentModal(); }; +window.nuvioNativeViewportChanged = () => { + root.classList.add("native-resizing"); + window.clearTimeout(nativeViewportTimer); + nativeViewportTimer = window.setTimeout(() => { + root.classList.remove("native-resizing"); + }, 180); + if (activeModal) renderActiveModal(); +}; + +window.addEventListener("resize", () => { + window.nuvioNativeViewportChanged(); +}); + const trackListSignature = tracks => normalizeTracks(tracks) .map(track => [