Skip to content

Instantly share code, notes, and snippets.

@hydrargyrum
Created October 24, 2019 16:25
Show Gist options
  • Save hydrargyrum/d637d8e6d2cc6df5d9890b6adc25f11f to your computer and use it in GitHub Desktop.
Save hydrargyrum/d637d8e6d2cc6df5d9890b6adc25f11f to your computer and use it in GitHub Desktop.
revealjs audio
/*****************************************************************
** Author: Asvin Goel, [email protected]
**
** Audio slideshow is a plugin for reveal.js allowing to
** automatically play audio files for a slide deck. After an audio
** file has completed playing the next slide or fragment is
** automatically shown and the respective audio file is played.
** If no audio file is available, a blank audio file with default
** duration is played instead.
**
** Version: 0.6.1
**
** License: MIT license (see LICENSE.md)
**
******************************************************************/
var RevealAudioSlideshow = window.RevealAudioSlideshow || (function(){
// default parameters
var prefix = "audio/";
var suffix = ".ogg";
var textToSpeechURL = null; // no text to speech converter
// var textToSpeechURL = "http://api.voicerss.org/?key=[YOUR_KEY]&hl=en-gb&c=ogg&src="; // the text to speech converter
var defaultNotes = false; // use slide notes as default for the text to speech converter
var defaultText = false; // use slide text as default for the text to speech converter
var defaultDuration = 5; // value in seconds
var defaultAudios = true; // try to obtain audio for slide and fragment numbers
var advance = 0; // advance to next slide after given time in milliseconds after audio has played, use negative value to not advance
var autoplay = false; // automatically start slideshow
var playerOpacity = .05; // opacity when the mouse is far from to the audioplayer
var startAtFragment = false; // when moving to a slide, start at the current fragment or at the start of the slide
var playerStyle = "position: fixed; bottom: 4px; left: 25%; width: 50%; height:75px; z-index: 33;"; // style used for container of audio controls
// ------------------
var silence;
var currentAudio = null;
var previousAudio = null;
var timer = null;
Reveal.addEventListener( 'fragmentshown', function( event ) {
if ( timer ) { clearTimeout( timer ); timer = null; }
//console.debug( "fragmentshown ");
selectAudio();
} );
Reveal.addEventListener( 'fragmenthidden', function( event ) {
if ( timer ) { clearTimeout( timer ); timer = null; }
//console.debug( "fragmenthidden ");
selectAudio();
} );
Reveal.addEventListener( 'ready', function( event ) {
setup();
//console.debug( "ready ");
selectAudio();
document.dispatchEvent( new CustomEvent('stopplayback') );
} );
Reveal.addEventListener( 'slidechanged', function( event ) {
if ( timer ) { clearTimeout( timer ); timer = null; }
//console.debug( "slidechanged ");
var indices = Reveal.getIndices();
if ( !startAtFragment && typeof indices.f !== 'undefined' && indices.f >= 0) {
// hide fragments when slide is shown
Reveal.slide(indices.h, indices.v, -1);
}
selectAudio();
} );
Reveal.addEventListener( 'paused', function( event ) {
if ( timer ) { clearTimeout( timer ); timer = null; }
currentAudio.pause();
} );
Reveal.addEventListener( 'resumed', function( event ) {
if ( timer ) { clearTimeout( timer ); timer = null; }
} );
Reveal.addEventListener( 'overviewshown', function( event ) {
if ( timer ) { clearTimeout( timer ); timer = null; }
currentAudio.pause();
document.querySelector(".audio-controls").style.visibility = "hidden";
} );
Reveal.addEventListener( 'overviewhidden', function( event ) {
if ( timer ) { clearTimeout( timer ); timer = null; }
document.querySelector(".audio-controls").style.visibility = "visible";
} );
function selectAudio( previousAudio ) {
if ( currentAudio ) {
currentAudio.pause();
currentAudio.style.display = "none";
}
var indices = Reveal.getIndices();
var id = "audioplayer-" + indices.h + '.' + indices.v;
if ( indices.f != undefined && indices.f >= 0 ) id = id + '.' + indices.f;
currentAudio = document.getElementById( id );
if ( currentAudio ) {
currentAudio.style.display = "block";
if ( previousAudio ) {
if ( currentAudio.id != previousAudio.id ) {
currentAudio.volume = previousAudio.volume;
currentAudio.muted = previousAudio.muted;
//console.debug( "Play " + currentAudio.id);
currentAudio.play();
}
}
else if ( autoplay ) {
currentAudio.play();
}
}
}
function setup() {
// deprecated parameters
if ( Reveal.getConfig().audioPrefix ) {
prefix = Reveal.getConfig().audioPrefix;
console.warn('Setting parameter "audioPrefix" is deprecated!');
}
if ( Reveal.getConfig().audioSuffix ) {
suffix = Reveal.getConfig().audioSuffix;
console.warn('Setting parameter "audioSuffix" is deprecated!');
}
if ( Reveal.getConfig().audioTextToSpeechURL ) {
textToSpeechURL = Reveal.getConfig().audioTextToSpeechURL;
console.warn('Setting parameter "audioTextToSpeechURL" is deprecated!');
}
if ( Reveal.getConfig().audioDefaultDuration ) {
defaultDuration = Reveal.getConfig().audioDefaultDuration;
console.warn('Setting parameter "audioDefaultDuration" is deprecated!');
}
if ( Reveal.getConfig().audioAutoplay ) {
autoplay = Reveal.getConfig().audioAutoplay;
console.warn('Setting parameter "audioAutoplay" is deprecated!');
}
if ( Reveal.getConfig().audioPlayerOpacity ) {
playerOpacity = Reveal.getConfig().audioPlayerOpacity;
console.warn('Setting parameter "audioPlayerOpacity" is deprecated!');
}
// set parameters
var config = Reveal.getConfig().audio;
if ( config ) {
if ( config.prefix != null ) prefix = config.prefix;
if ( config.suffix != null ) suffix = config.suffix;
if ( config.textToSpeechURL != null ) textToSpeechURL = config.textToSpeechURL;
if ( config.defaultNotes != null ) defaultNotes = config.defaultNotes;
if ( config.defaultText != null ) defaultText = config.defaultText;
if ( config.defaultDuration != null ) defaultDuration = config.defaultDuration;
if ( config.defaultAudios != null ) defaultAudios = config.defaultAudios;
if ( config.advance != null ) advance = config.advance;
if ( config.autoplay != null ) autoplay = config.autoplay;
if ( config.playerOpacity != null ) playerOpacity = config.playerOpacity;
if ( config.playerStyle != null ) playerStyle = config.playerStyle;
}
if ( 'ontouchstart' in window || navigator.msMaxTouchPoints ) {
opacity = 1;
}
if ( Reveal.getConfig().audioStartAtFragment ) startAtFragment = Reveal.getConfig().audioStartAtFragment;
// set style so that audio controls are shown on hover
var css='.audio-controls>audio { opacity:' + playerOpacity + ';} .audio-controls:hover>audio { opacity:1;}';
style=document.createElement( 'style' );
if ( style.styleSheet ) {
style.styleSheet.cssText=css;
}
else {
style.appendChild( document.createTextNode( css ) );
}
document.getElementsByTagName( 'head' )[0].appendChild( style );
silence = new SilentAudio( defaultDuration ); // create the wave file
var divElement = document.createElement( 'div' );
divElement.className = "audio-controls";
divElement.setAttribute( 'style', playerStyle );
document.querySelector( ".reveal" ).appendChild( divElement );
// create audio players for all slides
var horizontalSlides = document.querySelectorAll( '.reveal .slides>section' );
for( var h = 0, len1 = horizontalSlides.length; h < len1; h++ ) {
var verticalSlides = horizontalSlides[ h ].querySelectorAll( 'section' );
if ( !verticalSlides.length ) {
setupAllAudioElements( divElement, h, 0, horizontalSlides[ h ] );
}
else {
for( var v = 0, len2 = verticalSlides.length; v < len2; v++ ) {
setupAllAudioElements( divElement, h, v, verticalSlides[ v ] );
}
}
}
}
function getText( textContainer ) {
var elements = textContainer.querySelectorAll( '[data-audio-text]' ) ;
for( var i = 0, len = elements.length; i < len; i++ ) {
// replace all elements with data-audio-text by specified text
textContainer.innerHTML = textContainer.innerHTML.replace(elements[i].outerHTML,elements[i].getAttribute('data-audio-text'));
}
return textContainer.textContent.trim().replace(/\s+/g, ' ');
}
function setupAllAudioElements( container, h, v, slide ) {
var textContainer = document.createElement( 'div' );
var text = null;
if ( !slide.hasAttribute( 'data-audio-src' ) ) {
// determine text for TTS
if ( slide.hasAttribute( 'data-audio-text' ) ) {
text = slide.getAttribute( 'data-audio-text' );
}
else if ( defaultNotes && Reveal.getSlideNotes( slide ) ) {
// defaultNotes
var div = document.createElement("div");
div.innerHTML = Reveal.getSlideNotes( slide );
text = div.textContent || '';
}
else if ( defaultText ) {
textContainer.innerHTML = slide.innerHTML;
// remove fragments
var fragments = textContainer.querySelectorAll( '.fragment' ) ;
for( var f = 0, len = fragments.length; f < len; f++ ) {
textContainer.innerHTML = textContainer.innerHTML.replace(fragments[f].outerHTML,'');
}
text = getText( textContainer);
}
// alert( h + '.' + v + ": " + text );
// console.log( h + '.' + v + ": " + text );
}
setupAudioElement( container, h + '.' + v, slide.getAttribute( 'data-audio-src' ), text, slide.querySelector( ':not(.fragment) > video[data-audio-controls]' ) );
var i = 0;
var fragments;
while ( (fragments = slide.querySelectorAll( '.fragment[data-fragment-index="' + i +'"]' )).length > 0 ) {
var audio = null;
var video = null;
var text = '';
for( var f = 0, len = fragments.length; f < len; f++ ) {
if ( !audio ) audio = fragments[ f ].getAttribute( 'data-audio-src' );
if ( !video ) video = fragments[ f ].querySelector( 'video[data-audio-controls]' );
// determine text for TTS
if ( fragments[ f ].hasAttribute( 'data-audio-text' ) ) {
text += fragments[ f ].getAttribute( 'data-audio-text' ) + ' ';
}
else if ( defaultText ) {
textContainer.innerHTML = fragments[ f ].textContent;
text += getText( textContainer );
}
}
//console.log( h + '.' + v + '.' + i + ": >" + text +"<")
setupAudioElement( container, h + '.' + v + '.' + i, audio, text, video );
i++;
}
}
function linkVideoToAudioControls( audioElement, videoElement ) {
audioElement.addEventListener( 'playing', function( event ) {
videoElement.currentTime = audioElement.currentTime;
} );
audioElement.addEventListener( 'play', function( event ) {
videoElement.currentTime = audioElement.currentTime;
if ( videoElement.paused ) videoElement.play();
} );
audioElement.addEventListener( 'pause', function( event ) {
videoElement.currentTime = audioElement.currentTime;
if ( !videoElement.paused ) videoElement.pause();
} );
audioElement.addEventListener( 'volumechange', function( event ) {
videoElement.volume = audioElement.volume;
videoElement.muted = audioElement.muted;
} );
audioElement.addEventListener( 'seeked', function( event ) {
videoElement.currentTime = audioElement.currentTime;
} );
// add silent audio to video to be used as fallback
var audioSource = audioElement.querySelector('source[data-audio-silent]');
if ( audioSource ) audioElement.removeChild( audioSource );
audioSource = document.createElement( 'source' );
var videoSilence = new SilentAudio( Math.round(videoElement.duration + .5) ); // create the wave file
audioSource.src= videoSilence.dataURI;
audioSource.setAttribute("data-audio-silent", videoElement.duration);
audioElement.appendChild(audioSource, audioElement.firstChild);
}
function setupFallbackAudio( audioElement, text, videoElement ) {
// default file cannot be read
if ( textToSpeechURL != null && text != null && text != "" ) {
var audioSource = document.createElement( 'source' );
audioSource.src = textToSpeechURL + encodeURIComponent(text);
audioSource.setAttribute('data-tts',audioElement.id.split( '-' ).pop());
audioElement.appendChild(audioSource, audioElement.firstChild);
}
else {
if ( !audioElement.querySelector('source[data-audio-silent]') ) {
// create silent source if not yet existent
var audioSource = document.createElement( 'source' );
audioSource.src = silence.dataURI;
audioSource.setAttribute("data-audio-silent", defaultDuration);
audioElement.appendChild(audioSource, audioElement.firstChild);
}
}
}
function setupAudioElement( container, indices, audioFile, text, videoElement ) {
var audioElement = document.createElement( 'audio' );
audioElement.setAttribute( 'style', "position: relative; top: 20px; left: 10%; width: 80%;" );
audioElement.id = "audioplayer-" + indices;
audioElement.style.display = "none";
audioElement.setAttribute( 'controls', '' );
audioElement.setAttribute( 'preload', 'none' );
if ( videoElement ) {
// connect play, pause, volumechange, mute, timeupdate events to video
if ( videoElement.duration ) {
linkVideoToAudioControls( audioElement, videoElement );
}
else {
videoElement.onloadedmetadata = function() {
linkVideoToAudioControls( audioElement, videoElement );
};
}
}
audioElement.addEventListener( 'ended', function( event ) {
if ( typeof Recorder == 'undefined' || !Recorder.isRecording ) {
// determine whether and when slideshow advances with next slide
var advanceNow = advance;
var slide = Reveal.getCurrentSlide();
// check current fragment
var indices = Reveal.getIndices();
if ( typeof indices.f !== 'undefined' && indices.f >= 0) {
var fragment = slide.querySelector( '.fragment[data-fragment-index="' + indices.f + '"][data-audio-advance]' ) ;
if ( fragment ) {
advanceNow = fragment.getAttribute( 'data-audio-advance' );
}
}
else if ( slide.hasAttribute( 'data-audio-advance' ) ) {
advanceNow = slide.getAttribute( 'data-audio-advance' );
}
// advance immediately or set a timer - or do nothing
if ( advance == "true" || advanceNow == 0 ) {
var previousAudio = currentAudio;
Reveal.next();
selectAudio( previousAudio );
}
else if ( advanceNow > 0 ) {
timer = setTimeout( function() {
var previousAudio = currentAudio;
Reveal.next();
selectAudio( previousAudio );
timer = null;
}, advanceNow );
}
}
} );
audioElement.addEventListener( 'play', function( event ) {
var evt = new CustomEvent('startplayback');
evt.timestamp = 1000 * audioElement.currentTime;
document.dispatchEvent( evt );
if ( timer ) { clearTimeout( timer ); timer = null; }
// preload next audio element so that it is available after slide change
var indices = Reveal.getIndices();
var nextId = "audioplayer-" + indices.h + '.' + indices.v;
if ( indices.f != undefined && indices.f >= 0 ) {
nextId = nextId + '.' + (indices.f + 1);
}
else {
nextId = nextId + '.0';
}
var nextAudio = document.getElementById( nextId );
if ( !nextAudio ) {
nextId = "audioplayer-" + indices.h + '.' + (indices.v+1);
nextAudio = document.getElementById( nextId );
if ( !nextAudio ) {
nextId = "audioplayer-" + (indices.h+1) + '.0';
nextAudio = document.getElementById( nextId );
}
}
if ( nextAudio ) {
//console.debug( "Preload: " + nextAudio.id );
nextAudio.load();
}
} );
audioElement.addEventListener( 'pause', function( event ) {
if ( timer ) { clearTimeout( timer ); timer = null; }
document.dispatchEvent( new CustomEvent('stopplayback') );
} );
audioElement.addEventListener( 'seeked', function( event ) {
var evt = new CustomEvent('seekplayback');
evt.timestamp = 1000 * audioElement.currentTime;
document.dispatchEvent( evt );
if ( timer ) { clearTimeout( timer ); timer = null; }
} );
if ( audioFile != null ) {
// Support comma separated lists of audio sources
audioFile.split( ',' ).forEach( function( source ) {
var audioSource = document.createElement( 'source' );
audioSource.src = source;
audioElement.insertBefore(audioSource, audioElement.firstChild);
} );
}
else if ( defaultAudios ) {
var audioExists = false;
try {
// check if audio file exists
var xhr = new XMLHttpRequest();
xhr.open('HEAD', prefix + indices + suffix, true);
xhr.onload = function() {
if (xhr.readyState === 4 && xhr.status >= 200 && xhr.status < 300) {
var audioSource = document.createElement( 'source' );
audioSource.src = prefix + indices + suffix;
audioElement.insertBefore(audioSource, audioElement.firstChild);
audioExists = true;
}
else {
setupFallbackAudio( audioElement, text, videoElement );
}
}
xhr.send(null);
} catch( error ) {
//console.log("Error checking audio" + audioExists);
// fallback if checking of audio file fails (e.g. when running the slideshow locally)
var audioSource = document.createElement( 'source' );
audioSource.src = prefix + indices + suffix;
audioElement.insertBefore(audioSource, audioElement.firstChild);
setupFallbackAudio( audioElement, text, videoElement );
}
}
if ( audioFile != null || defaultDuration > 0 ) {
container.appendChild( audioElement );
}
}
})();
/*****************************************************************
** Create SilentAudio
** based on: RIFFWAVE.js v0.03
** http://www.codebase.es/riffwave/riffwave.js
**
** Usage:
** silence = new SilentAudio( 10 ); // create 10 seconds wave file
**
******************************************************************/
var FastBase64={chars:"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",encLookup:[],Init:function(){for(var e=0;4096>e;e++)this.encLookup[e]=this.chars[e>>6]+this.chars[63&e]},Encode:function(e){for(var h=e.length,a="",t=0;h>2;)n=e[t]<<16|e[t+1]<<8|e[t+2],a+=this.encLookup[n>>12]+this.encLookup[4095&n],h-=3,t+=3;if(h>0){var s=(252&e[t])>>2,i=(3&e[t])<<4;if(h>1&&(i|=(240&e[++t])>>4),a+=this.chars[s],a+=this.chars[i],2==h){var r=(15&e[t++])<<2;r|=(192&e[t])>>6,a+=this.chars[r]}1==h&&(a+="="),a+="="}return a}};FastBase64.Init();var SilentAudio=function(e){function h(e){return[255&e,e>>8&255,e>>16&255,e>>24&255]}function a(e){return[255&e,e>>8&255]}function t(e){for(var h=[],a=0,t=e.length,s=0;t>s;s++)h[a++]=255&e[s],h[a++]=e[s]>>8&255;return h}this.data=[],this.wav=[],this.dataURI="",this.header={chunkId:[82,73,70,70],chunkSize:0,format:[87,65,86,69],subChunk1Id:[102,109,116,32],subChunk1Size:16,audioFormat:1,numChannels:1,sampleRate:8e3,byteRate:0,blockAlign:0,bitsPerSample:8,subChunk2Id:[100,97,116,97],subChunk2Size:0},this.Make=function(e){for(var s=0;s<e*this.header.sampleRate;s++)this.data[s]=127;this.header.blockAlign=this.header.numChannels*this.header.bitsPerSample>>3,this.header.byteRate=this.header.blockAlign*this.sampleRate,this.header.subChunk2Size=this.data.length*(this.header.bitsPerSample>>3),this.header.chunkSize=36+this.header.subChunk2Size,this.wav=this.header.chunkId.concat(h(this.header.chunkSize),this.header.format,this.header.subChunk1Id,h(this.header.subChunk1Size),a(this.header.audioFormat),a(this.header.numChannels),h(this.header.sampleRate),h(this.header.byteRate),a(this.header.blockAlign),a(this.header.bitsPerSample),this.header.subChunk2Id,h(this.header.subChunk2Size),16==this.header.bitsPerSample?t(this.data):this.data),this.dataURI="data:audio/wav;base64,"+FastBase64.Encode(this.wav)},this.Make(e)};
'use strict';
// Last time updated: 2018-09-12 1:14:20 PM UTC
// ________________
// RecordRTC v5.4.8
// Open-Sourced: https://github.com/muaz-khan/RecordRTC
// --------------------------------------------------
// Muaz Khan - www.MuazKhan.com
// MIT License - www.WebRTC-Experiment.com/licence
// --------------------------------------------------
// ____________
// RecordRTC.js
/**
* {@link https://github.com/muaz-khan/RecordRTC|RecordRTC} is a WebRTC JavaScript library for audio/video as well as screen activity recording. It supports Chrome, Firefox, Opera, Android, and Microsoft Edge. Platforms: Linux, Mac and Windows.
* @summary Record audio, video or screen inside the browser.
* @license {@link https://github.com/muaz-khan/RecordRTC#license|MIT}
* @author {@link http://www.MuazKhan.com|Muaz Khan}
* @typedef RecordRTC
* @class
* @example
* var recorder = RecordRTC(mediaStream or [arrayOfMediaStream], {
* type: 'video', // audio or video or gif or canvas
* recorderType: MediaStreamRecorder || CanvasRecorder || StereoAudioRecorder || Etc
* });
* recorder.startRecording();
* @see For further information:
* @see {@link https://github.com/muaz-khan/RecordRTC|RecordRTC Source Code}
* @param {MediaStream} mediaStream - Single media-stream object, array of media-streams, html-canvas-element, etc.
* @param {object} config - {type:"video", recorderType: MediaStreamRecorder, disableLogs: true, numberOfAudioChannels: 1, bufferSize: 0, sampleRate: 0, desiredSampRate: 16000, video: HTMLVideoElement, etc.}
*/
function RecordRTC(mediaStream, config) {
if (!mediaStream) {
throw 'First parameter is required.';
}
config = config || {
type: 'video'
};
config = new RecordRTCConfiguration(mediaStream, config);
// a reference to user's recordRTC object
var self = this;
function startRecording(config2) {
if (!!config2) {
// allow users to set options using startRecording method
// config2 is similar to main "config" object (second parameter over RecordRTC constructor)
config = new RecordRTCConfiguration(mediaStream, config2);
}
if (!config.disableLogs) {
console.log('started recording ' + config.type + ' stream.');
}
if (mediaRecorder) {
mediaRecorder.clearRecordedData();
mediaRecorder.record();
setState('recording');
if (self.recordingDuration) {
handleRecordingDuration();
}
return self;
}
initRecorder(function() {
if (self.recordingDuration) {
handleRecordingDuration();
}
});
return self;
}
function initRecorder(initCallback) {
if (initCallback) {
config.initCallback = function() {
initCallback();
initCallback = config.initCallback = null; // recorder.initRecorder should be call-backed once.
};
}
var Recorder = new GetRecorderType(mediaStream, config);
mediaRecorder = new Recorder(mediaStream, config);
mediaRecorder.record();
setState('recording');
if (!config.disableLogs) {
console.log('Initialized recorderType:', mediaRecorder.constructor.name, 'for output-type:', config.type);
}
}
function stopRecording(callback) {
callback = callback || function() {};
if (!mediaRecorder) {
warningLog();
return;
}
if (self.state === 'paused') {
self.resumeRecording();
setTimeout(function() {
stopRecording(callback);
}, 1);
return;
}
if (self.state !== 'recording' && !config.disableLogs) {
console.warn('Recording state should be: "recording", however current state is: ', self.state);
}
if (!config.disableLogs) {
console.log('Stopped recording ' + config.type + ' stream.');
}
if (config.type !== 'gif') {
mediaRecorder.stop(_callback);
} else {
mediaRecorder.stop();
_callback();
}
setState('stopped');
function _callback(__blob) {
if (!mediaRecorder) {
if (typeof callback.call === 'function') {
callback.call(self, '');
} else {
callback('');
}
return;
}
Object.keys(mediaRecorder).forEach(function(key) {
if (typeof mediaRecorder[key] === 'function') {
return;
}
self[key] = mediaRecorder[key];
});
var blob = mediaRecorder.blob;
if (!blob) {
if (__blob) {
mediaRecorder.blob = blob = __blob;
} else {
throw 'Recording failed.';
}
}
if (blob && !config.disableLogs) {
console.log(blob.type, '->', bytesToSize(blob.size));
}
if (callback) {
var url = URL.createObjectURL(blob);
if (typeof callback.call === 'function') {
callback.call(self, url);
} else {
callback(url);
}
}
if (!config.autoWriteToDisk) {
return;
}
getDataURL(function(dataURL) {
var parameter = {};
parameter[config.type + 'Blob'] = dataURL;
DiskStorage.Store(parameter);
});
}
}
function pauseRecording() {
if (!mediaRecorder) {
warningLog();
return;
}
if (self.state !== 'recording') {
if (!config.disableLogs) {
console.warn('Unable to pause the recording. Recording state: ', self.state);
}
return;
}
setState('paused');
mediaRecorder.pause();
if (!config.disableLogs) {
console.log('Paused recording.');
}
}
function resumeRecording() {
if (!mediaRecorder) {
warningLog();
return;
}
if (self.state !== 'paused') {
if (!config.disableLogs) {
console.warn('Unable to resume the recording. Recording state: ', self.state);
}
return;
}
setState('recording');
// not all libs have this method yet
mediaRecorder.resume();
if (!config.disableLogs) {
console.log('Resumed recording.');
}
}
function readFile(_blob) {
postMessage(new FileReaderSync().readAsDataURL(_blob));
}
function getDataURL(callback, _mediaRecorder) {
if (!callback) {
throw 'Pass a callback function over getDataURL.';
}
var blob = _mediaRecorder ? _mediaRecorder.blob : (mediaRecorder || {}).blob;
if (!blob) {
if (!config.disableLogs) {
console.warn('Blob encoder did not finish its job yet.');
}
setTimeout(function() {
getDataURL(callback, _mediaRecorder);
}, 1000);
return;
}
if (typeof Worker !== 'undefined' && !navigator.mozGetUserMedia) {
var webWorker = processInWebWorker(readFile);
webWorker.onmessage = function(event) {
callback(event.data);
};
webWorker.postMessage(blob);
} else {
var reader = new FileReader();
reader.readAsDataURL(blob);
reader.onload = function(event) {
callback(event.target.result);
};
}
function processInWebWorker(_function) {
var blob = URL.createObjectURL(new Blob([_function.toString(),
'this.onmessage = function (eee) {' + _function.name + '(eee.data);}'
], {
type: 'application/javascript'
}));
var worker = new Worker(blob);
URL.revokeObjectURL(blob);
return worker;
}
}
function handleRecordingDuration(counter) {
counter = counter || 0;
if (self.state === 'paused') {
setTimeout(function() {
handleRecordingDuration(counter);
}, 1000);
return;
}
if (self.state === 'stopped') {
return;
}
if (counter >= self.recordingDuration) {
stopRecording(self.onRecordingStopped);
return;
}
counter += 1000; // 1-second
setTimeout(function() {
handleRecordingDuration(counter);
}, 1000);
}
function setState(state) {
if (!self) {
return;
}
self.state = state;
if (typeof self.onStateChanged.call === 'function') {
self.onStateChanged.call(self, state);
} else {
self.onStateChanged(state);
}
}
var WARNING = 'It seems that recorder is destroyed or "startRecording" is not invoked for ' + config.type + ' recorder.';
function warningLog() {
if (config.disableLogs === true) {
return;
}
console.warn(WARNING);
}
var mediaRecorder;
var returnObject = {
/**
* This method starts the recording.
* @method
* @memberof RecordRTC
* @instance
* @example
* var recorder = RecordRTC(mediaStream, {
* type: 'video'
* });
* recorder.startRecording();
*/
startRecording: startRecording,
/**
* This method stops the recording. It is strongly recommended to get "blob" or "URI" inside the callback to make sure all recorders finished their job.
* @param {function} callback - Callback to get the recorded blob.
* @method
* @memberof RecordRTC
* @instance
* @example
* recorder.stopRecording(function() {
* // use either "this" or "recorder" object; both are identical
* video.src = this.toURL();
* var blob = this.getBlob();
* });
*/
stopRecording: stopRecording,
/**
* This method pauses the recording. You can resume recording using "resumeRecording" method.
* @method
* @memberof RecordRTC
* @instance
* @todo Firefox is unable to pause the recording. Fix it.
* @example
* recorder.pauseRecording(); // pause the recording
* recorder.resumeRecording(); // resume again
*/
pauseRecording: pauseRecording,
/**
* This method resumes the recording.
* @method
* @memberof RecordRTC
* @instance
* @example
* recorder.pauseRecording(); // first of all, pause the recording
* recorder.resumeRecording(); // now resume it
*/
resumeRecording: resumeRecording,
/**
* This method initializes the recording.
* @method
* @memberof RecordRTC
* @instance
* @todo This method should be deprecated.
* @example
* recorder.initRecorder();
*/
initRecorder: initRecorder,
/**
* Ask RecordRTC to auto-stop the recording after 5 minutes.
* @method
* @memberof RecordRTC
* @instance
* @example
* var fiveMinutes = 5 * 1000 * 60;
* recorder.setRecordingDuration(fiveMinutes, function() {
* var blob = this.getBlob();
* video.src = this.toURL();
* });
*
* // or otherwise
* recorder.setRecordingDuration(fiveMinutes).onRecordingStopped(function() {
* var blob = this.getBlob();
* video.src = this.toURL();
* });
*/
setRecordingDuration: function(recordingDuration, callback) {
if (typeof recordingDuration === 'undefined') {
throw 'recordingDuration is required.';
}
if (typeof recordingDuration !== 'number') {
throw 'recordingDuration must be a number.';
}
self.recordingDuration = recordingDuration;
self.onRecordingStopped = callback || function() {};
return {
onRecordingStopped: function(callback) {
self.onRecordingStopped = callback;
}
};
},
/**
* This method can be used to clear/reset all the recorded data.
* @method
* @memberof RecordRTC
* @instance
* @todo Figure out the difference between "reset" and "clearRecordedData" methods.
* @example
* recorder.clearRecordedData();
*/
clearRecordedData: function() {
if (!mediaRecorder) {
warningLog();
return;
}
mediaRecorder.clearRecordedData();
if (!config.disableLogs) {
console.log('Cleared old recorded data.');
}
},
/**
* Get the recorded blob. Use this method inside the "stopRecording" callback.
* @method
* @memberof RecordRTC
* @instance
* @example
* recorder.stopRecording(function() {
* var blob = this.getBlob();
*
* var file = new File([blob], 'filename.webm', {
* type: 'video/webm'
* });
*
* var formData = new FormData();
* formData.append('file', file); // upload "File" object rather than a "Blob"
* uploadToServer(formData);
* });
* @returns {Blob} Returns recorded data as "Blob" object.
*/
getBlob: function() {
if (!mediaRecorder) {
warningLog();
return;
}
return mediaRecorder.blob;
},
/**
* Get data-URI instead of Blob.
* @param {function} callback - Callback to get the Data-URI.
* @method
* @memberof RecordRTC
* @instance
* @example
* recorder.stopRecording(function() {
* recorder.getDataURL(function(dataURI) {
* video.src = dataURI;
* });
* });
*/
getDataURL: getDataURL,
/**
* Get virtual/temporary URL. Usage of this URL is limited to current tab.
* @method
* @memberof RecordRTC
* @instance
* @example
* recorder.stopRecording(function() {
* video.src = this.toURL();
* });
* @returns {String} Returns a virtual/temporary URL for the recorded "Blob".
*/
toURL: function() {
if (!mediaRecorder) {
warningLog();
return;
}
return URL.createObjectURL(mediaRecorder.blob);
},
/**
* Get internal recording object (i.e. internal module) e.g. MutliStreamRecorder, MediaStreamRecorder, StereoAudioRecorder or WhammyRecorder etc.
* @method
* @memberof RecordRTC
* @instance
* @example
* var internal = recorder.getInternalRecorder();
* if(internal instanceof MultiStreamRecorder) {
* internal.addStreams([newAudioStream]);
* internal.resetVideoStreams([screenStream]);
* }
* @returns {Object} Returns internal recording object.
*/
getInternalRecorder: function() {
return mediaRecorder;
},
/**
* Invoke save-as dialog to save the recorded blob into your disk.
* @param {string} fileName - Set your own file name.
* @method
* @memberof RecordRTC
* @instance
* @example
* recorder.stopRecording(function() {
* this.save('file-name');
*
* // or manually:
* invokeSaveAsDialog(this.getBlob(), 'filename.webm');
* });
*/
save: function(fileName) {
if (!mediaRecorder) {
warningLog();
return;
}
invokeSaveAsDialog(mediaRecorder.blob, fileName);
},
/**
* This method gets a blob from indexed-DB storage.
* @param {function} callback - Callback to get the recorded blob.
* @method
* @memberof RecordRTC
* @instance
* @example
* recorder.getFromDisk(function(dataURL) {
* video.src = dataURL;
* });
*/
getFromDisk: function(callback) {
if (!mediaRecorder) {
warningLog();
return;
}
RecordRTC.getFromDisk(config.type, callback);
},
/**
* This method appends an array of webp images to the recorded video-blob. It takes an "array" object.
* @type {Array.<Array>}
* @param {Array} arrayOfWebPImages - Array of webp images.
* @method
* @memberof RecordRTC
* @instance
* @todo This method should be deprecated.
* @example
* var arrayOfWebPImages = [];
* arrayOfWebPImages.push({
* duration: index,
* image: 'data:image/webp;base64,...'
* });
* recorder.setAdvertisementArray(arrayOfWebPImages);
*/
setAdvertisementArray: function(arrayOfWebPImages) {
config.advertisement = [];
var length = arrayOfWebPImages.length;
for (var i = 0; i < length; i++) {
config.advertisement.push({
duration: i,
image: arrayOfWebPImages[i]
});
}
},
/**
* It is equivalent to <code class="str">"recorder.getBlob()"</code> method. Usage of "getBlob" is recommended, though.
* @property {Blob} blob - Recorded Blob can be accessed using this property.
* @memberof RecordRTC
* @instance
* @readonly
* @example
* recorder.stopRecording(function() {
* var blob = this.blob;
*
* // below one is recommended
* var blob = this.getBlob();
* });
*/
blob: null,
/**
* This works only with {recorderType:StereoAudioRecorder}. Use this property on "stopRecording" to verify the encoder's sample-rates.
* @property {number} bufferSize - Buffer-size used to encode the WAV container
* @memberof RecordRTC
* @instance
* @readonly
* @example
* recorder.stopRecording(function() {
* alert('Recorder used this buffer-size: ' + this.bufferSize);
* });
*/
bufferSize: 0,
/**
* This works only with {recorderType:StereoAudioRecorder}. Use this property on "stopRecording" to verify the encoder's sample-rates.
* @property {number} sampleRate - Sample-rates used to encode the WAV container
* @memberof RecordRTC
* @instance
* @readonly
* @example
* recorder.stopRecording(function() {
* alert('Recorder used these sample-rates: ' + this.sampleRate);
* });
*/
sampleRate: 0,
/**
* {recorderType:StereoAudioRecorder} returns ArrayBuffer object.
* @property {ArrayBuffer} buffer - Audio ArrayBuffer, supported only in Chrome.
* @memberof RecordRTC
* @instance
* @readonly
* @example
* recorder.stopRecording(function() {
* var arrayBuffer = this.buffer;
* alert(arrayBuffer.byteLength);
* });
*/
buffer: null,
/**
* This method resets the recorder. So that you can reuse single recorder instance many times.
* @method
* @memberof RecordRTC
* @instance
* @example
* recorder.reset();
* recorder.startRecording();
*/
reset: function() {
if (mediaRecorder && typeof mediaRecorder.clearRecordedData === 'function') {
mediaRecorder.clearRecordedData();
}
mediaRecorder = null;
setState('inactive');
self.blob = null;
},
/**
* This method is called whenever recorder's state changes. Use this as an "event".
* @property {String} state - A recorder's state can be: recording, paused, stopped or inactive.
* @method
* @memberof RecordRTC
* @instance
* @example
* recorder.onStateChanged = function(state) {
* console.log('Recorder state: ', state);
* };
*/
onStateChanged: function(state) {
if (!config.disableLogs) {
console.log('Recorder state changed:', state);
}
},
/**
* A recorder can have inactive, recording, paused or stopped states.
* @property {String} state - A recorder's state can be: recording, paused, stopped or inactive.
* @memberof RecordRTC
* @static
* @readonly
* @example
* // this looper function will keep you updated about the recorder's states.
* (function looper() {
* document.querySelector('h1').innerHTML = 'Recorder's state is: ' + recorder.state;
* if(recorder.state === 'stopped') return; // ignore+stop
* setTimeout(looper, 1000); // update after every 3-seconds
* })();
* recorder.startRecording();
*/
state: 'inactive',
/**
* Get recorder's readonly state.
* @method
* @memberof RecordRTC
* @example
* var state = recorder.getState();
* @returns {String} Returns recording state.
*/
getState: function() {
return self.state;
},
/**
* Destroy RecordRTC instance. Clear all recorders and objects.
* @method
* @memberof RecordRTC
* @example
* recorder.destroy();
*/
destroy: function() {
var disableLogsCache = config.disableLogs;
config = {
disableLogs: true
};
self.reset();
setState('destroyed');
returnObject = self = null;
if (Storage.AudioContextConstructor) {
Storage.AudioContextConstructor.close();
Storage.AudioContextConstructor = null;
}
config.disableLogs = disableLogsCache;
if (!config.disableLogs) {
console.warn('RecordRTC is destroyed.');
}
},
/**
* RecordRTC version number
* @property {String} version - Release version number.
* @memberof RecordRTC
* @static
* @readonly
* @example
* alert(recorder.version);
*/
version: '5.4.8'
};
if (!this) {
self = returnObject;
return returnObject;
}
// if someone wants to use RecordRTC with the "new" keyword.
for (var prop in returnObject) {
this[prop] = returnObject[prop];
}
self = this;
return returnObject;
}
RecordRTC.version = '5.4.8';
if (typeof module !== 'undefined' /* && !!module.exports*/ ) {
module.exports = RecordRTC;
}
if (typeof define === 'function' && define.amd) {
define('RecordRTC', [], function() {
return RecordRTC;
});
}
RecordRTC.getFromDisk = function(type, callback) {
if (!callback) {
throw 'callback is mandatory.';
}
console.log('Getting recorded ' + (type === 'all' ? 'blobs' : type + ' blob ') + ' from disk!');
DiskStorage.Fetch(function(dataURL, _type) {
if (type !== 'all' && _type === type + 'Blob' && callback) {
callback(dataURL);
}
if (type === 'all' && callback) {
callback(dataURL, _type.replace('Blob', ''));
}
});
};
/**
* This method can be used to store recorded blobs into IndexedDB storage.
* @param {object} options - {audio: Blob, video: Blob, gif: Blob}
* @method
* @memberof RecordRTC
* @example
* RecordRTC.writeToDisk({
* audio: audioBlob,
* video: videoBlob,
* gif : gifBlob
* });
*/
RecordRTC.writeToDisk = function(options) {
console.log('Writing recorded blob(s) to disk!');
options = options || {};
if (options.audio && options.video && options.gif) {
options.audio.getDataURL(function(audioDataURL) {
options.video.getDataURL(function(videoDataURL) {
options.gif.getDataURL(function(gifDataURL) {
DiskStorage.Store({
audioBlob: audioDataURL,
videoBlob: videoDataURL,
gifBlob: gifDataURL
});
});
});
});
} else if (options.audio && options.video) {
options.audio.getDataURL(function(audioDataURL) {
options.video.getDataURL(function(videoDataURL) {
DiskStorage.Store({
audioBlob: audioDataURL,
videoBlob: videoDataURL
});
});
});
} else if (options.audio && options.gif) {
options.audio.getDataURL(function(audioDataURL) {
options.gif.getDataURL(function(gifDataURL) {
DiskStorage.Store({
audioBlob: audioDataURL,
gifBlob: gifDataURL
});
});
});
} else if (options.video && options.gif) {
options.video.getDataURL(function(videoDataURL) {
options.gif.getDataURL(function(gifDataURL) {
DiskStorage.Store({
videoBlob: videoDataURL,
gifBlob: gifDataURL
});
});
});
} else if (options.audio) {
options.audio.getDataURL(function(audioDataURL) {
DiskStorage.Store({
audioBlob: audioDataURL
});
});
} else if (options.video) {
options.video.getDataURL(function(videoDataURL) {
DiskStorage.Store({
videoBlob: videoDataURL
});
});
} else if (options.gif) {
options.gif.getDataURL(function(gifDataURL) {
DiskStorage.Store({
gifBlob: gifDataURL
});
});
}
};
// __________________________
// RecordRTC-Configuration.js
/**
* {@link RecordRTCConfiguration} is an inner/private helper for {@link RecordRTC}.
* @summary It configures the 2nd parameter passed over {@link RecordRTC} and returns a valid "config" object.
* @license {@link https://github.com/muaz-khan/RecordRTC#license|MIT}
* @author {@link http://www.MuazKhan.com|Muaz Khan}
* @typedef RecordRTCConfiguration
* @class
* @example
* var options = RecordRTCConfiguration(mediaStream, options);
* @see {@link https://github.com/muaz-khan/RecordRTC|RecordRTC Source Code}
* @param {MediaStream} mediaStream - MediaStream object fetched using getUserMedia API or generated using captureStreamUntilEnded or WebAudio API.
* @param {object} config - {type:"video", disableLogs: true, numberOfAudioChannels: 1, bufferSize: 0, sampleRate: 0, video: HTMLVideoElement, getNativeBlob:true, etc.}
*/
function RecordRTCConfiguration(mediaStream, config) {
if (!config.recorderType && !config.type) {
if (!!config.audio && !!config.video) {
config.type = 'video';
} else if (!!config.audio && !config.video) {
config.type = 'audio';
}
}
if (config.recorderType && !config.type) {
if (config.recorderType === WhammyRecorder || config.recorderType === CanvasRecorder) {
config.type = 'video';
} else if (config.recorderType === GifRecorder) {
config.type = 'gif';
} else if (config.recorderType === StereoAudioRecorder) {
config.type = 'audio';
} else if (config.recorderType === MediaStreamRecorder) {
if (mediaStream.getAudioTracks().length && mediaStream.getVideoTracks().length) {
config.type = 'video';
} else if (mediaStream.getAudioTracks().length && !mediaStream.getVideoTracks().length) {
config.type = 'audio';
} else if (!mediaStream.getAudioTracks().length && mediaStream.getVideoTracks().length) {
config.type = 'audio';
} else {
// config.type = 'UnKnown';
}
}
}
if (typeof MediaStreamRecorder !== 'undefined' && typeof MediaRecorder !== 'undefined' && 'requestData' in MediaRecorder.prototype) {
if (!config.mimeType) {
config.mimeType = 'video/webm';
}
if (!config.type) {
config.type = config.mimeType.split('/')[0];
}
if (!config.bitsPerSecond) {
// config.bitsPerSecond = 128000;
}
}
// consider default type=audio
if (!config.type) {
if (config.mimeType) {
config.type = config.mimeType.split('/')[0];
}
if (!config.type) {
config.type = 'audio';
}
}
return config;
}
// __________________
// GetRecorderType.js
/**
* {@link GetRecorderType} is an inner/private helper for {@link RecordRTC}.
* @summary It returns best recorder-type available for your browser.
* @license {@link https://github.com/muaz-khan/RecordRTC#license|MIT}
* @author {@link http://www.MuazKhan.com|Muaz Khan}
* @typedef GetRecorderType
* @class
* @example
* var RecorderType = GetRecorderType(options);
* var recorder = new RecorderType(options);
* @see {@link https://github.com/muaz-khan/RecordRTC|RecordRTC Source Code}
* @param {MediaStream} mediaStream - MediaStream object fetched using getUserMedia API or generated using captureStreamUntilEnded or WebAudio API.
* @param {object} config - {type:"video", disableLogs: true, numberOfAudioChannels: 1, bufferSize: 0, sampleRate: 0, video: HTMLVideoElement, etc.}
*/
function GetRecorderType(mediaStream, config) {
var recorder;
// StereoAudioRecorder can work with all three: Edge, Firefox and Chrome
// todo: detect if it is Edge, then auto use: StereoAudioRecorder
if (isChrome || isEdge || isOpera) {
// Media Stream Recording API has not been implemented in chrome yet;
// That's why using WebAudio API to record stereo audio in WAV format
recorder = StereoAudioRecorder;
}
if (typeof MediaRecorder !== 'undefined' && 'requestData' in MediaRecorder.prototype && !isChrome) {
recorder = MediaStreamRecorder;
}
// video recorder (in WebM format)
if (config.type === 'video' && (isChrome || isOpera)) {
recorder = WhammyRecorder;
}
// video recorder (in Gif format)
if (config.type === 'gif') {
recorder = GifRecorder;
}
// html2canvas recording!
if (config.type === 'canvas') {
recorder = CanvasRecorder;
}
if (isMediaRecorderCompatible() && recorder !== CanvasRecorder && recorder !== GifRecorder && typeof MediaRecorder !== 'undefined' && 'requestData' in MediaRecorder.prototype) {
if ((mediaStream.getVideoTracks && mediaStream.getVideoTracks().length) || (mediaStream.getAudioTracks && mediaStream.getAudioTracks().length)) {
// audio-only recording
if (config.type === 'audio') {
if (typeof MediaRecorder.isTypeSupported === 'function' && MediaRecorder.isTypeSupported('audio/webm')) {
recorder = MediaStreamRecorder;
}
// else recorder = StereoAudioRecorder;
} else {
// video or screen tracks
if (typeof MediaRecorder.isTypeSupported === 'function' && MediaRecorder.isTypeSupported('video/webm')) {
recorder = MediaStreamRecorder;
}
}
}
}
if (mediaStream instanceof Array && mediaStream.length) {
recorder = MultiStreamRecorder;
}
if (config.recorderType) {
recorder = config.recorderType;
}
if (!config.disableLogs && !!recorder && !!recorder.name) {
console.log('Using recorderType:', recorder.name || recorder.constructor.name);
}
return recorder;
}
// _____________
// MRecordRTC.js
/**
* MRecordRTC runs on top of {@link RecordRTC} to bring multiple recordings in a single place, by providing simple API.
* @summary MRecordRTC stands for "Multiple-RecordRTC".
* @license {@link https://github.com/muaz-khan/RecordRTC#license|MIT}
* @author {@link http://www.MuazKhan.com|Muaz Khan}
* @typedef MRecordRTC
* @class
* @example
* var recorder = new MRecordRTC();
* recorder.addStream(MediaStream);
* recorder.mediaType = {
* audio: true, // or StereoAudioRecorder or MediaStreamRecorder
* video: true, // or WhammyRecorder or MediaStreamRecorder
* gif: true // or GifRecorder
* };
* // mimeType is optional and should be set only in advance cases.
* recorder.mimeType = {
* audio: 'audio/wav',
* video: 'video/webm',
* gif: 'image/gif'
* };
* recorder.startRecording();
* @see For further information:
* @see {@link https://github.com/muaz-khan/RecordRTC/tree/master/MRecordRTC|MRecordRTC Source Code}
* @param {MediaStream} mediaStream - MediaStream object fetched using getUserMedia API or generated using captureStreamUntilEnded or WebAudio API.
* @requires {@link RecordRTC}
*/
function MRecordRTC(mediaStream) {
/**
* This method attaches MediaStream object to {@link MRecordRTC}.
* @param {MediaStream} mediaStream - A MediaStream object, either fetched using getUserMedia API, or generated using captureStreamUntilEnded or WebAudio API.
* @method
* @memberof MRecordRTC
* @example
* recorder.addStream(MediaStream);
*/
this.addStream = function(_mediaStream) {
if (_mediaStream) {
mediaStream = _mediaStream;
}
};
/**
* This property can be used to set the recording type e.g. audio, or video, or gif, or canvas.
* @property {object} mediaType - {audio: true, video: true, gif: true}
* @memberof MRecordRTC
* @example
* var recorder = new MRecordRTC();
* recorder.mediaType = {
* audio: true, // TRUE or StereoAudioRecorder or MediaStreamRecorder
* video: true, // TRUE or WhammyRecorder or MediaStreamRecorder
* gif : true // TRUE or GifRecorder
* };
*/
this.mediaType = {
audio: true,
video: true
};
/**
* This method starts recording.
* @method
* @memberof MRecordRTC
* @example
* recorder.startRecording();
*/
this.startRecording = function() {
var mediaType = this.mediaType;
var recorderType;
var mimeType = this.mimeType || {
audio: null,
video: null,
gif: null
};
if (typeof mediaType.audio !== 'function' && isMediaRecorderCompatible() && mediaStream.getAudioTracks && !mediaStream.getAudioTracks().length) {
mediaType.audio = false;
}
if (typeof mediaType.video !== 'function' && isMediaRecorderCompatible() && mediaStream.getVideoTracks && !mediaStream.getVideoTracks().length) {
mediaType.video = false;
}
if (typeof mediaType.gif !== 'function' && isMediaRecorderCompatible() && mediaStream.getVideoTracks && !mediaStream.getVideoTracks().length) {
mediaType.gif = false;
}
if (!mediaType.audio && !mediaType.video && !mediaType.gif) {
throw 'MediaStream must have either audio or video tracks.';
}
if (!!mediaType.audio) {
recorderType = null;
if (typeof mediaType.audio === 'function') {
recorderType = mediaType.audio;
}
this.audioRecorder = new RecordRTC(mediaStream, {
type: 'audio',
bufferSize: this.bufferSize,
sampleRate: this.sampleRate,
numberOfAudioChannels: this.numberOfAudioChannels || 2,
disableLogs: this.disableLogs,
recorderType: recorderType,
mimeType: mimeType.audio,
timeSlice: this.timeSlice,
onTimeStamp: this.onTimeStamp
});
if (!mediaType.video) {
this.audioRecorder.startRecording();
}
}
if (!!mediaType.video) {
recorderType = null;
if (typeof mediaType.video === 'function') {
recorderType = mediaType.video;
}
var newStream = mediaStream;
if (isMediaRecorderCompatible() && !!mediaType.audio && typeof mediaType.audio === 'function') {
var videoTrack = mediaStream.getVideoTracks()[0];
if (!!navigator.mozGetUserMedia) {
newStream = new MediaStream();
newStream.addTrack(videoTrack);
if (recorderType && recorderType === WhammyRecorder) {
// Firefox does NOT support webp-encoding yet
recorderType = MediaStreamRecorder;
}
} else {
newStream = new MediaStream([videoTrack]);
}
}
this.videoRecorder = new RecordRTC(newStream, {
type: 'video',
video: this.video,
canvas: this.canvas,
frameInterval: this.frameInterval || 10,
disableLogs: this.disableLogs,
recorderType: recorderType,
mimeType: mimeType.video,
timeSlice: this.timeSlice,
onTimeStamp: this.onTimeStamp
});
if (!mediaType.audio) {
this.videoRecorder.startRecording();
}
}
if (!!mediaType.audio && !!mediaType.video) {
var self = this;
// this line prevents StereoAudioRecorder
// todo: fix it
if (isMediaRecorderCompatible() /* && !this.audioRecorder */ ) {
self.audioRecorder = null;
self.videoRecorder.startRecording();
} else {
self.videoRecorder.initRecorder(function() {
self.audioRecorder.initRecorder(function() {
// Both recorders are ready to record things accurately
self.videoRecorder.startRecording();
self.audioRecorder.startRecording();
});
});
}
}
if (!!mediaType.gif) {
recorderType = null;
if (typeof mediaType.gif === 'function') {
recorderType = mediaType.gif;
}
this.gifRecorder = new RecordRTC(mediaStream, {
type: 'gif',
frameRate: this.frameRate || 200,
quality: this.quality || 10,
disableLogs: this.disableLogs,
recorderType: recorderType,
mimeType: mimeType.gif
});
this.gifRecorder.startRecording();
}
};
/**
* This method stops recording.
* @param {function} callback - Callback function is invoked when all encoders finished their jobs.
* @method
* @memberof MRecordRTC
* @example
* recorder.stopRecording(function(recording){
* var audioBlob = recording.audio;
* var videoBlob = recording.video;
* var gifBlob = recording.gif;
* });
*/
this.stopRecording = function(callback) {
callback = callback || function() {};
if (this.audioRecorder) {
this.audioRecorder.stopRecording(function(blobURL) {
callback(blobURL, 'audio');
});
}
if (this.videoRecorder) {
this.videoRecorder.stopRecording(function(blobURL) {
callback(blobURL, 'video');
});
}
if (this.gifRecorder) {
this.gifRecorder.stopRecording(function(blobURL) {
callback(blobURL, 'gif');
});
}
};
/**
* This method pauses recording.
* @method
* @memberof MRecordRTC
* @example
* recorder.pauseRecording();
*/
this.pauseRecording = function() {
if (this.audioRecorder) {
this.audioRecorder.pauseRecording();
}
if (this.videoRecorder) {
this.videoRecorder.pauseRecording();
}
if (this.gifRecorder) {
this.gifRecorder.pauseRecording();
}
};
/**
* This method resumes recording.
* @method
* @memberof MRecordRTC
* @example
* recorder.resumeRecording();
*/
this.resumeRecording = function() {
if (this.audioRecorder) {
this.audioRecorder.resumeRecording();
}
if (this.videoRecorder) {
this.videoRecorder.resumeRecording();
}
if (this.gifRecorder) {
this.gifRecorder.resumeRecording();
}
};
/**
* This method can be used to manually get all recorded blobs.
* @param {function} callback - All recorded blobs are passed back to the "callback" function.
* @method
* @memberof MRecordRTC
* @example
* recorder.getBlob(function(recording){
* var audioBlob = recording.audio;
* var videoBlob = recording.video;
* var gifBlob = recording.gif;
* });
* // or
* var audioBlob = recorder.getBlob().audio;
* var videoBlob = recorder.getBlob().video;
*/
this.getBlob = function(callback) {
var output = {};
if (this.audioRecorder) {
output.audio = this.audioRecorder.getBlob();
}
if (this.videoRecorder) {
output.video = this.videoRecorder.getBlob();
}
if (this.gifRecorder) {
output.gif = this.gifRecorder.getBlob();
}
if (callback) {
callback(output);
}
return output;
};
/**
* Destroy all recorder instances.
* @method
* @memberof MRecordRTC
* @example
* recorder.destroy();
*/
this.destroy = function() {
if (this.audioRecorder) {
this.audioRecorder.destroy();
this.audioRecorder = null;
}
if (this.videoRecorder) {
this.videoRecorder.destroy();
this.videoRecorder = null;
}
if (this.gifRecorder) {
this.gifRecorder.destroy();
this.gifRecorder = null;
}
};
/**
* This method can be used to manually get all recorded blobs' DataURLs.
* @param {function} callback - All recorded blobs' DataURLs are passed back to the "callback" function.
* @method
* @memberof MRecordRTC
* @example
* recorder.getDataURL(function(recording){
* var audioDataURL = recording.audio;
* var videoDataURL = recording.video;
* var gifDataURL = recording.gif;
* });
*/
this.getDataURL = function(callback) {
this.getBlob(function(blob) {
if (blob.audio && blob.video) {
getDataURL(blob.audio, function(_audioDataURL) {
getDataURL(blob.video, function(_videoDataURL) {
callback({
audio: _audioDataURL,
video: _videoDataURL
});
});
});
} else if (blob.audio) {
getDataURL(blob.audio, function(_audioDataURL) {
callback({
audio: _audioDataURL
});
});
} else if (blob.video) {
getDataURL(blob.video, function(_videoDataURL) {
callback({
video: _videoDataURL
});
});
}
});
function getDataURL(blob, callback00) {
if (typeof Worker !== 'undefined') {
var webWorker = processInWebWorker(function readFile(_blob) {
postMessage(new FileReaderSync().readAsDataURL(_blob));
});
webWorker.onmessage = function(event) {
callback00(event.data);
};
webWorker.postMessage(blob);
} else {
var reader = new FileReader();
reader.readAsDataURL(blob);
reader.onload = function(event) {
callback00(event.target.result);
};
}
}
function processInWebWorker(_function) {
var blob = URL.createObjectURL(new Blob([_function.toString(),
'this.onmessage = function (eee) {' + _function.name + '(eee.data);}'
], {
type: 'application/javascript'
}));
var worker = new Worker(blob);
var url;
if (typeof URL !== 'undefined') {
url = URL;
} else if (typeof webkitURL !== 'undefined') {
url = webkitURL;
} else {
throw 'Neither URL nor webkitURL detected.';
}
url.revokeObjectURL(blob);
return worker;
}
};
/**
* This method can be used to ask {@link MRecordRTC} to write all recorded blobs into IndexedDB storage.
* @method
* @memberof MRecordRTC
* @example
* recorder.writeToDisk();
*/
this.writeToDisk = function() {
RecordRTC.writeToDisk({
audio: this.audioRecorder,
video: this.videoRecorder,
gif: this.gifRecorder
});
};
/**
* This method can be used to invoke a save-as dialog for all recorded blobs.
* @param {object} args - {audio: 'audio-name', video: 'video-name', gif: 'gif-name'}
* @method
* @memberof MRecordRTC
* @example
* recorder.save({
* audio: 'audio-file-name',
* video: 'video-file-name',
* gif : 'gif-file-name'
* });
*/
this.save = function(args) {
args = args || {
audio: true,
video: true,
gif: true
};
if (!!args.audio && this.audioRecorder) {
this.audioRecorder.save(typeof args.audio === 'string' ? args.audio : '');
}
if (!!args.video && this.videoRecorder) {
this.videoRecorder.save(typeof args.video === 'string' ? args.video : '');
}
if (!!args.gif && this.gifRecorder) {
this.gifRecorder.save(typeof args.gif === 'string' ? args.gif : '');
}
};
}
/**
* This method can be used to get all recorded blobs from IndexedDB storage.
* @param {string} type - 'all' or 'audio' or 'video' or 'gif'
* @param {function} callback - Callback function to get all stored blobs.
* @method
* @memberof MRecordRTC
* @example
* MRecordRTC.getFromDisk('all', function(dataURL, type){
* if(type === 'audio') { }
* if(type === 'video') { }
* if(type === 'gif') { }
* });
*/
MRecordRTC.getFromDisk = RecordRTC.getFromDisk;
/**
* This method can be used to store recorded blobs into IndexedDB storage.
* @param {object} options - {audio: Blob, video: Blob, gif: Blob}
* @method
* @memberof MRecordRTC
* @example
* MRecordRTC.writeToDisk({
* audio: audioBlob,
* video: videoBlob,
* gif : gifBlob
* });
*/
MRecordRTC.writeToDisk = RecordRTC.writeToDisk;
if (typeof RecordRTC !== 'undefined') {
RecordRTC.MRecordRTC = MRecordRTC;
}
var browserFakeUserAgent = 'Fake/5.0 (FakeOS) AppleWebKit/123 (KHTML, like Gecko) Fake/12.3.4567.89 Fake/123.45';
(function(that) {
if (!that) {
return;
}
if (typeof window !== 'undefined') {
return;
}
if (typeof global === 'undefined') {
return;
}
global.navigator = {
userAgent: browserFakeUserAgent,
getUserMedia: function() {}
};
if (!global.console) {
global.console = {};
}
if (typeof global.console.log === 'undefined' || typeof global.console.error === 'undefined') {
global.console.error = global.console.log = global.console.log || function() {
console.log(arguments);
};
}
if (typeof document === 'undefined') {
/*global document:true */
that.document = {};
document.createElement = document.captureStream = document.mozCaptureStream = function() {
var obj = {
getContext: function() {
return obj;
},
play: function() {},
pause: function() {},
drawImage: function() {},
toDataURL: function() {
return '';
}
};
return obj;
};
that.HTMLVideoElement = function() {};
}
if (typeof location === 'undefined') {
/*global location:true */
that.location = {
protocol: 'file:',
href: '',
hash: ''
};
}
if (typeof screen === 'undefined') {
/*global screen:true */
that.screen = {
width: 0,
height: 0
};
}
if (typeof URL === 'undefined') {
/*global screen:true */
that.URL = {
createObjectURL: function() {
return '';
},
revokeObjectURL: function() {
return '';
}
};
}
/*global window:true */
that.window = global;
})(typeof global !== 'undefined' ? global : null);
// _____________________________
// Cross-Browser-Declarations.js
// animation-frame used in WebM recording
/*jshint -W079 */
var requestAnimationFrame = window.requestAnimationFrame;
if (typeof requestAnimationFrame === 'undefined') {
if (typeof webkitRequestAnimationFrame !== 'undefined') {
/*global requestAnimationFrame:true */
requestAnimationFrame = webkitRequestAnimationFrame;
} else if (typeof mozRequestAnimationFrame !== 'undefined') {
/*global requestAnimationFrame:true */
requestAnimationFrame = mozRequestAnimationFrame;
} else if (typeof msRequestAnimationFrame !== 'undefined') {
/*global requestAnimationFrame:true */
requestAnimationFrame = msRequestAnimationFrame;
} else if (typeof requestAnimationFrame === 'undefined') {
// via: https://gist.github.com/paulirish/1579671
var lastTime = 0;
/*global requestAnimationFrame:true */
requestAnimationFrame = function(callback, element) {
var currTime = new Date().getTime();
var timeToCall = Math.max(0, 16 - (currTime - lastTime));
var id = setTimeout(function() {
callback(currTime + timeToCall);
}, timeToCall);
lastTime = currTime + timeToCall;
return id;
};
}
}
/*jshint -W079 */
var cancelAnimationFrame = window.cancelAnimationFrame;
if (typeof cancelAnimationFrame === 'undefined') {
if (typeof webkitCancelAnimationFrame !== 'undefined') {
/*global cancelAnimationFrame:true */
cancelAnimationFrame = webkitCancelAnimationFrame;
} else if (typeof mozCancelAnimationFrame !== 'undefined') {
/*global cancelAnimationFrame:true */
cancelAnimationFrame = mozCancelAnimationFrame;
} else if (typeof msCancelAnimationFrame !== 'undefined') {
/*global cancelAnimationFrame:true */
cancelAnimationFrame = msCancelAnimationFrame;
} else if (typeof cancelAnimationFrame === 'undefined') {
/*global cancelAnimationFrame:true */
cancelAnimationFrame = function(id) {
clearTimeout(id);
};
}
}
// WebAudio API representer
var AudioContext = window.AudioContext;
if (typeof AudioContext === 'undefined') {
if (typeof webkitAudioContext !== 'undefined') {
/*global AudioContext:true */
AudioContext = webkitAudioContext;
}
if (typeof mozAudioContext !== 'undefined') {
/*global AudioContext:true */
AudioContext = mozAudioContext;
}
}
/*jshint -W079 */
var URL = window.URL;
if (typeof URL === 'undefined' && typeof webkitURL !== 'undefined') {
/*global URL:true */
URL = webkitURL;
}
if (typeof navigator !== 'undefined' && typeof navigator.getUserMedia === 'undefined') { // maybe window.navigator?
if (typeof navigator.webkitGetUserMedia !== 'undefined') {
navigator.getUserMedia = navigator.webkitGetUserMedia;
}
if (typeof navigator.mozGetUserMedia !== 'undefined') {
navigator.getUserMedia = navigator.mozGetUserMedia;
}
}
var isEdge = navigator.userAgent.indexOf('Edge') !== -1 && (!!navigator.msSaveBlob || !!navigator.msSaveOrOpenBlob);
var isOpera = !!window.opera || navigator.userAgent.indexOf('OPR/') !== -1;
var isSafari = navigator.userAgent.toLowerCase().indexOf('safari/') > -1;
var isChrome = (!isOpera && !isEdge && !!navigator.webkitGetUserMedia) || isElectron() || isSafari;
var MediaStream = window.MediaStream;
if (typeof MediaStream === 'undefined' && typeof webkitMediaStream !== 'undefined') {
MediaStream = webkitMediaStream;
}
/*global MediaStream:true */
if (typeof MediaStream !== 'undefined') {
if (!('getVideoTracks' in MediaStream.prototype)) {
MediaStream.prototype.getVideoTracks = function() {
if (!this.getTracks) {
return [];
}
var tracks = [];
this.getTracks().forEach(function(track) {
if (track.kind.toString().indexOf('video') !== -1) {
tracks.push(track);
}
});
return tracks;
};
MediaStream.prototype.getAudioTracks = function() {
if (!this.getTracks) {
return [];
}
var tracks = [];
this.getTracks().forEach(function(track) {
if (track.kind.toString().indexOf('audio') !== -1) {
tracks.push(track);
}
});
return tracks;
};
}
// override "stop" method for all browsers
if (typeof MediaStream.prototype.stop === 'undefined') {
MediaStream.prototype.stop = function() {
this.getTracks().forEach(function(track) {
track.stop();
});
};
}
}
// below function via: http://goo.gl/B3ae8c
/**
* @param {number} bytes - Pass bytes and get formafted string.
* @returns {string} - formafted string
* @example
* bytesToSize(1024*1024*5) === '5 GB'
* @see {@link https://github.com/muaz-khan/RecordRTC|RecordRTC Source Code}
*/
function bytesToSize(bytes) {
var k = 1000;
var sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
if (bytes === 0) {
return '0 Bytes';
}
var i = parseInt(Math.floor(Math.log(bytes) / Math.log(k)), 10);
return (bytes / Math.pow(k, i)).toPrecision(3) + ' ' + sizes[i];
}
/**
* @param {Blob} file - File or Blob object. This parameter is required.
* @param {string} fileName - Optional file name e.g. "Recorded-Video.webm"
* @example
* invokeSaveAsDialog(blob or file, [optional] fileName);
* @see {@link https://github.com/muaz-khan/RecordRTC|RecordRTC Source Code}
*/
function invokeSaveAsDialog(file, fileName) {
if (!file) {
throw 'Blob object is required.';
}
if (!file.type) {
try {
file.type = 'video/webm';
} catch (e) {}
}
var fileExtension = (file.type || 'video/webm').split('/')[1];
if (fileName && fileName.indexOf('.') !== -1) {
var splitted = fileName.split('.');
fileName = splitted[0];
fileExtension = splitted[1];
}
var fileFullName = (fileName || (Math.round(Math.random() * 9999999999) + 888888888)) + '.' + fileExtension;
if (typeof navigator.msSaveOrOpenBlob !== 'undefined') {
return navigator.msSaveOrOpenBlob(file, fileFullName);
} else if (typeof navigator.msSaveBlob !== 'undefined') {
return navigator.msSaveBlob(file, fileFullName);
}
var hyperlink = document.createElement('a');
hyperlink.href = URL.createObjectURL(file);
hyperlink.download = fileFullName;
hyperlink.style = 'display:none;opacity:0;color:transparent;';
(document.body || document.documentElement).appendChild(hyperlink);
if (typeof hyperlink.click === 'function') {
hyperlink.click();
} else {
hyperlink.target = '_blank';
hyperlink.dispatchEvent(new MouseEvent('click', {
view: window,
bubbles: true,
cancelable: true
}));
}
URL.revokeObjectURL(hyperlink.href);
}
/**
* from: https://github.com/cheton/is-electron/blob/master/index.js
**/
function isElectron() {
// Renderer process
if (typeof window !== 'undefined' && typeof window.process === 'object' && window.process.type === 'renderer') {
return true;
}
// Main process
if (typeof process !== 'undefined' && typeof process.versions === 'object' && !!process.versions.electron) {
return true;
}
// Detect the user agent when the `nodeIntegration` option is set to true
if (typeof navigator === 'object' && typeof navigator.userAgent === 'string' && navigator.userAgent.indexOf('Electron') >= 0) {
return true;
}
return false;
}
function setSrcObject(stream, element, ignoreCreateObjectURL) {
if ('createObjectURL' in URL && !ignoreCreateObjectURL) {
try {
element.src = URL.createObjectURL(stream);
} catch (e) {
setSrcObject(stream, element, true);
return;
}
} else if ('srcObject' in element) {
element.srcObject = stream;
} else if ('mozSrcObject' in element) {
element.mozSrcObject = stream;
} else {
alert('createObjectURL/srcObject both are not supported.');
}
}
// __________ (used to handle stuff like http://goo.gl/xmE5eg) issue #129
// Storage.js
/**
* Storage is a standalone object used by {@link RecordRTC} to store reusable objects e.g. "new AudioContext".
* @license {@link https://github.com/muaz-khan/RecordRTC#license|MIT}
* @author {@link http://www.MuazKhan.com|Muaz Khan}
* @example
* Storage.AudioContext === webkitAudioContext
* @property {webkitAudioContext} AudioContext - Keeps a reference to AudioContext object.
* @see {@link https://github.com/muaz-khan/RecordRTC|RecordRTC Source Code}
*/
var Storage = {};
if (typeof AudioContext !== 'undefined') {
Storage.AudioContext = AudioContext;
} else if (typeof webkitAudioContext !== 'undefined') {
Storage.AudioContext = webkitAudioContext;
}
if (typeof RecordRTC !== 'undefined') {
RecordRTC.Storage = Storage;
}
function isMediaRecorderCompatible() {
var isOpera = !!window.opera || navigator.userAgent.indexOf(' OPR/') >= 0;
var isChrome = (!!window.chrome && !isOpera) || isElectron();
var isFirefox = typeof window.InstallTrigger !== 'undefined';
if (isFirefox) {
return true;
}
var nVer = navigator.appVersion;
var nAgt = navigator.userAgent;
var fullVersion = '' + parseFloat(navigator.appVersion);
var majorVersion = parseInt(navigator.appVersion, 10);
var nameOffset, verOffset, ix;
if (isChrome || isOpera) {
verOffset = nAgt.indexOf('Chrome');
fullVersion = nAgt.substring(verOffset + 7);
}
// trim the fullVersion string at semicolon/space if present
if ((ix = fullVersion.indexOf(';')) !== -1) {
fullVersion = fullVersion.substring(0, ix);
}
if ((ix = fullVersion.indexOf(' ')) !== -1) {
fullVersion = fullVersion.substring(0, ix);
}
majorVersion = parseInt('' + fullVersion, 10);
if (isNaN(majorVersion)) {
fullVersion = '' + parseFloat(navigator.appVersion);
majorVersion = parseInt(navigator.appVersion, 10);
}
return majorVersion >= 49;
}
// ______________________
// MediaStreamRecorder.js
/**
* MediaStreamRecorder is an abstraction layer for {@link https://w3c.github.io/mediacapture-record/MediaRecorder.html|MediaRecorder API}. It is used by {@link RecordRTC} to record MediaStream(s) in both Chrome and Firefox.
* @summary Runs top over {@link https://w3c.github.io/mediacapture-record/MediaRecorder.html|MediaRecorder API}.
* @license {@link https://github.com/muaz-khan/RecordRTC#license|MIT}
* @author {@link https://github.com/muaz-khan|Muaz Khan}
* @typedef MediaStreamRecorder
* @class
* @example
* var config = {
* mimeType: 'video/webm', // vp8, vp9, h264, mkv, opus/vorbis
* audioBitsPerSecond : 256 * 8 * 1024,
* videoBitsPerSecond : 256 * 8 * 1024,
* bitsPerSecond: 256 * 8 * 1024, // if this is provided, skip above two
* checkForInactiveTracks: true,
* timeSlice: 1000, // concatenate intervals based blobs
* ondataavailable: function() {} // get intervals based blobs
* }
* var recorder = new MediaStreamRecorder(mediaStream, config);
* recorder.record();
* recorder.stop(function(blob) {
* video.src = URL.createObjectURL(blob);
*
* // or
* var blob = recorder.blob;
* });
* @see {@link https://github.com/muaz-khan/RecordRTC|RecordRTC Source Code}
* @param {MediaStream} mediaStream - MediaStream object fetched using getUserMedia API or generated using captureStreamUntilEnded or WebAudio API.
* @param {object} config - {disableLogs:true, initCallback: function, mimeType: "video/webm", timeSlice: 1000}
* @throws Will throw an error if first argument "MediaStream" is missing. Also throws error if "MediaRecorder API" are not supported by the browser.
*/
function MediaStreamRecorder(mediaStream, config) {
var self = this;
if (typeof mediaStream === 'undefined') {
throw 'First argument "MediaStream" is required.';
}
if (typeof MediaRecorder === 'undefined') {
throw 'Your browser does not supports Media Recorder API. Please try other modules e.g. WhammyRecorder or StereoAudioRecorder.';
}
config = config || {
// bitsPerSecond: 256 * 8 * 1024,
mimeType: 'video/webm'
};
if (config.type === 'audio') {
if (mediaStream.getVideoTracks().length && mediaStream.getAudioTracks().length) {
var stream;
if (!!navigator.mozGetUserMedia) {
stream = new MediaStream();
stream.addTrack(mediaStream.getAudioTracks()[0]);
} else {
// webkitMediaStream
stream = new MediaStream(mediaStream.getAudioTracks());
}
mediaStream = stream;
}
if (!config.mimeType || config.mimeType.toString().toLowerCase().indexOf('audio') === -1) {
config.mimeType = isChrome ? 'audio/webm' : 'audio/ogg';
}
if (config.mimeType && config.mimeType.toString().toLowerCase() !== 'audio/ogg' && !!navigator.mozGetUserMedia) {
// forcing better codecs on Firefox (via #166)
config.mimeType = 'audio/ogg';
}
}
var arrayOfBlobs = [];
/**
* This method returns array of blobs. Use only with "timeSlice". Its useful to preview recording anytime, without using the "stop" method.
* @method
* @memberof MediaStreamRecorder
* @example
* var arrayOfBlobs = recorder.getArrayOfBlobs();
* @returns {Array} Returns array of recorded blobs.
*/
this.getArrayOfBlobs = function() {
return arrayOfBlobs;
};
/**
* This method records MediaStream.
* @method
* @memberof MediaStreamRecorder
* @example
* recorder.record();
*/
this.record = function() {
// set defaults
self.blob = null;
self.clearRecordedData();
self.timestamps = [];
allStates = [];
arrayOfBlobs = [];
var recorderHints = config;
if (!config.disableLogs) {
console.log('Passing following config over MediaRecorder API.', recorderHints);
}
if (mediaRecorder) {
// mandatory to make sure Firefox doesn't fails to record streams 3-4 times without reloading the page.
mediaRecorder = null;
}
if (isChrome && !isMediaRecorderCompatible()) {
// to support video-only recording on stable
recorderHints = 'video/vp8';
}
if (typeof MediaRecorder.isTypeSupported === 'function' && recorderHints.mimeType) {
if (!MediaRecorder.isTypeSupported(recorderHints.mimeType)) {
if (!config.disableLogs) {
console.warn('MediaRecorder API seems unable to record mimeType:', recorderHints.mimeType);
}
recorderHints.mimeType = config.type === 'audio' ? 'audio/webm' : 'video/webm';
}
}
// using MediaRecorder API here
try {
mediaRecorder = new MediaRecorder(mediaStream, recorderHints);
// reset
config.mimeType = recorderHints.mimeType;
} catch (e) {
// chrome-based fallback
mediaRecorder = new MediaRecorder(mediaStream);
}
// old hack?
if (recorderHints.mimeType && !MediaRecorder.isTypeSupported && 'canRecordMimeType' in mediaRecorder && mediaRecorder.canRecordMimeType(recorderHints.mimeType) === false) {
if (!config.disableLogs) {
console.warn('MediaRecorder API seems unable to record mimeType:', recorderHints.mimeType);
}
}
// Dispatching OnDataAvailable Handler
mediaRecorder.ondataavailable = function(e) {
if (e.data) {
allStates.push('ondataavailable: ' + bytesToSize(e.data.size));
}
if (typeof config.timeSlice === 'number') {
if (e.data && e.data.size && e.data.size > 100) {
arrayOfBlobs.push(e.data);
updateTimeStamp();
if (typeof config.ondataavailable === 'function') {
// intervals based blobs
var blob = config.getNativeBlob ? e.data : new Blob([e.data], {
type: getMimeType(recorderHints)
});
config.ondataavailable(blob);
}
}
return;
}
if (!e.data || !e.data.size || e.data.size < 100 || self.blob) {
// make sure that stopRecording always getting fired
// even if there is invalid data
if (self.recordingCallback) {
self.recordingCallback(new Blob([], {
type: getMimeType(recorderHints)
}));
self.recordingCallback = null;
}
return;
}
self.blob = config.getNativeBlob ? e.data : new Blob([e.data], {
type: getMimeType(recorderHints)
});
if (self.recordingCallback) {
self.recordingCallback(self.blob);
self.recordingCallback = null;
}
};
mediaRecorder.onstart = function() {
allStates.push('started');
};
mediaRecorder.onpause = function() {
allStates.push('paused');
};
mediaRecorder.onresume = function() {
allStates.push('resumed');
};
mediaRecorder.onstop = function() {
allStates.push('stopped');
};
mediaRecorder.onerror = function(error) {
if (!error) {
return;
}
if (!error.name) {
error.name = 'UnknownError';
}
allStates.push('error: ' + error);
if (!config.disableLogs) {
// via: https://w3c.github.io/mediacapture-record/MediaRecorder.html#exception-summary
if (error.name.toString().toLowerCase().indexOf('invalidstate') !== -1) {
console.error('The MediaRecorder is not in a state in which the proposed operation is allowed to be executed.', error);
} else if (error.name.toString().toLowerCase().indexOf('notsupported') !== -1) {
console.error('MIME type (', recorderHints.mimeType, ') is not supported.', error);
} else if (error.name.toString().toLowerCase().indexOf('security') !== -1) {
console.error('MediaRecorder security error', error);
}
// older code below
else if (error.name === 'OutOfMemory') {
console.error('The UA has exhaused the available memory. User agents SHOULD provide as much additional information as possible in the message attribute.', error);
} else if (error.name === 'IllegalStreamModification') {
console.error('A modification to the stream has occurred that makes it impossible to continue recording. An example would be the addition of a Track while recording is occurring. User agents SHOULD provide as much additional information as possible in the message attribute.', error);
} else if (error.name === 'OtherRecordingError') {
console.error('Used for an fatal error other than those listed above. User agents SHOULD provide as much additional information as possible in the message attribute.', error);
} else if (error.name === 'GenericError') {
console.error('The UA cannot provide the codec or recording option that has been requested.', error);
} else {
console.error('MediaRecorder Error', error);
}
}
(function(looper) {
if (!self.manuallyStopped && mediaRecorder && mediaRecorder.state === 'inactive') {
delete config.timeslice;
// 10 minutes, enough?
mediaRecorder.start(10 * 60 * 1000);
return;
}
setTimeout(looper, 1000);
})();
if (mediaRecorder.state !== 'inactive' && mediaRecorder.state !== 'stopped') {
mediaRecorder.stop();
}
};
if (typeof config.timeSlice === 'number') {
updateTimeStamp();
mediaRecorder.start(config.timeSlice);
} else {
// default is 60 minutes; enough?
// use config => {timeSlice: 1000} otherwise
mediaRecorder.start(3.6e+6);
}
if (config.initCallback) {
config.initCallback(); // old code
}
};
/**
* @property {Array} timestamps - Array of time stamps
* @memberof MediaStreamRecorder
* @example
* console.log(recorder.timestamps);
*/
this.timestamps = [];
function updateTimeStamp() {
self.timestamps.push(new Date().getTime());
if (typeof config.onTimeStamp === 'function') {
config.onTimeStamp(self.timestamps[self.timestamps.length - 1], self.timestamps);
}
}
function getMimeType(secondObject) {
if (mediaRecorder && mediaRecorder.mimeType) {
return mediaRecorder.mimeType;
}
return secondObject.mimeType || 'video/webm';
}
/**
* This method stops recording MediaStream.
* @param {function} callback - Callback function, that is used to pass recorded blob back to the callee.
* @method
* @memberof MediaStreamRecorder
* @example
* recorder.stop(function(blob) {
* video.src = URL.createObjectURL(blob);
* });
*/
this.stop = function(callback) {
callback = callback || function() {};
self.manuallyStopped = true; // used inside the mediaRecorder.onerror
if (!mediaRecorder) {
return;
}
this.recordingCallback = callback;
if (mediaRecorder.state === 'recording') {
mediaRecorder.stop();
}
if (typeof config.timeSlice === 'number') {
setTimeout(function() {
self.blob = new Blob(arrayOfBlobs, {
type: getMimeType(config)
});
self.recordingCallback(self.blob);
}, 100);
}
};
/**
* This method pauses the recording process.
* @method
* @memberof MediaStreamRecorder
* @example
* recorder.pause();
*/
this.pause = function() {
if (!mediaRecorder) {
return;
}
if (mediaRecorder.state === 'recording') {
mediaRecorder.pause();
}
};
/**
* This method resumes the recording process.
* @method
* @memberof MediaStreamRecorder
* @example
* recorder.resume();
*/
this.resume = function() {
if (!mediaRecorder) {
return;
}
if (mediaRecorder.state === 'paused') {
mediaRecorder.resume();
}
};
/**
* This method resets currently recorded data.
* @method
* @memberof MediaStreamRecorder
* @example
* recorder.clearRecordedData();
*/
this.clearRecordedData = function() {
if (mediaRecorder && mediaRecorder.state === 'recording') {
self.stop(clearRecordedDataCB);
}
clearRecordedDataCB();
};
function clearRecordedDataCB() {
arrayOfBlobs = [];
mediaRecorder = null;
self.timestamps = [];
}
// Reference to "MediaRecorder" object
var mediaRecorder;
/**
* Access to native MediaRecorder API
* @method
* @memberof MediaStreamRecorder
* @instance
* @example
* var internal = recorder.getInternalRecorder();
* internal.ondataavailable = function() {}; // override
* internal.stream, internal.onpause, internal.onstop, etc.
* @returns {Object} Returns internal recording object.
*/
this.getInternalRecorder = function() {
return mediaRecorder;
};
function isMediaStreamActive() {
if ('active' in mediaStream) {
if (!mediaStream.active) {
return false;
}
} else if ('ended' in mediaStream) { // old hack
if (mediaStream.ended) {
return false;
}
}
return true;
}
/**
* @property {Blob} blob - Recorded data as "Blob" object.
* @memberof MediaStreamRecorder
* @example
* recorder.stop(function() {
* var blob = recorder.blob;
* });
*/
this.blob = null;
/**
* Get MediaRecorder readonly state.
* @method
* @memberof MediaStreamRecorder
* @example
* var state = recorder.getState();
* @returns {String} Returns recording state.
*/
this.getState = function() {
if (!mediaRecorder) {
return 'inactive';
}
return mediaRecorder.state || 'inactive';
};
// list of all recording states
var allStates = [];
/**
* Get MediaRecorder all recording states.
* @method
* @memberof MediaStreamRecorder
* @example
* var state = recorder.getAllStates();
* @returns {Array} Returns all recording states
*/
this.getAllStates = function() {
return allStates;
};
// if any Track within the MediaStream is muted or not enabled at any time,
// the browser will only record black frames
// or silence since that is the content produced by the Track
// so we need to stopRecording as soon as any single track ends.
if (typeof config.checkForInactiveTracks === 'undefined') {
config.checkForInactiveTracks = false; // disable to minimize CPU usage
}
var self = this;
// this method checks if media stream is stopped
// or if any track is ended.
(function looper() {
if (!mediaRecorder || config.checkForInactiveTracks === false) {
return;
}
if (isMediaStreamActive() === false) {
if (!config.disableLogs) {
console.log('MediaStream seems stopped.');
}
self.stop();
return;
}
setTimeout(looper, 1000); // check every second
})();
// for debugging
this.name = 'MediaStreamRecorder';
this.toString = function() {
return this.name;
};
}
if (typeof RecordRTC !== 'undefined') {
RecordRTC.MediaStreamRecorder = MediaStreamRecorder;
}
// source code from: http://typedarray.org/wp-content/projects/WebAudioRecorder/script.js
// https://github.com/mattdiamond/Recorderjs#license-mit
// ______________________
// StereoAudioRecorder.js
/**
* StereoAudioRecorder is a standalone class used by {@link RecordRTC} to bring "stereo" audio-recording in chrome.
* @summary JavaScript standalone object for stereo audio recording.
* @license {@link https://github.com/muaz-khan/RecordRTC#license|MIT}
* @author {@link http://www.MuazKhan.com|Muaz Khan}
* @typedef StereoAudioRecorder
* @class
* @example
* var recorder = new StereoAudioRecorder(MediaStream, {
* sampleRate: 44100,
* bufferSize: 4096
* });
* recorder.record();
* recorder.stop(function(blob) {
* video.src = URL.createObjectURL(blob);
* });
* @see {@link https://github.com/muaz-khan/RecordRTC|RecordRTC Source Code}
* @param {MediaStream} mediaStream - MediaStream object fetched using getUserMedia API or generated using captureStreamUntilEnded or WebAudio API.
* @param {object} config - {sampleRate: 44100, bufferSize: 4096, numberOfAudioChannels: 1, etc.}
*/
function StereoAudioRecorder(mediaStream, config) {
if (!mediaStream.getAudioTracks().length) {
throw 'Your stream has no audio tracks.';
}
config = config || {};
var self = this;
// variables
var leftchannel = [];
var rightchannel = [];
var recording = false;
var recordingLength = 0;
var jsAudioNode;
var numberOfAudioChannels = 2;
/**
* Set sample rates such as 8K or 16K. Reference: http://stackoverflow.com/a/28977136/552182
* @property {number} desiredSampRate - Desired Bits per sample * 1000
* @memberof StereoAudioRecorder
* @instance
* @example
* var recorder = StereoAudioRecorder(mediaStream, {
* desiredSampRate: 16 * 1000 // bits-per-sample * 1000
* });
*/
var desiredSampRate = config.desiredSampRate;
// backward compatibility
if (config.leftChannel === true) {
numberOfAudioChannels = 1;
}
if (config.numberOfAudioChannels === 1) {
numberOfAudioChannels = 1;
}
if (!numberOfAudioChannels || numberOfAudioChannels < 1) {
numberOfAudioChannels = 2;
}
if (!config.disableLogs) {
console.log('StereoAudioRecorder is set to record number of channels: ', numberOfAudioChannels);
}
// if any Track within the MediaStream is muted or not enabled at any time,
// the browser will only record black frames
// or silence since that is the content produced by the Track
// so we need to stopRecording as soon as any single track ends.
if (typeof config.checkForInactiveTracks === 'undefined') {
config.checkForInactiveTracks = true;
}
function isMediaStreamActive() {
if (config.checkForInactiveTracks === false) {
// always return "true"
return true;
}
if ('active' in mediaStream) {
if (!mediaStream.active) {
return false;
}
} else if ('ended' in mediaStream) { // old hack
if (mediaStream.ended) {
return false;
}
}
return true;
}
/**
* This method records MediaStream.
* @method
* @memberof StereoAudioRecorder
* @example
* recorder.record();
*/
this.record = function() {
if (isMediaStreamActive() === false) {
throw 'Please make sure MediaStream is active.';
}
resetVariables();
isAudioProcessStarted = isPaused = false;
recording = true;
if (typeof config.timeSlice !== 'undefined') {
looper();
}
};
function mergeLeftRightBuffers(config, callback) {
function mergeAudioBuffers(config, cb) {
var numberOfAudioChannels = config.numberOfAudioChannels;
// todo: "slice(0)" --- is it causes loop? Should be removed?
var leftBuffers = config.leftBuffers.slice(0);
var rightBuffers = config.rightBuffers.slice(0);
var sampleRate = config.sampleRate;
var internalInterleavedLength = config.internalInterleavedLength;
var desiredSampRate = config.desiredSampRate;
if (numberOfAudioChannels === 2) {
leftBuffers = mergeBuffers(leftBuffers, internalInterleavedLength);
rightBuffers = mergeBuffers(rightBuffers, internalInterleavedLength);
if (desiredSampRate) {
leftBuffers = interpolateArray(leftBuffers, desiredSampRate, sampleRate);
rightBuffers = interpolateArray(rightBuffers, desiredSampRate, sampleRate);
}
}
if (numberOfAudioChannels === 1) {
leftBuffers = mergeBuffers(leftBuffers, internalInterleavedLength);
if (desiredSampRate) {
leftBuffers = interpolateArray(leftBuffers, desiredSampRate, sampleRate);
}
}
// set sample rate as desired sample rate
if (desiredSampRate) {
sampleRate = desiredSampRate;
}
// for changing the sampling rate, reference:
// http://stackoverflow.com/a/28977136/552182
function interpolateArray(data, newSampleRate, oldSampleRate) {
var fitCount = Math.round(data.length * (newSampleRate / oldSampleRate));
var newData = [];
var springFactor = Number((data.length - 1) / (fitCount - 1));
newData[0] = data[0];
for (var i = 1; i < fitCount - 1; i++) {
var tmp = i * springFactor;
var before = Number(Math.floor(tmp)).toFixed();
var after = Number(Math.ceil(tmp)).toFixed();
var atPoint = tmp - before;
newData[i] = linearInterpolate(data[before], data[after], atPoint);
}
newData[fitCount - 1] = data[data.length - 1];
return newData;
}
function linearInterpolate(before, after, atPoint) {
return before + (after - before) * atPoint;
}
function mergeBuffers(channelBuffer, rLength) {
var result = new Float64Array(rLength);
var offset = 0;
var lng = channelBuffer.length;
for (var i = 0; i < lng; i++) {
var buffer = channelBuffer[i];
result.set(buffer, offset);
offset += buffer.length;
}
return result;
}
function interleave(leftChannel, rightChannel) {
var length = leftChannel.length + rightChannel.length;
var result = new Float64Array(length);
var inputIndex = 0;
for (var index = 0; index < length;) {
result[index++] = leftChannel[inputIndex];
result[index++] = rightChannel[inputIndex];
inputIndex++;
}
return result;
}
function writeUTFBytes(view, offset, string) {
var lng = string.length;
for (var i = 0; i < lng; i++) {
view.setUint8(offset + i, string.charCodeAt(i));
}
}
// interleave both channels together
var interleaved;
if (numberOfAudioChannels === 2) {
interleaved = interleave(leftBuffers, rightBuffers);
}
if (numberOfAudioChannels === 1) {
interleaved = leftBuffers;
}
var interleavedLength = interleaved.length;
// create wav file
var resultingBufferLength = 44 + interleavedLength * 2;
var buffer = new ArrayBuffer(resultingBufferLength);
var view = new DataView(buffer);
// RIFF chunk descriptor/identifier
writeUTFBytes(view, 0, 'RIFF');
// RIFF chunk length
view.setUint32(4, 44 + interleavedLength * 2, true);
// RIFF type
writeUTFBytes(view, 8, 'WAVE');
// format chunk identifier
// FMT sub-chunk
writeUTFBytes(view, 12, 'fmt ');
// format chunk length
view.setUint32(16, 16, true);
// sample format (raw)
view.setUint16(20, 1, true);
// stereo (2 channels)
view.setUint16(22, numberOfAudioChannels, true);
// sample rate
view.setUint32(24, sampleRate, true);
// byte rate (sample rate * block align)
view.setUint32(28, sampleRate * 2, true);
// block align (channel count * bytes per sample)
view.setUint16(32, numberOfAudioChannels * 2, true);
// bits per sample
view.setUint16(34, 16, true);
// data sub-chunk
// data chunk identifier
writeUTFBytes(view, 36, 'data');
// data chunk length
view.setUint32(40, interleavedLength * 2, true);
// write the PCM samples
var lng = interleavedLength;
var index = 44;
var volume = 1;
for (var i = 0; i < lng; i++) {
view.setInt16(index, interleaved[i] * (0x7FFF * volume), true);
index += 2;
}
if (cb) {
return cb({
buffer: buffer,
view: view
});
}
postMessage({
buffer: buffer,
view: view
});
}
if (isEdge || isOpera || isSafari || config.noWorker) {
mergeAudioBuffers(config, function(data) {
callback(data.buffer, data.view);
});
return;
}
var webWorker = processInWebWorker(mergeAudioBuffers);
webWorker.onmessage = function(event) {
callback(event.data.buffer, event.data.view);
// release memory
URL.revokeObjectURL(webWorker.workerURL);
// kill webworker (or Chrome will kill your page after ~25 calls)
webWorker.terminate();
};
webWorker.postMessage(config);
}
function processInWebWorker(_function) {
var workerURL = URL.createObjectURL(new Blob([_function.toString(),
';this.onmessage = function (eee) {' + _function.name + '(eee.data);}'
], {
type: 'application/javascript'
}));
var worker = new Worker(workerURL);
worker.workerURL = workerURL;
return worker;
}
/**
* This method stops recording MediaStream.
* @param {function} callback - Callback function, that is used to pass recorded blob back to the callee.
* @method
* @memberof StereoAudioRecorder
* @example
* recorder.stop(function(blob) {
* video.src = URL.createObjectURL(blob);
* });
*/
this.stop = function(callback) {
callback = callback || function() {};
// stop recording
recording = false;
mergeLeftRightBuffers({
desiredSampRate: desiredSampRate,
sampleRate: sampleRate,
numberOfAudioChannels: numberOfAudioChannels,
internalInterleavedLength: recordingLength,
leftBuffers: leftchannel,
rightBuffers: numberOfAudioChannels === 1 ? [] : rightchannel
}, function(buffer, view) {
/**
* @property {Blob} blob - The recorded blob object.
* @memberof StereoAudioRecorder
* @example
* recorder.stop(function(){
* var blob = recorder.blob;
* });
*/
self.blob = new Blob([view], {
type: 'audio/wav'
});
/**
* @property {ArrayBuffer} buffer - The recorded buffer object.
* @memberof StereoAudioRecorder
* @example
* recorder.stop(function(){
* var buffer = recorder.buffer;
* });
*/
self.buffer = new ArrayBuffer(view.buffer.byteLength);
/**
* @property {DataView} view - The recorded data-view object.
* @memberof StereoAudioRecorder
* @example
* recorder.stop(function(){
* var view = recorder.view;
* });
*/
self.view = view;
self.sampleRate = desiredSampRate || sampleRate;
self.bufferSize = bufferSize;
// recorded audio length
self.length = recordingLength;
isAudioProcessStarted = false;
if (callback) {
callback(self.blob);
}
});
};
if (!Storage.AudioContextConstructor) {
Storage.AudioContextConstructor = new Storage.AudioContext();
}
var context = Storage.AudioContextConstructor;
// creates an audio node from the microphone incoming stream
var audioInput = context.createMediaStreamSource(mediaStream);
var legalBufferValues = [0, 256, 512, 1024, 2048, 4096, 8192, 16384];
/**
* From the spec: This value controls how frequently the audioprocess event is
* dispatched and how many sample-frames need to be processed each call.
* Lower values for buffer size will result in a lower (better) latency.
* Higher values will be necessary to avoid audio breakup and glitches
* The size of the buffer (in sample-frames) which needs to
* be processed each time onprocessaudio is called.
* Legal values are (256, 512, 1024, 2048, 4096, 8192, 16384).
* @property {number} bufferSize - Buffer-size for how frequently the audioprocess event is dispatched.
* @memberof StereoAudioRecorder
* @example
* recorder = new StereoAudioRecorder(mediaStream, {
* bufferSize: 4096
* });
*/
// "0" means, let chrome decide the most accurate buffer-size for current platform.
var bufferSize = typeof config.bufferSize === 'undefined' ? 4096 : config.bufferSize;
if (legalBufferValues.indexOf(bufferSize) === -1) {
if (!config.disableLogs) {
console.warn('Legal values for buffer-size are ' + JSON.stringify(legalBufferValues, null, '\t'));
}
}
if (context.createJavaScriptNode) {
jsAudioNode = context.createJavaScriptNode(bufferSize, numberOfAudioChannels, numberOfAudioChannels);
} else if (context.createScriptProcessor) {
jsAudioNode = context.createScriptProcessor(bufferSize, numberOfAudioChannels, numberOfAudioChannels);
} else {
throw 'WebAudio API has no support on this browser.';
}
// connect the stream to the script processor
audioInput.connect(jsAudioNode);
if (!config.bufferSize) {
bufferSize = jsAudioNode.bufferSize; // device buffer-size
}
/**
* The sample rate (in sample-frames per second) at which the
* AudioContext handles audio. It is assumed that all AudioNodes
* in the context run at this rate. In making this assumption,
* sample-rate converters or "varispeed" processors are not supported
* in real-time processing.
* The sampleRate parameter describes the sample-rate of the
* linear PCM audio data in the buffer in sample-frames per second.
* An implementation must support sample-rates in at least
* the range 22050 to 96000.
* @property {number} sampleRate - Buffer-size for how frequently the audioprocess event is dispatched.
* @memberof StereoAudioRecorder
* @example
* recorder = new StereoAudioRecorder(mediaStream, {
* sampleRate: 44100
* });
*/
var sampleRate = typeof config.sampleRate !== 'undefined' ? config.sampleRate : context.sampleRate || 44100;
if (sampleRate < 22050 || sampleRate > 96000) {
// Ref: http://stackoverflow.com/a/26303918/552182
if (!config.disableLogs) {
console.warn('sample-rate must be under range 22050 and 96000.');
}
}
if (!config.disableLogs) {
console.log('sample-rate', sampleRate);
console.log('buffer-size', bufferSize);
if (config.desiredSampRate) {
console.log('Desired sample-rate', config.desiredSampRate);
}
}
var isPaused = false;
/**
* This method pauses the recording process.
* @method
* @memberof StereoAudioRecorder
* @example
* recorder.pause();
*/
this.pause = function() {
isPaused = true;
};
/**
* This method resumes the recording process.
* @method
* @memberof StereoAudioRecorder
* @example
* recorder.resume();
*/
this.resume = function() {
if (isMediaStreamActive() === false) {
throw 'Please make sure MediaStream is active.';
}
if (!recording) {
if (!config.disableLogs) {
console.log('Seems recording has been restarted.');
}
this.record();
return;
}
isPaused = false;
};
/**
* This method resets currently recorded data.
* @method
* @memberof StereoAudioRecorder
* @example
* recorder.clearRecordedData();
*/
this.clearRecordedData = function() {
config.checkForInactiveTracks = false;
if (recording) {
this.stop(clearRecordedDataCB);
}
clearRecordedDataCB();
};
function resetVariables() {
leftchannel = [];
rightchannel = [];
recordingLength = 0;
isAudioProcessStarted = false;
recording = false;
isPaused = false;
context = null;
self.leftchannel = leftchannel;
self.rightchannel = rightchannel;
self.numberOfAudioChannels = numberOfAudioChannels;
self.desiredSampRate = desiredSampRate;
self.sampleRate = sampleRate;
self.recordingLength = recordingLength;
intervalsBasedBuffers = {
left: [],
right: [],
recordingLength: 0
};
}
function clearRecordedDataCB() {
if (jsAudioNode) {
jsAudioNode.onaudioprocess = null;
jsAudioNode.disconnect();
jsAudioNode = null;
}
if (audioInput) {
audioInput.disconnect();
audioInput = null;
}
resetVariables();
}
// for debugging
this.name = 'StereoAudioRecorder';
this.toString = function() {
return this.name;
};
var isAudioProcessStarted = false;
function onAudioProcessDataAvailable(e) {
if (isPaused) {
return;
}
if (isMediaStreamActive() === false) {
if (!config.disableLogs) {
console.log('MediaStream seems stopped.');
}
jsAudioNode.disconnect();
recording = false;
}
if (!recording) {
if (audioInput) {
audioInput.disconnect();
audioInput = null;
}
return;
}
/**
* This method is called on "onaudioprocess" event's first invocation.
* @method {function} onAudioProcessStarted
* @memberof StereoAudioRecorder
* @example
* recorder.onAudioProcessStarted: function() { };
*/
if (!isAudioProcessStarted) {
isAudioProcessStarted = true;
if (config.onAudioProcessStarted) {
config.onAudioProcessStarted();
}
if (config.initCallback) {
config.initCallback();
}
}
var left = e.inputBuffer.getChannelData(0);
// we clone the samples
var chLeft = new Float32Array(left);
leftchannel.push(chLeft);
if (numberOfAudioChannels === 2) {
var right = e.inputBuffer.getChannelData(1);
var chRight = new Float32Array(right);
rightchannel.push(chRight);
}
recordingLength += bufferSize;
// export raw PCM
self.recordingLength = recordingLength;
if (typeof config.timeSlice !== 'undefined') {
intervalsBasedBuffers.recordingLength += bufferSize;
intervalsBasedBuffers.left.push(chLeft);
if (numberOfAudioChannels === 2) {
intervalsBasedBuffers.right.push(chRight);
}
}
}
jsAudioNode.onaudioprocess = onAudioProcessDataAvailable;
// to prevent self audio to be connected with speakers
jsAudioNode.connect(context.destination);
// export raw PCM
this.leftchannel = leftchannel;
this.rightchannel = rightchannel;
this.numberOfAudioChannels = numberOfAudioChannels;
this.desiredSampRate = desiredSampRate;
this.sampleRate = sampleRate;
self.recordingLength = recordingLength;
// helper for intervals based blobs
var intervalsBasedBuffers = {
left: [],
right: [],
recordingLength: 0
};
// this looper is used to support intervals based blobs (via timeSlice+ondataavailable)
function looper() {
if (!recording || typeof config.ondataavailable !== 'function' || typeof config.timeSlice === 'undefined') {
return;
}
if (intervalsBasedBuffers.left.length) {
mergeLeftRightBuffers({
desiredSampRate: desiredSampRate,
sampleRate: sampleRate,
numberOfAudioChannels: numberOfAudioChannels,
internalInterleavedLength: intervalsBasedBuffers.recordingLength,
leftBuffers: intervalsBasedBuffers.left,
rightBuffers: numberOfAudioChannels === 1 ? [] : intervalsBasedBuffers.right
}, function(buffer, view) {
var blob = new Blob([view], {
type: 'audio/wav'
});
config.ondataavailable(blob);
setTimeout(looper, config.timeSlice);
});
intervalsBasedBuffers = {
left: [],
right: [],
recordingLength: 0
};
} else {
setTimeout(looper, config.timeSlice);
}
}
}
if (typeof RecordRTC !== 'undefined') {
RecordRTC.StereoAudioRecorder = StereoAudioRecorder;
}
// _________________
// CanvasRecorder.js
/**
* CanvasRecorder is a standalone class used by {@link RecordRTC} to bring HTML5-Canvas recording into video WebM. It uses HTML2Canvas library and runs top over {@link Whammy}.
* @summary HTML2Canvas recording into video WebM.
* @license {@link https://github.com/muaz-khan/RecordRTC#license|MIT}
* @author {@link http://www.MuazKhan.com|Muaz Khan}
* @typedef CanvasRecorder
* @class
* @example
* var recorder = new CanvasRecorder(htmlElement, { disableLogs: true, useWhammyRecorder: true });
* recorder.record();
* recorder.stop(function(blob) {
* video.src = URL.createObjectURL(blob);
* });
* @see {@link https://github.com/muaz-khan/RecordRTC|RecordRTC Source Code}
* @param {HTMLElement} htmlElement - querySelector/getElementById/getElementsByTagName[0]/etc.
* @param {object} config - {disableLogs:true, initCallback: function}
*/
function CanvasRecorder(htmlElement, config) {
if (typeof html2canvas === 'undefined') {
throw 'Please link: https://cdn.webrtc-experiment.com/screenshot.js';
}
config = config || {};
if (!config.frameInterval) {
config.frameInterval = 10;
}
// via DetectRTC.js
var isCanvasSupportsStreamCapturing = false;
['captureStream', 'mozCaptureStream', 'webkitCaptureStream'].forEach(function(item) {
if (item in document.createElement('canvas')) {
isCanvasSupportsStreamCapturing = true;
}
});
var _isChrome = (!!window.webkitRTCPeerConnection || !!window.webkitGetUserMedia) && !!window.chrome;
var chromeVersion = 50;
var matchArray = navigator.userAgent.match(/Chrom(e|ium)\/([0-9]+)\./);
if (_isChrome && matchArray && matchArray[2]) {
chromeVersion = parseInt(matchArray[2], 10);
}
if (_isChrome && chromeVersion < 52) {
isCanvasSupportsStreamCapturing = false;
}
if (config.useWhammyRecorder) {
isCanvasSupportsStreamCapturing = false;
}
var globalCanvas, mediaStreamRecorder;
if (isCanvasSupportsStreamCapturing) {
if (!config.disableLogs) {
console.log('Your browser supports both MediRecorder API and canvas.captureStream!');
}
if (htmlElement instanceof HTMLCanvasElement) {
globalCanvas = htmlElement;
} else if (htmlElement instanceof CanvasRenderingContext2D) {
globalCanvas = htmlElement.canvas;
} else {
throw 'Please pass either HTMLCanvasElement or CanvasRenderingContext2D.';
}
} else if (!!navigator.mozGetUserMedia) {
if (!config.disableLogs) {
console.error('Canvas recording is NOT supported in Firefox.');
}
}
var isRecording;
/**
* This method records Canvas.
* @method
* @memberof CanvasRecorder
* @example
* recorder.record();
*/
this.record = function() {
isRecording = true;
if (isCanvasSupportsStreamCapturing && !config.useWhammyRecorder) {
// CanvasCaptureMediaStream
var canvasMediaStream;
if ('captureStream' in globalCanvas) {
canvasMediaStream = globalCanvas.captureStream(25); // 25 FPS
} else if ('mozCaptureStream' in globalCanvas) {
canvasMediaStream = globalCanvas.mozCaptureStream(25);
} else if ('webkitCaptureStream' in globalCanvas) {
canvasMediaStream = globalCanvas.webkitCaptureStream(25);
}
try {
var mdStream = new MediaStream();
mdStream.addTrack(canvasMediaStream.getVideoTracks()[0]);
canvasMediaStream = mdStream;
} catch (e) {}
if (!canvasMediaStream) {
throw 'captureStream API are NOT available.';
}
// Note: Jan 18, 2016 status is that,
// Firefox MediaRecorder API can't record CanvasCaptureMediaStream object.
mediaStreamRecorder = new MediaStreamRecorder(canvasMediaStream, {
mimeType: 'video/webm'
});
mediaStreamRecorder.record();
} else {
whammy.frames = [];
lastTime = new Date().getTime();
drawCanvasFrame();
}
if (config.initCallback) {
config.initCallback();
}
};
this.getWebPImages = function(callback) {
if (htmlElement.nodeName.toLowerCase() !== 'canvas') {
callback();
return;
}
var framesLength = whammy.frames.length;
whammy.frames.forEach(function(frame, idx) {
var framesRemaining = framesLength - idx;
if (!config.disableLogs) {
console.log(framesRemaining + '/' + framesLength + ' frames remaining');
}
if (config.onEncodingCallback) {
config.onEncodingCallback(framesRemaining, framesLength);
}
var webp = frame.image.toDataURL('image/webp', 1);
whammy.frames[idx].image = webp;
});
if (!config.disableLogs) {
console.log('Generating WebM');
}
callback();
};
/**
* This method stops recording Canvas.
* @param {function} callback - Callback function, that is used to pass recorded blob back to the callee.
* @method
* @memberof CanvasRecorder
* @example
* recorder.stop(function(blob) {
* video.src = URL.createObjectURL(blob);
* });
*/
this.stop = function(callback) {
isRecording = false;
var that = this;
if (isCanvasSupportsStreamCapturing && mediaStreamRecorder) {
mediaStreamRecorder.stop(callback);
return;
}
this.getWebPImages(function() {
/**
* @property {Blob} blob - Recorded frames in video/webm blob.
* @memberof CanvasRecorder
* @example
* recorder.stop(function() {
* var blob = recorder.blob;
* });
*/
whammy.compile(function(blob) {
if (!config.disableLogs) {
console.log('Recording finished!');
}
that.blob = blob;
if (that.blob.forEach) {
that.blob = new Blob([], {
type: 'video/webm'
});
}
if (callback) {
callback(that.blob);
}
whammy.frames = [];
});
});
};
var isPausedRecording = false;
/**
* This method pauses the recording process.
* @method
* @memberof CanvasRecorder
* @example
* recorder.pause();
*/
this.pause = function() {
isPausedRecording = true;
if (mediaStreamRecorder instanceof MediaStreamRecorder) {
mediaStreamRecorder.pause();
return;
}
};
/**
* This method resumes the recording process.
* @method
* @memberof CanvasRecorder
* @example
* recorder.resume();
*/
this.resume = function() {
isPausedRecording = false;
if (mediaStreamRecorder instanceof MediaStreamRecorder) {
mediaStreamRecorder.resume();
return;
}
if (!isRecording) {
this.record();
}
};
/**
* This method resets currently recorded data.
* @method
* @memberof CanvasRecorder
* @example
* recorder.clearRecordedData();
*/
this.clearRecordedData = function() {
if (isRecording) {
this.stop(clearRecordedDataCB);
}
clearRecordedDataCB();
};
function clearRecordedDataCB() {
whammy.frames = [];
isRecording = false;
isPausedRecording = false;
}
// for debugging
this.name = 'CanvasRecorder';
this.toString = function() {
return this.name;
};
function cloneCanvas() {
//create a new canvas
var newCanvas = document.createElement('canvas');
var context = newCanvas.getContext('2d');
//set dimensions
newCanvas.width = htmlElement.width;
newCanvas.height = htmlElement.height;
//apply the old canvas to the new one
context.drawImage(htmlElement, 0, 0);
//return the new canvas
return newCanvas;
}
function drawCanvasFrame() {
if (isPausedRecording) {
lastTime = new Date().getTime();
return setTimeout(drawCanvasFrame, 500);
}
if (htmlElement.nodeName.toLowerCase() === 'canvas') {
var duration = new Date().getTime() - lastTime;
// via #206, by Jack i.e. @Seymourr
lastTime = new Date().getTime();
whammy.frames.push({
image: cloneCanvas(),
duration: duration
});
if (isRecording) {
setTimeout(drawCanvasFrame, config.frameInterval);
}
return;
}
html2canvas(htmlElement, {
grabMouse: typeof config.showMousePointer === 'undefined' || config.showMousePointer,
onrendered: function(canvas) {
var duration = new Date().getTime() - lastTime;
if (!duration) {
return setTimeout(drawCanvasFrame, config.frameInterval);
}
// via #206, by Jack i.e. @Seymourr
lastTime = new Date().getTime();
whammy.frames.push({
image: canvas.toDataURL('image/webp', 1),
duration: duration
});
if (isRecording) {
setTimeout(drawCanvasFrame, config.frameInterval);
}
}
});
}
var lastTime = new Date().getTime();
var whammy = new Whammy.Video(100);
}
if (typeof RecordRTC !== 'undefined') {
RecordRTC.CanvasRecorder = CanvasRecorder;
}
// _________________
// WhammyRecorder.js
/**
* WhammyRecorder is a standalone class used by {@link RecordRTC} to bring video recording in Chrome. It runs top over {@link Whammy}.
* @summary Video recording feature in Chrome.
* @license {@link https://github.com/muaz-khan/RecordRTC#license|MIT}
* @author {@link http://www.MuazKhan.com|Muaz Khan}
* @typedef WhammyRecorder
* @class
* @example
* var recorder = new WhammyRecorder(mediaStream);
* recorder.record();
* recorder.stop(function(blob) {
* video.src = URL.createObjectURL(blob);
* });
* @see {@link https://github.com/muaz-khan/RecordRTC|RecordRTC Source Code}
* @param {MediaStream} mediaStream - MediaStream object fetched using getUserMedia API or generated using captureStreamUntilEnded or WebAudio API.
* @param {object} config - {disableLogs: true, initCallback: function, video: HTMLVideoElement, etc.}
*/
function WhammyRecorder(mediaStream, config) {
config = config || {};
if (!config.frameInterval) {
config.frameInterval = 10;
}
if (!config.disableLogs) {
console.log('Using frames-interval:', config.frameInterval);
}
/**
* This method records video.
* @method
* @memberof WhammyRecorder
* @example
* recorder.record();
*/
this.record = function() {
if (!config.width) {
config.width = 320;
}
if (!config.height) {
config.height = 240;
}
if (!config.video) {
config.video = {
width: config.width,
height: config.height
};
}
if (!config.canvas) {
config.canvas = {
width: config.width,
height: config.height
};
}
canvas.width = config.canvas.width || 320;
canvas.height = config.canvas.height || 240;
context = canvas.getContext('2d');
// setting defaults
if (config.video && config.video instanceof HTMLVideoElement) {
video = config.video.cloneNode();
if (config.initCallback) {
config.initCallback();
}
} else {
video = document.createElement('video');
setSrcObject(mediaStream, video);
video.onloadedmetadata = function() { // "onloadedmetadata" may NOT work in FF?
if (config.initCallback) {
config.initCallback();
}
};
video.width = config.video.width;
video.height = config.video.height;
}
video.muted = true;
video.play();
lastTime = new Date().getTime();
whammy = new Whammy.Video();
if (!config.disableLogs) {
console.log('canvas resolutions', canvas.width, '*', canvas.height);
console.log('video width/height', video.width || canvas.width, '*', video.height || canvas.height);
}
drawFrames(config.frameInterval);
};
/**
* Draw and push frames to Whammy
* @param {integer} frameInterval - set minimum interval (in milliseconds) between each time we push a frame to Whammy
*/
function drawFrames(frameInterval) {
frameInterval = typeof frameInterval !== 'undefined' ? frameInterval : 10;
var duration = new Date().getTime() - lastTime;
if (!duration) {
return setTimeout(drawFrames, frameInterval, frameInterval);
}
if (isPausedRecording) {
lastTime = new Date().getTime();
return setTimeout(drawFrames, 100);
}
// via #206, by Jack i.e. @Seymourr
lastTime = new Date().getTime();
if (video.paused) {
// via: https://github.com/muaz-khan/WebRTC-Experiment/pull/316
// Tweak for Android Chrome
video.play();
}
context.drawImage(video, 0, 0, canvas.width, canvas.height);
whammy.frames.push({
duration: duration,
image: canvas.toDataURL('image/webp')
});
if (!isStopDrawing) {
setTimeout(drawFrames, frameInterval, frameInterval);
}
}
function asyncLoop(o) {
var i = -1,
length = o.length;
(function loop() {
i++;
if (i === length) {
o.callback();
return;
}
// "setTimeout" added by Jim McLeod
setTimeout(function() {
o.functionToLoop(loop, i);
}, 1);
})();
}
/**
* remove black frames from the beginning to the specified frame
* @param {Array} _frames - array of frames to be checked
* @param {number} _framesToCheck - number of frame until check will be executed (-1 - will drop all frames until frame not matched will be found)
* @param {number} _pixTolerance - 0 - very strict (only black pixel color) ; 1 - all
* @param {number} _frameTolerance - 0 - very strict (only black frame color) ; 1 - all
* @returns {Array} - array of frames
*/
// pull#293 by @volodalexey
function dropBlackFrames(_frames, _framesToCheck, _pixTolerance, _frameTolerance, callback) {
var localCanvas = document.createElement('canvas');
localCanvas.width = canvas.width;
localCanvas.height = canvas.height;
var context2d = localCanvas.getContext('2d');
var resultFrames = [];
var checkUntilNotBlack = _framesToCheck === -1;
var endCheckFrame = (_framesToCheck && _framesToCheck > 0 && _framesToCheck <= _frames.length) ?
_framesToCheck : _frames.length;
var sampleColor = {
r: 0,
g: 0,
b: 0
};
var maxColorDifference = Math.sqrt(
Math.pow(255, 2) +
Math.pow(255, 2) +
Math.pow(255, 2)
);
var pixTolerance = _pixTolerance && _pixTolerance >= 0 && _pixTolerance <= 1 ? _pixTolerance : 0;
var frameTolerance = _frameTolerance && _frameTolerance >= 0 && _frameTolerance <= 1 ? _frameTolerance : 0;
var doNotCheckNext = false;
asyncLoop({
length: endCheckFrame,
functionToLoop: function(loop, f) {
var matchPixCount, endPixCheck, maxPixCount;
var finishImage = function() {
if (!doNotCheckNext && maxPixCount - matchPixCount <= maxPixCount * frameTolerance) {
// console.log('removed black frame : ' + f + ' ; frame duration ' + _frames[f].duration);
} else {
// console.log('frame is passed : ' + f);
if (checkUntilNotBlack) {
doNotCheckNext = true;
}
resultFrames.push(_frames[f]);
}
loop();
};
if (!doNotCheckNext) {
var image = new Image();
image.onload = function() {
context2d.drawImage(image, 0, 0, canvas.width, canvas.height);
var imageData = context2d.getImageData(0, 0, canvas.width, canvas.height);
matchPixCount = 0;
endPixCheck = imageData.data.length;
maxPixCount = imageData.data.length / 4;
for (var pix = 0; pix < endPixCheck; pix += 4) {
var currentColor = {
r: imageData.data[pix],
g: imageData.data[pix + 1],
b: imageData.data[pix + 2]
};
var colorDifference = Math.sqrt(
Math.pow(currentColor.r - sampleColor.r, 2) +
Math.pow(currentColor.g - sampleColor.g, 2) +
Math.pow(currentColor.b - sampleColor.b, 2)
);
// difference in color it is difference in color vectors (r1,g1,b1) <=> (r2,g2,b2)
if (colorDifference <= maxColorDifference * pixTolerance) {
matchPixCount++;
}
}
finishImage();
};
image.src = _frames[f].image;
} else {
finishImage();
}
},
callback: function() {
resultFrames = resultFrames.concat(_frames.slice(endCheckFrame));
if (resultFrames.length <= 0) {
// at least one last frame should be available for next manipulation
// if total duration of all frames will be < 1000 than ffmpeg doesn't work well...
resultFrames.push(_frames[_frames.length - 1]);
}
callback(resultFrames);
}
});
}
var isStopDrawing = false;
/**
* This method stops recording video.
* @param {function} callback - Callback function, that is used to pass recorded blob back to the callee.
* @method
* @memberof WhammyRecorder
* @example
* recorder.stop(function(blob) {
* video.src = URL.createObjectURL(blob);
* });
*/
this.stop = function(callback) {
callback = callback || function() {};
isStopDrawing = true;
var _this = this;
// analyse of all frames takes some time!
setTimeout(function() {
// e.g. dropBlackFrames(frames, 10, 1, 1) - will cut all 10 frames
// e.g. dropBlackFrames(frames, 10, 0.5, 0.5) - will analyse 10 frames
// e.g. dropBlackFrames(frames, 10) === dropBlackFrames(frames, 10, 0, 0) - will analyse 10 frames with strict black color
dropBlackFrames(whammy.frames, -1, null, null, function(frames) {
whammy.frames = frames;
// to display advertisement images!
if (config.advertisement && config.advertisement.length) {
whammy.frames = config.advertisement.concat(whammy.frames);
}
/**
* @property {Blob} blob - Recorded frames in video/webm blob.
* @memberof WhammyRecorder
* @example
* recorder.stop(function() {
* var blob = recorder.blob;
* });
*/
whammy.compile(function(blob) {
_this.blob = blob;
if (_this.blob.forEach) {
_this.blob = new Blob([], {
type: 'video/webm'
});
}
if (callback) {
callback(_this.blob);
}
});
});
}, 10);
};
var isPausedRecording = false;
/**
* This method pauses the recording process.
* @method
* @memberof WhammyRecorder
* @example
* recorder.pause();
*/
this.pause = function() {
isPausedRecording = true;
};
/**
* This method resumes the recording process.
* @method
* @memberof WhammyRecorder
* @example
* recorder.resume();
*/
this.resume = function() {
isPausedRecording = false;
if (isStopDrawing) {
this.record();
}
};
/**
* This method resets currently recorded data.
* @method
* @memberof WhammyRecorder
* @example
* recorder.clearRecordedData();
*/
this.clearRecordedData = function() {
if (!isStopDrawing) {
this.stop(clearRecordedDataCB);
}
clearRecordedDataCB();
};
function clearRecordedDataCB() {
whammy.frames = [];
isStopDrawing = true;
isPausedRecording = false;
}
// for debugging
this.name = 'WhammyRecorder';
this.toString = function() {
return this.name;
};
var canvas = document.createElement('canvas');
var context = canvas.getContext('2d');
var video;
var lastTime;
var whammy;
}
if (typeof RecordRTC !== 'undefined') {
RecordRTC.WhammyRecorder = WhammyRecorder;
}
// https://github.com/antimatter15/whammy/blob/master/LICENSE
// _________
// Whammy.js
// todo: Firefox now supports webp for webm containers!
// their MediaRecorder implementation works well!
// should we provide an option to record via Whammy.js or MediaRecorder API is a better solution?
/**
* Whammy is a standalone class used by {@link RecordRTC} to bring video recording in Chrome. It is written by {@link https://github.com/antimatter15|antimatter15}
* @summary A real time javascript webm encoder based on a canvas hack.
* @license {@link https://github.com/muaz-khan/RecordRTC#license|MIT}
* @author {@link http://www.MuazKhan.com|Muaz Khan}
* @typedef Whammy
* @class
* @example
* var recorder = new Whammy().Video(15);
* recorder.add(context || canvas || dataURL);
* var output = recorder.compile();
* @see {@link https://github.com/muaz-khan/RecordRTC|RecordRTC Source Code}
*/
var Whammy = (function() {
// a more abstract-ish API
function WhammyVideo(duration) {
this.frames = [];
this.duration = duration || 1;
this.quality = 0.8;
}
/**
* Pass Canvas or Context or image/webp(string) to {@link Whammy} encoder.
* @method
* @memberof Whammy
* @example
* recorder = new Whammy().Video(0.8, 100);
* recorder.add(canvas || context || 'image/webp');
* @param {string} frame - Canvas || Context || image/webp
* @param {number} duration - Stick a duration (in milliseconds)
*/
WhammyVideo.prototype.add = function(frame, duration) {
if ('canvas' in frame) { //CanvasRenderingContext2D
frame = frame.canvas;
}
if ('toDataURL' in frame) {
frame = frame.toDataURL('image/webp', this.quality);
}
if (!(/^data:image\/webp;base64,/ig).test(frame)) {
throw 'Input must be formatted properly as a base64 encoded DataURI of type image/webp';
}
this.frames.push({
image: frame,
duration: duration || this.duration
});
};
function processInWebWorker(_function) {
var blob = URL.createObjectURL(new Blob([_function.toString(),
'this.onmessage = function (eee) {' + _function.name + '(eee.data);}'
], {
type: 'application/javascript'
}));
var worker = new Worker(blob);
URL.revokeObjectURL(blob);
return worker;
}
function whammyInWebWorker(frames) {
function ArrayToWebM(frames) {
var info = checkFrames(frames);
if (!info) {
return [];
}
var clusterMaxDuration = 30000;
var EBML = [{
'id': 0x1a45dfa3, // EBML
'data': [{
'data': 1,
'id': 0x4286 // EBMLVersion
}, {
'data': 1,
'id': 0x42f7 // EBMLReadVersion
}, {
'data': 4,
'id': 0x42f2 // EBMLMaxIDLength
}, {
'data': 8,
'id': 0x42f3 // EBMLMaxSizeLength
}, {
'data': 'webm',
'id': 0x4282 // DocType
}, {
'data': 2,
'id': 0x4287 // DocTypeVersion
}, {
'data': 2,
'id': 0x4285 // DocTypeReadVersion
}]
}, {
'id': 0x18538067, // Segment
'data': [{
'id': 0x1549a966, // Info
'data': [{
'data': 1e6, //do things in millisecs (num of nanosecs for duration scale)
'id': 0x2ad7b1 // TimecodeScale
}, {
'data': 'whammy',
'id': 0x4d80 // MuxingApp
}, {
'data': 'whammy',
'id': 0x5741 // WritingApp
}, {
'data': doubleToString(info.duration),
'id': 0x4489 // Duration
}]
}, {
'id': 0x1654ae6b, // Tracks
'data': [{
'id': 0xae, // TrackEntry
'data': [{
'data': 1,
'id': 0xd7 // TrackNumber
}, {
'data': 1,
'id': 0x73c5 // TrackUID
}, {
'data': 0,
'id': 0x9c // FlagLacing
}, {
'data': 'und',
'id': 0x22b59c // Language
}, {
'data': 'V_VP8',
'id': 0x86 // CodecID
}, {
'data': 'VP8',
'id': 0x258688 // CodecName
}, {
'data': 1,
'id': 0x83 // TrackType
}, {
'id': 0xe0, // Video
'data': [{
'data': info.width,
'id': 0xb0 // PixelWidth
}, {
'data': info.height,
'id': 0xba // PixelHeight
}]
}]
}]
}]
}];
//Generate clusters (max duration)
var frameNumber = 0;
var clusterTimecode = 0;
while (frameNumber < frames.length) {
var clusterFrames = [];
var clusterDuration = 0;
do {
clusterFrames.push(frames[frameNumber]);
clusterDuration += frames[frameNumber].duration;
frameNumber++;
} while (frameNumber < frames.length && clusterDuration < clusterMaxDuration);
var clusterCounter = 0;
var cluster = {
'id': 0x1f43b675, // Cluster
'data': getClusterData(clusterTimecode, clusterCounter, clusterFrames)
}; //Add cluster to segment
EBML[1].data.push(cluster);
clusterTimecode += clusterDuration;
}
return generateEBML(EBML);
}
function getClusterData(clusterTimecode, clusterCounter, clusterFrames) {
return [{
'data': clusterTimecode,
'id': 0xe7 // Timecode
}].concat(clusterFrames.map(function(webp) {
var block = makeSimpleBlock({
discardable: 0,
frame: webp.data.slice(4),
invisible: 0,
keyframe: 1,
lacing: 0,
trackNum: 1,
timecode: Math.round(clusterCounter)
});
clusterCounter += webp.duration;
return {
data: block,
id: 0xa3
};
}));
}
// sums the lengths of all the frames and gets the duration
function checkFrames(frames) {
if (!frames[0]) {
postMessage({
error: 'Something went wrong. Maybe WebP format is not supported in the current browser.'
});
return;
}
var width = frames[0].width,
height = frames[0].height,
duration = frames[0].duration;
for (var i = 1; i < frames.length; i++) {
duration += frames[i].duration;
}
return {
duration: duration,
width: width,
height: height
};
}
function numToBuffer(num) {
var parts = [];
while (num > 0) {
parts.push(num & 0xff);
num = num >> 8;
}
return new Uint8Array(parts.reverse());
}
function strToBuffer(str) {
return new Uint8Array(str.split('').map(function(e) {
return e.charCodeAt(0);
}));
}
function bitsToBuffer(bits) {
var data = [];
var pad = (bits.length % 8) ? (new Array(1 + 8 - (bits.length % 8))).join('0') : '';
bits = pad + bits;
for (var i = 0; i < bits.length; i += 8) {
data.push(parseInt(bits.substr(i, 8), 2));
}
return new Uint8Array(data);
}
function generateEBML(json) {
var ebml = [];
for (var i = 0; i < json.length; i++) {
var data = json[i].data;
if (typeof data === 'object') {
data = generateEBML(data);
}
if (typeof data === 'number') {
data = bitsToBuffer(data.toString(2));
}
if (typeof data === 'string') {
data = strToBuffer(data);
}
var len = data.size || data.byteLength || data.length;
var zeroes = Math.ceil(Math.ceil(Math.log(len) / Math.log(2)) / 8);
var sizeToString = len.toString(2);
var padded = (new Array((zeroes * 7 + 7 + 1) - sizeToString.length)).join('0') + sizeToString;
var size = (new Array(zeroes)).join('0') + '1' + padded;
ebml.push(numToBuffer(json[i].id));
ebml.push(bitsToBuffer(size));
ebml.push(data);
}
return new Blob(ebml, {
type: 'video/webm'
});
}
function toBinStrOld(bits) {
var data = '';
var pad = (bits.length % 8) ? (new Array(1 + 8 - (bits.length % 8))).join('0') : '';
bits = pad + bits;
for (var i = 0; i < bits.length; i += 8) {
data += String.fromCharCode(parseInt(bits.substr(i, 8), 2));
}
return data;
}
function makeSimpleBlock(data) {
var flags = 0;
if (data.keyframe) {
flags |= 128;
}
if (data.invisible) {
flags |= 8;
}
if (data.lacing) {
flags |= (data.lacing << 1);
}
if (data.discardable) {
flags |= 1;
}
if (data.trackNum > 127) {
throw 'TrackNumber > 127 not supported';
}
var out = [data.trackNum | 0x80, data.timecode >> 8, data.timecode & 0xff, flags].map(function(e) {
return String.fromCharCode(e);
}).join('') + data.frame;
return out;
}
function parseWebP(riff) {
var VP8 = riff.RIFF[0].WEBP[0];
var frameStart = VP8.indexOf('\x9d\x01\x2a'); // A VP8 keyframe starts with the 0x9d012a header
for (var i = 0, c = []; i < 4; i++) {
c[i] = VP8.charCodeAt(frameStart + 3 + i);
}
var width, height, tmp;
//the code below is literally copied verbatim from the bitstream spec
tmp = (c[1] << 8) | c[0];
width = tmp & 0x3FFF;
tmp = (c[3] << 8) | c[2];
height = tmp & 0x3FFF;
return {
width: width,
height: height,
data: VP8,
riff: riff
};
}
function getStrLength(string, offset) {
return parseInt(string.substr(offset + 4, 4).split('').map(function(i) {
var unpadded = i.charCodeAt(0).toString(2);
return (new Array(8 - unpadded.length + 1)).join('0') + unpadded;
}).join(''), 2);
}
function parseRIFF(string) {
var offset = 0;
var chunks = {};
while (offset < string.length) {
var id = string.substr(offset, 4);
var len = getStrLength(string, offset);
var data = string.substr(offset + 4 + 4, len);
offset += 4 + 4 + len;
chunks[id] = chunks[id] || [];
if (id === 'RIFF' || id === 'LIST') {
chunks[id].push(parseRIFF(data));
} else {
chunks[id].push(data);
}
}
return chunks;
}
function doubleToString(num) {
return [].slice.call(
new Uint8Array((new Float64Array([num])).buffer), 0).map(function(e) {
return String.fromCharCode(e);
}).reverse().join('');
}
var webm = new ArrayToWebM(frames.map(function(frame) {
var webp = parseWebP(parseRIFF(atob(frame.image.slice(23))));
webp.duration = frame.duration;
return webp;
}));
postMessage(webm);
}
/**
* Encodes frames in WebM container. It uses WebWorkinvoke to invoke 'ArrayToWebM' method.
* @param {function} callback - Callback function, that is used to pass recorded blob back to the callee.
* @method
* @memberof Whammy
* @example
* recorder = new Whammy().Video(0.8, 100);
* recorder.compile(function(blob) {
* // blob.size - blob.type
* });
*/
WhammyVideo.prototype.compile = function(callback) {
var webWorker = processInWebWorker(whammyInWebWorker);
webWorker.onmessage = function(event) {
if (event.data.error) {
console.error(event.data.error);
return;
}
callback(event.data);
};
webWorker.postMessage(this.frames);
};
return {
/**
* A more abstract-ish API.
* @method
* @memberof Whammy
* @example
* recorder = new Whammy().Video(0.8, 100);
* @param {?number} speed - 0.8
* @param {?number} quality - 100
*/
Video: WhammyVideo
};
})();
if (typeof RecordRTC !== 'undefined') {
RecordRTC.Whammy = Whammy;
}
// ______________ (indexed-db)
// DiskStorage.js
/**
* DiskStorage is a standalone object used by {@link RecordRTC} to store recorded blobs in IndexedDB storage.
* @summary Writing blobs into IndexedDB.
* @license {@link https://github.com/muaz-khan/RecordRTC#license|MIT}
* @author {@link http://www.MuazKhan.com|Muaz Khan}
* @example
* DiskStorage.Store({
* audioBlob: yourAudioBlob,
* videoBlob: yourVideoBlob,
* gifBlob : yourGifBlob
* });
* DiskStorage.Fetch(function(dataURL, type) {
* if(type === 'audioBlob') { }
* if(type === 'videoBlob') { }
* if(type === 'gifBlob') { }
* });
* // DiskStorage.dataStoreName = 'recordRTC';
* // DiskStorage.onError = function(error) { };
* @property {function} init - This method must be called once to initialize IndexedDB ObjectStore. Though, it is auto-used internally.
* @property {function} Fetch - This method fetches stored blobs from IndexedDB.
* @property {function} Store - This method stores blobs in IndexedDB.
* @property {function} onError - This function is invoked for any known/unknown error.
* @property {string} dataStoreName - Name of the ObjectStore created in IndexedDB storage.
* @see {@link https://github.com/muaz-khan/RecordRTC|RecordRTC Source Code}
*/
var DiskStorage = {
/**
* This method must be called once to initialize IndexedDB ObjectStore. Though, it is auto-used internally.
* @method
* @memberof DiskStorage
* @internal
* @example
* DiskStorage.init();
*/
init: function() {
var self = this;
if (typeof indexedDB === 'undefined' || typeof indexedDB.open === 'undefined') {
console.error('IndexedDB API are not available in this browser.');
return;
}
var dbVersion = 1;
var dbName = this.dbName || location.href.replace(/\/|:|#|%|\.|\[|\]/g, ''),
db;
var request = indexedDB.open(dbName, dbVersion);
function createObjectStore(dataBase) {
dataBase.createObjectStore(self.dataStoreName);
}
function putInDB() {
var transaction = db.transaction([self.dataStoreName], 'readwrite');
if (self.videoBlob) {
transaction.objectStore(self.dataStoreName).put(self.videoBlob, 'videoBlob');
}
if (self.gifBlob) {
transaction.objectStore(self.dataStoreName).put(self.gifBlob, 'gifBlob');
}
if (self.audioBlob) {
transaction.objectStore(self.dataStoreName).put(self.audioBlob, 'audioBlob');
}
function getFromStore(portionName) {
transaction.objectStore(self.dataStoreName).get(portionName).onsuccess = function(event) {
if (self.callback) {
self.callback(event.target.result, portionName);
}
};
}
getFromStore('audioBlob');
getFromStore('videoBlob');
getFromStore('gifBlob');
}
request.onerror = self.onError;
request.onsuccess = function() {
db = request.result;
db.onerror = self.onError;
if (db.setVersion) {
if (db.version !== dbVersion) {
var setVersion = db.setVersion(dbVersion);
setVersion.onsuccess = function() {
createObjectStore(db);
putInDB();
};
} else {
putInDB();
}
} else {
putInDB();
}
};
request.onupgradeneeded = function(event) {
createObjectStore(event.target.result);
};
},
/**
* This method fetches stored blobs from IndexedDB.
* @method
* @memberof DiskStorage
* @internal
* @example
* DiskStorage.Fetch(function(dataURL, type) {
* if(type === 'audioBlob') { }
* if(type === 'videoBlob') { }
* if(type === 'gifBlob') { }
* });
*/
Fetch: function(callback) {
this.callback = callback;
this.init();
return this;
},
/**
* This method stores blobs in IndexedDB.
* @method
* @memberof DiskStorage
* @internal
* @example
* DiskStorage.Store({
* audioBlob: yourAudioBlob,
* videoBlob: yourVideoBlob,
* gifBlob : yourGifBlob
* });
*/
Store: function(config) {
this.audioBlob = config.audioBlob;
this.videoBlob = config.videoBlob;
this.gifBlob = config.gifBlob;
this.init();
return this;
},
/**
* This function is invoked for any known/unknown error.
* @method
* @memberof DiskStorage
* @internal
* @example
* DiskStorage.onError = function(error){
* alerot( JSON.stringify(error) );
* };
*/
onError: function(error) {
console.error(JSON.stringify(error, null, '\t'));
},
/**
* @property {string} dataStoreName - Name of the ObjectStore created in IndexedDB storage.
* @memberof DiskStorage
* @internal
* @example
* DiskStorage.dataStoreName = 'recordRTC';
*/
dataStoreName: 'recordRTC',
dbName: null
};
if (typeof RecordRTC !== 'undefined') {
RecordRTC.DiskStorage = DiskStorage;
}
// ______________
// GifRecorder.js
/**
* GifRecorder is standalone calss used by {@link RecordRTC} to record video or canvas into animated gif.
* @license {@link https://github.com/muaz-khan/RecordRTC#license|MIT}
* @author {@link http://www.MuazKhan.com|Muaz Khan}
* @typedef GifRecorder
* @class
* @example
* var recorder = new GifRecorder(mediaStream || canvas || context, { onGifPreview: function, onGifRecordingStarted: function, width: 1280, height: 720, frameRate: 200, quality: 10 });
* recorder.record();
* recorder.stop(function(blob) {
* img.src = URL.createObjectURL(blob);
* });
* @see {@link https://github.com/muaz-khan/RecordRTC|RecordRTC Source Code}
* @param {MediaStream} mediaStream - MediaStream object or HTMLCanvasElement or CanvasRenderingContext2D.
* @param {object} config - {disableLogs:true, initCallback: function, width: 320, height: 240, frameRate: 200, quality: 10}
*/
function GifRecorder(mediaStream, config) {
if (typeof GIFEncoder === 'undefined') {
var script = document.createElement('script');
script.src = 'https://cdn.webrtc-experiment.com/gif-recorder.js';
(document.body || document.documentElement).appendChild(script);
}
config = config || {};
var isHTMLObject = mediaStream instanceof CanvasRenderingContext2D || mediaStream instanceof HTMLCanvasElement;
/**
* This method records MediaStream.
* @method
* @memberof GifRecorder
* @example
* recorder.record();
*/
this.record = function() {
if (typeof GIFEncoder === 'undefined') {
setTimeout(self.record, 1000);
return;
}
if (!isLoadedMetaData) {
setTimeout(self.record, 1000);
return;
}
if (!isHTMLObject) {
if (!config.width) {
config.width = video.offsetWidth || 320;
}
if (!config.height) {
config.height = video.offsetHeight || 240;
}
if (!config.video) {
config.video = {
width: config.width,
height: config.height
};
}
if (!config.canvas) {
config.canvas = {
width: config.width,
height: config.height
};
}
canvas.width = config.canvas.width || 320;
canvas.height = config.canvas.height || 240;
video.width = config.video.width || 320;
video.height = config.video.height || 240;
}
// external library to record as GIF images
gifEncoder = new GIFEncoder();
// void setRepeat(int iter)
// Sets the number of times the set of GIF frames should be played.
// Default is 1; 0 means play indefinitely.
gifEncoder.setRepeat(0);
// void setFrameRate(Number fps)
// Sets frame rate in frames per second.
// Equivalent to setDelay(1000/fps).
// Using "setDelay" instead of "setFrameRate"
gifEncoder.setDelay(config.frameRate || 200);
// void setQuality(int quality)
// Sets quality of color quantization (conversion of images to the
// maximum 256 colors allowed by the GIF specification).
// Lower values (minimum = 1) produce better colors,
// but slow processing significantly. 10 is the default,
// and produces good color mapping at reasonable speeds.
// Values greater than 20 do not yield significant improvements in speed.
gifEncoder.setQuality(config.quality || 10);
// Boolean start()
// This writes the GIF Header and returns false if it fails.
gifEncoder.start();
if (typeof config.onGifRecordingStarted === 'function') {
config.onGifRecordingStarted();
}
startTime = Date.now();
function drawVideoFrame(time) {
if (self.clearedRecordedData === true) {
return;
}
if (isPausedRecording) {
return setTimeout(function() {
drawVideoFrame(time);
}, 100);
}
lastAnimationFrame = requestAnimationFrame(drawVideoFrame);
if (typeof lastFrameTime === undefined) {
lastFrameTime = time;
}
// ~10 fps
if (time - lastFrameTime < 90) {
return;
}
if (!isHTMLObject && video.paused) {
// via: https://github.com/muaz-khan/WebRTC-Experiment/pull/316
// Tweak for Android Chrome
video.play();
}
if (!isHTMLObject) {
context.drawImage(video, 0, 0, canvas.width, canvas.height);
}
if (config.onGifPreview) {
config.onGifPreview(canvas.toDataURL('image/png'));
}
gifEncoder.addFrame(context);
lastFrameTime = time;
}
lastAnimationFrame = requestAnimationFrame(drawVideoFrame);
if (config.initCallback) {
config.initCallback();
}
};
/**
* This method stops recording MediaStream.
* @param {function} callback - Callback function, that is used to pass recorded blob back to the callee.
* @method
* @memberof GifRecorder
* @example
* recorder.stop(function(blob) {
* img.src = URL.createObjectURL(blob);
* });
*/
this.stop = function(callback) {
callback = callback || function() {};
if (lastAnimationFrame) {
cancelAnimationFrame(lastAnimationFrame);
}
endTime = Date.now();
/**
* @property {Blob} blob - The recorded blob object.
* @memberof GifRecorder
* @example
* recorder.stop(function(){
* var blob = recorder.blob;
* });
*/
this.blob = new Blob([new Uint8Array(gifEncoder.stream().bin)], {
type: 'image/gif'
});
callback(this.blob);
// bug: find a way to clear old recorded blobs
gifEncoder.stream().bin = [];
};
var isPausedRecording = false;
/**
* This method pauses the recording process.
* @method
* @memberof GifRecorder
* @example
* recorder.pause();
*/
this.pause = function() {
isPausedRecording = true;
};
/**
* This method resumes the recording process.
* @method
* @memberof GifRecorder
* @example
* recorder.resume();
*/
this.resume = function() {
isPausedRecording = false;
};
/**
* This method resets currently recorded data.
* @method
* @memberof GifRecorder
* @example
* recorder.clearRecordedData();
*/
this.clearRecordedData = function() {
self.clearedRecordedData = true;
clearRecordedDataCB();
};
function clearRecordedDataCB() {
if (gifEncoder) {
gifEncoder.stream().bin = [];
}
}
// for debugging
this.name = 'GifRecorder';
this.toString = function() {
return this.name;
};
var canvas = document.createElement('canvas');
var context = canvas.getContext('2d');
if (isHTMLObject) {
if (mediaStream instanceof CanvasRenderingContext2D) {
context = mediaStream;
canvas = context.canvas;
} else if (mediaStream instanceof HTMLCanvasElement) {
context = mediaStream.getContext('2d');
canvas = mediaStream;
}
}
var isLoadedMetaData = true;
if (!isHTMLObject) {
var video = document.createElement('video');
video.muted = true;
video.autoplay = true;
isLoadedMetaData = false;
video.onloadedmetadata = function() {
isLoadedMetaData = true;
};
setSrcObject(mediaStream, video);
video.play();
}
var lastAnimationFrame = null;
var startTime, endTime, lastFrameTime;
var gifEncoder;
var self = this;
}
if (typeof RecordRTC !== 'undefined') {
RecordRTC.GifRecorder = GifRecorder;
}
// Last time updated: 2018-03-02 2:56:28 AM UTC
// ________________________
// MultiStreamsMixer v1.0.5
// Open-Sourced: https://github.com/muaz-khan/MultiStreamsMixer
// --------------------------------------------------
// Muaz Khan - www.MuazKhan.com
// MIT License - www.WebRTC-Experiment.com/licence
// --------------------------------------------------
function MultiStreamsMixer(arrayOfMediaStreams) {
// requires: chrome://flags/#enable-experimental-web-platform-features
var videos = [];
var isStopDrawingFrames = false;
var canvas = document.createElement('canvas');
var context = canvas.getContext('2d');
canvas.style = 'opacity:0;position:absolute;z-index:-1;top: -100000000;left:-1000000000; margin-top:-1000000000;margin-left:-1000000000;';
(document.body || document.documentElement).appendChild(canvas);
this.disableLogs = false;
this.frameInterval = 10;
this.width = 360;
this.height = 240;
// use gain node to prevent echo
this.useGainNode = true;
var self = this;
// _____________________________
// Cross-Browser-Declarations.js
// WebAudio API representer
var AudioContext = window.AudioContext;
if (typeof AudioContext === 'undefined') {
if (typeof webkitAudioContext !== 'undefined') {
/*global AudioContext:true */
AudioContext = webkitAudioContext;
}
if (typeof mozAudioContext !== 'undefined') {
/*global AudioContext:true */
AudioContext = mozAudioContext;
}
}
/*jshint -W079 */
var URL = window.URL;
if (typeof URL === 'undefined' && typeof webkitURL !== 'undefined') {
/*global URL:true */
URL = webkitURL;
}
if (typeof navigator !== 'undefined' && typeof navigator.getUserMedia === 'undefined') { // maybe window.navigator?
if (typeof navigator.webkitGetUserMedia !== 'undefined') {
navigator.getUserMedia = navigator.webkitGetUserMedia;
}
if (typeof navigator.mozGetUserMedia !== 'undefined') {
navigator.getUserMedia = navigator.mozGetUserMedia;
}
}
var MediaStream = window.MediaStream;
if (typeof MediaStream === 'undefined' && typeof webkitMediaStream !== 'undefined') {
MediaStream = webkitMediaStream;
}
/*global MediaStream:true */
if (typeof MediaStream !== 'undefined') {
if (!('getVideoTracks' in MediaStream.prototype)) {
MediaStream.prototype.getVideoTracks = function() {
if (!this.getTracks) {
return [];
}
var tracks = [];
this.getTracks.forEach(function(track) {
if (track.kind.toString().indexOf('video') !== -1) {
tracks.push(track);
}
});
return tracks;
};
MediaStream.prototype.getAudioTracks = function() {
if (!this.getTracks) {
return [];
}
var tracks = [];
this.getTracks.forEach(function(track) {
if (track.kind.toString().indexOf('audio') !== -1) {
tracks.push(track);
}
});
return tracks;
};
}
// override "stop" method for all browsers
if (typeof MediaStream.prototype.stop === 'undefined') {
MediaStream.prototype.stop = function() {
this.getTracks().forEach(function(track) {
track.stop();
});
};
}
}
var Storage = {};
if (typeof AudioContext !== 'undefined') {
Storage.AudioContext = AudioContext;
} else if (typeof webkitAudioContext !== 'undefined') {
Storage.AudioContext = webkitAudioContext;
}
function setSrcObject(stream, element, ignoreCreateObjectURL) {
if ('createObjectURL' in URL && !ignoreCreateObjectURL) {
try {
element.src = URL.createObjectURL(stream);
} catch (e) {
setSrcObject(stream, element, true);
return;
}
} else if ('srcObject' in element) {
element.srcObject = stream;
} else if ('mozSrcObject' in element) {
element.mozSrcObject = stream;
} else {
alert('createObjectURL/srcObject both are not supported.');
}
}
this.startDrawingFrames = function() {
drawVideosToCanvas();
};
function drawVideosToCanvas() {
if (isStopDrawingFrames) {
return;
}
var videosLength = videos.length;
var fullcanvas = false;
var remaining = [];
videos.forEach(function(video) {
if (!video.stream) {
video.stream = {};
}
if (video.stream.fullcanvas) {
fullcanvas = video;
} else {
remaining.push(video);
}
});
if (fullcanvas) {
canvas.width = fullcanvas.stream.width;
canvas.height = fullcanvas.stream.height;
} else if (remaining.length) {
canvas.width = videosLength > 1 ? remaining[0].width * 2 : remaining[0].width;
var height = 1;
if (videosLength === 3 || videosLength === 4) {
height = 2;
}
if (videosLength === 5 || videosLength === 6) {
height = 3;
}
if (videosLength === 7 || videosLength === 8) {
height = 4;
}
if (videosLength === 9 || videosLength === 10) {
height = 5;
}
canvas.height = remaining[0].height * height;
} else {
canvas.width = self.width || 360;
canvas.height = self.height || 240;
}
if (fullcanvas && fullcanvas instanceof HTMLVideoElement) {
drawImage(fullcanvas);
}
remaining.forEach(function(video, idx) {
drawImage(video, idx);
});
setTimeout(drawVideosToCanvas, self.frameInterval);
}
function drawImage(video, idx) {
if (isStopDrawingFrames) {
return;
}
var x = 0;
var y = 0;
var width = video.width;
var height = video.height;
if (idx === 1) {
x = video.width;
}
if (idx === 2) {
y = video.height;
}
if (idx === 3) {
x = video.width;
y = video.height;
}
if (idx === 4) {
y = video.height * 2;
}
if (idx === 5) {
x = video.width;
y = video.height * 2;
}
if (idx === 6) {
y = video.height * 3;
}
if (idx === 7) {
x = video.width;
y = video.height * 3;
}
if (typeof video.stream.left !== 'undefined') {
x = video.stream.left;
}
if (typeof video.stream.top !== 'undefined') {
y = video.stream.top;
}
if (typeof video.stream.width !== 'undefined') {
width = video.stream.width;
}
if (typeof video.stream.height !== 'undefined') {
height = video.stream.height;
}
context.drawImage(video, x, y, width, height);
if (typeof video.stream.onRender === 'function') {
video.stream.onRender(context, x, y, width, height, idx);
}
}
function getMixedStream() {
isStopDrawingFrames = false;
var mixedVideoStream = getMixedVideoStream();
var mixedAudioStream = getMixedAudioStream();
if (mixedAudioStream) {
mixedAudioStream.getAudioTracks().forEach(function(track) {
mixedVideoStream.addTrack(track);
});
}
var fullcanvas;
arrayOfMediaStreams.forEach(function(stream) {
if (stream.fullcanvas) {
fullcanvas = true;
}
});
return mixedVideoStream;
}
function getMixedVideoStream() {
resetVideoStreams();
var capturedStream;
if ('captureStream' in canvas) {
capturedStream = canvas.captureStream();
} else if ('mozCaptureStream' in canvas) {
capturedStream = canvas.mozCaptureStream();
} else if (!self.disableLogs) {
console.error('Upgrade to latest Chrome or otherwise enable this flag: chrome://flags/#enable-experimental-web-platform-features');
}
var videoStream = new MediaStream();
capturedStream.getVideoTracks().forEach(function(track) {
videoStream.addTrack(track);
});
canvas.stream = videoStream;
return videoStream;
}
function getMixedAudioStream() {
// via: @pehrsons
if (!Storage.AudioContextConstructor) {
Storage.AudioContextConstructor = new Storage.AudioContext();
}
self.audioContext = Storage.AudioContextConstructor;
self.audioSources = [];
if (self.useGainNode === true) {
self.gainNode = self.audioContext.createGain();
self.gainNode.connect(self.audioContext.destination);
self.gainNode.gain.value = 0; // don't hear self
}
var audioTracksLength = 0;
arrayOfMediaStreams.forEach(function(stream) {
if (!stream.getAudioTracks().length) {
return;
}
audioTracksLength++;
var audioSource = self.audioContext.createMediaStreamSource(stream);
if (self.useGainNode === true) {
audioSource.connect(self.gainNode);
}
self.audioSources.push(audioSource);
});
if (!audioTracksLength) {
return;
}
self.audioDestination = self.audioContext.createMediaStreamDestination();
self.audioSources.forEach(function(audioSource) {
audioSource.connect(self.audioDestination);
});
return self.audioDestination.stream;
}
function getVideo(stream) {
var video = document.createElement('video');
setSrcObject(stream, video);
video.muted = true;
video.volume = 0;
video.width = stream.width || self.width || 360;
video.height = stream.height || self.height || 240;
video.play();
return video;
}
this.appendStreams = function(streams) {
if (!streams) {
throw 'First parameter is required.';
}
if (!(streams instanceof Array)) {
streams = [streams];
}
arrayOfMediaStreams.concat(streams);
streams.forEach(function(stream) {
if (stream.getVideoTracks().length) {
var video = getVideo(stream);
video.stream = stream;
videos.push(video);
}
if (stream.getAudioTracks().length && self.audioContext) {
var audioSource = self.audioContext.createMediaStreamSource(stream);
audioSource.connect(self.audioDestination);
self.audioSources.push(audioSource);
}
});
};
this.releaseStreams = function() {
videos = [];
isStopDrawingFrames = true;
if (self.gainNode) {
self.gainNode.disconnect();
self.gainNode = null;
}
if (self.audioSources.length) {
self.audioSources.forEach(function(source) {
source.disconnect();
});
self.audioSources = [];
}
if (self.audioDestination) {
self.audioDestination.disconnect();
self.audioDestination = null;
}
if (self.audioContext) {
self.audioContext.close();
}
self.audioContext = null;
context.clearRect(0, 0, canvas.width, canvas.height);
if (canvas.stream) {
canvas.stream.stop();
canvas.stream = null;
}
};
this.resetVideoStreams = function(streams) {
if (streams && !(streams instanceof Array)) {
streams = [streams];
}
resetVideoStreams(streams);
};
function resetVideoStreams(streams) {
videos = [];
streams = streams || arrayOfMediaStreams;
// via: @adrian-ber
streams.forEach(function(stream) {
if (!stream.getVideoTracks().length) {
return;
}
var video = getVideo(stream);
video.stream = stream;
videos.push(video);
});
}
// for debugging
this.name = 'MultiStreamsMixer';
this.toString = function() {
return this.name;
};
this.getMixedStream = getMixedStream;
}
// ______________________
// MultiStreamRecorder.js
/*
* Video conference recording, using captureStream API along with WebAudio and Canvas2D API.
*/
/**
* MultiStreamRecorder can record multiple videos in single container.
* @summary Multi-videos recorder.
* @license {@link https://github.com/muaz-khan/RecordRTC#license|MIT}
* @author {@link http://www.MuazKhan.com|Muaz Khan}
* @typedef MultiStreamRecorder
* @class
* @example
* var options = {
* mimeType: 'video/webm'
* }
* var recorder = new MultiStreamRecorder(ArrayOfMediaStreams, options);
* recorder.record();
* recorder.stop(function(blob) {
* video.src = URL.createObjectURL(blob);
*
* // or
* var blob = recorder.blob;
* });
* @see {@link https://github.com/muaz-khan/RecordRTC|RecordRTC Source Code}
* @param {MediaStreams} mediaStreams - Array of MediaStreams.
* @param {object} config - {disableLogs:true, frameInterval: 1, mimeType: "video/webm"}
*/
function MultiStreamRecorder(arrayOfMediaStreams, options) {
arrayOfMediaStreams = arrayOfMediaStreams || [];
var self = this;
var mixer;
var mediaRecorder;
options = options || {
mimeType: 'video/webm',
video: {
width: 360,
height: 240
}
};
if (!options.frameInterval) {
options.frameInterval = 10;
}
if (!options.video) {
options.video = {};
}
if (!options.video.width) {
options.video.width = 360;
}
if (!options.video.height) {
options.video.height = 240;
}
/**
* This method records all MediaStreams.
* @method
* @memberof MultiStreamRecorder
* @example
* recorder.record();
*/
this.record = function() {
// github/muaz-khan/MultiStreamsMixer
mixer = new MultiStreamsMixer(arrayOfMediaStreams);
if (getVideoTracks().length) {
mixer.frameInterval = options.frameInterval || 10;
mixer.width = options.video.width || 360;
mixer.height = options.video.height || 240;
mixer.startDrawingFrames();
}
if (options.previewStream && typeof options.previewStream === 'function') {
options.previewStream(mixer.getMixedStream());
}
// record using MediaRecorder API
mediaRecorder = new MediaStreamRecorder(mixer.getMixedStream(), options);
mediaRecorder.record();
};
function getVideoTracks() {
var tracks = [];
arrayOfMediaStreams.forEach(function(stream) {
stream.getVideoTracks().forEach(function(track) {
tracks.push(track);
});
});
return tracks;
}
/**
* This method stops recording MediaStream.
* @param {function} callback - Callback function, that is used to pass recorded blob back to the callee.
* @method
* @memberof MultiStreamRecorder
* @example
* recorder.stop(function(blob) {
* video.src = URL.createObjectURL(blob);
* });
*/
this.stop = function(callback) {
if (!mediaRecorder) {
return;
}
mediaRecorder.stop(function(blob) {
self.blob = blob;
callback(blob);
self.clearRecordedData();
});
};
/**
* This method pauses the recording process.
* @method
* @memberof MultiStreamRecorder
* @example
* recorder.pause();
*/
this.pause = function() {
if (mediaRecorder) {
mediaRecorder.pause();
}
};
/**
* This method resumes the recording process.
* @method
* @memberof MultiStreamRecorder
* @example
* recorder.resume();
*/
this.resume = function() {
if (mediaRecorder) {
mediaRecorder.resume();
}
};
/**
* This method resets currently recorded data.
* @method
* @memberof MultiStreamRecorder
* @example
* recorder.clearRecordedData();
*/
this.clearRecordedData = function() {
if (mediaRecorder) {
mediaRecorder.clearRecordedData();
mediaRecorder = null;
}
if (mixer) {
mixer.releaseStreams();
mixer = null;
}
};
/**
* Add extra media-streams to existing recordings.
* @method
* @memberof MultiStreamRecorder
* @param {MediaStreams} mediaStreams - Array of MediaStreams
* @example
* recorder.addStreams([newAudioStream, newVideoStream]);
*/
this.addStreams = function(streams) {
if (!streams) {
throw 'First parameter is required.';
}
if (!(streams instanceof Array)) {
streams = [streams];
}
arrayOfMediaStreams.concat(streams);
if (!mediaRecorder || !mixer) {
return;
}
mixer.appendStreams(streams);
};
/**
* Reset videos during live recording. Replace old videos e.g. replace cameras with full-screen.
* @method
* @memberof MultiStreamRecorder
* @param {MediaStreams} mediaStreams - Array of MediaStreams
* @example
* recorder.resetVideoStreams([newVideo1, newVideo2]);
*/
this.resetVideoStreams = function(streams) {
if (!mixer) {
return;
}
if (streams && !(streams instanceof Array)) {
streams = [streams];
}
mixer.resetVideoStreams(streams);
};
// for debugging
this.name = 'MultiStreamRecorder';
this.toString = function() {
return this.name;
};
}
if (typeof RecordRTC !== 'undefined') {
RecordRTC.MultiStreamRecorder = MultiStreamRecorder;
}
// _____________________
// RecordRTC.promises.js
/**
* RecordRTCPromisesHandler adds promises support in {@link RecordRTC}. Try a {@link https://github.com/muaz-khan/RecordRTC/blob/master/simple-demos/RecordRTCPromisesHandler.html|demo here}
* @summary Promises for {@link RecordRTC}
* @license {@link https://github.com/muaz-khan/RecordRTC#license|MIT}
* @author {@link http://www.MuazKhan.com|Muaz Khan}
* @typedef RecordRTCPromisesHandler
* @class
* @example
* var recorder = new RecordRTCPromisesHandler(mediaStream, options);
* recorder.startRecording()
* .then(successCB)
* .catch(errorCB);
* @see {@link https://github.com/muaz-khan/RecordRTC|RecordRTC Source Code}
* @param {MediaStream} mediaStream - Single media-stream object, array of media-streams, html-canvas-element, etc.
* @param {object} config - {type:"video", recorderType: MediaStreamRecorder, disableLogs: true, numberOfAudioChannels: 1, bufferSize: 0, sampleRate: 0, video: HTMLVideoElement, etc.}
* @throws Will throw an error if "new" keyword is not used to initiate "RecordRTCPromisesHandler". Also throws error if first argument "MediaStream" is missing.
* @requires {@link RecordRTC}
*/
function RecordRTCPromisesHandler(mediaStream, options) {
if (!this) {
throw 'Use "new RecordRTCPromisesHandler()"';
}
if (typeof mediaStream === 'undefined') {
throw 'First argument "MediaStream" is required.';
}
var self = this;
/**
* @property {Blob} blob - Access/reach the native {@link RecordRTC} object.
* @memberof RecordRTCPromisesHandler
* @example
* var internal = recorder.recordRTC.getInternalRecorder();
* alert(internal instanceof MediaStreamRecorder);
*/
self.recordRTC = new RecordRTC(mediaStream, options);
/**
* This method records MediaStream.
* @method
* @memberof RecordRTCPromisesHandler
* @example
* recorder.startRecording()
* .then(successCB)
* .catch(errorCB);
*/
this.startRecording = function() {
return new Promise(function(resolve, reject) {
try {
self.recordRTC.startRecording();
resolve();
} catch (e) {
reject(e);
}
});
};
/**
* This method stops the recording.
* @method
* @memberof RecordRTCPromisesHandler
* @example
* recorder.stopRecording().then(function() {
* var blob = recorder.getBlob();
* }).catch(errorCB);
*/
this.stopRecording = function() {
return new Promise(function(resolve, reject) {
try {
self.recordRTC.stopRecording(function(url) {
self.blob = self.recordRTC.getBlob();
resolve(url);
});
} catch (e) {
reject(e);
}
});
};
/**
* This method returns data-url for the recorded blob.
* @method
* @memberof RecordRTCPromisesHandler
* @example
* recorder.stopRecording().then(function() {
* recorder.getDataURL().then(function(dataURL) {
* window.open(dataURL);
* }).catch(errorCB);;
* }).catch(errorCB);
*/
this.getDataURL = function(callback) {
return new Promise(function(resolve, reject) {
try {
self.recordRTC.getDataURL(function(dataURL) {
resolve(dataURL);
});
} catch (e) {
reject(e);
}
});
};
/**
* This method returns the recorded blob.
* @method
* @memberof RecordRTCPromisesHandler
* @example
* recorder.stopRecording().then(function() {
* var blob = recorder.getBlob();
* }).catch(errorCB);
*/
this.getBlob = function() {
return self.recordRTC.getBlob();
};
/**
* @property {Blob} blob - Recorded data as "Blob" object.
* @memberof RecordRTCPromisesHandler
* @example
* recorder.stopRecording().then(function() {
* var blob = recorder.getBlob();
* }).catch(errorCB);
*/
this.blob = null;
}
if (typeof RecordRTC !== 'undefined') {
RecordRTC.RecordRTCPromisesHandler = RecordRTCPromisesHandler;
}
/*****************************************************************
** Author: Asvin Goel, [email protected]
**
** The slide show recorder is a plugin for reveal.js allowing to
** record audio for a slide deck.
**
** Version: 0.4
**
** License: MIT license (see LICENSE.md)
**
** Credits:
** - Muaz Khan for RecordRTC.js
** - Stuart Knightley for JSzip.js
******************************************************************/
var Recorder = {
audio: null,
audioStream: null,
recordRTC: null,
zip: null,
indices: null,
recordedAudio: null,
canvas: null,
isRecording: false,
isPaused: false,
initialize : function initialize() {
this.audio = new Audio();
this.audio.autoplay = true;
this.zip = new JSZip();
// Create canvas on which red circle can be drawn
this.canvas = document.createElement( 'canvas' );
this.canvas.className = 'recorder';
this.canvas.setAttribute( 'style', "position: fixed; top: 25px; right: 50px;" );
this.canvas.width = 25;
this.canvas.height = 25;
document.querySelector( '.reveal' ).appendChild( this.canvas );
},
toggleRecording: function toggleRecording( override ) {
var wasRecording = this.isRecording;
if( typeof override === 'boolean' ) {
this.isRecording = override;
this.isPaused = false;
}
else {
this.isRecording = !this.isRecording;
}
// turn of recording if overview is shown or screen is black
this.isRecording = ( this.isRecording && !Reveal.isOverview() && !Reveal.isPaused() );
if ( !wasRecording && this.isRecording ) {
this.start();
}
else if ( wasRecording && !this.isRecording) {
this.stop();
}
},
start : function start() {
window.onbeforeunload = confirmExit;
function confirmExit()
{
return "You have attempted to leave this page. All unsaved audio recordings will be lost. Are you sure you want to exit this page?";
}
this.indices = Reveal.getIndices();
// determine audio element for slide
var id = "audioplayer-" + this.indices.h + "." + this.indices.v;
if ( this.indices.f != undefined && this.indices.f >= 0 ) id = id + "." + this.indices.f;
this.recordedAudio = document.getElementById( id );
if ( !this.recordedAudio ) {
alert("Audio player is not found. Please check that audio-slideshow plugin is loaded!");
}
if ( !this.audioStream || !this.recordRTC ) {
navigator.getUserMedia( { audio: true, video: false }, function( stream ) {
if ( window.IsChrome ) stream = new window.MediaStream( stream.getAudioTracks() );
Recorder.audioStream = stream;
Recorder.recordRTC = window.RecordRTC( stream, { type: 'audio' }, { bufferSize: 256 } );
Recorder.recordRTC.startRecording();
// Draw red circle over auto slide control
var context = Recorder.canvas.getContext( '2d' );
context.beginPath();
context.arc( ( Recorder.canvas.width / 2 ), ( Recorder.canvas.height / 2 ), ( Recorder.canvas.width / 2 ) - 3, 0, Math.PI * 2, false );
context.lineWidth = 3;
context.fillStyle = '#f00';
context.fill();
context.strokeStyle = '#f00';
context.stroke();
// Let others know recording has started
document.dispatchEvent( new CustomEvent('startrecording') );
}, function( error ) {
alert( 'Something went wrong in accessing the microphone. (error code ' + error.code + ')' );
} );
}
else {
// this.audio.src = URL.createObjectURL( this.audioStream ); // deprecated since FF54
this.audio.srcObject = this.audioStream;
this.audio.volume = 0.0;
this.recordRTC.startRecording();
// Draw red circle over auto slide control
var context = this.canvas.getContext( '2d' );
context.beginPath();
context.arc( ( this.canvas.width / 2 ), ( this.canvas.height / 2 ), ( this.canvas.width / 2 ) - 3, 0, Math.PI * 2, false );
context.lineWidth = 3;
context.fillStyle = '#f00';
context.fill();
context.strokeStyle = '#f00';
context.stroke();
// Let others know recording has started
document.dispatchEvent( new CustomEvent('startrecording') );
}
},
stop : function stop() {
this.audio.src = '';
if ( this.recordRTC ) {
this.filename = this.indices.h + '.' + this.indices.v;
if ( ( typeof this.indices.f != 'undefined' && this.indices.f >= 0) ) this.filename = this.filename + '.' + this.indices.f;
this.recordRTC.stopRecording( function( url ) {
// add audio URL to slide
Recorder.recordedAudio.src = url;
// add audio to zip
var blob = Recorder.recordRTC.getBlob();
Recorder.filename = Recorder.filename + '.' + blob.type.split( '/' ).pop();
var reader = new window.FileReader();
reader.readAsBinaryString(blob);
reader.onloadend = function() {
blobBinaryString = reader.result;
Recorder.zip.file( Recorder.filename, blobBinaryString, { binary: true } );
Recorder.filename = null;
}
} );
this.indices = null;
}
// Remove red circle over auto slide control
var context = this.canvas.getContext( '2d' );
context.clearRect ( 0 , 0 , this.canvas.width , this.canvas.height );
// Let others know recording has stopped
document.dispatchEvent( new CustomEvent('stoprecording') );
},
next : function next() {
// Remove red or yellow circle
var context = this.canvas.getContext( '2d' );
context.clearRect ( 0 , 0 , this.canvas.width , this.canvas.height );
this.audio.src = '';
if ( this.recordRTC ) {
this.filename = this.indices.h + '.' + this.indices.v;
if ( ( typeof this.indices.f != 'undefined' && this.indices.f >= 0) ) {
this.filename = this.filename + '.' + this.indices.f;
}
this.recordRTC.stopRecording( function( url ) {
// add audio URL to slide
Recorder.recordedAudio.src = url;
// add audio to zip
var blob = Recorder.recordRTC.getBlob();
Recorder.filename = Recorder.filename + '.' + blob.type.split( '/' ).pop();
var reader = new window.FileReader();
reader.readAsBinaryString(blob);
reader.onloadend = function() {
blobBinaryString = reader.result;
Recorder.zip.file( Recorder.filename, blobBinaryString, { binary: true } );
Recorder.filename = null;
if ( !Recorder.isPaused ) Recorder.start();
}
} );
}
if ( this.isPaused ) {
// Draw yellow circle over auto slide control
var context = this.canvas.getContext( '2d' );
context.beginPath();
context.arc( ( this.canvas.width / 2 ), ( this.canvas.height / 2 ), ( this.canvas.width / 2 ) - 3, 0, Math.PI * 2, false );
context.lineWidth = 3;
context.fillStyle = '#ff0';
context.fill();
context.strokeStyle = '#ff0';
context.stroke();
}
},
downloadZip : function downloadZip() {
var a = document.createElement('a');
document.body.appendChild(a);
try {
a.download = "audio.zip";
var blob = this.zip.generate( {type:"blob"} );
a.href = window.URL.createObjectURL( blob );
} catch( error ) {
a.innerHTML += " (" + error + ")";
}
a.click();
document.body.removeChild(a);
},
fetchTTS : function fetchTTS() {
function fetchAudio( audioSources ) {
if ( audioSources.length ) {
// take first audio from array
var audioSource = audioSources.shift();
var progress = Math.round(100 * ( progressBar.getAttribute( 'data-max' ) - audioSources.length ) / progressBar.getAttribute( 'data-max' ) );
progressBar.setAttribute( 'style', "width: " + progress + "%" );
var filename = audioSource.getAttribute('data-tts');
var xhr = new XMLHttpRequest();
xhr.open('GET', audioSource.src, true);
xhr.responseType = 'blob';
xhr.onload = function() {
if (xhr.readyState === 4 && xhr.status === 200) {
var blobURL = window.URL.createObjectURL(xhr.response);
filename += '.' + xhr.response.type.split( '/' ).pop().split( 'x-' ).pop();
// convert blob to binary string
var reader = new window.FileReader();
reader.readAsBinaryString(xhr.response);
reader.onloadend = function() {
blobBinaryString = reader.result;
// add blob to zip
Recorder.zip.file( filename, blobBinaryString, { binary: true } );
// fetch next audio file
fetchAudio( audioSources );
}
}
}
xhr.onerror = function() {
alert ( "Unable to fetch TTS-files!" );
// remove progress bar
document.querySelector( ".reveal" ).removeChild( progressContainer );
}
try {
xhr.send(null); // fetch TTS
console.log("Fetch TTS for slide " + audioSource.getAttribute('data-tts'));
} catch ( error ) {
alert ( "Unable to fetch TTS-files! " + error );
// remove progress bar
document.querySelector( ".reveal" ).removeChild( progressContainer );
}
}
else {
// generate zip for download
var blob = Recorder.zip.generate( {type:"blob"} );
var a = document.createElement('a');
document.body.appendChild(a);
try {
a.download = "audio.zip";
a.href = window.URL.createObjectURL( blob );
} catch( error ) {
a.innerHTML += " (" + error + ")";
}
a.click();
document.body.removeChild(a);
// remove progress bar
document.querySelector( ".reveal" ).removeChild( progressContainer );
}
}
var TTS = document.querySelectorAll('audio>source[data-tts]');
if ( TTS.length ) {
// show progress bar
var progressContainer = document.createElement( 'div' );
progressContainer.className = "progress";
progressContainer.setAttribute( 'style', "display: block; top: 0; bottom: auto; height: 12px;" );
var progressBar = document.createElement( 'span' );
progressBar.setAttribute( 'style', "width: 0%;" );
progressBar.setAttribute( 'data-max', TTS.length );
progressContainer.appendChild( progressBar );
document.querySelector( ".reveal" ).appendChild( progressContainer );
fetchAudio( Array.prototype.slice.call(TTS) );
}
else {
alert("Either there is no audio to fetch from the text to speech generator or all audio files are already provided.");
}
}
};
(function(){
Reveal.addEventListener( 'fragmentshown', function( event ) {
if ( Recorder.isRecording ) {
if ( recordedAudioExists( Reveal.getIndices() ) ) {
Recorder.isPaused = true;
Recorder.next();
}
else if ( Recorder.isPaused ) {
// resume recording
Recorder.isPaused = false;
Recorder.start();
}
else {
Recorder.next();
}
}
} );
Reveal.addEventListener( 'fragmenthidden', function( event ) {
if ( Recorder.isRecording ) {
if ( recordedAudioExists( Reveal.getIndices() ) ) {
Recorder.isPaused = true;
Recorder.next();
}
else if ( Recorder.isPaused ) {
// resume recording
Recorder.isPaused = false;
Recorder.start();
}
else {
Recorder.next();
}
}
} );
Reveal.addEventListener( 'overviewshown', function( event ) {
Recorder.toggleRecording( false );
} );
Reveal.addEventListener( 'paused', function( event ) {
Recorder.toggleRecording( false );
} );
Reveal.addEventListener( 'ready', function( event ) {
Recorder.initialize();
} );
Reveal.addEventListener( 'slidechanged', function( event ) {
if ( Recorder.isRecording ) {
if ( recordedAudioExists( Reveal.getIndices() ) ) {
Recorder.isPaused = true;
Recorder.next();
}
else if ( Recorder.isPaused ) {
// resume recording
Recorder.isPaused = false;
Recorder.start();
}
else {
Recorder.next();
}
}
} );
function recordedAudioExists( indices ) {
var id = "audioplayer-" + indices.h + "." + indices.v;
if ( indices.f != undefined && indices.f >= 0 ) id = id + "." + indices.f;
return ( document.getElementById( id ).src.substring(0,4) == "blob");
}
})();
/*****************************************************************
** jszip.js
******************************************************************/
/*!
JSZip - A Javascript class for generating and reading zip files
<http://stuartk.com/jszip>
(c) 2009-2014 Stuart Knightley <stuart [at] stuartk.com>
Dual licenced under the MIT license or GPLv3. See https://raw.github.com/Stuk/jszip/master/LICENSE.markdown.
JSZip uses the library pako released under the MIT license :
https://github.com/nodeca/pako/blob/master/LICENSE
*/
!function(a){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=a();else if("function"==typeof define&&define.amd)define([],a);else{var b;"undefined"!=typeof window?b=window:"undefined"!=typeof global?b=global:"undefined"!=typeof self&&(b=self),b.JSZip=a()}}(function(){return function a(b,c,d){function e(g,h){if(!c[g]){if(!b[g]){var i="function"==typeof require&&require;if(!h&&i)return i(g,!0);if(f)return f(g,!0);throw new Error("Cannot find module '"+g+"'")}var j=c[g]={exports:{}};b[g][0].call(j.exports,function(a){var c=b[g][1][a];return e(c?c:a)},j,j.exports,a,b,c,d)}return c[g].exports}for(var f="function"==typeof require&&require,g=0;g<d.length;g++)e(d[g]);return e}({1:[function(a,b,c){"use strict";var d="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";c.encode=function(a){for(var b,c,e,f,g,h,i,j="",k=0;k<a.length;)b=a.charCodeAt(k++),c=a.charCodeAt(k++),e=a.charCodeAt(k++),f=b>>2,g=(3&b)<<4|c>>4,h=(15&c)<<2|e>>6,i=63&e,isNaN(c)?h=i=64:isNaN(e)&&(i=64),j=j+d.charAt(f)+d.charAt(g)+d.charAt(h)+d.charAt(i);return j},c.decode=function(a){var b,c,e,f,g,h,i,j="",k=0;for(a=a.replace(/[^A-Za-z0-9\+\/\=]/g,"");k<a.length;)f=d.indexOf(a.charAt(k++)),g=d.indexOf(a.charAt(k++)),h=d.indexOf(a.charAt(k++)),i=d.indexOf(a.charAt(k++)),b=f<<2|g>>4,c=(15&g)<<4|h>>2,e=(3&h)<<6|i,j+=String.fromCharCode(b),64!=h&&(j+=String.fromCharCode(c)),64!=i&&(j+=String.fromCharCode(e));return j}},{}],2:[function(a,b){"use strict";function c(){this.compressedSize=0,this.uncompressedSize=0,this.crc32=0,this.compressionMethod=null,this.compressedContent=null}c.prototype={getContent:function(){return null},getCompressedContent:function(){return null}},b.exports=c},{}],3:[function(a,b,c){"use strict";c.STORE={magic:"\x00\x00",compress:function(a){return a},uncompress:function(a){return a},compressInputType:null,uncompressInputType:null},c.DEFLATE=a("./flate")},{"./flate":8}],4:[function(a,b){"use strict";var c=a("./utils"),d=[0,1996959894,3993919788,2567524794,124634137,1886057615,3915621685,2657392035,249268274,2044508324,3772115230,2547177864,162941995,2125561021,3887607047,2428444049,498536548,1789927666,4089016648,2227061214,450548861,1843258603,4107580753,2211677639,325883990,1684777152,4251122042,2321926636,335633487,1661365465,4195302755,2366115317,997073096,1281953886,3579855332,2724688242,1006888145,1258607687,3524101629,2768942443,901097722,1119000684,3686517206,2898065728,853044451,1172266101,3705015759,2882616665,651767980,1373503546,3369554304,3218104598,565507253,1454621731,3485111705,3099436303,671266974,1594198024,3322730930,2970347812,795835527,1483230225,3244367275,3060149565,1994146192,31158534,2563907772,4023717930,1907459465,112637215,2680153253,3904427059,2013776290,251722036,2517215374,3775830040,2137656763,141376813,2439277719,3865271297,1802195444,476864866,2238001368,4066508878,1812370925,453092731,2181625025,4111451223,1706088902,314042704,2344532202,4240017532,1658658271,366619977,2362670323,4224994405,1303535960,984961486,2747007092,3569037538,1256170817,1037604311,2765210733,3554079995,1131014506,879679996,2909243462,3663771856,1141124467,855842277,2852801631,3708648649,1342533948,654459306,3188396048,3373015174,1466479909,544179635,3110523913,3462522015,1591671054,702138776,2966460450,3352799412,1504918807,783551873,3082640443,3233442989,3988292384,2596254646,62317068,1957810842,3939845945,2647816111,81470997,1943803523,3814918930,2489596804,225274430,2053790376,3826175755,2466906013,167816743,2097651377,4027552580,2265490386,503444072,1762050814,4150417245,2154129355,426522225,1852507879,4275313526,2312317920,282753626,1742555852,4189708143,2394877945,397917763,1622183637,3604390888,2714866558,953729732,1340076626,3518719985,2797360999,1068828381,1219638859,3624741850,2936675148,906185462,1090812512,3747672003,2825379669,829329135,1181335161,3412177804,3160834842,628085408,1382605366,3423369109,3138078467,570562233,1426400815,3317316542,2998733608,733239954,1555261956,3268935591,3050360625,752459403,1541320221,2607071920,3965973030,1969922972,40735498,2617837225,3943577151,1913087877,83908371,2512341634,3803740692,2075208622,213261112,2463272603,3855990285,2094854071,198958881,2262029012,4057260610,1759359992,534414190,2176718541,4139329115,1873836001,414664567,2282248934,4279200368,1711684554,285281116,2405801727,4167216745,1634467795,376229701,2685067896,3608007406,1308918612,956543938,2808555105,3495958263,1231636301,1047427035,2932959818,3654703836,1088359270,936918e3,2847714899,3736837829,1202900863,817233897,3183342108,3401237130,1404277552,615818150,3134207493,3453421203,1423857449,601450431,3009837614,3294710456,1567103746,711928724,3020668471,3272380065,1510334235,755167117];b.exports=function(a,b){if("undefined"==typeof a||!a.length)return 0;var e="string"!==c.getTypeOf(a);"undefined"==typeof b&&(b=0);var f=0,g=0,h=0;b=-1^b;for(var i=0,j=a.length;j>i;i++)h=e?a[i]:a.charCodeAt(i),g=255&(b^h),f=d[g],b=b>>>8^f;return-1^b}},{"./utils":21}],5:[function(a,b){"use strict";function c(){this.data=null,this.length=0,this.index=0}var d=a("./utils");c.prototype={checkOffset:function(a){this.checkIndex(this.index+a)},checkIndex:function(a){if(this.length<a||0>a)throw new Error("End of data reached (data length = "+this.length+", asked index = "+a+"). Corrupted zip ?")},setIndex:function(a){this.checkIndex(a),this.index=a},skip:function(a){this.setIndex(this.index+a)},byteAt:function(){},readInt:function(a){var b,c=0;for(this.checkOffset(a),b=this.index+a-1;b>=this.index;b--)c=(c<<8)+this.byteAt(b);return this.index+=a,c},readString:function(a){return d.transformTo("string",this.readData(a))},readData:function(){},lastIndexOfSignature:function(){},readDate:function(){var a=this.readInt(4);return new Date((a>>25&127)+1980,(a>>21&15)-1,a>>16&31,a>>11&31,a>>5&63,(31&a)<<1)}},b.exports=c},{"./utils":21}],6:[function(a,b,c){"use strict";c.base64=!1,c.binary=!1,c.dir=!1,c.createFolders=!1,c.date=null,c.compression=null,c.comment=null},{}],7:[function(a,b,c){"use strict";var d=a("./utils");c.string2binary=function(a){return d.string2binary(a)},c.string2Uint8Array=function(a){return d.transformTo("uint8array",a)},c.uint8Array2String=function(a){return d.transformTo("string",a)},c.string2Blob=function(a){var b=d.transformTo("arraybuffer",a);return d.arrayBuffer2Blob(b)},c.arrayBuffer2Blob=function(a){return d.arrayBuffer2Blob(a)},c.transformTo=function(a,b){return d.transformTo(a,b)},c.getTypeOf=function(a){return d.getTypeOf(a)},c.checkSupport=function(a){return d.checkSupport(a)},c.MAX_VALUE_16BITS=d.MAX_VALUE_16BITS,c.MAX_VALUE_32BITS=d.MAX_VALUE_32BITS,c.pretty=function(a){return d.pretty(a)},c.findCompression=function(a){return d.findCompression(a)},c.isRegExp=function(a){return d.isRegExp(a)}},{"./utils":21}],8:[function(a,b,c){"use strict";var d="undefined"!=typeof Uint8Array&&"undefined"!=typeof Uint16Array&&"undefined"!=typeof Uint32Array,e=a("pako");c.uncompressInputType=d?"uint8array":"array",c.compressInputType=d?"uint8array":"array",c.magic="\b\x00",c.compress=function(a){return e.deflateRaw(a)},c.uncompress=function(a){return e.inflateRaw(a)}},{pako:24}],9:[function(a,b){"use strict";function c(a,b){return this instanceof c?(this.files={},this.comment=null,this.root="",a&&this.load(a,b),void(this.clone=function(){var a=new c;for(var b in this)"function"!=typeof this[b]&&(a[b]=this[b]);return a})):new c(a,b)}var d=a("./base64");c.prototype=a("./object"),c.prototype.load=a("./load"),c.support=a("./support"),c.defaults=a("./defaults"),c.utils=a("./deprecatedPublicUtils"),c.base64={encode:function(a){return d.encode(a)},decode:function(a){return d.decode(a)}},c.compressions=a("./compressions"),b.exports=c},{"./base64":1,"./compressions":3,"./defaults":6,"./deprecatedPublicUtils":7,"./load":10,"./object":13,"./support":17}],10:[function(a,b){"use strict";var c=a("./base64"),d=a("./zipEntries");b.exports=function(a,b){var e,f,g,h;for(b=b||{},b.base64&&(a=c.decode(a)),f=new d(a,b),e=f.files,g=0;g<e.length;g++)h=e[g],this.file(h.fileName,h.decompressed,{binary:!0,optimizedBinaryString:!0,date:h.date,dir:h.dir,comment:h.fileComment.length?h.fileComment:null,createFolders:b.createFolders});return f.zipComment.length&&(this.comment=f.zipComment),this}},{"./base64":1,"./zipEntries":22}],11:[function(a,b){(function(a){"use strict";b.exports=function(b,c){return new a(b,c)},b.exports.test=function(b){return a.isBuffer(b)}}).call(this,"undefined"!=typeof Buffer?Buffer:void 0)},{}],12:[function(a,b){"use strict";function c(a){this.data=a,this.length=this.data.length,this.index=0}var d=a("./uint8ArrayReader");c.prototype=new d,c.prototype.readData=function(a){this.checkOffset(a);var b=this.data.slice(this.index,this.index+a);return this.index+=a,b},b.exports=c},{"./uint8ArrayReader":18}],13:[function(a,b){"use strict";var c=a("./support"),d=a("./utils"),e=a("./crc32"),f=a("./signature"),g=a("./defaults"),h=a("./base64"),i=a("./compressions"),j=a("./compressedObject"),k=a("./nodeBuffer"),l=a("./utf8"),m=a("./stringWriter"),n=a("./uint8ArrayWriter"),o=function(a){if(a._data instanceof j&&(a._data=a._data.getContent(),a.options.binary=!0,a.options.base64=!1,"uint8array"===d.getTypeOf(a._data))){var b=a._data;a._data=new Uint8Array(b.length),0!==b.length&&a._data.set(b,0)}return a._data},p=function(a){var b=o(a),e=d.getTypeOf(b);return"string"===e?!a.options.binary&&c.nodebuffer?k(b,"utf-8"):a.asBinary():b},q=function(a){var b=o(this);return null===b||"undefined"==typeof b?"":(this.options.base64&&(b=h.decode(b)),b=a&&this.options.binary?A.utf8decode(b):d.transformTo("string",b),a||this.options.binary||(b=d.transformTo("string",A.utf8encode(b))),b)},r=function(a,b,c){this.name=a,this.dir=c.dir,this.date=c.date,this.comment=c.comment,this._data=b,this.options=c,this._initialMetadata={dir:c.dir,date:c.date}};r.prototype={asText:function(){return q.call(this,!0)},asBinary:function(){return q.call(this,!1)},asNodeBuffer:function(){var a=p(this);return d.transformTo("nodebuffer",a)},asUint8Array:function(){var a=p(this);return d.transformTo("uint8array",a)},asArrayBuffer:function(){return this.asUint8Array().buffer}};var s=function(a,b){var c,d="";for(c=0;b>c;c++)d+=String.fromCharCode(255&a),a>>>=8;return d},t=function(){var a,b,c={};for(a=0;a<arguments.length;a++)for(b in arguments[a])arguments[a].hasOwnProperty(b)&&"undefined"==typeof c[b]&&(c[b]=arguments[a][b]);return c},u=function(a){return a=a||{},a.base64!==!0||null!==a.binary&&void 0!==a.binary||(a.binary=!0),a=t(a,g),a.date=a.date||new Date,null!==a.compression&&(a.compression=a.compression.toUpperCase()),a},v=function(a,b,c){var e,f=d.getTypeOf(b);if(c=u(c),c.createFolders&&(e=w(a))&&x.call(this,e,!0),c.dir||null===b||"undefined"==typeof b)c.base64=!1,c.binary=!1,b=null;else if("string"===f)c.binary&&!c.base64&&c.optimizedBinaryString!==!0&&(b=d.string2binary(b));else{if(c.base64=!1,c.binary=!0,!(f||b instanceof j))throw new Error("The data of '"+a+"' is in an unsupported format !");"arraybuffer"===f&&(b=d.transformTo("uint8array",b))}var g=new r(a,b,c);return this.files[a]=g,g},w=function(a){"/"==a.slice(-1)&&(a=a.substring(0,a.length-1));var b=a.lastIndexOf("/");return b>0?a.substring(0,b):""},x=function(a,b){return"/"!=a.slice(-1)&&(a+="/"),b="undefined"!=typeof b?b:!1,this.files[a]||v.call(this,a,null,{dir:!0,createFolders:b}),this.files[a]},y=function(a,b){var c,f=new j;return a._data instanceof j?(f.uncompressedSize=a._data.uncompressedSize,f.crc32=a._data.crc32,0===f.uncompressedSize||a.dir?(b=i.STORE,f.compressedContent="",f.crc32=0):a._data.compressionMethod===b.magic?f.compressedContent=a._data.getCompressedContent():(c=a._data.getContent(),f.compressedContent=b.compress(d.transformTo(b.compressInputType,c)))):(c=p(a),(!c||0===c.length||a.dir)&&(b=i.STORE,c=""),f.uncompressedSize=c.length,f.crc32=e(c),f.compressedContent=b.compress(d.transformTo(b.compressInputType,c))),f.compressedSize=f.compressedContent.length,f.compressionMethod=b.magic,f},z=function(a,b,c,g){var h,i,j,k,m=(c.compressedContent,d.transformTo("string",l.utf8encode(b.name))),n=b.comment||"",o=d.transformTo("string",l.utf8encode(n)),p=m.length!==b.name.length,q=o.length!==n.length,r=b.options,t="",u="",v="";j=b._initialMetadata.dir!==b.dir?b.dir:r.dir,k=b._initialMetadata.date!==b.date?b.date:r.date,h=k.getHours(),h<<=6,h|=k.getMinutes(),h<<=5,h|=k.getSeconds()/2,i=k.getFullYear()-1980,i<<=4,i|=k.getMonth()+1,i<<=5,i|=k.getDate(),p&&(u=s(1,1)+s(e(m),4)+m,t+="up"+s(u.length,2)+u),q&&(v=s(1,1)+s(this.crc32(o),4)+o,t+="uc"+s(v.length,2)+v);var w="";w+="\n\x00",w+=p||q?"\x00\b":"\x00\x00",w+=c.compressionMethod,w+=s(h,2),w+=s(i,2),w+=s(c.crc32,4),w+=s(c.compressedSize,4),w+=s(c.uncompressedSize,4),w+=s(m.length,2),w+=s(t.length,2);var x=f.LOCAL_FILE_HEADER+w+m+t,y=f.CENTRAL_FILE_HEADER+"\x00"+w+s(o.length,2)+"\x00\x00\x00\x00"+(j===!0?"\x00\x00\x00":"\x00\x00\x00\x00")+s(g,4)+m+t+o;return{fileRecord:x,dirRecord:y,compressedObject:c}},A={load:function(){throw new Error("Load method is not defined. Is the file jszip-load.js included ?")},filter:function(a){var b,c,d,e,f=[];for(b in this.files)this.files.hasOwnProperty(b)&&(d=this.files[b],e=new r(d.name,d._data,t(d.options)),c=b.slice(this.root.length,b.length),b.slice(0,this.root.length)===this.root&&a(c,e)&&f.push(e));return f},file:function(a,b,c){if(1===arguments.length){if(d.isRegExp(a)){var e=a;return this.filter(function(a,b){return!b.dir&&e.test(a)})}return this.filter(function(b,c){return!c.dir&&b===a})[0]||null}return a=this.root+a,v.call(this,a,b,c),this},folder:function(a){if(!a)return this;if(d.isRegExp(a))return this.filter(function(b,c){return c.dir&&a.test(b)});var b=this.root+a,c=x.call(this,b),e=this.clone();return e.root=c.name,e},remove:function(a){a=this.root+a;var b=this.files[a];if(b||("/"!=a.slice(-1)&&(a+="/"),b=this.files[a]),b&&!b.dir)delete this.files[a];else for(var c=this.filter(function(b,c){return c.name.slice(0,a.length)===a}),d=0;d<c.length;d++)delete this.files[c[d].name];return this},generate:function(a){a=t(a||{},{base64:!0,compression:"STORE",type:"base64",comment:null}),d.checkSupport(a.type);var b,c,e=[],g=0,j=0,k=d.transformTo("string",this.utf8encode(a.comment||this.comment||""));for(var l in this.files)if(this.files.hasOwnProperty(l)){var o=this.files[l],p=o.options.compression||a.compression.toUpperCase(),q=i[p];if(!q)throw new Error(p+" is not a valid compression method !");var r=y.call(this,o,q),u=z.call(this,l,o,r,g);g+=u.fileRecord.length+r.compressedSize,j+=u.dirRecord.length,e.push(u)}var v="";v=f.CENTRAL_DIRECTORY_END+"\x00\x00\x00\x00"+s(e.length,2)+s(e.length,2)+s(j,4)+s(g,4)+s(k.length,2)+k;var w=a.type.toLowerCase();for(b="uint8array"===w||"arraybuffer"===w||"blob"===w||"nodebuffer"===w?new n(g+j+v.length):new m(g+j+v.length),c=0;c<e.length;c++)b.append(e[c].fileRecord),b.append(e[c].compressedObject.compressedContent);for(c=0;c<e.length;c++)b.append(e[c].dirRecord);b.append(v);var x=b.finalize();switch(a.type.toLowerCase()){case"uint8array":case"arraybuffer":case"nodebuffer":return d.transformTo(a.type.toLowerCase(),x);case"blob":return d.arrayBuffer2Blob(d.transformTo("arraybuffer",x));case"base64":return a.base64?h.encode(x):x;default:return x}},crc32:function(a,b){return e(a,b)},utf8encode:function(a){return d.transformTo("string",l.utf8encode(a))},utf8decode:function(a){return l.utf8decode(a)}};b.exports=A},{"./base64":1,"./compressedObject":2,"./compressions":3,"./crc32":4,"./defaults":6,"./nodeBuffer":11,"./signature":14,"./stringWriter":16,"./support":17,"./uint8ArrayWriter":19,"./utf8":20,"./utils":21}],14:[function(a,b,c){"use strict";c.LOCAL_FILE_HEADER="PK",c.CENTRAL_FILE_HEADER="PK",c.CENTRAL_DIRECTORY_END="PK",c.ZIP64_CENTRAL_DIRECTORY_LOCATOR="PK",c.ZIP64_CENTRAL_DIRECTORY_END="PK",c.DATA_DESCRIPTOR="PK\b"},{}],15:[function(a,b){"use strict";function c(a,b){this.data=a,b||(this.data=e.string2binary(this.data)),this.length=this.data.length,this.index=0}var d=a("./dataReader"),e=a("./utils");c.prototype=new d,c.prototype.byteAt=function(a){return this.data.charCodeAt(a)},c.prototype.lastIndexOfSignature=function(a){return this.data.lastIndexOf(a)},c.prototype.readData=function(a){this.checkOffset(a);var b=this.data.slice(this.index,this.index+a);return this.index+=a,b},b.exports=c},{"./dataReader":5,"./utils":21}],16:[function(a,b){"use strict";var c=a("./utils"),d=function(){this.data=[]};d.prototype={append:function(a){a=c.transformTo("string",a),this.data.push(a)},finalize:function(){return this.data.join("")}},b.exports=d},{"./utils":21}],17:[function(a,b,c){(function(a){"use strict";if(c.base64=!0,c.array=!0,c.string=!0,c.arraybuffer="undefined"!=typeof ArrayBuffer&&"undefined"!=typeof Uint8Array,c.nodebuffer="undefined"!=typeof a,c.uint8array="undefined"!=typeof Uint8Array,"undefined"==typeof ArrayBuffer)c.blob=!1;else{var b=new ArrayBuffer(0);try{c.blob=0===new Blob([b],{type:"application/zip"}).size}catch(d){try{var e=window.BlobBuilder||window.WebKitBlobBuilder||window.MozBlobBuilder||window.MSBlobBuilder,f=new e;f.append(b),c.blob=0===f.getBlob("application/zip").size}catch(d){c.blob=!1}}}}).call(this,"undefined"!=typeof Buffer?Buffer:void 0)},{}],18:[function(a,b){"use strict";function c(a){a&&(this.data=a,this.length=this.data.length,this.index=0)}var d=a("./dataReader");c.prototype=new d,c.prototype.byteAt=function(a){return this.data[a]},c.prototype.lastIndexOfSignature=function(a){for(var b=a.charCodeAt(0),c=a.charCodeAt(1),d=a.charCodeAt(2),e=a.charCodeAt(3),f=this.length-4;f>=0;--f)if(this.data[f]===b&&this.data[f+1]===c&&this.data[f+2]===d&&this.data[f+3]===e)return f;return-1},c.prototype.readData=function(a){if(this.checkOffset(a),0===a)return new Uint8Array(0);var b=this.data.subarray(this.index,this.index+a);return this.index+=a,b},b.exports=c},{"./dataReader":5}],19:[function(a,b){"use strict";var c=a("./utils"),d=function(a){this.data=new Uint8Array(a),this.index=0};d.prototype={append:function(a){0!==a.length&&(a=c.transformTo("uint8array",a),this.data.set(a,this.index),this.index+=a.length)},finalize:function(){return this.data}},b.exports=d},{"./utils":21}],20:[function(a,b,c){"use strict";for(var d=a("./utils"),e=a("./support"),f=a("./nodeBuffer"),g=new Array(256),h=0;256>h;h++)g[h]=h>=252?6:h>=248?5:h>=240?4:h>=224?3:h>=192?2:1;g[254]=g[254]=1;var i=function(a){var b,c,d,f,g,h=a.length,i=0;for(f=0;h>f;f++)c=a.charCodeAt(f),55296===(64512&c)&&h>f+1&&(d=a.charCodeAt(f+1),56320===(64512&d)&&(c=65536+(c-55296<<10)+(d-56320),f++)),i+=128>c?1:2048>c?2:65536>c?3:4;for(b=e.uint8array?new Uint8Array(i):new Array(i),g=0,f=0;i>g;f++)c=a.charCodeAt(f),55296===(64512&c)&&h>f+1&&(d=a.charCodeAt(f+1),56320===(64512&d)&&(c=65536+(c-55296<<10)+(d-56320),f++)),128>c?b[g++]=c:2048>c?(b[g++]=192|c>>>6,b[g++]=128|63&c):65536>c?(b[g++]=224|c>>>12,b[g++]=128|c>>>6&63,b[g++]=128|63&c):(b[g++]=240|c>>>18,b[g++]=128|c>>>12&63,b[g++]=128|c>>>6&63,b[g++]=128|63&c);return b},j=function(a,b){var c;for(b=b||a.length,b>a.length&&(b=a.length),c=b-1;c>=0&&128===(192&a[c]);)c--;return 0>c?b:0===c?b:c+g[a[c]]>b?c:b},k=function(a){var b,c,e,f,h=a.length,i=new Array(2*h);for(c=0,b=0;h>b;)if(e=a[b++],128>e)i[c++]=e;else if(f=g[e],f>4)i[c++]=65533,b+=f-1;else{for(e&=2===f?31:3===f?15:7;f>1&&h>b;)e=e<<6|63&a[b++],f--;f>1?i[c++]=65533:65536>e?i[c++]=e:(e-=65536,i[c++]=55296|e>>10&1023,i[c++]=56320|1023&e)}return i.length!==c&&(i.subarray?i=i.subarray(0,c):i.length=c),d.applyFromCharCode(i)};c.utf8encode=function(a){return e.nodebuffer?f(a,"utf-8"):i(a)},c.utf8decode=function(a){if(e.nodebuffer)return d.transformTo("nodebuffer",a).toString("utf-8");a=d.transformTo(e.uint8array?"uint8array":"array",a);for(var b=[],c=0,f=a.length,g=65536;f>c;){var h=j(a,Math.min(c+g,f));b.push(e.uint8array?k(a.subarray(c,h)):k(a.slice(c,h))),c=h}return b.join("")}},{"./nodeBuffer":11,"./support":17,"./utils":21}],21:[function(a,b,c){"use strict";function d(a){return a}function e(a,b){for(var c=0;c<a.length;++c)b[c]=255&a.charCodeAt(c);return b}function f(a){var b=65536,d=[],e=a.length,f=c.getTypeOf(a),g=0,h=!0;try{switch(f){case"uint8array":String.fromCharCode.apply(null,new Uint8Array(0));break;case"nodebuffer":String.fromCharCode.apply(null,j(0))}}catch(i){h=!1}if(!h){for(var k="",l=0;l<a.length;l++)k+=String.fromCharCode(a[l]);return k}for(;e>g&&b>1;)try{d.push("array"===f||"nodebuffer"===f?String.fromCharCode.apply(null,a.slice(g,Math.min(g+b,e))):String.fromCharCode.apply(null,a.subarray(g,Math.min(g+b,e)))),g+=b}catch(i){b=Math.floor(b/2)}return d.join("")}function g(a,b){for(var c=0;c<a.length;c++)b[c]=a[c];return b}var h=a("./support"),i=a("./compressions"),j=a("./nodeBuffer");c.string2binary=function(a){for(var b="",c=0;c<a.length;c++)b+=String.fromCharCode(255&a.charCodeAt(c));return b},c.arrayBuffer2Blob=function(a){c.checkSupport("blob");try{return new Blob([a],{type:"application/zip"})}catch(b){try{var d=window.BlobBuilder||window.WebKitBlobBuilder||window.MozBlobBuilder||window.MSBlobBuilder,e=new d;return e.append(a),e.getBlob("application/zip")}catch(b){throw new Error("Bug : can't construct the Blob.")}}},c.applyFromCharCode=f;var k={};k.string={string:d,array:function(a){return e(a,new Array(a.length))},arraybuffer:function(a){return k.string.uint8array(a).buffer},uint8array:function(a){return e(a,new Uint8Array(a.length))},nodebuffer:function(a){return e(a,j(a.length))}},k.array={string:f,array:d,arraybuffer:function(a){return new Uint8Array(a).buffer},uint8array:function(a){return new Uint8Array(a)},nodebuffer:function(a){return j(a)}},k.arraybuffer={string:function(a){return f(new Uint8Array(a))},array:function(a){return g(new Uint8Array(a),new Array(a.byteLength))},arraybuffer:d,uint8array:function(a){return new Uint8Array(a)},nodebuffer:function(a){return j(new Uint8Array(a))}},k.uint8array={string:f,array:function(a){return g(a,new Array(a.length))},arraybuffer:function(a){return a.buffer},uint8array:d,nodebuffer:function(a){return j(a)}},k.nodebuffer={string:f,array:function(a){return g(a,new Array(a.length))},arraybuffer:function(a){return k.nodebuffer.uint8array(a).buffer},uint8array:function(a){return g(a,new Uint8Array(a.length))},nodebuffer:d},c.transformTo=function(a,b){if(b||(b=""),!a)return b;c.checkSupport(a);var d=c.getTypeOf(b),e=k[d][a](b);return e},c.getTypeOf=function(a){return"string"==typeof a?"string":"[object Array]"===Object.prototype.toString.call(a)?"array":h.nodebuffer&&j.test(a)?"nodebuffer":h.uint8array&&a instanceof Uint8Array?"uint8array":h.arraybuffer&&a instanceof ArrayBuffer?"arraybuffer":void 0},c.checkSupport=function(a){var b=h[a.toLowerCase()];if(!b)throw new Error(a+" is not supported by this browser")},c.MAX_VALUE_16BITS=65535,c.MAX_VALUE_32BITS=-1,c.pretty=function(a){var b,c,d="";for(c=0;c<(a||"").length;c++)b=a.charCodeAt(c),d+="\\x"+(16>b?"0":"")+b.toString(16).toUpperCase();return d},c.findCompression=function(a){for(var b in i)if(i.hasOwnProperty(b)&&i[b].magic===a)return i[b];return null},c.isRegExp=function(a){return"[object RegExp]"===Object.prototype.toString.call(a)}},{"./compressions":3,"./nodeBuffer":11,"./support":17}],22:[function(a,b){"use strict";function c(a,b){this.files=[],this.loadOptions=b,a&&this.load(a)}var d=a("./stringReader"),e=a("./nodeBufferReader"),f=a("./uint8ArrayReader"),g=a("./utils"),h=a("./signature"),i=a("./zipEntry"),j=a("./support"),k=a("./object");c.prototype={checkSignature:function(a){var b=this.reader.readString(4);if(b!==a)throw new Error("Corrupted zip or bug : unexpected signature ("+g.pretty(b)+", expected "+g.pretty(a)+")")},readBlockEndOfCentral:function(){this.diskNumber=this.reader.readInt(2),this.diskWithCentralDirStart=this.reader.readInt(2),this.centralDirRecordsOnThisDisk=this.reader.readInt(2),this.centralDirRecords=this.reader.readInt(2),this.centralDirSize=this.reader.readInt(4),this.centralDirOffset=this.reader.readInt(4),this.zipCommentLength=this.reader.readInt(2),this.zipComment=this.reader.readString(this.zipCommentLength),this.zipComment=k.utf8decode(this.zipComment)},readBlockZip64EndOfCentral:function(){this.zip64EndOfCentralSize=this.reader.readInt(8),this.versionMadeBy=this.reader.readString(2),this.versionNeeded=this.reader.readInt(2),this.diskNumber=this.reader.readInt(4),this.diskWithCentralDirStart=this.reader.readInt(4),this.centralDirRecordsOnThisDisk=this.reader.readInt(8),this.centralDirRecords=this.reader.readInt(8),this.centralDirSize=this.reader.readInt(8),this.centralDirOffset=this.reader.readInt(8),this.zip64ExtensibleData={};for(var a,b,c,d=this.zip64EndOfCentralSize-44,e=0;d>e;)a=this.reader.readInt(2),b=this.reader.readInt(4),c=this.reader.readString(b),this.zip64ExtensibleData[a]={id:a,length:b,value:c}},readBlockZip64EndOfCentralLocator:function(){if(this.diskWithZip64CentralDirStart=this.reader.readInt(4),this.relativeOffsetEndOfZip64CentralDir=this.reader.readInt(8),this.disksCount=this.reader.readInt(4),this.disksCount>1)throw new Error("Multi-volumes zip are not supported")},readLocalFiles:function(){var a,b;for(a=0;a<this.files.length;a++)b=this.files[a],this.reader.setIndex(b.localHeaderOffset),this.checkSignature(h.LOCAL_FILE_HEADER),b.readLocalPart(this.reader),b.handleUTF8()},readCentralDir:function(){var a;for(this.reader.setIndex(this.centralDirOffset);this.reader.readString(4)===h.CENTRAL_FILE_HEADER;)a=new i({zip64:this.zip64},this.loadOptions),a.readCentralPart(this.reader),this.files.push(a)},readEndOfCentral:function(){var a=this.reader.lastIndexOfSignature(h.CENTRAL_DIRECTORY_END);if(-1===a)throw new Error("Corrupted zip : can't find end of central directory");if(this.reader.setIndex(a),this.checkSignature(h.CENTRAL_DIRECTORY_END),this.readBlockEndOfCentral(),this.diskNumber===g.MAX_VALUE_16BITS||this.diskWithCentralDirStart===g.MAX_VALUE_16BITS||this.centralDirRecordsOnThisDisk===g.MAX_VALUE_16BITS||this.centralDirRecords===g.MAX_VALUE_16BITS||this.centralDirSize===g.MAX_VALUE_32BITS||this.centralDirOffset===g.MAX_VALUE_32BITS){if(this.zip64=!0,a=this.reader.lastIndexOfSignature(h.ZIP64_CENTRAL_DIRECTORY_LOCATOR),-1===a)throw new Error("Corrupted zip : can't find the ZIP64 end of central directory locator");this.reader.setIndex(a),this.checkSignature(h.ZIP64_CENTRAL_DIRECTORY_LOCATOR),this.readBlockZip64EndOfCentralLocator(),this.reader.setIndex(this.relativeOffsetEndOfZip64CentralDir),this.checkSignature(h.ZIP64_CENTRAL_DIRECTORY_END),this.readBlockZip64EndOfCentral()}},prepareReader:function(a){var b=g.getTypeOf(a);this.reader="string"!==b||j.uint8array?"nodebuffer"===b?new e(a):new f(g.transformTo("uint8array",a)):new d(a,this.loadOptions.optimizedBinaryString)},load:function(a){this.prepareReader(a),this.readEndOfCentral(),this.readCentralDir(),this.readLocalFiles()}},b.exports=c},{"./nodeBufferReader":12,"./object":13,"./signature":14,"./stringReader":15,"./support":17,"./uint8ArrayReader":18,"./utils":21,"./zipEntry":23}],23:[function(a,b){"use strict";function c(a,b){this.options=a,this.loadOptions=b}var d=a("./stringReader"),e=a("./utils"),f=a("./compressedObject"),g=a("./object");c.prototype={isEncrypted:function(){return 1===(1&this.bitFlag)},useUTF8:function(){return 2048===(2048&this.bitFlag)},prepareCompressedContent:function(a,b,c){return function(){var d=a.index;a.setIndex(b);var e=a.readData(c);return a.setIndex(d),e}},prepareContent:function(a,b,c,d,f){return function(){var a=e.transformTo(d.uncompressInputType,this.getCompressedContent()),b=d.uncompress(a);if(b.length!==f)throw new Error("Bug : uncompressed data size mismatch");return b}},readLocalPart:function(a){var b,c;if(a.skip(22),this.fileNameLength=a.readInt(2),c=a.readInt(2),this.fileName=a.readString(this.fileNameLength),a.skip(c),-1==this.compressedSize||-1==this.uncompressedSize)throw new Error("Bug or corrupted zip : didn't get enough informations from the central directory (compressedSize == -1 || uncompressedSize == -1)");if(b=e.findCompression(this.compressionMethod),null===b)throw new Error("Corrupted zip : compression "+e.pretty(this.compressionMethod)+" unknown (inner file : "+this.fileName+")");if(this.decompressed=new f,this.decompressed.compressedSize=this.compressedSize,this.decompressed.uncompressedSize=this.uncompressedSize,this.decompressed.crc32=this.crc32,this.decompressed.compressionMethod=this.compressionMethod,this.decompressed.getCompressedContent=this.prepareCompressedContent(a,a.index,this.compressedSize,b),this.decompressed.getContent=this.prepareContent(a,a.index,this.compressedSize,b,this.uncompressedSize),this.loadOptions.checkCRC32&&(this.decompressed=e.transformTo("string",this.decompressed.getContent()),g.crc32(this.decompressed)!==this.crc32))throw new Error("Corrupted zip : CRC32 mismatch")},readCentralPart:function(a){if(this.versionMadeBy=a.readString(2),this.versionNeeded=a.readInt(2),this.bitFlag=a.readInt(2),this.compressionMethod=a.readString(2),this.date=a.readDate(),this.crc32=a.readInt(4),this.compressedSize=a.readInt(4),this.uncompressedSize=a.readInt(4),this.fileNameLength=a.readInt(2),this.extraFieldsLength=a.readInt(2),this.fileCommentLength=a.readInt(2),this.diskNumberStart=a.readInt(2),this.internalFileAttributes=a.readInt(2),this.externalFileAttributes=a.readInt(4),this.localHeaderOffset=a.readInt(4),this.isEncrypted())throw new Error("Encrypted zip are not supported");this.fileName=a.readString(this.fileNameLength),this.readExtraFields(a),this.parseZIP64ExtraField(a),this.fileComment=a.readString(this.fileCommentLength),this.dir=16&this.externalFileAttributes?!0:!1},parseZIP64ExtraField:function(){if(this.extraFields[1]){var a=new d(this.extraFields[1].value);this.uncompressedSize===e.MAX_VALUE_32BITS&&(this.uncompressedSize=a.readInt(8)),this.compressedSize===e.MAX_VALUE_32BITS&&(this.compressedSize=a.readInt(8)),this.localHeaderOffset===e.MAX_VALUE_32BITS&&(this.localHeaderOffset=a.readInt(8)),this.diskNumberStart===e.MAX_VALUE_32BITS&&(this.diskNumberStart=a.readInt(4))}},readExtraFields:function(a){var b,c,d,e=a.index;for(this.extraFields=this.extraFields||{};a.index<e+this.extraFieldsLength;)b=a.readInt(2),c=a.readInt(2),d=a.readString(c),this.extraFields[b]={id:b,length:c,value:d}},handleUTF8:function(){if(this.useUTF8())this.fileName=g.utf8decode(this.fileName),this.fileComment=g.utf8decode(this.fileComment);else{var a=this.findExtraFieldUnicodePath();null!==a&&(this.fileName=a);var b=this.findExtraFieldUnicodeComment();null!==b&&(this.fileComment=b)}},findExtraFieldUnicodePath:function(){var a=this.extraFields[28789];if(a){var b=new d(a.value);return 1!==b.readInt(1)?null:g.crc32(this.fileName)!==b.readInt(4)?null:g.utf8decode(b.readString(a.length-5))}return null},findExtraFieldUnicodeComment:function(){var a=this.extraFields[25461];if(a){var b=new d(a.value);return 1!==b.readInt(1)?null:g.crc32(this.fileComment)!==b.readInt(4)?null:g.utf8decode(b.readString(a.length-5))}return null}},b.exports=c},{"./compressedObject":2,"./object":13,"./stringReader":15,"./utils":21}],24:[function(a,b){"use strict";var c=a("./lib/utils/common").assign,d=a("./lib/deflate"),e=a("./lib/inflate"),f=a("./lib/zlib/constants"),g={};c(g,d,e,f),b.exports=g},{"./lib/deflate":25,"./lib/inflate":26,"./lib/utils/common":27,"./lib/zlib/constants":30}],25:[function(a,b,c){"use strict";function d(a,b){var c=new s(b);if(c.push(a,!0),c.err)throw c.msg;return c.result}function e(a,b){return b=b||{},b.raw=!0,d(a,b)}function f(a,b){return b=b||{},b.gzip=!0,d(a,b)}var g=a("./zlib/deflate.js"),h=a("./utils/common"),i=a("./utils/strings"),j=a("./zlib/messages"),k=a("./zlib/zstream"),l=0,m=4,n=0,o=1,p=-1,q=0,r=8,s=function(a){this.options=h.assign({level:p,method:r,chunkSize:16384,windowBits:15,memLevel:8,strategy:q,to:""},a||{});var b=this.options;b.raw&&b.windowBits>0?b.windowBits=-b.windowBits:b.gzip&&b.windowBits>0&&b.windowBits<16&&(b.windowBits+=16),this.err=0,this.msg="",this.ended=!1,this.chunks=[],this.strm=new k,this.strm.avail_out=0;var c=g.deflateInit2(this.strm,b.level,b.method,b.windowBits,b.memLevel,b.strategy);if(c!==n)throw new Error(j[c]);b.header&&g.deflateSetHeader(this.strm,b.header)
};s.prototype.push=function(a,b){var c,d,e=this.strm,f=this.options.chunkSize;if(this.ended)return!1;d=b===~~b?b:b===!0?m:l,e.input="string"==typeof a?i.string2buf(a):a,e.next_in=0,e.avail_in=e.input.length;do{if(0===e.avail_out&&(e.output=new h.Buf8(f),e.next_out=0,e.avail_out=f),c=g.deflate(e,d),c!==o&&c!==n)return this.onEnd(c),this.ended=!0,!1;(0===e.avail_out||0===e.avail_in&&d===m)&&this.onData("string"===this.options.to?i.buf2binstring(h.shrinkBuf(e.output,e.next_out)):h.shrinkBuf(e.output,e.next_out))}while((e.avail_in>0||0===e.avail_out)&&c!==o);return d===m?(c=g.deflateEnd(this.strm),this.onEnd(c),this.ended=!0,c===n):!0},s.prototype.onData=function(a){this.chunks.push(a)},s.prototype.onEnd=function(a){a===n&&(this.result="string"===this.options.to?this.chunks.join(""):h.flattenChunks(this.chunks)),this.chunks=[],this.err=a,this.msg=this.strm.msg},c.Deflate=s,c.deflate=d,c.deflateRaw=e,c.gzip=f},{"./utils/common":27,"./utils/strings":28,"./zlib/deflate.js":32,"./zlib/messages":37,"./zlib/zstream":39}],26:[function(a,b,c){"use strict";function d(a,b){var c=new m(b);if(c.push(a,!0),c.err)throw c.msg;return c.result}function e(a,b){return b=b||{},b.raw=!0,d(a,b)}var f=a("./zlib/inflate.js"),g=a("./utils/common"),h=a("./utils/strings"),i=a("./zlib/constants"),j=a("./zlib/messages"),k=a("./zlib/zstream"),l=a("./zlib/gzheader"),m=function(a){this.options=g.assign({chunkSize:16384,windowBits:0,to:""},a||{});var b=this.options;b.raw&&b.windowBits>=0&&b.windowBits<16&&(b.windowBits=-b.windowBits,0===b.windowBits&&(b.windowBits=-15)),!(b.windowBits>=0&&b.windowBits<16)||a&&a.windowBits||(b.windowBits+=32),b.windowBits>15&&b.windowBits<48&&0===(15&b.windowBits)&&(b.windowBits|=15),this.err=0,this.msg="",this.ended=!1,this.chunks=[],this.strm=new k,this.strm.avail_out=0;var c=f.inflateInit2(this.strm,b.windowBits);if(c!==i.Z_OK)throw new Error(j[c]);this.header=new l,f.inflateGetHeader(this.strm,this.header)};m.prototype.push=function(a,b){var c,d,e,j,k,l=this.strm,m=this.options.chunkSize;if(this.ended)return!1;d=b===~~b?b:b===!0?i.Z_FINISH:i.Z_NO_FLUSH,l.input="string"==typeof a?h.binstring2buf(a):a,l.next_in=0,l.avail_in=l.input.length;do{if(0===l.avail_out&&(l.output=new g.Buf8(m),l.next_out=0,l.avail_out=m),c=f.inflate(l,i.Z_NO_FLUSH),c!==i.Z_STREAM_END&&c!==i.Z_OK)return this.onEnd(c),this.ended=!0,!1;l.next_out&&(0===l.avail_out||c===i.Z_STREAM_END||0===l.avail_in&&d===i.Z_FINISH)&&("string"===this.options.to?(e=h.utf8border(l.output,l.next_out),j=l.next_out-e,k=h.buf2string(l.output,e),l.next_out=j,l.avail_out=m-j,j&&g.arraySet(l.output,l.output,e,j,0),this.onData(k)):this.onData(g.shrinkBuf(l.output,l.next_out)))}while(l.avail_in>0&&c!==i.Z_STREAM_END);return c===i.Z_STREAM_END&&(d=i.Z_FINISH),d===i.Z_FINISH?(c=f.inflateEnd(this.strm),this.onEnd(c),this.ended=!0,c===i.Z_OK):!0},m.prototype.onData=function(a){this.chunks.push(a)},m.prototype.onEnd=function(a){a===i.Z_OK&&(this.result="string"===this.options.to?this.chunks.join(""):g.flattenChunks(this.chunks)),this.chunks=[],this.err=a,this.msg=this.strm.msg},c.Inflate=m,c.inflate=d,c.inflateRaw=e,c.ungzip=d},{"./utils/common":27,"./utils/strings":28,"./zlib/constants":30,"./zlib/gzheader":33,"./zlib/inflate.js":35,"./zlib/messages":37,"./zlib/zstream":39}],27:[function(a,b,c){"use strict";var d="undefined"!=typeof Uint8Array&&"undefined"!=typeof Uint16Array&&"undefined"!=typeof Int32Array;c.assign=function(a){for(var b=Array.prototype.slice.call(arguments,1);b.length;){var c=b.shift();if(c){if("object"!=typeof c)throw new TypeError(c+"must be non-object");for(var d in c)c.hasOwnProperty(d)&&(a[d]=c[d])}}return a},c.shrinkBuf=function(a,b){return a.length===b?a:a.subarray?a.subarray(0,b):(a.length=b,a)};var e={arraySet:function(a,b,c,d,e){if(b.subarray&&a.subarray)return void a.set(b.subarray(c,c+d),e);for(var f=0;d>f;f++)a[e+f]=b[c+f]},flattenChunks:function(a){var b,c,d,e,f,g;for(d=0,b=0,c=a.length;c>b;b++)d+=a[b].length;for(g=new Uint8Array(d),e=0,b=0,c=a.length;c>b;b++)f=a[b],g.set(f,e),e+=f.length;return g}},f={arraySet:function(a,b,c,d,e){for(var f=0;d>f;f++)a[e+f]=b[c+f]},flattenChunks:function(a){return[].concat.apply([],a)}};c.setTyped=function(a){a?(c.Buf8=Uint8Array,c.Buf16=Uint16Array,c.Buf32=Int32Array,c.assign(c,e)):(c.Buf8=Array,c.Buf16=Array,c.Buf32=Array,c.assign(c,f))},c.setTyped(d)},{}],28:[function(a,b,c){"use strict";function d(a,b){if(65537>b&&(a.subarray&&g||!a.subarray&&f))return String.fromCharCode.apply(null,e.shrinkBuf(a,b));for(var c="",d=0;b>d;d++)c+=String.fromCharCode(a[d]);return c}var e=a("./common"),f=!0,g=!0;try{String.fromCharCode.apply(null,[0])}catch(h){f=!1}try{String.fromCharCode.apply(null,new Uint8Array(1))}catch(h){g=!1}for(var i=new e.Buf8(256),j=0;256>j;j++)i[j]=j>=252?6:j>=248?5:j>=240?4:j>=224?3:j>=192?2:1;i[254]=i[254]=1,c.string2buf=function(a){var b,c,d,f,g,h=a.length,i=0;for(f=0;h>f;f++)c=a.charCodeAt(f),55296===(64512&c)&&h>f+1&&(d=a.charCodeAt(f+1),56320===(64512&d)&&(c=65536+(c-55296<<10)+(d-56320),f++)),i+=128>c?1:2048>c?2:65536>c?3:4;for(b=new e.Buf8(i),g=0,f=0;i>g;f++)c=a.charCodeAt(f),55296===(64512&c)&&h>f+1&&(d=a.charCodeAt(f+1),56320===(64512&d)&&(c=65536+(c-55296<<10)+(d-56320),f++)),128>c?b[g++]=c:2048>c?(b[g++]=192|c>>>6,b[g++]=128|63&c):65536>c?(b[g++]=224|c>>>12,b[g++]=128|c>>>6&63,b[g++]=128|63&c):(b[g++]=240|c>>>18,b[g++]=128|c>>>12&63,b[g++]=128|c>>>6&63,b[g++]=128|63&c);return b},c.buf2binstring=function(a){return d(a,a.length)},c.binstring2buf=function(a){for(var b=new e.Buf8(a.length),c=0,d=b.length;d>c;c++)b[c]=a.charCodeAt(c);return b},c.buf2string=function(a,b){var c,e,f,g,h=b||a.length,j=new Array(2*h);for(e=0,c=0;h>c;)if(f=a[c++],128>f)j[e++]=f;else if(g=i[f],g>4)j[e++]=65533,c+=g-1;else{for(f&=2===g?31:3===g?15:7;g>1&&h>c;)f=f<<6|63&a[c++],g--;g>1?j[e++]=65533:65536>f?j[e++]=f:(f-=65536,j[e++]=55296|f>>10&1023,j[e++]=56320|1023&f)}return d(j,e)},c.utf8border=function(a,b){var c;for(b=b||a.length,b>a.length&&(b=a.length),c=b-1;c>=0&&128===(192&a[c]);)c--;return 0>c?b:0===c?b:c+i[a[c]]>b?c:b}},{"./common":27}],29:[function(a,b){"use strict";function c(a,b,c,d){for(var e=65535&a|0,f=a>>>16&65535|0,g=0;0!==c;){g=c>2e3?2e3:c,c-=g;do e=e+b[d++]|0,f=f+e|0;while(--g);e%=65521,f%=65521}return e|f<<16|0}b.exports=c},{}],30:[function(a,b){b.exports={Z_NO_FLUSH:0,Z_PARTIAL_FLUSH:1,Z_SYNC_FLUSH:2,Z_FULL_FLUSH:3,Z_FINISH:4,Z_BLOCK:5,Z_TREES:6,Z_OK:0,Z_STREAM_END:1,Z_NEED_DICT:2,Z_ERRNO:-1,Z_STREAM_ERROR:-2,Z_DATA_ERROR:-3,Z_BUF_ERROR:-5,Z_NO_COMPRESSION:0,Z_BEST_SPEED:1,Z_BEST_COMPRESSION:9,Z_DEFAULT_COMPRESSION:-1,Z_FILTERED:1,Z_HUFFMAN_ONLY:2,Z_RLE:3,Z_FIXED:4,Z_DEFAULT_STRATEGY:0,Z_BINARY:0,Z_TEXT:1,Z_UNKNOWN:2,Z_DEFLATED:8}},{}],31:[function(a,b){"use strict";function c(){for(var a,b=[],c=0;256>c;c++){a=c;for(var d=0;8>d;d++)a=1&a?3988292384^a>>>1:a>>>1;b[c]=a}return b}function d(a,b,c,d){var f=e,g=d+c;a=-1^a;for(var h=d;g>h;h++)a=a>>>8^f[255&(a^b[h])];return-1^a}var e=c();b.exports=d},{}],32:[function(a,b,c){"use strict";function d(a,b){return a.msg=G[b],b}function e(a){return(a<<1)-(a>4?9:0)}function f(a){for(var b=a.length;--b>=0;)a[b]=0}function g(a){var b=a.state,c=b.pending;c>a.avail_out&&(c=a.avail_out),0!==c&&(C.arraySet(a.output,b.pending_buf,b.pending_out,c,a.next_out),a.next_out+=c,b.pending_out+=c,a.total_out+=c,a.avail_out-=c,b.pending-=c,0===b.pending&&(b.pending_out=0))}function h(a,b){D._tr_flush_block(a,a.block_start>=0?a.block_start:-1,a.strstart-a.block_start,b),a.block_start=a.strstart,g(a.strm)}function i(a,b){a.pending_buf[a.pending++]=b}function j(a,b){a.pending_buf[a.pending++]=b>>>8&255,a.pending_buf[a.pending++]=255&b}function k(a,b,c,d){var e=a.avail_in;return e>d&&(e=d),0===e?0:(a.avail_in-=e,C.arraySet(b,a.input,a.next_in,e,c),1===a.state.wrap?a.adler=E(a.adler,b,e,c):2===a.state.wrap&&(a.adler=F(a.adler,b,e,c)),a.next_in+=e,a.total_in+=e,e)}function l(a,b){var c,d,e=a.max_chain_length,f=a.strstart,g=a.prev_length,h=a.nice_match,i=a.strstart>a.w_size-jb?a.strstart-(a.w_size-jb):0,j=a.window,k=a.w_mask,l=a.prev,m=a.strstart+ib,n=j[f+g-1],o=j[f+g];a.prev_length>=a.good_match&&(e>>=2),h>a.lookahead&&(h=a.lookahead);do if(c=b,j[c+g]===o&&j[c+g-1]===n&&j[c]===j[f]&&j[++c]===j[f+1]){f+=2,c++;do;while(j[++f]===j[++c]&&j[++f]===j[++c]&&j[++f]===j[++c]&&j[++f]===j[++c]&&j[++f]===j[++c]&&j[++f]===j[++c]&&j[++f]===j[++c]&&j[++f]===j[++c]&&m>f);if(d=ib-(m-f),f=m-ib,d>g){if(a.match_start=b,g=d,d>=h)break;n=j[f+g-1],o=j[f+g]}}while((b=l[b&k])>i&&0!==--e);return g<=a.lookahead?g:a.lookahead}function m(a){var b,c,d,e,f,g=a.w_size;do{if(e=a.window_size-a.lookahead-a.strstart,a.strstart>=g+(g-jb)){C.arraySet(a.window,a.window,g,g,0),a.match_start-=g,a.strstart-=g,a.block_start-=g,c=a.hash_size,b=c;do d=a.head[--b],a.head[b]=d>=g?d-g:0;while(--c);c=g,b=c;do d=a.prev[--b],a.prev[b]=d>=g?d-g:0;while(--c);e+=g}if(0===a.strm.avail_in)break;if(c=k(a.strm,a.window,a.strstart+a.lookahead,e),a.lookahead+=c,a.lookahead+a.insert>=hb)for(f=a.strstart-a.insert,a.ins_h=a.window[f],a.ins_h=(a.ins_h<<a.hash_shift^a.window[f+1])&a.hash_mask;a.insert&&(a.ins_h=(a.ins_h<<a.hash_shift^a.window[f+hb-1])&a.hash_mask,a.prev[f&a.w_mask]=a.head[a.ins_h],a.head[a.ins_h]=f,f++,a.insert--,!(a.lookahead+a.insert<hb)););}while(a.lookahead<jb&&0!==a.strm.avail_in)}function n(a,b){var c=65535;for(c>a.pending_buf_size-5&&(c=a.pending_buf_size-5);;){if(a.lookahead<=1){if(m(a),0===a.lookahead&&b===H)return sb;if(0===a.lookahead)break}a.strstart+=a.lookahead,a.lookahead=0;var d=a.block_start+c;if((0===a.strstart||a.strstart>=d)&&(a.lookahead=a.strstart-d,a.strstart=d,h(a,!1),0===a.strm.avail_out))return sb;if(a.strstart-a.block_start>=a.w_size-jb&&(h(a,!1),0===a.strm.avail_out))return sb}return a.insert=0,b===K?(h(a,!0),0===a.strm.avail_out?ub:vb):a.strstart>a.block_start&&(h(a,!1),0===a.strm.avail_out)?sb:sb}function o(a,b){for(var c,d;;){if(a.lookahead<jb){if(m(a),a.lookahead<jb&&b===H)return sb;if(0===a.lookahead)break}if(c=0,a.lookahead>=hb&&(a.ins_h=(a.ins_h<<a.hash_shift^a.window[a.strstart+hb-1])&a.hash_mask,c=a.prev[a.strstart&a.w_mask]=a.head[a.ins_h],a.head[a.ins_h]=a.strstart),0!==c&&a.strstart-c<=a.w_size-jb&&(a.match_length=l(a,c)),a.match_length>=hb)if(d=D._tr_tally(a,a.strstart-a.match_start,a.match_length-hb),a.lookahead-=a.match_length,a.match_length<=a.max_lazy_match&&a.lookahead>=hb){a.match_length--;do a.strstart++,a.ins_h=(a.ins_h<<a.hash_shift^a.window[a.strstart+hb-1])&a.hash_mask,c=a.prev[a.strstart&a.w_mask]=a.head[a.ins_h],a.head[a.ins_h]=a.strstart;while(0!==--a.match_length);a.strstart++}else a.strstart+=a.match_length,a.match_length=0,a.ins_h=a.window[a.strstart],a.ins_h=(a.ins_h<<a.hash_shift^a.window[a.strstart+1])&a.hash_mask;else d=D._tr_tally(a,0,a.window[a.strstart]),a.lookahead--,a.strstart++;if(d&&(h(a,!1),0===a.strm.avail_out))return sb}return a.insert=a.strstart<hb-1?a.strstart:hb-1,b===K?(h(a,!0),0===a.strm.avail_out?ub:vb):a.last_lit&&(h(a,!1),0===a.strm.avail_out)?sb:tb}function p(a,b){for(var c,d,e;;){if(a.lookahead<jb){if(m(a),a.lookahead<jb&&b===H)return sb;if(0===a.lookahead)break}if(c=0,a.lookahead>=hb&&(a.ins_h=(a.ins_h<<a.hash_shift^a.window[a.strstart+hb-1])&a.hash_mask,c=a.prev[a.strstart&a.w_mask]=a.head[a.ins_h],a.head[a.ins_h]=a.strstart),a.prev_length=a.match_length,a.prev_match=a.match_start,a.match_length=hb-1,0!==c&&a.prev_length<a.max_lazy_match&&a.strstart-c<=a.w_size-jb&&(a.match_length=l(a,c),a.match_length<=5&&(a.strategy===S||a.match_length===hb&&a.strstart-a.match_start>4096)&&(a.match_length=hb-1)),a.prev_length>=hb&&a.match_length<=a.prev_length){e=a.strstart+a.lookahead-hb,d=D._tr_tally(a,a.strstart-1-a.prev_match,a.prev_length-hb),a.lookahead-=a.prev_length-1,a.prev_length-=2;do++a.strstart<=e&&(a.ins_h=(a.ins_h<<a.hash_shift^a.window[a.strstart+hb-1])&a.hash_mask,c=a.prev[a.strstart&a.w_mask]=a.head[a.ins_h],a.head[a.ins_h]=a.strstart);while(0!==--a.prev_length);if(a.match_available=0,a.match_length=hb-1,a.strstart++,d&&(h(a,!1),0===a.strm.avail_out))return sb}else if(a.match_available){if(d=D._tr_tally(a,0,a.window[a.strstart-1]),d&&h(a,!1),a.strstart++,a.lookahead--,0===a.strm.avail_out)return sb}else a.match_available=1,a.strstart++,a.lookahead--}return a.match_available&&(d=D._tr_tally(a,0,a.window[a.strstart-1]),a.match_available=0),a.insert=a.strstart<hb-1?a.strstart:hb-1,b===K?(h(a,!0),0===a.strm.avail_out?ub:vb):a.last_lit&&(h(a,!1),0===a.strm.avail_out)?sb:tb}function q(a,b){for(var c,d,e,f,g=a.window;;){if(a.lookahead<=ib){if(m(a),a.lookahead<=ib&&b===H)return sb;if(0===a.lookahead)break}if(a.match_length=0,a.lookahead>=hb&&a.strstart>0&&(e=a.strstart-1,d=g[e],d===g[++e]&&d===g[++e]&&d===g[++e])){f=a.strstart+ib;do;while(d===g[++e]&&d===g[++e]&&d===g[++e]&&d===g[++e]&&d===g[++e]&&d===g[++e]&&d===g[++e]&&d===g[++e]&&f>e);a.match_length=ib-(f-e),a.match_length>a.lookahead&&(a.match_length=a.lookahead)}if(a.match_length>=hb?(c=D._tr_tally(a,1,a.match_length-hb),a.lookahead-=a.match_length,a.strstart+=a.match_length,a.match_length=0):(c=D._tr_tally(a,0,a.window[a.strstart]),a.lookahead--,a.strstart++),c&&(h(a,!1),0===a.strm.avail_out))return sb}return a.insert=0,b===K?(h(a,!0),0===a.strm.avail_out?ub:vb):a.last_lit&&(h(a,!1),0===a.strm.avail_out)?sb:tb}function r(a,b){for(var c;;){if(0===a.lookahead&&(m(a),0===a.lookahead)){if(b===H)return sb;break}if(a.match_length=0,c=D._tr_tally(a,0,a.window[a.strstart]),a.lookahead--,a.strstart++,c&&(h(a,!1),0===a.strm.avail_out))return sb}return a.insert=0,b===K?(h(a,!0),0===a.strm.avail_out?ub:vb):a.last_lit&&(h(a,!1),0===a.strm.avail_out)?sb:tb}function s(a){a.window_size=2*a.w_size,f(a.head),a.max_lazy_match=B[a.level].max_lazy,a.good_match=B[a.level].good_length,a.nice_match=B[a.level].nice_length,a.max_chain_length=B[a.level].max_chain,a.strstart=0,a.block_start=0,a.lookahead=0,a.insert=0,a.match_length=a.prev_length=hb-1,a.match_available=0,a.ins_h=0}function t(){this.strm=null,this.status=0,this.pending_buf=null,this.pending_buf_size=0,this.pending_out=0,this.pending=0,this.wrap=0,this.gzhead=null,this.gzindex=0,this.method=Y,this.last_flush=-1,this.w_size=0,this.w_bits=0,this.w_mask=0,this.window=null,this.window_size=0,this.prev=null,this.head=null,this.ins_h=0,this.hash_size=0,this.hash_bits=0,this.hash_mask=0,this.hash_shift=0,this.block_start=0,this.match_length=0,this.prev_match=0,this.match_available=0,this.strstart=0,this.match_start=0,this.lookahead=0,this.prev_length=0,this.max_chain_length=0,this.max_lazy_match=0,this.level=0,this.strategy=0,this.good_match=0,this.nice_match=0,this.dyn_ltree=new C.Buf16(2*fb),this.dyn_dtree=new C.Buf16(2*(2*db+1)),this.bl_tree=new C.Buf16(2*(2*eb+1)),f(this.dyn_ltree),f(this.dyn_dtree),f(this.bl_tree),this.l_desc=null,this.d_desc=null,this.bl_desc=null,this.bl_count=new C.Buf16(gb+1),this.heap=new C.Buf16(2*cb+1),f(this.heap),this.heap_len=0,this.heap_max=0,this.depth=new C.Buf16(2*cb+1),f(this.depth),this.l_buf=0,this.lit_bufsize=0,this.last_lit=0,this.d_buf=0,this.opt_len=0,this.static_len=0,this.matches=0,this.insert=0,this.bi_buf=0,this.bi_valid=0}function u(a){var b;return a&&a.state?(a.total_in=a.total_out=0,a.data_type=X,b=a.state,b.pending=0,b.pending_out=0,b.wrap<0&&(b.wrap=-b.wrap),b.status=b.wrap?lb:qb,a.adler=2===b.wrap?0:1,b.last_flush=H,D._tr_init(b),M):d(a,O)}function v(a){var b=u(a);return b===M&&s(a.state),b}function w(a,b){return a&&a.state?2!==a.state.wrap?O:(a.state.gzhead=b,M):O}function x(a,b,c,e,f,g){if(!a)return O;var h=1;if(b===R&&(b=6),0>e?(h=0,e=-e):e>15&&(h=2,e-=16),1>f||f>Z||c!==Y||8>e||e>15||0>b||b>9||0>g||g>V)return d(a,O);8===e&&(e=9);var i=new t;return a.state=i,i.strm=a,i.wrap=h,i.gzhead=null,i.w_bits=e,i.w_size=1<<i.w_bits,i.w_mask=i.w_size-1,i.hash_bits=f+7,i.hash_size=1<<i.hash_bits,i.hash_mask=i.hash_size-1,i.hash_shift=~~((i.hash_bits+hb-1)/hb),i.window=new C.Buf8(2*i.w_size),i.head=new C.Buf16(i.hash_size),i.prev=new C.Buf16(i.w_size),i.lit_bufsize=1<<f+6,i.pending_buf_size=4*i.lit_bufsize,i.pending_buf=new C.Buf8(i.pending_buf_size),i.d_buf=i.lit_bufsize>>1,i.l_buf=3*i.lit_bufsize,i.level=b,i.strategy=g,i.method=c,v(a)}function y(a,b){return x(a,b,Y,$,_,W)}function z(a,b){var c,h,k,l;if(!a||!a.state||b>L||0>b)return a?d(a,O):O;if(h=a.state,!a.output||!a.input&&0!==a.avail_in||h.status===rb&&b!==K)return d(a,0===a.avail_out?Q:O);if(h.strm=a,c=h.last_flush,h.last_flush=b,h.status===lb)if(2===h.wrap)a.adler=0,i(h,31),i(h,139),i(h,8),h.gzhead?(i(h,(h.gzhead.text?1:0)+(h.gzhead.hcrc?2:0)+(h.gzhead.extra?4:0)+(h.gzhead.name?8:0)+(h.gzhead.comment?16:0)),i(h,255&h.gzhead.time),i(h,h.gzhead.time>>8&255),i(h,h.gzhead.time>>16&255),i(h,h.gzhead.time>>24&255),i(h,9===h.level?2:h.strategy>=T||h.level<2?4:0),i(h,255&h.gzhead.os),h.gzhead.extra&&h.gzhead.extra.length&&(i(h,255&h.gzhead.extra.length),i(h,h.gzhead.extra.length>>8&255)),h.gzhead.hcrc&&(a.adler=F(a.adler,h.pending_buf,h.pending,0)),h.gzindex=0,h.status=mb):(i(h,0),i(h,0),i(h,0),i(h,0),i(h,0),i(h,9===h.level?2:h.strategy>=T||h.level<2?4:0),i(h,wb),h.status=qb);else{var m=Y+(h.w_bits-8<<4)<<8,n=-1;n=h.strategy>=T||h.level<2?0:h.level<6?1:6===h.level?2:3,m|=n<<6,0!==h.strstart&&(m|=kb),m+=31-m%31,h.status=qb,j(h,m),0!==h.strstart&&(j(h,a.adler>>>16),j(h,65535&a.adler)),a.adler=1}if(h.status===mb)if(h.gzhead.extra){for(k=h.pending;h.gzindex<(65535&h.gzhead.extra.length)&&(h.pending!==h.pending_buf_size||(h.gzhead.hcrc&&h.pending>k&&(a.adler=F(a.adler,h.pending_buf,h.pending-k,k)),g(a),k=h.pending,h.pending!==h.pending_buf_size));)i(h,255&h.gzhead.extra[h.gzindex]),h.gzindex++;h.gzhead.hcrc&&h.pending>k&&(a.adler=F(a.adler,h.pending_buf,h.pending-k,k)),h.gzindex===h.gzhead.extra.length&&(h.gzindex=0,h.status=nb)}else h.status=nb;if(h.status===nb)if(h.gzhead.name){k=h.pending;do{if(h.pending===h.pending_buf_size&&(h.gzhead.hcrc&&h.pending>k&&(a.adler=F(a.adler,h.pending_buf,h.pending-k,k)),g(a),k=h.pending,h.pending===h.pending_buf_size)){l=1;break}l=h.gzindex<h.gzhead.name.length?255&h.gzhead.name.charCodeAt(h.gzindex++):0,i(h,l)}while(0!==l);h.gzhead.hcrc&&h.pending>k&&(a.adler=F(a.adler,h.pending_buf,h.pending-k,k)),0===l&&(h.gzindex=0,h.status=ob)}else h.status=ob;if(h.status===ob)if(h.gzhead.comment){k=h.pending;do{if(h.pending===h.pending_buf_size&&(h.gzhead.hcrc&&h.pending>k&&(a.adler=F(a.adler,h.pending_buf,h.pending-k,k)),g(a),k=h.pending,h.pending===h.pending_buf_size)){l=1;break}l=h.gzindex<h.gzhead.comment.length?255&h.gzhead.comment.charCodeAt(h.gzindex++):0,i(h,l)}while(0!==l);h.gzhead.hcrc&&h.pending>k&&(a.adler=F(a.adler,h.pending_buf,h.pending-k,k)),0===l&&(h.status=pb)}else h.status=pb;if(h.status===pb&&(h.gzhead.hcrc?(h.pending+2>h.pending_buf_size&&g(a),h.pending+2<=h.pending_buf_size&&(i(h,255&a.adler),i(h,a.adler>>8&255),a.adler=0,h.status=qb)):h.status=qb),0!==h.pending){if(g(a),0===a.avail_out)return h.last_flush=-1,M}else if(0===a.avail_in&&e(b)<=e(c)&&b!==K)return d(a,Q);if(h.status===rb&&0!==a.avail_in)return d(a,Q);if(0!==a.avail_in||0!==h.lookahead||b!==H&&h.status!==rb){var o=h.strategy===T?r(h,b):h.strategy===U?q(h,b):B[h.level].func(h,b);if((o===ub||o===vb)&&(h.status=rb),o===sb||o===ub)return 0===a.avail_out&&(h.last_flush=-1),M;if(o===tb&&(b===I?D._tr_align(h):b!==L&&(D._tr_stored_block(h,0,0,!1),b===J&&(f(h.head),0===h.lookahead&&(h.strstart=0,h.block_start=0,h.insert=0))),g(a),0===a.avail_out))return h.last_flush=-1,M}return b!==K?M:h.wrap<=0?N:(2===h.wrap?(i(h,255&a.adler),i(h,a.adler>>8&255),i(h,a.adler>>16&255),i(h,a.adler>>24&255),i(h,255&a.total_in),i(h,a.total_in>>8&255),i(h,a.total_in>>16&255),i(h,a.total_in>>24&255)):(j(h,a.adler>>>16),j(h,65535&a.adler)),g(a),h.wrap>0&&(h.wrap=-h.wrap),0!==h.pending?M:N)}function A(a){var b;return a&&a.state?(b=a.state.status,b!==lb&&b!==mb&&b!==nb&&b!==ob&&b!==pb&&b!==qb&&b!==rb?d(a,O):(a.state=null,b===qb?d(a,P):M)):O}var B,C=a("../utils/common"),D=a("./trees"),E=a("./adler32"),F=a("./crc32"),G=a("./messages"),H=0,I=1,J=3,K=4,L=5,M=0,N=1,O=-2,P=-3,Q=-5,R=-1,S=1,T=2,U=3,V=4,W=0,X=2,Y=8,Z=9,$=15,_=8,ab=29,bb=256,cb=bb+1+ab,db=30,eb=19,fb=2*cb+1,gb=15,hb=3,ib=258,jb=ib+hb+1,kb=32,lb=42,mb=69,nb=73,ob=91,pb=103,qb=113,rb=666,sb=1,tb=2,ub=3,vb=4,wb=3,xb=function(a,b,c,d,e){this.good_length=a,this.max_lazy=b,this.nice_length=c,this.max_chain=d,this.func=e};B=[new xb(0,0,0,0,n),new xb(4,4,8,4,o),new xb(4,5,16,8,o),new xb(4,6,32,32,o),new xb(4,4,16,16,p),new xb(8,16,32,32,p),new xb(8,16,128,128,p),new xb(8,32,128,256,p),new xb(32,128,258,1024,p),new xb(32,258,258,4096,p)],c.deflateInit=y,c.deflateInit2=x,c.deflateReset=v,c.deflateResetKeep=u,c.deflateSetHeader=w,c.deflate=z,c.deflateEnd=A,c.deflateInfo="pako deflate (from Nodeca project)"},{"../utils/common":27,"./adler32":29,"./crc32":31,"./messages":37,"./trees":38}],33:[function(a,b){"use strict";function c(){this.text=0,this.time=0,this.xflags=0,this.os=0,this.extra=null,this.extra_len=0,this.name="",this.comment="",this.hcrc=0,this.done=!1}b.exports=c},{}],34:[function(a,b){"use strict";var c=30,d=12;b.exports=function(a,b){var e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z,A,B,C;e=a.state,f=a.next_in,B=a.input,g=f+(a.avail_in-5),h=a.next_out,C=a.output,i=h-(b-a.avail_out),j=h+(a.avail_out-257),k=e.dmax,l=e.wsize,m=e.whave,n=e.wnext,o=e.window,p=e.hold,q=e.bits,r=e.lencode,s=e.distcode,t=(1<<e.lenbits)-1,u=(1<<e.distbits)-1;a:do{15>q&&(p+=B[f++]<<q,q+=8,p+=B[f++]<<q,q+=8),v=r[p&t];b:for(;;){if(w=v>>>24,p>>>=w,q-=w,w=v>>>16&255,0===w)C[h++]=65535&v;else{if(!(16&w)){if(0===(64&w)){v=r[(65535&v)+(p&(1<<w)-1)];continue b}if(32&w){e.mode=d;break a}a.msg="invalid literal/length code",e.mode=c;break a}x=65535&v,w&=15,w&&(w>q&&(p+=B[f++]<<q,q+=8),x+=p&(1<<w)-1,p>>>=w,q-=w),15>q&&(p+=B[f++]<<q,q+=8,p+=B[f++]<<q,q+=8),v=s[p&u];c:for(;;){if(w=v>>>24,p>>>=w,q-=w,w=v>>>16&255,!(16&w)){if(0===(64&w)){v=s[(65535&v)+(p&(1<<w)-1)];continue c}a.msg="invalid distance code",e.mode=c;break a}if(y=65535&v,w&=15,w>q&&(p+=B[f++]<<q,q+=8,w>q&&(p+=B[f++]<<q,q+=8)),y+=p&(1<<w)-1,y>k){a.msg="invalid distance too far back",e.mode=c;break a}if(p>>>=w,q-=w,w=h-i,y>w){if(w=y-w,w>m&&e.sane){a.msg="invalid distance too far back",e.mode=c;break a}if(z=0,A=o,0===n){if(z+=l-w,x>w){x-=w;do C[h++]=o[z++];while(--w);z=h-y,A=C}}else if(w>n){if(z+=l+n-w,w-=n,x>w){x-=w;do C[h++]=o[z++];while(--w);if(z=0,x>n){w=n,x-=w;do C[h++]=o[z++];while(--w);z=h-y,A=C}}}else if(z+=n-w,x>w){x-=w;do C[h++]=o[z++];while(--w);z=h-y,A=C}for(;x>2;)C[h++]=A[z++],C[h++]=A[z++],C[h++]=A[z++],x-=3;x&&(C[h++]=A[z++],x>1&&(C[h++]=A[z++]))}else{z=h-y;do C[h++]=C[z++],C[h++]=C[z++],C[h++]=C[z++],x-=3;while(x>2);x&&(C[h++]=C[z++],x>1&&(C[h++]=C[z++]))}break}}break}}while(g>f&&j>h);x=q>>3,f-=x,q-=x<<3,p&=(1<<q)-1,a.next_in=f,a.next_out=h,a.avail_in=g>f?5+(g-f):5-(f-g),a.avail_out=j>h?257+(j-h):257-(h-j),e.hold=p,e.bits=q}},{}],35:[function(a,b,c){"use strict";function d(a){return(a>>>24&255)+(a>>>8&65280)+((65280&a)<<8)+((255&a)<<24)}function e(){this.mode=0,this.last=!1,this.wrap=0,this.havedict=!1,this.flags=0,this.dmax=0,this.check=0,this.total=0,this.head=null,this.wbits=0,this.wsize=0,this.whave=0,this.wnext=0,this.window=null,this.hold=0,this.bits=0,this.length=0,this.offset=0,this.extra=0,this.lencode=null,this.distcode=null,this.lenbits=0,this.distbits=0,this.ncode=0,this.nlen=0,this.ndist=0,this.have=0,this.next=null,this.lens=new r.Buf16(320),this.work=new r.Buf16(288),this.lendyn=null,this.distdyn=null,this.sane=0,this.back=0,this.was=0}function f(a){var b;return a&&a.state?(b=a.state,a.total_in=a.total_out=b.total=0,a.msg="",b.wrap&&(a.adler=1&b.wrap),b.mode=K,b.last=0,b.havedict=0,b.dmax=32768,b.head=null,b.hold=0,b.bits=0,b.lencode=b.lendyn=new r.Buf32(ob),b.distcode=b.distdyn=new r.Buf32(pb),b.sane=1,b.back=-1,C):F}function g(a){var b;return a&&a.state?(b=a.state,b.wsize=0,b.whave=0,b.wnext=0,f(a)):F}function h(a,b){var c,d;return a&&a.state?(d=a.state,0>b?(c=0,b=-b):(c=(b>>4)+1,48>b&&(b&=15)),b&&(8>b||b>15)?F:(null!==d.window&&d.wbits!==b&&(d.window=null),d.wrap=c,d.wbits=b,g(a))):F}function i(a,b){var c,d;return a?(d=new e,a.state=d,d.window=null,c=h(a,b),c!==C&&(a.state=null),c):F}function j(a){return i(a,rb)}function k(a){if(sb){var b;for(p=new r.Buf32(512),q=new r.Buf32(32),b=0;144>b;)a.lens[b++]=8;for(;256>b;)a.lens[b++]=9;for(;280>b;)a.lens[b++]=7;for(;288>b;)a.lens[b++]=8;for(v(x,a.lens,0,288,p,0,a.work,{bits:9}),b=0;32>b;)a.lens[b++]=5;v(y,a.lens,0,32,q,0,a.work,{bits:5}),sb=!1}a.lencode=p,a.lenbits=9,a.distcode=q,a.distbits=5}function l(a,b,c,d){var e,f=a.state;return null===f.window&&(f.wsize=1<<f.wbits,f.wnext=0,f.whave=0,f.window=new r.Buf8(f.wsize)),d>=f.wsize?(r.arraySet(f.window,b,c-f.wsize,f.wsize,0),f.wnext=0,f.whave=f.wsize):(e=f.wsize-f.wnext,e>d&&(e=d),r.arraySet(f.window,b,c-d,e,f.wnext),d-=e,d?(r.arraySet(f.window,b,c-d,d,0),f.wnext=d,f.whave=f.wsize):(f.wnext+=e,f.wnext===f.wsize&&(f.wnext=0),f.whave<f.wsize&&(f.whave+=e))),0}function m(a,b){var c,e,f,g,h,i,j,m,n,o,p,q,ob,pb,qb,rb,sb,tb,ub,vb,wb,xb,yb,zb,Ab=0,Bb=new r.Buf8(4),Cb=[16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15];if(!a||!a.state||!a.output||!a.input&&0!==a.avail_in)return F;c=a.state,c.mode===V&&(c.mode=W),h=a.next_out,f=a.output,j=a.avail_out,g=a.next_in,e=a.input,i=a.avail_in,m=c.hold,n=c.bits,o=i,p=j,xb=C;a:for(;;)switch(c.mode){case K:if(0===c.wrap){c.mode=W;break}for(;16>n;){if(0===i)break a;i--,m+=e[g++]<<n,n+=8}if(2&c.wrap&&35615===m){c.check=0,Bb[0]=255&m,Bb[1]=m>>>8&255,c.check=t(c.check,Bb,2,0),m=0,n=0,c.mode=L;break}if(c.flags=0,c.head&&(c.head.done=!1),!(1&c.wrap)||(((255&m)<<8)+(m>>8))%31){a.msg="incorrect header check",c.mode=lb;break}if((15&m)!==J){a.msg="unknown compression method",c.mode=lb;break}if(m>>>=4,n-=4,wb=(15&m)+8,0===c.wbits)c.wbits=wb;else if(wb>c.wbits){a.msg="invalid window size",c.mode=lb;break}c.dmax=1<<wb,a.adler=c.check=1,c.mode=512&m?T:V,m=0,n=0;break;case L:for(;16>n;){if(0===i)break a;i--,m+=e[g++]<<n,n+=8}if(c.flags=m,(255&c.flags)!==J){a.msg="unknown compression method",c.mode=lb;break}if(57344&c.flags){a.msg="unknown header flags set",c.mode=lb;break}c.head&&(c.head.text=m>>8&1),512&c.flags&&(Bb[0]=255&m,Bb[1]=m>>>8&255,c.check=t(c.check,Bb,2,0)),m=0,n=0,c.mode=M;case M:for(;32>n;){if(0===i)break a;i--,m+=e[g++]<<n,n+=8}c.head&&(c.head.time=m),512&c.flags&&(Bb[0]=255&m,Bb[1]=m>>>8&255,Bb[2]=m>>>16&255,Bb[3]=m>>>24&255,c.check=t(c.check,Bb,4,0)),m=0,n=0,c.mode=N;case N:for(;16>n;){if(0===i)break a;i--,m+=e[g++]<<n,n+=8}c.head&&(c.head.xflags=255&m,c.head.os=m>>8),512&c.flags&&(Bb[0]=255&m,Bb[1]=m>>>8&255,c.check=t(c.check,Bb,2,0)),m=0,n=0,c.mode=O;case O:if(1024&c.flags){for(;16>n;){if(0===i)break a;i--,m+=e[g++]<<n,n+=8}c.length=m,c.head&&(c.head.extra_len=m),512&c.flags&&(Bb[0]=255&m,Bb[1]=m>>>8&255,c.check=t(c.check,Bb,2,0)),m=0,n=0}else c.head&&(c.head.extra=null);c.mode=P;case P:if(1024&c.flags&&(q=c.length,q>i&&(q=i),q&&(c.head&&(wb=c.head.extra_len-c.length,c.head.extra||(c.head.extra=new Array(c.head.extra_len)),r.arraySet(c.head.extra,e,g,q,wb)),512&c.flags&&(c.check=t(c.check,e,q,g)),i-=q,g+=q,c.length-=q),c.length))break a;c.length=0,c.mode=Q;case Q:if(2048&c.flags){if(0===i)break a;q=0;do wb=e[g+q++],c.head&&wb&&c.length<65536&&(c.head.name+=String.fromCharCode(wb));while(wb&&i>q);if(512&c.flags&&(c.check=t(c.check,e,q,g)),i-=q,g+=q,wb)break a}else c.head&&(c.head.name=null);c.length=0,c.mode=R;case R:if(4096&c.flags){if(0===i)break a;q=0;do wb=e[g+q++],c.head&&wb&&c.length<65536&&(c.head.comment+=String.fromCharCode(wb));while(wb&&i>q);if(512&c.flags&&(c.check=t(c.check,e,q,g)),i-=q,g+=q,wb)break a}else c.head&&(c.head.comment=null);c.mode=S;case S:if(512&c.flags){for(;16>n;){if(0===i)break a;i--,m+=e[g++]<<n,n+=8}if(m!==(65535&c.check)){a.msg="header crc mismatch",c.mode=lb;break}m=0,n=0}c.head&&(c.head.hcrc=c.flags>>9&1,c.head.done=!0),a.adler=c.check=0,c.mode=V;break;case T:for(;32>n;){if(0===i)break a;i--,m+=e[g++]<<n,n+=8}a.adler=c.check=d(m),m=0,n=0,c.mode=U;case U:if(0===c.havedict)return a.next_out=h,a.avail_out=j,a.next_in=g,a.avail_in=i,c.hold=m,c.bits=n,E;a.adler=c.check=1,c.mode=V;case V:if(b===A||b===B)break a;case W:if(c.last){m>>>=7&n,n-=7&n,c.mode=ib;break}for(;3>n;){if(0===i)break a;i--,m+=e[g++]<<n,n+=8}switch(c.last=1&m,m>>>=1,n-=1,3&m){case 0:c.mode=X;break;case 1:if(k(c),c.mode=bb,b===B){m>>>=2,n-=2;break a}break;case 2:c.mode=$;break;case 3:a.msg="invalid block type",c.mode=lb}m>>>=2,n-=2;break;case X:for(m>>>=7&n,n-=7&n;32>n;){if(0===i)break a;i--,m+=e[g++]<<n,n+=8}if((65535&m)!==(m>>>16^65535)){a.msg="invalid stored block lengths",c.mode=lb;break}if(c.length=65535&m,m=0,n=0,c.mode=Y,b===B)break a;case Y:c.mode=Z;case Z:if(q=c.length){if(q>i&&(q=i),q>j&&(q=j),0===q)break a;r.arraySet(f,e,g,q,h),i-=q,g+=q,j-=q,h+=q,c.length-=q;break}c.mode=V;break;case $:for(;14>n;){if(0===i)break a;i--,m+=e[g++]<<n,n+=8}if(c.nlen=(31&m)+257,m>>>=5,n-=5,c.ndist=(31&m)+1,m>>>=5,n-=5,c.ncode=(15&m)+4,m>>>=4,n-=4,c.nlen>286||c.ndist>30){a.msg="too many length or distance symbols",c.mode=lb;break}c.have=0,c.mode=_;case _:for(;c.have<c.ncode;){for(;3>n;){if(0===i)break a;i--,m+=e[g++]<<n,n+=8}c.lens[Cb[c.have++]]=7&m,m>>>=3,n-=3}for(;c.have<19;)c.lens[Cb[c.have++]]=0;if(c.lencode=c.lendyn,c.lenbits=7,yb={bits:c.lenbits},xb=v(w,c.lens,0,19,c.lencode,0,c.work,yb),c.lenbits=yb.bits,xb){a.msg="invalid code lengths set",c.mode=lb;break}c.have=0,c.mode=ab;case ab:for(;c.have<c.nlen+c.ndist;){for(;Ab=c.lencode[m&(1<<c.lenbits)-1],qb=Ab>>>24,rb=Ab>>>16&255,sb=65535&Ab,!(n>=qb);){if(0===i)break a;i--,m+=e[g++]<<n,n+=8}if(16>sb)m>>>=qb,n-=qb,c.lens[c.have++]=sb;else{if(16===sb){for(zb=qb+2;zb>n;){if(0===i)break a;i--,m+=e[g++]<<n,n+=8}if(m>>>=qb,n-=qb,0===c.have){a.msg="invalid bit length repeat",c.mode=lb;break}wb=c.lens[c.have-1],q=3+(3&m),m>>>=2,n-=2}else if(17===sb){for(zb=qb+3;zb>n;){if(0===i)break a;i--,m+=e[g++]<<n,n+=8}m>>>=qb,n-=qb,wb=0,q=3+(7&m),m>>>=3,n-=3}else{for(zb=qb+7;zb>n;){if(0===i)break a;i--,m+=e[g++]<<n,n+=8}m>>>=qb,n-=qb,wb=0,q=11+(127&m),m>>>=7,n-=7}if(c.have+q>c.nlen+c.ndist){a.msg="invalid bit length repeat",c.mode=lb;break}for(;q--;)c.lens[c.have++]=wb}}if(c.mode===lb)break;if(0===c.lens[256]){a.msg="invalid code -- missing end-of-block",c.mode=lb;break}if(c.lenbits=9,yb={bits:c.lenbits},xb=v(x,c.lens,0,c.nlen,c.lencode,0,c.work,yb),c.lenbits=yb.bits,xb){a.msg="invalid literal/lengths set",c.mode=lb;break}if(c.distbits=6,c.distcode=c.distdyn,yb={bits:c.distbits},xb=v(y,c.lens,c.nlen,c.ndist,c.distcode,0,c.work,yb),c.distbits=yb.bits,xb){a.msg="invalid distances set",c.mode=lb;break}if(c.mode=bb,b===B)break a;case bb:c.mode=cb;case cb:if(i>=6&&j>=258){a.next_out=h,a.avail_out=j,a.next_in=g,a.avail_in=i,c.hold=m,c.bits=n,u(a,p),h=a.next_out,f=a.output,j=a.avail_out,g=a.next_in,e=a.input,i=a.avail_in,m=c.hold,n=c.bits,c.mode===V&&(c.back=-1);break}for(c.back=0;Ab=c.lencode[m&(1<<c.lenbits)-1],qb=Ab>>>24,rb=Ab>>>16&255,sb=65535&Ab,!(n>=qb);){if(0===i)break a;i--,m+=e[g++]<<n,n+=8}if(rb&&0===(240&rb)){for(tb=qb,ub=rb,vb=sb;Ab=c.lencode[vb+((m&(1<<tb+ub)-1)>>tb)],qb=Ab>>>24,rb=Ab>>>16&255,sb=65535&Ab,!(n>=tb+qb);){if(0===i)break a;i--,m+=e[g++]<<n,n+=8}m>>>=tb,n-=tb,c.back+=tb}if(m>>>=qb,n-=qb,c.back+=qb,c.length=sb,0===rb){c.mode=hb;break}if(32&rb){c.back=-1,c.mode=V;break}if(64&rb){a.msg="invalid literal/length code",c.mode=lb;break}c.extra=15&rb,c.mode=db;case db:if(c.extra){for(zb=c.extra;zb>n;){if(0===i)break a;i--,m+=e[g++]<<n,n+=8}c.length+=m&(1<<c.extra)-1,m>>>=c.extra,n-=c.extra,c.back+=c.extra}c.was=c.length,c.mode=eb;case eb:for(;Ab=c.distcode[m&(1<<c.distbits)-1],qb=Ab>>>24,rb=Ab>>>16&255,sb=65535&Ab,!(n>=qb);){if(0===i)break a;i--,m+=e[g++]<<n,n+=8}if(0===(240&rb)){for(tb=qb,ub=rb,vb=sb;Ab=c.distcode[vb+((m&(1<<tb+ub)-1)>>tb)],qb=Ab>>>24,rb=Ab>>>16&255,sb=65535&Ab,!(n>=tb+qb);){if(0===i)break a;i--,m+=e[g++]<<n,n+=8}m>>>=tb,n-=tb,c.back+=tb}if(m>>>=qb,n-=qb,c.back+=qb,64&rb){a.msg="invalid distance code",c.mode=lb;break}c.offset=sb,c.extra=15&rb,c.mode=fb;case fb:if(c.extra){for(zb=c.extra;zb>n;){if(0===i)break a;i--,m+=e[g++]<<n,n+=8}c.offset+=m&(1<<c.extra)-1,m>>>=c.extra,n-=c.extra,c.back+=c.extra}if(c.offset>c.dmax){a.msg="invalid distance too far back",c.mode=lb;break}c.mode=gb;case gb:if(0===j)break a;
if(q=p-j,c.offset>q){if(q=c.offset-q,q>c.whave&&c.sane){a.msg="invalid distance too far back",c.mode=lb;break}q>c.wnext?(q-=c.wnext,ob=c.wsize-q):ob=c.wnext-q,q>c.length&&(q=c.length),pb=c.window}else pb=f,ob=h-c.offset,q=c.length;q>j&&(q=j),j-=q,c.length-=q;do f[h++]=pb[ob++];while(--q);0===c.length&&(c.mode=cb);break;case hb:if(0===j)break a;f[h++]=c.length,j--,c.mode=cb;break;case ib:if(c.wrap){for(;32>n;){if(0===i)break a;i--,m|=e[g++]<<n,n+=8}if(p-=j,a.total_out+=p,c.total+=p,p&&(a.adler=c.check=c.flags?t(c.check,f,p,h-p):s(c.check,f,p,h-p)),p=j,(c.flags?m:d(m))!==c.check){a.msg="incorrect data check",c.mode=lb;break}m=0,n=0}c.mode=jb;case jb:if(c.wrap&&c.flags){for(;32>n;){if(0===i)break a;i--,m+=e[g++]<<n,n+=8}if(m!==(4294967295&c.total)){a.msg="incorrect length check",c.mode=lb;break}m=0,n=0}c.mode=kb;case kb:xb=D;break a;case lb:xb=G;break a;case mb:return H;case nb:default:return F}return a.next_out=h,a.avail_out=j,a.next_in=g,a.avail_in=i,c.hold=m,c.bits=n,(c.wsize||p!==a.avail_out&&c.mode<lb&&(c.mode<ib||b!==z))&&l(a,a.output,a.next_out,p-a.avail_out)?(c.mode=mb,H):(o-=a.avail_in,p-=a.avail_out,a.total_in+=o,a.total_out+=p,c.total+=p,c.wrap&&p&&(a.adler=c.check=c.flags?t(c.check,f,p,a.next_out-p):s(c.check,f,p,a.next_out-p)),a.data_type=c.bits+(c.last?64:0)+(c.mode===V?128:0)+(c.mode===bb||c.mode===Y?256:0),(0===o&&0===p||b===z)&&xb===C&&(xb=I),xb)}function n(a){if(!a||!a.state)return F;var b=a.state;return b.window&&(b.window=null),a.state=null,C}function o(a,b){var c;return a&&a.state?(c=a.state,0===(2&c.wrap)?F:(c.head=b,b.done=!1,C)):F}var p,q,r=a("../utils/common"),s=a("./adler32"),t=a("./crc32"),u=a("./inffast"),v=a("./inftrees"),w=0,x=1,y=2,z=4,A=5,B=6,C=0,D=1,E=2,F=-2,G=-3,H=-4,I=-5,J=8,K=1,L=2,M=3,N=4,O=5,P=6,Q=7,R=8,S=9,T=10,U=11,V=12,W=13,X=14,Y=15,Z=16,$=17,_=18,ab=19,bb=20,cb=21,db=22,eb=23,fb=24,gb=25,hb=26,ib=27,jb=28,kb=29,lb=30,mb=31,nb=32,ob=852,pb=592,qb=15,rb=qb,sb=!0;c.inflateReset=g,c.inflateReset2=h,c.inflateResetKeep=f,c.inflateInit=j,c.inflateInit2=i,c.inflate=m,c.inflateEnd=n,c.inflateGetHeader=o,c.inflateInfo="pako inflate (from Nodeca project)"},{"../utils/common":27,"./adler32":29,"./crc32":31,"./inffast":34,"./inftrees":36}],36:[function(a,b){"use strict";var c=a("../utils/common"),d=15,e=852,f=592,g=0,h=1,i=2,j=[3,4,5,6,7,8,9,10,11,13,15,17,19,23,27,31,35,43,51,59,67,83,99,115,131,163,195,227,258,0,0],k=[16,16,16,16,16,16,16,16,17,17,17,17,18,18,18,18,19,19,19,19,20,20,20,20,21,21,21,21,16,72,78],l=[1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193,257,385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577,0,0],m=[16,16,16,16,17,17,18,18,19,19,20,20,21,21,22,22,23,23,24,24,25,25,26,26,27,27,28,28,29,29,64,64];b.exports=function(a,b,n,o,p,q,r,s){var t,u,v,w,x,y,z,A,B,C=s.bits,D=0,E=0,F=0,G=0,H=0,I=0,J=0,K=0,L=0,M=0,N=null,O=0,P=new c.Buf16(d+1),Q=new c.Buf16(d+1),R=null,S=0;for(D=0;d>=D;D++)P[D]=0;for(E=0;o>E;E++)P[b[n+E]]++;for(H=C,G=d;G>=1&&0===P[G];G--);if(H>G&&(H=G),0===G)return p[q++]=20971520,p[q++]=20971520,s.bits=1,0;for(F=1;G>F&&0===P[F];F++);for(F>H&&(H=F),K=1,D=1;d>=D;D++)if(K<<=1,K-=P[D],0>K)return-1;if(K>0&&(a===g||1!==G))return-1;for(Q[1]=0,D=1;d>D;D++)Q[D+1]=Q[D]+P[D];for(E=0;o>E;E++)0!==b[n+E]&&(r[Q[b[n+E]]++]=E);if(a===g?(N=R=r,y=19):a===h?(N=j,O-=257,R=k,S-=257,y=256):(N=l,R=m,y=-1),M=0,E=0,D=F,x=q,I=H,J=0,v=-1,L=1<<H,w=L-1,a===h&&L>e||a===i&&L>f)return 1;for(var T=0;;){T++,z=D-J,r[E]<y?(A=0,B=r[E]):r[E]>y?(A=R[S+r[E]],B=N[O+r[E]]):(A=96,B=0),t=1<<D-J,u=1<<I,F=u;do u-=t,p[x+(M>>J)+u]=z<<24|A<<16|B|0;while(0!==u);for(t=1<<D-1;M&t;)t>>=1;if(0!==t?(M&=t-1,M+=t):M=0,E++,0===--P[D]){if(D===G)break;D=b[n+r[E]]}if(D>H&&(M&w)!==v){for(0===J&&(J=H),x+=F,I=D-J,K=1<<I;G>I+J&&(K-=P[I+J],!(0>=K));)I++,K<<=1;if(L+=1<<I,a===h&&L>e||a===i&&L>f)return 1;v=M&w,p[v]=H<<24|I<<16|x-q|0}}return 0!==M&&(p[x+M]=D-J<<24|64<<16|0),s.bits=H,0}},{"../utils/common":27}],37:[function(a,b){"use strict";b.exports={2:"need dictionary",1:"stream end",0:"","-1":"file error","-2":"stream error","-3":"data error","-4":"insufficient memory","-5":"buffer error","-6":"incompatible version"}},{}],38:[function(a,b,c){"use strict";function d(a){for(var b=a.length;--b>=0;)a[b]=0}function e(a){return 256>a?gb[a]:gb[256+(a>>>7)]}function f(a,b){a.pending_buf[a.pending++]=255&b,a.pending_buf[a.pending++]=b>>>8&255}function g(a,b,c){a.bi_valid>V-c?(a.bi_buf|=b<<a.bi_valid&65535,f(a,a.bi_buf),a.bi_buf=b>>V-a.bi_valid,a.bi_valid+=c-V):(a.bi_buf|=b<<a.bi_valid&65535,a.bi_valid+=c)}function h(a,b,c){g(a,c[2*b],c[2*b+1])}function i(a,b){var c=0;do c|=1&a,a>>>=1,c<<=1;while(--b>0);return c>>>1}function j(a){16===a.bi_valid?(f(a,a.bi_buf),a.bi_buf=0,a.bi_valid=0):a.bi_valid>=8&&(a.pending_buf[a.pending++]=255&a.bi_buf,a.bi_buf>>=8,a.bi_valid-=8)}function k(a,b){var c,d,e,f,g,h,i=b.dyn_tree,j=b.max_code,k=b.stat_desc.static_tree,l=b.stat_desc.has_stree,m=b.stat_desc.extra_bits,n=b.stat_desc.extra_base,o=b.stat_desc.max_length,p=0;for(f=0;U>=f;f++)a.bl_count[f]=0;for(i[2*a.heap[a.heap_max]+1]=0,c=a.heap_max+1;T>c;c++)d=a.heap[c],f=i[2*i[2*d+1]+1]+1,f>o&&(f=o,p++),i[2*d+1]=f,d>j||(a.bl_count[f]++,g=0,d>=n&&(g=m[d-n]),h=i[2*d],a.opt_len+=h*(f+g),l&&(a.static_len+=h*(k[2*d+1]+g)));if(0!==p){do{for(f=o-1;0===a.bl_count[f];)f--;a.bl_count[f]--,a.bl_count[f+1]+=2,a.bl_count[o]--,p-=2}while(p>0);for(f=o;0!==f;f--)for(d=a.bl_count[f];0!==d;)e=a.heap[--c],e>j||(i[2*e+1]!==f&&(a.opt_len+=(f-i[2*e+1])*i[2*e],i[2*e+1]=f),d--)}}function l(a,b,c){var d,e,f=new Array(U+1),g=0;for(d=1;U>=d;d++)f[d]=g=g+c[d-1]<<1;for(e=0;b>=e;e++){var h=a[2*e+1];0!==h&&(a[2*e]=i(f[h]++,h))}}function m(){var a,b,c,d,e,f=new Array(U+1);for(c=0,d=0;O-1>d;d++)for(ib[d]=c,a=0;a<1<<_[d];a++)hb[c++]=d;for(hb[c-1]=d,e=0,d=0;16>d;d++)for(jb[d]=e,a=0;a<1<<ab[d];a++)gb[e++]=d;for(e>>=7;R>d;d++)for(jb[d]=e<<7,a=0;a<1<<ab[d]-7;a++)gb[256+e++]=d;for(b=0;U>=b;b++)f[b]=0;for(a=0;143>=a;)eb[2*a+1]=8,a++,f[8]++;for(;255>=a;)eb[2*a+1]=9,a++,f[9]++;for(;279>=a;)eb[2*a+1]=7,a++,f[7]++;for(;287>=a;)eb[2*a+1]=8,a++,f[8]++;for(l(eb,Q+1,f),a=0;R>a;a++)fb[2*a+1]=5,fb[2*a]=i(a,5);kb=new nb(eb,_,P+1,Q,U),lb=new nb(fb,ab,0,R,U),mb=new nb(new Array(0),bb,0,S,W)}function n(a){var b;for(b=0;Q>b;b++)a.dyn_ltree[2*b]=0;for(b=0;R>b;b++)a.dyn_dtree[2*b]=0;for(b=0;S>b;b++)a.bl_tree[2*b]=0;a.dyn_ltree[2*X]=1,a.opt_len=a.static_len=0,a.last_lit=a.matches=0}function o(a){a.bi_valid>8?f(a,a.bi_buf):a.bi_valid>0&&(a.pending_buf[a.pending++]=a.bi_buf),a.bi_buf=0,a.bi_valid=0}function p(a,b,c,d){o(a),d&&(f(a,c),f(a,~c)),E.arraySet(a.pending_buf,a.window,b,c,a.pending),a.pending+=c}function q(a,b,c,d){var e=2*b,f=2*c;return a[e]<a[f]||a[e]===a[f]&&d[b]<=d[c]}function r(a,b,c){for(var d=a.heap[c],e=c<<1;e<=a.heap_len&&(e<a.heap_len&&q(b,a.heap[e+1],a.heap[e],a.depth)&&e++,!q(b,d,a.heap[e],a.depth));)a.heap[c]=a.heap[e],c=e,e<<=1;a.heap[c]=d}function s(a,b,c){var d,f,i,j,k=0;if(0!==a.last_lit)do d=a.pending_buf[a.d_buf+2*k]<<8|a.pending_buf[a.d_buf+2*k+1],f=a.pending_buf[a.l_buf+k],k++,0===d?h(a,f,b):(i=hb[f],h(a,i+P+1,b),j=_[i],0!==j&&(f-=ib[i],g(a,f,j)),d--,i=e(d),h(a,i,c),j=ab[i],0!==j&&(d-=jb[i],g(a,d,j)));while(k<a.last_lit);h(a,X,b)}function t(a,b){var c,d,e,f=b.dyn_tree,g=b.stat_desc.static_tree,h=b.stat_desc.has_stree,i=b.stat_desc.elems,j=-1;for(a.heap_len=0,a.heap_max=T,c=0;i>c;c++)0!==f[2*c]?(a.heap[++a.heap_len]=j=c,a.depth[c]=0):f[2*c+1]=0;for(;a.heap_len<2;)e=a.heap[++a.heap_len]=2>j?++j:0,f[2*e]=1,a.depth[e]=0,a.opt_len--,h&&(a.static_len-=g[2*e+1]);for(b.max_code=j,c=a.heap_len>>1;c>=1;c--)r(a,f,c);e=i;do c=a.heap[1],a.heap[1]=a.heap[a.heap_len--],r(a,f,1),d=a.heap[1],a.heap[--a.heap_max]=c,a.heap[--a.heap_max]=d,f[2*e]=f[2*c]+f[2*d],a.depth[e]=(a.depth[c]>=a.depth[d]?a.depth[c]:a.depth[d])+1,f[2*c+1]=f[2*d+1]=e,a.heap[1]=e++,r(a,f,1);while(a.heap_len>=2);a.heap[--a.heap_max]=a.heap[1],k(a,b),l(f,j,a.bl_count)}function u(a,b,c){var d,e,f=-1,g=b[1],h=0,i=7,j=4;for(0===g&&(i=138,j=3),b[2*(c+1)+1]=65535,d=0;c>=d;d++)e=g,g=b[2*(d+1)+1],++h<i&&e===g||(j>h?a.bl_tree[2*e]+=h:0!==e?(e!==f&&a.bl_tree[2*e]++,a.bl_tree[2*Y]++):10>=h?a.bl_tree[2*Z]++:a.bl_tree[2*$]++,h=0,f=e,0===g?(i=138,j=3):e===g?(i=6,j=3):(i=7,j=4))}function v(a,b,c){var d,e,f=-1,i=b[1],j=0,k=7,l=4;for(0===i&&(k=138,l=3),d=0;c>=d;d++)if(e=i,i=b[2*(d+1)+1],!(++j<k&&e===i)){if(l>j){do h(a,e,a.bl_tree);while(0!==--j)}else 0!==e?(e!==f&&(h(a,e,a.bl_tree),j--),h(a,Y,a.bl_tree),g(a,j-3,2)):10>=j?(h(a,Z,a.bl_tree),g(a,j-3,3)):(h(a,$,a.bl_tree),g(a,j-11,7));j=0,f=e,0===i?(k=138,l=3):e===i?(k=6,l=3):(k=7,l=4)}}function w(a){var b;for(u(a,a.dyn_ltree,a.l_desc.max_code),u(a,a.dyn_dtree,a.d_desc.max_code),t(a,a.bl_desc),b=S-1;b>=3&&0===a.bl_tree[2*cb[b]+1];b--);return a.opt_len+=3*(b+1)+5+5+4,b}function x(a,b,c,d){var e;for(g(a,b-257,5),g(a,c-1,5),g(a,d-4,4),e=0;d>e;e++)g(a,a.bl_tree[2*cb[e]+1],3);v(a,a.dyn_ltree,b-1),v(a,a.dyn_dtree,c-1)}function y(a){var b,c=4093624447;for(b=0;31>=b;b++,c>>>=1)if(1&c&&0!==a.dyn_ltree[2*b])return G;if(0!==a.dyn_ltree[18]||0!==a.dyn_ltree[20]||0!==a.dyn_ltree[26])return H;for(b=32;P>b;b++)if(0!==a.dyn_ltree[2*b])return H;return G}function z(a){pb||(m(),pb=!0),a.l_desc=new ob(a.dyn_ltree,kb),a.d_desc=new ob(a.dyn_dtree,lb),a.bl_desc=new ob(a.bl_tree,mb),a.bi_buf=0,a.bi_valid=0,n(a)}function A(a,b,c,d){g(a,(J<<1)+(d?1:0),3),p(a,b,c,!0)}function B(a){g(a,K<<1,3),h(a,X,eb),j(a)}function C(a,b,c,d){var e,f,h=0;a.level>0?(a.strm.data_type===I&&(a.strm.data_type=y(a)),t(a,a.l_desc),t(a,a.d_desc),h=w(a),e=a.opt_len+3+7>>>3,f=a.static_len+3+7>>>3,e>=f&&(e=f)):e=f=c+5,e>=c+4&&-1!==b?A(a,b,c,d):a.strategy===F||f===e?(g(a,(K<<1)+(d?1:0),3),s(a,eb,fb)):(g(a,(L<<1)+(d?1:0),3),x(a,a.l_desc.max_code+1,a.d_desc.max_code+1,h+1),s(a,a.dyn_ltree,a.dyn_dtree)),n(a),d&&o(a)}function D(a,b,c){return a.pending_buf[a.d_buf+2*a.last_lit]=b>>>8&255,a.pending_buf[a.d_buf+2*a.last_lit+1]=255&b,a.pending_buf[a.l_buf+a.last_lit]=255&c,a.last_lit++,0===b?a.dyn_ltree[2*c]++:(a.matches++,b--,a.dyn_ltree[2*(hb[c]+P+1)]++,a.dyn_dtree[2*e(b)]++),a.last_lit===a.lit_bufsize-1}var E=a("../utils/common"),F=4,G=0,H=1,I=2,J=0,K=1,L=2,M=3,N=258,O=29,P=256,Q=P+1+O,R=30,S=19,T=2*Q+1,U=15,V=16,W=7,X=256,Y=16,Z=17,$=18,_=[0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0],ab=[0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13],bb=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,3,7],cb=[16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15],db=512,eb=new Array(2*(Q+2));d(eb);var fb=new Array(2*R);d(fb);var gb=new Array(db);d(gb);var hb=new Array(N-M+1);d(hb);var ib=new Array(O);d(ib);var jb=new Array(R);d(jb);var kb,lb,mb,nb=function(a,b,c,d,e){this.static_tree=a,this.extra_bits=b,this.extra_base=c,this.elems=d,this.max_length=e,this.has_stree=a&&a.length},ob=function(a,b){this.dyn_tree=a,this.max_code=0,this.stat_desc=b},pb=!1;c._tr_init=z,c._tr_stored_block=A,c._tr_flush_block=C,c._tr_tally=D,c._tr_align=B},{"../utils/common":27}],39:[function(a,b){"use strict";function c(){this.input=null,this.next_in=0,this.avail_in=0,this.total_in=0,this.output=null,this.next_out=0,this.avail_out=0,this.total_out=0,this.msg="",this.state=null,this.data_type=2,this.adler=0}b.exports=c},{}]},{},[9])(9)});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment