From e5f5fb0dcb765566adc67a6656fe3fdb91c677d0 Mon Sep 17 00:00:00 2001 From: Dimitrios Ververidis Date: Mon, 22 Nov 2021 08:02:25 +0200 Subject: [PATCH 01/22] In the instructions, you forgot to copy the dist file --- docs/getting-started-local.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/getting-started-local.md b/docs/getting-started-local.md index 06b83c15..15a7c388 100644 --- a/docs/getting-started-local.md +++ b/docs/getting-started-local.md @@ -60,10 +60,12 @@ Let's copy the example server that comes with networked-aframe into our project. # MacOS & Linux cp -r ./node_modules/networked-aframe/server/ ./server/ cp -r ./node_modules/networked-aframe/examples/ ./examples/ +cp -r ./node_modules/networked-aframe/dist/ ./examples/dist/ /e # Windows robocopy .\node_modules\networked-aframe\server\ .\server\ /e robocopy .\node_modules\networked-aframe\examples\ .\examples\ /e +robocopy .\node_modules\networked-aframe\dist\ .\examples\dist\ /e ``` You'll now see another new folder: `server/`. Inside it you'll see `easyrtc-server.js` which is the server code. From dcde012a2f010f677ed589314bd74aebef6af583 Mon Sep 17 00:00:00 2001 From: Dimitrios Ververidis Date: Mon, 24 Jan 2022 07:33:22 +0200 Subject: [PATCH 02/22] In the instructions, you forgot to copy the dist file --- dist/networked-aframe.js | 10 +- docs/getting-started-local.md | 2 +- examples/basic-video-mediapiped.html | 394 +++++++++++++++++++++++++++ 3 files changed, 400 insertions(+), 6 deletions(-) create mode 100644 examples/basic-video-mediapiped.html diff --git a/dist/networked-aframe.js b/dist/networked-aframe.js index 9fcafb4d..7b367e29 100644 --- a/dist/networked-aframe.js +++ b/dist/networked-aframe.js @@ -214,7 +214,7 @@ eval("\n\nfunction _classCallCheck(instance, Constructor) { if (!(instance insta /***/ (function(module, exports, __webpack_require__) { "use strict"; -eval("\n\nfunction _typeof(obj) { \"@babel/helpers - typeof\"; if (typeof Symbol === \"function\" && typeof Symbol.iterator === \"symbol\") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === \"function\" && obj.constructor === Symbol && obj !== Symbol.prototype ? \"symbol\" : typeof obj; }; } return _typeof(obj); }\n\nfunction _slicedToArray(arr, i) { return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest(); }\n\nfunction _nonIterableRest() { throw new TypeError(\"Invalid attempt to destructure non-iterable instance.\\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.\"); }\n\nfunction _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === \"string\") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === \"Object\" && o.constructor) n = o.constructor.name; if (n === \"Map\" || n === \"Set\") return Array.from(o); if (n === \"Arguments\" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }\n\nfunction _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) { arr2[i] = arr[i]; } return arr2; }\n\nfunction _iterableToArrayLimit(arr, i) { if (typeof Symbol === \"undefined\" || !(Symbol.iterator in Object(arr))) return; var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i[\"return\"] != null) _i[\"return\"](); } finally { if (_d) throw _e; } } return _arr; }\n\nfunction _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }\n\nfunction _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError(\"Cannot call a class as a function\"); } }\n\nfunction _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if (\"value\" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }\n\nfunction _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }\n\nfunction _inherits(subClass, superClass) { if (typeof superClass !== \"function\" && superClass !== null) { throw new TypeError(\"Super expression must either be null or a function\"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); if (superClass) _setPrototypeOf(subClass, superClass); }\n\nfunction _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); }\n\nfunction _createSuper(Derived) { var hasNativeReflectConstruct = _isNativeReflectConstruct(); return function _createSuperInternal() { var Super = _getPrototypeOf(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = _getPrototypeOf(this).constructor; result = Reflect.construct(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return _possibleConstructorReturn(this, result); }; }\n\nfunction _possibleConstructorReturn(self, call) { if (call && (_typeof(call) === \"object\" || typeof call === \"function\")) { return call; } return _assertThisInitialized(self); }\n\nfunction _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError(\"this hasn't been initialised - super() hasn't been called\"); } return self; }\n\nfunction _isNativeReflectConstruct() { if (typeof Reflect === \"undefined\" || !Reflect.construct) return false; if (Reflect.construct.sham) return false; if (typeof Proxy === \"function\") return true; try { Date.prototype.toString.call(Reflect.construct(Date, [], function () {})); return true; } catch (e) { return false; } }\n\nfunction _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); }\n\n/* global NAF */\nvar NoOpAdapter = __webpack_require__(/*! ./NoOpAdapter */ \"./src/adapters/NoOpAdapter.js\");\n\nvar EasyRtcAdapter = /*#__PURE__*/function (_NoOpAdapter) {\n _inherits(EasyRtcAdapter, _NoOpAdapter);\n\n var _super = _createSuper(EasyRtcAdapter);\n\n function EasyRtcAdapter(easyrtc) {\n var _this;\n\n _classCallCheck(this, EasyRtcAdapter);\n\n _this = _super.call(this);\n _this.easyrtc = easyrtc || window.easyrtc;\n _this.app = \"default\";\n _this.room = \"default\";\n _this.mediaStreams = {};\n _this.remoteClients = {};\n _this.pendingMediaRequests = new Map();\n _this.serverTimeRequests = 0;\n _this.timeOffsets = [];\n _this.avgTimeOffset = 0;\n\n _this.easyrtc.setPeerOpenListener(function (clientId) {\n var clientConnection = _this.easyrtc.getPeerConnectionByUserId(clientId);\n\n _this.remoteClients[clientId] = clientConnection;\n });\n\n _this.easyrtc.setPeerClosedListener(function (clientId) {\n delete _this.remoteClients[clientId];\n });\n\n return _this;\n }\n\n _createClass(EasyRtcAdapter, [{\n key: \"setServerUrl\",\n value: function setServerUrl(url) {\n this.easyrtc.setSocketUrl(url);\n }\n }, {\n key: \"setApp\",\n value: function setApp(appName) {\n this.app = appName;\n }\n }, {\n key: \"setRoom\",\n value: function setRoom(roomName) {\n this.room = roomName;\n this.easyrtc.joinRoom(roomName, null);\n } // options: { datachannel: bool, audio: bool, video: bool }\n\n }, {\n key: \"setWebRtcOptions\",\n value: function setWebRtcOptions(options) {\n // this.easyrtc.enableDebug(true);\n this.easyrtc.enableDataChannels(options.datachannel);\n this.easyrtc.enableVideo(options.video);\n this.easyrtc.enableAudio(options.audio); // TODO receive(audio|video) options ?\n\n this.easyrtc.enableVideoReceive(true);\n this.easyrtc.enableAudioReceive(true);\n }\n }, {\n key: \"setServerConnectListeners\",\n value: function setServerConnectListeners(successListener, failureListener) {\n this.connectSuccess = successListener;\n this.connectFailure = failureListener;\n }\n }, {\n key: \"setRoomOccupantListener\",\n value: function setRoomOccupantListener(occupantListener) {\n this.easyrtc.setRoomOccupantListener(function (roomName, occupants, primary) {\n occupantListener(occupants);\n });\n }\n }, {\n key: \"setDataChannelListeners\",\n value: function setDataChannelListeners(openListener, closedListener, messageListener) {\n this.easyrtc.setDataChannelOpenListener(openListener);\n this.easyrtc.setDataChannelCloseListener(closedListener);\n this.easyrtc.setPeerListener(messageListener);\n }\n }, {\n key: \"updateTimeOffset\",\n value: function updateTimeOffset() {\n var _this2 = this;\n\n var clientSentTime = Date.now() + this.avgTimeOffset;\n return fetch(document.location.href, {\n method: \"HEAD\",\n cache: \"no-cache\"\n }).then(function (res) {\n var precision = 1000;\n var serverReceivedTime = new Date(res.headers.get(\"Date\")).getTime() + precision / 2;\n var clientReceivedTime = Date.now();\n var serverTime = serverReceivedTime + (clientReceivedTime - clientSentTime) / 2;\n var timeOffset = serverTime - clientReceivedTime;\n _this2.serverTimeRequests++;\n\n if (_this2.serverTimeRequests <= 10) {\n _this2.timeOffsets.push(timeOffset);\n } else {\n _this2.timeOffsets[_this2.serverTimeRequests % 10] = timeOffset;\n }\n\n _this2.avgTimeOffset = _this2.timeOffsets.reduce(function (acc, offset) {\n return acc += offset;\n }, 0) / _this2.timeOffsets.length;\n\n if (_this2.serverTimeRequests > 10) {\n setTimeout(function () {\n return _this2.updateTimeOffset();\n }, 5 * 60 * 1000); // Sync clock every 5 minutes.\n } else {\n _this2.updateTimeOffset();\n }\n });\n }\n }, {\n key: \"connect\",\n value: function connect() {\n var _this3 = this;\n\n Promise.all([this.updateTimeOffset(), new Promise(function (resolve, reject) {\n _this3._connect(resolve, reject);\n })]).then(function (_ref) {\n var _ref2 = _slicedToArray(_ref, 2),\n _ = _ref2[0],\n clientId = _ref2[1];\n\n _this3._myRoomJoinTime = _this3._getRoomJoinTime(clientId);\n\n _this3.connectSuccess(clientId);\n })[\"catch\"](this.connectFailure);\n }\n }, {\n key: \"shouldStartConnectionTo\",\n value: function shouldStartConnectionTo(client) {\n return this._myRoomJoinTime <= client.roomJoinTime;\n }\n }, {\n key: \"startStreamConnection\",\n value: function startStreamConnection(clientId) {\n this.easyrtc.call(clientId, function (caller, media) {\n if (media === \"datachannel\") {\n NAF.log.write(\"Successfully started datachannel to \", caller);\n }\n }, function (errorCode, errorText) {\n NAF.log.error(errorCode, errorText);\n }, function (wasAccepted) {// console.log(\"was accepted=\" + wasAccepted);\n });\n }\n }, {\n key: \"closeStreamConnection\",\n value: function closeStreamConnection(clientId) {\n this.easyrtc.hangup(clientId);\n }\n }, {\n key: \"sendData\",\n value: function sendData(clientId, dataType, data) {\n // send via webrtc otherwise fallback to websockets\n this.easyrtc.sendData(clientId, dataType, data);\n }\n }, {\n key: \"sendDataGuaranteed\",\n value: function sendDataGuaranteed(clientId, dataType, data) {\n this.easyrtc.sendDataWS(clientId, dataType, data);\n }\n }, {\n key: \"broadcastData\",\n value: function broadcastData(dataType, data) {\n var roomOccupants = this.easyrtc.getRoomOccupantsAsMap(this.room); // Iterate over the keys of the easyrtc room occupants map.\n // getRoomOccupantsAsArray uses Object.keys which allocates memory.\n\n for (var roomOccupant in roomOccupants) {\n if (roomOccupants[roomOccupant] && roomOccupant !== this.easyrtc.myEasyrtcid) {\n // send via webrtc otherwise fallback to websockets\n this.easyrtc.sendData(roomOccupant, dataType, data);\n }\n }\n }\n }, {\n key: \"broadcastDataGuaranteed\",\n value: function broadcastDataGuaranteed(dataType, data) {\n var destination = {\n targetRoom: this.room\n };\n this.easyrtc.sendDataWS(destination, dataType, data);\n }\n }, {\n key: \"getConnectStatus\",\n value: function getConnectStatus(clientId) {\n var status = this.easyrtc.getConnectStatus(clientId);\n\n if (status == this.easyrtc.IS_CONNECTED) {\n return NAF.adapters.IS_CONNECTED;\n } else if (status == this.easyrtc.NOT_CONNECTED) {\n return NAF.adapters.NOT_CONNECTED;\n } else {\n return NAF.adapters.CONNECTING;\n }\n }\n }, {\n key: \"getMediaStream\",\n value: function getMediaStream(clientId) {\n var streamName = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : \"audio\";\n\n if (this.mediaStreams[clientId] && this.mediaStreams[clientId][streamName]) {\n NAF.log.write(\"Already had \".concat(streamName, \" for \").concat(clientId));\n return Promise.resolve(this.mediaStreams[clientId][streamName]);\n } else {\n NAF.log.write(\"Waiting on \".concat(streamName, \" for \").concat(clientId)); // Create initial pendingMediaRequests with audio|video alias\n\n if (!this.pendingMediaRequests.has(clientId)) {\n var _pendingMediaRequests = {};\n var audioPromise = new Promise(function (resolve, reject) {\n _pendingMediaRequests.audio = {\n resolve: resolve,\n reject: reject\n };\n })[\"catch\"](function (e) {\n return NAF.log.warn(\"\".concat(clientId, \" getMediaStream Audio Error\"), e);\n });\n _pendingMediaRequests.audio.promise = audioPromise;\n var videoPromise = new Promise(function (resolve, reject) {\n _pendingMediaRequests.video = {\n resolve: resolve,\n reject: reject\n };\n })[\"catch\"](function (e) {\n return NAF.log.warn(\"\".concat(clientId, \" getMediaStream Video Error\"), e);\n });\n _pendingMediaRequests.video.promise = videoPromise;\n this.pendingMediaRequests.set(clientId, _pendingMediaRequests);\n }\n\n var pendingMediaRequests = this.pendingMediaRequests.get(clientId); // Create initial pendingMediaRequests with streamName\n\n if (!pendingMediaRequests[streamName]) {\n var streamPromise = new Promise(function (resolve, reject) {\n pendingMediaRequests[streamName] = {\n resolve: resolve,\n reject: reject\n };\n })[\"catch\"](function (e) {\n return NAF.log.warn(\"\".concat(clientId, \" getMediaStream \\\"\").concat(streamName, \"\\\" Error\"), e);\n });\n pendingMediaRequests[streamName].promise = streamPromise;\n }\n\n return this.pendingMediaRequests.get(clientId)[streamName].promise;\n }\n }\n }, {\n key: \"setMediaStream\",\n value: function setMediaStream(clientId, stream, streamName) {\n var pendingMediaRequests = this.pendingMediaRequests.get(clientId); // return undefined if there is no entry in the Map\n\n var clientMediaStreams = this.mediaStreams[clientId] = this.mediaStreams[clientId] || {};\n\n if (streamName === 'default') {\n // Safari doesn't like it when you use a mixed media stream where one of the tracks is inactive, so we\n // split the tracks into two streams.\n // Add mediaStreams audio streamName alias\n var audioTracks = stream.getAudioTracks();\n\n if (audioTracks.length > 0) {\n var audioStream = new MediaStream();\n\n try {\n audioTracks.forEach(function (track) {\n return audioStream.addTrack(track);\n });\n clientMediaStreams.audio = audioStream;\n } catch (e) {\n NAF.log.warn(\"\".concat(clientId, \" setMediaStream \\\"audio\\\" alias Error\"), e);\n } // Resolve the promise for the user's media stream audio alias if it exists.\n\n\n if (pendingMediaRequests) pendingMediaRequests.audio.resolve(audioStream);\n } // Add mediaStreams video streamName alias\n\n\n var videoTracks = stream.getVideoTracks();\n\n if (videoTracks.length > 0) {\n var videoStream = new MediaStream();\n\n try {\n videoTracks.forEach(function (track) {\n return videoStream.addTrack(track);\n });\n clientMediaStreams.video = videoStream;\n } catch (e) {\n NAF.log.warn(\"\".concat(clientId, \" setMediaStream \\\"video\\\" alias Error\"), e);\n } // Resolve the promise for the user's media stream video alias if it exists.\n\n\n if (pendingMediaRequests) pendingMediaRequests.video.resolve(videoStream);\n }\n } else {\n clientMediaStreams[streamName] = stream; // Resolve the promise for the user's media stream by StreamName if it exists.\n\n if (pendingMediaRequests && pendingMediaRequests[streamName]) {\n pendingMediaRequests[streamName].resolve(stream);\n }\n }\n }\n }, {\n key: \"addLocalMediaStream\",\n value: function addLocalMediaStream(stream, streamName) {\n var easyrtc = this.easyrtc;\n streamName = streamName || stream.id;\n this.setMediaStream(\"local\", stream, streamName);\n easyrtc.register3rdPartyLocalMediaStream(stream, streamName); // Add local stream to existing connections\n\n Object.keys(this.remoteClients).forEach(function (clientId) {\n if (easyrtc.getConnectStatus(clientId) !== easyrtc.NOT_CONNECTED) {\n easyrtc.addStreamToCall(clientId, streamName);\n }\n });\n }\n }, {\n key: \"removeLocalMediaStream\",\n value: function removeLocalMediaStream(streamName) {\n this.easyrtc.closeLocalMediaStream(streamName);\n delete this.mediaStreams[\"local\"][streamName];\n }\n }, {\n key: \"enableMicrophone\",\n value: function enableMicrophone(enabled) {\n this.easyrtc.enableMicrophone(enabled);\n }\n }, {\n key: \"enableCamera\",\n value: function enableCamera(enabled) {\n this.easyrtc.enableCamera(enabled);\n }\n }, {\n key: \"disconnect\",\n value: function disconnect() {\n this.easyrtc.disconnect();\n }\n /**\n * Privates\n */\n\n }, {\n key: \"_connect\",\n value: function _connect(connectSuccess, connectFailure) {\n var that = this;\n this.easyrtc.setStreamAcceptor(this.setMediaStream.bind(this));\n this.easyrtc.setOnStreamClosed(function (clientId, stream, streamName) {\n delete this.mediaStreams[clientId][streamName];\n });\n\n if (that.easyrtc.audioEnabled || that.easyrtc.videoEnabled) {\n navigator.mediaDevices.getUserMedia({\n video: that.easyrtc.videoEnabled,\n audio: that.easyrtc.audioEnabled\n }).then(function (stream) {\n that.addLocalMediaStream(stream, \"default\");\n that.easyrtc.connect(that.app, connectSuccess, connectFailure);\n }, function (errorCode, errmesg) {\n NAF.log.error(errorCode, errmesg);\n });\n } else {\n that.easyrtc.connect(that.app, connectSuccess, connectFailure);\n }\n }\n }, {\n key: \"_getRoomJoinTime\",\n value: function _getRoomJoinTime(clientId) {\n var myRoomId = NAF.room;\n var joinTime = this.easyrtc.getRoomOccupantsAsMap(myRoomId)[clientId].roomJoinTime;\n return joinTime;\n }\n }, {\n key: \"getServerTime\",\n value: function getServerTime() {\n return Date.now() + this.avgTimeOffset;\n }\n }]);\n\n return EasyRtcAdapter;\n}(NoOpAdapter);\n\nmodule.exports = EasyRtcAdapter;\n\n//# sourceURL=webpack:///./src/adapters/EasyRtcAdapter.js?"); +eval("\n\nfunction _typeof(obj) { \"@babel/helpers - typeof\"; if (typeof Symbol === \"function\" && typeof Symbol.iterator === \"symbol\") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === \"function\" && obj.constructor === Symbol && obj !== Symbol.prototype ? \"symbol\" : typeof obj; }; } return _typeof(obj); }\n\nfunction _slicedToArray(arr, i) { return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest(); }\n\nfunction _nonIterableRest() { throw new TypeError(\"Invalid attempt to destructure non-iterable instance.\\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.\"); }\n\nfunction _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === \"string\") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === \"Object\" && o.constructor) n = o.constructor.name; if (n === \"Map\" || n === \"Set\") return Array.from(o); if (n === \"Arguments\" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }\n\nfunction _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) { arr2[i] = arr[i]; } return arr2; }\n\nfunction _iterableToArrayLimit(arr, i) { if (typeof Symbol === \"undefined\" || !(Symbol.iterator in Object(arr))) return; var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i[\"return\"] != null) _i[\"return\"](); } finally { if (_d) throw _e; } } return _arr; }\n\nfunction _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }\n\nfunction _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError(\"Cannot call a class as a function\"); } }\n\nfunction _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if (\"value\" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }\n\nfunction _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }\n\nfunction _inherits(subClass, superClass) { if (typeof superClass !== \"function\" && superClass !== null) { throw new TypeError(\"Super expression must either be null or a function\"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); if (superClass) _setPrototypeOf(subClass, superClass); }\n\nfunction _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); }\n\nfunction _createSuper(Derived) { var hasNativeReflectConstruct = _isNativeReflectConstruct(); return function _createSuperInternal() { var Super = _getPrototypeOf(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = _getPrototypeOf(this).constructor; result = Reflect.construct(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return _possibleConstructorReturn(this, result); }; }\n\nfunction _possibleConstructorReturn(self, call) { if (call && (_typeof(call) === \"object\" || typeof call === \"function\")) { return call; } return _assertThisInitialized(self); }\n\nfunction _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError(\"this hasn't been initialised - super() hasn't been called\"); } return self; }\n\nfunction _isNativeReflectConstruct() { if (typeof Reflect === \"undefined\" || !Reflect.construct) return false; if (Reflect.construct.sham) return false; if (typeof Proxy === \"function\") return true; try { Date.prototype.toString.call(Reflect.construct(Date, [], function () {})); return true; } catch (e) { return false; } }\n\nfunction _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); }\n\n/* global NAF */\nvar NoOpAdapter = __webpack_require__(/*! ./NoOpAdapter */ \"./src/adapters/NoOpAdapter.js\");\n\nvar EasyRtcAdapter = /*#__PURE__*/function (_NoOpAdapter) {\n _inherits(EasyRtcAdapter, _NoOpAdapter);\n\n var _super = _createSuper(EasyRtcAdapter);\n\n function EasyRtcAdapter(easyrtc) {\n var _this;\n\n _classCallCheck(this, EasyRtcAdapter);\n\n _this = _super.call(this);\n _this.easyrtc = easyrtc || window.easyrtc;\n _this.app = \"default\";\n _this.room = \"default\";\n _this.mediaStreams = {};\n _this.remoteClients = {};\n _this.pendingMediaRequests = new Map();\n _this.serverTimeRequests = 0;\n _this.timeOffsets = [];\n _this.avgTimeOffset = 0;\n\n _this.easyrtc.setPeerOpenListener(function (clientId) {\n var clientConnection = _this.easyrtc.getPeerConnectionByUserId(clientId);\n\n _this.remoteClients[clientId] = clientConnection;\n });\n\n _this.easyrtc.setPeerClosedListener(function (clientId) {\n delete _this.remoteClients[clientId];\n });\n\n return _this;\n }\n\n _createClass(EasyRtcAdapter, [{\n key: \"setServerUrl\",\n value: function setServerUrl(url) {\n this.easyrtc.setSocketUrl(url);\n }\n }, {\n key: \"setApp\",\n value: function setApp(appName) {\n this.app = appName;\n }\n }, {\n key: \"setRoom\",\n value: function setRoom(roomName) {\n this.room = roomName;\n this.easyrtc.joinRoom(roomName, null);\n } // options: { datachannel: bool, audio: bool, video: bool }\n\n }, {\n key: \"setWebRtcOptions\",\n value: function setWebRtcOptions(options) {\n // this.easyrtc.enableDebug(true);\n this.easyrtc.enableDataChannels(options.datachannel);\n this.easyrtc.enableVideo(options.video);\n this.easyrtc.enableAudio(options.audio); // TODO receive(audio|video) options ?\n\n this.easyrtc.enableVideoReceive(true);\n this.easyrtc.enableAudioReceive(true);\n }\n }, {\n key: \"setServerConnectListeners\",\n value: function setServerConnectListeners(successListener, failureListener) {\n this.connectSuccess = successListener;\n this.connectFailure = failureListener;\n }\n }, {\n key: \"setRoomOccupantListener\",\n value: function setRoomOccupantListener(occupantListener) {\n this.easyrtc.setRoomOccupantListener(function (roomName, occupants, primary) {\n occupantListener(occupants);\n });\n }\n }, {\n key: \"setDataChannelListeners\",\n value: function setDataChannelListeners(openListener, closedListener, messageListener) {\n this.easyrtc.setDataChannelOpenListener(openListener);\n this.easyrtc.setDataChannelCloseListener(closedListener);\n this.easyrtc.setPeerListener(messageListener);\n }\n }, {\n key: \"updateTimeOffset\",\n value: function updateTimeOffset() {\n var _this2 = this;\n\n var clientSentTime = Date.now() + this.avgTimeOffset;\n return fetch(document.location.href, {\n method: \"HEAD\",\n cache: \"no-cache\"\n }).then(function (res) {\n var precision = 1000;\n var serverReceivedTime = new Date(res.headers.get(\"Date\")).getTime() + precision / 2;\n var clientReceivedTime = Date.now();\n var serverTime = serverReceivedTime + (clientReceivedTime - clientSentTime) / 2;\n var timeOffset = serverTime - clientReceivedTime;\n _this2.serverTimeRequests++;\n\n if (_this2.serverTimeRequests <= 10) {\n _this2.timeOffsets.push(timeOffset);\n } else {\n _this2.timeOffsets[_this2.serverTimeRequests % 10] = timeOffset;\n }\n\n _this2.avgTimeOffset = _this2.timeOffsets.reduce(function (acc, offset) {\n return acc += offset;\n }, 0) / _this2.timeOffsets.length;\n\n if (_this2.serverTimeRequests > 10) {\n setTimeout(function () {\n return _this2.updateTimeOffset();\n }, 5 * 60 * 1000); // Sync clock every 5 minutes.\n } else {\n _this2.updateTimeOffset();\n }\n });\n }\n }, {\n key: \"connect\",\n value: function connect() {\n var _this3 = this;\n\n Promise.all([this.updateTimeOffset(), new Promise(function (resolve, reject) {\n _this3._connect(resolve, reject);\n })]).then(function (_ref) {\n var _ref2 = _slicedToArray(_ref, 2),\n _ = _ref2[0],\n clientId = _ref2[1];\n\n _this3._myRoomJoinTime = _this3._getRoomJoinTime(clientId);\n\n _this3.connectSuccess(clientId);\n })[\"catch\"](this.connectFailure);\n }\n }, {\n key: \"shouldStartConnectionTo\",\n value: function shouldStartConnectionTo(client) {\n return this._myRoomJoinTime <= client.roomJoinTime;\n }\n }, {\n key: \"startStreamConnection\",\n value: function startStreamConnection(clientId) {\n this.easyrtc.call(clientId, function (caller, media) {\n if (media === \"datachannel\") {\n NAF.log.write(\"Successfully started datachannel to \", caller);\n }\n }, function (errorCode, errorText) {\n NAF.log.error(errorCode, errorText);\n }, function (wasAccepted) {// console.log(\"was accepted=\" + wasAccepted);\n });\n }\n }, {\n key: \"closeStreamConnection\",\n value: function closeStreamConnection(clientId) {\n this.easyrtc.hangup(clientId);\n }\n }, {\n key: \"sendData\",\n value: function sendData(clientId, dataType, data) {\n // send via webrtc otherwise fallback to websockets\n this.easyrtc.sendData(clientId, dataType, data);\n }\n }, {\n key: \"sendDataGuaranteed\",\n value: function sendDataGuaranteed(clientId, dataType, data) {\n this.easyrtc.sendDataWS(clientId, dataType, data);\n }\n }, {\n key: \"broadcastData\",\n value: function broadcastData(dataType, data) {\n var roomOccupants = this.easyrtc.getRoomOccupantsAsMap(this.room); // Iterate over the keys of the easyrtc room occupants map.\n // getRoomOccupantsAsArray uses Object.keys which allocates memory.\n\n for (var roomOccupant in roomOccupants) {\n if (roomOccupants[roomOccupant] && roomOccupant !== this.easyrtc.myEasyrtcid) {\n // send via webrtc otherwise fallback to websockets\n this.easyrtc.sendData(roomOccupant, dataType, data);\n }\n }\n }\n }, {\n key: \"broadcastDataGuaranteed\",\n value: function broadcastDataGuaranteed(dataType, data) {\n var destination = {\n targetRoom: this.room\n };\n this.easyrtc.sendDataWS(destination, dataType, data);\n }\n }, {\n key: \"getConnectStatus\",\n value: function getConnectStatus(clientId) {\n var status = this.easyrtc.getConnectStatus(clientId);\n\n if (status == this.easyrtc.IS_CONNECTED) {\n return NAF.adapters.IS_CONNECTED;\n } else if (status == this.easyrtc.NOT_CONNECTED) {\n return NAF.adapters.NOT_CONNECTED;\n } else {\n return NAF.adapters.CONNECTING;\n }\n }\n }, {\n key: \"getMediaStream\",\n value: function getMediaStream(clientId) {\n var streamName = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : \"audio\";\n\n if (this.mediaStreams[clientId] && this.mediaStreams[clientId][streamName]) {\n NAF.log.write(\"Already had \".concat(streamName, \" for \").concat(clientId));\n return Promise.resolve(this.mediaStreams[clientId][streamName]);\n } else {\n NAF.log.write(\"Waiting on \".concat(streamName, \" for \").concat(clientId)); // Create initial pendingMediaRequests with audio|video alias\n\n if (!this.pendingMediaRequests.has(clientId)) {\n var _pendingMediaRequests = {};\n var audioPromise = new Promise(function (resolve, reject) {\n _pendingMediaRequests.audio = {\n resolve: resolve,\n reject: reject\n };\n })[\"catch\"](function (e) {\n return NAF.log.warn(\"\".concat(clientId, \" getMediaStream Audio Error\"), e);\n });\n _pendingMediaRequests.audio.promise = audioPromise;\n var videoPromise = new Promise(function (resolve, reject) {\n _pendingMediaRequests.video = {\n resolve: resolve,\n reject: reject\n };\n })[\"catch\"](function (e) {\n return NAF.log.warn(\"\".concat(clientId, \" getMediaStream Video Error\"), e);\n });\n _pendingMediaRequests.video.promise = videoPromise;\n this.pendingMediaRequests.set(clientId, _pendingMediaRequests);\n }\n\n var pendingMediaRequests = this.pendingMediaRequests.get(clientId); // Create initial pendingMediaRequests with streamName\n\n if (!pendingMediaRequests[streamName]) {\n var streamPromise = new Promise(function (resolve, reject) {\n pendingMediaRequests[streamName] = {\n resolve: resolve,\n reject: reject\n };\n })[\"catch\"](function (e) {\n return NAF.log.warn(\"\".concat(clientId, \" getMediaStream \\\"\").concat(streamName, \"\\\" Error\"), e);\n });\n pendingMediaRequests[streamName].promise = streamPromise;\n }\n\n return this.pendingMediaRequests.get(clientId)[streamName].promise;\n }\n }\n }, {\n key: \"setMediaStream\",\n value: function setMediaStream(clientId, stream, streamName) {\n var pendingMediaRequests = this.pendingMediaRequests.get(clientId); // return undefined if there is no entry in the Map\n\n var clientMediaStreams = this.mediaStreams[clientId] = this.mediaStreams[clientId] || {};\n\n if (streamName === 'default') {\n // Safari doesn't like it when you use a mixed media stream where one of the tracks is inactive, so we\n // split the tracks into two streams.\n // Add mediaStreams audio streamName alias\n var audioTracks = stream.getAudioTracks();\n\n if (audioTracks.length > 0) {\n var audioStream = new MediaStream();\n\n try {\n audioTracks.forEach(function (track) {\n return audioStream.addTrack(track);\n });\n clientMediaStreams.audio = audioStream;\n } catch (e) {\n NAF.log.warn(\"\".concat(clientId, \" setMediaStream \\\"audio\\\" alias Error\"), e);\n } // Resolve the promise for the user's media stream audio alias if it exists.\n\n\n if (pendingMediaRequests) pendingMediaRequests.audio.resolve(audioStream);\n } // Add mediaStreams video streamName alias\n\n\n var videoTracks = stream.getVideoTracks();\n\n if (videoTracks.length > 0) {\n var videoStream = new MediaStream();\n\n try {\n videoTracks.forEach(function (track) {\n return videoStream.addTrack(track);\n });\n clientMediaStreams.video = videoStream;\n } catch (e) {\n NAF.log.warn(\"\".concat(clientId, \" setMediaStream \\\"video\\\" alias Error\"), e);\n } // Resolve the promise for the user's media stream video alias if it exists.\n\n\n if (pendingMediaRequests) pendingMediaRequests.video.resolve(videoStream);\n }\n } else {\n clientMediaStreams[streamName] = stream; // Resolve the promise for the user's media stream by StreamName if it exists.\n\n if (pendingMediaRequests && pendingMediaRequests[streamName]) {\n pendingMediaRequests[streamName].resolve(stream);\n }\n }\n }\n }, {\n key: \"addLocalMediaStream\",\n value: function addLocalMediaStream(stream, streamName) {\n var easyrtc = this.easyrtc;\n streamName = streamName || stream.id;\n this.setMediaStream(\"local\", stream, streamName);\n easyrtc.register3rdPartyLocalMediaStream(stream, streamName); // Add local stream to existing connections\n\n Object.keys(this.remoteClients).forEach(function (clientId) {\n if (easyrtc.getConnectStatus(clientId) !== easyrtc.NOT_CONNECTED) {\n easyrtc.addStreamToCall(clientId, streamName);\n }\n });\n }\n }, {\n key: \"removeLocalMediaStream\",\n value: function removeLocalMediaStream(streamName) {\n this.easyrtc.closeLocalMediaStream(streamName);\n delete this.mediaStreams[\"local\"][streamName];\n }\n }, {\n key: \"enableMicrophone\",\n value: function enableMicrophone(enabled) {\n this.easyrtc.enableMicrophone(enabled);\n }\n }, {\n key: \"enableCamera\",\n value: function enableCamera(enabled) {\n this.easyrtc.enableCamera(enabled);\n }\n }, {\n key: \"disconnect\",\n value: function disconnect() {\n this.easyrtc.disconnect();\n }\n /**\r\n * Privates\r\n */\n\n }, {\n key: \"_connect\",\n value: function _connect(connectSuccess, connectFailure) {\n var that = this;\n this.easyrtc.setStreamAcceptor(this.setMediaStream.bind(this));\n this.easyrtc.setOnStreamClosed(function (clientId, stream, streamName) {\n delete this.mediaStreams[clientId][streamName];\n });\n\n if (that.easyrtc.audioEnabled || that.easyrtc.videoEnabled) {\n navigator.mediaDevices.getUserMedia({\n video: that.easyrtc.videoEnabled,\n audio: that.easyrtc.audioEnabled\n }).then(function (stream) {\n that.addLocalMediaStream(stream, \"default\");\n that.easyrtc.connect(that.app, connectSuccess, connectFailure);\n }, function (errorCode, errmesg) {\n NAF.log.error(errorCode, errmesg);\n });\n } else {\n that.easyrtc.connect(that.app, connectSuccess, connectFailure);\n }\n }\n }, {\n key: \"_getRoomJoinTime\",\n value: function _getRoomJoinTime(clientId) {\n var myRoomId = NAF.room;\n var joinTime = this.easyrtc.getRoomOccupantsAsMap(myRoomId)[clientId].roomJoinTime;\n return joinTime;\n }\n }, {\n key: \"getServerTime\",\n value: function getServerTime() {\n return Date.now() + this.avgTimeOffset;\n }\n }]);\n\n return EasyRtcAdapter;\n}(NoOpAdapter);\n\nmodule.exports = EasyRtcAdapter;\n\n//# sourceURL=webpack:///./src/adapters/EasyRtcAdapter.js?"); /***/ }), @@ -250,7 +250,7 @@ eval("\n\nfunction _typeof(obj) { \"@babel/helpers - typeof\"; if (typeof Symbol /***/ (function(module, exports, __webpack_require__) { "use strict"; -eval("\n\nfunction _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError(\"Cannot call a class as a function\"); } }\n\nfunction _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if (\"value\" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }\n\nfunction _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }\n\n/* global NAF, io */\n\n/**\n * SocketIO Adapter (socketio)\n * networked-scene: serverURL needs to be ws://localhost:8080 when running locally\n */\nvar SocketioAdapter = /*#__PURE__*/function () {\n function SocketioAdapter() {\n _classCallCheck(this, SocketioAdapter);\n\n if (io === undefined) console.warn('It looks like socket.io has not been loaded before SocketioAdapter. Please do that.');\n this.app = \"default\";\n this.room = \"default\";\n this.occupantListener = null;\n this.myRoomJoinTime = null;\n this.myId = null;\n this.occupants = {}; // id -> joinTimestamp\n\n this.connectedClients = [];\n this.serverTimeRequests = 0;\n this.timeOffsets = [];\n this.avgTimeOffset = 0;\n }\n\n _createClass(SocketioAdapter, [{\n key: \"setServerUrl\",\n value: function setServerUrl(wsUrl) {\n this.wsUrl = wsUrl;\n }\n }, {\n key: \"setApp\",\n value: function setApp(appName) {\n this.app = appName;\n }\n }, {\n key: \"setRoom\",\n value: function setRoom(roomName) {\n this.room = roomName;\n }\n }, {\n key: \"setWebRtcOptions\",\n value: function setWebRtcOptions(options) {// No WebRTC support\n }\n }, {\n key: \"setServerConnectListeners\",\n value: function setServerConnectListeners(successListener, failureListener) {\n this.connectSuccess = successListener;\n this.connectFailure = failureListener;\n }\n }, {\n key: \"setRoomOccupantListener\",\n value: function setRoomOccupantListener(occupantListener) {\n this.occupantListener = occupantListener;\n }\n }, {\n key: \"setDataChannelListeners\",\n value: function setDataChannelListeners(openListener, closedListener, messageListener) {\n this.openListener = openListener;\n this.closedListener = closedListener;\n this.messageListener = messageListener;\n }\n }, {\n key: \"connect\",\n value: function connect() {\n var self = this;\n this.updateTimeOffset().then(function () {\n if (!self.wsUrl || self.wsUrl === \"/\") {\n if (location.protocol === \"https:\") {\n self.wsUrl = \"wss://\" + location.host;\n } else {\n self.wsUrl = \"ws://\" + location.host;\n }\n }\n\n NAF.log.write(\"Attempting to connect to socket.io\");\n var socket = self.socket = io(self.wsUrl);\n socket.on(\"connect\", function () {\n NAF.log.write(\"User connected\", socket.id);\n self.myId = socket.id;\n self.joinRoom();\n });\n socket.on(\"connectSuccess\", function (data) {\n var joinedTime = data.joinedTime;\n self.myRoomJoinTime = joinedTime;\n NAF.log.write(\"Successfully joined room\", self.room, \"at server time\", joinedTime);\n self.connectSuccess(self.myId);\n });\n socket.on(\"error\", function (err) {\n console.error(\"Socket connection failure\", err);\n self.connectFailure();\n });\n socket.on(\"occupantsChanged\", function (data) {\n var occupants = data.occupants;\n NAF.log.write('occupants changed', data);\n self.receivedOccupants(occupants);\n });\n\n function receiveData(packet) {\n var from = packet.from;\n var type = packet.type;\n var data = packet.data;\n self.messageListener(from, type, data);\n }\n\n socket.on(\"send\", receiveData);\n socket.on(\"broadcast\", receiveData);\n });\n }\n }, {\n key: \"joinRoom\",\n value: function joinRoom() {\n NAF.log.write(\"Joining room\", this.room);\n this.socket.emit(\"joinRoom\", {\n room: this.room\n });\n }\n }, {\n key: \"receivedOccupants\",\n value: function receivedOccupants(occupants) {\n delete occupants[this.myId];\n this.occupants = occupants;\n this.occupantListener(occupants);\n }\n }, {\n key: \"shouldStartConnectionTo\",\n value: function shouldStartConnectionTo(client) {\n return true;\n }\n }, {\n key: \"startStreamConnection\",\n value: function startStreamConnection(remoteId) {\n this.connectedClients.push(remoteId);\n this.openListener(remoteId);\n }\n }, {\n key: \"closeStreamConnection\",\n value: function closeStreamConnection(clientId) {\n this.connectedClients = this.connectedClients.filter(function (c) {\n return c != clientId;\n });\n this.closedListener(clientId);\n }\n }, {\n key: \"getConnectStatus\",\n value: function getConnectStatus(clientId) {\n var connected = this.connectedClients.indexOf(clientId) != -1;\n\n if (connected) {\n return NAF.adapters.IS_CONNECTED;\n } else {\n return NAF.adapters.NOT_CONNECTED;\n }\n }\n }, {\n key: \"sendData\",\n value: function sendData(to, type, data) {\n this.sendDataGuaranteed(to, type, data);\n }\n }, {\n key: \"sendDataGuaranteed\",\n value: function sendDataGuaranteed(to, type, data) {\n var packet = {\n from: this.myId,\n to: to,\n type: type,\n data: data,\n sending: true\n };\n\n if (this.socket) {\n this.socket.emit(\"send\", packet);\n } else {\n NAF.log.warn('SocketIO socket not created yet');\n }\n }\n }, {\n key: \"broadcastData\",\n value: function broadcastData(type, data) {\n this.broadcastDataGuaranteed(type, data);\n }\n }, {\n key: \"broadcastDataGuaranteed\",\n value: function broadcastDataGuaranteed(type, data) {\n var packet = {\n from: this.myId,\n type: type,\n data: data,\n broadcasting: true\n };\n\n if (this.socket) {\n this.socket.emit(\"broadcast\", packet);\n } else {\n NAF.log.warn('SocketIO socket not created yet');\n }\n }\n }, {\n key: \"getMediaStream\",\n value: function getMediaStream(clientId) {// Do not support WebRTC\n }\n }, {\n key: \"updateTimeOffset\",\n value: function updateTimeOffset() {\n var _this = this;\n\n var clientSentTime = Date.now() + this.avgTimeOffset;\n return fetch(document.location.href, {\n method: \"HEAD\",\n cache: \"no-cache\"\n }).then(function (res) {\n var precision = 1000;\n var serverReceivedTime = new Date(res.headers.get(\"Date\")).getTime() + precision / 2;\n var clientReceivedTime = Date.now();\n var serverTime = serverReceivedTime + (clientReceivedTime - clientSentTime) / 2;\n var timeOffset = serverTime - clientReceivedTime;\n _this.serverTimeRequests++;\n\n if (_this.serverTimeRequests <= 10) {\n _this.timeOffsets.push(timeOffset);\n } else {\n _this.timeOffsets[_this.serverTimeRequests % 10] = timeOffset;\n }\n\n _this.avgTimeOffset = _this.timeOffsets.reduce(function (acc, offset) {\n return acc += offset;\n }, 0) / _this.timeOffsets.length;\n\n if (_this.serverTimeRequests > 10) {\n setTimeout(function () {\n return _this.updateTimeOffset();\n }, 5 * 60 * 1000); // Sync clock every 5 minutes.\n } else {\n _this.updateTimeOffset();\n }\n });\n }\n }, {\n key: \"getServerTime\",\n value: function getServerTime() {\n return new Date().getTime() + this.avgTimeOffset;\n }\n }, {\n key: \"disconnect\",\n value: function disconnect() {\n this.socket.disconnect();\n }\n }]);\n\n return SocketioAdapter;\n}(); // NAF.adapters.register(\"socketio\", SocketioAdapter);\n\n\nmodule.exports = SocketioAdapter;\n\n//# sourceURL=webpack:///./src/adapters/naf-socketio-adapter.js?"); +eval("\n\nfunction _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError(\"Cannot call a class as a function\"); } }\n\nfunction _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if (\"value\" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }\n\nfunction _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }\n\n/* global NAF, io */\n\n/**\r\n * SocketIO Adapter (socketio)\r\n * networked-scene: serverURL needs to be ws://localhost:8080 when running locally\r\n */\nvar SocketioAdapter = /*#__PURE__*/function () {\n function SocketioAdapter() {\n _classCallCheck(this, SocketioAdapter);\n\n if (io === undefined) console.warn('It looks like socket.io has not been loaded before SocketioAdapter. Please do that.');\n this.app = \"default\";\n this.room = \"default\";\n this.occupantListener = null;\n this.myRoomJoinTime = null;\n this.myId = null;\n this.occupants = {}; // id -> joinTimestamp\n\n this.connectedClients = [];\n this.serverTimeRequests = 0;\n this.timeOffsets = [];\n this.avgTimeOffset = 0;\n }\n\n _createClass(SocketioAdapter, [{\n key: \"setServerUrl\",\n value: function setServerUrl(wsUrl) {\n this.wsUrl = wsUrl;\n }\n }, {\n key: \"setApp\",\n value: function setApp(appName) {\n this.app = appName;\n }\n }, {\n key: \"setRoom\",\n value: function setRoom(roomName) {\n this.room = roomName;\n }\n }, {\n key: \"setWebRtcOptions\",\n value: function setWebRtcOptions(options) {// No WebRTC support\n }\n }, {\n key: \"setServerConnectListeners\",\n value: function setServerConnectListeners(successListener, failureListener) {\n this.connectSuccess = successListener;\n this.connectFailure = failureListener;\n }\n }, {\n key: \"setRoomOccupantListener\",\n value: function setRoomOccupantListener(occupantListener) {\n this.occupantListener = occupantListener;\n }\n }, {\n key: \"setDataChannelListeners\",\n value: function setDataChannelListeners(openListener, closedListener, messageListener) {\n this.openListener = openListener;\n this.closedListener = closedListener;\n this.messageListener = messageListener;\n }\n }, {\n key: \"connect\",\n value: function connect() {\n var self = this;\n this.updateTimeOffset().then(function () {\n if (!self.wsUrl || self.wsUrl === \"/\") {\n if (location.protocol === \"https:\") {\n self.wsUrl = \"wss://\" + location.host;\n } else {\n self.wsUrl = \"ws://\" + location.host;\n }\n }\n\n NAF.log.write(\"Attempting to connect to socket.io\");\n var socket = self.socket = io(self.wsUrl);\n socket.on(\"connect\", function () {\n NAF.log.write(\"User connected\", socket.id);\n self.myId = socket.id;\n self.joinRoom();\n });\n socket.on(\"connectSuccess\", function (data) {\n var joinedTime = data.joinedTime;\n self.myRoomJoinTime = joinedTime;\n NAF.log.write(\"Successfully joined room\", self.room, \"at server time\", joinedTime);\n self.connectSuccess(self.myId);\n });\n socket.on(\"error\", function (err) {\n console.error(\"Socket connection failure\", err);\n self.connectFailure();\n });\n socket.on(\"occupantsChanged\", function (data) {\n var occupants = data.occupants;\n NAF.log.write('occupants changed', data);\n self.receivedOccupants(occupants);\n });\n\n function receiveData(packet) {\n var from = packet.from;\n var type = packet.type;\n var data = packet.data;\n self.messageListener(from, type, data);\n }\n\n socket.on(\"send\", receiveData);\n socket.on(\"broadcast\", receiveData);\n });\n }\n }, {\n key: \"joinRoom\",\n value: function joinRoom() {\n NAF.log.write(\"Joining room\", this.room);\n this.socket.emit(\"joinRoom\", {\n room: this.room\n });\n }\n }, {\n key: \"receivedOccupants\",\n value: function receivedOccupants(occupants) {\n delete occupants[this.myId];\n this.occupants = occupants;\n this.occupantListener(occupants);\n }\n }, {\n key: \"shouldStartConnectionTo\",\n value: function shouldStartConnectionTo(client) {\n return true;\n }\n }, {\n key: \"startStreamConnection\",\n value: function startStreamConnection(remoteId) {\n this.connectedClients.push(remoteId);\n this.openListener(remoteId);\n }\n }, {\n key: \"closeStreamConnection\",\n value: function closeStreamConnection(clientId) {\n this.connectedClients = this.connectedClients.filter(function (c) {\n return c != clientId;\n });\n this.closedListener(clientId);\n }\n }, {\n key: \"getConnectStatus\",\n value: function getConnectStatus(clientId) {\n var connected = this.connectedClients.indexOf(clientId) != -1;\n\n if (connected) {\n return NAF.adapters.IS_CONNECTED;\n } else {\n return NAF.adapters.NOT_CONNECTED;\n }\n }\n }, {\n key: \"sendData\",\n value: function sendData(to, type, data) {\n this.sendDataGuaranteed(to, type, data);\n }\n }, {\n key: \"sendDataGuaranteed\",\n value: function sendDataGuaranteed(to, type, data) {\n var packet = {\n from: this.myId,\n to: to,\n type: type,\n data: data,\n sending: true\n };\n\n if (this.socket) {\n this.socket.emit(\"send\", packet);\n } else {\n NAF.log.warn('SocketIO socket not created yet');\n }\n }\n }, {\n key: \"broadcastData\",\n value: function broadcastData(type, data) {\n this.broadcastDataGuaranteed(type, data);\n }\n }, {\n key: \"broadcastDataGuaranteed\",\n value: function broadcastDataGuaranteed(type, data) {\n var packet = {\n from: this.myId,\n type: type,\n data: data,\n broadcasting: true\n };\n\n if (this.socket) {\n this.socket.emit(\"broadcast\", packet);\n } else {\n NAF.log.warn('SocketIO socket not created yet');\n }\n }\n }, {\n key: \"getMediaStream\",\n value: function getMediaStream(clientId) {// Do not support WebRTC\n }\n }, {\n key: \"updateTimeOffset\",\n value: function updateTimeOffset() {\n var _this = this;\n\n var clientSentTime = Date.now() + this.avgTimeOffset;\n return fetch(document.location.href, {\n method: \"HEAD\",\n cache: \"no-cache\"\n }).then(function (res) {\n var precision = 1000;\n var serverReceivedTime = new Date(res.headers.get(\"Date\")).getTime() + precision / 2;\n var clientReceivedTime = Date.now();\n var serverTime = serverReceivedTime + (clientReceivedTime - clientSentTime) / 2;\n var timeOffset = serverTime - clientReceivedTime;\n _this.serverTimeRequests++;\n\n if (_this.serverTimeRequests <= 10) {\n _this.timeOffsets.push(timeOffset);\n } else {\n _this.timeOffsets[_this.serverTimeRequests % 10] = timeOffset;\n }\n\n _this.avgTimeOffset = _this.timeOffsets.reduce(function (acc, offset) {\n return acc += offset;\n }, 0) / _this.timeOffsets.length;\n\n if (_this.serverTimeRequests > 10) {\n setTimeout(function () {\n return _this.updateTimeOffset();\n }, 5 * 60 * 1000); // Sync clock every 5 minutes.\n } else {\n _this.updateTimeOffset();\n }\n });\n }\n }, {\n key: \"getServerTime\",\n value: function getServerTime() {\n return new Date().getTime() + this.avgTimeOffset;\n }\n }, {\n key: \"disconnect\",\n value: function disconnect() {\n this.socket.disconnect();\n }\n }]);\n\n return SocketioAdapter;\n}(); // NAF.adapters.register(\"socketio\", SocketioAdapter);\n\n\nmodule.exports = SocketioAdapter;\n\n//# sourceURL=webpack:///./src/adapters/naf-socketio-adapter.js?"); /***/ }), @@ -262,7 +262,7 @@ eval("\n\nfunction _classCallCheck(instance, Constructor) { if (!(instance insta /***/ (function(module, exports, __webpack_require__) { "use strict"; -eval("\n\nfunction _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError(\"Cannot call a class as a function\"); } }\n\nfunction _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if (\"value\" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }\n\nfunction _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }\n\n/* global NAF, io */\nvar WebRtcPeer = /*#__PURE__*/function () {\n function WebRtcPeer(localId, remoteId, sendSignalFunc) {\n _classCallCheck(this, WebRtcPeer);\n\n this.localId = localId;\n this.remoteId = remoteId;\n this.sendSignalFunc = sendSignalFunc;\n this.open = false;\n this.channelLabel = \"networked-aframe-channel\";\n this.pc = this.createPeerConnection();\n this.channel = null;\n }\n\n _createClass(WebRtcPeer, [{\n key: \"setDatachannelListeners\",\n value: function setDatachannelListeners(openListener, closedListener, messageListener, trackListener) {\n this.openListener = openListener;\n this.closedListener = closedListener;\n this.messageListener = messageListener;\n this.trackListener = trackListener;\n }\n }, {\n key: \"offer\",\n value: function offer(options) {\n var self = this; // reliable: false - UDP\n\n this.setupChannel(this.pc.createDataChannel(this.channelLabel, {\n reliable: false\n })); // If there are errors with Safari implement this:\n // https://github.com/OpenVidu/openvidu/blob/master/openvidu-browser/src/OpenViduInternal/WebRtcPeer/WebRtcPeer.ts#L154\n\n if (options.sendAudio) {\n options.localAudioStream.getTracks().forEach(function (track) {\n return self.pc.addTrack(track, options.localAudioStream);\n });\n }\n\n this.pc.createOffer(function (sdp) {\n self.handleSessionDescription(sdp);\n }, function (error) {\n NAF.log.error(\"WebRtcPeer.offer: \" + error);\n }, {\n offerToReceiveAudio: true,\n offerToReceiveVideo: false\n });\n }\n }, {\n key: \"handleSignal\",\n value: function handleSignal(signal) {\n // ignores signal if it isn't for me\n if (this.localId !== signal.to || this.remoteId !== signal.from) return;\n\n switch (signal.type) {\n case \"offer\":\n this.handleOffer(signal);\n break;\n\n case \"answer\":\n this.handleAnswer(signal);\n break;\n\n case \"candidate\":\n this.handleCandidate(signal);\n break;\n\n default:\n NAF.log.error(\"WebRtcPeer.handleSignal: Unknown signal type \" + signal.type);\n break;\n }\n }\n }, {\n key: \"send\",\n value: function send(type, data) {\n if (this.channel === null || this.channel.readyState !== \"open\") {\n return;\n }\n\n this.channel.send(JSON.stringify({\n type: type,\n data: data\n }));\n }\n }, {\n key: \"getStatus\",\n value: function getStatus() {\n if (this.channel === null) return WebRtcPeer.NOT_CONNECTED;\n\n switch (this.channel.readyState) {\n case \"open\":\n return WebRtcPeer.IS_CONNECTED;\n\n case \"connecting\":\n return WebRtcPeer.CONNECTING;\n\n case \"closing\":\n case \"closed\":\n default:\n return WebRtcPeer.NOT_CONNECTED;\n }\n }\n /*\n * Privates\n */\n\n }, {\n key: \"createPeerConnection\",\n value: function createPeerConnection() {\n var self = this;\n var RTCPeerConnection = window.RTCPeerConnection || window.webkitRTCPeerConnection || window.mozRTCPeerConnection || window.msRTCPeerConnection;\n\n if (RTCPeerConnection === undefined) {\n throw new Error(\"WebRtcPeer.createPeerConnection: This browser does not seem to support WebRTC.\");\n }\n\n var pc = new RTCPeerConnection({\n iceServers: WebRtcPeer.ICE_SERVERS\n });\n\n pc.onicecandidate = function (event) {\n if (event.candidate) {\n self.sendSignalFunc({\n from: self.localId,\n to: self.remoteId,\n type: \"candidate\",\n sdpMLineIndex: event.candidate.sdpMLineIndex,\n candidate: event.candidate.candidate\n });\n }\n }; // Note: seems like channel.onclose hander is unreliable on some platforms,\n // so also tries to detect disconnection here.\n\n\n pc.oniceconnectionstatechange = function () {\n if (self.open && pc.iceConnectionState === \"disconnected\") {\n self.open = false;\n self.closedListener(self.remoteId);\n }\n };\n\n pc.ontrack = function (e) {\n self.trackListener(self.remoteId, e.streams[0]);\n };\n\n return pc;\n }\n }, {\n key: \"setupChannel\",\n value: function setupChannel(channel) {\n var self = this;\n this.channel = channel; // received data from a remote peer\n\n this.channel.onmessage = function (event) {\n var data = JSON.parse(event.data);\n self.messageListener(self.remoteId, data.type, data.data);\n }; // connected with a remote peer\n\n\n this.channel.onopen = function (_event) {\n self.open = true;\n self.openListener(self.remoteId);\n }; // disconnected with a remote peer\n\n\n this.channel.onclose = function (_event) {\n if (!self.open) return;\n self.open = false;\n self.closedListener(self.remoteId);\n }; // error occurred with a remote peer\n\n\n this.channel.onerror = function (error) {\n NAF.log.error(\"WebRtcPeer.channel.onerror: \" + error);\n };\n }\n }, {\n key: \"handleOffer\",\n value: function handleOffer(message) {\n var self = this;\n\n this.pc.ondatachannel = function (event) {\n self.setupChannel(event.channel);\n };\n\n this.setRemoteDescription(message);\n this.pc.createAnswer(function (sdp) {\n self.handleSessionDescription(sdp);\n }, function (error) {\n NAF.log.error(\"WebRtcPeer.handleOffer: \" + error);\n });\n }\n }, {\n key: \"handleAnswer\",\n value: function handleAnswer(message) {\n this.setRemoteDescription(message);\n }\n }, {\n key: \"handleCandidate\",\n value: function handleCandidate(message) {\n var RTCIceCandidate = window.RTCIceCandidate || window.webkitRTCIceCandidate || window.mozRTCIceCandidate;\n this.pc.addIceCandidate(new RTCIceCandidate(message), function () {}, function (error) {\n NAF.log.error(\"WebRtcPeer.handleCandidate: \" + error);\n });\n }\n }, {\n key: \"handleSessionDescription\",\n value: function handleSessionDescription(sdp) {\n this.pc.setLocalDescription(sdp, function () {}, function (error) {\n NAF.log.error(\"WebRtcPeer.handleSessionDescription: \" + error);\n });\n this.sendSignalFunc({\n from: this.localId,\n to: this.remoteId,\n type: sdp.type,\n sdp: sdp.sdp\n });\n }\n }, {\n key: \"setRemoteDescription\",\n value: function setRemoteDescription(message) {\n var RTCSessionDescription = window.RTCSessionDescription || window.webkitRTCSessionDescription || window.mozRTCSessionDescription || window.msRTCSessionDescription;\n this.pc.setRemoteDescription(new RTCSessionDescription(message), function () {}, function (error) {\n NAF.log.error(\"WebRtcPeer.setRemoteDescription: \" + error);\n });\n }\n }, {\n key: \"close\",\n value: function close() {\n if (this.pc) {\n this.pc.close();\n }\n }\n }]);\n\n return WebRtcPeer;\n}();\n\nWebRtcPeer.IS_CONNECTED = \"IS_CONNECTED\";\nWebRtcPeer.CONNECTING = \"CONNECTING\";\nWebRtcPeer.NOT_CONNECTED = \"NOT_CONNECTED\";\nWebRtcPeer.ICE_SERVERS = [{\n urls: \"stun:stun1.l.google.com:19302\"\n}, {\n urls: \"stun:stun2.l.google.com:19302\"\n}, {\n urls: \"stun:stun3.l.google.com:19302\"\n}, {\n urls: \"stun:stun4.l.google.com:19302\"\n}];\n/**\n * Native WebRTC Adapter (native-webrtc)\n * For use with uws-server.js\n * networked-scene: serverURL needs to be ws://localhost:8080 when running locally\n */\n\nvar WebrtcAdapter = /*#__PURE__*/function () {\n function WebrtcAdapter() {\n _classCallCheck(this, WebrtcAdapter);\n\n if (io === undefined) console.warn('It looks like socket.io has not been loaded before WebrtcAdapter. Please do that.');\n this.app = \"default\";\n this.room = \"default\";\n this.occupantListener = null;\n this.myRoomJoinTime = null;\n this.myId = null;\n this.peers = {}; // id -> WebRtcPeer\n\n this.occupants = {}; // id -> joinTimestamp\n\n this.audioStreams = {};\n this.pendingAudioRequest = {};\n this.serverTimeRequests = 0;\n this.timeOffsets = [];\n this.avgTimeOffset = 0;\n }\n\n _createClass(WebrtcAdapter, [{\n key: \"setServerUrl\",\n value: function setServerUrl(wsUrl) {\n this.wsUrl = wsUrl;\n }\n }, {\n key: \"setApp\",\n value: function setApp(appName) {\n this.app = appName;\n }\n }, {\n key: \"setRoom\",\n value: function setRoom(roomName) {\n this.room = roomName;\n }\n }, {\n key: \"setWebRtcOptions\",\n value: function setWebRtcOptions(options) {\n if (options.datachannel === false) {\n NAF.log.error(\"WebrtcAdapter.setWebRtcOptions: datachannel must be true.\");\n }\n\n if (options.audio === true) {\n this.sendAudio = true;\n }\n\n if (options.video === true) {\n NAF.log.warn(\"WebrtcAdapter does not support video yet.\");\n }\n }\n }, {\n key: \"setServerConnectListeners\",\n value: function setServerConnectListeners(successListener, failureListener) {\n this.connectSuccess = successListener;\n this.connectFailure = failureListener;\n }\n }, {\n key: \"setRoomOccupantListener\",\n value: function setRoomOccupantListener(occupantListener) {\n this.occupantListener = occupantListener;\n }\n }, {\n key: \"setDataChannelListeners\",\n value: function setDataChannelListeners(openListener, closedListener, messageListener) {\n this.openListener = openListener;\n this.closedListener = closedListener;\n this.messageListener = messageListener;\n }\n }, {\n key: \"connect\",\n value: function connect() {\n var self = this;\n this.updateTimeOffset().then(function () {\n if (!self.wsUrl || self.wsUrl === \"/\") {\n if (location.protocol === \"https:\") {\n self.wsUrl = \"wss://\" + location.host;\n } else {\n self.wsUrl = \"ws://\" + location.host;\n }\n }\n\n NAF.log.write(\"Attempting to connect to socket.io\");\n var socket = self.socket = io(self.wsUrl);\n socket.on(\"connect\", function () {\n NAF.log.write(\"User connected\", socket.id);\n self.myId = socket.id;\n self.joinRoom();\n });\n socket.on(\"connectSuccess\", function (data) {\n var joinedTime = data.joinedTime;\n self.myRoomJoinTime = joinedTime;\n NAF.log.write(\"Successfully joined room\", self.room, \"at server time\", joinedTime);\n\n if (self.sendAudio) {\n var mediaConstraints = {\n audio: true,\n video: false\n };\n navigator.mediaDevices.getUserMedia(mediaConstraints).then(function (localStream) {\n self.storeAudioStream(self.myId, localStream);\n self.connectSuccess(self.myId);\n localStream.getTracks().forEach(function (track) {\n Object.keys(self.peers).forEach(function (peerId) {\n self.peers[peerId].pc.addTrack(track, localStream);\n });\n });\n })[\"catch\"](function (e) {\n NAF.log.error(e);\n console.error(\"Microphone is disabled due to lack of permissions\");\n self.sendAudio = false;\n self.connectSuccess(self.myId);\n });\n } else {\n self.connectSuccess(self.myId);\n }\n });\n socket.on(\"error\", function (err) {\n console.error(\"Socket connection failure\", err);\n self.connectFailure();\n });\n socket.on(\"occupantsChanged\", function (data) {\n var occupants = data.occupants;\n NAF.log.write('occupants changed', data);\n self.receivedOccupants(occupants);\n });\n\n function receiveData(packet) {\n var from = packet.from;\n var type = packet.type;\n var data = packet.data;\n\n if (type === 'ice-candidate') {\n self.peers[from].handleSignal(data);\n return;\n }\n\n self.messageListener(from, type, data);\n }\n\n socket.on(\"send\", receiveData);\n socket.on(\"broadcast\", receiveData);\n });\n }\n }, {\n key: \"joinRoom\",\n value: function joinRoom() {\n NAF.log.write(\"Joining room\", this.room);\n this.socket.emit(\"joinRoom\", {\n room: this.room\n });\n }\n }, {\n key: \"receivedOccupants\",\n value: function receivedOccupants(occupants) {\n var _this = this;\n\n delete occupants[this.myId];\n this.occupants = occupants;\n var self = this;\n var localId = this.myId;\n\n var _loop = function _loop(key) {\n var remoteId = key;\n if (_this.peers[remoteId]) return \"continue\";\n var peer = new WebRtcPeer(localId, remoteId, function (data) {\n self.socket.emit('send', {\n from: localId,\n to: remoteId,\n type: 'ice-candidate',\n data: data,\n sending: true\n });\n });\n peer.setDatachannelListeners(self.openListener, self.closedListener, self.messageListener, self.trackListener.bind(self));\n self.peers[remoteId] = peer;\n };\n\n for (var key in occupants) {\n var _ret = _loop(key);\n\n if (_ret === \"continue\") continue;\n }\n\n this.occupantListener(occupants);\n }\n }, {\n key: \"shouldStartConnectionTo\",\n value: function shouldStartConnectionTo(client) {\n return (this.myRoomJoinTime || 0) <= (client || 0);\n }\n }, {\n key: \"startStreamConnection\",\n value: function startStreamConnection(remoteId) {\n var _this2 = this;\n\n NAF.log.write('starting offer process');\n\n if (this.sendAudio) {\n this.getMediaStream(this.myId).then(function (stream) {\n var options = {\n sendAudio: true,\n localAudioStream: stream\n };\n\n _this2.peers[remoteId].offer(options);\n });\n } else {\n this.peers[remoteId].offer({});\n }\n }\n }, {\n key: \"closeStreamConnection\",\n value: function closeStreamConnection(clientId) {\n NAF.log.write('closeStreamConnection', clientId, this.peers);\n this.peers[clientId].close();\n delete this.peers[clientId];\n delete this.occupants[clientId];\n this.closedListener(clientId);\n }\n }, {\n key: \"getConnectStatus\",\n value: function getConnectStatus(clientId) {\n var peer = this.peers[clientId];\n if (peer === undefined) return NAF.adapters.NOT_CONNECTED;\n\n switch (peer.getStatus()) {\n case WebRtcPeer.IS_CONNECTED:\n return NAF.adapters.IS_CONNECTED;\n\n case WebRtcPeer.CONNECTING:\n return NAF.adapters.CONNECTING;\n\n case WebRtcPeer.NOT_CONNECTED:\n default:\n return NAF.adapters.NOT_CONNECTED;\n }\n }\n }, {\n key: \"sendData\",\n value: function sendData(to, type, data) {\n this.peers[to].send(type, data);\n }\n }, {\n key: \"sendDataGuaranteed\",\n value: function sendDataGuaranteed(to, type, data) {\n var packet = {\n from: this.myId,\n to: to,\n type: type,\n data: data,\n sending: true\n };\n this.socket.emit(\"send\", packet);\n }\n }, {\n key: \"broadcastData\",\n value: function broadcastData(type, data) {\n for (var clientId in this.peers) {\n this.sendData(clientId, type, data);\n }\n }\n }, {\n key: \"broadcastDataGuaranteed\",\n value: function broadcastDataGuaranteed(type, data) {\n var packet = {\n from: this.myId,\n type: type,\n data: data,\n broadcasting: true\n };\n this.socket.emit(\"broadcast\", packet);\n }\n }, {\n key: \"storeAudioStream\",\n value: function storeAudioStream(clientId, stream) {\n this.audioStreams[clientId] = stream;\n\n if (this.pendingAudioRequest[clientId]) {\n NAF.log.write(\"Received pending audio for \" + clientId);\n this.pendingAudioRequest[clientId](stream);\n delete this.pendingAudioRequest[clientId](stream);\n }\n }\n }, {\n key: \"trackListener\",\n value: function trackListener(clientId, stream) {\n this.storeAudioStream(clientId, stream);\n }\n }, {\n key: \"getMediaStream\",\n value: function getMediaStream(clientId) {\n var self = this;\n\n if (this.audioStreams[clientId]) {\n NAF.log.write(\"Already had audio for \" + clientId);\n return Promise.resolve(this.audioStreams[clientId]);\n } else {\n NAF.log.write(\"Waiting on audio for \" + clientId);\n return new Promise(function (resolve) {\n self.pendingAudioRequest[clientId] = resolve;\n });\n }\n }\n }, {\n key: \"updateTimeOffset\",\n value: function updateTimeOffset() {\n var _this3 = this;\n\n var clientSentTime = Date.now() + this.avgTimeOffset;\n return fetch(document.location.href, {\n method: \"HEAD\",\n cache: \"no-cache\"\n }).then(function (res) {\n var precision = 1000;\n var serverReceivedTime = new Date(res.headers.get(\"Date\")).getTime() + precision / 2;\n var clientReceivedTime = Date.now();\n var serverTime = serverReceivedTime + (clientReceivedTime - clientSentTime) / 2;\n var timeOffset = serverTime - clientReceivedTime;\n _this3.serverTimeRequests++;\n\n if (_this3.serverTimeRequests <= 10) {\n _this3.timeOffsets.push(timeOffset);\n } else {\n _this3.timeOffsets[_this3.serverTimeRequests % 10] = timeOffset;\n }\n\n _this3.avgTimeOffset = _this3.timeOffsets.reduce(function (acc, offset) {\n return acc += offset;\n }, 0) / _this3.timeOffsets.length;\n\n if (_this3.serverTimeRequests > 10) {\n setTimeout(function () {\n return _this3.updateTimeOffset();\n }, 5 * 60 * 1000); // Sync clock every 5 minutes.\n } else {\n _this3.updateTimeOffset();\n }\n });\n }\n }, {\n key: \"getServerTime\",\n value: function getServerTime() {\n return new Date().getTime() + this.avgTimeOffset;\n }\n }]);\n\n return WebrtcAdapter;\n}(); // NAF.adapters.register(\"native-webrtc\", WebrtcAdapter);\n\n\nmodule.exports = WebrtcAdapter;\n\n//# sourceURL=webpack:///./src/adapters/naf-webrtc-adapter.js?"); +eval("\n\nfunction _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError(\"Cannot call a class as a function\"); } }\n\nfunction _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if (\"value\" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }\n\nfunction _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }\n\n/* global NAF, io */\nvar WebRtcPeer = /*#__PURE__*/function () {\n function WebRtcPeer(localId, remoteId, sendSignalFunc) {\n _classCallCheck(this, WebRtcPeer);\n\n this.localId = localId;\n this.remoteId = remoteId;\n this.sendSignalFunc = sendSignalFunc;\n this.open = false;\n this.channelLabel = \"networked-aframe-channel\";\n this.pc = this.createPeerConnection();\n this.channel = null;\n }\n\n _createClass(WebRtcPeer, [{\n key: \"setDatachannelListeners\",\n value: function setDatachannelListeners(openListener, closedListener, messageListener, trackListener) {\n this.openListener = openListener;\n this.closedListener = closedListener;\n this.messageListener = messageListener;\n this.trackListener = trackListener;\n }\n }, {\n key: \"offer\",\n value: function offer(options) {\n var self = this; // reliable: false - UDP\n\n this.setupChannel(this.pc.createDataChannel(this.channelLabel, {\n reliable: false\n })); // If there are errors with Safari implement this:\n // https://github.com/OpenVidu/openvidu/blob/master/openvidu-browser/src/OpenViduInternal/WebRtcPeer/WebRtcPeer.ts#L154\n\n if (options.sendAudio) {\n options.localAudioStream.getTracks().forEach(function (track) {\n return self.pc.addTrack(track, options.localAudioStream);\n });\n }\n\n this.pc.createOffer(function (sdp) {\n self.handleSessionDescription(sdp);\n }, function (error) {\n NAF.log.error(\"WebRtcPeer.offer: \" + error);\n }, {\n offerToReceiveAudio: true,\n offerToReceiveVideo: false\n });\n }\n }, {\n key: \"handleSignal\",\n value: function handleSignal(signal) {\n // ignores signal if it isn't for me\n if (this.localId !== signal.to || this.remoteId !== signal.from) return;\n\n switch (signal.type) {\n case \"offer\":\n this.handleOffer(signal);\n break;\n\n case \"answer\":\n this.handleAnswer(signal);\n break;\n\n case \"candidate\":\n this.handleCandidate(signal);\n break;\n\n default:\n NAF.log.error(\"WebRtcPeer.handleSignal: Unknown signal type \" + signal.type);\n break;\n }\n }\n }, {\n key: \"send\",\n value: function send(type, data) {\n if (this.channel === null || this.channel.readyState !== \"open\") {\n return;\n }\n\n this.channel.send(JSON.stringify({\n type: type,\n data: data\n }));\n }\n }, {\n key: \"getStatus\",\n value: function getStatus() {\n if (this.channel === null) return WebRtcPeer.NOT_CONNECTED;\n\n switch (this.channel.readyState) {\n case \"open\":\n return WebRtcPeer.IS_CONNECTED;\n\n case \"connecting\":\n return WebRtcPeer.CONNECTING;\n\n case \"closing\":\n case \"closed\":\n default:\n return WebRtcPeer.NOT_CONNECTED;\n }\n }\n /*\r\n * Privates\r\n */\n\n }, {\n key: \"createPeerConnection\",\n value: function createPeerConnection() {\n var self = this;\n var RTCPeerConnection = window.RTCPeerConnection || window.webkitRTCPeerConnection || window.mozRTCPeerConnection || window.msRTCPeerConnection;\n\n if (RTCPeerConnection === undefined) {\n throw new Error(\"WebRtcPeer.createPeerConnection: This browser does not seem to support WebRTC.\");\n }\n\n var pc = new RTCPeerConnection({\n iceServers: WebRtcPeer.ICE_SERVERS\n });\n\n pc.onicecandidate = function (event) {\n if (event.candidate) {\n self.sendSignalFunc({\n from: self.localId,\n to: self.remoteId,\n type: \"candidate\",\n sdpMLineIndex: event.candidate.sdpMLineIndex,\n candidate: event.candidate.candidate\n });\n }\n }; // Note: seems like channel.onclose hander is unreliable on some platforms,\n // so also tries to detect disconnection here.\n\n\n pc.oniceconnectionstatechange = function () {\n if (self.open && pc.iceConnectionState === \"disconnected\") {\n self.open = false;\n self.closedListener(self.remoteId);\n }\n };\n\n pc.ontrack = function (e) {\n self.trackListener(self.remoteId, e.streams[0]);\n };\n\n return pc;\n }\n }, {\n key: \"setupChannel\",\n value: function setupChannel(channel) {\n var self = this;\n this.channel = channel; // received data from a remote peer\n\n this.channel.onmessage = function (event) {\n var data = JSON.parse(event.data);\n self.messageListener(self.remoteId, data.type, data.data);\n }; // connected with a remote peer\n\n\n this.channel.onopen = function (_event) {\n self.open = true;\n self.openListener(self.remoteId);\n }; // disconnected with a remote peer\n\n\n this.channel.onclose = function (_event) {\n if (!self.open) return;\n self.open = false;\n self.closedListener(self.remoteId);\n }; // error occurred with a remote peer\n\n\n this.channel.onerror = function (error) {\n NAF.log.error(\"WebRtcPeer.channel.onerror: \" + error);\n };\n }\n }, {\n key: \"handleOffer\",\n value: function handleOffer(message) {\n var self = this;\n\n this.pc.ondatachannel = function (event) {\n self.setupChannel(event.channel);\n };\n\n this.setRemoteDescription(message);\n this.pc.createAnswer(function (sdp) {\n self.handleSessionDescription(sdp);\n }, function (error) {\n NAF.log.error(\"WebRtcPeer.handleOffer: \" + error);\n });\n }\n }, {\n key: \"handleAnswer\",\n value: function handleAnswer(message) {\n this.setRemoteDescription(message);\n }\n }, {\n key: \"handleCandidate\",\n value: function handleCandidate(message) {\n var RTCIceCandidate = window.RTCIceCandidate || window.webkitRTCIceCandidate || window.mozRTCIceCandidate;\n this.pc.addIceCandidate(new RTCIceCandidate(message), function () {}, function (error) {\n NAF.log.error(\"WebRtcPeer.handleCandidate: \" + error);\n });\n }\n }, {\n key: \"handleSessionDescription\",\n value: function handleSessionDescription(sdp) {\n this.pc.setLocalDescription(sdp, function () {}, function (error) {\n NAF.log.error(\"WebRtcPeer.handleSessionDescription: \" + error);\n });\n this.sendSignalFunc({\n from: this.localId,\n to: this.remoteId,\n type: sdp.type,\n sdp: sdp.sdp\n });\n }\n }, {\n key: \"setRemoteDescription\",\n value: function setRemoteDescription(message) {\n var RTCSessionDescription = window.RTCSessionDescription || window.webkitRTCSessionDescription || window.mozRTCSessionDescription || window.msRTCSessionDescription;\n this.pc.setRemoteDescription(new RTCSessionDescription(message), function () {}, function (error) {\n NAF.log.error(\"WebRtcPeer.setRemoteDescription: \" + error);\n });\n }\n }, {\n key: \"close\",\n value: function close() {\n if (this.pc) {\n this.pc.close();\n }\n }\n }]);\n\n return WebRtcPeer;\n}();\n\nWebRtcPeer.IS_CONNECTED = \"IS_CONNECTED\";\nWebRtcPeer.CONNECTING = \"CONNECTING\";\nWebRtcPeer.NOT_CONNECTED = \"NOT_CONNECTED\";\nWebRtcPeer.ICE_SERVERS = [{\n urls: \"stun:stun1.l.google.com:19302\"\n}, {\n urls: \"stun:stun2.l.google.com:19302\"\n}, {\n urls: \"stun:stun3.l.google.com:19302\"\n}, {\n urls: \"stun:stun4.l.google.com:19302\"\n}];\n/**\r\n * Native WebRTC Adapter (native-webrtc)\r\n * For use with uws-server.js\r\n * networked-scene: serverURL needs to be ws://localhost:8080 when running locally\r\n */\n\nvar WebrtcAdapter = /*#__PURE__*/function () {\n function WebrtcAdapter() {\n _classCallCheck(this, WebrtcAdapter);\n\n if (io === undefined) console.warn('It looks like socket.io has not been loaded before WebrtcAdapter. Please do that.');\n this.app = \"default\";\n this.room = \"default\";\n this.occupantListener = null;\n this.myRoomJoinTime = null;\n this.myId = null;\n this.peers = {}; // id -> WebRtcPeer\n\n this.occupants = {}; // id -> joinTimestamp\n\n this.audioStreams = {};\n this.pendingAudioRequest = {};\n this.serverTimeRequests = 0;\n this.timeOffsets = [];\n this.avgTimeOffset = 0;\n }\n\n _createClass(WebrtcAdapter, [{\n key: \"setServerUrl\",\n value: function setServerUrl(wsUrl) {\n this.wsUrl = wsUrl;\n }\n }, {\n key: \"setApp\",\n value: function setApp(appName) {\n this.app = appName;\n }\n }, {\n key: \"setRoom\",\n value: function setRoom(roomName) {\n this.room = roomName;\n }\n }, {\n key: \"setWebRtcOptions\",\n value: function setWebRtcOptions(options) {\n if (options.datachannel === false) {\n NAF.log.error(\"WebrtcAdapter.setWebRtcOptions: datachannel must be true.\");\n }\n\n if (options.audio === true) {\n this.sendAudio = true;\n }\n\n if (options.video === true) {\n NAF.log.warn(\"WebrtcAdapter does not support video yet.\");\n }\n }\n }, {\n key: \"setServerConnectListeners\",\n value: function setServerConnectListeners(successListener, failureListener) {\n this.connectSuccess = successListener;\n this.connectFailure = failureListener;\n }\n }, {\n key: \"setRoomOccupantListener\",\n value: function setRoomOccupantListener(occupantListener) {\n this.occupantListener = occupantListener;\n }\n }, {\n key: \"setDataChannelListeners\",\n value: function setDataChannelListeners(openListener, closedListener, messageListener) {\n this.openListener = openListener;\n this.closedListener = closedListener;\n this.messageListener = messageListener;\n }\n }, {\n key: \"connect\",\n value: function connect() {\n var self = this;\n this.updateTimeOffset().then(function () {\n if (!self.wsUrl || self.wsUrl === \"/\") {\n if (location.protocol === \"https:\") {\n self.wsUrl = \"wss://\" + location.host;\n } else {\n self.wsUrl = \"ws://\" + location.host;\n }\n }\n\n NAF.log.write(\"Attempting to connect to socket.io\");\n var socket = self.socket = io(self.wsUrl);\n socket.on(\"connect\", function () {\n NAF.log.write(\"User connected\", socket.id);\n self.myId = socket.id;\n self.joinRoom();\n });\n socket.on(\"connectSuccess\", function (data) {\n var joinedTime = data.joinedTime;\n self.myRoomJoinTime = joinedTime;\n NAF.log.write(\"Successfully joined room\", self.room, \"at server time\", joinedTime);\n\n if (self.sendAudio) {\n var mediaConstraints = {\n audio: true,\n video: false\n };\n navigator.mediaDevices.getUserMedia(mediaConstraints).then(function (localStream) {\n self.storeAudioStream(self.myId, localStream);\n self.connectSuccess(self.myId);\n localStream.getTracks().forEach(function (track) {\n Object.keys(self.peers).forEach(function (peerId) {\n self.peers[peerId].pc.addTrack(track, localStream);\n });\n });\n })[\"catch\"](function (e) {\n NAF.log.error(e);\n console.error(\"Microphone is disabled due to lack of permissions\");\n self.sendAudio = false;\n self.connectSuccess(self.myId);\n });\n } else {\n self.connectSuccess(self.myId);\n }\n });\n socket.on(\"error\", function (err) {\n console.error(\"Socket connection failure\", err);\n self.connectFailure();\n });\n socket.on(\"occupantsChanged\", function (data) {\n var occupants = data.occupants;\n NAF.log.write('occupants changed', data);\n self.receivedOccupants(occupants);\n });\n\n function receiveData(packet) {\n var from = packet.from;\n var type = packet.type;\n var data = packet.data;\n\n if (type === 'ice-candidate') {\n self.peers[from].handleSignal(data);\n return;\n }\n\n self.messageListener(from, type, data);\n }\n\n socket.on(\"send\", receiveData);\n socket.on(\"broadcast\", receiveData);\n });\n }\n }, {\n key: \"joinRoom\",\n value: function joinRoom() {\n NAF.log.write(\"Joining room\", this.room);\n this.socket.emit(\"joinRoom\", {\n room: this.room\n });\n }\n }, {\n key: \"receivedOccupants\",\n value: function receivedOccupants(occupants) {\n var _this = this;\n\n delete occupants[this.myId];\n this.occupants = occupants;\n var self = this;\n var localId = this.myId;\n\n var _loop = function _loop(key) {\n var remoteId = key;\n if (_this.peers[remoteId]) return \"continue\";\n var peer = new WebRtcPeer(localId, remoteId, function (data) {\n self.socket.emit('send', {\n from: localId,\n to: remoteId,\n type: 'ice-candidate',\n data: data,\n sending: true\n });\n });\n peer.setDatachannelListeners(self.openListener, self.closedListener, self.messageListener, self.trackListener.bind(self));\n self.peers[remoteId] = peer;\n };\n\n for (var key in occupants) {\n var _ret = _loop(key);\n\n if (_ret === \"continue\") continue;\n }\n\n this.occupantListener(occupants);\n }\n }, {\n key: \"shouldStartConnectionTo\",\n value: function shouldStartConnectionTo(client) {\n return (this.myRoomJoinTime || 0) <= (client || 0);\n }\n }, {\n key: \"startStreamConnection\",\n value: function startStreamConnection(remoteId) {\n var _this2 = this;\n\n NAF.log.write('starting offer process');\n\n if (this.sendAudio) {\n this.getMediaStream(this.myId).then(function (stream) {\n var options = {\n sendAudio: true,\n localAudioStream: stream\n };\n\n _this2.peers[remoteId].offer(options);\n });\n } else {\n this.peers[remoteId].offer({});\n }\n }\n }, {\n key: \"closeStreamConnection\",\n value: function closeStreamConnection(clientId) {\n NAF.log.write('closeStreamConnection', clientId, this.peers);\n this.peers[clientId].close();\n delete this.peers[clientId];\n delete this.occupants[clientId];\n this.closedListener(clientId);\n }\n }, {\n key: \"getConnectStatus\",\n value: function getConnectStatus(clientId) {\n var peer = this.peers[clientId];\n if (peer === undefined) return NAF.adapters.NOT_CONNECTED;\n\n switch (peer.getStatus()) {\n case WebRtcPeer.IS_CONNECTED:\n return NAF.adapters.IS_CONNECTED;\n\n case WebRtcPeer.CONNECTING:\n return NAF.adapters.CONNECTING;\n\n case WebRtcPeer.NOT_CONNECTED:\n default:\n return NAF.adapters.NOT_CONNECTED;\n }\n }\n }, {\n key: \"sendData\",\n value: function sendData(to, type, data) {\n this.peers[to].send(type, data);\n }\n }, {\n key: \"sendDataGuaranteed\",\n value: function sendDataGuaranteed(to, type, data) {\n var packet = {\n from: this.myId,\n to: to,\n type: type,\n data: data,\n sending: true\n };\n this.socket.emit(\"send\", packet);\n }\n }, {\n key: \"broadcastData\",\n value: function broadcastData(type, data) {\n for (var clientId in this.peers) {\n this.sendData(clientId, type, data);\n }\n }\n }, {\n key: \"broadcastDataGuaranteed\",\n value: function broadcastDataGuaranteed(type, data) {\n var packet = {\n from: this.myId,\n type: type,\n data: data,\n broadcasting: true\n };\n this.socket.emit(\"broadcast\", packet);\n }\n }, {\n key: \"storeAudioStream\",\n value: function storeAudioStream(clientId, stream) {\n this.audioStreams[clientId] = stream;\n\n if (this.pendingAudioRequest[clientId]) {\n NAF.log.write(\"Received pending audio for \" + clientId);\n this.pendingAudioRequest[clientId](stream);\n delete this.pendingAudioRequest[clientId](stream);\n }\n }\n }, {\n key: \"trackListener\",\n value: function trackListener(clientId, stream) {\n this.storeAudioStream(clientId, stream);\n }\n }, {\n key: \"getMediaStream\",\n value: function getMediaStream(clientId) {\n var self = this;\n\n if (this.audioStreams[clientId]) {\n NAF.log.write(\"Already had audio for \" + clientId);\n return Promise.resolve(this.audioStreams[clientId]);\n } else {\n NAF.log.write(\"Waiting on audio for \" + clientId);\n return new Promise(function (resolve) {\n self.pendingAudioRequest[clientId] = resolve;\n });\n }\n }\n }, {\n key: \"updateTimeOffset\",\n value: function updateTimeOffset() {\n var _this3 = this;\n\n var clientSentTime = Date.now() + this.avgTimeOffset;\n return fetch(document.location.href, {\n method: \"HEAD\",\n cache: \"no-cache\"\n }).then(function (res) {\n var precision = 1000;\n var serverReceivedTime = new Date(res.headers.get(\"Date\")).getTime() + precision / 2;\n var clientReceivedTime = Date.now();\n var serverTime = serverReceivedTime + (clientReceivedTime - clientSentTime) / 2;\n var timeOffset = serverTime - clientReceivedTime;\n _this3.serverTimeRequests++;\n\n if (_this3.serverTimeRequests <= 10) {\n _this3.timeOffsets.push(timeOffset);\n } else {\n _this3.timeOffsets[_this3.serverTimeRequests % 10] = timeOffset;\n }\n\n _this3.avgTimeOffset = _this3.timeOffsets.reduce(function (acc, offset) {\n return acc += offset;\n }, 0) / _this3.timeOffsets.length;\n\n if (_this3.serverTimeRequests > 10) {\n setTimeout(function () {\n return _this3.updateTimeOffset();\n }, 5 * 60 * 1000); // Sync clock every 5 minutes.\n } else {\n _this3.updateTimeOffset();\n }\n });\n }\n }, {\n key: \"getServerTime\",\n value: function getServerTime() {\n return new Date().getTime() + this.avgTimeOffset;\n }\n }]);\n\n return WebrtcAdapter;\n}(); // NAF.adapters.register(\"native-webrtc\", WebrtcAdapter);\n\n\nmodule.exports = WebrtcAdapter;\n\n//# sourceURL=webpack:///./src/adapters/naf-webrtc-adapter.js?"); /***/ }), @@ -286,7 +286,7 @@ eval("\n\n/* global AFRAME, NAF, THREE */\nvar naf = __webpack_require__(/*! ../ /***/ (function(module, exports, __webpack_require__) { "use strict"; -eval("\n\n/* global AFRAME, NAF */\nAFRAME.registerComponent('networked-scene', {\n schema: {\n serverURL: {\n \"default\": '/'\n },\n app: {\n \"default\": 'default'\n },\n room: {\n \"default\": 'default'\n },\n connectOnLoad: {\n \"default\": true\n },\n onConnect: {\n \"default\": 'onConnect'\n },\n adapter: {\n \"default\": 'wseasyrtc'\n },\n // See https://github.com/networked-aframe/networked-aframe#adapters for list of adapters\n audio: {\n \"default\": false\n },\n // Only if adapter supports audio\n video: {\n \"default\": false\n },\n // Only if adapter supports video\n debug: {\n \"default\": false\n }\n },\n init: function init() {\n var el = this.el;\n this.connect = this.connect.bind(this);\n el.addEventListener('connect', this.connect);\n\n if (this.data.connectOnLoad) {\n el.emit('connect', null, false);\n }\n },\n\n /**\n * Connect to signalling server and begin connecting to other clients\n */\n connect: function connect() {\n NAF.log.setDebug(this.data.debug);\n NAF.log.write('Networked-Aframe Connecting...');\n this.checkDeprecatedProperties();\n this.setupNetworkAdapter();\n\n if (this.hasOnConnectFunction()) {\n this.callOnConnect();\n }\n\n return NAF.connection.connect(this.data.serverURL, this.data.app, this.data.room, this.data.audio, this.data.video);\n },\n checkDeprecatedProperties: function checkDeprecatedProperties() {// No current\n },\n setupNetworkAdapter: function setupNetworkAdapter() {\n var adapterName = this.data.adapter;\n var adapter = NAF.adapters.make(adapterName);\n NAF.connection.setNetworkAdapter(adapter);\n this.el.emit('adapter-ready', adapter, false);\n },\n hasOnConnectFunction: function hasOnConnectFunction() {\n return this.data.onConnect != '' && window[this.data.onConnect];\n },\n callOnConnect: function callOnConnect() {\n NAF.connection.onConnect(window[this.data.onConnect]);\n },\n remove: function remove() {\n NAF.log.write('networked-scene disconnected');\n this.el.removeEventListener('connect', this.connect);\n NAF.connection.disconnect();\n }\n});\n\n//# sourceURL=webpack:///./src/components/networked-scene.js?"); +eval("\n\n/* global AFRAME, NAF */\nAFRAME.registerComponent('networked-scene', {\n schema: {\n serverURL: {\n \"default\": '/'\n },\n app: {\n \"default\": 'default'\n },\n room: {\n \"default\": 'default'\n },\n connectOnLoad: {\n \"default\": true\n },\n onConnect: {\n \"default\": 'onConnect'\n },\n adapter: {\n \"default\": 'wseasyrtc'\n },\n // See https://github.com/networked-aframe/networked-aframe#adapters for list of adapters\n audio: {\n \"default\": false\n },\n // Only if adapter supports audio\n video: {\n \"default\": false\n },\n // Only if adapter supports video\n debug: {\n \"default\": false\n }\n },\n init: function init() {\n var el = this.el;\n this.connect = this.connect.bind(this);\n el.addEventListener('connect', this.connect);\n\n if (this.data.connectOnLoad) {\n el.emit('connect', null, false);\n }\n },\n\n /**\r\n * Connect to signalling server and begin connecting to other clients\r\n */\n connect: function connect() {\n NAF.log.setDebug(this.data.debug);\n NAF.log.write('Networked-Aframe Connecting...');\n this.checkDeprecatedProperties();\n this.setupNetworkAdapter();\n\n if (this.hasOnConnectFunction()) {\n this.callOnConnect();\n }\n\n return NAF.connection.connect(this.data.serverURL, this.data.app, this.data.room, this.data.audio, this.data.video);\n },\n checkDeprecatedProperties: function checkDeprecatedProperties() {// No current\n },\n setupNetworkAdapter: function setupNetworkAdapter() {\n var adapterName = this.data.adapter;\n var adapter = NAF.adapters.make(adapterName);\n NAF.connection.setNetworkAdapter(adapter);\n this.el.emit('adapter-ready', adapter, false);\n },\n hasOnConnectFunction: function hasOnConnectFunction() {\n return this.data.onConnect != '' && window[this.data.onConnect];\n },\n callOnConnect: function callOnConnect() {\n NAF.connection.onConnect(window[this.data.onConnect]);\n },\n remove: function remove() {\n NAF.log.write('networked-scene disconnected');\n this.el.removeEventListener('connect', this.connect);\n NAF.connection.disconnect();\n }\n});\n\n//# sourceURL=webpack:///./src/components/networked-scene.js?"); /***/ }), @@ -346,7 +346,7 @@ eval("\n\nvar options = {\n debug: false,\n updateRate: 15,\n // How often ne /***/ (function(module, exports, __webpack_require__) { "use strict"; -eval("\n\n/* global NAF */\nmodule.exports.whenEntityLoaded = function (entity, callback) {\n if (entity.hasLoaded) {\n callback();\n }\n\n entity.addEventListener('loaded', function () {\n callback();\n });\n};\n\nmodule.exports.createHtmlNodeFromString = function (str) {\n var div = document.createElement('div');\n div.innerHTML = str;\n var child = div.firstChild;\n return child;\n};\n\nmodule.exports.getCreator = function (el) {\n var components = el.components;\n\n if (components['networked']) {\n return components['networked'].data.creator;\n }\n\n return null;\n};\n\nmodule.exports.getNetworkOwner = function (el) {\n var components = el.components;\n\n if (components['networked']) {\n return components['networked'].data.owner;\n }\n\n return null;\n};\n\nmodule.exports.getNetworkId = function (el) {\n var components = el.components;\n\n if (components['networked']) {\n return components['networked'].data.networkId;\n }\n\n return null;\n};\n\nmodule.exports.now = function () {\n return Date.now();\n};\n\nmodule.exports.createNetworkId = function () {\n return Math.random().toString(36).substring(2, 9);\n};\n/**\n * Find the closest ancestor (including the passed in entity) that has a `networked` component\n * @param {ANode} entity - Entity to begin the search on\n * @returns {Promise} An promise that resolves to an entity with a `networked` component\n */\n\n\nfunction getNetworkedEntity(entity) {\n return new Promise(function (resolve, reject) {\n var curEntity = entity;\n\n while (curEntity && curEntity.components && !curEntity.components.networked) {\n curEntity = curEntity.parentNode;\n }\n\n if (!curEntity || !curEntity.components || !curEntity.components.networked) {\n return reject(\"Entity does not have and is not a child of an entity with the [networked] component \");\n }\n\n if (curEntity.hasLoaded) {\n resolve(curEntity);\n } else {\n curEntity.addEventListener(\"instantiated\", function () {\n resolve(curEntity);\n }, {\n once: true\n });\n }\n });\n}\n\nmodule.exports.getNetworkedEntity = getNetworkedEntity;\n\nmodule.exports.takeOwnership = function (entity) {\n var curEntity = entity;\n\n while (curEntity && curEntity.components && !curEntity.components.networked) {\n curEntity = curEntity.parentNode;\n }\n\n if (!curEntity || !curEntity.components || !curEntity.components.networked) {\n throw new Error(\"Entity does not have and is not a child of an entity with the [networked] component \");\n }\n\n return curEntity.components.networked.takeOwnership();\n};\n\nmodule.exports.isMine = function (entity) {\n var curEntity = entity;\n\n while (curEntity && curEntity.components && !curEntity.components.networked) {\n curEntity = curEntity.parentNode;\n }\n\n if (!curEntity || !curEntity.components || !curEntity.components.networked) {\n throw new Error(\"Entity does not have and is not a child of an entity with the [networked] component \");\n } // When remote networked entities are initially created, there's a frame delay before they are completely instantiated.\n // On that frame, data is undefined so we can't check the owner. In this instance we assume that the user is not the owner of the entity.\n\n\n if (!curEntity.components.networked.data) {\n return false;\n }\n\n return curEntity.components.networked.data.owner === NAF.clientId;\n};\n\nmodule.exports.almostEqualVec3 = function (u, v, epsilon) {\n return Math.abs(u.x - v.x) < epsilon && Math.abs(u.y - v.y) < epsilon && Math.abs(u.z - v.z) < epsilon;\n};\n\n//# sourceURL=webpack:///./src/utils.js?"); +eval("\n\n/* global NAF */\nmodule.exports.whenEntityLoaded = function (entity, callback) {\n if (entity.hasLoaded) {\n callback();\n }\n\n entity.addEventListener('loaded', function () {\n callback();\n });\n};\n\nmodule.exports.createHtmlNodeFromString = function (str) {\n var div = document.createElement('div');\n div.innerHTML = str;\n var child = div.firstChild;\n return child;\n};\n\nmodule.exports.getCreator = function (el) {\n var components = el.components;\n\n if (components['networked']) {\n return components['networked'].data.creator;\n }\n\n return null;\n};\n\nmodule.exports.getNetworkOwner = function (el) {\n var components = el.components;\n\n if (components['networked']) {\n return components['networked'].data.owner;\n }\n\n return null;\n};\n\nmodule.exports.getNetworkId = function (el) {\n var components = el.components;\n\n if (components['networked']) {\n return components['networked'].data.networkId;\n }\n\n return null;\n};\n\nmodule.exports.now = function () {\n return Date.now();\n};\n\nmodule.exports.createNetworkId = function () {\n return Math.random().toString(36).substring(2, 9);\n};\n/**\r\n * Find the closest ancestor (including the passed in entity) that has a `networked` component\r\n * @param {ANode} entity - Entity to begin the search on\r\n * @returns {Promise} An promise that resolves to an entity with a `networked` component\r\n */\n\n\nfunction getNetworkedEntity(entity) {\n return new Promise(function (resolve, reject) {\n var curEntity = entity;\n\n while (curEntity && curEntity.components && !curEntity.components.networked) {\n curEntity = curEntity.parentNode;\n }\n\n if (!curEntity || !curEntity.components || !curEntity.components.networked) {\n return reject(\"Entity does not have and is not a child of an entity with the [networked] component \");\n }\n\n if (curEntity.hasLoaded) {\n resolve(curEntity);\n } else {\n curEntity.addEventListener(\"instantiated\", function () {\n resolve(curEntity);\n }, {\n once: true\n });\n }\n });\n}\n\nmodule.exports.getNetworkedEntity = getNetworkedEntity;\n\nmodule.exports.takeOwnership = function (entity) {\n var curEntity = entity;\n\n while (curEntity && curEntity.components && !curEntity.components.networked) {\n curEntity = curEntity.parentNode;\n }\n\n if (!curEntity || !curEntity.components || !curEntity.components.networked) {\n throw new Error(\"Entity does not have and is not a child of an entity with the [networked] component \");\n }\n\n return curEntity.components.networked.takeOwnership();\n};\n\nmodule.exports.isMine = function (entity) {\n var curEntity = entity;\n\n while (curEntity && curEntity.components && !curEntity.components.networked) {\n curEntity = curEntity.parentNode;\n }\n\n if (!curEntity || !curEntity.components || !curEntity.components.networked) {\n throw new Error(\"Entity does not have and is not a child of an entity with the [networked] component \");\n } // When remote networked entities are initially created, there's a frame delay before they are completely instantiated.\n // On that frame, data is undefined so we can't check the owner. In this instance we assume that the user is not the owner of the entity.\n\n\n if (!curEntity.components.networked.data) {\n return false;\n }\n\n return curEntity.components.networked.data.owner === NAF.clientId;\n};\n\nmodule.exports.almostEqualVec3 = function (u, v, epsilon) {\n return Math.abs(u.x - v.x) < epsilon && Math.abs(u.y - v.y) < epsilon && Math.abs(u.z - v.z) < epsilon;\n};\n\n//# sourceURL=webpack:///./src/utils.js?"); /***/ }) diff --git a/docs/getting-started-local.md b/docs/getting-started-local.md index 15a7c388..813eee96 100644 --- a/docs/getting-started-local.md +++ b/docs/getting-started-local.md @@ -60,7 +60,7 @@ Let's copy the example server that comes with networked-aframe into our project. # MacOS & Linux cp -r ./node_modules/networked-aframe/server/ ./server/ cp -r ./node_modules/networked-aframe/examples/ ./examples/ -cp -r ./node_modules/networked-aframe/dist/ ./examples/dist/ /e +cp -r ./node_modules/networked-aframe/dist/ ./examples/dist/ # Windows robocopy .\node_modules\networked-aframe\server\ .\server\ /e diff --git a/examples/basic-video-mediapiped.html b/examples/basic-video-mediapiped.html new file mode 100644 index 00000000..6ea5653f --- /dev/null +++ b/examples/basic-video-mediapiped.html @@ -0,0 +1,394 @@ + + + + Video Streaming Example — Networked-Aframe + + + + + + + + + + + + + + + + + + + + + + + + + +
+ Selfie live Transmission +
+ + + +
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + From dfd02587632f564cb8281af169a0fae6bde88e96 Mon Sep 17 00:00:00 2001 From: Dimitrios Ververidis Date: Tue, 25 Jan 2022 06:54:59 +0200 Subject: [PATCH 03/22] Video Multistream with shaders.v1. --- dist/networked-aframe.js | 14 +- dist/networked-aframe.min.js | 2 +- docs/getting-started-local.md | 30 +++ examples/advanced-video-green-screen.html | 182 ++++++++++++++++++ examples/css/adv-screen.css | 51 +++++ examples/img/rhino.jpg | Bin 0 -> 37912 bytes examples/img/strawb.png | Bin 0 -> 542145 bytes examples/index.html | 28 ++- server/easyrtc-server.js | 4 +- .../networked-video-source-green-screen.js | 144 ++++++++++++++ src/index.js | 1 + 11 files changed, 451 insertions(+), 5 deletions(-) create mode 100644 examples/advanced-video-green-screen.html create mode 100644 examples/css/adv-screen.css create mode 100644 examples/img/rhino.jpg create mode 100644 examples/img/strawb.png create mode 100644 src/components/networked-video-source-green-screen.js diff --git a/dist/networked-aframe.js b/dist/networked-aframe.js index 7b367e29..97600a2f 100644 --- a/dist/networked-aframe.js +++ b/dist/networked-aframe.js @@ -290,6 +290,18 @@ eval("\n\n/* global AFRAME, NAF */\nAFRAME.registerComponent('networked-scene', /***/ }), +/***/ "./src/components/networked-video-source-green-screen.js": +/*!***************************************************************!*\ + !*** ./src/components/networked-video-source-green-screen.js ***! + \***************************************************************/ +/*! no static exports found */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; +eval("\n\n/* global AFRAME, NAF, THREE */\nvar naf = __webpack_require__(/*! ../NafIndex */ \"./src/NafIndex.js\");\n\nAFRAME.registerComponent('networked-video-source-green-screen', {\n schema: {\n streamName: {\n \"default\": 'video'\n }\n },\n dependencies: ['material'],\n init: function init() {\n var _this = this;\n\n this.videoTexture = null;\n this.video = null;\n this.stream = null;\n this._setMediaStream = this._setMediaStream.bind(this);\n NAF.utils.getNetworkedEntity(this.el).then(function (networkedEl) {\n var ownerId = networkedEl.components.networked.data.owner;\n\n if (ownerId) {\n NAF.connection.adapter.getMediaStream(ownerId, _this.data.streamName).then(_this._setMediaStream)[\"catch\"](function (e) {\n return naf.log.error(\"Error getting media stream for \".concat(ownerId), e);\n });\n } else {// Correctly configured local entity, perhaps do something here for enabling debug audio loopback\n }\n });\n },\n _setMediaStream: function _setMediaStream(newStream) {\n if (!this.video) {\n this.setupVideo();\n }\n\n if (newStream != this.stream) {\n if (this.stream) {\n this._clearMediaStream();\n }\n\n if (newStream) {\n this.video.srcObject = newStream;\n var playResult = this.video.play();\n\n if (playResult instanceof Promise) {\n playResult[\"catch\"](function (e) {\n return naf.log.error(\"Error play video stream\", e);\n });\n }\n\n if (this.videoTexture) {\n this.videoTexture.dispose();\n }\n\n this.videoTexture = new THREE.VideoTexture(this.video); //this.videoTexture.format = THREE.RGBAFormat;\n\n var mesh = this.el.getObject3D('mesh'); //--------- begin replace green with transparent ---------\n // let uniforms = {};\n // uniforms.uMap = {type: 't', value: this.videoTexture }\n // uniforms = THREE.UniformsUtils.merge([\n // uniforms,\n // THREE.UniformsLib['lights']\n // ]);\n //\n // let materialIncoming = new THREE.ShaderMaterial({\n // uniforms: uniforms\n // })\n //\n // materialIncoming.vertexShader = `\n // varying vec2 vUv;\n //\n // void main() {\n // vec4 worldPosition = modelViewMatrix * vec4( position, 1.0 );\n // vec3 vWorldPosition = worldPosition.xyz;\n // vUv = uv;\n // gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );\n // }\n // `;\n //\n // materialIncoming.fragmentShader = `\n // varying vec2 vUv;\n // uniform sampler2D uMap;\n //\n // void main() {\n // vec2 uv = vUv;\n // vec4 tex1 = texture2D(uMap, uv * 1.0);\n // if (tex1.g - tex1.r > 0.15 && tex1.g - tex1.r > 0.15)\n // gl_FragColor = vec4(0,0,0,0);\n // else\n // gl_FragColor = vec4(tex1.r,tex1.g,tex1.b,1.0);\n // }\n // `;\n //\n // materialIncoming.transparent = true;\n // materialIncoming.side = THREE.BackSide;\n // mesh.material = materialIncoming;\n //---------- end of replace -----------------\n\n mesh.material.map = this.videoTexture;\n mesh.material.needsUpdate = true;\n }\n\n this.stream = newStream;\n }\n },\n _clearMediaStream: function _clearMediaStream() {\n this.stream = null;\n\n if (this.videoTexture) {\n if (this.videoTexture.image instanceof HTMLVideoElement) {\n // Note: this.videoTexture.image === this.video\n var video = this.videoTexture.image;\n video.pause();\n video.srcObject = null;\n video.load();\n }\n\n this.videoTexture.dispose();\n this.videoTexture = null;\n }\n },\n remove: function remove() {\n this._clearMediaStream();\n },\n setupVideo: function setupVideo() {\n if (!this.video) {\n var video = document.createElement('video');\n video.setAttribute('autoplay', true);\n video.setAttribute('playsinline', true);\n video.setAttribute('muted', true);\n this.video = video;\n }\n }\n});\n\n//# sourceURL=webpack:///./src/components/networked-video-source-green-screen.js?"); + +/***/ }), + /***/ "./src/components/networked-video-source.js": /*!**************************************************!*\ !*** ./src/components/networked-video-source.js ***! @@ -322,7 +334,7 @@ eval("\n\n/* global AFRAME, NAF, THREE */\nvar deepEqual = __webpack_require__(/ /***/ (function(module, exports, __webpack_require__) { "use strict"; -eval("\n\n// Global vars and functions\n__webpack_require__(/*! ./NafIndex.js */ \"./src/NafIndex.js\"); // Network components\n\n\n__webpack_require__(/*! ./components/networked-scene */ \"./src/components/networked-scene.js\");\n\n__webpack_require__(/*! ./components/networked */ \"./src/components/networked.js\");\n\n__webpack_require__(/*! ./components/networked-audio-source */ \"./src/components/networked-audio-source.js\");\n\n__webpack_require__(/*! ./components/networked-video-source */ \"./src/components/networked-video-source.js\");\n\n//# sourceURL=webpack:///./src/index.js?"); +eval("\n\n// Global vars and functions\n__webpack_require__(/*! ./NafIndex.js */ \"./src/NafIndex.js\"); // Network components\n\n\n__webpack_require__(/*! ./components/networked-scene */ \"./src/components/networked-scene.js\");\n\n__webpack_require__(/*! ./components/networked */ \"./src/components/networked.js\");\n\n__webpack_require__(/*! ./components/networked-audio-source */ \"./src/components/networked-audio-source.js\");\n\n__webpack_require__(/*! ./components/networked-video-source */ \"./src/components/networked-video-source.js\");\n\n__webpack_require__(/*! ./components/networked-video-source-green-screen */ \"./src/components/networked-video-source-green-screen.js\");\n\n//# sourceURL=webpack:///./src/index.js?"); /***/ }), diff --git a/dist/networked-aframe.min.js b/dist/networked-aframe.min.js index d07ef7a1..a526367b 100644 --- a/dist/networked-aframe.min.js +++ b/dist/networked-aframe.min.js @@ -1 +1 @@ -!function(e){var t={};function n(i){if(t[i])return t[i].exports;var o=t[i]={i:i,l:!1,exports:{}};return e[i].call(o.exports,o,o.exports,n),o.l=!0,o.exports}n.m=e,n.c=t,n.d=function(e,t,i){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:i})},n.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var i=Object.create(null);if(n.r(i),Object.defineProperty(i,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var o in e)n.d(i,o,function(t){return e[t]}.bind(null,o));return i},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="",n(n.s=2)}([function(e,t,n){"use strict";var i=n(3),o=n(4),r=n(5),a=n(6),s=n(7),c=n(9),u=n(10),l={app:"",room:"",clientId:""};l.options=i,l.utils=o,l.log=new r,l.schemas=new a,l.version="0.8.3",l.adapters=new u;var d=new s,h=new c(d);l.connection=h,l.entities=d,e.exports=window.NAF=l},function(e,t,n){"use strict";function i(e){return(i="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e})(e)}function o(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function r(e,t){for(var n=0;n is defined."));if(!this.validateTemplate(e,t))return;this.templateCache[e.template]=document.importNode(t.content,!0)}else NAF.log.error("Schema not valid: ",e),NAF.log.error("See https://github.com/networked-aframe/networked-aframe#syncing-custom-components")}},{key:"getCachedTemplate",value:function(e){return this.templateIsCached(e)||(this.templateExistsInScene(e)?this.add(this.createDefaultSchema(e)):NAF.log.error("Template el for ".concat(e," is not in the scene, add the template to and register with NAF.schemas.add."))),this.templateCache[e].firstElementChild.cloneNode(!0)}},{key:"templateIsCached",value:function(e){return!!this.templateCache[e]}},{key:"getComponents",value:function(e){var t=["position","rotation"];return this.hasTemplate(e)&&(t=this.schemaDict[e].components),t}},{key:"hasTemplate",value:function(e){return!!this.schemaDict[e]}},{key:"templateExistsInScene",value:function(e){var t=document.querySelector(e);return t&&this.isTemplateTag(t)}},{key:"validateSchema",value:function(e){return!(!e.template||!e.components)}},{key:"validateTemplate",value:function(e,t){return this.isTemplateTag(t)?!!this.templateHasOneOrZeroChildren(t)||(NAF.log.error("Template for ".concat(e.template," has more than one child. Templates must have one direct child element, no more. Template found:"),t),!1):(NAF.log.error("Template for ".concat(e.template," is not a