From ce029d27ec7cf8c9e7320c35c928814009b89b49 Mon Sep 17 00:00:00 2001 From: Muaz Khan Date: Thu, 5 May 2016 11:33:40 +0500 Subject: [PATCH] Added msr@1.3.1 Fixed MediaRecorder implementation --- AudioStreamRecorder/MediaRecorderWrapper.js | 19 ++++- MediaStreamRecorder.js | 35 ++++++-- MediaStreamRecorder.min.js | 4 +- README.md | 95 +++------------------ bower.json | 4 +- common/MediaStreamRecorder.js | 14 ++- demos/audio-recorder.html | 12 ++- demos/video-recorder.html | 2 +- package.json | 4 +- 9 files changed, 86 insertions(+), 103 deletions(-) diff --git a/AudioStreamRecorder/MediaRecorderWrapper.js b/AudioStreamRecorder/MediaRecorderWrapper.js index 91863c5..59a47f4 100644 --- a/AudioStreamRecorder/MediaRecorderWrapper.js +++ b/AudioStreamRecorder/MediaRecorderWrapper.js @@ -98,6 +98,9 @@ function MediaRecorderWrapper(mediaStream) { }); self.ondataavailable(blob); + + // record next interval + self.start(timeSlice); }; mediaRecorder.onerror = function(error) { @@ -133,7 +136,19 @@ function MediaRecorderWrapper(mediaStream) { // handler. "mTimeSlice < 0" means Session object does not push encoded data to // onDataAvailable, instead, it passive wait the client side pull encoded data // by calling requestData API. - mediaRecorder.start(timeSlice || 3.6e+6); + mediaRecorder.start(3.6e+6); + + setTimeout(function() { + if (!mediaRecorder) { + return; + } + + if (mediaRecorder.state === 'recording') { + // "stop" method auto invokes "requestData"! + mediaRecorder.requestData(); + mediaRecorder.stop(); + } + }, timeSlice); // Start recording. If timeSlice has been provided, mediaRecorder will // raise a dataavailable event containing the Blob of collected data on every timeSlice milliseconds. @@ -254,8 +269,6 @@ function MediaRecorderWrapper(mediaStream) { return true; } - var self = this; - // this method checks if media stream is stopped // or any track is ended. (function looper() { diff --git a/MediaStreamRecorder.js b/MediaStreamRecorder.js index 8517c79..5387a5c 100644 --- a/MediaStreamRecorder.js +++ b/MediaStreamRecorder.js @@ -1,4 +1,4 @@ -// Last time updated: 2016-05-05 5:44:00 AM UTC +// Last time updated: 2016-05-05 6:31:59 AM UTC // links: // Open-Sourced: https://github.com/streamproc/MediaStreamRecorder @@ -50,6 +50,11 @@ function MediaStreamRecorder(mediaStream) { Recorder = GifRecorder; } + // audio/wav is supported only via StereoAudioRecorder + if (this.mimeType === 'audio/wav') { + Recorder = StereoAudioRecorder; + } + // allows forcing StereoAudioRecorder.js on Edge/Firefox if (this.recorderType) { Recorder = this.recorderType; @@ -127,7 +132,14 @@ function MediaStreamRecorder(mediaStream) { console.log('Resumed recording.', this.mimeType || mediaRecorder.mimeType); }; - this.recorderType = null; // StereoAudioRecorder || WhammyRecorder || MediaRecorderWrapper || GifRecorder + // StereoAudioRecorder || WhammyRecorder || MediaRecorderWrapper || GifRecorder + this.recorderType = null; + + // video/webm or audio/webm or audio/ogg or audio/wav + this.mimeType = 'video/webm'; + + // logs are enabled by default + this.disableLogs = false; // Reference to "MediaRecorder.js" var mediaRecorder; @@ -680,6 +692,9 @@ function MediaRecorderWrapper(mediaStream) { }); self.ondataavailable(blob); + + // record next interval + self.start(timeSlice); }; mediaRecorder.onerror = function(error) { @@ -715,7 +730,19 @@ function MediaRecorderWrapper(mediaStream) { // handler. "mTimeSlice < 0" means Session object does not push encoded data to // onDataAvailable, instead, it passive wait the client side pull encoded data // by calling requestData API. - mediaRecorder.start(timeSlice || 3.6e+6); + mediaRecorder.start(3.6e+6); + + setTimeout(function() { + if (!mediaRecorder) { + return; + } + + if (mediaRecorder.state === 'recording') { + // "stop" method auto invokes "requestData"! + mediaRecorder.requestData(); + mediaRecorder.stop(); + } + }, timeSlice); // Start recording. If timeSlice has been provided, mediaRecorder will // raise a dataavailable event containing the Blob of collected data on every timeSlice milliseconds. @@ -836,8 +863,6 @@ function MediaRecorderWrapper(mediaStream) { return true; } - var self = this; - // this method checks if media stream is stopped // or any track is ended. (function looper() { diff --git a/MediaStreamRecorder.min.js b/MediaStreamRecorder.min.js index 9a9be27..4aae5de 100644 --- a/MediaStreamRecorder.min.js +++ b/MediaStreamRecorder.min.js @@ -1,3 +1,3 @@ -// Last time updated: 2016-05-05 5:44:00 AM UTC +// Last time updated: 2016-05-05 6:31:59 AM UTC -function MediaStreamRecorder(mediaStream){if(!mediaStream)throw"MediaStream is mandatory.";this.start=function(timeSlice){var Recorder;"undefined"!=typeof MediaRecorder?Recorder=MediaRecorderWrapper:(IsChrome||IsOpera||IsEdge)&&(-1!==this.mimeType.indexOf("video")?Recorder=WhammyRecorder:-1!==this.mimeType.indexOf("audio")&&(Recorder=StereoAudioRecorder)),"image/gif"===this.mimeType&&(Recorder=GifRecorder),this.recorderType&&(Recorder=this.recorderType),mediaRecorder=new Recorder(mediaStream),mediaRecorder.blobs=[];var self=this;mediaRecorder.ondataavailable=function(data){mediaRecorder.blobs.push(data),self.ondataavailable(data)},mediaRecorder.onstop=this.onstop,mediaRecorder.onStartedDrawingNonBlankFrames=this.onStartedDrawingNonBlankFrames,mediaRecorder=mergeProps(mediaRecorder,this),mediaRecorder.start(timeSlice)},this.onStartedDrawingNonBlankFrames=function(){},this.clearOldRecordedFrames=function(){mediaRecorder&&mediaRecorder.clearOldRecordedFrames()},this.stop=function(){mediaRecorder&&mediaRecorder.stop()},this.ondataavailable=function(blob){console.log("ondataavailable..",blob)},this.onstop=function(error){console.warn("stopped..",error)},this.save=function(file,fileName){if(!file){if(!mediaRecorder)return;var bigBlob=new Blob(mediaRecorder.blobs,{type:mediaRecorder.blobs[0].type||this.mimeType});return void invokeSaveAsDialog(bigBlob)}invokeSaveAsDialog(file,fileName)},this.pause=function(){mediaRecorder&&(mediaRecorder.pause(),console.log("Paused recording.",this.mimeType||mediaRecorder.mimeType))},this.resume=function(){mediaRecorder&&(mediaRecorder.resume(),console.log("Resumed recording.",this.mimeType||mediaRecorder.mimeType))},this.recorderType=null;var mediaRecorder}function MultiStreamRecorder(mediaStream){if(!mediaStream)throw"MediaStream is mandatory.";var self=this,isMediaRecorder=isMediaRecorderCompatible();this.stream=mediaStream,this.start=function(timeSlice){function fireOnDataAvailableEvent(blobs){recordingInterval++,self.ondataavailable(blobs)}audioRecorder=new MediaStreamRecorder(mediaStream),videoRecorder=new MediaStreamRecorder(mediaStream),audioRecorder.mimeType="audio/ogg",videoRecorder.mimeType="video/webm";for(var prop in this)"function"!=typeof this[prop]&&(audioRecorder[prop]=videoRecorder[prop]=this[prop]);audioRecorder.ondataavailable=function(blob){audioVideoBlobs[recordingInterval]||(audioVideoBlobs[recordingInterval]={}),audioVideoBlobs[recordingInterval].audio=blob,audioVideoBlobs[recordingInterval].video&&!audioVideoBlobs[recordingInterval].onDataAvailableEventFired&&(audioVideoBlobs[recordingInterval].onDataAvailableEventFired=!0,fireOnDataAvailableEvent(audioVideoBlobs[recordingInterval]))},videoRecorder.ondataavailable=function(blob){return isMediaRecorder?self.ondataavailable({video:blob,audio:blob}):(audioVideoBlobs[recordingInterval]||(audioVideoBlobs[recordingInterval]={}),audioVideoBlobs[recordingInterval].video=blob,void(audioVideoBlobs[recordingInterval].audio&&!audioVideoBlobs[recordingInterval].onDataAvailableEventFired&&(audioVideoBlobs[recordingInterval].onDataAvailableEventFired=!0,fireOnDataAvailableEvent(audioVideoBlobs[recordingInterval]))))},videoRecorder.onstop=audioRecorder.onstop=function(error){self.onstop(error)},isMediaRecorder?videoRecorder.start(timeSlice):(videoRecorder.onStartedDrawingNonBlankFrames=function(){videoRecorder.clearOldRecordedFrames(),audioRecorder.start(timeSlice)},videoRecorder.start(timeSlice))},this.stop=function(){audioRecorder&&audioRecorder.stop(),videoRecorder&&videoRecorder.stop()},this.ondataavailable=function(blob){console.log("ondataavailable..",blob)},this.onstop=function(error){console.warn("stopped..",error)},this.pause=function(){audioRecorder&&audioRecorder.pause(),videoRecorder&&videoRecorder.pause()},this.resume=function(){audioRecorder&&audioRecorder.resume(),videoRecorder&&videoRecorder.resume()};var audioRecorder,videoRecorder,audioVideoBlobs={},recordingInterval=0}function mergeProps(mergein,mergeto){for(var t in mergeto)"function"!=typeof mergeto[t]&&(mergein[t]=mergeto[t]);return mergein}function dropFirstFrame(arr){return arr.shift(),arr}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&&-1!==fileName.indexOf(".")){var splitted=fileName.split(".");fileName=splitted[0],fileExtension=splitted[1]}var fileFullName=(fileName||Math.round(9999999999*Math.random())+888888888)+"."+fileExtension;if("undefined"!=typeof navigator.msSaveOrOpenBlob)return navigator.msSaveOrOpenBlob(file,fileFullName);if("undefined"!=typeof navigator.msSaveBlob)return navigator.msSaveBlob(file,fileFullName);var hyperlink=document.createElement("a");hyperlink.href=URL.createObjectURL(file),hyperlink.target="_blank",hyperlink.download=fileFullName,navigator.mozGetUserMedia&&(hyperlink.onclick=function(){(document.body||document.documentElement).removeChild(hyperlink)},(document.body||document.documentElement).appendChild(hyperlink));var evt=new MouseEvent("click",{view:window,bubbles:!0,cancelable:!0});hyperlink.dispatchEvent(evt),navigator.mozGetUserMedia||URL.revokeObjectURL(hyperlink.href)}function bytesToSize(bytes){var k=1e3,sizes=["Bytes","KB","MB","GB","TB"];if(0===bytes)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]}function isMediaRecorderCompatible(){var isOpera=!!window.opera||navigator.userAgent.indexOf(" OPR/")>=0,isChrome=!!window.chrome&&!isOpera,isFirefox="undefined"!=typeof window.InstallTrigger;if(isFirefox)return!0;if(!isChrome)return!1;var verOffset,ix,nAgt=(navigator.appVersion,navigator.userAgent),fullVersion=""+parseFloat(navigator.appVersion),majorVersion=parseInt(navigator.appVersion,10);return isChrome&&(verOffset=nAgt.indexOf("Chrome"),fullVersion=nAgt.substring(verOffset+7)),-1!==(ix=fullVersion.indexOf(";"))&&(fullVersion=fullVersion.substring(0,ix)),-1!==(ix=fullVersion.indexOf(" "))&&(fullVersion=fullVersion.substring(0,ix)),majorVersion=parseInt(""+fullVersion,10),isNaN(majorVersion)&&(fullVersion=""+parseFloat(navigator.appVersion),majorVersion=parseInt(navigator.appVersion,10)),majorVersion>=49}function MediaRecorderWrapper(mediaStream){function isMediaStreamActive(){if("active"in mediaStream){if(!mediaStream.active)return!1}else if("ended"in mediaStream&&mediaStream.ended)return!1;return!0}var self=this;this.start=function(timeSlice){if(self.mimeType||(self.mimeType="video/webm"),-1!==self.mimeType.indexOf("audio")&&mediaStream.getVideoTracks().length&&mediaStream.getAudioTracks().length){var stream;navigator.mozGetUserMedia?(stream=new MediaStream,stream.addTrack(mediaStream.getAudioTracks()[0])):stream=new MediaStream(mediaStream.getAudioTracks()),mediaStream=stream}-1!==self.mimeType.indexOf("audio")&&(self.mimeType=IsChrome?"audio/webm":"audio/ogg"),self.blob=null;var recorderHints={mimeType:self.mimeType};self.disableLogs||console.log("Passing following params over MediaRecorder API.",recorderHints),mediaRecorder&&(mediaRecorder=null),IsChrome&&!isMediaRecorderCompatible()&&(recorderHints="video/vp8"),mediaRecorder=new MediaRecorder(mediaStream,recorderHints),"canRecordMimeType"in mediaRecorder&&mediaRecorder.canRecordMimeType(self.mimeType)===!1&&(self.disableLogs||console.warn("MediaRecorder API seems unable to record mimeType:",self.mimeType)),mediaRecorder.ignoreMutedMedia=self.ignoreMutedMedia||!1,mediaRecorder.ondataavailable=function(e){if(!self.dontFireOnDataAvailableEvent&&e.data&&e.data.size&&!(e.data.size<100)&&!self.blob){var blob=self.getNativeBlob?e.data:new Blob([e.data],{type:self.mimeType||"video/webm"});self.ondataavailable(blob)}},mediaRecorder.onerror=function(error){self.disableLogs||("InvalidState"===error.name?console.error("The MediaRecorder is not in a state in which the proposed operation is allowed to be executed."):"OutOfMemory"===error.name?console.error("The UA has exhaused the available memory. User agents SHOULD provide as much additional information as possible in the message attribute."):"IllegalStreamModification"===error.name?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."):"OtherRecordingError"===error.name?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."):"GenericError"===error.name?console.error("The UA cannot provide the codec or recording option that has been requested.",error):console.error("MediaRecorder Error",error)),"inactive"!==mediaRecorder.state&&"stopped"!==mediaRecorder.state&&mediaRecorder.stop()},mediaRecorder.start(timeSlice||36e5)},this.stop=function(callback){mediaRecorder&&"recording"===mediaRecorder.state&&(mediaRecorder.requestData(),mediaRecorder.stop())},this.pause=function(){mediaRecorder&&"recording"===mediaRecorder.state&&mediaRecorder.pause()},this.ondataavailable=function(blob){console.log("recorded-blob",blob)},this.resume=function(){if(this.dontFireOnDataAvailableEvent){this.dontFireOnDataAvailableEvent=!1;var disableLogs=self.disableLogs;return self.disableLogs=!0,this.record(),void(self.disableLogs=disableLogs)}mediaRecorder&&"paused"===mediaRecorder.state&&mediaRecorder.resume()},this.clearRecordedData=function(){mediaRecorder&&(this.pause(),this.dontFireOnDataAvailableEvent=!0,this.stop())};var mediaRecorder,self=this;!function looper(){return mediaRecorder?isMediaStreamActive()===!1?void self.stop():void setTimeout(looper,1e3):void 0}()}function StereoAudioRecorder(mediaStream){this.start=function(timeSlice){timeSlice=timeSlice||1e3,mediaRecorder=new StereoAudioRecorderHelper(mediaStream,this),mediaRecorder.record(),timeout=setInterval(function(){mediaRecorder.requestData()},timeSlice)},this.stop=function(){mediaRecorder&&(mediaRecorder.stop(),clearTimeout(timeout))},this.pause=function(){mediaRecorder&&mediaRecorder.pause()},this.resume=function(){mediaRecorder&&mediaRecorder.resume()},this.ondataavailable=function(){};var mediaRecorder,timeout}function StereoAudioRecorderHelper(mediaStream,root){function interleave(leftChannel,rightChannel){for(var length=leftChannel.length+rightChannel.length,result=new Float32Array(length),inputIndex=0,index=0;length>index;)result[index++]=leftChannel[inputIndex],result[index++]=rightChannel[inputIndex],inputIndex++;return result}function mergeBuffers(channelBuffer,recordingLength){for(var result=new Float32Array(recordingLength),offset=0,lng=channelBuffer.length,i=0;lng>i;i++){var buffer=channelBuffer[i];result.set(buffer,offset),offset+=buffer.length}return result}function writeUTFBytes(view,offset,string){for(var lng=string.length,i=0;lng>i;i++)view.setUint8(offset+i,string.charCodeAt(i))}var deviceSampleRate=44100;ObjectStore.AudioContextConstructor||(ObjectStore.AudioContextConstructor=new ObjectStore.AudioContext),deviceSampleRate=ObjectStore.AudioContextConstructor.sampleRate;var scriptprocessornode,volume,audioInput,context,leftchannel=[],rightchannel=[],recording=!1,recordingLength=0,sampleRate=root.sampleRate||deviceSampleRate,numChannels=root.audioChannels||2;this.record=function(){recording=!0,leftchannel.length=rightchannel.length=0,recordingLength=0},this.requestData=function(){if(!isPaused){if(0===recordingLength)return void(requestDataInvoked=!1);requestDataInvoked=!0;var internalLeftChannel=leftchannel.slice(0),internalRecordingLength=(rightchannel.slice(0),recordingLength);leftchannel.length=rightchannel.length=[],recordingLength=0,requestDataInvoked=!1;var leftBuffer=mergeBuffers(internalLeftChannel,internalRecordingLength),rightBuffer=mergeBuffers(internalLeftChannel,internalRecordingLength);if(2===numChannels)var interleaved=interleave(leftBuffer,rightBuffer);else var interleaved=leftBuffer;var buffer=new ArrayBuffer(44+2*interleaved.length),view=new DataView(buffer);writeUTFBytes(view,0,"RIFF"),view.setUint32(4,44+2*interleaved.length,!0),writeUTFBytes(view,8,"WAVE"),writeUTFBytes(view,12,"fmt "),view.setUint32(16,16,!0),view.setUint16(20,1,!0),view.setUint16(22,numChannels,!0),view.setUint32(24,sampleRate,!0),view.setUint32(28,4*sampleRate,!0),view.setUint16(32,2*numChannels,!0),view.setUint16(34,16,!0),writeUTFBytes(view,36,"data"),view.setUint32(40,2*interleaved.length,!0);for(var lng=interleaved.length,index=44,volume=1,i=0;lng>i;i++)view.setInt16(index,interleaved[i]*(32767*volume),!0),index+=2;var blob=new Blob([view],{type:"audio/wav"});console.debug("audio recorded blob size:",bytesToSize(blob.size)),root.ondataavailable(blob)}},this.stop=function(){recording=!1,this.requestData(),audioInput.disconnect()};var context=ObjectStore.AudioContextConstructor;ObjectStore.VolumeGainNode=context.createGain();var volume=ObjectStore.VolumeGainNode;ObjectStore.AudioInput=context.createMediaStreamSource(mediaStream);var audioInput=ObjectStore.AudioInput;audioInput.connect(volume);var bufferSize=root.bufferSize||2048;if(0===root.bufferSize&&(bufferSize=0),context.createJavaScriptNode)scriptprocessornode=context.createJavaScriptNode(bufferSize,numChannels,numChannels);else{if(!context.createScriptProcessor)throw"WebAudio API has no support on this browser.";scriptprocessornode=context.createScriptProcessor(bufferSize,numChannels,numChannels)}bufferSize=scriptprocessornode.bufferSize,console.debug("using audio buffer-size:",bufferSize);var requestDataInvoked=!1;window.scriptprocessornode=scriptprocessornode,1===numChannels&&console.debug("All right-channels are skipped.");var isPaused=!1;this.pause=function(){isPaused=!0},this.resume=function(){isPaused=!1},scriptprocessornode.onaudioprocess=function(e){if(recording&&!requestDataInvoked&&!isPaused){var left=e.inputBuffer.getChannelData(0);if(leftchannel.push(new Float32Array(left)),2===numChannels){var right=e.inputBuffer.getChannelData(1);rightchannel.push(new Float32Array(right))}recordingLength+=bufferSize}},volume.connect(scriptprocessornode),scriptprocessornode.connect(context.destination)}function WhammyRecorder(mediaStream){this.start=function(timeSlice){timeSlice=timeSlice||1e3,mediaRecorder=new WhammyRecorderHelper(mediaStream,this);for(var prop in this)"function"!=typeof this[prop]&&(mediaRecorder[prop]=this[prop]);mediaRecorder.record(),timeout=setInterval(function(){mediaRecorder.requestData()},timeSlice)},this.stop=function(){mediaRecorder&&(mediaRecorder.stop(),clearTimeout(timeout))},this.clearOldRecordedFrames=function(){mediaRecorder&&mediaRecorder.clearOldRecordedFrames()},this.pause=function(){mediaRecorder&&mediaRecorder.pause()},this.resume=function(){mediaRecorder&&mediaRecorder.resume()},this.ondataavailable=function(){};var mediaRecorder,timeout}function WhammyRecorderHelper(mediaStream,root){function drawFrames(){if(isPaused)return lastTime=(new Date).getTime(),void setTimeout(drawFrames,500);if(!isStopDrawing){if(requestDataInvoked)return setTimeout(drawFrames,100);var duration=(new Date).getTime()-lastTime;if(!duration)return drawFrames();lastTime=(new Date).getTime(),!self.isHTMLObject&&video.paused&&video.play(),context.drawImage(video,0,0,canvas.width,canvas.height),isStopDrawing||whammy.frames.push({duration:duration,image:canvas.toDataURL("image/webp")}),isOnStartedDrawingNonBlankFramesInvoked||isBlankFrame(whammy.frames[whammy.frames.length-1])||(isOnStartedDrawingNonBlankFramesInvoked=!0,root.onStartedDrawingNonBlankFrames()),setTimeout(drawFrames,10)}}function isBlankFrame(frame,_pixTolerance,_frameTolerance){var localCanvas=document.createElement("canvas");localCanvas.width=canvas.width,localCanvas.height=canvas.height;var matchPixCount,endPixCheck,maxPixCount,context2d=localCanvas.getContext("2d"),sampleColor={r:0,g:0,b:0},maxColorDifference=Math.sqrt(Math.pow(255,2)+Math.pow(255,2)+Math.pow(255,2)),pixTolerance=_pixTolerance&&_pixTolerance>=0&&1>=_pixTolerance?_pixTolerance:0,frameTolerance=_frameTolerance&&_frameTolerance>=0&&1>=_frameTolerance?_frameTolerance:0,image=new Image;image.src=frame.image,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;endPixCheck>pix;pix+=4){var currentColor={r:imageData.data[pix],g:imageData.data[pix+1],b:imageData.data[pix+2]},colorDifference=Math.sqrt(Math.pow(currentColor.r-sampleColor.r,2)+Math.pow(currentColor.g-sampleColor.g,2)+Math.pow(currentColor.b-sampleColor.b,2));maxColorDifference*pixTolerance>=colorDifference&&matchPixCount++}return maxPixCount*frameTolerance>=maxPixCount-matchPixCount?!1:!0}function dropBlackFrames(_frames,_framesToCheck,_pixTolerance,_frameTolerance){var localCanvas=document.createElement("canvas");localCanvas.width=canvas.width,localCanvas.height=canvas.height;for(var context2d=localCanvas.getContext("2d"),resultFrames=[],checkUntilNotBlack=-1===_framesToCheck,endCheckFrame=_framesToCheck&&_framesToCheck>0&&_framesToCheck<=_frames.length?_framesToCheck:_frames.length,sampleColor={r:0,g:0,b:0},maxColorDifference=Math.sqrt(Math.pow(255,2)+Math.pow(255,2)+Math.pow(255,2)),pixTolerance=_pixTolerance&&_pixTolerance>=0&&1>=_pixTolerance?_pixTolerance:0,frameTolerance=_frameTolerance&&_frameTolerance>=0&&1>=_frameTolerance?_frameTolerance:0,doNotCheckNext=!1,f=0;endCheckFrame>f;f++){var matchPixCount,endPixCheck,maxPixCount;if(!doNotCheckNext){var image=new Image;image.src=_frames[f].image,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;endPixCheck>pix;pix+=4){var currentColor={r:imageData.data[pix],g:imageData.data[pix+1],b:imageData.data[pix+2]},colorDifference=Math.sqrt(Math.pow(currentColor.r-sampleColor.r,2)+Math.pow(currentColor.g-sampleColor.g,2)+Math.pow(currentColor.b-sampleColor.b,2));maxColorDifference*pixTolerance>=colorDifference&&matchPixCount++}}!doNotCheckNext&&maxPixCount*frameTolerance>=maxPixCount-matchPixCount||(checkUntilNotBlack&&(doNotCheckNext=!0),resultFrames.push(_frames[f]))}return resultFrames=resultFrames.concat(_frames.slice(endCheckFrame)),resultFrames.length<=0&&resultFrames.push(_frames[_frames.length-1]),resultFrames}this.record=function(timeSlice){this.width||(this.width=320),this.height||(this.height=240),this.video&&this.video instanceof HTMLVideoElement&&(this.width||(this.width=video.videoWidth||video.clientWidth||320),this.height||(this.height=video.videoHeight||video.clientHeight||240)),this.video||(this.video={width:this.width,height:this.height}),this.canvas&&this.canvas.width&&this.canvas.height||(this.canvas={width:this.width,height:this.height}),canvas.width=this.canvas.width,canvas.height=this.canvas.height,this.video&&this.video instanceof HTMLVideoElement?(this.isHTMLObject=!0,video=this.video.cloneNode()):(video=document.createElement("video"),video.src=URL.createObjectURL(mediaStream),video.width=this.video.width,video.height=this.video.height),video.muted=!0,video.play(),lastTime=(new Date).getTime(),whammy=new Whammy.Video(root.speed,root.quality),console.log("canvas resolutions",canvas.width,"*",canvas.height),console.log("video width/height",video.width||canvas.width,"*",video.height||canvas.height),drawFrames()},this.clearOldRecordedFrames=function(){whammy.frames=[]};var requestDataInvoked=!1;this.requestData=function(){if(!isPaused){if(!whammy.frames.length)return void(requestDataInvoked=!1);requestDataInvoked=!0;var internalFrames=whammy.frames.slice(0);whammy.frames=dropBlackFrames(internalFrames,-1),whammy.compile(function(whammyBlob){root.ondataavailable(whammyBlob),console.debug("video recorded blob size:",bytesToSize(whammyBlob.size))}),whammy.frames=[],requestDataInvoked=!1}};var isOnStartedDrawingNonBlankFramesInvoked=!1,isStopDrawing=!1;this.stop=function(){isStopDrawing=!0,this.requestData()};var video,lastTime,whammy,canvas=document.createElement("canvas"),context=canvas.getContext("2d"),self=this,isPaused=!1;this.pause=function(){isPaused=!0},this.resume=function(){isPaused=!1}}function GifRecorder(mediaStream){function doneRecording(){endTime=Date.now();var gifBlob=new Blob([new Uint8Array(gifEncoder.stream().bin)],{type:"image/gif"});self.ondataavailable(gifBlob),gifEncoder.stream().bin=[]}if("undefined"==typeof GIFEncoder)throw"Please link: https://cdn.webrtc-experiment.com/gif-recorder.js";this.start=function(timeSlice){function drawVideoFrame(time){return isPaused?void setTimeout(drawVideoFrame,500,time):(lastAnimationFrame=requestAnimationFrame(drawVideoFrame),void 0===typeof lastFrameTime&&(lastFrameTime=time),void(90>time-lastFrameTime||(video.paused&&video.play(),context.drawImage(video,0,0,imageWidth,imageHeight),gifEncoder.addFrame(context),lastFrameTime=time)))}timeSlice=timeSlice||1e3;var imageWidth=this.videoWidth||320,imageHeight=this.videoHeight||240;canvas.width=video.width=imageWidth,canvas.height=video.height=imageHeight,gifEncoder=new GIFEncoder,gifEncoder.setRepeat(0),gifEncoder.setDelay(this.frameRate||this.speed||200),gifEncoder.setQuality(this.quality||1),gifEncoder.start(),startTime=Date.now(),lastAnimationFrame=requestAnimationFrame(drawVideoFrame),timeout=setTimeout(doneRecording,timeSlice)},this.stop=function(){lastAnimationFrame&&(cancelAnimationFrame(lastAnimationFrame),clearTimeout(timeout),doneRecording())};var isPaused=!1;this.pause=function(){isPaused=!0},this.resume=function(){isPaused=!1},this.ondataavailable=function(){},this.onstop=function(){};var self=this,canvas=document.createElement("canvas"),context=canvas.getContext("2d"),video=document.createElement("video");video.muted=!0,video.autoplay=!0,video.src=URL.createObjectURL(mediaStream),video.play();var startTime,endTime,lastFrameTime,gifEncoder,timeout,lastAnimationFrame=null}"undefined"!=typeof MediaStreamRecorder&&(MediaStreamRecorder.MultiStreamRecorder=MultiStreamRecorder);var browserFakeUserAgent="Fake/5.0 (FakeOS) AppleWebKit/123 (KHTML, like Gecko) Fake/12.3.4567.89 Fake/123.45";!function(that){"undefined"==typeof window&&("undefined"==typeof window&&"undefined"!=typeof global?(global.navigator={userAgent:browserFakeUserAgent,getUserMedia:function(){}},that.window=global):"undefined"==typeof window,"undefined"==typeof document&&(that.document={},document.createElement=document.captureStream=document.mozCaptureStream=function(){return{}}),"undefined"==typeof location&&(that.location={protocol:"file:",href:"",hash:""}),"undefined"==typeof screen&&(that.screen={width:0,height:0}))}("undefined"!=typeof global?global:window);var AudioContext=window.AudioContext;"undefined"==typeof AudioContext&&("undefined"!=typeof webkitAudioContext&&(AudioContext=webkitAudioContext),"undefined"!=typeof mozAudioContext&&(AudioContext=mozAudioContext)),"undefined"==typeof window&&(window={});var AudioContext=window.AudioContext;"undefined"==typeof AudioContext&&("undefined"!=typeof webkitAudioContext&&(AudioContext=webkitAudioContext),"undefined"!=typeof mozAudioContext&&(AudioContext=mozAudioContext));var URL=window.URL;"undefined"==typeof URL&&"undefined"!=typeof webkitURL&&(URL=webkitURL),"undefined"!=typeof navigator?("undefined"!=typeof navigator.webkitGetUserMedia&&(navigator.getUserMedia=navigator.webkitGetUserMedia),"undefined"!=typeof navigator.mozGetUserMedia&&(navigator.getUserMedia=navigator.mozGetUserMedia)):navigator={getUserMedia:function(){},userAgent:browserFakeUserAgent};var IsEdge=!(-1===navigator.userAgent.indexOf("Edge")||!navigator.msSaveBlob&&!navigator.msSaveOrOpenBlob),IsOpera=!1;"undefined"!=typeof opera&&navigator.userAgent&&-1!==navigator.userAgent.indexOf("OPR/")&&(IsOpera=!0);var IsChrome=!IsEdge&&!IsEdge&&!!navigator.webkitGetUserMedia,MediaStream=window.MediaStream;"undefined"==typeof MediaStream&&"undefined"!=typeof webkitMediaStream&&(MediaStream=webkitMediaStream),"undefined"!=typeof MediaStream&&("getVideoTracks"in MediaStream.prototype||(MediaStream.prototype.getVideoTracks=function(){if(!this.getTracks)return[];var tracks=[];return this.getTracks.forEach(function(track){-1!==track.kind.toString().indexOf("video")&&tracks.push(track)}),tracks},MediaStream.prototype.getAudioTracks=function(){if(!this.getTracks)return[];var tracks=[];return this.getTracks.forEach(function(track){-1!==track.kind.toString().indexOf("audio")&&tracks.push(track)}),tracks}),"stop"in MediaStream.prototype||(MediaStream.prototype.stop=function(){this.getAudioTracks().forEach(function(track){track.stop&&track.stop()}),this.getVideoTracks().forEach(function(track){track.stop&&track.stop()})})),"undefined"!=typeof location&&0===location.href.indexOf("file:")&&console.error("Please load this HTML file on HTTP or HTTPS.");var ObjectStore={AudioContext:AudioContext},ObjectStore={AudioContext:window.AudioContext||window.webkitAudioContext};"undefined"!=typeof MediaStreamRecorder&&(MediaStreamRecorder.MediaRecorderWrapper=MediaRecorderWrapper),"undefined"!=typeof MediaStreamRecorder&&(MediaStreamRecorder.StereoAudioRecorder=StereoAudioRecorder),"undefined"!=typeof MediaStreamRecorder&&(MediaStreamRecorder.StereoAudioRecorderHelper=StereoAudioRecorderHelper),"undefined"!=typeof MediaStreamRecorder&&(MediaStreamRecorder.WhammyRecorder=WhammyRecorder),"undefined"!=typeof MediaStreamRecorder&&(MediaStreamRecorder.WhammyRecorderHelper=WhammyRecorderHelper),"undefined"!=typeof MediaStreamRecorder&&(MediaStreamRecorder.GifRecorder=GifRecorder);var Whammy=function(){function WhammyVideo(duration,quality){this.frames=[],duration||(duration=1),this.duration=1e3/duration,this.quality=quality||.8}function processInWebWorker(_function){var blob=URL.createObjectURL(new Blob([_function.toString(),"this.onmessage = function (e) {"+_function.name+"(e.data);}"],{type:"application/javascript"})),worker=new Worker(blob);return URL.revokeObjectURL(blob),worker}function whammyInWebWorker(frames){function ArrayToWebM(frames){var info=checkFrames(frames);if(!info)return[];for(var clusterMaxDuration=3e4,EBML=[{id:440786851,data:[{data:1,id:17030},{data:1,id:17143},{data:4,id:17138},{data:8,id:17139},{data:"webm",id:17026},{data:2,id:17031},{data:2,id:17029}]},{id:408125543,data:[{id:357149030,data:[{data:1e6,id:2807729},{data:"whammy",id:19840},{data:"whammy",id:22337},{data:doubleToString(info.duration),id:17545}]},{id:374648427,data:[{id:174,data:[{data:1,id:215},{data:1,id:29637},{data:0,id:156},{data:"und",id:2274716},{data:"V_VP8",id:134},{data:"VP8",id:2459272},{data:1,id:131},{id:224,data:[{data:info.width,id:176},{data:info.height,id:186}]}]}]}]}],frameNumber=0,clusterTimecode=0;frameNumberclusterDuration);var clusterCounter=0,cluster={id:524531317,data:getClusterData(clusterTimecode,clusterCounter,clusterFrames)};EBML[1].data.push(cluster),clusterTimecode+=clusterDuration}return generateEBML(EBML)}function getClusterData(clusterTimecode,clusterCounter,clusterFrames){return[{data:clusterTimecode,id:231}].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)});return clusterCounter+=webp.duration,{data:block,id:163}}))}function checkFrames(frames){if(!frames[0])return void postMessage({error:"Something went wrong. Maybe WebP format is not supported in the current browser."});for(var width=frames[0].width,height=frames[0].height,duration=frames[0].duration,i=1;i0;)parts.push(255&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=[],pad=bits.length%8?new Array(9-bits.length%8).join("0"):"";bits=pad+bits;for(var i=0;i127)throw"TrackNumber > 127 not supported";var out=[128|data.trackNum,data.timecode>>8,255&data.timecode,flags].map(function(e){return String.fromCharCode(e)}).join("")+data.frame;return out}function parseWebP(riff){for(var VP8=riff.RIFF[0].WEBP[0],frameStart=VP8.indexOf("*"),i=0,c=[];4>i;i++)c[i]=VP8.charCodeAt(frameStart+3+i);var width,height,tmp;return tmp=c[1]<<8|c[0],width=16383&tmp,tmp=c[3]<<8|c[2],height=16383&tmp,{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){for(var offset=0,chunks={};offset=0,isChrome=!!window.chrome&&!isOpera,isFirefox="undefined"!=typeof window.InstallTrigger;if(isFirefox)return!0;if(!isChrome)return!1;var verOffset,ix,nAgt=(navigator.appVersion,navigator.userAgent),fullVersion=""+parseFloat(navigator.appVersion),majorVersion=parseInt(navigator.appVersion,10);return isChrome&&(verOffset=nAgt.indexOf("Chrome"),fullVersion=nAgt.substring(verOffset+7)),-1!==(ix=fullVersion.indexOf(";"))&&(fullVersion=fullVersion.substring(0,ix)),-1!==(ix=fullVersion.indexOf(" "))&&(fullVersion=fullVersion.substring(0,ix)),majorVersion=parseInt(""+fullVersion,10),isNaN(majorVersion)&&(fullVersion=""+parseFloat(navigator.appVersion),majorVersion=parseInt(navigator.appVersion,10)),majorVersion>=49}function MediaRecorderWrapper(mediaStream){function isMediaStreamActive(){if("active"in mediaStream){if(!mediaStream.active)return!1}else if("ended"in mediaStream&&mediaStream.ended)return!1;return!0}var self=this;this.start=function(timeSlice){if(self.mimeType||(self.mimeType="video/webm"),-1!==self.mimeType.indexOf("audio")&&mediaStream.getVideoTracks().length&&mediaStream.getAudioTracks().length){var stream;navigator.mozGetUserMedia?(stream=new MediaStream,stream.addTrack(mediaStream.getAudioTracks()[0])):stream=new MediaStream(mediaStream.getAudioTracks()),mediaStream=stream}-1!==self.mimeType.indexOf("audio")&&(self.mimeType=IsChrome?"audio/webm":"audio/ogg"),self.blob=null;var recorderHints={mimeType:self.mimeType};self.disableLogs||console.log("Passing following params over MediaRecorder API.",recorderHints),mediaRecorder&&(mediaRecorder=null),IsChrome&&!isMediaRecorderCompatible()&&(recorderHints="video/vp8"),mediaRecorder=new MediaRecorder(mediaStream,recorderHints),"canRecordMimeType"in mediaRecorder&&mediaRecorder.canRecordMimeType(self.mimeType)===!1&&(self.disableLogs||console.warn("MediaRecorder API seems unable to record mimeType:",self.mimeType)),mediaRecorder.ignoreMutedMedia=self.ignoreMutedMedia||!1,mediaRecorder.ondataavailable=function(e){if(!self.dontFireOnDataAvailableEvent&&e.data&&e.data.size&&!(e.data.size<100)&&!self.blob){var blob=self.getNativeBlob?e.data:new Blob([e.data],{type:self.mimeType||"video/webm"});self.ondataavailable(blob),self.start(timeSlice)}},mediaRecorder.onerror=function(error){self.disableLogs||("InvalidState"===error.name?console.error("The MediaRecorder is not in a state in which the proposed operation is allowed to be executed."):"OutOfMemory"===error.name?console.error("The UA has exhaused the available memory. User agents SHOULD provide as much additional information as possible in the message attribute."):"IllegalStreamModification"===error.name?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."):"OtherRecordingError"===error.name?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."):"GenericError"===error.name?console.error("The UA cannot provide the codec or recording option that has been requested.",error):console.error("MediaRecorder Error",error)),"inactive"!==mediaRecorder.state&&"stopped"!==mediaRecorder.state&&mediaRecorder.stop()},mediaRecorder.start(36e5),setTimeout(function(){mediaRecorder&&"recording"===mediaRecorder.state&&(mediaRecorder.requestData(),mediaRecorder.stop())},timeSlice)},this.stop=function(callback){mediaRecorder&&"recording"===mediaRecorder.state&&(mediaRecorder.requestData(),mediaRecorder.stop())},this.pause=function(){mediaRecorder&&"recording"===mediaRecorder.state&&mediaRecorder.pause()},this.ondataavailable=function(blob){console.log("recorded-blob",blob)},this.resume=function(){if(this.dontFireOnDataAvailableEvent){this.dontFireOnDataAvailableEvent=!1;var disableLogs=self.disableLogs;return self.disableLogs=!0,this.record(),void(self.disableLogs=disableLogs)}mediaRecorder&&"paused"===mediaRecorder.state&&mediaRecorder.resume()},this.clearRecordedData=function(){mediaRecorder&&(this.pause(),this.dontFireOnDataAvailableEvent=!0,this.stop())};var mediaRecorder;!function looper(){return mediaRecorder?isMediaStreamActive()===!1?void self.stop():void setTimeout(looper,1e3):void 0}()}function StereoAudioRecorder(mediaStream){this.start=function(timeSlice){timeSlice=timeSlice||1e3,mediaRecorder=new StereoAudioRecorderHelper(mediaStream,this),mediaRecorder.record(),timeout=setInterval(function(){mediaRecorder.requestData()},timeSlice)},this.stop=function(){mediaRecorder&&(mediaRecorder.stop(),clearTimeout(timeout))},this.pause=function(){mediaRecorder&&mediaRecorder.pause()},this.resume=function(){mediaRecorder&&mediaRecorder.resume()},this.ondataavailable=function(){};var mediaRecorder,timeout}function StereoAudioRecorderHelper(mediaStream,root){function interleave(leftChannel,rightChannel){for(var length=leftChannel.length+rightChannel.length,result=new Float32Array(length),inputIndex=0,index=0;length>index;)result[index++]=leftChannel[inputIndex],result[index++]=rightChannel[inputIndex],inputIndex++;return result}function mergeBuffers(channelBuffer,recordingLength){for(var result=new Float32Array(recordingLength),offset=0,lng=channelBuffer.length,i=0;lng>i;i++){var buffer=channelBuffer[i];result.set(buffer,offset),offset+=buffer.length}return result}function writeUTFBytes(view,offset,string){for(var lng=string.length,i=0;lng>i;i++)view.setUint8(offset+i,string.charCodeAt(i))}var deviceSampleRate=44100;ObjectStore.AudioContextConstructor||(ObjectStore.AudioContextConstructor=new ObjectStore.AudioContext),deviceSampleRate=ObjectStore.AudioContextConstructor.sampleRate;var scriptprocessornode,volume,audioInput,context,leftchannel=[],rightchannel=[],recording=!1,recordingLength=0,sampleRate=root.sampleRate||deviceSampleRate,numChannels=root.audioChannels||2;this.record=function(){recording=!0,leftchannel.length=rightchannel.length=0,recordingLength=0},this.requestData=function(){if(!isPaused){if(0===recordingLength)return void(requestDataInvoked=!1);requestDataInvoked=!0;var internalLeftChannel=leftchannel.slice(0),internalRecordingLength=(rightchannel.slice(0),recordingLength);leftchannel.length=rightchannel.length=[],recordingLength=0,requestDataInvoked=!1;var leftBuffer=mergeBuffers(internalLeftChannel,internalRecordingLength),rightBuffer=mergeBuffers(internalLeftChannel,internalRecordingLength);if(2===numChannels)var interleaved=interleave(leftBuffer,rightBuffer);else var interleaved=leftBuffer;var buffer=new ArrayBuffer(44+2*interleaved.length),view=new DataView(buffer);writeUTFBytes(view,0,"RIFF"),view.setUint32(4,44+2*interleaved.length,!0),writeUTFBytes(view,8,"WAVE"),writeUTFBytes(view,12,"fmt "),view.setUint32(16,16,!0),view.setUint16(20,1,!0),view.setUint16(22,numChannels,!0),view.setUint32(24,sampleRate,!0),view.setUint32(28,4*sampleRate,!0),view.setUint16(32,2*numChannels,!0),view.setUint16(34,16,!0),writeUTFBytes(view,36,"data"),view.setUint32(40,2*interleaved.length,!0);for(var lng=interleaved.length,index=44,volume=1,i=0;lng>i;i++)view.setInt16(index,interleaved[i]*(32767*volume),!0),index+=2;var blob=new Blob([view],{type:"audio/wav"});console.debug("audio recorded blob size:",bytesToSize(blob.size)),root.ondataavailable(blob)}},this.stop=function(){recording=!1,this.requestData(),audioInput.disconnect()};var context=ObjectStore.AudioContextConstructor;ObjectStore.VolumeGainNode=context.createGain();var volume=ObjectStore.VolumeGainNode;ObjectStore.AudioInput=context.createMediaStreamSource(mediaStream);var audioInput=ObjectStore.AudioInput;audioInput.connect(volume);var bufferSize=root.bufferSize||2048;if(0===root.bufferSize&&(bufferSize=0),context.createJavaScriptNode)scriptprocessornode=context.createJavaScriptNode(bufferSize,numChannels,numChannels);else{if(!context.createScriptProcessor)throw"WebAudio API has no support on this browser.";scriptprocessornode=context.createScriptProcessor(bufferSize,numChannels,numChannels)}bufferSize=scriptprocessornode.bufferSize,console.debug("using audio buffer-size:",bufferSize);var requestDataInvoked=!1;window.scriptprocessornode=scriptprocessornode,1===numChannels&&console.debug("All right-channels are skipped.");var isPaused=!1;this.pause=function(){isPaused=!0},this.resume=function(){isPaused=!1},scriptprocessornode.onaudioprocess=function(e){if(recording&&!requestDataInvoked&&!isPaused){var left=e.inputBuffer.getChannelData(0);if(leftchannel.push(new Float32Array(left)),2===numChannels){var right=e.inputBuffer.getChannelData(1);rightchannel.push(new Float32Array(right))}recordingLength+=bufferSize}},volume.connect(scriptprocessornode),scriptprocessornode.connect(context.destination)}function WhammyRecorder(mediaStream){this.start=function(timeSlice){timeSlice=timeSlice||1e3,mediaRecorder=new WhammyRecorderHelper(mediaStream,this);for(var prop in this)"function"!=typeof this[prop]&&(mediaRecorder[prop]=this[prop]);mediaRecorder.record(),timeout=setInterval(function(){mediaRecorder.requestData()},timeSlice)},this.stop=function(){mediaRecorder&&(mediaRecorder.stop(),clearTimeout(timeout))},this.clearOldRecordedFrames=function(){mediaRecorder&&mediaRecorder.clearOldRecordedFrames()},this.pause=function(){mediaRecorder&&mediaRecorder.pause()},this.resume=function(){mediaRecorder&&mediaRecorder.resume()},this.ondataavailable=function(){};var mediaRecorder,timeout}function WhammyRecorderHelper(mediaStream,root){function drawFrames(){if(isPaused)return lastTime=(new Date).getTime(),void setTimeout(drawFrames,500);if(!isStopDrawing){if(requestDataInvoked)return setTimeout(drawFrames,100);var duration=(new Date).getTime()-lastTime;if(!duration)return drawFrames();lastTime=(new Date).getTime(),!self.isHTMLObject&&video.paused&&video.play(),context.drawImage(video,0,0,canvas.width,canvas.height),isStopDrawing||whammy.frames.push({duration:duration,image:canvas.toDataURL("image/webp")}),isOnStartedDrawingNonBlankFramesInvoked||isBlankFrame(whammy.frames[whammy.frames.length-1])||(isOnStartedDrawingNonBlankFramesInvoked=!0,root.onStartedDrawingNonBlankFrames()),setTimeout(drawFrames,10)}}function isBlankFrame(frame,_pixTolerance,_frameTolerance){var localCanvas=document.createElement("canvas");localCanvas.width=canvas.width,localCanvas.height=canvas.height;var matchPixCount,endPixCheck,maxPixCount,context2d=localCanvas.getContext("2d"),sampleColor={r:0,g:0,b:0},maxColorDifference=Math.sqrt(Math.pow(255,2)+Math.pow(255,2)+Math.pow(255,2)),pixTolerance=_pixTolerance&&_pixTolerance>=0&&1>=_pixTolerance?_pixTolerance:0,frameTolerance=_frameTolerance&&_frameTolerance>=0&&1>=_frameTolerance?_frameTolerance:0,image=new Image;image.src=frame.image,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;endPixCheck>pix;pix+=4){var currentColor={r:imageData.data[pix],g:imageData.data[pix+1],b:imageData.data[pix+2]},colorDifference=Math.sqrt(Math.pow(currentColor.r-sampleColor.r,2)+Math.pow(currentColor.g-sampleColor.g,2)+Math.pow(currentColor.b-sampleColor.b,2));maxColorDifference*pixTolerance>=colorDifference&&matchPixCount++}return maxPixCount*frameTolerance>=maxPixCount-matchPixCount?!1:!0}function dropBlackFrames(_frames,_framesToCheck,_pixTolerance,_frameTolerance){var localCanvas=document.createElement("canvas");localCanvas.width=canvas.width,localCanvas.height=canvas.height;for(var context2d=localCanvas.getContext("2d"),resultFrames=[],checkUntilNotBlack=-1===_framesToCheck,endCheckFrame=_framesToCheck&&_framesToCheck>0&&_framesToCheck<=_frames.length?_framesToCheck:_frames.length,sampleColor={r:0,g:0,b:0},maxColorDifference=Math.sqrt(Math.pow(255,2)+Math.pow(255,2)+Math.pow(255,2)),pixTolerance=_pixTolerance&&_pixTolerance>=0&&1>=_pixTolerance?_pixTolerance:0,frameTolerance=_frameTolerance&&_frameTolerance>=0&&1>=_frameTolerance?_frameTolerance:0,doNotCheckNext=!1,f=0;endCheckFrame>f;f++){var matchPixCount,endPixCheck,maxPixCount;if(!doNotCheckNext){var image=new Image;image.src=_frames[f].image,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;endPixCheck>pix;pix+=4){var currentColor={r:imageData.data[pix],g:imageData.data[pix+1],b:imageData.data[pix+2]},colorDifference=Math.sqrt(Math.pow(currentColor.r-sampleColor.r,2)+Math.pow(currentColor.g-sampleColor.g,2)+Math.pow(currentColor.b-sampleColor.b,2));maxColorDifference*pixTolerance>=colorDifference&&matchPixCount++}}!doNotCheckNext&&maxPixCount*frameTolerance>=maxPixCount-matchPixCount||(checkUntilNotBlack&&(doNotCheckNext=!0),resultFrames.push(_frames[f]))}return resultFrames=resultFrames.concat(_frames.slice(endCheckFrame)),resultFrames.length<=0&&resultFrames.push(_frames[_frames.length-1]),resultFrames}this.record=function(timeSlice){this.width||(this.width=320),this.height||(this.height=240),this.video&&this.video instanceof HTMLVideoElement&&(this.width||(this.width=video.videoWidth||video.clientWidth||320),this.height||(this.height=video.videoHeight||video.clientHeight||240)),this.video||(this.video={width:this.width,height:this.height}),this.canvas&&this.canvas.width&&this.canvas.height||(this.canvas={width:this.width,height:this.height}),canvas.width=this.canvas.width,canvas.height=this.canvas.height,this.video&&this.video instanceof HTMLVideoElement?(this.isHTMLObject=!0,video=this.video.cloneNode()):(video=document.createElement("video"),video.src=URL.createObjectURL(mediaStream),video.width=this.video.width,video.height=this.video.height),video.muted=!0,video.play(),lastTime=(new Date).getTime(),whammy=new Whammy.Video(root.speed,root.quality),console.log("canvas resolutions",canvas.width,"*",canvas.height),console.log("video width/height",video.width||canvas.width,"*",video.height||canvas.height),drawFrames()},this.clearOldRecordedFrames=function(){whammy.frames=[]};var requestDataInvoked=!1;this.requestData=function(){if(!isPaused){if(!whammy.frames.length)return void(requestDataInvoked=!1);requestDataInvoked=!0;var internalFrames=whammy.frames.slice(0);whammy.frames=dropBlackFrames(internalFrames,-1),whammy.compile(function(whammyBlob){root.ondataavailable(whammyBlob),console.debug("video recorded blob size:",bytesToSize(whammyBlob.size))}),whammy.frames=[],requestDataInvoked=!1}};var isOnStartedDrawingNonBlankFramesInvoked=!1,isStopDrawing=!1;this.stop=function(){isStopDrawing=!0,this.requestData()};var video,lastTime,whammy,canvas=document.createElement("canvas"),context=canvas.getContext("2d"),self=this,isPaused=!1;this.pause=function(){isPaused=!0},this.resume=function(){isPaused=!1}}function GifRecorder(mediaStream){function doneRecording(){endTime=Date.now();var gifBlob=new Blob([new Uint8Array(gifEncoder.stream().bin)],{type:"image/gif"});self.ondataavailable(gifBlob),gifEncoder.stream().bin=[]}if("undefined"==typeof GIFEncoder)throw"Please link: https://cdn.webrtc-experiment.com/gif-recorder.js";this.start=function(timeSlice){function drawVideoFrame(time){return isPaused?void setTimeout(drawVideoFrame,500,time):(lastAnimationFrame=requestAnimationFrame(drawVideoFrame),void 0===typeof lastFrameTime&&(lastFrameTime=time),void(90>time-lastFrameTime||(video.paused&&video.play(),context.drawImage(video,0,0,imageWidth,imageHeight),gifEncoder.addFrame(context),lastFrameTime=time)))}timeSlice=timeSlice||1e3;var imageWidth=this.videoWidth||320,imageHeight=this.videoHeight||240;canvas.width=video.width=imageWidth,canvas.height=video.height=imageHeight,gifEncoder=new GIFEncoder,gifEncoder.setRepeat(0),gifEncoder.setDelay(this.frameRate||this.speed||200),gifEncoder.setQuality(this.quality||1),gifEncoder.start(),startTime=Date.now(),lastAnimationFrame=requestAnimationFrame(drawVideoFrame),timeout=setTimeout(doneRecording,timeSlice)},this.stop=function(){lastAnimationFrame&&(cancelAnimationFrame(lastAnimationFrame),clearTimeout(timeout),doneRecording())};var isPaused=!1;this.pause=function(){isPaused=!0},this.resume=function(){isPaused=!1},this.ondataavailable=function(){},this.onstop=function(){};var self=this,canvas=document.createElement("canvas"),context=canvas.getContext("2d"),video=document.createElement("video");video.muted=!0,video.autoplay=!0,video.src=URL.createObjectURL(mediaStream),video.play();var startTime,endTime,lastFrameTime,gifEncoder,timeout,lastAnimationFrame=null}"undefined"!=typeof MediaStreamRecorder&&(MediaStreamRecorder.MultiStreamRecorder=MultiStreamRecorder);var browserFakeUserAgent="Fake/5.0 (FakeOS) AppleWebKit/123 (KHTML, like Gecko) Fake/12.3.4567.89 Fake/123.45";!function(that){"undefined"==typeof window&&("undefined"==typeof window&&"undefined"!=typeof global?(global.navigator={userAgent:browserFakeUserAgent,getUserMedia:function(){}},that.window=global):"undefined"==typeof window,"undefined"==typeof document&&(that.document={},document.createElement=document.captureStream=document.mozCaptureStream=function(){return{}}),"undefined"==typeof location&&(that.location={protocol:"file:",href:"",hash:""}),"undefined"==typeof screen&&(that.screen={width:0,height:0}))}("undefined"!=typeof global?global:window);var AudioContext=window.AudioContext;"undefined"==typeof AudioContext&&("undefined"!=typeof webkitAudioContext&&(AudioContext=webkitAudioContext),"undefined"!=typeof mozAudioContext&&(AudioContext=mozAudioContext)),"undefined"==typeof window&&(window={});var AudioContext=window.AudioContext;"undefined"==typeof AudioContext&&("undefined"!=typeof webkitAudioContext&&(AudioContext=webkitAudioContext),"undefined"!=typeof mozAudioContext&&(AudioContext=mozAudioContext));var URL=window.URL;"undefined"==typeof URL&&"undefined"!=typeof webkitURL&&(URL=webkitURL),"undefined"!=typeof navigator?("undefined"!=typeof navigator.webkitGetUserMedia&&(navigator.getUserMedia=navigator.webkitGetUserMedia),"undefined"!=typeof navigator.mozGetUserMedia&&(navigator.getUserMedia=navigator.mozGetUserMedia)):navigator={getUserMedia:function(){},userAgent:browserFakeUserAgent};var IsEdge=!(-1===navigator.userAgent.indexOf("Edge")||!navigator.msSaveBlob&&!navigator.msSaveOrOpenBlob),IsOpera=!1;"undefined"!=typeof opera&&navigator.userAgent&&-1!==navigator.userAgent.indexOf("OPR/")&&(IsOpera=!0);var IsChrome=!IsEdge&&!IsEdge&&!!navigator.webkitGetUserMedia,MediaStream=window.MediaStream;"undefined"==typeof MediaStream&&"undefined"!=typeof webkitMediaStream&&(MediaStream=webkitMediaStream),"undefined"!=typeof MediaStream&&("getVideoTracks"in MediaStream.prototype||(MediaStream.prototype.getVideoTracks=function(){if(!this.getTracks)return[];var tracks=[];return this.getTracks.forEach(function(track){-1!==track.kind.toString().indexOf("video")&&tracks.push(track)}),tracks},MediaStream.prototype.getAudioTracks=function(){if(!this.getTracks)return[];var tracks=[];return this.getTracks.forEach(function(track){-1!==track.kind.toString().indexOf("audio")&&tracks.push(track)}),tracks}),"stop"in MediaStream.prototype||(MediaStream.prototype.stop=function(){this.getAudioTracks().forEach(function(track){track.stop&&track.stop()}),this.getVideoTracks().forEach(function(track){track.stop&&track.stop()})})),"undefined"!=typeof location&&0===location.href.indexOf("file:")&&console.error("Please load this HTML file on HTTP or HTTPS.");var ObjectStore={AudioContext:AudioContext},ObjectStore={AudioContext:window.AudioContext||window.webkitAudioContext};"undefined"!=typeof MediaStreamRecorder&&(MediaStreamRecorder.MediaRecorderWrapper=MediaRecorderWrapper),"undefined"!=typeof MediaStreamRecorder&&(MediaStreamRecorder.StereoAudioRecorder=StereoAudioRecorder),"undefined"!=typeof MediaStreamRecorder&&(MediaStreamRecorder.StereoAudioRecorderHelper=StereoAudioRecorderHelper),"undefined"!=typeof MediaStreamRecorder&&(MediaStreamRecorder.WhammyRecorder=WhammyRecorder),"undefined"!=typeof MediaStreamRecorder&&(MediaStreamRecorder.WhammyRecorderHelper=WhammyRecorderHelper),"undefined"!=typeof MediaStreamRecorder&&(MediaStreamRecorder.GifRecorder=GifRecorder);var Whammy=function(){function WhammyVideo(duration,quality){this.frames=[],duration||(duration=1),this.duration=1e3/duration,this.quality=quality||.8}function processInWebWorker(_function){var blob=URL.createObjectURL(new Blob([_function.toString(),"this.onmessage = function (e) {"+_function.name+"(e.data);}"],{type:"application/javascript"})),worker=new Worker(blob);return URL.revokeObjectURL(blob),worker}function whammyInWebWorker(frames){function ArrayToWebM(frames){var info=checkFrames(frames);if(!info)return[];for(var clusterMaxDuration=3e4,EBML=[{id:440786851,data:[{data:1,id:17030},{data:1,id:17143},{data:4,id:17138},{data:8,id:17139},{data:"webm",id:17026},{data:2,id:17031},{data:2,id:17029}]},{id:408125543,data:[{id:357149030,data:[{data:1e6,id:2807729},{data:"whammy",id:19840},{data:"whammy",id:22337},{data:doubleToString(info.duration),id:17545}]},{id:374648427,data:[{id:174,data:[{data:1,id:215},{data:1,id:29637},{data:0,id:156},{data:"und",id:2274716},{data:"V_VP8",id:134},{data:"VP8",id:2459272},{data:1,id:131},{id:224,data:[{data:info.width,id:176},{data:info.height,id:186}]}]}]}]}],frameNumber=0,clusterTimecode=0;frameNumberclusterDuration);var clusterCounter=0,cluster={id:524531317,data:getClusterData(clusterTimecode,clusterCounter,clusterFrames)};EBML[1].data.push(cluster),clusterTimecode+=clusterDuration}return generateEBML(EBML)}function getClusterData(clusterTimecode,clusterCounter,clusterFrames){return[{data:clusterTimecode,id:231}].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)});return clusterCounter+=webp.duration,{data:block,id:163}}))}function checkFrames(frames){if(!frames[0])return void postMessage({error:"Something went wrong. Maybe WebP format is not supported in the current browser."});for(var width=frames[0].width,height=frames[0].height,duration=frames[0].duration,i=1;i0;)parts.push(255&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=[],pad=bits.length%8?new Array(9-bits.length%8).join("0"):"";bits=pad+bits;for(var i=0;i127)throw"TrackNumber > 127 not supported";var out=[128|data.trackNum,data.timecode>>8,255&data.timecode,flags].map(function(e){return String.fromCharCode(e)}).join("")+data.frame;return out}function parseWebP(riff){for(var VP8=riff.RIFF[0].WEBP[0],frameStart=VP8.indexOf("*"),i=0,c=[];4>i;i++)c[i]=VP8.charCodeAt(frameStart+3+i);var width,height,tmp;return tmp=c[1]<<8|c[0],width=16383&tmp,tmp=c[3]<<8|c[2],height=16383&tmp,{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){for(var offset=0,chunks={};offset -``` - -## Otherwise, you can "directly" link standalone file from CDN: -```html + - - -https://cdn.rawgit.com/streamproc/MediaStreamRecorder/master/MediaStreamRecorder.js + + ``` -## Record audio+video in Firefox in single WebM +## Record audio+video ```html ``` -## Record audio+video in Chrome - -`MultiStreamRecorder.js` records both audio/video and returns both blobs in single `ondataavailable` event. +## Record audio/wav ```html -``` - -## Record only audio in Chrome/Firefox - -```html - -``` - -```javascript var mediaConstraints = { audio: true }; @@ -157,42 +120,7 @@ navigator.getUserMedia(mediaConstraints, onMediaSuccess, onMediaError); function onMediaSuccess(stream) { var mediaRecorder = new MediaStreamRecorder(stream); - mediaRecorder.mimeType = 'audio/ogg'; - mediaRecorder.audioChannels = 1; - mediaRecorder.ondataavailable = function (blob) { - // POST/PUT "Blob" using FormData/XHR2 - var blobURL = URL.createObjectURL(blob); - document.write('' + blobURL + ''); - }; - mediaRecorder.start(3000); -} - -function onMediaError(e) { - console.error('media error', e); -} -``` - -## Record only-video in chrome - -```html - -