mirror of
https://github.com/zen-browser/desktop.git
synced 2026-03-06 16:57:04 +00:00
@@ -37,6 +37,8 @@ var gZenUIManager = {
|
||||
|
||||
window.addEventListener('TabClose', this.onTabClose.bind(this));
|
||||
this.tabsWrapper.addEventListener('scroll', this.saveScrollbarState.bind(this));
|
||||
|
||||
gZenMediaController.init();
|
||||
},
|
||||
|
||||
updateTabsToolbar() {
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
<link rel="stylesheet" type="text/css" href="chrome://browser/skin/zen-icons/icons.css" />
|
||||
<link rel="stylesheet" type="text/css" href="chrome://browser/content/zen-styles/zen-branding.css" />
|
||||
<link rel="stylesheet" type="text/css" href="chrome://browser/content/zen-styles/zen-welcome.css" />
|
||||
<link rel="stylesheet" type="text/css" href="chrome://browser/content/zen-styles/zen-media-controls.css" />
|
||||
</linkset>
|
||||
|
||||
# Scripts used all over the browser
|
||||
@@ -42,6 +43,7 @@
|
||||
<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/ZenGlanceManager.mjs" />
|
||||
<script src="chrome://browser/content/zen-components/ZenMediaController.mjs" />
|
||||
|
||||
# Unimportant scripts
|
||||
<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/ZenEmojies.mjs (zen-components/ZenEmojies.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-buttons.css (content/zen-styles/zen-buttons.css)
|
||||
@@ -49,6 +50,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)
|
||||
|
||||
51
src/browser/base/content/zen-media-player.inc.xhtml
Normal file
51
src/browser/base/content/zen-media-player.inc.xhtml
Normal file
@@ -0,0 +1,51 @@
|
||||
<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">
|
||||
<hbox id="zen-media-service-button">
|
||||
<image/>
|
||||
</hbox>
|
||||
<hbox id="zen-media-service-title" fadein="true">
|
||||
<label class="service" />
|
||||
<label class="tld" />
|
||||
</hbox>
|
||||
</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,4 @@
|
||||
#include zen-media-player.inc.xhtml
|
||||
<toolbar brighttext="true"
|
||||
id="zen-sidebar-bottom-buttons"
|
||||
fullscreentoolbar="true"
|
||||
|
||||
252
src/browser/base/content/zen-styles/zen-media-controls.css
Normal file
252
src/browser/base/content/zen-styles/zen-media-controls.css
Normal file
@@ -0,0 +1,252 @@
|
||||
#zen-media-controls-toolbar {
|
||||
--progress-height: 5px;
|
||||
--button-spacing: 2px;
|
||||
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
min-width: 0;
|
||||
background: transparent;
|
||||
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 {
|
||||
& #zen-media-main-vbox {
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.show-on-hover {
|
||||
max-height: 50px;
|
||||
margin: 2px 6px;
|
||||
margin-bottom: 0;
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
#zen-media-focus-button {
|
||||
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;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
padding: 4px;
|
||||
border-radius: var(--border-radius-medium);
|
||||
background: light-dark(rgb(255, 255, 255), rgb(11, 11, 11)) !important;
|
||||
width: 100%;
|
||||
box-shadow: 0 0 10px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.show-on-hover {
|
||||
max-height: 0;
|
||||
opacity: 0;
|
||||
overflow: hidden;
|
||||
transform: translateY(5px);
|
||||
margin: 0 6px;
|
||||
transition:
|
||||
max-height 0.1s ease-in-out,
|
||||
opacity 0.1s ease-in-out,
|
||||
transform 0.05s ease-in-out,
|
||||
margin 0.15s ease-in-out;
|
||||
}
|
||||
|
||||
#zen-media-current-time,
|
||||
#zen-media-duration {
|
||||
margin: 0 0 0 1px;
|
||||
font-size: x-small;
|
||||
opacity: 0.7;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes zen-media-controls-show {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(10px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
#zen-media-controls-toolbar {
|
||||
display: none;
|
||||
animation: none;
|
||||
transition: none;
|
||||
|
||||
&:not([hidden]) {
|
||||
animation: zen-media-controls-show 0.2s ease-out;
|
||||
display: flex;
|
||||
height: 2.5rem;
|
||||
overflow: visible;
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
}
|
||||
}
|
||||
|
||||
#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-left: 6px;
|
||||
|
||||
& label {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
& .tld {
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
|
||||
#zen-media-title,
|
||||
#zen-media-artist {
|
||||
align-self: start;
|
||||
}
|
||||
|
||||
#zen-media-artist {
|
||||
opacity: 0.7;
|
||||
font-weight: 500;
|
||||
font-size: smaller;
|
||||
}
|
||||
|
||||
#zen-media-title {
|
||||
height: 16px;
|
||||
font-size: math;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#zen-media-main-vbox,
|
||||
#zen-media-service-hbox,
|
||||
#zen-media-info-vbox,
|
||||
#zen-media-progress-hbox {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#zen-media-service-hbox {
|
||||
margin-top: 6px;
|
||||
}
|
||||
|
||||
#zen-media-info-vbox {
|
||||
& label {
|
||||
width: 100%;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
|
||||
#zen-media-main-vbox {
|
||||
height: 100%;
|
||||
justify-content: space-between;
|
||||
overflow: hidden;
|
||||
transition: gap 0.1s ease-out;
|
||||
transition-delay: 0.1s;
|
||||
gap: 0px;
|
||||
}
|
||||
|
||||
#zen-media-progress-hbox {
|
||||
flex-grow: 1;
|
||||
height: 1.1rem;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
#zen-media-controls-hbox {
|
||||
align-items: flex-end;
|
||||
justify-content: space-between;
|
||||
max-width: 100%;
|
||||
overflow: hidden;
|
||||
--toolbarbutton-outer-padding: 0;
|
||||
}
|
||||
|
||||
#zen-media-service-button {
|
||||
align-items: center;
|
||||
|
||||
& image {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
:root:not([zen-sidebar-expanded='true']) {
|
||||
#zen-media-controls-toolbar {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
295
src/browser/base/zen-components/ZenMediaController.mjs
Normal file
295
src/browser/base/zen-components/ZenMediaController.mjs
Normal file
@@ -0,0 +1,295 @@
|
||||
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;
|
||||
mediaProgressBarContainer = 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');
|
||||
this.mediaProgressBarContainer = document.querySelector('#zen-media-progress-hbox');
|
||||
|
||||
window.addEventListener('TabSelect', (event) => {
|
||||
if (this._currentBrowser) {
|
||||
if (event.target.linkedBrowser.browserId === this._currentBrowser.browserId) {
|
||||
this.mediaControlBar.setAttribute('hidden', 'true');
|
||||
} else {
|
||||
this.mediaControlBar.removeAttribute('hidden');
|
||||
}
|
||||
|
||||
gZenUIManager.updateTabsToolbar();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 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');
|
||||
|
||||
gZenUIManager.updateTabsToolbar();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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');
|
||||
}
|
||||
|
||||
// Have it displayed as e.g. <white>youtube</white><grey>.com</grey>
|
||||
let host = this._currentBrowser._originalURI.displayHost;
|
||||
if (host.startsWith('www.')) host = host.slice(4);
|
||||
// note: we might have subdomains, so we need to split the host
|
||||
const [service, ...tld] = host.split('.');
|
||||
this.mediaServiceTitle.querySelector('.service').textContent = service;
|
||||
this.mediaServiceTitle.querySelector('.tld').textContent = '.' + tld.join('.');
|
||||
|
||||
this.mediaServiceIcon.src = this._currentBrowser.mIconURL;
|
||||
this.mediaFocusButton.style.listStyleImage = `url(${this._currentBrowser.mIconURL})`;
|
||||
|
||||
this.mediaTitle.textContent = metadata.title || '';
|
||||
this.mediaArtist.textContent = metadata.artist || '';
|
||||
|
||||
gZenUIManager.updateTabsToolbar();
|
||||
|
||||
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) {
|
||||
this.updateMuteState();
|
||||
|
||||
if (this._currentBrowser?.browserId === browser.browserId) return;
|
||||
else {
|
||||
this.deinitMediaController(this._currentMediaController);
|
||||
this._currentMediaController = mediaController;
|
||||
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);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
if (this._currentDuration >= 900_000) return this.mediaProgressBarContainer.setAttribute('hidden', 'true');
|
||||
else this.mediaProgressBarContainer.removeAttribute('hidden');
|
||||
|
||||
this.mediaCurrentTime.textContent = this.formatSecondsToTime(this._currentPosition);
|
||||
this.mediaDuration.textContent = this.formatSecondsToTime(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.formatSecondsToTime(this._currentPosition);
|
||||
this.mediaProgressBar.value = (this._currentPosition / this._currentDuration) * 100;
|
||||
} else {
|
||||
clearInterval(this._mediaUpdateInterval);
|
||||
this._mediaUpdateInterval = null;
|
||||
}
|
||||
}, 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';
|
||||
|
||||
const totalSeconds = Math.max(0, Math.ceil(seconds));
|
||||
const hours = Math.floor(totalSeconds / 3600);
|
||||
const minutes = Math.floor((totalSeconds % 3600) / 60).toString();
|
||||
const secs = (totalSeconds % 60).toString();
|
||||
|
||||
if (hours > 0) {
|
||||
return `${hours}:${minutes.padStart(2, '0')}:${secs.padStart(2, '0')}`;
|
||||
}
|
||||
|
||||
return `${minutes}:${secs.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.formatSecondsToTime(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();
|
||||
}
|
||||
}
|
||||
|
||||
updateMuteState() {
|
||||
if (!this._currentBrowser) return;
|
||||
if (this._currentBrowser._audioMuted) {
|
||||
this.mediaControlBar.setAttribute('muted', '');
|
||||
} else {
|
||||
this.mediaControlBar.removeAttribute('muted');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
window.gZenMediaController = new ZenMediaController();
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
1
src/browser/themes/shared/zen-icons/lin/media-next.svg
Normal file
1
src/browser/themes/shared/zen-icons/lin/media-next.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="18px" height="18px" viewBox="0 0 18 18"><path d="M4.05,3.677l8.254,4.57c.595,.33,.595,1.177,0,1.506L4.05,14.323c-.582,.322-1.3-.094-1.3-.753V4.43c0-.66,.718-1.075,1.3-.753Z" fill="none" stroke="context-fill" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"></path><line x1="15.25" y1="15.25" x2="15.25" y2="2.75" fill="none" stroke="context-fill" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" data-color="color-2"></line></svg>
|
||||
|
After Width: | Height: | Size: 571 B |
@@ -1 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="18" width="18" viewBox="0 0 18 18"><g stroke-linecap="round" stroke-width="1.5" fill="none" stroke="context-fill" stroke-opacity="context-fill-opacity" stroke-linejoin="round" class="nc-icon-wrapper"><rect x="2.75" y="2.75" width="3.5" height="12.5" rx="1" ry="1"></rect><rect x="11.75" y="2.75" width="3.5" height="12.5" rx="1" ry="1" data-color="color-2"></rect></g></svg>
|
||||
<svg width="21" height="20" viewBox="0 0 21 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M6.33333 2.22217H4.66667C3.59278 2.22217 2.72222 3.09273 2.72222 4.16661V15.8333C2.72222 16.9072 3.59278 17.7777 4.66667 17.7777H6.33333C7.40722 17.7777 8.27778 16.9072 8.27778 15.8333V4.16661C8.27778 3.09273 7.40722 2.22217 6.33333 2.22217Z" fill="context-fill" fill-opacity="context-fill-opacity"/>
|
||||
<path d="M16.3333 2.22217H14.6667C13.5928 2.22217 12.7222 3.09273 12.7222 4.16661V15.8333C12.7222 16.9072 13.5928 17.7777 14.6667 17.7777H16.3333C17.4072 17.7777 18.2778 16.9072 18.2778 15.8333V4.16661C18.2778 3.09273 17.4072 2.22217 16.3333 2.22217Z" fill="context-fill" fill-opacity="context-fill-opacity"/>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 423 B After Width: | Height: | Size: 723 B |
@@ -1 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="18" width="18" viewBox="0 0 18 18"><g stroke-linecap="round" stroke-width="1.5" fill="none" stroke="context-fill" stroke-opacity="context-fill-opacity" stroke-linejoin="round" class="nc-icon-wrapper"><path d="M11.652,8.568l-3.651-2.129c-.333-.194-.752,.046-.752,.432v4.259c0,.386,.419,.626,.752,.432l3.651-2.129c.331-.193,.331-.671,0-.864Z" data-color="color-2"></path><path d="M4.987,2.961c.778-.518,1.662-.89,2.612-1.075"></path><path d="M1.879,7.631c.185-.968,.562-1.867,1.091-2.657"></path><path d="M4.987,15.039c.778,.518,1.662,.89,2.612,1.075"></path><path d="M1.879,10.369c.185,.968,.562,1.867,1.091,2.657"></path><path d="M10.435,1.892c3.317,.666,5.815,3.595,5.815,7.108s-2.499,6.443-5.817,7.108"></path></g></svg>
|
||||
<svg width="21" height="20" viewBox="0 0 21 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M17.2778 8.30893L6.73111 2.46893C6.11667 2.12893 5.39111 2.13893 4.78778 2.49448C4.19 2.84671 3.83334 3.46893 3.83334 4.16004V15.84C3.83334 16.5312 4.19 17.1534 4.78778 17.5056C5.09778 17.6878 5.43889 17.78 5.78222 17.78C6.10778 17.78 6.43334 17.6967 6.73111 17.5323L17.2767 11.6923C17.8944 11.3512 18.2778 10.7023 18.2778 10.0012C18.2778 9.30004 17.8944 8.65004 17.2778 8.30893Z" fill="context-fill" fill-opacity="context-fill-opacity"/>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 771 B After Width: | Height: | Size: 551 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="18px" height="18px" viewBox="0 0 18 18"><path d="M13.95,14.323L5.697,9.753c-.595-.33-.595-1.177,0-1.506L13.95,3.677c.582-.322,1.3,.094,1.3,.753V13.57c0,.66-.718,1.075-1.3,.753Z" fill="none" stroke="context-fill" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"></path><line x1="2.75" y1="2.75" x2="2.75" y2="15.25" fill="none" stroke="context-fill" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" data-color="color-2"></line></svg>
|
||||
|
After Width: | Height: | Size: 573 B |
27
src/dom/chrome-webidl/MediaController-webidl.patch
Normal file
27
src/dom/chrome-webidl/MediaController-webidl.patch
Normal file
@@ -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<MediaControlKey> supportedKeys;
|
||||
|
||||
12
src/dom/html/HTMLMediaElement-cpp.patch
Normal file
12
src/dom/html/HTMLMediaElement-cpp.patch
Normal file
@@ -0,0 +1,12 @@
|
||||
diff --git a/dom/html/HTMLMediaElement.cpp b/dom/html/HTMLMediaElement.cpp
|
||||
index 0b8dee1ca22b1f0ddcf7e6a976f606990104b56e..a3a5a7cfcaa8a4719e56988bc917183fa3cc9d10 100644
|
||||
--- a/dom/html/HTMLMediaElement.cpp
|
||||
+++ b/dom/html/HTMLMediaElement.cpp
|
||||
@@ -455,6 +455,7 @@ class HTMLMediaElement::MediaControlKeyListener final
|
||||
// audible state. Therefore, in that case we would noitfy the audible state
|
||||
// when media starts playing.
|
||||
if (mState == MediaPlaybackState::ePlayed) {
|
||||
+ NotifyMediaPositionState();
|
||||
NotifyAudibleStateChanged(mIsOwnerAudible
|
||||
? MediaAudibleState::eAudible
|
||||
: MediaAudibleState::eInaudible);
|
||||
30
src/dom/media/mediacontrol/MediaController-cpp.patch
Normal file
30
src/dom/media/mediacontrol/MediaController-cpp.patch
Normal file
@@ -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<PositionState> 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) {
|
||||
12
src/dom/media/mediacontrol/MediaController-h.patch
Normal file
12
src/dom/media/mediacontrol/MediaController-h.patch
Normal file
@@ -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<JSObject*> aGivenProto) override;
|
||||
void GetSupportedKeys(nsTArray<MediaControlKey>& 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);
|
||||
17
src/toolkit/actors/AudioPlaybackParent-sys-mjs.patch
Normal file
17
src/toolkit/actors/AudioPlaybackParent-sys-mjs.patch
Normal file
@@ -0,0 +1,17 @@
|
||||
diff --git a/toolkit/actors/AudioPlaybackParent.sys.mjs b/toolkit/actors/AudioPlaybackParent.sys.mjs
|
||||
index db682fd90b2bb5330497d2cf2158ff4cac6bbc47..4e6f891275d489418b8ea58a10345a1baa3a554c 100644
|
||||
--- a/toolkit/actors/AudioPlaybackParent.sys.mjs
|
||||
+++ b/toolkit/actors/AudioPlaybackParent.sys.mjs
|
||||
@@ -14,10 +14,12 @@ export class AudioPlaybackParent extends JSWindowActorParent {
|
||||
switch (aMessage.name) {
|
||||
case "AudioPlayback:Start":
|
||||
this._hasAudioPlayback = true;
|
||||
+ browser.ownerGlobal.gZenMediaController.activateMediaControls(this.browsingContext.mediaController, browser);
|
||||
browser.audioPlaybackStarted();
|
||||
break;
|
||||
case "AudioPlayback:Stop":
|
||||
this._hasAudioPlayback = false;
|
||||
+ browser.ownerGlobal.gZenMediaController.updateMuteState();
|
||||
browser.audioPlaybackStopped();
|
||||
break;
|
||||
case "AudioPlayback:ActiveMediaBlockStart":
|
||||
Reference in New Issue
Block a user