blob: 03108d4d2811c5e4602afcedb70a3e75cd11882d [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
5/**
6 * @implements {SDK.SDKModelObserver<!WebAudio.WebAudioModel>}
7 */
8WebAudio.WebAudioView = class extends UI.ThrottledWidget {
9 constructor() {
10 super(true, 1000);
11 this.element.classList.add('web-audio-drawer');
12 this.registerRequiredCSS('web_audio/webAudio.css');
13
14 // Creates the toolbar.
15 const toolbarContainer = this.contentElement.createChild(
16 'div', 'web-audio-toolbar-container vbox');
17 this._contextSelector = new WebAudio.AudioContextSelector(ls`BaseAudioContexts`);
18 const toolbar = new UI.Toolbar('web-audio-toolbar', toolbarContainer);
19 toolbar.appendToolbarItem(UI.Toolbar.createActionButtonForId('components.collect-garbage'));
20 toolbar.appendSeparator();
21 toolbar.appendToolbarItem(this._contextSelector.toolbarItem());
22
23 // Creates the detail view.
24 this._detailViewContainer = this.contentElement.createChild('div', 'vbox flex-auto');
25
Gaoping Huanga471b302019-08-23 16:48:2926 this._graphManager = new WebAudio.GraphVisualizer.GraphManager();
27
Hongchan Choi7dd0b3e2019-05-13 21:19:0328 // Creates the landing page.
29 this._landingPage = new UI.VBox();
30 this._landingPage.contentElement.classList.add('web-audio-landing-page', 'fill');
31 this._landingPage.contentElement.appendChild(UI.html`
32 <div>
33 <p>${ls`Open a page that uses Web Audio API to start monitoring.`}</p>
34 </div>
35 `);
36 this._landingPage.show(this._detailViewContainer);
37
38 // Creates the summary bar.
39 this._summaryBarContainer = this.contentElement.createChild('div', 'web-audio-summary-container');
40
41 this._contextSelector.addEventListener(WebAudio.AudioContextSelector.Events.ContextSelected, event => {
42 const context =
43 /** @type {!Protocol.WebAudio.BaseAudioContext} */ (event.data);
44 this._updateDetailView(context);
45 this.doUpdate();
46 });
47
48 SDK.targetManager.observeModels(WebAudio.WebAudioModel, this);
49 }
50
51 /**
52 * @override
53 */
54 wasShown() {
Hongchan Choide21c962019-06-06 22:15:0855 super.wasShown();
Tim van der Lippe1d6e57a2019-09-30 11:55:3456 for (const model of SDK.targetManager.models(WebAudio.WebAudioModel)) {
Hongchan Choi7dd0b3e2019-05-13 21:19:0357 this._addEventListeners(model);
Tim van der Lippe1d6e57a2019-09-30 11:55:3458 }
Hongchan Choi7dd0b3e2019-05-13 21:19:0359 }
60
61 /**
62 * @override
63 */
64 willHide() {
Tim van der Lippe1d6e57a2019-09-30 11:55:3465 for (const model of SDK.targetManager.models(WebAudio.WebAudioModel)) {
Hongchan Choi7dd0b3e2019-05-13 21:19:0366 this._removeEventListeners(model);
Tim van der Lippe1d6e57a2019-09-30 11:55:3467 }
Hongchan Choi7dd0b3e2019-05-13 21:19:0368 }
69
70 /**
71 * @override
72 * @param {!WebAudio.WebAudioModel} webAudioModel
73 */
74 modelAdded(webAudioModel) {
Tim van der Lippe1d6e57a2019-09-30 11:55:3475 if (this.isShowing()) {
Hongchan Choi7dd0b3e2019-05-13 21:19:0376 this._addEventListeners(webAudioModel);
Tim van der Lippe1d6e57a2019-09-30 11:55:3477 }
Hongchan Choi7dd0b3e2019-05-13 21:19:0378 }
79
80 /**
81 * @override
82 * @param {!WebAudio.WebAudioModel} webAudioModel
83 */
84 modelRemoved(webAudioModel) {
85 this._removeEventListeners(webAudioModel);
86 }
87
88 /**
89 * @override
90 * @return {!Promise<?>}
91 */
92 async doUpdate() {
93 await this._pollRealtimeData();
94 this.update();
95 }
96
97 /**
98 * @param {!WebAudio.WebAudioModel} webAudioModel
99 */
100 _addEventListeners(webAudioModel) {
101 webAudioModel.ensureEnabled();
102 webAudioModel.addEventListener(WebAudio.WebAudioModel.Events.ContextCreated, this._contextCreated, this);
103 webAudioModel.addEventListener(WebAudio.WebAudioModel.Events.ContextDestroyed, this._contextDestroyed, this);
104 webAudioModel.addEventListener(WebAudio.WebAudioModel.Events.ContextChanged, this._contextChanged, this);
Hongchan Choi2aae57a2019-05-23 20:42:41105 webAudioModel.addEventListener(WebAudio.WebAudioModel.Events.ModelReset, this._reset, this);
Gaoping Huanga471b302019-08-23 16:48:29106 webAudioModel.addEventListener(WebAudio.WebAudioModel.Events.ModelSuspend, this._suspendModel, this);
107 webAudioModel.addEventListener(
108 WebAudio.WebAudioModel.Events.AudioListenerCreated, this._audioListenerCreated, this);
109 webAudioModel.addEventListener(
110 WebAudio.WebAudioModel.Events.AudioListenerWillBeDestroyed, this._audioListenerWillBeDestroyed, this);
111 webAudioModel.addEventListener(WebAudio.WebAudioModel.Events.AudioNodeCreated, this._audioNodeCreated, this);
112 webAudioModel.addEventListener(
113 WebAudio.WebAudioModel.Events.AudioNodeWillBeDestroyed, this._audioNodeWillBeDestroyed, this);
114 webAudioModel.addEventListener(WebAudio.WebAudioModel.Events.AudioParamCreated, this._audioParamCreated, this);
115 webAudioModel.addEventListener(
116 WebAudio.WebAudioModel.Events.AudioParamWillBeDestroyed, this._audioParamWillBeDestroyed, this);
117 webAudioModel.addEventListener(WebAudio.WebAudioModel.Events.NodesConnected, this._nodesConnected, this);
118 webAudioModel.addEventListener(WebAudio.WebAudioModel.Events.NodesDisconnected, this._nodesDisconnected, this);
119 webAudioModel.addEventListener(WebAudio.WebAudioModel.Events.NodeParamConnected, this._nodeParamConnected, this);
120 webAudioModel.addEventListener(
121 WebAudio.WebAudioModel.Events.NodeParamDisconnected, this._nodeParamDisconnected, this);
Hongchan Choi7dd0b3e2019-05-13 21:19:03122 }
123
124 /**
125 * @param {!WebAudio.WebAudioModel} webAudioModel
126 */
127 _removeEventListeners(webAudioModel) {
128 webAudioModel.removeEventListener(WebAudio.WebAudioModel.Events.ContextCreated, this._contextCreated, this);
129 webAudioModel.removeEventListener(WebAudio.WebAudioModel.Events.ContextDestroyed, this._contextDestroyed, this);
130 webAudioModel.removeEventListener(WebAudio.WebAudioModel.Events.ContextChanged, this._contextChanged, this);
Hongchan Choi2aae57a2019-05-23 20:42:41131 webAudioModel.removeEventListener(WebAudio.WebAudioModel.Events.ModelReset, this._reset, this);
Gaoping Huanga471b302019-08-23 16:48:29132 webAudioModel.removeEventListener(WebAudio.WebAudioModel.Events.ModelSuspend, this._suspendModel, this);
133 webAudioModel.removeEventListener(
134 WebAudio.WebAudioModel.Events.AudioListenerCreated, this._audioListenerCreated, this);
135 webAudioModel.removeEventListener(
136 WebAudio.WebAudioModel.Events.AudioListenerWillBeDestroyed, this._audioListenerWillBeDestroyed, this);
137 webAudioModel.removeEventListener(WebAudio.WebAudioModel.Events.AudioNodeCreated, this._audioNodeCreated, this);
138 webAudioModel.removeEventListener(
139 WebAudio.WebAudioModel.Events.AudioNodeWillBeDestroyed, this._audioNodeWillBeDestroyed, this);
140 webAudioModel.removeEventListener(WebAudio.WebAudioModel.Events.AudioParamCreated, this._audioParamCreated, this);
141 webAudioModel.removeEventListener(
142 WebAudio.WebAudioModel.Events.AudioParamWillBeDestroyed, this._audioParamWillBeDestroyed, this);
143 webAudioModel.removeEventListener(WebAudio.WebAudioModel.Events.NodesConnected, this._nodesConnected, this);
144 webAudioModel.removeEventListener(WebAudio.WebAudioModel.Events.NodesDisconnected, this._nodesDisconnected, this);
145 webAudioModel.removeEventListener(WebAudio.WebAudioModel.Events.NodeParamConnected, this._nodeParamConnected, this);
146 webAudioModel.removeEventListener(
147 WebAudio.WebAudioModel.Events.NodeParamDisconnected, this._nodeParamDisconnected, this);
Hongchan Choi7dd0b3e2019-05-13 21:19:03148 }
149
150 /**
151 * @param {!Common.Event} event
152 */
153 _contextCreated(event) {
Gaoping Huanga471b302019-08-23 16:48:29154 const context = /** @type {!Protocol.WebAudio.BaseAudioContext} */ (event.data);
155 this._graphManager.createContext(context.contextId);
Hongchan Choi7dd0b3e2019-05-13 21:19:03156 this._contextSelector.contextCreated(event);
157 }
158
159 /**
160 * @param {!Common.Event} event
161 */
162 _contextDestroyed(event) {
Gaoping Huanga471b302019-08-23 16:48:29163 const contextId = /** @type {!Protocol.WebAudio.GraphObjectId} */ (event.data);
164 this._graphManager.destroyContext(contextId);
Hongchan Choi7dd0b3e2019-05-13 21:19:03165 this._contextSelector.contextDestroyed(event);
166 }
167
168 /**
169 * @param {!Common.Event} event
170 */
171 _contextChanged(event) {
Gaoping Huanga471b302019-08-23 16:48:29172 const context = /** @type {!Protocol.WebAudio.BaseAudioContext} */ (event.data);
Tim van der Lippe1d6e57a2019-09-30 11:55:34173 if (!this._graphManager.hasContext(context.contextId)) {
Gaoping Huanga471b302019-08-23 16:48:29174 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:34175 }
Gaoping Huanga471b302019-08-23 16:48:29176
Hongchan Choi7dd0b3e2019-05-13 21:19:03177 this._contextSelector.contextChanged(event);
178 }
179
180 _reset() {
Tim van der Lippe1d6e57a2019-09-30 11:55:34181 if (this._landingPage.isShowing()) {
Hongchan Choi7dd0b3e2019-05-13 21:19:03182 this._landingPage.detach();
Tim van der Lippe1d6e57a2019-09-30 11:55:34183 }
Hongchan Choi7dd0b3e2019-05-13 21:19:03184 this._contextSelector.reset();
185 this._detailViewContainer.removeChildren();
186 this._landingPage.show(this._detailViewContainer);
Gaoping Huanga471b302019-08-23 16:48:29187 this._graphManager.clearGraphs();
188 }
189
190 _suspendModel() {
191 this._graphManager.clearGraphs();
192 }
193
194 /**
195 * @param {!Common.Event} event
196 */
197 _audioListenerCreated(event) {
198 const listener = /** @type {!Protocol.WebAudio.AudioListener} */ (event.data);
199 const graph = this._graphManager.getGraph(listener.contextId);
Tim van der Lippe1d6e57a2019-09-30 11:55:34200 if (!graph) {
Gaoping Huanga471b302019-08-23 16:48:29201 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:34202 }
Gaoping Huanga471b302019-08-23 16:48:29203 graph.addNode({
204 nodeId: listener.listenerId,
205 nodeType: 'Listener',
206 numberOfInputs: 0,
207 numberOfOutputs: 0,
208 });
209 }
210
211 /**
212 * @param {!Common.Event} event
213 */
214 _audioListenerWillBeDestroyed(event) {
215 const {contextId, listenerId} = event.data;
216 const graph = this._graphManager.getGraph(contextId);
Tim van der Lippe1d6e57a2019-09-30 11:55:34217 if (!graph) {
Gaoping Huanga471b302019-08-23 16:48:29218 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:34219 }
Gaoping Huanga471b302019-08-23 16:48:29220 graph.removeNode(listenerId);
221 }
222
223 /**
224 * @param {!Common.Event} event
225 */
226 _audioNodeCreated(event) {
227 const node = /** @type {!Protocol.WebAudio.AudioNode} */ (event.data);
228 const graph = this._graphManager.getGraph(node.contextId);
Tim van der Lippe1d6e57a2019-09-30 11:55:34229 if (!graph) {
Gaoping Huanga471b302019-08-23 16:48:29230 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:34231 }
Gaoping Huanga471b302019-08-23 16:48:29232 graph.addNode({
233 nodeId: node.nodeId,
234 nodeType: node.nodeType,
235 numberOfInputs: node.numberOfInputs,
236 numberOfOutputs: node.numberOfOutputs,
237 });
238 }
239
240 /**
241 * @param {!Common.Event} event
242 */
243 _audioNodeWillBeDestroyed(event) {
244 const {contextId, nodeId} = event.data;
245 const graph = this._graphManager.getGraph(contextId);
Tim van der Lippe1d6e57a2019-09-30 11:55:34246 if (!graph) {
Gaoping Huanga471b302019-08-23 16:48:29247 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:34248 }
Gaoping Huanga471b302019-08-23 16:48:29249 graph.removeNode(nodeId);
250 }
251
252 /**
253 * @param {!Common.Event} event
254 */
255 _audioParamCreated(event) {
256 const param = /** @type {!Protocol.WebAudio.AudioParam} */ (event.data);
257 const graph = this._graphManager.getGraph(param.contextId);
Tim van der Lippe1d6e57a2019-09-30 11:55:34258 if (!graph) {
Gaoping Huanga471b302019-08-23 16:48:29259 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:34260 }
Gaoping Huanga471b302019-08-23 16:48:29261 graph.addParam({
262 paramId: param.paramId,
263 paramType: param.paramType,
264 nodeId: param.nodeId,
265 });
266 }
267
268 /**
269 * @param {!Common.Event} event
270 */
271 _audioParamWillBeDestroyed(event) {
272 const {contextId, paramId} = event.data;
273 const graph = this._graphManager.getGraph(contextId);
Tim van der Lippe1d6e57a2019-09-30 11:55:34274 if (!graph) {
Gaoping Huanga471b302019-08-23 16:48:29275 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:34276 }
Gaoping Huanga471b302019-08-23 16:48:29277 graph.removeParam(paramId);
278 }
279
280 /**
281 * @param {!Common.Event} event
282 */
283 _nodesConnected(event) {
284 const {contextId, sourceId, destinationId, sourceOutputIndex, destinationInputIndex} = event.data;
285 const graph = this._graphManager.getGraph(contextId);
Tim van der Lippe1d6e57a2019-09-30 11:55:34286 if (!graph) {
Gaoping Huanga471b302019-08-23 16:48:29287 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:34288 }
Gaoping Huanga471b302019-08-23 16:48:29289 graph.addNodeToNodeConnection({
290 sourceId,
291 destinationId,
292 sourceOutputIndex,
293 destinationInputIndex,
294 });
295 }
296
297 /**
298 * @param {!Common.Event} event
299 */
300 _nodesDisconnected(event) {
301 const {contextId, sourceId, destinationId, sourceOutputIndex, destinationInputIndex} = event.data;
302 const graph = this._graphManager.getGraph(contextId);
Tim van der Lippe1d6e57a2019-09-30 11:55:34303 if (!graph) {
Gaoping Huanga471b302019-08-23 16:48:29304 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:34305 }
Gaoping Huanga471b302019-08-23 16:48:29306 graph.removeNodeToNodeConnection({
307 sourceId,
308 destinationId,
309 sourceOutputIndex,
310 destinationInputIndex,
311 });
312 }
313
314 /**
315 * @param {!Common.Event} event
316 */
317 _nodeParamConnected(event) {
318 const {contextId, sourceId, destinationId, sourceOutputIndex} = event.data;
319 const graph = this._graphManager.getGraph(contextId);
Tim van der Lippe1d6e57a2019-09-30 11:55:34320 if (!graph) {
Gaoping Huanga471b302019-08-23 16:48:29321 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:34322 }
Gaoping Huanga471b302019-08-23 16:48:29323 // Since the destinationId is AudioParamId, we need to find the nodeId as the
324 // real destinationId.
325 const nodeId = graph.getNodeIdByParamId(destinationId);
Tim van der Lippe1d6e57a2019-09-30 11:55:34326 if (!nodeId) {
Gaoping Huanga471b302019-08-23 16:48:29327 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:34328 }
Gaoping Huanga471b302019-08-23 16:48:29329 graph.addNodeToParamConnection({
330 sourceId,
331 destinationId: nodeId,
332 sourceOutputIndex,
333 destinationParamId: destinationId,
334 });
335 }
336
337 /**
338 * @param {!Common.Event} event
339 */
340 _nodeParamDisconnected(event) {
341 const {contextId, sourceId, destinationId, sourceOutputIndex} = event.data;
342 const graph = this._graphManager.getGraph(contextId);
Tim van der Lippe1d6e57a2019-09-30 11:55:34343 if (!graph) {
Gaoping Huanga471b302019-08-23 16:48:29344 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:34345 }
Gaoping Huanga471b302019-08-23 16:48:29346 // Since the destinationId is AudioParamId, we need to find the nodeId as the
347 // real destinationId.
348 const nodeId = graph.getNodeIdByParamId(destinationId);
Tim van der Lippe1d6e57a2019-09-30 11:55:34349 if (!nodeId) {
Gaoping Huanga471b302019-08-23 16:48:29350 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:34351 }
Gaoping Huanga471b302019-08-23 16:48:29352 graph.removeNodeToParamConnection({
353 sourceId,
354 destinationId: nodeId,
355 sourceOutputIndex,
356 destinationParamId: destinationId,
357 });
Hongchan Choi7dd0b3e2019-05-13 21:19:03358 }
359
360 /**
361 * @param {!Protocol.WebAudio.BaseAudioContext} context
362 */
363 _updateDetailView(context) {
Tim van der Lippe1d6e57a2019-09-30 11:55:34364 if (this._landingPage.isShowing()) {
Hongchan Choi7dd0b3e2019-05-13 21:19:03365 this._landingPage.detach();
Tim van der Lippe1d6e57a2019-09-30 11:55:34366 }
Hongchan Choi7dd0b3e2019-05-13 21:19:03367 const detailBuilder = new WebAudio.ContextDetailBuilder(context);
368 this._detailViewContainer.removeChildren();
369 this._detailViewContainer.appendChild(detailBuilder.getFragment());
370 }
371
372 /**
Hongchan Choi65088332019-08-08 01:12:33373 * @param {!Protocol.WebAudio.GraphObjectId} contextId
Hongchan Choi7dd0b3e2019-05-13 21:19:03374 * @param {!Protocol.WebAudio.ContextRealtimeData} contextRealtimeData
375 */
376 _updateSummaryBar(contextId, contextRealtimeData) {
377 const summaryBuilder =
378 new WebAudio.AudioContextSummaryBuilder(contextId, contextRealtimeData);
379 this._summaryBarContainer.removeChildren();
380 this._summaryBarContainer.appendChild(summaryBuilder.getFragment());
381 }
382
383 _clearSummaryBar() {
384 this._summaryBarContainer.removeChildren();
385 }
386
387 async _pollRealtimeData() {
388 const context = this._contextSelector.selectedContext();
389 if (!context) {
390 this._clearSummaryBar();
391 return;
392 }
393
394 for (const model of SDK.targetManager.models(WebAudio.WebAudioModel)) {
395 // Display summary only for real-time context.
396 if (context.contextType === 'realtime') {
Tim van der Lippe1d6e57a2019-09-30 11:55:34397 if (!this._graphManager.hasContext(context.contextId)) {
Gaoping Huanga471b302019-08-23 16:48:29398 continue;
Tim van der Lippe1d6e57a2019-09-30 11:55:34399 }
Hongchan Choi7dd0b3e2019-05-13 21:19:03400 const realtimeData = await model.requestRealtimeData(context.contextId);
Tim van der Lippe1d6e57a2019-09-30 11:55:34401 if (realtimeData) {
Hongchan Choi7dd0b3e2019-05-13 21:19:03402 this._updateSummaryBar(context.contextId, realtimeData);
Tim van der Lippe1d6e57a2019-09-30 11:55:34403 }
Hongchan Choi7dd0b3e2019-05-13 21:19:03404 } else {
405 this._clearSummaryBar();
406 }
407 }
408 }
409};