RTCMultiConnection

Library for comfortable using WebRTC technology.

目前为 2014-04-07 提交的版本。查看 最新版本

此脚本不应直接安装,它是一个供其他脚本使用的外部库。如果您需要使用该库,请在脚本元属性加入:// @require https://update.cn-greasyfork.org/scripts/348/1119/RTCMultiConnection.js

  1. // ==UserScript==
  2. // @name RTCMultiConnection
  3. // @version 1.7
  4. // @description Library for comfortable using WebRTC technology.
  5. // ==/UserScript==
  6.  
  7. (function () {
  8. // www.RTCMultiConnection.org/docs/constructor/
  9. window.RTCMultiConnection = function (channel) {
  10. // a reference to your constructor!
  11. var connection = this;
  12.  
  13. // www.RTCMultiConnection.org/docs/channel-id/
  14. connection.channel = channel || location.href.replace(/\/|:|#|%|\.|\[|\]/g, '');
  15.  
  16. var rtcMultiSession; // a reference to backbone object i.e. RTCMultiSession!
  17.  
  18. // to allow single user to join multiple rooms;
  19. // you can change this property at runtime!
  20. connection.isAcceptNewSession = true;
  21.  
  22. // www.RTCMultiConnection.org/docs/open/
  23. connection.open = function (args) {
  24. connection.isAcceptNewSession = false;
  25. // www.RTCMultiConnection.org/docs/session-initiator/
  26. // you can always use this property to determine room owner!
  27. connection.isInitiator = true;
  28.  
  29. var dontTransmit = false;
  30.  
  31. // a channel can contain multiple rooms i.e. sessions
  32. if (args) {
  33. if (typeof args == 'string') {
  34. connection.sessionid = args;
  35. } else {
  36. if (typeof args.transmitRoomOnce != 'undefined') {
  37. connection.transmitRoomOnce = args.transmitRoomOnce;
  38. }
  39.  
  40. if (typeof args.dontTransmit != 'undefined') {
  41. dontTransmit = args.dontTransmit;
  42. }
  43.  
  44. if (typeof args.sessionid != 'undefined') {
  45. connection.sessionid = args.sessionid;
  46. }
  47. }
  48. }
  49.  
  50. // if firebase && if session initiator
  51. if (connection.socket && connection.socket.remove) {
  52. connection.socket.remove();
  53. }
  54.  
  55. if (!connection.sessionid) connection.sessionid = connection.channel;
  56. var sessionDescription = {
  57. sessionid: connection.sessionid,
  58. userid: connection.userid,
  59. session: connection.session,
  60. extra: connection.extra
  61. };
  62.  
  63. if (!connection.stats.sessions[sessionDescription.sessionid]) {
  64. connection.stats.numberOfSessions++;
  65. connection.stats.sessions[sessionDescription.sessionid] = sessionDescription;
  66. }
  67.  
  68. // verify to see if "openSignalingChannel" exists!
  69. prepareSignalingChannel(function () {
  70. // connect with signaling channel
  71. initRTCMultiSession(function () {
  72. // for session-initiator, user-media is captured as soon as "open" is invoked.
  73. captureUserMedia(function () {
  74. rtcMultiSession.initSession({
  75. sessionDescription: sessionDescription,
  76. dontTransmit: dontTransmit
  77. });
  78. });
  79. });
  80. });
  81. return sessionDescription;
  82. };
  83.  
  84. // www.RTCMultiConnection.org/docs/connect/
  85. this.connect = function (sessionid) {
  86. // a channel can contain multiple rooms i.e. sessions
  87. if (sessionid) {
  88. connection.sessionid = sessionid;
  89. }
  90.  
  91. // verify to see if "openSignalingChannel" exists!
  92. prepareSignalingChannel(function () {
  93. // connect with signaling channel
  94. initRTCMultiSession();
  95. });
  96.  
  97. return this;
  98. };
  99.  
  100. // www.RTCMultiConnection.org/docs/join/
  101. this.join = joinSession;
  102.  
  103. // www.RTCMultiConnection.org/docs/send/
  104. this.send = function (data, _channel) {
  105. // send file/data or /text
  106. if (!data)
  107. throw 'No file, data or text message to share.';
  108.  
  109. // connection.send([file1, file2, file3])
  110. // you can share multiple files, strings or data objects using "send" method!
  111. if (!!data.forEach) {
  112. // todo: this mechanism can cause failure for subsequent packets/data
  113. // on Firefox especially; and on chrome as well!
  114. // todo: need to use setTimeout instead.
  115. for (var i = 0; i < data.length; i++) {
  116. connection.send(data[i], _channel);
  117. }
  118. return;
  119. }
  120.  
  121. // File or Blob object MUST have "type" and "size" properties
  122. if (typeof data.size != 'undefined' && typeof data.type != 'undefined') {
  123. // to send multiple files concurrently!
  124. // file of any size; maximum length: 1GB
  125. FileSender.send({
  126. file: data,
  127. channel: rtcMultiSession,
  128. _channel: _channel,
  129. connection: connection
  130. });
  131. } else {
  132. // to allow longest string messages
  133. // and largest data objects
  134. // or anything of any size!
  135. // to send multiple data objects concurrently!
  136. TextSender.send({
  137. text: data,
  138. channel: rtcMultiSession,
  139. _channel: _channel,
  140. connection: connection
  141. });
  142. }
  143. };
  144.  
  145. // this method checks to verify "openSignalingChannel" method
  146. // github.com/muaz-khan/WebRTC-Experiment/blob/master/Signaling.md
  147.  
  148. function prepareSignalingChannel(callback) {
  149. if (connection.openSignalingChannel) return callback();
  150.  
  151. // make sure firebase.js is loaded before using their JavaScript API
  152. if (!window.Firebase) {
  153. return loadScript('https://www.webrtc-experiment.com/firebase.js', function () {
  154. prepareSignalingChannel(callback);
  155. });
  156. }
  157.  
  158. // Single socket is a preferred solution!
  159. var socketCallbacks = {};
  160. var firebase = new Firebase('https://' + connection.firebase + '.firebaseio.com/' + connection.channel);
  161. firebase.on('child_added', function (snap) {
  162. var data = snap.val();
  163. if (data.sender == connection.userid) return;
  164.  
  165. if (socketCallbacks[data.channel]) {
  166. socketCallbacks[data.channel](data.message);
  167. }
  168. snap.ref().remove();
  169. });
  170.  
  171. // www.RTCMultiConnection.org/docs/openSignalingChannel/
  172. connection.openSignalingChannel = function (args) {
  173. var callbackid = args.channel || connection.channel;
  174. socketCallbacks[callbackid] = args.onmessage;
  175.  
  176. if (args.onopen) setTimeout(args.onopen, 1000);
  177. return {
  178. send: function (message) {
  179. firebase.push({
  180. sender: connection.userid,
  181. channel: callbackid,
  182. message: message
  183. });
  184. },
  185. channel: channel // todo: remove this "channel" object
  186. };
  187. };
  188.  
  189. firebase.onDisconnect().remove();
  190.  
  191. callback();
  192. }
  193.  
  194. function initRTCMultiSession(onSignalingReady) {
  195. // RTCMultiSession is the backbone object;
  196. // this object MUST be initialized once!
  197. if (rtcMultiSession) return onSignalingReady();
  198.  
  199. // your everything is passed over RTCMultiSession constructor!
  200. rtcMultiSession = new RTCMultiSession(connection, onSignalingReady);
  201. }
  202.  
  203. function joinSession(session) {
  204. if (!session || !session.userid || !session.sessionid)
  205. throw 'invalid data passed over "join" method';
  206.  
  207. if (!rtcMultiSession) {
  208. // verify to see if "openSignalingChannel" exists!
  209. prepareSignalingChannel(function () {
  210. // connect with signaling channel
  211. initRTCMultiSession(function () {
  212. joinSession(session);
  213. });
  214. });
  215. return;
  216. }
  217.  
  218. connection.session = session.session;
  219.  
  220. extra = connection.extra || session.extra || {};
  221.  
  222. // todo: need to verify that if-block statement works as expected.
  223. // expectations: if it is oneway streaming; or if it is data-only connection
  224. // then, it shouldn't capture user-media on participant's side.
  225. if (session.oneway || isData(session)) {
  226. rtcMultiSession.joinSession(session, extra);
  227. } else {
  228. captureUserMedia(function () {
  229. rtcMultiSession.joinSession(session, extra);
  230. });
  231. }
  232. }
  233.  
  234. var isFirstSession = true;
  235.  
  236. // www.RTCMultiConnection.org/docs/captureUserMedia/
  237.  
  238. function captureUserMedia(callback, _session) {
  239. // capture user's media resources
  240. var session = _session || connection.session;
  241.  
  242. if (isEmpty(session)) {
  243. if (callback) callback();
  244. return;
  245. }
  246.  
  247. // you can force to skip media capturing!
  248. if (connection.dontAttachStream)
  249. return callback();
  250.  
  251. // if it is data-only connection
  252. // if it is one-way connection and current user is participant
  253. if (isData(session) || (!connection.isInitiator && session.oneway)) {
  254. // www.RTCMultiConnection.org/docs/attachStreams/
  255. connection.attachStreams = [];
  256. return callback();
  257. }
  258.  
  259. var constraints = {
  260. audio: !!session.audio,
  261. video: !!session.video
  262. };
  263.  
  264. // if custom audio device is selected
  265. if (connection._mediaSources.audio) {
  266. constraints.audio = {
  267. optional: [{
  268. sourceId: connection._mediaSources.audio
  269. }]
  270. };
  271. }
  272.  
  273. // if custom video device is selected
  274. if (connection._mediaSources.video) {
  275. constraints.video = {
  276. optional: [{
  277. sourceId: connection._mediaSources.video
  278. }]
  279. };
  280. }
  281.  
  282. var screen_constraints = {
  283. audio: false,
  284. video: {
  285. mandatory: {
  286. chromeMediaSource: 'screen'
  287. },
  288. optional: []
  289. }
  290. };
  291.  
  292. // if screen is prompted
  293. if (session.screen) {
  294. var _isFirstSession = isFirstSession;
  295.  
  296. _captureUserMedia(screen_constraints, constraints.audio || constraints.video ? function () {
  297.  
  298. if (_isFirstSession) isFirstSession = true;
  299.  
  300. _captureUserMedia(constraints, callback);
  301. } : callback);
  302. } else _captureUserMedia(constraints, callback, session.audio && !session.video);
  303.  
  304. function _captureUserMedia(forcedConstraints, forcedCallback, isRemoveVideoTracks) {
  305. var mediaConfig = {
  306. onsuccess: function (stream, returnBack, idInstance, streamid) {
  307. if (isRemoveVideoTracks && isChrome) {
  308. stream = new window.webkitMediaStream(stream.getAudioTracks());
  309. }
  310.  
  311. // var streamid = getRandomString();
  312. connection.localStreamids.push(streamid);
  313. stream.onended = function () {
  314. connection.onstreamended(streamedObject);
  315.  
  316. // if user clicks "stop" button to close screen sharing
  317. var _stream = connection.streams[streamid];
  318. if (_stream && _stream.sockets.length) {
  319. _stream.sockets.forEach(function (socket) {
  320. socket.send({
  321. streamid: _stream.streamid,
  322. userid: _stream.rtcMultiConnection.userid,
  323. extra: _stream.rtcMultiConnection.extra,
  324. stopped: true
  325. });
  326. });
  327. }
  328.  
  329. currentUserMediaRequest.mutex = false;
  330. // to make sure same stream can be captured again!
  331. if (currentUserMediaRequest.streams[idInstance]) {
  332. delete currentUserMediaRequest.streams[idInstance];
  333. }
  334. };
  335.  
  336. var mediaElement = createMediaElement(stream, session);
  337.  
  338. mediaElement.muted = true;
  339.  
  340. stream.streamid = streamid;
  341.  
  342. var streamedObject = {
  343. stream: stream,
  344. streamid: streamid,
  345. mediaElement: mediaElement,
  346. blobURL: mediaElement.mozSrcObject || mediaElement.src,
  347. type: 'local',
  348. userid: connection.userid,
  349. extra: connection.extra,
  350. session: session,
  351. isVideo: stream.getVideoTracks().length > 0,
  352. isAudio: !stream.getVideoTracks().length && stream.getAudioTracks().length > 0,
  353. isInitiator: !!connection.isInitiator
  354. };
  355.  
  356. var sObject = {
  357. stream: stream,
  358. userid: connection.userid,
  359. streamid: streamid,
  360. session: session,
  361. type: 'local',
  362. streamObject: streamedObject,
  363. mediaElement: mediaElement,
  364. rtcMultiConnection: connection
  365. };
  366.  
  367. if (isFirstSession) {
  368. connection.attachStreams.push(stream);
  369. }
  370. isFirstSession = false;
  371.  
  372. connection.streams[streamid] = connection._getStream(sObject);
  373.  
  374. if (!returnBack) {
  375. connection.onstream(streamedObject);
  376. }
  377.  
  378. if (connection.setDefaultEventsForMediaElement) {
  379. connection.setDefaultEventsForMediaElement(mediaElement, streamid);
  380. }
  381.  
  382. if (forcedCallback) forcedCallback(stream, streamedObject);
  383.  
  384. if (connection.onspeaking) {
  385. var soundMeter = new SoundMeter({
  386. context: connection._audioContext,
  387. connection: connection,
  388. event: streamedObject
  389. });
  390. soundMeter.connectToSource(stream);
  391. }
  392. },
  393. onerror: function (e, idInstance) {
  394. connection.onMediaError(toStr(e));
  395.  
  396. if (session.audio) {
  397. connection.onMediaError('Maybe microphone access is denied.');
  398. }
  399.  
  400. if (session.video) {
  401. connection.onMediaError('Maybe webcam access is denied.');
  402. }
  403.  
  404. if (session.screen) {
  405. if (isFirefox) {
  406. connection.onMediaError('Firefox has not yet released their screen capturing modules. Still work in progress! Please try chrome for now!');
  407. } else if (location.protocol !== 'https:') {
  408. connection.onMediaError('<https> is mandatory to capture screen.');
  409. } else {
  410. connection.onMediaError('Unable to detect actual issue. Maybe "deprecated" screen capturing flag is not enabled or maybe you clicked "No" button.');
  411. }
  412.  
  413. currentUserMediaRequest.mutex = false;
  414.  
  415. // to make sure same stream can be captured again!
  416. if (currentUserMediaRequest.streams[idInstance]) {
  417. delete currentUserMediaRequest.streams[idInstance];
  418. }
  419. }
  420. },
  421. mediaConstraints: connection.mediaConstraints || {}
  422. };
  423.  
  424. mediaConfig.constraints = forcedConstraints || constraints;
  425. mediaConfig.media = connection.media;
  426. getUserMedia(mediaConfig);
  427. }
  428. }
  429.  
  430. // www.RTCMultiConnection.org/docs/captureUserMedia/
  431. this.captureUserMedia = captureUserMedia;
  432.  
  433. // www.RTCMultiConnection.org/docs/leave/
  434. this.leave = function (userid) {
  435. // eject a user; or leave the session
  436. rtcMultiSession.leave(userid);
  437.  
  438. if (!userid) {
  439. var streams = connection.attachStreams;
  440. for (var i = 0; i < streams.length; i++) {
  441. stopTracks(streams[i]);
  442. }
  443. currentUserMediaRequest.streams = [];
  444. connection.attachStreams = [];
  445. }
  446.  
  447. // if firebase; remove data from firebase servers
  448. if (connection.isInitiator && !!connection.socket && !!connection.socket.remove) {
  449. connection.socket.remove();
  450. }
  451. };
  452.  
  453. // www.RTCMultiConnection.org/docs/eject/
  454. this.eject = function (userid) {
  455. if (!connection.isInitiator) throw 'Only session-initiator can eject a user.';
  456. this.leave(userid);
  457. };
  458.  
  459. // www.RTCMultiConnection.org/docs/close/
  460. this.close = function () {
  461. // close entire session
  462. connection.autoCloseEntireSession = true;
  463. rtcMultiSession.leave();
  464. };
  465.  
  466. // www.RTCMultiConnection.org/docs/renegotiate/
  467. this.renegotiate = function (stream, session) {
  468. rtcMultiSession.addStream({
  469. renegotiate: session || {
  470. oneway: true,
  471. audio: true,
  472. video: true
  473. },
  474. stream: stream
  475. });
  476. };
  477.  
  478. // www.RTCMultiConnection.org/docs/addStream/
  479. this.addStream = function (session, socket) {
  480. // www.RTCMultiConnection.org/docs/renegotiation/
  481.  
  482. // renegotiate new media stream
  483. if (session) {
  484. var isOneWayStreamFromParticipant;
  485. if (!connection.isInitiator && session.oneway) {
  486. session.oneway = false;
  487. isOneWayStreamFromParticipant = true;
  488. }
  489.  
  490. captureUserMedia(function (stream) {
  491. if (isOneWayStreamFromParticipant) {
  492. session.oneway = true;
  493. }
  494. addStream(stream);
  495. }, session);
  496. } else addStream();
  497.  
  498. function addStream(stream) {
  499. rtcMultiSession.addStream({
  500. stream: stream,
  501. renegotiate: session || connection.session,
  502. socket: socket
  503. });
  504. }
  505. };
  506.  
  507. // www.RTCMultiConnection.org/docs/removeStream/
  508. this.removeStream = function (streamid) {
  509. // detach pre-attached streams
  510. if (!this.streams[streamid]) return warn('No such stream exists. Stream-id:', streamid);
  511.  
  512. // www.RTCMultiConnection.org/docs/detachStreams/
  513. this.detachStreams.push(streamid);
  514. this.renegotiate();
  515. };
  516.  
  517. // set RTCMultiConnection defaults on constructor invocation
  518. setDefaults(this);
  519. };
  520.  
  521. function RTCMultiSession(connection, onSignalingReady) {
  522. var fileReceiver = new FileReceiver(connection);
  523. var textReceiver = new TextReceiver(connection);
  524.  
  525. function onDataChannelMessage(e) {
  526. if (!e) return;
  527.  
  528. e = JSON.parse(e);
  529.  
  530. if (e.data.type === 'text') {
  531. textReceiver.receive(e.data, e.userid, e.extra);
  532. } else if (typeof e.data.maxChunks != 'undefined') {
  533. fileReceiver.receive(e.data);
  534. } else {
  535. if (connection.autoTranslateText) {
  536. e.original = e.data;
  537. connection.Translator.TranslateText(e.data, function (translatedText) {
  538. e.data = translatedText;
  539. connection.onmessage(e);
  540. });
  541. } else connection.onmessage(e);
  542. }
  543. }
  544.  
  545. function onNewSession(session) {
  546. // todo: make sure this works as expected.
  547. // i.e. "onNewSession" should be fired only for
  548. // sessionid that is passed over "connect" method.
  549. if (connection.sessionid && session.sessionid != connection.sessionid) return;
  550.  
  551. if (connection.onNewSession) {
  552. session.join = function (forceSession) {
  553. if (!forceSession) return connection.join(session);
  554.  
  555. for (var f in forceSession) {
  556. session.session[f] = forceSession[f];
  557. }
  558.  
  559. // keeping previous state
  560. var isDontAttachStream = connection.dontAttachStream;
  561.  
  562. connection.dontAttachStream = false;
  563. connection.captureUserMedia(function () {
  564. connection.dontAttachStream = true;
  565. connection.join(session);
  566.  
  567. // returning back previous state
  568. connection.dontAttachStream = isDontAttachStream;
  569. }, forceSession);
  570. };
  571. if (!session.extra) session.extra = {};
  572.  
  573. return connection.onNewSession(session);
  574. }
  575.  
  576. connection.join(session);
  577. }
  578.  
  579. var socketObjects = {};
  580. var sockets = [];
  581.  
  582. var rtcMultiSession = this;
  583.  
  584. var participants = {};
  585.  
  586. function updateSocketForLocalStreams(socket) {
  587. for (var i = 0; i < connection.localStreamids.length; i++) {
  588. var streamid = connection.localStreamids[i];
  589. if (connection.streams[streamid]) {
  590. // using "sockets" array to keep references of all sockets using
  591. // this media stream; so we can fire "onstreamended" among all users.
  592. connection.streams[streamid].sockets.push(socket);
  593. }
  594. }
  595. }
  596.  
  597. function newPrivateSocket(_config) {
  598. var socketConfig = {
  599. channel: _config.channel,
  600. onmessage: socketResponse,
  601. onopen: function (_socket) {
  602. if (_socket) socket = _socket;
  603.  
  604. if (isofferer && !peer) {
  605. peerConfig.session = connection.session;
  606. if (!peer) peer = new PeerConnection();
  607. peer.create('offer', peerConfig);
  608. }
  609.  
  610. _config.socketIndex = socket.index = sockets.length;
  611. socketObjects[socketConfig.channel] = socket;
  612. sockets[_config.socketIndex] = socket;
  613.  
  614. updateSocketForLocalStreams(socket);
  615. }
  616. };
  617.  
  618. socketConfig.callback = function (_socket) {
  619. socket = _socket;
  620. socketConfig.onopen();
  621. };
  622.  
  623. var socket = connection.openSignalingChannel(socketConfig),
  624. isofferer = _config.isofferer,
  625. peer;
  626.  
  627. var peerConfig = {
  628. onopen: onChannelOpened,
  629. onicecandidate: function (candidate) {
  630. if (!connection.candidates) throw 'ICE candidates are mandatory.';
  631. if (!connection.candidates.host && candidate.candidate.indexOf('typ host') != -1) return;
  632. if (!connection.candidates.relay && candidate.candidate.indexOf('relay') != -1) return;
  633. if (!connection.candidates.reflexive && candidate.candidate.indexOf('srflx') != -1) return;
  634.  
  635. log(candidate.candidate);
  636.  
  637. socket && socket.send({
  638. userid: connection.userid,
  639. candidate: {
  640. sdpMLineIndex: candidate.sdpMLineIndex,
  641. candidate: JSON.stringify(candidate.candidate)
  642. }
  643. });
  644. },
  645. onmessage: onDataChannelMessage,
  646. onaddstream: function (stream, session) {
  647. session = session || _config.renegotiate || connection.session;
  648.  
  649. // if it is Firefox; then return.
  650. if (isData(session)) return;
  651.  
  652. if (_config.streaminfo) {
  653. var streaminfo = _config.streaminfo.split('----');
  654. for (var i = 0; i < streaminfo.length; i++) {
  655. stream.streamid = streaminfo[i];
  656. }
  657.  
  658. _config.streaminfo = swap(streaminfo.pop()).join('----');
  659. }
  660.  
  661. var mediaElement = createMediaElement(stream, merge({ remote: true }, session));
  662. _config.stream = stream;
  663.  
  664. if (!stream.getVideoTracks().length)
  665. mediaElement.addEventListener('play', function () {
  666. setTimeout(function () {
  667. mediaElement.muted = false;
  668. afterRemoteStreamStartedFlowing(mediaElement, session);
  669. }, 3000);
  670. }, false);
  671. else
  672. waitUntilRemoteStreamStartsFlowing(mediaElement, session);
  673.  
  674. if (connection.setDefaultEventsForMediaElement) {
  675. connection.setDefaultEventsForMediaElement(mediaElement, stream.streamid);
  676. }
  677.  
  678. // to allow this user join all existing users!
  679. if (connection.isInitiator && getLength(participants) > 1 && getLength(participants) <= connection.maxParticipantsAllowed) {
  680. if (!connection.session.oneway && !connection.session.broadcast) {
  681. defaultSocket.send({
  682. joinUsers: participants,
  683. userid: connection.userid,
  684. extra: connection.extra
  685. });
  686. }
  687. }
  688. },
  689.  
  690. onremovestream: function (event) {
  691. warn('onremovestream', event);
  692. },
  693.  
  694. onclose: function (e) {
  695. e.extra = _config.extra;
  696. e.userid = _config.userid;
  697. connection.onclose(e);
  698.  
  699. // suggested in #71 by "efaj"
  700. if (connection.channels[e.userid])
  701. delete connection.channels[e.userid];
  702. },
  703. onerror: function (e) {
  704. e.extra = _config.extra;
  705. e.userid = _config.userid;
  706. connection.onerror(e);
  707. },
  708.  
  709. oniceconnectionstatechange: function (event) {
  710. log('oniceconnectionstatechange', toStr(event));
  711. if (connection.peers[_config.userid] && connection.peers[_config.userid].oniceconnectionstatechange) {
  712. connection.peers[_config.userid].oniceconnectionstatechange(event);
  713. }
  714.  
  715. if (!connection.autoReDialOnFailure) return;
  716.  
  717. if (connection.peers[_config.userid]) {
  718. if (connection.peers[_config.userid].peer.connection.iceConnectionState != 'disconnected') {
  719. _config.redialing = false;
  720. }
  721.  
  722. if (connection.peers[_config.userid].peer.connection.iceConnectionState == 'disconnected' && !_config.redialing) {
  723. _config.redialing = true;
  724. warn('Peer connection is closed.', toStr(connection.peers[_config.userid].peer.connection), 'ReDialing..');
  725. connection.peers[_config.userid].socket.send({
  726. userid: connection.userid,
  727. extra: connection.extra || {},
  728. redial: true
  729. });
  730.  
  731. // to make sure all old "remote" streams are also removed!
  732. for (var stream in connection.streams) {
  733. stream = connection.streams[stream];
  734. if (stream.userid == _config.userid && stream.type == 'remote') {
  735. connection.onstreamended(stream.streamObject);
  736. }
  737. }
  738. }
  739. }
  740. },
  741.  
  742. onsignalingstatechange: function (event) {
  743. log('onsignalingstatechange', toStr(event));
  744. },
  745.  
  746. attachStreams: connection.attachStreams,
  747. iceServers: connection.iceServers,
  748. bandwidth: connection.bandwidth,
  749. sdpConstraints: connection.sdpConstraints,
  750. optionalArgument: connection.optionalArgument,
  751. disableDtlsSrtp: connection.disableDtlsSrtp,
  752. dataChannelDict: connection.dataChannelDict,
  753. preferSCTP: connection.preferSCTP,
  754.  
  755. onSessionDescription: function (sessionDescription, streaminfo) {
  756. sendsdp({
  757. sdp: sessionDescription,
  758. socket: socket,
  759. streaminfo: streaminfo
  760. });
  761. },
  762.  
  763. socket: socket,
  764. selfUserid: connection.userid
  765. };
  766.  
  767. function waitUntilRemoteStreamStartsFlowing(mediaElement, session, numberOfTimes) {
  768. if (!numberOfTimes) numberOfTimes = 0;
  769. numberOfTimes++;
  770.  
  771. if (!(mediaElement.readyState <= HTMLMediaElement.HAVE_CURRENT_DATA || mediaElement.paused || mediaElement.currentTime <= 0)) {
  772. afterRemoteStreamStartedFlowing(mediaElement, session);
  773. } else {
  774. if (numberOfTimes >= 100) {
  775. socket.send({
  776. userid: connection.userid,
  777. extra: connection.extra,
  778. failedToReceiveRemoteVideo: true,
  779. streamid: _config.stream.streamid
  780. });
  781. } else
  782. setTimeout(function () {
  783. log('waiting for remote video to play: ' + numberOfTimes);
  784. waitUntilRemoteStreamStartsFlowing(mediaElement, session, numberOfTimes);
  785. }, 200);
  786. }
  787. }
  788.  
  789. function initFakeChannel() {
  790. if (!connection.fakeDataChannels || connection.channels[_config.userid]) return;
  791.  
  792. // for non-data connections; allow fake data sender!
  793. if (!connection.session.data) {
  794. var fakeChannel = {
  795. send: function (data) {
  796. socket.send({
  797. fakeData: data
  798. });
  799. },
  800. readyState: 'open'
  801. };
  802. // connection.channels['user-id'].send(data);
  803. connection.channels[_config.userid] = {
  804. channel: fakeChannel,
  805. send: function (data) {
  806. this.channel.send(data);
  807. }
  808. };
  809. peerConfig.onopen(fakeChannel);
  810. }
  811. }
  812.  
  813. function afterRemoteStreamStartedFlowing(mediaElement, session) {
  814. var stream = _config.stream;
  815.  
  816. stream.onended = function () {
  817. connection.onstreamended(streamedObject);
  818. };
  819.  
  820. var streamedObject = {
  821. mediaElement: mediaElement,
  822.  
  823. stream: stream,
  824. streamid: stream.streamid,
  825. session: session || connection.session,
  826.  
  827. blobURL: mediaElement.mozSrcObject || mediaElement.src,
  828. type: 'remote',
  829.  
  830. extra: _config.extra,
  831. userid: _config.userid,
  832.  
  833. isVideo: stream.getVideoTracks().length > 0,
  834. isAudio: !stream.getVideoTracks().length && stream.getAudioTracks().length > 0,
  835. isInitiator: !!_config.isInitiator
  836. };
  837.  
  838. // connection.streams['stream-id'].mute({audio:true})
  839. connection.streams[stream.streamid] = connection._getStream({
  840. stream: stream,
  841. userid: _config.userid,
  842. streamid: stream.streamid,
  843. socket: socket,
  844. type: 'remote',
  845. streamObject: streamedObject,
  846. mediaElement: mediaElement,
  847. rtcMultiConnection: connection,
  848. session: session || connection.session
  849. });
  850.  
  851. connection.onstream(streamedObject);
  852.  
  853. onSessionOpened();
  854.  
  855. if (connection.onspeaking) {
  856. var soundMeter = new SoundMeter({
  857. context: connection._audioContext,
  858. connection: connection,
  859. event: streamedObject
  860. });
  861. soundMeter.connectToSource(stream);
  862. }
  863. }
  864.  
  865. function onChannelOpened(channel) {
  866. _config.channel = channel;
  867.  
  868. // connection.channels['user-id'].send(data);
  869. connection.channels[_config.userid] = {
  870. channel: _config.channel,
  871. send: function (data) {
  872. connection.send(data, this.channel);
  873. }
  874. };
  875.  
  876. connection.onopen({
  877. extra: _config.extra,
  878. userid: _config.userid
  879. });
  880.  
  881. // fetch files from file-queue
  882. for (var q in connection.fileQueue) {
  883. connection.send(connection.fileQueue[q], channel);
  884. }
  885.  
  886. if (isData(connection.session)) onSessionOpened();
  887. }
  888.  
  889. function updateSocket() {
  890. // todo: need to check following {if-block} MUST not affect "redial" process
  891. if (socket.userid == _config.userid)
  892. return;
  893.  
  894. socket.userid = _config.userid;
  895. sockets[_config.socketIndex] = socket;
  896.  
  897. connection.stats.numberOfConnectedUsers++;
  898. // connection.peers['user-id'].addStream({audio:true})
  899. connection.peers[_config.userid] = {
  900. socket: socket,
  901. peer: peer,
  902. userid: _config.userid,
  903. extra: _config.extra,
  904. addStream: function (session00) {
  905. // connection.peers['user-id'].addStream({audio: true, video: true);
  906.  
  907. connection.addStream(session00, this.socket);
  908. },
  909. removeStream: function (streamid) {
  910. if (!connection.streams[streamid])
  911. return warn('No such stream exists. Stream-id:', streamid);
  912.  
  913. this.peer.connection.removeStream(connection.streams[streamid].stream);
  914. this.renegotiate();
  915. },
  916. renegotiate: function (stream, session) {
  917. // connection.peers['user-id'].renegotiate();
  918.  
  919. connection.renegotiate(stream, session);
  920. },
  921. changeBandwidth: function (bandwidth) {
  922. // connection.peers['user-id'].changeBandwidth();
  923.  
  924. if (!bandwidth) throw 'You MUST pass bandwidth object.';
  925. if (typeof bandwidth == 'string') throw 'Pass object for bandwidth instead of string; e.g. {audio:10, video:20}';
  926.  
  927. // set bandwidth for self
  928. this.peer.bandwidth = bandwidth;
  929.  
  930. // ask remote user to synchronize bandwidth
  931. this.socket.send({
  932. userid: connection.userid,
  933. extra: connection.extra || {},
  934. changeBandwidth: true,
  935. bandwidth: bandwidth
  936. });
  937. },
  938. sendCustomMessage: function (message) {
  939. // connection.peers['user-id'].sendCustomMessage();
  940.  
  941. this.socket.send({
  942. userid: connection.userid,
  943. extra: connection.extra || {},
  944. customMessage: true,
  945. message: message
  946. });
  947. },
  948. onCustomMessage: function (message) {
  949. log('Received "private" message from', this.userid,
  950. typeof message == 'string' ? message : toStr(message));
  951. },
  952. drop: function (dontSendMessage) {
  953. // connection.peers['user-id'].drop();
  954.  
  955. for (var stream in connection.streams) {
  956. if (connection._skip.indexOf(stream) == -1) {
  957. stream = connection.streams[stream];
  958.  
  959. if (stream.userid == connection.userid && stream.type == 'local') {
  960. this.peer.connection.removeStream(stream.stream);
  961. connection.onstreamended(stream.streamObject);
  962. }
  963.  
  964. if (stream.type == 'remote' && stream.userid == this.userid) {
  965. connection.onstreamended(stream.streamObject);
  966. }
  967. }
  968. }
  969.  
  970. !dontSendMessage && this.socket.send({
  971. userid: connection.userid,
  972. extra: connection.extra || {},
  973. drop: true
  974. });
  975. },
  976. hold: function (holdMLine) {
  977. // connection.peers['user-id'].hold();
  978.  
  979. this.socket.send({
  980. userid: connection.userid,
  981. extra: connection.extra || {},
  982. hold: true,
  983. holdMLine: holdMLine || 'both'
  984. });
  985.  
  986. this.peer.hold = true;
  987. this.fireHoldUnHoldEvents({
  988. kind: holdMLine,
  989. isHold: true,
  990. userid: connection.userid,
  991. remoteUser: this.userid
  992. });
  993. },
  994. unhold: function (holdMLine) {
  995. // connection.peers['user-id'].unhold();
  996.  
  997. this.socket.send({
  998. userid: connection.userid,
  999. extra: connection.extra || {},
  1000. unhold: true,
  1001. holdMLine: holdMLine || 'both'
  1002. });
  1003.  
  1004. this.peer.hold = false;
  1005. this.fireHoldUnHoldEvents({
  1006. kind: holdMLine,
  1007. isHold: false,
  1008. userid: connection.userid,
  1009. remoteUser: this.userid
  1010. });
  1011. },
  1012. fireHoldUnHoldEvents: function (e) {
  1013. // this method is for inner usages only!
  1014.  
  1015. var isHold = e.isHold;
  1016. var kind = e.kind;
  1017. var userid = e.remoteUser || e.userid;
  1018.  
  1019. // hold means inactive a specific media line!
  1020. // a media line can contain multiple synced sources (ssrc)
  1021. // i.e. a media line can reference multiple tracks!
  1022. // that's why hold will affect all relevant tracks in a specific media line!
  1023. for (var stream in connection.streams) {
  1024. if (connection._skip.indexOf(stream) == -1) {
  1025. stream = connection.streams[stream];
  1026.  
  1027. if (stream.userid == userid) {
  1028. // www.RTCMultiConnection.org/docs/onhold/
  1029. if (isHold)
  1030. connection.onhold(merge({
  1031. kind: kind
  1032. }, stream.streamObject));
  1033.  
  1034. // www.RTCMultiConnection.org/docs/onunhold/
  1035. if (!isHold)
  1036. connection.onunhold(merge({
  1037. kind: kind
  1038. }, stream.streamObject));
  1039. }
  1040. }
  1041. }
  1042. },
  1043. redial: function () {
  1044. // connection.peers['user-id'].redial();
  1045.  
  1046. // 1st of all; remove all relevant remote media streams
  1047. for (var stream in connection.streams) {
  1048. if (connection._skip.indexOf(stream) == -1) {
  1049. stream = connection.streams[stream];
  1050.  
  1051. if (stream.userid == this.userid && stream.type == 'remote') {
  1052. connection.onstreamended(stream.streamObject);
  1053. }
  1054. }
  1055. }
  1056.  
  1057. log('ReDialing...');
  1058.  
  1059. socket.send({
  1060. userid: connection.userid,
  1061. extra: connection.extra,
  1062. recreatePeer: true
  1063. });
  1064.  
  1065. peer = new PeerConnection();
  1066. peer.create('offer', peerConfig);
  1067. },
  1068. sharePartOfScreen: function (args) {
  1069. // www.RTCMultiConnection.org/docs/onpartofscreen/
  1070.  
  1071. var element = args.element;
  1072. var that = this;
  1073.  
  1074. if (!window.html2canvas) {
  1075. return loadScript('https://www.webrtc-experiment.com/screenshot.js', function () {
  1076. that.sharePartOfScreen(args);
  1077. });
  1078. }
  1079.  
  1080. if (typeof element == 'string') {
  1081. element = document.querySelector(element);
  1082. if (!element) element = document.getElementById(element);
  1083. }
  1084. if (!element) throw 'HTML Element is inaccessible!';
  1085.  
  1086. function partOfScreenCapturer() {
  1087. // if stopped
  1088. if (that.stopPartOfScreenSharing) {
  1089. that.stopPartOfScreenSharing = false;
  1090.  
  1091. if (connection.onpartofscreenstopped) {
  1092. connection.onpartofscreenstopped();
  1093. }
  1094. return;
  1095. }
  1096.  
  1097. // if paused
  1098. if (that.pausePartOfScreenSharing) {
  1099. if (connection.onpartofscreenpaused) {
  1100. connection.onpartofscreenpaused();
  1101. }
  1102.  
  1103. return setTimeout(partOfScreenCapturer, args.interval || 200);
  1104. }
  1105.  
  1106. // html2canvas.js is used to take screenshots
  1107. html2canvas(element, {
  1108. onrendered: function (canvas) {
  1109. var screenshot = canvas.toDataURL();
  1110.  
  1111. if (!connection.channels[that.userid]) {
  1112. throw 'No such data channel exists.';
  1113. }
  1114.  
  1115. connection.channels[that.userid].send({
  1116. userid: connection.userid,
  1117. extra: connection.extra,
  1118. screenshot: screenshot,
  1119. isPartOfScreen: true
  1120. });
  1121.  
  1122. // "once" can be used to share single screenshot
  1123. !args.once && setTimeout(partOfScreenCapturer, args.interval || 200);
  1124. }
  1125. });
  1126. }
  1127.  
  1128. partOfScreenCapturer();
  1129. }
  1130. };
  1131. }
  1132.  
  1133. function onSessionOpened() {
  1134. // admin/guest is one-to-one relationship
  1135. if (connection.userType && connection.direction !== 'many-to-many') return;
  1136.  
  1137. // original conferencing infrastructure!
  1138. if (connection.isInitiator && getLength(participants) > 1 && getLength(participants) <= connection.maxParticipantsAllowed) {
  1139. if (!connection.session.oneway && !connection.session.broadcast) {
  1140. defaultSocket.send({
  1141. sessionid: connection.sessionid,
  1142. newParticipant: _config.userid || socket.channel,
  1143. userid: connection.userid,
  1144. extra: connection.extra,
  1145. userData: {
  1146. userid: _config.userid,
  1147. extra: _config.extra
  1148. }
  1149. });
  1150. } else if (connection.interconnect) {
  1151. socket.send({
  1152. joinUsers: participants,
  1153. userid: connection.userid,
  1154. extra: connection.extra
  1155. });
  1156. }
  1157. }
  1158.  
  1159. if (connection.isInitiator) {
  1160. // this code snippet is added to make sure that "previously-renegotiated" streams are also
  1161. // renegotiated to this new user
  1162. // todo: currently renegotiating only one stream; need renegotiate all.
  1163. if (connection.renegotiatedSessions[0]) {
  1164. connection.peers[_config.userid].renegotiate(connection.renegotiatedSessions[0].stream, connection.renegotiatedSessions[0].session);
  1165. }
  1166. }
  1167. }
  1168.  
  1169. function socketResponse(response) {
  1170. if (response.userid == connection.userid)
  1171. return;
  1172.  
  1173. if (response.sdp) {
  1174. _config.userid = response.userid;
  1175. _config.extra = response.extra || {};
  1176. _config.renegotiate = response.renegotiate;
  1177. _config.streaminfo = response.streaminfo;
  1178. _config.isInitiator = response.isInitiator;
  1179.  
  1180. var sdp = JSON.parse(response.sdp);
  1181.  
  1182. if (sdp.type == 'offer') {
  1183. // to synchronize SCTP or RTP
  1184. peerConfig.preferSCTP = !!response.preferSCTP;
  1185. connection.fakeDataChannels = !!response.fakeDataChannels;
  1186. }
  1187.  
  1188. // initializing fake channel
  1189. initFakeChannel();
  1190.  
  1191. sdpInvoker(sdp, response.labels);
  1192. }
  1193.  
  1194. if (response.candidate) {
  1195. peer && peer.addIceCandidate({
  1196. sdpMLineIndex: response.candidate.sdpMLineIndex,
  1197. candidate: JSON.parse(response.candidate.candidate)
  1198. });
  1199. }
  1200.  
  1201. if (response.mute || response.unmute) {
  1202. if (response.promptMuteUnmute) {
  1203. if (connection.streams[response.streamid]) {
  1204. if (response.mute && !connection.streams[response.streamid].muted) {
  1205. connection.streams[response.streamid].mute(response.session);
  1206. }
  1207. if (response.unmute && connection.streams[response.streamid].muted) {
  1208. connection.streams[response.streamid].unmute(response.session);
  1209. }
  1210. }
  1211. } else {
  1212. var streamObject = {};
  1213. if (connection.streams[response.streamid]) {
  1214. streamObject = connection.streams[response.streamid].streamObject;
  1215. }
  1216.  
  1217. var session = response.session;
  1218. var fakeObject = merge({}, streamObject);
  1219. fakeObject.session = session;
  1220. fakeObject.isAudio = session.audio && !session.video;
  1221. fakeObject.isVideo = (!session.audio && session.video) || (session.audio && session.video);
  1222.  
  1223. if (response.mute) connection.onmute(fakeObject || response);
  1224. if (response.unmute) connection.onunmute(fakeObject || response);
  1225. }
  1226. }
  1227.  
  1228. if (response.isVolumeChanged) {
  1229. log('Volume of stream: ' + response.streamid + ' has changed to: ' + response.volume);
  1230. if (connection.streams[response.streamid]) {
  1231. var mediaElement = connection.streams[response.streamid].mediaElement;
  1232. if (mediaElement) mediaElement.volume = response.volume;
  1233. }
  1234. }
  1235.  
  1236. // to stop local stream
  1237. if (response.stopped) {
  1238. if (connection.streams[response.streamid]) {
  1239. connection.onstreamended(connection.streams[response.streamid].streamObject);
  1240. }
  1241. }
  1242.  
  1243. // to stop remote stream
  1244. if (response.promptStreamStop /* && !connection.isInitiator */) {
  1245. // var forceToStopRemoteStream = true;
  1246. // connection.streams['remote-stream-id'].stop( forceToStopRemoteStream );
  1247. warn('Remote stream has been manually stopped!');
  1248. if (connection.streams[response.streamid]) {
  1249. connection.streams[response.streamid].stop();
  1250. }
  1251. }
  1252.  
  1253. if (response.left) {
  1254. // firefox is unable to stop remote streams
  1255. // firefox doesn't auto stop streams when peer.close() is called.
  1256. if (isFirefox) {
  1257. var userLeft = response.userid;
  1258. for (var stream in connection.streams) {
  1259. stream = connection.streams[stream];
  1260. if (stream.userid == userLeft) {
  1261. stopTracks(stream);
  1262. stream.stream.onended(stream.streamObject);
  1263. }
  1264. }
  1265. }
  1266.  
  1267. if (peer && peer.connection) {
  1268. peer.connection.close();
  1269. peer.connection = null;
  1270. }
  1271.  
  1272. if (response.closeEntireSession) {
  1273. connection.close();
  1274. connection.refresh();
  1275. } else if (socket && response.ejected) {
  1276. // if user is ejected; his stream MUST be removed
  1277. // from all other users' side
  1278. socket.send({
  1279. left: true,
  1280. extra: connection.extra,
  1281. userid: connection.userid
  1282. });
  1283.  
  1284. if (sockets[_config.socketIndex])
  1285. delete sockets[_config.socketIndex];
  1286. if (socketObjects[socket.channel])
  1287. delete socketObjects[socket.channel];
  1288.  
  1289. socket = null;
  1290. }
  1291.  
  1292. connection.remove(response.userid);
  1293.  
  1294. if (participants[response.userid]) delete participants[response.userid];
  1295.  
  1296. connection.onleave({
  1297. userid: response.userid,
  1298. extra: response.extra,
  1299. entireSessionClosed: !!response.closeEntireSession
  1300. });
  1301.  
  1302. if (connection.userType) connection.busy = false;
  1303. }
  1304.  
  1305. // keeping session active even if initiator leaves
  1306. if (response.playRoleOfBroadcaster) {
  1307. if (response.extra) {
  1308. connection.extra = merge(connection.extra, response.extra);
  1309. }
  1310. setTimeout(connection.playRoleOfInitiator, 2000);
  1311. }
  1312.  
  1313. if (response.isCreateDataChannel) {
  1314. if (isFirefox) {
  1315. peer.createDataChannel();
  1316. }
  1317. }
  1318.  
  1319. if (response.changeBandwidth) {
  1320. if (!connection.peers[response.userid]) throw 'No such peer exists.';
  1321.  
  1322. // synchronize bandwidth
  1323. connection.peers[response.userid].peer.bandwidth = response.bandwidth;
  1324.  
  1325. // renegotiate to apply bandwidth
  1326. connection.peers[response.userid].renegotiate();
  1327. }
  1328.  
  1329. if (response.customMessage) {
  1330. if (!connection.peers[response.userid]) throw 'No such peer exists.';
  1331. connection.peers[response.userid].onCustomMessage(response.message);
  1332. }
  1333.  
  1334. if (response.drop) {
  1335. if (!connection.peers[response.userid]) throw 'No such peer exists.';
  1336. connection.peers[response.userid].drop(true);
  1337. connection.peers[response.userid].renegotiate();
  1338.  
  1339. connection.ondrop(response.userid);
  1340. }
  1341.  
  1342. if (response.hold) {
  1343. if (!connection.peers[response.userid]) throw 'No such peer exists.';
  1344. connection.peers[response.userid].peer.hold = true;
  1345. connection.peers[response.userid].peer.holdMLine = response.holdMLine;
  1346. connection.peers[response.userid].renegotiate();
  1347.  
  1348. connection.peers[response.userid].fireHoldUnHoldEvents({
  1349. kind: response.holdMLine,
  1350. isHold: true,
  1351. userid: response.userid
  1352. });
  1353. }
  1354.  
  1355. if (response.unhold) {
  1356. if (!connection.peers[response.userid]) throw 'No such peer exists.';
  1357. connection.peers[response.userid].peer.hold = false;
  1358. connection.peers[response.userid].peer.holdMLine = response.holdMLine;
  1359. connection.peers[response.userid].renegotiate();
  1360.  
  1361. connection.peers[response.userid].fireHoldUnHoldEvents({
  1362. kind: response.holdMLine,
  1363. isHold: false,
  1364. userid: response.userid
  1365. });
  1366. }
  1367.  
  1368. // fake data channels!
  1369. if (response.fakeData) {
  1370. peerConfig.onmessage(response.fakeData);
  1371. }
  1372.  
  1373. // sometimes we don't need to renegotiate e.g. when peers are disconnected
  1374. // or if it is firefox
  1375. if (response.recreatePeer) {
  1376. peer = new PeerConnection();
  1377. }
  1378.  
  1379. // remote video failed either out of ICE gathering process or ICE connectivity check-up
  1380. // or IceAgent was unable to locate valid candidates/ports.
  1381. if (response.failedToReceiveRemoteVideo) {
  1382. log('Remote peer hasn\'t received stream: ' + response.streamid + '. Renegotiating...');
  1383. if (connection.peers[response.userid]) {
  1384. connection.peers[response.userid].renegotiate();
  1385. }
  1386. }
  1387.  
  1388. if (response.joinUsers) {
  1389. for (var user in response.joinUsers) {
  1390. if (!participants[response.joinUsers[user]]) {
  1391. onNewParticipant({
  1392. sessionid: connection.sessionid,
  1393. newParticipant: response.joinUsers[user],
  1394. userid: connection.userid,
  1395. extra: connection.extra,
  1396. interconnect: true
  1397. });
  1398. }
  1399. }
  1400. }
  1401.  
  1402. if (response.redial) {
  1403. if (connection.peers[response.userid]) {
  1404. if (connection.peers[response.userid].peer.connection.iceConnectionState != 'disconnected') {
  1405. _config.redialing = false;
  1406. }
  1407.  
  1408. if (connection.peers[response.userid].peer.connection.iceConnectionState == 'disconnected' && !_config.redialing) {
  1409. _config.redialing = true;
  1410.  
  1411. warn('Peer connection is closed.', toStr(connection.peers[response.userid].peer.connection), 'ReDialing..');
  1412. connection.peers[response.userid].redial();
  1413. }
  1414. }
  1415. }
  1416. }
  1417.  
  1418. connection.playRoleOfInitiator = function () {
  1419. connection.dontAttachStream = true;
  1420. connection.open();
  1421. sockets = swap(sockets);
  1422. connection.dontAttachStream = false;
  1423. };
  1424.  
  1425. function sdpInvoker(sdp, labels) {
  1426. log(sdp.type, sdp.sdp);
  1427.  
  1428. if (sdp.type == 'answer') {
  1429. peer.setRemoteDescription(sdp);
  1430. updateSocket();
  1431. return;
  1432. }
  1433. if (!_config.renegotiate && sdp.type == 'offer') {
  1434. peerConfig.offerDescription = sdp;
  1435. peerConfig.session = connection.session;
  1436. if (!peer) peer = new PeerConnection();
  1437. peer.create('answer', peerConfig);
  1438.  
  1439. updateSocket();
  1440. return;
  1441. }
  1442.  
  1443. var session = _config.renegotiate;
  1444. // detach streams
  1445. detachMediaStream(labels, peer.connection);
  1446.  
  1447. if (session.oneway || isData(session)) {
  1448. createAnswer();
  1449. } else {
  1450. if (_config.capturing)
  1451. return;
  1452.  
  1453. _config.capturing = true;
  1454.  
  1455. connection.captureUserMedia(function (stream) {
  1456. _config.capturing = false;
  1457.  
  1458. if (isChrome || (isFirefox && !peer.connection.getLocalStreams().length)) {
  1459. peer.connection.addStream(stream);
  1460. }
  1461. createAnswer();
  1462. }, _config.renegotiate);
  1463. }
  1464.  
  1465. delete _config.renegotiate;
  1466.  
  1467. function createAnswer() {
  1468. if (isFirefox) {
  1469. if (connection.peers[_config.userid]) {
  1470. connection.peers[_config.userid].redial();
  1471. }
  1472. return;
  1473. }
  1474.  
  1475. peer.recreateAnswer(sdp, session, function (_sdp, streaminfo) {
  1476. sendsdp({
  1477. sdp: _sdp,
  1478. socket: socket,
  1479. streaminfo: streaminfo
  1480. });
  1481. });
  1482. }
  1483. }
  1484. }
  1485.  
  1486. function detachMediaStream(labels, peer) {
  1487. if (!labels) return;
  1488. for (var i = 0; i < labels.length; i++) {
  1489. var label = labels[i];
  1490. if (connection.streams[label]) {
  1491. peer.removeStream(connection.streams[label].stream);
  1492. }
  1493. }
  1494. }
  1495.  
  1496. function sendsdp(e) {
  1497. e.socket.send({
  1498. userid: connection.userid,
  1499. sdp: JSON.stringify(e.sdp),
  1500. extra: connection.extra,
  1501. renegotiate: !!e.renegotiate ? e.renegotiate : false,
  1502. streaminfo: e.streaminfo || '',
  1503. labels: e.labels || [],
  1504. preferSCTP: !!connection.preferSCTP,
  1505. fakeDataChannels: !!connection.fakeDataChannels,
  1506. isInitiator: !!connection.isInitiator
  1507. });
  1508. }
  1509.  
  1510. // sharing new user with existing participants
  1511.  
  1512. function onNewParticipant(response) {
  1513. if (response.interconnect && !connection.interconnect) return;
  1514.  
  1515. // todo: make sure this works as expected.
  1516. // if(connection.sessionid && response.sessionid != connection.sessionid) return;
  1517.  
  1518. var channel = response.newParticipant;
  1519.  
  1520. if (!channel || !!participants[channel] || channel == connection.userid)
  1521. return;
  1522.  
  1523. participants[channel] = channel;
  1524.  
  1525. var new_channel = connection.token();
  1526. newPrivateSocket({
  1527. channel: new_channel,
  1528. extra: response.userData ? response.userData.extra : response.extra,
  1529. userid: response.userData ? response.userData.userid : response.userid
  1530. });
  1531.  
  1532. defaultSocket.send({
  1533. participant: true,
  1534. userid: connection.userid,
  1535. targetUser: channel,
  1536. channel: new_channel,
  1537. extra: connection.extra
  1538. });
  1539. }
  1540.  
  1541. // if a user leaves
  1542.  
  1543. function clearSession(channel) {
  1544. connection.stats.numberOfConnectedUsers--;
  1545.  
  1546. var alert = {
  1547. left: true,
  1548. extra: connection.extra,
  1549. userid: connection.userid,
  1550. sessionid: connection.sessionid
  1551. };
  1552.  
  1553. if (connection.isInitiator) {
  1554. if (connection.autoCloseEntireSession) {
  1555. alert.closeEntireSession = true;
  1556. } else if (sockets[0]) {
  1557. sockets[0].send({
  1558. playRoleOfBroadcaster: true,
  1559. userid: connection.userid
  1560. });
  1561. }
  1562. }
  1563.  
  1564. if (!channel) {
  1565. var length = sockets.length;
  1566. for (var i = 0; i < length; i++) {
  1567. socket = sockets[i];
  1568. if (socket) {
  1569. socket.send(alert);
  1570.  
  1571. if (socketObjects[socket.channel])
  1572. delete socketObjects[socket.channel];
  1573.  
  1574. delete sockets[i];
  1575. }
  1576. }
  1577. }
  1578.  
  1579. // eject a specific user!
  1580. if (channel) {
  1581. socket = socketObjects[channel];
  1582. if (socket) {
  1583. alert.ejected = true;
  1584. socket.send(alert);
  1585.  
  1586. if (sockets[socket.index])
  1587. delete sockets[socket.index];
  1588.  
  1589. delete socketObjects[channel];
  1590. }
  1591. }
  1592.  
  1593. sockets = swap(sockets);
  1594. }
  1595.  
  1596. // www.RTCMultiConnection.org/docs/remove/
  1597. connection.remove = function (userid) {
  1598. if (rtcMultiSession.requestsFrom && rtcMultiSession.requestsFrom[userid]) delete rtcMultiSession.requestsFrom[userid];
  1599.  
  1600. if (connection.peers[userid]) {
  1601. if (connection.peers[userid].peer && connection.peers[userid].peer.connection) {
  1602. connection.peers[userid].peer.connection.close();
  1603. connection.peers[userid].peer.connection = null;
  1604. }
  1605. delete connection.peers[userid];
  1606. }
  1607. if (participants[userid]) {
  1608. delete participants[userid];
  1609. }
  1610.  
  1611. for (var stream in connection.streams) {
  1612. stream = connection.streams[stream];
  1613. if (stream.userid == userid) {
  1614. connection.onstreamended(stream.streamObject);
  1615. if (stream.stop) stream.stop();
  1616. delete connection.streams[stream];
  1617. }
  1618. }
  1619.  
  1620. if (socketObjects[userid]) {
  1621. delete socketObjects[userid];
  1622. }
  1623. };
  1624.  
  1625. // www.RTCMultiConnection.org/docs/refresh/
  1626. connection.refresh = function () {
  1627. participants = [];
  1628. connection.isAcceptNewSession = true;
  1629. connection.busy = false;
  1630.  
  1631. // to stop/remove self streams
  1632. for (var i = 0; i < connection.attachStreams.length; i++) {
  1633. stopTracks(connection.attachStreams[i]);
  1634. }
  1635. connection.attachStreams = [];
  1636.  
  1637. // to allow capturing of identical streams
  1638. currentUserMediaRequest = {
  1639. streams: [],
  1640. mutex: false,
  1641. queueRequests: []
  1642. };
  1643. rtcMultiSession.isOwnerLeaving = true;
  1644. connection.isInitiator = false;
  1645. };
  1646.  
  1647. // www.RTCMultiConnection.org/docs/reject/
  1648. connection.reject = function (userid) {
  1649. if (typeof userid != 'string') userid = userid.userid;
  1650. defaultSocket.send({
  1651. rejectedRequestOf: userid,
  1652. userid: connection.userid,
  1653. extra: connection.extra || {}
  1654. });
  1655. };
  1656.  
  1657. window.addEventListener('beforeunload', function () {
  1658. clearSession();
  1659. }, false);
  1660.  
  1661. window.addEventListener('keyup', function (e) {
  1662. if (e.keyCode == 116)
  1663. clearSession();
  1664. }, false);
  1665.  
  1666. function initDefaultSocket() {
  1667. defaultSocket = connection.openSignalingChannel({
  1668. onmessage: function (response) {
  1669. if (response.userid == connection.userid) return;
  1670.  
  1671. if (response.sessionid && response.userid) {
  1672. if (!connection.stats.sessions[response.sessionid]) {
  1673. connection.stats.numberOfSessions++;
  1674. connection.stats.sessions[response.sessionid] = response;
  1675. }
  1676. }
  1677.  
  1678. if (connection.isAcceptNewSession && response.sessionid && response.userid) {
  1679. connection.session = response.session;
  1680. onNewSession(response);
  1681. }
  1682.  
  1683. if (response.newParticipant && !connection.isAcceptNewSession && rtcMultiSession.broadcasterid === response.userid) {
  1684. onNewParticipant(response);
  1685. }
  1686.  
  1687. if (getLength(participants) < connection.maxParticipantsAllowed && response.userid && response.targetUser == connection.userid && response.participant && !participants[response.userid]) {
  1688. acceptRequest(response);
  1689. }
  1690.  
  1691. if (response.userType && response.userType != connection.userType) {
  1692. if (!connection.busy) {
  1693. if (response.userType == 'admin') {
  1694. if (connection.onAdmin) connection.onAdmin(response);
  1695. else connection.accept(response.userid);
  1696. }
  1697. if (response.userType == 'guest') {
  1698. if (connection.onGuest) connection.onGuest(response);
  1699. else connection.accept(response.userid);
  1700. }
  1701. } else {
  1702. if (response.userType != connection.userType) {
  1703. connection.reject(response.userid);
  1704. }
  1705. }
  1706. }
  1707.  
  1708. if (response.acceptedRequestOf == connection.userid) {
  1709. if (connection.onstats) connection.onstats('accepted', response);
  1710. }
  1711.  
  1712. if (response.rejectedRequestOf == connection.userid) {
  1713. if (connection.onstats) connection.onstats(connection.userType ? 'busy' : 'rejected', response);
  1714. sendRequest();
  1715. }
  1716.  
  1717. if (response.customMessage) {
  1718. if (response.message.drop) {
  1719. connection.ondrop(response.userid);
  1720.  
  1721. connection.attachStreams = [];
  1722. // "drop" should detach all local streams
  1723. for (var stream in connection.streams) {
  1724. if (connection._skip.indexOf(stream) == -1) {
  1725. stream = connection.streams[stream];
  1726. if (stream.type == 'local') {
  1727. connection.detachStreams.push(stream.streamid);
  1728. connection.onstreamended(stream.streamObject);
  1729. } else connection.onstreamended(stream.streamObject);
  1730. }
  1731. }
  1732.  
  1733. if (response.message.renegotiate) {
  1734. // renegotiate; so "peer.removeStream" happens.
  1735. connection.addStream();
  1736. }
  1737. } else if (connection.onCustomMessage) {
  1738. connection.onCustomMessage(response.message);
  1739. }
  1740. }
  1741.  
  1742. if (response.joinUsers) {
  1743. for (var user in response.joinUsers) {
  1744. if (!participants[response.joinUsers[user]]) {
  1745. onNewParticipant({
  1746. sessionid: connection.sessionid,
  1747. newParticipant: response.joinUsers[user],
  1748. userid: connection.userid,
  1749. extra: connection.extra,
  1750. interconnect: true
  1751. });
  1752. }
  1753. }
  1754. }
  1755. },
  1756. callback: function (socket) {
  1757. if (socket) defaultSocket = socket;
  1758. if (connection.userType) sendRequest(socket || defaultSocket);
  1759. if (onSignalingReady) onSignalingReady();
  1760. },
  1761. onopen: function (socket) {
  1762. if (socket) defaultSocket = socket;
  1763. if (connection.userType) sendRequest(socket || defaultSocket);
  1764. if (onSignalingReady) onSignalingReady();
  1765. }
  1766. });
  1767. }
  1768.  
  1769. var defaultSocket;
  1770.  
  1771. initDefaultSocket();
  1772.  
  1773. function sendRequest(socket) {
  1774. if (!socket) {
  1775. return setTimeout(function () {
  1776. sendRequest(defaultSocket);
  1777. }, 1000);
  1778. }
  1779.  
  1780. socket.send({
  1781. userType: connection.userType,
  1782. userid: connection.userid,
  1783. extra: connection.extra || {}
  1784. });
  1785. }
  1786.  
  1787. function setDirections() {
  1788. var userMaxParticipantsAllowed = 0;
  1789.  
  1790. // if user has set a custom max participant setting, remember it
  1791. if (connection.maxParticipantsAllowed != 256) {
  1792. userMaxParticipantsAllowed = connection.maxParticipantsAllowed;
  1793. }
  1794.  
  1795. if (connection.direction == 'one-way') connection.session.oneway = true;
  1796. if (connection.direction == 'one-to-one') connection.maxParticipantsAllowed = 1;
  1797. if (connection.direction == 'one-to-many') connection.session.broadcast = true;
  1798. if (connection.direction == 'many-to-many') {
  1799. if (!connection.maxParticipantsAllowed || connection.maxParticipantsAllowed == 1) {
  1800. connection.maxParticipantsAllowed = 256;
  1801. }
  1802. }
  1803.  
  1804. // if user has set a custom max participant setting, set it back
  1805. if (userMaxParticipantsAllowed && connection.maxParticipantsAllowed != 1) {
  1806. connection.maxParticipantsAllowed = userMaxParticipantsAllowed;
  1807. }
  1808. }
  1809.  
  1810. // open new session
  1811. this.initSession = function (args) {
  1812. rtcMultiSession.isOwnerLeaving = false;
  1813.  
  1814. setDirections();
  1815. participants = {};
  1816.  
  1817. rtcMultiSession.isOwnerLeaving = false;
  1818.  
  1819. if (typeof args.transmitRoomOnce != 'undefined') {
  1820. connection.transmitRoomOnce = args.transmitRoomOnce;
  1821. }
  1822.  
  1823. function transmit() {
  1824. if (getLength(participants) < connection.maxParticipantsAllowed && !rtcMultiSession.isOwnerLeaving) {
  1825. defaultSocket && defaultSocket.send(args.sessionDescription);
  1826. }
  1827.  
  1828. if (!connection.transmitRoomOnce && !rtcMultiSession.isOwnerLeaving)
  1829. setTimeout(transmit, connection.interval || 3000);
  1830. }
  1831.  
  1832. // todo: test and fix next line.
  1833. if (!args.dontTransmit /* || connection.transmitRoomOnce */) transmit();
  1834. };
  1835.  
  1836. // join existing session
  1837. this.joinSession = function (_config) {
  1838. if (!defaultSocket)
  1839. return setTimeout(function () {
  1840. warn('Default-Socket is not yet initialized.');
  1841. rtcMultiSession.joinSession(_config);
  1842. }, 1000);
  1843.  
  1844. _config = _config || {};
  1845. participants = {};
  1846. connection.session = _config.session || {};
  1847. rtcMultiSession.broadcasterid = _config.userid;
  1848.  
  1849. if (_config.sessionid) {
  1850. // used later to prevent external rooms messages to be used by this user!
  1851. connection.sessionid = _config.sessionid;
  1852. }
  1853.  
  1854. connection.isAcceptNewSession = false;
  1855.  
  1856. var channel = getRandomString();
  1857. newPrivateSocket({
  1858. channel: channel,
  1859. extra: _config.extra || {},
  1860. userid: _config.userid
  1861. });
  1862.  
  1863. defaultSocket.send({
  1864. participant: true,
  1865. userid: connection.userid,
  1866. channel: channel,
  1867. targetUser: _config.userid,
  1868. extra: connection.extra,
  1869. session: connection.session
  1870. });
  1871. };
  1872.  
  1873. // send file/data or text message
  1874. this.send = function (message, _channel) {
  1875. message = JSON.stringify({
  1876. extra: connection.extra,
  1877. userid: connection.userid,
  1878. data: message
  1879. });
  1880.  
  1881. if (_channel) {
  1882. if (_channel.readyState == 'open') {
  1883. _channel.send(message);
  1884. }
  1885. return;
  1886. }
  1887.  
  1888. for (var dataChannel in connection.channels) {
  1889. var channel = connection.channels[dataChannel].channel;
  1890. if (channel.readyState == 'open') {
  1891. channel.send(message);
  1892. }
  1893. }
  1894. };
  1895.  
  1896. // leave session
  1897. this.leave = function (userid) {
  1898. clearSession(userid);
  1899.  
  1900. if (connection.isInitiator) {
  1901. rtcMultiSession.isOwnerLeaving = true;
  1902. connection.isInitiator = false;
  1903. }
  1904.  
  1905. // to stop/remove self streams
  1906. for (var i = 0; i < connection.attachStreams.length; i++) {
  1907. stopTracks(connection.attachStreams[i]);
  1908. }
  1909. connection.attachStreams = [];
  1910.  
  1911. // to allow capturing of identical streams
  1912. currentUserMediaRequest = {
  1913. streams: [],
  1914. mutex: false,
  1915. queueRequests: []
  1916. };
  1917.  
  1918. if (!userid) {
  1919. connection.isAcceptNewSession = true;
  1920. }
  1921.  
  1922. connection.busy = false;
  1923. };
  1924.  
  1925. // renegotiate new stream
  1926. this.addStream = function (e) {
  1927. var session = e.renegotiate;
  1928.  
  1929. connection.renegotiatedSessions.push({
  1930. session: e.renegotiate,
  1931. stream: e.stream
  1932. });
  1933.  
  1934. if (e.socket) {
  1935. addStream(connection.peers[e.socket.userid]);
  1936. } else {
  1937. for (var peer in connection.peers) {
  1938. addStream(connection.peers[peer]);
  1939. }
  1940. }
  1941.  
  1942. function addStream(_peer) {
  1943. var socket = _peer.socket;
  1944. if (!socket) {
  1945. warn(_peer, 'doesn\'t has socket.');
  1946. return;
  1947. }
  1948.  
  1949. updateSocketForLocalStreams(socket);
  1950.  
  1951. if (!_peer || !_peer.peer) {
  1952. throw 'No peer to renegotiate.';
  1953. }
  1954.  
  1955. var peer = _peer.peer;
  1956.  
  1957. if (e.stream) {
  1958. peer.attachStreams = [e.stream];
  1959. }
  1960.  
  1961. // detaching old streams
  1962. detachMediaStream(connection.detachStreams, peer.connection);
  1963.  
  1964. if (e.stream && (session.audio || session.video || session.screen)) {
  1965. // removeStream is not yet implemented in Firefox
  1966. // if(isFirefox) peer.connection.removeStream(e.stream);
  1967.  
  1968. if (isChrome || (isFirefox && !peer.connection.getLocalStreams().length)) {
  1969. peer.connection.addStream(e.stream);
  1970. }
  1971. }
  1972.  
  1973. // if isFirefox, try to create peer connection again!
  1974. if (isFirefox) {
  1975. return _peer.redial();
  1976. }
  1977.  
  1978. peer.recreateOffer(session, function (sdp, streaminfo) {
  1979. sendsdp({
  1980. sdp: sdp,
  1981. socket: socket,
  1982. renegotiate: session,
  1983. labels: connection.detachStreams,
  1984. streaminfo: streaminfo
  1985. });
  1986. connection.detachStreams = [];
  1987. });
  1988. }
  1989. };
  1990.  
  1991. // www.RTCMultiConnection.org/docs/request/
  1992. connection.request = function (userid, extra) {
  1993. if (connection.direction === 'many-to-many') connection.busy = true;
  1994.  
  1995. connection.captureUserMedia(function () {
  1996. // open private socket that will be used to receive offer-sdp
  1997. newPrivateSocket({
  1998. channel: connection.userid,
  1999. extra: extra || {},
  2000. userid: userid
  2001. });
  2002.  
  2003. // ask other user to create offer-sdp
  2004. defaultSocket.send({
  2005. participant: true,
  2006. userid: connection.userid,
  2007. extra: connection.extra || {},
  2008. targetUser: userid
  2009. });
  2010. });
  2011. };
  2012.  
  2013. function acceptRequest(response) {
  2014. if (!rtcMultiSession.requestsFrom) rtcMultiSession.requestsFrom = {};
  2015. if (connection.busy || rtcMultiSession.requestsFrom[response.userid]) return;
  2016.  
  2017. var obj = {
  2018. userid: response.userid,
  2019. extra: response.extra,
  2020. channel: response.channel || response.userid,
  2021. session: response.session || connection.session
  2022. };
  2023.  
  2024. rtcMultiSession.requestsFrom[response.userid] = obj;
  2025.  
  2026. // www.RTCMultiConnection.org/docs/onRequest/
  2027. if (connection.onRequest && (!connection.userType && connection.isInitiator)) {
  2028. connection.onRequest(obj);
  2029. } else _accept(obj);
  2030. }
  2031.  
  2032. function _accept(e) {
  2033. if (connection.userType) {
  2034. if (connection.direction === 'many-to-many') connection.busy = true;
  2035. defaultSocket.send({
  2036. acceptedRequestOf: e.userid,
  2037. userid: connection.userid,
  2038. extra: connection.extra || {}
  2039. });
  2040. }
  2041.  
  2042. participants[e.userid] = e.userid;
  2043. newPrivateSocket({
  2044. isofferer: true,
  2045. userid: e.userid,
  2046. channel: e.channel,
  2047. extra: e.extra || {},
  2048. session: e.session || connection.session
  2049. });
  2050. }
  2051.  
  2052. // www.RTCMultiConnection.org/docs/sendMessage/
  2053. connection.sendCustomMessage = function (message) {
  2054. if (!defaultSocket) {
  2055. return setTimeout(function () {
  2056. connection.sendMessage(message);
  2057. }, 1000);
  2058. }
  2059.  
  2060. defaultSocket.send({
  2061. userid: connection.userid,
  2062. customMessage: true,
  2063. message: message
  2064. });
  2065. };
  2066.  
  2067. // www.RTCMultiConnection.org/docs/accept/
  2068. connection.accept = function (e) {
  2069. // for backward compatibility
  2070. if (arguments.length > 1 && typeof arguments[0] == 'string') {
  2071. e = {};
  2072. if (arguments[0]) e.userid = arguments[0];
  2073. if (arguments[1]) e.extra = arguments[1];
  2074. if (arguments[2]) e.channel = arguments[2];
  2075. }
  2076.  
  2077. connection.captureUserMedia(function () {
  2078. _accept(e);
  2079. });
  2080. };
  2081. }
  2082.  
  2083. var RTCPeerConnection = window.mozRTCPeerConnection || window.webkitRTCPeerConnection;
  2084. var RTCSessionDescription = window.mozRTCSessionDescription || window.RTCSessionDescription;
  2085. var RTCIceCandidate = window.mozRTCIceCandidate || window.RTCIceCandidate;
  2086.  
  2087. function PeerConnection() {
  2088. return {
  2089. create: function (type, options) {
  2090. merge(this, options);
  2091.  
  2092. var self = this;
  2093.  
  2094. this.type = type;
  2095. this.init();
  2096. this.attachMediaStreams();
  2097.  
  2098. if (isData(this.session) && isFirefox) {
  2099. navigator.mozGetUserMedia({
  2100. audio: true,
  2101. fake: true
  2102. }, function (stream) {
  2103. self.connection.addStream(stream);
  2104.  
  2105. if (type == 'offer') {
  2106. self.createDataChannel();
  2107. }
  2108.  
  2109. self.getLocalDescription(type);
  2110.  
  2111. if (type == 'answer') {
  2112. self.createDataChannel();
  2113. }
  2114. }, this.onMediaError);
  2115. }
  2116.  
  2117. if (!isData(this.session) && isFirefox) {
  2118. if (this.session.data && type == 'offer') {
  2119. this.createDataChannel();
  2120. }
  2121.  
  2122. this.getLocalDescription(type);
  2123.  
  2124. if (this.session.data && type == 'answer') {
  2125. this.createDataChannel();
  2126. }
  2127. }
  2128.  
  2129. isChrome && self.getLocalDescription(type);
  2130. return this;
  2131. },
  2132. getLocalDescription: function (type) {
  2133. log('peer type is', type);
  2134.  
  2135. if (type == 'answer') {
  2136. this.setRemoteDescription(this.offerDescription);
  2137. }
  2138.  
  2139. var self = this;
  2140. this.connection[type == 'offer' ? 'createOffer' : 'createAnswer'](function (sessionDescription) {
  2141. sessionDescription.sdp = self.serializeSdp(sessionDescription.sdp);
  2142. self.connection.setLocalDescription(sessionDescription);
  2143. self.onSessionDescription(sessionDescription, self.streaminfo);
  2144. }, this.onSdpError, this.constraints);
  2145. },
  2146. serializeSdp: function (sdp) {
  2147. sdp = this.setBandwidth(sdp);
  2148. if (this.holdMLine == 'both') {
  2149. if (this.hold) {
  2150. this.prevSDP = sdp;
  2151. sdp = sdp.replace(/sendonly|recvonly|sendrecv/g, 'inactive');
  2152. } else if (this.prevSDP) {
  2153. // sdp = sdp.replace(/inactive/g, 'sendrecv');
  2154. sdp = this.prevSDP;
  2155. }
  2156. } else if (this.holdMLine == 'audio' || this.holdMLine == 'video') {
  2157. sdp = sdp.split('m=');
  2158.  
  2159. var audio = '';
  2160. var video = '';
  2161.  
  2162. if (sdp[1] && sdp[1].indexOf('audio') == 0) {
  2163. audio = 'm=' + sdp[1];
  2164. }
  2165. if (sdp[2] && sdp[2].indexOf('audio') == 0) {
  2166. audio = 'm=' + sdp[2];
  2167. }
  2168.  
  2169. if (sdp[1] && sdp[1].indexOf('video') == 0) {
  2170. video = 'm=' + sdp[1];
  2171. }
  2172. if (sdp[2] && sdp[2].indexOf('video') == 0) {
  2173. video = 'm=' + sdp[2];
  2174. }
  2175.  
  2176. if (this.holdMLine == 'audio') {
  2177. if (this.hold) {
  2178. this.prevSDP = sdp[0] + audio + video;
  2179. sdp = sdp[0] + audio.replace(/sendonly|recvonly|sendrecv/g, 'inactive') + video;
  2180. } else if (this.prevSDP) {
  2181. // sdp = sdp[0] + audio.replace(/inactive/g, 'sendrecv') + video;
  2182. sdp = this.prevSDP;
  2183. }
  2184. }
  2185.  
  2186. if (this.holdMLine == 'video') {
  2187. if (this.hold) {
  2188. this.prevSDP = sdp[0] + audio + video;
  2189. sdp = sdp[0] + audio + video.replace(/sendonly|recvonly|sendrecv/g, 'inactive');
  2190. } else if (this.prevSDP) {
  2191. // sdp = sdp[0] + audio + video.replace(/inactive/g, 'sendrecv');
  2192. sdp = this.prevSDP;
  2193. }
  2194. }
  2195. }
  2196. return sdp;
  2197. },
  2198. init: function () {
  2199. this.setConstraints();
  2200. this.connection = new RTCPeerConnection(this.iceServers, this.optionalArgument);
  2201.  
  2202. if (this.session.data && isChrome) {
  2203. this.createDataChannel();
  2204. }
  2205.  
  2206. this.connection.onicecandidate = function (event) {
  2207. if (event.candidate) {
  2208. self.onicecandidate(event.candidate);
  2209. }
  2210. };
  2211.  
  2212. this.connection.onaddstream = function (e) {
  2213. self.onaddstream(e.stream, self.session);
  2214.  
  2215. log('onaddstream', toStr(e.stream));
  2216. };
  2217.  
  2218. this.connection.onremovestream = function (e) {
  2219. self.onremovestream(e.stream);
  2220. };
  2221.  
  2222. this.connection.onsignalingstatechange = function () {
  2223. self.connection && self.oniceconnectionstatechange({
  2224. iceConnectionState: self.connection.iceConnectionState,
  2225. iceGatheringState: self.connection.iceGatheringState,
  2226. signalingState: self.connection.signalingState
  2227. });
  2228. };
  2229.  
  2230. this.connection.oniceconnectionstatechange = function () {
  2231. self.connection && self.oniceconnectionstatechange({
  2232. iceConnectionState: self.connection.iceConnectionState,
  2233. iceGatheringState: self.connection.iceGatheringState,
  2234. signalingState: self.connection.signalingState
  2235. });
  2236. };
  2237. var self = this;
  2238. },
  2239. setBandwidth: function (sdp) {
  2240. // sdp.replace( /a=sendrecv\r\n/g , 'a=sendrecv\r\nb=AS:50\r\n');
  2241.  
  2242. if (isMobileDevice || isFirefox || !this.bandwidth) return sdp;
  2243.  
  2244. var bandwidth = this.bandwidth;
  2245.  
  2246. // if screen; must use at least 300kbs
  2247. if (bandwidth.screen && this.session.screen && isEmpty(bandwidth)) {
  2248. sdp = sdp.replace(/b=AS([^\r\n]+\r\n)/g, '');
  2249. sdp = sdp.replace(/a=mid:video\r\n/g, 'a=mid:video\r\nb=AS:' + bandwidth.screen + '\r\n');
  2250. }
  2251.  
  2252. // remove existing bandwidth lines
  2253. if (bandwidth.audio || bandwidth.video || bandwidth.data) {
  2254. sdp = sdp.replace(/b=AS([^\r\n]+\r\n)/g, '');
  2255. }
  2256.  
  2257. if (bandwidth.audio) {
  2258. sdp = sdp.replace(/a=mid:audio\r\n/g, 'a=mid:audio\r\nb=AS:' + bandwidth.audio + '\r\n');
  2259. }
  2260.  
  2261. if (bandwidth.video) {
  2262. sdp = sdp.replace(/a=mid:video\r\n/g, 'a=mid:video\r\nb=AS:' + (this.session.screen ? '300' : bandwidth.video) + '\r\n');
  2263. }
  2264.  
  2265. if (bandwidth.data && !this.preferSCTP) {
  2266. sdp = sdp.replace(/a=mid:data\r\n/g, 'a=mid:data\r\nb=AS:' + bandwidth.data + '\r\n');
  2267. }
  2268.  
  2269. return sdp;
  2270. },
  2271. setConstraints: function () {
  2272. this.constraints = {
  2273. optional: this.sdpConstraints.optional || [],
  2274. mandatory: this.sdpConstraints.mandatory || {
  2275. OfferToReceiveAudio: !!this.session.audio,
  2276. OfferToReceiveVideo: !!this.session.video || !!this.session.screen
  2277. }
  2278. };
  2279.  
  2280. // workaround for older firefox
  2281. if (this.session.data && isFirefox && this.constraints.mandatory) {
  2282. this.constraints.mandatory.OfferToReceiveAudio = true;
  2283. }
  2284.  
  2285. log('sdp-constraints', toStr(this.constraints.mandatory));
  2286.  
  2287. this.optionalArgument = {
  2288. optional: this.optionalArgument.optional || [{
  2289. DtlsSrtpKeyAgreement: true
  2290. }],
  2291. mandatory: this.optionalArgument.mandatory || {}
  2292. };
  2293.  
  2294. if (isChrome && chromeVersion >= 32 && !isNodeWebkit) {
  2295. this.optionalArgument.optional.push({
  2296. googIPv6: true
  2297. });
  2298. this.optionalArgument.optional.push({ googDscp: true });
  2299. }
  2300.  
  2301. if (!this.preferSCTP) {
  2302. this.optionalArgument.optional.push({
  2303. RtpDataChannels: true
  2304. });
  2305. }
  2306.  
  2307. log('optional-argument', toStr(this.optionalArgument.optional));
  2308.  
  2309. this.iceServers = {
  2310. iceServers: this.iceServers
  2311. };
  2312.  
  2313. log('ice-servers', toStr(this.iceServers.iceServers));
  2314. },
  2315. onSdpError: function (e) {
  2316. var message = toStr(e);
  2317.  
  2318. if (message && message.indexOf('RTP/SAVPF Expects at least 4 fields') != -1) {
  2319. message = 'It seems that you are trying to interop RTP-datachannels with SCTP. It is not supported!';
  2320. }
  2321. error('onSdpError:', message);
  2322. },
  2323. onMediaError: function (err) {
  2324. error(toStr(err));
  2325. },
  2326. setRemoteDescription: function (sessionDescription) {
  2327. if (!sessionDescription) throw 'Remote session description should NOT be NULL.';
  2328.  
  2329. log('setting remote description', sessionDescription.type, sessionDescription.sdp);
  2330. this.connection.setRemoteDescription(
  2331. new RTCSessionDescription(sessionDescription)
  2332. );
  2333. },
  2334. addIceCandidate: function (candidate) {
  2335. var iceCandidate = new RTCIceCandidate({
  2336. sdpMLineIndex: candidate.sdpMLineIndex,
  2337. candidate: candidate.candidate
  2338. });
  2339.  
  2340. if (isNodeWebkit) {
  2341. this.connection.addIceCandidate(iceCandidate);
  2342. } else {
  2343. // landed in chrome M33
  2344. // node-webkit doesn't support this format yet!
  2345. this.connection.addIceCandidate(iceCandidate, this.onIceSuccess, this.onIceFailure);
  2346. }
  2347. },
  2348. onIceSuccess: function () {
  2349. log('ice success', toStr(arguments));
  2350. },
  2351. onIceFailure: function () {
  2352. warn('ice failure', toStr(arguments));
  2353. },
  2354. createDataChannel: function (channelIdentifier) {
  2355. if (!this.channels) this.channels = [];
  2356.  
  2357. // protocol: 'text/chat', preset: true, stream: 16
  2358. // maxRetransmits:0 && ordered:false
  2359. var dataChannelDict = {};
  2360.  
  2361. if (this.dataChannelDict) dataChannelDict = this.dataChannelDict;
  2362.  
  2363. if (isChrome && !this.preferSCTP) {
  2364. dataChannelDict.reliable = false; // Deprecated!
  2365. }
  2366.  
  2367. log('dataChannelDict', toStr(dataChannelDict));
  2368.  
  2369. if (isFirefox) {
  2370. this.connection.onconnection = function () {
  2371. self.socket.send({
  2372. userid: self.selfUserid,
  2373. isCreateDataChannel: true
  2374. });
  2375. };
  2376. }
  2377.  
  2378. if (this.type == 'answer' || isFirefox) {
  2379. this.connection.ondatachannel = function (event) {
  2380. self.setChannelEvents(event.channel);
  2381. };
  2382. }
  2383.  
  2384. if ((isChrome && this.type == 'offer') || isFirefox) {
  2385. this.setChannelEvents(
  2386. this.connection.createDataChannel(channelIdentifier || 'channel', dataChannelDict)
  2387. );
  2388. }
  2389.  
  2390. var self = this;
  2391. },
  2392. setChannelEvents: function (channel) {
  2393. var self = this;
  2394. channel.onmessage = function (event) {
  2395. self.onmessage(event.data);
  2396. };
  2397.  
  2398. var numberOfTimes = 0;
  2399. channel.onopen = function () {
  2400. channel.push = channel.send;
  2401. channel.send = function (data) {
  2402. if (channel.readyState != 'open') {
  2403. numberOfTimes++;
  2404. return setTimeout(function () {
  2405. if (numberOfTimes < 20) {
  2406. channel.send(data);
  2407. } else throw 'Number of times exceeded to wait for WebRTC data connection to be opened.';
  2408. }, 1000);
  2409. }
  2410. try {
  2411. channel.push(data);
  2412. } catch (e) {
  2413. numberOfTimes++;
  2414. warn('Data transmission failed. Re-transmitting..', numberOfTimes, toStr(e));
  2415. if (numberOfTimes >= 20) throw 'Number of times exceeded to resend data packets over WebRTC data channels.';
  2416. setTimeout(function () {
  2417. channel.send(data);
  2418. }, 100);
  2419. }
  2420. };
  2421. self.onopen(channel);
  2422. };
  2423.  
  2424. channel.onerror = function (event) {
  2425. self.onerror(event);
  2426. };
  2427.  
  2428. channel.onclose = function (event) {
  2429. self.onclose(event);
  2430. };
  2431.  
  2432. this.channels.push(channel);
  2433. },
  2434. attachMediaStreams: function () {
  2435. var streams = this.attachStreams;
  2436. for (var i = 0; i < streams.length; i++) {
  2437. log('attaching stream:', streams[i].streamid);
  2438. this.connection.addStream(streams[i]);
  2439. }
  2440. this.getStreamInfo();
  2441. },
  2442. getStreamInfo: function () {
  2443. this.streaminfo = '';
  2444. var streams = this.attachStreams;
  2445. for (var i = 0; i < streams.length; i++) {
  2446. if (i == 0) {
  2447. this.streaminfo = streams[i].streamid;
  2448. } else {
  2449. this.streaminfo += '----' + streams[i].streamid;
  2450. }
  2451. }
  2452. this.attachStreams = [];
  2453. },
  2454. recreateOffer: function (renegotiate, callback) {
  2455. // if(isFirefox) this.create(this.type, this);
  2456.  
  2457. log('recreating offer');
  2458.  
  2459. this.type = 'offer';
  2460. this.renegotiate = true;
  2461. this.session = renegotiate;
  2462. this.setConstraints();
  2463.  
  2464. this.onSessionDescription = callback;
  2465. this.getStreamInfo();
  2466.  
  2467. // one can renegotiate data connection in existing audio/video/screen connection!
  2468. if (this.session.data && isChrome) {
  2469. this.createDataChannel();
  2470. }
  2471.  
  2472. this.getLocalDescription('offer');
  2473. },
  2474. recreateAnswer: function (sdp, session, callback) {
  2475. // if(isFirefox) this.create(this.type, this);
  2476.  
  2477. log('recreating answer');
  2478.  
  2479. this.type = 'answer';
  2480. this.renegotiate = true;
  2481. this.session = session;
  2482. this.setConstraints();
  2483.  
  2484. this.onSessionDescription = callback;
  2485. this.offerDescription = sdp;
  2486. this.getStreamInfo();
  2487.  
  2488. // one can renegotiate data connection in existing audio/video/screen connection!
  2489. if (this.session.data && isChrome) {
  2490. this.createDataChannel();
  2491. }
  2492.  
  2493. this.getLocalDescription('answer');
  2494. }
  2495. };
  2496. }
  2497.  
  2498. var video_constraints = {
  2499. mandatory: {},
  2500. optional: []
  2501. };
  2502.  
  2503. /* by @FreCap pull request #41 */
  2504. var currentUserMediaRequest = {
  2505. streams: [],
  2506. mutex: false,
  2507. queueRequests: []
  2508. };
  2509.  
  2510. function getUserMedia(options) {
  2511. if (currentUserMediaRequest.mutex === true) {
  2512. currentUserMediaRequest.queueRequests.push(options);
  2513. return;
  2514. }
  2515. currentUserMediaRequest.mutex = true;
  2516.  
  2517. // tools.ietf.org/html/draft-alvestrand-constraints-resolution-00
  2518. var mediaConstraints = options.mediaConstraints || {};
  2519. var n = navigator,
  2520. hints = options.constraints || {
  2521. audio: true,
  2522. video: video_constraints
  2523. };
  2524.  
  2525. if (hints.video == true) hints.video = video_constraints;
  2526.  
  2527. // connection.mediaConstraints.audio = false;
  2528. if (typeof mediaConstraints.audio != 'undefined') {
  2529. hints.audio = mediaConstraints.audio;
  2530. }
  2531.  
  2532. // connection.media.min(320,180);
  2533. // connection.media.max(1920,1080);
  2534. var media = options.media;
  2535. if (isChrome) {
  2536. var mandatory = {
  2537. minWidth: media.minWidth,
  2538. minHeight: media.minHeight,
  2539. maxWidth: media.maxWidth,
  2540. maxHeight: media.maxHeight,
  2541. minAspectRatio: media.minAspectRatio
  2542. };
  2543.  
  2544. // code.google.com/p/chromium/issues/detail?id=143631#c9
  2545. var allowed = ['1920:1080', '1280:720', '960:720', '640:360', '640:480', '320:240', '320:180'];
  2546.  
  2547. if (allowed.indexOf(mandatory.minWidth + ':' + mandatory.minHeight) == -1 ||
  2548. allowed.indexOf(mandatory.maxWidth + ':' + mandatory.maxHeight) == -1) {
  2549. error('The min/max width/height constraints you passed "seems" NOT supported.', toStr(mandatory));
  2550. }
  2551.  
  2552. if (mandatory.minWidth > mandatory.maxWidth || mandatory.minHeight > mandatory.maxHeight) {
  2553. error('Minimum value must not exceed maximum value.', toStr(mandatory));
  2554. }
  2555.  
  2556. if (mandatory.minWidth >= 1280 && mandatory.minHeight >= 720) {
  2557. warn('Enjoy HD video! min/' + mandatory.minWidth + ':' + mandatory.minHeight + ', max/' + mandatory.maxWidth + ':' + mandatory.maxHeight);
  2558. }
  2559.  
  2560. hints.video.mandatory = merge(hints.video.mandatory, mandatory);
  2561. }
  2562.  
  2563. if (mediaConstraints.mandatory)
  2564. hints.video.mandatory = merge(hints.video.mandatory, mediaConstraints.mandatory);
  2565.  
  2566. // mediaConstraints.optional.bandwidth = 1638400;
  2567. if (mediaConstraints.optional)
  2568. hints.video.optional[0] = merge({}, mediaConstraints.optional);
  2569.  
  2570. log('media hints:', toStr(hints));
  2571.  
  2572. // easy way to match
  2573. var idInstance = JSON.stringify(hints);
  2574.  
  2575. function streaming(stream, returnBack, streamid) {
  2576. if (!streamid) streamid = getRandomString();
  2577.  
  2578. var video = options.video;
  2579. if (video) {
  2580. video[isFirefox ? 'mozSrcObject' : 'src'] = isFirefox ? stream : window.webkitURL.createObjectURL(stream);
  2581. video.play();
  2582. }
  2583.  
  2584. options.onsuccess(stream, returnBack, idInstance, streamid);
  2585. currentUserMediaRequest.streams[idInstance] = {
  2586. stream: stream,
  2587. streamid: streamid
  2588. };
  2589. currentUserMediaRequest.mutex = false;
  2590. if (currentUserMediaRequest.queueRequests.length)
  2591. getUserMedia(currentUserMediaRequest.queueRequests.shift());
  2592. }
  2593.  
  2594. if (currentUserMediaRequest.streams[idInstance]) {
  2595. streaming(currentUserMediaRequest.streams[idInstance].stream, true, currentUserMediaRequest.streams[idInstance].streamid);
  2596. } else {
  2597. n.getMedia = n.webkitGetUserMedia || n.mozGetUserMedia;
  2598. n.getMedia(hints, streaming, function (err) {
  2599. if (options.onerror) options.onerror(err, idInstance);
  2600. else error(toStr(err));
  2601. });
  2602. }
  2603. }
  2604.  
  2605. var FileSender = {
  2606. send: function (config) {
  2607. var connection = config.connection;
  2608. var channel = config.channel;
  2609. var privateChannel = config._channel;
  2610. var file = config.file;
  2611.  
  2612. if (!config.file) {
  2613. error('You must attach/select a file.');
  2614. return;
  2615. }
  2616.  
  2617. // max chunk sending limit on chrome is 64k
  2618. // max chunk receiving limit on firefox is 16k
  2619. var packetSize = (!!navigator.mozGetUserMedia || connection.preferSCTP) ? 15 * 1000 : 1 * 1000;
  2620.  
  2621. if (connection.chunkSize) {
  2622. packetSize = connection.chunkSize;
  2623. }
  2624.  
  2625. var textToTransfer = '';
  2626. var numberOfPackets = 0;
  2627. var packets = 0;
  2628.  
  2629. file.uuid = getRandomString();
  2630.  
  2631. function processInWebWorker() {
  2632. var blob = URL.createObjectURL(new Blob(['function readFile(_file) {postMessage(new FileReaderSync().readAsDataURL(_file));};this.onmessage = function (e) {readFile(e.data);}'], {
  2633. type: 'application/javascript'
  2634. }));
  2635.  
  2636. var worker = new Worker(blob);
  2637. URL.revokeObjectURL(blob);
  2638. return worker;
  2639. }
  2640.  
  2641. if (!!window.Worker && !isMobileDevice) {
  2642. var webWorker = processInWebWorker();
  2643.  
  2644. webWorker.onmessage = function (event) {
  2645. onReadAsDataURL(event.data);
  2646. };
  2647.  
  2648. webWorker.postMessage(file);
  2649. } else {
  2650. var reader = new FileReader();
  2651. reader.onload = function (e) {
  2652. onReadAsDataURL(e.target.result);
  2653. };
  2654. reader.readAsDataURL(file);
  2655. }
  2656.  
  2657. function onReadAsDataURL(dataURL, text) {
  2658. var data = {
  2659. type: 'file',
  2660. uuid: file.uuid,
  2661. maxChunks: numberOfPackets,
  2662. currentPosition: numberOfPackets - packets,
  2663. name: file.name,
  2664. fileType: file.type,
  2665. size: file.size,
  2666.  
  2667. userid: connection.userid,
  2668. extra: connection.extra
  2669. };
  2670.  
  2671. if (dataURL) {
  2672. text = dataURL;
  2673. numberOfPackets = packets = data.packets = parseInt(text.length / packetSize);
  2674.  
  2675. file.maxChunks = data.maxChunks = numberOfPackets;
  2676. data.currentPosition = numberOfPackets - packets;
  2677.  
  2678. file.userid = connection.userid;
  2679. file.extra = connection.extra;
  2680. file.sending = true;
  2681. connection.onFileStart(file);
  2682. }
  2683.  
  2684. connection.onFileProgress({
  2685. remaining: packets--,
  2686. length: numberOfPackets,
  2687. sent: numberOfPackets - packets,
  2688.  
  2689. maxChunks: numberOfPackets,
  2690. uuid: file.uuid,
  2691. currentPosition: numberOfPackets - packets,
  2692.  
  2693. sending: true
  2694. }, file.uuid);
  2695.  
  2696. if (text.length > packetSize) data.message = text.slice(0, packetSize);
  2697. else {
  2698. data.message = text;
  2699. data.last = true;
  2700. data.name = file.name;
  2701.  
  2702. file.url = URL.createObjectURL(file);
  2703. file.userid = connection.userid;
  2704. file.extra = connection.extra;
  2705. file.sending = true;
  2706. connection.onFileEnd(file);
  2707. }
  2708.  
  2709. channel.send(data, privateChannel);
  2710.  
  2711. textToTransfer = text.slice(data.message.length);
  2712. if (textToTransfer.length) {
  2713. setTimeout(function () {
  2714. onReadAsDataURL(null, textToTransfer);
  2715. }, connection.chunkInterval || 100);
  2716. }
  2717. }
  2718. }
  2719. };
  2720.  
  2721. function FileReceiver(connection) {
  2722. var content = {},
  2723. packets = {},
  2724. numberOfPackets = {};
  2725.  
  2726. function receive(data) {
  2727. var uuid = data.uuid;
  2728.  
  2729. if (typeof data.packets !== 'undefined') {
  2730. numberOfPackets[uuid] = packets[uuid] = parseInt(data.packets);
  2731. data.sending = false;
  2732. connection.onFileStart(data);
  2733. }
  2734.  
  2735. connection.onFileProgress({
  2736. remaining: packets[uuid]--,
  2737. length: numberOfPackets[uuid],
  2738. received: numberOfPackets[uuid] - packets[uuid],
  2739.  
  2740. maxChunks: numberOfPackets[uuid],
  2741. uuid: uuid,
  2742. currentPosition: numberOfPackets[uuid] - packets[uuid],
  2743.  
  2744. sending: false
  2745. }, uuid);
  2746.  
  2747. if (!content[uuid]) content[uuid] = [];
  2748.  
  2749. content[uuid].push(data.message);
  2750.  
  2751. if (data.last) {
  2752. var dataURL = content[uuid].join('');
  2753.  
  2754. FileConverter.DataURLToBlob(dataURL, data.fileType, function (blob) {
  2755. blob.uuid = uuid;
  2756. blob.name = data.name;
  2757. blob.type = data.fileType;
  2758.  
  2759. blob.url = (window.URL || window.webkitURL).createObjectURL(blob);
  2760.  
  2761. blob.sending = false;
  2762. blob.userid = data.userid || connection.userid;
  2763. blob.extra = data.extra || connection.extra;
  2764. connection.onFileEnd(blob);
  2765.  
  2766. if (connection.autoSaveToDisk) {
  2767. FileSaver.SaveToDisk(blob.url, data.name);
  2768. }
  2769.  
  2770. delete content[uuid];
  2771. });
  2772. }
  2773. }
  2774.  
  2775. return {
  2776. receive: receive
  2777. };
  2778. }
  2779.  
  2780. var FileSaver = {
  2781. SaveToDisk: function (fileUrl, fileName) {
  2782. var hyperlink = document.createElement('a');
  2783. hyperlink.href = fileUrl;
  2784. hyperlink.target = '_blank';
  2785. hyperlink.download = fileName || fileUrl;
  2786.  
  2787. var mouseEvent = new MouseEvent('click', {
  2788. view: window,
  2789. bubbles: true,
  2790. cancelable: true
  2791. });
  2792.  
  2793. hyperlink.dispatchEvent(mouseEvent);
  2794.  
  2795. // (window.URL || window.webkitURL).revokeObjectURL(hyperlink.href);
  2796. }
  2797. };
  2798.  
  2799. var FileConverter = {
  2800. DataURLToBlob: function (dataURL, fileType, callback) {
  2801.  
  2802. function processInWebWorker() {
  2803. var blob = URL.createObjectURL(new Blob(['function getBlob(_dataURL, _fileType) {var binary = atob(_dataURL.substr(_dataURL.indexOf(",") + 1)),i = binary.length,view = new Uint8Array(i);while (i--) {view[i] = binary.charCodeAt(i);};postMessage(new Blob([view], {type: _fileType}));};this.onmessage = function (e) {var data = JSON.parse(e.data); getBlob(data.dataURL, data.fileType);}'], {
  2804. type: 'application/javascript'
  2805. }));
  2806.  
  2807. var worker = new Worker(blob);
  2808. URL.revokeObjectURL(blob);
  2809. return worker;
  2810. }
  2811.  
  2812. if (!!window.Worker && !isMobileDevice) {
  2813. var webWorker = processInWebWorker();
  2814.  
  2815. webWorker.onmessage = function (event) {
  2816. callback(event.data);
  2817. };
  2818.  
  2819. webWorker.postMessage(JSON.stringify({
  2820. dataURL: dataURL,
  2821. fileType: fileType
  2822. }));
  2823. } else {
  2824. var binary = atob(dataURL.substr(dataURL.indexOf(',') + 1)),
  2825. i = binary.length,
  2826. view = new Uint8Array(i);
  2827.  
  2828. while (i--) {
  2829. view[i] = binary.charCodeAt(i);
  2830. }
  2831.  
  2832. callback(new Blob([view]));
  2833. }
  2834. }
  2835. };
  2836.  
  2837. var TextSender = {
  2838. send: function (config) {
  2839. var connection = config.connection;
  2840.  
  2841. var channel = config.channel,
  2842. _channel = config._channel,
  2843. initialText = config.text,
  2844. packetSize = connection.chunkSize || 1000,
  2845. textToTransfer = '',
  2846. isobject = false;
  2847.  
  2848. if (typeof initialText !== 'string') {
  2849. isobject = true;
  2850. initialText = JSON.stringify(initialText);
  2851. }
  2852.  
  2853. // uuid is used to uniquely identify sending instance
  2854. var uuid = getRandomString();
  2855. var sendingTime = new Date().getTime();
  2856.  
  2857. sendText(initialText);
  2858.  
  2859. function sendText(textMessage, text) {
  2860. var data = {
  2861. type: 'text',
  2862. uuid: uuid,
  2863. sendingTime: sendingTime
  2864. };
  2865.  
  2866. if (textMessage) {
  2867. text = textMessage;
  2868. data.packets = parseInt(text.length / packetSize);
  2869. }
  2870.  
  2871. if (text.length > packetSize)
  2872. data.message = text.slice(0, packetSize);
  2873. else {
  2874. data.message = text;
  2875. data.last = true;
  2876. data.isobject = isobject;
  2877. }
  2878.  
  2879. channel.send(data, _channel);
  2880.  
  2881. textToTransfer = text.slice(data.message.length);
  2882.  
  2883. if (textToTransfer.length) {
  2884. setTimeout(function () {
  2885. sendText(null, textToTransfer);
  2886. }, connection.chunkInterval || 100);
  2887. }
  2888. }
  2889. }
  2890. };
  2891.  
  2892. // _______________
  2893. // TextReceiver.js
  2894.  
  2895. function TextReceiver(connection) {
  2896. var content = {};
  2897.  
  2898. function receive(data, userid, extra) {
  2899. // uuid is used to uniquely identify sending instance
  2900. var uuid = data.uuid;
  2901. if (!content[uuid]) content[uuid] = [];
  2902.  
  2903. content[uuid].push(data.message);
  2904. if (data.last) {
  2905. var message = content[uuid].join('');
  2906. if (data.isobject) message = JSON.parse(message);
  2907.  
  2908. // latency detection
  2909. var receivingTime = new Date().getTime();
  2910. var latency = receivingTime - data.sendingTime;
  2911.  
  2912. var e = {
  2913. data: message,
  2914. userid: userid,
  2915. extra: extra,
  2916. latency: latency
  2917. };
  2918.  
  2919. if (message.preRecordedMediaChunk) {
  2920. if (!connection.preRecordedMedias[message.streamerid]) {
  2921. connection.shareMediaFile(null, null, message.streamerid);
  2922. }
  2923. connection.preRecordedMedias[message.streamerid].onData(message.chunk);
  2924. } else if (connection.autoTranslateText) {
  2925. e.original = e.data;
  2926. connection.Translator.TranslateText(e.data, function (translatedText) {
  2927. e.data = translatedText;
  2928. connection.onmessage(e);
  2929. });
  2930. } else if (message.isPartOfScreen) {
  2931. connection.onpartofscreen(message);
  2932. } else connection.onmessage(e);
  2933.  
  2934. delete content[uuid];
  2935. }
  2936. }
  2937.  
  2938. return {
  2939. receive: receive
  2940. };
  2941. }
  2942.  
  2943. // Sound meter is used to detect speaker
  2944. // SoundMeter.js copyright goes to someone else!
  2945.  
  2946. function SoundMeter(config) {
  2947. var connection = config.connection;
  2948. var context = config.context;
  2949. this.context = context;
  2950. this.volume = 0.0;
  2951. this.slow_volume = 0.0;
  2952. this.clip = 0.0;
  2953.  
  2954. // Legal values are (256, 512, 1024, 2048, 4096, 8192, 16384)
  2955. this.script = context.createScriptProcessor(256, 1, 1);
  2956. that = this;
  2957.  
  2958. this.script.onaudioprocess = function (event) {
  2959. var input = event.inputBuffer.getChannelData(0);
  2960. var i;
  2961. var sum = 0.0;
  2962. var clipcount = 0;
  2963. for (i = 0; i < input.length; ++i) {
  2964. sum += input[i] * input[i];
  2965. if (Math.abs(input[i]) > 0.99) {
  2966. clipcount += 1;
  2967. }
  2968. }
  2969. that.volume = Math.sqrt(sum / input.length);
  2970.  
  2971. var volume = that.volume.toFixed(2);
  2972.  
  2973. if (volume >= .1 && connection.onspeaking) {
  2974. connection.onspeaking(config.event);
  2975. }
  2976.  
  2977. if (volume < .1 && connection.onsilence) {
  2978. connection.onsilence(config.event);
  2979. }
  2980. };
  2981. }
  2982.  
  2983. SoundMeter.prototype.connectToSource = function (stream) {
  2984. this.mic = this.context.createMediaStreamSource(stream);
  2985. this.mic.connect(this.script);
  2986. this.script.connect(this.context.destination);
  2987. };
  2988.  
  2989. SoundMeter.prototype.stop = function () {
  2990. this.mic.disconnect();
  2991. this.script.disconnect();
  2992. };
  2993.  
  2994.  
  2995. var isChrome = !!navigator.webkitGetUserMedia;
  2996. var isFirefox = !!navigator.mozGetUserMedia;
  2997. var isMobileDevice = navigator.userAgent.match(/Android|iPhone|iPad|iPod|BlackBerry|IEMobile/i);
  2998.  
  2999. // detect node-webkit
  3000. var isNodeWebkit = window.process && (typeof window.process == 'object') && window.process.versions && window.process.versions['node-webkit'];
  3001.  
  3002. window.MediaStream = window.MediaStream || window.webkitMediaStream;
  3003. window.AudioContext = window.AudioContext || window.webkitAudioContext;
  3004.  
  3005. function getRandomString() {
  3006. return (Math.random() * new Date().getTime()).toString(36).replace(/\./g, '');
  3007. }
  3008.  
  3009. var chromeVersion = !!navigator.mozGetUserMedia ? 0 : parseInt(navigator.userAgent.match(/Chrom(e|ium)\/([0-9]+)\./)[2]);
  3010.  
  3011. function isData(session) {
  3012. return !session.audio && !session.video && !session.screen && session.data;
  3013. }
  3014.  
  3015. function isEmpty(session) {
  3016. var length = 0;
  3017. for (var s in session) {
  3018. length++;
  3019. }
  3020. return length == 0;
  3021. }
  3022.  
  3023. function swap(arr) {
  3024. var swapped = [],
  3025. length = arr.length;
  3026. for (var i = 0; i < length; i++)
  3027. if (arr[i] && arr[i] !== true)
  3028. swapped.push(arr[i]);
  3029. return swapped;
  3030. }
  3031.  
  3032. var log = console.log.bind(console);
  3033. var error = console.error.bind(console);
  3034. var warn = console.warn.bind(console);
  3035.  
  3036. function toStr(obj) {
  3037. return JSON.stringify(obj, function (key, value) {
  3038. if (value && value.sdp) {
  3039. log(value.sdp.type, '\t', value.sdp.sdp);
  3040. return '';
  3041. } else return value;
  3042. }, '\t');
  3043. }
  3044.  
  3045. function getLength(obj) {
  3046. var length = 0;
  3047. for (var o in obj)
  3048. if (o) length++;
  3049. return length;
  3050. }
  3051.  
  3052. // Get HTMLAudioElement/HTMLVideoElement accordingly
  3053.  
  3054. function createMediaElement(stream, session) {
  3055. var isAudio = session.audio && !session.video && !session.screen;
  3056. if (isChrome && stream.getAudioTracks && stream.getVideoTracks) {
  3057. isAudio = stream.getAudioTracks().length && !stream.getVideoTracks().length;
  3058. }
  3059.  
  3060. var mediaElement = document.createElement(isAudio ? 'audio' : 'video');
  3061.  
  3062. // "mozSrcObject" is always preferred over "src"!!
  3063. mediaElement[isFirefox ? 'mozSrcObject' : 'src'] = isFirefox ? stream : window.webkitURL.createObjectURL(stream);
  3064.  
  3065. mediaElement.controls = true;
  3066. mediaElement.autoplay = !!session.remote;
  3067. mediaElement.muted = session.remote ? false : true;
  3068.  
  3069. mediaElement.play();
  3070.  
  3071. return mediaElement;
  3072. }
  3073.  
  3074. function merge(mergein, mergeto) {
  3075. if (!mergein) mergein = {};
  3076. if (!mergeto) return mergein;
  3077.  
  3078. for (var item in mergeto) {
  3079. mergein[item] = mergeto[item];
  3080. }
  3081. return mergein;
  3082. }
  3083.  
  3084. function loadScript(src, onload) {
  3085. var script = document.createElement('script');
  3086. script.src = src;
  3087. if (onload) script.onload = onload;
  3088. document.documentElement.appendChild(script);
  3089. }
  3090.  
  3091. function muteOrUnmute(e) {
  3092. var stream = e.stream,
  3093. root = e.root,
  3094. session = e.session || {},
  3095. enabled = e.enabled;
  3096.  
  3097. if (!session.audio && !session.video) {
  3098. if (typeof session != 'string') {
  3099. session = merge(session, {
  3100. audio: true,
  3101. video: true
  3102. });
  3103. } else {
  3104. session = {
  3105. audio: true,
  3106. video: true
  3107. };
  3108. }
  3109. }
  3110.  
  3111. // implementation from #68
  3112. if (session.type) {
  3113. if (session.type == 'remote' && root.type != 'remote') return;
  3114. if (session.type == 'local' && root.type != 'local') return;
  3115. }
  3116.  
  3117. log(enabled ? 'mute' : 'unmute', 'session', session);
  3118.  
  3119. // enable/disable audio/video tracks
  3120.  
  3121. if (session.audio) {
  3122. var audioTracks = stream.getAudioTracks()[0];
  3123. if (audioTracks)
  3124. audioTracks.enabled = !enabled;
  3125. }
  3126.  
  3127. if (session.video) {
  3128. var videoTracks = stream.getVideoTracks()[0];
  3129. if (videoTracks)
  3130. videoTracks.enabled = !enabled;
  3131. }
  3132.  
  3133. root.sockets.forEach(function (socket) {
  3134. if (root.type == 'local')
  3135. socket.send({
  3136. userid: root.rtcMultiConnection.userid,
  3137. streamid: root.streamid,
  3138. mute: !!enabled,
  3139. unmute: !enabled,
  3140. session: session
  3141. });
  3142.  
  3143. if (root.type == 'remote')
  3144. socket.send({
  3145. userid: root.rtcMultiConnection.userid,
  3146. promptMuteUnmute: true,
  3147. streamid: root.streamid,
  3148. mute: !!enabled,
  3149. unmute: !enabled,
  3150. session: session
  3151. });
  3152. });
  3153.  
  3154. // According to issue #135, onmute/onumute must be fired for self
  3155. // "fakeObject" is used because we need to keep session for renegotiated streams;
  3156. // and MUST pass accurate session over "onstreamended" event.
  3157. var fakeObject = merge({}, root.streamObject);
  3158. fakeObject.session = session;
  3159. fakeObject.isAudio = session.audio && !session.video;
  3160. fakeObject.isVideo = (!session.audio && session.video) || (session.audio && session.video);
  3161. if (!!enabled) {
  3162. root.rtcMultiConnection.onmute(fakeObject);
  3163. }
  3164.  
  3165. if (!enabled) {
  3166. root.rtcMultiConnection.onunmute(fakeObject);
  3167. }
  3168. }
  3169.  
  3170. function stopTracks(mediaStream) {
  3171. // if getAudioTracks is not implemented
  3172. if ((!mediaStream.getAudioTracks || !mediaStream.getVideoTracks) && mediaStream.stop) {
  3173. mediaStream.stop();
  3174. return;
  3175. }
  3176.  
  3177. var fallback = false,
  3178. i;
  3179.  
  3180. // MediaStream.stop should be avoided. It still exist and works but
  3181. // it is removed from the spec and instead MediaStreamTrack.stop should be used
  3182. var audioTracks = mediaStream.getAudioTracks();
  3183. var videoTracks = mediaStream.getVideoTracks();
  3184.  
  3185. for (i = 0; i < audioTracks.length; i++) {
  3186. if (audioTracks[i].stop) {
  3187. // for chrome canary; which has "stop" method; however not functional yet!
  3188. try {
  3189. audioTracks[i].stop();
  3190. } catch (e) {
  3191. fallback = true;
  3192. continue;
  3193. }
  3194. } else {
  3195. fallback = true;
  3196. continue;
  3197. }
  3198. }
  3199.  
  3200. for (i = 0; i < videoTracks.length; i++) {
  3201. if (videoTracks[i].stop) {
  3202. // for chrome canary; which has "stop" method; however not functional yet!
  3203. try {
  3204. videoTracks[i].stop();
  3205. } catch (e) {
  3206. fallback = true;
  3207. continue;
  3208. }
  3209. } else {
  3210. fallback = true;
  3211. continue;
  3212. }
  3213. }
  3214.  
  3215. if (fallback && mediaStream.stop) mediaStream.stop();
  3216. }
  3217.  
  3218. // this object is used for pre-recorded media streaming!
  3219.  
  3220. function Streamer(connection) {
  3221. var prefix = !!navigator.webkitGetUserMedia ? '' : 'moz';
  3222. var self = this;
  3223.  
  3224. self.stream = streamPreRecordedMedia;
  3225.  
  3226. window.MediaSource = window.MediaSource || window.WebKitMediaSource;
  3227. if (!window.MediaSource) throw 'Chrome >=M28 (or Firefox with flag "media.mediasource.enabled=true") is mandatory to test this experiment.';
  3228.  
  3229. function streamPreRecordedMedia(file) {
  3230. if (!self.push) throw '<push> method is mandatory.';
  3231.  
  3232. var reader = new window.FileReader();
  3233. reader.readAsArrayBuffer(file);
  3234. reader.onload = function (e) {
  3235. startStreaming(new window.Blob([new window.Uint8Array(e.target.result)]));
  3236. };
  3237.  
  3238. var sourceBuffer, mediaSource = new MediaSource();
  3239. mediaSource.addEventListener(prefix + 'sourceopen', function () {
  3240. sourceBuffer = mediaSource.addSourceBuffer('video/webm; codecs="vorbis,vp8"');
  3241. log('MediaSource readyState: <', this.readyState, '>');
  3242. }, false);
  3243.  
  3244. mediaSource.addEventListener(prefix + 'sourceended', function () {
  3245. log('MediaSource readyState: <', this.readyState, '>');
  3246. }, false);
  3247.  
  3248. function startStreaming(blob) {
  3249. if (!blob) return;
  3250. var size = blob.size,
  3251. startIndex = 0,
  3252. plus = 3000;
  3253.  
  3254. log('one chunk size: <', plus, '>');
  3255.  
  3256. function inner_streamer() {
  3257. reader = new window.FileReader();
  3258. reader.onload = function (e) {
  3259. self.push(new window.Uint8Array(e.target.result));
  3260.  
  3261. startIndex += plus;
  3262. if (startIndex <= size) {
  3263. setTimeout(inner_streamer, connection.chunkInterval || 100);
  3264. } else {
  3265. self.push({
  3266. end: true
  3267. });
  3268. }
  3269. };
  3270. reader.readAsArrayBuffer(blob.slice(startIndex, startIndex + plus));
  3271. }
  3272.  
  3273. inner_streamer();
  3274. }
  3275.  
  3276. startStreaming();
  3277. }
  3278.  
  3279. self.receive = receive;
  3280.  
  3281. function receive() {
  3282. var mediaSource = new MediaSource();
  3283.  
  3284. self.video.src = window.URL.createObjectURL(mediaSource);
  3285. mediaSource.addEventListener(prefix + 'sourceopen', function () {
  3286. self.receiver = mediaSource.addSourceBuffer('video/webm; codecs="vorbis,vp8"');
  3287. self.mediaSource = mediaSource;
  3288.  
  3289. log('MediaSource readyState: <', this.readyState, '>');
  3290. }, false);
  3291.  
  3292.  
  3293. mediaSource.addEventListener(prefix + 'sourceended', function () {
  3294. warn('MediaSource readyState: <', this.readyState, '>');
  3295. }, false);
  3296. }
  3297.  
  3298. this.append = function (data) {
  3299. var that = this;
  3300. if (!self.receiver)
  3301. return setTimeout(function () {
  3302. that.append(data);
  3303. });
  3304.  
  3305. try {
  3306. var uint8array = new window.Uint8Array(data);
  3307. self.receiver.appendBuffer(uint8array);
  3308. } catch (e) {
  3309. error('Pre-recorded media streaming:', e);
  3310. }
  3311. };
  3312.  
  3313. this.end = function () {
  3314. self.mediaSource.endOfStream();
  3315. };
  3316. }
  3317.  
  3318. function setDefaults(connection) {
  3319. // www.RTCMultiConnection.org/docs/onmessage/
  3320. connection.onmessage = function (e) {
  3321. log('onmessage', toStr(e));
  3322. };
  3323.  
  3324. // www.RTCMultiConnection.org/docs/onopen/
  3325. connection.onopen = function (e) {
  3326. log('Data connection is opened between you and', e.userid);
  3327. };
  3328.  
  3329. // www.RTCMultiConnection.org/docs/onerror/
  3330. connection.onerror = function (e) {
  3331. error(onerror, toStr(e));
  3332. };
  3333.  
  3334. // www.RTCMultiConnection.org/docs/onclose/
  3335. connection.onclose = function (e) {
  3336. warn('onclose', toStr(e));
  3337. };
  3338.  
  3339. var progressHelper = {};
  3340.  
  3341. // www.RTCMultiConnection.org/docs/body/
  3342. connection.body = document.body || document.documentElement;
  3343.  
  3344. // www.RTCMultiConnection.org/docs/autoSaveToDisk/
  3345. // to make sure file-saver dialog is not invoked.
  3346. connection.autoSaveToDisk = false;
  3347.  
  3348. // www.RTCMultiConnection.org/docs/onFileStart/
  3349. connection.onFileStart = function (file) {
  3350. var div = document.createElement('div');
  3351. div.title = file.name;
  3352. div.innerHTML = '<label>0%</label> <progress></progress>';
  3353. connection.body.insertBefore(div, connection.body.firstChild);
  3354. progressHelper[file.uuid] = {
  3355. div: div,
  3356. progress: div.querySelector('progress'),
  3357. label: div.querySelector('label')
  3358. };
  3359. progressHelper[file.uuid].progress.max = file.maxChunks;
  3360. };
  3361.  
  3362. // www.RTCMultiConnection.org/docs/onFileProgress/
  3363. connection.onFileProgress = function (chunk) {
  3364. var helper = progressHelper[chunk.uuid];
  3365. if (!helper) return;
  3366. helper.progress.value = chunk.currentPosition || chunk.maxChunks || helper.progress.max;
  3367. updateLabel(helper.progress, helper.label);
  3368. };
  3369.  
  3370. // www.RTCMultiConnection.org/docs/onFileEnd/
  3371. connection.onFileEnd = function (file) {
  3372. if (progressHelper[file.uuid]) progressHelper[file.uuid].div.innerHTML = '<a href="' + file.url + '" target="_blank" download="' + file.name + '">' + file.name + '</a>';
  3373.  
  3374. // for backward compatibility
  3375. if (connection.onFileSent || connection.onFileReceived) {
  3376. warn('Now, "autoSaveToDisk" is false. Read more here: http://www.RTCMultiConnection.org/docs/autoSaveToDisk/');
  3377. if (connection.onFileSent) connection.onFileSent(file, file.uuid);
  3378. if (connection.onFileReceived) connection.onFileReceived(file.name, file);
  3379. }
  3380. };
  3381.  
  3382. function updateLabel(progress, label) {
  3383. if (progress.position == -1) return;
  3384. var position = +progress.position.toFixed(2).split('.')[1] || 100;
  3385. label.innerHTML = position + '%';
  3386. }
  3387.  
  3388. // www.RTCMultiConnection.org/docs/dontAttachStream/
  3389. connection.dontAttachStream = false;
  3390.  
  3391. // www.RTCMultiConnection.org/docs/onstream/
  3392. connection.onstream = function (e) {
  3393. connection.body.insertBefore(e.mediaElement, connection.body.firstChild);
  3394. };
  3395.  
  3396. // www.RTCMultiConnection.org/docs/onstreamended/
  3397. connection.onstreamended = function (e) {
  3398. if (e.mediaElement && e.mediaElement.parentNode) {
  3399. e.mediaElement.parentNode.removeChild(e.mediaElement);
  3400. }
  3401. };
  3402.  
  3403. // www.RTCMultiConnection.org/docs/onmute/
  3404. connection.onmute = function (e) {
  3405. log('onmute', e);
  3406. if (e.isVideo && e.mediaElement) {
  3407. e.mediaElement.pause();
  3408. e.mediaElement.setAttribute('poster', e.snapshot || 'https://www.webrtc-experiment.com/images/muted.png');
  3409. }
  3410. if (e.isAudio && e.mediaElement) {
  3411. e.mediaElement.muted = true;
  3412. }
  3413. };
  3414.  
  3415. // www.RTCMultiConnection.org/docs/onunmute/
  3416. connection.onunmute = function (e) {
  3417. log('onunmute', e);
  3418. if (e.isVideo && e.mediaElement) {
  3419. e.mediaElement.play();
  3420. e.mediaElement.removeAttribute('poster');
  3421. }
  3422. if (e.isAudio && e.mediaElement) {
  3423. e.mediaElement.muted = false;
  3424. }
  3425. };
  3426.  
  3427. // www.RTCMultiConnection.org/docs/onleave/
  3428. connection.onleave = function (e) {
  3429. log('onleave', toStr(e));
  3430. };
  3431.  
  3432. connection.token = function () {
  3433. // suggested by @rvulpescu from #154
  3434. if (window.crypto) {
  3435. var a = window.crypto.getRandomValues(new Uint32Array(3)),
  3436. token = '';
  3437. for (var i = 0, l = a.length; i < l; i++) token += a[i].toString(36);
  3438. return token;
  3439. } else {
  3440. return (Math.random() * new Date().getTime()).toString(36).replace(/\./g, '');
  3441. }
  3442. };
  3443.  
  3444. // www.RTCMultiConnection.org/docs/userid/
  3445. connection.userid = connection.token();
  3446.  
  3447. // www.RTCMultiConnection.org/docs/peers/
  3448. connection.peers = {};
  3449. connection.peers[connection.userid] = {
  3450. drop: function () {
  3451. connection.drop();
  3452. },
  3453. renegotiate: function () {
  3454. },
  3455. addStream: function () {
  3456. },
  3457. hold: function () {
  3458. },
  3459. unhold: function () {
  3460. },
  3461. changeBandwidth: function () {
  3462. },
  3463. sharePartOfScreen: function () {
  3464. }
  3465. };
  3466.  
  3467. connection._skip = ['stop', 'mute', 'unmute', '_private'];
  3468.  
  3469. // www.RTCMultiConnection.org/docs/streams/
  3470. connection.streams = {
  3471. mute: function (session) {
  3472. this._private(session, true);
  3473. },
  3474. unmute: function (session) {
  3475. this._private(session, false);
  3476. },
  3477. _private: function (session, enabled) {
  3478. // implementation from #68
  3479. for (var stream in this) {
  3480. if (connection._skip.indexOf(stream) == -1) {
  3481. this[stream]._private(session, enabled);
  3482. }
  3483. }
  3484. },
  3485. stop: function (type) {
  3486. // connection.streams.stop('local');
  3487. var _stream;
  3488. for (var stream in this) {
  3489. if (stream != 'stop' && stream != 'mute' && stream != 'unmute' && stream != '_private') {
  3490. _stream = this[stream];
  3491.  
  3492. if (!type) _stream.stop();
  3493.  
  3494. if (type == 'local' && _stream.type == 'local')
  3495. _stream.stop();
  3496.  
  3497. if (type == 'remote' && _stream.type == 'remote')
  3498. _stream.stop();
  3499. }
  3500. }
  3501. }
  3502. };
  3503.  
  3504. // this array is aimed to store all renegotiated streams' session-types
  3505. connection.renegotiatedSessions = [];
  3506.  
  3507. // www.RTCMultiConnection.org/docs/channels/
  3508. connection.channels = {};
  3509.  
  3510. // www.RTCMultiConnection.org/docs/extra/
  3511. connection.extra = {};
  3512.  
  3513. // www.RTCMultiConnection.org/docs/session/
  3514. connection.session = {
  3515. audio: true,
  3516. video: true
  3517. };
  3518.  
  3519. // www.RTCMultiConnection.org/docs/bandwidth/
  3520. connection.bandwidth = {
  3521. screen: 300 // 300kbps (old workaround!)
  3522. };
  3523.  
  3524. connection.sdpConstraints = {};
  3525. connection.mediaConstraints = {};
  3526. connection.optionalArgument = {};
  3527. connection.dataChannelDict = {};
  3528.  
  3529. var iceServers = [];
  3530.  
  3531. if (isFirefox) {
  3532. iceServers.push({
  3533. url: 'stun:23.21.150.121'
  3534. });
  3535.  
  3536. iceServers.push({
  3537. url: 'stun:stun.services.mozilla.com'
  3538. });
  3539. }
  3540.  
  3541. if (isChrome) {
  3542. iceServers.push({
  3543. url: 'stun:stun.l.google.com:19302'
  3544. });
  3545.  
  3546. iceServers.push({
  3547. url: 'stun:stun.anyfirewall.com:3478'
  3548. });
  3549. }
  3550.  
  3551. if (isChrome && chromeVersion < 28) {
  3552. iceServers.push({
  3553. url: 'turn:homeo@turn.bistri.com:80?transport=udp',
  3554. credential: 'homeo'
  3555. });
  3556.  
  3557. iceServers.push({
  3558. url: 'turn:homeo@turn.bistri.com:80?transport=tcp',
  3559. credential: 'homeo'
  3560. });
  3561. }
  3562.  
  3563. if (isChrome && chromeVersion >= 28) {
  3564. iceServers.push({
  3565. url: 'turn:turn.bistri.com:80?transport=udp',
  3566. credential: 'homeo',
  3567. username: 'homeo'
  3568. });
  3569.  
  3570. iceServers.push({
  3571. url: 'turn:turn.bistri.com:80?transport=tcp',
  3572. credential: 'homeo',
  3573. username: 'homeo'
  3574. });
  3575.  
  3576. iceServers.push({
  3577. url: 'turn:turn.anyfirewall.com:443?transport=tcp',
  3578. credential: 'webrtc',
  3579. username: 'webrtc'
  3580. });
  3581. }
  3582. connection.iceServers = iceServers;
  3583.  
  3584. // www.RTCMultiConnection.org/docs/preferSCTP/
  3585. connection.preferSCTP = isFirefox || chromeVersion >= 32 ? true : false;
  3586. connection.chunkInterval = isFirefox || chromeVersion >= 32 ? 100 : 500; // 500ms for RTP and 100ms for SCTP
  3587. connection.chunkSize = isFirefox || chromeVersion >= 32 ? 13 * 1000 : 1000; // 1000 chars for RTP and 13000 chars for SCTP
  3588.  
  3589. if (isFirefox) {
  3590. connection.preferSCTP = true; // FF supports only SCTP!
  3591. }
  3592.  
  3593. // www.RTCMultiConnection.org/docs/fakeDataChannels/
  3594. connection.fakeDataChannels = false;
  3595.  
  3596. // www.RTCMultiConnection.org/docs/UA/
  3597. connection.UA = {
  3598. Firefox: isFirefox,
  3599. Chrome: isChrome,
  3600. Mobile: isMobileDevice,
  3601. Version: chromeVersion,
  3602. NodeWebkit: isNodeWebkit
  3603. };
  3604.  
  3605. // file queue: to store previous file objects in memory;
  3606. // and stream over newly connected peers
  3607. // www.RTCMultiConnection.org/docs/fileQueue/
  3608. connection.fileQueue = {};
  3609.  
  3610. // www.RTCMultiConnection.org/docs/media/
  3611. connection.media = {
  3612. min: function (width, height) {
  3613. this.minWidth = width;
  3614. this.minHeight = height;
  3615. },
  3616. minWidth: 640,
  3617. minHeight: 360,
  3618. max: function (width, height) {
  3619. this.maxWidth = width;
  3620. this.maxHeight = height;
  3621. },
  3622. maxWidth: 1280,
  3623. maxHeight: 720,
  3624. bandwidth: 256,
  3625. minFrameRate: 1,
  3626. maxFrameRate: 30,
  3627. minAspectRatio: 1.77
  3628. };
  3629.  
  3630. // www.RTCMultiConnection.org/docs/candidates/
  3631. connection.candidates = {
  3632. host: true,
  3633. relay: true,
  3634. reflexive: true
  3635. };
  3636.  
  3637. // www.RTCMultiConnection.org/docs/attachStreams/
  3638. connection.attachStreams = [];
  3639.  
  3640. // www.RTCMultiConnection.org/docs/detachStreams/
  3641. connection.detachStreams = [];
  3642.  
  3643. // www.RTCMultiConnection.org/docs/maxParticipantsAllowed/
  3644. connection.maxParticipantsAllowed = 256;
  3645.  
  3646. // www.RTCMultiConnection.org/docs/direction/
  3647. // 'many-to-many' / 'one-to-many' / 'one-to-one' / 'one-way'
  3648. connection.direction = 'many-to-many';
  3649.  
  3650. connection._getStream = function (e) {
  3651. return {
  3652. rtcMultiConnection: e.rtcMultiConnection,
  3653. streamObject: e.streamObject,
  3654. stream: e.stream,
  3655. session: e.session,
  3656. userid: e.userid,
  3657. streamid: e.streamid,
  3658. sockets: e.socket ? [e.socket] : [],
  3659. type: e.type,
  3660. mediaElement: e.mediaElement,
  3661. stop: function (forceToStopRemoteStream) {
  3662. this.sockets.forEach(function (socket) {
  3663. if (this.type == 'local') {
  3664. socket.send({
  3665. userid: this.rtcMultiConnection.userid,
  3666. extra: this.rtcMultiConnection.extra,
  3667. streamid: this.streamid,
  3668. stopped: true
  3669. });
  3670. }
  3671.  
  3672. if (this.type == 'remote' && !!forceToStopRemoteStream) {
  3673. socket.send({
  3674. userid: this.rtcMultiConnection.userid,
  3675. promptStreamStop: true,
  3676. streamid: this.streamid
  3677. });
  3678. }
  3679. });
  3680.  
  3681. var stream = this.stream;
  3682. if (stream && stream.stop) {
  3683. stopTracks(stream);
  3684. }
  3685. },
  3686. mute: function (session) {
  3687. this.muted = true;
  3688. this._private(session, true);
  3689. },
  3690. unmute: function (session) {
  3691. this.muted = false;
  3692. this._private(session, false);
  3693. },
  3694. _private: function (session, enabled) {
  3695. muteOrUnmute({
  3696. root: this,
  3697. session: session,
  3698. enabled: enabled,
  3699. stream: this.stream
  3700. });
  3701. },
  3702. startRecording: function (session) {
  3703. if (!session)
  3704. session = {
  3705. audio: true,
  3706. video: true
  3707. };
  3708.  
  3709. if (isFirefox) {
  3710. // https://www.webrtc-experiment.com/RecordRTC/AudioVideo-on-Firefox.html
  3711. session = { audio: true };
  3712. }
  3713.  
  3714. if (!window.RecordRTC) {
  3715. var self = this;
  3716. return loadScript('https://www.webrtc-experiment.com/RecordRTC.js', function () {
  3717. self.startRecording(session);
  3718. });
  3719. }
  3720.  
  3721. this.recorder = new MRecordRTC();
  3722. this.recorder.mediaType = session;
  3723. this.recorder.addStream(this.stream);
  3724. this.recorder.startRecording();
  3725. },
  3726. stopRecording: function (callback) {
  3727. this.recorder.stopRecording();
  3728. this.recorder.getBlob(function (blob) {
  3729. callback(blob.audio || blob.video, blob.video);
  3730. });
  3731. }
  3732. };
  3733. };
  3734.  
  3735. // new RTCMultiConnection().set({properties}).connect()
  3736. connection.set = function (properties) {
  3737. for (var property in properties) {
  3738. this[property] = properties[property];
  3739. }
  3740. return this;
  3741. };
  3742.  
  3743. // www.RTCMultiConnection.org/docs/firebase/
  3744. connection.firebase = 'chat';
  3745.  
  3746. // www.RTCMultiConnection.org/docs/onMediaError/
  3747. connection.onMediaError = function (_error) {
  3748. error(_error);
  3749. };
  3750.  
  3751. // www.RTCMultiConnection.org/docs/stats/
  3752. connection.stats = {
  3753. numberOfConnectedUsers: 0,
  3754. numberOfSessions: 0,
  3755. sessions: {}
  3756. };
  3757.  
  3758. // www.RTCMultiConnection.org/docs/getStats/
  3759. connection.getStats = function (callback) {
  3760. var numberOfConnectedUsers = 0;
  3761. for (var peer in connection.peers) {
  3762. numberOfConnectedUsers++;
  3763. }
  3764.  
  3765. connection.stats.numberOfConnectedUsers = numberOfConnectedUsers;
  3766.  
  3767. // numberOfSessions
  3768.  
  3769. if (callback) callback(connection.stats);
  3770. };
  3771.  
  3772. // www.RTCMultiConnection.org/docs/caniuse/
  3773. connection.caniuse = {
  3774. RTCPeerConnection: !!RTCPeerConnection,
  3775. getUserMedia: !!getUserMedia,
  3776. AudioContext: !!AudioContext,
  3777.  
  3778. // there is no way to check whether "getUserMedia" flag is enabled or not!
  3779. ScreenSharing: isChrome && chromeVersion >= 26 && location.protocol == 'https:',
  3780. checkIfScreenSharingFlagEnabled: function (callback) {
  3781. var warning;
  3782. if (isFirefox) {
  3783. warning = 'Screen sharing is NOT supported on Firefox.';
  3784. error(warning);
  3785. if (callback) callback(false);
  3786. }
  3787.  
  3788. if (location.protocol !== 'https:') {
  3789. warning = 'Screen sharing is NOT supported on ' + location.protocol + ' Try https!';
  3790. error(warning);
  3791. if (callback) return callback(false);
  3792. }
  3793.  
  3794. if (chromeVersion < 26) {
  3795. warning = 'Screen sharing support is suspicious!';
  3796. warn(warning);
  3797. }
  3798.  
  3799. var screen_constraints = {
  3800. video: {
  3801. mandatory: {
  3802. chromeMediaSource: 'screen'
  3803. }
  3804. }
  3805. };
  3806.  
  3807. var invocationInterval = 0,
  3808. stop;
  3809. (function selfInvoker() {
  3810. invocationInterval++;
  3811. if (!stop) setTimeout(selfInvoker, 10);
  3812. })();
  3813.  
  3814. navigator.webkitGetUserMedia(screen_constraints, onsuccess, onfailure);
  3815.  
  3816. function onsuccess(stream) {
  3817. if (stream.stop) {
  3818. stream.stop();
  3819. }
  3820.  
  3821. if (callback) {
  3822. callback(true);
  3823. }
  3824. }
  3825.  
  3826. function onfailure() {
  3827. stop = true;
  3828. if (callback) callback(invocationInterval > 5, warning);
  3829. }
  3830. },
  3831.  
  3832. RtpDataChannels: isChrome && chromeVersion >= 25,
  3833. SctpDataChannels: isChrome && chromeVersion >= 31
  3834. };
  3835.  
  3836. // www.RTCMultiConnection.org/docs/snapshots/
  3837. connection.snapshots = {};
  3838.  
  3839. // www.RTCMultiConnection.org/docs/takeSnapshot/
  3840. connection.takeSnapshot = function (userid, callback) {
  3841. for (var stream in connection.streams) {
  3842. stream = connection.streams[stream];
  3843. if (stream.userid == userid && stream.stream && stream.stream.getVideoTracks && stream.stream.getVideoTracks().length) {
  3844. var video = stream.streamObject.mediaElement;
  3845. var canvas = document.createElement('canvas');
  3846. canvas.width = video.videoWidth || video.clientWidth;
  3847. canvas.height = video.videoHeight || video.clientHeight;
  3848.  
  3849. var context = canvas.getContext('2d');
  3850. context.drawImage(video, 0, 0, canvas.width, canvas.height);
  3851.  
  3852. connection.snapshots[userid] = canvas.toDataURL();
  3853. callback && callback(connection.snapshots[userid]);
  3854. continue;
  3855. }
  3856. }
  3857. };
  3858.  
  3859. connection.saveToDisk = function (blob, fileName) {
  3860. if (blob.size && blob.type) FileSaver.SaveToDisk(URL.createObjectURL(blob), fileName || blob.name || blob.type.replace('/', '-') + blob.type.split('/')[1]);
  3861. else FileSaver.SaveToDisk(blob, fileName);
  3862. };
  3863.  
  3864. // www.WebRTC-Experiment.com/demos/MediaStreamTrack.getSources.html
  3865. connection._mediaSources = {};
  3866.  
  3867. // www.RTCMultiConnection.org/docs/selectDevices/
  3868. connection.selectDevices = function (device1, device2) {
  3869. if (device1) select(this.devices[device1]);
  3870. if (device2) select(this.devices[device2]);
  3871.  
  3872. function select(device) {
  3873. if (!device) return;
  3874. connection._mediaSources[device.kind] = device.id;
  3875. }
  3876. };
  3877.  
  3878. // www.RTCMultiConnection.org/docs/devices/
  3879. connection.devices = {};
  3880.  
  3881. // www.RTCMultiConnection.org/docs/getDevices/
  3882. connection.getDevices = function (callback) {
  3883. if (!!window.MediaStreamTrack && !!MediaStreamTrack.getSources) {
  3884. MediaStreamTrack.getSources(function (media_sources) {
  3885. var sources = [];
  3886. for (var i = 0; i < media_sources.length; i++) {
  3887. sources.push(media_sources[i]);
  3888. }
  3889.  
  3890. getAllUserMedias(sources);
  3891.  
  3892. if (callback) callback(connection.devices);
  3893. });
  3894.  
  3895. var index = 0;
  3896.  
  3897. var devicesFetched = {};
  3898.  
  3899. function getAllUserMedias(media_sources) {
  3900. var media_source = media_sources[index];
  3901. if (!media_source) return;
  3902.  
  3903. // to prevent duplicated devices to be fetched.
  3904. if (devicesFetched[media_source.id]) {
  3905. index++;
  3906. return getAllUserMedias(media_sources);
  3907. }
  3908. devicesFetched[media_source.id] = media_source;
  3909.  
  3910. connection.devices[media_source.id] = media_source;
  3911.  
  3912. index++;
  3913. getAllUserMedias(media_sources);
  3914. }
  3915. }
  3916. };
  3917.  
  3918. // www.RTCMultiConnection.org/docs/onCustomMessage/
  3919. connection.onCustomMessage = function (message) {
  3920. log('Custom message', message);
  3921. };
  3922.  
  3923. // www.RTCMultiConnection.org/docs/ondrop/
  3924. connection.ondrop = function (droppedBy) {
  3925. log('Media connection is dropped by ' + droppedBy);
  3926. };
  3927.  
  3928. // www.RTCMultiConnection.org/docs/drop/
  3929. connection.drop = function (config) {
  3930. config = config || {};
  3931. this.attachStreams = [];
  3932.  
  3933. // "drop" should detach all local streams
  3934. for (var stream in this.streams) {
  3935. if (this._skip.indexOf(stream) == -1) {
  3936. stream = this.streams[stream];
  3937. if (stream.type == 'local') {
  3938. this.detachStreams.push(stream.streamid);
  3939. this.onstreamended(stream.streamObject);
  3940. } else this.onstreamended(stream.streamObject);
  3941. }
  3942. }
  3943.  
  3944. // www.RTCMultiConnection.org/docs/sendCustomMessage/
  3945. this.sendCustomMessage({
  3946. drop: true,
  3947. dontRenegotiate: typeof config.renegotiate == 'undefined' ? true : config.renegotiate
  3948. });
  3949. };
  3950.  
  3951. // used for SoundMeter
  3952. if (!!window.AudioContext) {
  3953. connection._audioContext = new AudioContext();
  3954. }
  3955.  
  3956. // www.RTCMultiConnection.org/docs/language/ (to see list of all supported languages)
  3957. connection.language = 'en';
  3958.  
  3959. // www.RTCMultiConnection.org/docs/autoTranslateText/
  3960. connection.autoTranslateText = false;
  3961.  
  3962. // please use your own API key; if possible
  3963. connection.googKey = 'AIzaSyCUmCjvKRb-kOYrnoL2xaXb8I-_JJeKpf0';
  3964.  
  3965. // www.RTCMultiConnection.org/docs/Translator/
  3966. connection.Translator = {
  3967. TranslateText: function (text, callback) {
  3968. // if(location.protocol === 'https:') return callback(text);
  3969.  
  3970. var newScript = document.createElement('script');
  3971. newScript.type = 'text/javascript';
  3972.  
  3973. var sourceText = encodeURIComponent(text); // escape
  3974.  
  3975. var randomNumber = 'method' + connection.token();
  3976. window[randomNumber] = function (response) {
  3977. if (response.data && response.data.translations[0] && callback) {
  3978. callback(response.data.translations[0].translatedText);
  3979. }
  3980. };
  3981.  
  3982. var source = 'https://www.googleapis.com/language/translate/v2?key=' + connection.googKey + '&target=' + (connection.language || 'en-US') + '&callback=window.' + randomNumber + '&q=' + sourceText;
  3983. newScript.src = source;
  3984. document.getElementsByTagName('head')[0].appendChild(newScript);
  3985. }
  3986. };
  3987.  
  3988. // you can easily override it by setting it NULL!
  3989. connection.setDefaultEventsForMediaElement = function (mediaElement, streamid) {
  3990. mediaElement.onpause = function () {
  3991. if (connection.streams[streamid] && !connection.streams[streamid].muted) {
  3992. connection.streams[streamid].mute();
  3993. }
  3994. };
  3995.  
  3996. // todo: need to make sure that "onplay" EVENT doesn't play self-voice!
  3997. mediaElement.onplay = function () {
  3998. if (connection.streams[streamid] && connection.streams[streamid].muted) {
  3999. connection.streams[streamid].unmute();
  4000. }
  4001. };
  4002.  
  4003. var volumeChangeEventFired = false;
  4004. mediaElement.onvolumechange = function () {
  4005. if (!volumeChangeEventFired) {
  4006. volumeChangeEventFired = true;
  4007. setTimeout(function () {
  4008. var root = connection.streams[streamid];
  4009. connection.streams[streamid].sockets.forEach(function (socket) {
  4010. socket.send({
  4011. userid: connection.userid,
  4012. streamid: root.streamid,
  4013. isVolumeChanged: true,
  4014. volume: mediaElement.volume
  4015. });
  4016. });
  4017. volumeChangeEventFired = false;
  4018. }, 2000);
  4019. }
  4020. };
  4021. };
  4022.  
  4023. connection.localStreamids = [];
  4024.  
  4025. // www.RTCMultiConnection.org/docs/onMediaFile/
  4026. connection.onMediaFile = function (e) {
  4027. log('onMediaFile', e);
  4028. connection.body.appendChild(e.mediaElement);
  4029. };
  4030.  
  4031. // this object stores pre-recorded media streaming uids
  4032. // multiple pre-recorded media files can be streamed concurrently.
  4033. connection.preRecordedMedias = {};
  4034.  
  4035. // www.RTCMultiConnection.org/docs/shareMediaFile/
  4036. // this method handles pre-recorded media streaming
  4037. connection.shareMediaFile = function (file, video, streamerid) {
  4038. if (file && (typeof file.size == 'undefined' || typeof file.type == 'undefined')) throw 'You MUST attach file using input[type=file] or pass a Blob.';
  4039.  
  4040. warn('Pre-recorded media streaming is added as experimental feature.');
  4041.  
  4042. video = video || document.createElement('video');
  4043.  
  4044. video.autoplay = true;
  4045. video.controls = true;
  4046.  
  4047. streamerid = streamerid || connection.token();
  4048.  
  4049. var streamer = new Streamer(this);
  4050.  
  4051. streamer.push = function (chunk) {
  4052. connection.send({
  4053. preRecordedMediaChunk: true,
  4054. chunk: chunk,
  4055. streamerid: streamerid
  4056. });
  4057. };
  4058.  
  4059. if (file) {
  4060. streamer.stream(file);
  4061. }
  4062.  
  4063. streamer.video = video;
  4064.  
  4065. streamer.receive();
  4066.  
  4067. connection.preRecordedMedias[streamerid] = {
  4068. video: video,
  4069. streamer: streamer,
  4070. onData: function (data) {
  4071. if (data.end) this.streamer.end();
  4072. else this.streamer.append(data);
  4073. }
  4074. };
  4075.  
  4076. connection.onMediaFile({
  4077. mediaElement: video,
  4078. userid: connection.userid,
  4079. extra: connection.extra
  4080. });
  4081.  
  4082. return streamerid;
  4083. };
  4084.  
  4085. // www.RTCMultiConnection.org/docs/onpartofscreen/
  4086. connection.onpartofscreen = function (e) {
  4087. var image = document.createElement('img');
  4088. image.src = e.screenshot;
  4089. connection.body.appendChild(image);
  4090. };
  4091.  
  4092. connection.skipLogs = function () {
  4093. log = error = warn = function () {
  4094. };
  4095. };
  4096.  
  4097. // www.RTCMultiConnection.org/docs/hold/
  4098. connection.hold = function (mLine) {
  4099. for (var peer in connection.peers) {
  4100. connection.peers[peer].hold(mLine);
  4101. }
  4102. };
  4103.  
  4104. // www.RTCMultiConnection.org/docs/onhold/
  4105. connection.onhold = function (track) {
  4106. log('onhold', track);
  4107.  
  4108. if (track.kind != 'audio') {
  4109. track.mediaElement.pause();
  4110. track.mediaElement.setAttribute('poster', track.screenshot || 'https://www.webrtc-experiment.com/images/muted.png');
  4111. }
  4112. if (track.kind == 'audio') {
  4113. track.mediaElement.muted = true;
  4114. }
  4115. };
  4116.  
  4117. // www.RTCMultiConnection.org/docs/unhold/
  4118. connection.unhold = function (mLine) {
  4119. for (var peer in connection.peers) {
  4120. connection.peers[peer].unhold(mLine);
  4121. }
  4122. };
  4123.  
  4124. // www.RTCMultiConnection.org/docs/onunhold/
  4125. connection.onunhold = function (track) {
  4126. log('onunhold', track);
  4127.  
  4128. if (track.kind != 'audio') {
  4129. track.mediaElement.play();
  4130. track.mediaElement.removeAttribute('poster');
  4131. }
  4132. if (track.kind != 'audio') {
  4133. track.mediaElement.muted = false;
  4134. }
  4135. };
  4136.  
  4137. connection.sharePartOfScreen = function (args) {
  4138. for (var peer in connection.peers) {
  4139. connection.peers[peer].sharePartOfScreen(args);
  4140. }
  4141. };
  4142.  
  4143. connection.pausePartOfScreenSharing = function () {
  4144. for (var peer in connection.peers) {
  4145. connection.peers[peer].pausePartOfScreenSharing = true;
  4146. }
  4147. };
  4148.  
  4149. connection.stopPartOfScreenSharing = function () {
  4150. for (var peer in connection.peers) {
  4151. connection.peers[peer].stopPartOfScreenSharing = true;
  4152. }
  4153. };
  4154.  
  4155. // it is false because workaround that is used to capture connections' failures
  4156. // affects renegotiation scenarios!
  4157. // todo: fix it!
  4158. connection.autoReDialOnFailure = false;
  4159.  
  4160. connection.isInitiator = false;
  4161. }
  4162. })();