├── .editorconfig ├── .gitignore ├── .jshintrc ├── .npmignore ├── .travis.yml ├── LICENSE ├── README.md ├── examples ├── diagnostic │ └── index.js └── loopback │ └── index.js ├── index.js ├── lib ├── copyStyle.js ├── findPlatform.js ├── temasys │ ├── MockRTC.js │ ├── createPluginElement.js │ ├── createVideoElement.js │ ├── getDownloadURL.js │ ├── getRTC.js │ ├── index.js │ └── isInstalled.js └── temp-mock.js ├── package.json ├── platforms ├── chrome │ ├── attachStream.js │ ├── gum.js │ ├── match.js │ └── rtc.js ├── cordova-ios │ ├── attachStream.js │ ├── gum.js │ ├── match.js │ └── rtc.js ├── edge │ ├── attachStream.js │ ├── gum.js │ ├── match.js │ └── rtc.js ├── firefox │ ├── attachStream.js │ ├── gum.js │ ├── match.js │ └── rtc.js ├── ie │ ├── attachStream.js │ ├── gum.js │ ├── match.js │ └── rtc.js ├── node │ ├── attachStream.js │ ├── gum.js │ ├── match.js │ └── rtc.js ├── react-native │ ├── attachStream.js │ ├── gum.js │ ├── match.js │ └── rtc.js ├── safari │ ├── attachStream.js │ ├── gum.js │ ├── match.js │ └── rtc.js └── unsupported │ ├── attachStream.js │ ├── gum.js │ └── rtc.js ├── test ├── index.js ├── lib │ ├── copyStyle.js │ └── temp-mock.js └── util │ ├── onMicChange.js │ └── onStreamLoaded.js └── util ├── onMicChange.js └── onStreamLoaded ├── hasValidTrack.js ├── index.js ├── isAudioWorking.js └── isVideoWorking.js /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | build 3 | lib-cov 4 | *.seed 5 | *.log 6 | *.csv 7 | *.dat 8 | *.out 9 | *.pid 10 | *.gz 11 | 12 | pids 13 | logs 14 | results 15 | 16 | npm-debug.log 17 | node_modules 18 | *.sublime* 19 | *.node 20 | coverage 21 | *.orig 22 | .idea 23 | sandbox 24 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "camelcase": true, 3 | "curly": false, 4 | "eqeqeq": true, 5 | "freeze": true, 6 | "indent": 2, 7 | "newcap": false, 8 | "quotmark": "single", 9 | "maxdepth": 3, 10 | "maxstatements": 50, 11 | "maxlen": 80, 12 | "eqnull": true, 13 | "funcscope": true, 14 | "strict": true, 15 | "undef": true, 16 | "unused": true, 17 | "node": true, 18 | "browser": true, 19 | "mocha": true, 20 | "globals": { 21 | "cordova": true, 22 | "ActiveXObject": true 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | build 3 | lib-cov 4 | *.seed 5 | *.log 6 | *.csv 7 | *.dat 8 | *.out 9 | *.pid 10 | *.gz 11 | 12 | pids 13 | logs 14 | results 15 | 16 | npm-debug.log 17 | node_modules 18 | *.sublime* 19 | *.node 20 | coverage 21 | *.orig 22 | .idea 23 | sandbox 24 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: node_js 3 | node_js: 4 | - 'iojs' 5 | - '0.12' 6 | - '0.10' 7 | - '4.0.0' 8 | - '5.0.0' 9 | after_script: 10 | - npm run coveralls 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015 Contra 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # rtc-everywhere [![NPM version][npm-image]][npm-url] [![Downloads][downloads-image]][npm-url] [![Build Status][travis-image]][travis-url] 2 | 3 | 4 | 5 | ## What is this? 6 | 7 | Sick of the incompatible mess of vendor prefixes, adapters, plugins, extensions, and native modules? rtc-everywhere aims to provide a spec-compliant WebRTC implementation in as many environments as possible. 8 | 9 | ### Supported Environments 10 | 11 | #### :computer: Desktop 12 | 13 | - Chrome 14 | - Firefox 15 | - MS Edge [Partial] 16 | - No data channels 17 | - Safari 7+ 18 | - Requires Temasys Plugin 19 | - Internet Explorer 9+ [In Progress] 20 | - Requires Temasys Plugin 21 | 22 | #### :iphone: Mobile 23 | 24 | - Android 5+ 25 | - Cordova iOS 26 | - Requires install of `cordova-iosrtc` 27 | - Cordova Android 28 | - Requires install of `cordova-crosswalk` 29 | - react-native iOS/Android [In Progress] 30 | - Requires install of `react-native-webrtc` 31 | 32 | #### Other 33 | 34 | - Node.js 0.10+ 35 | - Requires install of `wrtc` 36 | - Only works on x64 Linux, Mac, and Windows 37 | - MediaStream APIs are not supported at this time 38 | - See [wrtc](https://github.com/js-platform/node-webrtc) for more info. 39 | 40 | ### Getting Started 41 | 42 | ``` 43 | npm install rtc-everywhere --save 44 | ``` 45 | 46 | ```js 47 | var rtc = require('rtc-everywhere')(); 48 | 49 | // Available: 50 | // rtc.RTCPeerConnection 51 | // rtc.RTCIceCandidate 52 | // rtc.RTCSessionDescription 53 | // rtc.getUserMedia 54 | // rtc.attachStream(stream, videoElement) 55 | ``` 56 | 57 | :crystal_ball: Want a more detailed example that uses these functions? Check out the [loopback stream example!](https://github.com/contra/rtc-everywhere/blob/master/examples/loopback/index.js) 58 | 59 | ### API 60 | #### RTCPeerConnection 61 | 62 | Exactly the same as the specification. See the [Specification Documentation](https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection)! 63 | 64 | #### RTCIceCandidate 65 | 66 | Exactly the same as the specification. See the [Specification Documentation](http://html5index.org/WebRTC%20-%20RTCIceCandidate.html)! 67 | 68 | #### RTCSessionDescription 69 | 70 | Exactly the same as the specification. See the [Specification Documentation](https://developer.mozilla.org/en-US/docs/Web/API/RTCSessionDescription)! 71 | 72 | #### getUserMedia(constraints, cb) 73 | 74 | Similar to the [specification](https://developer.mozilla.org/en-US/docs/Web/API/Navigator/getUserMedia), but slightly adjusted to have an easier API. 75 | 76 | ##### Modifications 77 | 78 | - `constraints` is optional (makes things easier) 79 | - Defaults to `{video: true, audio: true}` 80 | 81 | - `cb` is a node-style error first callback 82 | 83 | ```js 84 | // these are the same thing 85 | rtc.getUserMedia(function(err, stream){}); 86 | rtc.getUserMedia({video: true, audio: true}, function(err, stream){}); 87 | ``` 88 | 89 | #### attachStream(stream, element) 90 | 91 | - Attaches a stream to a given video element 92 | - Returns the element the video was attached to 93 | - In IE and Safari, the video element will be replaced by an `object` element 94 | - Elements will not be replaced or modified unless they exist on the DOM 95 | - Regardless of replacement, the new `object` element will be returned 96 | 97 | ### Related Libraries 98 | 99 | - [simple-peer](https://github.com/feross/simple-peer) 100 | - [blob-util](https://github.com/nolanlawson/blob-util) 101 | 102 | 103 | [downloads-image]: http://img.shields.io/npm/dm/rtc-everywhere.svg 104 | [npm-url]: https://npmjs.org/package/rtc-everywhere 105 | [npm-image]: http://img.shields.io/npm/v/rtc-everywhere.svg 106 | [travis-url]: https://travis-ci.org/contra/rtc-everywhere 107 | [travis-image]: https://travis-ci.org/contra/rtc-everywhere.png?branch=master 108 | [depstat-url]: https://david-dm.org/contra/rtc-everywhere 109 | [depstat-image]: https://david-dm.org/contra/rtc-everywhere.png 110 | [david-url]: https://david-dm.org/contra/rtc-everywhere 111 | [david-image]: https://david-dm.org/contra/rtc-everywhere.png?theme=shields.io 112 | -------------------------------------------------------------------------------- /examples/diagnostic/index.js: -------------------------------------------------------------------------------- 1 | //require('es5-shim-sham'); 2 | 3 | var ctor = require('../../'); 4 | var rtc = ctor(); 5 | var Peer = require('simple-peer'); 6 | var crel = require('crel'); 7 | var browser = require('detect-browser'); 8 | var onStreamLoaded = require('../../util/onStreamLoaded'); 9 | 10 | window.ctor = ctor; 11 | window.rtc = rtc; 12 | bootstrap(); 13 | 14 | 15 | function bootstrap(){ 16 | console.log('Platform:', rtc.platform); 17 | console.debug('Inspect:', rtc); 18 | 19 | if (rtc.platform === 'unsupported') { 20 | console.error('Platform not supported!'); 21 | return; 22 | } 23 | 24 | rtc.getUserMedia(function(err, stream){ 25 | if (err) { 26 | return console.error(err); 27 | } 28 | console.debug('User camera:', stream); 29 | createLoopback(stream); 30 | }); 31 | } 32 | 33 | function createLoopback(stream){ 34 | 35 | // make this funky wrapper due to edge issues 36 | function getStream(){ 37 | if (rtc.platform === 'edge') { 38 | return stream.clone(); 39 | } 40 | return stream; 41 | } 42 | 43 | onStreamLoaded(getStream(), function(err, res){ 44 | if (err) console.error('StreamLoaded error:', err); 45 | if (res) console.log('StreamLoaded:', res); 46 | }); 47 | 48 | var initiator = new Peer({ 49 | initiator: true, 50 | trickle: (rtc.platform !== 'edge'), 51 | stream: getStream(), 52 | wrtc: rtc 53 | }); 54 | 55 | var receiver = new Peer({ 56 | stream: getStream(), 57 | trickle: (rtc.platform !== 'edge'), 58 | wrtc: rtc 59 | }); 60 | 61 | //debug(initiator, 'initiator'); 62 | //debug(receiver, 'receiver'); 63 | 64 | initiator.on('error', console.error.bind(console)); 65 | receiver.on('error', console.error.bind(console)); 66 | 67 | initiator.on('signal', receiver.signal.bind(receiver)); 68 | receiver.on('signal', initiator.signal.bind(initiator)); 69 | 70 | initiator.once('connect', function(){ 71 | console.debug('Connected!'); 72 | }); 73 | 74 | initiator.once('stream', function(stream){ 75 | console.debug('Stream relayed receiver -> initiator'); 76 | crel(document.body, makeVideo(stream)); 77 | }); 78 | 79 | receiver.once('stream', function(stream){ 80 | console.debug('Stream relayed initiator -> receiver'); 81 | crel(document.body, makeVideo(stream)); 82 | }); 83 | } 84 | 85 | function debug(peer, id){ 86 | peer.on('signal', function(m){ 87 | console.debug(id, 'signal:', JSON.stringify(m)); 88 | }); 89 | } 90 | 91 | function makeVideo(stream) { 92 | var el = crel('video', { 93 | muted: true, 94 | autoplay: true, 95 | className: 'video-stream', 96 | style: 'max-height:100px; max-width:100px; border: 2px solid gray; display:inline-block; background-color:black;' 97 | }); 98 | return rtc.attachStream(stream, el); 99 | } 100 | -------------------------------------------------------------------------------- /examples/loopback/index.js: -------------------------------------------------------------------------------- 1 | var rtc = require('../../')(); 2 | var Peer = require('simple-peer'); 3 | var crel = require('crel'); 4 | 5 | if (rtc.platform === 'unsupported') { 6 | console.error('Platform not supported!'); 7 | } 8 | 9 | rtc.getUserMedia(function(err, stream){ 10 | if (err) return console.error(err); 11 | createLoopback(stream); 12 | }); 13 | 14 | function createLoopback(stream){ 15 | var initiator = new Peer({ 16 | initiator: true, 17 | stream: stream, 18 | wrtc: rtc 19 | }); 20 | 21 | var receiver = new Peer({ 22 | stream: stream, 23 | wrtc: rtc 24 | }); 25 | 26 | initiator.on('error', console.error.bind(console)); 27 | receiver.on('error', console.error.bind(console)); 28 | 29 | initiator.on('signal', receiver.signal.bind(receiver)); 30 | receiver.on('signal', initiator.signal.bind(initiator)); 31 | 32 | initiator.once('stream', function(stream){ 33 | console.debug('Stream relayed receiver -> initiator'); 34 | crel(document.body, makeVideo(stream)); 35 | }); 36 | 37 | receiver.once('stream', function(stream){ 38 | console.debug('Stream relayed initiator -> receiver'); 39 | crel(document.body, makeVideo(stream)); 40 | }); 41 | } 42 | 43 | function makeVideo(stream) { 44 | var el = crel('video', { 45 | muted: true, 46 | autoplay: true 47 | }); 48 | return rtc.attachStream(stream, el); 49 | } 50 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var findPlatform = require('./lib/findPlatform'); 4 | 5 | module.exports = function(opt) { 6 | if (!opt) opt = {}; 7 | var match = findPlatform(opt); 8 | var ctors = match.platform.rtc(opt); 9 | var gum = match.platform.gum(opt); 10 | var attachStream = match.platform.attachStream(opt); 11 | var supported = !!ctors.RTCPeerConnection; 12 | 13 | return { 14 | platform: match.name, 15 | supported: supported, 16 | getUserMedia: gum, 17 | RTCPeerConnection: ctors.RTCPeerConnection, 18 | RTCSessionDescription: ctors.RTCSessionDescription, 19 | RTCIceCandidate: ctors.RTCIceCandidate, 20 | attachStream: attachStream 21 | }; 22 | }; 23 | 24 | module.exports.platforms = findPlatform.platforms; 25 | -------------------------------------------------------------------------------- /lib/copyStyle.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | function copyStyle(src, dest){ 4 | dest.setAttribute('style', dest.style.cssText + src.style.cssText); 5 | if (dest.className) { 6 | dest.className = dest.className + ' ' + src.className; 7 | } else { 8 | dest.className = src.className; 9 | } 10 | } 11 | 12 | module.exports = copyStyle; 13 | -------------------------------------------------------------------------------- /lib/findPlatform.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var requireDir = require('bulk-require'); 4 | var platforms = requireDir(__dirname + '/../platforms', ['**/*.js']); 5 | var platformKeys = Object.keys(platforms); 6 | 7 | function matches(k){ 8 | var platform = platforms[k]; 9 | return platform && platform.match && platform.match(); 10 | } 11 | 12 | // valid options: platforms, platform 13 | function findPlatform(opt) { 14 | var availPlatforms = platformKeys; 15 | var foundKey; 16 | 17 | if (opt && opt.platforms) { 18 | if (!Array.isArray(opt.platforms)) { 19 | throw new Error('Invalid platforms option'); 20 | } 21 | availPlatforms = availPlatforms.filter(function(p){ 22 | return opt.platforms.indexOf(p) !== -1; 23 | }); 24 | } 25 | 26 | if (opt && opt.platform) { 27 | if (availPlatforms.indexOf(opt.platform) === -1) { 28 | throw new Error('Invalid platform option'); 29 | } 30 | foundKey = opt.platform; 31 | } else { 32 | foundKey = availPlatforms.filter(matches)[0]; 33 | } 34 | 35 | return { 36 | name: foundKey || 'unsupported', 37 | platform: platforms[foundKey] || platforms.unsupported 38 | }; 39 | } 40 | 41 | module.exports = findPlatform; 42 | module.exports.platforms = platformKeys; 43 | -------------------------------------------------------------------------------- /lib/temasys/MockRTC.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var mock = require('../temp-mock'); 4 | var temasys = require('./index'); 5 | var isInstalled = require('./isInstalled'); 6 | var nu = require('new-operator'); 7 | 8 | var RTCPeerConnectionMock = mock({ 9 | methods: [ 10 | 'addIceCandidate', 11 | 'addStream', 12 | 'close', 13 | 'createAnswer', 14 | 'createDataChannel', 15 | 'createOffer', 16 | 'setLocalDescription', 17 | 'setRemoteDescription', 18 | 'removeStream', 19 | 'getLocalStreams', 20 | 'getRemoteStreams', 21 | 'getStats' 22 | ], 23 | properties: [ 24 | 'iceConnectionState', 25 | 'iceGatheringState', 26 | 'localDescription', 27 | 'remoteDescription', 28 | 'onaddstream', 29 | 'ondatachannel', 30 | 'onicecandidate', 31 | 'oniceconnectionstatechange', 32 | 'onnegotiationneeded', 33 | 'onremovestream', 34 | 'onsignalingstatechange', 35 | 'signalingState' 36 | ] 37 | }); 38 | 39 | /* 40 | var RTCSessionDescriptionMock = mock({ 41 | properties: [ 42 | 'type', 43 | 'sdp' 44 | ] 45 | }); 46 | 47 | var RTCIceCandidateMock = mock({ 48 | properties: [ 49 | 'candidate', 50 | 'sdpMid', 51 | 'sdpMLineIndex' 52 | ] 53 | }); 54 | */ 55 | 56 | if (!isInstalled()) { 57 | module.exports = {}; 58 | } else { 59 | module.exports = { 60 | RTCPeerConnection: function(){ 61 | var inst = nu.apply(RTCPeerConnectionMock, arguments); 62 | temasys(function(rtc){ 63 | mock.resolve(inst, rtc.RTCPeerConnection); 64 | }); 65 | return inst; 66 | }, 67 | RTCSessionDescription: function(){ 68 | var ctor; 69 | temasys(function(rtc){ 70 | ctor = rtc.RTCSessionDescription; 71 | }); 72 | if (!ctor) { 73 | throw new Error('Tried RTCSessionDescription before RTCPeerConnection'); 74 | } 75 | return nu.apply(ctor, arguments); 76 | }, 77 | RTCIceCandidate: function(){ 78 | var ctor; 79 | temasys(function(rtc){ 80 | ctor = rtc.RTCIceCandidate; 81 | }); 82 | if (!ctor) { 83 | throw new Error('Tried RTCIceCandidate before RTCPeerConnection'); 84 | } 85 | return nu.apply(ctor, arguments); 86 | } 87 | }; 88 | } 89 | -------------------------------------------------------------------------------- /lib/temasys/createPluginElement.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var crel = require('crel'); 4 | 5 | module.exports = function(opt) { 6 | var params = [ 7 | {name: 'onload', value: opt.onLoad}, 8 | {name: 'pluginId', value: opt.pluginId}, 9 | {name: 'windowless', value: false}, 10 | {name: 'forceGetAllCams', value: true}, 11 | {name: 'pageId', value: opt.pageId} 12 | ].map(crel.bind(null, 'param')); 13 | 14 | return crel('object', { 15 | width: '1px', 16 | height: '1px', 17 | type: 'application/x-temwebrtcplugin', 18 | id: opt.pluginId 19 | }, params); 20 | }; 21 | -------------------------------------------------------------------------------- /lib/temasys/createVideoElement.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var crel = require('crel'); 4 | var uuid = require('uuid'); 5 | 6 | module.exports = function(opt) { 7 | var renderId = uuid.v4(); 8 | var params = [ 9 | {name: 'pluginId', value: opt.pluginId}, 10 | {name: 'windowless', value: true}, 11 | {name: 'pageId', value: opt.pageId}, 12 | {name: 'streamId', value: opt.stream.id}, 13 | ].map(crel.bind(null, 'param')); 14 | 15 | opt.stream.enableSoundTracks(!opt.muted); 16 | 17 | return crel('object', { 18 | type: 'application/x-temwebrtcplugin', 19 | id: renderId 20 | }, params); 21 | }; 22 | -------------------------------------------------------------------------------- /lib/temasys/getDownloadURL.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function(){ 4 | var browser = require('detect-browser'); 5 | if (browser.name === 'safari' && !!navigator.platform.match(/^Mac/i)) { 6 | return 'http://bit.ly/1n77hco'; 7 | } 8 | if (browser.name === 'ie' && !!navigator.platform.match(/^Win/i)) { 9 | return 'http://bit.ly/1kkS4FN'; 10 | } 11 | }; 12 | -------------------------------------------------------------------------------- /lib/temasys/getRTC.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | function RTCPeerConnection(plugin, pageId, opt, constraints){ 4 | if (!constraints) constraints = {}; 5 | if (!opt) opt = {}; 6 | var iceServers = (opt.iceServers || []).map(function(s) { 7 | s.hasCredentials = !!s.credential; 8 | return s; 9 | }); 10 | return plugin.PeerConnection( 11 | pageId, 12 | iceServers, 13 | constraints.mandatory || null, 14 | constraints.optional || null 15 | ); 16 | } 17 | 18 | function RTCSessionDescription(plugin, opt) { 19 | if (!opt) opt = {}; 20 | return plugin.ConstructSessionDescription(opt.type, opt.sdp); 21 | } 22 | 23 | function RTCIceCandidate(plugin, opt) { 24 | if (!opt) opt = {}; 25 | return plugin.ConstructIceCandidate( 26 | opt.sdpMid || '', 27 | opt.sdpMLineIndex, 28 | opt.candidate 29 | ); 30 | } 31 | 32 | module.exports = function(plugin, pageId){ 33 | return { 34 | getUserMedia: plugin.getUserMedia, 35 | RTCPeerConnection: RTCPeerConnection.bind(null, plugin, pageId), 36 | RTCSessionDescription: RTCSessionDescription.bind(null, plugin), 37 | RTCIceCandidate: RTCIceCandidate.bind(null, plugin) 38 | }; 39 | }; 40 | -------------------------------------------------------------------------------- /lib/temasys/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var uuid = require('uuid'); 4 | var domReady = require('domready'); 5 | var crel = require('crel'); 6 | var merge = require('lodash.merge'); 7 | var EventEmitter = require('events').EventEmitter; 8 | var getRTC = require('./getRTC'); 9 | var createPluginElement = require('./createPluginElement'); 10 | var createVideoElement = require('./createVideoElement'); 11 | var isInstalled = require('./isInstalled'); 12 | 13 | var plugin, rtc, loading; 14 | var ee = new EventEmitter(); 15 | var pluginId = uuid.v4(); 16 | var pageId = uuid.v4(); 17 | var onLoadFn = '___$' + pluginId; 18 | 19 | // TODO: handle not installed 20 | // this takes about 272ms to load the plugin 21 | module.exports = function(cb){ 22 | if (!isInstalled()) { 23 | if (cb) cb(); 24 | return; 25 | } 26 | if (rtc) { 27 | if (cb) cb(rtc); 28 | return; 29 | } 30 | loadPlugin(); 31 | if (cb) ee.once('ready', cb); 32 | }; 33 | module.exports.createVideo = function(stream, opt) { 34 | if (!opt) opt = {}; 35 | return createVideoElement(merge({ 36 | pluginId: pluginId, 37 | pageId: pageId, 38 | stream: stream 39 | }, opt)); 40 | }; 41 | 42 | function loadPlugin(){ 43 | if (plugin || loading) return; 44 | loading = true; 45 | window[onLoadFn] = handleLoad; 46 | domReady(function(){ 47 | plugin = createPluginElement({ 48 | pluginId: pluginId, 49 | pageId: pageId, 50 | onLoad: onLoadFn 51 | }); 52 | loading = false; 53 | crel(document.body, plugin); 54 | }); 55 | } 56 | 57 | function handleLoad() { 58 | delete window[onLoadFn]; 59 | plugin.setPluginId(pageId, pluginId); 60 | plugin.setLogFunction(console); 61 | plugin.setLogLevel('VERBOSE'); 62 | rtc = getRTC(plugin, pageId); 63 | ee.emit('ready', rtc); 64 | } 65 | -------------------------------------------------------------------------------- /lib/temasys/isInstalled.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var pluginName = 'TemWebRTCPlugin'; 4 | var companyPrefix = 'Tem'; 5 | 6 | function getPlugins(){ 7 | var plugs = Array.prototype.slice.call(navigator.plugins); 8 | return plugs.reduce(function(prev, p){ 9 | prev[p.name] = p; 10 | return prev; 11 | }, {}); 12 | } 13 | 14 | function AXExists(prefix, plugin) { 15 | try { 16 | new ActiveXObject(prefix + '.' + plugin); 17 | return true; 18 | } catch (e) { 19 | return false; 20 | } 21 | } 22 | module.exports = function(){ 23 | var browser = require('detect-browser'); 24 | if (browser.name === 'safari') { 25 | return getPlugins()[pluginName]; 26 | } 27 | if (browser.name === 'ie') { 28 | return AXExists(companyPrefix, pluginName); 29 | } 30 | return false; 31 | }; 32 | -------------------------------------------------------------------------------- /lib/temp-mock.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // temporarily mock a class 4 | // when the thing is loaded, replay all actions 5 | // that happened on the mock against the real thing 6 | 7 | function mockit(opt) { 8 | if (!opt) opt = {}; 9 | if (!opt.properties) opt.properties = []; 10 | if (!opt.methods) opt.methods = []; 11 | 12 | function Mock(){ 13 | this._ctorArgs = arguments; 14 | this._actions = []; 15 | this._state = {}; 16 | this._opt = opt; 17 | } 18 | 19 | // TODO: detect key conflicts between these options 20 | opt.properties.forEach(hookProperty); 21 | opt.methods.forEach(hookMethod); 22 | return Mock; 23 | 24 | function hookMethod(k){ 25 | Mock.prototype[k] = function(){ 26 | this._actions.push({ 27 | type: 'call', 28 | key: k, 29 | args: arguments 30 | }); 31 | return this; 32 | }; 33 | } 34 | 35 | function hookProperty(k){ 36 | Object.defineProperty(Mock.prototype, k, { 37 | configurable: true, 38 | set: function(v){ 39 | this._state[k] = v; 40 | this._actions.push({ 41 | type: 'set', 42 | key: k, 43 | value: v 44 | }); 45 | }, 46 | get: function(){ 47 | return this._state[k]; 48 | } 49 | }); 50 | } 51 | } 52 | 53 | mockit.resolve = function(inst, fn) { 54 | // create the real thing 55 | var clazz = fn.apply(fn, inst._ctorArgs); 56 | if (typeof clazz === 'undefined') { 57 | clazz = {}; 58 | } 59 | 60 | // replay all of our stuff onto the real thing 61 | inst._actions.forEach(function(action){ 62 | if (action.type === 'set') { 63 | clazz[action.key] = action.value; 64 | } else if (action.type === 'call' && 65 | typeof clazz[action.key] === 'function') { 66 | clazz[action.key].apply(inst, action.args); 67 | } 68 | }); 69 | 70 | // make our fake thing into the real thing 71 | inst._opt.methods.forEach(function(k){ 72 | inst[k] = clazz[k]; 73 | }); 74 | inst._opt.properties.forEach(function(k){ 75 | Object.defineProperty(inst, k, { 76 | configurable: true, 77 | set: function(v) { 78 | clazz[k] = v; 79 | }, 80 | get: function(){ 81 | return clazz[k]; 82 | } 83 | }); 84 | }); 85 | 86 | delete inst._opt; 87 | delete inst._state; 88 | delete inst._actions; 89 | delete inst._ctorArgs; 90 | return clazz; 91 | }; 92 | 93 | module.exports = mockit; 94 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rtc-everywhere", 3 | "description": "Cross-everything WebRTC Adapter/Polyfill", 4 | "version": "0.0.6", 5 | "homepage": "http://github.com/contra/rtc-everywhere", 6 | "repository": "git://github.com/contra/rtc-everywhere.git", 7 | "author": "Contra (http://contra.io)", 8 | "keywords": [ 9 | "webrtc", 10 | "adapter", 11 | "shim", 12 | "polyfill", 13 | "ios", 14 | "android", 15 | "chrome", 16 | "firefox", 17 | "node", 18 | "safari", 19 | "ortc", 20 | "internet explorer", 21 | "edge", 22 | "getusermedia", 23 | "rtcpeerconnection" 24 | ], 25 | "dependencies": { 26 | "add-event-listener": "0.0.1", 27 | "async": "^1.5.0", 28 | "browserify-optional": "^1.0.0", 29 | "crel": "^2.1.8", 30 | "detect-browser": "^1.2.0", 31 | "domready": "^1.0.8", 32 | "lodash.merge": "^3.3.2", 33 | "new-operator": "^1.0.3", 34 | "ortc-adapter": "^0.1.6", 35 | "uuid": "^2.0.1", 36 | "volume-meter": "^2.0.1", 37 | "bulkify": "^1.1.1", 38 | "bulk-require": "^0.2.1" 39 | }, 40 | "devDependencies": { 41 | "es5-shim-sham": "0.0.1", 42 | "istanbul-coveralls": "^1.0.1", 43 | "jshint": "^2.5.11", 44 | "jshint-stylish": "^2.0.0", 45 | "mocha": "^2.3.4", 46 | "mochify": "dylanfm/mochify.js", 47 | "mochify-istanbul": "^2.1.1", 48 | "should": "^8.0.0", 49 | "simple-peer": "^5.11.6", 50 | "watchify": "^3.6.1" 51 | }, 52 | "peerDependencies": { 53 | "wrtc": "^0.0.59", 54 | "react-native-webrtc": "^0.2.7" 55 | }, 56 | "main": "./index.js", 57 | "scripts": { 58 | "lint": "jshint index.js lib --exclude node_modules --config .jshintrc --reporter node_modules/jshint-stylish/index.js", 59 | "test-node": "mocha --recursive --reporter spec", 60 | "test-browser-local": "mochify --recursive --reporter spec", 61 | "test": "npm run lint && npm run-script test-browser-local", 62 | "coveralls": "mochify --recursive --plugin [ mochify-istanbul --exclude '**/+(test|node_modules)/**/*' --report lcov --dir ./coverage ] && istanbul-coveralls" 63 | }, 64 | "browserify": { 65 | "transform": [ 66 | "browserify-optional", 67 | "bulkify" 68 | ] 69 | }, 70 | "browser": { 71 | "wrtc": false, 72 | "react-native-webrtc": false 73 | }, 74 | "engines": { 75 | "node": ">= 0.10" 76 | }, 77 | "license": "MIT" 78 | } 79 | -------------------------------------------------------------------------------- /platforms/chrome/attachStream.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function(){ 4 | var URL = window.URL || window.webkitURL; 5 | return function(stream, el) { 6 | el.src = URL.createObjectURL(stream); 7 | return el; 8 | }; 9 | }; 10 | -------------------------------------------------------------------------------- /platforms/chrome/gum.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function(opt) { 4 | return function(constraints, cb) { 5 | var getUserMedia = navigator.getUserMedia || 6 | navigator.webkitGetUserMedia; 7 | 8 | if (!getUserMedia) { 9 | throw new Error('Failed to find getUserMedia'); 10 | } 11 | 12 | // make constraints optional 13 | if (arguments.length !== 2) { 14 | cb = constraints; 15 | constraints = { 16 | video: true, 17 | audio: true 18 | }; 19 | } 20 | 21 | function success(stream) { 22 | stream._rtcOpt = opt; 23 | cb(null, stream); 24 | } 25 | 26 | function error(err) { 27 | cb(err); 28 | } 29 | 30 | getUserMedia.call(navigator, constraints, success, error); 31 | }; 32 | }; 33 | -------------------------------------------------------------------------------- /platforms/chrome/match.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function(){ 4 | var browser = require('detect-browser'); 5 | return typeof window !== 'undefined' && 6 | typeof window.chrome !== 'undefined' && 7 | browser.name === 'chrome'; 8 | }; 9 | -------------------------------------------------------------------------------- /platforms/chrome/rtc.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function() { 4 | return { 5 | RTCPeerConnection: window.RTCPeerConnection || 6 | window.webkitRTCPeerConnection, 7 | RTCSessionDescription: window.RTCSessionDescription || 8 | window.webkitRTCSessionDescription, 9 | RTCIceCandidate: window.RTCIceCandidate || 10 | window.webkitRTCIceCandidate 11 | }; 12 | }; 13 | -------------------------------------------------------------------------------- /platforms/cordova-ios/attachStream.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | function needPlatform(){ 4 | throw new Error('Missing iosrtc plugin for attachStream'); 5 | } 6 | 7 | module.exports = function(){ 8 | var URL = window.URL || window.webkitURL; 9 | 10 | return function(stream, el) { 11 | if (typeof cordova === 'undefined') return needPlatform(); 12 | if (typeof cordova.plugins === 'undefined') return needPlatform(); 13 | if (typeof cordova.plugins.iosrtc === 'undefined') return needPlatform(); 14 | 15 | el.src = URL.createObjectURL(stream); 16 | el.setAttribute('webkit-playsinline', 'true'); 17 | cordova.plugins.iosrtc.observeVideo(el); 18 | 19 | return el; 20 | }; 21 | }; 22 | -------------------------------------------------------------------------------- /platforms/cordova-ios/gum.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | function needPlatform(){ 4 | throw new Error('Missing iosrtc plugin for getUserMedia'); 5 | } 6 | 7 | module.exports = function(opt) { 8 | return function(constraints, cb) { 9 | if (typeof cordova === 'undefined') return needPlatform(); 10 | if (typeof cordova.plugins === 'undefined') return needPlatform(); 11 | if (typeof cordova.plugins.iosrtc === 'undefined') return needPlatform(); 12 | 13 | // make constraints optional 14 | if (arguments.length !== 2) { 15 | cb = constraints; 16 | constraints = { 17 | video: true, 18 | audio: true 19 | }; 20 | } 21 | 22 | function success(stream) { 23 | stream._rtcOpt = opt; 24 | cb(null, stream); 25 | } 26 | 27 | function error(err) { 28 | cb(err); 29 | } 30 | 31 | cordova.plugins.iosrtc.getUserMedia(constraints, success, error); 32 | }; 33 | }; 34 | -------------------------------------------------------------------------------- /platforms/cordova-ios/match.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function(){ 4 | return typeof cordova !== 'undefined' && window.device.platform === 'iOS'; 5 | }; 6 | -------------------------------------------------------------------------------- /platforms/cordova-ios/rtc.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | function needPlatform(){ 4 | throw new Error('Missing iosrtc plugin for RTCPeerConnection'); 5 | } 6 | 7 | module.exports = function() { 8 | if (typeof cordova === 'undefined') return needPlatform(); 9 | if (typeof cordova.plugins === 'undefined') return needPlatform(); 10 | if (typeof cordova.plugins.iosrtc === 'undefined') return needPlatform(); 11 | 12 | return { 13 | RTCPeerConnection: cordova.plugins.iosrtc.RTCPeerConnection, 14 | RTCSessionDescription: cordova.plugins.iosrtc.RTCSessionDescription, 15 | RTCIceCandidate: cordova.plugins.iosrtc.RTCIceCandidate 16 | }; 17 | }; 18 | -------------------------------------------------------------------------------- /platforms/edge/attachStream.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function(){ 4 | return function(stream, el) { 5 | el.srcObject = stream; 6 | return el; 7 | }; 8 | }; 9 | -------------------------------------------------------------------------------- /platforms/edge/gum.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function(opt) { 4 | return function(constraints, cb) { 5 | var getUserMedia = navigator.getUserMedia || 6 | navigator.msGetUserMedia; 7 | 8 | if (!getUserMedia) { 9 | throw new Error('Failed to find getUserMedia'); 10 | } 11 | // make constraints optional 12 | if (arguments.length !== 2) { 13 | cb = constraints; 14 | constraints = { 15 | video: true, 16 | audio: true 17 | }; 18 | } 19 | 20 | function success(stream) { 21 | stream._rtcOpt = opt; 22 | cb(null, stream); 23 | } 24 | 25 | function error(err) { 26 | cb(err); 27 | } 28 | 29 | getUserMedia.call(navigator, constraints, success, error); 30 | }; 31 | }; 32 | -------------------------------------------------------------------------------- /platforms/edge/match.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function(){ 4 | var browser = require('detect-browser'); 5 | return browser.name === 'edge' && 6 | window.RTCIceGatherer; 7 | }; 8 | -------------------------------------------------------------------------------- /platforms/edge/rtc.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | function fakeDC(name){ 4 | return { 5 | label: name, 6 | send: function(){}, 7 | close: function(){} 8 | }; 9 | } 10 | 11 | module.exports = function() { 12 | var ortc = require('ortc-adapter'); 13 | ortc.RTCPeerConnection.prototype.createDataChannel = fakeDC; 14 | 15 | return { 16 | RTCPeerConnection: ortc.RTCPeerConnection, 17 | RTCSessionDescription: ortc.RTCSessionDescription, 18 | RTCIceCandidate: ortc.RTCIceCandidate 19 | }; 20 | }; 21 | -------------------------------------------------------------------------------- /platforms/firefox/attachStream.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function(){ 4 | return function(stream, el) { 5 | el.srcObject = stream; 6 | return el; 7 | }; 8 | }; 9 | -------------------------------------------------------------------------------- /platforms/firefox/gum.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function(opt) { 4 | return function(constraints, cb) { 5 | var getUserMedia = navigator.getUserMedia || 6 | navigator.mozGetUserMedia; 7 | 8 | if (!getUserMedia) { 9 | throw new Error('Failed to find getUserMedia'); 10 | } 11 | 12 | // make constraints optional 13 | if (arguments.length !== 2) { 14 | cb = constraints; 15 | constraints = { 16 | video: true, 17 | audio: true 18 | }; 19 | } 20 | 21 | function success(stream) { 22 | stream._rtcOpt = opt; 23 | cb(null, stream); 24 | } 25 | 26 | function error(err) { 27 | cb(err); 28 | } 29 | 30 | getUserMedia.call(navigator, constraints, success, error); 31 | }; 32 | }; 33 | -------------------------------------------------------------------------------- /platforms/firefox/match.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function(){ 4 | var browser = require('detect-browser'); 5 | return browser.name === 'firefox'; 6 | }; 7 | -------------------------------------------------------------------------------- /platforms/firefox/rtc.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function() { 4 | return { 5 | RTCPeerConnection: window.RTCPeerConnection || 6 | window.mozRTCPeerConnection, 7 | RTCSessionDescription: window.RTCSessionDescription || 8 | window.mozRTCSessionDescription, 9 | RTCIceCandidate: window.RTCIceCandidate || 10 | window.mozRTCIceCandidate 11 | }; 12 | }; 13 | -------------------------------------------------------------------------------- /platforms/ie/attachStream.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function(){ 4 | var copyStyle = require('../../lib/copyStyle'); 5 | var temasys = require('../../lib/temasys'); 6 | return function(stream, el) { 7 | var newVideo = temasys.createVideo(stream, { 8 | muted: Boolean(el.muted) 9 | }); 10 | copyStyle(el, newVideo); 11 | 12 | // replace el if needed 13 | var container = el.parentNode; 14 | if (container) { 15 | container.insertBefore(newVideo); 16 | container.removeChild(el); 17 | } 18 | return newVideo; 19 | }; 20 | }; 21 | -------------------------------------------------------------------------------- /platforms/ie/gum.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // TODO: handle not installed 4 | 5 | module.exports = function(opt) { 6 | var temasys = require('../../lib/temasys'); 7 | temasys(); // start loading ahead of time 8 | 9 | return function(constraints, cb) { 10 | // make constraints optional 11 | if (arguments.length !== 2) { 12 | cb = constraints; 13 | constraints = { 14 | video: true, 15 | audio: true 16 | }; 17 | } 18 | 19 | function success(stream) { 20 | stream._rtcOpt = opt; 21 | cb(null, stream); 22 | } 23 | 24 | function error(err) { 25 | cb(err); 26 | } 27 | 28 | temasys(function(rtc){ 29 | if (!rtc || !rtc.getUserMedia) { 30 | throw new Error('Failed to find getUserMedia'); 31 | } 32 | 33 | rtc.getUserMedia(constraints, success, error); 34 | }); 35 | }; 36 | }; 37 | -------------------------------------------------------------------------------- /platforms/ie/match.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function(){ 4 | var browser = require('detect-browser'); 5 | return browser.name === 'ie'; 6 | }; 7 | -------------------------------------------------------------------------------- /platforms/ie/rtc.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // TODO: handle not installed 4 | 5 | module.exports = function() { 6 | var MockRTC = require('../../lib/temasys/MockRTC'); 7 | return MockRTC; 8 | }; 9 | -------------------------------------------------------------------------------- /platforms/node/attachStream.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = require('../unsupported/attachStream'); 4 | -------------------------------------------------------------------------------- /platforms/node/gum.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = require('../unsupported/gum'); 4 | -------------------------------------------------------------------------------- /platforms/node/match.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function(){ 4 | return typeof window === 'undefined'; 5 | }; 6 | -------------------------------------------------------------------------------- /platforms/node/rtc.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function() { 4 | var wrtc; 5 | try { 6 | wrtc = require('wrtc'); 7 | } catch (err) { 8 | return {}; 9 | } 10 | return { 11 | RTCPeerConnection: wrtc.RTCPeerConnection, 12 | RTCSessionDescription: wrtc.RTCSessionDescription, 13 | RTCIceCandidate: wrtc.RTCIceCandidate 14 | }; 15 | }; 16 | -------------------------------------------------------------------------------- /platforms/react-native/attachStream.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function(){ 4 | return function(stream, el) { 5 | // TODO 6 | }; 7 | }; 8 | -------------------------------------------------------------------------------- /platforms/react-native/gum.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function(opt) { 4 | var rtc; 5 | try { 6 | rtc = require('react-native-webrtc'); 7 | } catch (err) { 8 | throw new Error('Missing react-native-webrtc!'); 9 | } 10 | return function(constraints, cb) { 11 | var getUserMedia = navigator.getUserMedia; 12 | 13 | if (!getUserMedia) { 14 | throw new Error('Failed to find getUserMedia'); 15 | } 16 | 17 | // make constraints optional 18 | if (arguments.length !== 2) { 19 | cb = constraints; 20 | constraints = { 21 | video: true, 22 | audio: true 23 | }; 24 | } 25 | 26 | function success(stream) { 27 | stream._rtcOpt = opt; 28 | cb(null, stream); 29 | } 30 | 31 | function error(err) { 32 | cb(err); 33 | } 34 | 35 | getUserMedia.call(navigator, constraints, success, error); 36 | }; 37 | }; 38 | -------------------------------------------------------------------------------- /platforms/react-native/match.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function(){ 4 | // TODO 5 | return false; 6 | }; 7 | -------------------------------------------------------------------------------- /platforms/react-native/rtc.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function() { 4 | var rtc; 5 | try { 6 | rtc = require('react-native-webrtc'); 7 | } catch (err) { 8 | throw new Error('Missing react-native-webrtc!'); 9 | } 10 | return { 11 | RTCPeerConnection: rtc.RTCPeerConnection, 12 | RTCSessionDescription: rtc.RTCSessionDescription, 13 | RTCIceCandidate: rtc.RTCIceCandidate 14 | }; 15 | }; 16 | -------------------------------------------------------------------------------- /platforms/safari/attachStream.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function(){ 4 | var copyStyle = require('../../lib/copyStyle'); 5 | var temasys = require('../../lib/temasys'); 6 | return function(stream, el) { 7 | var newVideo = temasys.createVideo(stream, { 8 | muted: Boolean(el.muted) 9 | }); 10 | copyStyle(el, newVideo); 11 | 12 | // replace el if needed 13 | var container = el.parentNode; 14 | if (container) { 15 | container.insertBefore(newVideo); 16 | container.removeChild(el); 17 | } 18 | return newVideo; 19 | }; 20 | }; 21 | -------------------------------------------------------------------------------- /platforms/safari/gum.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // TODO: handle not installed 4 | 5 | module.exports = function(opt) { 6 | var temasys = require('../../lib/temasys'); 7 | temasys(); // start loading ahead of time 8 | 9 | return function(constraints, cb) { 10 | // make constraints optional 11 | if (arguments.length !== 2) { 12 | cb = constraints; 13 | constraints = { 14 | video: true, 15 | audio: true 16 | }; 17 | } 18 | 19 | function success(stream) { 20 | stream._rtcOpt = opt; 21 | cb(null, stream); 22 | } 23 | 24 | function error(err) { 25 | cb(err); 26 | } 27 | 28 | temasys(function(rtc){ 29 | if (!rtc || !rtc.getUserMedia) { 30 | throw new Error('Failed to find getUserMedia'); 31 | } 32 | rtc.getUserMedia(constraints, success, error); 33 | }); 34 | }; 35 | }; 36 | -------------------------------------------------------------------------------- /platforms/safari/match.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function(){ 4 | var browser = require('detect-browser'); 5 | return browser.name === 'safari' && typeof cordova === 'undefined'; 6 | }; 7 | -------------------------------------------------------------------------------- /platforms/safari/rtc.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // TODO: handle not installed 4 | 5 | module.exports = function() { 6 | var MockRTC = require('../../lib/temasys/MockRTC'); 7 | return MockRTC; 8 | }; 9 | -------------------------------------------------------------------------------- /platforms/unsupported/attachStream.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function(){ 4 | return function(stream, el) { 5 | throw new Error('No DOM, what are you doing?'); 6 | }; 7 | }; 8 | -------------------------------------------------------------------------------- /platforms/unsupported/gum.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function() { 4 | return function(constraints, cb) { 5 | 6 | // make constraints optional 7 | if (arguments.length !== 2) { 8 | cb = constraints; 9 | } 10 | 11 | var err = new Error('MediaStreamError'); 12 | err.name = 'NotSupportedError'; 13 | return cb(err); 14 | }; 15 | }; 16 | -------------------------------------------------------------------------------- /platforms/unsupported/rtc.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function() { 4 | return { 5 | RTCPeerConnection: null, 6 | RTCSessionDescription: null, 7 | RTCIceCandidate: null 8 | }; 9 | }; 10 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var should = require('should'); 4 | var rtc = require('../'); 5 | 6 | describe('rtc', function() { 7 | it('should export a function', function(done) { 8 | should.exist(rtc); 9 | (typeof rtc).should.equal('function'); 10 | done(); 11 | }); 12 | }); 13 | 14 | describe('rtc()', function(){ 15 | it('should export an object', function(done) { 16 | var val = rtc(); 17 | (typeof val).should.equal('object'); 18 | done(); 19 | }); 20 | it('should export the correct keys', function(done) { 21 | var val = rtc(); 22 | Object.keys(val).should.eql([ 23 | 'platform', 24 | 'supported', 25 | 'getUserMedia', 26 | 'RTCPeerConnection', 27 | 'RTCSessionDescription', 28 | 'RTCIceCandidate', 29 | 'attachStream' 30 | ]); 31 | done(); 32 | }); 33 | it.skip('should be supported', function(done) { 34 | var val = rtc(); 35 | val.supported.should.equal(true); 36 | done(); 37 | }); 38 | }); 39 | -------------------------------------------------------------------------------- /test/lib/copyStyle.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var should = require('should'); 4 | var crel = require('crel'); 5 | var copyStyle = require('../../lib/copyStyle'); 6 | 7 | describe('copyStyle()', function() { 8 | it('should copy style object', function() { 9 | var src = crel('div', { 10 | style: 'display: none;' 11 | }); 12 | var dest = crel('div', { 13 | style: 'display: inline-block;' 14 | }); 15 | copyStyle(src, dest); 16 | dest.style.display.should.equal('none'); 17 | }); 18 | it('should copy style object but not overwrite', function() { 19 | var src = crel('div', { 20 | style: 'display: none;' 21 | }); 22 | var dest = crel('div', { 23 | style: 'display: inline-block; color: red;' 24 | }); 25 | copyStyle(src, dest); 26 | dest.style.display.should.equal('none'); 27 | dest.style.color.should.equal('red'); 28 | }); 29 | 30 | it('should copy class string', function() { 31 | var src = crel('div', { 32 | class: 'test' 33 | }); 34 | var dest = crel('div'); 35 | copyStyle(src, dest); 36 | dest.className.should.equal('test'); 37 | }); 38 | 39 | it('should merge a class string', function() { 40 | var src = crel('div', { 41 | class: 'test' 42 | }); 43 | var dest = crel('div', { 44 | class: 'test2' 45 | }); 46 | copyStyle(src, dest); 47 | dest.className.should.equal('test2 test'); 48 | }); 49 | }); 50 | -------------------------------------------------------------------------------- /test/lib/temp-mock.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var should = require('should'); 4 | var mock = require('../../lib/temp-mock'); 5 | 6 | describe('temp-mock', function() { 7 | it('should construct', function(done) { 8 | var Clazz = mock(); 9 | var o = new Clazz(1, 2, 3); 10 | 11 | mock.resolve(o, function(){ 12 | Array.prototype.slice.call(arguments).should.eql([1, 2, 3]); 13 | done(); 14 | }); 15 | }); 16 | 17 | it('should play back a method', function(done) { 18 | var Clazz = mock({ 19 | methods: ['test'] 20 | }); 21 | var o = new Clazz(); 22 | o.test(4, 5, 6); 23 | 24 | var actual = function(){ 25 | Array.prototype.slice.call(arguments).should.eql([]); 26 | return { 27 | test: function(a, b, c) { 28 | a.should.equal(4); 29 | b.should.equal(5); 30 | c.should.equal(6); 31 | done(); 32 | } 33 | }; 34 | }; 35 | mock.resolve(o, actual); 36 | }); 37 | 38 | it('should correctly forward a method after resolve', function(done) { 39 | var Clazz = mock({ 40 | methods: ['test'] 41 | }); 42 | var o = new Clazz(); 43 | o.test(4, 5, 6); 44 | 45 | var calls = 0; 46 | var actual = function(){ 47 | Array.prototype.slice.call(arguments).should.eql([]); 48 | return { 49 | test: function(a, b, c) { 50 | ++calls; 51 | if (calls === 1) { 52 | a.should.equal(4); 53 | b.should.equal(5); 54 | c.should.equal(6); 55 | return; 56 | } 57 | 58 | if (calls === 2) { 59 | a.should.equal(7); 60 | b.should.equal(8); 61 | c.should.equal(9); 62 | } 63 | } 64 | }; 65 | }; 66 | mock.resolve(o, actual); 67 | o.test(7, 8, 9); 68 | calls.should.equal(2); 69 | done(); 70 | }); 71 | 72 | it('should play back a set', function(done) { 73 | var Clazz = mock({ 74 | properties: ['test'] 75 | }); 76 | var o = new Clazz(); 77 | o.test = 123; 78 | 79 | var actual = function(){ 80 | return {}; 81 | }; 82 | var inst = mock.resolve(o, actual); 83 | inst.test.should.equal(123); 84 | done(); 85 | }); 86 | 87 | it('should forward a set after resolve', function(done) { 88 | var Clazz = mock({ 89 | properties: ['test'] 90 | }); 91 | var o = new Clazz(); 92 | var actual = function(){ 93 | return {}; 94 | }; 95 | var inst = mock.resolve(o, actual); 96 | o.test = 456; 97 | inst.test.should.equal(456); 98 | o.test.should.equal(456); 99 | done(); 100 | }); 101 | 102 | it('should play back a function set', function(done) { 103 | var Clazz = mock({ 104 | properties: ['test'] 105 | }); 106 | var o = new Clazz(); 107 | o.test = function(a){ 108 | should.exist(a); 109 | a.should.equal(123); 110 | done(); 111 | }; 112 | 113 | var actual = function(){ 114 | return {}; 115 | }; 116 | var inst = mock.resolve(o, actual); 117 | inst.test(123); 118 | }); 119 | 120 | it('should play back two sets', function(done) { 121 | var Clazz = mock({ 122 | properties: ['test'] 123 | }); 124 | var o = new Clazz(); 125 | o.test = 123; 126 | o.test = 456; 127 | var actual = function(){ 128 | return {}; 129 | }; 130 | var inst = mock.resolve(o, actual); 131 | inst.test.should.equal(456); 132 | done(); 133 | }); 134 | 135 | it('should play back everything in order', function(done) { 136 | var Clazz = mock({ 137 | methods: ['test'], 138 | properties: ['testing'] 139 | }); 140 | var o = new Clazz(1, 2, 3); 141 | o.testing = 123; 142 | o.test(4, 5, 6); 143 | 144 | var actual = function(){ 145 | Array.prototype.slice.call(arguments).should.eql([1, 2, 3]); 146 | return { 147 | test: function(a, b, c) { 148 | this.testing.should.equal(123); 149 | a.should.equal(4); 150 | b.should.equal(5); 151 | c.should.equal(6); 152 | } 153 | }; 154 | }; 155 | var inst = mock.resolve(o, actual); 156 | inst.testing.should.equal(123); 157 | done(); 158 | }); 159 | }); 160 | -------------------------------------------------------------------------------- /test/util/onMicChange.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var should = require('should'); 4 | var onMicChange = require('../../util/onMicChange'); 5 | 6 | describe('onMicChange()', function() { 7 | it.skip('should call the callback on volume change', function(done) { 8 | 9 | }); 10 | it.skip('should stop listening when end is called', function(done) { 11 | 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /test/util/onStreamLoaded.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var should = require('should'); 4 | var onStreamLoaded = require('../../util/onStreamLoaded'); 5 | 6 | function createMockStream(tracks) { 7 | return { 8 | getTracks: function(){ 9 | return tracks.map(function(t){ 10 | return { 11 | kind: t.type, 12 | readyState: t.state || 'playing', 13 | muted: t.muted == null ? false : t.muted, 14 | enabled: t.enabled == null ? true : t.enabled 15 | }; 16 | }); 17 | } 18 | }; 19 | } 20 | 21 | describe('onStreamLoaded()', function() { 22 | it('should detect ended video stream', function(done) { 23 | var fakeStream = createMockStream([ 24 | { 25 | type: 'video', 26 | state: 'ended' 27 | } 28 | ]); 29 | onStreamLoaded({video: true}, fakeStream, function(err, res){ 30 | should.exist(err); 31 | done(); 32 | }); 33 | }); 34 | it('should detect muted video stream', function(done) { 35 | var fakeStream = createMockStream([ 36 | { 37 | type: 'video', 38 | muted: true 39 | } 40 | ]); 41 | onStreamLoaded({video: true}, fakeStream, function(err, res){ 42 | should.exist(err); 43 | done(); 44 | }); 45 | }); 46 | it('should detect disabled video stream', function(done) { 47 | var fakeStream = createMockStream([ 48 | { 49 | type: 'video', 50 | enabled: false 51 | } 52 | ]); 53 | onStreamLoaded({video: true}, fakeStream, function(err, res){ 54 | should.exist(err); 55 | done(); 56 | }); 57 | }); 58 | it('should detect missing video stream', function(done) { 59 | var fakeStream = createMockStream([{ 60 | type: 'audio' 61 | }]); 62 | onStreamLoaded({video: true}, fakeStream, function(err, res){ 63 | should.exist(err); 64 | done(); 65 | }); 66 | }); 67 | it('should detect ended audio stream', function(done) { 68 | var fakeStream = createMockStream([ 69 | { 70 | type: 'audio', 71 | state: 'ended' 72 | } 73 | ]); 74 | onStreamLoaded({audio: true}, fakeStream, function(err, res){ 75 | should.exist(err); 76 | done(); 77 | }); 78 | }); 79 | it('should detect muted audio stream', function(done) { 80 | var fakeStream = createMockStream([ 81 | { 82 | type: 'audio', 83 | muted: true 84 | } 85 | ]); 86 | onStreamLoaded({audio: true}, fakeStream, function(err, res){ 87 | should.exist(err); 88 | done(); 89 | }); 90 | }); 91 | it('should detect disabled audio stream', function(done) { 92 | var fakeStream = createMockStream([ 93 | { 94 | type: 'audio', 95 | enabled: false 96 | } 97 | ]); 98 | onStreamLoaded({audio: true}, fakeStream, function(err, res){ 99 | should.exist(err); 100 | done(); 101 | }); 102 | }); 103 | it('should detect missing audio stream', function(done) { 104 | var fakeStream = createMockStream([{ 105 | type: 'video' 106 | }]); 107 | onStreamLoaded({audio: true}, fakeStream, function(err, res){ 108 | should.exist(err); 109 | done(); 110 | }); 111 | }); 112 | it.skip('should detect hardware muted video stream', function(done) { 113 | 114 | }); 115 | it.skip('should detect hardware muted audio stream', function(done) { 116 | 117 | }); 118 | it.skip('should return video metadata', function(done) { 119 | 120 | }); 121 | it.skip('should return audio metadata', function(done) { 122 | 123 | }); 124 | }); 125 | -------------------------------------------------------------------------------- /util/onMicChange.js: -------------------------------------------------------------------------------- 1 | var meterOpt = { 2 | tweenIn: 2, 3 | tweenOut: 6 4 | }; 5 | 6 | module.exports = function(stream, cb) { 7 | var AudioContext = window.AudioContext || 8 | window.webkitAudioContext || 9 | window.mozAudioContext || 10 | window.msAudioContext; 11 | 12 | if (!AudioContext || !AudioContext.prototype.createMediaStreamSource) { 13 | return; 14 | } 15 | 16 | var watchVolume = require('volume-meter'); 17 | var ctx = new AudioContext(); 18 | var src = ctx.createMediaStreamSource(stream); 19 | var meter = watchVolume(ctx, meterOpt, cb); 20 | var gain = ctx.createGain(); 21 | gain.gain.value = 0; 22 | 23 | // stream -> meter -> zero gain -> dest 24 | src.connect(meter); 25 | meter.connect(gain); 26 | gain.connect(ctx.destination); 27 | 28 | return { 29 | end: function(){ 30 | // TODO: verify this works 31 | if (meter.disconnect) meter.disconnect(); 32 | if (gain.disconnect) gain.disconnect(); 33 | if (src.disconnect) src.disconnect(); 34 | 35 | if (meter.stop) meter.stop(); 36 | if (gain.stop) gain.stop(); 37 | if (src.stop) src.stop(); 38 | if (ctx.close) ctx.close(); 39 | } 40 | }; 41 | }; 42 | -------------------------------------------------------------------------------- /util/onStreamLoaded/hasValidTrack.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | function isTrackEnabled(type, t){ 4 | return t.kind === type && t.enabled && 5 | !t.muted && t.readyState !== 'ended'; 6 | } 7 | 8 | function hasValidTrack(stream, type){ 9 | return stream.getTracks().some(isTrackEnabled.bind(null, type)); 10 | } 11 | 12 | module.exports = hasValidTrack; 13 | -------------------------------------------------------------------------------- /util/onStreamLoaded/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var async = require('async'); 4 | var isAudioWorking = require('./isAudioWorking'); 5 | var isVideoWorking = require('./isVideoWorking'); 6 | 7 | function isStreamWorking(opt, stream, cb) { 8 | if (arguments.length !== 3) { 9 | cb = stream; 10 | stream = opt; 11 | opt = null; 12 | } 13 | if (!opt) { 14 | opt = { 15 | video: true, 16 | audio: true 17 | }; 18 | } 19 | var tasks = {}; 20 | if (opt.video) { 21 | tasks.video = isVideoWorking.bind(null, stream); 22 | } 23 | if (opt.audio) { 24 | tasks.audio = isAudioWorking.bind(null, stream); 25 | } 26 | 27 | async.parallel(tasks, cb); 28 | } 29 | 30 | module.exports = isStreamWorking; 31 | -------------------------------------------------------------------------------- /util/onStreamLoaded/isAudioWorking.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var hasValidTrack = require('./hasValidTrack'); 4 | var onMicChange = require('../onMicChange'); 5 | var timeoutTime = 20000; 6 | 7 | // basic premise: 8 | // - check for working audio tracks 9 | // - listen for an audio event with any volume to happen 10 | 11 | function isAudioWorking(stream, cb){ 12 | if (!hasValidTrack(stream, 'audio')) return cb('dead audio'); 13 | if (stream._audioMeta) return cb(null, stream._audioMeta); 14 | if (typeof cordova !== 'undefined') return cb(); 15 | var finished = false; 16 | var listener = onMicChange(stream, handleMicEvent); 17 | if (!listener) return cb(); // raw audio not supported 18 | var timeout = setTimeout(finishIt.bind(null, 'no microphone data'), timeoutTime); 19 | 20 | function finishIt(err){ 21 | if (finished) return; 22 | finished = true; 23 | clearTimeout(timeout); 24 | listener.end(); 25 | if (!err) { 26 | stream._audioMeta = {}; // TODO 27 | } 28 | cb(err, stream._audioMeta); 29 | } 30 | function handleMicEvent(vol){ 31 | if (!finished && vol > 0) finishIt(); 32 | } 33 | } 34 | 35 | module.exports = isAudioWorking; 36 | -------------------------------------------------------------------------------- /util/onStreamLoaded/isVideoWorking.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var events = require('add-event-listener'); 4 | var hasValidTrack = require('./hasValidTrack'); 5 | var crel = require('crel'); 6 | var rtc = require('../../'); 7 | var timeoutTime = 20000; 8 | 9 | // basic premise: 10 | // - check for working video tracks 11 | // - load video into dummy video tag 12 | // - wait for onplaying event 13 | 14 | function isVideoWorking(stream, cb){ 15 | if (!hasValidTrack(stream, 'video')) return cb('dead video'); 16 | if (stream._videoMeta) return cb(null, stream._videoMeta); 17 | 18 | var rtcInst = rtc(stream._rtcOpt); 19 | var finished = false; 20 | var timeout = setTimeout(finishIt.bind(null, 'no video data'), timeoutTime); 21 | 22 | var vidEl = crel('video', { 23 | muted: true, 24 | autoplay: true, 25 | style: 'display: none;' 26 | }); 27 | 28 | var actualEl = rtcInst.attachStream(stream, vidEl); 29 | events.addEventListener(actualEl, 'canplay', finishIt.bind(null, null)); 30 | events.addEventListener(actualEl, 'playing', finishIt.bind(null, null)); 31 | 32 | function finishIt(err){ 33 | if (finished) return; 34 | finished = true; 35 | clearTimeout(timeout); 36 | 37 | if (!err) { 38 | stream._videoMeta = { 39 | height: actualEl.videoHeight, 40 | width: actualEl.videoWidth 41 | }; 42 | } 43 | 44 | vidEl.remove(); 45 | actualEl.remove(); 46 | cb(err, stream._videoMeta); 47 | } 48 | } 49 | 50 | module.exports = isVideoWorking; 51 | --------------------------------------------------------------------------------