From e6552c8dda22f10874dcda893b62ceec8391b3cb Mon Sep 17 00:00:00 2001 From: Slowlife01 Date: Sun, 9 Mar 2025 09:53:07 +0700 Subject: [PATCH] Feat: basic media control Fixed dragging undefined tabs --- src/browser/base/content/ZenUIManager.mjs | 251 ++++++++++++++++++ .../content/navigator-toolbox-inc-xhtml.patch | 7 +- src/browser/base/content/zen-assets.inc.xhtml | 1 + .../base/content/zen-assets.jar.inc.mn | 1 + .../content/zen-media-controller.inc.xhtml | 48 ++++ .../content/zen-styles/zen-media-controls.css | 191 +++++++++++++ src/browser/themes/shared/zen-icons/icons.css | 34 +++ .../themes/shared/zen-icons/jar.inc.mn | 6 + .../shared/zen-icons/lin/media-next.svg | 8 + .../shared/zen-icons/lin/media-previous.svg | 8 + .../MediaController-webidl.patch | 27 ++ .../mediacontrol/MediaController-cpp.patch | 30 +++ .../mediacontrol/MediaController-h.patch | 12 + .../actors/AudioPlaybackParent-sys-mjs.patch | 17 ++ 14 files changed, 638 insertions(+), 3 deletions(-) create mode 100644 src/browser/base/content/zen-media-controller.inc.xhtml create mode 100644 src/browser/base/content/zen-styles/zen-media-controls.css create mode 100644 src/browser/themes/shared/zen-icons/lin/media-next.svg create mode 100644 src/browser/themes/shared/zen-icons/lin/media-previous.svg create mode 100644 src/dom/chrome-webidl/MediaController-webidl.patch create mode 100644 src/dom/media/mediacontrol/MediaController-cpp.patch create mode 100644 src/dom/media/mediacontrol/MediaController-h.patch create mode 100644 src/toolkit/actors/AudioPlaybackParent-sys-mjs.patch diff --git a/src/browser/base/content/ZenUIManager.mjs b/src/browser/base/content/ZenUIManager.mjs index f644915ca..1f84425ae 100644 --- a/src/browser/base/content/ZenUIManager.mjs +++ b/src/browser/base/content/ZenUIManager.mjs @@ -37,6 +37,8 @@ var gZenUIManager = { window.addEventListener('TabClose', this.onTabClose.bind(this)); this.tabsWrapper.addEventListener('scroll', this.saveScrollbarState.bind(this)); + + gZenMediaController.init(); }, updateTabsToolbar() { @@ -768,3 +770,252 @@ var gZenVerticalTabsManager = { this._tabEdited = null; }, }; + +var gZenMediaController = { + _currentMediaController: null, + _currentBrowser: null, + _mediaUpdateInterval: null, + + mediaTitle: null, + mediaArtist: null, + mediaControlBar: null, + mediaServiceIcon: null, + mediaServiceTitle: null, + mediaProgressBar: null, + mediaCurrentTime: null, + mediaDuration: null, + mediaFocusButton: null, + + supportedKeys: ['playpause', 'previoustrack', 'nexttrack'], + + init() { + this.mediaTitle = document.querySelector('#zen-media-title'); + this.mediaArtist = document.querySelector('#zen-media-artist'); + this.mediaControlBar = document.querySelector('#zen-media-controls-toolbar'); + this.mediaServiceIcon = document.querySelector('#zen-media-service-button > image'); + this.mediaServiceTitle = document.querySelector('#zen-media-service-title'); + this.mediaProgressBar = document.querySelector('#zen-media-progress-bar'); + this.mediaCurrentTime = document.querySelector('#zen-media-current-time'); + this.mediaDuration = document.querySelector('#zen-media-duration'); + this.mediaFocusButton = document.querySelector('#zen-media-focus-button'); + }, + + /** + * Deinitializes a media controller, removing all event listeners and resetting state. + * @param {Object} mediaController - The media controller to deinitialize. + */ + deinitMediaController(mediaController) { + if (!mediaController) return; + + mediaController.onpositionstatechange = null; + mediaController.onplaybackstatechange = null; + mediaController.onsupportedkeyschange = null; + mediaController.onmetadatachange = null; + mediaController.ondeactivated = null; + + this._currentMediaController = null; + this._currentBrowser = null; + + if (this._mediaUpdateInterval) { + clearInterval(this._mediaUpdateInterval); + this._mediaUpdateInterval = null; + } + + this.mediaControlBar.setAttribute('hidden', 'true'); + this.mediaControlBar.removeAttribute('muted'); + this.mediaControlBar.classList.remove('playing'); + }, + + /** + * 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). + */ + setupMediaControl(metadata, positionState) { + if (!this.mediaControlBar.classList.contains('playing')) { + this.mediaControlBar.classList.add('playing'); + } + + this.mediaServiceTitle.textContent = this._currentBrowser._originalURI.displayHost; + this.mediaServiceIcon.src = this._currentBrowser.mIconURL; + this.mediaFocusButton.style.listStyleImage = `url(${this._currentBrowser.mIconURL})`; + + this.mediaControlBar.removeAttribute('hidden'); + this.mediaTitle.textContent = metadata.title || ''; + this.mediaArtist.textContent = metadata.artist || ''; + + this._currentPosition = positionState.position; + this._currentDuration = positionState.duration; + this.updateMediaPosition(); + + for (const key of this.supportedKeys) { + const button = this.mediaControlBar.querySelector(`#zen-media-${key}-button`); + button.disabled = !this._currentMediaController.supportedKeys.includes(key); + } + }, + + /** + * @param {Object} mediaController - The media controller to activate. + * @param {Object} browser - The browser associated with the media controller. + */ + activateMediaControls(mediaController, browser) { + if (this._currentBrowser?.browserId === browser.browserId) return; + else this._currentBrowser = browser; + + mediaController.onpositionstatechange = this.onPositionstateChange.bind(this); + mediaController.onplaybackstatechange = this.onPlaybackstateChange.bind(this); + mediaController.onsupportedkeyschange = this.onSupportedKeysChange.bind(this); + mediaController.onmetadatachange = this.onMetadataChange.bind(this); + mediaController.ondeactivated = this.onDeactivated.bind(this); + + if (this._currentMediaController === mediaController) return; + else this.deinitMediaController(this._currentMediaController); + + this._currentMediaController = mediaController; + + const metadata = mediaController.getMetadata(); + const positionState = mediaController.getPositionState(); + + this.setupMediaControl(metadata, positionState); + }, + + /** + * @param {Event} event - The deactivation event. + */ + onDeactivated(event) { + if (event.target === this._currentMediaController) { + this.deinitMediaController(event.target); + } + }, + + /** + * Updates playback state and UI based on changes. + * @param {Event} event - The playback state change event. + */ + onPlaybackstateChange(event) { + this.mediaControlBar.classList.toggle('playing', event.target.isPlaying); + }, + + /** + * Updates supported keys in the UI. + * @param {Event} event - The supported keys change event. + */ + onSupportedKeysChange(event) { + 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) return; + + this._currentPosition = event.position; + this._currentDuration = event.duration; + this.updateMediaPosition(); + }, + + /** + * Updates the media progress bar and time display. + */ + updateMediaPosition() { + if (this._mediaUpdateInterval) { + clearInterval(this._mediaUpdateInterval); + this._mediaUpdateInterval = null; + } + + this.mediaCurrentTime.textContent = this.formatSecondsToMinutes(this._currentPosition); + this.mediaDuration.textContent = this.formatSecondsToMinutes(this._currentDuration); + this.mediaProgressBar.value = (this._currentPosition / this._currentDuration) * 100; + + if (this._currentMediaController?.isPlaying) { + this._mediaUpdateInterval = setInterval(() => { + if (this._currentMediaController?.isPlaying) { + this._currentPosition += 1; + if (this._currentPosition > this._currentDuration) { + this._currentPosition = this._currentDuration; + } + this.mediaCurrentTime.textContent = this.formatSecondsToMinutes(this._currentPosition); + this.mediaProgressBar.value = (this._currentPosition / this._currentDuration) * 100; + } else { + clearInterval(this._mediaUpdateInterval); + this._mediaUpdateInterval = null; + } + }, 1000); + } + }, + + /** + * Formats seconds into a minutes:seconds string. + * @param {number} seconds - The time in seconds. + * @returns {string} Formatted time string. + */ + formatSecondsToMinutes(seconds) { + const totalSeconds = Math.ceil(seconds); + const minutes = Math.floor(totalSeconds / 60); + const remainingSeconds = totalSeconds % 60; + return `${minutes}:${remainingSeconds.toString().padStart(2, '0')}`; + }, + + /** + * Updates metadata in the UI. + * @param {Event} event - The metadata change event. + */ + onMetadataChange(event) { + const metadata = event.target.getMetadata(); + this.mediaTitle.textContent = metadata.title || ''; + this.mediaArtist.textContent = metadata.artist || ''; + }, + + onMediaPlayPrev() { + if (this._currentMediaController?.supportedKeys.includes('previoustrack')) { + this._currentMediaController.prevTrack(); + } + }, + + onMediaPlayNext() { + if (this._currentMediaController?.supportedKeys.includes('nexttrack')) { + this._currentMediaController.nextTrack(); + } + }, + + onMediaSeekDrag(event) { + this._currentMediaController?.pause(); + const newTime = (event.target.value / 100) * this._currentDuration; + this.mediaCurrentTime.textContent = this.formatSecondsToMinutes(newTime); + }, + + onMediaSeekComplete(event) { + const newPosition = (event.target.value / 100) * this._currentDuration; + if (this._currentMediaController?.supportedKeys.includes('seekto')) { + this._currentMediaController.seekTo(newPosition); + this._currentMediaController.play(); + } + }, + + onMediaFocus() { + this._currentMediaController?.focus(); + }, + + onMediaMute() { + if (!this.mediaControlBar.hasAttribute('muted')) { + this._currentBrowser.mute(); + this.mediaControlBar.setAttribute('muted', ''); + } else { + this._currentBrowser.unmute(); + this.mediaControlBar.removeAttribute('muted'); + } + }, + + onMediaToggle() { + if (this.mediaControlBar.classList.contains('playing')) { + this._currentMediaController?.pause(); + } else { + this._currentMediaController?.play(); + } + }, +}; diff --git a/src/browser/base/content/navigator-toolbox-inc-xhtml.patch b/src/browser/base/content/navigator-toolbox-inc-xhtml.patch index f2be3c096..b2f5e92ae 100644 --- a/src/browser/base/content/navigator-toolbox-inc-xhtml.patch +++ b/src/browser/base/content/navigator-toolbox-inc-xhtml.patch @@ -1,5 +1,5 @@ diff --git a/browser/base/content/navigator-toolbox.inc.xhtml b/browser/base/content/navigator-toolbox.inc.xhtml -index a0a382643a2f74b6d789f3641ef300eed202d5e9..a962e155f1452362a2a35df89c8f56e1c0d9968c 100644 +index 2a57e254c876589bf64bfbd5df188a2b435d8482..c4aecd9738baed13b8dd5687edc95695b0d5af37 100644 --- a/browser/base/content/navigator-toolbox.inc.xhtml +++ b/browser/base/content/navigator-toolbox.inc.xhtml @@ -2,7 +2,7 @@ @@ -73,7 +73,7 @@ index a0a382643a2f74b6d789f3641ef300eed202d5e9..a962e155f1452362a2a35df89c8f56e1 @@ -82,6 +82,7 @@ index a0a382643a2f74b6d789f3641ef300eed202d5e9..a962e155f1452362a2a35df89c8f56e1 #include titlebar-items.inc.xhtml - +#endif ++#include zen-media-controller.inc.xhtml +#include zen-sidebar-icons.inc.xhtml - @@ -89,7 +90,7 @@ index a0a382643a2f74b6d789f3641ef300eed202d5e9..a962e155f1452362a2a35df89c8f56e1 diff --git a/src/browser/base/content/zen-assets.inc.xhtml b/src/browser/base/content/zen-assets.inc.xhtml index 33e923742..c5f6d0b17 100644 --- a/src/browser/base/content/zen-assets.inc.xhtml +++ b/src/browser/base/content/zen-assets.inc.xhtml @@ -26,6 +26,7 @@ + # Scripts used all over the browser diff --git a/src/browser/base/content/zen-assets.jar.inc.mn b/src/browser/base/content/zen-assets.jar.inc.mn index 2f20814ab..e024f82b9 100644 --- a/src/browser/base/content/zen-assets.jar.inc.mn +++ b/src/browser/base/content/zen-assets.jar.inc.mn @@ -49,6 +49,7 @@ content/browser/zen-styles/zen-rices.css (content/zen-styles/zen-rices.css) content/browser/zen-styles/zen-branding.css (content/zen-styles/zen-branding.css) content/browser/zen-styles/zen-welcome.css (content/zen-styles/zen-welcome.css) + content/browser/zen-styles/zen-media-controls.css (content/zen-styles/zen-media-controls.css) content/browser/zen-styles/zen-panels/bookmarks.css (content/zen-styles/zen-panels/bookmarks.css) content/browser/zen-styles/zen-panels/extensions.css (content/zen-styles/zen-panels/extensions.css) diff --git a/src/browser/base/content/zen-media-controller.inc.xhtml b/src/browser/base/content/zen-media-controller.inc.xhtml new file mode 100644 index 000000000..488aa44d4 --- /dev/null +++ b/src/browser/base/content/zen-media-controller.inc.xhtml @@ -0,0 +1,48 @@ + \ No newline at end of file diff --git a/src/browser/base/content/zen-styles/zen-media-controls.css b/src/browser/base/content/zen-styles/zen-media-controls.css new file mode 100644 index 000000000..f1b86a8f2 --- /dev/null +++ b/src/browser/base/content/zen-styles/zen-media-controls.css @@ -0,0 +1,191 @@ +#zen-media-controls-toolbar { + --progress-height: 5px; + --button-spacing: 2px; + + display: flex; + justify-content: space-between; + min-width: 0; + padding: 5px; + border-radius: 10px; + background: var(--zen-toolbar-element-bg) !important; + container-type: inline-size; + + .toolbarbutton-1 { + border-radius: 5px; + color: white; + } + + #zen-media-prev-button, + #zen-media-play-pause-button, + #zen-media-next-button { + margin: 0; + } + + image.toolbarbutton-icon { + padding: 5px; + width: 26px; + height: 26px; + } + + #zen-media-progress-bar { + appearance: none; + width: 100%; + height: var(--progress-height); + margin: 0 8px; + border-radius: 2px; + background-color: rgba(255, 255, 255, 0.2); + cursor: pointer; + transition: height 0.15s ease-out; + + &::-moz-range-track { + background: var(--zen-colors-border); + border-radius: 999px; + height: var(--progress-height); + } + + &::-moz-range-progress { + background: var(--zen-primary-color); + border-radius: 999px; + height: var(--progress-height); + } + + &::-moz-range-thumb { + background: var(--zen-primary-color); + border: none; + width: 14px; + height: 14px; + border-radius: 50%; + cursor: pointer; + } + } + + &:hover { + .show-on-hover { + max-height: 50px; + opacity: 1; + transform: translateY(0); + } + } + + #zen-media-focus-button { + width: 34px; + height: 26px; + align-self: center; + transition: opacity 0.2s ease, transform 0.2s ease; + + @container (max-width: 185px) { + width: 0; + height: 0; + opacity: 0; + padding: 0; + transform: translateX(-20px); + } + + @container (min-width: 185px) { + opacity: 1; + transform: translateX(0); + } + } + + toolbaritem { + flex-grow: 1; + padding: 0; + transition: padding 0.3s ease-out; + } + + .show-on-hover { + padding-inline: 4px; + max-height: 0; + opacity: 0; + overflow: hidden; + transform: translateY(5px); + transition: max-height 0.35s ease-in-out, + opacity 0.25s ease-in-out, + transform 0.3s ease-in-out; + } + + #zen-media-current-time, + #zen-media-duration { + margin: 0 0 0 1px; + } +} + +@keyframes zen-media-controls-show { + from { opacity: 0; transform: translateY(10px); } + to { opacity: 1; transform: translateY(0); } +} + +#zen-media-controls-toolbar { + &:not([hidden]) { + animation: zen-media-controls-show 0.2s ease-out; + display: flex; + } + + &[hidden] { + display: none; + animation: none; + transition: none; + } +} + +#zen-media-service-title, +#zen-media-title, +#zen-media-artist { + white-space: nowrap; + width: 0; + margin-left: 0; +} + +#zen-media-service-title { + align-self: center; + font-size: 13px; + margin-bottom: 5px; + margin-left: 6px; +} + +#zen-media-title, +#zen-media-artist { + align-self: start; + margin-top: 5px; +} + +#zen-media-title { + height: 16px; + font-size: 16px; + font-weight: bold; +} + +#zen-media-artist { + height: 5px; + padding-bottom: 20px; +} + +#zen-media-main-vbox, +#zen-media-service-hbox, +#zen-media-info-vbox, +#zen-media-progress-hbox { + width: 100%; +} + +#zen-media-main-vbox { + height: 100%; + justify-content: space-between; + overflow: hidden; +} + +#zen-media-progress-hbox { + flex-grow: 1; + align-items: center; +} + +#zen-media-controls-hbox { + align-items: flex-end; + justify-content: space-evenly; + max-width: 100%; + overflow: hidden; +} + +#zen-media-service-button image { + width: 20px; + height: 20px; +} \ No newline at end of file diff --git a/src/browser/themes/shared/zen-icons/icons.css b/src/browser/themes/shared/zen-icons/icons.css index c8c69e4e8..f36e37502 100644 --- a/src/browser/themes/shared/zen-icons/icons.css +++ b/src/browser/themes/shared/zen-icons/icons.css @@ -1150,3 +1150,37 @@ menupopup > menuitem:is([type='checkbox']) .menu-iconic-left { #sidebarRevampSeparator { display: none !important; } + +#zen-media-playpause-button { + list-style-image: url('media-play.svg') !important; +} + +#zen-media-controls-toolbar.playing #zen-media-playpause-button { + list-style-image: url('media-pause.svg') !important; +} + +#zen-media-nexttrack-button { + list-style-image: url('media-next.svg') !important; +} + +#zen-media-previoustrack-button { + list-style-image: url('media-previous.svg') !important; +} + +#zen-media-controls-toolbar[muted] #zen-media-mute-button { + list-style-image: url('media-mute.svg') !important; +} + +#zen-media-mute-button { + list-style-image: url('media-unmute.svg') !important; +} + +#zen-media-close-button { + list-style-image: url('close.svg') !important; +} + +#zen-media-controls-toolbar:hover { + #zen-media-focus-button { + list-style-image: url('screen.svg') !important; + } +} diff --git a/src/browser/themes/shared/zen-icons/jar.inc.mn b/src/browser/themes/shared/zen-icons/jar.inc.mn index fa20d2220..515c183f9 100644 --- a/src/browser/themes/shared/zen-icons/jar.inc.mn +++ b/src/browser/themes/shared/zen-icons/jar.inc.mn @@ -66,9 +66,11 @@ skin/classic/browser/zen-icons/manage.svg (../shared/zen-icons/lin/manage.svg) skin/classic/browser/zen-icons/media-loop.svg (../shared/zen-icons/lin/media-loop.svg) skin/classic/browser/zen-icons/media-mute.svg (../shared/zen-icons/lin/media-mute.svg) + skin/classic/browser/zen-icons/media-next.svg (../shared/zen-icons/lin/media-next.svg) skin/classic/browser/zen-icons/media-pause.svg (../shared/zen-icons/lin/media-pause.svg) skin/classic/browser/zen-icons/media-pip.svg (../shared/zen-icons/lin/media-pip.svg) skin/classic/browser/zen-icons/media-play.svg (../shared/zen-icons/lin/media-play.svg) + skin/classic/browser/zen-icons/media-previous.svg (../shared/zen-icons/lin/media-previous.svg) skin/classic/browser/zen-icons/media-speed.svg (../shared/zen-icons/lin/media-speed.svg) skin/classic/browser/zen-icons/media-unmute.svg (../shared/zen-icons/lin/media-unmute.svg) skin/classic/browser/zen-icons/menu-bar.svg (../shared/zen-icons/lin/menu-bar.svg) @@ -198,9 +200,11 @@ skin/classic/browser/zen-icons/manage.svg (../shared/zen-icons/lin/manage.svg) skin/classic/browser/zen-icons/media-loop.svg (../shared/zen-icons/lin/media-loop.svg) skin/classic/browser/zen-icons/media-mute.svg (../shared/zen-icons/lin/media-mute.svg) + skin/classic/browser/zen-icons/media-next.svg (../shared/zen-icons/lin/media-next.svg) skin/classic/browser/zen-icons/media-pause.svg (../shared/zen-icons/lin/media-pause.svg) skin/classic/browser/zen-icons/media-pip.svg (../shared/zen-icons/lin/media-pip.svg) skin/classic/browser/zen-icons/media-play.svg (../shared/zen-icons/lin/media-play.svg) + skin/classic/browser/zen-icons/media-previous.svg (../shared/zen-icons/lin/media-previous.svg) skin/classic/browser/zen-icons/media-speed.svg (../shared/zen-icons/lin/media-speed.svg) skin/classic/browser/zen-icons/media-unmute.svg (../shared/zen-icons/lin/media-unmute.svg) skin/classic/browser/zen-icons/menu-bar.svg (../shared/zen-icons/lin/menu-bar.svg) @@ -330,9 +334,11 @@ skin/classic/browser/zen-icons/manage.svg (../shared/zen-icons/lin/manage.svg) skin/classic/browser/zen-icons/media-loop.svg (../shared/zen-icons/lin/media-loop.svg) skin/classic/browser/zen-icons/media-mute.svg (../shared/zen-icons/lin/media-mute.svg) + skin/classic/browser/zen-icons/media-next.svg (../shared/zen-icons/lin/media-next.svg) skin/classic/browser/zen-icons/media-pause.svg (../shared/zen-icons/lin/media-pause.svg) skin/classic/browser/zen-icons/media-pip.svg (../shared/zen-icons/lin/media-pip.svg) skin/classic/browser/zen-icons/media-play.svg (../shared/zen-icons/lin/media-play.svg) + skin/classic/browser/zen-icons/media-previous.svg (../shared/zen-icons/lin/media-previous.svg) skin/classic/browser/zen-icons/media-speed.svg (../shared/zen-icons/lin/media-speed.svg) skin/classic/browser/zen-icons/media-unmute.svg (../shared/zen-icons/lin/media-unmute.svg) skin/classic/browser/zen-icons/menu-bar.svg (../shared/zen-icons/lin/menu-bar.svg) diff --git a/src/browser/themes/shared/zen-icons/lin/media-next.svg b/src/browser/themes/shared/zen-icons/lin/media-next.svg new file mode 100644 index 000000000..24fd40e36 --- /dev/null +++ b/src/browser/themes/shared/zen-icons/lin/media-next.svg @@ -0,0 +1,8 @@ + + + diff --git a/src/browser/themes/shared/zen-icons/lin/media-previous.svg b/src/browser/themes/shared/zen-icons/lin/media-previous.svg new file mode 100644 index 000000000..d99e7ab53 --- /dev/null +++ b/src/browser/themes/shared/zen-icons/lin/media-previous.svg @@ -0,0 +1,8 @@ + + + diff --git a/src/dom/chrome-webidl/MediaController-webidl.patch b/src/dom/chrome-webidl/MediaController-webidl.patch new file mode 100644 index 000000000..73a252539 --- /dev/null +++ b/src/dom/chrome-webidl/MediaController-webidl.patch @@ -0,0 +1,27 @@ +diff --git a/dom/chrome-webidl/MediaController.webidl b/dom/chrome-webidl/MediaController.webidl +index 20f416d1c3b41798e0f90bbac5db40ed2a4ab000..06cb4c847fcfba964eeb93089613e293dc10bd87 100644 +--- a/dom/chrome-webidl/MediaController.webidl ++++ b/dom/chrome-webidl/MediaController.webidl +@@ -20,6 +20,12 @@ enum MediaControlKey { + "stop", + }; + ++dictionary MediaControllerPositionState { ++ required double duration; ++ required double playbackRate; ++ required double position; ++}; ++ + /** + * 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 { + [Throws] + MediaMetadataInit getMetadata(); + ++ [Throws] ++ MediaControllerPositionState getPositionState(); ++ + [Frozen, Cached, Pure] + readonly attribute sequence supportedKeys; + diff --git a/src/dom/media/mediacontrol/MediaController-cpp.patch b/src/dom/media/mediacontrol/MediaController-cpp.patch new file mode 100644 index 000000000..562756783 --- /dev/null +++ b/src/dom/media/mediacontrol/MediaController-cpp.patch @@ -0,0 +1,30 @@ +diff --git a/dom/media/mediacontrol/MediaController.cpp b/dom/media/mediacontrol/MediaController.cpp +index 3f08d24d4ed56bb72ed513ed602b2c8fa48afe7b..690d9abdb0ab8efc019dd606743b82504834faa0 100644 +--- a/dom/media/mediacontrol/MediaController.cpp ++++ b/dom/media/mediacontrol/MediaController.cpp +@@ -51,6 +51,25 @@ void MediaController::GetSupportedKeys( + } + } + ++void MediaController::GetPositionState(MediaControllerPositionState& aPositionState, ErrorResult& aRv) { ++ if (!IsActive() || mShutdown) { ++ LOG("Cannot get position state: controller is inactive or shut down"); ++ aRv.Throw(NS_ERROR_NOT_AVAILABLE); ++ return; ++ } ++ ++ Maybe currentPositionState = GetCurrentPositionState(); ++ if (!currentPositionState) { ++ LOG("No position state available for controller %" PRId64, Id()); ++ aRv.Throw(NS_ERROR_NOT_AVAILABLE); ++ return; ++ } ++ ++ aPositionState.mDuration = currentPositionState->mDuration; ++ aPositionState.mPosition = currentPositionState->mLastReportedPlaybackPosition; ++ aPositionState.mPlaybackRate = currentPositionState->mPlaybackRate; ++} ++ + void MediaController::GetMetadata(MediaMetadataInit& aMetadata, + ErrorResult& aRv) { + if (!IsActive() || mShutdown) { diff --git a/src/dom/media/mediacontrol/MediaController-h.patch b/src/dom/media/mediacontrol/MediaController-h.patch new file mode 100644 index 000000000..f2fec73c4 --- /dev/null +++ b/src/dom/media/mediacontrol/MediaController-h.patch @@ -0,0 +1,12 @@ +diff --git a/dom/media/mediacontrol/MediaController.h b/dom/media/mediacontrol/MediaController.h +index 8fec9c59e38bc24b9ff6d30ddbaebff67107bc76..5e7f3634f9edef48d6f96b4900f82a7ebbd730be 100644 +--- a/dom/media/mediacontrol/MediaController.h ++++ b/dom/media/mediacontrol/MediaController.h +@@ -90,6 +90,7 @@ class MediaController final : public DOMEventTargetHelper, + JS::Handle aGivenProto) override; + void GetSupportedKeys(nsTArray& aRetVal) const; + void GetMetadata(MediaMetadataInit& aMetadata, ErrorResult& aRv); ++ void GetPositionState(MediaControllerPositionState& aPositionState, ErrorResult& aRv); + IMPL_EVENT_HANDLER(activated); + IMPL_EVENT_HANDLER(deactivated); + IMPL_EVENT_HANDLER(metadatachange); diff --git a/src/toolkit/actors/AudioPlaybackParent-sys-mjs.patch b/src/toolkit/actors/AudioPlaybackParent-sys-mjs.patch new file mode 100644 index 000000000..a00527952 --- /dev/null +++ b/src/toolkit/actors/AudioPlaybackParent-sys-mjs.patch @@ -0,0 +1,17 @@ +diff --git a/toolkit/actors/AudioPlaybackParent.sys.mjs b/toolkit/actors/AudioPlaybackParent.sys.mjs +index db682fd90b2bb5330497d2cf2158ff4cac6bbc47..c3eacff3b2215d29104216dd6086c486a86013e9 100644 +--- a/toolkit/actors/AudioPlaybackParent.sys.mjs ++++ b/toolkit/actors/AudioPlaybackParent.sys.mjs +@@ -11,9 +11,12 @@ export class AudioPlaybackParent extends JSWindowActorParent { + } + receiveMessage(aMessage) { + const browser = this.browsingContext.top.embedderElement; ++ const mediaController = this.browsingContext.mediaController; ++ + switch (aMessage.name) { + case "AudioPlayback:Start": + this._hasAudioPlayback = true; ++ browser.ownerGlobal.gZenMediaController.activateMediaControls(mediaController, browser); + browser.audioPlaybackStarted(); + break; + case "AudioPlayback:Stop":