UNPKG

9.59 kBtext/coffeescriptView Raw
1# begin compatibility insanity
2
3PeerConnection = window.mozRTCPeerConnection or window.PeerConnection or window.webkitPeerConnection00 or window.webkitRTCPeerConnection
4IceCandidate = window.mozRTCIceCandidate or window.RTCIceCandidate
5SessionDescription = window.mozRTCSessionDescription or window.RTCSessionDescription
6MediaStream = window.MediaStream or window.webkitMediaStream
7getUserMedia = navigator.mozGetUserMedia or navigator.getUserMedia or navigator.webkitGetUserMedia or navigator.msGetUserMedia
8URL = window.URL or window.webkitURL or window.msURL or window.oURL
9getUserMedia = getUserMedia.bind navigator
10
11browser = (if navigator.mozGetUserMedia then 'firefox' else 'chrome')
12supported = (PeerConnection? and getUserMedia?)
13
14extract = (str, reg) ->
15 match = str.match reg
16 return (if match? then match[1] else null)
17
18replaceCodec = (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
29removeCN = (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
42useOPUS = (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
56processSDPOut = (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
69processSDPIn = (sdp) -> return sdp
70
71attachStream = (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
82saveBlob = (file, blob) ->
83 link = document.createElement "a"
84 link.href = blob
85 link.target = "_blank"
86 link.download = file
87
88 # trigger fake click
89 evt = document.createEvent "Event"
90 evt.initEvent "click", true, true
91 link.dispatchEvent evt
92
93 URL.revokeObjectURL link.href
94 return
95
96loadBlob = (blob, cb) ->
97 reader = new FileReader
98 reader.readAsDataURL blob
99 reader.onload = (event) ->
100 cb event.target.result
101
102recordVideo = (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
145shim = ->
146 return unless supported # no need to shim
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/* https://github.com/antimatter15/whammy */
227var 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,
228data:[{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;
229b=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]<<
2318|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&&
232this.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";
233this.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
236module.exports = shim()
\No newline at end of file