GeckoViewMediaControl.sys.mjs (6241B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this file, 3 * You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 import { GeckoViewModule } from "resource://gre/modules/GeckoViewModule.sys.mjs"; 6 7 export class GeckoViewMediaControl extends GeckoViewModule { 8 onInit() { 9 debug`onInit`; 10 } 11 12 onInitBrowser() { 13 debug`onInitBrowser`; 14 15 const options = { 16 mozSystemGroup: true, 17 capture: false, 18 }; 19 20 this.controller.addEventListener("activated", this, options); 21 this.controller.addEventListener("deactivated", this, options); 22 this.controller.addEventListener("supportedkeyschange", this, options); 23 this.controller.addEventListener("positionstatechange", this, options); 24 this.controller.addEventListener("metadatachange", this, options); 25 this.controller.addEventListener("playbackstatechange", this, options); 26 } 27 28 onDestroyBrowser() { 29 debug`onDestroyBrowser`; 30 31 this.controller.removeEventListener("activated", this); 32 this.controller.removeEventListener("deactivated", this); 33 this.controller.removeEventListener("supportedkeyschange", this); 34 this.controller.removeEventListener("positionstatechange", this); 35 this.controller.removeEventListener("metadatachange", this); 36 this.controller.removeEventListener("playbackstatechange", this); 37 } 38 39 onEnable() { 40 debug`onEnable`; 41 42 if (this.controller.isActive) { 43 this.handleActivated(); 44 } 45 46 this.registerListener([ 47 "GeckoView:MediaSession:Play", 48 "GeckoView:MediaSession:Pause", 49 "GeckoView:MediaSession:Stop", 50 "GeckoView:MediaSession:NextTrack", 51 "GeckoView:MediaSession:PrevTrack", 52 "GeckoView:MediaSession:SeekForward", 53 "GeckoView:MediaSession:SeekBackward", 54 "GeckoView:MediaSession:SkipAd", 55 "GeckoView:MediaSession:SeekTo", 56 "GeckoView:MediaSession:MuteAudio", 57 ]); 58 } 59 60 onDisable() { 61 debug`onDisable`; 62 63 this.unregisterListener(); 64 } 65 66 get controller() { 67 return this.browser.browsingContext.mediaController; 68 } 69 70 onEvent(aEvent, aData) { 71 debug`onEvent: event=${aEvent}, data=${aData}`; 72 73 switch (aEvent) { 74 case "GeckoView:MediaSession:Play": 75 this.controller.play(); 76 break; 77 case "GeckoView:MediaSession:Pause": 78 this.controller.pause(); 79 break; 80 case "GeckoView:MediaSession:Stop": 81 this.controller.stop(); 82 this.eventDispatcher.sendRequest({ 83 type: "GeckoView:MediaSession:Playback:None", 84 }); 85 break; 86 case "GeckoView:MediaSession:NextTrack": 87 this.controller.nextTrack(); 88 break; 89 case "GeckoView:MediaSession:PrevTrack": 90 this.controller.prevTrack(); 91 break; 92 case "GeckoView:MediaSession:SeekForward": 93 this.controller.seekForward(); 94 break; 95 case "GeckoView:MediaSession:SeekBackward": 96 this.controller.seekBackward(); 97 break; 98 case "GeckoView:MediaSession:SkipAd": 99 this.controller.skipAd(); 100 break; 101 case "GeckoView:MediaSession:SeekTo": 102 this.controller.seekTo(aData.time, aData.fast); 103 break; 104 case "GeckoView:MediaSession:MuteAudio": 105 if (aData.mute) { 106 this.browser.mute(); 107 } else { 108 this.browser.unmute(); 109 } 110 break; 111 } 112 } 113 114 // eslint-disable-next-line complexity 115 handleEvent(aEvent) { 116 debug`handleEvent: ${aEvent.type}`; 117 118 switch (aEvent.type) { 119 case "activated": 120 this.handleActivated(); 121 break; 122 case "deactivated": 123 this.handleDeactivated(); 124 break; 125 case "supportedkeyschange": 126 this.handleSupportedKeysChanged(); 127 break; 128 case "positionstatechange": 129 this.handlePositionStateChanged(aEvent); 130 break; 131 case "metadatachange": 132 this.handleMetadataChanged(); 133 break; 134 case "playbackstatechange": 135 this.handlePlaybackStateChanged(); 136 break; 137 default: 138 warn`Unknown event type ${aEvent.type}`; 139 break; 140 } 141 } 142 143 handleActivated() { 144 debug`handleActivated`; 145 146 this.eventDispatcher.sendRequest({ 147 type: "GeckoView:MediaSession:Activated", 148 }); 149 } 150 151 handleDeactivated() { 152 debug`handleDeactivated`; 153 154 this.eventDispatcher.sendRequest({ 155 type: "GeckoView:MediaSession:Deactivated", 156 }); 157 } 158 159 handlePositionStateChanged(aEvent) { 160 debug`handlePositionStateChanged`; 161 162 this.eventDispatcher.sendRequest({ 163 type: "GeckoView:MediaSession:PositionState", 164 state: { 165 duration: aEvent.duration, 166 playbackRate: aEvent.playbackRate, 167 position: aEvent.position, 168 }, 169 }); 170 } 171 172 handleSupportedKeysChanged() { 173 const supported = this.controller.supportedKeys; 174 175 debug`handleSupportedKeysChanged ${supported}`; 176 177 // Mapping it to a key-value store for compatibility with the JNI 178 // implementation for now. 179 const features = new Map(); 180 supported.forEach(key => { 181 features[key] = true; 182 }); 183 184 this.eventDispatcher.sendRequest({ 185 type: "GeckoView:MediaSession:Features", 186 features, 187 }); 188 } 189 190 handleMetadataChanged() { 191 let metadata = null; 192 try { 193 metadata = this.controller.getMetadata(); 194 } catch (e) { 195 warn`Metadata not available`; 196 } 197 debug`handleMetadataChanged ${metadata}`; 198 199 if (metadata) { 200 this.eventDispatcher.sendRequest({ 201 type: "GeckoView:MediaSession:Metadata", 202 metadata, 203 }); 204 } 205 } 206 207 handlePlaybackStateChanged() { 208 const state = this.controller.playbackState; 209 let type = null; 210 211 debug`handlePlaybackStateChanged ${state}`; 212 213 switch (state) { 214 case "none": 215 type = "GeckoView:MediaSession:Playback:None"; 216 break; 217 case "paused": 218 type = "GeckoView:MediaSession:Playback:Paused"; 219 break; 220 case "playing": 221 type = "GeckoView:MediaSession:Playback:Playing"; 222 break; 223 } 224 225 if (!type) { 226 return; 227 } 228 229 this.eventDispatcher.sendRequest({ 230 type, 231 }); 232 } 233 } 234 235 const { debug, warn } = GeckoViewMediaControl.initLogging( 236 "GeckoViewMediaControl" 237 );