mirror of
https://github.com/zen-browser/desktop.git
synced 2025-10-06 18:06:35 +00:00
address reviews
This commit is contained in:
@@ -770,252 +770,3 @@ var gZenVerticalTabsManager = {
|
|||||||
this._tabEdited = null;
|
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();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
diff --git a/browser/base/content/navigator-toolbox.inc.xhtml b/browser/base/content/navigator-toolbox.inc.xhtml
|
diff --git a/browser/base/content/navigator-toolbox.inc.xhtml b/browser/base/content/navigator-toolbox.inc.xhtml
|
||||||
index 2a57e254c876589bf64bfbd5df188a2b435d8482..c4aecd9738baed13b8dd5687edc95695b0d5af37 100644
|
index 2a57e254c876589bf64bfbd5df188a2b435d8482..fcb6958cd1379bb5f2a7f63d5c7d8145726001cd 100644
|
||||||
--- a/browser/base/content/navigator-toolbox.inc.xhtml
|
--- a/browser/base/content/navigator-toolbox.inc.xhtml
|
||||||
+++ b/browser/base/content/navigator-toolbox.inc.xhtml
|
+++ b/browser/base/content/navigator-toolbox.inc.xhtml
|
||||||
@@ -2,7 +2,7 @@
|
@@ -2,7 +2,7 @@
|
||||||
@@ -73,7 +73,7 @@ index 2a57e254c876589bf64bfbd5df188a2b435d8482..c4aecd9738baed13b8dd5687edc95695
|
|||||||
</tabs>
|
</tabs>
|
||||||
|
|
||||||
<toolbarbutton id="new-tab-button"
|
<toolbarbutton id="new-tab-button"
|
||||||
@@ -100,11 +108,13 @@
|
@@ -100,11 +108,12 @@
|
||||||
#include private-browsing-indicator.inc.xhtml
|
#include private-browsing-indicator.inc.xhtml
|
||||||
<toolbarbutton id="content-analysis-indicator"
|
<toolbarbutton id="content-analysis-indicator"
|
||||||
class="toolbarbutton-1 content-analysis-indicator-icon"/>
|
class="toolbarbutton-1 content-analysis-indicator-icon"/>
|
||||||
@@ -82,7 +82,6 @@ index 2a57e254c876589bf64bfbd5df188a2b435d8482..c4aecd9738baed13b8dd5687edc95695
|
|||||||
#include titlebar-items.inc.xhtml
|
#include titlebar-items.inc.xhtml
|
||||||
-
|
-
|
||||||
+#endif
|
+#endif
|
||||||
+#include zen-media-controller.inc.xhtml
|
|
||||||
+#include zen-sidebar-icons.inc.xhtml
|
+#include zen-sidebar-icons.inc.xhtml
|
||||||
</toolbar>
|
</toolbar>
|
||||||
-
|
-
|
||||||
@@ -90,7 +89,7 @@ index 2a57e254c876589bf64bfbd5df188a2b435d8482..c4aecd9738baed13b8dd5687edc95695
|
|||||||
<toolbar id="nav-bar"
|
<toolbar id="nav-bar"
|
||||||
class="browser-toolbar chromeclass-location"
|
class="browser-toolbar chromeclass-location"
|
||||||
data-l10n-id="navbar-accessible"
|
data-l10n-id="navbar-accessible"
|
||||||
@@ -490,10 +500,12 @@
|
@@ -490,10 +499,12 @@
|
||||||
consumeanchor="PanelUI-button"
|
consumeanchor="PanelUI-button"
|
||||||
data-l10n-id="appmenu-menu-button-closed2"/>
|
data-l10n-id="appmenu-menu-button-closed2"/>
|
||||||
</toolbaritem>
|
</toolbaritem>
|
||||||
|
@@ -43,6 +43,7 @@
|
|||||||
<script src="chrome://browser/content/zen-components/ZenGradientGenerator.mjs" />
|
<script src="chrome://browser/content/zen-components/ZenGradientGenerator.mjs" />
|
||||||
<script src="chrome://browser/content/zen-components/ZenViewSplitter.mjs"/>
|
<script src="chrome://browser/content/zen-components/ZenViewSplitter.mjs"/>
|
||||||
<script src="chrome://browser/content/zen-components/ZenGlanceManager.mjs" />
|
<script src="chrome://browser/content/zen-components/ZenGlanceManager.mjs" />
|
||||||
|
<script src="chrome://browser/content/zen-components/ZenMediaController.mjs" />
|
||||||
|
|
||||||
# Unimportant scripts
|
# Unimportant scripts
|
||||||
<script src="chrome://browser/content/zen-components/ZenRices.mjs" />
|
<script src="chrome://browser/content/zen-components/ZenRices.mjs" />
|
||||||
|
@@ -25,6 +25,7 @@
|
|||||||
content/browser/zen-components/ZenRices.mjs (zen-components/ZenRices.mjs)
|
content/browser/zen-components/ZenRices.mjs (zen-components/ZenRices.mjs)
|
||||||
content/browser/zen-components/ZenEmojies.mjs (zen-components/ZenEmojies.mjs)
|
content/browser/zen-components/ZenEmojies.mjs (zen-components/ZenEmojies.mjs)
|
||||||
content/browser/zen-components/ZenWelcome.mjs (zen-components/ZenWelcome.mjs)
|
content/browser/zen-components/ZenWelcome.mjs (zen-components/ZenWelcome.mjs)
|
||||||
|
content/browser/zen-components/ZenMediaController.mjs (zen-components/ZenMediaController.mjs)
|
||||||
|
|
||||||
content/browser/zen-styles/zen-theme.css (content/zen-styles/zen-theme.css)
|
content/browser/zen-styles/zen-theme.css (content/zen-styles/zen-theme.css)
|
||||||
content/browser/zen-styles/zen-buttons.css (content/zen-styles/zen-buttons.css)
|
content/browser/zen-styles/zen-buttons.css (content/zen-styles/zen-buttons.css)
|
||||||
|
@@ -1,48 +0,0 @@
|
|||||||
<toolbar id="zen-media-controls-toolbar"
|
|
||||||
class="browser-toolbar customization-target zen-sidebar-toolbar"
|
|
||||||
context="toolbar-context-menu"
|
|
||||||
mode="icons"
|
|
||||||
hidden="true">
|
|
||||||
<toolbaritem>
|
|
||||||
<vbox id="zen-media-main-vbox">
|
|
||||||
<hbox id="zen-media-service-hbox" class="show-on-hover">
|
|
||||||
<html:div id="zen-media-service-button">
|
|
||||||
<image/>
|
|
||||||
</html:div>
|
|
||||||
<label id="zen-media-service-title" fadein="true"/>
|
|
||||||
</hbox>
|
|
||||||
|
|
||||||
<vbox id="zen-media-info-vbox" class="show-on-hover">
|
|
||||||
<label id="zen-media-title" fadein="true"/>
|
|
||||||
<label id="zen-media-artist" fadein="true"/>
|
|
||||||
</vbox>
|
|
||||||
|
|
||||||
<hbox id="zen-media-progress-hbox" class="show-on-hover">
|
|
||||||
<label id="zen-media-current-time">0:00</label>
|
|
||||||
<html:input type="range" id="zen-media-progress-bar"
|
|
||||||
value="0" min="0" max="100" step="0.1"
|
|
||||||
oninput="gZenMediaController.onMediaSeekDrag(event);"
|
|
||||||
onchange="gZenMediaController.onMediaSeekComplete(event);"/>
|
|
||||||
<label id="zen-media-duration">0:00</label>
|
|
||||||
</hbox>
|
|
||||||
|
|
||||||
<hbox id="zen-media-controls-hbox">
|
|
||||||
<toolbarbutton id="zen-media-focus-button"
|
|
||||||
class="toolbarbutton-1"
|
|
||||||
oncommand="gZenMediaController.onMediaFocus();" />
|
|
||||||
<toolbarbutton id="zen-media-previoustrack-button"
|
|
||||||
class="toolbarbutton-1"
|
|
||||||
oncommand="gZenMediaController.onMediaPlayPrev();" />
|
|
||||||
<toolbarbutton id="zen-media-playpause-button"
|
|
||||||
class="toolbarbutton-1"
|
|
||||||
oncommand="gZenMediaController.onMediaToggle();" />
|
|
||||||
<toolbarbutton id="zen-media-nexttrack-button"
|
|
||||||
class="toolbarbutton-1"
|
|
||||||
oncommand="gZenMediaController.onMediaPlayNext();" />
|
|
||||||
<toolbarbutton id="zen-media-mute-button"
|
|
||||||
class="toolbarbutton-1"
|
|
||||||
oncommand="gZenMediaController.onMediaMute();" />
|
|
||||||
</hbox>
|
|
||||||
</vbox>
|
|
||||||
</toolbaritem>
|
|
||||||
</toolbar>
|
|
@@ -1,3 +1,52 @@
|
|||||||
|
<toolbar id="zen-media-controls-toolbar"
|
||||||
|
class="browser-toolbar customization-target zen-sidebar-toolbar"
|
||||||
|
context="toolbar-context-menu"
|
||||||
|
mode="icons"
|
||||||
|
hidden="true">
|
||||||
|
<toolbaritem>
|
||||||
|
<vbox id="zen-media-main-vbox">
|
||||||
|
<hbox id="zen-media-service-hbox" class="show-on-hover">
|
||||||
|
<html:div id="zen-media-service-button">
|
||||||
|
<image/>
|
||||||
|
</html:div>
|
||||||
|
<label id="zen-media-service-title" fadein="true"/>
|
||||||
|
</hbox>
|
||||||
|
|
||||||
|
<vbox id="zen-media-info-vbox" class="show-on-hover">
|
||||||
|
<label id="zen-media-title" fadein="true"/>
|
||||||
|
<label id="zen-media-artist" fadein="true"/>
|
||||||
|
</vbox>
|
||||||
|
|
||||||
|
<hbox id="zen-media-progress-hbox" class="show-on-hover">
|
||||||
|
<label id="zen-media-current-time">0:00</label>
|
||||||
|
<html:input type="range" id="zen-media-progress-bar"
|
||||||
|
value="0" min="0" max="100" step="0.1"
|
||||||
|
oninput="gZenMediaController.onMediaSeekDrag(event);"
|
||||||
|
onchange="gZenMediaController.onMediaSeekComplete(event);"/>
|
||||||
|
<label id="zen-media-duration">0:00</label>
|
||||||
|
</hbox>
|
||||||
|
|
||||||
|
<hbox id="zen-media-controls-hbox">
|
||||||
|
<toolbarbutton id="zen-media-focus-button"
|
||||||
|
class="toolbarbutton-1"
|
||||||
|
oncommand="gZenMediaController.onMediaFocus();" />
|
||||||
|
<toolbarbutton id="zen-media-previoustrack-button"
|
||||||
|
class="toolbarbutton-1"
|
||||||
|
oncommand="gZenMediaController.onMediaPlayPrev();" />
|
||||||
|
<toolbarbutton id="zen-media-playpause-button"
|
||||||
|
class="toolbarbutton-1"
|
||||||
|
oncommand="gZenMediaController.onMediaToggle();" />
|
||||||
|
<toolbarbutton id="zen-media-nexttrack-button"
|
||||||
|
class="toolbarbutton-1"
|
||||||
|
oncommand="gZenMediaController.onMediaPlayNext();" />
|
||||||
|
<toolbarbutton id="zen-media-mute-button"
|
||||||
|
class="toolbarbutton-1"
|
||||||
|
oncommand="gZenMediaController.onMediaMute();" />
|
||||||
|
</hbox>
|
||||||
|
</vbox>
|
||||||
|
</toolbaritem>
|
||||||
|
</toolbar>
|
||||||
|
|
||||||
<toolbar brighttext="true"
|
<toolbar brighttext="true"
|
||||||
id="zen-sidebar-bottom-buttons"
|
id="zen-sidebar-bottom-buttons"
|
||||||
fullscreentoolbar="true"
|
fullscreentoolbar="true"
|
||||||
|
250
src/browser/base/zen-components/ZenMediaController.mjs
Normal file
250
src/browser/base/zen-components/ZenMediaController.mjs
Normal file
@@ -0,0 +1,250 @@
|
|||||||
|
class ZenMediaController {
|
||||||
|
_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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
window.gZenMediaController = new ZenMediaController();
|
@@ -1,17 +1,12 @@
|
|||||||
diff --git a/toolkit/actors/AudioPlaybackParent.sys.mjs b/toolkit/actors/AudioPlaybackParent.sys.mjs
|
diff --git a/toolkit/actors/AudioPlaybackParent.sys.mjs b/toolkit/actors/AudioPlaybackParent.sys.mjs
|
||||||
index db682fd90b2bb5330497d2cf2158ff4cac6bbc47..c3eacff3b2215d29104216dd6086c486a86013e9 100644
|
index db682fd90b2bb5330497d2cf2158ff4cac6bbc47..c44e39f47f9d9b13918930a6c96ee7aa39c51b36 100644
|
||||||
--- a/toolkit/actors/AudioPlaybackParent.sys.mjs
|
--- a/toolkit/actors/AudioPlaybackParent.sys.mjs
|
||||||
+++ b/toolkit/actors/AudioPlaybackParent.sys.mjs
|
+++ b/toolkit/actors/AudioPlaybackParent.sys.mjs
|
||||||
@@ -11,9 +11,12 @@ export class AudioPlaybackParent extends JSWindowActorParent {
|
@@ -14,6 +14,7 @@ export class AudioPlaybackParent extends JSWindowActorParent {
|
||||||
}
|
|
||||||
receiveMessage(aMessage) {
|
|
||||||
const browser = this.browsingContext.top.embedderElement;
|
|
||||||
+ const mediaController = this.browsingContext.mediaController;
|
|
||||||
+
|
|
||||||
switch (aMessage.name) {
|
switch (aMessage.name) {
|
||||||
case "AudioPlayback:Start":
|
case "AudioPlayback:Start":
|
||||||
this._hasAudioPlayback = true;
|
this._hasAudioPlayback = true;
|
||||||
+ browser.ownerGlobal.gZenMediaController.activateMediaControls(mediaController, browser);
|
+ browser.ownerGlobal.gZenMediaController.activateMediaControls(this.browsingContext.mediaController, browser);
|
||||||
browser.audioPlaybackStarted();
|
browser.audioPlaybackStarted();
|
||||||
break;
|
break;
|
||||||
case "AudioPlayback:Stop":
|
case "AudioPlayback:Stop":
|
||||||
|
Reference in New Issue
Block a user