Redesign chrome://media-internals with a modern look and feel
The new design features:
- A two-pane layout for players & information
- A dark theme when chrome://settings/appearance is set to dark
- Redesigned property and log views.
I gemini'd most of this, and then tweaked a few CSS bits.
Mobile screenshots:
https://files.tedm.io/media-internals/1.jpg
https://files.tedm.io/media-internals/2.jpg
https://files.tedm.io/media-internals/3.jpg
https://files.tedm.io/media-internals/4.jpg
Desktop screenshots:
https://files.tedm.io/media-internals/5.png
https://files.tedm.io/media-internals/6.png
https://files.tedm.io/media-internals/7.png
I've uploaded a video of the new UX: https://youtu.be/Xl7R085gtPU
Change-Id: Iedd7f1ac62d6922db50ad3f3ec11ee912312fe60
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/6861607
Reviewed-by: Dale Curtis <dalecurtis@chromium.org>
Commit-Queue: Ted (Chromium) Meyer <tmathmeyer@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1508005}
diff --git a/content/browser/resources/media/client_renderer.js b/content/browser/resources/media/client_renderer.js
index 299d620..837057c 100644
--- a/content/browser/resources/media/client_renderer.js
+++ b/content/browser/resources/media/client_renderer.js
@@ -116,19 +116,9 @@
this.filterText.onkeyup = this.onTextChange_.bind(this);
}
- const copyPropertiesButtons =
- document.getElementsByClassName('copy-properties-button');
- if (copyPropertiesButtons) {
- for (let i = 0; i < copyPropertiesButtons.length; i++) {
- copyPropertiesButtons[i].onclick = this.copyProperties_.bind(this);
- }
- }
-
- const copyLogButtons = document.getElementsByClassName('copy-log-button');
- if (copyLogButtons) {
- for (let i = 0; i < copyLogButtons.length; i++) {
- copyLogButtons[i].onclick = this.copyLog_.bind(this);
- }
+ this.copyLogButton = $('copy-log-button');
+ if (this.copyLogButton) {
+ this.copyLogButton.onclick = this.copyLog_.bind(this);
}
this.saveLogButton = $('save-log-button');
@@ -136,11 +126,30 @@
this.saveLogButton.onclick = this.saveLog_.bind(this);
}
+ this.closePlayerViewButton = $('close-player-view-button');
+ if (this.closePlayerViewButton) {
+ this.closePlayerViewButton.onclick = () => {
+ $('main-container').classList.remove('mobile-player-view-active');
+ document.body.classList.add(ClientRendererCss.NO_PLAYERS_SELECTED);
+ if (this.selectedPlayer) {
+ const element = this.playerListElement.querySelector(
+ `.tree-item[data-id="${this.selectedPlayer.id}"]`);
+ if (element) {
+ element.classList.remove('selected');
+ }
+ this.selectedPlayer = null;
+ const titleElement = $('player-details-title');
+ if (titleElement) {
+ titleElement.textContent = 'Player Properties';
+ titleElement.title = '';
+ }
+ }
+ };
+ }
+
this.hiddenKeys = ['component_id', 'component_type', 'owner_id'];
- // Tell CSS to hide certain content prior to making selections.
document.body.classList.add(ClientRendererCss.NO_PLAYERS_SELECTED);
- document.body.classList.add(ClientRendererCss.NO_COMPONENTS_SELECTED);
}
/**
@@ -229,6 +238,12 @@
removeChildren(this.logTable);
removeChildren(this.graphElement);
document.body.classList.add(ClientRendererCss.NO_PLAYERS_SELECTED);
+ this.selectedPlayer = null;
+ const titleElement = $('player-details-title');
+ if (titleElement) {
+ titleElement.textContent = 'Player Properties';
+ titleElement.title = '';
+ }
}
this.redrawPlayerList_(players);
}
@@ -365,10 +380,6 @@
}
redrawAudioComponentList_(componentType, components) {
- // Group name imposes rule that only one component can be selected
- // (and have its properties displayed) at a time.
- const buttonGroupName = 'audio-components';
-
const listElement = this.getListElementForAudioComponent_(componentType);
if (!listElement) {
console.error(
@@ -378,15 +389,29 @@
const fragment = document.createDocumentFragment();
for (const id in components) {
- const li = document.createElement('li');
- const buttonCb = this.selectAudioComponent_.bind(
- this, componentType, id, components[id]);
- const friendlyName = this.getAudioComponentName_(componentType, id);
- const label = document.createElement('label');
- label.appendChild(document.createTextNode(friendlyName));
- li.appendChild(
- createSelectableButton(id, buttonGroupName, label, buttonCb));
- fragment.appendChild(li);
+ const component = components[id];
+
+ const treeItem = document.createElement('div');
+ treeItem.classList.add('tree-item');
+ treeItem.dataset.id = id;
+ treeItem.classList.add(ClientRendererCss.ACTIVE_PLAYER);
+
+ const treeItemHeader = document.createElement('div');
+ treeItemHeader.classList.add('tree-item-header');
+ treeItemHeader.textContent =
+ this.getAudioComponentName_(componentType, id);
+ treeItem.appendChild(treeItemHeader);
+
+ const children = document.createElement('div');
+ children.classList.add('tree-item-children');
+ treeItem.appendChild(children);
+
+ treeItemHeader.addEventListener('click', (e) => {
+ treeItem.classList.toggle('expanded');
+ this.selectAudioComponent_(componentType, id, component);
+ });
+
+ fragment.appendChild(treeItem);
}
removeChildren(listElement);
listElement.appendChild(fragment);
@@ -395,12 +420,32 @@
this.selectedAudioComponentType === componentType &&
this.selectedAudioComponentId in components) {
// Re-select the selected component since the button was just recreated.
- selectSelectableButton(this.selectedAudioComponentId);
+ const element = listElement.querySelector(
+ `.tree-item[data-id="${this.selectedAudioComponentId}"]`);
+ if (element) {
+ element.classList.add('selected');
+ }
}
}
selectAudioComponent_(componentType, componentId, componentData) {
- document.body.classList.remove(ClientRendererCss.NO_COMPONENTS_SELECTED);
+ const audioWrapper = $('audio-component-list-wrapper');
+ if (audioWrapper) {
+ const previouslySelected =
+ audioWrapper.querySelector('.tree-item.selected');
+ if (previouslySelected) {
+ previouslySelected.classList.remove('selected');
+ }
+ }
+
+ const listElement = this.getListElementForAudioComponent_(componentType);
+ if (listElement) {
+ const element =
+ listElement.querySelector(`.tree-item[data-id="${componentId}"]`);
+ if (element) {
+ element.classList.add('selected');
+ }
+ }
this.selectedAudioComponentType = componentType;
this.selectedAudioComponentId = componentId;
@@ -415,83 +460,96 @@
redrawPlayerList_(players) {
this.players = players;
- // Group name imposes rule that only one component can be selected
- // (and have its properties displayed) at a time.
- const buttonGroupName = 'player-buttons';
-
- let hasPlayers = false;
const fragment = document.createDocumentFragment();
for (const id in players) {
- hasPlayers = true;
const player = players[id];
const p = player.properties;
- const label = document.createElement('label');
- const nameText = p.url || 'Player ' + player.id;
- const nameNode = document.createElement('div');
- nameNode.appendChild(document.createTextNode(nameText));
- nameNode.className = 'player-name';
- label.appendChild(nameNode);
+ const treeItem = document.createElement('div');
+ treeItem.classList.add('tree-item');
+ if (player.playerState === 'errored') {
+ treeItem.classList.add(ClientRendererCss.ERRORED_PLAYER);
+ } else if (player.playerState === 'ended') {
+ treeItem.classList.add(ClientRendererCss.ENDED_PLAYER);
+ } else {
+ treeItem.classList.add(ClientRendererCss.ACTIVE_PLAYER);
+ }
+ treeItem.dataset.id = id;
- const frame = [];
- if (p.frame_title) {
- frame.push(p.frame_title);
+ const treeItemHeader = document.createElement('div');
+ treeItemHeader.classList.add('tree-item-header');
+ treeItemHeader.classList.add('selectable-button');
+
+ const playerName = document.createElement('div');
+ playerName.classList.add('player-name');
+ const url = p.url || 'Player ' + player.id;
+ if (url.length > 64) {
+ playerName.textContent = url.substring(0, 61) + '...';
+ } else {
+ playerName.textContent = url;
}
- if (p.frame_url) {
- frame.push(p.frame_url);
- }
- const frameText = frame.join(' - ');
- if (frameText) {
- const frameNode = document.createElement('div');
- frameNode.className = 'player-frame';
- frameNode.appendChild(document.createTextNode(frameText));
- label.appendChild(frameNode);
+ playerName.title = url;
+ treeItemHeader.appendChild(playerName);
+
+ let lastEvent = '';
+ for (let i = player.allEvents.length - 1; i >= 0; i--) {
+ if (player.allEvents[i].key === 'event') {
+ lastEvent = player.allEvents[i].value;
+ break;
+ }
}
- const desc = [];
- if (p.width && p.height) {
- desc.push(p.width + 'x' + p.height);
+ if (lastEvent) {
+ const playerFrame = document.createElement('div');
+ playerFrame.classList.add('player-frame');
+ playerFrame.textContent = lastEvent;
+ treeItemHeader.appendChild(playerFrame);
}
- if (p.video_codec_name) {
- desc.push(p.video_codec_name);
- }
- if (p.video_codec_name && p.audio_codec_name) {
- desc.push('+');
- }
- if (p.audio_codec_name) {
- desc.push(p.audio_codec_name);
- }
- if (p.event) {
- desc.push('(' + p.event + ')');
- }
- const descText = desc.join(' ');
- if (descText) {
- const descNode = document.createElement('div');
- descNode.className = 'player-desc';
- descNode.appendChild(document.createTextNode(descText));
- label.appendChild(descNode);
- }
+ treeItem.appendChild(treeItemHeader);
- const li = document.createElement('li');
- const buttonCb = this.selectPlayer_.bind(this, player);
- li.appendChild(createSelectableButton(
- id, buttonGroupName, label, buttonCb, player.playerState));
- fragment.appendChild(li);
+ const children = document.createElement('div');
+ children.classList.add('tree-item-children');
+ treeItem.appendChild(children);
+
+ treeItemHeader.addEventListener('click', (e) => {
+ treeItem.classList.toggle('expanded');
+ this.selectPlayer_(player);
+ });
+
+ fragment.appendChild(treeItem);
}
removeChildren(this.playerListElement);
this.playerListElement.appendChild(fragment);
if (this.selectedPlayer && this.selectedPlayer.id in players) {
// Re-select the selected player since the button was just recreated.
- selectSelectableButton(this.selectedPlayer.id);
+ const element = this.playerListElement.querySelector(
+ `.tree-item[data-id="${this.selectedPlayer.id}"]`);
+ if (element) {
+ element.classList.add('selected');
+ }
}
-
- this.saveLogButton.style.display = hasPlayers ? 'inline-block' : 'none';
}
selectPlayer_(player) {
+ if (window.innerWidth <= 768) {
+ $('main-container').classList.add('mobile-player-view-active');
+ }
+
document.body.classList.remove(ClientRendererCss.NO_PLAYERS_SELECTED);
+ const previouslySelected =
+ this.playerListElement.querySelector('.tree-item.selected');
+ if (previouslySelected) {
+ previouslySelected.classList.remove('selected');
+ }
+
+ const element = this.playerListElement.querySelector(
+ `.tree-item[data-id="${player.id}"]`);
+ if (element) {
+ element.classList.add('selected');
+ }
+
this.selectedPlayer = player;
this.selectedPlayerLogIndex = 0;
this.selectedAudioComponentType = null;
@@ -502,6 +560,13 @@
removeChildren(this.logTable);
removeChildren(this.graphElement);
this.drawLog_();
+
+ const titleElement = $('player-details-title');
+ if (titleElement) {
+ const playerName = player.properties.url || 'Player ' + player.id;
+ titleElement.textContent = playerName;
+ titleElement.title = playerName;
+ }
}
drawProperties_(propertyMap, propertiesTable) {
@@ -519,21 +584,52 @@
const valueCell = row.insertCell(-1);
keyCell.appendChild(document.createTextNode(key));
- valueCell.appendChild(document.createTextNode(JSON.stringify(value)));
+
+ try {
+ if (key === 'kHlsBufferedRanges') {
+ throw new Error('Do Not Render As JSON');
+ }
+ const pre = document.createElement('pre');
+ pre.textContent = JSON.stringify(value, null, 2);
+ valueCell.appendChild(pre);
+ } catch (e) {
+ valueCell.appendChild(document.createTextNode(JSON.stringify(value)));
+ }
}
}
appendEventToLog_(event) {
if (this.filterFunction(event.key)) {
const row = this.logTable.insertRow(-1);
+ row.classList.add('log-entry');
const timestampCell = row.insertCell(-1);
- timestampCell.classList.add('timestamp');
- timestampCell.appendChild(
- document.createTextNode(millisecondsToString(event.time)));
- row.insertCell(-1).appendChild(document.createTextNode(event.key));
- row.insertCell(-1).appendChild(
- document.createTextNode(JSON.stringify(event.value)));
+ timestampCell.classList.add('log-timestamp');
+ timestampCell.textContent = millisecondsToString(event.time);
+
+ const propertyCell = row.insertCell(-1);
+ propertyCell.classList.add('log-property');
+ propertyCell.textContent = event.key;
+
+ const valueCell = row.insertCell(-1);
+ valueCell.classList.add('log-value');
+ try {
+ if (event.key === 'kHlsBufferedRanges') {
+ throw new Error('Do Not Render As JSON');
+ }
+ const pre = document.createElement('pre');
+ pre.textContent = JSON.stringify(event.value, null, 2);
+ valueCell.appendChild(pre);
+ } catch (e) {
+ valueCell.appendChild(
+ document.createTextNode(JSON.stringify(event.value)));
+ }
+
+ if (event.key.toLowerCase().includes('error')) {
+ row.classList.add('log-error');
+ } else if (event.key.toLowerCase().includes('warning')) {
+ row.classList.add('log-warning');
+ }
}
}
@@ -570,25 +666,6 @@
navigator.clipboard.writeText(string);
}
- copyProperties_() {
- if (!this.selectedPlayer && !this.selectedAudioCompontentData) {
- return;
- }
- const properties =
- this.selectedAudioCompontentData || this.selectedPlayer.properties;
- const stringBuffer = [];
-
- for (const key in properties) {
- const value = properties[key];
- stringBuffer.push(key.toString());
- stringBuffer.push(': ');
- stringBuffer.push(value.toString());
- stringBuffer.push('\n');
- }
-
- this.renderClipboard(stringBuffer.join(''));
- }
-
onTextChange_(event) {
const text = this.filterText.value.toLowerCase();
const parts = text.split(',')
@@ -624,14 +701,33 @@
createCdmRow_(cdm) {
const template = $('cdm-row');
- const span = template.content.querySelectorAll('span');
- span[0].textContent = 'Key System: ' + cdm.key_system;
- span[1].textContent = 'Robustness: ' + cdm.robustness;
- span[2].textContent = 'Name: ' + cdm.name;
- span[3].textContent = 'Version: ' + cdm.version;
- span[4].textContent = 'Path: ' + cdm.path;
- span[5].textContent = 'Status: ' + cdm.status;
- span[6].textContent = 'Capabilities: ' + JSON.stringify(cdm.capability);
- return document.importNode(template.content, true);
+ const clone = document.importNode(template.content, true);
+ const header = clone.querySelector('.cdm-header');
+ const tableBody = clone.querySelector('tbody');
+
+ header.textContent = cdm.key_system;
+
+ const addRow = (key, value) => {
+ const row = tableBody.insertRow(-1);
+ const keyCell = row.insertCell(-1);
+ const valueCell = row.insertCell(-1);
+ keyCell.textContent = key;
+ if (typeof value === 'object') {
+ const pre = document.createElement('pre');
+ pre.textContent = JSON.stringify(value, null, 2);
+ valueCell.appendChild(pre);
+ } else {
+ valueCell.textContent = value;
+ }
+ };
+
+ addRow('Robustness', cdm.robustness);
+ addRow('Name', cdm.name);
+ addRow('Version', cdm.version);
+ addRow('Path', cdm.path);
+ addRow('Status', cdm.status);
+ addRow('Capabilities', cdm.capability);
+
+ return clone;
}
}
diff --git a/content/browser/resources/media/media_internals.css b/content/browser/resources/media/media_internals.css
index 67421e8f..5574e2d 100644
--- a/content/browser/resources/media/media_internals.css
+++ b/content/browser/resources/media/media_internals.css
@@ -2,225 +2,260 @@
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file. */
+:root {
+ --background-color: #fff;
+ --text-color: #202124;
+ --header-color: #202124;
+ --border-color: #dadce0;
+ --selected-background-color: #e8eaed;
+ --hover-background-color: #f1f3f4;
+ --toolbar-background-color: #f8f9fa;
+ --button-background-color: #e8eaed;
+ --button-hover-background-color: #dadce0;
+ --scrollbar-thumb-color: #dadce0;
+ --scrollbar-track-color: #f1f3f4;
+ --log-property-text: #1358a2;
+ --log-value-text: #6d4d72;
+}
+
+@media (prefers-color-scheme: dark) {
+ :root {
+ --background-color: #202124;
+ --text-color: #e8eaed;
+ --header-color: #e8eaed;
+ --border-color: #5f6368;
+ --selected-background-color: #3c4043;
+ --hover-background-color: #2e2f32;
+ --toolbar-background-color: #282a2d;
+ --button-background-color: #3c4043;
+ --button-hover-background-color: #4c5054;
+ --scrollbar-thumb-color: #5f6368;
+ --scrollbar-track-color: #3c4043;
+ --log-property-text: #67b0ff;
+ --log-value-text: #d596df;
+ }
+}
+
html,
-body,
-#container {
- font-family: Arial;
+body {
+ font-family: 'Roboto', sans-serif;
height: 100%;
margin: 0;
padding: 0;
width: 100%;
+ background-color: var(--background-color);
+ color: var(--text-color);
}
-cr-tab-box {
+#main-container {
+ display: flex;
+ flex-direction: column;
+ height: 100%;
+}
+
+#content-container {
+ display: flex;
+ flex-grow: 1;
+ overflow-y: auto;
+}
+
+#left-pane {
+ width: 250px;
+ min-width: 250px;
+ border-right: 1px solid var(--border-color);
+ padding: 10px;
+ display: flex;
+ flex-direction: column;
+}
+
+#player-list-wrapper {
+ flex: 1;
+ overflow-y: auto;
+ /* To make the above combination work, we need to set min-height to 0. */
+ min-height: 0;
+}
+
+#left-pane-controls {
padding-top: 10px;
}
-div[slot='tab'] {
- -webkit-user-select: none;
+#hide-players-button {
+ width: 100%;
}
-body div[slot='panel'] {
- box-shadow: none;
+#right-pane {
+ flex-grow: 1;
+ display: flex;
+ flex-direction: column;
+ overflow-y: auto;
}
-div[slot='panel'] {
- padding: 10px;
+#right-pane-toolbar {
+ display: flex;
+ border-bottom: 1px solid var(--border-color);
+ background-color: var(--toolbar-background-color);
+ padding: 5px;
+ flex-shrink: 0;
}
-table {
- -webkit-font-smoothing: antialiased;
+.toolbar-button {
+ background-color: transparent;
+ border: 1px solid transparent;
+ color: var(--text-color);
+ padding: 8px 12px;
+ cursor: pointer;
+ font-size: 14px;
+}
+
+.toolbar-button.selected {
+ background-color: var(--selected-background-color);
+ border-color: var(--border-color);
+}
+
+.toolbar-button:hover {
+ background-color: var(--hover-background-color);
+}
+
+#right-pane-content {
+ overflow-y: auto;
+}
+
+.panel {
+ display: none;
+ padding: 20px;
+ box-sizing: border-box;
+}
+
+#players-panel {
+ padding: 0;
+}
+
+.panel.active {
display: block;
- font-family: sans-serif;
- font-size: 115%;
- overflow: auto;
- width: auto;
-}
-th {
- background-color: rgb(74, 169, 228);
- color: white;
- font-weight: normal;
- min-width: 230px;
- padding: 2px;
- text-align: center;
-}
-td {
- background-color: rgb(238, 238, 238);
- color: rgb(111, 111, 111);
- min-width: 230px;
- padding: 2px;
- word-wrap: break-word;
}
h1,
h2,
h3 {
- color: rgb(50,50,50);
+ color: var(--header-color);
+ font-weight: 500;
}
-#container {
- align-content: stretch;
- align-items: flex-start;
+.property-wrapper h2,
+#log-wrapper h2,
+#video-capture-capabilities-wrapper h2 {
display: flex;
- flex-direction: row;
- flex-wrap: wrap;
- justify-content: space-between;
+ align-items: center;
}
-#container > * {
- margin: 0;
- padding: 0;
- padding-inline-start: 25px;
+.copy-properties-button,
+.copy-log-button,
+#video-capture-capabilities-copy-button {
+ margin-left: auto;
+ font-size: 12px;
+ padding: 4px 8px;
}
-#list-wrapper {
- align-content: stretch;
- align-items: flex-start;
- display: flex;
- flex-direction: column;
- justify-content: space-between;
+table {
+ width: 100%;
+ border-collapse: collapse;
+ margin-top: 10px;
}
-#player-list-wrapper,
-#audio-component-list-wrapper {
- align-self: stretch;
- flex-grow: 1;
- min-width: 200px;
- overflow: auto;
+th,
+td {
+ border: 1px solid var(--border-color);
+ padding: 8px;
+ text-align: left;
}
-#player-list-wrapper ul,
-#player-list-wrapper li,
-#audio-component-list-wrapper ul,
-#audio-component-list-wrapper li {
+th {
+ background-color: var(--toolbar-background-color);
+}
+
+tr:nth-child(even) {
+ background-color: var(--hover-background-color);
+}
+
+td {
+ word-wrap: break-word;
+}
+
+#player-property-table th:first-child,
+#player-property-table td:first-child {
+ width: 250px;
+}
+
+ul {
list-style-type: none;
padding: 0;
}
-#list-wrapper button {
- padding: 0;
-}
-
-.property-wrapper,
-#log-wrapper {
- align-self: stretch;
- display: block;
- flex-grow: 0.25;
- margin-bottom: 10px;
- overflow: auto;
-}
-
-#video-capture-capabilities-wrapper {
- align-self: stretch;
- flex-grow: 0.5;
- overflow: auto;
-}
-
-#log-wrapper > thead {
- position: fixed;
-}
-
-#graphs li {
- list-style-type: none;
-}
-
-#clipboard-dialog {
- bottom: 0;
- padding-top: 0;
- top: 0;
-}
-
-::backdrop {
- background-color: #000;
- opacity: 0.5;
-}
-
-.timestamp {
- min-width: 115px;
-}
-
-#video-capture-capabilities-table {
- margin-bottom: 30px;
-}
-
-#video-capture-capabilities-table th,
-#video-capture-capabilities-table td {
- min-width: 120px;
-}
-
-#video-capture-capabilities-table td {
- padding: 5px;
-}
-
-#video-capture-capabilities-table tr td {
- font-size: 13px;
- text-align: center;
-}
-
-#video-capture-capabilities-table .video-capture-formats-table th,
-#video-capture-capabilities-table .video-capture-formats-table td {
- min-width: 80px;
- text-align: end;
-}
-
-#video-capture-capabilities-table .video-capture-formats-table th {
- background: none;
- color: #666;
- font-size: 13px;
- font-weight: bold;
-}
-
-#video-capture-capabilities-table .video-capture-formats-table td {
- padding: 2px;
-}
.show-none-if-empty:empty::after {
- color: rgba(0, 0, 0, .5);
+ color: rgba(255, 255, 255, 0.5);
content: 'none';
}
-label.audio-focus-session,
-label.cdm,
-label.selectable-button {
- border: solid 1px #999;
+.audio-focus-session {
+ border: 1px solid var(--border-color);
border-radius: 3px;
display: block;
line-height: 1.4;
- margin: 4px 0;
- padding: 6px;
- word-break: break-all;
+ margin: 8px 0;
+ padding: 12px;
+ overflow-wrap: break-word;
}
-label.selectable-button {
- background: rgb(187, 221, 255);
- cursor: pointer;
- user-select: none;
+.cdm-item {
+ border: 1px solid var(--border-color);
+ border-radius: 3px;
+ margin-bottom: 15px;
+ list-style-type: none;
}
-input.selectable-button {
- display: none;
+.cdm-info-list {
+ list-style-type: disc;
+ padding-inline-start: 40px;
+ margin-bottom: 15px;
}
-input.selectable-button:checked + label.selectable-button {
- background-color: rgb(33, 150, 243);
- border-color: #666;
- color: #FFF;
+.cdm-header {
+ background-color: var(--toolbar-background-color);
+ padding: 8px 12px;
+ font-weight: 500;
+ border-bottom: 1px solid var(--border-color);
}
-input.selectable-button:hover + label.selectable-button {
- border-color: #666;
+.cdm-properties-table {
+ width: 100%;
+ border-collapse: collapse;
+ margin-top: 0;
}
-label.audio-focus-session,
-label.cdm {
- background-color: #EEE;
+.cdm-properties-table td {
+ padding: 8px 12px;
+ border: none;
+ border-bottom: 1px solid var(--border-color);
+ vertical-align: top;
+ background-color: transparent;
+}
+
+.cdm-properties-table tr:last-child td {
+ border-bottom: none;
+}
+
+.cdm-properties-table td:first-child {
+ font-weight: 500;
+ width: 120px;
+ color: var(--log-property-text);
}
label.errored-player {
- background-color: rgb(255, 187, 187);
+ background-color: #6b2c2c;
}
label.ended-player {
- background-color: rgb(207, 255, 187);
+ background-color: #2c6b2c;
}
.player-name {
@@ -231,22 +266,255 @@
font-style: italic;
overflow: hidden;
text-overflow: ellipsis;
+ overflow-wrap: anywhere;
+}
+
+.tree-item {
+ margin-bottom: 5px;
+}
+
+.tree-item-header {
+ border: 1px solid var(--border-color);
+ border-left: 5px solid transparent;
+ border-radius: 3px;
+ cursor: pointer;
+ min-height: 30px;
+ overflow-wrap: anywhere;
+ padding: 8px 12px;
+ position: relative;
+ user-select: none;
+}
+
+.tree-item-header:hover {
+ background-color: var(--hover-background-color);
+}
+
+.tree-item.errored-player > .tree-item-header {
+ border-left-color: #d93025; /* Red */
+}
+
+.tree-item.ended-player > .tree-item-header {
+ border-left-color: #1e8e3e; /* Green */
+}
+
+.tree-item.active-player > .tree-item-header {
+ border-left-color: #1a73e8; /* Blue */
+}
+
+
+
+.tree-item.selected > .tree-item-header {
+ background-color: var(--selected-background-color);
+}
+
+.tree-item-children {
+ display: none;
+ padding-left: 15px;
+}
+
+.tree-item.expanded > .tree-item-children {
+ display: block;
+}
+
+.log-entry {
+ border-bottom: 1px solid var(--border-color);
+}
+
+.log-timestamp {
+ color: #9e9e9e; /* Grey */
+}
+
+.log-property {
+ color: var(--log-property-text);
+}
+
+.log-value {
+ color: var(--log-value-text);
+}
+
+.log-error {
+ background-color: rgba(244, 67, 54, 0.2); /* Red */
+ color: #ef5350;
+}
+
+.log-warning {
+ background-color: rgba(255, 235, 59, 0.2); /* Yellow */
+ color: #ffee58;
+}
+
+#player-details-container {
+ display: flex;
+ flex-direction: column;
+ height: 100%;
+}
+
+#player-details-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: 10px;
+ border-bottom: 1px solid var(--border-color);
+ background-color: var(--background-color);
+ position: sticky;
+ top: 0;
+ z-index: 1;
+}
+
+#player-details-title {
+ min-width: 0;
+ margin-right: 1em;
+ overflow: hidden;
+ text-overflow: ellipsis;
white-space: nowrap;
}
-.no-players-selected #players .property-wrapper,
-.no-players-selected #players #log-wrapper {
+#player-actions {
+ display: flex;
+ gap: 10px;
+ flex-shrink: 0;
+}
+
+#player-details-content {
+ overflow-y: auto;
+ flex-grow: 1;
+ padding: 10px;
+}
+
+.property-wrapper,
+#log-wrapper {
+ margin-bottom: 20px;
+}
+
+#filter-text {
+ background-color: var(--background-color);
+ color: var(--text-color);
+ border: 1px solid var(--border-color);
+ padding: 5px;
+}
+
+button {
+ background-color: var(--button-background-color);
+ color: var(--text-color);
+ border: 1px solid var(--border-color);
+ padding: 8px 12px;
+ cursor: pointer;
+}
+
+button:hover {
+ background-color: var(--button-hover-background-color);
+}
+
+::-webkit-scrollbar {
+ width: 10px;
+}
+
+::-webkit-scrollbar-track {
+ background: var(--scrollbar-track-color);
+}
+
+pre {
+ margin: 0;
+ white-space: pre-wrap;
+ word-break: break-all;
+}
+
+::-webkit-scrollbar-thumb {
+ background: var(--scrollbar-thumb-color);
+}
+
+.no-players-selected #player-details-container {
display: none;
}
-.no-components-selected #audio .property-wrapper {
+.no-players-selected #player-placeholder {
+ display: block;
+}
+
+#player-list:empty + #player-list-placeholder {
+ display: block;
+}
+
+#player-placeholder {
+ display: none;
+ padding: 20px;
+ text-align: center;
+ font-size: 16px;
+ color: var(--text-color);
+}
+
+#audio-component-list-wrapper {
+ margin-bottom: 20px;
+ padding-left: 20px;
+}
+
+#audio-output-stream-list > .tree-item-header {
+ line-height: 30px;
+}
+
+#audio-panel .tree-item {
+ width: 400px;
+}
+
+#close-player-view-button {
display: none;
}
-#audio-focus-session-list {
- list-style: none;
-}
+@media (max-width: 768px) {
+ #content-container {
+ flex-direction: column;
+ }
-#cdm-list {
- list-style: none;
+ body.players-tab-active.no-players-selected #right-pane {
+ display: none;
+ }
+
+ #left-pane {
+ width: auto;
+ min-width: 0;
+ border-right: none;
+ border-bottom: 1px solid var(--border-color);
+ height: 100%;
+ }
+
+ #right-pane-toolbar {
+ flex-wrap: nowrap;
+ overflow-x: auto;
+ }
+
+ .toolbar-button {
+ padding: 6px 8px;
+ }
+
+ #player-details-header {
+ flex-wrap: wrap;
+ gap: 10px;
+ }
+
+ #player-actions {
+ flex-wrap: wrap;
+ }
+
+ .mobile-player-view-active #left-pane,
+ .mobile-player-view-active #right-pane-toolbar {
+ display: none;
+ }
+
+ .mobile-player-view-active #right-pane {
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ z-index: 2;
+ background-color: var(--background-color);
+ }
+
+ .mobile-player-view-active #close-player-view-button {
+ display: inline-block;
+ }
+
+ #player-property-table th:first-child,
+ #player-property-table td:first-child {
+ width: 80px;
+ }
}
diff --git a/content/browser/resources/media/media_internals.html b/content/browser/resources/media/media_internals.html
index 82a30c16..36f355fc 100644
--- a/content/browser/resources/media/media_internals.html
+++ b/content/browser/resources/media/media_internals.html
@@ -14,161 +14,171 @@
</head>
<body>
- <cr-tab-box hidden>
- <div slot="tab">Players</div>
- <div slot="tab">Audio</div>
- <div slot="tab">Video Capture</div>
- <div slot="tab">Audio Focus</div>
- <div slot="tab">CDMs</div>
- <div slot="panel" id="players">
- <button id="save-log-button" title="Save all player logs into a file." style="display:none">Save log</button>
- <button id="hide-players-button" title="Hide all players in the current view." stype="display:inline-block">Hide players</button>
- <button id="copy-all-player-button">Copy all to clipboard</button>
- <div id="list-wrapper">
+ <div id="main-container">
+ <div id="right-pane-toolbar">
+ <button id="players-tab-button" class="toolbar-button selected">Players</button>
+ <button id="audio-tab-button" class="toolbar-button">Audio</button>
+ <button id="video-capture-tab-button" class="toolbar-button">Video Capture</button>
+ <button id="audio-focus-tab-button" class="toolbar-button">Audio Focus</button>
+ <button id="cdms-tab-button" class="toolbar-button">CDMs</button>
+ </div>
+ <div id="content-container">
+ <div id="left-pane">
<div id="player-list-wrapper">
- <h2>Recent Players</h2>
+ <h2>Select a player</h2>
<ul id="player-list" class="show-none-if-empty"></ul>
+ <div id="player-list-placeholder" class="placeholder-text" hidden>
+ No media player logs
+ </div>
+ </div>
+ <div id="left-pane-controls">
+ <button id="hide-players-button">Clear Players</button>
</div>
</div>
- <div class="property-wrapper">
- <h2>
- Player Properties
- <button class="copy-properties-button">Copy to clipboard</button>
- </h2>
- <table id="player-property-table">
- <thead>
- <tr>
- <th>Property</th>
- <th>Value</th>
- </tr>
- </thead>
- <tbody></tbody>
- </table>
- </div>
- <div id="log-wrapper">
- <h2>
- Log <input id="filter-text" type="text" placeholder="property filter">
- <button class="copy-log-button">Copy to clipboard</button>
- </h2>
- <table id="log">
- <thead>
- <tr>
- <th class="timestamp">Timestamp</th>
- <th>Property</th>
- <th>Value</th>
- </tr>
- </thead>
- <tbody></tbody>
- </table>
- </div>
- <ul id="graphs"></ul>
- </div>
- <div slot="panel" id="audio">
- <button id="copy-all-audio-button">Copy all to clipboard</button>
- <div>
- <h2>General Information</h2>
- <table id="general-audio-info-table">
- <thead>
- <tr>
- <th>Property</th>
- <th>Value</th>
- </tr>
- </thead>
- <tbody></tbody>
- </table>
- </div>
- <div id="audio-component-list-wrapper">
- <h2>Input Controllers</h2>
- <ul id="audio-input-controller-list" class="show-none-if-empty"></ul>
- </div>
- <div id="audio-component-list-wrapper">
- <h2>Output Controllers</h2>
- <ul id="audio-output-controller-list" class="show-none-if-empty"></ul>
- </div>
- <div id="audio-component-list-wrapper">
- <h2>Output Streams</h2>
- <ul id="audio-output-stream-list" class="show-none-if-empty"></ul>
- </div>
- <div class="property-wrapper">
- <h2>
- <span id="audio-property-name"></span> Properties
- <button class="copy-properties-button">Copy to clipboard</button>
- </h2>
- <table id="audio-property-table">
- <thead>
- <tr>
- <th>Property</th>
- <th>Value</th>
- </tr>
- </thead>
- <tbody></tbody>
- </table>
+ <div id="right-pane">
+ <div id="right-pane-content">
+ <div id="players-panel" class="panel">
+ <div id="player-placeholder" class="placeholder-text">
+ Select a player to view properties and events.
+ </div>
+ <div id="player-details-container">
+ <div id="player-details-header">
+ <h2 id="player-details-title">Player Properties</h2>
+ <div id="player-actions">
+ <input id="filter-text" type="text" placeholder="Filter log...">
+ <button id="copy-log-button">Copy All</button>
+ <button id="save-log-button">Save Log</button>
+ <button id="close-player-view-button">Close</button>
+ </div>
+ </div>
+ <div id="player-details-content">
+ <table id="player-property-table">
+ <thead>
+ <tr>
+ <th>Property</th>
+ <th>Value</th>
+ </tr>
+ </thead>
+ <tbody></tbody>
+ </table>
+ <div id="log-wrapper">
+ <h2>Log</h2>
+ <table id="log">
+ <thead>
+ <tr>
+ <th class="timestamp">Timestamp</th>
+ <th>Property</th>
+ <th>Value</th>
+ </tr>
+ </thead>
+ <tbody></tbody>
+ </table>
+ </div>
+ </div>
+ <ul id="graphs"></ul>
+ </div>
+ </div>
+ <div id="audio-panel" class="panel" hidden>
+ <div id="audio-component-list-wrapper">
+ <h2>Audio</h2>
+ <h3>Input Controllers</h3>
+ <ul id="audio-input-controller-list" class="show-none-if-empty"></ul>
+ <h3>Output Controllers</h3>
+ <ul id="audio-output-controller-list" class="show-none-if-empty"></ul>
+ <h3>Output Streams</h3>
+ <ul id="audio-output-stream-list" class="show-none-if-empty"></ul>
+ </div>
+ <div>
+ <h2>General Information</h2>
+ <table id="general-audio-info-table">
+ <thead>
+ <tr>
+ <th>Property</th>
+ <th>Value</th>
+ </tr>
+ </thead>
+ <tbody></tbody>
+ </table>
+ </div>
+ <div class="property-wrapper">
+ <h2>
+ <span id="audio-property-name"></span> Properties
+ <button class="copy-properties-button">Copy to clipboard</button>
+ </h2>
+ <table id="audio-property-table">
+ <thead>
+ <tr>
+ <th>Property</th>
+ <th>Value</th>
+ </tr>
+ </thead>
+ <tbody></tbody>
+ </table>
+ </div>
+ </div>
+ <div id="video-capture-panel" class="panel" hidden>
+ <div id="video-capture-capabilities-wrapper">
+ <h2>
+ <span>Video Capture Device Capabilities</span>
+ <button id="video-capture-capabilities-copy-button">
+ Copy to clipboard
+ </button>
+ </h2>
+ <table id="video-capture-capabilities-table">
+ <thead>
+ <tr>
+ <th>Device Name</th>
+ <th>Formats</th>
+ <th>Capture API</th>
+ <th>Pan-Tilt-Zoom</th>
+ <th>Device ID</th>
+ </tr>
+ </thead>
+ <tbody id="video-capture-capabilities-tbody" class="show-none-if-empty"></tbody>
+ </table>
+ </div>
+ </div>
+ <div id="audio-focus-panel" class="panel" hidden>
+ <div id="list-wrapper">
+ <h2>Active Sessions</h2>
+ <ul id="audio-focus-session-list" class="show-none-if-empty"></ul>
+ </div>
+ <template id="audio-focus-session-row">
+ <li>
+ <label class="audio-focus-session">
+ <span class="player-name"></span><br />
+ <span class="player-frame"></span><br />
+ <span class="player-desc"></span>
+ </label>
+ </li>
+ </template>
+ </div>
+ <div id="cdms-panel" class="panel" hidden>
+ <div id="list-wrapper">
+ <h2>Registered Content Decryption Modules</h2>
+ <ul class="cdm-info-list">
+ <li>Clear Key ("org.w3.clearkey") is always supported and not listed
+ here.</li>
+ <li>Empty video codec profile list means we are not differentiating
+ and assume all profiles are supported.</li>
+ <li>Codecs marked with "*" signals clear lead not supported.</li>
+ </ul>
+ <ul id="cdm-list" class="show-none-if-empty"></ul>
+ </div>
+ <template id="cdm-row">
+ <li class="cdm-item">
+ <div class="cdm-header"></div>
+ <table class="cdm-properties-table">
+ <tbody>
+ </tbody>
+ </table>
+ </li>
+ </template>
+ </div>
+ </div>
</div>
</div>
- <div slot="panel" id="video-capture">
- <div id="video-capture-capabilities-wrapper">
- <h2>
- <span>Video Capture Device Capabilities</span>
- <button id="video-capture-capabilities-copy-button">
- Copy to clipboard
- </button>
- </h2>
- <table id="video-capture-capabilities-table">
- <thead>
- <tr>
- <th>Device Name</th>
- <th>Formats</th>
- <th>Capture API</th>
- <th>Pan-Tilt-Zoom</th>
- <th>Device ID</th>
- </tr>
- </thead>
- <tbody id="video-capture-capabilities-tbody" class="show-none-if-empty"></tbody>
- </table>
- </div>
- </div>
- <div slot="panel" id="audio-focus">
- <div id="list-wrapper">
- <h2>Active Sessions</h2>
- <ul id="audio-focus-session-list" class="show-none-if-empty"></ul>
- </div>
- <template id="audio-focus-session-row">
- <li>
- <label class="audio-focus-session">
- <span class="player-name"></span><br />
- <span class="player-frame"></span><br />
- <span class="player-desc"></span>
- </label>
- </li>
- </template>
- </div>
- <div slot="panel" id="cdms">
- <div id="list-wrapper">
- <h2>Registered Content Decryption Modules</h2>
- <ul>
- <li>Clear Key ("org.w3.clearkey") is always supported and not listed
- here.</li>
- <li>Empty video codec profile list means we are not differentiating
- and assume all profiles are supported.</li>
- <li>Codecs marked with "*" signals clear lead not supported.</li>
- </ul>
- <ul id="cdm-list" class="show-none-if-empty"></ul>
- </div>
- <template id="cdm-row">
- <li>
- <label class="cdm">
- <span class="key-system"></span><br />
- <span class="robustness"></span><br />
- <span class="name"></span><br />
- <span class="version"></span><br />
- <span class="path"></span><br />
- <span class="capability-status"></span><br />
- <span class="capability"></span><br />
- </label>
- </li>
- </template>
- </div>
- </cr-tab-box>
+ </div>
<script type="module" src="media_internals.js"></script>
</body>
-</html>
+</html>
\ No newline at end of file
diff --git a/content/browser/resources/media/media_internals.js b/content/browser/resources/media/media_internals.js
index 9951deb6..547ba8b3 100644
--- a/content/browser/resources/media/media_internals.js
+++ b/content/browser/resources/media/media_internals.js
@@ -2,25 +2,65 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-import 'chrome://resources/cr_elements/cr_tab_box/cr_tab_box.js';
-
import {ClientRenderer} from './client_renderer.js';
import {initialize} from './main.js';
import {Manager} from './manager.js';
initialize(new Manager(new ClientRenderer()));
-const tabBox = document.querySelector('cr-tab-box');
-tabBox.hidden = false;
-const _TabIndicies = {
- '#players': 0,
- '#audio': 1,
- '#video-capture': 2,
- '#audio-focus': 3,
- '#cdms': 4,
-};
+const toolbar = document.getElementById('right-pane-toolbar');
+const panels = document.getElementById('right-pane-content');
-const tabHash = window.location.hash.toLowerCase();
-if (tabHash in _TabIndicies) {
- tabBox.setAttribute('selected-index', _TabIndicies[tabHash]);
+toolbar.addEventListener('click', (event) => {
+ if (event.target.classList.contains('toolbar-button')) {
+ const selectedButton = toolbar.querySelector('.selected');
+ if (selectedButton) {
+ selectedButton.classList.remove('selected');
+ }
+ event.target.classList.add('selected');
+
+ const panelId = event.target.id.replace('-tab-button', '-panel');
+ const activePanel = panels.querySelector('.panel.active');
+ if (activePanel) {
+ activePanel.classList.remove('active');
+ activePanel.hidden = true;
+ }
+ const newActivePanel = panels.querySelector(`#${panelId}`);
+ if (newActivePanel) {
+ newActivePanel.classList.add('active');
+ newActivePanel.hidden = false;
+ }
+
+ if (event.target.id === 'players-tab-button') {
+ document.getElementById('left-pane').style.display = 'flex';
+ document.body.classList.add('players-tab-active');
+ } else {
+ document.getElementById('left-pane').style.display = 'none';
+ document.body.classList.remove('players-tab-active');
+ }
+ }
+});
+
+// Default to the players tab
+document.getElementById('players-tab-button').click();
+
+const contentContainer = document.getElementById('content-container');
+const leftPane = document.getElementById('left-pane');
+const rightPane = document.getElementById('right-pane');
+
+function updateLayoutForMobile() {
+ if (window.innerWidth <= 768) {
+ // Move left-pane below the toolbar in right-pane
+ if (leftPane.parentElement !== contentContainer) {
+ contentContainer.insertBefore(leftPane, rightPane);
+ }
+ } else {
+ // Move left-pane back to the main container
+ if (leftPane.parentElement !== contentContainer) {
+ contentContainer.insertBefore(leftPane, rightPane);
+ }
+ }
}
+
+window.addEventListener('resize', updateLayoutForMobile);
+updateLayoutForMobile();