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.