├── README.md
├── assets
├── promo440x280.png
└── screenshot640x400.png
├── doc
└── readme.html
├── extension
├── audio
│ ├── are-you-for-real.m4a
│ ├── bewdy.m4a
│ ├── bonzer.m4a
│ ├── fair-dinkum.m4a
│ ├── fair-game.m4a
│ ├── gday.m4a
│ ├── get-real.m4a
│ ├── no-dramas.m4a
│ ├── no-worries.m4a
│ ├── oh-mate.m4a
│ ├── oh-you-piker (1).m4a
│ ├── oh-you-piker.m4a
│ ├── rack-off.m4a
│ ├── ripper.m4a
│ ├── ta-ta.m4a
│ ├── toodle-oo.m4a
│ ├── totally-stoked.m4a
│ └── whinger.m4a
├── css
│ ├── global.css
│ ├── injected.css
│ └── popup.css
├── images
│ ├── pause22.png
│ ├── tabCapture128.png
│ ├── tabCapture16.png
│ ├── tabCapture22.png
│ ├── tabCapture32.png
│ └── tabCapture48.png
├── js
│ ├── background.js
│ ├── contentscript.js
│ ├── lib
│ │ ├── adapter.js
│ │ ├── socket.io-client.js
│ │ └── socket.io.js
│ └── popup.js
└── manifest.json
├── originals
├── apprtcJavaScript.js
├── media-record.svg
├── paused128.png
├── paused16.png
├── paused22.png
├── paused32.png
├── paused48.png
├── recording22.png
└── svg.html
└── psd
├── buttons.psd
├── icon.psd
├── kangaroo.psd
├── promo440x280.psd
└── screenshot640x400.psd
/README.md:
--------------------------------------------------------------------------------
1 | rtcshare
2 | ========
3 |
4 | I built this WebRTC screensharing demo a couple of years ago.
5 |
6 | It's now a bit out of date, but hopefully some of the code might still be useful.
7 |
--------------------------------------------------------------------------------
/assets/promo440x280.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/samdutton/rtcshare/6998287784e52583b2d3ff82d42605bd68d2420f/assets/promo440x280.png
--------------------------------------------------------------------------------
/assets/screenshot640x400.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/samdutton/rtcshare/6998287784e52583b2d3ff82d42605bd68d2420f/assets/screenshot640x400.png
--------------------------------------------------------------------------------
/doc/readme.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
100 |
101 |
Framegrabber
102 |
103 |
Framegrabber is a simple extension for bookmarking video timecodes and taking framegrabs.
104 |
105 |
Framegrabs are still images of individual film frames.
106 |
107 |
The Framegrabber extension makes it possible to take framegrabs from HTML5 video. Framegrabs can be stored in a local database or opened in a tab so they can be saved as JPEG files.
108 |
109 |
Framegrabber is also useful for bookmarking video timecodes.
110 |
111 |
Framegrabber works for any page that uses the HTML video element.
112 |
113 |
One major caveat: to take framegrabs, video must be from the same host as the page it's on, so Framegrabber can't take framegrabs on sites like YouTube and Vimeo. If Framegrabber cannot take a framegrab, it stores only the current timecode.
114 |
115 |
Videos from which framegrabs can taken can be found on many sites, including Dive Into HTML5, Mozilla and my own website samdutton.com.
116 |
117 |
How to use Framegrabber
118 |
119 |
To save framegrabs, use the icons that the extension displays at the top left of video(s) using the HTML video element:
120 |
121 | - click on the green plus icon to open a framegrab in a new tab
122 | - click on the red circle icon to save a framegrab in local database storage.
123 |
124 |
125 |
Note that on some pages, you may need to start playing the video before an HTML video element is actually added to the page.
126 |
127 |
Click the extension icon (to the right of the address bar) to display stored framegrabs in a popup. Click a framegrab image in the popup to navigate to the video and timecode from which the framegrab was taken.
128 |
129 |
130 |
How does it work?
131 |
132 |
Framegrabber uses several relatively new web technologies, including Canvas, HTMLMediaElement and Web SQL Database.
133 |
134 |
Below are some technical details of how the extension works.
135 |
136 |
Framegrabber creates a canvas element (but doesn't add it to the DOM) then uses the drawImage() canvas context method to draw a video frame on it. The canvas toDataURL() method is then used to create a data URL string representing the image. The image data URL can then either be opened in a new tab, or stored locally along with the URL of the page containing the video and the timecode of the framegrab. Except in order to view pages containing framegrabs, no server or internet access is required.
137 |
138 |
Local storage is accomplished using the Chrome Web SQL Data
139 | base implementation, which is fast and reliable enough to store strings such as image data URLs, which can be 200KB or more in size (i.e. 200,000+ characters in length).
140 |
141 |
When the extension icon is clicked, data URLs are retrieved from the local database and set as the src value for framegrabs displayed in the popup.
142 |
143 |
144 |
145 |
Feedback
146 |
147 |
Please send bug reports, comments or feature requests to samdutton@gmail.com.
148 |
149 |
For more information, please visit my website samdutton.com or my blog at samdutton.wordpress.com.
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
--------------------------------------------------------------------------------
/extension/audio/are-you-for-real.m4a:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/samdutton/rtcshare/6998287784e52583b2d3ff82d42605bd68d2420f/extension/audio/are-you-for-real.m4a
--------------------------------------------------------------------------------
/extension/audio/bewdy.m4a:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/samdutton/rtcshare/6998287784e52583b2d3ff82d42605bd68d2420f/extension/audio/bewdy.m4a
--------------------------------------------------------------------------------
/extension/audio/bonzer.m4a:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/samdutton/rtcshare/6998287784e52583b2d3ff82d42605bd68d2420f/extension/audio/bonzer.m4a
--------------------------------------------------------------------------------
/extension/audio/fair-dinkum.m4a:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/samdutton/rtcshare/6998287784e52583b2d3ff82d42605bd68d2420f/extension/audio/fair-dinkum.m4a
--------------------------------------------------------------------------------
/extension/audio/fair-game.m4a:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/samdutton/rtcshare/6998287784e52583b2d3ff82d42605bd68d2420f/extension/audio/fair-game.m4a
--------------------------------------------------------------------------------
/extension/audio/gday.m4a:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/samdutton/rtcshare/6998287784e52583b2d3ff82d42605bd68d2420f/extension/audio/gday.m4a
--------------------------------------------------------------------------------
/extension/audio/get-real.m4a:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/samdutton/rtcshare/6998287784e52583b2d3ff82d42605bd68d2420f/extension/audio/get-real.m4a
--------------------------------------------------------------------------------
/extension/audio/no-dramas.m4a:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/samdutton/rtcshare/6998287784e52583b2d3ff82d42605bd68d2420f/extension/audio/no-dramas.m4a
--------------------------------------------------------------------------------
/extension/audio/no-worries.m4a:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/samdutton/rtcshare/6998287784e52583b2d3ff82d42605bd68d2420f/extension/audio/no-worries.m4a
--------------------------------------------------------------------------------
/extension/audio/oh-mate.m4a:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/samdutton/rtcshare/6998287784e52583b2d3ff82d42605bd68d2420f/extension/audio/oh-mate.m4a
--------------------------------------------------------------------------------
/extension/audio/oh-you-piker (1).m4a:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/samdutton/rtcshare/6998287784e52583b2d3ff82d42605bd68d2420f/extension/audio/oh-you-piker (1).m4a
--------------------------------------------------------------------------------
/extension/audio/oh-you-piker.m4a:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/samdutton/rtcshare/6998287784e52583b2d3ff82d42605bd68d2420f/extension/audio/oh-you-piker.m4a
--------------------------------------------------------------------------------
/extension/audio/rack-off.m4a:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/samdutton/rtcshare/6998287784e52583b2d3ff82d42605bd68d2420f/extension/audio/rack-off.m4a
--------------------------------------------------------------------------------
/extension/audio/ripper.m4a:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/samdutton/rtcshare/6998287784e52583b2d3ff82d42605bd68d2420f/extension/audio/ripper.m4a
--------------------------------------------------------------------------------
/extension/audio/ta-ta.m4a:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/samdutton/rtcshare/6998287784e52583b2d3ff82d42605bd68d2420f/extension/audio/ta-ta.m4a
--------------------------------------------------------------------------------
/extension/audio/toodle-oo.m4a:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/samdutton/rtcshare/6998287784e52583b2d3ff82d42605bd68d2420f/extension/audio/toodle-oo.m4a
--------------------------------------------------------------------------------
/extension/audio/totally-stoked.m4a:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/samdutton/rtcshare/6998287784e52583b2d3ff82d42605bd68d2420f/extension/audio/totally-stoked.m4a
--------------------------------------------------------------------------------
/extension/audio/whinger.m4a:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/samdutton/rtcshare/6998287784e52583b2d3ff82d42605bd68d2420f/extension/audio/whinger.m4a
--------------------------------------------------------------------------------
/extension/css/global.css:
--------------------------------------------------------------------------------
1 | body {
2 | background: #666;
3 | font-family: Arial, sans-serif;
4 | padding: 50px;
5 | }
6 |
7 | div#container {
8 | background: #000;
9 | margin: 0 auto 0 auto;
10 | padding: 20px 20px 20px 20px;
11 | width: 530px;
12 | }
13 |
14 |
15 | #videoCanvasImageContainer {
16 | margin: 0 0 20px 0;
17 | }
18 |
19 | #video {
20 | height: 96px;
21 | margin: 0 20px 0 0;
22 | width: 160px;
23 | }
24 |
25 | #canvas {
26 | display: none;
27 | height: 96px;
28 | margin: 0 20px 0 0;
29 | width: 160px;
30 | }
31 |
32 | #img {
33 | border: none;
34 | height: 96px;
35 | width: 160px;
36 | }
37 |
38 |
39 | #framegrabs {
40 | background: #666;
41 | height: 115px;
42 | margin: 0 0 20px 0;
43 | overflow-y: scroll;
44 | padding: 20px 0 0 20px;
45 | width: 200px;
46 | }
47 |
48 | #framegrabs > div {
49 | height: 96px;
50 | margin: 0 20px 20px 0;
51 | position: relative;
52 | width: 160px;
53 | }
54 |
55 | #framegrabs div.framegrabTimecode {
56 | background: #aaa;
57 | color: black;
58 | font-size: 70%;
59 | height: 15px;
60 | opacity: 0.6;
61 | padding: 2px 5px 1px 5px;
62 | right: 0px;
63 | position: absolute;
64 | text-align: right;
65 | top: 10px;
66 | z-index: 1;
67 | }
68 |
69 | #framegrabs img {
70 | cursor: pointer;
71 | float: left;
72 | position: absolute;
73 | }
74 |
75 |
76 | div#scrubBarContainer {
77 | border: 1px solid green;
78 | margin: 0 0 20px 0;
79 | }
80 |
81 | div#buttons input {
82 | margin: 0 11px 0 0;
83 | width: 90px;
84 | }
--------------------------------------------------------------------------------
/extension/css/injected.css:
--------------------------------------------------------------------------------
1 | div.framegrabberControls {
2 | opacity: 0.8;
3 | position: absolute;
4 | left: 0px;
5 | padding: 10px 10px 10px 10px;
6 | z-index: 2000; // youtube video-blocker is 1010
7 | }
8 |
9 | div.framegrabberControls:hover {
10 | opacity: 1;
11 | }
12 |
13 | div.framegrabberControls img {
14 | display: block;
15 | cursor: pointer !important;;
16 | float: left;
17 | height: 15px;
18 | margin: 0 10px 0 0;
19 | opacity: 0.8;
20 | width: 15px;
21 | }
22 |
23 | div.framegrabberControls img:hover {
24 | opacity: 1;
25 | }
26 |
27 | div.framegrabberControls img.grab {
28 | }
29 |
30 | div.framegrabberControls img.openFramegrabInNewTab {
31 | }
--------------------------------------------------------------------------------
/extension/css/popup.css:
--------------------------------------------------------------------------------
1 | body {
2 | font-family: Lucida Grande, Open Sans, Arial, sans-serif;
3 | padding: 10px 10px 10px 10px;
4 | width: 270px;
5 | }
6 |
7 | label{
8 | font-size: 14px;
9 | margin: 0 6px 0 0;
10 | }
11 |
12 | input[type=checkbox]{
13 | }
14 |
15 | fieldset{
16 | border: 1px solid #ddd;
17 | color: #999;
18 | border-radius: 3px;
19 | padding: 15px 15px 15px 15px;
20 | }
21 |
22 | fieldset div{
23 | color: #666;
24 | }
25 |
26 | legend{
27 | border: 1px solid #ddd;
28 | border-radius: 3px;
29 | font-family: Lucida Grande, Open Sans, Arial, sans-serif;
30 | font-size: 13px;
31 | padding: 2px 4px 2px 4px;
32 | }
33 |
34 |
35 | div#languageSelectContainer {
36 | margin: 0 0 30px 0;
37 | }
38 |
--------------------------------------------------------------------------------
/extension/images/pause22.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/samdutton/rtcshare/6998287784e52583b2d3ff82d42605bd68d2420f/extension/images/pause22.png
--------------------------------------------------------------------------------
/extension/images/tabCapture128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/samdutton/rtcshare/6998287784e52583b2d3ff82d42605bd68d2420f/extension/images/tabCapture128.png
--------------------------------------------------------------------------------
/extension/images/tabCapture16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/samdutton/rtcshare/6998287784e52583b2d3ff82d42605bd68d2420f/extension/images/tabCapture16.png
--------------------------------------------------------------------------------
/extension/images/tabCapture22.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/samdutton/rtcshare/6998287784e52583b2d3ff82d42605bd68d2420f/extension/images/tabCapture22.png
--------------------------------------------------------------------------------
/extension/images/tabCapture32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/samdutton/rtcshare/6998287784e52583b2d3ff82d42605bd68d2420f/extension/images/tabCapture32.png
--------------------------------------------------------------------------------
/extension/images/tabCapture48.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/samdutton/rtcshare/6998287784e52583b2d3ff82d42605bd68d2420f/extension/images/tabCapture48.png
--------------------------------------------------------------------------------
/extension/js/background.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | // to includet socket.io.js from node server
4 | // would love to know if there's a better way...
5 | (function() {
6 | var ga = document.createElement('script');
7 | ga.type = 'text/javascript';
8 | ga.src = 'https://samdutton-nodertc.jit.su/socket.io/socket.io.js';
9 | var s = document.getElementsByTagName('script')[0];
10 | s.parentNode.insertBefore(ga, s);
11 | })();
12 |
13 | var audioElement;
14 | var isChannelReady;
15 | var isInitiator;
16 | var isStarted;
17 | var localStream;
18 | var pc;
19 | var remoteStream;
20 | var socket;
21 | var turnReady;
22 |
23 | var localVideo = document.querySelector('#localVideo');
24 | var remoteVideo = document.querySelector('#remoteVideo');
25 |
26 | // should change to extension install event
27 | window.onload = init;
28 | function init() {
29 | // if (typeof localStorage["capturing"] === "undefined") {
30 | localStorage["capturing"] = "off";
31 | // }
32 | }
33 |
34 | function handleCapture(stream){
35 | console.log("backround.js stream: ", stream);
36 | localStream = stream; // set global used by apprtc code and when stopping stream
37 | initialize(); // start of connection process using apprtc code below
38 | }
39 |
40 | function startCapture(){
41 | chrome.tabs.getSelected(null, function(tab) {
42 | var selectedTabId = tab.id;
43 | chrome.tabCapture.capture({audio:true, video:true}, handleCapture);
44 | });
45 | }
46 |
47 |
48 | // extension methods
49 |
50 | var iconPath = "images/";
51 | var iconCapture = "tabCapture22.png";
52 | var iconPause = "pause22.png";
53 |
54 |
55 | // when the record/pause button is clicked toggle the button icon and title
56 | chrome.browserAction.onClicked.addListener(function(tab) {
57 | var currentMode = localStorage["capturing"];
58 | var newMode = currentMode === "on" ? "off" : "on";
59 | // start capture
60 | if (newMode === "on"){
61 | appendIframe(); // capture starts once iframe created
62 | // stop capture
63 | } else {
64 | chrome.tabs.getSelected(null, function(tab){
65 | console.log("stop capture! newMode :", newMode);
66 | var selectedTabId = tab.id;
67 | localStream.stop();
68 | onRemoteHangup();
69 | // chrome.tabCapture.capture(selectedTabId, {audio:false, video:false});
70 | });
71 | }
72 | localStorage["capturing"] = newMode;
73 | // if capturing is now on, display pause icon -- and vice versa
74 | var iconFileName = newMode === "on" ? iconPause : iconCapture;
75 | chrome.browserAction.setIcon({path: iconPath + iconFileName});
76 | var title = newMode === "on" ? "Click to stop capture" : "Click to start capture";
77 | chrome.browserAction.setTitle({"title": title});
78 | });
79 |
80 |
81 |
82 |
83 |
84 |
85 | /////////////////////////////////
86 |
87 | var pc_config = webrtcDetectedBrowser === 'firefox' ?
88 | {'iceServers':[{'url':'stun:23.21.150.121'}]} : // number IP
89 | {'iceServers': [{'url': 'stun:stun.l.google.com:19302'}]};
90 |
91 | var pc_constraints = {'optional': [{'DtlsSrtpKeyAgreement': true}]};
92 |
93 | // Set up audio and video regardless of what devices are present.
94 | var sdpConstraints = {'mandatory': {
95 | 'OfferToReceiveAudio':true,
96 | 'OfferToReceiveVideo':true }};
97 |
98 | /////////////////////////////////////////////
99 |
100 | function setupSocket(){
101 | // var room = location.pathname.substring(1);
102 | // if (room === '') {
103 | // // room = prompt('Enter room name:');
104 | // room = 'rtcshare';
105 | // } else {
106 | // //
107 | // }
108 |
109 | socket = io.connect('samdutton-nodertc.jit.su')
110 |
111 | socket.on('created', function (room){
112 | console.log('Created room ' + room);
113 | isInitiator = true;
114 | // initiator does screencapture and Web Audio sharing
115 | console.log('starting screencapture');
116 | startCapture();
117 | });
118 |
119 | socket.on('full', function (room){
120 | console.log('Room ' + room + ' is full');
121 | });
122 |
123 | socket.on('join', function (room){
124 | console.log('Another peer made a request to join room ' + room);
125 | console.log('This peer is the initiator of room ' + room + '!');
126 | isChannelReady = true;
127 | });
128 |
129 | socket.on('joined', function (room){
130 | isInitiator = false;
131 | console.log('This peer has joined room ' + room);
132 | isChannelReady = true;
133 | // initiator does screencapture and Web Audio sharing
134 | var constraints = {video: true};
135 | getUserMedia(constraints, handleUserMedia, handleUserMediaError);
136 | console.log('Getting user media with constraints', constraints);
137 | });
138 |
139 | socket.on('log', function (array){
140 | console.log.apply(console, array);
141 | });
142 |
143 | ////////////////////////////////////////////////
144 |
145 | function sendMessage(message){
146 | console.log('Sending message: ', message);
147 | socket.emit('message', message);
148 | }
149 |
150 | socket.on('message', function (message){
151 | console.log('Received message:', message);
152 | if (message === 'got user media') {
153 | maybeStart();
154 | } else if (message.type === 'offer') {
155 | if (!isInitiator && !isStarted) {
156 | maybeStart();
157 | }
158 | pc.setRemoteDescription(new RTCSessionDescription(message));
159 | doAnswer();
160 | } else if (message.type === 'answer' && isStarted) {
161 | pc.setRemoteDescription(new RTCSessionDescription(message));
162 | } else if (message.type === 'candidate' && isStarted) {
163 | var candidate = new RTCIceCandidate({sdpMLineIndex:message.label,
164 | candidate:message.candidate});
165 | pc.addIceCandidate(candidate);
166 | } else if (message === 'bye' && isStarted) {
167 | handleRemoteHangup();
168 | }
169 | });
170 |
171 | var room = 'rtcshare';
172 |
173 | if (room !== '') {
174 | console.log('Create or join room', room);
175 | socket.emit('create or join', room);
176 | }
177 |
178 | ////////////////////////////////////////////////////
179 | }
180 |
181 | // must wait for socket.io.js to load :(
182 | // there must be a better way...
183 | setTimeout(setupSocket, 1000);
184 |
185 | function handleUserMedia(stream) {
186 | localStream = stream;
187 | // attachMediaStream(localVideo, stream);
188 | console.log('Adding local stream.');
189 | sendMessage('got user media');
190 | if (isInitiator) {
191 | maybeStart();
192 | }
193 | }
194 |
195 | function handleUserMediaError(error){
196 | console.log('navigator.getUserMedia error: ', error);
197 | }
198 |
199 |
200 | // requestTurn('https://computeengineondemand.appspot.com/turn?username=41784574&key=4080218913');
201 |
202 | function maybeStart() {
203 | if (!isStarted && localStream && isChannelReady) {
204 | createPeerConnection();
205 | pc.addStream(localStream);
206 | isStarted = true;
207 | if (isInitiator) {
208 | doCall();
209 | }
210 | }
211 | }
212 |
213 | window.onbeforeunload = function(e){
214 | sendMessage('bye');
215 | }
216 |
217 | /////////////////////////////////////////////////////////
218 |
219 | function createPeerConnection() {
220 | try {
221 | pc = new RTCPeerConnection(pc_config, pc_constraints);
222 | pc.onicecandidate = handleIceCandidate;
223 | console.log('Created RTCPeerConnnection with:\n' +
224 | ' config: \'' + JSON.stringify(pc_config) + '\';\n' +
225 | ' constraints: \'' + JSON.stringify(pc_constraints) + '\'.');
226 | } catch (e) {
227 | console.log('Failed to create PeerConnection, exception: ' + e.message);
228 | alert('Cannot create RTCPeerConnection object.');
229 | return;
230 | }
231 | pc.onaddstream = handleRemoteStreamAdded;
232 | pc.onremovestream = handleRemoteStreamRemoved;
233 | }
234 |
235 | function handleIceCandidate(event) {
236 | console.log('handleIceCandidate event: ', event);
237 | if (event.candidate) {
238 | sendMessage({
239 | type: 'candidate',
240 | label: event.candidate.sdpMLineIndex,
241 | id: event.candidate.sdpMid,
242 | candidate: event.candidate.candidate});
243 | } else {
244 | console.log('End of candidates.');
245 | }
246 | }
247 |
248 | function handleRemoteStreamAdded(event) {
249 | console.log('Remote stream added.');
250 | // reattachMediaStream(miniVideo, localVideo);
251 | if (!isInitiator){
252 | attachMediaStream(remoteVideo, event.stream);
253 | }
254 | remoteStream = event.stream;
255 | // waitForRemoteVideo();
256 | }
257 |
258 | function doCall() {
259 | var constraints = {'optional': [], 'mandatory': {'MozDontOfferDataChannel': true}};
260 | // temporary measure to remove Moz* constraints in Chrome
261 | if (webrtcDetectedBrowser === 'chrome') {
262 | for (var prop in constraints.mandatory) {
263 | if (prop.indexOf('Moz') !== -1) {
264 | delete constraints.mandatory[prop];
265 | }
266 | }
267 | }
268 | constraints = mergeConstraints(constraints, sdpConstraints);
269 | console.log('Sending offer to peer, with constraints: \n' +
270 | ' \'' + JSON.stringify(constraints) + '\'.');
271 | if (isInitiator) {
272 | addWebAudio();
273 | }
274 | pc.createOffer(setLocalAndSendMessage, null, constraints);
275 | }
276 |
277 | function doAnswer() {
278 | console.log('Sending answer to peer.');
279 | pc.createAnswer(setLocalAndSendMessage, null, sdpConstraints);
280 | }
281 |
282 | function mergeConstraints(cons1, cons2) {
283 | var merged = cons1;
284 | for (var name in cons2.mandatory) {
285 | merged.mandatory[name] = cons2.mandatory[name];
286 | }
287 | merged.optional.concat(cons2.optional);
288 | return merged;
289 | }
290 |
291 | function setLocalAndSendMessage(sessionDescription) {
292 | // Set Opus as the preferred codec in SDP if Opus is present.
293 | sessionDescription.sdp = preferOpus(sessionDescription.sdp);
294 | pc.setLocalDescription(sessionDescription);
295 | sendMessage(sessionDescription);
296 | }
297 |
298 | function requestTurn(turn_url) {
299 | var turnExists = false;
300 | for (var i in pc_config.iceServers) {
301 | if (pc_config.iceServers[i].url.substr(0, 5) === 'turn:') {
302 | turnExists = true;
303 | turnReady = true;
304 | break;
305 | }
306 | }
307 | if (!turnExists) {
308 | console.log('Getting TURN server from ', turn_url);
309 | // No TURN server. Get one from computeengineondemand.appspot.com:
310 | var xhr = new XMLHttpRequest();
311 | xhr.onreadystatechange = function(){
312 | if (xhr.readyState === 4 && xhr.status === 200) {
313 | var turnServer = JSON.parse(xhr.responseText);
314 | console.log('Got TURN server: ', turnServer);
315 | pc_config.iceServers.push({
316 | 'url': 'turn:' + turnServer.username + '@' + turnServer.turn,
317 | 'credential': turnServer.password
318 | });
319 | turnReady = true;
320 | }
321 | };
322 | xhr.open('GET', turn_url, true);
323 | xhr.send();
324 | }
325 | }
326 |
327 | function handleRemoteStreamAdded(event) {
328 | console.log('Remote stream added.');
329 | // reattachMediaStream(miniVideo, localVideo);
330 | attachMediaStream(remoteVideo, event.stream);
331 | remoteStream = event.stream;
332 | // waitForRemoteVideo();
333 | }
334 | function handleRemoteStreamRemoved(event) {
335 | console.log('Remote stream removed. Event: ', event);
336 | }
337 |
338 | function hangup() {
339 | console.log('Hanging up.');
340 | stop();
341 | sendMessage('bye');
342 | }
343 |
344 | function handleRemoteHangup() {
345 | console.log('Session terminated.');
346 | stop();
347 | isInitiator = false;
348 | }
349 |
350 | function stop() {
351 | isStarted = false;
352 | // isAudioMuted = false;
353 | // isVideoMuted = false;
354 | pc.close();
355 | pc = null;
356 | }
357 |
358 | ///////////////////////////////////////////
359 |
360 | // Set Opus as the default audio codec if it's present.
361 | function preferOpus(sdp) {
362 | var sdpLines = sdp.split('\r\n');
363 | var mLineIndex;
364 | // Search for m line.
365 | for (var i = 0; i < sdpLines.length; i++) {
366 | if (sdpLines[i].search('m=audio') !== -1) {
367 | mLineIndex = i;
368 | break;
369 | }
370 | }
371 | if (mLineIndex === null) {
372 | return sdp;
373 | }
374 |
375 | // If Opus is available, set it as the default in m line.
376 | for (i = 0; i < sdpLines.length; i++) {
377 | if (sdpLines[i].search('opus/48000') !== -1) {
378 | var opusPayload = extractSdp(sdpLines[i], /:(\d+) opus\/48000/i);
379 | if (opusPayload) {
380 | sdpLines[mLineIndex] = setDefaultCodec(sdpLines[mLineIndex], opusPayload);
381 | }
382 | break;
383 | }
384 | }
385 |
386 | // Remove CN in m line and sdp.
387 | sdpLines = removeCN(sdpLines, mLineIndex);
388 |
389 | sdp = sdpLines.join('\r\n');
390 | return sdp;
391 | }
392 |
393 | function extractSdp(sdpLine, pattern) {
394 | var result = sdpLine.match(pattern);
395 | return result && result.length === 2 ? result[1] : null;
396 | }
397 |
398 | // Set the selected codec to the first in m line.
399 | function setDefaultCodec(mLine, payload) {
400 | var elements = mLine.split(' ');
401 | var newLine = [];
402 | var index = 0;
403 | for (var i = 0; i < elements.length; i++) {
404 | if (index === 3) { // Format of media starts from the fourth.
405 | newLine[index++] = payload; // Put target payload to the first.
406 | }
407 | if (elements[i] !== payload) {
408 | newLine[index++] = elements[i];
409 | }
410 | }
411 | return newLine.join(' ');
412 | }
413 |
414 | // Strip CN from sdp before CN constraints is ready.
415 | function removeCN(sdpLines, mLineIndex) {
416 | var mLineElements = sdpLines[mLineIndex].split(' ');
417 | // Scan from end for the convenience of removing an item.
418 | for (var i = sdpLines.length-1; i >= 0; i--) {
419 | var payload = extractSdp(sdpLines[i], /a=rtpmap:(\d+) CN\/\d+/i);
420 | if (payload) {
421 | var cnPos = mLineElements.indexOf(payload);
422 | if (cnPos !== -1) {
423 | // Remove CN payload from m line.
424 | mLineElements.splice(cnPos, 1);
425 | }
426 | // Remove CN line in sdp
427 | sdpLines.splice(i, 1);
428 | }
429 | }
430 |
431 | sdpLines[mLineIndex] = mLineElements.join(' ');
432 | return sdpLines;
433 | }
434 |
435 | ///////////////////////////////////
436 |
437 | // add stream from Web Audio
438 | function addWebAudio(){
439 | // cope with browser differences
440 | var context;
441 | if (typeof webkitAudioContext === "function") {
442 | context = new webkitAudioContext();
443 | } else if (typeof AudioContext === "function") {
444 | context = new AudioContext();
445 | } else {
446 | alert("Sorry! Web Audio is not supported by this browser");
447 | }
448 |
449 | // use the audio element to create the source node
450 | audioElement = document.createElement("Audio"); // global scope, for console
451 | audioElement.src = 'audio/human-voice.wav';
452 | audioElement.loop = true;
453 | var sourceNode = context.createMediaElementSource(audioElement);
454 | // sourceNode.loop = true;
455 | //audioElement.addEventListener('loadedmetadata', function(){console.log('loadedmetadata')});
456 |
457 | // connect the source node to a filter node
458 | var filterNode = context.createBiquadFilter();
459 | // see https://dvcs.w3.org/hg/audio/raw-file/tip/webaudio/specification.html#BiquadFilterNode-section
460 | filterNode.type = 1; // HIGHPASS
461 | // cutoff frequency: for HIGHPASS, audio is attenuated below this frequency
462 | sourceNode.connect(filterNode);
463 |
464 | // connect the filter node to a gain node (to change audio volume)
465 | var gainNode = context.createGainNode();
466 | // default is 1 (no change); less than 1 means audio is attenuated, and vice versa
467 | gainNode.gain.value = 0.5;
468 | filterNode.connect(gainNode);
469 |
470 | var mediaStreamDestination = context.createMediaStreamDestination();
471 | gainNode.connect(mediaStreamDestination);
472 | pc.addStream(mediaStreamDestination.stream);
473 | audioElement.play();
474 | console.log('audioElement play() called, mediaStreamDestination.stream: ', mediaStreamDestination.stream);
475 | }
476 |
--------------------------------------------------------------------------------
/extension/js/contentscript.js:
--------------------------------------------------------------------------------
1 | // document.body.onkeydown = function(event){
2 | // chrome.extension.sendMessage({type: "playSound", keyCode: event.keyCode}, function(response) {
3 | // // console.log("response", response);
4 | // });
5 | // };
6 |
7 | // chrome.extension.sendMessage({type: "hello", }, function(response) {
8 | // console.log("response", response);
9 | // });
10 |
--------------------------------------------------------------------------------
/extension/js/lib/adapter.js:
--------------------------------------------------------------------------------
1 | var RTCPeerConnection = null;
2 | var getUserMedia = null;
3 | var attachMediaStream = null;
4 | var reattachMediaStream = null;
5 | var webrtcDetectedBrowser = null;
6 | var webrtcDetectedVersion = null;
7 |
8 | function trace(text) {
9 | // This function is used for logging.
10 | if (text[text.length - 1] == '\n') {
11 | text = text.substring(0, text.length - 1);
12 | }
13 | console.log((performance.now() / 1000).toFixed(3) + ": " + text);
14 | }
15 |
16 | if (navigator.mozGetUserMedia) {
17 | console.log("This appears to be Firefox");
18 |
19 | webrtcDetectedBrowser = "firefox";
20 |
21 | // The RTCPeerConnection object.
22 | RTCPeerConnection = mozRTCPeerConnection;
23 |
24 | // The RTCSessionDescription object.
25 | RTCSessionDescription = mozRTCSessionDescription;
26 |
27 | // The RTCIceCandidate object.
28 | RTCIceCandidate = mozRTCIceCandidate;
29 |
30 | // Get UserMedia (only difference is the prefix).
31 | // Code from Adam Barth.
32 | getUserMedia = navigator.mozGetUserMedia.bind(navigator);
33 |
34 | // Creates Turn Uri with new turn format.
35 | createIceServer = function(turn_url, username, password) {
36 | var iceServer = { 'url': turn_url,
37 | 'credential': password,
38 | 'username': username };
39 | return iceServer;
40 | };
41 |
42 | // Attach a media stream to an element.
43 | attachMediaStream = function(element, stream) {
44 | console.log("Attaching media stream");
45 | element.mozSrcObject = stream;
46 | element.play();
47 | };
48 |
49 | reattachMediaStream = function(to, from) {
50 | console.log("Reattaching media stream");
51 | to.mozSrcObject = from.mozSrcObject;
52 | to.play();
53 | };
54 |
55 | // Fake get{Video,Audio}Tracks
56 | MediaStream.prototype.getVideoTracks = function() {
57 | return [];
58 | };
59 |
60 | MediaStream.prototype.getAudioTracks = function() {
61 | return [];
62 | };
63 | } else if (navigator.webkitGetUserMedia) {
64 | console.log("This appears to be Chrome");
65 |
66 | webrtcDetectedBrowser = "chrome";
67 | webrtcDetectedVersion =
68 | parseInt(navigator.userAgent.match(/Chrom(e|ium)\/([0-9]+)\./)[2]);
69 |
70 | // For pre-M28 chrome versions use old turn format, else use the new format.
71 | if (webrtcDetectedVersion < 28) {
72 | createIceServer = function(turn_url, username, password) {
73 | var iceServer = { 'url': 'turn:' + username + '@' + turn_url,
74 | 'credential': password };
75 | return iceServer;
76 | };
77 | } else {
78 | createIceServer = function(turn_url, username, password) {
79 | var iceServer = { 'url': turn_url,
80 | 'credential': password,
81 | 'username': username };
82 | return iceServer;
83 | };
84 | }
85 |
86 | // The RTCPeerConnection object.
87 | RTCPeerConnection = webkitRTCPeerConnection;
88 |
89 | // Get UserMedia (only difference is the prefix).
90 | // Code from Adam Barth.
91 | getUserMedia = navigator.webkitGetUserMedia.bind(navigator);
92 |
93 | // Attach a media stream to an element.
94 | attachMediaStream = function(element, stream) {
95 | if (typeof element.srcObject !== 'undefined') {
96 | element.srcObject = stream;
97 | } else if (typeof element.mozSrcObject !== 'undefined') {
98 | element.mozSrcObject = stream;
99 | } else if (typeof element.src !== 'undefined') {
100 | element.src = URL.createObjectURL(stream);
101 | } else {
102 | console.log('Error attaching stream to element.');
103 | }
104 | };
105 |
106 | reattachMediaStream = function(to, from) {
107 | to.src = from.src;
108 | };
109 |
110 | // The representation of tracks in a stream is changed in M26.
111 | // Unify them for earlier Chrome versions in the coexisting period.
112 | if (!webkitMediaStream.prototype.getVideoTracks) {
113 | webkitMediaStream.prototype.getVideoTracks = function() {
114 | return this.videoTracks;
115 | };
116 | webkitMediaStream.prototype.getAudioTracks = function() {
117 | return this.audioTracks;
118 | };
119 | }
120 |
121 | // New syntax of getXXXStreams method in M26.
122 | if (!webkitRTCPeerConnection.prototype.getLocalStreams) {
123 | webkitRTCPeerConnection.prototype.getLocalStreams = function() {
124 | return this.localStreams;
125 | };
126 | webkitRTCPeerConnection.prototype.getRemoteStreams = function() {
127 | return this.remoteStreams;
128 | };
129 | }
130 | } else {
131 | console.log("Browser does not appear to be WebRTC-capable");
132 | }
133 |
--------------------------------------------------------------------------------
/extension/js/lib/socket.io-client.js:
--------------------------------------------------------------------------------
1 | (function(){function require(p,parent,orig){var path=require.resolve(p),mod=require.modules[path];console.log('path, p: ', path, p);if(null==path){orig=orig||p;parent=parent||"root";throw new Error('failed to require "'+orig+'" from "'+parent+'"')}if(!mod.exports){mod.exports={};mod.client=mod.component=true;mod.call(this,mod,mod.exports,require.relative(path))}return mod.exports}require.modules={};require.aliases={};require.resolve=function(path){var orig=path,reg=path+".js",regJSON=path+".json",index=path+"/index.js",indexJSON=path+"/index.json";return require.modules[reg]&®||require.modules[regJSON]&®JSON||require.modules[index]&&index||require.modules[indexJSON]&&indexJSON||require.modules[orig]&&orig||require.aliases[index]};require.normalize=function(curr,path){var segs=[];if("."!=path.charAt(0))return path;curr=curr.split("/");path=path.split("/");for(var i=0;i