blob: 895687b8d60986292b6cdd5eabffc58210d4e0b5 [file] [log] [blame]
// 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);
}