blob: 8e47411058361d8cdc6611c7318bcffdf5401687 [file] [log] [blame]
// Copyright (c) 2017 The WebRTC project authors. All Rights Reserved.
//
// Use of this source code is governed by a BSD-style license
// that can be found in the LICENSE file in the root of the source
// tree. An additional intellectual property rights grant can be found
// in the file PATENTS. All contributing project authors may
// be found in the AUTHORS file in the root of the source tree.
/**
* Opens the score stats inspector dialog.
* @param {String} dialogId: identifier of the dialog to show.
* @return {DOMElement} The dialog element that has been opened.
*/
function openScoreStatsInspector(dialogId) {
var dialog = document.getElementById(dialogId);
dialog.showModal();
return dialog;
}
/**
* Closes the score stats inspector dialog.
*/
function closeScoreStatsInspector() {
var dialog = document.querySelector('dialog[open]');
if (dialog == null)
return;
dialog.close();
}
/**
* Audio inspector class.
* @constructor
*/
function AudioInspector() {
console.debug('Creating an AudioInspector instance.');
this.audioPlayer_ = new Audio();
this.metadata_ = {};
this.currentScore_ = null;
this.audioInspector_ = null;
this.snackbarContainer_ = document.querySelector('#snackbar');
// Get base URL without anchors.
this.baseUrl_ = window.location.href;
var index = this.baseUrl_.indexOf('#');
if (index > 0)
this.baseUrl_ = this.baseUrl_.substr(0, index)
console.info('Base URL set to "' + window.location.href + '".');
window.event.stopPropagation();
this.createTextAreasForCopy_();
this.createAudioInspector_();
this.initializeEventHandlers_();
// When MDL is ready, parse the anchor (if any) to show the requested
// experiment.
var self = this;
document.querySelectorAll('header a')[0].addEventListener(
'mdl-componentupgraded', function() {
if (!self.parseWindowAnchor()) {
// If not experiment is requested, open the first section.
console.info('No anchor parsing, opening the first section.');
document.querySelectorAll('header a > span')[0].click();
}
});
}
/**
* Parse the anchor in the window URL.
* @return {bool} True if the parsing succeeded.
*/
AudioInspector.prototype.parseWindowAnchor = function() {
var index = location.href.indexOf('#');
if (index == -1) {
console.debug('No # found in the URL.');
return false;
}
var anchor = location.href.substr(index - location.href.length + 1);
console.info('Anchor changed: "' + anchor + '".');
var parts = anchor.split('&');
if (parts.length != 3) {
console.info('Ignoring anchor with invalid number of fields.');
return false;
}
var openDialog = document.querySelector('dialog[open]');
try {
// Open the requested dialog if not already open.
if (!openDialog || openDialog.id != parts[1]) {
!openDialog || openDialog.close();
document.querySelectorAll('header a > span')[
parseInt(parts[0].substr(1))].click();
openDialog = openScoreStatsInspector(parts[1]);
}
// Trigger click on cell.
var cell = openDialog.querySelector('td.' + parts[2]);
cell.focus();
cell.click();
this.showNotification_('Experiment selected.');
return true;
} catch (e) {
this.showNotification_('Cannot select experiment :(');
console.error('Exception caught while selecting experiment: "' + e + '".');
}
return false;
}
/**
* Set up the inspector for a new score.
* @param {DOMElement} element: Element linked to the selected score.
*/
AudioInspector.prototype.selectedScoreChange = function(element) {
if (this.currentScore_ == element) { return; }
if (this.currentScore_ != null) {
this.currentScore_.classList.remove('selected-score');
}
this.currentScore_ = element;
this.currentScore_.classList.add('selected-score');
this.stopAudio();
// Read metadata.
var matches = element.querySelectorAll('input[type=hidden]');
this.metadata_ = {};
for (var index = 0; index < matches.length; ++index) {
this.metadata_[matches[index].name] = matches[index].value;
}
// Show the audio inspector interface.
var container = element.parentNode.parentNode.parentNode.parentNode;
var audioInspectorPlaceholder = container.querySelector(
'.audio-inspector-placeholder');
this.moveInspector_(audioInspectorPlaceholder);
};
/**
* Stop playing audio.
*/
AudioInspector.prototype.stopAudio = function() {
console.info('Pausing audio play out.');
this.audioPlayer_.pause();
};
/**
* Show a text message using the snackbar.
*/
AudioInspector.prototype.showNotification_ = function(text) {
try {
this.snackbarContainer_.MaterialSnackbar.showSnackbar({
message: text, timeout: 2000});
} catch (e) {
// Fallback to an alert.
alert(text);
console.warn('Cannot use snackbar: "' + e + '"');
}
}
/**
* Move the audio inspector DOM node into the given parent.
* @param {DOMElement} newParentNode: New parent for the inspector.
*/
AudioInspector.prototype.moveInspector_ = function(newParentNode) {
newParentNode.appendChild(this.audioInspector_);
};
/**
* Play audio file from url.
* @param {string} metadataFieldName: Metadata field name.
*/
AudioInspector.prototype.playAudio = function(metadataFieldName) {
if (this.metadata_[metadataFieldName] == undefined) { return; }
if (this.metadata_[metadataFieldName] == 'None') {
alert('The selected stream was not used during the experiment.');
return;
}
this.stopAudio();
this.audioPlayer_.src = this.metadata_[metadataFieldName];
console.debug('Audio source URL: "' + this.audioPlayer_.src + '"');
this.audioPlayer_.play();
console.info('Playing out audio.');
};
/**
* Create hidden text areas to copy URLs.
*
* For each dialog, one text area is created since it is not possible to select
* text on a text area outside of the active dialog.
*/
AudioInspector.prototype.createTextAreasForCopy_ = function() {
var self = this;
document.querySelectorAll('dialog.mdl-dialog').forEach(function(element) {
var textArea = document.createElement("textarea");
textArea.classList.add('url-copy');
textArea.style.position = 'fixed';
textArea.style.bottom = 0;
textArea.style.left = 0;
textArea.style.width = '2em';
textArea.style.height = '2em';
textArea.style.border = 'none';
textArea.style.outline = 'none';
textArea.style.boxShadow = 'none';
textArea.style.background = 'transparent';
textArea.style.fontSize = '6px';
element.appendChild(textArea);
});
}
/**
* Create audio inspector.
*/
AudioInspector.prototype.createAudioInspector_ = function() {
var buttonIndex = 0;
function getButtonHtml(icon, toolTipText, caption, metadataFieldName) {
var buttonId = 'audioInspectorButton' + buttonIndex++;
html = caption == null ? '' : caption;
html += '<button class="mdl-button mdl-js-button mdl-button--icon ' +
'mdl-js-ripple-effect" id="' + buttonId + '">' +
'<i class="material-icons">' + icon + '</i>' +
'<div class="mdl-tooltip" data-mdl-for="' + buttonId + '">' +
toolTipText +
'</div>';
if (metadataFieldName != null) {
html += '<input type="hidden" value="' + metadataFieldName + '">'
}
html += '</button>'
return html;
}
// TODO(alessiob): Add timeline and highlight current track by changing icon
// color.
this.audioInspector_ = document.createElement('div');
this.audioInspector_.classList.add('audio-inspector');
this.audioInspector_.innerHTML =
'<div class="mdl-grid">' +
'<div class="mdl-layout-spacer"></div>' +
'<div class="mdl-cell mdl-cell--2-col">' +
getButtonHtml('play_arrow', 'Simulated echo', 'E<sub>in</sub>',
'echo_filepath') +
'</div>' +
'<div class="mdl-cell mdl-cell--2-col">' +
getButtonHtml('stop', 'Stop playing [S]', null, '__stop__') +
'</div>' +
'<div class="mdl-cell mdl-cell--2-col">' +
getButtonHtml('play_arrow', 'Render stream', 'R<sub>in</sub>',
'render_filepath') +
'</div>' +
'<div class="mdl-layout-spacer"></div>' +
'</div>' +
'<div class="mdl-grid">' +
'<div class="mdl-layout-spacer"></div>' +
'<div class="mdl-cell mdl-cell--2-col">' +
getButtonHtml('play_arrow', 'Capture stream (APM input) [1]',
'Y\'<sub>in</sub>', 'capture_filepath') +
'</div>' +
'<div class="mdl-cell mdl-cell--2-col"><strong>APM</strong></div>' +
'<div class="mdl-cell mdl-cell--2-col">' +
getButtonHtml('play_arrow', 'APM output [2]', 'Y<sub>out</sub>',
'apm_output_filepath') +
'</div>' +
'<div class="mdl-layout-spacer"></div>' +
'</div>' +
'<div class="mdl-grid">' +
'<div class="mdl-layout-spacer"></div>' +
'<div class="mdl-cell mdl-cell--2-col">' +
getButtonHtml('play_arrow', 'Echo-free capture stream',
'Y<sub>in</sub>', 'echo_free_capture_filepath') +
'</div>' +
'<div class="mdl-cell mdl-cell--2-col">' +
getButtonHtml('play_arrow', 'Clean capture stream',
'Y<sub>clean</sub>', 'clean_capture_input_filepath') +
'</div>' +
'<div class="mdl-cell mdl-cell--2-col">' +
getButtonHtml('play_arrow', 'APM reference [3]', 'Y<sub>ref</sub>',
'apm_reference_filepath') +
'</div>' +
'<div class="mdl-layout-spacer"></div>' +
'</div>';
// Add an invisible node as initial container for the audio inspector.
var parent = document.createElement('div');
parent.style.display = 'none';
this.moveInspector_(parent);
document.body.appendChild(parent);
};
/**
* Initialize event handlers.
*/
AudioInspector.prototype.initializeEventHandlers_ = function() {
var self = this;
// Score cells.
document.querySelectorAll('td.single-score-cell').forEach(function(element) {
element.onclick = function() {
self.selectedScoreChange(this);
}
});
// Copy anchor URLs icons.
if (document.queryCommandSupported('copy')) {
document.querySelectorAll('td.single-score-cell button').forEach(
function(element) {
element.onclick = function() {
// Find the text area in the dialog.
var textArea = element.closest('dialog').querySelector(
'textarea.url-copy');
// Copy.
textArea.value = self.baseUrl_ + '#' + element.getAttribute(
'data-anchor');
textArea.select();
try {
if (!document.execCommand('copy'))
throw 'Copy returned false';
self.showNotification_('Experiment URL copied.');
} catch (e) {
self.showNotification_('Cannot copy experiment URL :(');
console.error(e);
}
}
});
} else {
self.showNotification_(
'The copy command is disabled. URL copy is not enabled.');
}
// Audio inspector buttons.
this.audioInspector_.querySelectorAll('button').forEach(function(element) {
var target = element.querySelector('input[type=hidden]');
if (target == null) { return; }
element.onclick = function() {
if (target.value == '__stop__') {
self.stopAudio();
} else {
self.playAudio(target.value);
}
};
});
// Dialog close handlers.
var dialogs = document.querySelectorAll('dialog').forEach(function(element) {
element.onclose = function() {
self.stopAudio();
}
});
// Keyboard shortcuts.
window.onkeyup = function(e) {
var key = e.keyCode ? e.keyCode : e.which;
switch (key) {
case 49: // 1.
self.playAudio('capture_filepath');
break;
case 50: // 2.
self.playAudio('apm_output_filepath');
break;
case 51: // 3.
self.playAudio('apm_reference_filepath');
break;
case 83: // S.
case 115: // s.
self.stopAudio();
break;
}
};
// Hash change.
window.onhashchange = function(e) {
self.parseWindowAnchor();
}
};