blob: 7dba4cc550e904a420ec532ae27e1d8d37508ca5 [file] [log] [blame]
Hongchan Choi7dd0b3e2019-05-13 21:19:031// Copyright 2019 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
Hongchan Choi0927e702020-01-15 20:13:375import {ContextDetailBuilder, ContextSummaryBuilder} from './AudioContextContentBuilder.js';
6import {AudioContextSelector, Events as SelectorEvents} from './AudioContextSelector.js';
7import {GraphManager} from './graph_visualizer/GraphManager.js';
8import {Events as ModelEvents, WebAudioModel} from './WebAudioModel.js';
9
Hongchan Choi7dd0b3e2019-05-13 21:19:0310/**
Hongchan Choi0927e702020-01-15 20:13:3711 * @implements {SDK.SDKModelObserver<!WebAudioModel>}
Hongchan Choi7dd0b3e2019-05-13 21:19:0312 */
Paul Lewis2766f082019-11-28 13:42:5713export class WebAudioView extends UI.ThrottledWidget {
Hongchan Choi7dd0b3e2019-05-13 21:19:0314 constructor() {
15 super(true, 1000);
16 this.element.classList.add('web-audio-drawer');
17 this.registerRequiredCSS('web_audio/webAudio.css');
18
19 // Creates the toolbar.
Hongchan Choi0927e702020-01-15 20:13:3720 const toolbarContainer = this.contentElement.createChild('div', 'web-audio-toolbar-container vbox');
21 this._contextSelector = new AudioContextSelector();
Hongchan Choi7dd0b3e2019-05-13 21:19:0322 const toolbar = new UI.Toolbar('web-audio-toolbar', toolbarContainer);
23 toolbar.appendToolbarItem(UI.Toolbar.createActionButtonForId('components.collect-garbage'));
24 toolbar.appendSeparator();
25 toolbar.appendToolbarItem(this._contextSelector.toolbarItem());
26
27 // Creates the detail view.
28 this._detailViewContainer = this.contentElement.createChild('div', 'vbox flex-auto');
29
Hongchan Choi0927e702020-01-15 20:13:3730 this._graphManager = new GraphManager();
Gaoping Huanga471b302019-08-23 16:48:2931
Hongchan Choi7dd0b3e2019-05-13 21:19:0332 // Creates the landing page.
33 this._landingPage = new UI.VBox();
34 this._landingPage.contentElement.classList.add('web-audio-landing-page', 'fill');
35 this._landingPage.contentElement.appendChild(UI.html`
36 <div>
37 <p>${ls`Open a page that uses Web Audio API to start monitoring.`}</p>
38 </div>
39 `);
40 this._landingPage.show(this._detailViewContainer);
41
42 // Creates the summary bar.
43 this._summaryBarContainer = this.contentElement.createChild('div', 'web-audio-summary-container');
44
Hongchan Choi0927e702020-01-15 20:13:3745 this._contextSelector.addEventListener(SelectorEvents.ContextSelected, event => {
Hongchan Choi7dd0b3e2019-05-13 21:19:0346 const context =
47 /** @type {!Protocol.WebAudio.BaseAudioContext} */ (event.data);
48 this._updateDetailView(context);
49 this.doUpdate();
50 });
51
Hongchan Choi0927e702020-01-15 20:13:3752 SDK.targetManager.observeModels(WebAudioModel, this);
Hongchan Choi7dd0b3e2019-05-13 21:19:0353 }
54
55 /**
56 * @override
57 */
58 wasShown() {
Hongchan Choide21c962019-06-06 22:15:0859 super.wasShown();
Hongchan Choi0927e702020-01-15 20:13:3760 for (const model of SDK.targetManager.models(WebAudioModel)) {
Hongchan Choi7dd0b3e2019-05-13 21:19:0361 this._addEventListeners(model);
Tim van der Lippe1d6e57a2019-09-30 11:55:3462 }
Hongchan Choi7dd0b3e2019-05-13 21:19:0363 }
64
65 /**
66 * @override
67 */
68 willHide() {
Hongchan Choi0927e702020-01-15 20:13:3769 for (const model of SDK.targetManager.models(WebAudioModel)) {
Hongchan Choi7dd0b3e2019-05-13 21:19:0370 this._removeEventListeners(model);
Tim van der Lippe1d6e57a2019-09-30 11:55:3471 }
Hongchan Choi7dd0b3e2019-05-13 21:19:0372 }
73
74 /**
75 * @override
Hongchan Choi0927e702020-01-15 20:13:3776 * @param {!WebAudioModel} webAudioModel
Hongchan Choi7dd0b3e2019-05-13 21:19:0377 */
78 modelAdded(webAudioModel) {
Tim van der Lippe1d6e57a2019-09-30 11:55:3479 if (this.isShowing()) {
Hongchan Choi7dd0b3e2019-05-13 21:19:0380 this._addEventListeners(webAudioModel);
Tim van der Lippe1d6e57a2019-09-30 11:55:3481 }
Hongchan Choi7dd0b3e2019-05-13 21:19:0382 }
83
84 /**
85 * @override
Hongchan Choi0927e702020-01-15 20:13:3786 * @param {!WebAudioModel} webAudioModel
Hongchan Choi7dd0b3e2019-05-13 21:19:0387 */
88 modelRemoved(webAudioModel) {
89 this._removeEventListeners(webAudioModel);
90 }
91
92 /**
93 * @override
94 * @return {!Promise<?>}
95 */
96 async doUpdate() {
97 await this._pollRealtimeData();
98 this.update();
99 }
100
101 /**
Hongchan Choi0927e702020-01-15 20:13:37102 * @param {!WebAudioModel} webAudioModel
Hongchan Choi7dd0b3e2019-05-13 21:19:03103 */
104 _addEventListeners(webAudioModel) {
105 webAudioModel.ensureEnabled();
Hongchan Choi0927e702020-01-15 20:13:37106 webAudioModel.addEventListener(ModelEvents.ContextCreated, this._contextCreated, this);
107 webAudioModel.addEventListener(ModelEvents.ContextDestroyed, this._contextDestroyed, this);
108 webAudioModel.addEventListener(ModelEvents.ContextChanged, this._contextChanged, this);
109 webAudioModel.addEventListener(ModelEvents.ModelReset, this._reset, this);
110 webAudioModel.addEventListener(ModelEvents.ModelSuspend, this._suspendModel, this);
111 webAudioModel.addEventListener(ModelEvents.AudioListenerCreated, this._audioListenerCreated, this);
112 webAudioModel.addEventListener(ModelEvents.AudioListenerWillBeDestroyed, this._audioListenerWillBeDestroyed, this);
113 webAudioModel.addEventListener(ModelEvents.AudioNodeCreated, this._audioNodeCreated, this);
114 webAudioModel.addEventListener(ModelEvents.AudioNodeWillBeDestroyed, this._audioNodeWillBeDestroyed, this);
115 webAudioModel.addEventListener(ModelEvents.AudioParamCreated, this._audioParamCreated, this);
116 webAudioModel.addEventListener(ModelEvents.AudioParamWillBeDestroyed, this._audioParamWillBeDestroyed, this);
117 webAudioModel.addEventListener(ModelEvents.NodesConnected, this._nodesConnected, this);
118 webAudioModel.addEventListener(ModelEvents.NodesDisconnected, this._nodesDisconnected, this);
119 webAudioModel.addEventListener(ModelEvents.NodeParamConnected, this._nodeParamConnected, this);
120 webAudioModel.addEventListener(ModelEvents.NodeParamDisconnected, this._nodeParamDisconnected, this);
Hongchan Choi7dd0b3e2019-05-13 21:19:03121 }
122
123 /**
124 * @param {!WebAudio.WebAudioModel} webAudioModel
125 */
126 _removeEventListeners(webAudioModel) {
Hongchan Choi0927e702020-01-15 20:13:37127 webAudioModel.removeEventListener(ModelEvents.ContextCreated, this._contextCreated, this);
128 webAudioModel.removeEventListener(ModelEvents.ContextDestroyed, this._contextDestroyed, this);
129 webAudioModel.removeEventListener(ModelEvents.ContextChanged, this._contextChanged, this);
130 webAudioModel.removeEventListener(ModelEvents.ModelReset, this._reset, this);
131 webAudioModel.removeEventListener(ModelEvents.ModelSuspend, this._suspendModel, this);
132 webAudioModel.removeEventListener(ModelEvents.AudioListenerCreated, this._audioListenerCreated, this);
Gaoping Huanga471b302019-08-23 16:48:29133 webAudioModel.removeEventListener(
Hongchan Choi0927e702020-01-15 20:13:37134 ModelEvents.AudioListenerWillBeDestroyed, this._audioListenerWillBeDestroyed, this);
135 webAudioModel.removeEventListener(ModelEvents.AudioNodeCreated, this._audioNodeCreated, this);
136 webAudioModel.removeEventListener(ModelEvents.AudioNodeWillBeDestroyed, this._audioNodeWillBeDestroyed, this);
137 webAudioModel.removeEventListener(ModelEvents.AudioParamCreated, this._audioParamCreated, this);
138 webAudioModel.removeEventListener(ModelEvents.AudioParamWillBeDestroyed, this._audioParamWillBeDestroyed, this);
139 webAudioModel.removeEventListener(ModelEvents.NodesConnected, this._nodesConnected, this);
140 webAudioModel.removeEventListener(ModelEvents.NodesDisconnected, this._nodesDisconnected, this);
141 webAudioModel.removeEventListener(ModelEvents.NodeParamConnected, this._nodeParamConnected, this);
142 webAudioModel.removeEventListener(ModelEvents.NodeParamDisconnected, this._nodeParamDisconnected, this);
Hongchan Choi7dd0b3e2019-05-13 21:19:03143 }
144
145 /**
146 * @param {!Common.Event} event
147 */
148 _contextCreated(event) {
Gaoping Huanga471b302019-08-23 16:48:29149 const context = /** @type {!Protocol.WebAudio.BaseAudioContext} */ (event.data);
150 this._graphManager.createContext(context.contextId);
Hongchan Choi7dd0b3e2019-05-13 21:19:03151 this._contextSelector.contextCreated(event);
152 }
153
154 /**
155 * @param {!Common.Event} event
156 */
157 _contextDestroyed(event) {
Gaoping Huanga471b302019-08-23 16:48:29158 const contextId = /** @type {!Protocol.WebAudio.GraphObjectId} */ (event.data);
159 this._graphManager.destroyContext(contextId);
Hongchan Choi7dd0b3e2019-05-13 21:19:03160 this._contextSelector.contextDestroyed(event);
161 }
162
163 /**
164 * @param {!Common.Event} event
165 */
166 _contextChanged(event) {
Gaoping Huanga471b302019-08-23 16:48:29167 const context = /** @type {!Protocol.WebAudio.BaseAudioContext} */ (event.data);
Tim van der Lippe1d6e57a2019-09-30 11:55:34168 if (!this._graphManager.hasContext(context.contextId)) {
Gaoping Huanga471b302019-08-23 16:48:29169 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:34170 }
Gaoping Huanga471b302019-08-23 16:48:29171
Hongchan Choi7dd0b3e2019-05-13 21:19:03172 this._contextSelector.contextChanged(event);
173 }
174
175 _reset() {
Tim van der Lippe1d6e57a2019-09-30 11:55:34176 if (this._landingPage.isShowing()) {
Hongchan Choi7dd0b3e2019-05-13 21:19:03177 this._landingPage.detach();
Tim van der Lippe1d6e57a2019-09-30 11:55:34178 }
Hongchan Choi7dd0b3e2019-05-13 21:19:03179 this._contextSelector.reset();
180 this._detailViewContainer.removeChildren();
181 this._landingPage.show(this._detailViewContainer);
Gaoping Huanga471b302019-08-23 16:48:29182 this._graphManager.clearGraphs();
183 }
184
185 _suspendModel() {
186 this._graphManager.clearGraphs();
187 }
188
189 /**
190 * @param {!Common.Event} event
191 */
192 _audioListenerCreated(event) {
193 const listener = /** @type {!Protocol.WebAudio.AudioListener} */ (event.data);
194 const graph = this._graphManager.getGraph(listener.contextId);
Tim van der Lippe1d6e57a2019-09-30 11:55:34195 if (!graph) {
Gaoping Huanga471b302019-08-23 16:48:29196 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:34197 }
Gaoping Huanga471b302019-08-23 16:48:29198 graph.addNode({
199 nodeId: listener.listenerId,
200 nodeType: 'Listener',
201 numberOfInputs: 0,
202 numberOfOutputs: 0,
203 });
204 }
205
206 /**
207 * @param {!Common.Event} event
208 */
209 _audioListenerWillBeDestroyed(event) {
210 const {contextId, listenerId} = event.data;
211 const graph = this._graphManager.getGraph(contextId);
Tim van der Lippe1d6e57a2019-09-30 11:55:34212 if (!graph) {
Gaoping Huanga471b302019-08-23 16:48:29213 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:34214 }
Gaoping Huanga471b302019-08-23 16:48:29215 graph.removeNode(listenerId);
216 }
217
218 /**
219 * @param {!Common.Event} event
220 */
221 _audioNodeCreated(event) {
222 const node = /** @type {!Protocol.WebAudio.AudioNode} */ (event.data);
223 const graph = this._graphManager.getGraph(node.contextId);
Tim van der Lippe1d6e57a2019-09-30 11:55:34224 if (!graph) {
Gaoping Huanga471b302019-08-23 16:48:29225 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:34226 }
Gaoping Huanga471b302019-08-23 16:48:29227 graph.addNode({
228 nodeId: node.nodeId,
229 nodeType: node.nodeType,
230 numberOfInputs: node.numberOfInputs,
231 numberOfOutputs: node.numberOfOutputs,
232 });
233 }
234
235 /**
236 * @param {!Common.Event} event
237 */
238 _audioNodeWillBeDestroyed(event) {
239 const {contextId, nodeId} = event.data;
240 const graph = this._graphManager.getGraph(contextId);
Tim van der Lippe1d6e57a2019-09-30 11:55:34241 if (!graph) {
Gaoping Huanga471b302019-08-23 16:48:29242 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:34243 }
Gaoping Huanga471b302019-08-23 16:48:29244 graph.removeNode(nodeId);
245 }
246
247 /**
248 * @param {!Common.Event} event
249 */
250 _audioParamCreated(event) {
251 const param = /** @type {!Protocol.WebAudio.AudioParam} */ (event.data);
252 const graph = this._graphManager.getGraph(param.contextId);
Tim van der Lippe1d6e57a2019-09-30 11:55:34253 if (!graph) {
Gaoping Huanga471b302019-08-23 16:48:29254 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:34255 }
Gaoping Huanga471b302019-08-23 16:48:29256 graph.addParam({
257 paramId: param.paramId,
258 paramType: param.paramType,
259 nodeId: param.nodeId,
260 });
261 }
262
263 /**
264 * @param {!Common.Event} event
265 */
266 _audioParamWillBeDestroyed(event) {
267 const {contextId, paramId} = event.data;
268 const graph = this._graphManager.getGraph(contextId);
Tim van der Lippe1d6e57a2019-09-30 11:55:34269 if (!graph) {
Gaoping Huanga471b302019-08-23 16:48:29270 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:34271 }
Gaoping Huanga471b302019-08-23 16:48:29272 graph.removeParam(paramId);
273 }
274
275 /**
276 * @param {!Common.Event} event
277 */
278 _nodesConnected(event) {
279 const {contextId, sourceId, destinationId, sourceOutputIndex, destinationInputIndex} = event.data;
280 const graph = this._graphManager.getGraph(contextId);
Tim van der Lippe1d6e57a2019-09-30 11:55:34281 if (!graph) {
Gaoping Huanga471b302019-08-23 16:48:29282 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:34283 }
Gaoping Huanga471b302019-08-23 16:48:29284 graph.addNodeToNodeConnection({
285 sourceId,
286 destinationId,
287 sourceOutputIndex,
288 destinationInputIndex,
289 });
290 }
291
292 /**
293 * @param {!Common.Event} event
294 */
295 _nodesDisconnected(event) {
296 const {contextId, sourceId, destinationId, sourceOutputIndex, destinationInputIndex} = event.data;
297 const graph = this._graphManager.getGraph(contextId);
Tim van der Lippe1d6e57a2019-09-30 11:55:34298 if (!graph) {
Gaoping Huanga471b302019-08-23 16:48:29299 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:34300 }
Gaoping Huanga471b302019-08-23 16:48:29301 graph.removeNodeToNodeConnection({
302 sourceId,
303 destinationId,
304 sourceOutputIndex,
305 destinationInputIndex,
306 });
307 }
308
309 /**
310 * @param {!Common.Event} event
311 */
312 _nodeParamConnected(event) {
313 const {contextId, sourceId, destinationId, sourceOutputIndex} = event.data;
314 const graph = this._graphManager.getGraph(contextId);
Tim van der Lippe1d6e57a2019-09-30 11:55:34315 if (!graph) {
Gaoping Huanga471b302019-08-23 16:48:29316 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:34317 }
Gaoping Huanga471b302019-08-23 16:48:29318 // Since the destinationId is AudioParamId, we need to find the nodeId as the
319 // real destinationId.
320 const nodeId = graph.getNodeIdByParamId(destinationId);
Tim van der Lippe1d6e57a2019-09-30 11:55:34321 if (!nodeId) {
Gaoping Huanga471b302019-08-23 16:48:29322 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:34323 }
Gaoping Huanga471b302019-08-23 16:48:29324 graph.addNodeToParamConnection({
325 sourceId,
326 destinationId: nodeId,
327 sourceOutputIndex,
328 destinationParamId: destinationId,
329 });
330 }
331
332 /**
333 * @param {!Common.Event} event
334 */
335 _nodeParamDisconnected(event) {
336 const {contextId, sourceId, destinationId, sourceOutputIndex} = event.data;
337 const graph = this._graphManager.getGraph(contextId);
Tim van der Lippe1d6e57a2019-09-30 11:55:34338 if (!graph) {
Gaoping Huanga471b302019-08-23 16:48:29339 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:34340 }
Gaoping Huanga471b302019-08-23 16:48:29341 // Since the destinationId is AudioParamId, we need to find the nodeId as the
342 // real destinationId.
343 const nodeId = graph.getNodeIdByParamId(destinationId);
Tim van der Lippe1d6e57a2019-09-30 11:55:34344 if (!nodeId) {
Gaoping Huanga471b302019-08-23 16:48:29345 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:34346 }
Gaoping Huanga471b302019-08-23 16:48:29347 graph.removeNodeToParamConnection({
348 sourceId,
349 destinationId: nodeId,
350 sourceOutputIndex,
351 destinationParamId: destinationId,
352 });
Hongchan Choi7dd0b3e2019-05-13 21:19:03353 }
354
355 /**
356 * @param {!Protocol.WebAudio.BaseAudioContext} context
357 */
358 _updateDetailView(context) {
Tim van der Lippe1d6e57a2019-09-30 11:55:34359 if (this._landingPage.isShowing()) {
Hongchan Choi7dd0b3e2019-05-13 21:19:03360 this._landingPage.detach();
Tim van der Lippe1d6e57a2019-09-30 11:55:34361 }
Hongchan Choi0927e702020-01-15 20:13:37362 const detailBuilder = new ContextDetailBuilder(context);
Hongchan Choi7dd0b3e2019-05-13 21:19:03363 this._detailViewContainer.removeChildren();
364 this._detailViewContainer.appendChild(detailBuilder.getFragment());
365 }
366
367 /**
Hongchan Choi65088332019-08-08 01:12:33368 * @param {!Protocol.WebAudio.GraphObjectId} contextId
Hongchan Choi7dd0b3e2019-05-13 21:19:03369 * @param {!Protocol.WebAudio.ContextRealtimeData} contextRealtimeData
370 */
371 _updateSummaryBar(contextId, contextRealtimeData) {
Hongchan Choi0927e702020-01-15 20:13:37372 const summaryBuilder = new ContextSummaryBuilder(contextId, contextRealtimeData);
Hongchan Choi7dd0b3e2019-05-13 21:19:03373 this._summaryBarContainer.removeChildren();
374 this._summaryBarContainer.appendChild(summaryBuilder.getFragment());
375 }
376
377 _clearSummaryBar() {
378 this._summaryBarContainer.removeChildren();
379 }
380
381 async _pollRealtimeData() {
382 const context = this._contextSelector.selectedContext();
383 if (!context) {
384 this._clearSummaryBar();
385 return;
386 }
387
388 for (const model of SDK.targetManager.models(WebAudio.WebAudioModel)) {
389 // Display summary only for real-time context.
390 if (context.contextType === 'realtime') {
Tim van der Lippe1d6e57a2019-09-30 11:55:34391 if (!this._graphManager.hasContext(context.contextId)) {
Gaoping Huanga471b302019-08-23 16:48:29392 continue;
Tim van der Lippe1d6e57a2019-09-30 11:55:34393 }
Hongchan Choi7dd0b3e2019-05-13 21:19:03394 const realtimeData = await model.requestRealtimeData(context.contextId);
Tim van der Lippe1d6e57a2019-09-30 11:55:34395 if (realtimeData) {
Hongchan Choi7dd0b3e2019-05-13 21:19:03396 this._updateSummaryBar(context.contextId, realtimeData);
Tim van der Lippe1d6e57a2019-09-30 11:55:34397 }
Hongchan Choi7dd0b3e2019-05-13 21:19:03398 } else {
399 this._clearSummaryBar();
400 }
401 }
402 }
Paul Lewis2766f082019-11-28 13:42:57403}