From 7c387db3e855691c67710e4fc1b0b18ad4e510e8 Mon Sep 17 00:00:00 2001 From: Slowlife01 Date: Mon, 17 Mar 2025 20:14:53 +0700 Subject: [PATCH 1/2] Fix media control not working for web panels --- .../zen-components/ZenMediaController.mjs | 97 +++++-------------- .../MediaController-webidl.patch | 9 +- .../mediacontrol/MediaController-cpp.patch | 10 +- 3 files changed, 40 insertions(+), 76 deletions(-) diff --git a/src/browser/base/zen-components/ZenMediaController.mjs b/src/browser/base/zen-components/ZenMediaController.mjs index e4d304072..c9ef00ac2 100644 --- a/src/browser/base/zen-components/ZenMediaController.mjs +++ b/src/browser/base/zen-components/ZenMediaController.mjs @@ -35,6 +35,7 @@ class ZenMediaController { this.onSupportedKeysChange = this._onSupportedKeysChange.bind(this); this.onMetadataChange = this._onMetadataChange.bind(this); this.onDeactivated = this._onDeactivated.bind(this); + this.onPipModeChange = this._onPictureInPictureModeChange.bind(this); window.addEventListener('TabSelect', (event) => { const linkedBrowser = event.target.linkedBrowser; @@ -87,21 +88,13 @@ class ZenMediaController { window.addEventListener('DOMAudioPlaybackStopped', () => this.updateMuteState()); } - /** - * Deinitializes a media controller, removing all event listeners and resetting state. - * @param {Object} mediaController - The media controller to deinitialize. - */ async deinitMediaController(mediaController, shouldForget = true, shouldOverride = true, shouldHide = true) { if (!mediaController) return; const retrievedMediaController = this.mediaControllersMap.get(mediaController.id); - if (this.tabObserver && shouldOverride) { - this.tabObserver.disconnect(); - this.tabObserver = null; - } - if (shouldForget) { + mediaController.removeEventListener('pictureinpicturemodechange', this.onPipModeChange); mediaController.removeEventListener('positionstatechange', this.onPositionstateChange); mediaController.removeEventListener('playbackstatechange', this.onPlaybackstateChange); mediaController.removeEventListener('supportedkeyschange', this.onSupportedKeysChange); @@ -148,10 +141,9 @@ class ZenMediaController { } showMediaControls() { - const tab = gBrowser.getTabForBrowser(this._currentBrowser); - if (tab.hasAttribute('pictureinpicture')) return this.hideMediaControls(); - + if (this._currentMediaController.isBeingUsedInPIPModeOrFullscreen) return this.hideMediaControls(); if (!this.mediaControlBar.hasAttribute('hidden')) return; + this.updatePipButton(); this.mediaControlBar.removeAttribute('hidden'); @@ -190,27 +182,6 @@ class ZenMediaController { this.updatePipButton(); - this.tabObserver = new MutationObserver((entries) => { - for (const entry of entries) { - if (entry.target.hasAttribute('pictureinpicture')) { - this.hideMediaControls(); - this.mediaControlBar.setAttribute('pip', ''); - } else { - const { selectedBrowser } = gBrowser; - if (selectedBrowser.browserId !== this._currentBrowser.browserId) { - this.showMediaControls(); - } - - this.mediaControlBar.removeAttribute('pip'); - } - } - }); - - this.tabObserver.observe(gBrowser.getTabForBrowser(browser), { - attributes: true, - attributeFilter: ['pictureinpicture'], - }); - const positionState = mediaController.getPositionState(); this.mediaControllersMap.set(mediaController.id, { controller: mediaController, @@ -221,11 +192,6 @@ class ZenMediaController { }); } - /** - * Sets up the media control UI with metadata and position state. - * @param {Object} metadata - The media metadata (title, artist, etc.). - * @param {Object} positionState - The position state (position, duration). - */ setupMediaControlUI(metadata, positionState) { this.updatePipButton(); @@ -251,10 +217,6 @@ class ZenMediaController { } } - /** - * @param {Object} mediaController - The media controller to activate. - * @param {Object} browser - The browser associated with the media controller. - */ activateMediaControls(mediaController, browser) { this.updateMuteState(); this.switchController(); @@ -276,6 +238,7 @@ class ZenMediaController { this.setupMediaControlUI(metadata, positionState); } + mediaController.addEventListener('pictureinpicturemodechange', this.onPipModeChange); mediaController.addEventListener('positionstatechange', this.onPositionstateChange); mediaController.addEventListener('playbackstatechange', this.onPlaybackstateChange); mediaController.addEventListener('supportedkeyschange', this.onSupportedKeysChange); @@ -287,18 +250,11 @@ class ZenMediaController { this.pipEligibilityMap.set(browser.browserId, isEligible); } - /** - * @param {Event} event - The deactivation event. - */ _onDeactivated(event) { this.deinitMediaController(event.target, true, event.target.id === this._currentMediaController.id, true); this.switchController(); } - /** - * Updates playback state and UI based on changes. - * @param {Event} event - The playback state change event. - */ _onPlaybackstateChange() { if (this._currentMediaController?.isPlaying) { this.mediaControlBar.classList.add('playing'); @@ -308,24 +264,16 @@ class ZenMediaController { } } - /** - * Updates supported keys in the UI. - * @param {Event} event - The supported keys change event. - */ _onSupportedKeysChange(event) { - if (event.target !== this._currentMediaController) return; + if (event.target.id !== this._currentMediaController?.id) return; for (const key of this.supportedKeys) { const button = this.mediaControlBar.querySelector(`#zen-media-${key}-button`); button.disabled = !event.target.supportedKeys.includes(key); } } - /** - * Updates position state and UI when the media position changes. - * @param {Event} event - The position state change event. - */ _onPositionstateChange(event) { - if (event.target !== this._currentMediaController) { + if (event.target.id !== this._currentMediaController?.id) { const mediaController = this.mediaControllersMap.get(event.target.id); this.mediaControllersMap.set(event.target.id, { ...mediaController, @@ -362,7 +310,7 @@ class ZenMediaController { .shift(); if (nextController) { - this.deinitMediaController(this._currentMediaController, false, true, false).then(() => { + this.deinitMediaController(this._currentMediaController, false, true).then(() => { this.setupMediaController(nextController.controller, nextController.browser); const elapsedTime = Math.floor((Date.now() - nextController.lastUpdated) / 1000); @@ -380,9 +328,6 @@ class ZenMediaController { }, timeout); } - /** - * Updates the media progress bar and time display. - */ updateMediaPosition() { if (this._mediaUpdateInterval) { clearInterval(this._mediaUpdateInterval); @@ -413,11 +358,6 @@ class ZenMediaController { }, 1000); } - /** - * Formats seconds into a hours:minutes:seconds string. - * @param {number} seconds - The time in seconds. - * @returns {string} Formatted time string. - */ formatSecondsToTime(seconds) { if (!seconds || isNaN(seconds)) return '0:00'; @@ -433,12 +373,8 @@ class ZenMediaController { return `${minutes}:${secs.padStart(2, '0')}`; } - /** - * Updates metadata in the UI. - * @param {Event} event - The metadata change event. - */ _onMetadataChange(event) { - if (event.target !== this._currentMediaController) return; + if (event.target.id !== this._currentMediaController.id) return; this.updatePipButton(); const metadata = event.target.getMetadata(); @@ -446,6 +382,21 @@ class ZenMediaController { this.mediaArtist.textContent = metadata.artist || ''; } + _onPictureInPictureModeChange(event) { + if (event.target.id !== this._currentMediaController.id) return; + if (event.target.isBeingUsedInPIPModeOrFullscreen) { + this.hideMediaControls(); + this.mediaControlBar.setAttribute('pip', ''); + } else { + const { selectedBrowser } = gBrowser; + if (selectedBrowser.browserId !== this._currentBrowser.browserId) { + this.showMediaControls(); + } + + this.mediaControlBar.removeAttribute('pip'); + } + } + onMediaPlayPrev() { if (this._currentMediaController?.supportedKeys.includes('previoustrack')) { this._currentMediaController.prevTrack(); diff --git a/src/dom/chrome-webidl/MediaController-webidl.patch b/src/dom/chrome-webidl/MediaController-webidl.patch index 73a252539..d7c868534 100644 --- a/src/dom/chrome-webidl/MediaController-webidl.patch +++ b/src/dom/chrome-webidl/MediaController-webidl.patch @@ -1,5 +1,5 @@ diff --git a/dom/chrome-webidl/MediaController.webidl b/dom/chrome-webidl/MediaController.webidl -index 20f416d1c3b41798e0f90bbac5db40ed2a4ab000..06cb4c847fcfba964eeb93089613e293dc10bd87 100644 +index 20f416d1c3b41798e0f90bbac5db40ed2a4ab000..1c5d893f9166a3aa7bc7802bb0d1207d169033ee 100644 --- a/dom/chrome-webidl/MediaController.webidl +++ b/dom/chrome-webidl/MediaController.webidl @@ -20,6 +20,12 @@ enum MediaControlKey { @@ -15,7 +15,12 @@ index 20f416d1c3b41798e0f90bbac5db40ed2a4ab000..06cb4c847fcfba964eeb93089613e293 /** * MediaController is used to control media playback for a tab, and each tab * would only have one media controller, which can be accessed from the -@@ -36,6 +42,9 @@ interface MediaController : EventTarget { +@@ -32,10 +38,14 @@ interface MediaController : EventTarget { + readonly attribute boolean isAudible; + readonly attribute boolean isPlaying; + readonly attribute MediaSessionPlaybackState playbackState; ++ readonly attribute boolean isBeingUsedInPIPModeOrFullscreen; + [Throws] MediaMetadataInit getMetadata(); diff --git a/src/dom/media/mediacontrol/MediaController-cpp.patch b/src/dom/media/mediacontrol/MediaController-cpp.patch index 562756783..68b730922 100644 --- a/src/dom/media/mediacontrol/MediaController-cpp.patch +++ b/src/dom/media/mediacontrol/MediaController-cpp.patch @@ -1,5 +1,5 @@ diff --git a/dom/media/mediacontrol/MediaController.cpp b/dom/media/mediacontrol/MediaController.cpp -index 3f08d24d4ed56bb72ed513ed602b2c8fa48afe7b..690d9abdb0ab8efc019dd606743b82504834faa0 100644 +index 3f08d24d4ed56bb72ed513ed602b2c8fa48afe7b..98dfe4df48f5daebd2b619f0d4d4eb3ac873a66f 100644 --- a/dom/media/mediacontrol/MediaController.cpp +++ b/dom/media/mediacontrol/MediaController.cpp @@ -51,6 +51,25 @@ void MediaController::GetSupportedKeys( @@ -28,3 +28,11 @@ index 3f08d24d4ed56bb72ed513ed602b2c8fa48afe7b..690d9abdb0ab8efc019dd606743b8250 void MediaController::GetMetadata(MediaMetadataInit& aMetadata, ErrorResult& aRv) { if (!IsActive() || mShutdown) { +@@ -412,6 +431,7 @@ void MediaController::SetIsInPictureInPictureMode( + ForceToBecomeMainControllerIfNeeded(); + UpdateDeactivationTimerIfNeeded(); + mPictureInPictureModeChangedEvent.Notify(mIsInPictureInPictureMode); ++ DispatchAsyncEvent(u"pictureinpicturemodechange"_ns); + } + + void MediaController::NotifyMediaFullScreenState(uint64_t aBrowsingContextId, From 658e7eac1c2751b5d83903ee4cca379952ea83a7 Mon Sep 17 00:00:00 2001 From: Slowlife01 Date: Mon, 17 Mar 2025 20:21:51 +0700 Subject: [PATCH 2/2] Null check just in case.. --- src/browser/base/zen-components/ZenMediaController.mjs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/browser/base/zen-components/ZenMediaController.mjs b/src/browser/base/zen-components/ZenMediaController.mjs index c9ef00ac2..1e181f5b0 100644 --- a/src/browser/base/zen-components/ZenMediaController.mjs +++ b/src/browser/base/zen-components/ZenMediaController.mjs @@ -374,7 +374,7 @@ class ZenMediaController { } _onMetadataChange(event) { - if (event.target.id !== this._currentMediaController.id) return; + if (event.target.id !== this._currentMediaController?.id) return; this.updatePipButton(); const metadata = event.target.getMetadata(); @@ -383,7 +383,7 @@ class ZenMediaController { } _onPictureInPictureModeChange(event) { - if (event.target.id !== this._currentMediaController.id) return; + if (event.target.id !== this._currentMediaController?.id) return; if (event.target.isBeingUsedInPIPModeOrFullscreen) { this.hideMediaControls(); this.mediaControlBar.setAttribute('pip', '');