PageSourceSearch

https://www.augustdorfernachrichten.de/cdnjs.cloudflare.com/ajax/libs/wavesurfer.js/1.4.0/wavesurfer.js

js augustdorfernachrichten.de collected 2026-10-04 03:35:52 UTC 85,998 bytes, 2,768 lines download raw bytes

vendor: 32,741 bytes, lines 1-1104
1(function (root, factory) {
2  if (typeof define === 'function' && define.amd) {
3    // AMD. Register as an anonymous module unless amdModuleId is set
4    define('wavesurfer', [], function () {
5      return (root['WaveSurfer'] = factory());
6    });
7  } else if (typeof exports === 'object') {
8    // Node. Does not work with strict CommonJS, but
9    // only CommonJS-like environments that support module.exports,
10    // like Node.
11    module.exports = factory();
12  } else {
13    root['WaveSurfer'] = factory();
14  }
15}(this, function () {
16
17'use strict';
18
19var WaveSurfer = {
20    defaultParams: {
21        audioContext  : null,
22        audioRate     : 1,
23        autoCenter    : true,
24        backend       : 'WebAudio',
25        barHeight     : 1,
26        closeAudioContext: false,
27        container     : null,
28        cursorColor   : '#333',
29        cursorWidth   : 1,
30        dragSelection : true,
31        fillParent    : true,
32        forceDecode   : false,
33        height        : 128,
34        hideScrollbar : false,
35        interact      : true,
36        loopSelection : true,
37        mediaContainer: null,
38        mediaControls : false,
39        mediaType     : 'audio',
40        minPxPerSec   : 20,
41        partialRender : false,
42        pixelRatio    : window.devicePixelRatio || screen.deviceXDPI / screen.logicalXDPI,
43        progressColor : '#555',
44        normalize     : false,
45        renderer      : 'MultiCanvas',
46        scrollParent  : false,
47        skipLength    : 2,
48        splitChannels : false,
49        waveColor     : '#999',
50    },
51
52    init: function (params) {
53        // Extract relevant parameters (or defaults)
54        this.params = WaveSurfer.util.extend({}, this.defaultParams, params);
55
56        this.container = 'string' == typeof params.container ?
57            document.querySelector(this.params.container) :
58            this.params.container;
59
60        if (!this.container) {
61            throw new Error('Container element not found');
62        }
63
64        if (this.params.mediaContainer == null) {
65            this.mediaContainer = this.container;
66        } else if (typeof this.params.mediaContainer == 'string') {
67            this.mediaContainer = document.querySelector(this.params.mediaContainer);
68        } else {
69            this.mediaContainer = this.params.mediaContainer;
70        }
71
72        if (!this.mediaContainer) {
73            throw new Error('Media Container element not found');
74        }
75
76        // Used to save the current volume when muting so we can
77        // restore once unmuted
78        this.savedVolume = 0;
79
80        // The current muted state
81        this.isMuted = false;
82
83        // Will hold a list of event descriptors that need to be
84        // cancelled on subsequent loads of audio
85        this.tmpEvents = [];
86
87        // Holds any running audio downloads
88        this.currentAjax = null;
89
90        this.createDrawer();
91        this.createBackend();
92        this.createPeakCache();
93
94        this.isDestroyed = false;
95    },
96
97    createDrawer: function () {
98        var my = this;
99
100        this.drawer = Object.create(WaveSurfer.Drawer[this.params.renderer]);
101        this.drawer.init(this.container, this.params);
102
103        this.drawer.on('redraw', function () {
104            my.drawBuffer();
105            my.drawer.progress(my.backend.getPlayedPercents());
106        });
107
108        // Click-to-seek
109        this.drawer.on('click', function (e, progress) {
110            setTimeout(function () {
111                my.seekTo(progress);
112            }, 0);
113        });
114
115        // Relay the scroll event from the drawer
116        this.drawer.on('scroll', function (e) {
117            if (my.params.partialRender) {
118                my.drawBuffer();
119            }
120            my.fireEvent('scroll', e);
121        });
122    },
123
124    createBackend: function () {
125        var my = this;
126
127        if (this.backend) {
128            this.backend.destroy();
129        }
130
131        // Back compat
132        if (this.params.backend == 'AudioElement') {
133            this.params.backend = 'MediaElement';
134        }
135
136        if (this.params.backend == 'WebAudio' && !WaveSurfer.WebAudio.supportsWebAudio()) {
137            this.params.backend = 'MediaElement';
138        }
139
140        this.backend = Object.create(WaveSurfer[this.params.backend]);
141        this.backend.init(this.params);
142
143        this.backend.on('finish', function () { my.fireEvent('finish'); });
144        this.backend.on('play', function () { my.fireEvent('play'); });
145        this.backend.on('pause', function () { my.fireEvent('pause'); });
146
147        this.backend.on('audioprocess', function (time) {
148            my.drawer.progress(my.backend.getPlayedPercents());
149            my.fireEvent('audioprocess', time);
150        });
151    },
152
153    createPeakCache: function() {
154        if (this.params.partialRender) {
155            this.peakCache = Object.create(WaveSurfer.PeakCache);
156            this.peakCache.init();
157        }
158    },
159
160    getDuration: function () {
161        return this.backend.getDuration();
162    },
163
164    getCurrentTime: function () {
165        return this.backend.getCurrentTime();
166    },
167
168    play: function (start, end) {
169        this.fireEvent('interaction', this.play.bind(this, start, end));
170        this.backend.play(start, end);
171    },
172
173    pause: function () {
174        this.backend.isPaused() || this.backend.pause();
175    },
176
177    playPause: function () {
178        this.backend.isPaused() ? this.play() : this.pause();
179    },
180
181    isPlaying: function () {
182        return !this.backend.isPaused();
183    },
184
185    skipBackward: function (seconds) {
186        this.skip(-seconds || -this.params.skipLength);
187    },
188
189    skipForward: function (seconds) {
190        this.skip(seconds || this.params.skipLength);
191    },
192
193    skip: function (offset) {
194        var position = this.getCurrentTime() || 0;
195        var duration = this.getDuration() || 1;
196        position = Math.max(0, Math.min(duration, position + (offset || 0)));
197        this.seekAndCenter(position / duration);
198    },
199
200    seekAndCenter: function (progress) {
201        this.seekTo(progress);
202        this.drawer.recenter(progress);
203    },
204
205    seekTo: function (progress) {
206        this.fireEvent('interaction', this.seekTo.bind(this, progress));
207
208        var paused = this.backend.isPaused();
209        // avoid draw wrong position while playing backward seeking
210        if (!paused) {
211            this.backend.pause();
212        }
213        // avoid small scrolls while paused seeking
214        var oldScrollParent = this.params.scrollParent;
215        this.params.scrollParent = false;
216        this.backend.seekTo(progress * this.getDuration());
217        this.drawer.progress(this.backend.getPlayedPercents());
218
219        if (!paused) {
220            this.backend.play();
221        }
222        this.params.scrollParent = oldScrollParent;
223        this.fireEvent('seek', progress);
224    },
225
226    stop: function () {
227        this.pause();
228        this.seekTo(0);
229        this.drawer.progress(0);
230    },
231
232    /**
233     * Set the playback volume.
234     *
235     * @param {Number} newVolume A value between 0 and 1, 0 being no
236     * volume and 1 being full volume.
237     */
238    setVolume: function (newVolume) {
239        this.backend.setVolume(newVolume);
240    },
241
242    /**
243     * Get the playback volume.
244     */
245    getVolume: function () {
246        return this.backend.getVolume();
247    },
248
249    /**
250     * Set the playback rate.
251     *
252     * @param {Number} rate A positive number. E.g. 0.5 means half the
253     * normal speed, 2 means double speed and so on.
254     */
255    setPlaybackRate: function (rate) {
256        this.backend.setPlaybackRate(rate);
257    },
258
259    /**
260     * Get the playback rate.
261     */
262    getPlaybackRate: function () {
263        return this.backend.getPlaybackRate();
264    },
265
266    /**
267     * Toggle the volume on and off. It not currenly muted it will
268     * save the current volume value and turn the volume off.
269     * If currently muted then it will restore the volume to the saved
270     * value, and then rest the saved value.
271     */
272    toggleMute: function () {
273        this.setMute(!this.isMuted);
274    },
275
276    setMute: function (mute) {
277        // ignore all muting requests if the audio is already in that state
278        if (mute === this.isMuted) {
279            return;
280        }
281
282        if (mute) {
283            // If currently not muted then save current volume,
284            // turn off the volume and update the mute properties
285            this.savedVolume = this.backend.getVolume();
286            this.backend.setVolume(0);
287            this.isMuted = true;
288        } else {
289            // If currently muted then restore to the saved volume
290            // and update the mute properties
291            this.backend.setVolume(this.savedVolume);
292            this.isMuted = false;
293        }
294    },
295
296    /**
297     * Get the current mute status.
298     */
299    getMute: function () {
300        return this.isMuted;
301    },
302
303    /**
304     * Get the list of current set filters as an array.
305     *
306     * Filters must be set with setFilters method first
307     */
308    getFilters: function() {
309        return this.backend.filters || [];
310    },
311
312    toggleScroll: function () {
313        this.params.scrollParent = !this.params.scrollParent;
314        this.drawBuffer();
315    },
316
317    toggleInteraction: function () {
318        this.params.interact = !this.params.interact;
319    },
320
321    drawBuffer: function () {
322        var nominalWidth = Math.round(
323            this.getDuration() * this.params.minPxPerSec * this.params.pixelRatio
324        );
325        var parentWidth = this.drawer.getWidth();
326        var width = nominalWidth;
327        var start = this.drawer.getScrollX();
328        var end = Math.min(start + parentWidth, width);
329
330        // Fill container
331        if (this.params.fillParent && (!this.params.scrollParent || nominalWidth < parentWidth)) {
332            width = parentWidth;
333            start = 0;
334            end = width;
335        }
336
337        if (this.params.partialRender) {
338            var newRanges = this.peakCache.addRangeToPeakCache(width, start, end);
339            for (var i = 0; i < newRanges.length; i++) {
340              var peaks = this.backend.getPeaks(width, newRanges[i][0], newRanges[i][1]);
341              this.drawer.drawPeaks(peaks, width, newRanges[i][0], newRanges[i][1]);
342            }
343        } else {
344            start = 0;
345            end = width;
346            var peaks = this.backend.getPeaks(width, start, end);
347            this.drawer.drawPeaks(peaks, width, start, end);
348        }
349        this.fireEvent('redraw', peaks, width);
350    },
351
352    zoom: function (pxPerSec) {
353        this.params.minPxPerSec = pxPerSec;
354
355        this.params.scrollParent = true;
356
357        this.drawBuffer();
358        this.drawer.progress(this.backend.getPlayedPercents());
359
360        this.drawer.recenter(
361            this.getCurrentTime() / this.getDuration()
362        );
363        this.fireEvent('zoom', pxPerSec);
364    },
365
366    /**
367     * Internal method.
368     */
369    loadArrayBuffer: function (arraybuffer) {
370        this.decodeArrayBuffer(arraybuffer, function (data) {
371            if (!this.isDestroyed) {
372                this.loadDecodedBuffer(data);
373            }
374        }.bind(this));
375    },
376
377    /**
378     * Directly load an externally decoded AudioBuffer.
379     */
380    loadDecodedBuffer: function (buffer) {
381        this.backend.load(buffer);
382        this.drawBuffer();
383        this.fireEvent('ready');
384    },
385
386    /**
387     * Loads audio data from a Blob or File object.
388     *
389     * @param {Blob|File} blob Audio data.
390     */
391    loadBlob: function (blob) {
392        var my = this;
393        // Create file reader
394        var reader = new FileReader();
395        reader.addEventListener('progress', function (e) {
396            my.onProgress(e);
397        });
398        reader.addEventListener('load', function (e) {
399            my.loadArrayBuffer(e.target.result);
400        });
401        reader.addEventListener('error', function () {
402            my.fireEvent('error', 'Error reading file');
403        });
404        reader.readAsArrayBuffer(blob);
405        this.empty();
406    },
407
408    /**
409     * Loads audio and re-renders the waveform.
410     */
411    load: function (url, peaks, preload) {
412        this.empty();
413        this.isMuted = false;
414
415        switch (this.params.backend) {
416            case 'WebAudio': return this.loadBuffer(url, peaks);
417            case 'MediaElement': return this.loadMediaElement(url, peaks, preload);
418        }
419    },
420
421    /**
422     * Loads audio using Web Audio buffer backend.
423     */
424    loadBuffer: function (url, peaks) {
425        var load = (function (action) {
426            if (action) {
427                this.tmpEvents.push(this.once('ready', action));
428            }
429            return this.getArrayBuffer(url, this.loadArrayBuffer.bind(this));
430        }).bind(this);
431
432        if (peaks) {
433            this.backend.setPeaks(peaks);
434            this.drawBuffer();
435            this.tmpEvents.push(this.once('interaction', load));
436        } else {
437            return load();
438        }
439    },
440
441    /**
442     *  Either create a media element, or load
443     *  an existing media element.
444     *  @param  {String|HTMLElement} urlOrElt Either a path to a media file,
445     *                                          or an existing HTML5 Audio/Video
446     *                                          Element
447     *  @param  {Array}            [peaks]     Array of peaks. Required to bypass
448     *                                          web audio dependency
449     */
450    loadMediaElement: function (urlOrElt, peaks, preload) {
451        var url = urlOrElt;
452
453        if (typeof urlOrElt === 'string') {
454            this.backend.load(url, this.mediaContainer, peaks, preload);
455        } else {
456            var elt = urlOrElt;
457            this.backend.loadElt(elt, peaks);
458
459            // If peaks are not provided,
460            // url = element.src so we can get peaks with web audio
461            url = elt.src;
462        }
463
464        this.tmpEvents.push(
465            this.backend.once('canplay', (function () {
466                this.drawBuffer();
467                this.fireEvent('ready');
468            }).bind(this)),
469
470            this.backend.once('error', (function (err) {
471                this.fireEvent('error', err);
472            }).bind(this))
473        );
474
475        // If no pre-decoded peaks provided or pre-decoded peaks are
476        // provided with forceDecode flag, attempt to download the
477        // audio file and decode it with Web Audio.
478        if (peaks) { this.backend.setPeaks(peaks); }
479
480        if ((!peaks || this.params.forceDecode) && this.backend.supportsWebAudio()) {
481            this.getArrayBuffer(url, (function (arraybuffer) {
482                this.decodeArrayBuffer(arraybuffer, (function (buffer) {
483                    this.backend.buffer = buffer;
484                    this.backend.setPeaks(null);
485                    this.drawBuffer();
486                    this.fireEvent('waveform-ready');
487                }).bind(this));
488            }).bind(this));
489        }
490    },
491
492    decodeArrayBuffer: function (arraybuffer, callback) {
493        this.arraybuffer = arraybuffer;
494
495        this.backend.decodeArrayBuffer(
496            arraybuffer,
497            (function (data) {
498                // Only use the decoded data if we haven't been destroyed or another decode started in the meantime
499                if (!this.isDestroyed && this.arraybuffer == arraybuffer) {
500                    callback(data);
501                    this.arraybuffer = null;
502                }
503            }).bind(this),
504            this.fireEvent.bind(this, 'error', 'Error decoding audiobuffer')
505        );
506    },
507
508    getArrayBuffer: function (url, callback) {
509        var my = this;
510
511        var ajax = WaveSurfer.util.ajax({
512            url: url,
513            responseType: 'arraybuffer'
514        });
515
516        this.currentAjax = ajax;
517
518        this.tmpEvents.push(
519            ajax.on('progress', function (e) {
520                my.onProgress(e);
521            }),
522            ajax.on('success', function (data, e) {
523                callback(data);
524                my.currentAjax = null;
525            }),
526            ajax.on('error', function (e) {
527                my.fireEvent('error', 'XHR error: ' + e.target.statusText);
528                my.currentAjax = null;
529            })
530        );
531
532        return ajax;
533    },
534
535    onProgress: function (e) {
536        if (e.lengthComputable) {
537            var percentComplete = e.loaded / e.total;
538        } else {
539            // Approximate progress with an asymptotic
540            // function, and assume downloads in the 1-3 MB range.
541            percentComplete = e.loaded / (e.loaded + 1000000);
542        }
543        this.fireEvent('loading', Math.round(percentComplete * 100), e.target);
544    },
545
546    /**
547     * Exports PCM data into a JSON array and opens in a new window.
548     */
549    exportPCM: function (length, accuracy, noWindow) {
550        length = length || 1024;
551        accuracy = accuracy || 10000;
552        noWindow = noWindow || false;
553        var peaks = this.backend.getPeaks(length, accuracy);
554        var arr = [].map.call(peaks, function (val) {
555            return Math.round(val * accuracy) / accuracy;
556        });
557        var json = JSON.stringify(arr);
558        if (!noWindow) {
559            window.open('data:application/json;charset=utf-8,' +
560                encodeURIComponent(json));
561        }
562        return json;
563    },
564
565    /**
566     * Save waveform image as data URI.
567     *
568     * The default format is 'image/png'. Other supported types are
569     * 'image/jpeg' and 'image/webp'.
570     */
571    exportImage: function(format, quality) {
572        if (!format) {
573            format = 'image/png';
574        }
575        if (!quality) {
576            quality = 1;
577        }
578
579        return this.drawer.getImage(format, quality);
580    },
581
582    cancelAjax: function () {
583        if (this.currentAjax) {
584            this.currentAjax.xhr.abort();
585            this.currentAjax = null;
586        }
587    },
588
589    clearTmpEvents: function () {
590        this.tmpEvents.forEach(function (e) { e.un(); });
591    },
592
593    /**
594     * Display empty waveform.
595     */
596    empty: function () {
597        if (!this.backend.isPaused()) {
598            this.stop();
599            this.backend.disconnectSource();
600        }
601        this.cancelAjax();
602        this.clearTmpEvents();
603        this.drawer.progress(0);
604        this.drawer.setWidth(0);
605        this.drawer.drawPeaks({ length: this.drawer.getWidth() }, 0);
606    },
607
608    /**
609     * Remove events, elements and disconnect WebAudio nodes.
610     */
611    destroy: function () {
612        this.fireEvent('destroy');
613        this.cancelAjax();
614        this.clearTmpEvents();
615        this.unAll();
616        this.backend.destroy();
617        this.drawer.destroy();
618        this.isDestroyed = true;
619    }
620};
621
622WaveSurfer.create = function (params) {
623    var wavesurfer = Object.create(WaveSurfer);
624    wavesurfer.init(params);
625    return wavesurfer;
626};
627
628WaveSurfer.util = {
629    extend: function (dest) {
630        var sources = Array.prototype.slice.call(arguments, 1);
631        sources.forEach(function (source) {
632            Object.keys(source).forEach(function (key) {
633                dest[key] = source[key];
634            });
635        });
636        return dest;
637    },
638
639    debounce: function (func, wait, immediate) {
640        var args, context, timeout;
641        var later = function() {
642            timeout = null;
643            if (!immediate) {
644                func.apply(context, args);
645            }
646        };
647        return function() {
648            context = this;
649            args = arguments;
650            var callNow = immediate && !timeout;
651            clearTimeout(timeout);
652            timeout = setTimeout(later, wait);
653            if (!timeout) {
654                timeout = setTimeout(later, wait);
655            }
656            if (callNow) {
657                func.apply(context, args);
658            }
659        };
660    },
661
662    min: function (values) {
663        var min = +Infinity;
664        for (var i in values) {
665            if (values[i] < min) {
666                min = values[i];
667            }
668        }
669
670        return min;
671    },
672
673    max: function (values) {
674        var max = -Infinity;
675        for (var i in values) {
676            if (values[i] > max) {
677                max = values[i];
678            }
679        }
680
681        return max;
682    },
683
684    getId: function () {
685        return 'wavesurfer_' + Math.random().toString(32).substring(2);
686    },
687
688    ajax: function (options) {
689        var ajax = Object.create(WaveSurfer.Observer);
690        var xhr = new XMLHttpRequest();
691        var fired100 = false;
692
693        xhr.open(options.method || 'GET', options.url, true);
694        xhr.responseType = options.responseType || 'json';
695
696        xhr.addEventListener('progress', function (e) {
697            ajax.fireEvent('progress', e);
698            if (e.lengthComputable && e.loaded == e.total) {
699                fired100 = true;
700            }
701        });
702
703        xhr.addEventListener('load', function (e) {
704            if (!fired100) {
705                ajax.fireEvent('progress', e);
706            }
707            ajax.fireEvent('load', e);
708
709            if (200 == xhr.status || 206 == xhr.status) {
710                ajax.fireEvent('success', xhr.response, e);
711            } else {
712                ajax.fireEvent('error', e);
713            }
714        });
715
716        xhr.addEventListener('error', function (e) {
717            ajax.fireEvent('error', e);
718        });
719
720        xhr.send();
721        ajax.xhr = xhr;
722        return ajax;
723    }
724};
725
726/* Observer */
727WaveSurfer.Observer = {
728    /**
729     * Attach a handler function for an event.
730     */
731    on: function (event, fn) {
732        if (!this.handlers) { this.handlers = {}; }
733
734        var handlers = this.handlers[event];
735        if (!handlers) {
736            handlers = this.handlers[event] = [];
737        }
738        handlers.push(fn);
739
740        // Return an event descriptor
741        return {
742            name: event,
743            callback: fn,
744            un: this.un.bind(this, event, fn)
745        };
746    },
747
748    /**
749     * Remove an event handler.
750     */
751    un: function (event, fn) {
752        if (!this.handlers) { return; }
753
754        var handlers = this.handlers[event];
755        if (handlers) {
756            if (fn) {
757                for (var i = handlers.length - 1; i >= 0; i--) {
758                    if (handlers[i] == fn) {
759                        handlers.splice(i, 1);
760                    }
761                }
762            } else {
763                handlers.length = 0;
764            }
765        }
766    },
767
768    /**
769     * Remove all event handlers.
770     */
771    unAll: function () {
772        this.handlers = null;
773    },
774
775    /**
776     * Attach a handler to an event. The handler is executed at most once per
777     * event type.
778     */
779    once: function (event, handler) {
780        var my = this;
781        var fn = function () {
782            handler.apply(this, arguments);
783            setTimeout(function () {
784                my.un(event, fn);
785            }, 0);
786        };
787        return this.on(event, fn);
788    },
789
790    fireEvent: function (event) {
791        if (!this.handlers) { return; }
792        var handlers = this.handlers[event];
793        var args = Array.prototype.slice.call(arguments, 1);
794        handlers && handlers.forEach(function (fn) {
795            fn.apply(null, args);
796        });
797    }
798};
799
800/* Make the main WaveSurfer object an observer */
801WaveSurfer.util.extend(WaveSurfer, WaveSurfer.Observer);
802
803'use strict';
804
805WaveSurfer.WebAudio = {
806    scriptBufferSize: 256,
807    PLAYING_STATE: 0,
808    PAUSED_STATE: 1,
809    FINISHED_STATE: 2,
810
811    supportsWebAudio: function () {
812        return !!(window.AudioContext || window.webkitAudioContext);
813    },
814
815    getAudioContext: function () {
816        if (!WaveSurfer.WebAudio.audioContext) {
817            WaveSurfer.WebAudio.audioContext = new (
818                window.AudioContext || window.webkitAudioContext
819            );
820        }
821        return WaveSurfer.WebAudio.audioContext;
822    },
823
824    getOfflineAudioContext: function (sampleRate) {
825        if (!WaveSurfer.WebAudio.offlineAudioContext) {
826            WaveSurfer.WebAudio.offlineAudioContext = new (
827                window.OfflineAudioContext || window.webkitOfflineAudioContext
828            )(1, 2, sampleRate);
829        }
830        return WaveSurfer.WebAudio.offlineAudioContext;
831    },
832
833    init: function (params) {
834        this.params = params;
835        this.ac = params.audioContext || this.getAudioContext();
836
837        this.lastPlay = this.ac.currentTime;
838        this.startPosition = 0;
839        this.scheduledPause = null;
840
841        this.states = [
842            Object.create(WaveSurfer.WebAudio.state.playing),
843            Object.create(WaveSurfer.WebAudio.state.paused),
844            Object.create(WaveSurfer.WebAudio.state.finished)
845        ];
846
847        this.createVolumeNode();
848        this.createScriptNode();
849        this.createAnalyserNode();
850
851        this.setState(this.PAUSED_STATE);
852        this.setPlaybackRate(this.params.audioRate);
853        this.setLength(0);
854    },
855
856    disconnectFilters: function () {
857        if (this.filters) {
858            this.filters.forEach(function (filter) {
859                filter && filter.disconnect();
860            });
861            this.filters = null;
862            // Reconnect direct path
863            this.analyser.connect(this.gainNode);
864        }
865    },
866
867    setState: function (state) {
868        if (this.state !== this.states[state]) {
869            this.state = this.states[state];
870            this.state.init.call(this);
871        }
872    },
873
874    // Unpacked filters
875    setFilter: function () {
876        this.setFilters([].slice.call(arguments));
877    },
878
879    /**
880     * @param {Array} filters Packed ilters array
881     */
882    setFilters: function (filters) {
883        // Remove existing filters
884        this.disconnectFilters();
885
886        // Insert filters if filter array not empty
887        if (filters && filters.length) {
888            this.filters = filters;
889
890            // Disconnect direct path before inserting filters
891            this.analyser.disconnect();
892
893            // Connect each filter in turn
894            filters.reduce(function (prev, curr) {
895                prev.connect(curr);
896                return curr;
897            }, this.analyser).connect(this.gainNode);
898        }
899
900    },
901
902    createScriptNode: function () {
903        if (this.ac.createScriptProcessor) {
904            this.scriptNode = this.ac.createScriptProcessor(this.scriptBufferSize);
905        } else {
906            this.scriptNode = this.ac.createJavaScriptNode(this.scriptBufferSize);
907        }
908
909        this.scriptNode.connect(this.ac.destination);
910    },
911
912    addOnAudioProcess: function () {
913        var my = this;
914
915        this.scriptNode.onaudioprocess = function () {
916            var time = my.getCurrentTime();
917
918            if (time >= my.getDuration()) {
919                my.setState(my.FINISHED_STATE);
920                my.fireEvent('pause');
921            } else if (time >= my.scheduledPause) {
922                my.pause();
923            } else if (my.state === my.states[my.PLAYING_STATE]) {
924                my.fireEvent('audioprocess', time);
925            }
926        };
927    },
928
929    removeOnAudioProcess: function () {
930        this.scriptNode.onaudioprocess = null;
931    },
932
933    createAnalyserNode: function () {
934        this.analyser = this.ac.createAnalyser();
935        this.analyser.connect(this.gainNode);
936    },
937
938    /**
939     * Create the gain node needed to control the playback volume.
940     */
941    createVolumeNode: function () {
942        // Create gain node using the AudioContext
943        if (this.ac.createGain) {
944            this.gainNode = this.ac.createGain();
945        } else {
946            this.gainNode = this.ac.createGainNode();
947        }
948        // Add the gain node to the graph
949        this.gainNode.connect(this.ac.destination);
950    },
951
952    /**
953     * Set the gain to a new value.
954     *
955     * @param {Number} newGain The new gain, a floating point value
956     * between 0 and 1. 0 being no gain and 1 being maximum gain.
957     */
958    setVolume: function (newGain) {
959        this.gainNode.gain.value = newGain;
960    },
961
962    /**
963     * Get the current gain.
964     *
965     * @returns {Number} The current gain, a floating point value
966     * between 0 and 1. 0 being no gain and 1 being maximum gain.
967     */
968    getVolume: function () {
969        return this.gainNode.gain.value;
970    },
971
972    decodeArrayBuffer: function (arraybuffer, callback, errback) {
973        if (!this.offlineAc) {
974            this.offlineAc = this.getOfflineAudioContext(this.ac ? this.ac.sampleRate : 44100);
975        }
976        this.offlineAc.decodeAudioData(arraybuffer, (function (data) {
977            callback(data);
978        }).bind(this), errback);
979    },
980
981    /**
982     * Set pre-decoded peaks.
983     */
984    setPeaks: function (peaks) {
985        this.peaks = peaks;
986    },
987
988    /**
989     * Set the rendered length (different from the length of the audio).
990     */
991    setLength: function (length) {
992        // No resize, we can preserve the cached peaks.
993        if (this.mergedPeaks && length == ((2 * this.mergedPeaks.length - 1) + 2)) {
994          return;
995        }
996
997        this.splitPeaks = [];
998        this.mergedPeaks = [];
999        // Set the last element of the sparse array so the peak arrays are
1000        // appropriately sized for other calculations.
1001        var channels = this.buffer ? this.buffer.numberOfChannels : 1;
1002        for (var c = 0; c < channels; c++) {
1003          this.splitPeaks[c] = [];
1004          this.splitPeaks[c][2 * (length - 1)] = 0;
1005          this.splitPeaks[c][2 * (length - 1) + 1] = 0;
1006        }
1007        this.mergedPeaks[2 * (length - 1)] = 0;
1008        this.mergedPeaks[2 * (length - 1) + 1] = 0;
1009    },
1010
1011    /**
1012     * Compute the max and min value of the waveform when broken into
1013     * <length> subranges.
1014     * @param {Number} length How many subranges to break the waveform into.
1015     * @param {Number} first First sample in the required range.
1016     * @param {Number} last Last sample in the required range.
1017     * @returns {Array} Array of 2*<length> peaks or array of arrays
1018     * of peaks consisting of (max, min) values for each subrange.
1019     */
1020    getPeaks: function (length, first, last) {
1021        if (this.peaks) { return this.peaks; }
1022
1023        this.setLength(length);
1024
1025        var sampleSize = this.buffer.length / length;
1026        var sampleStep = ~~(sampleSize / 10) || 1;
1027        var channels = this.buffer.numberOfChannels;
1028
1029        for (var c = 0; c < channels; c++) {
1030            var peaks = this.splitPeaks[c];
1031            var chan = this.buffer.getChannelData(c);
1032
1033            for (var i = first; i <= last; i++) {
1034                var start = ~~(i * sampleSize);
1035                var end = ~~(start + sampleSize);
1036                var min = 0;
1037                var max = 0;
1038
1039                for (var j = start; j < end; j += sampleStep) {
1040                    var value = chan[j];
1041
1042                    if (value > max) {
1043                        max = value;
1044                    }
1045
1046                    if (value < min) {
1047                        min = value;
1048                    }
1049                }
1050
1051                peaks[2 * i] = max;
1052                peaks[2 * i + 1] = min;
1053
1054                if (c == 0 || max > this.mergedPeaks[2 * i]) {
1055                    this.mergedPeaks[2 * i] = max;
1056                }
1057
1058                if (c == 0 || min < this.mergedPeaks[2 * i + 1]) {
1059                    this.mergedPeaks[2 * i + 1] = min;
1060                }
1061            }
1062        }
1063
1064        return this.params.splitChannels ? this.splitPeaks : this.mergedPeaks;
1065    },
1066
1067    getPlayedPercents: function () {
1068        return this.state.getPlayedPercents.call(this);
1069    },
1070
1071    disconnectSource: function () {
1072        if (this.source) {
1073            this.source.disconnect();
1074        }
1075    },
1076
1077    destroy: function () {
1078        if (!this.isPaused()) {
1079            this.pause();
1080        }
1081        this.unAll();
1082        this.buffer = null;
1083        this.disconnectFilters();
1084        this.disconnectSource();
1085        this.gainNode.disconnect();
1086        this.scriptNode.disconnect();
1087        this.analyser.disconnect();
1088        // close the audioContext if closeAudioContext option is set to true
1089        if (this.params.closeAudioContext) {
1090            // check if browser supports AudioContext.close()
1091            if (typeof this.ac.close === 'function' && this.ac.state != 'closed') {
1092                this.ac.close();
1093            }
1094            // clear the reference to the audiocontext
1095            this.ac = null;
1096            // clear the actual audiocontext, either passed as param or the
1097            // global singleton
1098            if (!this.params.audioContext) {
1099                WaveSurfer.WebAudio.audioContext = null;
1100            } else {
1101                this.params.audioContext = null;
1102            }
1103            // clear the offlineAudioContext
1104            WaveSurfer.WebAudio.offlineAudioContext = null;
vendor: 8,604 bytes, lines 1104-1428
1104
1105        }
1106    },
1107
1108    load: function (buffer) {
1109        this.startPosition = 0;
1110        this.lastPlay = this.ac.currentTime;
1111        this.buffer = buffer;
1112        this.createSource();
1113    },
1114
1115    createSource: function () {
1116        this.disconnectSource();
1117        this.source = this.ac.createBufferSource();
1118
1119        //adjust for old browsers.
1120        this.source.start = this.source.start || this.source.noteGrainOn;
1121        this.source.stop = this.source.stop || this.source.noteOff;
1122
1123        this.source.playbackRate.value = this.playbackRate;
1124        this.source.buffer = this.buffer;
1125        this.source.connect(this.analyser);
1126    },
1127
1128    isPaused: function () {
1129        return this.state !== this.states[this.PLAYING_STATE];
1130    },
1131
1132    getDuration: function () {
1133        if (!this.buffer) {
1134            return 0;
1135        }
1136        return this.buffer.duration;
1137    },
1138
1139    seekTo: function (start, end) {
1140        if (!this.buffer) { return; }
1141
1142        this.scheduledPause = null;
1143
1144        if (start == null) {
1145            start = this.getCurrentTime();
1146            if (start >= this.getDuration()) {
1147                start = 0;
1148            }
1149        }
1150        if (end == null) {
1151            end = this.getDuration();
1152        }
1153
1154        this.startPosition = start;
1155        this.lastPlay = this.ac.currentTime;
1156
1157        if (this.state === this.states[this.FINISHED_STATE]) {
1158            this.setState(this.PAUSED_STATE);
1159        }
1160
1161        return { start: start, end: end };
1162    },
1163
1164    getPlayedTime: function () {
1165        return (this.ac.currentTime - this.lastPlay) * this.playbackRate;
1166    },
1167
1168    /**
1169     * Plays the loaded audio region.
1170     *
1171     * @param {Number} start Start offset in seconds,
1172     * relative to the beginning of a clip.
1173     * @param {Number} end When to stop
1174     * relative to the beginning of a clip.
1175     */
1176    play: function (start, end) {
1177        if (!this.buffer) { return; }
1178
1179        // need to re-create source on each playback
1180        this.createSource();
1181
1182        var adjustedTime = this.seekTo(start, end);
1183
1184        start = adjustedTime.start;
1185        end = adjustedTime.end;
1186
1187        this.scheduledPause = end;
1188
1189        this.source.start(0, start, end - start);
1190
1191        if (this.ac.state == 'suspended') {
1192          this.ac.resume && this.ac.resume();
1193        }
1194
1195        this.setState(this.PLAYING_STATE);
1196
1197        this.fireEvent('play');
1198    },
1199
1200    /**
1201     * Pauses the loaded audio.
1202     */
1203    pause: function () {
1204        this.scheduledPause = null;
1205
1206        this.startPosition += this.getPlayedTime();
1207        this.source && this.source.stop(0);
1208
1209        this.setState(this.PAUSED_STATE);
1210
1211        this.fireEvent('pause');
1212    },
1213
1214    /**
1215    *   Returns the current time in seconds relative to the audioclip's duration.
1216    */
1217    getCurrentTime: function () {
1218        return this.state.getCurrentTime.call(this);
1219    },
1220
1221    /**
1222    *   Returns the current playback rate.
1223    */
1224    getPlaybackRate: function () {
1225        return this.playbackRate;
1226    },
1227
1228    /**
1229     * Set the audio source playback rate.
1230     */
1231    setPlaybackRate: function (value) {
1232        value = value || 1;
1233        if (this.isPaused()) {
1234            this.playbackRate = value;
1235        } else {
1236            this.pause();
1237            this.playbackRate = value;
1238            this.play();
1239        }
1240    }
1241};
1242
1243WaveSurfer.WebAudio.state = {};
1244
1245WaveSurfer.WebAudio.state.playing = {
1246    init: function () {
1247        this.addOnAudioProcess();
1248    },
1249    getPlayedPercents: function () {
1250        var duration = this.getDuration();
1251        return (this.getCurrentTime() / duration) || 0;
1252    },
1253    getCurrentTime: function () {
1254        return this.startPosition + this.getPlayedTime();
1255    }
1256};
1257
1258WaveSurfer.WebAudio.state.paused = {
1259    init: function () {
1260        this.removeOnAudioProcess();
1261    },
1262    getPlayedPercents: function () {
1263        var duration = this.getDuration();
1264        return (this.getCurrentTime() / duration) || 0;
1265    },
1266    getCurrentTime: function () {
1267        return this.startPosition;
1268    }
1269};
1270
1271WaveSurfer.WebAudio.state.finished = {
1272    init: function () {
1273        this.removeOnAudioProcess();
1274        this.fireEvent('finish');
1275    },
1276    getPlayedPercents: function () {
1277        return 1;
1278    },
1279    getCurrentTime: function () {
1280        return this.getDuration();
1281    }
1282};
1283
1284WaveSurfer.util.extend(WaveSurfer.WebAudio, WaveSurfer.Observer);
1285
1286'use strict';
1287
1288WaveSurfer.MediaElement = Object.create(WaveSurfer.WebAudio);
1289
1290WaveSurfer.util.extend(WaveSurfer.MediaElement, {
1291    init: function (params) {
1292        this.params = params;
1293
1294        // Dummy media to catch errors
1295        this.media = {
1296            currentTime: 0,
1297            duration: 0,
1298            paused: true,
1299            playbackRate: 1,
1300            play: function () {},
1301            pause: function () {}
1302        };
1303
1304        this.mediaType = params.mediaType.toLowerCase();
1305        this.elementPosition = params.elementPosition;
1306        this.setPlaybackRate(this.params.audioRate);
1307        this.createTimer();
1308    },
1309
1310
1311    /**
1312     * Create a timer to provide a more precise `audioprocess' event.
1313     */
1314    createTimer: function () {
1315        var my = this;
1316        var playing = false;
1317
1318        var onAudioProcess = function () {
1319            if (my.isPaused()) { return; }
1320
1321            my.fireEvent('audioprocess', my.getCurrentTime());
1322
1323            // Call again in the next frame
1324            var requestAnimationFrame = window.requestAnimationFrame || window.webkitRequestAnimationFrame;
1325            requestAnimationFrame(onAudioProcess);
1326        };
1327
1328        this.on('play', onAudioProcess);
1329    },
1330
1331    /**
1332     *  Create media element with url as its source,
1333     *  and append to container element.
1334     *  @param  {String}        url         path to media file
1335     *  @param  {HTMLElement}   container   HTML element
1336     *  @param  {Array}         peaks       array of peak data
1337     *  @param  {String}        preload     HTML 5 preload attribute value
1338     */
1339    load: function (url, container, peaks, preload) {
1340        var my = this;
1341
1342        var media = document.createElement(this.mediaType);
1343        media.controls = this.params.mediaControls;
1344        media.autoplay = this.params.autoplay || false;
1345        media.preload = preload == null ? 'auto' : preload;
1346        media.src = url;
1347        media.style.width = '100%';
1348
1349        var prevMedia = container.querySelector(this.mediaType);
1350        if (prevMedia) {
1351            container.removeChild(prevMedia);
1352        }
1353        container.appendChild(media);
1354
1355        this._load(media, peaks);
1356    },
1357
1358    /**
1359     *  Load existing media element.
1360     *  @param  {MediaElement}  elt     HTML5 Audio or Video element
1361     *  @param  {Array}         peaks   array of peak data
1362     */
1363    loadElt: function (elt, peaks) {
1364        var my = this;
1365
1366        var media = elt;
1367        media.controls = this.params.mediaControls;
1368        media.autoplay = this.params.autoplay || false;
1369
1370        this._load(media, peaks);
1371    },
1372
1373    /**
1374     *  Private method called by both load (from url)
1375     *  and loadElt (existing media element).
1376     *  @param  {MediaElement}  media     HTML5 Audio or Video element
1377     *  @param  {Array}         peaks   array of peak data
1378     *  @private
1379     */
1380    _load: function (media, peaks) {
1381        var my = this;
1382
1383        // load must be called manually on iOS, otherwise peaks won't draw
1384        // until a user interaction triggers load --> 'ready' event
1385        if (typeof media.load == 'function') {
1386            media.load();
1387        }
1388
1389        media.addEventListener('error', function () {
1390            my.fireEvent('error', 'Error loading media element');
1391        });
1392
1393        media.addEventListener('canplay', function () {
1394            my.fireEvent('canplay');
1395        });
1396
1397        media.addEventListener('ended', function () {
1398            my.fireEvent('finish');
1399        });
1400
1401        this.media = media;
1402        this.peaks = peaks;
1403        this.onPlayEnd = null;
1404        this.buffer = null;
1405        this.setPlaybackRate(this.playbackRate);
1406    },
1407
1408    isPaused: function () {
1409        return !this.media || this.media.paused;
1410    },
1411
1412    getDuration: function () {
1413        var duration = (this.buffer || this.media).duration;
1414        if (duration >= Infinity) { // streaming audio
1415            duration = this.media.seekable.end(0);
1416        }
1417        return duration;
1418    },
1419
1420    getCurrentTime: function () {
1421        return this.media && this.media.currentTime;
1422    },
1423
1424    getPlayedPercents: function () {
1425        return (this.getCurrentTime() / this.getDuration()) || 0;
1426    },
1427
1428    
1428getPlaybackRate: function () {
1429        return this.playbackRate || this.media.playbackRate
vendor: 11,954 bytes, lines 1429-1841
1429;
1430    },
1431
1432    /**
1433     * Set the audio source playback rate.
1434     */
1435    setPlaybackRate: function (value) {
1436        this.playbackRate = value || 1;
1437        this.media.playbackRate = this.playbackRate;
1438    },
1439
1440    seekTo: function (start) {
1441        if (start != null) {
1442            this.media.currentTime = start;
1443        }
1444        this.clearPlayEnd();
1445    },
1446
1447    /**
1448     * Plays the loaded audio region.
1449     *
1450     * @param {Number} start Start offset in seconds,
1451     * relative to the beginning of a clip.
1452     * @param {Number} end End offset in seconds,
1453     * relative to the beginning of a clip.
1454     */
1455    play: function (start, end) {
1456        this.seekTo(start);
1457        this.media.play();
1458        end && this.setPlayEnd(end);
1459        this.fireEvent('play');
1460    },
1461
1462    /**
1463     * Pauses the loaded audio.
1464     */
1465    pause: function () {
1466        this.media && this.media.pause();
1467        this.clearPlayEnd();
1468        this.fireEvent('pause');
1469    },
1470
1471    setPlayEnd: function (end) {
1472        var my = this;
1473        this.onPlayEnd = function (time) {
1474            if (time >= end) {
1475                my.pause();
1476                my.seekTo(end);
1477            }
1478        };
1479        this.on('audioprocess', this.onPlayEnd);
1480    },
1481
1482    clearPlayEnd: function () {
1483        if (this.onPlayEnd) {
1484            this.un('audioprocess', this.onPlayEnd);
1485            this.onPlayEnd = null;
1486        }
1487    },
1488
1489    getPeaks: function (length, start, end) {
1490        if (this.buffer) {
1491            return WaveSurfer.WebAudio.getPeaks.call(this, length, start, end);
1492        }
1493        return this.peaks || [];
1494    },
1495
1496    getVolume: function () {
1497        return this.media.volume;
1498    },
1499
1500    setVolume: function (val) {
1501        this.media.volume = val;
1502    },
1503
1504    destroy: function () {
1505        this.pause();
1506        this.unAll();
1507        this.media && this.media.parentNode && this.media.parentNode.removeChild(this.media);
1508        this.media = null;
1509    }
1510});
1511
1512//For backwards compatibility
1513WaveSurfer.AudioElement = WaveSurfer.MediaElement;
1514
1515'use strict';
1516
1517WaveSurfer.Drawer = {
1518    init: function (container, params) {
1519        this.container = container;
1520        this.params = params;
1521
1522        this.width = 0;
1523        this.height = params.height * this.params.pixelRatio;
1524
1525        this.lastPos = 0;
1526
1527        this.initDrawer(params);
1528        this.createWrapper();
1529        this.createElements();
1530    },
1531
1532    createWrapper: function () {
1533        this.wrapper = this.container.appendChild(
1534            document.createElement('wave')
1535        );
1536
1537        this.style(this.wrapper, {
1538            display: 'block',
1539            position: 'relative',
1540            userSelect: 'none',
1541            webkitUserSelect: 'none',
1542            height: this.params.height + 'px'
1543        });
1544
1545        if (this.params.fillParent || this.params.scrollParent) {
1546            this.style(this.wrapper, {
1547                width: '100%',
1548                overflowX: this.params.hideScrollbar ? 'hidden' : 'auto',
1549                overflowY: 'hidden'
1550            });
1551        }
1552
1553        this.setupWrapperEvents();
1554    },
1555
1556    handleEvent: function (e, noPrevent) {
1557        !noPrevent && e.preventDefault();
1558
1559        var clientX = e.targetTouches ? e.targetTouches[0].clientX : e.clientX;
1560        var bbox = this.wrapper.getBoundingClientRect();
1561
1562        var nominalWidth = this.width;
1563        var parentWidth = this.getWidth();
1564
1565        var progress;
1566
1567        if (!this.params.fillParent && nominalWidth < parentWidth) {
1568            progress = ((clientX - bbox.left) * this.params.pixelRatio / nominalWidth) || 0;
1569
1570            if (progress > 1) {
1571                progress = 1;
1572            }
1573        } else {
1574            progress = ((clientX - bbox.left + this.wrapper.scrollLeft) / this.wrapper.scrollWidth) || 0;
1575        }
1576
1577        return progress;
1578    },
1579
1580    setupWrapperEvents: function () {
1581        var my = this;
1582
1583        this.wrapper.addEventListener('click', function (e) {
1584            var scrollbarHeight = my.wrapper.offsetHeight - my.wrapper.clientHeight;
1585            if (scrollbarHeight != 0) {
1586                // scrollbar is visible.  Check if click was on it
1587                var bbox = my.wrapper.getBoundingClientRect();
1588                if (e.clientY >= bbox.bottom - scrollbarHeight) {
1589                    // ignore mousedown as it was on the scrollbar
1590                    return;
1591                }
1592            }
1593
1594            if (my.params.interact) {
1595                my.fireEvent('click', e, my.handleEvent(e));
1596            }
1597        });
1598
1599        this.wrapper.addEventListener('scroll', function (e) {
1600            my.fireEvent('scroll', e);
1601        });
1602    },
1603
1604    drawPeaks: function (peaks, length, start, end) {
1605        this.setWidth(length);
1606
1607        this.params.barWidth ?
1608            this.drawBars(peaks, 0, start, end) :
1609            this.drawWave(peaks, 0, start, end);
1610    },
1611
1612    style: function (el, styles) {
1613        Object.keys(styles).forEach(function (prop) {
1614            if (el.style[prop] !== styles[prop]) {
1615                el.style[prop] = styles[prop];
1616            }
1617        });
1618        return el;
1619    },
1620
1621    resetScroll: function () {
1622        if (this.wrapper !== null) {
1623            this.wrapper.scrollLeft = 0;
1624        }
1625    },
1626
1627    recenter: function (percent) {
1628        var position = this.wrapper.scrollWidth * percent;
1629        this.recenterOnPosition(position, true);
1630    },
1631
1632    recenterOnPosition: function (position, immediate) {
1633        var scrollLeft = this.wrapper.scrollLeft;
1634        var half = ~~(this.wrapper.clientWidth / 2);
1635        var target = position - half;
1636        var offset = target - scrollLeft;
1637        var maxScroll = this.wrapper.scrollWidth - this.wrapper.clientWidth;
1638
1639        if (maxScroll == 0) {
1640            // no need to continue if scrollbar is not there
1641            return;
1642        }
1643
1644        // if the cursor is currently visible...
1645        if (!immediate && -half <= offset && offset < half) {
1646            // we'll limit the "re-center" rate.
1647            var rate = 5;
1648            offset = Math.max(-rate, Math.min(rate, offset));
1649            target = scrollLeft + offset;
1650        }
1651
1652        // limit target to valid range (0 to maxScroll)
1653        target = Math.max(0, Math.min(maxScroll, target));
1654        // no use attempting to scroll if we're not moving
1655        if (target != scrollLeft) {
1656            this.wrapper.scrollLeft = target;
1657        }
1658
1659    },
1660
1661    getScrollX: function() {
1662        return Math.round(this.wrapper.scrollLeft * this.params.pixelRatio);
1663    },
1664
1665    getWidth: function () {
1666        return Math.round(this.container.clientWidth * this.params.pixelRatio);
1667    },
1668
1669    setWidth: function (width) {
1670        if (this.width == width) {
1671          return;
1672        }
1673
1674        this.width = width;
1675
1676        if (this.params.fillParent || this.params.scrollParent) {
1677            this.style(this.wrapper, {
1678                width: ''
1679            });
1680        } else {
1681            this.style(this.wrapper, {
1682                width: ~~(this.width / this.params.pixelRatio) + 'px'
1683            });
1684        }
1685
1686        this.updateSize();
1687    },
1688
1689    setHeight: function (height) {
1690        if (height == this.height) { return; }
1691        this.height = height;
1692        this.style(this.wrapper, {
1693            height: ~~(this.height / this.params.pixelRatio) + 'px'
1694        });
1695        this.updateSize();
1696    },
1697
1698    progress: function (progress) {
1699        var minPxDelta = 1 / this.params.pixelRatio;
1700        var pos = Math.round(progress * this.width) * minPxDelta;
1701
1702        if (pos < this.lastPos || pos - this.lastPos >= minPxDelta) {
1703            this.lastPos = pos;
1704
1705            if (this.params.scrollParent && this.params.autoCenter) {
1706                var newPos = ~~(this.wrapper.scrollWidth * progress);
1707                this.recenterOnPosition(newPos);
1708            }
1709
1710            this.updateProgress(pos);
1711        }
1712    },
1713
1714    destroy: function () {
1715        this.unAll();
1716        if (this.wrapper) {
1717            this.container.removeChild(this.wrapper);
1718            this.wrapper = null;
1719        }
1720    },
1721
1722    /* Renderer-specific methods */
1723    initDrawer: function () {},
1724
1725    createElements: function () {},
1726
1727    updateSize: function () {},
1728
1729    drawWave: function (peaks, max) {},
1730
1731    clearWave: function () {},
1732
1733    updateProgress: function (position) {}
1734};
1735
1736WaveSurfer.util.extend(WaveSurfer.Drawer, WaveSurfer.Observer);
1737
1738'use strict';
1739
1740WaveSurfer.Drawer.Canvas = Object.create(WaveSurfer.Drawer);
1741
1742WaveSurfer.util.extend(WaveSurfer.Drawer.Canvas, {
1743    createElements: function () {
1744        var waveCanvas = this.wrapper.appendChild(
1745            this.style(document.createElement('canvas'), {
1746                position: 'absolute',
1747                zIndex: 1,
1748                left: 0,
1749                top: 0,
1750                bottom: 0
1751            })
1752        );
1753        this.waveCc = waveCanvas.getContext('2d');
1754
1755        this.progressWave = this.wrapper.appendChild(
1756            this.style(document.createElement('wave'), {
1757                position: 'absolute',
1758                zIndex: 2,
1759                left: 0,
1760                top: 0,
1761                bottom: 0,
1762                overflow: 'hidden',
1763                width: '0',
1764                display: 'none',
1765                boxSizing: 'border-box',
1766                borderRightStyle: 'solid',
1767                borderRightWidth: this.params.cursorWidth + 'px',
1768                borderRightColor: this.params.cursorColor
1769            })
1770        );
1771
1772        if (this.params.waveColor != this.params.progressColor) {
1773            var progressCanvas = this.progressWave.appendChild(
1774                document.createElement('canvas')
1775            );
1776            this.progressCc = progressCanvas.getContext('2d');
1777        }
1778    },
1779
1780    updateSize: function () {
1781        var width = Math.round(this.width / this.params.pixelRatio);
1782
1783        this.waveCc.canvas.width = this.width;
1784        this.waveCc.canvas.height = this.height;
1785        this.style(this.waveCc.canvas, { width: width + 'px'});
1786
1787        this.style(this.progressWave, { display: 'block'});
1788
1789        if (this.progressCc) {
1790            this.progressCc.canvas.width = this.width;
1791            this.progressCc.canvas.height = this.height;
1792            this.style(this.progressCc.canvas, { width: width + 'px'});
1793        }
1794
1795        this.clearWave();
1796    },
1797
1798    clearWave: function () {
1799        this.waveCc.clearRect(0, 0, this.width, this.height);
1800        if (this.progressCc) {
1801            this.progressCc.clearRect(0, 0, this.width, this.height);
1802        }
1803    },
1804
1805    drawBars: function (peaks, channelIndex, start, end) {
1806        var my = this;
1807        // Split channels
1808        if (peaks[0] instanceof Array) {
1809            var channels = peaks;
1810            if (this.params.splitChannels) {
1811                this.setHeight(channels.length * this.params.height * this.params.pixelRatio);
1812                channels.forEach(function(channelPeaks, i) {
1813                    my.drawBars(channelPeaks, i, start, end);
1814                });
1815                return;
1816            } else {
1817                peaks = channels[0];
1818            }
1819        }
1820
1821        // Bar wave draws the bottom only as a reflection of the top,
1822        // so we don't need negative values
1823        var hasMinVals = [].some.call(peaks, function (val) { return val < 0; });
1824        // Skip every other value if there are negatives.
1825        var peakIndexScale = 1;
1826        if (hasMinVals) {
1827            peakIndexScale = 2;
1828        }
1829
1830        // A half-pixel offset makes lines crisp
1831        var $ = 0.5 / this.params.pixelRatio;
1832        var width = this.width;
1833        var height = this.params.height * this.params.pixelRatio;
1834        var offsetY = height * channelIndex || 0;
1835        var halfH = height / 2;
1836        var length = peaks.length / peakIndexScale;
1837        var bar = this.params.barWidth * this.params.pixelRatio;
1838        var gap = Math.max(this.params.pixelRatio, ~~(bar / 2));
1839        var step = bar + gap;
1840
1841        var absmax = 1
vendor: 15,899 bytes, lines 1841-2286
1841 / this.params.barHeight;
1842        if (this.params.normalize) {
1843            var max = WaveSurfer.util.max(peaks);
1844            var min = WaveSurfer.util.min(peaks);
1845            absmax = -min > max ? -min : max;
1846        }
1847
1848        var scale = length / width;
1849
1850        this.waveCc.fillStyle = this.params.waveColor;
1851        if (this.progressCc) {
1852            this.progressCc.fillStyle = this.params.progressColor;
1853        }
1854
1855        [ this.waveCc, this.progressCc ].forEach(function (cc) {
1856            if (!cc) { return; }
1857
1858            for (var i = (start / scale); i < (end / scale); i += step) {
1859                var peak = peaks[Math.floor(i * scale * peakIndexScale)] || 0;
1860                var h = Math.round(peak / absmax * halfH);
1861                cc.fillRect(i + $, halfH - h + offsetY, bar + $, h * 2);
1862            }
1863        }, this);
1864    },
1865
1866    drawWave: function (peaks, channelIndex, start, end) {
1867        var my = this;
1868        // Split channels
1869        if (peaks[0] instanceof Array) {
1870            var channels = peaks;
1871            if (this.params.splitChannels) {
1872                this.setHeight(channels.length * this.params.height * this.params.pixelRatio);
1873                channels.forEach(function(channelPeaks, i) {
1874                    my.drawWave(channelPeaks, i, start, end);
1875                });
1876                return;
1877            } else {
1878                peaks = channels[0];
1879            }
1880        }
1881
1882        // Support arrays without negative peaks
1883        var hasMinValues = [].some.call(peaks, function (val) { return val < 0; });
1884        if (!hasMinValues) {
1885            var reflectedPeaks = [];
1886            for (var i = 0, len = peaks.length; i < len; i++) {
1887                reflectedPeaks[2 * i] = peaks[i];
1888                reflectedPeaks[2 * i + 1] = -peaks[i];
1889            }
1890            peaks = reflectedPeaks;
1891        }
1892
1893        // A half-pixel offset makes lines crisp
1894        var $ = 0.5 / this.params.pixelRatio;
1895        var height = this.params.height * this.params.pixelRatio;
1896        var offsetY = height * channelIndex || 0;
1897        var halfH = height / 2;
1898        var length = ~~(peaks.length / 2);
1899
1900        var scale = 1;
1901        if (this.params.fillParent && this.width != length) {
1902            scale = this.width / length;
1903        }
1904
1905        var absmax = 1 / this.params.barHeight;
1906        if (this.params.normalize) {
1907            var max = WaveSurfer.util.max(peaks);
1908            var min = WaveSurfer.util.min(peaks);
1909            absmax = -min > max ? -min : max;
1910        }
1911
1912        this.waveCc.fillStyle = this.params.waveColor;
1913        if (this.progressCc) {
1914            this.progressCc.fillStyle = this.params.progressColor;
1915        }
1916
1917        [ this.waveCc, this.progressCc ].forEach(function (cc) {
1918            if (!cc) { return; }
1919
1920            cc.beginPath();
1921            cc.moveTo(start * scale + $, halfH + offsetY);
1922
1923            for (var i = start; i < end; i++) {
1924                var h = Math.round(peaks[2 * i] / absmax * halfH);
1925                cc.lineTo(i * scale + $, halfH - h + offsetY);
1926            }
1927
1928            // Draw the bottom edge going backwards, to make a single
1929            // closed hull to fill.
1930            for (var i = end - 1; i >= start; i--) {
1931                var h = Math.round(peaks[2 * i + 1] / absmax * halfH);
1932                cc.lineTo(i * scale + $, halfH - h + offsetY);
1933            }
1934
1935            cc.closePath();
1936            cc.fill();
1937
1938            // Always draw a median line
1939            cc.fillRect(0, halfH + offsetY - $, this.width, $);
1940        }, this);
1941    },
1942
1943    updateProgress: function (pos) {
1944        this.style(this.progressWave, { width: pos + 'px' });
1945    },
1946
1947    getImage: function(type, quality) {
1948        return this.waveCc.canvas.toDataURL(type, quality);
1949    }
1950});
1951
1952'use strict';
1953
1954WaveSurfer.Drawer.MultiCanvas = Object.create(WaveSurfer.Drawer);
1955
1956WaveSurfer.util.extend(WaveSurfer.Drawer.MultiCanvas, {
1957
1958    initDrawer: function (params) {
1959        this.maxCanvasWidth = params.maxCanvasWidth != null ? params.maxCanvasWidth : 4000;
1960        this.maxCanvasElementWidth = Math.round(this.maxCanvasWidth / this.params.pixelRatio);
1961
1962        if (this.maxCanvasWidth <= 1) {
1963            throw 'maxCanvasWidth must be greater than 1.';
1964        } else if (this.maxCanvasWidth % 2 == 1) {
1965            throw 'maxCanvasWidth must be an even number.';
1966        }
1967
1968        this.hasProgressCanvas = this.params.waveColor != this.params.progressColor;
1969        this.halfPixel = 0.5 / this.params.pixelRatio;
1970        this.canvases = [];
1971    },
1972
1973    createElements: function () {
1974        this.progressWave = this.wrapper.appendChild(
1975            this.style(document.createElement('wave'), {
1976                position: 'absolute',
1977                zIndex: 2,
1978                left: 0,
1979                top: 0,
1980                bottom: 0,
1981                overflow: 'hidden',
1982                width: '0',
1983                display: 'none',
1984                boxSizing: 'border-box',
1985                borderRightStyle: 'solid',
1986                borderRightWidth: this.params.cursorWidth + 'px',
1987                borderRightColor: this.params.cursorColor
1988            })
1989        );
1990
1991        this.addCanvas();
1992    },
1993
1994    updateSize: function () {
1995        var totalWidth = Math.round(this.width / this.params.pixelRatio),
1996            requiredCanvases = Math.ceil(totalWidth / this.maxCanvasElementWidth);
1997
1998        while (this.canvases.length < requiredCanvases) {
1999            this.addCanvas();
2000        }
2001
2002        while (this.canvases.length > requiredCanvases) {
2003            this.removeCanvas();
2004        }
2005
2006        for (var i in this.canvases) {
2007            // Add some overlap to prevent vertical white stripes, keep the width even for simplicity.
2008            var canvasWidth = this.maxCanvasWidth + 2 * Math.ceil(this.params.pixelRatio / 2);
2009
2010            if (i == this.canvases.length - 1) {
2011                canvasWidth = this.width - (this.maxCanvasWidth * (this.canvases.length - 1));
2012            }
2013
2014            this.updateDimensions(this.canvases[i], canvasWidth, this.height);
2015            this.clearWaveForEntry(this.canvases[i]);
2016        }
2017    },
2018
2019    addCanvas: function () {
2020        var entry = {},
2021            leftOffset = this.maxCanvasElementWidth * this.canvases.length;
2022
2023        entry.wave = this.wrapper.appendChild(
2024            this.style(document.createElement('canvas'), {
2025                position: 'absolute',
2026                zIndex: 1,
2027                left: leftOffset + 'px',
2028                top: 0,
2029                bottom: 0,
2030                height: '100%'
2031            })
2032        );
2033        entry.waveCtx = entry.wave.getContext('2d');
2034
2035        if (this.hasProgressCanvas) {
2036            entry.progress = this.progressWave.appendChild(
2037                this.style(document.createElement('canvas'), {
2038                    position: 'absolute',
2039                    left: leftOffset + 'px',
2040                    top: 0,
2041                    bottom: 0,
2042                    height: '100%'
2043                })
2044            );
2045            entry.progressCtx = entry.progress.getContext('2d');
2046        }
2047
2048        this.canvases.push(entry);
2049    },
2050
2051    removeCanvas: function () {
2052        var lastEntry = this.canvases.pop();
2053        lastEntry.wave.parentElement.removeChild(lastEntry.wave);
2054        if (this.hasProgressCanvas) {
2055            lastEntry.progress.parentElement.removeChild(lastEntry.progress);
2056        }
2057    },
2058
2059    updateDimensions: function (entry, width, height) {
2060        var elementWidth = Math.round(width / this.params.pixelRatio),
2061            totalWidth = Math.round(this.width / this.params.pixelRatio);
2062
2063        // Where the canvas starts and ends in the waveform, represented as a decimal between 0 and 1.
2064        entry.start = (entry.waveCtx.canvas.offsetLeft / totalWidth) || 0;
2065        entry.end = entry.start + elementWidth / totalWidth;
2066
2067        entry.waveCtx.canvas.width = width;
2068        entry.waveCtx.canvas.height = height;
2069        this.style(entry.waveCtx.canvas, { width: elementWidth + 'px'});
2070
2071        this.style(this.progressWave, { display: 'block'});
2072
2073        if (this.hasProgressCanvas) {
2074            entry.progressCtx.canvas.width = width;
2075            entry.progressCtx.canvas.height = height;
2076            this.style(entry.progressCtx.canvas, { width: elementWidth + 'px'});
2077        }
2078    },
2079
2080    clearWave: function () {
2081        for (var i in this.canvases) {
2082            this.clearWaveForEntry(this.canvases[i]);
2083        }
2084    },
2085
2086    clearWaveForEntry: function (entry) {
2087        entry.waveCtx.clearRect(0, 0, entry.waveCtx.canvas.width, entry.waveCtx.canvas.height);
2088        if (this.hasProgressCanvas) {
2089            entry.progressCtx.clearRect(0, 0, entry.progressCtx.canvas.width, entry.progressCtx.canvas.height);
2090        }
2091    },
2092
2093    drawBars: function (peaks, channelIndex, start, end) {
2094        var my = this;
2095        // Split channels
2096        if (peaks[0] instanceof Array) {
2097            var channels = peaks;
2098            if (this.params.splitChannels) {
2099                this.setHeight(channels.length * this.params.height * this.params.pixelRatio);
2100                channels.forEach(function(channelPeaks, i) {
2101                    my.drawBars(channelPeaks, i, start, end);
2102                });
2103                return;
2104            } else {
2105                peaks = channels[0];
2106            }
2107        }
2108
2109        // Bar wave draws the bottom only as a reflection of the top,
2110        // so we don't need negative values
2111        var hasMinVals = [].some.call(peaks, function (val) { return val < 0; });
2112        // Skip every other value if there are negatives.
2113        var peakIndexScale = 1;
2114        if (hasMinVals) {
2115            peakIndexScale = 2;
2116        }
2117
2118        // A half-pixel offset makes lines crisp
2119        var width = this.width;
2120        var height = this.params.height * this.params.pixelRatio;
2121        var offsetY = height * channelIndex || 0;
2122        var halfH = height / 2;
2123        var length = peaks.length / peakIndexScale;
2124        var bar = this.params.barWidth * this.params.pixelRatio;
2125        var gap = Math.max(this.params.pixelRatio, ~~(bar / 2));
2126        var step = bar + gap;
2127
2128        var absmax = 1 / this.params.barHeight;
2129        if (this.params.normalize) {
2130            var max = WaveSurfer.util.max(peaks);
2131            var min = WaveSurfer.util.min(peaks);
2132            absmax = -min > max ? -min : max;
2133        }
2134
2135        var scale = length / width;
2136
2137        for (var i = (start / scale); i < (end / scale); i += step) {
2138            var peak = peaks[Math.floor(i * scale * peakIndexScale)] || 0;
2139            var h = Math.round(peak / absmax * halfH);
2140            this.fillRect(i + this.halfPixel, halfH - h + offsetY, bar + this.halfPixel, h * 2);
2141        }
2142    },
2143
2144    drawWave: function (peaks, channelIndex, start, end) {
2145        var my = this;
2146        // Split channels
2147        if (peaks[0] instanceof Array) {
2148            var channels = peaks;
2149            if (this.params.splitChannels) {
2150                this.setHeight(channels.length * this.params.height * this.params.pixelRatio);
2151                channels.forEach(function(channelPeaks, i) {
2152                    my.drawWave(channelPeaks, i, start, end);
2153                });
2154                return;
2155            } else {
2156                peaks = channels[0];
2157            }
2158        }
2159
2160        // Support arrays without negative peaks
2161        var hasMinValues = [].some.call(peaks, function (val) { return val < 0; });
2162        if (!hasMinValues) {
2163            var reflectedPeaks = [];
2164            for (var i = 0, len = peaks.length; i < len; i++) {
2165                reflectedPeaks[2 * i] = peaks[i];
2166                reflectedPeaks[2 * i + 1] = -peaks[i];
2167            }
2168            peaks = reflectedPeaks;
2169        }
2170
2171        // A half-pixel offset makes lines crisp
2172        var height = this.params.height * this.params.pixelRatio;
2173        var offsetY = height * channelIndex || 0;
2174        var halfH = height / 2;
2175
2176        var absmax = 1 / this.params.barHeight;
2177        if (this.params.normalize) {
2178            var max = WaveSurfer.util.max(peaks);
2179            var min = WaveSurfer.util.min(peaks);
2180            absmax = -min > max ? -min : max;
2181        }
2182
2183        this.drawLine(peaks, absmax, halfH, offsetY, start, end);
2184
2185        // Always draw a median line
2186        this.fillRect(0, halfH + offsetY - this.halfPixel, this.width, this.halfPixel);
2187    },
2188
2189    drawLine: function (peaks, absmax, halfH, offsetY, start, end) {
2190        for (var index in this.canvases) {
2191            var entry = this.canvases[index];
2192
2193            this.setFillStyles(entry);
2194
2195            this.drawLineToContext(entry, entry.waveCtx, peaks, absmax, halfH, offsetY, start, end);
2196            this.drawLineToContext(entry, entry.progressCtx, peaks, absmax, halfH, offsetY, start, end);
2197        }
2198    },
2199
2200    drawLineToContext: function (entry, ctx, peaks, absmax, halfH, offsetY, start, end) {
2201        if (!ctx) { return; }
2202
2203        var length = peaks.length / 2;
2204
2205        var scale = 1;
2206        if (this.params.fillParent && this.width != length) {
2207            scale = this.width / length;
2208        }
2209
2210        var first = Math.round(length * entry.start),
2211            last = Math.round(length * entry.end);
2212        if (first > end || last < start) { return; }
2213        var canvasStart = Math.max(first, start);
2214        var canvasEnd = Math.min(last, end);
2215
2216        ctx.beginPath();
2217        ctx.moveTo((canvasStart - first) * scale + this.halfPixel, halfH + offsetY);
2218
2219        for (var i = canvasStart; i < canvasEnd; i++) {
2220            var peak = peaks[2 * i] || 0;
2221            var h = Math.round(peak / absmax * halfH);
2222            ctx.lineTo((i - first) * scale + this.halfPixel, halfH - h + offsetY);
2223        }
2224
2225        // Draw the bottom edge going backwards, to make a single
2226        // closed hull to fill.
2227        for (var i = canvasEnd - 1; i >= canvasStart; i--) {
2228            var peak = peaks[2 * i + 1] || 0;
2229            var h = Math.round(peak / absmax * halfH);
2230            ctx.lineTo((i - first) * scale + this.halfPixel, halfH - h + offsetY);
2231        }
2232
2233        ctx.closePath();
2234        ctx.fill();
2235    },
2236
2237    fillRect: function (x, y, width, height) {
2238        var startCanvas = Math.floor(x / this.maxCanvasWidth);
2239        var endCanvas = Math.min(Math.ceil((x + width) / this.maxCanvasWidth) + 1,
2240                                 this.canvases.length);
2241        for (var i = startCanvas; i < endCanvas; i++) {
2242            var entry = this.canvases[i],
2243                leftOffset = i * this.maxCanvasWidth;
2244
2245            var intersection = {
2246                x1: Math.max(x, i * this.maxCanvasWidth),
2247                y1: y,
2248                x2: Math.min(x + width, i * this.maxCanvasWidth + entry.waveCtx.canvas.width),
2249                y2: y + height
2250            };
2251
2252            if (intersection.x1 < intersection.x2) {
2253                this.setFillStyles(entry);
2254
2255                this.fillRectToContext(entry.waveCtx,
2256                        intersection.x1 - leftOffset,
2257                        intersection.y1,
2258                        intersection.x2 - intersection.x1,
2259                        intersection.y2 - intersection.y1);
2260
2261                this.fillRectToContext(entry.progressCtx,
2262                        intersection.x1 - leftOffset,
2263                        intersection.y1,
2264                        intersection.x2 - intersection.x1,
2265                        intersection.y2 - intersection.y1);
2266            }
2267        }
2268    },
2269
2270    fillRectToContext: function (ctx, x, y, width, height) {
2271        if (!ctx) { return; }
2272        ctx.fillRect(x, y, width, height);
2273    },
2274
2275    setFillStyles: function (entry) {
2276        entry.waveCtx.fillStyle = this.params.waveColor;
2277        if (this.hasProgressCanvas) {
2278            entry.progressCtx.fillStyle = this.params.progressColor;
2279        }
2280    },
2281
2282    updateProgress: function (pos) {
2283        this.style(this.progressWave, { width: pos + 'px' });
2284    },
2285
2286    /**
2287     * Combine all available canvasses together.
2288     *
2289     * @param {String} type - an optional value of a format type. Default is image/png.
2290     * @param {Number} quality - an optional value between 0 and 1. Default is 0.92.
2291     *
2292     */
2293    getImage: function(type, quality) {
2294        var availableCanvas = [];
2295        this.canvases.forEach(function (entry) {
2296            availableCanvas.push(entry.wave.toDataURL(type, quality));
2297        });
2298        return availableCanvas.length > 1 ? availableCanvas : availableCanvas[0];
2299    }
2300});
2301
2302'use strict';
2303
2304WaveSurfer.Drawer.SplitWavePointPlot = Object.create(WaveSurfer.Drawer.Canvas);
2305
2306WaveSurfer.util.extend(WaveSurfer.Drawer.SplitWavePointPlot, {
2307
2308    defaultPlotParams: {
2309        plotNormalizeTo: 'whole',
2310        plotTimeStart: 0,
2311        plotMin: 0,
2312        plotMax: 1,
2313        plotColor     : '#f63',
2314        plotProgressColor : '#F00',
2315        plotPointHeight: 2,
2316        plotPointWidth: 2,
2317        plotSeparator: true,
2318        plotSeparatorColor: 'black',
2319        plotRangeDisplay: false,
2320        plotRangeUnits: '',
2321        plotRangePrecision: 4,
2322        plotRangeIgnoreOutliers: false,
2323        plotRangeFontSize: 12,
2324        plotRangeFontType: 'Ariel',
2325        waveDrawMedianLine: true,
2326        plotFileDelimiter:  '\t'
2327    },
2328
2329    //object variables that get manipulated by various object functions
2330    plotTimeStart: 0,  //the start time of our wave according to plot data
2331    plotTimeEnd: -1,   //the end of our wave according to plot data
2332    plotArrayLoaded: false,
2333    plotArray: [],     //array of plot data objects containing time and plot value
2334    plotPoints: [],        //calculated average plot points corresponding to value of our wave
2335    plotMin: 0,
2336    plotMax: 1,
2337
2338    /**
2339     * Initializes the plot array. If params.plotFileUrl is provided an ajax call will be
2340     * executed and drawing of the wave is delayed until plot info is retrieved
2341     * @param params
2342     */
2343    initDrawer: function (params) {
2344        var my = this;
2345
2346        //set defaults if not passed in
2347        for(var paramName in this.defaultPlotParams) {
2348            if(this.params[paramName] === undefined) {
2349                this.params[paramName] = this.defaultPlotParams[paramName];
2350            }
2351        }
2352
2353        //set the plotTimeStart
2354        this.plotTimeStart = this.params.plotTimeStart;
2355
2356        //check to see if plotTimeEnd
2357        if(this.params.plotTimeEnd !== undefined) {
2358            this.plotTimeEnd = this.params.plotTimeEnd;
2359        }
2360
2361        //set the plot array
2362        if (Array.isArray(params.plotArray)) {
2363            this.plotArray = params.plotArray;
2364            this.plotArrayLoaded = true;
2365        }
2366        //Need to load the plot array from ajax with our callback
2367        else {
2368            var onPlotArrayLoaded = function (plotArray) {
2369                my.plotArray = plotArray;
2370                my.plotArrayLoaded = true;
2371                my.fireEvent('plot_array_loaded');
2372            };
2373            this.loadPlotArrayFromFile(params.plotFileUrl, onPlotArrayLoaded, this.params.plotFileDelimiter);
2374        }
2375    },
2376
2377    /**
2378     * Draw the peaks - this overrides the drawer.js function and does the following additional steps
2379     * - ensures that the plotArray has already been loaded, if not it loads via ajax
2380     * - moves the wave form to where channel 1 would normally be
2381     * @param peaks
2382     * @param length
2383     * @param start
2384     * @param end
2385     */
2386    drawPeaks: function (peaks, length, start, end) {
2387        //make sure that the plot array is already loaded
2388        if (this.plotArrayLoaded == true) {
2389
2390            this.setWidth(length);
2391
2392            //fake that we are splitting channels
2393            this.splitChannels = true;
2394            this.params.height = this.params.height/2;
2395            if (peaks[0] instanceof Array) {
2396               peaks = peaks[0];
2397            }
2398
2399            this.params.barWidth ?
2400                this.drawBars(peaks, 1, start, end) :
2401                this.drawWave(peaks, 1, start, end);
2402
2403            //set the height back to the original
2404            this.params.height = this.params.height*2;
2405
2406            this.calculatePlots();
2407            this.drawPlots();
2408
2409        }
2410        //otherwise wait for the plot array to be loaded and then draw again
2411        else {
2412            var my = this;
2413            my.on('plot-array-loaded', function () {
2414                my.drawPeaks(peaks, length, start, end);
2415            });
2416        }
2417    },
2418
2419
2420
2421
2422    /**
2423     * Loop through the calculated plot values and actually draw them
2424     */
2425    drawPlots: function() {
2426        var height = this.params.height * this.params.pixelRatio / 2;
2427
2428        var $ = 0.5 / this.params.pixelRatio;
2429
2430        this.waveCc.fillStyle = this.params.plotColor;
2431        if(this.progressCc) {
2432            this.progressCc.fillStyle = this.params.plotProgressColor;
2433        }
2434        for(var i in this.plotPoints) {
2435            var x = parseInt(i);
2436            var y = height - this.params.plotPointHeight - (this.plotPoints[i] * (height - this.params.plotPointHeight));
2437            var pointHeight = this.params.plotPointHeight;
2438
2439            this.waveCc.fillRect(x, y, this.params.plotPointWidth, pointHeight);
2440
2441            if(this.progressCc) {
2442                this.progressCc.fillRect(x, y, this.params.plotPointWidth, pointHeight);
2443            }
2444        }
2445
2446        //draw line to separate the two waves
2447        if(this.params.plotSeparator) {
2448            this.waveCc.fillStyle = this.params.plotSeparatorColor;
2449            this.waveCc.fillRect(0, height, this.width, $);
2450        }
2451
2452        if(this.params.plotRangeDisplay) {
2453            this.displayPlotRange();
2454        }
2455    },
2456
2457
2458    /**
2459     * Display the range for the plot graph
2460     */
2461    displayPlotRange: function()
2462    {
2463        var fontSize = this.params.plotRangeFontSize * this.params.pixelRatio;
2464        var maxRange = this.plotMax.toPrecision(this.params.plotRangePrecision) + ' ' + this.params.plotRangeUnits;
2465        var minRange = this.plotMin.toPrecision(this.params.plotRangePrecision) + ' ' + this.params.plotRangeUnits;
2466        this.waveCc.font = fontSize.toString() + 'px ' + this.params.plotRangeFontType;
2467        this.waveCc.fillText(maxRange, 3, fontSize);
2468        this.waveCc.fillText(minRange, 3, this.height/2);
2469
2470    },
2471    /**
2472     * This function loops through the plotArray and converts it to the plot points
2473     * to be drawn on the canvas keyed by their position
2474     */
2475    calculatePlots: function() {
2476        //reset plots array
2477        this.plotPoints = {};
2478
2479        //make sure we have our plotTimeEnd
2480        this.calculatePlotTimeEnd();
2481
2482        var pointsForAverage = [];
2483        var previousWaveIndex = -1;
2484        var maxPlot = 0;
2485        var minPlot = 99999999999999;
2486        var maxSegmentPlot = 0;
2487        var minSegmentPlot = 99999999999999;
2488        var duration = this.plotTimeEnd - this.plotTimeStart;
2489
2490        //loop through our plotArray and map values to wave indexes and take the average values for each wave index
2491        for(var i = 0; i < this.plotArray.length; i++) {
2492            var dataPoint = this.plotArray[i];
2493            if(dataPoint.value > maxPlot) {maxPlot = dataPoint.value;}
2494            if(dataPoint.value < minPlot) {minPlot = dataPoint.value;}
2495
2496            //make sure we are in the specified range
2497            if(dataPoint.time >= this.plotTimeStart && dataPoint.time <= this.plotTimeEnd) {
2498                //get the wave index corresponding to the data point
2499                var waveIndex = Math.round(this.width * (dataPoint.time - this.plotTimeStart) / duration);
2500
2501                pointsForAverage.push(dataPoint.value);
2502
2503                //if we have moved on to a new position in our wave record average and reset previousWaveIndex
2504                if(waveIndex !== previousWaveIndex) {
2505                    if(pointsForAverage.length > 0) {
2506                        //get the average plot for this point
2507                        var avgPlot = this.avg(pointsForAverage);
2508
2509                        //check for min max
2510                        if(avgPlot > maxSegmentPlot) {maxSegmentPlot = avgPlot;}
2511                        if(avgPlot < minSegmentPlot) {minSegmentPlot = avgPlot;}
2512
2513                        //add plot to the position
2514                        this.plotPoints[previousWaveIndex] = avgPlot;
2515                        pointsForAverage = [];
2516                    }
2517                }
2518                previousWaveIndex = waveIndex;
2519            }
2520        }
2521
2522        //normalize the plots points
2523        if(this.params.plotNormalizeTo == 'whole') {
2524            this.plotMin = minPlot;
2525            this.plotMax = maxPlot;
2526        }
2527        else if(this.params.plotNormalizeTo == 'values') {
2528            this.plotMin = this.params.plotMin;
2529            this.plotMax = this.params.plotMax;
2530        }
2531        else {
2532            this.plotMin = minSegmentPlot;
2533            this.plotMax = maxSegmentPlot;
2534        }
2535        this.normalizeValues();
2536    },
2537
2538    /**
2539     * Function to take all of the plots in this.plots and normalize them from 0 to one
2540     * depending on this.plotMin and this.plotMax values
2541     */
2542    normalizeValues: function() {
2543        var normalizedValues = {};
2544
2545        //check to make sure we should be normalizing
2546        if(this.params.plotNormalizeTo === 'none') {return;}
2547
2548        for(var i in this.plotPoints) {
2549            //get the normalized value between 0 and 1
2550            var normalizedValue = (this.plotPoints[i] - this.plotMin) / (this.plotMax - this.plotMin);
2551
2552            //check if the value is above our specified range max
2553            if(normalizedValue > 1) {
2554                if(!this.params.plotRangeIgnoreOutliers) {
2555                    normalizedValues[i] = 1;
2556                }
2557            }
2558            //check if hte value is below our specified rant
2559            else if(normalizedValue < 0) {
2560                if(!this.params.plotRangeIgnoreOutliers) {
2561                    normalizedValues[i] = 0;
2562                }
2563            }
2564            //in our range add the normalized value
2565            else {
2566                normalizedValues[i] = normalizedValue;
2567            }
2568        }
2569        this.plotPoints = normalizedValues;
2570    },
2571    /**
2572     *
2573     */
2574
2575    /**
2576     * Function to load the plot array from a external file
2577     *
2578     * The text file should contain a series of lines.
2579     * Each line should contain [audio time] [delimiter character] [plot value]
2580     * e.g. "1.2355 [tab] 124.2321"
2581     *
2582     * @param plotFileUrl  url of the file containing time and value information
2583     * @param onSuccess    function to run on success
2584     * @param delimiter    the delimiter that separates the time and values on each line
2585     */
2586    loadPlotArrayFromFile: function(plotFileUrl, onSuccess, delimiter) {
2587        //default delimiter to tab character
2588        if (delimiter === undefined) {delimiter = '\t';}
2589
2590        var plotArray = [];
2591
2592        var options = {
2593            url: plotFileUrl,
2594            responseType: 'text'
2595        };
2596        var fileAjax = WaveSurfer.util.ajax(options);
2597
2598        fileAjax.on('load', function (data) {
2599            if (data.currentTarget.status == 200) {
2600                //split the file by line endings
2601                var plotLines = data.currentTarget.responseText.split('\n');
2602                //loop through each line and find the time and plot values (delimited by tab)
2603                for (var i = 0; i < plotLines.length; i++) {
2604                    var plotParts = plotLines[i].split(delimiter);
2605                    if(plotParts.length == 2) {
2606                        plotArray.push({time: parseFloat(plotParts[0]), value: parseFloat(plotParts[1])});
2607                    }
2608                }
2609                //run success function
2610                onSuccess(plotArray);
2611            }
2612        });
2613    },
2614
2615    /***
2616     * Calculate the end time of the plot
2617     */
2618    calculatePlotTimeEnd: function() {
2619        if(this.params.plotTimeEnd !== undefined) {
2620            this.plotTimeEnd = this.params.plotTimeEnd;
2621        }
2622        else {
2623            this.plotTimeEnd = this.plotArray[this.plotArray.length -1].time;
2624        }
2625    },
2626
2627    /**
2628     * Quick convenience function to average numbers in an array
2629     * @param  array of values
2630     * @returns {number}
2631     */
2632    avg: function(values) {
2633        var sum = values.reduce(function(a, b) {return a+b;});
2634        return sum/values.length;
2635    }
2636});
2637
2638WaveSurfer.util.extend(WaveSurfer.Drawer.SplitWavePointPlot, WaveSurfer.Observer
vendor: 4,216 bytes, lines 2638-2768
2638);
2639
2640'use strict';
2641
2642WaveSurfer.PeakCache = {
2643    init: function() {
2644        this.clearPeakCache();
2645    },
2646
2647    clearPeakCache: function() {
2648	// Flat array with entries that are always in pairs to mark the
2649	// beginning and end of each subrange.  This is a convenience so we can
2650	// iterate over the pairs for easy set difference operations.
2651        this.peakCacheRanges = [];
2652	// Length of the entire cachable region, used for resetting the cache
2653	// when this changes (zoom events, for instance).
2654        this.peakCacheLength = -1;
2655    },
2656
2657    addRangeToPeakCache: function(length, start, end) {
2658        if (length != this.peakCacheLength) {
2659            this.clearPeakCache();
2660            this.peakCacheLength = length;
2661        }
2662
2663        // Return ranges that weren't in the cache before the call.
2664        var uncachedRanges = [];
2665        var i = 0;
2666        // Skip ranges before the current start.
2667        while (i < this.peakCacheRanges.length && this.peakCacheRanges[i] < start) {
2668            i++;
2669        }
2670	// If |i| is even, |start| falls after an existing range.  Otherwise,
2671	// |start| falls between an existing range, and the uncached region
2672	// starts when we encounter the next node in |peakCacheRanges| or
2673	// |end|, whichever comes first.
2674        if (i % 2 == 0) {
2675            uncachedRanges.push(start);
2676        }
2677        while (i < this.peakCacheRanges.length && this.peakCacheRanges[i] <= end) {
2678            uncachedRanges.push(this.peakCacheRanges[i]);
2679            i++;
2680        }
2681        // If |i| is even, |end| is after all existing ranges.
2682        if (i % 2 == 0) {
2683            uncachedRanges.push(end);
2684        }
2685
2686        // Filter out the 0-length ranges.
2687        uncachedRanges = uncachedRanges.filter(function(item, pos, arr) {
2688            if (pos == 0) {
2689                return item != arr[pos + 1];
2690            } else if (pos == arr.length - 1) {
2691                return item != arr[pos - 1];
2692            } else {
2693                return item != arr[pos - 1] && item != arr[pos + 1];
2694            }
2695        });
2696
2697	// Merge the two ranges together, uncachedRanges will either contain
2698	// wholly new points, or duplicates of points in peakCacheRanges.  If
2699	// duplicates are detected, remove both and extend the range.
2700        this.peakCacheRanges = this.peakCacheRanges.concat(uncachedRanges);
2701        this.peakCacheRanges = this.peakCacheRanges.sort(function(a, b) {
2702            return a - b;
2703        }).filter(function(item, pos, arr) {
2704            if (pos == 0) {
2705                return item != arr[pos + 1];
2706            } else if (pos == arr.length - 1) {
2707                return item != arr[pos - 1];
2708            } else {
2709                return item != arr[pos - 1] && item != arr[pos + 1];
2710            }
2711        });
2712
2713	// Push the uncached ranges into an array of arrays for ease of
2714	// iteration in the functions that call this.
2715        var uncachedRangePairs = [];
2716        for (i = 0; i < uncachedRanges.length; i += 2) {
2717            uncachedRangePairs.push([uncachedRanges[i], uncachedRanges[i+1]]);
2718        }
2719
2720        return uncachedRangePairs;
2721    },
2722
2723    // For testing
2724    getCacheRanges: function() {
2725      var peakCacheRangePairs = [];
2726      for (var i = 0; i < this.peakCacheRanges.length; i += 2) {
2727          peakCacheRangePairs.push([this.peakCacheRanges[i], this.peakCacheRanges[i+1]]);
2728      }
2729      return peakCacheRangePairs;
2730    }
2731};
2732
2733'use strict';
2734
2735/* Init from HTML */
2736(function () {
2737    var init = function () {
2738        var containers = document.querySelectorAll('wavesurfer');
2739
2740        Array.prototype.forEach.call(containers, function (el) {
2741            var params = WaveSurfer.util.extend({
2742                container: el,
2743                backend: 'MediaElement',
2744                mediaControls: true
2745            }, el.dataset);
2746
2747            el.style.display = 'block';
2748
2749            var wavesurfer = WaveSurfer.create(params);
2750
2751            if (el.dataset.peaks) {
2752                var peaks = JSON.parse(el.dataset.peaks);
2753            }
2754
2755            wavesurfer.load(el.dataset.url, peaks);
2756        });
2757    };
2758
2759    if (document.readyState === 'complete') {
2760        init();
2761    } else {
2762        window.addEventListener('load', init);
2763    }
2764}());
2765
2766return WaveSurfer;
2767
2768}));

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.