1 | import _defaults from "lodash.defaultsdeep";
|
2 | import h from "virtual-dom/h";
|
3 | import diff from "virtual-dom/diff";
|
4 | import patch from "virtual-dom/patch";
|
5 | import InlineWorker from "inline-worker";
|
6 | import { pixelsToSeconds } from "./utils/conversions";
|
7 | import LoaderFactory from "./track/loader/LoaderFactory";
|
8 | import ScrollHook from "./render/ScrollHook";
|
9 | import TimeScale from "./TimeScale";
|
10 | import Track from "./Track";
|
11 | import Playout from "./Playout";
|
12 | import AnnotationList from "./annotation/AnnotationList";
|
13 | import RecorderWorkerFunction from "./utils/recorderWorker";
|
14 | import ExportWavWorkerFunction from "./utils/exportWavWorker";
|
15 | export default class {
|
16 | constructor() {
|
17 | this.tracks = [];
|
18 | this.soloedTracks = [];
|
19 | this.mutedTracks = [];
|
20 | this.collapsedTracks = [];
|
21 | this.playoutPromises = [];
|
22 | this.cursor = 0;
|
23 | this.playbackSeconds = 0;
|
24 | this.duration = 0;
|
25 | this.scrollLeft = 0;
|
26 | this.scrollTimer = undefined;
|
27 | this.showTimescale = false;
|
28 |
|
29 | this.isScrolling = false;
|
30 | this.fadeType = "logarithmic";
|
31 | this.masterGain = 1;
|
32 | this.annotations = [];
|
33 | this.durationFormat = "hh:mm:ss.uuu";
|
34 | this.isAutomaticScroll = false;
|
35 | this.resetDrawTimer = undefined;
|
36 | }
|
37 |
|
38 |
|
39 | initExporter() {
|
40 | this.exportWorker = new InlineWorker(ExportWavWorkerFunction);
|
41 | }
|
42 |
|
43 |
|
44 | initRecorder(stream) {
|
45 | this.mediaRecorder = new MediaRecorder(stream);
|
46 |
|
47 | this.mediaRecorder.onstart = () => {
|
48 | const track = new Track();
|
49 | track.setName("Recording");
|
50 | track.setEnabledStates();
|
51 | track.setEventEmitter(this.ee);
|
52 | this.recordingTrack = track;
|
53 | this.tracks.push(track);
|
54 | this.chunks = [];
|
55 | this.working = false;
|
56 | };
|
57 |
|
58 | this.mediaRecorder.ondataavailable = e => {
|
59 | this.chunks.push(e.data);
|
60 |
|
61 | if (!this.working) {
|
62 | const recording = new Blob(this.chunks, {
|
63 | type: "audio/ogg; codecs=opus"
|
64 | });
|
65 | const loader = LoaderFactory.createLoader(recording, this.ac);
|
66 | loader.load().then(audioBuffer => {
|
67 |
|
68 | this.recorderWorker.postMessage({
|
69 | samples: audioBuffer.getChannelData(0),
|
70 | samplesPerPixel: this.samplesPerPixel
|
71 | });
|
72 | this.recordingTrack.setCues(0, audioBuffer.duration);
|
73 | this.recordingTrack.setBuffer(audioBuffer);
|
74 | this.recordingTrack.setPlayout(new Playout(this.ac, audioBuffer));
|
75 | this.adjustDuration();
|
76 | }).catch(() => {
|
77 | this.working = false;
|
78 | });
|
79 | this.working = true;
|
80 | }
|
81 | };
|
82 |
|
83 | this.mediaRecorder.onstop = () => {
|
84 | this.chunks = [];
|
85 | this.working = false;
|
86 | };
|
87 |
|
88 | this.recorderWorker = new InlineWorker(RecorderWorkerFunction);
|
89 |
|
90 | this.recorderWorker.onmessage = e => {
|
91 | this.recordingTrack.setPeaks(e.data);
|
92 | this.working = false;
|
93 | this.drawRequest();
|
94 | };
|
95 | }
|
96 |
|
97 | setShowTimeScale(show) {
|
98 | this.showTimescale = show;
|
99 | }
|
100 |
|
101 | setMono(mono) {
|
102 | this.mono = mono;
|
103 | }
|
104 |
|
105 | setExclSolo(exclSolo) {
|
106 | this.exclSolo = exclSolo;
|
107 | }
|
108 |
|
109 | setSeekStyle(style) {
|
110 | this.seekStyle = style;
|
111 | }
|
112 |
|
113 | getSeekStyle() {
|
114 | return this.seekStyle;
|
115 | }
|
116 |
|
117 | setSampleRate(sampleRate) {
|
118 | this.sampleRate = sampleRate;
|
119 | }
|
120 |
|
121 | setSamplesPerPixel(samplesPerPixel) {
|
122 | this.samplesPerPixel = samplesPerPixel;
|
123 | }
|
124 |
|
125 | setAudioContext(ac) {
|
126 | this.ac = ac;
|
127 | }
|
128 |
|
129 | setControlOptions(controlOptions) {
|
130 | this.controls = controlOptions;
|
131 | }
|
132 |
|
133 | setWaveHeight(height) {
|
134 | this.waveHeight = height;
|
135 | }
|
136 |
|
137 | setCollapsedWaveHeight(height) {
|
138 | this.collapsedWaveHeight = height;
|
139 | }
|
140 |
|
141 | setColors(colors) {
|
142 | this.colors = colors;
|
143 | }
|
144 |
|
145 | setBarWidth(width) {
|
146 | this.barWidth = width;
|
147 | }
|
148 |
|
149 | setBarGap(width) {
|
150 | this.barGap = width;
|
151 | }
|
152 |
|
153 | setAnnotations(config) {
|
154 | const controlWidth = this.controls.show ? this.controls.width : 0;
|
155 | this.annotationList = new AnnotationList(this, config.annotations, config.controls, config.editable, config.linkEndpoints, config.isContinuousPlay, controlWidth);
|
156 | }
|
157 |
|
158 | setEventEmitter(ee) {
|
159 | this.ee = ee;
|
160 | }
|
161 |
|
162 | getEventEmitter() {
|
163 | return this.ee;
|
164 | }
|
165 |
|
166 | setUpEventEmitter() {
|
167 | const ee = this.ee;
|
168 | ee.on("automaticscroll", val => {
|
169 | this.isAutomaticScroll = val;
|
170 | });
|
171 | ee.on("durationformat", format => {
|
172 | this.durationFormat = format;
|
173 | this.drawRequest();
|
174 | });
|
175 | ee.on("select", (start, end, track) => {
|
176 | if (this.isPlaying()) {
|
177 | this.lastSeeked = start;
|
178 | this.pausedAt = undefined;
|
179 | this.restartPlayFrom(start);
|
180 | } else {
|
181 |
|
182 | this.seek(start, end, track);
|
183 | this.ee.emit("timeupdate", start);
|
184 | this.drawRequest();
|
185 | }
|
186 | });
|
187 | ee.on("startaudiorendering", type => {
|
188 | this.startOfflineRender(type);
|
189 | });
|
190 | ee.on("statechange", state => {
|
191 | this.setState(state);
|
192 | this.drawRequest();
|
193 | });
|
194 | ee.on("shift", (deltaTime, track) => {
|
195 | track.setStartTime(track.getStartTime() + deltaTime);
|
196 | this.adjustDuration();
|
197 | this.drawRequest();
|
198 | });
|
199 | ee.on("record", () => {
|
200 | this.record();
|
201 | });
|
202 | ee.on("play", (start, end) => {
|
203 | this.play(start, end);
|
204 | });
|
205 | ee.on("pause", () => {
|
206 | this.pause();
|
207 | });
|
208 | ee.on("stop", () => {
|
209 | this.stop();
|
210 | });
|
211 | ee.on("rewind", () => {
|
212 | this.rewind();
|
213 | });
|
214 | ee.on("fastforward", () => {
|
215 | this.fastForward();
|
216 | });
|
217 | ee.on("clear", () => {
|
218 | this.clear().then(() => {
|
219 | this.drawRequest();
|
220 | });
|
221 | });
|
222 | ee.on("solo", track => {
|
223 | this.soloTrack(track);
|
224 | this.adjustTrackPlayout();
|
225 | this.drawRequest();
|
226 | });
|
227 | ee.on("mute", track => {
|
228 | this.muteTrack(track);
|
229 | this.adjustTrackPlayout();
|
230 | this.drawRequest();
|
231 | });
|
232 | ee.on("removeTrack", track => {
|
233 | this.removeTrack(track);
|
234 | this.adjustTrackPlayout();
|
235 | this.drawRequest();
|
236 | });
|
237 | ee.on("changeTrackView", (track, opts) => {
|
238 | this.collapseTrack(track, opts);
|
239 | this.drawRequest();
|
240 | });
|
241 | ee.on("volumechange", (volume, track) => {
|
242 | track.setGainLevel(volume / 100);
|
243 | this.drawRequest();
|
244 | });
|
245 | ee.on("mastervolumechange", volume => {
|
246 | this.masterGain = volume / 100;
|
247 | this.tracks.forEach(track => {
|
248 | track.setMasterGainLevel(this.masterGain);
|
249 | });
|
250 | });
|
251 | ee.on("fadein", (duration, track) => {
|
252 | track.setFadeIn(duration, this.fadeType);
|
253 | this.drawRequest();
|
254 | });
|
255 | ee.on("fadeout", (duration, track) => {
|
256 | track.setFadeOut(duration, this.fadeType);
|
257 | this.drawRequest();
|
258 | });
|
259 | ee.on("stereopan", (panvalue, track) => {
|
260 | track.setStereoPanValue(panvalue);
|
261 | this.drawRequest();
|
262 | });
|
263 | ee.on("fadetype", type => {
|
264 | this.fadeType = type;
|
265 | });
|
266 | ee.on("newtrack", file => {
|
267 | this.load([{
|
268 | src: file,
|
269 | name: file.name
|
270 | }]);
|
271 | });
|
272 | ee.on("trim", () => {
|
273 | const track = this.getActiveTrack();
|
274 | const timeSelection = this.getTimeSelection();
|
275 | track.trim(timeSelection.start, timeSelection.end);
|
276 | track.calculatePeaks(this.samplesPerPixel, this.sampleRate);
|
277 | this.setTimeSelection(0, 0);
|
278 | this.drawRequest();
|
279 | });
|
280 | ee.on("zoomin", () => {
|
281 | const zoomIndex = Math.max(0, this.zoomIndex - 1);
|
282 | const zoom = this.zoomLevels[zoomIndex];
|
283 |
|
284 | if (zoom !== this.samplesPerPixel) {
|
285 | this.setZoom(zoom);
|
286 | this.drawRequest();
|
287 | }
|
288 | });
|
289 | ee.on("zoomout", () => {
|
290 | const zoomIndex = Math.min(this.zoomLevels.length - 1, this.zoomIndex + 1);
|
291 | const zoom = this.zoomLevels[zoomIndex];
|
292 |
|
293 | if (zoom !== this.samplesPerPixel) {
|
294 | this.setZoom(zoom);
|
295 | this.drawRequest();
|
296 | }
|
297 | });
|
298 | ee.on("scroll", () => {
|
299 | this.isScrolling = true;
|
300 | this.drawRequest();
|
301 | clearTimeout(this.scrollTimer);
|
302 | this.scrollTimer = setTimeout(() => {
|
303 | this.isScrolling = false;
|
304 | }, 200);
|
305 | });
|
306 | }
|
307 |
|
308 | load(trackList) {
|
309 | const loadPromises = trackList.map(trackInfo => {
|
310 | const loader = LoaderFactory.createLoader(trackInfo.src, this.ac, this.ee);
|
311 | return loader.load();
|
312 | });
|
313 | return Promise.all(loadPromises).then(audioBuffers => {
|
314 | this.ee.emit("audiosourcesloaded");
|
315 | const tracks = audioBuffers.map((audioBuffer, index) => {
|
316 | const info = trackList[index];
|
317 | const name = info.name || "Untitled";
|
318 | const start = info.start || 0;
|
319 | const states = info.states || {};
|
320 | const fadeIn = info.fadeIn;
|
321 | const fadeOut = info.fadeOut;
|
322 | const cueIn = info.cuein || 0;
|
323 | const cueOut = info.cueout || audioBuffer.duration;
|
324 | const gain = info.gain || 1;
|
325 | const muted = info.muted || false;
|
326 | const soloed = info.soloed || false;
|
327 | const selection = info.selected;
|
328 | const peaks = info.peaks || {
|
329 | type: "WebAudio",
|
330 | mono: this.mono
|
331 | };
|
332 | const customClass = info.customClass || undefined;
|
333 | const waveOutlineColor = info.waveOutlineColor || undefined;
|
334 | const stereoPan = info.stereoPan || 0;
|
335 |
|
336 | const playout = new Playout(this.ac, audioBuffer);
|
337 | const track = new Track();
|
338 | track.src = info.src;
|
339 | track.setBuffer(audioBuffer);
|
340 | track.setName(name);
|
341 | track.setEventEmitter(this.ee);
|
342 | track.setEnabledStates(states);
|
343 | track.setCues(cueIn, cueOut);
|
344 | track.setCustomClass(customClass);
|
345 | track.setWaveOutlineColor(waveOutlineColor);
|
346 |
|
347 | if (fadeIn !== undefined) {
|
348 | track.setFadeIn(fadeIn.duration, fadeIn.shape);
|
349 | }
|
350 |
|
351 | if (fadeOut !== undefined) {
|
352 | track.setFadeOut(fadeOut.duration, fadeOut.shape);
|
353 | }
|
354 |
|
355 | if (selection !== undefined) {
|
356 | this.setActiveTrack(track);
|
357 | this.setTimeSelection(selection.start, selection.end);
|
358 | }
|
359 |
|
360 | if (peaks !== undefined) {
|
361 | track.setPeakData(peaks);
|
362 | }
|
363 |
|
364 | track.setState(this.getState());
|
365 | track.setStartTime(start);
|
366 | track.setPlayout(playout);
|
367 | track.setGainLevel(gain);
|
368 | track.setStereoPanValue(stereoPan);
|
369 |
|
370 | if (muted) {
|
371 | this.muteTrack(track);
|
372 | }
|
373 |
|
374 | if (soloed) {
|
375 | this.soloTrack(track);
|
376 | }
|
377 |
|
378 |
|
379 | track.calculatePeaks(this.samplesPerPixel, this.sampleRate);
|
380 | return track;
|
381 | });
|
382 | this.tracks = this.tracks.concat(tracks);
|
383 | this.adjustDuration();
|
384 | this.draw(this.render());
|
385 | this.ee.emit("audiosourcesrendered");
|
386 | }).catch(e => {
|
387 | this.ee.emit("audiosourceserror", e);
|
388 | });
|
389 | }
|
390 | |
391 |
|
392 |
|
393 |
|
394 |
|
395 | setActiveTrack(track) {
|
396 | this.activeTrack = track;
|
397 | }
|
398 |
|
399 | getActiveTrack() {
|
400 | return this.activeTrack;
|
401 | }
|
402 |
|
403 | isSegmentSelection() {
|
404 | return this.timeSelection.start !== this.timeSelection.end;
|
405 | }
|
406 | |
407 |
|
408 |
|
409 |
|
410 |
|
411 | setTimeSelection(start = 0, end) {
|
412 | this.timeSelection = {
|
413 | start,
|
414 | end: end === undefined ? start : end
|
415 | };
|
416 | this.cursor = start;
|
417 | }
|
418 |
|
419 | startOfflineRender(type) {
|
420 | if (this.isRendering) {
|
421 | return;
|
422 | }
|
423 |
|
424 | this.isRendering = true;
|
425 | this.offlineAudioContext = new OfflineAudioContext(2, 44100 * this.duration, 44100);
|
426 | const currentTime = this.offlineAudioContext.currentTime;
|
427 | this.tracks.forEach(track => {
|
428 | track.setOfflinePlayout(new Playout(this.offlineAudioContext, track.buffer));
|
429 | track.schedulePlay(currentTime, 0, 0, {
|
430 | shouldPlay: this.shouldTrackPlay(track),
|
431 | masterGain: 1,
|
432 | isOffline: true
|
433 | });
|
434 | });
|
435 | |
436 |
|
437 |
|
438 |
|
439 | this.offlineAudioContext.startRendering().then(audioBuffer => {
|
440 | if (type === "buffer") {
|
441 | this.ee.emit("audiorenderingfinished", type, audioBuffer);
|
442 | this.isRendering = false;
|
443 | return;
|
444 | }
|
445 |
|
446 | if (type === "wav") {
|
447 | this.exportWorker.postMessage({
|
448 | command: "init",
|
449 | config: {
|
450 | sampleRate: 44100
|
451 | }
|
452 | });
|
453 |
|
454 | this.exportWorker.onmessage = e => {
|
455 | this.ee.emit("audiorenderingfinished", type, e.data);
|
456 | this.isRendering = false;
|
457 |
|
458 | this.exportWorker.postMessage({
|
459 | command: "clear"
|
460 | });
|
461 | };
|
462 |
|
463 |
|
464 | this.exportWorker.postMessage({
|
465 | command: "record",
|
466 | buffer: [audioBuffer.getChannelData(0), audioBuffer.getChannelData(1)]
|
467 | });
|
468 |
|
469 | this.exportWorker.postMessage({
|
470 | command: "exportWAV",
|
471 | type: "audio/wav"
|
472 | });
|
473 | }
|
474 | }).catch(e => {
|
475 | throw e;
|
476 | });
|
477 | }
|
478 |
|
479 | getTimeSelection() {
|
480 | return this.timeSelection;
|
481 | }
|
482 |
|
483 | setState(state) {
|
484 | this.state = state;
|
485 | this.tracks.forEach(track => {
|
486 | track.setState(state);
|
487 | });
|
488 | }
|
489 |
|
490 | getState() {
|
491 | return this.state;
|
492 | }
|
493 |
|
494 | setZoomIndex(index) {
|
495 | this.zoomIndex = index;
|
496 | }
|
497 |
|
498 | setZoomLevels(levels) {
|
499 | this.zoomLevels = levels;
|
500 | }
|
501 |
|
502 | setZoom(zoom) {
|
503 | this.samplesPerPixel = zoom;
|
504 | this.zoomIndex = this.zoomLevels.indexOf(zoom);
|
505 | this.tracks.forEach(track => {
|
506 | track.calculatePeaks(zoom, this.sampleRate);
|
507 | });
|
508 | }
|
509 |
|
510 | muteTrack(track) {
|
511 | const index = this.mutedTracks.indexOf(track);
|
512 |
|
513 | if (index > -1) {
|
514 | this.mutedTracks.splice(index, 1);
|
515 | } else {
|
516 | this.mutedTracks.push(track);
|
517 | }
|
518 | }
|
519 |
|
520 | soloTrack(track) {
|
521 | const index = this.soloedTracks.indexOf(track);
|
522 |
|
523 | if (index > -1) {
|
524 | this.soloedTracks.splice(index, 1);
|
525 | } else if (this.exclSolo) {
|
526 | this.soloedTracks = [track];
|
527 | } else {
|
528 | this.soloedTracks.push(track);
|
529 | }
|
530 | }
|
531 |
|
532 | collapseTrack(track, opts) {
|
533 | if (opts.collapsed) {
|
534 | this.collapsedTracks.push(track);
|
535 | } else {
|
536 | const index = this.collapsedTracks.indexOf(track);
|
537 |
|
538 | if (index > -1) {
|
539 | this.collapsedTracks.splice(index, 1);
|
540 | }
|
541 | }
|
542 | }
|
543 |
|
544 | removeTrack(track) {
|
545 | if (track.isPlaying()) {
|
546 | track.scheduleStop();
|
547 | }
|
548 |
|
549 | const trackLists = [this.mutedTracks, this.soloedTracks, this.collapsedTracks, this.tracks];
|
550 | trackLists.forEach(list => {
|
551 | const index = list.indexOf(track);
|
552 |
|
553 | if (index > -1) {
|
554 | list.splice(index, 1);
|
555 | }
|
556 | });
|
557 | }
|
558 |
|
559 | adjustTrackPlayout() {
|
560 | this.tracks.forEach(track => {
|
561 | track.setShouldPlay(this.shouldTrackPlay(track));
|
562 | });
|
563 | }
|
564 |
|
565 | adjustDuration() {
|
566 | this.duration = this.tracks.reduce((duration, track) => Math.max(duration, track.getEndTime()), 0);
|
567 | }
|
568 |
|
569 | shouldTrackPlay(track) {
|
570 | let shouldPlay;
|
571 |
|
572 | if (this.soloedTracks.length > 0) {
|
573 | shouldPlay = false;
|
574 |
|
575 | if (this.soloedTracks.indexOf(track) > -1) {
|
576 | shouldPlay = true;
|
577 | }
|
578 | } else {
|
579 |
|
580 | shouldPlay = true;
|
581 |
|
582 | if (this.mutedTracks.indexOf(track) > -1) {
|
583 | shouldPlay = false;
|
584 | }
|
585 | }
|
586 |
|
587 | return shouldPlay;
|
588 | }
|
589 |
|
590 | isPlaying() {
|
591 | return this.tracks.reduce((isPlaying, track) => isPlaying || track.isPlaying(), false);
|
592 | }
|
593 | |
594 |
|
595 |
|
596 |
|
597 |
|
598 | getCurrentTime() {
|
599 | const cursorPos = this.lastSeeked || this.pausedAt || this.cursor;
|
600 | return cursorPos + this.getElapsedTime();
|
601 | }
|
602 |
|
603 | getElapsedTime() {
|
604 | return this.ac.currentTime - this.lastPlay;
|
605 | }
|
606 |
|
607 | setMasterGain(gain) {
|
608 | this.ee.emit("mastervolumechange", gain);
|
609 | }
|
610 |
|
611 | restartPlayFrom(start, end) {
|
612 | this.stopAnimation();
|
613 | this.tracks.forEach(editor => {
|
614 | editor.scheduleStop();
|
615 | });
|
616 | return Promise.all(this.playoutPromises).then(this.play.bind(this, start, end));
|
617 | }
|
618 |
|
619 | play(startTime, endTime) {
|
620 | clearTimeout(this.resetDrawTimer);
|
621 | const currentTime = this.ac.currentTime;
|
622 | const selected = this.getTimeSelection();
|
623 | const playoutPromises = [];
|
624 | const start = startTime || this.pausedAt || this.cursor;
|
625 | let end = endTime;
|
626 |
|
627 | if (!end && selected.end !== selected.start && selected.end > start) {
|
628 | end = selected.end;
|
629 | }
|
630 |
|
631 | if (this.isPlaying()) {
|
632 | return this.restartPlayFrom(start, end);
|
633 | }
|
634 |
|
635 | this.tracks.forEach(track => {
|
636 | track.setState("cursor");
|
637 | playoutPromises.push(track.schedulePlay(currentTime, start, end, {
|
638 | shouldPlay: this.shouldTrackPlay(track),
|
639 | masterGain: this.masterGain
|
640 | }));
|
641 | });
|
642 | this.lastPlay = currentTime;
|
643 |
|
644 | this.playoutPromises = playoutPromises;
|
645 | this.startAnimation(start);
|
646 | return Promise.all(this.playoutPromises);
|
647 | }
|
648 |
|
649 | pause() {
|
650 | if (!this.isPlaying()) {
|
651 | return Promise.all(this.playoutPromises);
|
652 | }
|
653 |
|
654 | this.pausedAt = this.getCurrentTime();
|
655 | return this.playbackReset();
|
656 | }
|
657 |
|
658 | stop() {
|
659 | if (this.mediaRecorder && this.mediaRecorder.state === "recording") {
|
660 | this.mediaRecorder.stop();
|
661 | }
|
662 |
|
663 | this.pausedAt = undefined;
|
664 | this.playbackSeconds = 0;
|
665 | return this.playbackReset();
|
666 | }
|
667 |
|
668 | playbackReset() {
|
669 | this.lastSeeked = undefined;
|
670 | this.stopAnimation();
|
671 | this.tracks.forEach(track => {
|
672 | track.scheduleStop();
|
673 | track.setState(this.getState());
|
674 | });
|
675 | this.drawRequest();
|
676 | return Promise.all(this.playoutPromises);
|
677 | }
|
678 |
|
679 | rewind() {
|
680 | return this.stop().then(() => {
|
681 | this.scrollLeft = 0;
|
682 | this.ee.emit("select", 0, 0);
|
683 | });
|
684 | }
|
685 |
|
686 | fastForward() {
|
687 | return this.stop().then(() => {
|
688 | if (this.viewDuration < this.duration) {
|
689 | this.scrollLeft = this.duration - this.viewDuration;
|
690 | } else {
|
691 | this.scrollLeft = 0;
|
692 | }
|
693 |
|
694 | this.ee.emit("select", this.duration, this.duration);
|
695 | });
|
696 | }
|
697 |
|
698 | clear() {
|
699 | return this.stop().then(() => {
|
700 | this.tracks = [];
|
701 | this.soloedTracks = [];
|
702 | this.mutedTracks = [];
|
703 | this.playoutPromises = [];
|
704 | this.cursor = 0;
|
705 | this.playbackSeconds = 0;
|
706 | this.duration = 0;
|
707 | this.scrollLeft = 0;
|
708 | this.seek(0, 0, undefined);
|
709 | });
|
710 | }
|
711 |
|
712 | record() {
|
713 | const playoutPromises = [];
|
714 | this.mediaRecorder.start(300);
|
715 | this.tracks.forEach(track => {
|
716 | track.setState("none");
|
717 | playoutPromises.push(track.schedulePlay(this.ac.currentTime, 0, undefined, {
|
718 | shouldPlay: this.shouldTrackPlay(track)
|
719 | }));
|
720 | });
|
721 | this.playoutPromises = playoutPromises;
|
722 | }
|
723 |
|
724 | startAnimation(startTime) {
|
725 | this.lastDraw = this.ac.currentTime;
|
726 | this.animationRequest = window.requestAnimationFrame(() => {
|
727 | this.updateEditor(startTime);
|
728 | });
|
729 | }
|
730 |
|
731 | stopAnimation() {
|
732 | window.cancelAnimationFrame(this.animationRequest);
|
733 | this.lastDraw = undefined;
|
734 | }
|
735 |
|
736 | seek(start, end, track) {
|
737 | if (this.isPlaying()) {
|
738 | this.lastSeeked = start;
|
739 | this.pausedAt = undefined;
|
740 | this.restartPlayFrom(start);
|
741 | } else {
|
742 |
|
743 | this.setActiveTrack(track || this.tracks[0]);
|
744 | this.pausedAt = start;
|
745 | this.setTimeSelection(start, end);
|
746 |
|
747 | if (this.getSeekStyle() === "fill") {
|
748 | this.playbackSeconds = start;
|
749 | }
|
750 | }
|
751 | }
|
752 | |
753 |
|
754 |
|
755 |
|
756 |
|
757 |
|
758 | updateEditor(cursor) {
|
759 | const currentTime = this.ac.currentTime;
|
760 | const selection = this.getTimeSelection();
|
761 | const cursorPos = cursor || this.cursor;
|
762 | const elapsed = currentTime - this.lastDraw;
|
763 |
|
764 | if (this.isPlaying()) {
|
765 | const playbackSeconds = cursorPos + elapsed;
|
766 | this.ee.emit("timeupdate", playbackSeconds);
|
767 | this.animationRequest = window.requestAnimationFrame(() => {
|
768 | this.updateEditor(playbackSeconds);
|
769 | });
|
770 | this.playbackSeconds = playbackSeconds;
|
771 | this.draw(this.render());
|
772 | this.lastDraw = currentTime;
|
773 | } else {
|
774 | if (cursorPos + elapsed >= (this.isSegmentSelection() ? selection.end : this.duration)) {
|
775 | this.ee.emit("finished");
|
776 | }
|
777 |
|
778 | this.stopAnimation();
|
779 | this.resetDrawTimer = setTimeout(() => {
|
780 | this.pausedAt = undefined;
|
781 | this.lastSeeked = undefined;
|
782 | this.setState(this.getState());
|
783 | this.playbackSeconds = 0;
|
784 | this.draw(this.render());
|
785 | }, 0);
|
786 | }
|
787 | }
|
788 |
|
789 | drawRequest() {
|
790 | window.requestAnimationFrame(() => {
|
791 | this.draw(this.render());
|
792 | });
|
793 | }
|
794 |
|
795 | draw(newTree) {
|
796 | const patches = diff(this.tree, newTree);
|
797 | this.rootNode = patch(this.rootNode, patches);
|
798 | this.tree = newTree;
|
799 |
|
800 | this.viewDuration = pixelsToSeconds(this.rootNode.clientWidth - this.controls.width, this.samplesPerPixel, this.sampleRate);
|
801 | }
|
802 |
|
803 | getTrackRenderData(data = {}) {
|
804 | const defaults = {
|
805 | height: this.waveHeight,
|
806 | resolution: this.samplesPerPixel,
|
807 | sampleRate: this.sampleRate,
|
808 | controls: this.controls,
|
809 | isActive: false,
|
810 | timeSelection: this.getTimeSelection(),
|
811 | playlistLength: this.duration,
|
812 | playbackSeconds: this.playbackSeconds,
|
813 | colors: this.colors,
|
814 | barWidth: this.barWidth,
|
815 | barGap: this.barGap
|
816 | };
|
817 | return _defaults({}, data, defaults);
|
818 | }
|
819 |
|
820 | isActiveTrack(track) {
|
821 | const activeTrack = this.getActiveTrack();
|
822 |
|
823 | if (this.isSegmentSelection()) {
|
824 | return activeTrack === track;
|
825 | }
|
826 |
|
827 | return true;
|
828 | }
|
829 |
|
830 | renderAnnotations() {
|
831 | return this.annotationList.render();
|
832 | }
|
833 |
|
834 | renderTimeScale() {
|
835 | const controlWidth = this.controls.show ? this.controls.width : 0;
|
836 | const timeScale = new TimeScale(this.duration, this.scrollLeft, this.samplesPerPixel, this.sampleRate, controlWidth, this.colors);
|
837 | return timeScale.render();
|
838 | }
|
839 |
|
840 | renderTrackSection() {
|
841 | const trackElements = this.tracks.map(track => {
|
842 | const collapsed = this.collapsedTracks.indexOf(track) > -1;
|
843 | return track.render(this.getTrackRenderData({
|
844 | isActive: this.isActiveTrack(track),
|
845 | shouldPlay: this.shouldTrackPlay(track),
|
846 | soloed: this.soloedTracks.indexOf(track) > -1,
|
847 | muted: this.mutedTracks.indexOf(track) > -1,
|
848 | collapsed,
|
849 | height: collapsed ? this.collapsedWaveHeight : this.waveHeight,
|
850 | barGap: this.barGap,
|
851 | barWidth: this.barWidth
|
852 | }));
|
853 | });
|
854 | return h("div.playlist-tracks", {
|
855 | attributes: {
|
856 | style: "overflow: auto;"
|
857 | },
|
858 | onscroll: e => {
|
859 | this.scrollLeft = pixelsToSeconds(e.target.scrollLeft, this.samplesPerPixel, this.sampleRate);
|
860 | this.ee.emit("scroll");
|
861 | },
|
862 | hook: new ScrollHook(this)
|
863 | }, trackElements);
|
864 | }
|
865 |
|
866 | render() {
|
867 | const containerChildren = [];
|
868 |
|
869 | if (this.showTimescale) {
|
870 | containerChildren.push(this.renderTimeScale());
|
871 | }
|
872 |
|
873 | containerChildren.push(this.renderTrackSection());
|
874 |
|
875 | if (this.annotationList.length) {
|
876 | containerChildren.push(this.renderAnnotations());
|
877 | }
|
878 |
|
879 | return h("div.playlist", {
|
880 | attributes: {
|
881 | style: "overflow: hidden; position: relative;"
|
882 | }
|
883 | }, containerChildren);
|
884 | }
|
885 |
|
886 | getInfo() {
|
887 | const info = [];
|
888 | this.tracks.forEach(track => {
|
889 | info.push(track.getTrackDetails());
|
890 | });
|
891 | return info;
|
892 | }
|
893 |
|
894 | } |
\ | No newline at end of file |