Skip to content

Commit 964c886

Browse files
Merge pull request #7569 from Slowlife01/mediadevice
feat(MediaController): controls for media devices
2 parents f6536aa + ae07ed9 commit 964c886

10 files changed

+237
-32
lines changed

src/browser/base/content/zen-media-player.inc.xhtml renamed to src/browser/base/content/zen-media-controls.inc.xhtml

+6
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,12 @@
3636
class="toolbarbutton-1" />
3737
<toolbarbutton id="zen-media-mute-button"
3838
class="toolbarbutton-1" />
39+
<hbox id="media-device-buttons">
40+
<toolbarbutton id="zen-media-mute-mic-button"
41+
class="toolbarbutton-1" />
42+
<toolbarbutton id="zen-media-mute-camera-button"
43+
class="toolbarbutton-1" />
44+
</hbox>
3945
</hbox>
4046
</vbox>
4147
</toolbaritem>

src/browser/base/content/zen-sidebar-icons.inc.xhtml

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
#include zen-media-player.inc.xhtml
1+
#include zen-media-controls.inc.xhtml
22
<toolbar brighttext="true"
33
id="zen-sidebar-bottom-buttons"
44
fullscreentoolbar="true"

src/browser/base/content/zen-styles/zen-media-controls.css

+16-2
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,20 @@
1919
--toolbarbutton-outer-padding: 2px;
2020
}
2121

22+
&:not([media-sharing]) {
23+
#media-device-buttons {
24+
display: none;
25+
}
26+
}
27+
28+
&[media-sharing] #zen-media-controls-hbox > toolbarbutton:not(:first-child) {
29+
display: none;
30+
31+
#media-device-buttons {
32+
display: flex;
33+
}
34+
}
35+
2236
&:not([can-pip]) {
2337
#zen-media-info-vbox {
2438
width: calc(100% - 26px);
@@ -206,7 +220,7 @@
206220
}
207221

208222
#zen-media-info-vbox {
209-
#zen-media-controls-toolbar:not([media-position-hidden='true']) & {
223+
#zen-media-controls-toolbar:not([media-position-hidden]) & {
210224
transition-delay: 0.01s !important;
211225
}
212226
overflow-x: hidden;
@@ -251,7 +265,7 @@
251265
align-items: center;
252266
padding-top: 0px !important;
253267

254-
#zen-media-controls-toolbar[media-position-hidden='true'] & {
268+
#zen-media-controls-toolbar[media-position-hidden] & {
255269
display: none;
256270
}
257271
}

src/browser/base/zen-components/ZenMediaController.mjs

+167-29
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@
2727
_tabTimeout = null;
2828
_controllerSwitchTimeout = null;
2929

30+
#isSeeking = false;
31+
3032
init() {
3133
if (!Services.prefs.getBoolPref('zen.mediacontrols.enabled', true)) return;
3234

@@ -50,6 +52,11 @@
5052
}
5153

5254
#initEventListeners() {
55+
this.mediaControlBar.addEventListener('mousedown', (event) => {
56+
if (event.target.closest(':is(toolbarbutton,#zen-media-progress-hbox)')) return;
57+
else this.onMediaFocus();
58+
});
59+
5360
this.mediaControlBar.addEventListener('command', (event) => {
5461
const button = event.target.closest('toolbarbutton');
5562
if (!button) return;
@@ -75,13 +82,21 @@
7582
case 'zen-media-playpause-button':
7683
this.onMediaToggle();
7784
break;
85+
case 'zen-media-mute-mic-button':
86+
this.onMicrophoneMuteToggle();
87+
break;
88+
case 'zen-media-mute-camera-button':
89+
this.onCameraMuteToggle();
90+
break;
7891
}
7992
});
8093

8194
this.mediaProgressBar.addEventListener('input', this.onMediaSeekDrag.bind(this));
8295
this.mediaProgressBar.addEventListener('change', this.onMediaSeekComplete.bind(this));
8396

8497
window.addEventListener('TabSelect', (event) => {
98+
if (this.isSharing) return;
99+
85100
const linkedBrowser = event.target.linkedBrowser;
86101
this.switchController();
87102

@@ -123,25 +138,29 @@
123138
});
124139

125140
window.addEventListener('DOMAudioPlaybackStopped', () => this.updateMuteState());
141+
window.webrtcUI.on('peer-request-allowed', this._onMediaShareStart.bind(this));
126142
}
127143

128144
onTabDiscardedOrClosed(event) {
129-
const linkedBrowser = event.target.linkedBrowser;
130-
if (!linkedBrowser?.browsingContext?.mediaController) return;
131-
this.deinitMediaController(
132-
linkedBrowser.browsingContext.mediaController,
133-
true,
134-
linkedBrowser.browserId === this._currentBrowser?.browserId,
135-
true
136-
);
137-
}
145+
const { linkedBrowser } = event.target;
138146

139-
async deinitMediaController(mediaController, shouldForget = true, shouldOverride = true, shouldHide = true) {
140-
if (!mediaController) return;
147+
if (linkedBrowser?.browserId === this._currentBrowser?.browserId) {
148+
this.deinitMediaSharingControls(linkedBrowser);
149+
this.hideMediaControls();
150+
}
141151

142-
const retrievedMediaController = this.mediaControllersMap.get(mediaController.id);
152+
if (linkedBrowser?.browsingContext.mediaController) {
153+
this.deinitMediaController(
154+
linkedBrowser.browsingContext.mediaController,
155+
true,
156+
linkedBrowser.browserId === this._currentBrowser?.browserId,
157+
true
158+
);
159+
}
160+
}
143161

144-
if (shouldForget) {
162+
async deinitMediaController(mediaController, shouldForget = true, shouldOverride = true, shouldHide = true) {
163+
if (shouldForget && mediaController) {
145164
mediaController.removeEventListener('pictureinpicturemodechange', this.onPipModeChange);
146165
mediaController.removeEventListener('positionstatechange', this.onPositionstateChange);
147166
mediaController.removeEventListener('playbackstatechange', this.onPlaybackstateChange);
@@ -167,6 +186,26 @@
167186
}
168187
}
169188

189+
get isSharing() {
190+
return this.mediaControlBar.hasAttribute('media-sharing');
191+
}
192+
193+
set isSharing(value) {
194+
if (this._currentBrowser && !value) {
195+
const webRTC = this._currentBrowser.browsingContext.currentWindowGlobal.getActor('WebRTC');
196+
webRTC.sendAsyncMessage('webrtc:UnmuteMicrophone');
197+
webRTC.sendAsyncMessage('webrtc:UnmuteCamera');
198+
}
199+
200+
if (!value) {
201+
this.mediaControlBar.removeAttribute('mic-muted');
202+
this.mediaControlBar.removeAttribute('camera-muted');
203+
} else {
204+
this.mediaControlBar.setAttribute('media-position-hidden', '');
205+
this.mediaControlBar.setAttribute('media-sharing', '');
206+
}
207+
}
208+
170209
hideMediaControls() {
171210
if (this.mediaControlBar.hasAttribute('hidden')) return;
172211

@@ -189,12 +228,16 @@
189228
}
190229

191230
showMediaControls() {
192-
if (!this._currentMediaController) return;
193-
194-
if (this._currentMediaController.isBeingUsedInPIPModeOrFullscreen) return this.hideMediaControls();
195231
if (!this.mediaControlBar.hasAttribute('hidden')) return;
196232

197-
this.updatePipButton();
233+
if (!this.isSharing) {
234+
if (!this._currentMediaController) return;
235+
if (this._currentMediaController.isBeingUsedInPIPModeOrFullscreen) return this.hideMediaControls();
236+
237+
this.updatePipButton();
238+
this.mediaControlBar.removeAttribute('media-sharing');
239+
}
240+
198241
const mediaInfoElements = [this.mediaTitle, this.mediaArtist];
199242
for (const element of mediaInfoElements) {
200243
element.removeAttribute('overflow'); // So we can properly recalculate the overflow
@@ -282,7 +325,7 @@
282325
lastUpdated: Date.now(),
283326
});
284327

285-
if (!this._currentBrowser) {
328+
if (!this._currentBrowser && !this.isSharing) {
286329
this.setupMediaController(mediaController, browser);
287330
this.setupMediaControlUI(metadata, positionState);
288331
}
@@ -295,6 +338,70 @@
295338
mediaController.addEventListener('deactivated', this.onDeactivated);
296339
}
297340

341+
activateMediaDeviceControls(browser) {
342+
if (browser?.browsingContext.currentWindowGlobal.hasActivePeerConnections()) {
343+
this.mediaControlBar.removeAttribute('can-pip');
344+
this._currentBrowser = browser;
345+
346+
const tab = window.gBrowser.getTabForBrowser(browser);
347+
const iconURL = browser.mIconURL || `page-icon:${browser.currentURI.spec}`;
348+
349+
this.isSharing = true;
350+
351+
this.mediaFocusButton.style.listStyleImage = `url(${iconURL})`;
352+
this.mediaTitle.textContent = tab.label;
353+
this.mediaArtist.textContent = '';
354+
355+
this.showMediaControls();
356+
tab.addEventListener('TabAttrModified', this._onTabAttrModified.bind(this));
357+
}
358+
}
359+
360+
deinitMediaSharingControls(browser) {
361+
const tab = window.gBrowser.getTabForBrowser(browser);
362+
if (tab) tab.removeEventListener('TabAttrModified', this._onTabAttrModified.bind(this));
363+
364+
this.isSharing = false;
365+
this._currentBrowser = null;
366+
}
367+
368+
_onTabAttrModified(event) {
369+
const { changed } = event.detail;
370+
const { linkedBrowser } = event.target;
371+
372+
if (changed.includes('sharing') && !linkedBrowser.browsingContext.currentWindowGlobal.hasActivePeerConnections()) {
373+
if (this._currentBrowser?.browserId === linkedBrowser.browserId) {
374+
event.target.removeEventListener('TabAttrModified', this._onTabAttrModified.bind(this));
375+
this.deinitMediaSharingControls(linkedBrowser);
376+
377+
this.hideMediaControls();
378+
this.switchController(true);
379+
}
380+
}
381+
}
382+
383+
_onMediaShareStart(event) {
384+
const { innerWindowID } = event;
385+
386+
for (const browser of window.gBrowser.browsers) {
387+
if (browser.innerWindowID === innerWindowID) {
388+
const webRTC = browser.browsingContext.currentWindowGlobal.getActor('WebRTC');
389+
webRTC.sendAsyncMessage('webrtc:UnmuteMicrophone');
390+
webRTC.sendAsyncMessage('webrtc:UnmuteCamera');
391+
392+
if (this._currentBrowser) this.deinitMediaSharingControls(this._currentBrowser);
393+
if (this._currentMediaController) {
394+
this._currentMediaController.pause();
395+
this.deinitMediaController(this._currentMediaController, true, true).then(() =>
396+
this.activateMediaDeviceControls(browser)
397+
);
398+
} else this.activateMediaDeviceControls(browser);
399+
400+
break;
401+
}
402+
}
403+
}
404+
298405
_onDeactivated(event) {
299406
this.deinitMediaController(event.target, true, event.target.id === this._currentMediaController.id, true);
300407
this.switchController();
@@ -339,6 +446,9 @@
339446
switchController(force = false) {
340447
let timeout = 3000;
341448

449+
if (this.isSharing) return;
450+
if (this.#isSeeking) return;
451+
342452
if (this._controllerSwitchTimeout) {
343453
clearTimeout(this._controllerSwitchTimeout);
344454
this._controllerSwitchTimeout = null;
@@ -466,6 +576,8 @@
466576
}
467577

468578
onMediaSeekDrag(event) {
579+
this.#isSeeking = true;
580+
469581
this._currentMediaController?.pause();
470582
const newTime = (event.target.value / 100) * this._currentDuration;
471583
this.mediaCurrentTime.textContent = this.formatSecondsToTime(newTime);
@@ -477,11 +589,18 @@
477589
this._currentMediaController.seekTo(newPosition);
478590
this._currentMediaController.play();
479591
}
592+
593+
this.#isSeeking = false;
480594
}
481595

482596
onMediaFocus() {
483597
if (!this._currentBrowser) return;
484-
this._currentMediaController?.focus();
598+
599+
if (this._currentMediaController) this._currentMediaController.focus();
600+
else if (this._currentBrowser) {
601+
const tab = window.gBrowser.getTabForBrowser(this._currentBrowser);
602+
if (tab) window.ZenWorkspaces.switchTabIfNeeded(tab);
603+
}
485604
}
486605

487606
onMediaMute() {
@@ -503,9 +622,13 @@
503622
}
504623

505624
onControllerClose() {
506-
this._currentMediaController?.pause();
625+
if (this._currentMediaController) {
626+
this._currentMediaController.pause();
627+
this.deinitMediaController(this._currentMediaController);
628+
} else if (this.isSharing) this.deinitMediaSharingControls(this._currentBrowser);
629+
630+
this.hideMediaControls();
507631
this.switchController(true);
508-
this.deinitMediaController(this._currentMediaController);
509632
}
510633

511634
onMediaPip() {
@@ -514,22 +637,37 @@
514637
.sendAsyncMessage('PictureInPicture:KeyToggle');
515638
}
516639

640+
onMicrophoneMuteToggle() {
641+
if (this._currentBrowser) {
642+
const shouldMute = this.mediaControlBar.hasAttribute('mic-muted') ? 'webrtc:UnmuteMicrophone' : 'webrtc:MuteMicrophone';
643+
644+
this._currentBrowser.browsingContext.currentWindowGlobal.getActor('WebRTC').sendAsyncMessage(shouldMute);
645+
this.mediaControlBar.toggleAttribute('mic-muted');
646+
}
647+
}
648+
649+
onCameraMuteToggle() {
650+
if (this._currentBrowser) {
651+
const shouldMute = this.mediaControlBar.hasAttribute('camera-muted') ? 'webrtc:UnmuteCamera' : 'webrtc:MuteCamera';
652+
653+
this._currentBrowser.browsingContext.currentWindowGlobal.getActor('WebRTC').sendAsyncMessage(shouldMute);
654+
this.mediaControlBar.toggleAttribute('camera-muted');
655+
}
656+
}
657+
517658
updateMuteState() {
518659
if (!this._currentBrowser) return;
519-
if (this._currentBrowser._audioMuted) {
520-
this.mediaControlBar.setAttribute('muted', '');
521-
} else {
522-
this.mediaControlBar.removeAttribute('muted');
523-
}
660+
this.mediaControlBar.toggleAttribute('muted', this._currentBrowser._audioMuted);
524661
}
525662

526663
updatePipButton() {
527664
if (!this._currentBrowser) return;
665+
if (this.isSharing) return;
666+
528667
const { totalPipCount, totalPipDisabled } = PictureInPicture.getEligiblePipVideoCount(this._currentBrowser);
668+
const canPip = totalPipCount === 1 || (totalPipDisabled > 0 && lazy.RESPECT_PIP_DISABLED);
529669

530-
if (totalPipCount === 1 || (totalPipDisabled > 0 && lazy.RESPECT_PIP_DISABLED))
531-
this.mediaControlBar.setAttribute('can-pip', '');
532-
else this.mediaControlBar.removeAttribute('can-pip');
670+
this.mediaControlBar.toggleAttribute('can-pip', canPip);
533671
}
534672
}
535673

src/browser/themes/shared/zen-icons/icons.css

+18
Original file line numberDiff line numberDiff line change
@@ -1181,6 +1181,24 @@ menupopup > menuitem:is([type='checkbox']) .menu-iconic-left {
11811181
list-style-image: url('close.svg') !important;
11821182
}
11831183

1184+
#zen-media-mute-mic-button {
1185+
list-style-image: url('microphone.fill.svg') !important;
1186+
}
1187+
1188+
#zen-media-controls-toolbar[mic-muted] #zen-media-mute-mic-button {
1189+
list-style-image: url('microphone-blocked.fill.svg') !important;
1190+
fill: rgb(224, 41, 29);
1191+
}
1192+
1193+
#zen-media-mute-camera-button {
1194+
list-style-image: url('video.fill.svg') !important;
1195+
}
1196+
1197+
#zen-media-controls-toolbar[camera-muted] #zen-media-mute-camera-button {
1198+
list-style-image: url('video-blocked.fill.svg') !important;
1199+
fill: rgb(224, 41, 29);
1200+
}
1201+
11841202
#zen-media-pip-button {
11851203
list-style-image: url('chrome://global/skin/media/picture-in-picture-open.svg') !important;
11861204
}

0 commit comments

Comments
 (0)