| // 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<!WebAudio.WebAudioModel>} |
| */ |
| export class WebAudioView extends UI.ThrottledWidget { |
| constructor() { |
| super(true, 1000); |
| this.element.classList.add('web-audio-drawer'); |
| this.registerRequiredCSS('web_audio/webAudio.css'); |
| |
| // Creates the toolbar. |
| const toolbarContainer = this.contentElement.createChild( |
| 'div', 'web-audio-toolbar-container vbox'); |
| this._contextSelector = new WebAudio.AudioContextSelector(); |
| const toolbar = new UI.Toolbar('web-audio-toolbar', toolbarContainer); |
| toolbar.appendToolbarItem(UI.Toolbar.createActionButtonForId('components.collect-garbage')); |
| toolbar.appendSeparator(); |
| toolbar.appendToolbarItem(this._contextSelector.toolbarItem()); |
| |
| // Creates the detail view. |
| this._detailViewContainer = this.contentElement.createChild('div', 'vbox flex-auto'); |
| |
| this._graphManager = new WebAudio.GraphVisualizer.GraphManager(); |
| |
| // Creates the landing page. |
| this._landingPage = new UI.VBox(); |
| this._landingPage.contentElement.classList.add('web-audio-landing-page', 'fill'); |
| this._landingPage.contentElement.appendChild(UI.html` |
| <div> |
| <p>${ls`Open a page that uses Web Audio API to start monitoring.`}</p> |
| </div> |
| `); |
| this._landingPage.show(this._detailViewContainer); |
| |
| // Creates the summary bar. |
| this._summaryBarContainer = this.contentElement.createChild('div', 'web-audio-summary-container'); |
| |
| this._contextSelector.addEventListener(WebAudio.AudioContextSelector.Events.ContextSelected, event => { |
| const context = |
| /** @type {!Protocol.WebAudio.BaseAudioContext} */ (event.data); |
| this._updateDetailView(context); |
| this.doUpdate(); |
| }); |
| |
| SDK.targetManager.observeModels(WebAudio.WebAudioModel, this); |
| } |
| |
| /** |
| * @override |
| */ |
| wasShown() { |
| super.wasShown(); |
| for (const model of SDK.targetManager.models(WebAudio.WebAudioModel)) { |
| this._addEventListeners(model); |
| } |
| } |
| |
| /** |
| * @override |
| */ |
| willHide() { |
| for (const model of SDK.targetManager.models(WebAudio.WebAudioModel)) { |
| this._removeEventListeners(model); |
| } |
| } |
| |
| /** |
| * @override |
| * @param {!WebAudio.WebAudioModel} webAudioModel |
| */ |
| modelAdded(webAudioModel) { |
| if (this.isShowing()) { |
| this._addEventListeners(webAudioModel); |
| } |
| } |
| |
| /** |
| * @override |
| * @param {!WebAudio.WebAudioModel} webAudioModel |
| */ |
| modelRemoved(webAudioModel) { |
| this._removeEventListeners(webAudioModel); |
| } |
| |
| /** |
| * @override |
| * @return {!Promise<?>} |
| */ |
| async doUpdate() { |
| await this._pollRealtimeData(); |
| this.update(); |
| } |
| |
| /** |
| * @param {!WebAudio.WebAudioModel} webAudioModel |
| */ |
| _addEventListeners(webAudioModel) { |
| webAudioModel.ensureEnabled(); |
| webAudioModel.addEventListener(WebAudio.WebAudioModel.Events.ContextCreated, this._contextCreated, this); |
| webAudioModel.addEventListener(WebAudio.WebAudioModel.Events.ContextDestroyed, this._contextDestroyed, this); |
| webAudioModel.addEventListener(WebAudio.WebAudioModel.Events.ContextChanged, this._contextChanged, this); |
| webAudioModel.addEventListener(WebAudio.WebAudioModel.Events.ModelReset, this._reset, this); |
| webAudioModel.addEventListener(WebAudio.WebAudioModel.Events.ModelSuspend, this._suspendModel, this); |
| webAudioModel.addEventListener( |
| WebAudio.WebAudioModel.Events.AudioListenerCreated, this._audioListenerCreated, this); |
| webAudioModel.addEventListener( |
| WebAudio.WebAudioModel.Events.AudioListenerWillBeDestroyed, this._audioListenerWillBeDestroyed, this); |
| webAudioModel.addEventListener(WebAudio.WebAudioModel.Events.AudioNodeCreated, this._audioNodeCreated, this); |
| webAudioModel.addEventListener( |
| WebAudio.WebAudioModel.Events.AudioNodeWillBeDestroyed, this._audioNodeWillBeDestroyed, this); |
| webAudioModel.addEventListener(WebAudio.WebAudioModel.Events.AudioParamCreated, this._audioParamCreated, this); |
| webAudioModel.addEventListener( |
| WebAudio.WebAudioModel.Events.AudioParamWillBeDestroyed, this._audioParamWillBeDestroyed, this); |
| webAudioModel.addEventListener(WebAudio.WebAudioModel.Events.NodesConnected, this._nodesConnected, this); |
| webAudioModel.addEventListener(WebAudio.WebAudioModel.Events.NodesDisconnected, this._nodesDisconnected, this); |
| webAudioModel.addEventListener(WebAudio.WebAudioModel.Events.NodeParamConnected, this._nodeParamConnected, this); |
| webAudioModel.addEventListener( |
| WebAudio.WebAudioModel.Events.NodeParamDisconnected, this._nodeParamDisconnected, this); |
| } |
| |
| /** |
| * @param {!WebAudio.WebAudioModel} webAudioModel |
| */ |
| _removeEventListeners(webAudioModel) { |
| webAudioModel.removeEventListener(WebAudio.WebAudioModel.Events.ContextCreated, this._contextCreated, this); |
| webAudioModel.removeEventListener(WebAudio.WebAudioModel.Events.ContextDestroyed, this._contextDestroyed, this); |
| webAudioModel.removeEventListener(WebAudio.WebAudioModel.Events.ContextChanged, this._contextChanged, this); |
| webAudioModel.removeEventListener(WebAudio.WebAudioModel.Events.ModelReset, this._reset, this); |
| webAudioModel.removeEventListener(WebAudio.WebAudioModel.Events.ModelSuspend, this._suspendModel, this); |
| webAudioModel.removeEventListener( |
| WebAudio.WebAudioModel.Events.AudioListenerCreated, this._audioListenerCreated, this); |
| webAudioModel.removeEventListener( |
| WebAudio.WebAudioModel.Events.AudioListenerWillBeDestroyed, this._audioListenerWillBeDestroyed, this); |
| webAudioModel.removeEventListener(WebAudio.WebAudioModel.Events.AudioNodeCreated, this._audioNodeCreated, this); |
| webAudioModel.removeEventListener( |
| WebAudio.WebAudioModel.Events.AudioNodeWillBeDestroyed, this._audioNodeWillBeDestroyed, this); |
| webAudioModel.removeEventListener(WebAudio.WebAudioModel.Events.AudioParamCreated, this._audioParamCreated, this); |
| webAudioModel.removeEventListener( |
| WebAudio.WebAudioModel.Events.AudioParamWillBeDestroyed, this._audioParamWillBeDestroyed, this); |
| webAudioModel.removeEventListener(WebAudio.WebAudioModel.Events.NodesConnected, this._nodesConnected, this); |
| webAudioModel.removeEventListener(WebAudio.WebAudioModel.Events.NodesDisconnected, this._nodesDisconnected, this); |
| webAudioModel.removeEventListener(WebAudio.WebAudioModel.Events.NodeParamConnected, this._nodeParamConnected, this); |
| webAudioModel.removeEventListener( |
| WebAudio.WebAudioModel.Events.NodeParamDisconnected, this._nodeParamDisconnected, this); |
| } |
| |
| /** |
| * @param {!Common.Event} event |
| */ |
| _contextCreated(event) { |
| const context = /** @type {!Protocol.WebAudio.BaseAudioContext} */ (event.data); |
| this._graphManager.createContext(context.contextId); |
| this._contextSelector.contextCreated(event); |
| } |
| |
| /** |
| * @param {!Common.Event} event |
| */ |
| _contextDestroyed(event) { |
| const contextId = /** @type {!Protocol.WebAudio.GraphObjectId} */ (event.data); |
| this._graphManager.destroyContext(contextId); |
| this._contextSelector.contextDestroyed(event); |
| } |
| |
| /** |
| * @param {!Common.Event} event |
| */ |
| _contextChanged(event) { |
| const context = /** @type {!Protocol.WebAudio.BaseAudioContext} */ (event.data); |
| if (!this._graphManager.hasContext(context.contextId)) { |
| return; |
| } |
| |
| this._contextSelector.contextChanged(event); |
| } |
| |
| _reset() { |
| if (this._landingPage.isShowing()) { |
| this._landingPage.detach(); |
| } |
| this._contextSelector.reset(); |
| this._detailViewContainer.removeChildren(); |
| this._landingPage.show(this._detailViewContainer); |
| this._graphManager.clearGraphs(); |
| } |
| |
| _suspendModel() { |
| this._graphManager.clearGraphs(); |
| } |
| |
| /** |
| * @param {!Common.Event} event |
| */ |
| _audioListenerCreated(event) { |
| const listener = /** @type {!Protocol.WebAudio.AudioListener} */ (event.data); |
| const graph = this._graphManager.getGraph(listener.contextId); |
| if (!graph) { |
| return; |
| } |
| graph.addNode({ |
| nodeId: listener.listenerId, |
| nodeType: 'Listener', |
| numberOfInputs: 0, |
| numberOfOutputs: 0, |
| }); |
| } |
| |
| /** |
| * @param {!Common.Event} event |
| */ |
| _audioListenerWillBeDestroyed(event) { |
| const {contextId, listenerId} = event.data; |
| const graph = this._graphManager.getGraph(contextId); |
| if (!graph) { |
| return; |
| } |
| graph.removeNode(listenerId); |
| } |
| |
| /** |
| * @param {!Common.Event} event |
| */ |
| _audioNodeCreated(event) { |
| const node = /** @type {!Protocol.WebAudio.AudioNode} */ (event.data); |
| const graph = this._graphManager.getGraph(node.contextId); |
| if (!graph) { |
| return; |
| } |
| graph.addNode({ |
| nodeId: node.nodeId, |
| nodeType: node.nodeType, |
| numberOfInputs: node.numberOfInputs, |
| numberOfOutputs: node.numberOfOutputs, |
| }); |
| } |
| |
| /** |
| * @param {!Common.Event} event |
| */ |
| _audioNodeWillBeDestroyed(event) { |
| const {contextId, nodeId} = event.data; |
| const graph = this._graphManager.getGraph(contextId); |
| if (!graph) { |
| return; |
| } |
| graph.removeNode(nodeId); |
| } |
| |
| /** |
| * @param {!Common.Event} event |
| */ |
| _audioParamCreated(event) { |
| const param = /** @type {!Protocol.WebAudio.AudioParam} */ (event.data); |
| const graph = this._graphManager.getGraph(param.contextId); |
| if (!graph) { |
| return; |
| } |
| graph.addParam({ |
| paramId: param.paramId, |
| paramType: param.paramType, |
| nodeId: param.nodeId, |
| }); |
| } |
| |
| /** |
| * @param {!Common.Event} event |
| */ |
| _audioParamWillBeDestroyed(event) { |
| const {contextId, paramId} = event.data; |
| const graph = this._graphManager.getGraph(contextId); |
| if (!graph) { |
| return; |
| } |
| graph.removeParam(paramId); |
| } |
| |
| /** |
| * @param {!Common.Event} event |
| */ |
| _nodesConnected(event) { |
| const {contextId, sourceId, destinationId, sourceOutputIndex, destinationInputIndex} = event.data; |
| const graph = this._graphManager.getGraph(contextId); |
| if (!graph) { |
| return; |
| } |
| graph.addNodeToNodeConnection({ |
| sourceId, |
| destinationId, |
| sourceOutputIndex, |
| destinationInputIndex, |
| }); |
| } |
| |
| /** |
| * @param {!Common.Event} event |
| */ |
| _nodesDisconnected(event) { |
| const {contextId, sourceId, destinationId, sourceOutputIndex, destinationInputIndex} = event.data; |
| const graph = this._graphManager.getGraph(contextId); |
| if (!graph) { |
| return; |
| } |
| graph.removeNodeToNodeConnection({ |
| sourceId, |
| destinationId, |
| sourceOutputIndex, |
| destinationInputIndex, |
| }); |
| } |
| |
| /** |
| * @param {!Common.Event} event |
| */ |
| _nodeParamConnected(event) { |
| const {contextId, sourceId, destinationId, sourceOutputIndex} = event.data; |
| const graph = this._graphManager.getGraph(contextId); |
| if (!graph) { |
| return; |
| } |
| // Since the destinationId is AudioParamId, we need to find the nodeId as the |
| // real destinationId. |
| const nodeId = graph.getNodeIdByParamId(destinationId); |
| if (!nodeId) { |
| return; |
| } |
| graph.addNodeToParamConnection({ |
| sourceId, |
| destinationId: nodeId, |
| sourceOutputIndex, |
| destinationParamId: destinationId, |
| }); |
| } |
| |
| /** |
| * @param {!Common.Event} event |
| */ |
| _nodeParamDisconnected(event) { |
| const {contextId, sourceId, destinationId, sourceOutputIndex} = event.data; |
| const graph = this._graphManager.getGraph(contextId); |
| if (!graph) { |
| return; |
| } |
| // Since the destinationId is AudioParamId, we need to find the nodeId as the |
| // real destinationId. |
| const nodeId = graph.getNodeIdByParamId(destinationId); |
| if (!nodeId) { |
| return; |
| } |
| graph.removeNodeToParamConnection({ |
| sourceId, |
| destinationId: nodeId, |
| sourceOutputIndex, |
| destinationParamId: destinationId, |
| }); |
| } |
| |
| /** |
| * @param {!Protocol.WebAudio.BaseAudioContext} context |
| */ |
| _updateDetailView(context) { |
| if (this._landingPage.isShowing()) { |
| this._landingPage.detach(); |
| } |
| const detailBuilder = new WebAudio.ContextDetailBuilder(context); |
| this._detailViewContainer.removeChildren(); |
| this._detailViewContainer.appendChild(detailBuilder.getFragment()); |
| } |
| |
| /** |
| * @param {!Protocol.WebAudio.GraphObjectId} contextId |
| * @param {!Protocol.WebAudio.ContextRealtimeData} contextRealtimeData |
| */ |
| _updateSummaryBar(contextId, contextRealtimeData) { |
| const summaryBuilder = |
| new WebAudio.AudioContextSummaryBuilder(contextId, contextRealtimeData); |
| this._summaryBarContainer.removeChildren(); |
| this._summaryBarContainer.appendChild(summaryBuilder.getFragment()); |
| } |
| |
| _clearSummaryBar() { |
| this._summaryBarContainer.removeChildren(); |
| } |
| |
| async _pollRealtimeData() { |
| const context = this._contextSelector.selectedContext(); |
| if (!context) { |
| this._clearSummaryBar(); |
| return; |
| } |
| |
| for (const model of SDK.targetManager.models(WebAudio.WebAudioModel)) { |
| // Display summary only for real-time context. |
| if (context.contextType === 'realtime') { |
| if (!this._graphManager.hasContext(context.contextId)) { |
| continue; |
| } |
| const realtimeData = await model.requestRealtimeData(context.contextId); |
| if (realtimeData) { |
| this._updateSummaryBar(context.contextId, realtimeData); |
| } |
| } else { |
| this._clearSummaryBar(); |
| } |
| } |
| } |
| } |
| |
| /* Legacy exported object */ |
| self.WebAudio = self.WebAudio || {}; |
| |
| /* Legacy exported object */ |
| WebAudio = WebAudio || {}; |
| |
| /** |
| * @constructor |
| */ |
| WebAudio.WebAudioView = WebAudioView; |