|
27 | 27 | _tabTimeout = null;
|
28 | 28 | _controllerSwitchTimeout = null;
|
29 | 29 |
|
| 30 | + #isSeeking = false; |
| 31 | + |
30 | 32 | init() {
|
31 | 33 | if (!Services.prefs.getBoolPref('zen.mediacontrols.enabled', true)) return;
|
32 | 34 |
|
|
50 | 52 | }
|
51 | 53 |
|
52 | 54 | #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 | + |
53 | 60 | this.mediaControlBar.addEventListener('command', (event) => {
|
54 | 61 | const button = event.target.closest('toolbarbutton');
|
55 | 62 | if (!button) return;
|
|
75 | 82 | case 'zen-media-playpause-button':
|
76 | 83 | this.onMediaToggle();
|
77 | 84 | 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; |
78 | 91 | }
|
79 | 92 | });
|
80 | 93 |
|
81 | 94 | this.mediaProgressBar.addEventListener('input', this.onMediaSeekDrag.bind(this));
|
82 | 95 | this.mediaProgressBar.addEventListener('change', this.onMediaSeekComplete.bind(this));
|
83 | 96 |
|
84 | 97 | window.addEventListener('TabSelect', (event) => {
|
| 98 | + if (this.isSharing) return; |
| 99 | + |
85 | 100 | const linkedBrowser = event.target.linkedBrowser;
|
86 | 101 | this.switchController();
|
87 | 102 |
|
|
123 | 138 | });
|
124 | 139 |
|
125 | 140 | window.addEventListener('DOMAudioPlaybackStopped', () => this.updateMuteState());
|
| 141 | + window.webrtcUI.on('peer-request-allowed', this._onMediaShareStart.bind(this)); |
126 | 142 | }
|
127 | 143 |
|
128 | 144 | 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; |
138 | 146 |
|
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 | + } |
141 | 151 |
|
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 | + } |
143 | 161 |
|
144 |
| - if (shouldForget) { |
| 162 | + async deinitMediaController(mediaController, shouldForget = true, shouldOverride = true, shouldHide = true) { |
| 163 | + if (shouldForget && mediaController) { |
145 | 164 | mediaController.removeEventListener('pictureinpicturemodechange', this.onPipModeChange);
|
146 | 165 | mediaController.removeEventListener('positionstatechange', this.onPositionstateChange);
|
147 | 166 | mediaController.removeEventListener('playbackstatechange', this.onPlaybackstateChange);
|
|
167 | 186 | }
|
168 | 187 | }
|
169 | 188 |
|
| 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 | + |
170 | 209 | hideMediaControls() {
|
171 | 210 | if (this.mediaControlBar.hasAttribute('hidden')) return;
|
172 | 211 |
|
|
189 | 228 | }
|
190 | 229 |
|
191 | 230 | showMediaControls() {
|
192 |
| - if (!this._currentMediaController) return; |
193 |
| - |
194 |
| - if (this._currentMediaController.isBeingUsedInPIPModeOrFullscreen) return this.hideMediaControls(); |
195 | 231 | if (!this.mediaControlBar.hasAttribute('hidden')) return;
|
196 | 232 |
|
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 | + |
198 | 241 | const mediaInfoElements = [this.mediaTitle, this.mediaArtist];
|
199 | 242 | for (const element of mediaInfoElements) {
|
200 | 243 | element.removeAttribute('overflow'); // So we can properly recalculate the overflow
|
|
282 | 325 | lastUpdated: Date.now(),
|
283 | 326 | });
|
284 | 327 |
|
285 |
| - if (!this._currentBrowser) { |
| 328 | + if (!this._currentBrowser && !this.isSharing) { |
286 | 329 | this.setupMediaController(mediaController, browser);
|
287 | 330 | this.setupMediaControlUI(metadata, positionState);
|
288 | 331 | }
|
|
295 | 338 | mediaController.addEventListener('deactivated', this.onDeactivated);
|
296 | 339 | }
|
297 | 340 |
|
| 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 | + |
298 | 405 | _onDeactivated(event) {
|
299 | 406 | this.deinitMediaController(event.target, true, event.target.id === this._currentMediaController.id, true);
|
300 | 407 | this.switchController();
|
|
339 | 446 | switchController(force = false) {
|
340 | 447 | let timeout = 3000;
|
341 | 448 |
|
| 449 | + if (this.isSharing) return; |
| 450 | + if (this.#isSeeking) return; |
| 451 | + |
342 | 452 | if (this._controllerSwitchTimeout) {
|
343 | 453 | clearTimeout(this._controllerSwitchTimeout);
|
344 | 454 | this._controllerSwitchTimeout = null;
|
|
466 | 576 | }
|
467 | 577 |
|
468 | 578 | onMediaSeekDrag(event) {
|
| 579 | + this.#isSeeking = true; |
| 580 | + |
469 | 581 | this._currentMediaController?.pause();
|
470 | 582 | const newTime = (event.target.value / 100) * this._currentDuration;
|
471 | 583 | this.mediaCurrentTime.textContent = this.formatSecondsToTime(newTime);
|
|
477 | 589 | this._currentMediaController.seekTo(newPosition);
|
478 | 590 | this._currentMediaController.play();
|
479 | 591 | }
|
| 592 | + |
| 593 | + this.#isSeeking = false; |
480 | 594 | }
|
481 | 595 |
|
482 | 596 | onMediaFocus() {
|
483 | 597 | 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 | + } |
485 | 604 | }
|
486 | 605 |
|
487 | 606 | onMediaMute() {
|
|
503 | 622 | }
|
504 | 623 |
|
505 | 624 | 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(); |
507 | 631 | this.switchController(true);
|
508 |
| - this.deinitMediaController(this._currentMediaController); |
509 | 632 | }
|
510 | 633 |
|
511 | 634 | onMediaPip() {
|
|
514 | 637 | .sendAsyncMessage('PictureInPicture:KeyToggle');
|
515 | 638 | }
|
516 | 639 |
|
| 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 | + |
517 | 658 | updateMuteState() {
|
518 | 659 | 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); |
524 | 661 | }
|
525 | 662 |
|
526 | 663 | updatePipButton() {
|
527 | 664 | if (!this._currentBrowser) return;
|
| 665 | + if (this.isSharing) return; |
| 666 | + |
528 | 667 | const { totalPipCount, totalPipDisabled } = PictureInPicture.getEligiblePipVideoCount(this._currentBrowser);
|
| 668 | + const canPip = totalPipCount === 1 || (totalPipDisabled > 0 && lazy.RESPECT_PIP_DISABLED); |
529 | 669 |
|
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); |
533 | 671 | }
|
534 | 672 | }
|
535 | 673 |
|
|
0 commit comments