Created
October 24, 2019 16:25
-
-
Save hydrargyrum/d637d8e6d2cc6df5d9890b6adc25f11f to your computer and use it in GitHub Desktop.
revealjs audio
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/***************************************************************** | |
** 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)}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
'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; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/***************************************************************** | |
** 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