blob: 6ceae44981e4e1286cc1c13daa02fd1eab12972a [file] [log] [blame]
// 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.
import * as Common from '../common/common.js'; // eslint-disable-line no-unused-vars
import * as SDK from '../sdk/sdk.js';
import * as UI from '../ui/ui.js';
import {MediaModel, PlayerEvent, ProtocolTriggers} from './MediaModel.js'; // eslint-disable-line no-unused-vars
import {PlayerDetailView} from './PlayerDetailView.js';
import {PlayerListView} from './PlayerListView.js';
/** @interface */
export class TriggerHandler {
/** @param {!Protocol.Media.PlayerProperty} property */
onProperty(property) {
}
/** @param {!Protocol.Media.PlayerError} error */
onError(error) {
}
/** @param {!Protocol.Media.PlayerMessage} message */
onMessage(message) {
}
/** @param {!PlayerEvent} event */
onEvent(event) {
}
}
/** @interface */
export class TriggerDispatcher {
/**
* @param {string} playerID
* @param {!Protocol.Media.PlayerProperty} property
*/
onProperty(playerID, property) {
}
/**
* @param {string} playerID
* @param {!Protocol.Media.PlayerError} error
*/
onError(playerID, error) {
}
/**
* @param {string} playerID
* @param {!Protocol.Media.PlayerMessage} message
*/
onMessage(playerID, message) {
}
/**
* @param {string} playerID
* @param {!PlayerEvent} event
*/
onEvent(playerID, event) {
}
}
/**
* @implements TriggerHandler
*/
class PlayerDataCollection {
constructor() {
/** @type {!Map<string, string>} */
this._properties = new Map();
/** @type {!Array<!Protocol.Media.PlayerMessage>} */
this._messages = [];
/** @type {!Array<!PlayerEvent>} */
this._events = [];
/** @type {!Array<!Protocol.Media.PlayerError>} */
this._errors = [];
}
/**
* @override
* @param {!Protocol.Media.PlayerProperty} property
*/
onProperty(property) {
this._properties.set(property.name, property.value);
}
/**
* @override
* @param {!Protocol.Media.PlayerError} error */
onError(error) {
this._errors.push(error);
}
/**
* @override
* @param {!Protocol.Media.PlayerMessage} message
*/
onMessage(message) {
this._messages.push(message);
}
/**
* @override
* @param {!PlayerEvent} event
*/
onEvent(event) {
this._events.push(event);
}
export() {
return {'properties': this._properties, 'messages': this._messages, 'events': this._events, 'errors': this._errors};
}
}
/**
* @implements TriggerDispatcher
*/
class PlayerDataDownloadManager {
constructor() {
/**
* @type {!Map<string, !PlayerDataCollection>}
*/
this._playerDataCollection = new Map();
}
/**
* @param {string} playerID
*/
addPlayer(playerID) {
this._playerDataCollection.set(playerID, new PlayerDataCollection());
}
/**
* @override
* @param {string} playerID
* @param {!Protocol.Media.PlayerProperty} property
*/
onProperty(playerID, property) {
const playerProperty = this._playerDataCollection.get(playerID);
if (!playerProperty) {
return;
}
playerProperty.onProperty(property);
}
/**
* @override
* @param {string} playerID
* @param {!Protocol.Media.PlayerError} error
*/
onError(playerID, error) {
const playerProperty = this._playerDataCollection.get(playerID);
if (!playerProperty) {
return;
}
playerProperty.onError(error);
}
/**
* @override
* @param {string} playerID
* @param {!Protocol.Media.PlayerMessage} message
*/
onMessage(playerID, message) {
const playerProperty = this._playerDataCollection.get(playerID);
if (!playerProperty) {
return;
}
playerProperty.onMessage(message);
}
/**
* @override
* @param {string} playerID
* @param {!PlayerEvent} event
*/
onEvent(playerID, event) {
const playerProperty = this._playerDataCollection.get(playerID);
if (!playerProperty) {
return;
}
playerProperty.onEvent(event);
}
/**
* @param {string} playerID
*/
exportPlayerData(playerID) {
const playerProperty = this._playerDataCollection.get(playerID);
if (!playerProperty) {
throw new Error('Unable to find player');
}
return playerProperty.export();
}
/**
* @param {string} playerID
*/
deletePlayer(playerID) {
this._playerDataCollection.delete(playerID);
}
}
/**
* @implements {SDK.SDKModel.SDKModelObserver<!MediaModel>}
*/
export class MainView extends UI.Panel.PanelWithSidebar {
constructor() {
super('Media');
this.registerRequiredCSS('media/mediaView.css', {enableLegacyPatching: true});
// Map<PlayerDetailView>
this._detailPanels = new Map();
// Map<string>
this._deletedPlayers = new Set();
this._downloadStore = new PlayerDataDownloadManager();
this._sidebar = new PlayerListView(this);
this._sidebar.show(this.panelSidebarElement());
SDK.SDKModel.TargetManager.instance().observeModels(MediaModel, this);
}
/**
* @param {string} playerID
*/
renderMainPanel(playerID) {
if (!this._detailPanels.has(playerID)) {
return;
}
const mainWidget = this.splitWidget().mainWidget();
if (mainWidget) {
mainWidget.detachChildWidgets();
}
this._detailPanels.get(playerID).show(this.mainElement());
}
/**
* @override
*/
wasShown() {
super.wasShown();
for (const model of SDK.SDKModel.TargetManager.instance().models(MediaModel)) {
this._addEventListeners(model);
}
}
/**
* @override
*/
willHide() {
for (const model of SDK.SDKModel.TargetManager.instance().models(MediaModel)) {
this._removeEventListeners(model);
}
}
/**
* @override
* @param {!MediaModel} model
*/
modelAdded(model) {
if (this.isShowing()) {
this._addEventListeners(model);
}
}
/**
* @override
* @param {!MediaModel} model
*/
modelRemoved(model) {
this._removeEventListeners(model);
}
/**
* @param {!MediaModel} mediaModel
*/
_addEventListeners(mediaModel) {
mediaModel.ensureEnabled();
mediaModel.addEventListener(ProtocolTriggers.PlayerPropertiesChanged, this._propertiesChanged, this);
mediaModel.addEventListener(ProtocolTriggers.PlayerEventsAdded, this._eventsAdded, this);
mediaModel.addEventListener(ProtocolTriggers.PlayerMessagesLogged, this._messagesLogged, this);
mediaModel.addEventListener(ProtocolTriggers.PlayerErrorsRaised, this._errorsRaised, this);
mediaModel.addEventListener(ProtocolTriggers.PlayersCreated, this._playersCreated, this);
}
/**
* @param {!MediaModel} mediaModel
*/
_removeEventListeners(mediaModel) {
mediaModel.removeEventListener(ProtocolTriggers.PlayerPropertiesChanged, this._propertiesChanged, this);
mediaModel.removeEventListener(ProtocolTriggers.PlayerEventsAdded, this._eventsAdded, this);
mediaModel.removeEventListener(ProtocolTriggers.PlayerMessagesLogged, this._messagesLogged, this);
mediaModel.removeEventListener(ProtocolTriggers.PlayerErrorsRaised, this._errorsRaised, this);
mediaModel.removeEventListener(ProtocolTriggers.PlayersCreated, this._playersCreated, this);
}
/**
* @param {string} playerID
*/
_onPlayerCreated(playerID) {
this._sidebar.addMediaElementItem(playerID);
this._detailPanels.set(playerID, new PlayerDetailView());
this._downloadStore.addPlayer(playerID);
}
/**
* @param {!Common.EventTarget.EventTargetEvent} event
*/
_propertiesChanged(event) {
for (const property of event.data.properties) {
this.onProperty(event.data.playerId, property);
}
}
/**
* @param {!Common.EventTarget.EventTargetEvent} event
*/
_eventsAdded(event) {
for (const ev of event.data.events) {
this.onEvent(event.data.playerId, ev);
}
}
/**
* @param {!Common.EventTarget.EventTargetEvent} event
*/
_messagesLogged(event) {
for (const message of event.data.messages) {
this.onMessage(event.data.playerId, message);
}
}
/**
* @param {!Common.EventTarget.EventTargetEvent} event
*/
_errorsRaised(event) {
for (const error of event.data.errors) {
this.onError(event.data.playerId, error);
}
}
/**
* @param {string} playerID
* @return {boolean}
*/
_shouldPropagate(playerID) {
return !this._deletedPlayers.has(playerID) && this._detailPanels.has(playerID);
}
/**
* @param {string} playerID
* @param {!Protocol.Media.PlayerProperty} property
*/
onProperty(playerID, property) {
if (!this._shouldPropagate(playerID)) {
return;
}
this._sidebar.onProperty(playerID, property);
this._downloadStore.onProperty(playerID, property);
this._detailPanels.get(playerID).onProperty(property);
}
/**
* @param {string} playerID
* @param {!Protocol.Media.PlayerError} error
*/
onError(playerID, error) {
if (!this._shouldPropagate(playerID)) {
return;
}
this._sidebar.onError(playerID, error);
this._downloadStore.onError(playerID, error);
this._detailPanels.get(playerID).onError(error);
}
/**
* @param {string} playerID
* @param {!Protocol.Media.PlayerMessage} message
*/
onMessage(playerID, message) {
if (!this._shouldPropagate(playerID)) {
return;
}
this._sidebar.onMessage(playerID, message);
this._downloadStore.onMessage(playerID, message);
this._detailPanels.get(playerID).onMessage(message);
}
/**
* @param {string} playerID
* @param {!PlayerEvent} event
*/
onEvent(playerID, event) {
if (!this._shouldPropagate(playerID)) {
return;
}
this._sidebar.onEvent(playerID, event);
this._downloadStore.onEvent(playerID, event);
this._detailPanels.get(playerID).onEvent(event);
}
/**
* @param {!Common.EventTarget.EventTargetEvent} event
*/
_playersCreated(event) {
const playerlist = /** @type {!Iterable.<string>} */ (event.data);
for (const playerID of playerlist) {
this._onPlayerCreated(playerID);
}
}
/**
* @param {string} playerID
*/
markPlayerForDeletion(playerID) {
// TODO(tmathmeyer): send this to chromium to save the storage space there too.
this._deletedPlayers.add(playerID);
this._detailPanels.delete(playerID);
this._sidebar.deletePlayer(playerID);
this._downloadStore.deletePlayer(playerID);
}
/**
* @param {string} playerID
*/
markOtherPlayersForDeletion(playerID) {
for (const keyID of this._detailPanels.keys()) {
if (keyID !== playerID) {
this.markPlayerForDeletion(keyID);
}
}
}
/**
* @param {string} playerID
*/
exportPlayerData(playerID) {
const dump = this._downloadStore.exportPlayerData(playerID);
const uriContent = 'data:application/octet-stream,' + encodeURIComponent(JSON.stringify(dump, null, 2));
const anchor = document.createElement('a');
anchor.href = uriContent;
anchor.download = playerID + '.json';
anchor.click();
}
}