| // Copyright 2019 The Cobalt Authors. All Rights Reserved. | |
| // | |
| // Licensed under the Apache License, Version 2.0 (the "License"); | |
| // you may not use this file except in compliance with the License. | |
| // You may obtain a copy of the License at | |
| // | |
| // http://www.apache.org/licenses/LICENSE-2.0 | |
| // | |
| // Unless required by applicable law or agreed to in writing, software | |
| // distributed under the License is distributed on an "AS IS" BASIS, | |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| // See the License for the specific language governing permissions and | |
| // limitations under the License. | |
| const kNewline = '\r\n'; | |
| const kMediaConsoleFrame = 'mediaConsoleFrame'; | |
| const videoCodecFamilies = ['av01', 'avc1', 'avc3', 'hev1', 'hvc1', 'vp09', | |
| 'vp8', 'vp9']; | |
| const audioCodecFamilies = ['ac-3', 'ec-3', 'mp4a', 'opus', 'vorbis']; | |
| const kAltModifier = 'A-'; | |
| const kCtrlModifier = 'C-'; | |
| function MediaConsole(debuggerClient) { | |
| let mediaConsoleNode = document.getElementById('mediaConsole'); | |
| this.debuggerClient = debuggerClient; | |
| this.getDisabledMediaCodecs = window.debugHub.cVal.getValue.bind( | |
| window.debugHub.cVal, 'Media.DisabledMediaCodecs'); | |
| this.setDisabledMediaCodecs = window.debugHub.sendConsoleCommand.bind( | |
| window.debugHub, 'disable_media_codecs'); | |
| this.lastUpdateTime = window.performance.now(); | |
| this.updatePeriod = 1000; | |
| // Registry of all the hotkeys and their function handlers. | |
| // NOTE: This is not the full list, since more will be added once the codec | |
| // specific controls are registered. | |
| this.hotkeyRegistry = new Map([ | |
| ['p', {handler: this.onPlayPause.bind(this), help: '(p)ause/(p)lay'}], | |
| [']', {handler: this.onIncreasePlaybackRate.bind(this), | |
| help: '(])Increase Playback Rate'}], | |
| ['[', {handler: this.onDecreasePlaybackRate.bind(this), | |
| help: '([)Decrease Playback Rate'}], | |
| ]); | |
| this.hotkeyHelpNode = document.createTextNode(''); | |
| mediaConsoleNode.appendChild(this.hotkeyHelpNode); | |
| // Dynamically added div will be changed as we get state information. | |
| let playerStateElem = document.createElement('div'); | |
| this.playerStateText = document.createTextNode(''); | |
| playerStateElem.appendChild(this.playerStateText); | |
| mediaConsoleNode.appendChild(playerStateElem); | |
| this.printToMediaConsoleCallback = this.printToMediaConsole.bind(this); | |
| this.codecSupportMap = new Map(); | |
| this.isInitialized = false; | |
| } | |
| MediaConsole.prototype.setVisible = function(visible) { | |
| if (visible) { | |
| this.initialize(); | |
| } | |
| } | |
| MediaConsole.prototype.initialize = function() { | |
| if (this.initialized) { return; } | |
| this.debuggerClient.attach(); | |
| this.initializeMediaConsoleContext(); | |
| this.initializeSupportedCodecs(); | |
| // Since |initializeSupportedCodecs| is resolved asynchronously, we need to | |
| // wait until |codecSupportMap| is fully populated before finishing the rest | |
| // of the initialization. | |
| if (this.codecSupportMap.size > 0) { | |
| this.registerCodecHotkeys(); | |
| this.generateHotkeyHelpText(); | |
| this.isInitialized = true; | |
| } | |
| } | |
| MediaConsole.prototype.initializeMediaConsoleContext = function() { | |
| let js = debugHub.readDebugContentText('media_console_context.js'); | |
| this.debuggerClient.evaluate(js); | |
| } | |
| MediaConsole.prototype.initializeSupportedCodecs = function() { | |
| if (this.codecSupportMap.size > 0) { | |
| return; | |
| } | |
| function handleSupportQueryResponse(response) { | |
| let results = JSON.parse(response.result.value); | |
| results.forEach(record => { | |
| let codecFamily = record[0]; | |
| let isSupported = record[1] || !!this.codecSupportMap.get(codecFamily); | |
| this.codecSupportMap.set(codecFamily, isSupported); | |
| }); | |
| }; | |
| this.debuggerClient.evaluate(`_mediaConsoleContext.getSupportedCodecs()`, | |
| handleSupportQueryResponse.bind(this)); | |
| } | |
| MediaConsole.prototype.registerCodecHotkeys = function() { | |
| // Codec control hotkeys are of the form: Modifier + Number. | |
| // Due to the large number of codecs, we split all the video codecs into Ctrl | |
| // and all the audio codecs into Alt. | |
| // "C-" prefix indicates a key with the ctrlKey modifier. | |
| // "A-" prefix indicates a key with the altKey modifier. | |
| // Additionally, we only register those hotkeys that we filter as supported. | |
| function registerCodecHotkey(modifier, codec, index) { | |
| if (!this.codecSupportMap.get(codec)) { | |
| return; | |
| } | |
| let modAndNum = `${modifier}${index + 1}`; | |
| let helpStr = `( ${modAndNum} ) ${codec}`; | |
| this.hotkeyRegistry.set(modAndNum, { | |
| handler: this.onToggleCodec.bind(this, codec), | |
| help: helpStr | |
| }); | |
| }; | |
| videoCodecFamilies.forEach(registerCodecHotkey.bind(this, kCtrlModifier)); | |
| audioCodecFamilies.forEach(registerCodecHotkey.bind(this, kAltModifier)); | |
| } | |
| MediaConsole.prototype.generateHotkeyHelpText = function() { | |
| // Generate the help text that will be displayed at the top of the console | |
| // output. | |
| let hotkeysHelp = `Hotkeys: ${kNewline}`; | |
| hotkeysHelp += `Ctrl+Num toggles video codecs ${kNewline}`; | |
| hotkeysHelp += `Alt+Num toggles audio codecs ${kNewline}`; | |
| const generateHelpText = function(hotkeyInfo, hotkey, map) { | |
| hotkeysHelp += hotkeyInfo.help + ', '; | |
| }; | |
| this.hotkeyRegistry.forEach(generateHelpText); | |
| hotkeysHelp = hotkeysHelp.substring(0, hotkeysHelp.length-2); | |
| hotkeysHelp += kNewline; | |
| hotkeysHelp += kNewline; | |
| hotkeysHelp += kNewline; | |
| this.hotkeyHelpNode.nodeValue = hotkeysHelp; | |
| } | |
| MediaConsole.prototype.parseStateFromResponse = function(response) { | |
| // TODO: Handle the situation where the response contains exceptionDetails. | |
| // https://chromedevtools.github.io/devtools-protocol/1-3/Runtime#method-evaluate | |
| return JSON.parse(response.result.value); | |
| } | |
| MediaConsole.prototype.printToMediaConsole = function(response) { | |
| const state = this.parseStateFromResponse(response); | |
| let videoStatus = 'No primary video.'; | |
| if(state.hasPrimaryVideo) { videoStatus = ''; } | |
| this.playerStateText.textContent = | |
| `Primary Video: ${videoStatus} ${kNewline} \ | |
| Paused: ${state.paused} ${kNewline} \ | |
| Current Time: ${state.currentTime} ${kNewline} \ | |
| Duration: ${state.duration} ${kNewline} \ | |
| Playback Rate: ${state.playbackRate} \ | |
| (default: ${state.defaultPlaybackRate}) ${kNewline} \ | |
| Disabled Media Codecs: ${state.disabledMediaCodecs}`; | |
| } | |
| MediaConsole.prototype.update = function(forceUpdate) { | |
| const t = window.performance.now(); | |
| if (forceUpdate || t > this.lastUpdateTime + this.updatePeriod) { | |
| this.lastUpdateTime = t; | |
| this.debuggerClient.evaluate('_mediaConsoleContext.getPlayerState()', | |
| this.printToMediaConsoleCallback); | |
| } | |
| } | |
| MediaConsole.prototype.onWheel = function(event) {} | |
| MediaConsole.prototype.onKeyup = function(event) {} | |
| MediaConsole.prototype.onKeypress = function(event) {} | |
| MediaConsole.prototype.onInput = function(event) {} | |
| MediaConsole.prototype.onKeydown = function(event) { | |
| let matchedHotkeyEntry = null; | |
| this.hotkeyRegistry.forEach(function(hotkeyInfo, hotkey, map) { | |
| let eventKey = event.key; | |
| if(event.altKey) { eventKey = kAltModifier + eventKey; } | |
| if(event.ctrlKey) { eventKey = kCtrlModifier + eventKey; } | |
| if(eventKey == hotkey) { | |
| matchedHotkeyEntry = hotkeyInfo; | |
| } | |
| }); | |
| if (matchedHotkeyEntry) { | |
| matchedHotkeyEntry.handler(); | |
| } | |
| } | |
| MediaConsole.prototype.onPlayPause = function() { | |
| this.debuggerClient.evaluate('_mediaConsoleContext.togglePlayPause()', | |
| this.printToMediaConsoleCallback); | |
| } | |
| MediaConsole.prototype.onIncreasePlaybackRate = function() { | |
| this.debuggerClient.evaluate('_mediaConsoleContext.increasePlaybackRate()', | |
| this.printToMediaConsoleCallback); | |
| } | |
| MediaConsole.prototype.onDecreasePlaybackRate = function() { | |
| this.debuggerClient.evaluate('_mediaConsoleContext.decreasePlaybackRate()', | |
| this.printToMediaConsoleCallback); | |
| } | |
| MediaConsole.prototype.onToggleCodec = function(codecToToggle) { | |
| let codecs = this.getDisabledMediaCodecs().split(";"); | |
| codecs = codecs.filter(s => s.length > 0); | |
| toggled = codecs.filter(c => c != codecToToggle); | |
| if (codecs.length == toggled.length) { toggled.push(codecToToggle); } | |
| this.setDisabledMediaCodecs(toggled.join(';')); | |
| // Force an immediate update of the console to reflect the above change. | |
| this.update(true); | |
| } |