Commit 66019915 authored by Ted Meyer's avatar Ted Meyer Committed by Commit Bot

Preliminary UI for media log inspecting.

New UI allows the selection and viewing of events and properties for
each media player open on the tab being inspected.

This feature depends on enabling the InspectorMediaLogging flag to get
any data.

What's missing:
 - A timeline view for events
 - A right click menu on a media player to inspect it

Design Doc: https://docs.google.com/document/d/1NMTjKc-Q99UDTS0QFoftVwhmzZTEh0fsHGQJ6I82IRY/edit#heading=h.vhj3t4rsmjte
UX discussion: https://docs.google.com/document/d/1EKuv_KNFFIclJOEx6k70WMUOPgsXJs7DmMHNQayKSuI/edit?ts=5d7c04d2

Screenshots:
https://files.tedm.io/dtss/5.png
https://files.tedm.io/dtss/6.png

Bug: 794255
Change-Id: Iff2d4f057168e7f20829ef4752f6dfcdea75a293
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1755008Reviewed-by: default avatarYang Guo <yangguo@chromium.org>
Commit-Queue: Ted Meyer <tmathmeyer@chromium.org>
Cr-Commit-Position: refs/heads/master@{#703831}
parent edec205d
......@@ -373,6 +373,16 @@ all_devtools_files = [
"front_end/mobile_throttling/ThrottlingPresets.js",
"front_end/mobile_throttling/throttlingSettingsTab.css",
"front_end/mobile_throttling/ThrottlingSettingsTab.js",
"front_end/media/eventDisplayTable.css",
"front_end/media/EventDisplayTable.js",
"front_end/media/MainView.js",
"front_end/media/MediaTable.js",
"front_end/media/PlayerDetailView.js",
"front_end/media/playerListView.css",
"front_end/media/PlayerListView.js",
"front_end/media/module.json",
"front_end/media/MediaModel.js",
"front_end/media/mediaView.css",
"front_end/ndb_app.json",
"front_end/network/binaryResourceView.css",
"front_end/network/blockedURLsPane.css",
......@@ -1416,6 +1426,7 @@ generated_non_autostart_non_remote_modules = [
"$resources_out_dir/js_profiler/js_profiler_module.js",
"$resources_out_dir/layer_viewer/layer_viewer_module.js",
"$resources_out_dir/layers/layers_module.js",
"$resources_out_dir/media/media_module.js",
"$resources_out_dir/network/network_module.js",
"$resources_out_dir/node_debugger/node_debugger_module.js",
"$resources_out_dir/object_ui/object_ui_module.js",
......
{
"securityIcons.svg": "27676f7c1f1542659c7c49a8052259dc",
"accelerometer-back.svg": "342973eb940ef43b409b28c2c6b0d520",
"largeIcons.svg": "faf26930e93e7525a3cbcc595527662c",
"breakpoint.svg": "69cd92d807259c022791112809b97799",
"breakpointConditional.svg": "4cf90210b2af2ed84db2f60b07bcde28",
"checkboxCheckmark.svg": "f039bf85cee42ad5c30ca3bfdce7912a",
"errorWave.svg": "e183fa242a22ed4784a92f6becbc2c45",
"smallIcons.svg": "19940dda6f171380bfd7d04d0061b44c",
"smallIcons.svg": "ed10eae550f101ce8d1cc9e26dd8a33d",
"mediumIcons.svg": "9cb32f670ba43a7ab424eab281043e6b",
"accelerometer-back.svg": "342973eb940ef43b409b28c2c6b0d520",
"breakpoint.svg": "69cd92d807259c022791112809b97799",
"treeoutlineTriangles.svg": "2d26ab85d919f83d5021f2f385dffd0b",
"chevrons.svg": "79b4b527771e30b6388ce664077b3409",
"audits_logo.svg": "3a4893bd2ef5bb233e924f15e51af69a",
......
......@@ -8,11 +8,11 @@
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="110"
width="130"
height="110"
id="svg4185"
version="1.1"
inkscape:version="0.92.2pre0 (973e216, 2017-07-25)"
inkscape:version="0.92.4 (5da689c313, 2019-01-14)"
sodipodi:docname="smallIcons.svg"
inkscape:export-filename="/Users/pfeldman/code/chromium/src/third_party/WebKit/Source/devtools/front_end/Images/smallIcons_2x.png"
inkscape:export-xdpi="180"
......@@ -78,16 +78,16 @@
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1272"
inkscape:window-height="1006"
inkscape:window-width="1432"
inkscape:window-height="1419"
id="namedview4455"
showgrid="true"
inkscape:zoom="7.4167646"
inkscape:cx="72.154899"
inkscape:cy="32.871453"
inkscape:window-x="768"
inkscape:window-y="0"
inkscape:window-maximized="0"
inkscape:zoom="5.2444445"
inkscape:cx="94.607116"
inkscape:cy="82.226632"
inkscape:window-x="4002"
inkscape:window-y="1063"
inkscape:window-maximized="1"
inkscape:current-layer="svg4185">
<inkscape:grid
type="xygrid"
......@@ -1046,4 +1046,61 @@
inkscape:connector-curvature="0"
d="M 88.06,3.059999 85,6.113332 81.94,3.059999 l -0.94,0.94 4,4 4,-4 z"
id="path3974" />
<path
style="stroke-width:0.02383474"
d=""
id="path5923"
inkscape:connector-curvature="0" />
<path
style="stroke-width:0.02383474"
d=""
id="path5925"
inkscape:connector-curvature="0" />
<path
style="stroke-width:0.02383474"
d=""
id="path5927"
inkscape:connector-curvature="0" />
<rect
id="rect5943"
width="2"
height="6"
x="102"
y="1.9999998"
style="stroke-width:1.13202608" />
<a
id="a6015">
<rect
style="stroke-width:1.13202608"
y="2"
x="106"
height="6"
width="2"
id="rect5943-5" />
</a>
<text
xml:space="preserve"
style="font-style:normal;font-weight:normal;line-height:0%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#cccccc;fill-opacity:1;stroke:none"
x="122.66023"
y="117.2188"
id="text5191-77-3-3"><tspan
sodipodi:role="line"
id="tspan5193-6-6-5"
x="122.66023"
y="117.2188"
style="font-size:8px;line-height:1.25;font-family:sans-serif">g</tspan></text>
<path
inkscape:transform-center-x="0.029808665"
inkscape:transform-center-y="-0.33844643"
d="m 128,5 -6,3 V 2 Z"
id="path6489"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cccc" />
<path
inkscape:transform-center-x="0.34573841"
inkscape:transform-center-y="-0.78316773"
d="M 128,26.5 126.5,28 125,26.5 123.5,28 122,26.5 123.5,25 122,23.5 l 1.5,-1.5 1.5,1.5 1.5,-1.5 1.5,1.5 -1.5,1.5 z"
id="path6495"
inkscape:connector-curvature="0"
sodipodi:nodetypes="ccccccccccccc" />
</svg>
......@@ -25,7 +25,8 @@
{ "name": "security" },
{ "name": "timeline" },
{ "name": "timeline_model" },
{ "name": "web_audio" }
{ "name": "web_audio" },
{ "name": "media" }
],
"extends": "shell",
"has_html": true
......
......@@ -189,9 +189,6 @@
<message name="IDS_DEVTOOLS_9a0364b9e99bb480dd25e1f0284c8555" desc="Text in Metrics Sidebar Pane of the Elements panel">
content
</message>
<message name="IDS_DEVTOOLS_9fc2d28c05ed9eb1d75ba4465abf15a9" desc="Title of the 'Properties' tool in the sidebar of the elements tool">
Properties
</message>
<message name="IDS_DEVTOOLS_a05fb8660778069f6d4a5c5b40a6dbc9" desc="Title of a setting under the Elements category in Settings">
Show user agent shadow DOM
</message>
......
......@@ -41,6 +41,7 @@
<part file="../layer_viewer/layer_viewer_strings.grdp" />
<part file="../layers/layers_strings.grdp" />
<part file="../main/main_strings.grdp" />
<part file="../media/media_strings.grdp" />
<part file="../mobile_throttling/mobile_throttling_strings.grdp" />
<part file="../network/network_strings.grdp" />
<part file="../node_debugger/node_debugger_strings.grdp" />
......
......@@ -382,6 +382,9 @@
<message name="IDS_DEVTOOLS_9f29da220ed82809ec5dd70af4e52904" desc="Text for the total time of something">
Total Time
</message>
<message name="IDS_DEVTOOLS_9fc2d28c05ed9eb1d75ba4465abf15a9" desc="Title of the 'Properties' tool in the sidebar of the elements tool">
Properties
</message>
<message name="IDS_DEVTOOLS_a02c83a7dbd96295beaefb72c2bee2de" desc="Text that refers to the main target">
Main
</message>
......
......@@ -129,6 +129,7 @@ Main.Main = class {
Root.Runtime.experiments.register('emptySourceMapAutoStepping', 'Empty sourcemap auto-stepping');
Root.Runtime.experiments.register('inputEventsOnTimelineOverview', 'Input events on Timeline overview', true);
Root.Runtime.experiments.register('liveHeapProfile', 'Live heap profile', true);
Root.Runtime.experiments.register('mediaInspector', 'Media Element Inspection');
Root.Runtime.experiments.register('nativeHeapProfiler', 'Native memory sampling heap profiler', true);
Root.Runtime.experiments.register('protocolMonitor', 'Protocol Monitor');
Root.Runtime.experiments.register(
......
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/**
* @typedef {{
* id: string,
* title: string,
* sortable: boolean,
* weight: (number|undefined),
* sortingFunction: (!function(!Media.EventNode, !Media.EventNode):number|undefined),
* }}
*/
Media.EventDisplayColumnConfig;
/**
* @typedef {{
* name: string,
* value: string,
* timestamp: (number|string|undefined)
* }}
*/
Media.Event;
/**
* @unrestricted
*/
Media.EventNode = class extends DataGrid.SortableDataGridNode {
/**
* @param {!Media.Event} event
*/
constructor(event) {
super(event, false);
}
/**
* @override
* @param {string} columnId
* @return {!Element}
*/
createCell(columnId) {
const cell = this.createTD(columnId);
const cellData = /** @type string */ (this.data[columnId]);
cell.createTextChild(cellData);
return cell;
}
/**
* @override
* @return {number}
*/
nodeSelfHeight() {
return 20;
}
};
/**
* @unrestricted
*/
Media.EventDisplayTable = class extends UI.VBox {
/**
* @param {!Array.<!Media.EventDisplayColumnConfig>} headerDescriptors
* @param {?string=} uniqueColumn
* @param {?string=} defaultSortingColumnId
*/
constructor(headerDescriptors, uniqueColumn, defaultSortingColumnId) {
super();
// Set up element styles.
this.registerRequiredCSS('media/eventDisplayTable.css');
this.contentElement.classList.add('event-display-table-contents-table-container');
this._uniqueColumnEntryKey = uniqueColumn;
this._uniqueColumnMap = new Map();
this._dataGrid = this._createDataGrid(headerDescriptors, defaultSortingColumnId);
this._dataGrid.setStriped(true);
this._dataGrid.asWidget().show(this.contentElement);
}
/**
* @param {!Array.<!Media.EventDisplayColumnConfig>} headers
* @param {?string|undefined} default_sort
* @return !DataGrid.SortableDataGrid
*/
_createDataGrid(headers, default_sort) {
const gridColumnDescs = [];
const sortFunctionMap = new Map();
for (const headerDesc of headers) {
gridColumnDescs.push(Media.EventDisplayTable._convertToGridDescriptor(headerDesc));
if (headerDesc.sortable) {
sortFunctionMap.set(headerDesc.id, headerDesc.sortingFunction);
if (!default_sort) {
default_sort = headerDesc.id;
}
}
}
const datagrid = new DataGrid.SortableDataGrid(gridColumnDescs);
if (default_sort) {
datagrid.sortNodes(sortFunctionMap.get(default_sort), !datagrid.isSortOrderAscending());
function sortGrid() {
const comparator = sortFunctionMap.get(datagrid.sortColumnId());
datagrid.sortNodes(comparator, !datagrid.isSortOrderAscending());
}
datagrid.addEventListener(DataGrid.DataGrid.Events.SortingChanged, sortGrid);
}
datagrid.asWidget().contentElement.classList.add('no-border-top-datagrid');
return datagrid;
}
/**
* @param {!Array.<!Media.Event>} events
*/
addEvents(events) {
for (const event of events) {
this.addEvent(event);
}
}
/**
* @param {!Media.Event} event
*/
addEvent(event) {
if (this._uniqueColumnEntryKey) {
const eventValue = event[this._uniqueColumnEntryKey];
if (this._uniqueColumnMap.has(eventValue)) {
this._uniqueColumnMap.get(eventValue).data = event;
return;
}
}
const node = new Media.EventNode(event);
this._dataGrid.rootNode().insertChildOrdered(node);
if (this._uniqueColumnEntryKey) {
this._uniqueColumnMap.set(event[this._uniqueColumnEntryKey], node);
}
}
/**
* @param {!Media.EventDisplayColumnConfig} columnConfig
* @return {!DataGrid.DataGrid.ColumnDescriptor}
*/
static _convertToGridDescriptor(columnConfig) {
return /** @type {!DataGrid.DataGrid.ColumnDescriptor} */ ({
id: columnConfig.id,
title: columnConfig.title,
sortable: columnConfig.sortable,
weight: columnConfig.weight || 0,
sort: DataGrid.DataGrid.Order.Ascending
});
}
};
\ No newline at end of file
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/**
* @implements {SDK.SDKModelObserver<!Media.MediaModel>}
*/
Media.MainView = class extends UI.PanelWithSidebar {
constructor() {
super('Media');
this.registerRequiredCSS('media/mediaView.css');
// Map<Media.PlayerDetailView>
this._detailPanels = new Map();
// Map<string>
this._deletedPlayers = new Set();
this._sidebar = new Media.PlayerListView(this);
this._sidebar.show(this.panelSidebarElement());
SDK.targetManager.observeModels(Media.MediaModel, this);
}
/**
* @param {string} playerID
* @param {!Array.<!Media.Event>} changes
* @param {!Media.MediaModel.MediaChangeTypeKeys} changeType
*/
renderChanges(playerID, changes, changeType) {
if (this._deletedPlayers.has(playerID)) {
return;
}
if (!this._detailPanels.has(playerID)) {
return;
}
this._sidebar.renderChanges(playerID, changes, changeType);
this._detailPanels.get(playerID).renderChanges(playerID, changes, changeType);
}
/**
* @param {string} playerID
*/
renderMainPanel(playerID) {
if (!this._detailPanels.has(playerID)) {
return;
}
this.splitWidget().mainWidget().detachChildWidgets();
this._detailPanels.get(playerID).show(this.mainElement());
}
/**
* @param {string} playerID
*/
_onPlayerCreated(playerID) {
this._sidebar.addMediaElementItem(playerID);
this._detailPanels.set(playerID, new Media.PlayerDetailView());
}
/**
* @override
*/
wasShown() {
super.wasShown();
for (const model of SDK.targetManager.models(Media.MediaModel)) {
this._addEventListeners(model);
}
}
/**
* @override
*/
willHide() {
for (const model of SDK.targetManager.models(Media.MediaModel)) {
this._removeEventListeners(model);
}
}
/**
* @override
* @param {!Media.MediaModel} mediaModel
*/
modelAdded(mediaModel) {
if (this.isShowing()) {
this._addEventListeners(mediaModel);
}
}
/**
* @override
* @param {!Media.MediaModel} mediaModel
*/
modelRemoved(mediaModel) {
this._removeEventListeners(mediaModel);
}
/**
* @param {!Media.MediaModel} mediaModel
*/
_addEventListeners(mediaModel) {
mediaModel.ensureEnabled();
mediaModel.addEventListener(Media.MediaModel.Events.PlayerPropertiesChanged, this._propertiesChanged, this);
mediaModel.addEventListener(Media.MediaModel.Events.PlayerEventsAdded, this._eventsAdded, this);
mediaModel.addEventListener(Media.MediaModel.Events.PlayersCreated, this._playersCreated, this);
}
/**
* @param {!Media.MediaModel} mediaModel
*/
_removeEventListeners(mediaModel) {
mediaModel.removeEventListener(Media.MediaModel.Events.PlayerPropertiesChanged, this._propertiesChanged, this);
mediaModel.removeEventListener(Media.MediaModel.Events.PlayerEventsAdded, this._eventsAdded, this);
mediaModel.removeEventListener(Media.MediaModel.Events.PlayersCreated, this._playersCreated, this);
}
/**
* @param {!Common.Event} event
*/
_propertiesChanged(event) {
this.renderChanges(event.data.playerId, event.data.properties, Media.MediaModel.MediaChangeTypeKeys.Property);
}
/**
* @param {!Common.Event} event
*/
_eventsAdded(event) {
this.renderChanges(event.data.playerId, event.data.events, Media.MediaModel.MediaChangeTypeKeys.Event);
}
/**
* @param {!Common.Event} event
*/
_playersCreated(event) {
const playerlist = /** @type {!Iterable.<string>} */ (event.data);
for (const playerID of playerlist) {
this._onPlayerCreated(playerID);
}
}
};
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/**
* @implements {Protocol.MediaDispatcher}
*/
Media.MediaModel = class extends SDK.SDKModel {
/**
* @param {!SDK.Target} target
*/
constructor(target) {
super(target);
this._enabled = false;
this._agent = target.mediaAgent();
target.registerMediaDispatcher(this);
}
/**
* @override
* @return {!Promise}
*/
resumeModel() {
if (!this._enabled) {
return Promise.resolve();
}
return this._agent.enable();
}
ensureEnabled() {
this._agent.enable();
this._enabled = true;
}
/**
* @param {!Protocol.Media.PlayerId} playerId
* @param {!Array.<!Protocol.Media.PlayerProperty>} properties
* @override
*/
playerPropertiesChanged(playerId, properties) {
this.dispatchEventToListeners(
Media.MediaModel.Events.PlayerPropertiesChanged, {playerId: playerId, properties: properties});
}
/**
* @param {!Protocol.Media.PlayerId} playerId
* @param {!Array.<!Protocol.Media.PlayerEvent>} events
* @override
*/
playerEventsAdded(playerId, events) {
this.dispatchEventToListeners(Media.MediaModel.Events.PlayerEventsAdded, {playerId: playerId, events: events});
}
/**
* @param {!Array.<!Protocol.Media.PlayerId>} playerIds
* @override
*/
playersCreated(playerIds) {
this.dispatchEventToListeners(Media.MediaModel.Events.PlayersCreated, playerIds);
}
};
SDK.SDKModel.register(Media.MediaModel, SDK.Target.Capability.DOM, false);
/** @enum {symbol} */
Media.MediaModel.Events = {
PlayerPropertiesChanged: Symbol('PlayerPropertiesChanged'),
PlayerEventsAdded: Symbol('PlayerEventsAdded'),
PlayersCreated: Symbol('PlayersCreated')
};
/** @enum {string} */
Media.MediaModel.MediaChangeTypeKeys = {
Event: 'Events',
Property: 'Properties'
};
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/**
* @unrestricted
*/
Media.MediaPlayerPropertiesRenderer = class extends Media.EventDisplayTable {
constructor() {
super(
[
{
id: 'name',
title: 'Property Name',
sortable: true,
weight: 2,
sortingFunction: DataGrid.SortableDataGrid.StringComparator.bind(null, 'name')
},
{id: 'value', title: 'Value', sortable: false, weight: 7}
],
'name');
}
/**
* @param {string} playerID
* @param {!Array.<!Media.Event>} changes
* @param {!Media.MediaModel.MediaChangeTypeKeys} change_type
*/
renderChanges(playerID, changes, change_type) {
this.addEvents(changes);
}
};
/**
* @unrestricted
*/
Media.MediaPlayerEventTableRenderer = class extends Media.EventDisplayTable {
constructor() {
super([
{
id: 'timestamp',
title: 'Timestamp',
weight: 1,
sortable: true,
sortingFunction: DataGrid.SortableDataGrid.NumericComparator.bind(null, 'timestamp')
},
{id: 'name', title: 'Event Name', weight: 2, sortable: false},
{id: 'value', title: 'Value', weight: 7, sortable: false}
]);
this._firstEventTime = 0;
}
/**
* @param {string} playerID
* @param {!Array.<!Media.Event>} changes
* @param {!Media.MediaModel.MediaChangeTypeKeys} change_type
*/
renderChanges(playerID, changes, change_type) {
if (this._firstEventTime === 0 && changes.length > 0) {
this._firstEventTime = changes[0].timestamp;
}
this.addEvents(changes.map(this._subtractFirstEventTime.bind(this, this._firstEventTime)));
}
/**
* @param {number|string|undefined} first_event_time
* @param {!Media.Event} event
*/
_subtractFirstEventTime(first_event_time, event) {
event.timestamp = (event.timestamp - first_event_time).toFixed(3);
return event;
}
};
\ No newline at end of file
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/**
* @unrestricted
*/
Media.PlayerDetailView = class extends UI.TabbedPane {
constructor() {
super();
const propertyTable = new Media.MediaPlayerPropertiesRenderer();
const eventTable = new Media.MediaPlayerEventTableRenderer();
// maps handler type to a list of panels that support rendering changes.
this._panels = new Map([
[Media.MediaModel.MediaChangeTypeKeys.Property, [propertyTable]],
[Media.MediaModel.MediaChangeTypeKeys.Event, [eventTable]]
]);
this.appendTab(
Media.PlayerDetailView.Tabs.Properties, Common.UIString('Properties'), propertyTable,
Common.UIString('Player properties'));
this.appendTab(
Media.PlayerDetailView.Tabs.Events, Common.UIString('Events'), eventTable, Common.UIString('Player events'));
}
/**
* @param {string} playerID
* @param {!Array.<!Media.Event>} changes
* @param {!Media.MediaModel.MediaChangeTypeKeys} changeType
*/
renderChanges(playerID, changes, changeType) {
for (const panel of this._panels.get(changeType)) {
panel.renderChanges(playerID, changes, changeType);
}
}
};
/**
* @enum {string}
*/
Media.PlayerDetailView.Tabs = {
Events: 'events',
Properties: 'properties',
};
\ No newline at end of file
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/**
* @typedef {{playerTitle: string, playerID: string, exists: boolean, playing: boolean, titleEdited: boolean}}
*/
Media.PlayerStatus;
/**
* @typedef {{playerStatus: !Media.PlayerStatus, playerTitleElement: ?HTMLElement}}
*/
Media.PlayerStatusMapElement;
Media.PlayerEntryTreeElement = class extends UI.TreeElement {
/**
* @param {!Media.PlayerStatus} playerStatus
* @param {!Media.MainView} displayContainer
*/
constructor(playerStatus, displayContainer) {
super(playerStatus.playerTitle, false);
this.titleFromUrl = true;
this._playerStatus = playerStatus;
this._displayContainer = displayContainer;
this.setLeadingIcons([UI.Icon.create('smallicon-videoplayer-playing', 'media-player')]);
}
/**
* @override
* @return {boolean}
*/
onselect(selectedByUser) {
this._displayContainer.renderMainPanel(this._playerStatus.playerID);
return true;
}
};
Media.PlayerListView = class extends UI.VBox {
/**
* @param {!Media.MainView} mainContainer
*/
constructor(mainContainer) {
super(true);
this._playerStatuses = new Map();
// Container where new panels can be added based on clicks.
this._mainContainer = mainContainer;
// The parent tree for storing sections
this._sidebarTree = new UI.TreeOutlineInShadow();
this.contentElement.appendChild(this._sidebarTree.element);
this._sidebarTree.registerRequiredCSS('media/playerListView.css');
// Audio capture / output devices.
this._audioDevices = this._addListSection(Common.UIString('Audio I/O'));
// Video capture devices.
this._videoDevices = this._addListSection(Common.UIString('Video Capture Devices'));
// Players active in this tab.
this._playerList = this._addListSection(Common.UIString('Players'));
}
/**
* @param {string} title
* @return {!UI.TreeElement}
*/
_addListSection(title) {
const treeElement = new UI.TreeElement(title, true);
treeElement.listItemElement.classList.add('storage-group-list-item');
treeElement.setCollapsible(false);
treeElement.selectable = false;
this._sidebarTree.appendChild(treeElement);
return treeElement;
}
/**
* @param {string} playerID
*/
addMediaElementItem(playerID) {
const playerStatus = {playerTitle: playerID, playerID: playerID, exists: true, playing: false, titleEdited: false};
const playerElement = new Media.PlayerEntryTreeElement(playerStatus, this._mainContainer);
this._playerStatuses.set(playerID, playerElement);
this._playerList.appendChild(playerElement);
}
/**
* @param {string} playerID
* @param {string} newTitle
* @param {boolean} isTitleExtractedFromUrl
*/
setMediaElementPlayerTitle(playerID, newTitle, isTitleExtractedFromUrl) {
if (this._playerStatuses.has(playerID)) {
const sidebarEntry = this._playerStatuses.get(playerID);
if (!isTitleExtractedFromUrl || sidebarEntry.titleFromUrl) {
sidebarEntry.title = newTitle;
sidebarEntry.titleFromUrl = isTitleExtractedFromUrl;
}
}
}
/**
* @param {string} playerID
* @param {string} iconName
*/
setMediaElementPlayerIcon(playerID, iconName) {
if (this._playerStatuses.has(playerID)) {
const sidebarEntry = this._playerStatuses.get(playerID);
sidebarEntry.setLeadingIcons([UI.Icon.create('smallicon-videoplayer-' + iconName, 'media-player')]);
}
}
/**
* @param {string} playerID
* @param {!Array.<!Media.Event>} changes
* @param {string} changeType
*/
renderChanges(playerID, changes, changeType) {
// We only want to try setting the title from the 'frame_title' and 'frame_url' properties.
if (changeType === Media.MediaModel.MediaChangeTypeKeys.Property) {
for (const change of changes) {
// Sometimes frame_title can be an empty string.
if (change.name === 'frame_title' && change.value) {
this.setMediaElementPlayerTitle(playerID, change.value, false);
}
if (change.name === 'frame_url') {
const url_path_component = change.value.substring(change.value.lastIndexOf('/') + 1);
this.setMediaElementPlayerTitle(playerID, url_path_component, true);
}
}
}
if (changeType === Media.MediaModel.MediaChangeTypeKeys.Event) {
let change_to = null;
for (const change of changes) {
if (change.name === 'Event') {
if (change.value === 'PLAY') {
change_to = 'playing';
} else if (change.value === 'PAUSE') {
change_to = 'paused';
} else if (change.value === 'WEBMEDIAPLAYER_DESTROYED') {
change_to = 'destroyed';
}
}
}
if (change_to) {
this.setMediaElementPlayerIcon(playerID, change_to);
}
}
}
};
\ No newline at end of file
/*
* Copyright 2019 The Chromium Authors. All rights reserved.
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
.no-border-top-datagrid>.data-grid {
/* make sure there is no top border, it ruins the menu view */
border-top: 0px;
}
.event-display-table-contents-table-container>.widget>.data-grid {
height: 100%;
}
\ No newline at end of file
/*
* Copyright (c) 2019 The Chromium Authors. All rights reserved.
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
.playerlist-sidebar {
display: flex;
flex-direction: column;
align-items: stretch;
}
.playerlist-sidebar-header {
font-size: 22px;
padding: 8px 20px;
border-bottom:1px solid var(--divider-color);
}
.playerlist-entry-title>pre {
margin: 0px;
}
.playerlist-entry-title {
float: left;
}
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<grit-part>
<message name="IDS_DEVTOOLS_421b47ffd946ca083b65cd668c6b17e6" desc="Video player">
video
</message>
<message name="IDS_DEVTOOLS_62933a2951ef01f4eafd9bdf4d3cd2f0" desc="Media player">
media
</message>
<message name="IDS_DEVTOOLS_83341e272df7a4f2982e7b96b7f26a4a" desc="Button text for viewing properties.">
Player properties
</message>
<message name="IDS_DEVTOOLS_87f9f735a1d36793ceaecd4e47124b63" desc="Button text for viewing events.">
Events
</message>
<message name="IDS_DEVTOOLS_cf6523ecab64a91c7a9b3d83b58f9e61" desc="Hover text for the Events button.">
Player events
</message>
<message name="IDS_DEVTOOLS_93ec972c68d5caca43370a9746a08cea" desc="Side-panel entry title text for the players section.">
Players
</message>
<message name="IDS_DEVTOOLS_a20467c4e87b5469cbb1a6c31775393c" desc="Side-panel entry title text for the audio devices section.">
Audio I/O
</message>
<message name="IDS_DEVTOOLS_d9f6db9c0f0579391eccedbac66aef9b" desc="Side-panel entry title text for the video capture devices section.">
Video Capture Devices
</message>
</grit-part>
\ No newline at end of file
{
"extensions": [
{
"type": "view",
"location": "panel",
"id": "media",
"title": "Media",
"persistence": "closeable",
"order": 100,
"className": "Media.MainView",
"tags": "media, video",
"experiment": "mediaInspector"
}
],
"dependencies": [
"components",
"sdk",
"ui",
"data_grid"
],
"scripts": [
"EventDisplayTable.js",
"MainView.js",
"MediaTable.js",
"PlayerDetailView.js",
"PlayerListView.js",
"MediaModel.js"
],
"resources": [
"eventDisplayTable.css",
"mediaView.css",
"playerListView.css"
]
}
\ No newline at end of file
/*
* Copyright 2019 The Chromium Authors. All rights reserved.
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
.tree-outline {
padding-left: 0;
color: rgb(90, 90, 90);
}
li.storage-group-list-item {
padding: 10px 8px 6px 8px;
}
li.storage-group-list-item:not(:first-child) {
border-top: 1px solid rgb(230, 230, 230);
}
li.storage-group-list-item::before {
display: none;
}
\ No newline at end of file
......@@ -139,6 +139,9 @@ export const Descriptors = {
'smallicon-clear-info': {position: 'f2', spritesheet: 'smallicons'},
'smallicon-clear-error': {position: 'f3', spritesheet: 'smallicons'},
'smallicon-account-circle': {position: 'f4', spritesheet: 'smallicons'},
'smallicon-videoplayer-paused': {position: 'f6', spritesheet: 'smallicons', isMask: true},
'smallicon-videoplayer-playing': {position: 'g6', spritesheet: 'smallicons', isMask: true},
'smallicon-videoplayer-destroyed': {position: 'g5', spritesheet: 'smallicons', isMask: true},
'mediumicon-clear-storage': {position: 'a4', spritesheet: 'mediumicons', isMask: true},
'mediumicon-cookie': {position: 'b4', spritesheet: 'mediumicons', isMask: true},
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment