1 |
|
2 |
|
3 | PeerConnection = window.mozRTCPeerConnection or window.PeerConnection or window.webkitPeerConnection00 or window.webkitRTCPeerConnection
|
4 | IceCandidate = window.mozRTCIceCandidate or window.RTCIceCandidate
|
5 | SessionDescription = window.mozRTCSessionDescription or window.RTCSessionDescription
|
6 | MediaStream = window.MediaStream or window.webkitMediaStream
|
7 | getUserMedia = navigator.mozGetUserMedia or navigator.getUserMedia or navigator.webkitGetUserMedia or navigator.msGetUserMedia
|
8 | URL = window.URL or window.webkitURL or window.msURL or window.oURL
|
9 | getUserMedia = getUserMedia.bind navigator
|
10 |
|
11 | browser = (if navigator.mozGetUserMedia then 'firefox' else 'chrome')
|
12 | supported = (PeerConnection? and getUserMedia?)
|
13 |
|
14 | extract = (str, reg) ->
|
15 | match = str.match reg
|
16 | return (if match? then match[1] else null)
|
17 |
|
18 | replaceCodec = (line, codec) ->
|
19 | els = line.split ' '
|
20 | out = []
|
21 | for el, idx in els
|
22 | if idx is 3
|
23 | out[idx++] = codec
|
24 | if el isnt codec
|
25 | out[idx++] = el
|
26 |
|
27 | return out.join ' '
|
28 |
|
29 | removeCN = (lines, mLineIdx) ->
|
30 | mLineEls = lines[mLineIdx].split ' '
|
31 | for line, idx in lines when line?
|
32 | payload = extract line, /a=rtpmap:(\d+) CN\/\d+/i
|
33 | if payload?
|
34 | cnPos = mLineEls.indexOf payload
|
35 | if cnPos isnt -1
|
36 | mLineEls.splice cnPos, 1
|
37 | lines.splice idx, 1
|
38 |
|
39 | lines[mLineIdx] = mLineEls.join ' '
|
40 | return lines
|
41 |
|
42 | useOPUS = (sdp) ->
|
43 | lines = sdp.split '\r\n'
|
44 | [mLineIdx] = (idx for line,idx in lines when line.indexOf('m=audio') isnt -1)
|
45 | return sdp unless mLineIdx?
|
46 | for line, idx in lines when line.indexOf('opus/48000') isnt -1
|
47 | payload = extract line, /:(\d+) opus\/48000/i
|
48 | if payload?
|
49 | lines[mLineIdx] = replaceCodec lines[mLineIdx], payload
|
50 | break
|
51 |
|
52 | lines = removeCN lines, mLineIdx
|
53 |
|
54 | return lines.join '\r\n'
|
55 |
|
56 | processSDPOut = (sdp) ->
|
57 | out = []
|
58 | if browser is 'firefox'
|
59 | addCrypto = "a=crypto:1 AES_CM_128_HMAC_SHA1_80 inline:BAADBAADBAADBAADBAADBAADBAADBAADBAADBAAD"
|
60 | for line in sdp.split '\r\n'
|
61 | out.push line
|
62 | out.push addCrypto if line.indexOf('m=') is 0
|
63 | else
|
64 | for line in sdp.split '\r\n'
|
65 | if line.indexOf("a=ice-options:google-ice") is -1
|
66 | out.push line
|
67 | return useOPUS out.join '\r\n'
|
68 |
|
69 | processSDPIn = (sdp) -> return sdp
|
70 |
|
71 | attachStream = (uri, el) ->
|
72 | if typeof el is "string"
|
73 | return attachStream uri, document.getElementById el
|
74 | else if el.jquery
|
75 | el.attr 'src', uri
|
76 | e.play() for e in el
|
77 | else
|
78 | el.src = uri
|
79 | el.play()
|
80 | return el
|
81 |
|
82 | saveBlob = (file, blob) ->
|
83 | link = document.createElement "a"
|
84 | link.href = blob
|
85 | link.target = "_blank"
|
86 | link.download = file
|
87 |
|
88 |
|
89 | evt = document.createEvent "Event"
|
90 | evt.initEvent "click", true, true
|
91 | link.dispatchEvent evt
|
92 |
|
93 | URL.revokeObjectURL link.href
|
94 | return
|
95 |
|
96 | loadBlob = (blob, cb) ->
|
97 | reader = new FileReader
|
98 | reader.readAsDataURL blob
|
99 | reader.onload = (event) ->
|
100 | cb event.target.result
|
101 |
|
102 | recordVideo = (el) ->
|
103 | if el.jquery
|
104 | h = el.height()
|
105 | w = el.width()
|
106 | el = el[0]
|
107 | else
|
108 | h = el.height
|
109 | w = el.width
|
110 |
|
111 | can = document.createElement 'canvas'
|
112 | ctx = can.getContext '2d'
|
113 | can.width = w
|
114 | can.height = h
|
115 |
|
116 | frames = []
|
117 |
|
118 | grab = ->
|
119 | requested = requestAnimationFrame grab
|
120 | ctx.drawImage el, 0, 0, w, h
|
121 | frames.push can.toDataURL 'image/webp', 1
|
122 | return
|
123 |
|
124 | getBlob = (cb) ->
|
125 | blob = Whammy.fromImageArray frames, 1000/60
|
126 | loadBlob blob, cb
|
127 | return ctrl
|
128 |
|
129 | save = (file="recording.webp") ->
|
130 | getBlob (blob) -> saveBlob file, blob
|
131 | return ctrl
|
132 |
|
133 | end = (cb) ->
|
134 | cancelAnimationFrame requested
|
135 | return ctrl
|
136 |
|
137 | requested = requestAnimationFrame grab
|
138 |
|
139 | ctrl =
|
140 | save: save
|
141 | getBlob: getBlob
|
142 | end: end
|
143 | return ctrl
|
144 |
|
145 | shim = ->
|
146 | return unless supported
|
147 | if browser is 'firefox'
|
148 | PeerConnConfig =
|
149 | iceServers: [
|
150 | url: "stun:23.21.150.121"
|
151 | ]
|
152 |
|
153 | mediaConstraints =
|
154 | mandatory:
|
155 | OfferToReceiveAudio: true
|
156 | OfferToReceiveVideo: true
|
157 | MozDontOfferDataChannel: true
|
158 |
|
159 | MediaStream::getVideoTracks = -> []
|
160 | MediaStream::getAudioTracks = -> []
|
161 | else
|
162 | PeerConnConfig =
|
163 | iceServers: [
|
164 | url: "stun:stun.l.google.com:19302"
|
165 | ]
|
166 | mediaConstraints =
|
167 | mandatory:
|
168 | OfferToReceiveAudio: true
|
169 | OfferToReceiveVideo: true
|
170 | optional: [
|
171 | DtlsSrtpKeyAgreement: true
|
172 | ]
|
173 |
|
174 | unless MediaStream::getVideoTracks
|
175 | MediaStream::getVideoTracks = -> @videoTracks
|
176 | MediaStream::getAudioTracks = -> @audioTracks
|
177 |
|
178 | unless PeerConnection::getLocalStreams
|
179 | PeerConnection::getLocalStreams = -> @localStreams
|
180 | PeerConnection::getRemoteStreams = -> @remoteStreams
|
181 |
|
182 | out =
|
183 | PeerConnection: PeerConnection
|
184 | IceCandidate: IceCandidate
|
185 | SessionDescription: SessionDescription
|
186 | MediaStream: MediaStream
|
187 | getUserMedia: getUserMedia
|
188 | URL: URL
|
189 | attachStream: attachStream
|
190 | processSDPIn: processSDPIn
|
191 | processSDPOut: processSDPOut
|
192 | PeerConnConfig: PeerConnConfig
|
193 | browser: browser
|
194 | supported: supported
|
195 | constraints: mediaConstraints
|
196 | recordVideo: recordVideo
|
197 | loadBlob: loadBlob
|
198 | saveBlob: saveBlob
|
199 | return out
|
200 |
|
201 | `
|
202 | (function() {
|
203 | var lastTime = 0;
|
204 | var vendors = ['ms', 'moz', 'webkit', 'o'];
|
205 | for(var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {
|
206 | window.requestAnimationFrame = window[vendors[x]+'RequestAnimationFrame'];
|
207 | window.cancelAnimationFrame =
|
208 | window[vendors[x]+'CancelAnimationFrame'] || window[vendors[x]+'CancelRequestAnimationFrame'];
|
209 | }
|
210 |
|
211 | if (!window.requestAnimationFrame)
|
212 | window.requestAnimationFrame = function(callback, element) {
|
213 | var currTime = new Date().getTime();
|
214 | var timeToCall = Math.max(0, 16 - (currTime - lastTime));
|
215 | var id = window.setTimeout(function() { callback(currTime + timeToCall); },
|
216 | timeToCall);
|
217 | lastTime = currTime + timeToCall;
|
218 | return id;
|
219 | };
|
220 |
|
221 | if (!window.cancelAnimationFrame)
|
222 | window.cancelAnimationFrame = function(id) {
|
223 | clearTimeout(id);
|
224 | };
|
225 | }());
|
226 |
|
227 | var Whammy=function(){function g(a){for(var b=a[0].width,e=a[0].height,c=a[0].duration,d=1;d<a.length;d++){if(a[d].width!=b)throw"Frame "+(d+1)+" has a different width";if(a[d].height!=e)throw"Frame "+(d+1)+" has a different height";if(0>a[d].duration)throw"Frame "+(d+1)+" has a weird duration";c+=a[d].duration}var f=0,a=[{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,
|
228 | data:[{data:1E6,id:2807729},{data:"whammy",id:19840},{data:"whammy",id:22337},{data:[].slice.call(new Uint8Array((new Float64Array([c])).buffer),0).map(function(a){return String.fromCharCode(a)}).reverse().join(""),id:17545}]},{id:374648427,data:[{id:174,data:[{data:1,id:215},{data:1,id:25541},{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:b,id:176},{data:e,id:186}]}]}]},{id:524531317,data:[{data:0,id:231}].concat(a.map(function(a){var b;
|
229 | b=a.data.slice(4);var c=Math.round(f);b=[129,c>>8,c&255,128].map(function(a){return String.fromCharCode(a)}).join("")+b;f+=a.duration;return{data:b,id:163}}))}]}];return j(a)}function m(a){for(var b=[];0<a;)b.push(a&255),a>>=8;return new Uint8Array(b.reverse())}function k(a){for(var b=[],a=(a.length%8?Array(9-a.length%8).join("0"):"")+a,e=0;e<a.length;e+=8)b.push(parseInt(a.substr(e,8),2));return new Uint8Array(b)}function j(a){for(var b=[],e=0;e<a.length;e++){var c=a[e].data;"object"==typeof c&&
|
230 | (c=j(c));"number"==typeof c&&(c=k(c.toString(2)));if("string"==typeof c){for(var d=new Uint8Array(c.length),f=0;f<c.length;f++)d[f]=c.charCodeAt(f);c=d}f=c.size||c.byteLength;d=Math.ceil(Math.ceil(Math.log(f)/Math.log(2))/8);f=f.toString(2);f=Array(7*d+8-f.length).join("0")+f;d=Array(d).join("0")+"1"+f;b.push(m(a[e].id));b.push(k(d));b.push(c)}return new Blob(b,{type:"video/webm"})}function l(a){for(var b=a.RIFF[0].WEBP[0],e=b.indexOf("\u009d\u0001*"),c=0,d=[];4>c;c++)d[c]=b.charCodeAt(e+3+c);c=d[1]<<
|
231 | 8|d[0];e=c&16383;c=d[3]<<8|d[2];return{width:e,height:c&16383,data:b,riff:a}}function h(a){for(var b=0,e={};b<a.length;){var c=a.substr(b,4),d=parseInt(a.substr(b+4,4).split("").map(function(a){a=a.charCodeAt(0).toString(2);return Array(8-a.length+1).join("0")+a}).join(""),2),f=a.substr(b+4+4,d),b=b+(8+d);e[c]=e[c]||[];"RIFF"==c||"LIST"==c?e[c].push(h(f)):e[c].push(f)}return e}function i(a,b){this.frames=[];this.duration=1E3/a;this.quality=b||0.8}i.prototype.add=function(a,b){if("undefined"!=typeof b&&
|
232 | this.duration)throw"you can't pass a duration if the fps is set";if("undefined"==typeof b&&!this.duration)throw"if you don't have the fps set, you ned to have durations here.";a.canvas&&(a=a.canvas);if(a.toDataURL)a=a.toDataURL("image/webp",this.quality);else if("string"!=typeof a)throw"frame must be a a HTMLCanvasElement, a CanvasRenderingContext2D or a DataURI formatted string";if(!/^data:image\/webp;base64,/ig.test(a))throw"Input must be formatted properly as a base64 encoded DataURI of type image/webp";
|
233 | this.frames.push({image:a,duration:b||this.duration})};i.prototype.compile=function(){return new g(this.frames.map(function(a){var b=l(h(atob(a.image.slice(23))));b.duration=a.duration;return b}))};return{Video:i,fromImageArray:function(a,b){return g(a.map(function(a){a=l(h(atob(a.slice(23))));a.duration=1E3/b;return a}))},toWebM:g}}();
|
234 | `
|
235 |
|
236 | module.exports = shim() |
\ | No newline at end of file |