From b03928adcfbde96899afdda310a2d624337af0d4 Mon Sep 17 00:00:00 2001 From: Muaz Khan Date: Thu, 20 Dec 2018 07:29:26 +0500 Subject: [PATCH] getStats, Canvas-Designer, RecordRTC, DetectRTC, MultiStreamsMixer, RTCMultiConnection-Server, Chrome-Extensions, cordova-mobile-apps, FileBufferReader, getScreenId, RTCMultiConnection, video-coferencing, audio/video broadcasting, WebRTC File Sharing, socket.io, screen-sharing etc. updated. --- Canvas-Designer/.npmignore | 30 +- Canvas-Designer/README.md | 17 +- Canvas-Designer/bower.json | 2 +- Canvas-Designer/canvas-designer-widget.js | 22 +- Canvas-Designer/dev/data-uris.js | 5 +- Canvas-Designer/dev/decorator.js | 8 +- Canvas-Designer/dev/pdf-handler.js | 49 +- Canvas-Designer/dev/share-drawings.js | 6 + Canvas-Designer/index.html | 2 +- Canvas-Designer/package.json | 2 +- Canvas-Designer/simple.html | 520 + Canvas-Designer/widget.html | 9 + Canvas-Designer/widget.js | 70 +- Canvas-Designer/widget.min.js | 15 +- .../Screen-Capturing.js/README.md | 8 + .../desktopCapture-p2p/README.md | 13 +- .../background/captureCamera.js | 68 + .../background/captureDesktop.js | 177 + .../background/captureTabUsingTabCapture.js | 44 + .../desktopCapture-p2p/background/common.js | 78 + .../desktopCapture-p2p/background/globals.js | 27 + .../background/gotStream.js | 50 + .../background/gotTabCaptureStream.js | 22 + .../background/helpers/AntMediaWrapper.js | 375 + .../{ => background/helpers}/CodecsHandler.js | 0 .../background/helpers/IceServersHandler.js | 53 + .../helpers}/MultiStreamsMixer.js | 0 .../helpers}/RTCMultiConnection.min.js | 0 .../{ => background/helpers}/adapter.js | 0 .../{ => background/helpers}/getStats.js | 0 .../{ => background/helpers}/socket.io.js | 0 .../background/onAccessApproved.js | 99 + .../background/online-offline.js | 13 + .../background/runtimePort.js | 14 + .../background/setDefaults.js | 54 + .../background/setupWebRTCConnection.js | 13 + .../shareStreamUsingAntMediaServer.js | 44 + .../shareStreamUsingRTCMultiConnection.js | 160 + .../desktopCapture-p2p/camera-mic.html | 1 - .../desktopCapture-p2p/desktop-capturing.js | 820 - .../extension-pages/camera-mic.html | 3 + .../{ => extension-pages}/camera-mic.js | 0 .../{ => extension-pages}/dropdown.html | 39 +- .../{ => extension-pages}/dropdown.js | 0 .../extension-pages/options.html | 185 + .../extension-pages/options.js | 178 + .../{ => extension-pages}/video.html | 5 +- .../{ => extension-pages}/video.js | 0 .../desktopCapture-p2p/images/help-icon.png | Bin 0 -> 4297 bytes .../desktopCapture-p2p/images/progress.gif | Bin 0 -> 748 bytes .../desktopCapture-p2p/manifest.json | 34 +- .../desktopCapture-p2p/options.html | 80 - .../desktopCapture-p2p/options.js | 103 - .../screen-receivers/ant/index.php | 128 + .../{ => screen-receivers}/index.php | 20 +- .../desktopCapture-p2p/server.sh | 1 - Chrome-Extensions/file-sharing/manifest.json | 4 +- .../file-sharing/rmc-files-handler.js | 15 +- .../getUserMedia-on-http/README.md | 2 +- .../getUserMedia-on-http/content-script.js | 3 +- .../getUserMedia-on-http/manifest.json | 2 +- Chrome-Extensions/screen-recording/README.md | 87 +- .../background/background.common.js | 6 +- .../background/background.contentScript.js | 176 + .../background/background.desktopCapture.js | 4 +- .../background/background.getUserMedia.js | 8 +- .../screen-recording/background/background.js | 84 +- .../background/background.messaging.js | 71 +- .../screen-recording/camera-mic.html | 2 +- .../screen-recording/camera-mic.js | 37 +- .../screen-recording/dropdown.js | 36 +- .../screen-recording/manifest.json | 15 +- Chrome-Extensions/tabCapture/CodecsHandler.js | 361 + .../tabCapture/IceServersHandler.js | 53 + Chrome-Extensions/tabCapture/README.md | 48 +- .../tabCapture/RTCMultiConnection.min.js | 23 +- Chrome-Extensions/tabCapture/manifest.json | 6 +- Chrome-Extensions/tabCapture/options.html | 4 - .../shareStreamUsingRTCMultiConnection.js | 160 + Chrome-Extensions/tabCapture/socket.io.js | 8 + Chrome-Extensions/tabCapture/tab-capturing.js | 376 +- Chrome-Extensions/tabCapture/websocket.js | 96 - DetectRTC/.npmignore | 3 +- DetectRTC/DetectRTC.js | 52 +- DetectRTC/DetectRTC.min.js | 6 +- DetectRTC/Gruntfile.js | 10 + DetectRTC/bower.json | 4 +- DetectRTC/dev/DetectLocalIPAddress.js | 40 +- DetectRTC/dev/getBrowserInfo.js | 6 + DetectRTC/index.html | 41 +- DetectRTC/package.json | 7 +- FileBufferReader/.npmignore | 2 - FileBufferReader/bower.json | 2 +- .../demo}/IceServersHandler.js | 0 FileBufferReader/demo/PeerConnection.js | 85 +- FileBufferReader/demo/PeerUI.js | 174 +- FileBufferReader/demo/adapter-latest.js | 5438 ++++ .../demo/circular-progress-bar.css | 1065 - FileBufferReader/demo/index.html | 388 + FileBufferReader/icons/close-icon.png | Bin 0 -> 1920 bytes FileBufferReader/icons/loading.gif | Bin 0 -> 748 bytes FileBufferReader/icons/pause-icon.png | Bin 0 -> 508 bytes FileBufferReader/icons/resume-icon.png | Bin 0 -> 1303 bytes FileBufferReader/icons/select-file.png | Bin 0 -> 5608 bytes FileBufferReader/index.html | 935 +- FileBufferReader/nohup.out | 11 - FileBufferReader/package.json | 2 +- MultiStreamsMixer/Gruntfile.js | 10 + MultiStreamsMixer/MultiStreamsMixer.js | 69 +- MultiStreamsMixer/MultiStreamsMixer.min.js | 4 +- MultiStreamsMixer/README.md | 8 +- MultiStreamsMixer/bower.json | 2 +- MultiStreamsMixer/dev/append-streams.js | 8 +- .../dev/cross-browser-declarations.js | 43 +- .../dev/get-mixed-audio-stream.js | 4 +- MultiStreamsMixer/dev/get-mixed-stream.js | 4 +- .../dev/get-mixed-video-stream.js | 4 +- MultiStreamsMixer/dev/replace-streams.js | 4 +- MultiStreamsMixer/index.html | 42 +- MultiStreamsMixer/package.json | 5 +- README.md | 306 +- RTCDataConnection/README.md | 70 - .../RTCDataConnection-Manual-Test.html | 282 - .../RTCDataConnection-Simple-Test.html | 260 - RTCDataConnection/RTCDataConnection-Test.html | 262 - RTCDataConnection/RTCDataConnection-v1.0.js | 737 - RTCMultiConnection-Server/.gitignore | 21 + RTCMultiConnection-Server/.npmignore | 22 + RTCMultiConnection-Server/LICENSE | 21 + RTCMultiConnection-Server/README.md | 30 + RTCMultiConnection-Server/bower.json | 47 + RTCMultiConnection-Server/config.json | 17 + .../fake-keys/certificate.pem | 16 + .../fake-keys/privatekey.pem | 15 + RTCMultiConnection-Server/logs.json | 1 + .../node_scripts/BASH_COLORS_HELPER.js | 0 .../node_scripts/CONST_STRINGS.js | 17 + .../node_scripts/Scalable-Broadcast.js | 58 +- .../node_scripts/Signaling-Server.js | 664 +- .../node_scripts/get-bash-parameters.js | 36 +- .../get-values-from-config-json.js | 30 +- .../node_scripts/getJsonFile.js | 16 + .../node_scripts/index.js | 289 + .../node_scripts/pushLogs.js | 38 +- .../node_scripts/resolveURL.js | 0 .../node_scripts/verify-admin.js | 9 + RTCMultiConnection-Server/package.json | 57 + RTCMultiConnection-Server/server.js | 8 + .../Scripts/RTCMultiConnection.js | 6177 +++++ .../Scripts/RTCMultiConnection.min.js | 19 + .../RTCMultiConnection/index.html | 4 +- RTCMultiConnection/.gitignore | 15 +- RTCMultiConnection/.npmignore | 32 +- RTCMultiConnection/Gruntfile.js | 7 +- RTCMultiConnection/README.md | 17 +- RTCMultiConnection/admin/admin-ui.js | 461 + RTCMultiConnection/admin/index.html | 451 +- RTCMultiConnection/bower.json | 4 +- RTCMultiConnection/config.json | 8 +- .../demos/Audio+ScreenSharing.html | 2 +- .../Audio+Video+TextChat+FileSharing.html | 4 +- .../demos/Audio-Conferencing.html | 31 +- .../demos/Audio-Video-Screen.html | 2 +- .../demos/Call-By-UserName.html | 2 +- .../demos/Cross-Domain-Screen-Capturing.html | 3 +- .../demos/Disconnect+Rejoin.html | 3 +- ...File-Sharing-Without-FileBufferReader.html | 354 + .../demos/Files-Scalable-Broadcast.html | 3 +- RTCMultiConnection/demos/Firebase-Demo.html | 5 +- RTCMultiConnection/demos/MCU.html | 2 +- .../Multi-Broadcasters-and-Many-Viewers.html | 181 +- RTCMultiConnection/demos/MultiRTC/index.html | 7 +- RTCMultiConnection/demos/One-to-One.html | 614 +- .../demos/Password-Protected-Rooms.html | 611 +- .../demos/Pre-recorded-Media-Streaming.html | 3 +- RTCMultiConnection/demos/PubNub-Demo.html | 3 +- RTCMultiConnection/demos/README.md | 41 - RTCMultiConnection/demos/SFU.html | 1 + RTCMultiConnection/demos/SSEConnection.html | 6 +- .../demos/Scalable-Audio-Broadcast.html | 385 + .../demos/Scalable-Broadcast.html | 146 +- .../demos/Scalable-Screen-Broadcast.html | 145 +- .../Scalable-Screen-plus-Audio-Broadcast.html | 145 +- .../demos/Scalable-Video-Conferencing.html | 1 + RTCMultiConnection/demos/StreamHasData.html | 1 + .../demos/TextChat+FileSharing.html | 5 +- .../demos/Translate-TextChat.html | 3 +- .../demos/Video-Conferencing.html | 115 +- .../demos/Video-Scalable-Broadcast.html | 8 +- RTCMultiConnection/demos/Videos-Layout.html | 1 + RTCMultiConnection/demos/WebSocket-Demo.html | 5 +- .../demos/addStream-in-Chat-room.html | 4 +- RTCMultiConnection/demos/admin-guest.html | 43 +- .../demos/applyConstraints.html | 1 + .../demos/autoCreateMediaElement.html | 2 +- RTCMultiConnection/demos/camera-zoom.html | 1 + .../demos/change-resolutions.html | 1 + RTCMultiConnection/demos/checkPresence.html | 1 + .../demos/css/bootstrap.min.css | 7 + .../demos/css/emojionearea.min.css | 1 + .../demos/custom-socket-event.html | 1 + RTCMultiConnection/demos/dashboard.html | 538 - .../demos/dashboard/canvas-designer.html | 799 + RTCMultiConnection/demos/dashboard/index.html | 767 + RTCMultiConnection/demos/file-sharing.html | 22 +- .../demos/getPublicModerators.html | 292 - RTCMultiConnection/demos/getStats.html | 5 +- RTCMultiConnection/demos/index.html | 53 +- .../demos/is-turn-server-still-running.js | 143 + RTCMultiConnection/demos/js/bootstrap.min.js | 7 + .../demos/js/emojionearea.min.js | 4 + .../demos/js/jquery-3.3.1.slim.min.js | 2 + RTCMultiConnection/demos/js/jquery.min.js | 2 + RTCMultiConnection/demos/js/popper.min.js | 5 + RTCMultiConnection/demos/keep-rejoining.html | 16 +- RTCMultiConnection/demos/replaceTrack.html | 1 + RTCMultiConnection/demos/screen-sharing.html | 4 +- .../demos/share-part-of-screen.html | 1 + RTCMultiConnection/demos/switch-cameras.html | 2 +- RTCMultiConnection/demos/v2-conference.html | 1 + .../demos/video-and-screen-sharing.html | 4 +- .../demos/video-broadcasting.html | 2 +- .../demos/video-conference/index.html | 765 + .../video-conference/video-conference.html | 268 + .../demos/vuejs-video-conferencing.html | 2 +- RTCMultiConnection/dev/DetectRTC.js | 1122 - RTCMultiConnection/dev/IceServersHandler.js | 10 +- RTCMultiConnection/dev/MultiPeersHandler.js | 4 +- RTCMultiConnection/dev/RTCMultiConnection.js | 235 +- RTCMultiConnection/dev/Screen-Capturing.js | 242 - RTCMultiConnection/dev/SocketConnection.js | 40 +- RTCMultiConnection/dev/adapter.js | 5108 ---- RTCMultiConnection/dev/amd.js | 27 +- RTCMultiConnection/dev/getScreenId.js | 289 - RTCMultiConnection/dev/getStats.js | 567 - RTCMultiConnection/dev/head.js | 2 +- RTCMultiConnection/dev/tail.js | 10 + RTCMultiConnection/dist/RTCMultiConnection.js | 666 +- .../dist/RTCMultiConnection.min.js | 12 +- RTCMultiConnection/docs/api.md | 116 +- RTCMultiConnection/docs/how-to-use.md | 5 +- RTCMultiConnection/node_scripts/uncache.js | 28 - .../node_scripts/verify-admin.js | 22 - RTCMultiConnection/npm-test.js | 39 + RTCMultiConnection/package.json | 16 +- RTCMultiConnection/server.js | 395 +- RecordRTC/README.md | 2 + RecordRTC/RecordRTC.js | 16 +- RecordRTC/RecordRTC.min.js | 4 +- RecordRTC/bower.json | 2 +- RecordRTC/dev/CanvasRecorder.js | 2 +- RecordRTC/dev/Cross-Browser-Declarations.js | 5 +- RecordRTC/index.html | 14 +- RecordRTC/libs/screenshot-dev.js | 21162 ---------------- RecordRTC/libs/screenshot.js | 10 - RecordRTC/package.json | 2 +- .../simple-demos/16khz-audio-recording.html | 2 - RecordRTC/simple-demos/README.md | 36 + RecordRTC/simple-demos/Record-Mp3-or-Wav.html | 1 - .../RecordRTCPromisesHandler.html | 2 - .../simple-demos/RecordRTC_Extension.html | 76 + RecordRTC/simple-demos/audio-recording.html | 2 - .../simple-demos/auto-stop-on-silence.html | 4 +- RecordRTC/simple-demos/bitsPerSecond.html | 1 - RecordRTC/simple-demos/destroy.html | 2 - .../simple-demos/edge-audio-recording.html | 2 - RecordRTC/simple-demos/gif-recording.html | 2 - RecordRTC/simple-demos/index.html | 20 +- .../simple-demos/multi-audios-recording.html | 1 - .../simple-demos/multi-cameras-recording.html | 2 - RecordRTC/simple-demos/onStateChanged.html | 2 - RecordRTC/simple-demos/onTimeStamp.html | 2 - .../ondataavailable-StereoAudioRecorder.html | 2 - RecordRTC/simple-demos/ondataavailable.html | 2 - .../pass-getUserMedia-constraints.html | 2 - .../preview-blob-size-during-recording.html | 2 - RecordRTC/simple-demos/raw-pcm.html | 2 - .../simple-demos/recording-html-element.html | 95 +- .../simple-demos/reuse-same-instance.html | 1 - .../simple-demos/setRecordingDuration.html | 2 - .../show-animated-bar-on-video.html | 1 - .../show-logo-on-recorded-video.html | 1 - .../simple-demos/video-mirror-recording.html | 1 - .../video-plus-screen-recording.html | 3 - RecordRTC/simple-demos/video-recording.html | 15 +- RecordRTC/test/html-test-files/README.md | 21 + ...o-recording-using-StereoAudioRecorder.html | 47 + .../test/html-test-files/audio-recording.html | 46 + .../html-test-files/canvas-recording.html | 116 + .../video-recording-using-WhammyRecorder.html | 47 + .../test/html-test-files/video-recording.html | 46 + WebRTC-File-Sharing/index.html | 5 +- audio-broadcast/broadcast-ui.js | 1 + broadcast/broadcast-ui.js | 1 + chat-hangout/hangout-ui.js | 1 + cordova-mobile-apps/README.md | 2 + .../audio-conference/README.md | 81 + .../audio-conference/config.xml | 45 + .../hooks/README.md | 0 .../hooks/iosrtc-swift-support.js | 0 .../node_modules/README.md | 0 .../audio-conference/package.json | 23 + .../www/README.md | 0 .../audio-conference/www/css/audios.css | 1 + .../www/css/stylesheet.css | 0 .../www/img/cross-icon.png | Bin .../audio-conference/www/img/icon.png | Bin 0 -> 4262 bytes .../www/img/logo.png | Bin .../www/img/menu-icon.png | Bin .../www/img/reload-icon.png | Bin .../audio-conference/www/index.html | 77 + .../www/js/RTCMultiConnection.js | 0 .../audio-conference/www/js/index.js | 327 + .../www/js/menu.js | 0 .../www/js/socket.io.js | 0 cordova-mobile-apps/file-sharing/README.md | 81 + cordova-mobile-apps/file-sharing/config.xml | 40 + .../file-sharing/hooks/README.md | 3 + .../hooks/iosrtc-swift-support.js | 143 + .../file-sharing/node_modules/README.md | 3 + cordova-mobile-apps/file-sharing/package.json | 32 + .../file-sharing/www/README.md | 5 + .../file-sharing/www/css/index.css | 169 + .../file-sharing/www/img/btn-select-file.png | Bin 0 -> 4289 bytes .../file-sharing/www/img/folder-icon.png | Bin 0 -> 37930 bytes .../file-sharing/www/img/icon.png | Bin 0 -> 855 bytes .../file-sharing/www/index.html | 41 + .../www/js/RTCMultiConnection.min.js | 18 + .../file-sharing/www/js/index.js | 662 + .../file-sharing/www/js/rmc3.fbr.min.js | 116 +- .../file-sharing/www/js/socket.io.js | 7685 ++++++ .../scalable-broadcast/README.md | 7 +- .../README.md | 7 +- .../config.xml | 0 .../video-conference/hooks/README.md | 3 + .../hooks/iosrtc-swift-support.js | 143 + .../video-conference/node_modules/README.md | 3 + .../package.json | 0 .../video-conference/www/README.md | 5 + .../video-conference/www/css/stylesheet.css | 262 + .../www/css/videos.css | 0 .../video-conference/www/img/cross-icon.png | Bin 0 -> 17123 bytes .../www/img/icon.png | Bin .../video-conference/www/img/logo.png | Bin 0 -> 5134 bytes .../video-conference/www/img/menu-icon.png | Bin 0 -> 2153 bytes .../video-conference/www/img/reload-icon.png | Bin 0 -> 4861 bytes .../www/index.html | 0 .../www/js/README.md | 0 .../www/js/RTCMultiConnection.js | 6039 +++++ .../www/js/index.js | 0 .../video-conference/www/js/menu.js | 21 + .../video-conference/www/js/socket.io.js | 7685 ++++++ demos/screen-and-video-from-single-peer.html | 166 +- docs/TURN-server-installation-guide.html | 433 +- docs/webrtcpedia/index.html | 18 +- file-hangout/file-hangout.js | 1 + file-sharing/index.html | 1 + getScreenId.js/.npmignore | 11 + getScreenId.js/README.md | 6 + getScreenId.js/Screen-Capturing.js | 193 + getScreenId.js/package.json | 43 + getStats/Gruntfile.js | 13 + getStats/bower.json | 4 +- getStats/dev/candidate-pair.js | 81 +- getStats/dev/getStats.js | 8 +- getStats/dev/globals.js | 18 +- getStats/dev/googCertificate.js | 9 + getStats/dev/googCodecName.audio.js | 55 +- getStats/dev/googCodecName.video.js | 57 +- getStats/dev/inbound-rtp.js | 38 + getStats/dev/outbound-rtp.js | 38 + getStats/dev/parameters.js | 20 +- getStats/dev/track.js | 13 + getStats/dev/wrapper.js | 20 +- getStats/getStats.js | 364 +- getStats/getStats.min.js | 6 +- getStats/index.html | 162 +- getStats/package.json | 5 +- screen-sharing/index.html | 1 + socket.io/index.html | 4 +- text-chat/index.html | 1 + video-conferencing/index.html | 1 + webrtc-broadcasting/index.html | 1 + 383 files changed, 49734 insertions(+), 37537 deletions(-) create mode 100644 Canvas-Designer/simple.html create mode 100644 Chrome-Extensions/desktopCapture-p2p/background/captureCamera.js create mode 100644 Chrome-Extensions/desktopCapture-p2p/background/captureDesktop.js create mode 100644 Chrome-Extensions/desktopCapture-p2p/background/captureTabUsingTabCapture.js create mode 100644 Chrome-Extensions/desktopCapture-p2p/background/common.js create mode 100644 Chrome-Extensions/desktopCapture-p2p/background/globals.js create mode 100644 Chrome-Extensions/desktopCapture-p2p/background/gotStream.js create mode 100644 Chrome-Extensions/desktopCapture-p2p/background/gotTabCaptureStream.js create mode 100644 Chrome-Extensions/desktopCapture-p2p/background/helpers/AntMediaWrapper.js rename Chrome-Extensions/desktopCapture-p2p/{ => background/helpers}/CodecsHandler.js (100%) create mode 100644 Chrome-Extensions/desktopCapture-p2p/background/helpers/IceServersHandler.js rename Chrome-Extensions/desktopCapture-p2p/{ => background/helpers}/MultiStreamsMixer.js (100%) rename Chrome-Extensions/desktopCapture-p2p/{ => background/helpers}/RTCMultiConnection.min.js (100%) rename Chrome-Extensions/desktopCapture-p2p/{ => background/helpers}/adapter.js (100%) rename Chrome-Extensions/desktopCapture-p2p/{ => background/helpers}/getStats.js (100%) rename Chrome-Extensions/desktopCapture-p2p/{ => background/helpers}/socket.io.js (100%) create mode 100644 Chrome-Extensions/desktopCapture-p2p/background/onAccessApproved.js create mode 100644 Chrome-Extensions/desktopCapture-p2p/background/online-offline.js create mode 100644 Chrome-Extensions/desktopCapture-p2p/background/runtimePort.js create mode 100644 Chrome-Extensions/desktopCapture-p2p/background/setDefaults.js create mode 100644 Chrome-Extensions/desktopCapture-p2p/background/setupWebRTCConnection.js create mode 100644 Chrome-Extensions/desktopCapture-p2p/background/shareStreamUsingAntMediaServer.js create mode 100644 Chrome-Extensions/desktopCapture-p2p/background/shareStreamUsingRTCMultiConnection.js delete mode 100644 Chrome-Extensions/desktopCapture-p2p/camera-mic.html delete mode 100644 Chrome-Extensions/desktopCapture-p2p/desktop-capturing.js create mode 100644 Chrome-Extensions/desktopCapture-p2p/extension-pages/camera-mic.html rename Chrome-Extensions/desktopCapture-p2p/{ => extension-pages}/camera-mic.js (100%) rename Chrome-Extensions/desktopCapture-p2p/{ => extension-pages}/dropdown.html (65%) rename Chrome-Extensions/desktopCapture-p2p/{ => extension-pages}/dropdown.js (100%) create mode 100644 Chrome-Extensions/desktopCapture-p2p/extension-pages/options.html create mode 100644 Chrome-Extensions/desktopCapture-p2p/extension-pages/options.js rename Chrome-Extensions/desktopCapture-p2p/{ => extension-pages}/video.html (73%) rename Chrome-Extensions/desktopCapture-p2p/{ => extension-pages}/video.js (100%) create mode 100644 Chrome-Extensions/desktopCapture-p2p/images/help-icon.png create mode 100644 Chrome-Extensions/desktopCapture-p2p/images/progress.gif delete mode 100644 Chrome-Extensions/desktopCapture-p2p/options.html delete mode 100644 Chrome-Extensions/desktopCapture-p2p/options.js create mode 100644 Chrome-Extensions/desktopCapture-p2p/screen-receivers/ant/index.php rename Chrome-Extensions/desktopCapture-p2p/{ => screen-receivers}/index.php (91%) delete mode 100644 Chrome-Extensions/desktopCapture-p2p/server.sh create mode 100644 Chrome-Extensions/screen-recording/background/background.contentScript.js create mode 100644 Chrome-Extensions/tabCapture/CodecsHandler.js create mode 100644 Chrome-Extensions/tabCapture/IceServersHandler.js create mode 100644 Chrome-Extensions/tabCapture/shareStreamUsingRTCMultiConnection.js create mode 100644 Chrome-Extensions/tabCapture/socket.io.js delete mode 100644 Chrome-Extensions/tabCapture/websocket.js rename {Chrome-Extensions/desktopCapture-p2p => FileBufferReader/demo}/IceServersHandler.js (100%) create mode 100644 FileBufferReader/demo/adapter-latest.js delete mode 100644 FileBufferReader/demo/circular-progress-bar.css create mode 100644 FileBufferReader/demo/index.html create mode 100644 FileBufferReader/icons/close-icon.png create mode 100644 FileBufferReader/icons/loading.gif create mode 100644 FileBufferReader/icons/pause-icon.png create mode 100644 FileBufferReader/icons/resume-icon.png create mode 100644 FileBufferReader/icons/select-file.png delete mode 100644 FileBufferReader/nohup.out delete mode 100644 RTCDataConnection/README.md delete mode 100644 RTCDataConnection/RTCDataConnection-Manual-Test.html delete mode 100644 RTCDataConnection/RTCDataConnection-Simple-Test.html delete mode 100644 RTCDataConnection/RTCDataConnection-Test.html delete mode 100644 RTCDataConnection/RTCDataConnection-v1.0.js create mode 100644 RTCMultiConnection-Server/.gitignore create mode 100644 RTCMultiConnection-Server/.npmignore create mode 100644 RTCMultiConnection-Server/LICENSE create mode 100644 RTCMultiConnection-Server/README.md create mode 100644 RTCMultiConnection-Server/bower.json create mode 100644 RTCMultiConnection-Server/config.json create mode 100644 RTCMultiConnection-Server/fake-keys/certificate.pem create mode 100644 RTCMultiConnection-Server/fake-keys/privatekey.pem create mode 100644 RTCMultiConnection-Server/logs.json rename {RTCMultiConnection => RTCMultiConnection-Server}/node_scripts/BASH_COLORS_HELPER.js (100%) create mode 100644 RTCMultiConnection-Server/node_scripts/CONST_STRINGS.js rename {RTCMultiConnection => RTCMultiConnection-Server}/node_scripts/Scalable-Broadcast.js (83%) rename {RTCMultiConnection => RTCMultiConnection-Server}/node_scripts/Signaling-Server.js (55%) rename {RTCMultiConnection => RTCMultiConnection-Server}/node_scripts/get-bash-parameters.js (83%) rename {RTCMultiConnection => RTCMultiConnection-Server}/node_scripts/get-values-from-config-json.js (75%) create mode 100644 RTCMultiConnection-Server/node_scripts/getJsonFile.js create mode 100644 RTCMultiConnection-Server/node_scripts/index.js rename {RTCMultiConnection => RTCMultiConnection-Server}/node_scripts/pushLogs.js (74%) rename {RTCMultiConnection => RTCMultiConnection-Server}/node_scripts/resolveURL.js (100%) create mode 100644 RTCMultiConnection-Server/node_scripts/verify-admin.js create mode 100644 RTCMultiConnection-Server/package.json create mode 100644 RTCMultiConnection-Server/server.js create mode 100644 RTCMultiConnection-SignalR/RTCMultiConnection/Scripts/RTCMultiConnection.js create mode 100644 RTCMultiConnection-SignalR/RTCMultiConnection/Scripts/RTCMultiConnection.min.js create mode 100644 RTCMultiConnection/admin/admin-ui.js create mode 100644 RTCMultiConnection/demos/File-Sharing-Without-FileBufferReader.html create mode 100644 RTCMultiConnection/demos/Scalable-Audio-Broadcast.html create mode 100644 RTCMultiConnection/demos/css/bootstrap.min.css create mode 100644 RTCMultiConnection/demos/css/emojionearea.min.css delete mode 100644 RTCMultiConnection/demos/dashboard.html create mode 100644 RTCMultiConnection/demos/dashboard/canvas-designer.html create mode 100644 RTCMultiConnection/demos/dashboard/index.html delete mode 100755 RTCMultiConnection/demos/getPublicModerators.html create mode 100644 RTCMultiConnection/demos/is-turn-server-still-running.js create mode 100644 RTCMultiConnection/demos/js/bootstrap.min.js create mode 100644 RTCMultiConnection/demos/js/emojionearea.min.js create mode 100644 RTCMultiConnection/demos/js/jquery-3.3.1.slim.min.js create mode 100644 RTCMultiConnection/demos/js/jquery.min.js create mode 100644 RTCMultiConnection/demos/js/popper.min.js create mode 100644 RTCMultiConnection/demos/video-conference/index.html create mode 100644 RTCMultiConnection/demos/video-conference/video-conference.html delete mode 100644 RTCMultiConnection/dev/DetectRTC.js delete mode 100755 RTCMultiConnection/dev/Screen-Capturing.js delete mode 100644 RTCMultiConnection/dev/adapter.js delete mode 100644 RTCMultiConnection/dev/getScreenId.js delete mode 100644 RTCMultiConnection/dev/getStats.js delete mode 100644 RTCMultiConnection/node_scripts/uncache.js delete mode 100644 RTCMultiConnection/node_scripts/verify-admin.js create mode 100644 RTCMultiConnection/npm-test.js delete mode 100644 RecordRTC/libs/screenshot-dev.js delete mode 100644 RecordRTC/libs/screenshot.js create mode 100644 RecordRTC/simple-demos/README.md create mode 100644 RecordRTC/simple-demos/RecordRTC_Extension.html create mode 100644 RecordRTC/test/html-test-files/README.md create mode 100644 RecordRTC/test/html-test-files/audio-recording-using-StereoAudioRecorder.html create mode 100644 RecordRTC/test/html-test-files/audio-recording.html create mode 100644 RecordRTC/test/html-test-files/canvas-recording.html create mode 100644 RecordRTC/test/html-test-files/video-recording-using-WhammyRecorder.html create mode 100644 RecordRTC/test/html-test-files/video-recording.html create mode 100644 cordova-mobile-apps/audio-conference/README.md create mode 100644 cordova-mobile-apps/audio-conference/config.xml rename cordova-mobile-apps/{video conference => audio-conference}/hooks/README.md (100%) rename cordova-mobile-apps/{video conference => audio-conference}/hooks/iosrtc-swift-support.js (100%) rename cordova-mobile-apps/{video conference => audio-conference}/node_modules/README.md (100%) create mode 100644 cordova-mobile-apps/audio-conference/package.json rename cordova-mobile-apps/{video conference => audio-conference}/www/README.md (100%) create mode 100644 cordova-mobile-apps/audio-conference/www/css/audios.css rename cordova-mobile-apps/{video conference => audio-conference}/www/css/stylesheet.css (100%) rename cordova-mobile-apps/{video conference => audio-conference}/www/img/cross-icon.png (100%) create mode 100644 cordova-mobile-apps/audio-conference/www/img/icon.png rename cordova-mobile-apps/{video conference => audio-conference}/www/img/logo.png (100%) rename cordova-mobile-apps/{video conference => audio-conference}/www/img/menu-icon.png (100%) rename cordova-mobile-apps/{video conference => audio-conference}/www/img/reload-icon.png (100%) create mode 100644 cordova-mobile-apps/audio-conference/www/index.html rename cordova-mobile-apps/{video conference => audio-conference}/www/js/RTCMultiConnection.js (100%) create mode 100644 cordova-mobile-apps/audio-conference/www/js/index.js rename cordova-mobile-apps/{video conference => audio-conference}/www/js/menu.js (100%) rename cordova-mobile-apps/{video conference => audio-conference}/www/js/socket.io.js (100%) create mode 100644 cordova-mobile-apps/file-sharing/README.md create mode 100644 cordova-mobile-apps/file-sharing/config.xml create mode 100644 cordova-mobile-apps/file-sharing/hooks/README.md create mode 100644 cordova-mobile-apps/file-sharing/hooks/iosrtc-swift-support.js create mode 100644 cordova-mobile-apps/file-sharing/node_modules/README.md create mode 100644 cordova-mobile-apps/file-sharing/package.json create mode 100644 cordova-mobile-apps/file-sharing/www/README.md create mode 100644 cordova-mobile-apps/file-sharing/www/css/index.css create mode 100644 cordova-mobile-apps/file-sharing/www/img/btn-select-file.png create mode 100644 cordova-mobile-apps/file-sharing/www/img/folder-icon.png create mode 100644 cordova-mobile-apps/file-sharing/www/img/icon.png create mode 100644 cordova-mobile-apps/file-sharing/www/index.html create mode 100644 cordova-mobile-apps/file-sharing/www/js/RTCMultiConnection.min.js create mode 100644 cordova-mobile-apps/file-sharing/www/js/index.js rename RTCMultiConnection/dev/FileBufferReader.js => cordova-mobile-apps/file-sharing/www/js/rmc3.fbr.min.js (91%) create mode 100644 cordova-mobile-apps/file-sharing/www/js/socket.io.js rename cordova-mobile-apps/{video conference => video-conference}/README.md (89%) rename cordova-mobile-apps/{video conference => video-conference}/config.xml (100%) create mode 100644 cordova-mobile-apps/video-conference/hooks/README.md create mode 100644 cordova-mobile-apps/video-conference/hooks/iosrtc-swift-support.js create mode 100644 cordova-mobile-apps/video-conference/node_modules/README.md rename cordova-mobile-apps/{video conference => video-conference}/package.json (100%) create mode 100644 cordova-mobile-apps/video-conference/www/README.md create mode 100644 cordova-mobile-apps/video-conference/www/css/stylesheet.css rename cordova-mobile-apps/{video conference => video-conference}/www/css/videos.css (100%) create mode 100644 cordova-mobile-apps/video-conference/www/img/cross-icon.png rename cordova-mobile-apps/{video conference => video-conference}/www/img/icon.png (100%) create mode 100644 cordova-mobile-apps/video-conference/www/img/logo.png create mode 100644 cordova-mobile-apps/video-conference/www/img/menu-icon.png create mode 100644 cordova-mobile-apps/video-conference/www/img/reload-icon.png rename cordova-mobile-apps/{video conference => video-conference}/www/index.html (100%) rename cordova-mobile-apps/{video conference => video-conference}/www/js/README.md (100%) create mode 100644 cordova-mobile-apps/video-conference/www/js/RTCMultiConnection.js rename cordova-mobile-apps/{video conference => video-conference}/www/js/index.js (100%) create mode 100644 cordova-mobile-apps/video-conference/www/js/menu.js create mode 100644 cordova-mobile-apps/video-conference/www/js/socket.io.js create mode 100644 getScreenId.js/.npmignore create mode 100644 getScreenId.js/Screen-Capturing.js create mode 100644 getScreenId.js/package.json create mode 100644 getStats/dev/inbound-rtp.js create mode 100644 getStats/dev/outbound-rtp.js create mode 100644 getStats/dev/track.js diff --git a/Canvas-Designer/.npmignore b/Canvas-Designer/.npmignore index dfeff733..ef9630ac 100755 --- a/Canvas-Designer/.npmignore +++ b/Canvas-Designer/.npmignore @@ -1,18 +1,14 @@ -node_modules -bower_components +# ignore everything +* -*.tar.gz -lib-cov - -.*.swp -._* -.DS_Store -.git -.hg -.npmrc -.lock-wscript -.svn -.wafpickle-* -config.gypi -CVS -npm-debug.log \ No newline at end of file +# but not these files... +!canvas-designer-widget.js +!widget.html +!widget.js +!widget.min.js +!dev/webrtc-handler.js +!index.html +!package.json +!bower.json +!server.js +!README.md \ No newline at end of file diff --git a/Canvas-Designer/README.md b/Canvas-Designer/README.md index d096a420..ace033bd 100644 --- a/Canvas-Designer/README.md +++ b/Canvas-Designer/README.md @@ -2,7 +2,7 @@ ## Demo: https://www.webrtc-experiment.com/Canvas-Designer/ -## Advance Demo: [demos/dashboard.html](https://rtcmulticonnection.herokuapp.com/demos/dashboard.html) +## Advance Demo: [demos/dashboard/](https://rtcmulticonnection.herokuapp.com/demos/dashboard/) Multiple designers demo: https://www.webrtc-experiment.com/Canvas-Designer/multiple.html @@ -236,6 +236,14 @@ Pass array-of-points that are shared by remote users using socket.io or websocke designer.syncData(arrayOfPoints); ``` +## `clearCanvas` + +Remove and clear all drawings from the canvas: + +```javascript +designer.clearCanvas(); +``` + ## `addSyncListener` This callback is invoked as soon as something new is drawn. An array-of-points is passed over this function. That array MUST be shared with remote users for collaboration. @@ -306,6 +314,8 @@ designer.icons = { text: '/icons/text.png', image: '/icons/image.png', pdf: '/icons/pdf.png', + pdf_next: '/icons/pdf-next.png', + pdf_prev: '/icons/pdf-prev.png', marker: '/icons/marker.png', zoom: '/icons/zoom.png', lineWidth: '/icons/lineWidth.png', @@ -329,6 +339,11 @@ CanvasDesigner is a widget; that widget should be appended to a DOM object. This ```javascript designer.appendTo(document.body || document.documentElement); + +// or +designer.appendTo(document.body || document.documentElement, function() { + alert('iframe load callback'); +}); ``` The correct name for `appendTo` is: `append-iframe to target HTML-DOM-element` diff --git a/Canvas-Designer/bower.json b/Canvas-Designer/bower.json index 670e31af..f9cdb8ca 100644 --- a/Canvas-Designer/bower.json +++ b/Canvas-Designer/bower.json @@ -1,7 +1,7 @@ { "name": "canvas-designer", "preferGlobal": false, - "version": "1.2.1", + "version": "1.2.7", "author": { "name": "Muaz Khan", "email": "muazkh@gmail.com", diff --git a/Canvas-Designer/canvas-designer-widget.js b/Canvas-Designer/canvas-designer-widget.js index 79127756..29d6b967 100644 --- a/Canvas-Designer/canvas-designer-widget.js +++ b/Canvas-Designer/canvas-designer-widget.js @@ -48,6 +48,9 @@ function CanvasDesigner() { text: null, image: null, pdf: null, + pdf_next: null, + pdf_prev: null, + pdf_close: null, marker: null, zoom: null, lineWidth: null, @@ -112,8 +115,17 @@ function CanvasDesigner() { designer.uid = getRandomString(); - designer.appendTo = function(parentNode) { + designer.appendTo = function(parentNode, callback) { + callback = callback || function() {}; + designer.iframe = document.createElement('iframe'); + + // designer load callback + designer.iframe.onload = function() { + callback(); + callback = null; + }; + designer.iframe.src = designer.widgetHtmlURL + '?widgetJsURL=' + designer.widgetJsURL + '&tools=' + JSON.stringify(tools) + '&selectedIcon=' + selectedIcon + '&icons=' + JSON.stringify(designer.icons); designer.iframe.style.width = '100%'; designer.iframe.style.height = '100%'; @@ -193,6 +205,14 @@ function CanvasDesigner() { }); }; + designer.clearCanvas = function () { + if (!designer.iframe) return; + + designer.postMessage({ + clearCanvas: true + }); + }; + designer.widgetHtmlURL = 'widget.html'; designer.widgetJsURL = 'widget.min.js'; } diff --git a/Canvas-Designer/dev/data-uris.js b/Canvas-Designer/dev/data-uris.js index a459ef47..9f10e413 100644 --- a/Canvas-Designer/dev/data-uris.js +++ b/Canvas-Designer/dev/data-uris.js @@ -27,5 +27,8 @@ var data_uris = { lineWidth: icons.lineWidth || 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAYAAADDPmHLAAAACXBIWXMAAAsTAAALEwEAmpwYAAABgUlEQVR4nO3csU0DMRgF4AdKoKFhGsa5YViCHTIIomEVGlJAAwWhoYAj57N98vdJr7XQ70jBlvMSAAAAAAAAAAAAAAAAYHsuCq93k+QuyW3hdfnykuQpyWvrP+SnfZL7JMckH7JqjqdZ72ftTAWXSQ5pP5jRcjjNvrkp7YcxaqYZ+7O6x7QfxKh5nLE/vyrxT+BbkqsC6/B/70mulyzQxXcI7ZT4ADwXWIPzLJ59iQ/AQ4E1OE8Xs3cMbJNujoFJsouLoFr5vgjazdqZP7gK3pZur4IBAAAAAAAAAACAtjwK3ZZuH4XqB6j/LFw/wODp5ochU9oPY9RMM/ZndfoB2kU/wOD0A7CMfoBt0w8wuC5m7xjYJt0cAxP9ADWjH2Bg3V4FAwAAAAAAAAAAAG15FLot3T4K1Q9Q/1m4foDB080PQ6a0H8aomWbsz+r0A7SLfoDB6QdgGf0A26YfYHBdzN4xsE26OQYm+gFqRj/AwLq9CgYAAAAAAAAAAAAAAABq+gRzFKdEQnj1fQAAAABJRU5ErkJggg==', colorsPicker: icons.colorsPicker || 'data:image/png;base64,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', extraOptions: icons.extraOptions || 'data:image/png;base64,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', - undo: icons.undo || 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAYAAADDPmHLAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAABPxJREFUeNrsnVuIVVUYx9dpQiIdvOBtDGa8RILSQz4koWYvRZeXEkRztJd8ExGkSPBRSxDMB0ESxBBpSnyLMCGJChV6SSURpEkcZia8RjqpmTTT97HXYQYcZ87Za5+z917f7wd/BhT1eNZvfWutvdbeuzI0NOTALk/wFSAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAlBq9LyAkJeY5yWeSmabbz6gA70n6JX9LplkWwNoQME/yleSwZI7ktn6HzAFsoL3+R8kaBv5hnjTS63fR8DYrAL3eaAWg1xuuAPR6oxWAXm+4AtDrjVYA7fWfSNbSlPYqQLXX0/jGKgC93nAFoNcbrQD0esMVgF5vtALQ6w1XAHq90QqQR6//jwpgt9dXJE9LWqwKUAk91lWpVEI/Q4fkY0lnDv9/7f2/Sf5yyfEw/XlLck3SI+mT9PqfA0VswOD2y1mATt/4HQXuJPcl170oFyVnJeckl/zvIUDKsV4b/t2SVs5/Jb9Lzki+l/wg+aOMAuRxKniDL69DEUUrxDE/f5nWbAHKcix8ruTLyBp+tFyW7JY8jwDDrI+w14+Xu5IjkhctCzDPSK8fK/9IDrrkTiRTAmww2OvHii4tt/nrDlELoGP9FzT4Y6Mrh5diFaCTXl9T9KLShzEJMJexPlW6JDPKLsBGfxGEBk2XXyQL8xIgi82gQQchvCA5IVma27XkDIaADiZ+mawSXolhEniFxkydm5KVLANt56pkSQwXgqgG6dMtaY/hUjBzg/TR01ETY9kMWkc1SJW9sW0HUw3qiy6x34lFAOYG6aLf1eyYBGBuUH8OxCYAc4P6omcQl8UoQN5zg0G/M6enex8WXILv3Cj3cYS2XxHuCxh5gGRnrevfjPhT8qo/tTPJZ6pLnh/c7uXU4WqBZLrL/0aatyTHy34qeLxq0NXEXqWneVvH+UwtXoiXJZslR3M883DSJXczRTUEPG6l0Iwv+Ybv2fUyRfKGZJ9L7g9olgB6J9MKCwI0a26QVoCRTJa87ZL7AgaaIMEhKwJUWd/AlUIWAoxkkZ/HXG7wjmGbJQEaWQ2yFqCKHvPa6jd1GiHBRmsCNGpu0CgBqsxyydNL72UswNdWBci6GjRagCp6DPznDAXQW9jnWBUgy7lBswSorhz2ZyjBKusCZFENmilAlS2SBxkIsAcBwncY8xBAWe2G31eUNj/plUkECNthzEsA5U1/KTrkFPFMBAirBnkKoLwuuROwkbWc18Y9ilaBlX5PoejoDSHv++3euvfhXHL7fRCxvjSqx1eC6oMpioxeQv4g5Z99NvhfN/Dm0LFWCnkPASPZk2IY+Jw5QNjcoEgCTJB8W6cA3yBA2EqhSAIoz7j6NpJOI0D6atDnL8jMKNhne81PCmsR4FcESM98l7w+vq2An21HjQJ0I0A4RVwJPSU5VYMAvVwHCKeID7jQQ6qbXPIA67FoidF+SDjvkrME5sofDPOpS84RNKx6IUCx0aFgq1+tUAGMog+WPIAAttFl4ZVRfr2CADbQo+DbEcA2+iTW4whgF73w85HL+OVVCFAuLjh/GNQzIfQvLNLt4VAbrX4o0FfS9Ev7Lc5VACg3DAEIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgApeN/AQYAUjtq/Zx6jVYAAAAASUVORK5CYII=' + undo: icons.undo || 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAYAAADDPmHLAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAABPxJREFUeNrsnVuIVVUYx9dpQiIdvOBtDGa8RILSQz4koWYvRZeXEkRztJd8ExGkSPBRSxDMB0ESxBBpSnyLMCGJChV6SSURpEkcZia8RjqpmTTT97HXYQYcZ87Za5+z917f7wd/BhT1eNZvfWutvdbeuzI0NOTALk/wFSAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAlBq9LyAkJeY5yWeSmabbz6gA70n6JX9LplkWwNoQME/yleSwZI7ktn6HzAFsoL3+R8kaBv5hnjTS63fR8DYrAL3eaAWg1xuuAPR6oxWAXm+4AtDrjVYA7fWfSNbSlPYqQLXX0/jGKgC93nAFoNcbrQD0esMVgF5vtALQ6w1XAHq90QqQR6//jwpgt9dXJE9LWqwKUAk91lWpVEI/Q4fkY0lnDv9/7f2/Sf5yyfEw/XlLck3SI+mT9PqfA0VswOD2y1mATt/4HQXuJPcl170oFyVnJeckl/zvIUDKsV4b/t2SVs5/Jb9Lzki+l/wg+aOMAuRxKniDL69DEUUrxDE/f5nWbAHKcix8ruTLyBp+tFyW7JY8jwDDrI+w14+Xu5IjkhctCzDPSK8fK/9IDrrkTiRTAmww2OvHii4tt/nrDlELoGP9FzT4Y6Mrh5diFaCTXl9T9KLShzEJMJexPlW6JDPKLsBGfxGEBk2XXyQL8xIgi82gQQchvCA5IVma27XkDIaADiZ+mawSXolhEniFxkydm5KVLANt56pkSQwXgqgG6dMtaY/hUjBzg/TR01ETY9kMWkc1SJW9sW0HUw3qiy6x34lFAOYG6aLf1eyYBGBuUH8OxCYAc4P6omcQl8UoQN5zg0G/M6enex8WXILv3Cj3cYS2XxHuCxh5gGRnrevfjPhT8qo/tTPJZ6pLnh/c7uXU4WqBZLrL/0aatyTHy34qeLxq0NXEXqWneVvH+UwtXoiXJZslR3M883DSJXczRTUEPG6l0Iwv+Ybv2fUyRfKGZJ9L7g9olgB6J9MKCwI0a26QVoCRTJa87ZL7AgaaIMEhKwJUWd/AlUIWAoxkkZ/HXG7wjmGbJQEaWQ2yFqCKHvPa6jd1GiHBRmsCNGpu0CgBqsxyydNL72UswNdWBci6GjRagCp6DPznDAXQW9jnWBUgy7lBswSorhz2ZyjBKusCZFENmilAlS2SBxkIsAcBwncY8xBAWe2G31eUNj/plUkECNthzEsA5U1/KTrkFPFMBAirBnkKoLwuuROwkbWc18Y9ilaBlX5PoejoDSHv++3euvfhXHL7fRCxvjSqx1eC6oMpioxeQv4g5Z99NvhfN/Dm0LFWCnkPASPZk2IY+Jw5QNjcoEgCTJB8W6cA3yBA2EqhSAIoz7j6NpJOI0D6atDnL8jMKNhne81PCmsR4FcESM98l7w+vq2An21HjQJ0I0A4RVwJPSU5VYMAvVwHCKeID7jQQ6qbXPIA67FoidF+SDjvkrME5sofDPOpS84RNKx6IUCx0aFgq1+tUAGMog+WPIAAttFl4ZVRfr2CADbQo+DbEcA2+iTW4whgF73w85HL+OVVCFAuLjh/GNQzIfQvLNLt4VAbrX4o0FfS9Ev7Lc5VACg3DAEIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgApeN/AQYAUjtq/Zx6jVYAAAAASUVORK5CYII=', + pdf_next: icons.pdf_next || 'data:image/png;base64,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', + pdf_prev: icons.pdf_prev || 'data:image/png;base64,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', + pdf_close: icons.pdf_close || 'data:image/png;base64,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' }; diff --git a/Canvas-Designer/dev/decorator.js b/Canvas-Designer/dev/decorator.js index 844587d8..cba55baa 100644 --- a/Canvas-Designer/dev/decorator.js +++ b/Canvas-Designer/dev/decorator.js @@ -90,7 +90,7 @@ window.addEventListener('load', function() { } addEvent(context.canvas, 'click', function() { - pdfHandler.pdfPageContainer.style.display = 'none'; + // pdfHandler.pdfPageContainer.style.display = 'none'; if (textHandler.text.length) { textHandler.appendPoints(); @@ -201,8 +201,9 @@ window.addEventListener('load', function() { image.src = data_uris.dragSingle; } + decorateDragLastPath(); + if (tools.dragSingle === true) { - decorateDragLastPath(); document.getElementById('drag-last-path').style.display = 'block'; } @@ -217,8 +218,9 @@ window.addEventListener('load', function() { image.src = data_uris.dragMultiple; } + decorateDragAllPaths(); + if (tools.dragMultiple === true) { - decorateDragAllPaths(); document.getElementById('drag-all-paths').style.display = 'block'; } diff --git a/Canvas-Designer/dev/pdf-handler.js b/Canvas-Designer/dev/pdf-handler.js index 4ef73c58..fa6042f4 100644 --- a/Canvas-Designer/dev/pdf-handler.js +++ b/Canvas-Designer/dev/pdf-handler.js @@ -1,10 +1,13 @@ var pdfHandler = { lastPdfURL: null, lastIndex: 0, - lastPageIndex: null, - removeWhiteBackground: true, + lastPointIndex: 0, + removeWhiteBackground: false, pdfPageContainer: document.getElementById('pdf-page-container'), pdfPagesList: document.getElementById('pdf-pages-list'), + pdfNext: document.getElementById('pdf-next'), + pdfPrev: document.getElementById('pdf-prev'), + pdfClose: document.getElementById('pdf-close'), pageNumber: 1, images: [], @@ -37,10 +40,13 @@ var pdfHandler = { var renderContext = { canvasContext: ctx, - viewport: viewport, - background: 'rgba(0,0,0,0)' + viewport: viewport }; + if (pdfHandler.removeWhiteBackground === true) { + renderContext.background = 'rgba(0,0,0,0)'; + } + page.render(renderContext).then(function() { if (pdfHandler.removeWhiteBackground === true) { var imgd = ctx.getImageData(0, 0, cav.width, cav.height); @@ -80,6 +86,8 @@ var pdfHandler = { var t = pdfHandler; pdfHandler.lastIndex = pdfHandler.images.length; var point = [lastPage, 60, 20, width, height, pdfHandler.lastIndex]; + + pdfHandler.lastPointIndex = points.length; points[points.length] = ['pdf', point, drawHelper.getOptions()]; pdfHandler.pdfPagesList.innerHTML = ''; @@ -93,14 +101,33 @@ var pdfHandler = { option.selected = true; } } - pdfHandler.pdfPageContainer.style.top = '20px'; - pdfHandler.pdfPageContainer.style.left = point[2] + 'px'; pdfHandler.pdfPagesList.onchange = function() { pdfHandler.load(lastPdfURL); }; + pdfHandler.pdfNext.onclick = function() { + pdfHandler.pdfPagesList.selectedIndex++; + pdfHandler.pdfPagesList.onchange(); + }; + + pdfHandler.pdfPrev.onclick = function() { + pdfHandler.pdfPagesList.selectedIndex--; + pdfHandler.pdfPagesList.onchange(); + }; + + pdfHandler.pdfClose.onclick = function() { + pdfHandler.pdfPageContainer.style.display = 'none'; + }; + document.getElementById('drag-last-path').click(); + + pdfHandler.pdfPrev.src = data_uris.pdf_next; + pdfHandler.pdfNext.src = data_uris.pdf_prev; + pdfHandler.pdfClose.src = data_uris.pdf_close; + + pdfHandler.pdfPageContainer.style.top = '20px'; + pdfHandler.pdfPageContainer.style.left = (point[3] - parseInt(point[3] / 2)) + 'px'; pdfHandler.pdfPageContainer.style.display = 'block'; // share to webrtc @@ -124,7 +151,9 @@ var pdfHandler = { var t = this; if (t.ismousedown) { - points[points.length] = ['pdf', [pdfHandler.lastPage, t.prevX, t.prevY, x - t.prevX, y - t.prevY, pdfHandler.lastIndex], drawHelper.getOptions()]; + if (points[pdfHandler.lastPointIndex]) { + points[pdfHandler.lastPointIndex] = ['pdf', [pdfHandler.lastPage, t.prevX, t.prevY, x - t.prevX, y - t.prevY, pdfHandler.lastIndex], drawHelper.getOptions()]; + } t.ismousedown = false; } @@ -141,9 +170,11 @@ var pdfHandler = { }, reset_pos: function(x, y) { pdfHandler.pdfPageContainer.style.top = y + 'px'; - pdfHandler.pdfPageContainer.style.left = x + 'px'; + if (!points[pdfHandler.lastPointIndex]) return; + var point = points[pdfHandler.lastPointIndex][1]; + pdfHandler.pdfPageContainer.style.left = (point[1] + point[3] - parseInt(point[3] / 2) - parseInt(pdfHandler.pdfPageContainer.clientWidth / 2)) + 'px'; }, end: function() { - pdfHandler.pdfPageContainer.style.display = 'none'; + // pdfHandler.pdfPageContainer.style.display = 'none'; } }; diff --git a/Canvas-Designer/dev/share-drawings.js b/Canvas-Designer/dev/share-drawings.js index 0156dab1..d3f047d4 100644 --- a/Canvas-Designer/dev/share-drawings.js +++ b/Canvas-Designer/dev/share-drawings.js @@ -82,6 +82,12 @@ window.addEventListener('message', function(event) { return; } + if (event.data.clearCanvas) { + points = []; + drawHelper.redraw(); + return; + } + if (!event.data.canvasDesignerSyncData) return; // drawing is shared here (array of points) diff --git a/Canvas-Designer/index.html b/Canvas-Designer/index.html index 7adfc6b9..7045a42c 100644 --- a/Canvas-Designer/index.html +++ b/Canvas-Designer/index.html @@ -336,7 +336,7 @@

How to use
-

Try multiple designers!

+

WebRTC Dashboard




diff --git a/Canvas-Designer/package.json b/Canvas-Designer/package.json index 3c478523..b08f6f3c 100644 --- a/Canvas-Designer/package.json +++ b/Canvas-Designer/package.json @@ -1,7 +1,7 @@ { "name": "canvas-designer", "preferGlobal": false, - "version": "1.2.1", + "version": "1.2.7", "author": { "name": "Muaz Khan", "email": "muazkh@gmail.com", diff --git a/Canvas-Designer/simple.html b/Canvas-Designer/simple.html new file mode 100644 index 00000000..9e0ad7a2 --- /dev/null +++ b/Canvas-Designer/simple.html @@ -0,0 +1,520 @@ + + + + + + + + Canvas Designer Simple Demo + + + + + + + + + + +
+ +
+ +
+ + +

Select Tools

+ +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +

+ +
+

Shortcut Keys

+ ctrl+t (to display text-fonts box)
+ ctrl+z (to undo last-single shape)
+ ctrl+a (to select all shapes)
+ ctrl+c (copy last-selected shape)
+ ctrl+v (paste last-copied shape)

+ ctrl+mousedown (to copy/paste all shapes) +
+ +
+ + +
+ +
+ + + + +
+ Digit/Number:
+ +
+ +




+ + +
+ +
+ + + + + + +




+ + +
+ +
+ + +

Click right-side "x" button to close this popup.

+
+

Feedback

+
+ +
+ Enter your email too; if you want "direct" reply! +
+
+ +
+

Canvas Designer!

+

+ This tool is Open-Sourced! +

+ + Author: Muaz Khan + +
+

How to use in your own site?

+ +
+ +

WebRTC Dashboard

+ +


+ +
+

Ask Questions Here:

+ +
+
+ + + + + + diff --git a/Canvas-Designer/widget.html b/Canvas-Designer/widget.html index c500a751..a2e5c3e5 100644 --- a/Canvas-Designer/widget.html +++ b/Canvas-Designer/widget.html @@ -461,6 +461,12 @@ #marker-colors-list td:hover, #pencil-colors-list td:hover { border: 1px solid white; } + + #pdf-next, #pdf-prev, #pdf-close { + width: 22px; + vertical-align: middle; + cursor: pointer; + } @@ -506,7 +512,10 @@
+ + +
diff --git a/Canvas-Designer/widget.js b/Canvas-Designer/widget.js index 18f0cdd9..caa7cb64 100644 --- a/Canvas-Designer/widget.js +++ b/Canvas-Designer/widget.js @@ -1,4 +1,4 @@ -// Last time updated: 2018-09-24 6:09:38 AM UTC +// Last time updated: 2018-12-19 10:54:19 AM UTC // _______________ // Canvas-Designer @@ -2551,10 +2551,13 @@ var pdfHandler = { lastPdfURL: null, lastIndex: 0, - lastPageIndex: null, - removeWhiteBackground: true, + lastPointIndex: 0, + removeWhiteBackground: false, pdfPageContainer: document.getElementById('pdf-page-container'), pdfPagesList: document.getElementById('pdf-pages-list'), + pdfNext: document.getElementById('pdf-next'), + pdfPrev: document.getElementById('pdf-prev'), + pdfClose: document.getElementById('pdf-close'), pageNumber: 1, images: [], @@ -2587,10 +2590,13 @@ var renderContext = { canvasContext: ctx, - viewport: viewport, - background: 'rgba(0,0,0,0)' + viewport: viewport }; + if (pdfHandler.removeWhiteBackground === true) { + renderContext.background = 'rgba(0,0,0,0)'; + } + page.render(renderContext).then(function() { if (pdfHandler.removeWhiteBackground === true) { var imgd = ctx.getImageData(0, 0, cav.width, cav.height); @@ -2630,6 +2636,8 @@ var t = pdfHandler; pdfHandler.lastIndex = pdfHandler.images.length; var point = [lastPage, 60, 20, width, height, pdfHandler.lastIndex]; + + pdfHandler.lastPointIndex = points.length; points[points.length] = ['pdf', point, drawHelper.getOptions()]; pdfHandler.pdfPagesList.innerHTML = ''; @@ -2643,14 +2651,33 @@ option.selected = true; } } - pdfHandler.pdfPageContainer.style.top = '20px'; - pdfHandler.pdfPageContainer.style.left = point[2] + 'px'; pdfHandler.pdfPagesList.onchange = function() { pdfHandler.load(lastPdfURL); }; + pdfHandler.pdfNext.onclick = function() { + pdfHandler.pdfPagesList.selectedIndex++; + pdfHandler.pdfPagesList.onchange(); + }; + + pdfHandler.pdfPrev.onclick = function() { + pdfHandler.pdfPagesList.selectedIndex--; + pdfHandler.pdfPagesList.onchange(); + }; + + pdfHandler.pdfClose.onclick = function() { + pdfHandler.pdfPageContainer.style.display = 'none'; + }; + document.getElementById('drag-last-path').click(); + + pdfHandler.pdfPrev.src = data_uris.pdf_next; + pdfHandler.pdfNext.src = data_uris.pdf_prev; + pdfHandler.pdfClose.src = data_uris.pdf_close; + + pdfHandler.pdfPageContainer.style.top = '20px'; + pdfHandler.pdfPageContainer.style.left = (point[3] - parseInt(point[3] / 2)) + 'px'; pdfHandler.pdfPageContainer.style.display = 'block'; // share to webrtc @@ -2674,7 +2701,9 @@ var t = this; if (t.ismousedown) { - points[points.length] = ['pdf', [pdfHandler.lastPage, t.prevX, t.prevY, x - t.prevX, y - t.prevY, pdfHandler.lastIndex], drawHelper.getOptions()]; + if (points[pdfHandler.lastPointIndex]) { + points[pdfHandler.lastPointIndex] = ['pdf', [pdfHandler.lastPage, t.prevX, t.prevY, x - t.prevX, y - t.prevY, pdfHandler.lastIndex], drawHelper.getOptions()]; + } t.ismousedown = false; } @@ -2691,10 +2720,12 @@ }, reset_pos: function(x, y) { pdfHandler.pdfPageContainer.style.top = y + 'px'; - pdfHandler.pdfPageContainer.style.left = x + 'px'; + if (!points[pdfHandler.lastPointIndex]) return; + var point = points[pdfHandler.lastPointIndex][1]; + pdfHandler.pdfPageContainer.style.left = (point[1] + point[3] - parseInt(point[3] / 2) - parseInt(pdfHandler.pdfPageContainer.clientWidth / 2)) + 'px'; }, end: function() { - pdfHandler.pdfPageContainer.style.display = 'none'; + // pdfHandler.pdfPageContainer.style.display = 'none'; } }; @@ -2727,7 +2758,10 @@ lineWidth: icons.lineWidth || 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAYAAADDPmHLAAAACXBIWXMAAAsTAAALEwEAmpwYAAABgUlEQVR4nO3csU0DMRgF4AdKoKFhGsa5YViCHTIIomEVGlJAAwWhoYAj57N98vdJr7XQ70jBlvMSAAAAAAAAAAAAAAAAYHsuCq93k+QuyW3hdfnykuQpyWvrP+SnfZL7JMckH7JqjqdZ72ftTAWXSQ5pP5jRcjjNvrkp7YcxaqYZ+7O6x7QfxKh5nLE/vyrxT+BbkqsC6/B/70mulyzQxXcI7ZT4ADwXWIPzLJ59iQ/AQ4E1OE8Xs3cMbJNujoFJsouLoFr5vgjazdqZP7gK3pZur4IBAAAAAAAAAACAtjwK3ZZuH4XqB6j/LFw/wODp5ochU9oPY9RMM/ZndfoB2kU/wOD0A7CMfoBt0w8wuC5m7xjYJt0cAxP9ADWjH2Bg3V4FAwAAAAAAAAAAAG15FLot3T4K1Q9Q/1m4foDB080PQ6a0H8aomWbsz+r0A7SLfoDB6QdgGf0A26YfYHBdzN4xsE26OQYm+gFqRj/AwLq9CgYAAAAAAAAAAAAAAABq+gRzFKdEQnj1fQAAAABJRU5ErkJggg==', colorsPicker: icons.colorsPicker || 'data:image/png;base64,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', extraOptions: icons.extraOptions || 'data:image/png;base64,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', - undo: icons.undo || 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAYAAADDPmHLAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAABPxJREFUeNrsnVuIVVUYx9dpQiIdvOBtDGa8RILSQz4koWYvRZeXEkRztJd8ExGkSPBRSxDMB0ESxBBpSnyLMCGJChV6SSURpEkcZia8RjqpmTTT97HXYQYcZ87Za5+z917f7wd/BhT1eNZvfWutvdbeuzI0NOTALk/wFSAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAlBq9LyAkJeY5yWeSmabbz6gA70n6JX9LplkWwNoQME/yleSwZI7ktn6HzAFsoL3+R8kaBv5hnjTS63fR8DYrAL3eaAWg1xuuAPR6oxWAXm+4AtDrjVYA7fWfSNbSlPYqQLXX0/jGKgC93nAFoNcbrQD0esMVgF5vtALQ6w1XAHq90QqQR6//jwpgt9dXJE9LWqwKUAk91lWpVEI/Q4fkY0lnDv9/7f2/Sf5yyfEw/XlLck3SI+mT9PqfA0VswOD2y1mATt/4HQXuJPcl170oFyVnJeckl/zvIUDKsV4b/t2SVs5/Jb9Lzki+l/wg+aOMAuRxKniDL69DEUUrxDE/f5nWbAHKcix8ruTLyBp+tFyW7JY8jwDDrI+w14+Xu5IjkhctCzDPSK8fK/9IDrrkTiRTAmww2OvHii4tt/nrDlELoGP9FzT4Y6Mrh5diFaCTXl9T9KLShzEJMJexPlW6JDPKLsBGfxGEBk2XXyQL8xIgi82gQQchvCA5IVma27XkDIaADiZ+mawSXolhEniFxkydm5KVLANt56pkSQwXgqgG6dMtaY/hUjBzg/TR01ETY9kMWkc1SJW9sW0HUw3qiy6x34lFAOYG6aLf1eyYBGBuUH8OxCYAc4P6omcQl8UoQN5zg0G/M6enex8WXILv3Cj3cYS2XxHuCxh5gGRnrevfjPhT8qo/tTPJZ6pLnh/c7uXU4WqBZLrL/0aatyTHy34qeLxq0NXEXqWneVvH+UwtXoiXJZslR3M883DSJXczRTUEPG6l0Iwv+Ybv2fUyRfKGZJ9L7g9olgB6J9MKCwI0a26QVoCRTJa87ZL7AgaaIMEhKwJUWd/AlUIWAoxkkZ/HXG7wjmGbJQEaWQ2yFqCKHvPa6jd1GiHBRmsCNGpu0CgBqsxyydNL72UswNdWBci6GjRagCp6DPznDAXQW9jnWBUgy7lBswSorhz2ZyjBKusCZFENmilAlS2SBxkIsAcBwncY8xBAWe2G31eUNj/plUkECNthzEsA5U1/KTrkFPFMBAirBnkKoLwuuROwkbWc18Y9ilaBlX5PoejoDSHv++3euvfhXHL7fRCxvjSqx1eC6oMpioxeQv4g5Z99NvhfN/Dm0LFWCnkPASPZk2IY+Jw5QNjcoEgCTJB8W6cA3yBA2EqhSAIoz7j6NpJOI0D6atDnL8jMKNhne81PCmsR4FcESM98l7w+vq2An21HjQJ0I0A4RVwJPSU5VYMAvVwHCKeID7jQQ6qbXPIA67FoidF+SDjvkrME5sofDPOpS84RNKx6IUCx0aFgq1+tUAGMog+WPIAAttFl4ZVRfr2CADbQo+DbEcA2+iTW4whgF73w85HL+OVVCFAuLjh/GNQzIfQvLNLt4VAbrX4o0FfS9Ev7Lc5VACg3DAEIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgApeN/AQYAUjtq/Zx6jVYAAAAASUVORK5CYII=' + undo: icons.undo || 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAYAAADDPmHLAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAABPxJREFUeNrsnVuIVVUYx9dpQiIdvOBtDGa8RILSQz4koWYvRZeXEkRztJd8ExGkSPBRSxDMB0ESxBBpSnyLMCGJChV6SSURpEkcZia8RjqpmTTT97HXYQYcZ87Za5+z917f7wd/BhT1eNZvfWutvdbeuzI0NOTALk/wFSAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAlBq9LyAkJeY5yWeSmabbz6gA70n6JX9LplkWwNoQME/yleSwZI7ktn6HzAFsoL3+R8kaBv5hnjTS63fR8DYrAL3eaAWg1xuuAPR6oxWAXm+4AtDrjVYA7fWfSNbSlPYqQLXX0/jGKgC93nAFoNcbrQD0esMVgF5vtALQ6w1XAHq90QqQR6//jwpgt9dXJE9LWqwKUAk91lWpVEI/Q4fkY0lnDv9/7f2/Sf5yyfEw/XlLck3SI+mT9PqfA0VswOD2y1mATt/4HQXuJPcl170oFyVnJeckl/zvIUDKsV4b/t2SVs5/Jb9Lzki+l/wg+aOMAuRxKniDL69DEUUrxDE/f5nWbAHKcix8ruTLyBp+tFyW7JY8jwDDrI+w14+Xu5IjkhctCzDPSK8fK/9IDrrkTiRTAmww2OvHii4tt/nrDlELoGP9FzT4Y6Mrh5diFaCTXl9T9KLShzEJMJexPlW6JDPKLsBGfxGEBk2XXyQL8xIgi82gQQchvCA5IVma27XkDIaADiZ+mawSXolhEniFxkydm5KVLANt56pkSQwXgqgG6dMtaY/hUjBzg/TR01ETY9kMWkc1SJW9sW0HUw3qiy6x34lFAOYG6aLf1eyYBGBuUH8OxCYAc4P6omcQl8UoQN5zg0G/M6enex8WXILv3Cj3cYS2XxHuCxh5gGRnrevfjPhT8qo/tTPJZ6pLnh/c7uXU4WqBZLrL/0aatyTHy34qeLxq0NXEXqWneVvH+UwtXoiXJZslR3M883DSJXczRTUEPG6l0Iwv+Ybv2fUyRfKGZJ9L7g9olgB6J9MKCwI0a26QVoCRTJa87ZL7AgaaIMEhKwJUWd/AlUIWAoxkkZ/HXG7wjmGbJQEaWQ2yFqCKHvPa6jd1GiHBRmsCNGpu0CgBqsxyydNL72UswNdWBci6GjRagCp6DPznDAXQW9jnWBUgy7lBswSorhz2ZyjBKusCZFENmilAlS2SBxkIsAcBwncY8xBAWe2G31eUNj/plUkECNthzEsA5U1/KTrkFPFMBAirBnkKoLwuuROwkbWc18Y9ilaBlX5PoejoDSHv++3euvfhXHL7fRCxvjSqx1eC6oMpioxeQv4g5Z99NvhfN/Dm0LFWCnkPASPZk2IY+Jw5QNjcoEgCTJB8W6cA3yBA2EqhSAIoz7j6NpJOI0D6atDnL8jMKNhne81PCmsR4FcESM98l7w+vq2An21HjQJ0I0A4RVwJPSU5VYMAvVwHCKeID7jQQ6qbXPIA67FoidF+SDjvkrME5sofDPOpS84RNKx6IUCx0aFgq1+tUAGMog+WPIAAttFl4ZVRfr2CADbQo+DbEcA2+iTW4whgF73w85HL+OVVCFAuLjh/GNQzIfQvLNLt4VAbrX4o0FfS9Ev7Lc5VACg3DAEIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgApeN/AQYAUjtq/Zx6jVYAAAAASUVORK5CYII=', + pdf_next: icons.pdf_next || 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAYAAADDPmHLAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAAEnQAABJ0Ad5mH3gAAAAYdEVYdFNvZnR3YXJlAHBhaW50Lm5ldCA0LjEuMWMqnEsAAAYASURBVHhe7d3RjRs3EMZxPwRwAwFcQAA3EMANBK4gcAOBKwjcQOAKzh3EFcQdxB1cCdeBS1DyKVhYof/y6cghOeTMw+/l81niLme5XO5KenY6nVJgGKY4MExxYJjiwDDFgWGKA8MUB4YpDgxTHBimODBMcWCY4sAwxYFhigPDFAeGKQ4MUxwYpjgwTHFguIuHh4fT358/n/54//7sl9evH/Xrmzfnv7378OH8f798+fLvS/Hr7wDDVd3f3587Tp3444sXpx+ePzfx08uXp9/evj39+fHjSUVVvu/KMFyJOv33d+/OnUSd18PPr16dC22HYsDQOw3L6oCRnX6NThsaGco2rgJDr3TE6fxsObxbUTGqbavNGTD0RjvVa8eX1EaNTuU2eIWhJ9qZK3R8SSOCriLK7fEGQw80udP5lXbuSnRF4vm0gOFsqx7112hbvI4GGM6iI0VHDO3EHehytdzm2TCcQUO+rq9px+1EpzVPpwQMR1Pn7zTkP0aF7mURCcOR/vr0KVTnH7TNKvxyf4yG4ShaQaOdE4WHIsBwBG047ZRoZhcBhr1FO+c/RnOCWRNDDHvShmbnf0tFUO6rETDsKcKlXi09c1Dur94w7EULIbTh6avRt5Yx7EFLobTB6f90ehy5RoChNZ33PTy8sQqtFpb7sBcMreXQ/3SjTgUYWlrlel9Dr6erE7VlxKUhhpZWuKevna1C9bY+MeLuIYZWVpj4HZ1/tNlbEfSeEGJoxfvRX3b+wVMR9F4bwNCC96P/WucfPBVBz1EAQwuej/7HOv/gpQj0RHTZNisYtlLF0oZ4cGvnHzwUgd6/bJcVDFt5ve5/aucfPBSBHpwp22UBw1YeV/1qO188PLiih2XLdlnAsIV2Mm3ATKt3/qHHwhCGLbwN/7t0vvQ4DWDYwtP9/p06X3qsCWBYS0MUNXyG3TpfNLcq29oKw1peFn927PyD9TwAw1pasKBGj7Rz54v1ZwwxrDX7c327d75YrwpiWGvmBDBC54v1egCGtajBI0TpfLF+XAzDGrOuACJ1vmh7y+1ogWGNGVcA0Tr/UG5LCwxrjC6AqJ0v5fa0wLDGyAKI3PliuRaAYY1RBRC988VyLQDDGqMKoPY6WEeNiodeczWhC0B0JJfvfwuNHDsUQfgCkMhFUG5TCwxrjC4AiVoE5fa0wLDGjAKQiEVQbksLDGtRY0eIVATWzwRgWIsaPEqUInB7L0BmfxgkQhFYPxaGYS01jho90u5FcGf8WwQY1vLwRJDsXASWawCCYa1ZVwKktgg8bQMp29sKwxbU6Flqi0D/j15vth7fJYhhC2+fCt6pCHp8YwiGLTRJocbPtEsRWJ//BcMWXj8avnoRaHJats0Chq28fh3sykXQ66tiMGzl8TRwqCkCD5eHakPZLgsYtvL0GUHylCLw0Pk9PhN4wNCCh1XB77mlCLwsDNWeum6BoQXP3xN0+N6O1b956Hy1wfoDoZcwtLLCbwBSESijv52h5zeECYZWNITSRnlzWQSeOr/30S8YWvI+Fzio4z11vtwZ3/kjGFrSXMDDuXQ1PWf+lzC0pkqmjUzX9Vj2JRj2kD8WdbsRXxN/wLAHL9fU3mno7z3xu4RhL94mWR71WvK9BsOeVrkqmOHycnQUDHvL+cC3et3tewyGvekcl0XwlfWz/k+B4Qg5KfyPDoSRk74ShqNEL4LZnS8YjqQi0KUP7aCdeeh8wXC0aHMCTfg8dL5gOIN2yAq3j1v1vr37VBjOtOt9A811ev3uTwsMZ9ttXqDLvJ6//dcCQw90SvD662O30lE/4p5+Cww90Wjg7eNmt9BEz+tRfwlDj3T+XOG0oGIddS/fAoae6YaJxxFBVzArdfwBwxXo1KBhduZKokYkzVNWGOqvwXA1Oj2MKgZ1ut7L4yVdDQxXppFBM28NyRZzBq1QqsP1mnrt8v1Wh+FudG4WrcIdNHRrLiGXuehvd+xsgmGKA8MUB4YpDgxTHBimODBMcWCY4sAwxYFhigPDFAeGKQ4MUxwYpjgwTHFgmOLAMMWBYYoDwxTF6dk/P8ngRg7z9FEAAAAASUVORK5CYII=', + pdf_prev: icons.pdf_prev || 'data:image/png;base64,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', + pdf_close: icons.pdf_close || 'data:image/png;base64,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' }; var tools = { @@ -2822,7 +2856,7 @@ } addEvent(context.canvas, 'click', function() { - pdfHandler.pdfPageContainer.style.display = 'none'; + // pdfHandler.pdfPageContainer.style.display = 'none'; if (textHandler.text.length) { textHandler.appendPoints(); @@ -2933,8 +2967,9 @@ image.src = data_uris.dragSingle; } + decorateDragLastPath(); + if (tools.dragSingle === true) { - decorateDragLastPath(); document.getElementById('drag-last-path').style.display = 'block'; } @@ -2949,8 +2984,9 @@ image.src = data_uris.dragMultiple; } + decorateDragAllPaths(); + if (tools.dragMultiple === true) { - decorateDragAllPaths(); document.getElementById('drag-all-paths').style.display = 'block'; } @@ -3929,6 +3965,12 @@ return; } + if (event.data.clearCanvas) { + points = []; + drawHelper.redraw(); + return; + } + if (!event.data.canvasDesignerSyncData) return; // drawing is shared here (array of points) diff --git a/Canvas-Designer/widget.min.js b/Canvas-Designer/widget.min.js index 54fd15c6..6b2637f4 100644 --- a/Canvas-Designer/widget.min.js +++ b/Canvas-Designer/widget.min.js @@ -1,9 +1,10 @@ -// Last time updated: 2018-09-24 6:09:39 AM UTC +// Last time updated: 2018-12-19 10:54:19 AM UTC "use strict";!function(){function addEvent(element,eventType,callback){if(!(eventType.split(" ").length>1))return element.addEventListener?(element.addEventListener(eventType,callback,!1),!0):element.attachEvent?element.attachEvent("on"+eventType,callback):(element["on"+eventType]=callback,this);for(var events=eventType.split(" "),i=0;icompareWith?prefix+" + "+(pointToCompare-compareWith):pointToComparefirst-10&&xsecond-10&&yprev?otherPoint+(point-prev):otherPoint-(prev-point)},getXYWidthHeight:function(x,y,prevX,prevY,oldPoints){return"stretch-first"==oldPoints.pointsToMove&&(x>prevX?(oldPoints.x=oldPoints.x+(x-prevX),oldPoints.width=oldPoints.width-(x-prevX)):(oldPoints.x=oldPoints.x-(prevX-x),oldPoints.width=oldPoints.width+(prevX-x)),y>prevY?(oldPoints.y=oldPoints.y+(y-prevY),oldPoints.height=oldPoints.height-(y-prevY)):(oldPoints.y=oldPoints.y-(prevY-y),oldPoints.height=oldPoints.height+(prevY-y))),"stretch-second"==oldPoints.pointsToMove&&(oldPoints.width=x>prevX?oldPoints.width+(x-prevX):oldPoints.width-(prevX-x),yprevX?(oldPoints.x=oldPoints.x+(x-prevX),oldPoints.width=oldPoints.width-(x-prevX)):(oldPoints.x=oldPoints.x-(prevX-x),oldPoints.width=oldPoints.width+(prevX-x)),oldPoints.height=yx?prevX-x:x-prevX,yy=prevY>y?prevY-y:y-prevY;angle=(xx+yy)/(2*c),points[points.length]=["arc",[prevX+radius,prevY+radius,radius,angle,1],drawHelper.getOptions()];var arcRange=g.arcRange,arcRangeContainer=g.arcRangeContainer;arcRangeContainer.style.display="block",arcRange.focus(),arcRangeContainer.style.top=y+canvas.offsetTop+20+"px",arcRangeContainer.style.left=x+"px",arcRange.value=2}else g.isCircleDrawn&&!g.isCircleEnded&&this.end();g.ismousedown=!1,this.fixAllPoints()},mousemove:function(e){var g=this.global,x=e.pageX-canvas.offsetLeft,y=e.pageY-canvas.offsetTop,ismousedown=g.ismousedown,isCircleDrawn=g.isCircleDrawn,isCircleEnded=g.isCircledEnded;if(ismousedown&&!isCircleDrawn&&isCircleEnded){var prevX=g.prevX,prevY=g.prevY,radius=(x-prevX+(y-prevY))/3;tempContext.clearRect(0,0,2e3,2e3),drawHelper.arc(tempContext,[prevX+radius,prevY+radius,radius,2*Math.PI,!0])}},fixAllPoints:function(){for(var toFixed=this.toFixed,i=0;i0?value:.02)-.02),!key||13==key||39==key||40==key||37==key||38==key){var range=Math.PI*arcHandler.toFixed(value),p=points[points.length-1];if("arc"===p[0]){var point=p[1];points[points.length-1]=["arc",[point[0],point[1],point[2],range,g.isClockwise?1:0],p[2]],drawHelper.redraw()}}},toFixed:function(input){return Number(input).toFixed(1)},end:function(){var g=this.global;g.arcRangeContainer.style.display="none",g.arcRange.value=2,g.isCircleDrawn=!1,g.isCircleEnded=!0,drawHelper.redraw()}};arcHandler.init();var lineHandler={ismousedown:!1,prevX:0,prevY:0,mousedown:function(e){var x=e.pageX-canvas.offsetLeft,y=e.pageY-canvas.offsetTop,t=this;t.prevX=x,t.prevY=y,t.ismousedown=!0},mouseup:function(e){var x=e.pageX-canvas.offsetLeft,y=e.pageY-canvas.offsetTop,t=this;t.ismousedown&&(points[points.length]=["line",[t.prevX,t.prevY,x,y],drawHelper.getOptions()],t.ismousedown=!1)},mousemove:function(e){var x=e.pageX-canvas.offsetLeft,y=e.pageY-canvas.offsetTop,t=this;t.ismousedown&&(tempContext.clearRect(0,0,innerWidth,innerHeight),drawHelper.line(tempContext,[t.prevX,t.prevY,x,y]))}},arrowHandler={ismousedown:!1,prevX:0,prevY:0,arrowSize:10,mousedown:function(e){var x=e.pageX-canvas.offsetLeft,y=e.pageY-canvas.offsetTop,t=this;t.prevX=x,t.prevY=y,t.ismousedown=!0},mouseup:function(e){var x=e.pageX-canvas.offsetLeft,y=e.pageY-canvas.offsetTop,t=this;t.ismousedown&&(points[points.length]=["arrow",[t.prevX,t.prevY,x,y],drawHelper.getOptions()],t.ismousedown=!1)},mousemove:function(e){var x=e.pageX-canvas.offsetLeft,y=e.pageY-canvas.offsetTop,t=this;t.ismousedown&&(tempContext.clearRect(0,0,innerWidth,innerHeight),drawHelper.arrow(tempContext,[t.prevX,t.prevY,x,y]))}},rectHandler={ismousedown:!1,prevX:0,prevY:0,mousedown:function(e){var x=e.pageX-canvas.offsetLeft,y=e.pageY-canvas.offsetTop,t=this;t.prevX=x,t.prevY=y,t.ismousedown=!0},mouseup:function(e){var x=e.pageX-canvas.offsetLeft,y=e.pageY-canvas.offsetTop,t=this;t.ismousedown&&(points[points.length]=["rect",[t.prevX,t.prevY,x-t.prevX,y-t.prevY],drawHelper.getOptions()],t.ismousedown=!1)},mousemove:function(e){var x=e.pageX-canvas.offsetLeft,y=e.pageY-canvas.offsetTop,t=this;t.ismousedown&&(tempContext.clearRect(0,0,innerWidth,innerHeight),drawHelper.rect(tempContext,[t.prevX,t.prevY,x-t.prevX,y-t.prevY]))}},quadraticHandler={global:{ismousedown:!1,prevX:0,prevY:0,controlPointX:0,controlPointY:0,isFirstStep:!0,isLastStep:!1},mousedown:function(e){var g=this.global,x=e.pageX-canvas.offsetLeft,y=e.pageY-canvas.offsetTop;g.isLastStep||(g.prevX=x,g.prevY=y),g.ismousedown=!0,g.isLastStep&&g.ismousedown&&this.end(x,y)},mouseup:function(e){var g=this.global,x=e.pageX-canvas.offsetLeft,y=e.pageY-canvas.offsetTop;g.ismousedown&&g.isFirstStep&&(g.controlPointX=x,g.controlPointY=y,g.isFirstStep=!1,g.isLastStep=!0)},mousemove:function(e){var x=e.pageX-canvas.offsetLeft,y=e.pageY-canvas.offsetTop,g=this.global;tempContext.clearRect(0,0,innerWidth,innerHeight),g.ismousedown&&g.isFirstStep&&drawHelper.quadratic(tempContext,[g.prevX,g.prevY,x,y,x,y]),g.isLastStep&&drawHelper.quadratic(tempContext,[g.prevX,g.prevY,g.controlPointX,g.controlPointY,x,y])},end:function(x,y){var g=this.global;g.ismousedown&&(g.isLastStep=!1,g.isFirstStep=!0,g.ismousedown=!1,x=x||g.controlPointX||g.prevX,y=y||g.controlPointY||g.prevY,points[points.length]=["quadratic",[g.prevX,g.prevY,g.controlPointX,g.controlPointY,x,y],drawHelper.getOptions()])}},bezierHandler={global:{ismousedown:!1,prevX:0,prevY:0,firstControlPointX:0,firstControlPointY:0,secondControlPointX:0,secondControlPointY:0,isFirstStep:!0,isSecondStep:!1,isLastStep:!1},mousedown:function(e){var g=this.global,x=e.pageX-canvas.offsetLeft,y=e.pageY-canvas.offsetTop;g.isLastStep||g.isSecondStep||(g.prevX=x,g.prevY=y),g.ismousedown=!0,g.isLastStep&&g.ismousedown&&this.end(x,y),g.ismousedown&&g.isSecondStep&&(g.secondControlPointX=x,g.secondControlPointY=y,g.isSecondStep=!1,g.isLastStep=!0)},mouseup:function(e){var g=this.global,x=e.pageX-canvas.offsetLeft,y=e.pageY-canvas.offsetTop;g.ismousedown&&g.isFirstStep&&(g.firstControlPointX=x,g.firstControlPointY=y,g.isFirstStep=!1,g.isSecondStep=!0)},mousemove:function(e){var x=e.pageX-canvas.offsetLeft,y=e.pageY-canvas.offsetTop,g=this.global;tempContext.clearRect(0,0,innerWidth,innerHeight),g.ismousedown&&g.isFirstStep&&drawHelper.bezier(tempContext,[g.prevX,g.prevY,x,y,x,y,x,y]),g.ismousedown&&g.isSecondStep&&drawHelper.bezier(tempContext,[g.prevX,g.prevY,g.firstControlPointX,g.firstControlPointY,x,y,x,y]),g.isLastStep&&drawHelper.bezier(tempContext,[g.prevX,g.prevY,g.firstControlPointX,g.firstControlPointY,g.secondControlPointX,g.secondControlPointY,x,y])},end:function(x,y){var g=this.global;g.ismousedown&&(g.isLastStep=g.isSecondStep=!1,g.isFirstStep=!0,g.ismousedown=!1,g.secondControlPointX=g.secondControlPointX||g.firstControlPointX,g.secondControlPointY=g.secondControlPointY||g.firstControlPointY,x=x||g.secondControlPointX,y=y||g.secondControlPointY,points[points.length]=["bezier",[g.prevX,g.prevY,g.firstControlPointX,g.firstControlPointY,g.secondControlPointX,g.secondControlPointY,x,y],drawHelper.getOptions()])}},zoomHandler={scale:1,up:function(e){this.scale+=.01,this.apply()},down:function(e){this.scale-=.01,this.apply()},apply:function(){tempContext.scale(this.scale,this.scale),context.scale(this.scale,this.scale),drawHelper.redraw()},icons:{up:function(ctx){ctx.font="22px Verdana",ctx.strokeText("+",10,30)},down:function(ctx){ctx.font="22px Verdana",ctx.strokeText("-",15,30)}}},FileSelector=function(){function selectFile(callback,multiple,accept){var file=document.createElement("input");file.type="file",multiple&&(file.multiple=!0),file.accept=accept||"image/*",file.onchange=function(){return multiple?file.files.length?void callback(file.files):void console.error("No file selected."):file.files[0]?(callback(file.files[0]),void file.parentNode.removeChild(file)):void console.error("No file selected.")},file.style.display="none",(document.body||document.documentElement).appendChild(file),fireClickEvent(file)}function fireClickEvent(element){var evt=new window.MouseEvent("click",{view:window,bubbles:!0,cancelable:!0,button:0,buttons:0,mozInputSource:1});element.dispatchEvent(evt)}var selector=this;selector.selectSingleFile=selectFile,selector.selectMultipleFiles=function(callback){selectFile(callback,!0)}},imageHandler={lastImageURL:null,lastImageIndex:0,images:[],ismousedown:!1,prevX:0,prevY:0,load:function(width,height){var t=imageHandler;points[points.length]=["image",[imageHandler.lastImageURL,t.prevX,t.prevY,width,height,imageHandler.lastImageIndex],drawHelper.getOptions()],document.getElementById("drag-last-path").click(),syncPoints(!0)},mousedown:function(e){var x=e.pageX-canvas.offsetLeft,y=e.pageY-canvas.offsetTop,t=this;t.prevX=x,t.prevY=y,t.ismousedown=!0},mouseup:function(e){var x=e.pageX-canvas.offsetLeft,y=e.pageY-canvas.offsetTop,t=this;t.ismousedown&&(points[points.length]=["image",[imageHandler.lastImageURL,t.prevX,t.prevY,x-t.prevX,y-t.prevY,imageHandler.lastImageIndex],drawHelper.getOptions()],t.ismousedown=!1)},mousemove:function(e){var x=e.pageX-canvas.offsetLeft,y=e.pageY-canvas.offsetTop,t=this;t.ismousedown&&(tempContext.clearRect(0,0,innerWidth,innerHeight),drawHelper.image(tempContext,[imageHandler.lastImageURL,t.prevX,t.prevY,x-t.prevX,y-t.prevY,imageHandler.lastImageIndex]))}},pdfHandler={lastPdfURL:null,lastIndex:0,lastPageIndex:null,removeWhiteBackground:!0,pdfPageContainer:document.getElementById("pdf-page-container"),pdfPagesList:document.getElementById("pdf-pages-list"),pageNumber:1,images:[],ismousedown:!1,prevX:0,prevY:0,getPage:function(pageNumber,callback){if(pageNumber=parseInt(pageNumber)||1,!pdfHandler.pdf)return pdfjsLib.disableWorker=!1,void pdfjsLib.getDocument(pdfHandler.lastPdfURL).then(function(pdf){pdfHandler.pdf=pdf,pdfHandler.getPage(pageNumber,callback)});var pdf=pdfHandler.pdf;pdf.getPage(pageNumber).then(function(page){pdfHandler.pageNumber=pageNumber;var scale=1.5,viewport=page.getViewport(scale),cav=document.createElement("canvas"),ctx=cav.getContext("2d");cav.height=viewport.height,cav.width=viewport.width;var renderContext={canvasContext:ctx,viewport:viewport,background:"rgba(0,0,0,0)"};page.render(renderContext).then(function(){if(!0===pdfHandler.removeWhiteBackground){for(var imgd=ctx.getImageData(0,0,cav.width,cav.height),pix=imgd.data,newColor={r:0,g:0,b:0,a:0},i=0,n=pix.length;i'}),row+="",pencilColorsList.innerHTML+=row}),Array.prototype.slice.call(pencilColorsList.getElementsByTagName("td")).forEach(function(td){addEvent(td,"mouseover",function(){var elColor=td.getAttribute("data-color");pencilSelectedColor2.style.backgroundColor="#"+elColor,fillStyleText.value=elColor}),addEvent(td,"click",function(){var elColor=td.getAttribute("data-color");pencilSelectedColor.style.backgroundColor=pencilSelectedColor2.style.backgroundColor="#"+elColor,fillStyleText.value=elColor,pencilColorContainer.style.display="none"})}),addEvent(canvas,"click",function(){hideContainers(),pencilContainer.style.display="block",pencilContainer.style.top=canvas.offsetTop+1+"px",pencilContainer.style.left=canvas.offsetLeft+canvas.clientWidth+"px",fillStyleText.focus()}),addEvent(btnPencilDone,"click",function(){pencilContainer.style.display="none",pencilColorContainer.style.display="none",pencilLineWidth=strokeStyleText.value,pencilStrokeStyle=hexToRGBA(fillStyleText.value,alpha)}),addEvent(pencilSelectedColor,"click",function(){pencilColorContainer.style.display="block"})}function decorateMarker(){function hexToRGBA(h,alpha){return"rgba("+hexToRGB(h).join(",")+","+alpha+")"}var colors=[["FFFFFF","006600","000099","CC0000","8C4600"],["CCCCCC","00CC00","6633CC","FF0000","B28500"],["666666","66FFB2","006DD9","FF7373","FF9933"],["333333","26FF26","6699FF","CC33FF","FFCC99"],["000000","CCFF99","BFDFFF","FFBFBF","FFFF33"]],context=getContext("marker-icon"),image=new Image;image.onload=function(){context.drawImage(image,4,4,32,32),bindEvent(context,"Marker")},image.src=data_uris.marker;var markerContainer=find("marker-container"),markerColorContainer=find("marker-fill-colors"),strokeStyleText=find("marker-stroke-style"),markerColorsList=find("marker-colors-list"),fillStyleText=find("marker-fill-style"),markerSelectedColor=find("marker-selected-color"),markerSelectedColor2=find("marker-selected-color-2"),btnMarkerDone=find("marker-done"),canvas=context.canvas,alpha=.2;markerStrokeStyle=hexToRGBA(fillStyleText.value,alpha),markerSelectedColor.style.backgroundColor=markerSelectedColor2.style.backgroundColor="#"+fillStyleText.value,colors.forEach(function(colorRow){var row="";colorRow.forEach(function(color){row+=''}),row+="",markerColorsList.innerHTML+=row}),Array.prototype.slice.call(markerColorsList.getElementsByTagName("td")).forEach(function(td){addEvent(td,"mouseover",function(){var elColor=td.getAttribute("data-color");markerSelectedColor2.style.backgroundColor="#"+elColor,fillStyleText.value=elColor}),addEvent(td,"click",function(){var elColor=td.getAttribute("data-color");markerSelectedColor.style.backgroundColor=markerSelectedColor2.style.backgroundColor="#"+elColor,fillStyleText.value=elColor,markerColorContainer.style.display="none"})}),addEvent(canvas,"click",function(){hideContainers(),markerContainer.style.display="block",markerContainer.style.top=canvas.offsetTop+1+"px",markerContainer.style.left=canvas.offsetLeft+canvas.clientWidth+"px",fillStyleText.focus()}),addEvent(btnMarkerDone,"click",function(){markerContainer.style.display="none",markerColorContainer.style.display="none",markerLineWidth=strokeStyleText.value,markerStrokeStyle=hexToRGBA(fillStyleText.value,alpha)}),addEvent(markerSelectedColor,"click",function(){markerColorContainer.style.display="block"})}function decorateEraser(){var context=getContext("eraser-icon"),image=new Image;image.onload=function(){context.drawImage(image,4,4,32,32),bindEvent(context,"Eraser")},image.src=data_uris.eraser}function decorateText(){var context=getContext("text-icon"),image=new Image;image.onload=function(){context.drawImage(image,4,4,32,32),bindEvent(context,"Text")},image.src=data_uris.text}function decorateImage(){var context=getContext("image-icon"),image=new Image;image.onload=function(){context.drawImage(image,4,4,32,32),bindEvent(context,"Image")},image.src=data_uris.image}function decoratePDF(){var context=getContext("pdf-icon"),image=new Image;image.onload=function(){context.drawImage(image,4,4,32,32),bindEvent(context,"Pdf")},image.src=data_uris.pdf}function decorateArc(){var context=getContext("arc"),image=new Image;image.onload=function(){context.drawImage(image,4,4,32,32),bindEvent(context,"Arc")},image.src=data_uris.arc}function decorateRect(){var context=getContext("rectangle"),image=new Image;image.onload=function(){context.drawImage(image,4,4,32,32),bindEvent(context,"Rectangle")},image.src=data_uris.rectangle}function decorateQuadratic(){var context=getContext("quadratic-curve"),image=new Image;image.onload=function(){context.drawImage(image,4,4,32,32),bindEvent(context,"QuadraticCurve")},image.src=data_uris.quadratic}function decorateBezier(){var context=getContext("bezier-curve"),image=new Image;image.onload=function(){context.drawImage(image,4,4,32,32),bindEvent(context,"Bezier")},image.src=data_uris.bezier}function decorateLineWidth(){var context=getContext("line-width"),image=new Image;image.onload=function(){context.drawImage(image,4,4,32,32)},image.src=data_uris.lineWidth;var lineWidthContainer=find("line-width-container"),lineWidthText=find("line-width-text"),btnLineWidthDone=find("line-width-done"),canvas=(document.getElementsByTagName("h1")[0],context.canvas);addEvent(canvas,"click",function(){hideContainers(),lineWidthContainer.style.display="block",lineWidthContainer.style.top=canvas.offsetTop+1+"px",lineWidthContainer.style.left=canvas.offsetLeft+canvas.clientWidth+"px",lineWidthText.focus()}),addEvent(btnLineWidthDone,"click",function(){lineWidthContainer.style.display="none",lineWidth=lineWidthText.value})}function decorateColors(){var context=getContext("colors"),image=new Image;image.onload=function(){context.drawImage(image,4,4,32,32)},image.src=data_uris.colorsPicker;var colorsContainer=find("colors-container"),strokeStyleText=find("stroke-style"),fillStyleText=find("fill-style"),btnColorsDone=find("colors-done"),canvas=(document.getElementsByTagName("h1")[0],context.canvas);addEvent(canvas,"click",function(){hideContainers(),colorsContainer.style.display="block",colorsContainer.style.top=canvas.offsetTop+1+"px",colorsContainer.style.left=canvas.offsetLeft+canvas.clientWidth+"px",strokeStyleText.focus()}),addEvent(btnColorsDone,"click",function(){colorsContainer.style.display="none",strokeStyle=strokeStyleText.value,fillStyle=fillStyleText.value})}function decorateAdditionalOptions(){var context=getContext("additional"),image=new Image;image.onload=function(){context.drawImage(image,4,4,32,32)},image.src=data_uris.extraOptions;var additionalContainer=find("additional-container"),btnAdditionalClose=find("additional-close"),canvas=(document.getElementsByTagName("h1")[0],context.canvas),globalAlphaSelect=find("globalAlpha-select"),globalCompositeOperationSelect=find("globalCompositeOperation-select");addEvent(canvas,"click",function(){hideContainers(),additionalContainer.style.display="block",additionalContainer.style.top=canvas.offsetTop+1+"px",additionalContainer.style.left=canvas.offsetLeft+canvas.clientWidth+"px"}),addEvent(btnAdditionalClose,"click",function(){additionalContainer.style.display="none",globalAlpha=globalAlphaSelect.value,globalCompositeOperation=globalCompositeOperationSelect.value,lineCap=lineCapSelect.value,lineJoin=lineJoinSelect.value})}function btnDesignerPreviewClicked(){codeText.parentNode.style.display="none",optionsContainer.style.display="none",hideContainers(),endLastPath()}function btnCodePreviewClicked(){codeText.parentNode.style.display="block",optionsContainer.style.display="block",codeText.focus(),common.updateTextArea(),setHeightForCodeAndOptionsContainer(),hideContainers(),endLastPath()}function setHeightForCodeAndOptionsContainer(){codeText.style.width=innerWidth-optionsContainer.clientWidth-30+"px",codeText.style.height=innerHeight-40+"px",codeText.style.marginLeft=optionsContainer.clientWidth+"px",optionsContainer.style.height=innerHeight+"px"}var cache={},lineCapSelect=find("lineCap-select"),lineJoinSelect=find("lineJoin-select");find("tool-box").style.height=innerHeight+"px",!0===tools.dragSingle&&(decorateDragLastPath(),document.getElementById("drag-last-path").style.display="block"),!0===tools.dragMultiple&&(decorateDragAllPaths(),document.getElementById("drag-all-paths").style.display="block"),!0===tools.line&&(decorateLine(),document.getElementById("line").style.display="block"),!0===tools.undo&&(decorateUndo(),document.getElementById("undo").style.display="block"),!0===tools.arrow&&(decorateArrow(),document.getElementById("arrow").style.display="block"),!0===tools.zoom&&(decoreZoomUp(),decoreZoomDown(),document.getElementById("zoom-up").style.display="block",document.getElementById("zoom-down").style.display="block"),!0===tools.pencil&&(decoratePencil(),document.getElementById("pencil-icon").style.display="block"),!0===tools.marker&&(decorateMarker(),document.getElementById("marker-icon").style.display="block"),!0===tools.eraser&&(decorateEraser(),document.getElementById("eraser-icon").style.display="block"),!0===tools.text&&(decorateText(),document.getElementById("text-icon").style.display="block"),!0===tools.image&&(decorateImage(),document.getElementById("image-icon").style.display="block"),!0===tools.pdf&&(decoratePDF(),document.getElementById("pdf-icon").style.display="block"),!0===tools.arc&&(decorateArc(),document.getElementById("arc").style.display="block"),!0===tools.rectangle&&(decorateRect(),document.getElementById("rectangle").style.display="block"),!0===tools.quadratic&&(decorateQuadratic(),document.getElementById("quadratic-curve").style.display="block"),!0===tools.bezier&&(decorateBezier(),document.getElementById("bezier-curve").style.display="block"),!0===tools.lineWidth&&(decorateLineWidth(),document.getElementById("line-width").style.display="block"),!0===tools.colorsPicker&&(decorateColors(),document.getElementById("colors").style.display="block"),!0===tools.extraOptions&&(decorateAdditionalOptions(),document.getElementById("additional").style.display="block");var designPreview=find("design-preview"),codePreview=find("code-preview");window.selectBtn=function(btn,isSkipWebRTCMessage){codePreview.className=designPreview.className="",btn==designPreview?designPreview.className="preview-selected":codePreview.className="preview-selected",!isSkipWebRTCMessage&&window.connection&&connection.numberOfConnectedUsers>=1?connection.send({btnSelected:btn.id}):btn==designPreview?btnDesignerPreviewClicked():btnCodePreviewClicked()},addEvent(designPreview,"click",function(){selectBtn(designPreview),btnDesignerPreviewClicked()}),addEvent(codePreview,"click",function(){selectBtn(codePreview),btnCodePreviewClicked()});var codeText=find("code-text"),optionsContainer=find("options-container"),isAbsolute=find("is-absolute-points");addEvent(find("is-shorten-code"),"change",common.updateTextArea),addEvent(isAbsolute,"change",common.updateTextArea)}();var canvas=tempContext.canvas,isTouch="createTouch"in document;addEvent(canvas,isTouch?"touchstart mousedown":"mousedown",function(e){isTouch&&(e=e.pageX?e:e.touches.length?e.touches[0]:{pageX:0,pageY:0});var cache=is;cache.isLine?lineHandler.mousedown(e):cache.isArc?arcHandler.mousedown(e):cache.isRectangle?rectHandler.mousedown(e):cache.isQuadraticCurve?quadraticHandler.mousedown(e):cache.isBezierCurve?bezierHandler.mousedown(e):cache.isDragLastPath||cache.isDragAllPaths?dragHelper.mousedown(e):cache.isPencil?pencilHandler.mousedown(e):cache.isEraser?eraserHandler.mousedown(e):cache.isText?textHandler.mousedown(e):cache.isImage?imageHandler.mousedown(e):cache.isPdf?pdfHandler.mousedown(e):cache.isArrow?arrowHandler.mousedown(e):cache.isMarker&&markerHandler.mousedown(e),!cache.isPdf&&drawHelper.redraw(),preventStopEvent(e)}),addEvent(canvas,isTouch?"touchend touchcancel mouseup":"mouseup",function(e){!isTouch||e&&"pageX"in e||(e=e&&e.touches&&e.touches.length?e.touches[0]:e&&e.changedTouches&&e.changedTouches.length?e.changedTouches[0]:{pageX:0,pageY:0});var cache=is;cache.isLine?lineHandler.mouseup(e):cache.isArc?arcHandler.mouseup(e):cache.isRectangle?rectHandler.mouseup(e):cache.isQuadraticCurve?quadraticHandler.mouseup(e):cache.isBezierCurve?bezierHandler.mouseup(e):cache.isDragLastPath||cache.isDragAllPaths?dragHelper.mouseup(e):cache.isPencil?pencilHandler.mouseup(e):cache.isEraser?eraserHandler.mouseup(e):cache.isText?textHandler.mouseup(e):cache.isImage?imageHandler.mouseup(e):cache.isPdf?pdfHandler.mousedown(e):cache.isArrow?arrowHandler.mouseup(e):cache.isMarker&&markerHandler.mouseup(e),!cache.isPdf&&drawHelper.redraw(),syncPoints(!(!is.isDragAllPaths&&!is.isDragLastPath)),preventStopEvent(e)}),addEvent(canvas,isTouch?"touchmove mousemove":"mousemove",function(e){isTouch&&(e=e.pageX?e:e.touches.length?e.touches[0]:{pageX:0,pageY:0});var cache=is;cache.isLine?lineHandler.mousemove(e):cache.isArc?arcHandler.mousemove(e):cache.isRectangle?rectHandler.mousemove(e):cache.isQuadraticCurve?quadraticHandler.mousemove(e):cache.isBezierCurve?bezierHandler.mousemove(e):cache.isDragLastPath||cache.isDragAllPaths?dragHelper.mousemove(e):cache.isPencil?pencilHandler.mousemove(e):cache.isEraser?eraserHandler.mousemove(e):cache.isText?textHandler.mousemove(e):cache.isImage?imageHandler.mousemove(e):cache.isPdf?pdfHandler.mousedown(e):cache.isArrow?arrowHandler.mousemove(e):cache.isMarker&&markerHandler.mousemove(e),preventStopEvent(e)});var keyCode;addEvent(document,"keydown",onkeydown),addEvent(document,"keyup",onkeyup),addEvent(document,"keypress",onkeypress),addEvent(document,"paste",onTextFromClipboard);var uid,lastPointIndex=0;window.addEventListener("message",function(event){if(event.data){if(uid||(uid=event.data.uid),event.data.captureStream)return void webrtcHandler.createOffer(function(sdp){sdp.uid=uid,window.parent.postMessage(sdp,"*")});if(event.data.sdp)return void webrtcHandler.setRemoteDescription(event.data);if(event.data.genDataURL){var dataURL=context.canvas.toDataURL(event.data.format,1);return void window.parent.postMessage({dataURL:dataURL,uid:uid},"*")}if(event.data.undo&&points.length){var index=event.data.index;if("all"===index)return points=[],drawHelper.redraw(),void syncPoints(!0);if(index.numberOfLastShapes){try{points.length-=index.numberOfLastShapes}catch(e){points=[]}return drawHelper.redraw(),void syncPoints(!0)}if(-1===index)return points.length=points.length-1,drawHelper.redraw(),void syncPoints(!0);if(points[index]){for(var newPoints=[],i=0;ix?prevX-x:x-prevX,yy=prevY>y?prevY-y:y-prevY;angle=(xx+yy)/(2*c),points[points.length]=["arc",[prevX+radius,prevY+radius,radius,angle,1],drawHelper.getOptions()];var arcRange=g.arcRange,arcRangeContainer=g.arcRangeContainer;arcRangeContainer.style.display="block",arcRange.focus(),arcRangeContainer.style.top=y+canvas.offsetTop+20+"px",arcRangeContainer.style.left=x+"px",arcRange.value=2}else g.isCircleDrawn&&!g.isCircleEnded&&this.end();g.ismousedown=!1,this.fixAllPoints()},mousemove:function(e){var g=this.global,x=e.pageX-canvas.offsetLeft,y=e.pageY-canvas.offsetTop,ismousedown=g.ismousedown,isCircleDrawn=g.isCircleDrawn,isCircleEnded=g.isCircledEnded;if(ismousedown&&!isCircleDrawn&&isCircleEnded){var prevX=g.prevX,prevY=g.prevY,radius=(x-prevX+(y-prevY))/3;tempContext.clearRect(0,0,2e3,2e3),drawHelper.arc(tempContext,[prevX+radius,prevY+radius,radius,2*Math.PI,!0])}},fixAllPoints:function(){for(var toFixed=this.toFixed,i=0;i0?value:.02)-.02),!key||13==key||39==key||40==key||37==key||38==key){var range=Math.PI*arcHandler.toFixed(value),p=points[points.length-1];if("arc"===p[0]){var point=p[1];points[points.length-1]=["arc",[point[0],point[1],point[2],range,g.isClockwise?1:0],p[2]],drawHelper.redraw()}}},toFixed:function(input){return Number(input).toFixed(1)},end:function(){var g=this.global;g.arcRangeContainer.style.display="none",g.arcRange.value=2,g.isCircleDrawn=!1,g.isCircleEnded=!0,drawHelper.redraw()}};arcHandler.init();var lineHandler={ismousedown:!1,prevX:0,prevY:0,mousedown:function(e){var x=e.pageX-canvas.offsetLeft,y=e.pageY-canvas.offsetTop,t=this;t.prevX=x,t.prevY=y,t.ismousedown=!0},mouseup:function(e){var x=e.pageX-canvas.offsetLeft,y=e.pageY-canvas.offsetTop,t=this;t.ismousedown&&(points[points.length]=["line",[t.prevX,t.prevY,x,y],drawHelper.getOptions()],t.ismousedown=!1)},mousemove:function(e){var x=e.pageX-canvas.offsetLeft,y=e.pageY-canvas.offsetTop,t=this;t.ismousedown&&(tempContext.clearRect(0,0,innerWidth,innerHeight),drawHelper.line(tempContext,[t.prevX,t.prevY,x,y]))}},arrowHandler={ismousedown:!1,prevX:0,prevY:0,arrowSize:10,mousedown:function(e){var x=e.pageX-canvas.offsetLeft,y=e.pageY-canvas.offsetTop,t=this;t.prevX=x,t.prevY=y,t.ismousedown=!0},mouseup:function(e){var x=e.pageX-canvas.offsetLeft,y=e.pageY-canvas.offsetTop,t=this;t.ismousedown&&(points[points.length]=["arrow",[t.prevX,t.prevY,x,y],drawHelper.getOptions()],t.ismousedown=!1)},mousemove:function(e){var x=e.pageX-canvas.offsetLeft,y=e.pageY-canvas.offsetTop,t=this;t.ismousedown&&(tempContext.clearRect(0,0,innerWidth,innerHeight),drawHelper.arrow(tempContext,[t.prevX,t.prevY,x,y]))}},rectHandler={ismousedown:!1,prevX:0,prevY:0,mousedown:function(e){var x=e.pageX-canvas.offsetLeft,y=e.pageY-canvas.offsetTop,t=this;t.prevX=x,t.prevY=y,t.ismousedown=!0},mouseup:function(e){var x=e.pageX-canvas.offsetLeft,y=e.pageY-canvas.offsetTop,t=this;t.ismousedown&&(points[points.length]=["rect",[t.prevX,t.prevY,x-t.prevX,y-t.prevY],drawHelper.getOptions()],t.ismousedown=!1)},mousemove:function(e){var x=e.pageX-canvas.offsetLeft,y=e.pageY-canvas.offsetTop,t=this;t.ismousedown&&(tempContext.clearRect(0,0,innerWidth,innerHeight),drawHelper.rect(tempContext,[t.prevX,t.prevY,x-t.prevX,y-t.prevY]))}},quadraticHandler={global:{ismousedown:!1,prevX:0,prevY:0,controlPointX:0,controlPointY:0,isFirstStep:!0,isLastStep:!1},mousedown:function(e){var g=this.global,x=e.pageX-canvas.offsetLeft,y=e.pageY-canvas.offsetTop;g.isLastStep||(g.prevX=x,g.prevY=y),g.ismousedown=!0,g.isLastStep&&g.ismousedown&&this.end(x,y)},mouseup:function(e){var g=this.global,x=e.pageX-canvas.offsetLeft,y=e.pageY-canvas.offsetTop;g.ismousedown&&g.isFirstStep&&(g.controlPointX=x,g.controlPointY=y,g.isFirstStep=!1,g.isLastStep=!0)},mousemove:function(e){var x=e.pageX-canvas.offsetLeft,y=e.pageY-canvas.offsetTop,g=this.global;tempContext.clearRect(0,0,innerWidth,innerHeight),g.ismousedown&&g.isFirstStep&&drawHelper.quadratic(tempContext,[g.prevX,g.prevY,x,y,x,y]),g.isLastStep&&drawHelper.quadratic(tempContext,[g.prevX,g.prevY,g.controlPointX,g.controlPointY,x,y])},end:function(x,y){var g=this.global;g.ismousedown&&(g.isLastStep=!1,g.isFirstStep=!0,g.ismousedown=!1,x=x||g.controlPointX||g.prevX,y=y||g.controlPointY||g.prevY,points[points.length]=["quadratic",[g.prevX,g.prevY,g.controlPointX,g.controlPointY,x,y],drawHelper.getOptions()])}},bezierHandler={global:{ismousedown:!1,prevX:0,prevY:0,firstControlPointX:0,firstControlPointY:0,secondControlPointX:0,secondControlPointY:0,isFirstStep:!0,isSecondStep:!1,isLastStep:!1},mousedown:function(e){var g=this.global,x=e.pageX-canvas.offsetLeft,y=e.pageY-canvas.offsetTop;g.isLastStep||g.isSecondStep||(g.prevX=x,g.prevY=y),g.ismousedown=!0,g.isLastStep&&g.ismousedown&&this.end(x,y),g.ismousedown&&g.isSecondStep&&(g.secondControlPointX=x,g.secondControlPointY=y,g.isSecondStep=!1,g.isLastStep=!0)},mouseup:function(e){var g=this.global,x=e.pageX-canvas.offsetLeft,y=e.pageY-canvas.offsetTop;g.ismousedown&&g.isFirstStep&&(g.firstControlPointX=x,g.firstControlPointY=y,g.isFirstStep=!1,g.isSecondStep=!0)},mousemove:function(e){var x=e.pageX-canvas.offsetLeft,y=e.pageY-canvas.offsetTop,g=this.global;tempContext.clearRect(0,0,innerWidth,innerHeight),g.ismousedown&&g.isFirstStep&&drawHelper.bezier(tempContext,[g.prevX,g.prevY,x,y,x,y,x,y]),g.ismousedown&&g.isSecondStep&&drawHelper.bezier(tempContext,[g.prevX,g.prevY,g.firstControlPointX,g.firstControlPointY,x,y,x,y]),g.isLastStep&&drawHelper.bezier(tempContext,[g.prevX,g.prevY,g.firstControlPointX,g.firstControlPointY,g.secondControlPointX,g.secondControlPointY,x,y])},end:function(x,y){var g=this.global;g.ismousedown&&(g.isLastStep=g.isSecondStep=!1,g.isFirstStep=!0,g.ismousedown=!1,g.secondControlPointX=g.secondControlPointX||g.firstControlPointX,g.secondControlPointY=g.secondControlPointY||g.firstControlPointY,x=x||g.secondControlPointX,y=y||g.secondControlPointY,points[points.length]=["bezier",[g.prevX,g.prevY,g.firstControlPointX,g.firstControlPointY,g.secondControlPointX,g.secondControlPointY,x,y],drawHelper.getOptions()])}},zoomHandler={scale:1,up:function(e){this.scale+=.01,this.apply()},down:function(e){this.scale-=.01,this.apply()},apply:function(){tempContext.scale(this.scale,this.scale),context.scale(this.scale,this.scale),drawHelper.redraw()},icons:{up:function(ctx){ctx.font="22px Verdana",ctx.strokeText("+",10,30)},down:function(ctx){ctx.font="22px Verdana",ctx.strokeText("-",15,30)}}},FileSelector=function(){function selectFile(callback,multiple,accept){var file=document.createElement("input");file.type="file",multiple&&(file.multiple=!0),file.accept=accept||"image/*",file.onchange=function(){return multiple?file.files.length?void callback(file.files):void console.error("No file selected."):file.files[0]?(callback(file.files[0]),void file.parentNode.removeChild(file)):void console.error("No file selected.")},file.style.display="none",(document.body||document.documentElement).appendChild(file),fireClickEvent(file)}function fireClickEvent(element){var evt=new window.MouseEvent("click",{view:window,bubbles:!0,cancelable:!0,button:0,buttons:0,mozInputSource:1});element.dispatchEvent(evt)}var selector=this;selector.selectSingleFile=selectFile,selector.selectMultipleFiles=function(callback){selectFile(callback,!0)}},imageHandler={lastImageURL:null,lastImageIndex:0,images:[],ismousedown:!1,prevX:0,prevY:0,load:function(width,height){var t=imageHandler;points[points.length]=["image",[imageHandler.lastImageURL,t.prevX,t.prevY,width,height,imageHandler.lastImageIndex],drawHelper.getOptions()],document.getElementById("drag-last-path").click(),syncPoints(!0)},mousedown:function(e){var x=e.pageX-canvas.offsetLeft,y=e.pageY-canvas.offsetTop,t=this;t.prevX=x,t.prevY=y,t.ismousedown=!0},mouseup:function(e){var x=e.pageX-canvas.offsetLeft,y=e.pageY-canvas.offsetTop,t=this;t.ismousedown&&(points[points.length]=["image",[imageHandler.lastImageURL,t.prevX,t.prevY,x-t.prevX,y-t.prevY,imageHandler.lastImageIndex],drawHelper.getOptions()],t.ismousedown=!1)},mousemove:function(e){var x=e.pageX-canvas.offsetLeft,y=e.pageY-canvas.offsetTop,t=this;t.ismousedown&&(tempContext.clearRect(0,0,innerWidth,innerHeight),drawHelper.image(tempContext,[imageHandler.lastImageURL,t.prevX,t.prevY,x-t.prevX,y-t.prevY,imageHandler.lastImageIndex]))}},pdfHandler={lastPdfURL:null,lastIndex:0,lastPointIndex:0,removeWhiteBackground:!1,pdfPageContainer:document.getElementById("pdf-page-container"),pdfPagesList:document.getElementById("pdf-pages-list"),pdfNext:document.getElementById("pdf-next"),pdfPrev:document.getElementById("pdf-prev"),pdfClose:document.getElementById("pdf-close"),pageNumber:1,images:[],ismousedown:!1,prevX:0,prevY:0,getPage:function(pageNumber,callback){if(pageNumber=parseInt(pageNumber)||1,!pdfHandler.pdf)return pdfjsLib.disableWorker=!1,void pdfjsLib.getDocument(pdfHandler.lastPdfURL).then(function(pdf){pdfHandler.pdf=pdf,pdfHandler.getPage(pageNumber,callback)});var pdf=pdfHandler.pdf;pdf.getPage(pageNumber).then(function(page){pdfHandler.pageNumber=pageNumber;var scale=1.5,viewport=page.getViewport(scale),cav=document.createElement("canvas"),ctx=cav.getContext("2d");cav.height=viewport.height,cav.width=viewport.width;var renderContext={canvasContext:ctx,viewport:viewport};!0===pdfHandler.removeWhiteBackground&&(renderContext.background="rgba(0,0,0,0)"),page.render(renderContext).then(function(){if(!0===pdfHandler.removeWhiteBackground){for(var imgd=ctx.getImageData(0,0,cav.width,cav.height),pix=imgd.data,newColor={r:0,g:0,b:0,a:0},i=0,n=pix.length;i'}),row+="",pencilColorsList.innerHTML+=row}),Array.prototype.slice.call(pencilColorsList.getElementsByTagName("td")).forEach(function(td){addEvent(td,"mouseover",function(){var elColor=td.getAttribute("data-color");pencilSelectedColor2.style.backgroundColor="#"+elColor,fillStyleText.value=elColor}),addEvent(td,"click",function(){var elColor=td.getAttribute("data-color");pencilSelectedColor.style.backgroundColor=pencilSelectedColor2.style.backgroundColor="#"+elColor,fillStyleText.value=elColor,pencilColorContainer.style.display="none"})}),addEvent(canvas,"click",function(){hideContainers(),pencilContainer.style.display="block",pencilContainer.style.top=canvas.offsetTop+1+"px",pencilContainer.style.left=canvas.offsetLeft+canvas.clientWidth+"px",fillStyleText.focus()}),addEvent(btnPencilDone,"click",function(){pencilContainer.style.display="none",pencilColorContainer.style.display="none",pencilLineWidth=strokeStyleText.value,pencilStrokeStyle=hexToRGBA(fillStyleText.value,alpha)}),addEvent(pencilSelectedColor,"click",function(){pencilColorContainer.style.display="block"})}function decorateMarker(){function hexToRGBA(h,alpha){return"rgba("+hexToRGB(h).join(",")+","+alpha+")"}var colors=[["FFFFFF","006600","000099","CC0000","8C4600"],["CCCCCC","00CC00","6633CC","FF0000","B28500"],["666666","66FFB2","006DD9","FF7373","FF9933"],["333333","26FF26","6699FF","CC33FF","FFCC99"],["000000","CCFF99","BFDFFF","FFBFBF","FFFF33"]],context=getContext("marker-icon"),image=new Image;image.onload=function(){context.drawImage(image,4,4,32,32),bindEvent(context,"Marker")},image.src=data_uris.marker;var markerContainer=find("marker-container"),markerColorContainer=find("marker-fill-colors"),strokeStyleText=find("marker-stroke-style"),markerColorsList=find("marker-colors-list"),fillStyleText=find("marker-fill-style"),markerSelectedColor=find("marker-selected-color"),markerSelectedColor2=find("marker-selected-color-2"),btnMarkerDone=find("marker-done"),canvas=context.canvas,alpha=.2;markerStrokeStyle=hexToRGBA(fillStyleText.value,alpha),markerSelectedColor.style.backgroundColor=markerSelectedColor2.style.backgroundColor="#"+fillStyleText.value,colors.forEach(function(colorRow){var row="";colorRow.forEach(function(color){row+=''}),row+="",markerColorsList.innerHTML+=row}),Array.prototype.slice.call(markerColorsList.getElementsByTagName("td")).forEach(function(td){addEvent(td,"mouseover",function(){var elColor=td.getAttribute("data-color");markerSelectedColor2.style.backgroundColor="#"+elColor,fillStyleText.value=elColor}),addEvent(td,"click",function(){var elColor=td.getAttribute("data-color");markerSelectedColor.style.backgroundColor=markerSelectedColor2.style.backgroundColor="#"+elColor,fillStyleText.value=elColor,markerColorContainer.style.display="none"})}),addEvent(canvas,"click",function(){hideContainers(),markerContainer.style.display="block",markerContainer.style.top=canvas.offsetTop+1+"px",markerContainer.style.left=canvas.offsetLeft+canvas.clientWidth+"px",fillStyleText.focus()}),addEvent(btnMarkerDone,"click",function(){markerContainer.style.display="none",markerColorContainer.style.display="none",markerLineWidth=strokeStyleText.value,markerStrokeStyle=hexToRGBA(fillStyleText.value,alpha)}),addEvent(markerSelectedColor,"click",function(){markerColorContainer.style.display="block"})}function decorateEraser(){var context=getContext("eraser-icon"),image=new Image;image.onload=function(){context.drawImage(image,4,4,32,32),bindEvent(context,"Eraser")},image.src=data_uris.eraser}function decorateText(){var context=getContext("text-icon"),image=new Image;image.onload=function(){context.drawImage(image,4,4,32,32),bindEvent(context,"Text")},image.src=data_uris.text}function decorateImage(){var context=getContext("image-icon"),image=new Image;image.onload=function(){context.drawImage(image,4,4,32,32),bindEvent(context,"Image")},image.src=data_uris.image}function decoratePDF(){var context=getContext("pdf-icon"),image=new Image;image.onload=function(){context.drawImage(image,4,4,32,32),bindEvent(context,"Pdf")},image.src=data_uris.pdf}function decorateArc(){var context=getContext("arc"),image=new Image;image.onload=function(){context.drawImage(image,4,4,32,32),bindEvent(context,"Arc")},image.src=data_uris.arc}function decorateRect(){var context=getContext("rectangle"),image=new Image;image.onload=function(){context.drawImage(image,4,4,32,32),bindEvent(context,"Rectangle")},image.src=data_uris.rectangle}function decorateQuadratic(){var context=getContext("quadratic-curve"),image=new Image;image.onload=function(){context.drawImage(image,4,4,32,32),bindEvent(context,"QuadraticCurve")},image.src=data_uris.quadratic}function decorateBezier(){var context=getContext("bezier-curve"),image=new Image;image.onload=function(){context.drawImage(image,4,4,32,32),bindEvent(context,"Bezier")},image.src=data_uris.bezier}function decorateLineWidth(){var context=getContext("line-width"),image=new Image;image.onload=function(){context.drawImage(image,4,4,32,32)},image.src=data_uris.lineWidth;var lineWidthContainer=find("line-width-container"),lineWidthText=find("line-width-text"),btnLineWidthDone=find("line-width-done"),canvas=(document.getElementsByTagName("h1")[0],context.canvas);addEvent(canvas,"click",function(){hideContainers(),lineWidthContainer.style.display="block",lineWidthContainer.style.top=canvas.offsetTop+1+"px",lineWidthContainer.style.left=canvas.offsetLeft+canvas.clientWidth+"px",lineWidthText.focus()}),addEvent(btnLineWidthDone,"click",function(){lineWidthContainer.style.display="none",lineWidth=lineWidthText.value})}function decorateColors(){var context=getContext("colors"),image=new Image;image.onload=function(){context.drawImage(image,4,4,32,32)},image.src=data_uris.colorsPicker;var colorsContainer=find("colors-container"),strokeStyleText=find("stroke-style"),fillStyleText=find("fill-style"),btnColorsDone=find("colors-done"),canvas=(document.getElementsByTagName("h1")[0],context.canvas);addEvent(canvas,"click",function(){hideContainers(),colorsContainer.style.display="block",colorsContainer.style.top=canvas.offsetTop+1+"px",colorsContainer.style.left=canvas.offsetLeft+canvas.clientWidth+"px",strokeStyleText.focus()}),addEvent(btnColorsDone,"click",function(){colorsContainer.style.display="none",strokeStyle=strokeStyleText.value,fillStyle=fillStyleText.value})}function decorateAdditionalOptions(){var context=getContext("additional"),image=new Image;image.onload=function(){context.drawImage(image,4,4,32,32)},image.src=data_uris.extraOptions;var additionalContainer=find("additional-container"),btnAdditionalClose=find("additional-close"),canvas=(document.getElementsByTagName("h1")[0],context.canvas),globalAlphaSelect=find("globalAlpha-select"),globalCompositeOperationSelect=find("globalCompositeOperation-select");addEvent(canvas,"click",function(){hideContainers(),additionalContainer.style.display="block",additionalContainer.style.top=canvas.offsetTop+1+"px",additionalContainer.style.left=canvas.offsetLeft+canvas.clientWidth+"px"}),addEvent(btnAdditionalClose,"click",function(){additionalContainer.style.display="none",globalAlpha=globalAlphaSelect.value,globalCompositeOperation=globalCompositeOperationSelect.value,lineCap=lineCapSelect.value,lineJoin=lineJoinSelect.value})}function btnDesignerPreviewClicked(){codeText.parentNode.style.display="none",optionsContainer.style.display="none",hideContainers(),endLastPath()}function btnCodePreviewClicked(){codeText.parentNode.style.display="block",optionsContainer.style.display="block",codeText.focus(),common.updateTextArea(),setHeightForCodeAndOptionsContainer(),hideContainers(),endLastPath()}function setHeightForCodeAndOptionsContainer(){codeText.style.width=innerWidth-optionsContainer.clientWidth-30+"px",codeText.style.height=innerHeight-40+"px",codeText.style.marginLeft=optionsContainer.clientWidth+"px",optionsContainer.style.height=innerHeight+"px"}var cache={},lineCapSelect=find("lineCap-select"),lineJoinSelect=find("lineJoin-select");find("tool-box").style.height=innerHeight+"px",decorateDragLastPath(),!0===tools.dragSingle&&(document.getElementById("drag-last-path").style.display="block"),decorateDragAllPaths(),!0===tools.dragMultiple&&(document.getElementById("drag-all-paths").style.display="block"),!0===tools.line&&(decorateLine(),document.getElementById("line").style.display="block"),!0===tools.undo&&(decorateUndo(),document.getElementById("undo").style.display="block"),!0===tools.arrow&&(decorateArrow(),document.getElementById("arrow").style.display="block"),!0===tools.zoom&&(decoreZoomUp(),decoreZoomDown(),document.getElementById("zoom-up").style.display="block",document.getElementById("zoom-down").style.display="block"),!0===tools.pencil&&(decoratePencil(),document.getElementById("pencil-icon").style.display="block"),!0===tools.marker&&(decorateMarker(),document.getElementById("marker-icon").style.display="block"),!0===tools.eraser&&(decorateEraser(),document.getElementById("eraser-icon").style.display="block"),!0===tools.text&&(decorateText(),document.getElementById("text-icon").style.display="block"),!0===tools.image&&(decorateImage(),document.getElementById("image-icon").style.display="block"),!0===tools.pdf&&(decoratePDF(),document.getElementById("pdf-icon").style.display="block"),!0===tools.arc&&(decorateArc(),document.getElementById("arc").style.display="block"),!0===tools.rectangle&&(decorateRect(),document.getElementById("rectangle").style.display="block"),!0===tools.quadratic&&(decorateQuadratic(),document.getElementById("quadratic-curve").style.display="block"),!0===tools.bezier&&(decorateBezier(),document.getElementById("bezier-curve").style.display="block"),!0===tools.lineWidth&&(decorateLineWidth(), +document.getElementById("line-width").style.display="block"),!0===tools.colorsPicker&&(decorateColors(),document.getElementById("colors").style.display="block"),!0===tools.extraOptions&&(decorateAdditionalOptions(),document.getElementById("additional").style.display="block");var designPreview=find("design-preview"),codePreview=find("code-preview");window.selectBtn=function(btn,isSkipWebRTCMessage){codePreview.className=designPreview.className="",btn==designPreview?designPreview.className="preview-selected":codePreview.className="preview-selected",!isSkipWebRTCMessage&&window.connection&&connection.numberOfConnectedUsers>=1?connection.send({btnSelected:btn.id}):btn==designPreview?btnDesignerPreviewClicked():btnCodePreviewClicked()},addEvent(designPreview,"click",function(){selectBtn(designPreview),btnDesignerPreviewClicked()}),addEvent(codePreview,"click",function(){selectBtn(codePreview),btnCodePreviewClicked()});var codeText=find("code-text"),optionsContainer=find("options-container"),isAbsolute=find("is-absolute-points");addEvent(find("is-shorten-code"),"change",common.updateTextArea),addEvent(isAbsolute,"change",common.updateTextArea)}();var canvas=tempContext.canvas,isTouch="createTouch"in document;addEvent(canvas,isTouch?"touchstart mousedown":"mousedown",function(e){isTouch&&(e=e.pageX?e:e.touches.length?e.touches[0]:{pageX:0,pageY:0});var cache=is;cache.isLine?lineHandler.mousedown(e):cache.isArc?arcHandler.mousedown(e):cache.isRectangle?rectHandler.mousedown(e):cache.isQuadraticCurve?quadraticHandler.mousedown(e):cache.isBezierCurve?bezierHandler.mousedown(e):cache.isDragLastPath||cache.isDragAllPaths?dragHelper.mousedown(e):cache.isPencil?pencilHandler.mousedown(e):cache.isEraser?eraserHandler.mousedown(e):cache.isText?textHandler.mousedown(e):cache.isImage?imageHandler.mousedown(e):cache.isPdf?pdfHandler.mousedown(e):cache.isArrow?arrowHandler.mousedown(e):cache.isMarker&&markerHandler.mousedown(e),!cache.isPdf&&drawHelper.redraw(),preventStopEvent(e)}),addEvent(canvas,isTouch?"touchend touchcancel mouseup":"mouseup",function(e){!isTouch||e&&"pageX"in e||(e=e&&e.touches&&e.touches.length?e.touches[0]:e&&e.changedTouches&&e.changedTouches.length?e.changedTouches[0]:{pageX:0,pageY:0});var cache=is;cache.isLine?lineHandler.mouseup(e):cache.isArc?arcHandler.mouseup(e):cache.isRectangle?rectHandler.mouseup(e):cache.isQuadraticCurve?quadraticHandler.mouseup(e):cache.isBezierCurve?bezierHandler.mouseup(e):cache.isDragLastPath||cache.isDragAllPaths?dragHelper.mouseup(e):cache.isPencil?pencilHandler.mouseup(e):cache.isEraser?eraserHandler.mouseup(e):cache.isText?textHandler.mouseup(e):cache.isImage?imageHandler.mouseup(e):cache.isPdf?pdfHandler.mousedown(e):cache.isArrow?arrowHandler.mouseup(e):cache.isMarker&&markerHandler.mouseup(e),!cache.isPdf&&drawHelper.redraw(),syncPoints(!(!is.isDragAllPaths&&!is.isDragLastPath)),preventStopEvent(e)}),addEvent(canvas,isTouch?"touchmove mousemove":"mousemove",function(e){isTouch&&(e=e.pageX?e:e.touches.length?e.touches[0]:{pageX:0,pageY:0});var cache=is;cache.isLine?lineHandler.mousemove(e):cache.isArc?arcHandler.mousemove(e):cache.isRectangle?rectHandler.mousemove(e):cache.isQuadraticCurve?quadraticHandler.mousemove(e):cache.isBezierCurve?bezierHandler.mousemove(e):cache.isDragLastPath||cache.isDragAllPaths?dragHelper.mousemove(e):cache.isPencil?pencilHandler.mousemove(e):cache.isEraser?eraserHandler.mousemove(e):cache.isText?textHandler.mousemove(e):cache.isImage?imageHandler.mousemove(e):cache.isPdf?pdfHandler.mousedown(e):cache.isArrow?arrowHandler.mousemove(e):cache.isMarker&&markerHandler.mousemove(e),preventStopEvent(e)});var keyCode;addEvent(document,"keydown",onkeydown),addEvent(document,"keyup",onkeyup),addEvent(document,"keypress",onkeypress),addEvent(document,"paste",onTextFromClipboard);var uid,lastPointIndex=0;window.addEventListener("message",function(event){if(event.data){if(uid||(uid=event.data.uid),event.data.captureStream)return void webrtcHandler.createOffer(function(sdp){sdp.uid=uid,window.parent.postMessage(sdp,"*")});if(event.data.sdp)return void webrtcHandler.setRemoteDescription(event.data);if(event.data.genDataURL){var dataURL=context.canvas.toDataURL(event.data.format,1);return void window.parent.postMessage({dataURL:dataURL,uid:uid},"*")}if(event.data.undo&&points.length){var index=event.data.index;if("all"===index)return points=[],drawHelper.redraw(),void syncPoints(!0);if(index.numberOfLastShapes){try{points.length-=index.numberOfLastShapes}catch(e){points=[]}return drawHelper.redraw(),void syncPoints(!0)}if(-1===index)return points.length=points.length-1,drawHelper.redraw(),void syncPoints(!0);if(points[index]){for(var newPoints=[],i=0;iWebRTC Screen Sharing +WebRTC Screen Sharing + +WebRTC Screen Sharing ## How to install? @@ -14,7 +16,8 @@ Try any of the below URL. Replace `your_room_id` with real room-id: ``` https://webrtcweb.com/screen?s=your_room_id -https://cdn.rawgit.com/muaz-khan/Chrome-Extensions/master/desktopCapture-p2p/index.html +https://www.webrtc-experiment.com/screen/?s=your_room_id +https://cdn.rawgit.com/muaz-khan/Chrome-Extensions/master/desktopCapture-p2p/screen-receivers/index.php?s=your_room_id ``` ## Developer Notes @@ -40,12 +43,12 @@ var resultingURL = 'https://webrtcweb.com/screen?s=' + connection.sessionid; Replace above line with your own server/website: ```javascript -var resultingURL = 'https://yourWebSite.com/index.html?s=' + connection.sessionid; +var resultingURL = 'https://yourWebSite.com/screen-receivers/index.php?s=' + connection.sessionid; ``` -You can find `index.html` here: +You can find `index.php` here: -* [desktopCapture-p2p/index.html](https://github.com/muaz-khan/Chrome-Extensions/blob/master/desktopCapture-p2p/index.html) +* [desktopCapture-p2p/screen-receivers/index.php](https://github.com/muaz-khan/Chrome-Extensions/blob/master/desktopCapture-p2p/screen-receivers/index.php) ## How to publish it for your own business? diff --git a/Chrome-Extensions/desktopCapture-p2p/background/captureCamera.js b/Chrome-Extensions/desktopCapture-p2p/background/captureCamera.js new file mode 100644 index 00000000..2baa155c --- /dev/null +++ b/Chrome-Extensions/desktopCapture-p2p/background/captureCamera.js @@ -0,0 +1,68 @@ +function captureCamera(callback) { + var supported = navigator.mediaDevices.getSupportedConstraints(); + var constraints = {}; + + if (enableCamera) { + constraints.video = { + width: { + min: 640, + ideal: 1920, + max: 1920 + }, + height: { + min: 400, + ideal: 1080 + } + }; + + if (supported.aspectRatio) { + constraints.video.aspectRatio = 1.777777778; + } + + if (supported.frameRate) { + constraints.video.frameRate = { + ideal: 30 + }; + } + + if (cameraDevice && cameraDevice.length) { + constraints.video.deviceId = cameraDevice; + } + } + + if (enableMicrophone) { + constraints.audio = {}; + + if (microphoneDevice && microphoneDevice.length) { + constraints.audio.deviceId = microphoneDevice; + } + + if (supported.echoCancellation) { + constraints.audio.echoCancellation = true; + } + } + + navigator.mediaDevices.getUserMedia(constraints).then(function(stream) { + initVideoPlayer(stream); + callback(stream); + + if (enableCamera && !enableScreen) { + openVideoPreview(stream); + } + }).catch(function(error) { + // error.message === 'Failed due to shutdown' + chrome.tabs.create({ + url: 'extension-pages/camera-mic.html' + }); + + // setDefaults(); + chrome.storage.sync.set({ + enableTabCaptureAPI: 'false', + enableMicrophone: 'false', + enableCamera: 'false', + enableScreen: 'false', + isSharingOn: 'false', + enableSpeakers: 'false' + }); + }); +} diff --git a/Chrome-Extensions/desktopCapture-p2p/background/captureDesktop.js b/Chrome-Extensions/desktopCapture-p2p/background/captureDesktop.js new file mode 100644 index 00000000..bf92bad7 --- /dev/null +++ b/Chrome-Extensions/desktopCapture-p2p/background/captureDesktop.js @@ -0,0 +1,177 @@ +function captureDesktop() { + false && chrome.storage.sync.set({ + isSharingOn: 'false' + }); + + if (connection && connection.attachStreams[0]) { + setDefaults(); + + connection && connection.attachStreams.forEach(function(stream) { + stream.getTracks().forEach(function(track) { + track.stop(); + }); + }); + + chrome.storage.sync.set({ + enableTabCaptureAPI: 'false', + enableMicrophone: 'false', + enableCamera: 'false', + enableScreen: 'false', + isSharingOn: 'false', + enableSpeakers: 'false' + }); + return; + } + + chrome.browserAction.setTitle({ + title: 'Capturing Desktop' + }); + + desktop_id = null; + constraints = null; + room_password = ''; + room_id = ''; + codecs = 'default'; + bandwidth = null; + + enableTabCaptureAPI = null; + enableMicrophone = null; + enableSpeakers = null; + enableCamera = null; + enableScreen = null; + isSharingOn = null; + + streaming_method = 'RTCMultiConnection'; + + room_url_box = true; + + chrome.storage.sync.get(null, function(items) { + var resolutions = {}; + + if (items['room_password']) { + room_password = items['room_password']; + } + + if (items['room_id']) { + room_id = items['room_id']; + } + + if (items['streaming_method']) { + streaming_method = items['streaming_method']; + } + + if (items['room_url_box'] === 'false') { + room_url_box = false; + } + + if (items['codecs']) { + codecs = items['codecs']; + } + + if (items['bandwidth']) { + bandwidth = items['bandwidth']; + } + + if (items['enableTabCaptureAPI'] == 'true') { + enableTabCaptureAPI = items['enableTabCaptureAPI']; + } + + if (items['enableMicrophone'] == 'true') { + enableMicrophone = items['enableMicrophone']; + } + + if (items['enableSpeakers'] == 'true') { + enableSpeakers = items['enableSpeakers']; + } + + if (items['enableCamera'] == 'true') { + enableCamera = items['enableCamera']; + } + + if (items['enableScreen'] == 'true') { + enableScreen = items['enableScreen']; + } + + if (items['enableTabCaptureAPI'] == 'true') { + enableTabCaptureAPI = items['enableTabCaptureAPI']; + } + + if (items['isSharingOn'] == 'true') { + isSharingOn = items['isSharingOn']; + } + + var _resolutions = items['resolutions']; + if (!_resolutions) { + _resolutions = 'fit-screen'; + chrome.storage.sync.set({ + resolutions: 'fit-screen' + }, function() {}); + } + + if (_resolutions === 'fit-screen') { + // resolutions.maxWidth = screen.availWidth; + // resolutions.maxHeight = screen.availHeight; + + resolutions.maxWidth = screen.width; + resolutions.maxHeight = screen.height; + } + + if (_resolutions === '4K') { + resolutions.maxWidth = 3840; + resolutions.maxHeight = 2160; + } + + if (_resolutions === '1080p') { + resolutions.maxWidth = 1920; + resolutions.maxHeight = 1080; + } + + if (_resolutions === '720p') { + resolutions.maxWidth = 1280; + resolutions.maxHeight = 720; + } + + if (_resolutions === '360p') { + resolutions.maxWidth = 640; + resolutions.maxHeight = 360; + } + + if (_resolutions === '4K') { + alert('"4K" resolutions is not stable in Chrome. Please try "fit-screen" instead.'); + } + + var sources = ['screen', 'window', 'tab']; + + if (enableSpeakers) { + sources.push('audio'); + } + + if (enableTabCaptureAPI) { + captureTabUsingTabCapture(resolutions); + return; + } + + if (enableCamera || enableMicrophone) { + captureCamera(function(stream) { + if (!enableScreen) { + gotStream(stream); + return; + } + + desktop_id = chrome.desktopCapture.chooseDesktopMedia(sources, function(chromeMediaSourceId, opts) { + opts = opts || {}; + opts.resolutions = resolutions; + opts.stream = stream; + onAccessApproved(chromeMediaSourceId, opts); + }); + }); + return; + } + + desktop_id = chrome.desktopCapture.chooseDesktopMedia(sources, function(chromeMediaSourceId, opts) { + opts = opts || {}; + opts.resolutions = resolutions; + onAccessApproved(chromeMediaSourceId, opts); + }); + }); +} diff --git a/Chrome-Extensions/desktopCapture-p2p/background/captureTabUsingTabCapture.js b/Chrome-Extensions/desktopCapture-p2p/background/captureTabUsingTabCapture.js new file mode 100644 index 00000000..104d4576 --- /dev/null +++ b/Chrome-Extensions/desktopCapture-p2p/background/captureTabUsingTabCapture.js @@ -0,0 +1,44 @@ +function captureTabUsingTabCapture(resolutions) { + chrome.tabs.query({ + active: true, + currentWindow: true + }, function(arrayOfTabs) { + var activeTab = arrayOfTabs[0]; + var activeTabId = activeTab.id; // or do whatever you need + + var constraints = { + video: true, + videoConstraints: { + mandatory: { + chromeMediaSource: 'tab', + maxWidth: resolutions.maxWidth, + maxHeight: resolutions.maxHeight, + minWidth: resolutions.minWidth, + minHeight: resolutions.minHeight, + minAspectRatio: getAspectRatio(resolutions.maxWidth, resolutions.maxHeight), + maxAspectRatio: getAspectRatio(resolutions.maxWidth, resolutions.maxHeight), + minFrameRate: 64, + maxFrameRate: 128 + } + } + }; + + if (!!enableSpeakers) { + constraints.audio = true; + constraints.audioConstraints = { + mandatory: { + echoCancellation: true + }, + optional: [{ + googDisableLocalEcho: false // https://www.chromestatus.com/feature/5056629556903936 + }] + }; + } + + // chrome.tabCapture.onStatusChanged.addListener(function(event) { /* event.status */ }); + + chrome.tabCapture.capture(constraints, function(stream) { + gotTabCaptureStream(stream, constraints); + }); + }); +} diff --git a/Chrome-Extensions/desktopCapture-p2p/background/common.js b/Chrome-Extensions/desktopCapture-p2p/background/common.js new file mode 100644 index 00000000..cfd6bf9f --- /dev/null +++ b/Chrome-Extensions/desktopCapture-p2p/background/common.js @@ -0,0 +1,78 @@ +function getAspectRatio(w, h) { + function gcd(a, b) { + return (b == 0) ? a : gcd(b, a % b); + } + var r = gcd(w, h); + return (w / r) / (h / r); +} + +function openVideoPreview(stream) { + // var win = window.open("extension-pages/video.html?src=" + URL.createObjectURL(stream), "_blank", "top=0,left=0"); + var win = window.open("extension-pages/video.html", "_blank", "top=0,left=0"); + var timer = setInterval(function() { + if (win.closed) { + // captureDesktop(); + setDefaults(); + + clearInterval(timer); + } + }, 1000); + return win; +} + +function initVideoPlayer(stream) { + var videoPlayer = document.createElement('video'); + videoPlayer.muted = !enableTabCaptureAPI; + videoPlayer.volume = !!enableTabCaptureAPI; + videoPlayer.autoplay = true; + videoPlayer.srcObject = stream; + videoPlayers.push(videoPlayer); +} + +function addStreamStopListener(stream, callback) { + var streamEndedEvent = 'ended'; + if ('oninactive' in stream) { + streamEndedEvent = 'inactive'; + } + stream.addEventListener(streamEndedEvent, function() { + callback(); + callback = function() {}; + }, false); + stream.getAudioTracks().forEach(function(track) { + track.addEventListener(streamEndedEvent, function() { + callback(); + callback = function() {}; + }, false); + }); + stream.getVideoTracks().forEach(function(track) { + track.addEventListener(streamEndedEvent, function() { + callback(); + callback = function() {}; + }, false); + }); +} + +function getUserMediaError(e) { + setDefaults(); + + chrome.windows.create({ + url: "data:text/html,

getUserMediaError: " + JSON.stringify(e, null, '
') + "


Constraints used:
" + JSON.stringify(constraints, null, '
') + '
', + type: 'popup', + width: screen.width / 2, + height: 170 + }); +} + +function setBadgeText(text) { + chrome.browserAction.setBadgeBackgroundColor({ + color: [255, 0, 0, 255] + }); + + chrome.browserAction.setBadgeText({ + text: text + '' + }); + + chrome.browserAction.setTitle({ + title: text + ' users are viewing your screen!' + }); +} diff --git a/Chrome-Extensions/desktopCapture-p2p/background/globals.js b/Chrome-Extensions/desktopCapture-p2p/background/globals.js new file mode 100644 index 00000000..f2b462b8 --- /dev/null +++ b/Chrome-Extensions/desktopCapture-p2p/background/globals.js @@ -0,0 +1,27 @@ +var runtimePort; + +var desktop_id; +var constraints; +var room_password = ''; +var room_id = ''; +var codecs = 'default'; +var bandwidth; + +var enableTabCaptureAPI; +var enableMicrophone; +var enableSpeakers; +var enableCamera; +var enableScreen; +var isSharingOn; + +var streaming_method = 'RTCMultiConnection'; + +var room_url_box = true; + +var connection; // RTCMultiConnection +var popup_id; + +var videoPlayers = []; + +var microphoneDevice = false; +var cameraDevice = false; diff --git a/Chrome-Extensions/desktopCapture-p2p/background/gotStream.js b/Chrome-Extensions/desktopCapture-p2p/background/gotStream.js new file mode 100644 index 00000000..4efa13b1 --- /dev/null +++ b/Chrome-Extensions/desktopCapture-p2p/background/gotStream.js @@ -0,0 +1,50 @@ +function gotStream(stream) { + if (!stream) { + setDefaults(); + + chrome.windows.create({ + url: "data:text/html,

Internal error occurred while capturing the screen.

", + type: 'popup', + width: screen.width / 2, + height: 170 + }); + return; + } + + chrome.browserAction.setTitle({ + title: 'Connecting to WebSockets server.' + }); + + chrome.browserAction.disable(); + + addStreamStopListener(stream, function() { + setDefaults(); + chrome.runtime.reload(); + }); + + // as it is reported that if you drag chrome screen's status-bar + // and scroll up/down the screen-viewer page. + // chrome auto-stops the screen without firing any 'onended' event. + // chrome also hides screen status bar. + chrome.windows.create({ + url: chrome.extension.getURL('_generated_background_page.html'), + type: 'popup', + focused: false, + width: 1, + height: 1, + top: parseInt(screen.height), + left: parseInt(screen.width) + }, function(win) { + var background_page_id = win.id; + + setTimeout(function() { + // chrome.windows.remove(background_page_id); + }, 3000); + }); + + setupWebRTCConnection(stream); + + chrome.browserAction.setIcon({ + path: 'images/pause22.png' + }); +} diff --git a/Chrome-Extensions/desktopCapture-p2p/background/gotTabCaptureStream.js b/Chrome-Extensions/desktopCapture-p2p/background/gotTabCaptureStream.js new file mode 100644 index 00000000..3a7e89b0 --- /dev/null +++ b/Chrome-Extensions/desktopCapture-p2p/background/gotTabCaptureStream.js @@ -0,0 +1,22 @@ +function gotTabCaptureStream(stream, constraints) { + if (!stream) { + if (constraints.audio === true) { + enableSpeakers = false; + captureTabUsingTabCapture(resolutions); + return; + } + return alert('still no tabCapture stream'); + chrome.runtime.reload(); + return; + } + + var newStream = new MediaStream(); + + stream.getTracks().forEach(function(track) { + newStream.addTrack(track); + }); + + initVideoPlayer(newStream); + + gotStream(newStream); +} diff --git a/Chrome-Extensions/desktopCapture-p2p/background/helpers/AntMediaWrapper.js b/Chrome-Extensions/desktopCapture-p2p/background/helpers/AntMediaWrapper.js new file mode 100644 index 00000000..18f8bd2d --- /dev/null +++ b/Chrome-Extensions/desktopCapture-p2p/background/helpers/AntMediaWrapper.js @@ -0,0 +1,375 @@ +function AntMediaWrapper() { + var callbacks = { + onopen: function() {}, + onclose: function() {}, + onerror: function() {}, + publish_callback: function() {}, + receive_callback: function() {}, + streamInfo_callback: function() {} + }; + + var websocket = new WebSocket('ws://webrtcweb.com:5080/WebRTCAppEE/websocket'); + websocket.userid = (Math.random() * 100).toString().replace('.', ''); + websocket._send = websocket.send; + websocket.send = function(data) { + websocket._send(JSON.stringify(data)); + }; + websocket.onopen = function() { + callbacks.onopen(); + }; + websocket.onclose = function() { + callbacks.onclose(); + }; + websocket.onerror = function() { + callbacks.onerror(); + }; + websocket.onmessage = function(event) { + var data = JSON.parse(event.data); + + if(data.command === 'streamInformation') { + // audioBitrate, videoBitrate, streamHeight, streamWidth + callbacks.streamInfo_callback(data.streamInfo[0]); + } + + if(data.command === 'error') { + if(data.definition === 'no_stream_exist') { + callbacks.receive_callback(null, data.streamId + ': 404 not found. No such stream exist.'); + callbacks.streamInfo_callback(null, data.streamId + ': 404 not found. No such stream exist.'); + } + + if(data.definition === 'noStreamNameSpecified') { + alert('Please pass stream-id.'); + } + } + + if(data.command === 'start') { + rtc.createOffer(rtc.stream, function(response) { + if(response.sdp) { + websocket.send({ + command: 'takeConfiguration', + streamId: rtc.streamId, + type: response.type, + sdp: response.sdp + }); + } + + if(response.candidate) { + websocket.send({ + command: 'takeCandidate', + streamId: rtc.streamId, + candidate: response.candidate, + label: response.label, + id: response.sdpMid + }); + } + + if(response.stream) { + callbacks.receive_callback(response.stream); + } + }); + } + + if(data.command === 'notification') { + if(data.definition === 'publish_started') { + callbacks.publish_callback(true); + } + + if(data.definition === 'play_started') { + // + } + + if(data.definition === 'joined') { + rtc.isOfferer = true; + rtc.createOffer(rtc.stream, function(response) { + if(response.sdp) { + websocket.send({ + command: 'takeConfiguration', + streamId: rtc.streamId, + type: response.type, + sdp: response.sdp + }); + } + + if(response.candidate) { + websocket.send({ + command: 'takeCandidate', + streamId: rtc.streamId, + candidate: response.candidate, + label: response.label, + id: response.sdpMid + }); + } + + if(response.stream) { + callbacks.receive_callback(response.stream); + } + }); + } + + if(data.definition === 'joinedTheRoom') { + // rtc.streamId = data.streamId; + websocket.send({ + command : "publish", + streamId: rtc.streamId, + token : websocket.userid, + video: true, + audio: true + }); + } + } + + if(data.command === 'takeCandidate') { + rtc.addIceCandidate({ + label: data.label, + candidate: data.candidate, + id: data.id + }); + } + + if(data.command === 'takeConfiguration') { + data.type === 'answer' && rtc.setRemoteDescription({ + type: data.type, + sdp: data.sdp + }); + + data.type === 'offer' && rtc.createAnswer({ + sdp: data.sdp, + type: data.type + }, function(response) { + if(response.sdp) { + websocket.send({ + command: 'takeConfiguration', + streamId: rtc.streamId, + type: response.type, + sdp: response.sdp + }); + } + + if(response.candidate) { + websocket.send({ + command: 'takeCandidate', + streamId: rtc.streamId, + candidate: response.candidate, + label: response.label, + id: response.sdpMid + }); + } + + if(response.stream) { + callbacks.receive_callback(response.stream); + } + }); + } + }; + + var rtc = new webrtcHandler(); + + function stopPublishingStream() { + websocket.send({ + command : "stop", + streamId: rtc.streamId + }); + } + + function receiveStream() { + websocket.send({ + command : "play", + streamId : rtc.streamId, + token : websocket.userid + }); + } + + function publishStream() { + websocket.send({ + command : "publish", + streamId: rtc.streamId, + token : websocket.userid, + video: isTrackExist('video', rtc.stream), + audio: isTrackExist('audio', rtc.stream) + }); + } + + function getStreamInfo() { + websocket.send({ + command : "getStreamInfo", + streamId: rtc.streamId + }); + } + + function isTrackExist(kind, stream) { + if(!stream) return false; + + var found = false; + stream.getTracks().forEach(function(track) { + if(track.kind === kind && track.readyState === 'live') { + found = true; + } + }); + return found; + } + + return { + publish: function(stream, streamId, callback) { + rtc.stream = stream; + rtc.streamId = streamId; + callbacks.publish_callback = callback; + publishStream(); + }, + receive: function(streamId, callback) { + rtc.streamId = streamId; + callbacks.receive_callback = callback; + receiveStream(); + }, + getStreamInfo: function(streamId, callback) { + rtc.streamId = streamId; + callbacks.streamInfo_callback = callback; + getStreamInfo(); + }, + callbacks: function(id, callback) { + callbacks[id] = callback; + } + } +} + +function webrtcHandler() { + return { + createOffer: function(stream, callback) { + var peer = this.getPeer(); + + if(stream) { + if ('addStream' in peer) { + peer.addStream(stream); + } else { + stream.getTracks().forEach(function(track) { + peer.addTrack(track, stream); + }); + } + } + + peer.onicecandidate = function(event) { + if (!event || !event.candidate) { + return; + } + + callback({ + candidate: event.candidate.candidate, + label: event.candidate.sdpMLineIndex, + sdpMid: event.candidate.sdpMid + }); + }; + peer.createOffer({ + OfferToReceiveAudio: true, + OfferToReceiveVideo: true + }).then(function(sdp) { + peer.setLocalDescription(sdp); + callback({ + sdp: peer.localDescription.sdp, + type: peer.localDescription.type + }); + }); + + if ('onaddstream' in peer) { + peer.onaddstream = function(event) { + callback({ + stream: event.stream + }); + }; + } else if ('track' in peer) { + peer.ontrack = function(event) { + callback({ + stream: event.stream + }); + }; + } else { + peer.onaddtrack = function(event) { + callback({ + stream: event.streams[0] + }); + }; + } + }, + addIceCandidate: function(candidate) { + this.peer.addIceCandidate(new RTCIceCandidate({ + candidate: candidate.candidate, + sdpMLineIndex: candidate.label, + sdpMid: candidate.id + })); + }, + setRemoteDescription: function(sdp) { + this.peer.setRemoteDescription(new RTCSessionDescription(sdp)); + }, + createAnswer: function(sdp, callback) { + var peer = this.getPeer(); + peer.onicecandidate = function(event) { + if (!event || !event.candidate) { + return; + } + + callback({ + candidate: event.candidate.candidate, + label: event.candidate.sdpMLineIndex, + sdpMid: event.candidate.sdpMid + }); + }; + this.peer.setRemoteDescription(new RTCSessionDescription(sdp)).then(function() { + peer.createAnswer({ + OfferToReceiveAudio: true, + OfferToReceiveVideo: true + }).then(function(sdp) { + peer.setLocalDescription(sdp); + callback({ + sdp: peer.localDescription.sdp, + type: peer.localDescription.type + }); + }); + }); + + if ('onaddstream' in peer) { + peer.onaddstream = function(event) { + callback({ + stream: event.stream + }); + }; + } else if ('track' in peer) { + peer.ontrack = function(event) { + callback({ + stream: event.stream + }); + }; + } else { + peer.onaddtrack = function(event) { + callback({ + stream: event.streams[0] + }); + }; + } + }, + getParams: function() { + // rtcpMuxPolicy: require or negotitate + // bundlePolicy: max-bundle + // iceCandidatePoolSize: 0 + return params = { + iceServers: IceServersHandler.getIceServers(), + iceTransportPolicy: 'all' + }; + }, + getPeer: function() { + var WebRTC_Native_Peer = window.RTCPeerConnection || window.webkitRTCPeerConnection || window.mozRTCPeerConnection; + var peer = new WebRTC_Native_Peer(this.getParams()); + this.peer = peer; + return peer; + } + }; +} + +// IceServersHandler.js + +var IceServersHandler = (function() { + function getIceServers(connection) { + return [{ urls: 'stun:stun.l.google.com:19302' }, { urls: 'stun:stun1.l.google.com:19302' }, { urls: 'stun:stun2.l.google.com:19302' }]; + } + + return { + getIceServers: getIceServers + }; +})(); diff --git a/Chrome-Extensions/desktopCapture-p2p/CodecsHandler.js b/Chrome-Extensions/desktopCapture-p2p/background/helpers/CodecsHandler.js similarity index 100% rename from Chrome-Extensions/desktopCapture-p2p/CodecsHandler.js rename to Chrome-Extensions/desktopCapture-p2p/background/helpers/CodecsHandler.js diff --git a/Chrome-Extensions/desktopCapture-p2p/background/helpers/IceServersHandler.js b/Chrome-Extensions/desktopCapture-p2p/background/helpers/IceServersHandler.js new file mode 100644 index 00000000..aae816f1 --- /dev/null +++ b/Chrome-Extensions/desktopCapture-p2p/background/helpers/IceServersHandler.js @@ -0,0 +1,53 @@ +// IceServersHandler.js + +var IceServersHandler = (function() { + function getIceServers(connection) { + // resiprocate: 3344+4433 + // pions: 7575 + var iceServers = [{ + 'urls': [ + 'stun:webrtcweb.com:7788', // coTURN + 'stun:webrtcweb.com:7788?transport=udp', // coTURN + ], + 'username': 'muazkh', + 'credential': 'muazkh' + }, + { + 'urls': [ + 'turn:webrtcweb.com:7788', // coTURN 7788+8877 + 'turn:webrtcweb.com:4455?transport=udp', // restund udp + + 'turn:webrtcweb.com:8877?transport=udp', // coTURN udp + 'turn:webrtcweb.com:8877?transport=tcp', // coTURN tcp + ], + 'username': 'muazkh', + 'credential': 'muazkh' + }, + { + 'urls': [ + 'stun:stun.l.google.com:19302', + 'stun:stun1.l.google.com:19302', + 'stun:stun2.l.google.com:19302', + 'stun:stun.l.google.com:19302?transport=udp', + ] + } + ]; + + if (typeof window.InstallTrigger !== 'undefined') { + iceServers = [{ + 'urls': [ + 'turn:webrtcweb.com:7788', + 'stun:webrtcweb.com:7788', + ], + 'username': 'muazkh', + 'credential': 'muazkh' + }]; + } + + return iceServers; + } + + return { + getIceServers: getIceServers + }; +})(); diff --git a/Chrome-Extensions/desktopCapture-p2p/MultiStreamsMixer.js b/Chrome-Extensions/desktopCapture-p2p/background/helpers/MultiStreamsMixer.js similarity index 100% rename from Chrome-Extensions/desktopCapture-p2p/MultiStreamsMixer.js rename to Chrome-Extensions/desktopCapture-p2p/background/helpers/MultiStreamsMixer.js diff --git a/Chrome-Extensions/desktopCapture-p2p/RTCMultiConnection.min.js b/Chrome-Extensions/desktopCapture-p2p/background/helpers/RTCMultiConnection.min.js similarity index 100% rename from Chrome-Extensions/desktopCapture-p2p/RTCMultiConnection.min.js rename to Chrome-Extensions/desktopCapture-p2p/background/helpers/RTCMultiConnection.min.js diff --git a/Chrome-Extensions/desktopCapture-p2p/adapter.js b/Chrome-Extensions/desktopCapture-p2p/background/helpers/adapter.js similarity index 100% rename from Chrome-Extensions/desktopCapture-p2p/adapter.js rename to Chrome-Extensions/desktopCapture-p2p/background/helpers/adapter.js diff --git a/Chrome-Extensions/desktopCapture-p2p/getStats.js b/Chrome-Extensions/desktopCapture-p2p/background/helpers/getStats.js similarity index 100% rename from Chrome-Extensions/desktopCapture-p2p/getStats.js rename to Chrome-Extensions/desktopCapture-p2p/background/helpers/getStats.js diff --git a/Chrome-Extensions/desktopCapture-p2p/socket.io.js b/Chrome-Extensions/desktopCapture-p2p/background/helpers/socket.io.js similarity index 100% rename from Chrome-Extensions/desktopCapture-p2p/socket.io.js rename to Chrome-Extensions/desktopCapture-p2p/background/helpers/socket.io.js diff --git a/Chrome-Extensions/desktopCapture-p2p/background/onAccessApproved.js b/Chrome-Extensions/desktopCapture-p2p/background/onAccessApproved.js new file mode 100644 index 00000000..03ab02e6 --- /dev/null +++ b/Chrome-Extensions/desktopCapture-p2p/background/onAccessApproved.js @@ -0,0 +1,99 @@ +function onAccessApproved(chromeMediaSourceId, opts) { + if (!chromeMediaSourceId) { + setDefaults(); + return; + } + + var resolutions = opts.resolutions; + + chrome.storage.sync.get(null, function(items) { + constraints = { + audio: false, + video: { + mandatory: { + chromeMediaSource: 'desktop', + chromeMediaSourceId: chromeMediaSourceId, + maxWidth: resolutions.maxWidth, + maxHeight: resolutions.maxHeight, + minWidth: resolutions.minWidth, + minHeight: resolutions.minHeight, + minAspectRatio: getAspectRatio(resolutions.maxWidth, resolutions.maxHeight), + maxAspectRatio: getAspectRatio(resolutions.maxWidth, resolutions.maxHeight), + minFrameRate: 64, + maxFrameRate: 128 + }, + optional: [] + } + }; + + if (opts.canRequestAudioTrack === true) { + constraints.audio = { + mandatory: { + chromeMediaSource: 'desktop', + chromeMediaSourceId: chromeMediaSourceId, + echoCancellation: true + }, + optional: [{ + googDisableLocalEcho: false // https://www.chromestatus.com/feature/5056629556903936 + }] + }; + } + + navigator.webkitGetUserMedia(constraints, function(screenStream) { + var win; + addStreamStopListener(screenStream, function() { + if (win && !win.closed) { + win.close(); + } else { + captureDesktop(); + } + }); + + if (opts.stream) { + if (enableCamera && opts.stream.getVideoTracks().length) { + var cameraStream = opts.stream; + + screenStream.fullcanvas = true; + screenStream.width = screen.width; // or 3840 + screenStream.height = screen.height; // or 2160 + + cameraStream.width = parseInt((15 / 100) * screenStream.width); + cameraStream.height = parseInt((15 / 100) * screenStream.height); + cameraStream.top = screenStream.height - cameraStream.height - 20; + cameraStream.left = screenStream.width - cameraStream.width - 20; + + var mixer = new MultiStreamsMixer([screenStream, cameraStream]); + + mixer.frameInterval = 1; + mixer.startDrawingFrames(); + + screenStream = mixer.getMixedStream(); + // win = openVideoPreview(screenStream); + } else if (enableMicrophone && opts.stream.getAudioTracks().length) { + var speakers = new MediaStream(); + screenStream.getAudioTracks().forEach(function(track) { + speakers.addTrack(track); + screenStream.removeTrack(track); + }); + + var mixer = new MultiStreamsMixer([speakers, opts.stream]); + mixer.getMixedStream().getAudioTracks().forEach(function(track) { + screenStream.addTrack(track); + }); + + screenStream.getVideoTracks().forEach(function(track) { + track.onended = function() { + if (win && !win.closed) { + win.close(); + } else { + captureDesktop(); + } + }; + }) + } + } + + gotStream(screenStream); + }, getUserMediaError); + }); +} diff --git a/Chrome-Extensions/desktopCapture-p2p/background/online-offline.js b/Chrome-Extensions/desktopCapture-p2p/background/online-offline.js new file mode 100644 index 00000000..ead83103 --- /dev/null +++ b/Chrome-Extensions/desktopCapture-p2p/background/online-offline.js @@ -0,0 +1,13 @@ +window.addEventListener('offline', function() { + if (!connection || !connection.attachStreams.length) return; + + setDefaults(); + chrome.runtime.reload(); +}, false); + +window.addEventListener('online', function() { + if (!connection) return; + + setDefaults(); + chrome.runtime.reload(); +}, false); \ No newline at end of file diff --git a/Chrome-Extensions/desktopCapture-p2p/background/runtimePort.js b/Chrome-Extensions/desktopCapture-p2p/background/runtimePort.js new file mode 100644 index 00000000..792fb58c --- /dev/null +++ b/Chrome-Extensions/desktopCapture-p2p/background/runtimePort.js @@ -0,0 +1,14 @@ +chrome.runtime.onConnect.addListener(function(port) { + runtimePort = port; + + runtimePort.onMessage.addListener(function(message) { + if (!message || !message.messageFromContentScript1234) { + return; + } + + if (message.startSharing || message.stopSharing) { + captureDesktop(); + return; + } + }); +}); diff --git a/Chrome-Extensions/desktopCapture-p2p/background/setDefaults.js b/Chrome-Extensions/desktopCapture-p2p/background/setDefaults.js new file mode 100644 index 00000000..12615f03 --- /dev/null +++ b/Chrome-Extensions/desktopCapture-p2p/background/setDefaults.js @@ -0,0 +1,54 @@ +function setDefaults() { + chrome.storage.sync.set({ + enableTabCaptureAPI: 'false', + enableMicrophone: 'false', + enableCamera: 'false', + enableScreen: 'false', + isSharingOn: 'false', + enableSpeakers: 'false' + }); + + if (connection) { + connection.attachStreams.forEach(function(stream) { + try { + stream.getTracks().forEach(function(track) { + try { + track.stop(); + } catch (e) {} + }); + } catch (e) {} + }); + + try { + connection.close(); + } catch (e) {} + + try { + connection.closeSocket(); + } catch (e) {} + + connection = null; + } + + chrome.browserAction.setIcon({ + path: 'images/desktopCapture22.png' + }); + + if (popup_id) { + try { + chrome.windows.remove(popup_id); + } catch (e) {} + + popup_id = null; + } + + chrome.browserAction.setTitle({ + title: 'Share Desktop' + }); + + chrome.browserAction.setBadgeText({ + text: '' + }); + + chrome.runtime.reload(); +} diff --git a/Chrome-Extensions/desktopCapture-p2p/background/setupWebRTCConnection.js b/Chrome-Extensions/desktopCapture-p2p/background/setupWebRTCConnection.js new file mode 100644 index 00000000..fa072313 --- /dev/null +++ b/Chrome-Extensions/desktopCapture-p2p/background/setupWebRTCConnection.js @@ -0,0 +1,13 @@ +function setupWebRTCConnection(stream) { + // forcing RTCMultiConnection ONLY + // reason: webserver doesn't has enough memory and CPU to serve any media server + shareStreamUsingRTCMultiConnection(stream); + + if (streaming_method === 'RTCMultiConnection') { + // shareStreamUsingRTCMultiConnection(stream); + } + + if (streaming_method === 'AntMediaServer') { + // shareStreamUsingAntMediaServer(stream); + } +} diff --git a/Chrome-Extensions/desktopCapture-p2p/background/shareStreamUsingAntMediaServer.js b/Chrome-Extensions/desktopCapture-p2p/background/shareStreamUsingAntMediaServer.js new file mode 100644 index 00000000..76d48778 --- /dev/null +++ b/Chrome-Extensions/desktopCapture-p2p/background/shareStreamUsingAntMediaServer.js @@ -0,0 +1,44 @@ +function shareStreamUsingAntMediaServer(stream) { + var wrapper = new AntMediaWrapper(); + wrapper.callbacks('onopen', function() { + var uid = room_id || (Math.random() * 100).toString().replace('.', ''); + wrapper.publish(stream, uid, function(isPublished, error) { + if (isPublished === true) { + var resultingURL = 'http://webrtcweb.com/screen/ant/?s=' + uid; + var hlsURL = 'http://webrtcweb.com/screen/ant/?s=' + uid + '&hls=true'; + + var html = "Unique Room URL

Copy following private URL:

"; + html += "


"; + html += "


"; + + var popup_width = 600; + var popup_height = 230; + + chrome.windows.create({ + url: "data:text/html," + html, + type: 'popup', + width: popup_width, + height: popup_height, + top: parseInt((screen.height / 2) - (popup_height / 2)), + left: parseInt((screen.width / 2) - (popup_width / 2)), + focused: true + }, function(win) { + popup_id = win.id; + }); + + chrome.browserAction.enable(); + setBadgeText(0); + } else { + alert(error); + } + }); + }); + + wrapper.callbacks('onerror', function() { + alert('Ant-Media Server is down or not reachable in the moment. Please go to options page and try other streaming method.'); + stream.getTracks().forEach(function(track) { + track.stop(); + }); + setDefaults(); + }); +} diff --git a/Chrome-Extensions/desktopCapture-p2p/background/shareStreamUsingRTCMultiConnection.js b/Chrome-Extensions/desktopCapture-p2p/background/shareStreamUsingRTCMultiConnection.js new file mode 100644 index 00000000..049d8745 --- /dev/null +++ b/Chrome-Extensions/desktopCapture-p2p/background/shareStreamUsingRTCMultiConnection.js @@ -0,0 +1,160 @@ +function shareStreamUsingRTCMultiConnection(stream) { + // www.RTCMultiConnection.org/docs/ + connection = new RTCMultiConnection(); + // connection.socketURL = 'https://rtcmulticonnection.herokuapp.com:443/'; + connection.socketURL = 'https://webrtcweb.com:9001/'; + connection.autoCloseEntireSession = true; + + // this must match the viewer page + connection.socketMessageEvent = 'desktopCapture'; + + connection.password = null; + if (room_password && room_password.length) { + connection.password = room_password; + } + + connection.enableLogs = true; + connection.session = { + audio: true, + video: true, + oneway: true + }; + + connection.optionalArgument = { + optional: [], + mandatory: {} + }; + + connection.channel = connection.sessionid = connection.userid; + + if (room_id && room_id.length) { + connection.channel = connection.sessionid = connection.userid = room_id; + } + + connection.autoReDialOnFailure = true; + connection.getExternalIceServers = false; + + connection.iceServers = IceServersHandler.getIceServers(); + + function setBandwidth(sdp, value) { + sdp = sdp.replace(/b=AS([^\r\n]+\r\n)/g, ''); + sdp = sdp.replace(/a=mid:video\r\n/g, 'a=mid:video\r\nb=AS:' + value + '\r\n'); + return sdp; + } + + connection.processSdp = function(sdp) { + if (bandwidth) { + try { + bandwidth = parseInt(bandwidth); + } catch (e) { + bandwidth = null; + } + + if (bandwidth && bandwidth != NaN && bandwidth != 'NaN' && typeof bandwidth == 'number') { + sdp = setBandwidth(sdp, bandwidth); + sdp = BandwidthHandler.setVideoBitrates(sdp, { + min: bandwidth, + max: bandwidth + }); + } + } + + if (!!codecs && codecs !== 'default') { + sdp = CodecsHandler.preferCodec(sdp, codecs); + } + return sdp; + }; + + // www.rtcmulticonnection.org/docs/sdpConstraints/ + connection.sdpConstraints.mandatory = { + OfferToReceiveAudio: false, + OfferToReceiveVideo: false + }; + + connection.onstream = connection.onstreamended = function(event) { + try { + event.mediaElement.pause(); + delete event.mediaElement; + } catch (e) {} + }; + + // www.RTCMultiConnection.org/docs/dontCaptureUserMedia/ + connection.dontCaptureUserMedia = true; + + // www.RTCMultiConnection.org/docs/attachStreams/ + connection.attachStreams.push(stream); + + var text = '-'; + (function looper() { + if (!connection) { + setBadgeText(''); + return; + } + + if (connection.isInitiator) { + setBadgeText('0'); + return; + } + + text += ' -'; + if (text.length > 6) { + text = '-'; + } + + setBadgeText(text); + setTimeout(looper, 500); + })(); + + // www.RTCMultiConnection.org/docs/open/ + connection.socketCustomEvent = connection.sessionid; + + function roomOpenCallback() { + chrome.browserAction.enable(); + setBadgeText(0); + + if (room_url_box === true) { + var resultingURL = 'https://webrtcweb.com/screen/?s=' + connection.sessionid; + // resultingURL = 'https://www.webrtc-experiment.com/screen/?s=' + connection.sessionid; + + // resultingURL = 'http://localhost:9001/?s=' + connection.sessionid; + + if (room_password && room_password.length) { + resultingURL += '&p=' + room_password; + } + + if (bandwidth) { + resultingURL += '&bandwidth=' + bandwidth; + } + if (!!codecs && codecs !== 'default') { + resultingURL += '&codecs=' + codecs; + } + + var popup_width = 600; + var popup_height = 170; + + chrome.windows.create({ + url: "data:text/html,Unique Room URL

Copy following private URL:

You can share this private-session URI with fellows using email or social networks.

", + type: 'popup', + width: popup_width, + height: popup_height, + top: parseInt((screen.height / 2) - (popup_height / 2)), + left: parseInt((screen.width / 2) - (popup_width / 2)), + focused: true + }, function(win) { + popup_id = win.id; + }); + } + + connection.socket.on(connection.socketCustomEvent, function(message) { + if (message.receivedYourScreen) { + setBadgeText(connection.isInitiator ? connection.getAllParticipants().length : ''); + } + }); + } + + connection.open(connection.sessionid, roomOpenCallback); + + connection.onleave = connection.onPeerStateChanged = function() { + setBadgeText(connection.isInitiator ? connection.getAllParticipants().length : ''); + }; +} diff --git a/Chrome-Extensions/desktopCapture-p2p/camera-mic.html b/Chrome-Extensions/desktopCapture-p2p/camera-mic.html deleted file mode 100644 index 7a1363d2..00000000 --- a/Chrome-Extensions/desktopCapture-p2p/camera-mic.html +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/Chrome-Extensions/desktopCapture-p2p/desktop-capturing.js b/Chrome-Extensions/desktopCapture-p2p/desktop-capturing.js deleted file mode 100644 index e6e73bac..00000000 --- a/Chrome-Extensions/desktopCapture-p2p/desktop-capturing.js +++ /dev/null @@ -1,820 +0,0 @@ -// Muaz Khan - https://github.com/muaz-khan -// MIT License - https://www.WebRTC-Experiment.com/licence/ -// Source Code - https://github.com/muaz-khan/Chrome-Extensions - -// this page is using desktopCapture API to capture and share desktop -// http://developer.chrome.com/extensions/desktopCapture.html - -// chrome.browserAction.onClicked.addListener(captureDesktop); - -var runtimePort; - -chrome.runtime.onConnect.addListener(function(port) { - runtimePort = port; - - runtimePort.onMessage.addListener(function(message) { - if (!message || !message.messageFromContentScript1234) { - return; - } - - if (message.startSharing || message.stopSharing) { - captureDesktop(); - return; - } - }); -}); - - -window.addEventListener('offline', function() { - if (!connection || !connection.attachStreams.length) return; - - setDefaults(); - chrome.runtime.reload(); -}, false); - -window.addEventListener('online', function() { - if (!connection) return; - - setDefaults(); - chrome.runtime.reload(); -}, false); - -function captureDesktop() { - if (connection && connection.attachStreams[0]) { - setDefaults(); - - connection && connection.attachStreams.forEach(function(stream) { - stream.getTracks().forEach(function(track) { - track.stop(); - }); - }); - - chrome.storage.sync.set({ - enableTabCaptureAPI: 'false', - enableMicrophone: 'false', - enableCamera: 'false', - enableScreen: 'false', - isSharingOn: 'false', - enableSpeakers: 'false' - }); - return; - } - - chrome.browserAction.setTitle({ - title: 'Capturing Desktop' - }); - - desktop_id = null; - constraints = null; - room_password = ''; - room_id = ''; - codecs = 'default'; - bandwidth = null; - - enableTabCaptureAPI = null; - enableMicrophone = null; - enableSpeakers = null; - enableCamera = null; - enableScreen = null; - isSharingOn = null; - - room_url_box = true; - - chrome.storage.sync.get(null, function(items) { - var resolutions = {}; - - if (items['room_password']) { - room_password = items['room_password']; - } - - if (items['room_id']) { - room_id = items['room_id']; - } - - if(items['room_url_box'] === 'false') { - room_url_box = false; - } - - if (items['codecs']) { - codecs = items['codecs']; - } - - if (items['bandwidth']) { - bandwidth = items['bandwidth']; - } - - if (items['enableTabCaptureAPI'] == 'true') { - enableTabCaptureAPI = items['enableTabCaptureAPI']; - } - - if (items['enableMicrophone'] == 'true') { - enableMicrophone = items['enableMicrophone']; - } - - if (items['enableSpeakers'] == 'true') { - enableSpeakers = items['enableSpeakers']; - } - - if (items['enableCamera'] == 'true') { - enableCamera = items['enableCamera']; - } - - if (items['enableScreen'] == 'true') { - enableScreen = items['enableScreen']; - } - - if (items['enableTabCaptureAPI'] == 'true') { - enableTabCaptureAPI = items['enableTabCaptureAPI']; - } - - if (items['isSharingOn'] == 'true') { - isSharingOn = items['isSharingOn']; - } - - var _resolutions = items['resolutions']; - if (!_resolutions) { - _resolutions = 'fit-screen'; - chrome.storage.sync.set({ - resolutions: 'fit-screen' - }, function() {}); - } - - if (_resolutions === 'fit-screen') { - // resolutions.maxWidth = screen.availWidth; - // resolutions.maxHeight = screen.availHeight; - - resolutions.maxWidth = screen.width; - resolutions.maxHeight = screen.height; - } - - if (_resolutions === '4K') { - resolutions.maxWidth = 3840; - resolutions.maxHeight = 2160; - } - - if (_resolutions === '1080p') { - resolutions.maxWidth = 1920; - resolutions.maxHeight = 1080; - } - - if (_resolutions === '720p') { - resolutions.maxWidth = 1280; - resolutions.maxHeight = 720; - } - - if (_resolutions === '360p') { - resolutions.maxWidth = 640; - resolutions.maxHeight = 360; - } - - if (_resolutions === '4K') { - alert('"4K" resolutions is not stable in Chrome. Please try "fit-screen" instead.'); - } - - var sources = ['screen', 'window', 'tab']; - - if (enableSpeakers) { - sources.push('audio'); - } - - if (enableTabCaptureAPI) { - captureTabUsingTabCapture(resolutions); - return; - } - - if (enableCamera || enableMicrophone) { - captureCamera(function(stream) { - if (!enableScreen) { - gotStream(stream); - return; - } - - desktop_id = chrome.desktopCapture.chooseDesktopMedia(sources, function(chromeMediaSourceId, opts) { - opts = opts || {}; - opts.resolutions = resolutions; - opts.stream = stream; - onAccessApproved(chromeMediaSourceId, opts); - }); - }); - return; - } - - desktop_id = chrome.desktopCapture.chooseDesktopMedia(sources, function(chromeMediaSourceId, opts) { - opts = opts || {}; - opts.resolutions = resolutions; - onAccessApproved(chromeMediaSourceId, opts); - }); - }); -} - -function captureTabUsingTabCapture(resolutions) { - chrome.tabs.query({ - active: true, - currentWindow: true - }, function(arrayOfTabs) { - var activeTab = arrayOfTabs[0]; - var activeTabId = activeTab.id; // or do whatever you need - - var constraints = { - video: true, - videoConstraints: { - mandatory: { - chromeMediaSource: 'tab', - maxWidth: resolutions.maxWidth, - maxHeight: resolutions.maxHeight, - minWidth: resolutions.minWidth, - minHeight: resolutions.minHeight, - minAspectRatio: getAspectRatio(resolutions.maxWidth, resolutions.maxHeight), - maxAspectRatio: getAspectRatio(resolutions.maxWidth, resolutions.maxHeight), - minFrameRate: 64, - maxFrameRate: 128 - } - } - }; - - if (!!enableSpeakers) { - constraints.audio = true; - constraints.audioConstraints = { - mandatory: { - echoCancellation: true - }, - optional: [{ - googDisableLocalEcho: false // https://www.chromestatus.com/feature/5056629556903936 - }] - }; - } - - // chrome.tabCapture.onStatusChanged.addListener(function(event) { /* event.status */ }); - - chrome.tabCapture.capture(constraints, function(stream) { - gotTabCaptureStream(stream, constraints); - }); - }); -} - -function gotTabCaptureStream(stream, constraints) { - if (!stream) { - if (constraints.audio === true) { - enableSpeakers = false; - captureTabUsingTabCapture(resolutions); - return; - } - return alert('still no tabCapture stream'); - chrome.runtime.reload(); - return; - } - - var newStream = new MediaStream(); - - stream.getTracks().forEach(function(track) { - newStream.addTrack(track); - }); - - initVideoPlayer(newStream); - - gotStream(newStream); -} - -var desktop_id; -var constraints; -var room_password = ''; -var room_id = ''; -var codecs = 'default'; -var bandwidth; - -var enableTabCaptureAPI; -var enableMicrophone; -var enableSpeakers; -var enableCamera; -var enableScreen; -var isSharingOn; - -var room_url_box = true; - -function getAspectRatio(w, h) { - function gcd(a, b) { - return (b == 0) ? a : gcd(b, a % b); - } - var r = gcd(w, h); - return (w / r) / (h / r); -} - -function onAccessApproved(chromeMediaSourceId, opts) { - if (!chromeMediaSourceId) { - setDefaults(); - return; - } - - var resolutions = opts.resolutions; - - chrome.storage.sync.get(null, function(items) { - constraints = { - audio: false, - video: { - mandatory: { - chromeMediaSource: 'desktop', - chromeMediaSourceId: chromeMediaSourceId, - maxWidth: resolutions.maxWidth, - maxHeight: resolutions.maxHeight, - minWidth: resolutions.minWidth, - minHeight: resolutions.minHeight, - minAspectRatio: getAspectRatio(resolutions.maxWidth, resolutions.maxHeight), - maxAspectRatio: getAspectRatio(resolutions.maxWidth, resolutions.maxHeight), - minFrameRate: 64, - maxFrameRate: 128 - }, - optional: [] - } - }; - - if (opts.canRequestAudioTrack === true) { - constraints.audio = { - mandatory: { - chromeMediaSource: 'desktop', - chromeMediaSourceId: chromeMediaSourceId, - echoCancellation: true - }, - optional: [{ - googDisableLocalEcho: false // https://www.chromestatus.com/feature/5056629556903936 - }] - }; - } - - navigator.webkitGetUserMedia(constraints, function(screenStream) { - var win; - addStreamStopListener(screenStream, function() { - if (win && !win.closed) { - win.close(); - } else { - captureDesktop(); - } - }); - - if (opts.stream) { - if (enableCamera && opts.stream.getVideoTracks().length) { - var cameraStream = opts.stream; - - screenStream.fullcanvas = true; - screenStream.width = screen.width; // or 3840 - screenStream.height = screen.height; // or 2160 - - cameraStream.width = parseInt((15 / 100) * screenStream.width); - cameraStream.height = parseInt((15 / 100) * screenStream.height); - cameraStream.top = screenStream.height - cameraStream.height - 20; - cameraStream.left = screenStream.width - cameraStream.width - 20; - - var mixer = new MultiStreamsMixer([screenStream, cameraStream]); - - mixer.frameInterval = 1; - mixer.startDrawingFrames(); - - screenStream = mixer.getMixedStream(); - // win = openVideoPreview(screenStream); - } else if (enableMicrophone && opts.stream.getAudioTracks().length) { - var speakers = new MediaStream(); - screenStream.getAudioTracks().forEach(function(track) { - speakers.addTrack(track); - screenStream.removeTrack(track); - }); - - var mixer = new MultiStreamsMixer([speakers, opts.stream]); - mixer.getMixedStream().getAudioTracks().forEach(function(track) { - screenStream.addTrack(track); - }); - - screenStream.getVideoTracks().forEach(function(track) { - track.onended = function() { - if (win && !win.closed) { - win.close(); - } else { - captureDesktop(); - } - }; - }) - } - } - - gotStream(screenStream); - }, getUserMediaError); - }); -} - -function openVideoPreview(stream) { - // var win = window.open("video.html?src=" + URL.createObjectURL(stream), "_blank", "top=0,left=0"); - var win = window.open("video.html", "_blank", "top=0,left=0"); - var timer = setInterval(function() { - if (win.closed) { - clearInterval(timer); - captureDesktop(); - } - }, 1000); - return win; -} - -function addStreamStopListener(stream, callback) { - var streamEndedEvent = 'ended'; - if ('oninactive' in stream) { - streamEndedEvent = 'inactive'; - } - stream.addEventListener(streamEndedEvent, function() { - callback(); - callback = function() {}; - }, false); - stream.getAudioTracks().forEach(function(track) { - track.addEventListener(streamEndedEvent, function() { - callback(); - callback = function() {}; - }, false); - }); - stream.getVideoTracks().forEach(function(track) { - track.addEventListener(streamEndedEvent, function() { - callback(); - callback = function() {}; - }, false); - }); -} - -function gotStream(stream) { - if (!stream) { - setDefaults(); - - chrome.windows.create({ - url: "data:text/html,

Internal error occurred while capturing the screen.

", - type: 'popup', - width: screen.width / 2, - height: 170 - }); - return; - } - - chrome.browserAction.setTitle({ - title: 'Connecting to WebSockets server.' - }); - - chrome.browserAction.disable(); - - addStreamStopListener(stream, function() { - setDefaults(); - chrome.runtime.reload(); - }); - - // as it is reported that if you drag chrome screen's status-bar - // and scroll up/down the screen-viewer page. - // chrome auto-stops the screen without firing any 'onended' event. - // chrome also hides screen status bar. - chrome.windows.create({ - url: chrome.extension.getURL('_generated_background_page.html'), - type: 'popup', - focused: false, - width: 1, - height: 1, - top: parseInt(screen.height), - left: parseInt(screen.width) - }, function(win) { - var background_page_id = win.id; - - setTimeout(function() { - // chrome.windows.remove(background_page_id); - }, 3000); - }); - - setupRTCMultiConnection(stream); - - chrome.browserAction.setIcon({ - path: 'images/pause22.png' - }); -} - -function getUserMediaError(e) { - setDefaults(); - - chrome.windows.create({ - url: "data:text/html,

getUserMediaError: " + JSON.stringify(e, null, '
') + "


Constraints used:
" + JSON.stringify(constraints, null, '
') + '
', - type: 'popup', - width: screen.width / 2, - height: 170 - }); -} - -// RTCMultiConnection - www.RTCMultiConnection.org -var connection; -var popup_id; - -function setBadgeText(text) { - chrome.browserAction.setBadgeBackgroundColor({ - color: [255, 0, 0, 255] - }); - - chrome.browserAction.setBadgeText({ - text: text + '' - }); - - chrome.browserAction.setTitle({ - title: text + ' users are viewing your screen!' - }); -} - -function setupRTCMultiConnection(stream) { - // www.RTCMultiConnection.org/docs/ - connection = new RTCMultiConnection(); - // connection.socketURL = 'https://rtcmulticonnection.herokuapp.com:443/'; - connection.socketURL = 'https://webrtcweb.com:9001/'; - connection.autoCloseEntireSession = true; - - connection.enableLogs = true; - connection.session = { - audio: true, - video: true, - oneway: true - }; - - connection.optionalArgument = { - optional: [], - mandatory: {} - }; - - connection.channel = connection.sessionid = connection.userid; - - if (room_id && room_id.length) { - connection.channel = connection.sessionid = connection.userid = room_id; - } - - connection.autoReDialOnFailure = true; - connection.getExternalIceServers = false; - - connection.iceServers = IceServersHandler.getIceServers(); - - function setBandwidth(sdp, value) { - sdp = sdp.replace(/b=AS([^\r\n]+\r\n)/g, ''); - sdp = sdp.replace(/a=mid:video\r\n/g, 'a=mid:video\r\nb=AS:' + value + '\r\n'); - return sdp; - } - - connection.processSdp = function(sdp) { - if (bandwidth) { - try { - bandwidth = parseInt(bandwidth); - } catch (e) { - bandwidth = null; - } - - if (bandwidth && bandwidth != NaN && bandwidth != 'NaN' && typeof bandwidth == 'number') { - sdp = setBandwidth(sdp, bandwidth); - sdp = BandwidthHandler.setVideoBitrates(sdp, { - min: bandwidth, - max: bandwidth - }); - } - } - - if (!!codecs && codecs !== 'default') { - sdp = CodecsHandler.preferCodec(sdp, codecs); - } - return sdp; - }; - - // www.rtcmulticonnection.org/docs/sdpConstraints/ - connection.sdpConstraints.mandatory = { - OfferToReceiveAudio: false, - OfferToReceiveVideo: false - }; - - connection.onstream = connection.onstreamended = function(event) { - try { - event.mediaElement.pause(); - delete event.mediaElement; - } catch (e) {} - }; - - // www.RTCMultiConnection.org/docs/dontCaptureUserMedia/ - connection.dontCaptureUserMedia = true; - - // www.RTCMultiConnection.org/docs/attachStreams/ - connection.attachStreams.push(stream); - - if (room_password && room_password.length) { - connection.onRequest = function(request) { - if (request.extra.password !== room_password) { - connection.reject(request); - chrome.windows.create({ - url: "data:text/html,

A user tried to join your room with invalid password. His request is rejected. He tried password: " + request.extra.password + "

", - type: 'popup', - width: screen.width / 2, - height: 170 - }); - return; - } - - connection.accept(request); - }; - } - - var text = '-'; - (function looper() { - if (!connection) { - setBadgeText(''); - return; - } - - if (connection.isInitiator) { - setBadgeText('0'); - return; - } - - text += ' -'; - if (text.length > 6) { - text = '-'; - } - - setBadgeText(text); - setTimeout(looper, 500); - })(); - - // www.RTCMultiConnection.org/docs/open/ - connection.socketCustomEvent = connection.sessionid; - - function roomOpenCallback() { - chrome.browserAction.enable(); - setBadgeText(0); - - if(room_url_box === true) { - var resultingURL = 'https://webrtcweb.com/screen?s=' + connection.sessionid; - - // resultingURL = 'http://localhost:9001/?s=' + connection.sessionid; - - if (room_password && room_password.length) { - resultingURL += '&p=' + room_password; - } - - if(bandwidth) { - resultingURL += '&bandwidth=' + bandwidth; - } - if (!!codecs && codecs !== 'default') { - resultingURL += '&codecs=' + codecs; - } - - var popup_width = 600; - var popup_height = 170; - - chrome.windows.create({ - url: "data:text/html,Unique Room URL

Copy following private URL:

You can share this private-session URI with fellows using email or social networks.

", - type: 'popup', - width: popup_width, - height: popup_height, - top: parseInt((screen.height / 2) - (popup_height / 2)), - left: parseInt((screen.width / 2) - (popup_width / 2)), - focused: true - }, function(win) { - popup_id = win.id; - }); - } - - connection.socket.on(connection.socketCustomEvent, function(message) { - if(message.receivedYourScreen) { - setBadgeText(connection.isInitiator ? connection.getAllParticipants().length : ''); - } - }); - } - - connection.password = null; - if (room_password && room_password.length) { - connection.password = room_password; - } - - connection.open(connection.sessionid, roomOpenCallback); - - connection.onleave = connection.onPeerStateChanged = function() { - setBadgeText(connection.isInitiator ? connection.getAllParticipants().length : ''); - }; -} - -function setDefaults() { - if (connection) { - connection.attachStreams.forEach(function(stream) { - try { - stream.getTracks().forEach(function(track){ - try { - track.stop(); - } - catch(e) {} - }); - } - catch(e) {} - }); - - try { - connection.close(); - } - catch(e) {} - - try { - connection.closeSocket(); - } - catch(e) {} - - connection = null; - - chrome.storage.sync.set({ - enableTabCaptureAPI: 'false', - enableMicrophone: 'false', - enableCamera: 'false', - enableScreen: 'false', - isSharingOn: 'false', - enableSpeakers: 'false' - }); - } - - chrome.browserAction.setIcon({ - path: 'images/desktopCapture22.png' - }); - - if (popup_id) { - try { - chrome.windows.remove(popup_id); - } catch (e) {} - - popup_id = null; - } - - chrome.browserAction.setTitle({ - title: 'Share Desktop' - }); - - chrome.browserAction.setBadgeText({ - text: '' - }); -} - -var videoPlayers = []; - -function initVideoPlayer(stream) { - var videoPlayer = document.createElement('video'); - videoPlayer.muted = !enableTabCaptureAPI; - videoPlayer.volume = !!enableTabCaptureAPI; - videoPlayer.autoplay = true; - videoPlayer.srcObject = stream; - videoPlayers.push(videoPlayer); -} - -var microphoneDevice = false; -var cameraDevice = false; - -function captureCamera(callback) { - var supported = navigator.mediaDevices.getSupportedConstraints(); - var constraints = {}; - - if (enableCamera) { - constraints.video = { - width: { - min: 640, - ideal: 1920, - max: 1920 - }, - height: { - min: 400, - ideal: 1080 - } - }; - - if (supported.aspectRatio) { - constraints.video.aspectRatio = 1.777777778; - } - - if (supported.frameRate) { - constraints.video.frameRate = { - ideal: 30 - }; - } - - if (cameraDevice && cameraDevice.length) { - constraints.video.deviceId = cameraDevice; - } - } - - if (enableMicrophone) { - constraints.audio = {}; - - if (microphoneDevice && microphoneDevice.length) { - constraints.audio.deviceId = microphoneDevice; - } - - if (supported.echoCancellation) { - constraints.audio.echoCancellation = true; - } - } - - navigator.mediaDevices.getUserMedia(constraints).then(function(stream) { - initVideoPlayer(stream); - callback(stream); - - if (enableCamera && !enableScreen) { - openVideoPreview(stream); - } - }).catch(function(error) { - setDefaults(); - - chrome.tabs.create({ - url: 'camera-mic.html' - }); - }); -} diff --git a/Chrome-Extensions/desktopCapture-p2p/extension-pages/camera-mic.html b/Chrome-Extensions/desktopCapture-p2p/extension-pages/camera-mic.html new file mode 100644 index 00000000..2faf4eaa --- /dev/null +++ b/Chrome-Extensions/desktopCapture-p2p/extension-pages/camera-mic.html @@ -0,0 +1,3 @@ + +WebRTC Desktop Sharing + \ No newline at end of file diff --git a/Chrome-Extensions/desktopCapture-p2p/camera-mic.js b/Chrome-Extensions/desktopCapture-p2p/extension-pages/camera-mic.js similarity index 100% rename from Chrome-Extensions/desktopCapture-p2p/camera-mic.js rename to Chrome-Extensions/desktopCapture-p2p/extension-pages/camera-mic.js diff --git a/Chrome-Extensions/desktopCapture-p2p/dropdown.html b/Chrome-Extensions/desktopCapture-p2p/extension-pages/dropdown.html similarity index 65% rename from Chrome-Extensions/desktopCapture-p2p/dropdown.html rename to Chrome-Extensions/desktopCapture-p2p/extension-pages/dropdown.html index dd96e83b..9d8bcc4d 100644 --- a/Chrome-Extensions/desktopCapture-p2p/dropdown.html +++ b/Chrome-Extensions/desktopCapture-p2p/extension-pages/dropdown.html @@ -1,5 +1,6 @@  + +WebRTC Desktop Sharing + + +
Loading configuration
+ +
+

Resolutions:

+
+ +
+
+ +
+

Codecs:

+
+ +
+
+ + + +
+

Bandwidth:

+ + + E.g. 8192 or 1048 or 512 or any digit. +
+ +
+

Room Password:

+ + + Keep empty for normal rooms. Currently works only for Peer-to-Peer streaming method. +
+ +
+

Room Unique ID:

+ + + Set Your Own Room-ID (to make it secure & protected & reusable). Keep empty for random room-id. +
+ +
+

Room URL box:

+ + + You can ignore/hide the link box. For reusable room-ids: we do not need to show popup again and again. +
+ + \ No newline at end of file diff --git a/Chrome-Extensions/desktopCapture-p2p/extension-pages/options.js b/Chrome-Extensions/desktopCapture-p2p/extension-pages/options.js new file mode 100644 index 00000000..fe49917d --- /dev/null +++ b/Chrome-Extensions/desktopCapture-p2p/extension-pages/options.js @@ -0,0 +1,178 @@ +function querySelectorAll(selector, element) { + element = element || document; + return Array.prototype.slice.call(element.querySelectorAll(selector)); +} + +var elements_ids = querySelectorAll('input, select').map(function(item) { + return item.id; +}); + +var changes = document.getElementById('changes'); + +chrome.storage.sync.get(null, function(items) { + elements_ids.forEach(function(id) { + if (!items[id]) return; + + var element = document.getElementById(id); + if (items[id]) { + if (element.type === 'checkbox') { + element.checked = items[id] === 'true'; + } else { + element.value = items[id]; + } + } else { + var item = {}; + item[element.id] = element.type === 'checkbox' ? + (element.checked === true ? 'true' : 'false') : + element.value; + + try { + chrome.storage.sync.set(item); + } + catch(e) { + location.reload(); + } + } + }); + setCustomSelectElements(); + setTimeout(function() { + changes.style.display = 'none'; + }, 600); +}); + +elements_ids.forEach(function(id) { + var element = document.getElementById(id); + element.onchange = function(e) { + e && e.stopPropagation(); + element.disabled = true; + + changes.querySelector('span').innerHTML = 'Saving Changes'; + changes.style.display = 'block'; + + var item = {}; + item[element.id] = element.type === 'checkbox' ? + (element.checked === true ? 'true' : 'false') : + element.value; + + try { + chrome.storage.sync.set(item, function() { + element.disabled = false; + setTimeout(function() { + changes.style.display = 'none'; + }, 600); + }); + } + catch(e) { + location.reload(); + } + }; + + if (element.type) { + element.parentNode.onclick = function() { + if (element.type !== 'checkbox') { + element.focus(); + } else { + element.checked = !element.checked; + } + }; + + element.onclick = element.onfocus = function(e) { + e && e.stopPropagation(); + }; + } +}); + +querySelectorAll('.help-icon').forEach(function(helpIcon) { + var shown = false; + helpIcon.onclick = function(e) { + e.stopPropagation(); + + var small = helpIcon.parentNode.querySelector('small'); + if(shown) { + small.style.marginTop = '0px'; + setTimeout(function() { + small.style.height = '0px'; + }, 500); + shown = false; + } + else { + small.style.height = 'auto'; + small.style.marginTop = '10px'; + shown = true; + } + }; +}); + +/* custom select */ +function setCustomSelectElements() { + var x, i, j, selElmnt, a, b, c; + x = document.getElementsByClassName('custom-select'); + for (i = 0; i < x.length; i++) { + selElmnt = x[i].getElementsByTagName('select')[0]; + a = document.createElement('DIV'); + a.setAttribute('class', 'select-selected'); + a.innerHTML = selElmnt.options[selElmnt.selectedIndex].innerHTML; + x[i].appendChild(a); + b = document.createElement('DIV'); + b.setAttribute('class', 'select-items select-hide'); + + for (j = 0; j < selElmnt.length; j++) { + c = document.createElement('DIV'); + c.innerHTML = selElmnt.options[j].innerHTML; + + c.setAttribute('data-value', selElmnt.options[j].value); + c.setAttribute('data-target', selElmnt.id); + + c.onclick = function(e) { + var y, i, k, s, h; + s = this.parentNode.parentNode.getElementsByTagName('select')[0]; + h = this.parentNode.previousSibling; + for (i = 0; i < s.length; i++) { + if (s.options[i].innerHTML == this.innerHTML) { + s.selectedIndex = i; + h.innerHTML = this.innerHTML; + y = this.parentNode.getElementsByClassName('same-as-selected'); + for (k = 0; k < y.length; k++) { + y[k].removeAttribute('class'); + } + this.setAttribute('class', 'same-as-selected'); + break; + } + } + h.click(); + + var value = this.getAttribute('data-value'); + var target = this.getAttribute('data-target'); + document.getElementById(target).value = value; + document.getElementById(target).onchange(); + }; + b.appendChild(c); + } + x[i].appendChild(b); + a.onclick = function(e) { + e.stopPropagation(); + closeAllSelect(this); + this.nextSibling.classList.toggle('select-hide'); + this.classList.toggle('select-arrow-active'); + }; + } + + function closeAllSelect(elmnt) { + var x, y, i, arrNo = []; + x = document.getElementsByClassName('select-items'); + y = document.getElementsByClassName('select-selected'); + for (i = 0; i < y.length; i++) { + if (elmnt == y[i]) { + arrNo.push(i) + } else { + y[i].classList.remove('select-arrow-active'); + } + } + for (i = 0; i < x.length; i++) { + if (arrNo.indexOf(i)) { + x[i].classList.add('select-hide'); + } + } + } + document.onclick = closeAllSelect; +} diff --git a/Chrome-Extensions/desktopCapture-p2p/video.html b/Chrome-Extensions/desktopCapture-p2p/extension-pages/video.html similarity index 73% rename from Chrome-Extensions/desktopCapture-p2p/video.html rename to Chrome-Extensions/desktopCapture-p2p/extension-pages/video.html index 00b742c3..d1ea8dc4 100644 --- a/Chrome-Extensions/desktopCapture-p2p/video.html +++ b/Chrome-Extensions/desktopCapture-p2p/extension-pages/video.html @@ -1,7 +1,8 @@  + +WebRTC Desktop Sharing -RecordRTC - -

Select Screen Resolutions:

- -
-

Select Codecs:

- -
- - -
- - -
-Keep empty for No password. -
- - -
-Set Your Own Room-ID. Keep empty for random room-id. -
-
- - -
-You can ignore/hide the link box. -
- - \ No newline at end of file diff --git a/Chrome-Extensions/desktopCapture-p2p/options.js b/Chrome-Extensions/desktopCapture-p2p/options.js deleted file mode 100644 index acd17e20..00000000 --- a/Chrome-Extensions/desktopCapture-p2p/options.js +++ /dev/null @@ -1,103 +0,0 @@ -chrome.storage.sync.get(null, function(items) { - if (items['resolutions']) { - document.getElementById('resolutions').value = items['resolutions']; - } else { - chrome.storage.sync.set({ - resolutions: 'fit-screen' - }, function() { - document.getElementById('resolutions').value = 'fit-screen' - }); - } - - if (items['codecs']) { - document.getElementById('codecs').value = items['codecs']; - } else { - chrome.storage.sync.set({ - codecs: 'default' - }, function() { - document.getElementById('codecs').value = 'default' - }); - } - - if (items['room_password']) { - document.getElementById('room_password').value = items['room_password']; - } - - if (items['bandwidth']) { - document.getElementById('bandwidth').value = items['bandwidth']; - } - - if (items['room_id']) { - document.getElementById('room_id').value = items['room_id']; - } - - if (items['room_url_box']) { - document.getElementById('room_url_box').checked = items['room_url_box'] === 'true'; - } else { - chrome.storage.sync.set({ - room_url_box: 'true' - }, function() { - document.getElementById('room_url_box').checked = true; - }); - } -}); - -document.getElementById('resolutions').onchange = function() { - this.disabled = true; - - chrome.storage.sync.set({ - resolutions: this.value - }, function() { - document.getElementById('resolutions').disabled = false; - }); -}; - -document.getElementById('codecs').onchange = function() { - this.disabled = true; - - chrome.storage.sync.set({ - codecs: this.value - }, function() { - document.getElementById('codecs').disabled = false; - }); -}; - -document.getElementById('bandwidth').onblur = function() { - this.disabled = true; - - chrome.storage.sync.set({ - bandwidth: this.value - }, function() { - document.getElementById('bandwidth').disabled = false; - }); -}; - -document.getElementById('room_password').onblur = function() { - this.disabled = true; - - chrome.storage.sync.set({ - room_password: this.value - }, function() { - document.getElementById('room_password').disabled = false; - }); -}; - -document.getElementById('room_id').onblur = function() { - this.disabled = true; - - chrome.storage.sync.set({ - room_id: this.value - }, function() { - document.getElementById('room_id').disabled = false; - }); -}; - -document.getElementById('room_url_box').onchange = function() { - this.disabled = true; - - chrome.storage.sync.set({ - room_url_box: this.checked === true ? 'true' : 'false' - }, function() { - document.getElementById('room_url_box').disabled = false; - }); -}; diff --git a/Chrome-Extensions/desktopCapture-p2p/screen-receivers/ant/index.php b/Chrome-Extensions/desktopCapture-p2p/screen-receivers/ant/index.php new file mode 100644 index 00000000..7773672d --- /dev/null +++ b/Chrome-Extensions/desktopCapture-p2p/screen-receivers/ant/index.php @@ -0,0 +1,128 @@ + +* {margin:0;padding:0;} +iframe { + border: 0; + outline: none; + width: 100%; + height: 100%; +} + + +EOT; + echo $html; + die(); + exit(); +} +?> + +WebRTC AntMedia Broadcast Viewer + + + + + + + + + + + + + + + +
+
+
+ + + + + diff --git a/Chrome-Extensions/desktopCapture-p2p/index.php b/Chrome-Extensions/desktopCapture-p2p/screen-receivers/index.php similarity index 91% rename from Chrome-Extensions/desktopCapture-p2p/index.php rename to Chrome-Extensions/desktopCapture-p2p/screen-receivers/index.php index cbfc963d..1c890bc8 100644 --- a/Chrome-Extensions/desktopCapture-p2p/index.php +++ b/Chrome-Extensions/desktopCapture-p2p/screen-receivers/index.php @@ -97,12 +97,12 @@
- - - - - - + + + + + + \ No newline at end of file diff --git a/Chrome-Extensions/screen-recording/camera-mic.js b/Chrome-Extensions/screen-recording/camera-mic.js index 2600b9e4..eabb18b1 100644 --- a/Chrome-Extensions/screen-recording/camera-mic.js +++ b/Chrome-Extensions/screen-recording/camera-mic.js @@ -1,14 +1,35 @@ document.write('

The purpose of this page is to access your camera and microphone.

'); -document.write('

RecordRTC chrome extension will use your camera only when you manually click the startRecording buttons.

'); -var constraints = { +var port = chrome.runtime.connect(); + +navigator.mediaDevices.getUserMedia({ audio: true, video: true -}; +}).then(function(stream) { + var tracksLength = stream.getTracks().length; + + stream.getTracks().forEach(function(track) { + track.stop(); + }); + + if(tracksLength <= 1) { + throw new Error('Expected two tracks but received: ' + tracksLength); + } + + port.postMessage({ + messageFromContentScript1234: true, + startRecording: true + }); + window.close(); +}).catch(function(e) { + var html = '

Unable to access your camera and/or microphone.

'; + html += '

Please go to following pages and remove "RecordRTC" from blocked-list:

'; + html += '
chrome://settings/content/microphone?search=camera
'; + html += '
chrome://settings/content/microphone?search=microphone
'; + + if(e.message && e.message.toString().length) { + html += '
Error Message: ' + e.message + '
'; + } -navigator.mediaDevices.getUserMedia(constraints).then(function(stream) { - document.write('

'); - document.querySelector('h1').innerHTML = 'Now you can close this page and click extension icon again.' -}).catch(function() { - document.querySelector('h1').innerHTML = 'Unable to capture your camera and microphone.'; + document.body.innerHTML = html; }); diff --git a/Chrome-Extensions/screen-recording/dropdown.js b/Chrome-Extensions/screen-recording/dropdown.js index cbc60b63..8571aa43 100644 --- a/Chrome-Extensions/screen-recording/dropdown.js +++ b/Chrome-Extensions/screen-recording/dropdown.js @@ -45,7 +45,8 @@ document.getElementById('stop-recording').onclick = function() { }, function() { runtimePort.postMessage({ messageFromContentScript1234: true, - stopRecording: true + stopRecording: true, + dropdown: true }); window.close(); }); @@ -63,7 +64,8 @@ document.getElementById('full-screen').onclick = function() { }, function() { runtimePort.postMessage({ messageFromContentScript1234: true, - startRecording: true + startRecording: true, + dropdown: true }); window.close(); }); @@ -81,7 +83,8 @@ document.getElementById('full-screen-audio').onclick = function() { }, function() { runtimePort.postMessage({ messageFromContentScript1234: true, - startRecording: true + startRecording: true, + dropdown: true }); window.close(); }); @@ -99,7 +102,8 @@ document.getElementById('full-screen-microphone-audio').onclick = function() { }, function() { runtimePort.postMessage({ messageFromContentScript1234: true, - startRecording: true + startRecording: true, + dropdown: true }); window.close(); }); @@ -117,7 +121,8 @@ document.getElementById('selected-tab').onclick = function() { }, function() { runtimePort.postMessage({ messageFromContentScript1234: true, - startRecording: true + startRecording: true, + dropdown: true }); window.close(); }); @@ -135,7 +140,8 @@ document.getElementById('selected-tab-audio-only').onclick = function() { }, function() { runtimePort.postMessage({ messageFromContentScript1234: true, - startRecording: true + startRecording: true, + dropdown: true }); window.close(); }); @@ -153,7 +159,8 @@ document.getElementById('microphone-screen').onclick = function() { }, function() { runtimePort.postMessage({ messageFromContentScript1234: true, - startRecording: true + startRecording: true, + dropdown: true }); window.close(); }); @@ -171,7 +178,8 @@ document.getElementById('microphone-screen-camera').onclick = function() { }, function() { runtimePort.postMessage({ messageFromContentScript1234: true, - startRecording: true + startRecording: true, + dropdown: true }); window.close(); }); @@ -189,7 +197,8 @@ document.getElementById('microphone-webcam').onclick = function() { }, function() { runtimePort.postMessage({ messageFromContentScript1234: true, - startRecording: true + startRecording: true, + dropdown: true }); window.close(); }); @@ -207,7 +216,8 @@ document.getElementById('microphone-speakers').onclick = function() { }, function() { runtimePort.postMessage({ messageFromContentScript1234: true, - startRecording: true + startRecording: true, + dropdown: true }); window.close(); }); @@ -225,7 +235,8 @@ document.getElementById('microphone-only').onclick = function() { }, function() { runtimePort.postMessage({ messageFromContentScript1234: true, - startRecording: true + startRecording: true, + dropdown: true }); window.close(); }); @@ -243,7 +254,8 @@ document.getElementById('speakers-only').onclick = function() { }, function() { runtimePort.postMessage({ messageFromContentScript1234: true, - startRecording: true + startRecording: true, + dropdown: true }); window.close(); }); diff --git a/Chrome-Extensions/screen-recording/manifest.json b/Chrome-Extensions/screen-recording/manifest.json index 1cca5543..7415f72b 100755 --- a/Chrome-Extensions/screen-recording/manifest.json +++ b/Chrome-Extensions/screen-recording/manifest.json @@ -2,7 +2,7 @@ "name":"RecordRTC", "short_name":"RecordRTC", "author":"Muaz Khan", - "version":"7.6", + "version":"8.0", "manifest_version":2, "minimum_chrome_version":"49", "description":"Record screen activity, camera, mic, tab, speakers in 4K HD video format (using RecordRTC)", @@ -24,6 +24,18 @@ ], "persistent":false }, + "content_scripts":[ + { + "matches":[ + "" + ], + "js":[ + "background/background.contentScript.js" + ], + "all_frames": true, + "run_at": "document_start" + } + ], "browser_action":{ "default_icon":"images/main-icon.png", "default_title":"Record Your Screen, Tab or Camera", @@ -42,6 +54,7 @@ "tabs", "tabCapture", "activeTab", + "", "identity", "https://www.googleapis.com/*" ], diff --git a/Chrome-Extensions/tabCapture/CodecsHandler.js b/Chrome-Extensions/tabCapture/CodecsHandler.js new file mode 100644 index 00000000..37bd9b15 --- /dev/null +++ b/Chrome-Extensions/tabCapture/CodecsHandler.js @@ -0,0 +1,361 @@ +// CodecsHandler.js + +var CodecsHandler = (function() { + function preferCodec(sdp, codecName) { + var info = splitLines(sdp); + + if (!info.videoCodecNumbers) { + return sdp; + } + + if (codecName === 'vp8' && info.vp8LineNumber === info.videoCodecNumbers[0]) { + return sdp; + } + + if (codecName === 'vp9' && info.vp9LineNumber === info.videoCodecNumbers[0]) { + return sdp; + } + + if (codecName === 'h264' && info.h264LineNumber === info.videoCodecNumbers[0]) { + return sdp; + } + + sdp = preferCodecHelper(sdp, codecName, info); + + return sdp; + } + + function preferCodecHelper(sdp, codec, info, ignore) { + var preferCodecNumber = ''; + + if (codec === 'vp8') { + if (!info.vp8LineNumber) { + return sdp; + } + preferCodecNumber = info.vp8LineNumber; + } + + if (codec === 'vp9') { + if (!info.vp9LineNumber) { + return sdp; + } + preferCodecNumber = info.vp9LineNumber; + } + + if (codec === 'h264') { + if (!info.h264LineNumber) { + return sdp; + } + + preferCodecNumber = info.h264LineNumber; + } + + var newLine = info.videoCodecNumbersOriginal.split('SAVPF')[0] + 'SAVPF '; + + var newOrder = [preferCodecNumber]; + + if (ignore) { + newOrder = []; + } + + info.videoCodecNumbers.forEach(function(codecNumber) { + if (codecNumber === preferCodecNumber) return; + newOrder.push(codecNumber); + }); + + newLine += newOrder.join(' '); + + sdp = sdp.replace(info.videoCodecNumbersOriginal, newLine); + return sdp; + } + + function splitLines(sdp) { + var info = {}; + sdp.split('\n').forEach(function(line) { + if (line.indexOf('m=video') === 0) { + info.videoCodecNumbers = []; + line.split('SAVPF')[1].split(' ').forEach(function(codecNumber) { + codecNumber = codecNumber.trim(); + if (!codecNumber || !codecNumber.length) return; + info.videoCodecNumbers.push(codecNumber); + info.videoCodecNumbersOriginal = line; + }); + } + + if (line.indexOf('VP8/90000') !== -1 && !info.vp8LineNumber) { + info.vp8LineNumber = line.replace('a=rtpmap:', '').split(' ')[0]; + } + + if (line.indexOf('VP9/90000') !== -1 && !info.vp9LineNumber) { + info.vp9LineNumber = line.replace('a=rtpmap:', '').split(' ')[0]; + } + + if (line.indexOf('H264/90000') !== -1 && !info.h264LineNumber) { + info.h264LineNumber = line.replace('a=rtpmap:', '').split(' ')[0]; + } + }); + + return info; + } + + function removeVPX(sdp) { + var info = splitLines(sdp); + + // last parameter below means: ignore these codecs + sdp = preferCodecHelper(sdp, 'vp9', info, true); + sdp = preferCodecHelper(sdp, 'vp8', info, true); + + return sdp; + } + + function disableNACK(sdp) { + if (!sdp || typeof sdp !== 'string') { + throw 'Invalid arguments.'; + } + + sdp = sdp.replace('a=rtcp-fb:126 nack\r\n', ''); + sdp = sdp.replace('a=rtcp-fb:126 nack pli\r\n', 'a=rtcp-fb:126 pli\r\n'); + sdp = sdp.replace('a=rtcp-fb:97 nack\r\n', ''); + sdp = sdp.replace('a=rtcp-fb:97 nack pli\r\n', 'a=rtcp-fb:97 pli\r\n'); + + return sdp; + } + + function prioritize(codecMimeType, peer) { + if (!peer || !peer.getSenders || !peer.getSenders().length) { + return; + } + + if (!codecMimeType || typeof codecMimeType !== 'string') { + throw 'Invalid arguments.'; + } + + peer.getSenders().forEach(function(sender) { + var params = sender.getParameters(); + for (var i = 0; i < params.codecs.length; i++) { + if (params.codecs[i].mimeType == codecMimeType) { + params.codecs.unshift(params.codecs.splice(i, 1)); + break; + } + } + sender.setParameters(params); + }); + } + + function removeNonG722(sdp) { + return sdp.replace(/m=audio ([0-9]+) RTP\/SAVPF ([0-9 ]*)/g, 'm=audio $1 RTP\/SAVPF 9'); + } + + function setBAS(sdp, bandwidth, isScreen) { + if (!bandwidth) { + return sdp; + } + + if (typeof isFirefox !== 'undefined' && isFirefox) { + return sdp; + } + + if (isScreen) { + if (!bandwidth.screen) { + console.warn('It seems that you are not using bandwidth for screen. Screen sharing is expected to fail.'); + } else if (bandwidth.screen < 300) { + console.warn('It seems that you are using wrong bandwidth value for screen. Screen sharing is expected to fail.'); + } + } + + // if screen; must use at least 300kbs + if (bandwidth.screen && isScreen) { + sdp = sdp.replace(/b=AS([^\r\n]+\r\n)/g, ''); + sdp = sdp.replace(/a=mid:video\r\n/g, 'a=mid:video\r\nb=AS:' + bandwidth.screen + '\r\n'); + } + + // remove existing bandwidth lines + if (bandwidth.audio || bandwidth.video) { + sdp = sdp.replace(/b=AS([^\r\n]+\r\n)/g, ''); + } + + if (bandwidth.audio) { + sdp = sdp.replace(/a=mid:audio\r\n/g, 'a=mid:audio\r\nb=AS:' + bandwidth.audio + '\r\n'); + } + + if (bandwidth.screen) { + sdp = sdp.replace(/a=mid:video\r\n/g, 'a=mid:video\r\nb=AS:' + bandwidth.screen + '\r\n'); + } else if (bandwidth.video) { + sdp = sdp.replace(/a=mid:video\r\n/g, 'a=mid:video\r\nb=AS:' + bandwidth.video + '\r\n'); + } + + return sdp; + } + + // Find the line in sdpLines that starts with |prefix|, and, if specified, + // contains |substr| (case-insensitive search). + function findLine(sdpLines, prefix, substr) { + return findLineInRange(sdpLines, 0, -1, prefix, substr); + } + + // Find the line in sdpLines[startLine...endLine - 1] that starts with |prefix| + // and, if specified, contains |substr| (case-insensitive search). + function findLineInRange(sdpLines, startLine, endLine, prefix, substr) { + var realEndLine = endLine !== -1 ? endLine : sdpLines.length; + for (var i = startLine; i < realEndLine; ++i) { + if (sdpLines[i].indexOf(prefix) === 0) { + if (!substr || + sdpLines[i].toLowerCase().indexOf(substr.toLowerCase()) !== -1) { + return i; + } + } + } + return null; + } + + // Gets the codec payload type from an a=rtpmap:X line. + function getCodecPayloadType(sdpLine) { + var pattern = new RegExp('a=rtpmap:(\\d+) \\w+\\/\\d+'); + var result = sdpLine.match(pattern); + return (result && result.length === 2) ? result[1] : null; + } + + function setVideoBitrates(sdp, params) { + params = params || {}; + var xgoogle_min_bitrate = params.min; + var xgoogle_max_bitrate = params.max; + + var sdpLines = sdp.split('\r\n'); + + // VP8 + var vp8Index = findLine(sdpLines, 'a=rtpmap', 'VP8/90000'); + var vp8Payload; + if (vp8Index) { + vp8Payload = getCodecPayloadType(sdpLines[vp8Index]); + } + + if (!vp8Payload) { + return sdp; + } + + var rtxIndex = findLine(sdpLines, 'a=rtpmap', 'rtx/90000'); + var rtxPayload; + if (rtxIndex) { + rtxPayload = getCodecPayloadType(sdpLines[rtxIndex]); + } + + if (!rtxIndex) { + return sdp; + } + + var rtxFmtpLineIndex = findLine(sdpLines, 'a=fmtp:' + rtxPayload.toString()); + if (rtxFmtpLineIndex !== null) { + var appendrtxNext = '\r\n'; + appendrtxNext += 'a=fmtp:' + vp8Payload + ' x-google-min-bitrate=' + (xgoogle_min_bitrate || '228') + '; x-google-max-bitrate=' + (xgoogle_max_bitrate || '228'); + sdpLines[rtxFmtpLineIndex] = sdpLines[rtxFmtpLineIndex].concat(appendrtxNext); + sdp = sdpLines.join('\r\n'); + } + + return sdp; + } + + function setOpusAttributes(sdp, params) { + params = params || {}; + + var sdpLines = sdp.split('\r\n'); + + // Opus + var opusIndex = findLine(sdpLines, 'a=rtpmap', 'opus/48000'); + var opusPayload; + if (opusIndex) { + opusPayload = getCodecPayloadType(sdpLines[opusIndex]); + } + + if (!opusPayload) { + return sdp; + } + + var opusFmtpLineIndex = findLine(sdpLines, 'a=fmtp:' + opusPayload.toString()); + if (opusFmtpLineIndex === null) { + return sdp; + } + + var appendOpusNext = ''; + appendOpusNext += '; stereo=' + (typeof params.stereo != 'undefined' ? params.stereo : '1'); + appendOpusNext += '; sprop-stereo=' + (typeof params['sprop-stereo'] != 'undefined' ? params['sprop-stereo'] : '1'); + + if (typeof params.maxaveragebitrate != 'undefined') { + appendOpusNext += '; maxaveragebitrate=' + (params.maxaveragebitrate || 128 * 1024 * 8); + } + + if (typeof params.maxplaybackrate != 'undefined') { + appendOpusNext += '; maxplaybackrate=' + (params.maxplaybackrate || 128 * 1024 * 8); + } + + if (typeof params.cbr != 'undefined') { + appendOpusNext += '; cbr=' + (typeof params.cbr != 'undefined' ? params.cbr : '1'); + } + + if (typeof params.useinbandfec != 'undefined') { + appendOpusNext += '; useinbandfec=' + params.useinbandfec; + } + + if (typeof params.usedtx != 'undefined') { + appendOpusNext += '; usedtx=' + params.usedtx; + } + + if (typeof params.maxptime != 'undefined') { + appendOpusNext += '\r\na=maxptime:' + params.maxptime; + } + + sdpLines[opusFmtpLineIndex] = sdpLines[opusFmtpLineIndex].concat(appendOpusNext); + + sdp = sdpLines.join('\r\n'); + return sdp; + } + + // forceStereoAudio => via webrtcexample.com + // requires getUserMedia => echoCancellation:false + function forceStereoAudio(sdp) { + var sdpLines = sdp.split('\r\n'); + var fmtpLineIndex = null; + for (var i = 0; i < sdpLines.length; i++) { + if (sdpLines[i].search('opus/48000') !== -1) { + var opusPayload = extractSdp(sdpLines[i], /:(\d+) opus\/48000/i); + break; + } + } + for (var i = 0; i < sdpLines.length; i++) { + if (sdpLines[i].search('a=fmtp') !== -1) { + var payload = extractSdp(sdpLines[i], /a=fmtp:(\d+)/); + if (payload === opusPayload) { + fmtpLineIndex = i; + break; + } + } + } + if (fmtpLineIndex === null) return sdp; + sdpLines[fmtpLineIndex] = sdpLines[fmtpLineIndex].concat('; stereo=1; sprop-stereo=1'); + sdp = sdpLines.join('\r\n'); + return sdp; + } + + return { + removeVPX: removeVPX, + disableNACK: disableNACK, + prioritize: prioritize, + removeNonG722: removeNonG722, + setApplicationSpecificBandwidth: function(sdp, bandwidth, isScreen) { + return setBAS(sdp, bandwidth, isScreen); + }, + setVideoBitrates: function(sdp, params) { + return setVideoBitrates(sdp, params); + }, + setOpusAttributes: function(sdp, params) { + return setOpusAttributes(sdp, params); + }, + preferVP9: function(sdp) { + return preferCodec(sdp, 'vp9'); + }, + preferCodec: preferCodec, + forceStereoAudio: forceStereoAudio + }; +})(); + +// backward compatibility +window.BandwidthHandler = CodecsHandler; diff --git a/Chrome-Extensions/tabCapture/IceServersHandler.js b/Chrome-Extensions/tabCapture/IceServersHandler.js new file mode 100644 index 00000000..aae816f1 --- /dev/null +++ b/Chrome-Extensions/tabCapture/IceServersHandler.js @@ -0,0 +1,53 @@ +// IceServersHandler.js + +var IceServersHandler = (function() { + function getIceServers(connection) { + // resiprocate: 3344+4433 + // pions: 7575 + var iceServers = [{ + 'urls': [ + 'stun:webrtcweb.com:7788', // coTURN + 'stun:webrtcweb.com:7788?transport=udp', // coTURN + ], + 'username': 'muazkh', + 'credential': 'muazkh' + }, + { + 'urls': [ + 'turn:webrtcweb.com:7788', // coTURN 7788+8877 + 'turn:webrtcweb.com:4455?transport=udp', // restund udp + + 'turn:webrtcweb.com:8877?transport=udp', // coTURN udp + 'turn:webrtcweb.com:8877?transport=tcp', // coTURN tcp + ], + 'username': 'muazkh', + 'credential': 'muazkh' + }, + { + 'urls': [ + 'stun:stun.l.google.com:19302', + 'stun:stun1.l.google.com:19302', + 'stun:stun2.l.google.com:19302', + 'stun:stun.l.google.com:19302?transport=udp', + ] + } + ]; + + if (typeof window.InstallTrigger !== 'undefined') { + iceServers = [{ + 'urls': [ + 'turn:webrtcweb.com:7788', + 'stun:webrtcweb.com:7788', + ], + 'username': 'muazkh', + 'credential': 'muazkh' + }]; + } + + return iceServers; + } + + return { + getIceServers: getIceServers + }; +})(); diff --git a/Chrome-Extensions/tabCapture/README.md b/Chrome-Extensions/tabCapture/README.md index 1c18e4f6..fc034b81 100644 --- a/Chrome-Extensions/tabCapture/README.md +++ b/Chrome-Extensions/tabCapture/README.md @@ -2,6 +2,12 @@ This chrome extension not only captures content of the selected tab, but also provides multi-user peer-to-peer tab-streaming. +## Codes to view the shared tab + +Here are different HTML pages that can view your tab: + +* [desktopCapture-p2p/screen-receivers](https://github.com/muaz-khan/Chrome-Extensions/tree/master/desktopCapture-p2p/screen-receivers) + ## How to install? * https://chrome.google.com/webstore/detail/tab-capturing-sharing/pcnepejfgcmidedoimegcafiabjnodhk @@ -34,48 +40,6 @@ To learn more about how to publish a chrome extension in Google App Store: * https://developer.chrome.com/webstore/publish -## How to add inline-install button? - -**Make sure that you added and verified your webpage/domain using Google WebMaster tools.** Additional instructions available [here](https://support.google.com/webmasters/answer/35179?hl=en). - -```html - - - - - - - - - - - - - -``` - ## For more information For additional information, click [this link](https://github.com/muaz-khan/WebRTC-Experiment/blob/7cd04a81b30cdca2db159eb746e2714307640767/Chrome-Extensions/desktopCapture/README.md). diff --git a/Chrome-Extensions/tabCapture/RTCMultiConnection.min.js b/Chrome-Extensions/tabCapture/RTCMultiConnection.min.js index 9d23f665..5cb69a95 100755 --- a/Chrome-Extensions/tabCapture/RTCMultiConnection.min.js +++ b/Chrome-Extensions/tabCapture/RTCMultiConnection.min.js @@ -1,5 +1,18 @@ -!function(){function RTCMultiSession(connection,callbackForSignalingReady){function onDataChannelMessage(e){return e.data.checkingPresence&&connection.channels[e.userid]?void connection.channels[e.userid].send({presenceDetected:!0}):e.data.presenceDetected&&connection.peers[e.userid]?void(connection.peers[e.userid].connected=!0):void("text"===e.data.type?textReceiver.receive(e.data,e.userid,e.extra):connection.autoTranslateText?(e.original=e.data,connection.Translator.TranslateText(e.data,function(translatedText){e.data=translatedText,connection.onmessage(e)})):connection.onmessage(e))}function onNewSession(session){return connection.skipOnNewSession||(session.session||(session.session={}),session.extra||(session.extra={}),connection.sessionid&&session.sessionid!=connection.sessionid)?void 0:connection.onNewSession?(session.join=function(forceSession){if(!forceSession)return connection.join(session);for(var f in forceSession)session.session[f]=forceSession[f];var isDontCaptureUserMedia=connection.dontCaptureUserMedia;connection.dontCaptureUserMedia=!1,connection.captureUserMedia(function(){connection.dontCaptureUserMedia=!0,connection.join(session),connection.dontCaptureUserMedia=isDontCaptureUserMedia},forceSession)},session.extra||(session.extra={}),connection.onNewSession(session)):void connection.join(session)}function updateSocketForLocalStreams(socket){for(var i=0;i=60?socket.send({failedToReceiveRemoteVideo:!0,streamid:args.stream.streamid}):void setTimeout(function(){log("Waiting for incoming remote stream to be started flowing: "+args.numberOfTimes+" seconds."),waitUntilRemoteStreamStartsFlowing(args)},900):afterRemoteStreamStartedFlowing(args))}function initFakeChannel(){if(connection.fakeDataChannels&&!connection.channels[_config.userid]&&!connection.session.data){var fakeChannel={send:function(data){socket.send({fakeData:data})},readyState:"open"};connection.channels[_config.userid]={channel:fakeChannel,send:function(data){this.channel.send(data)}},peerConfig.onopen(fakeChannel)}}function afterRemoteStreamStartedFlowing(args){var mediaElement=args.mediaElement,session=args.session,stream=args.stream;stream.onended=function(){streamedObject.mediaElement&&!streamedObject.mediaElement.parentNode&&document.getElementById(stream.streamid)&&(streamedObject.mediaElement=document.getElementById(stream.streamid)),onStreamEndedHandler(streamedObject,connection)};var streamedObject={mediaElement:mediaElement,stream:stream,streamid:stream.streamid,session:session||connection.session,blobURL:isPluginRTC?"":mediaElement.mozSrcObject?URL.createObjectURL(stream):mediaElement.src,type:"remote",extra:_config.extra,userid:_config.userid,isVideo:isPluginRTC?!!session.video:!!stream.isVideo,isAudio:isPluginRTC?!!session.audio&&!session.video:!!stream.isAudio,isScreen:!!stream.isScreen,isInitiator:!!_config.isInitiator,rtcMultiConnection:connection,socket:socket};if(connection.streams[stream.streamid]=connection._getStream(streamedObject),connection.onstream(streamedObject),!isEmpty(args.preMuted)&&(args.preMuted.audio||args.preMuted.video)){var fakeObject=merge({},streamedObject);fakeObject.session=merge(fakeObject.session,args.preMuted),fakeObject.isAudio=!!fakeObject.session.audio&&!fakeObject.session.video,fakeObject.isVideo=!!fakeObject.session.video,fakeObject.isScreen=!1,connection.onmute(fakeObject)}log("on:add:stream",streamedObject),onSessionOpened(),connection.onspeaking&&initHark({stream:stream,streamedObject:streamedObject,connection:connection})}function onChannelOpened(channel){_config.channel=channel,connection.channels[_config.userid]={channel:_config.channel,send:function(data){connection.send(data,this.channel)}},connection.onopen({extra:_config.extra,userid:_config.userid,channel:channel});for(var q in connection.fileQueue)connection.send(connection.fileQueue[q],channel);isData(connection.session)&&onSessionOpened(),connection.partOfScreen&&connection.partOfScreen.sharing&&connection.peers[_config.userid].sharePartOfScreen(connection.partOfScreen)}function updateSocket(){socket.userid!=_config.userid&&(socket.userid=_config.userid,sockets[_config.socketIndex]=socket,connection.numberOfConnectedUsers++,connection.peers[_config.userid]={socket:socket,peer:peer,userid:_config.userid,extra:_config.extra,userinfo:_config.userinfo,addStream:function(session00){connection.addStream(session00,this.socket)},removeStream:function(streamid){return connection.streams[streamid]?(this.peer.connection.removeStream(connection.streams[streamid].stream),void this.renegotiate()):warn("No such stream exists. Stream-id:",streamid)},renegotiate:function(stream,session){connection.renegotiate(stream,session)},changeBandwidth:function(bandwidth){if(!bandwidth)throw"You MUST pass bandwidth object.";if(isString(bandwidth))throw"Pass object for bandwidth instead of string; e.g. {audio:10, video:20}";this.peer.bandwidth=bandwidth,this.socket.send({changeBandwidth:!0,bandwidth:bandwidth})},sendCustomMessage:function(message){this.socket.send({customMessage:!0,message:message})},onCustomMessage:function(message){log('Received "private" message from',this.userid,isString(message)?message:toStr(message))},drop:function(dontSendMessage){for(var stream in connection.streams)-1==connection._skip.indexOf(stream)&&(stream=connection.streams[stream],stream.userid==connection.userid&&"local"==stream.type&&(this.peer.connection.removeStream(stream.stream),onStreamEndedHandler(stream,connection)),"remote"==stream.type&&stream.userid==this.userid&&onStreamEndedHandler(stream,connection));!dontSendMessage&&this.socket.send({drop:!0})},hold:function(holdMLine){return"answer"==peer.prevCreateType?void this.socket.send({unhold:!0,holdMLine:holdMLine||"both",takeAction:!0}):(this.socket.send({hold:!0,holdMLine:holdMLine||"both"}),this.peer.hold=!0,void this.fireHoldUnHoldEvents({kind:holdMLine,isHold:!0,userid:connection.userid,remoteUser:this.userid}))},unhold:function(holdMLine){return"answer"==peer.prevCreateType?void this.socket.send({unhold:!0,holdMLine:holdMLine||"both",takeAction:!0}):(this.socket.send({unhold:!0,holdMLine:holdMLine||"both"}),this.peer.hold=!1,void this.fireHoldUnHoldEvents({kind:holdMLine,isHold:!1,userid:connection.userid,remoteUser:this.userid}))},fireHoldUnHoldEvents:function(e){var isHold=e.isHold,kind=e.kind,userid=e.remoteUser||e.userid;for(var stream in connection.streams)-1==connection._skip.indexOf(stream)&&(stream=connection.streams[stream],stream.userid==userid&&(isHold&&connection.onhold(merge({kind:kind},stream)),isHold||connection.onunhold(merge({kind:kind},stream))))},redial:function(){for(var stream in connection.streams)-1==connection._skip.indexOf(stream)&&(stream=connection.streams[stream],stream.userid==this.userid&&"remote"==stream.type&&onStreamEndedHandler(stream,connection));log("ReDialing..."),socket.send({recreatePeer:!0}),peer=new PeerConnection,peer.create("offer",peerConfig)},sharePartOfScreen:function(args){function partOfScreenCapturer(){return that.stopPartOfScreenSharing?(that.stopPartOfScreenSharing=!1,void(connection.onpartofscreenstopped&&connection.onpartofscreenstopped())):that.pausePartOfScreenSharing?(connection.onpartofscreenpaused&&connection.onpartofscreenpaused(),setTimeout(partOfScreenCapturer,args.interval||200)):void capturePartOfScreen({element:args.element,connection:connection,callback:function(screenshot){if(!connection.channels[that.userid])throw"No such data channel exists.";screenshot!=lastScreenshot&&(lastScreenshot=screenshot,connection.channels[that.userid].send({screenshot:screenshot,isPartOfScreen:!0})),!args.once&&setTimeout(partOfScreenCapturer,args.interval||200)}})}var that=this,lastScreenshot="";partOfScreenCapturer()},getConnectionStats:function(callback,interval){function invoker(){RTCPeerConnection.prototype.getConnectionStats=window.getConnectionStats,peer.connection&&peer.connection.getConnectionStats(callback,interval)}if(!callback)throw"callback is mandatory.";window.getConnectionStats?invoker():loadScript(connection.resources.getConnectionStats,invoker)},takeSnapshot:function(callback){takeSnapshot({userid:this.userid,connection:connection,callback:callback})}})}function onSessionOpened(){if(connection.isInitiator&&getLength(participants)&&getLength(participants)<=connection.maxParticipantsAllowed&&(connection.session.oneway||connection.session.broadcast||defaultSocket.send({sessionid:connection.sessionid,newParticipant:_config.userid||socket.channel,userData:{userid:_config.userid||socket.channel,extra:_config.extra}})),"chrome"==_config.userinfo.browser&&!_config.renegotiatedOnce)for(var rSession in connection.renegotiatedSessions)_config.renegotiatedOnce=!0,connection.renegotiatedSessions[rSession]&&connection.renegotiatedSessions[rSession].stream&&connection.peers[_config.userid].renegotiate(connection.renegotiatedSessions[rSession].stream,connection.renegotiatedSessions[rSession].session)}function socketResponse(response){if(!isRMSDeleted&&response.userid!=connection.userid){if(response.sdp){_config.userid=response.userid,_config.extra=response.extra||{},_config.renegotiate=response.renegotiate,_config.streaminfo=response.streaminfo,_config.isInitiator=response.isInitiator,_config.userinfo=response.userinfo;var sdp=JSON.parse(response.sdp);"offer"==sdp.type&&(peerConfig.preferSCTP=!!response.preferSCTP,connection.fakeDataChannels=!!response.fakeDataChannels),initFakeChannel(),sdpInvoker(sdp,response.labels)}if(response.candidate&&peer&&peer.addIceCandidate(JSON.parse(response.candidate)),response.streamid&&(rtcMultiSession.streamids||(rtcMultiSession.streamids={}),rtcMultiSession.streamids[response.streamid]||(rtcMultiSession.streamids[response.streamid]=response.streamid,connection.onstreamid(response))),response.mute||response.unmute)if(response.promptMuteUnmute){if(!connection.privileges.canMuteRemoteStream)return void connection.onstatechange({userid:response.userid,extra:response.extra,name:"mute-request-denied",reason:response.userid+' tried to mute your stream; however "privileges.canMuteRemoteStream" is "false".'});connection.streams[response.streamid]&&(response.mute&&!connection.streams[response.streamid].muted&&connection.streams[response.streamid].mute(response.session),response.unmute&&connection.streams[response.streamid].muted&&connection.streams[response.streamid].unmute(response.session))}else{var streamObject={};connection.streams[response.streamid]&&(streamObject=connection.streams[response.streamid]);var session=response.session,fakeObject=merge({},streamObject);fakeObject.session=session,fakeObject.isAudio=!!fakeObject.session.audio&&!fakeObject.session.video,fakeObject.isVideo=!!fakeObject.session.video,fakeObject.isScreen=!!fakeObject.session.screen,response.mute&&connection.onmute(fakeObject||response),response.unmute&&connection.onunmute(fakeObject||response)}if(response.isVolumeChanged&&(log("Volume of stream: "+response.streamid+" has changed to: "+response.volume),connection.streams[response.streamid])){var mediaElement=connection.streams[response.streamid].mediaElement;mediaElement&&(mediaElement.volume=response.volume)}if(response.stopped&&connection.streams[response.streamid]&&onStreamEndedHandler(connection.streams[response.streamid],connection),response.promptStreamStop){if(!connection.privileges.canStopRemoteStream)return void connection.onstatechange({userid:response.userid,extra:response.extra,name:"stop-request-denied",reason:response.userid+' tried to stop your stream; however "privileges.canStopRemoteStream" is "false".'});warn("Remote stream has been manually stopped!"),connection.streams[response.streamid]&&connection.streams[response.streamid].stop()}if(response.left){if(isFirefox){var userLeft=response.userid;for(var stream in connection.streams)stream=connection.streams[stream],stream.userid==userLeft&&(connection.stopMediaStream(stream),onStreamEndedHandler(stream,connection))}if(peer&&peer.connection&&("closed"!=peer.connection.signalingState&&-1==peer.connection.iceConnectionState.search(/disconnected|failed/gi)&&peer.connection.close(),peer.connection=null),participants[response.userid]&&delete participants[response.userid],response.closeEntireSession)return connection.onSessionClosed(response),void connection.leave();connection.remove(response.userid),onLeaveHandler({userid:response.userid,extra:response.extra||{},entireSessionClosed:!!response.closeEntireSession},connection)}if(response.playRoleOfBroadcaster&&(response.extra&&(connection.extra=merge(connection.extra,response.extra)),response.participants&&(participants=response.participants,participants[connection.userid]&&delete participants[connection.userid],sockets[0]&&sockets[0].userid==response.userid&&(delete sockets[0],sockets=swap(sockets)),socketObjects[response.userid]&&delete socketObjects[response.userid]),setTimeout(connection.playRoleOfInitiator,2e3)),response.changeBandwidth){if(!connection.peers[response.userid])throw"No such peer exists.";connection.peers[response.userid].peer.bandwidth=response.bandwidth,connection.peers[response.userid].renegotiate()}if(response.customMessage){if(!connection.peers[response.userid])throw"No such peer exists.";if(response.message.ejected){if(connection.sessionDescriptions[connection.sessionid].userid!=response.userid)throw"only initiator can eject a user.";connection.leave(),connection.onSessionClosed({userid:response.userid,extra:response.extra||_config.extra,isEjected:!0})}else connection.peers[response.userid].onCustomMessage(response.message)}if(response.drop){if(!connection.peers[response.userid])throw"No such peer exists.";connection.peers[response.userid].drop(!0),connection.peers[response.userid].renegotiate(),connection.ondrop(response.userid)}if(response.hold||response.unhold){if(!connection.peers[response.userid])throw"No such peer exists.";if(response.takeAction)return void connection.peers[response.userid][response.hold?"hold":"unhold"](response.holdMLine);connection.peers[response.userid].peer.hold=!!response.hold,connection.peers[response.userid].peer.holdMLine=response.holdMLine,socket.send({isRenegotiate:!0}),connection.peers[response.userid].fireHoldUnHoldEvents({kind:response.holdMLine,isHold:!!response.hold,userid:response.userid})}response.isRenegotiate&&connection.peers[response.userid].renegotiate(null,connection.peers[response.userid].peer.session),response.fakeData&&peerConfig.onmessage(response.fakeData),response.recreatePeer&&(peer=new PeerConnection),response.failedToReceiveRemoteVideo&&(log("Remote peer hasn't received stream: "+response.streamid+". Renegotiating..."),connection.peers[response.userid]&&connection.peers[response.userid].renegotiate()),response.redial&&connection.peers[response.userid]&&("disconnected"!=connection.peers[response.userid].peer.connection.iceConnectionState&&(_config.redialing=!1),"disconnected"!=connection.peers[response.userid].peer.connection.iceConnectionState||_config.redialing||(_config.redialing=!0,warn("Peer connection is closed.",toStr(connection.peers[response.userid].peer.connection),"ReDialing.."),connection.peers[response.userid].redial()))}}function sdpInvoker(sdp,labels){function createAnswer(){peer.recreateAnswer(sdp,session,function(_sdp,streaminfo){sendsdp({sdp:_sdp,socket:socket,streaminfo:streaminfo}),connection.detachStreams=[]})}if("answer"==sdp.type)return peer.setRemoteDescription(sdp),void updateSocket();if(!_config.renegotiate&&"offer"==sdp.type)return peerConfig.offerDescription=sdp,peerConfig.session=connection.session,peer||(peer=new PeerConnection),peer.create("answer",peerConfig),void updateSocket();var session=_config.renegotiate;if(detachMediaStream(labels,peer.connection),session.oneway||isData(session))createAnswer(),delete _config.renegotiate;else{if(_config.capturing)return;_config.capturing=!0,connection.captureUserMedia(function(stream){_config.capturing=!1,peer.addStream(stream),connection.renegotiatedSessions[JSON.stringify(_config.renegotiate)]={session:_config.renegotiate,stream:stream},delete _config.renegotiate,createAnswer()},_config.renegotiate)}}var socketConfig={channel:_config.channel,onmessage:socketResponse,onopen:function(_socket){_socket&&(socket=_socket),isofferer&&!peer&&(peerConfig.session=connection.session,peer||(peer=new PeerConnection),peer.create("offer",peerConfig)),_config.socketIndex=socket.index=sockets.length,socketObjects[socketConfig.channel]=socket,sockets[_config.socketIndex]=socket,updateSocketForLocalStreams(socket),socket.__push||(socket.__push=socket.send,socket.send=function(message){message.userid=message.userid||connection.userid,message.extra=message.extra||connection.extra||{},socket.__push(message)})}};socketConfig.callback=function(_socket){socket=_socket,socketConfig.onopen()};var socket=connection.openSignalingChannel(socketConfig);socket&&socketConfig.onopen(socket);var peer,isofferer=_config.isofferer,peerConfig={onopen:onChannelOpened,onicecandidate:function(candidate){if(!connection.candidates)throw"ICE candidates are mandatory.";if(!connection.iceProtocols)throw"At least one must be true; UDP or TCP.";var iceCandidates=connection.candidates,stun=iceCandidates.stun,turn=iceCandidates.turn;if(isNull(iceCandidates.reflexive)||(stun=iceCandidates.reflexive),isNull(iceCandidates.relay)||(turn=iceCandidates.relay),(iceCandidates.host||!candidate.candidate.match(/a=candidate.*typ host/g))&&(turn||!candidate.candidate.match(/a=candidate.*typ relay/g))&&(stun||!candidate.candidate.match(/a=candidate.*typ srflx/g))){var protocol=connection.iceProtocols;(protocol.udp||!candidate.candidate.match(/a=candidate.* udp/g))&&(protocol.tcp||!candidate.candidate.match(/a=candidate.* tcp/g))&&(window.selfNPObject||(window.selfNPObject=candidate),socket&&socket.send({candidate:JSON.stringify({candidate:candidate.candidate,sdpMid:candidate.sdpMid,sdpMLineIndex:candidate.sdpMLineIndex})}))}},onmessage:function(data){if(data){var abToStr=ab2str(data);if(-1!=abToStr.indexOf('"userid":'))abToStr=JSON.parse(abToStr),onDataChannelMessage(abToStr);else if(data instanceof ArrayBuffer||data instanceof DataView){if(!connection.enableFileSharing)throw'It seems that receiving data is either "Blob" or "File" but file sharing is disabled.';if(!rtcMultiSession.fileBufferReader){var that=this;return void initFileBufferReader(connection,function(fbr){rtcMultiSession.fileBufferReader=fbr,that.onmessage(data)})}var fileBufferReader=rtcMultiSession.fileBufferReader;return void fileBufferReader.convertToObject(data,function(chunk){return chunk.maxChunks||chunk.readyForNextChunk?chunk.readyForNextChunk?void fileBufferReader.getNextChunk(chunk.uuid,function(nextChunk,isLastChunk,extra){rtcMultiSession.send(nextChunk)}):void fileBufferReader.addChunk(chunk,function(promptNextChunk){rtcMultiSession.send(promptNextChunk)}):void connection.onmessage({data:chunk,userid:_config.userid,extra:_config.extra})})}}},onaddstream:function(stream,session){function eventListener(){setTimeout(function(){mediaElement.muted=!1,afterRemoteStreamStartedFlowing({mediaElement:mediaElement,session:session,stream:stream,preMuted:preMuted})},3e3),mediaElement.removeEventListener("play",eventListener)}if(session=session||_config.renegotiate||connection.session,!isData(session)){session.screen&&(session.audio||session.video)&&(_config.gotAudioOrVideo?(session.audio=!1,session.video=!1):(_config.gotAudioOrVideo=!0,session.screen=!1));var preMuted={};if(_config.streaminfo){var streaminfo=_config.streaminfo.split("----"),strInfo=JSON.parse(streaminfo[streaminfo.length-1]);isIE||(stream.streamid=strInfo.streamid,stream.isScreen=!!strInfo.isScreen,stream.isVideo=!!strInfo.isVideo,stream.isAudio=!!strInfo.isAudio,preMuted=strInfo.preMuted),streaminfo.pop(),_config.streaminfo=streaminfo.join("----")}var mediaElement=createMediaElement(stream,merge({remote:!0},session));return connection.setDefaultEventsForMediaElement&&connection.setDefaultEventsForMediaElement(mediaElement,stream.streamid),isPluginRTC||stream.getVideoTracks().length?void waitUntilRemoteStreamStartsFlowing({mediaElement:mediaElement,session:session,stream:stream,preMuted:preMuted}):mediaElement.addEventListener("play",eventListener,!1)}},onremovestream:function(stream){stream&&stream.streamid?(stream=connection.streams[stream.streamid],stream&&(log("on:stream:ended via on:remove:stream",stream),onStreamEndedHandler(stream,connection))):log("on:remove:stream",stream)},onclose:function(e){e.extra=_config.extra,e.userid=_config.userid,connection.onclose(e),connection.channels[e.userid]&&delete connection.channels[e.userid]},onerror:function(e){e.extra=_config.extra,e.userid=_config.userid,connection.onerror(e)},oniceconnectionstatechange:function(event){log("oniceconnectionstatechange",toStr(event)),peer.connection&&"connected"==peer.connection.iceConnectionState&&"complete"==peer.connection.iceGatheringState&&"stable"==peer.connection.signalingState&&1==connection.numberOfConnectedUsers&&connection.onconnected({userid:_config.userid,extra:_config.extra,peer:connection.peers[_config.userid],targetuser:_config.userinfo}),!connection.isInitiator&&peer.connection&&"connected"==peer.connection.iceConnectionState&&"complete"==peer.connection.iceGatheringState&&"stable"==peer.connection.signalingState&&1==connection.numberOfConnectedUsers&&connection.onstatechange({userid:_config.userid,extra:_config.extra,name:"connected-with-initiator",reason:"ICE connection state seems connected; gathering state is completed; and signaling state is stable."}),connection.peers[_config.userid]&&connection.peers[_config.userid].oniceconnectionstatechange&&connection.peers[_config.userid].oniceconnectionstatechange(event),connection.peers[_config.userid]&&"failed"==connection.peers[_config.userid].peer.connection.iceConnectionState&&connection.onfailed({userid:_config.userid,extra:_config.extra,peer:connection.peers[_config.userid],targetuser:_config.userinfo}),connection.peers[_config.userid]&&"disconnected"==connection.peers[_config.userid].peer.connection.iceConnectionState&&(!peer.connection.renegotiate&&connection.ondisconnected({userid:_config.userid,extra:_config.extra,peer:connection.peers[_config.userid],targetuser:_config.userinfo}),peer.connection.renegotiate=!1),connection.autoReDialOnFailure&&connection.peers[_config.userid]&&("disconnected"!=connection.peers[_config.userid].peer.connection.iceConnectionState&&(_config.redialing=!1),"disconnected"!=connection.peers[_config.userid].peer.connection.iceConnectionState||_config.redialing||(_config.redialing=!0,warn("Peer connection is closed.",toStr(connection.peers[_config.userid].peer.connection),"ReDialing.."),connection.peers[_config.userid].socket.send({redial:!0}),connection.streams.remove({remote:!0,userid:_config.userid})))},onsignalingstatechange:function(event){log("onsignalingstatechange",toStr(event))},attachStreams:connection.dontAttachStream?[]:connection.attachStreams,iceServers:connection.iceServers,rtcConfiguration:connection.rtcConfiguration,bandwidth:connection.bandwidth,sdpConstraints:connection.sdpConstraints,optionalArgument:connection.optionalArgument,disableDtlsSrtp:connection.disableDtlsSrtp,dataChannelDict:connection.dataChannelDict,preferSCTP:connection.preferSCTP,onSessionDescription:function(sessionDescription,streaminfo){sendsdp({sdp:sessionDescription,socket:socket,streaminfo:streaminfo})},trickleIce:connection.trickleIce,processSdp:connection.processSdp,sendStreamId:function(stream){socket&&socket.send({streamid:stream.streamid,isScreen:!!stream.isScreen,isAudio:!!stream.isAudio,isVideo:!!stream.isVideo})},rtcMultiConnection:connection};connection.playRoleOfInitiator=function(){connection.dontCaptureUserMedia=!0,connection.open(),sockets=swap(sockets),connection.dontCaptureUserMedia=!1},connection.askToShareParticipants=function(){defaultSocket&&defaultSocket.send({askToShareParticipants:!0})},connection.shareParticipants=function(args){var message={joinUsers:participants,userid:connection.userid,extra:connection.extra};args&&(args.dontShareWith&&(message.dontShareWith=args.dontShareWith),args.shareWith&&(message.shareWith=args.shareWith)),defaultSocket.send(message)}}function detachMediaStream(labels,peer){if(labels)for(var i=0;i1&&isString(arguments[0])&&(e={},arguments[0]&&(e.userid=arguments[0]),arguments[1]&&(e.extra=arguments[1]),arguments[2]&&(e.channel=arguments[2])),connection.captureUserMedia(function(){_accept(e)})};var isRMSDeleted=!1;this.disconnect=function(){if(this.isOwnerLeaving=!0,!connection.keepStreamsOpened){for(var streamid in connection.localStreams)connection.localStreams[streamid].stop();connection.localStreams={},currentUserMediaRequest={streams:[],mutex:!1,queueRequests:[]}}connection.isInitiator&&defaultSocket.send({isDisconnectSockets:!0}),connection.refresh(),rtcMultiSession.defaultSocket=defaultSocket=null,isRMSDeleted=!0,connection.ondisconnected({userid:connection.userid,extra:connection.extra,peer:connection.peers[connection.userid],isSocketsDisconnected:!0}),connection.close(),window.removeEventListener("beforeunload",rtcMultiSession.leaveHandler),window.removeEventListener("keyup",rtcMultiSession.leaveHandler),delete this,log("Disconnected your sockets, peers, streams and everything except RTCMultiConnection object.")}}function convertToAudioStream(mediaStream){if(!mediaStream)throw"MediaStream is mandatory.";if(mediaStream.getVideoTracks&&!mediaStream.getVideoTracks().length)return mediaStream;var context=new AudioContext,mediaStreamSource=context.createMediaStreamSource(mediaStream),destination=context.createMediaStreamDestination();return mediaStreamSource.connect(destination),webAudioMediaStreamSources.push(mediaStreamSource),destination.stream}function getRandomString(){if(window.crypto&&crypto.getRandomValues&&-1==navigator.userAgent.indexOf("Safari")){for(var a=window.crypto.getRandomValues(new Uint32Array(3)),token="",i=0,l=a.length;l>i;i++)token+=a[i].toString(36);return token}return(Math.random()*(new Date).getTime()).toString(36).replace(/\./g,"")}function isData(session){return!session.audio&&!session.video&&!session.screen&&session.data}function isNull(obj){return"undefined"==typeof obj}function isString(obj){return"string"==typeof obj}function isEmpty(session){var length=0;for(var s in session)length++;return 0==length}function ab2str(buf){var result="";try{result=String.fromCharCode.apply(null,new Uint16Array(buf))}catch(e){}return result}function str2ab(str){isString(str)||(str=JSON.stringify(str));for(var buf=new ArrayBuffer(2*str.length),bufView=new Uint16Array(buf),i=0,strLen=str.length;strLen>i;i++)bufView[i]=str.charCodeAt(i);return buf}function swap(arr){for(var swapped=[],length=arr.length,i=0;length>i;i++)arr[i]&&arr[i]!==!0&&swapped.push(arr[i]);return swapped}function forEach(obj,callback){for(var item in obj)callback(obj[item],item)}function log(){console.log(arguments)}function error(){console.error(arguments)}function warn(){console.warn(arguments)}function toStr(obj){return JSON.stringify(obj,function(key,value){return value&&value.sdp?(log(value.sdp.type," ",value.sdp.sdp),""):value}," ")}function getLength(obj){var length=0;for(var o in obj)o&&length++;return length}function createMediaElement(stream,session){var mediaElement=document.createElement(stream.isAudio?"audio":"video");if(mediaElement.id=stream.streamid,isPluginRTC){var body=document.body||document.documentElement;return body.insertBefore(mediaElement,body.firstChild),setTimeout(function(){Plugin.attachMediaStream(mediaElement,stream)},1e3),Plugin.attachMediaStream(mediaElement,stream)}return mediaElement[isFirefox?"mozSrcObject":"src"]=isFirefox?stream:(window.URL||window.webkitURL).createObjectURL(stream),mediaElement.controls=!0,mediaElement.autoplay=!!session.remote,mediaElement.muted=session.remote?!1:!0,isFirefox&&mediaElement.addEventListener("ended",function(){stream.onended()},!1),mediaElement.play(),mediaElement}function onStreamEndedHandler(streamedObject,connection){(!streamedObject.mediaElement||streamedObject.mediaElement.parentNode)&&(onStreamEndedHandlerFiredFor[streamedObject.streamid]||(onStreamEndedHandlerFiredFor[streamedObject.streamid]=streamedObject,connection.onstreamended(streamedObject)))}function onLeaveHandler(event,connection){onLeaveHandlerFiredFor[event.userid]||(onLeaveHandlerFiredFor[event.userid]=event,connection.onleave(event))}function takeSnapshot(args){function _takeSnapshot(video){var canvas=document.createElement("canvas");canvas.width=video.videoWidth||video.clientWidth,canvas.height=video.videoHeight||video.clientHeight;var context=canvas.getContext("2d");context.drawImage(video,0,0,canvas.width,canvas.height),connection.snapshots[userid]=canvas.toDataURL("image/png"),args.callback&&args.callback(connection.snapshots[userid])}var userid=args.userid,connection=args.connection;if(args.mediaElement)return _takeSnapshot(args.mediaElement);for(var stream in connection.streams)stream=connection.streams[stream],stream.userid==userid&&stream.stream&&stream.stream.getVideoTracks&&stream.stream.getVideoTracks().length&&_takeSnapshot(stream.mediaElement)}function invokeMediaCaptured(connection){connection.onMediaCaptured&&(connection.onMediaCaptured(),delete connection.onMediaCaptured)}function merge(mergein,mergeto){if(mergein||(mergein={}),!mergeto)return mergein;for(var item in mergeto)mergein[item]=mergeto[item];return mergein}function loadScript(src,onload){var script=document.createElement("script");script.src=src,script.onload=function(){log("loaded resource:",src),onload&&onload()},document.documentElement.appendChild(script)}function capturePartOfScreen(args){var connection=args.connection,element=args.element;if(!window.html2canvas)return loadScript(connection.resources.html2canvas,function(){capturePartOfScreen(args)});if(isString(element)&&(element=document.querySelector(element),element||(element=document.getElementById(element))),!element)throw"HTML DOM Element is not accessible!";html2canvas(element,{onrendered:function(canvas){args.callback(canvas.toDataURL())}})}function initFileBufferReader(connection,callback){function _private(chunk){return chunk.userid=chunk.extra.userid,chunk}if(!window.FileBufferReader)return void loadScript(connection.resources.FileBufferReader,function(){initFileBufferReader(connection,callback)});var fileBufferReader=new FileBufferReader;fileBufferReader.onProgress=function(chunk){connection.onFileProgress(_private(chunk),chunk.uuid)},fileBufferReader.onBegin=function(file){connection.onFileStart(_private(file))},fileBufferReader.onEnd=function(file){connection.onFileEnd(_private(file))},callback(fileBufferReader)}function loadScreenFrame(skip){if(DetectRTC.screen.extensionid==ReservedExtensionID&&!loadedScreenFrame){if(!skip)return loadScreenFrame(!0);loadedScreenFrame=!0;var iframe=document.createElement("iframe");iframe.onload=function(){iframe.isLoaded=!0,log("Screen Capturing frame is loaded.")},iframe.src="https://www.webrtc-experiment.com/getSourceId/",iframe.style.display="none",(document.body||document.documentElement).appendChild(iframe),screenFrame={postMessage:function(){return iframe.isLoaded?void iframe.contentWindow.postMessage({captureSourceId:!0},"*"):void setTimeout(screenFrame.postMessage,100)}}}}function loadIceFrame(callback,skip){if(!loadedIceFrame){if(!skip)return loadIceFrame(callback,!0);loadedIceFrame=!0;var iframe=document.createElement("iframe");iframe.onload=function(){function iFrameLoaderCallback(event){event.data&&event.data.iceServers&&(callback(event.data.iceServers),window.removeEventListener("message",iFrameLoaderCallback))}iframe.isLoaded=!0,listenEventHandler("message",iFrameLoaderCallback),iframe.contentWindow.postMessage("get-ice-servers","*")},iframe.src="https://cdn.webrtc-experiment.com/getIceServers/",iframe.style.display="none",(document.body||document.documentElement).appendChild(iframe)}}function muteOrUnmute(e){var stream=e.stream,root=e.root,session=e.session||{},enabled=e.enabled;if(session.audio||session.video||(session=isString(session)?{audio:!0,video:!0}:merge(session,{audio:!0,video:!0})),session.type){if("remote"==session.type&&"remote"!=root.type)return;if("local"==session.type&&"local"!=root.type)return}if(log(enabled?"Muting":"UnMuting","session",toStr(session)),"local"==root.type&&session.audio&&stream.getAudioTracks){var audioTracks=stream.getAudioTracks()[0];audioTracks&&(audioTracks.enabled=!enabled)}if("local"==root.type&&(session.video||session.screen)&&stream.getVideoTracks){var videoTracks=stream.getVideoTracks()[0];videoTracks&&(videoTracks.enabled=!enabled)}if(root.sockets.forEach(function(socket){"local"==root.type&&socket.send({streamid:root.streamid,mute:!!enabled,unmute:!enabled,session:session}),"remote"==root.type&&socket.send({promptMuteUnmute:!0,streamid:root.streamid,mute:!!enabled,unmute:!enabled,session:session})}),"remote"!=root.type){var fakeObject=merge({},root);fakeObject.session=session,fakeObject.isAudio=!!fakeObject.session.audio&&!fakeObject.session.video,fakeObject.isVideo=!!fakeObject.session.video,fakeObject.isScreen=!!fakeObject.session.screen,enabled&&(stream.preMuted={audio:stream.getAudioTracks().length&&!stream.getAudioTracks()[0].enabled,video:stream.getVideoTracks().length&&!stream.getVideoTracks()[0].enabled},root.rtcMultiConnection.onmute(fakeObject)),enabled||(stream.preMuted={},root.rtcMultiConnection.onunmute(fakeObject))}}function initHark(args){if(!window.hark)return void loadScript(args.connection.resources.hark,function(){initHark(args)});var connection=args.connection,streamedObject=args.streamedObject,stream=args.stream,options={},speechEvents=hark(stream,options);speechEvents.on("speaking",function(){connection.onspeaking&&connection.onspeaking(streamedObject)}),speechEvents.on("stopped_speaking",function(){connection.onsilence&&connection.onsilence(streamedObject)}),speechEvents.on("volume_change",function(volume,threshold){connection.onvolumechange&&connection.onvolumechange(merge({volume:volume,threshold:threshold},streamedObject))})}function getUserMedia(options){function streaming(stream,returnBack,streamid){streamid||(streamid=getRandomString()),connection.localStreams[streamid]=stream;var video=options.video;video&&(video[isFirefox?"mozSrcObject":"src"]=isFirefox?stream:(window.URL||window.webkitURL).createObjectURL(stream),video.play()),options.onsuccess(stream,returnBack,idInstance,streamid),currentUserMediaRequest.streams[idInstance]={stream:stream,streamid:streamid},currentUserMediaRequest.mutex=!1,currentUserMediaRequest.queueRequests.length&&getUserMedia(currentUserMediaRequest.queueRequests.shift())}if(isPluginRTC)return Plugin.getUserMedia?Plugin.getUserMedia(options.constraints||{audio:!0,video:!0},options.onsuccess,options.onerror):void setTimeout(function(){getUserMedia(options)},1e3);if(currentUserMediaRequest.mutex===!0)return void currentUserMediaRequest.queueRequests.push(options);currentUserMediaRequest.mutex=!0;var connection=options.connection,mediaConstraints=options.mediaConstraints||{},videoConstraints="boolean"==typeof mediaConstraints.video?mediaConstraints.video:mediaConstraints.video||mediaConstraints,audioConstraints="boolean"==typeof mediaConstraints.audio?mediaConstraints.audio:mediaConstraints.audio||defaultConstraints,n=navigator,hints=options.constraints||{audio:defaultConstraints,video:defaultConstraints};hints.video&&hints.video.mozMediaSource&&(videoConstraints={}),1==hints.video&&(hints.video=defaultConstraints),1==hints.audio&&(hints.audio=defaultConstraints),"boolean"==typeof audioConstraints&&hints.audio&&(hints.audio=audioConstraints),"boolean"==typeof videoConstraints&&hints.video&&(hints.video=videoConstraints);var audioMandatoryConstraints=audioConstraints.mandatory;isEmpty(audioMandatoryConstraints)||(hints.audio.mandatory=merge(hints.audio.mandatory,audioMandatoryConstraints));var videoMandatoryConstraints=videoConstraints.mandatory;if(videoMandatoryConstraints){var mandatory={};if(videoMandatoryConstraints.minWidth&&(mandatory.minWidth=videoMandatoryConstraints.minWidth),videoMandatoryConstraints.minHeight&&(mandatory.minHeight=videoMandatoryConstraints.minHeight),videoMandatoryConstraints.maxWidth&&(mandatory.maxWidth=videoMandatoryConstraints.maxWidth),videoMandatoryConstraints.maxHeight&&(mandatory.maxHeight=videoMandatoryConstraints.maxHeight),videoMandatoryConstraints.minAspectRatio&&(mandatory.minAspectRatio=videoMandatoryConstraints.minAspectRatio),videoMandatoryConstraints.maxFrameRate&&(mandatory.maxFrameRate=videoMandatoryConstraints.maxFrameRate),videoMandatoryConstraints.minFrameRate&&(mandatory.minFrameRate=videoMandatoryConstraints.minFrameRate),mandatory.minWidth&&mandatory.minHeight){var allowed=["1920:1080","1280:720","960:720","640:360","640:480","320:240","320:180"];(-1==allowed.indexOf(mandatory.minWidth+":"+mandatory.minHeight)||-1==allowed.indexOf(mandatory.maxWidth+":"+mandatory.maxHeight))&&error('The min/max width/height constraints you passed "seems" NOT supported.',toStr(mandatory)),(mandatory.minWidth>mandatory.maxWidth||mandatory.minHeight>mandatory.maxHeight)&&error("Minimum value must not exceed maximum value.",toStr(mandatory)),mandatory.minWidth>=1280&&mandatory.minHeight>=720&&warn("Enjoy HD video! min/"+mandatory.minWidth+":"+mandatory.minHeight+", max/"+mandatory.maxWidth+":"+mandatory.maxHeight)}hints.video.mandatory=merge(hints.video.mandatory,mandatory)}videoMandatoryConstraints&&(hints.video.mandatory=merge(hints.video.mandatory,videoMandatoryConstraints)),videoConstraints.optional&&videoConstraints.optional instanceof Array&&videoConstraints.optional.length&&(hints.video.optional=hints.video.optional?hints.video.optional.concat(videoConstraints.optional):videoConstraints.optional),audioConstraints.optional&&audioConstraints.optional instanceof Array&&audioConstraints.optional.length&&(hints.audio.optional=hints.audio.optional?hints.audio.optional.concat(audioConstraints.optional):audioConstraints.optional),hints.video.mandatory&&!isEmpty(hints.video.mandatory)&&connection._mediaSources.video&&(hints.video.optional.forEach(function(video,index){video.sourceId==connection._mediaSources.video&&delete hints.video.optional[index]}),hints.video.optional=swap(hints.video.optional),hints.video.optional.push({sourceId:connection._mediaSources.video})),hints.audio.mandatory&&!isEmpty(hints.audio.mandatory)&&connection._mediaSources.audio&&(hints.audio.optional.forEach(function(audio,index){audio.sourceId==connection._mediaSources.audio&&delete hints.audio.optional[index]}),hints.audio.optional=swap(hints.audio.optional),hints.audio.optional.push({sourceId:connection._mediaSources.audio})),hints.video&&!hints.video.mozMediaSource&&hints.video.optional&&hints.video.mandatory&&!hints.video.optional.length&&isEmpty(hints.video.mandatory)&&(hints.video=!0),isMobileDevice&&(hints={audio:!!hints.audio,video:!!hints.video}),log("invoked getUserMedia with constraints:",toStr(hints));var idInstance=JSON.stringify(hints);currentUserMediaRequest.streams[idInstance]?streaming(currentUserMediaRequest.streams[idInstance].stream,!0,currentUserMediaRequest.streams[idInstance].streamid):(n.getMedia=n.webkitGetUserMedia||n.mozGetUserMedia,n.getMedia(hints,streaming,function(error){options.onerror(error,hints)}))}function setSdpConstraints(config){var sdpConstraints,sdpConstraints_mandatory={OfferToReceiveAudio:!!config.OfferToReceiveAudio,OfferToReceiveVideo:!!config.OfferToReceiveVideo};return sdpConstraints={mandatory:sdpConstraints_mandatory,optional:[{VoiceActivityDetection:!1}]},navigator.mozGetUserMedia&&firefoxVersion>34&&(sdpConstraints={OfferToReceiveAudio:!!config.OfferToReceiveAudio,OfferToReceiveVideo:!!config.OfferToReceiveVideo}),sdpConstraints}function PeerConnection(){return{create:function(type,options){merge(this,options);var self=this;return this.type=type,this.init(),this.attachMediaStreams(),isFirefox&&this.session.data?(this.session.data&&"offer"==type&&this.createDataChannel(),this.getLocalDescription(type),this.session.data&&"answer"==type&&this.createDataChannel()):self.getLocalDescription(type),this},getLocalDescription:function(createType){function createDescription(){self.connection["offer"==createType?"createOffer":"createAnswer"](function(sessionDescription){sessionDescription.sdp=self.serializeSdp(sessionDescription.sdp,createType),self.connection.setLocalDescription(sessionDescription),self.trickleIce&&self.onSessionDescription(sessionDescription,self.streaminfo),"offer"==sessionDescription.type&&log("offer sdp",sessionDescription.sdp),self.prevCreateType=createType},self.onSdpError,self.constraints)}log("(getLocalDescription) peer createType is",createType),this.session.inactive&&isNull(this.rtcMultiConnection.waitUntilRemoteStreamStartsFlowing)&&(this.rtcMultiConnection.waitUntilRemoteStreamStartsFlowing=!1);var self=this;"answer"==createType?this.setRemoteDescription(this.offerDescription,createDescription):createDescription()},serializeSdp:function(sdp,createType){if(sdp=this.processSdp(sdp),isFirefox)return sdp;if(this.session.inactive&&!this.holdMLine&&(this.hold=!0,(this.session.screen||this.session.video)&&this.session.audio?this.holdMLine="both":this.session.screen||this.session.video?this.holdMLine="video":this.session.audio&&(this.holdMLine="audio")),sdp=this.setBandwidth(sdp),"both"==this.holdMLine){if(this.hold)this.prevSDP=sdp,sdp=sdp.replace(/a=sendonly|a=recvonly|a=sendrecv/g,"a=inactive");else if(this.prevSDP&&!this.session.inactive&&(sdp=this.prevSDP,35>=chromeVersion))return sdp}else if("audio"==this.holdMLine||"video"==this.holdMLine){sdp=sdp.split("m=");var audio="",video="";sdp[1]&&0==sdp[1].indexOf("audio")&&(audio="m="+sdp[1]),sdp[2]&&0==sdp[2].indexOf("audio")&&(audio="m="+sdp[2]),sdp[1]&&0==sdp[1].indexOf("video")&&(video="m="+sdp[1]),sdp[2]&&0==sdp[2].indexOf("video")&&(video="m="+sdp[2]),"audio"==this.holdMLine&&(this.hold?(this.prevSDP=sdp[0]+audio+video,sdp=sdp[0]+audio.replace(/a=sendonly|a=recvonly|a=sendrecv/g,"a=inactive")+video):this.prevSDP&&(sdp=this.prevSDP)),"video"==this.holdMLine&&(this.hold?(this.prevSDP=sdp[0]+audio+video,sdp=sdp[0]+audio+video.replace(/a=sendonly|a=recvonly|a=sendrecv/g,"a=inactive")):this.prevSDP&&(sdp=this.prevSDP))}return!this.hold&&this.session.inactive&&(sdp="offer"==createType?sdp.replace(/a=setup:passive|a=setup:active|a=setup:holdconn/g,"a=setup:actpass"):sdp.replace(/a=setup:actpass|a=setup:passive|a=setup:holdconn/g,"a=setup:active"),sdp=sdp.replace(/a=inactive/g,"a=sendrecv")),sdp},init:function(){function returnSDP(){return self.returnedSDP?void(self.returnedSDP=!1):(self.returnedSDP=!0,void self.onSessionDescription(self.connection.localDescription,self.streaminfo))}this.setConstraints(),this.connection=new RTCPeerConnection(this.iceServers,this.optionalArgument),this.session.data&&(log("invoked: createDataChannel"),this.createDataChannel()),this.connection.onicecandidate=function(event){return event.candidate?void(self.trickleIce&&self.onicecandidate(event.candidate)):void(self.trickleIce||returnSDP())},this.connection.onaddstream=function(e){log("onaddstream",isPluginRTC?e.stream:toStr(e.stream)),self.onaddstream(e.stream,self.session)},this.connection.onremovestream=function(e){self.onremovestream(e.stream)},this.connection.onsignalingstatechange=function(){self.connection&&self.oniceconnectionstatechange({iceConnectionState:self.connection.iceConnectionState,iceGatheringState:self.connection.iceGatheringState,signalingState:self.connection.signalingState})},this.connection.oniceconnectionstatechange=function(){self.connection&&(self.oniceconnectionstatechange({iceConnectionState:self.connection.iceConnectionState,iceGatheringState:self.connection.iceGatheringState,signalingState:self.connection.signalingState}),self.trickleIce||"complete"==self.connection.iceGatheringState&&(log("iceGatheringState",self.connection.iceGatheringState),returnSDP()))};var self=this},setBandwidth:function(sdp){if(isMobileDevice||isFirefox||!this.bandwidth)return sdp;var bandwidth=this.bandwidth;return this.session.screen&&(bandwidth.screen?bandwidth.screen<300&&warn("It seems that you are using wrong bandwidth value for screen. Screen sharing is expected to fail."):warn("It seems that you are not using bandwidth for screen. Screen sharing is expected to fail.")),bandwidth.screen&&this.session.screen&&(sdp=sdp.replace(/b=AS([^\r\n]+\r\n)/g,""),sdp=sdp.replace(/a=mid:video\r\n/g,"a=mid:video\r\nb=AS:"+bandwidth.screen+"\r\n")),(bandwidth.audio||bandwidth.video||bandwidth.data)&&(sdp=sdp.replace(/b=AS([^\r\n]+\r\n)/g,"")),bandwidth.audio&&(sdp=sdp.replace(/a=mid:audio\r\n/g,"a=mid:audio\r\nb=AS:"+bandwidth.audio+"\r\n")),bandwidth.video&&(sdp=sdp.replace(/a=mid:video\r\n/g,"a=mid:video\r\nb=AS:"+(this.session.screen?bandwidth.screen:bandwidth.video)+"\r\n")),bandwidth.data&&!this.preferSCTP&&(sdp=sdp.replace(/a=mid:data\r\n/g,"a=mid:data\r\nb=AS:"+bandwidth.data+"\r\n")),sdp},setConstraints:function(){var sdpConstraints=setSdpConstraints({OfferToReceiveAudio:!!this.session.audio,OfferToReceiveVideo:!!this.session.video||!!this.session.screen});if(this.sdpConstraints.mandatory&&(sdpConstraints=setSdpConstraints(this.sdpConstraints.mandatory)),this.constraints=sdpConstraints,this.constraints&&log("sdp-constraints",toStr(this.constraints)),this.optionalArgument={optional:this.optionalArgument.optional||[],mandatory:this.optionalArgument.mandatory||{}},this.preferSCTP||this.optionalArgument.optional.push({RtpDataChannels:!0}),log("optional-argument",toStr(this.optionalArgument)),isNull(this.iceServers))this.iceServers=null;else{var iceCandidates=this.rtcMultiConnection.candidates,stun=iceCandidates.stun,turn=iceCandidates.turn,host=iceCandidates.host;isNull(iceCandidates.reflexive)||(stun=iceCandidates.reflexive),isNull(iceCandidates.relay)||(turn=iceCandidates.relay),host||stun||!turn?host||stun||turn||(this.rtcConfiguration.iceTransports="none"):this.rtcConfiguration.iceTransports="relay",this.iceServers={iceServers:this.iceServers,iceTransports:this.rtcConfiguration.iceTransports}}log("rtc-configuration",toStr(this.iceServers))},onSdpError:function(e){var message=toStr(e);message&&-1!=message.indexOf("RTP/SAVPF Expects at least 4 fields")&&(message="It seems that you are trying to interop RTP-datachannels with SCTP. It is not supported!"),error("onSdpError:",message)},onSdpSuccess:function(){log("sdp success")},onMediaError:function(err){error(toStr(err))},setRemoteDescription:function(sessionDescription,onSdpSuccess){if(!sessionDescription)throw"Remote session description should NOT be NULL.";if(this.connection){log("setting remote description",sessionDescription.type,sessionDescription.sdp);var self=this;this.connection.setRemoteDescription(new RTCSessionDescription(sessionDescription),onSdpSuccess||this.onSdpSuccess,function(error){ --1==error.search(/STATE_SENTINITIATE|STATE_INPROGRESS/gi)&&self.onSdpError(error)})}},addIceCandidate:function(candidate){function onAddIceCandidate(iceCandidate){self.connection.addIceCandidate(iceCandidate,function(){log("added:",candidate.sdpMid,candidate.candidate)},function(){error("onIceFailure",arguments,candidate.candidate)})}var self=this;isPluginRTC?RTCIceCandidate(candidate,function(iceCandidate){onAddIceCandidate(iceCandidate)}):onAddIceCandidate(new RTCIceCandidate(candidate))},createDataChannel:function(channelIdentifier){if(!this.channels||!this.channels.length){var self=this;this.channels||(this.channels=[]);var dataChannelDict={};this.dataChannelDict&&(dataChannelDict=this.dataChannelDict),isChrome&&!this.preferSCTP&&(dataChannelDict.reliable=!1),log("dataChannelDict",toStr(dataChannelDict)),("answer"==this.type||isFirefox)&&(this.connection.ondatachannel=function(event){self.setChannelEvents(event.channel)}),(isChrome&&"offer"==this.type||isFirefox)&&this.setChannelEvents(this.connection.createDataChannel(channelIdentifier||"channel",dataChannelDict))}},setChannelEvents:function(channel){var self=this;channel.binaryType="arraybuffer",this.dataChannelDict.binaryType&&(channel.binaryType=this.dataChannelDict.binaryType),channel.onmessage=function(event){self.onmessage(event.data)};var numberOfTimes=0;channel.onopen=function(){channel.push=channel.send,channel.send=function(data){if("disconnected"!=self.connection.iceConnectionState&&-1==channel.readyState.search(/closing|closed/g)&&-1!=channel.readyState.search(/connecting|open/g)){if("connecting"==channel.readyState)return numberOfTimes++,setTimeout(function(){if(!(20>numberOfTimes))throw"Number of times exceeded to wait for WebRTC data connection to be opened.";channel.send(data)},1e3);try{channel.push(data)}catch(e){if(numberOfTimes++,warn("Data transmission failed. Re-transmitting..",numberOfTimes,toStr(e)),numberOfTimes>=20)throw"Number of times exceeded to resend data packets over WebRTC data channels.";setTimeout(function(){channel.send(data)},100)}}},self.onopen(channel)},channel.onerror=function(event){self.onerror(event)},channel.onclose=function(event){self.onclose(event)},this.channels.push(channel)},addStream:function(stream){stream.streamid||isIE||(stream.streamid=getRandomString()),log("attaching stream:",stream.streamid,isPluginRTC?stream:toStr(stream)),this.connection.addStream(stream),this.sendStreamId(stream),this.getStreamInfo()},attachMediaStreams:function(){for(var streams=this.attachStreams,i=0;i=32?!0:!1,connection.chunkInterval=isFirefox||chromeVersion>=32?100:500,connection.chunkSize=isFirefox||chromeVersion>=32?13e3:1e3,connection.fakeDataChannels=!1,connection.waitUntilRemoteStreamStartsFlowing=null,connection.leaveOnPageUnload=!0,connection.getExternalIceServers=isChrome,connection.UA={isFirefox:isFirefox,isChrome:isChrome,isMobileDevice:isMobileDevice,version:isChrome?chromeVersion:firefoxVersion,isNodeWebkit:isNodeWebkit,isSafari:isSafari,isIE:isIE,isOpera:isOpera},connection.fileQueue={},connection.renegotiatedSessions={},connection.channels={},connection.extra={},connection.bandwidth={screen:300},connection.caniuse={RTCPeerConnection:DetectRTC.isWebRTCSupported,getUserMedia:!!navigator.webkitGetUserMedia||!!navigator.mozGetUserMedia,AudioContext:DetectRTC.isAudioContextSupported,ScreenSharing:DetectRTC.isScreenCapturingSupported,RtpDataChannels:DetectRTC.isRtpDataChannelsSupported,SctpDataChannels:DetectRTC.isSctpDataChannelsSupported},connection.snapshots={},connection._mediaSources={},connection.devices={},connection.language="en",connection.autoTranslateText=!1,connection.googKey="AIzaSyCgB5hmFY74WYB-EoWkhr9cAGr6TiTHrEE",connection.localStreamids=[],connection.localStreams={},connection.preRecordedMedias={},connection.attachStreams=[],connection.detachStreams=[],connection.optionalArgument={optional:[{DtlsSrtpKeyAgreement:!0},{googImprovedWifiBwe:!0},{googScreencastMinBitrate:300}],mandatory:{}},connection.dataChannelDict={},connection.dontAttachStream=!1,connection.dontCaptureUserMedia=!1,connection.preventSSLAutoAllowed=!1,connection.autoReDialOnFailure=!0,connection.isInitiator=!1,connection.DetectRTC=DetectRTC,connection.trickleIce=!0,connection.sessionDescriptions={},connection.sessionDescription=null,connection.resources={RecordRTC:"https://cdn.webrtc-experiment.com/RecordRTC.js",PreRecordedMediaStreamer:"https://cdn.webrtc-experiment.com/PreRecordedMediaStreamer.js",customGetUserMediaBar:"https://cdn.webrtc-experiment.com/navigator.customGetUserMediaBar.js",html2canvas:"https://cdn.webrtc-experiment.com/screenshot.js",hark:"https://cdn.webrtc-experiment.com/hark.js",firebase:"https://cdn.webrtc-experiment.com/firebase.js",firebaseio:"https://webrtc-experiment.firebaseIO.com/",muted:"https://cdn.webrtc-experiment.com/images/muted.png",getConnectionStats:"https://cdn.webrtc-experiment.com/getConnectionStats.js",FileBufferReader:"https://cdn.webrtc-experiment.com/FileBufferReader.js"},connection.body=document.body||document.documentElement,connection.peers={},connection.firebase="chat",connection.numberOfSessions=0,connection.numberOfConnectedUsers=0,connection.enableFileSharing=!0,connection.autoSaveToDisk=!1,connection.processSdp=function(sdp){return sdp},connection.onmessage=function(e){log("onmessage",toStr(e))},connection.onopen=function(e){log("Data connection is opened between you and",e.userid)},connection.onerror=function(e){error(onerror,toStr(e))},connection.onclose=function(e){warn("onclose",toStr(e)),connection.streams.remove({userid:e.userid})};var progressHelper={};connection.onFileStart=function(file){var div=document.createElement("div");div.title=file.name,div.innerHTML=" ",connection.body.insertBefore(div,connection.body.firstChild),progressHelper[file.uuid]={div:div,progress:div.querySelector("progress"),label:div.querySelector("label")},progressHelper[file.uuid].progress.max=file.maxChunks},connection.onFileProgress=function(chunk){var helper=progressHelper[chunk.uuid];helper&&(helper.progress.value=chunk.currentPosition||chunk.maxChunks||helper.progress.max,updateLabel(helper.progress,helper.label))},connection.onFileEnd=function(file){progressHelper[file.uuid]&&(progressHelper[file.uuid].div.innerHTML=''+file.name+""),(connection.onFileSent||connection.onFileReceived)&&(connection.onFileSent&&connection.onFileSent(file,file.uuid),connection.onFileReceived&&connection.onFileReceived(file.name,file))},connection.onstream=function(e){connection.body.insertBefore(e.mediaElement,connection.body.firstChild)},connection.onstreamended=function(e){if(log("onStreamEndedHandler:",e),!e.mediaElement)return warn("Event.mediaElement is undefined",e);if(!e.mediaElement.parentNode){if(e.mediaElement=document.getElementById(e.streamid),!e.mediaElement)return warn("Event.mediaElement is undefined",e);if(!e.mediaElement.parentNode)return warn("Event.mediElement.parentNode is null.",e)}e.mediaElement.parentNode.removeChild(e.mediaElement)},connection.onSessionClosed=function(session){session.isEjected?warn(session.userid,"ejected you."):warn("Session has been closed.",session)},connection.onmute=function(e){e.isVideo&&e.mediaElement&&(e.mediaElement.pause(),e.mediaElement.setAttribute("poster",e.snapshot||connection.resources.muted)),e.isAudio&&e.mediaElement&&(e.mediaElement.muted=!0)},connection.onunmute=function(e){e.isVideo&&e.mediaElement&&(e.mediaElement.play(),e.mediaElement.removeAttribute("poster")),e.isAudio&&e.mediaElement&&(e.mediaElement.muted=!1)},connection.onleave=function(e){log("onleave",toStr(e))},connection.token=getRandomString,connection.peers[connection.userid]={drop:function(){connection.drop()},renegotiate:function(){},addStream:function(){},hold:function(){},unhold:function(){},changeBandwidth:function(){},sharePartOfScreen:function(){}},connection._skip=["stop","mute","unmute","_private","_selectStreams","selectFirst","selectAll","remove"],connection.streams={mute:function(session){this._private(session,!0)},unmute:function(session){this._private(session,!1)},_private:function(session,enabled){function _muteOrUnMute(stream,session,isMute){session.local&&"local"!=stream.type||session.remote&&"remote"!=stream.type||(!session.isScreen||stream.isScreen)&&(!session.isAudio||stream.isAudio)&&(!session.isVideo||stream.isVideo)&&(isMute?stream.mute(session):stream.unmute(session))}if(!session||isString(session))for(var stream in this)-1==connection._skip.indexOf(stream)&&this[stream]._private(session,enabled);else for(var stream in this)-1==connection._skip.indexOf(stream)&&_muteOrUnMute(this[stream],session,enabled)},stop:function(type){function _stopStream(_stream,config){config.userid&&_stream.userid!=config.userid||config.local&&"local"!=_stream.type||config.remote&&"remote"!=_stream.type||(config.screen&&_stream.isScreen&&_stream.stop(),config.audio&&_stream.isAudio&&_stream.stop(),config.video&&_stream.isVideo&&_stream.stop(),config.audio||config.video||config.screen||_stream.stop())}var _stream;for(var stream in this)if(-1==connection._skip.indexOf(stream))if(_stream=this[stream],type)if(isString(type)){var config={};config[type]=!0,_stopStream(_stream,config)}else _stopStream(_stream,type);else _stream.stop()},remove:function(type){function _stopAndRemoveStream(_stream,config){config.userid&&_stream.userid!=config.userid||config.local&&"local"!=_stream.type||config.remote&&"remote"!=_stream.type||(config.screen&&_stream.isScreen&&endStream(_stream),config.audio&&_stream.isAudio&&endStream(_stream),config.video&&_stream.isVideo&&endStream(_stream),config.audio||config.video||config.screen||endStream(_stream))}function endStream(_stream){onStreamEndedHandler(_stream,connection),delete connection.streams[_stream.streamid]}var _stream;for(var stream in this)if(-1==connection._skip.indexOf(stream))if(_stream=this[stream],type)if(isString(type)){var config={};config[type]=!0,_stopAndRemoveStream(_stream,config)}else _stopAndRemoveStream(_stream,type);else _stopAndRemoveStream(_stream,{local:!0,remote:!0})},selectFirst:function(args){return this._selectStreams(args,!1)},selectAll:function(args){return this._selectStreams(args,!0)},_selectStreams:function(args,all){if(!args||isString(args)||isEmpty(args))throw"Invalid arguments.";isNull(args.local)&&isNull(args.remote)&&isNull(args.userid)&&(args.local=args.remote=!0),args.isAudio||args.isVideo||args.isScreen||(args.isAudio=args.isVideo=args.isScreen=!0);var selectedStreams=[];for(var stream in this)-1==connection._skip.indexOf(stream)&&(stream=this[stream])&&(args.local&&"local"==stream.type||args.remote&&"remote"==stream.type||args.userid&&stream.userid==args.userid)&&(args.isVideo&&stream.isVideo&&selectedStreams.push(stream),args.isAudio&&stream.isAudio&&selectedStreams.push(stream),args.isScreen&&stream.isScreen&&selectedStreams.push(stream));return all?selectedStreams:selectedStreams[0]}};var iceServers=[];iceServers.push({url:"stun:stun.l.google.com:19302"}),iceServers.push({url:"stun:stun.anyfirewall.com:3478"}),iceServers.push({url:"turn:turn.bistri.com:80",credential:"homeo",username:"homeo"}),iceServers.push({url:"turn:turn.anyfirewall.com:443?transport=tcp",credential:"webrtc",username:"webrtc"}),connection.iceServers=iceServers,connection.rtcConfiguration={iceServers:null,iceTransports:"all",peerIdentity:!1},connection.media={min:function(width,height){connection.mediaConstraints.video&&(connection.mediaConstraints.video.mandatory||(connection.mediaConstraints.video.mandatory={}),connection.mediaConstraints.video.mandatory.minWidth=width,connection.mediaConstraints.video.mandatory.minHeight=height)},max:function(width,height){connection.mediaConstraints.video&&(connection.mediaConstraints.video.mandatory||(connection.mediaConstraints.video.mandatory={}),connection.mediaConstraints.video.mandatory.maxWidth=width,connection.mediaConstraints.video.mandatory.maxHeight=height)}},connection._getStream=function(event){function muteOrUnmuteLocally(session,isPause,mediaElement){if(mediaElement){var lastPauseState=mediaElement.onpause,lastPlayState=mediaElement.onplay;mediaElement.onpause=mediaElement.onplay=function(){},isPause?mediaElement.pause():mediaElement.play(),mediaElement.onpause=lastPauseState,mediaElement.onplay=lastPlayState}}var resultingObject=merge({sockets:event.socket?[event.socket]:[]},event);return resultingObject.stop=function(){var self=this;if(self.sockets.forEach(function(socket){"local"==self.type&&socket.send({streamid:self.streamid,stopped:!0}),"remote"==self.type&&socket.send({promptStreamStop:!0,streamid:self.streamid})}),"remote"!=self.type){var stream=self.stream;stream&&self.rtcMultiConnection.stopMediaStream(stream)}},resultingObject.mute=function(session){this.muted=!0,this._private(session,!0)},resultingObject.unmute=function(session){this.muted=!1,this._private(session,!1)},resultingObject._private=function(session,enabled){return session&&!isNull(session.sync)&&0==session.sync?void muteOrUnmuteLocally(session,enabled,this.mediaElement):void muteOrUnmute({root:this,session:session,enabled:enabled,stream:this.stream})},resultingObject.startRecording=function(session){var self=this;return session||(session={audio:!0,video:!0}),isString(session)&&(session={audio:"audio"==session,video:"video"==session}),window.RecordRTC?(log("started recording session",session),self.videoRecorder=self.audioRecorder=null,isFirefox?session.video?self.videoRecorder=RecordRTC(self.stream,{type:"video"}):session.audio&&(self.audioRecorder=RecordRTC(self.stream,{type:"audio"})):isChrome&&(session.video&&(self.videoRecorder=RecordRTC(self.stream,{type:"video"})),session.audio&&(self.audioRecorder=RecordRTC(self.stream,{type:"audio"}))),self.audioRecorder&&self.audioRecorder.startRecording(),void(self.videoRecorder&&self.videoRecorder.startRecording())):loadScript(self.rtcMultiConnection.resources.RecordRTC,function(){self.startRecording(session)})},resultingObject.stopRecording=function(callback,session){session||(session={audio:!0,video:!0}),isString(session)&&(session={audio:"audio"==session,video:"video"==session}),log("stopped recording session",session);var self=this;session.audio&&self.audioRecorder?self.audioRecorder.stopRecording(function(){session.video&&self.videoRecorder?self.videoRecorder.stopRecording(function(){callback({audio:self.audioRecorder.getBlob(),video:self.videoRecorder.getBlob()})}):callback({audio:self.audioRecorder.getBlob()})}):session.video&&self.videoRecorder&&self.videoRecorder.stopRecording(function(){callback({video:self.videoRecorder.getBlob()})})},resultingObject.takeSnapshot=function(callback){takeSnapshot({mediaElement:this.mediaElement,userid:this.userid,connection:connection,callback:callback})},resultingObject.streamObject=resultingObject,resultingObject},connection.set=function(properties){for(var property in properties)this[property]=properties[property];return this},connection.onMediaError=function(event){error("name",event.name),error("constraintName",toStr(event.constraintName)),error("message",event.message),error("original session",event.session)},connection.takeSnapshot=function(userid,callback){takeSnapshot({userid:userid,connection:connection,callback:callback})},connection.saveToDisk=function(blob,fileName){blob.size&&blob.type?FileSaver.SaveToDisk(URL.createObjectURL(blob),fileName||blob.name||blob.type.replace("/","-")+blob.type.split("/")[1]):FileSaver.SaveToDisk(blob,fileName)},connection.selectDevices=function(device1,device2){function select(device){device&&(connection._mediaSources[device.kind]=device.id)}device1&&select(this.devices[device1]),device2&&select(this.devices[device2])},connection.getDevices=function(callback){return DetectRTC.MediaDevices.length?(DetectRTC.MediaDevices.forEach(function(device){connection.devices[device.deviceId]=device}),void(callback&&callback(connection.devices))):setTimeout(function(){connection.getDevices(callback)},1e3)},connection.getMediaDevices=connection.enumerateDevices=function(callback){if(!callback)throw"callback is mandatory.";connection.getDevices(function(){callback(connection.DetectRTC.MediaDevices)})},connection.onCustomMessage=function(message){log("Custom message",message)},connection.ondrop=function(droppedBy){log("Media connection is dropped by "+droppedBy)},connection.drop=function(config){config=config||{},connection.attachStreams=[];for(var stream in connection.streams)-1==connection._skip.indexOf(stream)&&(stream=connection.streams[stream],"local"==stream.type?(connection.detachStreams.push(stream.streamid),onStreamEndedHandler(stream,connection)):onStreamEndedHandler(stream,connection));connection.sendCustomMessage({drop:!0,dontRenegotiate:isNull(config.renegotiate)?!0:config.renegotiate})},connection.Translator={TranslateText:function(text,callback){var newScript=document.createElement("script");newScript.type="text/javascript";var sourceText=encodeURIComponent(text),randomNumber="method"+connection.token();window[randomNumber]=function(response){response.data&&response.data.translations[0]&&callback&&callback(response.data.translations[0].translatedText),response.error&&"Daily Limit Exceeded"==response.error.message&&(warn('Text translation failed. Error message: "Daily Limit Exceeded."'),callback(text))};var source="https://www.googleapis.com/language/translate/v2?key="+connection.googKey+"&target="+(connection.language||"en-US")+"&callback=window."+randomNumber+"&q="+sourceText;newScript.src=source,document.getElementsByTagName("head")[0].appendChild(newScript)}},connection.setDefaultEventsForMediaElement=function(mediaElement,streamid){mediaElement.onpause=function(){connection.streams[streamid]&&!connection.streams[streamid].muted&&connection.streams[streamid].mute()},mediaElement.onplay=function(){connection.streams[streamid]&&connection.streams[streamid].muted&&connection.streams[streamid].unmute()};var volumeChangeEventFired=!1;mediaElement.onvolumechange=function(){volumeChangeEventFired||(volumeChangeEventFired=!0,connection.streams[streamid]&&setTimeout(function(){var root=connection.streams[streamid];connection.streams[streamid].sockets.forEach(function(socket){socket.send({streamid:root.streamid,isVolumeChanged:!0,volume:mediaElement.volume})}),volumeChangeEventFired=!1},2e3))}},connection.onMediaFile=function(e){log("onMediaFile",e),connection.body.appendChild(e.mediaElement)},connection.shareMediaFile=function(file,video,streamerid){return streamerid=streamerid||connection.token(),PreRecordedMediaStreamer?PreRecordedMediaStreamer.shareMediaFile({file:file,video:video,streamerid:streamerid,connection:connection}):(loadScript(connection.resources.PreRecordedMediaStreamer,function(){connection.shareMediaFile(file,video,streamerid)}),streamerid)},connection.onpartofscreen=function(e){var image=document.createElement("img");image.src=e.screenshot,connection.body.appendChild(image)},connection.skipLogs=function(){log=error=warn=function(){}},connection.hold=function(mLine){for(var peer in connection.peers)connection.peers[peer].hold(mLine)},connection.onhold=function(track){log("onhold",track),"audio"!=track.kind&&(track.mediaElement.pause(),track.mediaElement.setAttribute("poster",track.screenshot||connection.resources.muted)),"audio"==track.kind&&(track.mediaElement.muted=!0)},connection.unhold=function(mLine){for(var peer in connection.peers)connection.peers[peer].unhold(mLine)},connection.onunhold=function(track){log("onunhold",track),"audio"!=track.kind&&(track.mediaElement.play(),track.mediaElement.removeAttribute("poster")),"audio"!=track.kind&&(track.mediaElement.muted=!1)},connection.sharePartOfScreen=function(args){function partOfScreenCapturer(){(!connection.partOfScreen||connection.partOfScreen.sharing)&&capturePartOfScreen({element:args.element,connection:connection,callback:function(screenshot){if(screenshot!=lastScreenshot){lastScreenshot=screenshot;for(var channel in connection.channels)connection.channels[channel].send({screenshot:screenshot,isPartOfScreen:!0})}!args.once&&setTimeout(partOfScreenCapturer,args.interval||200)}})}var lastScreenshot="";partOfScreenCapturer(),connection.partOfScreen=merge({sharing:!0},args)},connection.pausePartOfScreenSharing=function(){for(var peer in connection.peers)connection.peers[peer].pausePartOfScreenSharing=!0;connection.partOfScreen&&(connection.partOfScreen.sharing=!1)},connection.resumePartOfScreenSharing=function(){for(var peer in connection.peers)connection.peers[peer].pausePartOfScreenSharing=!1;connection.partOfScreen&&(connection.partOfScreen.sharing=!0)},connection.stopPartOfScreenSharing=function(){for(var peer in connection.peers)connection.peers[peer].stopPartOfScreenSharing=!0;connection.partOfScreen&&(connection.partOfScreen.sharing=!1)},connection.takeScreenshot=function(element,callback){if(!element||!callback)throw"Invalid number of arguments.";if(!window.html2canvas)return loadScript(connection.resources.html2canvas,function(){connection.takeScreenshot(element)});if(isString(element)&&(element=document.querySelector(element),element||(element=document.getElementById(element))),!element)throw"HTML Element is inaccessible!";html2canvas(element,{onrendered:function(canvas){callback(canvas.toDataURL())}})},connection.onScreenCapturingExtensionAvailable=function(){log("It seems that screen capturing extension is installed and available on your system!")},!isPluginRTC&&DetectRTC.screen.onScreenCapturingExtensionAvailable&&(DetectRTC.screen.onScreenCapturingExtensionAvailable=function(){connection.onScreenCapturingExtensionAvailable()}),connection.changeBandwidth=function(bandwidth){for(var peer in connection.peers)connection.peers[peer].changeBandwidth(bandwidth)},connection.convertToAudioStream=function(mediaStream){convertToAudioStream(mediaStream)},connection.onstatechange=function(state){log("on:state:change ("+state.userid+"):",state.name+":",state.reason||"")},connection.onfailed=function(event){event.peer.numOfRetries||(event.peer.numOfRetries=0),event.peer.numOfRetries++,error("ICE connectivity check is failed. Renegotiating peer connection."),event.peer.numOfRetries<2&&event.peer.renegotiate(),event.peer.numOfRetries>=2&&(event.peer.numOfRetries=0)},connection.onconnected=function(event){log("Peer connection has been established between you and",event.userid)},connection.ondisconnected=function(event){error("Peer connection seems has been disconnected between you and",event.userid),isEmpty(connection.channels)||connection.channels[event.userid]&&(connection.channels[event.userid].send({checkingPresence:!0}),setTimeout(function(){return connection.peers[event.userid].connected?void delete connection.peers[event.userid].connected:(connection.streams.remove({remote:!0,userid:event.userid}),void connection.remove(event.userid))},3e3))},connection.onstreamid=function(event){log("got remote streamid",event.streamid,"from",event.userid)},connection.stopMediaStream=function(mediaStream){if(!mediaStream)throw"MediaStream argument is mandatory.";if(connection.keepStreamsOpened)return void(mediaStream.onended&&mediaStream.onended());connection.localStreams[mediaStream.streamid]&&delete connection.localStreams[mediaStream.streamid],isFirefox&&mediaStream.onended&&mediaStream.onended();var checkForMediaStreamTrackStop=Boolean((mediaStream.getAudioTracks||mediaStream.getVideoTracks)&&(mediaStream.getAudioTracks()[0]&&!mediaStream.getAudioTracks()[0].stop||mediaStream.getVideoTracks()[0]&&!mediaStream.getVideoTracks()[0].stop));return!mediaStream.getAudioTracks||checkForMediaStreamTrackStop?void(mediaStream.stop&&mediaStream.stop()):(mediaStream.getAudioTracks().length&&mediaStream.getAudioTracks()[0].stop&&mediaStream.getAudioTracks().forEach(function(track){track.stop()}),void(mediaStream.getVideoTracks().length&&mediaStream.getVideoTracks()[0].stop&&mediaStream.getVideoTracks().forEach(function(track){track.stop()})))},connection.changeBandwidth=function(bandwidth){if(!bandwidth||isString(bandwidth)||isEmpty(bandwidth))throw'Invalid "bandwidth" arguments.';forEach(connection.peers,function(peer){peer.peer.bandwidth=bandwidth}),connection.renegotiate()},connection.openSignalingChannel=function(config){if(!window.Firebase)return loadScript(connection.resources.firebase,function(){connection.openSignalingChannel(config)});var channel=config.channel||connection.channel;connection.firebase&&(connection.resources.firebaseio=connection.resources.firebaseio.replace("//chat.","//"+connection.firebase+"."));var firebase=new Firebase(connection.resources.firebaseio+channel);firebase.channel=channel,firebase.on("child_added",function(data){config.onmessage(data.val())}),firebase.send=function(data){for(var prop in data)(isNull(data[prop])||"function"==typeof data[prop])&&(data[prop]=!1);this.push(data)},connection.socket||(connection.socket=firebase),firebase.onDisconnect().remove(),setTimeout(function(){config.callback(firebase)},1)},connection.Plugin=Plugin}window.RMCDefaultChannel=location.href.replace(/\/|:|#|\?|\$|\^|%|\.|`|~|!|\+|@|\[|\||]|\|*. /g,"").split("\n").join("").split("\r").join(""),window.RTCMultiConnection=function(channel){function initRTCMultiSession(onSignalingReady){return screenFrame&&loadScreenFrame(),rtcMultiSession?onSignalingReady():void(rtcMultiSession=new RTCMultiSession(connection,onSignalingReady))}function joinSession(session,joinAs){if(isString(session)&&(connection.skipOnNewSession=!0),!rtcMultiSession)return log("Signaling channel is not ready. Connecting..."),void initRTCMultiSession(function(){log("Signaling channel is connected. Joining the session again..."),setTimeout(function(){joinSession(session,joinAs)},1e3)});if(isString(session)){if(!connection.sessionDescriptions[session])return setTimeout(function(){log("Session-Descriptions not found. Rechecking.."),joinSession(session,joinAs)},1e3);session=connection.sessionDescriptions[session]}if(joinAs)return captureUserMedia(function(){session.oneway=!0,joinSession(session)},joinAs);if(!session||!session.userid||!session.sessionid){error("missing arguments",arguments);var error='Invalid data passed over "connection.join" method.';throw connection.onstatechange({userid:"browser",extra:{},name:"Unexpected data detected.",reason:error}),error}connection.dontOverrideSession||(connection.session=session.session);var extra=connection.extra||session.extra||{};session.oneway||isData(session)?rtcMultiSession.joinSession(session,extra):captureUserMedia(function(){rtcMultiSession.joinSession(session,extra)})}function captureUserMedia(callback,_session,dontCheckChromExtension){function onIFrameCallback(event){if(event.data&&event.data.chromeMediaSourceId){window.removeEventListener("message",onIFrameCallback);var sourceId=event.data.chromeMediaSourceId;if(DetectRTC.screen.sourceId=sourceId,DetectRTC.screen.chromeMediaSource="desktop","PermissionDeniedError"==sourceId){var mediaStreamError={message:"https:"==location.protocol?"User denied to share content of his screen.":SCREEN_COMMON_FAILURE,name:"PermissionDeniedError",constraintName:screen_constraints,session:session};return currentUserMediaRequest.mutex=!1,DetectRTC.screen.sourceId=null,connection.onMediaError(mediaStreamError)}captureUserMedia(callback,_session)}event.data&&event.data.chromeExtensionStatus&&(warn("Screen capturing extension status is:",event.data.chromeExtensionStatus),DetectRTC.screen.chromeMediaSource="screen",captureUserMedia(callback,_session,!0))}function _captureUserMedia(forcedConstraints,forcedCallback,isRemoveVideoTracks,dontPreventSSLAutoAllowed){if(connection.onstatechange({userid:"browser",extra:{},name:"fetching-usermedia",reason:"About to capture user-media with constraints: "+toStr(forcedConstraints)}),connection.preventSSLAutoAllowed&&!dontPreventSSLAutoAllowed&&isChrome)return navigator.customGetUserMediaBar?void navigator.customGetUserMediaBar(forcedConstraints,function(){_captureUserMedia(forcedConstraints,forcedCallback,isRemoveVideoTracks,!0)},function(){connection.onMediaError({name:"PermissionDeniedError",message:"User denied permission.",constraintName:forcedConstraints,session:session})}):void loadScript(connection.resources.customGetUserMediaBar,function(){_captureUserMedia(forcedConstraints,forcedCallback,isRemoveVideoTracks,dontPreventSSLAutoAllowed)});var mediaConfig={onsuccess:function(stream,returnBack,idInstance,streamid){onStreamSuccessCallback(stream,returnBack,idInstance,streamid,forcedConstraints,forcedCallback,isRemoveVideoTracks,screen_constraints,constraints,session); -},onerror:function(e,constraintUsed){if(isFirefox&&"PERMISSION_DENIED"==e&&(e={message:"",name:"PermissionDeniedError",constraintName:constraintUsed,session:session}),isFirefox&&constraintUsed.video&&constraintUsed.video.mozMediaSource)return mediaStreamError={message:Firefox_Screen_Capturing_Warning,name:e.name||"PermissionDeniedError",constraintName:constraintUsed,session:session},void connection.onMediaError(mediaStreamError);if(isString(e))return connection.onMediaError({message:"Unknown Error",name:e,constraintName:constraintUsed,session:session});if(e.name&&("PermissionDeniedError"==e.name||"DevicesNotFoundError"==e.name)){var mediaStreamError="Either: ";mediaStreamError+="\n Media resolutions are not permitted.",mediaStreamError+="\n Another application is using same media device.",mediaStreamError+="\n Media device is not attached or drivers not installed.",mediaStreamError+="\n You denied access once and it is still denied.",e.message&&e.message.length&&(mediaStreamError+="\n "+e.message),mediaStreamError={message:mediaStreamError,name:e.name,constraintName:constraintUsed,session:session},connection.onMediaError(mediaStreamError),isChrome&&(session.audio||session.video)&&DetectRTC.load(function(){session.audio&&!DetectRTC.hasMicrophone&&(warn("It seems that you have no microphone attached to your device/system."),session.audio=session.audio=!1,session.video||(alert("It seems that you are capturing microphone and there is no device available or access is denied. Reloading..."),location.reload())),session.video&&!DetectRTC.hasWebcam&&(warn("It seems that you have no webcam attached to your device/system."),session.video=session.video=!1,session.audio||(alert("It seems that you are capturing webcam and there is no device available or access is denied. Reloading..."),location.reload())),DetectRTC.hasMicrophone||DetectRTC.hasWebcam?connection.getUserMediaPromptedOnce||(connection.getUserMediaPromptedOnce=!0,captureUserMedia(callback,session)):(alert("It seems that either both microphone/webcam are not available or access is denied. Reloading..."),location.reload())})}if(e.name&&"ConstraintNotSatisfiedError"==e.name){var mediaStreamError="Either: ";mediaStreamError+="\n You are prompting unknown media resolutions.",mediaStreamError+="\n You are using invalid media constraints.",e.message&&e.message.length&&(mediaStreamError+="\n "+e.message),mediaStreamError={message:mediaStreamError,name:e.name,constraintName:constraintUsed,session:session},connection.onMediaError(mediaStreamError)}session.screen&&(isFirefox?error(Firefox_Screen_Capturing_Warning):"https:"!==location.protocol?isNodeWebkit||"file:"!=location.protocol&&"http:"!=location.protocol||error("You cannot use HTTP or file protocol for screen capturing. You must either use HTTPs or chrome extension page or Node-Webkit page."):error('Unable to detect actual issue. Maybe "deprecated" screen capturing flag was not set using command line or maybe you clicked "No" button or maybe chrome extension returned invalid "sourceId". Please install chrome-extension: http://bit.ly/webrtc-screen-extension')),currentUserMediaRequest.mutex=!1;var idInstance=JSON.stringify(constraintUsed);currentUserMediaRequest.streams[idInstance]&&delete currentUserMediaRequest.streams[idInstance]},mediaConstraints:connection.mediaConstraints||{}};mediaConfig.constraints=forcedConstraints||constraints,mediaConfig.connection=connection,getUserMedia(mediaConfig)}var session=_session||connection.session;if(isEmpty(session))return void(callback&&callback());if(connection.dontCaptureUserMedia)return callback();if(isData(session)||!connection.isInitiator&&session.oneway)return connection.attachStreams=[],callback();var constraints={audio:session.audio?{mandatory:{},optional:[{chromeRenderToAssociatedSink:!0}]}:!1,video:!!session.video};if(connection._mediaSources.audio&&constraints.audio.optional.push({sourceId:connection._mediaSources.audio}),connection._mediaSources.video&&(constraints.video={optional:[{sourceId:connection._mediaSources.video}]}),!session.screen&&!constraints.audio&&!constraints.video)return callback();var screen_constraints={audio:!1,video:{mandatory:{chromeMediaSource:DetectRTC.screen.chromeMediaSource,maxWidth:screen.width>1920?screen.width:1920,maxHeight:screen.height>1080?screen.height:1080},optional:[]}};if(isFirefox&&session.screen){if("https:"!==location.protocol)return error(SCREEN_COMMON_FAILURE);warn(Firefox_Screen_Capturing_Warning),screen_constraints.video=merge(screen_constraints.video.mandatory,{mozMediaSource:"window",mediaSource:"window"}),constraints.audio&&(screen_constraints.audio=!0,constraints={}),delete screen_constraints.video.chromeMediaSource}if(session.screen){if(isChrome&&DetectRTC.screen.extensionid!=ReservedExtensionID&&(useCustomChromeExtensionForScreenCapturing=!0),isChrome&&!useCustomChromeExtensionForScreenCapturing&&!dontCheckChromExtension&&!DetectRTC.screen.sourceId)return listenEventHandler("message",onIFrameCallback),screenFrame||loadScreenFrame(),void screenFrame.postMessage();if(isChrome&&useCustomChromeExtensionForScreenCapturing&&!dontCheckChromExtension&&"screen"==DetectRTC.screen.chromeMediaSource&&DetectRTC.screen.extensionid)return DetectRTC.screen.extensionid==ReservedExtensionID&&-1==document.domain.indexOf("webrtc-experiment.com")?captureUserMedia(callback,_session,!0):(log("checking if chrome extension is installed."),void DetectRTC.screen.getChromeExtensionStatus(function(status){"installed-enabled"==status&&(DetectRTC.screen.chromeMediaSource="desktop"),captureUserMedia(callback,_session,!0),log("chrome extension is installed?","desktop"==DetectRTC.screen.chromeMediaSource)}));if(isChrome&&useCustomChromeExtensionForScreenCapturing&&"desktop"==DetectRTC.screen.chromeMediaSource&&!DetectRTC.screen.sourceId)return void DetectRTC.screen.getSourceId(function(sourceId){if("PermissionDeniedError"==sourceId){var mediaStreamError={message:"User denied to share content of his screen.",name:"PermissionDeniedError",constraintName:screen_constraints,session:session};return currentUserMediaRequest.mutex=!1,DetectRTC.screen.chromeMediaSource="desktop",connection.onMediaError(mediaStreamError)}return"No-Response"==sourceId?(error("Chrome extension seems not available. Make sure that manifest.json#L16 has valid content-script matches pointing to your URL."),DetectRTC.screen.chromeMediaSource="screen",captureUserMedia(callback,_session,!0)):void captureUserMedia(callback,_session,!0)});isChrome&&"desktop"==DetectRTC.screen.chromeMediaSource&&(screen_constraints.video.mandatory.chromeMediaSourceId=DetectRTC.screen.sourceId);var _isFirstSession=isFirstSession;_captureUserMedia(screen_constraints,constraints.audio||constraints.video?function(){_isFirstSession&&(isFirstSession=!0),_captureUserMedia(constraints,callback)}:callback)}else _captureUserMedia(constraints,callback,session.audio&&!session.video)}function onStreamSuccessCallback(stream,returnBack,idInstance,streamid,forcedConstraints,forcedCallback,isRemoveVideoTracks,screen_constraints,constraints,session){streamid||(streamid=getRandomString()),connection.onstatechange({userid:"browser",extra:{},name:"usermedia-fetched",reason:"Captured user media using constraints: "+toStr(forcedConstraints)}),isRemoveVideoTracks&&(stream=convertToAudioStream(stream)),connection.localStreamids.push(streamid),stream.onended=function(){streamedObject.mediaElement&&!streamedObject.mediaElement.parentNode&&document.getElementById(stream.streamid)&&(streamedObject.mediaElement=document.getElementById(stream.streamid)),connection.attachStreams.forEach(function(_stream,index){_stream==stream&&(delete connection.attachStreams[index],connection.attachStreams=swap(connection.attachStreams))}),onStreamEndedHandler(streamedObject,connection),connection.streams[streamid]&&connection.removeStream(streamid);var _stream=connection.streams[streamid];_stream&&_stream.sockets.length&&_stream.sockets.forEach(function(socket){socket.send({streamid:_stream.streamid,stopped:!0})}),currentUserMediaRequest.mutex=!1,currentUserMediaRequest.streams[idInstance]&&delete currentUserMediaRequest.streams[idInstance],DetectRTC.screen.sourceId=null},isIE||(stream.streamid=streamid,stream.isScreen=forcedConstraints==screen_constraints,stream.isVideo=forcedConstraints==constraints&&!!constraints.video,stream.isAudio=forcedConstraints==constraints&&!!constraints.audio&&!constraints.video,stream.preMuted={audio:stream.getAudioTracks().length&&!stream.getAudioTracks()[0].enabled,video:stream.getVideoTracks().length&&!stream.getVideoTracks()[0].enabled});var mediaElement=createMediaElement(stream,session);mediaElement.muted=!0;var streamedObject={stream:stream,streamid:streamid,mediaElement:mediaElement,blobURL:mediaElement.mozSrcObject?URL.createObjectURL(stream):mediaElement.src,type:"local",userid:connection.userid,extra:connection.extra,session:session,isVideo:!!stream.isVideo,isAudio:!!stream.isAudio,isScreen:!!stream.isScreen,isInitiator:!!connection.isInitiator,rtcMultiConnection:connection};isFirstSession&&connection.attachStreams.push(stream),isFirstSession=!1,connection.streams[streamid]=connection._getStream(streamedObject),returnBack||connection.onstream(streamedObject),connection.setDefaultEventsForMediaElement&&connection.setDefaultEventsForMediaElement(mediaElement,streamid),forcedCallback&&forcedCallback(stream,streamedObject),connection.onspeaking&&initHark({stream:stream,streamedObject:streamedObject,connection:connection})}var rtcMultiSession,connection=this;connection.channel=channel||RMCDefaultChannel,connection.isAcceptNewSession=!0,connection.open=function(args){connection.isAcceptNewSession=!1,connection.isInitiator=!0;var dontTransmit=!1;return args&&(isString(args)?connection.sessionid=args:(isNull(args.transmitRoomOnce)||(connection.transmitRoomOnce=args.transmitRoomOnce),isNull(args.dontTransmit)||(dontTransmit=args.dontTransmit),isNull(args.sessionid)||(connection.sessionid=args.sessionid))),connection.socket&&connection.socket.remove&&connection.socket.remove(),connection.sessionid||(connection.sessionid=connection.channel),connection.sessionDescription={sessionid:connection.sessionid,userid:connection.userid,session:connection.session,extra:connection.extra},connection.sessionDescriptions[connection.sessionDescription.sessionid]||(connection.numberOfSessions++,connection.sessionDescriptions[connection.sessionDescription.sessionid]=connection.sessionDescription),initRTCMultiSession(function(){rtcMultiSession.captureUserMediaOnDemand=args?!!args.captureUserMediaOnDemand:!1,args&&args.onMediaCaptured&&(connection.onMediaCaptured=args.onMediaCaptured),rtcMultiSession.captureUserMediaOnDemand||captureUserMedia(function(){rtcMultiSession.initSession({sessionDescription:connection.sessionDescription,dontTransmit:dontTransmit}),invokeMediaCaptured(connection)}),rtcMultiSession.captureUserMediaOnDemand&&rtcMultiSession.initSession({sessionDescription:connection.sessionDescription,dontTransmit:dontTransmit})}),connection.sessionDescription},connection.connect=function(sessionid){return sessionid&&(connection.sessionid=sessionid),initRTCMultiSession(function(){log("Signaling channel is ready.")}),this},connection.join=joinSession,connection.send=function(data,_channel){if(connection.numberOfConnectedUsers<=0)return void setTimeout(function(){connection.send(data,_channel)},1e3);if(!data)throw"No file, data or text message to share.";if(data instanceof Array&&!isNull(data[0].size)&&!isNull(data[0].type))for(var i=0;i=0,isFirefox="undefined"!=typeof window.InstallTrigger,isSafari=Object.prototype.toString.call(window.HTMLElement).indexOf("Constructor")>0,isChrome=!!window.chrome&&!isOpera,isIE=!!document.documentMode,isPluginRTC=isSafari||isIE,isMobileDevice=!!navigator.userAgent.match(/Android|iPhone|iPad|iPod|BlackBerry|IEMobile/i),isNodeWebkit=!!(window.process&&"object"==typeof window.process&&window.process.versions&&window.process.versions["node-webkit"]);window.MediaStream=window.MediaStream||window.webkitMediaStream,window.AudioContext=window.AudioContext||window.webkitAudioContext;var chromeVersion=50,matchArray=navigator.userAgent.match(/Chrom(e|ium)\/([0-9]+)\./);isChrome&&matchArray&&matchArray[2]&&(chromeVersion=parseInt(matchArray[2],10));var firefoxVersion=50;matchArray=navigator.userAgent.match(/Firefox\/(.*)/),isFirefox&&matchArray&&matchArray[1]&&(firefoxVersion=parseInt(matchArray[1],10));var console=window.console||{log:function(){},error:function(){},warn:function(){}};if(isChrome||isFirefox||isSafari)var log=console.log.bind(console),error=console.error.bind(console),warn=console.warn.bind(console);var screenFrame,loadedScreenFrame,loadedIceFrame,onStreamEndedHandlerFiredFor={},onLeaveHandlerFiredFor={},Firefox_Screen_Capturing_Warning='Make sure that you are using Firefox Nightly and you enabled: media.getusermedia.screensharing.enabled flag from about:config page. You also need to add your domain in "media.getusermedia.screensharing.allowed_domains" flag. If you are using WinXP then also enable "media.getusermedia.screensharing.allow_on_old_platforms" flag. NEVER forget to use "only" HTTPs for screen capturing!',SCREEN_COMMON_FAILURE="HTTPs i.e. SSL-based URI is mandatory to use screen capturing.",ReservedExtensionID="ajhifddimkapgcifgcodmmfdlknahffk",useCustomChromeExtensionForScreenCapturing=-1!=document.domain.indexOf("webrtc-experiment.com");attachEventListener=function(video,type,listener,useCapture){video.addEventListener(type,listener,useCapture)};var Plugin=window.PluginRTC||{};window.onPluginRTCInitialized=function(pluginRTCObject){Plugin=pluginRTCObject,MediaStreamTrack=Plugin.MediaStreamTrack,RTCPeerConnection=Plugin.RTCPeerConnection,RTCIceCandidate=Plugin.RTCIceCandidate,RTCSessionDescription=Plugin.RTCSessionDescription,log(isPluginRTC?"Java-Applet":"ActiveX","plugin has been loaded.")},isEmpty(Plugin)||window.onPluginRTCInitialized(Plugin),isPluginRTC&&loadScript("https://cdn.webrtc-experiment.com/Plugin.EveryWhere.js");var MediaStream=window.MediaStream;"undefined"==typeof MediaStream&&"undefined"!=typeof webkitMediaStream&&(MediaStream=webkitMediaStream),"undefined"==typeof MediaStream||"stop"in MediaStream.prototype||(MediaStream.prototype.stop=function(){this.getAudioTracks().forEach(function(track){track.stop()}),this.getVideoTracks().forEach(function(track){track.stop()})});var RTCPeerConnection,defaultConstraints={mandatory:{},optional:[]},currentUserMediaRequest={streams:[],mutex:!1,queueRequests:[]},RTCSessionDescription=window.RTCSessionDescription||window.mozRTCSessionDescription,RTCIceCandidate=window.RTCIceCandidate||window.mozRTCIceCandidate;if("undefined"!=typeof mozRTCPeerConnection)RTCPeerConnection=mozRTCPeerConnection;else if("undefined"!=typeof webkitRTCPeerConnection)RTCPeerConnection=webkitRTCPeerConnection;else{if("undefined"==typeof window.RTCPeerConnection)throw"WebRTC 1.0 (RTCPeerConnection) API are NOT available in this browser.";RTCPeerConnection=window.RTCPeerConnection}var FileSaver={SaveToDisk:invokeSaveAsDialog},TextSender={send:function(config){function sendText(textMessage,text){var data={type:"text",uuid:uuid,sendingTime:sendingTime};textMessage&&(text=textMessage,data.packets=parseInt(text.length/packetSize)),text.length>packetSize?data.message=text.slice(0,packetSize):(data.message=text,data.last=!0,data.isobject=isobject),channel.send(data,_channel),textToTransfer=text.slice(data.message.length),textToTransfer.length&&setTimeout(function(){sendText(null,textToTransfer)},connection.chunkInterval||100)}var connection=config.connection;if(config.text instanceof ArrayBuffer||config.text instanceof DataView)return config.channel.send(config.text,config._channel);var channel=config.channel,_channel=config._channel,initialText=config.text,packetSize=connection.chunkSize||1e3,textToTransfer="",isobject=!1;isString(initialText)||(isobject=!0,initialText=JSON.stringify(initialText));var uuid=getRandomString(),sendingTime=(new Date).getTime();sendText(initialText)}};!function(){"use strict";function getBrowserInfo(){var nameOffset,verOffset,ix,nAgt=(navigator.appVersion,navigator.userAgent),browserName=navigator.appName,fullVersion=""+parseFloat(navigator.appVersion),majorVersion=parseInt(navigator.appVersion,10);return-1!==(verOffset=nAgt.indexOf("Opera"))?(browserName="Opera",fullVersion=nAgt.substring(verOffset+6),-1!==(verOffset=nAgt.indexOf("Version"))&&(fullVersion=nAgt.substring(verOffset+8))):-1!==(verOffset=nAgt.indexOf("MSIE"))?(browserName="IE",fullVersion=nAgt.substring(verOffset+5)):-1!==(verOffset=nAgt.indexOf("Chrome"))?(browserName="Chrome",fullVersion=nAgt.substring(verOffset+7)):-1!==(verOffset=nAgt.indexOf("Safari"))?(browserName="Safari",fullVersion=nAgt.substring(verOffset+7),-1!==(verOffset=nAgt.indexOf("Version"))&&(fullVersion=nAgt.substring(verOffset+8))):-1!==(verOffset=nAgt.indexOf("Firefox"))?(browserName="Firefox",fullVersion=nAgt.substring(verOffset+8)):(nameOffset=nAgt.lastIndexOf(" ")+1)<(verOffset=nAgt.lastIndexOf("/"))&&(browserName=nAgt.substring(nameOffset,verOffset),fullVersion=nAgt.substring(verOffset+1),browserName.toLowerCase()===browserName.toUpperCase()&&(browserName=navigator.appName)),isEdge&&(browserName="Edge",fullVersion=parseInt(navigator.userAgent.match(/Edge\/(\d+).(\d+)$/)[2],10)),-1!==(ix=fullVersion.indexOf(";"))&&(fullVersion=fullVersion.substring(0,ix)),-1!==(ix=fullVersion.indexOf(" "))&&(fullVersion=fullVersion.substring(0,ix)),majorVersion=parseInt(""+fullVersion,10),isNaN(majorVersion)&&(fullVersion=""+parseFloat(navigator.appVersion),majorVersion=parseInt(navigator.appVersion,10)),{fullVersion:fullVersion,version:majorVersion,name:browserName}}function DetectLocalIPAddress(callback){getIPs(function(ip){callback(ip.match(/^(192\.168\.|169\.254\.|10\.|172\.(1[6-9]|2\d|3[01]))/)?"Local: "+ip:"Public: "+ip)})}function getIPs(callback){function handleCandidate(candidate){var ipRegex=/([0-9]{1,3}(\.[0-9]{1,3}){3})/,ipAddress=ipRegex.exec(candidate)[1];void 0===ipDuplicates[ipAddress]&&callback(ipAddress),ipDuplicates[ipAddress]=!0}var ipDuplicates={},RTCPeerConnection=window.RTCPeerConnection||window.mozRTCPeerConnection||window.webkitRTCPeerConnection,useWebKit=!!window.webkitRTCPeerConnection;if(!RTCPeerConnection){var iframe=document.getElementById("iframe");if(!iframe)throw"NOTE: you need to have an iframe in the page right above the script tag.";var win=iframe.contentWindow;RTCPeerConnection=win.RTCPeerConnection||win.mozRTCPeerConnection||win.webkitRTCPeerConnection,useWebKit=!!win.webkitRTCPeerConnection}var servers,mediaConstraints={optional:[{RtpDataChannels:!0}]};useWebKit&&(servers={iceServers:[{urls:"stun:stun.services.mozilla.com"}]},"undefined"!=typeof DetectRTC&&DetectRTC.browser.isFirefox&&DetectRTC.browser.version<=38&&(servers[0]={url:servers[0].urls}));var pc=new RTCPeerConnection(servers,mediaConstraints);pc.onicecandidate=function(ice){ice.candidate&&handleCandidate(ice.candidate.candidate)},pc.createDataChannel(""),pc.createOffer(function(result){pc.setLocalDescription(result,function(){},function(){})},function(){}),setTimeout(function(){var lines=pc.localDescription.sdp.split("\n");lines.forEach(function(line){0===line.indexOf("a=candidate:")&&handleCandidate(line)})},1e3)}function checkDeviceSupport(callback){return!navigator.enumerateDevices&&window.MediaStreamTrack&&window.MediaStreamTrack.getSources&&(navigator.enumerateDevices=window.MediaStreamTrack.getSources.bind(window.MediaStreamTrack)),!navigator.enumerateDevices&&navigator.enumerateDevices&&(navigator.enumerateDevices=navigator.enumerateDevices.bind(navigator)),navigator.enumerateDevices?(MediaDevices=[],void navigator.enumerateDevices(function(devices){devices.forEach(function(_device){var device={};for(var d in _device)device[d]=_device[d];var skip;MediaDevices.forEach(function(d){d.id===device.id&&(skip=!0)}),skip||("audio"===device.kind&&(device.kind="audioinput"),"video"===device.kind&&(device.kind="videoinput"),device.deviceId||(device.deviceId=device.id),device.id||(device.id=device.deviceId),device.label||(device.label="Please invoke getUserMedia once.",isHTTPs||(device.label="HTTPs is required to get label of this "+device.kind+" device.")),("audioinput"===device.kind||"audio"===device.kind)&&(hasMicrophone=!0),"audiooutput"===device.kind&&(hasSpeakers=!0),("videoinput"===device.kind||"video"===device.kind)&&(hasWebcam=!0),MediaDevices.push(device))}),"undefined"!=typeof DetectRTC&&(DetectRTC.MediaDevices=MediaDevices,DetectRTC.hasMicrophone=hasMicrophone,DetectRTC.hasSpeakers=hasSpeakers,DetectRTC.hasWebcam=hasWebcam),callback&&callback()})):void(callback&&callback())}var navigator=window.navigator;navigator.mediaDevices&&navigator.mediaDevices.enumerateDevices&&(navigator.enumerateDevices=function(callback){navigator.mediaDevices.enumerateDevices().then(callback)}),"undefined"!=typeof navigator?("undefined"!=typeof navigator.webkitGetUserMedia&&(navigator.getUserMedia=navigator.webkitGetUserMedia),"undefined"!=typeof navigator.mozGetUserMedia&&(navigator.getUserMedia=navigator.mozGetUserMedia)):navigator={getUserMedia:function(){}};var isMobileDevice=!!navigator.userAgent.match(/Android|iPhone|iPad|iPod|BlackBerry|IEMobile/i),isEdge=!(-1===navigator.userAgent.indexOf("Edge")||!navigator.msSaveOrOpenBlob&&!navigator.msSaveBlob),isMobile={Android:function(){return navigator.userAgent.match(/Android/i)},BlackBerry:function(){return navigator.userAgent.match(/BlackBerry/i)},iOS:function(){return navigator.userAgent.match(/iPhone|iPad|iPod/i)},Opera:function(){return navigator.userAgent.match(/Opera Mini/i)},Windows:function(){return navigator.userAgent.match(/IEMobile/i)},any:function(){return isMobile.Android()||isMobile.BlackBerry()||isMobile.iOS()||isMobile.Opera()||isMobile.Windows()},getOsName:function(){var osName="Unknown OS";return isMobile.Android()&&(osName="Android"),isMobile.BlackBerry()&&(osName="BlackBerry"),isMobile.iOS()&&(osName="iOS"),isMobile.Opera()&&(osName="Opera Mini"),isMobile.Windows()&&(osName="Windows"),osName}},osName="Unknown OS";isMobile.any()?osName=isMobile.getOsName():(-1!==navigator.appVersion.indexOf("Win")&&(osName="Windows"),-1!==navigator.appVersion.indexOf("Mac")&&(osName="MacOS"),-1!==navigator.appVersion.indexOf("X11")&&(osName="UNIX"),-1!==navigator.appVersion.indexOf("Linux")&&(osName="Linux"));var isCanvasSupportsStreamCapturing=!1,isVideoSupportsStreamCapturing=!1;["captureStream","mozCaptureStream","webkitCaptureStream"].forEach(function(item){item in document.createElement("canvas")&&(isCanvasSupportsStreamCapturing=!0),item in document.createElement("video")&&(isVideoSupportsStreamCapturing=!0)});var MediaDevices=[],canEnumerate=!1;"undefined"!=typeof MediaStreamTrack&&"getSources"in MediaStreamTrack?canEnumerate=!0:navigator.mediaDevices&&navigator.mediaDevices.enumerateDevices&&(canEnumerate=!0);var hasMicrophone=canEnumerate,hasSpeakers=canEnumerate,hasWebcam=canEnumerate;checkDeviceSupport();var DetectRTC={};DetectRTC.browser=getBrowserInfo(),DetectRTC.browser["is"+DetectRTC.browser.name]=!0;var isHTTPs="https:"===location.protocol,isWebRTCSupported=(!!(window.process&&"object"==typeof window.process&&window.process.versions&&window.process.versions["node-webkit"]),!1);["webkitRTCPeerConnection","mozRTCPeerConnection","RTCIceGatherer"].forEach(function(item){item in window&&(isWebRTCSupported=!0)}),DetectRTC.isWebRTCSupported=isWebRTCSupported,DetectRTC.isORTCSupported="undefined"!=typeof RTCIceGatherer;var isScreenCapturingSupported=!1;DetectRTC.browser.isChrome&&DetectRTC.browser.version>=35?isScreenCapturingSupported=!0:DetectRTC.browser.isFirefox&&DetectRTC.browser.version>=34&&(isScreenCapturingSupported=!0),isHTTPs||(isScreenCapturingSupported=!1),DetectRTC.isScreenCapturingSupported=isScreenCapturingSupported;var webAudio={};["AudioContext","webkitAudioContext","mozAudioContext","msAudioContext"].forEach(function(item){webAudio.isSupported&&webAudio.isCreateMediaStreamSourceSupported||item in window&&(webAudio.isSupported=!0,"createMediaStreamSource"in window[item].prototype&&(webAudio.isCreateMediaStreamSourceSupported=!0))}),DetectRTC.isAudioContextSupported=webAudio.isSupported,DetectRTC.isCreateMediaStreamSourceSupported=webAudio.isCreateMediaStreamSourceSupported;var isRtpDataChannelsSupported=!1;DetectRTC.browser.isChrome&&DetectRTC.browser.version>31&&(isRtpDataChannelsSupported=!0),DetectRTC.isRtpDataChannelsSupported=isRtpDataChannelsSupported;var isSCTPSupportd=!1;if(DetectRTC.browser.isFirefox&&DetectRTC.browser.version>28?isSCTPSupportd=!0:DetectRTC.browser.isChrome&&DetectRTC.browser.version>25?isSCTPSupportd=!0:DetectRTC.browser.isOpera&&DetectRTC.browser.version>=11&&(isSCTPSupportd=!0),DetectRTC.isSctpDataChannelsSupported=isSCTPSupportd,DetectRTC.isMobileDevice=isMobileDevice,DetectRTC.isWebSocketsSupported="WebSocket"in window&&2===window.WebSocket.CLOSING,DetectRTC.isWebSocketsBlocked="Checking",DetectRTC.isWebSocketsSupported){var websocket=new WebSocket("wss://echo.websocket.org:443/");websocket.onopen=function(){DetectRTC.isWebSocketsBlocked=!1,DetectRTC.loadCallback&&DetectRTC.loadCallback()},websocket.onerror=function(){DetectRTC.isWebSocketsBlocked=!0,DetectRTC.loadCallback&&DetectRTC.loadCallback()}}var isGetUserMediaSupported=!1;navigator.getUserMedia?isGetUserMediaSupported=!0:navigator.mediaDevices&&navigator.mediaDevices.getUserMedia&&(isGetUserMediaSupported=!0),DetectRTC.browser.isChrome&&DetectRTC.browser.version>=47&&!isHTTPs&&(DetectRTC.isGetUserMediaSupported="Requires HTTPs"),DetectRTC.isGetUserMediaSupported=isGetUserMediaSupported,DetectRTC.osName=osName,DetectRTC.isCanvasSupportsStreamCapturing=isCanvasSupportsStreamCapturing,DetectRTC.isVideoSupportsStreamCapturing=isVideoSupportsStreamCapturing,DetectRTC.DetectLocalIPAddress=DetectLocalIPAddress,DetectRTC.load=function(callback){this.loadCallback=callback,checkDeviceSupport(callback)},DetectRTC.MediaDevices=MediaDevices,DetectRTC.hasMicrophone=hasMicrophone,DetectRTC.hasSpeakers=hasSpeakers,DetectRTC.hasWebcam=hasWebcam;var isSetSinkIdSupported=!1;"setSinkId"in document.createElement("video")&&(isSetSinkIdSupported=!0),DetectRTC.isSetSinkIdSupported=isSetSinkIdSupported;var isRTPSenderReplaceTracksSupported=!1;DetectRTC.browser.isFirefox?"getSenders"in mozRTCPeerConnection.prototype&&(isRTPSenderReplaceTracksSupported=!0):DetectRTC.browser.isChrome&&"getSenders"in webkitRTCPeerConnection.prototype&&(isRTPSenderReplaceTracksSupported=!0),DetectRTC.isRTPSenderReplaceTracksSupported=isRTPSenderReplaceTracksSupported;var isRemoteStreamProcessingSupported=!1;DetectRTC.browser.isFirefox&&DetectRTC.browser.version>38&&(isRemoteStreamProcessingSupported=!0),DetectRTC.isRemoteStreamProcessingSupported=isRemoteStreamProcessingSupported;var isApplyConstraintsSupported=!1;"undefined"!=typeof MediaStreamTrack&&"applyConstraints"in MediaStreamTrack.prototype&&(isApplyConstraintsSupported=!0),DetectRTC.isApplyConstraintsSupported=isApplyConstraintsSupported;var isMultiMonitorScreenCapturingSupported=!1;DetectRTC.browser.isFirefox&&DetectRTC.browser.version>=43&&(isMultiMonitorScreenCapturingSupported=!0), -DetectRTC.isMultiMonitorScreenCapturingSupported=isMultiMonitorScreenCapturingSupported,window.DetectRTC=DetectRTC}();var screenCallback;DetectRTC.screen={chromeMediaSource:"screen",extensionid:ReservedExtensionID,getSourceId:function(callback){function onstatus(status){return"installed-enabled"==status?(screenCallback=callback,void window.postMessage("get-sourceId","*")):(DetectRTC.screen.chromeMediaSource="screen",void callback("No-Response"))}if(!callback)throw'"callback" parameter is mandatory.';DetectRTC.screen.status?onstatus(DetectRTC.screen.status):DetectRTC.screen.getChromeExtensionStatus(onstatus)},onMessageCallback:function(data){if(isString(data)||data.sourceId){if(log("chrome message",data),"PermissionDeniedError"==data){if(DetectRTC.screen.chromeMediaSource="PermissionDeniedError",screenCallback)return screenCallback("PermissionDeniedError");throw new Error("PermissionDeniedError")}"rtcmulticonnection-extension-loaded"==data&&(DetectRTC.screen.chromeMediaSource="desktop",DetectRTC.screen.onScreenCapturingExtensionAvailable&&(DetectRTC.screen.onScreenCapturingExtensionAvailable(),DetectRTC.screen.onScreenCapturingExtensionAvailable=null)),data.sourceId&&(DetectRTC.screen.sourceId=data.sourceId,screenCallback&&screenCallback(DetectRTC.screen.sourceId))}},getChromeExtensionStatus:function(extensionid,callback){function _callback(status){DetectRTC.screen.status=status,callback(status)}if(isFirefox)return _callback("not-chrome");2!=arguments.length&&(callback=extensionid,extensionid=this.extensionid);var image=document.createElement("img");image.src="chrome-extension://"+extensionid+"/icon.png",image.onload=function(){DetectRTC.screen.chromeMediaSource="screen",window.postMessage("are-you-there","*"),setTimeout(function(){_callback("screen"==DetectRTC.screen.chromeMediaSource?"desktop"==DetectRTC.screen.chromeMediaSource?"installed-enabled":"installed-disabled":"installed-enabled")},2e3)},image.onerror=function(){_callback("not-installed")}}},window.addEventListener||(window.addEventListener=function(el,eventName,eventHandler){el.attachEvent&&el.attachEvent("on"+eventName,eventHandler)}),window.addEventListener("message",function(event){event.origin==window.location.origin&&DetectRTC.screen.onMessageCallback(event.data)})}(); \ No newline at end of file +'use strict'; + +// Last time updated: 2018-06-09 7:47:26 AM UTC + +// _________________________ +// RTCMultiConnection v3.4.4 + +// Open-Sourced: https://github.com/muaz-khan/RTCMultiConnection + +// -------------------------------------------------- +// Muaz Khan - www.MuazKhan.com +// MIT License - www.WebRTC-Experiment.com/licence +// -------------------------------------------------- + +"use strict";window.RTCMultiConnection=function(roomid,forceOptions){function SocketConnection(connection,connectCallback){function onMessageEvent(message){if(message.remoteUserId==connection.userid){if(connection.peers[message.sender]&&connection.peers[message.sender].extra!=message.message.extra&&(connection.peers[message.sender].extra=message.extra,connection.onExtraDataUpdated({userid:message.sender,extra:message.extra})),"next-possible-initiator"===message.message){if(connection.nextPossibleInitiatorIfThisUserLeave)return;return void(connection.nextPossibleInitiatorIfThisUserLeave=message.sender)}if(message.message.streamSyncNeeded&&connection.peers[message.sender]){var stream=connection.streamEvents[message.message.streamid];if(!stream||!stream.stream)return;var action=message.message.action;if("ended"===action||"inactive"===action||"stream-removed"===action)return connection.peersBackup[stream.userid]&&(stream.extra=connection.peersBackup[stream.userid].extra),void connection.onstreamended(stream);var type="both"!=message.message.type?message.message.type:null;return void("function"==typeof stream.stream[action]&&stream.stream[action](type))}if("connectWithAllParticipants"===message.message)return connection.broadcasters.indexOf(message.sender)===-1&&connection.broadcasters.push(message.sender),void mPeer.onNegotiationNeeded({allParticipants:connection.getAllParticipants(message.sender)},message.sender);if("removeFromBroadcastersList"===message.message)return void(connection.broadcasters.indexOf(message.sender)!==-1&&(delete connection.broadcasters[connection.broadcasters.indexOf(message.sender)],connection.broadcasters=removeNullEntries(connection.broadcasters)));if("dropPeerConnection"===message.message)return void connection.deletePeer(message.sender);if(message.message.allParticipants)return message.message.allParticipants.indexOf(message.sender)===-1&&message.message.allParticipants.push(message.sender),void message.message.allParticipants.forEach(function(participant){mPeer[connection.peers[participant]?"renegotiatePeer":"createNewPeer"](participant,{localPeerSdpConstraints:{OfferToReceiveAudio:connection.sdpConstraints.mandatory.OfferToReceiveAudio,OfferToReceiveVideo:connection.sdpConstraints.mandatory.OfferToReceiveVideo},remotePeerSdpConstraints:{OfferToReceiveAudio:connection.session.oneway?!!connection.session.audio:connection.sdpConstraints.mandatory.OfferToReceiveAudio,OfferToReceiveVideo:connection.session.oneway?!!connection.session.video||!!connection.session.screen:connection.sdpConstraints.mandatory.OfferToReceiveVideo},isOneWay:!!connection.session.oneway||"one-way"===connection.direction,isDataOnly:isData(connection.session)})});if(message.message.newParticipant){if(message.message.newParticipant==connection.userid)return;if(connection.peers[message.message.newParticipant])return;return void mPeer.createNewPeer(message.message.newParticipant,message.message.userPreferences||{localPeerSdpConstraints:{OfferToReceiveAudio:connection.sdpConstraints.mandatory.OfferToReceiveAudio,OfferToReceiveVideo:connection.sdpConstraints.mandatory.OfferToReceiveVideo},remotePeerSdpConstraints:{OfferToReceiveAudio:connection.session.oneway?!!connection.session.audio:connection.sdpConstraints.mandatory.OfferToReceiveAudio,OfferToReceiveVideo:connection.session.oneway?!!connection.session.video||!!connection.session.screen:connection.sdpConstraints.mandatory.OfferToReceiveVideo},isOneWay:!!connection.session.oneway||"one-way"===connection.direction,isDataOnly:isData(connection.session)})}if(message.message.readyForOffer||message.message.addMeAsBroadcaster){if(connection.attachStreams.length&&(connection.waitingForLocalMedia=!1),connection.waitingForLocalMedia)return void setTimeout(function(){onMessageEvent(message)},1e3);connection.addNewBroadcaster(message.sender)}if(message.message.newParticipationRequest&&message.sender!==connection.userid){connection.peers[message.sender]&&connection.deletePeer(message.sender);var userPreferences={extra:message.extra||{},localPeerSdpConstraints:message.message.remotePeerSdpConstraints||{OfferToReceiveAudio:connection.sdpConstraints.mandatory.OfferToReceiveAudio,OfferToReceiveVideo:connection.sdpConstraints.mandatory.OfferToReceiveVideo},remotePeerSdpConstraints:message.message.localPeerSdpConstraints||{OfferToReceiveAudio:connection.session.oneway?!!connection.session.audio:connection.sdpConstraints.mandatory.OfferToReceiveAudio,OfferToReceiveVideo:connection.session.oneway?!!connection.session.video||!!connection.session.screen:connection.sdpConstraints.mandatory.OfferToReceiveVideo},isOneWay:"undefined"!=typeof message.message.isOneWay?message.message.isOneWay:!!connection.session.oneway||"one-way"===connection.direction,isDataOnly:"undefined"!=typeof message.message.isDataOnly?message.message.isDataOnly:isData(connection.session),dontGetRemoteStream:"undefined"!=typeof message.message.isOneWay?message.message.isOneWay:!!connection.session.oneway||"one-way"===connection.direction,dontAttachLocalStream:!!message.message.dontGetRemoteStream,connectionDescription:message,successCallback:function(){("undefined"!=typeof message.message.isOneWay?message.message.isOneWay:!!connection.session.oneway||"one-way"===connection.direction)&&connection.addNewBroadcaster(message.sender,userPreferences),(connection.session.oneway||"one-way"===connection.direction||isData(connection.session))&&connection.addNewBroadcaster(message.sender,userPreferences)}};return void connection.onNewParticipant(message.sender,userPreferences)}return message.message.shiftedModerationControl?void connection.onShiftedModerationControl(message.sender,message.message.broadcasters):(message.message.changedUUID&&connection.peers[message.message.oldUUID]&&(connection.peers[message.message.newUUID]=connection.peers[message.message.oldUUID],delete connection.peers[message.message.oldUUID]),message.message.userLeft?(mPeer.onUserLeft(message.sender),void(message.message.autoCloseEntireSession&&connection.leave())):void mPeer.addNegotiatedMessage(message.message,message.sender))}}var parameters="";parameters+="?userid="+connection.userid,parameters+="&sessionid="+connection.sessionid,parameters+="&msgEvent="+connection.socketMessageEvent,parameters+="&socketCustomEvent="+connection.socketCustomEvent,parameters+="&autoCloseEntireSession="+!!connection.autoCloseEntireSession,connection.session.broadcast===!0&&(parameters+="&oneToMany=true"),parameters+="&maxParticipantsAllowed="+connection.maxParticipantsAllowed,connection.enableScalableBroadcast&&(parameters+="&enableScalableBroadcast=true",parameters+="&maxRelayLimitPerUser="+(connection.maxRelayLimitPerUser||2)),connection.socketCustomParameters&&(parameters+=connection.socketCustomParameters);try{io.sockets={}}catch(e){}if(connection.socketURL||(connection.socketURL="/"),"/"!=connection.socketURL.substr(connection.socketURL.length-1,1))throw'"socketURL" MUST end with a slash.';connection.enableLogs&&("/"==connection.socketURL?console.info("socket.io is connected at: ",location.origin+"/"):console.info("socket.io is connected at: ",connection.socketURL));try{connection.socket=io(connection.socketURL+parameters)}catch(e){connection.socket=io.connect(connection.socketURL+parameters,connection.socketOptions)}connection.socket.isIO=!0;var mPeer=connection.multiPeersHandler;connection.socket.on("extra-data-updated",function(remoteUserId,extra){connection.peers[remoteUserId]&&(connection.peers[remoteUserId].extra=extra,connection.onExtraDataUpdated({userid:remoteUserId,extra:extra}),connection.peersBackup[remoteUserId]||(connection.peersBackup[remoteUserId]={userid:remoteUserId,extra:{}}),connection.peersBackup[remoteUserId].extra=extra)}),connection.socket.on(connection.socketMessageEvent,onMessageEvent),connection.socket.on("user-left",function(userid){onUserLeft(userid),connection.onUserStatusChanged({userid:userid,status:"offline",extra:connection.peers[userid]?connection.peers[userid].extra||{}:{}});var eventObject={userid:userid,extra:{}};connection.peersBackup[eventObject.userid]&&(eventObject.extra=connection.peersBackup[eventObject.userid].extra),connection.onleave(eventObject),connection.nextPossibleInitiatorIfThisUserLeave===userid&&(connection.nextPossibleInitiatorIfThisUserLeave=null,connection.open(connection.sessionid))});var alreadyConnected=!1;connection.socket.resetProps=function(){alreadyConnected=!1},connection.socket.on("connect",function(){alreadyConnected||(alreadyConnected=!0,connection.enableLogs&&console.info("socket.io connection is opened."),setTimeout(function(){connection.socket.emit("extra-data-updated",connection.extra),connectCallback&&connectCallback(connection.socket)},1e3))}),connection.socket.on("disconnect",function(){connection.enableLogs&&console.warn("socket.io connection is closed")}),connection.socket.on("join-with-password",function(remoteUserId){connection.onJoinWithPassword(remoteUserId)}),connection.socket.on("invalid-password",function(remoteUserId,oldPassword){connection.onInvalidPassword(remoteUserId,oldPassword)}),connection.socket.on("password-max-tries-over",function(remoteUserId){connection.onPasswordMaxTriesOver(remoteUserId)}),connection.socket.on("user-disconnected",function(remoteUserId){remoteUserId!==connection.userid&&(connection.onUserStatusChanged({userid:remoteUserId,status:"offline",extra:connection.peers[remoteUserId]?connection.peers[remoteUserId].extra||{}:{}}),connection.deletePeer(remoteUserId))}),connection.socket.on("user-connected",function(userid){userid!==connection.userid&&connection.onUserStatusChanged({userid:userid,status:"online",extra:connection.peers[userid]?connection.peers[userid].extra||{}:{}})}),connection.socket.on("closed-entire-session",function(sessionid,extra){connection.leave(),connection.onEntireSessionClosed({sessionid:sessionid,userid:sessionid,extra:extra})}),connection.socket.on("userid-already-taken",function(useridAlreadyTaken,yourNewUserId){connection.isInitiator=!1,connection.userid=yourNewUserId,connection.onUserIdAlreadyTaken(useridAlreadyTaken,yourNewUserId)}),connection.socket.on("logs",function(log){connection.enableLogs&&console.debug("server-logs",log)}),connection.socket.on("number-of-broadcast-viewers-updated",function(data){connection.onNumberOfBroadcastViewersUpdated(data)}),connection.socket.on("room-full",function(roomid){connection.onRoomFull(roomid)}),connection.socket.on("become-next-modrator",function(sessionid){sessionid==connection.sessionid&&setTimeout(function(){connection.open(sessionid),connection.socket.emit("shift-moderator-control-on-disconnect")},1e3)})}function MultiPeers(connection){function initFileBufferReader(){connection.fbr=new FileBufferReader,connection.fbr.onProgress=function(chunk){connection.onFileProgress(chunk)},connection.fbr.onBegin=function(file){connection.onFileStart(file)},connection.fbr.onEnd=function(file){connection.onFileEnd(file)}}var self=this,skipPeers=["getAllParticipants","getLength","selectFirst","streams","send","forEach"];connection.peersBackup={},connection.peers={getLength:function(){var numberOfPeers=0;for(var peer in this)skipPeers.indexOf(peer)==-1&&numberOfPeers++;return numberOfPeers},selectFirst:function(){var firstPeer;for(var peer in this)skipPeers.indexOf(peer)==-1&&(firstPeer=this[peer]);return firstPeer},getAllParticipants:function(sender){var allPeers=[];for(var peer in this)skipPeers.indexOf(peer)==-1&&peer!=sender&&allPeers.push(peer);return allPeers},forEach:function(callbcak){this.getAllParticipants().forEach(function(participant){callbcak(connection.peers[participant])})},send:function(data,remoteUserId){var that=this;if(!isNull(data.size)&&!isNull(data.type))return void self.shareFile(data,remoteUserId);if(!("text"===data.type||data instanceof ArrayBuffer||data instanceof DataView))return void TextSender.send({text:data,channel:this,connection:connection,remoteUserId:remoteUserId});if("text"===data.type&&(data=JSON.stringify(data)),remoteUserId){var remoteUser=connection.peers[remoteUserId];if(remoteUser)return remoteUser.channels.length?void remoteUser.channels.forEach(function(channel){channel.send(data)}):(connection.peers[remoteUserId].createDataChannel(),connection.renegotiate(remoteUserId),void setTimeout(function(){that.send(data,remoteUserId)},3e3))}this.getAllParticipants().forEach(function(participant){return that[participant].channels.length?void that[participant].channels.forEach(function(channel){channel.send(data)}):(connection.peers[participant].createDataChannel(),connection.renegotiate(participant),void setTimeout(function(){that[participant].channels.forEach(function(channel){channel.send(data)})},3e3))})}},this.uuid=connection.userid,this.getLocalConfig=function(remoteSdp,remoteUserId,userPreferences){return userPreferences||(userPreferences={}),{streamsToShare:userPreferences.streamsToShare||{},rtcMultiConnection:connection,connectionDescription:userPreferences.connectionDescription,userid:remoteUserId,localPeerSdpConstraints:userPreferences.localPeerSdpConstraints,remotePeerSdpConstraints:userPreferences.remotePeerSdpConstraints,dontGetRemoteStream:!!userPreferences.dontGetRemoteStream,dontAttachLocalStream:!!userPreferences.dontAttachLocalStream,renegotiatingPeer:!!userPreferences.renegotiatingPeer,peerRef:userPreferences.peerRef,channels:userPreferences.channels||[],onLocalSdp:function(localSdp){self.onNegotiationNeeded(localSdp,remoteUserId)},onLocalCandidate:function(localCandidate){localCandidate=OnIceCandidateHandler.processCandidates(connection,localCandidate),localCandidate&&self.onNegotiationNeeded(localCandidate,remoteUserId)},remoteSdp:remoteSdp,onDataChannelMessage:function(message){if(!connection.fbr&&connection.enableFileSharing&&initFileBufferReader(),"string"==typeof message||!connection.enableFileSharing)return void self.onDataChannelMessage(message,remoteUserId);var that=this;return message instanceof ArrayBuffer||message instanceof DataView?void connection.fbr.convertToObject(message,function(object){that.onDataChannelMessage(object)}):message.readyForNextChunk?void connection.fbr.getNextChunk(message,function(nextChunk,isLastChunk){connection.peers[remoteUserId].channels.forEach(function(channel){channel.send(nextChunk)})},remoteUserId):message.chunkMissing?void connection.fbr.chunkMissing(message):void connection.fbr.addChunk(message,function(promptNextChunk){connection.peers[remoteUserId].peer.channel.send(promptNextChunk)})},onDataChannelError:function(error){self.onDataChannelError(error,remoteUserId)},onDataChannelOpened:function(channel){self.onDataChannelOpened(channel,remoteUserId)},onDataChannelClosed:function(event){self.onDataChannelClosed(event,remoteUserId)},onRemoteStream:function(stream){connection.peers[remoteUserId]&&connection.peers[remoteUserId].streams.push(stream),self.onGettingRemoteMedia(stream,remoteUserId)},onRemoteStreamRemoved:function(stream){self.onRemovingRemoteMedia(stream,remoteUserId)},onPeerStateChanged:function(states){self.onPeerStateChanged(states),"new"===states.iceConnectionState&&self.onNegotiationStarted(remoteUserId,states),"connected"===states.iceConnectionState&&self.onNegotiationCompleted(remoteUserId,states),states.iceConnectionState.search(/closed|failed/gi)!==-1&&(self.onUserLeft(remoteUserId),self.disconnectWith(remoteUserId))}}},this.createNewPeer=function(remoteUserId,userPreferences){if(!(connection.maxParticipantsAllowed<=connection.getAllParticipants().length)){if(userPreferences=userPreferences||{},connection.isInitiator&&connection.session.audio&&"two-way"===connection.session.audio&&!userPreferences.streamsToShare&&(userPreferences.isOneWay=!1,userPreferences.isDataOnly=!1,userPreferences.session=connection.session),!userPreferences.isOneWay&&!userPreferences.isDataOnly)return userPreferences.isOneWay=!0,void this.onNegotiationNeeded({enableMedia:!0,userPreferences:userPreferences},remoteUserId);userPreferences=connection.setUserPreferences(userPreferences,remoteUserId);var localConfig=this.getLocalConfig(null,remoteUserId,userPreferences);connection.peers[remoteUserId]=new PeerInitiator(localConfig),this.checkIfNextPossibleInitiator(remoteUserId)}},this.createAnsweringPeer=function(remoteSdp,remoteUserId,userPreferences){userPreferences=connection.setUserPreferences(userPreferences||{},remoteUserId);var localConfig=this.getLocalConfig(remoteSdp,remoteUserId,userPreferences);connection.peers[remoteUserId]=new PeerInitiator(localConfig),this.checkIfNextPossibleInitiator(remoteUserId)},this.renegotiatePeer=function(remoteUserId,userPreferences,remoteSdp){if(!connection.peers[remoteUserId])return void(connection.enableLogs&&console.error("Peer ("+remoteUserId+") does not exist. Renegotiation skipped."));userPreferences||(userPreferences={}),userPreferences.renegotiatingPeer=!0,userPreferences.peerRef=connection.peers[remoteUserId].peer,userPreferences.channels=connection.peers[remoteUserId].channels;var localConfig=this.getLocalConfig(remoteSdp,remoteUserId,userPreferences);connection.peers[remoteUserId]=new PeerInitiator(localConfig),this.checkIfNextPossibleInitiator(remoteUserId)},this.replaceTrack=function(track,remoteUserId,isVideoTrack){if(!connection.peers[remoteUserId])throw"This peer ("+remoteUserId+") does not exist.";var peer=connection.peers[remoteUserId].peer;return peer.getSenders&&"function"==typeof peer.getSenders&&peer.getSenders().length?void peer.getSenders().forEach(function(rtpSender){isVideoTrack&&rtpSender.track instanceof VideoStreamTrack&&(connection.peers[remoteUserId].peer.lastVideoTrack=rtpSender.track,rtpSender.replaceTrack(track)),!isVideoTrack&&rtpSender.track instanceof AudioStreamTrack&&(connection.peers[remoteUserId].peer.lastAudioTrack=rtpSender.track,rtpSender.replaceTrack(track))}):(console.warn("RTPSender.replaceTrack is NOT supported."),void this.renegotiatePeer(remoteUserId))},this.onNegotiationNeeded=function(message,remoteUserId){},this.addNegotiatedMessage=function(message,remoteUserId){if(message.type&&message.sdp)return"answer"==message.type&&connection.peers[remoteUserId]&&connection.peers[remoteUserId].addRemoteSdp(message),"offer"==message.type&&(message.renegotiatingPeer?this.renegotiatePeer(remoteUserId,null,message):this.createAnsweringPeer(message,remoteUserId)),void(connection.enableLogs&&console.log("Remote peer's sdp:",message.sdp));if(message.candidate)return connection.peers[remoteUserId]&&connection.peers[remoteUserId].addRemoteCandidate(message),void(connection.enableLogs&&console.log("Remote peer's candidate pairs:",message.candidate));if(message.enableMedia){connection.session=message.userPreferences.session||connection.session,connection.session.oneway&&connection.attachStreams.length&&(connection.attachStreams=[]),message.userPreferences.isDataOnly&&connection.attachStreams.length&&(connection.attachStreams.length=[]);var streamsToShare={};connection.attachStreams.forEach(function(stream){streamsToShare[stream.streamid]={isAudio:!!stream.isAudio,isVideo:!!stream.isVideo,isScreen:!!stream.isScreen}}),message.userPreferences.streamsToShare=streamsToShare,self.onNegotiationNeeded({readyForOffer:!0,userPreferences:message.userPreferences},remoteUserId)}message.readyForOffer&&connection.onReadyForOffer(remoteUserId,message.userPreferences)},this.connectNewParticipantWithAllBroadcasters=function(newParticipantId,userPreferences,broadcastersList){if(!connection.socket.isIO&&(broadcastersList=(broadcastersList||"").split("|-,-|"),broadcastersList.length)){var firstBroadcaster,remainingBroadcasters=[];broadcastersList.forEach(function(list){list=(list||"").replace(/ /g,""),list.length&&(firstBroadcaster?remainingBroadcasters.push(list):firstBroadcaster=list)}),firstBroadcaster&&(self.onNegotiationNeeded({newParticipant:newParticipantId,userPreferences:userPreferences||!1},firstBroadcaster),remainingBroadcasters.length&&setTimeout(function(){self.connectNewParticipantWithAllBroadcasters(newParticipantId,userPreferences,remainingBroadcasters.join("|-,-|"))},3e3))}},this.onGettingRemoteMedia=function(stream,remoteUserId){},this.onRemovingRemoteMedia=function(stream,remoteUserId){},this.onGettingLocalMedia=function(localStream){},this.onLocalMediaError=function(error,constraints){connection.onMediaError(error,constraints)},this.shareFile=function(file,remoteUserId){if(!connection.enableFileSharing)throw'"connection.enableFileSharing" is false.';initFileBufferReader(),connection.fbr.readAsArrayBuffer(file,function(uuid){var arrayOfUsers=connection.getAllParticipants();remoteUserId&&(arrayOfUsers=[remoteUserId]),arrayOfUsers.forEach(function(participant){connection.fbr.getNextChunk(uuid,function(nextChunk){connection.peers[participant].channels.forEach(function(channel){channel.send(nextChunk)})},participant)})},{userid:connection.userid,chunkSize:"Firefox"===DetectRTC.browser.name?15e3:connection.chunkSize||0})};var textReceiver=new TextReceiver(connection);this.onDataChannelMessage=function(message,remoteUserId){textReceiver.receive(JSON.parse(message),remoteUserId,connection.peers[remoteUserId]?connection.peers[remoteUserId].extra:{})},this.onDataChannelClosed=function(event,remoteUserId){event.userid=remoteUserId,event.extra=connection.peers[remoteUserId]?connection.peers[remoteUserId].extra:{},connection.onclose(event)},this.onDataChannelError=function(error,remoteUserId){error.userid=remoteUserId,event.extra=connection.peers[remoteUserId]?connection.peers[remoteUserId].extra:{},connection.onerror(error)},this.onDataChannelOpened=function(channel,remoteUserId){return connection.peers[remoteUserId].channels.length?void(connection.peers[remoteUserId].channels=[channel]):(connection.peers[remoteUserId].channels.push(channel),void connection.onopen({userid:remoteUserId,extra:connection.peers[remoteUserId]?connection.peers[remoteUserId].extra:{},channel:channel}))},this.onPeerStateChanged=function(state){connection.onPeerStateChanged(state)},this.onNegotiationStarted=function(remoteUserId,states){},this.onNegotiationCompleted=function(remoteUserId,states){},this.getRemoteStreams=function(remoteUserId){return remoteUserId=remoteUserId||connection.peers.getAllParticipants()[0],connection.peers[remoteUserId]?connection.peers[remoteUserId].streams:[]},this.checkIfNextPossibleInitiator=function(remoteUserId){connection.sessionid!==remoteUserId&&(connection.autoCloseEntireSession||connection.isInitiator&&connection.getAllParticipants().length>1||connection.socket.emit(connection.socketMessageEvent,{remoteUserId:remoteUserId,message:"next-possible-initiator",sender:connection.userid}))}}function fireEvent(obj,eventName,args){if("undefined"!=typeof CustomEvent){var eventDetail={arguments:args,__exposedProps__:args},event=new CustomEvent(eventName,eventDetail);obj.dispatchEvent(event)}}function setHarkEvents(connection,streamEvent){if(streamEvent.stream&&streamEvent.stream.getAudioTracks&&streamEvent.stream.getAudioTracks().length){if(!connection||!streamEvent)throw"Both arguments are required.";if(connection.onspeaking&&connection.onsilence){if("undefined"==typeof hark)throw"hark.js not found.";hark(streamEvent.stream,{onspeaking:function(){connection.onspeaking(streamEvent)},onsilence:function(){connection.onsilence(streamEvent)},onvolumechange:function(volume,threshold){connection.onvolumechange&&connection.onvolumechange(merge({volume:volume,threshold:threshold},streamEvent))}})}}}function setMuteHandlers(connection,streamEvent){streamEvent.stream&&streamEvent.stream&&streamEvent.stream.addEventListener&&(streamEvent.stream.addEventListener("mute",function(event){event=connection.streamEvents[streamEvent.streamid],event.session={audio:"audio"===event.muteType,video:"video"===event.muteType},connection.onmute(event)},!1),streamEvent.stream.addEventListener("unmute",function(event){event=connection.streamEvents[streamEvent.streamid],event.session={audio:"audio"===event.unmuteType,video:"video"===event.unmuteType},connection.onunmute(event)},!1))}function getRandomString(){if(window.crypto&&window.crypto.getRandomValues&&navigator.userAgent.indexOf("Safari")===-1){for(var a=window.crypto.getRandomValues(new Uint32Array(3)),token="",i=0,l=a.length;i=60,"Firefox"===DetectRTC.browser.name&&DetectRTC.browser.version>=54&&(oldBrowser=!1),oldBrowser&&(sdpConstraints={mandatory:sdpConstraints,optional:[{VoiceActivityDetection:!1}]}),sdpConstraints}function PeerInitiator(config){function getLocalStreams(){if("undefined"!=typeof window.InstallTrigger&&"getSenders"in peer&&"function"==typeof peer.getSenders){var streamObject2=new MediaStream;return peer.getSenders().forEach(function(sender){try{streamObject2.addTrack(sender.track)}catch(e){}}),streamObject2}return peer.getLocalStreams()}function oldAddRemoteSdp(remoteSdp,cb){cb=cb||function(){},"Safari"!==DetectRTC.browser.name&&(remoteSdp.sdp=connection.processSdp(remoteSdp.sdp)),peer.setRemoteDescription(new RTCSessionDescription(remoteSdp),cb,function(error){connection.enableLogs&&console.error("setRemoteDescription failed","\n",error,"\n",remoteSdp.sdp),cb()})}function setChannelEvents(channel){channel.binaryType="arraybuffer",channel.onmessage=function(event){config.onDataChannelMessage(event.data)},channel.onopen=function(){config.onDataChannelOpened(channel)},channel.onerror=function(error){config.onDataChannelError(error)},channel.onclose=function(event){config.onDataChannelClosed(event)},channel.internalSend=channel.send,channel.send=function(data){"open"===channel.readyState&&channel.internalSend(data)},peer.channel=channel}function oldCreateOfferOrAnswer(_method){peer[_method](function(localSdp){"Safari"!==DetectRTC.browser.name&&(localSdp.sdp=connection.processSdp(localSdp.sdp)),peer.setLocalDescription(localSdp,function(){connection.trickleIce&&(config.onLocalSdp({type:localSdp.type,sdp:localSdp.sdp,remotePeerSdpConstraints:config.remotePeerSdpConstraints||!1,renegotiatingPeer:!!config.renegotiatingPeer||!1,connectionDescription:self.connectionDescription,dontGetRemoteStream:!!config.dontGetRemoteStream,extra:connection?connection.extra:{},streamsToShare:streamsToShare}),connection.onSettingLocalDescription(self))},function(error){connection.enableLogs&&console.error("setLocalDescription-error",error)})},function(error){connection.enableLogs&&console.error("sdp-"+_method+"-error",error)},defaults.sdpConstraints)}function createOfferOrAnswer(_method){return window.enableAdapter?void peer[_method](defaults.sdpConstraints).then(function(localSdp){"Safari"!==DetectRTC.browser.name&&(localSdp.sdp=connection.processSdp(localSdp.sdp)),peer.setLocalDescription(localSdp).then(function(){connection.trickleIce&&(config.onLocalSdp({type:localSdp.type,sdp:localSdp.sdp,remotePeerSdpConstraints:config.remotePeerSdpConstraints||!1,renegotiatingPeer:!!config.renegotiatingPeer||!1,connectionDescription:self.connectionDescription,dontGetRemoteStream:!!config.dontGetRemoteStream,extra:connection?connection.extra:{},streamsToShare:streamsToShare}),connection.onSettingLocalDescription(self))},function(error){connection.enableLogs&&console.error("setLocalDescription error",error)})},function(error){connection.enableLogs&&console.error("sdp-error",error)}):oldCreateOfferOrAnswer(_method)}if("undefined"!=typeof window.RTCPeerConnection?RTCPeerConnection=window.RTCPeerConnection:"undefined"!=typeof mozRTCPeerConnection?RTCPeerConnection=mozRTCPeerConnection:"undefined"!=typeof webkitRTCPeerConnection&&(RTCPeerConnection=webkitRTCPeerConnection),RTCSessionDescription=window.RTCSessionDescription||window.mozRTCSessionDescription,RTCIceCandidate=window.RTCIceCandidate||window.mozRTCIceCandidate,MediaStreamTrack=window.MediaStreamTrack,!RTCPeerConnection)throw"WebRTC 1.0 (RTCPeerConnection) API are NOT available in this browser.";var connection=config.rtcMultiConnection;this.extra=config.remoteSdp?config.remoteSdp.extra:connection.extra, +this.userid=config.userid,this.streams=[],this.channels=config.channels||[],this.connectionDescription=config.connectionDescription,this.addStream=function(session){connection.addStream(session,self.userid)},this.removeStream=function(streamid){connection.removeStream(streamid,self.userid)};var self=this;config.remoteSdp&&(this.connectionDescription=config.remoteSdp.connectionDescription);var allRemoteStreams={};defaults.sdpConstraints=setSdpConstraints({OfferToReceiveAudio:!0,OfferToReceiveVideo:!0});var peer,renegotiatingPeer=!!config.renegotiatingPeer;config.remoteSdp&&(renegotiatingPeer=!!config.remoteSdp.renegotiatingPeer);var localStreams=[];if(connection.attachStreams.forEach(function(stream){stream&&localStreams.push(stream)}),renegotiatingPeer)peer=config.peerRef;else{var iceTransports="all";(connection.candidates.turn||connection.candidates.relay)&&(connection.candidates.stun||connection.candidates.reflexive||connection.candidates.host||(iceTransports="relay"));try{var params={};"Chrome"!==DetectRTC.browser.name&&(params.iceServers=connection.iceServers),"Chrome"===DetectRTC.browser.name&&(params={iceServers:connection.iceServers,iceTransportPolicy:connection.iceTransportPolicy||iceTransports}),connection.iceServers.length||(params=null,connection.optionalArgument=null),peer=new RTCPeerConnection(params,connection.optionalArgument)}catch(e){try{var params={iceServers:connection.iceServers};peer=new RTCPeerConnection(params)}catch(e){peer=new RTCPeerConnection}}}peer.onicecandidate=function(event){if(event.candidate)connection.trickleIce&&config.onLocalCandidate({candidate:event.candidate.candidate,sdpMid:event.candidate.sdpMid,sdpMLineIndex:event.candidate.sdpMLineIndex});else if(!connection.trickleIce){var localSdp=peer.localDescription;config.onLocalSdp({type:localSdp.type,sdp:localSdp.sdp,remotePeerSdpConstraints:config.remotePeerSdpConstraints||!1,renegotiatingPeer:!!config.renegotiatingPeer||!1,connectionDescription:self.connectionDescription,dontGetRemoteStream:!!config.dontGetRemoteStream,extra:connection?connection.extra:{},streamsToShare:streamsToShare})}},localStreams.forEach(function(localStream){if(!(config.remoteSdp&&config.remoteSdp.remotePeerSdpConstraints&&config.remoteSdp.remotePeerSdpConstraints.dontGetRemoteStream)&&!config.dontAttachLocalStream&&(localStream=connection.beforeAddingStream(localStream,self)))if(getLocalStreams().forEach&&getLocalStreams().forEach(function(stream){localStream&&stream.id==localStream.id&&(localStream=null)}),localStream&&"function"==typeof peer.addTrack)localStream.getTracks().forEach(function(track){try{peer.addTrack(track,localStream)}catch(e){}});else if(localStream&&"function"==typeof peer.addStream)peer.addStream(localStream);else try{peer.addStream(localStream)}catch(e){localStream&&localStream.getTracks().forEach(function(track){try{peer.addTrack(track,localStream)}catch(e){}})}}),peer.oniceconnectionstatechange=peer.onsignalingstatechange=function(){var extra=self.extra;connection.peers[self.userid]&&(extra=connection.peers[self.userid].extra||extra),peer&&(config.onPeerStateChanged({iceConnectionState:peer.iceConnectionState,iceGatheringState:peer.iceGatheringState,signalingState:peer.signalingState,extra:extra,userid:self.userid}),peer&&peer.iceConnectionState&&peer.iceConnectionState.search(/closed|failed/gi)!==-1&&self.streams instanceof Array&&self.streams.forEach(function(stream){var streamEvent=connection.streamEvents[stream.id]||{streamid:stream.id,stream:stream,type:"remote"};connection.onstreamended(streamEvent)}))};var sdpConstraints={OfferToReceiveAudio:!!localStreams.length,OfferToReceiveVideo:!!localStreams.length};config.localPeerSdpConstraints&&(sdpConstraints=config.localPeerSdpConstraints),defaults.sdpConstraints=setSdpConstraints(sdpConstraints);var dontDuplicate={},incomingStreamEvent="track";window.enableAdapter||(incomingStreamEvent="addstream"),peer.addEventListener(incomingStreamEvent,function(event){if(event&&("track"===incomingStreamEvent&&(event.stream=event.streams[event.streams.length-1]),!dontDuplicate[event.stream.id]||"Safari"===DetectRTC.browser.name)){dontDuplicate[event.stream.id]=event.stream.id;var streamsToShare={};config.remoteSdp&&config.remoteSdp.streamsToShare?streamsToShare=config.remoteSdp.streamsToShare:config.streamsToShare&&(streamsToShare=config.streamsToShare);var streamToShare=streamsToShare[event.stream.id];streamToShare?(event.stream.isAudio=streamToShare.isAudio,event.stream.isVideo=streamToShare.isVideo,event.stream.isScreen=streamToShare.isScreen):(event.stream.isVideo=!!event.stream.getVideoTracks().length,event.stream.isAudio=!event.stream.isVideo,event.stream.isScreen=!1),event.stream.streamid=event.stream.id,"Firefox"!=DetectRTC.browser.name&&event.stream.stop||(event.stream.stop=function(){var streamEndedEvent="ended";"oninactive"in event.stream&&(streamEndedEvent="inactive"),fireEvent(event.stream,streamEndedEvent)}),allRemoteStreams[event.stream.id]=event.stream,config.onRemoteStream(event.stream)}},!1),peer.onremovestream=function(event){event.stream.streamid=event.stream.id,allRemoteStreams[event.stream.id]&&delete allRemoteStreams[event.stream.id],config.onRemoteStreamRemoved(event.stream)},this.addRemoteCandidate=function(remoteCandidate){peer.addIceCandidate(new RTCIceCandidate(remoteCandidate))},this.addRemoteSdp=function(remoteSdp,cb){return cb=cb||function(){},window.enableAdapter?("Safari"!==DetectRTC.browser.name&&(remoteSdp.sdp=connection.processSdp(remoteSdp.sdp)),void peer.setRemoteDescription(new RTCSessionDescription(remoteSdp)).then(cb,function(error){connection.enableLogs&&console.error("setRemoteDescription failed","\n",error,"\n",remoteSdp.sdp),cb()})):oldAddRemoteSdp(remoteSdp,cb)};var isOfferer=!0;config.remoteSdp&&(isOfferer=!1),this.createDataChannel=function(){var channel=peer.createDataChannel("sctp",{});setChannelEvents(channel)},connection.session.data!==!0||renegotiatingPeer||(isOfferer?this.createDataChannel():peer.ondatachannel=function(event){var channel=event.channel;setChannelEvents(channel)}),config.remoteSdp&&(config.remoteSdp.remotePeerSdpConstraints&&(sdpConstraints=config.remoteSdp.remotePeerSdpConstraints),defaults.sdpConstraints=setSdpConstraints(sdpConstraints),this.addRemoteSdp(config.remoteSdp,function(){createOfferOrAnswer("createAnswer")})),"two-way"!=connection.session.audio&&"two-way"!=connection.session.video&&"two-way"!=connection.session.screen||(defaults.sdpConstraints=setSdpConstraints({OfferToReceiveAudio:"two-way"==connection.session.audio||config.remoteSdp&&config.remoteSdp.remotePeerSdpConstraints&&config.remoteSdp.remotePeerSdpConstraints.OfferToReceiveAudio,OfferToReceiveVideo:"two-way"==connection.session.video||"two-way"==connection.session.screen||config.remoteSdp&&config.remoteSdp.remotePeerSdpConstraints&&config.remoteSdp.remotePeerSdpConstraints.OfferToReceiveAudio}));var streamsToShare={};getLocalStreams().forEach&&getLocalStreams().forEach(function(stream){streamsToShare[stream.streamid]={isAudio:!!stream.isAudio,isVideo:!!stream.isVideo,isScreen:!!stream.isScreen}}),isOfferer&&createOfferOrAnswer("createOffer"),peer.nativeClose=peer.close,peer.close=function(){if(peer){try{peer.nativeClose!==peer.close&&peer.nativeClose()}catch(e){}peer=null,self.peer=null}},this.peer=peer}function setStreamType(constraints,stream){constraints.mandatory&&constraints.mandatory.chromeMediaSource?stream.isScreen=!0:constraints.mozMediaSource||constraints.mediaSource?stream.isScreen=!0:constraints.video?stream.isVideo=!0:constraints.audio&&(stream.isAudio=!0)}function getUserMediaHandler(options){function streaming(stream,returnBack){setStreamType(options.localMediaConstraints,stream);var streamEndedEvent="ended";"oninactive"in stream&&(streamEndedEvent="inactive"),stream.addEventListener(streamEndedEvent,function(){delete currentUserMediaRequest.streams[idInstance],currentUserMediaRequest.mutex=!1,currentUserMediaRequest.queueRequests.indexOf(options)&&(delete currentUserMediaRequest.queueRequests[currentUserMediaRequest.queueRequests.indexOf(options)],currentUserMediaRequest.queueRequests=removeNullEntries(currentUserMediaRequest.queueRequests))},!1),currentUserMediaRequest.streams[idInstance]={stream:stream},currentUserMediaRequest.mutex=!1,currentUserMediaRequest.queueRequests.length&&getUserMediaHandler(currentUserMediaRequest.queueRequests.shift()),options.onGettingLocalMedia(stream,returnBack)}if(currentUserMediaRequest.mutex===!0)return void currentUserMediaRequest.queueRequests.push(options);currentUserMediaRequest.mutex=!0;var idInstance=JSON.stringify(options.localMediaConstraints);if(currentUserMediaRequest.streams[idInstance])streaming(currentUserMediaRequest.streams[idInstance].stream,!0);else{var isBlackBerry=!!/BB10|BlackBerry/i.test(navigator.userAgent||"");if(isBlackBerry||"undefined"==typeof navigator.mediaDevices||"function"!=typeof navigator.mediaDevices.getUserMedia)return navigator.getUserMedia=navigator.getUserMedia||navigator.webkitGetUserMedia||navigator.mozGetUserMedia,void navigator.getUserMedia(options.localMediaConstraints,function(stream){stream.streamid=stream.streamid||stream.id||getRandomString(),stream.idInstance=idInstance,streaming(stream)},function(error){options.onLocalMediaError(error,options.localMediaConstraints)});if("undefined"==typeof navigator.mediaDevices){navigator.getUserMedia=navigator.getUserMedia||navigator.webkitGetUserMedia||navigator.mozGetUserMedia;var getUserMediaStream,getUserMediaError,getUserMediaSuccess=function(){},getUserMediaFailure=function(){};navigator.mediaDevices={getUserMedia:function(hints){return navigator.getUserMedia(hints,function(getUserMediaSuccess){getUserMediaSuccess(stream),getUserMediaStream=stream},function(error){getUserMediaFailure(error),getUserMediaError=error}),{then:function(successCB){return getUserMediaStream?void successCB(getUserMediaStream):(getUserMediaSuccess=successCB,{then:function(failureCB){return getUserMediaError?void failureCB(getUserMediaError):void(getUserMediaFailure=failureCB)}})}}}}}navigator.mediaDevices.getUserMedia(options.localMediaConstraints).then(function(stream){stream.streamid=stream.streamid||stream.id||getRandomString(),stream.idInstance=idInstance,streaming(stream)})["catch"](function(error){options.onLocalMediaError(error,options.localMediaConstraints)})}}function onMessageCallback(data){if("PermissionDeniedError"==data){if(chromeMediaSource="PermissionDeniedError",screenCallback)return screenCallback("PermissionDeniedError");throw new Error("PermissionDeniedError: User rejected to share his screen.")}"rtcmulticonnection-extension-loaded"==data&&(chromeMediaSource="desktop"),data.sourceId&&screenCallback&&(sourceId=data.sourceId,screenCallback(sourceId))}function isChromeExtensionAvailable(callback){if(callback){if("Firefox"===DetectRTC.browser.name)return isFirefoxExtensionAvailable(callback);if(window.IsAndroidChrome)return chromeMediaSource="screen",void callback(!0);if("desktop"==chromeMediaSource)return void callback(!0);window.postMessage("are-you-there","*"),setTimeout(function(){callback("screen"==chromeMediaSource?!1:!0)},2e3)}}function isFirefoxExtensionAvailable(callback){function messageCallback(event){var addonMessage=event.data;addonMessage&&"undefined"!=typeof addonMessage.isScreenCapturingEnabled&&(isFirefoxAddonResponded=!0,callback(addonMessage.isScreenCapturingEnabled===!0?!0:!1),window.removeEventListener("message",messageCallback,!1))}if(callback){if("Firefox"!==DetectRTC.browser.name)return isChromeExtensionAvailable(callback);var isFirefoxAddonResponded=!1;window.addEventListener("message",messageCallback,!1),window.postMessage({checkIfScreenCapturingEnabled:!0,domains:[document.domain]},"*"),setTimeout(function(){isFirefoxAddonResponded||callback(!0)},2e3)}}function getSourceId(callback,audioPlusTab){if(!callback)throw'"callback" parameter is mandatory.';return sourceId?(callback(sourceId),void(sourceId=null)):(screenCallback=callback,audioPlusTab?void window.postMessage("audio-plus-tab","*"):void window.postMessage("get-sourceId","*"))}function getChromeExtensionStatus(extensionid,callback){if(window.IsAndroidChrome)return chromeMediaSource="screen",void callback("installed-enabled");if(2!=arguments.length&&(callback=extensionid,extensionid=window.RMCExtensionID||"ajhifddimkapgcifgcodmmfdlknahffk"),"Firefox"===DetectRTC.browser.name)return callback("not-chrome");var image=document.createElement("img");image.src="chrome-extension://"+extensionid+"/icon.png",image.onload=function(){sourceId=null,chromeMediaSource="screen",window.postMessage("are-you-there","*"),setTimeout(function(){callback("screen"==chromeMediaSource?"installed-disabled":"installed-enabled")},2e3)},image.onerror=function(){callback("not-installed")}}function getAspectRatio(w,h){function gcd(a,b){return 0==b?a:gcd(b,a%b)}var r=gcd(w,h);return w/r/(h/r)}function getScreenConstraints(callback,audioPlusTab){var firefoxScreenConstraints={mozMediaSource:"window",mediaSource:"window"};return"Firefox"===DetectRTC.browser.name?callback(null,firefoxScreenConstraints):(sourceId=null,void isChromeExtensionAvailable(function(isAvailable){var screen_constraints={mandatory:{chromeMediaSource:chromeMediaSource,maxWidth:screen.width,maxHeight:screen.height,minWidth:screen.width,minHeight:screen.height,minAspectRatio:getAspectRatio(screen.width,screen.height),maxAspectRatio:getAspectRatio(screen.width,screen.height),minFrameRate:64,maxFrameRate:128},optional:[]};return window.IsAndroidChrome?void callback(null,screen_constraints):"desktop"!=chromeMediaSource||sourceId?("desktop"==chromeMediaSource&&(screen_constraints.mandatory.chromeMediaSourceId=sourceId),sourceId=null,chromeMediaSource="screen",screenCallback=null,void callback(null,screen_constraints)):void getSourceId(function(){screen_constraints.mandatory.chromeMediaSourceId=sourceId,callback("PermissionDeniedError"==sourceId?sourceId:null,screen_constraints),sourceId=null},audioPlusTab)}))}function TextReceiver(connection){function receive(data,userid,extra){var uuid=data.uuid;if(content[uuid]||(content[uuid]=[]),content[uuid].push(data.message),data.last){var message=content[uuid].join("");data.isobject&&(message=JSON.parse(message));var receivingTime=(new Date).getTime(),latency=receivingTime-data.sendingTime,e={data:message,userid:userid,extra:extra,latency:latency};connection.autoTranslateText?(e.original=e.data,connection.Translator.TranslateText(e.data,function(translatedText){e.data=translatedText,connection.onmessage(e)})):connection.onmessage(e),delete content[uuid]}}var content={};return{receive:receive}}!function(){function getBrowserInfo(){var nameOffset,verOffset,ix,nAgt=(navigator.appVersion,navigator.userAgent),browserName=navigator.appName,fullVersion=""+parseFloat(navigator.appVersion),majorVersion=parseInt(navigator.appVersion,10);if(isOpera){browserName="Opera";try{fullVersion=navigator.userAgent.split("OPR/")[1].split(" ")[0],majorVersion=fullVersion.split(".")[0]}catch(e){fullVersion="0.0.0.0",majorVersion=0}}else isIE?(verOffset=nAgt.indexOf("rv:"),verOffset>0?fullVersion=nAgt.substring(verOffset+3):(verOffset=nAgt.indexOf("MSIE"),fullVersion=nAgt.substring(verOffset+5)),browserName="IE"):isChrome?(verOffset=nAgt.indexOf("Chrome"),browserName="Chrome",fullVersion=nAgt.substring(verOffset+7)):isSafari?(verOffset=nAgt.indexOf("Safari"),browserName="Safari",fullVersion=nAgt.substring(verOffset+7),(verOffset=nAgt.indexOf("Version"))!==-1&&(fullVersion=nAgt.substring(verOffset+8)),navigator.userAgent.indexOf("Version/")!==-1&&(fullVersion=navigator.userAgent.split("Version/")[1].split(" ")[0])):isFirefox?(verOffset=nAgt.indexOf("Firefox"),browserName="Firefox",fullVersion=nAgt.substring(verOffset+8)):(nameOffset=nAgt.lastIndexOf(" ")+1)<(verOffset=nAgt.lastIndexOf("/"))&&(browserName=nAgt.substring(nameOffset,verOffset),fullVersion=nAgt.substring(verOffset+1),browserName.toLowerCase()===browserName.toUpperCase()&&(browserName=navigator.appName));return isEdge&&(browserName="Edge",fullVersion=navigator.userAgent.split("Edge/")[1]),(ix=fullVersion.search(/[; \)]/))!==-1&&(fullVersion=fullVersion.substring(0,ix)),majorVersion=parseInt(""+fullVersion,10),isNaN(majorVersion)&&(fullVersion=""+parseFloat(navigator.appVersion),majorVersion=parseInt(navigator.appVersion,10)),{fullVersion:fullVersion,version:majorVersion,name:browserName,isPrivateBrowsing:!1}}function retry(isDone,next){var currentTrial=0,maxRetry=50,isTimeout=!1,id=window.setInterval(function(){isDone()&&(window.clearInterval(id),next(isTimeout)),currentTrial++>maxRetry&&(window.clearInterval(id),isTimeout=!0,next(isTimeout))},10)}function isIE10OrLater(userAgent){var ua=userAgent.toLowerCase();if(0===ua.indexOf("msie")&&0===ua.indexOf("trident"))return!1;var match=/(?:msie|rv:)\s?([\d\.]+)/.exec(ua);return!!(match&&parseInt(match[1],10)>=10)}function detectPrivateMode(callback){var isPrivate;try{if(window.webkitRequestFileSystem)window.webkitRequestFileSystem(window.TEMPORARY,1,function(){isPrivate=!1},function(e){isPrivate=!0});else if(window.indexedDB&&/Firefox/.test(window.navigator.userAgent)){var db;try{db=window.indexedDB.open("test"),db.onerror=function(){return!0}}catch(e){isPrivate=!0}"undefined"==typeof isPrivate&&retry(function(){return"done"===db.readyState},function(isTimeout){isTimeout||(isPrivate=!db.result)})}else if(isIE10OrLater(window.navigator.userAgent)){isPrivate=!1;try{window.indexedDB||(isPrivate=!0)}catch(e){isPrivate=!0}}else if(window.localStorage&&/Safari/.test(window.navigator.userAgent)){try{window.localStorage.setItem("test",1)}catch(e){isPrivate=!0}"undefined"==typeof isPrivate&&(isPrivate=!1,window.localStorage.removeItem("test"))}}catch(e){isPrivate=!1}retry(function(){return"undefined"!=typeof isPrivate},function(isTimeout){callback(isPrivate)})}function detectDesktopOS(){for(var cs,unknown="-",nVer=navigator.appVersion,nAgt=navigator.userAgent,os=unknown,clientStrings=[{s:"Windows 10",r:/(Windows 10.0|Windows NT 10.0)/},{s:"Windows 8.1",r:/(Windows 8.1|Windows NT 6.3)/},{s:"Windows 8",r:/(Windows 8|Windows NT 6.2)/},{s:"Windows 7",r:/(Windows 7|Windows NT 6.1)/},{s:"Windows Vista",r:/Windows NT 6.0/},{s:"Windows Server 2003",r:/Windows NT 5.2/},{s:"Windows XP",r:/(Windows NT 5.1|Windows XP)/},{s:"Windows 2000",r:/(Windows NT 5.0|Windows 2000)/},{s:"Windows ME",r:/(Win 9x 4.90|Windows ME)/},{s:"Windows 98",r:/(Windows 98|Win98)/},{s:"Windows 95",r:/(Windows 95|Win95|Windows_95)/},{s:"Windows NT 4.0",r:/(Windows NT 4.0|WinNT4.0|WinNT|Windows NT)/},{s:"Windows CE",r:/Windows CE/},{s:"Windows 3.11",r:/Win16/},{s:"Android",r:/Android/},{s:"Open BSD",r:/OpenBSD/},{s:"Sun OS",r:/SunOS/},{s:"Linux",r:/(Linux|X11)/},{s:"iOS",r:/(iPhone|iPad|iPod)/},{s:"Mac OS X",r:/Mac OS X/},{s:"Mac OS",r:/(MacPPC|MacIntel|Mac_PowerPC|Macintosh)/},{s:"QNX",r:/QNX/},{s:"UNIX",r:/UNIX/},{s:"BeOS",r:/BeOS/},{s:"OS/2",r:/OS\/2/},{s:"Search Bot",r:/(nuhk|Googlebot|Yammybot|Openbot|Slurp|MSNBot|Ask Jeeves\/Teoma|ia_archiver)/}],i=0;cs=clientStrings[i];i++)if(cs.r.test(nAgt)){os=cs.s;break}var osVersion=unknown;switch(/Windows/.test(os)&&(/Windows (.*)/.test(os)&&(osVersion=/Windows (.*)/.exec(os)[1]),os="Windows"),os){case"Mac OS X":/Mac OS X (10[\.\_\d]+)/.test(nAgt)&&(osVersion=/Mac OS X (10[\.\_\d]+)/.exec(nAgt)[1]);break;case"Android":/Android ([\.\_\d]+)/.test(nAgt)&&(osVersion=/Android ([\.\_\d]+)/.exec(nAgt)[1]);break;case"iOS":/OS (\d+)_(\d+)_?(\d+)?/.test(nAgt)&&(osVersion=/OS (\d+)_(\d+)_?(\d+)?/.exec(nVer),osVersion=osVersion[1]+"."+osVersion[2]+"."+(0|osVersion[3]))}return{osName:os,osVersion:osVersion}}function getAndroidVersion(ua){ua=(ua||navigator.userAgent).toLowerCase();var match=ua.match(/android\s([0-9\.]*)/);return!!match&&match[1]}function DetectLocalIPAddress(callback,stream){DetectRTC.isWebRTCSupported&&getIPs(function(ip){callback(ip.match(/^(192\.168\.|169\.254\.|10\.|172\.(1[6-9]|2\d|3[01]))/)?"Local: "+ip:"Public: "+ip)},stream)}function getIPs(callback,stream){function handleCandidate(candidate){var ipRegex=/([0-9]{1,3}(\.[0-9]{1,3}){3})/,match=ipRegex.exec(candidate);if(match){var ipAddress=match[1];void 0===ipDuplicates[ipAddress]&&callback(ipAddress),ipDuplicates[ipAddress]=!0}}function afterCreateOffer(){var lines=pc.localDescription.sdp.split("\n");lines.forEach(function(line){0===line.indexOf("a=candidate:")&&handleCandidate(line)})}if("undefined"!=typeof document&&"function"==typeof document.getElementById){var ipDuplicates={},RTCPeerConnection=window.RTCPeerConnection||window.mozRTCPeerConnection||window.webkitRTCPeerConnection;if(!RTCPeerConnection){var iframe=document.getElementById("iframe");if(!iframe)return;var win=iframe.contentWindow;RTCPeerConnection=win.RTCPeerConnection||win.mozRTCPeerConnection||win.webkitRTCPeerConnection}if(RTCPeerConnection){var peerConfig=null;"Chrome"===DetectRTC.browser&&DetectRTC.browser.version<58&&(peerConfig={optional:[{RtpDataChannels:!0}]});var servers={iceServers:[{urls:"stun:stun.l.google.com:19302"}]},pc=new RTCPeerConnection(servers,peerConfig);if(stream&&(pc.addStream?pc.addStream(stream):pc.addTrack&&stream.getTracks()[0]&&pc.addTrack(stream.getTracks()[0],stream)),pc.onicecandidate=function(ice){ice.candidate&&handleCandidate(ice.candidate.candidate)},!stream)try{pc.createDataChannel("sctp",{})}catch(e){}DetectRTC.isPromisesSupported?pc.createOffer().then(function(result){pc.setLocalDescription(result).then(afterCreateOffer)}):pc.createOffer(function(result){pc.setLocalDescription(result,afterCreateOffer,function(){})},function(){})}}}function checkDeviceSupport(callback){if(!canEnumerate)return void(callback&&callback());if(!navigator.enumerateDevices&&window.MediaStreamTrack&&window.MediaStreamTrack.getSources&&(navigator.enumerateDevices=window.MediaStreamTrack.getSources.bind(window.MediaStreamTrack)),!navigator.enumerateDevices&&navigator.enumerateDevices&&(navigator.enumerateDevices=navigator.enumerateDevices.bind(navigator)),!navigator.enumerateDevices)return void(callback&&callback());MediaDevices=[],audioInputDevices=[],audioOutputDevices=[],videoInputDevices=[],hasMicrophone=!1,hasSpeakers=!1,hasWebcam=!1,isWebsiteHasMicrophonePermissions=!1,isWebsiteHasWebcamPermissions=!1;var alreadyUsedDevices={};navigator.enumerateDevices(function(devices){devices.forEach(function(_device){var device={};for(var d in _device)try{"function"!=typeof _device[d]&&(device[d]=_device[d])}catch(e){}alreadyUsedDevices[device.deviceId+device.label+device.kind]||("audio"===device.kind&&(device.kind="audioinput"),"video"===device.kind&&(device.kind="videoinput"),device.deviceId||(device.deviceId=device.id),device.id||(device.id=device.deviceId),device.label?("videoinput"!==device.kind||isWebsiteHasWebcamPermissions||(isWebsiteHasWebcamPermissions=!0),"audioinput"!==device.kind||isWebsiteHasMicrophonePermissions||(isWebsiteHasMicrophonePermissions=!0)):(device.isCustomLabel=!0,"videoinput"===device.kind?device.label="Camera "+(videoInputDevices.length+1):"audioinput"===device.kind?device.label="Microphone "+(audioInputDevices.length+1):"audiooutput"===device.kind?device.label="Speaker "+(audioOutputDevices.length+1):device.label="Please invoke getUserMedia once.","undefined"!=typeof DetectRTC&&DetectRTC.browser.isChrome&&DetectRTC.browser.version>=46&&!/^(https:|chrome-extension:)$/g.test(location.protocol||"")&&"undefined"!=typeof document&&"string"==typeof document.domain&&document.domain.search&&document.domain.search(/localhost|127.0./g)===-1&&(device.label="HTTPs is required to get label of this "+device.kind+" device.")),"audioinput"===device.kind&&(hasMicrophone=!0,audioInputDevices.indexOf(device)===-1&&audioInputDevices.push(device)),"audiooutput"===device.kind&&(hasSpeakers=!0,audioOutputDevices.indexOf(device)===-1&&audioOutputDevices.push(device)),"videoinput"===device.kind&&(hasWebcam=!0,videoInputDevices.indexOf(device)===-1&&videoInputDevices.push(device)),MediaDevices.push(device),alreadyUsedDevices[device.deviceId+device.label+device.kind]=device)}),"undefined"!=typeof DetectRTC&&(DetectRTC.MediaDevices=MediaDevices,DetectRTC.hasMicrophone=hasMicrophone,DetectRTC.hasSpeakers=hasSpeakers,DetectRTC.hasWebcam=hasWebcam,DetectRTC.isWebsiteHasWebcamPermissions=isWebsiteHasWebcamPermissions,DetectRTC.isWebsiteHasMicrophonePermissions=isWebsiteHasMicrophonePermissions,DetectRTC.audioInputDevices=audioInputDevices,DetectRTC.audioOutputDevices=audioOutputDevices,DetectRTC.videoInputDevices=videoInputDevices),callback&&callback()})}function getAspectRatio(w,h){function gcd(a,b){return 0==b?a:gcd(b,a%b)}var r=gcd(w,h);return w/r/(h/r)}var browserFakeUserAgent="Fake/5.0 (FakeOS) AppleWebKit/123 (KHTML, like Gecko) Fake/12.3.4567.89 Fake/123.45",isNodejs="object"==typeof process&&"object"==typeof process.versions&&process.versions.node&&!process.browser;if(isNodejs){var version=process.versions.node.toString().replace("v","");browserFakeUserAgent="Nodejs/"+version+" (NodeOS) AppleWebKit/"+version+" (KHTML, like Gecko) Nodejs/"+version+" Nodejs/"+version}!function(that){"undefined"==typeof window&&("undefined"==typeof window&&"undefined"!=typeof global?(global.navigator={userAgent:browserFakeUserAgent,getUserMedia:function(){}},that.window=global):"undefined"==typeof window,"undefined"==typeof location&&(that.location={protocol:"file:",href:"",hash:""}),"undefined"==typeof screen&&(that.screen={width:0,height:0}))}("undefined"!=typeof global?global:window);var navigator=window.navigator;"undefined"!=typeof navigator?("undefined"!=typeof navigator.webkitGetUserMedia&&(navigator.getUserMedia=navigator.webkitGetUserMedia),"undefined"!=typeof navigator.mozGetUserMedia&&(navigator.getUserMedia=navigator.mozGetUserMedia)):navigator={getUserMedia:function(){},userAgent:browserFakeUserAgent};var isMobileDevice=!!/Android|webOS|iPhone|iPad|iPod|BB10|BlackBerry|IEMobile|Opera Mini|Mobile|mobile/i.test(navigator.userAgent||""),isEdge=!(navigator.userAgent.indexOf("Edge")===-1||!navigator.msSaveOrOpenBlob&&!navigator.msSaveBlob),isOpera=!!window.opera||navigator.userAgent.indexOf(" OPR/")>=0,isFirefox="undefined"!=typeof window.InstallTrigger,isSafari=/^((?!chrome|android).)*safari/i.test(navigator.userAgent),isChrome=!!window.chrome&&!isOpera,isIE="undefined"!=typeof document&&!!document.documentMode&&!isEdge,isMobile={Android:function(){return navigator.userAgent.match(/Android/i)},BlackBerry:function(){return navigator.userAgent.match(/BlackBerry|BB10/i)},iOS:function(){return navigator.userAgent.match(/iPhone|iPad|iPod/i)},Opera:function(){return navigator.userAgent.match(/Opera Mini/i)},Windows:function(){return navigator.userAgent.match(/IEMobile/i)},any:function(){return isMobile.Android()||isMobile.BlackBerry()||isMobile.iOS()||isMobile.Opera()||isMobile.Windows()},getOsName:function(){var osName="Unknown OS";return isMobile.Android()&&(osName="Android"),isMobile.BlackBerry()&&(osName="BlackBerry"),isMobile.iOS()&&(osName="iOS"),isMobile.Opera()&&(osName="Opera Mini"),isMobile.Windows()&&(osName="Windows"),osName}},osName="Unknown OS",osVersion="Unknown OS Version",osInfo=detectDesktopOS();osInfo&&osInfo.osName&&"-"!=osInfo.osName?(osName=osInfo.osName,osVersion=osInfo.osVersion):isMobile.any()&&(osName=isMobile.getOsName(),"Android"==osName&&(osVersion=getAndroidVersion()));var isNodejs="object"==typeof process&&"object"==typeof process.versions&&process.versions.node;"Unknown OS"===osName&&isNodejs&&(osName="Nodejs",osVersion=process.versions.node.toString().replace("v",""));var isCanvasSupportsStreamCapturing=!1,isVideoSupportsStreamCapturing=!1;["captureStream","mozCaptureStream","webkitCaptureStream"].forEach(function(item){"undefined"!=typeof document&&"function"==typeof document.createElement&&(!isCanvasSupportsStreamCapturing&&item in document.createElement("canvas")&&(isCanvasSupportsStreamCapturing=!0),!isVideoSupportsStreamCapturing&&item in document.createElement("video")&&(isVideoSupportsStreamCapturing=!0))});var MediaDevices=[],audioInputDevices=[],audioOutputDevices=[],videoInputDevices=[];navigator.mediaDevices&&navigator.mediaDevices.enumerateDevices&&(navigator.enumerateDevices=function(callback){var enumerateDevices=navigator.mediaDevices.enumerateDevices();enumerateDevices&&enumerateDevices.then?navigator.mediaDevices.enumerateDevices().then(callback)["catch"](function(){callback([])}):callback([])});var canEnumerate=!1;"undefined"!=typeof MediaStreamTrack&&"getSources"in MediaStreamTrack?canEnumerate=!0:navigator.mediaDevices&&navigator.mediaDevices.enumerateDevices&&(canEnumerate=!0);var hasMicrophone=!1,hasSpeakers=!1,hasWebcam=!1,isWebsiteHasMicrophonePermissions=!1,isWebsiteHasWebcamPermissions=!1,DetectRTC=window.DetectRTC||{};DetectRTC.browser=getBrowserInfo(),detectPrivateMode(function(isPrivateBrowsing){DetectRTC.browser.isPrivateBrowsing=!!isPrivateBrowsing}),DetectRTC.browser["is"+DetectRTC.browser.name]=!0,DetectRTC.osName=osName,DetectRTC.osVersion=osVersion;var isWebRTCSupported=("object"==typeof process&&"object"==typeof process.versions&&process.versions["node-webkit"],!1);["RTCPeerConnection","webkitRTCPeerConnection","mozRTCPeerConnection","RTCIceGatherer"].forEach(function(item){isWebRTCSupported||item in window&&(isWebRTCSupported=!0)}),DetectRTC.isWebRTCSupported=isWebRTCSupported,DetectRTC.isORTCSupported="undefined"!=typeof RTCIceGatherer;var isScreenCapturingSupported=!1;if(DetectRTC.browser.isChrome&&DetectRTC.browser.version>=35?isScreenCapturingSupported=!0:DetectRTC.browser.isFirefox&&DetectRTC.browser.version>=34?isScreenCapturingSupported=!0:DetectRTC.browser.isEdge&&DetectRTC.browser.version>=17?isScreenCapturingSupported=!0:"Android"===DetectRTC.osName&&DetectRTC.browser.isChrome&&(isScreenCapturingSupported=!0),!/^(https:|chrome-extension:)$/g.test(location.protocol||"")){var isNonLocalHost="undefined"!=typeof document&&"string"==typeof document.domain&&document.domain.search&&document.domain.search(/localhost|127.0./g)===-1;isNonLocalHost&&(DetectRTC.browser.isChrome||DetectRTC.browser.isEdge||DetectRTC.browser.isOpera)?isScreenCapturingSupported=!1:DetectRTC.browser.isFirefox&&(isScreenCapturingSupported=!1)}DetectRTC.isScreenCapturingSupported=isScreenCapturingSupported;var webAudio={isSupported:!1,isCreateMediaStreamSourceSupported:!1};["AudioContext","webkitAudioContext","mozAudioContext","msAudioContext"].forEach(function(item){webAudio.isSupported||item in window&&(webAudio.isSupported=!0,window[item]&&"createMediaStreamSource"in window[item].prototype&&(webAudio.isCreateMediaStreamSourceSupported=!0))}),DetectRTC.isAudioContextSupported=webAudio.isSupported,DetectRTC.isCreateMediaStreamSourceSupported=webAudio.isCreateMediaStreamSourceSupported;var isRtpDataChannelsSupported=!1;DetectRTC.browser.isChrome&&DetectRTC.browser.version>31&&(isRtpDataChannelsSupported=!0),DetectRTC.isRtpDataChannelsSupported=isRtpDataChannelsSupported;var isSCTPSupportd=!1;DetectRTC.browser.isFirefox&&DetectRTC.browser.version>28?isSCTPSupportd=!0:DetectRTC.browser.isChrome&&DetectRTC.browser.version>25?isSCTPSupportd=!0:DetectRTC.browser.isOpera&&DetectRTC.browser.version>=11&&(isSCTPSupportd=!0),DetectRTC.isSctpDataChannelsSupported=isSCTPSupportd,DetectRTC.isMobileDevice=isMobileDevice;var isGetUserMediaSupported=!1;navigator.getUserMedia?isGetUserMediaSupported=!0:navigator.mediaDevices&&navigator.mediaDevices.getUserMedia&&(isGetUserMediaSupported=!0),DetectRTC.browser.isChrome&&DetectRTC.browser.version>=46&&!/^(https:|chrome-extension:)$/g.test(location.protocol||"")&&"undefined"!=typeof document&&"string"==typeof document.domain&&document.domain.search&&document.domain.search(/localhost|127.0./g)===-1&&(isGetUserMediaSupported="Requires HTTPs"),"Nodejs"===DetectRTC.osName&&(isGetUserMediaSupported=!1),DetectRTC.isGetUserMediaSupported=isGetUserMediaSupported;var displayResolution="";if(screen.width){var width=screen.width?screen.width:"",height=screen.height?screen.height:"";displayResolution+=""+width+" x "+height}DetectRTC.displayResolution=displayResolution,DetectRTC.displayAspectRatio=getAspectRatio(screen.width,screen.height).toFixed(2), +DetectRTC.isCanvasSupportsStreamCapturing=isCanvasSupportsStreamCapturing,DetectRTC.isVideoSupportsStreamCapturing=isVideoSupportsStreamCapturing,"Chrome"==DetectRTC.browser.name&&DetectRTC.browser.version>=53&&(DetectRTC.isCanvasSupportsStreamCapturing||(DetectRTC.isCanvasSupportsStreamCapturing="Requires chrome flag: enable-experimental-web-platform-features"),DetectRTC.isVideoSupportsStreamCapturing||(DetectRTC.isVideoSupportsStreamCapturing="Requires chrome flag: enable-experimental-web-platform-features")),DetectRTC.DetectLocalIPAddress=DetectLocalIPAddress,DetectRTC.isWebSocketsSupported="WebSocket"in window&&2===window.WebSocket.CLOSING,DetectRTC.isWebSocketsBlocked=!DetectRTC.isWebSocketsSupported,"Nodejs"===DetectRTC.osName&&(DetectRTC.isWebSocketsSupported=!0,DetectRTC.isWebSocketsBlocked=!1),DetectRTC.checkWebSocketsSupport=function(callback){callback=callback||function(){};try{var starttime,websocket=new WebSocket("wss://echo.websocket.org:443/");websocket.onopen=function(){DetectRTC.isWebSocketsBlocked=!1,starttime=(new Date).getTime(),websocket.send("ping")},websocket.onmessage=function(){DetectRTC.WebsocketLatency=(new Date).getTime()-starttime+"ms",callback(),websocket.close(),websocket=null},websocket.onerror=function(){DetectRTC.isWebSocketsBlocked=!0,callback()}}catch(e){DetectRTC.isWebSocketsBlocked=!0,callback()}},DetectRTC.load=function(callback){callback=callback||function(){},checkDeviceSupport(callback)},"undefined"!=typeof MediaDevices?DetectRTC.MediaDevices=MediaDevices:DetectRTC.MediaDevices=[],DetectRTC.hasMicrophone=hasMicrophone,DetectRTC.hasSpeakers=hasSpeakers,DetectRTC.hasWebcam=hasWebcam,DetectRTC.isWebsiteHasWebcamPermissions=isWebsiteHasWebcamPermissions,DetectRTC.isWebsiteHasMicrophonePermissions=isWebsiteHasMicrophonePermissions,DetectRTC.audioInputDevices=audioInputDevices,DetectRTC.audioOutputDevices=audioOutputDevices,DetectRTC.videoInputDevices=videoInputDevices;var isSetSinkIdSupported=!1;"undefined"!=typeof document&&"function"==typeof document.createElement&&"setSinkId"in document.createElement("video")&&(isSetSinkIdSupported=!0),DetectRTC.isSetSinkIdSupported=isSetSinkIdSupported;var isRTPSenderReplaceTracksSupported=!1;DetectRTC.browser.isFirefox&&"undefined"!=typeof mozRTCPeerConnection?"getSenders"in mozRTCPeerConnection.prototype&&(isRTPSenderReplaceTracksSupported=!0):DetectRTC.browser.isChrome&&"undefined"!=typeof webkitRTCPeerConnection&&"getSenders"in webkitRTCPeerConnection.prototype&&(isRTPSenderReplaceTracksSupported=!0),DetectRTC.isRTPSenderReplaceTracksSupported=isRTPSenderReplaceTracksSupported;var isRemoteStreamProcessingSupported=!1;DetectRTC.browser.isFirefox&&DetectRTC.browser.version>38&&(isRemoteStreamProcessingSupported=!0),DetectRTC.isRemoteStreamProcessingSupported=isRemoteStreamProcessingSupported;var isApplyConstraintsSupported=!1;"undefined"!=typeof MediaStreamTrack&&"applyConstraints"in MediaStreamTrack.prototype&&(isApplyConstraintsSupported=!0),DetectRTC.isApplyConstraintsSupported=isApplyConstraintsSupported;var isMultiMonitorScreenCapturingSupported=!1;DetectRTC.browser.isFirefox&&DetectRTC.browser.version>=43&&(isMultiMonitorScreenCapturingSupported=!0),DetectRTC.isMultiMonitorScreenCapturingSupported=isMultiMonitorScreenCapturingSupported,DetectRTC.isPromisesSupported=!!("Promise"in window),DetectRTC.version="1.3.6","undefined"==typeof DetectRTC&&(window.DetectRTC={});var MediaStream=window.MediaStream;"undefined"==typeof MediaStream&&"undefined"!=typeof webkitMediaStream&&(MediaStream=webkitMediaStream),"undefined"!=typeof MediaStream&&"function"==typeof MediaStream?DetectRTC.MediaStream=Object.keys(MediaStream.prototype):DetectRTC.MediaStream=!1,"undefined"!=typeof MediaStreamTrack?DetectRTC.MediaStreamTrack=Object.keys(MediaStreamTrack.prototype):DetectRTC.MediaStreamTrack=!1;var RTCPeerConnection=window.RTCPeerConnection||window.mozRTCPeerConnection||window.webkitRTCPeerConnection;"undefined"!=typeof RTCPeerConnection?DetectRTC.RTCPeerConnection=Object.keys(RTCPeerConnection.prototype):DetectRTC.RTCPeerConnection=!1,window.DetectRTC=DetectRTC,"undefined"!=typeof module&&(module.exports=DetectRTC),"function"==typeof define&&define.amd&&define("DetectRTC",[],function(){return DetectRTC})}(),"undefined"!=typeof cordova&&(DetectRTC.isMobileDevice=!0,DetectRTC.browser.name="Chrome"),navigator&&navigator.userAgent&&navigator.userAgent.indexOf("Crosswalk")!==-1&&(DetectRTC.isMobileDevice=!0,DetectRTC.browser.name="Chrome"),window.addEventListener||(window.addEventListener=function(el,eventName,eventHandler){el.attachEvent&&el.attachEvent("on"+eventName,eventHandler)}),window.attachEventListener=function(video,type,listener,useCapture){video.addEventListener(type,listener,useCapture)};var MediaStream=window.MediaStream;"undefined"==typeof MediaStream&&"undefined"!=typeof webkitMediaStream&&(MediaStream=webkitMediaStream),"undefined"!=typeof MediaStream&&("stop"in MediaStream.prototype||(MediaStream.prototype.stop=function(){this.getTracks().forEach(function(track){track.stop()})})),window.iOSDefaultAudioOutputDevice=window.iOSDefaultAudioOutputDevice||"speaker","undefined"==typeof window.enableAdapter&&("Firefox"===DetectRTC.browser.name&&DetectRTC.browser.version>=54&&(window.enableAdapter=!0),"Chrome"===DetectRTC.browser.name&&DetectRTC.browser.version>=60,"undefined"!=typeof adapter&&adapter.browserDetails&&"string"==typeof adapter.browserDetails.browser&&(window.enableAdapter=!0)),window.enableAdapter||("undefined"==typeof URL.createObjectURL&&(URL.createObjectURL=function(stream){return"blob:https://"+document.domain+"/"+getRandomString()}),"srcObject"in HTMLMediaElement.prototype||(HTMLMediaElement.prototype.srcObject=function(stream){return"mozSrcObject"in this?void(this.mozSrcObject=stream):void(this.src=URL.createObjectURL(stream))})),document.addEventListener("deviceready",setCordovaAPIs,!1),setCordovaAPIs();var RTCPeerConnection,defaults={};"undefined"!=typeof window.RTCPeerConnection?RTCPeerConnection=window.RTCPeerConnection:"undefined"!=typeof mozRTCPeerConnection?RTCPeerConnection=mozRTCPeerConnection:"undefined"!=typeof webkitRTCPeerConnection&&(RTCPeerConnection=webkitRTCPeerConnection);var RTCSessionDescription=window.RTCSessionDescription||window.mozRTCSessionDescription,RTCIceCandidate=window.RTCIceCandidate||window.mozRTCIceCandidate,MediaStreamTrack=window.MediaStreamTrack,CodecsHandler=function(){function preferCodec(sdp,codecName){var info=splitLines(sdp);return info.videoCodecNumbers?"vp8"===codecName&&info.vp8LineNumber===info.videoCodecNumbers[0]?sdp:"vp9"===codecName&&info.vp9LineNumber===info.videoCodecNumbers[0]?sdp:"h264"===codecName&&info.h264LineNumber===info.videoCodecNumbers[0]?sdp:sdp=preferCodecHelper(sdp,codecName,info):sdp}function preferCodecHelper(sdp,codec,info,ignore){var preferCodecNumber="";if("vp8"===codec){if(!info.vp8LineNumber)return sdp;preferCodecNumber=info.vp8LineNumber}if("vp9"===codec){if(!info.vp9LineNumber)return sdp;preferCodecNumber=info.vp9LineNumber}if("h264"===codec){if(!info.h264LineNumber)return sdp;preferCodecNumber=info.h264LineNumber}var newLine=info.videoCodecNumbersOriginal.split("SAVPF")[0]+"SAVPF ",newOrder=[preferCodecNumber];return ignore&&(newOrder=[]),info.videoCodecNumbers.forEach(function(codecNumber){codecNumber!==preferCodecNumber&&newOrder.push(codecNumber)}),newLine+=newOrder.join(" "),sdp=sdp.replace(info.videoCodecNumbersOriginal,newLine)}function splitLines(sdp){var info={};return sdp.split("\n").forEach(function(line){0===line.indexOf("m=video")&&(info.videoCodecNumbers=[],line.split("SAVPF")[1].split(" ").forEach(function(codecNumber){codecNumber=codecNumber.trim(),codecNumber&&codecNumber.length&&(info.videoCodecNumbers.push(codecNumber),info.videoCodecNumbersOriginal=line)})),line.indexOf("VP8/90000")===-1||info.vp8LineNumber||(info.vp8LineNumber=line.replace("a=rtpmap:","").split(" ")[0]),line.indexOf("VP9/90000")===-1||info.vp9LineNumber||(info.vp9LineNumber=line.replace("a=rtpmap:","").split(" ")[0]),line.indexOf("H264/90000")===-1||info.h264LineNumber||(info.h264LineNumber=line.replace("a=rtpmap:","").split(" ")[0])}),info}function removeVPX(sdp){var info=splitLines(sdp);return sdp=preferCodecHelper(sdp,"vp9",info,!0),sdp=preferCodecHelper(sdp,"vp8",info,!0)}function disableNACK(sdp){if(!sdp||"string"!=typeof sdp)throw"Invalid arguments.";return sdp=sdp.replace("a=rtcp-fb:126 nack\r\n",""),sdp=sdp.replace("a=rtcp-fb:126 nack pli\r\n","a=rtcp-fb:126 pli\r\n"),sdp=sdp.replace("a=rtcp-fb:97 nack\r\n",""),sdp=sdp.replace("a=rtcp-fb:97 nack pli\r\n","a=rtcp-fb:97 pli\r\n")}function prioritize(codecMimeType,peer){if(peer&&peer.getSenders&&peer.getSenders().length){if(!codecMimeType||"string"!=typeof codecMimeType)throw"Invalid arguments.";peer.getSenders().forEach(function(sender){for(var params=sender.getParameters(),i=0;i=numberOfTimes||setTimeout(function(){callback(),afterEach(setTimeoutInteval,numberOfTimes,callback,startedTimes)},setTimeoutInteval)}return{setHandlers:setHandlers,onSyncNeeded:function(streamid,action,type){}}}();window.addEventListener("message",function(event){event.origin==window.location.origin&&onMessageCallback(event.data)}),window.IsAndroidChrome=!1;try{navigator.userAgent.toLowerCase().indexOf("android")>-1&&/Chrome/.test(navigator.userAgent)&&/Google Inc/.test(navigator.vendor)&&(window.IsAndroidChrome=!0)}catch(e){}var sourceId,screenCallback,chromeMediaSource="screen",TextSender={send:function(config){function sendText(textMessage,text){var data={type:"text",uuid:uuid,sendingTime:sendingTime};textMessage&&(text=textMessage,data.packets=parseInt(text.length/packetSize)),text.length>packetSize?data.message=text.slice(0,packetSize):(data.message=text,data.last=!0,data.isobject=isobject),channel.send(data,remoteUserId),textToTransfer=text.slice(data.message.length),textToTransfer.length&&setTimeout(function(){sendText(null,textToTransfer)},connection.chunkInterval||100)}var connection=config.connection,channel=config.channel,remoteUserId=config.remoteUserId,initialText=config.text,packetSize=connection.chunkSize||1e3,textToTransfer="",isobject=!1;isString(initialText)||(isobject=!0,initialText=JSON.stringify(initialText));var uuid=getRandomString(),sendingTime=(new Date).getTime();sendText(initialText)}},FileProgressBarHandler=function(){function handle(connection){function updateLabel(progress,label){if(progress.position!==-1){var position=+progress.position.toFixed(2).split(".")[1]||100;label.innerHTML=position+"%"}}var progressHelper={};connection.onFileStart=function(file){var div=document.createElement("div");return div.title=file.name,div.innerHTML=" ",file.remoteUserId&&(div.innerHTML+=" (Sharing with:"+file.remoteUserId+")"),connection.filesContainer||(connection.filesContainer=document.body||document.documentElement),connection.filesContainer.insertBefore(div,connection.filesContainer.firstChild),file.remoteUserId?(progressHelper[file.uuid]||(progressHelper[file.uuid]={}),progressHelper[file.uuid][file.remoteUserId]={div:div,progress:div.querySelector("progress"),label:div.querySelector("label")},void(progressHelper[file.uuid][file.remoteUserId].progress.max=file.maxChunks)):(progressHelper[file.uuid]={div:div,progress:div.querySelector("progress"),label:div.querySelector("label")},void(progressHelper[file.uuid].progress.max=file.maxChunks))},connection.onFileProgress=function(chunk){var helper=progressHelper[chunk.uuid];helper&&(chunk.remoteUserId&&!(helper=progressHelper[chunk.uuid][chunk.remoteUserId])||(helper.progress.value=chunk.currentPosition||chunk.maxChunks||helper.progress.max,updateLabel(helper.progress,helper.label)))},connection.onFileEnd=function(file){var helper=progressHelper[file.uuid];if(!helper)return void console.error("No such progress-helper element exist.",file);if(!file.remoteUserId||(helper=progressHelper[file.uuid][file.remoteUserId])){var div=helper.div;file.type.indexOf("image")!=-1?div.innerHTML='Download '+file.name+'
':div.innerHTML='Download '+file.name+'
'}}}return{handle:handle}}(),TranslationHandler=function(){function handle(connection){connection.autoTranslateText=!1,connection.language="en",connection.googKey="AIzaSyCgB5hmFY74WYB-EoWkhr9cAGr6TiTHrEE",connection.Translator={TranslateText:function(text,callback){var newScript=document.createElement("script");newScript.type="text/javascript";var sourceText=encodeURIComponent(text),randomNumber="method"+connection.token();window[randomNumber]=function(response){return response.data&&response.data.translations[0]&&callback?void callback(response.data.translations[0].translatedText):response.error&&"Daily Limit Exceeded"===response.error.message?void console.error('Text translation failed. Error message: "Daily Limit Exceeded."'):response.error?void console.error(response.error.message):void console.error(response)};var source="https://www.googleapis.com/language/translate/v2?key="+connection.googKey+"&target="+(connection.language||"en-US")+"&callback=window."+randomNumber+"&q="+sourceText;newScript.src=source,document.getElementsByTagName("head")[0].appendChild(newScript)},getListOfLanguages:function(callback){var xhr=new XMLHttpRequest;xhr.onreadystatechange=function(){if(xhr.readyState==XMLHttpRequest.DONE){var response=JSON.parse(xhr.responseText);if(response&&response.data&&response.data.languages)return void callback(response.data.languages);if(response.error&&"Daily Limit Exceeded"===response.error.message)return void console.error('Text translation failed. Error message: "Daily Limit Exceeded."');if(response.error)return void console.error(response.error.message);console.error(response)}};var url="https://www.googleapis.com/language/translate/v2/languages?key="+connection.googKey+"&target=en";xhr.open("GET",url,!0),xhr.send(null)}}}return{handle:handle}}();!function(connection){function onUserLeft(remoteUserId){connection.deletePeer(remoteUserId)}function connectSocket(connectCallback){if(connection.socketAutoReConnect=!0,connection.socket)return void(connectCallback&&connectCallback(connection.socket));if("undefined"==typeof SocketConnection)if("undefined"!=typeof FirebaseConnection)window.SocketConnection=FirebaseConnection;else{if("undefined"==typeof PubNubConnection)throw"SocketConnection.js seems missed.";window.SocketConnection=PubNubConnection}new SocketConnection(connection,function(s){connectCallback&&connectCallback(connection.socket)})}function beforeJoin(userPreferences,callback){if(connection.dontCaptureUserMedia||userPreferences.isDataOnly)return void callback();var localMediaConstraints={};userPreferences.localPeerSdpConstraints.OfferToReceiveAudio&&(localMediaConstraints.audio=connection.mediaConstraints.audio),userPreferences.localPeerSdpConstraints.OfferToReceiveVideo&&(localMediaConstraints.video=connection.mediaConstraints.video);var session=userPreferences.session||connection.session;return session.oneway&&"two-way"!==session.audio&&"two-way"!==session.video&&"two-way"!==session.screen?void callback():(session.oneway&&session.audio&&"two-way"===session.audio&&(session={audio:!0}),void((session.audio||session.video||session.screen)&&(session.screen?"Edge"===DetectRTC.browser.name?navigator.getDisplayMedia({video:!0,audio:isAudioPlusTab(connection)}).then(function(screen){screen.isScreen=!0,mPeer.onGettingLocalMedia(screen),!session.audio&&!session.video||isAudioPlusTab(connection)?callback(screen):connection.invokeGetUserMedia(null,callback)},function(error){console.error("Unable to capture screen on Edge. HTTPs and version 17+ is required.")}):connection.getScreenConstraints(function(error,screen_constraints){connection.invokeGetUserMedia({audio:!!isAudioPlusTab(connection)&&getAudioScreenConstraints(screen_constraints),video:screen_constraints,isScreen:!0},!session.audio&&!session.video||isAudioPlusTab(connection)?callback:connection.invokeGetUserMedia(null,callback))}):(session.audio||session.video)&&connection.invokeGetUserMedia(null,callback,session))))}function beforeUnload(shiftModerationControlOnLeave,dontCloseSocket){connection.closeBeforeUnload&&(connection.isInitiator===!0&&connection.dontMakeMeModerator(),connection.peers.getAllParticipants().forEach(function(participant){mPeer.onNegotiationNeeded({userLeft:!0},participant),connection.peers[participant]&&connection.peers[participant].peer&&connection.peers[participant].peer.close(),delete connection.peers[participant]}),dontCloseSocket||connection.closeSocket(),connection.broadcasters=[],connection.isInitiator=!1)}function applyConstraints(stream,mediaConstraints){return stream?(mediaConstraints.audio&&stream.getAudioTracks().forEach(function(track){track.applyConstraints(mediaConstraints.audio)}),void(mediaConstraints.video&&stream.getVideoTracks().forEach(function(track){track.applyConstraints(mediaConstraints.video)}))):void(connection.enableLogs&&console.error("No stream to applyConstraints."))}function replaceTrack(track,remoteUserId,isVideoTrack){return remoteUserId?void mPeer.replaceTrack(track,remoteUserId,isVideoTrack):void connection.peers.getAllParticipants().forEach(function(participant){mPeer.replaceTrack(track,participant,isVideoTrack)})}function keepNextBroadcasterOnServer(){if(connection.isInitiator&&!connection.session.oneway&&!connection.session.broadcast&&"many-to-many"===connection.direction){var firstBroadcaster=connection.broadcasters[0],otherBroadcasters=[];connection.broadcasters.forEach(function(broadcaster){broadcaster!==firstBroadcaster&&otherBroadcasters.push(broadcaster)}),connection.autoCloseEntireSession||connection.shiftModerationControl(firstBroadcaster,otherBroadcasters,!0)}}forceOptions=forceOptions||{useDefaultDevices:!0},connection.channel=connection.sessionid=(roomid||location.href.replace(/\/|:|#|\?|\$|\^|%|\.|`|~|!|\+|@|\[|\||]|\|*. /g,"").split("\n").join("").split("\r").join(""))+"";var mPeer=new MultiPeers(connection),preventDuplicateOnStreamEvents={};mPeer.onGettingLocalMedia=function(stream,callback){if(callback=callback||function(){},!preventDuplicateOnStreamEvents[stream.streamid]){preventDuplicateOnStreamEvents[stream.streamid]=!0;try{stream.type="local"}catch(e){}connection.setStreamEndHandler(stream),getRMCMediaElement(stream,function(mediaElement){mediaElement.id=stream.streamid,mediaElement.muted=!0,mediaElement.volume=0,connection.attachStreams.indexOf(stream)===-1&&connection.attachStreams.push(stream),"undefined"!=typeof StreamsHandler&&StreamsHandler.setHandlers(stream,!0,connection),connection.streamEvents[stream.streamid]={stream:stream,type:"local",mediaElement:mediaElement,userid:connection.userid,extra:connection.extra,streamid:stream.streamid,isAudioMuted:!0},setHarkEvents(connection,connection.streamEvents[stream.streamid]),setMuteHandlers(connection,connection.streamEvents[stream.streamid]),connection.onstream(connection.streamEvents[stream.streamid]),callback()},connection)}},mPeer.onGettingRemoteMedia=function(stream,remoteUserId){try{stream.type="remote"}catch(e){}connection.setStreamEndHandler(stream,"remote-stream"),getRMCMediaElement(stream,function(mediaElement){mediaElement.id=stream.streamid,"undefined"!=typeof StreamsHandler&&StreamsHandler.setHandlers(stream,!1,connection),connection.streamEvents[stream.streamid]={stream:stream,type:"remote",userid:remoteUserId,extra:connection.peers[remoteUserId]?connection.peers[remoteUserId].extra:{},mediaElement:mediaElement,streamid:stream.streamid},setMuteHandlers(connection,connection.streamEvents[stream.streamid]),connection.onstream(connection.streamEvents[stream.streamid])},connection)},mPeer.onRemovingRemoteMedia=function(stream,remoteUserId){var streamEvent=connection.streamEvents[stream.streamid];streamEvent||(streamEvent={stream:stream,type:"remote",userid:remoteUserId,extra:connection.peers[remoteUserId]?connection.peers[remoteUserId].extra:{},streamid:stream.streamid,mediaElement:connection.streamEvents[stream.streamid]?connection.streamEvents[stream.streamid].mediaElement:null}),connection.peersBackup[streamEvent.userid]&&(streamEvent.extra=connection.peersBackup[streamEvent.userid].extra),connection.onstreamended(streamEvent),delete connection.streamEvents[stream.streamid]},mPeer.onNegotiationNeeded=function(message,remoteUserId,callback){remoteUserId=remoteUserId||message.remoteUserId,connectSocket(function(){connection.socket.emit(connection.socketMessageEvent,"password"in message?message:{remoteUserId:remoteUserId,message:message,sender:connection.userid},callback||function(){})})},mPeer.onUserLeft=onUserLeft,mPeer.disconnectWith=function(remoteUserId,callback){connection.socket&&connection.socket.emit("disconnect-with",remoteUserId,callback||function(){}),connection.deletePeer(remoteUserId)},connection.broadcasters=[],connection.socketOptions={transport:"polling"},connection.openOrJoin=function(localUserid,password,callback){callback=callback||function(){},connection.checkPresence(localUserid,function(isRoomExist,roomid){if("function"==typeof password&&"undefined"!=typeof password&&(callback=password,password=null),!password&&connection.password&&(password=connection.password),isRoomExist){connection.sessionid=roomid;var localPeerSdpConstraints=!1,remotePeerSdpConstraints=!1,isOneWay=!!connection.session.oneway,isDataOnly=isData(connection.session);remotePeerSdpConstraints={OfferToReceiveAudio:connection.sdpConstraints.mandatory.OfferToReceiveAudio,OfferToReceiveVideo:connection.sdpConstraints.mandatory.OfferToReceiveVideo},localPeerSdpConstraints={OfferToReceiveAudio:isOneWay?!!connection.session.audio:connection.sdpConstraints.mandatory.OfferToReceiveAudio,OfferToReceiveVideo:isOneWay?!!connection.session.video||!!connection.session.screen:connection.sdpConstraints.mandatory.OfferToReceiveVideo};var connectionDescription={remoteUserId:connection.sessionid,message:{newParticipationRequest:!0,isOneWay:isOneWay,isDataOnly:isDataOnly,localPeerSdpConstraints:localPeerSdpConstraints,remotePeerSdpConstraints:remotePeerSdpConstraints},sender:connection.userid,password:password||!1};return void beforeJoin(connectionDescription.message,function(){mPeer.onNegotiationNeeded(connectionDescription),callback(isRoomExist,roomid)})}connection.waitingForLocalMedia=!0,connection.isInitiator=!0;connection.userid;return connection.userid=connection.sessionid=localUserid||connection.sessionid,connection.userid+="", +connection.socket.emit("changed-uuid",connection.userid),password&&connection.socket.emit("set-password",password),isData(connection.session)?void(connection.waitingForLocalMedia=!1):void connection.captureUserMedia(function(){connection.waitingForLocalMedia=!1,callback(isRoomExist,roomid)})})},connection.waitingForLocalMedia=!1,connection.open=function(localUserid,isPublicModerator,callback){connection.waitingForLocalMedia=!0,connection.isInitiator=!0,callback=callback||function(){},"function"==typeof isPublicModerator&&(callback=isPublicModerator,isPublicModerator=!1);connection.userid;connection.userid=connection.sessionid=localUserid||connection.sessionid,connection.userid+="",connectSocket(function(){return connection.socket.emit("changed-uuid",connection.userid),connection.password&&connection.socket.emit("set-password",connection.password),1==isPublicModerator&&connection.becomePublicModerator(),isData(connection.session)?(connection.waitingForLocalMedia=!1,void callback()):void connection.captureUserMedia(function(){connection.waitingForLocalMedia=!1,callback()})})},connection.becomePublicModerator=function(){connection.isInitiator&&connection.socket.emit("become-a-public-moderator")},connection.dontMakeMeModerator=function(){connection.socket.emit("dont-make-me-moderator")},connection.deletePeer=function(remoteUserId){if(remoteUserId){var eventObject={userid:remoteUserId,extra:connection.peers[remoteUserId]?connection.peers[remoteUserId].extra:{}};if(connection.peersBackup[eventObject.userid]&&(eventObject.extra=connection.peersBackup[eventObject.userid].extra),connection.onleave(eventObject),connection.peers[remoteUserId]){connection.peers[remoteUserId].streams.forEach(function(stream){stream.stop()});var peer=connection.peers[remoteUserId].peer;if(peer&&"closed"!==peer.iceConnectionState)try{peer.close()}catch(e){}connection.peers[remoteUserId]&&(connection.peers[remoteUserId].peer=null,delete connection.peers[remoteUserId])}if(connection.broadcasters.indexOf(remoteUserId)!==-1){var newArray=[];connection.broadcasters.forEach(function(broadcaster){broadcaster!==remoteUserId&&newArray.push(broadcaster)}),connection.broadcasters=newArray,keepNextBroadcasterOnServer()}}},connection.rejoin=function(connectionDescription){if(!connection.isInitiator&&connectionDescription&&Object.keys(connectionDescription).length){var extra={};connection.peers[connectionDescription.remoteUserId]&&(extra=connection.peers[connectionDescription.remoteUserId].extra,connection.deletePeer(connectionDescription.remoteUserId)),connectionDescription&&connectionDescription.remoteUserId&&(connection.join(connectionDescription.remoteUserId),connection.onReConnecting({userid:connectionDescription.remoteUserId,extra:extra}))}},connection.join=connection.connect=function(remoteUserId,options){connection.sessionid=!!remoteUserId&&(remoteUserId.sessionid||remoteUserId.remoteUserId||remoteUserId)||connection.sessionid,connection.sessionid+="";var localPeerSdpConstraints=!1,remotePeerSdpConstraints=!1,isOneWay=!1,isDataOnly=!1;if(remoteUserId&&remoteUserId.session||!remoteUserId||"string"==typeof remoteUserId){var session=remoteUserId?remoteUserId.session||connection.session:connection.session;isOneWay=!!session.oneway,isDataOnly=isData(session),remotePeerSdpConstraints={OfferToReceiveAudio:connection.sdpConstraints.mandatory.OfferToReceiveAudio,OfferToReceiveVideo:connection.sdpConstraints.mandatory.OfferToReceiveVideo},localPeerSdpConstraints={OfferToReceiveAudio:isOneWay?!!connection.session.audio:connection.sdpConstraints.mandatory.OfferToReceiveAudio,OfferToReceiveVideo:isOneWay?!!connection.session.video||!!connection.session.screen:connection.sdpConstraints.mandatory.OfferToReceiveVideo}}options=options||{};var cb=function(){};"function"==typeof options&&(cb=options,options={}),"undefined"!=typeof options.localPeerSdpConstraints&&(localPeerSdpConstraints=options.localPeerSdpConstraints),"undefined"!=typeof options.remotePeerSdpConstraints&&(remotePeerSdpConstraints=options.remotePeerSdpConstraints),"undefined"!=typeof options.isOneWay&&(isOneWay=options.isOneWay),"undefined"!=typeof options.isDataOnly&&(isDataOnly=options.isDataOnly);var connectionDescription={remoteUserId:connection.sessionid,message:{newParticipationRequest:!0,isOneWay:isOneWay,isDataOnly:isDataOnly,localPeerSdpConstraints:localPeerSdpConstraints,remotePeerSdpConstraints:remotePeerSdpConstraints},sender:connection.userid,password:connection.password||!1};return beforeJoin(connectionDescription.message,function(){connectSocket(function(){connection.peers[connection.sessionid]||(mPeer.onNegotiationNeeded(connectionDescription),cb())})}),connectionDescription},connection.connectWithAllParticipants=function(remoteUserId){mPeer.onNegotiationNeeded("connectWithAllParticipants",remoteUserId||connection.sessionid)},connection.removeFromBroadcastersList=function(remoteUserId){mPeer.onNegotiationNeeded("removeFromBroadcastersList",remoteUserId||connection.sessionid),connection.peers.getAllParticipants(remoteUserId||connection.sessionid).forEach(function(participant){mPeer.onNegotiationNeeded("dropPeerConnection",participant),connection.deletePeer(participant)}),connection.attachStreams.forEach(function(stream){stream.stop()})},connection.getUserMedia=connection.captureUserMedia=function(callback,sessionForced){callback=callback||function(){};var session=sessionForced||connection.session;return connection.dontCaptureUserMedia||isData(session)?void callback():void((session.audio||session.video||session.screen)&&(session.screen?"Edge"===DetectRTC.browser.name?navigator.getDisplayMedia({video:!0,audio:isAudioPlusTab(connection)}).then(function(screen){if(screen.isScreen=!0,mPeer.onGettingLocalMedia(screen),(session.audio||session.video)&&!isAudioPlusTab(connection)){var nonScreenSession={};for(var s in session)"screen"!==s&&(nonScreenSession[s]=session[s]);return void connection.invokeGetUserMedia(sessionForced,callback,nonScreenSession)}callback(screen)},function(error){console.error("Unable to capture screen on Edge. HTTPs and version 17+ is required.")}):connection.getScreenConstraints(function(error,screen_constraints){if(error)throw error;connection.invokeGetUserMedia({audio:!!isAudioPlusTab(connection)&&getAudioScreenConstraints(screen_constraints),video:screen_constraints,isScreen:!0},function(stream){if((session.audio||session.video)&&!isAudioPlusTab(connection)){var nonScreenSession={};for(var s in session)"screen"!==s&&(nonScreenSession[s]=session[s]);return void connection.invokeGetUserMedia(sessionForced,callback,nonScreenSession)}callback(stream)})}):(session.audio||session.video)&&connection.invokeGetUserMedia(sessionForced,callback,session)))},connection.closeBeforeUnload=!0,window.addEventListener("beforeunload",beforeUnload,!1),connection.userid=getRandomString(),connection.changeUserId=function(newUserId,callback){callback=callback||function(){},connection.userid=newUserId||getRandomString(),connection.socket.emit("changed-uuid",connection.userid,callback)},connection.extra={},connection.attachStreams=[],connection.session={audio:!0,video:!0},connection.enableFileSharing=!1,connection.bandwidth={screen:!1,audio:!1,video:!1},connection.codecs={audio:"opus",video:"VP9"},connection.processSdp=function(sdp){return"Safari"===DetectRTC.browser.name?sdp:("VP8"===connection.codecs.video.toUpperCase()&&(sdp=CodecsHandler.preferCodec(sdp,"vp8")),"VP9"===connection.codecs.video.toUpperCase()&&(sdp=CodecsHandler.preferCodec(sdp,"vp9")),"H264"===connection.codecs.video.toUpperCase()&&(sdp=CodecsHandler.preferCodec(sdp,"h264")),"G722"===connection.codecs.audio&&(sdp=CodecsHandler.removeNonG722(sdp)),"Firefox"===DetectRTC.browser.name?sdp:((connection.bandwidth.video||connection.bandwidth.screen)&&(sdp=CodecsHandler.setApplicationSpecificBandwidth(sdp,connection.bandwidth,!!connection.session.screen)),connection.bandwidth.video&&(sdp=CodecsHandler.setVideoBitrates(sdp,{min:8*connection.bandwidth.video*1024,max:8*connection.bandwidth.video*1024})),connection.bandwidth.audio&&(sdp=CodecsHandler.setOpusAttributes(sdp,{maxaveragebitrate:8*connection.bandwidth.audio*1024,maxplaybackrate:8*connection.bandwidth.audio*1024,stereo:1,maxptime:3})),sdp))},"undefined"!=typeof CodecsHandler&&(connection.BandwidthHandler=connection.CodecsHandler=CodecsHandler),connection.mediaConstraints={audio:{mandatory:{},optional:connection.bandwidth.audio?[{bandwidth:8*connection.bandwidth.audio*1024||1048576}]:[]},video:{mandatory:{},optional:connection.bandwidth.video?[{bandwidth:8*connection.bandwidth.video*1024||1048576},{facingMode:"user"}]:[{facingMode:"user"}]}},"Firefox"===DetectRTC.browser.name&&(connection.mediaConstraints={audio:!0,video:!0}),forceOptions.useDefaultDevices||DetectRTC.isMobileDevice||DetectRTC.load(function(){var lastAudioDevice,lastVideoDevice;if(DetectRTC.MediaDevices.forEach(function(device){"audioinput"===device.kind&&connection.mediaConstraints.audio!==!1&&(lastAudioDevice=device),"videoinput"===device.kind&&connection.mediaConstraints.video!==!1&&(lastVideoDevice=device)}),lastAudioDevice){if("Firefox"===DetectRTC.browser.name)return void(connection.mediaConstraints.audio!==!0?connection.mediaConstraints.audio.deviceId=lastAudioDevice.id:connection.mediaConstraints.audio={deviceId:lastAudioDevice.id});1==connection.mediaConstraints.audio&&(connection.mediaConstraints.audio={mandatory:{},optional:[]}),connection.mediaConstraints.audio.optional||(connection.mediaConstraints.audio.optional=[]);var optional=[{sourceId:lastAudioDevice.id}];connection.mediaConstraints.audio.optional=optional.concat(connection.mediaConstraints.audio.optional)}if(lastVideoDevice){if("Firefox"===DetectRTC.browser.name)return void(connection.mediaConstraints.video!==!0?connection.mediaConstraints.video.deviceId=lastVideoDevice.id:connection.mediaConstraints.video={deviceId:lastVideoDevice.id});1==connection.mediaConstraints.video&&(connection.mediaConstraints.video={mandatory:{},optional:[]}),connection.mediaConstraints.video.optional||(connection.mediaConstraints.video.optional=[]);var optional=[{sourceId:lastVideoDevice.id}];connection.mediaConstraints.video.optional=optional.concat(connection.mediaConstraints.video.optional)}}),connection.sdpConstraints={mandatory:{OfferToReceiveAudio:!0,OfferToReceiveVideo:!0},optional:[{VoiceActivityDetection:!1}]},connection.rtcpMuxPolicy="require",connection.iceTransportPolicy=null,connection.optionalArgument={optional:[{DtlsSrtpKeyAgreement:!0},{googImprovedWifiBwe:!0},{googScreencastMinBitrate:300},{googIPv6:!0},{googDscp:!0},{googCpuUnderuseThreshold:55},{googCpuOveruseThreshold:85},{googSuspendBelowMinBitrate:!0},{googCpuOveruseDetection:!0}],mandatory:{}},connection.iceServers=IceServersHandler.getIceServers(connection),connection.candidates={host:!0,stun:!0,turn:!0},connection.iceProtocols={tcp:!0,udp:!0},connection.onopen=function(event){connection.enableLogs&&console.info("Data connection has been opened between you & ",event.userid)},connection.onclose=function(event){connection.enableLogs&&console.warn("Data connection has been closed between you & ",event.userid)},connection.onerror=function(error){connection.enableLogs&&console.error(error.userid,"data-error",error)},connection.onmessage=function(event){connection.enableLogs&&console.debug("data-message",event.userid,event.data)},connection.send=function(data,remoteUserId){connection.peers.send(data,remoteUserId)},connection.close=connection.disconnect=connection.leave=function(){beforeUnload(!1,!0)},connection.closeEntireSession=function(callback){callback=callback||function(){},connection.socket.emit("close-entire-session",function looper(){return connection.getAllParticipants().length?void setTimeout(looper,100):(connection.onEntireSessionClosed({sessionid:connection.sessionid,userid:connection.userid,extra:connection.extra}),void connection.changeUserId(null,function(){connection.close(),callback()}))})},connection.onEntireSessionClosed=function(event){connection.enableLogs&&console.info("Entire session is closed: ",event.sessionid,event.extra)},connection.onstream=function(e){var parentNode=connection.videosContainer;parentNode.insertBefore(e.mediaElement,parentNode.firstChild);var played=e.mediaElement.play();return"undefined"!=typeof played?void played["catch"](function(){}).then(function(){setTimeout(function(){e.mediaElement.play()},2e3)}):void setTimeout(function(){e.mediaElement.play()},2e3)},connection.onstreamended=function(e){e.mediaElement||(e.mediaElement=document.getElementById(e.streamid)),e.mediaElement&&e.mediaElement.parentNode&&e.mediaElement.parentNode.removeChild(e.mediaElement)},connection.direction="many-to-many",connection.removeStream=function(streamid,remoteUserId){var stream;return connection.attachStreams.forEach(function(localStream){localStream.id===streamid&&(stream=localStream)}),stream?(connection.peers.getAllParticipants().forEach(function(participant){if(!remoteUserId||participant===remoteUserId){var user=connection.peers[participant];try{user.peer.removeStream(stream)}catch(e){}}}),void connection.renegotiate()):void console.warn("No such stream exist.",streamid)},connection.addStream=function(session,remoteUserId){function gumCallback(stream){session.streamCallback&&session.streamCallback(stream),connection.renegotiate(remoteUserId)}return session.getAudioTracks?(connection.attachStreams.indexOf(session)===-1&&(session.streamid||(session.streamid=session.id),connection.attachStreams.push(session)),void connection.renegotiate(remoteUserId)):isData(session)?void connection.renegotiate(remoteUserId):void((session.audio||session.video||session.screen)&&(session.screen?"Edge"===DetectRTC.browser.name?navigator.getDisplayMedia({video:!0,audio:isAudioPlusTab(connection)}).then(function(screen){screen.isScreen=!0,mPeer.onGettingLocalMedia(screen),!session.audio&&!session.video||isAudioPlusTab(connection)?gumCallback(screen):connection.invokeGetUserMedia(null,function(stream){gumCallback(stream)})},function(error){console.error("Unable to capture screen on Edge. HTTPs and version 17+ is required.")}):connection.getScreenConstraints(function(error,screen_constraints){return error?"PermissionDeniedError"===error?(session.streamCallback&&session.streamCallback(null),void(connection.enableLogs&&console.error("User rejected to share his screen."))):alert(error):void connection.invokeGetUserMedia({audio:!!isAudioPlusTab(connection)&&getAudioScreenConstraints(screen_constraints),video:screen_constraints,isScreen:!0},function(stream){!session.audio&&!session.video||isAudioPlusTab(connection)?gumCallback(stream):connection.invokeGetUserMedia(null,function(stream){gumCallback(stream)})})}):(session.audio||session.video)&&connection.invokeGetUserMedia(null,gumCallback)))},connection.invokeGetUserMedia=function(localMediaConstraints,callback,session){session||(session=connection.session),localMediaConstraints||(localMediaConstraints=connection.mediaConstraints),getUserMediaHandler({onGettingLocalMedia:function(stream){var videoConstraints=localMediaConstraints.video;videoConstraints&&(videoConstraints.mediaSource||videoConstraints.mozMediaSource?stream.isScreen=!0:videoConstraints.mandatory&&videoConstraints.mandatory.chromeMediaSource&&(stream.isScreen=!0)),stream.isScreen||(stream.isVideo=stream.getVideoTracks().length,stream.isAudio=!stream.isVideo&&stream.getAudioTracks().length),mPeer.onGettingLocalMedia(stream,function(){"function"==typeof callback&&callback(stream)})},onLocalMediaError:function(error,constraints){mPeer.onLocalMediaError(error,constraints)},localMediaConstraints:localMediaConstraints||{audio:!!session.audio&&localMediaConstraints.audio,video:!!session.video&&localMediaConstraints.video}})},connection.applyConstraints=function(mediaConstraints,streamid){if(!MediaStreamTrack||!MediaStreamTrack.prototype.applyConstraints)return void alert("track.applyConstraints is NOT supported in your browser.");if(streamid){var stream;return connection.streamEvents[streamid]&&(stream=connection.streamEvents[streamid].stream),void applyConstraints(stream,mediaConstraints)}connection.attachStreams.forEach(function(stream){applyConstraints(stream,mediaConstraints)})},connection.replaceTrack=function(session,remoteUserId,isVideoTrack){function gumCallback(stream){connection.replaceTrack(stream,remoteUserId,isVideoTrack||session.video||session.screen)}if(session=session||{},!RTCPeerConnection.prototype.getSenders)return void connection.addStream(session);if(session instanceof MediaStreamTrack)return void replaceTrack(session,remoteUserId,isVideoTrack);if(session instanceof MediaStream)return session.getVideoTracks().length&&replaceTrack(session.getVideoTracks()[0],remoteUserId,!0),void(session.getAudioTracks().length&&replaceTrack(session.getAudioTracks()[0],remoteUserId,!1));if(isData(session))throw"connection.replaceTrack requires audio and/or video and/or screen.";(session.audio||session.video||session.screen)&&(session.screen?"Edge"===DetectRTC.browser.name?navigator.getDisplayMedia({video:!0,audio:isAudioPlusTab(connection)}).then(function(screen){screen.isScreen=!0,mPeer.onGettingLocalMedia(screen),!session.audio&&!session.video||isAudioPlusTab(connection)?gumCallback(screen):connection.invokeGetUserMedia(null,gumCallback)},function(error){console.error("Unable to capture screen on Edge. HTTPs and version 17+ is required.")}):connection.getScreenConstraints(function(error,screen_constraints){return error?alert(error):void connection.invokeGetUserMedia({audio:!!isAudioPlusTab(connection)&&getAudioScreenConstraints(screen_constraints),video:screen_constraints,isScreen:!0},!session.audio&&!session.video||isAudioPlusTab(connection)?gumCallback:connection.invokeGetUserMedia(null,gumCallback))}):(session.audio||session.video)&&connection.invokeGetUserMedia(null,gumCallback))},connection.resetTrack=function(remoteUsersIds,isVideoTrack){remoteUsersIds||(remoteUsersIds=connection.getAllParticipants()),"string"==typeof remoteUsersIds&&(remoteUsersIds=[remoteUsersIds]),remoteUsersIds.forEach(function(participant){var peer=connection.peers[participant].peer;"undefined"!=typeof isVideoTrack&&isVideoTrack!==!0||!peer.lastVideoTrack||connection.replaceTrack(peer.lastVideoTrack,participant,!0),"undefined"!=typeof isVideoTrack&&isVideoTrack!==!1||!peer.lastAudioTrack||connection.replaceTrack(peer.lastAudioTrack,participant,!1)})},connection.renegotiate=function(remoteUserId){return remoteUserId?void mPeer.renegotiatePeer(remoteUserId):void connection.peers.getAllParticipants().forEach(function(participant){mPeer.renegotiatePeer(participant)})},connection.setStreamEndHandler=function(stream,isRemote){if(stream&&stream.addEventListener&&(isRemote=!!isRemote,!stream.alreadySetEndHandler)){stream.alreadySetEndHandler=!0;var streamEndedEvent="ended";"oninactive"in stream&&(streamEndedEvent="inactive"),stream.addEventListener(streamEndedEvent,function(){if(stream.idInstance&¤tUserMediaRequest.remove(stream.idInstance),!isRemote){var streams=[];connection.attachStreams.forEach(function(s){s.id!=stream.id&&streams.push(s)}),connection.attachStreams=streams}var streamEvent=connection.streamEvents[stream.streamid];if(streamEvent||(streamEvent={stream:stream,streamid:stream.streamid,type:isRemote?"remote":"local",userid:connection.userid,extra:connection.extra,mediaElement:connection.streamEvents[stream.streamid]?connection.streamEvents[stream.streamid].mediaElement:null}),isRemote&&connection.peers[streamEvent.userid]){var peer=connection.peers[streamEvent.userid].peer,streams=[];peer.getRemoteStreams().forEach(function(s){s.id!=stream.id&&streams.push(s)}),connection.peers[streamEvent.userid].streams=streams}streamEvent.userid===connection.userid&&"remote"===streamEvent.type||(connection.peersBackup[streamEvent.userid]&&(streamEvent.extra=connection.peersBackup[streamEvent.userid].extra),connection.onstreamended(streamEvent),delete connection.streamEvents[stream.streamid])},!1)}},connection.onMediaError=function(error,constraints){connection.enableLogs&&console.error(error,constraints)},connection.addNewBroadcaster=function(broadcasterId,userPreferences){connection.socket.isIO||(connection.broadcasters.length&&setTimeout(function(){mPeer.connectNewParticipantWithAllBroadcasters(broadcasterId,userPreferences,connection.broadcasters.join("|-,-|"))},1e4),connection.session.oneway||connection.session.broadcast||"many-to-many"!==connection.direction||connection.broadcasters.indexOf(broadcasterId)!==-1||(connection.broadcasters.push(broadcasterId),keepNextBroadcasterOnServer()))},connection.autoCloseEntireSession=!1,connection.filesContainer=connection.videosContainer=document.body||document.documentElement,connection.isInitiator=!1,connection.shareFile=mPeer.shareFile,"undefined"!=typeof FileProgressBarHandler&&FileProgressBarHandler.handle(connection),"undefined"!=typeof TranslationHandler&&TranslationHandler.handle(connection),connection.token=getRandomString,connection.onNewParticipant=function(participantId,userPreferences){connection.acceptParticipationRequest(participantId,userPreferences)},connection.acceptParticipationRequest=function(participantId,userPreferences){userPreferences.successCallback&&(userPreferences.successCallback(),delete userPreferences.successCallback),mPeer.createNewPeer(participantId,userPreferences)},connection.onShiftedModerationControl=function(sender,existingBroadcasters){connection.acceptModerationControl(sender,existingBroadcasters)},connection.acceptModerationControl=function(sender,existingBroadcasters){connection.isInitiator=!0,connection.broadcasters=existingBroadcasters,connection.peers.getAllParticipants().forEach(function(participant){mPeer.onNegotiationNeeded({changedUUID:sender,oldUUID:connection.userid,newUUID:sender},participant)}),connection.userid=sender,connection.changeUserId(connection.userid)},connection.shiftModerationControl=function(remoteUserId,existingBroadcasters,firedOnLeave){mPeer.onNegotiationNeeded({shiftedModerationControl:!0,broadcasters:existingBroadcasters,firedOnLeave:!!firedOnLeave},remoteUserId)},"undefined"!=typeof StreamsHandler&&(connection.StreamsHandler=StreamsHandler),connection.onleave=function(userid){},connection.invokeSelectFileDialog=function(callback){var selector=new FileSelector;selector.accept="*.*",selector.selectSingleFile(callback)},connection.getPublicModerators=connection.getPublicUsers=function(userIdStartsWith,callback){"function"==typeof userIdStartsWith&&(callback=userIdStartsWith),connectSocket(function(){connection.socket.emit("get-public-moderators","string"==typeof userIdStartsWith?userIdStartsWith:"",callback)})},connection.onmute=function(e){if(e&&e.mediaElement)if("both"===e.muteType||"video"===e.muteType){e.mediaElement.src=null;var paused=e.mediaElement.pause();"undefined"!=typeof paused?paused.then(function(){e.mediaElement.poster=e.snapshot||"https://cdn.webrtc-experiment.com/images/muted.png"}):e.mediaElement.poster=e.snapshot||"https://cdn.webrtc-experiment.com/images/muted.png"}else"audio"===e.muteType&&(e.mediaElement.muted=!0)},connection.onunmute=function(e){e&&e.mediaElement&&e.stream&&("both"===e.unmuteType||"video"===e.unmuteType?(e.mediaElement.poster=null,e.mediaElement.srcObject=e.stream,e.mediaElement.play()):"audio"===e.unmuteType&&(e.mediaElement.muted=!1))},connection.onExtraDataUpdated=function(event){event.status="online",connection.onUserStatusChanged(event,!0)},connection.onJoinWithPassword=function(remoteUserId){console.warn(remoteUserId,"is password protected. Please join with password.")},connection.onInvalidPassword=function(remoteUserId,oldPassword){console.warn(remoteUserId,"is password protected. Please join with valid password. Your old password",oldPassword,"is wrong.")},connection.onPasswordMaxTriesOver=function(remoteUserId){console.warn(remoteUserId,"is password protected. Your max password tries exceeded the limit.")},connection.getAllParticipants=function(sender){return connection.peers.getAllParticipants(sender)},"undefined"!=typeof StreamsHandler&&(StreamsHandler.onSyncNeeded=function(streamid,action,type){connection.peers.getAllParticipants().forEach(function(participant){mPeer.onNegotiationNeeded({streamid:streamid,action:action,streamSyncNeeded:!0,type:type||"both"},participant)})}),connection.connectSocket=function(callback){connectSocket(callback)},connection.closeSocket=function(){try{io.sockets={}}catch(e){}connection.socket&&("function"==typeof connection.socket.disconnect&&connection.socket.disconnect(),"function"==typeof connection.socket.resetProps&&connection.socket.resetProps(),connection.socket=null)},connection.getSocket=function(callback){return connection.socket?callback&&callback(connection.socket):connectSocket(callback),connection.socket},connection.getRemoteStreams=mPeer.getRemoteStreams;var skipStreams=["selectFirst","selectAll","forEach"];if(connection.streamEvents={selectFirst:function(options){if(!options){var firstStream;for(var str in connection.streamEvents)skipStreams.indexOf(str)!==-1||firstStream||(firstStream=connection.streamEvents[str]);return firstStream}},selectAll:function(){}},connection.socketURL="/",connection.socketMessageEvent="RTCMultiConnection-Message",connection.socketCustomEvent="RTCMultiConnection-Custom-Message",connection.DetectRTC=DetectRTC,connection.setCustomSocketEvent=function(customEvent){customEvent&&(connection.socketCustomEvent=customEvent),connection.socket&&connection.socket.emit("set-custom-socket-event-listener",connection.socketCustomEvent)},connection.getNumberOfBroadcastViewers=function(broadcastId,callback){connection.socket&&broadcastId&&callback&&connection.socket.emit("get-number-of-users-in-specific-broadcast",broadcastId,callback)},connection.onNumberOfBroadcastViewersUpdated=function(event){connection.enableLogs&&connection.isInitiator&&console.info("Number of broadcast (",event.broadcastId,") viewers",event.numberOfBroadcastViewers)},connection.onUserStatusChanged=function(event,dontWriteLogs){connection.enableLogs&&!dontWriteLogs&&console.info(event.userid,event.status)},connection.getUserMediaHandler=getUserMediaHandler,connection.multiPeersHandler=mPeer,connection.enableLogs=!0,connection.setCustomSocketHandler=function(customSocketHandler){"undefined"!=typeof SocketConnection&&(SocketConnection=customSocketHandler)},connection.chunkSize=65e3,connection.maxParticipantsAllowed=1e3,connection.disconnectWith=mPeer.disconnectWith,connection.checkPresence=function(remoteUserId,callback){return"SSEConnection"===SocketConnection.name?void SSEConnection.checkPresence(remoteUserId,function(isRoomExist,roomid){return connection.socket?void callback(isRoomExist,roomid):(isRoomExist||(connection.userid=roomid),void connection.connectSocket(function(){callback(isRoomExist,roomid)}))}):connection.socket?void connection.socket.emit("check-presence",(remoteUserId||connection.sessionid)+"",callback):void connection.connectSocket(function(){connection.checkPresence(remoteUserId,callback)})},connection.onReadyForOffer=function(remoteUserId,userPreferences){connection.multiPeersHandler.createNewPeer(remoteUserId,userPreferences)},connection.setUserPreferences=function(userPreferences){return connection.dontAttachStream&&(userPreferences.dontAttachLocalStream=!0),connection.dontGetRemoteStream&&(userPreferences.dontGetRemoteStream=!0),userPreferences},connection.updateExtraData=function(){connection.socket.emit("extra-data-updated",connection.extra)},connection.enableScalableBroadcast=!1,connection.maxRelayLimitPerUser=3,connection.dontCaptureUserMedia=!1,connection.dontAttachStream=!1,connection.dontGetRemoteStream=!1,connection.onReConnecting=function(event){connection.enableLogs&&console.info("ReConnecting with",event.userid,"...")},connection.beforeAddingStream=function(stream){return stream},connection.beforeRemovingStream=function(stream){return stream},"undefined"!=typeof isChromeExtensionAvailable&&(connection.checkIfChromeExtensionAvailable=isChromeExtensionAvailable),"undefined"!=typeof isFirefoxExtensionAvailable&&(connection.checkIfChromeExtensionAvailable=isFirefoxExtensionAvailable),"undefined"!=typeof getChromeExtensionStatus&&(connection.getChromeExtensionStatus=getChromeExtensionStatus),connection.getScreenConstraints=function(callback,audioPlusTab){isAudioPlusTab(connection,audioPlusTab)&&(audioPlusTab=!0),getScreenConstraints(function(error,screen_constraints){error||(screen_constraints=connection.modifyScreenConstraints(screen_constraints),callback(error,screen_constraints))},audioPlusTab)},connection.modifyScreenConstraints=function(screen_constraints){return screen_constraints},connection.onPeerStateChanged=function(state){connection.enableLogs&&state.iceConnectionState.search(/closed|failed/gi)!==-1&&console.error("Peer connection is closed between you & ",state.userid,state.extra,"state:",state.iceConnectionState)},connection.isOnline=!0,listenEventHandler("online",function(){connection.isOnline=!0}),listenEventHandler("offline",function(){connection.isOnline=!1}),connection.isLowBandwidth=!1,navigator&&navigator.connection&&navigator.connection.type&&(connection.isLowBandwidth=navigator.connection.type.toString().toLowerCase().search(/wifi|cell/g)!==-1,connection.isLowBandwidth)){if(connection.bandwidth={audio:!1,video:!1,screen:!1},connection.mediaConstraints.audio&&connection.mediaConstraints.audio.optional&&connection.mediaConstraints.audio.optional.length){var newArray=[];connection.mediaConstraints.audio.optional.forEach(function(opt){"undefined"==typeof opt.bandwidth&&newArray.push(opt)}),connection.mediaConstraints.audio.optional=newArray}if(connection.mediaConstraints.video&&connection.mediaConstraints.video.optional&&connection.mediaConstraints.video.optional.length){var newArray=[];connection.mediaConstraints.video.optional.forEach(function(opt){"undefined"==typeof opt.bandwidth&&newArray.push(opt)}),connection.mediaConstraints.video.optional=newArray}}connection.getExtraData=function(remoteUserId){if(!remoteUserId)throw"remoteUserId is required.";return connection.peers[remoteUserId]?connection.peers[remoteUserId].extra:{}},forceOptions.autoOpenOrJoin&&connection.openOrJoin(connection.sessionid),connection.onUserIdAlreadyTaken=function(useridAlreadyTaken,yourNewUserId){connection.enableLogs&&console.warn("Userid already taken.",useridAlreadyTaken,"Your new userid:",yourNewUserId),connection.join(useridAlreadyTaken)},connection.onRoomFull=function(roomid){connection.enableLogs&&console.warn(roomid,"is full.")},connection.trickleIce=!0,connection.version="3.4.4",connection.onSettingLocalDescription=function(event){connection.enableLogs&&console.info("Set local description for remote user",event.userid)},connection.oneRoomAlreadyExist=function(roomid){connection.enableLogs&&console.info('Server says "Room ',roomid,"already exist. Joining instead."),connection.join(roomid)},connection.resetScreen=function(){sourceId=null,DetectRTC&&DetectRTC.screen&&delete DetectRTC.screen.sourceId,currentUserMediaRequest={streams:[],mutex:!1,queueRequests:[]}},connection.autoCreateMediaElement=!0,connection.password=null}(this)}; \ No newline at end of file diff --git a/Chrome-Extensions/tabCapture/manifest.json b/Chrome-Extensions/tabCapture/manifest.json index b9c16dda..f935f766 100644 --- a/Chrome-Extensions/tabCapture/manifest.json +++ b/Chrome-Extensions/tabCapture/manifest.json @@ -1,12 +1,12 @@ { "name" : "Tab Capturing & Sharing", "author": "Muaz Khan", - "version" : "1.6", + "version" : "2.0", "manifest_version" : 2, "description" : "Capture & Share any tab privately with multiple users!", - "homepage_url": "https://www.webrtc-experiment.com/", + "homepage_url": "https://github.com/muaz-khan/Chrome-Extensions/tree/master/tabCapture", "background": { - "scripts": ["websocket.js", "RTCMultiConnection.min.js", "tab-capturing.js"], + "scripts": ["socket.io.js", "RTCMultiConnection.min.js", "IceServersHandler.js", "CodecsHandler.js", "shareStreamUsingRTCMultiConnection.js", "tab-capturing.js"], "persistent": false }, "permissions": [ diff --git a/Chrome-Extensions/tabCapture/options.html b/Chrome-Extensions/tabCapture/options.html index ed47387a..e506408c 100644 --- a/Chrome-Extensions/tabCapture/options.html +++ b/Chrome-Extensions/tabCapture/options.html @@ -63,10 +63,6 @@

Set Your Own Room ID:


It will use your room-id instead of generating random string. -
- E.g. You can always share this with screen viewers: -
- https://www.webrtc-experiment.com/!/?s=your_room_id

diff --git a/Chrome-Extensions/tabCapture/shareStreamUsingRTCMultiConnection.js b/Chrome-Extensions/tabCapture/shareStreamUsingRTCMultiConnection.js new file mode 100644 index 00000000..fd48635a --- /dev/null +++ b/Chrome-Extensions/tabCapture/shareStreamUsingRTCMultiConnection.js @@ -0,0 +1,160 @@ +function shareStreamUsingRTCMultiConnection(stream) { + // www.RTCMultiConnection.org/docs/ + connection = new RTCMultiConnection(); + // connection.socketURL = 'https://rtcmulticonnection.herokuapp.com:443/'; + connection.socketURL = 'https://webrtcweb.com:9001/'; + connection.autoCloseEntireSession = true; + + // this must match the viewer page + connection.socketMessageEvent = 'desktopCapture'; + + connection.password = null; + if (room_password && room_password.length) { + connection.password = room_password; + } + + connection.enableLogs = false; + connection.session = { + audio: true, + video: true, + oneway: true + }; + + connection.optionalArgument = { + optional: [], + mandatory: {} + }; + + connection.channel = connection.sessionid = connection.userid; + + if (room_id && room_id.length) { + connection.channel = connection.sessionid = connection.userid = room_id; + } + + connection.autoReDialOnFailure = true; + connection.getExternalIceServers = false; + + connection.iceServers = IceServersHandler.getIceServers(); + + function setBandwidth(sdp, value) { + sdp = sdp.replace(/b=AS([^\r\n]+\r\n)/g, ''); + sdp = sdp.replace(/a=mid:video\r\n/g, 'a=mid:video\r\nb=AS:' + value + '\r\n'); + return sdp; + } + + connection.processSdp = function(sdp) { + if (bandwidth) { + try { + bandwidth = parseInt(bandwidth); + } catch (e) { + bandwidth = null; + } + + if (bandwidth && bandwidth != NaN && bandwidth != 'NaN' && typeof bandwidth == 'number') { + sdp = setBandwidth(sdp, bandwidth); + sdp = BandwidthHandler.setVideoBitrates(sdp, { + min: bandwidth, + max: bandwidth + }); + } + } + + if (!!codecs && codecs !== 'default') { + sdp = CodecsHandler.preferCodec(sdp, codecs); + } + return sdp; + }; + + // www.rtcmulticonnection.org/docs/sdpConstraints/ + connection.sdpConstraints.mandatory = { + OfferToReceiveAudio: false, + OfferToReceiveVideo: false + }; + + connection.onstream = connection.onstreamended = function(event) { + try { + event.mediaElement.pause(); + delete event.mediaElement; + } catch (e) {} + }; + + // www.RTCMultiConnection.org/docs/dontCaptureUserMedia/ + connection.dontCaptureUserMedia = true; + + // www.RTCMultiConnection.org/docs/attachStreams/ + connection.attachStreams.push(stream); + + var text = '-'; + (function looper() { + if (!connection) { + setBadgeText(''); + return; + } + + if (connection.isInitiator) { + setBadgeText('0'); + return; + } + + text += ' -'; + if (text.length > 6) { + text = '-'; + } + + setBadgeText(text); + setTimeout(looper, 500); + })(); + + // www.RTCMultiConnection.org/docs/open/ + connection.socketCustomEvent = connection.sessionid; + + function roomOpenCallback() { + chrome.browserAction.enable(); + setBadgeText(0); + + if (room_url_box === true) { + var resultingURL = 'https://webrtcweb.com/screen/?s=' + connection.sessionid; + // resultingURL = 'https://www.webrtc-experiment.com/screen/?s=' + connection.sessionid; + + // resultingURL = 'http://localhost:9001/?s=' + connection.sessionid; + + if (room_password && room_password.length) { + resultingURL += '&p=' + room_password; + } + + if (bandwidth) { + resultingURL += '&bandwidth=' + bandwidth; + } + if (!!codecs && codecs !== 'default') { + resultingURL += '&codecs=' + codecs; + } + + var popup_width = 600; + var popup_height = 170; + + chrome.windows.create({ + url: "data:text/html,Unique Room URL

Copy following private URL:

You can share this private-session URI with fellows using email or social networks.

", + type: 'popup', + width: popup_width, + height: popup_height, + top: parseInt((screen.height / 2) - (popup_height / 2)), + left: parseInt((screen.width / 2) - (popup_width / 2)), + focused: true + }, function(win) { + // popup_id = win.id; + }); + } + + connection.socket.on(connection.socketCustomEvent, function(message) { + if (message.receivedYourScreen) { + setBadgeText(connection.isInitiator ? connection.getAllParticipants().length : ''); + } + }); + } + + connection.open(connection.sessionid, roomOpenCallback); + + connection.onleave = connection.onPeerStateChanged = function() { + setBadgeText(connection.isInitiator ? connection.getAllParticipants().length : ''); + }; +} diff --git a/Chrome-Extensions/tabCapture/socket.io.js b/Chrome-Extensions/tabCapture/socket.io.js new file mode 100644 index 00000000..827407bd --- /dev/null +++ b/Chrome-Extensions/tabCapture/socket.io.js @@ -0,0 +1,8 @@ +/*! + * Socket.IO v2.1.0 + * (c) 2014-2018 Guillermo Rauch + * Released under the MIT License. + */ +!function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.io=e():t.io=e()}(this,function(){return function(t){function e(r){if(n[r])return n[r].exports;var o=n[r]={exports:{},id:r,loaded:!1};return t[r].call(o.exports,o,o.exports,e),o.loaded=!0,o.exports}var n={};return e.m=t,e.c=n,e.p="",e(0)}([function(t,e,n){"use strict";function r(t,e){"object"===("undefined"==typeof t?"undefined":o(t))&&(e=t,t=void 0),e=e||{};var n,r=i(t),s=r.source,p=r.id,h=r.path,f=u[p]&&h in u[p].nsps,l=e.forceNew||e["force new connection"]||!1===e.multiplex||f;return l?(c("ignoring socket cache for %s",s),n=a(s,e)):(u[p]||(c("new io instance for %s",s),u[p]=a(s,e)),n=u[p]),r.query&&!e.query&&(e.query=r.query),n.socket(r.path,e)}var o="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t},i=n(1),s=n(7),a=n(12),c=n(3)("socket.io-client");t.exports=e=r;var u=e.managers={};e.protocol=s.protocol,e.connect=r,e.Manager=n(12),e.Socket=n(37)},function(t,e,n){(function(e){"use strict";function r(t,n){var r=t;n=n||e.location,null==t&&(t=n.protocol+"//"+n.host),"string"==typeof t&&("/"===t.charAt(0)&&(t="/"===t.charAt(1)?n.protocol+t:n.host+t),/^(https?|wss?):\/\//.test(t)||(i("protocol-less url %s",t),t="undefined"!=typeof n?n.protocol+"//"+t:"https://"+t),i("parse %s",t),r=o(t)),r.port||(/^(http|ws)$/.test(r.protocol)?r.port="80":/^(http|ws)s$/.test(r.protocol)&&(r.port="443")),r.path=r.path||"/";var s=r.host.indexOf(":")!==-1,a=s?"["+r.host+"]":r.host;return r.id=r.protocol+"://"+a+":"+r.port,r.href=r.protocol+"://"+a+(n&&n.port===r.port?"":":"+r.port),r}var o=n(2),i=n(3)("socket.io-client:url");t.exports=r}).call(e,function(){return this}())},function(t,e){var n=/^(?:(?![^:@]+:[^:@\/]*@)(http|https|ws|wss):\/\/)?((?:(([^:@]*)(?::([^:@]*))?)?@)?((?:[a-f0-9]{0,4}:){2,7}[a-f0-9]{0,4}|[^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/,r=["source","protocol","authority","userInfo","user","password","host","port","relative","path","directory","file","query","anchor"];t.exports=function(t){var e=t,o=t.indexOf("["),i=t.indexOf("]");o!=-1&&i!=-1&&(t=t.substring(0,o)+t.substring(o,i).replace(/:/g,";")+t.substring(i,t.length));for(var s=n.exec(t||""),a={},c=14;c--;)a[r[c]]=s[c]||"";return o!=-1&&i!=-1&&(a.source=e,a.host=a.host.substring(1,a.host.length-1).replace(/;/g,":"),a.authority=a.authority.replace("[","").replace("]","").replace(/;/g,":"),a.ipv6uri=!0),a}},function(t,e,n){(function(r){function o(){return!("undefined"==typeof window||!window.process||"renderer"!==window.process.type)||("undefined"==typeof navigator||!navigator.userAgent||!navigator.userAgent.toLowerCase().match(/(edge|trident)\/(\d+)/))&&("undefined"!=typeof document&&document.documentElement&&document.documentElement.style&&document.documentElement.style.WebkitAppearance||"undefined"!=typeof window&&window.console&&(window.console.firebug||window.console.exception&&window.console.table)||"undefined"!=typeof navigator&&navigator.userAgent&&navigator.userAgent.toLowerCase().match(/firefox\/(\d+)/)&&parseInt(RegExp.$1,10)>=31||"undefined"!=typeof navigator&&navigator.userAgent&&navigator.userAgent.toLowerCase().match(/applewebkit\/(\d+)/))}function i(t){var n=this.useColors;if(t[0]=(n?"%c":"")+this.namespace+(n?" %c":" ")+t[0]+(n?"%c ":" ")+"+"+e.humanize(this.diff),n){var r="color: "+this.color;t.splice(1,0,r,"color: inherit");var o=0,i=0;t[0].replace(/%[a-zA-Z%]/g,function(t){"%%"!==t&&(o++,"%c"===t&&(i=o))}),t.splice(i,0,r)}}function s(){return"object"==typeof console&&console.log&&Function.prototype.apply.call(console.log,console,arguments)}function a(t){try{null==t?e.storage.removeItem("debug"):e.storage.debug=t}catch(n){}}function c(){var t;try{t=e.storage.debug}catch(n){}return!t&&"undefined"!=typeof r&&"env"in r&&(t=r.env.DEBUG),t}function u(){try{return window.localStorage}catch(t){}}e=t.exports=n(5),e.log=s,e.formatArgs=i,e.save=a,e.load=c,e.useColors=o,e.storage="undefined"!=typeof chrome&&"undefined"!=typeof chrome.storage?chrome.storage.local:u(),e.colors=["#0000CC","#0000FF","#0033CC","#0033FF","#0066CC","#0066FF","#0099CC","#0099FF","#00CC00","#00CC33","#00CC66","#00CC99","#00CCCC","#00CCFF","#3300CC","#3300FF","#3333CC","#3333FF","#3366CC","#3366FF","#3399CC","#3399FF","#33CC00","#33CC33","#33CC66","#33CC99","#33CCCC","#33CCFF","#6600CC","#6600FF","#6633CC","#6633FF","#66CC00","#66CC33","#9900CC","#9900FF","#9933CC","#9933FF","#99CC00","#99CC33","#CC0000","#CC0033","#CC0066","#CC0099","#CC00CC","#CC00FF","#CC3300","#CC3333","#CC3366","#CC3399","#CC33CC","#CC33FF","#CC6600","#CC6633","#CC9900","#CC9933","#CCCC00","#CCCC33","#FF0000","#FF0033","#FF0066","#FF0099","#FF00CC","#FF00FF","#FF3300","#FF3333","#FF3366","#FF3399","#FF33CC","#FF33FF","#FF6600","#FF6633","#FF9900","#FF9933","#FFCC00","#FFCC33"],e.formatters.j=function(t){try{return JSON.stringify(t)}catch(e){return"[UnexpectedJSONParseError]: "+e.message}},e.enable(c())}).call(e,n(4))},function(t,e){function n(){throw new Error("setTimeout has not been defined")}function r(){throw new Error("clearTimeout has not been defined")}function o(t){if(p===setTimeout)return setTimeout(t,0);if((p===n||!p)&&setTimeout)return p=setTimeout,setTimeout(t,0);try{return p(t,0)}catch(e){try{return p.call(null,t,0)}catch(e){return p.call(this,t,0)}}}function i(t){if(h===clearTimeout)return clearTimeout(t);if((h===r||!h)&&clearTimeout)return h=clearTimeout,clearTimeout(t);try{return h(t)}catch(e){try{return h.call(null,t)}catch(e){return h.call(this,t)}}}function s(){y&&l&&(y=!1,l.length?d=l.concat(d):m=-1,d.length&&a())}function a(){if(!y){var t=o(s);y=!0;for(var e=d.length;e;){for(l=d,d=[];++m1)for(var n=1;n100)){var e=/^((?:\d+)?\.?\d+) *(milliseconds?|msecs?|ms|seconds?|secs?|s|minutes?|mins?|m|hours?|hrs?|h|days?|d|years?|yrs?|y)?$/i.exec(t);if(e){var n=parseFloat(e[1]),r=(e[2]||"ms").toLowerCase();switch(r){case"years":case"year":case"yrs":case"yr":case"y":return n*p;case"days":case"day":case"d":return n*u;case"hours":case"hour":case"hrs":case"hr":case"h":return n*c;case"minutes":case"minute":case"mins":case"min":case"m":return n*a;case"seconds":case"second":case"secs":case"sec":case"s":return n*s;case"milliseconds":case"millisecond":case"msecs":case"msec":case"ms":return n;default:return}}}}function r(t){return t>=u?Math.round(t/u)+"d":t>=c?Math.round(t/c)+"h":t>=a?Math.round(t/a)+"m":t>=s?Math.round(t/s)+"s":t+"ms"}function o(t){return i(t,u,"day")||i(t,c,"hour")||i(t,a,"minute")||i(t,s,"second")||t+" ms"}function i(t,e,n){if(!(t0)return n(t);if("number"===i&&isNaN(t)===!1)return e["long"]?o(t):r(t);throw new Error("val is not a non-empty string or a valid number. val="+JSON.stringify(t))}},function(t,e,n){function r(){}function o(t){var n=""+t.type;if(e.BINARY_EVENT!==t.type&&e.BINARY_ACK!==t.type||(n+=t.attachments+"-"),t.nsp&&"/"!==t.nsp&&(n+=t.nsp+","),null!=t.id&&(n+=t.id),null!=t.data){var r=i(t.data);if(r===!1)return g;n+=r}return f("encoded %j as %s",t,n),n}function i(t){try{return JSON.stringify(t)}catch(e){return!1}}function s(t,e){function n(t){var n=d.deconstructPacket(t),r=o(n.packet),i=n.buffers;i.unshift(r),e(i)}d.removeBlobs(t,n)}function a(){this.reconstructor=null}function c(t){var n=0,r={type:Number(t.charAt(0))};if(null==e.types[r.type])return h("unknown packet type "+r.type);if(e.BINARY_EVENT===r.type||e.BINARY_ACK===r.type){for(var o="";"-"!==t.charAt(++n)&&(o+=t.charAt(n),n!=t.length););if(o!=Number(o)||"-"!==t.charAt(n))throw new Error("Illegal attachments");r.attachments=Number(o)}if("/"===t.charAt(n+1))for(r.nsp="";++n;){var i=t.charAt(n);if(","===i)break;if(r.nsp+=i,n===t.length)break}else r.nsp="/";var s=t.charAt(n+1);if(""!==s&&Number(s)==s){for(r.id="";++n;){var i=t.charAt(n);if(null==i||Number(i)!=i){--n;break}if(r.id+=t.charAt(n),n===t.length)break}r.id=Number(r.id)}if(t.charAt(++n)){var a=u(t.substr(n)),c=a!==!1&&(r.type===e.ERROR||y(a));if(!c)return h("invalid payload");r.data=a}return f("decoded %s as %j",t,r),r}function u(t){try{return JSON.parse(t)}catch(e){return!1}}function p(t){this.reconPack=t,this.buffers=[]}function h(t){return{type:e.ERROR,data:"parser error: "+t}}var f=n(3)("socket.io-parser"),l=n(8),d=n(9),y=n(10),m=n(11);e.protocol=4,e.types=["CONNECT","DISCONNECT","EVENT","ACK","ERROR","BINARY_EVENT","BINARY_ACK"],e.CONNECT=0,e.DISCONNECT=1,e.EVENT=2,e.ACK=3,e.ERROR=4,e.BINARY_EVENT=5,e.BINARY_ACK=6,e.Encoder=r,e.Decoder=a;var g=e.ERROR+'"encode error"';r.prototype.encode=function(t,n){if(f("encoding packet %j",t),e.BINARY_EVENT===t.type||e.BINARY_ACK===t.type)s(t,n);else{var r=o(t);n([r])}},l(a.prototype),a.prototype.add=function(t){var n;if("string"==typeof t)n=c(t),e.BINARY_EVENT===n.type||e.BINARY_ACK===n.type?(this.reconstructor=new p(n),0===this.reconstructor.reconPack.attachments&&this.emit("decoded",n)):this.emit("decoded",n);else{if(!m(t)&&!t.base64)throw new Error("Unknown type: "+t);if(!this.reconstructor)throw new Error("got binary data when not reconstructing a packet");n=this.reconstructor.takeBinaryData(t),n&&(this.reconstructor=null,this.emit("decoded",n))}},a.prototype.destroy=function(){this.reconstructor&&this.reconstructor.finishedReconstruction()},p.prototype.takeBinaryData=function(t){if(this.buffers.push(t),this.buffers.length===this.reconPack.attachments){var e=d.reconstructPacket(this.reconPack,this.buffers);return this.finishedReconstruction(),e}return null},p.prototype.finishedReconstruction=function(){this.reconPack=null,this.buffers=[]}},function(t,e,n){function r(t){if(t)return o(t)}function o(t){for(var e in r.prototype)t[e]=r.prototype[e];return t}t.exports=r,r.prototype.on=r.prototype.addEventListener=function(t,e){return this._callbacks=this._callbacks||{},(this._callbacks["$"+t]=this._callbacks["$"+t]||[]).push(e),this},r.prototype.once=function(t,e){function n(){this.off(t,n),e.apply(this,arguments)}return n.fn=e,this.on(t,n),this},r.prototype.off=r.prototype.removeListener=r.prototype.removeAllListeners=r.prototype.removeEventListener=function(t,e){if(this._callbacks=this._callbacks||{},0==arguments.length)return this._callbacks={},this;var n=this._callbacks["$"+t];if(!n)return this;if(1==arguments.length)return delete this._callbacks["$"+t],this;for(var r,o=0;o0&&!this.encoding){var t=this.packetBuffer.shift();this.packet(t)}},r.prototype.cleanup=function(){h("cleanup");for(var t=this.subs.length,e=0;e=this._reconnectionAttempts)h("reconnect failed"),this.backoff.reset(),this.emitAll("reconnect_failed"),this.reconnecting=!1;else{var e=this.backoff.duration();h("will wait %dms before reconnect attempt",e),this.reconnecting=!0;var n=setTimeout(function(){t.skipReconnect||(h("attempting reconnect"),t.emitAll("reconnect_attempt",t.backoff.attempts),t.emitAll("reconnecting",t.backoff.attempts),t.skipReconnect||t.open(function(e){e?(h("reconnect attempt error"),t.reconnecting=!1,t.reconnect(),t.emitAll("reconnect_error",e.data)):(h("reconnect success"),t.onreconnect())}))},e);this.subs.push({destroy:function(){clearTimeout(n)}})}},r.prototype.onreconnect=function(){var t=this.backoff.attempts;this.reconnecting=!1,this.backoff.reset(),this.updateSocketIds(),this.emitAll("reconnect",t)}},function(t,e,n){t.exports=n(14),t.exports.parser=n(21)},function(t,e,n){(function(e){function r(t,n){if(!(this instanceof r))return new r(t,n);n=n||{},t&&"object"==typeof t&&(n=t,t=null),t?(t=p(t),n.hostname=t.host,n.secure="https"===t.protocol||"wss"===t.protocol,n.port=t.port,t.query&&(n.query=t.query)):n.host&&(n.hostname=p(n.host).host),this.secure=null!=n.secure?n.secure:e.location&&"https:"===location.protocol,n.hostname&&!n.port&&(n.port=this.secure?"443":"80"),this.agent=n.agent||!1,this.hostname=n.hostname||(e.location?location.hostname:"localhost"),this.port=n.port||(e.location&&location.port?location.port:this.secure?443:80),this.query=n.query||{},"string"==typeof this.query&&(this.query=h.decode(this.query)),this.upgrade=!1!==n.upgrade,this.path=(n.path||"/engine.io").replace(/\/$/,"")+"/",this.forceJSONP=!!n.forceJSONP,this.jsonp=!1!==n.jsonp,this.forceBase64=!!n.forceBase64,this.enablesXDR=!!n.enablesXDR,this.timestampParam=n.timestampParam||"t",this.timestampRequests=n.timestampRequests,this.transports=n.transports||["polling","websocket"],this.transportOptions=n.transportOptions||{},this.readyState="",this.writeBuffer=[],this.prevBufferLen=0,this.policyPort=n.policyPort||843,this.rememberUpgrade=n.rememberUpgrade||!1,this.binaryType=null,this.onlyBinaryUpgrades=n.onlyBinaryUpgrades,this.perMessageDeflate=!1!==n.perMessageDeflate&&(n.perMessageDeflate||{}),!0===this.perMessageDeflate&&(this.perMessageDeflate={}),this.perMessageDeflate&&null==this.perMessageDeflate.threshold&&(this.perMessageDeflate.threshold=1024),this.pfx=n.pfx||null,this.key=n.key||null,this.passphrase=n.passphrase||null,this.cert=n.cert||null,this.ca=n.ca||null,this.ciphers=n.ciphers||null,this.rejectUnauthorized=void 0===n.rejectUnauthorized||n.rejectUnauthorized,this.forceNode=!!n.forceNode;var o="object"==typeof e&&e;o.global===o&&(n.extraHeaders&&Object.keys(n.extraHeaders).length>0&&(this.extraHeaders=n.extraHeaders),n.localAddress&&(this.localAddress=n.localAddress)),this.id=null,this.upgrades=null,this.pingInterval=null,this.pingTimeout=null,this.pingIntervalTimer=null,this.pingTimeoutTimer=null,this.open()}function o(t){var e={};for(var n in t)t.hasOwnProperty(n)&&(e[n]=t[n]);return e}var i=n(15),s=n(8),a=n(3)("engine.io-client:socket"),c=n(36),u=n(21),p=n(2),h=n(30);t.exports=r,r.priorWebsocketSuccess=!1,s(r.prototype),r.protocol=u.protocol,r.Socket=r,r.Transport=n(20),r.transports=n(15),r.parser=n(21),r.prototype.createTransport=function(t){a('creating transport "%s"',t);var e=o(this.query);e.EIO=u.protocol,e.transport=t;var n=this.transportOptions[t]||{};this.id&&(e.sid=this.id);var r=new i[t]({query:e,socket:this,agent:n.agent||this.agent,hostname:n.hostname||this.hostname,port:n.port||this.port,secure:n.secure||this.secure,path:n.path||this.path,forceJSONP:n.forceJSONP||this.forceJSONP,jsonp:n.jsonp||this.jsonp,forceBase64:n.forceBase64||this.forceBase64,enablesXDR:n.enablesXDR||this.enablesXDR,timestampRequests:n.timestampRequests||this.timestampRequests,timestampParam:n.timestampParam||this.timestampParam,policyPort:n.policyPort||this.policyPort,pfx:n.pfx||this.pfx,key:n.key||this.key,passphrase:n.passphrase||this.passphrase,cert:n.cert||this.cert,ca:n.ca||this.ca,ciphers:n.ciphers||this.ciphers,rejectUnauthorized:n.rejectUnauthorized||this.rejectUnauthorized,perMessageDeflate:n.perMessageDeflate||this.perMessageDeflate,extraHeaders:n.extraHeaders||this.extraHeaders,forceNode:n.forceNode||this.forceNode,localAddress:n.localAddress||this.localAddress,requestTimeout:n.requestTimeout||this.requestTimeout,protocols:n.protocols||void 0});return r},r.prototype.open=function(){var t;if(this.rememberUpgrade&&r.priorWebsocketSuccess&&this.transports.indexOf("websocket")!==-1)t="websocket";else{if(0===this.transports.length){var e=this;return void setTimeout(function(){e.emit("error","No transports available")},0)}t=this.transports[0]}this.readyState="opening";try{t=this.createTransport(t)}catch(n){return this.transports.shift(),void this.open()}t.open(),this.setTransport(t)},r.prototype.setTransport=function(t){a("setting transport %s",t.name);var e=this;this.transport&&(a("clearing existing transport %s",this.transport.name),this.transport.removeAllListeners()),this.transport=t,t.on("drain",function(){e.onDrain()}).on("packet",function(t){e.onPacket(t)}).on("error",function(t){e.onError(t)}).on("close",function(){e.onClose("transport close")})},r.prototype.probe=function(t){function e(){if(f.onlyBinaryUpgrades){var e=!this.supportsBinary&&f.transport.supportsBinary;h=h||e}h||(a('probe transport "%s" opened',t),p.send([{type:"ping",data:"probe"}]),p.once("packet",function(e){if(!h)if("pong"===e.type&&"probe"===e.data){if(a('probe transport "%s" pong',t),f.upgrading=!0,f.emit("upgrading",p),!p)return;r.priorWebsocketSuccess="websocket"===p.name,a('pausing current transport "%s"',f.transport.name),f.transport.pause(function(){h||"closed"!==f.readyState&&(a("changing transport and sending upgrade packet"),u(),f.setTransport(p),p.send([{type:"upgrade"}]),f.emit("upgrade",p),p=null,f.upgrading=!1,f.flush())})}else{a('probe transport "%s" failed',t);var n=new Error("probe error");n.transport=p.name,f.emit("upgradeError",n)}}))}function n(){h||(h=!0,u(),p.close(),p=null)}function o(e){var r=new Error("probe error: "+e);r.transport=p.name,n(),a('probe transport "%s" failed because of error: %s',t,e),f.emit("upgradeError",r)}function i(){o("transport closed")}function s(){o("socket closed")}function c(t){p&&t.name!==p.name&&(a('"%s" works - aborting "%s"',t.name,p.name),n())}function u(){p.removeListener("open",e),p.removeListener("error",o),p.removeListener("close",i),f.removeListener("close",s),f.removeListener("upgrading",c)}a('probing transport "%s"',t);var p=this.createTransport(t,{probe:1}),h=!1,f=this;r.priorWebsocketSuccess=!1,p.once("open",e),p.once("error",o),p.once("close",i),this.once("close",s),this.once("upgrading",c),p.open()},r.prototype.onOpen=function(){if(a("socket open"),this.readyState="open",r.priorWebsocketSuccess="websocket"===this.transport.name,this.emit("open"),this.flush(),"open"===this.readyState&&this.upgrade&&this.transport.pause){a("starting upgrade probes");for(var t=0,e=this.upgrades.length;t1?{type:b[o],data:t.substring(1)}:{type:b[o]}:w}var i=new Uint8Array(t),o=i[0],s=f(t,1);return k&&"blob"===n&&(s=new k([s])),{type:b[o],data:s}},e.decodeBase64Packet=function(t,e){var n=b[t.charAt(0)];if(!u)return{type:n,data:{base64:!0,data:t.substr(1)}};var r=u.decode(t.substr(1));return"blob"===e&&k&&(r=new k([r])),{type:n,data:r}},e.encodePayload=function(t,n,r){function o(t){return t.length+":"+t}function i(t,r){e.encodePacket(t,!!s&&n,!1,function(t){r(null,o(t))})}"function"==typeof n&&(r=n,n=null);var s=h(t);return n&&s?k&&!g?e.encodePayloadAsBlob(t,r):e.encodePayloadAsArrayBuffer(t,r):t.length?void c(t,i,function(t,e){return r(e.join(""))}):r("0:")},e.decodePayload=function(t,n,r){if("string"!=typeof t)return e.decodePayloadAsBinary(t,n,r);"function"==typeof n&&(r=n,n=null);var o;if(""===t)return r(w,0,1);for(var i,s,a="",c=0,u=t.length;c0;){for(var s=new Uint8Array(o),a=0===s[0],c="",u=1;255!==s[u];u++){if(c.length>310)return r(w,0,1);c+=s[u]}o=f(o,2+c.length),c=parseInt(c);var p=f(o,0,c);if(a)try{p=String.fromCharCode.apply(null,new Uint8Array(p))}catch(h){var l=new Uint8Array(p);p="";for(var u=0;ur&&(n=r),e>=r||e>=n||0===r)return new ArrayBuffer(0);for(var o=new Uint8Array(t),i=new Uint8Array(n-e),s=e,a=0;s=55296&&e<=56319&&o65535&&(e-=65536,o+=w(e>>>10&1023|55296),e=56320|1023&e),o+=w(e);return o}function c(t,e){if(t>=55296&&t<=57343){if(e)throw Error("Lone surrogate U+"+t.toString(16).toUpperCase()+" is not a scalar value");return!1}return!0}function u(t,e){return w(t>>e&63|128)}function p(t,e){if(0==(4294967168&t))return w(t);var n="";return 0==(4294965248&t)?n=w(t>>6&31|192):0==(4294901760&t)?(c(t,e)||(t=65533),n=w(t>>12&15|224),n+=u(t,6)):0==(4292870144&t)&&(n=w(t>>18&7|240),n+=u(t,12),n+=u(t,6)),n+=w(63&t|128)}function h(t,e){e=e||{};for(var n,r=!1!==e.strict,o=s(t),i=o.length,a=-1,c="";++a=v)throw Error("Invalid byte index");var t=255&g[b];if(b++,128==(192&t))return 63&t;throw Error("Invalid continuation byte")}function l(t){var e,n,r,o,i;if(b>v)throw Error("Invalid byte index");if(b==v)return!1;if(e=255&g[b],b++,0==(128&e))return e;if(192==(224&e)){if(n=f(),i=(31&e)<<6|n,i>=128)return i;throw Error("Invalid continuation byte")}if(224==(240&e)){if(n=f(),r=f(),i=(15&e)<<12|n<<6|r,i>=2048)return c(i,t)?i:65533;throw Error("Invalid continuation byte")}if(240==(248&e)&&(n=f(),r=f(),o=f(),i=(7&e)<<18|n<<12|r<<6|o,i>=65536&&i<=1114111))return i;throw Error("Invalid UTF-8 detected")}function d(t,e){e=e||{};var n=!1!==e.strict;g=s(t),v=g.length,b=0;for(var r,o=[];(r=l(n))!==!1;)o.push(r);return a(o)}var y="object"==typeof e&&e,m=("object"==typeof t&&t&&t.exports==y&&t,"object"==typeof o&&o);m.global!==m&&m.window!==m||(i=m);var g,v,b,w=String.fromCharCode,k={version:"2.1.2",encode:h,decode:d};r=function(){return k}.call(e,n,e,t),!(void 0!==r&&(t.exports=r))}(this)}).call(e,n(27)(t),function(){return this}())},function(t,e){t.exports=function(t){return t.webpackPolyfill||(t.deprecate=function(){},t.paths=[],t.children=[],t.webpackPolyfill=1),t}},function(t,e){!function(){"use strict";for(var t="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",n=new Uint8Array(256),r=0;r>2],i+=t[(3&r[n])<<4|r[n+1]>>4],i+=t[(15&r[n+1])<<2|r[n+2]>>6],i+=t[63&r[n+2]];return o%3===2?i=i.substring(0,i.length-1)+"=":o%3===1&&(i=i.substring(0,i.length-2)+"=="),i},e.decode=function(t){var e,r,o,i,s,a=.75*t.length,c=t.length,u=0;"="===t[t.length-1]&&(a--,"="===t[t.length-2]&&a--);var p=new ArrayBuffer(a),h=new Uint8Array(p);for(e=0;e>4,h[u++]=(15&o)<<4|i>>2,h[u++]=(3&i)<<6|63&s;return p}}()},function(t,e){(function(e){function n(t){for(var e=0;e0);return e}function r(t){var e=0;for(p=0;p';i=document.createElement(e)}catch(t){i=document.createElement("iframe"),i.name=o.iframeId,i.src="javascript:0"}i.id=o.iframeId,o.form.appendChild(i),o.iframe=i}var o=this;if(!this.form){var i,s=document.createElement("form"),a=document.createElement("textarea"),p=this.iframeId="eio_iframe_"+this.index;s.className="socketio",s.style.position="absolute",s.style.top="-1000px",s.style.left="-1000px",s.target=p,s.method="POST",s.setAttribute("accept-charset","utf-8"),a.name="d",s.appendChild(a),document.body.appendChild(s),this.form=s,this.area=a}this.form.action=this.uri(),r(),t=t.replace(u,"\\\n"),this.area.value=t.replace(c,"\\n");try{this.form.submit()}catch(h){}this.iframe.attachEvent?this.iframe.onreadystatechange=function(){"complete"===o.iframe.readyState&&n()}:this.iframe.onload=n}}).call(e,function(){return this}())},function(t,e,n){(function(e){function r(t){var e=t&&t.forceBase64;e&&(this.supportsBinary=!1),this.perMessageDeflate=t.perMessageDeflate,this.usingBrowserWebSocket=h&&!t.forceNode,this.protocols=t.protocols,this.usingBrowserWebSocket||(l=o),i.call(this,t)}var o,i=n(20),s=n(21),a=n(30),c=n(31),u=n(32),p=n(3)("engine.io-client:websocket"),h=e.WebSocket||e.MozWebSocket;if("undefined"==typeof window)try{o=n(35)}catch(f){}var l=h;l||"undefined"!=typeof window||(l=o),t.exports=r,c(r,i),r.prototype.name="websocket",r.prototype.supportsBinary=!0,r.prototype.doOpen=function(){if(this.check()){var t=this.uri(),e=this.protocols,n={agent:this.agent,perMessageDeflate:this.perMessageDeflate};n.pfx=this.pfx,n.key=this.key,n.passphrase=this.passphrase,n.cert=this.cert,n.ca=this.ca,n.ciphers=this.ciphers,n.rejectUnauthorized=this.rejectUnauthorized,this.extraHeaders&&(n.headers=this.extraHeaders),this.localAddress&&(n.localAddress=this.localAddress);try{this.ws=this.usingBrowserWebSocket?e?new l(t,e):new l(t):new l(t,e,n)}catch(r){return this.emit("error",r)}void 0===this.ws.binaryType&&(this.supportsBinary=!1),this.ws.supports&&this.ws.supports.binary?(this.supportsBinary=!0,this.ws.binaryType="nodebuffer"):this.ws.binaryType="arraybuffer",this.addEventListeners()}},r.prototype.addEventListeners=function(){var t=this;this.ws.onopen=function(){t.onOpen()},this.ws.onclose=function(){t.onClose()},this.ws.onmessage=function(e){t.onData(e.data)},this.ws.onerror=function(e){t.onError("websocket error",e)}},r.prototype.write=function(t){function n(){r.emit("flush"),setTimeout(function(){r.writable=!0,r.emit("drain")},0)}var r=this;this.writable=!1;for(var o=t.length,i=0,a=o;i0&&t.jitter<=1?t.jitter:0,this.attempts=0}t.exports=n,n.prototype.duration=function(){var t=this.ms*Math.pow(this.factor,this.attempts++);if(this.jitter){var e=Math.random(),n=Math.floor(e*this.jitter*t);t=0==(1&Math.floor(10*e))?t-n:t+n}return 0|Math.min(t,this.max)},n.prototype.reset=function(){this.attempts=0},n.prototype.setMin=function(t){this.ms=t},n.prototype.setMax=function(t){this.max=t},n.prototype.setJitter=function(t){this.jitter=t}}])}); +//# sourceMappingURL=socket.io.js.map \ No newline at end of file diff --git a/Chrome-Extensions/tabCapture/tab-capturing.js b/Chrome-Extensions/tabCapture/tab-capturing.js index 689bfd3b..b217223f 100644 --- a/Chrome-Extensions/tabCapture/tab-capturing.js +++ b/Chrome-Extensions/tabCapture/tab-capturing.js @@ -5,7 +5,6 @@ chrome.browserAction.onClicked.addListener(function() { if (connection && connection.attachStreams[0]) { setDefaults(); - connection.attachStreams[0].stop(); return; } @@ -18,22 +17,38 @@ chrome.browserAction.onClicked.addListener(function() { }); }); +chrome.browserAction.setIcon({ + path: 'images/tabCapture22.png' +}); + +chrome.browserAction.setTitle({ + title: 'Share this tab!' +}); + +chrome.browserAction.setBadgeText({ + text: '' +}); + var constraints; -var min_bandwidth = 512; -var max_bandwidth = 1048; +var min_bandwidth = 0; +var max_bandwidth = 0; var room_password = ''; var room_id = ''; +var room_url_box = true; +var bandwidth = min_bandwidth; +var codecs = 'default'; // h264, vp8, vp9 function captureTab() { chrome.storage.sync.get(null, function(items) { var resolutions = {}; if (items['min_bandwidth']) { - min_bandwidth = parseInt(items['min_bandwidth']); + // min_bandwidth = parseInt(items['min_bandwidth']); + // bandwidth = min_bandwidth; } if (items['max_bandwidth']) { - max_bandwidth = parseInt(items['max_bandwidth']); + // max_bandwidth = parseInt(items['max_bandwidth']); } if (items['room_password']) { @@ -47,18 +62,18 @@ function captureTab() { var _resolutions = items['resolutions']; if (!_resolutions) { resolutions = { - maxWidth: screen.width > 1920 ? screen.width : 1920, - maxHeight: screen.height > 1080 ? screen.height : 1080 + maxWidth: innerWidth, + maxHeight: innerHeight } chrome.storage.sync.set({ - resolutions: '1080p' + resolutions: 'fit-screen' }, function() {}); } if (_resolutions === 'fit-screen') { - resolutions.maxWidth = screen.width; - resolutions.maxHeight = screen.height; + resolutions.maxWidth = innerWidth; + resolutions.maxHeight = innerHeight; } if (_resolutions === '1080p') { @@ -77,18 +92,21 @@ function captureTab() { } constraints = { - audio: false, + audio: true, video: true, + audioConstraints: { + mandatory: { + chromeMediaSource: 'tab', + echoCancellation: true + } + }, videoConstraints: { mandatory: { chromeMediaSource: 'tab', maxWidth: resolutions.maxWidth, maxHeight: resolutions.maxHeight, minFrameRate: 30, - maxFrameRate: 64, - minAspectRatio: 1.77, - googLeakyBucket: true, - googTemporalLayeredScreencast: true + minAspectRatio: 1.77 } } }; @@ -114,10 +132,9 @@ function captureTab() { chrome.browserAction.disable(); - stream.onended = function() { + addStreamStopListener(stream, function() { setDefaults(); - chrome.runtime.reload(); - }; + }); // as it is reported that if you drag chrome screen's status-bar // and scroll up/down the screen-viewer page. @@ -135,21 +152,29 @@ function captureTab() { var background_page_id = win.id; setTimeout(function() { - chrome.windows.remove(background_page_id); + // chrome.windows.remove(background_page_id); }, 3000); }); - setupRTCMultiConnection(stream); + shareStreamUsingRTCMultiConnection(stream); chrome.browserAction.setIcon({ path: 'images/pause22.png' }); + + videoPlayer = document.createElement('video'); + videoPlayer.muted = true; + videoPlayer.volume = 0; + videoPlayer.autoplay = true; + videoPlayer.srcObject = stream; + videoPlayer.play(); } } // RTCMultiConnection - www.RTCMultiConnection.org var connection; var popup_id; +var videoPlayer; function setBadgeText(text) { /* @@ -167,219 +192,11 @@ function setBadgeText(text) { }); } -function setupRTCMultiConnection(stream) { - // www.RTCMultiConnection.org/docs/ - connection = new RTCMultiConnection(); - - connection.optionalArgument = { - optional: [{ - DtlsSrtpKeyAgreement: true - }, { - googImprovedWifiBwe: true - }, { - googScreencastMinBitrate: 300 - }, { - googIPv6: true - }, { - googDscp: true - }, { - googCpuUnderuseThreshold: 55 - }, { - googCpuOveruseThreshold: 85 - }, { - googSuspendBelowMinBitrate: true - }, { - googCpuOveruseDetection: true - }], - mandatory: {} - }; - - connection.channel = connection.sessionid = connection.userid; - - if (room_id && room_id.length) { - connection.channel = connection.sessionid = connection.userid = room_id; - } - - connection.autoReDialOnFailure = true; - connection.getExternalIceServers = false; - - setBandwidth(connection); - - // www.RTCMultiConnection.org/docs/session/ - connection.session = { - video: true, - oneway: true - }; - - // www.rtcmulticonnection.org/docs/sdpConstraints/ - connection.sdpConstraints.mandatory = { - OfferToReceiveAudio: false, - OfferToReceiveVideo: false - }; - - // www.RTCMultiConnection.org/docs/dontCaptureUserMedia/ - connection.dontCaptureUserMedia = true; - - // www.RTCMultiConnection.org/docs/attachStreams/ - connection.attachStreams.push(stream); - - if (room_password && room_password.length) { - connection.onRequest = function(request) { - if (request.extra.password !== room_password) { - connection.reject(request); - chrome.windows.create({ - url: "data:text/html,

A user tried to join your room with invalid password. His request is rejected. He tried password: " + request.extra.password + "

", - type: 'popup', - width: screen.width / 2, - height: 170 - }); - return; - } - - connection.accept(request); - }; - } - - // www.RTCMultiConnection.org/docs/openSignalingChannel/ - var onMessageCallbacks = {}; - var pub = 'pub-c-3c0fc243-9892-4858-aa38-1445e58b4ecb'; - var sub = 'sub-c-d0c386c6-7263-11e2-8b02-12313f022c90'; - - WebSocket = PUBNUB.ws; - var websocket = new WebSocket('wss://pubsub.pubnub.com/' + pub + '/' + sub + '/' + connection.channel); - - var connectedUsers = 0; - connection.ondisconnected = function() { - connectedUsers--; - setBadgeText(connectedUsers); - }; - - websocket.onmessage = function(e) { - data = JSON.parse(e.data); - - if (data === 'received-your-screen') { - connectedUsers++; - setBadgeText(connectedUsers); - } - - if (data.sender == connection.userid) return; - - if (onMessageCallbacks[data.channel]) { - onMessageCallbacks[data.channel](data.message); - }; - }; - - websocket.push = websocket.send; - websocket.send = function(data) { - data.sender = connection.userid; - websocket.push(JSON.stringify(data)); - }; - - // overriding "openSignalingChannel" method - connection.openSignalingChannel = function(config) { - var channel = config.channel || this.channel; - onMessageCallbacks[channel] = config.onmessage; - - if (config.onopen) setTimeout(config.onopen, 1000); - - // directly returning socket object using "return" statement - return { - send: function(message) { - websocket.send({ - sender: connection.userid, - channel: channel, - message: message - }); - }, - channel: channel - }; - }; - - websocket.onerror = function() { - if (connection && connection.numberOfConnectedUsers > 0) { - return; - } - - chrome.windows.create({ - url: "data:text/html,

Failed connecting the WebSockets server. Please click screen icon to try again.

", - type: 'popup', - width: screen.width / 2, - height: 170 - }); - - setDefaults(); - chrome.runtime.reload(); - }; - - websocket.onclose = function() { - if (connection && connection.numberOfConnectedUsers > 0) { - return; - } - - chrome.windows.create({ - url: "data:text/html,

WebSocket connection seems closed. It is not possible to share your screen without using a medium like WebSockets. Please click screen icon to share again.

", - type: 'popup', - width: screen.width / 2, - height: 150 - }); - - setDefaults(); - chrome.runtime.reload(); - }; - - websocket.onopen = function() { - chrome.browserAction.enable(); - - setBadgeText(0); - - console.info('WebSockets connection is opened.'); - - // www.RTCMultiConnection.org/docs/open/ - var sessionDescription = connection.open({ - dontTransmit: true - }); - - var resultingURL = 'https://www.webrtc-experiment.com/!/?s=' + connection.sessionid; - - if (room_password && room_password.length) { - resultingURL += '&p=' + room_password; - } - - var popup_width = 600; - var popup_height = 170; - - chrome.windows.create({ - url: "data:text/html,Unique Room URL

Copy following private URL:

You can share this private-session URI with fellows using email or social networks.

", - type: 'popup', - width: popup_width, - height: popup_height, - top: parseInt((screen.height / 2) - (popup_height / 2)), - left: parseInt((screen.width / 2) - (popup_width / 2)), - focused: true - }, function(win) { - popup_id = win.id; - }); - }; -} - function setDefaults() { - if (connection) { - connection.close(); - connection.attachStreams = []; - } - chrome.browserAction.setIcon({ path: 'images/tabCapture22.png' }); - if (popup_id) { - try { - chrome.windows.remove(popup_id); - } catch (e) {} - - popup_id = null; - } - chrome.browserAction.setTitle({ title: 'Share this tab!' }); @@ -387,79 +204,48 @@ function setDefaults() { chrome.browserAction.setBadgeText({ text: '' }); -} - -function setBandwidth(connection) { - // www.RTCMultiConnection.org/docs/bandwidth/ - connection.bandwidth = { - screen: min_bandwidth // 300kbps - }; - - connection.processSdp = function(sdp) { - sdp = setSendBandwidth(sdp); - return sdp; - }; - - function setSendBandwidth(sdp) { - var sdpLines = sdp.split('\r\n'); - - // VP8 - var vp8Index = findLine(sdpLines, 'a=rtpmap', 'VP8/90000'); - var vp8Payload; - if (vp8Index) { - vp8Payload = getCodecPayloadType(sdpLines[vp8Index]); - } - - var rtxIndex = findLine(sdpLines, 'a=rtpmap', 'rtx/90000'); - - var rtxPayload; - if (rtxIndex) { - rtxPayload = getCodecPayloadType(sdpLines[rtxIndex]); - } - - if (!rtxPayload) { - return sdp; - } - if (!vp8Payload) { - return sdp; + if (connection) { + try { + connection.close(); + connection.closeSocket(); + connection.attachStreams.forEach(function(stream) { + stream.getTracks().forEach(function(track) { + track.stop(); + }); + }); + connection.attachStreams = []; } + catch(e) {} - var rtxFmtpLineIndex = findLine(sdpLines, 'a=fmtp:' + rtxPayload.toString()); - if (rtxFmtpLineIndex !== null) { - var appendrtxNext = '\r\n'; - - if (max_bandwidth < min_bandwidth) { - max_bandwidth = min_bandwidth; - } - - appendrtxNext += 'a=fmtp:' + vp8Payload + ' x-google-min-bitrate=' + min_bandwidth + '; x-google-max-bitrate=' + max_bandwidth; - sdpLines[rtxFmtpLineIndex] = sdpLines[rtxFmtpLineIndex].concat(appendrtxNext); - sdp = sdpLines.join('\r\n'); - } - return sdp; + connection = null; } - function findLine(sdpLines, prefix, substr) { - return findLineInRange(sdpLines, 0, -1, prefix, substr); + if(videoPlayer) { + videoPlayer.srcObject = null; + videoPlayer = null; } - function findLineInRange(sdpLines, startLine, endLine, prefix, substr) { - var realEndLine = endLine !== -1 ? endLine : sdpLines.length; - for (var i = startLine; i < realEndLine; ++i) { - if (sdpLines[i].indexOf(prefix) === 0) { - if (!substr || - sdpLines[i].toLowerCase().indexOf(substr.toLowerCase()) !== -1) { - return i; - } - } - } - return null; - } + chrome.runtime.reload(); +} - function getCodecPayloadType(sdpLine) { - var pattern = new RegExp('a=rtpmap:(\\d+) \\w+\\/\\d+'); - var result = sdpLine.match(pattern); - return (result && result.length === 2) ? result[1] : null; - } +function addStreamStopListener(stream, callback) { + stream.addEventListener('ended', function() { + callback(); + callback = function() {}; + }, false); + stream.addEventListener('inactive', function() { + callback(); + callback = function() {}; + }, false); + stream.getTracks().forEach(function(track) { + track.addEventListener('ended', function() { + callback(); + callback = function() {}; + }, false); + track.addEventListener('inactive', function() { + callback(); + callback = function() {}; + }, false); + }); } diff --git a/Chrome-Extensions/tabCapture/websocket.js b/Chrome-Extensions/tabCapture/websocket.js deleted file mode 100644 index eb29cfb0..00000000 --- a/Chrome-Extensions/tabCapture/websocket.js +++ /dev/null @@ -1,96 +0,0 @@ -// Version: 3.6.7 -(function(){ -/* -CryptoJS v3.1.2 -code.google.com/p/crypto-js -(c) 2009-2013 by Jeff Mott. All rights reserved. -code.google.com/p/crypto-js/wiki/License -*/ -var CryptoJS=CryptoJS||function(h,s){var f={},g=f.lib={},q=function(){},m=g.Base={extend:function(a){q.prototype=this;var c=new q;a&&c.mixIn(a);c.hasOwnProperty("init")||(c.init=function(){c.$super.init.apply(this,arguments)});c.init.prototype=c;c.$super=this;return c},create:function(){var a=this.extend();a.init.apply(a,arguments);return a},init:function(){},mixIn:function(a){for(var c in a)a.hasOwnProperty(c)&&(this[c]=a[c]);a.hasOwnProperty("toString")&&(this.toString=a.toString)},clone:function(){return this.init.prototype.extend(this)}}, -r=g.WordArray=m.extend({init:function(a,c){a=this.words=a||[];this.sigBytes=c!=s?c:4*a.length},toString:function(a){return(a||k).stringify(this)},concat:function(a){var c=this.words,d=a.words,b=this.sigBytes;a=a.sigBytes;this.clamp();if(b%4)for(var e=0;e>>2]|=(d[e>>>2]>>>24-8*(e%4)&255)<<24-8*((b+e)%4);else if(65535>>2]=d[e>>>2];else c.push.apply(c,d);this.sigBytes+=a;return this},clamp:function(){var a=this.words,c=this.sigBytes;a[c>>>2]&=4294967295<< -32-8*(c%4);a.length=h.ceil(c/4)},clone:function(){var a=m.clone.call(this);a.words=this.words.slice(0);return a},random:function(a){for(var c=[],d=0;d>>2]>>>24-8*(b%4)&255;d.push((e>>>4).toString(16));d.push((e&15).toString(16))}return d.join("")},parse:function(a){for(var c=a.length,d=[],b=0;b>>3]|=parseInt(a.substr(b, -2),16)<<24-4*(b%8);return new r.init(d,c/2)}},n=l.Latin1={stringify:function(a){var c=a.words;a=a.sigBytes;for(var d=[],b=0;b>>2]>>>24-8*(b%4)&255));return d.join("")},parse:function(a){for(var c=a.length,d=[],b=0;b>>2]|=(a.charCodeAt(b)&255)<<24-8*(b%4);return new r.init(d,c)}},j=l.Utf8={stringify:function(a){try{return decodeURIComponent(escape(n.stringify(a)))}catch(c){throw Error("Malformed UTF-8 data");}},parse:function(a){return n.parse(unescape(encodeURIComponent(a)))}}, -u=g.BufferedBlockAlgorithm=m.extend({reset:function(){this._data=new r.init;this._nDataBytes=0},_append:function(a){"string"==typeof a&&(a=j.parse(a));this._data.concat(a);this._nDataBytes+=a.sigBytes},_process:function(a){var c=this._data,d=c.words,b=c.sigBytes,e=this.blockSize,f=b/(4*e),f=a?h.ceil(f):h.max((f|0)-this._minBufferSize,0);a=f*e;b=h.min(4*a,b);if(a){for(var g=0;gn;){var j;a:{j=k;for(var u=h.sqrt(j),t=2;t<=u;t++)if(!(j%t)){j=!1;break a}j=!0}j&&(8>n&&(m[n]=l(h.pow(k,0.5))),r[n]=l(h.pow(k,1/3)),n++);k++}var a=[],f=f.SHA256=q.extend({_doReset:function(){this._hash=new g.init(m.slice(0))},_doProcessBlock:function(c,d){for(var b=this._hash.words,e=b[0],f=b[1],g=b[2],j=b[3],h=b[4],m=b[5],n=b[6],q=b[7],p=0;64>p;p++){if(16>p)a[p]= -c[d+p]|0;else{var k=a[p-15],l=a[p-2];a[p]=((k<<25|k>>>7)^(k<<14|k>>>18)^k>>>3)+a[p-7]+((l<<15|l>>>17)^(l<<13|l>>>19)^l>>>10)+a[p-16]}k=q+((h<<26|h>>>6)^(h<<21|h>>>11)^(h<<7|h>>>25))+(h&m^~h&n)+r[p]+a[p];l=((e<<30|e>>>2)^(e<<19|e>>>13)^(e<<10|e>>>22))+(e&f^e&g^f&g);q=n;n=m;m=h;h=j+k|0;j=g;g=f;f=e;e=k+l|0}b[0]=b[0]+e|0;b[1]=b[1]+f|0;b[2]=b[2]+g|0;b[3]=b[3]+j|0;b[4]=b[4]+h|0;b[5]=b[5]+m|0;b[6]=b[6]+n|0;b[7]=b[7]+q|0},_doFinalize:function(){var a=this._data,d=a.words,b=8*this._nDataBytes,e=8*a.sigBytes; -d[e>>>5]|=128<<24-e%32;d[(e+64>>>9<<4)+14]=h.floor(b/4294967296);d[(e+64>>>9<<4)+15]=b;a.sigBytes=4*d.length;this._process();return this._hash},clone:function(){var a=q.clone.call(this);a._hash=this._hash.clone();return a}});s.SHA256=q._createHelper(f);s.HmacSHA256=q._createHmacHelper(f)})(Math); -(function(){var h=CryptoJS,s=h.enc.Utf8;h.algo.HMAC=h.lib.Base.extend({init:function(f,g){f=this._hasher=new f.init;"string"==typeof g&&(g=s.parse(g));var h=f.blockSize,m=4*h;g.sigBytes>m&&(g=f.finalize(g));g.clamp();for(var r=this._oKey=g.clone(),l=this._iKey=g.clone(),k=r.words,n=l.words,j=0;j>>2]>>>24-8*(a%4)&255)<<16|(e[a+1>>>2]>>>24-8*((a+1)%4)&255)<<8|e[a+2>>>2]>>>24-8*((a+2)%4)&255,g=0;4>g&&a+0.75*g>>6*(3-g)&63));if(e=c.charAt(64))for(;b.length%4;)b.push(e);return b.join("")},parse:function(b){var e=b.length,f=this._map,c=f.charAt(64);c&&(c=b.indexOf(c),-1!=c&&(e=c));for(var c=[],a=0,d=0;d< -e;d++)if(d%4){var g=f.indexOf(b.charAt(d-1))<<2*(d%4),h=f.indexOf(b.charAt(d))>>>6-2*(d%4);c[a>>>2]|=(g|h)<<24-8*(a%4);a++}return j.create(c,a)},_map:"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="}})(); -var v=void 0,y=!0,z=null,A=!1;function C(){return function(){}} -window.JSON&&window.JSON.stringify||function(){function a(){try{return this.valueOf()}catch(a){return z}}function d(a){c.lastIndex=0;return c.test(a)?'"'+a.replace(c,function(a){var b=q[a];return"string"===typeof b?b:"\\u"+("0000"+a.charCodeAt(0).toString(16)).slice(-4)})+'"':'"'+a+'"'}function b(c,q){var t,r,g,j,h,l=f,e=q[c];e&&"object"===typeof e&&(e=a.call(e));"function"===typeof m&&(e=m.call(q,c,e));switch(typeof e){case "string":return d(e);case "number":return isFinite(e)?String(e):"null";case "boolean":case "null":return String(e); -case "object":if(!e)return"null";f+=p;h=[];if("[object Array]"===Object.prototype.toString.apply(e)){j=e.length;for(t=0;t++na?na:na=1))||a}; -function qa(a,d){var b=a.join(ia),c=[];if(!d)return b;N(d,function(a,b){var d="object"==typeof b?JSON.stringify(b):b;"undefined"!=typeof b&&(b!=z&&0G()?(clearTimeout(c),c=setTimeout(b,d)):(f=G(),a())}var c,f=0;return b}function sa(a,d){var b=[];N(a||[],function(a){d(a)&&b.push(a)});return b}function ta(a,d){return a.replace(ka,function(a,c){return d[c]||a})} -function pa(a){var d="xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,function(a){var c=16*Math.random()|0;return("x"==a?c:c&3|8).toString(16)});a&&a(d);return d}function N(a,d){if(a&&d)if(a&&(Array.isArray&&Array.isArray(a)||"number"===typeof a.length))for(var b=0,c=a.length;ba.search("-pnpres")&&f.f&&b.push(a):f.f&&b.push(a)});return b.sort()}function wa(){setTimeout(function(){fa||(fa=1,N(ga,function(a){a()}))},D)}var O,T=14,U=8,xa=A;function ya(a,d){var b="",c,f;if(d){c=a[15];if(16f;f++)b+=String.fromCharCode(a[f]);return b} -function za(a,d){var b=[],c;if(!d)try{a=unescape(encodeURIComponent(a))}catch(f){throw"Error on UTF-8 encode";}for(c=0;cx.length&&(r=16-x.length,t=[r,r,r,r,r,r,r,r,r,r,r,r,r,r,r,r]);for(r=0;rc;c++)b[c]=d[a[c]];return b}function Na(a){var d=[],b=xa?[0,13,10,7,4,1,14,11,8,5,2,15,12,9,6,3]:[0,5,10,15,4,9,14,3,8,13,2,7,12,1,6,11],c;for(c=0;16>c;c++)d[c]=a[b[c]];return d} -function Oa(a){var d=[],b;if(xa)for(b=0;4>b;b++)d[4*b]=Ta[a[4*b]]^Ua[a[1+4*b]]^Za[a[2+4*b]]^$a[a[3+4*b]],d[1+4*b]=$a[a[4*b]]^Ta[a[1+4*b]]^Ua[a[2+4*b]]^Za[a[3+4*b]],d[2+4*b]=Za[a[4*b]]^$a[a[1+4*b]]^Ta[a[2+4*b]]^Ua[a[3+4*b]],d[3+4*b]=Ua[a[4*b]]^Za[a[1+4*b]]^$a[a[2+4*b]]^Ta[a[3+4*b]];else for(b=0;4>b;b++)d[4*b]=ab[a[4*b]]^bb[a[1+4*b]]^a[2+4*b]^a[3+4*b],d[1+4*b]=a[4*b]^ab[a[1+4*b]]^bb[a[2+4*b]]^a[3+4*b],d[2+4*b]=a[4*b]^a[1+4*b]^ab[a[2+4*b]]^bb[a[3+4*b]],d[3+4*b]=bb[a[4*b]]^a[1+4*b]^a[2+4*b]^ab[a[3+4* -b]];return d}function La(a,d,b){var c=[],f;for(f=0;16>f;f++)c[f]=a[f]^d[b][f];return c}function Da(a,d){var b=[],c;for(c=0;16>c;c++)b[c]=a[c]^d[c];return b} -function Ca(a){var d=[],b=[],c,f,p=[];for(c=0;ca;a++)b[a]=d[c-1][a];if(0===c%U){a=b[0];f=v;for(f=0;4>f;f++)b[f]=b[f+1];b[3]=a;b=cb(b);b[0]^=db[c/U-1]}else 6a;a++)d[c][a]=d[c-U][a]^b[a]}for(c=0;cb;b++)p[c].push(d[4*c+b][0],d[4*c+b][1],d[4*c+b][2],d[4*c+b][3])}return p}function cb(a){for(var d=0;4>d;d++)a[d]=Qa[a[d]];return a} -function eb(a,d){var b=[];for(i=0;ib;b++){for(var c=a,f=b,p=v,q=v,p=q=0;8>p;p++)q=1==(f&1)?q^c:q,c=127>>=1;d[b]=q}return d} -var Qa=eb("637c777bf26b6fc53001672bfed7ab76ca82c97dfa5947f0add4a2af9ca472c0b7fd9326363ff7cc34a5e5f171d8311504c723c31896059a071280e2eb27b27509832c1a1b6e5aa0523bd6b329e32f8453d100ed20fcb15b6acbbe394a4c58cfd0efaafb434d338545f9027f503c9fa851a3408f929d38f5bcb6da2110fff3d2cd0c13ec5f974417c4a77e3d645d197360814fdc222a908846eeb814de5e0bdbe0323a0a4906245cc2d3ac629195e479e7c8376d8dd54ea96c56f4ea657aae08ba78252e1ca6b4c6e8dd741f4bbd8b8a703eb5664803f60e613557b986c11d9ee1f8981169d98e949b1e87e9ce5528df8ca1890dbfe6426841992d0fb054bb16",2), -Pa,gb=Qa,hb=[];for(i=0;i>2],b+=kb[(d[c]&3)<<4|d[c+1]>>4],b=d[c+1]!==v?b+kb[(d[c+1]&15)<<2|d[c+2]>>6]:b+"=",b=d[c+2]!==v?b+kb[d[c+2]&63]:b+"=";a=b.slice(0,64);for(c=1;c>4,c[1]=(b[1]&15)<<4|b[2]>>2,c[2]=(b[2]&3)<<6|b[3],d.push(c[0],c[1],c[2]);return d=d.slice(0,d.length-d.length%16)}}; -O={size:function(a){switch(a){case 128:T=10;U=4;break;case 192:T=12;U=6;break;case 256:T=14;U=8;break;default:throw"Invalid Key Size Specified:"+a;}},h2a:function(a){var d=[];a.replace(/(..)/g,function(a){d.push(parseInt(a,16))});return d},expandKey:Ca,encryptBlock:Ea,decryptBlock:Ga,Decrypt:xa,s2a:za,rawEncrypt:Ba,rawDecrypt:Fa,dec:function(a,d,b){var a=ib.q(a),c=a.slice(8,16),c=Aa(za(d,b),c),d=c.key,c=c.i,a=a.slice(16,a.length);return a=Fa(a,d,c,b)},openSSLKey:Aa,a2h:function(a){var d="",b;for(b= -0;ba[b]?"0":"")+a[b].toString(16);return d},enc:function(a,d,b){var c;c=[];var f;for(f=0;8>f;f++)c=c.concat(Math.floor(256*Math.random()));f=Aa(za(d,b),c);d=f.key;f=f.i;c=[[83,97,108,116,101,100,95,95].concat(c)];a=za(a,b);a=Ba(a,d,f);a=c.concat(a);return ib.s(a)},Hash:{MD5:function(a){function d(a,b){var c,d,f,e,g;f=a&2147483648;e=b&2147483648;c=a&1073741824;d=b&1073741824;g=(a&1073741823)+(b&1073741823);return c&d?g^2147483648^f^e:c|d?g&1073741824?g^3221225472^f^e:g^1073741824^ -f^e:g^f^e}function b(a,b,c,f,e,g,l){a=d(a,d(d(b&c|~b&f,e),l));return d(a<>>32-g,b)}function c(a,b,c,f,e,g,l){a=d(a,d(d(b&f|c&~f,e),l));return d(a<>>32-g,b)}function f(a,b,c,f,e,g,l){a=d(a,d(d(b^c^f,e),l));return d(a<>>32-g,b)}function p(a,b,c,f,g,e,l){a=d(a,d(d(c^(b|~f),g),l));return d(a<>>32-e,b)}function q(a){var b,c,d=[];for(c=0;3>=c;c++)b=a>>>8*c&255,d=d.concat(b);return d}var m=[],u,x,t,r,g,j,h,l,e=eb("67452301efcdab8998badcfe10325476d76aa478e8c7b756242070dbc1bdceeef57c0faf4787c62aa8304613fd469501698098d88b44f7afffff5bb1895cd7be6b901122fd987193a679438e49b40821f61e2562c040b340265e5a51e9b6c7aad62f105d02441453d8a1e681e7d3fbc821e1cde6c33707d6f4d50d87455a14eda9e3e905fcefa3f8676f02d98d2a4c8afffa39428771f6816d9d6122fde5380ca4beea444bdecfa9f6bb4b60bebfbc70289b7ec6eaa127fad4ef308504881d05d9d4d039e6db99e51fa27cf8c4ac5665f4292244432aff97ab9423a7fc93a039655b59c38f0ccc92ffeff47d85845dd16fa87e4ffe2ce6e0a30143144e0811a1f7537e82bd3af2352ad7d2bbeb86d391", -8),m=a.length;u=m+8;x=16*((u-u%64)/64+1);t=[];for(g=r=0;g>>29;m=t;g=e[0];j=e[1];h=e[2];l=e[3];for(a=0;aJ||!va(B,y).length?Wa=A:(Wa=y,I.presence_heartbeat({callback:function(){W=setTimeout(r,J*D)},error:function(a){s&&s("Presence Heartbeat unable to reach Pubnub servers."+JSON.stringify(a));W=setTimeout(r,J*D)}}))}function g(a,b){return ba.decrypt(a,b||X)||ba.decrypt(a,X)||a}function j(a,b,c){var d=A;if("number"===typeof a)d=5 5 or x = 0). Current Value : "+(b||5)),b|| -5):a}function h(a){var b="",c=[];N(a,function(a){c.push(a)});var d=c.sort(),f;for(f in d){var e=d[f],b=b+(e+"="+encodeURIComponent(a[e]));f!=d.length-1&&(b+="&")}return b}function l(a){a||(a={});N(Y,function(b,c){b in a||(a[b]=c)});return a}function e(a){return Jb(a)}function aa(a){function b(a,c){var d=(a&65535)+(c&65535);return(a>>16)+(c>>16)+(d>>16)<<16|d&65535}function c(a,b){return a>>>b|a<<32-b}var d;d=a.replace(/\r\n/g,"\n");for(var a="",f=0;fe?a+=String.fromCharCode(e): -(127e?a+=String.fromCharCode(e>>6|192):(a+=String.fromCharCode(e>>12|224),a+=String.fromCharCode(e>>6&63|128)),a+=String.fromCharCode(e&63|128))}f=a;d=[];for(e=0;e<8*f.length;e+=8)d[e>>5]|=(f.charCodeAt(e/8)&255)<<24-e%32;var g=8*a.length,f=[1116352408,1899447441,3049323471,3921009573,961987163,1508970993,2453635748,2870763221,3624381080,310598401,607225278,1426881987,1925078388,2162078206,2614888103,3248222580,3835390401,4022224774,264347078,604807628,770255983,1249150122,1555081692,1996064986, -2554220882,2821834349,2952996808,3210313671,3336571891,3584528711,113926993,338241895,666307205,773529912,1294757372,1396182291,1695183700,1986661051,2177026350,2456956037,2730485921,2820302411,3259730800,3345764771,3516065817,3600352804,4094571909,275423344,430227734,506948616,659060556,883997877,958139571,1322822218,1537002063,1747873779,1955562222,2024104815,2227730452,2361852424,2428436474,2756734187,3204031479,3329325298],a=[1779033703,3144134277,1013904242,2773480762,1359893119,2600822924,528734635, -1541459225],e=Array(64),l,m,j,h,q,p,r,t,s,w,u;d[g>>5]|=128<<24-g%32;d[(g+64>>9<<4)+15]=g;for(t=0;ts;s++)e[s]=16>s?d[s+t]:b(b(b(c(e[s-2],17)^c(e[s-2],19)^e[s-2]>>>10,e[s-7]),c(e[s-15],7)^c(e[s-15],18)^e[s-15]>>>3),e[s-16]),w=b(b(b(b(r,c(h,6)^c(h,11)^c(h,25)),h&q^~h&p),f[s]),e[s]),u=b(c(g,2)^c(g,13)^c(g,22),g&l^g&m^l&m),r=p,p=q,q=h,h=b(j,w),j=m,m=l,l=g,g=b(w,u);a[0]=b(g,a[0]);a[1]=b(l,a[1]);a[2]=b(m,a[2]);a[3]=b(j,a[3]); -a[4]=b(h,a[4]);a[5]=b(q,a[5]);a[6]=b(p,a[6]);a[7]=b(r,a[7])}d="";for(f=0;f<4*a.length;f++)d+="0123456789abcdef".charAt(a[f>>2]>>8*(3-f%4)+4&15)+"0123456789abcdef".charAt(a[f>>2]>>8*(3-f%4)&15);return d}a.jsonp&&(ub=0);var H=a.subscribe_key||"";a.uuid||Eb.get(H+"uuid");var Ra=a.leave_on_unload||0;a.xdr=Ab;a.db=Eb;a.error=a.error||nb;a._is_online=Cb;a.jsonp_cb=zb;a.hmac_SHA256=lb;O.size(256);var S=O.s2a("0123456789012345");a.crypto_obj={encrypt:function(a,b){if(!b)return a;var c=O.s2a(aa(b).slice(0, -32)),d=O.s2a(JSON.stringify(a)),c=O.rawEncrypt(d,c,S);return O.Base64.encode(c)||a},decrypt:function(a,b){if(!b)return a;var c=O.s2a(aa(b).slice(0,32));try{var d=O.Base64.decode(a),e=O.rawDecrypt(d,c,S,A);return JSON.parse(e)}catch(f){}}};a.params={pnsdk:"PubNub-JS-Web/3.6.7"};var Sa=+a.windowing||10,Hb=(+a.timeout||310)*D,ob=(+a.keepalive||60)*D,Lb=a.noleave||0,P=a.publish_key||"demo",w=a.subscribe_key||"demo",M=a.auth_key||"",Ia=a.secret_key||"",vb=a.hmac_SHA256,Xa=a.ssl?"s":"",oa="http"+Xa+"://"+ -(a.origin||"pubsub.pubnub.com"),K=ma(oa),wb=ma(oa),Q=[],Va=0,xb=0,yb=0,Ha=0,Ja=a.restore||0,da=0,Ya=A,B={},Z={},W=z,R=j(a.heartbeat||a.pnexpires||0,a.error),J=a.heartbeat_interval||R-3,Wa=A,Ob=a.no_wait_for_pending,Pb=a["compatible_3.5"]||A,E=a.xdr,Y=a.params||{},s=a.error||C(),Nb=a._is_online||function(){return 1},L=a.jsonp_cb||function(){return 0},ea=a.db||{get:C(),set:C()},X=a.cipher_key,F=a.uuid||ea&&ea.get(w+"uuid")||"",ba=a.crypto_obj||{encrypt:function(a){return a},decrypt:function(a){return a}}, -I={LEAVE:function(a,b,c,d){var e={uuid:F,auth:M},f=ma(oa),c=c||C(),g=d||C(),d=L();if(0R&&(d.heartbeat=R);"0"!=a&&(d.callback=a);var e=E,d=l(d),f=5*D,g=K,h=w,j=va(B,y).join(",");e({a:a,data:d,timeout:f,url:[g,"v2","presence","sub-key",h,"channel",encodeURIComponent(j),"heartbeat"],c:function(a){m(a,b,c)},b:function(a){q(a,c)}})},xdr:E,ready:wa,db:ea,uuid:pa,map:ua,each:N,"each-channel":u,grep:sa,offline:function(){c(1,{message:"Offline. Please check your network settings."})}, -supplant:ta,now:G,unique:la,updater:ra};F||(F=I.uuid());ea.set(w+"uuid",F);setTimeout(p,D);setTimeout(f,ob);W=setTimeout(t,(J-3)*D);b();var H=I,Ka;for(Ka in H)H.hasOwnProperty(Ka)&&(e[Ka]=H[Ka]);e.css=sb;e.$=mb;e.create=tb;e.bind=qb;e.head=rb;e.search=pb;e.attr=V;e.events=Gb;e.init=e;e.secure=e;qb("beforeunload",window,function(){if(Ra)e["each-channel"](function(a){e.LEAVE(a.name,0)});return y});if(a.notest)return e;qb("offline",window,e.offline);qb("offline",document,e.offline);return e};Jb.init= -Jb;Jb.secure=Jb;"complete"===document.readyState?setTimeout(wa,0):qb("load",window,function(){setTimeout(wa,0)});var Kb=Ib||{};PUBNUB=Jb({notest:1,publish_key:V(Kb,"pub-key"),subscribe_key:V(Kb,"sub-key"),ssl:!document.location.href.indexOf("https")||"on"==V(Kb,"ssl"),origin:V(Kb,"origin"),uuid:V(Kb,"uuid")});window.jQuery&&(window.jQuery.PUBNUB=Jb);"undefined"!==typeof module&&(module.exports=PUBNUB)&&wa();var Db=mb("pubnubs")||0;if(Ib){sb(Ib,{position:"absolute",top:-D});if("opera"in window||V(Ib, -"flash"))Ib.innerHTML="";PUBNUB.rdx=function(a,d){if(!d)return $[a].onerror();$[a].responseText=unescape(d);$[a].onload()};$.id=D}} -var Mb=PUBNUB.ws=function(a,d){if(!(this instanceof Mb))return new Mb(a,d);var b=this,a=b.url=a||"";b.protocol=d||"Sec-WebSocket-Protocol";var c=a.split("/"),c={ssl:"wss:"===c[0],origin:c[2],publish_key:c[3],subscribe_key:c[4],channel:c[5]};b.CONNECTING=0;b.OPEN=1;b.CLOSING=2;b.CLOSED=3;b.CLOSE_NORMAL=1E3;b.CLOSE_GOING_AWAY=1001;b.CLOSE_PROTOCOL_ERROR=1002;b.CLOSE_UNSUPPORTED=1003;b.CLOSE_TOO_LARGE=1004;b.CLOSE_NO_STATUS=1005;b.CLOSE_ABNORMAL=1006;b.onclose=b.onerror=b.onmessage=b.onopen=b.onsend= -C();b.binaryType="";b.extensions="";b.bufferedAmount=0;b.trasnmitting=A;b.buffer=[];b.readyState=b.CONNECTING;if(!a)return b.readyState=b.CLOSED,b.onclose({code:b.CLOSE_ABNORMAL,reason:"Missing URL",wasClean:y}),b;b.e=PUBNUB.init(c);b.e.k=c;b.k=c;b.e.subscribe({restore:A,channel:c.channel,disconnect:b.onerror,reconnect:b.onopen,error:function(){b.onclose({code:b.CLOSE_ABNORMAL,reason:"Missing URL",wasClean:A})},callback:function(a){b.onmessage({data:a})},connect:function(){b.readyState=b.OPEN;b.onopen()}})}; -Mb.prototype.send=function(a){var d=this;d.e.publish({channel:d.e.k.channel,message:a,callback:function(a){d.onsend({data:a})}})}; -})(); diff --git a/DetectRTC/.npmignore b/DetectRTC/.npmignore index 230db982..91ea7329 100644 --- a/DetectRTC/.npmignore +++ b/DetectRTC/.npmignore @@ -6,7 +6,6 @@ !DetectRTC.min.js !index.html !package.json -!bower.json !npm-test.js !server.js -!README.md \ No newline at end of file +!README.md diff --git a/DetectRTC/DetectRTC.js b/DetectRTC/DetectRTC.js index 742a31c2..9bde0789 100644 --- a/DetectRTC/DetectRTC.js +++ b/DetectRTC/DetectRTC.js @@ -1,9 +1,9 @@ 'use strict'; -// Last Updated On: 2018-05-05 12:25:07 PM UTC +// Last Updated On: 2018-12-12 7:52:57 AM UTC // ________________ -// DetectRTC v1.3.6 +// DetectRTC v1.3.8 // Open-Sourced: https://github.com/muaz-khan/DetectRTC @@ -96,6 +96,12 @@ var majorVersion = parseInt(navigator.appVersion, 10); var nameOffset, verOffset, ix; + // both and safri and chrome has same userAgent + if (isSafari && !isChrome && nAgt.indexOf('CriOS') !== -1) { + isSafari = false; + isChrome = true; + } + // In Opera, the true version is after 'Opera' or after 'Version' if (isOpera) { browserName = 'Opera'; @@ -510,17 +516,29 @@ } }); + const regexIpv4Local = /^(192\.168\.|169\.254\.|10\.|172\.(1[6-9]|2\d|3[01]))/, + regexIpv4 = /([0-9]{1,3}(\.[0-9]{1,3}){3})/, + regexIpv6 = /[a-f0-9]{1,4}(:[a-f0-9]{1,4}){7}/; + // via: https://github.com/diafygi/webrtc-ips function DetectLocalIPAddress(callback, stream) { if (!DetectRTC.isWebRTCSupported) { return; } + var isPublic = true, + isIpv4 = true; getIPs(function(ip) { - if (ip.match(/^(192\.168\.|169\.254\.|10\.|172\.(1[6-9]|2\d|3[01]))/)) { - callback('Local: ' + ip); + if (!ip) { + callback(); // Pass nothing to tell that ICE-gathering-ended + } else if (ip.match(regexIpv4Local)) { + isPublic = false; + callback('Local: ' + ip, isPublic, isIpv4); + } else if (ip.match(regexIpv6)) { //via https://ourcodeworld.com/articles/read/257/how-to-get-the-client-ip-address-with-javascript-only + isIpv4 = false; + callback('Public: ' + ip, isPublic, isIpv4); } else { - callback('Public: ' + ip); + callback('Public: ' + ip, isPublic, isIpv4); } }, stream); } @@ -575,24 +593,32 @@ } function handleCandidate(candidate) { - var ipRegex = /([0-9]{1,3}(\.[0-9]{1,3}){3})/; - var match = ipRegex.exec(candidate); + if (!candidate) { + callback(); // Pass nothing to tell that ICE-gathering-ended + return; + } + + var match = regexIpv4.exec(candidate); if (!match) { return; } var ipAddress = match[1]; + const isPublic = (candidate.match(regexIpv4Local)), + isIpv4 = true; if (ipDuplicates[ipAddress] === undefined) { - callback(ipAddress); + callback(ipAddress, isPublic, isIpv4); } ipDuplicates[ipAddress] = true; } // listen for candidate events - pc.onicecandidate = function(ice) { - if (ice.candidate) { - handleCandidate(ice.candidate.candidate); + pc.onicecandidate = function(event) { + if (event.candidate && event.candidate.candidate) { + handleCandidate(event.candidate.candidate); + } else { + handleCandidate(); // Pass nothing to tell that ICE-gathering-ended } }; @@ -618,7 +644,7 @@ var lines = pc.localDescription.sdp.split('\n'); lines.forEach(function(line) { - if (line.indexOf('a=candidate:') === 0) { + if (line && line.indexOf('a=candidate:') === 0) { handleCandidate(line); } }); @@ -1082,7 +1108,7 @@ DetectRTC.isPromisesSupported = !!('Promise' in window); // version is generated by "grunt" - DetectRTC.version = '1.3.6'; + DetectRTC.version = '1.3.8'; if (typeof DetectRTC === 'undefined') { window.DetectRTC = {}; diff --git a/DetectRTC/DetectRTC.min.js b/DetectRTC/DetectRTC.min.js index 75f063fe..5d9f2edd 100644 --- a/DetectRTC/DetectRTC.min.js +++ b/DetectRTC/DetectRTC.min.js @@ -1,9 +1,9 @@ 'use strict'; -// Last Updated On: 2018-05-05 12:25:07 PM UTC +// Last Updated On: 2018-12-12 7:52:57 AM UTC // ________________ -// DetectRTC v1.3.6 +// DetectRTC v1.3.8 // Open-Sourced: https://github.com/muaz-khan/DetectRTC @@ -12,4 +12,4 @@ // MIT License - www.WebRTC-Experiment.com/licence // -------------------------------------------------- -"use strict";!function(){function getBrowserInfo(){var nameOffset,verOffset,ix,nAgt=(navigator.appVersion,navigator.userAgent),browserName=navigator.appName,fullVersion=""+parseFloat(navigator.appVersion),majorVersion=parseInt(navigator.appVersion,10);if(isOpera){browserName="Opera";try{fullVersion=navigator.userAgent.split("OPR/")[1].split(" ")[0],majorVersion=fullVersion.split(".")[0]}catch(e){fullVersion="0.0.0.0",majorVersion=0}}else isIE?(verOffset=nAgt.indexOf("rv:"),verOffset>0?fullVersion=nAgt.substring(verOffset+3):(verOffset=nAgt.indexOf("MSIE"),fullVersion=nAgt.substring(verOffset+5)),browserName="IE"):isChrome?(verOffset=nAgt.indexOf("Chrome"),browserName="Chrome",fullVersion=nAgt.substring(verOffset+7)):isSafari?(verOffset=nAgt.indexOf("Safari"),browserName="Safari",fullVersion=nAgt.substring(verOffset+7),(verOffset=nAgt.indexOf("Version"))!==-1&&(fullVersion=nAgt.substring(verOffset+8)),navigator.userAgent.indexOf("Version/")!==-1&&(fullVersion=navigator.userAgent.split("Version/")[1].split(" ")[0])):isFirefox?(verOffset=nAgt.indexOf("Firefox"),browserName="Firefox",fullVersion=nAgt.substring(verOffset+8)):(nameOffset=nAgt.lastIndexOf(" ")+1)<(verOffset=nAgt.lastIndexOf("/"))&&(browserName=nAgt.substring(nameOffset,verOffset),fullVersion=nAgt.substring(verOffset+1),browserName.toLowerCase()===browserName.toUpperCase()&&(browserName=navigator.appName));return isEdge&&(browserName="Edge",fullVersion=navigator.userAgent.split("Edge/")[1]),(ix=fullVersion.search(/[; \)]/))!==-1&&(fullVersion=fullVersion.substring(0,ix)),majorVersion=parseInt(""+fullVersion,10),isNaN(majorVersion)&&(fullVersion=""+parseFloat(navigator.appVersion),majorVersion=parseInt(navigator.appVersion,10)),{fullVersion:fullVersion,version:majorVersion,name:browserName,isPrivateBrowsing:!1}}function retry(isDone,next){var currentTrial=0,maxRetry=50,isTimeout=!1,id=window.setInterval(function(){isDone()&&(window.clearInterval(id),next(isTimeout)),currentTrial++>maxRetry&&(window.clearInterval(id),isTimeout=!0,next(isTimeout))},10)}function isIE10OrLater(userAgent){var ua=userAgent.toLowerCase();if(0===ua.indexOf("msie")&&0===ua.indexOf("trident"))return!1;var match=/(?:msie|rv:)\s?([\d\.]+)/.exec(ua);return!!(match&&parseInt(match[1],10)>=10)}function detectPrivateMode(callback){var isPrivate;try{if(window.webkitRequestFileSystem)window.webkitRequestFileSystem(window.TEMPORARY,1,function(){isPrivate=!1},function(e){isPrivate=!0});else if(window.indexedDB&&/Firefox/.test(window.navigator.userAgent)){var db;try{db=window.indexedDB.open("test"),db.onerror=function(){return!0}}catch(e){isPrivate=!0}"undefined"==typeof isPrivate&&retry(function(){return"done"===db.readyState},function(isTimeout){isTimeout||(isPrivate=!db.result)})}else if(isIE10OrLater(window.navigator.userAgent)){isPrivate=!1;try{window.indexedDB||(isPrivate=!0)}catch(e){isPrivate=!0}}else if(window.localStorage&&/Safari/.test(window.navigator.userAgent)){try{window.localStorage.setItem("test",1)}catch(e){isPrivate=!0}"undefined"==typeof isPrivate&&(isPrivate=!1,window.localStorage.removeItem("test"))}}catch(e){isPrivate=!1}retry(function(){return"undefined"!=typeof isPrivate},function(isTimeout){callback(isPrivate)})}function detectDesktopOS(){for(var cs,unknown="-",nVer=navigator.appVersion,nAgt=navigator.userAgent,os=unknown,clientStrings=[{s:"Windows 10",r:/(Windows 10.0|Windows NT 10.0)/},{s:"Windows 8.1",r:/(Windows 8.1|Windows NT 6.3)/},{s:"Windows 8",r:/(Windows 8|Windows NT 6.2)/},{s:"Windows 7",r:/(Windows 7|Windows NT 6.1)/},{s:"Windows Vista",r:/Windows NT 6.0/},{s:"Windows Server 2003",r:/Windows NT 5.2/},{s:"Windows XP",r:/(Windows NT 5.1|Windows XP)/},{s:"Windows 2000",r:/(Windows NT 5.0|Windows 2000)/},{s:"Windows ME",r:/(Win 9x 4.90|Windows ME)/},{s:"Windows 98",r:/(Windows 98|Win98)/},{s:"Windows 95",r:/(Windows 95|Win95|Windows_95)/},{s:"Windows NT 4.0",r:/(Windows NT 4.0|WinNT4.0|WinNT|Windows NT)/},{s:"Windows CE",r:/Windows CE/},{s:"Windows 3.11",r:/Win16/},{s:"Android",r:/Android/},{s:"Open BSD",r:/OpenBSD/},{s:"Sun OS",r:/SunOS/},{s:"Linux",r:/(Linux|X11)/},{s:"iOS",r:/(iPhone|iPad|iPod)/},{s:"Mac OS X",r:/Mac OS X/},{s:"Mac OS",r:/(MacPPC|MacIntel|Mac_PowerPC|Macintosh)/},{s:"QNX",r:/QNX/},{s:"UNIX",r:/UNIX/},{s:"BeOS",r:/BeOS/},{s:"OS/2",r:/OS\/2/},{s:"Search Bot",r:/(nuhk|Googlebot|Yammybot|Openbot|Slurp|MSNBot|Ask Jeeves\/Teoma|ia_archiver)/}],i=0;cs=clientStrings[i];i++)if(cs.r.test(nAgt)){os=cs.s;break}var osVersion=unknown;switch(/Windows/.test(os)&&(/Windows (.*)/.test(os)&&(osVersion=/Windows (.*)/.exec(os)[1]),os="Windows"),os){case"Mac OS X":/Mac OS X (10[\.\_\d]+)/.test(nAgt)&&(osVersion=/Mac OS X (10[\.\_\d]+)/.exec(nAgt)[1]);break;case"Android":/Android ([\.\_\d]+)/.test(nAgt)&&(osVersion=/Android ([\.\_\d]+)/.exec(nAgt)[1]);break;case"iOS":/OS (\d+)_(\d+)_?(\d+)?/.test(nAgt)&&(osVersion=/OS (\d+)_(\d+)_?(\d+)?/.exec(nVer),osVersion=osVersion[1]+"."+osVersion[2]+"."+(0|osVersion[3]))}return{osName:os,osVersion:osVersion}}function getAndroidVersion(ua){ua=(ua||navigator.userAgent).toLowerCase();var match=ua.match(/android\s([0-9\.]*)/);return!!match&&match[1]}function DetectLocalIPAddress(callback,stream){DetectRTC.isWebRTCSupported&&getIPs(function(ip){callback(ip.match(/^(192\.168\.|169\.254\.|10\.|172\.(1[6-9]|2\d|3[01]))/)?"Local: "+ip:"Public: "+ip)},stream)}function getIPs(callback,stream){function handleCandidate(candidate){var ipRegex=/([0-9]{1,3}(\.[0-9]{1,3}){3})/,match=ipRegex.exec(candidate);if(match){var ipAddress=match[1];void 0===ipDuplicates[ipAddress]&&callback(ipAddress),ipDuplicates[ipAddress]=!0}}function afterCreateOffer(){var lines=pc.localDescription.sdp.split("\n");lines.forEach(function(line){0===line.indexOf("a=candidate:")&&handleCandidate(line)})}if("undefined"!=typeof document&&"function"==typeof document.getElementById){var ipDuplicates={},RTCPeerConnection=window.RTCPeerConnection||window.mozRTCPeerConnection||window.webkitRTCPeerConnection;if(!RTCPeerConnection){var iframe=document.getElementById("iframe");if(!iframe)return;var win=iframe.contentWindow;RTCPeerConnection=win.RTCPeerConnection||win.mozRTCPeerConnection||win.webkitRTCPeerConnection}if(RTCPeerConnection){var peerConfig=null;"Chrome"===DetectRTC.browser&&DetectRTC.browser.version<58&&(peerConfig={optional:[{RtpDataChannels:!0}]});var servers={iceServers:[{urls:"stun:stun.l.google.com:19302"}]},pc=new RTCPeerConnection(servers,peerConfig);if(stream&&(pc.addStream?pc.addStream(stream):pc.addTrack&&stream.getTracks()[0]&&pc.addTrack(stream.getTracks()[0],stream)),pc.onicecandidate=function(ice){ice.candidate&&handleCandidate(ice.candidate.candidate)},!stream)try{pc.createDataChannel("sctp",{})}catch(e){}DetectRTC.isPromisesSupported?pc.createOffer().then(function(result){pc.setLocalDescription(result).then(afterCreateOffer)}):pc.createOffer(function(result){pc.setLocalDescription(result,afterCreateOffer,function(){})},function(){})}}}function checkDeviceSupport(callback){if(!canEnumerate)return void(callback&&callback());if(!navigator.enumerateDevices&&window.MediaStreamTrack&&window.MediaStreamTrack.getSources&&(navigator.enumerateDevices=window.MediaStreamTrack.getSources.bind(window.MediaStreamTrack)),!navigator.enumerateDevices&&navigator.enumerateDevices&&(navigator.enumerateDevices=navigator.enumerateDevices.bind(navigator)),!navigator.enumerateDevices)return void(callback&&callback());MediaDevices=[],audioInputDevices=[],audioOutputDevices=[],videoInputDevices=[],hasMicrophone=!1,hasSpeakers=!1,hasWebcam=!1,isWebsiteHasMicrophonePermissions=!1,isWebsiteHasWebcamPermissions=!1;var alreadyUsedDevices={};navigator.enumerateDevices(function(devices){devices.forEach(function(_device){var device={};for(var d in _device)try{"function"!=typeof _device[d]&&(device[d]=_device[d])}catch(e){}alreadyUsedDevices[device.deviceId+device.label+device.kind]||("audio"===device.kind&&(device.kind="audioinput"),"video"===device.kind&&(device.kind="videoinput"),device.deviceId||(device.deviceId=device.id),device.id||(device.id=device.deviceId),device.label?("videoinput"!==device.kind||isWebsiteHasWebcamPermissions||(isWebsiteHasWebcamPermissions=!0),"audioinput"!==device.kind||isWebsiteHasMicrophonePermissions||(isWebsiteHasMicrophonePermissions=!0)):(device.isCustomLabel=!0,"videoinput"===device.kind?device.label="Camera "+(videoInputDevices.length+1):"audioinput"===device.kind?device.label="Microphone "+(audioInputDevices.length+1):"audiooutput"===device.kind?device.label="Speaker "+(audioOutputDevices.length+1):device.label="Please invoke getUserMedia once.","undefined"!=typeof DetectRTC&&DetectRTC.browser.isChrome&&DetectRTC.browser.version>=46&&!/^(https:|chrome-extension:)$/g.test(location.protocol||"")&&"undefined"!=typeof document&&"string"==typeof document.domain&&document.domain.search&&document.domain.search(/localhost|127.0./g)===-1&&(device.label="HTTPs is required to get label of this "+device.kind+" device.")),"audioinput"===device.kind&&(hasMicrophone=!0,audioInputDevices.indexOf(device)===-1&&audioInputDevices.push(device)),"audiooutput"===device.kind&&(hasSpeakers=!0,audioOutputDevices.indexOf(device)===-1&&audioOutputDevices.push(device)),"videoinput"===device.kind&&(hasWebcam=!0,videoInputDevices.indexOf(device)===-1&&videoInputDevices.push(device)),MediaDevices.push(device),alreadyUsedDevices[device.deviceId+device.label+device.kind]=device)}),"undefined"!=typeof DetectRTC&&(DetectRTC.MediaDevices=MediaDevices,DetectRTC.hasMicrophone=hasMicrophone,DetectRTC.hasSpeakers=hasSpeakers,DetectRTC.hasWebcam=hasWebcam,DetectRTC.isWebsiteHasWebcamPermissions=isWebsiteHasWebcamPermissions,DetectRTC.isWebsiteHasMicrophonePermissions=isWebsiteHasMicrophonePermissions,DetectRTC.audioInputDevices=audioInputDevices,DetectRTC.audioOutputDevices=audioOutputDevices,DetectRTC.videoInputDevices=videoInputDevices),callback&&callback()})}function getAspectRatio(w,h){function gcd(a,b){return 0==b?a:gcd(b,a%b)}var r=gcd(w,h);return w/r/(h/r)}var browserFakeUserAgent="Fake/5.0 (FakeOS) AppleWebKit/123 (KHTML, like Gecko) Fake/12.3.4567.89 Fake/123.45",isNodejs="object"==typeof process&&"object"==typeof process.versions&&process.versions.node&&!process.browser;if(isNodejs){var version=process.versions.node.toString().replace("v","");browserFakeUserAgent="Nodejs/"+version+" (NodeOS) AppleWebKit/"+version+" (KHTML, like Gecko) Nodejs/"+version+" Nodejs/"+version}!function(that){"undefined"==typeof window&&("undefined"==typeof window&&"undefined"!=typeof global?(global.navigator={userAgent:browserFakeUserAgent,getUserMedia:function(){}},that.window=global):"undefined"==typeof window,"undefined"==typeof location&&(that.location={protocol:"file:",href:"",hash:""}),"undefined"==typeof screen&&(that.screen={width:0,height:0}))}("undefined"!=typeof global?global:window);var navigator=window.navigator;"undefined"!=typeof navigator?("undefined"!=typeof navigator.webkitGetUserMedia&&(navigator.getUserMedia=navigator.webkitGetUserMedia),"undefined"!=typeof navigator.mozGetUserMedia&&(navigator.getUserMedia=navigator.mozGetUserMedia)):navigator={getUserMedia:function(){},userAgent:browserFakeUserAgent};var isMobileDevice=!!/Android|webOS|iPhone|iPad|iPod|BB10|BlackBerry|IEMobile|Opera Mini|Mobile|mobile/i.test(navigator.userAgent||""),isEdge=!(navigator.userAgent.indexOf("Edge")===-1||!navigator.msSaveOrOpenBlob&&!navigator.msSaveBlob),isOpera=!!window.opera||navigator.userAgent.indexOf(" OPR/")>=0,isFirefox="undefined"!=typeof window.InstallTrigger,isSafari=/^((?!chrome|android).)*safari/i.test(navigator.userAgent),isChrome=!!window.chrome&&!isOpera,isIE="undefined"!=typeof document&&!!document.documentMode&&!isEdge,isMobile={Android:function(){return navigator.userAgent.match(/Android/i)},BlackBerry:function(){return navigator.userAgent.match(/BlackBerry|BB10/i)},iOS:function(){return navigator.userAgent.match(/iPhone|iPad|iPod/i)},Opera:function(){return navigator.userAgent.match(/Opera Mini/i)},Windows:function(){return navigator.userAgent.match(/IEMobile/i)},any:function(){return isMobile.Android()||isMobile.BlackBerry()||isMobile.iOS()||isMobile.Opera()||isMobile.Windows()},getOsName:function(){var osName="Unknown OS";return isMobile.Android()&&(osName="Android"),isMobile.BlackBerry()&&(osName="BlackBerry"),isMobile.iOS()&&(osName="iOS"),isMobile.Opera()&&(osName="Opera Mini"),isMobile.Windows()&&(osName="Windows"),osName}},osName="Unknown OS",osVersion="Unknown OS Version",osInfo=detectDesktopOS();osInfo&&osInfo.osName&&"-"!=osInfo.osName?(osName=osInfo.osName,osVersion=osInfo.osVersion):isMobile.any()&&(osName=isMobile.getOsName(),"Android"==osName&&(osVersion=getAndroidVersion()));var isNodejs="object"==typeof process&&"object"==typeof process.versions&&process.versions.node;"Unknown OS"===osName&&isNodejs&&(osName="Nodejs",osVersion=process.versions.node.toString().replace("v",""));var isCanvasSupportsStreamCapturing=!1,isVideoSupportsStreamCapturing=!1;["captureStream","mozCaptureStream","webkitCaptureStream"].forEach(function(item){"undefined"!=typeof document&&"function"==typeof document.createElement&&(!isCanvasSupportsStreamCapturing&&item in document.createElement("canvas")&&(isCanvasSupportsStreamCapturing=!0),!isVideoSupportsStreamCapturing&&item in document.createElement("video")&&(isVideoSupportsStreamCapturing=!0))});var MediaDevices=[],audioInputDevices=[],audioOutputDevices=[],videoInputDevices=[];navigator.mediaDevices&&navigator.mediaDevices.enumerateDevices&&(navigator.enumerateDevices=function(callback){var enumerateDevices=navigator.mediaDevices.enumerateDevices();enumerateDevices&&enumerateDevices.then?navigator.mediaDevices.enumerateDevices().then(callback)["catch"](function(){callback([])}):callback([])});var canEnumerate=!1;"undefined"!=typeof MediaStreamTrack&&"getSources"in MediaStreamTrack?canEnumerate=!0:navigator.mediaDevices&&navigator.mediaDevices.enumerateDevices&&(canEnumerate=!0);var hasMicrophone=!1,hasSpeakers=!1,hasWebcam=!1,isWebsiteHasMicrophonePermissions=!1,isWebsiteHasWebcamPermissions=!1,DetectRTC=window.DetectRTC||{};DetectRTC.browser=getBrowserInfo(),detectPrivateMode(function(isPrivateBrowsing){DetectRTC.browser.isPrivateBrowsing=!!isPrivateBrowsing}),DetectRTC.browser["is"+DetectRTC.browser.name]=!0,DetectRTC.osName=osName,DetectRTC.osVersion=osVersion;var isWebRTCSupported=("object"==typeof process&&"object"==typeof process.versions&&process.versions["node-webkit"],!1);["RTCPeerConnection","webkitRTCPeerConnection","mozRTCPeerConnection","RTCIceGatherer"].forEach(function(item){isWebRTCSupported||item in window&&(isWebRTCSupported=!0)}),DetectRTC.isWebRTCSupported=isWebRTCSupported,DetectRTC.isORTCSupported="undefined"!=typeof RTCIceGatherer;var isScreenCapturingSupported=!1;if(DetectRTC.browser.isChrome&&DetectRTC.browser.version>=35?isScreenCapturingSupported=!0:DetectRTC.browser.isFirefox&&DetectRTC.browser.version>=34?isScreenCapturingSupported=!0:DetectRTC.browser.isEdge&&DetectRTC.browser.version>=17?isScreenCapturingSupported=!0:"Android"===DetectRTC.osName&&DetectRTC.browser.isChrome&&(isScreenCapturingSupported=!0),!/^(https:|chrome-extension:)$/g.test(location.protocol||"")){var isNonLocalHost="undefined"!=typeof document&&"string"==typeof document.domain&&document.domain.search&&document.domain.search(/localhost|127.0./g)===-1;isNonLocalHost&&(DetectRTC.browser.isChrome||DetectRTC.browser.isEdge||DetectRTC.browser.isOpera)?isScreenCapturingSupported=!1:DetectRTC.browser.isFirefox&&(isScreenCapturingSupported=!1)}DetectRTC.isScreenCapturingSupported=isScreenCapturingSupported;var webAudio={isSupported:!1,isCreateMediaStreamSourceSupported:!1};["AudioContext","webkitAudioContext","mozAudioContext","msAudioContext"].forEach(function(item){webAudio.isSupported||item in window&&(webAudio.isSupported=!0,window[item]&&"createMediaStreamSource"in window[item].prototype&&(webAudio.isCreateMediaStreamSourceSupported=!0))}),DetectRTC.isAudioContextSupported=webAudio.isSupported,DetectRTC.isCreateMediaStreamSourceSupported=webAudio.isCreateMediaStreamSourceSupported;var isRtpDataChannelsSupported=!1;DetectRTC.browser.isChrome&&DetectRTC.browser.version>31&&(isRtpDataChannelsSupported=!0),DetectRTC.isRtpDataChannelsSupported=isRtpDataChannelsSupported;var isSCTPSupportd=!1;DetectRTC.browser.isFirefox&&DetectRTC.browser.version>28?isSCTPSupportd=!0:DetectRTC.browser.isChrome&&DetectRTC.browser.version>25?isSCTPSupportd=!0:DetectRTC.browser.isOpera&&DetectRTC.browser.version>=11&&(isSCTPSupportd=!0),DetectRTC.isSctpDataChannelsSupported=isSCTPSupportd,DetectRTC.isMobileDevice=isMobileDevice;var isGetUserMediaSupported=!1;navigator.getUserMedia?isGetUserMediaSupported=!0:navigator.mediaDevices&&navigator.mediaDevices.getUserMedia&&(isGetUserMediaSupported=!0),DetectRTC.browser.isChrome&&DetectRTC.browser.version>=46&&!/^(https:|chrome-extension:)$/g.test(location.protocol||"")&&"undefined"!=typeof document&&"string"==typeof document.domain&&document.domain.search&&document.domain.search(/localhost|127.0./g)===-1&&(isGetUserMediaSupported="Requires HTTPs"),"Nodejs"===DetectRTC.osName&&(isGetUserMediaSupported=!1),DetectRTC.isGetUserMediaSupported=isGetUserMediaSupported;var displayResolution="";if(screen.width){var width=screen.width?screen.width:"",height=screen.height?screen.height:"";displayResolution+=""+width+" x "+height}DetectRTC.displayResolution=displayResolution,DetectRTC.displayAspectRatio=getAspectRatio(screen.width,screen.height).toFixed(2),DetectRTC.isCanvasSupportsStreamCapturing=isCanvasSupportsStreamCapturing,DetectRTC.isVideoSupportsStreamCapturing=isVideoSupportsStreamCapturing,"Chrome"==DetectRTC.browser.name&&DetectRTC.browser.version>=53&&(DetectRTC.isCanvasSupportsStreamCapturing||(DetectRTC.isCanvasSupportsStreamCapturing="Requires chrome flag: enable-experimental-web-platform-features"),DetectRTC.isVideoSupportsStreamCapturing||(DetectRTC.isVideoSupportsStreamCapturing="Requires chrome flag: enable-experimental-web-platform-features")),DetectRTC.DetectLocalIPAddress=DetectLocalIPAddress,DetectRTC.isWebSocketsSupported="WebSocket"in window&&2===window.WebSocket.CLOSING,DetectRTC.isWebSocketsBlocked=!DetectRTC.isWebSocketsSupported,"Nodejs"===DetectRTC.osName&&(DetectRTC.isWebSocketsSupported=!0,DetectRTC.isWebSocketsBlocked=!1),DetectRTC.checkWebSocketsSupport=function(callback){callback=callback||function(){};try{var starttime,websocket=new WebSocket("wss://echo.websocket.org:443/");websocket.onopen=function(){DetectRTC.isWebSocketsBlocked=!1,starttime=(new Date).getTime(),websocket.send("ping")},websocket.onmessage=function(){DetectRTC.WebsocketLatency=(new Date).getTime()-starttime+"ms",callback(),websocket.close(),websocket=null},websocket.onerror=function(){DetectRTC.isWebSocketsBlocked=!0,callback()}}catch(e){DetectRTC.isWebSocketsBlocked=!0,callback()}},DetectRTC.load=function(callback){callback=callback||function(){},checkDeviceSupport(callback)},"undefined"!=typeof MediaDevices?DetectRTC.MediaDevices=MediaDevices:DetectRTC.MediaDevices=[],DetectRTC.hasMicrophone=hasMicrophone,DetectRTC.hasSpeakers=hasSpeakers,DetectRTC.hasWebcam=hasWebcam,DetectRTC.isWebsiteHasWebcamPermissions=isWebsiteHasWebcamPermissions,DetectRTC.isWebsiteHasMicrophonePermissions=isWebsiteHasMicrophonePermissions,DetectRTC.audioInputDevices=audioInputDevices,DetectRTC.audioOutputDevices=audioOutputDevices,DetectRTC.videoInputDevices=videoInputDevices;var isSetSinkIdSupported=!1;"undefined"!=typeof document&&"function"==typeof document.createElement&&"setSinkId"in document.createElement("video")&&(isSetSinkIdSupported=!0),DetectRTC.isSetSinkIdSupported=isSetSinkIdSupported;var isRTPSenderReplaceTracksSupported=!1;DetectRTC.browser.isFirefox&&"undefined"!=typeof mozRTCPeerConnection?"getSenders"in mozRTCPeerConnection.prototype&&(isRTPSenderReplaceTracksSupported=!0):DetectRTC.browser.isChrome&&"undefined"!=typeof webkitRTCPeerConnection&&"getSenders"in webkitRTCPeerConnection.prototype&&(isRTPSenderReplaceTracksSupported=!0),DetectRTC.isRTPSenderReplaceTracksSupported=isRTPSenderReplaceTracksSupported;var isRemoteStreamProcessingSupported=!1;DetectRTC.browser.isFirefox&&DetectRTC.browser.version>38&&(isRemoteStreamProcessingSupported=!0),DetectRTC.isRemoteStreamProcessingSupported=isRemoteStreamProcessingSupported;var isApplyConstraintsSupported=!1;"undefined"!=typeof MediaStreamTrack&&"applyConstraints"in MediaStreamTrack.prototype&&(isApplyConstraintsSupported=!0),DetectRTC.isApplyConstraintsSupported=isApplyConstraintsSupported;var isMultiMonitorScreenCapturingSupported=!1;DetectRTC.browser.isFirefox&&DetectRTC.browser.version>=43&&(isMultiMonitorScreenCapturingSupported=!0),DetectRTC.isMultiMonitorScreenCapturingSupported=isMultiMonitorScreenCapturingSupported,DetectRTC.isPromisesSupported=!!("Promise"in window),DetectRTC.version="1.3.6","undefined"==typeof DetectRTC&&(window.DetectRTC={});var MediaStream=window.MediaStream;"undefined"==typeof MediaStream&&"undefined"!=typeof webkitMediaStream&&(MediaStream=webkitMediaStream),"undefined"!=typeof MediaStream&&"function"==typeof MediaStream?DetectRTC.MediaStream=Object.keys(MediaStream.prototype):DetectRTC.MediaStream=!1,"undefined"!=typeof MediaStreamTrack?DetectRTC.MediaStreamTrack=Object.keys(MediaStreamTrack.prototype):DetectRTC.MediaStreamTrack=!1;var RTCPeerConnection=window.RTCPeerConnection||window.mozRTCPeerConnection||window.webkitRTCPeerConnection;"undefined"!=typeof RTCPeerConnection?DetectRTC.RTCPeerConnection=Object.keys(RTCPeerConnection.prototype):DetectRTC.RTCPeerConnection=!1,window.DetectRTC=DetectRTC,"undefined"!=typeof module&&(module.exports=DetectRTC),"function"==typeof define&&define.amd&&define("DetectRTC",[],function(){return DetectRTC})}(); \ No newline at end of file +"use strict";!function(){function getBrowserInfo(){var nameOffset,verOffset,ix,nAgt=(navigator.appVersion,navigator.userAgent),browserName=navigator.appName,fullVersion=""+parseFloat(navigator.appVersion),majorVersion=parseInt(navigator.appVersion,10);if(isSafari&&!isChrome&&nAgt.indexOf("CriOS")!==-1&&(isSafari=!1,isChrome=!0),isOpera){browserName="Opera";try{fullVersion=navigator.userAgent.split("OPR/")[1].split(" ")[0],majorVersion=fullVersion.split(".")[0]}catch(e){fullVersion="0.0.0.0",majorVersion=0}}else isIE?(verOffset=nAgt.indexOf("rv:"),verOffset>0?fullVersion=nAgt.substring(verOffset+3):(verOffset=nAgt.indexOf("MSIE"),fullVersion=nAgt.substring(verOffset+5)),browserName="IE"):isChrome?(verOffset=nAgt.indexOf("Chrome"),browserName="Chrome",fullVersion=nAgt.substring(verOffset+7)):isSafari?(verOffset=nAgt.indexOf("Safari"),browserName="Safari",fullVersion=nAgt.substring(verOffset+7),(verOffset=nAgt.indexOf("Version"))!==-1&&(fullVersion=nAgt.substring(verOffset+8)),navigator.userAgent.indexOf("Version/")!==-1&&(fullVersion=navigator.userAgent.split("Version/")[1].split(" ")[0])):isFirefox?(verOffset=nAgt.indexOf("Firefox"),browserName="Firefox",fullVersion=nAgt.substring(verOffset+8)):(nameOffset=nAgt.lastIndexOf(" ")+1)<(verOffset=nAgt.lastIndexOf("/"))&&(browserName=nAgt.substring(nameOffset,verOffset),fullVersion=nAgt.substring(verOffset+1),browserName.toLowerCase()===browserName.toUpperCase()&&(browserName=navigator.appName));return isEdge&&(browserName="Edge",fullVersion=navigator.userAgent.split("Edge/")[1]),(ix=fullVersion.search(/[; \)]/))!==-1&&(fullVersion=fullVersion.substring(0,ix)),majorVersion=parseInt(""+fullVersion,10),isNaN(majorVersion)&&(fullVersion=""+parseFloat(navigator.appVersion),majorVersion=parseInt(navigator.appVersion,10)),{fullVersion:fullVersion,version:majorVersion,name:browserName,isPrivateBrowsing:!1}}function retry(isDone,next){var currentTrial=0,maxRetry=50,isTimeout=!1,id=window.setInterval(function(){isDone()&&(window.clearInterval(id),next(isTimeout)),currentTrial++>maxRetry&&(window.clearInterval(id),isTimeout=!0,next(isTimeout))},10)}function isIE10OrLater(userAgent){var ua=userAgent.toLowerCase();if(0===ua.indexOf("msie")&&0===ua.indexOf("trident"))return!1;var match=/(?:msie|rv:)\s?([\d\.]+)/.exec(ua);return!!(match&&parseInt(match[1],10)>=10)}function detectPrivateMode(callback){var isPrivate;try{if(window.webkitRequestFileSystem)window.webkitRequestFileSystem(window.TEMPORARY,1,function(){isPrivate=!1},function(e){isPrivate=!0});else if(window.indexedDB&&/Firefox/.test(window.navigator.userAgent)){var db;try{db=window.indexedDB.open("test"),db.onerror=function(){return!0}}catch(e){isPrivate=!0}"undefined"==typeof isPrivate&&retry(function(){return"done"===db.readyState},function(isTimeout){isTimeout||(isPrivate=!db.result)})}else if(isIE10OrLater(window.navigator.userAgent)){isPrivate=!1;try{window.indexedDB||(isPrivate=!0)}catch(e){isPrivate=!0}}else if(window.localStorage&&/Safari/.test(window.navigator.userAgent)){try{window.localStorage.setItem("test",1)}catch(e){isPrivate=!0}"undefined"==typeof isPrivate&&(isPrivate=!1,window.localStorage.removeItem("test"))}}catch(e){isPrivate=!1}retry(function(){return"undefined"!=typeof isPrivate},function(isTimeout){callback(isPrivate)})}function detectDesktopOS(){for(var cs,unknown="-",nVer=navigator.appVersion,nAgt=navigator.userAgent,os=unknown,clientStrings=[{s:"Windows 10",r:/(Windows 10.0|Windows NT 10.0)/},{s:"Windows 8.1",r:/(Windows 8.1|Windows NT 6.3)/},{s:"Windows 8",r:/(Windows 8|Windows NT 6.2)/},{s:"Windows 7",r:/(Windows 7|Windows NT 6.1)/},{s:"Windows Vista",r:/Windows NT 6.0/},{s:"Windows Server 2003",r:/Windows NT 5.2/},{s:"Windows XP",r:/(Windows NT 5.1|Windows XP)/},{s:"Windows 2000",r:/(Windows NT 5.0|Windows 2000)/},{s:"Windows ME",r:/(Win 9x 4.90|Windows ME)/},{s:"Windows 98",r:/(Windows 98|Win98)/},{s:"Windows 95",r:/(Windows 95|Win95|Windows_95)/},{s:"Windows NT 4.0",r:/(Windows NT 4.0|WinNT4.0|WinNT|Windows NT)/},{s:"Windows CE",r:/Windows CE/},{s:"Windows 3.11",r:/Win16/},{s:"Android",r:/Android/},{s:"Open BSD",r:/OpenBSD/},{s:"Sun OS",r:/SunOS/},{s:"Linux",r:/(Linux|X11)/},{s:"iOS",r:/(iPhone|iPad|iPod)/},{s:"Mac OS X",r:/Mac OS X/},{s:"Mac OS",r:/(MacPPC|MacIntel|Mac_PowerPC|Macintosh)/},{s:"QNX",r:/QNX/},{s:"UNIX",r:/UNIX/},{s:"BeOS",r:/BeOS/},{s:"OS/2",r:/OS\/2/},{s:"Search Bot",r:/(nuhk|Googlebot|Yammybot|Openbot|Slurp|MSNBot|Ask Jeeves\/Teoma|ia_archiver)/}],i=0;cs=clientStrings[i];i++)if(cs.r.test(nAgt)){os=cs.s;break}var osVersion=unknown;switch(/Windows/.test(os)&&(/Windows (.*)/.test(os)&&(osVersion=/Windows (.*)/.exec(os)[1]),os="Windows"),os){case"Mac OS X":/Mac OS X (10[\.\_\d]+)/.test(nAgt)&&(osVersion=/Mac OS X (10[\.\_\d]+)/.exec(nAgt)[1]);break;case"Android":/Android ([\.\_\d]+)/.test(nAgt)&&(osVersion=/Android ([\.\_\d]+)/.exec(nAgt)[1]);break;case"iOS":/OS (\d+)_(\d+)_?(\d+)?/.test(nAgt)&&(osVersion=/OS (\d+)_(\d+)_?(\d+)?/.exec(nVer),osVersion=osVersion[1]+"."+osVersion[2]+"."+(0|osVersion[3]))}return{osName:os,osVersion:osVersion}}function getAndroidVersion(ua){ua=(ua||navigator.userAgent).toLowerCase();var match=ua.match(/android\s([0-9\.]*)/);return!!match&&match[1]}function DetectLocalIPAddress(callback,stream){if(DetectRTC.isWebRTCSupported){var isPublic=!0,isIpv4=!0;getIPs(function(ip){ip?ip.match(regexIpv4Local)?(isPublic=!1,callback("Local: "+ip,isPublic,isIpv4)):ip.match(regexIpv6)?(isIpv4=!1,callback("Public: "+ip,isPublic,isIpv4)):callback("Public: "+ip,isPublic,isIpv4):callback()},stream)}}function getIPs(callback,stream){function handleCandidate(candidate){if(!candidate)return void callback();var match=regexIpv4.exec(candidate);if(match){var ipAddress=match[1];const isPublic=candidate.match(regexIpv4Local),isIpv4=!0;void 0===ipDuplicates[ipAddress]&&callback(ipAddress,isPublic,isIpv4),ipDuplicates[ipAddress]=!0}}function afterCreateOffer(){var lines=pc.localDescription.sdp.split("\n");lines.forEach(function(line){line&&0===line.indexOf("a=candidate:")&&handleCandidate(line)})}if("undefined"!=typeof document&&"function"==typeof document.getElementById){var ipDuplicates={},RTCPeerConnection=window.RTCPeerConnection||window.mozRTCPeerConnection||window.webkitRTCPeerConnection;if(!RTCPeerConnection){var iframe=document.getElementById("iframe");if(!iframe)return;var win=iframe.contentWindow;RTCPeerConnection=win.RTCPeerConnection||win.mozRTCPeerConnection||win.webkitRTCPeerConnection}if(RTCPeerConnection){var peerConfig=null;"Chrome"===DetectRTC.browser&&DetectRTC.browser.version<58&&(peerConfig={optional:[{RtpDataChannels:!0}]});var servers={iceServers:[{urls:"stun:stun.l.google.com:19302"}]},pc=new RTCPeerConnection(servers,peerConfig);if(stream&&(pc.addStream?pc.addStream(stream):pc.addTrack&&stream.getTracks()[0]&&pc.addTrack(stream.getTracks()[0],stream)),pc.onicecandidate=function(event){event.candidate&&event.candidate.candidate?handleCandidate(event.candidate.candidate):handleCandidate()},!stream)try{pc.createDataChannel("sctp",{})}catch(e){}DetectRTC.isPromisesSupported?pc.createOffer().then(function(result){pc.setLocalDescription(result).then(afterCreateOffer)}):pc.createOffer(function(result){pc.setLocalDescription(result,afterCreateOffer,function(){})},function(){})}}}function checkDeviceSupport(callback){if(!canEnumerate)return void(callback&&callback());if(!navigator.enumerateDevices&&window.MediaStreamTrack&&window.MediaStreamTrack.getSources&&(navigator.enumerateDevices=window.MediaStreamTrack.getSources.bind(window.MediaStreamTrack)),!navigator.enumerateDevices&&navigator.enumerateDevices&&(navigator.enumerateDevices=navigator.enumerateDevices.bind(navigator)),!navigator.enumerateDevices)return void(callback&&callback());MediaDevices=[],audioInputDevices=[],audioOutputDevices=[],videoInputDevices=[],hasMicrophone=!1,hasSpeakers=!1,hasWebcam=!1,isWebsiteHasMicrophonePermissions=!1,isWebsiteHasWebcamPermissions=!1;var alreadyUsedDevices={};navigator.enumerateDevices(function(devices){devices.forEach(function(_device){var device={};for(var d in _device)try{"function"!=typeof _device[d]&&(device[d]=_device[d])}catch(e){}alreadyUsedDevices[device.deviceId+device.label+device.kind]||("audio"===device.kind&&(device.kind="audioinput"),"video"===device.kind&&(device.kind="videoinput"),device.deviceId||(device.deviceId=device.id),device.id||(device.id=device.deviceId),device.label?("videoinput"!==device.kind||isWebsiteHasWebcamPermissions||(isWebsiteHasWebcamPermissions=!0),"audioinput"!==device.kind||isWebsiteHasMicrophonePermissions||(isWebsiteHasMicrophonePermissions=!0)):(device.isCustomLabel=!0,"videoinput"===device.kind?device.label="Camera "+(videoInputDevices.length+1):"audioinput"===device.kind?device.label="Microphone "+(audioInputDevices.length+1):"audiooutput"===device.kind?device.label="Speaker "+(audioOutputDevices.length+1):device.label="Please invoke getUserMedia once.","undefined"!=typeof DetectRTC&&DetectRTC.browser.isChrome&&DetectRTC.browser.version>=46&&!/^(https:|chrome-extension:)$/g.test(location.protocol||"")&&"undefined"!=typeof document&&"string"==typeof document.domain&&document.domain.search&&document.domain.search(/localhost|127.0./g)===-1&&(device.label="HTTPs is required to get label of this "+device.kind+" device.")),"audioinput"===device.kind&&(hasMicrophone=!0,audioInputDevices.indexOf(device)===-1&&audioInputDevices.push(device)),"audiooutput"===device.kind&&(hasSpeakers=!0,audioOutputDevices.indexOf(device)===-1&&audioOutputDevices.push(device)),"videoinput"===device.kind&&(hasWebcam=!0,videoInputDevices.indexOf(device)===-1&&videoInputDevices.push(device)),MediaDevices.push(device),alreadyUsedDevices[device.deviceId+device.label+device.kind]=device)}),"undefined"!=typeof DetectRTC&&(DetectRTC.MediaDevices=MediaDevices,DetectRTC.hasMicrophone=hasMicrophone,DetectRTC.hasSpeakers=hasSpeakers,DetectRTC.hasWebcam=hasWebcam,DetectRTC.isWebsiteHasWebcamPermissions=isWebsiteHasWebcamPermissions,DetectRTC.isWebsiteHasMicrophonePermissions=isWebsiteHasMicrophonePermissions,DetectRTC.audioInputDevices=audioInputDevices,DetectRTC.audioOutputDevices=audioOutputDevices,DetectRTC.videoInputDevices=videoInputDevices),callback&&callback()})}function getAspectRatio(w,h){function gcd(a,b){return 0==b?a:gcd(b,a%b)}var r=gcd(w,h);return w/r/(h/r)}var browserFakeUserAgent="Fake/5.0 (FakeOS) AppleWebKit/123 (KHTML, like Gecko) Fake/12.3.4567.89 Fake/123.45",isNodejs="object"==typeof process&&"object"==typeof process.versions&&process.versions.node&&!process.browser;if(isNodejs){var version=process.versions.node.toString().replace("v","");browserFakeUserAgent="Nodejs/"+version+" (NodeOS) AppleWebKit/"+version+" (KHTML, like Gecko) Nodejs/"+version+" Nodejs/"+version}!function(that){"undefined"==typeof window&&("undefined"==typeof window&&"undefined"!=typeof global?(global.navigator={userAgent:browserFakeUserAgent,getUserMedia:function(){}},that.window=global):"undefined"==typeof window,"undefined"==typeof location&&(that.location={protocol:"file:",href:"",hash:""}),"undefined"==typeof screen&&(that.screen={width:0,height:0}))}("undefined"!=typeof global?global:window);var navigator=window.navigator;"undefined"!=typeof navigator?("undefined"!=typeof navigator.webkitGetUserMedia&&(navigator.getUserMedia=navigator.webkitGetUserMedia),"undefined"!=typeof navigator.mozGetUserMedia&&(navigator.getUserMedia=navigator.mozGetUserMedia)):navigator={getUserMedia:function(){},userAgent:browserFakeUserAgent};var isMobileDevice=!!/Android|webOS|iPhone|iPad|iPod|BB10|BlackBerry|IEMobile|Opera Mini|Mobile|mobile/i.test(navigator.userAgent||""),isEdge=!(navigator.userAgent.indexOf("Edge")===-1||!navigator.msSaveOrOpenBlob&&!navigator.msSaveBlob),isOpera=!!window.opera||navigator.userAgent.indexOf(" OPR/")>=0,isFirefox="undefined"!=typeof window.InstallTrigger,isSafari=/^((?!chrome|android).)*safari/i.test(navigator.userAgent),isChrome=!!window.chrome&&!isOpera,isIE="undefined"!=typeof document&&!!document.documentMode&&!isEdge,isMobile={Android:function(){return navigator.userAgent.match(/Android/i)},BlackBerry:function(){return navigator.userAgent.match(/BlackBerry|BB10/i)},iOS:function(){return navigator.userAgent.match(/iPhone|iPad|iPod/i)},Opera:function(){return navigator.userAgent.match(/Opera Mini/i)},Windows:function(){return navigator.userAgent.match(/IEMobile/i)},any:function(){return isMobile.Android()||isMobile.BlackBerry()||isMobile.iOS()||isMobile.Opera()||isMobile.Windows()},getOsName:function(){var osName="Unknown OS";return isMobile.Android()&&(osName="Android"),isMobile.BlackBerry()&&(osName="BlackBerry"),isMobile.iOS()&&(osName="iOS"),isMobile.Opera()&&(osName="Opera Mini"),isMobile.Windows()&&(osName="Windows"),osName}},osName="Unknown OS",osVersion="Unknown OS Version",osInfo=detectDesktopOS();osInfo&&osInfo.osName&&"-"!=osInfo.osName?(osName=osInfo.osName,osVersion=osInfo.osVersion):isMobile.any()&&(osName=isMobile.getOsName(),"Android"==osName&&(osVersion=getAndroidVersion()));var isNodejs="object"==typeof process&&"object"==typeof process.versions&&process.versions.node;"Unknown OS"===osName&&isNodejs&&(osName="Nodejs",osVersion=process.versions.node.toString().replace("v",""));var isCanvasSupportsStreamCapturing=!1,isVideoSupportsStreamCapturing=!1;["captureStream","mozCaptureStream","webkitCaptureStream"].forEach(function(item){"undefined"!=typeof document&&"function"==typeof document.createElement&&(!isCanvasSupportsStreamCapturing&&item in document.createElement("canvas")&&(isCanvasSupportsStreamCapturing=!0),!isVideoSupportsStreamCapturing&&item in document.createElement("video")&&(isVideoSupportsStreamCapturing=!0))});const regexIpv4Local=/^(192\.168\.|169\.254\.|10\.|172\.(1[6-9]|2\d|3[01]))/,regexIpv4=/([0-9]{1,3}(\.[0-9]{1,3}){3})/,regexIpv6=/[a-f0-9]{1,4}(:[a-f0-9]{1,4}){7}/;var MediaDevices=[],audioInputDevices=[],audioOutputDevices=[],videoInputDevices=[];navigator.mediaDevices&&navigator.mediaDevices.enumerateDevices&&(navigator.enumerateDevices=function(callback){var enumerateDevices=navigator.mediaDevices.enumerateDevices();enumerateDevices&&enumerateDevices.then?navigator.mediaDevices.enumerateDevices().then(callback)["catch"](function(){callback([])}):callback([])});var canEnumerate=!1;"undefined"!=typeof MediaStreamTrack&&"getSources"in MediaStreamTrack?canEnumerate=!0:navigator.mediaDevices&&navigator.mediaDevices.enumerateDevices&&(canEnumerate=!0);var hasMicrophone=!1,hasSpeakers=!1,hasWebcam=!1,isWebsiteHasMicrophonePermissions=!1,isWebsiteHasWebcamPermissions=!1,DetectRTC=window.DetectRTC||{};DetectRTC.browser=getBrowserInfo(),detectPrivateMode(function(isPrivateBrowsing){DetectRTC.browser.isPrivateBrowsing=!!isPrivateBrowsing}),DetectRTC.browser["is"+DetectRTC.browser.name]=!0,DetectRTC.osName=osName,DetectRTC.osVersion=osVersion;var isWebRTCSupported=("object"==typeof process&&"object"==typeof process.versions&&process.versions["node-webkit"],!1);["RTCPeerConnection","webkitRTCPeerConnection","mozRTCPeerConnection","RTCIceGatherer"].forEach(function(item){isWebRTCSupported||item in window&&(isWebRTCSupported=!0)}),DetectRTC.isWebRTCSupported=isWebRTCSupported,DetectRTC.isORTCSupported="undefined"!=typeof RTCIceGatherer;var isScreenCapturingSupported=!1;if(DetectRTC.browser.isChrome&&DetectRTC.browser.version>=35?isScreenCapturingSupported=!0:DetectRTC.browser.isFirefox&&DetectRTC.browser.version>=34?isScreenCapturingSupported=!0:DetectRTC.browser.isEdge&&DetectRTC.browser.version>=17?isScreenCapturingSupported=!0:"Android"===DetectRTC.osName&&DetectRTC.browser.isChrome&&(isScreenCapturingSupported=!0),!/^(https:|chrome-extension:)$/g.test(location.protocol||"")){var isNonLocalHost="undefined"!=typeof document&&"string"==typeof document.domain&&document.domain.search&&document.domain.search(/localhost|127.0./g)===-1;isNonLocalHost&&(DetectRTC.browser.isChrome||DetectRTC.browser.isEdge||DetectRTC.browser.isOpera)?isScreenCapturingSupported=!1:DetectRTC.browser.isFirefox&&(isScreenCapturingSupported=!1)}DetectRTC.isScreenCapturingSupported=isScreenCapturingSupported;var webAudio={isSupported:!1,isCreateMediaStreamSourceSupported:!1};["AudioContext","webkitAudioContext","mozAudioContext","msAudioContext"].forEach(function(item){webAudio.isSupported||item in window&&(webAudio.isSupported=!0,window[item]&&"createMediaStreamSource"in window[item].prototype&&(webAudio.isCreateMediaStreamSourceSupported=!0))}),DetectRTC.isAudioContextSupported=webAudio.isSupported,DetectRTC.isCreateMediaStreamSourceSupported=webAudio.isCreateMediaStreamSourceSupported;var isRtpDataChannelsSupported=!1;DetectRTC.browser.isChrome&&DetectRTC.browser.version>31&&(isRtpDataChannelsSupported=!0),DetectRTC.isRtpDataChannelsSupported=isRtpDataChannelsSupported;var isSCTPSupportd=!1;DetectRTC.browser.isFirefox&&DetectRTC.browser.version>28?isSCTPSupportd=!0:DetectRTC.browser.isChrome&&DetectRTC.browser.version>25?isSCTPSupportd=!0:DetectRTC.browser.isOpera&&DetectRTC.browser.version>=11&&(isSCTPSupportd=!0),DetectRTC.isSctpDataChannelsSupported=isSCTPSupportd,DetectRTC.isMobileDevice=isMobileDevice;var isGetUserMediaSupported=!1;navigator.getUserMedia?isGetUserMediaSupported=!0:navigator.mediaDevices&&navigator.mediaDevices.getUserMedia&&(isGetUserMediaSupported=!0),DetectRTC.browser.isChrome&&DetectRTC.browser.version>=46&&!/^(https:|chrome-extension:)$/g.test(location.protocol||"")&&"undefined"!=typeof document&&"string"==typeof document.domain&&document.domain.search&&document.domain.search(/localhost|127.0./g)===-1&&(isGetUserMediaSupported="Requires HTTPs"),"Nodejs"===DetectRTC.osName&&(isGetUserMediaSupported=!1),DetectRTC.isGetUserMediaSupported=isGetUserMediaSupported;var displayResolution="";if(screen.width){var width=screen.width?screen.width:"",height=screen.height?screen.height:"";displayResolution+=""+width+" x "+height}DetectRTC.displayResolution=displayResolution,DetectRTC.displayAspectRatio=getAspectRatio(screen.width,screen.height).toFixed(2),DetectRTC.isCanvasSupportsStreamCapturing=isCanvasSupportsStreamCapturing,DetectRTC.isVideoSupportsStreamCapturing=isVideoSupportsStreamCapturing,"Chrome"==DetectRTC.browser.name&&DetectRTC.browser.version>=53&&(DetectRTC.isCanvasSupportsStreamCapturing||(DetectRTC.isCanvasSupportsStreamCapturing="Requires chrome flag: enable-experimental-web-platform-features"),DetectRTC.isVideoSupportsStreamCapturing||(DetectRTC.isVideoSupportsStreamCapturing="Requires chrome flag: enable-experimental-web-platform-features")),DetectRTC.DetectLocalIPAddress=DetectLocalIPAddress,DetectRTC.isWebSocketsSupported="WebSocket"in window&&2===window.WebSocket.CLOSING,DetectRTC.isWebSocketsBlocked=!DetectRTC.isWebSocketsSupported,"Nodejs"===DetectRTC.osName&&(DetectRTC.isWebSocketsSupported=!0,DetectRTC.isWebSocketsBlocked=!1),DetectRTC.checkWebSocketsSupport=function(callback){callback=callback||function(){};try{var starttime,websocket=new WebSocket("wss://echo.websocket.org:443/");websocket.onopen=function(){DetectRTC.isWebSocketsBlocked=!1,starttime=(new Date).getTime(),websocket.send("ping")},websocket.onmessage=function(){DetectRTC.WebsocketLatency=(new Date).getTime()-starttime+"ms",callback(),websocket.close(),websocket=null},websocket.onerror=function(){DetectRTC.isWebSocketsBlocked=!0,callback()}}catch(e){DetectRTC.isWebSocketsBlocked=!0,callback()}},DetectRTC.load=function(callback){callback=callback||function(){},checkDeviceSupport(callback)},"undefined"!=typeof MediaDevices?DetectRTC.MediaDevices=MediaDevices:DetectRTC.MediaDevices=[],DetectRTC.hasMicrophone=hasMicrophone,DetectRTC.hasSpeakers=hasSpeakers,DetectRTC.hasWebcam=hasWebcam,DetectRTC.isWebsiteHasWebcamPermissions=isWebsiteHasWebcamPermissions,DetectRTC.isWebsiteHasMicrophonePermissions=isWebsiteHasMicrophonePermissions,DetectRTC.audioInputDevices=audioInputDevices,DetectRTC.audioOutputDevices=audioOutputDevices,DetectRTC.videoInputDevices=videoInputDevices;var isSetSinkIdSupported=!1;"undefined"!=typeof document&&"function"==typeof document.createElement&&"setSinkId"in document.createElement("video")&&(isSetSinkIdSupported=!0),DetectRTC.isSetSinkIdSupported=isSetSinkIdSupported;var isRTPSenderReplaceTracksSupported=!1;DetectRTC.browser.isFirefox&&"undefined"!=typeof mozRTCPeerConnection?"getSenders"in mozRTCPeerConnection.prototype&&(isRTPSenderReplaceTracksSupported=!0):DetectRTC.browser.isChrome&&"undefined"!=typeof webkitRTCPeerConnection&&"getSenders"in webkitRTCPeerConnection.prototype&&(isRTPSenderReplaceTracksSupported=!0),DetectRTC.isRTPSenderReplaceTracksSupported=isRTPSenderReplaceTracksSupported;var isRemoteStreamProcessingSupported=!1;DetectRTC.browser.isFirefox&&DetectRTC.browser.version>38&&(isRemoteStreamProcessingSupported=!0),DetectRTC.isRemoteStreamProcessingSupported=isRemoteStreamProcessingSupported;var isApplyConstraintsSupported=!1;"undefined"!=typeof MediaStreamTrack&&"applyConstraints"in MediaStreamTrack.prototype&&(isApplyConstraintsSupported=!0),DetectRTC.isApplyConstraintsSupported=isApplyConstraintsSupported;var isMultiMonitorScreenCapturingSupported=!1;DetectRTC.browser.isFirefox&&DetectRTC.browser.version>=43&&(isMultiMonitorScreenCapturingSupported=!0),DetectRTC.isMultiMonitorScreenCapturingSupported=isMultiMonitorScreenCapturingSupported,DetectRTC.isPromisesSupported=!!("Promise"in window),DetectRTC.version="1.3.8","undefined"==typeof DetectRTC&&(window.DetectRTC={});var MediaStream=window.MediaStream;"undefined"==typeof MediaStream&&"undefined"!=typeof webkitMediaStream&&(MediaStream=webkitMediaStream),"undefined"!=typeof MediaStream&&"function"==typeof MediaStream?DetectRTC.MediaStream=Object.keys(MediaStream.prototype):DetectRTC.MediaStream=!1,"undefined"!=typeof MediaStreamTrack?DetectRTC.MediaStreamTrack=Object.keys(MediaStreamTrack.prototype):DetectRTC.MediaStreamTrack=!1;var RTCPeerConnection=window.RTCPeerConnection||window.mozRTCPeerConnection||window.webkitRTCPeerConnection;"undefined"!=typeof RTCPeerConnection?DetectRTC.RTCPeerConnection=Object.keys(RTCPeerConnection.prototype):DetectRTC.RTCPeerConnection=!1,window.DetectRTC=DetectRTC,"undefined"!=typeof module&&(module.exports=DetectRTC),"function"==typeof define&&define.amd&&define("DetectRTC",[],function(){return DetectRTC})}(); \ No newline at end of file diff --git a/DetectRTC/Gruntfile.js b/DetectRTC/Gruntfile.js index a10ce0bb..156b71c2 100644 --- a/DetectRTC/Gruntfile.js +++ b/DetectRTC/Gruntfile.js @@ -136,6 +136,15 @@ module.exports = function(grunt) { pushTo: 'upstream', gitDescribeOptions: '--tags --always --abbrev=1 --dirty=-d' } + }, + watch: { + scripts: { + files: ['dev/*.js'], + tasks: ['concat', 'replace', 'jsbeautifier', 'uglify', 'clean'], + options: { + spawn: false, + }, + } } }); @@ -144,4 +153,5 @@ module.exports = function(grunt) { // set default tasks to run when grunt is called without parameters // http://gruntjs.com/api/grunt.task grunt.registerTask('default', ['concat', 'replace', 'jsbeautifier', 'uglify', 'clean']); + grunt.loadNpmTasks('grunt-contrib-watch'); }; diff --git a/DetectRTC/bower.json b/DetectRTC/bower.json index 5253067c..6d898e42 100644 --- a/DetectRTC/bower.json +++ b/DetectRTC/bower.json @@ -1,11 +1,11 @@ { "name": "detectrtc", - "version": "1.3.6", + "version": "1.3.9", "authors": [ { "name": "Muaz Khan", "email": "muazkh@gmail.com", - "homepage": "http://www.muazkhan.com/" + "homepage": "https://muazkhan.com/" }, { "name": "Kenneth Miles", diff --git a/DetectRTC/dev/DetectLocalIPAddress.js b/DetectRTC/dev/DetectLocalIPAddress.js index 11bbe7e1..3fb0b772 100644 --- a/DetectRTC/dev/DetectLocalIPAddress.js +++ b/DetectRTC/dev/DetectLocalIPAddress.js @@ -1,14 +1,26 @@ +const regexIpv4Local = /^(192\.168\.|169\.254\.|10\.|172\.(1[6-9]|2\d|3[01]))/, + regexIpv4 = /([0-9]{1,3}(\.[0-9]{1,3}){3})/, + regexIpv6 = /[a-f0-9]{1,4}(:[a-f0-9]{1,4}){7}/; + // via: https://github.com/diafygi/webrtc-ips function DetectLocalIPAddress(callback, stream) { if (!DetectRTC.isWebRTCSupported) { return; } + var isPublic = true, + isIpv4 = true; getIPs(function(ip) { - if (ip.match(/^(192\.168\.|169\.254\.|10\.|172\.(1[6-9]|2\d|3[01]))/)) { - callback('Local: ' + ip); + if (!ip) { + callback(); // Pass nothing to tell that ICE-gathering-ended + } else if (ip.match(regexIpv4Local)) { + isPublic = false; + callback('Local: ' + ip, isPublic, isIpv4); + } else if (ip.match(regexIpv6)) { //via https://ourcodeworld.com/articles/read/257/how-to-get-the-client-ip-address-with-javascript-only + isIpv4 = false; + callback('Public: ' + ip, isPublic, isIpv4); } else { - callback('Public: ' + ip); + callback('Public: ' + ip, isPublic, isIpv4); } }, stream); } @@ -63,24 +75,32 @@ function getIPs(callback, stream) { } function handleCandidate(candidate) { - var ipRegex = /([0-9]{1,3}(\.[0-9]{1,3}){3})/; - var match = ipRegex.exec(candidate); + if (!candidate) { + callback(); // Pass nothing to tell that ICE-gathering-ended + return; + } + + var match = regexIpv4.exec(candidate); if (!match) { return; } var ipAddress = match[1]; + const isPublic = (candidate.match(regexIpv4Local)), + isIpv4 = true; if (ipDuplicates[ipAddress] === undefined) { - callback(ipAddress); + callback(ipAddress, isPublic, isIpv4); } ipDuplicates[ipAddress] = true; } // listen for candidate events - pc.onicecandidate = function(ice) { - if (ice.candidate) { - handleCandidate(ice.candidate.candidate); + pc.onicecandidate = function(event) { + if (event.candidate && event.candidate.candidate) { + handleCandidate(event.candidate.candidate); + } else { + handleCandidate(); // Pass nothing to tell that ICE-gathering-ended } }; @@ -106,7 +126,7 @@ function getIPs(callback, stream) { var lines = pc.localDescription.sdp.split('\n'); lines.forEach(function(line) { - if (line.indexOf('a=candidate:') === 0) { + if (line && line.indexOf('a=candidate:') === 0) { handleCandidate(line); } }); diff --git a/DetectRTC/dev/getBrowserInfo.js b/DetectRTC/dev/getBrowserInfo.js index c300e2e5..091ef6c8 100644 --- a/DetectRTC/dev/getBrowserInfo.js +++ b/DetectRTC/dev/getBrowserInfo.js @@ -19,6 +19,12 @@ function getBrowserInfo() { var majorVersion = parseInt(navigator.appVersion, 10); var nameOffset, verOffset, ix; + // both and safri and chrome has same userAgent + if (isSafari && !isChrome && nAgt.indexOf('CriOS') !== -1) { + isSafari = false; + isChrome = true; + } + // In Opera, the true version is after 'Opera' or after 'Version' if (isOpera) { browserName = 'Opera'; diff --git a/DetectRTC/index.html b/DetectRTC/index.html index c89825ee..d05741c9 100755 --- a/DetectRTC/index.html +++ b/DetectRTC/index.html @@ -1,4 +1,4 @@ - + +
+ + DetectRTC + DetectRTC Google Chrome Extension + +
+ - - - - - - - - diff --git a/DetectRTC/package.json b/DetectRTC/package.json index 8fc4cc06..af99d257 100644 --- a/DetectRTC/package.json +++ b/DetectRTC/package.json @@ -1,11 +1,11 @@ { "name": "detectrtc", "preferGlobal": false, - "version": "1.3.6", + "version": "1.3.9", "author": { "name": "Muaz Khan", "email": "muazkh@gmail.com", - "url": "http://www.muazkhan.com/" + "url": "https://muazkhan.com/" }, "description": "A tiny JavaScript library that can be used to detect WebRTC features e.g. system having speakers, microphone or webcam, screen capturing is supported, number of audio/video devices etc.", "repository": { @@ -51,6 +51,7 @@ "grunt-jsbeautifier": "0.2.10", "grunt-replace": "0.11.0", "grunt-contrib-clean": "0.6.0", - "grunt-bump": "0.7.0" + "grunt-bump": "0.7.0", + "grunt-contrib-watch": "^1.1.0" } } diff --git a/FileBufferReader/.npmignore b/FileBufferReader/.npmignore index ecc3bd1a..bca5c3b2 100644 --- a/FileBufferReader/.npmignore +++ b/FileBufferReader/.npmignore @@ -4,8 +4,6 @@ # but not these files... !FileBufferReader.js !FileBufferReader.min.js -!index.html !package.json !bower.json -!server.js !README.md \ No newline at end of file diff --git a/FileBufferReader/bower.json b/FileBufferReader/bower.json index 4ac54092..2671c9b0 100755 --- a/FileBufferReader/bower.json +++ b/FileBufferReader/bower.json @@ -1,7 +1,7 @@ { "name": "fbr", "preferGlobal": true, - "version": "2.0.5", + "version": "2.0.8", "author": { "name": "Muaz Khan", "email": "muazkh@gmail.com", diff --git a/Chrome-Extensions/desktopCapture-p2p/IceServersHandler.js b/FileBufferReader/demo/IceServersHandler.js similarity index 100% rename from Chrome-Extensions/desktopCapture-p2p/IceServersHandler.js rename to FileBufferReader/demo/IceServersHandler.js diff --git a/FileBufferReader/demo/PeerConnection.js b/FileBufferReader/demo/PeerConnection.js index 93f40df0..57a9b619 100644 --- a/FileBufferReader/demo/PeerConnection.js +++ b/FileBufferReader/demo/PeerConnection.js @@ -1,4 +1,4 @@ -// Last time updated at Feb 15, 2016, 08:32:23 +// Last time updated at November 17, 2018 // Muaz Khan - www.MuazKhan.com // MIT License - www.WebRTC-Experiment.com/licence @@ -13,7 +13,7 @@ this.userid = userid || getToken(); this.peers = {}; - if (!socketURL) throw 'Socket-URL is mandatory.'; + if (!socketURL) throw 'Socket-URL is required.'; var signaler = new Signaler(this, socketURL); @@ -21,15 +21,18 @@ this.send = function(data) { var channel = answererDataChannel || offererDataChannel; - if (channel.readyState != 'open') + if (channel.readyState != 'open') { return setTimeout(function() { that.send(data); }, 1000); + } channel.send(data); }; signaler.ondata = function(data) { - if (that.ondata) that.ondata(data); + if (that.ondata) { + that.ondata(data); + } }; this.onopen = function() { @@ -47,8 +50,7 @@ broadcasting: true }); - !self.participantFound && !self.stopBroadcasting && - setTimeout(transmit, 3000); + !self.participantFound && !self.stopBroadcasting && setTimeout(transmit, 3000); })(); }; @@ -113,7 +115,9 @@ self.stopBroadcasting = true; for (var userid in root.peers) { - root.peers[userid].peer.close(); + if(root.peers[userid] && root.peers[userid].peer) { + root.peers[userid].peer.close(); + } } root.peers = {}; } @@ -132,8 +136,9 @@ }; window.onkeyup = function(e) { - if (e.keyCode == 116) + if (e.keyCode == 116) { root.close(); + } }; // users who broadcasts themselves @@ -227,11 +232,32 @@ }; var optionalArgument = { - optional: [], + optional: [{ + DtlsSrtpKeyAgreement: true + }, { + googImprovedWifiBwe: true + }, { + googScreencastMinBitrate: 300 + }, { + googIPv6: true + }, { + googDscp: true + }, { + googCpuUnderuseThreshold: 55 + }, { + googCpuOveruseThreshold: 85 + }, { + googSuspendBelowMinBitrate: true + }, { + googCpuOveruseDetection: true + }], mandatory: {} }; var offerAnswerConstraints = { + OfferToReceiveAudio: false, + OfferToReceiveVideo: false, + optional: [], mandatory: { OfferToReceiveAudio: false, @@ -254,9 +280,11 @@ function setChannelEvents(channel, config) { channel.binaryType = 'arraybuffer'; + channel.onmessage = function(event) { config.ondata(event.data); }; + channel.onopen = function() { config.onopen(); }; @@ -281,30 +309,43 @@ self.config = config; peer.onicecandidate = function(event) { - if (event.candidate) + if (event.candidate && event.candidate.candidate) { config.onicecandidate(event.candidate); + } }; peer.onsignalingstatechange = function() { console.log('onsignalingstatechange:', JSON.stringify({ iceGatheringState: peer.iceGatheringState, - signalingState: peer.signalingState + signalingState: peer.signalingState, + iceConnectionState: peer.iceConnectionState })); + + if (peer.iceConnectionState.search(/closed|failed/gi) !== -1) { + config.onclose(); + } }; + peer.oniceconnectionstatechange = function() { console.log('oniceconnectionstatechange:', JSON.stringify({ iceGatheringState: peer.iceGatheringState, - signalingState: peer.signalingState + signalingState: peer.signalingState, + iceConnectionState: peer.iceConnectionState })); + + if (peer.iceConnectionState.search(/closed|failed/gi) !== -1) { + config.onclose(); + } }; this.createDataChannel(peer); window.peer = peer; - peer.createOffer(function(sdp) { - peer.setLocalDescription(sdp); - config.onsdp(sdp); - }, onSdpError, offerAnswerConstraints); + peer.createOffer(offerAnswerConstraints).then(function(sdp) { + peer.setLocalDescription(sdp).then(function() { + config.onsdp(sdp); + }); + }).catch(onSdpError); this.peer = peer; @@ -357,11 +398,13 @@ })); }; - peer.setRemoteDescription(new RTCSessionDescription(config.sdp)); - peer.createAnswer(function(sdp) { - peer.setLocalDescription(sdp); - config.onsdp(sdp); - }, onSdpError, offerAnswerConstraints); + peer.setRemoteDescription(new RTCSessionDescription(config.sdp)).then(function() { + peer.createAnswer(offerAnswerConstraints).then(function(sdp) { + peer.setLocalDescription(sdp).then(function() { + config.onsdp(sdp); + }); + }).catch(onSdpError); + }); this.peer = peer; diff --git a/FileBufferReader/demo/PeerUI.js b/FileBufferReader/demo/PeerUI.js index dfead727..d70b8a0a 100644 --- a/FileBufferReader/demo/PeerUI.js +++ b/FileBufferReader/demo/PeerUI.js @@ -9,6 +9,7 @@ window.addEventListener('load', function() { var setupOffer = document.getElementById('setup-offer'), innerHTML; + var SIGNALING_URI = 'wss://websocket-over-nodejs.herokuapp.com:443/'; var SIGNALING_URI = 'wss://webrtcweb.com:9449/'; var channel = location.href.replace(/\/|:|#|%|\.|\[|\]/g, ''); @@ -77,41 +78,44 @@ window.addEventListener('load', function() { html += ''; } else if (file.name.match(/\.webm|\.flv|\.mp4/gi)) { html += ''; - } else if (file.name.match(/\.pdf|\.js|\.txt|\.sh/gi)) { + } else if (file.name.match(/\.js|\.txt|\.sh/gi)) { html += ''; html += '
'; } - progressHelper[file.uuid].li.innerHTML = html; + progressHelper[file.uuid].div.innerHTML = html; fileSelector.lastSelectedFile = false; } var FileHelper = { onBegin: function(file) { - var li = document.createElement('li'); + var div = document.createElement('div'); - var html = '
';
+            var fName = '';
             if (file.extra && file.extra.webkitRelativePath) {
-                html += file.extra.webkitRelativePath;
+                fName = file.extra.webkitRelativePath;
             } else {
-                html += file.name;
+                fName = file.name;
             }
-            html += '

25%
'; - li.innerHTML = html; - li.style['min-height'] = '350px'; - outputPanel.insertBefore(li, outputPanel.firstChild); + var html = '
'; + html += '
'; + html += '
'; + html += '
1% complete
'; + html += '
'; + html += ''; + html += ''; + html += ''; + html += '
'; - // outputPanel.className = 'fit-screen'; - // outputPanel.style.height = innerHeight + 'px'; + div.innerHTML = html; + outputPanel.insertBefore(div, outputPanel.firstChild); progressHelper[file.uuid] = { - li: li, - progress: li.querySelector('progress'), - label: li.querySelector('label') + div: div, + file: file }; - progressHelper[file.uuid].progress.max = file.maxChunks; btnSelectFile.disabled = true; btnSelectDirectory.disabled = true; @@ -140,9 +144,42 @@ window.addEventListener('load', function() { } resetTimeCalculator(); - timeCalculator(progressHelper[file.uuid].progress); + timeCalculator(div.querySelector('progress')); progressHelper.lastFileUUID = file.uuid; + + div.querySelector('.btn-close').onclick = function() { + peerConnection.stopCallback = function() { + div.parentNode.removeChild(div); + peerConnection.send('stopped:::' + file.uuid); + isStoppedTimer = true; + }; + }; + + var paused = false; + div.querySelector('.btn-pause').onclick = function() { + var btn = div.querySelector('.btn-pause'); + if(paused) { + paused = false; + isPausedTimer = false; + btn.style.backgroundImage = 'url(https://cdn.webrtc-experiment.com/FileBufferReader/icons/pause-icon.png)'; + if(peerConnection.resumeCallback) { + peerConnection.resumeCallback(); + } + peerConnection.paused = false; + + peerConnection.send('resumed:::' + file.uuid); + return; + } + + paused = true; + isPausedTimer = true; + btn.style.backgroundImage = 'url(https://cdn.webrtc-experiment.com/FileBufferReader/icons/resume-icon.png)'; + peerConnection.resumeCallback = null; + peerConnection.paused = true; + + peerConnection.send('paused:::' + file.uuid); + }; }, onEnd: function(file) { previewFile(file); @@ -157,8 +194,6 @@ window.addEventListener('load', function() { } progressHelper.lastFileUUID = null; - // outputPanel.className = ''; - // outputPanel.style.height = 'auto'; if (filesRemaining.files) { filesRemaining.idx++; @@ -167,39 +202,46 @@ window.addEventListener('load', function() { }, onProgress: function(chunk) { var helper = progressHelper[chunk.uuid]; - helper.progress.value = chunk.currentPosition || chunk.maxChunks || helper.progress.max; + if(!helper) return; + var div = helper.div; + var file = helper.file; + + var progress = div.querySelector('progress'); + var percentComplete = div.querySelector('.percent-complete'); + var itemsRemaining = div.querySelector('.items-remaining'); + var sizeRemaining = div.querySelector('.size-remaining'); + var timeRemaining = div.querySelector('.time-remaining'); - if (helper.progress.position > 0 && helper.li.querySelector('.circular-progress-bar-percentage')) { - var position = +helper.progress.position.toFixed(2).split('.')[1] || 100; - helper.li.querySelector('.circular-progress-bar-percentage').innerHTML = position + '%'; - helper.li.querySelector('.circular-progress-bar').className = 'circular-progress-bar c100 p' + position; + if(!progress) return; + + progress.value = chunk.currentPosition || chunk.maxChunks || progress.max; + + if (progress.position > 0) { + var position = +progress.position.toFixed(2).split('.')[1] || 100; + percentComplete.innerHTML = position + '% complete'; } - if (chunk.currentPosition + 2 != chunk.maxChunks && helper.li.querySelector('.file-name')) { + if (chunk.currentPosition + 2 != chunk.maxChunks) { progressHelper[chunk.uuid].lastChunk = chunk; - progressHelper.callback = function(timeRemaining) { + progressHelper.callback = function(tRemaining) { var lastChunk = progressHelper[chunk.uuid].lastChunk; var singleChunkSize = chunk.size / lastChunk.maxChunks; - var html = 'File name: ' + lastChunk.name + ' (File size: ' + bytesToSize(chunk.size) + ')'; - html += '
Pieces (total/remaining): ' + lastChunk.maxChunks + '/' + lastChunk.currentPosition; - html += ' (Single piece size: ' + bytesToSize(singleChunkSize) + ')'; - var endedAt = (new Date).getTime(); var timeElapsed = endedAt - (progressHelper.startedAt || (new Date).getTime()); progressHelper.latencies.push(timeElapsed); var avg = calculateAverage(progressHelper.latencies); - html += '
Latency in millseconds: ' + timeElapsed + ' (Average): ' + avg + ''; + // html += '
Latency in millseconds: ' + timeElapsed + ' (Average): ' + avg + ''; var remainingFileSize = singleChunkSize * (lastChunk.maxChunks - lastChunk.currentPosition); - html += '
Remaining (time): ' + timeRemaining + ' (Remaining file size): ' + bytesToSize(remainingFileSize); - - helper.li.querySelector('.file-name').innerHTML = html; - progressHelper.startedAt = (new Date).getTime(); + + itemsRemaining.innerHTML = (lastChunk.maxChunks - lastChunk.currentPosition); + sizeRemaining.innerHTML = bytesToSize(remainingFileSize); + timeRemaining.innerHTML = tRemaining; }; } else { btnSelectFile.innerHTML = 'Single'; @@ -250,9 +292,9 @@ window.addEventListener('load', function() { peerConnection.isOpened = false; var helper = progressHelper[progressHelper.lastFileUUID]; - if (helper && helper.li && helper.li.parentNode) { + if (helper && helper.div && helper.div.parentNode) { isStoppedTimer = true; - helper.li.parentNode.removeChild(helper.li); + helper.div.parentNode.removeChild(helper.div); } }; @@ -287,6 +329,34 @@ window.addEventListener('load', function() { }; peerConnection.ondata = function(chunk) { + if(typeof chunk === 'string' && chunk.indexOf('stopped:::') !== -1) { + var div = document.getElementById(chunk.split('stopped:::')[1]); + if(div && div.parentNode) { + div.parentNode.removeChild(div); + } + + isStoppedTimer = true; + return; + } + + if(typeof chunk === 'string' && chunk.indexOf('paused:::') !== -1) { + var div = document.getElementById(chunk.split('paused:::')[1]); + if(div && div.querySelector('.btn-pause')) { + div.querySelector('.btn-pause').style.backgroundImage = 'url(https://cdn.webrtc-experiment.com/FileBufferReader/icons/resume-icon.png)'; + } + isPausedTimer = true; + return; + } + + if(typeof chunk === 'string' && chunk.indexOf('resumed:::') !== -1) { + var div = document.getElementById(chunk.split('resumed:::')[1]); + if(div && div.querySelector('.btn-pause')) { + div.querySelector('.btn-pause').style.backgroundImage = 'url(https://cdn.webrtc-experiment.com/FileBufferReader/icons/pause-icon.png)'; + } + isPausedTimer = false; + return; + } + if (chunk instanceof ArrayBuffer || chunk instanceof DataView) { // array buffers are passed using WebRTC data channels // need to convert data back into JavaScript objects @@ -299,6 +369,18 @@ window.addEventListener('load', function() { // if target user requested next chunk if (chunk.readyForNextChunk) { + if(peerConnection.paused) { + peerConnection.resumeCallback = function() { + fileBufferReader.getNextChunk(chunk, getNextChunkCallback); + }; + return; + } + + if(peerConnection.stopCallback) { + peerConnection.stopCallback(); + return; + } + fileBufferReader.getNextChunk(chunk /*aka metadata*/ , getNextChunkCallback); return; } @@ -312,6 +394,19 @@ window.addEventListener('load', function() { // if chunk is received fileBufferReader.addChunk(chunk, function(promptNextChunk) { // request next chunk + + if(peerConnection.paused) { + peerConnection.resumeCallback = function() { + peerConnection.send(promptNextChunk); + }; + return; + } + + if(peerConnection.stopCallback) { + peerConnection.stopCallback(); + return; + } + peerConnection.send(promptNextChunk); }); }; @@ -337,13 +432,13 @@ window.addEventListener('load', function() { } var isStoppedTimer = false; + var isPausedTimer = false; // https://github.com/23/resumable.js/issues/168#issuecomment-65297110 function timeCalculator(progress, selfInvoker) { if (isStoppedTimer) return; var step = 1; - var remainingProgress = 1.0 - progress.position; var estimatedCompletionTime = Math.round((remainingProgress / progress.position) * progressIterations); @@ -370,7 +465,8 @@ window.addEventListener('load', function() { if (displaySeconds > 0) { output += displaySeconds + ' seconds '; } - if (output.length) { + + if (output.length && !isPausedTimer) { progressHelper.callback(output); } } diff --git a/FileBufferReader/demo/adapter-latest.js b/FileBufferReader/demo/adapter-latest.js new file mode 100644 index 00000000..7bdead59 --- /dev/null +++ b/FileBufferReader/demo/adapter-latest.js @@ -0,0 +1,5438 @@ +(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.adapter = f()}})(function(){var define,module,exports;return (function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i= 14393 && + url.indexOf('?transport=udp') === -1; + }); + + delete server.url; + server.urls = isString ? urls[0] : urls; + return !!urls.length; + } + }); +} + +// Determines the intersection of local and remote capabilities. +function getCommonCapabilities(localCapabilities, remoteCapabilities) { + var commonCapabilities = { + codecs: [], + headerExtensions: [], + fecMechanisms: [] + }; + + var findCodecByPayloadType = function(pt, codecs) { + pt = parseInt(pt, 10); + for (var i = 0; i < codecs.length; i++) { + if (codecs[i].payloadType === pt || + codecs[i].preferredPayloadType === pt) { + return codecs[i]; + } + } + }; + + var rtxCapabilityMatches = function(lRtx, rRtx, lCodecs, rCodecs) { + var lCodec = findCodecByPayloadType(lRtx.parameters.apt, lCodecs); + var rCodec = findCodecByPayloadType(rRtx.parameters.apt, rCodecs); + return lCodec && rCodec && + lCodec.name.toLowerCase() === rCodec.name.toLowerCase(); + }; + + localCapabilities.codecs.forEach(function(lCodec) { + for (var i = 0; i < remoteCapabilities.codecs.length; i++) { + var rCodec = remoteCapabilities.codecs[i]; + if (lCodec.name.toLowerCase() === rCodec.name.toLowerCase() && + lCodec.clockRate === rCodec.clockRate) { + if (lCodec.name.toLowerCase() === 'rtx' && + lCodec.parameters && rCodec.parameters.apt) { + // for RTX we need to find the local rtx that has a apt + // which points to the same local codec as the remote one. + if (!rtxCapabilityMatches(lCodec, rCodec, + localCapabilities.codecs, remoteCapabilities.codecs)) { + continue; + } + } + rCodec = JSON.parse(JSON.stringify(rCodec)); // deepcopy + // number of channels is the highest common number of channels + rCodec.numChannels = Math.min(lCodec.numChannels, + rCodec.numChannels); + // push rCodec so we reply with offerer payload type + commonCapabilities.codecs.push(rCodec); + + // determine common feedback mechanisms + rCodec.rtcpFeedback = rCodec.rtcpFeedback.filter(function(fb) { + for (var j = 0; j < lCodec.rtcpFeedback.length; j++) { + if (lCodec.rtcpFeedback[j].type === fb.type && + lCodec.rtcpFeedback[j].parameter === fb.parameter) { + return true; + } + } + return false; + }); + // FIXME: also need to determine .parameters + // see https://github.com/openpeer/ortc/issues/569 + break; + } + } + }); + + localCapabilities.headerExtensions.forEach(function(lHeaderExtension) { + for (var i = 0; i < remoteCapabilities.headerExtensions.length; + i++) { + var rHeaderExtension = remoteCapabilities.headerExtensions[i]; + if (lHeaderExtension.uri === rHeaderExtension.uri) { + commonCapabilities.headerExtensions.push(rHeaderExtension); + break; + } + } + }); + + // FIXME: fecMechanisms + return commonCapabilities; +} + +// is action=setLocalDescription with type allowed in signalingState +function isActionAllowedInSignalingState(action, type, signalingState) { + return { + offer: { + setLocalDescription: ['stable', 'have-local-offer'], + setRemoteDescription: ['stable', 'have-remote-offer'] + }, + answer: { + setLocalDescription: ['have-remote-offer', 'have-local-pranswer'], + setRemoteDescription: ['have-local-offer', 'have-remote-pranswer'] + } + }[type][action].indexOf(signalingState) !== -1; +} + +function maybeAddCandidate(iceTransport, candidate) { + // Edge's internal representation adds some fields therefore + // not all fieldѕ are taken into account. + var alreadyAdded = iceTransport.getRemoteCandidates() + .find(function(remoteCandidate) { + return candidate.foundation === remoteCandidate.foundation && + candidate.ip === remoteCandidate.ip && + candidate.port === remoteCandidate.port && + candidate.priority === remoteCandidate.priority && + candidate.protocol === remoteCandidate.protocol && + candidate.type === remoteCandidate.type; + }); + if (!alreadyAdded) { + iceTransport.addRemoteCandidate(candidate); + } + return !alreadyAdded; +} + + +function makeError(name, description) { + var e = new Error(description); + e.name = name; + // legacy error codes from https://heycam.github.io/webidl/#idl-DOMException-error-names + e.code = { + NotSupportedError: 9, + InvalidStateError: 11, + InvalidAccessError: 15, + TypeError: undefined, + OperationError: undefined + }[name]; + return e; +} + +module.exports = function(window, edgeVersion) { + // https://w3c.github.io/mediacapture-main/#mediastream + // Helper function to add the track to the stream and + // dispatch the event ourselves. + function addTrackToStreamAndFireEvent(track, stream) { + stream.addTrack(track); + stream.dispatchEvent(new window.MediaStreamTrackEvent('addtrack', + {track: track})); + } + + function removeTrackFromStreamAndFireEvent(track, stream) { + stream.removeTrack(track); + stream.dispatchEvent(new window.MediaStreamTrackEvent('removetrack', + {track: track})); + } + + function fireAddTrack(pc, track, receiver, streams) { + var trackEvent = new Event('track'); + trackEvent.track = track; + trackEvent.receiver = receiver; + trackEvent.transceiver = {receiver: receiver}; + trackEvent.streams = streams; + window.setTimeout(function() { + pc._dispatchEvent('track', trackEvent); + }); + } + + var RTCPeerConnection = function(config) { + var pc = this; + + var _eventTarget = document.createDocumentFragment(); + ['addEventListener', 'removeEventListener', 'dispatchEvent'] + .forEach(function(method) { + pc[method] = _eventTarget[method].bind(_eventTarget); + }); + + this.canTrickleIceCandidates = null; + + this.needNegotiation = false; + + this.localStreams = []; + this.remoteStreams = []; + + this._localDescription = null; + this._remoteDescription = null; + + this.signalingState = 'stable'; + this.iceConnectionState = 'new'; + this.connectionState = 'new'; + this.iceGatheringState = 'new'; + + config = JSON.parse(JSON.stringify(config || {})); + + this.usingBundle = config.bundlePolicy === 'max-bundle'; + if (config.rtcpMuxPolicy === 'negotiate') { + throw(makeError('NotSupportedError', + 'rtcpMuxPolicy \'negotiate\' is not supported')); + } else if (!config.rtcpMuxPolicy) { + config.rtcpMuxPolicy = 'require'; + } + + switch (config.iceTransportPolicy) { + case 'all': + case 'relay': + break; + default: + config.iceTransportPolicy = 'all'; + break; + } + + switch (config.bundlePolicy) { + case 'balanced': + case 'max-compat': + case 'max-bundle': + break; + default: + config.bundlePolicy = 'balanced'; + break; + } + + config.iceServers = filterIceServers(config.iceServers || [], edgeVersion); + + this._iceGatherers = []; + if (config.iceCandidatePoolSize) { + for (var i = config.iceCandidatePoolSize; i > 0; i--) { + this._iceGatherers.push(new window.RTCIceGatherer({ + iceServers: config.iceServers, + gatherPolicy: config.iceTransportPolicy + })); + } + } else { + config.iceCandidatePoolSize = 0; + } + + this._config = config; + + // per-track iceGathers, iceTransports, dtlsTransports, rtpSenders, ... + // everything that is needed to describe a SDP m-line. + this.transceivers = []; + + this._sdpSessionId = SDPUtils.generateSessionId(); + this._sdpSessionVersion = 0; + + this._dtlsRole = undefined; // role for a=setup to use in answers. + + this._isClosed = false; + }; + + Object.defineProperty(RTCPeerConnection.prototype, 'localDescription', { + configurable: true, + get: function() { + return this._localDescription; + } + }); + Object.defineProperty(RTCPeerConnection.prototype, 'remoteDescription', { + configurable: true, + get: function() { + return this._remoteDescription; + } + }); + + // set up event handlers on prototype + RTCPeerConnection.prototype.onicecandidate = null; + RTCPeerConnection.prototype.onaddstream = null; + RTCPeerConnection.prototype.ontrack = null; + RTCPeerConnection.prototype.onremovestream = null; + RTCPeerConnection.prototype.onsignalingstatechange = null; + RTCPeerConnection.prototype.oniceconnectionstatechange = null; + RTCPeerConnection.prototype.onconnectionstatechange = null; + RTCPeerConnection.prototype.onicegatheringstatechange = null; + RTCPeerConnection.prototype.onnegotiationneeded = null; + RTCPeerConnection.prototype.ondatachannel = null; + + RTCPeerConnection.prototype._dispatchEvent = function(name, event) { + if (this._isClosed) { + return; + } + this.dispatchEvent(event); + if (typeof this['on' + name] === 'function') { + this['on' + name](event); + } + }; + + RTCPeerConnection.prototype._emitGatheringStateChange = function() { + var event = new Event('icegatheringstatechange'); + this._dispatchEvent('icegatheringstatechange', event); + }; + + RTCPeerConnection.prototype.getConfiguration = function() { + return this._config; + }; + + RTCPeerConnection.prototype.getLocalStreams = function() { + return this.localStreams; + }; + + RTCPeerConnection.prototype.getRemoteStreams = function() { + return this.remoteStreams; + }; + + // internal helper to create a transceiver object. + // (which is not yet the same as the WebRTC 1.0 transceiver) + RTCPeerConnection.prototype._createTransceiver = function(kind, doNotAdd) { + var hasBundleTransport = this.transceivers.length > 0; + var transceiver = { + track: null, + iceGatherer: null, + iceTransport: null, + dtlsTransport: null, + localCapabilities: null, + remoteCapabilities: null, + rtpSender: null, + rtpReceiver: null, + kind: kind, + mid: null, + sendEncodingParameters: null, + recvEncodingParameters: null, + stream: null, + associatedRemoteMediaStreams: [], + wantReceive: true + }; + if (this.usingBundle && hasBundleTransport) { + transceiver.iceTransport = this.transceivers[0].iceTransport; + transceiver.dtlsTransport = this.transceivers[0].dtlsTransport; + } else { + var transports = this._createIceAndDtlsTransports(); + transceiver.iceTransport = transports.iceTransport; + transceiver.dtlsTransport = transports.dtlsTransport; + } + if (!doNotAdd) { + this.transceivers.push(transceiver); + } + return transceiver; + }; + + RTCPeerConnection.prototype.addTrack = function(track, stream) { + if (this._isClosed) { + throw makeError('InvalidStateError', + 'Attempted to call addTrack on a closed peerconnection.'); + } + + var alreadyExists = this.transceivers.find(function(s) { + return s.track === track; + }); + + if (alreadyExists) { + throw makeError('InvalidAccessError', 'Track already exists.'); + } + + var transceiver; + for (var i = 0; i < this.transceivers.length; i++) { + if (!this.transceivers[i].track && + this.transceivers[i].kind === track.kind) { + transceiver = this.transceivers[i]; + } + } + if (!transceiver) { + transceiver = this._createTransceiver(track.kind); + } + + this._maybeFireNegotiationNeeded(); + + if (this.localStreams.indexOf(stream) === -1) { + this.localStreams.push(stream); + } + + transceiver.track = track; + transceiver.stream = stream; + transceiver.rtpSender = new window.RTCRtpSender(track, + transceiver.dtlsTransport); + return transceiver.rtpSender; + }; + + RTCPeerConnection.prototype.addStream = function(stream) { + var pc = this; + if (edgeVersion >= 15025) { + stream.getTracks().forEach(function(track) { + pc.addTrack(track, stream); + }); + } else { + // Clone is necessary for local demos mostly, attaching directly + // to two different senders does not work (build 10547). + // Fixed in 15025 (or earlier) + var clonedStream = stream.clone(); + stream.getTracks().forEach(function(track, idx) { + var clonedTrack = clonedStream.getTracks()[idx]; + track.addEventListener('enabled', function(event) { + clonedTrack.enabled = event.enabled; + }); + }); + clonedStream.getTracks().forEach(function(track) { + pc.addTrack(track, clonedStream); + }); + } + }; + + RTCPeerConnection.prototype.removeTrack = function(sender) { + if (this._isClosed) { + throw makeError('InvalidStateError', + 'Attempted to call removeTrack on a closed peerconnection.'); + } + + if (!(sender instanceof window.RTCRtpSender)) { + throw new TypeError('Argument 1 of RTCPeerConnection.removeTrack ' + + 'does not implement interface RTCRtpSender.'); + } + + var transceiver = this.transceivers.find(function(t) { + return t.rtpSender === sender; + }); + + if (!transceiver) { + throw makeError('InvalidAccessError', + 'Sender was not created by this connection.'); + } + var stream = transceiver.stream; + + transceiver.rtpSender.stop(); + transceiver.rtpSender = null; + transceiver.track = null; + transceiver.stream = null; + + // remove the stream from the set of local streams + var localStreams = this.transceivers.map(function(t) { + return t.stream; + }); + if (localStreams.indexOf(stream) === -1 && + this.localStreams.indexOf(stream) > -1) { + this.localStreams.splice(this.localStreams.indexOf(stream), 1); + } + + this._maybeFireNegotiationNeeded(); + }; + + RTCPeerConnection.prototype.removeStream = function(stream) { + var pc = this; + stream.getTracks().forEach(function(track) { + var sender = pc.getSenders().find(function(s) { + return s.track === track; + }); + if (sender) { + pc.removeTrack(sender); + } + }); + }; + + RTCPeerConnection.prototype.getSenders = function() { + return this.transceivers.filter(function(transceiver) { + return !!transceiver.rtpSender; + }) + .map(function(transceiver) { + return transceiver.rtpSender; + }); + }; + + RTCPeerConnection.prototype.getReceivers = function() { + return this.transceivers.filter(function(transceiver) { + return !!transceiver.rtpReceiver; + }) + .map(function(transceiver) { + return transceiver.rtpReceiver; + }); + }; + + + RTCPeerConnection.prototype._createIceGatherer = function(sdpMLineIndex, + usingBundle) { + var pc = this; + if (usingBundle && sdpMLineIndex > 0) { + return this.transceivers[0].iceGatherer; + } else if (this._iceGatherers.length) { + return this._iceGatherers.shift(); + } + var iceGatherer = new window.RTCIceGatherer({ + iceServers: this._config.iceServers, + gatherPolicy: this._config.iceTransportPolicy + }); + Object.defineProperty(iceGatherer, 'state', + {value: 'new', writable: true} + ); + + this.transceivers[sdpMLineIndex].bufferedCandidateEvents = []; + this.transceivers[sdpMLineIndex].bufferCandidates = function(event) { + var end = !event.candidate || Object.keys(event.candidate).length === 0; + // polyfill since RTCIceGatherer.state is not implemented in + // Edge 10547 yet. + iceGatherer.state = end ? 'completed' : 'gathering'; + if (pc.transceivers[sdpMLineIndex].bufferedCandidateEvents !== null) { + pc.transceivers[sdpMLineIndex].bufferedCandidateEvents.push(event); + } + }; + iceGatherer.addEventListener('localcandidate', + this.transceivers[sdpMLineIndex].bufferCandidates); + return iceGatherer; + }; + + // start gathering from an RTCIceGatherer. + RTCPeerConnection.prototype._gather = function(mid, sdpMLineIndex) { + var pc = this; + var iceGatherer = this.transceivers[sdpMLineIndex].iceGatherer; + if (iceGatherer.onlocalcandidate) { + return; + } + var bufferedCandidateEvents = + this.transceivers[sdpMLineIndex].bufferedCandidateEvents; + this.transceivers[sdpMLineIndex].bufferedCandidateEvents = null; + iceGatherer.removeEventListener('localcandidate', + this.transceivers[sdpMLineIndex].bufferCandidates); + iceGatherer.onlocalcandidate = function(evt) { + if (pc.usingBundle && sdpMLineIndex > 0) { + // if we know that we use bundle we can drop candidates with + // ѕdpMLineIndex > 0. If we don't do this then our state gets + // confused since we dispose the extra ice gatherer. + return; + } + var event = new Event('icecandidate'); + event.candidate = {sdpMid: mid, sdpMLineIndex: sdpMLineIndex}; + + var cand = evt.candidate; + // Edge emits an empty object for RTCIceCandidateComplete‥ + var end = !cand || Object.keys(cand).length === 0; + if (end) { + // polyfill since RTCIceGatherer.state is not implemented in + // Edge 10547 yet. + if (iceGatherer.state === 'new' || iceGatherer.state === 'gathering') { + iceGatherer.state = 'completed'; + } + } else { + if (iceGatherer.state === 'new') { + iceGatherer.state = 'gathering'; + } + // RTCIceCandidate doesn't have a component, needs to be added + cand.component = 1; + // also the usernameFragment. TODO: update SDP to take both variants. + cand.ufrag = iceGatherer.getLocalParameters().usernameFragment; + + var serializedCandidate = SDPUtils.writeCandidate(cand); + event.candidate = Object.assign(event.candidate, + SDPUtils.parseCandidate(serializedCandidate)); + + event.candidate.candidate = serializedCandidate; + event.candidate.toJSON = function() { + return { + candidate: event.candidate.candidate, + sdpMid: event.candidate.sdpMid, + sdpMLineIndex: event.candidate.sdpMLineIndex, + usernameFragment: event.candidate.usernameFragment + }; + }; + } + + // update local description. + var sections = SDPUtils.getMediaSections(pc._localDescription.sdp); + if (!end) { + sections[event.candidate.sdpMLineIndex] += + 'a=' + event.candidate.candidate + '\r\n'; + } else { + sections[event.candidate.sdpMLineIndex] += + 'a=end-of-candidates\r\n'; + } + pc._localDescription.sdp = + SDPUtils.getDescription(pc._localDescription.sdp) + + sections.join(''); + var complete = pc.transceivers.every(function(transceiver) { + return transceiver.iceGatherer && + transceiver.iceGatherer.state === 'completed'; + }); + + if (pc.iceGatheringState !== 'gathering') { + pc.iceGatheringState = 'gathering'; + pc._emitGatheringStateChange(); + } + + // Emit candidate. Also emit null candidate when all gatherers are + // complete. + if (!end) { + pc._dispatchEvent('icecandidate', event); + } + if (complete) { + pc._dispatchEvent('icecandidate', new Event('icecandidate')); + pc.iceGatheringState = 'complete'; + pc._emitGatheringStateChange(); + } + }; + + // emit already gathered candidates. + window.setTimeout(function() { + bufferedCandidateEvents.forEach(function(e) { + iceGatherer.onlocalcandidate(e); + }); + }, 0); + }; + + // Create ICE transport and DTLS transport. + RTCPeerConnection.prototype._createIceAndDtlsTransports = function() { + var pc = this; + var iceTransport = new window.RTCIceTransport(null); + iceTransport.onicestatechange = function() { + pc._updateIceConnectionState(); + pc._updateConnectionState(); + }; + + var dtlsTransport = new window.RTCDtlsTransport(iceTransport); + dtlsTransport.ondtlsstatechange = function() { + pc._updateConnectionState(); + }; + dtlsTransport.onerror = function() { + // onerror does not set state to failed by itself. + Object.defineProperty(dtlsTransport, 'state', + {value: 'failed', writable: true}); + pc._updateConnectionState(); + }; + + return { + iceTransport: iceTransport, + dtlsTransport: dtlsTransport + }; + }; + + // Destroy ICE gatherer, ICE transport and DTLS transport. + // Without triggering the callbacks. + RTCPeerConnection.prototype._disposeIceAndDtlsTransports = function( + sdpMLineIndex) { + var iceGatherer = this.transceivers[sdpMLineIndex].iceGatherer; + if (iceGatherer) { + delete iceGatherer.onlocalcandidate; + delete this.transceivers[sdpMLineIndex].iceGatherer; + } + var iceTransport = this.transceivers[sdpMLineIndex].iceTransport; + if (iceTransport) { + delete iceTransport.onicestatechange; + delete this.transceivers[sdpMLineIndex].iceTransport; + } + var dtlsTransport = this.transceivers[sdpMLineIndex].dtlsTransport; + if (dtlsTransport) { + delete dtlsTransport.ondtlsstatechange; + delete dtlsTransport.onerror; + delete this.transceivers[sdpMLineIndex].dtlsTransport; + } + }; + + // Start the RTP Sender and Receiver for a transceiver. + RTCPeerConnection.prototype._transceive = function(transceiver, + send, recv) { + var params = getCommonCapabilities(transceiver.localCapabilities, + transceiver.remoteCapabilities); + if (send && transceiver.rtpSender) { + params.encodings = transceiver.sendEncodingParameters; + params.rtcp = { + cname: SDPUtils.localCName, + compound: transceiver.rtcpParameters.compound + }; + if (transceiver.recvEncodingParameters.length) { + params.rtcp.ssrc = transceiver.recvEncodingParameters[0].ssrc; + } + transceiver.rtpSender.send(params); + } + if (recv && transceiver.rtpReceiver && params.codecs.length > 0) { + // remove RTX field in Edge 14942 + if (transceiver.kind === 'video' + && transceiver.recvEncodingParameters + && edgeVersion < 15019) { + transceiver.recvEncodingParameters.forEach(function(p) { + delete p.rtx; + }); + } + if (transceiver.recvEncodingParameters.length) { + params.encodings = transceiver.recvEncodingParameters; + } else { + params.encodings = [{}]; + } + params.rtcp = { + compound: transceiver.rtcpParameters.compound + }; + if (transceiver.rtcpParameters.cname) { + params.rtcp.cname = transceiver.rtcpParameters.cname; + } + if (transceiver.sendEncodingParameters.length) { + params.rtcp.ssrc = transceiver.sendEncodingParameters[0].ssrc; + } + transceiver.rtpReceiver.receive(params); + } + }; + + RTCPeerConnection.prototype.setLocalDescription = function(description) { + var pc = this; + + // Note: pranswer is not supported. + if (['offer', 'answer'].indexOf(description.type) === -1) { + return Promise.reject(makeError('TypeError', + 'Unsupported type "' + description.type + '"')); + } + + if (!isActionAllowedInSignalingState('setLocalDescription', + description.type, pc.signalingState) || pc._isClosed) { + return Promise.reject(makeError('InvalidStateError', + 'Can not set local ' + description.type + + ' in state ' + pc.signalingState)); + } + + var sections; + var sessionpart; + if (description.type === 'offer') { + // VERY limited support for SDP munging. Limited to: + // * changing the order of codecs + sections = SDPUtils.splitSections(description.sdp); + sessionpart = sections.shift(); + sections.forEach(function(mediaSection, sdpMLineIndex) { + var caps = SDPUtils.parseRtpParameters(mediaSection); + pc.transceivers[sdpMLineIndex].localCapabilities = caps; + }); + + pc.transceivers.forEach(function(transceiver, sdpMLineIndex) { + pc._gather(transceiver.mid, sdpMLineIndex); + }); + } else if (description.type === 'answer') { + sections = SDPUtils.splitSections(pc._remoteDescription.sdp); + sessionpart = sections.shift(); + var isIceLite = SDPUtils.matchPrefix(sessionpart, + 'a=ice-lite').length > 0; + sections.forEach(function(mediaSection, sdpMLineIndex) { + var transceiver = pc.transceivers[sdpMLineIndex]; + var iceGatherer = transceiver.iceGatherer; + var iceTransport = transceiver.iceTransport; + var dtlsTransport = transceiver.dtlsTransport; + var localCapabilities = transceiver.localCapabilities; + var remoteCapabilities = transceiver.remoteCapabilities; + + // treat bundle-only as not-rejected. + var rejected = SDPUtils.isRejected(mediaSection) && + SDPUtils.matchPrefix(mediaSection, 'a=bundle-only').length === 0; + + if (!rejected && !transceiver.rejected) { + var remoteIceParameters = SDPUtils.getIceParameters( + mediaSection, sessionpart); + var remoteDtlsParameters = SDPUtils.getDtlsParameters( + mediaSection, sessionpart); + if (isIceLite) { + remoteDtlsParameters.role = 'server'; + } + + if (!pc.usingBundle || sdpMLineIndex === 0) { + pc._gather(transceiver.mid, sdpMLineIndex); + if (iceTransport.state === 'new') { + iceTransport.start(iceGatherer, remoteIceParameters, + isIceLite ? 'controlling' : 'controlled'); + } + if (dtlsTransport.state === 'new') { + dtlsTransport.start(remoteDtlsParameters); + } + } + + // Calculate intersection of capabilities. + var params = getCommonCapabilities(localCapabilities, + remoteCapabilities); + + // Start the RTCRtpSender. The RTCRtpReceiver for this + // transceiver has already been started in setRemoteDescription. + pc._transceive(transceiver, + params.codecs.length > 0, + false); + } + }); + } + + pc._localDescription = { + type: description.type, + sdp: description.sdp + }; + if (description.type === 'offer') { + pc._updateSignalingState('have-local-offer'); + } else { + pc._updateSignalingState('stable'); + } + + return Promise.resolve(); + }; + + RTCPeerConnection.prototype.setRemoteDescription = function(description) { + var pc = this; + + // Note: pranswer is not supported. + if (['offer', 'answer'].indexOf(description.type) === -1) { + return Promise.reject(makeError('TypeError', + 'Unsupported type "' + description.type + '"')); + } + + if (!isActionAllowedInSignalingState('setRemoteDescription', + description.type, pc.signalingState) || pc._isClosed) { + return Promise.reject(makeError('InvalidStateError', + 'Can not set remote ' + description.type + + ' in state ' + pc.signalingState)); + } + + var streams = {}; + pc.remoteStreams.forEach(function(stream) { + streams[stream.id] = stream; + }); + var receiverList = []; + var sections = SDPUtils.splitSections(description.sdp); + var sessionpart = sections.shift(); + var isIceLite = SDPUtils.matchPrefix(sessionpart, + 'a=ice-lite').length > 0; + var usingBundle = SDPUtils.matchPrefix(sessionpart, + 'a=group:BUNDLE ').length > 0; + pc.usingBundle = usingBundle; + var iceOptions = SDPUtils.matchPrefix(sessionpart, + 'a=ice-options:')[0]; + if (iceOptions) { + pc.canTrickleIceCandidates = iceOptions.substr(14).split(' ') + .indexOf('trickle') >= 0; + } else { + pc.canTrickleIceCandidates = false; + } + + sections.forEach(function(mediaSection, sdpMLineIndex) { + var lines = SDPUtils.splitLines(mediaSection); + var kind = SDPUtils.getKind(mediaSection); + // treat bundle-only as not-rejected. + var rejected = SDPUtils.isRejected(mediaSection) && + SDPUtils.matchPrefix(mediaSection, 'a=bundle-only').length === 0; + var protocol = lines[0].substr(2).split(' ')[2]; + + var direction = SDPUtils.getDirection(mediaSection, sessionpart); + var remoteMsid = SDPUtils.parseMsid(mediaSection); + + var mid = SDPUtils.getMid(mediaSection) || SDPUtils.generateIdentifier(); + + // Reject datachannels which are not implemented yet. + if (rejected || (kind === 'application' && (protocol === 'DTLS/SCTP' || + protocol === 'UDP/DTLS/SCTP'))) { + // TODO: this is dangerous in the case where a non-rejected m-line + // becomes rejected. + pc.transceivers[sdpMLineIndex] = { + mid: mid, + kind: kind, + protocol: protocol, + rejected: true + }; + return; + } + + if (!rejected && pc.transceivers[sdpMLineIndex] && + pc.transceivers[sdpMLineIndex].rejected) { + // recycle a rejected transceiver. + pc.transceivers[sdpMLineIndex] = pc._createTransceiver(kind, true); + } + + var transceiver; + var iceGatherer; + var iceTransport; + var dtlsTransport; + var rtpReceiver; + var sendEncodingParameters; + var recvEncodingParameters; + var localCapabilities; + + var track; + // FIXME: ensure the mediaSection has rtcp-mux set. + var remoteCapabilities = SDPUtils.parseRtpParameters(mediaSection); + var remoteIceParameters; + var remoteDtlsParameters; + if (!rejected) { + remoteIceParameters = SDPUtils.getIceParameters(mediaSection, + sessionpart); + remoteDtlsParameters = SDPUtils.getDtlsParameters(mediaSection, + sessionpart); + remoteDtlsParameters.role = 'client'; + } + recvEncodingParameters = + SDPUtils.parseRtpEncodingParameters(mediaSection); + + var rtcpParameters = SDPUtils.parseRtcpParameters(mediaSection); + + var isComplete = SDPUtils.matchPrefix(mediaSection, + 'a=end-of-candidates', sessionpart).length > 0; + var cands = SDPUtils.matchPrefix(mediaSection, 'a=candidate:') + .map(function(cand) { + return SDPUtils.parseCandidate(cand); + }) + .filter(function(cand) { + return cand.component === 1; + }); + + // Check if we can use BUNDLE and dispose transports. + if ((description.type === 'offer' || description.type === 'answer') && + !rejected && usingBundle && sdpMLineIndex > 0 && + pc.transceivers[sdpMLineIndex]) { + pc._disposeIceAndDtlsTransports(sdpMLineIndex); + pc.transceivers[sdpMLineIndex].iceGatherer = + pc.transceivers[0].iceGatherer; + pc.transceivers[sdpMLineIndex].iceTransport = + pc.transceivers[0].iceTransport; + pc.transceivers[sdpMLineIndex].dtlsTransport = + pc.transceivers[0].dtlsTransport; + if (pc.transceivers[sdpMLineIndex].rtpSender) { + pc.transceivers[sdpMLineIndex].rtpSender.setTransport( + pc.transceivers[0].dtlsTransport); + } + if (pc.transceivers[sdpMLineIndex].rtpReceiver) { + pc.transceivers[sdpMLineIndex].rtpReceiver.setTransport( + pc.transceivers[0].dtlsTransport); + } + } + if (description.type === 'offer' && !rejected) { + transceiver = pc.transceivers[sdpMLineIndex] || + pc._createTransceiver(kind); + transceiver.mid = mid; + + if (!transceiver.iceGatherer) { + transceiver.iceGatherer = pc._createIceGatherer(sdpMLineIndex, + usingBundle); + } + + if (cands.length && transceiver.iceTransport.state === 'new') { + if (isComplete && (!usingBundle || sdpMLineIndex === 0)) { + transceiver.iceTransport.setRemoteCandidates(cands); + } else { + cands.forEach(function(candidate) { + maybeAddCandidate(transceiver.iceTransport, candidate); + }); + } + } + + localCapabilities = window.RTCRtpReceiver.getCapabilities(kind); + + // filter RTX until additional stuff needed for RTX is implemented + // in adapter.js + if (edgeVersion < 15019) { + localCapabilities.codecs = localCapabilities.codecs.filter( + function(codec) { + return codec.name !== 'rtx'; + }); + } + + sendEncodingParameters = transceiver.sendEncodingParameters || [{ + ssrc: (2 * sdpMLineIndex + 2) * 1001 + }]; + + // TODO: rewrite to use http://w3c.github.io/webrtc-pc/#set-associated-remote-streams + var isNewTrack = false; + if (direction === 'sendrecv' || direction === 'sendonly') { + isNewTrack = !transceiver.rtpReceiver; + rtpReceiver = transceiver.rtpReceiver || + new window.RTCRtpReceiver(transceiver.dtlsTransport, kind); + + if (isNewTrack) { + var stream; + track = rtpReceiver.track; + // FIXME: does not work with Plan B. + if (remoteMsid && remoteMsid.stream === '-') { + // no-op. a stream id of '-' means: no associated stream. + } else if (remoteMsid) { + if (!streams[remoteMsid.stream]) { + streams[remoteMsid.stream] = new window.MediaStream(); + Object.defineProperty(streams[remoteMsid.stream], 'id', { + get: function() { + return remoteMsid.stream; + } + }); + } + Object.defineProperty(track, 'id', { + get: function() { + return remoteMsid.track; + } + }); + stream = streams[remoteMsid.stream]; + } else { + if (!streams.default) { + streams.default = new window.MediaStream(); + } + stream = streams.default; + } + if (stream) { + addTrackToStreamAndFireEvent(track, stream); + transceiver.associatedRemoteMediaStreams.push(stream); + } + receiverList.push([track, rtpReceiver, stream]); + } + } else if (transceiver.rtpReceiver && transceiver.rtpReceiver.track) { + transceiver.associatedRemoteMediaStreams.forEach(function(s) { + var nativeTrack = s.getTracks().find(function(t) { + return t.id === transceiver.rtpReceiver.track.id; + }); + if (nativeTrack) { + removeTrackFromStreamAndFireEvent(nativeTrack, s); + } + }); + transceiver.associatedRemoteMediaStreams = []; + } + + transceiver.localCapabilities = localCapabilities; + transceiver.remoteCapabilities = remoteCapabilities; + transceiver.rtpReceiver = rtpReceiver; + transceiver.rtcpParameters = rtcpParameters; + transceiver.sendEncodingParameters = sendEncodingParameters; + transceiver.recvEncodingParameters = recvEncodingParameters; + + // Start the RTCRtpReceiver now. The RTPSender is started in + // setLocalDescription. + pc._transceive(pc.transceivers[sdpMLineIndex], + false, + isNewTrack); + } else if (description.type === 'answer' && !rejected) { + transceiver = pc.transceivers[sdpMLineIndex]; + iceGatherer = transceiver.iceGatherer; + iceTransport = transceiver.iceTransport; + dtlsTransport = transceiver.dtlsTransport; + rtpReceiver = transceiver.rtpReceiver; + sendEncodingParameters = transceiver.sendEncodingParameters; + localCapabilities = transceiver.localCapabilities; + + pc.transceivers[sdpMLineIndex].recvEncodingParameters = + recvEncodingParameters; + pc.transceivers[sdpMLineIndex].remoteCapabilities = + remoteCapabilities; + pc.transceivers[sdpMLineIndex].rtcpParameters = rtcpParameters; + + if (cands.length && iceTransport.state === 'new') { + if ((isIceLite || isComplete) && + (!usingBundle || sdpMLineIndex === 0)) { + iceTransport.setRemoteCandidates(cands); + } else { + cands.forEach(function(candidate) { + maybeAddCandidate(transceiver.iceTransport, candidate); + }); + } + } + + if (!usingBundle || sdpMLineIndex === 0) { + if (iceTransport.state === 'new') { + iceTransport.start(iceGatherer, remoteIceParameters, + 'controlling'); + } + if (dtlsTransport.state === 'new') { + dtlsTransport.start(remoteDtlsParameters); + } + } + + // If the offer contained RTX but the answer did not, + // remove RTX from sendEncodingParameters. + var commonCapabilities = getCommonCapabilities( + transceiver.localCapabilities, + transceiver.remoteCapabilities); + + var hasRtx = commonCapabilities.codecs.filter(function(c) { + return c.name.toLowerCase() === 'rtx'; + }).length; + if (!hasRtx && transceiver.sendEncodingParameters[0].rtx) { + delete transceiver.sendEncodingParameters[0].rtx; + } + + pc._transceive(transceiver, + direction === 'sendrecv' || direction === 'recvonly', + direction === 'sendrecv' || direction === 'sendonly'); + + // TODO: rewrite to use http://w3c.github.io/webrtc-pc/#set-associated-remote-streams + if (rtpReceiver && + (direction === 'sendrecv' || direction === 'sendonly')) { + track = rtpReceiver.track; + if (remoteMsid) { + if (!streams[remoteMsid.stream]) { + streams[remoteMsid.stream] = new window.MediaStream(); + } + addTrackToStreamAndFireEvent(track, streams[remoteMsid.stream]); + receiverList.push([track, rtpReceiver, streams[remoteMsid.stream]]); + } else { + if (!streams.default) { + streams.default = new window.MediaStream(); + } + addTrackToStreamAndFireEvent(track, streams.default); + receiverList.push([track, rtpReceiver, streams.default]); + } + } else { + // FIXME: actually the receiver should be created later. + delete transceiver.rtpReceiver; + } + } + }); + + if (pc._dtlsRole === undefined) { + pc._dtlsRole = description.type === 'offer' ? 'active' : 'passive'; + } + + pc._remoteDescription = { + type: description.type, + sdp: description.sdp + }; + if (description.type === 'offer') { + pc._updateSignalingState('have-remote-offer'); + } else { + pc._updateSignalingState('stable'); + } + Object.keys(streams).forEach(function(sid) { + var stream = streams[sid]; + if (stream.getTracks().length) { + if (pc.remoteStreams.indexOf(stream) === -1) { + pc.remoteStreams.push(stream); + var event = new Event('addstream'); + event.stream = stream; + window.setTimeout(function() { + pc._dispatchEvent('addstream', event); + }); + } + + receiverList.forEach(function(item) { + var track = item[0]; + var receiver = item[1]; + if (stream.id !== item[2].id) { + return; + } + fireAddTrack(pc, track, receiver, [stream]); + }); + } + }); + receiverList.forEach(function(item) { + if (item[2]) { + return; + } + fireAddTrack(pc, item[0], item[1], []); + }); + + // check whether addIceCandidate({}) was called within four seconds after + // setRemoteDescription. + window.setTimeout(function() { + if (!(pc && pc.transceivers)) { + return; + } + pc.transceivers.forEach(function(transceiver) { + if (transceiver.iceTransport && + transceiver.iceTransport.state === 'new' && + transceiver.iceTransport.getRemoteCandidates().length > 0) { + console.warn('Timeout for addRemoteCandidate. Consider sending ' + + 'an end-of-candidates notification'); + transceiver.iceTransport.addRemoteCandidate({}); + } + }); + }, 4000); + + return Promise.resolve(); + }; + + RTCPeerConnection.prototype.close = function() { + this.transceivers.forEach(function(transceiver) { + /* not yet + if (transceiver.iceGatherer) { + transceiver.iceGatherer.close(); + } + */ + if (transceiver.iceTransport) { + transceiver.iceTransport.stop(); + } + if (transceiver.dtlsTransport) { + transceiver.dtlsTransport.stop(); + } + if (transceiver.rtpSender) { + transceiver.rtpSender.stop(); + } + if (transceiver.rtpReceiver) { + transceiver.rtpReceiver.stop(); + } + }); + // FIXME: clean up tracks, local streams, remote streams, etc + this._isClosed = true; + this._updateSignalingState('closed'); + }; + + // Update the signaling state. + RTCPeerConnection.prototype._updateSignalingState = function(newState) { + this.signalingState = newState; + var event = new Event('signalingstatechange'); + this._dispatchEvent('signalingstatechange', event); + }; + + // Determine whether to fire the negotiationneeded event. + RTCPeerConnection.prototype._maybeFireNegotiationNeeded = function() { + var pc = this; + if (this.signalingState !== 'stable' || this.needNegotiation === true) { + return; + } + this.needNegotiation = true; + window.setTimeout(function() { + if (pc.needNegotiation) { + pc.needNegotiation = false; + var event = new Event('negotiationneeded'); + pc._dispatchEvent('negotiationneeded', event); + } + }, 0); + }; + + // Update the ice connection state. + RTCPeerConnection.prototype._updateIceConnectionState = function() { + var newState; + var states = { + 'new': 0, + closed: 0, + checking: 0, + connected: 0, + completed: 0, + disconnected: 0, + failed: 0 + }; + this.transceivers.forEach(function(transceiver) { + states[transceiver.iceTransport.state]++; + }); + + newState = 'new'; + if (states.failed > 0) { + newState = 'failed'; + } else if (states.checking > 0) { + newState = 'checking'; + } else if (states.disconnected > 0) { + newState = 'disconnected'; + } else if (states.new > 0) { + newState = 'new'; + } else if (states.connected > 0) { + newState = 'connected'; + } else if (states.completed > 0) { + newState = 'completed'; + } + + if (newState !== this.iceConnectionState) { + this.iceConnectionState = newState; + var event = new Event('iceconnectionstatechange'); + this._dispatchEvent('iceconnectionstatechange', event); + } + }; + + // Update the connection state. + RTCPeerConnection.prototype._updateConnectionState = function() { + var newState; + var states = { + 'new': 0, + closed: 0, + connecting: 0, + connected: 0, + completed: 0, + disconnected: 0, + failed: 0 + }; + this.transceivers.forEach(function(transceiver) { + states[transceiver.iceTransport.state]++; + states[transceiver.dtlsTransport.state]++; + }); + // ICETransport.completed and connected are the same for this purpose. + states.connected += states.completed; + + newState = 'new'; + if (states.failed > 0) { + newState = 'failed'; + } else if (states.connecting > 0) { + newState = 'connecting'; + } else if (states.disconnected > 0) { + newState = 'disconnected'; + } else if (states.new > 0) { + newState = 'new'; + } else if (states.connected > 0) { + newState = 'connected'; + } + + if (newState !== this.connectionState) { + this.connectionState = newState; + var event = new Event('connectionstatechange'); + this._dispatchEvent('connectionstatechange', event); + } + }; + + RTCPeerConnection.prototype.createOffer = function() { + var pc = this; + + if (pc._isClosed) { + return Promise.reject(makeError('InvalidStateError', + 'Can not call createOffer after close')); + } + + var numAudioTracks = pc.transceivers.filter(function(t) { + return t.kind === 'audio'; + }).length; + var numVideoTracks = pc.transceivers.filter(function(t) { + return t.kind === 'video'; + }).length; + + // Determine number of audio and video tracks we need to send/recv. + var offerOptions = arguments[0]; + if (offerOptions) { + // Reject Chrome legacy constraints. + if (offerOptions.mandatory || offerOptions.optional) { + throw new TypeError( + 'Legacy mandatory/optional constraints not supported.'); + } + if (offerOptions.offerToReceiveAudio !== undefined) { + if (offerOptions.offerToReceiveAudio === true) { + numAudioTracks = 1; + } else if (offerOptions.offerToReceiveAudio === false) { + numAudioTracks = 0; + } else { + numAudioTracks = offerOptions.offerToReceiveAudio; + } + } + if (offerOptions.offerToReceiveVideo !== undefined) { + if (offerOptions.offerToReceiveVideo === true) { + numVideoTracks = 1; + } else if (offerOptions.offerToReceiveVideo === false) { + numVideoTracks = 0; + } else { + numVideoTracks = offerOptions.offerToReceiveVideo; + } + } + } + + pc.transceivers.forEach(function(transceiver) { + if (transceiver.kind === 'audio') { + numAudioTracks--; + if (numAudioTracks < 0) { + transceiver.wantReceive = false; + } + } else if (transceiver.kind === 'video') { + numVideoTracks--; + if (numVideoTracks < 0) { + transceiver.wantReceive = false; + } + } + }); + + // Create M-lines for recvonly streams. + while (numAudioTracks > 0 || numVideoTracks > 0) { + if (numAudioTracks > 0) { + pc._createTransceiver('audio'); + numAudioTracks--; + } + if (numVideoTracks > 0) { + pc._createTransceiver('video'); + numVideoTracks--; + } + } + + var sdp = SDPUtils.writeSessionBoilerplate(pc._sdpSessionId, + pc._sdpSessionVersion++); + pc.transceivers.forEach(function(transceiver, sdpMLineIndex) { + // For each track, create an ice gatherer, ice transport, + // dtls transport, potentially rtpsender and rtpreceiver. + var track = transceiver.track; + var kind = transceiver.kind; + var mid = transceiver.mid || SDPUtils.generateIdentifier(); + transceiver.mid = mid; + + if (!transceiver.iceGatherer) { + transceiver.iceGatherer = pc._createIceGatherer(sdpMLineIndex, + pc.usingBundle); + } + + var localCapabilities = window.RTCRtpSender.getCapabilities(kind); + // filter RTX until additional stuff needed for RTX is implemented + // in adapter.js + if (edgeVersion < 15019) { + localCapabilities.codecs = localCapabilities.codecs.filter( + function(codec) { + return codec.name !== 'rtx'; + }); + } + localCapabilities.codecs.forEach(function(codec) { + // work around https://bugs.chromium.org/p/webrtc/issues/detail?id=6552 + // by adding level-asymmetry-allowed=1 + if (codec.name === 'H264' && + codec.parameters['level-asymmetry-allowed'] === undefined) { + codec.parameters['level-asymmetry-allowed'] = '1'; + } + + // for subsequent offers, we might have to re-use the payload + // type of the last offer. + if (transceiver.remoteCapabilities && + transceiver.remoteCapabilities.codecs) { + transceiver.remoteCapabilities.codecs.forEach(function(remoteCodec) { + if (codec.name.toLowerCase() === remoteCodec.name.toLowerCase() && + codec.clockRate === remoteCodec.clockRate) { + codec.preferredPayloadType = remoteCodec.payloadType; + } + }); + } + }); + localCapabilities.headerExtensions.forEach(function(hdrExt) { + var remoteExtensions = transceiver.remoteCapabilities && + transceiver.remoteCapabilities.headerExtensions || []; + remoteExtensions.forEach(function(rHdrExt) { + if (hdrExt.uri === rHdrExt.uri) { + hdrExt.id = rHdrExt.id; + } + }); + }); + + // generate an ssrc now, to be used later in rtpSender.send + var sendEncodingParameters = transceiver.sendEncodingParameters || [{ + ssrc: (2 * sdpMLineIndex + 1) * 1001 + }]; + if (track) { + // add RTX + if (edgeVersion >= 15019 && kind === 'video' && + !sendEncodingParameters[0].rtx) { + sendEncodingParameters[0].rtx = { + ssrc: sendEncodingParameters[0].ssrc + 1 + }; + } + } + + if (transceiver.wantReceive) { + transceiver.rtpReceiver = new window.RTCRtpReceiver( + transceiver.dtlsTransport, kind); + } + + transceiver.localCapabilities = localCapabilities; + transceiver.sendEncodingParameters = sendEncodingParameters; + }); + + // always offer BUNDLE and dispose on return if not supported. + if (pc._config.bundlePolicy !== 'max-compat') { + sdp += 'a=group:BUNDLE ' + pc.transceivers.map(function(t) { + return t.mid; + }).join(' ') + '\r\n'; + } + sdp += 'a=ice-options:trickle\r\n'; + + pc.transceivers.forEach(function(transceiver, sdpMLineIndex) { + sdp += writeMediaSection(transceiver, transceiver.localCapabilities, + 'offer', transceiver.stream, pc._dtlsRole); + sdp += 'a=rtcp-rsize\r\n'; + + if (transceiver.iceGatherer && pc.iceGatheringState !== 'new' && + (sdpMLineIndex === 0 || !pc.usingBundle)) { + transceiver.iceGatherer.getLocalCandidates().forEach(function(cand) { + cand.component = 1; + sdp += 'a=' + SDPUtils.writeCandidate(cand) + '\r\n'; + }); + + if (transceiver.iceGatherer.state === 'completed') { + sdp += 'a=end-of-candidates\r\n'; + } + } + }); + + var desc = new window.RTCSessionDescription({ + type: 'offer', + sdp: sdp + }); + return Promise.resolve(desc); + }; + + RTCPeerConnection.prototype.createAnswer = function() { + var pc = this; + + if (pc._isClosed) { + return Promise.reject(makeError('InvalidStateError', + 'Can not call createAnswer after close')); + } + + if (!(pc.signalingState === 'have-remote-offer' || + pc.signalingState === 'have-local-pranswer')) { + return Promise.reject(makeError('InvalidStateError', + 'Can not call createAnswer in signalingState ' + pc.signalingState)); + } + + var sdp = SDPUtils.writeSessionBoilerplate(pc._sdpSessionId, + pc._sdpSessionVersion++); + if (pc.usingBundle) { + sdp += 'a=group:BUNDLE ' + pc.transceivers.map(function(t) { + return t.mid; + }).join(' ') + '\r\n'; + } + sdp += 'a=ice-options:trickle\r\n'; + + var mediaSectionsInOffer = SDPUtils.getMediaSections( + pc._remoteDescription.sdp).length; + pc.transceivers.forEach(function(transceiver, sdpMLineIndex) { + if (sdpMLineIndex + 1 > mediaSectionsInOffer) { + return; + } + if (transceiver.rejected) { + if (transceiver.kind === 'application') { + if (transceiver.protocol === 'DTLS/SCTP') { // legacy fmt + sdp += 'm=application 0 DTLS/SCTP 5000\r\n'; + } else { + sdp += 'm=application 0 ' + transceiver.protocol + + ' webrtc-datachannel\r\n'; + } + } else if (transceiver.kind === 'audio') { + sdp += 'm=audio 0 UDP/TLS/RTP/SAVPF 0\r\n' + + 'a=rtpmap:0 PCMU/8000\r\n'; + } else if (transceiver.kind === 'video') { + sdp += 'm=video 0 UDP/TLS/RTP/SAVPF 120\r\n' + + 'a=rtpmap:120 VP8/90000\r\n'; + } + sdp += 'c=IN IP4 0.0.0.0\r\n' + + 'a=inactive\r\n' + + 'a=mid:' + transceiver.mid + '\r\n'; + return; + } + + // FIXME: look at direction. + if (transceiver.stream) { + var localTrack; + if (transceiver.kind === 'audio') { + localTrack = transceiver.stream.getAudioTracks()[0]; + } else if (transceiver.kind === 'video') { + localTrack = transceiver.stream.getVideoTracks()[0]; + } + if (localTrack) { + // add RTX + if (edgeVersion >= 15019 && transceiver.kind === 'video' && + !transceiver.sendEncodingParameters[0].rtx) { + transceiver.sendEncodingParameters[0].rtx = { + ssrc: transceiver.sendEncodingParameters[0].ssrc + 1 + }; + } + } + } + + // Calculate intersection of capabilities. + var commonCapabilities = getCommonCapabilities( + transceiver.localCapabilities, + transceiver.remoteCapabilities); + + var hasRtx = commonCapabilities.codecs.filter(function(c) { + return c.name.toLowerCase() === 'rtx'; + }).length; + if (!hasRtx && transceiver.sendEncodingParameters[0].rtx) { + delete transceiver.sendEncodingParameters[0].rtx; + } + + sdp += writeMediaSection(transceiver, commonCapabilities, + 'answer', transceiver.stream, pc._dtlsRole); + if (transceiver.rtcpParameters && + transceiver.rtcpParameters.reducedSize) { + sdp += 'a=rtcp-rsize\r\n'; + } + }); + + var desc = new window.RTCSessionDescription({ + type: 'answer', + sdp: sdp + }); + return Promise.resolve(desc); + }; + + RTCPeerConnection.prototype.addIceCandidate = function(candidate) { + var pc = this; + var sections; + if (candidate && !(candidate.sdpMLineIndex !== undefined || + candidate.sdpMid)) { + return Promise.reject(new TypeError('sdpMLineIndex or sdpMid required')); + } + + // TODO: needs to go into ops queue. + return new Promise(function(resolve, reject) { + if (!pc._remoteDescription) { + return reject(makeError('InvalidStateError', + 'Can not add ICE candidate without a remote description')); + } else if (!candidate || candidate.candidate === '') { + for (var j = 0; j < pc.transceivers.length; j++) { + if (pc.transceivers[j].rejected) { + continue; + } + pc.transceivers[j].iceTransport.addRemoteCandidate({}); + sections = SDPUtils.getMediaSections(pc._remoteDescription.sdp); + sections[j] += 'a=end-of-candidates\r\n'; + pc._remoteDescription.sdp = + SDPUtils.getDescription(pc._remoteDescription.sdp) + + sections.join(''); + if (pc.usingBundle) { + break; + } + } + } else { + var sdpMLineIndex = candidate.sdpMLineIndex; + if (candidate.sdpMid) { + for (var i = 0; i < pc.transceivers.length; i++) { + if (pc.transceivers[i].mid === candidate.sdpMid) { + sdpMLineIndex = i; + break; + } + } + } + var transceiver = pc.transceivers[sdpMLineIndex]; + if (transceiver) { + if (transceiver.rejected) { + return resolve(); + } + var cand = Object.keys(candidate.candidate).length > 0 ? + SDPUtils.parseCandidate(candidate.candidate) : {}; + // Ignore Chrome's invalid candidates since Edge does not like them. + if (cand.protocol === 'tcp' && (cand.port === 0 || cand.port === 9)) { + return resolve(); + } + // Ignore RTCP candidates, we assume RTCP-MUX. + if (cand.component && cand.component !== 1) { + return resolve(); + } + // when using bundle, avoid adding candidates to the wrong + // ice transport. And avoid adding candidates added in the SDP. + if (sdpMLineIndex === 0 || (sdpMLineIndex > 0 && + transceiver.iceTransport !== pc.transceivers[0].iceTransport)) { + if (!maybeAddCandidate(transceiver.iceTransport, cand)) { + return reject(makeError('OperationError', + 'Can not add ICE candidate')); + } + } + + // update the remoteDescription. + var candidateString = candidate.candidate.trim(); + if (candidateString.indexOf('a=') === 0) { + candidateString = candidateString.substr(2); + } + sections = SDPUtils.getMediaSections(pc._remoteDescription.sdp); + sections[sdpMLineIndex] += 'a=' + + (cand.type ? candidateString : 'end-of-candidates') + + '\r\n'; + pc._remoteDescription.sdp = + SDPUtils.getDescription(pc._remoteDescription.sdp) + + sections.join(''); + } else { + return reject(makeError('OperationError', + 'Can not add ICE candidate')); + } + } + resolve(); + }); + }; + + RTCPeerConnection.prototype.getStats = function(selector) { + if (selector && selector instanceof window.MediaStreamTrack) { + var senderOrReceiver = null; + this.transceivers.forEach(function(transceiver) { + if (transceiver.rtpSender && + transceiver.rtpSender.track === selector) { + senderOrReceiver = transceiver.rtpSender; + } else if (transceiver.rtpReceiver && + transceiver.rtpReceiver.track === selector) { + senderOrReceiver = transceiver.rtpReceiver; + } + }); + if (!senderOrReceiver) { + throw makeError('InvalidAccessError', 'Invalid selector.'); + } + return senderOrReceiver.getStats(); + } + + var promises = []; + this.transceivers.forEach(function(transceiver) { + ['rtpSender', 'rtpReceiver', 'iceGatherer', 'iceTransport', + 'dtlsTransport'].forEach(function(method) { + if (transceiver[method]) { + promises.push(transceiver[method].getStats()); + } + }); + }); + return Promise.all(promises).then(function(allStats) { + var results = new Map(); + allStats.forEach(function(stats) { + stats.forEach(function(stat) { + results.set(stat.id, stat); + }); + }); + return results; + }); + }; + + // fix low-level stat names and return Map instead of object. + var ortcObjects = ['RTCRtpSender', 'RTCRtpReceiver', 'RTCIceGatherer', + 'RTCIceTransport', 'RTCDtlsTransport']; + ortcObjects.forEach(function(ortcObjectName) { + var obj = window[ortcObjectName]; + if (obj && obj.prototype && obj.prototype.getStats) { + var nativeGetstats = obj.prototype.getStats; + obj.prototype.getStats = function() { + return nativeGetstats.apply(this) + .then(function(nativeStats) { + var mapStats = new Map(); + Object.keys(nativeStats).forEach(function(id) { + nativeStats[id].type = fixStatsType(nativeStats[id]); + mapStats.set(id, nativeStats[id]); + }); + return mapStats; + }); + }; + } + }); + + // legacy callback shims. Should be moved to adapter.js some days. + var methods = ['createOffer', 'createAnswer']; + methods.forEach(function(method) { + var nativeMethod = RTCPeerConnection.prototype[method]; + RTCPeerConnection.prototype[method] = function() { + var args = arguments; + if (typeof args[0] === 'function' || + typeof args[1] === 'function') { // legacy + return nativeMethod.apply(this, [arguments[2]]) + .then(function(description) { + if (typeof args[0] === 'function') { + args[0].apply(null, [description]); + } + }, function(error) { + if (typeof args[1] === 'function') { + args[1].apply(null, [error]); + } + }); + } + return nativeMethod.apply(this, arguments); + }; + }); + + methods = ['setLocalDescription', 'setRemoteDescription', 'addIceCandidate']; + methods.forEach(function(method) { + var nativeMethod = RTCPeerConnection.prototype[method]; + RTCPeerConnection.prototype[method] = function() { + var args = arguments; + if (typeof args[1] === 'function' || + typeof args[2] === 'function') { // legacy + return nativeMethod.apply(this, arguments) + .then(function() { + if (typeof args[1] === 'function') { + args[1].apply(null); + } + }, function(error) { + if (typeof args[2] === 'function') { + args[2].apply(null, [error]); + } + }); + } + return nativeMethod.apply(this, arguments); + }; + }); + + // getStats is special. It doesn't have a spec legacy method yet we support + // getStats(something, cb) without error callbacks. + ['getStats'].forEach(function(method) { + var nativeMethod = RTCPeerConnection.prototype[method]; + RTCPeerConnection.prototype[method] = function() { + var args = arguments; + if (typeof args[1] === 'function') { + return nativeMethod.apply(this, arguments) + .then(function() { + if (typeof args[1] === 'function') { + args[1].apply(null); + } + }); + } + return nativeMethod.apply(this, arguments); + }; + }); + + return RTCPeerConnection; +}; + +},{"sdp":2}],2:[function(require,module,exports){ + /* eslint-env node */ +'use strict'; + +// SDP helpers. +var SDPUtils = {}; + +// Generate an alphanumeric identifier for cname or mids. +// TODO: use UUIDs instead? https://gist.github.com/jed/982883 +SDPUtils.generateIdentifier = function() { + return Math.random().toString(36).substr(2, 10); +}; + +// The RTCP CNAME used by all peerconnections from the same JS. +SDPUtils.localCName = SDPUtils.generateIdentifier(); + +// Splits SDP into lines, dealing with both CRLF and LF. +SDPUtils.splitLines = function(blob) { + return blob.trim().split('\n').map(function(line) { + return line.trim(); + }); +}; +// Splits SDP into sessionpart and mediasections. Ensures CRLF. +SDPUtils.splitSections = function(blob) { + var parts = blob.split('\nm='); + return parts.map(function(part, index) { + return (index > 0 ? 'm=' + part : part).trim() + '\r\n'; + }); +}; + +// returns the session description. +SDPUtils.getDescription = function(blob) { + var sections = SDPUtils.splitSections(blob); + return sections && sections[0]; +}; + +// returns the individual media sections. +SDPUtils.getMediaSections = function(blob) { + var sections = SDPUtils.splitSections(blob); + sections.shift(); + return sections; +}; + +// Returns lines that start with a certain prefix. +SDPUtils.matchPrefix = function(blob, prefix) { + return SDPUtils.splitLines(blob).filter(function(line) { + return line.indexOf(prefix) === 0; + }); +}; + +// Parses an ICE candidate line. Sample input: +// candidate:702786350 2 udp 41819902 8.8.8.8 60769 typ relay raddr 8.8.8.8 +// rport 55996" +SDPUtils.parseCandidate = function(line) { + var parts; + // Parse both variants. + if (line.indexOf('a=candidate:') === 0) { + parts = line.substring(12).split(' '); + } else { + parts = line.substring(10).split(' '); + } + + var candidate = { + foundation: parts[0], + component: parseInt(parts[1], 10), + protocol: parts[2].toLowerCase(), + priority: parseInt(parts[3], 10), + ip: parts[4], + address: parts[4], // address is an alias for ip. + port: parseInt(parts[5], 10), + // skip parts[6] == 'typ' + type: parts[7] + }; + + for (var i = 8; i < parts.length; i += 2) { + switch (parts[i]) { + case 'raddr': + candidate.relatedAddress = parts[i + 1]; + break; + case 'rport': + candidate.relatedPort = parseInt(parts[i + 1], 10); + break; + case 'tcptype': + candidate.tcpType = parts[i + 1]; + break; + case 'ufrag': + candidate.ufrag = parts[i + 1]; // for backward compability. + candidate.usernameFragment = parts[i + 1]; + break; + default: // extension handling, in particular ufrag + candidate[parts[i]] = parts[i + 1]; + break; + } + } + return candidate; +}; + +// Translates a candidate object into SDP candidate attribute. +SDPUtils.writeCandidate = function(candidate) { + var sdp = []; + sdp.push(candidate.foundation); + sdp.push(candidate.component); + sdp.push(candidate.protocol.toUpperCase()); + sdp.push(candidate.priority); + sdp.push(candidate.address || candidate.ip); + sdp.push(candidate.port); + + var type = candidate.type; + sdp.push('typ'); + sdp.push(type); + if (type !== 'host' && candidate.relatedAddress && + candidate.relatedPort) { + sdp.push('raddr'); + sdp.push(candidate.relatedAddress); + sdp.push('rport'); + sdp.push(candidate.relatedPort); + } + if (candidate.tcpType && candidate.protocol.toLowerCase() === 'tcp') { + sdp.push('tcptype'); + sdp.push(candidate.tcpType); + } + if (candidate.usernameFragment || candidate.ufrag) { + sdp.push('ufrag'); + sdp.push(candidate.usernameFragment || candidate.ufrag); + } + return 'candidate:' + sdp.join(' '); +}; + +// Parses an ice-options line, returns an array of option tags. +// a=ice-options:foo bar +SDPUtils.parseIceOptions = function(line) { + return line.substr(14).split(' '); +}; + +// Parses an rtpmap line, returns RTCRtpCoddecParameters. Sample input: +// a=rtpmap:111 opus/48000/2 +SDPUtils.parseRtpMap = function(line) { + var parts = line.substr(9).split(' '); + var parsed = { + payloadType: parseInt(parts.shift(), 10) // was: id + }; + + parts = parts[0].split('/'); + + parsed.name = parts[0]; + parsed.clockRate = parseInt(parts[1], 10); // was: clockrate + parsed.channels = parts.length === 3 ? parseInt(parts[2], 10) : 1; + // legacy alias, got renamed back to channels in ORTC. + parsed.numChannels = parsed.channels; + return parsed; +}; + +// Generate an a=rtpmap line from RTCRtpCodecCapability or +// RTCRtpCodecParameters. +SDPUtils.writeRtpMap = function(codec) { + var pt = codec.payloadType; + if (codec.preferredPayloadType !== undefined) { + pt = codec.preferredPayloadType; + } + var channels = codec.channels || codec.numChannels || 1; + return 'a=rtpmap:' + pt + ' ' + codec.name + '/' + codec.clockRate + + (channels !== 1 ? '/' + channels : '') + '\r\n'; +}; + +// Parses an a=extmap line (headerextension from RFC 5285). Sample input: +// a=extmap:2 urn:ietf:params:rtp-hdrext:toffset +// a=extmap:2/sendonly urn:ietf:params:rtp-hdrext:toffset +SDPUtils.parseExtmap = function(line) { + var parts = line.substr(9).split(' '); + return { + id: parseInt(parts[0], 10), + direction: parts[0].indexOf('/') > 0 ? parts[0].split('/')[1] : 'sendrecv', + uri: parts[1] + }; +}; + +// Generates a=extmap line from RTCRtpHeaderExtensionParameters or +// RTCRtpHeaderExtension. +SDPUtils.writeExtmap = function(headerExtension) { + return 'a=extmap:' + (headerExtension.id || headerExtension.preferredId) + + (headerExtension.direction && headerExtension.direction !== 'sendrecv' + ? '/' + headerExtension.direction + : '') + + ' ' + headerExtension.uri + '\r\n'; +}; + +// Parses an ftmp line, returns dictionary. Sample input: +// a=fmtp:96 vbr=on;cng=on +// Also deals with vbr=on; cng=on +SDPUtils.parseFmtp = function(line) { + var parsed = {}; + var kv; + var parts = line.substr(line.indexOf(' ') + 1).split(';'); + for (var j = 0; j < parts.length; j++) { + kv = parts[j].trim().split('='); + parsed[kv[0].trim()] = kv[1]; + } + return parsed; +}; + +// Generates an a=ftmp line from RTCRtpCodecCapability or RTCRtpCodecParameters. +SDPUtils.writeFmtp = function(codec) { + var line = ''; + var pt = codec.payloadType; + if (codec.preferredPayloadType !== undefined) { + pt = codec.preferredPayloadType; + } + if (codec.parameters && Object.keys(codec.parameters).length) { + var params = []; + Object.keys(codec.parameters).forEach(function(param) { + if (codec.parameters[param]) { + params.push(param + '=' + codec.parameters[param]); + } else { + params.push(param); + } + }); + line += 'a=fmtp:' + pt + ' ' + params.join(';') + '\r\n'; + } + return line; +}; + +// Parses an rtcp-fb line, returns RTCPRtcpFeedback object. Sample input: +// a=rtcp-fb:98 nack rpsi +SDPUtils.parseRtcpFb = function(line) { + var parts = line.substr(line.indexOf(' ') + 1).split(' '); + return { + type: parts.shift(), + parameter: parts.join(' ') + }; +}; +// Generate a=rtcp-fb lines from RTCRtpCodecCapability or RTCRtpCodecParameters. +SDPUtils.writeRtcpFb = function(codec) { + var lines = ''; + var pt = codec.payloadType; + if (codec.preferredPayloadType !== undefined) { + pt = codec.preferredPayloadType; + } + if (codec.rtcpFeedback && codec.rtcpFeedback.length) { + // FIXME: special handling for trr-int? + codec.rtcpFeedback.forEach(function(fb) { + lines += 'a=rtcp-fb:' + pt + ' ' + fb.type + + (fb.parameter && fb.parameter.length ? ' ' + fb.parameter : '') + + '\r\n'; + }); + } + return lines; +}; + +// Parses an RFC 5576 ssrc media attribute. Sample input: +// a=ssrc:3735928559 cname:something +SDPUtils.parseSsrcMedia = function(line) { + var sp = line.indexOf(' '); + var parts = { + ssrc: parseInt(line.substr(7, sp - 7), 10) + }; + var colon = line.indexOf(':', sp); + if (colon > -1) { + parts.attribute = line.substr(sp + 1, colon - sp - 1); + parts.value = line.substr(colon + 1); + } else { + parts.attribute = line.substr(sp + 1); + } + return parts; +}; + +SDPUtils.parseSsrcGroup = function(line) { + var parts = line.substr(13).split(' '); + return { + semantics: parts.shift(), + ssrcs: parts.map(function(ssrc) { + return parseInt(ssrc, 10); + }) + }; +}; + +// Extracts the MID (RFC 5888) from a media section. +// returns the MID or undefined if no mid line was found. +SDPUtils.getMid = function(mediaSection) { + var mid = SDPUtils.matchPrefix(mediaSection, 'a=mid:')[0]; + if (mid) { + return mid.substr(6); + } +}; + +SDPUtils.parseFingerprint = function(line) { + var parts = line.substr(14).split(' '); + return { + algorithm: parts[0].toLowerCase(), // algorithm is case-sensitive in Edge. + value: parts[1] + }; +}; + +// Extracts DTLS parameters from SDP media section or sessionpart. +// FIXME: for consistency with other functions this should only +// get the fingerprint line as input. See also getIceParameters. +SDPUtils.getDtlsParameters = function(mediaSection, sessionpart) { + var lines = SDPUtils.matchPrefix(mediaSection + sessionpart, + 'a=fingerprint:'); + // Note: a=setup line is ignored since we use the 'auto' role. + // Note2: 'algorithm' is not case sensitive except in Edge. + return { + role: 'auto', + fingerprints: lines.map(SDPUtils.parseFingerprint) + }; +}; + +// Serializes DTLS parameters to SDP. +SDPUtils.writeDtlsParameters = function(params, setupType) { + var sdp = 'a=setup:' + setupType + '\r\n'; + params.fingerprints.forEach(function(fp) { + sdp += 'a=fingerprint:' + fp.algorithm + ' ' + fp.value + '\r\n'; + }); + return sdp; +}; +// Parses ICE information from SDP media section or sessionpart. +// FIXME: for consistency with other functions this should only +// get the ice-ufrag and ice-pwd lines as input. +SDPUtils.getIceParameters = function(mediaSection, sessionpart) { + var lines = SDPUtils.splitLines(mediaSection); + // Search in session part, too. + lines = lines.concat(SDPUtils.splitLines(sessionpart)); + var iceParameters = { + usernameFragment: lines.filter(function(line) { + return line.indexOf('a=ice-ufrag:') === 0; + })[0].substr(12), + password: lines.filter(function(line) { + return line.indexOf('a=ice-pwd:') === 0; + })[0].substr(10) + }; + return iceParameters; +}; + +// Serializes ICE parameters to SDP. +SDPUtils.writeIceParameters = function(params) { + return 'a=ice-ufrag:' + params.usernameFragment + '\r\n' + + 'a=ice-pwd:' + params.password + '\r\n'; +}; + +// Parses the SDP media section and returns RTCRtpParameters. +SDPUtils.parseRtpParameters = function(mediaSection) { + var description = { + codecs: [], + headerExtensions: [], + fecMechanisms: [], + rtcp: [] + }; + var lines = SDPUtils.splitLines(mediaSection); + var mline = lines[0].split(' '); + for (var i = 3; i < mline.length; i++) { // find all codecs from mline[3..] + var pt = mline[i]; + var rtpmapline = SDPUtils.matchPrefix( + mediaSection, 'a=rtpmap:' + pt + ' ')[0]; + if (rtpmapline) { + var codec = SDPUtils.parseRtpMap(rtpmapline); + var fmtps = SDPUtils.matchPrefix( + mediaSection, 'a=fmtp:' + pt + ' '); + // Only the first a=fmtp: is considered. + codec.parameters = fmtps.length ? SDPUtils.parseFmtp(fmtps[0]) : {}; + codec.rtcpFeedback = SDPUtils.matchPrefix( + mediaSection, 'a=rtcp-fb:' + pt + ' ') + .map(SDPUtils.parseRtcpFb); + description.codecs.push(codec); + // parse FEC mechanisms from rtpmap lines. + switch (codec.name.toUpperCase()) { + case 'RED': + case 'ULPFEC': + description.fecMechanisms.push(codec.name.toUpperCase()); + break; + default: // only RED and ULPFEC are recognized as FEC mechanisms. + break; + } + } + } + SDPUtils.matchPrefix(mediaSection, 'a=extmap:').forEach(function(line) { + description.headerExtensions.push(SDPUtils.parseExtmap(line)); + }); + // FIXME: parse rtcp. + return description; +}; + +// Generates parts of the SDP media section describing the capabilities / +// parameters. +SDPUtils.writeRtpDescription = function(kind, caps) { + var sdp = ''; + + // Build the mline. + sdp += 'm=' + kind + ' '; + sdp += caps.codecs.length > 0 ? '9' : '0'; // reject if no codecs. + sdp += ' UDP/TLS/RTP/SAVPF '; + sdp += caps.codecs.map(function(codec) { + if (codec.preferredPayloadType !== undefined) { + return codec.preferredPayloadType; + } + return codec.payloadType; + }).join(' ') + '\r\n'; + + sdp += 'c=IN IP4 0.0.0.0\r\n'; + sdp += 'a=rtcp:9 IN IP4 0.0.0.0\r\n'; + + // Add a=rtpmap lines for each codec. Also fmtp and rtcp-fb. + caps.codecs.forEach(function(codec) { + sdp += SDPUtils.writeRtpMap(codec); + sdp += SDPUtils.writeFmtp(codec); + sdp += SDPUtils.writeRtcpFb(codec); + }); + var maxptime = 0; + caps.codecs.forEach(function(codec) { + if (codec.maxptime > maxptime) { + maxptime = codec.maxptime; + } + }); + if (maxptime > 0) { + sdp += 'a=maxptime:' + maxptime + '\r\n'; + } + sdp += 'a=rtcp-mux\r\n'; + + if (caps.headerExtensions) { + caps.headerExtensions.forEach(function(extension) { + sdp += SDPUtils.writeExtmap(extension); + }); + } + // FIXME: write fecMechanisms. + return sdp; +}; + +// Parses the SDP media section and returns an array of +// RTCRtpEncodingParameters. +SDPUtils.parseRtpEncodingParameters = function(mediaSection) { + var encodingParameters = []; + var description = SDPUtils.parseRtpParameters(mediaSection); + var hasRed = description.fecMechanisms.indexOf('RED') !== -1; + var hasUlpfec = description.fecMechanisms.indexOf('ULPFEC') !== -1; + + // filter a=ssrc:... cname:, ignore PlanB-msid + var ssrcs = SDPUtils.matchPrefix(mediaSection, 'a=ssrc:') + .map(function(line) { + return SDPUtils.parseSsrcMedia(line); + }) + .filter(function(parts) { + return parts.attribute === 'cname'; + }); + var primarySsrc = ssrcs.length > 0 && ssrcs[0].ssrc; + var secondarySsrc; + + var flows = SDPUtils.matchPrefix(mediaSection, 'a=ssrc-group:FID') + .map(function(line) { + var parts = line.substr(17).split(' '); + return parts.map(function(part) { + return parseInt(part, 10); + }); + }); + if (flows.length > 0 && flows[0].length > 1 && flows[0][0] === primarySsrc) { + secondarySsrc = flows[0][1]; + } + + description.codecs.forEach(function(codec) { + if (codec.name.toUpperCase() === 'RTX' && codec.parameters.apt) { + var encParam = { + ssrc: primarySsrc, + codecPayloadType: parseInt(codec.parameters.apt, 10) + }; + if (primarySsrc && secondarySsrc) { + encParam.rtx = {ssrc: secondarySsrc}; + } + encodingParameters.push(encParam); + if (hasRed) { + encParam = JSON.parse(JSON.stringify(encParam)); + encParam.fec = { + ssrc: primarySsrc, + mechanism: hasUlpfec ? 'red+ulpfec' : 'red' + }; + encodingParameters.push(encParam); + } + } + }); + if (encodingParameters.length === 0 && primarySsrc) { + encodingParameters.push({ + ssrc: primarySsrc + }); + } + + // we support both b=AS and b=TIAS but interpret AS as TIAS. + var bandwidth = SDPUtils.matchPrefix(mediaSection, 'b='); + if (bandwidth.length) { + if (bandwidth[0].indexOf('b=TIAS:') === 0) { + bandwidth = parseInt(bandwidth[0].substr(7), 10); + } else if (bandwidth[0].indexOf('b=AS:') === 0) { + // use formula from JSEP to convert b=AS to TIAS value. + bandwidth = parseInt(bandwidth[0].substr(5), 10) * 1000 * 0.95 + - (50 * 40 * 8); + } else { + bandwidth = undefined; + } + encodingParameters.forEach(function(params) { + params.maxBitrate = bandwidth; + }); + } + return encodingParameters; +}; + +// parses http://draft.ortc.org/#rtcrtcpparameters* +SDPUtils.parseRtcpParameters = function(mediaSection) { + var rtcpParameters = {}; + + // Gets the first SSRC. Note tha with RTX there might be multiple + // SSRCs. + var remoteSsrc = SDPUtils.matchPrefix(mediaSection, 'a=ssrc:') + .map(function(line) { + return SDPUtils.parseSsrcMedia(line); + }) + .filter(function(obj) { + return obj.attribute === 'cname'; + })[0]; + if (remoteSsrc) { + rtcpParameters.cname = remoteSsrc.value; + rtcpParameters.ssrc = remoteSsrc.ssrc; + } + + // Edge uses the compound attribute instead of reducedSize + // compound is !reducedSize + var rsize = SDPUtils.matchPrefix(mediaSection, 'a=rtcp-rsize'); + rtcpParameters.reducedSize = rsize.length > 0; + rtcpParameters.compound = rsize.length === 0; + + // parses the rtcp-mux attrіbute. + // Note that Edge does not support unmuxed RTCP. + var mux = SDPUtils.matchPrefix(mediaSection, 'a=rtcp-mux'); + rtcpParameters.mux = mux.length > 0; + + return rtcpParameters; +}; + +// parses either a=msid: or a=ssrc:... msid lines and returns +// the id of the MediaStream and MediaStreamTrack. +SDPUtils.parseMsid = function(mediaSection) { + var parts; + var spec = SDPUtils.matchPrefix(mediaSection, 'a=msid:'); + if (spec.length === 1) { + parts = spec[0].substr(7).split(' '); + return {stream: parts[0], track: parts[1]}; + } + var planB = SDPUtils.matchPrefix(mediaSection, 'a=ssrc:') + .map(function(line) { + return SDPUtils.parseSsrcMedia(line); + }) + .filter(function(msidParts) { + return msidParts.attribute === 'msid'; + }); + if (planB.length > 0) { + parts = planB[0].value.split(' '); + return {stream: parts[0], track: parts[1]}; + } +}; + +// Generate a session ID for SDP. +// https://tools.ietf.org/html/draft-ietf-rtcweb-jsep-20#section-5.2.1 +// recommends using a cryptographically random +ve 64-bit value +// but right now this should be acceptable and within the right range +SDPUtils.generateSessionId = function() { + return Math.random().toString().substr(2, 21); +}; + +// Write boilder plate for start of SDP +// sessId argument is optional - if not supplied it will +// be generated randomly +// sessVersion is optional and defaults to 2 +// sessUser is optional and defaults to 'thisisadapterortc' +SDPUtils.writeSessionBoilerplate = function(sessId, sessVer, sessUser) { + var sessionId; + var version = sessVer !== undefined ? sessVer : 2; + if (sessId) { + sessionId = sessId; + } else { + sessionId = SDPUtils.generateSessionId(); + } + var user = sessUser || 'thisisadapterortc'; + // FIXME: sess-id should be an NTP timestamp. + return 'v=0\r\n' + + 'o=' + user + ' ' + sessionId + ' ' + version + + ' IN IP4 127.0.0.1\r\n' + + 's=-\r\n' + + 't=0 0\r\n'; +}; + +SDPUtils.writeMediaSection = function(transceiver, caps, type, stream) { + var sdp = SDPUtils.writeRtpDescription(transceiver.kind, caps); + + // Map ICE parameters (ufrag, pwd) to SDP. + sdp += SDPUtils.writeIceParameters( + transceiver.iceGatherer.getLocalParameters()); + + // Map DTLS parameters to SDP. + sdp += SDPUtils.writeDtlsParameters( + transceiver.dtlsTransport.getLocalParameters(), + type === 'offer' ? 'actpass' : 'active'); + + sdp += 'a=mid:' + transceiver.mid + '\r\n'; + + if (transceiver.direction) { + sdp += 'a=' + transceiver.direction + '\r\n'; + } else if (transceiver.rtpSender && transceiver.rtpReceiver) { + sdp += 'a=sendrecv\r\n'; + } else if (transceiver.rtpSender) { + sdp += 'a=sendonly\r\n'; + } else if (transceiver.rtpReceiver) { + sdp += 'a=recvonly\r\n'; + } else { + sdp += 'a=inactive\r\n'; + } + + if (transceiver.rtpSender) { + // spec. + var msid = 'msid:' + stream.id + ' ' + + transceiver.rtpSender.track.id + '\r\n'; + sdp += 'a=' + msid; + + // for Chrome. + sdp += 'a=ssrc:' + transceiver.sendEncodingParameters[0].ssrc + + ' ' + msid; + if (transceiver.sendEncodingParameters[0].rtx) { + sdp += 'a=ssrc:' + transceiver.sendEncodingParameters[0].rtx.ssrc + + ' ' + msid; + sdp += 'a=ssrc-group:FID ' + + transceiver.sendEncodingParameters[0].ssrc + ' ' + + transceiver.sendEncodingParameters[0].rtx.ssrc + + '\r\n'; + } + } + // FIXME: this should be written by writeRtpDescription. + sdp += 'a=ssrc:' + transceiver.sendEncodingParameters[0].ssrc + + ' cname:' + SDPUtils.localCName + '\r\n'; + if (transceiver.rtpSender && transceiver.sendEncodingParameters[0].rtx) { + sdp += 'a=ssrc:' + transceiver.sendEncodingParameters[0].rtx.ssrc + + ' cname:' + SDPUtils.localCName + '\r\n'; + } + return sdp; +}; + +// Gets the direction from the mediaSection or the sessionpart. +SDPUtils.getDirection = function(mediaSection, sessionpart) { + // Look for sendrecv, sendonly, recvonly, inactive, default to sendrecv. + var lines = SDPUtils.splitLines(mediaSection); + for (var i = 0; i < lines.length; i++) { + switch (lines[i]) { + case 'a=sendrecv': + case 'a=sendonly': + case 'a=recvonly': + case 'a=inactive': + return lines[i].substr(2); + default: + // FIXME: What should happen here? + } + } + if (sessionpart) { + return SDPUtils.getDirection(sessionpart); + } + return 'sendrecv'; +}; + +SDPUtils.getKind = function(mediaSection) { + var lines = SDPUtils.splitLines(mediaSection); + var mline = lines[0].split(' '); + return mline[0].substr(2); +}; + +SDPUtils.isRejected = function(mediaSection) { + return mediaSection.split(' ', 2)[1] === '0'; +}; + +SDPUtils.parseMLine = function(mediaSection) { + var lines = SDPUtils.splitLines(mediaSection); + var parts = lines[0].substr(2).split(' '); + return { + kind: parts[0], + port: parseInt(parts[1], 10), + protocol: parts[2], + fmt: parts.slice(3).join(' ') + }; +}; + +SDPUtils.parseOLine = function(mediaSection) { + var line = SDPUtils.matchPrefix(mediaSection, 'o=')[0]; + var parts = line.substr(2).split(' '); + return { + username: parts[0], + sessionId: parts[1], + sessionVersion: parseInt(parts[2], 10), + netType: parts[3], + addressType: parts[4], + address: parts[5] + }; +}; + +// a very naive interpretation of a valid SDP. +SDPUtils.isValidSDP = function(blob) { + if (typeof blob !== 'string' || blob.length === 0) { + return false; + } + var lines = SDPUtils.splitLines(blob); + for (var i = 0; i < lines.length; i++) { + if (lines[i].length < 2 || lines[i].charAt(1) !== '=') { + return false; + } + // TODO: check the modifier a bit more. + } + return true; +}; + +// Expose public methods. +if (typeof module === 'object') { + module.exports = SDPUtils; +} + +},{}],3:[function(require,module,exports){ +(function (global){ +/* + * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. + */ + /* eslint-env node */ + +'use strict'; + +var adapterFactory = require('./adapter_factory.js'); +module.exports = adapterFactory({window: global.window}); + +}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) +},{"./adapter_factory.js":4}],4:[function(require,module,exports){ +/* + * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. + */ + /* eslint-env node */ + +'use strict'; + +var utils = require('./utils'); +// Shimming starts here. +module.exports = function(dependencies, opts) { + var window = dependencies && dependencies.window; + + var options = { + shimChrome: true, + shimFirefox: true, + shimEdge: true, + shimSafari: true, + }; + + for (var key in opts) { + if (hasOwnProperty.call(opts, key)) { + options[key] = opts[key]; + } + } + + // Utils. + var logging = utils.log; + var browserDetails = utils.detectBrowser(window); + + // Uncomment the line below if you want logging to occur, including logging + // for the switch statement below. Can also be turned on in the browser via + // adapter.disableLog(false), but then logging from the switch statement below + // will not appear. + // require('./utils').disableLog(false); + + // Browser shims. + var chromeShim = require('./chrome/chrome_shim') || null; + var edgeShim = require('./edge/edge_shim') || null; + var firefoxShim = require('./firefox/firefox_shim') || null; + var safariShim = require('./safari/safari_shim') || null; + var commonShim = require('./common_shim') || null; + + // Export to the adapter global object visible in the browser. + var adapter = { + browserDetails: browserDetails, + commonShim: commonShim, + extractVersion: utils.extractVersion, + disableLog: utils.disableLog, + disableWarnings: utils.disableWarnings + }; + + // Shim browser if found. + switch (browserDetails.browser) { + case 'chrome': + if (!chromeShim || !chromeShim.shimPeerConnection || + !options.shimChrome) { + logging('Chrome shim is not included in this adapter release.'); + return adapter; + } + logging('adapter.js shimming chrome.'); + // Export to the adapter global object visible in the browser. + adapter.browserShim = chromeShim; + commonShim.shimCreateObjectURL(window); + + chromeShim.shimGetUserMedia(window); + chromeShim.shimMediaStream(window); + chromeShim.shimSourceObject(window); + chromeShim.shimPeerConnection(window); + chromeShim.shimOnTrack(window); + chromeShim.shimAddTrackRemoveTrack(window); + chromeShim.shimGetSendersWithDtmf(window); + chromeShim.shimSenderReceiverGetStats(window); + chromeShim.fixNegotiationNeeded(window); + + commonShim.shimRTCIceCandidate(window); + commonShim.shimMaxMessageSize(window); + commonShim.shimSendThrowTypeError(window); + break; + case 'firefox': + if (!firefoxShim || !firefoxShim.shimPeerConnection || + !options.shimFirefox) { + logging('Firefox shim is not included in this adapter release.'); + return adapter; + } + logging('adapter.js shimming firefox.'); + // Export to the adapter global object visible in the browser. + adapter.browserShim = firefoxShim; + commonShim.shimCreateObjectURL(window); + + firefoxShim.shimGetUserMedia(window); + firefoxShim.shimSourceObject(window); + firefoxShim.shimPeerConnection(window); + firefoxShim.shimOnTrack(window); + firefoxShim.shimRemoveStream(window); + firefoxShim.shimSenderGetStats(window); + firefoxShim.shimReceiverGetStats(window); + firefoxShim.shimRTCDataChannel(window); + + commonShim.shimRTCIceCandidate(window); + commonShim.shimMaxMessageSize(window); + commonShim.shimSendThrowTypeError(window); + break; + case 'edge': + if (!edgeShim || !edgeShim.shimPeerConnection || !options.shimEdge) { + logging('MS edge shim is not included in this adapter release.'); + return adapter; + } + logging('adapter.js shimming edge.'); + // Export to the adapter global object visible in the browser. + adapter.browserShim = edgeShim; + commonShim.shimCreateObjectURL(window); + + edgeShim.shimGetUserMedia(window); + edgeShim.shimPeerConnection(window); + edgeShim.shimReplaceTrack(window); + edgeShim.shimGetDisplayMedia(window); + + // the edge shim implements the full RTCIceCandidate object. + + commonShim.shimMaxMessageSize(window); + commonShim.shimSendThrowTypeError(window); + break; + case 'safari': + if (!safariShim || !options.shimSafari) { + logging('Safari shim is not included in this adapter release.'); + return adapter; + } + logging('adapter.js shimming safari.'); + // Export to the adapter global object visible in the browser. + adapter.browserShim = safariShim; + commonShim.shimCreateObjectURL(window); + + safariShim.shimRTCIceServerUrls(window); + safariShim.shimCreateOfferLegacy(window); + safariShim.shimCallbacksAPI(window); + safariShim.shimLocalStreamsAPI(window); + safariShim.shimRemoteStreamsAPI(window); + safariShim.shimTrackEventTransceiver(window); + safariShim.shimGetUserMedia(window); + + commonShim.shimRTCIceCandidate(window); + commonShim.shimMaxMessageSize(window); + commonShim.shimSendThrowTypeError(window); + break; + default: + logging('Unsupported browser!'); + break; + } + + return adapter; +}; + +},{"./chrome/chrome_shim":5,"./common_shim":7,"./edge/edge_shim":8,"./firefox/firefox_shim":11,"./safari/safari_shim":13,"./utils":14}],5:[function(require,module,exports){ + +/* + * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. + */ + /* eslint-env node */ +'use strict'; +var utils = require('../utils.js'); +var logging = utils.log; + +/* iterates the stats graph recursively. */ +function walkStats(stats, base, resultSet) { + if (!base || resultSet.has(base.id)) { + return; + } + resultSet.set(base.id, base); + Object.keys(base).forEach(function(name) { + if (name.endsWith('Id')) { + walkStats(stats, stats.get(base[name]), resultSet); + } else if (name.endsWith('Ids')) { + base[name].forEach(function(id) { + walkStats(stats, stats.get(id), resultSet); + }); + } + }); +} + +/* filter getStats for a sender/receiver track. */ +function filterStats(result, track, outbound) { + var streamStatsType = outbound ? 'outbound-rtp' : 'inbound-rtp'; + var filteredResult = new Map(); + if (track === null) { + return filteredResult; + } + var trackStats = []; + result.forEach(function(value) { + if (value.type === 'track' && + value.trackIdentifier === track.id) { + trackStats.push(value); + } + }); + trackStats.forEach(function(trackStat) { + result.forEach(function(stats) { + if (stats.type === streamStatsType && stats.trackId === trackStat.id) { + walkStats(result, stats, filteredResult); + } + }); + }); + return filteredResult; +} + +module.exports = { + shimGetUserMedia: require('./getusermedia'), + shimMediaStream: function(window) { + window.MediaStream = window.MediaStream || window.webkitMediaStream; + }, + + shimOnTrack: function(window) { + if (typeof window === 'object' && window.RTCPeerConnection && !('ontrack' in + window.RTCPeerConnection.prototype)) { + Object.defineProperty(window.RTCPeerConnection.prototype, 'ontrack', { + get: function() { + return this._ontrack; + }, + set: function(f) { + if (this._ontrack) { + this.removeEventListener('track', this._ontrack); + } + this.addEventListener('track', this._ontrack = f); + }, + enumerable: true, + configurable: true + }); + var origSetRemoteDescription = + window.RTCPeerConnection.prototype.setRemoteDescription; + window.RTCPeerConnection.prototype.setRemoteDescription = function() { + var pc = this; + if (!pc._ontrackpoly) { + pc._ontrackpoly = function(e) { + // onaddstream does not fire when a track is added to an existing + // stream. But stream.onaddtrack is implemented so we use that. + e.stream.addEventListener('addtrack', function(te) { + var receiver; + if (window.RTCPeerConnection.prototype.getReceivers) { + receiver = pc.getReceivers().find(function(r) { + return r.track && r.track.id === te.track.id; + }); + } else { + receiver = {track: te.track}; + } + + var event = new Event('track'); + event.track = te.track; + event.receiver = receiver; + event.transceiver = {receiver: receiver}; + event.streams = [e.stream]; + pc.dispatchEvent(event); + }); + e.stream.getTracks().forEach(function(track) { + var receiver; + if (window.RTCPeerConnection.prototype.getReceivers) { + receiver = pc.getReceivers().find(function(r) { + return r.track && r.track.id === track.id; + }); + } else { + receiver = {track: track}; + } + var event = new Event('track'); + event.track = track; + event.receiver = receiver; + event.transceiver = {receiver: receiver}; + event.streams = [e.stream]; + pc.dispatchEvent(event); + }); + }; + pc.addEventListener('addstream', pc._ontrackpoly); + } + return origSetRemoteDescription.apply(pc, arguments); + }; + } else { + // even if RTCRtpTransceiver is in window, it is only used and + // emitted in unified-plan. Unfortunately this means we need + // to unconditionally wrap the event. + utils.wrapPeerConnectionEvent(window, 'track', function(e) { + if (!e.transceiver) { + Object.defineProperty(e, 'transceiver', + {value: {receiver: e.receiver}}); + } + return e; + }); + } + }, + + shimGetSendersWithDtmf: function(window) { + // Overrides addTrack/removeTrack, depends on shimAddTrackRemoveTrack. + if (typeof window === 'object' && window.RTCPeerConnection && + !('getSenders' in window.RTCPeerConnection.prototype) && + 'createDTMFSender' in window.RTCPeerConnection.prototype) { + var shimSenderWithDtmf = function(pc, track) { + return { + track: track, + get dtmf() { + if (this._dtmf === undefined) { + if (track.kind === 'audio') { + this._dtmf = pc.createDTMFSender(track); + } else { + this._dtmf = null; + } + } + return this._dtmf; + }, + _pc: pc + }; + }; + + // augment addTrack when getSenders is not available. + if (!window.RTCPeerConnection.prototype.getSenders) { + window.RTCPeerConnection.prototype.getSenders = function() { + this._senders = this._senders || []; + return this._senders.slice(); // return a copy of the internal state. + }; + var origAddTrack = window.RTCPeerConnection.prototype.addTrack; + window.RTCPeerConnection.prototype.addTrack = function(track, stream) { + var pc = this; + var sender = origAddTrack.apply(pc, arguments); + if (!sender) { + sender = shimSenderWithDtmf(pc, track); + pc._senders.push(sender); + } + return sender; + }; + + var origRemoveTrack = window.RTCPeerConnection.prototype.removeTrack; + window.RTCPeerConnection.prototype.removeTrack = function(sender) { + var pc = this; + origRemoveTrack.apply(pc, arguments); + var idx = pc._senders.indexOf(sender); + if (idx !== -1) { + pc._senders.splice(idx, 1); + } + }; + } + var origAddStream = window.RTCPeerConnection.prototype.addStream; + window.RTCPeerConnection.prototype.addStream = function(stream) { + var pc = this; + pc._senders = pc._senders || []; + origAddStream.apply(pc, [stream]); + stream.getTracks().forEach(function(track) { + pc._senders.push(shimSenderWithDtmf(pc, track)); + }); + }; + + var origRemoveStream = window.RTCPeerConnection.prototype.removeStream; + window.RTCPeerConnection.prototype.removeStream = function(stream) { + var pc = this; + pc._senders = pc._senders || []; + origRemoveStream.apply(pc, [stream]); + + stream.getTracks().forEach(function(track) { + var sender = pc._senders.find(function(s) { + return s.track === track; + }); + if (sender) { + pc._senders.splice(pc._senders.indexOf(sender), 1); // remove sender + } + }); + }; + } else if (typeof window === 'object' && window.RTCPeerConnection && + 'getSenders' in window.RTCPeerConnection.prototype && + 'createDTMFSender' in window.RTCPeerConnection.prototype && + window.RTCRtpSender && + !('dtmf' in window.RTCRtpSender.prototype)) { + var origGetSenders = window.RTCPeerConnection.prototype.getSenders; + window.RTCPeerConnection.prototype.getSenders = function() { + var pc = this; + var senders = origGetSenders.apply(pc, []); + senders.forEach(function(sender) { + sender._pc = pc; + }); + return senders; + }; + + Object.defineProperty(window.RTCRtpSender.prototype, 'dtmf', { + get: function() { + if (this._dtmf === undefined) { + if (this.track.kind === 'audio') { + this._dtmf = this._pc.createDTMFSender(this.track); + } else { + this._dtmf = null; + } + } + return this._dtmf; + } + }); + } + }, + + shimSenderReceiverGetStats: function(window) { + if (!(typeof window === 'object' && window.RTCPeerConnection && + window.RTCRtpSender && window.RTCRtpReceiver)) { + return; + } + + // shim sender stats. + if (!('getStats' in window.RTCRtpSender.prototype)) { + var origGetSenders = window.RTCPeerConnection.prototype.getSenders; + if (origGetSenders) { + window.RTCPeerConnection.prototype.getSenders = function() { + var pc = this; + var senders = origGetSenders.apply(pc, []); + senders.forEach(function(sender) { + sender._pc = pc; + }); + return senders; + }; + } + + var origAddTrack = window.RTCPeerConnection.prototype.addTrack; + if (origAddTrack) { + window.RTCPeerConnection.prototype.addTrack = function() { + var sender = origAddTrack.apply(this, arguments); + sender._pc = this; + return sender; + }; + } + window.RTCRtpSender.prototype.getStats = function() { + var sender = this; + return this._pc.getStats().then(function(result) { + /* Note: this will include stats of all senders that + * send a track with the same id as sender.track as + * it is not possible to identify the RTCRtpSender. + */ + return filterStats(result, sender.track, true); + }); + }; + } + + // shim receiver stats. + if (!('getStats' in window.RTCRtpReceiver.prototype)) { + var origGetReceivers = window.RTCPeerConnection.prototype.getReceivers; + if (origGetReceivers) { + window.RTCPeerConnection.prototype.getReceivers = function() { + var pc = this; + var receivers = origGetReceivers.apply(pc, []); + receivers.forEach(function(receiver) { + receiver._pc = pc; + }); + return receivers; + }; + } + utils.wrapPeerConnectionEvent(window, 'track', function(e) { + e.receiver._pc = e.srcElement; + return e; + }); + window.RTCRtpReceiver.prototype.getStats = function() { + var receiver = this; + return this._pc.getStats().then(function(result) { + return filterStats(result, receiver.track, false); + }); + }; + } + + if (!('getStats' in window.RTCRtpSender.prototype && + 'getStats' in window.RTCRtpReceiver.prototype)) { + return; + } + + // shim RTCPeerConnection.getStats(track). + var origGetStats = window.RTCPeerConnection.prototype.getStats; + window.RTCPeerConnection.prototype.getStats = function() { + var pc = this; + if (arguments.length > 0 && + arguments[0] instanceof window.MediaStreamTrack) { + var track = arguments[0]; + var sender; + var receiver; + var err; + pc.getSenders().forEach(function(s) { + if (s.track === track) { + if (sender) { + err = true; + } else { + sender = s; + } + } + }); + pc.getReceivers().forEach(function(r) { + if (r.track === track) { + if (receiver) { + err = true; + } else { + receiver = r; + } + } + return r.track === track; + }); + if (err || (sender && receiver)) { + return Promise.reject(new DOMException( + 'There are more than one sender or receiver for the track.', + 'InvalidAccessError')); + } else if (sender) { + return sender.getStats(); + } else if (receiver) { + return receiver.getStats(); + } + return Promise.reject(new DOMException( + 'There is no sender or receiver for the track.', + 'InvalidAccessError')); + } + return origGetStats.apply(pc, arguments); + }; + }, + + shimSourceObject: function(window) { + var URL = window && window.URL; + + if (typeof window === 'object') { + if (window.HTMLMediaElement && + !('srcObject' in window.HTMLMediaElement.prototype)) { + // Shim the srcObject property, once, when HTMLMediaElement is found. + Object.defineProperty(window.HTMLMediaElement.prototype, 'srcObject', { + get: function() { + return this._srcObject; + }, + set: function(stream) { + var self = this; + // Use _srcObject as a private property for this shim + this._srcObject = stream; + if (this.src) { + URL.revokeObjectURL(this.src); + } + + if (!stream) { + this.src = ''; + return undefined; + } + this.src = URL.createObjectURL(stream); + // We need to recreate the blob url when a track is added or + // removed. Doing it manually since we want to avoid a recursion. + stream.addEventListener('addtrack', function() { + if (self.src) { + URL.revokeObjectURL(self.src); + } + self.src = URL.createObjectURL(stream); + }); + stream.addEventListener('removetrack', function() { + if (self.src) { + URL.revokeObjectURL(self.src); + } + self.src = URL.createObjectURL(stream); + }); + } + }); + } + } + }, + + shimAddTrackRemoveTrackWithNative: function(window) { + // shim addTrack/removeTrack with native variants in order to make + // the interactions with legacy getLocalStreams behave as in other browsers. + // Keeps a mapping stream.id => [stream, rtpsenders...] + window.RTCPeerConnection.prototype.getLocalStreams = function() { + var pc = this; + this._shimmedLocalStreams = this._shimmedLocalStreams || {}; + return Object.keys(this._shimmedLocalStreams).map(function(streamId) { + return pc._shimmedLocalStreams[streamId][0]; + }); + }; + + var origAddTrack = window.RTCPeerConnection.prototype.addTrack; + window.RTCPeerConnection.prototype.addTrack = function(track, stream) { + if (!stream) { + return origAddTrack.apply(this, arguments); + } + this._shimmedLocalStreams = this._shimmedLocalStreams || {}; + + var sender = origAddTrack.apply(this, arguments); + if (!this._shimmedLocalStreams[stream.id]) { + this._shimmedLocalStreams[stream.id] = [stream, sender]; + } else if (this._shimmedLocalStreams[stream.id].indexOf(sender) === -1) { + this._shimmedLocalStreams[stream.id].push(sender); + } + return sender; + }; + + var origAddStream = window.RTCPeerConnection.prototype.addStream; + window.RTCPeerConnection.prototype.addStream = function(stream) { + var pc = this; + this._shimmedLocalStreams = this._shimmedLocalStreams || {}; + + stream.getTracks().forEach(function(track) { + var alreadyExists = pc.getSenders().find(function(s) { + return s.track === track; + }); + if (alreadyExists) { + throw new DOMException('Track already exists.', + 'InvalidAccessError'); + } + }); + var existingSenders = pc.getSenders(); + origAddStream.apply(this, arguments); + var newSenders = pc.getSenders().filter(function(newSender) { + return existingSenders.indexOf(newSender) === -1; + }); + this._shimmedLocalStreams[stream.id] = [stream].concat(newSenders); + }; + + var origRemoveStream = window.RTCPeerConnection.prototype.removeStream; + window.RTCPeerConnection.prototype.removeStream = function(stream) { + this._shimmedLocalStreams = this._shimmedLocalStreams || {}; + delete this._shimmedLocalStreams[stream.id]; + return origRemoveStream.apply(this, arguments); + }; + + var origRemoveTrack = window.RTCPeerConnection.prototype.removeTrack; + window.RTCPeerConnection.prototype.removeTrack = function(sender) { + var pc = this; + this._shimmedLocalStreams = this._shimmedLocalStreams || {}; + if (sender) { + Object.keys(this._shimmedLocalStreams).forEach(function(streamId) { + var idx = pc._shimmedLocalStreams[streamId].indexOf(sender); + if (idx !== -1) { + pc._shimmedLocalStreams[streamId].splice(idx, 1); + } + if (pc._shimmedLocalStreams[streamId].length === 1) { + delete pc._shimmedLocalStreams[streamId]; + } + }); + } + return origRemoveTrack.apply(this, arguments); + }; + }, + + shimAddTrackRemoveTrack: function(window) { + var browserDetails = utils.detectBrowser(window); + // shim addTrack and removeTrack. + if (window.RTCPeerConnection.prototype.addTrack && + browserDetails.version >= 65) { + return this.shimAddTrackRemoveTrackWithNative(window); + } + + // also shim pc.getLocalStreams when addTrack is shimmed + // to return the original streams. + var origGetLocalStreams = window.RTCPeerConnection.prototype + .getLocalStreams; + window.RTCPeerConnection.prototype.getLocalStreams = function() { + var pc = this; + var nativeStreams = origGetLocalStreams.apply(this); + pc._reverseStreams = pc._reverseStreams || {}; + return nativeStreams.map(function(stream) { + return pc._reverseStreams[stream.id]; + }); + }; + + var origAddStream = window.RTCPeerConnection.prototype.addStream; + window.RTCPeerConnection.prototype.addStream = function(stream) { + var pc = this; + pc._streams = pc._streams || {}; + pc._reverseStreams = pc._reverseStreams || {}; + + stream.getTracks().forEach(function(track) { + var alreadyExists = pc.getSenders().find(function(s) { + return s.track === track; + }); + if (alreadyExists) { + throw new DOMException('Track already exists.', + 'InvalidAccessError'); + } + }); + // Add identity mapping for consistency with addTrack. + // Unless this is being used with a stream from addTrack. + if (!pc._reverseStreams[stream.id]) { + var newStream = new window.MediaStream(stream.getTracks()); + pc._streams[stream.id] = newStream; + pc._reverseStreams[newStream.id] = stream; + stream = newStream; + } + origAddStream.apply(pc, [stream]); + }; + + var origRemoveStream = window.RTCPeerConnection.prototype.removeStream; + window.RTCPeerConnection.prototype.removeStream = function(stream) { + var pc = this; + pc._streams = pc._streams || {}; + pc._reverseStreams = pc._reverseStreams || {}; + + origRemoveStream.apply(pc, [(pc._streams[stream.id] || stream)]); + delete pc._reverseStreams[(pc._streams[stream.id] ? + pc._streams[stream.id].id : stream.id)]; + delete pc._streams[stream.id]; + }; + + window.RTCPeerConnection.prototype.addTrack = function(track, stream) { + var pc = this; + if (pc.signalingState === 'closed') { + throw new DOMException( + 'The RTCPeerConnection\'s signalingState is \'closed\'.', + 'InvalidStateError'); + } + var streams = [].slice.call(arguments, 1); + if (streams.length !== 1 || + !streams[0].getTracks().find(function(t) { + return t === track; + })) { + // this is not fully correct but all we can manage without + // [[associated MediaStreams]] internal slot. + throw new DOMException( + 'The adapter.js addTrack polyfill only supports a single ' + + ' stream which is associated with the specified track.', + 'NotSupportedError'); + } + + var alreadyExists = pc.getSenders().find(function(s) { + return s.track === track; + }); + if (alreadyExists) { + throw new DOMException('Track already exists.', + 'InvalidAccessError'); + } + + pc._streams = pc._streams || {}; + pc._reverseStreams = pc._reverseStreams || {}; + var oldStream = pc._streams[stream.id]; + if (oldStream) { + // this is using odd Chrome behaviour, use with caution: + // https://bugs.chromium.org/p/webrtc/issues/detail?id=7815 + // Note: we rely on the high-level addTrack/dtmf shim to + // create the sender with a dtmf sender. + oldStream.addTrack(track); + + // Trigger ONN async. + Promise.resolve().then(function() { + pc.dispatchEvent(new Event('negotiationneeded')); + }); + } else { + var newStream = new window.MediaStream([track]); + pc._streams[stream.id] = newStream; + pc._reverseStreams[newStream.id] = stream; + pc.addStream(newStream); + } + return pc.getSenders().find(function(s) { + return s.track === track; + }); + }; + + // replace the internal stream id with the external one and + // vice versa. + function replaceInternalStreamId(pc, description) { + var sdp = description.sdp; + Object.keys(pc._reverseStreams || []).forEach(function(internalId) { + var externalStream = pc._reverseStreams[internalId]; + var internalStream = pc._streams[externalStream.id]; + sdp = sdp.replace(new RegExp(internalStream.id, 'g'), + externalStream.id); + }); + return new RTCSessionDescription({ + type: description.type, + sdp: sdp + }); + } + function replaceExternalStreamId(pc, description) { + var sdp = description.sdp; + Object.keys(pc._reverseStreams || []).forEach(function(internalId) { + var externalStream = pc._reverseStreams[internalId]; + var internalStream = pc._streams[externalStream.id]; + sdp = sdp.replace(new RegExp(externalStream.id, 'g'), + internalStream.id); + }); + return new RTCSessionDescription({ + type: description.type, + sdp: sdp + }); + } + ['createOffer', 'createAnswer'].forEach(function(method) { + var nativeMethod = window.RTCPeerConnection.prototype[method]; + window.RTCPeerConnection.prototype[method] = function() { + var pc = this; + var args = arguments; + var isLegacyCall = arguments.length && + typeof arguments[0] === 'function'; + if (isLegacyCall) { + return nativeMethod.apply(pc, [ + function(description) { + var desc = replaceInternalStreamId(pc, description); + args[0].apply(null, [desc]); + }, + function(err) { + if (args[1]) { + args[1].apply(null, err); + } + }, arguments[2] + ]); + } + return nativeMethod.apply(pc, arguments) + .then(function(description) { + return replaceInternalStreamId(pc, description); + }); + }; + }); + + var origSetLocalDescription = + window.RTCPeerConnection.prototype.setLocalDescription; + window.RTCPeerConnection.prototype.setLocalDescription = function() { + var pc = this; + if (!arguments.length || !arguments[0].type) { + return origSetLocalDescription.apply(pc, arguments); + } + arguments[0] = replaceExternalStreamId(pc, arguments[0]); + return origSetLocalDescription.apply(pc, arguments); + }; + + // TODO: mangle getStats: https://w3c.github.io/webrtc-stats/#dom-rtcmediastreamstats-streamidentifier + + var origLocalDescription = Object.getOwnPropertyDescriptor( + window.RTCPeerConnection.prototype, 'localDescription'); + Object.defineProperty(window.RTCPeerConnection.prototype, + 'localDescription', { + get: function() { + var pc = this; + var description = origLocalDescription.get.apply(this); + if (description.type === '') { + return description; + } + return replaceInternalStreamId(pc, description); + } + }); + + window.RTCPeerConnection.prototype.removeTrack = function(sender) { + var pc = this; + if (pc.signalingState === 'closed') { + throw new DOMException( + 'The RTCPeerConnection\'s signalingState is \'closed\'.', + 'InvalidStateError'); + } + // We can not yet check for sender instanceof RTCRtpSender + // since we shim RTPSender. So we check if sender._pc is set. + if (!sender._pc) { + throw new DOMException('Argument 1 of RTCPeerConnection.removeTrack ' + + 'does not implement interface RTCRtpSender.', 'TypeError'); + } + var isLocal = sender._pc === pc; + if (!isLocal) { + throw new DOMException('Sender was not created by this connection.', + 'InvalidAccessError'); + } + + // Search for the native stream the senders track belongs to. + pc._streams = pc._streams || {}; + var stream; + Object.keys(pc._streams).forEach(function(streamid) { + var hasTrack = pc._streams[streamid].getTracks().find(function(track) { + return sender.track === track; + }); + if (hasTrack) { + stream = pc._streams[streamid]; + } + }); + + if (stream) { + if (stream.getTracks().length === 1) { + // if this is the last track of the stream, remove the stream. This + // takes care of any shimmed _senders. + pc.removeStream(pc._reverseStreams[stream.id]); + } else { + // relying on the same odd chrome behaviour as above. + stream.removeTrack(sender.track); + } + pc.dispatchEvent(new Event('negotiationneeded')); + } + }; + }, + + shimPeerConnection: function(window) { + var browserDetails = utils.detectBrowser(window); + + // The RTCPeerConnection object. + if (!window.RTCPeerConnection && window.webkitRTCPeerConnection) { + window.RTCPeerConnection = function(pcConfig, pcConstraints) { + // Translate iceTransportPolicy to iceTransports, + // see https://code.google.com/p/webrtc/issues/detail?id=4869 + // this was fixed in M56 along with unprefixing RTCPeerConnection. + logging('PeerConnection'); + if (pcConfig && pcConfig.iceTransportPolicy) { + pcConfig.iceTransports = pcConfig.iceTransportPolicy; + } + + return new window.webkitRTCPeerConnection(pcConfig, pcConstraints); + }; + window.RTCPeerConnection.prototype = + window.webkitRTCPeerConnection.prototype; + // wrap static methods. Currently just generateCertificate. + if (window.webkitRTCPeerConnection.generateCertificate) { + Object.defineProperty(window.RTCPeerConnection, 'generateCertificate', { + get: function() { + return window.webkitRTCPeerConnection.generateCertificate; + } + }); + } + } + + var origGetStats = window.RTCPeerConnection.prototype.getStats; + window.RTCPeerConnection.prototype.getStats = function(selector, + successCallback, errorCallback) { + var pc = this; + var args = arguments; + + // If selector is a function then we are in the old style stats so just + // pass back the original getStats format to avoid breaking old users. + if (arguments.length > 0 && typeof selector === 'function') { + return origGetStats.apply(this, arguments); + } + + // When spec-style getStats is supported, return those when called with + // either no arguments or the selector argument is null. + if (origGetStats.length === 0 && (arguments.length === 0 || + typeof arguments[0] !== 'function')) { + return origGetStats.apply(this, []); + } + + var fixChromeStats_ = function(response) { + var standardReport = {}; + var reports = response.result(); + reports.forEach(function(report) { + var standardStats = { + id: report.id, + timestamp: report.timestamp, + type: { + localcandidate: 'local-candidate', + remotecandidate: 'remote-candidate' + }[report.type] || report.type + }; + report.names().forEach(function(name) { + standardStats[name] = report.stat(name); + }); + standardReport[standardStats.id] = standardStats; + }); + + return standardReport; + }; + + // shim getStats with maplike support + var makeMapStats = function(stats) { + return new Map(Object.keys(stats).map(function(key) { + return [key, stats[key]]; + })); + }; + + if (arguments.length >= 2) { + var successCallbackWrapper_ = function(response) { + args[1](makeMapStats(fixChromeStats_(response))); + }; + + return origGetStats.apply(this, [successCallbackWrapper_, + arguments[0]]); + } + + // promise-support + return new Promise(function(resolve, reject) { + origGetStats.apply(pc, [ + function(response) { + resolve(makeMapStats(fixChromeStats_(response))); + }, reject]); + }).then(successCallback, errorCallback); + }; + + // add promise support -- natively available in Chrome 51 + if (browserDetails.version < 51) { + ['setLocalDescription', 'setRemoteDescription', 'addIceCandidate'] + .forEach(function(method) { + var nativeMethod = window.RTCPeerConnection.prototype[method]; + window.RTCPeerConnection.prototype[method] = function() { + var args = arguments; + var pc = this; + var promise = new Promise(function(resolve, reject) { + nativeMethod.apply(pc, [args[0], resolve, reject]); + }); + if (args.length < 2) { + return promise; + } + return promise.then(function() { + args[1].apply(null, []); + }, + function(err) { + if (args.length >= 3) { + args[2].apply(null, [err]); + } + }); + }; + }); + } + + // promise support for createOffer and createAnswer. Available (without + // bugs) since M52: crbug/619289 + if (browserDetails.version < 52) { + ['createOffer', 'createAnswer'].forEach(function(method) { + var nativeMethod = window.RTCPeerConnection.prototype[method]; + window.RTCPeerConnection.prototype[method] = function() { + var pc = this; + if (arguments.length < 1 || (arguments.length === 1 && + typeof arguments[0] === 'object')) { + var opts = arguments.length === 1 ? arguments[0] : undefined; + return new Promise(function(resolve, reject) { + nativeMethod.apply(pc, [resolve, reject, opts]); + }); + } + return nativeMethod.apply(this, arguments); + }; + }); + } + + // shim implicit creation of RTCSessionDescription/RTCIceCandidate + ['setLocalDescription', 'setRemoteDescription', 'addIceCandidate'] + .forEach(function(method) { + var nativeMethod = window.RTCPeerConnection.prototype[method]; + window.RTCPeerConnection.prototype[method] = function() { + arguments[0] = new ((method === 'addIceCandidate') ? + window.RTCIceCandidate : + window.RTCSessionDescription)(arguments[0]); + return nativeMethod.apply(this, arguments); + }; + }); + + // support for addIceCandidate(null or undefined) + var nativeAddIceCandidate = + window.RTCPeerConnection.prototype.addIceCandidate; + window.RTCPeerConnection.prototype.addIceCandidate = function() { + if (!arguments[0]) { + if (arguments[1]) { + arguments[1].apply(null); + } + return Promise.resolve(); + } + return nativeAddIceCandidate.apply(this, arguments); + }; + }, + + fixNegotiationNeeded: function(window) { + utils.wrapPeerConnectionEvent(window, 'negotiationneeded', function(e) { + var pc = e.target; + if (pc.signalingState !== 'stable') { + return; + } + return e; + }); + }, + + shimGetDisplayMedia: function(window, getSourceId) { + if (!window.navigator || !window.navigator.mediaDevices || + 'getDisplayMedia' in window.navigator.mediaDevices) { + return; + } + // getSourceId is a function that returns a promise resolving with + // the sourceId of the screen/window/tab to be shared. + if (typeof getSourceId !== 'function') { + console.error('shimGetDisplayMedia: getSourceId argument is not ' + + 'a function'); + return; + } + window.navigator.mediaDevices.getDisplayMedia = function(constraints) { + return getSourceId(constraints) + .then(function(sourceId) { + var widthSpecified = constraints.video && constraints.video.width; + var heightSpecified = constraints.video && constraints.video.height; + var frameRateSpecified = constraints.video && + constraints.video.frameRate; + constraints.video = { + mandatory: { + chromeMediaSource: 'desktop', + chromeMediaSourceId: sourceId, + maxFrameRate: frameRateSpecified || 3 + } + }; + if (widthSpecified) { + constraints.video.mandatory.maxWidth = widthSpecified; + } + if (heightSpecified) { + constraints.video.mandatory.maxHeight = heightSpecified; + } + return window.navigator.mediaDevices.getUserMedia(constraints); + }); + }; + window.navigator.getDisplayMedia = function(constraints) { + utils.deprecated('navigator.getDisplayMedia', + 'navigator.mediaDevices.getDisplayMedia'); + return window.navigator.mediaDevices.getDisplayMedia(constraints); + }; + } +}; + +},{"../utils.js":14,"./getusermedia":6}],6:[function(require,module,exports){ +/* + * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. + */ + /* eslint-env node */ +'use strict'; +var utils = require('../utils.js'); +var logging = utils.log; + +// Expose public methods. +module.exports = function(window) { + var browserDetails = utils.detectBrowser(window); + var navigator = window && window.navigator; + + var constraintsToChrome_ = function(c) { + if (typeof c !== 'object' || c.mandatory || c.optional) { + return c; + } + var cc = {}; + Object.keys(c).forEach(function(key) { + if (key === 'require' || key === 'advanced' || key === 'mediaSource') { + return; + } + var r = (typeof c[key] === 'object') ? c[key] : {ideal: c[key]}; + if (r.exact !== undefined && typeof r.exact === 'number') { + r.min = r.max = r.exact; + } + var oldname_ = function(prefix, name) { + if (prefix) { + return prefix + name.charAt(0).toUpperCase() + name.slice(1); + } + return (name === 'deviceId') ? 'sourceId' : name; + }; + if (r.ideal !== undefined) { + cc.optional = cc.optional || []; + var oc = {}; + if (typeof r.ideal === 'number') { + oc[oldname_('min', key)] = r.ideal; + cc.optional.push(oc); + oc = {}; + oc[oldname_('max', key)] = r.ideal; + cc.optional.push(oc); + } else { + oc[oldname_('', key)] = r.ideal; + cc.optional.push(oc); + } + } + if (r.exact !== undefined && typeof r.exact !== 'number') { + cc.mandatory = cc.mandatory || {}; + cc.mandatory[oldname_('', key)] = r.exact; + } else { + ['min', 'max'].forEach(function(mix) { + if (r[mix] !== undefined) { + cc.mandatory = cc.mandatory || {}; + cc.mandatory[oldname_(mix, key)] = r[mix]; + } + }); + } + }); + if (c.advanced) { + cc.optional = (cc.optional || []).concat(c.advanced); + } + return cc; + }; + + var shimConstraints_ = function(constraints, func) { + if (browserDetails.version >= 61) { + return func(constraints); + } + constraints = JSON.parse(JSON.stringify(constraints)); + if (constraints && typeof constraints.audio === 'object') { + var remap = function(obj, a, b) { + if (a in obj && !(b in obj)) { + obj[b] = obj[a]; + delete obj[a]; + } + }; + constraints = JSON.parse(JSON.stringify(constraints)); + remap(constraints.audio, 'autoGainControl', 'googAutoGainControl'); + remap(constraints.audio, 'noiseSuppression', 'googNoiseSuppression'); + constraints.audio = constraintsToChrome_(constraints.audio); + } + if (constraints && typeof constraints.video === 'object') { + // Shim facingMode for mobile & surface pro. + var face = constraints.video.facingMode; + face = face && ((typeof face === 'object') ? face : {ideal: face}); + var getSupportedFacingModeLies = browserDetails.version < 66; + + if ((face && (face.exact === 'user' || face.exact === 'environment' || + face.ideal === 'user' || face.ideal === 'environment')) && + !(navigator.mediaDevices.getSupportedConstraints && + navigator.mediaDevices.getSupportedConstraints().facingMode && + !getSupportedFacingModeLies)) { + delete constraints.video.facingMode; + var matches; + if (face.exact === 'environment' || face.ideal === 'environment') { + matches = ['back', 'rear']; + } else if (face.exact === 'user' || face.ideal === 'user') { + matches = ['front']; + } + if (matches) { + // Look for matches in label, or use last cam for back (typical). + return navigator.mediaDevices.enumerateDevices() + .then(function(devices) { + devices = devices.filter(function(d) { + return d.kind === 'videoinput'; + }); + var dev = devices.find(function(d) { + return matches.some(function(match) { + return d.label.toLowerCase().indexOf(match) !== -1; + }); + }); + if (!dev && devices.length && matches.indexOf('back') !== -1) { + dev = devices[devices.length - 1]; // more likely the back cam + } + if (dev) { + constraints.video.deviceId = face.exact ? {exact: dev.deviceId} : + {ideal: dev.deviceId}; + } + constraints.video = constraintsToChrome_(constraints.video); + logging('chrome: ' + JSON.stringify(constraints)); + return func(constraints); + }); + } + } + constraints.video = constraintsToChrome_(constraints.video); + } + logging('chrome: ' + JSON.stringify(constraints)); + return func(constraints); + }; + + var shimError_ = function(e) { + if (browserDetails.version >= 64) { + return e; + } + return { + name: { + PermissionDeniedError: 'NotAllowedError', + PermissionDismissedError: 'NotAllowedError', + InvalidStateError: 'NotAllowedError', + DevicesNotFoundError: 'NotFoundError', + ConstraintNotSatisfiedError: 'OverconstrainedError', + TrackStartError: 'NotReadableError', + MediaDeviceFailedDueToShutdown: 'NotAllowedError', + MediaDeviceKillSwitchOn: 'NotAllowedError', + TabCaptureError: 'AbortError', + ScreenCaptureError: 'AbortError', + DeviceCaptureError: 'AbortError' + }[e.name] || e.name, + message: e.message, + constraint: e.constraint || e.constraintName, + toString: function() { + return this.name + (this.message && ': ') + this.message; + } + }; + }; + + var getUserMedia_ = function(constraints, onSuccess, onError) { + shimConstraints_(constraints, function(c) { + navigator.webkitGetUserMedia(c, onSuccess, function(e) { + if (onError) { + onError(shimError_(e)); + } + }); + }); + }; + + navigator.getUserMedia = getUserMedia_; + + // Returns the result of getUserMedia as a Promise. + var getUserMediaPromise_ = function(constraints) { + return new Promise(function(resolve, reject) { + navigator.getUserMedia(constraints, resolve, reject); + }); + }; + + if (!navigator.mediaDevices) { + navigator.mediaDevices = { + getUserMedia: getUserMediaPromise_, + enumerateDevices: function() { + return new Promise(function(resolve) { + var kinds = {audio: 'audioinput', video: 'videoinput'}; + return window.MediaStreamTrack.getSources(function(devices) { + resolve(devices.map(function(device) { + return {label: device.label, + kind: kinds[device.kind], + deviceId: device.id, + groupId: ''}; + })); + }); + }); + }, + getSupportedConstraints: function() { + return { + deviceId: true, echoCancellation: true, facingMode: true, + frameRate: true, height: true, width: true + }; + } + }; + } + + // A shim for getUserMedia method on the mediaDevices object. + // TODO(KaptenJansson) remove once implemented in Chrome stable. + if (!navigator.mediaDevices.getUserMedia) { + navigator.mediaDevices.getUserMedia = function(constraints) { + return getUserMediaPromise_(constraints); + }; + } else { + // Even though Chrome 45 has navigator.mediaDevices and a getUserMedia + // function which returns a Promise, it does not accept spec-style + // constraints. + var origGetUserMedia = navigator.mediaDevices.getUserMedia. + bind(navigator.mediaDevices); + navigator.mediaDevices.getUserMedia = function(cs) { + return shimConstraints_(cs, function(c) { + return origGetUserMedia(c).then(function(stream) { + if (c.audio && !stream.getAudioTracks().length || + c.video && !stream.getVideoTracks().length) { + stream.getTracks().forEach(function(track) { + track.stop(); + }); + throw new DOMException('', 'NotFoundError'); + } + return stream; + }, function(e) { + return Promise.reject(shimError_(e)); + }); + }); + }; + } + + // Dummy devicechange event methods. + // TODO(KaptenJansson) remove once implemented in Chrome stable. + if (typeof navigator.mediaDevices.addEventListener === 'undefined') { + navigator.mediaDevices.addEventListener = function() { + logging('Dummy mediaDevices.addEventListener called.'); + }; + } + if (typeof navigator.mediaDevices.removeEventListener === 'undefined') { + navigator.mediaDevices.removeEventListener = function() { + logging('Dummy mediaDevices.removeEventListener called.'); + }; + } +}; + +},{"../utils.js":14}],7:[function(require,module,exports){ +/* + * Copyright (c) 2017 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. + */ + /* eslint-env node */ +'use strict'; + +var SDPUtils = require('sdp'); +var utils = require('./utils'); + +module.exports = { + shimRTCIceCandidate: function(window) { + // foundation is arbitrarily chosen as an indicator for full support for + // https://w3c.github.io/webrtc-pc/#rtcicecandidate-interface + if (!window.RTCIceCandidate || (window.RTCIceCandidate && 'foundation' in + window.RTCIceCandidate.prototype)) { + return; + } + + var NativeRTCIceCandidate = window.RTCIceCandidate; + window.RTCIceCandidate = function(args) { + // Remove the a= which shouldn't be part of the candidate string. + if (typeof args === 'object' && args.candidate && + args.candidate.indexOf('a=') === 0) { + args = JSON.parse(JSON.stringify(args)); + args.candidate = args.candidate.substr(2); + } + + if (args.candidate && args.candidate.length) { + // Augment the native candidate with the parsed fields. + var nativeCandidate = new NativeRTCIceCandidate(args); + var parsedCandidate = SDPUtils.parseCandidate(args.candidate); + var augmentedCandidate = Object.assign(nativeCandidate, + parsedCandidate); + + // Add a serializer that does not serialize the extra attributes. + augmentedCandidate.toJSON = function() { + return { + candidate: augmentedCandidate.candidate, + sdpMid: augmentedCandidate.sdpMid, + sdpMLineIndex: augmentedCandidate.sdpMLineIndex, + usernameFragment: augmentedCandidate.usernameFragment, + }; + }; + return augmentedCandidate; + } + return new NativeRTCIceCandidate(args); + }; + window.RTCIceCandidate.prototype = NativeRTCIceCandidate.prototype; + + // Hook up the augmented candidate in onicecandidate and + // addEventListener('icecandidate', ...) + utils.wrapPeerConnectionEvent(window, 'icecandidate', function(e) { + if (e.candidate) { + Object.defineProperty(e, 'candidate', { + value: new window.RTCIceCandidate(e.candidate), + writable: 'false' + }); + } + return e; + }); + }, + + // shimCreateObjectURL must be called before shimSourceObject to avoid loop. + + shimCreateObjectURL: function(window) { + var URL = window && window.URL; + + if (!(typeof window === 'object' && window.HTMLMediaElement && + 'srcObject' in window.HTMLMediaElement.prototype && + URL.createObjectURL && URL.revokeObjectURL)) { + // Only shim CreateObjectURL using srcObject if srcObject exists. + return undefined; + } + + var nativeCreateObjectURL = URL.createObjectURL.bind(URL); + var nativeRevokeObjectURL = URL.revokeObjectURL.bind(URL); + var streams = new Map(), newId = 0; + + URL.createObjectURL = function(stream) { + if ('getTracks' in stream) { + var url = 'polyblob:' + (++newId); + streams.set(url, stream); + utils.deprecated('URL.createObjectURL(stream)', + 'elem.srcObject = stream'); + return url; + } + return nativeCreateObjectURL(stream); + }; + URL.revokeObjectURL = function(url) { + nativeRevokeObjectURL(url); + streams.delete(url); + }; + + var dsc = Object.getOwnPropertyDescriptor(window.HTMLMediaElement.prototype, + 'src'); + Object.defineProperty(window.HTMLMediaElement.prototype, 'src', { + get: function() { + return dsc.get.apply(this); + }, + set: function(url) { + this.srcObject = streams.get(url) || null; + return dsc.set.apply(this, [url]); + } + }); + + var nativeSetAttribute = window.HTMLMediaElement.prototype.setAttribute; + window.HTMLMediaElement.prototype.setAttribute = function() { + if (arguments.length === 2 && + ('' + arguments[0]).toLowerCase() === 'src') { + this.srcObject = streams.get(arguments[1]) || null; + } + return nativeSetAttribute.apply(this, arguments); + }; + }, + + shimMaxMessageSize: function(window) { + if (window.RTCSctpTransport || !window.RTCPeerConnection) { + return; + } + var browserDetails = utils.detectBrowser(window); + + if (!('sctp' in window.RTCPeerConnection.prototype)) { + Object.defineProperty(window.RTCPeerConnection.prototype, 'sctp', { + get: function() { + return typeof this._sctp === 'undefined' ? null : this._sctp; + } + }); + } + + var sctpInDescription = function(description) { + var sections = SDPUtils.splitSections(description.sdp); + sections.shift(); + return sections.some(function(mediaSection) { + var mLine = SDPUtils.parseMLine(mediaSection); + return mLine && mLine.kind === 'application' + && mLine.protocol.indexOf('SCTP') !== -1; + }); + }; + + var getRemoteFirefoxVersion = function(description) { + // TODO: Is there a better solution for detecting Firefox? + var match = description.sdp.match(/mozilla...THIS_IS_SDPARTA-(\d+)/); + if (match === null || match.length < 2) { + return -1; + } + var version = parseInt(match[1], 10); + // Test for NaN (yes, this is ugly) + return version !== version ? -1 : version; + }; + + var getCanSendMaxMessageSize = function(remoteIsFirefox) { + // Every implementation we know can send at least 64 KiB. + // Note: Although Chrome is technically able to send up to 256 KiB, the + // data does not reach the other peer reliably. + // See: https://bugs.chromium.org/p/webrtc/issues/detail?id=8419 + var canSendMaxMessageSize = 65536; + if (browserDetails.browser === 'firefox') { + if (browserDetails.version < 57) { + if (remoteIsFirefox === -1) { + // FF < 57 will send in 16 KiB chunks using the deprecated PPID + // fragmentation. + canSendMaxMessageSize = 16384; + } else { + // However, other FF (and RAWRTC) can reassemble PPID-fragmented + // messages. Thus, supporting ~2 GiB when sending. + canSendMaxMessageSize = 2147483637; + } + } else if (browserDetails.version < 60) { + // Currently, all FF >= 57 will reset the remote maximum message size + // to the default value when a data channel is created at a later + // stage. :( + // See: https://bugzilla.mozilla.org/show_bug.cgi?id=1426831 + canSendMaxMessageSize = + browserDetails.version === 57 ? 65535 : 65536; + } else { + // FF >= 60 supports sending ~2 GiB + canSendMaxMessageSize = 2147483637; + } + } + return canSendMaxMessageSize; + }; + + var getMaxMessageSize = function(description, remoteIsFirefox) { + // Note: 65536 bytes is the default value from the SDP spec. Also, + // every implementation we know supports receiving 65536 bytes. + var maxMessageSize = 65536; + + // FF 57 has a slightly incorrect default remote max message size, so + // we need to adjust it here to avoid a failure when sending. + // See: https://bugzilla.mozilla.org/show_bug.cgi?id=1425697 + if (browserDetails.browser === 'firefox' + && browserDetails.version === 57) { + maxMessageSize = 65535; + } + + var match = SDPUtils.matchPrefix(description.sdp, 'a=max-message-size:'); + if (match.length > 0) { + maxMessageSize = parseInt(match[0].substr(19), 10); + } else if (browserDetails.browser === 'firefox' && + remoteIsFirefox !== -1) { + // If the maximum message size is not present in the remote SDP and + // both local and remote are Firefox, the remote peer can receive + // ~2 GiB. + maxMessageSize = 2147483637; + } + return maxMessageSize; + }; + + var origSetRemoteDescription = + window.RTCPeerConnection.prototype.setRemoteDescription; + window.RTCPeerConnection.prototype.setRemoteDescription = function() { + var pc = this; + pc._sctp = null; + + if (sctpInDescription(arguments[0])) { + // Check if the remote is FF. + var isFirefox = getRemoteFirefoxVersion(arguments[0]); + + // Get the maximum message size the local peer is capable of sending + var canSendMMS = getCanSendMaxMessageSize(isFirefox); + + // Get the maximum message size of the remote peer. + var remoteMMS = getMaxMessageSize(arguments[0], isFirefox); + + // Determine final maximum message size + var maxMessageSize; + if (canSendMMS === 0 && remoteMMS === 0) { + maxMessageSize = Number.POSITIVE_INFINITY; + } else if (canSendMMS === 0 || remoteMMS === 0) { + maxMessageSize = Math.max(canSendMMS, remoteMMS); + } else { + maxMessageSize = Math.min(canSendMMS, remoteMMS); + } + + // Create a dummy RTCSctpTransport object and the 'maxMessageSize' + // attribute. + var sctp = {}; + Object.defineProperty(sctp, 'maxMessageSize', { + get: function() { + return maxMessageSize; + } + }); + pc._sctp = sctp; + } + + return origSetRemoteDescription.apply(pc, arguments); + }; + }, + + shimSendThrowTypeError: function(window) { + if (!(window.RTCPeerConnection && + 'createDataChannel' in window.RTCPeerConnection.prototype)) { + return; + } + + // Note: Although Firefox >= 57 has a native implementation, the maximum + // message size can be reset for all data channels at a later stage. + // See: https://bugzilla.mozilla.org/show_bug.cgi?id=1426831 + + function wrapDcSend(dc, pc) { + var origDataChannelSend = dc.send; + dc.send = function() { + var data = arguments[0]; + var length = data.length || data.size || data.byteLength; + if (dc.readyState === 'open' && + pc.sctp && length > pc.sctp.maxMessageSize) { + throw new TypeError('Message too large (can send a maximum of ' + + pc.sctp.maxMessageSize + ' bytes)'); + } + return origDataChannelSend.apply(dc, arguments); + }; + } + var origCreateDataChannel = + window.RTCPeerConnection.prototype.createDataChannel; + window.RTCPeerConnection.prototype.createDataChannel = function() { + var pc = this; + var dataChannel = origCreateDataChannel.apply(pc, arguments); + wrapDcSend(dataChannel, pc); + return dataChannel; + }; + utils.wrapPeerConnectionEvent(window, 'datachannel', function(e) { + wrapDcSend(e.channel, e.target); + return e; + }); + } +}; + +},{"./utils":14,"sdp":2}],8:[function(require,module,exports){ +/* + * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. + */ + /* eslint-env node */ +'use strict'; + +var utils = require('../utils'); +var filterIceServers = require('./filtericeservers'); +var shimRTCPeerConnection = require('rtcpeerconnection-shim'); + +module.exports = { + shimGetUserMedia: require('./getusermedia'), + shimPeerConnection: function(window) { + var browserDetails = utils.detectBrowser(window); + + if (window.RTCIceGatherer) { + if (!window.RTCIceCandidate) { + window.RTCIceCandidate = function(args) { + return args; + }; + } + if (!window.RTCSessionDescription) { + window.RTCSessionDescription = function(args) { + return args; + }; + } + // this adds an additional event listener to MediaStrackTrack that signals + // when a tracks enabled property was changed. Workaround for a bug in + // addStream, see below. No longer required in 15025+ + if (browserDetails.version < 15025) { + var origMSTEnabled = Object.getOwnPropertyDescriptor( + window.MediaStreamTrack.prototype, 'enabled'); + Object.defineProperty(window.MediaStreamTrack.prototype, 'enabled', { + set: function(value) { + origMSTEnabled.set.call(this, value); + var ev = new Event('enabled'); + ev.enabled = value; + this.dispatchEvent(ev); + } + }); + } + } + + // ORTC defines the DTMF sender a bit different. + // https://github.com/w3c/ortc/issues/714 + if (window.RTCRtpSender && !('dtmf' in window.RTCRtpSender.prototype)) { + Object.defineProperty(window.RTCRtpSender.prototype, 'dtmf', { + get: function() { + if (this._dtmf === undefined) { + if (this.track.kind === 'audio') { + this._dtmf = new window.RTCDtmfSender(this); + } else if (this.track.kind === 'video') { + this._dtmf = null; + } + } + return this._dtmf; + } + }); + } + // Edge currently only implements the RTCDtmfSender, not the + // RTCDTMFSender alias. See http://draft.ortc.org/#rtcdtmfsender2* + if (window.RTCDtmfSender && !window.RTCDTMFSender) { + window.RTCDTMFSender = window.RTCDtmfSender; + } + + var RTCPeerConnectionShim = shimRTCPeerConnection(window, + browserDetails.version); + window.RTCPeerConnection = function(config) { + if (config && config.iceServers) { + config.iceServers = filterIceServers(config.iceServers); + } + return new RTCPeerConnectionShim(config); + }; + window.RTCPeerConnection.prototype = RTCPeerConnectionShim.prototype; + }, + shimReplaceTrack: function(window) { + // ORTC has replaceTrack -- https://github.com/w3c/ortc/issues/614 + if (window.RTCRtpSender && + !('replaceTrack' in window.RTCRtpSender.prototype)) { + window.RTCRtpSender.prototype.replaceTrack = + window.RTCRtpSender.prototype.setTrack; + } + }, + shimGetDisplayMedia: function(window, preferredMediaSource) { + if (!('getDisplayMedia' in window.navigator) || + !window.navigator.mediaDevices || + 'getDisplayMedia' in window.navigator.mediaDevices) { + return; + } + var origGetDisplayMedia = window.navigator.getDisplayMedia; + window.navigator.mediaDevices.getDisplayMedia = function(constraints) { + return origGetDisplayMedia(constraints); + }; + window.navigator.getDisplayMedia = function(constraints) { + utils.deprecated('navigator.getDisplayMedia', + 'navigator.mediaDevices.getDisplayMedia'); + return origGetDisplayMedia(constraints); + }; + } +}; + +},{"../utils":14,"./filtericeservers":9,"./getusermedia":10,"rtcpeerconnection-shim":1}],9:[function(require,module,exports){ +/* + * Copyright (c) 2018 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. + */ + /* eslint-env node */ +'use strict'; + +var utils = require('../utils'); +// Edge does not like +// 1) stun: filtered after 14393 unless ?transport=udp is present +// 2) turn: that does not have all of turn:host:port?transport=udp +// 3) turn: with ipv6 addresses +// 4) turn: occurring muliple times +module.exports = function(iceServers, edgeVersion) { + var hasTurn = false; + iceServers = JSON.parse(JSON.stringify(iceServers)); + return iceServers.filter(function(server) { + if (server && (server.urls || server.url)) { + var urls = server.urls || server.url; + if (server.url && !server.urls) { + utils.deprecated('RTCIceServer.url', 'RTCIceServer.urls'); + } + var isString = typeof urls === 'string'; + if (isString) { + urls = [urls]; + } + urls = urls.filter(function(url) { + var validTurn = url.indexOf('turn:') === 0 && + url.indexOf('transport=udp') !== -1 && + url.indexOf('turn:[') === -1 && + !hasTurn; + + if (validTurn) { + hasTurn = true; + return true; + } + return url.indexOf('stun:') === 0 && edgeVersion >= 14393 && + url.indexOf('?transport=udp') === -1; + }); + + delete server.url; + server.urls = isString ? urls[0] : urls; + return !!urls.length; + } + }); +}; + +},{"../utils":14}],10:[function(require,module,exports){ +/* + * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. + */ + /* eslint-env node */ +'use strict'; + +// Expose public methods. +module.exports = function(window) { + var navigator = window && window.navigator; + + var shimError_ = function(e) { + return { + name: {PermissionDeniedError: 'NotAllowedError'}[e.name] || e.name, + message: e.message, + constraint: e.constraint, + toString: function() { + return this.name; + } + }; + }; + + // getUserMedia error shim. + var origGetUserMedia = navigator.mediaDevices.getUserMedia. + bind(navigator.mediaDevices); + navigator.mediaDevices.getUserMedia = function(c) { + return origGetUserMedia(c).catch(function(e) { + return Promise.reject(shimError_(e)); + }); + }; +}; + +},{}],11:[function(require,module,exports){ +/* + * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. + */ + /* eslint-env node */ +'use strict'; + +var utils = require('../utils'); + +module.exports = { + shimGetUserMedia: require('./getusermedia'), + shimOnTrack: function(window) { + if (typeof window === 'object' && window.RTCPeerConnection && !('ontrack' in + window.RTCPeerConnection.prototype)) { + Object.defineProperty(window.RTCPeerConnection.prototype, 'ontrack', { + get: function() { + return this._ontrack; + }, + set: function(f) { + if (this._ontrack) { + this.removeEventListener('track', this._ontrack); + this.removeEventListener('addstream', this._ontrackpoly); + } + this.addEventListener('track', this._ontrack = f); + this.addEventListener('addstream', this._ontrackpoly = function(e) { + e.stream.getTracks().forEach(function(track) { + var event = new Event('track'); + event.track = track; + event.receiver = {track: track}; + event.transceiver = {receiver: event.receiver}; + event.streams = [e.stream]; + this.dispatchEvent(event); + }.bind(this)); + }.bind(this)); + }, + enumerable: true, + configurable: true + }); + } + if (typeof window === 'object' && window.RTCTrackEvent && + ('receiver' in window.RTCTrackEvent.prototype) && + !('transceiver' in window.RTCTrackEvent.prototype)) { + Object.defineProperty(window.RTCTrackEvent.prototype, 'transceiver', { + get: function() { + return {receiver: this.receiver}; + } + }); + } + }, + + shimSourceObject: function(window) { + // Firefox has supported mozSrcObject since FF22, unprefixed in 42. + if (typeof window === 'object') { + if (window.HTMLMediaElement && + !('srcObject' in window.HTMLMediaElement.prototype)) { + // Shim the srcObject property, once, when HTMLMediaElement is found. + Object.defineProperty(window.HTMLMediaElement.prototype, 'srcObject', { + get: function() { + return this.mozSrcObject; + }, + set: function(stream) { + this.mozSrcObject = stream; + } + }); + } + } + }, + + shimPeerConnection: function(window) { + var browserDetails = utils.detectBrowser(window); + + if (typeof window !== 'object' || !(window.RTCPeerConnection || + window.mozRTCPeerConnection)) { + return; // probably media.peerconnection.enabled=false in about:config + } + // The RTCPeerConnection object. + if (!window.RTCPeerConnection) { + window.RTCPeerConnection = function(pcConfig, pcConstraints) { + if (browserDetails.version < 38) { + // .urls is not supported in FF < 38. + // create RTCIceServers with a single url. + if (pcConfig && pcConfig.iceServers) { + var newIceServers = []; + for (var i = 0; i < pcConfig.iceServers.length; i++) { + var server = pcConfig.iceServers[i]; + if (server.hasOwnProperty('urls')) { + for (var j = 0; j < server.urls.length; j++) { + var newServer = { + url: server.urls[j] + }; + if (server.urls[j].indexOf('turn') === 0) { + newServer.username = server.username; + newServer.credential = server.credential; + } + newIceServers.push(newServer); + } + } else { + newIceServers.push(pcConfig.iceServers[i]); + } + } + pcConfig.iceServers = newIceServers; + } + } + return new window.mozRTCPeerConnection(pcConfig, pcConstraints); + }; + window.RTCPeerConnection.prototype = + window.mozRTCPeerConnection.prototype; + + // wrap static methods. Currently just generateCertificate. + if (window.mozRTCPeerConnection.generateCertificate) { + Object.defineProperty(window.RTCPeerConnection, 'generateCertificate', { + get: function() { + return window.mozRTCPeerConnection.generateCertificate; + } + }); + } + + window.RTCSessionDescription = window.mozRTCSessionDescription; + window.RTCIceCandidate = window.mozRTCIceCandidate; + } + + // shim away need for obsolete RTCIceCandidate/RTCSessionDescription. + ['setLocalDescription', 'setRemoteDescription', 'addIceCandidate'] + .forEach(function(method) { + var nativeMethod = window.RTCPeerConnection.prototype[method]; + window.RTCPeerConnection.prototype[method] = function() { + arguments[0] = new ((method === 'addIceCandidate') ? + window.RTCIceCandidate : + window.RTCSessionDescription)(arguments[0]); + return nativeMethod.apply(this, arguments); + }; + }); + + // support for addIceCandidate(null or undefined) + var nativeAddIceCandidate = + window.RTCPeerConnection.prototype.addIceCandidate; + window.RTCPeerConnection.prototype.addIceCandidate = function() { + if (!arguments[0]) { + if (arguments[1]) { + arguments[1].apply(null); + } + return Promise.resolve(); + } + return nativeAddIceCandidate.apply(this, arguments); + }; + + // shim getStats with maplike support + var makeMapStats = function(stats) { + var map = new Map(); + Object.keys(stats).forEach(function(key) { + map.set(key, stats[key]); + map[key] = stats[key]; + }); + return map; + }; + + var modernStatsTypes = { + inboundrtp: 'inbound-rtp', + outboundrtp: 'outbound-rtp', + candidatepair: 'candidate-pair', + localcandidate: 'local-candidate', + remotecandidate: 'remote-candidate' + }; + + var nativeGetStats = window.RTCPeerConnection.prototype.getStats; + window.RTCPeerConnection.prototype.getStats = function( + selector, + onSucc, + onErr + ) { + return nativeGetStats.apply(this, [selector || null]) + .then(function(stats) { + if (browserDetails.version < 48) { + stats = makeMapStats(stats); + } + if (browserDetails.version < 53 && !onSucc) { + // Shim only promise getStats with spec-hyphens in type names + // Leave callback version alone; misc old uses of forEach before Map + try { + stats.forEach(function(stat) { + stat.type = modernStatsTypes[stat.type] || stat.type; + }); + } catch (e) { + if (e.name !== 'TypeError') { + throw e; + } + // Avoid TypeError: "type" is read-only, in old versions. 34-43ish + stats.forEach(function(stat, i) { + stats.set(i, Object.assign({}, stat, { + type: modernStatsTypes[stat.type] || stat.type + })); + }); + } + } + return stats; + }) + .then(onSucc, onErr); + }; + }, + + shimSenderGetStats: function(window) { + if (!(typeof window === 'object' && window.RTCPeerConnection && + window.RTCRtpSender)) { + return; + } + if (window.RTCRtpSender && 'getStats' in window.RTCRtpSender.prototype) { + return; + } + var origGetSenders = window.RTCPeerConnection.prototype.getSenders; + if (origGetSenders) { + window.RTCPeerConnection.prototype.getSenders = function() { + var pc = this; + var senders = origGetSenders.apply(pc, []); + senders.forEach(function(sender) { + sender._pc = pc; + }); + return senders; + }; + } + + var origAddTrack = window.RTCPeerConnection.prototype.addTrack; + if (origAddTrack) { + window.RTCPeerConnection.prototype.addTrack = function() { + var sender = origAddTrack.apply(this, arguments); + sender._pc = this; + return sender; + }; + } + window.RTCRtpSender.prototype.getStats = function() { + return this.track ? this._pc.getStats(this.track) : + Promise.resolve(new Map()); + }; + }, + + shimReceiverGetStats: function(window) { + if (!(typeof window === 'object' && window.RTCPeerConnection && + window.RTCRtpSender)) { + return; + } + if (window.RTCRtpSender && 'getStats' in window.RTCRtpReceiver.prototype) { + return; + } + var origGetReceivers = window.RTCPeerConnection.prototype.getReceivers; + if (origGetReceivers) { + window.RTCPeerConnection.prototype.getReceivers = function() { + var pc = this; + var receivers = origGetReceivers.apply(pc, []); + receivers.forEach(function(receiver) { + receiver._pc = pc; + }); + return receivers; + }; + } + utils.wrapPeerConnectionEvent(window, 'track', function(e) { + e.receiver._pc = e.srcElement; + return e; + }); + window.RTCRtpReceiver.prototype.getStats = function() { + return this._pc.getStats(this.track); + }; + }, + + shimRemoveStream: function(window) { + if (!window.RTCPeerConnection || + 'removeStream' in window.RTCPeerConnection.prototype) { + return; + } + window.RTCPeerConnection.prototype.removeStream = function(stream) { + var pc = this; + utils.deprecated('removeStream', 'removeTrack'); + this.getSenders().forEach(function(sender) { + if (sender.track && stream.getTracks().indexOf(sender.track) !== -1) { + pc.removeTrack(sender); + } + }); + }; + }, + + shimRTCDataChannel: function(window) { + // rename DataChannel to RTCDataChannel (native fix in FF60): + // https://bugzilla.mozilla.org/show_bug.cgi?id=1173851 + if (window.DataChannel && !window.RTCDataChannel) { + window.RTCDataChannel = window.DataChannel; + } + }, + + shimGetDisplayMedia: function(window, preferredMediaSource) { + if (!window.navigator || !window.navigator.mediaDevices || + 'getDisplayMedia' in window.navigator.mediaDevices) { + return; + } + window.navigator.mediaDevices.getDisplayMedia = function(constraints) { + if (!(constraints && constraints.video)) { + var err = new DOMException('getDisplayMedia without video ' + + 'constraints is undefined'); + err.name = 'NotFoundError'; + // from https://heycam.github.io/webidl/#idl-DOMException-error-names + err.code = 8; + return Promise.reject(err); + } + if (constraints.video === true) { + constraints.video = {mediaSource: preferredMediaSource}; + } else { + constraints.video.mediaSource = preferredMediaSource; + } + return window.navigator.mediaDevices.getUserMedia(constraints); + }; + window.navigator.getDisplayMedia = function(constraints) { + utils.deprecated('navigator.getDisplayMedia', + 'navigator.mediaDevices.getDisplayMedia'); + return window.navigator.mediaDevices.getDisplayMedia(constraints); + }; + } +}; + +},{"../utils":14,"./getusermedia":12}],12:[function(require,module,exports){ +/* + * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. + */ + /* eslint-env node */ +'use strict'; + +var utils = require('../utils'); +var logging = utils.log; + +// Expose public methods. +module.exports = function(window) { + var browserDetails = utils.detectBrowser(window); + var navigator = window && window.navigator; + var MediaStreamTrack = window && window.MediaStreamTrack; + + var shimError_ = function(e) { + return { + name: { + InternalError: 'NotReadableError', + NotSupportedError: 'TypeError', + PermissionDeniedError: 'NotAllowedError', + SecurityError: 'NotAllowedError' + }[e.name] || e.name, + message: { + 'The operation is insecure.': 'The request is not allowed by the ' + + 'user agent or the platform in the current context.' + }[e.message] || e.message, + constraint: e.constraint, + toString: function() { + return this.name + (this.message && ': ') + this.message; + } + }; + }; + + // getUserMedia constraints shim. + var getUserMedia_ = function(constraints, onSuccess, onError) { + var constraintsToFF37_ = function(c) { + if (typeof c !== 'object' || c.require) { + return c; + } + var require = []; + Object.keys(c).forEach(function(key) { + if (key === 'require' || key === 'advanced' || key === 'mediaSource') { + return; + } + var r = c[key] = (typeof c[key] === 'object') ? + c[key] : {ideal: c[key]}; + if (r.min !== undefined || + r.max !== undefined || r.exact !== undefined) { + require.push(key); + } + if (r.exact !== undefined) { + if (typeof r.exact === 'number') { + r. min = r.max = r.exact; + } else { + c[key] = r.exact; + } + delete r.exact; + } + if (r.ideal !== undefined) { + c.advanced = c.advanced || []; + var oc = {}; + if (typeof r.ideal === 'number') { + oc[key] = {min: r.ideal, max: r.ideal}; + } else { + oc[key] = r.ideal; + } + c.advanced.push(oc); + delete r.ideal; + if (!Object.keys(r).length) { + delete c[key]; + } + } + }); + if (require.length) { + c.require = require; + } + return c; + }; + constraints = JSON.parse(JSON.stringify(constraints)); + if (browserDetails.version < 38) { + logging('spec: ' + JSON.stringify(constraints)); + if (constraints.audio) { + constraints.audio = constraintsToFF37_(constraints.audio); + } + if (constraints.video) { + constraints.video = constraintsToFF37_(constraints.video); + } + logging('ff37: ' + JSON.stringify(constraints)); + } + return navigator.mozGetUserMedia(constraints, onSuccess, function(e) { + onError(shimError_(e)); + }); + }; + + // Returns the result of getUserMedia as a Promise. + var getUserMediaPromise_ = function(constraints) { + return new Promise(function(resolve, reject) { + getUserMedia_(constraints, resolve, reject); + }); + }; + + // Shim for mediaDevices on older versions. + if (!navigator.mediaDevices) { + navigator.mediaDevices = {getUserMedia: getUserMediaPromise_, + addEventListener: function() { }, + removeEventListener: function() { } + }; + } + navigator.mediaDevices.enumerateDevices = + navigator.mediaDevices.enumerateDevices || function() { + return new Promise(function(resolve) { + var infos = [ + {kind: 'audioinput', deviceId: 'default', label: '', groupId: ''}, + {kind: 'videoinput', deviceId: 'default', label: '', groupId: ''} + ]; + resolve(infos); + }); + }; + + if (browserDetails.version < 41) { + // Work around http://bugzil.la/1169665 + var orgEnumerateDevices = + navigator.mediaDevices.enumerateDevices.bind(navigator.mediaDevices); + navigator.mediaDevices.enumerateDevices = function() { + return orgEnumerateDevices().then(undefined, function(e) { + if (e.name === 'NotFoundError') { + return []; + } + throw e; + }); + }; + } + if (browserDetails.version < 49) { + var origGetUserMedia = navigator.mediaDevices.getUserMedia. + bind(navigator.mediaDevices); + navigator.mediaDevices.getUserMedia = function(c) { + return origGetUserMedia(c).then(function(stream) { + // Work around https://bugzil.la/802326 + if (c.audio && !stream.getAudioTracks().length || + c.video && !stream.getVideoTracks().length) { + stream.getTracks().forEach(function(track) { + track.stop(); + }); + throw new DOMException('The object can not be found here.', + 'NotFoundError'); + } + return stream; + }, function(e) { + return Promise.reject(shimError_(e)); + }); + }; + } + if (!(browserDetails.version > 55 && + 'autoGainControl' in navigator.mediaDevices.getSupportedConstraints())) { + var remap = function(obj, a, b) { + if (a in obj && !(b in obj)) { + obj[b] = obj[a]; + delete obj[a]; + } + }; + + var nativeGetUserMedia = navigator.mediaDevices.getUserMedia. + bind(navigator.mediaDevices); + navigator.mediaDevices.getUserMedia = function(c) { + if (typeof c === 'object' && typeof c.audio === 'object') { + c = JSON.parse(JSON.stringify(c)); + remap(c.audio, 'autoGainControl', 'mozAutoGainControl'); + remap(c.audio, 'noiseSuppression', 'mozNoiseSuppression'); + } + return nativeGetUserMedia(c); + }; + + if (MediaStreamTrack && MediaStreamTrack.prototype.getSettings) { + var nativeGetSettings = MediaStreamTrack.prototype.getSettings; + MediaStreamTrack.prototype.getSettings = function() { + var obj = nativeGetSettings.apply(this, arguments); + remap(obj, 'mozAutoGainControl', 'autoGainControl'); + remap(obj, 'mozNoiseSuppression', 'noiseSuppression'); + return obj; + }; + } + + if (MediaStreamTrack && MediaStreamTrack.prototype.applyConstraints) { + var nativeApplyConstraints = MediaStreamTrack.prototype.applyConstraints; + MediaStreamTrack.prototype.applyConstraints = function(c) { + if (this.kind === 'audio' && typeof c === 'object') { + c = JSON.parse(JSON.stringify(c)); + remap(c, 'autoGainControl', 'mozAutoGainControl'); + remap(c, 'noiseSuppression', 'mozNoiseSuppression'); + } + return nativeApplyConstraints.apply(this, [c]); + }; + } + } + navigator.getUserMedia = function(constraints, onSuccess, onError) { + if (browserDetails.version < 44) { + return getUserMedia_(constraints, onSuccess, onError); + } + // Replace Firefox 44+'s deprecation warning with unprefixed version. + utils.deprecated('navigator.getUserMedia', + 'navigator.mediaDevices.getUserMedia'); + navigator.mediaDevices.getUserMedia(constraints).then(onSuccess, onError); + }; +}; + +},{"../utils":14}],13:[function(require,module,exports){ +/* + * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. + */ +'use strict'; +var utils = require('../utils'); + +module.exports = { + shimLocalStreamsAPI: function(window) { + if (typeof window !== 'object' || !window.RTCPeerConnection) { + return; + } + if (!('getLocalStreams' in window.RTCPeerConnection.prototype)) { + window.RTCPeerConnection.prototype.getLocalStreams = function() { + if (!this._localStreams) { + this._localStreams = []; + } + return this._localStreams; + }; + } + if (!('getStreamById' in window.RTCPeerConnection.prototype)) { + window.RTCPeerConnection.prototype.getStreamById = function(id) { + var result = null; + if (this._localStreams) { + this._localStreams.forEach(function(stream) { + if (stream.id === id) { + result = stream; + } + }); + } + if (this._remoteStreams) { + this._remoteStreams.forEach(function(stream) { + if (stream.id === id) { + result = stream; + } + }); + } + return result; + }; + } + if (!('addStream' in window.RTCPeerConnection.prototype)) { + var _addTrack = window.RTCPeerConnection.prototype.addTrack; + window.RTCPeerConnection.prototype.addStream = function(stream) { + if (!this._localStreams) { + this._localStreams = []; + } + if (this._localStreams.indexOf(stream) === -1) { + this._localStreams.push(stream); + } + var pc = this; + stream.getTracks().forEach(function(track) { + _addTrack.call(pc, track, stream); + }); + }; + + window.RTCPeerConnection.prototype.addTrack = function(track, stream) { + if (stream) { + if (!this._localStreams) { + this._localStreams = [stream]; + } else if (this._localStreams.indexOf(stream) === -1) { + this._localStreams.push(stream); + } + } + return _addTrack.call(this, track, stream); + }; + } + if (!('removeStream' in window.RTCPeerConnection.prototype)) { + window.RTCPeerConnection.prototype.removeStream = function(stream) { + if (!this._localStreams) { + this._localStreams = []; + } + var index = this._localStreams.indexOf(stream); + if (index === -1) { + return; + } + this._localStreams.splice(index, 1); + var pc = this; + var tracks = stream.getTracks(); + this.getSenders().forEach(function(sender) { + if (tracks.indexOf(sender.track) !== -1) { + pc.removeTrack(sender); + } + }); + }; + } + }, + shimRemoteStreamsAPI: function(window) { + if (typeof window !== 'object' || !window.RTCPeerConnection) { + return; + } + if (!('getRemoteStreams' in window.RTCPeerConnection.prototype)) { + window.RTCPeerConnection.prototype.getRemoteStreams = function() { + return this._remoteStreams ? this._remoteStreams : []; + }; + } + if (!('onaddstream' in window.RTCPeerConnection.prototype)) { + Object.defineProperty(window.RTCPeerConnection.prototype, 'onaddstream', { + get: function() { + return this._onaddstream; + }, + set: function(f) { + if (this._onaddstream) { + this.removeEventListener('addstream', this._onaddstream); + } + this.addEventListener('addstream', this._onaddstream = f); + } + }); + var origSetRemoteDescription = + window.RTCPeerConnection.prototype.setRemoteDescription; + window.RTCPeerConnection.prototype.setRemoteDescription = function() { + var pc = this; + if (!this._onaddstreampoly) { + this.addEventListener('track', this._onaddstreampoly = function(e) { + e.streams.forEach(function(stream) { + if (!pc._remoteStreams) { + pc._remoteStreams = []; + } + if (pc._remoteStreams.indexOf(stream) >= 0) { + return; + } + pc._remoteStreams.push(stream); + var event = new Event('addstream'); + event.stream = stream; + pc.dispatchEvent(event); + }); + }); + } + return origSetRemoteDescription.apply(pc, arguments); + }; + } + }, + shimCallbacksAPI: function(window) { + if (typeof window !== 'object' || !window.RTCPeerConnection) { + return; + } + var prototype = window.RTCPeerConnection.prototype; + var createOffer = prototype.createOffer; + var createAnswer = prototype.createAnswer; + var setLocalDescription = prototype.setLocalDescription; + var setRemoteDescription = prototype.setRemoteDescription; + var addIceCandidate = prototype.addIceCandidate; + + prototype.createOffer = function(successCallback, failureCallback) { + var options = (arguments.length >= 2) ? arguments[2] : arguments[0]; + var promise = createOffer.apply(this, [options]); + if (!failureCallback) { + return promise; + } + promise.then(successCallback, failureCallback); + return Promise.resolve(); + }; + + prototype.createAnswer = function(successCallback, failureCallback) { + var options = (arguments.length >= 2) ? arguments[2] : arguments[0]; + var promise = createAnswer.apply(this, [options]); + if (!failureCallback) { + return promise; + } + promise.then(successCallback, failureCallback); + return Promise.resolve(); + }; + + var withCallback = function(description, successCallback, failureCallback) { + var promise = setLocalDescription.apply(this, [description]); + if (!failureCallback) { + return promise; + } + promise.then(successCallback, failureCallback); + return Promise.resolve(); + }; + prototype.setLocalDescription = withCallback; + + withCallback = function(description, successCallback, failureCallback) { + var promise = setRemoteDescription.apply(this, [description]); + if (!failureCallback) { + return promise; + } + promise.then(successCallback, failureCallback); + return Promise.resolve(); + }; + prototype.setRemoteDescription = withCallback; + + withCallback = function(candidate, successCallback, failureCallback) { + var promise = addIceCandidate.apply(this, [candidate]); + if (!failureCallback) { + return promise; + } + promise.then(successCallback, failureCallback); + return Promise.resolve(); + }; + prototype.addIceCandidate = withCallback; + }, + shimGetUserMedia: function(window) { + var navigator = window && window.navigator; + + if (!navigator.getUserMedia) { + if (navigator.webkitGetUserMedia) { + navigator.getUserMedia = navigator.webkitGetUserMedia.bind(navigator); + } else if (navigator.mediaDevices && + navigator.mediaDevices.getUserMedia) { + navigator.getUserMedia = function(constraints, cb, errcb) { + navigator.mediaDevices.getUserMedia(constraints) + .then(cb, errcb); + }.bind(navigator); + } + } + }, + shimRTCIceServerUrls: function(window) { + // migrate from non-spec RTCIceServer.url to RTCIceServer.urls + var OrigPeerConnection = window.RTCPeerConnection; + window.RTCPeerConnection = function(pcConfig, pcConstraints) { + if (pcConfig && pcConfig.iceServers) { + var newIceServers = []; + for (var i = 0; i < pcConfig.iceServers.length; i++) { + var server = pcConfig.iceServers[i]; + if (!server.hasOwnProperty('urls') && + server.hasOwnProperty('url')) { + utils.deprecated('RTCIceServer.url', 'RTCIceServer.urls'); + server = JSON.parse(JSON.stringify(server)); + server.urls = server.url; + delete server.url; + newIceServers.push(server); + } else { + newIceServers.push(pcConfig.iceServers[i]); + } + } + pcConfig.iceServers = newIceServers; + } + return new OrigPeerConnection(pcConfig, pcConstraints); + }; + window.RTCPeerConnection.prototype = OrigPeerConnection.prototype; + // wrap static methods. Currently just generateCertificate. + if ('generateCertificate' in window.RTCPeerConnection) { + Object.defineProperty(window.RTCPeerConnection, 'generateCertificate', { + get: function() { + return OrigPeerConnection.generateCertificate; + } + }); + } + }, + shimTrackEventTransceiver: function(window) { + // Add event.transceiver member over deprecated event.receiver + if (typeof window === 'object' && window.RTCPeerConnection && + ('receiver' in window.RTCTrackEvent.prototype) && + // can't check 'transceiver' in window.RTCTrackEvent.prototype, as it is + // defined for some reason even when window.RTCTransceiver is not. + !window.RTCTransceiver) { + Object.defineProperty(window.RTCTrackEvent.prototype, 'transceiver', { + get: function() { + return {receiver: this.receiver}; + } + }); + } + }, + + shimCreateOfferLegacy: function(window) { + var origCreateOffer = window.RTCPeerConnection.prototype.createOffer; + window.RTCPeerConnection.prototype.createOffer = function(offerOptions) { + var pc = this; + if (offerOptions) { + if (typeof offerOptions.offerToReceiveAudio !== 'undefined') { + // support bit values + offerOptions.offerToReceiveAudio = !!offerOptions.offerToReceiveAudio; + } + var audioTransceiver = pc.getTransceivers().find(function(transceiver) { + return transceiver.sender.track && + transceiver.sender.track.kind === 'audio'; + }); + if (offerOptions.offerToReceiveAudio === false && audioTransceiver) { + if (audioTransceiver.direction === 'sendrecv') { + if (audioTransceiver.setDirection) { + audioTransceiver.setDirection('sendonly'); + } else { + audioTransceiver.direction = 'sendonly'; + } + } else if (audioTransceiver.direction === 'recvonly') { + if (audioTransceiver.setDirection) { + audioTransceiver.setDirection('inactive'); + } else { + audioTransceiver.direction = 'inactive'; + } + } + } else if (offerOptions.offerToReceiveAudio === true && + !audioTransceiver) { + pc.addTransceiver('audio'); + } + + + if (typeof offerOptions.offerToReceiveVideo !== 'undefined') { + // support bit values + offerOptions.offerToReceiveVideo = !!offerOptions.offerToReceiveVideo; + } + var videoTransceiver = pc.getTransceivers().find(function(transceiver) { + return transceiver.sender.track && + transceiver.sender.track.kind === 'video'; + }); + if (offerOptions.offerToReceiveVideo === false && videoTransceiver) { + if (videoTransceiver.direction === 'sendrecv') { + videoTransceiver.setDirection('sendonly'); + } else if (videoTransceiver.direction === 'recvonly') { + videoTransceiver.setDirection('inactive'); + } + } else if (offerOptions.offerToReceiveVideo === true && + !videoTransceiver) { + pc.addTransceiver('video'); + } + } + return origCreateOffer.apply(pc, arguments); + }; + } +}; + +},{"../utils":14}],14:[function(require,module,exports){ +/* + * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. + */ + /* eslint-env node */ +'use strict'; + +var logDisabled_ = true; +var deprecationWarnings_ = true; + +/** + * Extract browser version out of the provided user agent string. + * + * @param {!string} uastring userAgent string. + * @param {!string} expr Regular expression used as match criteria. + * @param {!number} pos position in the version string to be returned. + * @return {!number} browser version. + */ +function extractVersion(uastring, expr, pos) { + var match = uastring.match(expr); + return match && match.length >= pos && parseInt(match[pos], 10); +} + +// Wraps the peerconnection event eventNameToWrap in a function +// which returns the modified event object (or false to prevent +// the event). +function wrapPeerConnectionEvent(window, eventNameToWrap, wrapper) { + if (!window.RTCPeerConnection) { + return; + } + var proto = window.RTCPeerConnection.prototype; + var nativeAddEventListener = proto.addEventListener; + proto.addEventListener = function(nativeEventName, cb) { + if (nativeEventName !== eventNameToWrap) { + return nativeAddEventListener.apply(this, arguments); + } + var wrappedCallback = function(e) { + var modifiedEvent = wrapper(e); + if (modifiedEvent) { + cb(modifiedEvent); + } + }; + this._eventMap = this._eventMap || {}; + this._eventMap[cb] = wrappedCallback; + return nativeAddEventListener.apply(this, [nativeEventName, + wrappedCallback]); + }; + + var nativeRemoveEventListener = proto.removeEventListener; + proto.removeEventListener = function(nativeEventName, cb) { + if (nativeEventName !== eventNameToWrap || !this._eventMap + || !this._eventMap[cb]) { + return nativeRemoveEventListener.apply(this, arguments); + } + var unwrappedCb = this._eventMap[cb]; + delete this._eventMap[cb]; + return nativeRemoveEventListener.apply(this, [nativeEventName, + unwrappedCb]); + }; + + Object.defineProperty(proto, 'on' + eventNameToWrap, { + get: function() { + return this['_on' + eventNameToWrap]; + }, + set: function(cb) { + if (this['_on' + eventNameToWrap]) { + this.removeEventListener(eventNameToWrap, + this['_on' + eventNameToWrap]); + delete this['_on' + eventNameToWrap]; + } + if (cb) { + this.addEventListener(eventNameToWrap, + this['_on' + eventNameToWrap] = cb); + } + }, + enumerable: true, + configurable: true + }); +} + +// Utility methods. +module.exports = { + extractVersion: extractVersion, + wrapPeerConnectionEvent: wrapPeerConnectionEvent, + disableLog: function(bool) { + if (typeof bool !== 'boolean') { + return new Error('Argument type: ' + typeof bool + + '. Please use a boolean.'); + } + logDisabled_ = bool; + return (bool) ? 'adapter.js logging disabled' : + 'adapter.js logging enabled'; + }, + + /** + * Disable or enable deprecation warnings + * @param {!boolean} bool set to true to disable warnings. + */ + disableWarnings: function(bool) { + if (typeof bool !== 'boolean') { + return new Error('Argument type: ' + typeof bool + + '. Please use a boolean.'); + } + deprecationWarnings_ = !bool; + return 'adapter.js deprecation warnings ' + (bool ? 'disabled' : 'enabled'); + }, + + log: function() { + if (typeof window === 'object') { + if (logDisabled_) { + return; + } + if (typeof console !== 'undefined' && typeof console.log === 'function') { + console.log.apply(console, arguments); + } + } + }, + + /** + * Shows a deprecation warning suggesting the modern and spec-compatible API. + */ + deprecated: function(oldMethod, newMethod) { + if (!deprecationWarnings_) { + return; + } + console.warn(oldMethod + ' is deprecated, please use ' + newMethod + + ' instead.'); + }, + + /** + * Browser detector. + * + * @return {object} result containing browser and version + * properties. + */ + detectBrowser: function(window) { + var navigator = window && window.navigator; + + // Returned result object. + var result = {}; + result.browser = null; + result.version = null; + + // Fail early if it's not a browser + if (typeof window === 'undefined' || !window.navigator) { + result.browser = 'Not a browser.'; + return result; + } + + if (navigator.mozGetUserMedia) { // Firefox. + result.browser = 'firefox'; + result.version = extractVersion(navigator.userAgent, + /Firefox\/(\d+)\./, 1); + } else if (navigator.webkitGetUserMedia) { + // Chrome, Chromium, Webview, Opera. + // Version matches Chrome/WebRTC version. + result.browser = 'chrome'; + result.version = extractVersion(navigator.userAgent, + /Chrom(e|ium)\/(\d+)\./, 2); + } else if (navigator.mediaDevices && + navigator.userAgent.match(/Edge\/(\d+).(\d+)$/)) { // Edge. + result.browser = 'edge'; + result.version = extractVersion(navigator.userAgent, + /Edge\/(\d+).(\d+)$/, 2); + } else if (window.RTCPeerConnection && + navigator.userAgent.match(/AppleWebKit\/(\d+)\./)) { // Safari. + result.browser = 'safari'; + result.version = extractVersion(navigator.userAgent, + /AppleWebKit\/(\d+)\./, 1); + } else { // Default fallthrough: not supported. + result.browser = 'Not a supported browser.'; + return result; + } + + return result; + } +}; + +},{}]},{},[3])(3) +}); diff --git a/FileBufferReader/demo/circular-progress-bar.css b/FileBufferReader/demo/circular-progress-bar.css deleted file mode 100644 index 0880dbad..00000000 --- a/FileBufferReader/demo/circular-progress-bar.css +++ /dev/null @@ -1,1065 +0,0 @@ -.rect-auto, -.c100.p51 .slice, -.c100.p52 .slice, -.c100.p53 .slice, -.c100.p54 .slice, -.c100.p55 .slice, -.c100.p56 .slice, -.c100.p57 .slice, -.c100.p58 .slice, -.c100.p59 .slice, -.c100.p60 .slice, -.c100.p61 .slice, -.c100.p62 .slice, -.c100.p63 .slice, -.c100.p64 .slice, -.c100.p65 .slice, -.c100.p66 .slice, -.c100.p67 .slice, -.c100.p68 .slice, -.c100.p69 .slice, -.c100.p70 .slice, -.c100.p71 .slice, -.c100.p72 .slice, -.c100.p73 .slice, -.c100.p74 .slice, -.c100.p75 .slice, -.c100.p76 .slice, -.c100.p77 .slice, -.c100.p78 .slice, -.c100.p79 .slice, -.c100.p80 .slice, -.c100.p81 .slice, -.c100.p82 .slice, -.c100.p83 .slice, -.c100.p84 .slice, -.c100.p85 .slice, -.c100.p86 .slice, -.c100.p87 .slice, -.c100.p88 .slice, -.c100.p89 .slice, -.c100.p90 .slice, -.c100.p91 .slice, -.c100.p92 .slice, -.c100.p93 .slice, -.c100.p94 .slice, -.c100.p95 .slice, -.c100.p96 .slice, -.c100.p97 .slice, -.c100.p98 .slice, -.c100.p99 .slice, -.c100.p100 .slice { - clip: rect(auto, auto, auto, auto); -} -.pie, -.c100 .bar, -.c100.p51 .fill, -.c100.p52 .fill, -.c100.p53 .fill, -.c100.p54 .fill, -.c100.p55 .fill, -.c100.p56 .fill, -.c100.p57 .fill, -.c100.p58 .fill, -.c100.p59 .fill, -.c100.p60 .fill, -.c100.p61 .fill, -.c100.p62 .fill, -.c100.p63 .fill, -.c100.p64 .fill, -.c100.p65 .fill, -.c100.p66 .fill, -.c100.p67 .fill, -.c100.p68 .fill, -.c100.p69 .fill, -.c100.p70 .fill, -.c100.p71 .fill, -.c100.p72 .fill, -.c100.p73 .fill, -.c100.p74 .fill, -.c100.p75 .fill, -.c100.p76 .fill, -.c100.p77 .fill, -.c100.p78 .fill, -.c100.p79 .fill, -.c100.p80 .fill, -.c100.p81 .fill, -.c100.p82 .fill, -.c100.p83 .fill, -.c100.p84 .fill, -.c100.p85 .fill, -.c100.p86 .fill, -.c100.p87 .fill, -.c100.p88 .fill, -.c100.p89 .fill, -.c100.p90 .fill, -.c100.p91 .fill, -.c100.p92 .fill, -.c100.p93 .fill, -.c100.p94 .fill, -.c100.p95 .fill, -.c100.p96 .fill, -.c100.p97 .fill, -.c100.p98 .fill, -.c100.p99 .fill, -.c100.p100 .fill { - position: absolute; - border: 0.08em solid #307bbb; - width: 0.84em; - height: 0.84em; - clip: rect(0em, 0.5em, 1em, 0em); - border-radius: 50%; - -webkit-transform: rotate(0deg); - -moz-transform: rotate(0deg); - -ms-transform: rotate(0deg); - -o-transform: rotate(0deg); - transform: rotate(0deg); -} -.pie-fill, -.c100.p51 .bar:after, -.c100.p51 .fill, -.c100.p52 .bar:after, -.c100.p52 .fill, -.c100.p53 .bar:after, -.c100.p53 .fill, -.c100.p54 .bar:after, -.c100.p54 .fill, -.c100.p55 .bar:after, -.c100.p55 .fill, -.c100.p56 .bar:after, -.c100.p56 .fill, -.c100.p57 .bar:after, -.c100.p57 .fill, -.c100.p58 .bar:after, -.c100.p58 .fill, -.c100.p59 .bar:after, -.c100.p59 .fill, -.c100.p60 .bar:after, -.c100.p60 .fill, -.c100.p61 .bar:after, -.c100.p61 .fill, -.c100.p62 .bar:after, -.c100.p62 .fill, -.c100.p63 .bar:after, -.c100.p63 .fill, -.c100.p64 .bar:after, -.c100.p64 .fill, -.c100.p65 .bar:after, -.c100.p65 .fill, -.c100.p66 .bar:after, -.c100.p66 .fill, -.c100.p67 .bar:after, -.c100.p67 .fill, -.c100.p68 .bar:after, -.c100.p68 .fill, -.c100.p69 .bar:after, -.c100.p69 .fill, -.c100.p70 .bar:after, -.c100.p70 .fill, -.c100.p71 .bar:after, -.c100.p71 .fill, -.c100.p72 .bar:after, -.c100.p72 .fill, -.c100.p73 .bar:after, -.c100.p73 .fill, -.c100.p74 .bar:after, -.c100.p74 .fill, -.c100.p75 .bar:after, -.c100.p75 .fill, -.c100.p76 .bar:after, -.c100.p76 .fill, -.c100.p77 .bar:after, -.c100.p77 .fill, -.c100.p78 .bar:after, -.c100.p78 .fill, -.c100.p79 .bar:after, -.c100.p79 .fill, -.c100.p80 .bar:after, -.c100.p80 .fill, -.c100.p81 .bar:after, -.c100.p81 .fill, -.c100.p82 .bar:after, -.c100.p82 .fill, -.c100.p83 .bar:after, -.c100.p83 .fill, -.c100.p84 .bar:after, -.c100.p84 .fill, -.c100.p85 .bar:after, -.c100.p85 .fill, -.c100.p86 .bar:after, -.c100.p86 .fill, -.c100.p87 .bar:after, -.c100.p87 .fill, -.c100.p88 .bar:after, -.c100.p88 .fill, -.c100.p89 .bar:after, -.c100.p89 .fill, -.c100.p90 .bar:after, -.c100.p90 .fill, -.c100.p91 .bar:after, -.c100.p91 .fill, -.c100.p92 .bar:after, -.c100.p92 .fill, -.c100.p93 .bar:after, -.c100.p93 .fill, -.c100.p94 .bar:after, -.c100.p94 .fill, -.c100.p95 .bar:after, -.c100.p95 .fill, -.c100.p96 .bar:after, -.c100.p96 .fill, -.c100.p97 .bar:after, -.c100.p97 .fill, -.c100.p98 .bar:after, -.c100.p98 .fill, -.c100.p99 .bar:after, -.c100.p99 .fill, -.c100.p100 .bar:after, -.c100.p100 .fill { - -webkit-transform: rotate(180deg); - -moz-transform: rotate(180deg); - -ms-transform: rotate(180deg); - -o-transform: rotate(180deg); - transform: rotate(180deg); -} -.c100 { - position: relative; - font-size: 120px; - width: 1em; - height: 1em; - border-radius: 50%; - float: left; - margin: 0 0.1em 0.1em 0; - background-color: #cccccc; -} -.c100 *, -.c100 *:before, -.c100 *:after { - -webkit-box-sizing: content-box; - -moz-box-sizing: content-box; - box-sizing: content-box; -} -.c100.center { - float: none; - margin: 0 auto; -} -.c100.big { - font-size: 240px; -} -.c100.small { - font-size: 80px; -} -.c100 > span { - position: absolute; - width: 100%; - z-index: 1; - left: 0; - top: 0; - width: 5em; - line-height: 5em; - font-size: 0.2em; - color: #cccccc; - display: block; - text-align: center; - white-space: nowrap; - -webkit-transition-property: all; - -moz-transition-property: all; - -o-transition-property: all; - transition-property: all; - -webkit-transition-duration: 0.2s; - -moz-transition-duration: 0.2s; - -o-transition-duration: 0.2s; - transition-duration: 0.2s; - -webkit-transition-timing-function: ease-out; - -moz-transition-timing-function: ease-out; - -o-transition-timing-function: ease-out; - transition-timing-function: ease-out; -} -.c100:after { - position: absolute; - top: 0.08em; - left: 0.08em; - display: block; - content: " "; - border-radius: 50%; - background-color: #f5f5f5; - width: 0.84em; - height: 0.84em; - -webkit-transition-property: all; - -moz-transition-property: all; - -o-transition-property: all; - transition-property: all; - -webkit-transition-duration: 0.2s; - -moz-transition-duration: 0.2s; - -o-transition-duration: 0.2s; - transition-duration: 0.2s; - -webkit-transition-timing-function: ease-in; - -moz-transition-timing-function: ease-in; - -o-transition-timing-function: ease-in; - transition-timing-function: ease-in; -} -.c100 .slice { - position: absolute; - width: 1em; - height: 1em; - clip: rect(0em, 1em, 1em, 0.5em); -} -.c100.p1 .bar { - -webkit-transform: rotate(3.6deg); - -moz-transform: rotate(3.6deg); - -ms-transform: rotate(3.6deg); - -o-transform: rotate(3.6deg); - transform: rotate(3.6deg); -} -.c100.p2 .bar { - -webkit-transform: rotate(7.2deg); - -moz-transform: rotate(7.2deg); - -ms-transform: rotate(7.2deg); - -o-transform: rotate(7.2deg); - transform: rotate(7.2deg); -} -.c100.p3 .bar { - -webkit-transform: rotate(10.8deg); - -moz-transform: rotate(10.8deg); - -ms-transform: rotate(10.8deg); - -o-transform: rotate(10.8deg); - transform: rotate(10.8deg); -} -.c100.p4 .bar { - -webkit-transform: rotate(14.4deg); - -moz-transform: rotate(14.4deg); - -ms-transform: rotate(14.4deg); - -o-transform: rotate(14.4deg); - transform: rotate(14.4deg); -} -.c100.p5 .bar { - -webkit-transform: rotate(18deg); - -moz-transform: rotate(18deg); - -ms-transform: rotate(18deg); - -o-transform: rotate(18deg); - transform: rotate(18deg); -} -.c100.p6 .bar { - -webkit-transform: rotate(21.6deg); - -moz-transform: rotate(21.6deg); - -ms-transform: rotate(21.6deg); - -o-transform: rotate(21.6deg); - transform: rotate(21.6deg); -} -.c100.p7 .bar { - -webkit-transform: rotate(25.2deg); - -moz-transform: rotate(25.2deg); - -ms-transform: rotate(25.2deg); - -o-transform: rotate(25.2deg); - transform: rotate(25.2deg); -} -.c100.p8 .bar { - -webkit-transform: rotate(28.8deg); - -moz-transform: rotate(28.8deg); - -ms-transform: rotate(28.8deg); - -o-transform: rotate(28.8deg); - transform: rotate(28.8deg); -} -.c100.p9 .bar { - -webkit-transform: rotate(32.4deg); - -moz-transform: rotate(32.4deg); - -ms-transform: rotate(32.4deg); - -o-transform: rotate(32.4deg); - transform: rotate(32.4deg); -} -.c100.p10 .bar { - -webkit-transform: rotate(36deg); - -moz-transform: rotate(36deg); - -ms-transform: rotate(36deg); - -o-transform: rotate(36deg); - transform: rotate(36deg); -} -.c100.p11 .bar { - -webkit-transform: rotate(39.6deg); - -moz-transform: rotate(39.6deg); - -ms-transform: rotate(39.6deg); - -o-transform: rotate(39.6deg); - transform: rotate(39.6deg); -} -.c100.p12 .bar { - -webkit-transform: rotate(43.2deg); - -moz-transform: rotate(43.2deg); - -ms-transform: rotate(43.2deg); - -o-transform: rotate(43.2deg); - transform: rotate(43.2deg); -} -.c100.p13 .bar { - -webkit-transform: rotate(46.800000000000004deg); - -moz-transform: rotate(46.800000000000004deg); - -ms-transform: rotate(46.800000000000004deg); - -o-transform: rotate(46.800000000000004deg); - transform: rotate(46.800000000000004deg); -} -.c100.p14 .bar { - -webkit-transform: rotate(50.4deg); - -moz-transform: rotate(50.4deg); - -ms-transform: rotate(50.4deg); - -o-transform: rotate(50.4deg); - transform: rotate(50.4deg); -} -.c100.p15 .bar { - -webkit-transform: rotate(54deg); - -moz-transform: rotate(54deg); - -ms-transform: rotate(54deg); - -o-transform: rotate(54deg); - transform: rotate(54deg); -} -.c100.p16 .bar { - -webkit-transform: rotate(57.6deg); - -moz-transform: rotate(57.6deg); - -ms-transform: rotate(57.6deg); - -o-transform: rotate(57.6deg); - transform: rotate(57.6deg); -} -.c100.p17 .bar { - -webkit-transform: rotate(61.2deg); - -moz-transform: rotate(61.2deg); - -ms-transform: rotate(61.2deg); - -o-transform: rotate(61.2deg); - transform: rotate(61.2deg); -} -.c100.p18 .bar { - -webkit-transform: rotate(64.8deg); - -moz-transform: rotate(64.8deg); - -ms-transform: rotate(64.8deg); - -o-transform: rotate(64.8deg); - transform: rotate(64.8deg); -} -.c100.p19 .bar { - -webkit-transform: rotate(68.4deg); - -moz-transform: rotate(68.4deg); - -ms-transform: rotate(68.4deg); - -o-transform: rotate(68.4deg); - transform: rotate(68.4deg); -} -.c100.p20 .bar { - -webkit-transform: rotate(72deg); - -moz-transform: rotate(72deg); - -ms-transform: rotate(72deg); - -o-transform: rotate(72deg); - transform: rotate(72deg); -} -.c100.p21 .bar { - -webkit-transform: rotate(75.60000000000001deg); - -moz-transform: rotate(75.60000000000001deg); - -ms-transform: rotate(75.60000000000001deg); - -o-transform: rotate(75.60000000000001deg); - transform: rotate(75.60000000000001deg); -} -.c100.p22 .bar { - -webkit-transform: rotate(79.2deg); - -moz-transform: rotate(79.2deg); - -ms-transform: rotate(79.2deg); - -o-transform: rotate(79.2deg); - transform: rotate(79.2deg); -} -.c100.p23 .bar { - -webkit-transform: rotate(82.8deg); - -moz-transform: rotate(82.8deg); - -ms-transform: rotate(82.8deg); - -o-transform: rotate(82.8deg); - transform: rotate(82.8deg); -} -.c100.p24 .bar { - -webkit-transform: rotate(86.4deg); - -moz-transform: rotate(86.4deg); - -ms-transform: rotate(86.4deg); - -o-transform: rotate(86.4deg); - transform: rotate(86.4deg); -} -.c100.p25 .bar { - -webkit-transform: rotate(90deg); - -moz-transform: rotate(90deg); - -ms-transform: rotate(90deg); - -o-transform: rotate(90deg); - transform: rotate(90deg); -} -.c100.p26 .bar { - -webkit-transform: rotate(93.60000000000001deg); - -moz-transform: rotate(93.60000000000001deg); - -ms-transform: rotate(93.60000000000001deg); - -o-transform: rotate(93.60000000000001deg); - transform: rotate(93.60000000000001deg); -} -.c100.p27 .bar { - -webkit-transform: rotate(97.2deg); - -moz-transform: rotate(97.2deg); - -ms-transform: rotate(97.2deg); - -o-transform: rotate(97.2deg); - transform: rotate(97.2deg); -} -.c100.p28 .bar { - -webkit-transform: rotate(100.8deg); - -moz-transform: rotate(100.8deg); - -ms-transform: rotate(100.8deg); - -o-transform: rotate(100.8deg); - transform: rotate(100.8deg); -} -.c100.p29 .bar { - -webkit-transform: rotate(104.4deg); - -moz-transform: rotate(104.4deg); - -ms-transform: rotate(104.4deg); - -o-transform: rotate(104.4deg); - transform: rotate(104.4deg); -} -.c100.p30 .bar { - -webkit-transform: rotate(108deg); - -moz-transform: rotate(108deg); - -ms-transform: rotate(108deg); - -o-transform: rotate(108deg); - transform: rotate(108deg); -} -.c100.p31 .bar { - -webkit-transform: rotate(111.60000000000001deg); - -moz-transform: rotate(111.60000000000001deg); - -ms-transform: rotate(111.60000000000001deg); - -o-transform: rotate(111.60000000000001deg); - transform: rotate(111.60000000000001deg); -} -.c100.p32 .bar { - -webkit-transform: rotate(115.2deg); - -moz-transform: rotate(115.2deg); - -ms-transform: rotate(115.2deg); - -o-transform: rotate(115.2deg); - transform: rotate(115.2deg); -} -.c100.p33 .bar { - -webkit-transform: rotate(118.8deg); - -moz-transform: rotate(118.8deg); - -ms-transform: rotate(118.8deg); - -o-transform: rotate(118.8deg); - transform: rotate(118.8deg); -} -.c100.p34 .bar { - -webkit-transform: rotate(122.4deg); - -moz-transform: rotate(122.4deg); - -ms-transform: rotate(122.4deg); - -o-transform: rotate(122.4deg); - transform: rotate(122.4deg); -} -.c100.p35 .bar { - -webkit-transform: rotate(126deg); - -moz-transform: rotate(126deg); - -ms-transform: rotate(126deg); - -o-transform: rotate(126deg); - transform: rotate(126deg); -} -.c100.p36 .bar { - -webkit-transform: rotate(129.6deg); - -moz-transform: rotate(129.6deg); - -ms-transform: rotate(129.6deg); - -o-transform: rotate(129.6deg); - transform: rotate(129.6deg); -} -.c100.p37 .bar { - -webkit-transform: rotate(133.20000000000002deg); - -moz-transform: rotate(133.20000000000002deg); - -ms-transform: rotate(133.20000000000002deg); - -o-transform: rotate(133.20000000000002deg); - transform: rotate(133.20000000000002deg); -} -.c100.p38 .bar { - -webkit-transform: rotate(136.8deg); - -moz-transform: rotate(136.8deg); - -ms-transform: rotate(136.8deg); - -o-transform: rotate(136.8deg); - transform: rotate(136.8deg); -} -.c100.p39 .bar { - -webkit-transform: rotate(140.4deg); - -moz-transform: rotate(140.4deg); - -ms-transform: rotate(140.4deg); - -o-transform: rotate(140.4deg); - transform: rotate(140.4deg); -} -.c100.p40 .bar { - -webkit-transform: rotate(144deg); - -moz-transform: rotate(144deg); - -ms-transform: rotate(144deg); - -o-transform: rotate(144deg); - transform: rotate(144deg); -} -.c100.p41 .bar { - -webkit-transform: rotate(147.6deg); - -moz-transform: rotate(147.6deg); - -ms-transform: rotate(147.6deg); - -o-transform: rotate(147.6deg); - transform: rotate(147.6deg); -} -.c100.p42 .bar { - -webkit-transform: rotate(151.20000000000002deg); - -moz-transform: rotate(151.20000000000002deg); - -ms-transform: rotate(151.20000000000002deg); - -o-transform: rotate(151.20000000000002deg); - transform: rotate(151.20000000000002deg); -} -.c100.p43 .bar { - -webkit-transform: rotate(154.8deg); - -moz-transform: rotate(154.8deg); - -ms-transform: rotate(154.8deg); - -o-transform: rotate(154.8deg); - transform: rotate(154.8deg); -} -.c100.p44 .bar { - -webkit-transform: rotate(158.4deg); - -moz-transform: rotate(158.4deg); - -ms-transform: rotate(158.4deg); - -o-transform: rotate(158.4deg); - transform: rotate(158.4deg); -} -.c100.p45 .bar { - -webkit-transform: rotate(162deg); - -moz-transform: rotate(162deg); - -ms-transform: rotate(162deg); - -o-transform: rotate(162deg); - transform: rotate(162deg); -} -.c100.p46 .bar { - -webkit-transform: rotate(165.6deg); - -moz-transform: rotate(165.6deg); - -ms-transform: rotate(165.6deg); - -o-transform: rotate(165.6deg); - transform: rotate(165.6deg); -} -.c100.p47 .bar { - -webkit-transform: rotate(169.20000000000002deg); - -moz-transform: rotate(169.20000000000002deg); - -ms-transform: rotate(169.20000000000002deg); - -o-transform: rotate(169.20000000000002deg); - transform: rotate(169.20000000000002deg); -} -.c100.p48 .bar { - -webkit-transform: rotate(172.8deg); - -moz-transform: rotate(172.8deg); - -ms-transform: rotate(172.8deg); - -o-transform: rotate(172.8deg); - transform: rotate(172.8deg); -} -.c100.p49 .bar { - -webkit-transform: rotate(176.4deg); - -moz-transform: rotate(176.4deg); - -ms-transform: rotate(176.4deg); - -o-transform: rotate(176.4deg); - transform: rotate(176.4deg); -} -.c100.p50 .bar { - -webkit-transform: rotate(180deg); - -moz-transform: rotate(180deg); - -ms-transform: rotate(180deg); - -o-transform: rotate(180deg); - transform: rotate(180deg); -} -.c100.p51 .bar { - -webkit-transform: rotate(183.6deg); - -moz-transform: rotate(183.6deg); - -ms-transform: rotate(183.6deg); - -o-transform: rotate(183.6deg); - transform: rotate(183.6deg); -} -.c100.p52 .bar { - -webkit-transform: rotate(187.20000000000002deg); - -moz-transform: rotate(187.20000000000002deg); - -ms-transform: rotate(187.20000000000002deg); - -o-transform: rotate(187.20000000000002deg); - transform: rotate(187.20000000000002deg); -} -.c100.p53 .bar { - -webkit-transform: rotate(190.8deg); - -moz-transform: rotate(190.8deg); - -ms-transform: rotate(190.8deg); - -o-transform: rotate(190.8deg); - transform: rotate(190.8deg); -} -.c100.p54 .bar { - -webkit-transform: rotate(194.4deg); - -moz-transform: rotate(194.4deg); - -ms-transform: rotate(194.4deg); - -o-transform: rotate(194.4deg); - transform: rotate(194.4deg); -} -.c100.p55 .bar { - -webkit-transform: rotate(198deg); - -moz-transform: rotate(198deg); - -ms-transform: rotate(198deg); - -o-transform: rotate(198deg); - transform: rotate(198deg); -} -.c100.p56 .bar { - -webkit-transform: rotate(201.6deg); - -moz-transform: rotate(201.6deg); - -ms-transform: rotate(201.6deg); - -o-transform: rotate(201.6deg); - transform: rotate(201.6deg); -} -.c100.p57 .bar { - -webkit-transform: rotate(205.20000000000002deg); - -moz-transform: rotate(205.20000000000002deg); - -ms-transform: rotate(205.20000000000002deg); - -o-transform: rotate(205.20000000000002deg); - transform: rotate(205.20000000000002deg); -} -.c100.p58 .bar { - -webkit-transform: rotate(208.8deg); - -moz-transform: rotate(208.8deg); - -ms-transform: rotate(208.8deg); - -o-transform: rotate(208.8deg); - transform: rotate(208.8deg); -} -.c100.p59 .bar { - -webkit-transform: rotate(212.4deg); - -moz-transform: rotate(212.4deg); - -ms-transform: rotate(212.4deg); - -o-transform: rotate(212.4deg); - transform: rotate(212.4deg); -} -.c100.p60 .bar { - -webkit-transform: rotate(216deg); - -moz-transform: rotate(216deg); - -ms-transform: rotate(216deg); - -o-transform: rotate(216deg); - transform: rotate(216deg); -} -.c100.p61 .bar { - -webkit-transform: rotate(219.6deg); - -moz-transform: rotate(219.6deg); - -ms-transform: rotate(219.6deg); - -o-transform: rotate(219.6deg); - transform: rotate(219.6deg); -} -.c100.p62 .bar { - -webkit-transform: rotate(223.20000000000002deg); - -moz-transform: rotate(223.20000000000002deg); - -ms-transform: rotate(223.20000000000002deg); - -o-transform: rotate(223.20000000000002deg); - transform: rotate(223.20000000000002deg); -} -.c100.p63 .bar { - -webkit-transform: rotate(226.8deg); - -moz-transform: rotate(226.8deg); - -ms-transform: rotate(226.8deg); - -o-transform: rotate(226.8deg); - transform: rotate(226.8deg); -} -.c100.p64 .bar { - -webkit-transform: rotate(230.4deg); - -moz-transform: rotate(230.4deg); - -ms-transform: rotate(230.4deg); - -o-transform: rotate(230.4deg); - transform: rotate(230.4deg); -} -.c100.p65 .bar { - -webkit-transform: rotate(234deg); - -moz-transform: rotate(234deg); - -ms-transform: rotate(234deg); - -o-transform: rotate(234deg); - transform: rotate(234deg); -} -.c100.p66 .bar { - -webkit-transform: rotate(237.6deg); - -moz-transform: rotate(237.6deg); - -ms-transform: rotate(237.6deg); - -o-transform: rotate(237.6deg); - transform: rotate(237.6deg); -} -.c100.p67 .bar { - -webkit-transform: rotate(241.20000000000002deg); - -moz-transform: rotate(241.20000000000002deg); - -ms-transform: rotate(241.20000000000002deg); - -o-transform: rotate(241.20000000000002deg); - transform: rotate(241.20000000000002deg); -} -.c100.p68 .bar { - -webkit-transform: rotate(244.8deg); - -moz-transform: rotate(244.8deg); - -ms-transform: rotate(244.8deg); - -o-transform: rotate(244.8deg); - transform: rotate(244.8deg); -} -.c100.p69 .bar { - -webkit-transform: rotate(248.4deg); - -moz-transform: rotate(248.4deg); - -ms-transform: rotate(248.4deg); - -o-transform: rotate(248.4deg); - transform: rotate(248.4deg); -} -.c100.p70 .bar { - -webkit-transform: rotate(252deg); - -moz-transform: rotate(252deg); - -ms-transform: rotate(252deg); - -o-transform: rotate(252deg); - transform: rotate(252deg); -} -.c100.p71 .bar { - -webkit-transform: rotate(255.6deg); - -moz-transform: rotate(255.6deg); - -ms-transform: rotate(255.6deg); - -o-transform: rotate(255.6deg); - transform: rotate(255.6deg); -} -.c100.p72 .bar { - -webkit-transform: rotate(259.2deg); - -moz-transform: rotate(259.2deg); - -ms-transform: rotate(259.2deg); - -o-transform: rotate(259.2deg); - transform: rotate(259.2deg); -} -.c100.p73 .bar { - -webkit-transform: rotate(262.8deg); - -moz-transform: rotate(262.8deg); - -ms-transform: rotate(262.8deg); - -o-transform: rotate(262.8deg); - transform: rotate(262.8deg); -} -.c100.p74 .bar { - -webkit-transform: rotate(266.40000000000003deg); - -moz-transform: rotate(266.40000000000003deg); - -ms-transform: rotate(266.40000000000003deg); - -o-transform: rotate(266.40000000000003deg); - transform: rotate(266.40000000000003deg); -} -.c100.p75 .bar { - -webkit-transform: rotate(270deg); - -moz-transform: rotate(270deg); - -ms-transform: rotate(270deg); - -o-transform: rotate(270deg); - transform: rotate(270deg); -} -.c100.p76 .bar { - -webkit-transform: rotate(273.6deg); - -moz-transform: rotate(273.6deg); - -ms-transform: rotate(273.6deg); - -o-transform: rotate(273.6deg); - transform: rotate(273.6deg); -} -.c100.p77 .bar { - -webkit-transform: rotate(277.2deg); - -moz-transform: rotate(277.2deg); - -ms-transform: rotate(277.2deg); - -o-transform: rotate(277.2deg); - transform: rotate(277.2deg); -} -.c100.p78 .bar { - -webkit-transform: rotate(280.8deg); - -moz-transform: rotate(280.8deg); - -ms-transform: rotate(280.8deg); - -o-transform: rotate(280.8deg); - transform: rotate(280.8deg); -} -.c100.p79 .bar { - -webkit-transform: rotate(284.40000000000003deg); - -moz-transform: rotate(284.40000000000003deg); - -ms-transform: rotate(284.40000000000003deg); - -o-transform: rotate(284.40000000000003deg); - transform: rotate(284.40000000000003deg); -} -.c100.p80 .bar { - -webkit-transform: rotate(288deg); - -moz-transform: rotate(288deg); - -ms-transform: rotate(288deg); - -o-transform: rotate(288deg); - transform: rotate(288deg); -} -.c100.p81 .bar { - -webkit-transform: rotate(291.6deg); - -moz-transform: rotate(291.6deg); - -ms-transform: rotate(291.6deg); - -o-transform: rotate(291.6deg); - transform: rotate(291.6deg); -} -.c100.p82 .bar { - -webkit-transform: rotate(295.2deg); - -moz-transform: rotate(295.2deg); - -ms-transform: rotate(295.2deg); - -o-transform: rotate(295.2deg); - transform: rotate(295.2deg); -} -.c100.p83 .bar { - -webkit-transform: rotate(298.8deg); - -moz-transform: rotate(298.8deg); - -ms-transform: rotate(298.8deg); - -o-transform: rotate(298.8deg); - transform: rotate(298.8deg); -} -.c100.p84 .bar { - -webkit-transform: rotate(302.40000000000003deg); - -moz-transform: rotate(302.40000000000003deg); - -ms-transform: rotate(302.40000000000003deg); - -o-transform: rotate(302.40000000000003deg); - transform: rotate(302.40000000000003deg); -} -.c100.p85 .bar { - -webkit-transform: rotate(306deg); - -moz-transform: rotate(306deg); - -ms-transform: rotate(306deg); - -o-transform: rotate(306deg); - transform: rotate(306deg); -} -.c100.p86 .bar { - -webkit-transform: rotate(309.6deg); - -moz-transform: rotate(309.6deg); - -ms-transform: rotate(309.6deg); - -o-transform: rotate(309.6deg); - transform: rotate(309.6deg); -} -.c100.p87 .bar { - -webkit-transform: rotate(313.2deg); - -moz-transform: rotate(313.2deg); - -ms-transform: rotate(313.2deg); - -o-transform: rotate(313.2deg); - transform: rotate(313.2deg); -} -.c100.p88 .bar { - -webkit-transform: rotate(316.8deg); - -moz-transform: rotate(316.8deg); - -ms-transform: rotate(316.8deg); - -o-transform: rotate(316.8deg); - transform: rotate(316.8deg); -} -.c100.p89 .bar { - -webkit-transform: rotate(320.40000000000003deg); - -moz-transform: rotate(320.40000000000003deg); - -ms-transform: rotate(320.40000000000003deg); - -o-transform: rotate(320.40000000000003deg); - transform: rotate(320.40000000000003deg); -} -.c100.p90 .bar { - -webkit-transform: rotate(324deg); - -moz-transform: rotate(324deg); - -ms-transform: rotate(324deg); - -o-transform: rotate(324deg); - transform: rotate(324deg); -} -.c100.p91 .bar { - -webkit-transform: rotate(327.6deg); - -moz-transform: rotate(327.6deg); - -ms-transform: rotate(327.6deg); - -o-transform: rotate(327.6deg); - transform: rotate(327.6deg); -} -.c100.p92 .bar { - -webkit-transform: rotate(331.2deg); - -moz-transform: rotate(331.2deg); - -ms-transform: rotate(331.2deg); - -o-transform: rotate(331.2deg); - transform: rotate(331.2deg); -} -.c100.p93 .bar { - -webkit-transform: rotate(334.8deg); - -moz-transform: rotate(334.8deg); - -ms-transform: rotate(334.8deg); - -o-transform: rotate(334.8deg); - transform: rotate(334.8deg); -} -.c100.p94 .bar { - -webkit-transform: rotate(338.40000000000003deg); - -moz-transform: rotate(338.40000000000003deg); - -ms-transform: rotate(338.40000000000003deg); - -o-transform: rotate(338.40000000000003deg); - transform: rotate(338.40000000000003deg); -} -.c100.p95 .bar { - -webkit-transform: rotate(342deg); - -moz-transform: rotate(342deg); - -ms-transform: rotate(342deg); - -o-transform: rotate(342deg); - transform: rotate(342deg); -} -.c100.p96 .bar { - -webkit-transform: rotate(345.6deg); - -moz-transform: rotate(345.6deg); - -ms-transform: rotate(345.6deg); - -o-transform: rotate(345.6deg); - transform: rotate(345.6deg); -} -.c100.p97 .bar { - -webkit-transform: rotate(349.2deg); - -moz-transform: rotate(349.2deg); - -ms-transform: rotate(349.2deg); - -o-transform: rotate(349.2deg); - transform: rotate(349.2deg); -} -.c100.p98 .bar { - -webkit-transform: rotate(352.8deg); - -moz-transform: rotate(352.8deg); - -ms-transform: rotate(352.8deg); - -o-transform: rotate(352.8deg); - transform: rotate(352.8deg); -} -.c100.p99 .bar { - -webkit-transform: rotate(356.40000000000003deg); - -moz-transform: rotate(356.40000000000003deg); - -ms-transform: rotate(356.40000000000003deg); - -o-transform: rotate(356.40000000000003deg); - transform: rotate(356.40000000000003deg); -} -.c100.p100 .bar { - -webkit-transform: rotate(360deg); - -moz-transform: rotate(360deg); - -ms-transform: rotate(360deg); - -o-transform: rotate(360deg); - transform: rotate(360deg); -} -.c100:hover { - cursor: default; -} -.c100:hover > span { - width: 3.33em; - line-height: 3.33em; - font-size: 0.3em; - color: #307bbb; -} -.c100:hover:after { - top: 0.04em; - left: 0.04em; - width: 0.92em; - height: 0.92em; -} -.c100.dark { - background-color: #777777; -} -.c100.dark .bar, -.c100.dark .fill { - border-color: #c6ff00 !important; -} -.c100.dark > span { - color: #777777; -} -.c100.dark:after { - background-color: #666666; -} -.c100.dark:hover > span { - color: #c6ff00; -} -.c100.green .bar, -.c100.green .fill { - border-color: #4db53c !important; -} -.c100.green:hover > span { - color: #4db53c; -} -.c100.green.dark .bar, -.c100.green.dark .fill { - border-color: #5fd400 !important; -} -.c100.green.dark:hover > span { - color: #5fd400; -} -.c100.orange .bar, -.c100.orange .fill { - border-color: #dd9d22 !important; -} -.c100.orange:hover > span { - color: #dd9d22; -} -.c100.orange.dark .bar, -.c100.orange.dark .fill { - border-color: #e08833 !important; -} -.c100.orange.dark:hover > span { - color: #e08833; -} diff --git a/FileBufferReader/demo/index.html b/FileBufferReader/demo/index.html new file mode 100644 index 00000000..7f947736 --- /dev/null +++ b/FileBufferReader/demo/index.html @@ -0,0 +1,388 @@ + + + + + + + + + WebRTC File Sharing | FileBufferReader + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ WebRTC File Sharing | FileBufferReader +

+ +

+ Github Source Codes | + What's New? +

+
+ +
+ + +
+ +
+
+ Watch a YouTube video to understand how this demo works. +
+ +
+
+ + +
+ +

+ + + + +
+ +
+ + +
+ +
+ FileBufferReader is a JavaScript library reads file and returns chunkified array-buffers. The resulting buffers can be shared using WebRTC data channels or socket.io. It is Open Sourced on Github +
+ +
+ +
+
+ + + + + + diff --git a/FileBufferReader/icons/close-icon.png b/FileBufferReader/icons/close-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..9f63fb59912c9cfb98f782612e8888975317be9d GIT binary patch literal 1920 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 literal 0 HcmV?d00001 diff --git a/FileBufferReader/icons/resume-icon.png b/FileBufferReader/icons/resume-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..13ed283cb5bd1d4731f5517afb049899e4133ea4 GIT binary patch literal 1303 zcmV+y1?c*TP)`jqAQA+1p(|ae5EYWBNN_+D6%-5v zjW|Rjp(S*x(_Jpghtj5~s`u`9zdM{CoORv%o!iyvO1<|$L_|bHL_|bHL_|bHL_{Q* zL%bIt@m&jS2YSGMU>h*yxgd#WBhVj`&A^#{OA~8r7+!vp{lEs`Xs0!bp{+?Z!!x!4lIqoWC^M50ci3wa8vYEOE6szK$8!F^CGWZ z0tj^g`oIC;8DM$T9Y`l34?vUOfLqZgidnEY08KsyE{wMoX~XgW^np3x1>m??8@GIsf%}! zU9%Hd8+s@;@h&h_-T_tyo=p|Jiwl_^@ECAR&_1;WtnZjs$i2U zly^zinFXE%mW3KvX}k-Hm0y6H(KnU<$Ge!=`3N{a$m9y)U5H2KfalN!EP#I*p4koD zPUYWJ_AViXd;(n5b&$F89)2pB2Q~r6cN=GZya%6J_5gPQhjkfhPP_-6YQ6w2>oC`B zcn>=DyaJrudb%0#9&irX3*1AY7qEB_H>Z39T-k2S>GB?Gj(NJ(q!Z;m%v^JSn_*8X z#(!M&BfLh5W`VOAd{fy>)gMtNZV_;&^^52Lr0pu}7tpVOwDmB)T1oE!X*2)f(E&)? z4(k`t0Z7|s>le_!0n*iD@MoauyMW#S()AD#eg(wf{fSzRd7{;%6XiY3obn}bdAl*E z%X_FfAfQ`Vhp$1kO?}9Az9≦QI*NNB#Zn2ToZCgCG##TWM+Uzfun*> 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 Fe*+v?Yjgkr literal 0 HcmV?d00001 diff --git a/FileBufferReader/index.html b/FileBufferReader/index.html index 726e93c7..db1f1496 100644 --- a/FileBufferReader/index.html +++ b/FileBufferReader/index.html @@ -1,11 +1,3 @@ - - - - @@ -28,300 +20,719 @@ - + + + + + + + - - - +selectFile.addEventListener('dragenter', function(e) { + e.preventDefault(); + e.stopPropagation(); - - - - + if (!peerConnection || !peerConnection.isOpened) return; - - + e.dataTransfer.dropEffect = 'copy'; + onDragOver(); +}, false); - -
-
-

- WebRTC File Sharing | FileBufferReader -

- -

- Github Source Codes | - What's New? -

-
- -
- - -
- -
-
- Watch a YouTube video to understand how this demo works. -
- -
-
- - -
- -

- - - - -
- -
    - - -
    - -
    - FileBufferReader is a JavaScript library reads file and returns chunkified array-buffers. The resulting buffers can be shared using WebRTC data channels or socket.io. It is Open Sourced on Github -
    - -
    - -
    -
    - - - - +selectFile.addEventListener('dragleave', function(e) { + e.preventDefault(); + e.stopPropagation(); + + if (!peerConnection || !peerConnection.isOpened) return; + + e.dataTransfer.dropEffect = 'copy'; + onDragLeave(); +}, false); + +selectFile.addEventListener('dragover', function(e) { + e.preventDefault(); + e.stopPropagation(); + + if (!peerConnection || !peerConnection.isOpened) return; + + e.dataTransfer.dropEffect = 'copy'; + onDragOver(); +}, false); + +selectFile.addEventListener('drop', function(e) { + e.preventDefault(); + e.stopPropagation(); + + if (!peerConnection || !peerConnection.isOpened) return; + + onDragLeave(); + if (!e.dataTransfer.files || !e.dataTransfer.files.length) { + return; + } + + var file = e.dataTransfer.files[0]; + + filesRemaining = { + files: e.dataTransfer.files, + idx: 0 + }; + + if (!peerConnection || !peerConnection.isOpened) { + alert('Pleas setup WebRTC connection before sharing this file.'); + return; + } + + onFileSelected(file); +}, false); + +// -------------------------------------------------------- +selectFile.onclick = function(event) { + if (event !== true) { + fileSelector.selectMultipleFiles(function(files) { + filesRemaining = { + files: files, + idx: 0 + }; + + if (peerConnection.isOpened) { + sendEntireDirectory(); + } + else { + peerConnection.onWebRTCOpened = function() { + sendEntireDirectory(); + }; + + alert('Selected file will be shared as soon as someone joins you.'); + } + }, onNoFileSelected); + } + + if (peerConnection.isOpened) { + return; + } + + connectionStatus.innerHTML = 'Waiting for someone to join you'; + connectionStatus.classList.remove('connected', 'connecting'); + + setTimeout(function() { + if (!peerConnection.isOpened) { + selectFile.onclick(true); + } + }, 5 * 1000); +}; + +function millsecondsToSeconds(millis) { + var seconds = ((millis % 60000) / 1000).toFixed(1); + return seconds; +} + +function bytesToSize(bytes) { + var k = 1000; + var sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB']; + if (bytes === 0) { + return '0 Bytes'; + } + var i = parseInt(Math.floor(Math.log(bytes) / Math.log(k)), 10); + return (bytes / Math.pow(k, i)).toPrecision(3) + ' ' + sizes[i]; +} + +function getToken() { + if (window.crypto && window.crypto.getRandomValues && navigator.userAgent.indexOf('Safari') === -1) { + var a = window.crypto.getRandomValues(new Uint32Array(3)), + token = ''; + for (var i = 0, l = a.length; i < l; i++) { + token += a[i].toString(36); + } + return token; + } else { + return (Math.random() * new Date().getTime()).toString(36).replace(/\./g, ''); + } +} + + diff --git a/FileBufferReader/nohup.out b/FileBufferReader/nohup.out deleted file mode 100644 index 150655f8..00000000 --- a/FileBufferReader/nohup.out +++ /dev/null @@ -1,11 +0,0 @@ -module.js:472 - throw err; - ^ - -Error: Cannot find module '/Users/muaz/gitRepos/FileBufferReader/ssl.js' - at Function.Module._resolveFilename (module.js:470:15) - at Function.Module._load (module.js:418:25) - at Module.runMain (module.js:605:10) - at run (bootstrap_node.js:420:7) - at startup (bootstrap_node.js:139:9) - at bootstrap_node.js:535:3 diff --git a/FileBufferReader/package.json b/FileBufferReader/package.json index e844ff98..d85e0304 100644 --- a/FileBufferReader/package.json +++ b/FileBufferReader/package.json @@ -1,7 +1,7 @@ { "name": "fbr", "preferGlobal": true, - "version": "2.0.6", + "version": "2.0.8", "author": { "name": "Muaz Khan", "email": "muazkh@gmail.com", diff --git a/MultiStreamsMixer/Gruntfile.js b/MultiStreamsMixer/Gruntfile.js index 3b8e9f71..b04f7ce0 100644 --- a/MultiStreamsMixer/Gruntfile.js +++ b/MultiStreamsMixer/Gruntfile.js @@ -113,6 +113,15 @@ module.exports = function(grunt) { pushTo: 'upstream', gitDescribeOptions: '--tags --always --abbrev=1 --dirty=-d' } + }, + watch: { + scripts: { + files: ['dev/*.js'], + tasks: ['concat', 'jsbeautifier', 'uglify'], + options: { + spawn: false, + }, + } } }); @@ -121,4 +130,5 @@ module.exports = function(grunt) { // set default tasks to run when grunt is called without parameters // http://gruntjs.com/api/grunt.task grunt.registerTask('default', ['concat', 'jsbeautifier', 'uglify']); + grunt.loadNpmTasks('grunt-contrib-watch'); }; diff --git a/MultiStreamsMixer/MultiStreamsMixer.js b/MultiStreamsMixer/MultiStreamsMixer.js index ecb4a5a1..fef6801e 100644 --- a/MultiStreamsMixer/MultiStreamsMixer.js +++ b/MultiStreamsMixer/MultiStreamsMixer.js @@ -1,4 +1,4 @@ -// Last time updated: 2018-03-02 2:56:28 AM UTC +// Last time updated: 2018-10-26 9:05:36 AM UTC // ________________________ // MultiStreamsMixer v1.0.5 @@ -77,36 +77,6 @@ function MultiStreamsMixer(arrayOfMediaStreams) { /*global MediaStream:true */ if (typeof MediaStream !== 'undefined') { - if (!('getVideoTracks' in MediaStream.prototype)) { - MediaStream.prototype.getVideoTracks = function() { - if (!this.getTracks) { - return []; - } - - var tracks = []; - this.getTracks.forEach(function(track) { - if (track.kind.toString().indexOf('video') !== -1) { - tracks.push(track); - } - }); - return tracks; - }; - - MediaStream.prototype.getAudioTracks = function() { - if (!this.getTracks) { - return []; - } - - var tracks = []; - this.getTracks.forEach(function(track) { - if (track.kind.toString().indexOf('audio') !== -1) { - tracks.push(track); - } - }); - return tracks; - }; - } - // override "stop" method for all browsers if (typeof MediaStream.prototype.stop === 'undefined') { MediaStream.prototype.stop = function() { @@ -125,18 +95,13 @@ function MultiStreamsMixer(arrayOfMediaStreams) { Storage.AudioContext = webkitAudioContext; } - function setSrcObject(stream, element, ignoreCreateObjectURL) { - if ('createObjectURL' in URL && !ignoreCreateObjectURL) { - try { - element.src = URL.createObjectURL(stream); - } catch (e) { - setSrcObject(stream, element, true); - return; - } - } else if ('srcObject' in element) { + function setSrcObject(stream, element) { + if ('srcObject' in element) { element.srcObject = stream; } else if ('mozSrcObject' in element) { element.mozSrcObject = stream; + } else if ('createObjectURL' in URL) { + element.src = URL.createObjectURL(stream); } else { alert('createObjectURL/srcObject both are not supported.'); } @@ -273,7 +238,9 @@ function MultiStreamsMixer(arrayOfMediaStreams) { var mixedAudioStream = getMixedAudioStream(); if (mixedAudioStream) { - mixedAudioStream.getAudioTracks().forEach(function(track) { + mixedAudioStream.getTracks().filter(function(t) { + return t.kind === 'audio'; + }).forEach(function(track) { mixedVideoStream.addTrack(track); }); } @@ -303,7 +270,9 @@ function MultiStreamsMixer(arrayOfMediaStreams) { var videoStream = new MediaStream(); - capturedStream.getVideoTracks().forEach(function(track) { + capturedStream.getTracks().filter(function(t) { + return t.kind === 'video'; + }).forEach(function(track) { videoStream.addTrack(track); }); @@ -330,7 +299,9 @@ function MultiStreamsMixer(arrayOfMediaStreams) { var audioTracksLength = 0; arrayOfMediaStreams.forEach(function(stream) { - if (!stream.getAudioTracks().length) { + if (!stream.getTracks().filter(function(t) { + return t.kind === 'audio'; + }).length) { return; } @@ -384,13 +355,17 @@ function MultiStreamsMixer(arrayOfMediaStreams) { arrayOfMediaStreams.concat(streams); streams.forEach(function(stream) { - if (stream.getVideoTracks().length) { + if (stream.getTracks().filter(function(t) { + return t.kind === 'video'; + }).length) { var video = getVideo(stream); video.stream = stream; videos.push(video); } - if (stream.getAudioTracks().length && self.audioContext) { + if (stream.getTracks().filter(function(t) { + return t.kind === 'audio'; + }).length && self.audioContext) { var audioSource = self.audioContext.createMediaStreamSource(stream); audioSource.connect(self.audioDestination); self.audioSources.push(audioSource); @@ -447,7 +422,9 @@ function MultiStreamsMixer(arrayOfMediaStreams) { // via: @adrian-ber streams.forEach(function(stream) { - if (!stream.getVideoTracks().length) { + if (!stream.getTracks().filter(function(t) { + return t.kind === 'video'; + }).length) { return; } diff --git a/MultiStreamsMixer/MultiStreamsMixer.min.js b/MultiStreamsMixer/MultiStreamsMixer.min.js index 74ffd040..2b4f3fe4 100644 --- a/MultiStreamsMixer/MultiStreamsMixer.min.js +++ b/MultiStreamsMixer/MultiStreamsMixer.min.js @@ -1,4 +1,4 @@ -// Last time updated: 2018-03-02 2:56:28 AM UTC +// Last time updated: 2018-10-26 9:05:36 AM UTC // ________________________ // MultiStreamsMixer v1.0.5 @@ -10,4 +10,4 @@ // MIT License - www.WebRTC-Experiment.com/licence // -------------------------------------------------- -function MultiStreamsMixer(arrayOfMediaStreams){function setSrcObject(stream,element,ignoreCreateObjectURL){if("createObjectURL"in URL&&!ignoreCreateObjectURL)try{element.src=URL.createObjectURL(stream)}catch(e){return void setSrcObject(stream,element,!0)}else"srcObject"in element?element.srcObject=stream:"mozSrcObject"in element?element.mozSrcObject=stream:alert("createObjectURL/srcObject both are not supported.")}function drawVideosToCanvas(){if(!isStopDrawingFrames){var videosLength=videos.length,fullcanvas=!1,remaining=[];if(videos.forEach(function(video){video.stream||(video.stream={}),video.stream.fullcanvas?fullcanvas=video:remaining.push(video)}),fullcanvas)canvas.width=fullcanvas.stream.width,canvas.height=fullcanvas.stream.height;else if(remaining.length){canvas.width=videosLength>1?2*remaining[0].width:remaining[0].width;var height=1;3!==videosLength&&4!==videosLength||(height=2),5!==videosLength&&6!==videosLength||(height=3),7!==videosLength&&8!==videosLength||(height=4),9!==videosLength&&10!==videosLength||(height=5),canvas.height=remaining[0].height*height}else canvas.width=self.width||360,canvas.height=self.height||240;fullcanvas&&fullcanvas instanceof HTMLVideoElement&&drawImage(fullcanvas),remaining.forEach(function(video,idx){drawImage(video,idx)}),setTimeout(drawVideosToCanvas,self.frameInterval)}}function drawImage(video,idx){if(!isStopDrawingFrames){var x=0,y=0,width=video.width,height=video.height;1===idx&&(x=video.width),2===idx&&(y=video.height),3===idx&&(x=video.width,y=video.height),4===idx&&(y=2*video.height),5===idx&&(x=video.width,y=2*video.height),6===idx&&(y=3*video.height),7===idx&&(x=video.width,y=3*video.height),"undefined"!=typeof video.stream.left&&(x=video.stream.left),"undefined"!=typeof video.stream.top&&(y=video.stream.top),"undefined"!=typeof video.stream.width&&(width=video.stream.width),"undefined"!=typeof video.stream.height&&(height=video.stream.height),context.drawImage(video,x,y,width,height),"function"==typeof video.stream.onRender&&video.stream.onRender(context,x,y,width,height,idx)}}function getMixedStream(){isStopDrawingFrames=!1;var mixedVideoStream=getMixedVideoStream(),mixedAudioStream=getMixedAudioStream();mixedAudioStream&&mixedAudioStream.getAudioTracks().forEach(function(track){mixedVideoStream.addTrack(track)});var fullcanvas;return arrayOfMediaStreams.forEach(function(stream){stream.fullcanvas&&(fullcanvas=!0)}),mixedVideoStream}function getMixedVideoStream(){resetVideoStreams();var capturedStream;"captureStream"in canvas?capturedStream=canvas.captureStream():"mozCaptureStream"in canvas?capturedStream=canvas.mozCaptureStream():self.disableLogs||console.error("Upgrade to latest Chrome or otherwise enable this flag: chrome://flags/#enable-experimental-web-platform-features");var videoStream=new MediaStream;return capturedStream.getVideoTracks().forEach(function(track){videoStream.addTrack(track)}),canvas.stream=videoStream,videoStream}function getMixedAudioStream(){Storage.AudioContextConstructor||(Storage.AudioContextConstructor=new Storage.AudioContext),self.audioContext=Storage.AudioContextConstructor,self.audioSources=[],self.useGainNode===!0&&(self.gainNode=self.audioContext.createGain(),self.gainNode.connect(self.audioContext.destination),self.gainNode.gain.value=0);var audioTracksLength=0;if(arrayOfMediaStreams.forEach(function(stream){if(stream.getAudioTracks().length){audioTracksLength++;var audioSource=self.audioContext.createMediaStreamSource(stream);self.useGainNode===!0&&audioSource.connect(self.gainNode),self.audioSources.push(audioSource)}}),audioTracksLength)return self.audioDestination=self.audioContext.createMediaStreamDestination(),self.audioSources.forEach(function(audioSource){audioSource.connect(self.audioDestination)}),self.audioDestination.stream}function getVideo(stream){var video=document.createElement("video");return setSrcObject(stream,video),video.muted=!0,video.volume=0,video.width=stream.width||self.width||360,video.height=stream.height||self.height||240,video.play(),video}function resetVideoStreams(streams){videos=[],streams=streams||arrayOfMediaStreams,streams.forEach(function(stream){if(stream.getVideoTracks().length){var video=getVideo(stream);video.stream=stream,videos.push(video)}})}var videos=[],isStopDrawingFrames=!1,canvas=document.createElement("canvas"),context=canvas.getContext("2d");canvas.style="opacity:0;position:absolute;z-index:-1;top: -100000000;left:-1000000000; margin-top:-1000000000;margin-left:-1000000000;",(document.body||document.documentElement).appendChild(canvas),this.disableLogs=!1,this.frameInterval=10,this.width=360,this.height=240,this.useGainNode=!0;var self=this,AudioContext=window.AudioContext;"undefined"==typeof AudioContext&&("undefined"!=typeof webkitAudioContext&&(AudioContext=webkitAudioContext),"undefined"!=typeof mozAudioContext&&(AudioContext=mozAudioContext));var URL=window.URL;"undefined"==typeof URL&&"undefined"!=typeof webkitURL&&(URL=webkitURL),"undefined"!=typeof navigator&&"undefined"==typeof navigator.getUserMedia&&("undefined"!=typeof navigator.webkitGetUserMedia&&(navigator.getUserMedia=navigator.webkitGetUserMedia),"undefined"!=typeof navigator.mozGetUserMedia&&(navigator.getUserMedia=navigator.mozGetUserMedia));var MediaStream=window.MediaStream;"undefined"==typeof MediaStream&&"undefined"!=typeof webkitMediaStream&&(MediaStream=webkitMediaStream),"undefined"!=typeof MediaStream&&("getVideoTracks"in MediaStream.prototype||(MediaStream.prototype.getVideoTracks=function(){if(!this.getTracks)return[];var tracks=[];return this.getTracks.forEach(function(track){track.kind.toString().indexOf("video")!==-1&&tracks.push(track)}),tracks},MediaStream.prototype.getAudioTracks=function(){if(!this.getTracks)return[];var tracks=[];return this.getTracks.forEach(function(track){track.kind.toString().indexOf("audio")!==-1&&tracks.push(track)}),tracks}),"undefined"==typeof MediaStream.prototype.stop&&(MediaStream.prototype.stop=function(){this.getTracks().forEach(function(track){track.stop()})}));var Storage={};"undefined"!=typeof AudioContext?Storage.AudioContext=AudioContext:"undefined"!=typeof webkitAudioContext&&(Storage.AudioContext=webkitAudioContext),this.startDrawingFrames=function(){drawVideosToCanvas()},this.appendStreams=function(streams){if(!streams)throw"First parameter is required.";streams instanceof Array||(streams=[streams]),arrayOfMediaStreams.concat(streams),streams.forEach(function(stream){if(stream.getVideoTracks().length){var video=getVideo(stream);video.stream=stream,videos.push(video)}if(stream.getAudioTracks().length&&self.audioContext){var audioSource=self.audioContext.createMediaStreamSource(stream);audioSource.connect(self.audioDestination),self.audioSources.push(audioSource)}})},this.releaseStreams=function(){videos=[],isStopDrawingFrames=!0,self.gainNode&&(self.gainNode.disconnect(),self.gainNode=null),self.audioSources.length&&(self.audioSources.forEach(function(source){source.disconnect()}),self.audioSources=[]),self.audioDestination&&(self.audioDestination.disconnect(),self.audioDestination=null),self.audioContext&&self.audioContext.close(),self.audioContext=null,context.clearRect(0,0,canvas.width,canvas.height),canvas.stream&&(canvas.stream.stop(),canvas.stream=null)},this.resetVideoStreams=function(streams){!streams||streams instanceof Array||(streams=[streams]),resetVideoStreams(streams)},this.name="MultiStreamsMixer",this.toString=function(){return this.name},this.getMixedStream=getMixedStream} \ No newline at end of file +function MultiStreamsMixer(arrayOfMediaStreams){function setSrcObject(stream,element){"srcObject"in element?element.srcObject=stream:"mozSrcObject"in element?element.mozSrcObject=stream:"createObjectURL"in URL?element.src=URL.createObjectURL(stream):alert("createObjectURL/srcObject both are not supported.")}function drawVideosToCanvas(){if(!isStopDrawingFrames){var videosLength=videos.length,fullcanvas=!1,remaining=[];if(videos.forEach(function(video){video.stream||(video.stream={}),video.stream.fullcanvas?fullcanvas=video:remaining.push(video)}),fullcanvas)canvas.width=fullcanvas.stream.width,canvas.height=fullcanvas.stream.height;else if(remaining.length){canvas.width=videosLength>1?2*remaining[0].width:remaining[0].width;var height=1;3!==videosLength&&4!==videosLength||(height=2),5!==videosLength&&6!==videosLength||(height=3),7!==videosLength&&8!==videosLength||(height=4),9!==videosLength&&10!==videosLength||(height=5),canvas.height=remaining[0].height*height}else canvas.width=self.width||360,canvas.height=self.height||240;fullcanvas&&fullcanvas instanceof HTMLVideoElement&&drawImage(fullcanvas),remaining.forEach(function(video,idx){drawImage(video,idx)}),setTimeout(drawVideosToCanvas,self.frameInterval)}}function drawImage(video,idx){if(!isStopDrawingFrames){var x=0,y=0,width=video.width,height=video.height;1===idx&&(x=video.width),2===idx&&(y=video.height),3===idx&&(x=video.width,y=video.height),4===idx&&(y=2*video.height),5===idx&&(x=video.width,y=2*video.height),6===idx&&(y=3*video.height),7===idx&&(x=video.width,y=3*video.height),"undefined"!=typeof video.stream.left&&(x=video.stream.left),"undefined"!=typeof video.stream.top&&(y=video.stream.top),"undefined"!=typeof video.stream.width&&(width=video.stream.width),"undefined"!=typeof video.stream.height&&(height=video.stream.height),context.drawImage(video,x,y,width,height),"function"==typeof video.stream.onRender&&video.stream.onRender(context,x,y,width,height,idx)}}function getMixedStream(){isStopDrawingFrames=!1;var mixedVideoStream=getMixedVideoStream(),mixedAudioStream=getMixedAudioStream();mixedAudioStream&&mixedAudioStream.getTracks().filter(function(t){return"audio"===t.kind}).forEach(function(track){mixedVideoStream.addTrack(track)});var fullcanvas;return arrayOfMediaStreams.forEach(function(stream){stream.fullcanvas&&(fullcanvas=!0)}),mixedVideoStream}function getMixedVideoStream(){resetVideoStreams();var capturedStream;"captureStream"in canvas?capturedStream=canvas.captureStream():"mozCaptureStream"in canvas?capturedStream=canvas.mozCaptureStream():self.disableLogs||console.error("Upgrade to latest Chrome or otherwise enable this flag: chrome://flags/#enable-experimental-web-platform-features");var videoStream=new MediaStream;return capturedStream.getTracks().filter(function(t){return"video"===t.kind}).forEach(function(track){videoStream.addTrack(track)}),canvas.stream=videoStream,videoStream}function getMixedAudioStream(){Storage.AudioContextConstructor||(Storage.AudioContextConstructor=new Storage.AudioContext),self.audioContext=Storage.AudioContextConstructor,self.audioSources=[],self.useGainNode===!0&&(self.gainNode=self.audioContext.createGain(),self.gainNode.connect(self.audioContext.destination),self.gainNode.gain.value=0);var audioTracksLength=0;if(arrayOfMediaStreams.forEach(function(stream){if(stream.getTracks().filter(function(t){return"audio"===t.kind}).length){audioTracksLength++;var audioSource=self.audioContext.createMediaStreamSource(stream);self.useGainNode===!0&&audioSource.connect(self.gainNode),self.audioSources.push(audioSource)}}),audioTracksLength)return self.audioDestination=self.audioContext.createMediaStreamDestination(),self.audioSources.forEach(function(audioSource){audioSource.connect(self.audioDestination)}),self.audioDestination.stream}function getVideo(stream){var video=document.createElement("video");return setSrcObject(stream,video),video.muted=!0,video.volume=0,video.width=stream.width||self.width||360,video.height=stream.height||self.height||240,video.play(),video}function resetVideoStreams(streams){videos=[],streams=streams||arrayOfMediaStreams,streams.forEach(function(stream){if(stream.getTracks().filter(function(t){return"video"===t.kind}).length){var video=getVideo(stream);video.stream=stream,videos.push(video)}})}var videos=[],isStopDrawingFrames=!1,canvas=document.createElement("canvas"),context=canvas.getContext("2d");canvas.style="opacity:0;position:absolute;z-index:-1;top: -100000000;left:-1000000000; margin-top:-1000000000;margin-left:-1000000000;",(document.body||document.documentElement).appendChild(canvas),this.disableLogs=!1,this.frameInterval=10,this.width=360,this.height=240,this.useGainNode=!0;var self=this,AudioContext=window.AudioContext;"undefined"==typeof AudioContext&&("undefined"!=typeof webkitAudioContext&&(AudioContext=webkitAudioContext),"undefined"!=typeof mozAudioContext&&(AudioContext=mozAudioContext));var URL=window.URL;"undefined"==typeof URL&&"undefined"!=typeof webkitURL&&(URL=webkitURL),"undefined"!=typeof navigator&&"undefined"==typeof navigator.getUserMedia&&("undefined"!=typeof navigator.webkitGetUserMedia&&(navigator.getUserMedia=navigator.webkitGetUserMedia),"undefined"!=typeof navigator.mozGetUserMedia&&(navigator.getUserMedia=navigator.mozGetUserMedia));var MediaStream=window.MediaStream;"undefined"==typeof MediaStream&&"undefined"!=typeof webkitMediaStream&&(MediaStream=webkitMediaStream),"undefined"!=typeof MediaStream&&"undefined"==typeof MediaStream.prototype.stop&&(MediaStream.prototype.stop=function(){this.getTracks().forEach(function(track){track.stop()})});var Storage={};"undefined"!=typeof AudioContext?Storage.AudioContext=AudioContext:"undefined"!=typeof webkitAudioContext&&(Storage.AudioContext=webkitAudioContext),this.startDrawingFrames=function(){drawVideosToCanvas()},this.appendStreams=function(streams){if(!streams)throw"First parameter is required.";streams instanceof Array||(streams=[streams]),arrayOfMediaStreams.concat(streams),streams.forEach(function(stream){if(stream.getTracks().filter(function(t){return"video"===t.kind}).length){var video=getVideo(stream);video.stream=stream,videos.push(video)}if(stream.getTracks().filter(function(t){return"audio"===t.kind}).length&&self.audioContext){var audioSource=self.audioContext.createMediaStreamSource(stream);audioSource.connect(self.audioDestination),self.audioSources.push(audioSource)}})},this.releaseStreams=function(){videos=[],isStopDrawingFrames=!0,self.gainNode&&(self.gainNode.disconnect(),self.gainNode=null),self.audioSources.length&&(self.audioSources.forEach(function(source){source.disconnect()}),self.audioSources=[]),self.audioDestination&&(self.audioDestination.disconnect(),self.audioDestination=null),self.audioContext&&self.audioContext.close(),self.audioContext=null,context.clearRect(0,0,canvas.width,canvas.height),canvas.stream&&(canvas.stream.stop(),canvas.stream=null)},this.resetVideoStreams=function(streams){!streams||streams instanceof Array||(streams=[streams]),resetVideoStreams(streams)},this.name="MultiStreamsMixer",this.toString=function(){return this.name},this.getMixedStream=getMixedStream} \ No newline at end of file diff --git a/MultiStreamsMixer/README.md b/MultiStreamsMixer/README.md index e8f12334..b72f9d5d 100644 --- a/MultiStreamsMixer/README.md +++ b/MultiStreamsMixer/README.md @@ -39,6 +39,12 @@ Or link specific build: ``` +Or install using NPM: + +```sh +npm install multistreamsmixer +``` + # How to mix audios? ```javascript @@ -81,7 +87,7 @@ btnAppendNewStreams.onclick = function() { btnStopScreenSharing.onclick = function() { // replace all old streams with this one // it will replace only video tracks - mixer.replaceStreams([cameraStreamOnly]); + mixer.resetVideoStreams([cameraStreamOnly]); }; ``` diff --git a/MultiStreamsMixer/bower.json b/MultiStreamsMixer/bower.json index f03b5cc3..a7f3041b 100644 --- a/MultiStreamsMixer/bower.json +++ b/MultiStreamsMixer/bower.json @@ -1,7 +1,7 @@ { "name": "multistreamsmixer", "description": "Pass multiple streams (e.g. screen+camera or multiple-cameras) and get single stream.", - "version": "1.0.4", + "version": "1.0.6", "authors": [ { "name": "Muaz Khan", diff --git a/MultiStreamsMixer/dev/append-streams.js b/MultiStreamsMixer/dev/append-streams.js index 259b6bde..ea46ab15 100644 --- a/MultiStreamsMixer/dev/append-streams.js +++ b/MultiStreamsMixer/dev/append-streams.js @@ -10,13 +10,17 @@ this.appendStreams = function(streams) { arrayOfMediaStreams.concat(streams); streams.forEach(function(stream) { - if (stream.getVideoTracks().length) { + if (stream.getTracks().filter(function(t) { + return t.kind === 'video'; + }).length) { var video = getVideo(stream); video.stream = stream; videos.push(video); } - if (stream.getAudioTracks().length && self.audioContext) { + if (stream.getTracks().filter(function(t) { + return t.kind === 'audio'; + }).length && self.audioContext) { var audioSource = self.audioContext.createMediaStreamSource(stream); audioSource.connect(self.audioDestination); self.audioSources.push(audioSource); diff --git a/MultiStreamsMixer/dev/cross-browser-declarations.js b/MultiStreamsMixer/dev/cross-browser-declarations.js index e77bab3e..33c89881 100644 --- a/MultiStreamsMixer/dev/cross-browser-declarations.js +++ b/MultiStreamsMixer/dev/cross-browser-declarations.js @@ -42,36 +42,6 @@ if (typeof MediaStream === 'undefined' && typeof webkitMediaStream !== 'undefine /*global MediaStream:true */ if (typeof MediaStream !== 'undefined') { - if (!('getVideoTracks' in MediaStream.prototype)) { - MediaStream.prototype.getVideoTracks = function() { - if (!this.getTracks) { - return []; - } - - var tracks = []; - this.getTracks.forEach(function(track) { - if (track.kind.toString().indexOf('video') !== -1) { - tracks.push(track); - } - }); - return tracks; - }; - - MediaStream.prototype.getAudioTracks = function() { - if (!this.getTracks) { - return []; - } - - var tracks = []; - this.getTracks.forEach(function(track) { - if (track.kind.toString().indexOf('audio') !== -1) { - tracks.push(track); - } - }); - return tracks; - }; - } - // override "stop" method for all browsers if (typeof MediaStream.prototype.stop === 'undefined') { MediaStream.prototype.stop = function() { @@ -90,18 +60,13 @@ if (typeof AudioContext !== 'undefined') { Storage.AudioContext = webkitAudioContext; } -function setSrcObject(stream, element, ignoreCreateObjectURL) { - if ('createObjectURL' in URL && !ignoreCreateObjectURL) { - try { - element.src = URL.createObjectURL(stream); - } catch (e) { - setSrcObject(stream, element, true); - return; - } - } else if ('srcObject' in element) { +function setSrcObject(stream, element) { + if ('srcObject' in element) { element.srcObject = stream; } else if ('mozSrcObject' in element) { element.mozSrcObject = stream; + } else if ('createObjectURL' in URL) { + element.src = URL.createObjectURL(stream); } else { alert('createObjectURL/srcObject both are not supported.'); } diff --git a/MultiStreamsMixer/dev/get-mixed-audio-stream.js b/MultiStreamsMixer/dev/get-mixed-audio-stream.js index c3a6104a..37ec3033 100644 --- a/MultiStreamsMixer/dev/get-mixed-audio-stream.js +++ b/MultiStreamsMixer/dev/get-mixed-audio-stream.js @@ -16,7 +16,9 @@ function getMixedAudioStream() { var audioTracksLength = 0; arrayOfMediaStreams.forEach(function(stream) { - if (!stream.getAudioTracks().length) { + if (!stream.getTracks().filter(function(t) { + return t.kind === 'audio'; + }).length) { return; } diff --git a/MultiStreamsMixer/dev/get-mixed-stream.js b/MultiStreamsMixer/dev/get-mixed-stream.js index 1de6c4e7..f231c3da 100644 --- a/MultiStreamsMixer/dev/get-mixed-stream.js +++ b/MultiStreamsMixer/dev/get-mixed-stream.js @@ -4,7 +4,9 @@ function getMixedStream() { var mixedAudioStream = getMixedAudioStream(); if (mixedAudioStream) { - mixedAudioStream.getAudioTracks().forEach(function(track) { + mixedAudioStream.getTracks().filter(function(t) { + return t.kind === 'audio'; + }).forEach(function(track) { mixedVideoStream.addTrack(track); }); } diff --git a/MultiStreamsMixer/dev/get-mixed-video-stream.js b/MultiStreamsMixer/dev/get-mixed-video-stream.js index 36e61032..974c7575 100644 --- a/MultiStreamsMixer/dev/get-mixed-video-stream.js +++ b/MultiStreamsMixer/dev/get-mixed-video-stream.js @@ -13,7 +13,9 @@ function getMixedVideoStream() { var videoStream = new MediaStream(); - capturedStream.getVideoTracks().forEach(function(track) { + capturedStream.getTracks().filter(function(t) { + return t.kind === 'video'; + }).forEach(function(track) { videoStream.addTrack(track); }); diff --git a/MultiStreamsMixer/dev/replace-streams.js b/MultiStreamsMixer/dev/replace-streams.js index 8b43dd98..5bd5393e 100644 --- a/MultiStreamsMixer/dev/replace-streams.js +++ b/MultiStreamsMixer/dev/replace-streams.js @@ -12,7 +12,9 @@ function resetVideoStreams(streams) { // via: @adrian-ber streams.forEach(function(stream) { - if (!stream.getVideoTracks().length) { + if (!stream.getTracks().filter(function(t) { + return t.kind === 'video'; + }).length) { return; } diff --git a/MultiStreamsMixer/index.html b/MultiStreamsMixer/index.html index d2b5f91a..d073ae46 100644 --- a/MultiStreamsMixer/index.html +++ b/MultiStreamsMixer/index.html @@ -1,4 +1,4 @@ - + - - @@ -94,25 +93,14 @@


    -
    +
    +

    + +
    -``` - -```javascript -var rtcDataConnection = new RTCDataConnection({ - onmessage: function (data) { - console.log(data); - }, - openSignalingChannel: function (config) { - var socket = io.connect('http://your-site:8888'); - socket.channel = config.channel || 'WebRTC-RTCDataConnection'; - socket.on('message', config.onmessage); - - socket.send = function (data) { - socket.emit('message', data); - }; - - if (config.onopen) setTimeout(config.onopen, 1); - return socket; - }, - - // 'one-to-one' || 'one-to-many' || 'many-to-many' - // default: 'many-to-many' ------- it is optional - direction: 'one-to-many' -}); - -// Only session initiator should call below line; -// All other 10000 room participants don't need to call "initDataConnection"! -rtcDataConnection.initDataConnection(); - -// to send file/data /or text -rtcDataConnection.send( file || data || 'text' ); -``` - -= - -#### Features - -1. Share files of any size — directly -2. Share text messages of any length -3. Share any kind of data — directly - -= - -#### Read [RTCDataConnection Documentation](http://bit.ly/RTCDataConnection) - -= - -#### Browser Support -[RTCDataConnection](http://bit.ly/RTCDataConnection) works fine on following web-browsers: - -| Browser | Support | -| ------------- |:-------------:| -| Firefox | [Stable](http://www.mozilla.org/en-US/firefox/new/) / [Aurora](http://www.mozilla.org/en-US/firefox/aurora/) / [Nightly](http://nightly.mozilla.org/) | -| Google Chrome | [Stable](https://www.google.com/intl/en_uk/chrome/browser/) / [Canary](https://www.google.com/intl/en/chrome/browser/canary.html) / [Beta](https://www.google.com/intl/en/chrome/browser/beta.html) / [Dev](https://www.google.com/intl/en/chrome/browser/index.html?extra=devchannel#eula) | - -= - -#### License - -[RTCDataConnection](http://bit.ly/RTCDataConnection) is released under [MIT licence](https://www.webrtc-experiment.com/licence/) . Copyright (c) 2013 [Muaz Khan](https://plus.google.com/100325991024054712503). diff --git a/RTCDataConnection/RTCDataConnection-Manual-Test.html b/RTCDataConnection/RTCDataConnection-Manual-Test.html deleted file mode 100644 index 711bddf3..00000000 --- a/RTCDataConnection/RTCDataConnection-Manual-Test.html +++ /dev/null @@ -1,282 +0,0 @@ - - - - - Manual RTCDataConnection Test ® Muaz Khan - - - - - - - - - - - - - - - - -
    - â†HOME -

    - Manual RTCDataConnection Test / Other Demos -

    - -

    - Copyright © 2013 Muaz Khan<@muazkh>. -

    -
    -
    -
    - -
    - - -
    -
    - -
    -
    - - - - - -

    -

    How to use RTCDataConnection? Documentation

    -
    -<script src="https://bit.ly/RTCDataConnection-v1-0"></script>
    -var rtcDataConnection = new RTCDataConnection({
    -    onmessage: function (data) {
    -        console.log(data);
    -    },
    -    openSignalingChannel: function (config) {
    -        // How to use your own socket.io implementation?
    -    },
    -	
    -    // 'one-to-one' or 'one-to-many' or 'many-to-many'
    -    direction: 'one-to-many'
    -});
    -
    -// Only session initiator should call below line; 
    -// All other 10000 room participants don't need to call "initDataConnection"!
    -rtcDataConnection.initDataConnection();
    -
    -// to send file/data /or text
    -var file = inputFile.files[0];
    -rtcDataConnection.send( file );
    -
    -rtcDataConnection.send( data );
    -rtcDataConnection.send( 'text' );
    -
    - -

    - - - - -
    -
    - -
    - -
    -
    - - - - - \ No newline at end of file diff --git a/RTCDataConnection/RTCDataConnection-Simple-Test.html b/RTCDataConnection/RTCDataConnection-Simple-Test.html deleted file mode 100644 index 25c21368..00000000 --- a/RTCDataConnection/RTCDataConnection-Simple-Test.html +++ /dev/null @@ -1,260 +0,0 @@ - - - - - Simplest RTCDataConnection Test ® Muaz Khan - - - - - - - - - - - - - - - - -
    - â†HOME -

    - Simplest RTCDataConnection Test / Other Demos -

    - -

    - Copyright © 2013 Muaz Khan<@muazkh>. -

    - -
    -
    -
    - -
    - - - - - - -

    -

    How to use RTCDataConnection? Documentation

    -
    -<script src="https://bit.ly/RTCDataConnection-v1-0"></script>
    -var rtcDataConnection = new RTCDataConnection({
    -    onmessage: function (data) {
    -        console.log(data);
    -    },
    -    openSignalingChannel: function (config) {
    -        // How to use your own socket.io implementation?
    -    },
    -	
    -    // 'one-to-one' or 'one-to-many' or 'many-to-many'
    -    direction: 'one-to-many'
    -});
    -
    -// Only session initiator should call below line; 
    -// All other 10000 room participants don't need to call "initDataConnection"!
    -rtcDataConnection.initDataConnection();
    -
    -// to send file/data /or text
    -var file = inputFile.files[0];
    -rtcDataConnection.send( file );
    -
    -rtcDataConnection.send( data );
    -rtcDataConnection.send( 'text' );
    -
    - -

    - - - - -
    -
    - -
    - -
    -
    - - - - - \ No newline at end of file diff --git a/RTCDataConnection/RTCDataConnection-Test.html b/RTCDataConnection/RTCDataConnection-Test.html deleted file mode 100644 index da188f6b..00000000 --- a/RTCDataConnection/RTCDataConnection-Test.html +++ /dev/null @@ -1,262 +0,0 @@ - - - - - RTCDataConnection Test ® Muaz Khan - - - - - - - - - - - - - - - - -
    - â†HOME -

    - RTCDataConnection Test / Other Demos -

    - -

    - Copyright © 2013 Muaz Khan<@muazkh>. -

    -
    -
    -
    - -
    - - - - - - -

    -

    How to use RTCDataConnection? Documentation

    -
    -<script src="https://bit.ly/RTCDataConnection-v1-0"></script>
    -var rtcDataConnection = new RTCDataConnection({
    -    onmessage: function (data) {
    -        console.log(data);
    -    },
    -    openSignalingChannel: function (config) {
    -        // How to use your own socket.io implementation?
    -    },
    -	
    -    // 'one-to-one' or 'one-to-many' or 'many-to-many'
    -    direction: 'one-to-many'
    -});
    -
    -// Only session initiator should call below line; 
    -// All other 10000 room participants don't need to call "initDataConnection"!
    -rtcDataConnection.initDataConnection();
    -
    -// to send file/data /or text
    -var file = inputFile.files[0];
    -rtcDataConnection.send( file );
    -
    -rtcDataConnection.send( data );
    -rtcDataConnection.send( 'text' );
    -
    - -

    - - - - -
    -
    - -
    - -
    -
    - - - - - \ No newline at end of file diff --git a/RTCDataConnection/RTCDataConnection-v1.0.js b/RTCDataConnection/RTCDataConnection-v1.0.js deleted file mode 100644 index de21e504..00000000 --- a/RTCDataConnection/RTCDataConnection-v1.0.js +++ /dev/null @@ -1,737 +0,0 @@ -/* MIT License: https://webrtc-experiment.appspot.com/licence/ - 2013, Muaz Khan--[ github.com/muaz-khan ] - - For documentation and examples: http://bit.ly/RTCDataConnection */ -function RTCDataConnection(configuration) { - var config = { - openSocket: configuration.openSignalingChannel, - onRoomFound: function (room) { - if (configuration.onNewConnection) - configuration.onNewConnection({ - id: room.broadcaster, - session: room.roomToken - }); - else - ConnectorUI.joinRoom({ - roomToken: room.roomToken, - joinUser: room.broadcaster - }); - }, - onChannelOpened: function (channel) { - console.debug('WebRTC Data Connection Opened.'); - if (configuration.onopen) configuration.onopen(channel); - }, - onChannelMessage: function (data) { - if (!data.size) data = JSON.parse(data); - - if (data.type === 'text') - textReceiver.receive(data, configuration.onmessage); - else if (data.size || data.type === 'file') - fileReceiver.receive(data, config); - else if (configuration.onmessage) - configuration.onmessage(data); - }, - direction: configuration.direction || 'many-to-many', - onChannelClosed: function (e) { - if (configuration.onclose) configuration.onclose(e); - }, - onChannelError: function (e) { - if (configuration.onerror) configuration.onerror(e); - }, - onFileReceived: function (fileName) { - console.debug('File <' + fileName + '> received successfully.'); - if (configuration.onFileReceived) configuration.onFileReceived(fileName); - }, - getFileStats: function (data) { - console.log(data.items + ' items remaining.'); - if (configuration.getFileStats) configuration.getFileStats(data); - } - }; - - var ConnectorUI = new Connector(config), - fileReceiver = new FileReceiver(), - textReceiver = new TextReceiver(); - - function joinUser(user) { - if (!user || !user.id || !user.session) throw 'invalid data passed.'; - ConnectorUI.joinRoom({ - roomToken: user.session, - joinUser: user.id - }); - } - - return { - initDataConnection: function () { - ConnectorUI.createRoom(); - }, - connect: joinUser, - send: function (data) { - if (!data) throw 'No file, data or text message to share.'; - if (data.size) FileSender.send({ - file: data, - channel: ConnectorUI, - onFileSent: configuration.onFileSent - }); - else - TextSender.send({ - text: data, - channel: ConnectorUI - }); - } - }; -} - -window.moz = !! navigator.mozGetUserMedia; - -/* RTCPeerConnection is a wrapper for RTCWeb APIs */ -function RTCPeerConnection(options) { - var w = window, - PeerConnection = w.mozRTCPeerConnection || w.webkitRTCPeerConnection, - SessionDescription = w.mozRTCSessionDescription || w.RTCSessionDescription, - IceCandidate = w.mozRTCIceCandidate || w.RTCIceCandidate; - - var STUN = { - iceServers: [{ - url: !moz ? 'stun:stun.l.google.com:19302' : 'stun:23.21.150.121' - } - ] - }, - TURN = { - iceServers: [{ - url: 'turn:webrtc%40live.com@numb.viagenie.ca', - credential: 'muazkh' - } - ] - }; - - var optional = { - optional: [] - }; - - if (!moz) { - optional.optional = [{ - DtlsSrtpKeyAgreement: true - } - ]; - - if (options.onChannelMessage) - optional.optional = [{ - RtpDataChannels: true - } - ]; - } - - var peerConnection = new PeerConnection(location.search.indexOf('turn=true') !== -1 ? TURN : STUN, optional); - - var dataPorts = getPorts(); - openOffererChannel(); - - peerConnection.onicecandidate = onicecandidate; - - function onicecandidate(event) { - if (!event.candidate || !peerConnection) return; - if (options.onICE) options.onICE(event.candidate); - } - - var constraints = options.constraints || { - optional: [], - mandatory: { - OfferToReceiveAudio: !! moz, - OfferToReceiveVideo: !! moz - } - }; - - function createOffer() { - if (!options.onOfferSDP) return; - - peerConnection.createOffer(function (sessionDescription) { - peerConnection.setLocalDescription(sessionDescription); - options.onOfferSDP(sessionDescription); - }, null, constraints); - } - - function createAnswer() { - if (!options.onAnswerSDP) return; - - options.offerSDP = new SessionDescription(options.offerSDP); - peerConnection.setRemoteDescription(options.offerSDP); - - peerConnection.createAnswer(function (sessionDescription) { - peerConnection.setLocalDescription(sessionDescription); - options.onAnswerSDP(sessionDescription); - - /* signaling method MUST be faster; otherwise increase "300" */ - moz && options.onChannelMessage && setTimeout(function () { - peerConnection.connectDataConnection(dataPorts[0], dataPorts[1]); - }, 300); - }, null, constraints); - } - - if ((options.onChannelMessage && !moz) || !options.onChannelMessage) { - createOffer(); - createAnswer(); - } - - var channel; - - function openOffererChannel() { - if (!options.onChannelMessage || (moz && !options.onOfferSDP)) return; - - if (!moz) _openOffererChannel(); - else peerConnection.onconnection = _openOffererChannel; - - if (moz && !options.attachStream) { - navigator.mozGetUserMedia({ - audio: true, - fake: true - }, function (stream) { - peerConnection.addStream(stream); - createOffer(); - }, useless); - } - } - - function _openOffererChannel() { - channel = peerConnection.createDataChannel( - options.channel || 'RTCDataChannel', - moz ? {} : { - reliable: false - }); - setChannelEvents(); - } - - function setChannelEvents() { - channel.onmessage = function (event) { - if (options.onChannelMessage) options.onChannelMessage(event); - }; - - channel.onopen = function () { - if (options.onChannelOpened) options.onChannelOpened(channel); - }; - channel.onclose = function (event) { - if (options.onChannelClosed) options.onChannelClosed(event); - }; - channel.onerror = function (event) { - console.error(event); - if (options.onChannelError) options.onChannelError(event); - }; - } - - if (options.onAnswerSDP && moz) openAnswererChannel(); - - function openAnswererChannel() { - peerConnection.ondatachannel = function (_channel) { - channel = _channel; - channel.binaryType = 'blob'; - setChannelEvents(); - }; - - if (moz && !options.attachStream) { - navigator.mozGetUserMedia({ - audio: true, - fake: true - }, function (stream) { - peerConnection.addStream(stream); - createAnswer(); - }, useless); - } - } - - function useless() {} - - function getPorts(ports) { - if (!moz || !options.onChannelMessage) return false; - ports = ports || options.dataPorts || [5000, 5001]; - console.log('--------using data ports: ', ports[0], ' and ', ports[1]); - return ports; - } - - var video_constraints = { - mandatory: {}, - optional: [] - }; - - function getUserMedia(options) { - var n = navigator, - media; - n.getMedia = n.webkitGetUserMedia || n.mozGetUserMedia; - n.getMedia(options.constraints || { - audio: true, - video: video_constraints - }, streaming, options.onerror || function (e) { - console.error(e); - }); - - function streaming(stream) { - var video = options.video; - if (video) { - video[moz ? 'mozSrcObject' : 'src'] = moz ? stream : window.webkitURL.createObjectURL(stream); - video.play(); - } - options.onsuccess && options.onsuccess(stream); - media = stream; - } - - return media; - } - - return { - addAnswerSDP: function (sdp, _dataPorts) { - sdp = new SessionDescription(sdp); - peerConnection.setRemoteDescription(sdp, function () { - if (moz && options.onChannelMessage) { - var ports = getPorts(_dataPorts); - peerConnection.connectDataConnection(ports[1], ports[0]); - } - }); - }, - addICE: function (candidate) { - peerConnection.addIceCandidate(new IceCandidate({ - sdpMLineIndex: candidate.sdpMLineIndex, - candidate: candidate.candidate - })); - }, - - peer: peerConnection, - channel: channel, - sendData: function (message) { - channel && channel.send(message); - } - }; -} - -function Connector(config) { - var self = { - userToken: uniqueToken(), - userName: 'Anonymous' - }, - channels = '--', - isbroadcaster, - isGetNewRoom = true, - defaultSocket = {}, RTCDataChannels = []; - - function openDefaultSocket() { - defaultSocket = config.openSocket({ - onmessage: onDefaultSocketResponse - }); - } - - function onDefaultSocketResponse(response) { - if (response.userToken == self.userToken) return; - - if (isGetNewRoom && response.roomToken && response.broadcaster) config.onRoomFound(response); - - if (response.newParticipant) onNewParticipant(response.newParticipant); - - if (response.userToken && response.joinUser == self.userToken && response.participant && channels.indexOf(response.userToken) == -1) { - channels += response.userToken + '--'; - openSubSocket({ - isofferer: true, - channel: response.channel || response.userToken, - closeSocket: true - }); - } - } - - function getPort() { - return Math.random() * 1000 << 10; - } - - function openSubSocket(_config) { - if (!_config.channel) return; - var socketConfig = { - channel: _config.channel, - onmessage: socketResponse, - onopen: function () { - if (isofferer && !peer) initPeer(); - } - }; - - var socket = config.openSocket(socketConfig), - isofferer = _config.isofferer, - gotstream, - inner = {}, - dataPorts = [getPort(), getPort()], - peer; - - var peerConfig = { - onICE: function (candidate) { - socket.send({ - userToken: self.userToken, - candidate: { - sdpMLineIndex: candidate.sdpMLineIndex, - candidate: JSON.stringify(candidate.candidate) - } - }); - }, - onChannelOpened: onChannelOpened, - onChannelMessage: function (event) { - if(config.onChannelMessage) config.onChannelMessage(event.data); - } - }; - - function initPeer(offerSDP) { - if (!offerSDP) { - peerConfig.onOfferSDP = sendsdp; - } else { - peerConfig.offerSDP = offerSDP; - peerConfig.onAnswerSDP = sendsdp; - peerConfig.dataPorts = dataPorts; - } - - peer = RTCPeerConnection(peerConfig); - } - - function onChannelOpened(channel) { - RTCDataChannels[RTCDataChannels.length] = channel; - if (config.onChannelOpened) config.onChannelOpened(channel); - - if (config.direction === 'many-to-many' && isbroadcaster && channels.split('--').length > 3) { - /* broadcasting newly connected participant for video-conferencing! */ - defaultSocket.send({ - newParticipant: socket.channel, - userToken: self.userToken - }); - } - - /* closing subsocket here on the offerer side */ - if (_config.closeSocket) socket = null; - - window.isFirstConnectionOpened = gotstream = true; - } - - function sendsdp(sdp) { - sdp = JSON.stringify(sdp); - var part = parseInt(sdp.length / 3); - - var firstPart = sdp.slice(0, part), - secondPart = sdp.slice(part, sdp.length - 1), - thirdPart = ''; - - if (sdp.length > part + part) { - secondPart = sdp.slice(part, part + part); - thirdPart = sdp.slice(part + part, sdp.length); - } - - socket.send({ - userToken: self.userToken, - firstPart: firstPart, - - /* sending RTCDataChannel ports alongwith sdp */ - dataPorts: dataPorts - }); - - socket.send({ - userToken: self.userToken, - secondPart: secondPart - }); - - socket.send({ - userToken: self.userToken, - thirdPart: thirdPart - }); - } - - function socketResponse(response) { - if (response.userToken == self.userToken) return; - - if (response.firstPart || response.secondPart || response.thirdPart) { - if (response.dataPorts) inner.dataPorts = response.dataPorts; - if (response.firstPart) { - inner.firstPart = response.firstPart; - if (inner.secondPart && inner.thirdPart) selfInvoker(); - } - if (response.secondPart) { - inner.secondPart = response.secondPart; - if (inner.firstPart && inner.thirdPart) selfInvoker(); - } - - if (response.thirdPart) { - inner.thirdPart = response.thirdPart; - if (inner.firstPart && inner.secondPart) selfInvoker(); - } - } - - if (response.candidate && !gotstream) { - peer && peer.addICE({ - sdpMLineIndex: response.candidate.sdpMLineIndex, - candidate: JSON.parse(response.candidate.candidate) - }); - } - } - - var invokedOnce = false; - - function selfInvoker() { - if (invokedOnce) return; - - invokedOnce = true; - - inner.sdp = JSON.parse(inner.firstPart + inner.secondPart + inner.thirdPart); - - /* using random data ports to support wide connection on firefox! */ - if (isofferer) peer.addAnswerSDP(inner.sdp, inner.dataPorts); - else initPeer(inner.sdp); - } - } - - function startBroadcasting() { - defaultSocket.send({ - roomToken: self.roomToken, - broadcaster: self.userToken - }); - - if (config.direction === 'one-to-one') { - if (!window.isFirstConnectionOpened) setTimeout(startBroadcasting, 3000); - } else setTimeout(startBroadcasting, 3000); - } - - function onNewParticipant(channel) { - if (!channel || channels.indexOf(channel) != -1 || channel == self.userToken) return; - channels += channel + '--'; - - var new_channel = uniqueToken(); - openSubSocket({ - channel: new_channel, - closeSocket: true - }); - - defaultSocket.send({ - participant: true, - userToken: self.userToken, - joinUser: channel, - channel: new_channel - }); - } - - function uniqueToken() { - var s4 = function () { - return Math.floor(Math.random() * 0x10000).toString(16); - }; - return s4() + s4() + "-" + s4() + "-" + s4() + "-" + s4() + "-" + s4() + s4() + s4(); - } - - openDefaultSocket(); - return { - createRoom: function (_config) { - self.roomToken = uniqueToken(); - - isbroadcaster = true; - isGetNewRoom = false; - startBroadcasting(); - }, - joinRoom: function (_config) { - self.roomToken = _config.roomToken; - isGetNewRoom = false; - - openSubSocket({ - channel: self.userToken - }); - - defaultSocket.send({ - participant: true, - userToken: self.userToken, - joinUser: _config.joinUser - }); - }, - send: function (message) { - var channels = RTCDataChannels, - data, length = channels.length; - if (!length) return; - - if (moz && message.file) data = message.file; - else data = JSON.stringify(message); - - for (var i = 0; i < length; i++) - channels[i].send(data); - } - }; -} - -var FileSender = { - send: function (config) { - var channel = config.channel, - file = config.file; - - /* if firefox nightly: share file blob directly */ - if (moz) { - /* used on the receiver side to set received file name */ - channel.send({ - fileName: file.name, - type: 'file' - }); - - /* sending entire file at once */ - channel.send({ - file: file - }); - - if (config.onFileSent) config.onFileSent(file); - } - - /* if chrome */ - if (!moz) { - var reader = new window.FileReader(); - reader.readAsDataURL(file); - reader.onload = onReadAsDataURL; - } - - var packetSize = 1000, - textToTransfer = '', - numberOfPackets = 0, - packets = 0; - - function onReadAsDataURL(event, text) { - var data = { - type: 'file' - }; - - if (event) { - text = event.target.result; - numberOfPackets = packets = data.packets = parseInt(text.length / packetSize); - } - - if (config.getFileStats) - config.getFileStats({ - remaining: packets--, - length: numberOfPackets, - sent: numberOfPackets - packets - }); - - if (text.length > packetSize) data.message = text.slice(0, packetSize); - else { - data.message = text; - data.last = true; - data.name = file.name; - - if (config.onFileSent) config.onFileSent(file); - } - - channel.send(data); - - textToTransfer = text.slice(data.message.length); - - if (textToTransfer.length) - setTimeout(function () { - onReadAsDataURL(null, textToTransfer); - }, 500); - } - } -}; - -function FileReceiver() { - var content = [], - fileName = '', - packets = 0, - numberOfPackets = 0; - - function receive(data, config) { - /* if firefox nightly & file blob shared */ - if (moz) { - if (data.fileName) fileName = data.fileName; - if (data.size) { - var reader = new window.FileReader(); - reader.readAsDataURL(data); - reader.onload = function (event) { - FileSaver.SaveToDisk(event.target.result, fileName); - if (config.onFileReceived) config.onFileReceived(fileName); - }; - } - } - - if (!moz) { - if (data.packets) numberOfPackets = packets = parseInt(data.packets); - - if (config.getFileStats) - config.getFileStats({ - remaining: packets--, - length: numberOfPackets, - received: numberOfPackets - packets - }); - - content.push(data.message); - - if (data.last) { - FileSaver.SaveToDisk(content.join(''), data.name); - if (config.onFileReceived) config.onFileReceived(data.name); - content = []; - } - } - } - - return { - receive: receive - }; -} - -var FileSaver = { - SaveToDisk: function (fileUrl, fileName) { - var save = document.createElement("a"); - save.href = fileUrl; - save.target = "_blank"; - save.download = fileName || fileUrl; - - var evt = document.createEvent('MouseEvents'); - evt.initMouseEvent('click', true, true, window, 1, 0, 0, 0, 0, false, false, false, false, 0, null); - - save.dispatchEvent(evt); - - (window.URL || window.webkitURL).revokeObjectURL(save.href); - } -}; - -var TextSender = { - send: function (config) { - var channel = config.channel, - initialText = config.text - packetSize = 1000 /* chars */ , - textToTransfer = ''; - - if (typeof initialText !== 'string') initialText = JSON.stringify(initialText); - - if (moz || initialText.length <= packetSize) channel.send(config.text); - else sendText(initialText); - - function sendText(textMessage, text) { - var data = { - type: 'text' - }; - - if (textMessage) { - text = textMessage; - data.packets = parseInt(text.length / packetSize); - } - - if (text.length > packetSize) - data.message = text.slice(0, packetSize); - else { - data.message = text; - data.last = true; - } - - channel.send(data); - - textToTransfer = text.slice(data.message.length); - - if (textToTransfer.length) - setTimeout(function () { - sendText(null, textToTransfer); - }, 500); - } - } -}; - -function TextReceiver() { - var content = []; - - function receive(data, onmessage) { - content.push(data.message); - if (data.last) { - if (onmessage) onmessage(content.join('')); - content = []; - } - } - return { - receive: receive - }; -} \ No newline at end of file diff --git a/RTCMultiConnection-Server/.gitignore b/RTCMultiConnection-Server/.gitignore new file mode 100644 index 00000000..198587af --- /dev/null +++ b/RTCMultiConnection-Server/.gitignore @@ -0,0 +1,21 @@ +node_modules +bower_components + +make-tar.sh + +*.tar.gz +lib-cov + +.*.swp +._* +.DS_Store +.git +.hg +.npmrc +.lock-wscript +.svn +.wafpickle-* +config.gypi +CVS +npm-debug.log +rmc-debug.log \ No newline at end of file diff --git a/RTCMultiConnection-Server/.npmignore b/RTCMultiConnection-Server/.npmignore new file mode 100644 index 00000000..3523a2d1 --- /dev/null +++ b/RTCMultiConnection-Server/.npmignore @@ -0,0 +1,22 @@ +node_modules +bower_components + +make-tar.sh + +*.tar.gz +lib-cov +v2.2.2 + +.*.swp +._* +.DS_Store +.git +.hg +.npmrc +.lock-wscript +.svn +.wafpickle-* +config.gypi +CVS +npm-debug.log +rmc-debug.log \ No newline at end of file diff --git a/RTCMultiConnection-Server/LICENSE b/RTCMultiConnection-Server/LICENSE new file mode 100644 index 00000000..0c82fc35 --- /dev/null +++ b/RTCMultiConnection-Server/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018 Muaz Khan + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/RTCMultiConnection-Server/README.md b/RTCMultiConnection-Server/README.md new file mode 100644 index 00000000..17b5f68e --- /dev/null +++ b/RTCMultiConnection-Server/README.md @@ -0,0 +1,30 @@ + + +## RTCMultiConnection Socket.io Server + +[![npm](https://img.shields.io/npm/v/rtcmulticonnection-server.svg)](https://npmjs.org/package/rtcmulticonnection-server) [![downloads](https://img.shields.io/npm/dm/rtcmulticonnection-server.svg)](https://npmjs.org/package/rtcmulticonnection-server) + +**Installation Guide:** https://github.com/muaz-khan/RTCMultiConnection-Server/wiki + +Free servers: + +```javascript +connectin.socketURL = 'https://rtcmulticonnection.herokuapp.com:443/'; +connectin.socketURL = 'https://webrtcweb.com:9002/'; +``` + +## `config.json` + +* https://github.com/muaz-khan/RTCMultiConnection-Server/wiki/config.json + +## Integrate inside nodejs applications + +* https://github.com/muaz-khan/RTCMultiConnection-Server/wiki/Integrate-inside-nodejs-applications + +## Demos + +* https://github.com/muaz-khan/RTCMultiConnection + +## License + +[RTCMultiConnection Socket.io Server](https://github.com/muaz-khan/RTCMultiConnection-Server) is released under [MIT licence](https://github.com/muaz-khan/RTCMultiConnection/blob/master/LICENSE.md) . Copyright (c) [Muaz Khan](https://MuazKhan.com/). diff --git a/RTCMultiConnection-Server/bower.json b/RTCMultiConnection-Server/bower.json new file mode 100644 index 00000000..055b069a --- /dev/null +++ b/RTCMultiConnection-Server/bower.json @@ -0,0 +1,47 @@ +{ + "name": "rtcmulticonnection-server", + "description": "RTCMultiConnection socket.io server", + "version": "1.3.0", + "authors": [ + { + "name": "Muaz Khan", + "email": "muazkh@gmail.com", + "homepage": "https://muazkhan.com/" + } + ], + "main": "server.js", + "keywords": [ + "peer-to-peer", + "peer2peer", + "peer", + "webrtc", + "rtcmulticonnection", + "webrtc-library", + "library", + "javascript", + "chrome", + "firefox", + "opera", + "ie", + "internet-explorer", + "android", + "rtcweb", + "rtcmulticonnection.js", + "multirtc", + "webrtc-experiment", + "javascript-library", + "muaz", + "muaz-khan" + ], + "repository": { + "type": "git", + "url": "https://github.com/muaz-khan/RTCMultiConnection.git" + }, + "homepage": "https://rtcmulticonnection.herokuapp.com/demos/", + "ignore": [ + "**/.*", + "node_modules", + "test", + "tests" + ] +} diff --git a/RTCMultiConnection-Server/config.json b/RTCMultiConnection-Server/config.json new file mode 100644 index 00000000..0730bfa6 --- /dev/null +++ b/RTCMultiConnection-Server/config.json @@ -0,0 +1,17 @@ +{ + "socketURL": "/", + "dirPath": "", + "homePage": "/demos/index.html", + "socketMessageEvent": "RTCMultiConnection-Message", + "socketCustomEvent": "RTCMultiConnection-Custom-Message", + "port": "9001", + "enableLogs": "false", + "autoRebootServerOnFailure": "false", + "isUseHTTPs": "false", + "sslKey": "./fake-keys/privatekey.pem", + "sslCert": "./fake-keys/certificate.pem", + "sslCabundle": "", + "enableAdmin": "false", + "adminUserName": "username", + "adminPassword": "password" +} diff --git a/RTCMultiConnection-Server/fake-keys/certificate.pem b/RTCMultiConnection-Server/fake-keys/certificate.pem new file mode 100644 index 00000000..1041beec --- /dev/null +++ b/RTCMultiConnection-Server/fake-keys/certificate.pem @@ -0,0 +1,16 @@ +-----BEGIN CERTIFICATE----- +MIICjTCCAfYCCQC8xCdh8aBfxDANBgkqhkiG9w0BAQUFADCBijELMAkGA1UEBhMC +VVMxEzARBgNVBAgTCldhc2hpbmd0b24xETAPBgNVBAcTCFJpY2hsYW5kMQ0wCwYD +VQQKFAQmeWV0MQswCQYDVQQLFAImITEVMBMGA1UEAxMMTmF0aGFuIEZyaXR6MSAw +HgYJKoZIhvcNAQkBFhFuYXRoYW5AYW5keWV0Lm5ldDAeFw0xMTEwMTkwNjI2Mzha +Fw0xMTExMTgwNjI2MzhaMIGKMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGlu +Z3RvbjERMA8GA1UEBxMIUmljaGxhbmQxDTALBgNVBAoUBCZ5ZXQxCzAJBgNVBAsU +AiYhMRUwEwYDVQQDEwxOYXRoYW4gRnJpdHoxIDAeBgkqhkiG9w0BCQEWEW5hdGhh +bkBhbmR5ZXQubmV0MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDRKUxB1pLV +RQ/dcUEP1p1oTIg0GoEvMPl6s7kC2Mroyovn/FaCzsgvwYhuwIeA6qgYoNIkSkXM +QRmtfTpBvJNqM6A7jpUUmYuaUgqdrh5GZ5FGJjgAGIRJBWtovqxnCaHcmBYxlj0o +/nxDmzgK655WBso7nwpixrzbsV3x7ZG45QIDAQABMA0GCSqGSIb3DQEBBQUAA4GB +ALeMY0Og6SfSNXzvATyR1BYSjJCG19AwR/vafK4vB6ejta37TGEPOM66BdtxH8J7 +T3QuMki9Eqid0zPATOttTlAhBeDGzPOzD4ohJu55PwY0jTJ2+qFUiDKmmCuaUbC6 +JCt3LWcZMvkkMfsk1HgyUEKat/Lrs/iaVU6TDMFa52v5 +-----END CERTIFICATE----- diff --git a/RTCMultiConnection-Server/fake-keys/privatekey.pem b/RTCMultiConnection-Server/fake-keys/privatekey.pem new file mode 100644 index 00000000..922b0593 --- /dev/null +++ b/RTCMultiConnection-Server/fake-keys/privatekey.pem @@ -0,0 +1,15 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICXQIBAAKBgQDRKUxB1pLVRQ/dcUEP1p1oTIg0GoEvMPl6s7kC2Mroyovn/FaC +zsgvwYhuwIeA6qgYoNIkSkXMQRmtfTpBvJNqM6A7jpUUmYuaUgqdrh5GZ5FGJjgA +GIRJBWtovqxnCaHcmBYxlj0o/nxDmzgK655WBso7nwpixrzbsV3x7ZG45QIDAQAB +AoGBAJQzUtkDlJ6QhKE+8f6q7nVMZOWmMgqiBOMwHNMrkPpJKcCCRzoAEk/kLSts +N5bcraZlrQARsEr9hZgrtu+FEl1ROdKc6B3bJ5B6FigwY7m8/Z3+YdgwqV6NJGQk +3twY4PoJEdeZ7GX2QnX8RDjyFvLaZ12jiDic30Nrn1gwvOCxAkEA9Dp5r9yg4DT/ +V4SE5+NPCJmeV7zwfW3XUQHWD4TaFbOCjnjWB/BnrrjXhvd3VNzwajrJvqq/UiM4 +bAG4VLz0CwJBANs+IYm3tYfeP5YsYJVMOJ5TcOAZ3T9jMF+QC9/ObwepW4D1woRr +rCYxe01JyZpqqWnfeIUoJ70QL9uP8AgTrM8CQFFqGNymKL71C9XJ6GBA5zzPsPhA +lM7LSgbIHOrJd8XaNIB4CalV28pj9f0ZC5+vkzlmZZB47RRdh1aB8EfXQWcCQGa8 +KI8WLNRsCrPeO6v6OZXHV99Lf2eSnTpKj6XiYBjg/WXiw7G1mseS7Ep9RyE61gQs +mZccB/MKQMLMIhhGz/UCQQDog5KBVaMhwrw1hwZ5gDyZs2YrE75522BnAU1ajQj+ +VmTkcBwCtfnbXsWcHnYQnLlvz2Bi9ov2JncmJ5F1kiIw +-----END RSA PRIVATE KEY----- diff --git a/RTCMultiConnection-Server/logs.json b/RTCMultiConnection-Server/logs.json new file mode 100644 index 00000000..9e26dfee --- /dev/null +++ b/RTCMultiConnection-Server/logs.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/RTCMultiConnection/node_scripts/BASH_COLORS_HELPER.js b/RTCMultiConnection-Server/node_scripts/BASH_COLORS_HELPER.js similarity index 100% rename from RTCMultiConnection/node_scripts/BASH_COLORS_HELPER.js rename to RTCMultiConnection-Server/node_scripts/BASH_COLORS_HELPER.js diff --git a/RTCMultiConnection-Server/node_scripts/CONST_STRINGS.js b/RTCMultiConnection-Server/node_scripts/CONST_STRINGS.js new file mode 100644 index 00000000..471f136e --- /dev/null +++ b/RTCMultiConnection-Server/node_scripts/CONST_STRINGS.js @@ -0,0 +1,17 @@ +// Muaz Khan - www.MuazKhan.com +// MIT License - www.WebRTC-Experiment.com/licence +// Documentation - github.com/muaz-khan/RTCMultiConnection + +var CONST_STRINGS = { + ROOM_NOT_AVAILABLE: 'Room not available', + INVALID_PASSWORD: 'Invalid password', + USERID_NOT_AVAILABLE: 'User ID does not exist', + ROOM_PERMISSION_DENIED: 'Room permission denied', + ROOM_FULL: 'Room full', + DID_NOT_JOIN_ANY_ROOM: 'Did not join any room yet', + INVALID_SOCKET: 'Invalid socket', + PUBLIC_IDENTIFIER_MISSING: 'publicRoomIdentifier is required', + INVALID_ADMIN_CREDENTIAL: 'Invalid username or password attempted' +}; + +module.exports = exports = CONST_STRINGS; diff --git a/RTCMultiConnection/node_scripts/Scalable-Broadcast.js b/RTCMultiConnection-Server/node_scripts/Scalable-Broadcast.js similarity index 83% rename from RTCMultiConnection/node_scripts/Scalable-Broadcast.js rename to RTCMultiConnection-Server/node_scripts/Scalable-Broadcast.js index ab9f654e..46d76dde 100644 --- a/RTCMultiConnection/node_scripts/Scalable-Broadcast.js +++ b/RTCMultiConnection-Server/node_scripts/Scalable-Broadcast.js @@ -3,18 +3,11 @@ // Documentation - github.com/muaz-khan/RTCMultiConnection // pushLogs is used to write error logs into logs.json -var pushLogs = function(name, error) { - console.log(name, error); -}; -try { - pushLogs = require('./pushLogs.js'); -} catch (e) { - console.log('Unable to read pushLogs.js', e); -} +var pushLogs = require('./pushLogs.js'); var users = {}; -module.exports = exports = function(socket, maxRelayLimitPerUser) { +module.exports = exports = function(root, socket, maxRelayLimitPerUser) { try { maxRelayLimitPerUser = parseInt(maxRelayLimitPerUser) || 2; } catch (e) { @@ -80,7 +73,7 @@ module.exports = exports = function(socket, maxRelayLimitPerUser) { socket.emit('logs', 'You <' + user.userid + '> are now serving the broadcast.'); } } catch (e) { - pushLogs('join-broadcast', e); + pushLogs(root, 'join-broadcast', e); } }); @@ -105,7 +98,7 @@ module.exports = exports = function(socket, maxRelayLimitPerUser) { try { callback(!!users[userid] && users[userid].isBroadcastInitiator === true); } catch (e) { - pushLogs('check-broadcast-presence', e); + pushLogs(root, 'check-broadcast-presence', e); } }); @@ -200,7 +193,44 @@ module.exports = exports = function(socket, maxRelayLimitPerUser) { delete users[socket.userid]; } catch (e) { - pushLogs('scalable-broadcast-disconnect', e); + pushLogs(root, 'scalable-broadcast-disconnect', e); + } + }; + + return { + getUsers: function() { + try { + var list = []; + Object.keys(users).forEach(function(uid) { + var user = users[uid]; + if(!user) return; + + try { + var relayReceivers = []; + user.relayReceivers.forEach(function(s) { + relayReceivers.push(s.userid); + }); + + list.push({ + userid: user.userid, + broadcastId: user.broadcastId, + isBroadcastInitiator: user.isBroadcastInitiator, + maxRelayLimitPerUser: user.maxRelayLimitPerUser, + relayReceivers: relayReceivers, + receivingFrom: user.receivingFrom, + canRelay: user.canRelay, + typeOfStreams: user.typeOfStreams + }); + } + catch(e) { + pushLogs('getUsers', e); + } + }); + return list; + } + catch(e) { + pushLogs('getUsers', e); + } } }; }; @@ -218,7 +248,7 @@ function askNestedUsersToRejoin(relayReceivers) { }); } catch (e) { - pushLogs('askNestedUsersToRejoin', e); + pushLogs(root, 'askNestedUsersToRejoin', e); } } @@ -262,6 +292,6 @@ function getFirstAvailableBroadcaster(broadcastId, maxRelayLimitPerUser) { // so that each relaying user can distribute the bandwidth return broadcastInitiator; } catch (e) { - pushLogs('getFirstAvailableBroadcaster', e); + pushLogs(root, 'getFirstAvailableBroadcaster', e); } } diff --git a/RTCMultiConnection/node_scripts/Signaling-Server.js b/RTCMultiConnection-Server/node_scripts/Signaling-Server.js similarity index 55% rename from RTCMultiConnection/node_scripts/Signaling-Server.js rename to RTCMultiConnection-Server/node_scripts/Signaling-Server.js index b28d1902..0c61ec7b 100644 --- a/RTCMultiConnection/node_scripts/Signaling-Server.js +++ b/RTCMultiConnection-Server/node_scripts/Signaling-Server.js @@ -4,23 +4,23 @@ var listOfUsers = {}; var listOfRooms = {}; + var adminSocket; // for scalable-broadcast demos var ScalableBroadcast; // pushLogs is used to write error logs into logs.json -var pushLogs = function(name, error) { - console.log(name, error); -}; -try { - pushLogs = require('./pushLogs.js'); -} catch (e) { - console.log('Unable to read pushLogs.js', e); -} +var pushLogs = require('./pushLogs.js'); + +// const strings +var CONST_STRINGS = require('./CONST_STRINGS.js'); + +var isAdminAuthorized = require('./verify-admin.js'); -module.exports = exports = function(app, socketCallback) { +module.exports = exports = function(root, app, socketCallback) { socketCallback = socketCallback || function() {}; + app.config = app.config || {}; if (!!app.listen) { var io = require('socket.io'); @@ -51,14 +51,9 @@ module.exports = exports = function(app, socketCallback) { // to secure your socket.io usage: (via: docs/tips-tricks.md) // io.set('origins', 'https://domain.com'); - function appendUser(socket) { + function appendUser(socket, params) { try { - var alreadyExist = listOfUsers[socket.userid]; - var extra = {}; - - if (alreadyExist && alreadyExist.extra) { - extra = alreadyExist.extra; - } + var extra = params.extra; var params = socket.handshake.query; @@ -76,115 +71,218 @@ module.exports = exports = function(app, socketCallback) { listOfUsers[socket.userid] = { socket: socket, connectedWith: {}, - isPublic: false, // means: isPublicModerator extra: extra || {}, admininfo: {}, - maxParticipantsAllowed: params.maxParticipantsAllowed || 1000 + socketMessageEvent: params.socketMessageEvent || '', + socketCustomEvent: params.socketCustomEvent || '' }; } catch (e) { - pushLogs('appendUser', e); + pushLogs(root, 'appendUser', e); } sendToAdmin(); } - function sendToAdmin() { + function sendToAdmin(all) { + if(app.config.enableAdmin !== true) { + return; + } + try { if (adminSocket) { + var users = []; + // temporarily disabled + app.config.enableAdmin === true && Object.keys(listOfUsers).forEach(function(userid) { + try { + var item = listOfUsers[userid]; + if (!item) return; // maybe user just left? + + if (!item.connectedWith) { + item.connectedWith = {}; + } + + if (!item.socket) { + item.socket = {}; + } + + users.push({ + userid: userid, + admininfo: item.socket.admininfo || '', + connectedWith: Object.keys(item.connectedWith) + }); + } catch (e) { + pushLogs(root, 'admin.user-looper', e); + } + }); + + var scalableBroadcastUsers = 0; + if(ScalableBroadcast && ScalableBroadcast._) { + scalableBroadcastUsers = ScalableBroadcast._.getUsers(); + } + adminSocket.emit('admin', { - listOfRooms: listOfRooms + newUpdates: !all, + listOfRooms: !!all ? listOfRooms : [], + listOfUsers: Object.keys(listOfUsers).length, // users + scalableBroadcastUsers: scalableBroadcastUsers.length }); } } catch (e) { - pushLogs('admin', e); + pushLogs(root, 'admin', e); } } - function onConnection(socket) { - var params = socket.handshake.query; - var socketMessageEvent = params.msgEvent || 'RTCMultiConnection-Message'; + function handleAdminSocket(socket, params) { + if(app.config.enableAdmin !== true || !params.adminUserName || !params.adminPassword) { + socket.emit('admin', { + error: 'Please pass "adminUserName" and "adminPassword" via socket.io parameters.' + }); + + pushLogs(root, 'invalid-admin', { + message: CONST_STRINGS.INVALID_ADMIN_CREDENTIAL, + stack: 'name: ' + params.adminUserName + '\n' + 'password: ' + params.adminPassword + }); - var autoCloseEntireSession = params.autoCloseEntireSession === true || params.autoCloseEntireSession === 'true'; - var sessionid = params.sessionid; - var maxParticipantsAllowed = params.maxParticipantsAllowed || 1000; - var enableScalableBroadcast = params.enableScalableBroadcast === true || params.enableScalableBroadcast === 'true'; + socket.disconnect(); //disabled admin + return; + } + + if (!app.config || !isAdminAuthorized(params, app.config)) { + socket.emit('admin', { + error: 'Invalid admin username or password.' + }); + + pushLogs(root, 'invalid-admin', { + message: CONST_STRINGS.INVALID_ADMIN_CREDENTIAL, + stack: 'name: ' + params.adminUserName + '\n' + 'password: ' + params.adminPassword + }); + + socket.disconnect(); + return; + } + + socket.emit('admin', { + connected: true + }); + + adminSocket = socket; + socket.on('admin', function(message, callback) { + if (!app.config || !isAdminAuthorized(params, app.config)) { + socket.emit('admin', { + error: 'Invalid admin username or password.' + }); + + pushLogs(root, 'invalid-admin', { + message: CONST_STRINGS.INVALID_ADMIN_CREDENTIAL, + stack: 'name: ' + params.adminUserName + '\n' + 'password: ' + params.adminPassword + }); - if (params.userid === 'admin') { - if (!app.request || !app.isAdminAuthorized || !app.config || !app.isAdminAuthorized(app.request, app.config)) { socket.disconnect(); return; } - adminSocket = socket; - socket.on('admin', function(message, callback) { - if (!app.request || !app.isAdminAuthorized || !app.config || !app.isAdminAuthorized(app.request, app.config)) { - socket.disconnect(); - return; - } - - callback = callback || function() {}; + callback = callback || function() {}; - if (message.all === true) { - sendToAdmin(); - } + if (message.all === true) { + sendToAdmin(true); + } - if (message.userinfo === true && message.userid) { - try { - var user = listOfUsers[message.userid]; - if (user) { - callback(user.socket.admininfo || {}); - } else { - callback({ - error: 'User-id "' + message.userid + '" does not exist.' - }); - } - } catch (e) { - pushLogs('userinfo', e); + if (message.userinfo === true && message.userid) { + try { + var user = listOfUsers[message.userid]; + if (user) { + callback(user.socket.admininfo || {}); + } else { + callback({ + error: CONST_STRINGS.USERID_NOT_AVAILABLE + }); } + } catch (e) { + pushLogs(root, 'userinfo', e); } + } - if (message.clearLogs === true) { - // last callback parameter will force to clear logs - pushLogs('', '', callback); - } + if (message.clearLogs === true) { + // last callback parameter will force to clear logs + pushLogs(root, '', '', callback); + } - if (message.deleteUser === true) { - try { - var user = listOfUsers[message.userid]; + if (message.deleteUser === true) { + try { + var user = listOfUsers[message.userid]; - if (user) { - user.socket.disconnect(); + if (user) { + if (user.socket.owner) { + // delete listOfRooms[user.socket.owner]; } - // delete listOfUsers[message.userid]; - callback(true); - } catch (e) { - pushLogs('deleteUser', e); - callback(false); + user.socket.disconnect(); } + + // delete listOfUsers[message.userid]; + callback(true); + } catch (e) { + pushLogs(root, 'deleteUser', e); + callback(false); } + } - if (message.deleteRoom === true) { - try { - var room = listOfRooms[message.roomid]; - - if (room) { - var participants = room.participants; - delete listOfRooms[message.roomid]; - participants.forEach(function(userid) { - var user = listOfUsers[userid]; - if (user) { - user.socket.disconnect(); - } - }); - } - callback(true); - } catch (e) { - pushLogs('deleteRoom', e); - callback(false); + if (message.deleteRoom === true) { + try { + var room = listOfRooms[message.roomid]; + + if (room) { + var participants = room.participants; + delete listOfRooms[message.roomid]; + participants.forEach(function(userid) { + var user = listOfUsers[userid]; + if (user) { + user.socket.disconnect(); + } + }); } + callback(true); + } catch (e) { + pushLogs(root, 'deleteRoom', e); + callback(false); } - }); + } + }); + } + + function onConnection(socket) { + var params = socket.handshake.query; + + if(!params.userid) { + params.userid = (Math.random() * 100).toString().replace('.', ''); + } + + if(!params.sessionid) { + params.sessionid = (Math.random() * 100).toString().replace('.', ''); + } + + if (params.extra) { + try { + params.extra = JSON.parse(params.extra); + } catch (e) { + params.extra = {}; + } + } else { + params.extra = {}; + } + + var socketMessageEvent = params.msgEvent || 'RTCMultiConnection-Message'; + + // for admin's record + params.socketMessageEvent = socketMessageEvent; + + var autoCloseEntireSession = params.autoCloseEntireSession === true || params.autoCloseEntireSession === 'true'; + var sessionid = params.sessionid; + var maxParticipantsAllowed = parseInt(params.maxParticipantsAllowed || 1000) || 1000; + var enableScalableBroadcast = params.enableScalableBroadcast === true || params.enableScalableBroadcast === 'true'; + + if (params.userid === 'admin') { + handleAdminSocket(socket, params); return; } @@ -194,20 +292,14 @@ module.exports = exports = function(app, socketCallback) { // path to scalable broadcast script must be accurate ScalableBroadcast = require('./Scalable-Broadcast.js'); } - ScalableBroadcast(socket, params.maxRelayLimitPerUser); + ScalableBroadcast._ = ScalableBroadcast(root, socket, params.maxRelayLimitPerUser); } catch (e) { - pushLogs('ScalableBroadcast', e); + pushLogs(root, 'ScalableBroadcast', e); } - - // scalable-broadcast can ignore rest of the codes - // however it must have evens like open-room, join-room, and socketMessageEvent - // return; } - // [disabled] - if (false && !!listOfUsers[params.userid]) { - params.dontUpdateUserId = true; - + // do not allow to override userid + if (!!listOfUsers[params.userid]) { var useridAlreadyTaken = params.userid; params.userid = (Math.random() * 1000).toString().replace('.', ''); socket.emit('userid-already-taken', useridAlreadyTaken, params.userid); @@ -215,7 +307,7 @@ module.exports = exports = function(app, socketCallback) { } socket.userid = params.userid; - appendUser(socket); + appendUser(socket, params); socket.on('extra-data-updated', function(extra) { try { @@ -228,32 +320,60 @@ module.exports = exports = function(app, socketCallback) { // todo: use "admininfo.extra" instead of below one listOfUsers[socket.userid].extra = extra; - for (var user in listOfUsers[socket.userid].connectedWith) { - listOfUsers[user].socket.emit('extra-data-updated', socket.userid, extra); + try { + for (var user in listOfUsers[socket.userid].connectedWith) { + try { + listOfUsers[user].socket.emit('extra-data-updated', socket.userid, extra); + } catch (e) { + pushLogs(root, 'extra-data-updated.connectedWith', e); + } + } + } catch (e) { + pushLogs(root, 'extra-data-updated.connectedWith', e); + } + + // sent alert to all room participants + if (!socket.admininfo) { + sendToAdmin(); + return; + } + + var roomid = socket.admininfo.sessionid; + if (roomid && listOfRooms[roomid]) { + if (socket.userid == listOfRooms[roomid].owner) { + // room's extra must match owner's extra + listOfRooms[roomid].extra = extra; + } + listOfRooms[roomid].participants.forEach(function(pid) { + try { + var user = listOfUsers[pid]; + if (!user) { + // todo: remove this user from participants list + return; + } + + user.socket.emit('extra-data-updated', socket.userid, extra); + } catch (e) { + pushLogs(root, 'extra-data-updated.participants', e); + } + }); } + + sendToAdmin(); } catch (e) { - pushLogs('extra-data-updated', e); + pushLogs(root, 'extra-data-updated', e); } }); socket.on('get-remote-user-extra-data', function(remoteUserId, callback) { callback = callback || function() {}; if (!remoteUserId || !listOfUsers[remoteUserId]) { - callback('remoteUserId (' + remoteUserId + ') does NOT exist.'); + callback(CONST_STRINGS.USERID_NOT_AVAILABLE); return; } callback(listOfUsers[remoteUserId].extra); }); - socket.on('become-a-public-moderator', function() { - try { - if (!listOfUsers[socket.userid]) return; - listOfUsers[socket.userid].isPublic = true; - } catch (e) { - pushLogs('become-a-public-moderator', e); - } - }); - var dontDuplicateListeners = {}; socket.on('set-custom-socket-event-listener', function(customEvent) { if (dontDuplicateListeners[customEvent]) return; @@ -266,43 +386,9 @@ module.exports = exports = function(app, socketCallback) { }); }); - socket.on('dont-make-me-moderator', function() { - try { - if (!listOfUsers[socket.userid]) return; - listOfUsers[socket.userid].isPublic = false; - } catch (e) { - pushLogs('dont-make-me-moderator', e); - } - }); - - socket.on('get-public-moderators', function(userIdStartsWith, callback) { - try { - userIdStartsWith = userIdStartsWith || ''; - var allPublicModerators = []; - for (var moderatorId in listOfUsers) { - if (listOfUsers[moderatorId].isPublic && moderatorId.indexOf(userIdStartsWith) === 0 && moderatorId !== socket.userid) { - var moderator = listOfUsers[moderatorId]; - allPublicModerators.push({ - userid: moderatorId, - extra: moderator.extra - }); - } - } - - callback(allPublicModerators); - } catch (e) { - pushLogs('get-public-moderators', e); - } - }); - socket.on('changed-uuid', function(newUserId, callback) { callback = callback || function() {}; - if (params.dontUpdateUserId) { - delete params.dontUpdateUserId; - return; - } - try { if (listOfUsers[socket.userid] && listOfUsers[socket.userid].socket.userid == socket.userid) { if (newUserId === socket.userid) return; @@ -317,21 +403,34 @@ module.exports = exports = function(app, socketCallback) { } socket.userid = newUserId; - appendUser(socket); + appendUser(socket, params); callback(); } catch (e) { - pushLogs('changed-uuid', e); + pushLogs(root, 'changed-uuid', e); } }); - socket.on('set-password', function(password) { + socket.on('set-password', function(password, callback) { try { - if (listOfRooms[socket.roomid] && listOfRooms[socket.roomid].owner == socket.userid) { - listOfRooms[socket.roomid].password = password; + callback = callback || function() {}; + + if (!socket.admininfo) { + callback(null, null, CONST_STRINGS.DID_NOT_JOIN_ANY_ROOM); + return; + } + + var roomid = socket.admininfo.sessionid; + + if (listOfRooms[roomid] && listOfRooms[roomid].owner == socket.userid) { + listOfRooms[roomid].password = password; + callback(true, roomid, null); + } + else { + callback(false, roomid, CONST_STRINGS.ROOM_PERMISSION_DENIED); } } catch (e) { - pushLogs('set-password', e); + pushLogs(root, 'set-password', e); } }); @@ -352,35 +451,59 @@ module.exports = exports = function(app, socketCallback) { } callback(); } catch (e) { - pushLogs('disconnect-with', e); + pushLogs(root, 'disconnect-with', e); } }); socket.on('close-entire-session', function(callback) { try { - var connectedWith = listOfUsers[socket.userid].connectedWith; - Object.keys(connectedWith).forEach(function(key) { - if (connectedWith[key] && connectedWith[key].emit) { - try { - connectedWith[key].emit('closed-entire-session', socket.userid, listOfUsers[socket.userid].extra); - } catch (e) {} - } - }); - callback(); + if(!callback || typeof callback !== 'function') { + callback = function() {}; + } + + var user = listOfUsers[socket.userid]; + + if(!user) return callback(false, CONST_STRINGS.USERID_NOT_AVAILABLE); + if(!user.roomid) return callback(false, CONST_STRINGS.ROOM_NOT_AVAILABLE); + if(!socket.admininfo) return callback(false, CONST_STRINGS.INVALID_SOCKET); + + var room = listOfRooms[user.roomid]; + if(!room) return callback(false, CONST_STRINGS.ROOM_NOT_AVAILABLE); + if(room.owner !== user.userid) return callback(false, CONST_STRINGS.ROOM_PERMISSION_DENIED); + + autoCloseEntireSession = true; + closeOrShiftRoom(); + + callback(true); } catch (e) { - pushLogs('close-entire-session', e); + pushLogs(root, 'close-entire-session', e); } }); socket.on('check-presence', function(roomid, callback) { try { - if (!listOfRooms[roomid]) { - callback(false, roomid, {}); + if (!listOfRooms[roomid] || !listOfRooms[roomid].participants.length) { + callback(false, roomid, { + _room: { + isFull: false, + isPasswordProtected: false + } + }); } else { - callback(true, roomid, listOfRooms[roomid].extra); + var extra = listOfRooms[roomid].extra; + if(typeof extra !== 'object' || !extra) { + extra = { + value: extra + }; + } + extra._room = { + isFull: listOfRooms[roomid].participants.length >= listOfRooms[roomid].maxParticipantsAllowed, + isPasswordProtected: listOfRooms[roomid].password && listOfRooms[roomid].password.toString().replace(/ /g, '').length + }; + callback(true, roomid, extra); } } catch (e) { - pushLogs('check-presence', e); + pushLogs(root, 'check-presence', e); } }); @@ -391,6 +514,10 @@ module.exports = exports = function(app, socketCallback) { return; } + // we don't need "connectedWith" anymore + // todo: remove all these redundant codes + // fire "onUserStatusChanged" for room-participants instead of individual users + // rename "user-connected" to "user-status-changed" if (!message.message.userLeft && !listOfUsers[message.sender].connectedWith[message.remoteUserId] && !!listOfUsers[message.remoteUserId]) { listOfUsers[message.sender].connectedWith[message.remoteUserId] = listOfUsers[message.remoteUserId].socket; listOfUsers[message.sender].socket.emit('user-connected', message.remoteUserId); @@ -399,10 +526,8 @@ module.exports = exports = function(app, socketCallback) { listOfUsers[message.remoteUserId] = { socket: null, connectedWith: {}, - isPublic: false, extra: {}, - admininfo: {}, - maxParticipantsAllowed: params.maxParticipantsAllowed || 1000 + admininfo: {} }; } @@ -415,14 +540,14 @@ module.exports = exports = function(app, socketCallback) { sendToAdmin(); } - if (listOfUsers[message.sender].connectedWith[message.remoteUserId] && listOfUsers[socket.userid]) { + if (listOfUsers[message.sender] && listOfUsers[message.sender].connectedWith[message.remoteUserId] && listOfUsers[socket.userid]) { message.extra = listOfUsers[socket.userid].extra; listOfUsers[message.sender].connectedWith[message.remoteUserId].emit(socketMessageEvent, message); sendToAdmin(); } } catch (e) { - pushLogs('onMessageCallback', e); + pushLogs(root, 'onMessageCallback', e); } } @@ -435,8 +560,11 @@ module.exports = exports = function(app, socketCallback) { if (!listOfRooms[roomid]) return; // find a solution? - if (listOfRooms[roomid].participants.length > params.maxParticipantsAllowed) { - socket.emit('room-full', roomid); + if (listOfRooms[roomid].participants.length >= listOfRooms[roomid].maxParticipantsAllowed && listOfRooms[roomid].participants.indexOf(socket.userid) === -1) { + // room is full + // todo: how to tell user that room is full? + // do not fire "room-full" event + // find something else return; } @@ -459,7 +587,7 @@ module.exports = exports = function(app, socketCallback) { if (enableScalableBroadcast === false) { // connect with all participants listOfRooms[roomid].participants.forEach(function(pid) { - if (pid === socket.userid) return; + if (pid === socket.userid || !listOfUsers[pid]) return; var user = listOfUsers[pid]; message.remoteUserId = pid; @@ -467,7 +595,7 @@ module.exports = exports = function(app, socketCallback) { }); } } catch (e) { - pushLogs('joinARoom', e); + pushLogs(root, 'joinARoom', e); } sendToAdmin(); @@ -477,10 +605,13 @@ module.exports = exports = function(app, socketCallback) { try { if (!listOfRooms[roomid]) { listOfRooms[roomid] = { - isPublic: params.isPublic || false, - maxParticipantsAllowed: params.maxParticipantsAllowed || 1000, + maxParticipantsAllowed: parseInt(params.maxParticipantsAllowed || 1000) || 1000, owner: userid, // this can change if owner leaves and if control shifts participants: [userid], + extra: {}, // usually owner's extra-data + socketMessageEvent: '', + socketCustomEvent: '', + identifier: '', session: { audio: true, video: true @@ -491,7 +622,7 @@ module.exports = exports = function(app, socketCallback) { if (listOfRooms[roomid].participants.indexOf(userid) !== -1) return; listOfRooms[roomid].participants.push(userid); } catch (e) { - pushLogs('appendToRoom', e); + pushLogs(root, 'appendToRoom', e); } } @@ -517,11 +648,10 @@ module.exports = exports = function(app, socketCallback) { // reset owner priviliges listOfRooms[roomid].owner = firstParticipant.socket.userid; - // "become-next-modrator" merely sets "connection.isInitiator=true" - // though it is not important; maybe below line is redundant? - firstParticipant.socket.emit('become-next-modrator', roomid); + // redundant? + firstParticipant.socket.emit('set-isInitiator-true', roomid); - // remove moderator from room's participants list + // remove from room's participants list var newParticipantsList = []; listOfRooms[roomid].participants.forEach(function(pid) { if (pid != socket.userid) { @@ -546,11 +676,10 @@ module.exports = exports = function(app, socketCallback) { } } } catch (e) { - pushLogs('closeOrShiftRoom', e); + pushLogs(root, 'closeOrShiftRoom', e); } } - var numberOfPasswordTries = 0; socket.on(socketMessageEvent, function(message, callback) { if (message.remoteUserId && message.remoteUserId === socket.userid) { // remoteUserId MUST be unique @@ -559,32 +688,13 @@ module.exports = exports = function(app, socketCallback) { try { if (message.remoteUserId && message.remoteUserId != 'system' && message.message.newParticipationRequest) { - if (listOfRooms[message.remoteUserId] && listOfRooms[message.remoteUserId].password) { - if (numberOfPasswordTries > 3) { - socket.emit('password-max-tries-over', message.remoteUserId); - return; - } - - if (!message.password) { - numberOfPasswordTries++; - socket.emit('join-with-password', message.remoteUserId); - return; - } - - if (message.password != listOfRooms[message.remoteUserId].password) { - numberOfPasswordTries++; - socket.emit('invalid-password', message.remoteUserId, message.password); - return; - } - } - if (enableScalableBroadcast === true) { var user = listOfUsers[message.remoteUserId]; if (user) { user.socket.emit(socketMessageEvent, message); } - if (listOfUsers[socket.userid].extra.broadcastId) { + if (listOfUsers[socket.userid] && listOfUsers[socket.userid].extra.broadcastId) { // for /admin/ page appendToRoom(listOfUsers[socket.userid].extra.broadcastId, socket.userid); } @@ -611,10 +721,8 @@ module.exports = exports = function(app, socketCallback) { listOfUsers[message.sender] = { socket: socket, connectedWith: {}, - isPublic: false, extra: {}, - admininfo: {}, - maxParticipantsAllowed: params.maxParticipantsAllowed || 1000 + admininfo: {} }; } @@ -648,7 +756,73 @@ module.exports = exports = function(app, socketCallback) { onMessageCallback(message); } catch (e) { - pushLogs('on-socketMessageEvent', e); + pushLogs(root, 'on-socketMessageEvent', e); + } + }); + + socket.on('is-valid-password', function(password, roomid, callback) { + try { + callback = callback || function() {}; + + if(!password || !password.toString().replace(/ /g, '').length) { + callback(false, roomid, 'You did not enter the password.'); + return; + } + + if(!roomid || !roomid.toString().replace(/ /g, '').length) { + callback(false, roomid, 'You did not enter the room-id.'); + return; + } + + if(!listOfRooms[roomid]) { + callback(false, roomid, CONST_STRINGS.ROOM_NOT_AVAILABLE); + return; + } + + if(!listOfRooms[roomid].password) { + callback(false, roomid, 'This room do not have any password.'); + return; + } + + if(listOfRooms[roomid].password === password) { + callback(true, roomid, false); + } + else { + callback(false, roomid, CONST_STRINGS.INVALID_PASSWORD); + } + } + catch(e) { + pushLogs('is-valid-password', e); + } + }); + + socket.on('get-public-rooms', function(identifier, callback) { + try { + if(!identifier || !identifier.toString().length || !identifier.toString().replace(/ /g, '').length) { + callback(null, CONST_STRINGS.PUBLIC_IDENTIFIER_MISSING); + return; + } + + var rooms = []; + Object.keys(listOfRooms).forEach(function(key) { + var room = listOfRooms[key]; + if(!room || !room.identifier || !room.identifier.toString().length || room.identifier !== identifier) return; + rooms.push({ + maxParticipantsAllowed: room.maxParticipantsAllowed, + owner: room.owner, + participants: room.participants, + extra: room.extra, + session: room.session, + sessionid: key, + isRoomFull: room.participants.length >= room.maxParticipantsAllowed, + isPasswordProtected: !!room.password && room.password.replace(/ /g, '').length > 0 + }); + }); + + callback(rooms); + } + catch(e) { + pushLogs('get-public-rooms', e); } }); @@ -659,8 +833,8 @@ module.exports = exports = function(app, socketCallback) { // if already joined a room, either leave or close it closeOrShiftRoom(); - if (listOfRooms[arg.sessionid]) { - callback(false, 'Room in use. Pease join instead or create another room.'); + if (listOfRooms[arg.sessionid] && listOfRooms[arg.sessionid].participants.length) { + callback(false, CONST_STRINGS.ROOM_NOT_AVAILABLE); return; } @@ -674,10 +848,10 @@ module.exports = exports = function(app, socketCallback) { listOfUsers[socket.userid] = { socket: socket, connectedWith: {}, - isPublic: false, // means: isPublicModerator extra: arg.extra, admininfo: {}, - maxParticipantsAllowed: params.maxParticipantsAllowed || 1000 + socketMessageEvent: params.socketMessageEvent || '', + socketCustomEvent: params.socketCustomEvent || '' }; } listOfUsers[socket.userid].extra = arg.extra; @@ -686,7 +860,7 @@ module.exports = exports = function(app, socketCallback) { autoCloseEntireSession = true; } } catch (e) { - pushLogs('open-room', e); + pushLogs(root, 'open-room', e); } // append this user into participants list @@ -703,10 +877,22 @@ module.exports = exports = function(app, socketCallback) { // for non-scalable-broadcast demos listOfRooms[arg.sessionid].owner = socket.userid; listOfRooms[arg.sessionid].session = arg.session; + listOfRooms[arg.sessionid].extra = arg.extra || {}; + listOfRooms[arg.sessionid].socketMessageEvent = listOfUsers[socket.userid].socketMessageEvent; + listOfRooms[arg.sessionid].socketCustomEvent = listOfUsers[socket.userid].socketCustomEvent; + listOfRooms[arg.sessionid].maxParticipantsAllowed = parseInt(params.maxParticipantsAllowed || 1000) || 1000; + + if(arg.identifier && arg.identifier.toString().length) { + listOfRooms[arg.sessionid].identifier = arg.identifier; + } - if (typeof arg.password !== 'undefined' && arg.password.length) { - // password protected room? - listOfRooms[arg.sessionid].password = arg.password; + try { + if (typeof arg.password !== 'undefined' && arg.password.toString().length) { + // password protected room? + listOfRooms[arg.sessionid].password = arg.password; + } + } catch (e) { + pushLogs(root, 'open-room.password', e); } } @@ -720,7 +906,7 @@ module.exports = exports = function(app, socketCallback) { extra: arg.extra }; } catch (e) { - pushLogs('open-room', e); + pushLogs(root, 'open-room', e); } sendToAdmin(); @@ -728,7 +914,7 @@ module.exports = exports = function(app, socketCallback) { try { callback(true); } catch (e) { - pushLogs('open-room', e); + pushLogs(root, 'open-room', e); } }); @@ -749,29 +935,42 @@ module.exports = exports = function(app, socketCallback) { listOfUsers[socket.userid] = { socket: socket, connectedWith: {}, - isPublic: false, // means: isPublicModerator extra: arg.extra, admininfo: {}, - maxParticipantsAllowed: params.maxParticipantsAllowed || 1000 + socketMessageEvent: params.socketMessageEvent || '', + socketCustomEvent: params.socketCustomEvent || '' }; } listOfUsers[socket.userid].extra = arg.extra; } catch (e) { - pushLogs('join-room', e); + pushLogs(root, 'join-room', e); } try { if (!listOfRooms[arg.sessionid]) { - callback(false, 'Room does not exist.'); + callback(false, CONST_STRINGS.ROOM_NOT_AVAILABLE); return; } + } catch (e) { + pushLogs(root, 'join-room', e); + } + try { if (listOfRooms[arg.sessionid].password && listOfRooms[arg.sessionid].password != arg.password) { - callback(false, 'Invalid password.'); + callback(false, CONST_STRINGS.INVALID_PASSWORD); return; } } catch (e) { - pushLogs('join-room', e); + pushLogs(root, 'join-room.password', e); + } + + try { + if (listOfRooms[arg.sessionid].participants.length >= listOfRooms[arg.sessionid].maxParticipantsAllowed) { + callback(false, CONST_STRINGS.ROOM_FULL); + return; + } + } catch (e) { + pushLogs(root, 'join-room.ROOM_FULL', e); } // append this user into participants list @@ -788,7 +987,7 @@ module.exports = exports = function(app, socketCallback) { extra: arg.extra }; } catch (e) { - pushLogs('join-room', e); + pushLogs(root, 'join-room', e); } sendToAdmin(); @@ -796,7 +995,7 @@ module.exports = exports = function(app, socketCallback) { try { callback(true); } catch (e) { - pushLogs('join-room', e); + pushLogs(root, 'join-room', e); } }); @@ -806,7 +1005,7 @@ module.exports = exports = function(app, socketCallback) { delete socket.namespace.sockets[this.id]; } } catch (e) { - pushLogs('disconnect', e); + pushLogs(root, 'disconnect', e); } try { @@ -823,7 +1022,7 @@ module.exports = exports = function(app, socketCallback) { } } } catch (e) { - pushLogs('disconnect', e); + pushLogs(root, 'disconnect', e); } closeOrShiftRoom(); @@ -831,8 +1030,13 @@ module.exports = exports = function(app, socketCallback) { delete listOfUsers[socket.userid]; if (socket.ondisconnect) { - // scalable-broadcast.js - socket.ondisconnect(); + try { + // scalable-broadcast.js + socket.ondisconnect(); + } + catch(e) { + pushLogs('socket.ondisconnect', e); + } } sendToAdmin(); diff --git a/RTCMultiConnection/node_scripts/get-bash-parameters.js b/RTCMultiConnection-Server/node_scripts/get-bash-parameters.js similarity index 83% rename from RTCMultiConnection/node_scripts/get-bash-parameters.js rename to RTCMultiConnection-Server/node_scripts/get-bash-parameters.js index b38c79ea..ead84e2b 100644 --- a/RTCMultiConnection/node_scripts/get-bash-parameters.js +++ b/RTCMultiConnection-Server/node_scripts/get-bash-parameters.js @@ -24,9 +24,9 @@ module.exports = exports = function(config, BASH_COLORS_HELPER) { } } - // node server.js --autoRebootServerOnFailure=false - if (val.indexOf('--autoRebootServerOnFailure=false') === 0) { - config.autoRebootServerOnFailure = false; + // node server.js --autoRebootServerOnFailure=true + if (val.indexOf('--autoRebootServerOnFailure=true') === 0) { + config.autoRebootServerOnFailure = true; } // node server.js --port=9002 @@ -38,15 +38,29 @@ module.exports = exports = function(config, BASH_COLORS_HELPER) { } } - // node server.js --defaultDemo=/demos/Video-Conferencing.html - if (val.indexOf('--defaultDemo') === 0) { - var inner = val.split('--defaultDemo=')[1]; + // node server.js --dirPath=/var/www/html/ + if (val.indexOf('--dirPath') === 0) { + var inner = val.split('--dirPath=')[1]; if (inner) { inner = inner.split(' ')[0].trim(); - config.defaultDemo = inner; + config.dirPath = inner; } } + // node server.js --homePage=/demos/Video-Conferencing.html + if (val.indexOf('--homePage') === 0) { + var inner = val.split('--homePage=')[1]; + if (inner) { + inner = inner.split(' ')[0].trim(); + config.homePage = inner; + } + } + + // node server.js --enableAdmin=true + if (val.indexOf('--enableAdmin=true') === 0) { + config.enableAdmin = true; + } + // node server.js --adminUserName=username if (val.indexOf('--adminUserName') === 0) { var inner = val.split('--adminUserName=')[1]; @@ -144,8 +158,12 @@ module.exports = exports = function(config, BASH_COLORS_HELPER) { console.log('\tCheck all RTCMultiConnection dependencies.'); console.log(BASH_COLORS_HELPER.getYellowFG(), '--autoRebootServerOnFailure=false'); console.log('\tDisable auto-restart server.js on failure.'); - console.log(BASH_COLORS_HELPER.getYellowFG(), '--defaultDemo=/demos/Video-Conferencing.html'); + console.log(BASH_COLORS_HELPER.getYellowFG(), '--dirPath=/var/www/html/'); + console.log('\tDirectory path that is used for HTML/CSS/JS content delivery.'); + console.log(BASH_COLORS_HELPER.getYellowFG(), '--homePage=/demos/Video-Conferencing.html'); console.log('\tOpen a specific demo instead of loading list of demos.'); + console.log(BASH_COLORS_HELPER.getYellowFG(), '--enableAdmin=true'); + console.log('\tEnable /admin/ page.'); console.log(BASH_COLORS_HELPER.getYellowFG(), '--adminUserName=username'); console.log('\t/admin/ page\'s username.'); console.log(BASH_COLORS_HELPER.getYellowFG(), '--adminPassword=password'); @@ -155,4 +173,6 @@ module.exports = exports = function(config, BASH_COLORS_HELPER) { process.exit(1); } }); + + return config; }; diff --git a/RTCMultiConnection/node_scripts/get-values-from-config-json.js b/RTCMultiConnection-Server/node_scripts/get-values-from-config-json.js similarity index 75% rename from RTCMultiConnection/node_scripts/get-values-from-config-json.js rename to RTCMultiConnection-Server/node_scripts/get-values-from-config-json.js index 10d28809..5f419306 100644 --- a/RTCMultiConnection/node_scripts/get-values-from-config-json.js +++ b/RTCMultiConnection-Server/node_scripts/get-values-from-config-json.js @@ -2,32 +2,36 @@ // MIT License - www.WebRTC-Experiment.com/licence // Documentation - github.com/muaz-khan/RTCMultiConnection -function getValues() { +function getValues(root) { var path = require('path'); var fs = require('fs'); + var getJsonFile = require('./getJsonFile.js'); var result = { socketURL: '/', - defaultDemo: '/demos/index.html', + dirPath: null, + homePage: '/demos/index.html', socketMessageEvent: 'RTCMultiConnection-Message', socketCustomEvent: 'RTCMultiConnection-Custom-Message', port: process.env.PORT || 9001, - enableLogs: true, - autoRebootServerOnFailure: null, + enableLogs: false, + autoRebootServerOnFailure: false, isUseHTTPs: null, sslKey: null, sslCert: null, sslCabundle: null, + enableAdmin: false, adminUserName: null, adminPassword: null }; - if (!fs.existsSync('./config.json')) { + if (!fs.existsSync(root.config)) { + console.log('File does not exist', root.config); return result; } - var config = require('../config.json'); + var config = getJsonFile(root.config); ['sslKey', 'sslCert', 'sslCabundle'].forEach(function(key) { if (!config[key] || config[key].toString().length == 0) { @@ -69,8 +73,12 @@ function getValues() { result.socketURL = (config.socketURL || '').toString(); } - if ((config.defaultDemo || '').toString().length) { - result.defaultDemo = (config.defaultDemo || '').toString(); + if ((config.dirPath || '').toString().length) { + result.dirPath = (config.dirPath || '').toString(); + } + + if ((config.homePage || '').toString().length) { + result.homePage = (config.homePage || '').toString(); } if ((config.socketMessageEvent || '').toString().length) { @@ -81,6 +89,10 @@ function getValues() { result.socketCustomEvent = (config.socketCustomEvent || '').toString(); } + if ((config.enableAdmin || '').toString() === 'true') { + result.enableAdmin = true; + } + if ((config.adminUserName || '').toString().length) { result.adminUserName = (config.adminUserName || '').toString(); } @@ -92,4 +104,4 @@ function getValues() { return result; } -module.exports = exports = getValues(); +module.exports = exports = getValues; diff --git a/RTCMultiConnection-Server/node_scripts/getJsonFile.js b/RTCMultiConnection-Server/node_scripts/getJsonFile.js new file mode 100644 index 00000000..ee94cbd1 --- /dev/null +++ b/RTCMultiConnection-Server/node_scripts/getJsonFile.js @@ -0,0 +1,16 @@ +function getJsonFile(path) { + var fs = require('fs'); + var output = {}; + try { + var json = fs.readFileSync(path); + output = JSON.parse(json); + } + catch(e) { + output = {}; + + // console.log(e.message, e.stack); + } + return output; +} + +module.exports = exports = getJsonFile; diff --git a/RTCMultiConnection-Server/node_scripts/index.js b/RTCMultiConnection-Server/node_scripts/index.js new file mode 100644 index 00000000..0fb21297 --- /dev/null +++ b/RTCMultiConnection-Server/node_scripts/index.js @@ -0,0 +1,289 @@ +// Muaz Khan - www.MuazKhan.com +// MIT License - www.WebRTC-Experiment.com/licence +// Documentation - github.com/muaz-khan/RTCMultiConnection + +module.exports = exports = function(root, callback) { + var fs = require('fs'); + var path = require('path'); + var url = require('url'); + + root = root || {}; + root.config = root.config || 'config.json'; + root.logs = root.logs || 'logs.json'; + + var resolveURL = require('./resolveURL.js'); + var BASH_COLORS_HELPER = require('./BASH_COLORS_HELPER.js'); + + var getValuesFromConfigJson = require('./get-values-from-config-json.js'); + var config = getValuesFromConfigJson(root); + + var getBashParameters = require('./get-bash-parameters.js'); + + config = getBashParameters(config, BASH_COLORS_HELPER); + root.enableLogs = config.enableLogs; // used by "pushLogs" + + var getJsonFile = require('./getJsonFile.js'); + + // pushLogs is used to write error logs into logs.json + var pushLogs = require('./pushLogs.js'); + var server = require(config.isUseHTTPs ? 'https' : 'http'); + + function serverHandler(request, response) { + try { + // to make sure we always get valid info from json file + // even if nested codes are overriding it + config = getValuesFromConfigJson(root); + config = getBashParameters(config, BASH_COLORS_HELPER); + + app.config = config; + } catch (e) { + pushLogs('serverHandler', e); + } + + if (typeof callback === 'function') { + callback(request, response, config, root, BASH_COLORS_HELPER, pushLogs, resolveURL, getJsonFile); + } else { + response.writeHead(200, { + 'Content-Type': 'text/plain' + }); + response.write('RTCMultiConnection Socket.io Server.\n\n' + 'https://github.com/muaz-khan/RTCMultiConnection-Server\n\n' + 'npm install RTCMultiConnection-Server'); + response.end(); + } + } + + var app; + + try { + if (config.isUseHTTPs) { + // See how to use a valid certificate: + // https://github.com/muaz-khan/WebRTC-Experiment/issues/62 + var options = { + key: null, + cert: null, + ca: null + }; + + var pfx = false; + + if (!fs.existsSync(config.sslKey)) { + console.log(BASH_COLORS_HELPER.getRedFG(), 'sslKey:\t ' + config.sslKey + ' does not exist.'); + } else { + pfx = config.sslKey.indexOf('.pfx') !== -1; + options.key = fs.readFileSync(config.sslKey); + } + + if (!fs.existsSync(config.sslCert)) { + console.log(BASH_COLORS_HELPER.getRedFG(), 'sslCert:\t ' + config.sslCert + ' does not exist.'); + } else { + options.cert = fs.readFileSync(config.sslCert); + } + + if (config.sslCabundle) { + if (!fs.existsSync(config.sslCabundle)) { + console.log(BASH_COLORS_HELPER.getRedFG(), 'sslCabundle:\t ' + config.sslCabundle + ' does not exist.'); + } + + options.ca = fs.readFileSync(config.sslCabundle); + } + + if (pfx === true) { + options = { + pfx: sslKey + }; + } + + app = server.createServer(options, serverHandler); + } else { + app = server.createServer(serverHandler); + } + } catch (e) { + pushLogs(root, 'createServer', e); + } + + function runServer() { + try { + app.on('error', function(e) { + pushLogs(root, 'app.onerror', e); + + if (e.code != 'EADDRINUSE') return; + + try { + function cmd_exec(cmd, args, cb_stdout, cb_end) { + try { + var spawn = require('child_process').spawn; + var child = spawn(cmd, args); + var me = this; + me.exit = 0; + me.stdout = ""; + child.stdout.on('data', function(data) { + try { + cb_stdout(me, data); + } catch (e) { + pushLogs(root, 'stdout.data', e); + } + }); + child.stdout.on('end', function() { + try { + cb_end(me); + } catch (e) { + pushLogs(root, 'stdout.end', e); + } + }); + } catch (e) { + pushLogs(root, 'cmd_exec', e); + } + } + + function log_console() { + try { + console.log(foo.stdout); + + var pidToBeKilled = foo.stdout.split('\nnode ')[1].split(' ')[0]; + console.log('------------------------------'); + console.log('Please execute below command:'); + console.log('\x1b[31m%s\x1b[0m ', 'kill ' + pidToBeKilled); + console.log('Then try to run "server.js" again.'); + console.log('------------------------------'); + + } catch (e) { + pushLogs(root, 'log_console', e); + } + } + + if (e.address === '0.0.0.0') { + e.address = 'localhost'; + } + + var socketURL = (config.isUseHTTPs ? 'https' : 'http') + '://' + e.address + ':' + e.port + '/'; + + console.log('------------------------------'); + console.log('\x1b[31m%s\x1b[0m ', 'Unable to listen on port: ' + e.port); + console.log('\x1b[31m%s\x1b[0m ', socketURL + ' is already in use. Please kill below processes using "kill PID".'); + console.log('------------------------------'); + + foo = new cmd_exec('lsof', ['-n', '-i4TCP:9001'], + function(me, data) { + try { + me.stdout += data.toString(); + } catch (e) { + pushLogs(root, 'lsof', e); + } + }, + function(me) { + try { + me.exit = 1; + } catch (e) { + pushLogs(root, 'lsof.exit', e); + } + } + ); + + setTimeout(log_console, 250); + } catch (e) { + pushLogs(root, 'app.onerror.EADDRINUSE', e); + } + }); + + app = app.listen(config.port, process.env.IP || '0.0.0.0', function(error) { + try { + var addr = app.address(); + + if (addr.address === '0.0.0.0') { + addr.address = 'localhost'; + } + + var domainURL = (config.isUseHTTPs ? 'https' : 'http') + '://' + addr.address + ':' + addr.port + '/'; + + console.log('\n'); + + console.log('Socket.io is listening at:'); + console.log(BASH_COLORS_HELPER.getGreenFG(), '\t' + domainURL); + + if (!config.isUseHTTPs) { + console.log('You can use --ssl to enable HTTPs:'); + console.log(BASH_COLORS_HELPER.getYellowFG(), '\t' + 'node server --ssl'); + } + + console.log('Your web-browser (HTML file) MUST set this line:'); + console.log(BASH_COLORS_HELPER.getGreenFG(), '\tconnection.socketURL = "' + domainURL + '";'); + + if (addr.address != 'localhost' && !config.isUseHTTPs) { + console.log(BASH_COLORS_HELPER.getRedBG(), 'Warning:'); + console.log(BASH_COLORS_HELPER.getRedBG(), 'Please run on HTTPs to make sure audio,video and screen demos can work on Google Chrome as well.'); + } + + if (config.enableAdmin === true) { + console.log('Admin page is enabled and running on: ' + domainURL + 'admin/'); + console.log('\tAdmin page username: ' + config.adminUserName); + console.log('\tAdmin page password: ' + config.adminPassword); + } + + console.log('For more help: ', BASH_COLORS_HELPER.getYellowFG('node server.js --help')); + console.log('\n'); + } catch (e) { + pushLogs(root, 'app.listen.callback', e); + } + }); + } catch (e) { + pushLogs(root, 'app.listen', e); + } + + try { + require('./Signaling-Server.js')(root, app, function(socket) { + try { + var params = socket.handshake.query; + + // "socket" object is totally in your own hands! + // do whatever you want! + + // in your HTML page, you can access socket as following: + // connection.socketCustomEvent = 'custom-message'; + // var socket = connection.getSocket(); + // socket.emit(connection.socketCustomEvent, { test: true }); + + if (!params.socketCustomEvent) { + params.socketCustomEvent = 'custom-message'; + } + + socket.on(params.socketCustomEvent, function(message) { + try { + socket.broadcast.emit(params.socketCustomEvent, message); + } catch (e) { + pushLogs(root, 'socket.broadcast.socketCustomEvent'); + } + }); + } catch (e) { + pushLogs(root, 'Signaling-Server.callback', e); + } + }); + } catch (e) { + pushLogs(root, 'require.Signaling-Server', e); + } + } + + if (config.autoRebootServerOnFailure) { + try { + // auto restart app on failure + var cluster = require('cluster'); + if (cluster.isMaster) { + cluster.fork(); + + cluster.on('exit', function(worker, code, signal) { + try { + cluster.fork(); + } catch (e) { + pushLogs(root, 'cluster.exit.fork', e); + } + }); + } + + if (cluster.isWorker) { + runServer(); + } + } catch (e) { + pushLogs(root, 'cluster.require.fork', e); + } + } else { + runServer(); + } +}; diff --git a/RTCMultiConnection/node_scripts/pushLogs.js b/RTCMultiConnection-Server/node_scripts/pushLogs.js similarity index 74% rename from RTCMultiConnection/node_scripts/pushLogs.js rename to RTCMultiConnection-Server/node_scripts/pushLogs.js index 4b9e0da2..e212ed10 100644 --- a/RTCMultiConnection/node_scripts/pushLogs.js +++ b/RTCMultiConnection-Server/node_scripts/pushLogs.js @@ -2,22 +2,18 @@ // MIT License - www.WebRTC-Experiment.com/licence // Documentation - github.com/muaz-khan/RTCMultiConnection -var enableLogs = false; - -try { - var _enableLogs = require('../config.json').enableLogs; - - if (_enableLogs) { - enableLogs = true; - } -} catch (e) { - enableLogs = false; -} - var fs = require('fs'); +var getJsonFile = require('./getJsonFile.js'); -function pushLogs(name, error, clearLogsCallback) { - if (!enableLogs) return; +function pushLogs(root, name, error, clearLogsCallback) { + // return console.log(error.message, error.stack); + if (!root.enableLogs) { + try { + console.log(name, error.message, error.stack); + } + catch(e) {} + return; + } if (!clearLogsCallback || typeof clearLogsCallback !== 'function') { if (!name || !error || !error.message || !error.stack) { @@ -31,17 +27,7 @@ function pushLogs(name, error, clearLogsCallback) { utcDateString += (Math.random() * 100).toString(); utcDateString = utcDateString.replace(/ |-|,|:|\./g, ''); - // uncache to fetch recent (up-to-dated) - var uncache = require('./uncache.js'); - uncache('../logs.json'); - - var logs = {}; - - try { - logs = require('../logs.json'); - } catch (e) { - console.log('Unable to read logs.json', e); - } + var logs = getJsonFile(root.logs); try { if (!!clearLogsCallback && typeof clearLogsCallback === 'function') { @@ -55,7 +41,7 @@ function pushLogs(name, error, clearLogsCallback) { }; } - fs.writeFileSync('./logs.json', JSON.stringify(logs)); + fs.writeFileSync(root.logs, JSON.stringify(logs)); if (!!clearLogsCallback && typeof clearLogsCallback === 'function') { clearLogsCallback(true); diff --git a/RTCMultiConnection/node_scripts/resolveURL.js b/RTCMultiConnection-Server/node_scripts/resolveURL.js similarity index 100% rename from RTCMultiConnection/node_scripts/resolveURL.js rename to RTCMultiConnection-Server/node_scripts/resolveURL.js diff --git a/RTCMultiConnection-Server/node_scripts/verify-admin.js b/RTCMultiConnection-Server/node_scripts/verify-admin.js new file mode 100644 index 00000000..92c8d3f5 --- /dev/null +++ b/RTCMultiConnection-Server/node_scripts/verify-admin.js @@ -0,0 +1,9 @@ +// Muaz Khan - www.MuazKhan.com +// MIT License - www.WebRTC-Experiment.com/licence +// Documentation - github.com/muaz-khan/RTCMultiConnection + +// /admin/ page +module.exports = exports = function(params, config) { + if(!params || !params.adminUserName || !params.adminPassword) return false; + return params.adminUserName === config.adminUserName && params.adminPassword === config.adminPassword; +}; diff --git a/RTCMultiConnection-Server/package.json b/RTCMultiConnection-Server/package.json new file mode 100644 index 00000000..5b97685a --- /dev/null +++ b/RTCMultiConnection-Server/package.json @@ -0,0 +1,57 @@ +{ + "name": "rtcmulticonnection-server", + "preferGlobal": false, + "version": "1.3.0", + "author": { + "name": "Muaz Khan", + "email": "muazkh@gmail.com", + "url": "https://muazkhan.com/" + }, + "description": "RTCMultiConnection socket.io server", + "repository": { + "type": "git", + "url": "https://github.com/muaz-khan/RTCMultiConnection-Server.git" + }, + "scripts": { + "start": "node server.js" + }, + "engines": { + "node": "latest" + }, + "main": "./node_scripts/index.js", + "keywords": [ + "peer-to-peer", + "peer2peer", + "peer", + "webrtc", + "rtcmulticonnection", + "webrtc-library", + "library", + "javascript", + "chrome", + "firefox", + "opera", + "ie", + "internet-explorer", + "android", + "rtcweb", + "rtcmulticonnection.js", + "multirtc", + "webrtc-experiment", + "javascript-library", + "muaz", + "muaz-khan" + ], + "dependencies": { + "socket.io": "latest" + }, + "analyze": false, + "license": "MIT", + "readmeFilename": "README.md", + "bugs": { + "url": "https://github.com/muaz-khan/RTCMultiConnection-Server/issues", + "email": "muazkh@gmail.com" + }, + "homepage": "https://rtcmulticonnection.herokuapp.com/demos/", + "_from": "rtcmulticonnection-server@" +} diff --git a/RTCMultiConnection-Server/server.js b/RTCMultiConnection-Server/server.js new file mode 100644 index 00000000..61f82e05 --- /dev/null +++ b/RTCMultiConnection-Server/server.js @@ -0,0 +1,8 @@ +require('./node_scripts/index.js')(); + +/* +require('./node_scripts/index.js')({ + config: __dirname + resolveURL('/config.json'), + logs: __dirname + resolveURL('/logs.json') +}); +*/ diff --git a/RTCMultiConnection-SignalR/RTCMultiConnection/Scripts/RTCMultiConnection.js b/RTCMultiConnection-SignalR/RTCMultiConnection/Scripts/RTCMultiConnection.js new file mode 100644 index 00000000..f21db8d5 --- /dev/null +++ b/RTCMultiConnection-SignalR/RTCMultiConnection/Scripts/RTCMultiConnection.js @@ -0,0 +1,6177 @@ +'use strict'; + +// Last time updated: 2018-09-15 3:49:42 PM UTC + +// _________________________ +// RTCMultiConnection v3.4.7 + +// Open-Sourced: https://github.com/muaz-khan/RTCMultiConnection + +// -------------------------------------------------- +// Muaz Khan - www.MuazKhan.com +// MIT License - www.WebRTC-Experiment.com/licence +// -------------------------------------------------- + +window.RTCMultiConnection = function(roomid, forceOptions) { + + function SocketConnection(connection, connectCallback) { + var parameters = ''; + + parameters += '?userid=' + connection.userid; + parameters += '&sessionid=' + connection.sessionid; + parameters += '&msgEvent=' + connection.socketMessageEvent; + parameters += '&socketCustomEvent=' + connection.socketCustomEvent; + parameters += '&autoCloseEntireSession=' + !!connection.autoCloseEntireSession; + + if (connection.session.broadcast === true) { + parameters += '&oneToMany=true'; + } + + parameters += '&maxParticipantsAllowed=' + connection.maxParticipantsAllowed; + + if (connection.enableScalableBroadcast) { + parameters += '&enableScalableBroadcast=true'; + parameters += '&maxRelayLimitPerUser=' + (connection.maxRelayLimitPerUser || 2); + } + + if (connection.socketCustomParameters) { + parameters += connection.socketCustomParameters; + } + + try { + io.sockets = {}; + } catch (e) {}; + + if (!connection.socketURL) { + connection.socketURL = '/'; + } + + if (connection.socketURL.substr(connection.socketURL.length - 1, 1) != '/') { + // connection.socketURL = 'https://domain.com:9001/'; + throw '"socketURL" MUST end with a slash.'; + } + + if (connection.enableLogs) { + if (connection.socketURL == '/') { + console.info('socket.io is connected at: ', location.origin + '/'); + } else { + console.info('socket.io is connected at: ', connection.socketURL); + } + } + + try { + connection.socket = io(connection.socketURL + parameters); + } catch (e) { + connection.socket = io.connect(connection.socketURL + parameters, connection.socketOptions); + } + + // detect signaling medium + connection.socket.isIO = true; + + var mPeer = connection.multiPeersHandler; + + connection.socket.on('extra-data-updated', function(remoteUserId, extra) { + if (!connection.peers[remoteUserId]) return; + connection.peers[remoteUserId].extra = extra; + + connection.onExtraDataUpdated({ + userid: remoteUserId, + extra: extra + }); + + if (!connection.peersBackup[remoteUserId]) { + connection.peersBackup[remoteUserId] = { + userid: remoteUserId, + extra: {} + }; + } + + connection.peersBackup[remoteUserId].extra = extra; + }); + + function onMessageEvent(message) { + if (message.remoteUserId != connection.userid) return; + + if (connection.peers[message.sender] && connection.peers[message.sender].extra != message.message.extra) { + connection.peers[message.sender].extra = message.extra; + connection.onExtraDataUpdated({ + userid: message.sender, + extra: message.extra + }); + } + + if (message.message === 'next-possible-initiator') { + if (connection.nextPossibleInitiatorIfThisUserLeave) return; + connection.nextPossibleInitiatorIfThisUserLeave = message.sender; + return; + } + + if (message.message.streamSyncNeeded && connection.peers[message.sender]) { + var stream = connection.streamEvents[message.message.streamid]; + if (!stream || !stream.stream) { + return; + } + + var action = message.message.action; + + if (action === 'ended' || action === 'inactive' || action === 'stream-removed') { + if (connection.peersBackup[stream.userid]) { + stream.extra = connection.peersBackup[stream.userid].extra; + } + connection.onstreamended(stream); + return; + } + + var type = message.message.type != 'both' ? message.message.type : null; + + if (typeof stream.stream[action] == 'function') { + stream.stream[action](type); + } + return; + } + + if (message.message === 'connectWithAllParticipants') { + if (connection.broadcasters.indexOf(message.sender) === -1) { + connection.broadcasters.push(message.sender); + } + + mPeer.onNegotiationNeeded({ + allParticipants: connection.getAllParticipants(message.sender) + }, message.sender); + return; + } + + if (message.message === 'removeFromBroadcastersList') { + if (connection.broadcasters.indexOf(message.sender) !== -1) { + delete connection.broadcasters[connection.broadcasters.indexOf(message.sender)]; + connection.broadcasters = removeNullEntries(connection.broadcasters); + } + return; + } + + if (message.message === 'dropPeerConnection') { + connection.deletePeer(message.sender); + return; + } + + if (message.message.allParticipants) { + if (message.message.allParticipants.indexOf(message.sender) === -1) { + message.message.allParticipants.push(message.sender); + } + + message.message.allParticipants.forEach(function(participant) { + mPeer[!connection.peers[participant] ? 'createNewPeer' : 'renegotiatePeer'](participant, { + localPeerSdpConstraints: { + OfferToReceiveAudio: connection.sdpConstraints.mandatory.OfferToReceiveAudio, + OfferToReceiveVideo: connection.sdpConstraints.mandatory.OfferToReceiveVideo + }, + remotePeerSdpConstraints: { + OfferToReceiveAudio: connection.session.oneway ? !!connection.session.audio : connection.sdpConstraints.mandatory.OfferToReceiveAudio, + OfferToReceiveVideo: connection.session.oneway ? !!connection.session.video || !!connection.session.screen : connection.sdpConstraints.mandatory.OfferToReceiveVideo + }, + isOneWay: !!connection.session.oneway || connection.direction === 'one-way', + isDataOnly: isData(connection.session) + }); + }); + return; + } + + if (message.message.newParticipant) { + if (message.message.newParticipant == connection.userid) return; + if (!!connection.peers[message.message.newParticipant]) return; + + mPeer.createNewPeer(message.message.newParticipant, message.message.userPreferences || { + localPeerSdpConstraints: { + OfferToReceiveAudio: connection.sdpConstraints.mandatory.OfferToReceiveAudio, + OfferToReceiveVideo: connection.sdpConstraints.mandatory.OfferToReceiveVideo + }, + remotePeerSdpConstraints: { + OfferToReceiveAudio: connection.session.oneway ? !!connection.session.audio : connection.sdpConstraints.mandatory.OfferToReceiveAudio, + OfferToReceiveVideo: connection.session.oneway ? !!connection.session.video || !!connection.session.screen : connection.sdpConstraints.mandatory.OfferToReceiveVideo + }, + isOneWay: !!connection.session.oneway || connection.direction === 'one-way', + isDataOnly: isData(connection.session) + }); + return; + } + + if (message.message.readyForOffer || message.message.addMeAsBroadcaster) { + if (connection.attachStreams.length) { + connection.waitingForLocalMedia = false; + } + + if (connection.waitingForLocalMedia) { + // if someone is waiting to join you + // make sure that we've local media before making a handshake + setTimeout(function() { + onMessageEvent(message); + }, 1000); + return; + } + + connection.addNewBroadcaster(message.sender); + } + + if (message.message.newParticipationRequest && message.sender !== connection.userid) { + if (connection.peers[message.sender]) { + connection.deletePeer(message.sender); + } + + var userPreferences = { + extra: message.extra || {}, + localPeerSdpConstraints: message.message.remotePeerSdpConstraints || { + OfferToReceiveAudio: connection.sdpConstraints.mandatory.OfferToReceiveAudio, + OfferToReceiveVideo: connection.sdpConstraints.mandatory.OfferToReceiveVideo + }, + remotePeerSdpConstraints: message.message.localPeerSdpConstraints || { + OfferToReceiveAudio: connection.session.oneway ? !!connection.session.audio : connection.sdpConstraints.mandatory.OfferToReceiveAudio, + OfferToReceiveVideo: connection.session.oneway ? !!connection.session.video || !!connection.session.screen : connection.sdpConstraints.mandatory.OfferToReceiveVideo + }, + isOneWay: typeof message.message.isOneWay !== 'undefined' ? message.message.isOneWay : !!connection.session.oneway || connection.direction === 'one-way', + isDataOnly: typeof message.message.isDataOnly !== 'undefined' ? message.message.isDataOnly : isData(connection.session), + dontGetRemoteStream: typeof message.message.isOneWay !== 'undefined' ? message.message.isOneWay : !!connection.session.oneway || connection.direction === 'one-way', + dontAttachLocalStream: !!message.message.dontGetRemoteStream, + connectionDescription: message, + successCallback: function() { + // if its oneway----- todo: THIS SEEMS NOT IMPORTANT. + if (typeof message.message.isOneWay !== 'undefined' ? message.message.isOneWay : !!connection.session.oneway || connection.direction === 'one-way') { + connection.addNewBroadcaster(message.sender, userPreferences); + } + + if (!!connection.session.oneway || connection.direction === 'one-way' || isData(connection.session)) { + connection.addNewBroadcaster(message.sender, userPreferences); + } + } + }; + + connection.onNewParticipant(message.sender, userPreferences); + return; + } + + if (message.message.shiftedModerationControl) { + connection.onShiftedModerationControl(message.sender, message.message.broadcasters); + return; + } + + if (message.message.changedUUID) { + if (connection.peers[message.message.oldUUID]) { + connection.peers[message.message.newUUID] = connection.peers[message.message.oldUUID]; + delete connection.peers[message.message.oldUUID]; + } + } + + if (message.message.userLeft) { + mPeer.onUserLeft(message.sender); + + if (!!message.message.autoCloseEntireSession) { + connection.leave(); + } + + return; + } + + mPeer.addNegotiatedMessage(message.message, message.sender); + } + + connection.socket.on(connection.socketMessageEvent, onMessageEvent); + + connection.socket.on('user-left', function(userid) { + onUserLeft(userid); + + connection.onUserStatusChanged({ + userid: userid, + status: 'offline', + extra: connection.peers[userid] ? connection.peers[userid].extra || {} : {} + }); + + var eventObject = { + userid: userid, + extra: {} + }; + + if (connection.peersBackup[eventObject.userid]) { + eventObject.extra = connection.peersBackup[eventObject.userid].extra; + } + + connection.onleave(eventObject); + + if (connection.nextPossibleInitiatorIfThisUserLeave === userid) { + connection.nextPossibleInitiatorIfThisUserLeave = null; + connection.open(connection.sessionid); + } + }); + + var alreadyConnected = false; + + connection.socket.resetProps = function() { + alreadyConnected = false; + }; + + connection.socket.on('connect', function() { + if (alreadyConnected) { + return; + } + alreadyConnected = true; + + if (connection.enableLogs) { + console.info('socket.io connection is opened.'); + } + + setTimeout(function() { + connection.socket.emit('extra-data-updated', connection.extra); + + if (connectCallback) { + connectCallback(connection.socket); + } + }, 1000); + }); + + connection.socket.on('disconnect', function() { + if (connection.enableLogs) { + console.warn('socket.io connection is closed'); + } + }); + + connection.socket.on('join-with-password', function(remoteUserId) { + connection.onJoinWithPassword(remoteUserId); + }); + + connection.socket.on('invalid-password', function(remoteUserId, oldPassword) { + connection.onInvalidPassword(remoteUserId, oldPassword); + }); + + connection.socket.on('password-max-tries-over', function(remoteUserId) { + connection.onPasswordMaxTriesOver(remoteUserId); + }); + + connection.socket.on('user-disconnected', function(remoteUserId) { + if (remoteUserId === connection.userid) { + return; + } + + connection.onUserStatusChanged({ + userid: remoteUserId, + status: 'offline', + extra: connection.peers[remoteUserId] ? connection.peers[remoteUserId].extra || {} : {} + }); + + connection.deletePeer(remoteUserId); + }); + + connection.socket.on('user-connected', function(userid) { + if (userid === connection.userid) { + return; + } + + connection.onUserStatusChanged({ + userid: userid, + status: 'online', + extra: connection.peers[userid] ? connection.peers[userid].extra || {} : {} + }); + }); + + connection.socket.on('closed-entire-session', function(sessionid, extra) { + connection.leave(); + connection.onEntireSessionClosed({ + sessionid: sessionid, + userid: sessionid, + extra: extra + }); + }); + + connection.socket.on('userid-already-taken', function(useridAlreadyTaken, yourNewUserId) { + connection.isInitiator = false; + connection.userid = yourNewUserId; + + connection.onUserIdAlreadyTaken(useridAlreadyTaken, yourNewUserId); + }) + + connection.socket.on('logs', function(log) { + if (!connection.enableLogs) return; + console.debug('server-logs', log); + }); + + connection.socket.on('number-of-broadcast-viewers-updated', function(data) { + connection.onNumberOfBroadcastViewersUpdated(data); + }); + + connection.socket.on('room-full', function(roomid) { + connection.onRoomFull(roomid); + }); + + connection.socket.on('become-next-modrator', function(sessionid) { + if (sessionid != connection.sessionid) return; + setTimeout(function() { + connection.open(sessionid); + connection.socket.emit('shift-moderator-control-on-disconnect'); + }, 1000); + }); + } + + function MultiPeers(connection) { + var self = this; + + var skipPeers = ['getAllParticipants', 'getLength', 'selectFirst', 'streams', 'send', 'forEach']; + connection.peersBackup = {}; + connection.peers = { + getLength: function() { + var numberOfPeers = 0; + for (var peer in this) { + if (skipPeers.indexOf(peer) == -1) { + numberOfPeers++; + } + } + return numberOfPeers; + }, + selectFirst: function() { + var firstPeer; + for (var peer in this) { + if (skipPeers.indexOf(peer) == -1) { + firstPeer = this[peer]; + } + } + return firstPeer; + }, + getAllParticipants: function(sender) { + var allPeers = []; + for (var peer in this) { + if (skipPeers.indexOf(peer) == -1 && peer != sender) { + allPeers.push(peer); + } + } + return allPeers; + }, + forEach: function(callbcak) { + this.getAllParticipants().forEach(function(participant) { + callbcak(connection.peers[participant]); + }); + }, + send: function(data, remoteUserId) { + var that = this; + + if (!isNull(data.size) && !isNull(data.type)) { + self.shareFile(data, remoteUserId); + return; + } + + if (data.type !== 'text' && !(data instanceof ArrayBuffer) && !(data instanceof DataView)) { + TextSender.send({ + text: data, + channel: this, + connection: connection, + remoteUserId: remoteUserId + }); + return; + } + + if (data.type === 'text') { + data = JSON.stringify(data); + } + + if (remoteUserId) { + var remoteUser = connection.peers[remoteUserId]; + if (remoteUser) { + if (!remoteUser.channels.length) { + connection.peers[remoteUserId].createDataChannel(); + connection.renegotiate(remoteUserId); + setTimeout(function() { + that.send(data, remoteUserId); + }, 3000); + return; + } + + remoteUser.channels.forEach(function(channel) { + channel.send(data); + }); + return; + } + } + + this.getAllParticipants().forEach(function(participant) { + if (!that[participant].channels.length) { + connection.peers[participant].createDataChannel(); + connection.renegotiate(participant); + setTimeout(function() { + that[participant].channels.forEach(function(channel) { + channel.send(data); + }); + }, 3000); + return; + } + + that[participant].channels.forEach(function(channel) { + channel.send(data); + }); + }); + } + }; + + this.uuid = connection.userid; + + this.getLocalConfig = function(remoteSdp, remoteUserId, userPreferences) { + if (!userPreferences) { + userPreferences = {}; + } + + return { + streamsToShare: userPreferences.streamsToShare || {}, + rtcMultiConnection: connection, + connectionDescription: userPreferences.connectionDescription, + userid: remoteUserId, + localPeerSdpConstraints: userPreferences.localPeerSdpConstraints, + remotePeerSdpConstraints: userPreferences.remotePeerSdpConstraints, + dontGetRemoteStream: !!userPreferences.dontGetRemoteStream, + dontAttachLocalStream: !!userPreferences.dontAttachLocalStream, + renegotiatingPeer: !!userPreferences.renegotiatingPeer, + peerRef: userPreferences.peerRef, + channels: userPreferences.channels || [], + onLocalSdp: function(localSdp) { + self.onNegotiationNeeded(localSdp, remoteUserId); + }, + onLocalCandidate: function(localCandidate) { + localCandidate = OnIceCandidateHandler.processCandidates(connection, localCandidate) + if (localCandidate) { + self.onNegotiationNeeded(localCandidate, remoteUserId); + } + }, + remoteSdp: remoteSdp, + onDataChannelMessage: function(message) { + if (!connection.fbr && connection.enableFileSharing) initFileBufferReader(); + + if (typeof message == 'string' || !connection.enableFileSharing) { + self.onDataChannelMessage(message, remoteUserId); + return; + } + + var that = this; + + if (message instanceof ArrayBuffer || message instanceof DataView) { + connection.fbr.convertToObject(message, function(object) { + that.onDataChannelMessage(object); + }); + return; + } + + if (message.readyForNextChunk) { + connection.fbr.getNextChunk(message, function(nextChunk, isLastChunk) { + connection.peers[remoteUserId].channels.forEach(function(channel) { + channel.send(nextChunk); + }); + }, remoteUserId); + return; + } + + if (message.chunkMissing) { + connection.fbr.chunkMissing(message); + return; + } + + connection.fbr.addChunk(message, function(promptNextChunk) { + connection.peers[remoteUserId].peer.channel.send(promptNextChunk); + }); + }, + onDataChannelError: function(error) { + self.onDataChannelError(error, remoteUserId); + }, + onDataChannelOpened: function(channel) { + self.onDataChannelOpened(channel, remoteUserId); + }, + onDataChannelClosed: function(event) { + self.onDataChannelClosed(event, remoteUserId); + }, + onRemoteStream: function(stream) { + if (connection.peers[remoteUserId]) { + connection.peers[remoteUserId].streams.push(stream); + } + + self.onGettingRemoteMedia(stream, remoteUserId); + }, + onRemoteStreamRemoved: function(stream) { + self.onRemovingRemoteMedia(stream, remoteUserId); + }, + onPeerStateChanged: function(states) { + self.onPeerStateChanged(states); + + if (states.iceConnectionState === 'new') { + self.onNegotiationStarted(remoteUserId, states); + } + + if (states.iceConnectionState === 'connected') { + self.onNegotiationCompleted(remoteUserId, states); + } + + if (states.iceConnectionState.search(/closed|failed/gi) !== -1) { + self.onUserLeft(remoteUserId); + self.disconnectWith(remoteUserId); + } + } + }; + }; + + this.createNewPeer = function(remoteUserId, userPreferences) { + if (connection.maxParticipantsAllowed <= connection.getAllParticipants().length) { + return; + } + + userPreferences = userPreferences || {}; + + if (connection.isInitiator && !!connection.session.audio && connection.session.audio === 'two-way' && !userPreferences.streamsToShare) { + userPreferences.isOneWay = false; + userPreferences.isDataOnly = false; + userPreferences.session = connection.session; + } + + if (!userPreferences.isOneWay && !userPreferences.isDataOnly) { + userPreferences.isOneWay = true; + this.onNegotiationNeeded({ + enableMedia: true, + userPreferences: userPreferences + }, remoteUserId); + return; + } + + userPreferences = connection.setUserPreferences(userPreferences, remoteUserId); + var localConfig = this.getLocalConfig(null, remoteUserId, userPreferences); + connection.peers[remoteUserId] = new PeerInitiator(localConfig); + + this.checkIfNextPossibleInitiator(remoteUserId); + }; + + this.createAnsweringPeer = function(remoteSdp, remoteUserId, userPreferences) { + userPreferences = connection.setUserPreferences(userPreferences || {}, remoteUserId); + + var localConfig = this.getLocalConfig(remoteSdp, remoteUserId, userPreferences); + connection.peers[remoteUserId] = new PeerInitiator(localConfig); + this.checkIfNextPossibleInitiator(remoteUserId); + }; + + this.renegotiatePeer = function(remoteUserId, userPreferences, remoteSdp) { + if (!connection.peers[remoteUserId]) { + if (connection.enableLogs) { + console.error('Peer (' + remoteUserId + ') does not exist. Renegotiation skipped.'); + } + return; + } + + if (!userPreferences) { + userPreferences = {}; + } + + userPreferences.renegotiatingPeer = true; + userPreferences.peerRef = connection.peers[remoteUserId].peer; + userPreferences.channels = connection.peers[remoteUserId].channels; + + var localConfig = this.getLocalConfig(remoteSdp, remoteUserId, userPreferences); + + connection.peers[remoteUserId] = new PeerInitiator(localConfig); + this.checkIfNextPossibleInitiator(remoteUserId); + }; + + this.replaceTrack = function(track, remoteUserId, isVideoTrack) { + if (!connection.peers[remoteUserId]) { + throw 'This peer (' + remoteUserId + ') does not exist.'; + } + + var peer = connection.peers[remoteUserId].peer; + + if (!!peer.getSenders && typeof peer.getSenders === 'function' && peer.getSenders().length) { + peer.getSenders().forEach(function(rtpSender) { + if (isVideoTrack && rtpSender.track instanceof VideoStreamTrack) { + connection.peers[remoteUserId].peer.lastVideoTrack = rtpSender.track; + rtpSender.replaceTrack(track); + } + + if (!isVideoTrack && rtpSender.track instanceof AudioStreamTrack) { + connection.peers[remoteUserId].peer.lastAudioTrack = rtpSender.track; + rtpSender.replaceTrack(track); + } + }); + return; + } + + console.warn('RTPSender.replaceTrack is NOT supported.'); + this.renegotiatePeer(remoteUserId); + }; + + this.onNegotiationNeeded = function(message, remoteUserId) {}; + this.addNegotiatedMessage = function(message, remoteUserId) { + if (message.type && message.sdp) { + if (message.type == 'answer') { + if (connection.peers[remoteUserId]) { + connection.peers[remoteUserId].addRemoteSdp(message); + } + } + + if (message.type == 'offer') { + if (message.renegotiatingPeer) { + this.renegotiatePeer(remoteUserId, null, message); + } else { + this.createAnsweringPeer(message, remoteUserId); + } + } + + if (connection.enableLogs) { + console.log('Remote peer\'s sdp:', message.sdp); + } + return; + } + + if (message.candidate) { + if (connection.peers[remoteUserId]) { + connection.peers[remoteUserId].addRemoteCandidate(message); + } + + if (connection.enableLogs) { + console.log('Remote peer\'s candidate pairs:', message.candidate); + } + return; + } + + if (message.enableMedia) { + connection.session = message.userPreferences.session || connection.session; + + if (connection.session.oneway && connection.attachStreams.length) { + connection.attachStreams = []; + } + + if (message.userPreferences.isDataOnly && connection.attachStreams.length) { + connection.attachStreams.length = []; + } + + var streamsToShare = {}; + connection.attachStreams.forEach(function(stream) { + streamsToShare[stream.streamid] = { + isAudio: !!stream.isAudio, + isVideo: !!stream.isVideo, + isScreen: !!stream.isScreen + }; + }); + message.userPreferences.streamsToShare = streamsToShare; + + self.onNegotiationNeeded({ + readyForOffer: true, + userPreferences: message.userPreferences + }, remoteUserId); + } + + if (message.readyForOffer) { + connection.onReadyForOffer(remoteUserId, message.userPreferences); + } + + function cb(stream) { + gumCallback(stream, message, remoteUserId); + } + }; + + function gumCallback(stream, message, remoteUserId) { + var streamsToShare = {}; + connection.attachStreams.forEach(function(stream) { + streamsToShare[stream.streamid] = { + isAudio: !!stream.isAudio, + isVideo: !!stream.isVideo, + isScreen: !!stream.isScreen + }; + }); + message.userPreferences.streamsToShare = streamsToShare; + + self.onNegotiationNeeded({ + readyForOffer: true, + userPreferences: message.userPreferences + }, remoteUserId); + } + + this.connectNewParticipantWithAllBroadcasters = function(newParticipantId, userPreferences, broadcastersList) { + if (connection.socket.isIO) { + return; + } + + broadcastersList = (broadcastersList || '').split('|-,-|'); + + if (!broadcastersList.length) { + return; + } + + var firstBroadcaster; + + var remainingBroadcasters = []; + broadcastersList.forEach(function(list) { + list = (list || '').replace(/ /g, ''); + if (list.length) { + if (!firstBroadcaster) { + firstBroadcaster = list; + } else { + remainingBroadcasters.push(list); + } + } + }); + + if (!firstBroadcaster) { + return; + } + + self.onNegotiationNeeded({ + newParticipant: newParticipantId, + userPreferences: userPreferences || false + }, firstBroadcaster); + + if (!remainingBroadcasters.length) { + return; + } + + setTimeout(function() { + self.connectNewParticipantWithAllBroadcasters(newParticipantId, userPreferences, remainingBroadcasters.join('|-,-|')); + }, 3 * 1000); + }; + + this.onGettingRemoteMedia = function(stream, remoteUserId) {}; + this.onRemovingRemoteMedia = function(stream, remoteUserId) {}; + this.onGettingLocalMedia = function(localStream) {}; + this.onLocalMediaError = function(error, constraints) { + connection.onMediaError(error, constraints); + }; + + function initFileBufferReader() { + connection.fbr = new FileBufferReader(); + connection.fbr.onProgress = function(chunk) { + connection.onFileProgress(chunk); + }; + connection.fbr.onBegin = function(file) { + connection.onFileStart(file); + }; + connection.fbr.onEnd = function(file) { + connection.onFileEnd(file); + }; + } + + this.shareFile = function(file, remoteUserId) { + if (!connection.enableFileSharing) { + throw '"connection.enableFileSharing" is false.'; + } + + initFileBufferReader(); + + connection.fbr.readAsArrayBuffer(file, function(uuid) { + var arrayOfUsers = connection.getAllParticipants(); + + if (remoteUserId) { + arrayOfUsers = [remoteUserId]; + } + + arrayOfUsers.forEach(function(participant) { + connection.fbr.getNextChunk(uuid, function(nextChunk) { + connection.peers[participant].channels.forEach(function(channel) { + channel.send(nextChunk); + }); + }, participant); + }); + }, { + userid: connection.userid, + // extra: connection.extra, + chunkSize: DetectRTC.browser.name === 'Firefox' ? 15 * 1000 : connection.chunkSize || 0 + }); + }; + + if (typeof 'TextReceiver' !== 'undefined') { + var textReceiver = new TextReceiver(connection); + } + + this.onDataChannelMessage = function(message, remoteUserId) { + textReceiver.receive(JSON.parse(message), remoteUserId, connection.peers[remoteUserId] ? connection.peers[remoteUserId].extra : {}); + }; + + this.onDataChannelClosed = function(event, remoteUserId) { + event.userid = remoteUserId; + event.extra = connection.peers[remoteUserId] ? connection.peers[remoteUserId].extra : {}; + connection.onclose(event); + }; + + this.onDataChannelError = function(error, remoteUserId) { + error.userid = remoteUserId; + event.extra = connection.peers[remoteUserId] ? connection.peers[remoteUserId].extra : {}; + connection.onerror(error); + }; + + this.onDataChannelOpened = function(channel, remoteUserId) { + // keep last channel only; we are not expecting parallel/channels channels + if (connection.peers[remoteUserId].channels.length) { + connection.peers[remoteUserId].channels = [channel]; + return; + } + + connection.peers[remoteUserId].channels.push(channel); + connection.onopen({ + userid: remoteUserId, + extra: connection.peers[remoteUserId] ? connection.peers[remoteUserId].extra : {}, + channel: channel + }); + }; + + this.onPeerStateChanged = function(state) { + connection.onPeerStateChanged(state); + }; + + this.onNegotiationStarted = function(remoteUserId, states) {}; + this.onNegotiationCompleted = function(remoteUserId, states) {}; + + this.getRemoteStreams = function(remoteUserId) { + remoteUserId = remoteUserId || connection.peers.getAllParticipants()[0]; + return connection.peers[remoteUserId] ? connection.peers[remoteUserId].streams : []; + }; + + this.checkIfNextPossibleInitiator = function(remoteUserId) { + if (connection.sessionid === remoteUserId) return; + if (connection.autoCloseEntireSession) return; + if (connection.isInitiator && connection.getAllParticipants().length > 1) return; + + connection.socket.emit(connection.socketMessageEvent, { + remoteUserId: remoteUserId, + message: 'next-possible-initiator', + sender: connection.userid + }); + }; + } + + 'use strict'; + + // Last Updated On: 2018-05-05 12:25:07 PM UTC + + // ________________ + // DetectRTC v1.3.6 + + // Open-Sourced: https://github.com/muaz-khan/DetectRTC + + // -------------------------------------------------- + // Muaz Khan - www.MuazKhan.com + // MIT License - www.WebRTC-Experiment.com/licence + // -------------------------------------------------- + + (function() { + + var browserFakeUserAgent = 'Fake/5.0 (FakeOS) AppleWebKit/123 (KHTML, like Gecko) Fake/12.3.4567.89 Fake/123.45'; + + var isNodejs = typeof process === 'object' && typeof process.versions === 'object' && process.versions.node && /*node-process*/ !process.browser; + if (isNodejs) { + var version = process.versions.node.toString().replace('v', ''); + browserFakeUserAgent = 'Nodejs/' + version + ' (NodeOS) AppleWebKit/' + version + ' (KHTML, like Gecko) Nodejs/' + version + ' Nodejs/' + version + } + + (function(that) { + if (typeof window !== 'undefined') { + return; + } + + if (typeof window === 'undefined' && typeof global !== 'undefined') { + global.navigator = { + userAgent: browserFakeUserAgent, + getUserMedia: function() {} + }; + + /*global window:true */ + that.window = global; + } else if (typeof window === 'undefined') { + // window = this; + } + + if (typeof location === 'undefined') { + /*global location:true */ + that.location = { + protocol: 'file:', + href: '', + hash: '' + }; + } + + if (typeof screen === 'undefined') { + /*global screen:true */ + that.screen = { + width: 0, + height: 0 + }; + } + })(typeof global !== 'undefined' ? global : window); + + /*global navigator:true */ + var navigator = window.navigator; + + if (typeof navigator !== 'undefined') { + if (typeof navigator.webkitGetUserMedia !== 'undefined') { + navigator.getUserMedia = navigator.webkitGetUserMedia; + } + + if (typeof navigator.mozGetUserMedia !== 'undefined') { + navigator.getUserMedia = navigator.mozGetUserMedia; + } + } else { + navigator = { + getUserMedia: function() {}, + userAgent: browserFakeUserAgent + }; + } + + var isMobileDevice = !!(/Android|webOS|iPhone|iPad|iPod|BB10|BlackBerry|IEMobile|Opera Mini|Mobile|mobile/i.test(navigator.userAgent || '')); + + var isEdge = navigator.userAgent.indexOf('Edge') !== -1 && (!!navigator.msSaveOrOpenBlob || !!navigator.msSaveBlob); + + var isOpera = !!window.opera || navigator.userAgent.indexOf(' OPR/') >= 0; + var isFirefox = typeof window.InstallTrigger !== 'undefined'; + var isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent); + var isChrome = !!window.chrome && !isOpera; + var isIE = typeof document !== 'undefined' && !!document.documentMode && !isEdge; + + // this one can also be used: + // https://www.websocket.org/js/stuff.js (DetectBrowser.js) + + function getBrowserInfo() { + var nVer = navigator.appVersion; + var nAgt = navigator.userAgent; + var browserName = navigator.appName; + var fullVersion = '' + parseFloat(navigator.appVersion); + var majorVersion = parseInt(navigator.appVersion, 10); + var nameOffset, verOffset, ix; + + // In Opera, the true version is after 'Opera' or after 'Version' + if (isOpera) { + browserName = 'Opera'; + try { + fullVersion = navigator.userAgent.split('OPR/')[1].split(' ')[0]; + majorVersion = fullVersion.split('.')[0]; + } catch (e) { + fullVersion = '0.0.0.0'; + majorVersion = 0; + } + } + // In MSIE version <=10, the true version is after 'MSIE' in userAgent + // In IE 11, look for the string after 'rv:' + else if (isIE) { + verOffset = nAgt.indexOf('rv:'); + if (verOffset > 0) { //IE 11 + fullVersion = nAgt.substring(verOffset + 3); + } else { //IE 10 or earlier + verOffset = nAgt.indexOf('MSIE'); + fullVersion = nAgt.substring(verOffset + 5); + } + browserName = 'IE'; + } + // In Chrome, the true version is after 'Chrome' + else if (isChrome) { + verOffset = nAgt.indexOf('Chrome'); + browserName = 'Chrome'; + fullVersion = nAgt.substring(verOffset + 7); + } + // In Safari, the true version is after 'Safari' or after 'Version' + else if (isSafari) { + verOffset = nAgt.indexOf('Safari'); + + browserName = 'Safari'; + fullVersion = nAgt.substring(verOffset + 7); + + if ((verOffset = nAgt.indexOf('Version')) !== -1) { + fullVersion = nAgt.substring(verOffset + 8); + } + + if (navigator.userAgent.indexOf('Version/') !== -1) { + fullVersion = navigator.userAgent.split('Version/')[1].split(' ')[0]; + } + } + // In Firefox, the true version is after 'Firefox' + else if (isFirefox) { + verOffset = nAgt.indexOf('Firefox'); + browserName = 'Firefox'; + fullVersion = nAgt.substring(verOffset + 8); + } + + // In most other browsers, 'name/version' is at the end of userAgent + else if ((nameOffset = nAgt.lastIndexOf(' ') + 1) < (verOffset = nAgt.lastIndexOf('/'))) { + browserName = nAgt.substring(nameOffset, verOffset); + fullVersion = nAgt.substring(verOffset + 1); + + if (browserName.toLowerCase() === browserName.toUpperCase()) { + browserName = navigator.appName; + } + } + + if (isEdge) { + browserName = 'Edge'; + fullVersion = navigator.userAgent.split('Edge/')[1]; + // fullVersion = parseInt(navigator.userAgent.match(/Edge\/(\d+).(\d+)$/)[2], 10).toString(); + } + + // trim the fullVersion string at semicolon/space/bracket if present + if ((ix = fullVersion.search(/[; \)]/)) !== -1) { + fullVersion = fullVersion.substring(0, ix); + } + + majorVersion = parseInt('' + fullVersion, 10); + + if (isNaN(majorVersion)) { + fullVersion = '' + parseFloat(navigator.appVersion); + majorVersion = parseInt(navigator.appVersion, 10); + } + + return { + fullVersion: fullVersion, + version: majorVersion, + name: browserName, + isPrivateBrowsing: false + }; + } + + // via: https://gist.github.com/cou929/7973956 + + function retry(isDone, next) { + var currentTrial = 0, + maxRetry = 50, + interval = 10, + isTimeout = false; + var id = window.setInterval( + function() { + if (isDone()) { + window.clearInterval(id); + next(isTimeout); + } + if (currentTrial++ > maxRetry) { + window.clearInterval(id); + isTimeout = true; + next(isTimeout); + } + }, + 10 + ); + } + + function isIE10OrLater(userAgent) { + var ua = userAgent.toLowerCase(); + if (ua.indexOf('msie') === 0 && ua.indexOf('trident') === 0) { + return false; + } + var match = /(?:msie|rv:)\s?([\d\.]+)/.exec(ua); + if (match && parseInt(match[1], 10) >= 10) { + return true; + } + return false; + } + + function detectPrivateMode(callback) { + var isPrivate; + + try { + + if (window.webkitRequestFileSystem) { + window.webkitRequestFileSystem( + window.TEMPORARY, 1, + function() { + isPrivate = false; + }, + function(e) { + isPrivate = true; + } + ); + } else if (window.indexedDB && /Firefox/.test(window.navigator.userAgent)) { + var db; + try { + db = window.indexedDB.open('test'); + db.onerror = function() { + return true; + }; + } catch (e) { + isPrivate = true; + } + + if (typeof isPrivate === 'undefined') { + retry( + function isDone() { + return db.readyState === 'done' ? true : false; + }, + function next(isTimeout) { + if (!isTimeout) { + isPrivate = db.result ? false : true; + } + } + ); + } + } else if (isIE10OrLater(window.navigator.userAgent)) { + isPrivate = false; + try { + if (!window.indexedDB) { + isPrivate = true; + } + } catch (e) { + isPrivate = true; + } + } else if (window.localStorage && /Safari/.test(window.navigator.userAgent)) { + try { + window.localStorage.setItem('test', 1); + } catch (e) { + isPrivate = true; + } + + if (typeof isPrivate === 'undefined') { + isPrivate = false; + window.localStorage.removeItem('test'); + } + } + + } catch (e) { + isPrivate = false; + } + + retry( + function isDone() { + return typeof isPrivate !== 'undefined' ? true : false; + }, + function next(isTimeout) { + callback(isPrivate); + } + ); + } + + var isMobile = { + Android: function() { + return navigator.userAgent.match(/Android/i); + }, + BlackBerry: function() { + return navigator.userAgent.match(/BlackBerry|BB10/i); + }, + iOS: function() { + return navigator.userAgent.match(/iPhone|iPad|iPod/i); + }, + Opera: function() { + return navigator.userAgent.match(/Opera Mini/i); + }, + Windows: function() { + return navigator.userAgent.match(/IEMobile/i); + }, + any: function() { + return (isMobile.Android() || isMobile.BlackBerry() || isMobile.iOS() || isMobile.Opera() || isMobile.Windows()); + }, + getOsName: function() { + var osName = 'Unknown OS'; + if (isMobile.Android()) { + osName = 'Android'; + } + + if (isMobile.BlackBerry()) { + osName = 'BlackBerry'; + } + + if (isMobile.iOS()) { + osName = 'iOS'; + } + + if (isMobile.Opera()) { + osName = 'Opera Mini'; + } + + if (isMobile.Windows()) { + osName = 'Windows'; + } + + return osName; + } + }; + + // via: http://jsfiddle.net/ChristianL/AVyND/ + function detectDesktopOS() { + var unknown = '-'; + + var nVer = navigator.appVersion; + var nAgt = navigator.userAgent; + + var os = unknown; + var clientStrings = [{ + s: 'Windows 10', + r: /(Windows 10.0|Windows NT 10.0)/ + }, { + s: 'Windows 8.1', + r: /(Windows 8.1|Windows NT 6.3)/ + }, { + s: 'Windows 8', + r: /(Windows 8|Windows NT 6.2)/ + }, { + s: 'Windows 7', + r: /(Windows 7|Windows NT 6.1)/ + }, { + s: 'Windows Vista', + r: /Windows NT 6.0/ + }, { + s: 'Windows Server 2003', + r: /Windows NT 5.2/ + }, { + s: 'Windows XP', + r: /(Windows NT 5.1|Windows XP)/ + }, { + s: 'Windows 2000', + r: /(Windows NT 5.0|Windows 2000)/ + }, { + s: 'Windows ME', + r: /(Win 9x 4.90|Windows ME)/ + }, { + s: 'Windows 98', + r: /(Windows 98|Win98)/ + }, { + s: 'Windows 95', + r: /(Windows 95|Win95|Windows_95)/ + }, { + s: 'Windows NT 4.0', + r: /(Windows NT 4.0|WinNT4.0|WinNT|Windows NT)/ + }, { + s: 'Windows CE', + r: /Windows CE/ + }, { + s: 'Windows 3.11', + r: /Win16/ + }, { + s: 'Android', + r: /Android/ + }, { + s: 'Open BSD', + r: /OpenBSD/ + }, { + s: 'Sun OS', + r: /SunOS/ + }, { + s: 'Linux', + r: /(Linux|X11)/ + }, { + s: 'iOS', + r: /(iPhone|iPad|iPod)/ + }, { + s: 'Mac OS X', + r: /Mac OS X/ + }, { + s: 'Mac OS', + r: /(MacPPC|MacIntel|Mac_PowerPC|Macintosh)/ + }, { + s: 'QNX', + r: /QNX/ + }, { + s: 'UNIX', + r: /UNIX/ + }, { + s: 'BeOS', + r: /BeOS/ + }, { + s: 'OS/2', + r: /OS\/2/ + }, { + s: 'Search Bot', + r: /(nuhk|Googlebot|Yammybot|Openbot|Slurp|MSNBot|Ask Jeeves\/Teoma|ia_archiver)/ + }]; + for (var i = 0, cs; cs = clientStrings[i]; i++) { + if (cs.r.test(nAgt)) { + os = cs.s; + break; + } + } + + var osVersion = unknown; + + if (/Windows/.test(os)) { + if (/Windows (.*)/.test(os)) { + osVersion = /Windows (.*)/.exec(os)[1]; + } + os = 'Windows'; + } + + switch (os) { + case 'Mac OS X': + if (/Mac OS X (10[\.\_\d]+)/.test(nAgt)) { + osVersion = /Mac OS X (10[\.\_\d]+)/.exec(nAgt)[1]; + } + break; + case 'Android': + if (/Android ([\.\_\d]+)/.test(nAgt)) { + osVersion = /Android ([\.\_\d]+)/.exec(nAgt)[1]; + } + break; + case 'iOS': + if (/OS (\d+)_(\d+)_?(\d+)?/.test(nAgt)) { + osVersion = /OS (\d+)_(\d+)_?(\d+)?/.exec(nVer); + osVersion = osVersion[1] + '.' + osVersion[2] + '.' + (osVersion[3] | 0); + } + break; + } + + return { + osName: os, + osVersion: osVersion + }; + } + + var osName = 'Unknown OS'; + var osVersion = 'Unknown OS Version'; + + function getAndroidVersion(ua) { + ua = (ua || navigator.userAgent).toLowerCase(); + var match = ua.match(/android\s([0-9\.]*)/); + return match ? match[1] : false; + } + + var osInfo = detectDesktopOS(); + + if (osInfo && osInfo.osName && osInfo.osName != '-') { + osName = osInfo.osName; + osVersion = osInfo.osVersion; + } else if (isMobile.any()) { + osName = isMobile.getOsName(); + + if (osName == 'Android') { + osVersion = getAndroidVersion(); + } + } + + var isNodejs = typeof process === 'object' && typeof process.versions === 'object' && process.versions.node; + + if (osName === 'Unknown OS' && isNodejs) { + osName = 'Nodejs'; + osVersion = process.versions.node.toString().replace('v', ''); + } + + var isCanvasSupportsStreamCapturing = false; + var isVideoSupportsStreamCapturing = false; + ['captureStream', 'mozCaptureStream', 'webkitCaptureStream'].forEach(function(item) { + if (typeof document === 'undefined' || typeof document.createElement !== 'function') { + return; + } + + if (!isCanvasSupportsStreamCapturing && item in document.createElement('canvas')) { + isCanvasSupportsStreamCapturing = true; + } + + if (!isVideoSupportsStreamCapturing && item in document.createElement('video')) { + isVideoSupportsStreamCapturing = true; + } + }); + + // via: https://github.com/diafygi/webrtc-ips + function DetectLocalIPAddress(callback, stream) { + if (!DetectRTC.isWebRTCSupported) { + return; + } + + getIPs(function(ip) { + if (ip.match(/^(192\.168\.|169\.254\.|10\.|172\.(1[6-9]|2\d|3[01]))/)) { + callback('Local: ' + ip); + } else { + callback('Public: ' + ip); + } + }, stream); + } + + function getIPs(callback, stream) { + if (typeof document === 'undefined' || typeof document.getElementById !== 'function') { + return; + } + + var ipDuplicates = {}; + + var RTCPeerConnection = window.RTCPeerConnection || window.mozRTCPeerConnection || window.webkitRTCPeerConnection; + + if (!RTCPeerConnection) { + var iframe = document.getElementById('iframe'); + if (!iframe) { + return; + } + var win = iframe.contentWindow; + RTCPeerConnection = win.RTCPeerConnection || win.mozRTCPeerConnection || win.webkitRTCPeerConnection; + } + + if (!RTCPeerConnection) { + return; + } + + var peerConfig = null; + + if (DetectRTC.browser === 'Chrome' && DetectRTC.browser.version < 58) { + // todo: add support for older Opera + peerConfig = { + optional: [{ + RtpDataChannels: true + }] + }; + } + + var servers = { + iceServers: [{ + urls: 'stun:stun.l.google.com:19302' + }] + }; + + var pc = new RTCPeerConnection(servers, peerConfig); + + if (stream) { + if (pc.addStream) { + pc.addStream(stream); + } else if (pc.addTrack && stream.getTracks()[0]) { + pc.addTrack(stream.getTracks()[0], stream); + } + } + + function handleCandidate(candidate) { + var ipRegex = /([0-9]{1,3}(\.[0-9]{1,3}){3})/; + var match = ipRegex.exec(candidate); + if (!match) { + return; + } + var ipAddress = match[1]; + + if (ipDuplicates[ipAddress] === undefined) { + callback(ipAddress); + } + + ipDuplicates[ipAddress] = true; + } + + // listen for candidate events + pc.onicecandidate = function(ice) { + if (ice.candidate) { + handleCandidate(ice.candidate.candidate); + } + }; + + // create data channel + if (!stream) { + try { + pc.createDataChannel('sctp', {}); + } catch (e) {} + } + + // create an offer sdp + if (DetectRTC.isPromisesSupported) { + pc.createOffer().then(function(result) { + pc.setLocalDescription(result).then(afterCreateOffer); + }); + } else { + pc.createOffer(function(result) { + pc.setLocalDescription(result, afterCreateOffer, function() {}); + }, function() {}); + } + + function afterCreateOffer() { + var lines = pc.localDescription.sdp.split('\n'); + + lines.forEach(function(line) { + if (line.indexOf('a=candidate:') === 0) { + handleCandidate(line); + } + }); + } + } + + var MediaDevices = []; + + var audioInputDevices = []; + var audioOutputDevices = []; + var videoInputDevices = []; + + if (navigator.mediaDevices && navigator.mediaDevices.enumerateDevices) { + // Firefox 38+ seems having support of enumerateDevices + // Thanks @xdumaine/enumerateDevices + navigator.enumerateDevices = function(callback) { + var enumerateDevices = navigator.mediaDevices.enumerateDevices(); + if (enumerateDevices && enumerateDevices.then) { + navigator.mediaDevices.enumerateDevices().then(callback).catch(function() { + callback([]); + }); + } else { + callback([]); + } + }; + } + + // Media Devices detection + var canEnumerate = false; + + /*global MediaStreamTrack:true */ + if (typeof MediaStreamTrack !== 'undefined' && 'getSources' in MediaStreamTrack) { + canEnumerate = true; + } else if (navigator.mediaDevices && !!navigator.mediaDevices.enumerateDevices) { + canEnumerate = true; + } + + var hasMicrophone = false; + var hasSpeakers = false; + var hasWebcam = false; + + var isWebsiteHasMicrophonePermissions = false; + var isWebsiteHasWebcamPermissions = false; + + // http://dev.w3.org/2011/webrtc/editor/getusermedia.html#mediadevices + function checkDeviceSupport(callback) { + if (!canEnumerate) { + if (callback) { + callback(); + } + return; + } + + if (!navigator.enumerateDevices && window.MediaStreamTrack && window.MediaStreamTrack.getSources) { + navigator.enumerateDevices = window.MediaStreamTrack.getSources.bind(window.MediaStreamTrack); + } + + if (!navigator.enumerateDevices && navigator.enumerateDevices) { + navigator.enumerateDevices = navigator.enumerateDevices.bind(navigator); + } + + if (!navigator.enumerateDevices) { + if (callback) { + callback(); + } + return; + } + + MediaDevices = []; + + audioInputDevices = []; + audioOutputDevices = []; + videoInputDevices = []; + + hasMicrophone = false; + hasSpeakers = false; + hasWebcam = false; + + isWebsiteHasMicrophonePermissions = false; + isWebsiteHasWebcamPermissions = false; + + // to prevent duplication + var alreadyUsedDevices = {}; + + navigator.enumerateDevices(function(devices) { + devices.forEach(function(_device) { + var device = {}; + for (var d in _device) { + try { + if (typeof _device[d] !== 'function') { + device[d] = _device[d]; + } + } catch (e) {} + } + + if (alreadyUsedDevices[device.deviceId + device.label + device.kind]) { + return; + } + + // if it is MediaStreamTrack.getSources + if (device.kind === 'audio') { + device.kind = 'audioinput'; + } + + if (device.kind === 'video') { + device.kind = 'videoinput'; + } + + if (!device.deviceId) { + device.deviceId = device.id; + } + + if (!device.id) { + device.id = device.deviceId; + } + + if (!device.label) { + device.isCustomLabel = true; + + if (device.kind === 'videoinput') { + device.label = 'Camera ' + (videoInputDevices.length + 1); + } else if (device.kind === 'audioinput') { + device.label = 'Microphone ' + (audioInputDevices.length + 1); + } else if (device.kind === 'audiooutput') { + device.label = 'Speaker ' + (audioOutputDevices.length + 1); + } else { + device.label = 'Please invoke getUserMedia once.'; + } + + if (typeof DetectRTC !== 'undefined' && DetectRTC.browser.isChrome && DetectRTC.browser.version >= 46 && !/^(https:|chrome-extension:)$/g.test(location.protocol || '')) { + if (typeof document !== 'undefined' && typeof document.domain === 'string' && document.domain.search && document.domain.search(/localhost|127.0./g) === -1) { + device.label = 'HTTPs is required to get label of this ' + device.kind + ' device.'; + } + } + } else { + // Firefox on Android still returns empty label + if (device.kind === 'videoinput' && !isWebsiteHasWebcamPermissions) { + isWebsiteHasWebcamPermissions = true; + } + + if (device.kind === 'audioinput' && !isWebsiteHasMicrophonePermissions) { + isWebsiteHasMicrophonePermissions = true; + } + } + + if (device.kind === 'audioinput') { + hasMicrophone = true; + + if (audioInputDevices.indexOf(device) === -1) { + audioInputDevices.push(device); + } + } + + if (device.kind === 'audiooutput') { + hasSpeakers = true; + + if (audioOutputDevices.indexOf(device) === -1) { + audioOutputDevices.push(device); + } + } + + if (device.kind === 'videoinput') { + hasWebcam = true; + + if (videoInputDevices.indexOf(device) === -1) { + videoInputDevices.push(device); + } + } + + // there is no 'videoouput' in the spec. + MediaDevices.push(device); + + alreadyUsedDevices[device.deviceId + device.label + device.kind] = device; + }); + + if (typeof DetectRTC !== 'undefined') { + // to sync latest outputs + DetectRTC.MediaDevices = MediaDevices; + DetectRTC.hasMicrophone = hasMicrophone; + DetectRTC.hasSpeakers = hasSpeakers; + DetectRTC.hasWebcam = hasWebcam; + + DetectRTC.isWebsiteHasWebcamPermissions = isWebsiteHasWebcamPermissions; + DetectRTC.isWebsiteHasMicrophonePermissions = isWebsiteHasMicrophonePermissions; + + DetectRTC.audioInputDevices = audioInputDevices; + DetectRTC.audioOutputDevices = audioOutputDevices; + DetectRTC.videoInputDevices = videoInputDevices; + } + + if (callback) { + callback(); + } + }); + } + + var DetectRTC = window.DetectRTC || {}; + + // ---------- + // DetectRTC.browser.name || DetectRTC.browser.version || DetectRTC.browser.fullVersion + DetectRTC.browser = getBrowserInfo(); + + detectPrivateMode(function(isPrivateBrowsing) { + DetectRTC.browser.isPrivateBrowsing = !!isPrivateBrowsing; + }); + + // DetectRTC.isChrome || DetectRTC.isFirefox || DetectRTC.isEdge + DetectRTC.browser['is' + DetectRTC.browser.name] = true; + + // ----------- + DetectRTC.osName = osName; + DetectRTC.osVersion = osVersion; + + var isNodeWebkit = typeof process === 'object' && typeof process.versions === 'object' && process.versions['node-webkit']; + + // --------- Detect if system supports WebRTC 1.0 or WebRTC 1.1. + var isWebRTCSupported = false; + ['RTCPeerConnection', 'webkitRTCPeerConnection', 'mozRTCPeerConnection', 'RTCIceGatherer'].forEach(function(item) { + if (isWebRTCSupported) { + return; + } + + if (item in window) { + isWebRTCSupported = true; + } + }); + DetectRTC.isWebRTCSupported = isWebRTCSupported; + + //------- + DetectRTC.isORTCSupported = typeof RTCIceGatherer !== 'undefined'; + + // --------- Detect if system supports screen capturing API + var isScreenCapturingSupported = false; + if (DetectRTC.browser.isChrome && DetectRTC.browser.version >= 35) { + isScreenCapturingSupported = true; + } else if (DetectRTC.browser.isFirefox && DetectRTC.browser.version >= 34) { + isScreenCapturingSupported = true; + } else if (DetectRTC.browser.isEdge && DetectRTC.browser.version >= 17) { + isScreenCapturingSupported = true; // navigator.getDisplayMedia + } else if (DetectRTC.osName === 'Android' && DetectRTC.browser.isChrome) { + isScreenCapturingSupported = true; + } + + if (!/^(https:|chrome-extension:)$/g.test(location.protocol || '')) { + var isNonLocalHost = typeof document !== 'undefined' && typeof document.domain === 'string' && document.domain.search && document.domain.search(/localhost|127.0./g) === -1; + if (isNonLocalHost && (DetectRTC.browser.isChrome || DetectRTC.browser.isEdge || DetectRTC.browser.isOpera)) { + isScreenCapturingSupported = false; + } else if (DetectRTC.browser.isFirefox) { + isScreenCapturingSupported = false; + } + } + DetectRTC.isScreenCapturingSupported = isScreenCapturingSupported; + + // --------- Detect if WebAudio API are supported + var webAudio = { + isSupported: false, + isCreateMediaStreamSourceSupported: false + }; + + ['AudioContext', 'webkitAudioContext', 'mozAudioContext', 'msAudioContext'].forEach(function(item) { + if (webAudio.isSupported) { + return; + } + + if (item in window) { + webAudio.isSupported = true; + + if (window[item] && 'createMediaStreamSource' in window[item].prototype) { + webAudio.isCreateMediaStreamSourceSupported = true; + } + } + }); + DetectRTC.isAudioContextSupported = webAudio.isSupported; + DetectRTC.isCreateMediaStreamSourceSupported = webAudio.isCreateMediaStreamSourceSupported; + + // ---------- Detect if SCTP/RTP channels are supported. + + var isRtpDataChannelsSupported = false; + if (DetectRTC.browser.isChrome && DetectRTC.browser.version > 31) { + isRtpDataChannelsSupported = true; + } + DetectRTC.isRtpDataChannelsSupported = isRtpDataChannelsSupported; + + var isSCTPSupportd = false; + if (DetectRTC.browser.isFirefox && DetectRTC.browser.version > 28) { + isSCTPSupportd = true; + } else if (DetectRTC.browser.isChrome && DetectRTC.browser.version > 25) { + isSCTPSupportd = true; + } else if (DetectRTC.browser.isOpera && DetectRTC.browser.version >= 11) { + isSCTPSupportd = true; + } + DetectRTC.isSctpDataChannelsSupported = isSCTPSupportd; + + // --------- + + DetectRTC.isMobileDevice = isMobileDevice; // "isMobileDevice" boolean is defined in "getBrowserInfo.js" + + // ------ + var isGetUserMediaSupported = false; + if (navigator.getUserMedia) { + isGetUserMediaSupported = true; + } else if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) { + isGetUserMediaSupported = true; + } + + if (DetectRTC.browser.isChrome && DetectRTC.browser.version >= 46 && !/^(https:|chrome-extension:)$/g.test(location.protocol || '')) { + if (typeof document !== 'undefined' && typeof document.domain === 'string' && document.domain.search && document.domain.search(/localhost|127.0./g) === -1) { + isGetUserMediaSupported = 'Requires HTTPs'; + } + } + + if (DetectRTC.osName === 'Nodejs') { + isGetUserMediaSupported = false; + } + DetectRTC.isGetUserMediaSupported = isGetUserMediaSupported; + + var displayResolution = ''; + if (screen.width) { + var width = (screen.width) ? screen.width : ''; + var height = (screen.height) ? screen.height : ''; + displayResolution += '' + width + ' x ' + height; + } + DetectRTC.displayResolution = displayResolution; + + function getAspectRatio(w, h) { + function gcd(a, b) { + return (b == 0) ? a : gcd(b, a % b); + } + var r = gcd(w, h); + return (w / r) / (h / r); + } + + DetectRTC.displayAspectRatio = getAspectRatio(screen.width, screen.height).toFixed(2); + + // ---------- + DetectRTC.isCanvasSupportsStreamCapturing = isCanvasSupportsStreamCapturing; + DetectRTC.isVideoSupportsStreamCapturing = isVideoSupportsStreamCapturing; + + if (DetectRTC.browser.name == 'Chrome' && DetectRTC.browser.version >= 53) { + if (!DetectRTC.isCanvasSupportsStreamCapturing) { + DetectRTC.isCanvasSupportsStreamCapturing = 'Requires chrome flag: enable-experimental-web-platform-features'; + } + + if (!DetectRTC.isVideoSupportsStreamCapturing) { + DetectRTC.isVideoSupportsStreamCapturing = 'Requires chrome flag: enable-experimental-web-platform-features'; + } + } + + // ------ + DetectRTC.DetectLocalIPAddress = DetectLocalIPAddress; + + DetectRTC.isWebSocketsSupported = 'WebSocket' in window && 2 === window.WebSocket.CLOSING; + DetectRTC.isWebSocketsBlocked = !DetectRTC.isWebSocketsSupported; + + if (DetectRTC.osName === 'Nodejs') { + DetectRTC.isWebSocketsSupported = true; + DetectRTC.isWebSocketsBlocked = false; + } + + DetectRTC.checkWebSocketsSupport = function(callback) { + callback = callback || function() {}; + try { + var starttime; + var websocket = new WebSocket('wss://echo.websocket.org:443/'); + websocket.onopen = function() { + DetectRTC.isWebSocketsBlocked = false; + starttime = (new Date).getTime(); + websocket.send('ping'); + }; + websocket.onmessage = function() { + DetectRTC.WebsocketLatency = (new Date).getTime() - starttime + 'ms'; + callback(); + websocket.close(); + websocket = null; + }; + websocket.onerror = function() { + DetectRTC.isWebSocketsBlocked = true; + callback(); + }; + } catch (e) { + DetectRTC.isWebSocketsBlocked = true; + callback(); + } + }; + + // ------- + DetectRTC.load = function(callback) { + callback = callback || function() {}; + checkDeviceSupport(callback); + }; + + // check for microphone/camera support! + if (typeof checkDeviceSupport === 'function') { + // checkDeviceSupport(); + } + + if (typeof MediaDevices !== 'undefined') { + DetectRTC.MediaDevices = MediaDevices; + } else { + DetectRTC.MediaDevices = []; + } + + DetectRTC.hasMicrophone = hasMicrophone; + DetectRTC.hasSpeakers = hasSpeakers; + DetectRTC.hasWebcam = hasWebcam; + + DetectRTC.isWebsiteHasWebcamPermissions = isWebsiteHasWebcamPermissions; + DetectRTC.isWebsiteHasMicrophonePermissions = isWebsiteHasMicrophonePermissions; + + DetectRTC.audioInputDevices = audioInputDevices; + DetectRTC.audioOutputDevices = audioOutputDevices; + DetectRTC.videoInputDevices = videoInputDevices; + + // ------ + var isSetSinkIdSupported = false; + if (typeof document !== 'undefined' && typeof document.createElement === 'function' && 'setSinkId' in document.createElement('video')) { + isSetSinkIdSupported = true; + } + DetectRTC.isSetSinkIdSupported = isSetSinkIdSupported; + + // ----- + var isRTPSenderReplaceTracksSupported = false; + if (DetectRTC.browser.isFirefox && typeof mozRTCPeerConnection !== 'undefined' /*&& DetectRTC.browser.version > 39*/ ) { + /*global mozRTCPeerConnection:true */ + if ('getSenders' in mozRTCPeerConnection.prototype) { + isRTPSenderReplaceTracksSupported = true; + } + } else if (DetectRTC.browser.isChrome && typeof webkitRTCPeerConnection !== 'undefined') { + /*global webkitRTCPeerConnection:true */ + if ('getSenders' in webkitRTCPeerConnection.prototype) { + isRTPSenderReplaceTracksSupported = true; + } + } + DetectRTC.isRTPSenderReplaceTracksSupported = isRTPSenderReplaceTracksSupported; + + //------ + var isRemoteStreamProcessingSupported = false; + if (DetectRTC.browser.isFirefox && DetectRTC.browser.version > 38) { + isRemoteStreamProcessingSupported = true; + } + DetectRTC.isRemoteStreamProcessingSupported = isRemoteStreamProcessingSupported; + + //------- + var isApplyConstraintsSupported = false; + + /*global MediaStreamTrack:true */ + if (typeof MediaStreamTrack !== 'undefined' && 'applyConstraints' in MediaStreamTrack.prototype) { + isApplyConstraintsSupported = true; + } + DetectRTC.isApplyConstraintsSupported = isApplyConstraintsSupported; + + //------- + var isMultiMonitorScreenCapturingSupported = false; + if (DetectRTC.browser.isFirefox && DetectRTC.browser.version >= 43) { + // version 43 merely supports platforms for multi-monitors + // version 44 will support exact multi-monitor selection i.e. you can select any monitor for screen capturing. + isMultiMonitorScreenCapturingSupported = true; + } + DetectRTC.isMultiMonitorScreenCapturingSupported = isMultiMonitorScreenCapturingSupported; + + DetectRTC.isPromisesSupported = !!('Promise' in window); + + // version is generated by "grunt" + DetectRTC.version = '1.3.6'; + + if (typeof DetectRTC === 'undefined') { + window.DetectRTC = {}; + } + + var MediaStream = window.MediaStream; + + if (typeof MediaStream === 'undefined' && typeof webkitMediaStream !== 'undefined') { + MediaStream = webkitMediaStream; + } + + if (typeof MediaStream !== 'undefined' && typeof MediaStream === 'function') { + DetectRTC.MediaStream = Object.keys(MediaStream.prototype); + } else DetectRTC.MediaStream = false; + + if (typeof MediaStreamTrack !== 'undefined') { + DetectRTC.MediaStreamTrack = Object.keys(MediaStreamTrack.prototype); + } else DetectRTC.MediaStreamTrack = false; + + var RTCPeerConnection = window.RTCPeerConnection || window.mozRTCPeerConnection || window.webkitRTCPeerConnection; + + if (typeof RTCPeerConnection !== 'undefined') { + DetectRTC.RTCPeerConnection = Object.keys(RTCPeerConnection.prototype); + } else DetectRTC.RTCPeerConnection = false; + + window.DetectRTC = DetectRTC; + + if (typeof module !== 'undefined' /* && !!module.exports*/ ) { + module.exports = DetectRTC; + } + + if (typeof define === 'function' && define.amd) { + define('DetectRTC', [], function() { + return DetectRTC; + }); + } + })(); + + // globals.js + + if (typeof cordova !== 'undefined') { + DetectRTC.isMobileDevice = true; + DetectRTC.browser.name = 'Chrome'; + } + + if (navigator && navigator.userAgent && navigator.userAgent.indexOf('Crosswalk') !== -1) { + DetectRTC.isMobileDevice = true; + DetectRTC.browser.name = 'Chrome'; + } + + function fireEvent(obj, eventName, args) { + if (typeof CustomEvent === 'undefined') { + return; + } + + var eventDetail = { + arguments: args, + __exposedProps__: args + }; + + var event = new CustomEvent(eventName, eventDetail); + obj.dispatchEvent(event); + } + + function setHarkEvents(connection, streamEvent) { + if (!streamEvent.stream || !streamEvent.stream.getAudioTracks || !streamEvent.stream.getAudioTracks().length) return; + + if (!connection || !streamEvent) { + throw 'Both arguments are required.'; + } + + if (!connection.onspeaking || !connection.onsilence) { + return; + } + + if (typeof hark === 'undefined') { + throw 'hark.js not found.'; + } + + hark(streamEvent.stream, { + onspeaking: function() { + connection.onspeaking(streamEvent); + }, + onsilence: function() { + connection.onsilence(streamEvent); + }, + onvolumechange: function(volume, threshold) { + if (!connection.onvolumechange) { + return; + } + connection.onvolumechange(merge({ + volume: volume, + threshold: threshold + }, streamEvent)); + } + }); + } + + function setMuteHandlers(connection, streamEvent) { + if (!streamEvent.stream || !streamEvent.stream || !streamEvent.stream.addEventListener) return; + + streamEvent.stream.addEventListener('mute', function(event) { + event = connection.streamEvents[streamEvent.streamid]; + + event.session = { + audio: event.muteType === 'audio', + video: event.muteType === 'video' + }; + + connection.onmute(event); + }, false); + + streamEvent.stream.addEventListener('unmute', function(event) { + event = connection.streamEvents[streamEvent.streamid]; + + event.session = { + audio: event.unmuteType === 'audio', + video: event.unmuteType === 'video' + }; + + connection.onunmute(event); + }, false); + } + + function getRandomString() { + if (window.crypto && window.crypto.getRandomValues && navigator.userAgent.indexOf('Safari') === -1) { + var a = window.crypto.getRandomValues(new Uint32Array(3)), + token = ''; + for (var i = 0, l = a.length; i < l; i++) { + token += a[i].toString(36); + } + return token; + } else { + return (Math.random() * new Date().getTime()).toString(36).replace(/\./g, ''); + } + } + + // Get HTMLAudioElement/HTMLVideoElement accordingly + // todo: add API documentation for connection.autoCreateMediaElement + + function getRMCMediaElement(stream, callback, connection) { + if (!connection.autoCreateMediaElement) { + callback({}); + return; + } + + var isAudioOnly = false; + if (!!stream.getVideoTracks && !stream.getVideoTracks().length && !stream.isVideo && !stream.isScreen) { + isAudioOnly = true; + } + + if (DetectRTC.browser.name === 'Firefox') { + if (connection.session.video || connection.session.screen) { + isAudioOnly = false; + } + } + + var mediaElement = document.createElement(isAudioOnly ? 'audio' : 'video'); + + mediaElement.srcObject = stream; + + try { + mediaElement.setAttributeNode(document.createAttribute('autoplay')); + mediaElement.setAttributeNode(document.createAttribute('playsinline')); + mediaElement.setAttributeNode(document.createAttribute('controls')); + } catch (e) { + mediaElement.setAttribute('autoplay', true); + mediaElement.setAttribute('playsinline', true); + mediaElement.setAttribute('controls', true); + } + + // http://goo.gl/WZ5nFl + // Firefox don't yet support onended for any stream (remote/local) + if (DetectRTC.browser.name === 'Firefox') { + var streamEndedEvent = 'ended'; + + if ('oninactive' in mediaElement) { + streamEndedEvent = 'inactive'; + } + + mediaElement.addEventListener(streamEndedEvent, function() { + // fireEvent(stream, streamEndedEvent, stream); + currentUserMediaRequest.remove(stream.idInstance); + + if (stream.type === 'local') { + streamEndedEvent = 'ended'; + + if ('oninactive' in stream) { + streamEndedEvent = 'inactive'; + } + + StreamsHandler.onSyncNeeded(stream.streamid, streamEndedEvent); + + connection.attachStreams.forEach(function(aStream, idx) { + if (stream.streamid === aStream.streamid) { + delete connection.attachStreams[idx]; + } + }); + + var newStreamsArray = []; + connection.attachStreams.forEach(function(aStream) { + if (aStream) { + newStreamsArray.push(aStream); + } + }); + connection.attachStreams = newStreamsArray; + + var streamEvent = connection.streamEvents[stream.streamid]; + + if (streamEvent) { + connection.onstreamended(streamEvent); + return; + } + if (this.parentNode) { + this.parentNode.removeChild(this); + } + } + }, false); + } + + var played = mediaElement.play(); + if (typeof played !== 'undefined') { + var cbFired = false; + setTimeout(function() { + if (!cbFired) { + cbFired = true; + callback(mediaElement); + } + }, 1000); + played.then(function() { + if (cbFired) return; + cbFired = true; + callback(mediaElement); + }).catch(function(error) { + if (cbFired) return; + cbFired = true; + callback(mediaElement); + }); + } else { + callback(mediaElement); + } + } + + // if IE + if (!window.addEventListener) { + window.addEventListener = function(el, eventName, eventHandler) { + if (!el.attachEvent) { + return; + } + el.attachEvent('on' + eventName, eventHandler); + }; + } + + function listenEventHandler(eventName, eventHandler) { + window.removeEventListener(eventName, eventHandler); + window.addEventListener(eventName, eventHandler, false); + } + + window.attachEventListener = function(video, type, listener, useCapture) { + video.addEventListener(type, listener, useCapture); + }; + + function removeNullEntries(array) { + var newArray = []; + array.forEach(function(item) { + if (item) { + newArray.push(item); + } + }); + return newArray; + } + + + function isData(session) { + return !session.audio && !session.video && !session.screen && session.data; + } + + function isNull(obj) { + return typeof obj === 'undefined'; + } + + function isString(obj) { + return typeof obj === 'string'; + } + + var MediaStream = window.MediaStream; + + if (typeof MediaStream === 'undefined' && typeof webkitMediaStream !== 'undefined') { + MediaStream = webkitMediaStream; + } + + /*global MediaStream:true */ + if (typeof MediaStream !== 'undefined') { + if (!('stop' in MediaStream.prototype)) { + MediaStream.prototype.stop = function() { + this.getTracks().forEach(function(track) { + track.stop(); + }); + }; + } + } + + function isAudioPlusTab(connection, audioPlusTab) { + if (connection.session.audio && connection.session.audio === 'two-way') { + return false; + } + + if (DetectRTC.browser.name === 'Firefox' && audioPlusTab !== false) { + return true; + } + + if (DetectRTC.browser.name !== 'Chrome' || DetectRTC.browser.version < 50) return false; + + if (typeof audioPlusTab === true) { + return true; + } + + if (typeof audioPlusTab === 'undefined' && connection.session.audio && connection.session.screen && !connection.session.video) { + audioPlusTab = true; + return true; + } + + return false; + } + + function getAudioScreenConstraints(screen_constraints) { + if (DetectRTC.browser.name === 'Firefox') { + return true; + } + + if (DetectRTC.browser.name !== 'Chrome') return false; + + return { + mandatory: { + chromeMediaSource: screen_constraints.mandatory.chromeMediaSource, + chromeMediaSourceId: screen_constraints.mandatory.chromeMediaSourceId + } + }; + } + + window.iOSDefaultAudioOutputDevice = window.iOSDefaultAudioOutputDevice || 'speaker'; // earpiece or speaker + + if (typeof window.enableAdapter === 'undefined') { + if (DetectRTC.browser.name === 'Firefox' && DetectRTC.browser.version >= 54) { + window.enableAdapter = true; + } + + if (DetectRTC.browser.name === 'Chrome' && DetectRTC.browser.version >= 60) { + // window.enableAdapter = true; + } + + if (typeof adapter !== 'undefined' && adapter.browserDetails && typeof adapter.browserDetails.browser === 'string') { + window.enableAdapter = true; + } + } + + if (!window.enableAdapter) { + if (typeof URL.createObjectURL === 'undefined') { + URL.createObjectURL = function(stream) { + return 'blob:https://' + document.domain + '/' + getRandomString(); + }; + } + + if (!('srcObject' in HTMLMediaElement.prototype)) { + HTMLMediaElement.prototype.srcObject = function(stream) { + if ('mozSrcObject' in this) { + this.mozSrcObject = stream; + return; + } + + this.src = URL.createObjectURL(stream); + }; + } + + // need RTCPeerConnection shim here + } + + // ios-hacks.js + + function setCordovaAPIs() { + // if (DetectRTC.osName !== 'iOS') return; + if (typeof cordova === 'undefined' || typeof cordova.plugins === 'undefined' || typeof cordova.plugins.iosrtc === 'undefined') return; + + var iosrtc = cordova.plugins.iosrtc; + window.webkitRTCPeerConnection = iosrtc.RTCPeerConnection; + window.RTCSessionDescription = iosrtc.RTCSessionDescription; + window.RTCIceCandidate = iosrtc.RTCIceCandidate; + window.MediaStream = iosrtc.MediaStream; + window.MediaStreamTrack = iosrtc.MediaStreamTrack; + navigator.getUserMedia = navigator.webkitGetUserMedia = iosrtc.getUserMedia; + + iosrtc.debug.enable('iosrtc*'); + if (typeof iosrtc.selectAudioOutput == 'function') { + iosrtc.selectAudioOutput(window.iOSDefaultAudioOutputDevice || 'speaker'); // earpiece or speaker + } + iosrtc.registerGlobals(); + } + + document.addEventListener('deviceready', setCordovaAPIs, false); + setCordovaAPIs(); + + // RTCPeerConnection.js + + var defaults = {}; + + function setSdpConstraints(config) { + var sdpConstraints = { + OfferToReceiveAudio: !!config.OfferToReceiveAudio, + OfferToReceiveVideo: !!config.OfferToReceiveVideo + }; + + var oldBrowser = !window.enableAdapter; + + if (DetectRTC.browser.name === 'Chrome' && DetectRTC.browser.version >= 60) { + // oldBrowser = false; + } + + if (DetectRTC.browser.name === 'Firefox' && DetectRTC.browser.version >= 54) { + oldBrowser = false; + } + + if (oldBrowser) { + sdpConstraints = { + mandatory: sdpConstraints, + optional: [{ + VoiceActivityDetection: false + }] + }; + } + + return sdpConstraints; + } + + var RTCPeerConnection; + if (typeof window.RTCPeerConnection !== 'undefined') { + RTCPeerConnection = window.RTCPeerConnection; + } else if (typeof mozRTCPeerConnection !== 'undefined') { + RTCPeerConnection = mozRTCPeerConnection; + } else if (typeof webkitRTCPeerConnection !== 'undefined') { + RTCPeerConnection = webkitRTCPeerConnection; + } + + var RTCSessionDescription = window.RTCSessionDescription || window.mozRTCSessionDescription; + var RTCIceCandidate = window.RTCIceCandidate || window.mozRTCIceCandidate; + var MediaStreamTrack = window.MediaStreamTrack; + + function PeerInitiator(config) { + if (typeof window.RTCPeerConnection !== 'undefined') { + RTCPeerConnection = window.RTCPeerConnection; + } else if (typeof mozRTCPeerConnection !== 'undefined') { + RTCPeerConnection = mozRTCPeerConnection; + } else if (typeof webkitRTCPeerConnection !== 'undefined') { + RTCPeerConnection = webkitRTCPeerConnection; + } + + RTCSessionDescription = window.RTCSessionDescription || window.mozRTCSessionDescription; + RTCIceCandidate = window.RTCIceCandidate || window.mozRTCIceCandidate; + MediaStreamTrack = window.MediaStreamTrack; + + if (!RTCPeerConnection) { + throw 'WebRTC 1.0 (RTCPeerConnection) API are NOT available in this browser.'; + } + + var connection = config.rtcMultiConnection; + + this.extra = config.remoteSdp ? config.remoteSdp.extra : connection.extra; + this.userid = config.userid; + this.streams = []; + this.channels = config.channels || []; + this.connectionDescription = config.connectionDescription; + + this.addStream = function(session) { + connection.addStream(session, self.userid); + }; + + this.removeStream = function(streamid) { + connection.removeStream(streamid, self.userid); + }; + + var self = this; + + if (config.remoteSdp) { + this.connectionDescription = config.remoteSdp.connectionDescription; + } + + var allRemoteStreams = {}; + + defaults.sdpConstraints = setSdpConstraints({ + OfferToReceiveAudio: true, + OfferToReceiveVideo: true + }); + + var peer; + + var renegotiatingPeer = !!config.renegotiatingPeer; + if (config.remoteSdp) { + renegotiatingPeer = !!config.remoteSdp.renegotiatingPeer; + } + + var localStreams = []; + connection.attachStreams.forEach(function(stream) { + if (!!stream) { + localStreams.push(stream); + } + }); + + if (!renegotiatingPeer) { + var iceTransports = 'all'; + if (connection.candidates.turn || connection.candidates.relay) { + if (!connection.candidates.stun && !connection.candidates.reflexive && !connection.candidates.host) { + iceTransports = 'relay'; + } + } + + try { + var params = {}; + + if (DetectRTC.browser.name !== 'Chrome') { + params.iceServers = connection.iceServers; + } + + if (DetectRTC.browser.name === 'Chrome') { + params = { + iceServers: connection.iceServers, + iceTransportPolicy: connection.iceTransportPolicy || iceTransports, + // rtcpMuxPolicy: connection.rtcpMuxPolicy || 'require', // or negotiate + // bundlePolicy: 'max-bundle', + // iceCandidatePoolSize: connection.iceCandidatePoolSize || 0 + }; + } + + if (!connection.iceServers.length) { + params = null; + connection.optionalArgument = null; + } + + peer = new RTCPeerConnection(params, connection.optionalArgument); + } catch (e) { + try { + var params = { + iceServers: connection.iceServers + }; + + peer = new RTCPeerConnection(params); + } catch (e) { + peer = new RTCPeerConnection(); + } + } + } else { + peer = config.peerRef; + } + + if (!peer.getRemoteStreams && peer.getReceivers) { + peer.getRemoteStreams = function() { + var stream = new MediaStream(); + peer.getReceivers().forEach(function(receiver) { + stream.addTrack(receiver.track); + }); + return [stream]; + }; + } + + if (!peer.getLocalStreams && peer.getSenders) { + peer.getLocalStreams = function() { + var stream = new MediaStream(); + peer.getSenders().forEach(function(sender) { + stream.addTrack(sender.track); + }); + return [stream]; + }; + } + + peer.onicecandidate = function(event) { + if (!event.candidate) { + if (!connection.trickleIce) { + var localSdp = peer.localDescription; + config.onLocalSdp({ + type: localSdp.type, + sdp: localSdp.sdp, + remotePeerSdpConstraints: config.remotePeerSdpConstraints || false, + renegotiatingPeer: !!config.renegotiatingPeer || false, + connectionDescription: self.connectionDescription, + dontGetRemoteStream: !!config.dontGetRemoteStream, + extra: connection ? connection.extra : {}, + streamsToShare: streamsToShare + }); + } + return; + } + + if (!connection.trickleIce) return; + config.onLocalCandidate({ + candidate: event.candidate.candidate, + sdpMid: event.candidate.sdpMid, + sdpMLineIndex: event.candidate.sdpMLineIndex + }); + }; + + localStreams.forEach(function(localStream) { + if (config.remoteSdp && config.remoteSdp.remotePeerSdpConstraints && config.remoteSdp.remotePeerSdpConstraints.dontGetRemoteStream) { + return; + } + + if (config.dontAttachLocalStream) { + return; + } + + localStream = connection.beforeAddingStream(localStream, self); + + if (!localStream) return; + + peer.getLocalStreams().forEach(function(stream) { + if (localStream && stream.id == localStream.id) { + localStream = null; + } + }); + + if (localStream && typeof peer.addTrack === 'function') { + localStream.getTracks().forEach(function(track) { + try { + peer.addTrack(track, localStream); + } catch (e) {} + }); + } else if (localStream && typeof peer.addStream === 'function') { + peer.addStream(localStream); + } else { + try { + peer.addStream(localStream); + } catch (e) { + localStream && localStream.getTracks().forEach(function(track) { + try { + peer.addTrack(track, localStream); + } catch (e) {} + }); + } + } + }); + + peer.oniceconnectionstatechange = peer.onsignalingstatechange = function() { + var extra = self.extra; + if (connection.peers[self.userid]) { + extra = connection.peers[self.userid].extra || extra; + } + + if (!peer) { + return; + } + + config.onPeerStateChanged({ + iceConnectionState: peer.iceConnectionState, + iceGatheringState: peer.iceGatheringState, + signalingState: peer.signalingState, + extra: extra, + userid: self.userid + }); + + if (peer && peer.iceConnectionState && peer.iceConnectionState.search(/closed|failed/gi) !== -1 && self.streams instanceof Array) { + self.streams.forEach(function(stream) { + var streamEvent = connection.streamEvents[stream.id] || { + streamid: stream.id, + stream: stream, + type: 'remote' + }; + + connection.onstreamended(streamEvent); + }); + } + }; + + var sdpConstraints = { + OfferToReceiveAudio: !!localStreams.length, + OfferToReceiveVideo: !!localStreams.length + }; + + if (config.localPeerSdpConstraints) sdpConstraints = config.localPeerSdpConstraints; + + defaults.sdpConstraints = setSdpConstraints(sdpConstraints); + + var streamObject; + var dontDuplicate = {}; + + var incomingStreamEvent = 'track'; + + if (!window.enableAdapter) { + incomingStreamEvent = 'addstream'; + } + + peer.addEventListener(incomingStreamEvent, function(event) { + if (!event) return; + + if (incomingStreamEvent === 'track') { + event.stream = event.streams[event.streams.length - 1]; + } + + if (dontDuplicate[event.stream.id] && DetectRTC.browser.name !== 'Safari') return; + dontDuplicate[event.stream.id] = event.stream.id; + + var streamsToShare = {}; + if (config.remoteSdp && config.remoteSdp.streamsToShare) { + streamsToShare = config.remoteSdp.streamsToShare; + } else if (config.streamsToShare) { + streamsToShare = config.streamsToShare; + } + + var streamToShare = streamsToShare[event.stream.id]; + if (streamToShare) { + event.stream.isAudio = streamToShare.isAudio; + event.stream.isVideo = streamToShare.isVideo; + event.stream.isScreen = streamToShare.isScreen; + } else { + event.stream.isVideo = !!event.stream.getVideoTracks().length; + event.stream.isAudio = !event.stream.isVideo; + event.stream.isScreen = false; + } + + event.stream.streamid = event.stream.id; + if (DetectRTC.browser.name == 'Firefox' || !event.stream.stop) { + event.stream.stop = function() { + var streamEndedEvent = 'ended'; + + if ('oninactive' in event.stream) { + streamEndedEvent = 'inactive'; + } + fireEvent(event.stream, streamEndedEvent); + }; + } + allRemoteStreams[event.stream.id] = event.stream; + config.onRemoteStream(event.stream); + }, false); + + peer.onremovestream = function(event) { + // this event doesn't works anymore + event.stream.streamid = event.stream.id; + + if (allRemoteStreams[event.stream.id]) { + delete allRemoteStreams[event.stream.id]; + } + + config.onRemoteStreamRemoved(event.stream); + }; + + this.addRemoteCandidate = function(remoteCandidate) { + peer.addIceCandidate(new RTCIceCandidate(remoteCandidate)); + }; + + function oldAddRemoteSdp(remoteSdp, cb) { + cb = cb || function() {}; + + if (DetectRTC.browser.name !== 'Safari') { + remoteSdp.sdp = connection.processSdp(remoteSdp.sdp); + } + peer.setRemoteDescription(new RTCSessionDescription(remoteSdp), cb, function(error) { + if (!!connection.enableLogs) { + console.error('setRemoteDescription failed', '\n', error, '\n', remoteSdp.sdp); + } + + cb(); + }); + } + + this.addRemoteSdp = function(remoteSdp, cb) { + cb = cb || function() {}; + + if (!window.enableAdapter) { + return oldAddRemoteSdp(remoteSdp, cb); + } + + if (DetectRTC.browser.name !== 'Safari') { + remoteSdp.sdp = connection.processSdp(remoteSdp.sdp); + } + peer.setRemoteDescription(new RTCSessionDescription(remoteSdp)).then(cb, function(error) { + if (!!connection.enableLogs) { + console.error('setRemoteDescription failed', '\n', error, '\n', remoteSdp.sdp); + } + + cb(); + }); + }; + + var isOfferer = true; + + if (config.remoteSdp) { + isOfferer = false; + } + + this.createDataChannel = function() { + var channel = peer.createDataChannel('sctp', {}); + setChannelEvents(channel); + }; + + if (connection.session.data === true && !renegotiatingPeer) { + if (!isOfferer) { + peer.ondatachannel = function(event) { + var channel = event.channel; + setChannelEvents(channel); + }; + } else { + this.createDataChannel(); + } + } + + this.enableDisableVideoEncoding = function(enable) { + var rtcp; + peer.getSenders().forEach(function(sender) { + if (!rtcp && sender.track.kind === 'video') { + rtcp = sender; + } + }); + + if (!rtcp || !rtcp.getParameters) return; + + var parameters = rtcp.getParameters(); + parameters.encodings[1] && (parameters.encodings[1].active = !!enable); + parameters.encodings[2] && (parameters.encodings[2].active = !!enable); + rtcp.setParameters(parameters); + }; + + if (config.remoteSdp) { + if (config.remoteSdp.remotePeerSdpConstraints) { + sdpConstraints = config.remoteSdp.remotePeerSdpConstraints; + } + defaults.sdpConstraints = setSdpConstraints(sdpConstraints); + this.addRemoteSdp(config.remoteSdp, function() { + createOfferOrAnswer('createAnswer'); + }); + } + + function setChannelEvents(channel) { + // force ArrayBuffer in Firefox; which uses "Blob" by default. + channel.binaryType = 'arraybuffer'; + + channel.onmessage = function(event) { + config.onDataChannelMessage(event.data); + }; + + channel.onopen = function() { + config.onDataChannelOpened(channel); + }; + + channel.onerror = function(error) { + config.onDataChannelError(error); + }; + + channel.onclose = function(event) { + config.onDataChannelClosed(event); + }; + + channel.internalSend = channel.send; + channel.send = function(data) { + if (channel.readyState !== 'open') { + return; + } + + channel.internalSend(data); + }; + + peer.channel = channel; + } + + if (connection.session.audio == 'two-way' || connection.session.video == 'two-way' || connection.session.screen == 'two-way') { + defaults.sdpConstraints = setSdpConstraints({ + OfferToReceiveAudio: connection.session.audio == 'two-way' || (config.remoteSdp && config.remoteSdp.remotePeerSdpConstraints && config.remoteSdp.remotePeerSdpConstraints.OfferToReceiveAudio), + OfferToReceiveVideo: connection.session.video == 'two-way' || connection.session.screen == 'two-way' || (config.remoteSdp && config.remoteSdp.remotePeerSdpConstraints && config.remoteSdp.remotePeerSdpConstraints.OfferToReceiveAudio) + }); + } + + var streamsToShare = {}; + peer.getLocalStreams().forEach(function(stream) { + streamsToShare[stream.streamid] = { + isAudio: !!stream.isAudio, + isVideo: !!stream.isVideo, + isScreen: !!stream.isScreen + }; + }); + + function oldCreateOfferOrAnswer(_method) { + peer[_method](function(localSdp) { + if (DetectRTC.browser.name !== 'Safari') { + localSdp.sdp = connection.processSdp(localSdp.sdp); + } + peer.setLocalDescription(localSdp, function() { + if (!connection.trickleIce) return; + + config.onLocalSdp({ + type: localSdp.type, + sdp: localSdp.sdp, + remotePeerSdpConstraints: config.remotePeerSdpConstraints || false, + renegotiatingPeer: !!config.renegotiatingPeer || false, + connectionDescription: self.connectionDescription, + dontGetRemoteStream: !!config.dontGetRemoteStream, + extra: connection ? connection.extra : {}, + streamsToShare: streamsToShare + }); + + connection.onSettingLocalDescription(self); + }, function(error) { + if (!!connection.enableLogs) { + console.error('setLocalDescription-error', error); + } + }); + }, function(error) { + if (!!connection.enableLogs) { + console.error('sdp-' + _method + '-error', error); + } + }, defaults.sdpConstraints); + } + + function createOfferOrAnswer(_method) { + if (!window.enableAdapter) { + return oldCreateOfferOrAnswer(_method); + } + + peer[_method](defaults.sdpConstraints).then(function(localSdp) { + if (DetectRTC.browser.name !== 'Safari') { + localSdp.sdp = connection.processSdp(localSdp.sdp); + } + peer.setLocalDescription(localSdp).then(function() { + if (!connection.trickleIce) return; + + config.onLocalSdp({ + type: localSdp.type, + sdp: localSdp.sdp, + remotePeerSdpConstraints: config.remotePeerSdpConstraints || false, + renegotiatingPeer: !!config.renegotiatingPeer || false, + connectionDescription: self.connectionDescription, + dontGetRemoteStream: !!config.dontGetRemoteStream, + extra: connection ? connection.extra : {}, + streamsToShare: streamsToShare + }); + + connection.onSettingLocalDescription(self); + }, function(error) { + if (!connection.enableLogs) return; + console.error('setLocalDescription error', error); + }); + }, function(error) { + if (!!connection.enableLogs) { + console.error('sdp-error', error); + } + }); + } + + if (isOfferer) { + createOfferOrAnswer('createOffer'); + } + + peer.nativeClose = peer.close; + peer.close = function() { + if (!peer) { + return; + } + + try { + if (peer.nativeClose !== peer.close) { + peer.nativeClose(); + } + } catch (e) {} + + peer = null; + self.peer = null; + }; + + this.peer = peer; + } + + // CodecsHandler.js + + var CodecsHandler = (function() { + function preferCodec(sdp, codecName) { + var info = splitLines(sdp); + + if (!info.videoCodecNumbers) { + return sdp; + } + + if (codecName === 'vp8' && info.vp8LineNumber === info.videoCodecNumbers[0]) { + return sdp; + } + + if (codecName === 'vp9' && info.vp9LineNumber === info.videoCodecNumbers[0]) { + return sdp; + } + + if (codecName === 'h264' && info.h264LineNumber === info.videoCodecNumbers[0]) { + return sdp; + } + + sdp = preferCodecHelper(sdp, codecName, info); + + return sdp; + } + + function preferCodecHelper(sdp, codec, info, ignore) { + var preferCodecNumber = ''; + + if (codec === 'vp8') { + if (!info.vp8LineNumber) { + return sdp; + } + preferCodecNumber = info.vp8LineNumber; + } + + if (codec === 'vp9') { + if (!info.vp9LineNumber) { + return sdp; + } + preferCodecNumber = info.vp9LineNumber; + } + + if (codec === 'h264') { + if (!info.h264LineNumber) { + return sdp; + } + + preferCodecNumber = info.h264LineNumber; + } + + var newLine = info.videoCodecNumbersOriginal.split('SAVPF')[0] + 'SAVPF '; + + var newOrder = [preferCodecNumber]; + + if (ignore) { + newOrder = []; + } + + info.videoCodecNumbers.forEach(function(codecNumber) { + if (codecNumber === preferCodecNumber) return; + newOrder.push(codecNumber); + }); + + newLine += newOrder.join(' '); + + sdp = sdp.replace(info.videoCodecNumbersOriginal, newLine); + return sdp; + } + + function splitLines(sdp) { + var info = {}; + sdp.split('\n').forEach(function(line) { + if (line.indexOf('m=video') === 0) { + info.videoCodecNumbers = []; + line.split('SAVPF')[1].split(' ').forEach(function(codecNumber) { + codecNumber = codecNumber.trim(); + if (!codecNumber || !codecNumber.length) return; + info.videoCodecNumbers.push(codecNumber); + info.videoCodecNumbersOriginal = line; + }); + } + + if (line.indexOf('VP8/90000') !== -1 && !info.vp8LineNumber) { + info.vp8LineNumber = line.replace('a=rtpmap:', '').split(' ')[0]; + } + + if (line.indexOf('VP9/90000') !== -1 && !info.vp9LineNumber) { + info.vp9LineNumber = line.replace('a=rtpmap:', '').split(' ')[0]; + } + + if (line.indexOf('H264/90000') !== -1 && !info.h264LineNumber) { + info.h264LineNumber = line.replace('a=rtpmap:', '').split(' ')[0]; + } + }); + + return info; + } + + function removeVPX(sdp) { + var info = splitLines(sdp); + + // last parameter below means: ignore these codecs + sdp = preferCodecHelper(sdp, 'vp9', info, true); + sdp = preferCodecHelper(sdp, 'vp8', info, true); + + return sdp; + } + + function disableNACK(sdp) { + if (!sdp || typeof sdp !== 'string') { + throw 'Invalid arguments.'; + } + + sdp = sdp.replace('a=rtcp-fb:126 nack\r\n', ''); + sdp = sdp.replace('a=rtcp-fb:126 nack pli\r\n', 'a=rtcp-fb:126 pli\r\n'); + sdp = sdp.replace('a=rtcp-fb:97 nack\r\n', ''); + sdp = sdp.replace('a=rtcp-fb:97 nack pli\r\n', 'a=rtcp-fb:97 pli\r\n'); + + return sdp; + } + + function prioritize(codecMimeType, peer) { + if (!peer || !peer.getSenders || !peer.getSenders().length) { + return; + } + + if (!codecMimeType || typeof codecMimeType !== 'string') { + throw 'Invalid arguments.'; + } + + peer.getSenders().forEach(function(sender) { + var params = sender.getParameters(); + for (var i = 0; i < params.codecs.length; i++) { + if (params.codecs[i].mimeType == codecMimeType) { + params.codecs.unshift(params.codecs.splice(i, 1)); + break; + } + } + sender.setParameters(params); + }); + } + + function removeNonG722(sdp) { + return sdp.replace(/m=audio ([0-9]+) RTP\/SAVPF ([0-9 ]*)/g, 'm=audio $1 RTP\/SAVPF 9'); + } + + function setBAS(sdp, bandwidth, isScreen) { + if (!bandwidth) { + return sdp; + } + + if (typeof isFirefox !== 'undefined' && isFirefox) { + return sdp; + } + + if (isScreen) { + if (!bandwidth.screen) { + console.warn('It seems that you are not using bandwidth for screen. Screen sharing is expected to fail.'); + } else if (bandwidth.screen < 300) { + console.warn('It seems that you are using wrong bandwidth value for screen. Screen sharing is expected to fail.'); + } + } + + // if screen; must use at least 300kbs + if (bandwidth.screen && isScreen) { + sdp = sdp.replace(/b=AS([^\r\n]+\r\n)/g, ''); + sdp = sdp.replace(/a=mid:video\r\n/g, 'a=mid:video\r\nb=AS:' + bandwidth.screen + '\r\n'); + } + + // remove existing bandwidth lines + if (bandwidth.audio || bandwidth.video) { + sdp = sdp.replace(/b=AS([^\r\n]+\r\n)/g, ''); + } + + if (bandwidth.audio) { + sdp = sdp.replace(/a=mid:audio\r\n/g, 'a=mid:audio\r\nb=AS:' + bandwidth.audio + '\r\n'); + } + + if (bandwidth.screen) { + sdp = sdp.replace(/a=mid:video\r\n/g, 'a=mid:video\r\nb=AS:' + bandwidth.screen + '\r\n'); + } else if (bandwidth.video) { + sdp = sdp.replace(/a=mid:video\r\n/g, 'a=mid:video\r\nb=AS:' + bandwidth.video + '\r\n'); + } + + return sdp; + } + + // Find the line in sdpLines that starts with |prefix|, and, if specified, + // contains |substr| (case-insensitive search). + function findLine(sdpLines, prefix, substr) { + return findLineInRange(sdpLines, 0, -1, prefix, substr); + } + + // Find the line in sdpLines[startLine...endLine - 1] that starts with |prefix| + // and, if specified, contains |substr| (case-insensitive search). + function findLineInRange(sdpLines, startLine, endLine, prefix, substr) { + var realEndLine = endLine !== -1 ? endLine : sdpLines.length; + for (var i = startLine; i < realEndLine; ++i) { + if (sdpLines[i].indexOf(prefix) === 0) { + if (!substr || + sdpLines[i].toLowerCase().indexOf(substr.toLowerCase()) !== -1) { + return i; + } + } + } + return null; + } + + // Gets the codec payload type from an a=rtpmap:X line. + function getCodecPayloadType(sdpLine) { + var pattern = new RegExp('a=rtpmap:(\\d+) \\w+\\/\\d+'); + var result = sdpLine.match(pattern); + return (result && result.length === 2) ? result[1] : null; + } + + function setVideoBitrates(sdp, params) { + params = params || {}; + var xgoogle_min_bitrate = params.min; + var xgoogle_max_bitrate = params.max; + + var sdpLines = sdp.split('\r\n'); + + // VP8 + var vp8Index = findLine(sdpLines, 'a=rtpmap', 'VP8/90000'); + var vp8Payload; + if (vp8Index) { + vp8Payload = getCodecPayloadType(sdpLines[vp8Index]); + } + + if (!vp8Payload) { + return sdp; + } + + var rtxIndex = findLine(sdpLines, 'a=rtpmap', 'rtx/90000'); + var rtxPayload; + if (rtxIndex) { + rtxPayload = getCodecPayloadType(sdpLines[rtxIndex]); + } + + if (!rtxIndex) { + return sdp; + } + + var rtxFmtpLineIndex = findLine(sdpLines, 'a=fmtp:' + rtxPayload.toString()); + if (rtxFmtpLineIndex !== null) { + var appendrtxNext = '\r\n'; + appendrtxNext += 'a=fmtp:' + vp8Payload + ' x-google-min-bitrate=' + (xgoogle_min_bitrate || '228') + '; x-google-max-bitrate=' + (xgoogle_max_bitrate || '228'); + sdpLines[rtxFmtpLineIndex] = sdpLines[rtxFmtpLineIndex].concat(appendrtxNext); + sdp = sdpLines.join('\r\n'); + } + + return sdp; + } + + function setOpusAttributes(sdp, params) { + params = params || {}; + + var sdpLines = sdp.split('\r\n'); + + // Opus + var opusIndex = findLine(sdpLines, 'a=rtpmap', 'opus/48000'); + var opusPayload; + if (opusIndex) { + opusPayload = getCodecPayloadType(sdpLines[opusIndex]); + } + + if (!opusPayload) { + return sdp; + } + + var opusFmtpLineIndex = findLine(sdpLines, 'a=fmtp:' + opusPayload.toString()); + if (opusFmtpLineIndex === null) { + return sdp; + } + + var appendOpusNext = ''; + appendOpusNext += '; stereo=' + (typeof params.stereo != 'undefined' ? params.stereo : '1'); + appendOpusNext += '; sprop-stereo=' + (typeof params['sprop-stereo'] != 'undefined' ? params['sprop-stereo'] : '1'); + + if (typeof params.maxaveragebitrate != 'undefined') { + appendOpusNext += '; maxaveragebitrate=' + (params.maxaveragebitrate || 128 * 1024 * 8); + } + + if (typeof params.maxplaybackrate != 'undefined') { + appendOpusNext += '; maxplaybackrate=' + (params.maxplaybackrate || 128 * 1024 * 8); + } + + if (typeof params.cbr != 'undefined') { + appendOpusNext += '; cbr=' + (typeof params.cbr != 'undefined' ? params.cbr : '1'); + } + + if (typeof params.useinbandfec != 'undefined') { + appendOpusNext += '; useinbandfec=' + params.useinbandfec; + } + + if (typeof params.usedtx != 'undefined') { + appendOpusNext += '; usedtx=' + params.usedtx; + } + + if (typeof params.maxptime != 'undefined') { + appendOpusNext += '\r\na=maxptime:' + params.maxptime; + } + + sdpLines[opusFmtpLineIndex] = sdpLines[opusFmtpLineIndex].concat(appendOpusNext); + + sdp = sdpLines.join('\r\n'); + return sdp; + } + + // forceStereoAudio => via webrtcexample.com + // requires getUserMedia => echoCancellation:false + function forceStereoAudio(sdp) { + var sdpLines = sdp.split('\r\n'); + var fmtpLineIndex = null; + for (var i = 0; i < sdpLines.length; i++) { + if (sdpLines[i].search('opus/48000') !== -1) { + var opusPayload = extractSdp(sdpLines[i], /:(\d+) opus\/48000/i); + break; + } + } + for (var i = 0; i < sdpLines.length; i++) { + if (sdpLines[i].search('a=fmtp') !== -1) { + var payload = extractSdp(sdpLines[i], /a=fmtp:(\d+)/); + if (payload === opusPayload) { + fmtpLineIndex = i; + break; + } + } + } + if (fmtpLineIndex === null) return sdp; + sdpLines[fmtpLineIndex] = sdpLines[fmtpLineIndex].concat('; stereo=1; sprop-stereo=1'); + sdp = sdpLines.join('\r\n'); + return sdp; + } + + return { + removeVPX: removeVPX, + disableNACK: disableNACK, + prioritize: prioritize, + removeNonG722: removeNonG722, + setApplicationSpecificBandwidth: function(sdp, bandwidth, isScreen) { + return setBAS(sdp, bandwidth, isScreen); + }, + setVideoBitrates: function(sdp, params) { + return setVideoBitrates(sdp, params); + }, + setOpusAttributes: function(sdp, params) { + return setOpusAttributes(sdp, params); + }, + preferVP9: function(sdp) { + return preferCodec(sdp, 'vp9'); + }, + preferCodec: preferCodec, + forceStereoAudio: forceStereoAudio + }; + })(); + + // backward compatibility + window.BandwidthHandler = CodecsHandler; + + // OnIceCandidateHandler.js + + var OnIceCandidateHandler = (function() { + function processCandidates(connection, icePair) { + var candidate = icePair.candidate; + + var iceRestrictions = connection.candidates; + var stun = iceRestrictions.stun; + var turn = iceRestrictions.turn; + + if (!isNull(iceRestrictions.reflexive)) { + stun = iceRestrictions.reflexive; + } + + if (!isNull(iceRestrictions.relay)) { + turn = iceRestrictions.relay; + } + + if (!iceRestrictions.host && !!candidate.match(/typ host/g)) { + return; + } + + if (!turn && !!candidate.match(/typ relay/g)) { + return; + } + + if (!stun && !!candidate.match(/typ srflx/g)) { + return; + } + + var protocol = connection.iceProtocols; + + if (!protocol.udp && !!candidate.match(/ udp /g)) { + return; + } + + if (!protocol.tcp && !!candidate.match(/ tcp /g)) { + return; + } + + if (connection.enableLogs) { + console.debug('Your candidate pairs:', candidate); + } + + return { + candidate: candidate, + sdpMid: icePair.sdpMid, + sdpMLineIndex: icePair.sdpMLineIndex + }; + } + + return { + processCandidates: processCandidates + }; + })(); + + // IceServersHandler.js + + var IceServersHandler = (function() { + function getIceServers(connection) { + // resiprocate: 3344+4433 + // pions: 7575 + var iceServers = [{ + 'urls': [ + 'stun:webrtcweb.com:7788', // coTURN + 'stun:webrtcweb.com:7788?transport=udp', // coTURN + ], + 'username': 'muazkh', + 'credential': 'muazkh' + }, + { + 'urls': [ + 'turn:webrtcweb.com:7788', // coTURN 7788+8877 + 'turn:webrtcweb.com:4455?transport=udp', // restund udp + + 'turn:webrtcweb.com:8877?transport=udp', // coTURN udp + 'turn:webrtcweb.com:8877?transport=tcp', // coTURN tcp + ], + 'username': 'muazkh', + 'credential': 'muazkh' + }, + { + 'urls': [ + 'stun:stun.l.google.com:19302', + 'stun:stun1.l.google.com:19302', + 'stun:stun2.l.google.com:19302', + 'stun:stun.l.google.com:19302?transport=udp', + ] + } + ]; + + if (typeof window.InstallTrigger !== 'undefined') { + iceServers[0].urls = iceServers[0].urls.pop(); + iceServers[1].urls = iceServers[1].urls.pop(); + } + + return iceServers; + } + + return { + getIceServers: getIceServers + }; + })(); + + // getUserMediaHandler.js + + function setStreamType(constraints, stream) { + if (constraints.mandatory && constraints.mandatory.chromeMediaSource) { + stream.isScreen = true; + } else if (constraints.mozMediaSource || constraints.mediaSource) { + stream.isScreen = true; + } else if (constraints.video) { + stream.isVideo = true; + } else if (constraints.audio) { + stream.isAudio = true; + } + } + + // allow users to manage this object (to support re-capturing of screen/etc.) + window.currentUserMediaRequest = { + streams: [], + mutex: false, + queueRequests: [], + remove: function(idInstance) { + this.mutex = false; + + var stream = this.streams[idInstance]; + if (!stream) { + return; + } + + stream = stream.stream; + + var options = stream.currentUserMediaRequestOptions; + + if (this.queueRequests.indexOf(options)) { + delete this.queueRequests[this.queueRequests.indexOf(options)]; + this.queueRequests = removeNullEntries(this.queueRequests); + } + + this.streams[idInstance].stream = null; + delete this.streams[idInstance]; + } + }; + + function getUserMediaHandler(options) { + if (currentUserMediaRequest.mutex === true) { + currentUserMediaRequest.queueRequests.push(options); + return; + } + currentUserMediaRequest.mutex = true; + + // easy way to match + var idInstance = JSON.stringify(options.localMediaConstraints); + + function streaming(stream, returnBack) { + setStreamType(options.localMediaConstraints, stream); + + var streamEndedEvent = 'ended'; + + if ('oninactive' in stream) { + streamEndedEvent = 'inactive'; + } + stream.addEventListener(streamEndedEvent, function() { + delete currentUserMediaRequest.streams[idInstance]; + + currentUserMediaRequest.mutex = false; + if (currentUserMediaRequest.queueRequests.indexOf(options)) { + delete currentUserMediaRequest.queueRequests[currentUserMediaRequest.queueRequests.indexOf(options)]; + currentUserMediaRequest.queueRequests = removeNullEntries(currentUserMediaRequest.queueRequests); + } + }, false); + + currentUserMediaRequest.streams[idInstance] = { + stream: stream + }; + currentUserMediaRequest.mutex = false; + + if (currentUserMediaRequest.queueRequests.length) { + getUserMediaHandler(currentUserMediaRequest.queueRequests.shift()); + } + + // callback + options.onGettingLocalMedia(stream, returnBack); + } + + if (currentUserMediaRequest.streams[idInstance]) { + streaming(currentUserMediaRequest.streams[idInstance].stream, true); + } else { + var isBlackBerry = !!(/BB10|BlackBerry/i.test(navigator.userAgent || '')); + if (isBlackBerry || typeof navigator.mediaDevices === 'undefined' || typeof navigator.mediaDevices.getUserMedia !== 'function') { + navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia; + navigator.getUserMedia(options.localMediaConstraints, function(stream) { + stream.streamid = stream.streamid || stream.id || getRandomString(); + stream.idInstance = idInstance; + streaming(stream); + }, function(error) { + options.onLocalMediaError(error, options.localMediaConstraints); + }); + return; + } + + if (typeof navigator.mediaDevices === 'undefined') { + navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia; + var getUserMediaSuccess = function() {}; + var getUserMediaFailure = function() {}; + + var getUserMediaStream, getUserMediaError; + navigator.mediaDevices = { + getUserMedia: function(hints) { + navigator.getUserMedia(hints, function(getUserMediaSuccess) { + getUserMediaSuccess(stream); + getUserMediaStream = stream; + }, function(error) { + getUserMediaFailure(error); + getUserMediaError = error; + }); + + return { + then: function(successCB) { + if (getUserMediaStream) { + successCB(getUserMediaStream); + return; + } + + getUserMediaSuccess = successCB; + + return { + then: function(failureCB) { + if (getUserMediaError) { + failureCB(getUserMediaError); + return; + } + + getUserMediaFailure = failureCB; + } + } + } + } + } + }; + } + + navigator.mediaDevices.getUserMedia(options.localMediaConstraints).then(function(stream) { + stream.streamid = stream.streamid || stream.id || getRandomString(); + stream.idInstance = idInstance; + + streaming(stream); + }).catch(function(error) { + options.onLocalMediaError(error, options.localMediaConstraints); + }); + } + } + + // StreamsHandler.js + + var StreamsHandler = (function() { + function handleType(type) { + if (!type) { + return; + } + + if (typeof type === 'string' || typeof type === 'undefined') { + return type; + } + + if (type.audio && type.video) { + return null; + } + + if (type.audio) { + return 'audio'; + } + + if (type.video) { + return 'video'; + } + + return; + } + + function setHandlers(stream, syncAction, connection) { + if (!stream || !stream.addEventListener) return; + + if (typeof syncAction == 'undefined' || syncAction == true) { + var streamEndedEvent = 'ended'; + + if ('oninactive' in stream) { + streamEndedEvent = 'inactive'; + } + + stream.addEventListener(streamEndedEvent, function() { + StreamsHandler.onSyncNeeded(this.streamid, streamEndedEvent); + }, false); + } + + stream.mute = function(type, isSyncAction) { + type = handleType(type); + + if (typeof isSyncAction !== 'undefined') { + syncAction = isSyncAction; + } + + if (typeof type == 'undefined' || type == 'audio') { + stream.getAudioTracks().forEach(function(track) { + track.enabled = false; + connection.streamEvents[stream.streamid].isAudioMuted = true; + }); + } + + if (typeof type == 'undefined' || type == 'video') { + stream.getVideoTracks().forEach(function(track) { + track.enabled = false; + }); + } + + if (typeof syncAction == 'undefined' || syncAction == true) { + StreamsHandler.onSyncNeeded(stream.streamid, 'mute', type); + } + + connection.streamEvents[stream.streamid].muteType = type || 'both'; + + fireEvent(stream, 'mute', type); + }; + + stream.unmute = function(type, isSyncAction) { + type = handleType(type); + + if (typeof isSyncAction !== 'undefined') { + syncAction = isSyncAction; + } + + graduallyIncreaseVolume(); + + if (typeof type == 'undefined' || type == 'audio') { + stream.getAudioTracks().forEach(function(track) { + track.enabled = true; + connection.streamEvents[stream.streamid].isAudioMuted = false; + }); + } + + if (typeof type == 'undefined' || type == 'video') { + stream.getVideoTracks().forEach(function(track) { + track.enabled = true; + }); + + // make sure that video unmute doesn't affects audio + if (typeof type !== 'undefined' && type == 'video' && connection.streamEvents[stream.streamid].isAudioMuted) { + (function looper(times) { + if (!times) { + times = 0; + } + + times++; + + // check until five-seconds + if (times < 100 && connection.streamEvents[stream.streamid].isAudioMuted) { + stream.mute('audio'); + + setTimeout(function() { + looper(times); + }, 50); + } + })(); + } + } + + if (typeof syncAction == 'undefined' || syncAction == true) { + StreamsHandler.onSyncNeeded(stream.streamid, 'unmute', type); + } + + connection.streamEvents[stream.streamid].unmuteType = type || 'both'; + + fireEvent(stream, 'unmute', type); + }; + + function graduallyIncreaseVolume() { + if (!connection.streamEvents[stream.streamid].mediaElement) { + return; + } + + var mediaElement = connection.streamEvents[stream.streamid].mediaElement; + mediaElement.volume = 0; + afterEach(200, 5, function() { + try { + mediaElement.volume += .20; + } catch (e) { + mediaElement.volume = 1; + } + }); + } + } + + function afterEach(setTimeoutInteval, numberOfTimes, callback, startedTimes) { + startedTimes = (startedTimes || 0) + 1; + if (startedTimes >= numberOfTimes) return; + + setTimeout(function() { + callback(); + afterEach(setTimeoutInteval, numberOfTimes, callback, startedTimes); + }, setTimeoutInteval); + } + + return { + setHandlers: setHandlers, + onSyncNeeded: function(streamid, action, type) {} + }; + })(); + + // Last time updated on: 5th May 2018 + + // Latest file can be found here: https://cdn.webrtc-experiment.com/Screen-Capturing.js + + // Muaz Khan - www.MuazKhan.com + // MIT License - www.WebRTC-Experiment.com/licence + // Documentation - https://github.com/muaz-khan/Chrome-Extensions/tree/master/Screen-Capturing.js + // Demo - https://www.webrtc-experiment.com/Screen-Capturing/ + + // ___________________ + // Screen-Capturing.js + + // Listen for postMessage handler + // postMessage is used to exchange "sourceId" between chrome extension and you webpage. + // though, there are tons other options as well, e.g. XHR-signaling, websockets, etc. + window.addEventListener('message', function(event) { + if (event.origin != window.location.origin) { + return; + } + + onMessageCallback(event.data); + }); + + // via: https://bugs.chromium.org/p/chromium/issues/detail?id=487935#c17 + // you can capture screen on Android Chrome >= 55 with flag: "Experimental ScreenCapture android" + window.IsAndroidChrome = false; + try { + if (navigator.userAgent.toLowerCase().indexOf("android") > -1 && /Chrome/.test(navigator.userAgent) && /Google Inc/.test(navigator.vendor)) { + window.IsAndroidChrome = true; + } + } catch (e) {} + + // and the function that handles received messages + + function onMessageCallback(data) { + // "cancel" button is clicked + if (data == 'PermissionDeniedError') { + chromeMediaSource = 'PermissionDeniedError'; + if (screenCallback) { + return screenCallback('PermissionDeniedError'); + } else { + throw new Error('PermissionDeniedError: User rejected to share his screen.'); + } + } + + // extension notified his presence + if (data == 'rtcmulticonnection-extension-loaded') { + chromeMediaSource = 'desktop'; + } + + // extension shared temp sourceId + if (data.sourceId && screenCallback) { + sourceId = data.sourceId; + screenCallback(sourceId); + } + } + + // global variables + var chromeMediaSource = 'screen'; + var sourceId; + var screenCallback; + + // this method can be used to check if chrome extension is installed & enabled. + function isChromeExtensionAvailable(callback) { + if (!callback) return; + + if (DetectRTC.browser.name === 'Firefox') return isFirefoxExtensionAvailable(callback); + + if (window.IsAndroidChrome) { + chromeMediaSource = 'screen'; + callback(true); + return; + } + + if (chromeMediaSource == 'desktop') { + callback(true); + return; + } + + // ask extension if it is available + window.postMessage('are-you-there', '*'); + + setTimeout(function() { + if (chromeMediaSource == 'screen') { + callback(false); + } else callback(true); + }, 2000); + } + + function isFirefoxExtensionAvailable(callback) { + if (!callback) return; + + if (DetectRTC.browser.name !== 'Firefox') return isChromeExtensionAvailable(callback); + + var isFirefoxAddonResponded = false; + + function messageCallback(event) { + var addonMessage = event.data; + + if (!addonMessage || typeof addonMessage.isScreenCapturingEnabled === 'undefined') return; + + isFirefoxAddonResponded = true; + + if (addonMessage.isScreenCapturingEnabled === true) { + callback(true); + } else { + callback(false); + } + + window.removeEventListener("message", messageCallback, false); + } + + window.addEventListener("message", messageCallback, false); + + window.postMessage({ + checkIfScreenCapturingEnabled: true, + domains: [document.domain] + }, "*"); + + setTimeout(function() { + if (!isFirefoxAddonResponded) { + callback(true); // can be old firefox extension + } + }, 2000); // wait 2-seconds-- todo: is this enough limit? + } + + // this function can be used to get "source-id" from the extension + function getSourceId(callback, audioPlusTab) { + if (!callback) throw '"callback" parameter is mandatory.'; + + sourceId = null; + + screenCallback = callback; + + if (!!audioPlusTab) { + window.postMessage('audio-plus-tab', '*'); + return; + } + window.postMessage('get-sourceId', '*'); + } + + function getChromeExtensionStatus(extensionid, callback) { + if (window.IsAndroidChrome) { + chromeMediaSource = 'screen'; + callback('installed-enabled'); + return; + } + + if (arguments.length != 2) { + callback = extensionid; + extensionid = window.RMCExtensionID || 'ajhifddimkapgcifgcodmmfdlknahffk'; // default extension-id + } + + if (DetectRTC.browser.name === 'Firefox') return callback('not-chrome'); + + sourceId = null; + chromeMediaSource = 'screen'; + + var image = document.createElement('img'); + image.src = 'chrome-extension://' + extensionid + '/icon.png'; + image.onload = function() { + window.postMessage('are-you-there', '*'); + setTimeout(function() { + if (chromeMediaSource == 'screen') { + callback('installed-disabled'); + } else callback('installed-enabled'); + }, 2000); + }; + image.onerror = function() { + callback('not-installed'); + }; + } + + function getAspectRatio(w, h) { + function gcd(a, b) { + return (b == 0) ? a : gcd(b, a % b); + } + var r = gcd(w, h); + return (w / r) / (h / r); + } + + // this function explains how to use above methods/objects + function getScreenConstraints(callback, audioPlusTab) { + var firefoxScreenConstraints = { + mozMediaSource: 'window', + mediaSource: 'window' + }; + + if (DetectRTC.browser.name === 'Firefox') return callback(null, firefoxScreenConstraints); + + // support recapture again & again + sourceId = null; + + isChromeExtensionAvailable(function(isAvailable) { + // this statement defines getUserMedia constraints + // that will be used to capture content of screen + var screen_constraints = { + mandatory: { + chromeMediaSource: chromeMediaSource, + maxWidth: screen.width, + maxHeight: screen.height, + minWidth: screen.width, + minHeight: screen.height, + minAspectRatio: getAspectRatio(screen.width, screen.height), + maxAspectRatio: getAspectRatio(screen.width, screen.height), + minFrameRate: 64, + maxFrameRate: 128 + }, + optional: [] + }; + + if (window.IsAndroidChrome) { + // now invoking native getUserMedia API + callback(null, screen_constraints); + return; + } + + // this statement verifies chrome extension availability + // if installed and available then it will invoke extension API + // otherwise it will fallback to command-line based screen capturing API + if (chromeMediaSource == 'desktop' && !sourceId) { + getSourceId(function() { + screen_constraints.mandatory.chromeMediaSourceId = sourceId; + callback(sourceId == 'PermissionDeniedError' ? sourceId : null, screen_constraints); + sourceId = null; + }, audioPlusTab); + return; + } + + // this statement sets gets 'sourceId" and sets "chromeMediaSourceId" + if (chromeMediaSource == 'desktop') { + screen_constraints.mandatory.chromeMediaSourceId = sourceId; + } + + sourceId = null; + // chromeMediaSource = 'screen'; // maybe this line is redundant? + screenCallback = null; + + // now invoking native getUserMedia API + callback(null, screen_constraints); + }); + } + + // TextReceiver.js & TextSender.js + + function TextReceiver(connection) { + var content = {}; + + function receive(data, userid, extra) { + // uuid is used to uniquely identify sending instance + var uuid = data.uuid; + if (!content[uuid]) { + content[uuid] = []; + } + + content[uuid].push(data.message); + + if (data.last) { + var message = content[uuid].join(''); + if (data.isobject) { + message = JSON.parse(message); + } + + // latency detection + var receivingTime = new Date().getTime(); + var latency = receivingTime - data.sendingTime; + + var e = { + data: message, + userid: userid, + extra: extra, + latency: latency + }; + + if (connection.autoTranslateText) { + e.original = e.data; + connection.Translator.TranslateText(e.data, function(translatedText) { + e.data = translatedText; + connection.onmessage(e); + }); + } else { + connection.onmessage(e); + } + + delete content[uuid]; + } + } + + return { + receive: receive + }; + } + + // TextSender.js + var TextSender = { + send: function(config) { + var connection = config.connection; + + var channel = config.channel, + remoteUserId = config.remoteUserId, + initialText = config.text, + packetSize = connection.chunkSize || 1000, + textToTransfer = '', + isobject = false; + + if (!isString(initialText)) { + isobject = true; + initialText = JSON.stringify(initialText); + } + + // uuid is used to uniquely identify sending instance + var uuid = getRandomString(); + var sendingTime = new Date().getTime(); + + sendText(initialText); + + function sendText(textMessage, text) { + var data = { + type: 'text', + uuid: uuid, + sendingTime: sendingTime + }; + + if (textMessage) { + text = textMessage; + data.packets = parseInt(text.length / packetSize); + } + + if (text.length > packetSize) { + data.message = text.slice(0, packetSize); + } else { + data.message = text; + data.last = true; + data.isobject = isobject; + } + + channel.send(data, remoteUserId); + + textToTransfer = text.slice(data.message.length); + + if (textToTransfer.length) { + setTimeout(function() { + sendText(null, textToTransfer); + }, connection.chunkInterval || 100); + } + } + } + }; + + // FileProgressBarHandler.js + + var FileProgressBarHandler = (function() { + function handle(connection) { + var progressHelper = {}; + + // www.RTCMultiConnection.org/docs/onFileStart/ + connection.onFileStart = function(file) { + var div = document.createElement('div'); + div.title = file.name; + div.innerHTML = ' '; + + if (file.remoteUserId) { + div.innerHTML += ' (Sharing with:' + file.remoteUserId + ')'; + } + + if (!connection.filesContainer) { + connection.filesContainer = document.body || document.documentElement; + } + + connection.filesContainer.insertBefore(div, connection.filesContainer.firstChild); + + if (!file.remoteUserId) { + progressHelper[file.uuid] = { + div: div, + progress: div.querySelector('progress'), + label: div.querySelector('label') + }; + progressHelper[file.uuid].progress.max = file.maxChunks; + return; + } + + if (!progressHelper[file.uuid]) { + progressHelper[file.uuid] = {}; + } + + progressHelper[file.uuid][file.remoteUserId] = { + div: div, + progress: div.querySelector('progress'), + label: div.querySelector('label') + }; + progressHelper[file.uuid][file.remoteUserId].progress.max = file.maxChunks; + }; + + // www.RTCMultiConnection.org/docs/onFileProgress/ + connection.onFileProgress = function(chunk) { + var helper = progressHelper[chunk.uuid]; + if (!helper) { + return; + } + if (chunk.remoteUserId) { + helper = progressHelper[chunk.uuid][chunk.remoteUserId]; + if (!helper) { + return; + } + } + + helper.progress.value = chunk.currentPosition || chunk.maxChunks || helper.progress.max; + updateLabel(helper.progress, helper.label); + }; + + // www.RTCMultiConnection.org/docs/onFileEnd/ + connection.onFileEnd = function(file) { + var helper = progressHelper[file.uuid]; + if (!helper) { + console.error('No such progress-helper element exist.', file); + return; + } + + if (file.remoteUserId) { + helper = progressHelper[file.uuid][file.remoteUserId]; + if (!helper) { + return; + } + } + + var div = helper.div; + if (file.type.indexOf('image') != -1) { + div.innerHTML = 'Download ' + file.name + '
    '; + } else { + div.innerHTML = 'Download ' + file.name + '
    '; + } + }; + + function updateLabel(progress, label) { + if (progress.position === -1) { + return; + } + + var position = +progress.position.toFixed(2).split('.')[1] || 100; + label.innerHTML = position + '%'; + } + } + + return { + handle: handle + }; + })(); + + // TranslationHandler.js + + var TranslationHandler = (function() { + function handle(connection) { + connection.autoTranslateText = false; + connection.language = 'en'; + connection.googKey = 'AIzaSyCgB5hmFY74WYB-EoWkhr9cAGr6TiTHrEE'; + + // www.RTCMultiConnection.org/docs/Translator/ + connection.Translator = { + TranslateText: function(text, callback) { + // if(location.protocol === 'https:') return callback(text); + + var newScript = document.createElement('script'); + newScript.type = 'text/javascript'; + + var sourceText = encodeURIComponent(text); // escape + + var randomNumber = 'method' + connection.token(); + window[randomNumber] = function(response) { + if (response.data && response.data.translations[0] && callback) { + callback(response.data.translations[0].translatedText); + return; + } + + if (response.error && response.error.message === 'Daily Limit Exceeded') { + console.error('Text translation failed. Error message: "Daily Limit Exceeded."'); + return; + } + + if (response.error) { + console.error(response.error.message); + return; + } + + console.error(response); + }; + + var source = 'https://www.googleapis.com/language/translate/v2?key=' + connection.googKey + '&target=' + (connection.language || 'en-US') + '&callback=window.' + randomNumber + '&q=' + sourceText; + newScript.src = source; + document.getElementsByTagName('head')[0].appendChild(newScript); + }, + getListOfLanguages: function(callback) { + var xhr = new XMLHttpRequest(); + xhr.onreadystatechange = function() { + if (xhr.readyState == XMLHttpRequest.DONE) { + var response = JSON.parse(xhr.responseText); + + if (response && response.data && response.data.languages) { + callback(response.data.languages); + return; + } + + if (response.error && response.error.message === 'Daily Limit Exceeded') { + console.error('Text translation failed. Error message: "Daily Limit Exceeded."'); + return; + } + + if (response.error) { + console.error(response.error.message); + return; + } + + console.error(response); + } + } + var url = 'https://www.googleapis.com/language/translate/v2/languages?key=' + connection.googKey + '&target=en'; + xhr.open('GET', url, true); + xhr.send(null); + } + }; + } + + return { + handle: handle + }; + })(); + + (function(connection) { + forceOptions = forceOptions || { + useDefaultDevices: true + }; + + connection.channel = connection.sessionid = (roomid || location.href.replace(/\/|:|#|\?|\$|\^|%|\.|`|~|!|\+|@|\[|\||]|\|*. /g, '').split('\n').join('').split('\r').join('')) + ''; + + var mPeer = new MultiPeers(connection); + + var preventDuplicateOnStreamEvents = {}; + mPeer.onGettingLocalMedia = function(stream, callback) { + callback = callback || function() {}; + + if (preventDuplicateOnStreamEvents[stream.streamid]) { + return; + } + preventDuplicateOnStreamEvents[stream.streamid] = true; + + try { + stream.type = 'local'; + } catch (e) {} + + connection.setStreamEndHandler(stream); + + getRMCMediaElement(stream, function(mediaElement) { + mediaElement.id = stream.streamid; + mediaElement.muted = true; + mediaElement.volume = 0; + + if (connection.attachStreams.indexOf(stream) === -1) { + connection.attachStreams.push(stream); + } + + if (typeof StreamsHandler !== 'undefined') { + StreamsHandler.setHandlers(stream, true, connection); + } + + connection.streamEvents[stream.streamid] = { + stream: stream, + type: 'local', + mediaElement: mediaElement, + userid: connection.userid, + extra: connection.extra, + streamid: stream.streamid, + isAudioMuted: true + }; + + setHarkEvents(connection, connection.streamEvents[stream.streamid]); + setMuteHandlers(connection, connection.streamEvents[stream.streamid]); + + connection.onstream(connection.streamEvents[stream.streamid]); + callback(); + }, connection); + }; + + mPeer.onGettingRemoteMedia = function(stream, remoteUserId) { + try { + stream.type = 'remote'; + } catch (e) {} + + connection.setStreamEndHandler(stream, 'remote-stream'); + + getRMCMediaElement(stream, function(mediaElement) { + mediaElement.id = stream.streamid; + + if (typeof StreamsHandler !== 'undefined') { + StreamsHandler.setHandlers(stream, false, connection); + } + + connection.streamEvents[stream.streamid] = { + stream: stream, + type: 'remote', + userid: remoteUserId, + extra: connection.peers[remoteUserId] ? connection.peers[remoteUserId].extra : {}, + mediaElement: mediaElement, + streamid: stream.streamid + }; + + setMuteHandlers(connection, connection.streamEvents[stream.streamid]); + + connection.onstream(connection.streamEvents[stream.streamid]); + }, connection); + }; + + mPeer.onRemovingRemoteMedia = function(stream, remoteUserId) { + var streamEvent = connection.streamEvents[stream.streamid]; + if (!streamEvent) { + streamEvent = { + stream: stream, + type: 'remote', + userid: remoteUserId, + extra: connection.peers[remoteUserId] ? connection.peers[remoteUserId].extra : {}, + streamid: stream.streamid, + mediaElement: connection.streamEvents[stream.streamid] ? connection.streamEvents[stream.streamid].mediaElement : null + }; + } + + if (connection.peersBackup[streamEvent.userid]) { + streamEvent.extra = connection.peersBackup[streamEvent.userid].extra; + } + + connection.onstreamended(streamEvent); + + delete connection.streamEvents[stream.streamid]; + }; + + mPeer.onNegotiationNeeded = function(message, remoteUserId, callback) { + remoteUserId = remoteUserId || message.remoteUserId; + message = message || ''; + connectSocket(function() { + connection.socket.emit(connection.socketMessageEvent, typeof message.password !== 'undefined' ? message : { + remoteUserId: remoteUserId, + message: message, + sender: connection.userid + }, callback || function() {}); + }); + }; + + function onUserLeft(remoteUserId) { + connection.deletePeer(remoteUserId); + } + + mPeer.onUserLeft = onUserLeft; + mPeer.disconnectWith = function(remoteUserId, callback) { + if (connection.socket) { + connection.socket.emit('disconnect-with', remoteUserId, callback || function() {}); + } + + connection.deletePeer(remoteUserId); + }; + + connection.broadcasters = []; + + connection.socketOptions = { + // 'force new connection': true, // For SocketIO version < 1.0 + // 'forceNew': true, // For SocketIO version >= 1.0 + 'transport': 'polling' // fixing transport:unknown issues + }; + + function connectSocket(connectCallback) { + connection.socketAutoReConnect = true; + + if (connection.socket) { // todo: check here readySate/etc. to make sure socket is still opened + if (connectCallback) { + connectCallback(connection.socket); + } + return; + } + + if (typeof SocketConnection === 'undefined') { + if (typeof FirebaseConnection !== 'undefined') { + window.SocketConnection = FirebaseConnection; + } else if (typeof PubNubConnection !== 'undefined') { + window.SocketConnection = PubNubConnection; + } else { + throw 'SocketConnection.js seems missed.'; + } + } + + new SocketConnection(connection, function(s) { + if (connectCallback) { + connectCallback(connection.socket); + } + }); + } + + // 1st paramter is roomid + // 2nd paramter can be either password or a callback function + // 3rd paramter is a callback function + connection.openOrJoin = function(localUserid, password, callback) { + callback = callback || function() {}; + + connection.checkPresence(localUserid, function(isRoomExist, roomid) { + // i.e. 2nd parameter is a callback function + if (typeof password === 'function' && typeof password !== 'undefined') { + callback = password; // switch callback functions + password = null; + } + + if (!password && !!connection.password) { + password = connection.password; + } + + if (isRoomExist) { + connection.sessionid = roomid; + + var localPeerSdpConstraints = false; + var remotePeerSdpConstraints = false; + var isOneWay = !!connection.session.oneway; + var isDataOnly = isData(connection.session); + + remotePeerSdpConstraints = { + OfferToReceiveAudio: connection.sdpConstraints.mandatory.OfferToReceiveAudio, + OfferToReceiveVideo: connection.sdpConstraints.mandatory.OfferToReceiveVideo + } + + localPeerSdpConstraints = { + OfferToReceiveAudio: isOneWay ? !!connection.session.audio : connection.sdpConstraints.mandatory.OfferToReceiveAudio, + OfferToReceiveVideo: isOneWay ? !!connection.session.video || !!connection.session.screen : connection.sdpConstraints.mandatory.OfferToReceiveVideo + } + + var connectionDescription = { + remoteUserId: connection.sessionid, + message: { + newParticipationRequest: true, + isOneWay: isOneWay, + isDataOnly: isDataOnly, + localPeerSdpConstraints: localPeerSdpConstraints, + remotePeerSdpConstraints: remotePeerSdpConstraints + }, + sender: connection.userid, + password: password || false + }; + + beforeJoin(connectionDescription.message, function() { + mPeer.onNegotiationNeeded(connectionDescription); + + // tell user if room was joined + callback(isRoomExist, roomid); + }); + return; + } + + connection.waitingForLocalMedia = true; + connection.isInitiator = true; + + var oldUserId = connection.userid; + connection.userid = connection.sessionid = localUserid || connection.sessionid; + connection.userid += ''; + + connection.socket.emit('changed-uuid', connection.userid); + + if (password) { + connection.socket.emit('set-password', password); + } + + if (isData(connection.session)) { + connection.waitingForLocalMedia = false; + return; + } + + connection.captureUserMedia(function() { + connection.waitingForLocalMedia = false; + + // tell user if room was opened + callback(isRoomExist, roomid); + }); + }); + }; + + // don't allow someone to join this person until he has the media + connection.waitingForLocalMedia = false; + + connection.open = function(localUserid, isPublicModerator, callback) { + connection.waitingForLocalMedia = true; + connection.isInitiator = true; + + callback = callback || function() {}; + if (typeof isPublicModerator === 'function') { + callback = isPublicModerator; + isPublicModerator = false; + } + + var oldUserId = connection.userid; + connection.userid = connection.sessionid = localUserid || connection.sessionid; + connection.userid += ''; + + connectSocket(function() { + connection.socket.emit('changed-uuid', connection.userid); + + if (!!connection.password) { + connection.socket.emit('set-password', connection.password); + } + + if (isPublicModerator == true) { + connection.becomePublicModerator(); + } + + if (isData(connection.session)) { + connection.waitingForLocalMedia = false; + callback(); + return; + } + + connection.captureUserMedia(function() { + connection.waitingForLocalMedia = false; + callback(); + }); + }); + }; + + connection.becomePublicModerator = function() { + if (!connection.isInitiator) return; + connection.socket.emit('become-a-public-moderator'); + }; + + connection.dontMakeMeModerator = function() { + connection.socket.emit('dont-make-me-moderator'); + }; + + connection.deletePeer = function(remoteUserId) { + if (!remoteUserId) { + return; + } + + var eventObject = { + userid: remoteUserId, + extra: connection.peers[remoteUserId] ? connection.peers[remoteUserId].extra : {} + }; + + if (connection.peersBackup[eventObject.userid]) { + eventObject.extra = connection.peersBackup[eventObject.userid].extra; + } + + connection.onleave(eventObject); + + if (!!connection.peers[remoteUserId]) { + connection.peers[remoteUserId].streams.forEach(function(stream) { + stream.stop(); + }); + + var peer = connection.peers[remoteUserId].peer; + if (peer && peer.iceConnectionState !== 'closed') { + try { + peer.close(); + } catch (e) {} + } + + if (connection.peers[remoteUserId]) { + connection.peers[remoteUserId].peer = null; + delete connection.peers[remoteUserId]; + } + } + + if (connection.broadcasters.indexOf(remoteUserId) !== -1) { + var newArray = []; + connection.broadcasters.forEach(function(broadcaster) { + if (broadcaster !== remoteUserId) { + newArray.push(broadcaster); + } + }); + connection.broadcasters = newArray; + keepNextBroadcasterOnServer(); + } + } + + connection.rejoin = function(connectionDescription) { + if (connection.isInitiator || !connectionDescription || !Object.keys(connectionDescription).length) { + return; + } + + var extra = {}; + + if (connection.peers[connectionDescription.remoteUserId]) { + extra = connection.peers[connectionDescription.remoteUserId].extra; + connection.deletePeer(connectionDescription.remoteUserId); + } + + if (connectionDescription && connectionDescription.remoteUserId) { + connection.join(connectionDescription.remoteUserId); + + connection.onReConnecting({ + userid: connectionDescription.remoteUserId, + extra: extra + }); + } + }; + + connection.join = connection.connect = function(remoteUserId, options) { + connection.sessionid = (remoteUserId ? remoteUserId.sessionid || remoteUserId.remoteUserId || remoteUserId : false) || connection.sessionid; + connection.sessionid += ''; + + var localPeerSdpConstraints = false; + var remotePeerSdpConstraints = false; + var isOneWay = false; + var isDataOnly = false; + + if ((remoteUserId && remoteUserId.session) || !remoteUserId || typeof remoteUserId === 'string') { + var session = remoteUserId ? remoteUserId.session || connection.session : connection.session; + + isOneWay = !!session.oneway; + isDataOnly = isData(session); + + remotePeerSdpConstraints = { + OfferToReceiveAudio: connection.sdpConstraints.mandatory.OfferToReceiveAudio, + OfferToReceiveVideo: connection.sdpConstraints.mandatory.OfferToReceiveVideo + }; + + localPeerSdpConstraints = { + OfferToReceiveAudio: isOneWay ? !!connection.session.audio : connection.sdpConstraints.mandatory.OfferToReceiveAudio, + OfferToReceiveVideo: isOneWay ? !!connection.session.video || !!connection.session.screen : connection.sdpConstraints.mandatory.OfferToReceiveVideo + }; + } + + options = options || {}; + + var cb = function() {}; + if (typeof options === 'function') { + cb = options; + options = {}; + } + + if (typeof options.localPeerSdpConstraints !== 'undefined') { + localPeerSdpConstraints = options.localPeerSdpConstraints; + } + + if (typeof options.remotePeerSdpConstraints !== 'undefined') { + remotePeerSdpConstraints = options.remotePeerSdpConstraints; + } + + if (typeof options.isOneWay !== 'undefined') { + isOneWay = options.isOneWay; + } + + if (typeof options.isDataOnly !== 'undefined') { + isDataOnly = options.isDataOnly; + } + + var connectionDescription = { + remoteUserId: connection.sessionid, + message: { + newParticipationRequest: true, + isOneWay: isOneWay, + isDataOnly: isDataOnly, + localPeerSdpConstraints: localPeerSdpConstraints, + remotePeerSdpConstraints: remotePeerSdpConstraints + }, + sender: connection.userid, + password: connection.password || false + }; + + beforeJoin(connectionDescription.message, function() { + connectSocket(function() { + if (!!connection.peers[connection.sessionid]) { + // on socket disconnect & reconnect + return; + } + + mPeer.onNegotiationNeeded(connectionDescription); + cb(); + }); + }); + return connectionDescription; + }; + + function beforeJoin(userPreferences, callback) { + if (connection.dontCaptureUserMedia || userPreferences.isDataOnly) { + callback(); + return; + } + + var localMediaConstraints = {}; + + if (userPreferences.localPeerSdpConstraints.OfferToReceiveAudio) { + localMediaConstraints.audio = connection.mediaConstraints.audio; + } + + if (userPreferences.localPeerSdpConstraints.OfferToReceiveVideo) { + localMediaConstraints.video = connection.mediaConstraints.video; + } + + var session = userPreferences.session || connection.session; + + if (session.oneway && session.audio !== 'two-way' && session.video !== 'two-way' && session.screen !== 'two-way') { + callback(); + return; + } + + if (session.oneway && session.audio && session.audio === 'two-way') { + session = { + audio: true + }; + } + + if (session.audio || session.video || session.screen) { + if (session.screen) { + if (DetectRTC.browser.name === 'Edge') { + navigator.getDisplayMedia({ + video: true, + audio: isAudioPlusTab(connection) + }).then(function(screen) { + screen.isScreen = true; + mPeer.onGettingLocalMedia(screen); + + if ((session.audio || session.video) && !isAudioPlusTab(connection)) { + connection.invokeGetUserMedia(null, callback); + } else { + callback(screen); + } + }, function(error) { + console.error('Unable to capture screen on Edge. HTTPs and version 17+ is required.'); + }); + } else { + connection.getScreenConstraints(function(error, screen_constraints) { + connection.invokeGetUserMedia({ + audio: isAudioPlusTab(connection) ? getAudioScreenConstraints(screen_constraints) : false, + video: screen_constraints, + isScreen: true + }, (session.audio || session.video) && !isAudioPlusTab(connection) ? connection.invokeGetUserMedia(null, callback) : callback); + }); + } + } else if (session.audio || session.video) { + connection.invokeGetUserMedia(null, callback, session); + } + } + } + + connection.connectWithAllParticipants = function(remoteUserId) { + mPeer.onNegotiationNeeded('connectWithAllParticipants', remoteUserId || connection.sessionid); + }; + + connection.removeFromBroadcastersList = function(remoteUserId) { + mPeer.onNegotiationNeeded('removeFromBroadcastersList', remoteUserId || connection.sessionid); + + connection.peers.getAllParticipants(remoteUserId || connection.sessionid).forEach(function(participant) { + mPeer.onNegotiationNeeded('dropPeerConnection', participant); + connection.deletePeer(participant); + }); + + connection.attachStreams.forEach(function(stream) { + stream.stop(); + }); + }; + + connection.getUserMedia = connection.captureUserMedia = function(callback, sessionForced) { + callback = callback || function() {}; + var session = sessionForced || connection.session; + + if (connection.dontCaptureUserMedia || isData(session)) { + callback(); + return; + } + + if (session.audio || session.video || session.screen) { + if (session.screen) { + if (DetectRTC.browser.name === 'Edge') { + navigator.getDisplayMedia({ + video: true, + audio: isAudioPlusTab(connection) + }).then(function(screen) { + screen.isScreen = true; + mPeer.onGettingLocalMedia(screen); + + if ((session.audio || session.video) && !isAudioPlusTab(connection)) { + var nonScreenSession = {}; + for (var s in session) { + if (s !== 'screen') { + nonScreenSession[s] = session[s]; + } + } + connection.invokeGetUserMedia(sessionForced, callback, nonScreenSession); + return; + } + callback(screen); + }, function(error) { + console.error('Unable to capture screen on Edge. HTTPs and version 17+ is required.'); + }); + } else { + connection.getScreenConstraints(function(error, screen_constraints) { + if (error) { + throw error; + } + + connection.invokeGetUserMedia({ + audio: isAudioPlusTab(connection) ? getAudioScreenConstraints(screen_constraints) : false, + video: screen_constraints, + isScreen: true + }, function(stream) { + if ((session.audio || session.video) && !isAudioPlusTab(connection)) { + var nonScreenSession = {}; + for (var s in session) { + if (s !== 'screen') { + nonScreenSession[s] = session[s]; + } + } + connection.invokeGetUserMedia(sessionForced, callback, nonScreenSession); + return; + } + callback(stream); + }); + }); + } + } else if (session.audio || session.video) { + connection.invokeGetUserMedia(sessionForced, callback, session); + } + } + }; + + function beforeUnload(shiftModerationControlOnLeave, dontCloseSocket) { + if (!connection.closeBeforeUnload) { + return; + } + + if (connection.isInitiator === true) { + connection.dontMakeMeModerator(); + } + + connection.peers.getAllParticipants().forEach(function(participant) { + mPeer.onNegotiationNeeded({ + userLeft: true + }, participant); + + if (connection.peers[participant] && connection.peers[participant].peer) { + connection.peers[participant].peer.close(); + } + + delete connection.peers[participant]; + }); + + if (!dontCloseSocket) { + connection.closeSocket(); + } + + connection.broadcasters = []; + connection.isInitiator = false; + } + + connection.closeBeforeUnload = true; + window.addEventListener('beforeunload', beforeUnload, false); + + connection.userid = getRandomString(); + connection.changeUserId = function(newUserId, callback) { + callback = callback || function() {}; + connection.userid = newUserId || getRandomString(); + connection.socket.emit('changed-uuid', connection.userid, callback); + }; + + connection.extra = {}; + connection.attachStreams = []; + + connection.session = { + audio: true, + video: true + }; + + connection.enableFileSharing = false; + + // all values in kbps + connection.bandwidth = { + screen: false, + audio: false, + video: false + }; + + connection.codecs = { + audio: 'opus', + video: 'VP9' + }; + + connection.processSdp = function(sdp) { + if (DetectRTC.browser.name === 'Safari') { + return sdp; + } + + if (connection.codecs.video.toUpperCase() === 'VP8') { + sdp = CodecsHandler.preferCodec(sdp, 'vp8'); + } + + if (connection.codecs.video.toUpperCase() === 'VP9') { + sdp = CodecsHandler.preferCodec(sdp, 'vp9'); + } + + if (connection.codecs.video.toUpperCase() === 'H264') { + sdp = CodecsHandler.preferCodec(sdp, 'h264'); + } + + if (connection.codecs.audio === 'G722') { + sdp = CodecsHandler.removeNonG722(sdp); + } + + if (DetectRTC.browser.name === 'Firefox') { + return sdp; + } + + if (connection.bandwidth.video || connection.bandwidth.screen) { + sdp = CodecsHandler.setApplicationSpecificBandwidth(sdp, connection.bandwidth, !!connection.session.screen); + } + + if (connection.bandwidth.video) { + sdp = CodecsHandler.setVideoBitrates(sdp, { + min: connection.bandwidth.video * 8 * 1024, + max: connection.bandwidth.video * 8 * 1024 + }); + } + + if (connection.bandwidth.audio) { + sdp = CodecsHandler.setOpusAttributes(sdp, { + maxaveragebitrate: connection.bandwidth.audio * 8 * 1024, + maxplaybackrate: connection.bandwidth.audio * 8 * 1024, + stereo: 1, + maxptime: 3 + }); + } + + return sdp; + }; + + if (typeof CodecsHandler !== 'undefined') { + connection.BandwidthHandler = connection.CodecsHandler = CodecsHandler; + } + + connection.mediaConstraints = { + audio: { + mandatory: {}, + optional: connection.bandwidth.audio ? [{ + bandwidth: connection.bandwidth.audio * 8 * 1024 || 128 * 8 * 1024 + }] : [] + }, + video: { + mandatory: {}, + optional: connection.bandwidth.video ? [{ + bandwidth: connection.bandwidth.video * 8 * 1024 || 128 * 8 * 1024 + }, { + facingMode: 'user' + }] : [{ + facingMode: 'user' + }] + } + }; + + if (DetectRTC.browser.name === 'Firefox') { + connection.mediaConstraints = { + audio: true, + video: true + }; + } + + if (!forceOptions.useDefaultDevices && !DetectRTC.isMobileDevice) { + DetectRTC.load(function() { + var lastAudioDevice, lastVideoDevice; + // it will force RTCMultiConnection to capture last-devices + // i.e. if external microphone is attached to system, we should prefer it over built-in devices. + DetectRTC.MediaDevices.forEach(function(device) { + if (device.kind === 'audioinput' && connection.mediaConstraints.audio !== false) { + lastAudioDevice = device; + } + + if (device.kind === 'videoinput' && connection.mediaConstraints.video !== false) { + lastVideoDevice = device; + } + }); + + if (lastAudioDevice) { + if (DetectRTC.browser.name === 'Firefox') { + if (connection.mediaConstraints.audio !== true) { + connection.mediaConstraints.audio.deviceId = lastAudioDevice.id; + } else { + connection.mediaConstraints.audio = { + deviceId: lastAudioDevice.id + } + } + return; + } + + if (connection.mediaConstraints.audio == true) { + connection.mediaConstraints.audio = { + mandatory: {}, + optional: [] + } + } + + if (!connection.mediaConstraints.audio.optional) { + connection.mediaConstraints.audio.optional = []; + } + + var optional = [{ + sourceId: lastAudioDevice.id + }]; + + connection.mediaConstraints.audio.optional = optional.concat(connection.mediaConstraints.audio.optional); + } + + if (lastVideoDevice) { + if (DetectRTC.browser.name === 'Firefox') { + if (connection.mediaConstraints.video !== true) { + connection.mediaConstraints.video.deviceId = lastVideoDevice.id; + } else { + connection.mediaConstraints.video = { + deviceId: lastVideoDevice.id + } + } + return; + } + + if (connection.mediaConstraints.video == true) { + connection.mediaConstraints.video = { + mandatory: {}, + optional: [] + } + } + + if (!connection.mediaConstraints.video.optional) { + connection.mediaConstraints.video.optional = []; + } + + var optional = [{ + sourceId: lastVideoDevice.id + }]; + + connection.mediaConstraints.video.optional = optional.concat(connection.mediaConstraints.video.optional); + } + }); + } + + connection.sdpConstraints = { + mandatory: { + OfferToReceiveAudio: true, + OfferToReceiveVideo: true + }, + optional: [{ + VoiceActivityDetection: false + }] + }; + + connection.rtcpMuxPolicy = 'require'; // "require" or "negotiate" + connection.iceTransportPolicy = null; // "relay" or "all" + connection.optionalArgument = { + optional: [{ + DtlsSrtpKeyAgreement: true + }, { + googImprovedWifiBwe: true + }, { + googScreencastMinBitrate: 300 + }, { + googIPv6: true + }, { + googDscp: true + }, { + googCpuUnderuseThreshold: 55 + }, { + googCpuOveruseThreshold: 85 + }, { + googSuspendBelowMinBitrate: true + }, { + googCpuOveruseDetection: true + }], + mandatory: {} + }; + + connection.iceServers = IceServersHandler.getIceServers(connection); + + connection.candidates = { + host: true, + stun: true, + turn: true + }; + + connection.iceProtocols = { + tcp: true, + udp: true + }; + + // EVENTs + connection.onopen = function(event) { + if (!!connection.enableLogs) { + console.info('Data connection has been opened between you & ', event.userid); + } + }; + + connection.onclose = function(event) { + if (!!connection.enableLogs) { + console.warn('Data connection has been closed between you & ', event.userid); + } + }; + + connection.onerror = function(error) { + if (!!connection.enableLogs) { + console.error(error.userid, 'data-error', error); + } + }; + + connection.onmessage = function(event) { + if (!!connection.enableLogs) { + console.debug('data-message', event.userid, event.data); + } + }; + + connection.send = function(data, remoteUserId) { + connection.peers.send(data, remoteUserId); + }; + + connection.close = connection.disconnect = connection.leave = function() { + beforeUnload(false, true); + }; + + connection.closeEntireSession = function(callback) { + callback = callback || function() {}; + connection.socket.emit('close-entire-session', function looper() { + if (connection.getAllParticipants().length) { + setTimeout(looper, 100); + return; + } + + connection.onEntireSessionClosed({ + sessionid: connection.sessionid, + userid: connection.userid, + extra: connection.extra + }); + + connection.changeUserId(null, function() { + connection.close(); + callback(); + }); + }); + }; + + connection.onEntireSessionClosed = function(event) { + if (!connection.enableLogs) return; + console.info('Entire session is closed: ', event.sessionid, event.extra); + }; + + connection.onstream = function(e) { + var parentNode = connection.videosContainer; + parentNode.insertBefore(e.mediaElement, parentNode.firstChild); + var played = e.mediaElement.play(); + + if (typeof played !== 'undefined') { + played.catch(function() { /*** iOS 11 doesn't allow automatic play and rejects ***/ }).then(function() { + setTimeout(function() { + e.mediaElement.play(); + }, 2000); + }); + return; + } + + setTimeout(function() { + e.mediaElement.play(); + }, 2000); + }; + + connection.onstreamended = function(e) { + if (!e.mediaElement) { + e.mediaElement = document.getElementById(e.streamid); + } + + if (!e.mediaElement || !e.mediaElement.parentNode) { + return; + } + + e.mediaElement.parentNode.removeChild(e.mediaElement); + }; + + connection.direction = 'many-to-many'; + + connection.removeStream = function(streamid, remoteUserId) { + var stream; + connection.attachStreams.forEach(function(localStream) { + if (localStream.id === streamid) { + stream = localStream; + } + }); + + if (!stream) { + console.warn('No such stream exist.', streamid); + return; + } + + connection.peers.getAllParticipants().forEach(function(participant) { + if (remoteUserId && participant !== remoteUserId) { + return; + } + + var user = connection.peers[participant]; + try { + user.peer.removeStream(stream); + } catch (e) {} + }); + + connection.renegotiate(); + }; + + connection.addStream = function(session, remoteUserId) { + if (!!session.getAudioTracks) { + if (connection.attachStreams.indexOf(session) === -1) { + if (!session.streamid) { + session.streamid = session.id; + } + + connection.attachStreams.push(session); + } + connection.renegotiate(remoteUserId); + return; + } + + if (isData(session)) { + connection.renegotiate(remoteUserId); + return; + } + + if (session.audio || session.video || session.screen) { + if (session.screen) { + if (DetectRTC.browser.name === 'Edge') { + navigator.getDisplayMedia({ + video: true, + audio: isAudioPlusTab(connection) + }).then(function(screen) { + screen.isScreen = true; + mPeer.onGettingLocalMedia(screen); + + if ((session.audio || session.video) && !isAudioPlusTab(connection)) { + connection.invokeGetUserMedia(null, function(stream) { + gumCallback(stream); + }); + } else { + gumCallback(screen); + } + }, function(error) { + console.error('Unable to capture screen on Edge. HTTPs and version 17+ is required.'); + }); + } else { + connection.getScreenConstraints(function(error, screen_constraints) { + if (error) { + if (error === 'PermissionDeniedError') { + if (session.streamCallback) { + session.streamCallback(null); + } + if (connection.enableLogs) { + console.error('User rejected to share his screen.'); + } + return; + } + return alert(error); + } + + connection.invokeGetUserMedia({ + audio: isAudioPlusTab(connection) ? getAudioScreenConstraints(screen_constraints) : false, + video: screen_constraints, + isScreen: true + }, function(stream) { + if ((session.audio || session.video) && !isAudioPlusTab(connection)) { + connection.invokeGetUserMedia(null, function(stream) { + gumCallback(stream); + }); + } else { + gumCallback(stream); + } + }); + }); + } + } else if (session.audio || session.video) { + connection.invokeGetUserMedia(null, gumCallback); + } + } + + function gumCallback(stream) { + if (session.streamCallback) { + session.streamCallback(stream); + } + + connection.renegotiate(remoteUserId); + } + }; + + connection.invokeGetUserMedia = function(localMediaConstraints, callback, session) { + if (!session) { + session = connection.session; + } + + if (!localMediaConstraints) { + localMediaConstraints = connection.mediaConstraints; + } + + getUserMediaHandler({ + onGettingLocalMedia: function(stream) { + var videoConstraints = localMediaConstraints.video; + if (videoConstraints) { + if (videoConstraints.mediaSource || videoConstraints.mozMediaSource) { + stream.isScreen = true; + } else if (videoConstraints.mandatory && videoConstraints.mandatory.chromeMediaSource) { + stream.isScreen = true; + } + } + + if (!stream.isScreen) { + stream.isVideo = stream.getVideoTracks().length; + stream.isAudio = !stream.isVideo && stream.getAudioTracks().length; + } + + mPeer.onGettingLocalMedia(stream, function() { + if (typeof callback === 'function') { + callback(stream); + } + }); + }, + onLocalMediaError: function(error, constraints) { + mPeer.onLocalMediaError(error, constraints); + }, + localMediaConstraints: localMediaConstraints || { + audio: session.audio ? localMediaConstraints.audio : false, + video: session.video ? localMediaConstraints.video : false + } + }); + }; + + function applyConstraints(stream, mediaConstraints) { + if (!stream) { + if (!!connection.enableLogs) { + console.error('No stream to applyConstraints.'); + } + return; + } + + if (mediaConstraints.audio) { + stream.getAudioTracks().forEach(function(track) { + track.applyConstraints(mediaConstraints.audio); + }); + } + + if (mediaConstraints.video) { + stream.getVideoTracks().forEach(function(track) { + track.applyConstraints(mediaConstraints.video); + }); + } + } + + connection.applyConstraints = function(mediaConstraints, streamid) { + if (!MediaStreamTrack || !MediaStreamTrack.prototype.applyConstraints) { + alert('track.applyConstraints is NOT supported in your browser.'); + return; + } + + if (streamid) { + var stream; + if (connection.streamEvents[streamid]) { + stream = connection.streamEvents[streamid].stream; + } + applyConstraints(stream, mediaConstraints); + return; + } + + connection.attachStreams.forEach(function(stream) { + applyConstraints(stream, mediaConstraints); + }); + }; + + function replaceTrack(track, remoteUserId, isVideoTrack) { + if (remoteUserId) { + mPeer.replaceTrack(track, remoteUserId, isVideoTrack); + return; + } + + connection.peers.getAllParticipants().forEach(function(participant) { + mPeer.replaceTrack(track, participant, isVideoTrack); + }); + } + + connection.replaceTrack = function(session, remoteUserId, isVideoTrack) { + session = session || {}; + + if (!RTCPeerConnection.prototype.getSenders) { + connection.addStream(session); + return; + } + + if (session instanceof MediaStreamTrack) { + replaceTrack(session, remoteUserId, isVideoTrack); + return; + } + + if (session instanceof MediaStream) { + if (session.getVideoTracks().length) { + replaceTrack(session.getVideoTracks()[0], remoteUserId, true); + } + + if (session.getAudioTracks().length) { + replaceTrack(session.getAudioTracks()[0], remoteUserId, false); + } + return; + } + + if (isData(session)) { + throw 'connection.replaceTrack requires audio and/or video and/or screen.'; + return; + } + + if (session.audio || session.video || session.screen) { + if (session.screen) { + if (DetectRTC.browser.name === 'Edge') { + navigator.getDisplayMedia({ + video: true, + audio: isAudioPlusTab(connection) + }).then(function(screen) { + screen.isScreen = true; + mPeer.onGettingLocalMedia(screen); + + if ((session.audio || session.video) && !isAudioPlusTab(connection)) { + connection.invokeGetUserMedia(null, gumCallback); + } else { + gumCallback(screen); + } + }, function(error) { + console.error('Unable to capture screen on Edge. HTTPs and version 17+ is required.'); + }); + } else { + connection.getScreenConstraints(function(error, screen_constraints) { + if (error) { + return alert(error); + } + + connection.invokeGetUserMedia({ + audio: isAudioPlusTab(connection) ? getAudioScreenConstraints(screen_constraints) : false, + video: screen_constraints, + isScreen: true + }, (session.audio || session.video) && !isAudioPlusTab(connection) ? connection.invokeGetUserMedia(null, gumCallback) : gumCallback); + }); + } + } else if (session.audio || session.video) { + connection.invokeGetUserMedia(null, gumCallback); + } + } + + function gumCallback(stream) { + connection.replaceTrack(stream, remoteUserId, isVideoTrack || session.video || session.screen); + } + }; + + connection.resetTrack = function(remoteUsersIds, isVideoTrack) { + if (!remoteUsersIds) { + remoteUsersIds = connection.getAllParticipants(); + } + + if (typeof remoteUsersIds == 'string') { + remoteUsersIds = [remoteUsersIds]; + } + + remoteUsersIds.forEach(function(participant) { + var peer = connection.peers[participant].peer; + + if ((typeof isVideoTrack === 'undefined' || isVideoTrack === true) && peer.lastVideoTrack) { + connection.replaceTrack(peer.lastVideoTrack, participant, true); + } + + if ((typeof isVideoTrack === 'undefined' || isVideoTrack === false) && peer.lastAudioTrack) { + connection.replaceTrack(peer.lastAudioTrack, participant, false); + } + }); + }; + + connection.renegotiate = function(remoteUserId) { + if (remoteUserId) { + mPeer.renegotiatePeer(remoteUserId); + return; + } + + connection.peers.getAllParticipants().forEach(function(participant) { + mPeer.renegotiatePeer(participant); + }); + }; + + connection.setStreamEndHandler = function(stream, isRemote) { + if (!stream || !stream.addEventListener) return; + + isRemote = !!isRemote; + + if (stream.alreadySetEndHandler) { + return; + } + stream.alreadySetEndHandler = true; + + var streamEndedEvent = 'ended'; + + if ('oninactive' in stream) { + streamEndedEvent = 'inactive'; + } + + stream.addEventListener(streamEndedEvent, function() { + if (stream.idInstance) { + currentUserMediaRequest.remove(stream.idInstance); + } + + if (!isRemote) { + // reset attachStreams + var streams = []; + connection.attachStreams.forEach(function(s) { + if (s.id != stream.id) { + streams.push(s); + } + }); + connection.attachStreams = streams; + } + + // connection.renegotiate(); + + var streamEvent = connection.streamEvents[stream.streamid]; + if (!streamEvent) { + streamEvent = { + stream: stream, + streamid: stream.streamid, + type: isRemote ? 'remote' : 'local', + userid: connection.userid, + extra: connection.extra, + mediaElement: connection.streamEvents[stream.streamid] ? connection.streamEvents[stream.streamid].mediaElement : null + }; + } + + if (isRemote && connection.peers[streamEvent.userid]) { + // reset remote "streams" + var peer = connection.peers[streamEvent.userid].peer; + var streams = []; + peer.getRemoteStreams().forEach(function(s) { + if (s.id != stream.id) { + streams.push(s); + } + }); + connection.peers[streamEvent.userid].streams = streams; + } + + if (streamEvent.userid === connection.userid && streamEvent.type === 'remote') { + return; + } + + if (connection.peersBackup[streamEvent.userid]) { + streamEvent.extra = connection.peersBackup[streamEvent.userid].extra; + } + + connection.onstreamended(streamEvent); + + delete connection.streamEvents[stream.streamid]; + }, false); + }; + + connection.onMediaError = function(error, constraints) { + if (!!connection.enableLogs) { + console.error(error, constraints); + } + }; + + connection.addNewBroadcaster = function(broadcasterId, userPreferences) { + if (connection.socket.isIO) { + return; + } + + if (connection.broadcasters.length) { + setTimeout(function() { + mPeer.connectNewParticipantWithAllBroadcasters(broadcasterId, userPreferences, connection.broadcasters.join('|-,-|')); + }, 10 * 1000); + } + + if (!connection.session.oneway && !connection.session.broadcast && connection.direction === 'many-to-many' && connection.broadcasters.indexOf(broadcasterId) === -1) { + connection.broadcasters.push(broadcasterId); + keepNextBroadcasterOnServer(); + } + }; + + connection.autoCloseEntireSession = false; + + function keepNextBroadcasterOnServer() { + if (!connection.isInitiator) return; + + if (connection.session.oneway || connection.session.broadcast || connection.direction !== 'many-to-many') { + return; + } + + var firstBroadcaster = connection.broadcasters[0]; + var otherBroadcasters = []; + connection.broadcasters.forEach(function(broadcaster) { + if (broadcaster !== firstBroadcaster) { + otherBroadcasters.push(broadcaster); + } + }); + + if (connection.autoCloseEntireSession) return; + connection.shiftModerationControl(firstBroadcaster, otherBroadcasters, true); + }; + + connection.filesContainer = connection.videosContainer = document.body || document.documentElement; + connection.isInitiator = false; + + connection.shareFile = mPeer.shareFile; + if (typeof FileProgressBarHandler !== 'undefined') { + FileProgressBarHandler.handle(connection); + } + + if (typeof TranslationHandler !== 'undefined') { + TranslationHandler.handle(connection); + } + + connection.token = getRandomString; + + connection.onNewParticipant = function(participantId, userPreferences) { + connection.acceptParticipationRequest(participantId, userPreferences); + }; + + connection.acceptParticipationRequest = function(participantId, userPreferences) { + if (userPreferences.successCallback) { + userPreferences.successCallback(); + delete userPreferences.successCallback; + } + + mPeer.createNewPeer(participantId, userPreferences); + }; + + connection.onShiftedModerationControl = function(sender, existingBroadcasters) { + connection.acceptModerationControl(sender, existingBroadcasters); + }; + + connection.acceptModerationControl = function(sender, existingBroadcasters) { + connection.isInitiator = true; // NEW initiator! + + connection.broadcasters = existingBroadcasters; + connection.peers.getAllParticipants().forEach(function(participant) { + mPeer.onNegotiationNeeded({ + changedUUID: sender, + oldUUID: connection.userid, + newUUID: sender + }, participant); + }); + connection.userid = sender; + connection.changeUserId(connection.userid); + }; + + connection.shiftModerationControl = function(remoteUserId, existingBroadcasters, firedOnLeave) { + mPeer.onNegotiationNeeded({ + shiftedModerationControl: true, + broadcasters: existingBroadcasters, + firedOnLeave: !!firedOnLeave + }, remoteUserId); + }; + + if (typeof StreamsHandler !== 'undefined') { + connection.StreamsHandler = StreamsHandler; + } + + connection.onleave = function(userid) {}; + + connection.invokeSelectFileDialog = function(callback) { + var selector = new FileSelector(); + selector.accept = '*.*'; + selector.selectSingleFile(callback); + }; + + connection.getPublicModerators = connection.getPublicUsers = function(userIdStartsWith, callback) { + if (typeof userIdStartsWith === 'function') { + callback = userIdStartsWith; + } + + connectSocket(function() { + connection.socket.emit( + 'get-public-moderators', + typeof userIdStartsWith === 'string' ? userIdStartsWith : '', + callback + ); + }); + }; + + connection.onmute = function(e) { + if (!e || !e.mediaElement) { + return; + } + + if (e.muteType === 'both' || e.muteType === 'video') { + e.mediaElement.src = null; + var paused = e.mediaElement.pause(); + if (typeof paused !== 'undefined') { + paused.then(function() { + e.mediaElement.poster = e.snapshot || 'https://cdn.webrtc-experiment.com/images/muted.png'; + }); + } else { + e.mediaElement.poster = e.snapshot || 'https://cdn.webrtc-experiment.com/images/muted.png'; + } + } else if (e.muteType === 'audio') { + e.mediaElement.muted = true; + } + }; + + connection.onunmute = function(e) { + if (!e || !e.mediaElement || !e.stream) { + return; + } + + if (e.unmuteType === 'both' || e.unmuteType === 'video') { + e.mediaElement.poster = null; + e.mediaElement.srcObject = e.stream; + e.mediaElement.play(); + } else if (e.unmuteType === 'audio') { + e.mediaElement.muted = false; + } + }; + + connection.onExtraDataUpdated = function(event) { + event.status = 'online'; + connection.onUserStatusChanged(event, true); + }; + + connection.onJoinWithPassword = function(remoteUserId) { + console.warn(remoteUserId, 'is password protected. Please join with password.'); + }; + + connection.onInvalidPassword = function(remoteUserId, oldPassword) { + console.warn(remoteUserId, 'is password protected. Please join with valid password. Your old password', oldPassword, 'is wrong.'); + }; + + connection.onPasswordMaxTriesOver = function(remoteUserId) { + console.warn(remoteUserId, 'is password protected. Your max password tries exceeded the limit.'); + }; + + connection.getAllParticipants = function(sender) { + return connection.peers.getAllParticipants(sender); + }; + + if (typeof StreamsHandler !== 'undefined') { + StreamsHandler.onSyncNeeded = function(streamid, action, type) { + connection.peers.getAllParticipants().forEach(function(participant) { + mPeer.onNegotiationNeeded({ + streamid: streamid, + action: action, + streamSyncNeeded: true, + type: type || 'both' + }, participant); + }); + }; + } + + connection.connectSocket = function(callback) { + connectSocket(callback); + }; + + connection.closeSocket = function() { + try { + io.sockets = {}; + } catch (e) {}; + + if (!connection.socket) return; + + if (typeof connection.socket.disconnect === 'function') { + connection.socket.disconnect(); + } + + if (typeof connection.socket.resetProps === 'function') { + connection.socket.resetProps(); + } + + connection.socket = null; + }; + + connection.getSocket = function(callback) { + if (!connection.socket) { + connectSocket(callback); + } else if (callback) { + callback(connection.socket); + } + + return connection.socket; + }; + + connection.getRemoteStreams = mPeer.getRemoteStreams; + + var skipStreams = ['selectFirst', 'selectAll', 'forEach']; + + connection.streamEvents = { + selectFirst: function(options) { + return connection.streamEvents.selectAll(options)[0]; + }, + selectAll: function(options) { + if (!options) { + // default will always be all streams + options = { + local: true, + remote: true, + isScreen: true, + isAudio: true, + isVideo: true + }; + } + + if (options == 'local') { + options = { + local: true + }; + } + + if (options == 'remote') { + options = { + remote: true + }; + } + + if (options == 'screen') { + options = { + isScreen: true + }; + } + + if (options == 'audio') { + options = { + isAudio: true + }; + } + + if (options == 'video') { + options = { + isVideo: true + }; + } + + var streams = []; + Object.keys(connection.streamEvents).forEach(function(key) { + var event = connection.streamEvents[key]; + + if (skipStreams.indexOf(key) !== -1) return; + var ignore = true; + + if (options.local && event.type === 'local') { + ignore = false; + } + + if (options.remote && event.type === 'remote') { + ignore = false; + } + + if (options.isScreen && event.stream.isScreen) { + ignore = false; + } + + if (options.isVideo && event.stream.isVideo) { + ignore = false; + } + + if (options.isAudio && event.stream.isAudio) { + ignore = false; + } + + if (options.userid && event.userid === options.userid) { + ignore = false; + } + + if (ignore === false) { + streams.push(event); + } + }); + + return streams; + } + }; + + connection.socketURL = '/'; // generated via config.json + connection.socketMessageEvent = 'RTCMultiConnection-Message'; // generated via config.json + connection.socketCustomEvent = 'RTCMultiConnection-Custom-Message'; // generated via config.json + connection.DetectRTC = DetectRTC; + + connection.setCustomSocketEvent = function(customEvent) { + if (customEvent) { + connection.socketCustomEvent = customEvent; + } + + if (!connection.socket) { + return; + } + + connection.socket.emit('set-custom-socket-event-listener', connection.socketCustomEvent); + }; + + connection.getNumberOfBroadcastViewers = function(broadcastId, callback) { + if (!connection.socket || !broadcastId || !callback) return; + + connection.socket.emit('get-number-of-users-in-specific-broadcast', broadcastId, callback); + }; + + connection.onNumberOfBroadcastViewersUpdated = function(event) { + if (!connection.enableLogs || !connection.isInitiator) return; + console.info('Number of broadcast (', event.broadcastId, ') viewers', event.numberOfBroadcastViewers); + }; + + connection.onUserStatusChanged = function(event, dontWriteLogs) { + if (!!connection.enableLogs && !dontWriteLogs) { + console.info(event.userid, event.status); + } + }; + + connection.getUserMediaHandler = getUserMediaHandler; + connection.multiPeersHandler = mPeer; + connection.enableLogs = true; + connection.setCustomSocketHandler = function(customSocketHandler) { + if (typeof SocketConnection !== 'undefined') { + SocketConnection = customSocketHandler; + } + }; + + // default value should be 15k because [old]Firefox's receiving limit is 16k! + // however 64k works chrome-to-chrome + connection.chunkSize = 65 * 1000; + + connection.maxParticipantsAllowed = 1000; + + // eject or leave single user + connection.disconnectWith = mPeer.disconnectWith; + + // check if room exist on server + // we will pass roomid to the server and wait for callback (i.e. server's response) + connection.checkPresence = function(remoteUserId, callback) { + if (SocketConnection.name === 'SSEConnection') { + SSEConnection.checkPresence(remoteUserId, function(isRoomExist, roomid) { + if (!connection.socket) { + if (!isRoomExist) { + connection.userid = roomid; + } + + connection.connectSocket(function() { + callback(isRoomExist, roomid); + }); + return; + } + callback(isRoomExist, roomid); + }); + return; + } + + if (!connection.socket) { + connection.connectSocket(function() { + connection.checkPresence(remoteUserId, callback); + }); + return; + } + connection.socket.emit('check-presence', (remoteUserId || connection.sessionid) + '', callback); + }; + + connection.onReadyForOffer = function(remoteUserId, userPreferences) { + connection.multiPeersHandler.createNewPeer(remoteUserId, userPreferences); + }; + + connection.setUserPreferences = function(userPreferences) { + if (connection.dontAttachStream) { + userPreferences.dontAttachLocalStream = true; + } + + if (connection.dontGetRemoteStream) { + userPreferences.dontGetRemoteStream = true; + } + + return userPreferences; + }; + + connection.updateExtraData = function() { + connection.socket.emit('extra-data-updated', connection.extra); + }; + + connection.enableScalableBroadcast = false; + connection.maxRelayLimitPerUser = 3; // each broadcast should serve only 3 users + + connection.dontCaptureUserMedia = false; + connection.dontAttachStream = false; + connection.dontGetRemoteStream = false; + + connection.onReConnecting = function(event) { + if (connection.enableLogs) { + console.info('ReConnecting with', event.userid, '...'); + } + }; + + connection.beforeAddingStream = function(stream) { + return stream; + }; + + connection.beforeRemovingStream = function(stream) { + return stream; + }; + + if (typeof isChromeExtensionAvailable !== 'undefined') { + connection.checkIfChromeExtensionAvailable = isChromeExtensionAvailable; + } + + if (typeof isFirefoxExtensionAvailable !== 'undefined') { + connection.checkIfChromeExtensionAvailable = isFirefoxExtensionAvailable; + } + + if (typeof getChromeExtensionStatus !== 'undefined') { + connection.getChromeExtensionStatus = getChromeExtensionStatus; + } + + connection.getScreenConstraints = function(callback, audioPlusTab) { + if (isAudioPlusTab(connection, audioPlusTab)) { + audioPlusTab = true; + } + + getScreenConstraints(function(error, screen_constraints) { + if (!error) { + screen_constraints = connection.modifyScreenConstraints(screen_constraints); + callback(error, screen_constraints); + } + }, audioPlusTab); + }; + + connection.modifyScreenConstraints = function(screen_constraints) { + return screen_constraints; + }; + + connection.onPeerStateChanged = function(state) { + if (connection.enableLogs) { + if (state.iceConnectionState.search(/closed|failed/gi) !== -1) { + console.error('Peer connection is closed between you & ', state.userid, state.extra, 'state:', state.iceConnectionState); + } + } + }; + + connection.isOnline = true; + + listenEventHandler('online', function() { + connection.isOnline = true; + }); + + listenEventHandler('offline', function() { + connection.isOnline = false; + }); + + connection.isLowBandwidth = false; + if (navigator && navigator.connection && navigator.connection.type) { + connection.isLowBandwidth = navigator.connection.type.toString().toLowerCase().search(/wifi|cell/g) !== -1; + if (connection.isLowBandwidth) { + connection.bandwidth = { + audio: false, + video: false, + screen: false + }; + + if (connection.mediaConstraints.audio && connection.mediaConstraints.audio.optional && connection.mediaConstraints.audio.optional.length) { + var newArray = []; + connection.mediaConstraints.audio.optional.forEach(function(opt) { + if (typeof opt.bandwidth === 'undefined') { + newArray.push(opt); + } + }); + connection.mediaConstraints.audio.optional = newArray; + } + + if (connection.mediaConstraints.video && connection.mediaConstraints.video.optional && connection.mediaConstraints.video.optional.length) { + var newArray = []; + connection.mediaConstraints.video.optional.forEach(function(opt) { + if (typeof opt.bandwidth === 'undefined') { + newArray.push(opt); + } + }); + connection.mediaConstraints.video.optional = newArray; + } + } + } + + connection.getExtraData = function(remoteUserId) { + if (!remoteUserId) throw 'remoteUserId is required.'; + if (!connection.peers[remoteUserId]) return {}; + return connection.peers[remoteUserId].extra; + }; + + if (!!forceOptions.autoOpenOrJoin) { + connection.openOrJoin(connection.sessionid); + } + + connection.onUserIdAlreadyTaken = function(useridAlreadyTaken, yourNewUserId) { + if (connection.enableLogs) { + console.warn('Userid already taken.', useridAlreadyTaken, 'Your new userid:', yourNewUserId); + } + + connection.join(useridAlreadyTaken); + }; + + connection.onRoomFull = function(roomid) { + if (connection.enableLogs) { + console.warn(roomid, 'is full.'); + } + }; + + connection.trickleIce = true; + connection.version = '3.4.7'; + + connection.onSettingLocalDescription = function(event) { + if (connection.enableLogs) { + console.info('Set local description for remote user', event.userid); + } + }; + + connection.oneRoomAlreadyExist = function(roomid) { + if (connection.enableLogs) { + console.info('Server says "Room ', roomid, 'already exist. Joining instead.'); + } + connection.join(roomid); + }; + + connection.resetScreen = function() { + sourceId = null; + if (DetectRTC && DetectRTC.screen) { + delete DetectRTC.screen.sourceId; + } + + currentUserMediaRequest = { + streams: [], + mutex: false, + queueRequests: [] + }; + }; + + // if disabled, "event.mediaElement" for "onstream" will be NULL + connection.autoCreateMediaElement = true; + + // open or join with a password + connection.password = null; + })(this); + +}; diff --git a/RTCMultiConnection-SignalR/RTCMultiConnection/Scripts/RTCMultiConnection.min.js b/RTCMultiConnection-SignalR/RTCMultiConnection/Scripts/RTCMultiConnection.min.js new file mode 100644 index 00000000..5a7ebdde --- /dev/null +++ b/RTCMultiConnection-SignalR/RTCMultiConnection/Scripts/RTCMultiConnection.min.js @@ -0,0 +1,19 @@ +'use strict'; + +// Last time updated: 2018-09-15 3:49:43 PM UTC + +// _________________________ +// RTCMultiConnection v3.4.7 + +// Open-Sourced: https://github.com/muaz-khan/RTCMultiConnection + +// -------------------------------------------------- +// Muaz Khan - www.MuazKhan.com +// MIT License - www.WebRTC-Experiment.com/licence +// -------------------------------------------------- + +"use strict";window.RTCMultiConnection=function(roomid,forceOptions){function SocketConnection(connection,connectCallback){function onMessageEvent(message){if(message.remoteUserId==connection.userid){if(connection.peers[message.sender]&&connection.peers[message.sender].extra!=message.message.extra&&(connection.peers[message.sender].extra=message.extra,connection.onExtraDataUpdated({userid:message.sender,extra:message.extra})),"next-possible-initiator"===message.message){if(connection.nextPossibleInitiatorIfThisUserLeave)return;return void(connection.nextPossibleInitiatorIfThisUserLeave=message.sender)}if(message.message.streamSyncNeeded&&connection.peers[message.sender]){var stream=connection.streamEvents[message.message.streamid];if(!stream||!stream.stream)return;var action=message.message.action;if("ended"===action||"inactive"===action||"stream-removed"===action)return connection.peersBackup[stream.userid]&&(stream.extra=connection.peersBackup[stream.userid].extra),void connection.onstreamended(stream);var type="both"!=message.message.type?message.message.type:null;return void("function"==typeof stream.stream[action]&&stream.stream[action](type))}if("connectWithAllParticipants"===message.message)return connection.broadcasters.indexOf(message.sender)===-1&&connection.broadcasters.push(message.sender),void mPeer.onNegotiationNeeded({allParticipants:connection.getAllParticipants(message.sender)},message.sender);if("removeFromBroadcastersList"===message.message)return void(connection.broadcasters.indexOf(message.sender)!==-1&&(delete connection.broadcasters[connection.broadcasters.indexOf(message.sender)],connection.broadcasters=removeNullEntries(connection.broadcasters)));if("dropPeerConnection"===message.message)return void connection.deletePeer(message.sender);if(message.message.allParticipants)return message.message.allParticipants.indexOf(message.sender)===-1&&message.message.allParticipants.push(message.sender),void message.message.allParticipants.forEach(function(participant){mPeer[connection.peers[participant]?"renegotiatePeer":"createNewPeer"](participant,{localPeerSdpConstraints:{OfferToReceiveAudio:connection.sdpConstraints.mandatory.OfferToReceiveAudio,OfferToReceiveVideo:connection.sdpConstraints.mandatory.OfferToReceiveVideo},remotePeerSdpConstraints:{OfferToReceiveAudio:connection.session.oneway?!!connection.session.audio:connection.sdpConstraints.mandatory.OfferToReceiveAudio,OfferToReceiveVideo:connection.session.oneway?!!connection.session.video||!!connection.session.screen:connection.sdpConstraints.mandatory.OfferToReceiveVideo},isOneWay:!!connection.session.oneway||"one-way"===connection.direction,isDataOnly:isData(connection.session)})});if(message.message.newParticipant){if(message.message.newParticipant==connection.userid)return;if(connection.peers[message.message.newParticipant])return;return void mPeer.createNewPeer(message.message.newParticipant,message.message.userPreferences||{localPeerSdpConstraints:{OfferToReceiveAudio:connection.sdpConstraints.mandatory.OfferToReceiveAudio,OfferToReceiveVideo:connection.sdpConstraints.mandatory.OfferToReceiveVideo},remotePeerSdpConstraints:{OfferToReceiveAudio:connection.session.oneway?!!connection.session.audio:connection.sdpConstraints.mandatory.OfferToReceiveAudio,OfferToReceiveVideo:connection.session.oneway?!!connection.session.video||!!connection.session.screen:connection.sdpConstraints.mandatory.OfferToReceiveVideo},isOneWay:!!connection.session.oneway||"one-way"===connection.direction,isDataOnly:isData(connection.session)})}if(message.message.readyForOffer||message.message.addMeAsBroadcaster){if(connection.attachStreams.length&&(connection.waitingForLocalMedia=!1),connection.waitingForLocalMedia)return void setTimeout(function(){onMessageEvent(message)},1e3);connection.addNewBroadcaster(message.sender)}if(message.message.newParticipationRequest&&message.sender!==connection.userid){connection.peers[message.sender]&&connection.deletePeer(message.sender);var userPreferences={extra:message.extra||{},localPeerSdpConstraints:message.message.remotePeerSdpConstraints||{OfferToReceiveAudio:connection.sdpConstraints.mandatory.OfferToReceiveAudio,OfferToReceiveVideo:connection.sdpConstraints.mandatory.OfferToReceiveVideo},remotePeerSdpConstraints:message.message.localPeerSdpConstraints||{OfferToReceiveAudio:connection.session.oneway?!!connection.session.audio:connection.sdpConstraints.mandatory.OfferToReceiveAudio,OfferToReceiveVideo:connection.session.oneway?!!connection.session.video||!!connection.session.screen:connection.sdpConstraints.mandatory.OfferToReceiveVideo},isOneWay:"undefined"!=typeof message.message.isOneWay?message.message.isOneWay:!!connection.session.oneway||"one-way"===connection.direction,isDataOnly:"undefined"!=typeof message.message.isDataOnly?message.message.isDataOnly:isData(connection.session),dontGetRemoteStream:"undefined"!=typeof message.message.isOneWay?message.message.isOneWay:!!connection.session.oneway||"one-way"===connection.direction,dontAttachLocalStream:!!message.message.dontGetRemoteStream,connectionDescription:message,successCallback:function(){("undefined"!=typeof message.message.isOneWay?message.message.isOneWay:!!connection.session.oneway||"one-way"===connection.direction)&&connection.addNewBroadcaster(message.sender,userPreferences),(connection.session.oneway||"one-way"===connection.direction||isData(connection.session))&&connection.addNewBroadcaster(message.sender,userPreferences)}};return void connection.onNewParticipant(message.sender,userPreferences)}return message.message.shiftedModerationControl?void connection.onShiftedModerationControl(message.sender,message.message.broadcasters):(message.message.changedUUID&&connection.peers[message.message.oldUUID]&&(connection.peers[message.message.newUUID]=connection.peers[message.message.oldUUID],delete connection.peers[message.message.oldUUID]),message.message.userLeft?(mPeer.onUserLeft(message.sender),void(message.message.autoCloseEntireSession&&connection.leave())):void mPeer.addNegotiatedMessage(message.message,message.sender))}}var parameters="";parameters+="?userid="+connection.userid,parameters+="&sessionid="+connection.sessionid,parameters+="&msgEvent="+connection.socketMessageEvent,parameters+="&socketCustomEvent="+connection.socketCustomEvent,parameters+="&autoCloseEntireSession="+!!connection.autoCloseEntireSession,connection.session.broadcast===!0&&(parameters+="&oneToMany=true"),parameters+="&maxParticipantsAllowed="+connection.maxParticipantsAllowed,connection.enableScalableBroadcast&&(parameters+="&enableScalableBroadcast=true",parameters+="&maxRelayLimitPerUser="+(connection.maxRelayLimitPerUser||2)),connection.socketCustomParameters&&(parameters+=connection.socketCustomParameters);try{io.sockets={}}catch(e){}if(connection.socketURL||(connection.socketURL="/"),"/"!=connection.socketURL.substr(connection.socketURL.length-1,1))throw'"socketURL" MUST end with a slash.';connection.enableLogs&&("/"==connection.socketURL?console.info("socket.io is connected at: ",location.origin+"/"):console.info("socket.io is connected at: ",connection.socketURL));try{connection.socket=io(connection.socketURL+parameters)}catch(e){connection.socket=io.connect(connection.socketURL+parameters,connection.socketOptions)}connection.socket.isIO=!0;var mPeer=connection.multiPeersHandler;connection.socket.on("extra-data-updated",function(remoteUserId,extra){connection.peers[remoteUserId]&&(connection.peers[remoteUserId].extra=extra,connection.onExtraDataUpdated({userid:remoteUserId,extra:extra}),connection.peersBackup[remoteUserId]||(connection.peersBackup[remoteUserId]={userid:remoteUserId,extra:{}}),connection.peersBackup[remoteUserId].extra=extra)}),connection.socket.on(connection.socketMessageEvent,onMessageEvent),connection.socket.on("user-left",function(userid){onUserLeft(userid),connection.onUserStatusChanged({userid:userid,status:"offline",extra:connection.peers[userid]?connection.peers[userid].extra||{}:{}});var eventObject={userid:userid,extra:{}};connection.peersBackup[eventObject.userid]&&(eventObject.extra=connection.peersBackup[eventObject.userid].extra),connection.onleave(eventObject),connection.nextPossibleInitiatorIfThisUserLeave===userid&&(connection.nextPossibleInitiatorIfThisUserLeave=null,connection.open(connection.sessionid))});var alreadyConnected=!1;connection.socket.resetProps=function(){alreadyConnected=!1},connection.socket.on("connect",function(){alreadyConnected||(alreadyConnected=!0,connection.enableLogs&&console.info("socket.io connection is opened."),setTimeout(function(){connection.socket.emit("extra-data-updated",connection.extra),connectCallback&&connectCallback(connection.socket)},1e3))}),connection.socket.on("disconnect",function(){connection.enableLogs&&console.warn("socket.io connection is closed")}),connection.socket.on("join-with-password",function(remoteUserId){connection.onJoinWithPassword(remoteUserId)}),connection.socket.on("invalid-password",function(remoteUserId,oldPassword){connection.onInvalidPassword(remoteUserId,oldPassword)}),connection.socket.on("password-max-tries-over",function(remoteUserId){connection.onPasswordMaxTriesOver(remoteUserId)}),connection.socket.on("user-disconnected",function(remoteUserId){remoteUserId!==connection.userid&&(connection.onUserStatusChanged({userid:remoteUserId,status:"offline",extra:connection.peers[remoteUserId]?connection.peers[remoteUserId].extra||{}:{}}),connection.deletePeer(remoteUserId))}),connection.socket.on("user-connected",function(userid){userid!==connection.userid&&connection.onUserStatusChanged({userid:userid,status:"online",extra:connection.peers[userid]?connection.peers[userid].extra||{}:{}})}),connection.socket.on("closed-entire-session",function(sessionid,extra){connection.leave(),connection.onEntireSessionClosed({sessionid:sessionid,userid:sessionid,extra:extra})}),connection.socket.on("userid-already-taken",function(useridAlreadyTaken,yourNewUserId){connection.isInitiator=!1,connection.userid=yourNewUserId,connection.onUserIdAlreadyTaken(useridAlreadyTaken,yourNewUserId)}),connection.socket.on("logs",function(log){connection.enableLogs&&console.debug("server-logs",log)}),connection.socket.on("number-of-broadcast-viewers-updated",function(data){connection.onNumberOfBroadcastViewersUpdated(data)}),connection.socket.on("room-full",function(roomid){connection.onRoomFull(roomid)}),connection.socket.on("become-next-modrator",function(sessionid){sessionid==connection.sessionid&&setTimeout(function(){connection.open(sessionid),connection.socket.emit("shift-moderator-control-on-disconnect")},1e3)})}function MultiPeers(connection){function initFileBufferReader(){connection.fbr=new FileBufferReader,connection.fbr.onProgress=function(chunk){connection.onFileProgress(chunk)},connection.fbr.onBegin=function(file){connection.onFileStart(file)},connection.fbr.onEnd=function(file){connection.onFileEnd(file)}}var self=this,skipPeers=["getAllParticipants","getLength","selectFirst","streams","send","forEach"];connection.peersBackup={},connection.peers={getLength:function(){var numberOfPeers=0;for(var peer in this)skipPeers.indexOf(peer)==-1&&numberOfPeers++;return numberOfPeers},selectFirst:function(){var firstPeer;for(var peer in this)skipPeers.indexOf(peer)==-1&&(firstPeer=this[peer]);return firstPeer},getAllParticipants:function(sender){var allPeers=[];for(var peer in this)skipPeers.indexOf(peer)==-1&&peer!=sender&&allPeers.push(peer);return allPeers},forEach:function(callbcak){this.getAllParticipants().forEach(function(participant){callbcak(connection.peers[participant])})},send:function(data,remoteUserId){var that=this;if(!isNull(data.size)&&!isNull(data.type))return void self.shareFile(data,remoteUserId);if(!("text"===data.type||data instanceof ArrayBuffer||data instanceof DataView))return void TextSender.send({text:data,channel:this,connection:connection,remoteUserId:remoteUserId});if("text"===data.type&&(data=JSON.stringify(data)),remoteUserId){var remoteUser=connection.peers[remoteUserId];if(remoteUser)return remoteUser.channels.length?void remoteUser.channels.forEach(function(channel){channel.send(data)}):(connection.peers[remoteUserId].createDataChannel(),connection.renegotiate(remoteUserId),void setTimeout(function(){that.send(data,remoteUserId)},3e3))}this.getAllParticipants().forEach(function(participant){return that[participant].channels.length?void that[participant].channels.forEach(function(channel){channel.send(data)}):(connection.peers[participant].createDataChannel(),connection.renegotiate(participant),void setTimeout(function(){that[participant].channels.forEach(function(channel){channel.send(data)})},3e3))})}},this.uuid=connection.userid,this.getLocalConfig=function(remoteSdp,remoteUserId,userPreferences){return userPreferences||(userPreferences={}),{streamsToShare:userPreferences.streamsToShare||{},rtcMultiConnection:connection,connectionDescription:userPreferences.connectionDescription,userid:remoteUserId,localPeerSdpConstraints:userPreferences.localPeerSdpConstraints,remotePeerSdpConstraints:userPreferences.remotePeerSdpConstraints,dontGetRemoteStream:!!userPreferences.dontGetRemoteStream,dontAttachLocalStream:!!userPreferences.dontAttachLocalStream,renegotiatingPeer:!!userPreferences.renegotiatingPeer,peerRef:userPreferences.peerRef,channels:userPreferences.channels||[],onLocalSdp:function(localSdp){self.onNegotiationNeeded(localSdp,remoteUserId)},onLocalCandidate:function(localCandidate){localCandidate=OnIceCandidateHandler.processCandidates(connection,localCandidate),localCandidate&&self.onNegotiationNeeded(localCandidate,remoteUserId)},remoteSdp:remoteSdp,onDataChannelMessage:function(message){if(!connection.fbr&&connection.enableFileSharing&&initFileBufferReader(),"string"==typeof message||!connection.enableFileSharing)return void self.onDataChannelMessage(message,remoteUserId);var that=this;return message instanceof ArrayBuffer||message instanceof DataView?void connection.fbr.convertToObject(message,function(object){that.onDataChannelMessage(object)}):message.readyForNextChunk?void connection.fbr.getNextChunk(message,function(nextChunk,isLastChunk){connection.peers[remoteUserId].channels.forEach(function(channel){channel.send(nextChunk)})},remoteUserId):message.chunkMissing?void connection.fbr.chunkMissing(message):void connection.fbr.addChunk(message,function(promptNextChunk){connection.peers[remoteUserId].peer.channel.send(promptNextChunk)})},onDataChannelError:function(error){self.onDataChannelError(error,remoteUserId)},onDataChannelOpened:function(channel){self.onDataChannelOpened(channel,remoteUserId)},onDataChannelClosed:function(event){self.onDataChannelClosed(event,remoteUserId)},onRemoteStream:function(stream){connection.peers[remoteUserId]&&connection.peers[remoteUserId].streams.push(stream),self.onGettingRemoteMedia(stream,remoteUserId)},onRemoteStreamRemoved:function(stream){self.onRemovingRemoteMedia(stream,remoteUserId)},onPeerStateChanged:function(states){self.onPeerStateChanged(states),"new"===states.iceConnectionState&&self.onNegotiationStarted(remoteUserId,states),"connected"===states.iceConnectionState&&self.onNegotiationCompleted(remoteUserId,states),states.iceConnectionState.search(/closed|failed/gi)!==-1&&(self.onUserLeft(remoteUserId),self.disconnectWith(remoteUserId))}}},this.createNewPeer=function(remoteUserId,userPreferences){if(!(connection.maxParticipantsAllowed<=connection.getAllParticipants().length)){if(userPreferences=userPreferences||{},connection.isInitiator&&connection.session.audio&&"two-way"===connection.session.audio&&!userPreferences.streamsToShare&&(userPreferences.isOneWay=!1,userPreferences.isDataOnly=!1,userPreferences.session=connection.session),!userPreferences.isOneWay&&!userPreferences.isDataOnly)return userPreferences.isOneWay=!0,void this.onNegotiationNeeded({enableMedia:!0,userPreferences:userPreferences},remoteUserId);userPreferences=connection.setUserPreferences(userPreferences,remoteUserId);var localConfig=this.getLocalConfig(null,remoteUserId,userPreferences);connection.peers[remoteUserId]=new PeerInitiator(localConfig),this.checkIfNextPossibleInitiator(remoteUserId)}},this.createAnsweringPeer=function(remoteSdp,remoteUserId,userPreferences){userPreferences=connection.setUserPreferences(userPreferences||{},remoteUserId);var localConfig=this.getLocalConfig(remoteSdp,remoteUserId,userPreferences);connection.peers[remoteUserId]=new PeerInitiator(localConfig),this.checkIfNextPossibleInitiator(remoteUserId)},this.renegotiatePeer=function(remoteUserId,userPreferences,remoteSdp){if(!connection.peers[remoteUserId])return void(connection.enableLogs&&console.error("Peer ("+remoteUserId+") does not exist. Renegotiation skipped."));userPreferences||(userPreferences={}),userPreferences.renegotiatingPeer=!0,userPreferences.peerRef=connection.peers[remoteUserId].peer,userPreferences.channels=connection.peers[remoteUserId].channels;var localConfig=this.getLocalConfig(remoteSdp,remoteUserId,userPreferences);connection.peers[remoteUserId]=new PeerInitiator(localConfig),this.checkIfNextPossibleInitiator(remoteUserId)},this.replaceTrack=function(track,remoteUserId,isVideoTrack){if(!connection.peers[remoteUserId])throw"This peer ("+remoteUserId+") does not exist.";var peer=connection.peers[remoteUserId].peer;return peer.getSenders&&"function"==typeof peer.getSenders&&peer.getSenders().length?void peer.getSenders().forEach(function(rtpSender){isVideoTrack&&rtpSender.track instanceof VideoStreamTrack&&(connection.peers[remoteUserId].peer.lastVideoTrack=rtpSender.track,rtpSender.replaceTrack(track)),!isVideoTrack&&rtpSender.track instanceof AudioStreamTrack&&(connection.peers[remoteUserId].peer.lastAudioTrack=rtpSender.track,rtpSender.replaceTrack(track))}):(console.warn("RTPSender.replaceTrack is NOT supported."),void this.renegotiatePeer(remoteUserId))},this.onNegotiationNeeded=function(message,remoteUserId){},this.addNegotiatedMessage=function(message,remoteUserId){if(message.type&&message.sdp)return"answer"==message.type&&connection.peers[remoteUserId]&&connection.peers[remoteUserId].addRemoteSdp(message),"offer"==message.type&&(message.renegotiatingPeer?this.renegotiatePeer(remoteUserId,null,message):this.createAnsweringPeer(message,remoteUserId)),void(connection.enableLogs&&console.log("Remote peer's sdp:",message.sdp));if(message.candidate)return connection.peers[remoteUserId]&&connection.peers[remoteUserId].addRemoteCandidate(message),void(connection.enableLogs&&console.log("Remote peer's candidate pairs:",message.candidate));if(message.enableMedia){connection.session=message.userPreferences.session||connection.session,connection.session.oneway&&connection.attachStreams.length&&(connection.attachStreams=[]),message.userPreferences.isDataOnly&&connection.attachStreams.length&&(connection.attachStreams.length=[]);var streamsToShare={};connection.attachStreams.forEach(function(stream){streamsToShare[stream.streamid]={isAudio:!!stream.isAudio,isVideo:!!stream.isVideo,isScreen:!!stream.isScreen}}),message.userPreferences.streamsToShare=streamsToShare,self.onNegotiationNeeded({readyForOffer:!0,userPreferences:message.userPreferences},remoteUserId)}message.readyForOffer&&connection.onReadyForOffer(remoteUserId,message.userPreferences)},this.connectNewParticipantWithAllBroadcasters=function(newParticipantId,userPreferences,broadcastersList){if(!connection.socket.isIO&&(broadcastersList=(broadcastersList||"").split("|-,-|"),broadcastersList.length)){var firstBroadcaster,remainingBroadcasters=[];broadcastersList.forEach(function(list){list=(list||"").replace(/ /g,""),list.length&&(firstBroadcaster?remainingBroadcasters.push(list):firstBroadcaster=list)}),firstBroadcaster&&(self.onNegotiationNeeded({newParticipant:newParticipantId,userPreferences:userPreferences||!1},firstBroadcaster),remainingBroadcasters.length&&setTimeout(function(){self.connectNewParticipantWithAllBroadcasters(newParticipantId,userPreferences,remainingBroadcasters.join("|-,-|"))},3e3))}},this.onGettingRemoteMedia=function(stream,remoteUserId){},this.onRemovingRemoteMedia=function(stream,remoteUserId){},this.onGettingLocalMedia=function(localStream){},this.onLocalMediaError=function(error,constraints){connection.onMediaError(error,constraints)},this.shareFile=function(file,remoteUserId){if(!connection.enableFileSharing)throw'"connection.enableFileSharing" is false.';initFileBufferReader(),connection.fbr.readAsArrayBuffer(file,function(uuid){var arrayOfUsers=connection.getAllParticipants();remoteUserId&&(arrayOfUsers=[remoteUserId]),arrayOfUsers.forEach(function(participant){connection.fbr.getNextChunk(uuid,function(nextChunk){connection.peers[participant].channels.forEach(function(channel){channel.send(nextChunk)})},participant)})},{userid:connection.userid,chunkSize:"Firefox"===DetectRTC.browser.name?15e3:connection.chunkSize||0})};var textReceiver=new TextReceiver(connection);this.onDataChannelMessage=function(message,remoteUserId){textReceiver.receive(JSON.parse(message),remoteUserId,connection.peers[remoteUserId]?connection.peers[remoteUserId].extra:{})},this.onDataChannelClosed=function(event,remoteUserId){event.userid=remoteUserId,event.extra=connection.peers[remoteUserId]?connection.peers[remoteUserId].extra:{},connection.onclose(event)},this.onDataChannelError=function(error,remoteUserId){error.userid=remoteUserId,event.extra=connection.peers[remoteUserId]?connection.peers[remoteUserId].extra:{},connection.onerror(error)},this.onDataChannelOpened=function(channel,remoteUserId){return connection.peers[remoteUserId].channels.length?void(connection.peers[remoteUserId].channels=[channel]):(connection.peers[remoteUserId].channels.push(channel),void connection.onopen({userid:remoteUserId,extra:connection.peers[remoteUserId]?connection.peers[remoteUserId].extra:{},channel:channel}))},this.onPeerStateChanged=function(state){connection.onPeerStateChanged(state)},this.onNegotiationStarted=function(remoteUserId,states){},this.onNegotiationCompleted=function(remoteUserId,states){},this.getRemoteStreams=function(remoteUserId){return remoteUserId=remoteUserId||connection.peers.getAllParticipants()[0],connection.peers[remoteUserId]?connection.peers[remoteUserId].streams:[]},this.checkIfNextPossibleInitiator=function(remoteUserId){connection.sessionid!==remoteUserId&&(connection.autoCloseEntireSession||connection.isInitiator&&connection.getAllParticipants().length>1||connection.socket.emit(connection.socketMessageEvent,{remoteUserId:remoteUserId,message:"next-possible-initiator",sender:connection.userid}))}}function fireEvent(obj,eventName,args){if("undefined"!=typeof CustomEvent){var eventDetail={arguments:args,__exposedProps__:args},event=new CustomEvent(eventName,eventDetail);obj.dispatchEvent(event)}}function setHarkEvents(connection,streamEvent){if(streamEvent.stream&&streamEvent.stream.getAudioTracks&&streamEvent.stream.getAudioTracks().length){if(!connection||!streamEvent)throw"Both arguments are required.";if(connection.onspeaking&&connection.onsilence){if("undefined"==typeof hark)throw"hark.js not found.";hark(streamEvent.stream,{onspeaking:function(){connection.onspeaking(streamEvent)},onsilence:function(){connection.onsilence(streamEvent)},onvolumechange:function(volume,threshold){connection.onvolumechange&&connection.onvolumechange(merge({volume:volume,threshold:threshold},streamEvent))}})}}}function setMuteHandlers(connection,streamEvent){streamEvent.stream&&streamEvent.stream&&streamEvent.stream.addEventListener&&(streamEvent.stream.addEventListener("mute",function(event){event=connection.streamEvents[streamEvent.streamid],event.session={audio:"audio"===event.muteType,video:"video"===event.muteType},connection.onmute(event)},!1),streamEvent.stream.addEventListener("unmute",function(event){event=connection.streamEvents[streamEvent.streamid],event.session={audio:"audio"===event.unmuteType,video:"video"===event.unmuteType},connection.onunmute(event)},!1))}function getRandomString(){if(window.crypto&&window.crypto.getRandomValues&&navigator.userAgent.indexOf("Safari")===-1){for(var a=window.crypto.getRandomValues(new Uint32Array(3)),token="",i=0,l=a.length;i=60,"Firefox"===DetectRTC.browser.name&&DetectRTC.browser.version>=54&&(oldBrowser=!1),oldBrowser&&(sdpConstraints={mandatory:sdpConstraints,optional:[{VoiceActivityDetection:!1}]}),sdpConstraints}function PeerInitiator(config){function oldAddRemoteSdp(remoteSdp,cb){cb=cb||function(){},"Safari"!==DetectRTC.browser.name&&(remoteSdp.sdp=connection.processSdp(remoteSdp.sdp)),peer.setRemoteDescription(new RTCSessionDescription(remoteSdp),cb,function(error){connection.enableLogs&&console.error("setRemoteDescription failed","\n",error,"\n",remoteSdp.sdp),cb()})}function setChannelEvents(channel){channel.binaryType="arraybuffer",channel.onmessage=function(event){config.onDataChannelMessage(event.data)},channel.onopen=function(){config.onDataChannelOpened(channel)},channel.onerror=function(error){config.onDataChannelError(error)},channel.onclose=function(event){config.onDataChannelClosed(event)},channel.internalSend=channel.send,channel.send=function(data){"open"===channel.readyState&&channel.internalSend(data)},peer.channel=channel}function oldCreateOfferOrAnswer(_method){peer[_method](function(localSdp){"Safari"!==DetectRTC.browser.name&&(localSdp.sdp=connection.processSdp(localSdp.sdp)),peer.setLocalDescription(localSdp,function(){connection.trickleIce&&(config.onLocalSdp({type:localSdp.type,sdp:localSdp.sdp,remotePeerSdpConstraints:config.remotePeerSdpConstraints||!1,renegotiatingPeer:!!config.renegotiatingPeer||!1,connectionDescription:self.connectionDescription,dontGetRemoteStream:!!config.dontGetRemoteStream,extra:connection?connection.extra:{},streamsToShare:streamsToShare}),connection.onSettingLocalDescription(self))},function(error){connection.enableLogs&&console.error("setLocalDescription-error",error)})},function(error){connection.enableLogs&&console.error("sdp-"+_method+"-error",error)},defaults.sdpConstraints)}function createOfferOrAnswer(_method){return window.enableAdapter?void peer[_method](defaults.sdpConstraints).then(function(localSdp){"Safari"!==DetectRTC.browser.name&&(localSdp.sdp=connection.processSdp(localSdp.sdp)),peer.setLocalDescription(localSdp).then(function(){connection.trickleIce&&(config.onLocalSdp({type:localSdp.type,sdp:localSdp.sdp,remotePeerSdpConstraints:config.remotePeerSdpConstraints||!1,renegotiatingPeer:!!config.renegotiatingPeer||!1,connectionDescription:self.connectionDescription,dontGetRemoteStream:!!config.dontGetRemoteStream,extra:connection?connection.extra:{},streamsToShare:streamsToShare}),connection.onSettingLocalDescription(self))},function(error){connection.enableLogs&&console.error("setLocalDescription error",error)})},function(error){connection.enableLogs&&console.error("sdp-error",error)}):oldCreateOfferOrAnswer(_method)}if("undefined"!=typeof window.RTCPeerConnection?RTCPeerConnection=window.RTCPeerConnection:"undefined"!=typeof mozRTCPeerConnection?RTCPeerConnection=mozRTCPeerConnection:"undefined"!=typeof webkitRTCPeerConnection&&(RTCPeerConnection=webkitRTCPeerConnection),RTCSessionDescription=window.RTCSessionDescription||window.mozRTCSessionDescription,RTCIceCandidate=window.RTCIceCandidate||window.mozRTCIceCandidate,MediaStreamTrack=window.MediaStreamTrack,!RTCPeerConnection)throw"WebRTC 1.0 (RTCPeerConnection) API are NOT available in this browser.";var connection=config.rtcMultiConnection;this.extra=config.remoteSdp?config.remoteSdp.extra:connection.extra,this.userid=config.userid,this.streams=[],this.channels=config.channels||[],this.connectionDescription=config.connectionDescription,this.addStream=function(session){connection.addStream(session,self.userid)},this.removeStream=function(streamid){connection.removeStream(streamid,self.userid); +};var self=this;config.remoteSdp&&(this.connectionDescription=config.remoteSdp.connectionDescription);var allRemoteStreams={};defaults.sdpConstraints=setSdpConstraints({OfferToReceiveAudio:!0,OfferToReceiveVideo:!0});var peer,renegotiatingPeer=!!config.renegotiatingPeer;config.remoteSdp&&(renegotiatingPeer=!!config.remoteSdp.renegotiatingPeer);var localStreams=[];if(connection.attachStreams.forEach(function(stream){stream&&localStreams.push(stream)}),renegotiatingPeer)peer=config.peerRef;else{var iceTransports="all";(connection.candidates.turn||connection.candidates.relay)&&(connection.candidates.stun||connection.candidates.reflexive||connection.candidates.host||(iceTransports="relay"));try{var params={};"Chrome"!==DetectRTC.browser.name&&(params.iceServers=connection.iceServers),"Chrome"===DetectRTC.browser.name&&(params={iceServers:connection.iceServers,iceTransportPolicy:connection.iceTransportPolicy||iceTransports}),connection.iceServers.length||(params=null,connection.optionalArgument=null),peer=new RTCPeerConnection(params,connection.optionalArgument)}catch(e){try{var params={iceServers:connection.iceServers};peer=new RTCPeerConnection(params)}catch(e){peer=new RTCPeerConnection}}}!peer.getRemoteStreams&&peer.getReceivers&&(peer.getRemoteStreams=function(){var stream=new MediaStream;return peer.getReceivers().forEach(function(receiver){stream.addTrack(receiver.track)}),[stream]}),!peer.getLocalStreams&&peer.getSenders&&(peer.getLocalStreams=function(){var stream=new MediaStream;return peer.getSenders().forEach(function(sender){stream.addTrack(sender.track)}),[stream]}),peer.onicecandidate=function(event){if(event.candidate)connection.trickleIce&&config.onLocalCandidate({candidate:event.candidate.candidate,sdpMid:event.candidate.sdpMid,sdpMLineIndex:event.candidate.sdpMLineIndex});else if(!connection.trickleIce){var localSdp=peer.localDescription;config.onLocalSdp({type:localSdp.type,sdp:localSdp.sdp,remotePeerSdpConstraints:config.remotePeerSdpConstraints||!1,renegotiatingPeer:!!config.renegotiatingPeer||!1,connectionDescription:self.connectionDescription,dontGetRemoteStream:!!config.dontGetRemoteStream,extra:connection?connection.extra:{},streamsToShare:streamsToShare})}},localStreams.forEach(function(localStream){if(!(config.remoteSdp&&config.remoteSdp.remotePeerSdpConstraints&&config.remoteSdp.remotePeerSdpConstraints.dontGetRemoteStream)&&!config.dontAttachLocalStream&&(localStream=connection.beforeAddingStream(localStream,self)))if(peer.getLocalStreams().forEach(function(stream){localStream&&stream.id==localStream.id&&(localStream=null)}),localStream&&"function"==typeof peer.addTrack)localStream.getTracks().forEach(function(track){try{peer.addTrack(track,localStream)}catch(e){}});else if(localStream&&"function"==typeof peer.addStream)peer.addStream(localStream);else try{peer.addStream(localStream)}catch(e){localStream&&localStream.getTracks().forEach(function(track){try{peer.addTrack(track,localStream)}catch(e){}})}}),peer.oniceconnectionstatechange=peer.onsignalingstatechange=function(){var extra=self.extra;connection.peers[self.userid]&&(extra=connection.peers[self.userid].extra||extra),peer&&(config.onPeerStateChanged({iceConnectionState:peer.iceConnectionState,iceGatheringState:peer.iceGatheringState,signalingState:peer.signalingState,extra:extra,userid:self.userid}),peer&&peer.iceConnectionState&&peer.iceConnectionState.search(/closed|failed/gi)!==-1&&self.streams instanceof Array&&self.streams.forEach(function(stream){var streamEvent=connection.streamEvents[stream.id]||{streamid:stream.id,stream:stream,type:"remote"};connection.onstreamended(streamEvent)}))};var sdpConstraints={OfferToReceiveAudio:!!localStreams.length,OfferToReceiveVideo:!!localStreams.length};config.localPeerSdpConstraints&&(sdpConstraints=config.localPeerSdpConstraints),defaults.sdpConstraints=setSdpConstraints(sdpConstraints);var dontDuplicate={},incomingStreamEvent="track";window.enableAdapter||(incomingStreamEvent="addstream"),peer.addEventListener(incomingStreamEvent,function(event){if(event&&("track"===incomingStreamEvent&&(event.stream=event.streams[event.streams.length-1]),!dontDuplicate[event.stream.id]||"Safari"===DetectRTC.browser.name)){dontDuplicate[event.stream.id]=event.stream.id;var streamsToShare={};config.remoteSdp&&config.remoteSdp.streamsToShare?streamsToShare=config.remoteSdp.streamsToShare:config.streamsToShare&&(streamsToShare=config.streamsToShare);var streamToShare=streamsToShare[event.stream.id];streamToShare?(event.stream.isAudio=streamToShare.isAudio,event.stream.isVideo=streamToShare.isVideo,event.stream.isScreen=streamToShare.isScreen):(event.stream.isVideo=!!event.stream.getVideoTracks().length,event.stream.isAudio=!event.stream.isVideo,event.stream.isScreen=!1),event.stream.streamid=event.stream.id,"Firefox"!=DetectRTC.browser.name&&event.stream.stop||(event.stream.stop=function(){var streamEndedEvent="ended";"oninactive"in event.stream&&(streamEndedEvent="inactive"),fireEvent(event.stream,streamEndedEvent)}),allRemoteStreams[event.stream.id]=event.stream,config.onRemoteStream(event.stream)}},!1),peer.onremovestream=function(event){event.stream.streamid=event.stream.id,allRemoteStreams[event.stream.id]&&delete allRemoteStreams[event.stream.id],config.onRemoteStreamRemoved(event.stream)},this.addRemoteCandidate=function(remoteCandidate){peer.addIceCandidate(new RTCIceCandidate(remoteCandidate))},this.addRemoteSdp=function(remoteSdp,cb){return cb=cb||function(){},window.enableAdapter?("Safari"!==DetectRTC.browser.name&&(remoteSdp.sdp=connection.processSdp(remoteSdp.sdp)),void peer.setRemoteDescription(new RTCSessionDescription(remoteSdp)).then(cb,function(error){connection.enableLogs&&console.error("setRemoteDescription failed","\n",error,"\n",remoteSdp.sdp),cb()})):oldAddRemoteSdp(remoteSdp,cb)};var isOfferer=!0;config.remoteSdp&&(isOfferer=!1),this.createDataChannel=function(){var channel=peer.createDataChannel("sctp",{});setChannelEvents(channel)},connection.session.data!==!0||renegotiatingPeer||(isOfferer?this.createDataChannel():peer.ondatachannel=function(event){var channel=event.channel;setChannelEvents(channel)}),this.enableDisableVideoEncoding=function(enable){var rtcp;if(peer.getSenders().forEach(function(sender){rtcp||"video"!==sender.track.kind||(rtcp=sender)}),rtcp&&rtcp.getParameters){var parameters=rtcp.getParameters();parameters.encodings[1]&&(parameters.encodings[1].active=!!enable),parameters.encodings[2]&&(parameters.encodings[2].active=!!enable),rtcp.setParameters(parameters)}},config.remoteSdp&&(config.remoteSdp.remotePeerSdpConstraints&&(sdpConstraints=config.remoteSdp.remotePeerSdpConstraints),defaults.sdpConstraints=setSdpConstraints(sdpConstraints),this.addRemoteSdp(config.remoteSdp,function(){createOfferOrAnswer("createAnswer")})),"two-way"!=connection.session.audio&&"two-way"!=connection.session.video&&"two-way"!=connection.session.screen||(defaults.sdpConstraints=setSdpConstraints({OfferToReceiveAudio:"two-way"==connection.session.audio||config.remoteSdp&&config.remoteSdp.remotePeerSdpConstraints&&config.remoteSdp.remotePeerSdpConstraints.OfferToReceiveAudio,OfferToReceiveVideo:"two-way"==connection.session.video||"two-way"==connection.session.screen||config.remoteSdp&&config.remoteSdp.remotePeerSdpConstraints&&config.remoteSdp.remotePeerSdpConstraints.OfferToReceiveAudio}));var streamsToShare={};peer.getLocalStreams().forEach(function(stream){streamsToShare[stream.streamid]={isAudio:!!stream.isAudio,isVideo:!!stream.isVideo,isScreen:!!stream.isScreen}}),isOfferer&&createOfferOrAnswer("createOffer"),peer.nativeClose=peer.close,peer.close=function(){if(peer){try{peer.nativeClose!==peer.close&&peer.nativeClose()}catch(e){}peer=null,self.peer=null}},this.peer=peer}function setStreamType(constraints,stream){constraints.mandatory&&constraints.mandatory.chromeMediaSource?stream.isScreen=!0:constraints.mozMediaSource||constraints.mediaSource?stream.isScreen=!0:constraints.video?stream.isVideo=!0:constraints.audio&&(stream.isAudio=!0)}function getUserMediaHandler(options){function streaming(stream,returnBack){setStreamType(options.localMediaConstraints,stream);var streamEndedEvent="ended";"oninactive"in stream&&(streamEndedEvent="inactive"),stream.addEventListener(streamEndedEvent,function(){delete currentUserMediaRequest.streams[idInstance],currentUserMediaRequest.mutex=!1,currentUserMediaRequest.queueRequests.indexOf(options)&&(delete currentUserMediaRequest.queueRequests[currentUserMediaRequest.queueRequests.indexOf(options)],currentUserMediaRequest.queueRequests=removeNullEntries(currentUserMediaRequest.queueRequests))},!1),currentUserMediaRequest.streams[idInstance]={stream:stream},currentUserMediaRequest.mutex=!1,currentUserMediaRequest.queueRequests.length&&getUserMediaHandler(currentUserMediaRequest.queueRequests.shift()),options.onGettingLocalMedia(stream,returnBack)}if(currentUserMediaRequest.mutex===!0)return void currentUserMediaRequest.queueRequests.push(options);currentUserMediaRequest.mutex=!0;var idInstance=JSON.stringify(options.localMediaConstraints);if(currentUserMediaRequest.streams[idInstance])streaming(currentUserMediaRequest.streams[idInstance].stream,!0);else{var isBlackBerry=!!/BB10|BlackBerry/i.test(navigator.userAgent||"");if(isBlackBerry||"undefined"==typeof navigator.mediaDevices||"function"!=typeof navigator.mediaDevices.getUserMedia)return navigator.getUserMedia=navigator.getUserMedia||navigator.webkitGetUserMedia||navigator.mozGetUserMedia,void navigator.getUserMedia(options.localMediaConstraints,function(stream){stream.streamid=stream.streamid||stream.id||getRandomString(),stream.idInstance=idInstance,streaming(stream)},function(error){options.onLocalMediaError(error,options.localMediaConstraints)});if("undefined"==typeof navigator.mediaDevices){navigator.getUserMedia=navigator.getUserMedia||navigator.webkitGetUserMedia||navigator.mozGetUserMedia;var getUserMediaStream,getUserMediaError,getUserMediaSuccess=function(){},getUserMediaFailure=function(){};navigator.mediaDevices={getUserMedia:function(hints){return navigator.getUserMedia(hints,function(getUserMediaSuccess){getUserMediaSuccess(stream),getUserMediaStream=stream},function(error){getUserMediaFailure(error),getUserMediaError=error}),{then:function(successCB){return getUserMediaStream?void successCB(getUserMediaStream):(getUserMediaSuccess=successCB,{then:function(failureCB){return getUserMediaError?void failureCB(getUserMediaError):void(getUserMediaFailure=failureCB)}})}}}}}navigator.mediaDevices.getUserMedia(options.localMediaConstraints).then(function(stream){stream.streamid=stream.streamid||stream.id||getRandomString(),stream.idInstance=idInstance,streaming(stream)})["catch"](function(error){options.onLocalMediaError(error,options.localMediaConstraints)})}}function onMessageCallback(data){if("PermissionDeniedError"==data){if(chromeMediaSource="PermissionDeniedError",screenCallback)return screenCallback("PermissionDeniedError");throw new Error("PermissionDeniedError: User rejected to share his screen.")}"rtcmulticonnection-extension-loaded"==data&&(chromeMediaSource="desktop"),data.sourceId&&screenCallback&&(sourceId=data.sourceId,screenCallback(sourceId))}function isChromeExtensionAvailable(callback){if(callback){if("Firefox"===DetectRTC.browser.name)return isFirefoxExtensionAvailable(callback);if(window.IsAndroidChrome)return chromeMediaSource="screen",void callback(!0);if("desktop"==chromeMediaSource)return void callback(!0);window.postMessage("are-you-there","*"),setTimeout(function(){callback("screen"==chromeMediaSource?!1:!0)},2e3)}}function isFirefoxExtensionAvailable(callback){function messageCallback(event){var addonMessage=event.data;addonMessage&&"undefined"!=typeof addonMessage.isScreenCapturingEnabled&&(isFirefoxAddonResponded=!0,callback(addonMessage.isScreenCapturingEnabled===!0?!0:!1),window.removeEventListener("message",messageCallback,!1))}if(callback){if("Firefox"!==DetectRTC.browser.name)return isChromeExtensionAvailable(callback);var isFirefoxAddonResponded=!1;window.addEventListener("message",messageCallback,!1),window.postMessage({checkIfScreenCapturingEnabled:!0,domains:[document.domain]},"*"),setTimeout(function(){isFirefoxAddonResponded||callback(!0)},2e3)}}function getSourceId(callback,audioPlusTab){if(!callback)throw'"callback" parameter is mandatory.';return sourceId=null,screenCallback=callback,audioPlusTab?void window.postMessage("audio-plus-tab","*"):void window.postMessage("get-sourceId","*")}function getChromeExtensionStatus(extensionid,callback){if(window.IsAndroidChrome)return chromeMediaSource="screen",void callback("installed-enabled");if(2!=arguments.length&&(callback=extensionid,extensionid=window.RMCExtensionID||"ajhifddimkapgcifgcodmmfdlknahffk"),"Firefox"===DetectRTC.browser.name)return callback("not-chrome");sourceId=null,chromeMediaSource="screen";var image=document.createElement("img");image.src="chrome-extension://"+extensionid+"/icon.png",image.onload=function(){window.postMessage("are-you-there","*"),setTimeout(function(){callback("screen"==chromeMediaSource?"installed-disabled":"installed-enabled")},2e3)},image.onerror=function(){callback("not-installed")}}function getAspectRatio(w,h){function gcd(a,b){return 0==b?a:gcd(b,a%b)}var r=gcd(w,h);return w/r/(h/r)}function getScreenConstraints(callback,audioPlusTab){var firefoxScreenConstraints={mozMediaSource:"window",mediaSource:"window"};return"Firefox"===DetectRTC.browser.name?callback(null,firefoxScreenConstraints):(sourceId=null,void isChromeExtensionAvailable(function(isAvailable){var screen_constraints={mandatory:{chromeMediaSource:chromeMediaSource,maxWidth:screen.width,maxHeight:screen.height,minWidth:screen.width,minHeight:screen.height,minAspectRatio:getAspectRatio(screen.width,screen.height),maxAspectRatio:getAspectRatio(screen.width,screen.height),minFrameRate:64,maxFrameRate:128},optional:[]};return window.IsAndroidChrome?void callback(null,screen_constraints):"desktop"!=chromeMediaSource||sourceId?("desktop"==chromeMediaSource&&(screen_constraints.mandatory.chromeMediaSourceId=sourceId),sourceId=null,screenCallback=null,void callback(null,screen_constraints)):void getSourceId(function(){screen_constraints.mandatory.chromeMediaSourceId=sourceId,callback("PermissionDeniedError"==sourceId?sourceId:null,screen_constraints),sourceId=null},audioPlusTab)}))}function TextReceiver(connection){function receive(data,userid,extra){var uuid=data.uuid;if(content[uuid]||(content[uuid]=[]),content[uuid].push(data.message),data.last){var message=content[uuid].join("");data.isobject&&(message=JSON.parse(message));var receivingTime=(new Date).getTime(),latency=receivingTime-data.sendingTime,e={data:message,userid:userid,extra:extra,latency:latency};connection.autoTranslateText?(e.original=e.data,connection.Translator.TranslateText(e.data,function(translatedText){e.data=translatedText,connection.onmessage(e)})):connection.onmessage(e),delete content[uuid]}}var content={};return{receive:receive}}!function(){function getBrowserInfo(){var nameOffset,verOffset,ix,nAgt=(navigator.appVersion,navigator.userAgent),browserName=navigator.appName,fullVersion=""+parseFloat(navigator.appVersion),majorVersion=parseInt(navigator.appVersion,10);if(isOpera){browserName="Opera";try{fullVersion=navigator.userAgent.split("OPR/")[1].split(" ")[0],majorVersion=fullVersion.split(".")[0]}catch(e){fullVersion="0.0.0.0",majorVersion=0}}else isIE?(verOffset=nAgt.indexOf("rv:"),verOffset>0?fullVersion=nAgt.substring(verOffset+3):(verOffset=nAgt.indexOf("MSIE"),fullVersion=nAgt.substring(verOffset+5)),browserName="IE"):isChrome?(verOffset=nAgt.indexOf("Chrome"),browserName="Chrome",fullVersion=nAgt.substring(verOffset+7)):isSafari?(verOffset=nAgt.indexOf("Safari"),browserName="Safari",fullVersion=nAgt.substring(verOffset+7),(verOffset=nAgt.indexOf("Version"))!==-1&&(fullVersion=nAgt.substring(verOffset+8)),navigator.userAgent.indexOf("Version/")!==-1&&(fullVersion=navigator.userAgent.split("Version/")[1].split(" ")[0])):isFirefox?(verOffset=nAgt.indexOf("Firefox"),browserName="Firefox",fullVersion=nAgt.substring(verOffset+8)):(nameOffset=nAgt.lastIndexOf(" ")+1)<(verOffset=nAgt.lastIndexOf("/"))&&(browserName=nAgt.substring(nameOffset,verOffset),fullVersion=nAgt.substring(verOffset+1),browserName.toLowerCase()===browserName.toUpperCase()&&(browserName=navigator.appName));return isEdge&&(browserName="Edge",fullVersion=navigator.userAgent.split("Edge/")[1]),(ix=fullVersion.search(/[; \)]/))!==-1&&(fullVersion=fullVersion.substring(0,ix)),majorVersion=parseInt(""+fullVersion,10),isNaN(majorVersion)&&(fullVersion=""+parseFloat(navigator.appVersion),majorVersion=parseInt(navigator.appVersion,10)),{fullVersion:fullVersion,version:majorVersion,name:browserName,isPrivateBrowsing:!1}}function retry(isDone,next){var currentTrial=0,maxRetry=50,isTimeout=!1,id=window.setInterval(function(){isDone()&&(window.clearInterval(id),next(isTimeout)),currentTrial++>maxRetry&&(window.clearInterval(id),isTimeout=!0,next(isTimeout))},10)}function isIE10OrLater(userAgent){var ua=userAgent.toLowerCase();if(0===ua.indexOf("msie")&&0===ua.indexOf("trident"))return!1;var match=/(?:msie|rv:)\s?([\d\.]+)/.exec(ua);return!!(match&&parseInt(match[1],10)>=10)}function detectPrivateMode(callback){var isPrivate;try{if(window.webkitRequestFileSystem)window.webkitRequestFileSystem(window.TEMPORARY,1,function(){isPrivate=!1},function(e){isPrivate=!0});else if(window.indexedDB&&/Firefox/.test(window.navigator.userAgent)){var db;try{db=window.indexedDB.open("test"),db.onerror=function(){return!0}}catch(e){isPrivate=!0}"undefined"==typeof isPrivate&&retry(function(){return"done"===db.readyState},function(isTimeout){isTimeout||(isPrivate=!db.result)})}else if(isIE10OrLater(window.navigator.userAgent)){isPrivate=!1;try{window.indexedDB||(isPrivate=!0)}catch(e){isPrivate=!0}}else if(window.localStorage&&/Safari/.test(window.navigator.userAgent)){try{window.localStorage.setItem("test",1)}catch(e){isPrivate=!0}"undefined"==typeof isPrivate&&(isPrivate=!1,window.localStorage.removeItem("test"))}}catch(e){isPrivate=!1}retry(function(){return"undefined"!=typeof isPrivate},function(isTimeout){callback(isPrivate)})}function detectDesktopOS(){for(var cs,unknown="-",nVer=navigator.appVersion,nAgt=navigator.userAgent,os=unknown,clientStrings=[{s:"Windows 10",r:/(Windows 10.0|Windows NT 10.0)/},{s:"Windows 8.1",r:/(Windows 8.1|Windows NT 6.3)/},{s:"Windows 8",r:/(Windows 8|Windows NT 6.2)/},{s:"Windows 7",r:/(Windows 7|Windows NT 6.1)/},{s:"Windows Vista",r:/Windows NT 6.0/},{s:"Windows Server 2003",r:/Windows NT 5.2/},{s:"Windows XP",r:/(Windows NT 5.1|Windows XP)/},{s:"Windows 2000",r:/(Windows NT 5.0|Windows 2000)/},{s:"Windows ME",r:/(Win 9x 4.90|Windows ME)/},{s:"Windows 98",r:/(Windows 98|Win98)/},{s:"Windows 95",r:/(Windows 95|Win95|Windows_95)/},{s:"Windows NT 4.0",r:/(Windows NT 4.0|WinNT4.0|WinNT|Windows NT)/},{s:"Windows CE",r:/Windows CE/},{s:"Windows 3.11",r:/Win16/},{s:"Android",r:/Android/},{s:"Open BSD",r:/OpenBSD/},{s:"Sun OS",r:/SunOS/},{s:"Linux",r:/(Linux|X11)/},{s:"iOS",r:/(iPhone|iPad|iPod)/},{s:"Mac OS X",r:/Mac OS X/},{s:"Mac OS",r:/(MacPPC|MacIntel|Mac_PowerPC|Macintosh)/},{s:"QNX",r:/QNX/},{s:"UNIX",r:/UNIX/},{s:"BeOS",r:/BeOS/},{s:"OS/2",r:/OS\/2/},{s:"Search Bot",r:/(nuhk|Googlebot|Yammybot|Openbot|Slurp|MSNBot|Ask Jeeves\/Teoma|ia_archiver)/}],i=0;cs=clientStrings[i];i++)if(cs.r.test(nAgt)){os=cs.s;break}var osVersion=unknown;switch(/Windows/.test(os)&&(/Windows (.*)/.test(os)&&(osVersion=/Windows (.*)/.exec(os)[1]),os="Windows"),os){case"Mac OS X":/Mac OS X (10[\.\_\d]+)/.test(nAgt)&&(osVersion=/Mac OS X (10[\.\_\d]+)/.exec(nAgt)[1]);break;case"Android":/Android ([\.\_\d]+)/.test(nAgt)&&(osVersion=/Android ([\.\_\d]+)/.exec(nAgt)[1]);break;case"iOS":/OS (\d+)_(\d+)_?(\d+)?/.test(nAgt)&&(osVersion=/OS (\d+)_(\d+)_?(\d+)?/.exec(nVer),osVersion=osVersion[1]+"."+osVersion[2]+"."+(0|osVersion[3]))}return{osName:os,osVersion:osVersion}}function getAndroidVersion(ua){ua=(ua||navigator.userAgent).toLowerCase();var match=ua.match(/android\s([0-9\.]*)/);return!!match&&match[1]}function DetectLocalIPAddress(callback,stream){DetectRTC.isWebRTCSupported&&getIPs(function(ip){callback(ip.match(/^(192\.168\.|169\.254\.|10\.|172\.(1[6-9]|2\d|3[01]))/)?"Local: "+ip:"Public: "+ip)},stream)}function getIPs(callback,stream){function handleCandidate(candidate){var ipRegex=/([0-9]{1,3}(\.[0-9]{1,3}){3})/,match=ipRegex.exec(candidate);if(match){var ipAddress=match[1];void 0===ipDuplicates[ipAddress]&&callback(ipAddress),ipDuplicates[ipAddress]=!0}}function afterCreateOffer(){var lines=pc.localDescription.sdp.split("\n");lines.forEach(function(line){0===line.indexOf("a=candidate:")&&handleCandidate(line)})}if("undefined"!=typeof document&&"function"==typeof document.getElementById){var ipDuplicates={},RTCPeerConnection=window.RTCPeerConnection||window.mozRTCPeerConnection||window.webkitRTCPeerConnection;if(!RTCPeerConnection){var iframe=document.getElementById("iframe");if(!iframe)return;var win=iframe.contentWindow;RTCPeerConnection=win.RTCPeerConnection||win.mozRTCPeerConnection||win.webkitRTCPeerConnection}if(RTCPeerConnection){var peerConfig=null;"Chrome"===DetectRTC.browser&&DetectRTC.browser.version<58&&(peerConfig={optional:[{RtpDataChannels:!0}]});var servers={iceServers:[{urls:"stun:stun.l.google.com:19302"}]},pc=new RTCPeerConnection(servers,peerConfig);if(stream&&(pc.addStream?pc.addStream(stream):pc.addTrack&&stream.getTracks()[0]&&pc.addTrack(stream.getTracks()[0],stream)),pc.onicecandidate=function(ice){ice.candidate&&handleCandidate(ice.candidate.candidate)},!stream)try{pc.createDataChannel("sctp",{})}catch(e){}DetectRTC.isPromisesSupported?pc.createOffer().then(function(result){pc.setLocalDescription(result).then(afterCreateOffer)}):pc.createOffer(function(result){pc.setLocalDescription(result,afterCreateOffer,function(){})},function(){})}}}function checkDeviceSupport(callback){if(!canEnumerate)return void(callback&&callback());if(!navigator.enumerateDevices&&window.MediaStreamTrack&&window.MediaStreamTrack.getSources&&(navigator.enumerateDevices=window.MediaStreamTrack.getSources.bind(window.MediaStreamTrack)),!navigator.enumerateDevices&&navigator.enumerateDevices&&(navigator.enumerateDevices=navigator.enumerateDevices.bind(navigator)),!navigator.enumerateDevices)return void(callback&&callback());MediaDevices=[],audioInputDevices=[],audioOutputDevices=[],videoInputDevices=[],hasMicrophone=!1,hasSpeakers=!1,hasWebcam=!1,isWebsiteHasMicrophonePermissions=!1,isWebsiteHasWebcamPermissions=!1;var alreadyUsedDevices={};navigator.enumerateDevices(function(devices){devices.forEach(function(_device){var device={};for(var d in _device)try{"function"!=typeof _device[d]&&(device[d]=_device[d])}catch(e){}alreadyUsedDevices[device.deviceId+device.label+device.kind]||("audio"===device.kind&&(device.kind="audioinput"),"video"===device.kind&&(device.kind="videoinput"),device.deviceId||(device.deviceId=device.id),device.id||(device.id=device.deviceId),device.label?("videoinput"!==device.kind||isWebsiteHasWebcamPermissions||(isWebsiteHasWebcamPermissions=!0),"audioinput"!==device.kind||isWebsiteHasMicrophonePermissions||(isWebsiteHasMicrophonePermissions=!0)):(device.isCustomLabel=!0,"videoinput"===device.kind?device.label="Camera "+(videoInputDevices.length+1):"audioinput"===device.kind?device.label="Microphone "+(audioInputDevices.length+1):"audiooutput"===device.kind?device.label="Speaker "+(audioOutputDevices.length+1):device.label="Please invoke getUserMedia once.","undefined"!=typeof DetectRTC&&DetectRTC.browser.isChrome&&DetectRTC.browser.version>=46&&!/^(https:|chrome-extension:)$/g.test(location.protocol||"")&&"undefined"!=typeof document&&"string"==typeof document.domain&&document.domain.search&&document.domain.search(/localhost|127.0./g)===-1&&(device.label="HTTPs is required to get label of this "+device.kind+" device.")),"audioinput"===device.kind&&(hasMicrophone=!0,audioInputDevices.indexOf(device)===-1&&audioInputDevices.push(device)),"audiooutput"===device.kind&&(hasSpeakers=!0,audioOutputDevices.indexOf(device)===-1&&audioOutputDevices.push(device)),"videoinput"===device.kind&&(hasWebcam=!0,videoInputDevices.indexOf(device)===-1&&videoInputDevices.push(device)),MediaDevices.push(device),alreadyUsedDevices[device.deviceId+device.label+device.kind]=device)}),"undefined"!=typeof DetectRTC&&(DetectRTC.MediaDevices=MediaDevices,DetectRTC.hasMicrophone=hasMicrophone,DetectRTC.hasSpeakers=hasSpeakers,DetectRTC.hasWebcam=hasWebcam,DetectRTC.isWebsiteHasWebcamPermissions=isWebsiteHasWebcamPermissions,DetectRTC.isWebsiteHasMicrophonePermissions=isWebsiteHasMicrophonePermissions,DetectRTC.audioInputDevices=audioInputDevices,DetectRTC.audioOutputDevices=audioOutputDevices,DetectRTC.videoInputDevices=videoInputDevices),callback&&callback()})}function getAspectRatio(w,h){function gcd(a,b){return 0==b?a:gcd(b,a%b)}var r=gcd(w,h);return w/r/(h/r)}var browserFakeUserAgent="Fake/5.0 (FakeOS) AppleWebKit/123 (KHTML, like Gecko) Fake/12.3.4567.89 Fake/123.45",isNodejs="object"==typeof process&&"object"==typeof process.versions&&process.versions.node&&!process.browser;if(isNodejs){var version=process.versions.node.toString().replace("v","");browserFakeUserAgent="Nodejs/"+version+" (NodeOS) AppleWebKit/"+version+" (KHTML, like Gecko) Nodejs/"+version+" Nodejs/"+version}!function(that){"undefined"==typeof window&&("undefined"==typeof window&&"undefined"!=typeof global?(global.navigator={userAgent:browserFakeUserAgent,getUserMedia:function(){}},that.window=global):"undefined"==typeof window,"undefined"==typeof location&&(that.location={protocol:"file:",href:"",hash:""}),"undefined"==typeof screen&&(that.screen={width:0,height:0}))}("undefined"!=typeof global?global:window);var navigator=window.navigator;"undefined"!=typeof navigator?("undefined"!=typeof navigator.webkitGetUserMedia&&(navigator.getUserMedia=navigator.webkitGetUserMedia),"undefined"!=typeof navigator.mozGetUserMedia&&(navigator.getUserMedia=navigator.mozGetUserMedia)):navigator={getUserMedia:function(){},userAgent:browserFakeUserAgent};var isMobileDevice=!!/Android|webOS|iPhone|iPad|iPod|BB10|BlackBerry|IEMobile|Opera Mini|Mobile|mobile/i.test(navigator.userAgent||""),isEdge=!(navigator.userAgent.indexOf("Edge")===-1||!navigator.msSaveOrOpenBlob&&!navigator.msSaveBlob),isOpera=!!window.opera||navigator.userAgent.indexOf(" OPR/")>=0,isFirefox="undefined"!=typeof window.InstallTrigger,isSafari=/^((?!chrome|android).)*safari/i.test(navigator.userAgent),isChrome=!!window.chrome&&!isOpera,isIE="undefined"!=typeof document&&!!document.documentMode&&!isEdge,isMobile={Android:function(){return navigator.userAgent.match(/Android/i)},BlackBerry:function(){return navigator.userAgent.match(/BlackBerry|BB10/i)},iOS:function(){return navigator.userAgent.match(/iPhone|iPad|iPod/i)},Opera:function(){return navigator.userAgent.match(/Opera Mini/i)},Windows:function(){return navigator.userAgent.match(/IEMobile/i)},any:function(){return isMobile.Android()||isMobile.BlackBerry()||isMobile.iOS()||isMobile.Opera()||isMobile.Windows()},getOsName:function(){var osName="Unknown OS";return isMobile.Android()&&(osName="Android"),isMobile.BlackBerry()&&(osName="BlackBerry"),isMobile.iOS()&&(osName="iOS"),isMobile.Opera()&&(osName="Opera Mini"),isMobile.Windows()&&(osName="Windows"),osName}},osName="Unknown OS",osVersion="Unknown OS Version",osInfo=detectDesktopOS();osInfo&&osInfo.osName&&"-"!=osInfo.osName?(osName=osInfo.osName,osVersion=osInfo.osVersion):isMobile.any()&&(osName=isMobile.getOsName(),"Android"==osName&&(osVersion=getAndroidVersion()));var isNodejs="object"==typeof process&&"object"==typeof process.versions&&process.versions.node;"Unknown OS"===osName&&isNodejs&&(osName="Nodejs",osVersion=process.versions.node.toString().replace("v",""));var isCanvasSupportsStreamCapturing=!1,isVideoSupportsStreamCapturing=!1;["captureStream","mozCaptureStream","webkitCaptureStream"].forEach(function(item){"undefined"!=typeof document&&"function"==typeof document.createElement&&(!isCanvasSupportsStreamCapturing&&item in document.createElement("canvas")&&(isCanvasSupportsStreamCapturing=!0),!isVideoSupportsStreamCapturing&&item in document.createElement("video")&&(isVideoSupportsStreamCapturing=!0))});var MediaDevices=[],audioInputDevices=[],audioOutputDevices=[],videoInputDevices=[];navigator.mediaDevices&&navigator.mediaDevices.enumerateDevices&&(navigator.enumerateDevices=function(callback){var enumerateDevices=navigator.mediaDevices.enumerateDevices();enumerateDevices&&enumerateDevices.then?navigator.mediaDevices.enumerateDevices().then(callback)["catch"](function(){callback([])}):callback([])});var canEnumerate=!1;"undefined"!=typeof MediaStreamTrack&&"getSources"in MediaStreamTrack?canEnumerate=!0:navigator.mediaDevices&&navigator.mediaDevices.enumerateDevices&&(canEnumerate=!0);var hasMicrophone=!1,hasSpeakers=!1,hasWebcam=!1,isWebsiteHasMicrophonePermissions=!1,isWebsiteHasWebcamPermissions=!1,DetectRTC=window.DetectRTC||{};DetectRTC.browser=getBrowserInfo(),detectPrivateMode(function(isPrivateBrowsing){DetectRTC.browser.isPrivateBrowsing=!!isPrivateBrowsing}),DetectRTC.browser["is"+DetectRTC.browser.name]=!0,DetectRTC.osName=osName,DetectRTC.osVersion=osVersion;var isWebRTCSupported=("object"==typeof process&&"object"==typeof process.versions&&process.versions["node-webkit"],!1);["RTCPeerConnection","webkitRTCPeerConnection","mozRTCPeerConnection","RTCIceGatherer"].forEach(function(item){isWebRTCSupported||item in window&&(isWebRTCSupported=!0)}),DetectRTC.isWebRTCSupported=isWebRTCSupported,DetectRTC.isORTCSupported="undefined"!=typeof RTCIceGatherer;var isScreenCapturingSupported=!1;if(DetectRTC.browser.isChrome&&DetectRTC.browser.version>=35?isScreenCapturingSupported=!0:DetectRTC.browser.isFirefox&&DetectRTC.browser.version>=34?isScreenCapturingSupported=!0:DetectRTC.browser.isEdge&&DetectRTC.browser.version>=17?isScreenCapturingSupported=!0:"Android"===DetectRTC.osName&&DetectRTC.browser.isChrome&&(isScreenCapturingSupported=!0),!/^(https:|chrome-extension:)$/g.test(location.protocol||"")){var isNonLocalHost="undefined"!=typeof document&&"string"==typeof document.domain&&document.domain.search&&document.domain.search(/localhost|127.0./g)===-1;isNonLocalHost&&(DetectRTC.browser.isChrome||DetectRTC.browser.isEdge||DetectRTC.browser.isOpera)?isScreenCapturingSupported=!1:DetectRTC.browser.isFirefox&&(isScreenCapturingSupported=!1)}DetectRTC.isScreenCapturingSupported=isScreenCapturingSupported;var webAudio={isSupported:!1,isCreateMediaStreamSourceSupported:!1};["AudioContext","webkitAudioContext","mozAudioContext","msAudioContext"].forEach(function(item){webAudio.isSupported||item in window&&(webAudio.isSupported=!0,window[item]&&"createMediaStreamSource"in window[item].prototype&&(webAudio.isCreateMediaStreamSourceSupported=!0))}),DetectRTC.isAudioContextSupported=webAudio.isSupported,DetectRTC.isCreateMediaStreamSourceSupported=webAudio.isCreateMediaStreamSourceSupported;var isRtpDataChannelsSupported=!1;DetectRTC.browser.isChrome&&DetectRTC.browser.version>31&&(isRtpDataChannelsSupported=!0),DetectRTC.isRtpDataChannelsSupported=isRtpDataChannelsSupported;var isSCTPSupportd=!1;DetectRTC.browser.isFirefox&&DetectRTC.browser.version>28?isSCTPSupportd=!0:DetectRTC.browser.isChrome&&DetectRTC.browser.version>25?isSCTPSupportd=!0:DetectRTC.browser.isOpera&&DetectRTC.browser.version>=11&&(isSCTPSupportd=!0),DetectRTC.isSctpDataChannelsSupported=isSCTPSupportd,DetectRTC.isMobileDevice=isMobileDevice;var isGetUserMediaSupported=!1;navigator.getUserMedia?isGetUserMediaSupported=!0:navigator.mediaDevices&&navigator.mediaDevices.getUserMedia&&(isGetUserMediaSupported=!0),DetectRTC.browser.isChrome&&DetectRTC.browser.version>=46&&!/^(https:|chrome-extension:)$/g.test(location.protocol||"")&&"undefined"!=typeof document&&"string"==typeof document.domain&&document.domain.search&&document.domain.search(/localhost|127.0./g)===-1&&(isGetUserMediaSupported="Requires HTTPs"), +"Nodejs"===DetectRTC.osName&&(isGetUserMediaSupported=!1),DetectRTC.isGetUserMediaSupported=isGetUserMediaSupported;var displayResolution="";if(screen.width){var width=screen.width?screen.width:"",height=screen.height?screen.height:"";displayResolution+=""+width+" x "+height}DetectRTC.displayResolution=displayResolution,DetectRTC.displayAspectRatio=getAspectRatio(screen.width,screen.height).toFixed(2),DetectRTC.isCanvasSupportsStreamCapturing=isCanvasSupportsStreamCapturing,DetectRTC.isVideoSupportsStreamCapturing=isVideoSupportsStreamCapturing,"Chrome"==DetectRTC.browser.name&&DetectRTC.browser.version>=53&&(DetectRTC.isCanvasSupportsStreamCapturing||(DetectRTC.isCanvasSupportsStreamCapturing="Requires chrome flag: enable-experimental-web-platform-features"),DetectRTC.isVideoSupportsStreamCapturing||(DetectRTC.isVideoSupportsStreamCapturing="Requires chrome flag: enable-experimental-web-platform-features")),DetectRTC.DetectLocalIPAddress=DetectLocalIPAddress,DetectRTC.isWebSocketsSupported="WebSocket"in window&&2===window.WebSocket.CLOSING,DetectRTC.isWebSocketsBlocked=!DetectRTC.isWebSocketsSupported,"Nodejs"===DetectRTC.osName&&(DetectRTC.isWebSocketsSupported=!0,DetectRTC.isWebSocketsBlocked=!1),DetectRTC.checkWebSocketsSupport=function(callback){callback=callback||function(){};try{var starttime,websocket=new WebSocket("wss://echo.websocket.org:443/");websocket.onopen=function(){DetectRTC.isWebSocketsBlocked=!1,starttime=(new Date).getTime(),websocket.send("ping")},websocket.onmessage=function(){DetectRTC.WebsocketLatency=(new Date).getTime()-starttime+"ms",callback(),websocket.close(),websocket=null},websocket.onerror=function(){DetectRTC.isWebSocketsBlocked=!0,callback()}}catch(e){DetectRTC.isWebSocketsBlocked=!0,callback()}},DetectRTC.load=function(callback){callback=callback||function(){},checkDeviceSupport(callback)},"undefined"!=typeof MediaDevices?DetectRTC.MediaDevices=MediaDevices:DetectRTC.MediaDevices=[],DetectRTC.hasMicrophone=hasMicrophone,DetectRTC.hasSpeakers=hasSpeakers,DetectRTC.hasWebcam=hasWebcam,DetectRTC.isWebsiteHasWebcamPermissions=isWebsiteHasWebcamPermissions,DetectRTC.isWebsiteHasMicrophonePermissions=isWebsiteHasMicrophonePermissions,DetectRTC.audioInputDevices=audioInputDevices,DetectRTC.audioOutputDevices=audioOutputDevices,DetectRTC.videoInputDevices=videoInputDevices;var isSetSinkIdSupported=!1;"undefined"!=typeof document&&"function"==typeof document.createElement&&"setSinkId"in document.createElement("video")&&(isSetSinkIdSupported=!0),DetectRTC.isSetSinkIdSupported=isSetSinkIdSupported;var isRTPSenderReplaceTracksSupported=!1;DetectRTC.browser.isFirefox&&"undefined"!=typeof mozRTCPeerConnection?"getSenders"in mozRTCPeerConnection.prototype&&(isRTPSenderReplaceTracksSupported=!0):DetectRTC.browser.isChrome&&"undefined"!=typeof webkitRTCPeerConnection&&"getSenders"in webkitRTCPeerConnection.prototype&&(isRTPSenderReplaceTracksSupported=!0),DetectRTC.isRTPSenderReplaceTracksSupported=isRTPSenderReplaceTracksSupported;var isRemoteStreamProcessingSupported=!1;DetectRTC.browser.isFirefox&&DetectRTC.browser.version>38&&(isRemoteStreamProcessingSupported=!0),DetectRTC.isRemoteStreamProcessingSupported=isRemoteStreamProcessingSupported;var isApplyConstraintsSupported=!1;"undefined"!=typeof MediaStreamTrack&&"applyConstraints"in MediaStreamTrack.prototype&&(isApplyConstraintsSupported=!0),DetectRTC.isApplyConstraintsSupported=isApplyConstraintsSupported;var isMultiMonitorScreenCapturingSupported=!1;DetectRTC.browser.isFirefox&&DetectRTC.browser.version>=43&&(isMultiMonitorScreenCapturingSupported=!0),DetectRTC.isMultiMonitorScreenCapturingSupported=isMultiMonitorScreenCapturingSupported,DetectRTC.isPromisesSupported=!!("Promise"in window),DetectRTC.version="1.3.6","undefined"==typeof DetectRTC&&(window.DetectRTC={});var MediaStream=window.MediaStream;"undefined"==typeof MediaStream&&"undefined"!=typeof webkitMediaStream&&(MediaStream=webkitMediaStream),"undefined"!=typeof MediaStream&&"function"==typeof MediaStream?DetectRTC.MediaStream=Object.keys(MediaStream.prototype):DetectRTC.MediaStream=!1,"undefined"!=typeof MediaStreamTrack?DetectRTC.MediaStreamTrack=Object.keys(MediaStreamTrack.prototype):DetectRTC.MediaStreamTrack=!1;var RTCPeerConnection=window.RTCPeerConnection||window.mozRTCPeerConnection||window.webkitRTCPeerConnection;"undefined"!=typeof RTCPeerConnection?DetectRTC.RTCPeerConnection=Object.keys(RTCPeerConnection.prototype):DetectRTC.RTCPeerConnection=!1,window.DetectRTC=DetectRTC,"undefined"!=typeof module&&(module.exports=DetectRTC),"function"==typeof define&&define.amd&&define("DetectRTC",[],function(){return DetectRTC})}(),"undefined"!=typeof cordova&&(DetectRTC.isMobileDevice=!0,DetectRTC.browser.name="Chrome"),navigator&&navigator.userAgent&&navigator.userAgent.indexOf("Crosswalk")!==-1&&(DetectRTC.isMobileDevice=!0,DetectRTC.browser.name="Chrome"),window.addEventListener||(window.addEventListener=function(el,eventName,eventHandler){el.attachEvent&&el.attachEvent("on"+eventName,eventHandler)}),window.attachEventListener=function(video,type,listener,useCapture){video.addEventListener(type,listener,useCapture)};var MediaStream=window.MediaStream;"undefined"==typeof MediaStream&&"undefined"!=typeof webkitMediaStream&&(MediaStream=webkitMediaStream),"undefined"!=typeof MediaStream&&("stop"in MediaStream.prototype||(MediaStream.prototype.stop=function(){this.getTracks().forEach(function(track){track.stop()})})),window.iOSDefaultAudioOutputDevice=window.iOSDefaultAudioOutputDevice||"speaker","undefined"==typeof window.enableAdapter&&("Firefox"===DetectRTC.browser.name&&DetectRTC.browser.version>=54&&(window.enableAdapter=!0),"Chrome"===DetectRTC.browser.name&&DetectRTC.browser.version>=60,"undefined"!=typeof adapter&&adapter.browserDetails&&"string"==typeof adapter.browserDetails.browser&&(window.enableAdapter=!0)),window.enableAdapter||("undefined"==typeof URL.createObjectURL&&(URL.createObjectURL=function(stream){return"blob:https://"+document.domain+"/"+getRandomString()}),"srcObject"in HTMLMediaElement.prototype||(HTMLMediaElement.prototype.srcObject=function(stream){return"mozSrcObject"in this?void(this.mozSrcObject=stream):void(this.src=URL.createObjectURL(stream))})),document.addEventListener("deviceready",setCordovaAPIs,!1),setCordovaAPIs();var RTCPeerConnection,defaults={};"undefined"!=typeof window.RTCPeerConnection?RTCPeerConnection=window.RTCPeerConnection:"undefined"!=typeof mozRTCPeerConnection?RTCPeerConnection=mozRTCPeerConnection:"undefined"!=typeof webkitRTCPeerConnection&&(RTCPeerConnection=webkitRTCPeerConnection);var RTCSessionDescription=window.RTCSessionDescription||window.mozRTCSessionDescription,RTCIceCandidate=window.RTCIceCandidate||window.mozRTCIceCandidate,MediaStreamTrack=window.MediaStreamTrack,CodecsHandler=function(){function preferCodec(sdp,codecName){var info=splitLines(sdp);return info.videoCodecNumbers?"vp8"===codecName&&info.vp8LineNumber===info.videoCodecNumbers[0]?sdp:"vp9"===codecName&&info.vp9LineNumber===info.videoCodecNumbers[0]?sdp:"h264"===codecName&&info.h264LineNumber===info.videoCodecNumbers[0]?sdp:sdp=preferCodecHelper(sdp,codecName,info):sdp}function preferCodecHelper(sdp,codec,info,ignore){var preferCodecNumber="";if("vp8"===codec){if(!info.vp8LineNumber)return sdp;preferCodecNumber=info.vp8LineNumber}if("vp9"===codec){if(!info.vp9LineNumber)return sdp;preferCodecNumber=info.vp9LineNumber}if("h264"===codec){if(!info.h264LineNumber)return sdp;preferCodecNumber=info.h264LineNumber}var newLine=info.videoCodecNumbersOriginal.split("SAVPF")[0]+"SAVPF ",newOrder=[preferCodecNumber];return ignore&&(newOrder=[]),info.videoCodecNumbers.forEach(function(codecNumber){codecNumber!==preferCodecNumber&&newOrder.push(codecNumber)}),newLine+=newOrder.join(" "),sdp=sdp.replace(info.videoCodecNumbersOriginal,newLine)}function splitLines(sdp){var info={};return sdp.split("\n").forEach(function(line){0===line.indexOf("m=video")&&(info.videoCodecNumbers=[],line.split("SAVPF")[1].split(" ").forEach(function(codecNumber){codecNumber=codecNumber.trim(),codecNumber&&codecNumber.length&&(info.videoCodecNumbers.push(codecNumber),info.videoCodecNumbersOriginal=line)})),line.indexOf("VP8/90000")===-1||info.vp8LineNumber||(info.vp8LineNumber=line.replace("a=rtpmap:","").split(" ")[0]),line.indexOf("VP9/90000")===-1||info.vp9LineNumber||(info.vp9LineNumber=line.replace("a=rtpmap:","").split(" ")[0]),line.indexOf("H264/90000")===-1||info.h264LineNumber||(info.h264LineNumber=line.replace("a=rtpmap:","").split(" ")[0])}),info}function removeVPX(sdp){var info=splitLines(sdp);return sdp=preferCodecHelper(sdp,"vp9",info,!0),sdp=preferCodecHelper(sdp,"vp8",info,!0)}function disableNACK(sdp){if(!sdp||"string"!=typeof sdp)throw"Invalid arguments.";return sdp=sdp.replace("a=rtcp-fb:126 nack\r\n",""),sdp=sdp.replace("a=rtcp-fb:126 nack pli\r\n","a=rtcp-fb:126 pli\r\n"),sdp=sdp.replace("a=rtcp-fb:97 nack\r\n",""),sdp=sdp.replace("a=rtcp-fb:97 nack pli\r\n","a=rtcp-fb:97 pli\r\n")}function prioritize(codecMimeType,peer){if(peer&&peer.getSenders&&peer.getSenders().length){if(!codecMimeType||"string"!=typeof codecMimeType)throw"Invalid arguments.";peer.getSenders().forEach(function(sender){for(var params=sender.getParameters(),i=0;i=numberOfTimes||setTimeout(function(){callback(),afterEach(setTimeoutInteval,numberOfTimes,callback,startedTimes)},setTimeoutInteval)}return{setHandlers:setHandlers,onSyncNeeded:function(streamid,action,type){}}}();window.addEventListener("message",function(event){event.origin==window.location.origin&&onMessageCallback(event.data)}),window.IsAndroidChrome=!1;try{navigator.userAgent.toLowerCase().indexOf("android")>-1&&/Chrome/.test(navigator.userAgent)&&/Google Inc/.test(navigator.vendor)&&(window.IsAndroidChrome=!0)}catch(e){}var sourceId,screenCallback,chromeMediaSource="screen",TextSender={send:function(config){function sendText(textMessage,text){var data={type:"text",uuid:uuid,sendingTime:sendingTime};textMessage&&(text=textMessage,data.packets=parseInt(text.length/packetSize)),text.length>packetSize?data.message=text.slice(0,packetSize):(data.message=text,data.last=!0,data.isobject=isobject),channel.send(data,remoteUserId),textToTransfer=text.slice(data.message.length),textToTransfer.length&&setTimeout(function(){sendText(null,textToTransfer)},connection.chunkInterval||100)}var connection=config.connection,channel=config.channel,remoteUserId=config.remoteUserId,initialText=config.text,packetSize=connection.chunkSize||1e3,textToTransfer="",isobject=!1;isString(initialText)||(isobject=!0,initialText=JSON.stringify(initialText));var uuid=getRandomString(),sendingTime=(new Date).getTime();sendText(initialText)}},FileProgressBarHandler=function(){function handle(connection){function updateLabel(progress,label){if(progress.position!==-1){var position=+progress.position.toFixed(2).split(".")[1]||100;label.innerHTML=position+"%"}}var progressHelper={};connection.onFileStart=function(file){var div=document.createElement("div");return div.title=file.name,div.innerHTML=" ",file.remoteUserId&&(div.innerHTML+=" (Sharing with:"+file.remoteUserId+")"),connection.filesContainer||(connection.filesContainer=document.body||document.documentElement),connection.filesContainer.insertBefore(div,connection.filesContainer.firstChild),file.remoteUserId?(progressHelper[file.uuid]||(progressHelper[file.uuid]={}),progressHelper[file.uuid][file.remoteUserId]={div:div,progress:div.querySelector("progress"),label:div.querySelector("label")},void(progressHelper[file.uuid][file.remoteUserId].progress.max=file.maxChunks)):(progressHelper[file.uuid]={div:div,progress:div.querySelector("progress"),label:div.querySelector("label")},void(progressHelper[file.uuid].progress.max=file.maxChunks))},connection.onFileProgress=function(chunk){var helper=progressHelper[chunk.uuid];helper&&(chunk.remoteUserId&&!(helper=progressHelper[chunk.uuid][chunk.remoteUserId])||(helper.progress.value=chunk.currentPosition||chunk.maxChunks||helper.progress.max,updateLabel(helper.progress,helper.label)))},connection.onFileEnd=function(file){var helper=progressHelper[file.uuid];if(!helper)return void console.error("No such progress-helper element exist.",file);if(!file.remoteUserId||(helper=progressHelper[file.uuid][file.remoteUserId])){var div=helper.div;file.type.indexOf("image")!=-1?div.innerHTML='Download '+file.name+'
    ':div.innerHTML='Download '+file.name+'
    '}}}return{handle:handle}}(),TranslationHandler=function(){function handle(connection){connection.autoTranslateText=!1,connection.language="en",connection.googKey="AIzaSyCgB5hmFY74WYB-EoWkhr9cAGr6TiTHrEE",connection.Translator={TranslateText:function(text,callback){var newScript=document.createElement("script");newScript.type="text/javascript";var sourceText=encodeURIComponent(text),randomNumber="method"+connection.token();window[randomNumber]=function(response){return response.data&&response.data.translations[0]&&callback?void callback(response.data.translations[0].translatedText):response.error&&"Daily Limit Exceeded"===response.error.message?void console.error('Text translation failed. Error message: "Daily Limit Exceeded."'):response.error?void console.error(response.error.message):void console.error(response)};var source="https://www.googleapis.com/language/translate/v2?key="+connection.googKey+"&target="+(connection.language||"en-US")+"&callback=window."+randomNumber+"&q="+sourceText;newScript.src=source,document.getElementsByTagName("head")[0].appendChild(newScript)},getListOfLanguages:function(callback){var xhr=new XMLHttpRequest;xhr.onreadystatechange=function(){if(xhr.readyState==XMLHttpRequest.DONE){var response=JSON.parse(xhr.responseText);if(response&&response.data&&response.data.languages)return void callback(response.data.languages);if(response.error&&"Daily Limit Exceeded"===response.error.message)return void console.error('Text translation failed. Error message: "Daily Limit Exceeded."');if(response.error)return void console.error(response.error.message);console.error(response)}};var url="https://www.googleapis.com/language/translate/v2/languages?key="+connection.googKey+"&target=en";xhr.open("GET",url,!0),xhr.send(null)}}}return{handle:handle}}();!function(connection){function onUserLeft(remoteUserId){connection.deletePeer(remoteUserId)}function connectSocket(connectCallback){if(connection.socketAutoReConnect=!0,connection.socket)return void(connectCallback&&connectCallback(connection.socket));if("undefined"==typeof SocketConnection)if("undefined"!=typeof FirebaseConnection)window.SocketConnection=FirebaseConnection;else{if("undefined"==typeof PubNubConnection)throw"SocketConnection.js seems missed.";window.SocketConnection=PubNubConnection}new SocketConnection(connection,function(s){connectCallback&&connectCallback(connection.socket)})}function beforeJoin(userPreferences,callback){if(connection.dontCaptureUserMedia||userPreferences.isDataOnly)return void callback();var localMediaConstraints={};userPreferences.localPeerSdpConstraints.OfferToReceiveAudio&&(localMediaConstraints.audio=connection.mediaConstraints.audio),userPreferences.localPeerSdpConstraints.OfferToReceiveVideo&&(localMediaConstraints.video=connection.mediaConstraints.video);var session=userPreferences.session||connection.session;return session.oneway&&"two-way"!==session.audio&&"two-way"!==session.video&&"two-way"!==session.screen?void callback():(session.oneway&&session.audio&&"two-way"===session.audio&&(session={audio:!0}),void((session.audio||session.video||session.screen)&&(session.screen?"Edge"===DetectRTC.browser.name?navigator.getDisplayMedia({video:!0,audio:isAudioPlusTab(connection)}).then(function(screen){screen.isScreen=!0,mPeer.onGettingLocalMedia(screen),!session.audio&&!session.video||isAudioPlusTab(connection)?callback(screen):connection.invokeGetUserMedia(null,callback)},function(error){console.error("Unable to capture screen on Edge. HTTPs and version 17+ is required.")}):connection.getScreenConstraints(function(error,screen_constraints){connection.invokeGetUserMedia({audio:!!isAudioPlusTab(connection)&&getAudioScreenConstraints(screen_constraints),video:screen_constraints,isScreen:!0},!session.audio&&!session.video||isAudioPlusTab(connection)?callback:connection.invokeGetUserMedia(null,callback))}):(session.audio||session.video)&&connection.invokeGetUserMedia(null,callback,session))))}function beforeUnload(shiftModerationControlOnLeave,dontCloseSocket){connection.closeBeforeUnload&&(connection.isInitiator===!0&&connection.dontMakeMeModerator(),connection.peers.getAllParticipants().forEach(function(participant){mPeer.onNegotiationNeeded({userLeft:!0},participant),connection.peers[participant]&&connection.peers[participant].peer&&connection.peers[participant].peer.close(),delete connection.peers[participant]}),dontCloseSocket||connection.closeSocket(),connection.broadcasters=[],connection.isInitiator=!1)}function applyConstraints(stream,mediaConstraints){return stream?(mediaConstraints.audio&&stream.getAudioTracks().forEach(function(track){track.applyConstraints(mediaConstraints.audio)}),void(mediaConstraints.video&&stream.getVideoTracks().forEach(function(track){track.applyConstraints(mediaConstraints.video)}))):void(connection.enableLogs&&console.error("No stream to applyConstraints."))}function replaceTrack(track,remoteUserId,isVideoTrack){return remoteUserId?void mPeer.replaceTrack(track,remoteUserId,isVideoTrack):void connection.peers.getAllParticipants().forEach(function(participant){mPeer.replaceTrack(track,participant,isVideoTrack)})}function keepNextBroadcasterOnServer(){if(connection.isInitiator&&!connection.session.oneway&&!connection.session.broadcast&&"many-to-many"===connection.direction){var firstBroadcaster=connection.broadcasters[0],otherBroadcasters=[];connection.broadcasters.forEach(function(broadcaster){broadcaster!==firstBroadcaster&&otherBroadcasters.push(broadcaster)}),connection.autoCloseEntireSession||connection.shiftModerationControl(firstBroadcaster,otherBroadcasters,!0)}}forceOptions=forceOptions||{useDefaultDevices:!0},connection.channel=connection.sessionid=(roomid||location.href.replace(/\/|:|#|\?|\$|\^|%|\.|`|~|!|\+|@|\[|\||]|\|*. /g,"").split("\n").join("").split("\r").join(""))+"";var mPeer=new MultiPeers(connection),preventDuplicateOnStreamEvents={};mPeer.onGettingLocalMedia=function(stream,callback){if(callback=callback||function(){},!preventDuplicateOnStreamEvents[stream.streamid]){preventDuplicateOnStreamEvents[stream.streamid]=!0;try{stream.type="local"}catch(e){}connection.setStreamEndHandler(stream),getRMCMediaElement(stream,function(mediaElement){mediaElement.id=stream.streamid,mediaElement.muted=!0,mediaElement.volume=0,connection.attachStreams.indexOf(stream)===-1&&connection.attachStreams.push(stream),"undefined"!=typeof StreamsHandler&&StreamsHandler.setHandlers(stream,!0,connection),connection.streamEvents[stream.streamid]={stream:stream,type:"local",mediaElement:mediaElement,userid:connection.userid,extra:connection.extra,streamid:stream.streamid,isAudioMuted:!0},setHarkEvents(connection,connection.streamEvents[stream.streamid]),setMuteHandlers(connection,connection.streamEvents[stream.streamid]),connection.onstream(connection.streamEvents[stream.streamid]),callback()},connection)}},mPeer.onGettingRemoteMedia=function(stream,remoteUserId){try{stream.type="remote"}catch(e){}connection.setStreamEndHandler(stream,"remote-stream"),getRMCMediaElement(stream,function(mediaElement){mediaElement.id=stream.streamid,"undefined"!=typeof StreamsHandler&&StreamsHandler.setHandlers(stream,!1,connection),connection.streamEvents[stream.streamid]={stream:stream,type:"remote",userid:remoteUserId,extra:connection.peers[remoteUserId]?connection.peers[remoteUserId].extra:{},mediaElement:mediaElement,streamid:stream.streamid},setMuteHandlers(connection,connection.streamEvents[stream.streamid]),connection.onstream(connection.streamEvents[stream.streamid])},connection)},mPeer.onRemovingRemoteMedia=function(stream,remoteUserId){var streamEvent=connection.streamEvents[stream.streamid];streamEvent||(streamEvent={stream:stream,type:"remote",userid:remoteUserId,extra:connection.peers[remoteUserId]?connection.peers[remoteUserId].extra:{},streamid:stream.streamid,mediaElement:connection.streamEvents[stream.streamid]?connection.streamEvents[stream.streamid].mediaElement:null}),connection.peersBackup[streamEvent.userid]&&(streamEvent.extra=connection.peersBackup[streamEvent.userid].extra),connection.onstreamended(streamEvent),delete connection.streamEvents[stream.streamid]},mPeer.onNegotiationNeeded=function(message,remoteUserId,callback){remoteUserId=remoteUserId||message.remoteUserId,message=message||"",connectSocket(function(){connection.socket.emit(connection.socketMessageEvent,"undefined"!=typeof message.password?message:{remoteUserId:remoteUserId,message:message,sender:connection.userid},callback||function(){})})},mPeer.onUserLeft=onUserLeft,mPeer.disconnectWith=function(remoteUserId,callback){connection.socket&&connection.socket.emit("disconnect-with",remoteUserId,callback||function(){}),connection.deletePeer(remoteUserId)},connection.broadcasters=[],connection.socketOptions={transport:"polling"},connection.openOrJoin=function(localUserid,password,callback){callback=callback||function(){},connection.checkPresence(localUserid,function(isRoomExist,roomid){if("function"==typeof password&&"undefined"!=typeof password&&(callback=password,password=null),!password&&connection.password&&(password=connection.password),isRoomExist){connection.sessionid=roomid;var localPeerSdpConstraints=!1,remotePeerSdpConstraints=!1,isOneWay=!!connection.session.oneway,isDataOnly=isData(connection.session);remotePeerSdpConstraints={OfferToReceiveAudio:connection.sdpConstraints.mandatory.OfferToReceiveAudio,OfferToReceiveVideo:connection.sdpConstraints.mandatory.OfferToReceiveVideo},localPeerSdpConstraints={OfferToReceiveAudio:isOneWay?!!connection.session.audio:connection.sdpConstraints.mandatory.OfferToReceiveAudio,OfferToReceiveVideo:isOneWay?!!connection.session.video||!!connection.session.screen:connection.sdpConstraints.mandatory.OfferToReceiveVideo};var connectionDescription={remoteUserId:connection.sessionid,message:{newParticipationRequest:!0, +isOneWay:isOneWay,isDataOnly:isDataOnly,localPeerSdpConstraints:localPeerSdpConstraints,remotePeerSdpConstraints:remotePeerSdpConstraints},sender:connection.userid,password:password||!1};return void beforeJoin(connectionDescription.message,function(){mPeer.onNegotiationNeeded(connectionDescription),callback(isRoomExist,roomid)})}connection.waitingForLocalMedia=!0,connection.isInitiator=!0;connection.userid;return connection.userid=connection.sessionid=localUserid||connection.sessionid,connection.userid+="",connection.socket.emit("changed-uuid",connection.userid),password&&connection.socket.emit("set-password",password),isData(connection.session)?void(connection.waitingForLocalMedia=!1):void connection.captureUserMedia(function(){connection.waitingForLocalMedia=!1,callback(isRoomExist,roomid)})})},connection.waitingForLocalMedia=!1,connection.open=function(localUserid,isPublicModerator,callback){connection.waitingForLocalMedia=!0,connection.isInitiator=!0,callback=callback||function(){},"function"==typeof isPublicModerator&&(callback=isPublicModerator,isPublicModerator=!1);connection.userid;connection.userid=connection.sessionid=localUserid||connection.sessionid,connection.userid+="",connectSocket(function(){return connection.socket.emit("changed-uuid",connection.userid),connection.password&&connection.socket.emit("set-password",connection.password),1==isPublicModerator&&connection.becomePublicModerator(),isData(connection.session)?(connection.waitingForLocalMedia=!1,void callback()):void connection.captureUserMedia(function(){connection.waitingForLocalMedia=!1,callback()})})},connection.becomePublicModerator=function(){connection.isInitiator&&connection.socket.emit("become-a-public-moderator")},connection.dontMakeMeModerator=function(){connection.socket.emit("dont-make-me-moderator")},connection.deletePeer=function(remoteUserId){if(remoteUserId){var eventObject={userid:remoteUserId,extra:connection.peers[remoteUserId]?connection.peers[remoteUserId].extra:{}};if(connection.peersBackup[eventObject.userid]&&(eventObject.extra=connection.peersBackup[eventObject.userid].extra),connection.onleave(eventObject),connection.peers[remoteUserId]){connection.peers[remoteUserId].streams.forEach(function(stream){stream.stop()});var peer=connection.peers[remoteUserId].peer;if(peer&&"closed"!==peer.iceConnectionState)try{peer.close()}catch(e){}connection.peers[remoteUserId]&&(connection.peers[remoteUserId].peer=null,delete connection.peers[remoteUserId])}if(connection.broadcasters.indexOf(remoteUserId)!==-1){var newArray=[];connection.broadcasters.forEach(function(broadcaster){broadcaster!==remoteUserId&&newArray.push(broadcaster)}),connection.broadcasters=newArray,keepNextBroadcasterOnServer()}}},connection.rejoin=function(connectionDescription){if(!connection.isInitiator&&connectionDescription&&Object.keys(connectionDescription).length){var extra={};connection.peers[connectionDescription.remoteUserId]&&(extra=connection.peers[connectionDescription.remoteUserId].extra,connection.deletePeer(connectionDescription.remoteUserId)),connectionDescription&&connectionDescription.remoteUserId&&(connection.join(connectionDescription.remoteUserId),connection.onReConnecting({userid:connectionDescription.remoteUserId,extra:extra}))}},connection.join=connection.connect=function(remoteUserId,options){connection.sessionid=!!remoteUserId&&(remoteUserId.sessionid||remoteUserId.remoteUserId||remoteUserId)||connection.sessionid,connection.sessionid+="";var localPeerSdpConstraints=!1,remotePeerSdpConstraints=!1,isOneWay=!1,isDataOnly=!1;if(remoteUserId&&remoteUserId.session||!remoteUserId||"string"==typeof remoteUserId){var session=remoteUserId?remoteUserId.session||connection.session:connection.session;isOneWay=!!session.oneway,isDataOnly=isData(session),remotePeerSdpConstraints={OfferToReceiveAudio:connection.sdpConstraints.mandatory.OfferToReceiveAudio,OfferToReceiveVideo:connection.sdpConstraints.mandatory.OfferToReceiveVideo},localPeerSdpConstraints={OfferToReceiveAudio:isOneWay?!!connection.session.audio:connection.sdpConstraints.mandatory.OfferToReceiveAudio,OfferToReceiveVideo:isOneWay?!!connection.session.video||!!connection.session.screen:connection.sdpConstraints.mandatory.OfferToReceiveVideo}}options=options||{};var cb=function(){};"function"==typeof options&&(cb=options,options={}),"undefined"!=typeof options.localPeerSdpConstraints&&(localPeerSdpConstraints=options.localPeerSdpConstraints),"undefined"!=typeof options.remotePeerSdpConstraints&&(remotePeerSdpConstraints=options.remotePeerSdpConstraints),"undefined"!=typeof options.isOneWay&&(isOneWay=options.isOneWay),"undefined"!=typeof options.isDataOnly&&(isDataOnly=options.isDataOnly);var connectionDescription={remoteUserId:connection.sessionid,message:{newParticipationRequest:!0,isOneWay:isOneWay,isDataOnly:isDataOnly,localPeerSdpConstraints:localPeerSdpConstraints,remotePeerSdpConstraints:remotePeerSdpConstraints},sender:connection.userid,password:connection.password||!1};return beforeJoin(connectionDescription.message,function(){connectSocket(function(){connection.peers[connection.sessionid]||(mPeer.onNegotiationNeeded(connectionDescription),cb())})}),connectionDescription},connection.connectWithAllParticipants=function(remoteUserId){mPeer.onNegotiationNeeded("connectWithAllParticipants",remoteUserId||connection.sessionid)},connection.removeFromBroadcastersList=function(remoteUserId){mPeer.onNegotiationNeeded("removeFromBroadcastersList",remoteUserId||connection.sessionid),connection.peers.getAllParticipants(remoteUserId||connection.sessionid).forEach(function(participant){mPeer.onNegotiationNeeded("dropPeerConnection",participant),connection.deletePeer(participant)}),connection.attachStreams.forEach(function(stream){stream.stop()})},connection.getUserMedia=connection.captureUserMedia=function(callback,sessionForced){callback=callback||function(){};var session=sessionForced||connection.session;return connection.dontCaptureUserMedia||isData(session)?void callback():void((session.audio||session.video||session.screen)&&(session.screen?"Edge"===DetectRTC.browser.name?navigator.getDisplayMedia({video:!0,audio:isAudioPlusTab(connection)}).then(function(screen){if(screen.isScreen=!0,mPeer.onGettingLocalMedia(screen),(session.audio||session.video)&&!isAudioPlusTab(connection)){var nonScreenSession={};for(var s in session)"screen"!==s&&(nonScreenSession[s]=session[s]);return void connection.invokeGetUserMedia(sessionForced,callback,nonScreenSession)}callback(screen)},function(error){console.error("Unable to capture screen on Edge. HTTPs and version 17+ is required.")}):connection.getScreenConstraints(function(error,screen_constraints){if(error)throw error;connection.invokeGetUserMedia({audio:!!isAudioPlusTab(connection)&&getAudioScreenConstraints(screen_constraints),video:screen_constraints,isScreen:!0},function(stream){if((session.audio||session.video)&&!isAudioPlusTab(connection)){var nonScreenSession={};for(var s in session)"screen"!==s&&(nonScreenSession[s]=session[s]);return void connection.invokeGetUserMedia(sessionForced,callback,nonScreenSession)}callback(stream)})}):(session.audio||session.video)&&connection.invokeGetUserMedia(sessionForced,callback,session)))},connection.closeBeforeUnload=!0,window.addEventListener("beforeunload",beforeUnload,!1),connection.userid=getRandomString(),connection.changeUserId=function(newUserId,callback){callback=callback||function(){},connection.userid=newUserId||getRandomString(),connection.socket.emit("changed-uuid",connection.userid,callback)},connection.extra={},connection.attachStreams=[],connection.session={audio:!0,video:!0},connection.enableFileSharing=!1,connection.bandwidth={screen:!1,audio:!1,video:!1},connection.codecs={audio:"opus",video:"VP9"},connection.processSdp=function(sdp){return"Safari"===DetectRTC.browser.name?sdp:("VP8"===connection.codecs.video.toUpperCase()&&(sdp=CodecsHandler.preferCodec(sdp,"vp8")),"VP9"===connection.codecs.video.toUpperCase()&&(sdp=CodecsHandler.preferCodec(sdp,"vp9")),"H264"===connection.codecs.video.toUpperCase()&&(sdp=CodecsHandler.preferCodec(sdp,"h264")),"G722"===connection.codecs.audio&&(sdp=CodecsHandler.removeNonG722(sdp)),"Firefox"===DetectRTC.browser.name?sdp:((connection.bandwidth.video||connection.bandwidth.screen)&&(sdp=CodecsHandler.setApplicationSpecificBandwidth(sdp,connection.bandwidth,!!connection.session.screen)),connection.bandwidth.video&&(sdp=CodecsHandler.setVideoBitrates(sdp,{min:8*connection.bandwidth.video*1024,max:8*connection.bandwidth.video*1024})),connection.bandwidth.audio&&(sdp=CodecsHandler.setOpusAttributes(sdp,{maxaveragebitrate:8*connection.bandwidth.audio*1024,maxplaybackrate:8*connection.bandwidth.audio*1024,stereo:1,maxptime:3})),sdp))},"undefined"!=typeof CodecsHandler&&(connection.BandwidthHandler=connection.CodecsHandler=CodecsHandler),connection.mediaConstraints={audio:{mandatory:{},optional:connection.bandwidth.audio?[{bandwidth:8*connection.bandwidth.audio*1024||1048576}]:[]},video:{mandatory:{},optional:connection.bandwidth.video?[{bandwidth:8*connection.bandwidth.video*1024||1048576},{facingMode:"user"}]:[{facingMode:"user"}]}},"Firefox"===DetectRTC.browser.name&&(connection.mediaConstraints={audio:!0,video:!0}),forceOptions.useDefaultDevices||DetectRTC.isMobileDevice||DetectRTC.load(function(){var lastAudioDevice,lastVideoDevice;if(DetectRTC.MediaDevices.forEach(function(device){"audioinput"===device.kind&&connection.mediaConstraints.audio!==!1&&(lastAudioDevice=device),"videoinput"===device.kind&&connection.mediaConstraints.video!==!1&&(lastVideoDevice=device)}),lastAudioDevice){if("Firefox"===DetectRTC.browser.name)return void(connection.mediaConstraints.audio!==!0?connection.mediaConstraints.audio.deviceId=lastAudioDevice.id:connection.mediaConstraints.audio={deviceId:lastAudioDevice.id});1==connection.mediaConstraints.audio&&(connection.mediaConstraints.audio={mandatory:{},optional:[]}),connection.mediaConstraints.audio.optional||(connection.mediaConstraints.audio.optional=[]);var optional=[{sourceId:lastAudioDevice.id}];connection.mediaConstraints.audio.optional=optional.concat(connection.mediaConstraints.audio.optional)}if(lastVideoDevice){if("Firefox"===DetectRTC.browser.name)return void(connection.mediaConstraints.video!==!0?connection.mediaConstraints.video.deviceId=lastVideoDevice.id:connection.mediaConstraints.video={deviceId:lastVideoDevice.id});1==connection.mediaConstraints.video&&(connection.mediaConstraints.video={mandatory:{},optional:[]}),connection.mediaConstraints.video.optional||(connection.mediaConstraints.video.optional=[]);var optional=[{sourceId:lastVideoDevice.id}];connection.mediaConstraints.video.optional=optional.concat(connection.mediaConstraints.video.optional)}}),connection.sdpConstraints={mandatory:{OfferToReceiveAudio:!0,OfferToReceiveVideo:!0},optional:[{VoiceActivityDetection:!1}]},connection.rtcpMuxPolicy="require",connection.iceTransportPolicy=null,connection.optionalArgument={optional:[{DtlsSrtpKeyAgreement:!0},{googImprovedWifiBwe:!0},{googScreencastMinBitrate:300},{googIPv6:!0},{googDscp:!0},{googCpuUnderuseThreshold:55},{googCpuOveruseThreshold:85},{googSuspendBelowMinBitrate:!0},{googCpuOveruseDetection:!0}],mandatory:{}},connection.iceServers=IceServersHandler.getIceServers(connection),connection.candidates={host:!0,stun:!0,turn:!0},connection.iceProtocols={tcp:!0,udp:!0},connection.onopen=function(event){connection.enableLogs&&console.info("Data connection has been opened between you & ",event.userid)},connection.onclose=function(event){connection.enableLogs&&console.warn("Data connection has been closed between you & ",event.userid)},connection.onerror=function(error){connection.enableLogs&&console.error(error.userid,"data-error",error)},connection.onmessage=function(event){connection.enableLogs&&console.debug("data-message",event.userid,event.data)},connection.send=function(data,remoteUserId){connection.peers.send(data,remoteUserId)},connection.close=connection.disconnect=connection.leave=function(){beforeUnload(!1,!0)},connection.closeEntireSession=function(callback){callback=callback||function(){},connection.socket.emit("close-entire-session",function looper(){return connection.getAllParticipants().length?void setTimeout(looper,100):(connection.onEntireSessionClosed({sessionid:connection.sessionid,userid:connection.userid,extra:connection.extra}),void connection.changeUserId(null,function(){connection.close(),callback()}))})},connection.onEntireSessionClosed=function(event){connection.enableLogs&&console.info("Entire session is closed: ",event.sessionid,event.extra)},connection.onstream=function(e){var parentNode=connection.videosContainer;parentNode.insertBefore(e.mediaElement,parentNode.firstChild);var played=e.mediaElement.play();return"undefined"!=typeof played?void played["catch"](function(){}).then(function(){setTimeout(function(){e.mediaElement.play()},2e3)}):void setTimeout(function(){e.mediaElement.play()},2e3)},connection.onstreamended=function(e){e.mediaElement||(e.mediaElement=document.getElementById(e.streamid)),e.mediaElement&&e.mediaElement.parentNode&&e.mediaElement.parentNode.removeChild(e.mediaElement)},connection.direction="many-to-many",connection.removeStream=function(streamid,remoteUserId){var stream;return connection.attachStreams.forEach(function(localStream){localStream.id===streamid&&(stream=localStream)}),stream?(connection.peers.getAllParticipants().forEach(function(participant){if(!remoteUserId||participant===remoteUserId){var user=connection.peers[participant];try{user.peer.removeStream(stream)}catch(e){}}}),void connection.renegotiate()):void console.warn("No such stream exist.",streamid)},connection.addStream=function(session,remoteUserId){function gumCallback(stream){session.streamCallback&&session.streamCallback(stream),connection.renegotiate(remoteUserId)}return session.getAudioTracks?(connection.attachStreams.indexOf(session)===-1&&(session.streamid||(session.streamid=session.id),connection.attachStreams.push(session)),void connection.renegotiate(remoteUserId)):isData(session)?void connection.renegotiate(remoteUserId):void((session.audio||session.video||session.screen)&&(session.screen?"Edge"===DetectRTC.browser.name?navigator.getDisplayMedia({video:!0,audio:isAudioPlusTab(connection)}).then(function(screen){screen.isScreen=!0,mPeer.onGettingLocalMedia(screen),!session.audio&&!session.video||isAudioPlusTab(connection)?gumCallback(screen):connection.invokeGetUserMedia(null,function(stream){gumCallback(stream)})},function(error){console.error("Unable to capture screen on Edge. HTTPs and version 17+ is required.")}):connection.getScreenConstraints(function(error,screen_constraints){return error?"PermissionDeniedError"===error?(session.streamCallback&&session.streamCallback(null),void(connection.enableLogs&&console.error("User rejected to share his screen."))):alert(error):void connection.invokeGetUserMedia({audio:!!isAudioPlusTab(connection)&&getAudioScreenConstraints(screen_constraints),video:screen_constraints,isScreen:!0},function(stream){!session.audio&&!session.video||isAudioPlusTab(connection)?gumCallback(stream):connection.invokeGetUserMedia(null,function(stream){gumCallback(stream)})})}):(session.audio||session.video)&&connection.invokeGetUserMedia(null,gumCallback)))},connection.invokeGetUserMedia=function(localMediaConstraints,callback,session){session||(session=connection.session),localMediaConstraints||(localMediaConstraints=connection.mediaConstraints),getUserMediaHandler({onGettingLocalMedia:function(stream){var videoConstraints=localMediaConstraints.video;videoConstraints&&(videoConstraints.mediaSource||videoConstraints.mozMediaSource?stream.isScreen=!0:videoConstraints.mandatory&&videoConstraints.mandatory.chromeMediaSource&&(stream.isScreen=!0)),stream.isScreen||(stream.isVideo=stream.getVideoTracks().length,stream.isAudio=!stream.isVideo&&stream.getAudioTracks().length),mPeer.onGettingLocalMedia(stream,function(){"function"==typeof callback&&callback(stream)})},onLocalMediaError:function(error,constraints){mPeer.onLocalMediaError(error,constraints)},localMediaConstraints:localMediaConstraints||{audio:!!session.audio&&localMediaConstraints.audio,video:!!session.video&&localMediaConstraints.video}})},connection.applyConstraints=function(mediaConstraints,streamid){if(!MediaStreamTrack||!MediaStreamTrack.prototype.applyConstraints)return void alert("track.applyConstraints is NOT supported in your browser.");if(streamid){var stream;return connection.streamEvents[streamid]&&(stream=connection.streamEvents[streamid].stream),void applyConstraints(stream,mediaConstraints)}connection.attachStreams.forEach(function(stream){applyConstraints(stream,mediaConstraints)})},connection.replaceTrack=function(session,remoteUserId,isVideoTrack){function gumCallback(stream){connection.replaceTrack(stream,remoteUserId,isVideoTrack||session.video||session.screen)}if(session=session||{},!RTCPeerConnection.prototype.getSenders)return void connection.addStream(session);if(session instanceof MediaStreamTrack)return void replaceTrack(session,remoteUserId,isVideoTrack);if(session instanceof MediaStream)return session.getVideoTracks().length&&replaceTrack(session.getVideoTracks()[0],remoteUserId,!0),void(session.getAudioTracks().length&&replaceTrack(session.getAudioTracks()[0],remoteUserId,!1));if(isData(session))throw"connection.replaceTrack requires audio and/or video and/or screen.";(session.audio||session.video||session.screen)&&(session.screen?"Edge"===DetectRTC.browser.name?navigator.getDisplayMedia({video:!0,audio:isAudioPlusTab(connection)}).then(function(screen){screen.isScreen=!0,mPeer.onGettingLocalMedia(screen),!session.audio&&!session.video||isAudioPlusTab(connection)?gumCallback(screen):connection.invokeGetUserMedia(null,gumCallback)},function(error){console.error("Unable to capture screen on Edge. HTTPs and version 17+ is required.")}):connection.getScreenConstraints(function(error,screen_constraints){return error?alert(error):void connection.invokeGetUserMedia({audio:!!isAudioPlusTab(connection)&&getAudioScreenConstraints(screen_constraints),video:screen_constraints,isScreen:!0},!session.audio&&!session.video||isAudioPlusTab(connection)?gumCallback:connection.invokeGetUserMedia(null,gumCallback))}):(session.audio||session.video)&&connection.invokeGetUserMedia(null,gumCallback))},connection.resetTrack=function(remoteUsersIds,isVideoTrack){remoteUsersIds||(remoteUsersIds=connection.getAllParticipants()),"string"==typeof remoteUsersIds&&(remoteUsersIds=[remoteUsersIds]),remoteUsersIds.forEach(function(participant){var peer=connection.peers[participant].peer;"undefined"!=typeof isVideoTrack&&isVideoTrack!==!0||!peer.lastVideoTrack||connection.replaceTrack(peer.lastVideoTrack,participant,!0),"undefined"!=typeof isVideoTrack&&isVideoTrack!==!1||!peer.lastAudioTrack||connection.replaceTrack(peer.lastAudioTrack,participant,!1)})},connection.renegotiate=function(remoteUserId){return remoteUserId?void mPeer.renegotiatePeer(remoteUserId):void connection.peers.getAllParticipants().forEach(function(participant){mPeer.renegotiatePeer(participant)})},connection.setStreamEndHandler=function(stream,isRemote){if(stream&&stream.addEventListener&&(isRemote=!!isRemote,!stream.alreadySetEndHandler)){stream.alreadySetEndHandler=!0;var streamEndedEvent="ended";"oninactive"in stream&&(streamEndedEvent="inactive"),stream.addEventListener(streamEndedEvent,function(){if(stream.idInstance&¤tUserMediaRequest.remove(stream.idInstance),!isRemote){var streams=[];connection.attachStreams.forEach(function(s){s.id!=stream.id&&streams.push(s)}),connection.attachStreams=streams}var streamEvent=connection.streamEvents[stream.streamid];if(streamEvent||(streamEvent={stream:stream,streamid:stream.streamid,type:isRemote?"remote":"local",userid:connection.userid,extra:connection.extra,mediaElement:connection.streamEvents[stream.streamid]?connection.streamEvents[stream.streamid].mediaElement:null}),isRemote&&connection.peers[streamEvent.userid]){var peer=connection.peers[streamEvent.userid].peer,streams=[];peer.getRemoteStreams().forEach(function(s){s.id!=stream.id&&streams.push(s)}),connection.peers[streamEvent.userid].streams=streams}streamEvent.userid===connection.userid&&"remote"===streamEvent.type||(connection.peersBackup[streamEvent.userid]&&(streamEvent.extra=connection.peersBackup[streamEvent.userid].extra),connection.onstreamended(streamEvent),delete connection.streamEvents[stream.streamid])},!1)}},connection.onMediaError=function(error,constraints){connection.enableLogs&&console.error(error,constraints)},connection.addNewBroadcaster=function(broadcasterId,userPreferences){connection.socket.isIO||(connection.broadcasters.length&&setTimeout(function(){mPeer.connectNewParticipantWithAllBroadcasters(broadcasterId,userPreferences,connection.broadcasters.join("|-,-|"))},1e4),connection.session.oneway||connection.session.broadcast||"many-to-many"!==connection.direction||connection.broadcasters.indexOf(broadcasterId)!==-1||(connection.broadcasters.push(broadcasterId),keepNextBroadcasterOnServer()))},connection.autoCloseEntireSession=!1,connection.filesContainer=connection.videosContainer=document.body||document.documentElement,connection.isInitiator=!1,connection.shareFile=mPeer.shareFile,"undefined"!=typeof FileProgressBarHandler&&FileProgressBarHandler.handle(connection),"undefined"!=typeof TranslationHandler&&TranslationHandler.handle(connection),connection.token=getRandomString,connection.onNewParticipant=function(participantId,userPreferences){connection.acceptParticipationRequest(participantId,userPreferences)},connection.acceptParticipationRequest=function(participantId,userPreferences){userPreferences.successCallback&&(userPreferences.successCallback(),delete userPreferences.successCallback),mPeer.createNewPeer(participantId,userPreferences)},connection.onShiftedModerationControl=function(sender,existingBroadcasters){connection.acceptModerationControl(sender,existingBroadcasters)},connection.acceptModerationControl=function(sender,existingBroadcasters){connection.isInitiator=!0,connection.broadcasters=existingBroadcasters,connection.peers.getAllParticipants().forEach(function(participant){mPeer.onNegotiationNeeded({changedUUID:sender,oldUUID:connection.userid,newUUID:sender},participant)}),connection.userid=sender,connection.changeUserId(connection.userid)},connection.shiftModerationControl=function(remoteUserId,existingBroadcasters,firedOnLeave){mPeer.onNegotiationNeeded({shiftedModerationControl:!0,broadcasters:existingBroadcasters,firedOnLeave:!!firedOnLeave},remoteUserId)},"undefined"!=typeof StreamsHandler&&(connection.StreamsHandler=StreamsHandler),connection.onleave=function(userid){},connection.invokeSelectFileDialog=function(callback){var selector=new FileSelector;selector.accept="*.*",selector.selectSingleFile(callback)},connection.getPublicModerators=connection.getPublicUsers=function(userIdStartsWith,callback){"function"==typeof userIdStartsWith&&(callback=userIdStartsWith),connectSocket(function(){connection.socket.emit("get-public-moderators","string"==typeof userIdStartsWith?userIdStartsWith:"",callback)})},connection.onmute=function(e){if(e&&e.mediaElement)if("both"===e.muteType||"video"===e.muteType){e.mediaElement.src=null;var paused=e.mediaElement.pause();"undefined"!=typeof paused?paused.then(function(){e.mediaElement.poster=e.snapshot||"https://cdn.webrtc-experiment.com/images/muted.png"}):e.mediaElement.poster=e.snapshot||"https://cdn.webrtc-experiment.com/images/muted.png"}else"audio"===e.muteType&&(e.mediaElement.muted=!0)},connection.onunmute=function(e){e&&e.mediaElement&&e.stream&&("both"===e.unmuteType||"video"===e.unmuteType?(e.mediaElement.poster=null,e.mediaElement.srcObject=e.stream,e.mediaElement.play()):"audio"===e.unmuteType&&(e.mediaElement.muted=!1))},connection.onExtraDataUpdated=function(event){event.status="online",connection.onUserStatusChanged(event,!0)},connection.onJoinWithPassword=function(remoteUserId){console.warn(remoteUserId,"is password protected. Please join with password.")},connection.onInvalidPassword=function(remoteUserId,oldPassword){console.warn(remoteUserId,"is password protected. Please join with valid password. Your old password",oldPassword,"is wrong.")},connection.onPasswordMaxTriesOver=function(remoteUserId){console.warn(remoteUserId,"is password protected. Your max password tries exceeded the limit.")},connection.getAllParticipants=function(sender){return connection.peers.getAllParticipants(sender)},"undefined"!=typeof StreamsHandler&&(StreamsHandler.onSyncNeeded=function(streamid,action,type){connection.peers.getAllParticipants().forEach(function(participant){mPeer.onNegotiationNeeded({streamid:streamid,action:action,streamSyncNeeded:!0,type:type||"both"},participant)})}),connection.connectSocket=function(callback){connectSocket(callback)},connection.closeSocket=function(){try{io.sockets={}}catch(e){}connection.socket&&("function"==typeof connection.socket.disconnect&&connection.socket.disconnect(),"function"==typeof connection.socket.resetProps&&connection.socket.resetProps(),connection.socket=null)},connection.getSocket=function(callback){return connection.socket?callback&&callback(connection.socket):connectSocket(callback),connection.socket},connection.getRemoteStreams=mPeer.getRemoteStreams;var skipStreams=["selectFirst","selectAll","forEach"];if(connection.streamEvents={selectFirst:function(options){return connection.streamEvents.selectAll(options)[0]},selectAll:function(options){options||(options={local:!0,remote:!0,isScreen:!0,isAudio:!0,isVideo:!0}),"local"==options&&(options={local:!0}),"remote"==options&&(options={remote:!0}),"screen"==options&&(options={isScreen:!0}),"audio"==options&&(options={isAudio:!0}),"video"==options&&(options={isVideo:!0});var streams=[];return Object.keys(connection.streamEvents).forEach(function(key){var event=connection.streamEvents[key];if(skipStreams.indexOf(key)===-1){var ignore=!0;options.local&&"local"===event.type&&(ignore=!1),options.remote&&"remote"===event.type&&(ignore=!1),options.isScreen&&event.stream.isScreen&&(ignore=!1),options.isVideo&&event.stream.isVideo&&(ignore=!1),options.isAudio&&event.stream.isAudio&&(ignore=!1),options.userid&&event.userid===options.userid&&(ignore=!1),ignore===!1&&streams.push(event)}}),streams}},connection.socketURL="/",connection.socketMessageEvent="RTCMultiConnection-Message",connection.socketCustomEvent="RTCMultiConnection-Custom-Message",connection.DetectRTC=DetectRTC,connection.setCustomSocketEvent=function(customEvent){customEvent&&(connection.socketCustomEvent=customEvent),connection.socket&&connection.socket.emit("set-custom-socket-event-listener",connection.socketCustomEvent)},connection.getNumberOfBroadcastViewers=function(broadcastId,callback){connection.socket&&broadcastId&&callback&&connection.socket.emit("get-number-of-users-in-specific-broadcast",broadcastId,callback)},connection.onNumberOfBroadcastViewersUpdated=function(event){connection.enableLogs&&connection.isInitiator&&console.info("Number of broadcast (",event.broadcastId,") viewers",event.numberOfBroadcastViewers)},connection.onUserStatusChanged=function(event,dontWriteLogs){connection.enableLogs&&!dontWriteLogs&&console.info(event.userid,event.status)},connection.getUserMediaHandler=getUserMediaHandler,connection.multiPeersHandler=mPeer,connection.enableLogs=!0,connection.setCustomSocketHandler=function(customSocketHandler){"undefined"!=typeof SocketConnection&&(SocketConnection=customSocketHandler)},connection.chunkSize=65e3,connection.maxParticipantsAllowed=1e3,connection.disconnectWith=mPeer.disconnectWith,connection.checkPresence=function(remoteUserId,callback){return"SSEConnection"===SocketConnection.name?void SSEConnection.checkPresence(remoteUserId,function(isRoomExist,roomid){return connection.socket?void callback(isRoomExist,roomid):(isRoomExist||(connection.userid=roomid),void connection.connectSocket(function(){callback(isRoomExist,roomid)}))}):connection.socket?void connection.socket.emit("check-presence",(remoteUserId||connection.sessionid)+"",callback):void connection.connectSocket(function(){connection.checkPresence(remoteUserId,callback)})},connection.onReadyForOffer=function(remoteUserId,userPreferences){connection.multiPeersHandler.createNewPeer(remoteUserId,userPreferences)},connection.setUserPreferences=function(userPreferences){return connection.dontAttachStream&&(userPreferences.dontAttachLocalStream=!0),connection.dontGetRemoteStream&&(userPreferences.dontGetRemoteStream=!0),userPreferences},connection.updateExtraData=function(){connection.socket.emit("extra-data-updated",connection.extra)},connection.enableScalableBroadcast=!1,connection.maxRelayLimitPerUser=3,connection.dontCaptureUserMedia=!1,connection.dontAttachStream=!1,connection.dontGetRemoteStream=!1,connection.onReConnecting=function(event){connection.enableLogs&&console.info("ReConnecting with",event.userid,"...")},connection.beforeAddingStream=function(stream){return stream},connection.beforeRemovingStream=function(stream){return stream},"undefined"!=typeof isChromeExtensionAvailable&&(connection.checkIfChromeExtensionAvailable=isChromeExtensionAvailable),"undefined"!=typeof isFirefoxExtensionAvailable&&(connection.checkIfChromeExtensionAvailable=isFirefoxExtensionAvailable),"undefined"!=typeof getChromeExtensionStatus&&(connection.getChromeExtensionStatus=getChromeExtensionStatus),connection.getScreenConstraints=function(callback,audioPlusTab){isAudioPlusTab(connection,audioPlusTab)&&(audioPlusTab=!0),getScreenConstraints(function(error,screen_constraints){error||(screen_constraints=connection.modifyScreenConstraints(screen_constraints),callback(error,screen_constraints))},audioPlusTab)},connection.modifyScreenConstraints=function(screen_constraints){return screen_constraints},connection.onPeerStateChanged=function(state){connection.enableLogs&&state.iceConnectionState.search(/closed|failed/gi)!==-1&&console.error("Peer connection is closed between you & ",state.userid,state.extra,"state:",state.iceConnectionState)},connection.isOnline=!0,listenEventHandler("online",function(){connection.isOnline=!0}),listenEventHandler("offline",function(){connection.isOnline=!1}),connection.isLowBandwidth=!1,navigator&&navigator.connection&&navigator.connection.type&&(connection.isLowBandwidth=navigator.connection.type.toString().toLowerCase().search(/wifi|cell/g)!==-1,connection.isLowBandwidth)){if(connection.bandwidth={audio:!1,video:!1,screen:!1},connection.mediaConstraints.audio&&connection.mediaConstraints.audio.optional&&connection.mediaConstraints.audio.optional.length){var newArray=[];connection.mediaConstraints.audio.optional.forEach(function(opt){"undefined"==typeof opt.bandwidth&&newArray.push(opt)}),connection.mediaConstraints.audio.optional=newArray}if(connection.mediaConstraints.video&&connection.mediaConstraints.video.optional&&connection.mediaConstraints.video.optional.length){var newArray=[];connection.mediaConstraints.video.optional.forEach(function(opt){"undefined"==typeof opt.bandwidth&&newArray.push(opt)}),connection.mediaConstraints.video.optional=newArray}}connection.getExtraData=function(remoteUserId){if(!remoteUserId)throw"remoteUserId is required.";return connection.peers[remoteUserId]?connection.peers[remoteUserId].extra:{}},forceOptions.autoOpenOrJoin&&connection.openOrJoin(connection.sessionid),connection.onUserIdAlreadyTaken=function(useridAlreadyTaken,yourNewUserId){connection.enableLogs&&console.warn("Userid already taken.",useridAlreadyTaken,"Your new userid:",yourNewUserId),connection.join(useridAlreadyTaken)},connection.onRoomFull=function(roomid){connection.enableLogs&&console.warn(roomid,"is full.")},connection.trickleIce=!0,connection.version="3.4.7",connection.onSettingLocalDescription=function(event){connection.enableLogs&&console.info("Set local description for remote user",event.userid)},connection.oneRoomAlreadyExist=function(roomid){connection.enableLogs&&console.info('Server says "Room ',roomid,"already exist. Joining instead."), +connection.join(roomid)},connection.resetScreen=function(){sourceId=null,DetectRTC&&DetectRTC.screen&&delete DetectRTC.screen.sourceId,currentUserMediaRequest={streams:[],mutex:!1,queueRequests:[]}},connection.autoCreateMediaElement=!0,connection.password=null}(this)}; \ No newline at end of file diff --git a/RTCMultiConnection-SignalR/RTCMultiConnection/index.html b/RTCMultiConnection-SignalR/RTCMultiConnection/index.html index d9e99ae6..02f7af86 100644 --- a/RTCMultiConnection-SignalR/RTCMultiConnection/index.html +++ b/RTCMultiConnection-SignalR/RTCMultiConnection/index.html @@ -62,8 +62,8 @@

    } - - + + diff --git a/RTCMultiConnection/.gitignore b/RTCMultiConnection/.gitignore index f6aed524..c28060af 100755 --- a/RTCMultiConnection/.gitignore +++ b/RTCMultiConnection/.gitignore @@ -1,11 +1,19 @@ +rmc-debug.log +.sass-cache +.jekyll-metadata +*.iml +.idea/* +_site +Gemfile.lock +*.pyc +dist/ +.vscode/ +v2.2.2 node_modules bower_components - make-tar.sh - *.tar.gz lib-cov - .*.swp ._* .DS_Store @@ -18,4 +26,3 @@ lib-cov config.gypi CVS npm-debug.log -rmc-debug.log \ No newline at end of file diff --git a/RTCMultiConnection/.npmignore b/RTCMultiConnection/.npmignore index 14d9cb6b..91bfc9e4 100755 --- a/RTCMultiConnection/.npmignore +++ b/RTCMultiConnection/.npmignore @@ -1,22 +1,12 @@ -node_modules -bower_components +# ignore everything +* -make-tar.sh - -*.tar.gz -lib-cov -v2.2.2 - -.*.swp -._* -.DS_Store -.git -.hg -.npmrc -.lock-wscript -.svn -.wafpickle-* -config.gypi -CVS -npm-debug.log -rmc-debug.log \ No newline at end of file +# but not these files... +!dist/RTCMultiConnection.js +!dist/RTCMultiConnection.min.js +!package.json +!logs.json +!config.json +!npm-test.js +!server.js +!README.md diff --git a/RTCMultiConnection/Gruntfile.js b/RTCMultiConnection/Gruntfile.js index eeebf2e5..f8c3143d 100755 --- a/RTCMultiConnection/Gruntfile.js +++ b/RTCMultiConnection/Gruntfile.js @@ -36,12 +36,13 @@ module.exports = function(grunt) { dist: { src: [ 'dev/head.js', + 'dev/amd.js', 'dev/SocketConnection.js', // You can replace it with: FirebaseConnection.js || PubNubConnection.js 'dev/MultiPeersHandler.js', // 'dev/adapter.js', ---- optional - 'dev/DetectRTC.js', + 'node_modules/detectrtc/DetectRTC.js', // npm install detectrtc 'dev/globals.js', 'dev/ios-hacks.js', // to support ios @@ -54,7 +55,7 @@ module.exports = function(grunt) { 'dev/getUserMedia.js', 'dev/StreamsHandler.js', - 'dev/Screen-Capturing.js', + 'node_modules/webrtc-screen-capturing/Screen-Capturing.js', // npm install webrtc-screen-capturing 'dev/TextSenderReceiver.js', 'dev/FileProgressBarHandler.js', @@ -108,7 +109,7 @@ module.exports = function(grunt) { }, }, jsbeautifier: { - files: ['RTCMultiConnection.js', 'dev/*.js', 'Gruntfile.js', 'node_scripts/*.js'], + files: ['RTCMultiConnection.js', 'dev/*.js', 'Gruntfile.js', 'node_scripts/*.js', 'admin/js/admin-ui.js'], options: { js: { braceStyle: "collapse", diff --git a/RTCMultiConnection/README.md b/RTCMultiConnection/README.md index 5448486c..89c139c6 100755 --- a/RTCMultiConnection/README.md +++ b/RTCMultiConnection/README.md @@ -27,6 +27,16 @@ connection.socketURL = 'https://rtcmulticonnection.herokuapp.com:443/'; connection.socketURL = 'https://webrtcweb.com:9001/'; ``` +## Install ONLY socket.io server + +```sh +npm install rtcmulticonnection-server +``` + +For more info, please check: + +* https://github.com/muaz-khan/RTCMultiConnection-Server + ## YouTube videos 1. [Getting started guide / RTCMultiConnection](https://www.youtube.com/watch?v=jqtC7mSTCgk) @@ -47,12 +57,9 @@ connection.socketURL = 'https://webrtcweb.com:9001/'; ## iOS+Android Demo Apps -> Note: RTCMultiConnection supports Safari-11 browser both on iOS and MacOSX. -> -> So you do not need to build a cordova or ionic application. - -* https://webrtcweb.com/cordova-apps/ +* https://github.com/muaz-khan/cordova-mobile-apps +> Note: RTCMultiConnection supports Safari-11 and Edge. ## Wiki Pages diff --git a/RTCMultiConnection/admin/admin-ui.js b/RTCMultiConnection/admin/admin-ui.js new file mode 100644 index 00000000..c17fa982 --- /dev/null +++ b/RTCMultiConnection/admin/admin-ui.js @@ -0,0 +1,461 @@ +var socket; + +$('#adminPasswordModel').modal({ + backdrop: 'static', + keyboard: false +}); + +$('.close-admin-credentials-box').click(function() { + location.reload(); +}); + +$('#btn-validate-admin').click(function() { + var adminUserName = $('#txt-admin-username').val(); + var adminPassword = $('#txt-admin-password').val(); + + if(!adminUserName || !adminPassword || !adminUserName.replace(/ /g, '').length || !adminPassword.replace(/ /g, '').length) { + alertBox('Admin username and password is required.', 'Invalid Credentials', null, function() { + location.reload(); + }); + return; + } + + $('#btn-validate-admin').html('Please wait...').prop('disabled', 'disabled').addClass('disabled'); + + connectSocket(adminUserName, adminPassword); +}); + +setTimeout(function() { + $('#txt-admin-username').focus(); +}, 500); + +function connectSocket(username, password) { + socket = io.connect('/?userid=admin&adminUserName=' + username + '&adminPassword=' + password); + socket.on('admin', function(message) { + if(message.error) { + alertBox(message.error, 'Invalid Credentials', null, function() { + location.reload(); + }); + return; + } + + if(message.connected === true) { + $('#adminPasswordModel').modal('hide'); + return; + } + + socket.isAdminConnected = true; + + if (message.newUpdates === true) { + if (socket.auto_update === true) { + socket.emit('admin', { + all: true + }); + return; + } + $('.new-updates-notifier').show(); + } else { + updateListOfRooms(message.listOfRooms || []); + // updateListOfUsers(message.listOfUsers || []); + } + + $('#active-users').html(message.listOfUsers || 0); + $('#scalable-users').html(message.scalableBroadcastUsers || 0); + // $('#all-sockts').html(message.allSockets || 0); + }); + $('.new-updates-notifier a').click(function(e) { + e.preventDefault(); + $('.new-updates-notifier').hide(); + socket.emit('admin', { + all: true + }); + }); + $('.new-updates-notifier input').click(function() { + socket.auto_update = true; + $('.new-updates-notifier a').click(); + }); + socket.on('connect', function() { + socket.emit('admin', { + all: true + }); + }); + socket.on('disconnect', function() { + if(socket.isAdminConnected === true) { + location.reload(); + } + }); +} + +function updateListOfUsers(listOfUsers) { + $('#active-users').html(listOfUsers.length); + + $('#users-list').html(''); + + if (!listOfUsers.length) { + $('#users-list').html('No active user found on this server.'); + return; + } + + listOfUsers.forEach(function(user, idx) { + var tr = document.createElement('tr'); + var html = ''; + + html += '' + (idx + 1) + ''; + html += '' + user.userid + ''; + html += '' + user.admininfo.sessionid + ''; + html += '' + JSON.stringify(user.admininfo.session) + ''; + html += '' + JSON.stringify(user.admininfo.extra) + ''; + + if (true) { + // stream-ids + html += ''; + html += ''; + html += ''; + html += ''; + html += ''; + html += ''; + html += ''; + + (user.admininfo.streams || []).forEach(function(stream) { + html += ''; + html += ''; + html += ''; + html += ''; + }); + html += ''; + html += '
    streamidtracks
    ' + stream.streamid + '' + stream.tracks + '
    '; + html += ''; + } + + html += '' + JSON.stringify(user.admininfo.mediaConstraints) + ''; + html += '' + JSON.stringify(user.admininfo.sdpConstraints) + ''; + html += '' + JSON.stringify(user.connectedWith) + ''; + + html += ''; + $(tr).html(html); + $('#users-list').append(tr); + + $(tr).find('.clickable').click(function() { + $('#txt-userid').val($(this).attr('data-userid')); + $('#view-userinfo').click(); + }); + + $(tr).find('.delete-user').click(function() { + var userid = $(this).attr('data-userid'); + confirmBox('User "' + userid + '" will be deleted from server.', function(isConfirmed) { + if (!isConfirmed) return; + + socket.emit('admin', { + deleteUser: true, + userid: userid + }, function(isDeleted) { + if (isDeleted) { + $('.bd-example-modal-lg').modal('hide'); + + socket.emit('admin', { + all: true + }); + } else { + alertBox('Unable to delete this user.', 'Can Not Delete'); + } + }); + }); + }); + }); +} + +function updateListOfRooms(rooms) { + var keys = Object.keys(rooms); + $('#active-rooms').html(keys.length); + + $('#rooms-list').html(''); + + if (!keys.length) { + $('#rooms-list').html('No active room found on this server.'); + return; + } + + keys.forEach(function(roomid, idx) { + var room = rooms[roomid]; + var tr = document.createElement('tr'); + var html = ''; + html += '' + (idx + 1) + ''; + html += '' + roomid + ''; + html += '' + room.owner + ''; + html += '' + (room.identifier || 'None') + ''; + html += '' + room.socketMessageEvent + ''; + + html += ''; + Object.keys(room.session || {}).forEach(function(key) { + html += '
    ' + key + ': ' + room.session[key] + '
    '; + }); + html += ''; + + html += '' + JSON.stringify(room.extra || {}) + ''; + + html += ''; + room.participants.forEach(function(pid) { + html += '' + pid + '
    '; + }); + html += ''; + + html += ''; + $(tr).html(html); + $('#rooms-list').append(tr); + + $(tr).find('.clickable').click(function() { + $('#txt-userid').val($(this).attr('data-userid')); + $('#view-userinfo').click(); + }); + + $(tr).find('.delete-room').click(function() { + var roomid = $(this).attr('data-roomid'); + confirmBox('Room "' + roomid + '" will be deleted from server. It will disconnect and remove its participants as well.', function(isConfirmed) { + if (!isConfirmed) return; + + socket.emit('admin', { + deleteRoom: true, + roomid: roomid + }, function(isDeleted) { + if (isDeleted) { + socket.emit('admin', { + all: true + }); + } else { + alertBox('Unable to delete this room.', 'Can Not Delete'); + } + }); + }) + }); + }); +} + +function updateViewLogsButton() { + return; + + var req = new XMLHttpRequest(); + req.open('GET', 'logs.json'); + req.onload = function() { + var json = JSON.parse(req.responseText); + var length = Object.keys(json).length; + if (length) { + $('#view-logs').html('Error Logs (' + length + ')'); + } else { + $('#view-logs').html('Error Logs'); + } + }; + req.send(); +} +// updateViewLogsButton(); + +$('#view-logs').click(function() { + return alertBox('This feature is temporarily disabled.'); + + $('#view-logs').html('Loading...'); + $('#logs-viewer').html('Loading...'); + + var req = new XMLHttpRequest(); + req.open('GET', 'logs.json'); + req.onload = function() { + var json = JSON.parse(req.responseText); + $('#view-logs').html('Error Logs'); + updateViewLogsButton(); + + var table = document.createElement('table'); + table.className = 'table'; + var html = ''; + html += ''; + html += 'Event/Method'; + html += 'Error Message'; + html += 'Error Stack'; + html += 'Date/Time'; + html += ''; + html += ''; + + var keys = Object.keys(json); + keys.forEach(function(key) { + var item = json[key]; + + html += ''; + html += '' + item.name + ''; + html += '' + item.message + ''; + html += '
    ' + item.stack + '
    '; + html += '' + item.date + ''; + html += ''; + }); + + if (!keys.length) { + html += 'Error logs file is empty.'; + } + html += ''; + $(table).html(html); + + if (keys.length) { + $('#logs-viewer').html('
    (permanently delete from logs.json)
    '); + $('#logs-viewer').append(table); + } else { + $('#logs-viewer').html('
    Error logs file is empty.
    '); + } + + if (keys.length) { + $('#clear-logs').click(function() { + $('#logs-viewer').html('
    Deleting...
    '); + socket.emit('admin', { + clearLogs: true + }, function(isCleared) { + if (isCleared) { + $('#logs-viewer').html('
    Error logs are cleared from logs.json.
    '); + updateViewLogsButton(); + } else { + $('#logs-viewer').html(isCleared); + } + }); + }); + } + }; + req.send(); +}); + +function getUserInfo(userid, callback) { + socket.emit('admin', { + userid: userid, + userinfo: true + }, function(userinfo) { + if (userinfo.error) { + var div = document.createElement('div'); + $(div).css({ + textAlign: 'center', + marginBottom: 10 + }); + $(div).html(userinfo.error); + callback(div); + return; + } + + var table = document.createElement('table'); + table.className = 'table'; + var html = ''; + html += 'userid' + userid + ''; + ['sessionid', 'session', 'extra', 'mediaConstraints', 'sdpConstraints', 'streams'].forEach(function(item) { + html += ''; + html += '' + (item === 'sessionid' ? 'roomid' : item) + ''; + if (typeof userinfo[item] === 'string') { + html += '' + userinfo[item] + ''; + } else { + if (item === 'streams' && userinfo[item]) { + html += ''; + html += ''; + html += ''; + html += ''; + html += ''; + html += ''; + html += ''; + + userinfo[item].forEach(function(stream) { + html += ''; + html += ''; + html += ''; + html += ''; + }); + html += ''; + html += '
    streamidtracks
    ' + stream.streamid + '' + stream.tracks + '
    '; + html += ''; + } else { + html += '' + JSON.stringify(userinfo[item] || '') + ''; + } + } + html += ''; + }); + $(table).html(html); + $(table).find('.delete-user').click(function() { + var that = this; + + var userid = $(this).attr('data-userid'); + confirmBox('User "' + userid + '" will be deleted from server.', function(isConfirmed) { + if (!isConfirmed) return; + + $(that).prop('disabled', true).html('Deleting...'); + + socket.emit('admin', { + deleteUser: true, + userid: userid + }, function(isDeleted) { + if (isDeleted) { + $('.bd-example-modal-lg').modal('hide'); + + socket.emit('admin', { + all: true + }); + } else { + alertBox('Unable to delete this user.', 'Can Not Delete'); + } + }); + }); + }); + callback(table); + }); +} + +function alertBox(message, title, specialMessage, callback) { + callback = callback || function() {}; + + $('.btn-alert-close').unbind('click').bind('click', function(e) { + e.preventDefault(); + $('#alert-box').modal('hide'); + $('#confirm-box-topper').hide(); + + callback(); + }); + + $('#alert-title').html(title || 'Alert'); + $('#alert-special').html(specialMessage || ''); + $('#alert-message').html(message); + $('#confirm-box-topper').show(); + + $('#alert-box').modal({ + backdrop: 'static', + keyboard: false + }); +} + +function confirmBox(message, callback) { + $('#btn-confirm-action').html('Confirm').unbind('click').bind('click', function(e) { + e.preventDefault(); + $('#confirm-box').modal('hide'); + $('#confirm-box-topper').hide(); + callback(true); + }); + + $('#btn-confirm-close').html('Cancel'); + + $('.btn-confirm-close').unbind('click').bind('click', function(e) { + e.preventDefault(); + $('#confirm-box').modal('hide'); + $('#confirm-box-topper').hide(); + callback(false); + }); + + $('#confirm-message').html(message); + $('#confirm-title').html('Please Confirm'); + $('#confirm-box-topper').show(); + + $('#confirm-box').modal({ + backdrop: 'static', + keyboard: false + }); +} + +$('#view-userinfo').click(function() { + var userid = $('#txt-userid').val(); + if (!userid || !userid.replace(/ /g, '').length) return; + + $('#view-userinfo').prop('disabled', true); + $('#logs-viewer').html('Loading...'); + getUserInfo(userid, function(table) { + $('#logs-viewer').html('').append(table); + $('#view-userinfo').prop('disabled', false); + }); + + $('#exampleModal').modal('hide'); + $('.bd-example-modal-lg').modal('show'); +}); diff --git a/RTCMultiConnection/admin/index.html b/RTCMultiConnection/admin/index.html index a5626cd7..38e1eb59 100644 --- a/RTCMultiConnection/admin/index.html +++ b/RTCMultiConnection/admin/index.html @@ -1,4 +1,4 @@ - + @@ -9,20 +9,13 @@ - - - - + + + + + +
    + There are new updates. Do you want to refresh? or Refresh +
    +
    - + Admin
    - Active rooms: 0 - Error Logs + Signaling-Server ( users: 0, rooms: 0 ) + Scalable-Broadcast ( users: 0 ) + + Error Logs
    @@ -371,6 +418,38 @@

    + +
    @@ -378,320 +457,40 @@ + + - + - - + +
    # Room ID Owner IDIdentifiersocketMessageEvent SessionPublic Room?Extra ParticipantsMax Participants Allowed Delete
    No active room found on this server.
    +
    + +
    + + + + + + + + + + + + + + + +
    #User IDRoom IDSessionExtrastreamsmediaConstraintssdpConstraintsconnectedWithDelete
    No active users found on this server.
    + +
    RTCMultiConnection | Admin Page
    - - -
    RTCMultiConnection | Admin Page
    + diff --git a/RTCMultiConnection/bower.json b/RTCMultiConnection/bower.json index 58cd43cc..1c02ee1a 100755 --- a/RTCMultiConnection/bower.json +++ b/RTCMultiConnection/bower.json @@ -1,7 +1,7 @@ { "name": "rtcmulticonnection", "description": "RTCMultiConnection is a WebRTC JavaScript wrapper library runs top over RTCPeerConnection API to provide multi-session establishment scenarios.", - "version": "3.4.8", + "version": "3.5.9", "authors": [ { "name": "Muaz Khan", @@ -34,7 +34,7 @@ "type": "git", "url": "https://github.com/muaz-khan/RTCMultiConnection.git" }, - "homepage": "http://www.rtcmulticonnection.org", + "homepage": "https://rtcmulticonnection.herokuapp.com/demos/", "ignore": [ "**/.*", "node_modules", diff --git a/RTCMultiConnection/config.json b/RTCMultiConnection/config.json index 5b70bfe4..0730bfa6 100644 --- a/RTCMultiConnection/config.json +++ b/RTCMultiConnection/config.json @@ -1,15 +1,17 @@ { "socketURL": "/", - "defaultDemo": "/demos/index.html", + "dirPath": "", + "homePage": "/demos/index.html", "socketMessageEvent": "RTCMultiConnection-Message", "socketCustomEvent": "RTCMultiConnection-Custom-Message", "port": "9001", - "enableLogs": "true", - "autoRebootServerOnFailure": "true", + "enableLogs": "false", + "autoRebootServerOnFailure": "false", "isUseHTTPs": "false", "sslKey": "./fake-keys/privatekey.pem", "sslCert": "./fake-keys/certificate.pem", "sslCabundle": "", + "enableAdmin": "false", "adminUserName": "username", "adminPassword": "password" } diff --git a/RTCMultiConnection/demos/Audio+ScreenSharing.html b/RTCMultiConnection/demos/Audio+ScreenSharing.html index 85a0bcc6..d5dbf103 100755 --- a/RTCMultiConnection/demos/Audio+ScreenSharing.html +++ b/RTCMultiConnection/demos/Audio+ScreenSharing.html @@ -87,7 +87,7 @@

    Audio+ScreenSharing using RTCMultiConnection

    - + diff --git a/RTCMultiConnection/demos/Audio+Video+TextChat+FileSharing.html b/RTCMultiConnection/demos/Audio+Video+TextChat+FileSharing.html index 7bdbdb81..f7a56dcc 100755 --- a/RTCMultiConnection/demos/Audio+Video+TextChat+FileSharing.html +++ b/RTCMultiConnection/demos/Audio+Video+TextChat+FileSharing.html @@ -68,14 +68,14 @@

    - + - + - + @@ -224,10 +224,25 @@

    } -
    -

    iOS or Android

    +

    - You can write iOS or Android apps for this demo as well. + You can write cordova/ionic/phonegap based iOS or Android apps for this demo as well. + + You can download an open-sourced cordova app both for iOS and Android as well. +

    + +

    + You can run same demo on Safari-11 as well. (both on MacOSX & iOS) +

    + +

    + You can install following Android app and join meetings from this demo: + + https://play.google.com/store/apps/details?id=rmc3.audioconference +

    + +

    + Microsoft Edge is also supported.

    diff --git a/RTCMultiConnection/demos/Audio-Video-Screen.html b/RTCMultiConnection/demos/Audio-Video-Screen.html index 412cbcf0..8b6131de 100644 --- a/RTCMultiConnection/demos/Audio-Video-Screen.html +++ b/RTCMultiConnection/demos/Audio-Video-Screen.html @@ -102,7 +102,7 @@

    Audio+Video+Screen Sharing using RTCMultiConnection

    - + - + diff --git a/RTCMultiConnection/demos/Cross-Domain-Screen-Capturing.html b/RTCMultiConnection/demos/Cross-Domain-Screen-Capturing.html index 4aa7d86f..51079aa0 100755 --- a/RTCMultiConnection/demos/Cross-Domain-Screen-Capturing.html +++ b/RTCMultiConnection/demos/Cross-Domain-Screen-Capturing.html @@ -96,11 +96,12 @@

    Capture & Share Screen from any domain! using RTCMultiConnection

    + - + + - + + + + +
    + + MenuMenu + +
    + +

    + FileSharing without using FileBufferReader +

    + This RTCMultiConnection demo tries to share files without using FileBufferReader. +

    +

    + +
    +
    + + + + +
    + +
    + +
    + +
    + + +
    + + + + + + + + +
    +

    + You can write cordova/ionic/phonegap based iOS or Android apps for this demo as well. +

    + +

    + You can run same demo on Safari-11 as well. (both on MacOSX & iOS) +

    + +

    + Microsoft Edge is also supported. +

    +
    + +
    + +
    + + + + diff --git a/RTCMultiConnection/demos/Files-Scalable-Broadcast.html b/RTCMultiConnection/demos/Files-Scalable-Broadcast.html index 7199367d..d1fc55e8 100755 --- a/RTCMultiConnection/demos/Files-Scalable-Broadcast.html +++ b/RTCMultiConnection/demos/Files-Scalable-Broadcast.html @@ -114,9 +114,10 @@

    WebRTC Scal + - + + - + - + - + - + - - - + +
    diff --git a/RTCMultiConnection/demos/One-to-One.html b/RTCMultiConnection/demos/One-to-One.html index 45d41d44..cd5b4f0a 100755 --- a/RTCMultiConnection/demos/One-to-One.html +++ b/RTCMultiConnection/demos/One-to-One.html @@ -1,310 +1,354 @@ - + - - + - - - - - - - - - One-to-One Video Chat using RTCMultiConnection - - - - - - - - -
    - -
    -

    One-to-One Video Chat using RTCMultiConnection

    -

    - HOME - © - Muaz Khan . - @WebRTCWeb . - Github . - Latest issues . - What's New? -

    -
    - -
    - -
    - Only one user can join a room. -
    - -
    -
    - - - - - - -
    - -
    -
    - - - - - - - - - - - setTimeout(reCheckRoomPresence, 5000); - }); - })(); +
    +

    + You can write cordova/ionic/phonegap based iOS or Android apps for this demo as well. +

    - disableInputButtons(); - } +

    + You can run same demo on Safari-11 as well. (both on MacOSX & iOS) +

    - // to make it one-to-one - connection.maxParticipantsAllowed = 1; - connection.onRoomFull = function(roomid) { - connection.closeSocket(); - connection.attachStreams.forEach(function(stream) { - stream.stop(); - }); - - document.getElementById('open-or-join-room').disabled = false; - document.getElementById('open-room').disabled = false; - document.getElementById('join-room').disabled = false; - document.getElementById('room-id').disabled = false; - - alert('Room is full.'); - }; - - -
    -

    Latest Updates

    -
    -
    - -
    -

    Latest Issues

    -
    -
    - -
    -

    Feedback

    -
    - -
    - Enter your email too; if you want "direct" reply! -
    - - - - - - -
    - - +

    + Microsoft Edge is also supported. +

    + - +
    + +
    + + diff --git a/RTCMultiConnection/demos/Password-Protected-Rooms.html b/RTCMultiConnection/demos/Password-Protected-Rooms.html index ad2cd0c9..610aaeba 100755 --- a/RTCMultiConnection/demos/Password-Protected-Rooms.html +++ b/RTCMultiConnection/demos/Password-Protected-Rooms.html @@ -1,322 +1,339 @@ - + - - + - - - - - Password+Protected+Rooms using RTCMultiConnection - - - - - + + Password Protected Rooms using RTCMultiConnection + + + + - -
    - -
    -

    Password+Protected+Rooms using RTCMultiConnection

    -

    - HOME - © - Muaz Khan . - @WebRTCWeb . - Github . - Latest issues . - What's New? -

    -
    - -
    - -
    -
    - Room-id: -
    - - Password: - -
    - - - - - - -

    - - -
    - -
    -
    -
    -
    - -
    -
    - - - - - - - + + + + + + + - document.getElementById('room-password').value = password; +
    +

    + You can write cordova/ionic/phonegap based iOS or Android apps for this demo as well. +

    - // auto-join-room - (function reCheckRoomPresence() { - connection.checkPresence(roomid, function(isRoomExists) { - if(isRoomExists) { - connection.join(roomid); - return; - } +

    + You can run same demo on Safari-11 as well. (both on MacOSX & iOS) +

    - setTimeout(reCheckRoomPresence, 5000); - }); - })(); +

    + Microsoft Edge is also supported. +

    +
    - disableInputButtons(); - } - - -
    -

    Latest Updates

    -
    -
    - -
    -

    Latest Issues

    -
    -
    - -
    -

    Feedback

    -
    - -
    - Enter your email too; if you want "direct" reply! -
    - - - - - - -
    - - +
    + +
    + - diff --git a/RTCMultiConnection/demos/Pre-recorded-Media-Streaming.html b/RTCMultiConnection/demos/Pre-recorded-Media-Streaming.html index 8e918bbd..e405b621 100755 --- a/RTCMultiConnection/demos/Pre-recorded-Media-Streaming.html +++ b/RTCMultiConnection/demos/Pre-recorded-Media-Streaming.html @@ -95,9 +95,10 @@

    Pre-recorded media streaming using RTCMultiConnection

    + - + diff --git a/RTCMultiConnection/demos/PubNub-Demo.html b/RTCMultiConnection/demos/PubNub-Demo.html index 97bb0b66..15ef20ff 100755 --- a/RTCMultiConnection/demos/PubNub-Demo.html +++ b/RTCMultiConnection/demos/PubNub-Demo.html @@ -76,9 +76,10 @@

    PubNub Video Conferencing Demo using RTCMultiConnection

    + - + diff --git a/RTCMultiConnection/demos/README.md b/RTCMultiConnection/demos/README.md index 3e96e572..576bf0c2 100755 --- a/RTCMultiConnection/demos/README.md +++ b/RTCMultiConnection/demos/README.md @@ -2,47 +2,6 @@ You can test all demos LIVE here: * https://rtcmulticonnection.herokuapp.com/ -# Demos - -* [List of All RTCMultiConnection Demos](https://rtcmulticonnection.herokuapp.com/demos/) - -| DemoTitle | TestLive | ViewSource | -| ------------- |-------------|-------------| -| Audio+Video+File+TextChat | [Demo](https://rtcmulticonnection.herokuapp.com/demos/Audio+Video+TextChat+FileSharing.html) | [Source](https://github.com/muaz-khan/RTCMultiConnection/tree/master/demos/Audio+Video+TextChat+FileSharing.html) | -| Pre-recorded media streaming (webm/mp3 live streaming) | [Demo](https://rtcmulticonnection.herokuapp.com/demos/Pre-recorded-Media-Streaming.html) | [Source](https://github.com/muaz-khan/RTCMultiConnection/tree/master/demos/Pre-recorded-Media-Streaming.html) | -| FileSharing | [Demo](https://rtcmulticonnection.herokuapp.com/demos/file-sharing.html) | [Source](https://github.com/muaz-khan/RTCMultiConnection/tree/master/demos/file-sharing.html) | -| Scalable Audio/Video Broadcast | [Demo](https://rtcmulticonnection.herokuapp.com/demos/Scalable-Broadcast.html) | [Source](https://github.com/muaz-khan/RTCMultiConnection/tree/master/demos/Scalable-Broadcast.html) | -| Scalable Screen Broadcast | [Demo](https://rtcmulticonnection.herokuapp.com/demos/Scalable-Screen-Broadcast.html) | [Source](https://github.com/muaz-khan/RTCMultiConnection/tree/master/demos/Scalable-Screen-Broadcast.html) | -| Scalable Video Broadcast | [Demo](https://rtcmulticonnection.herokuapp.com/demos/Video-Scalable-Broadcast.html) | [Source](https://github.com/muaz-khan/RTCMultiConnection/tree/master/demos/Video-Scalable-Broadcast.html) | -| Scalable File Sharing | [Demo](https://rtcmulticonnection.herokuapp.com/demos/Files-Scalable-Broadcast.html) | [Source](https://github.com/muaz-khan/RTCMultiConnection/tree/master/demos/Files-Scalable-Broadcast.html) | -| Video Conferencing | [Demo](https://rtcmulticonnection.herokuapp.com/demos/Video-Conferencing.html) | [Source](https://github.com/muaz-khan/RTCMultiConnection/tree/master/demos/Video-Conferencing.html) | -| SSEConnection (Server Sent Events) | [Demo](https://rtcmulticonnection.herokuapp.com/demos/SSEConnection.html) | [Source](https://github.com/muaz-khan/RTCMultiConnection/tree/master/demos/SSEConnection.html) | -| Audio+Video+Screen Sharing | [Demo](https://rtcmulticonnection.herokuapp.com/demos/Audio-Video-Screen.html) | [Source](https://github.com/muaz-khan/RTCMultiConnection/tree/master/demos/Audio-Video-Screen.html) | -| One-to-One Video Chat | [Demo](https://rtcmulticonnection.herokuapp.com/demos/One-to-One.html) | [Source](https://github.com/muaz-khan/RTCMultiConnection/tree/master/demos/One-to-One.html) | -| Audio Conferencing | [Demo](https://rtcmulticonnection.herokuapp.com/demos/Audio-Conferencing.html) | [Source](https://github.com/muaz-khan/RTCMultiConnection/tree/master/demos/Audio-Conferencing.html) | -| Video Broadcasting | [Demo](https://rtcmulticonnection.herokuapp.com/demos/Video-Broadcasting.html) | [Source](https://github.com/muaz-khan/RTCMultiConnection/blob/master/demos/video-broadcasting.html) | -| TextChat+FileSharing | [Demo](https://rtcmulticonnection.herokuapp.com/demos/TextChat+FileSharing.html) | [Source](https://github.com/muaz-khan/RTCMultiConnection/tree/master/demos/TextChat+FileSharing.html) | -| addStream in a Chat room | [Demo](https://rtcmulticonnection.herokuapp.com/demos/addStream-in-Chat-room.html) | [Source](https://github.com/muaz-khan/RTCMultiConnection/tree/master/demos/addStream-in-Chat-room.html) | -| Part-of-Screen Sharing | [Demo](https://rtcmulticonnection.herokuapp.com/demos/share-part-of-screen.html) | [Source](https://github.com/muaz-khan/RTCMultiConnection/tree/master/demos/share-part-of-screen.html) | -| Share Audio+Screen | [Demo](https://rtcmulticonnection.herokuapp.com/demos/Audio+ScreenSharing.html) | [Source](https://github.com/muaz-khan/RTCMultiConnection/tree/master/demos/Audio+ScreenSharing.html) | -| Screen Sharing | [Demo](https://rtcmulticonnection.herokuapp.com/demos/Screen-Sharing.html) | [Source](https://github.com/muaz-khan/RTCMultiConnection/tree/master/demos/screen-sharing.html) | -| Disconnect/Rejoin rooms | [Demo](https://rtcmulticonnection.herokuapp.com/demos/Disconnect+Rejoin.html) | [Source](https://github.com/muaz-khan/RTCMultiConnection/tree/master/demos/Disconnect+Rejoin.html) | -| Password Protected Rooms | [Demo](https://rtcmulticonnection.herokuapp.com/demos/Password-Protected-Rooms.html) | [Source](https://github.com/muaz-khan/RTCMultiConnection/tree/master/demos/Password-Protected-Rooms.html) | -| replaceTrack in Firefox | [Demo](https://rtcmulticonnection.herokuapp.com/demos/replaceTrack.html) | [Source](https://github.com/muaz-khan/RTCMultiConnection/tree/master/demos/replaceTrack.html) | -| applyConstraints in Firefox | [Demo](https://rtcmulticonnection.herokuapp.com/demos/applyConstraints.html) | [Source](https://github.com/muaz-khan/RTCMultiConnection/tree/master/demos/applyConstraints.html) | -| Firebase-Demo | [Demo](https://rtcmulticonnection.herokuapp.com/demos/Firebase-Demo.html) | [Source](https://github.com/muaz-khan/RTCMultiConnection/tree/master/demos/Firebase-Demo.html) | -| PubNub Demo | [Demo](https://rtcmulticonnection.herokuapp.com/demos/PubNub-Demo.html) | [Source](https://github.com/muaz-khan/RTCMultiConnection/tree/master/demos/PubNub-Demo.html) | -| Socket.io Custom-Messaging | [Demo](https://rtcmulticonnection.herokuapp.com/demos/custom-socket-event.html) | [Source](https://github.com/muaz-khan/RTCMultiConnection/tree/master/demos/custom-socket-event.html) | -| Check Rooms Presence | [Demo](https://rtcmulticonnection.herokuapp.com/demos/checkPresence.html) | [Source](https://github.com/muaz-khan/RTCMultiConnection/tree/master/demos/checkPresence.html) | -| getPublicModerators | [Demo](https://rtcmulticonnection.herokuapp.com/demos/getPublicModerators.html) | [Source](https://github.com/muaz-khan/RTCMultiConnection/tree/master/demos/getPublicModerators.html) | -| Change Cameras/Microphone | [Demo](https://rtcmulticonnection.herokuapp.com/demos/switch-cameras.html) | [Source](https://github.com/muaz-khan/RTCMultiConnection/tree/master/demos/switch-cameras.html) | -| MultiRTC: Skype-like app | [Demo](https://rtcmulticonnection.herokuapp.com/demos/MultiRTC/) | [Source](https://github.com/muaz-khan/RTCMultiConnection/tree/master/demos/MultiRTC/) | -| Change Video Resolutions in your Live Sessions | [Demo](https://rtcmulticonnection.herokuapp.com/demos/change-resolutions.html) | [Source](https://github.com/muaz-khan/RTCMultiConnection/tree/master/demos/change-resolutions.html) | -| Admin/Guest demo | [Demo](https://rtcmulticonnection.herokuapp.com/demos/admin-guest.html) | [Source](https://github.com/muaz-khan/RTCMultiConnection/tree/master/demos/admin-guest.html) | -| Check if StreamHasData | [Demo](https://rtcmulticonnection.herokuapp.com/demos/StreamHasData.html) | [Source](https://github.com/muaz-khan/RTCMultiConnection/tree/master/demos/StreamHasData.html) | -| Capture & Share Screen from any domain! | [Demo](https://rtcmulticonnection.herokuapp.com/demos/Cross-Domain-Screen-Capturing.html) | [Source](https://github.com/muaz-khan/RTCMultiConnection/tree/master/demos/Cross-Domain-Screen-Capturing.html) | -| SignalR demo for RTCMultiConnection | -- | [Source](https://github.com/muaz-khan/RTCMultiConnection-SignalR) | - # Older Versions * https://github.com/muaz-khan/RTCMultiConnection/tree/master/v2.2.2 diff --git a/RTCMultiConnection/demos/SFU.html b/RTCMultiConnection/demos/SFU.html index ba133a52..39c32afe 100644 --- a/RTCMultiConnection/demos/SFU.html +++ b/RTCMultiConnection/demos/SFU.html @@ -61,6 +61,7 @@

    + diff --git a/RTCMultiConnection/demos/SSEConnection.html b/RTCMultiConnection/demos/SSEConnection.html index ab611372..6a1013fe 100644 --- a/RTCMultiConnection/demos/SSEConnection.html +++ b/RTCMultiConnection/demos/SSEConnection.html @@ -1,4 +1,4 @@ - + @@ -55,8 +55,8 @@

    - - + + + + +
    + + MenuMenu + +
    + +

    + WebRTC Audio Scalable Broadcast using RTCMultiConnection +

    + Use peer-to-peer protocol to broadcast your audio over 20+ users. +

    +

    + +
    +

    +

    + + + +
    +

    + + +
    + + + + + + + +
    +

    How this works?

    +

    + This module simply initializes socket.io and configures it in a way that single audio/video/screen stream can be shared/relayed over unlimited users without any bandwidth/CPU usage issues. Everything happens peer-to-peer! +

    +

    + Check this thread or this github repository. +

    +
    + +
    +

    iOS or Android

    +

    + You can write iOS or Android apps for this demo as well. +

    +
    + +
    + +
    + + + + diff --git a/RTCMultiConnection/demos/Scalable-Broadcast.html b/RTCMultiConnection/demos/Scalable-Broadcast.html index d6402200..47c3724c 100755 --- a/RTCMultiConnection/demos/Scalable-Broadcast.html +++ b/RTCMultiConnection/demos/Scalable-Broadcast.html @@ -1,4 +1,4 @@ - + @@ -59,7 +59,7 @@

    - + + - + + - + + diff --git a/RTCMultiConnection/demos/StreamHasData.html b/RTCMultiConnection/demos/StreamHasData.html index ac0d12fc..60fa48df 100755 --- a/RTCMultiConnection/demos/StreamHasData.html +++ b/RTCMultiConnection/demos/StreamHasData.html @@ -90,6 +90,7 @@

    Check if StreamHasData using RTCMultiConnection

    + diff --git a/RTCMultiConnection/demos/TextChat+FileSharing.html b/RTCMultiConnection/demos/TextChat+FileSharing.html index a93ab2ce..306c2278 100755 --- a/RTCMultiConnection/demos/TextChat+FileSharing.html +++ b/RTCMultiConnection/demos/TextChat+FileSharing.html @@ -1,4 +1,4 @@ - + @@ -77,9 +77,10 @@

    TextChat+FileSharing using RTCMultiConnection

    + - + + - + - + + + @@ -128,11 +129,16 @@

    WebRTC Scal + diff --git a/RTCMultiConnection/demos/WebSocket-Demo.html b/RTCMultiConnection/demos/WebSocket-Demo.html index f48313a7..d6279700 100644 --- a/RTCMultiConnection/demos/WebSocket-Demo.html +++ b/RTCMultiConnection/demos/WebSocket-Demo.html @@ -76,10 +76,9 @@

    WebSocket Video Conferencing Demo using RTCMultiConnection

    + - - - + + - - + - - + + + - + diff --git a/RTCMultiConnection/demos/camera-zoom.html b/RTCMultiConnection/demos/camera-zoom.html index b323c0e5..812ae6f3 100644 --- a/RTCMultiConnection/demos/camera-zoom.html +++ b/RTCMultiConnection/demos/camera-zoom.html @@ -72,6 +72,7 @@

    Zoom Camera during WebRTC Live Streaming | RTCMultiConnection

    + + + + - - - -
    - -
    - -
    -
    -
    -
    - - -
    - - -
    - -
    -
    - -
    -
    - - - -
    -
    - - - - - - - - - - - - - - diff --git a/RTCMultiConnection/demos/dashboard/canvas-designer.html b/RTCMultiConnection/demos/dashboard/canvas-designer.html new file mode 100644 index 00000000..e11fb266 --- /dev/null +++ b/RTCMultiConnection/demos/dashboard/canvas-designer.html @@ -0,0 +1,799 @@ + + + + + + + + Dashboard + Video Conferencing + Chat + File Sharing | RTCMultiConnection + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + +
    + +
    +
    +
    +
    +
    + +
    +
    + + + + + +
    + + +
    +
    + + + + diff --git a/RTCMultiConnection/demos/dashboard/index.html b/RTCMultiConnection/demos/dashboard/index.html new file mode 100644 index 00000000..51f2b0c4 --- /dev/null +++ b/RTCMultiConnection/demos/dashboard/index.html @@ -0,0 +1,767 @@ + + + + + + + + Dashboard + Video Conferencing + Chat + File Sharing | RTCMultiConnection + + + + + + + + + + + + + + +
    + Dashboard Example + +
    + + + Active rooms: 0 +
    +
    + +
    + + + + + + + + +
    + + + + + + + + + + + + + +
    #Room IDOwner IDSessionExtraParticipantsJoin
    No active room found for this demo.
    +
    + +
    Dashboard + Video Conferencing + Chat + File Sharing
    + + + + diff --git a/RTCMultiConnection/demos/file-sharing.html b/RTCMultiConnection/demos/file-sharing.html index 2dec6418..242a9958 100755 --- a/RTCMultiConnection/demos/file-sharing.html +++ b/RTCMultiConnection/demos/file-sharing.html @@ -290,9 +290,10 @@ + - + - - - - -
    -

    Latest Updates

    -
    -
    - -
    -

    Latest Issues

    -
    -
    - -
    -

    Feedback

    -
    - -
    - Enter your email too; if you want "direct" reply! -
    - - - - - - - - - - - - - diff --git a/RTCMultiConnection/demos/getStats.html b/RTCMultiConnection/demos/getStats.html index d0a2696b..0726131c 100644 --- a/RTCMultiConnection/demos/getStats.html +++ b/RTCMultiConnection/demos/getStats.html @@ -1,4 +1,4 @@ - + @@ -63,13 +63,14 @@

    + - + - + + +
    + Video Conference + +
    + + + Active rooms: 0 +
    +
    + + + + + + + + + + +
    + + + + + + + + + + + + + +
    #Room IDOwner IDSessionExtraParticipantsJoin
    No active room found for this demo.
    +
    + +
    Video Conference using RTCMultiConnection
    + + + + diff --git a/RTCMultiConnection/demos/video-conference/video-conference.html b/RTCMultiConnection/demos/video-conference/video-conference.html new file mode 100644 index 00000000..90306a05 --- /dev/null +++ b/RTCMultiConnection/demos/video-conference/video-conference.html @@ -0,0 +1,268 @@ + + + + + + + Video Conference using RTCMultiConnection + + + + + + +
    + + MenuMenu + +
    + +

    + Video Conference using RTCMultiConnection +

    + Multi-user (many-to-many) video chat using mesh networking model. +

    +

    + +
    +
    + +
    + + + + + + + + + + +
    +

    + You can write cordova/ionic/phonegap based iOS or Android apps for this demo as well. + + You can download an open-sourced cordova app both for iOS and Android as well. +

    + +

    + You can run same demo on Safari-11 as well. (both on MacOSX & iOS) +

    + +

    + You can install following Android app and join meetings from this demo: + + https://play.google.com/store/apps/details?id=rmc3.videoconference +

    + +

    + Microsoft Edge is also supported. +

    +
    + +
    + +
    + + + + diff --git a/RTCMultiConnection/demos/vuejs-video-conferencing.html b/RTCMultiConnection/demos/vuejs-video-conferencing.html index 02bb4941..1b767374 100644 --- a/RTCMultiConnection/demos/vuejs-video-conferencing.html +++ b/RTCMultiConnection/demos/vuejs-video-conferencing.html @@ -64,7 +64,7 @@

    - + - + @@ -24,6 +24,9 @@ All files from `/dist` directory are available on CDN: `https://cdn.webrtc-exper + + + ``` If you're sharing files, you also need to link: diff --git a/RTCMultiConnection/node_scripts/uncache.js b/RTCMultiConnection/node_scripts/uncache.js deleted file mode 100644 index e455438d..00000000 --- a/RTCMultiConnection/node_scripts/uncache.js +++ /dev/null @@ -1,28 +0,0 @@ -// removing JSON from cache -function uncache(jsonFile) { - searchCache(jsonFile, function(mod) { - delete require.cache[mod.id]; - }); - - Object.keys(module.constructor._pathCache).forEach(function(cacheKey) { - if (cacheKey.indexOf(jsonFile) > 0) { - delete module.constructor._pathCache[cacheKey]; - } - }); -} - -function searchCache(jsonFile, callback) { - var mod = require.resolve(jsonFile); - - if (mod && ((mod = require.cache[mod]) !== undefined)) { - (function run(mod) { - mod.children.forEach(function(child) { - run(child); - }); - - callback(mod); - })(mod); - } -} - -module.exports = exports = uncache; diff --git a/RTCMultiConnection/node_scripts/verify-admin.js b/RTCMultiConnection/node_scripts/verify-admin.js deleted file mode 100644 index 5c42ed07..00000000 --- a/RTCMultiConnection/node_scripts/verify-admin.js +++ /dev/null @@ -1,22 +0,0 @@ -// Muaz Khan - www.MuazKhan.com -// MIT License - www.WebRTC-Experiment.com/licence -// Documentation - github.com/muaz-khan/RTCMultiConnection - -// basic-auth and tsscmp are used for /admin/ page -var adminAuthorization = require('basic-auth'); -var compareAdminCredentials = require('tsscmp'); - -module.exports = exports = function(request, config) { - var credentials = adminAuthorization(request); - if (!credentials || !isAdminAuthorized(credentials.name, credentials.pass, config)) { - return false; - } - return true; -} - -function isAdminAuthorized(name, pass, config) { - var valid = true - valid = compareAdminCredentials(name, config.adminUserName) && valid; - valid = compareAdminCredentials(pass, config.adminPassword) && valid; - return valid -} diff --git a/RTCMultiConnection/npm-test.js b/RTCMultiConnection/npm-test.js new file mode 100644 index 00000000..f073a60e --- /dev/null +++ b/RTCMultiConnection/npm-test.js @@ -0,0 +1,39 @@ +// https://tonicdev.com/npm/rtcmulticonnection + +var RTCMultiConnection; + +try { + RTCMultiConnection = require('rtcmulticonnection'); +} +catch(e) { + RTCMultiConnection = require('./dist/RTCMultiConnection.js'); +} + +var connection = new RTCMultiConnection(); + +connection.enableLogs = true; + +// test only data channels +connection.session = { + data: true +}; + +connection.dontCaptureUserMedia = true; + +console.log('\n------\n'); + +connection.open('room-id', function(isRoomOpened, roomid, error) { + if(isRoomOpened === true) { + console.log('Room opened: ' + roomid) + } + + if(error) { + console.log('Unable to open room: ' + error); + } +}); + +console.log('\n------\n'); + +console.log(connection); + +process.exit() diff --git a/RTCMultiConnection/package.json b/RTCMultiConnection/package.json index 296df76d..3edb9f1b 100644 --- a/RTCMultiConnection/package.json +++ b/RTCMultiConnection/package.json @@ -1,7 +1,7 @@ { "name": "rtcmulticonnection", "preferGlobal": false, - "version": "3.4.8", + "version": "3.5.9", "author": { "name": "Muaz Khan", "email": "muazkh@gmail.com", @@ -43,9 +43,16 @@ "muaz-khan" ], "dependencies": { + "rtcmulticonnection-server": "latest", + "detectrtc": "latest", + "recordrtc": "latest", + "getstats": "latest", + "fbr": "latest", + "webrtc-screen-capturing": "latest", "socket.io": "latest", - "basic-auth": "latest", - "tsscmp": "latest" + "webrtc-adapter": "latest", + "canvas-designer": "latest", + "multistreamsmixer": "latest" }, "analyze": false, "license": "MIT", @@ -54,7 +61,8 @@ "url": "https://github.com/muaz-khan/RTCMultiConnection/issues", "email": "muazkh@gmail.com" }, - "homepage": "https://rtcmulticonnection.herokuapp.com/", + "homepage": "https://rtcmulticonnection.herokuapp.com/demos/", + "tonicExampleFilename": "npm-test.js", "_from": "rtcmulticonnection@", "devDependencies": { "grunt": "0.4.5", diff --git a/RTCMultiConnection/server.js b/RTCMultiConnection/server.js index 3e594a27..3a4bdf0e 100755 --- a/RTCMultiConnection/server.js +++ b/RTCMultiConnection/server.js @@ -1,42 +1,31 @@ -// Muaz Khan - www.MuazKhan.com -// MIT License - www.WebRTC-Experiment.com/licence -// Documentation - github.com/muaz-khan/RTCMultiConnection var fs = require('fs'); var path = require('path'); +var url = require('url'); -var resolveURL = require('./node_scripts/resolveURL.js'); -var BASH_COLORS_HELPER = require('./node_scripts/BASH_COLORS_HELPER.js'); -var config = require('./node_scripts/get-values-from-config-json.js'); - -require('./node_scripts/get-bash-parameters.js')(config, BASH_COLORS_HELPER); - -var isAdminAuthorized = require('./node_scripts/verify-admin.js'); - -// pushLogs is used to write error logs into logs.json -var pushLogs = function(name, error) { - console.log(name, error); +/* +var firstParameter = { + config: __dirname + resolveURL('/config.json'), + logs: __dirname + resolveURL('/logs.json') }; -try { - pushLogs = require('./node_scripts/pushLogs.js'); -} catch (e) { - console.log('Unable to read pushLogs.js', e); -} +*/ -var server = require(config.isUseHTTPs ? 'https' : 'http'); -var url = require('url'); - -function serverHandler(request, response) { +require('rtcmulticonnection-server')(null, function(request, response, config, root, BASH_COLORS_HELPER, pushLogs, resolveURL, getJsonFile) { try { var uri, filename; try { + if (!config.dirPath || !config.dirPath.length) { + config.dirPath = null; + } + uri = url.parse(request.url).pathname; - filename = path.join(process.cwd(), uri); - } - catch(e) { - pushLogs('url.parse', e); + filename = path.join(config.dirPath ? resolveURL(config.dirPath) : process.cwd(), uri); + } catch (e) { + pushLogs(root, 'url.parse', e); } + filename = (filename || '').toString(); + if (request.method !== 'GET' || uri.indexOf('..') !== -1) { try { response.writeHead(401, { @@ -45,84 +34,40 @@ function serverHandler(request, response) { response.write('401 Unauthorized: ' + path.join('/', uri) + '\n'); response.end(); return; - } - catch(e) { - pushLogs('!GET or ..', e); + } catch (e) { + pushLogs(root, '!GET or ..', e); } } - var matched = false; - filename && ['/demos/', '/dev/', '/dist/', '/socket.io/', '/admin/'].forEach(function(item) { - if (filename.indexOf(resolveURL(item)) !== -1) { - matched = true; - } - }); - - if(filename.indexOf(resolveURL('/logs.json')) !== -1 && isAdminAuthorized(request, config)) { + if(filename.indexOf(resolveURL('/admin/')) !== -1 && config.enableAdmin !== true) { try { - // uncache to fetch recent (up-to-dated) - var uncache = require('./node_scripts/uncache.js'); - uncache('../logs.json'); - - var logs = require('./logs.json'); - response.writeHead(200, { + response.writeHead(401, { 'Content-Type': 'text/plain' }); - response.write(JSON.stringify(logs)); + response.write('401 Unauthorized: ' + path.join('/', uri) + '\n'); response.end(); return; + } catch (e) { + pushLogs(root, '!GET or ..', e); } - catch(e) { - pushLogs('/logs.json', e); - } + return; } - // handling /admin/ page - if (filename && (filename.indexOf('/admin/') !== -1 || filename.indexOf('\\admin\\') !== -1)) { - if (!isAdminAuthorized(request, config)) { - try { - response.writeHead(401, { - 'WWW-Authenticate': 'Basic realm="Node"' - }); - response.write('401 Unauthorized\n'); - response.end(); - return; - } - catch(e) { - pushLogs('/admin/ auth issues', e); - } + var matched = false; + ['/demos/', '/dev/', '/dist/', '/socket.io/', '/node_modules/canvas-designer/', '/admin/'].forEach(function(item) { + if (filename.indexOf(resolveURL(item)) !== -1) { + matched = true; } + }); - // these three values are used inside Signaling-Server.js - app.request = request; - app.isAdminAuthorized = isAdminAuthorized; - app.config = config; - - fs.readFile('admin/index.html', 'binary', function(err, file) { - try { - if (err) { - response.writeHead(500, { - 'Content-Type': 'text/plain' - }); - response.write('404 Not Found: admin/index.html\n'); - response.end(); - return; - } - - response.writeHead(200, { - 'Content-Type': 'text/html' - }); - response.write(file, 'binary'); - response.end(); - } - catch(e) { - pushLogs('admin/index.html', e); - } - }); - return; - } + // files from node_modules + ['RecordRTC.js', 'FileBufferReader.js', 'getStats.js', 'getScreenId.js', 'adapter.js', 'MultiStreamsMixer.js'].forEach(function(item) { + if (filename.indexOf(resolveURL('/node_modules/')) !== -1 && filename.indexOf(resolveURL(item)) !== -1) { + matched = true; + } + }); - if (filename && filename.search(/.js|.json/g) !== -1 && !matched) { + if (filename.search(/.js|.json/g) !== -1 && !matched) { try { response.writeHead(404, { 'Content-Type': 'text/plain' @@ -130,20 +75,18 @@ function serverHandler(request, response) { response.write('404 Not Found: ' + path.join('/', uri) + '\n'); response.end(); return; - } - catch(e) { - pushLogs('404 Not Found', e); + } catch (e) { + pushLogs(root, '404 Not Found', e); } } ['Video-Broadcasting', 'Screen-Sharing', 'Switch-Cameras'].forEach(function(fname) { try { - if (filename && filename.indexOf(fname + '.html') !== -1) { + if (filename.indexOf(fname + '.html') !== -1) { filename = filename.replace(fname + '.html', fname.toLowerCase() + '.html'); } - } - catch(e) { - pushLogs('forEach', e); + } catch (e) { + pushLogs(root, 'forEach', e); } }); @@ -152,7 +95,7 @@ function serverHandler(request, response) { try { stats = fs.lstatSync(filename); - if (filename && filename.search(/demos/g) === -1 && stats.isDirectory() && config.defaultDemo === '/demos/index.html') { + if (filename.search(/demos/g) === -1 && filename.search(/admin/g) === -1 && stats.isDirectory() && config.homePage === '/demos/index.html') { if (response.redirect) { response.redirect('/demos/'); } else { @@ -181,17 +124,25 @@ function serverHandler(request, response) { if (filename.indexOf(resolveURL('/demos/MultiRTC/')) !== -1) { filename = filename.replace(resolveURL('/demos/MultiRTC/'), ''); filename += resolveURL('/demos/MultiRTC/index.html'); + } else if (filename.indexOf(resolveURL('/admin/')) !== -1) { + filename = filename.replace(resolveURL('/admin/'), ''); + filename += resolveURL('/admin/index.html'); + } else if (filename.indexOf(resolveURL('/demos/dashboard/')) !== -1) { + filename = filename.replace(resolveURL('/demos/dashboard/'), ''); + filename += resolveURL('/demos/dashboard/index.html'); + } else if (filename.indexOf(resolveURL('/demos/video-conference/')) !== -1) { + filename = filename.replace(resolveURL('/demos/video-conference/'), ''); + filename += resolveURL('/demos/video-conference/index.html'); } else if (filename.indexOf(resolveURL('/demos')) !== -1) { filename = filename.replace(resolveURL('/demos/'), ''); filename = filename.replace(resolveURL('/demos'), ''); filename += resolveURL('/demos/index.html'); } else { - filename += resolveURL(config.defaultDemo); + filename += resolveURL(config.homePage); } } - } - catch(e) { - pushLogs('statSync.isDirectory', e); + } catch (e) { + pushLogs(root, 'statSync.isDirectory', e); } var contentType = 'text/plain'; @@ -226,244 +177,12 @@ function serverHandler(request, response) { response.end(); }); } catch (e) { - pushLogs('Unexpected', e); + pushLogs(root, 'Unexpected', e); response.writeHead(404, { 'Content-Type': 'text/plain' }); - response.write('404 Not Found: Unexpected error.\n'); + response.write('404 Not Found: Unexpected error.\n' + e.message + '\n\n' + e.stack); response.end(); } -} - -var app; - -try { - if (config.isUseHTTPs) { - // See how to use a valid certificate: - // https://github.com/muaz-khan/WebRTC-Experiment/issues/62 - var options = { - key: null, - cert: null, - ca: null - }; - - if (!fs.existsSync(config.sslKey)) { - console.log(BASH_COLORS_HELPER.getRedFG(), 'sslKey:\t ' + config.sslKey + ' does not exist.'); - } - - if (!fs.existsSync(config.sslCert)) { - console.log(BASH_COLORS_HELPER.getRedFG(), 'sslCert:\t ' + config.sslCert + ' does not exist.'); - } - - options.key = fs.readFileSync(config.sslKey); - options.cert = fs.readFileSync(config.sslCert); - - if (config.sslCabundle) { - if (!fs.existsSync(config.sslCabundle)) { - console.log(BASH_COLORS_HELPER.getRedFG(), 'sslCabundle:\t ' + config.sslCabundle + ' does not exist.'); - } - - options.ca = fs.readFileSync(config.sslCabundle); - } - - app = server.createServer(options, serverHandler); - } else { - app = server.createServer(serverHandler); - } -} -catch(e) { - pushLogs('createServer', e); -} - -function runServer() { - try { - app.on('error', function(e) { - pushLogs('app.onerror', e); - - if (e.code != 'EADDRINUSE') return; - - try { - function cmd_exec(cmd, args, cb_stdout, cb_end) { - try { - var spawn = require('child_process').spawn; - var child = spawn(cmd, args); - var me = this; - me.exit = 0; - me.stdout = ""; - child.stdout.on('data', function(data) { - try { - cb_stdout(me, data); - } - catch(e) { - pushLogs('stdout.data', e); - } - }); - child.stdout.on('end', function() { - try { - cb_end(me); - } - catch(e) { - pushLogs('stdout.end', e); - } - }); - } - catch(e) { - pushLogs('cmd_exec', e); - } - } - - function log_console() { - try { - console.log(foo.stdout); - - var pidToBeKilled = foo.stdout.split('\nnode ')[1].split(' ')[0]; - console.log('------------------------------'); - console.log('Please execute below command:'); - console.log('\x1b[31m%s\x1b[0m ', 'kill ' + pidToBeKilled); - console.log('Then try to run "server.js" again.'); - console.log('------------------------------'); - - } catch (e) { - pushLogs('log_console', e); - } - } - - if (e.address === '0.0.0.0') { - e.address = 'localhost'; - } - - var socketURL = (config.isUseHTTPs ? 'https' : 'http') + '://' + e.address + ':' + e.port + '/'; - - console.log('------------------------------'); - console.log('\x1b[31m%s\x1b[0m ', 'Unable to listen on port: ' + e.port); - console.log('\x1b[31m%s\x1b[0m ', socketURL + ' is already in use. Please kill below processes using "kill PID".'); - console.log('------------------------------'); - - foo = new cmd_exec('lsof', ['-n', '-i4TCP:9001'], - function(me, data) { - try { - me.stdout += data.toString(); - } - catch(e) { - pushLogs('lsof', e); - } - }, - function(me) { - try { - me.exit = 1; - } - catch(e) { - pushLogs('lsof.exit', e); - } - } - ); - - setTimeout(log_console, 250); - } - catch(e) { - pushLogs('app.onerror.EADDRINUSE', e); - } - }); - - app = app.listen(config.port, process.env.IP || '0.0.0.0', function(error) { - try { - var addr = app.address(); - - if (addr.address === '0.0.0.0') { - addr.address = 'localhost'; - } - - var domainURL = (config.isUseHTTPs ? 'https' : 'http') + '://' + addr.address + ':' + addr.port + '/'; - - console.log('\n'); - - console.log('Socket.io is listening at:'); - console.log(BASH_COLORS_HELPER.getGreenFG(), '\t' + domainURL); - - if (!config.isUseHTTPs) { - console.log('You can use --ssl to enable HTTPs:'); - console.log(BASH_COLORS_HELPER.getYellowFG(), '\t' + 'node server --ssl'); - } - - console.log('Your web-browser (HTML file) MUST set this line:'); - console.log(BASH_COLORS_HELPER.getGreenFG(), '\tconnection.socketURL = "' + domainURL + '";'); - - if (addr.address != 'localhost' && !config.isUseHTTPs) { - console.log(BASH_COLORS_HELPER.getRedBG(), 'Warning:'); - console.log(BASH_COLORS_HELPER.getRedBG(), 'Please run on HTTPs to make sure audio,video and screen demos can work on Google Chrome as well.'); - } - - console.log('For more help: ', BASH_COLORS_HELPER.getYellowFG('node server.js --help')); - console.log('\n'); - } - catch(e) { - pushLogs('app.listen.callback', e); - } - }); - } - catch(e) { - pushLogs('app.listen', e); - } - - try { - require('./node_scripts/Signaling-Server.js')(app, function(socket) { - try { - var params = socket.handshake.query; - - // "socket" object is totally in your own hands! - // do whatever you want! - - // in your HTML page, you can access socket as following: - // connection.socketCustomEvent = 'custom-message'; - // var socket = connection.getSocket(); - // socket.emit(connection.socketCustomEvent, { test: true }); - - if (!params.socketCustomEvent) { - params.socketCustomEvent = 'custom-message'; - } - - socket.on(params.socketCustomEvent, function(message) { - try { - socket.broadcast.emit(params.socketCustomEvent, message); - } catch (e) { - pushLogs('socket.broadcast.socketCustomEvent'); - } - }); - } catch (e) { - pushLogs('Signaling-Server.callback', e); - } - }); - } - catch(e) { - pushLogs('require.Signaling-Server', e); - } -} - -if (config.autoRebootServerOnFailure) { - try { - // auto restart app on failure - var cluster = require('cluster'); - if (cluster.isMaster) { - cluster.fork(); - - cluster.on('exit', function(worker, code, signal) { - try { - cluster.fork(); - } - catch(e) { - pushLogs('cluster.exit.fork', e); - } - }); - } - - if (cluster.isWorker) { - runServer(); - } - } - catch(e) { - pushLogs('cluster.require.fork', e); - } -} else { - runServer(); -} +}); diff --git a/RecordRTC/README.md b/RecordRTC/README.md index 2119b595..356d4b64 100644 --- a/RecordRTC/README.md +++ b/RecordRTC/README.md @@ -2,6 +2,8 @@ # Demo: https://www.webrtc-experiment.com/RecordRTC/ +# Chrome Extension: [Chrome-Extensions/screen-recording](https://github.com/muaz-khan/Chrome-Extensions/tree/master/screen-recording) + [RecordRTC Documentation](https://RecordRTC.org/) / [RecordRTC Wiki Pages](https://github.com/muaz-khan/RecordRTC/wiki) / [RecordRTC Demo](https://www.webrtc-experiment.com/RecordRTC/) / [WebRTC Experiments](https://www.webrtc-experiment.com/) [![npm](https://img.shields.io/npm/v/recordrtc.svg)](https://npmjs.org/package/recordrtc) [![downloads](https://img.shields.io/npm/dm/recordrtc.svg)](https://npmjs.org/package/recordrtc) [![Build Status: Linux](https://travis-ci.org/muaz-khan/RecordRTC.png?branch=master)](https://travis-ci.org/muaz-khan/RecordRTC) diff --git a/RecordRTC/RecordRTC.js b/RecordRTC/RecordRTC.js index be35bbef..fe1a006a 100644 --- a/RecordRTC/RecordRTC.js +++ b/RecordRTC/RecordRTC.js @@ -1,6 +1,6 @@ 'use strict'; -// Last time updated: 2018-10-02 1:32:58 PM UTC +// Last time updated: 2018-12-12 7:50:08 AM UTC // ________________ // RecordRTC v5.4.9 @@ -1761,8 +1761,9 @@ if (typeof MediaStream !== 'undefined') { // below function via: http://goo.gl/B3ae8c /** - * @param {number} bytes - Pass bytes and get formafted string. - * @returns {string} - formafted string + * Return human-readable file size. + * @param {number} bytes - Pass bytes and get formatted string. + * @returns {string} - formatted string * @example * bytesToSize(1024*1024*5) === '5 GB' * @see {@link https://github.com/muaz-khan/RecordRTC|RecordRTC Source Code} @@ -2837,7 +2838,7 @@ function StereoAudioRecorder(mediaStream, config) { }); }; - if(typeof Storage === 'undefined') { + if (typeof Storage === 'undefined') { var Storage = { AudioContextConstructor: null, AudioContext: window.AudioContext || window.webkitAudioContext @@ -3094,10 +3095,9 @@ function StereoAudioRecorder(mediaStream, config) { jsAudioNode.onaudioprocess = onAudioProcessDataAvailable; // to prevent self audio to be connected with speakers - if(context.createMediaStreamDestination) { + if (context.createMediaStreamDestination) { jsAudioNode.connect(context.createMediaStreamDestination()); - } - else { + } else { jsAudioNode.connect(context.destination); } @@ -3265,7 +3265,7 @@ function CanvasRecorder(htmlElement, config) { // Note: Jan 18, 2016 status is that, // Firefox MediaRecorder API can't record CanvasCaptureMediaStream object. mediaStreamRecorder = new MediaStreamRecorder(canvasMediaStream, { - mimeType: 'video/webm' + mimeType: config.mimeType || 'video/webm' }); mediaStreamRecorder.record(); } else { diff --git a/RecordRTC/RecordRTC.min.js b/RecordRTC/RecordRTC.min.js index 1b102a29..76cf359a 100644 --- a/RecordRTC/RecordRTC.min.js +++ b/RecordRTC/RecordRTC.min.js @@ -1,6 +1,6 @@ 'use strict'; -// Last time updated: 2018-10-02 1:32:59 PM UTC +// Last time updated: 2018-12-12 7:50:09 AM UTC // ________________ // RecordRTC v5.4.9 @@ -13,5 +13,5 @@ // -------------------------------------------------- "use strict";function RecordRTC(mediaStream,config){function startRecording(config2){return config2&&(config=new RecordRTCConfiguration(mediaStream,config2)),config.disableLogs||console.log("started recording "+config.type+" stream."),mediaRecorder?(mediaRecorder.clearRecordedData(),mediaRecorder.record(),setState("recording"),self.recordingDuration&&handleRecordingDuration(),self):(initRecorder(function(){self.recordingDuration&&handleRecordingDuration()}),self)}function initRecorder(initCallback){initCallback&&(config.initCallback=function(){initCallback(),initCallback=config.initCallback=null});var Recorder=new GetRecorderType(mediaStream,config);mediaRecorder=new Recorder(mediaStream,config),mediaRecorder.record(),setState("recording"),config.disableLogs||console.log("Initialized recorderType:",mediaRecorder.constructor.name,"for output-type:",config.type)}function stopRecording(callback){function _callback(__blob){if(!mediaRecorder)return void("function"==typeof callback.call?callback.call(self,""):callback(""));Object.keys(mediaRecorder).forEach(function(key){"function"!=typeof mediaRecorder[key]&&(self[key]=mediaRecorder[key])});var blob=mediaRecorder.blob;if(!blob){if(!__blob)throw"Recording failed.";mediaRecorder.blob=blob=__blob}if(blob&&!config.disableLogs&&console.log(blob.type,"->",bytesToSize(blob.size)),callback){var url=URL.createObjectURL(blob);"function"==typeof callback.call?callback.call(self,url):callback(url)}config.autoWriteToDisk&&getDataURL(function(dataURL){var parameter={};parameter[config.type+"Blob"]=dataURL,DiskStorage.Store(parameter)})}return callback=callback||function(){},mediaRecorder?"paused"===self.state?(self.resumeRecording(),void setTimeout(function(){stopRecording(callback)},1)):("recording"===self.state||config.disableLogs||console.warn('Recording state should be: "recording", however current state is: ',self.state),config.disableLogs||console.log("Stopped recording "+config.type+" stream."),"gif"!==config.type?mediaRecorder.stop(_callback):(mediaRecorder.stop(),_callback()),void setState("stopped")):void warningLog()}function pauseRecording(){return mediaRecorder?"recording"!==self.state?void(config.disableLogs||console.warn("Unable to pause the recording. Recording state: ",self.state)):(setState("paused"),mediaRecorder.pause(),void(config.disableLogs||console.log("Paused recording."))):void warningLog()}function resumeRecording(){return mediaRecorder?"paused"!==self.state?void(config.disableLogs||console.warn("Unable to resume the recording. Recording state: ",self.state)):(setState("recording"),mediaRecorder.resume(),void(config.disableLogs||console.log("Resumed recording."))):void warningLog()}function readFile(_blob){postMessage((new FileReaderSync).readAsDataURL(_blob))}function getDataURL(callback,_mediaRecorder){function processInWebWorker(_function){var blob=URL.createObjectURL(new Blob([_function.toString(),"this.onmessage = function (eee) {"+_function.name+"(eee.data);}"],{type:"application/javascript"})),worker=new Worker(blob);return URL.revokeObjectURL(blob),worker}if(!callback)throw"Pass a callback function over getDataURL.";var blob=_mediaRecorder?_mediaRecorder.blob:(mediaRecorder||{}).blob;if(!blob)return config.disableLogs||console.warn("Blob encoder did not finish its job yet."),void setTimeout(function(){getDataURL(callback,_mediaRecorder)},1e3);if("undefined"==typeof Worker||navigator.mozGetUserMedia){var reader=new FileReader;reader.readAsDataURL(blob),reader.onload=function(event){callback(event.target.result)}}else{var webWorker=processInWebWorker(readFile);webWorker.onmessage=function(event){callback(event.data)},webWorker.postMessage(blob)}}function handleRecordingDuration(counter){if(counter=counter||0,"paused"===self.state)return void setTimeout(function(){handleRecordingDuration(counter)},1e3);if("stopped"!==self.state){if(counter>=self.recordingDuration)return void stopRecording(self.onRecordingStopped);counter+=1e3,setTimeout(function(){handleRecordingDuration(counter)},1e3)}}function setState(state){self&&(self.state=state,"function"==typeof self.onStateChanged.call?self.onStateChanged.call(self,state):self.onStateChanged(state))}function warningLog(){config.disableLogs!==!0&&console.warn(WARNING)}if(!mediaStream)throw"First parameter is required.";config=config||{type:"video"},config=new RecordRTCConfiguration(mediaStream,config);var mediaRecorder,self=this,WARNING='It seems that recorder is destroyed or "startRecording" is not invoked for '+config.type+" recorder.",returnObject={startRecording:startRecording,stopRecording:stopRecording,pauseRecording:pauseRecording,resumeRecording:resumeRecording,initRecorder:initRecorder,setRecordingDuration:function(recordingDuration,callback){if("undefined"==typeof recordingDuration)throw"recordingDuration is required.";if("number"!=typeof recordingDuration)throw"recordingDuration must be a number.";return self.recordingDuration=recordingDuration,self.onRecordingStopped=callback||function(){},{onRecordingStopped:function(callback){self.onRecordingStopped=callback}}},clearRecordedData:function(){return mediaRecorder?(mediaRecorder.clearRecordedData(),void(config.disableLogs||console.log("Cleared old recorded data."))):void warningLog()},getBlob:function(){return mediaRecorder?mediaRecorder.blob:void warningLog()},getDataURL:getDataURL,toURL:function(){return mediaRecorder?URL.createObjectURL(mediaRecorder.blob):void warningLog()},getInternalRecorder:function(){return mediaRecorder},save:function(fileName){return mediaRecorder?void invokeSaveAsDialog(mediaRecorder.blob,fileName):void warningLog()},getFromDisk:function(callback){return mediaRecorder?void RecordRTC.getFromDisk(config.type,callback):void warningLog()},setAdvertisementArray:function(arrayOfWebPImages){config.advertisement=[];for(var length=arrayOfWebPImages.length,i=0;i=0)}function setSrcObject(stream,element,ignoreCreateObjectURL){if("srcObject"in element)element.srcObject=stream;else if("mozSrcObject"in element)element.mozSrcObject=stream;else if("createObjectURL"in URL&&!ignoreCreateObjectURL)try{element.src=URL.createObjectURL(stream)}catch(e){return void setSrcObject(stream,element,!0)}else alert("createObjectURL/srcObject both are not supported.")}function isMediaRecorderCompatible(){var isOpera=!!window.opera||navigator.userAgent.indexOf(" OPR/")>=0,isChrome=!!window.chrome&&!isOpera||isElectron(),isFirefox="undefined"!=typeof window.InstallTrigger;if(isFirefox)return!0;var verOffset,ix,nAgt=(navigator.appVersion,navigator.userAgent),fullVersion=""+parseFloat(navigator.appVersion),majorVersion=parseInt(navigator.appVersion,10);return(isChrome||isOpera)&&(verOffset=nAgt.indexOf("Chrome"),fullVersion=nAgt.substring(verOffset+7)),(ix=fullVersion.indexOf(";"))!==-1&&(fullVersion=fullVersion.substring(0,ix)),(ix=fullVersion.indexOf(" "))!==-1&&(fullVersion=fullVersion.substring(0,ix)),majorVersion=parseInt(""+fullVersion,10),isNaN(majorVersion)&&(fullVersion=""+parseFloat(navigator.appVersion),majorVersion=parseInt(navigator.appVersion,10)),majorVersion>=49}function MediaStreamRecorder(mediaStream,config){function updateTimeStamp(){self.timestamps.push((new Date).getTime()),"function"==typeof config.onTimeStamp&&config.onTimeStamp(self.timestamps[self.timestamps.length-1],self.timestamps)}function getMimeType(secondObject){return mediaRecorder&&mediaRecorder.mimeType?mediaRecorder.mimeType:secondObject.mimeType||"video/webm"}function clearRecordedDataCB(){arrayOfBlobs=[],mediaRecorder=null,self.timestamps=[]}function isMediaStreamActive(){if("active"in mediaStream){if(!mediaStream.active)return!1}else if("ended"in mediaStream&&mediaStream.ended)return!1;return!0}var self=this;if("undefined"==typeof mediaStream)throw'First argument "MediaStream" is required.';if("undefined"==typeof MediaRecorder)throw"Your browser does not supports Media Recorder API. Please try other modules e.g. WhammyRecorder or StereoAudioRecorder.";if(config=config||{mimeType:"video/webm"},"audio"===config.type){if(mediaStream.getVideoTracks().length&&mediaStream.getAudioTracks().length){var stream;navigator.mozGetUserMedia?(stream=new MediaStream,stream.addTrack(mediaStream.getAudioTracks()[0])):stream=new MediaStream(mediaStream.getAudioTracks()),mediaStream=stream}config.mimeType&&config.mimeType.toString().toLowerCase().indexOf("audio")!==-1||(config.mimeType=isChrome?"audio/webm":"audio/ogg"),config.mimeType&&"audio/ogg"!==config.mimeType.toString().toLowerCase()&&navigator.mozGetUserMedia&&(config.mimeType="audio/ogg")}var arrayOfBlobs=[];this.getArrayOfBlobs=function(){return arrayOfBlobs},this.record=function(){self.blob=null,self.clearRecordedData(),self.timestamps=[],allStates=[],arrayOfBlobs=[];var recorderHints=config;config.disableLogs||console.log("Passing following config over MediaRecorder API.",recorderHints),mediaRecorder&&(mediaRecorder=null),isChrome&&!isMediaRecorderCompatible()&&(recorderHints="video/vp8"),"function"==typeof MediaRecorder.isTypeSupported&&recorderHints.mimeType&&(MediaRecorder.isTypeSupported(recorderHints.mimeType)||(config.disableLogs||console.warn("MediaRecorder API seems unable to record mimeType:",recorderHints.mimeType),recorderHints.mimeType="audio"===config.type?"audio/webm":"video/webm"));try{mediaRecorder=new MediaRecorder(mediaStream,recorderHints),config.mimeType=recorderHints.mimeType}catch(e){mediaRecorder=new MediaRecorder(mediaStream)}recorderHints.mimeType&&!MediaRecorder.isTypeSupported&&"canRecordMimeType"in mediaRecorder&&mediaRecorder.canRecordMimeType(recorderHints.mimeType)===!1&&(config.disableLogs||console.warn("MediaRecorder API seems unable to record mimeType:",recorderHints.mimeType)),mediaRecorder.ondataavailable=function(e){if(e.data&&allStates.push("ondataavailable: "+bytesToSize(e.data.size)),"number"!=typeof config.timeSlice){if(!e.data||!e.data.size||e.data.size<100||self.blob)return void(self.recordingCallback&&(self.recordingCallback(new Blob([],{type:getMimeType(recorderHints)})),self.recordingCallback=null));self.blob=config.getNativeBlob?e.data:new Blob([e.data],{type:getMimeType(recorderHints)}),self.recordingCallback&&(self.recordingCallback(self.blob),self.recordingCallback=null)}else if(e.data&&e.data.size&&e.data.size>100&&(arrayOfBlobs.push(e.data),updateTimeStamp(),"function"==typeof config.ondataavailable)){var blob=config.getNativeBlob?e.data:new Blob([e.data],{type:getMimeType(recorderHints)});config.ondataavailable(blob)}},mediaRecorder.onstart=function(){allStates.push("started")},mediaRecorder.onpause=function(){allStates.push("paused")},mediaRecorder.onresume=function(){allStates.push("resumed")},mediaRecorder.onstop=function(){allStates.push("stopped")},mediaRecorder.onerror=function(error){error&&(error.name||(error.name="UnknownError"),allStates.push("error: "+error),config.disableLogs||(error.name.toString().toLowerCase().indexOf("invalidstate")!==-1?console.error("The MediaRecorder is not in a state in which the proposed operation is allowed to be executed.",error):error.name.toString().toLowerCase().indexOf("notsupported")!==-1?console.error("MIME type (",recorderHints.mimeType,") is not supported.",error):error.name.toString().toLowerCase().indexOf("security")!==-1?console.error("MediaRecorder security error",error):"OutOfMemory"===error.name?console.error("The UA has exhaused the available memory. User agents SHOULD provide as much additional information as possible in the message attribute.",error):"IllegalStreamModification"===error.name?console.error("A modification to the stream has occurred that makes it impossible to continue recording. An example would be the addition of a Track while recording is occurring. User agents SHOULD provide as much additional information as possible in the message attribute.",error):"OtherRecordingError"===error.name?console.error("Used for an fatal error other than those listed above. User agents SHOULD provide as much additional information as possible in the message attribute.",error):"GenericError"===error.name?console.error("The UA cannot provide the codec or recording option that has been requested.",error):console.error("MediaRecorder Error",error)),function(looper){return!self.manuallyStopped&&mediaRecorder&&"inactive"===mediaRecorder.state?(delete config.timeslice,void mediaRecorder.start(6e5)):void setTimeout(looper,1e3)}(),"inactive"!==mediaRecorder.state&&"stopped"!==mediaRecorder.state&&mediaRecorder.stop())},"number"==typeof config.timeSlice?(updateTimeStamp(),mediaRecorder.start(config.timeSlice)):mediaRecorder.start(36e5),config.initCallback&&config.initCallback()},this.timestamps=[],this.stop=function(callback){callback=callback||function(){},self.manuallyStopped=!0,mediaRecorder&&(this.recordingCallback=callback,"recording"===mediaRecorder.state&&mediaRecorder.stop(),"number"==typeof config.timeSlice&&setTimeout(function(){self.blob=new Blob(arrayOfBlobs,{type:getMimeType(config)}),self.recordingCallback(self.blob)},100))},this.pause=function(){mediaRecorder&&"recording"===mediaRecorder.state&&mediaRecorder.pause()},this.resume=function(){mediaRecorder&&"paused"===mediaRecorder.state&&mediaRecorder.resume()},this.clearRecordedData=function(){mediaRecorder&&"recording"===mediaRecorder.state&&self.stop(clearRecordedDataCB),clearRecordedDataCB()};var mediaRecorder;this.getInternalRecorder=function(){return mediaRecorder},this.blob=null,this.getState=function(){return mediaRecorder?mediaRecorder.state||"inactive":"inactive"};var allStates=[];this.getAllStates=function(){return allStates},"undefined"==typeof config.checkForInactiveTracks&&(config.checkForInactiveTracks=!1);var self=this;!function looper(){if(mediaRecorder&&config.checkForInactiveTracks!==!1)return isMediaStreamActive()===!1?(config.disableLogs||console.log("MediaStream seems stopped."),void self.stop()):void setTimeout(looper,1e3)}(),this.name="MediaStreamRecorder",this.toString=function(){return this.name}}function StereoAudioRecorder(mediaStream,config){function isMediaStreamActive(){if(config.checkForInactiveTracks===!1)return!0;if("active"in mediaStream){if(!mediaStream.active)return!1}else if("ended"in mediaStream&&mediaStream.ended)return!1;return!0}function mergeLeftRightBuffers(config,callback){function mergeAudioBuffers(config,cb){function interpolateArray(data,newSampleRate,oldSampleRate){var fitCount=Math.round(data.length*(newSampleRate/oldSampleRate)),newData=[],springFactor=Number((data.length-1)/(fitCount-1));newData[0]=data[0];for(var i=1;i96e3)&&(config.disableLogs||console.log("sample-rate must be under range 22050 and 96000.")),config.disableLogs||config.desiredSampRate&&console.log("Desired sample-rate: "+config.desiredSampRate);var isPaused=!1;this.pause=function(){isPaused=!0},this.resume=function(){if(isMediaStreamActive()===!1)throw"Please make sure MediaStream is active.";return recording?void(isPaused=!1):(config.disableLogs||console.log("Seems recording has been restarted."),void this.record())},this.clearRecordedData=function(){config.checkForInactiveTracks=!1,recording&&this.stop(clearRecordedDataCB),clearRecordedDataCB()},this.name="StereoAudioRecorder",this.toString=function(){return this.name};var isAudioProcessStarted=!1;jsAudioNode.onaudioprocess=onAudioProcessDataAvailable,context.createMediaStreamDestination?jsAudioNode.connect(context.createMediaStreamDestination()):jsAudioNode.connect(context.destination),this.leftchannel=leftchannel,this.rightchannel=rightchannel,this.numberOfAudioChannels=numberOfAudioChannels,this.desiredSampRate=desiredSampRate,this.sampleRate=sampleRate,self.recordingLength=recordingLength;var intervalsBasedBuffers={left:[],right:[],recordingLength:0}}function CanvasRecorder(htmlElement,config){function clearRecordedDataCB(){whammy.frames=[],isRecording=!1,isPausedRecording=!1}function cloneCanvas(){var newCanvas=document.createElement("canvas"),context=newCanvas.getContext("2d");return newCanvas.width=htmlElement.width,newCanvas.height=htmlElement.height,context.drawImage(htmlElement,0,0),newCanvas}function drawCanvasFrame(){if(isPausedRecording)return lastTime=(new Date).getTime(),setTimeout(drawCanvasFrame,500);if("canvas"===htmlElement.nodeName.toLowerCase()){var duration=(new Date).getTime()-lastTime;return lastTime=(new Date).getTime(),whammy.frames.push({image:cloneCanvas(),duration:duration}),void(isRecording&&setTimeout(drawCanvasFrame,config.frameInterval))}html2canvas(htmlElement,{grabMouse:"undefined"==typeof config.showMousePointer||config.showMousePointer,onrendered:function(canvas){var duration=(new Date).getTime()-lastTime;return duration?(lastTime=(new Date).getTime(),whammy.frames.push({image:canvas.toDataURL("image/webp",1),duration:duration}),void(isRecording&&setTimeout(drawCanvasFrame,config.frameInterval))):setTimeout(drawCanvasFrame,config.frameInterval)}})}if("undefined"==typeof html2canvas)throw"Please link: https://cdn.webrtc-experiment.com/screenshot.js";config=config||{},config.frameInterval||(config.frameInterval=10);var isCanvasSupportsStreamCapturing=!1;["captureStream","mozCaptureStream","webkitCaptureStream"].forEach(function(item){item in document.createElement("canvas")&&(isCanvasSupportsStreamCapturing=!0)});var _isChrome=!(!window.webkitRTCPeerConnection&&!window.webkitGetUserMedia||!window.chrome),chromeVersion=50,matchArray=navigator.userAgent.match(/Chrom(e|ium)\/([0-9]+)\./);_isChrome&&matchArray&&matchArray[2]&&(chromeVersion=parseInt(matchArray[2],10)),_isChrome&&chromeVersion<52&&(isCanvasSupportsStreamCapturing=!1),config.useWhammyRecorder&&(isCanvasSupportsStreamCapturing=!1);var globalCanvas,mediaStreamRecorder;if(isCanvasSupportsStreamCapturing)if(config.disableLogs||console.log("Your browser supports both MediRecorder API and canvas.captureStream!"),htmlElement instanceof HTMLCanvasElement)globalCanvas=htmlElement;else{if(!(htmlElement instanceof CanvasRenderingContext2D))throw"Please pass either HTMLCanvasElement or CanvasRenderingContext2D.";globalCanvas=htmlElement.canvas}else navigator.mozGetUserMedia&&(config.disableLogs||console.error("Canvas recording is NOT supported in Firefox."));var isRecording;this.record=function(){if(isRecording=!0,isCanvasSupportsStreamCapturing&&!config.useWhammyRecorder){var canvasMediaStream;"captureStream"in globalCanvas?canvasMediaStream=globalCanvas.captureStream(25):"mozCaptureStream"in globalCanvas?canvasMediaStream=globalCanvas.mozCaptureStream(25):"webkitCaptureStream"in globalCanvas&&(canvasMediaStream=globalCanvas.webkitCaptureStream(25));try{var mdStream=new MediaStream;mdStream.addTrack(canvasMediaStream.getVideoTracks()[0]),canvasMediaStream=mdStream}catch(e){}if(!canvasMediaStream)throw"captureStream API are NOT available.";mediaStreamRecorder=new MediaStreamRecorder(canvasMediaStream,{mimeType:"video/webm"}),mediaStreamRecorder.record()}else whammy.frames=[],lastTime=(new Date).getTime(),drawCanvasFrame();config.initCallback&&config.initCallback()},this.getWebPImages=function(callback){if("canvas"!==htmlElement.nodeName.toLowerCase())return void callback();var framesLength=whammy.frames.length;whammy.frames.forEach(function(frame,idx){var framesRemaining=framesLength-idx;config.disableLogs||console.log(framesRemaining+"/"+framesLength+" frames remaining"),config.onEncodingCallback&&config.onEncodingCallback(framesRemaining,framesLength);var webp=frame.image.toDataURL("image/webp",1);whammy.frames[idx].image=webp}),config.disableLogs||console.log("Generating WebM"),callback()},this.stop=function(callback){isRecording=!1;var that=this;return isCanvasSupportsStreamCapturing&&mediaStreamRecorder?void mediaStreamRecorder.stop(callback):void this.getWebPImages(function(){whammy.compile(function(blob){config.disableLogs||console.log("Recording finished!"),that.blob=blob,that.blob.forEach&&(that.blob=new Blob([],{type:"video/webm"})),callback&&callback(that.blob),whammy.frames=[]})})};var isPausedRecording=!1;this.pause=function(){if(isPausedRecording=!0,mediaStreamRecorder instanceof MediaStreamRecorder)return void mediaStreamRecorder.pause()},this.resume=function(){return isPausedRecording=!1,mediaStreamRecorder instanceof MediaStreamRecorder?void mediaStreamRecorder.resume():void(isRecording||this.record())},this.clearRecordedData=function(){isRecording&&this.stop(clearRecordedDataCB),clearRecordedDataCB()},this.name="CanvasRecorder",this.toString=function(){return this.name};var lastTime=(new Date).getTime(),whammy=new Whammy.Video(100)}function WhammyRecorder(mediaStream,config){function drawFrames(frameInterval){frameInterval="undefined"!=typeof frameInterval?frameInterval:10;var duration=(new Date).getTime()-lastTime;return duration?isPausedRecording?(lastTime=(new Date).getTime(),setTimeout(drawFrames,100)):(lastTime=(new Date).getTime(),video.paused&&video.play(),context.drawImage(video,0,0,canvas.width,canvas.height),whammy.frames.push({duration:duration,image:canvas.toDataURL("image/webp")}),void(isStopDrawing||setTimeout(drawFrames,frameInterval,frameInterval))):setTimeout(drawFrames,frameInterval,frameInterval)}function asyncLoop(o){var i=-1,length=o.length;!function loop(){return i++,i===length?void o.callback():void setTimeout(function(){o.functionToLoop(loop,i)},1)}()}function dropBlackFrames(_frames,_framesToCheck,_pixTolerance,_frameTolerance,callback){var localCanvas=document.createElement("canvas");localCanvas.width=canvas.width,localCanvas.height=canvas.height;var context2d=localCanvas.getContext("2d"),resultFrames=[],checkUntilNotBlack=_framesToCheck===-1,endCheckFrame=_framesToCheck&&_framesToCheck>0&&_framesToCheck<=_frames.length?_framesToCheck:_frames.length,sampleColor={r:0,g:0,b:0},maxColorDifference=Math.sqrt(Math.pow(255,2)+Math.pow(255,2)+Math.pow(255,2)),pixTolerance=_pixTolerance&&_pixTolerance>=0&&_pixTolerance<=1?_pixTolerance:0,frameTolerance=_frameTolerance&&_frameTolerance>=0&&_frameTolerance<=1?_frameTolerance:0,doNotCheckNext=!1;asyncLoop({length:endCheckFrame,functionToLoop:function(loop,f){var matchPixCount,endPixCheck,maxPixCount,finishImage=function(){!doNotCheckNext&&maxPixCount-matchPixCount<=maxPixCount*frameTolerance||(checkUntilNotBlack&&(doNotCheckNext=!0),resultFrames.push(_frames[f])),loop()};if(doNotCheckNext)finishImage();else{var image=new Image;image.onload=function(){context2d.drawImage(image,0,0,canvas.width,canvas.height);var imageData=context2d.getImageData(0,0,canvas.width,canvas.height);matchPixCount=0,endPixCheck=imageData.data.length,maxPixCount=imageData.data.length/4;for(var pix=0;pix1?2*remaining[0].width:remaining[0].width;var height=1;3!==videosLength&&4!==videosLength||(height=2),5!==videosLength&&6!==videosLength||(height=3),7!==videosLength&&8!==videosLength||(height=4),9!==videosLength&&10!==videosLength||(height=5),canvas.height=remaining[0].height*height}else canvas.width=self.width||360,canvas.height=self.height||240;fullcanvas&&fullcanvas instanceof HTMLVideoElement&&drawImage(fullcanvas),remaining.forEach(function(video,idx){drawImage(video,idx)}),setTimeout(drawVideosToCanvas,self.frameInterval)}}function drawImage(video,idx){if(!isStopDrawingFrames){var x=0,y=0,width=video.width,height=video.height;1===idx&&(x=video.width),2===idx&&(y=video.height),3===idx&&(x=video.width,y=video.height),4===idx&&(y=2*video.height),5===idx&&(x=video.width,y=2*video.height),6===idx&&(y=3*video.height),7===idx&&(x=video.width,y=3*video.height),"undefined"!=typeof video.stream.left&&(x=video.stream.left),"undefined"!=typeof video.stream.top&&(y=video.stream.top),"undefined"!=typeof video.stream.width&&(width=video.stream.width),"undefined"!=typeof video.stream.height&&(height=video.stream.height),context.drawImage(video,x,y,width,height),"function"==typeof video.stream.onRender&&video.stream.onRender(context,x,y,width,height,idx)}}function getMixedStream(){isStopDrawingFrames=!1;var mixedVideoStream=getMixedVideoStream(),mixedAudioStream=getMixedAudioStream();mixedAudioStream&&mixedAudioStream.getAudioTracks().forEach(function(track){mixedVideoStream.addTrack(track)});var fullcanvas;return arrayOfMediaStreams.forEach(function(stream){stream.fullcanvas&&(fullcanvas=!0)}),mixedVideoStream}function getMixedVideoStream(){resetVideoStreams();var capturedStream;"captureStream"in canvas?capturedStream=canvas.captureStream():"mozCaptureStream"in canvas?capturedStream=canvas.mozCaptureStream():self.disableLogs||console.error("Upgrade to latest Chrome or otherwise enable this flag: chrome://flags/#enable-experimental-web-platform-features");var videoStream=new MediaStream;return capturedStream.getVideoTracks().forEach(function(track){videoStream.addTrack(track)}),canvas.stream=videoStream,videoStream}function getMixedAudioStream(){Storage.AudioContextConstructor||(Storage.AudioContextConstructor=new Storage.AudioContext),self.audioContext=Storage.AudioContextConstructor,self.audioSources=[],self.useGainNode===!0&&(self.gainNode=self.audioContext.createGain(),self.gainNode.connect(self.audioContext.destination),self.gainNode.gain.value=0);var audioTracksLength=0;if(arrayOfMediaStreams.forEach(function(stream){if(stream.getAudioTracks().length){audioTracksLength++;var audioSource=self.audioContext.createMediaStreamSource(stream);self.useGainNode===!0&&audioSource.connect(self.gainNode),self.audioSources.push(audioSource)}}),audioTracksLength)return self.audioDestination=self.audioContext.createMediaStreamDestination(),self.audioSources.forEach(function(audioSource){audioSource.connect(self.audioDestination)}),self.audioDestination.stream}function getVideo(stream){var video=document.createElement("video");return setSrcObject(stream,video),video.muted=!0,video.volume=0,video.width=stream.width||self.width||360,video.height=stream.height||self.height||240,video.play(),video}function resetVideoStreams(streams){videos=[],streams=streams||arrayOfMediaStreams,streams.forEach(function(stream){if(stream.getVideoTracks().length){var video=getVideo(stream);video.stream=stream,videos.push(video)}})}var videos=[],isStopDrawingFrames=!1,canvas=document.createElement("canvas"),context=canvas.getContext("2d");canvas.style="opacity:0;position:absolute;z-index:-1;top: -100000000;left:-1000000000; margin-top:-1000000000;margin-left:-1000000000;",(document.body||document.documentElement).appendChild(canvas),this.disableLogs=!1,this.frameInterval=10,this.width=360,this.height=240,this.useGainNode=!0;var self=this,AudioContext=window.AudioContext;"undefined"==typeof AudioContext&&("undefined"!=typeof webkitAudioContext&&(AudioContext=webkitAudioContext),"undefined"!=typeof mozAudioContext&&(AudioContext=mozAudioContext));var URL=window.URL;"undefined"==typeof URL&&"undefined"!=typeof webkitURL&&(URL=webkitURL),"undefined"!=typeof navigator&&"undefined"==typeof navigator.getUserMedia&&("undefined"!=typeof navigator.webkitGetUserMedia&&(navigator.getUserMedia=navigator.webkitGetUserMedia),"undefined"!=typeof navigator.mozGetUserMedia&&(navigator.getUserMedia=navigator.mozGetUserMedia));var MediaStream=window.MediaStream;"undefined"==typeof MediaStream&&"undefined"!=typeof webkitMediaStream&&(MediaStream=webkitMediaStream),"undefined"!=typeof MediaStream&&("getVideoTracks"in MediaStream.prototype||(MediaStream.prototype.getVideoTracks=function(){if(!this.getTracks)return[];var tracks=[];return this.getTracks.forEach(function(track){track.kind.toString().indexOf("video")!==-1&&tracks.push(track)}),tracks},MediaStream.prototype.getAudioTracks=function(){if(!this.getTracks)return[];var tracks=[];return this.getTracks.forEach(function(track){track.kind.toString().indexOf("audio")!==-1&&tracks.push(track)}),tracks}),"undefined"==typeof MediaStream.prototype.stop&&(MediaStream.prototype.stop=function(){this.getTracks().forEach(function(track){track.stop()})}));var Storage={};"undefined"!=typeof AudioContext?Storage.AudioContext=AudioContext:"undefined"!=typeof webkitAudioContext&&(Storage.AudioContext=webkitAudioContext),this.startDrawingFrames=function(){drawVideosToCanvas()},this.appendStreams=function(streams){if(!streams)throw"First parameter is required.";streams instanceof Array||(streams=[streams]),arrayOfMediaStreams.concat(streams),streams.forEach(function(stream){if(stream.getVideoTracks().length){var video=getVideo(stream);video.stream=stream,videos.push(video)}if(stream.getAudioTracks().length&&self.audioContext){var audioSource=self.audioContext.createMediaStreamSource(stream);audioSource.connect(self.audioDestination),self.audioSources.push(audioSource)}})},this.releaseStreams=function(){videos=[],isStopDrawingFrames=!0,self.gainNode&&(self.gainNode.disconnect(),self.gainNode=null),self.audioSources.length&&(self.audioSources.forEach(function(source){source.disconnect()}),self.audioSources=[]),self.audioDestination&&(self.audioDestination.disconnect(),self.audioDestination=null),self.audioContext&&self.audioContext.close(),self.audioContext=null,context.clearRect(0,0,canvas.width,canvas.height),canvas.stream&&(canvas.stream.stop(),canvas.stream=null)},this.resetVideoStreams=function(streams){!streams||streams instanceof Array||(streams=[streams]),resetVideoStreams(streams)},this.name="MultiStreamsMixer",this.toString=function(){return this.name},this.getMixedStream=getMixedStream}function MultiStreamRecorder(arrayOfMediaStreams,options){function getVideoTracks(){var tracks=[];return arrayOfMediaStreams.forEach(function(stream){stream.getVideoTracks().forEach(function(track){tracks.push(track)})}),tracks}arrayOfMediaStreams=arrayOfMediaStreams||[];var mixer,mediaRecorder,self=this;options=options||{mimeType:"video/webm",video:{width:360,height:240}},options.frameInterval||(options.frameInterval=10),options.video||(options.video={}),options.video.width||(options.video.width=360),options.video.height||(options.video.height=240),this.record=function(){mixer=new MultiStreamsMixer(arrayOfMediaStreams),getVideoTracks().length&&(mixer.frameInterval=options.frameInterval||10,mixer.width=options.video.width||360,mixer.height=options.video.height||240,mixer.startDrawingFrames()),options.previewStream&&"function"==typeof options.previewStream&&options.previewStream(mixer.getMixedStream()),mediaRecorder=new MediaStreamRecorder(mixer.getMixedStream(),options),mediaRecorder.record()},this.stop=function(callback){mediaRecorder&&mediaRecorder.stop(function(blob){self.blob=blob,callback(blob),self.clearRecordedData()})},this.pause=function(){mediaRecorder&&mediaRecorder.pause()},this.resume=function(){mediaRecorder&&mediaRecorder.resume()},this.clearRecordedData=function(){mediaRecorder&&(mediaRecorder.clearRecordedData(),mediaRecorder=null),mixer&&(mixer.releaseStreams(),mixer=null)},this.addStreams=function(streams){if(!streams)throw"First parameter is required.";streams instanceof Array||(streams=[streams]),arrayOfMediaStreams.concat(streams),mediaRecorder&&mixer&&mixer.appendStreams(streams)},this.resetVideoStreams=function(streams){mixer&&(!streams||streams instanceof Array||(streams=[streams]),mixer.resetVideoStreams(streams))},this.name="MultiStreamRecorder",this.toString=function(){return this.name}}function RecordRTCPromisesHandler(mediaStream,options){if(!this)throw'Use "new RecordRTCPromisesHandler()"';if("undefined"==typeof mediaStream)throw'First argument "MediaStream" is required.';var self=this;self.recordRTC=new RecordRTC(mediaStream,options),this.startRecording=function(){return new Promise(function(resolve,reject){try{self.recordRTC.startRecording(),resolve()}catch(e){reject(e)}})},this.stopRecording=function(){return new Promise(function(resolve,reject){try{self.recordRTC.stopRecording(function(url){self.blob=self.recordRTC.getBlob(),resolve(url)})}catch(e){reject(e)}})},this.getDataURL=function(callback){return new Promise(function(resolve,reject){try{self.recordRTC.getDataURL(function(dataURL){resolve(dataURL)})}catch(e){reject(e)}})},this.getBlob=function(){return self.recordRTC.getBlob()},this.blob=null}RecordRTC.version="5.4.9","undefined"!=typeof module&&(module.exports=RecordRTC),"function"==typeof define&&define.amd&&define("RecordRTC",[],function(){return RecordRTC}),RecordRTC.getFromDisk=function(type,callback){if(!callback)throw"callback is mandatory.";console.log("Getting recorded "+("all"===type?"blobs":type+" blob ")+" from disk!"),DiskStorage.Fetch(function(dataURL,_type){"all"!==type&&_type===type+"Blob"&&callback&&callback(dataURL),"all"===type&&callback&&callback(dataURL,_type.replace("Blob",""))})},RecordRTC.writeToDisk=function(options){console.log("Writing recorded blob(s) to disk!"),options=options||{},options.audio&&options.video&&options.gif?options.audio.getDataURL(function(audioDataURL){options.video.getDataURL(function(videoDataURL){options.gif.getDataURL(function(gifDataURL){DiskStorage.Store({audioBlob:audioDataURL,videoBlob:videoDataURL,gifBlob:gifDataURL})})})}):options.audio&&options.video?options.audio.getDataURL(function(audioDataURL){options.video.getDataURL(function(videoDataURL){DiskStorage.Store({audioBlob:audioDataURL,videoBlob:videoDataURL})})}):options.audio&&options.gif?options.audio.getDataURL(function(audioDataURL){options.gif.getDataURL(function(gifDataURL){DiskStorage.Store({audioBlob:audioDataURL,gifBlob:gifDataURL})})}):options.video&&options.gif?options.video.getDataURL(function(videoDataURL){options.gif.getDataURL(function(gifDataURL){DiskStorage.Store({videoBlob:videoDataURL,gifBlob:gifDataURL})})}):options.audio?options.audio.getDataURL(function(audioDataURL){DiskStorage.Store({audioBlob:audioDataURL})}):options.video?options.video.getDataURL(function(videoDataURL){DiskStorage.Store({videoBlob:videoDataURL})}):options.gif&&options.gif.getDataURL(function(gifDataURL){DiskStorage.Store({gifBlob:gifDataURL})})},MRecordRTC.getFromDisk=RecordRTC.getFromDisk,MRecordRTC.writeToDisk=RecordRTC.writeToDisk,"undefined"!=typeof RecordRTC&&(RecordRTC.MRecordRTC=MRecordRTC);var browserFakeUserAgent="Fake/5.0 (FakeOS) AppleWebKit/123 (KHTML, like Gecko) Fake/12.3.4567.89 Fake/123.45";!function(that){that&&"undefined"==typeof window&&"undefined"!=typeof global&&(global.navigator={userAgent:browserFakeUserAgent,getUserMedia:function(){}},global.console||(global.console={}),"undefined"!=typeof global.console.log&&"undefined"!=typeof global.console.error||(global.console.error=global.console.log=global.console.log||function(){console.log(arguments)}),"undefined"==typeof document&&(that.document={},document.createElement=document.captureStream=document.mozCaptureStream=function(){var obj={getContext:function(){return obj},play:function(){},pause:function(){},drawImage:function(){},toDataURL:function(){return""}};return obj},that.HTMLVideoElement=function(){}),"undefined"==typeof location&&(that.location={protocol:"file:",href:"",hash:""}),"undefined"==typeof screen&&(that.screen={width:0,height:0}),"undefined"==typeof URL&&(that.URL={createObjectURL:function(){return""},revokeObjectURL:function(){return""}}),that.window=global)}("undefined"!=typeof global?global:null);var requestAnimationFrame=window.requestAnimationFrame;if("undefined"==typeof requestAnimationFrame)if("undefined"!=typeof webkitRequestAnimationFrame)requestAnimationFrame=webkitRequestAnimationFrame;else if("undefined"!=typeof mozRequestAnimationFrame)requestAnimationFrame=mozRequestAnimationFrame;else if("undefined"!=typeof msRequestAnimationFrame)requestAnimationFrame=msRequestAnimationFrame;else if("undefined"==typeof requestAnimationFrame){var lastTime=0;requestAnimationFrame=function(callback,element){var currTime=(new Date).getTime(),timeToCall=Math.max(0,16-(currTime-lastTime)),id=setTimeout(function(){callback(currTime+timeToCall)},timeToCall);return lastTime=currTime+timeToCall,id}}var cancelAnimationFrame=window.cancelAnimationFrame;"undefined"==typeof cancelAnimationFrame&&("undefined"!=typeof webkitCancelAnimationFrame?cancelAnimationFrame=webkitCancelAnimationFrame:"undefined"!=typeof mozCancelAnimationFrame?cancelAnimationFrame=mozCancelAnimationFrame:"undefined"!=typeof msCancelAnimationFrame?cancelAnimationFrame=msCancelAnimationFrame:"undefined"==typeof cancelAnimationFrame&&(cancelAnimationFrame=function(id){clearTimeout(id)}));var AudioContext=window.AudioContext;"undefined"==typeof AudioContext&&("undefined"!=typeof webkitAudioContext&&(AudioContext=webkitAudioContext),"undefined"!=typeof mozAudioContext&&(AudioContext=mozAudioContext));var URL=window.URL;"undefined"==typeof URL&&"undefined"!=typeof webkitURL&&(URL=webkitURL),"undefined"!=typeof navigator&&"undefined"==typeof navigator.getUserMedia&&("undefined"!=typeof navigator.webkitGetUserMedia&&(navigator.getUserMedia=navigator.webkitGetUserMedia),"undefined"!=typeof navigator.mozGetUserMedia&&(navigator.getUserMedia=navigator.mozGetUserMedia));var isEdge=!(navigator.userAgent.indexOf("Edge")===-1||!navigator.msSaveBlob&&!navigator.msSaveOrOpenBlob),isOpera=!!window.opera||navigator.userAgent.indexOf("OPR/")!==-1,isSafari=navigator.userAgent.toLowerCase().indexOf("safari/")!==-1&&navigator.userAgent.toLowerCase().indexOf("chrome/")===-1,isChrome=!isOpera&&!isEdge&&!!navigator.webkitGetUserMedia||isElectron()||navigator.userAgent.toLowerCase().indexOf("chrome/")!==-1,MediaStream=window.MediaStream;"undefined"==typeof MediaStream&&"undefined"!=typeof webkitMediaStream&&(MediaStream=webkitMediaStream),"undefined"!=typeof MediaStream&&("getVideoTracks"in MediaStream.prototype||(MediaStream.prototype.getVideoTracks=function(){if(!this.getTracks)return[];var tracks=[];return this.getTracks().forEach(function(track){track.kind.toString().indexOf("video")!==-1&&tracks.push(track)}),tracks},MediaStream.prototype.getAudioTracks=function(){if(!this.getTracks)return[];var tracks=[];return this.getTracks().forEach(function(track){track.kind.toString().indexOf("audio")!==-1&&tracks.push(track)}),tracks}),"undefined"==typeof MediaStream.prototype.stop&&(MediaStream.prototype.stop=function(){this.getTracks().forEach(function(track){track.stop()})}));var Storage={};"undefined"!=typeof AudioContext?Storage.AudioContext=AudioContext:"undefined"!=typeof webkitAudioContext&&(Storage.AudioContext=webkitAudioContext),"undefined"!=typeof RecordRTC&&(RecordRTC.Storage=Storage),"undefined"!=typeof RecordRTC&&(RecordRTC.MediaStreamRecorder=MediaStreamRecorder),"undefined"!=typeof RecordRTC&&(RecordRTC.StereoAudioRecorder=StereoAudioRecorder),"undefined"!=typeof RecordRTC&&(RecordRTC.CanvasRecorder=CanvasRecorder),"undefined"!=typeof RecordRTC&&(RecordRTC.WhammyRecorder=WhammyRecorder);var Whammy=function(){function WhammyVideo(duration){this.frames=[],this.duration=duration||1,this.quality=.8}function processInWebWorker(_function){var blob=URL.createObjectURL(new Blob([_function.toString(),"this.onmessage = function (eee) {"+_function.name+"(eee.data);}"],{type:"application/javascript"})),worker=new Worker(blob);return URL.revokeObjectURL(blob),worker}function whammyInWebWorker(frames){ +isAudioProcessStarted=!1,callback&&callback(self.blob)})},"undefined"==typeof Storage)var Storage={AudioContextConstructor:null,AudioContext:window.AudioContext||window.webkitAudioContext};Storage.AudioContextConstructor||(Storage.AudioContextConstructor=new Storage.AudioContext);var context=Storage.AudioContextConstructor,audioInput=context.createMediaStreamSource(mediaStream),legalBufferValues=[0,256,512,1024,2048,4096,8192,16384],bufferSize="undefined"==typeof config.bufferSize?4096:config.bufferSize;if(legalBufferValues.indexOf(bufferSize)===-1&&(config.disableLogs||console.log("Legal values for buffer-size are "+JSON.stringify(legalBufferValues,null,"\t"))),context.createJavaScriptNode)jsAudioNode=context.createJavaScriptNode(bufferSize,numberOfAudioChannels,numberOfAudioChannels);else{if(!context.createScriptProcessor)throw"WebAudio API has no support on this browser.";jsAudioNode=context.createScriptProcessor(bufferSize,numberOfAudioChannels,numberOfAudioChannels)}audioInput.connect(jsAudioNode),config.bufferSize||(bufferSize=jsAudioNode.bufferSize);var sampleRate="undefined"!=typeof config.sampleRate?config.sampleRate:context.sampleRate||44100;(sampleRate<22050||sampleRate>96e3)&&(config.disableLogs||console.log("sample-rate must be under range 22050 and 96000.")),config.disableLogs||config.desiredSampRate&&console.log("Desired sample-rate: "+config.desiredSampRate);var isPaused=!1;this.pause=function(){isPaused=!0},this.resume=function(){if(isMediaStreamActive()===!1)throw"Please make sure MediaStream is active.";return recording?void(isPaused=!1):(config.disableLogs||console.log("Seems recording has been restarted."),void this.record())},this.clearRecordedData=function(){config.checkForInactiveTracks=!1,recording&&this.stop(clearRecordedDataCB),clearRecordedDataCB()},this.name="StereoAudioRecorder",this.toString=function(){return this.name};var isAudioProcessStarted=!1;jsAudioNode.onaudioprocess=onAudioProcessDataAvailable,context.createMediaStreamDestination?jsAudioNode.connect(context.createMediaStreamDestination()):jsAudioNode.connect(context.destination),this.leftchannel=leftchannel,this.rightchannel=rightchannel,this.numberOfAudioChannels=numberOfAudioChannels,this.desiredSampRate=desiredSampRate,this.sampleRate=sampleRate,self.recordingLength=recordingLength;var intervalsBasedBuffers={left:[],right:[],recordingLength:0}}function CanvasRecorder(htmlElement,config){function clearRecordedDataCB(){whammy.frames=[],isRecording=!1,isPausedRecording=!1}function cloneCanvas(){var newCanvas=document.createElement("canvas"),context=newCanvas.getContext("2d");return newCanvas.width=htmlElement.width,newCanvas.height=htmlElement.height,context.drawImage(htmlElement,0,0),newCanvas}function drawCanvasFrame(){if(isPausedRecording)return lastTime=(new Date).getTime(),setTimeout(drawCanvasFrame,500);if("canvas"===htmlElement.nodeName.toLowerCase()){var duration=(new Date).getTime()-lastTime;return lastTime=(new Date).getTime(),whammy.frames.push({image:cloneCanvas(),duration:duration}),void(isRecording&&setTimeout(drawCanvasFrame,config.frameInterval))}html2canvas(htmlElement,{grabMouse:"undefined"==typeof config.showMousePointer||config.showMousePointer,onrendered:function(canvas){var duration=(new Date).getTime()-lastTime;return duration?(lastTime=(new Date).getTime(),whammy.frames.push({image:canvas.toDataURL("image/webp",1),duration:duration}),void(isRecording&&setTimeout(drawCanvasFrame,config.frameInterval))):setTimeout(drawCanvasFrame,config.frameInterval)}})}if("undefined"==typeof html2canvas)throw"Please link: https://cdn.webrtc-experiment.com/screenshot.js";config=config||{},config.frameInterval||(config.frameInterval=10);var isCanvasSupportsStreamCapturing=!1;["captureStream","mozCaptureStream","webkitCaptureStream"].forEach(function(item){item in document.createElement("canvas")&&(isCanvasSupportsStreamCapturing=!0)});var _isChrome=!(!window.webkitRTCPeerConnection&&!window.webkitGetUserMedia||!window.chrome),chromeVersion=50,matchArray=navigator.userAgent.match(/Chrom(e|ium)\/([0-9]+)\./);_isChrome&&matchArray&&matchArray[2]&&(chromeVersion=parseInt(matchArray[2],10)),_isChrome&&chromeVersion<52&&(isCanvasSupportsStreamCapturing=!1),config.useWhammyRecorder&&(isCanvasSupportsStreamCapturing=!1);var globalCanvas,mediaStreamRecorder;if(isCanvasSupportsStreamCapturing)if(config.disableLogs||console.log("Your browser supports both MediRecorder API and canvas.captureStream!"),htmlElement instanceof HTMLCanvasElement)globalCanvas=htmlElement;else{if(!(htmlElement instanceof CanvasRenderingContext2D))throw"Please pass either HTMLCanvasElement or CanvasRenderingContext2D.";globalCanvas=htmlElement.canvas}else navigator.mozGetUserMedia&&(config.disableLogs||console.error("Canvas recording is NOT supported in Firefox."));var isRecording;this.record=function(){if(isRecording=!0,isCanvasSupportsStreamCapturing&&!config.useWhammyRecorder){var canvasMediaStream;"captureStream"in globalCanvas?canvasMediaStream=globalCanvas.captureStream(25):"mozCaptureStream"in globalCanvas?canvasMediaStream=globalCanvas.mozCaptureStream(25):"webkitCaptureStream"in globalCanvas&&(canvasMediaStream=globalCanvas.webkitCaptureStream(25));try{var mdStream=new MediaStream;mdStream.addTrack(canvasMediaStream.getVideoTracks()[0]),canvasMediaStream=mdStream}catch(e){}if(!canvasMediaStream)throw"captureStream API are NOT available.";mediaStreamRecorder=new MediaStreamRecorder(canvasMediaStream,{mimeType:config.mimeType||"video/webm"}),mediaStreamRecorder.record()}else whammy.frames=[],lastTime=(new Date).getTime(),drawCanvasFrame();config.initCallback&&config.initCallback()},this.getWebPImages=function(callback){if("canvas"!==htmlElement.nodeName.toLowerCase())return void callback();var framesLength=whammy.frames.length;whammy.frames.forEach(function(frame,idx){var framesRemaining=framesLength-idx;config.disableLogs||console.log(framesRemaining+"/"+framesLength+" frames remaining"),config.onEncodingCallback&&config.onEncodingCallback(framesRemaining,framesLength);var webp=frame.image.toDataURL("image/webp",1);whammy.frames[idx].image=webp}),config.disableLogs||console.log("Generating WebM"),callback()},this.stop=function(callback){isRecording=!1;var that=this;return isCanvasSupportsStreamCapturing&&mediaStreamRecorder?void mediaStreamRecorder.stop(callback):void this.getWebPImages(function(){whammy.compile(function(blob){config.disableLogs||console.log("Recording finished!"),that.blob=blob,that.blob.forEach&&(that.blob=new Blob([],{type:"video/webm"})),callback&&callback(that.blob),whammy.frames=[]})})};var isPausedRecording=!1;this.pause=function(){if(isPausedRecording=!0,mediaStreamRecorder instanceof MediaStreamRecorder)return void mediaStreamRecorder.pause()},this.resume=function(){return isPausedRecording=!1,mediaStreamRecorder instanceof MediaStreamRecorder?void mediaStreamRecorder.resume():void(isRecording||this.record())},this.clearRecordedData=function(){isRecording&&this.stop(clearRecordedDataCB),clearRecordedDataCB()},this.name="CanvasRecorder",this.toString=function(){return this.name};var lastTime=(new Date).getTime(),whammy=new Whammy.Video(100)}function WhammyRecorder(mediaStream,config){function drawFrames(frameInterval){frameInterval="undefined"!=typeof frameInterval?frameInterval:10;var duration=(new Date).getTime()-lastTime;return duration?isPausedRecording?(lastTime=(new Date).getTime(),setTimeout(drawFrames,100)):(lastTime=(new Date).getTime(),video.paused&&video.play(),context.drawImage(video,0,0,canvas.width,canvas.height),whammy.frames.push({duration:duration,image:canvas.toDataURL("image/webp")}),void(isStopDrawing||setTimeout(drawFrames,frameInterval,frameInterval))):setTimeout(drawFrames,frameInterval,frameInterval)}function asyncLoop(o){var i=-1,length=o.length;!function loop(){return i++,i===length?void o.callback():void setTimeout(function(){o.functionToLoop(loop,i)},1)}()}function dropBlackFrames(_frames,_framesToCheck,_pixTolerance,_frameTolerance,callback){var localCanvas=document.createElement("canvas");localCanvas.width=canvas.width,localCanvas.height=canvas.height;var context2d=localCanvas.getContext("2d"),resultFrames=[],checkUntilNotBlack=_framesToCheck===-1,endCheckFrame=_framesToCheck&&_framesToCheck>0&&_framesToCheck<=_frames.length?_framesToCheck:_frames.length,sampleColor={r:0,g:0,b:0},maxColorDifference=Math.sqrt(Math.pow(255,2)+Math.pow(255,2)+Math.pow(255,2)),pixTolerance=_pixTolerance&&_pixTolerance>=0&&_pixTolerance<=1?_pixTolerance:0,frameTolerance=_frameTolerance&&_frameTolerance>=0&&_frameTolerance<=1?_frameTolerance:0,doNotCheckNext=!1;asyncLoop({length:endCheckFrame,functionToLoop:function(loop,f){var matchPixCount,endPixCheck,maxPixCount,finishImage=function(){!doNotCheckNext&&maxPixCount-matchPixCount<=maxPixCount*frameTolerance||(checkUntilNotBlack&&(doNotCheckNext=!0),resultFrames.push(_frames[f])),loop()};if(doNotCheckNext)finishImage();else{var image=new Image;image.onload=function(){context2d.drawImage(image,0,0,canvas.width,canvas.height);var imageData=context2d.getImageData(0,0,canvas.width,canvas.height);matchPixCount=0,endPixCheck=imageData.data.length,maxPixCount=imageData.data.length/4;for(var pix=0;pix1?2*remaining[0].width:remaining[0].width;var height=1;3!==videosLength&&4!==videosLength||(height=2),5!==videosLength&&6!==videosLength||(height=3),7!==videosLength&&8!==videosLength||(height=4),9!==videosLength&&10!==videosLength||(height=5),canvas.height=remaining[0].height*height}else canvas.width=self.width||360,canvas.height=self.height||240;fullcanvas&&fullcanvas instanceof HTMLVideoElement&&drawImage(fullcanvas),remaining.forEach(function(video,idx){drawImage(video,idx)}),setTimeout(drawVideosToCanvas,self.frameInterval)}}function drawImage(video,idx){if(!isStopDrawingFrames){var x=0,y=0,width=video.width,height=video.height;1===idx&&(x=video.width),2===idx&&(y=video.height),3===idx&&(x=video.width,y=video.height),4===idx&&(y=2*video.height),5===idx&&(x=video.width,y=2*video.height),6===idx&&(y=3*video.height),7===idx&&(x=video.width,y=3*video.height),"undefined"!=typeof video.stream.left&&(x=video.stream.left),"undefined"!=typeof video.stream.top&&(y=video.stream.top),"undefined"!=typeof video.stream.width&&(width=video.stream.width),"undefined"!=typeof video.stream.height&&(height=video.stream.height),context.drawImage(video,x,y,width,height),"function"==typeof video.stream.onRender&&video.stream.onRender(context,x,y,width,height,idx)}}function getMixedStream(){isStopDrawingFrames=!1;var mixedVideoStream=getMixedVideoStream(),mixedAudioStream=getMixedAudioStream();mixedAudioStream&&mixedAudioStream.getAudioTracks().forEach(function(track){mixedVideoStream.addTrack(track)});var fullcanvas;return arrayOfMediaStreams.forEach(function(stream){stream.fullcanvas&&(fullcanvas=!0)}),mixedVideoStream}function getMixedVideoStream(){resetVideoStreams();var capturedStream;"captureStream"in canvas?capturedStream=canvas.captureStream():"mozCaptureStream"in canvas?capturedStream=canvas.mozCaptureStream():self.disableLogs||console.error("Upgrade to latest Chrome or otherwise enable this flag: chrome://flags/#enable-experimental-web-platform-features");var videoStream=new MediaStream;return capturedStream.getVideoTracks().forEach(function(track){videoStream.addTrack(track)}),canvas.stream=videoStream,videoStream}function getMixedAudioStream(){Storage.AudioContextConstructor||(Storage.AudioContextConstructor=new Storage.AudioContext),self.audioContext=Storage.AudioContextConstructor,self.audioSources=[],self.useGainNode===!0&&(self.gainNode=self.audioContext.createGain(),self.gainNode.connect(self.audioContext.destination),self.gainNode.gain.value=0);var audioTracksLength=0;if(arrayOfMediaStreams.forEach(function(stream){if(stream.getAudioTracks().length){audioTracksLength++;var audioSource=self.audioContext.createMediaStreamSource(stream);self.useGainNode===!0&&audioSource.connect(self.gainNode),self.audioSources.push(audioSource)}}),audioTracksLength)return self.audioDestination=self.audioContext.createMediaStreamDestination(),self.audioSources.forEach(function(audioSource){audioSource.connect(self.audioDestination)}),self.audioDestination.stream}function getVideo(stream){var video=document.createElement("video");return setSrcObject(stream,video),video.muted=!0,video.volume=0,video.width=stream.width||self.width||360,video.height=stream.height||self.height||240,video.play(),video}function resetVideoStreams(streams){videos=[],streams=streams||arrayOfMediaStreams,streams.forEach(function(stream){if(stream.getVideoTracks().length){var video=getVideo(stream);video.stream=stream,videos.push(video)}})}var videos=[],isStopDrawingFrames=!1,canvas=document.createElement("canvas"),context=canvas.getContext("2d");canvas.style="opacity:0;position:absolute;z-index:-1;top: -100000000;left:-1000000000; margin-top:-1000000000;margin-left:-1000000000;",(document.body||document.documentElement).appendChild(canvas),this.disableLogs=!1,this.frameInterval=10,this.width=360,this.height=240,this.useGainNode=!0;var self=this,AudioContext=window.AudioContext;"undefined"==typeof AudioContext&&("undefined"!=typeof webkitAudioContext&&(AudioContext=webkitAudioContext),"undefined"!=typeof mozAudioContext&&(AudioContext=mozAudioContext));var URL=window.URL;"undefined"==typeof URL&&"undefined"!=typeof webkitURL&&(URL=webkitURL),"undefined"!=typeof navigator&&"undefined"==typeof navigator.getUserMedia&&("undefined"!=typeof navigator.webkitGetUserMedia&&(navigator.getUserMedia=navigator.webkitGetUserMedia),"undefined"!=typeof navigator.mozGetUserMedia&&(navigator.getUserMedia=navigator.mozGetUserMedia));var MediaStream=window.MediaStream;"undefined"==typeof MediaStream&&"undefined"!=typeof webkitMediaStream&&(MediaStream=webkitMediaStream),"undefined"!=typeof MediaStream&&("getVideoTracks"in MediaStream.prototype||(MediaStream.prototype.getVideoTracks=function(){if(!this.getTracks)return[];var tracks=[];return this.getTracks.forEach(function(track){track.kind.toString().indexOf("video")!==-1&&tracks.push(track)}),tracks},MediaStream.prototype.getAudioTracks=function(){if(!this.getTracks)return[];var tracks=[];return this.getTracks.forEach(function(track){track.kind.toString().indexOf("audio")!==-1&&tracks.push(track)}),tracks}),"undefined"==typeof MediaStream.prototype.stop&&(MediaStream.prototype.stop=function(){this.getTracks().forEach(function(track){track.stop()})}));var Storage={};"undefined"!=typeof AudioContext?Storage.AudioContext=AudioContext:"undefined"!=typeof webkitAudioContext&&(Storage.AudioContext=webkitAudioContext),this.startDrawingFrames=function(){drawVideosToCanvas()},this.appendStreams=function(streams){if(!streams)throw"First parameter is required.";streams instanceof Array||(streams=[streams]),arrayOfMediaStreams.concat(streams),streams.forEach(function(stream){if(stream.getVideoTracks().length){var video=getVideo(stream);video.stream=stream,videos.push(video)}if(stream.getAudioTracks().length&&self.audioContext){var audioSource=self.audioContext.createMediaStreamSource(stream);audioSource.connect(self.audioDestination),self.audioSources.push(audioSource)}})},this.releaseStreams=function(){videos=[],isStopDrawingFrames=!0,self.gainNode&&(self.gainNode.disconnect(),self.gainNode=null),self.audioSources.length&&(self.audioSources.forEach(function(source){source.disconnect()}),self.audioSources=[]),self.audioDestination&&(self.audioDestination.disconnect(),self.audioDestination=null),self.audioContext&&self.audioContext.close(),self.audioContext=null,context.clearRect(0,0,canvas.width,canvas.height),canvas.stream&&(canvas.stream.stop(),canvas.stream=null)},this.resetVideoStreams=function(streams){!streams||streams instanceof Array||(streams=[streams]),resetVideoStreams(streams)},this.name="MultiStreamsMixer",this.toString=function(){return this.name},this.getMixedStream=getMixedStream}function MultiStreamRecorder(arrayOfMediaStreams,options){function getVideoTracks(){var tracks=[];return arrayOfMediaStreams.forEach(function(stream){stream.getVideoTracks().forEach(function(track){tracks.push(track)})}),tracks}arrayOfMediaStreams=arrayOfMediaStreams||[];var mixer,mediaRecorder,self=this;options=options||{mimeType:"video/webm",video:{width:360,height:240}},options.frameInterval||(options.frameInterval=10),options.video||(options.video={}),options.video.width||(options.video.width=360),options.video.height||(options.video.height=240),this.record=function(){mixer=new MultiStreamsMixer(arrayOfMediaStreams),getVideoTracks().length&&(mixer.frameInterval=options.frameInterval||10,mixer.width=options.video.width||360,mixer.height=options.video.height||240,mixer.startDrawingFrames()),options.previewStream&&"function"==typeof options.previewStream&&options.previewStream(mixer.getMixedStream()),mediaRecorder=new MediaStreamRecorder(mixer.getMixedStream(),options),mediaRecorder.record()},this.stop=function(callback){mediaRecorder&&mediaRecorder.stop(function(blob){self.blob=blob,callback(blob),self.clearRecordedData()})},this.pause=function(){mediaRecorder&&mediaRecorder.pause()},this.resume=function(){mediaRecorder&&mediaRecorder.resume()},this.clearRecordedData=function(){mediaRecorder&&(mediaRecorder.clearRecordedData(),mediaRecorder=null),mixer&&(mixer.releaseStreams(),mixer=null)},this.addStreams=function(streams){if(!streams)throw"First parameter is required.";streams instanceof Array||(streams=[streams]),arrayOfMediaStreams.concat(streams),mediaRecorder&&mixer&&mixer.appendStreams(streams)},this.resetVideoStreams=function(streams){mixer&&(!streams||streams instanceof Array||(streams=[streams]),mixer.resetVideoStreams(streams))},this.name="MultiStreamRecorder",this.toString=function(){return this.name}}function RecordRTCPromisesHandler(mediaStream,options){if(!this)throw'Use "new RecordRTCPromisesHandler()"';if("undefined"==typeof mediaStream)throw'First argument "MediaStream" is required.';var self=this;self.recordRTC=new RecordRTC(mediaStream,options),this.startRecording=function(){return new Promise(function(resolve,reject){try{self.recordRTC.startRecording(),resolve()}catch(e){reject(e)}})},this.stopRecording=function(){return new Promise(function(resolve,reject){try{self.recordRTC.stopRecording(function(url){self.blob=self.recordRTC.getBlob(),resolve(url)})}catch(e){reject(e)}})},this.getDataURL=function(callback){return new Promise(function(resolve,reject){try{self.recordRTC.getDataURL(function(dataURL){resolve(dataURL)})}catch(e){reject(e)}})},this.getBlob=function(){return self.recordRTC.getBlob()},this.blob=null}RecordRTC.version="5.4.9","undefined"!=typeof module&&(module.exports=RecordRTC),"function"==typeof define&&define.amd&&define("RecordRTC",[],function(){return RecordRTC}),RecordRTC.getFromDisk=function(type,callback){if(!callback)throw"callback is mandatory.";console.log("Getting recorded "+("all"===type?"blobs":type+" blob ")+" from disk!"),DiskStorage.Fetch(function(dataURL,_type){"all"!==type&&_type===type+"Blob"&&callback&&callback(dataURL),"all"===type&&callback&&callback(dataURL,_type.replace("Blob",""))})},RecordRTC.writeToDisk=function(options){console.log("Writing recorded blob(s) to disk!"),options=options||{},options.audio&&options.video&&options.gif?options.audio.getDataURL(function(audioDataURL){options.video.getDataURL(function(videoDataURL){options.gif.getDataURL(function(gifDataURL){DiskStorage.Store({audioBlob:audioDataURL,videoBlob:videoDataURL,gifBlob:gifDataURL})})})}):options.audio&&options.video?options.audio.getDataURL(function(audioDataURL){options.video.getDataURL(function(videoDataURL){DiskStorage.Store({audioBlob:audioDataURL,videoBlob:videoDataURL})})}):options.audio&&options.gif?options.audio.getDataURL(function(audioDataURL){options.gif.getDataURL(function(gifDataURL){DiskStorage.Store({audioBlob:audioDataURL,gifBlob:gifDataURL})})}):options.video&&options.gif?options.video.getDataURL(function(videoDataURL){options.gif.getDataURL(function(gifDataURL){DiskStorage.Store({videoBlob:videoDataURL,gifBlob:gifDataURL})})}):options.audio?options.audio.getDataURL(function(audioDataURL){DiskStorage.Store({audioBlob:audioDataURL})}):options.video?options.video.getDataURL(function(videoDataURL){DiskStorage.Store({videoBlob:videoDataURL})}):options.gif&&options.gif.getDataURL(function(gifDataURL){DiskStorage.Store({gifBlob:gifDataURL})})},MRecordRTC.getFromDisk=RecordRTC.getFromDisk,MRecordRTC.writeToDisk=RecordRTC.writeToDisk,"undefined"!=typeof RecordRTC&&(RecordRTC.MRecordRTC=MRecordRTC);var browserFakeUserAgent="Fake/5.0 (FakeOS) AppleWebKit/123 (KHTML, like Gecko) Fake/12.3.4567.89 Fake/123.45";!function(that){that&&"undefined"==typeof window&&"undefined"!=typeof global&&(global.navigator={userAgent:browserFakeUserAgent,getUserMedia:function(){}},global.console||(global.console={}),"undefined"!=typeof global.console.log&&"undefined"!=typeof global.console.error||(global.console.error=global.console.log=global.console.log||function(){console.log(arguments)}),"undefined"==typeof document&&(that.document={},document.createElement=document.captureStream=document.mozCaptureStream=function(){var obj={getContext:function(){return obj},play:function(){},pause:function(){},drawImage:function(){},toDataURL:function(){return""}};return obj},that.HTMLVideoElement=function(){}),"undefined"==typeof location&&(that.location={protocol:"file:",href:"",hash:""}),"undefined"==typeof screen&&(that.screen={width:0,height:0}),"undefined"==typeof URL&&(that.URL={createObjectURL:function(){return""},revokeObjectURL:function(){return""}}),that.window=global)}("undefined"!=typeof global?global:null);var requestAnimationFrame=window.requestAnimationFrame;if("undefined"==typeof requestAnimationFrame)if("undefined"!=typeof webkitRequestAnimationFrame)requestAnimationFrame=webkitRequestAnimationFrame;else if("undefined"!=typeof mozRequestAnimationFrame)requestAnimationFrame=mozRequestAnimationFrame;else if("undefined"!=typeof msRequestAnimationFrame)requestAnimationFrame=msRequestAnimationFrame;else if("undefined"==typeof requestAnimationFrame){var lastTime=0;requestAnimationFrame=function(callback,element){var currTime=(new Date).getTime(),timeToCall=Math.max(0,16-(currTime-lastTime)),id=setTimeout(function(){callback(currTime+timeToCall)},timeToCall);return lastTime=currTime+timeToCall,id}}var cancelAnimationFrame=window.cancelAnimationFrame;"undefined"==typeof cancelAnimationFrame&&("undefined"!=typeof webkitCancelAnimationFrame?cancelAnimationFrame=webkitCancelAnimationFrame:"undefined"!=typeof mozCancelAnimationFrame?cancelAnimationFrame=mozCancelAnimationFrame:"undefined"!=typeof msCancelAnimationFrame?cancelAnimationFrame=msCancelAnimationFrame:"undefined"==typeof cancelAnimationFrame&&(cancelAnimationFrame=function(id){clearTimeout(id)}));var AudioContext=window.AudioContext;"undefined"==typeof AudioContext&&("undefined"!=typeof webkitAudioContext&&(AudioContext=webkitAudioContext),"undefined"!=typeof mozAudioContext&&(AudioContext=mozAudioContext));var URL=window.URL;"undefined"==typeof URL&&"undefined"!=typeof webkitURL&&(URL=webkitURL),"undefined"!=typeof navigator&&"undefined"==typeof navigator.getUserMedia&&("undefined"!=typeof navigator.webkitGetUserMedia&&(navigator.getUserMedia=navigator.webkitGetUserMedia),"undefined"!=typeof navigator.mozGetUserMedia&&(navigator.getUserMedia=navigator.mozGetUserMedia));var isEdge=!(navigator.userAgent.indexOf("Edge")===-1||!navigator.msSaveBlob&&!navigator.msSaveOrOpenBlob),isOpera=!!window.opera||navigator.userAgent.indexOf("OPR/")!==-1,isSafari=navigator.userAgent.toLowerCase().indexOf("safari/")!==-1&&navigator.userAgent.toLowerCase().indexOf("chrome/")===-1,isChrome=!isOpera&&!isEdge&&!!navigator.webkitGetUserMedia||isElectron()||navigator.userAgent.toLowerCase().indexOf("chrome/")!==-1,MediaStream=window.MediaStream;"undefined"==typeof MediaStream&&"undefined"!=typeof webkitMediaStream&&(MediaStream=webkitMediaStream),"undefined"!=typeof MediaStream&&("getVideoTracks"in MediaStream.prototype||(MediaStream.prototype.getVideoTracks=function(){if(!this.getTracks)return[];var tracks=[];return this.getTracks().forEach(function(track){track.kind.toString().indexOf("video")!==-1&&tracks.push(track)}),tracks},MediaStream.prototype.getAudioTracks=function(){if(!this.getTracks)return[];var tracks=[];return this.getTracks().forEach(function(track){track.kind.toString().indexOf("audio")!==-1&&tracks.push(track)}),tracks}),"undefined"==typeof MediaStream.prototype.stop&&(MediaStream.prototype.stop=function(){this.getTracks().forEach(function(track){track.stop()})}));var Storage={};"undefined"!=typeof AudioContext?Storage.AudioContext=AudioContext:"undefined"!=typeof webkitAudioContext&&(Storage.AudioContext=webkitAudioContext),"undefined"!=typeof RecordRTC&&(RecordRTC.Storage=Storage),"undefined"!=typeof RecordRTC&&(RecordRTC.MediaStreamRecorder=MediaStreamRecorder),"undefined"!=typeof RecordRTC&&(RecordRTC.StereoAudioRecorder=StereoAudioRecorder),"undefined"!=typeof RecordRTC&&(RecordRTC.CanvasRecorder=CanvasRecorder),"undefined"!=typeof RecordRTC&&(RecordRTC.WhammyRecorder=WhammyRecorder);var Whammy=function(){function WhammyVideo(duration){this.frames=[],this.duration=duration||1,this.quality=.8}function processInWebWorker(_function){var blob=URL.createObjectURL(new Blob([_function.toString(),"this.onmessage = function (eee) {"+_function.name+"(eee.data);}"],{type:"application/javascript"})),worker=new Worker(blob);return URL.revokeObjectURL(blob),worker}function whammyInWebWorker(frames){ function ArrayToWebM(frames){var info=checkFrames(frames);if(!info)return[];for(var clusterMaxDuration=3e4,EBML=[{id:440786851,data:[{data:1,id:17030},{data:1,id:17143},{data:4,id:17138},{data:8,id:17139},{data:"webm",id:17026},{data:2,id:17031},{data:2,id:17029}]},{id:408125543,data:[{id:357149030,data:[{data:1e6,id:2807729},{data:"whammy",id:19840},{data:"whammy",id:22337},{data:doubleToString(info.duration),id:17545}]},{id:374648427,data:[{id:174,data:[{data:1,id:215},{data:1,id:29637},{data:0,id:156},{data:"und",id:2274716},{data:"V_VP8",id:134},{data:"VP8",id:2459272},{data:1,id:131},{id:224,data:[{data:info.width,id:176},{data:info.height,id:186}]}]}]}]}],frameNumber=0,clusterTimecode=0;frameNumber0;)parts.push(255&num),num>>=8;return new Uint8Array(parts.reverse())}function strToBuffer(str){return new Uint8Array(str.split("").map(function(e){return e.charCodeAt(0)}))}function bitsToBuffer(bits){var data=[],pad=bits.length%8?new Array(9-bits.length%8).join("0"):"";bits=pad+bits;for(var i=0;i127)throw"TrackNumber > 127 not supported";var out=[128|data.trackNum,data.timecode>>8,255&data.timecode,flags].map(function(e){return String.fromCharCode(e)}).join("")+data.frame;return out}function parseWebP(riff){for(var VP8=riff.RIFF[0].WEBP[0],frameStart=VP8.indexOf("Â*"),i=0,c=[];i<4;i++)c[i]=VP8.charCodeAt(frameStart+3+i);var width,height,tmp;return tmp=c[1]<<8|c[0],width=16383&tmp,tmp=c[3]<<8|c[2],height=16383&tmp,{width:width,height:height,data:VP8,riff:riff}}function getStrLength(string,offset){return parseInt(string.substr(offset+4,4).split("").map(function(i){var unpadded=i.charCodeAt(0).toString(2);return new Array(8-unpadded.length+1).join("0")+unpadded}).join(""),2)}function parseRIFF(string){for(var offset=0,chunks={};offsetwindow.demoVersion = '2018.09.13'; + + + + + + \ No newline at end of file diff --git a/RecordRTC/test/html-test-files/video-recording-using-WhammyRecorder.html b/RecordRTC/test/html-test-files/video-recording-using-WhammyRecorder.html new file mode 100644 index 00000000..6523b996 --- /dev/null +++ b/RecordRTC/test/html-test-files/video-recording-using-WhammyRecorder.html @@ -0,0 +1,47 @@ +logs: logs
    +getUserMedia: Not invoked yet.
    +onStateChanged: Not initialized yet.
    +blobSize: 0
    +isRecordingStopped: false
    + + + diff --git a/RecordRTC/test/html-test-files/video-recording.html b/RecordRTC/test/html-test-files/video-recording.html new file mode 100644 index 00000000..941d6ee5 --- /dev/null +++ b/RecordRTC/test/html-test-files/video-recording.html @@ -0,0 +1,46 @@ +logs: logs
    +getUserMedia: Not invoked yet.
    +onStateChanged: Not initialized yet.
    +blobSize: 0
    +isRecordingStopped: false
    + + + diff --git a/WebRTC-File-Sharing/index.html b/WebRTC-File-Sharing/index.html index d292b1f4..d9722b24 100644 --- a/WebRTC-File-Sharing/index.html +++ b/WebRTC-File-Sharing/index.html @@ -282,8 +282,11 @@ }; // -------------------------------------------------------- + var SIGNALING_URI = 'wss://websocket-over-nodejs.herokuapp.com:443/'; + var SIGNALING_URI = 'wss://webrtcweb.com:9449/'; + var channel = location.href.replace( /\/|:|#|%|\.|\[|\]/g , ''); - var websocket = new WebSocket('wss://websocket-over-nodejs.herokuapp.com:443/'); + var websocket = new WebSocket(SIGNALING_URI); websocket.onopen = function() { console.log('websocket connection opened!'); websocket.push(JSON.stringify({ diff --git a/audio-broadcast/broadcast-ui.js b/audio-broadcast/broadcast-ui.js index 785cf02b..a1fe035a 100644 --- a/audio-broadcast/broadcast-ui.js +++ b/audio-broadcast/broadcast-ui.js @@ -5,6 +5,7 @@ var config = { openSocket: function(config) { var SIGNALING_SERVER = 'https://socketio-over-nodejs2.herokuapp.com:443/'; + var SIGNALING_SERVER = 'https://webrtcweb.com:9559/'; config.channel = config.channel || location.href.replace(/\/|:|#|%|\.|\[|\]/g, ''); var sender = Math.round(Math.random() * 999999999) + 999999999; diff --git a/broadcast/broadcast-ui.js b/broadcast/broadcast-ui.js index 51939024..1c7f5fc4 100644 --- a/broadcast/broadcast-ui.js +++ b/broadcast/broadcast-ui.js @@ -5,6 +5,7 @@ var config = { openSocket: function(config) { var SIGNALING_SERVER = 'https://socketio-over-nodejs2.herokuapp.com:443/'; + var SIGNALING_SERVER = 'https://webrtcweb.com:9559/'; config.channel = config.channel || location.href.replace(/\/|:|#|%|\.|\[|\]/g, ''); var sender = Math.round(Math.random() * 999999999) + 999999999; diff --git a/chat-hangout/hangout-ui.js b/chat-hangout/hangout-ui.js index e4db6d27..0145100b 100644 --- a/chat-hangout/hangout-ui.js +++ b/chat-hangout/hangout-ui.js @@ -5,6 +5,7 @@ var config = { openSocket: function(config) { var SIGNALING_SERVER = 'https://socketio-over-nodejs2.herokuapp.com:443/'; + var SIGNALING_SERVER = 'https://webrtcweb.com:9559/'; config.channel = config.channel || location.href.replace(/\/|:|#|%|\.|\[|\]/g, ''); var sender = Math.round(Math.random() * 999999999) + 999999999; diff --git a/cordova-mobile-apps/README.md b/cordova-mobile-apps/README.md index a4690d5d..5f5a92f3 100644 --- a/cordova-mobile-apps/README.md +++ b/cordova-mobile-apps/README.md @@ -2,6 +2,8 @@ 1. Video-Conferencing / [online demo](https://rtcmulticonnection.herokuapp.com/demos/Video-Conferencing.html) / [install from appstore](https://play.google.com/store/apps/details?id=rmc3.videoconference) 2. Scalable-Broadcast / [online demo](https://rtcmulticonnection.herokuapp.com/demos/Scalable-Broadcast.html) / [install from appstore](https://play.google.com/store/apps/details?id=com.webrtc.scalablebroadcast) +3. Audio-Conferencing / [online demo](https://rtcmulticonnection.herokuapp.com/demos/Audio-Conferencing.html) / [install from appstore](https://play.google.com/store/apps/details?id=rmc3.audioconference) +4. File-Sharing / [online demo](https://webrtcweb.com/fs) / [install from appstore](https://play.google.com/store/apps/details?id=com.webrtc.experiment) APK files and other source codes can be found here: diff --git a/cordova-mobile-apps/audio-conference/README.md b/cordova-mobile-apps/audio-conference/README.md new file mode 100644 index 00000000..6e97bfa5 --- /dev/null +++ b/cordova-mobile-apps/audio-conference/README.md @@ -0,0 +1,81 @@ +# Audio Conferencing / [online demo](https://rtcmulticonnection.herokuapp.com/demos/Audio-Conferencing.html) / [install from appstore](https://play.google.com/store/apps/details?id=rmc3.audioconference) + +APK files and other source codes can be found here: + +* https://webrtcweb.com/cordova-apps/audio-conferencing/ + +Articles: + +* [Write iOS apps using RTCMultiConnection](https://www.rtcmulticonnection.org/docs/Write-iOS-Apps/) +* [Write Android apps using RTCMultiConnection](https://www.rtcmulticonnection.org/docs/Write-Android-Apps/) + +# First of All: + +```sh +npm install --save-dev +``` + +# First Step, Modify `config.xml` + +```xml + +``` + +# Second Step, Install Platforms + +```sh +# android +cordova platform add android@6.3.0 + +# or ios +cordova platform add ios@4.4.0 +``` + +# Third Step, Install Plugins + +```sh +cordova plugin install cordova-plugin-whitelist +cordova plugin install cordova-plugin-statusbar +cordova plugin install cordova-plugin-device +cordova plugin install cordova-plugin-android-permissions + +# if you want to download images or files +cordova plugin add https://github.com/devgeeks/Canvas2ImagePlugin.git +``` + +# Fourth Step, Build APK + +```sh +cordova build android +``` + +# Fifth Step, Modify `AndroidManifest` + +Path: `./platforms/android/AndroidManifest` + +Find ` + + + + + +``` + +# Six Step, Build APK Again + +```sh +cordova build android +``` + +Now install APK on your android device. + +For more info, please check: + +* [docs/ios-android.md](https://github.com/muaz-khan/RTCMultiConnection/blob/master/docs/ios-android.md) + +# License + +[All Cordova Mobile Apps](https://github.com/muaz-khan/cordova-mobile-apps) are released under [MIT licence](https://www.webrtc-experiment.com/licence/) . Copyright (c) [Muaz Khan](https://plus.google.com/+MuazKhan). diff --git a/cordova-mobile-apps/audio-conference/config.xml b/cordova-mobile-apps/audio-conference/config.xml new file mode 100644 index 00000000..db43b9bd --- /dev/null +++ b/cordova-mobile-apps/audio-conference/config.xml @@ -0,0 +1,45 @@ + + + Audio Conferencing + + Multi-user (many-to-many) audio chat using mesh networking model. + Muaz khan + + + + + + + + + + + + + + + NSAppTransportSecurity + + NSAllowsArbitraryLoads + + + + + + WebRTC uses your microphone to make voice calls. + + + + + + + + + + + + + + + + diff --git a/cordova-mobile-apps/video conference/hooks/README.md b/cordova-mobile-apps/audio-conference/hooks/README.md similarity index 100% rename from cordova-mobile-apps/video conference/hooks/README.md rename to cordova-mobile-apps/audio-conference/hooks/README.md diff --git a/cordova-mobile-apps/video conference/hooks/iosrtc-swift-support.js b/cordova-mobile-apps/audio-conference/hooks/iosrtc-swift-support.js similarity index 100% rename from cordova-mobile-apps/video conference/hooks/iosrtc-swift-support.js rename to cordova-mobile-apps/audio-conference/hooks/iosrtc-swift-support.js diff --git a/cordova-mobile-apps/video conference/node_modules/README.md b/cordova-mobile-apps/audio-conference/node_modules/README.md similarity index 100% rename from cordova-mobile-apps/video conference/node_modules/README.md rename to cordova-mobile-apps/audio-conference/node_modules/README.md diff --git a/cordova-mobile-apps/audio-conference/package.json b/cordova-mobile-apps/audio-conference/package.json new file mode 100644 index 00000000..31f7833b --- /dev/null +++ b/cordova-mobile-apps/audio-conference/package.json @@ -0,0 +1,23 @@ +{ + "name": "rmc3.audiooconference", + "version": "3.0.3", + "displayName": "Audio Conferencing", + "cordova": { + "platforms": [ + "android" + ], + "plugins": { + "cordova-plugin-iosrtc": {}, + "cordova-plugin-android-permissions": {}, + "cordova-plugin-statusbar": {} + } + }, + "dependencies": { + "cordova-android": "^6.3.0", + "cordova-plugin-android-permissions": "^1.0.0", + "cordova-plugin-device": "^2.0.2", + "cordova-plugin-iosrtc": "latest", + "cordova-plugin-statusbar": "^2.4.2", + "cordova-plugin-whitelist": "^1.3.3" + } +} \ No newline at end of file diff --git a/cordova-mobile-apps/video conference/www/README.md b/cordova-mobile-apps/audio-conference/www/README.md similarity index 100% rename from cordova-mobile-apps/video conference/www/README.md rename to cordova-mobile-apps/audio-conference/www/README.md diff --git a/cordova-mobile-apps/audio-conference/www/css/audios.css b/cordova-mobile-apps/audio-conference/www/css/audios.css new file mode 100644 index 00000000..8f4bcab1 --- /dev/null +++ b/cordova-mobile-apps/audio-conference/www/css/audios.css @@ -0,0 +1 @@ +#audios-container audio {} \ No newline at end of file diff --git a/cordova-mobile-apps/video conference/www/css/stylesheet.css b/cordova-mobile-apps/audio-conference/www/css/stylesheet.css similarity index 100% rename from cordova-mobile-apps/video conference/www/css/stylesheet.css rename to cordova-mobile-apps/audio-conference/www/css/stylesheet.css diff --git a/cordova-mobile-apps/video conference/www/img/cross-icon.png b/cordova-mobile-apps/audio-conference/www/img/cross-icon.png similarity index 100% rename from cordova-mobile-apps/video conference/www/img/cross-icon.png rename to cordova-mobile-apps/audio-conference/www/img/cross-icon.png diff --git a/cordova-mobile-apps/audio-conference/www/img/icon.png b/cordova-mobile-apps/audio-conference/www/img/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..8c60bbdc35676e9582a2b382b4bd7fde948267c3 GIT binary patch literal 4262 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 O4MXUg>DA~u$NmR{$)y+o literal 0 HcmV?d00001 diff --git a/cordova-mobile-apps/video conference/www/img/logo.png b/cordova-mobile-apps/audio-conference/www/img/logo.png similarity index 100% rename from cordova-mobile-apps/video conference/www/img/logo.png rename to cordova-mobile-apps/audio-conference/www/img/logo.png diff --git a/cordova-mobile-apps/video conference/www/img/menu-icon.png b/cordova-mobile-apps/audio-conference/www/img/menu-icon.png similarity index 100% rename from cordova-mobile-apps/video conference/www/img/menu-icon.png rename to cordova-mobile-apps/audio-conference/www/img/menu-icon.png diff --git a/cordova-mobile-apps/video conference/www/img/reload-icon.png b/cordova-mobile-apps/audio-conference/www/img/reload-icon.png similarity index 100% rename from cordova-mobile-apps/video conference/www/img/reload-icon.png rename to cordova-mobile-apps/audio-conference/www/img/reload-icon.png diff --git a/cordova-mobile-apps/audio-conference/www/index.html b/cordova-mobile-apps/audio-conference/www/index.html new file mode 100644 index 00000000..096fe9f9 --- /dev/null +++ b/cordova-mobile-apps/audio-conference/www/index.html @@ -0,0 +1,77 @@ + + + + + + + Audio Conferencing + + + + + + + + + + + + + + +
    + + + MenuMenu + + + + +
    + + + +
    +
    + + + + +
    + + + +
    + You:
    +
    +
    + Other person:
    +
    +
    + + + + + + + + diff --git a/cordova-mobile-apps/video conference/www/js/RTCMultiConnection.js b/cordova-mobile-apps/audio-conference/www/js/RTCMultiConnection.js similarity index 100% rename from cordova-mobile-apps/video conference/www/js/RTCMultiConnection.js rename to cordova-mobile-apps/audio-conference/www/js/RTCMultiConnection.js diff --git a/cordova-mobile-apps/audio-conference/www/js/index.js b/cordova-mobile-apps/audio-conference/www/js/index.js new file mode 100644 index 00000000..fcbb12da --- /dev/null +++ b/cordova-mobile-apps/audio-conference/www/js/index.js @@ -0,0 +1,327 @@ +var app = { + logs: [], + initialize: function() { + console.error = window.onerror = console.log = console.debug = console.info = function() { + if (JSON.stringify(arguments).indexOf('iosrtc') !== -1) { + return; + } + + if (JSON.stringify(arguments).indexOf('No Content-Security-Policy') !== -1) { + return; + } + + if (JSON.stringify(arguments).indexOf('<') !== -1) { + return; + } + + app.logs.push(JSON.stringify(arguments, null, ' ')); + }; + + app.bindEvents(); + }, + bindEvents: function() { + document.addEventListener('deviceready', app.onDeviceReady, false); + + document.getElementById('btn-hide-logs').onclick = function() { + var logsPreview = document.getElementById('logs-preview'); + logsPreview.style.display = 'none'; + }; + + document.getElementById('how-to-use').onclick = function(e) { + e.preventDefault(); + var logs = app.getHowToUse(); + var logsPreview = document.getElementById('logs-preview'); + logsPreview.querySelector('.logs').innerHTML = logs.join('
    '); + logsPreview.style.display = 'block'; + }; + + document.getElementById('view-logs').onclick = function(e) { + e.preventDefault(); + var logs = app.logs; + + if(!logs.length) { + logs.push('No logs yet.'); + } + + var logsPreview = document.getElementById('logs-preview'); + logsPreview.querySelector('.logs').innerHTML = logs.join('
    '); + logsPreview.style.display = 'block'; + }; + + document.getElementById('bug-reports').onclick = function(e) { + e.preventDefault(); + var logs = app.getBugReports(); + var logsPreview = document.getElementById('logs-preview'); + logsPreview.querySelector('.logs').innerHTML = logs.join('
    '); + logsPreview.style.display = 'block'; + }; + }, + checkAndroidPermissions: function(callback) { + if (device.platform !== 'Android') { + callback(); + return; + } + + var permissions = cordova.plugins.permissions; + + var arr = [ + permissions.RECORD_AUDIO, + permissions.MODIFY_AUDIO_SETTINGS + ]; + + permissions.hasPermission(arr, function(status) { + if (status.hasPermission) { + callback(); + return; + } + + permissions.requestPermissions(arr, function(status) { + if (status.hasPermission) { + callback(); + return; + } + alert('Please manually enable microphone permissions.'); + }, function() { + alert('Please manually enable microphone permissions.'); + }); + }, function() { + alert('Please manually enable microphone permissions.'); + }); + }, + onDeviceReady: function() { + // original demo: https://rtcmulticonnection.herokuapp.com/demos/audio-conferencing.html + + // ...................................................... + // .......................UI Code........................ + // ...................................................... + document.getElementById('open-room').onclick = function() { + disableInputButtons(); + + app.checkAndroidPermissions(function() { + connection.open(document.getElementById('room-id').value, function() { + showRoomURL(connection.sessionid); + }); + }); + }; + + document.getElementById('join-room').onclick = function() { + disableInputButtons(); + + app.checkAndroidPermissions(function() { + connection.join(document.getElementById('room-id').value, function() { + showRoomURL(connection.sessionid); + }); + }); + }; + + document.getElementById('open-or-join-room').onclick = function() { + disableInputButtons(); + + app.checkAndroidPermissions(function() { + connection.openOrJoin(document.getElementById('room-id').value, function(isRoomExist, roomid) { + showRoomURL(roomid); + }); + }); + }; + + // ...................................................... + // ..................RTCMultiConnection Code............. + // ...................................................... + + window.enableAdapter = false; + + var connection = new RTCMultiConnection(); + + connection.onMediaError = function(error, constraints) { + alert(JSON.stringify(error, null, ' ')); + }; + + // http://www.rtcmulticonnection.org/docs/socketURL/ + connection.socketURL = 'https://rtcmulticonnection.herokuapp.com:443/'; + + connection.socketMessageEvent = 'audio-conference-demo'; + + connection.session = { + audio: true + }; + + connection.mediaConstraints = { + audio: true + }; + + connection.sdpConstraints.mandatory = { + OfferToReceiveAudio: true, + OfferToReceiveVideo: false + }; + + connection.onstream = function(event) { + document.querySelector('header').style.paddingTop = 0; + event.mediaElement.id = null; + event.mediaElement.src = null; + event.mediaElement.pause(); + + if(event.type === 'local') { + document.querySelector('#local-audio').parentNode.style.display = ''; + document.querySelector('#local-audio').src = URL.createObjectURL(event.stream); + } + + if(event.type === 'remote') { + document.querySelector('#remote-audio').parentNode.style.display = ''; + document.querySelector('#remote-audio').src = URL.createObjectURL(event.stream); + } + + connection.fixAllAudios(); + }; + + document.querySelector('#remote-audio').onclick = document.querySelector('#local-audio').onclick = function() { + connection.fixAllAudios(); + }; + + connection.onstreamended = function(event) { + if(event.type === 'local') { + document.querySelector('#local-audio').src = null; + document.querySelector('#local-audio').parentNode.style.display = 'none'; + } + + if(event.type === 'remote') { + document.querySelector('#remote-audio').src = null; + document.querySelector('#remote-audio').parentNode.style.display = 'none'; + } + }; + + function disableInputButtons() { + document.getElementById('open-or-join-room').disabled = true; + document.getElementById('open-room').disabled = true; + document.getElementById('join-room').disabled = true; + document.getElementById('room-id').disabled = true; + } + + // ...................................................... + // ......................Handling Room-ID................ + // ...................................................... + + function showRoomURL(roomid) { + var roomHashURL = '#' + roomid; + var roomQueryStringURL = '?roomid=' + roomid; + + var html = '

    Unique URL for your room:


    '; + + html += 'bit.ly/rmc3_conference' + roomHashURL + ''; + + var roomURLsDiv = document.getElementById('room-urls'); + roomURLsDiv.innerHTML = html; + + roomURLsDiv.style.display = 'block'; + document.getElementById('hide-after-openOrJoin').style.display = 'none'; + } + + (function() { + var params = {}, + r = /([^&=]+)=?([^&]*)/g; + + function d(s) { + return decodeURIComponent(s.replace(/\+/g, ' ')); + } + var match, search = window.location.search; + while (match = r.exec(search.substring(1))) + params[d(match[1])] = d(match[2]); + window.params = params; + })(); + + var roomid = ''; + if (localStorage.getItem(connection.socketMessageEvent)) { + roomid = localStorage.getItem(connection.socketMessageEvent); + } else { + roomid = connection.token(); + } + document.getElementById('room-id').value = roomid; + document.getElementById('room-id').onkeyup = function() { + localStorage.setItem(connection.socketMessageEvent, this.value); + }; + + var hashString = location.hash.replace('#', ''); + if (hashString.length && hashString.indexOf('comment-') == 0) { + hashString = ''; + } + + var roomid = params.roomid; + if (!roomid && hashString.length) { + roomid = hashString; + } + + if (roomid && roomid.length) { + document.getElementById('room-id').value = roomid; + localStorage.setItem(connection.socketMessageEvent, roomid); + + // auto-join-room + (function reCheckRoomPresence() { + connection.checkPresence(roomid, function(isRoomExist) { + if (isRoomExist) { + connection.join(roomid); + return; + } + + setTimeout(reCheckRoomPresence, 5000); + }); + })(); + + disableInputButtons(); + } + + window.closeEverything = function() { + document.querySelector('#local-audio').src = null; + document.querySelector('#remote-audio').src = null; + document.querySelector('#local-audio').parentNode.style.display = 'none'; + document.querySelector('#remote-audio').parentNode.style.display = 'none'; + + connection.close(); + connection.closeSocket(); + + location.reload(); + }; + + connection.fixAllAudios = function() { + Object.keys(connection.streamEvents).forEach(function(key) { + var event = connection.streamEvents[key]; + if(!event) return; + + if(event.type === 'local') { + document.querySelector('#local-audio').parentNode.style.display = ''; + document.querySelector('#local-audio').src = URL.createObjectURL(event.stream); + } + + if(event.type === 'remote') { + document.querySelector('#remote-audio').parentNode.style.display = ''; + document.querySelector('#remote-audio').src = URL.createObjectURL(event.stream); + } + }); + + var nav = document.querySelector('nav'); + nav.style.zIndex = 9; + }; + + connection.onPeerStateChanged = function(event) { + if(event.iceConnectionState.search(/closed|failed/gi) !== -1) { + window.closeEverything(); + } + }; + }, + logs: [], + getHowToUse: function() { + var arr = []; + arr.push('Enter something unique in the text box.'); + arr.push('Click "Open Room" button to start a audio conferencing room.'); + arr.push('Ask your friends to enter same thing in the text box.'); + arr.push('Your friends must click "Join Room" button to join you.'); + arr.push('If you are asked to enable mic, then please grant all such permissions.'); + return arr; + }, + getBugReports: function() { + var arr = []; + arr.push('Please send email to: muazkh@gmail.com'); + arr.push('Or open an issue here: https://github.com/muaz-khan/RTCMultiConnection/issues'); + return arr; + } +}; + +app.initialize(); diff --git a/cordova-mobile-apps/video conference/www/js/menu.js b/cordova-mobile-apps/audio-conference/www/js/menu.js similarity index 100% rename from cordova-mobile-apps/video conference/www/js/menu.js rename to cordova-mobile-apps/audio-conference/www/js/menu.js diff --git a/cordova-mobile-apps/video conference/www/js/socket.io.js b/cordova-mobile-apps/audio-conference/www/js/socket.io.js similarity index 100% rename from cordova-mobile-apps/video conference/www/js/socket.io.js rename to cordova-mobile-apps/audio-conference/www/js/socket.io.js diff --git a/cordova-mobile-apps/file-sharing/README.md b/cordova-mobile-apps/file-sharing/README.md new file mode 100644 index 00000000..57d3658a --- /dev/null +++ b/cordova-mobile-apps/file-sharing/README.md @@ -0,0 +1,81 @@ +# File Sharing / [online demo](https://webrtcweb.com/fs) / [install from appstore](https://play.google.com/store/apps/details?id=com.webrtc.experiment) + +APK files and other source codes can be found here: + +* https://webrtcweb.com/cordova-apps/filesharing/ + +There is an open-sourced **chrome extension** as well: + +* https://github.com/muaz-khan/Chrome-Extensions/tree/master/file-sharing + +Articles: + +* [Write iOS apps using RTCMultiConnection](https://www.rtcmulticonnection.org/docs/Write-iOS-Apps/) +* [Write Android apps using RTCMultiConnection](https://www.rtcmulticonnection.org/docs/Write-Android-Apps/) + +# First of All: + +```sh +npm install --save-dev +``` + +# First Step, Modify `config.xml` + +```xml + +``` + +# Second Step, Install Platforms + +```sh +# android +cordova platform add android@6.3.0 + +# or ios +cordova platform add ios@4.4.0 +``` + +# Third Step, Install Plugins + +```sh +cordova plugin install cordova-plugin-whitelist +cordova plugin install cordova-plugin-statusbar +cordova plugin install cordova-plugin-device +cordova plugin install cordova-plugin-android-permissions + +# if you want to download images or files +cordova plugin add https://github.com/devgeeks/Canvas2ImagePlugin.git +``` + +# Fourth Step, Build APK + +```sh +cordova build android +``` + +# Fifth Step, Modify `AndroidManifest` + +Path: `./platforms/android/AndroidManifest` + +Find ` + +``` + +# Six Step, Build APK Again + +```sh +cordova build android +``` + +Now install APK on your android device. + +For more info, please check: + +* [docs/ios-android.md](https://github.com/muaz-khan/RTCMultiConnection/blob/master/docs/ios-android.md) + +# License + +[All Cordova Mobile Apps](https://github.com/muaz-khan/cordova-mobile-apps) are released under [MIT licence](https://www.webrtc-experiment.com/licence/) . Copyright (c) [Muaz Khan](https://plus.google.com/+MuazKhan). diff --git a/cordova-mobile-apps/file-sharing/config.xml b/cordova-mobile-apps/file-sharing/config.xml new file mode 100644 index 00000000..07f0eedd --- /dev/null +++ b/cordova-mobile-apps/file-sharing/config.xml @@ -0,0 +1,40 @@ + + + File Sharing + + WebRTC File Sharing. + Muaz khan + + + + + + + + + + + + + + + NSAppTransportSecurity + + NSAllowsArbitraryLoads + + + + + + + + + + + + + + + + + diff --git a/cordova-mobile-apps/file-sharing/hooks/README.md b/cordova-mobile-apps/file-sharing/hooks/README.md new file mode 100644 index 00000000..db55f5a1 --- /dev/null +++ b/cordova-mobile-apps/file-sharing/hooks/README.md @@ -0,0 +1,3 @@ +### `iosrtc-swift-support.js` is used only if you install `cordova-iosrtc` + +### i.e. this file is required only for iOS apps. diff --git a/cordova-mobile-apps/file-sharing/hooks/iosrtc-swift-support.js b/cordova-mobile-apps/file-sharing/hooks/iosrtc-swift-support.js new file mode 100644 index 00000000..7bf50878 --- /dev/null +++ b/cordova-mobile-apps/file-sharing/hooks/iosrtc-swift-support.js @@ -0,0 +1,143 @@ +#!/usr/bin/env node + +'use strict'; + +// This hook automates this: +// https://github.com/BasqueVoIPMafia/cordova-plugin-iosrtc/blob/master/docs/Building.md + +var + fs = require("fs"), + path = require("path"), + xcode = require('xcode'), + + BUILD_VERSION = '9.0', + BUILD_VERSION_XCODE = '"' + BUILD_VERSION + '"', + SWIFT_VERSION = '3.0', + SWIFT_VERSION_XCODE = '"' + SWIFT_VERSION + '"', + RUNPATH_SEARCH_PATHS = '@executable_path/Frameworks', + RUNPATH_SEARCH_PATHS_XCODE = '"' + RUNPATH_SEARCH_PATHS + '"', + ENABLE_BITCODE = 'NO', + ENABLE_BITCODE_XCODE = '"' + ENABLE_BITCODE + '"', + BRIDGING_HEADER_END = '/Plugins/cordova-plugin-iosrtc/cordova-plugin-iosrtc-Bridging-Header.h', + COMMENT_KEY = /_comment$/; + + +// Helpers + +// Returns the project name +function getProjectName(protoPath) { + var + cordovaConfigPath = path.join(protoPath, 'config.xml'), + content = fs.readFileSync(cordovaConfigPath, 'utf-8'); + + return /([\s\S]*)<\/name>/mi.exec(content)[1].trim(); +} + +// Drops the comments +function nonComments(obj) { + var + keys = Object.keys(obj), + newObj = {}, + i = 0; + + for (i; i < keys.length; i += 1) { + if (!COMMENT_KEY.test(keys[i])) { + newObj[keys[i]] = obj[keys[i]]; + } + } + + return newObj; +} + + +// Starting here + +module.exports = function (context) { + var + projectRoot = context.opts.projectRoot, + projectName = getProjectName(projectRoot), + xcconfigPath = path.join(projectRoot, '/platforms/ios/cordova/build.xcconfig'), + xcodeProjectName = projectName + '.xcodeproj', + xcodeProjectPath = path.join(projectRoot, 'platforms', 'ios', xcodeProjectName, 'project.pbxproj'), + swiftBridgingHead = projectName + BRIDGING_HEADER_END, + swiftBridgingHeadXcode = '"' + swiftBridgingHead + '"', + swiftOptions = [''], // <-- begin to file appending AFTER initial newline + xcodeProject; + + // Checking if the project files are in the right place + if (!fs.existsSync(xcodeProjectPath)) { + debugerror('an error occurred searching the project file at: "' + xcodeProjectPath + '"'); + + return; + } + debug('".pbxproj" project file found: ' + xcodeProjectPath); + + if (!fs.existsSync(xcconfigPath)) { + debugerror('an error occurred searching the project file at: "' + xcconfigPath + '"'); + + return; + } + debug('".xcconfig" project file found: ' + xcconfigPath); + + xcodeProject = xcode.project(xcodeProjectPath); + + // Showing info about the tasks to do + debug('fixing issues in the generated project files:'); + debug('- "iOS Deployment Target" and "Deployment Target" to: ' + BUILD_VERSION_XCODE); + debug('- "Runpath Search Paths" to: ' + RUNPATH_SEARCH_PATHS_XCODE); + debug('- "Objective-C Bridging Header" to: ' + swiftBridgingHeadXcode); + debug('- "ENABLE_BITCODE" set to: ' + ENABLE_BITCODE_XCODE); + debug('- "SWIFT_VERSION" set to: ' + SWIFT_VERSION_XCODE); + + + // Massaging the files + + // "build.xcconfig" + swiftOptions.push('LD_RUNPATH_SEARCH_PATHS = ' + RUNPATH_SEARCH_PATHS); + swiftOptions.push('SWIFT_OBJC_BRIDGING_HEADER = ' + swiftBridgingHead); + swiftOptions.push('IPHONEOS_DEPLOYMENT_TARGET = ' + BUILD_VERSION); + swiftOptions.push('ENABLE_BITCODE = ' + ENABLE_BITCODE); + swiftOptions.push('SWIFT_VERSION = ' + SWIFT_VERSION); + // NOTE: Not needed + // swiftOptions.push('EMBEDDED_CONTENT_CONTAINS_SWIFT = YES'); + fs.appendFileSync(xcconfigPath, swiftOptions.join('\n')); + debug('file correctly fixed: ' + xcconfigPath); + + // "project.pbxproj" + // Parsing it + xcodeProject.parse(function (error) { + var configurations, buildSettings; + + if (error) { + debugerror('an error occurred during the parsing of the project file'); + + return; + } + + + configurations = nonComments(xcodeProject.pbxXCBuildConfigurationSection()); + // Adding or changing the parameters we need + Object.keys(configurations).forEach(function (config) { + buildSettings = configurations[config].buildSettings; + buildSettings.LD_RUNPATH_SEARCH_PATHS = RUNPATH_SEARCH_PATHS_XCODE; + buildSettings.SWIFT_OBJC_BRIDGING_HEADER = swiftBridgingHeadXcode; + buildSettings.IPHONEOS_DEPLOYMENT_TARGET = BUILD_VERSION_XCODE; + buildSettings.ENABLE_BITCODE = ENABLE_BITCODE_XCODE; + buildSettings.SWIFT_VERSION = SWIFT_VERSION_XCODE; + }); + + // Writing the file again + fs.writeFileSync(xcodeProjectPath, xcodeProject.writeSync(), 'utf-8'); + debug('file correctly fixed: ' + xcodeProjectPath); + }); +}; + + +function debug(msg) { + console.log('iosrtc-swift-support.js [INFO] ' + msg); +} + + +function debugerror(msg) { + console.error('iosrtc-swift-support.js [ERROR] ' + msg); +} diff --git a/cordova-mobile-apps/file-sharing/node_modules/README.md b/cordova-mobile-apps/file-sharing/node_modules/README.md new file mode 100644 index 00000000..19ef8a52 --- /dev/null +++ b/cordova-mobile-apps/file-sharing/node_modules/README.md @@ -0,0 +1,3 @@ +```sh +npm install --save-dev +``` diff --git a/cordova-mobile-apps/file-sharing/package.json b/cordova-mobile-apps/file-sharing/package.json new file mode 100644 index 00000000..12403fd8 --- /dev/null +++ b/cordova-mobile-apps/file-sharing/package.json @@ -0,0 +1,32 @@ +{ + "name": "com.webrtc.experiment", + "version": "1.0.0", + "displayName": "FileSharing", + "cordova": { + "platforms": [ + "android" + ], + "plugins": { + "cordova-plugin-whitelist": {}, + "cordova-plugin-statusbar": {}, + "cordova-plugin-x-socialsharing": {}, + "cordova-plugin-device": {}, + "cordova-plugin-file-transfer": {}, + "org.devgeeks.Canvas2ImagePlugin": {}, + "cordova-plugin-android-permissions": {} + } + }, + "dependencies": { + "cordova-android": "^6.3.0", + "cordova-plugin-android-permissions": "^1.0.0", + "cordova-plugin-canvas2image": "git+https://github.com/devgeeks/Canvas2ImagePlugin.git", + "cordova-plugin-device": "^2.0.2", + "cordova-plugin-file": "^6.0.1", + "cordova-plugin-file-transfer": "^1.7.1", + "cordova-plugin-statusbar": "^2.4.2", + "cordova-plugin-whitelist": "^1.3.2", + "cordova-plugin-x-socialsharing": "^5.4.0", + "es6-promise-plugin": "^4.2.2", + "org.devgeeks.Canvas2ImagePlugin": "https://github.com/devgeeks/Canvas2ImagePlugin.git" + } +} \ No newline at end of file diff --git a/cordova-mobile-apps/file-sharing/www/README.md b/cordova-mobile-apps/file-sharing/www/README.md new file mode 100644 index 00000000..1cfb43e3 --- /dev/null +++ b/cordova-mobile-apps/file-sharing/www/README.md @@ -0,0 +1,5 @@ +### All Your HTML code goes in this directory + +### You can remove all files + +### And add your own files diff --git a/cordova-mobile-apps/file-sharing/www/css/index.css b/cordova-mobile-apps/file-sharing/www/css/index.css new file mode 100644 index 00000000..6e690905 --- /dev/null +++ b/cordova-mobile-apps/file-sharing/www/css/index.css @@ -0,0 +1,169 @@ +* { + margin: 0; + padding: 0; +} + +html, body { + background-color: white; + font-family: 'Open Sans', 'Segoe UI Light','Segoe UI',Verdana,Arial; + font-size: 1.1em; + overflow: hidden; +} + +.overlay { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: black; + z-index: 1; +} + +.btn-select-file { + position: absolute; + z-index: 2; + left: 40%; + top: 40%; + width: 100px; + height: 100px; + -webkit-user-select: none; + border-radius: 50%; + text-shadow: 2px 2px white; + border: 1px solid #1B1B1B; + background-color: #D5D8D8; + cursor: pointer; + background-image: url(data:image/png;base64,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); + outline: none; +} + +.btn-select-file:hover { + background-color: #BBBBBB; +} + +.btn-select-file:active { + background-color: #7F7B7B; +} + +iframe, img, video { + position: absolute; + width: 80%; + height: 96%; + top: 40px; + left: 0; + right: 20; + bottom: 0; + border: 0; + outline: 0; + z-index: 5; + display: none; +} + +img, video { + display: none; +} + +#logs { + position: absolute; + background: white; + border-left: 1px solid black; + width: 20%; + top: 40px; + right: 0; + bottom: 0; + z-index: 6; + font-size: medium; + overflow: auto; + height: 100%; +} + +#logs p { + padding: 2px 4px; + border-bottom: 1px solid black; +} + +header { + position: absolute; + background: white; + text-align: center; + border-bottom: 1px solid black; + width: 100%; + height: 32px; + top: 0; + right: 0; + left: 0; + z-index: 7; + padding-top: 8px; +} + +#number-of-users, #exit-app { + position: absolute; + right: 1px; + top: 1px; + width: 36px; + height: 30px; + -webkit-user-select: none; + border-radius: 50%; + border: 1px solid #1D1C1C; + background-color: #E4FFDD; + z-index: 8; + text-align: center; + padding-top: 6px; +} + +#exit-app { + height: auto; + right: auto; + left: 3px; + color: #4A0C0C; + border-color: red; + border-radius: 3px; + width: auto; + padding-left: 10px; + padding-right: 10px; + background-color: #FD9E9E; + font-weight: bold; + padding-top: 2px; + padding-bottom: 2px; + top: 3px; + cursor: pointer; +} + +#room-id { + outline: none; + border: 1px solid #949494; + padding: 1px 3px; + font-size: 100%; +} + +#join-room { + border: 1px solid #949494; + background: #E2E2E2; + padding: 1px 3px; + border-left: 0; + font-size: 100%; +} + +@media all and (max-width: 500px) { + #logs { + position: fixed; + bottom: 0; + left: 0; + width: 100%; + top: auto; + height: 40%; + border-left: 0; + border-top: 1px solid black; + } + + .btn-select-file { + top: 20%; + left: 40%; + } + + #room-id { + width: 40%; + } +} + +hr {border:0;} \ No newline at end of file diff --git a/cordova-mobile-apps/file-sharing/www/img/btn-select-file.png b/cordova-mobile-apps/file-sharing/www/img/btn-select-file.png new file mode 100644 index 0000000000000000000000000000000000000000..6498260024ee8411181896cdcc50fcc9d3fdcdcb GIT binary patch literal 4289 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 literal 0 HcmV?d00001 diff --git a/cordova-mobile-apps/file-sharing/www/index.html b/cordova-mobile-apps/file-sharing/www/index.html new file mode 100644 index 00000000..7642d123 --- /dev/null +++ b/cordova-mobile-apps/file-sharing/www/index.html @@ -0,0 +1,41 @@ + + + + File Sharing using RTCMultiConnection-v3.0 + + + + + + + + + + + + + + + + +
    + + + + + + +
    +
    0
    +
    Exit
    +
    + + + + + + + + + + diff --git a/cordova-mobile-apps/file-sharing/www/js/RTCMultiConnection.min.js b/cordova-mobile-apps/file-sharing/www/js/RTCMultiConnection.min.js new file mode 100644 index 00000000..05e614ec --- /dev/null +++ b/cordova-mobile-apps/file-sharing/www/js/RTCMultiConnection.min.js @@ -0,0 +1,18 @@ +'use strict'; + +// Last time updated: 2018-10-27 5:25:44 AM UTC + +// _________________________ +// RTCMultiConnection v3.5.3 + +// Open-Sourced: https://github.com/muaz-khan/RTCMultiConnection + +// -------------------------------------------------- +// Muaz Khan - www.MuazKhan.com +// MIT License - www.WebRTC-Experiment.com/licence +// -------------------------------------------------- + +"use strict";var RTCMultiConnection=function(roomid,forceOptions){function SocketConnection(connection,connectCallback){function updateExtraBackup(remoteUserId,extra){connection.peersBackup[remoteUserId]||(connection.peersBackup[remoteUserId]={userid:remoteUserId,extra:{}}),connection.peersBackup[remoteUserId].extra=extra}function onMessageEvent(message){if(message.remoteUserId==connection.userid){if(connection.peers[message.sender]&&connection.peers[message.sender].extra!=message.message.extra&&(connection.peers[message.sender].extra=message.extra,connection.onExtraDataUpdated({userid:message.sender,extra:message.extra}),updateExtraBackup(message.sender,message.extra)),message.message.streamSyncNeeded&&connection.peers[message.sender]){var stream=connection.streamEvents[message.message.streamid];if(!stream||!stream.stream)return;var action=message.message.action;if("ended"===action||"inactive"===action||"stream-removed"===action)return connection.peersBackup[stream.userid]&&(stream.extra=connection.peersBackup[stream.userid].extra),void connection.onstreamended(stream);var type="both"!=message.message.type?message.message.type:null;return void("function"==typeof stream.stream[action]&&stream.stream[action](type))}if("dropPeerConnection"===message.message)return void connection.deletePeer(message.sender);if(message.message.allParticipants)return message.message.allParticipants.indexOf(message.sender)===-1&&message.message.allParticipants.push(message.sender),void message.message.allParticipants.forEach(function(participant){mPeer[connection.peers[participant]?"renegotiatePeer":"createNewPeer"](participant,{localPeerSdpConstraints:{OfferToReceiveAudio:connection.sdpConstraints.mandatory.OfferToReceiveAudio,OfferToReceiveVideo:connection.sdpConstraints.mandatory.OfferToReceiveVideo},remotePeerSdpConstraints:{OfferToReceiveAudio:connection.session.oneway?!!connection.session.audio:connection.sdpConstraints.mandatory.OfferToReceiveAudio,OfferToReceiveVideo:connection.session.oneway?!!connection.session.video||!!connection.session.screen:connection.sdpConstraints.mandatory.OfferToReceiveVideo},isOneWay:!!connection.session.oneway||"one-way"===connection.direction,isDataOnly:isData(connection.session)})});if(message.message.newParticipant){if(message.message.newParticipant==connection.userid)return;if(connection.peers[message.message.newParticipant])return;return void mPeer.createNewPeer(message.message.newParticipant,message.message.userPreferences||{localPeerSdpConstraints:{OfferToReceiveAudio:connection.sdpConstraints.mandatory.OfferToReceiveAudio,OfferToReceiveVideo:connection.sdpConstraints.mandatory.OfferToReceiveVideo},remotePeerSdpConstraints:{OfferToReceiveAudio:connection.session.oneway?!!connection.session.audio:connection.sdpConstraints.mandatory.OfferToReceiveAudio,OfferToReceiveVideo:connection.session.oneway?!!connection.session.video||!!connection.session.screen:connection.sdpConstraints.mandatory.OfferToReceiveVideo},isOneWay:!!connection.session.oneway||"one-way"===connection.direction,isDataOnly:isData(connection.session)})}if(message.message.readyForOffer&&(connection.attachStreams.length&&(connection.waitingForLocalMedia=!1),connection.waitingForLocalMedia))return void setTimeout(function(){onMessageEvent(message)},1);if(message.message.newParticipationRequest&&message.sender!==connection.userid){connection.peers[message.sender]&&connection.deletePeer(message.sender);var userPreferences={extra:message.extra||{},localPeerSdpConstraints:message.message.remotePeerSdpConstraints||{OfferToReceiveAudio:connection.sdpConstraints.mandatory.OfferToReceiveAudio,OfferToReceiveVideo:connection.sdpConstraints.mandatory.OfferToReceiveVideo},remotePeerSdpConstraints:message.message.localPeerSdpConstraints||{OfferToReceiveAudio:connection.session.oneway?!!connection.session.audio:connection.sdpConstraints.mandatory.OfferToReceiveAudio,OfferToReceiveVideo:connection.session.oneway?!!connection.session.video||!!connection.session.screen:connection.sdpConstraints.mandatory.OfferToReceiveVideo},isOneWay:"undefined"!=typeof message.message.isOneWay?message.message.isOneWay:!!connection.session.oneway||"one-way"===connection.direction,isDataOnly:"undefined"!=typeof message.message.isDataOnly?message.message.isDataOnly:isData(connection.session),dontGetRemoteStream:"undefined"!=typeof message.message.isOneWay?message.message.isOneWay:!!connection.session.oneway||"one-way"===connection.direction,dontAttachLocalStream:!!message.message.dontGetRemoteStream,connectionDescription:message,successCallback:function(){}};return void connection.onNewParticipant(message.sender,userPreferences)}return message.message.changedUUID&&connection.peers[message.message.oldUUID]&&(connection.peers[message.message.newUUID]=connection.peers[message.message.oldUUID],delete connection.peers[message.message.oldUUID]),message.message.userLeft?(mPeer.onUserLeft(message.sender),void(message.message.autoCloseEntireSession&&connection.leave())):void mPeer.addNegotiatedMessage(message.message,message.sender)}}var parameters="";parameters+="?userid="+connection.userid,parameters+="&sessionid="+connection.sessionid,parameters+="&msgEvent="+connection.socketMessageEvent,parameters+="&socketCustomEvent="+connection.socketCustomEvent,parameters+="&autoCloseEntireSession="+!!connection.autoCloseEntireSession,connection.session.broadcast===!0&&(parameters+="&oneToMany=true"),parameters+="&maxParticipantsAllowed="+connection.maxParticipantsAllowed,connection.enableScalableBroadcast&&(parameters+="&enableScalableBroadcast=true",parameters+="&maxRelayLimitPerUser="+(connection.maxRelayLimitPerUser||2)),parameters+="&extra="+JSON.stringify(connection.extra||{}),connection.socketCustomParameters&&(parameters+=connection.socketCustomParameters);try{io.sockets={}}catch(e){}if(connection.socketURL||(connection.socketURL="/"),"/"!=connection.socketURL.substr(connection.socketURL.length-1,1))throw'"socketURL" MUST end with a slash.';connection.enableLogs&&("/"==connection.socketURL?console.info("socket.io url is: ",location.origin+"/"):console.info("socket.io url is: ",connection.socketURL));try{connection.socket=io(connection.socketURL+parameters)}catch(e){connection.socket=io.connect(connection.socketURL+parameters,connection.socketOptions)}var mPeer=connection.multiPeersHandler;connection.socket.on("extra-data-updated",function(remoteUserId,extra){connection.peers[remoteUserId]&&(connection.peers[remoteUserId].extra=extra,connection.onExtraDataUpdated({userid:remoteUserId,extra:extra}),updateExtraBackup(remoteUserId,extra))}),connection.socket.on(connection.socketMessageEvent,onMessageEvent);var alreadyConnected=!1;connection.socket.resetProps=function(){alreadyConnected=!1},connection.socket.on("connect",function(){alreadyConnected||(alreadyConnected=!0,connection.enableLogs&&console.info("socket.io connection is opened."),setTimeout(function(){connection.socket.emit("extra-data-updated",connection.extra)},1e3),connectCallback&&connectCallback(connection.socket))}),connection.socket.on("disconnect",function(){connection.enableLogs&&console.warn("socket.io connection is closed")}),connection.socket.on("user-disconnected",function(remoteUserId){remoteUserId!==connection.userid&&(connection.onUserStatusChanged({userid:remoteUserId,status:"offline",extra:connection.peers[remoteUserId]?connection.peers[remoteUserId].extra||{}:{}}),connection.deletePeer(remoteUserId))}),connection.socket.on("user-connected",function(userid){userid!==connection.userid&&connection.onUserStatusChanged({userid:userid,status:"online",extra:connection.peers[userid]?connection.peers[userid].extra||{}:{}})}),connection.socket.on("closed-entire-session",function(sessionid,extra){connection.leave(),connection.onEntireSessionClosed({sessionid:sessionid,userid:sessionid,extra:extra})}),connection.socket.on("userid-already-taken",function(useridAlreadyTaken,yourNewUserId){connection.isInitiator=!1,connection.userid=yourNewUserId,connection.onUserIdAlreadyTaken(useridAlreadyTaken,yourNewUserId)}),connection.socket.on("logs",function(log){connection.enableLogs&&console.debug("server-logs",log)}),connection.socket.on("number-of-broadcast-viewers-updated",function(data){connection.onNumberOfBroadcastViewersUpdated(data)}),connection.socket.on("set-isInitiator-true",function(sessionid){sessionid==connection.sessionid&&(connection.isInitiator=!0)})}function MultiPeers(connection){function initFileBufferReader(){connection.fbr=new FileBufferReader,connection.fbr.onProgress=function(chunk){connection.onFileProgress(chunk)},connection.fbr.onBegin=function(file){connection.onFileStart(file)},connection.fbr.onEnd=function(file){connection.onFileEnd(file)}}var self=this,skipPeers=["getAllParticipants","getLength","selectFirst","streams","send","forEach"];connection.peers={getLength:function(){var numberOfPeers=0;for(var peer in this)skipPeers.indexOf(peer)==-1&&numberOfPeers++;return numberOfPeers},selectFirst:function(){var firstPeer;for(var peer in this)skipPeers.indexOf(peer)==-1&&(firstPeer=this[peer]);return firstPeer},getAllParticipants:function(sender){var allPeers=[];for(var peer in this)skipPeers.indexOf(peer)==-1&&peer!=sender&&allPeers.push(peer);return allPeers},forEach:function(callbcak){this.getAllParticipants().forEach(function(participant){callbcak(connection.peers[participant])})},send:function(data,remoteUserId){var that=this;if(!isNull(data.size)&&!isNull(data.type))return void self.shareFile(data,remoteUserId);if(!("text"===data.type||data instanceof ArrayBuffer||data instanceof DataView))return void TextSender.send({text:data,channel:this,connection:connection,remoteUserId:remoteUserId});if("text"===data.type&&(data=JSON.stringify(data)),remoteUserId){var remoteUser=connection.peers[remoteUserId];if(remoteUser)return remoteUser.channels.length?void remoteUser.channels.forEach(function(channel){channel.send(data)}):(connection.peers[remoteUserId].createDataChannel(),connection.renegotiate(remoteUserId),void setTimeout(function(){that.send(data,remoteUserId)},3e3))}this.getAllParticipants().forEach(function(participant){return that[participant].channels.length?void that[participant].channels.forEach(function(channel){channel.send(data)}):(connection.peers[participant].createDataChannel(),connection.renegotiate(participant),void setTimeout(function(){that[participant].channels.forEach(function(channel){channel.send(data)})},3e3))})}},this.uuid=connection.userid,this.getLocalConfig=function(remoteSdp,remoteUserId,userPreferences){return userPreferences||(userPreferences={}),{streamsToShare:userPreferences.streamsToShare||{},rtcMultiConnection:connection,connectionDescription:userPreferences.connectionDescription,userid:remoteUserId,localPeerSdpConstraints:userPreferences.localPeerSdpConstraints,remotePeerSdpConstraints:userPreferences.remotePeerSdpConstraints,dontGetRemoteStream:!!userPreferences.dontGetRemoteStream,dontAttachLocalStream:!!userPreferences.dontAttachLocalStream,renegotiatingPeer:!!userPreferences.renegotiatingPeer,peerRef:userPreferences.peerRef,channels:userPreferences.channels||[],onLocalSdp:function(localSdp){self.onNegotiationNeeded(localSdp,remoteUserId)},onLocalCandidate:function(localCandidate){localCandidate=OnIceCandidateHandler.processCandidates(connection,localCandidate),localCandidate&&self.onNegotiationNeeded(localCandidate,remoteUserId)},remoteSdp:remoteSdp,onDataChannelMessage:function(message){if(!connection.fbr&&connection.enableFileSharing&&initFileBufferReader(),"string"==typeof message||!connection.enableFileSharing)return void self.onDataChannelMessage(message,remoteUserId);var that=this;return message instanceof ArrayBuffer||message instanceof DataView?void connection.fbr.convertToObject(message,function(object){that.onDataChannelMessage(object)}):message.readyForNextChunk?void connection.fbr.getNextChunk(message,function(nextChunk,isLastChunk){connection.peers[remoteUserId].channels.forEach(function(channel){channel.send(nextChunk)})},remoteUserId):message.chunkMissing?void connection.fbr.chunkMissing(message):void connection.fbr.addChunk(message,function(promptNextChunk){connection.peers[remoteUserId].peer.channel.send(promptNextChunk)})},onDataChannelError:function(error){self.onDataChannelError(error,remoteUserId)},onDataChannelOpened:function(channel){self.onDataChannelOpened(channel,remoteUserId)},onDataChannelClosed:function(event){self.onDataChannelClosed(event,remoteUserId)},onRemoteStream:function(stream){connection.peers[remoteUserId]&&connection.peers[remoteUserId].streams.push(stream),self.onGettingRemoteMedia(stream,remoteUserId)},onRemoteStreamRemoved:function(stream){self.onRemovingRemoteMedia(stream,remoteUserId)},onPeerStateChanged:function(states){self.onPeerStateChanged(states),"new"===states.iceConnectionState&&self.onNegotiationStarted(remoteUserId,states),"connected"===states.iceConnectionState&&self.onNegotiationCompleted(remoteUserId,states),states.iceConnectionState.search(/closed|failed/gi)!==-1&&(self.onUserLeft(remoteUserId),self.disconnectWith(remoteUserId))}}},this.createNewPeer=function(remoteUserId,userPreferences){if(!(connection.maxParticipantsAllowed<=connection.getAllParticipants().length)){if(userPreferences=userPreferences||{},connection.isInitiator&&connection.session.audio&&"two-way"===connection.session.audio&&!userPreferences.streamsToShare&&(userPreferences.isOneWay=!1,userPreferences.isDataOnly=!1,userPreferences.session=connection.session),!userPreferences.isOneWay&&!userPreferences.isDataOnly)return userPreferences.isOneWay=!0,void this.onNegotiationNeeded({enableMedia:!0,userPreferences:userPreferences},remoteUserId);userPreferences=connection.setUserPreferences(userPreferences,remoteUserId);var localConfig=this.getLocalConfig(null,remoteUserId,userPreferences);connection.peers[remoteUserId]=new PeerInitiator(localConfig)}},this.createAnsweringPeer=function(remoteSdp,remoteUserId,userPreferences){userPreferences=connection.setUserPreferences(userPreferences||{},remoteUserId);var localConfig=this.getLocalConfig(remoteSdp,remoteUserId,userPreferences);connection.peers[remoteUserId]=new PeerInitiator(localConfig)},this.renegotiatePeer=function(remoteUserId,userPreferences,remoteSdp){if(!connection.peers[remoteUserId])return void(connection.enableLogs&&console.error("Peer ("+remoteUserId+") does not exist. Renegotiation skipped."));userPreferences||(userPreferences={}),userPreferences.renegotiatingPeer=!0,userPreferences.peerRef=connection.peers[remoteUserId].peer,userPreferences.channels=connection.peers[remoteUserId].channels;var localConfig=this.getLocalConfig(remoteSdp,remoteUserId,userPreferences);connection.peers[remoteUserId]=new PeerInitiator(localConfig)},this.replaceTrack=function(track,remoteUserId,isVideoTrack){if(!connection.peers[remoteUserId])throw"This peer ("+remoteUserId+") does not exist.";var peer=connection.peers[remoteUserId].peer;return peer.getSenders&&"function"==typeof peer.getSenders&&peer.getSenders().length?void peer.getSenders().forEach(function(rtpSender){isVideoTrack&&"video"===rtpSender.track.kind&&(connection.peers[remoteUserId].peer.lastVideoTrack=rtpSender.track,rtpSender.replaceTrack(track)),isVideoTrack||"audio"!==rtpSender.track.kind||(connection.peers[remoteUserId].peer.lastAudioTrack=rtpSender.track,rtpSender.replaceTrack(track))}):(console.warn("RTPSender.replaceTrack is NOT supported."),void this.renegotiatePeer(remoteUserId))},this.onNegotiationNeeded=function(message,remoteUserId){},this.addNegotiatedMessage=function(message,remoteUserId){if(message.type&&message.sdp)return"answer"==message.type&&connection.peers[remoteUserId]&&connection.peers[remoteUserId].addRemoteSdp(message),"offer"==message.type&&(message.renegotiatingPeer?this.renegotiatePeer(remoteUserId,null,message):this.createAnsweringPeer(message,remoteUserId)),void(connection.enableLogs&&console.log("Remote peer's sdp:",message.sdp));if(message.candidate)return connection.peers[remoteUserId]&&connection.peers[remoteUserId].addRemoteCandidate(message),void(connection.enableLogs&&console.log("Remote peer's candidate pairs:",message.candidate));if(message.enableMedia){connection.session=message.userPreferences.session||connection.session,connection.session.oneway&&connection.attachStreams.length&&(connection.attachStreams=[]),message.userPreferences.isDataOnly&&connection.attachStreams.length&&(connection.attachStreams.length=[]);var streamsToShare={};connection.attachStreams.forEach(function(stream){streamsToShare[stream.streamid]={isAudio:!!stream.isAudio,isVideo:!!stream.isVideo,isScreen:!!stream.isScreen}}),message.userPreferences.streamsToShare=streamsToShare,self.onNegotiationNeeded({readyForOffer:!0,userPreferences:message.userPreferences},remoteUserId)}message.readyForOffer&&connection.onReadyForOffer(remoteUserId,message.userPreferences)},this.onGettingRemoteMedia=function(stream,remoteUserId){},this.onRemovingRemoteMedia=function(stream,remoteUserId){},this.onGettingLocalMedia=function(localStream){},this.onLocalMediaError=function(error,constraints){connection.onMediaError(error,constraints)},this.shareFile=function(file,remoteUserId){if(!connection.enableFileSharing)throw'"connection.enableFileSharing" is false.';initFileBufferReader(),connection.fbr.readAsArrayBuffer(file,function(uuid){var arrayOfUsers=connection.getAllParticipants();remoteUserId&&(arrayOfUsers=[remoteUserId]),arrayOfUsers.forEach(function(participant){connection.fbr.getNextChunk(uuid,function(nextChunk){connection.peers[participant].channels.forEach(function(channel){channel.send(nextChunk)})},participant)})},{userid:connection.userid,chunkSize:"Firefox"===DetectRTC.browser.name?15e3:connection.chunkSize||0})};var textReceiver=new TextReceiver(connection);this.onDataChannelMessage=function(message,remoteUserId){textReceiver.receive(JSON.parse(message),remoteUserId,connection.peers[remoteUserId]?connection.peers[remoteUserId].extra:{})},this.onDataChannelClosed=function(event,remoteUserId){event.userid=remoteUserId,event.extra=connection.peers[remoteUserId]?connection.peers[remoteUserId].extra:{},connection.onclose(event)},this.onDataChannelError=function(error,remoteUserId){error.userid=remoteUserId,event.extra=connection.peers[remoteUserId]?connection.peers[remoteUserId].extra:{},connection.onerror(error)},this.onDataChannelOpened=function(channel,remoteUserId){return connection.peers[remoteUserId].channels.length?void(connection.peers[remoteUserId].channels=[channel]):(connection.peers[remoteUserId].channels.push(channel),void connection.onopen({userid:remoteUserId,extra:connection.peers[remoteUserId]?connection.peers[remoteUserId].extra:{},channel:channel}))},this.onPeerStateChanged=function(state){connection.onPeerStateChanged(state)},this.onNegotiationStarted=function(remoteUserId,states){},this.onNegotiationCompleted=function(remoteUserId,states){},this.getRemoteStreams=function(remoteUserId){return remoteUserId=remoteUserId||connection.peers.getAllParticipants()[0],connection.peers[remoteUserId]?connection.peers[remoteUserId].streams:[]}}function fireEvent(obj,eventName,args){if("undefined"!=typeof CustomEvent){var eventDetail={arguments:args,__exposedProps__:args},event=new CustomEvent(eventName,eventDetail);obj.dispatchEvent(event)}}function setHarkEvents(connection,streamEvent){if(streamEvent.stream&&streamEvent.stream.getAudioTracks&&streamEvent.stream.getAudioTracks().length){if(!connection||!streamEvent)throw"Both arguments are required.";if(connection.onspeaking&&connection.onsilence){if("undefined"==typeof hark)throw"hark.js not found.";hark(streamEvent.stream,{onspeaking:function(){connection.onspeaking(streamEvent)},onsilence:function(){connection.onsilence(streamEvent)},onvolumechange:function(volume,threshold){connection.onvolumechange&&connection.onvolumechange(merge({volume:volume,threshold:threshold},streamEvent))}})}}}function setMuteHandlers(connection,streamEvent){streamEvent.stream&&streamEvent.stream&&streamEvent.stream.addEventListener&&(streamEvent.stream.addEventListener("mute",function(event){event=connection.streamEvents[streamEvent.streamid],event.session={audio:"audio"===event.muteType,video:"video"===event.muteType},connection.onmute(event)},!1),streamEvent.stream.addEventListener("unmute",function(event){event=connection.streamEvents[streamEvent.streamid],event.session={audio:"audio"===event.unmuteType,video:"video"===event.unmuteType},connection.onunmute(event)},!1))}function getRandomString(){if(window.crypto&&window.crypto.getRandomValues&&navigator.userAgent.indexOf("Safari")===-1){for(var a=window.crypto.getRandomValues(new Uint32Array(3)),token="",i=0,l=a.length;i=60,"Firefox"===DetectRTC.browser.name&&DetectRTC.browser.version>=54&&(oldBrowser=!1),oldBrowser&&(sdpConstraints={mandatory:sdpConstraints,optional:[{VoiceActivityDetection:!1}]}),sdpConstraints}function PeerInitiator(config){function oldAddRemoteSdp(remoteSdp,cb){cb=cb||function(){},"Safari"!==DetectRTC.browser.name&&(remoteSdp.sdp=connection.processSdp(remoteSdp.sdp)),peer.setRemoteDescription(new RTCSessionDescription(remoteSdp),cb,function(error){connection.enableLogs&&console.error("setRemoteDescription failed","\n",error,"\n",remoteSdp.sdp),cb()})}function setChannelEvents(channel){channel.binaryType="arraybuffer",channel.onmessage=function(event){config.onDataChannelMessage(event.data)},channel.onopen=function(){config.onDataChannelOpened(channel)},channel.onerror=function(error){config.onDataChannelError(error)},channel.onclose=function(event){config.onDataChannelClosed(event)},channel.internalSend=channel.send,channel.send=function(data){"open"===channel.readyState&&channel.internalSend(data)},peer.channel=channel}function oldCreateOfferOrAnswer(_method){peer[_method](function(localSdp){"Safari"!==DetectRTC.browser.name&&(localSdp.sdp=connection.processSdp(localSdp.sdp)),peer.setLocalDescription(localSdp,function(){connection.trickleIce&&(config.onLocalSdp({type:localSdp.type,sdp:localSdp.sdp,remotePeerSdpConstraints:config.remotePeerSdpConstraints||!1,renegotiatingPeer:!!config.renegotiatingPeer||!1,connectionDescription:self.connectionDescription,dontGetRemoteStream:!!config.dontGetRemoteStream,extra:connection?connection.extra:{},streamsToShare:streamsToShare}),connection.onSettingLocalDescription(self))},function(error){connection.enableLogs&&console.error("setLocalDescription-error",error)})},function(error){connection.enableLogs&&console.error("sdp-"+_method+"-error",error)},defaults.sdpConstraints)}function createOfferOrAnswer(_method){return window.enableAdapter?void peer[_method](defaults.sdpConstraints).then(function(localSdp){"Safari"!==DetectRTC.browser.name&&(localSdp.sdp=connection.processSdp(localSdp.sdp)),peer.setLocalDescription(localSdp).then(function(){connection.trickleIce&&(config.onLocalSdp({type:localSdp.type,sdp:localSdp.sdp,remotePeerSdpConstraints:config.remotePeerSdpConstraints||!1,renegotiatingPeer:!!config.renegotiatingPeer||!1,connectionDescription:self.connectionDescription,dontGetRemoteStream:!!config.dontGetRemoteStream,extra:connection?connection.extra:{},streamsToShare:streamsToShare}),connection.onSettingLocalDescription(self))},function(error){connection.enableLogs&&console.error("setLocalDescription error",error)})},function(error){connection.enableLogs&&console.error("sdp-error",error)}):oldCreateOfferOrAnswer(_method)}if("undefined"!=typeof window.RTCPeerConnection?RTCPeerConnection=window.RTCPeerConnection:"undefined"!=typeof mozRTCPeerConnection?RTCPeerConnection=mozRTCPeerConnection:"undefined"!=typeof webkitRTCPeerConnection&&(RTCPeerConnection=webkitRTCPeerConnection),RTCSessionDescription=window.RTCSessionDescription||window.mozRTCSessionDescription,RTCIceCandidate=window.RTCIceCandidate||window.mozRTCIceCandidate,MediaStreamTrack=window.MediaStreamTrack,!RTCPeerConnection)throw"WebRTC 1.0 (RTCPeerConnection) API are NOT available in this browser.";var connection=config.rtcMultiConnection;this.extra=config.remoteSdp?config.remoteSdp.extra:connection.extra,this.userid=config.userid,this.streams=[],this.channels=config.channels||[],this.connectionDescription=config.connectionDescription,this.addStream=function(session){connection.addStream(session,self.userid)},this.removeStream=function(streamid){connection.removeStream(streamid,self.userid)};var self=this;config.remoteSdp&&(this.connectionDescription=config.remoteSdp.connectionDescription);var allRemoteStreams={};defaults.sdpConstraints=setSdpConstraints({OfferToReceiveAudio:!0,OfferToReceiveVideo:!0});var peer,renegotiatingPeer=!!config.renegotiatingPeer;config.remoteSdp&&(renegotiatingPeer=!!config.remoteSdp.renegotiatingPeer);var localStreams=[];if(connection.attachStreams.forEach(function(stream){stream&&localStreams.push(stream)}),renegotiatingPeer)peer=config.peerRef;else{var iceTransports="all";(connection.candidates.turn||connection.candidates.relay)&&(connection.candidates.stun||connection.candidates.reflexive||connection.candidates.host||(iceTransports="relay"));try{var params={};"Chrome"!==DetectRTC.browser.name&&(params.iceServers=connection.iceServers),"Chrome"===DetectRTC.browser.name&&(params={iceServers:connection.iceServers,iceTransportPolicy:connection.iceTransportPolicy||iceTransports}),connection.iceServers.length||(params=null,connection.optionalArgument=null),peer=new RTCPeerConnection(params,connection.optionalArgument)}catch(e){try{var params={iceServers:connection.iceServers};peer=new RTCPeerConnection(params)}catch(e){peer=new RTCPeerConnection}}}!peer.getRemoteStreams&&peer.getReceivers&&(peer.getRemoteStreams=function(){var stream=new MediaStream;return peer.getReceivers().forEach(function(receiver){stream.addTrack(receiver.track)}),[stream]}),!peer.getLocalStreams&&peer.getSenders&&(peer.getLocalStreams=function(){var stream=new MediaStream;return peer.getSenders().forEach(function(sender){stream.addTrack(sender.track)}),[stream]}),peer.onicecandidate=function(event){if(event.candidate)connection.trickleIce&&config.onLocalCandidate({candidate:event.candidate.candidate,sdpMid:event.candidate.sdpMid,sdpMLineIndex:event.candidate.sdpMLineIndex});else if(!connection.trickleIce){var localSdp=peer.localDescription;config.onLocalSdp({type:localSdp.type,sdp:localSdp.sdp,remotePeerSdpConstraints:config.remotePeerSdpConstraints||!1,renegotiatingPeer:!!config.renegotiatingPeer||!1,connectionDescription:self.connectionDescription,dontGetRemoteStream:!!config.dontGetRemoteStream,extra:connection?connection.extra:{},streamsToShare:streamsToShare})}},localStreams.forEach(function(localStream){if(!(config.remoteSdp&&config.remoteSdp.remotePeerSdpConstraints&&config.remoteSdp.remotePeerSdpConstraints.dontGetRemoteStream)&&!config.dontAttachLocalStream&&(localStream=connection.beforeAddingStream(localStream,self)))if(peer.getLocalStreams().forEach(function(stream){localStream&&stream.id==localStream.id&&(localStream=null)}),localStream&&"function"==typeof peer.addTrack)localStream.getTracks().forEach(function(track){try{peer.addTrack(track,localStream)}catch(e){}});else if(localStream&&"function"==typeof peer.addStream)peer.addStream(localStream);else try{peer.addStream(localStream)}catch(e){localStream&&localStream.getTracks().forEach(function(track){try{peer.addTrack(track,localStream)}catch(e){}})}}),peer.oniceconnectionstatechange=peer.onsignalingstatechange=function(){var extra=self.extra;connection.peers[self.userid]&&(extra=connection.peers[self.userid].extra||extra),peer&&(config.onPeerStateChanged({iceConnectionState:peer.iceConnectionState,iceGatheringState:peer.iceGatheringState,signalingState:peer.signalingState,extra:extra,userid:self.userid}),peer&&peer.iceConnectionState&&peer.iceConnectionState.search(/closed|failed/gi)!==-1&&self.streams instanceof Array&&self.streams.forEach(function(stream){ +var streamEvent=connection.streamEvents[stream.id]||{streamid:stream.id,stream:stream,type:"remote"};connection.onstreamended(streamEvent)}))};var sdpConstraints={OfferToReceiveAudio:!!localStreams.length,OfferToReceiveVideo:!!localStreams.length};config.localPeerSdpConstraints&&(sdpConstraints=config.localPeerSdpConstraints),defaults.sdpConstraints=setSdpConstraints(sdpConstraints);var dontDuplicate={},incomingStreamEvent="track";window.enableAdapter||(incomingStreamEvent="addstream"),peer.addEventListener(incomingStreamEvent,function(event){if(event&&("track"===incomingStreamEvent&&(event.stream=event.streams[event.streams.length-1]),!dontDuplicate[event.stream.id]||"Safari"===DetectRTC.browser.name)){dontDuplicate[event.stream.id]=event.stream.id;var streamsToShare={};config.remoteSdp&&config.remoteSdp.streamsToShare?streamsToShare=config.remoteSdp.streamsToShare:config.streamsToShare&&(streamsToShare=config.streamsToShare);var streamToShare=streamsToShare[event.stream.id];streamToShare?(event.stream.isAudio=streamToShare.isAudio,event.stream.isVideo=streamToShare.isVideo,event.stream.isScreen=streamToShare.isScreen):(event.stream.isVideo=!!event.stream.getVideoTracks().length,event.stream.isAudio=!event.stream.isVideo,event.stream.isScreen=!1),event.stream.streamid=event.stream.id,"Firefox"!=DetectRTC.browser.name&&event.stream.stop||(event.stream.stop=function(){var streamEndedEvent="ended";"oninactive"in event.stream&&(streamEndedEvent="inactive"),fireEvent(event.stream,streamEndedEvent)}),allRemoteStreams[event.stream.id]=event.stream,config.onRemoteStream(event.stream)}},!1),peer.onremovestream=function(event){event.stream.streamid=event.stream.id,allRemoteStreams[event.stream.id]&&delete allRemoteStreams[event.stream.id],config.onRemoteStreamRemoved(event.stream)},this.addRemoteCandidate=function(remoteCandidate){peer.addIceCandidate(new RTCIceCandidate(remoteCandidate))},this.addRemoteSdp=function(remoteSdp,cb){return cb=cb||function(){},window.enableAdapter?("Safari"!==DetectRTC.browser.name&&(remoteSdp.sdp=connection.processSdp(remoteSdp.sdp)),void peer.setRemoteDescription(new RTCSessionDescription(remoteSdp)).then(cb,function(error){connection.enableLogs&&console.error("setRemoteDescription failed","\n",error,"\n",remoteSdp.sdp),cb()})):oldAddRemoteSdp(remoteSdp,cb)};var isOfferer=!0;config.remoteSdp&&(isOfferer=!1),this.createDataChannel=function(){var channel=peer.createDataChannel("sctp",{});setChannelEvents(channel)},connection.session.data!==!0||renegotiatingPeer||(isOfferer?this.createDataChannel():peer.ondatachannel=function(event){var channel=event.channel;setChannelEvents(channel)}),this.enableDisableVideoEncoding=function(enable){var rtcp;if(peer.getSenders().forEach(function(sender){rtcp||"video"!==sender.track.kind||(rtcp=sender)}),rtcp&&rtcp.getParameters){var parameters=rtcp.getParameters();parameters.encodings[1]&&(parameters.encodings[1].active=!!enable),parameters.encodings[2]&&(parameters.encodings[2].active=!!enable),rtcp.setParameters(parameters)}},config.remoteSdp&&(config.remoteSdp.remotePeerSdpConstraints&&(sdpConstraints=config.remoteSdp.remotePeerSdpConstraints),defaults.sdpConstraints=setSdpConstraints(sdpConstraints),this.addRemoteSdp(config.remoteSdp,function(){createOfferOrAnswer("createAnswer")})),"two-way"!=connection.session.audio&&"two-way"!=connection.session.video&&"two-way"!=connection.session.screen||(defaults.sdpConstraints=setSdpConstraints({OfferToReceiveAudio:"two-way"==connection.session.audio||config.remoteSdp&&config.remoteSdp.remotePeerSdpConstraints&&config.remoteSdp.remotePeerSdpConstraints.OfferToReceiveAudio,OfferToReceiveVideo:"two-way"==connection.session.video||"two-way"==connection.session.screen||config.remoteSdp&&config.remoteSdp.remotePeerSdpConstraints&&config.remoteSdp.remotePeerSdpConstraints.OfferToReceiveAudio}));var streamsToShare={};peer.getLocalStreams().forEach(function(stream){streamsToShare[stream.streamid]={isAudio:!!stream.isAudio,isVideo:!!stream.isVideo,isScreen:!!stream.isScreen}}),isOfferer&&createOfferOrAnswer("createOffer"),peer.nativeClose=peer.close,peer.close=function(){if(peer){try{peer.nativeClose!==peer.close&&peer.nativeClose()}catch(e){}peer=null,self.peer=null}},this.peer=peer}function setStreamType(constraints,stream){constraints.mandatory&&constraints.mandatory.chromeMediaSource?stream.isScreen=!0:constraints.mozMediaSource||constraints.mediaSource?stream.isScreen=!0:constraints.video?stream.isVideo=!0:constraints.audio&&(stream.isAudio=!0)}function getUserMediaHandler(options){function streaming(stream,returnBack){setStreamType(options.localMediaConstraints,stream);var streamEndedEvent="ended";"oninactive"in stream&&(streamEndedEvent="inactive"),stream.addEventListener(streamEndedEvent,function(){delete currentUserMediaRequest.streams[idInstance],currentUserMediaRequest.mutex=!1,currentUserMediaRequest.queueRequests.indexOf(options)&&(delete currentUserMediaRequest.queueRequests[currentUserMediaRequest.queueRequests.indexOf(options)],currentUserMediaRequest.queueRequests=removeNullEntries(currentUserMediaRequest.queueRequests))},!1),currentUserMediaRequest.streams[idInstance]={stream:stream},currentUserMediaRequest.mutex=!1,currentUserMediaRequest.queueRequests.length&&getUserMediaHandler(currentUserMediaRequest.queueRequests.shift()),options.onGettingLocalMedia(stream,returnBack)}if(currentUserMediaRequest.mutex===!0)return void currentUserMediaRequest.queueRequests.push(options);currentUserMediaRequest.mutex=!0;var idInstance=JSON.stringify(options.localMediaConstraints);if(currentUserMediaRequest.streams[idInstance])streaming(currentUserMediaRequest.streams[idInstance].stream,!0);else{var isBlackBerry=!!/BB10|BlackBerry/i.test(navigator.userAgent||"");if(isBlackBerry||"undefined"==typeof navigator.mediaDevices||"function"!=typeof navigator.mediaDevices.getUserMedia)return navigator.getUserMedia=navigator.getUserMedia||navigator.webkitGetUserMedia||navigator.mozGetUserMedia,void navigator.getUserMedia(options.localMediaConstraints,function(stream){stream.streamid=stream.streamid||stream.id||getRandomString(),stream.idInstance=idInstance,streaming(stream)},function(error){options.onLocalMediaError(error,options.localMediaConstraints)});if("undefined"==typeof navigator.mediaDevices){navigator.getUserMedia=navigator.getUserMedia||navigator.webkitGetUserMedia||navigator.mozGetUserMedia;var getUserMediaStream,getUserMediaError,getUserMediaSuccess=function(){},getUserMediaFailure=function(){};navigator.mediaDevices={getUserMedia:function(hints){return navigator.getUserMedia(hints,function(getUserMediaSuccess){getUserMediaSuccess(stream),getUserMediaStream=stream},function(error){getUserMediaFailure(error),getUserMediaError=error}),{then:function(successCB){return getUserMediaStream?void successCB(getUserMediaStream):(getUserMediaSuccess=successCB,{then:function(failureCB){return getUserMediaError?void failureCB(getUserMediaError):void(getUserMediaFailure=failureCB)}})}}}}}navigator.mediaDevices.getUserMedia(options.localMediaConstraints).then(function(stream){stream.streamid=stream.streamid||stream.id||getRandomString(),stream.idInstance=idInstance,streaming(stream)})["catch"](function(error){options.onLocalMediaError(error,options.localMediaConstraints)})}}function onMessageCallback(data){if("PermissionDeniedError"==data){if(chromeMediaSource="PermissionDeniedError",screenCallback)return screenCallback("PermissionDeniedError");throw new Error("PermissionDeniedError")}"rtcmulticonnection-extension-loaded"==data&&(chromeMediaSource="desktop"),data.sourceId&&screenCallback&&screenCallback(sourceId=data.sourceId,data.canRequestAudioTrack===!0)}function isChromeExtensionAvailable(callback){if(callback){if("desktop"==chromeMediaSource)return callback(!0);window.postMessage("are-you-there","*"),setTimeout(function(){callback("screen"==chromeMediaSource?!1:!0)},2e3)}}function getSourceId(callback){if(!callback)throw'"callback" parameter is mandatory.';return sourceId?callback(sourceId):(screenCallback=callback,void window.postMessage("get-sourceId","*"))}function getSourceIdWithAudio(callback){if(!callback)throw'"callback" parameter is mandatory.';return sourceId?callback(sourceId):(screenCallback=callback,void window.postMessage("audio-plus-tab","*"))}function getChromeExtensionStatus(extensionid,callback){if(isFirefox)return callback("not-chrome");2!=arguments.length&&(callback=extensionid,extensionid="ajhifddimkapgcifgcodmmfdlknahffk");var image=document.createElement("img");image.src="chrome-extension://"+extensionid+"/icon.png",image.onload=function(){chromeMediaSource="screen",window.postMessage("are-you-there","*"),setTimeout(function(){callback("screen"==chromeMediaSource?"installed-disabled":"installed-enabled")},2e3)},image.onerror=function(){callback("not-installed")}}function getScreenConstraints(callback,captureSourceIdWithAudio){var firefoxScreenConstraints={mozMediaSource:"window",mediaSource:"window"};if(isFirefox)return callback(null,firefoxScreenConstraints);var screen_constraints={mandatory:{chromeMediaSource:chromeMediaSource,maxWidth:screen.width>1920?screen.width:1920,maxHeight:screen.height>1080?screen.height:1080},optional:[]};return"desktop"!=chromeMediaSource||sourceId?("desktop"==chromeMediaSource&&(screen_constraints.mandatory.chromeMediaSourceId=sourceId),void callback(null,screen_constraints)):void(captureSourceIdWithAudio?getSourceIdWithAudio(function(sourceId,canRequestAudioTrack){screen_constraints.mandatory.chromeMediaSourceId=sourceId,canRequestAudioTrack&&(screen_constraints.canRequestAudioTrack=!0),callback("PermissionDeniedError"==sourceId?sourceId:null,screen_constraints)}):getSourceId(function(sourceId){screen_constraints.mandatory.chromeMediaSourceId=sourceId,callback("PermissionDeniedError"==sourceId?sourceId:null,screen_constraints)}))}function TextReceiver(connection){function receive(data,userid,extra){var uuid=data.uuid;if(content[uuid]||(content[uuid]=[]),content[uuid].push(data.message),data.last){var message=content[uuid].join("");data.isobject&&(message=JSON.parse(message));var receivingTime=(new Date).getTime(),latency=receivingTime-data.sendingTime,e={data:message,userid:userid,extra:extra,latency:latency};connection.autoTranslateText?(e.original=e.data,connection.Translator.TranslateText(e.data,function(translatedText){e.data=translatedText,connection.onmessage(e)})):connection.onmessage(e),delete content[uuid]}}var content={};return{receive:receive}}var browserFakeUserAgent="Fake/5.0 (FakeOS) AppleWebKit/123 (KHTML, like Gecko) Fake/12.3.4567.89 Fake/123.45";!function(that){that&&"undefined"==typeof window&&"undefined"!=typeof global&&(global.navigator={userAgent:browserFakeUserAgent,getUserMedia:function(){}},global.console||(global.console={}),"undefined"==typeof global.console.debug&&(global.console.debug=global.console.info=global.console.error=global.console.log=global.console.log||function(){console.log(arguments)}),"undefined"==typeof document&&(that.document={},document.createElement=document.captureStream=document.mozCaptureStream=function(){var obj={getContext:function(){return obj},play:function(){},pause:function(){},drawImage:function(){},toDataURL:function(){return""}};return obj},document.addEventListener=document.removeEventListener=that.addEventListener=that.removeEventListener=function(){},that.HTMLVideoElement=that.HTMLMediaElement=function(){}),"undefined"==typeof io&&(that.io=function(){return{on:function(eventName,callback){callback=callback||function(){},"connect"===eventName&&callback()},emit:function(eventName,data,callback){callback=callback||function(){},"open-room"!==eventName&&"join-room"!==eventName||callback(!0,data.sessionid,null)}}}),"undefined"==typeof location&&(that.location={protocol:"file:",href:"",hash:"",origin:"self"}),"undefined"==typeof screen&&(that.screen={width:0,height:0}),"undefined"==typeof URL&&(that.URL={createObjectURL:function(){return""},revokeObjectURL:function(){return""}}),that.window=global)}("undefined"!=typeof global?global:null),function(){function getBrowserInfo(){var nameOffset,verOffset,ix,nAgt=(navigator.appVersion,navigator.userAgent),browserName=navigator.appName,fullVersion=""+parseFloat(navigator.appVersion),majorVersion=parseInt(navigator.appVersion,10);if(isSafari&&!isChrome&&nAgt.indexOf("CriOS")!==-1&&(isSafari=!1,isChrome=!0),isOpera){browserName="Opera";try{fullVersion=navigator.userAgent.split("OPR/")[1].split(" ")[0],majorVersion=fullVersion.split(".")[0]}catch(e){fullVersion="0.0.0.0",majorVersion=0}}else isIE?(verOffset=nAgt.indexOf("rv:"),verOffset>0?fullVersion=nAgt.substring(verOffset+3):(verOffset=nAgt.indexOf("MSIE"),fullVersion=nAgt.substring(verOffset+5)),browserName="IE"):isChrome?(verOffset=nAgt.indexOf("Chrome"),browserName="Chrome",fullVersion=nAgt.substring(verOffset+7)):isSafari?(verOffset=nAgt.indexOf("Safari"),browserName="Safari",fullVersion=nAgt.substring(verOffset+7),(verOffset=nAgt.indexOf("Version"))!==-1&&(fullVersion=nAgt.substring(verOffset+8)),navigator.userAgent.indexOf("Version/")!==-1&&(fullVersion=navigator.userAgent.split("Version/")[1].split(" ")[0])):isFirefox?(verOffset=nAgt.indexOf("Firefox"),browserName="Firefox",fullVersion=nAgt.substring(verOffset+8)):(nameOffset=nAgt.lastIndexOf(" ")+1)<(verOffset=nAgt.lastIndexOf("/"))&&(browserName=nAgt.substring(nameOffset,verOffset),fullVersion=nAgt.substring(verOffset+1),browserName.toLowerCase()===browserName.toUpperCase()&&(browserName=navigator.appName));return isEdge&&(browserName="Edge",fullVersion=navigator.userAgent.split("Edge/")[1]),(ix=fullVersion.search(/[; \)]/))!==-1&&(fullVersion=fullVersion.substring(0,ix)),majorVersion=parseInt(""+fullVersion,10),isNaN(majorVersion)&&(fullVersion=""+parseFloat(navigator.appVersion),majorVersion=parseInt(navigator.appVersion,10)),{fullVersion:fullVersion,version:majorVersion,name:browserName,isPrivateBrowsing:!1}}function retry(isDone,next){var currentTrial=0,maxRetry=50,isTimeout=!1,id=window.setInterval(function(){isDone()&&(window.clearInterval(id),next(isTimeout)),currentTrial++>maxRetry&&(window.clearInterval(id),isTimeout=!0,next(isTimeout))},10)}function isIE10OrLater(userAgent){var ua=userAgent.toLowerCase();if(0===ua.indexOf("msie")&&0===ua.indexOf("trident"))return!1;var match=/(?:msie|rv:)\s?([\d\.]+)/.exec(ua);return!!(match&&parseInt(match[1],10)>=10)}function detectPrivateMode(callback){var isPrivate;try{if(window.webkitRequestFileSystem)window.webkitRequestFileSystem(window.TEMPORARY,1,function(){isPrivate=!1},function(e){isPrivate=!0});else if(window.indexedDB&&/Firefox/.test(window.navigator.userAgent)){var db;try{db=window.indexedDB.open("test"),db.onerror=function(){return!0}}catch(e){isPrivate=!0}"undefined"==typeof isPrivate&&retry(function(){return"done"===db.readyState},function(isTimeout){isTimeout||(isPrivate=!db.result)})}else if(isIE10OrLater(window.navigator.userAgent)){isPrivate=!1;try{window.indexedDB||(isPrivate=!0)}catch(e){isPrivate=!0}}else if(window.localStorage&&/Safari/.test(window.navigator.userAgent)){try{window.localStorage.setItem("test",1)}catch(e){isPrivate=!0}"undefined"==typeof isPrivate&&(isPrivate=!1,window.localStorage.removeItem("test"))}}catch(e){isPrivate=!1}retry(function(){return"undefined"!=typeof isPrivate},function(isTimeout){callback(isPrivate)})}function detectDesktopOS(){for(var cs,unknown="-",nVer=navigator.appVersion,nAgt=navigator.userAgent,os=unknown,clientStrings=[{s:"Windows 10",r:/(Windows 10.0|Windows NT 10.0)/},{s:"Windows 8.1",r:/(Windows 8.1|Windows NT 6.3)/},{s:"Windows 8",r:/(Windows 8|Windows NT 6.2)/},{s:"Windows 7",r:/(Windows 7|Windows NT 6.1)/},{s:"Windows Vista",r:/Windows NT 6.0/},{s:"Windows Server 2003",r:/Windows NT 5.2/},{s:"Windows XP",r:/(Windows NT 5.1|Windows XP)/},{s:"Windows 2000",r:/(Windows NT 5.0|Windows 2000)/},{s:"Windows ME",r:/(Win 9x 4.90|Windows ME)/},{s:"Windows 98",r:/(Windows 98|Win98)/},{s:"Windows 95",r:/(Windows 95|Win95|Windows_95)/},{s:"Windows NT 4.0",r:/(Windows NT 4.0|WinNT4.0|WinNT|Windows NT)/},{s:"Windows CE",r:/Windows CE/},{s:"Windows 3.11",r:/Win16/},{s:"Android",r:/Android/},{s:"Open BSD",r:/OpenBSD/},{s:"Sun OS",r:/SunOS/},{s:"Linux",r:/(Linux|X11)/},{s:"iOS",r:/(iPhone|iPad|iPod)/},{s:"Mac OS X",r:/Mac OS X/},{s:"Mac OS",r:/(MacPPC|MacIntel|Mac_PowerPC|Macintosh)/},{s:"QNX",r:/QNX/},{s:"UNIX",r:/UNIX/},{s:"BeOS",r:/BeOS/},{s:"OS/2",r:/OS\/2/},{s:"Search Bot",r:/(nuhk|Googlebot|Yammybot|Openbot|Slurp|MSNBot|Ask Jeeves\/Teoma|ia_archiver)/}],i=0;cs=clientStrings[i];i++)if(cs.r.test(nAgt)){os=cs.s;break}var osVersion=unknown;switch(/Windows/.test(os)&&(/Windows (.*)/.test(os)&&(osVersion=/Windows (.*)/.exec(os)[1]),os="Windows"),os){case"Mac OS X":/Mac OS X (10[\.\_\d]+)/.test(nAgt)&&(osVersion=/Mac OS X (10[\.\_\d]+)/.exec(nAgt)[1]);break;case"Android":/Android ([\.\_\d]+)/.test(nAgt)&&(osVersion=/Android ([\.\_\d]+)/.exec(nAgt)[1]);break;case"iOS":/OS (\d+)_(\d+)_?(\d+)?/.test(nAgt)&&(osVersion=/OS (\d+)_(\d+)_?(\d+)?/.exec(nVer),osVersion=osVersion[1]+"."+osVersion[2]+"."+(0|osVersion[3]))}return{osName:os,osVersion:osVersion}}function getAndroidVersion(ua){ua=(ua||navigator.userAgent).toLowerCase();var match=ua.match(/android\s([0-9\.]*)/);return!!match&&match[1]}function DetectLocalIPAddress(callback,stream){if(DetectRTC.isWebRTCSupported){var isPublic=!0,isIpv4=!0;getIPs(function(ip){ip.match(regexIpv4Local)?(isPublic=!1,callback("Local: "+ip,isPublic,isIpv4)):ip.match(regexIpv6)?(isIpv4=!1,callback("Public: "+ip,isPublic,isIpv4)):callback("Public: "+ip,isPublic,isIpv4)},stream)}}function getIPs(callback,stream){function handleCandidate(candidate){var match=regexIpv4.exec(candidate);if(match){var ipAddress=match[1];const isPublic=candidate.match(regexIpv4Local),isIpv4=!0;void 0===ipDuplicates[ipAddress]&&callback(ipAddress,isPublic,isIpv4),ipDuplicates[ipAddress]=!0}}function afterCreateOffer(){var lines=pc.localDescription.sdp.split("\n");lines.forEach(function(line){0===line.indexOf("a=candidate:")&&handleCandidate(line)})}if("undefined"!=typeof document&&"function"==typeof document.getElementById){var ipDuplicates={},RTCPeerConnection=window.RTCPeerConnection||window.mozRTCPeerConnection||window.webkitRTCPeerConnection;if(!RTCPeerConnection){var iframe=document.getElementById("iframe");if(!iframe)return;var win=iframe.contentWindow;RTCPeerConnection=win.RTCPeerConnection||win.mozRTCPeerConnection||win.webkitRTCPeerConnection}if(RTCPeerConnection){var peerConfig=null;"Chrome"===DetectRTC.browser&&DetectRTC.browser.version<58&&(peerConfig={optional:[{RtpDataChannels:!0}]});var servers={iceServers:[{urls:"stun:stun.l.google.com:19302"}]},pc=new RTCPeerConnection(servers,peerConfig);if(stream&&(pc.addStream?pc.addStream(stream):pc.addTrack&&stream.getTracks()[0]&&pc.addTrack(stream.getTracks()[0],stream)),pc.onicecandidate=function(ice){ice.candidate&&handleCandidate(ice.candidate.candidate)},!stream)try{pc.createDataChannel("sctp",{})}catch(e){}DetectRTC.isPromisesSupported?pc.createOffer().then(function(result){pc.setLocalDescription(result).then(afterCreateOffer)}):pc.createOffer(function(result){pc.setLocalDescription(result,afterCreateOffer,function(){})},function(){})}}}function checkDeviceSupport(callback){if(!canEnumerate)return void(callback&&callback());if(!navigator.enumerateDevices&&window.MediaStreamTrack&&window.MediaStreamTrack.getSources&&(navigator.enumerateDevices=window.MediaStreamTrack.getSources.bind(window.MediaStreamTrack)),!navigator.enumerateDevices&&navigator.enumerateDevices&&(navigator.enumerateDevices=navigator.enumerateDevices.bind(navigator)),!navigator.enumerateDevices)return void(callback&&callback());MediaDevices=[],audioInputDevices=[],audioOutputDevices=[],videoInputDevices=[],hasMicrophone=!1,hasSpeakers=!1,hasWebcam=!1,isWebsiteHasMicrophonePermissions=!1,isWebsiteHasWebcamPermissions=!1;var alreadyUsedDevices={};navigator.enumerateDevices(function(devices){devices.forEach(function(_device){var device={};for(var d in _device)try{"function"!=typeof _device[d]&&(device[d]=_device[d])}catch(e){}alreadyUsedDevices[device.deviceId+device.label+device.kind]||("audio"===device.kind&&(device.kind="audioinput"),"video"===device.kind&&(device.kind="videoinput"),device.deviceId||(device.deviceId=device.id),device.id||(device.id=device.deviceId),device.label?("videoinput"!==device.kind||isWebsiteHasWebcamPermissions||(isWebsiteHasWebcamPermissions=!0),"audioinput"!==device.kind||isWebsiteHasMicrophonePermissions||(isWebsiteHasMicrophonePermissions=!0)):(device.isCustomLabel=!0,"videoinput"===device.kind?device.label="Camera "+(videoInputDevices.length+1):"audioinput"===device.kind?device.label="Microphone "+(audioInputDevices.length+1):"audiooutput"===device.kind?device.label="Speaker "+(audioOutputDevices.length+1):device.label="Please invoke getUserMedia once.","undefined"!=typeof DetectRTC&&DetectRTC.browser.isChrome&&DetectRTC.browser.version>=46&&!/^(https:|chrome-extension:)$/g.test(location.protocol||"")&&"undefined"!=typeof document&&"string"==typeof document.domain&&document.domain.search&&document.domain.search(/localhost|127.0./g)===-1&&(device.label="HTTPs is required to get label of this "+device.kind+" device.")),"audioinput"===device.kind&&(hasMicrophone=!0,audioInputDevices.indexOf(device)===-1&&audioInputDevices.push(device)),"audiooutput"===device.kind&&(hasSpeakers=!0,audioOutputDevices.indexOf(device)===-1&&audioOutputDevices.push(device)),"videoinput"===device.kind&&(hasWebcam=!0,videoInputDevices.indexOf(device)===-1&&videoInputDevices.push(device)),MediaDevices.push(device),alreadyUsedDevices[device.deviceId+device.label+device.kind]=device)}),"undefined"!=typeof DetectRTC&&(DetectRTC.MediaDevices=MediaDevices,DetectRTC.hasMicrophone=hasMicrophone,DetectRTC.hasSpeakers=hasSpeakers,DetectRTC.hasWebcam=hasWebcam,DetectRTC.isWebsiteHasWebcamPermissions=isWebsiteHasWebcamPermissions,DetectRTC.isWebsiteHasMicrophonePermissions=isWebsiteHasMicrophonePermissions,DetectRTC.audioInputDevices=audioInputDevices,DetectRTC.audioOutputDevices=audioOutputDevices,DetectRTC.videoInputDevices=videoInputDevices),callback&&callback()})}function getAspectRatio(w,h){function gcd(a,b){return 0==b?a:gcd(b,a%b)}var r=gcd(w,h);return w/r/(h/r)}var browserFakeUserAgent="Fake/5.0 (FakeOS) AppleWebKit/123 (KHTML, like Gecko) Fake/12.3.4567.89 Fake/123.45",isNodejs="object"==typeof process&&"object"==typeof process.versions&&process.versions.node&&!process.browser;if(isNodejs){var version=process.versions.node.toString().replace("v","");browserFakeUserAgent="Nodejs/"+version+" (NodeOS) AppleWebKit/"+version+" (KHTML, like Gecko) Nodejs/"+version+" Nodejs/"+version}!function(that){"undefined"==typeof window&&("undefined"==typeof window&&"undefined"!=typeof global?(global.navigator={userAgent:browserFakeUserAgent,getUserMedia:function(){}},that.window=global):"undefined"==typeof window,"undefined"==typeof location&&(that.location={protocol:"file:",href:"",hash:""}),"undefined"==typeof screen&&(that.screen={width:0,height:0}))}("undefined"!=typeof global?global:window);var navigator=window.navigator;"undefined"!=typeof navigator?("undefined"!=typeof navigator.webkitGetUserMedia&&(navigator.getUserMedia=navigator.webkitGetUserMedia),"undefined"!=typeof navigator.mozGetUserMedia&&(navigator.getUserMedia=navigator.mozGetUserMedia)):navigator={getUserMedia:function(){},userAgent:browserFakeUserAgent};var isMobileDevice=!!/Android|webOS|iPhone|iPad|iPod|BB10|BlackBerry|IEMobile|Opera Mini|Mobile|mobile/i.test(navigator.userAgent||""),isEdge=!(navigator.userAgent.indexOf("Edge")===-1||!navigator.msSaveOrOpenBlob&&!navigator.msSaveBlob),isOpera=!!window.opera||navigator.userAgent.indexOf(" OPR/")>=0,isFirefox="undefined"!=typeof window.InstallTrigger,isSafari=/^((?!chrome|android).)*safari/i.test(navigator.userAgent),isChrome=!!window.chrome&&!isOpera,isIE="undefined"!=typeof document&&!!document.documentMode&&!isEdge,isMobile={Android:function(){return navigator.userAgent.match(/Android/i)},BlackBerry:function(){return navigator.userAgent.match(/BlackBerry|BB10/i)},iOS:function(){return navigator.userAgent.match(/iPhone|iPad|iPod/i)},Opera:function(){return navigator.userAgent.match(/Opera Mini/i)},Windows:function(){return navigator.userAgent.match(/IEMobile/i)},any:function(){return isMobile.Android()||isMobile.BlackBerry()||isMobile.iOS()||isMobile.Opera()||isMobile.Windows()},getOsName:function(){var osName="Unknown OS";return isMobile.Android()&&(osName="Android"),isMobile.BlackBerry()&&(osName="BlackBerry"),isMobile.iOS()&&(osName="iOS"),isMobile.Opera()&&(osName="Opera Mini"),isMobile.Windows()&&(osName="Windows"),osName}},osName="Unknown OS",osVersion="Unknown OS Version",osInfo=detectDesktopOS();osInfo&&osInfo.osName&&"-"!=osInfo.osName?(osName=osInfo.osName,osVersion=osInfo.osVersion):isMobile.any()&&(osName=isMobile.getOsName(),"Android"==osName&&(osVersion=getAndroidVersion()));var isNodejs="object"==typeof process&&"object"==typeof process.versions&&process.versions.node;"Unknown OS"===osName&&isNodejs&&(osName="Nodejs",osVersion=process.versions.node.toString().replace("v",""));var isCanvasSupportsStreamCapturing=!1,isVideoSupportsStreamCapturing=!1;["captureStream","mozCaptureStream","webkitCaptureStream"].forEach(function(item){"undefined"!=typeof document&&"function"==typeof document.createElement&&(!isCanvasSupportsStreamCapturing&&item in document.createElement("canvas")&&(isCanvasSupportsStreamCapturing=!0),!isVideoSupportsStreamCapturing&&item in document.createElement("video")&&(isVideoSupportsStreamCapturing=!0))});const regexIpv4Local=/^(192\.168\.|169\.254\.|10\.|172\.(1[6-9]|2\d|3[01]))/,regexIpv4=/([0-9]{1,3}(\.[0-9]{1,3}){3})/,regexIpv6=/[a-f0-9]{1,4}(:[a-f0-9]{1,4}){7}/;var MediaDevices=[],audioInputDevices=[],audioOutputDevices=[],videoInputDevices=[];navigator.mediaDevices&&navigator.mediaDevices.enumerateDevices&&(navigator.enumerateDevices=function(callback){var enumerateDevices=navigator.mediaDevices.enumerateDevices();enumerateDevices&&enumerateDevices.then?navigator.mediaDevices.enumerateDevices().then(callback)["catch"](function(){callback([])}):callback([])});var canEnumerate=!1;"undefined"!=typeof MediaStreamTrack&&"getSources"in MediaStreamTrack?canEnumerate=!0:navigator.mediaDevices&&navigator.mediaDevices.enumerateDevices&&(canEnumerate=!0);var hasMicrophone=!1,hasSpeakers=!1,hasWebcam=!1,isWebsiteHasMicrophonePermissions=!1,isWebsiteHasWebcamPermissions=!1,DetectRTC=window.DetectRTC||{};DetectRTC.browser=getBrowserInfo(),detectPrivateMode(function(isPrivateBrowsing){DetectRTC.browser.isPrivateBrowsing=!!isPrivateBrowsing}),DetectRTC.browser["is"+DetectRTC.browser.name]=!0,DetectRTC.osName=osName,DetectRTC.osVersion=osVersion;var isWebRTCSupported=("object"==typeof process&&"object"==typeof process.versions&&process.versions["node-webkit"],!1);["RTCPeerConnection","webkitRTCPeerConnection","mozRTCPeerConnection","RTCIceGatherer"].forEach(function(item){isWebRTCSupported||item in window&&(isWebRTCSupported=!0)}),DetectRTC.isWebRTCSupported=isWebRTCSupported,DetectRTC.isORTCSupported="undefined"!=typeof RTCIceGatherer;var isScreenCapturingSupported=!1;if(DetectRTC.browser.isChrome&&DetectRTC.browser.version>=35?isScreenCapturingSupported=!0:DetectRTC.browser.isFirefox&&DetectRTC.browser.version>=34?isScreenCapturingSupported=!0:DetectRTC.browser.isEdge&&DetectRTC.browser.version>=17?isScreenCapturingSupported=!0:"Android"===DetectRTC.osName&&DetectRTC.browser.isChrome&&(isScreenCapturingSupported=!0),!/^(https:|chrome-extension:)$/g.test(location.protocol||"")){var isNonLocalHost="undefined"!=typeof document&&"string"==typeof document.domain&&document.domain.search&&document.domain.search(/localhost|127.0./g)===-1;isNonLocalHost&&(DetectRTC.browser.isChrome||DetectRTC.browser.isEdge||DetectRTC.browser.isOpera)?isScreenCapturingSupported=!1:DetectRTC.browser.isFirefox&&(isScreenCapturingSupported=!1)}DetectRTC.isScreenCapturingSupported=isScreenCapturingSupported;var webAudio={isSupported:!1,isCreateMediaStreamSourceSupported:!1};["AudioContext","webkitAudioContext","mozAudioContext","msAudioContext"].forEach(function(item){webAudio.isSupported||item in window&&(webAudio.isSupported=!0,window[item]&&"createMediaStreamSource"in window[item].prototype&&(webAudio.isCreateMediaStreamSourceSupported=!0))}),DetectRTC.isAudioContextSupported=webAudio.isSupported,DetectRTC.isCreateMediaStreamSourceSupported=webAudio.isCreateMediaStreamSourceSupported;var isRtpDataChannelsSupported=!1;DetectRTC.browser.isChrome&&DetectRTC.browser.version>31&&(isRtpDataChannelsSupported=!0),DetectRTC.isRtpDataChannelsSupported=isRtpDataChannelsSupported;var isSCTPSupportd=!1;DetectRTC.browser.isFirefox&&DetectRTC.browser.version>28?isSCTPSupportd=!0:DetectRTC.browser.isChrome&&DetectRTC.browser.version>25?isSCTPSupportd=!0:DetectRTC.browser.isOpera&&DetectRTC.browser.version>=11&&(isSCTPSupportd=!0),DetectRTC.isSctpDataChannelsSupported=isSCTPSupportd,DetectRTC.isMobileDevice=isMobileDevice;var isGetUserMediaSupported=!1;navigator.getUserMedia?isGetUserMediaSupported=!0:navigator.mediaDevices&&navigator.mediaDevices.getUserMedia&&(isGetUserMediaSupported=!0),DetectRTC.browser.isChrome&&DetectRTC.browser.version>=46&&!/^(https:|chrome-extension:)$/g.test(location.protocol||"")&&"undefined"!=typeof document&&"string"==typeof document.domain&&document.domain.search&&document.domain.search(/localhost|127.0./g)===-1&&(isGetUserMediaSupported="Requires HTTPs"),"Nodejs"===DetectRTC.osName&&(isGetUserMediaSupported=!1),DetectRTC.isGetUserMediaSupported=isGetUserMediaSupported;var displayResolution="";if(screen.width){var width=screen.width?screen.width:"",height=screen.height?screen.height:"";displayResolution+=""+width+" x "+height}DetectRTC.displayResolution=displayResolution,DetectRTC.displayAspectRatio=getAspectRatio(screen.width,screen.height).toFixed(2),DetectRTC.isCanvasSupportsStreamCapturing=isCanvasSupportsStreamCapturing,DetectRTC.isVideoSupportsStreamCapturing=isVideoSupportsStreamCapturing,"Chrome"==DetectRTC.browser.name&&DetectRTC.browser.version>=53&&(DetectRTC.isCanvasSupportsStreamCapturing||(DetectRTC.isCanvasSupportsStreamCapturing="Requires chrome flag: enable-experimental-web-platform-features"),DetectRTC.isVideoSupportsStreamCapturing||(DetectRTC.isVideoSupportsStreamCapturing="Requires chrome flag: enable-experimental-web-platform-features")),DetectRTC.DetectLocalIPAddress=DetectLocalIPAddress,DetectRTC.isWebSocketsSupported="WebSocket"in window&&2===window.WebSocket.CLOSING,DetectRTC.isWebSocketsBlocked=!DetectRTC.isWebSocketsSupported,"Nodejs"===DetectRTC.osName&&(DetectRTC.isWebSocketsSupported=!0,DetectRTC.isWebSocketsBlocked=!1),DetectRTC.checkWebSocketsSupport=function(callback){callback=callback||function(){};try{var starttime,websocket=new WebSocket("wss://echo.websocket.org:443/");websocket.onopen=function(){DetectRTC.isWebSocketsBlocked=!1,starttime=(new Date).getTime(),websocket.send("ping")},websocket.onmessage=function(){DetectRTC.WebsocketLatency=(new Date).getTime()-starttime+"ms",callback(),websocket.close(),websocket=null},websocket.onerror=function(){DetectRTC.isWebSocketsBlocked=!0,callback()}}catch(e){DetectRTC.isWebSocketsBlocked=!0,callback()}},DetectRTC.load=function(callback){callback=callback||function(){},checkDeviceSupport(callback)},"undefined"!=typeof MediaDevices?DetectRTC.MediaDevices=MediaDevices:DetectRTC.MediaDevices=[],DetectRTC.hasMicrophone=hasMicrophone,DetectRTC.hasSpeakers=hasSpeakers,DetectRTC.hasWebcam=hasWebcam,DetectRTC.isWebsiteHasWebcamPermissions=isWebsiteHasWebcamPermissions,DetectRTC.isWebsiteHasMicrophonePermissions=isWebsiteHasMicrophonePermissions,DetectRTC.audioInputDevices=audioInputDevices,DetectRTC.audioOutputDevices=audioOutputDevices,DetectRTC.videoInputDevices=videoInputDevices;var isSetSinkIdSupported=!1;"undefined"!=typeof document&&"function"==typeof document.createElement&&"setSinkId"in document.createElement("video")&&(isSetSinkIdSupported=!0), +DetectRTC.isSetSinkIdSupported=isSetSinkIdSupported;var isRTPSenderReplaceTracksSupported=!1;DetectRTC.browser.isFirefox&&"undefined"!=typeof mozRTCPeerConnection?"getSenders"in mozRTCPeerConnection.prototype&&(isRTPSenderReplaceTracksSupported=!0):DetectRTC.browser.isChrome&&"undefined"!=typeof webkitRTCPeerConnection&&"getSenders"in webkitRTCPeerConnection.prototype&&(isRTPSenderReplaceTracksSupported=!0),DetectRTC.isRTPSenderReplaceTracksSupported=isRTPSenderReplaceTracksSupported;var isRemoteStreamProcessingSupported=!1;DetectRTC.browser.isFirefox&&DetectRTC.browser.version>38&&(isRemoteStreamProcessingSupported=!0),DetectRTC.isRemoteStreamProcessingSupported=isRemoteStreamProcessingSupported;var isApplyConstraintsSupported=!1;"undefined"!=typeof MediaStreamTrack&&"applyConstraints"in MediaStreamTrack.prototype&&(isApplyConstraintsSupported=!0),DetectRTC.isApplyConstraintsSupported=isApplyConstraintsSupported;var isMultiMonitorScreenCapturingSupported=!1;DetectRTC.browser.isFirefox&&DetectRTC.browser.version>=43&&(isMultiMonitorScreenCapturingSupported=!0),DetectRTC.isMultiMonitorScreenCapturingSupported=isMultiMonitorScreenCapturingSupported,DetectRTC.isPromisesSupported=!!("Promise"in window),DetectRTC.version="1.3.7","undefined"==typeof DetectRTC&&(window.DetectRTC={});var MediaStream=window.MediaStream;"undefined"==typeof MediaStream&&"undefined"!=typeof webkitMediaStream&&(MediaStream=webkitMediaStream),"undefined"!=typeof MediaStream&&"function"==typeof MediaStream?DetectRTC.MediaStream=Object.keys(MediaStream.prototype):DetectRTC.MediaStream=!1,"undefined"!=typeof MediaStreamTrack?DetectRTC.MediaStreamTrack=Object.keys(MediaStreamTrack.prototype):DetectRTC.MediaStreamTrack=!1;var RTCPeerConnection=window.RTCPeerConnection||window.mozRTCPeerConnection||window.webkitRTCPeerConnection;"undefined"!=typeof RTCPeerConnection?DetectRTC.RTCPeerConnection=Object.keys(RTCPeerConnection.prototype):DetectRTC.RTCPeerConnection=!1,window.DetectRTC=DetectRTC,"undefined"!=typeof module&&(module.exports=DetectRTC),"function"==typeof define&&define.amd&&define("DetectRTC",[],function(){return DetectRTC})}(),"undefined"!=typeof cordova&&(DetectRTC.isMobileDevice=!0,DetectRTC.browser.name="Chrome"),navigator&&navigator.userAgent&&navigator.userAgent.indexOf("Crosswalk")!==-1&&(DetectRTC.isMobileDevice=!0,DetectRTC.browser.name="Chrome"),window.addEventListener||(window.addEventListener=function(el,eventName,eventHandler){el.attachEvent&&el.attachEvent("on"+eventName,eventHandler)}),window.attachEventListener=function(video,type,listener,useCapture){video.addEventListener(type,listener,useCapture)};var MediaStream=window.MediaStream;"undefined"==typeof MediaStream&&"undefined"!=typeof webkitMediaStream&&(MediaStream=webkitMediaStream),"undefined"!=typeof MediaStream&&("stop"in MediaStream.prototype||(MediaStream.prototype.stop=function(){this.getTracks().forEach(function(track){track.stop()})})),window.iOSDefaultAudioOutputDevice=window.iOSDefaultAudioOutputDevice||"speaker","undefined"==typeof window.enableAdapter&&("Firefox"===DetectRTC.browser.name&&DetectRTC.browser.version>=54&&(window.enableAdapter=!0),"Chrome"===DetectRTC.browser.name&&DetectRTC.browser.version>=60,"undefined"!=typeof adapter&&adapter.browserDetails&&"string"==typeof adapter.browserDetails.browser&&(window.enableAdapter=!0)),window.enableAdapter||("undefined"==typeof URL.createObjectURL&&(URL.createObjectURL=function(stream){return"blob:https://"+document.domain+"/"+getRandomString()}),"srcObject"in HTMLMediaElement.prototype||(HTMLMediaElement.prototype.srcObject=function(stream){return"mozSrcObject"in this?void(this.mozSrcObject=stream):void(this.src=URL.createObjectURL(stream))})),document.addEventListener("deviceready",setCordovaAPIs,!1),setCordovaAPIs();var RTCPeerConnection,defaults={};"undefined"!=typeof window.RTCPeerConnection?RTCPeerConnection=window.RTCPeerConnection:"undefined"!=typeof mozRTCPeerConnection?RTCPeerConnection=mozRTCPeerConnection:"undefined"!=typeof webkitRTCPeerConnection&&(RTCPeerConnection=webkitRTCPeerConnection);var RTCSessionDescription=window.RTCSessionDescription||window.mozRTCSessionDescription,RTCIceCandidate=window.RTCIceCandidate||window.mozRTCIceCandidate,MediaStreamTrack=window.MediaStreamTrack,CodecsHandler=function(){function preferCodec(sdp,codecName){var info=splitLines(sdp);return info.videoCodecNumbers?"vp8"===codecName&&info.vp8LineNumber===info.videoCodecNumbers[0]?sdp:"vp9"===codecName&&info.vp9LineNumber===info.videoCodecNumbers[0]?sdp:"h264"===codecName&&info.h264LineNumber===info.videoCodecNumbers[0]?sdp:sdp=preferCodecHelper(sdp,codecName,info):sdp}function preferCodecHelper(sdp,codec,info,ignore){var preferCodecNumber="";if("vp8"===codec){if(!info.vp8LineNumber)return sdp;preferCodecNumber=info.vp8LineNumber}if("vp9"===codec){if(!info.vp9LineNumber)return sdp;preferCodecNumber=info.vp9LineNumber}if("h264"===codec){if(!info.h264LineNumber)return sdp;preferCodecNumber=info.h264LineNumber}var newLine=info.videoCodecNumbersOriginal.split("SAVPF")[0]+"SAVPF ",newOrder=[preferCodecNumber];return ignore&&(newOrder=[]),info.videoCodecNumbers.forEach(function(codecNumber){codecNumber!==preferCodecNumber&&newOrder.push(codecNumber)}),newLine+=newOrder.join(" "),sdp=sdp.replace(info.videoCodecNumbersOriginal,newLine)}function splitLines(sdp){var info={};return sdp.split("\n").forEach(function(line){0===line.indexOf("m=video")&&(info.videoCodecNumbers=[],line.split("SAVPF")[1].split(" ").forEach(function(codecNumber){codecNumber=codecNumber.trim(),codecNumber&&codecNumber.length&&(info.videoCodecNumbers.push(codecNumber),info.videoCodecNumbersOriginal=line)})),line.indexOf("VP8/90000")===-1||info.vp8LineNumber||(info.vp8LineNumber=line.replace("a=rtpmap:","").split(" ")[0]),line.indexOf("VP9/90000")===-1||info.vp9LineNumber||(info.vp9LineNumber=line.replace("a=rtpmap:","").split(" ")[0]),line.indexOf("H264/90000")===-1||info.h264LineNumber||(info.h264LineNumber=line.replace("a=rtpmap:","").split(" ")[0])}),info}function removeVPX(sdp){var info=splitLines(sdp);return sdp=preferCodecHelper(sdp,"vp9",info,!0),sdp=preferCodecHelper(sdp,"vp8",info,!0)}function disableNACK(sdp){if(!sdp||"string"!=typeof sdp)throw"Invalid arguments.";return sdp=sdp.replace("a=rtcp-fb:126 nack\r\n",""),sdp=sdp.replace("a=rtcp-fb:126 nack pli\r\n","a=rtcp-fb:126 pli\r\n"),sdp=sdp.replace("a=rtcp-fb:97 nack\r\n",""),sdp=sdp.replace("a=rtcp-fb:97 nack pli\r\n","a=rtcp-fb:97 pli\r\n")}function prioritize(codecMimeType,peer){if(peer&&peer.getSenders&&peer.getSenders().length){if(!codecMimeType||"string"!=typeof codecMimeType)throw"Invalid arguments.";peer.getSenders().forEach(function(sender){for(var params=sender.getParameters(),i=0;i=numberOfTimes||setTimeout(function(){callback(),afterEach(setTimeoutInteval,numberOfTimes,callback,startedTimes)},setTimeoutInteval)}return{setHandlers:setHandlers,onSyncNeeded:function(streamid,action,type){}}}();window.addEventListener("message",function(event){event.origin==window.location.origin&&onMessageCallback(event.data)});var sourceId,screenCallback,chromeMediaSource="screen",isFirefox="undefined"!=typeof window.InstallTrigger,isOpera=!!window.opera||navigator.userAgent.indexOf(" OPR/")>=0,TextSender=(!!window.chrome&&!isOpera,{send:function(config){function sendText(textMessage,text){var data={type:"text",uuid:uuid,sendingTime:sendingTime};textMessage&&(text=textMessage,data.packets=parseInt(text.length/packetSize)),text.length>packetSize?data.message=text.slice(0,packetSize):(data.message=text,data.last=!0,data.isobject=isobject),channel.send(data,remoteUserId),textToTransfer=text.slice(data.message.length),textToTransfer.length&&setTimeout(function(){sendText(null,textToTransfer)},connection.chunkInterval||100)}var connection=config.connection,channel=config.channel,remoteUserId=config.remoteUserId,initialText=config.text,packetSize=connection.chunkSize||1e3,textToTransfer="",isobject=!1;isString(initialText)||(isobject=!0,initialText=JSON.stringify(initialText));var uuid=getRandomString(),sendingTime=(new Date).getTime();sendText(initialText)}}),FileProgressBarHandler=function(){function handle(connection){function updateLabel(progress,label){if(progress.position!==-1){var position=+progress.position.toFixed(2).split(".")[1]||100;label.innerHTML=position+"%"}}var progressHelper={};connection.onFileStart=function(file){var div=document.createElement("div");return div.title=file.name,div.innerHTML=" ",file.remoteUserId&&(div.innerHTML+=" (Sharing with:"+file.remoteUserId+")"),connection.filesContainer||(connection.filesContainer=document.body||document.documentElement),connection.filesContainer.insertBefore(div,connection.filesContainer.firstChild),file.remoteUserId?(progressHelper[file.uuid]||(progressHelper[file.uuid]={}),progressHelper[file.uuid][file.remoteUserId]={div:div,progress:div.querySelector("progress"),label:div.querySelector("label")},void(progressHelper[file.uuid][file.remoteUserId].progress.max=file.maxChunks)):(progressHelper[file.uuid]={div:div,progress:div.querySelector("progress"),label:div.querySelector("label")},void(progressHelper[file.uuid].progress.max=file.maxChunks))},connection.onFileProgress=function(chunk){var helper=progressHelper[chunk.uuid];helper&&(chunk.remoteUserId&&!(helper=progressHelper[chunk.uuid][chunk.remoteUserId])||(helper.progress.value=chunk.currentPosition||chunk.maxChunks||helper.progress.max,updateLabel(helper.progress,helper.label)))},connection.onFileEnd=function(file){var helper=progressHelper[file.uuid];if(!helper)return void console.error("No such progress-helper element exist.",file);if(!file.remoteUserId||(helper=progressHelper[file.uuid][file.remoteUserId])){var div=helper.div;file.type.indexOf("image")!=-1?div.innerHTML='
    Download '+file.name+'
    ':div.innerHTML='Download '+file.name+'
    '}}}return{handle:handle}}(),TranslationHandler=function(){function handle(connection){connection.autoTranslateText=!1,connection.language="en",connection.googKey="AIzaSyCgB5hmFY74WYB-EoWkhr9cAGr6TiTHrEE",connection.Translator={TranslateText:function(text,callback){var newScript=document.createElement("script");newScript.type="text/javascript";var sourceText=encodeURIComponent(text),randomNumber="method"+connection.token();window[randomNumber]=function(response){return response.data&&response.data.translations[0]&&callback?void callback(response.data.translations[0].translatedText):response.error&&"Daily Limit Exceeded"===response.error.message?void console.error('Text translation failed. Error message: "Daily Limit Exceeded."'):response.error?void console.error(response.error.message):void console.error(response)};var source="https://www.googleapis.com/language/translate/v2?key="+connection.googKey+"&target="+(connection.language||"en-US")+"&callback=window."+randomNumber+"&q="+sourceText;newScript.src=source,document.getElementsByTagName("head")[0].appendChild(newScript)},getListOfLanguages:function(callback){var xhr=new XMLHttpRequest;xhr.onreadystatechange=function(){if(xhr.readyState==XMLHttpRequest.DONE){var response=JSON.parse(xhr.responseText);if(response&&response.data&&response.data.languages)return void callback(response.data.languages);if(response.error&&"Daily Limit Exceeded"===response.error.message)return void console.error('Text translation failed. Error message: "Daily Limit Exceeded."');if(response.error)return void console.error(response.error.message);console.error(response)}};var url="https://www.googleapis.com/language/translate/v2/languages?key="+connection.googKey+"&target=en";xhr.open("GET",url,!0),xhr.send(null)}}}return{handle:handle}}();!function(connection){function onUserLeft(remoteUserId){connection.deletePeer(remoteUserId)}function connectSocket(connectCallback){if(connection.socketAutoReConnect=!0,connection.socket)return void(connectCallback&&connectCallback(connection.socket));if("undefined"==typeof SocketConnection)if("undefined"!=typeof FirebaseConnection)window.SocketConnection=FirebaseConnection;else{if("undefined"==typeof PubNubConnection)throw"SocketConnection.js seems missed.";window.SocketConnection=PubNubConnection}new SocketConnection(connection,function(s){connectCallback&&connectCallback(connection.socket)})}function joinRoom(connectionDescription,cb){connection.socket.emit("join-room",{sessionid:connection.sessionid,session:connection.session,mediaConstraints:connection.mediaConstraints,sdpConstraints:connection.sdpConstraints,streams:getStreamInfoForAdmin(),extra:connection.extra,password:"undefined"!=typeof connection.password&&"object"!=typeof connection.password?connection.password:""},function(isRoomJoined,error){if(isRoomJoined===!0){if(connection.enableLogs&&console.log("isRoomJoined: ",isRoomJoined," roomid: ",connection.sessionid),connection.peers[connection.sessionid])return;mPeer.onNegotiationNeeded(connectionDescription)}isRoomJoined===!1&&connection.enableLogs&&console.warn("isRoomJoined: ",error," roomid: ",connection.sessionid),cb(isRoomJoined,connection.sessionid,error)})}function openRoom(callback){connection.enableLogs&&console.log("Sending open-room signal to socket.io"),connection.waitingForLocalMedia=!1,connection.socket.emit("open-room",{sessionid:connection.sessionid,session:connection.session,mediaConstraints:connection.mediaConstraints,sdpConstraints:connection.sdpConstraints,streams:getStreamInfoForAdmin(),extra:connection.extra,identifier:connection.publicRoomIdentifier,password:"undefined"!=typeof connection.password&&"object"!=typeof connection.password?connection.password:""},function(isRoomOpened,error){isRoomOpened===!0&&(connection.enableLogs&&console.log("isRoomOpened: ",isRoomOpened," roomid: ",connection.sessionid),callback(isRoomOpened,connection.sessionid)),isRoomOpened===!1&&(connection.enableLogs&&console.warn("isRoomOpened: ",error," roomid: ",connection.sessionid),callback(isRoomOpened,connection.sessionid,error))})}function getStreamInfoForAdmin(){try{return connection.streamEvents.selectAll("local").map(function(event){return{streamid:event.streamid,tracks:event.stream.getTracks().length}})}catch(e){return[]}}function beforeJoin(userPreferences,callback){if(connection.dontCaptureUserMedia||userPreferences.isDataOnly)return void callback();var localMediaConstraints={};userPreferences.localPeerSdpConstraints.OfferToReceiveAudio&&(localMediaConstraints.audio=connection.mediaConstraints.audio),userPreferences.localPeerSdpConstraints.OfferToReceiveVideo&&(localMediaConstraints.video=connection.mediaConstraints.video);var session=userPreferences.session||connection.session;return session.oneway&&"two-way"!==session.audio&&"two-way"!==session.video&&"two-way"!==session.screen?void callback():(session.oneway&&session.audio&&"two-way"===session.audio&&(session={audio:!0}),void((session.audio||session.video||session.screen)&&(session.screen?"Edge"===DetectRTC.browser.name?navigator.getDisplayMedia({video:!0,audio:isAudioPlusTab(connection)}).then(function(screen){screen.isScreen=!0,mPeer.onGettingLocalMedia(screen),!session.audio&&!session.video||isAudioPlusTab(connection)?callback(screen):connection.invokeGetUserMedia(null,callback)},function(error){console.error("Unable to capture screen on Edge. HTTPs and version 17+ is required.")}):connection.getScreenConstraints(function(error,screen_constraints){connection.invokeGetUserMedia({audio:!!isAudioPlusTab(connection)&&getAudioScreenConstraints(screen_constraints),video:screen_constraints,isScreen:!0},!session.audio&&!session.video||isAudioPlusTab(connection)?callback:connection.invokeGetUserMedia(null,callback))}):(session.audio||session.video)&&connection.invokeGetUserMedia(null,callback,session))))}function applyConstraints(stream,mediaConstraints){return stream?(mediaConstraints.audio&&stream.getAudioTracks().forEach(function(track){track.applyConstraints(mediaConstraints.audio)}),void(mediaConstraints.video&&stream.getVideoTracks().forEach(function(track){track.applyConstraints(mediaConstraints.video)}))):void(connection.enableLogs&&console.error("No stream to applyConstraints."))}function replaceTrack(track,remoteUserId,isVideoTrack){return remoteUserId?void mPeer.replaceTrack(track,remoteUserId,isVideoTrack):void connection.peers.getAllParticipants().forEach(function(participant){mPeer.replaceTrack(track,participant,isVideoTrack)})}forceOptions=forceOptions||{useDefaultDevices:!0},connection.channel=connection.sessionid=(roomid||location.href.replace(/\/|:|#|\?|\$|\^|%|\.|`|~|!|\+|@|\[|\||]|\|*. /g,"").split("\n").join("").split("\r").join(""))+"";var mPeer=new MultiPeers(connection),preventDuplicateOnStreamEvents={};mPeer.onGettingLocalMedia=function(stream,callback){if(callback=callback||function(){},preventDuplicateOnStreamEvents[stream.streamid])return void callback();preventDuplicateOnStreamEvents[stream.streamid]=!0;try{stream.type="local"}catch(e){}connection.setStreamEndHandler(stream),getRMCMediaElement(stream,function(mediaElement){mediaElement.id=stream.streamid,mediaElement.muted=!0,mediaElement.volume=0,connection.attachStreams.indexOf(stream)===-1&&connection.attachStreams.push(stream),"undefined"!=typeof StreamsHandler&&StreamsHandler.setHandlers(stream,!0,connection),connection.streamEvents[stream.streamid]={stream:stream,type:"local",mediaElement:mediaElement,userid:connection.userid,extra:connection.extra,streamid:stream.streamid,isAudioMuted:!0};try{setHarkEvents(connection,connection.streamEvents[stream.streamid]),setMuteHandlers(connection,connection.streamEvents[stream.streamid]),connection.onstream(connection.streamEvents[stream.streamid])}catch(e){}callback()},connection)},mPeer.onGettingRemoteMedia=function(stream,remoteUserId){try{stream.type="remote"}catch(e){}connection.setStreamEndHandler(stream,"remote-stream"),getRMCMediaElement(stream,function(mediaElement){mediaElement.id=stream.streamid,"undefined"!=typeof StreamsHandler&&StreamsHandler.setHandlers(stream,!1,connection),connection.streamEvents[stream.streamid]={stream:stream,type:"remote",userid:remoteUserId,extra:connection.peers[remoteUserId]?connection.peers[remoteUserId].extra:{},mediaElement:mediaElement,streamid:stream.streamid},setMuteHandlers(connection,connection.streamEvents[stream.streamid]),connection.onstream(connection.streamEvents[stream.streamid])},connection)},mPeer.onRemovingRemoteMedia=function(stream,remoteUserId){var streamEvent=connection.streamEvents[stream.streamid];streamEvent||(streamEvent={stream:stream,type:"remote",userid:remoteUserId,extra:connection.peers[remoteUserId]?connection.peers[remoteUserId].extra:{},streamid:stream.streamid,mediaElement:connection.streamEvents[stream.streamid]?connection.streamEvents[stream.streamid].mediaElement:null}),connection.peersBackup[streamEvent.userid]&&(streamEvent.extra=connection.peersBackup[streamEvent.userid].extra),connection.onstreamended(streamEvent),delete connection.streamEvents[stream.streamid]},mPeer.onNegotiationNeeded=function(message,remoteUserId,callback){callback=callback||function(){},remoteUserId=remoteUserId||message.remoteUserId,message=message||"";var messageToDeliver={remoteUserId:remoteUserId,message:message,sender:connection.userid};message.remoteUserId&&message.message&&message.sender&&(messageToDeliver=message),connectSocket(function(){connection.socket.emit(connection.socketMessageEvent,messageToDeliver,callback)})},mPeer.onUserLeft=onUserLeft,mPeer.disconnectWith=function(remoteUserId,callback){connection.socket&&connection.socket.emit("disconnect-with",remoteUserId,callback||function(){}),connection.deletePeer(remoteUserId)},connection.socketOptions={transport:"polling"},connection.openOrJoin=function(roomid,callback){callback=callback||function(){},connection.checkPresence(roomid,function(isRoomExist,roomid){if(isRoomExist){connection.sessionid=roomid;var localPeerSdpConstraints=!1,remotePeerSdpConstraints=!1,isOneWay=!!connection.session.oneway,isDataOnly=isData(connection.session);remotePeerSdpConstraints={OfferToReceiveAudio:connection.sdpConstraints.mandatory.OfferToReceiveAudio,OfferToReceiveVideo:connection.sdpConstraints.mandatory.OfferToReceiveVideo},localPeerSdpConstraints={OfferToReceiveAudio:isOneWay?!!connection.session.audio:connection.sdpConstraints.mandatory.OfferToReceiveAudio,OfferToReceiveVideo:isOneWay?!!connection.session.video||!!connection.session.screen:connection.sdpConstraints.mandatory.OfferToReceiveVideo};var connectionDescription={remoteUserId:connection.sessionid,message:{newParticipationRequest:!0,isOneWay:isOneWay,isDataOnly:isDataOnly,localPeerSdpConstraints:localPeerSdpConstraints,remotePeerSdpConstraints:remotePeerSdpConstraints},sender:connection.userid};return void beforeJoin(connectionDescription.message,function(){joinRoom(connectionDescription,callback)})}return connection.waitingForLocalMedia=!0,connection.isInitiator=!0,connection.sessionid=roomid||connection.sessionid,isData(connection.session)?void openRoom(callback):void connection.captureUserMedia(function(){openRoom(callback)})})},connection.waitingForLocalMedia=!1,connection.open=function(roomid,callback){callback=callback||function(){},connection.waitingForLocalMedia=!0,connection.isInitiator=!0,connection.sessionid=roomid||connection.sessionid,connectSocket(function(){return isData(connection.session)?void openRoom(callback):void connection.captureUserMedia(function(){openRoom(callback)})})},connection.peersBackup={},connection.deletePeer=function(remoteUserId){if(remoteUserId&&connection.peers[remoteUserId]){var eventObject={userid:remoteUserId,extra:connection.peers[remoteUserId]?connection.peers[remoteUserId].extra:{}};if(connection.peersBackup[eventObject.userid]&&(eventObject.extra=connection.peersBackup[eventObject.userid].extra),connection.onleave(eventObject),connection.peers[remoteUserId]){connection.peers[remoteUserId].streams.forEach(function(stream){stream.stop()});var peer=connection.peers[remoteUserId].peer;if(peer&&"closed"!==peer.iceConnectionState)try{peer.close()}catch(e){}connection.peers[remoteUserId]&&(connection.peers[remoteUserId].peer=null,delete connection.peers[remoteUserId])}}},connection.rejoin=function(connectionDescription){if(!connection.isInitiator&&connectionDescription&&Object.keys(connectionDescription).length){var extra={}; +connection.peers[connectionDescription.remoteUserId]&&(extra=connection.peers[connectionDescription.remoteUserId].extra,connection.deletePeer(connectionDescription.remoteUserId)),connectionDescription&&connectionDescription.remoteUserId&&(connection.join(connectionDescription.remoteUserId),connection.onReConnecting({userid:connectionDescription.remoteUserId,extra:extra}))}},connection.join=function(remoteUserId,options){connection.sessionid=!!remoteUserId&&(remoteUserId.sessionid||remoteUserId.remoteUserId||remoteUserId)||connection.sessionid,connection.sessionid+="";var localPeerSdpConstraints=!1,remotePeerSdpConstraints=!1,isOneWay=!1,isDataOnly=!1;if(remoteUserId&&remoteUserId.session||!remoteUserId||"string"==typeof remoteUserId){var session=remoteUserId?remoteUserId.session||connection.session:connection.session;isOneWay=!!session.oneway,isDataOnly=isData(session),remotePeerSdpConstraints={OfferToReceiveAudio:connection.sdpConstraints.mandatory.OfferToReceiveAudio,OfferToReceiveVideo:connection.sdpConstraints.mandatory.OfferToReceiveVideo},localPeerSdpConstraints={OfferToReceiveAudio:isOneWay?!!connection.session.audio:connection.sdpConstraints.mandatory.OfferToReceiveAudio,OfferToReceiveVideo:isOneWay?!!connection.session.video||!!connection.session.screen:connection.sdpConstraints.mandatory.OfferToReceiveVideo}}options=options||{};var cb=function(){};"function"==typeof options&&(cb=options,options={}),"undefined"!=typeof options.localPeerSdpConstraints&&(localPeerSdpConstraints=options.localPeerSdpConstraints),"undefined"!=typeof options.remotePeerSdpConstraints&&(remotePeerSdpConstraints=options.remotePeerSdpConstraints),"undefined"!=typeof options.isOneWay&&(isOneWay=options.isOneWay),"undefined"!=typeof options.isDataOnly&&(isDataOnly=options.isDataOnly);var connectionDescription={remoteUserId:connection.sessionid,message:{newParticipationRequest:!0,isOneWay:isOneWay,isDataOnly:isDataOnly,localPeerSdpConstraints:localPeerSdpConstraints,remotePeerSdpConstraints:remotePeerSdpConstraints},sender:connection.userid};return beforeJoin(connectionDescription.message,function(){connectSocket(function(){joinRoom(connectionDescription,cb)})}),connectionDescription},connection.publicRoomIdentifier="",connection.getUserMedia=connection.captureUserMedia=function(callback,sessionForced){callback=callback||function(){};var session=sessionForced||connection.session;return connection.dontCaptureUserMedia||isData(session)?void callback():void((session.audio||session.video||session.screen)&&(session.screen?"Edge"===DetectRTC.browser.name?navigator.getDisplayMedia({video:!0,audio:isAudioPlusTab(connection)}).then(function(screen){if(screen.isScreen=!0,mPeer.onGettingLocalMedia(screen),(session.audio||session.video)&&!isAudioPlusTab(connection)){var nonScreenSession={};for(var s in session)"screen"!==s&&(nonScreenSession[s]=session[s]);return void connection.invokeGetUserMedia(sessionForced,callback,nonScreenSession)}callback(screen)},function(error){console.error("Unable to capture screen on Edge. HTTPs and version 17+ is required.")}):connection.getScreenConstraints(function(error,screen_constraints){if(error)throw error;connection.invokeGetUserMedia({audio:!!isAudioPlusTab(connection)&&getAudioScreenConstraints(screen_constraints),video:screen_constraints,isScreen:!0},function(stream){if((session.audio||session.video)&&!isAudioPlusTab(connection)){var nonScreenSession={};for(var s in session)"screen"!==s&&(nonScreenSession[s]=session[s]);return void connection.invokeGetUserMedia(sessionForced,callback,nonScreenSession)}callback(stream)})}):(session.audio||session.video)&&connection.invokeGetUserMedia(sessionForced,callback,session)))},connection.onbeforeunload=function(arg1,dontCloseSocket){connection.closeBeforeUnload&&(connection.peers.getAllParticipants().forEach(function(participant){mPeer.onNegotiationNeeded({userLeft:!0},participant),connection.peers[participant]&&connection.peers[participant].peer&&connection.peers[participant].peer.close(),delete connection.peers[participant]}),dontCloseSocket||connection.closeSocket(),connection.isInitiator=!1)},window.ignoreBeforeUnload?connection.closeBeforeUnload=!1:(connection.closeBeforeUnload=!0,window.addEventListener("beforeunload",connection.onbeforeunload,!1)),connection.userid=getRandomString(),connection.changeUserId=function(newUserId,callback){callback=callback||function(){},connection.userid=newUserId||getRandomString(),connection.socket.emit("changed-uuid",connection.userid,callback)},connection.extra={},connection.attachStreams=[],connection.session={audio:!0,video:!0},connection.enableFileSharing=!1,connection.bandwidth={screen:!1,audio:!1,video:!1},connection.codecs={audio:"opus",video:"VP9"},connection.processSdp=function(sdp){return"Safari"===DetectRTC.browser.name?sdp:("VP8"===connection.codecs.video.toUpperCase()&&(sdp=CodecsHandler.preferCodec(sdp,"vp8")),"VP9"===connection.codecs.video.toUpperCase()&&(sdp=CodecsHandler.preferCodec(sdp,"vp9")),"H264"===connection.codecs.video.toUpperCase()&&(sdp=CodecsHandler.preferCodec(sdp,"h264")),"G722"===connection.codecs.audio&&(sdp=CodecsHandler.removeNonG722(sdp)),"Firefox"===DetectRTC.browser.name?sdp:((connection.bandwidth.video||connection.bandwidth.screen)&&(sdp=CodecsHandler.setApplicationSpecificBandwidth(sdp,connection.bandwidth,!!connection.session.screen)),connection.bandwidth.video&&(sdp=CodecsHandler.setVideoBitrates(sdp,{min:8*connection.bandwidth.video*1024,max:8*connection.bandwidth.video*1024})),connection.bandwidth.audio&&(sdp=CodecsHandler.setOpusAttributes(sdp,{maxaveragebitrate:8*connection.bandwidth.audio*1024,maxplaybackrate:8*connection.bandwidth.audio*1024,stereo:1,maxptime:3})),sdp))},"undefined"!=typeof CodecsHandler&&(connection.BandwidthHandler=connection.CodecsHandler=CodecsHandler),connection.mediaConstraints={audio:{mandatory:{},optional:connection.bandwidth.audio?[{bandwidth:8*connection.bandwidth.audio*1024||1048576}]:[]},video:{mandatory:{},optional:connection.bandwidth.video?[{bandwidth:8*connection.bandwidth.video*1024||1048576},{facingMode:"user"}]:[{facingMode:"user"}]}},"Firefox"===DetectRTC.browser.name&&(connection.mediaConstraints={audio:!0,video:!0}),forceOptions.useDefaultDevices||DetectRTC.isMobileDevice||DetectRTC.load(function(){var lastAudioDevice,lastVideoDevice;if(DetectRTC.MediaDevices.forEach(function(device){"audioinput"===device.kind&&connection.mediaConstraints.audio!==!1&&(lastAudioDevice=device),"videoinput"===device.kind&&connection.mediaConstraints.video!==!1&&(lastVideoDevice=device)}),lastAudioDevice){if("Firefox"===DetectRTC.browser.name)return void(connection.mediaConstraints.audio!==!0?connection.mediaConstraints.audio.deviceId=lastAudioDevice.id:connection.mediaConstraints.audio={deviceId:lastAudioDevice.id});1==connection.mediaConstraints.audio&&(connection.mediaConstraints.audio={mandatory:{},optional:[]}),connection.mediaConstraints.audio.optional||(connection.mediaConstraints.audio.optional=[]);var optional=[{sourceId:lastAudioDevice.id}];connection.mediaConstraints.audio.optional=optional.concat(connection.mediaConstraints.audio.optional)}if(lastVideoDevice){if("Firefox"===DetectRTC.browser.name)return void(connection.mediaConstraints.video!==!0?connection.mediaConstraints.video.deviceId=lastVideoDevice.id:connection.mediaConstraints.video={deviceId:lastVideoDevice.id});1==connection.mediaConstraints.video&&(connection.mediaConstraints.video={mandatory:{},optional:[]}),connection.mediaConstraints.video.optional||(connection.mediaConstraints.video.optional=[]);var optional=[{sourceId:lastVideoDevice.id}];connection.mediaConstraints.video.optional=optional.concat(connection.mediaConstraints.video.optional)}}),connection.sdpConstraints={mandatory:{OfferToReceiveAudio:!0,OfferToReceiveVideo:!0},optional:[{VoiceActivityDetection:!1}]},connection.rtcpMuxPolicy="require",connection.iceTransportPolicy=null,connection.optionalArgument={optional:[{DtlsSrtpKeyAgreement:!0},{googImprovedWifiBwe:!0},{googScreencastMinBitrate:300},{googIPv6:!0},{googDscp:!0},{googCpuUnderuseThreshold:55},{googCpuOveruseThreshold:85},{googSuspendBelowMinBitrate:!0},{googCpuOveruseDetection:!0}],mandatory:{}},connection.iceServers=IceServersHandler.getIceServers(connection),connection.candidates={host:!0,stun:!0,turn:!0},connection.iceProtocols={tcp:!0,udp:!0},connection.onopen=function(event){connection.enableLogs&&console.info("Data connection has been opened between you & ",event.userid)},connection.onclose=function(event){connection.enableLogs&&console.warn("Data connection has been closed between you & ",event.userid)},connection.onerror=function(error){connection.enableLogs&&console.error(error.userid,"data-error",error)},connection.onmessage=function(event){connection.enableLogs&&console.debug("data-message",event.userid,event.data)},connection.send=function(data,remoteUserId){connection.peers.send(data,remoteUserId)},connection.close=connection.disconnect=connection.leave=function(){connection.onbeforeunload(!1,!0)},connection.closeEntireSession=function(callback){callback=callback||function(){},connection.socket.emit("close-entire-session",function looper(){return connection.getAllParticipants().length?void setTimeout(looper,100):(connection.onEntireSessionClosed({sessionid:connection.sessionid,userid:connection.userid,extra:connection.extra}),void connection.changeUserId(null,function(){connection.close(),callback()}))})},connection.onEntireSessionClosed=function(event){connection.enableLogs&&console.info("Entire session is closed: ",event.sessionid,event.extra)},connection.onstream=function(e){var parentNode=connection.videosContainer;parentNode.insertBefore(e.mediaElement,parentNode.firstChild);var played=e.mediaElement.play();return"undefined"!=typeof played?void played["catch"](function(){}).then(function(){setTimeout(function(){e.mediaElement.play()},2e3)}):void setTimeout(function(){e.mediaElement.play()},2e3)},connection.onstreamended=function(e){e.mediaElement||(e.mediaElement=document.getElementById(e.streamid)),e.mediaElement&&e.mediaElement.parentNode&&e.mediaElement.parentNode.removeChild(e.mediaElement)},connection.direction="many-to-many",connection.removeStream=function(streamid,remoteUserId){var stream;return connection.attachStreams.forEach(function(localStream){localStream.id===streamid&&(stream=localStream)}),stream?(connection.peers.getAllParticipants().forEach(function(participant){if(!remoteUserId||participant===remoteUserId){var user=connection.peers[participant];try{user.peer.removeStream(stream)}catch(e){}}}),void connection.renegotiate()):void console.warn("No such stream exist.",streamid)},connection.addStream=function(session,remoteUserId){function gumCallback(stream){session.streamCallback&&session.streamCallback(stream),connection.renegotiate(remoteUserId)}return session.getAudioTracks?(connection.attachStreams.indexOf(session)===-1&&(session.streamid||(session.streamid=session.id),connection.attachStreams.push(session)),void connection.renegotiate(remoteUserId)):isData(session)?void connection.renegotiate(remoteUserId):void((session.audio||session.video||session.screen)&&(session.screen?"Edge"===DetectRTC.browser.name?navigator.getDisplayMedia({video:!0,audio:isAudioPlusTab(connection)}).then(function(screen){screen.isScreen=!0,mPeer.onGettingLocalMedia(screen),!session.audio&&!session.video||isAudioPlusTab(connection)?gumCallback(screen):connection.invokeGetUserMedia(null,function(stream){gumCallback(stream)})},function(error){console.error("Unable to capture screen on Edge. HTTPs and version 17+ is required.")}):connection.getScreenConstraints(function(error,screen_constraints){return error?"PermissionDeniedError"===error?(session.streamCallback&&session.streamCallback(null),void(connection.enableLogs&&console.error("User rejected to share his screen."))):alert(error):void connection.invokeGetUserMedia({audio:!!isAudioPlusTab(connection)&&getAudioScreenConstraints(screen_constraints),video:screen_constraints,isScreen:!0},function(stream){!session.audio&&!session.video||isAudioPlusTab(connection)?gumCallback(stream):connection.invokeGetUserMedia(null,function(stream){gumCallback(stream)})})}):(session.audio||session.video)&&connection.invokeGetUserMedia(null,gumCallback)))},connection.invokeGetUserMedia=function(localMediaConstraints,callback,session){session||(session=connection.session),localMediaConstraints||(localMediaConstraints=connection.mediaConstraints),getUserMediaHandler({onGettingLocalMedia:function(stream){var videoConstraints=localMediaConstraints.video;videoConstraints&&(videoConstraints.mediaSource||videoConstraints.mozMediaSource?stream.isScreen=!0:videoConstraints.mandatory&&videoConstraints.mandatory.chromeMediaSource&&(stream.isScreen=!0)),stream.isScreen||(stream.isVideo=stream.getVideoTracks().length,stream.isAudio=!stream.isVideo&&stream.getAudioTracks().length),mPeer.onGettingLocalMedia(stream,function(){"function"==typeof callback&&callback(stream)})},onLocalMediaError:function(error,constraints){mPeer.onLocalMediaError(error,constraints)},localMediaConstraints:localMediaConstraints||{audio:!!session.audio&&localMediaConstraints.audio,video:!!session.video&&localMediaConstraints.video}})},connection.applyConstraints=function(mediaConstraints,streamid){if(!MediaStreamTrack||!MediaStreamTrack.prototype.applyConstraints)return void alert("track.applyConstraints is NOT supported in your browser.");if(streamid){var stream;return connection.streamEvents[streamid]&&(stream=connection.streamEvents[streamid].stream),void applyConstraints(stream,mediaConstraints)}connection.attachStreams.forEach(function(stream){applyConstraints(stream,mediaConstraints)})},connection.replaceTrack=function(session,remoteUserId,isVideoTrack){function gumCallback(stream){connection.replaceTrack(stream,remoteUserId,isVideoTrack||session.video||session.screen)}if(session=session||{},!RTCPeerConnection.prototype.getSenders)return void connection.addStream(session);if(session instanceof MediaStreamTrack)return void replaceTrack(session,remoteUserId,isVideoTrack);if(session instanceof MediaStream)return session.getVideoTracks().length&&replaceTrack(session.getVideoTracks()[0],remoteUserId,!0),void(session.getAudioTracks().length&&replaceTrack(session.getAudioTracks()[0],remoteUserId,!1));if(isData(session))throw"connection.replaceTrack requires audio and/or video and/or screen.";(session.audio||session.video||session.screen)&&(session.screen?"Edge"===DetectRTC.browser.name?navigator.getDisplayMedia({video:!0,audio:isAudioPlusTab(connection)}).then(function(screen){screen.isScreen=!0,mPeer.onGettingLocalMedia(screen),!session.audio&&!session.video||isAudioPlusTab(connection)?gumCallback(screen):connection.invokeGetUserMedia(null,gumCallback)},function(error){console.error("Unable to capture screen on Edge. HTTPs and version 17+ is required.")}):connection.getScreenConstraints(function(error,screen_constraints){return error?alert(error):void connection.invokeGetUserMedia({audio:!!isAudioPlusTab(connection)&&getAudioScreenConstraints(screen_constraints),video:screen_constraints,isScreen:!0},!session.audio&&!session.video||isAudioPlusTab(connection)?gumCallback:connection.invokeGetUserMedia(null,gumCallback))}):(session.audio||session.video)&&connection.invokeGetUserMedia(null,gumCallback))},connection.resetTrack=function(remoteUsersIds,isVideoTrack){remoteUsersIds||(remoteUsersIds=connection.getAllParticipants()),"string"==typeof remoteUsersIds&&(remoteUsersIds=[remoteUsersIds]),remoteUsersIds.forEach(function(participant){var peer=connection.peers[participant].peer;"undefined"!=typeof isVideoTrack&&isVideoTrack!==!0||!peer.lastVideoTrack||connection.replaceTrack(peer.lastVideoTrack,participant,!0),"undefined"!=typeof isVideoTrack&&isVideoTrack!==!1||!peer.lastAudioTrack||connection.replaceTrack(peer.lastAudioTrack,participant,!1)})},connection.renegotiate=function(remoteUserId){return remoteUserId?void mPeer.renegotiatePeer(remoteUserId):void connection.peers.getAllParticipants().forEach(function(participant){mPeer.renegotiatePeer(participant)})},connection.setStreamEndHandler=function(stream,isRemote){if(stream&&stream.addEventListener&&(isRemote=!!isRemote,!stream.alreadySetEndHandler)){stream.alreadySetEndHandler=!0;var streamEndedEvent="ended";"oninactive"in stream&&(streamEndedEvent="inactive"),stream.addEventListener(streamEndedEvent,function(){if(stream.idInstance&¤tUserMediaRequest.remove(stream.idInstance),!isRemote){var streams=[];connection.attachStreams.forEach(function(s){s.id!=stream.id&&streams.push(s)}),connection.attachStreams=streams}var streamEvent=connection.streamEvents[stream.streamid];if(streamEvent||(streamEvent={stream:stream,streamid:stream.streamid,type:isRemote?"remote":"local",userid:connection.userid,extra:connection.extra,mediaElement:connection.streamEvents[stream.streamid]?connection.streamEvents[stream.streamid].mediaElement:null}),isRemote&&connection.peers[streamEvent.userid]){var peer=connection.peers[streamEvent.userid].peer,streams=[];peer.getRemoteStreams().forEach(function(s){s.id!=stream.id&&streams.push(s)}),connection.peers[streamEvent.userid].streams=streams}streamEvent.userid===connection.userid&&"remote"===streamEvent.type||(connection.peersBackup[streamEvent.userid]&&(streamEvent.extra=connection.peersBackup[streamEvent.userid].extra),connection.onstreamended(streamEvent),delete connection.streamEvents[stream.streamid])},!1)}},connection.onMediaError=function(error,constraints){connection.enableLogs&&console.error(error,constraints)},connection.autoCloseEntireSession=!1,connection.filesContainer=connection.videosContainer=document.body||document.documentElement,connection.isInitiator=!1,connection.shareFile=mPeer.shareFile,"undefined"!=typeof FileProgressBarHandler&&FileProgressBarHandler.handle(connection),"undefined"!=typeof TranslationHandler&&TranslationHandler.handle(connection),connection.token=getRandomString,connection.onNewParticipant=function(participantId,userPreferences){connection.acceptParticipationRequest(participantId,userPreferences)},connection.acceptParticipationRequest=function(participantId,userPreferences){userPreferences.successCallback&&(userPreferences.successCallback(),delete userPreferences.successCallback),mPeer.createNewPeer(participantId,userPreferences)},"undefined"!=typeof StreamsHandler&&(connection.StreamsHandler=StreamsHandler),connection.onleave=function(userid){},connection.invokeSelectFileDialog=function(callback){var selector=new FileSelector;selector.accept="*.*",selector.selectSingleFile(callback)},connection.onmute=function(e){if(e&&e.mediaElement)if("both"===e.muteType||"video"===e.muteType){e.mediaElement.src=null;var paused=e.mediaElement.pause();"undefined"!=typeof paused?paused.then(function(){e.mediaElement.poster=e.snapshot||"https://cdn.webrtc-experiment.com/images/muted.png"}):e.mediaElement.poster=e.snapshot||"https://cdn.webrtc-experiment.com/images/muted.png"}else"audio"===e.muteType&&(e.mediaElement.muted=!0)},connection.onunmute=function(e){e&&e.mediaElement&&e.stream&&("both"===e.unmuteType||"video"===e.unmuteType?(e.mediaElement.poster=null,e.mediaElement.srcObject=e.stream,e.mediaElement.play()):"audio"===e.unmuteType&&(e.mediaElement.muted=!1))},connection.onExtraDataUpdated=function(event){event.status="online",connection.onUserStatusChanged(event,!0)},connection.getAllParticipants=function(sender){return connection.peers.getAllParticipants(sender)},"undefined"!=typeof StreamsHandler&&(StreamsHandler.onSyncNeeded=function(streamid,action,type){connection.peers.getAllParticipants().forEach(function(participant){mPeer.onNegotiationNeeded({streamid:streamid,action:action,streamSyncNeeded:!0,type:type||"both"},participant)})}),connection.connectSocket=function(callback){connectSocket(callback)},connection.closeSocket=function(){try{io.sockets={}}catch(e){}connection.socket&&("function"==typeof connection.socket.disconnect&&connection.socket.disconnect(),"function"==typeof connection.socket.resetProps&&connection.socket.resetProps(),connection.socket=null)},connection.getSocket=function(callback){return!callback&&connection.enableLogs&&console.warn("getSocket.callback paramter is required."),callback=callback||function(){},connection.socket?callback(connection.socket):connectSocket(function(){callback(connection.socket)}),connection.socket},connection.getRemoteStreams=mPeer.getRemoteStreams;var skipStreams=["selectFirst","selectAll","forEach"];if(connection.streamEvents={selectFirst:function(options){return connection.streamEvents.selectAll(options)[0]},selectAll:function(options){options||(options={local:!0,remote:!0,isScreen:!0,isAudio:!0,isVideo:!0}),"local"==options&&(options={local:!0}),"remote"==options&&(options={remote:!0}),"screen"==options&&(options={isScreen:!0}),"audio"==options&&(options={isAudio:!0}),"video"==options&&(options={isVideo:!0});var streams=[];return Object.keys(connection.streamEvents).forEach(function(key){var event=connection.streamEvents[key];if(skipStreams.indexOf(key)===-1){var ignore=!0;options.local&&"local"===event.type&&(ignore=!1),options.remote&&"remote"===event.type&&(ignore=!1),options.isScreen&&event.stream.isScreen&&(ignore=!1),options.isVideo&&event.stream.isVideo&&(ignore=!1),options.isAudio&&event.stream.isAudio&&(ignore=!1),options.userid&&event.userid===options.userid&&(ignore=!1),ignore===!1&&streams.push(event)}}),streams}},connection.socketURL="/",connection.socketMessageEvent="RTCMultiConnection-Message",connection.socketCustomEvent="RTCMultiConnection-Custom-Message",connection.DetectRTC=DetectRTC,connection.setCustomSocketEvent=function(customEvent){customEvent&&(connection.socketCustomEvent=customEvent),connection.socket&&connection.socket.emit("set-custom-socket-event-listener",connection.socketCustomEvent)},connection.getNumberOfBroadcastViewers=function(broadcastId,callback){connection.socket&&broadcastId&&callback&&connection.socket.emit("get-number-of-users-in-specific-broadcast",broadcastId,callback)},connection.onNumberOfBroadcastViewersUpdated=function(event){connection.enableLogs&&connection.isInitiator&&console.info("Number of broadcast (",event.broadcastId,") viewers",event.numberOfBroadcastViewers)},connection.onUserStatusChanged=function(event,dontWriteLogs){connection.enableLogs&&!dontWriteLogs&&console.info(event.userid,event.status)},connection.getUserMediaHandler=getUserMediaHandler,connection.multiPeersHandler=mPeer,connection.enableLogs=!0,connection.setCustomSocketHandler=function(customSocketHandler){"undefined"!=typeof SocketConnection&&(SocketConnection=customSocketHandler)},connection.chunkSize=65e3,connection.maxParticipantsAllowed=1e3,connection.disconnectWith=mPeer.disconnectWith,connection.checkPresence=function(roomid,callback){return roomid=roomid||connection.sessionid,"SSEConnection"===SocketConnection.name?void SSEConnection.checkPresence(roomid,function(isRoomExist,_roomid,extra){return connection.socket?void callback(isRoomExist,_roomid):(isRoomExist||(connection.userid=_roomid),void connection.connectSocket(function(){callback(isRoomExist,_roomid,extra)}))}):connection.socket?void connection.socket.emit("check-presence",roomid+"",function(isRoomExist,_roomid,extra){connection.enableLogs&&console.log("checkPresence.isRoomExist: ",isRoomExist," roomid: ",_roomid),callback(isRoomExist,_roomid,extra)}):void connection.connectSocket(function(){connection.checkPresence(roomid,callback)})},connection.onReadyForOffer=function(remoteUserId,userPreferences){connection.multiPeersHandler.createNewPeer(remoteUserId,userPreferences)},connection.setUserPreferences=function(userPreferences){return connection.dontAttachStream&&(userPreferences.dontAttachLocalStream=!0),connection.dontGetRemoteStream&&(userPreferences.dontGetRemoteStream=!0),userPreferences},connection.updateExtraData=function(){connection.socket.emit("extra-data-updated",connection.extra)},connection.enableScalableBroadcast=!1,connection.maxRelayLimitPerUser=3,connection.dontCaptureUserMedia=!1,connection.dontAttachStream=!1,connection.dontGetRemoteStream=!1,connection.onReConnecting=function(event){connection.enableLogs&&console.info("ReConnecting with",event.userid,"...")},connection.beforeAddingStream=function(stream){return stream},connection.beforeRemovingStream=function(stream){return stream},"undefined"!=typeof isChromeExtensionAvailable&&(connection.checkIfChromeExtensionAvailable=isChromeExtensionAvailable),"undefined"!=typeof isFirefoxExtensionAvailable&&(connection.checkIfChromeExtensionAvailable=isFirefoxExtensionAvailable),"undefined"!=typeof getChromeExtensionStatus&&(connection.getChromeExtensionStatus=getChromeExtensionStatus),connection.getScreenConstraints=function(callback,audioPlusTab){isAudioPlusTab(connection,audioPlusTab)&&(audioPlusTab=!0),getScreenConstraints(function(error,screen_constraints){error||(screen_constraints=connection.modifyScreenConstraints(screen_constraints),callback(error,screen_constraints))},audioPlusTab)},connection.modifyScreenConstraints=function(screen_constraints){return screen_constraints},connection.onPeerStateChanged=function(state){connection.enableLogs&&state.iceConnectionState.search(/closed|failed/gi)!==-1&&console.error("Peer connection is closed between you & ",state.userid,state.extra,"state:",state.iceConnectionState)},connection.isOnline=!0,listenEventHandler("online",function(){connection.isOnline=!0}),listenEventHandler("offline",function(){connection.isOnline=!1}),connection.isLowBandwidth=!1,navigator&&navigator.connection&&navigator.connection.type&&(connection.isLowBandwidth=navigator.connection.type.toString().toLowerCase().search(/wifi|cell/g)!==-1,connection.isLowBandwidth)){if(connection.bandwidth={audio:!1,video:!1,screen:!1},connection.mediaConstraints.audio&&connection.mediaConstraints.audio.optional&&connection.mediaConstraints.audio.optional.length){var newArray=[];connection.mediaConstraints.audio.optional.forEach(function(opt){"undefined"==typeof opt.bandwidth&&newArray.push(opt)}),connection.mediaConstraints.audio.optional=newArray}if(connection.mediaConstraints.video&&connection.mediaConstraints.video.optional&&connection.mediaConstraints.video.optional.length){var newArray=[];connection.mediaConstraints.video.optional.forEach(function(opt){"undefined"==typeof opt.bandwidth&&newArray.push(opt)}),connection.mediaConstraints.video.optional=newArray}}connection.getExtraData=function(remoteUserId){if(!remoteUserId)throw"remoteUserId is required.";return connection.peers[remoteUserId]?connection.peers[remoteUserId].extra:{}},forceOptions.autoOpenOrJoin&&connection.openOrJoin(connection.sessionid),connection.onUserIdAlreadyTaken=function(useridAlreadyTaken,yourNewUserId){connection.enableLogs&&console.warn("Userid already taken.",useridAlreadyTaken,"Your new userid:",yourNewUserId),connection.userid=connection.token(),connection.join(connection.sessionid)},connection.trickleIce=!0,connection.version="3.5.3",connection.onSettingLocalDescription=function(event){connection.enableLogs&&console.info("Set local description for remote user",event.userid)},connection.resetScreen=function(){sourceId=null,DetectRTC&&DetectRTC.screen&&delete DetectRTC.screen.sourceId,currentUserMediaRequest={streams:[],mutex:!1,queueRequests:[]}},connection.autoCreateMediaElement=!0,connection.password=null,connection.setPassword=function(password,callback){callback=callback||function(){},connection.socket?connection.socket.emit("set-password",password,callback):(connection.password=password,callback(!0,connection.sessionid,null))},connection.errors={ROOM_NOT_AVAILABLE:"Room not available",INVALID_PASSWORD:"Invalid password",USERID_NOT_AVAILABLE:"User ID does not exist",ROOM_PERMISSION_DENIED:"Room permission denied",ROOM_FULL:"Room full",DID_NOT_JOIN_ANY_ROOM:"Did not join any room yet",INVALID_SOCKET:"Invalid socket",PUBLIC_IDENTIFIER_MISSING:"publicRoomIdentifier is required",INVALID_ADMIN_CREDENTIAL:"Invalid username or password attempted"}}(this)};"undefined"!=typeof module&&(module.exports=exports=RTCMultiConnection),"function"==typeof define&&define.amd&&define("RTCMultiConnection",[],function(){return RTCMultiConnection}); \ No newline at end of file diff --git a/cordova-mobile-apps/file-sharing/www/js/index.js b/cordova-mobile-apps/file-sharing/www/js/index.js new file mode 100644 index 00000000..14b406d9 --- /dev/null +++ b/cordova-mobile-apps/file-sharing/www/js/index.js @@ -0,0 +1,662 @@ +var app = { + initialize: function() { + console.error = window.onerror = function() { + if(JSON.stringify(arguments).indexOf('iosrtc') !== -1) { + return; + } + + if(JSON.stringify(arguments).indexOf('No Content-Security-Policy') !== -1) { + return; + } + + if(JSON.stringify(arguments).indexOf('<') !== -1) { + return; + } + + document.getElementById('logs').innerHTML += '

    ' + JSON.stringify(arguments, null, ' ') + '

    '; + }; + + if(typeof device !== 'undefined' && device.platform !== 'Android') { + document.querySelector('#exit-app').style.display = 'none'; + } + + this.bindEvents(); + }, + bindEvents: function() { + document.addEventListener('deviceready', app.onDeviceReady, false); + document.addEventListener('resume', function() { + if(window.connection && connection.getAllParticipants().length) { + return; + } + location.reload(); + }, false); + + document.addEventListener('online', function() { + location.reload(); + }, false); + + document.addEventListener('offline', function() { + appendLog('Seems disconnected.', 'red'); + }, false); + }, + onDeviceReady: function() { + if(device.platform !== 'Android') { + document.querySelector('#exit-app').style.display = 'none'; + } + + if (window.localStorage.getItem('room-id')) { + document.getElementById('room-id').value = window.localStorage.getItem('room-id'); + } else { + document.getElementById('room-id').value = (Math.random() * 100).toString().replace('.', ''); + } + + document.getElementById('join-room').onclick = document.querySelector('.btn-select-file').onclick = function() { + document.getElementById('join-room').onclick.disabled = true; + document.getElementById('room-id').disabled = true; + + var roomId = document.getElementById('room-id').value; + window.localStorage.setItem('room-id', roomId); + + joinARoom(roomId); + }; + }, + saveToDisk: function(fileToSave, callback) { + if(true || (fileToSave.type !== 'image/png' && fileToSave.type !== 'image/jpg' && fileToSave.type !== 'image/jpeg' && fileToSave.type !== 'image/gif')) { + checkStoragePermissions(function() { + app.saveToDisk2(fileToSave, callback); + }); + return; + } + + // above "true ||" is actually ignoring below code + // i.e. below code will skipped unti if you remove "true||" from above if-block. + // reason: because saveImageToPhone does not works correctly. + + var file = fileToSave.url ? fileToSave : new File([fileToSave], fileToSave.name || 'image.png', { + type: fileToSave.type || 'image/png' + }); + + var image = document.createElement('img'); + image.onload = function() { + checkStoragePermissions(function() { + saveImageToPhone(image, function(path) { + callback('success', file, path); + }, function(e) { + callback('failure', file, e.toString()); + }); + }); + }; + image.src = file.url || URL.createObjectURL(file); + }, + saveToDisk2: function(fileToSave, callback) { + var errorHandler = function (fileName, e) { + var msg = ''; + + switch (e.code) { + case FileError.QUOTA_EXCEEDED_ERR: + msg = 'Storage quota exceeded'; + break; + case FileError.NOT_FOUND_ERR: + msg = 'File not found'; + break; + case FileError.SECURITY_ERR: + msg = 'Security error'; + break; + case FileError.INVALID_MODIFICATION_ERR: + msg = 'Invalid modification'; + break; + case FileError.INVALID_STATE_ERR: + msg = 'Invalid state'; + break; + default: + msg = 'Unknown error code: ' + e.code; + break; + }; + + callback('failure', fileToSave, msg); + }; + + window.requestFileSystem = window.requestFileSystem || window.webkitRequestFileSystem; + + window.requestFileSystem(LocalFileSystem.PERSISTENT, fileToSave.size, function(fileSystem) { + var path = cordova.file.externalRootDirectory; + + if(DetectRTC.osName == 'iOS') { + path = path || cordova.file.externalDataDirectory || cordova.file.documentsDirectory || cordova.file.cacheDirectory; + } + + if(!path || !path.length) { + path = ''; + } + + window.resolveLocalFileSystemURL(path, function(directoryEntry) { + directoryEntry.getFile(fileToSave.name, { + create: true, + exclusive: false + }, function(fileEntry) { + fileEntry.createWriter(function(fileWriter) { + fileWriter.onwriteend = function(e) { + callback('success', fileToSave, fileWriter.localURL); + }; + + fileWriter.onerror = function(e) { + callback('failure', fileToSave, e.toString()); + }; + + fileWriter.write(fileToSave); + }, errorHandler.bind(null, fileToSave.name)); + }, errorHandler.bind(null, fileToSave.name)); + }, errorHandler.bind(null, fileToSave.name)); + }, errorHandler.bind(null, fileToSave.name)); + } +}; + +function saveImageToPhone(img, success, error) { + var canvas, context; + canvas = document.createElement('canvas'); + canvas.width = img.clientWidth; + canvas.height = img.clientHeight; + context = canvas.getContext('2d'); + context.drawImage(img, 0, 0, canvas.width, canvas.height); + window.canvas2ImagePlugin.saveImageDataToLibrary( + success, + error, + canvas + ); +} + +function checkStoragePermissions(callback) { + if (device.platform !== 'Android') { + callback(); + return; + } + + var permissions = cordova.plugins.permissions; + + var arr = [ + permissions.WRITE_EXTERNAL_STORAGE + ]; + + permissions.hasPermission(arr, function(status) { + if (status.hasPermission) { + callback(); + return; + } + + permissions.requestPermissions(arr, function(status) { + if (status.hasPermission) { + callback(); + return; + } + alert('Please manually enable storage permissions.'); + }, function() { + alert('Please manually enable storage permissions.'); + }); + }, function() { + alert('Please manually enable storage permissions.'); + }); +} + +app.initialize(); + +function joinARoom(roomId) { + // Muaz Khan - https://github.com/muaz-khan + // MIT License - https://www.WebRTC-Experiment.com/licence/ + // Source Code - https://github.com/muaz-khan/RTCMultiConnection + var iframe = document.querySelector('iframe'); + var imagePreview = document.querySelector('#image-preview'); + var videoPreview = document.querySelector('#video-preview'); + + iframe.onclick = videoPreview.onclick = imagePreview.onclick = function() { + var element = this; + document.fullscreenEnabled = document.fullscreenEnabled || document.mozFullScreenEnabled || document.documentElement.webkitRequestFullScreen; + if (document.fullscreenEnabled) { + document.fullScreenedElement = element; + + if (element.requestFullscreen) { + element.requestFullscreen(); + } else if (element.mozRequestFullScreen) { + element.mozRequestFullScreen(); + } else if (element.webkitRequestFullScreen) { + element.webkitRequestFullScreen(Element.ALLOW_KEYBOARD_INPUT); + } + } + }; + + var btnSelectFile = document.querySelector('.btn-select-file'); + var btnExitApp = document.querySelector('#exit-app'); + + btnSelectFile.onclick = function() { + var fileSelector = new FileSelector(); + fileSelector.selectSingleFile(function(file) { + if(!file) return; + onFileSelected(file); + previewFile(file); + }); + }; + + var connection; + var lastSelectedFile; + + var room_id = ''; + + // 60k -- assuming receiving client is chrome + var chunk_size = 60 * 1000; + + function setupWebRTCConnection() { + if (connection) { + return; + } + + // www.RTCMultiConnection.org/docs/ + connection = new RTCMultiConnection(); + + connection.autoCloseEntireSession = true; + + // to make sure, "connection-reconnect" doesn't sends files again + connection.fileReceived = {}; + + // by default, socket.io server is assumed to be deployed on your own URL + // connection.socketURL = '/'; + + // comment-out below line if you do not have your own socket.io server + connection.socketURL = 'https://rtcmulticonnection.herokuapp.com:443/'; + + connection.socketMessageEvent = 'file-sharing-demo'; + + connection.chunkSize = chunk_size; + + connection.sdpConstraints.mandatory = { + OfferToReceiveAudio: false, + OfferToReceiveVideo: false + }; + + connection.enableFileSharing = true; + + if (room_id && room_id.length) { + connection.userid = room_id; + } + + connection.channel = connection.sessionid = roomId; + + connection.session = { + data: true, + // oneway: true --- to make it one-to-many + }; + + connection.filesContainer = logsDiv; + + connection.connectedWith = {}; + + connection.onmessage = function(event) { + if(event.data.doYouWannaReceiveThisFile) { + if(!connection.fileReceived[event.data.fileName]) { + connection.send({ + yesIWannaReceive:true, + fileName: event.data.fileName + }); + } + } + + if(event.data.yesIWannaReceive && !!lastSelectedFile) { + connection.shareFile(lastSelectedFile, event.userid); + } + }; + + connection.onopen = function(e) { + try { + chrome.power.requestKeepAwake('display'); + } + catch(e) {} + + if (connection.connectedWith[e.userid]) return; + connection.connectedWith[e.userid] = true; + + var message = '' + e.userid + '
    is connected.'; + appendLog(message); + + if (!lastSelectedFile) return; + + // already shared the file + + var file = lastSelectedFile; + setTimeout(function() { + appendLog('Sharing file
    ' + file.name + '
    Size: ' + bytesToSize(file.size) + '
    With ' + connection.getAllParticipants().length + ' users'); + + connection.send({ + doYouWannaReceiveThisFile: true, + fileName: file.size + file.name + }); + }, 500); + }; + + connection.onclose = function(e) { + incrementOrDecrementUsers(); + + if (connection.connectedWith[e.userid]) return; + + appendLog('Data connection has been closed between you and ' + e.userid + '. Re-Connecting..'); + connection.join(roomId); + }; + + connection.onerror = function(e) { + if (connection.connectedWith[e.userid]) return; + + appendLog('Data connection failed. between you and ' + e.userid + '. Retrying..'); + }; + + setFileProgressBarHandlers(connection); + + connection.onUserStatusChanged = function(user) { + incrementOrDecrementUsers(); + }; + + connection.onleave = function(user) { + user.status = 'offline'; + connection.onUserStatusChanged(user); + incrementOrDecrementUsers(); + }; + + var message = 'Connecting room: ' + connection.channel + ''; + appendLog(message); + + connection.openOrJoin(connection.channel, function(isRoomExists, roomid) { + var message = 'Successfully connected to room: ' + roomid + '
    Other users can join you on iPhone/Android using "' + roomid + '" or desktop (Windows/MacOSX/Ubuntu) users can join using this (secure/private) URL: webrtcweb.com/fs#' + roomid + ''; + + // if (isRoomEists) { } + appendLog(message); + + if(document.getElementById('room-id')) { + document.getElementById('room-id').parentNode.innerHTML = 'Joined room: ' + roomid; + } + + var socket = connection.getSocket(); + socket.on('disconnect', function() { + appendLog('Seems disconnected.', 'red'); + }); + socket.on('error', function() { + appendLog('Seems disconnected.', 'red'); + }); + socket.on('connect', function() { + connection.openOrJoin(connection.channel); + }); + }); + + window.connection = connection; + } + + function setFileProgressBarHandlers(connection) { + var progressHelper = {}; + + // www.RTCMultiConnection.org/docs/onFileStart/ + connection.onFileStart = function(file) { + if (connection.fileReceived[file.size + file.name]) return; + + var div = document.createElement('div'); + div.style.borderBottom = '1px solid black'; + div.style.padding = '2px 4px'; + div.id = file.uuid; + + var message = ''; + if (file.userid == connection.userid) { + message += 'Sharing with:' + file.remoteUserId; + } else { + message += 'Receiving from:' + file.userid; + } + + message += '
    ' + file.name + '.'; + message += '
    Size: ' + bytesToSize(file.size) + ''; + message += '
    '; + + if(file.userid !== connection.userid) { + message += '
    '; + } + + div.innerHTML = message; + + connection.filesContainer.insertBefore(div, connection.filesContainer.firstChild); + + if(file.userid !== connection.userid && div.querySelector('#resend')) { + div.querySelector('#resend').onclick = function(e) { + e.preventDefault(); + this.onclick = function() {}; + + if(connection.fileReceived[file.size + file.name]) { + delete connection.fileReceived[file.size + file.name]; + } + connection.send({ + yesIWannaReceive: true, + fileName: file.name + }, file.userid); + + div.parentNode.removeChild(div); + }; + } + + if (!file.remoteUserId) { + progressHelper[file.uuid] = { + div: div, + progress: div.querySelector('progress'), + label: div.querySelector('label') + }; + progressHelper[file.uuid].progress.max = file.maxChunks; + return; + } + + if (!progressHelper[file.uuid]) { + progressHelper[file.uuid] = {}; + } + + progressHelper[file.uuid][file.remoteUserId] = { + div: div, + progress: div.querySelector('progress'), + label: div.querySelector('label') + }; + progressHelper[file.uuid][file.remoteUserId].progress.max = file.maxChunks; + }; + + // www.RTCMultiConnection.org/docs/onFileProgress/ + connection.onFileProgress = function(chunk) { + if (connection.fileReceived[chunk.size + chunk.name]) return; + + var helper = progressHelper[chunk.uuid]; + if (!helper) { + return; + } + if (chunk.remoteUserId) { + helper = progressHelper[chunk.uuid][chunk.remoteUserId]; + if (!helper) { + return; + } + } + + helper.progress.value = chunk.currentPosition || chunk.maxChunks || helper.progress.max; + updateLabel(helper.progress, helper.label); + }; + + // www.RTCMultiConnection.org/docs/onFileEnd/ + connection.onFileEnd = function(file) { + if (connection.fileReceived[file.size + file.name]) return; + + var div = document.getElementById(file.uuid); + if (div) { + div.parentNode.removeChild(div); + } + + if (file.remoteUserId === connection.userid) { + previewFile(file); + + connection.fileReceived[file.size + file.name] = file; + + var message = 'Successfully received file'; + message += '
    ' + file.name + '.'; + message += '
    Size: ' + bytesToSize(file.size) + '.'; + message += '
    Download'; + var div = appendLog(message); + + div.querySelector('a').onclick = function(e) { + e.preventDefault(); + + if(div.querySelector('#saving') && div.querySelector('#saving').innerHTML !== 'Saved.') { + return; + } + + var span = document.createElement('span'); + span.style.display = 'block'; + span.style.color = 'red'; + span.id = 'saving'; + span.innerHTML = 'Saving...'; + div.appendChild(span); + + app.saveToDisk(file, function(result, file, error) { + if(result === 'success') { + var span = div.querySelector('#saving'); + span.innerHTML = 'Saved to SDCard root.'; + span.style.color = 'green'; + return; + } + div.querySelector('#saving').innerHTML = error; + }); + }; + return; + } + + var message = 'Successfully shared file'; + message += '
    ' + file.name + '.'; + message += '
    With: ' + file.remoteUserId + '.'; + message += '
    Size: ' + bytesToSize(file.size) + '.'; + appendLog(message); + }; + + function updateLabel(progress, label) { + if (progress.position === -1) { + return; + } + + var position = +progress.position.toFixed(2).split('.')[1] || 100; + label.innerHTML = position + '%'; + } + } + + function bytesToSize(bytes) { + var k = 1000; + var sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB']; + if (bytes === 0) { + return '0 Bytes'; + } + var i = parseInt(Math.floor(Math.log(bytes) / Math.log(k)), 10); + return (bytes / Math.pow(k, i)).toPrecision(3) + ' ' + sizes[i]; + } + + function onFileSelected(file) { + lastSelectedFile = file; + + if (connection) { + connection.send({ + doYouWannaReceiveThisFile: true, + fileName: lastSelectedFile.size + lastSelectedFile.name + }); + } + + var innerHTML = 'You selected:
    ' + lastSelectedFile.name + '
    Size: ' + bytesToSize(lastSelectedFile.size) + ''; + appendLog(innerHTML); + } + + var numberOfUsers = document.getElementById('number-of-users'); + + function incrementOrDecrementUsers() { + numberOfUsers.innerHTML = connection ? connection.getAllParticipants().length : 0; + } + + var logsDiv = document.getElementById('logs'); + + function appendLog(html, color) { + var div = document.createElement('div'); + div.innerHTML = '

    ' + html + '

    '; + logsDiv.insertBefore(div, logsDiv.firstChild); + + if(color) { + div.style.color = color; + } + + return div; + } + + appendLog('Live site: webrtcweb.com/fs'); + + window.onerror = function() { + appendLog('Error:
    ' + JSON.stringify(arguments), 'red') + }; + + console.error = function(err) { + appendLog('Error:
    ' + err, 'red') + }; + + function previewFile(file) { + btnSelectFile.style.left = '5px'; + btnSelectFile.style.right = 'auto'; + btnSelectFile.style.zIndex = 10; + btnSelectFile.style.top = '5px'; + btnSelectFile.style.outline = 'none'; + btnExitApp.style.left = ((screen.width/2) - (btnExitApp.clientWidth/2)) + 'px'; + btnExitApp.style.top = 'auto'; + btnExitApp.style.bottom = 0; + btnExitApp.style.paddingLeft = '15px'; + btnExitApp.style.paddingRight = '15px'; + + document.querySelector('.overlay').style.display = 'none'; + + iframe.style.display = 'none'; + imagePreview.style.display = 'none'; + videoPreview.style.display = 'none'; + + iframe.onload = videoPreview.onload = imagePreview.onload = function() { + if(this != iframe) { + this.style.width = '100%'; + this.style.height = 'auto'; + return; + } + + iframe && Array.prototype.slice.call(iframe.contentWindow.document.body.querySelectorAll('*')).forEach(function(element) { + element.style.maxWidth = '100%'; + }); + + if (!file.type || fileNameMatches || file.type.match(/image|video|audio|pdf/g) || iframe.src.indexOf('data:image/png') !== -1) { + iframe.contentWindow.document.body.style.textAlign = 'center'; + iframe.contentWindow.document.body.style.background = 'black'; + iframe.contentWindow.document.body.style.color = 'white'; + return; + } + iframe.contentWindow.document.body.style.textAlign = 'left'; + iframe.contentWindow.document.body.style.background = 'white'; + iframe.contentWindow.document.body.style.color = 'black'; + }; + + var fileNameMatches = (file.name || '').match(/.3gp|.mp3|.mp4|.m4a|.m3u8|.ts|.wav|.ogg|.aac|.flac|.mid|.xmf|.mxmf|.rtx|.mkv|.flv|.jpg|.jpeg|.png|.gif|.webp|.bmp|.webm|.txt|.rtf|.doc|docx|.js|.css|.cs/g); + if (file.type.match(/image/g) || (file.name || '').match(/.jpg|.jpeg|.png|.gif|.webp|.bmp/g)) { + iframe.style.display = 'none'; + videoPreview.style.display = 'none'; + imagePreview.style.display = 'block'; + + imagePreview.src = URL.createObjectURL(file); + } + else if (file.type.match(/video|audio/g) || (file.name || '').match(/.3gp|.mp3|.mp4|.m4a|.m3u8|.ts|.wav|.ogg|.aac|.flac|.mid|.xmf|.mxmf|.rtx|.mkv|.flv|.webm/g)) { + iframe.style.display = 'none'; + imagePreview.style.display = 'none'; + videoPreview.style.display = 'block'; + + videoPreview.src = URL.createObjectURL(file); + } + else if (file.type.match(/image|video|audio|pdf|txt|javascript|css|php|py/g) || fileNameMatches) { + imagePreview.style.display = 'none'; + videoPreview.style.display = 'none'; + iframe.style.display = 'block'; + + iframe.src = URL.createObjectURL(file); + } else { + iframe.src = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAUUAAADQCAMAAACndoMrAAAABGdBTUEAALGPC/xhBQAAAwBQTFRFAAAABAQECAgIDAwMEBAQFBQUGBgYHBwcICAgJCQkKCgoLCwsMDAwNDQ0ODg4PDw8QEBARERESEhITExMUFBQVFRUWFhYXFxcYGBgZGRkaGhobGxscHBwdHR0eHh4fHx8f39/g4ODh4eHi4uLj4+Pk5OTl5eXm5ubn5+fo6Ojp6enq6urr6+vs7Ozt7e3u7u7v7+/w8PDx8fHy8vLz8/P09PT19fX29vb39/f4+Pj5+fn6+vr7+/v8/Pz9/f3+/v7////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAxdBzUgAAAQB0Uk5T////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////AFP3ByUAAAAJcEhZcwAADsIAAA7CARUoSoAAAAAZdEVYdFNvZnR3YXJlAHBhaW50Lm5ldCA0LjAuMTJDBGvsAAAaVElEQVR4Xu1dCVfqPBCdsgmKioqIgFgRtSoqIMoiS///n/KbJV2gRQoUz/Nrc56vFNJpcjO5mSSTBL7jsD0CYIuo5L8BIP5bA4N8RaGnUHzJI4JxWB+B/AsBqVDEx834byMMbBTPEMBUtT0247AOAuN2NYXInSldJC2MIVwHQCvumGowo/iCH2qbiIifQQRqiN4LoogNS4zHFggA5EkdIRVX581RHBM3flfArG4uI34S0UMUsUK3Yyy2QKAtDQzEFXoLEM2x6vJtIyN+lo2duIXeWhGk6721mGgLULwYbRC2zn3Mi1tDiAJiXgwDRbIXY17cFsmYF7dFkJ6PeTE8FMOQFGkZMS+GUPwxL4YAYsyLYYAY24uhoPiH7MXJaJ0cD2eBYo97X1a86TDQE36R/hAv5qATPJsNuHRFnvUmvo+OCpj/d7PPgk/g0RtJz94FeOmivTjK5fry2HP2QL36LXsSQNLPUe5yR4bE6Oyfq6jt6nFKOyjx9/WsE4r+orLrjMjrUHZJqUHeV2YRtNLp+1cC7vHnAqgUuqOW5+Qsy6OnH52GpsQt4fygfKrB9ijmALRPlmZAlq+zugaQSNCkOFbVIl6tkNsCxTtd6v08ihV/FEfA6v2lcZ63QNHDixdwKpnIAKg5rTzcLiuEwN9nIQGiYxaK6I5x3ZvNeroGOcVh81lflB1IF61I86LGHV9OfYcEv6TXnm6HoocXHyFJIs1P/EWUAktMVfLAmHkjZgEnv5l+FIpt0J4k2mvCqko7Q3FJwp9VtZCft9BFTz/6S00I3kNZgwFJf1Jo9h+qt2/zLV//SdffBGrjqnL7yh+nxps5bdcbc21BFowiFFwoFsCi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'; + } + } + + setupWebRTCConnection(); +} diff --git a/RTCMultiConnection/dev/FileBufferReader.js b/cordova-mobile-apps/file-sharing/www/js/rmc3.fbr.min.js similarity index 91% rename from RTCMultiConnection/dev/FileBufferReader.js rename to cordova-mobile-apps/file-sharing/www/js/rmc3.fbr.min.js index d42945be..b8c3fc8e 100644 --- a/RTCMultiConnection/dev/FileBufferReader.js +++ b/cordova-mobile-apps/file-sharing/www/js/rmc3.fbr.min.js @@ -1,4 +1,4 @@ -// Last time updated: 2017-08-27 5:48:35 AM UTC +// Last time updated: 2016-11-04 7:11:11 AM UTC // ________________ // FileBufferReader @@ -34,12 +34,6 @@ } }; - if (file.extra && Object.keys(file.extra).length) { - Object.keys(file.extra).forEach(function(key) { - options.extra[key] = file.extra[key]; - }); - } - fbrHelper.readAsArrayBuffer(fbr, options); }; @@ -260,7 +254,7 @@ size: file.size, name: file.name, type: file.type, - lastModifiedDate: (file.lastModifiedDate || new Date()).toString(), + lastModifiedDate: file.lastModifiedDate.toString(), start: true }); @@ -284,7 +278,7 @@ maxChunks: maxChunks, size: file.size, name: file.name, - lastModifiedDate: (file.lastModifiedDate || new Date()).toString(), + lastModifiedDate: file.lastModifiedDate.toString(), url: URL.createObjectURL(file), type: file.type, end: true @@ -313,7 +307,7 @@ size: file.size, name: file.name, - lastModifiedDate: (file.lastModifiedDate || new Date()).toString(), + lastModifiedDate: file.lastModifiedDate.toString(), type: file.type }); @@ -332,35 +326,14 @@ function FileSelector() { var selector = this; - var noFileSelectedCallback = function() {}; - - selector.selectSingleFile = function(callback, failure) { - if (failure) { - noFileSelectedCallback = failure; - } - - selectFile(callback); - }; - selector.selectMultipleFiles = function(callback, failure) { - if (failure) { - noFileSelectedCallback = failure; - } - + selector.selectSingleFile = selectFile; + selector.selectMultipleFiles = function(callback) { selectFile(callback, true); }; - selector.selectDirectory = function(callback, failure) { - if (failure) { - noFileSelectedCallback = failure; - } - - selectFile(callback, true, true); - }; selector.accept = '*.*'; - function selectFile(callback, multiple, directory) { - callback = callback || function() {}; - + function selectFile(callback, multiple) { var file = document.createElement('input'); file.type = 'file'; @@ -368,40 +341,15 @@ file.multiple = true; } - if (directory) { - file.webkitdirectory = true; - } - file.accept = selector.accept; - file.onclick = function() { - file.clickStarted = true; - }; - - document.body.onfocus = function() { - setTimeout(function() { - if (!file.clickStarted) return; - file.clickStarted = false; - - if (!file.value) { - noFileSelectedCallback(); - } - }, 500); - }; - file.onchange = function() { if (multiple) { if (!file.files.length) { console.error('No file selected.'); return; } - - var arr = []; - Array.from(file.files).forEach(function(file) { - file.url = file.webkitRelativePath; - arr.push(file); - }); - callback(arr); + callback(file.files); return; } @@ -419,47 +367,17 @@ fireClickEvent(file); } - function getValidFileName(fileName) { - if (!fileName) { - fileName = 'file' + (new Date).toISOString().replace(/:|\.|-/g, '') - } - - var a = fileName; - a = a.replace(/^.*[\\\/]([^\\\/]*)$/i, "$1"); - a = a.replace(/\s/g, "_"); - a = a.replace(/,/g, ''); - a = a.toLowerCase(); - return a; - } - function fireClickEvent(element) { - if (typeof element.click === 'function') { - element.click(); - return; - } - - if (typeof element.change === 'function') { - element.change(); - return; - } - - if (typeof document.createEvent('Event') !== 'undefined') { - var event = document.createEvent('Event'); - - if (typeof event.initEvent === 'function' && typeof element.dispatchEvent === 'function') { - event.initEvent('click', true, true); - element.dispatchEvent(event); - return; - } - } - - var event = new MouseEvent('click', { + var evt = new window.MouseEvent('click', { view: window, bubbles: true, - cancelable: true + cancelable: true, + button: 0, + buttons: 0, + mozInputSource: 1 }); - element.dispatchEvent(event); + var fired = element.dispatchEvent(evt); } } @@ -667,14 +585,10 @@ } else { var const_name = obj.constructor.name; - var const_name_reflection = obj.constructor.toString().match(/\w+/g)[1]; - if (const_name !== undefined && Types[const_name.toUpperCase()] !== undefined) { + if (const_name !== undefined) { // return type by .constructor.name if possible type = Types[const_name.toUpperCase()]; - } else if (const_name_reflection !== undefined && Types[const_name_reflection.toUpperCase()] !== undefined) { - type = Types[const_name_reflection.toUpperCase()]; - } else { // Work around when constructor.name is not defined switch (typeof obj) { diff --git a/cordova-mobile-apps/file-sharing/www/js/socket.io.js b/cordova-mobile-apps/file-sharing/www/js/socket.io.js new file mode 100644 index 00000000..86a7c529 --- /dev/null +++ b/cordova-mobile-apps/file-sharing/www/js/socket.io.js @@ -0,0 +1,7685 @@ +(function webpackUniversalModuleDefinition(root, factory) { + if(typeof exports === 'object' && typeof module === 'object') + module.exports = factory(); + else if(typeof define === 'function' && define.amd) + define([], factory); + else if(typeof exports === 'object') + exports["io"] = factory(); + else + root["io"] = factory(); +})(this, function() { +return /******/ (function(modules) { // webpackBootstrap +/******/ // The module cache +/******/ var installedModules = {}; + +/******/ // The require function +/******/ function __webpack_require__(moduleId) { + +/******/ // Check if module is in cache +/******/ if(installedModules[moduleId]) +/******/ return installedModules[moduleId].exports; + +/******/ // Create a new module (and put it into the cache) +/******/ var module = installedModules[moduleId] = { +/******/ exports: {}, +/******/ id: moduleId, +/******/ loaded: false +/******/ }; + +/******/ // Execute the module function +/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); + +/******/ // Flag the module as loaded +/******/ module.loaded = true; + +/******/ // Return the exports of the module +/******/ return module.exports; +/******/ } + + +/******/ // expose the modules object (__webpack_modules__) +/******/ __webpack_require__.m = modules; + +/******/ // expose the module cache +/******/ __webpack_require__.c = installedModules; + +/******/ // __webpack_public_path__ +/******/ __webpack_require__.p = ""; + +/******/ // Load entry module and return exports +/******/ return __webpack_require__(0); +/******/ }) +/************************************************************************/ +/******/ ([ +/* 0 */ +/***/ function(module, exports, __webpack_require__) { + + 'use strict'; + + var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; + + /** + * Module dependencies. + */ + + var url = __webpack_require__(1); + var parser = __webpack_require__(6); + var Manager = __webpack_require__(13); + var debug = __webpack_require__(3)('socket.io-client'); + + /** + * Module exports. + */ + + module.exports = exports = lookup; + + /** + * Managers cache. + */ + + var cache = exports.managers = {}; + + /** + * Looks up an existing `Manager` for multiplexing. + * If the user summons: + * + * `io('http://localhost/a');` + * `io('http://localhost/b');` + * + * We reuse the existing instance based on same scheme/port/host, + * and we initialize sockets for each namespace. + * + * @api public + */ + + function lookup(uri, opts) { + if ((typeof uri === 'undefined' ? 'undefined' : _typeof(uri)) === 'object') { + opts = uri; + uri = undefined; + } + + opts = opts || {}; + + var parsed = url(uri); + var source = parsed.source; + var id = parsed.id; + var path = parsed.path; + var sameNamespace = cache[id] && path in cache[id].nsps; + var newConnection = opts.forceNew || opts['force new connection'] || false === opts.multiplex || sameNamespace; + + var io; + + if (newConnection) { + debug('ignoring socket cache for %s', source); + io = Manager(source, opts); + } else { + if (!cache[id]) { + debug('new io instance for %s', source); + cache[id] = Manager(source, opts); + } + io = cache[id]; + } + if (parsed.query && !opts.query) { + opts.query = parsed.query; + } else if (opts && 'object' === _typeof(opts.query)) { + opts.query = encodeQueryString(opts.query); + } + return io.socket(parsed.path, opts); + } + /** + * Helper method to parse query objects to string. + * @param {object} query + * @returns {string} + */ + function encodeQueryString(obj) { + var str = []; + for (var p in obj) { + if (obj.hasOwnProperty(p)) { + str.push(encodeURIComponent(p) + '=' + encodeURIComponent(obj[p])); + } + } + return str.join('&'); + } + /** + * Protocol version. + * + * @api public + */ + + exports.protocol = parser.protocol; + + /** + * `connect`. + * + * @param {String} uri + * @api public + */ + + exports.connect = lookup; + + /** + * Expose constructors for standalone build. + * + * @api public + */ + + exports.Manager = __webpack_require__(13); + exports.Socket = __webpack_require__(40); + +/***/ }, +/* 1 */ +/***/ function(module, exports, __webpack_require__) { + + /* WEBPACK VAR INJECTION */(function(global) {'use strict'; + + /** + * Module dependencies. + */ + + var parseuri = __webpack_require__(2); + var debug = __webpack_require__(3)('socket.io-client:url'); + + /** + * Module exports. + */ + + module.exports = url; + + /** + * URL parser. + * + * @param {String} url + * @param {Object} An object meant to mimic window.location. + * Defaults to window.location. + * @api public + */ + + function url(uri, loc) { + var obj = uri; + + // default to window.location + loc = loc || global.location; + if (null == uri) uri = loc.protocol + '//' + loc.host; + + // relative path support + if ('string' === typeof uri) { + if ('/' === uri.charAt(0)) { + if ('/' === uri.charAt(1)) { + uri = loc.protocol + uri; + } else { + uri = loc.host + uri; + } + } + + if (!/^(https?|wss?):\/\//.test(uri)) { + debug('protocol-less url %s', uri); + if ('undefined' !== typeof loc) { + uri = loc.protocol + '//' + uri; + } else { + uri = 'https://' + uri; + } + } + + // parse + debug('parse %s', uri); + obj = parseuri(uri); + } + + // make sure we treat `localhost:80` and `localhost` equally + if (!obj.port) { + if (/^(http|ws)$/.test(obj.protocol)) { + obj.port = '80'; + } else if (/^(http|ws)s$/.test(obj.protocol)) { + obj.port = '443'; + } + } + + obj.path = obj.path || '/'; + + var ipv6 = obj.host.indexOf(':') !== -1; + var host = ipv6 ? '[' + obj.host + ']' : obj.host; + + // define unique id + obj.id = obj.protocol + '://' + host + ':' + obj.port; + // define href + obj.href = obj.protocol + '://' + host + (loc && loc.port === obj.port ? '' : ':' + obj.port); + + return obj; + } + /* WEBPACK VAR INJECTION */}.call(exports, (function() { return this; }()))) + +/***/ }, +/* 2 */ +/***/ function(module, exports) { + + /** + * Parses an URI + * + * @author Steven Levithan (MIT license) + * @api private + */ + + var re = /^(?:(?![^:@]+:[^:@\/]*@)(http|https|ws|wss):\/\/)?((?:(([^:@]*)(?::([^:@]*))?)?@)?((?:[a-f0-9]{0,4}:){2,7}[a-f0-9]{0,4}|[^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/; + + var parts = [ + 'source', 'protocol', 'authority', 'userInfo', 'user', 'password', 'host', 'port', 'relative', 'path', 'directory', 'file', 'query', 'anchor' + ]; + + module.exports = function parseuri(str) { + var src = str, + b = str.indexOf('['), + e = str.indexOf(']'); + + if (b != -1 && e != -1) { + str = str.substring(0, b) + str.substring(b, e).replace(/:/g, ';') + str.substring(e, str.length); + } + + var m = re.exec(str || ''), + uri = {}, + i = 14; + + while (i--) { + uri[parts[i]] = m[i] || ''; + } + + if (b != -1 && e != -1) { + uri.source = src; + uri.host = uri.host.substring(1, uri.host.length - 1).replace(/;/g, ':'); + uri.authority = uri.authority.replace('[', '').replace(']', '').replace(/;/g, ':'); + uri.ipv6uri = true; + } + + return uri; + }; + + +/***/ }, +/* 3 */ +/***/ function(module, exports, __webpack_require__) { + + + /** + * This is the web browser implementation of `debug()`. + * + * Expose `debug()` as the module. + */ + + exports = module.exports = __webpack_require__(4); + exports.log = log; + exports.formatArgs = formatArgs; + exports.save = save; + exports.load = load; + exports.useColors = useColors; + exports.storage = 'undefined' != typeof chrome + && 'undefined' != typeof chrome.storage + ? chrome.storage.local + : localstorage(); + + /** + * Colors. + */ + + exports.colors = [ + 'lightseagreen', + 'forestgreen', + 'goldenrod', + 'dodgerblue', + 'darkorchid', + 'crimson' + ]; + + /** + * Currently only WebKit-based Web Inspectors, Firefox >= v31, + * and the Firebug extension (any Firefox version) are known + * to support "%c" CSS customizations. + * + * TODO: add a `localStorage` variable to explicitly enable/disable colors + */ + + function useColors() { + // is webkit? http://stackoverflow.com/a/16459606/376773 + return ('WebkitAppearance' in document.documentElement.style) || + // is firebug? http://stackoverflow.com/a/398120/376773 + (window.console && (console.firebug || (console.exception && console.table))) || + // is firefox >= v31? + // https://developer.mozilla.org/en-US/docs/Tools/Web_Console#Styling_messages + (navigator.userAgent.toLowerCase().match(/firefox\/(\d+)/) && parseInt(RegExp.$1, 10) >= 31); + } + + /** + * Map %j to `JSON.stringify()`, since no Web Inspectors do that by default. + */ + + exports.formatters.j = function(v) { + return JSON.stringify(v); + }; + + + /** + * Colorize log arguments if enabled. + * + * @api public + */ + + function formatArgs() { + var args = arguments; + var useColors = this.useColors; + + args[0] = (useColors ? '%c' : '') + + this.namespace + + (useColors ? ' %c' : ' ') + + args[0] + + (useColors ? '%c ' : ' ') + + '+' + exports.humanize(this.diff); + + if (!useColors) return args; + + var c = 'color: ' + this.color; + args = [args[0], c, 'color: inherit'].concat(Array.prototype.slice.call(args, 1)); + + // the final "%c" is somewhat tricky, because there could be other + // arguments passed either before or after the %c, so we need to + // figure out the correct index to insert the CSS into + var index = 0; + var lastC = 0; + args[0].replace(/%[a-z%]/g, function(match) { + if ('%%' === match) return; + index++; + if ('%c' === match) { + // we only are interested in the *last* %c + // (the user may have provided their own) + lastC = index; + } + }); + + args.splice(lastC, 0, c); + return args; + } + + /** + * Invokes `console.log()` when available. + * No-op when `console.log` is not a "function". + * + * @api public + */ + + function log() { + // this hackery is required for IE8/9, where + // the `console.log` function doesn't have 'apply' + return 'object' === typeof console + && console.log + && Function.prototype.apply.call(console.log, console, arguments); + } + + /** + * Save `namespaces`. + * + * @param {String} namespaces + * @api private + */ + + function save(namespaces) { + try { + if (null == namespaces) { + exports.storage.removeItem('debug'); + } else { + exports.storage.debug = namespaces; + } + } catch(e) {} + } + + /** + * Load `namespaces`. + * + * @return {String} returns the previously persisted debug modes + * @api private + */ + + function load() { + var r; + try { + r = exports.storage.debug; + } catch(e) {} + return r; + } + + /** + * Enable namespaces listed in `localStorage.debug` initially. + */ + + exports.enable(load()); + + /** + * Localstorage attempts to return the localstorage. + * + * This is necessary because safari throws + * when a user disables cookies/localstorage + * and you attempt to access it. + * + * @return {LocalStorage} + * @api private + */ + + function localstorage(){ + try { + return window.localStorage; + } catch (e) {} + } + + +/***/ }, +/* 4 */ +/***/ function(module, exports, __webpack_require__) { + + + /** + * This is the common logic for both the Node.js and web browser + * implementations of `debug()`. + * + * Expose `debug()` as the module. + */ + + exports = module.exports = debug; + exports.coerce = coerce; + exports.disable = disable; + exports.enable = enable; + exports.enabled = enabled; + exports.humanize = __webpack_require__(5); + + /** + * The currently active debug mode names, and names to skip. + */ + + exports.names = []; + exports.skips = []; + + /** + * Map of special "%n" handling functions, for the debug "format" argument. + * + * Valid key names are a single, lowercased letter, i.e. "n". + */ + + exports.formatters = {}; + + /** + * Previously assigned color. + */ + + var prevColor = 0; + + /** + * Previous log timestamp. + */ + + var prevTime; + + /** + * Select a color. + * + * @return {Number} + * @api private + */ + + function selectColor() { + return exports.colors[prevColor++ % exports.colors.length]; + } + + /** + * Create a debugger with the given `namespace`. + * + * @param {String} namespace + * @return {Function} + * @api public + */ + + function debug(namespace) { + + // define the `disabled` version + function disabled() { + } + disabled.enabled = false; + + // define the `enabled` version + function enabled() { + + var self = enabled; + + // set `diff` timestamp + var curr = +new Date(); + var ms = curr - (prevTime || curr); + self.diff = ms; + self.prev = prevTime; + self.curr = curr; + prevTime = curr; + + // add the `color` if not set + if (null == self.useColors) self.useColors = exports.useColors(); + if (null == self.color && self.useColors) self.color = selectColor(); + + var args = Array.prototype.slice.call(arguments); + + args[0] = exports.coerce(args[0]); + + if ('string' !== typeof args[0]) { + // anything else let's inspect with %o + args = ['%o'].concat(args); + } + + // apply any `formatters` transformations + var index = 0; + args[0] = args[0].replace(/%([a-z%])/g, function(match, format) { + // if we encounter an escaped % then don't increase the array index + if (match === '%%') return match; + index++; + var formatter = exports.formatters[format]; + if ('function' === typeof formatter) { + var val = args[index]; + match = formatter.call(self, val); + + // now we need to remove `args[index]` since it's inlined in the `format` + args.splice(index, 1); + index--; + } + return match; + }); + + if ('function' === typeof exports.formatArgs) { + args = exports.formatArgs.apply(self, args); + } + var logFn = enabled.log || exports.log || console.log.bind(console); + logFn.apply(self, args); + } + enabled.enabled = true; + + var fn = exports.enabled(namespace) ? enabled : disabled; + + fn.namespace = namespace; + + return fn; + } + + /** + * Enables a debug mode by namespaces. This can include modes + * separated by a colon and wildcards. + * + * @param {String} namespaces + * @api public + */ + + function enable(namespaces) { + exports.save(namespaces); + + var split = (namespaces || '').split(/[\s,]+/); + var len = split.length; + + for (var i = 0; i < len; i++) { + if (!split[i]) continue; // ignore empty strings + namespaces = split[i].replace(/\*/g, '.*?'); + if (namespaces[0] === '-') { + exports.skips.push(new RegExp('^' + namespaces.substr(1) + '$')); + } else { + exports.names.push(new RegExp('^' + namespaces + '$')); + } + } + } + + /** + * Disable debug output. + * + * @api public + */ + + function disable() { + exports.enable(''); + } + + /** + * Returns true if the given mode name is enabled, false otherwise. + * + * @param {String} name + * @return {Boolean} + * @api public + */ + + function enabled(name) { + var i, len; + for (i = 0, len = exports.skips.length; i < len; i++) { + if (exports.skips[i].test(name)) { + return false; + } + } + for (i = 0, len = exports.names.length; i < len; i++) { + if (exports.names[i].test(name)) { + return true; + } + } + return false; + } + + /** + * Coerce `val`. + * + * @param {Mixed} val + * @return {Mixed} + * @api private + */ + + function coerce(val) { + if (val instanceof Error) return val.stack || val.message; + return val; + } + + +/***/ }, +/* 5 */ +/***/ function(module, exports) { + + /** + * Helpers. + */ + + var s = 1000; + var m = s * 60; + var h = m * 60; + var d = h * 24; + var y = d * 365.25; + + /** + * Parse or format the given `val`. + * + * Options: + * + * - `long` verbose formatting [false] + * + * @param {String|Number} val + * @param {Object} options + * @return {String|Number} + * @api public + */ + + module.exports = function(val, options){ + options = options || {}; + if ('string' == typeof val) return parse(val); + return options.long + ? long(val) + : short(val); + }; + + /** + * Parse the given `str` and return milliseconds. + * + * @param {String} str + * @return {Number} + * @api private + */ + + function parse(str) { + str = '' + str; + if (str.length > 10000) return; + var match = /^((?:\d+)?\.?\d+) *(milliseconds?|msecs?|ms|seconds?|secs?|s|minutes?|mins?|m|hours?|hrs?|h|days?|d|years?|yrs?|y)?$/i.exec(str); + if (!match) return; + var n = parseFloat(match[1]); + var type = (match[2] || 'ms').toLowerCase(); + switch (type) { + case 'years': + case 'year': + case 'yrs': + case 'yr': + case 'y': + return n * y; + case 'days': + case 'day': + case 'd': + return n * d; + case 'hours': + case 'hour': + case 'hrs': + case 'hr': + case 'h': + return n * h; + case 'minutes': + case 'minute': + case 'mins': + case 'min': + case 'm': + return n * m; + case 'seconds': + case 'second': + case 'secs': + case 'sec': + case 's': + return n * s; + case 'milliseconds': + case 'millisecond': + case 'msecs': + case 'msec': + case 'ms': + return n; + } + } + + /** + * Short format for `ms`. + * + * @param {Number} ms + * @return {String} + * @api private + */ + + function short(ms) { + if (ms >= d) return Math.round(ms / d) + 'd'; + if (ms >= h) return Math.round(ms / h) + 'h'; + if (ms >= m) return Math.round(ms / m) + 'm'; + if (ms >= s) return Math.round(ms / s) + 's'; + return ms + 'ms'; + } + + /** + * Long format for `ms`. + * + * @param {Number} ms + * @return {String} + * @api private + */ + + function long(ms) { + return plural(ms, d, 'day') + || plural(ms, h, 'hour') + || plural(ms, m, 'minute') + || plural(ms, s, 'second') + || ms + ' ms'; + } + + /** + * Pluralization helper. + */ + + function plural(ms, n, name) { + if (ms < n) return; + if (ms < n * 1.5) return Math.floor(ms / n) + ' ' + name; + return Math.ceil(ms / n) + ' ' + name + 's'; + } + + +/***/ }, +/* 6 */ +/***/ function(module, exports, __webpack_require__) { + + + /** + * Module dependencies. + */ + + var debug = __webpack_require__(3)('socket.io-parser'); + var json = __webpack_require__(7); + var isArray = __webpack_require__(9); + var Emitter = __webpack_require__(10); + var binary = __webpack_require__(11); + var isBuf = __webpack_require__(12); + + /** + * Protocol version. + * + * @api public + */ + + exports.protocol = 4; + + /** + * Packet types. + * + * @api public + */ + + exports.types = [ + 'CONNECT', + 'DISCONNECT', + 'EVENT', + 'ACK', + 'ERROR', + 'BINARY_EVENT', + 'BINARY_ACK' + ]; + + /** + * Packet type `connect`. + * + * @api public + */ + + exports.CONNECT = 0; + + /** + * Packet type `disconnect`. + * + * @api public + */ + + exports.DISCONNECT = 1; + + /** + * Packet type `event`. + * + * @api public + */ + + exports.EVENT = 2; + + /** + * Packet type `ack`. + * + * @api public + */ + + exports.ACK = 3; + + /** + * Packet type `error`. + * + * @api public + */ + + exports.ERROR = 4; + + /** + * Packet type 'binary event' + * + * @api public + */ + + exports.BINARY_EVENT = 5; + + /** + * Packet type `binary ack`. For acks with binary arguments. + * + * @api public + */ + + exports.BINARY_ACK = 6; + + /** + * Encoder constructor. + * + * @api public + */ + + exports.Encoder = Encoder; + + /** + * Decoder constructor. + * + * @api public + */ + + exports.Decoder = Decoder; + + /** + * A socket.io Encoder instance + * + * @api public + */ + + function Encoder() {} + + /** + * Encode a packet as a single string if non-binary, or as a + * buffer sequence, depending on packet type. + * + * @param {Object} obj - packet object + * @param {Function} callback - function to handle encodings (likely engine.write) + * @return Calls callback with Array of encodings + * @api public + */ + + Encoder.prototype.encode = function(obj, callback){ + debug('encoding packet %j', obj); + + if (exports.BINARY_EVENT == obj.type || exports.BINARY_ACK == obj.type) { + encodeAsBinary(obj, callback); + } + else { + var encoding = encodeAsString(obj); + callback([encoding]); + } + }; + + /** + * Encode packet as string. + * + * @param {Object} packet + * @return {String} encoded + * @api private + */ + + function encodeAsString(obj) { + var str = ''; + var nsp = false; + + // first is type + str += obj.type; + + // attachments if we have them + if (exports.BINARY_EVENT == obj.type || exports.BINARY_ACK == obj.type) { + str += obj.attachments; + str += '-'; + } + + // if we have a namespace other than `/` + // we append it followed by a comma `,` + if (obj.nsp && '/' != obj.nsp) { + nsp = true; + str += obj.nsp; + } + + // immediately followed by the id + if (null != obj.id) { + if (nsp) { + str += ','; + nsp = false; + } + str += obj.id; + } + + // json data + if (null != obj.data) { + if (nsp) str += ','; + str += json.stringify(obj.data); + } + + debug('encoded %j as %s', obj, str); + return str; + } + + /** + * Encode packet as 'buffer sequence' by removing blobs, and + * deconstructing packet into object with placeholders and + * a list of buffers. + * + * @param {Object} packet + * @return {Buffer} encoded + * @api private + */ + + function encodeAsBinary(obj, callback) { + + function writeEncoding(bloblessData) { + var deconstruction = binary.deconstructPacket(bloblessData); + var pack = encodeAsString(deconstruction.packet); + var buffers = deconstruction.buffers; + + buffers.unshift(pack); // add packet info to beginning of data list + callback(buffers); // write all the buffers + } + + binary.removeBlobs(obj, writeEncoding); + } + + /** + * A socket.io Decoder instance + * + * @return {Object} decoder + * @api public + */ + + function Decoder() { + this.reconstructor = null; + } + + /** + * Mix in `Emitter` with Decoder. + */ + + Emitter(Decoder.prototype); + + /** + * Decodes an ecoded packet string into packet JSON. + * + * @param {String} obj - encoded packet + * @return {Object} packet + * @api public + */ + + Decoder.prototype.add = function(obj) { + var packet; + if ('string' == typeof obj) { + packet = decodeString(obj); + if (exports.BINARY_EVENT == packet.type || exports.BINARY_ACK == packet.type) { // binary packet's json + this.reconstructor = new BinaryReconstructor(packet); + + // no attachments, labeled binary but no binary data to follow + if (this.reconstructor.reconPack.attachments === 0) { + this.emit('decoded', packet); + } + } else { // non-binary full packet + this.emit('decoded', packet); + } + } + else if (isBuf(obj) || obj.base64) { // raw binary data + if (!this.reconstructor) { + throw new Error('got binary data when not reconstructing a packet'); + } else { + packet = this.reconstructor.takeBinaryData(obj); + if (packet) { // received final buffer + this.reconstructor = null; + this.emit('decoded', packet); + } + } + } + else { + throw new Error('Unknown type: ' + obj); + } + }; + + /** + * Decode a packet String (JSON data) + * + * @param {String} str + * @return {Object} packet + * @api private + */ + + function decodeString(str) { + var p = {}; + var i = 0; + + // look up type + p.type = Number(str.charAt(0)); + if (null == exports.types[p.type]) return error(); + + // look up attachments if type binary + if (exports.BINARY_EVENT == p.type || exports.BINARY_ACK == p.type) { + var buf = ''; + while (str.charAt(++i) != '-') { + buf += str.charAt(i); + if (i == str.length) break; + } + if (buf != Number(buf) || str.charAt(i) != '-') { + throw new Error('Illegal attachments'); + } + p.attachments = Number(buf); + } + + // look up namespace (if any) + if ('/' == str.charAt(i + 1)) { + p.nsp = ''; + while (++i) { + var c = str.charAt(i); + if (',' == c) break; + p.nsp += c; + if (i == str.length) break; + } + } else { + p.nsp = '/'; + } + + // look up id + var next = str.charAt(i + 1); + if ('' !== next && Number(next) == next) { + p.id = ''; + while (++i) { + var c = str.charAt(i); + if (null == c || Number(c) != c) { + --i; + break; + } + p.id += str.charAt(i); + if (i == str.length) break; + } + p.id = Number(p.id); + } + + // look up json data + if (str.charAt(++i)) { + try { + p.data = json.parse(str.substr(i)); + } catch(e){ + return error(); + } + } + + debug('decoded %s as %j', str, p); + return p; + } + + /** + * Deallocates a parser's resources + * + * @api public + */ + + Decoder.prototype.destroy = function() { + if (this.reconstructor) { + this.reconstructor.finishedReconstruction(); + } + }; + + /** + * A manager of a binary event's 'buffer sequence'. Should + * be constructed whenever a packet of type BINARY_EVENT is + * decoded. + * + * @param {Object} packet + * @return {BinaryReconstructor} initialized reconstructor + * @api private + */ + + function BinaryReconstructor(packet) { + this.reconPack = packet; + this.buffers = []; + } + + /** + * Method to be called when binary data received from connection + * after a BINARY_EVENT packet. + * + * @param {Buffer | ArrayBuffer} binData - the raw binary data received + * @return {null | Object} returns null if more binary data is expected or + * a reconstructed packet object if all buffers have been received. + * @api private + */ + + BinaryReconstructor.prototype.takeBinaryData = function(binData) { + this.buffers.push(binData); + if (this.buffers.length == this.reconPack.attachments) { // done with buffer list + var packet = binary.reconstructPacket(this.reconPack, this.buffers); + this.finishedReconstruction(); + return packet; + } + return null; + }; + + /** + * Cleans up binary packet reconstruction variables. + * + * @api private + */ + + BinaryReconstructor.prototype.finishedReconstruction = function() { + this.reconPack = null; + this.buffers = []; + }; + + function error(data){ + return { + type: exports.ERROR, + data: 'parser error' + }; + } + + +/***/ }, +/* 7 */ +/***/ function(module, exports, __webpack_require__) { + + /* WEBPACK VAR INJECTION */(function(module, global) {/*** IMPORTS FROM imports-loader ***/ + var define = false; + + /*! JSON v3.3.2 | http://bestiejs.github.io/json3 | Copyright 2012-2014, Kit Cambridge | http://kit.mit-license.org */ + ;(function () { + // Detect the `define` function exposed by asynchronous module loaders. The + // strict `define` check is necessary for compatibility with `r.js`. + var isLoader = typeof define === "function" && define.amd; + + // A set of types used to distinguish objects from primitives. + var objectTypes = { + "function": true, + "object": true + }; + + // Detect the `exports` object exposed by CommonJS implementations. + var freeExports = objectTypes[typeof exports] && exports && !exports.nodeType && exports; + + // Use the `global` object exposed by Node (including Browserify via + // `insert-module-globals`), Narwhal, and Ringo as the default context, + // and the `window` object in browsers. Rhino exports a `global` function + // instead. + var root = objectTypes[typeof window] && window || this, + freeGlobal = freeExports && objectTypes[typeof module] && module && !module.nodeType && typeof global == "object" && global; + + if (freeGlobal && (freeGlobal["global"] === freeGlobal || freeGlobal["window"] === freeGlobal || freeGlobal["self"] === freeGlobal)) { + root = freeGlobal; + } + + // Public: Initializes JSON 3 using the given `context` object, attaching the + // `stringify` and `parse` functions to the specified `exports` object. + function runInContext(context, exports) { + context || (context = root["Object"]()); + exports || (exports = root["Object"]()); + + // Native constructor aliases. + var Number = context["Number"] || root["Number"], + String = context["String"] || root["String"], + Object = context["Object"] || root["Object"], + Date = context["Date"] || root["Date"], + SyntaxError = context["SyntaxError"] || root["SyntaxError"], + TypeError = context["TypeError"] || root["TypeError"], + Math = context["Math"] || root["Math"], + nativeJSON = context["JSON"] || root["JSON"]; + + // Delegate to the native `stringify` and `parse` implementations. + if (typeof nativeJSON == "object" && nativeJSON) { + exports.stringify = nativeJSON.stringify; + exports.parse = nativeJSON.parse; + } + + // Convenience aliases. + var objectProto = Object.prototype, + getClass = objectProto.toString, + isProperty, forEach, undef; + + // Test the `Date#getUTC*` methods. Based on work by @Yaffle. + var isExtended = new Date(-3509827334573292); + try { + // The `getUTCFullYear`, `Month`, and `Date` methods return nonsensical + // results for certain dates in Opera >= 10.53. + isExtended = isExtended.getUTCFullYear() == -109252 && isExtended.getUTCMonth() === 0 && isExtended.getUTCDate() === 1 && + // Safari < 2.0.2 stores the internal millisecond time value correctly, + // but clips the values returned by the date methods to the range of + // signed 32-bit integers ([-2 ** 31, 2 ** 31 - 1]). + isExtended.getUTCHours() == 10 && isExtended.getUTCMinutes() == 37 && isExtended.getUTCSeconds() == 6 && isExtended.getUTCMilliseconds() == 708; + } catch (exception) {} + + // Internal: Determines whether the native `JSON.stringify` and `parse` + // implementations are spec-compliant. Based on work by Ken Snyder. + function has(name) { + if (has[name] !== undef) { + // Return cached feature test result. + return has[name]; + } + var isSupported; + if (name == "bug-string-char-index") { + // IE <= 7 doesn't support accessing string characters using square + // bracket notation. IE 8 only supports this for primitives. + isSupported = "a"[0] != "a"; + } else if (name == "json") { + // Indicates whether both `JSON.stringify` and `JSON.parse` are + // supported. + isSupported = has("json-stringify") && has("json-parse"); + } else { + var value, serialized = '{"a":[1,true,false,null,"\\u0000\\b\\n\\f\\r\\t"]}'; + // Test `JSON.stringify`. + if (name == "json-stringify") { + var stringify = exports.stringify, stringifySupported = typeof stringify == "function" && isExtended; + if (stringifySupported) { + // A test function object with a custom `toJSON` method. + (value = function () { + return 1; + }).toJSON = value; + try { + stringifySupported = + // Firefox 3.1b1 and b2 serialize string, number, and boolean + // primitives as object literals. + stringify(0) === "0" && + // FF 3.1b1, b2, and JSON 2 serialize wrapped primitives as object + // literals. + stringify(new Number()) === "0" && + stringify(new String()) == '""' && + // FF 3.1b1, 2 throw an error if the value is `null`, `undefined`, or + // does not define a canonical JSON representation (this applies to + // objects with `toJSON` properties as well, *unless* they are nested + // within an object or array). + stringify(getClass) === undef && + // IE 8 serializes `undefined` as `"undefined"`. Safari <= 5.1.7 and + // FF 3.1b3 pass this test. + stringify(undef) === undef && + // Safari <= 5.1.7 and FF 3.1b3 throw `Error`s and `TypeError`s, + // respectively, if the value is omitted entirely. + stringify() === undef && + // FF 3.1b1, 2 throw an error if the given value is not a number, + // string, array, object, Boolean, or `null` literal. This applies to + // objects with custom `toJSON` methods as well, unless they are nested + // inside object or array literals. YUI 3.0.0b1 ignores custom `toJSON` + // methods entirely. + stringify(value) === "1" && + stringify([value]) == "[1]" && + // Prototype <= 1.6.1 serializes `[undefined]` as `"[]"` instead of + // `"[null]"`. + stringify([undef]) == "[null]" && + // YUI 3.0.0b1 fails to serialize `null` literals. + stringify(null) == "null" && + // FF 3.1b1, 2 halts serialization if an array contains a function: + // `[1, true, getClass, 1]` serializes as "[1,true,],". FF 3.1b3 + // elides non-JSON values from objects and arrays, unless they + // define custom `toJSON` methods. + stringify([undef, getClass, null]) == "[null,null,null]" && + // Simple serialization test. FF 3.1b1 uses Unicode escape sequences + // where character escape codes are expected (e.g., `\b` => `\u0008`). + stringify({ "a": [value, true, false, null, "\x00\b\n\f\r\t"] }) == serialized && + // FF 3.1b1 and b2 ignore the `filter` and `width` arguments. + stringify(null, value) === "1" && + stringify([1, 2], null, 1) == "[\n 1,\n 2\n]" && + // JSON 2, Prototype <= 1.7, and older WebKit builds incorrectly + // serialize extended years. + stringify(new Date(-8.64e15)) == '"-271821-04-20T00:00:00.000Z"' && + // The milliseconds are optional in ES 5, but required in 5.1. + stringify(new Date(8.64e15)) == '"+275760-09-13T00:00:00.000Z"' && + // Firefox <= 11.0 incorrectly serializes years prior to 0 as negative + // four-digit years instead of six-digit years. Credits: @Yaffle. + stringify(new Date(-621987552e5)) == '"-000001-01-01T00:00:00.000Z"' && + // Safari <= 5.1.5 and Opera >= 10.53 incorrectly serialize millisecond + // values less than 1000. Credits: @Yaffle. + stringify(new Date(-1)) == '"1969-12-31T23:59:59.999Z"'; + } catch (exception) { + stringifySupported = false; + } + } + isSupported = stringifySupported; + } + // Test `JSON.parse`. + if (name == "json-parse") { + var parse = exports.parse; + if (typeof parse == "function") { + try { + // FF 3.1b1, b2 will throw an exception if a bare literal is provided. + // Conforming implementations should also coerce the initial argument to + // a string prior to parsing. + if (parse("0") === 0 && !parse(false)) { + // Simple parsing test. + value = parse(serialized); + var parseSupported = value["a"].length == 5 && value["a"][0] === 1; + if (parseSupported) { + try { + // Safari <= 5.1.2 and FF 3.1b1 allow unescaped tabs in strings. + parseSupported = !parse('"\t"'); + } catch (exception) {} + if (parseSupported) { + try { + // FF 4.0 and 4.0.1 allow leading `+` signs and leading + // decimal points. FF 4.0, 4.0.1, and IE 9-10 also allow + // certain octal literals. + parseSupported = parse("01") !== 1; + } catch (exception) {} + } + if (parseSupported) { + try { + // FF 4.0, 4.0.1, and Rhino 1.7R3-R4 allow trailing decimal + // points. These environments, along with FF 3.1b1 and 2, + // also allow trailing commas in JSON objects and arrays. + parseSupported = parse("1.") !== 1; + } catch (exception) {} + } + } + } + } catch (exception) { + parseSupported = false; + } + } + isSupported = parseSupported; + } + } + return has[name] = !!isSupported; + } + + if (!has("json")) { + // Common `[[Class]]` name aliases. + var functionClass = "[object Function]", + dateClass = "[object Date]", + numberClass = "[object Number]", + stringClass = "[object String]", + arrayClass = "[object Array]", + booleanClass = "[object Boolean]"; + + // Detect incomplete support for accessing string characters by index. + var charIndexBuggy = has("bug-string-char-index"); + + // Define additional utility methods if the `Date` methods are buggy. + if (!isExtended) { + var floor = Math.floor; + // A mapping between the months of the year and the number of days between + // January 1st and the first of the respective month. + var Months = [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334]; + // Internal: Calculates the number of days between the Unix epoch and the + // first day of the given month. + var getDay = function (year, month) { + return Months[month] + 365 * (year - 1970) + floor((year - 1969 + (month = +(month > 1))) / 4) - floor((year - 1901 + month) / 100) + floor((year - 1601 + month) / 400); + }; + } + + // Internal: Determines if a property is a direct property of the given + // object. Delegates to the native `Object#hasOwnProperty` method. + if (!(isProperty = objectProto.hasOwnProperty)) { + isProperty = function (property) { + var members = {}, constructor; + if ((members.__proto__ = null, members.__proto__ = { + // The *proto* property cannot be set multiple times in recent + // versions of Firefox and SeaMonkey. + "toString": 1 + }, members).toString != getClass) { + // Safari <= 2.0.3 doesn't implement `Object#hasOwnProperty`, but + // supports the mutable *proto* property. + isProperty = function (property) { + // Capture and break the object's prototype chain (see section 8.6.2 + // of the ES 5.1 spec). The parenthesized expression prevents an + // unsafe transformation by the Closure Compiler. + var original = this.__proto__, result = property in (this.__proto__ = null, this); + // Restore the original prototype chain. + this.__proto__ = original; + return result; + }; + } else { + // Capture a reference to the top-level `Object` constructor. + constructor = members.constructor; + // Use the `constructor` property to simulate `Object#hasOwnProperty` in + // other environments. + isProperty = function (property) { + var parent = (this.constructor || constructor).prototype; + return property in this && !(property in parent && this[property] === parent[property]); + }; + } + members = null; + return isProperty.call(this, property); + }; + } + + // Internal: Normalizes the `for...in` iteration algorithm across + // environments. Each enumerated key is yielded to a `callback` function. + forEach = function (object, callback) { + var size = 0, Properties, members, property; + + // Tests for bugs in the current environment's `for...in` algorithm. The + // `valueOf` property inherits the non-enumerable flag from + // `Object.prototype` in older versions of IE, Netscape, and Mozilla. + (Properties = function () { + this.valueOf = 0; + }).prototype.valueOf = 0; + + // Iterate over a new instance of the `Properties` class. + members = new Properties(); + for (property in members) { + // Ignore all properties inherited from `Object.prototype`. + if (isProperty.call(members, property)) { + size++; + } + } + Properties = members = null; + + // Normalize the iteration algorithm. + if (!size) { + // A list of non-enumerable properties inherited from `Object.prototype`. + members = ["valueOf", "toString", "toLocaleString", "propertyIsEnumerable", "isPrototypeOf", "hasOwnProperty", "constructor"]; + // IE <= 8, Mozilla 1.0, and Netscape 6.2 ignore shadowed non-enumerable + // properties. + forEach = function (object, callback) { + var isFunction = getClass.call(object) == functionClass, property, length; + var hasProperty = !isFunction && typeof object.constructor != "function" && objectTypes[typeof object.hasOwnProperty] && object.hasOwnProperty || isProperty; + for (property in object) { + // Gecko <= 1.0 enumerates the `prototype` property of functions under + // certain conditions; IE does not. + if (!(isFunction && property == "prototype") && hasProperty.call(object, property)) { + callback(property); + } + } + // Manually invoke the callback for each non-enumerable property. + for (length = members.length; property = members[--length]; hasProperty.call(object, property) && callback(property)); + }; + } else if (size == 2) { + // Safari <= 2.0.4 enumerates shadowed properties twice. + forEach = function (object, callback) { + // Create a set of iterated properties. + var members = {}, isFunction = getClass.call(object) == functionClass, property; + for (property in object) { + // Store each property name to prevent double enumeration. The + // `prototype` property of functions is not enumerated due to cross- + // environment inconsistencies. + if (!(isFunction && property == "prototype") && !isProperty.call(members, property) && (members[property] = 1) && isProperty.call(object, property)) { + callback(property); + } + } + }; + } else { + // No bugs detected; use the standard `for...in` algorithm. + forEach = function (object, callback) { + var isFunction = getClass.call(object) == functionClass, property, isConstructor; + for (property in object) { + if (!(isFunction && property == "prototype") && isProperty.call(object, property) && !(isConstructor = property === "constructor")) { + callback(property); + } + } + // Manually invoke the callback for the `constructor` property due to + // cross-environment inconsistencies. + if (isConstructor || isProperty.call(object, (property = "constructor"))) { + callback(property); + } + }; + } + return forEach(object, callback); + }; + + // Public: Serializes a JavaScript `value` as a JSON string. The optional + // `filter` argument may specify either a function that alters how object and + // array members are serialized, or an array of strings and numbers that + // indicates which properties should be serialized. The optional `width` + // argument may be either a string or number that specifies the indentation + // level of the output. + if (!has("json-stringify")) { + // Internal: A map of control characters and their escaped equivalents. + var Escapes = { + 92: "\\\\", + 34: '\\"', + 8: "\\b", + 12: "\\f", + 10: "\\n", + 13: "\\r", + 9: "\\t" + }; + + // Internal: Converts `value` into a zero-padded string such that its + // length is at least equal to `width`. The `width` must be <= 6. + var leadingZeroes = "000000"; + var toPaddedString = function (width, value) { + // The `|| 0` expression is necessary to work around a bug in + // Opera <= 7.54u2 where `0 == -0`, but `String(-0) !== "0"`. + return (leadingZeroes + (value || 0)).slice(-width); + }; + + // Internal: Double-quotes a string `value`, replacing all ASCII control + // characters (characters with code unit values between 0 and 31) with + // their escaped equivalents. This is an implementation of the + // `Quote(value)` operation defined in ES 5.1 section 15.12.3. + var unicodePrefix = "\\u00"; + var quote = function (value) { + var result = '"', index = 0, length = value.length, useCharIndex = !charIndexBuggy || length > 10; + var symbols = useCharIndex && (charIndexBuggy ? value.split("") : value); + for (; index < length; index++) { + var charCode = value.charCodeAt(index); + // If the character is a control character, append its Unicode or + // shorthand escape sequence; otherwise, append the character as-is. + switch (charCode) { + case 8: case 9: case 10: case 12: case 13: case 34: case 92: + result += Escapes[charCode]; + break; + default: + if (charCode < 32) { + result += unicodePrefix + toPaddedString(2, charCode.toString(16)); + break; + } + result += useCharIndex ? symbols[index] : value.charAt(index); + } + } + return result + '"'; + }; + + // Internal: Recursively serializes an object. Implements the + // `Str(key, holder)`, `JO(value)`, and `JA(value)` operations. + var serialize = function (property, object, callback, properties, whitespace, indentation, stack) { + var value, className, year, month, date, time, hours, minutes, seconds, milliseconds, results, element, index, length, prefix, result; + try { + // Necessary for host object support. + value = object[property]; + } catch (exception) {} + if (typeof value == "object" && value) { + className = getClass.call(value); + if (className == dateClass && !isProperty.call(value, "toJSON")) { + if (value > -1 / 0 && value < 1 / 0) { + // Dates are serialized according to the `Date#toJSON` method + // specified in ES 5.1 section 15.9.5.44. See section 15.9.1.15 + // for the ISO 8601 date time string format. + if (getDay) { + // Manually compute the year, month, date, hours, minutes, + // seconds, and milliseconds if the `getUTC*` methods are + // buggy. Adapted from @Yaffle's `date-shim` project. + date = floor(value / 864e5); + for (year = floor(date / 365.2425) + 1970 - 1; getDay(year + 1, 0) <= date; year++); + for (month = floor((date - getDay(year, 0)) / 30.42); getDay(year, month + 1) <= date; month++); + date = 1 + date - getDay(year, month); + // The `time` value specifies the time within the day (see ES + // 5.1 section 15.9.1.2). The formula `(A % B + B) % B` is used + // to compute `A modulo B`, as the `%` operator does not + // correspond to the `modulo` operation for negative numbers. + time = (value % 864e5 + 864e5) % 864e5; + // The hours, minutes, seconds, and milliseconds are obtained by + // decomposing the time within the day. See section 15.9.1.10. + hours = floor(time / 36e5) % 24; + minutes = floor(time / 6e4) % 60; + seconds = floor(time / 1e3) % 60; + milliseconds = time % 1e3; + } else { + year = value.getUTCFullYear(); + month = value.getUTCMonth(); + date = value.getUTCDate(); + hours = value.getUTCHours(); + minutes = value.getUTCMinutes(); + seconds = value.getUTCSeconds(); + milliseconds = value.getUTCMilliseconds(); + } + // Serialize extended years correctly. + value = (year <= 0 || year >= 1e4 ? (year < 0 ? "-" : "+") + toPaddedString(6, year < 0 ? -year : year) : toPaddedString(4, year)) + + "-" + toPaddedString(2, month + 1) + "-" + toPaddedString(2, date) + + // Months, dates, hours, minutes, and seconds should have two + // digits; milliseconds should have three. + "T" + toPaddedString(2, hours) + ":" + toPaddedString(2, minutes) + ":" + toPaddedString(2, seconds) + + // Milliseconds are optional in ES 5.0, but required in 5.1. + "." + toPaddedString(3, milliseconds) + "Z"; + } else { + value = null; + } + } else if (typeof value.toJSON == "function" && ((className != numberClass && className != stringClass && className != arrayClass) || isProperty.call(value, "toJSON"))) { + // Prototype <= 1.6.1 adds non-standard `toJSON` methods to the + // `Number`, `String`, `Date`, and `Array` prototypes. JSON 3 + // ignores all `toJSON` methods on these objects unless they are + // defined directly on an instance. + value = value.toJSON(property); + } + } + if (callback) { + // If a replacement function was provided, call it to obtain the value + // for serialization. + value = callback.call(object, property, value); + } + if (value === null) { + return "null"; + } + className = getClass.call(value); + if (className == booleanClass) { + // Booleans are represented literally. + return "" + value; + } else if (className == numberClass) { + // JSON numbers must be finite. `Infinity` and `NaN` are serialized as + // `"null"`. + return value > -1 / 0 && value < 1 / 0 ? "" + value : "null"; + } else if (className == stringClass) { + // Strings are double-quoted and escaped. + return quote("" + value); + } + // Recursively serialize objects and arrays. + if (typeof value == "object") { + // Check for cyclic structures. This is a linear search; performance + // is inversely proportional to the number of unique nested objects. + for (length = stack.length; length--;) { + if (stack[length] === value) { + // Cyclic structures cannot be serialized by `JSON.stringify`. + throw TypeError(); + } + } + // Add the object to the stack of traversed objects. + stack.push(value); + results = []; + // Save the current indentation level and indent one additional level. + prefix = indentation; + indentation += whitespace; + if (className == arrayClass) { + // Recursively serialize array elements. + for (index = 0, length = value.length; index < length; index++) { + element = serialize(index, value, callback, properties, whitespace, indentation, stack); + results.push(element === undef ? "null" : element); + } + result = results.length ? (whitespace ? "[\n" + indentation + results.join(",\n" + indentation) + "\n" + prefix + "]" : ("[" + results.join(",") + "]")) : "[]"; + } else { + // Recursively serialize object members. Members are selected from + // either a user-specified list of property names, or the object + // itself. + forEach(properties || value, function (property) { + var element = serialize(property, value, callback, properties, whitespace, indentation, stack); + if (element !== undef) { + // According to ES 5.1 section 15.12.3: "If `gap` {whitespace} + // is not the empty string, let `member` {quote(property) + ":"} + // be the concatenation of `member` and the `space` character." + // The "`space` character" refers to the literal space + // character, not the `space` {width} argument provided to + // `JSON.stringify`. + results.push(quote(property) + ":" + (whitespace ? " " : "") + element); + } + }); + result = results.length ? (whitespace ? "{\n" + indentation + results.join(",\n" + indentation) + "\n" + prefix + "}" : ("{" + results.join(",") + "}")) : "{}"; + } + // Remove the object from the traversed object stack. + stack.pop(); + return result; + } + }; + + // Public: `JSON.stringify`. See ES 5.1 section 15.12.3. + exports.stringify = function (source, filter, width) { + var whitespace, callback, properties, className; + if (objectTypes[typeof filter] && filter) { + if ((className = getClass.call(filter)) == functionClass) { + callback = filter; + } else if (className == arrayClass) { + // Convert the property names array into a makeshift set. + properties = {}; + for (var index = 0, length = filter.length, value; index < length; value = filter[index++], ((className = getClass.call(value)), className == stringClass || className == numberClass) && (properties[value] = 1)); + } + } + if (width) { + if ((className = getClass.call(width)) == numberClass) { + // Convert the `width` to an integer and create a string containing + // `width` number of space characters. + if ((width -= width % 1) > 0) { + for (whitespace = "", width > 10 && (width = 10); whitespace.length < width; whitespace += " "); + } + } else if (className == stringClass) { + whitespace = width.length <= 10 ? width : width.slice(0, 10); + } + } + // Opera <= 7.54u2 discards the values associated with empty string keys + // (`""`) only if they are used directly within an object member list + // (e.g., `!("" in { "": 1})`). + return serialize("", (value = {}, value[""] = source, value), callback, properties, whitespace, "", []); + }; + } + + // Public: Parses a JSON source string. + if (!has("json-parse")) { + var fromCharCode = String.fromCharCode; + + // Internal: A map of escaped control characters and their unescaped + // equivalents. + var Unescapes = { + 92: "\\", + 34: '"', + 47: "/", + 98: "\b", + 116: "\t", + 110: "\n", + 102: "\f", + 114: "\r" + }; + + // Internal: Stores the parser state. + var Index, Source; + + // Internal: Resets the parser state and throws a `SyntaxError`. + var abort = function () { + Index = Source = null; + throw SyntaxError(); + }; + + // Internal: Returns the next token, or `"$"` if the parser has reached + // the end of the source string. A token may be a string, number, `null` + // literal, or Boolean literal. + var lex = function () { + var source = Source, length = source.length, value, begin, position, isSigned, charCode; + while (Index < length) { + charCode = source.charCodeAt(Index); + switch (charCode) { + case 9: case 10: case 13: case 32: + // Skip whitespace tokens, including tabs, carriage returns, line + // feeds, and space characters. + Index++; + break; + case 123: case 125: case 91: case 93: case 58: case 44: + // Parse a punctuator token (`{`, `}`, `[`, `]`, `:`, or `,`) at + // the current position. + value = charIndexBuggy ? source.charAt(Index) : source[Index]; + Index++; + return value; + case 34: + // `"` delimits a JSON string; advance to the next character and + // begin parsing the string. String tokens are prefixed with the + // sentinel `@` character to distinguish them from punctuators and + // end-of-string tokens. + for (value = "@", Index++; Index < length;) { + charCode = source.charCodeAt(Index); + if (charCode < 32) { + // Unescaped ASCII control characters (those with a code unit + // less than the space character) are not permitted. + abort(); + } else if (charCode == 92) { + // A reverse solidus (`\`) marks the beginning of an escaped + // control character (including `"`, `\`, and `/`) or Unicode + // escape sequence. + charCode = source.charCodeAt(++Index); + switch (charCode) { + case 92: case 34: case 47: case 98: case 116: case 110: case 102: case 114: + // Revive escaped control characters. + value += Unescapes[charCode]; + Index++; + break; + case 117: + // `\u` marks the beginning of a Unicode escape sequence. + // Advance to the first character and validate the + // four-digit code point. + begin = ++Index; + for (position = Index + 4; Index < position; Index++) { + charCode = source.charCodeAt(Index); + // A valid sequence comprises four hexdigits (case- + // insensitive) that form a single hexadecimal value. + if (!(charCode >= 48 && charCode <= 57 || charCode >= 97 && charCode <= 102 || charCode >= 65 && charCode <= 70)) { + // Invalid Unicode escape sequence. + abort(); + } + } + // Revive the escaped character. + value += fromCharCode("0x" + source.slice(begin, Index)); + break; + default: + // Invalid escape sequence. + abort(); + } + } else { + if (charCode == 34) { + // An unescaped double-quote character marks the end of the + // string. + break; + } + charCode = source.charCodeAt(Index); + begin = Index; + // Optimize for the common case where a string is valid. + while (charCode >= 32 && charCode != 92 && charCode != 34) { + charCode = source.charCodeAt(++Index); + } + // Append the string as-is. + value += source.slice(begin, Index); + } + } + if (source.charCodeAt(Index) == 34) { + // Advance to the next character and return the revived string. + Index++; + return value; + } + // Unterminated string. + abort(); + default: + // Parse numbers and literals. + begin = Index; + // Advance past the negative sign, if one is specified. + if (charCode == 45) { + isSigned = true; + charCode = source.charCodeAt(++Index); + } + // Parse an integer or floating-point value. + if (charCode >= 48 && charCode <= 57) { + // Leading zeroes are interpreted as octal literals. + if (charCode == 48 && ((charCode = source.charCodeAt(Index + 1)), charCode >= 48 && charCode <= 57)) { + // Illegal octal literal. + abort(); + } + isSigned = false; + // Parse the integer component. + for (; Index < length && ((charCode = source.charCodeAt(Index)), charCode >= 48 && charCode <= 57); Index++); + // Floats cannot contain a leading decimal point; however, this + // case is already accounted for by the parser. + if (source.charCodeAt(Index) == 46) { + position = ++Index; + // Parse the decimal component. + for (; position < length && ((charCode = source.charCodeAt(position)), charCode >= 48 && charCode <= 57); position++); + if (position == Index) { + // Illegal trailing decimal. + abort(); + } + Index = position; + } + // Parse exponents. The `e` denoting the exponent is + // case-insensitive. + charCode = source.charCodeAt(Index); + if (charCode == 101 || charCode == 69) { + charCode = source.charCodeAt(++Index); + // Skip past the sign following the exponent, if one is + // specified. + if (charCode == 43 || charCode == 45) { + Index++; + } + // Parse the exponential component. + for (position = Index; position < length && ((charCode = source.charCodeAt(position)), charCode >= 48 && charCode <= 57); position++); + if (position == Index) { + // Illegal empty exponent. + abort(); + } + Index = position; + } + // Coerce the parsed value to a JavaScript number. + return +source.slice(begin, Index); + } + // A negative sign may only precede numbers. + if (isSigned) { + abort(); + } + // `true`, `false`, and `null` literals. + if (source.slice(Index, Index + 4) == "true") { + Index += 4; + return true; + } else if (source.slice(Index, Index + 5) == "false") { + Index += 5; + return false; + } else if (source.slice(Index, Index + 4) == "null") { + Index += 4; + return null; + } + // Unrecognized token. + abort(); + } + } + // Return the sentinel `$` character if the parser has reached the end + // of the source string. + return "$"; + }; + + // Internal: Parses a JSON `value` token. + var get = function (value) { + var results, hasMembers; + if (value == "$") { + // Unexpected end of input. + abort(); + } + if (typeof value == "string") { + if ((charIndexBuggy ? value.charAt(0) : value[0]) == "@") { + // Remove the sentinel `@` character. + return value.slice(1); + } + // Parse object and array literals. + if (value == "[") { + // Parses a JSON array, returning a new JavaScript array. + results = []; + for (;; hasMembers || (hasMembers = true)) { + value = lex(); + // A closing square bracket marks the end of the array literal. + if (value == "]") { + break; + } + // If the array literal contains elements, the current token + // should be a comma separating the previous element from the + // next. + if (hasMembers) { + if (value == ",") { + value = lex(); + if (value == "]") { + // Unexpected trailing `,` in array literal. + abort(); + } + } else { + // A `,` must separate each array element. + abort(); + } + } + // Elisions and leading commas are not permitted. + if (value == ",") { + abort(); + } + results.push(get(value)); + } + return results; + } else if (value == "{") { + // Parses a JSON object, returning a new JavaScript object. + results = {}; + for (;; hasMembers || (hasMembers = true)) { + value = lex(); + // A closing curly brace marks the end of the object literal. + if (value == "}") { + break; + } + // If the object literal contains members, the current token + // should be a comma separator. + if (hasMembers) { + if (value == ",") { + value = lex(); + if (value == "}") { + // Unexpected trailing `,` in object literal. + abort(); + } + } else { + // A `,` must separate each object member. + abort(); + } + } + // Leading commas are not permitted, object property names must be + // double-quoted strings, and a `:` must separate each property + // name and value. + if (value == "," || typeof value != "string" || (charIndexBuggy ? value.charAt(0) : value[0]) != "@" || lex() != ":") { + abort(); + } + results[value.slice(1)] = get(lex()); + } + return results; + } + // Unexpected token encountered. + abort(); + } + return value; + }; + + // Internal: Updates a traversed object member. + var update = function (source, property, callback) { + var element = walk(source, property, callback); + if (element === undef) { + delete source[property]; + } else { + source[property] = element; + } + }; + + // Internal: Recursively traverses a parsed JSON object, invoking the + // `callback` function for each value. This is an implementation of the + // `Walk(holder, name)` operation defined in ES 5.1 section 15.12.2. + var walk = function (source, property, callback) { + var value = source[property], length; + if (typeof value == "object" && value) { + // `forEach` can't be used to traverse an array in Opera <= 8.54 + // because its `Object#hasOwnProperty` implementation returns `false` + // for array indices (e.g., `![1, 2, 3].hasOwnProperty("0")`). + if (getClass.call(value) == arrayClass) { + for (length = value.length; length--;) { + update(value, length, callback); + } + } else { + forEach(value, function (property) { + update(value, property, callback); + }); + } + } + return callback.call(source, property, value); + }; + + // Public: `JSON.parse`. See ES 5.1 section 15.12.2. + exports.parse = function (source, callback) { + var result, value; + Index = 0; + Source = "" + source; + result = get(lex()); + // If a JSON string contains multiple tokens, it is invalid. + if (lex() != "$") { + abort(); + } + // Reset the parser state. + Index = Source = null; + return callback && getClass.call(callback) == functionClass ? walk((value = {}, value[""] = result, value), "", callback) : result; + }; + } + } + + exports["runInContext"] = runInContext; + return exports; + } + + if (freeExports && !isLoader) { + // Export for CommonJS environments. + runInContext(root, freeExports); + } else { + // Export for web browsers and JavaScript engines. + var nativeJSON = root.JSON, + previousJSON = root["JSON3"], + isRestored = false; + + var JSON3 = runInContext(root, (root["JSON3"] = { + // Public: Restores the original value of the global `JSON` object and + // returns a reference to the `JSON3` object. + "noConflict": function () { + if (!isRestored) { + isRestored = true; + root.JSON = nativeJSON; + root["JSON3"] = previousJSON; + nativeJSON = previousJSON = null; + } + return JSON3; + } + })); + + root.JSON = { + "parse": JSON3.parse, + "stringify": JSON3.stringify + }; + } + + // Export for asynchronous module loaders. + if (isLoader) { + define(function () { + return JSON3; + }); + } + }).call(this); + + + /* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(8)(module), (function() { return this; }()))) + +/***/ }, +/* 8 */ +/***/ function(module, exports) { + + module.exports = function(module) { + if(!module.webpackPolyfill) { + module.deprecate = function() {}; + module.paths = []; + // module.parent = undefined by default + module.children = []; + module.webpackPolyfill = 1; + } + return module; + } + + +/***/ }, +/* 9 */ +/***/ function(module, exports) { + + module.exports = Array.isArray || function (arr) { + return Object.prototype.toString.call(arr) == '[object Array]'; + }; + + +/***/ }, +/* 10 */ +/***/ function(module, exports) { + + + /** + * Expose `Emitter`. + */ + + module.exports = Emitter; + + /** + * Initialize a new `Emitter`. + * + * @api public + */ + + function Emitter(obj) { + if (obj) return mixin(obj); + }; + + /** + * Mixin the emitter properties. + * + * @param {Object} obj + * @return {Object} + * @api private + */ + + function mixin(obj) { + for (var key in Emitter.prototype) { + obj[key] = Emitter.prototype[key]; + } + return obj; + } + + /** + * Listen on the given `event` with `fn`. + * + * @param {String} event + * @param {Function} fn + * @return {Emitter} + * @api public + */ + + Emitter.prototype.on = + Emitter.prototype.addEventListener = function(event, fn){ + this._callbacks = this._callbacks || {}; + (this._callbacks[event] = this._callbacks[event] || []) + .push(fn); + return this; + }; + + /** + * Adds an `event` listener that will be invoked a single + * time then automatically removed. + * + * @param {String} event + * @param {Function} fn + * @return {Emitter} + * @api public + */ + + Emitter.prototype.once = function(event, fn){ + var self = this; + this._callbacks = this._callbacks || {}; + + function on() { + self.off(event, on); + fn.apply(this, arguments); + } + + on.fn = fn; + this.on(event, on); + return this; + }; + + /** + * Remove the given callback for `event` or all + * registered callbacks. + * + * @param {String} event + * @param {Function} fn + * @return {Emitter} + * @api public + */ + + Emitter.prototype.off = + Emitter.prototype.removeListener = + Emitter.prototype.removeAllListeners = + Emitter.prototype.removeEventListener = function(event, fn){ + this._callbacks = this._callbacks || {}; + + // all + if (0 == arguments.length) { + this._callbacks = {}; + return this; + } + + // specific event + var callbacks = this._callbacks[event]; + if (!callbacks) return this; + + // remove all handlers + if (1 == arguments.length) { + delete this._callbacks[event]; + return this; + } + + // remove specific handler + var cb; + for (var i = 0; i < callbacks.length; i++) { + cb = callbacks[i]; + if (cb === fn || cb.fn === fn) { + callbacks.splice(i, 1); + break; + } + } + return this; + }; + + /** + * Emit `event` with the given args. + * + * @param {String} event + * @param {Mixed} ... + * @return {Emitter} + */ + + Emitter.prototype.emit = function(event){ + this._callbacks = this._callbacks || {}; + var args = [].slice.call(arguments, 1) + , callbacks = this._callbacks[event]; + + if (callbacks) { + callbacks = callbacks.slice(0); + for (var i = 0, len = callbacks.length; i < len; ++i) { + callbacks[i].apply(this, args); + } + } + + return this; + }; + + /** + * Return array of callbacks for `event`. + * + * @param {String} event + * @return {Array} + * @api public + */ + + Emitter.prototype.listeners = function(event){ + this._callbacks = this._callbacks || {}; + return this._callbacks[event] || []; + }; + + /** + * Check if this emitter has `event` handlers. + * + * @param {String} event + * @return {Boolean} + * @api public + */ + + Emitter.prototype.hasListeners = function(event){ + return !! this.listeners(event).length; + }; + + +/***/ }, +/* 11 */ +/***/ function(module, exports, __webpack_require__) { + + /* WEBPACK VAR INJECTION */(function(global) {/*global Blob,File*/ + + /** + * Module requirements + */ + + var isArray = __webpack_require__(9); + var isBuf = __webpack_require__(12); + + /** + * Replaces every Buffer | ArrayBuffer in packet with a numbered placeholder. + * Anything with blobs or files should be fed through removeBlobs before coming + * here. + * + * @param {Object} packet - socket.io event packet + * @return {Object} with deconstructed packet and list of buffers + * @api public + */ + + exports.deconstructPacket = function(packet){ + var buffers = []; + var packetData = packet.data; + + function _deconstructPacket(data) { + if (!data) return data; + + if (isBuf(data)) { + var placeholder = { _placeholder: true, num: buffers.length }; + buffers.push(data); + return placeholder; + } else if (isArray(data)) { + var newData = new Array(data.length); + for (var i = 0; i < data.length; i++) { + newData[i] = _deconstructPacket(data[i]); + } + return newData; + } else if ('object' == typeof data && !(data instanceof Date)) { + var newData = {}; + for (var key in data) { + newData[key] = _deconstructPacket(data[key]); + } + return newData; + } + return data; + } + + var pack = packet; + pack.data = _deconstructPacket(packetData); + pack.attachments = buffers.length; // number of binary 'attachments' + return {packet: pack, buffers: buffers}; + }; + + /** + * Reconstructs a binary packet from its placeholder packet and buffers + * + * @param {Object} packet - event packet with placeholders + * @param {Array} buffers - binary buffers to put in placeholder positions + * @return {Object} reconstructed packet + * @api public + */ + + exports.reconstructPacket = function(packet, buffers) { + var curPlaceHolder = 0; + + function _reconstructPacket(data) { + if (data && data._placeholder) { + var buf = buffers[data.num]; // appropriate buffer (should be natural order anyway) + return buf; + } else if (isArray(data)) { + for (var i = 0; i < data.length; i++) { + data[i] = _reconstructPacket(data[i]); + } + return data; + } else if (data && 'object' == typeof data) { + for (var key in data) { + data[key] = _reconstructPacket(data[key]); + } + return data; + } + return data; + } + + packet.data = _reconstructPacket(packet.data); + packet.attachments = undefined; // no longer useful + return packet; + }; + + /** + * Asynchronously removes Blobs or Files from data via + * FileReader's readAsArrayBuffer method. Used before encoding + * data as msgpack. Calls callback with the blobless data. + * + * @param {Object} data + * @param {Function} callback + * @api private + */ + + exports.removeBlobs = function(data, callback) { + function _removeBlobs(obj, curKey, containingObject) { + if (!obj) return obj; + + // convert any blob + if ((global.Blob && obj instanceof Blob) || + (global.File && obj instanceof File)) { + pendingBlobs++; + + // async filereader + var fileReader = new FileReader(); + fileReader.onload = function() { // this.result == arraybuffer + if (containingObject) { + containingObject[curKey] = this.result; + } + else { + bloblessData = this.result; + } + + // if nothing pending its callback time + if(! --pendingBlobs) { + callback(bloblessData); + } + }; + + fileReader.readAsArrayBuffer(obj); // blob -> arraybuffer + } else if (isArray(obj)) { // handle array + for (var i = 0; i < obj.length; i++) { + _removeBlobs(obj[i], i, obj); + } + } else if (obj && 'object' == typeof obj && !isBuf(obj)) { // and object + for (var key in obj) { + _removeBlobs(obj[key], key, obj); + } + } + } + + var pendingBlobs = 0; + var bloblessData = data; + _removeBlobs(bloblessData); + if (!pendingBlobs) { + callback(bloblessData); + } + }; + + /* WEBPACK VAR INJECTION */}.call(exports, (function() { return this; }()))) + +/***/ }, +/* 12 */ +/***/ function(module, exports) { + + /* WEBPACK VAR INJECTION */(function(global) { + module.exports = isBuf; + + /** + * Returns true if obj is a buffer or an arraybuffer. + * + * @api private + */ + + function isBuf(obj) { + return (global.Buffer && global.Buffer.isBuffer(obj)) || + (global.ArrayBuffer && obj instanceof ArrayBuffer); + } + + /* WEBPACK VAR INJECTION */}.call(exports, (function() { return this; }()))) + +/***/ }, +/* 13 */ +/***/ function(module, exports, __webpack_require__) { + + 'use strict'; + + var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; + + /** + * Module dependencies. + */ + + var eio = __webpack_require__(14); + var Socket = __webpack_require__(40); + var Emitter = __webpack_require__(41); + var parser = __webpack_require__(6); + var on = __webpack_require__(43); + var bind = __webpack_require__(44); + var debug = __webpack_require__(3)('socket.io-client:manager'); + var indexOf = __webpack_require__(38); + var Backoff = __webpack_require__(46); + + /** + * IE6+ hasOwnProperty + */ + + var has = Object.prototype.hasOwnProperty; + + /** + * Module exports + */ + + module.exports = Manager; + + /** + * `Manager` constructor. + * + * @param {String} engine instance or engine uri/opts + * @param {Object} options + * @api public + */ + + function Manager(uri, opts) { + if (!(this instanceof Manager)) return new Manager(uri, opts); + if (uri && 'object' === (typeof uri === 'undefined' ? 'undefined' : _typeof(uri))) { + opts = uri; + uri = undefined; + } + opts = opts || {}; + + opts.path = opts.path || '/socket.io'; + this.nsps = {}; + this.subs = []; + this.opts = opts; + this.reconnection(opts.reconnection !== false); + this.reconnectionAttempts(opts.reconnectionAttempts || Infinity); + this.reconnectionDelay(opts.reconnectionDelay || 1000); + this.reconnectionDelayMax(opts.reconnectionDelayMax || 5000); + this.randomizationFactor(opts.randomizationFactor || 0.5); + this.backoff = new Backoff({ + min: this.reconnectionDelay(), + max: this.reconnectionDelayMax(), + jitter: this.randomizationFactor() + }); + this.timeout(null == opts.timeout ? 20000 : opts.timeout); + this.readyState = 'closed'; + this.uri = uri; + this.connecting = []; + this.lastPing = null; + this.encoding = false; + this.packetBuffer = []; + this.encoder = new parser.Encoder(); + this.decoder = new parser.Decoder(); + this.autoConnect = opts.autoConnect !== false; + if (this.autoConnect) this.open(); + } + + /** + * Propagate given event to sockets and emit on `this` + * + * @api private + */ + + Manager.prototype.emitAll = function () { + this.emit.apply(this, arguments); + for (var nsp in this.nsps) { + if (has.call(this.nsps, nsp)) { + this.nsps[nsp].emit.apply(this.nsps[nsp], arguments); + } + } + }; + + /** + * Update `socket.id` of all sockets + * + * @api private + */ + + Manager.prototype.updateSocketIds = function () { + for (var nsp in this.nsps) { + if (has.call(this.nsps, nsp)) { + this.nsps[nsp].id = this.engine.id; + } + } + }; + + /** + * Mix in `Emitter`. + */ + + Emitter(Manager.prototype); + + /** + * Sets the `reconnection` config. + * + * @param {Boolean} true/false if it should automatically reconnect + * @return {Manager} self or value + * @api public + */ + + Manager.prototype.reconnection = function (v) { + if (!arguments.length) return this._reconnection; + this._reconnection = !!v; + return this; + }; + + /** + * Sets the reconnection attempts config. + * + * @param {Number} max reconnection attempts before giving up + * @return {Manager} self or value + * @api public + */ + + Manager.prototype.reconnectionAttempts = function (v) { + if (!arguments.length) return this._reconnectionAttempts; + this._reconnectionAttempts = v; + return this; + }; + + /** + * Sets the delay between reconnections. + * + * @param {Number} delay + * @return {Manager} self or value + * @api public + */ + + Manager.prototype.reconnectionDelay = function (v) { + if (!arguments.length) return this._reconnectionDelay; + this._reconnectionDelay = v; + this.backoff && this.backoff.setMin(v); + return this; + }; + + Manager.prototype.randomizationFactor = function (v) { + if (!arguments.length) return this._randomizationFactor; + this._randomizationFactor = v; + this.backoff && this.backoff.setJitter(v); + return this; + }; + + /** + * Sets the maximum delay between reconnections. + * + * @param {Number} delay + * @return {Manager} self or value + * @api public + */ + + Manager.prototype.reconnectionDelayMax = function (v) { + if (!arguments.length) return this._reconnectionDelayMax; + this._reconnectionDelayMax = v; + this.backoff && this.backoff.setMax(v); + return this; + }; + + /** + * Sets the connection timeout. `false` to disable + * + * @return {Manager} self or value + * @api public + */ + + Manager.prototype.timeout = function (v) { + if (!arguments.length) return this._timeout; + this._timeout = v; + return this; + }; + + /** + * Starts trying to reconnect if reconnection is enabled and we have not + * started reconnecting yet + * + * @api private + */ + + Manager.prototype.maybeReconnectOnOpen = function () { + // Only try to reconnect if it's the first time we're connecting + if (!this.reconnecting && this._reconnection && this.backoff.attempts === 0) { + // keeps reconnection from firing twice for the same reconnection loop + this.reconnect(); + } + }; + + /** + * Sets the current transport `socket`. + * + * @param {Function} optional, callback + * @return {Manager} self + * @api public + */ + + Manager.prototype.open = Manager.prototype.connect = function (fn, opts) { + debug('readyState %s', this.readyState); + if (~this.readyState.indexOf('open')) return this; + + debug('opening %s', this.uri); + this.engine = eio(this.uri, this.opts); + var socket = this.engine; + var self = this; + this.readyState = 'opening'; + this.skipReconnect = false; + + // emit `open` + var openSub = on(socket, 'open', function () { + self.onopen(); + fn && fn(); + }); + + // emit `connect_error` + var errorSub = on(socket, 'error', function (data) { + debug('connect_error'); + self.cleanup(); + self.readyState = 'closed'; + self.emitAll('connect_error', data); + if (fn) { + var err = new Error('Connection error'); + err.data = data; + fn(err); + } else { + // Only do this if there is no fn to handle the error + self.maybeReconnectOnOpen(); + } + }); + + // emit `connect_timeout` + if (false !== this._timeout) { + var timeout = this._timeout; + debug('connect attempt will timeout after %d', timeout); + + // set timer + var timer = setTimeout(function () { + debug('connect attempt timed out after %d', timeout); + openSub.destroy(); + socket.close(); + socket.emit('error', 'timeout'); + self.emitAll('connect_timeout', timeout); + }, timeout); + + this.subs.push({ + destroy: function destroy() { + clearTimeout(timer); + } + }); + } + + this.subs.push(openSub); + this.subs.push(errorSub); + + return this; + }; + + /** + * Called upon transport open. + * + * @api private + */ + + Manager.prototype.onopen = function () { + debug('open'); + + // clear old subs + this.cleanup(); + + // mark as open + this.readyState = 'open'; + this.emit('open'); + + // add new subs + var socket = this.engine; + this.subs.push(on(socket, 'data', bind(this, 'ondata'))); + this.subs.push(on(socket, 'ping', bind(this, 'onping'))); + this.subs.push(on(socket, 'pong', bind(this, 'onpong'))); + this.subs.push(on(socket, 'error', bind(this, 'onerror'))); + this.subs.push(on(socket, 'close', bind(this, 'onclose'))); + this.subs.push(on(this.decoder, 'decoded', bind(this, 'ondecoded'))); + }; + + /** + * Called upon a ping. + * + * @api private + */ + + Manager.prototype.onping = function () { + this.lastPing = new Date(); + this.emitAll('ping'); + }; + + /** + * Called upon a packet. + * + * @api private + */ + + Manager.prototype.onpong = function () { + this.emitAll('pong', new Date() - this.lastPing); + }; + + /** + * Called with data. + * + * @api private + */ + + Manager.prototype.ondata = function (data) { + this.decoder.add(data); + }; + + /** + * Called when parser fully decodes a packet. + * + * @api private + */ + + Manager.prototype.ondecoded = function (packet) { + this.emit('packet', packet); + }; + + /** + * Called upon socket error. + * + * @api private + */ + + Manager.prototype.onerror = function (err) { + debug('error', err); + this.emitAll('error', err); + }; + + /** + * Creates a new socket for the given `nsp`. + * + * @return {Socket} + * @api public + */ + + Manager.prototype.socket = function (nsp, opts) { + var socket = this.nsps[nsp]; + if (!socket) { + socket = new Socket(this, nsp, opts); + this.nsps[nsp] = socket; + var self = this; + socket.on('connecting', onConnecting); + socket.on('connect', function () { + socket.id = self.engine.id; + }); + + if (this.autoConnect) { + // manually call here since connecting evnet is fired before listening + onConnecting(); + } + } + + function onConnecting() { + if (!~indexOf(self.connecting, socket)) { + self.connecting.push(socket); + } + } + + return socket; + }; + + /** + * Called upon a socket close. + * + * @param {Socket} socket + */ + + Manager.prototype.destroy = function (socket) { + var index = indexOf(this.connecting, socket); + if (~index) this.connecting.splice(index, 1); + if (this.connecting.length) return; + + this.close(); + }; + + /** + * Writes a packet. + * + * @param {Object} packet + * @api private + */ + + Manager.prototype.packet = function (packet) { + debug('writing packet %j', packet); + var self = this; + if (packet.query && packet.type === 0) packet.nsp += '?' + packet.query; + + if (!self.encoding) { + // encode, then write to engine with result + self.encoding = true; + this.encoder.encode(packet, function (encodedPackets) { + for (var i = 0; i < encodedPackets.length; i++) { + self.engine.write(encodedPackets[i], packet.options); + } + self.encoding = false; + self.processPacketQueue(); + }); + } else { + // add packet to the queue + self.packetBuffer.push(packet); + } + }; + + /** + * If packet buffer is non-empty, begins encoding the + * next packet in line. + * + * @api private + */ + + Manager.prototype.processPacketQueue = function () { + if (this.packetBuffer.length > 0 && !this.encoding) { + var pack = this.packetBuffer.shift(); + this.packet(pack); + } + }; + + /** + * Clean up transport subscriptions and packet buffer. + * + * @api private + */ + + Manager.prototype.cleanup = function () { + debug('cleanup'); + + var subsLength = this.subs.length; + for (var i = 0; i < subsLength; i++) { + var sub = this.subs.shift(); + sub.destroy(); + } + + this.packetBuffer = []; + this.encoding = false; + this.lastPing = null; + + this.decoder.destroy(); + }; + + /** + * Close the current socket. + * + * @api private + */ + + Manager.prototype.close = Manager.prototype.disconnect = function () { + debug('disconnect'); + this.skipReconnect = true; + this.reconnecting = false; + if ('opening' === this.readyState) { + // `onclose` will not fire because + // an open event never happened + this.cleanup(); + } + this.backoff.reset(); + this.readyState = 'closed'; + if (this.engine) this.engine.close(); + }; + + /** + * Called upon engine close. + * + * @api private + */ + + Manager.prototype.onclose = function (reason) { + debug('onclose'); + + this.cleanup(); + this.backoff.reset(); + this.readyState = 'closed'; + this.emit('close', reason); + + if (this._reconnection && !this.skipReconnect) { + this.reconnect(); + } + }; + + /** + * Attempt a reconnection. + * + * @api private + */ + + Manager.prototype.reconnect = function () { + if (this.reconnecting || this.skipReconnect) return this; + + var self = this; + + if (this.backoff.attempts >= this._reconnectionAttempts) { + debug('reconnect failed'); + this.backoff.reset(); + this.emitAll('reconnect_failed'); + this.reconnecting = false; + } else { + var delay = this.backoff.duration(); + debug('will wait %dms before reconnect attempt', delay); + + this.reconnecting = true; + var timer = setTimeout(function () { + if (self.skipReconnect) return; + + debug('attempting reconnect'); + self.emitAll('reconnect_attempt', self.backoff.attempts); + self.emitAll('reconnecting', self.backoff.attempts); + + // check again for the case socket closed in above events + if (self.skipReconnect) return; + + self.open(function (err) { + if (err) { + debug('reconnect attempt error'); + self.reconnecting = false; + self.reconnect(); + self.emitAll('reconnect_error', err.data); + } else { + debug('reconnect success'); + self.onreconnect(); + } + }); + }, delay); + + this.subs.push({ + destroy: function destroy() { + clearTimeout(timer); + } + }); + } + }; + + /** + * Called upon successful reconnect. + * + * @api private + */ + + Manager.prototype.onreconnect = function () { + var attempt = this.backoff.attempts; + this.reconnecting = false; + this.backoff.reset(); + this.updateSocketIds(); + this.emitAll('reconnect', attempt); + }; + +/***/ }, +/* 14 */ +/***/ function(module, exports, __webpack_require__) { + + + module.exports = __webpack_require__(15); + + +/***/ }, +/* 15 */ +/***/ function(module, exports, __webpack_require__) { + + + module.exports = __webpack_require__(16); + + /** + * Exports parser + * + * @api public + * + */ + module.exports.parser = __webpack_require__(23); + + +/***/ }, +/* 16 */ +/***/ function(module, exports, __webpack_require__) { + + /* WEBPACK VAR INJECTION */(function(global) {/** + * Module dependencies. + */ + + var transports = __webpack_require__(17); + var Emitter = __webpack_require__(31); + var debug = __webpack_require__(3)('engine.io-client:socket'); + var index = __webpack_require__(38); + var parser = __webpack_require__(23); + var parseuri = __webpack_require__(2); + var parsejson = __webpack_require__(39); + var parseqs = __webpack_require__(32); + + /** + * Module exports. + */ + + module.exports = Socket; + + /** + * Socket constructor. + * + * @param {String|Object} uri or options + * @param {Object} options + * @api public + */ + + function Socket (uri, opts) { + if (!(this instanceof Socket)) return new Socket(uri, opts); + + opts = opts || {}; + + if (uri && 'object' === typeof uri) { + opts = uri; + uri = null; + } + + if (uri) { + uri = parseuri(uri); + opts.hostname = uri.host; + opts.secure = uri.protocol === 'https' || uri.protocol === 'wss'; + opts.port = uri.port; + if (uri.query) opts.query = uri.query; + } else if (opts.host) { + opts.hostname = parseuri(opts.host).host; + } + + this.secure = null != opts.secure ? opts.secure + : (global.location && 'https:' === location.protocol); + + if (opts.hostname && !opts.port) { + // if no port is specified manually, use the protocol default + opts.port = this.secure ? '443' : '80'; + } + + this.agent = opts.agent || false; + this.hostname = opts.hostname || + (global.location ? location.hostname : 'localhost'); + this.port = opts.port || (global.location && location.port + ? location.port + : (this.secure ? 443 : 80)); + this.query = opts.query || {}; + if ('string' === typeof this.query) this.query = parseqs.decode(this.query); + this.upgrade = false !== opts.upgrade; + this.path = (opts.path || '/engine.io').replace(/\/$/, '') + '/'; + this.forceJSONP = !!opts.forceJSONP; + this.jsonp = false !== opts.jsonp; + this.forceBase64 = !!opts.forceBase64; + this.enablesXDR = !!opts.enablesXDR; + this.timestampParam = opts.timestampParam || 't'; + this.timestampRequests = opts.timestampRequests; + this.transports = opts.transports || ['polling', 'websocket']; + this.readyState = ''; + this.writeBuffer = []; + this.prevBufferLen = 0; + this.policyPort = opts.policyPort || 843; + this.rememberUpgrade = opts.rememberUpgrade || false; + this.binaryType = null; + this.onlyBinaryUpgrades = opts.onlyBinaryUpgrades; + this.perMessageDeflate = false !== opts.perMessageDeflate ? (opts.perMessageDeflate || {}) : false; + + if (true === this.perMessageDeflate) this.perMessageDeflate = {}; + if (this.perMessageDeflate && null == this.perMessageDeflate.threshold) { + this.perMessageDeflate.threshold = 1024; + } + + // SSL options for Node.js client + this.pfx = opts.pfx || null; + this.key = opts.key || null; + this.passphrase = opts.passphrase || null; + this.cert = opts.cert || null; + this.ca = opts.ca || null; + this.ciphers = opts.ciphers || null; + this.rejectUnauthorized = opts.rejectUnauthorized === undefined ? null : opts.rejectUnauthorized; + + // other options for Node.js client + var freeGlobal = typeof global === 'object' && global; + if (freeGlobal.global === freeGlobal) { + if (opts.extraHeaders && Object.keys(opts.extraHeaders).length > 0) { + this.extraHeaders = opts.extraHeaders; + } + } + + // set on handshake + this.id = null; + this.upgrades = null; + this.pingInterval = null; + this.pingTimeout = null; + + // set on heartbeat + this.pingIntervalTimer = null; + this.pingTimeoutTimer = null; + + this.open(); + } + + Socket.priorWebsocketSuccess = false; + + /** + * Mix in `Emitter`. + */ + + Emitter(Socket.prototype); + + /** + * Protocol version. + * + * @api public + */ + + Socket.protocol = parser.protocol; // this is an int + + /** + * Expose deps for legacy compatibility + * and standalone browser access. + */ + + Socket.Socket = Socket; + Socket.Transport = __webpack_require__(22); + Socket.transports = __webpack_require__(17); + Socket.parser = __webpack_require__(23); + + /** + * Creates transport of the given type. + * + * @param {String} transport name + * @return {Transport} + * @api private + */ + + Socket.prototype.createTransport = function (name) { + debug('creating transport "%s"', name); + var query = clone(this.query); + + // append engine.io protocol identifier + query.EIO = parser.protocol; + + // transport name + query.transport = name; + + // session id if we already have one + if (this.id) query.sid = this.id; + + var transport = new transports[name]({ + agent: this.agent, + hostname: this.hostname, + port: this.port, + secure: this.secure, + path: this.path, + query: query, + forceJSONP: this.forceJSONP, + jsonp: this.jsonp, + forceBase64: this.forceBase64, + enablesXDR: this.enablesXDR, + timestampRequests: this.timestampRequests, + timestampParam: this.timestampParam, + policyPort: this.policyPort, + socket: this, + pfx: this.pfx, + key: this.key, + passphrase: this.passphrase, + cert: this.cert, + ca: this.ca, + ciphers: this.ciphers, + rejectUnauthorized: this.rejectUnauthorized, + perMessageDeflate: this.perMessageDeflate, + extraHeaders: this.extraHeaders + }); + + return transport; + }; + + function clone (obj) { + var o = {}; + for (var i in obj) { + if (obj.hasOwnProperty(i)) { + o[i] = obj[i]; + } + } + return o; + } + + /** + * Initializes transport to use and starts probe. + * + * @api private + */ + Socket.prototype.open = function () { + var transport; + if (this.rememberUpgrade && Socket.priorWebsocketSuccess && this.transports.indexOf('websocket') !== -1) { + transport = 'websocket'; + } else if (0 === this.transports.length) { + // Emit error on next tick so it can be listened to + var self = this; + setTimeout(function () { + self.emit('error', 'No transports available'); + }, 0); + return; + } else { + transport = this.transports[0]; + } + this.readyState = 'opening'; + + // Retry with the next transport if the transport is disabled (jsonp: false) + try { + transport = this.createTransport(transport); + } catch (e) { + this.transports.shift(); + this.open(); + return; + } + + transport.open(); + this.setTransport(transport); + }; + + /** + * Sets the current transport. Disables the existing one (if any). + * + * @api private + */ + + Socket.prototype.setTransport = function (transport) { + debug('setting transport %s', transport.name); + var self = this; + + if (this.transport) { + debug('clearing existing transport %s', this.transport.name); + this.transport.removeAllListeners(); + } + + // set up transport + this.transport = transport; + + // set up transport listeners + transport + .on('drain', function () { + self.onDrain(); + }) + .on('packet', function (packet) { + self.onPacket(packet); + }) + .on('error', function (e) { + self.onError(e); + }) + .on('close', function () { + self.onClose('transport close'); + }); + }; + + /** + * Probes a transport. + * + * @param {String} transport name + * @api private + */ + + Socket.prototype.probe = function (name) { + debug('probing transport "%s"', name); + var transport = this.createTransport(name, { probe: 1 }); + var failed = false; + var self = this; + + Socket.priorWebsocketSuccess = false; + + function onTransportOpen () { + if (self.onlyBinaryUpgrades) { + var upgradeLosesBinary = !this.supportsBinary && self.transport.supportsBinary; + failed = failed || upgradeLosesBinary; + } + if (failed) return; + + debug('probe transport "%s" opened', name); + transport.send([{ type: 'ping', data: 'probe' }]); + transport.once('packet', function (msg) { + if (failed) return; + if ('pong' === msg.type && 'probe' === msg.data) { + debug('probe transport "%s" pong', name); + self.upgrading = true; + self.emit('upgrading', transport); + if (!transport) return; + Socket.priorWebsocketSuccess = 'websocket' === transport.name; + + debug('pausing current transport "%s"', self.transport.name); + self.transport.pause(function () { + if (failed) return; + if ('closed' === self.readyState) return; + debug('changing transport and sending upgrade packet'); + + cleanup(); + + self.setTransport(transport); + transport.send([{ type: 'upgrade' }]); + self.emit('upgrade', transport); + transport = null; + self.upgrading = false; + self.flush(); + }); + } else { + debug('probe transport "%s" failed', name); + var err = new Error('probe error'); + err.transport = transport.name; + self.emit('upgradeError', err); + } + }); + } + + function freezeTransport () { + if (failed) return; + + // Any callback called by transport should be ignored since now + failed = true; + + cleanup(); + + transport.close(); + transport = null; + } + + // Handle any error that happens while probing + function onerror (err) { + var error = new Error('probe error: ' + err); + error.transport = transport.name; + + freezeTransport(); + + debug('probe transport "%s" failed because of error: %s', name, err); + + self.emit('upgradeError', error); + } + + function onTransportClose () { + onerror('transport closed'); + } + + // When the socket is closed while we're probing + function onclose () { + onerror('socket closed'); + } + + // When the socket is upgraded while we're probing + function onupgrade (to) { + if (transport && to.name !== transport.name) { + debug('"%s" works - aborting "%s"', to.name, transport.name); + freezeTransport(); + } + } + + // Remove all listeners on the transport and on self + function cleanup () { + transport.removeListener('open', onTransportOpen); + transport.removeListener('error', onerror); + transport.removeListener('close', onTransportClose); + self.removeListener('close', onclose); + self.removeListener('upgrading', onupgrade); + } + + transport.once('open', onTransportOpen); + transport.once('error', onerror); + transport.once('close', onTransportClose); + + this.once('close', onclose); + this.once('upgrading', onupgrade); + + transport.open(); + }; + + /** + * Called when connection is deemed open. + * + * @api public + */ + + Socket.prototype.onOpen = function () { + debug('socket open'); + this.readyState = 'open'; + Socket.priorWebsocketSuccess = 'websocket' === this.transport.name; + this.emit('open'); + this.flush(); + + // we check for `readyState` in case an `open` + // listener already closed the socket + if ('open' === this.readyState && this.upgrade && this.transport.pause) { + debug('starting upgrade probes'); + for (var i = 0, l = this.upgrades.length; i < l; i++) { + this.probe(this.upgrades[i]); + } + } + }; + + /** + * Handles a packet. + * + * @api private + */ + + Socket.prototype.onPacket = function (packet) { + if ('opening' === this.readyState || 'open' === this.readyState) { + debug('socket receive: type "%s", data "%s"', packet.type, packet.data); + + this.emit('packet', packet); + + // Socket is live - any packet counts + this.emit('heartbeat'); + + switch (packet.type) { + case 'open': + this.onHandshake(parsejson(packet.data)); + break; + + case 'pong': + this.setPing(); + this.emit('pong'); + break; + + case 'error': + var err = new Error('server error'); + err.code = packet.data; + this.onError(err); + break; + + case 'message': + this.emit('data', packet.data); + this.emit('message', packet.data); + break; + } + } else { + debug('packet received with socket readyState "%s"', this.readyState); + } + }; + + /** + * Called upon handshake completion. + * + * @param {Object} handshake obj + * @api private + */ + + Socket.prototype.onHandshake = function (data) { + this.emit('handshake', data); + this.id = data.sid; + this.transport.query.sid = data.sid; + this.upgrades = this.filterUpgrades(data.upgrades); + this.pingInterval = data.pingInterval; + this.pingTimeout = data.pingTimeout; + this.onOpen(); + // In case open handler closes socket + if ('closed' === this.readyState) return; + this.setPing(); + + // Prolong liveness of socket on heartbeat + this.removeListener('heartbeat', this.onHeartbeat); + this.on('heartbeat', this.onHeartbeat); + }; + + /** + * Resets ping timeout. + * + * @api private + */ + + Socket.prototype.onHeartbeat = function (timeout) { + clearTimeout(this.pingTimeoutTimer); + var self = this; + self.pingTimeoutTimer = setTimeout(function () { + if ('closed' === self.readyState) return; + self.onClose('ping timeout'); + }, timeout || (self.pingInterval + self.pingTimeout)); + }; + + /** + * Pings server every `this.pingInterval` and expects response + * within `this.pingTimeout` or closes connection. + * + * @api private + */ + + Socket.prototype.setPing = function () { + var self = this; + clearTimeout(self.pingIntervalTimer); + self.pingIntervalTimer = setTimeout(function () { + debug('writing ping packet - expecting pong within %sms', self.pingTimeout); + self.ping(); + self.onHeartbeat(self.pingTimeout); + }, self.pingInterval); + }; + + /** + * Sends a ping packet. + * + * @api private + */ + + Socket.prototype.ping = function () { + var self = this; + this.sendPacket('ping', function () { + self.emit('ping'); + }); + }; + + /** + * Called on `drain` event + * + * @api private + */ + + Socket.prototype.onDrain = function () { + this.writeBuffer.splice(0, this.prevBufferLen); + + // setting prevBufferLen = 0 is very important + // for example, when upgrading, upgrade packet is sent over, + // and a nonzero prevBufferLen could cause problems on `drain` + this.prevBufferLen = 0; + + if (0 === this.writeBuffer.length) { + this.emit('drain'); + } else { + this.flush(); + } + }; + + /** + * Flush write buffers. + * + * @api private + */ + + Socket.prototype.flush = function () { + if ('closed' !== this.readyState && this.transport.writable && + !this.upgrading && this.writeBuffer.length) { + debug('flushing %d packets in socket', this.writeBuffer.length); + this.transport.send(this.writeBuffer); + // keep track of current length of writeBuffer + // splice writeBuffer and callbackBuffer on `drain` + this.prevBufferLen = this.writeBuffer.length; + this.emit('flush'); + } + }; + + /** + * Sends a message. + * + * @param {String} message. + * @param {Function} callback function. + * @param {Object} options. + * @return {Socket} for chaining. + * @api public + */ + + Socket.prototype.write = + Socket.prototype.send = function (msg, options, fn) { + this.sendPacket('message', msg, options, fn); + return this; + }; + + /** + * Sends a packet. + * + * @param {String} packet type. + * @param {String} data. + * @param {Object} options. + * @param {Function} callback function. + * @api private + */ + + Socket.prototype.sendPacket = function (type, data, options, fn) { + if ('function' === typeof data) { + fn = data; + data = undefined; + } + + if ('function' === typeof options) { + fn = options; + options = null; + } + + if ('closing' === this.readyState || 'closed' === this.readyState) { + return; + } + + options = options || {}; + options.compress = false !== options.compress; + + var packet = { + type: type, + data: data, + options: options + }; + this.emit('packetCreate', packet); + this.writeBuffer.push(packet); + if (fn) this.once('flush', fn); + this.flush(); + }; + + /** + * Closes the connection. + * + * @api private + */ + + Socket.prototype.close = function () { + if ('opening' === this.readyState || 'open' === this.readyState) { + this.readyState = 'closing'; + + var self = this; + + if (this.writeBuffer.length) { + this.once('drain', function () { + if (this.upgrading) { + waitForUpgrade(); + } else { + close(); + } + }); + } else if (this.upgrading) { + waitForUpgrade(); + } else { + close(); + } + } + + function close () { + self.onClose('forced close'); + debug('socket closing - telling transport to close'); + self.transport.close(); + } + + function cleanupAndClose () { + self.removeListener('upgrade', cleanupAndClose); + self.removeListener('upgradeError', cleanupAndClose); + close(); + } + + function waitForUpgrade () { + // wait for upgrade to finish since we can't send packets while pausing a transport + self.once('upgrade', cleanupAndClose); + self.once('upgradeError', cleanupAndClose); + } + + return this; + }; + + /** + * Called upon transport error + * + * @api private + */ + + Socket.prototype.onError = function (err) { + debug('socket error %j', err); + Socket.priorWebsocketSuccess = false; + this.emit('error', err); + this.onClose('transport error', err); + }; + + /** + * Called upon transport close. + * + * @api private + */ + + Socket.prototype.onClose = function (reason, desc) { + if ('opening' === this.readyState || 'open' === this.readyState || 'closing' === this.readyState) { + debug('socket close with reason: "%s"', reason); + var self = this; + + // clear timers + clearTimeout(this.pingIntervalTimer); + clearTimeout(this.pingTimeoutTimer); + + // stop event from firing again for transport + this.transport.removeAllListeners('close'); + + // ensure transport won't stay open + this.transport.close(); + + // ignore further transport communication + this.transport.removeAllListeners(); + + // set ready state + this.readyState = 'closed'; + + // clear session id + this.id = null; + + // emit close event + this.emit('close', reason, desc); + + // clean buffers after, so users can still + // grab the buffers on `close` event + self.writeBuffer = []; + self.prevBufferLen = 0; + } + }; + + /** + * Filters upgrades, returning only those matching client transports. + * + * @param {Array} server upgrades + * @api private + * + */ + + Socket.prototype.filterUpgrades = function (upgrades) { + var filteredUpgrades = []; + for (var i = 0, j = upgrades.length; i < j; i++) { + if (~index(this.transports, upgrades[i])) filteredUpgrades.push(upgrades[i]); + } + return filteredUpgrades; + }; + + /* WEBPACK VAR INJECTION */}.call(exports, (function() { return this; }()))) + +/***/ }, +/* 17 */ +/***/ function(module, exports, __webpack_require__) { + + /* WEBPACK VAR INJECTION */(function(global) {/** + * Module dependencies + */ + + var XMLHttpRequest = __webpack_require__(18); + var XHR = __webpack_require__(20); + var JSONP = __webpack_require__(35); + var websocket = __webpack_require__(36); + + /** + * Export transports. + */ + + exports.polling = polling; + exports.websocket = websocket; + + /** + * Polling transport polymorphic constructor. + * Decides on xhr vs jsonp based on feature detection. + * + * @api private + */ + + function polling (opts) { + var xhr; + var xd = false; + var xs = false; + var jsonp = false !== opts.jsonp; + + if (global.location) { + var isSSL = 'https:' === location.protocol; + var port = location.port; + + // some user agents have empty `location.port` + if (!port) { + port = isSSL ? 443 : 80; + } + + xd = opts.hostname !== location.hostname || port !== opts.port; + xs = opts.secure !== isSSL; + } + + opts.xdomain = xd; + opts.xscheme = xs; + xhr = new XMLHttpRequest(opts); + + if ('open' in xhr && !opts.forceJSONP) { + return new XHR(opts); + } else { + if (!jsonp) throw new Error('JSONP disabled'); + return new JSONP(opts); + } + } + + /* WEBPACK VAR INJECTION */}.call(exports, (function() { return this; }()))) + +/***/ }, +/* 18 */ +/***/ function(module, exports, __webpack_require__) { + + // browser shim for xmlhttprequest module + + // Indicate to eslint that ActiveXObject is global + /* global ActiveXObject */ + + var hasCORS = __webpack_require__(19); + + module.exports = function (opts) { + var xdomain = opts.xdomain; + + // scheme must be same when usign XDomainRequest + // http://blogs.msdn.com/b/ieinternals/archive/2010/05/13/xdomainrequest-restrictions-limitations-and-workarounds.aspx + var xscheme = opts.xscheme; + + // XDomainRequest has a flow of not sending cookie, therefore it should be disabled as a default. + // https://github.com/Automattic/engine.io-client/pull/217 + var enablesXDR = opts.enablesXDR; + + // XMLHttpRequest can be disabled on IE + try { + if ('undefined' !== typeof XMLHttpRequest && (!xdomain || hasCORS)) { + return new XMLHttpRequest(); + } + } catch (e) { } + + // Use XDomainRequest for IE8 if enablesXDR is true + // because loading bar keeps flashing when using jsonp-polling + // https://github.com/yujiosaka/socke.io-ie8-loading-example + try { + if ('undefined' !== typeof XDomainRequest && !xscheme && enablesXDR) { + return new XDomainRequest(); + } + } catch (e) { } + + if (!xdomain) { + try { + return new ActiveXObject('Microsoft.XMLHTTP'); + } catch (e) { } + } + }; + + +/***/ }, +/* 19 */ +/***/ function(module, exports) { + + + /** + * Module exports. + * + * Logic borrowed from Modernizr: + * + * - https://github.com/Modernizr/Modernizr/blob/master/feature-detects/cors.js + */ + + try { + module.exports = typeof XMLHttpRequest !== 'undefined' && + 'withCredentials' in new XMLHttpRequest(); + } catch (err) { + // if XMLHttp support is disabled in IE then it will throw + // when trying to create + module.exports = false; + } + + +/***/ }, +/* 20 */ +/***/ function(module, exports, __webpack_require__) { + + /* WEBPACK VAR INJECTION */(function(global) {/** + * Module requirements. + */ + + var XMLHttpRequest = __webpack_require__(18); + var Polling = __webpack_require__(21); + var Emitter = __webpack_require__(31); + var inherit = __webpack_require__(33); + var debug = __webpack_require__(3)('engine.io-client:polling-xhr'); + + /** + * Module exports. + */ + + module.exports = XHR; + module.exports.Request = Request; + + /** + * Empty function + */ + + function empty () {} + + /** + * XHR Polling constructor. + * + * @param {Object} opts + * @api public + */ + + function XHR (opts) { + Polling.call(this, opts); + + if (global.location) { + var isSSL = 'https:' === location.protocol; + var port = location.port; + + // some user agents have empty `location.port` + if (!port) { + port = isSSL ? 443 : 80; + } + + this.xd = opts.hostname !== global.location.hostname || + port !== opts.port; + this.xs = opts.secure !== isSSL; + } else { + this.extraHeaders = opts.extraHeaders; + } + } + + /** + * Inherits from Polling. + */ + + inherit(XHR, Polling); + + /** + * XHR supports binary + */ + + XHR.prototype.supportsBinary = true; + + /** + * Creates a request. + * + * @param {String} method + * @api private + */ + + XHR.prototype.request = function (opts) { + opts = opts || {}; + opts.uri = this.uri(); + opts.xd = this.xd; + opts.xs = this.xs; + opts.agent = this.agent || false; + opts.supportsBinary = this.supportsBinary; + opts.enablesXDR = this.enablesXDR; + + // SSL options for Node.js client + opts.pfx = this.pfx; + opts.key = this.key; + opts.passphrase = this.passphrase; + opts.cert = this.cert; + opts.ca = this.ca; + opts.ciphers = this.ciphers; + opts.rejectUnauthorized = this.rejectUnauthorized; + + // other options for Node.js client + opts.extraHeaders = this.extraHeaders; + + return new Request(opts); + }; + + /** + * Sends data. + * + * @param {String} data to send. + * @param {Function} called upon flush. + * @api private + */ + + XHR.prototype.doWrite = function (data, fn) { + var isBinary = typeof data !== 'string' && data !== undefined; + var req = this.request({ method: 'POST', data: data, isBinary: isBinary }); + var self = this; + req.on('success', fn); + req.on('error', function (err) { + self.onError('xhr post error', err); + }); + this.sendXhr = req; + }; + + /** + * Starts a poll cycle. + * + * @api private + */ + + XHR.prototype.doPoll = function () { + debug('xhr poll'); + var req = this.request(); + var self = this; + req.on('data', function (data) { + self.onData(data); + }); + req.on('error', function (err) { + self.onError('xhr poll error', err); + }); + this.pollXhr = req; + }; + + /** + * Request constructor + * + * @param {Object} options + * @api public + */ + + function Request (opts) { + this.method = opts.method || 'GET'; + this.uri = opts.uri; + this.xd = !!opts.xd; + this.xs = !!opts.xs; + this.async = false !== opts.async; + this.data = undefined !== opts.data ? opts.data : null; + this.agent = opts.agent; + this.isBinary = opts.isBinary; + this.supportsBinary = opts.supportsBinary; + this.enablesXDR = opts.enablesXDR; + + // SSL options for Node.js client + this.pfx = opts.pfx; + this.key = opts.key; + this.passphrase = opts.passphrase; + this.cert = opts.cert; + this.ca = opts.ca; + this.ciphers = opts.ciphers; + this.rejectUnauthorized = opts.rejectUnauthorized; + + // other options for Node.js client + this.extraHeaders = opts.extraHeaders; + + this.create(); + } + + /** + * Mix in `Emitter`. + */ + + Emitter(Request.prototype); + + /** + * Creates the XHR object and sends the request. + * + * @api private + */ + + Request.prototype.create = function () { + var opts = { agent: this.agent, xdomain: this.xd, xscheme: this.xs, enablesXDR: this.enablesXDR }; + + // SSL options for Node.js client + opts.pfx = this.pfx; + opts.key = this.key; + opts.passphrase = this.passphrase; + opts.cert = this.cert; + opts.ca = this.ca; + opts.ciphers = this.ciphers; + opts.rejectUnauthorized = this.rejectUnauthorized; + + var xhr = this.xhr = new XMLHttpRequest(opts); + var self = this; + + try { + debug('xhr open %s: %s', this.method, this.uri); + xhr.open(this.method, this.uri, this.async); + try { + if (this.extraHeaders) { + xhr.setDisableHeaderCheck(true); + for (var i in this.extraHeaders) { + if (this.extraHeaders.hasOwnProperty(i)) { + xhr.setRequestHeader(i, this.extraHeaders[i]); + } + } + } + } catch (e) {} + if (this.supportsBinary) { + // This has to be done after open because Firefox is stupid + // http://stackoverflow.com/questions/13216903/get-binary-data-with-xmlhttprequest-in-a-firefox-extension + xhr.responseType = 'arraybuffer'; + } + + if ('POST' === this.method) { + try { + if (this.isBinary) { + xhr.setRequestHeader('Content-type', 'application/octet-stream'); + } else { + xhr.setRequestHeader('Content-type', 'text/plain;charset=UTF-8'); + } + } catch (e) {} + } + + // ie6 check + if ('withCredentials' in xhr) { + xhr.withCredentials = true; + } + + if (this.hasXDR()) { + xhr.onload = function () { + self.onLoad(); + }; + xhr.onerror = function () { + self.onError(xhr.responseText); + }; + } else { + xhr.onreadystatechange = function () { + if (4 !== xhr.readyState) return; + if (200 === xhr.status || 1223 === xhr.status) { + self.onLoad(); + } else { + // make sure the `error` event handler that's user-set + // does not throw in the same tick and gets caught here + setTimeout(function () { + self.onError(xhr.status); + }, 0); + } + }; + } + + debug('xhr data %s', this.data); + xhr.send(this.data); + } catch (e) { + // Need to defer since .create() is called directly fhrom the constructor + // and thus the 'error' event can only be only bound *after* this exception + // occurs. Therefore, also, we cannot throw here at all. + setTimeout(function () { + self.onError(e); + }, 0); + return; + } + + if (global.document) { + this.index = Request.requestsCount++; + Request.requests[this.index] = this; + } + }; + + /** + * Called upon successful response. + * + * @api private + */ + + Request.prototype.onSuccess = function () { + this.emit('success'); + this.cleanup(); + }; + + /** + * Called if we have data. + * + * @api private + */ + + Request.prototype.onData = function (data) { + this.emit('data', data); + this.onSuccess(); + }; + + /** + * Called upon error. + * + * @api private + */ + + Request.prototype.onError = function (err) { + this.emit('error', err); + this.cleanup(true); + }; + + /** + * Cleans up house. + * + * @api private + */ + + Request.prototype.cleanup = function (fromError) { + if ('undefined' === typeof this.xhr || null === this.xhr) { + return; + } + // xmlhttprequest + if (this.hasXDR()) { + this.xhr.onload = this.xhr.onerror = empty; + } else { + this.xhr.onreadystatechange = empty; + } + + if (fromError) { + try { + this.xhr.abort(); + } catch (e) {} + } + + if (global.document) { + delete Request.requests[this.index]; + } + + this.xhr = null; + }; + + /** + * Called upon load. + * + * @api private + */ + + Request.prototype.onLoad = function () { + var data; + try { + var contentType; + try { + contentType = this.xhr.getResponseHeader('Content-Type').split(';')[0]; + } catch (e) {} + if (contentType === 'application/octet-stream') { + data = this.xhr.response || this.xhr.responseText; + } else { + if (!this.supportsBinary) { + data = this.xhr.responseText; + } else { + try { + data = String.fromCharCode.apply(null, new Uint8Array(this.xhr.response)); + } catch (e) { + var ui8Arr = new Uint8Array(this.xhr.response); + var dataArray = []; + for (var idx = 0, length = ui8Arr.length; idx < length; idx++) { + dataArray.push(ui8Arr[idx]); + } + + data = String.fromCharCode.apply(null, dataArray); + } + } + } + } catch (e) { + this.onError(e); + } + if (null != data) { + this.onData(data); + } + }; + + /** + * Check if it has XDomainRequest. + * + * @api private + */ + + Request.prototype.hasXDR = function () { + return 'undefined' !== typeof global.XDomainRequest && !this.xs && this.enablesXDR; + }; + + /** + * Aborts the request. + * + * @api public + */ + + Request.prototype.abort = function () { + this.cleanup(); + }; + + /** + * Aborts pending requests when unloading the window. This is needed to prevent + * memory leaks (e.g. when using IE) and to ensure that no spurious error is + * emitted. + */ + + Request.requestsCount = 0; + Request.requests = {}; + + if (global.document) { + if (global.attachEvent) { + global.attachEvent('onunload', unloadHandler); + } else if (global.addEventListener) { + global.addEventListener('beforeunload', unloadHandler, false); + } + } + + function unloadHandler () { + for (var i in Request.requests) { + if (Request.requests.hasOwnProperty(i)) { + Request.requests[i].abort(); + } + } + } + + /* WEBPACK VAR INJECTION */}.call(exports, (function() { return this; }()))) + +/***/ }, +/* 21 */ +/***/ function(module, exports, __webpack_require__) { + + /** + * Module dependencies. + */ + + var Transport = __webpack_require__(22); + var parseqs = __webpack_require__(32); + var parser = __webpack_require__(23); + var inherit = __webpack_require__(33); + var yeast = __webpack_require__(34); + var debug = __webpack_require__(3)('engine.io-client:polling'); + + /** + * Module exports. + */ + + module.exports = Polling; + + /** + * Is XHR2 supported? + */ + + var hasXHR2 = (function () { + var XMLHttpRequest = __webpack_require__(18); + var xhr = new XMLHttpRequest({ xdomain: false }); + return null != xhr.responseType; + })(); + + /** + * Polling interface. + * + * @param {Object} opts + * @api private + */ + + function Polling (opts) { + var forceBase64 = (opts && opts.forceBase64); + if (!hasXHR2 || forceBase64) { + this.supportsBinary = false; + } + Transport.call(this, opts); + } + + /** + * Inherits from Transport. + */ + + inherit(Polling, Transport); + + /** + * Transport name. + */ + + Polling.prototype.name = 'polling'; + + /** + * Opens the socket (triggers polling). We write a PING message to determine + * when the transport is open. + * + * @api private + */ + + Polling.prototype.doOpen = function () { + this.poll(); + }; + + /** + * Pauses polling. + * + * @param {Function} callback upon buffers are flushed and transport is paused + * @api private + */ + + Polling.prototype.pause = function (onPause) { + var self = this; + + this.readyState = 'pausing'; + + function pause () { + debug('paused'); + self.readyState = 'paused'; + onPause(); + } + + if (this.polling || !this.writable) { + var total = 0; + + if (this.polling) { + debug('we are currently polling - waiting to pause'); + total++; + this.once('pollComplete', function () { + debug('pre-pause polling complete'); + --total || pause(); + }); + } + + if (!this.writable) { + debug('we are currently writing - waiting to pause'); + total++; + this.once('drain', function () { + debug('pre-pause writing complete'); + --total || pause(); + }); + } + } else { + pause(); + } + }; + + /** + * Starts polling cycle. + * + * @api public + */ + + Polling.prototype.poll = function () { + debug('polling'); + this.polling = true; + this.doPoll(); + this.emit('poll'); + }; + + /** + * Overloads onData to detect payloads. + * + * @api private + */ + + Polling.prototype.onData = function (data) { + var self = this; + debug('polling got data %s', data); + var callback = function (packet, index, total) { + // if its the first message we consider the transport open + if ('opening' === self.readyState) { + self.onOpen(); + } + + // if its a close packet, we close the ongoing requests + if ('close' === packet.type) { + self.onClose(); + return false; + } + + // otherwise bypass onData and handle the message + self.onPacket(packet); + }; + + // decode payload + parser.decodePayload(data, this.socket.binaryType, callback); + + // if an event did not trigger closing + if ('closed' !== this.readyState) { + // if we got data we're not polling + this.polling = false; + this.emit('pollComplete'); + + if ('open' === this.readyState) { + this.poll(); + } else { + debug('ignoring poll - transport state "%s"', this.readyState); + } + } + }; + + /** + * For polling, send a close packet. + * + * @api private + */ + + Polling.prototype.doClose = function () { + var self = this; + + function close () { + debug('writing close packet'); + self.write([{ type: 'close' }]); + } + + if ('open' === this.readyState) { + debug('transport open - closing'); + close(); + } else { + // in case we're trying to close while + // handshaking is in progress (GH-164) + debug('transport not open - deferring close'); + this.once('open', close); + } + }; + + /** + * Writes a packets payload. + * + * @param {Array} data packets + * @param {Function} drain callback + * @api private + */ + + Polling.prototype.write = function (packets) { + var self = this; + this.writable = false; + var callbackfn = function () { + self.writable = true; + self.emit('drain'); + }; + + parser.encodePayload(packets, this.supportsBinary, function (data) { + self.doWrite(data, callbackfn); + }); + }; + + /** + * Generates uri for connection. + * + * @api private + */ + + Polling.prototype.uri = function () { + var query = this.query || {}; + var schema = this.secure ? 'https' : 'http'; + var port = ''; + + // cache busting is forced + if (false !== this.timestampRequests) { + query[this.timestampParam] = yeast(); + } + + if (!this.supportsBinary && !query.sid) { + query.b64 = 1; + } + + query = parseqs.encode(query); + + // avoid port if default for schema + if (this.port && (('https' === schema && this.port !== 443) || + ('http' === schema && this.port !== 80))) { + port = ':' + this.port; + } + + // prepend ? to query + if (query.length) { + query = '?' + query; + } + + var ipv6 = this.hostname.indexOf(':') !== -1; + return schema + '://' + (ipv6 ? '[' + this.hostname + ']' : this.hostname) + port + this.path + query; + }; + + +/***/ }, +/* 22 */ +/***/ function(module, exports, __webpack_require__) { + + /** + * Module dependencies. + */ + + var parser = __webpack_require__(23); + var Emitter = __webpack_require__(31); + + /** + * Module exports. + */ + + module.exports = Transport; + + /** + * Transport abstract constructor. + * + * @param {Object} options. + * @api private + */ + + function Transport (opts) { + this.path = opts.path; + this.hostname = opts.hostname; + this.port = opts.port; + this.secure = opts.secure; + this.query = opts.query; + this.timestampParam = opts.timestampParam; + this.timestampRequests = opts.timestampRequests; + this.readyState = ''; + this.agent = opts.agent || false; + this.socket = opts.socket; + this.enablesXDR = opts.enablesXDR; + + // SSL options for Node.js client + this.pfx = opts.pfx; + this.key = opts.key; + this.passphrase = opts.passphrase; + this.cert = opts.cert; + this.ca = opts.ca; + this.ciphers = opts.ciphers; + this.rejectUnauthorized = opts.rejectUnauthorized; + + // other options for Node.js client + this.extraHeaders = opts.extraHeaders; + } + + /** + * Mix in `Emitter`. + */ + + Emitter(Transport.prototype); + + /** + * Emits an error. + * + * @param {String} str + * @return {Transport} for chaining + * @api public + */ + + Transport.prototype.onError = function (msg, desc) { + var err = new Error(msg); + err.type = 'TransportError'; + err.description = desc; + this.emit('error', err); + return this; + }; + + /** + * Opens the transport. + * + * @api public + */ + + Transport.prototype.open = function () { + if ('closed' === this.readyState || '' === this.readyState) { + this.readyState = 'opening'; + this.doOpen(); + } + + return this; + }; + + /** + * Closes the transport. + * + * @api private + */ + + Transport.prototype.close = function () { + if ('opening' === this.readyState || 'open' === this.readyState) { + this.doClose(); + this.onClose(); + } + + return this; + }; + + /** + * Sends multiple packets. + * + * @param {Array} packets + * @api private + */ + + Transport.prototype.send = function (packets) { + if ('open' === this.readyState) { + this.write(packets); + } else { + throw new Error('Transport not open'); + } + }; + + /** + * Called upon open + * + * @api private + */ + + Transport.prototype.onOpen = function () { + this.readyState = 'open'; + this.writable = true; + this.emit('open'); + }; + + /** + * Called with data. + * + * @param {String} data + * @api private + */ + + Transport.prototype.onData = function (data) { + var packet = parser.decodePacket(data, this.socket.binaryType); + this.onPacket(packet); + }; + + /** + * Called with a decoded packet. + */ + + Transport.prototype.onPacket = function (packet) { + this.emit('packet', packet); + }; + + /** + * Called upon close. + * + * @api private + */ + + Transport.prototype.onClose = function () { + this.readyState = 'closed'; + this.emit('close'); + }; + + +/***/ }, +/* 23 */ +/***/ function(module, exports, __webpack_require__) { + + /* WEBPACK VAR INJECTION */(function(global) {/** + * Module dependencies. + */ + + var keys = __webpack_require__(24); + var hasBinary = __webpack_require__(25); + var sliceBuffer = __webpack_require__(26); + var after = __webpack_require__(27); + var utf8 = __webpack_require__(28); + + var base64encoder; + if (global && global.ArrayBuffer) { + base64encoder = __webpack_require__(29); + } + + /** + * Check if we are running an android browser. That requires us to use + * ArrayBuffer with polling transports... + * + * http://ghinda.net/jpeg-blob-ajax-android/ + */ + + var isAndroid = typeof navigator !== 'undefined' && /Android/i.test(navigator.userAgent); + + /** + * Check if we are running in PhantomJS. + * Uploading a Blob with PhantomJS does not work correctly, as reported here: + * https://github.com/ariya/phantomjs/issues/11395 + * @type boolean + */ + var isPhantomJS = typeof navigator !== 'undefined' && /PhantomJS/i.test(navigator.userAgent); + + /** + * When true, avoids using Blobs to encode payloads. + * @type boolean + */ + var dontSendBlobs = isAndroid || isPhantomJS; + + /** + * Current protocol version. + */ + + exports.protocol = 3; + + /** + * Packet types. + */ + + var packets = exports.packets = { + open: 0 // non-ws + , close: 1 // non-ws + , ping: 2 + , pong: 3 + , message: 4 + , upgrade: 5 + , noop: 6 + }; + + var packetslist = keys(packets); + + /** + * Premade error packet. + */ + + var err = { type: 'error', data: 'parser error' }; + + /** + * Create a blob api even for blob builder when vendor prefixes exist + */ + + var Blob = __webpack_require__(30); + + /** + * Encodes a packet. + * + * [ ] + * + * Example: + * + * 5hello world + * 3 + * 4 + * + * Binary is encoded in an identical principle + * + * @api private + */ + + exports.encodePacket = function (packet, supportsBinary, utf8encode, callback) { + if ('function' == typeof supportsBinary) { + callback = supportsBinary; + supportsBinary = false; + } + + if ('function' == typeof utf8encode) { + callback = utf8encode; + utf8encode = null; + } + + var data = (packet.data === undefined) + ? undefined + : packet.data.buffer || packet.data; + + if (global.ArrayBuffer && data instanceof ArrayBuffer) { + return encodeArrayBuffer(packet, supportsBinary, callback); + } else if (Blob && data instanceof global.Blob) { + return encodeBlob(packet, supportsBinary, callback); + } + + // might be an object with { base64: true, data: dataAsBase64String } + if (data && data.base64) { + return encodeBase64Object(packet, callback); + } + + // Sending data as a utf-8 string + var encoded = packets[packet.type]; + + // data fragment is optional + if (undefined !== packet.data) { + encoded += utf8encode ? utf8.encode(String(packet.data)) : String(packet.data); + } + + return callback('' + encoded); + + }; + + function encodeBase64Object(packet, callback) { + // packet data is an object { base64: true, data: dataAsBase64String } + var message = 'b' + exports.packets[packet.type] + packet.data.data; + return callback(message); + } + + /** + * Encode packet helpers for binary types + */ + + function encodeArrayBuffer(packet, supportsBinary, callback) { + if (!supportsBinary) { + return exports.encodeBase64Packet(packet, callback); + } + + var data = packet.data; + var contentArray = new Uint8Array(data); + var resultBuffer = new Uint8Array(1 + data.byteLength); + + resultBuffer[0] = packets[packet.type]; + for (var i = 0; i < contentArray.length; i++) { + resultBuffer[i+1] = contentArray[i]; + } + + return callback(resultBuffer.buffer); + } + + function encodeBlobAsArrayBuffer(packet, supportsBinary, callback) { + if (!supportsBinary) { + return exports.encodeBase64Packet(packet, callback); + } + + var fr = new FileReader(); + fr.onload = function() { + packet.data = fr.result; + exports.encodePacket(packet, supportsBinary, true, callback); + }; + return fr.readAsArrayBuffer(packet.data); + } + + function encodeBlob(packet, supportsBinary, callback) { + if (!supportsBinary) { + return exports.encodeBase64Packet(packet, callback); + } + + if (dontSendBlobs) { + return encodeBlobAsArrayBuffer(packet, supportsBinary, callback); + } + + var length = new Uint8Array(1); + length[0] = packets[packet.type]; + var blob = new Blob([length.buffer, packet.data]); + + return callback(blob); + } + + /** + * Encodes a packet with binary data in a base64 string + * + * @param {Object} packet, has `type` and `data` + * @return {String} base64 encoded message + */ + + exports.encodeBase64Packet = function(packet, callback) { + var message = 'b' + exports.packets[packet.type]; + if (Blob && packet.data instanceof global.Blob) { + var fr = new FileReader(); + fr.onload = function() { + var b64 = fr.result.split(',')[1]; + callback(message + b64); + }; + return fr.readAsDataURL(packet.data); + } + + var b64data; + try { + b64data = String.fromCharCode.apply(null, new Uint8Array(packet.data)); + } catch (e) { + // iPhone Safari doesn't let you apply with typed arrays + var typed = new Uint8Array(packet.data); + var basic = new Array(typed.length); + for (var i = 0; i < typed.length; i++) { + basic[i] = typed[i]; + } + b64data = String.fromCharCode.apply(null, basic); + } + message += global.btoa(b64data); + return callback(message); + }; + + /** + * Decodes a packet. Changes format to Blob if requested. + * + * @return {Object} with `type` and `data` (if any) + * @api private + */ + + exports.decodePacket = function (data, binaryType, utf8decode) { + if (data === undefined) { + return err; + } + // String data + if (typeof data == 'string') { + if (data.charAt(0) == 'b') { + return exports.decodeBase64Packet(data.substr(1), binaryType); + } + + if (utf8decode) { + data = tryDecode(data); + if (data === false) { + return err; + } + } + var type = data.charAt(0); + + if (Number(type) != type || !packetslist[type]) { + return err; + } + + if (data.length > 1) { + return { type: packetslist[type], data: data.substring(1) }; + } else { + return { type: packetslist[type] }; + } + } + + var asArray = new Uint8Array(data); + var type = asArray[0]; + var rest = sliceBuffer(data, 1); + if (Blob && binaryType === 'blob') { + rest = new Blob([rest]); + } + return { type: packetslist[type], data: rest }; + }; + + function tryDecode(data) { + try { + data = utf8.decode(data); + } catch (e) { + return false; + } + return data; + } + + /** + * Decodes a packet encoded in a base64 string + * + * @param {String} base64 encoded message + * @return {Object} with `type` and `data` (if any) + */ + + exports.decodeBase64Packet = function(msg, binaryType) { + var type = packetslist[msg.charAt(0)]; + if (!base64encoder) { + return { type: type, data: { base64: true, data: msg.substr(1) } }; + } + + var data = base64encoder.decode(msg.substr(1)); + + if (binaryType === 'blob' && Blob) { + data = new Blob([data]); + } + + return { type: type, data: data }; + }; + + /** + * Encodes multiple messages (payload). + * + * :data + * + * Example: + * + * 11:hello world2:hi + * + * If any contents are binary, they will be encoded as base64 strings. Base64 + * encoded strings are marked with a b before the length specifier + * + * @param {Array} packets + * @api private + */ + + exports.encodePayload = function (packets, supportsBinary, callback) { + if (typeof supportsBinary == 'function') { + callback = supportsBinary; + supportsBinary = null; + } + + var isBinary = hasBinary(packets); + + if (supportsBinary && isBinary) { + if (Blob && !dontSendBlobs) { + return exports.encodePayloadAsBlob(packets, callback); + } + + return exports.encodePayloadAsArrayBuffer(packets, callback); + } + + if (!packets.length) { + return callback('0:'); + } + + function setLengthHeader(message) { + return message.length + ':' + message; + } + + function encodeOne(packet, doneCallback) { + exports.encodePacket(packet, !isBinary ? false : supportsBinary, true, function(message) { + doneCallback(null, setLengthHeader(message)); + }); + } + + map(packets, encodeOne, function(err, results) { + return callback(results.join('')); + }); + }; + + /** + * Async array map using after + */ + + function map(ary, each, done) { + var result = new Array(ary.length); + var next = after(ary.length, done); + + var eachWithIndex = function(i, el, cb) { + each(el, function(error, msg) { + result[i] = msg; + cb(error, result); + }); + }; + + for (var i = 0; i < ary.length; i++) { + eachWithIndex(i, ary[i], next); + } + } + + /* + * Decodes data when a payload is maybe expected. Possible binary contents are + * decoded from their base64 representation + * + * @param {String} data, callback method + * @api public + */ + + exports.decodePayload = function (data, binaryType, callback) { + if (typeof data != 'string') { + return exports.decodePayloadAsBinary(data, binaryType, callback); + } + + if (typeof binaryType === 'function') { + callback = binaryType; + binaryType = null; + } + + var packet; + if (data == '') { + // parser error - ignoring payload + return callback(err, 0, 1); + } + + var length = '' + , n, msg; + + for (var i = 0, l = data.length; i < l; i++) { + var chr = data.charAt(i); + + if (':' != chr) { + length += chr; + } else { + if ('' == length || (length != (n = Number(length)))) { + // parser error - ignoring payload + return callback(err, 0, 1); + } + + msg = data.substr(i + 1, n); + + if (length != msg.length) { + // parser error - ignoring payload + return callback(err, 0, 1); + } + + if (msg.length) { + packet = exports.decodePacket(msg, binaryType, true); + + if (err.type == packet.type && err.data == packet.data) { + // parser error in individual packet - ignoring payload + return callback(err, 0, 1); + } + + var ret = callback(packet, i + n, l); + if (false === ret) return; + } + + // advance cursor + i += n; + length = ''; + } + } + + if (length != '') { + // parser error - ignoring payload + return callback(err, 0, 1); + } + + }; + + /** + * Encodes multiple messages (payload) as binary. + * + * <1 = binary, 0 = string>[...] + * + * Example: + * 1 3 255 1 2 3, if the binary contents are interpreted as 8 bit integers + * + * @param {Array} packets + * @return {ArrayBuffer} encoded payload + * @api private + */ + + exports.encodePayloadAsArrayBuffer = function(packets, callback) { + if (!packets.length) { + return callback(new ArrayBuffer(0)); + } + + function encodeOne(packet, doneCallback) { + exports.encodePacket(packet, true, true, function(data) { + return doneCallback(null, data); + }); + } + + map(packets, encodeOne, function(err, encodedPackets) { + var totalLength = encodedPackets.reduce(function(acc, p) { + var len; + if (typeof p === 'string'){ + len = p.length; + } else { + len = p.byteLength; + } + return acc + len.toString().length + len + 2; // string/binary identifier + separator = 2 + }, 0); + + var resultArray = new Uint8Array(totalLength); + + var bufferIndex = 0; + encodedPackets.forEach(function(p) { + var isString = typeof p === 'string'; + var ab = p; + if (isString) { + var view = new Uint8Array(p.length); + for (var i = 0; i < p.length; i++) { + view[i] = p.charCodeAt(i); + } + ab = view.buffer; + } + + if (isString) { // not true binary + resultArray[bufferIndex++] = 0; + } else { // true binary + resultArray[bufferIndex++] = 1; + } + + var lenStr = ab.byteLength.toString(); + for (var i = 0; i < lenStr.length; i++) { + resultArray[bufferIndex++] = parseInt(lenStr[i]); + } + resultArray[bufferIndex++] = 255; + + var view = new Uint8Array(ab); + for (var i = 0; i < view.length; i++) { + resultArray[bufferIndex++] = view[i]; + } + }); + + return callback(resultArray.buffer); + }); + }; + + /** + * Encode as Blob + */ + + exports.encodePayloadAsBlob = function(packets, callback) { + function encodeOne(packet, doneCallback) { + exports.encodePacket(packet, true, true, function(encoded) { + var binaryIdentifier = new Uint8Array(1); + binaryIdentifier[0] = 1; + if (typeof encoded === 'string') { + var view = new Uint8Array(encoded.length); + for (var i = 0; i < encoded.length; i++) { + view[i] = encoded.charCodeAt(i); + } + encoded = view.buffer; + binaryIdentifier[0] = 0; + } + + var len = (encoded instanceof ArrayBuffer) + ? encoded.byteLength + : encoded.size; + + var lenStr = len.toString(); + var lengthAry = new Uint8Array(lenStr.length + 1); + for (var i = 0; i < lenStr.length; i++) { + lengthAry[i] = parseInt(lenStr[i]); + } + lengthAry[lenStr.length] = 255; + + if (Blob) { + var blob = new Blob([binaryIdentifier.buffer, lengthAry.buffer, encoded]); + doneCallback(null, blob); + } + }); + } + + map(packets, encodeOne, function(err, results) { + return callback(new Blob(results)); + }); + }; + + /* + * Decodes data when a payload is maybe expected. Strings are decoded by + * interpreting each byte as a key code for entries marked to start with 0. See + * description of encodePayloadAsBinary + * + * @param {ArrayBuffer} data, callback method + * @api public + */ + + exports.decodePayloadAsBinary = function (data, binaryType, callback) { + if (typeof binaryType === 'function') { + callback = binaryType; + binaryType = null; + } + + var bufferTail = data; + var buffers = []; + + var numberTooLong = false; + while (bufferTail.byteLength > 0) { + var tailArray = new Uint8Array(bufferTail); + var isString = tailArray[0] === 0; + var msgLength = ''; + + for (var i = 1; ; i++) { + if (tailArray[i] == 255) break; + + if (msgLength.length > 310) { + numberTooLong = true; + break; + } + + msgLength += tailArray[i]; + } + + if(numberTooLong) return callback(err, 0, 1); + + bufferTail = sliceBuffer(bufferTail, 2 + msgLength.length); + msgLength = parseInt(msgLength); + + var msg = sliceBuffer(bufferTail, 0, msgLength); + if (isString) { + try { + msg = String.fromCharCode.apply(null, new Uint8Array(msg)); + } catch (e) { + // iPhone Safari doesn't let you apply to typed arrays + var typed = new Uint8Array(msg); + msg = ''; + for (var i = 0; i < typed.length; i++) { + msg += String.fromCharCode(typed[i]); + } + } + } + + buffers.push(msg); + bufferTail = sliceBuffer(bufferTail, msgLength); + } + + var total = buffers.length; + buffers.forEach(function(buffer, i) { + callback(exports.decodePacket(buffer, binaryType, true), i, total); + }); + }; + + /* WEBPACK VAR INJECTION */}.call(exports, (function() { return this; }()))) + +/***/ }, +/* 24 */ +/***/ function(module, exports) { + + + /** + * Gets the keys for an object. + * + * @return {Array} keys + * @api private + */ + + module.exports = Object.keys || function keys (obj){ + var arr = []; + var has = Object.prototype.hasOwnProperty; + + for (var i in obj) { + if (has.call(obj, i)) { + arr.push(i); + } + } + return arr; + }; + + +/***/ }, +/* 25 */ +/***/ function(module, exports, __webpack_require__) { + + /* WEBPACK VAR INJECTION */(function(global) { + /* + * Module requirements. + */ + + var isArray = __webpack_require__(9); + + /** + * Module exports. + */ + + module.exports = hasBinary; + + /** + * Checks for binary data. + * + * Right now only Buffer and ArrayBuffer are supported.. + * + * @param {Object} anything + * @api public + */ + + function hasBinary(data) { + + function _hasBinary(obj) { + if (!obj) return false; + + if ( (global.Buffer && global.Buffer.isBuffer(obj)) || + (global.ArrayBuffer && obj instanceof ArrayBuffer) || + (global.Blob && obj instanceof Blob) || + (global.File && obj instanceof File) + ) { + return true; + } + + if (isArray(obj)) { + for (var i = 0; i < obj.length; i++) { + if (_hasBinary(obj[i])) { + return true; + } + } + } else if (obj && 'object' == typeof obj) { + if (obj.toJSON) { + obj = obj.toJSON(); + } + + for (var key in obj) { + if (Object.prototype.hasOwnProperty.call(obj, key) && _hasBinary(obj[key])) { + return true; + } + } + } + + return false; + } + + return _hasBinary(data); + } + + /* WEBPACK VAR INJECTION */}.call(exports, (function() { return this; }()))) + +/***/ }, +/* 26 */ +/***/ function(module, exports) { + + /** + * An abstraction for slicing an arraybuffer even when + * ArrayBuffer.prototype.slice is not supported + * + * @api public + */ + + module.exports = function(arraybuffer, start, end) { + var bytes = arraybuffer.byteLength; + start = start || 0; + end = end || bytes; + + if (arraybuffer.slice) { return arraybuffer.slice(start, end); } + + if (start < 0) { start += bytes; } + if (end < 0) { end += bytes; } + if (end > bytes) { end = bytes; } + + if (start >= bytes || start >= end || bytes === 0) { + return new ArrayBuffer(0); + } + + var abv = new Uint8Array(arraybuffer); + var result = new Uint8Array(end - start); + for (var i = start, ii = 0; i < end; i++, ii++) { + result[ii] = abv[i]; + } + return result.buffer; + }; + + +/***/ }, +/* 27 */ +/***/ function(module, exports) { + + module.exports = after + + function after(count, callback, err_cb) { + var bail = false + err_cb = err_cb || noop + proxy.count = count + + return (count === 0) ? callback() : proxy + + function proxy(err, result) { + if (proxy.count <= 0) { + throw new Error('after called too many times') + } + --proxy.count + + // after first error, rest are passed to err_cb + if (err) { + bail = true + callback(err) + // future error callbacks will go to error handler + callback = err_cb + } else if (proxy.count === 0 && !bail) { + callback(null, result) + } + } + } + + function noop() {} + + +/***/ }, +/* 28 */ +/***/ function(module, exports, __webpack_require__) { + + var __WEBPACK_AMD_DEFINE_RESULT__;/* WEBPACK VAR INJECTION */(function(module, global) {/*! https://mths.be/wtf8 v1.0.0 by @mathias */ + ;(function(root) { + + // Detect free variables `exports` + var freeExports = typeof exports == 'object' && exports; + + // Detect free variable `module` + var freeModule = typeof module == 'object' && module && + module.exports == freeExports && module; + + // Detect free variable `global`, from Node.js or Browserified code, + // and use it as `root` + var freeGlobal = typeof global == 'object' && global; + if (freeGlobal.global === freeGlobal || freeGlobal.window === freeGlobal) { + root = freeGlobal; + } + + /*--------------------------------------------------------------------------*/ + + var stringFromCharCode = String.fromCharCode; + + // Taken from https://mths.be/punycode + function ucs2decode(string) { + var output = []; + var counter = 0; + var length = string.length; + var value; + var extra; + while (counter < length) { + value = string.charCodeAt(counter++); + if (value >= 0xD800 && value <= 0xDBFF && counter < length) { + // high surrogate, and there is a next character + extra = string.charCodeAt(counter++); + if ((extra & 0xFC00) == 0xDC00) { // low surrogate + output.push(((value & 0x3FF) << 10) + (extra & 0x3FF) + 0x10000); + } else { + // unmatched surrogate; only append this code unit, in case the next + // code unit is the high surrogate of a surrogate pair + output.push(value); + counter--; + } + } else { + output.push(value); + } + } + return output; + } + + // Taken from https://mths.be/punycode + function ucs2encode(array) { + var length = array.length; + var index = -1; + var value; + var output = ''; + while (++index < length) { + value = array[index]; + if (value > 0xFFFF) { + value -= 0x10000; + output += stringFromCharCode(value >>> 10 & 0x3FF | 0xD800); + value = 0xDC00 | value & 0x3FF; + } + output += stringFromCharCode(value); + } + return output; + } + + /*--------------------------------------------------------------------------*/ + + function createByte(codePoint, shift) { + return stringFromCharCode(((codePoint >> shift) & 0x3F) | 0x80); + } + + function encodeCodePoint(codePoint) { + if ((codePoint & 0xFFFFFF80) == 0) { // 1-byte sequence + return stringFromCharCode(codePoint); + } + var symbol = ''; + if ((codePoint & 0xFFFFF800) == 0) { // 2-byte sequence + symbol = stringFromCharCode(((codePoint >> 6) & 0x1F) | 0xC0); + } + else if ((codePoint & 0xFFFF0000) == 0) { // 3-byte sequence + symbol = stringFromCharCode(((codePoint >> 12) & 0x0F) | 0xE0); + symbol += createByte(codePoint, 6); + } + else if ((codePoint & 0xFFE00000) == 0) { // 4-byte sequence + symbol = stringFromCharCode(((codePoint >> 18) & 0x07) | 0xF0); + symbol += createByte(codePoint, 12); + symbol += createByte(codePoint, 6); + } + symbol += stringFromCharCode((codePoint & 0x3F) | 0x80); + return symbol; + } + + function wtf8encode(string) { + var codePoints = ucs2decode(string); + var length = codePoints.length; + var index = -1; + var codePoint; + var byteString = ''; + while (++index < length) { + codePoint = codePoints[index]; + byteString += encodeCodePoint(codePoint); + } + return byteString; + } + + /*--------------------------------------------------------------------------*/ + + function readContinuationByte() { + if (byteIndex >= byteCount) { + throw Error('Invalid byte index'); + } + + var continuationByte = byteArray[byteIndex] & 0xFF; + byteIndex++; + + if ((continuationByte & 0xC0) == 0x80) { + return continuationByte & 0x3F; + } + + // If we end up here, it’s not a continuation byte. + throw Error('Invalid continuation byte'); + } + + function decodeSymbol() { + var byte1; + var byte2; + var byte3; + var byte4; + var codePoint; + + if (byteIndex > byteCount) { + throw Error('Invalid byte index'); + } + + if (byteIndex == byteCount) { + return false; + } + + // Read the first byte. + byte1 = byteArray[byteIndex] & 0xFF; + byteIndex++; + + // 1-byte sequence (no continuation bytes) + if ((byte1 & 0x80) == 0) { + return byte1; + } + + // 2-byte sequence + if ((byte1 & 0xE0) == 0xC0) { + var byte2 = readContinuationByte(); + codePoint = ((byte1 & 0x1F) << 6) | byte2; + if (codePoint >= 0x80) { + return codePoint; + } else { + throw Error('Invalid continuation byte'); + } + } + + // 3-byte sequence (may include unpaired surrogates) + if ((byte1 & 0xF0) == 0xE0) { + byte2 = readContinuationByte(); + byte3 = readContinuationByte(); + codePoint = ((byte1 & 0x0F) << 12) | (byte2 << 6) | byte3; + if (codePoint >= 0x0800) { + return codePoint; + } else { + throw Error('Invalid continuation byte'); + } + } + + // 4-byte sequence + if ((byte1 & 0xF8) == 0xF0) { + byte2 = readContinuationByte(); + byte3 = readContinuationByte(); + byte4 = readContinuationByte(); + codePoint = ((byte1 & 0x0F) << 0x12) | (byte2 << 0x0C) | + (byte3 << 0x06) | byte4; + if (codePoint >= 0x010000 && codePoint <= 0x10FFFF) { + return codePoint; + } + } + + throw Error('Invalid WTF-8 detected'); + } + + var byteArray; + var byteCount; + var byteIndex; + function wtf8decode(byteString) { + byteArray = ucs2decode(byteString); + byteCount = byteArray.length; + byteIndex = 0; + var codePoints = []; + var tmp; + while ((tmp = decodeSymbol()) !== false) { + codePoints.push(tmp); + } + return ucs2encode(codePoints); + } + + /*--------------------------------------------------------------------------*/ + + var wtf8 = { + 'version': '1.0.0', + 'encode': wtf8encode, + 'decode': wtf8decode + }; + + // Some AMD build optimizers, like r.js, check for specific condition patterns + // like the following: + if ( + true + ) { + !(__WEBPACK_AMD_DEFINE_RESULT__ = function() { + return wtf8; + }.call(exports, __webpack_require__, exports, module), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__)); + } else if (freeExports && !freeExports.nodeType) { + if (freeModule) { // in Node.js or RingoJS v0.8.0+ + freeModule.exports = wtf8; + } else { // in Narwhal or RingoJS v0.7.0- + var object = {}; + var hasOwnProperty = object.hasOwnProperty; + for (var key in wtf8) { + hasOwnProperty.call(wtf8, key) && (freeExports[key] = wtf8[key]); + } + } + } else { // in Rhino or a web browser + root.wtf8 = wtf8; + } + + }(this)); + + /* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(8)(module), (function() { return this; }()))) + +/***/ }, +/* 29 */ +/***/ function(module, exports) { + + /* + * base64-arraybuffer + * https://github.com/niklasvh/base64-arraybuffer + * + * Copyright (c) 2012 Niklas von Hertzen + * Licensed under the MIT license. + */ + (function(){ + "use strict"; + + var chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + + // Use a lookup table to find the index. + var lookup = new Uint8Array(256); + for (var i = 0; i < chars.length; i++) { + lookup[chars.charCodeAt(i)] = i; + } + + exports.encode = function(arraybuffer) { + var bytes = new Uint8Array(arraybuffer), + i, len = bytes.length, base64 = ""; + + for (i = 0; i < len; i+=3) { + base64 += chars[bytes[i] >> 2]; + base64 += chars[((bytes[i] & 3) << 4) | (bytes[i + 1] >> 4)]; + base64 += chars[((bytes[i + 1] & 15) << 2) | (bytes[i + 2] >> 6)]; + base64 += chars[bytes[i + 2] & 63]; + } + + if ((len % 3) === 2) { + base64 = base64.substring(0, base64.length - 1) + "="; + } else if (len % 3 === 1) { + base64 = base64.substring(0, base64.length - 2) + "=="; + } + + return base64; + }; + + exports.decode = function(base64) { + var bufferLength = base64.length * 0.75, + len = base64.length, i, p = 0, + encoded1, encoded2, encoded3, encoded4; + + if (base64[base64.length - 1] === "=") { + bufferLength--; + if (base64[base64.length - 2] === "=") { + bufferLength--; + } + } + + var arraybuffer = new ArrayBuffer(bufferLength), + bytes = new Uint8Array(arraybuffer); + + for (i = 0; i < len; i+=4) { + encoded1 = lookup[base64.charCodeAt(i)]; + encoded2 = lookup[base64.charCodeAt(i+1)]; + encoded3 = lookup[base64.charCodeAt(i+2)]; + encoded4 = lookup[base64.charCodeAt(i+3)]; + + bytes[p++] = (encoded1 << 2) | (encoded2 >> 4); + bytes[p++] = ((encoded2 & 15) << 4) | (encoded3 >> 2); + bytes[p++] = ((encoded3 & 3) << 6) | (encoded4 & 63); + } + + return arraybuffer; + }; + })(); + + +/***/ }, +/* 30 */ +/***/ function(module, exports) { + + /* WEBPACK VAR INJECTION */(function(global) {/** + * Create a blob builder even when vendor prefixes exist + */ + + var BlobBuilder = global.BlobBuilder + || global.WebKitBlobBuilder + || global.MSBlobBuilder + || global.MozBlobBuilder; + + /** + * Check if Blob constructor is supported + */ + + var blobSupported = (function() { + try { + var a = new Blob(['hi']); + return a.size === 2; + } catch(e) { + return false; + } + })(); + + /** + * Check if Blob constructor supports ArrayBufferViews + * Fails in Safari 6, so we need to map to ArrayBuffers there. + */ + + var blobSupportsArrayBufferView = blobSupported && (function() { + try { + var b = new Blob([new Uint8Array([1,2])]); + return b.size === 2; + } catch(e) { + return false; + } + })(); + + /** + * Check if BlobBuilder is supported + */ + + var blobBuilderSupported = BlobBuilder + && BlobBuilder.prototype.append + && BlobBuilder.prototype.getBlob; + + /** + * Helper function that maps ArrayBufferViews to ArrayBuffers + * Used by BlobBuilder constructor and old browsers that didn't + * support it in the Blob constructor. + */ + + function mapArrayBufferViews(ary) { + for (var i = 0; i < ary.length; i++) { + var chunk = ary[i]; + if (chunk.buffer instanceof ArrayBuffer) { + var buf = chunk.buffer; + + // if this is a subarray, make a copy so we only + // include the subarray region from the underlying buffer + if (chunk.byteLength !== buf.byteLength) { + var copy = new Uint8Array(chunk.byteLength); + copy.set(new Uint8Array(buf, chunk.byteOffset, chunk.byteLength)); + buf = copy.buffer; + } + + ary[i] = buf; + } + } + } + + function BlobBuilderConstructor(ary, options) { + options = options || {}; + + var bb = new BlobBuilder(); + mapArrayBufferViews(ary); + + for (var i = 0; i < ary.length; i++) { + bb.append(ary[i]); + } + + return (options.type) ? bb.getBlob(options.type) : bb.getBlob(); + }; + + function BlobConstructor(ary, options) { + mapArrayBufferViews(ary); + return new Blob(ary, options || {}); + }; + + module.exports = (function() { + if (blobSupported) { + return blobSupportsArrayBufferView ? global.Blob : BlobConstructor; + } else if (blobBuilderSupported) { + return BlobBuilderConstructor; + } else { + return undefined; + } + })(); + + /* WEBPACK VAR INJECTION */}.call(exports, (function() { return this; }()))) + +/***/ }, +/* 31 */ +/***/ function(module, exports) { + + + /** + * Expose `Emitter`. + */ + + module.exports = Emitter; + + /** + * Initialize a new `Emitter`. + * + * @api public + */ + + function Emitter(obj) { + if (obj) return mixin(obj); + }; + + /** + * Mixin the emitter properties. + * + * @param {Object} obj + * @return {Object} + * @api private + */ + + function mixin(obj) { + for (var key in Emitter.prototype) { + obj[key] = Emitter.prototype[key]; + } + return obj; + } + + /** + * Listen on the given `event` with `fn`. + * + * @param {String} event + * @param {Function} fn + * @return {Emitter} + * @api public + */ + + Emitter.prototype.on = + Emitter.prototype.addEventListener = function(event, fn){ + this._callbacks = this._callbacks || {}; + (this._callbacks[event] = this._callbacks[event] || []) + .push(fn); + return this; + }; + + /** + * Adds an `event` listener that will be invoked a single + * time then automatically removed. + * + * @param {String} event + * @param {Function} fn + * @return {Emitter} + * @api public + */ + + Emitter.prototype.once = function(event, fn){ + var self = this; + this._callbacks = this._callbacks || {}; + + function on() { + self.off(event, on); + fn.apply(this, arguments); + } + + on.fn = fn; + this.on(event, on); + return this; + }; + + /** + * Remove the given callback for `event` or all + * registered callbacks. + * + * @param {String} event + * @param {Function} fn + * @return {Emitter} + * @api public + */ + + Emitter.prototype.off = + Emitter.prototype.removeListener = + Emitter.prototype.removeAllListeners = + Emitter.prototype.removeEventListener = function(event, fn){ + this._callbacks = this._callbacks || {}; + + // all + if (0 == arguments.length) { + this._callbacks = {}; + return this; + } + + // specific event + var callbacks = this._callbacks[event]; + if (!callbacks) return this; + + // remove all handlers + if (1 == arguments.length) { + delete this._callbacks[event]; + return this; + } + + // remove specific handler + var cb; + for (var i = 0; i < callbacks.length; i++) { + cb = callbacks[i]; + if (cb === fn || cb.fn === fn) { + callbacks.splice(i, 1); + break; + } + } + return this; + }; + + /** + * Emit `event` with the given args. + * + * @param {String} event + * @param {Mixed} ... + * @return {Emitter} + */ + + Emitter.prototype.emit = function(event){ + this._callbacks = this._callbacks || {}; + var args = [].slice.call(arguments, 1) + , callbacks = this._callbacks[event]; + + if (callbacks) { + callbacks = callbacks.slice(0); + for (var i = 0, len = callbacks.length; i < len; ++i) { + callbacks[i].apply(this, args); + } + } + + return this; + }; + + /** + * Return array of callbacks for `event`. + * + * @param {String} event + * @return {Array} + * @api public + */ + + Emitter.prototype.listeners = function(event){ + this._callbacks = this._callbacks || {}; + return this._callbacks[event] || []; + }; + + /** + * Check if this emitter has `event` handlers. + * + * @param {String} event + * @return {Boolean} + * @api public + */ + + Emitter.prototype.hasListeners = function(event){ + return !! this.listeners(event).length; + }; + + +/***/ }, +/* 32 */ +/***/ function(module, exports) { + + /** + * Compiles a querystring + * Returns string representation of the object + * + * @param {Object} + * @api private + */ + + exports.encode = function (obj) { + var str = ''; + + for (var i in obj) { + if (obj.hasOwnProperty(i)) { + if (str.length) str += '&'; + str += encodeURIComponent(i) + '=' + encodeURIComponent(obj[i]); + } + } + + return str; + }; + + /** + * Parses a simple querystring into an object + * + * @param {String} qs + * @api private + */ + + exports.decode = function(qs){ + var qry = {}; + var pairs = qs.split('&'); + for (var i = 0, l = pairs.length; i < l; i++) { + var pair = pairs[i].split('='); + qry[decodeURIComponent(pair[0])] = decodeURIComponent(pair[1]); + } + return qry; + }; + + +/***/ }, +/* 33 */ +/***/ function(module, exports) { + + + module.exports = function(a, b){ + var fn = function(){}; + fn.prototype = b.prototype; + a.prototype = new fn; + a.prototype.constructor = a; + }; + +/***/ }, +/* 34 */ +/***/ function(module, exports) { + + 'use strict'; + + var alphabet = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-_'.split('') + , length = 64 + , map = {} + , seed = 0 + , i = 0 + , prev; + + /** + * Return a string representing the specified number. + * + * @param {Number} num The number to convert. + * @returns {String} The string representation of the number. + * @api public + */ + function encode(num) { + var encoded = ''; + + do { + encoded = alphabet[num % length] + encoded; + num = Math.floor(num / length); + } while (num > 0); + + return encoded; + } + + /** + * Return the integer value specified by the given string. + * + * @param {String} str The string to convert. + * @returns {Number} The integer value represented by the string. + * @api public + */ + function decode(str) { + var decoded = 0; + + for (i = 0; i < str.length; i++) { + decoded = decoded * length + map[str.charAt(i)]; + } + + return decoded; + } + + /** + * Yeast: A tiny growing id generator. + * + * @returns {String} A unique id. + * @api public + */ + function yeast() { + var now = encode(+new Date()); + + if (now !== prev) return seed = 0, prev = now; + return now +'.'+ encode(seed++); + } + + // + // Map each character to its index. + // + for (; i < length; i++) map[alphabet[i]] = i; + + // + // Expose the `yeast`, `encode` and `decode` functions. + // + yeast.encode = encode; + yeast.decode = decode; + module.exports = yeast; + + +/***/ }, +/* 35 */ +/***/ function(module, exports, __webpack_require__) { + + /* WEBPACK VAR INJECTION */(function(global) { + /** + * Module requirements. + */ + + var Polling = __webpack_require__(21); + var inherit = __webpack_require__(33); + + /** + * Module exports. + */ + + module.exports = JSONPPolling; + + /** + * Cached regular expressions. + */ + + var rNewline = /\n/g; + var rEscapedNewline = /\\n/g; + + /** + * Global JSONP callbacks. + */ + + var callbacks; + + /** + * Noop. + */ + + function empty () { } + + /** + * JSONP Polling constructor. + * + * @param {Object} opts. + * @api public + */ + + function JSONPPolling (opts) { + Polling.call(this, opts); + + this.query = this.query || {}; + + // define global callbacks array if not present + // we do this here (lazily) to avoid unneeded global pollution + if (!callbacks) { + // we need to consider multiple engines in the same page + if (!global.___eio) global.___eio = []; + callbacks = global.___eio; + } + + // callback identifier + this.index = callbacks.length; + + // add callback to jsonp global + var self = this; + callbacks.push(function (msg) { + self.onData(msg); + }); + + // append to query string + this.query.j = this.index; + + // prevent spurious errors from being emitted when the window is unloaded + if (global.document && global.addEventListener) { + global.addEventListener('beforeunload', function () { + if (self.script) self.script.onerror = empty; + }, false); + } + } + + /** + * Inherits from Polling. + */ + + inherit(JSONPPolling, Polling); + + /* + * JSONP only supports binary as base64 encoded strings + */ + + JSONPPolling.prototype.supportsBinary = false; + + /** + * Closes the socket. + * + * @api private + */ + + JSONPPolling.prototype.doClose = function () { + if (this.script) { + this.script.parentNode.removeChild(this.script); + this.script = null; + } + + if (this.form) { + this.form.parentNode.removeChild(this.form); + this.form = null; + this.iframe = null; + } + + Polling.prototype.doClose.call(this); + }; + + /** + * Starts a poll cycle. + * + * @api private + */ + + JSONPPolling.prototype.doPoll = function () { + var self = this; + var script = document.createElement('script'); + + if (this.script) { + this.script.parentNode.removeChild(this.script); + this.script = null; + } + + script.async = true; + script.src = this.uri(); + script.onerror = function (e) { + self.onError('jsonp poll error', e); + }; + + var insertAt = document.getElementsByTagName('script')[0]; + if (insertAt) { + insertAt.parentNode.insertBefore(script, insertAt); + } else { + (document.head || document.body).appendChild(script); + } + this.script = script; + + var isUAgecko = 'undefined' !== typeof navigator && /gecko/i.test(navigator.userAgent); + + if (isUAgecko) { + setTimeout(function () { + var iframe = document.createElement('iframe'); + document.body.appendChild(iframe); + document.body.removeChild(iframe); + }, 100); + } + }; + + /** + * Writes with a hidden iframe. + * + * @param {String} data to send + * @param {Function} called upon flush. + * @api private + */ + + JSONPPolling.prototype.doWrite = function (data, fn) { + var self = this; + + if (!this.form) { + var form = document.createElement('form'); + var area = document.createElement('textarea'); + var id = this.iframeId = 'eio_iframe_' + this.index; + var iframe; + + form.className = 'socketio'; + form.style.position = 'absolute'; + form.style.top = '-1000px'; + form.style.left = '-1000px'; + form.target = id; + form.method = 'POST'; + form.setAttribute('accept-charset', 'utf-8'); + area.name = 'd'; + form.appendChild(area); + document.body.appendChild(form); + + this.form = form; + this.area = area; + } + + this.form.action = this.uri(); + + function complete () { + initIframe(); + fn(); + } + + function initIframe () { + if (self.iframe) { + try { + self.form.removeChild(self.iframe); + } catch (e) { + self.onError('jsonp polling iframe removal error', e); + } + } + + try { + // ie6 dynamic iframes with target="" support (thanks Chris Lambacher) + var html = ''; + } + }; + + function updateLabel(progress, label) { + if (progress.position === -1) { + return; + } + + var position = +progress.position.toFixed(2).split('.')[1] || 100; + label.innerHTML = position + '%'; + } + } + + return { + handle: handle + }; + })(); + + // TranslationHandler.js + + var TranslationHandler = (function() { + function handle(connection) { + connection.autoTranslateText = false; + connection.language = 'en'; + connection.googKey = 'AIzaSyCgB5hmFY74WYB-EoWkhr9cAGr6TiTHrEE'; + + // www.RTCMultiConnection.org/docs/Translator/ + connection.Translator = { + TranslateText: function(text, callback) { + // if(location.protocol === 'https:') return callback(text); + + var newScript = document.createElement('script'); + newScript.type = 'text/javascript'; + + var sourceText = encodeURIComponent(text); // escape + + var randomNumber = 'method' + connection.token(); + window[randomNumber] = function(response) { + if (response.data && response.data.translations[0] && callback) { + callback(response.data.translations[0].translatedText); + return; + } + + if (response.error && response.error.message === 'Daily Limit Exceeded') { + console.error('Text translation failed. Error message: "Daily Limit Exceeded."'); + return; + } + + if (response.error) { + console.error(response.error.message); + return; + } + + console.error(response); + }; + + var source = 'https://www.googleapis.com/language/translate/v2?key=' + connection.googKey + '&target=' + (connection.language || 'en-US') + '&callback=window.' + randomNumber + '&q=' + sourceText; + newScript.src = source; + document.getElementsByTagName('head')[0].appendChild(newScript); + }, + getListOfLanguages: function(callback) { + var xhr = new XMLHttpRequest(); + xhr.onreadystatechange = function() { + if (xhr.readyState == XMLHttpRequest.DONE) { + var response = JSON.parse(xhr.responseText); + + if (response && response.data && response.data.languages) { + callback(response.data.languages); + return; + } + + if (response.error && response.error.message === 'Daily Limit Exceeded') { + console.error('Text translation failed. Error message: "Daily Limit Exceeded."'); + return; + } + + if (response.error) { + console.error(response.error.message); + return; + } + + console.error(response); + } + } + var url = 'https://www.googleapis.com/language/translate/v2/languages?key=' + connection.googKey + '&target=en'; + xhr.open('GET', url, true); + xhr.send(null); + } + }; + } + + return { + handle: handle + }; + })(); + + (function(connection) { + forceOptions = forceOptions || { + useDefaultDevices: true + }; + + connection.channel = connection.sessionid = (roomid || location.href.replace(/\/|:|#|\?|\$|\^|%|\.|`|~|!|\+|@|\[|\||]|\|*. /g, '').split('\n').join('').split('\r').join('')) + ''; + + var mPeer = new MultiPeers(connection); + + var preventDuplicateOnStreamEvents = {}; + mPeer.onGettingLocalMedia = function(stream, callback) { + callback = callback || function() {}; + + if (preventDuplicateOnStreamEvents[stream.streamid]) { + return; + } + preventDuplicateOnStreamEvents[stream.streamid] = true; + + try { + stream.type = 'local'; + } catch (e) {} + + connection.setStreamEndHandler(stream); + + getRMCMediaElement(stream, function(mediaElement) { + mediaElement.id = stream.streamid; + mediaElement.muted = true; + mediaElement.volume = 0; + + if (connection.attachStreams.indexOf(stream) === -1) { + connection.attachStreams.push(stream); + } + + if (typeof StreamsHandler !== 'undefined') { + StreamsHandler.setHandlers(stream, true, connection); + } + + connection.streamEvents[stream.streamid] = { + stream: stream, + type: 'local', + mediaElement: mediaElement, + userid: connection.userid, + extra: connection.extra, + streamid: stream.streamid, + isAudioMuted: true + }; + + setHarkEvents(connection, connection.streamEvents[stream.streamid]); + setMuteHandlers(connection, connection.streamEvents[stream.streamid]); + + connection.onstream(connection.streamEvents[stream.streamid]); + callback(); + }, connection); + }; + + mPeer.onGettingRemoteMedia = function(stream, remoteUserId) { + try { + stream.type = 'remote'; + } catch (e) {} + + connection.setStreamEndHandler(stream, 'remote-stream'); + + getRMCMediaElement(stream, function(mediaElement) { + mediaElement.id = stream.streamid; + + if (typeof StreamsHandler !== 'undefined') { + StreamsHandler.setHandlers(stream, false, connection); + } + + connection.streamEvents[stream.streamid] = { + stream: stream, + type: 'remote', + userid: remoteUserId, + extra: connection.peers[remoteUserId] ? connection.peers[remoteUserId].extra : {}, + mediaElement: mediaElement, + streamid: stream.streamid + }; + + setMuteHandlers(connection, connection.streamEvents[stream.streamid]); + + connection.onstream(connection.streamEvents[stream.streamid]); + }, connection); + }; + + mPeer.onRemovingRemoteMedia = function(stream, remoteUserId) { + var streamEvent = connection.streamEvents[stream.streamid]; + if (!streamEvent) { + streamEvent = { + stream: stream, + type: 'remote', + userid: remoteUserId, + extra: connection.peers[remoteUserId] ? connection.peers[remoteUserId].extra : {}, + streamid: stream.streamid, + mediaElement: connection.streamEvents[stream.streamid] ? connection.streamEvents[stream.streamid].mediaElement : null + }; + } + + if (connection.peersBackup[streamEvent.userid]) { + streamEvent.extra = connection.peersBackup[streamEvent.userid].extra; + } + + connection.onstreamended(streamEvent); + + delete connection.streamEvents[stream.streamid]; + }; + + mPeer.onNegotiationNeeded = function(message, remoteUserId, callback) { + remoteUserId = remoteUserId || message.remoteUserId; + message = message || ''; + connectSocket(function() { + connection.socket.emit(connection.socketMessageEvent, typeof message.password !== 'undefined' ? message : { + remoteUserId: remoteUserId, + message: message, + sender: connection.userid + }, callback || function() {}); + }); + }; + + function onUserLeft(remoteUserId) { + connection.deletePeer(remoteUserId); + } + + mPeer.onUserLeft = onUserLeft; + mPeer.disconnectWith = function(remoteUserId, callback) { + if (connection.socket) { + connection.socket.emit('disconnect-with', remoteUserId, callback || function() {}); + } + + connection.deletePeer(remoteUserId); + }; + + connection.socketOptions = { + // 'force new connection': true, // For SocketIO version < 1.0 + // 'forceNew': true, // For SocketIO version >= 1.0 + 'transport': 'polling' // fixing transport:unknown issues + }; + + function connectSocket(connectCallback) { + connection.socketAutoReConnect = true; + + if (connection.socket) { // todo: check here readySate/etc. to make sure socket is still opened + if (connectCallback) { + connectCallback(connection.socket); + } + return; + } + + if (typeof SocketConnection === 'undefined') { + if (typeof FirebaseConnection !== 'undefined') { + window.SocketConnection = FirebaseConnection; + } else if (typeof PubNubConnection !== 'undefined') { + window.SocketConnection = PubNubConnection; + } else { + throw 'SocketConnection.js seems missed.'; + } + } + + new SocketConnection(connection, function(s) { + if (connectCallback) { + connectCallback(connection.socket); + } + }); + } + + // 1st paramter is roomid + // 2nd paramter can be either password or a callback function + // 3rd paramter is a callback function + connection.openOrJoin = function(roomid, password, callback) { + callback = callback || function() {}; + + connection.checkPresence(roomid, function(isRoomExist, roomid) { + // i.e. 2nd parameter is a callback function + if (typeof password === 'function' && typeof password !== 'undefined') { + callback = password; // switch callback functions + password = null; + } + + if (!password && !!connection.password) { + password = connection.password; + } + + if (isRoomExist) { + connection.sessionid = roomid; + + var localPeerSdpConstraints = false; + var remotePeerSdpConstraints = false; + var isOneWay = !!connection.session.oneway; + var isDataOnly = isData(connection.session); + + remotePeerSdpConstraints = { + OfferToReceiveAudio: connection.sdpConstraints.mandatory.OfferToReceiveAudio, + OfferToReceiveVideo: connection.sdpConstraints.mandatory.OfferToReceiveVideo + } + + localPeerSdpConstraints = { + OfferToReceiveAudio: isOneWay ? !!connection.session.audio : connection.sdpConstraints.mandatory.OfferToReceiveAudio, + OfferToReceiveVideo: isOneWay ? !!connection.session.video || !!connection.session.screen : connection.sdpConstraints.mandatory.OfferToReceiveVideo + } + + var connectionDescription = { + remoteUserId: connection.sessionid, + message: { + newParticipationRequest: true, + isOneWay: isOneWay, + isDataOnly: isDataOnly, + localPeerSdpConstraints: localPeerSdpConstraints, + remotePeerSdpConstraints: remotePeerSdpConstraints + }, + sender: connection.userid, + password: password || false + }; + + beforeJoin(connectionDescription.message, function() { + joinRoom(connectionDescription, password, function() {}); + }); + return; + } + + connection.waitingForLocalMedia = true; + connection.isInitiator = true; + + // var oldUserId = connection.userid; + // connection.userid = + connection.sessionid = roomid || connection.sessionid; + // connection.userid += ''; + + // connection.socket.emit('changed-uuid', connection.userid); + + if (isData(connection.session)) { + openRoom(callback, password); + return; + } + + connection.captureUserMedia(function() { + openRoom(callback, password); + }); + }); + }; + + // don't allow someone to join this person until he has the media + connection.waitingForLocalMedia = false; + + connection.open = function(roomid, isPublicModerator, callback) { + connection.waitingForLocalMedia = true; + connection.isInitiator = true; + + callback = callback || function() {}; + if (typeof isPublicModerator === 'function') { + callback = isPublicModerator; + isPublicModerator = false; + } + + // var oldUserId = connection.userid; + // connection.userid = + connection.sessionid = roomid || connection.sessionid; + // connection.userid += ''; + + connectSocket(function() { + // connection.socket.emit('changed-uuid', connection.userid); + + if (isPublicModerator == true) { + connection.becomePublicModerator(); + } + + if (isData(connection.session)) { + openRoom(callback, connection.password); + return; + } + + connection.captureUserMedia(function() { + openRoom(callback, connection.password); + }); + }); + }; + + connection.becomePublicModerator = function() { + if (!connection.isInitiator) return; + connection.socket.emit('become-a-public-moderator'); + }; + + connection.dontMakeMeModerator = function() { + connection.socket.emit('dont-make-me-moderator'); + }; + + // this object keeps extra-data records for all connected users + // this object is never cleared so you can always access extra-data even if a user left + connection.peersBackup = {}; + + connection.deletePeer = function(remoteUserId) { + if (!remoteUserId || !connection.peers[remoteUserId]) { + return; + } + + var eventObject = { + userid: remoteUserId, + extra: connection.peers[remoteUserId] ? connection.peers[remoteUserId].extra : {} + }; + + if (connection.peersBackup[eventObject.userid]) { + eventObject.extra = connection.peersBackup[eventObject.userid].extra; + } + + connection.onleave(eventObject); + + if (!!connection.peers[remoteUserId]) { + connection.peers[remoteUserId].streams.forEach(function(stream) { + stream.stop(); + }); + + var peer = connection.peers[remoteUserId].peer; + if (peer && peer.iceConnectionState !== 'closed') { + try { + peer.close(); + } catch (e) {} + } + + if (connection.peers[remoteUserId]) { + connection.peers[remoteUserId].peer = null; + delete connection.peers[remoteUserId]; + } + } + } + + connection.rejoin = function(connectionDescription) { + if (connection.isInitiator || !connectionDescription || !Object.keys(connectionDescription).length) { + return; + } + + var extra = {}; + + if (connection.peers[connectionDescription.remoteUserId]) { + extra = connection.peers[connectionDescription.remoteUserId].extra; + connection.deletePeer(connectionDescription.remoteUserId); + } + + if (connectionDescription && connectionDescription.remoteUserId) { + connection.join(connectionDescription.remoteUserId); + + connection.onReConnecting({ + userid: connectionDescription.remoteUserId, + extra: extra + }); + } + }; + + connection.join = connection.connect = function(remoteUserId, options) { + connection.sessionid = (remoteUserId ? remoteUserId.sessionid || remoteUserId.remoteUserId || remoteUserId : false) || connection.sessionid; + connection.sessionid += ''; + + var localPeerSdpConstraints = false; + var remotePeerSdpConstraints = false; + var isOneWay = false; + var isDataOnly = false; + + if ((remoteUserId && remoteUserId.session) || !remoteUserId || typeof remoteUserId === 'string') { + var session = remoteUserId ? remoteUserId.session || connection.session : connection.session; + + isOneWay = !!session.oneway; + isDataOnly = isData(session); + + remotePeerSdpConstraints = { + OfferToReceiveAudio: connection.sdpConstraints.mandatory.OfferToReceiveAudio, + OfferToReceiveVideo: connection.sdpConstraints.mandatory.OfferToReceiveVideo + }; + + localPeerSdpConstraints = { + OfferToReceiveAudio: isOneWay ? !!connection.session.audio : connection.sdpConstraints.mandatory.OfferToReceiveAudio, + OfferToReceiveVideo: isOneWay ? !!connection.session.video || !!connection.session.screen : connection.sdpConstraints.mandatory.OfferToReceiveVideo + }; + } + + options = options || {}; + + var cb = function() {}; + if (typeof options === 'function') { + cb = options; + options = {}; + } + + if (typeof options.localPeerSdpConstraints !== 'undefined') { + localPeerSdpConstraints = options.localPeerSdpConstraints; + } + + if (typeof options.remotePeerSdpConstraints !== 'undefined') { + remotePeerSdpConstraints = options.remotePeerSdpConstraints; + } + + if (typeof options.isOneWay !== 'undefined') { + isOneWay = options.isOneWay; + } + + if (typeof options.isDataOnly !== 'undefined') { + isDataOnly = options.isDataOnly; + } + + var connectionDescription = { + remoteUserId: connection.sessionid, + message: { + newParticipationRequest: true, + isOneWay: isOneWay, + isDataOnly: isDataOnly, + localPeerSdpConstraints: localPeerSdpConstraints, + remotePeerSdpConstraints: remotePeerSdpConstraints + }, + sender: connection.userid, + password: connection.password || false + }; + + beforeJoin(connectionDescription.message, function() { + connectSocket(function() { + joinRoom(connectionDescription, connection.password, cb); + }); + }); + return connectionDescription; + }; + + function joinRoom(connectionDescription, password, cb) { + if (password && (typeof password === 'function' || password.prototype || typeof password === 'object')) { + password = null; + } + + connection.socket.emit('join-room', { + sessionid: connection.sessionid, + session: connection.session, + mediaConstraints: connection.mediaConstraints, + sdpConstraints: connection.sdpConstraints, + streams: getStreamInfoForAdmin(), + extra: connection.extra, + password: typeof password !== 'undefined' && typeof password !== 'object' ? (password || onnection.password) : '' + }, function(isRoomJoined, error) { + if (isRoomJoined === true) { + if (connection.enableLogs) { + console.log('isRoomJoined: ', isRoomJoined, ' roomid: ', connection.sessionid); + } + + if (!!connection.peers[connection.sessionid]) { + // on socket disconnect & reconnect + return; + } + + mPeer.onNegotiationNeeded(connectionDescription); + cb(); + } + + if (isRoomJoined === false) { + if (connection.enableLogs) { + console.warn('isRoomJoined: ', error, ' roomid: ', connection.sessionid); + } + + // retry after 3 seconds + setTimeout(function() { + joinRoom(connectionDescription, password, cb); + }, 3000); + } + }); + } + + function openRoom(callback, password) { + if (password && (typeof password === 'function' || password.prototype || typeof password === 'object')) { + password = null; + } + + connection.waitingForLocalMedia = false; + connection.socket.emit('open-room', { + sessionid: connection.sessionid, + session: connection.session, + mediaConstraints: connection.mediaConstraints, + sdpConstraints: connection.sdpConstraints, + streams: getStreamInfoForAdmin(), + extra: connection.extra, + password: typeof password !== 'undefined' && typeof password !== 'object' ? (password || onnection.password) : '' + }, function(isRoomOpened, error) { + if (isRoomOpened === true) { + if (connection.enableLogs) { + console.log('isRoomOpened: ', isRoomOpened, ' roomid: ', connection.sessionid); + } + callback(isRoomOpened, connection.sessionid); + } + + if (isRoomOpened === false) { + if (connection.enableLogs) { + console.warn('isRoomOpened: ', error, ' roomid: ', connection.sessionid); + } + } + }); + } + + function getStreamInfoForAdmin() { + try { + return connection.streamEvents.selectAll('local').map(function(event) { + return { + streamid: event.streamid, + tracks: event.stream.getTracks().length + }; + }); + } catch (e) { + return []; + } + } + + function beforeJoin(userPreferences, callback) { + if (connection.dontCaptureUserMedia || userPreferences.isDataOnly) { + callback(); + return; + } + + var localMediaConstraints = {}; + + if (userPreferences.localPeerSdpConstraints.OfferToReceiveAudio) { + localMediaConstraints.audio = connection.mediaConstraints.audio; + } + + if (userPreferences.localPeerSdpConstraints.OfferToReceiveVideo) { + localMediaConstraints.video = connection.mediaConstraints.video; + } + + var session = userPreferences.session || connection.session; + + if (session.oneway && session.audio !== 'two-way' && session.video !== 'two-way' && session.screen !== 'two-way') { + callback(); + return; + } + + if (session.oneway && session.audio && session.audio === 'two-way') { + session = { + audio: true + }; + } + + if (session.audio || session.video || session.screen) { + if (session.screen) { + if (DetectRTC.browser.name === 'Edge') { + navigator.getDisplayMedia({ + video: true, + audio: isAudioPlusTab(connection) + }).then(function(screen) { + screen.isScreen = true; + mPeer.onGettingLocalMedia(screen); + + if ((session.audio || session.video) && !isAudioPlusTab(connection)) { + connection.invokeGetUserMedia(null, callback); + } else { + callback(screen); + } + }, function(error) { + console.error('Unable to capture screen on Edge. HTTPs and version 17+ is required.'); + }); + } else { + connection.getScreenConstraints(function(error, screen_constraints) { + connection.invokeGetUserMedia({ + audio: isAudioPlusTab(connection) ? getAudioScreenConstraints(screen_constraints) : false, + video: screen_constraints, + isScreen: true + }, (session.audio || session.video) && !isAudioPlusTab(connection) ? connection.invokeGetUserMedia(null, callback) : callback); + }); + } + } else if (session.audio || session.video) { + connection.invokeGetUserMedia(null, callback, session); + } + } + } + + connection.getUserMedia = connection.captureUserMedia = function(callback, sessionForced) { + callback = callback || function() {}; + var session = sessionForced || connection.session; + + if (connection.dontCaptureUserMedia || isData(session)) { + callback(); + return; + } + + if (session.audio || session.video || session.screen) { + if (session.screen) { + if (DetectRTC.browser.name === 'Edge') { + navigator.getDisplayMedia({ + video: true, + audio: isAudioPlusTab(connection) + }).then(function(screen) { + screen.isScreen = true; + mPeer.onGettingLocalMedia(screen); + + if ((session.audio || session.video) && !isAudioPlusTab(connection)) { + var nonScreenSession = {}; + for (var s in session) { + if (s !== 'screen') { + nonScreenSession[s] = session[s]; + } + } + connection.invokeGetUserMedia(sessionForced, callback, nonScreenSession); + return; + } + callback(screen); + }, function(error) { + console.error('Unable to capture screen on Edge. HTTPs and version 17+ is required.'); + }); + } else { + connection.getScreenConstraints(function(error, screen_constraints) { + if (error) { + throw error; + } + + connection.invokeGetUserMedia({ + audio: isAudioPlusTab(connection) ? getAudioScreenConstraints(screen_constraints) : false, + video: screen_constraints, + isScreen: true + }, function(stream) { + if ((session.audio || session.video) && !isAudioPlusTab(connection)) { + var nonScreenSession = {}; + for (var s in session) { + if (s !== 'screen') { + nonScreenSession[s] = session[s]; + } + } + connection.invokeGetUserMedia(sessionForced, callback, nonScreenSession); + return; + } + callback(stream); + }); + }); + } + } else if (session.audio || session.video) { + connection.invokeGetUserMedia(sessionForced, callback, session); + } + } + }; + + connection.onbeforeunload = function(arg1, dontCloseSocket) { + if (!connection.closeBeforeUnload) { + return; + } + + if (connection.isInitiator === true) { + connection.dontMakeMeModerator(); + } + + connection.peers.getAllParticipants().forEach(function(participant) { + mPeer.onNegotiationNeeded({ + userLeft: true + }, participant); + + if (connection.peers[participant] && connection.peers[participant].peer) { + connection.peers[participant].peer.close(); + } + + delete connection.peers[participant]; + }); + + if (!dontCloseSocket) { + connection.closeSocket(); + } + + connection.isInitiator = false; + }; + + if (!window.ignoreBeforeUnload) { + // user can implement its own version of window.onbeforeunload + connection.closeBeforeUnload = true; + window.addEventListener('beforeunload', connection.onbeforeunload, false); + } else { + connection.closeBeforeUnload = false; + } + + connection.userid = getRandomString(); + connection.changeUserId = function(newUserId, callback) { + callback = callback || function() {}; + connection.userid = newUserId || getRandomString(); + connection.socket.emit('changed-uuid', connection.userid, callback); + }; + + connection.extra = {}; + connection.attachStreams = []; + + connection.session = { + audio: true, + video: true + }; + + connection.enableFileSharing = false; + + // all values in kbps + connection.bandwidth = { + screen: false, + audio: false, + video: false + }; + + connection.codecs = { + audio: 'opus', + video: 'VP9' + }; + + connection.processSdp = function(sdp) { + if (DetectRTC.browser.name === 'Safari') { + return sdp; + } + + if (connection.codecs.video.toUpperCase() === 'VP8') { + sdp = CodecsHandler.preferCodec(sdp, 'vp8'); + } + + if (connection.codecs.video.toUpperCase() === 'VP9') { + sdp = CodecsHandler.preferCodec(sdp, 'vp9'); + } + + if (connection.codecs.video.toUpperCase() === 'H264') { + sdp = CodecsHandler.preferCodec(sdp, 'h264'); + } + + if (connection.codecs.audio === 'G722') { + sdp = CodecsHandler.removeNonG722(sdp); + } + + if (DetectRTC.browser.name === 'Firefox') { + return sdp; + } + + if (connection.bandwidth.video || connection.bandwidth.screen) { + sdp = CodecsHandler.setApplicationSpecificBandwidth(sdp, connection.bandwidth, !!connection.session.screen); + } + + if (connection.bandwidth.video) { + sdp = CodecsHandler.setVideoBitrates(sdp, { + min: connection.bandwidth.video * 8 * 1024, + max: connection.bandwidth.video * 8 * 1024 + }); + } + + if (connection.bandwidth.audio) { + sdp = CodecsHandler.setOpusAttributes(sdp, { + maxaveragebitrate: connection.bandwidth.audio * 8 * 1024, + maxplaybackrate: connection.bandwidth.audio * 8 * 1024, + stereo: 1, + maxptime: 3 + }); + } + + return sdp; + }; + + if (typeof CodecsHandler !== 'undefined') { + connection.BandwidthHandler = connection.CodecsHandler = CodecsHandler; + } + + connection.mediaConstraints = { + audio: { + mandatory: {}, + optional: connection.bandwidth.audio ? [{ + bandwidth: connection.bandwidth.audio * 8 * 1024 || 128 * 8 * 1024 + }] : [] + }, + video: { + mandatory: {}, + optional: connection.bandwidth.video ? [{ + bandwidth: connection.bandwidth.video * 8 * 1024 || 128 * 8 * 1024 + }, { + facingMode: 'user' + }] : [{ + facingMode: 'user' + }] + } + }; + + if (DetectRTC.browser.name === 'Firefox') { + connection.mediaConstraints = { + audio: true, + video: true + }; + } + + if (!forceOptions.useDefaultDevices && !DetectRTC.isMobileDevice) { + DetectRTC.load(function() { + var lastAudioDevice, lastVideoDevice; + // it will force RTCMultiConnection to capture last-devices + // i.e. if external microphone is attached to system, we should prefer it over built-in devices. + DetectRTC.MediaDevices.forEach(function(device) { + if (device.kind === 'audioinput' && connection.mediaConstraints.audio !== false) { + lastAudioDevice = device; + } + + if (device.kind === 'videoinput' && connection.mediaConstraints.video !== false) { + lastVideoDevice = device; + } + }); + + if (lastAudioDevice) { + if (DetectRTC.browser.name === 'Firefox') { + if (connection.mediaConstraints.audio !== true) { + connection.mediaConstraints.audio.deviceId = lastAudioDevice.id; + } else { + connection.mediaConstraints.audio = { + deviceId: lastAudioDevice.id + } + } + return; + } + + if (connection.mediaConstraints.audio == true) { + connection.mediaConstraints.audio = { + mandatory: {}, + optional: [] + } + } + + if (!connection.mediaConstraints.audio.optional) { + connection.mediaConstraints.audio.optional = []; + } + + var optional = [{ + sourceId: lastAudioDevice.id + }]; + + connection.mediaConstraints.audio.optional = optional.concat(connection.mediaConstraints.audio.optional); + } + + if (lastVideoDevice) { + if (DetectRTC.browser.name === 'Firefox') { + if (connection.mediaConstraints.video !== true) { + connection.mediaConstraints.video.deviceId = lastVideoDevice.id; + } else { + connection.mediaConstraints.video = { + deviceId: lastVideoDevice.id + } + } + return; + } + + if (connection.mediaConstraints.video == true) { + connection.mediaConstraints.video = { + mandatory: {}, + optional: [] + } + } + + if (!connection.mediaConstraints.video.optional) { + connection.mediaConstraints.video.optional = []; + } + + var optional = [{ + sourceId: lastVideoDevice.id + }]; + + connection.mediaConstraints.video.optional = optional.concat(connection.mediaConstraints.video.optional); + } + }); + } + + connection.sdpConstraints = { + mandatory: { + OfferToReceiveAudio: true, + OfferToReceiveVideo: true + }, + optional: [{ + VoiceActivityDetection: false + }] + }; + + connection.rtcpMuxPolicy = 'require'; // "require" or "negotiate" + connection.iceTransportPolicy = null; // "relay" or "all" + connection.optionalArgument = { + optional: [{ + DtlsSrtpKeyAgreement: true + }, { + googImprovedWifiBwe: true + }, { + googScreencastMinBitrate: 300 + }, { + googIPv6: true + }, { + googDscp: true + }, { + googCpuUnderuseThreshold: 55 + }, { + googCpuOveruseThreshold: 85 + }, { + googSuspendBelowMinBitrate: true + }, { + googCpuOveruseDetection: true + }], + mandatory: {} + }; + + connection.iceServers = IceServersHandler.getIceServers(connection); + + connection.candidates = { + host: true, + stun: true, + turn: true + }; + + connection.iceProtocols = { + tcp: true, + udp: true + }; + + // EVENTs + connection.onopen = function(event) { + if (!!connection.enableLogs) { + console.info('Data connection has been opened between you & ', event.userid); + } + }; + + connection.onclose = function(event) { + if (!!connection.enableLogs) { + console.warn('Data connection has been closed between you & ', event.userid); + } + }; + + connection.onerror = function(error) { + if (!!connection.enableLogs) { + console.error(error.userid, 'data-error', error); + } + }; + + connection.onmessage = function(event) { + if (!!connection.enableLogs) { + console.debug('data-message', event.userid, event.data); + } + }; + + connection.send = function(data, remoteUserId) { + connection.peers.send(data, remoteUserId); + }; + + connection.close = connection.disconnect = connection.leave = function() { + connection.onbeforeunload(false, true); + }; + + connection.closeEntireSession = function(callback) { + callback = callback || function() {}; + connection.socket.emit('close-entire-session', function looper() { + if (connection.getAllParticipants().length) { + setTimeout(looper, 100); + return; + } + + connection.onEntireSessionClosed({ + sessionid: connection.sessionid, + userid: connection.userid, + extra: connection.extra + }); + + connection.changeUserId(null, function() { + connection.close(); + callback(); + }); + }); + }; + + connection.onEntireSessionClosed = function(event) { + if (!connection.enableLogs) return; + console.info('Entire session is closed: ', event.sessionid, event.extra); + }; + + connection.onstream = function(e) { + var parentNode = connection.videosContainer; + parentNode.insertBefore(e.mediaElement, parentNode.firstChild); + var played = e.mediaElement.play(); + + if (typeof played !== 'undefined') { + played.catch(function() { /*** iOS 11 doesn't allow automatic play and rejects ***/ }).then(function() { + setTimeout(function() { + e.mediaElement.play(); + }, 2000); + }); + return; + } + + setTimeout(function() { + e.mediaElement.play(); + }, 2000); + }; + + connection.onstreamended = function(e) { + if (!e.mediaElement) { + e.mediaElement = document.getElementById(e.streamid); + } + + if (!e.mediaElement || !e.mediaElement.parentNode) { + return; + } + + e.mediaElement.parentNode.removeChild(e.mediaElement); + }; + + connection.direction = 'many-to-many'; + + connection.removeStream = function(streamid, remoteUserId) { + var stream; + connection.attachStreams.forEach(function(localStream) { + if (localStream.id === streamid) { + stream = localStream; + } + }); + + if (!stream) { + console.warn('No such stream exist.', streamid); + return; + } + + connection.peers.getAllParticipants().forEach(function(participant) { + if (remoteUserId && participant !== remoteUserId) { + return; + } + + var user = connection.peers[participant]; + try { + user.peer.removeStream(stream); + } catch (e) {} + }); + + connection.renegotiate(); + }; + + connection.addStream = function(session, remoteUserId) { + if (!!session.getAudioTracks) { + if (connection.attachStreams.indexOf(session) === -1) { + if (!session.streamid) { + session.streamid = session.id; + } + + connection.attachStreams.push(session); + } + connection.renegotiate(remoteUserId); + return; + } + + if (isData(session)) { + connection.renegotiate(remoteUserId); + return; + } + + if (session.audio || session.video || session.screen) { + if (session.screen) { + if (DetectRTC.browser.name === 'Edge') { + navigator.getDisplayMedia({ + video: true, + audio: isAudioPlusTab(connection) + }).then(function(screen) { + screen.isScreen = true; + mPeer.onGettingLocalMedia(screen); + + if ((session.audio || session.video) && !isAudioPlusTab(connection)) { + connection.invokeGetUserMedia(null, function(stream) { + gumCallback(stream); + }); + } else { + gumCallback(screen); + } + }, function(error) { + console.error('Unable to capture screen on Edge. HTTPs and version 17+ is required.'); + }); + } else { + connection.getScreenConstraints(function(error, screen_constraints) { + if (error) { + if (error === 'PermissionDeniedError') { + if (session.streamCallback) { + session.streamCallback(null); + } + if (connection.enableLogs) { + console.error('User rejected to share his screen.'); + } + return; + } + return alert(error); + } + + connection.invokeGetUserMedia({ + audio: isAudioPlusTab(connection) ? getAudioScreenConstraints(screen_constraints) : false, + video: screen_constraints, + isScreen: true + }, function(stream) { + if ((session.audio || session.video) && !isAudioPlusTab(connection)) { + connection.invokeGetUserMedia(null, function(stream) { + gumCallback(stream); + }); + } else { + gumCallback(stream); + } + }); + }); + } + } else if (session.audio || session.video) { + connection.invokeGetUserMedia(null, gumCallback); + } + } + + function gumCallback(stream) { + if (session.streamCallback) { + session.streamCallback(stream); + } + + connection.renegotiate(remoteUserId); + } + }; + + connection.invokeGetUserMedia = function(localMediaConstraints, callback, session) { + if (!session) { + session = connection.session; + } + + if (!localMediaConstraints) { + localMediaConstraints = connection.mediaConstraints; + } + + getUserMediaHandler({ + onGettingLocalMedia: function(stream) { + var videoConstraints = localMediaConstraints.video; + if (videoConstraints) { + if (videoConstraints.mediaSource || videoConstraints.mozMediaSource) { + stream.isScreen = true; + } else if (videoConstraints.mandatory && videoConstraints.mandatory.chromeMediaSource) { + stream.isScreen = true; + } + } + + if (!stream.isScreen) { + stream.isVideo = stream.getVideoTracks().length; + stream.isAudio = !stream.isVideo && stream.getAudioTracks().length; + } + + mPeer.onGettingLocalMedia(stream, function() { + if (typeof callback === 'function') { + callback(stream); + } + }); + }, + onLocalMediaError: function(error, constraints) { + mPeer.onLocalMediaError(error, constraints); + }, + localMediaConstraints: localMediaConstraints || { + audio: session.audio ? localMediaConstraints.audio : false, + video: session.video ? localMediaConstraints.video : false + } + }); + }; + + function applyConstraints(stream, mediaConstraints) { + if (!stream) { + if (!!connection.enableLogs) { + console.error('No stream to applyConstraints.'); + } + return; + } + + if (mediaConstraints.audio) { + stream.getAudioTracks().forEach(function(track) { + track.applyConstraints(mediaConstraints.audio); + }); + } + + if (mediaConstraints.video) { + stream.getVideoTracks().forEach(function(track) { + track.applyConstraints(mediaConstraints.video); + }); + } + } + + connection.applyConstraints = function(mediaConstraints, streamid) { + if (!MediaStreamTrack || !MediaStreamTrack.prototype.applyConstraints) { + alert('track.applyConstraints is NOT supported in your browser.'); + return; + } + + if (streamid) { + var stream; + if (connection.streamEvents[streamid]) { + stream = connection.streamEvents[streamid].stream; + } + applyConstraints(stream, mediaConstraints); + return; + } + + connection.attachStreams.forEach(function(stream) { + applyConstraints(stream, mediaConstraints); + }); + }; + + function replaceTrack(track, remoteUserId, isVideoTrack) { + if (remoteUserId) { + mPeer.replaceTrack(track, remoteUserId, isVideoTrack); + return; + } + + connection.peers.getAllParticipants().forEach(function(participant) { + mPeer.replaceTrack(track, participant, isVideoTrack); + }); + } + + connection.replaceTrack = function(session, remoteUserId, isVideoTrack) { + session = session || {}; + + if (!RTCPeerConnection.prototype.getSenders) { + connection.addStream(session); + return; + } + + if (session instanceof MediaStreamTrack) { + replaceTrack(session, remoteUserId, isVideoTrack); + return; + } + + if (session instanceof MediaStream) { + if (session.getVideoTracks().length) { + replaceTrack(session.getVideoTracks()[0], remoteUserId, true); + } + + if (session.getAudioTracks().length) { + replaceTrack(session.getAudioTracks()[0], remoteUserId, false); + } + return; + } + + if (isData(session)) { + throw 'connection.replaceTrack requires audio and/or video and/or screen.'; + return; + } + + if (session.audio || session.video || session.screen) { + if (session.screen) { + if (DetectRTC.browser.name === 'Edge') { + navigator.getDisplayMedia({ + video: true, + audio: isAudioPlusTab(connection) + }).then(function(screen) { + screen.isScreen = true; + mPeer.onGettingLocalMedia(screen); + + if ((session.audio || session.video) && !isAudioPlusTab(connection)) { + connection.invokeGetUserMedia(null, gumCallback); + } else { + gumCallback(screen); + } + }, function(error) { + console.error('Unable to capture screen on Edge. HTTPs and version 17+ is required.'); + }); + } else { + connection.getScreenConstraints(function(error, screen_constraints) { + if (error) { + return alert(error); + } + + connection.invokeGetUserMedia({ + audio: isAudioPlusTab(connection) ? getAudioScreenConstraints(screen_constraints) : false, + video: screen_constraints, + isScreen: true + }, (session.audio || session.video) && !isAudioPlusTab(connection) ? connection.invokeGetUserMedia(null, gumCallback) : gumCallback); + }); + } + } else if (session.audio || session.video) { + connection.invokeGetUserMedia(null, gumCallback); + } + } + + function gumCallback(stream) { + connection.replaceTrack(stream, remoteUserId, isVideoTrack || session.video || session.screen); + } + }; + + connection.resetTrack = function(remoteUsersIds, isVideoTrack) { + if (!remoteUsersIds) { + remoteUsersIds = connection.getAllParticipants(); + } + + if (typeof remoteUsersIds == 'string') { + remoteUsersIds = [remoteUsersIds]; + } + + remoteUsersIds.forEach(function(participant) { + var peer = connection.peers[participant].peer; + + if ((typeof isVideoTrack === 'undefined' || isVideoTrack === true) && peer.lastVideoTrack) { + connection.replaceTrack(peer.lastVideoTrack, participant, true); + } + + if ((typeof isVideoTrack === 'undefined' || isVideoTrack === false) && peer.lastAudioTrack) { + connection.replaceTrack(peer.lastAudioTrack, participant, false); + } + }); + }; + + connection.renegotiate = function(remoteUserId) { + if (remoteUserId) { + mPeer.renegotiatePeer(remoteUserId); + return; + } + + connection.peers.getAllParticipants().forEach(function(participant) { + mPeer.renegotiatePeer(participant); + }); + }; + + connection.setStreamEndHandler = function(stream, isRemote) { + if (!stream || !stream.addEventListener) return; + + isRemote = !!isRemote; + + if (stream.alreadySetEndHandler) { + return; + } + stream.alreadySetEndHandler = true; + + var streamEndedEvent = 'ended'; + + if ('oninactive' in stream) { + streamEndedEvent = 'inactive'; + } + + stream.addEventListener(streamEndedEvent, function() { + if (stream.idInstance) { + currentUserMediaRequest.remove(stream.idInstance); + } + + if (!isRemote) { + // reset attachStreams + var streams = []; + connection.attachStreams.forEach(function(s) { + if (s.id != stream.id) { + streams.push(s); + } + }); + connection.attachStreams = streams; + } + + // connection.renegotiate(); + + var streamEvent = connection.streamEvents[stream.streamid]; + if (!streamEvent) { + streamEvent = { + stream: stream, + streamid: stream.streamid, + type: isRemote ? 'remote' : 'local', + userid: connection.userid, + extra: connection.extra, + mediaElement: connection.streamEvents[stream.streamid] ? connection.streamEvents[stream.streamid].mediaElement : null + }; + } + + if (isRemote && connection.peers[streamEvent.userid]) { + // reset remote "streams" + var peer = connection.peers[streamEvent.userid].peer; + var streams = []; + peer.getRemoteStreams().forEach(function(s) { + if (s.id != stream.id) { + streams.push(s); + } + }); + connection.peers[streamEvent.userid].streams = streams; + } + + if (streamEvent.userid === connection.userid && streamEvent.type === 'remote') { + return; + } + + if (connection.peersBackup[streamEvent.userid]) { + streamEvent.extra = connection.peersBackup[streamEvent.userid].extra; + } + + connection.onstreamended(streamEvent); + + delete connection.streamEvents[stream.streamid]; + }, false); + }; + + connection.onMediaError = function(error, constraints) { + if (!!connection.enableLogs) { + console.error(error, constraints); + } + }; + + connection.autoCloseEntireSession = false; + + connection.filesContainer = connection.videosContainer = document.body || document.documentElement; + connection.isInitiator = false; + + connection.shareFile = mPeer.shareFile; + if (typeof FileProgressBarHandler !== 'undefined') { + FileProgressBarHandler.handle(connection); + } + + if (typeof TranslationHandler !== 'undefined') { + TranslationHandler.handle(connection); + } + + connection.token = getRandomString; + + connection.onNewParticipant = function(participantId, userPreferences) { + connection.acceptParticipationRequest(participantId, userPreferences); + }; + + connection.acceptParticipationRequest = function(participantId, userPreferences) { + if (userPreferences.successCallback) { + userPreferences.successCallback(); + delete userPreferences.successCallback; + } + + mPeer.createNewPeer(participantId, userPreferences); + }; + + if (typeof StreamsHandler !== 'undefined') { + connection.StreamsHandler = StreamsHandler; + } + + connection.onleave = function(userid) {}; + + connection.invokeSelectFileDialog = function(callback) { + var selector = new FileSelector(); + selector.accept = '*.*'; + selector.selectSingleFile(callback); + }; + + connection.getPublicModerators = connection.getPublicUsers = function(userIdStartsWith, callback) { + if (typeof userIdStartsWith === 'function') { + callback = userIdStartsWith; + } + + connectSocket(function() { + connection.socket.emit( + 'get-public-moderators', + typeof userIdStartsWith === 'string' ? userIdStartsWith : '', + callback + ); + }); + }; + + connection.onmute = function(e) { + if (!e || !e.mediaElement) { + return; + } + + if (e.muteType === 'both' || e.muteType === 'video') { + e.mediaElement.src = null; + var paused = e.mediaElement.pause(); + if (typeof paused !== 'undefined') { + paused.then(function() { + e.mediaElement.poster = e.snapshot || 'https://cdn.webrtc-experiment.com/images/muted.png'; + }); + } else { + e.mediaElement.poster = e.snapshot || 'https://cdn.webrtc-experiment.com/images/muted.png'; + } + } else if (e.muteType === 'audio') { + e.mediaElement.muted = true; + } + }; + + connection.onunmute = function(e) { + if (!e || !e.mediaElement || !e.stream) { + return; + } + + if (e.unmuteType === 'both' || e.unmuteType === 'video') { + e.mediaElement.poster = null; + e.mediaElement.srcObject = e.stream; + e.mediaElement.play(); + } else if (e.unmuteType === 'audio') { + e.mediaElement.muted = false; + } + }; + + connection.onExtraDataUpdated = function(event) { + event.status = 'online'; + connection.onUserStatusChanged(event, true); + }; + + connection.onJoinWithPassword = function(remoteUserId) { + console.warn(remoteUserId, 'is password protected. Please join with password.'); + }; + + connection.onInvalidPassword = function(remoteUserId, oldPassword) { + console.warn(remoteUserId, 'is password protected. Please join with valid password. Your old password', oldPassword, 'is wrong.'); + }; + + connection.onPasswordMaxTriesOver = function(remoteUserId) { + console.warn(remoteUserId, 'is password protected. Your max password tries exceeded the limit.'); + }; + + connection.getAllParticipants = function(sender) { + return connection.peers.getAllParticipants(sender); + }; + + if (typeof StreamsHandler !== 'undefined') { + StreamsHandler.onSyncNeeded = function(streamid, action, type) { + connection.peers.getAllParticipants().forEach(function(participant) { + mPeer.onNegotiationNeeded({ + streamid: streamid, + action: action, + streamSyncNeeded: true, + type: type || 'both' + }, participant); + }); + }; + } + + connection.connectSocket = function(callback) { + connectSocket(callback); + }; + + connection.closeSocket = function() { + try { + io.sockets = {}; + } catch (e) {}; + + if (!connection.socket) return; + + if (typeof connection.socket.disconnect === 'function') { + connection.socket.disconnect(); + } + + if (typeof connection.socket.resetProps === 'function') { + connection.socket.resetProps(); + } + + connection.socket = null; + }; + + connection.getSocket = function(callback) { + if (!connection.socket) { + connectSocket(callback); + } else if (callback) { + callback(connection.socket); + } + + return connection.socket; + }; + + connection.getRemoteStreams = mPeer.getRemoteStreams; + + var skipStreams = ['selectFirst', 'selectAll', 'forEach']; + + connection.streamEvents = { + selectFirst: function(options) { + return connection.streamEvents.selectAll(options)[0]; + }, + selectAll: function(options) { + if (!options) { + // default will always be all streams + options = { + local: true, + remote: true, + isScreen: true, + isAudio: true, + isVideo: true + }; + } + + if (options == 'local') { + options = { + local: true + }; + } + + if (options == 'remote') { + options = { + remote: true + }; + } + + if (options == 'screen') { + options = { + isScreen: true + }; + } + + if (options == 'audio') { + options = { + isAudio: true + }; + } + + if (options == 'video') { + options = { + isVideo: true + }; + } + + var streams = []; + Object.keys(connection.streamEvents).forEach(function(key) { + var event = connection.streamEvents[key]; + + if (skipStreams.indexOf(key) !== -1) return; + var ignore = true; + + if (options.local && event.type === 'local') { + ignore = false; + } + + if (options.remote && event.type === 'remote') { + ignore = false; + } + + if (options.isScreen && event.stream.isScreen) { + ignore = false; + } + + if (options.isVideo && event.stream.isVideo) { + ignore = false; + } + + if (options.isAudio && event.stream.isAudio) { + ignore = false; + } + + if (options.userid && event.userid === options.userid) { + ignore = false; + } + + if (ignore === false) { + streams.push(event); + } + }); + + return streams; + } + }; + + connection.socketURL = '/'; // generated via config.json + connection.socketMessageEvent = 'RTCMultiConnection-Message'; // generated via config.json + connection.socketCustomEvent = 'RTCMultiConnection-Custom-Message'; // generated via config.json + connection.DetectRTC = DetectRTC; + + connection.setCustomSocketEvent = function(customEvent) { + if (customEvent) { + connection.socketCustomEvent = customEvent; + } + + if (!connection.socket) { + return; + } + + connection.socket.emit('set-custom-socket-event-listener', connection.socketCustomEvent); + }; + + connection.getNumberOfBroadcastViewers = function(broadcastId, callback) { + if (!connection.socket || !broadcastId || !callback) return; + + connection.socket.emit('get-number-of-users-in-specific-broadcast', broadcastId, callback); + }; + + connection.onNumberOfBroadcastViewersUpdated = function(event) { + if (!connection.enableLogs || !connection.isInitiator) return; + console.info('Number of broadcast (', event.broadcastId, ') viewers', event.numberOfBroadcastViewers); + }; + + connection.onUserStatusChanged = function(event, dontWriteLogs) { + if (!!connection.enableLogs && !dontWriteLogs) { + console.info(event.userid, event.status); + } + }; + + connection.getUserMediaHandler = getUserMediaHandler; + connection.multiPeersHandler = mPeer; + connection.enableLogs = true; + connection.setCustomSocketHandler = function(customSocketHandler) { + if (typeof SocketConnection !== 'undefined') { + SocketConnection = customSocketHandler; + } + }; + + // default value should be 15k because [old]Firefox's receiving limit is 16k! + // however 64k works chrome-to-chrome + connection.chunkSize = 65 * 1000; + + connection.maxParticipantsAllowed = 1000; + + // eject or leave single user + connection.disconnectWith = mPeer.disconnectWith; + + // check if room exist on server + // we will pass roomid to the server and wait for callback (i.e. server's response) + connection.checkPresence = function(roomid, callback) { + roomid = roomid || connection.sessionid; + + if (SocketConnection.name === 'SSEConnection') { + SSEConnection.checkPresence(roomid, function(isRoomExist, _roomid) { + if (!connection.socket) { + if (!isRoomExist) { + connection.userid = _roomid; + } + + connection.connectSocket(function() { + callback(isRoomExist, _roomid); + }); + return; + } + callback(isRoomExist, _roomid); + }); + return; + } + + if (!connection.socket) { + connection.connectSocket(function() { + connection.checkPresence(roomid, callback); + }); + return; + } + connection.socket.emit('check-presence', roomid + '', function(isRoomExist, _roomid) { + if (connection.enableLogs) { + console.log('checkPresence.isRoomExist: ', isRoomExist, ' roomid: ', _roomid); + } + callback(isRoomExist, _roomid); + }); + }; + + connection.onReadyForOffer = function(remoteUserId, userPreferences) { + connection.multiPeersHandler.createNewPeer(remoteUserId, userPreferences); + }; + + connection.setUserPreferences = function(userPreferences) { + if (connection.dontAttachStream) { + userPreferences.dontAttachLocalStream = true; + } + + if (connection.dontGetRemoteStream) { + userPreferences.dontGetRemoteStream = true; + } + + return userPreferences; + }; + + connection.updateExtraData = function() { + connection.socket.emit('extra-data-updated', connection.extra); + }; + + connection.enableScalableBroadcast = false; + connection.maxRelayLimitPerUser = 3; // each broadcast should serve only 3 users + + connection.dontCaptureUserMedia = false; + connection.dontAttachStream = false; + connection.dontGetRemoteStream = false; + + connection.onReConnecting = function(event) { + if (connection.enableLogs) { + console.info('ReConnecting with', event.userid, '...'); + } + }; + + connection.beforeAddingStream = function(stream) { + return stream; + }; + + connection.beforeRemovingStream = function(stream) { + return stream; + }; + + if (typeof isChromeExtensionAvailable !== 'undefined') { + connection.checkIfChromeExtensionAvailable = isChromeExtensionAvailable; + } + + if (typeof isFirefoxExtensionAvailable !== 'undefined') { + connection.checkIfChromeExtensionAvailable = isFirefoxExtensionAvailable; + } + + if (typeof getChromeExtensionStatus !== 'undefined') { + connection.getChromeExtensionStatus = getChromeExtensionStatus; + } + + connection.getScreenConstraints = function(callback, audioPlusTab) { + if (isAudioPlusTab(connection, audioPlusTab)) { + audioPlusTab = true; + } + + getScreenConstraints(function(error, screen_constraints) { + if (!error) { + screen_constraints = connection.modifyScreenConstraints(screen_constraints); + callback(error, screen_constraints); + } + }, audioPlusTab); + }; + + connection.modifyScreenConstraints = function(screen_constraints) { + return screen_constraints; + }; + + connection.onPeerStateChanged = function(state) { + if (connection.enableLogs) { + if (state.iceConnectionState.search(/closed|failed/gi) !== -1) { + console.error('Peer connection is closed between you & ', state.userid, state.extra, 'state:', state.iceConnectionState); + } + } + }; + + connection.isOnline = true; + + listenEventHandler('online', function() { + connection.isOnline = true; + }); + + listenEventHandler('offline', function() { + connection.isOnline = false; + }); + + connection.isLowBandwidth = false; + if (navigator && navigator.connection && navigator.connection.type) { + connection.isLowBandwidth = navigator.connection.type.toString().toLowerCase().search(/wifi|cell/g) !== -1; + if (connection.isLowBandwidth) { + connection.bandwidth = { + audio: false, + video: false, + screen: false + }; + + if (connection.mediaConstraints.audio && connection.mediaConstraints.audio.optional && connection.mediaConstraints.audio.optional.length) { + var newArray = []; + connection.mediaConstraints.audio.optional.forEach(function(opt) { + if (typeof opt.bandwidth === 'undefined') { + newArray.push(opt); + } + }); + connection.mediaConstraints.audio.optional = newArray; + } + + if (connection.mediaConstraints.video && connection.mediaConstraints.video.optional && connection.mediaConstraints.video.optional.length) { + var newArray = []; + connection.mediaConstraints.video.optional.forEach(function(opt) { + if (typeof opt.bandwidth === 'undefined') { + newArray.push(opt); + } + }); + connection.mediaConstraints.video.optional = newArray; + } + } + } + + connection.getExtraData = function(remoteUserId) { + if (!remoteUserId) throw 'remoteUserId is required.'; + if (!connection.peers[remoteUserId]) return {}; + return connection.peers[remoteUserId].extra; + }; + + if (!!forceOptions.autoOpenOrJoin) { + connection.openOrJoin(connection.sessionid); + } + + connection.onUserIdAlreadyTaken = function(useridAlreadyTaken, yourNewUserId) { + if (connection.enableLogs) { + console.warn('Userid already taken.', useridAlreadyTaken, 'Your new userid:', yourNewUserId); + } + + connection.join(useridAlreadyTaken); + }; + + connection.onRoomFull = function(roomid) { + if (connection.enableLogs) { + console.warn(roomid, 'is full.'); + } + }; + + connection.trickleIce = true; + connection.version = '3.4.7'; + + connection.onSettingLocalDescription = function(event) { + if (connection.enableLogs) { + console.info('Set local description for remote user', event.userid); + } + }; + + connection.oneRoomAlreadyExist = function(roomid) { + if (connection.enableLogs) { + console.info('Server says "Room ', roomid, 'already exist. Joining instead.'); + } + connection.join(roomid); + }; + + connection.resetScreen = function() { + sourceId = null; + if (DetectRTC && DetectRTC.screen) { + delete DetectRTC.screen.sourceId; + } + + currentUserMediaRequest = { + streams: [], + mutex: false, + queueRequests: [] + }; + }; + + // if disabled, "event.mediaElement" for "onstream" will be NULL + connection.autoCreateMediaElement = true; + + // open or join with a password + connection.password = null; + })(this); + +}; diff --git a/cordova-mobile-apps/video conference/www/js/index.js b/cordova-mobile-apps/video-conference/www/js/index.js similarity index 100% rename from cordova-mobile-apps/video conference/www/js/index.js rename to cordova-mobile-apps/video-conference/www/js/index.js diff --git a/cordova-mobile-apps/video-conference/www/js/menu.js b/cordova-mobile-apps/video-conference/www/js/menu.js new file mode 100644 index 00000000..d1c987d2 --- /dev/null +++ b/cordova-mobile-apps/video-conference/www/js/menu.js @@ -0,0 +1,21 @@ +(function menuLooper() { + var menuExplorer = document.querySelector('.menu-explorer'); + + if (!menuExplorer) { + setTimeout(menuLooper, 500); + return; + } + + menuExplorer.onclick = function(e) { + e.preventDefault(); + e.stopPropagation(); + + var nav = document.querySelector('nav'); + nav.style.display = 'block'; + + document.documentElement.onclick = function() { + document.body.onclick = null; + nav.style.display = 'none'; + }; + }; +})(); diff --git a/cordova-mobile-apps/video-conference/www/js/socket.io.js b/cordova-mobile-apps/video-conference/www/js/socket.io.js new file mode 100644 index 00000000..86a7c529 --- /dev/null +++ b/cordova-mobile-apps/video-conference/www/js/socket.io.js @@ -0,0 +1,7685 @@ +(function webpackUniversalModuleDefinition(root, factory) { + if(typeof exports === 'object' && typeof module === 'object') + module.exports = factory(); + else if(typeof define === 'function' && define.amd) + define([], factory); + else if(typeof exports === 'object') + exports["io"] = factory(); + else + root["io"] = factory(); +})(this, function() { +return /******/ (function(modules) { // webpackBootstrap +/******/ // The module cache +/******/ var installedModules = {}; + +/******/ // The require function +/******/ function __webpack_require__(moduleId) { + +/******/ // Check if module is in cache +/******/ if(installedModules[moduleId]) +/******/ return installedModules[moduleId].exports; + +/******/ // Create a new module (and put it into the cache) +/******/ var module = installedModules[moduleId] = { +/******/ exports: {}, +/******/ id: moduleId, +/******/ loaded: false +/******/ }; + +/******/ // Execute the module function +/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); + +/******/ // Flag the module as loaded +/******/ module.loaded = true; + +/******/ // Return the exports of the module +/******/ return module.exports; +/******/ } + + +/******/ // expose the modules object (__webpack_modules__) +/******/ __webpack_require__.m = modules; + +/******/ // expose the module cache +/******/ __webpack_require__.c = installedModules; + +/******/ // __webpack_public_path__ +/******/ __webpack_require__.p = ""; + +/******/ // Load entry module and return exports +/******/ return __webpack_require__(0); +/******/ }) +/************************************************************************/ +/******/ ([ +/* 0 */ +/***/ function(module, exports, __webpack_require__) { + + 'use strict'; + + var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; + + /** + * Module dependencies. + */ + + var url = __webpack_require__(1); + var parser = __webpack_require__(6); + var Manager = __webpack_require__(13); + var debug = __webpack_require__(3)('socket.io-client'); + + /** + * Module exports. + */ + + module.exports = exports = lookup; + + /** + * Managers cache. + */ + + var cache = exports.managers = {}; + + /** + * Looks up an existing `Manager` for multiplexing. + * If the user summons: + * + * `io('http://localhost/a');` + * `io('http://localhost/b');` + * + * We reuse the existing instance based on same scheme/port/host, + * and we initialize sockets for each namespace. + * + * @api public + */ + + function lookup(uri, opts) { + if ((typeof uri === 'undefined' ? 'undefined' : _typeof(uri)) === 'object') { + opts = uri; + uri = undefined; + } + + opts = opts || {}; + + var parsed = url(uri); + var source = parsed.source; + var id = parsed.id; + var path = parsed.path; + var sameNamespace = cache[id] && path in cache[id].nsps; + var newConnection = opts.forceNew || opts['force new connection'] || false === opts.multiplex || sameNamespace; + + var io; + + if (newConnection) { + debug('ignoring socket cache for %s', source); + io = Manager(source, opts); + } else { + if (!cache[id]) { + debug('new io instance for %s', source); + cache[id] = Manager(source, opts); + } + io = cache[id]; + } + if (parsed.query && !opts.query) { + opts.query = parsed.query; + } else if (opts && 'object' === _typeof(opts.query)) { + opts.query = encodeQueryString(opts.query); + } + return io.socket(parsed.path, opts); + } + /** + * Helper method to parse query objects to string. + * @param {object} query + * @returns {string} + */ + function encodeQueryString(obj) { + var str = []; + for (var p in obj) { + if (obj.hasOwnProperty(p)) { + str.push(encodeURIComponent(p) + '=' + encodeURIComponent(obj[p])); + } + } + return str.join('&'); + } + /** + * Protocol version. + * + * @api public + */ + + exports.protocol = parser.protocol; + + /** + * `connect`. + * + * @param {String} uri + * @api public + */ + + exports.connect = lookup; + + /** + * Expose constructors for standalone build. + * + * @api public + */ + + exports.Manager = __webpack_require__(13); + exports.Socket = __webpack_require__(40); + +/***/ }, +/* 1 */ +/***/ function(module, exports, __webpack_require__) { + + /* WEBPACK VAR INJECTION */(function(global) {'use strict'; + + /** + * Module dependencies. + */ + + var parseuri = __webpack_require__(2); + var debug = __webpack_require__(3)('socket.io-client:url'); + + /** + * Module exports. + */ + + module.exports = url; + + /** + * URL parser. + * + * @param {String} url + * @param {Object} An object meant to mimic window.location. + * Defaults to window.location. + * @api public + */ + + function url(uri, loc) { + var obj = uri; + + // default to window.location + loc = loc || global.location; + if (null == uri) uri = loc.protocol + '//' + loc.host; + + // relative path support + if ('string' === typeof uri) { + if ('/' === uri.charAt(0)) { + if ('/' === uri.charAt(1)) { + uri = loc.protocol + uri; + } else { + uri = loc.host + uri; + } + } + + if (!/^(https?|wss?):\/\//.test(uri)) { + debug('protocol-less url %s', uri); + if ('undefined' !== typeof loc) { + uri = loc.protocol + '//' + uri; + } else { + uri = 'https://' + uri; + } + } + + // parse + debug('parse %s', uri); + obj = parseuri(uri); + } + + // make sure we treat `localhost:80` and `localhost` equally + if (!obj.port) { + if (/^(http|ws)$/.test(obj.protocol)) { + obj.port = '80'; + } else if (/^(http|ws)s$/.test(obj.protocol)) { + obj.port = '443'; + } + } + + obj.path = obj.path || '/'; + + var ipv6 = obj.host.indexOf(':') !== -1; + var host = ipv6 ? '[' + obj.host + ']' : obj.host; + + // define unique id + obj.id = obj.protocol + '://' + host + ':' + obj.port; + // define href + obj.href = obj.protocol + '://' + host + (loc && loc.port === obj.port ? '' : ':' + obj.port); + + return obj; + } + /* WEBPACK VAR INJECTION */}.call(exports, (function() { return this; }()))) + +/***/ }, +/* 2 */ +/***/ function(module, exports) { + + /** + * Parses an URI + * + * @author Steven Levithan (MIT license) + * @api private + */ + + var re = /^(?:(?![^:@]+:[^:@\/]*@)(http|https|ws|wss):\/\/)?((?:(([^:@]*)(?::([^:@]*))?)?@)?((?:[a-f0-9]{0,4}:){2,7}[a-f0-9]{0,4}|[^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/; + + var parts = [ + 'source', 'protocol', 'authority', 'userInfo', 'user', 'password', 'host', 'port', 'relative', 'path', 'directory', 'file', 'query', 'anchor' + ]; + + module.exports = function parseuri(str) { + var src = str, + b = str.indexOf('['), + e = str.indexOf(']'); + + if (b != -1 && e != -1) { + str = str.substring(0, b) + str.substring(b, e).replace(/:/g, ';') + str.substring(e, str.length); + } + + var m = re.exec(str || ''), + uri = {}, + i = 14; + + while (i--) { + uri[parts[i]] = m[i] || ''; + } + + if (b != -1 && e != -1) { + uri.source = src; + uri.host = uri.host.substring(1, uri.host.length - 1).replace(/;/g, ':'); + uri.authority = uri.authority.replace('[', '').replace(']', '').replace(/;/g, ':'); + uri.ipv6uri = true; + } + + return uri; + }; + + +/***/ }, +/* 3 */ +/***/ function(module, exports, __webpack_require__) { + + + /** + * This is the web browser implementation of `debug()`. + * + * Expose `debug()` as the module. + */ + + exports = module.exports = __webpack_require__(4); + exports.log = log; + exports.formatArgs = formatArgs; + exports.save = save; + exports.load = load; + exports.useColors = useColors; + exports.storage = 'undefined' != typeof chrome + && 'undefined' != typeof chrome.storage + ? chrome.storage.local + : localstorage(); + + /** + * Colors. + */ + + exports.colors = [ + 'lightseagreen', + 'forestgreen', + 'goldenrod', + 'dodgerblue', + 'darkorchid', + 'crimson' + ]; + + /** + * Currently only WebKit-based Web Inspectors, Firefox >= v31, + * and the Firebug extension (any Firefox version) are known + * to support "%c" CSS customizations. + * + * TODO: add a `localStorage` variable to explicitly enable/disable colors + */ + + function useColors() { + // is webkit? http://stackoverflow.com/a/16459606/376773 + return ('WebkitAppearance' in document.documentElement.style) || + // is firebug? http://stackoverflow.com/a/398120/376773 + (window.console && (console.firebug || (console.exception && console.table))) || + // is firefox >= v31? + // https://developer.mozilla.org/en-US/docs/Tools/Web_Console#Styling_messages + (navigator.userAgent.toLowerCase().match(/firefox\/(\d+)/) && parseInt(RegExp.$1, 10) >= 31); + } + + /** + * Map %j to `JSON.stringify()`, since no Web Inspectors do that by default. + */ + + exports.formatters.j = function(v) { + return JSON.stringify(v); + }; + + + /** + * Colorize log arguments if enabled. + * + * @api public + */ + + function formatArgs() { + var args = arguments; + var useColors = this.useColors; + + args[0] = (useColors ? '%c' : '') + + this.namespace + + (useColors ? ' %c' : ' ') + + args[0] + + (useColors ? '%c ' : ' ') + + '+' + exports.humanize(this.diff); + + if (!useColors) return args; + + var c = 'color: ' + this.color; + args = [args[0], c, 'color: inherit'].concat(Array.prototype.slice.call(args, 1)); + + // the final "%c" is somewhat tricky, because there could be other + // arguments passed either before or after the %c, so we need to + // figure out the correct index to insert the CSS into + var index = 0; + var lastC = 0; + args[0].replace(/%[a-z%]/g, function(match) { + if ('%%' === match) return; + index++; + if ('%c' === match) { + // we only are interested in the *last* %c + // (the user may have provided their own) + lastC = index; + } + }); + + args.splice(lastC, 0, c); + return args; + } + + /** + * Invokes `console.log()` when available. + * No-op when `console.log` is not a "function". + * + * @api public + */ + + function log() { + // this hackery is required for IE8/9, where + // the `console.log` function doesn't have 'apply' + return 'object' === typeof console + && console.log + && Function.prototype.apply.call(console.log, console, arguments); + } + + /** + * Save `namespaces`. + * + * @param {String} namespaces + * @api private + */ + + function save(namespaces) { + try { + if (null == namespaces) { + exports.storage.removeItem('debug'); + } else { + exports.storage.debug = namespaces; + } + } catch(e) {} + } + + /** + * Load `namespaces`. + * + * @return {String} returns the previously persisted debug modes + * @api private + */ + + function load() { + var r; + try { + r = exports.storage.debug; + } catch(e) {} + return r; + } + + /** + * Enable namespaces listed in `localStorage.debug` initially. + */ + + exports.enable(load()); + + /** + * Localstorage attempts to return the localstorage. + * + * This is necessary because safari throws + * when a user disables cookies/localstorage + * and you attempt to access it. + * + * @return {LocalStorage} + * @api private + */ + + function localstorage(){ + try { + return window.localStorage; + } catch (e) {} + } + + +/***/ }, +/* 4 */ +/***/ function(module, exports, __webpack_require__) { + + + /** + * This is the common logic for both the Node.js and web browser + * implementations of `debug()`. + * + * Expose `debug()` as the module. + */ + + exports = module.exports = debug; + exports.coerce = coerce; + exports.disable = disable; + exports.enable = enable; + exports.enabled = enabled; + exports.humanize = __webpack_require__(5); + + /** + * The currently active debug mode names, and names to skip. + */ + + exports.names = []; + exports.skips = []; + + /** + * Map of special "%n" handling functions, for the debug "format" argument. + * + * Valid key names are a single, lowercased letter, i.e. "n". + */ + + exports.formatters = {}; + + /** + * Previously assigned color. + */ + + var prevColor = 0; + + /** + * Previous log timestamp. + */ + + var prevTime; + + /** + * Select a color. + * + * @return {Number} + * @api private + */ + + function selectColor() { + return exports.colors[prevColor++ % exports.colors.length]; + } + + /** + * Create a debugger with the given `namespace`. + * + * @param {String} namespace + * @return {Function} + * @api public + */ + + function debug(namespace) { + + // define the `disabled` version + function disabled() { + } + disabled.enabled = false; + + // define the `enabled` version + function enabled() { + + var self = enabled; + + // set `diff` timestamp + var curr = +new Date(); + var ms = curr - (prevTime || curr); + self.diff = ms; + self.prev = prevTime; + self.curr = curr; + prevTime = curr; + + // add the `color` if not set + if (null == self.useColors) self.useColors = exports.useColors(); + if (null == self.color && self.useColors) self.color = selectColor(); + + var args = Array.prototype.slice.call(arguments); + + args[0] = exports.coerce(args[0]); + + if ('string' !== typeof args[0]) { + // anything else let's inspect with %o + args = ['%o'].concat(args); + } + + // apply any `formatters` transformations + var index = 0; + args[0] = args[0].replace(/%([a-z%])/g, function(match, format) { + // if we encounter an escaped % then don't increase the array index + if (match === '%%') return match; + index++; + var formatter = exports.formatters[format]; + if ('function' === typeof formatter) { + var val = args[index]; + match = formatter.call(self, val); + + // now we need to remove `args[index]` since it's inlined in the `format` + args.splice(index, 1); + index--; + } + return match; + }); + + if ('function' === typeof exports.formatArgs) { + args = exports.formatArgs.apply(self, args); + } + var logFn = enabled.log || exports.log || console.log.bind(console); + logFn.apply(self, args); + } + enabled.enabled = true; + + var fn = exports.enabled(namespace) ? enabled : disabled; + + fn.namespace = namespace; + + return fn; + } + + /** + * Enables a debug mode by namespaces. This can include modes + * separated by a colon and wildcards. + * + * @param {String} namespaces + * @api public + */ + + function enable(namespaces) { + exports.save(namespaces); + + var split = (namespaces || '').split(/[\s,]+/); + var len = split.length; + + for (var i = 0; i < len; i++) { + if (!split[i]) continue; // ignore empty strings + namespaces = split[i].replace(/\*/g, '.*?'); + if (namespaces[0] === '-') { + exports.skips.push(new RegExp('^' + namespaces.substr(1) + '$')); + } else { + exports.names.push(new RegExp('^' + namespaces + '$')); + } + } + } + + /** + * Disable debug output. + * + * @api public + */ + + function disable() { + exports.enable(''); + } + + /** + * Returns true if the given mode name is enabled, false otherwise. + * + * @param {String} name + * @return {Boolean} + * @api public + */ + + function enabled(name) { + var i, len; + for (i = 0, len = exports.skips.length; i < len; i++) { + if (exports.skips[i].test(name)) { + return false; + } + } + for (i = 0, len = exports.names.length; i < len; i++) { + if (exports.names[i].test(name)) { + return true; + } + } + return false; + } + + /** + * Coerce `val`. + * + * @param {Mixed} val + * @return {Mixed} + * @api private + */ + + function coerce(val) { + if (val instanceof Error) return val.stack || val.message; + return val; + } + + +/***/ }, +/* 5 */ +/***/ function(module, exports) { + + /** + * Helpers. + */ + + var s = 1000; + var m = s * 60; + var h = m * 60; + var d = h * 24; + var y = d * 365.25; + + /** + * Parse or format the given `val`. + * + * Options: + * + * - `long` verbose formatting [false] + * + * @param {String|Number} val + * @param {Object} options + * @return {String|Number} + * @api public + */ + + module.exports = function(val, options){ + options = options || {}; + if ('string' == typeof val) return parse(val); + return options.long + ? long(val) + : short(val); + }; + + /** + * Parse the given `str` and return milliseconds. + * + * @param {String} str + * @return {Number} + * @api private + */ + + function parse(str) { + str = '' + str; + if (str.length > 10000) return; + var match = /^((?:\d+)?\.?\d+) *(milliseconds?|msecs?|ms|seconds?|secs?|s|minutes?|mins?|m|hours?|hrs?|h|days?|d|years?|yrs?|y)?$/i.exec(str); + if (!match) return; + var n = parseFloat(match[1]); + var type = (match[2] || 'ms').toLowerCase(); + switch (type) { + case 'years': + case 'year': + case 'yrs': + case 'yr': + case 'y': + return n * y; + case 'days': + case 'day': + case 'd': + return n * d; + case 'hours': + case 'hour': + case 'hrs': + case 'hr': + case 'h': + return n * h; + case 'minutes': + case 'minute': + case 'mins': + case 'min': + case 'm': + return n * m; + case 'seconds': + case 'second': + case 'secs': + case 'sec': + case 's': + return n * s; + case 'milliseconds': + case 'millisecond': + case 'msecs': + case 'msec': + case 'ms': + return n; + } + } + + /** + * Short format for `ms`. + * + * @param {Number} ms + * @return {String} + * @api private + */ + + function short(ms) { + if (ms >= d) return Math.round(ms / d) + 'd'; + if (ms >= h) return Math.round(ms / h) + 'h'; + if (ms >= m) return Math.round(ms / m) + 'm'; + if (ms >= s) return Math.round(ms / s) + 's'; + return ms + 'ms'; + } + + /** + * Long format for `ms`. + * + * @param {Number} ms + * @return {String} + * @api private + */ + + function long(ms) { + return plural(ms, d, 'day') + || plural(ms, h, 'hour') + || plural(ms, m, 'minute') + || plural(ms, s, 'second') + || ms + ' ms'; + } + + /** + * Pluralization helper. + */ + + function plural(ms, n, name) { + if (ms < n) return; + if (ms < n * 1.5) return Math.floor(ms / n) + ' ' + name; + return Math.ceil(ms / n) + ' ' + name + 's'; + } + + +/***/ }, +/* 6 */ +/***/ function(module, exports, __webpack_require__) { + + + /** + * Module dependencies. + */ + + var debug = __webpack_require__(3)('socket.io-parser'); + var json = __webpack_require__(7); + var isArray = __webpack_require__(9); + var Emitter = __webpack_require__(10); + var binary = __webpack_require__(11); + var isBuf = __webpack_require__(12); + + /** + * Protocol version. + * + * @api public + */ + + exports.protocol = 4; + + /** + * Packet types. + * + * @api public + */ + + exports.types = [ + 'CONNECT', + 'DISCONNECT', + 'EVENT', + 'ACK', + 'ERROR', + 'BINARY_EVENT', + 'BINARY_ACK' + ]; + + /** + * Packet type `connect`. + * + * @api public + */ + + exports.CONNECT = 0; + + /** + * Packet type `disconnect`. + * + * @api public + */ + + exports.DISCONNECT = 1; + + /** + * Packet type `event`. + * + * @api public + */ + + exports.EVENT = 2; + + /** + * Packet type `ack`. + * + * @api public + */ + + exports.ACK = 3; + + /** + * Packet type `error`. + * + * @api public + */ + + exports.ERROR = 4; + + /** + * Packet type 'binary event' + * + * @api public + */ + + exports.BINARY_EVENT = 5; + + /** + * Packet type `binary ack`. For acks with binary arguments. + * + * @api public + */ + + exports.BINARY_ACK = 6; + + /** + * Encoder constructor. + * + * @api public + */ + + exports.Encoder = Encoder; + + /** + * Decoder constructor. + * + * @api public + */ + + exports.Decoder = Decoder; + + /** + * A socket.io Encoder instance + * + * @api public + */ + + function Encoder() {} + + /** + * Encode a packet as a single string if non-binary, or as a + * buffer sequence, depending on packet type. + * + * @param {Object} obj - packet object + * @param {Function} callback - function to handle encodings (likely engine.write) + * @return Calls callback with Array of encodings + * @api public + */ + + Encoder.prototype.encode = function(obj, callback){ + debug('encoding packet %j', obj); + + if (exports.BINARY_EVENT == obj.type || exports.BINARY_ACK == obj.type) { + encodeAsBinary(obj, callback); + } + else { + var encoding = encodeAsString(obj); + callback([encoding]); + } + }; + + /** + * Encode packet as string. + * + * @param {Object} packet + * @return {String} encoded + * @api private + */ + + function encodeAsString(obj) { + var str = ''; + var nsp = false; + + // first is type + str += obj.type; + + // attachments if we have them + if (exports.BINARY_EVENT == obj.type || exports.BINARY_ACK == obj.type) { + str += obj.attachments; + str += '-'; + } + + // if we have a namespace other than `/` + // we append it followed by a comma `,` + if (obj.nsp && '/' != obj.nsp) { + nsp = true; + str += obj.nsp; + } + + // immediately followed by the id + if (null != obj.id) { + if (nsp) { + str += ','; + nsp = false; + } + str += obj.id; + } + + // json data + if (null != obj.data) { + if (nsp) str += ','; + str += json.stringify(obj.data); + } + + debug('encoded %j as %s', obj, str); + return str; + } + + /** + * Encode packet as 'buffer sequence' by removing blobs, and + * deconstructing packet into object with placeholders and + * a list of buffers. + * + * @param {Object} packet + * @return {Buffer} encoded + * @api private + */ + + function encodeAsBinary(obj, callback) { + + function writeEncoding(bloblessData) { + var deconstruction = binary.deconstructPacket(bloblessData); + var pack = encodeAsString(deconstruction.packet); + var buffers = deconstruction.buffers; + + buffers.unshift(pack); // add packet info to beginning of data list + callback(buffers); // write all the buffers + } + + binary.removeBlobs(obj, writeEncoding); + } + + /** + * A socket.io Decoder instance + * + * @return {Object} decoder + * @api public + */ + + function Decoder() { + this.reconstructor = null; + } + + /** + * Mix in `Emitter` with Decoder. + */ + + Emitter(Decoder.prototype); + + /** + * Decodes an ecoded packet string into packet JSON. + * + * @param {String} obj - encoded packet + * @return {Object} packet + * @api public + */ + + Decoder.prototype.add = function(obj) { + var packet; + if ('string' == typeof obj) { + packet = decodeString(obj); + if (exports.BINARY_EVENT == packet.type || exports.BINARY_ACK == packet.type) { // binary packet's json + this.reconstructor = new BinaryReconstructor(packet); + + // no attachments, labeled binary but no binary data to follow + if (this.reconstructor.reconPack.attachments === 0) { + this.emit('decoded', packet); + } + } else { // non-binary full packet + this.emit('decoded', packet); + } + } + else if (isBuf(obj) || obj.base64) { // raw binary data + if (!this.reconstructor) { + throw new Error('got binary data when not reconstructing a packet'); + } else { + packet = this.reconstructor.takeBinaryData(obj); + if (packet) { // received final buffer + this.reconstructor = null; + this.emit('decoded', packet); + } + } + } + else { + throw new Error('Unknown type: ' + obj); + } + }; + + /** + * Decode a packet String (JSON data) + * + * @param {String} str + * @return {Object} packet + * @api private + */ + + function decodeString(str) { + var p = {}; + var i = 0; + + // look up type + p.type = Number(str.charAt(0)); + if (null == exports.types[p.type]) return error(); + + // look up attachments if type binary + if (exports.BINARY_EVENT == p.type || exports.BINARY_ACK == p.type) { + var buf = ''; + while (str.charAt(++i) != '-') { + buf += str.charAt(i); + if (i == str.length) break; + } + if (buf != Number(buf) || str.charAt(i) != '-') { + throw new Error('Illegal attachments'); + } + p.attachments = Number(buf); + } + + // look up namespace (if any) + if ('/' == str.charAt(i + 1)) { + p.nsp = ''; + while (++i) { + var c = str.charAt(i); + if (',' == c) break; + p.nsp += c; + if (i == str.length) break; + } + } else { + p.nsp = '/'; + } + + // look up id + var next = str.charAt(i + 1); + if ('' !== next && Number(next) == next) { + p.id = ''; + while (++i) { + var c = str.charAt(i); + if (null == c || Number(c) != c) { + --i; + break; + } + p.id += str.charAt(i); + if (i == str.length) break; + } + p.id = Number(p.id); + } + + // look up json data + if (str.charAt(++i)) { + try { + p.data = json.parse(str.substr(i)); + } catch(e){ + return error(); + } + } + + debug('decoded %s as %j', str, p); + return p; + } + + /** + * Deallocates a parser's resources + * + * @api public + */ + + Decoder.prototype.destroy = function() { + if (this.reconstructor) { + this.reconstructor.finishedReconstruction(); + } + }; + + /** + * A manager of a binary event's 'buffer sequence'. Should + * be constructed whenever a packet of type BINARY_EVENT is + * decoded. + * + * @param {Object} packet + * @return {BinaryReconstructor} initialized reconstructor + * @api private + */ + + function BinaryReconstructor(packet) { + this.reconPack = packet; + this.buffers = []; + } + + /** + * Method to be called when binary data received from connection + * after a BINARY_EVENT packet. + * + * @param {Buffer | ArrayBuffer} binData - the raw binary data received + * @return {null | Object} returns null if more binary data is expected or + * a reconstructed packet object if all buffers have been received. + * @api private + */ + + BinaryReconstructor.prototype.takeBinaryData = function(binData) { + this.buffers.push(binData); + if (this.buffers.length == this.reconPack.attachments) { // done with buffer list + var packet = binary.reconstructPacket(this.reconPack, this.buffers); + this.finishedReconstruction(); + return packet; + } + return null; + }; + + /** + * Cleans up binary packet reconstruction variables. + * + * @api private + */ + + BinaryReconstructor.prototype.finishedReconstruction = function() { + this.reconPack = null; + this.buffers = []; + }; + + function error(data){ + return { + type: exports.ERROR, + data: 'parser error' + }; + } + + +/***/ }, +/* 7 */ +/***/ function(module, exports, __webpack_require__) { + + /* WEBPACK VAR INJECTION */(function(module, global) {/*** IMPORTS FROM imports-loader ***/ + var define = false; + + /*! JSON v3.3.2 | http://bestiejs.github.io/json3 | Copyright 2012-2014, Kit Cambridge | http://kit.mit-license.org */ + ;(function () { + // Detect the `define` function exposed by asynchronous module loaders. The + // strict `define` check is necessary for compatibility with `r.js`. + var isLoader = typeof define === "function" && define.amd; + + // A set of types used to distinguish objects from primitives. + var objectTypes = { + "function": true, + "object": true + }; + + // Detect the `exports` object exposed by CommonJS implementations. + var freeExports = objectTypes[typeof exports] && exports && !exports.nodeType && exports; + + // Use the `global` object exposed by Node (including Browserify via + // `insert-module-globals`), Narwhal, and Ringo as the default context, + // and the `window` object in browsers. Rhino exports a `global` function + // instead. + var root = objectTypes[typeof window] && window || this, + freeGlobal = freeExports && objectTypes[typeof module] && module && !module.nodeType && typeof global == "object" && global; + + if (freeGlobal && (freeGlobal["global"] === freeGlobal || freeGlobal["window"] === freeGlobal || freeGlobal["self"] === freeGlobal)) { + root = freeGlobal; + } + + // Public: Initializes JSON 3 using the given `context` object, attaching the + // `stringify` and `parse` functions to the specified `exports` object. + function runInContext(context, exports) { + context || (context = root["Object"]()); + exports || (exports = root["Object"]()); + + // Native constructor aliases. + var Number = context["Number"] || root["Number"], + String = context["String"] || root["String"], + Object = context["Object"] || root["Object"], + Date = context["Date"] || root["Date"], + SyntaxError = context["SyntaxError"] || root["SyntaxError"], + TypeError = context["TypeError"] || root["TypeError"], + Math = context["Math"] || root["Math"], + nativeJSON = context["JSON"] || root["JSON"]; + + // Delegate to the native `stringify` and `parse` implementations. + if (typeof nativeJSON == "object" && nativeJSON) { + exports.stringify = nativeJSON.stringify; + exports.parse = nativeJSON.parse; + } + + // Convenience aliases. + var objectProto = Object.prototype, + getClass = objectProto.toString, + isProperty, forEach, undef; + + // Test the `Date#getUTC*` methods. Based on work by @Yaffle. + var isExtended = new Date(-3509827334573292); + try { + // The `getUTCFullYear`, `Month`, and `Date` methods return nonsensical + // results for certain dates in Opera >= 10.53. + isExtended = isExtended.getUTCFullYear() == -109252 && isExtended.getUTCMonth() === 0 && isExtended.getUTCDate() === 1 && + // Safari < 2.0.2 stores the internal millisecond time value correctly, + // but clips the values returned by the date methods to the range of + // signed 32-bit integers ([-2 ** 31, 2 ** 31 - 1]). + isExtended.getUTCHours() == 10 && isExtended.getUTCMinutes() == 37 && isExtended.getUTCSeconds() == 6 && isExtended.getUTCMilliseconds() == 708; + } catch (exception) {} + + // Internal: Determines whether the native `JSON.stringify` and `parse` + // implementations are spec-compliant. Based on work by Ken Snyder. + function has(name) { + if (has[name] !== undef) { + // Return cached feature test result. + return has[name]; + } + var isSupported; + if (name == "bug-string-char-index") { + // IE <= 7 doesn't support accessing string characters using square + // bracket notation. IE 8 only supports this for primitives. + isSupported = "a"[0] != "a"; + } else if (name == "json") { + // Indicates whether both `JSON.stringify` and `JSON.parse` are + // supported. + isSupported = has("json-stringify") && has("json-parse"); + } else { + var value, serialized = '{"a":[1,true,false,null,"\\u0000\\b\\n\\f\\r\\t"]}'; + // Test `JSON.stringify`. + if (name == "json-stringify") { + var stringify = exports.stringify, stringifySupported = typeof stringify == "function" && isExtended; + if (stringifySupported) { + // A test function object with a custom `toJSON` method. + (value = function () { + return 1; + }).toJSON = value; + try { + stringifySupported = + // Firefox 3.1b1 and b2 serialize string, number, and boolean + // primitives as object literals. + stringify(0) === "0" && + // FF 3.1b1, b2, and JSON 2 serialize wrapped primitives as object + // literals. + stringify(new Number()) === "0" && + stringify(new String()) == '""' && + // FF 3.1b1, 2 throw an error if the value is `null`, `undefined`, or + // does not define a canonical JSON representation (this applies to + // objects with `toJSON` properties as well, *unless* they are nested + // within an object or array). + stringify(getClass) === undef && + // IE 8 serializes `undefined` as `"undefined"`. Safari <= 5.1.7 and + // FF 3.1b3 pass this test. + stringify(undef) === undef && + // Safari <= 5.1.7 and FF 3.1b3 throw `Error`s and `TypeError`s, + // respectively, if the value is omitted entirely. + stringify() === undef && + // FF 3.1b1, 2 throw an error if the given value is not a number, + // string, array, object, Boolean, or `null` literal. This applies to + // objects with custom `toJSON` methods as well, unless they are nested + // inside object or array literals. YUI 3.0.0b1 ignores custom `toJSON` + // methods entirely. + stringify(value) === "1" && + stringify([value]) == "[1]" && + // Prototype <= 1.6.1 serializes `[undefined]` as `"[]"` instead of + // `"[null]"`. + stringify([undef]) == "[null]" && + // YUI 3.0.0b1 fails to serialize `null` literals. + stringify(null) == "null" && + // FF 3.1b1, 2 halts serialization if an array contains a function: + // `[1, true, getClass, 1]` serializes as "[1,true,],". FF 3.1b3 + // elides non-JSON values from objects and arrays, unless they + // define custom `toJSON` methods. + stringify([undef, getClass, null]) == "[null,null,null]" && + // Simple serialization test. FF 3.1b1 uses Unicode escape sequences + // where character escape codes are expected (e.g., `\b` => `\u0008`). + stringify({ "a": [value, true, false, null, "\x00\b\n\f\r\t"] }) == serialized && + // FF 3.1b1 and b2 ignore the `filter` and `width` arguments. + stringify(null, value) === "1" && + stringify([1, 2], null, 1) == "[\n 1,\n 2\n]" && + // JSON 2, Prototype <= 1.7, and older WebKit builds incorrectly + // serialize extended years. + stringify(new Date(-8.64e15)) == '"-271821-04-20T00:00:00.000Z"' && + // The milliseconds are optional in ES 5, but required in 5.1. + stringify(new Date(8.64e15)) == '"+275760-09-13T00:00:00.000Z"' && + // Firefox <= 11.0 incorrectly serializes years prior to 0 as negative + // four-digit years instead of six-digit years. Credits: @Yaffle. + stringify(new Date(-621987552e5)) == '"-000001-01-01T00:00:00.000Z"' && + // Safari <= 5.1.5 and Opera >= 10.53 incorrectly serialize millisecond + // values less than 1000. Credits: @Yaffle. + stringify(new Date(-1)) == '"1969-12-31T23:59:59.999Z"'; + } catch (exception) { + stringifySupported = false; + } + } + isSupported = stringifySupported; + } + // Test `JSON.parse`. + if (name == "json-parse") { + var parse = exports.parse; + if (typeof parse == "function") { + try { + // FF 3.1b1, b2 will throw an exception if a bare literal is provided. + // Conforming implementations should also coerce the initial argument to + // a string prior to parsing. + if (parse("0") === 0 && !parse(false)) { + // Simple parsing test. + value = parse(serialized); + var parseSupported = value["a"].length == 5 && value["a"][0] === 1; + if (parseSupported) { + try { + // Safari <= 5.1.2 and FF 3.1b1 allow unescaped tabs in strings. + parseSupported = !parse('"\t"'); + } catch (exception) {} + if (parseSupported) { + try { + // FF 4.0 and 4.0.1 allow leading `+` signs and leading + // decimal points. FF 4.0, 4.0.1, and IE 9-10 also allow + // certain octal literals. + parseSupported = parse("01") !== 1; + } catch (exception) {} + } + if (parseSupported) { + try { + // FF 4.0, 4.0.1, and Rhino 1.7R3-R4 allow trailing decimal + // points. These environments, along with FF 3.1b1 and 2, + // also allow trailing commas in JSON objects and arrays. + parseSupported = parse("1.") !== 1; + } catch (exception) {} + } + } + } + } catch (exception) { + parseSupported = false; + } + } + isSupported = parseSupported; + } + } + return has[name] = !!isSupported; + } + + if (!has("json")) { + // Common `[[Class]]` name aliases. + var functionClass = "[object Function]", + dateClass = "[object Date]", + numberClass = "[object Number]", + stringClass = "[object String]", + arrayClass = "[object Array]", + booleanClass = "[object Boolean]"; + + // Detect incomplete support for accessing string characters by index. + var charIndexBuggy = has("bug-string-char-index"); + + // Define additional utility methods if the `Date` methods are buggy. + if (!isExtended) { + var floor = Math.floor; + // A mapping between the months of the year and the number of days between + // January 1st and the first of the respective month. + var Months = [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334]; + // Internal: Calculates the number of days between the Unix epoch and the + // first day of the given month. + var getDay = function (year, month) { + return Months[month] + 365 * (year - 1970) + floor((year - 1969 + (month = +(month > 1))) / 4) - floor((year - 1901 + month) / 100) + floor((year - 1601 + month) / 400); + }; + } + + // Internal: Determines if a property is a direct property of the given + // object. Delegates to the native `Object#hasOwnProperty` method. + if (!(isProperty = objectProto.hasOwnProperty)) { + isProperty = function (property) { + var members = {}, constructor; + if ((members.__proto__ = null, members.__proto__ = { + // The *proto* property cannot be set multiple times in recent + // versions of Firefox and SeaMonkey. + "toString": 1 + }, members).toString != getClass) { + // Safari <= 2.0.3 doesn't implement `Object#hasOwnProperty`, but + // supports the mutable *proto* property. + isProperty = function (property) { + // Capture and break the object's prototype chain (see section 8.6.2 + // of the ES 5.1 spec). The parenthesized expression prevents an + // unsafe transformation by the Closure Compiler. + var original = this.__proto__, result = property in (this.__proto__ = null, this); + // Restore the original prototype chain. + this.__proto__ = original; + return result; + }; + } else { + // Capture a reference to the top-level `Object` constructor. + constructor = members.constructor; + // Use the `constructor` property to simulate `Object#hasOwnProperty` in + // other environments. + isProperty = function (property) { + var parent = (this.constructor || constructor).prototype; + return property in this && !(property in parent && this[property] === parent[property]); + }; + } + members = null; + return isProperty.call(this, property); + }; + } + + // Internal: Normalizes the `for...in` iteration algorithm across + // environments. Each enumerated key is yielded to a `callback` function. + forEach = function (object, callback) { + var size = 0, Properties, members, property; + + // Tests for bugs in the current environment's `for...in` algorithm. The + // `valueOf` property inherits the non-enumerable flag from + // `Object.prototype` in older versions of IE, Netscape, and Mozilla. + (Properties = function () { + this.valueOf = 0; + }).prototype.valueOf = 0; + + // Iterate over a new instance of the `Properties` class. + members = new Properties(); + for (property in members) { + // Ignore all properties inherited from `Object.prototype`. + if (isProperty.call(members, property)) { + size++; + } + } + Properties = members = null; + + // Normalize the iteration algorithm. + if (!size) { + // A list of non-enumerable properties inherited from `Object.prototype`. + members = ["valueOf", "toString", "toLocaleString", "propertyIsEnumerable", "isPrototypeOf", "hasOwnProperty", "constructor"]; + // IE <= 8, Mozilla 1.0, and Netscape 6.2 ignore shadowed non-enumerable + // properties. + forEach = function (object, callback) { + var isFunction = getClass.call(object) == functionClass, property, length; + var hasProperty = !isFunction && typeof object.constructor != "function" && objectTypes[typeof object.hasOwnProperty] && object.hasOwnProperty || isProperty; + for (property in object) { + // Gecko <= 1.0 enumerates the `prototype` property of functions under + // certain conditions; IE does not. + if (!(isFunction && property == "prototype") && hasProperty.call(object, property)) { + callback(property); + } + } + // Manually invoke the callback for each non-enumerable property. + for (length = members.length; property = members[--length]; hasProperty.call(object, property) && callback(property)); + }; + } else if (size == 2) { + // Safari <= 2.0.4 enumerates shadowed properties twice. + forEach = function (object, callback) { + // Create a set of iterated properties. + var members = {}, isFunction = getClass.call(object) == functionClass, property; + for (property in object) { + // Store each property name to prevent double enumeration. The + // `prototype` property of functions is not enumerated due to cross- + // environment inconsistencies. + if (!(isFunction && property == "prototype") && !isProperty.call(members, property) && (members[property] = 1) && isProperty.call(object, property)) { + callback(property); + } + } + }; + } else { + // No bugs detected; use the standard `for...in` algorithm. + forEach = function (object, callback) { + var isFunction = getClass.call(object) == functionClass, property, isConstructor; + for (property in object) { + if (!(isFunction && property == "prototype") && isProperty.call(object, property) && !(isConstructor = property === "constructor")) { + callback(property); + } + } + // Manually invoke the callback for the `constructor` property due to + // cross-environment inconsistencies. + if (isConstructor || isProperty.call(object, (property = "constructor"))) { + callback(property); + } + }; + } + return forEach(object, callback); + }; + + // Public: Serializes a JavaScript `value` as a JSON string. The optional + // `filter` argument may specify either a function that alters how object and + // array members are serialized, or an array of strings and numbers that + // indicates which properties should be serialized. The optional `width` + // argument may be either a string or number that specifies the indentation + // level of the output. + if (!has("json-stringify")) { + // Internal: A map of control characters and their escaped equivalents. + var Escapes = { + 92: "\\\\", + 34: '\\"', + 8: "\\b", + 12: "\\f", + 10: "\\n", + 13: "\\r", + 9: "\\t" + }; + + // Internal: Converts `value` into a zero-padded string such that its + // length is at least equal to `width`. The `width` must be <= 6. + var leadingZeroes = "000000"; + var toPaddedString = function (width, value) { + // The `|| 0` expression is necessary to work around a bug in + // Opera <= 7.54u2 where `0 == -0`, but `String(-0) !== "0"`. + return (leadingZeroes + (value || 0)).slice(-width); + }; + + // Internal: Double-quotes a string `value`, replacing all ASCII control + // characters (characters with code unit values between 0 and 31) with + // their escaped equivalents. This is an implementation of the + // `Quote(value)` operation defined in ES 5.1 section 15.12.3. + var unicodePrefix = "\\u00"; + var quote = function (value) { + var result = '"', index = 0, length = value.length, useCharIndex = !charIndexBuggy || length > 10; + var symbols = useCharIndex && (charIndexBuggy ? value.split("") : value); + for (; index < length; index++) { + var charCode = value.charCodeAt(index); + // If the character is a control character, append its Unicode or + // shorthand escape sequence; otherwise, append the character as-is. + switch (charCode) { + case 8: case 9: case 10: case 12: case 13: case 34: case 92: + result += Escapes[charCode]; + break; + default: + if (charCode < 32) { + result += unicodePrefix + toPaddedString(2, charCode.toString(16)); + break; + } + result += useCharIndex ? symbols[index] : value.charAt(index); + } + } + return result + '"'; + }; + + // Internal: Recursively serializes an object. Implements the + // `Str(key, holder)`, `JO(value)`, and `JA(value)` operations. + var serialize = function (property, object, callback, properties, whitespace, indentation, stack) { + var value, className, year, month, date, time, hours, minutes, seconds, milliseconds, results, element, index, length, prefix, result; + try { + // Necessary for host object support. + value = object[property]; + } catch (exception) {} + if (typeof value == "object" && value) { + className = getClass.call(value); + if (className == dateClass && !isProperty.call(value, "toJSON")) { + if (value > -1 / 0 && value < 1 / 0) { + // Dates are serialized according to the `Date#toJSON` method + // specified in ES 5.1 section 15.9.5.44. See section 15.9.1.15 + // for the ISO 8601 date time string format. + if (getDay) { + // Manually compute the year, month, date, hours, minutes, + // seconds, and milliseconds if the `getUTC*` methods are + // buggy. Adapted from @Yaffle's `date-shim` project. + date = floor(value / 864e5); + for (year = floor(date / 365.2425) + 1970 - 1; getDay(year + 1, 0) <= date; year++); + for (month = floor((date - getDay(year, 0)) / 30.42); getDay(year, month + 1) <= date; month++); + date = 1 + date - getDay(year, month); + // The `time` value specifies the time within the day (see ES + // 5.1 section 15.9.1.2). The formula `(A % B + B) % B` is used + // to compute `A modulo B`, as the `%` operator does not + // correspond to the `modulo` operation for negative numbers. + time = (value % 864e5 + 864e5) % 864e5; + // The hours, minutes, seconds, and milliseconds are obtained by + // decomposing the time within the day. See section 15.9.1.10. + hours = floor(time / 36e5) % 24; + minutes = floor(time / 6e4) % 60; + seconds = floor(time / 1e3) % 60; + milliseconds = time % 1e3; + } else { + year = value.getUTCFullYear(); + month = value.getUTCMonth(); + date = value.getUTCDate(); + hours = value.getUTCHours(); + minutes = value.getUTCMinutes(); + seconds = value.getUTCSeconds(); + milliseconds = value.getUTCMilliseconds(); + } + // Serialize extended years correctly. + value = (year <= 0 || year >= 1e4 ? (year < 0 ? "-" : "+") + toPaddedString(6, year < 0 ? -year : year) : toPaddedString(4, year)) + + "-" + toPaddedString(2, month + 1) + "-" + toPaddedString(2, date) + + // Months, dates, hours, minutes, and seconds should have two + // digits; milliseconds should have three. + "T" + toPaddedString(2, hours) + ":" + toPaddedString(2, minutes) + ":" + toPaddedString(2, seconds) + + // Milliseconds are optional in ES 5.0, but required in 5.1. + "." + toPaddedString(3, milliseconds) + "Z"; + } else { + value = null; + } + } else if (typeof value.toJSON == "function" && ((className != numberClass && className != stringClass && className != arrayClass) || isProperty.call(value, "toJSON"))) { + // Prototype <= 1.6.1 adds non-standard `toJSON` methods to the + // `Number`, `String`, `Date`, and `Array` prototypes. JSON 3 + // ignores all `toJSON` methods on these objects unless they are + // defined directly on an instance. + value = value.toJSON(property); + } + } + if (callback) { + // If a replacement function was provided, call it to obtain the value + // for serialization. + value = callback.call(object, property, value); + } + if (value === null) { + return "null"; + } + className = getClass.call(value); + if (className == booleanClass) { + // Booleans are represented literally. + return "" + value; + } else if (className == numberClass) { + // JSON numbers must be finite. `Infinity` and `NaN` are serialized as + // `"null"`. + return value > -1 / 0 && value < 1 / 0 ? "" + value : "null"; + } else if (className == stringClass) { + // Strings are double-quoted and escaped. + return quote("" + value); + } + // Recursively serialize objects and arrays. + if (typeof value == "object") { + // Check for cyclic structures. This is a linear search; performance + // is inversely proportional to the number of unique nested objects. + for (length = stack.length; length--;) { + if (stack[length] === value) { + // Cyclic structures cannot be serialized by `JSON.stringify`. + throw TypeError(); + } + } + // Add the object to the stack of traversed objects. + stack.push(value); + results = []; + // Save the current indentation level and indent one additional level. + prefix = indentation; + indentation += whitespace; + if (className == arrayClass) { + // Recursively serialize array elements. + for (index = 0, length = value.length; index < length; index++) { + element = serialize(index, value, callback, properties, whitespace, indentation, stack); + results.push(element === undef ? "null" : element); + } + result = results.length ? (whitespace ? "[\n" + indentation + results.join(",\n" + indentation) + "\n" + prefix + "]" : ("[" + results.join(",") + "]")) : "[]"; + } else { + // Recursively serialize object members. Members are selected from + // either a user-specified list of property names, or the object + // itself. + forEach(properties || value, function (property) { + var element = serialize(property, value, callback, properties, whitespace, indentation, stack); + if (element !== undef) { + // According to ES 5.1 section 15.12.3: "If `gap` {whitespace} + // is not the empty string, let `member` {quote(property) + ":"} + // be the concatenation of `member` and the `space` character." + // The "`space` character" refers to the literal space + // character, not the `space` {width} argument provided to + // `JSON.stringify`. + results.push(quote(property) + ":" + (whitespace ? " " : "") + element); + } + }); + result = results.length ? (whitespace ? "{\n" + indentation + results.join(",\n" + indentation) + "\n" + prefix + "}" : ("{" + results.join(",") + "}")) : "{}"; + } + // Remove the object from the traversed object stack. + stack.pop(); + return result; + } + }; + + // Public: `JSON.stringify`. See ES 5.1 section 15.12.3. + exports.stringify = function (source, filter, width) { + var whitespace, callback, properties, className; + if (objectTypes[typeof filter] && filter) { + if ((className = getClass.call(filter)) == functionClass) { + callback = filter; + } else if (className == arrayClass) { + // Convert the property names array into a makeshift set. + properties = {}; + for (var index = 0, length = filter.length, value; index < length; value = filter[index++], ((className = getClass.call(value)), className == stringClass || className == numberClass) && (properties[value] = 1)); + } + } + if (width) { + if ((className = getClass.call(width)) == numberClass) { + // Convert the `width` to an integer and create a string containing + // `width` number of space characters. + if ((width -= width % 1) > 0) { + for (whitespace = "", width > 10 && (width = 10); whitespace.length < width; whitespace += " "); + } + } else if (className == stringClass) { + whitespace = width.length <= 10 ? width : width.slice(0, 10); + } + } + // Opera <= 7.54u2 discards the values associated with empty string keys + // (`""`) only if they are used directly within an object member list + // (e.g., `!("" in { "": 1})`). + return serialize("", (value = {}, value[""] = source, value), callback, properties, whitespace, "", []); + }; + } + + // Public: Parses a JSON source string. + if (!has("json-parse")) { + var fromCharCode = String.fromCharCode; + + // Internal: A map of escaped control characters and their unescaped + // equivalents. + var Unescapes = { + 92: "\\", + 34: '"', + 47: "/", + 98: "\b", + 116: "\t", + 110: "\n", + 102: "\f", + 114: "\r" + }; + + // Internal: Stores the parser state. + var Index, Source; + + // Internal: Resets the parser state and throws a `SyntaxError`. + var abort = function () { + Index = Source = null; + throw SyntaxError(); + }; + + // Internal: Returns the next token, or `"$"` if the parser has reached + // the end of the source string. A token may be a string, number, `null` + // literal, or Boolean literal. + var lex = function () { + var source = Source, length = source.length, value, begin, position, isSigned, charCode; + while (Index < length) { + charCode = source.charCodeAt(Index); + switch (charCode) { + case 9: case 10: case 13: case 32: + // Skip whitespace tokens, including tabs, carriage returns, line + // feeds, and space characters. + Index++; + break; + case 123: case 125: case 91: case 93: case 58: case 44: + // Parse a punctuator token (`{`, `}`, `[`, `]`, `:`, or `,`) at + // the current position. + value = charIndexBuggy ? source.charAt(Index) : source[Index]; + Index++; + return value; + case 34: + // `"` delimits a JSON string; advance to the next character and + // begin parsing the string. String tokens are prefixed with the + // sentinel `@` character to distinguish them from punctuators and + // end-of-string tokens. + for (value = "@", Index++; Index < length;) { + charCode = source.charCodeAt(Index); + if (charCode < 32) { + // Unescaped ASCII control characters (those with a code unit + // less than the space character) are not permitted. + abort(); + } else if (charCode == 92) { + // A reverse solidus (`\`) marks the beginning of an escaped + // control character (including `"`, `\`, and `/`) or Unicode + // escape sequence. + charCode = source.charCodeAt(++Index); + switch (charCode) { + case 92: case 34: case 47: case 98: case 116: case 110: case 102: case 114: + // Revive escaped control characters. + value += Unescapes[charCode]; + Index++; + break; + case 117: + // `\u` marks the beginning of a Unicode escape sequence. + // Advance to the first character and validate the + // four-digit code point. + begin = ++Index; + for (position = Index + 4; Index < position; Index++) { + charCode = source.charCodeAt(Index); + // A valid sequence comprises four hexdigits (case- + // insensitive) that form a single hexadecimal value. + if (!(charCode >= 48 && charCode <= 57 || charCode >= 97 && charCode <= 102 || charCode >= 65 && charCode <= 70)) { + // Invalid Unicode escape sequence. + abort(); + } + } + // Revive the escaped character. + value += fromCharCode("0x" + source.slice(begin, Index)); + break; + default: + // Invalid escape sequence. + abort(); + } + } else { + if (charCode == 34) { + // An unescaped double-quote character marks the end of the + // string. + break; + } + charCode = source.charCodeAt(Index); + begin = Index; + // Optimize for the common case where a string is valid. + while (charCode >= 32 && charCode != 92 && charCode != 34) { + charCode = source.charCodeAt(++Index); + } + // Append the string as-is. + value += source.slice(begin, Index); + } + } + if (source.charCodeAt(Index) == 34) { + // Advance to the next character and return the revived string. + Index++; + return value; + } + // Unterminated string. + abort(); + default: + // Parse numbers and literals. + begin = Index; + // Advance past the negative sign, if one is specified. + if (charCode == 45) { + isSigned = true; + charCode = source.charCodeAt(++Index); + } + // Parse an integer or floating-point value. + if (charCode >= 48 && charCode <= 57) { + // Leading zeroes are interpreted as octal literals. + if (charCode == 48 && ((charCode = source.charCodeAt(Index + 1)), charCode >= 48 && charCode <= 57)) { + // Illegal octal literal. + abort(); + } + isSigned = false; + // Parse the integer component. + for (; Index < length && ((charCode = source.charCodeAt(Index)), charCode >= 48 && charCode <= 57); Index++); + // Floats cannot contain a leading decimal point; however, this + // case is already accounted for by the parser. + if (source.charCodeAt(Index) == 46) { + position = ++Index; + // Parse the decimal component. + for (; position < length && ((charCode = source.charCodeAt(position)), charCode >= 48 && charCode <= 57); position++); + if (position == Index) { + // Illegal trailing decimal. + abort(); + } + Index = position; + } + // Parse exponents. The `e` denoting the exponent is + // case-insensitive. + charCode = source.charCodeAt(Index); + if (charCode == 101 || charCode == 69) { + charCode = source.charCodeAt(++Index); + // Skip past the sign following the exponent, if one is + // specified. + if (charCode == 43 || charCode == 45) { + Index++; + } + // Parse the exponential component. + for (position = Index; position < length && ((charCode = source.charCodeAt(position)), charCode >= 48 && charCode <= 57); position++); + if (position == Index) { + // Illegal empty exponent. + abort(); + } + Index = position; + } + // Coerce the parsed value to a JavaScript number. + return +source.slice(begin, Index); + } + // A negative sign may only precede numbers. + if (isSigned) { + abort(); + } + // `true`, `false`, and `null` literals. + if (source.slice(Index, Index + 4) == "true") { + Index += 4; + return true; + } else if (source.slice(Index, Index + 5) == "false") { + Index += 5; + return false; + } else if (source.slice(Index, Index + 4) == "null") { + Index += 4; + return null; + } + // Unrecognized token. + abort(); + } + } + // Return the sentinel `$` character if the parser has reached the end + // of the source string. + return "$"; + }; + + // Internal: Parses a JSON `value` token. + var get = function (value) { + var results, hasMembers; + if (value == "$") { + // Unexpected end of input. + abort(); + } + if (typeof value == "string") { + if ((charIndexBuggy ? value.charAt(0) : value[0]) == "@") { + // Remove the sentinel `@` character. + return value.slice(1); + } + // Parse object and array literals. + if (value == "[") { + // Parses a JSON array, returning a new JavaScript array. + results = []; + for (;; hasMembers || (hasMembers = true)) { + value = lex(); + // A closing square bracket marks the end of the array literal. + if (value == "]") { + break; + } + // If the array literal contains elements, the current token + // should be a comma separating the previous element from the + // next. + if (hasMembers) { + if (value == ",") { + value = lex(); + if (value == "]") { + // Unexpected trailing `,` in array literal. + abort(); + } + } else { + // A `,` must separate each array element. + abort(); + } + } + // Elisions and leading commas are not permitted. + if (value == ",") { + abort(); + } + results.push(get(value)); + } + return results; + } else if (value == "{") { + // Parses a JSON object, returning a new JavaScript object. + results = {}; + for (;; hasMembers || (hasMembers = true)) { + value = lex(); + // A closing curly brace marks the end of the object literal. + if (value == "}") { + break; + } + // If the object literal contains members, the current token + // should be a comma separator. + if (hasMembers) { + if (value == ",") { + value = lex(); + if (value == "}") { + // Unexpected trailing `,` in object literal. + abort(); + } + } else { + // A `,` must separate each object member. + abort(); + } + } + // Leading commas are not permitted, object property names must be + // double-quoted strings, and a `:` must separate each property + // name and value. + if (value == "," || typeof value != "string" || (charIndexBuggy ? value.charAt(0) : value[0]) != "@" || lex() != ":") { + abort(); + } + results[value.slice(1)] = get(lex()); + } + return results; + } + // Unexpected token encountered. + abort(); + } + return value; + }; + + // Internal: Updates a traversed object member. + var update = function (source, property, callback) { + var element = walk(source, property, callback); + if (element === undef) { + delete source[property]; + } else { + source[property] = element; + } + }; + + // Internal: Recursively traverses a parsed JSON object, invoking the + // `callback` function for each value. This is an implementation of the + // `Walk(holder, name)` operation defined in ES 5.1 section 15.12.2. + var walk = function (source, property, callback) { + var value = source[property], length; + if (typeof value == "object" && value) { + // `forEach` can't be used to traverse an array in Opera <= 8.54 + // because its `Object#hasOwnProperty` implementation returns `false` + // for array indices (e.g., `![1, 2, 3].hasOwnProperty("0")`). + if (getClass.call(value) == arrayClass) { + for (length = value.length; length--;) { + update(value, length, callback); + } + } else { + forEach(value, function (property) { + update(value, property, callback); + }); + } + } + return callback.call(source, property, value); + }; + + // Public: `JSON.parse`. See ES 5.1 section 15.12.2. + exports.parse = function (source, callback) { + var result, value; + Index = 0; + Source = "" + source; + result = get(lex()); + // If a JSON string contains multiple tokens, it is invalid. + if (lex() != "$") { + abort(); + } + // Reset the parser state. + Index = Source = null; + return callback && getClass.call(callback) == functionClass ? walk((value = {}, value[""] = result, value), "", callback) : result; + }; + } + } + + exports["runInContext"] = runInContext; + return exports; + } + + if (freeExports && !isLoader) { + // Export for CommonJS environments. + runInContext(root, freeExports); + } else { + // Export for web browsers and JavaScript engines. + var nativeJSON = root.JSON, + previousJSON = root["JSON3"], + isRestored = false; + + var JSON3 = runInContext(root, (root["JSON3"] = { + // Public: Restores the original value of the global `JSON` object and + // returns a reference to the `JSON3` object. + "noConflict": function () { + if (!isRestored) { + isRestored = true; + root.JSON = nativeJSON; + root["JSON3"] = previousJSON; + nativeJSON = previousJSON = null; + } + return JSON3; + } + })); + + root.JSON = { + "parse": JSON3.parse, + "stringify": JSON3.stringify + }; + } + + // Export for asynchronous module loaders. + if (isLoader) { + define(function () { + return JSON3; + }); + } + }).call(this); + + + /* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(8)(module), (function() { return this; }()))) + +/***/ }, +/* 8 */ +/***/ function(module, exports) { + + module.exports = function(module) { + if(!module.webpackPolyfill) { + module.deprecate = function() {}; + module.paths = []; + // module.parent = undefined by default + module.children = []; + module.webpackPolyfill = 1; + } + return module; + } + + +/***/ }, +/* 9 */ +/***/ function(module, exports) { + + module.exports = Array.isArray || function (arr) { + return Object.prototype.toString.call(arr) == '[object Array]'; + }; + + +/***/ }, +/* 10 */ +/***/ function(module, exports) { + + + /** + * Expose `Emitter`. + */ + + module.exports = Emitter; + + /** + * Initialize a new `Emitter`. + * + * @api public + */ + + function Emitter(obj) { + if (obj) return mixin(obj); + }; + + /** + * Mixin the emitter properties. + * + * @param {Object} obj + * @return {Object} + * @api private + */ + + function mixin(obj) { + for (var key in Emitter.prototype) { + obj[key] = Emitter.prototype[key]; + } + return obj; + } + + /** + * Listen on the given `event` with `fn`. + * + * @param {String} event + * @param {Function} fn + * @return {Emitter} + * @api public + */ + + Emitter.prototype.on = + Emitter.prototype.addEventListener = function(event, fn){ + this._callbacks = this._callbacks || {}; + (this._callbacks[event] = this._callbacks[event] || []) + .push(fn); + return this; + }; + + /** + * Adds an `event` listener that will be invoked a single + * time then automatically removed. + * + * @param {String} event + * @param {Function} fn + * @return {Emitter} + * @api public + */ + + Emitter.prototype.once = function(event, fn){ + var self = this; + this._callbacks = this._callbacks || {}; + + function on() { + self.off(event, on); + fn.apply(this, arguments); + } + + on.fn = fn; + this.on(event, on); + return this; + }; + + /** + * Remove the given callback for `event` or all + * registered callbacks. + * + * @param {String} event + * @param {Function} fn + * @return {Emitter} + * @api public + */ + + Emitter.prototype.off = + Emitter.prototype.removeListener = + Emitter.prototype.removeAllListeners = + Emitter.prototype.removeEventListener = function(event, fn){ + this._callbacks = this._callbacks || {}; + + // all + if (0 == arguments.length) { + this._callbacks = {}; + return this; + } + + // specific event + var callbacks = this._callbacks[event]; + if (!callbacks) return this; + + // remove all handlers + if (1 == arguments.length) { + delete this._callbacks[event]; + return this; + } + + // remove specific handler + var cb; + for (var i = 0; i < callbacks.length; i++) { + cb = callbacks[i]; + if (cb === fn || cb.fn === fn) { + callbacks.splice(i, 1); + break; + } + } + return this; + }; + + /** + * Emit `event` with the given args. + * + * @param {String} event + * @param {Mixed} ... + * @return {Emitter} + */ + + Emitter.prototype.emit = function(event){ + this._callbacks = this._callbacks || {}; + var args = [].slice.call(arguments, 1) + , callbacks = this._callbacks[event]; + + if (callbacks) { + callbacks = callbacks.slice(0); + for (var i = 0, len = callbacks.length; i < len; ++i) { + callbacks[i].apply(this, args); + } + } + + return this; + }; + + /** + * Return array of callbacks for `event`. + * + * @param {String} event + * @return {Array} + * @api public + */ + + Emitter.prototype.listeners = function(event){ + this._callbacks = this._callbacks || {}; + return this._callbacks[event] || []; + }; + + /** + * Check if this emitter has `event` handlers. + * + * @param {String} event + * @return {Boolean} + * @api public + */ + + Emitter.prototype.hasListeners = function(event){ + return !! this.listeners(event).length; + }; + + +/***/ }, +/* 11 */ +/***/ function(module, exports, __webpack_require__) { + + /* WEBPACK VAR INJECTION */(function(global) {/*global Blob,File*/ + + /** + * Module requirements + */ + + var isArray = __webpack_require__(9); + var isBuf = __webpack_require__(12); + + /** + * Replaces every Buffer | ArrayBuffer in packet with a numbered placeholder. + * Anything with blobs or files should be fed through removeBlobs before coming + * here. + * + * @param {Object} packet - socket.io event packet + * @return {Object} with deconstructed packet and list of buffers + * @api public + */ + + exports.deconstructPacket = function(packet){ + var buffers = []; + var packetData = packet.data; + + function _deconstructPacket(data) { + if (!data) return data; + + if (isBuf(data)) { + var placeholder = { _placeholder: true, num: buffers.length }; + buffers.push(data); + return placeholder; + } else if (isArray(data)) { + var newData = new Array(data.length); + for (var i = 0; i < data.length; i++) { + newData[i] = _deconstructPacket(data[i]); + } + return newData; + } else if ('object' == typeof data && !(data instanceof Date)) { + var newData = {}; + for (var key in data) { + newData[key] = _deconstructPacket(data[key]); + } + return newData; + } + return data; + } + + var pack = packet; + pack.data = _deconstructPacket(packetData); + pack.attachments = buffers.length; // number of binary 'attachments' + return {packet: pack, buffers: buffers}; + }; + + /** + * Reconstructs a binary packet from its placeholder packet and buffers + * + * @param {Object} packet - event packet with placeholders + * @param {Array} buffers - binary buffers to put in placeholder positions + * @return {Object} reconstructed packet + * @api public + */ + + exports.reconstructPacket = function(packet, buffers) { + var curPlaceHolder = 0; + + function _reconstructPacket(data) { + if (data && data._placeholder) { + var buf = buffers[data.num]; // appropriate buffer (should be natural order anyway) + return buf; + } else if (isArray(data)) { + for (var i = 0; i < data.length; i++) { + data[i] = _reconstructPacket(data[i]); + } + return data; + } else if (data && 'object' == typeof data) { + for (var key in data) { + data[key] = _reconstructPacket(data[key]); + } + return data; + } + return data; + } + + packet.data = _reconstructPacket(packet.data); + packet.attachments = undefined; // no longer useful + return packet; + }; + + /** + * Asynchronously removes Blobs or Files from data via + * FileReader's readAsArrayBuffer method. Used before encoding + * data as msgpack. Calls callback with the blobless data. + * + * @param {Object} data + * @param {Function} callback + * @api private + */ + + exports.removeBlobs = function(data, callback) { + function _removeBlobs(obj, curKey, containingObject) { + if (!obj) return obj; + + // convert any blob + if ((global.Blob && obj instanceof Blob) || + (global.File && obj instanceof File)) { + pendingBlobs++; + + // async filereader + var fileReader = new FileReader(); + fileReader.onload = function() { // this.result == arraybuffer + if (containingObject) { + containingObject[curKey] = this.result; + } + else { + bloblessData = this.result; + } + + // if nothing pending its callback time + if(! --pendingBlobs) { + callback(bloblessData); + } + }; + + fileReader.readAsArrayBuffer(obj); // blob -> arraybuffer + } else if (isArray(obj)) { // handle array + for (var i = 0; i < obj.length; i++) { + _removeBlobs(obj[i], i, obj); + } + } else if (obj && 'object' == typeof obj && !isBuf(obj)) { // and object + for (var key in obj) { + _removeBlobs(obj[key], key, obj); + } + } + } + + var pendingBlobs = 0; + var bloblessData = data; + _removeBlobs(bloblessData); + if (!pendingBlobs) { + callback(bloblessData); + } + }; + + /* WEBPACK VAR INJECTION */}.call(exports, (function() { return this; }()))) + +/***/ }, +/* 12 */ +/***/ function(module, exports) { + + /* WEBPACK VAR INJECTION */(function(global) { + module.exports = isBuf; + + /** + * Returns true if obj is a buffer or an arraybuffer. + * + * @api private + */ + + function isBuf(obj) { + return (global.Buffer && global.Buffer.isBuffer(obj)) || + (global.ArrayBuffer && obj instanceof ArrayBuffer); + } + + /* WEBPACK VAR INJECTION */}.call(exports, (function() { return this; }()))) + +/***/ }, +/* 13 */ +/***/ function(module, exports, __webpack_require__) { + + 'use strict'; + + var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; + + /** + * Module dependencies. + */ + + var eio = __webpack_require__(14); + var Socket = __webpack_require__(40); + var Emitter = __webpack_require__(41); + var parser = __webpack_require__(6); + var on = __webpack_require__(43); + var bind = __webpack_require__(44); + var debug = __webpack_require__(3)('socket.io-client:manager'); + var indexOf = __webpack_require__(38); + var Backoff = __webpack_require__(46); + + /** + * IE6+ hasOwnProperty + */ + + var has = Object.prototype.hasOwnProperty; + + /** + * Module exports + */ + + module.exports = Manager; + + /** + * `Manager` constructor. + * + * @param {String} engine instance or engine uri/opts + * @param {Object} options + * @api public + */ + + function Manager(uri, opts) { + if (!(this instanceof Manager)) return new Manager(uri, opts); + if (uri && 'object' === (typeof uri === 'undefined' ? 'undefined' : _typeof(uri))) { + opts = uri; + uri = undefined; + } + opts = opts || {}; + + opts.path = opts.path || '/socket.io'; + this.nsps = {}; + this.subs = []; + this.opts = opts; + this.reconnection(opts.reconnection !== false); + this.reconnectionAttempts(opts.reconnectionAttempts || Infinity); + this.reconnectionDelay(opts.reconnectionDelay || 1000); + this.reconnectionDelayMax(opts.reconnectionDelayMax || 5000); + this.randomizationFactor(opts.randomizationFactor || 0.5); + this.backoff = new Backoff({ + min: this.reconnectionDelay(), + max: this.reconnectionDelayMax(), + jitter: this.randomizationFactor() + }); + this.timeout(null == opts.timeout ? 20000 : opts.timeout); + this.readyState = 'closed'; + this.uri = uri; + this.connecting = []; + this.lastPing = null; + this.encoding = false; + this.packetBuffer = []; + this.encoder = new parser.Encoder(); + this.decoder = new parser.Decoder(); + this.autoConnect = opts.autoConnect !== false; + if (this.autoConnect) this.open(); + } + + /** + * Propagate given event to sockets and emit on `this` + * + * @api private + */ + + Manager.prototype.emitAll = function () { + this.emit.apply(this, arguments); + for (var nsp in this.nsps) { + if (has.call(this.nsps, nsp)) { + this.nsps[nsp].emit.apply(this.nsps[nsp], arguments); + } + } + }; + + /** + * Update `socket.id` of all sockets + * + * @api private + */ + + Manager.prototype.updateSocketIds = function () { + for (var nsp in this.nsps) { + if (has.call(this.nsps, nsp)) { + this.nsps[nsp].id = this.engine.id; + } + } + }; + + /** + * Mix in `Emitter`. + */ + + Emitter(Manager.prototype); + + /** + * Sets the `reconnection` config. + * + * @param {Boolean} true/false if it should automatically reconnect + * @return {Manager} self or value + * @api public + */ + + Manager.prototype.reconnection = function (v) { + if (!arguments.length) return this._reconnection; + this._reconnection = !!v; + return this; + }; + + /** + * Sets the reconnection attempts config. + * + * @param {Number} max reconnection attempts before giving up + * @return {Manager} self or value + * @api public + */ + + Manager.prototype.reconnectionAttempts = function (v) { + if (!arguments.length) return this._reconnectionAttempts; + this._reconnectionAttempts = v; + return this; + }; + + /** + * Sets the delay between reconnections. + * + * @param {Number} delay + * @return {Manager} self or value + * @api public + */ + + Manager.prototype.reconnectionDelay = function (v) { + if (!arguments.length) return this._reconnectionDelay; + this._reconnectionDelay = v; + this.backoff && this.backoff.setMin(v); + return this; + }; + + Manager.prototype.randomizationFactor = function (v) { + if (!arguments.length) return this._randomizationFactor; + this._randomizationFactor = v; + this.backoff && this.backoff.setJitter(v); + return this; + }; + + /** + * Sets the maximum delay between reconnections. + * + * @param {Number} delay + * @return {Manager} self or value + * @api public + */ + + Manager.prototype.reconnectionDelayMax = function (v) { + if (!arguments.length) return this._reconnectionDelayMax; + this._reconnectionDelayMax = v; + this.backoff && this.backoff.setMax(v); + return this; + }; + + /** + * Sets the connection timeout. `false` to disable + * + * @return {Manager} self or value + * @api public + */ + + Manager.prototype.timeout = function (v) { + if (!arguments.length) return this._timeout; + this._timeout = v; + return this; + }; + + /** + * Starts trying to reconnect if reconnection is enabled and we have not + * started reconnecting yet + * + * @api private + */ + + Manager.prototype.maybeReconnectOnOpen = function () { + // Only try to reconnect if it's the first time we're connecting + if (!this.reconnecting && this._reconnection && this.backoff.attempts === 0) { + // keeps reconnection from firing twice for the same reconnection loop + this.reconnect(); + } + }; + + /** + * Sets the current transport `socket`. + * + * @param {Function} optional, callback + * @return {Manager} self + * @api public + */ + + Manager.prototype.open = Manager.prototype.connect = function (fn, opts) { + debug('readyState %s', this.readyState); + if (~this.readyState.indexOf('open')) return this; + + debug('opening %s', this.uri); + this.engine = eio(this.uri, this.opts); + var socket = this.engine; + var self = this; + this.readyState = 'opening'; + this.skipReconnect = false; + + // emit `open` + var openSub = on(socket, 'open', function () { + self.onopen(); + fn && fn(); + }); + + // emit `connect_error` + var errorSub = on(socket, 'error', function (data) { + debug('connect_error'); + self.cleanup(); + self.readyState = 'closed'; + self.emitAll('connect_error', data); + if (fn) { + var err = new Error('Connection error'); + err.data = data; + fn(err); + } else { + // Only do this if there is no fn to handle the error + self.maybeReconnectOnOpen(); + } + }); + + // emit `connect_timeout` + if (false !== this._timeout) { + var timeout = this._timeout; + debug('connect attempt will timeout after %d', timeout); + + // set timer + var timer = setTimeout(function () { + debug('connect attempt timed out after %d', timeout); + openSub.destroy(); + socket.close(); + socket.emit('error', 'timeout'); + self.emitAll('connect_timeout', timeout); + }, timeout); + + this.subs.push({ + destroy: function destroy() { + clearTimeout(timer); + } + }); + } + + this.subs.push(openSub); + this.subs.push(errorSub); + + return this; + }; + + /** + * Called upon transport open. + * + * @api private + */ + + Manager.prototype.onopen = function () { + debug('open'); + + // clear old subs + this.cleanup(); + + // mark as open + this.readyState = 'open'; + this.emit('open'); + + // add new subs + var socket = this.engine; + this.subs.push(on(socket, 'data', bind(this, 'ondata'))); + this.subs.push(on(socket, 'ping', bind(this, 'onping'))); + this.subs.push(on(socket, 'pong', bind(this, 'onpong'))); + this.subs.push(on(socket, 'error', bind(this, 'onerror'))); + this.subs.push(on(socket, 'close', bind(this, 'onclose'))); + this.subs.push(on(this.decoder, 'decoded', bind(this, 'ondecoded'))); + }; + + /** + * Called upon a ping. + * + * @api private + */ + + Manager.prototype.onping = function () { + this.lastPing = new Date(); + this.emitAll('ping'); + }; + + /** + * Called upon a packet. + * + * @api private + */ + + Manager.prototype.onpong = function () { + this.emitAll('pong', new Date() - this.lastPing); + }; + + /** + * Called with data. + * + * @api private + */ + + Manager.prototype.ondata = function (data) { + this.decoder.add(data); + }; + + /** + * Called when parser fully decodes a packet. + * + * @api private + */ + + Manager.prototype.ondecoded = function (packet) { + this.emit('packet', packet); + }; + + /** + * Called upon socket error. + * + * @api private + */ + + Manager.prototype.onerror = function (err) { + debug('error', err); + this.emitAll('error', err); + }; + + /** + * Creates a new socket for the given `nsp`. + * + * @return {Socket} + * @api public + */ + + Manager.prototype.socket = function (nsp, opts) { + var socket = this.nsps[nsp]; + if (!socket) { + socket = new Socket(this, nsp, opts); + this.nsps[nsp] = socket; + var self = this; + socket.on('connecting', onConnecting); + socket.on('connect', function () { + socket.id = self.engine.id; + }); + + if (this.autoConnect) { + // manually call here since connecting evnet is fired before listening + onConnecting(); + } + } + + function onConnecting() { + if (!~indexOf(self.connecting, socket)) { + self.connecting.push(socket); + } + } + + return socket; + }; + + /** + * Called upon a socket close. + * + * @param {Socket} socket + */ + + Manager.prototype.destroy = function (socket) { + var index = indexOf(this.connecting, socket); + if (~index) this.connecting.splice(index, 1); + if (this.connecting.length) return; + + this.close(); + }; + + /** + * Writes a packet. + * + * @param {Object} packet + * @api private + */ + + Manager.prototype.packet = function (packet) { + debug('writing packet %j', packet); + var self = this; + if (packet.query && packet.type === 0) packet.nsp += '?' + packet.query; + + if (!self.encoding) { + // encode, then write to engine with result + self.encoding = true; + this.encoder.encode(packet, function (encodedPackets) { + for (var i = 0; i < encodedPackets.length; i++) { + self.engine.write(encodedPackets[i], packet.options); + } + self.encoding = false; + self.processPacketQueue(); + }); + } else { + // add packet to the queue + self.packetBuffer.push(packet); + } + }; + + /** + * If packet buffer is non-empty, begins encoding the + * next packet in line. + * + * @api private + */ + + Manager.prototype.processPacketQueue = function () { + if (this.packetBuffer.length > 0 && !this.encoding) { + var pack = this.packetBuffer.shift(); + this.packet(pack); + } + }; + + /** + * Clean up transport subscriptions and packet buffer. + * + * @api private + */ + + Manager.prototype.cleanup = function () { + debug('cleanup'); + + var subsLength = this.subs.length; + for (var i = 0; i < subsLength; i++) { + var sub = this.subs.shift(); + sub.destroy(); + } + + this.packetBuffer = []; + this.encoding = false; + this.lastPing = null; + + this.decoder.destroy(); + }; + + /** + * Close the current socket. + * + * @api private + */ + + Manager.prototype.close = Manager.prototype.disconnect = function () { + debug('disconnect'); + this.skipReconnect = true; + this.reconnecting = false; + if ('opening' === this.readyState) { + // `onclose` will not fire because + // an open event never happened + this.cleanup(); + } + this.backoff.reset(); + this.readyState = 'closed'; + if (this.engine) this.engine.close(); + }; + + /** + * Called upon engine close. + * + * @api private + */ + + Manager.prototype.onclose = function (reason) { + debug('onclose'); + + this.cleanup(); + this.backoff.reset(); + this.readyState = 'closed'; + this.emit('close', reason); + + if (this._reconnection && !this.skipReconnect) { + this.reconnect(); + } + }; + + /** + * Attempt a reconnection. + * + * @api private + */ + + Manager.prototype.reconnect = function () { + if (this.reconnecting || this.skipReconnect) return this; + + var self = this; + + if (this.backoff.attempts >= this._reconnectionAttempts) { + debug('reconnect failed'); + this.backoff.reset(); + this.emitAll('reconnect_failed'); + this.reconnecting = false; + } else { + var delay = this.backoff.duration(); + debug('will wait %dms before reconnect attempt', delay); + + this.reconnecting = true; + var timer = setTimeout(function () { + if (self.skipReconnect) return; + + debug('attempting reconnect'); + self.emitAll('reconnect_attempt', self.backoff.attempts); + self.emitAll('reconnecting', self.backoff.attempts); + + // check again for the case socket closed in above events + if (self.skipReconnect) return; + + self.open(function (err) { + if (err) { + debug('reconnect attempt error'); + self.reconnecting = false; + self.reconnect(); + self.emitAll('reconnect_error', err.data); + } else { + debug('reconnect success'); + self.onreconnect(); + } + }); + }, delay); + + this.subs.push({ + destroy: function destroy() { + clearTimeout(timer); + } + }); + } + }; + + /** + * Called upon successful reconnect. + * + * @api private + */ + + Manager.prototype.onreconnect = function () { + var attempt = this.backoff.attempts; + this.reconnecting = false; + this.backoff.reset(); + this.updateSocketIds(); + this.emitAll('reconnect', attempt); + }; + +/***/ }, +/* 14 */ +/***/ function(module, exports, __webpack_require__) { + + + module.exports = __webpack_require__(15); + + +/***/ }, +/* 15 */ +/***/ function(module, exports, __webpack_require__) { + + + module.exports = __webpack_require__(16); + + /** + * Exports parser + * + * @api public + * + */ + module.exports.parser = __webpack_require__(23); + + +/***/ }, +/* 16 */ +/***/ function(module, exports, __webpack_require__) { + + /* WEBPACK VAR INJECTION */(function(global) {/** + * Module dependencies. + */ + + var transports = __webpack_require__(17); + var Emitter = __webpack_require__(31); + var debug = __webpack_require__(3)('engine.io-client:socket'); + var index = __webpack_require__(38); + var parser = __webpack_require__(23); + var parseuri = __webpack_require__(2); + var parsejson = __webpack_require__(39); + var parseqs = __webpack_require__(32); + + /** + * Module exports. + */ + + module.exports = Socket; + + /** + * Socket constructor. + * + * @param {String|Object} uri or options + * @param {Object} options + * @api public + */ + + function Socket (uri, opts) { + if (!(this instanceof Socket)) return new Socket(uri, opts); + + opts = opts || {}; + + if (uri && 'object' === typeof uri) { + opts = uri; + uri = null; + } + + if (uri) { + uri = parseuri(uri); + opts.hostname = uri.host; + opts.secure = uri.protocol === 'https' || uri.protocol === 'wss'; + opts.port = uri.port; + if (uri.query) opts.query = uri.query; + } else if (opts.host) { + opts.hostname = parseuri(opts.host).host; + } + + this.secure = null != opts.secure ? opts.secure + : (global.location && 'https:' === location.protocol); + + if (opts.hostname && !opts.port) { + // if no port is specified manually, use the protocol default + opts.port = this.secure ? '443' : '80'; + } + + this.agent = opts.agent || false; + this.hostname = opts.hostname || + (global.location ? location.hostname : 'localhost'); + this.port = opts.port || (global.location && location.port + ? location.port + : (this.secure ? 443 : 80)); + this.query = opts.query || {}; + if ('string' === typeof this.query) this.query = parseqs.decode(this.query); + this.upgrade = false !== opts.upgrade; + this.path = (opts.path || '/engine.io').replace(/\/$/, '') + '/'; + this.forceJSONP = !!opts.forceJSONP; + this.jsonp = false !== opts.jsonp; + this.forceBase64 = !!opts.forceBase64; + this.enablesXDR = !!opts.enablesXDR; + this.timestampParam = opts.timestampParam || 't'; + this.timestampRequests = opts.timestampRequests; + this.transports = opts.transports || ['polling', 'websocket']; + this.readyState = ''; + this.writeBuffer = []; + this.prevBufferLen = 0; + this.policyPort = opts.policyPort || 843; + this.rememberUpgrade = opts.rememberUpgrade || false; + this.binaryType = null; + this.onlyBinaryUpgrades = opts.onlyBinaryUpgrades; + this.perMessageDeflate = false !== opts.perMessageDeflate ? (opts.perMessageDeflate || {}) : false; + + if (true === this.perMessageDeflate) this.perMessageDeflate = {}; + if (this.perMessageDeflate && null == this.perMessageDeflate.threshold) { + this.perMessageDeflate.threshold = 1024; + } + + // SSL options for Node.js client + this.pfx = opts.pfx || null; + this.key = opts.key || null; + this.passphrase = opts.passphrase || null; + this.cert = opts.cert || null; + this.ca = opts.ca || null; + this.ciphers = opts.ciphers || null; + this.rejectUnauthorized = opts.rejectUnauthorized === undefined ? null : opts.rejectUnauthorized; + + // other options for Node.js client + var freeGlobal = typeof global === 'object' && global; + if (freeGlobal.global === freeGlobal) { + if (opts.extraHeaders && Object.keys(opts.extraHeaders).length > 0) { + this.extraHeaders = opts.extraHeaders; + } + } + + // set on handshake + this.id = null; + this.upgrades = null; + this.pingInterval = null; + this.pingTimeout = null; + + // set on heartbeat + this.pingIntervalTimer = null; + this.pingTimeoutTimer = null; + + this.open(); + } + + Socket.priorWebsocketSuccess = false; + + /** + * Mix in `Emitter`. + */ + + Emitter(Socket.prototype); + + /** + * Protocol version. + * + * @api public + */ + + Socket.protocol = parser.protocol; // this is an int + + /** + * Expose deps for legacy compatibility + * and standalone browser access. + */ + + Socket.Socket = Socket; + Socket.Transport = __webpack_require__(22); + Socket.transports = __webpack_require__(17); + Socket.parser = __webpack_require__(23); + + /** + * Creates transport of the given type. + * + * @param {String} transport name + * @return {Transport} + * @api private + */ + + Socket.prototype.createTransport = function (name) { + debug('creating transport "%s"', name); + var query = clone(this.query); + + // append engine.io protocol identifier + query.EIO = parser.protocol; + + // transport name + query.transport = name; + + // session id if we already have one + if (this.id) query.sid = this.id; + + var transport = new transports[name]({ + agent: this.agent, + hostname: this.hostname, + port: this.port, + secure: this.secure, + path: this.path, + query: query, + forceJSONP: this.forceJSONP, + jsonp: this.jsonp, + forceBase64: this.forceBase64, + enablesXDR: this.enablesXDR, + timestampRequests: this.timestampRequests, + timestampParam: this.timestampParam, + policyPort: this.policyPort, + socket: this, + pfx: this.pfx, + key: this.key, + passphrase: this.passphrase, + cert: this.cert, + ca: this.ca, + ciphers: this.ciphers, + rejectUnauthorized: this.rejectUnauthorized, + perMessageDeflate: this.perMessageDeflate, + extraHeaders: this.extraHeaders + }); + + return transport; + }; + + function clone (obj) { + var o = {}; + for (var i in obj) { + if (obj.hasOwnProperty(i)) { + o[i] = obj[i]; + } + } + return o; + } + + /** + * Initializes transport to use and starts probe. + * + * @api private + */ + Socket.prototype.open = function () { + var transport; + if (this.rememberUpgrade && Socket.priorWebsocketSuccess && this.transports.indexOf('websocket') !== -1) { + transport = 'websocket'; + } else if (0 === this.transports.length) { + // Emit error on next tick so it can be listened to + var self = this; + setTimeout(function () { + self.emit('error', 'No transports available'); + }, 0); + return; + } else { + transport = this.transports[0]; + } + this.readyState = 'opening'; + + // Retry with the next transport if the transport is disabled (jsonp: false) + try { + transport = this.createTransport(transport); + } catch (e) { + this.transports.shift(); + this.open(); + return; + } + + transport.open(); + this.setTransport(transport); + }; + + /** + * Sets the current transport. Disables the existing one (if any). + * + * @api private + */ + + Socket.prototype.setTransport = function (transport) { + debug('setting transport %s', transport.name); + var self = this; + + if (this.transport) { + debug('clearing existing transport %s', this.transport.name); + this.transport.removeAllListeners(); + } + + // set up transport + this.transport = transport; + + // set up transport listeners + transport + .on('drain', function () { + self.onDrain(); + }) + .on('packet', function (packet) { + self.onPacket(packet); + }) + .on('error', function (e) { + self.onError(e); + }) + .on('close', function () { + self.onClose('transport close'); + }); + }; + + /** + * Probes a transport. + * + * @param {String} transport name + * @api private + */ + + Socket.prototype.probe = function (name) { + debug('probing transport "%s"', name); + var transport = this.createTransport(name, { probe: 1 }); + var failed = false; + var self = this; + + Socket.priorWebsocketSuccess = false; + + function onTransportOpen () { + if (self.onlyBinaryUpgrades) { + var upgradeLosesBinary = !this.supportsBinary && self.transport.supportsBinary; + failed = failed || upgradeLosesBinary; + } + if (failed) return; + + debug('probe transport "%s" opened', name); + transport.send([{ type: 'ping', data: 'probe' }]); + transport.once('packet', function (msg) { + if (failed) return; + if ('pong' === msg.type && 'probe' === msg.data) { + debug('probe transport "%s" pong', name); + self.upgrading = true; + self.emit('upgrading', transport); + if (!transport) return; + Socket.priorWebsocketSuccess = 'websocket' === transport.name; + + debug('pausing current transport "%s"', self.transport.name); + self.transport.pause(function () { + if (failed) return; + if ('closed' === self.readyState) return; + debug('changing transport and sending upgrade packet'); + + cleanup(); + + self.setTransport(transport); + transport.send([{ type: 'upgrade' }]); + self.emit('upgrade', transport); + transport = null; + self.upgrading = false; + self.flush(); + }); + } else { + debug('probe transport "%s" failed', name); + var err = new Error('probe error'); + err.transport = transport.name; + self.emit('upgradeError', err); + } + }); + } + + function freezeTransport () { + if (failed) return; + + // Any callback called by transport should be ignored since now + failed = true; + + cleanup(); + + transport.close(); + transport = null; + } + + // Handle any error that happens while probing + function onerror (err) { + var error = new Error('probe error: ' + err); + error.transport = transport.name; + + freezeTransport(); + + debug('probe transport "%s" failed because of error: %s', name, err); + + self.emit('upgradeError', error); + } + + function onTransportClose () { + onerror('transport closed'); + } + + // When the socket is closed while we're probing + function onclose () { + onerror('socket closed'); + } + + // When the socket is upgraded while we're probing + function onupgrade (to) { + if (transport && to.name !== transport.name) { + debug('"%s" works - aborting "%s"', to.name, transport.name); + freezeTransport(); + } + } + + // Remove all listeners on the transport and on self + function cleanup () { + transport.removeListener('open', onTransportOpen); + transport.removeListener('error', onerror); + transport.removeListener('close', onTransportClose); + self.removeListener('close', onclose); + self.removeListener('upgrading', onupgrade); + } + + transport.once('open', onTransportOpen); + transport.once('error', onerror); + transport.once('close', onTransportClose); + + this.once('close', onclose); + this.once('upgrading', onupgrade); + + transport.open(); + }; + + /** + * Called when connection is deemed open. + * + * @api public + */ + + Socket.prototype.onOpen = function () { + debug('socket open'); + this.readyState = 'open'; + Socket.priorWebsocketSuccess = 'websocket' === this.transport.name; + this.emit('open'); + this.flush(); + + // we check for `readyState` in case an `open` + // listener already closed the socket + if ('open' === this.readyState && this.upgrade && this.transport.pause) { + debug('starting upgrade probes'); + for (var i = 0, l = this.upgrades.length; i < l; i++) { + this.probe(this.upgrades[i]); + } + } + }; + + /** + * Handles a packet. + * + * @api private + */ + + Socket.prototype.onPacket = function (packet) { + if ('opening' === this.readyState || 'open' === this.readyState) { + debug('socket receive: type "%s", data "%s"', packet.type, packet.data); + + this.emit('packet', packet); + + // Socket is live - any packet counts + this.emit('heartbeat'); + + switch (packet.type) { + case 'open': + this.onHandshake(parsejson(packet.data)); + break; + + case 'pong': + this.setPing(); + this.emit('pong'); + break; + + case 'error': + var err = new Error('server error'); + err.code = packet.data; + this.onError(err); + break; + + case 'message': + this.emit('data', packet.data); + this.emit('message', packet.data); + break; + } + } else { + debug('packet received with socket readyState "%s"', this.readyState); + } + }; + + /** + * Called upon handshake completion. + * + * @param {Object} handshake obj + * @api private + */ + + Socket.prototype.onHandshake = function (data) { + this.emit('handshake', data); + this.id = data.sid; + this.transport.query.sid = data.sid; + this.upgrades = this.filterUpgrades(data.upgrades); + this.pingInterval = data.pingInterval; + this.pingTimeout = data.pingTimeout; + this.onOpen(); + // In case open handler closes socket + if ('closed' === this.readyState) return; + this.setPing(); + + // Prolong liveness of socket on heartbeat + this.removeListener('heartbeat', this.onHeartbeat); + this.on('heartbeat', this.onHeartbeat); + }; + + /** + * Resets ping timeout. + * + * @api private + */ + + Socket.prototype.onHeartbeat = function (timeout) { + clearTimeout(this.pingTimeoutTimer); + var self = this; + self.pingTimeoutTimer = setTimeout(function () { + if ('closed' === self.readyState) return; + self.onClose('ping timeout'); + }, timeout || (self.pingInterval + self.pingTimeout)); + }; + + /** + * Pings server every `this.pingInterval` and expects response + * within `this.pingTimeout` or closes connection. + * + * @api private + */ + + Socket.prototype.setPing = function () { + var self = this; + clearTimeout(self.pingIntervalTimer); + self.pingIntervalTimer = setTimeout(function () { + debug('writing ping packet - expecting pong within %sms', self.pingTimeout); + self.ping(); + self.onHeartbeat(self.pingTimeout); + }, self.pingInterval); + }; + + /** + * Sends a ping packet. + * + * @api private + */ + + Socket.prototype.ping = function () { + var self = this; + this.sendPacket('ping', function () { + self.emit('ping'); + }); + }; + + /** + * Called on `drain` event + * + * @api private + */ + + Socket.prototype.onDrain = function () { + this.writeBuffer.splice(0, this.prevBufferLen); + + // setting prevBufferLen = 0 is very important + // for example, when upgrading, upgrade packet is sent over, + // and a nonzero prevBufferLen could cause problems on `drain` + this.prevBufferLen = 0; + + if (0 === this.writeBuffer.length) { + this.emit('drain'); + } else { + this.flush(); + } + }; + + /** + * Flush write buffers. + * + * @api private + */ + + Socket.prototype.flush = function () { + if ('closed' !== this.readyState && this.transport.writable && + !this.upgrading && this.writeBuffer.length) { + debug('flushing %d packets in socket', this.writeBuffer.length); + this.transport.send(this.writeBuffer); + // keep track of current length of writeBuffer + // splice writeBuffer and callbackBuffer on `drain` + this.prevBufferLen = this.writeBuffer.length; + this.emit('flush'); + } + }; + + /** + * Sends a message. + * + * @param {String} message. + * @param {Function} callback function. + * @param {Object} options. + * @return {Socket} for chaining. + * @api public + */ + + Socket.prototype.write = + Socket.prototype.send = function (msg, options, fn) { + this.sendPacket('message', msg, options, fn); + return this; + }; + + /** + * Sends a packet. + * + * @param {String} packet type. + * @param {String} data. + * @param {Object} options. + * @param {Function} callback function. + * @api private + */ + + Socket.prototype.sendPacket = function (type, data, options, fn) { + if ('function' === typeof data) { + fn = data; + data = undefined; + } + + if ('function' === typeof options) { + fn = options; + options = null; + } + + if ('closing' === this.readyState || 'closed' === this.readyState) { + return; + } + + options = options || {}; + options.compress = false !== options.compress; + + var packet = { + type: type, + data: data, + options: options + }; + this.emit('packetCreate', packet); + this.writeBuffer.push(packet); + if (fn) this.once('flush', fn); + this.flush(); + }; + + /** + * Closes the connection. + * + * @api private + */ + + Socket.prototype.close = function () { + if ('opening' === this.readyState || 'open' === this.readyState) { + this.readyState = 'closing'; + + var self = this; + + if (this.writeBuffer.length) { + this.once('drain', function () { + if (this.upgrading) { + waitForUpgrade(); + } else { + close(); + } + }); + } else if (this.upgrading) { + waitForUpgrade(); + } else { + close(); + } + } + + function close () { + self.onClose('forced close'); + debug('socket closing - telling transport to close'); + self.transport.close(); + } + + function cleanupAndClose () { + self.removeListener('upgrade', cleanupAndClose); + self.removeListener('upgradeError', cleanupAndClose); + close(); + } + + function waitForUpgrade () { + // wait for upgrade to finish since we can't send packets while pausing a transport + self.once('upgrade', cleanupAndClose); + self.once('upgradeError', cleanupAndClose); + } + + return this; + }; + + /** + * Called upon transport error + * + * @api private + */ + + Socket.prototype.onError = function (err) { + debug('socket error %j', err); + Socket.priorWebsocketSuccess = false; + this.emit('error', err); + this.onClose('transport error', err); + }; + + /** + * Called upon transport close. + * + * @api private + */ + + Socket.prototype.onClose = function (reason, desc) { + if ('opening' === this.readyState || 'open' === this.readyState || 'closing' === this.readyState) { + debug('socket close with reason: "%s"', reason); + var self = this; + + // clear timers + clearTimeout(this.pingIntervalTimer); + clearTimeout(this.pingTimeoutTimer); + + // stop event from firing again for transport + this.transport.removeAllListeners('close'); + + // ensure transport won't stay open + this.transport.close(); + + // ignore further transport communication + this.transport.removeAllListeners(); + + // set ready state + this.readyState = 'closed'; + + // clear session id + this.id = null; + + // emit close event + this.emit('close', reason, desc); + + // clean buffers after, so users can still + // grab the buffers on `close` event + self.writeBuffer = []; + self.prevBufferLen = 0; + } + }; + + /** + * Filters upgrades, returning only those matching client transports. + * + * @param {Array} server upgrades + * @api private + * + */ + + Socket.prototype.filterUpgrades = function (upgrades) { + var filteredUpgrades = []; + for (var i = 0, j = upgrades.length; i < j; i++) { + if (~index(this.transports, upgrades[i])) filteredUpgrades.push(upgrades[i]); + } + return filteredUpgrades; + }; + + /* WEBPACK VAR INJECTION */}.call(exports, (function() { return this; }()))) + +/***/ }, +/* 17 */ +/***/ function(module, exports, __webpack_require__) { + + /* WEBPACK VAR INJECTION */(function(global) {/** + * Module dependencies + */ + + var XMLHttpRequest = __webpack_require__(18); + var XHR = __webpack_require__(20); + var JSONP = __webpack_require__(35); + var websocket = __webpack_require__(36); + + /** + * Export transports. + */ + + exports.polling = polling; + exports.websocket = websocket; + + /** + * Polling transport polymorphic constructor. + * Decides on xhr vs jsonp based on feature detection. + * + * @api private + */ + + function polling (opts) { + var xhr; + var xd = false; + var xs = false; + var jsonp = false !== opts.jsonp; + + if (global.location) { + var isSSL = 'https:' === location.protocol; + var port = location.port; + + // some user agents have empty `location.port` + if (!port) { + port = isSSL ? 443 : 80; + } + + xd = opts.hostname !== location.hostname || port !== opts.port; + xs = opts.secure !== isSSL; + } + + opts.xdomain = xd; + opts.xscheme = xs; + xhr = new XMLHttpRequest(opts); + + if ('open' in xhr && !opts.forceJSONP) { + return new XHR(opts); + } else { + if (!jsonp) throw new Error('JSONP disabled'); + return new JSONP(opts); + } + } + + /* WEBPACK VAR INJECTION */}.call(exports, (function() { return this; }()))) + +/***/ }, +/* 18 */ +/***/ function(module, exports, __webpack_require__) { + + // browser shim for xmlhttprequest module + + // Indicate to eslint that ActiveXObject is global + /* global ActiveXObject */ + + var hasCORS = __webpack_require__(19); + + module.exports = function (opts) { + var xdomain = opts.xdomain; + + // scheme must be same when usign XDomainRequest + // http://blogs.msdn.com/b/ieinternals/archive/2010/05/13/xdomainrequest-restrictions-limitations-and-workarounds.aspx + var xscheme = opts.xscheme; + + // XDomainRequest has a flow of not sending cookie, therefore it should be disabled as a default. + // https://github.com/Automattic/engine.io-client/pull/217 + var enablesXDR = opts.enablesXDR; + + // XMLHttpRequest can be disabled on IE + try { + if ('undefined' !== typeof XMLHttpRequest && (!xdomain || hasCORS)) { + return new XMLHttpRequest(); + } + } catch (e) { } + + // Use XDomainRequest for IE8 if enablesXDR is true + // because loading bar keeps flashing when using jsonp-polling + // https://github.com/yujiosaka/socke.io-ie8-loading-example + try { + if ('undefined' !== typeof XDomainRequest && !xscheme && enablesXDR) { + return new XDomainRequest(); + } + } catch (e) { } + + if (!xdomain) { + try { + return new ActiveXObject('Microsoft.XMLHTTP'); + } catch (e) { } + } + }; + + +/***/ }, +/* 19 */ +/***/ function(module, exports) { + + + /** + * Module exports. + * + * Logic borrowed from Modernizr: + * + * - https://github.com/Modernizr/Modernizr/blob/master/feature-detects/cors.js + */ + + try { + module.exports = typeof XMLHttpRequest !== 'undefined' && + 'withCredentials' in new XMLHttpRequest(); + } catch (err) { + // if XMLHttp support is disabled in IE then it will throw + // when trying to create + module.exports = false; + } + + +/***/ }, +/* 20 */ +/***/ function(module, exports, __webpack_require__) { + + /* WEBPACK VAR INJECTION */(function(global) {/** + * Module requirements. + */ + + var XMLHttpRequest = __webpack_require__(18); + var Polling = __webpack_require__(21); + var Emitter = __webpack_require__(31); + var inherit = __webpack_require__(33); + var debug = __webpack_require__(3)('engine.io-client:polling-xhr'); + + /** + * Module exports. + */ + + module.exports = XHR; + module.exports.Request = Request; + + /** + * Empty function + */ + + function empty () {} + + /** + * XHR Polling constructor. + * + * @param {Object} opts + * @api public + */ + + function XHR (opts) { + Polling.call(this, opts); + + if (global.location) { + var isSSL = 'https:' === location.protocol; + var port = location.port; + + // some user agents have empty `location.port` + if (!port) { + port = isSSL ? 443 : 80; + } + + this.xd = opts.hostname !== global.location.hostname || + port !== opts.port; + this.xs = opts.secure !== isSSL; + } else { + this.extraHeaders = opts.extraHeaders; + } + } + + /** + * Inherits from Polling. + */ + + inherit(XHR, Polling); + + /** + * XHR supports binary + */ + + XHR.prototype.supportsBinary = true; + + /** + * Creates a request. + * + * @param {String} method + * @api private + */ + + XHR.prototype.request = function (opts) { + opts = opts || {}; + opts.uri = this.uri(); + opts.xd = this.xd; + opts.xs = this.xs; + opts.agent = this.agent || false; + opts.supportsBinary = this.supportsBinary; + opts.enablesXDR = this.enablesXDR; + + // SSL options for Node.js client + opts.pfx = this.pfx; + opts.key = this.key; + opts.passphrase = this.passphrase; + opts.cert = this.cert; + opts.ca = this.ca; + opts.ciphers = this.ciphers; + opts.rejectUnauthorized = this.rejectUnauthorized; + + // other options for Node.js client + opts.extraHeaders = this.extraHeaders; + + return new Request(opts); + }; + + /** + * Sends data. + * + * @param {String} data to send. + * @param {Function} called upon flush. + * @api private + */ + + XHR.prototype.doWrite = function (data, fn) { + var isBinary = typeof data !== 'string' && data !== undefined; + var req = this.request({ method: 'POST', data: data, isBinary: isBinary }); + var self = this; + req.on('success', fn); + req.on('error', function (err) { + self.onError('xhr post error', err); + }); + this.sendXhr = req; + }; + + /** + * Starts a poll cycle. + * + * @api private + */ + + XHR.prototype.doPoll = function () { + debug('xhr poll'); + var req = this.request(); + var self = this; + req.on('data', function (data) { + self.onData(data); + }); + req.on('error', function (err) { + self.onError('xhr poll error', err); + }); + this.pollXhr = req; + }; + + /** + * Request constructor + * + * @param {Object} options + * @api public + */ + + function Request (opts) { + this.method = opts.method || 'GET'; + this.uri = opts.uri; + this.xd = !!opts.xd; + this.xs = !!opts.xs; + this.async = false !== opts.async; + this.data = undefined !== opts.data ? opts.data : null; + this.agent = opts.agent; + this.isBinary = opts.isBinary; + this.supportsBinary = opts.supportsBinary; + this.enablesXDR = opts.enablesXDR; + + // SSL options for Node.js client + this.pfx = opts.pfx; + this.key = opts.key; + this.passphrase = opts.passphrase; + this.cert = opts.cert; + this.ca = opts.ca; + this.ciphers = opts.ciphers; + this.rejectUnauthorized = opts.rejectUnauthorized; + + // other options for Node.js client + this.extraHeaders = opts.extraHeaders; + + this.create(); + } + + /** + * Mix in `Emitter`. + */ + + Emitter(Request.prototype); + + /** + * Creates the XHR object and sends the request. + * + * @api private + */ + + Request.prototype.create = function () { + var opts = { agent: this.agent, xdomain: this.xd, xscheme: this.xs, enablesXDR: this.enablesXDR }; + + // SSL options for Node.js client + opts.pfx = this.pfx; + opts.key = this.key; + opts.passphrase = this.passphrase; + opts.cert = this.cert; + opts.ca = this.ca; + opts.ciphers = this.ciphers; + opts.rejectUnauthorized = this.rejectUnauthorized; + + var xhr = this.xhr = new XMLHttpRequest(opts); + var self = this; + + try { + debug('xhr open %s: %s', this.method, this.uri); + xhr.open(this.method, this.uri, this.async); + try { + if (this.extraHeaders) { + xhr.setDisableHeaderCheck(true); + for (var i in this.extraHeaders) { + if (this.extraHeaders.hasOwnProperty(i)) { + xhr.setRequestHeader(i, this.extraHeaders[i]); + } + } + } + } catch (e) {} + if (this.supportsBinary) { + // This has to be done after open because Firefox is stupid + // http://stackoverflow.com/questions/13216903/get-binary-data-with-xmlhttprequest-in-a-firefox-extension + xhr.responseType = 'arraybuffer'; + } + + if ('POST' === this.method) { + try { + if (this.isBinary) { + xhr.setRequestHeader('Content-type', 'application/octet-stream'); + } else { + xhr.setRequestHeader('Content-type', 'text/plain;charset=UTF-8'); + } + } catch (e) {} + } + + // ie6 check + if ('withCredentials' in xhr) { + xhr.withCredentials = true; + } + + if (this.hasXDR()) { + xhr.onload = function () { + self.onLoad(); + }; + xhr.onerror = function () { + self.onError(xhr.responseText); + }; + } else { + xhr.onreadystatechange = function () { + if (4 !== xhr.readyState) return; + if (200 === xhr.status || 1223 === xhr.status) { + self.onLoad(); + } else { + // make sure the `error` event handler that's user-set + // does not throw in the same tick and gets caught here + setTimeout(function () { + self.onError(xhr.status); + }, 0); + } + }; + } + + debug('xhr data %s', this.data); + xhr.send(this.data); + } catch (e) { + // Need to defer since .create() is called directly fhrom the constructor + // and thus the 'error' event can only be only bound *after* this exception + // occurs. Therefore, also, we cannot throw here at all. + setTimeout(function () { + self.onError(e); + }, 0); + return; + } + + if (global.document) { + this.index = Request.requestsCount++; + Request.requests[this.index] = this; + } + }; + + /** + * Called upon successful response. + * + * @api private + */ + + Request.prototype.onSuccess = function () { + this.emit('success'); + this.cleanup(); + }; + + /** + * Called if we have data. + * + * @api private + */ + + Request.prototype.onData = function (data) { + this.emit('data', data); + this.onSuccess(); + }; + + /** + * Called upon error. + * + * @api private + */ + + Request.prototype.onError = function (err) { + this.emit('error', err); + this.cleanup(true); + }; + + /** + * Cleans up house. + * + * @api private + */ + + Request.prototype.cleanup = function (fromError) { + if ('undefined' === typeof this.xhr || null === this.xhr) { + return; + } + // xmlhttprequest + if (this.hasXDR()) { + this.xhr.onload = this.xhr.onerror = empty; + } else { + this.xhr.onreadystatechange = empty; + } + + if (fromError) { + try { + this.xhr.abort(); + } catch (e) {} + } + + if (global.document) { + delete Request.requests[this.index]; + } + + this.xhr = null; + }; + + /** + * Called upon load. + * + * @api private + */ + + Request.prototype.onLoad = function () { + var data; + try { + var contentType; + try { + contentType = this.xhr.getResponseHeader('Content-Type').split(';')[0]; + } catch (e) {} + if (contentType === 'application/octet-stream') { + data = this.xhr.response || this.xhr.responseText; + } else { + if (!this.supportsBinary) { + data = this.xhr.responseText; + } else { + try { + data = String.fromCharCode.apply(null, new Uint8Array(this.xhr.response)); + } catch (e) { + var ui8Arr = new Uint8Array(this.xhr.response); + var dataArray = []; + for (var idx = 0, length = ui8Arr.length; idx < length; idx++) { + dataArray.push(ui8Arr[idx]); + } + + data = String.fromCharCode.apply(null, dataArray); + } + } + } + } catch (e) { + this.onError(e); + } + if (null != data) { + this.onData(data); + } + }; + + /** + * Check if it has XDomainRequest. + * + * @api private + */ + + Request.prototype.hasXDR = function () { + return 'undefined' !== typeof global.XDomainRequest && !this.xs && this.enablesXDR; + }; + + /** + * Aborts the request. + * + * @api public + */ + + Request.prototype.abort = function () { + this.cleanup(); + }; + + /** + * Aborts pending requests when unloading the window. This is needed to prevent + * memory leaks (e.g. when using IE) and to ensure that no spurious error is + * emitted. + */ + + Request.requestsCount = 0; + Request.requests = {}; + + if (global.document) { + if (global.attachEvent) { + global.attachEvent('onunload', unloadHandler); + } else if (global.addEventListener) { + global.addEventListener('beforeunload', unloadHandler, false); + } + } + + function unloadHandler () { + for (var i in Request.requests) { + if (Request.requests.hasOwnProperty(i)) { + Request.requests[i].abort(); + } + } + } + + /* WEBPACK VAR INJECTION */}.call(exports, (function() { return this; }()))) + +/***/ }, +/* 21 */ +/***/ function(module, exports, __webpack_require__) { + + /** + * Module dependencies. + */ + + var Transport = __webpack_require__(22); + var parseqs = __webpack_require__(32); + var parser = __webpack_require__(23); + var inherit = __webpack_require__(33); + var yeast = __webpack_require__(34); + var debug = __webpack_require__(3)('engine.io-client:polling'); + + /** + * Module exports. + */ + + module.exports = Polling; + + /** + * Is XHR2 supported? + */ + + var hasXHR2 = (function () { + var XMLHttpRequest = __webpack_require__(18); + var xhr = new XMLHttpRequest({ xdomain: false }); + return null != xhr.responseType; + })(); + + /** + * Polling interface. + * + * @param {Object} opts + * @api private + */ + + function Polling (opts) { + var forceBase64 = (opts && opts.forceBase64); + if (!hasXHR2 || forceBase64) { + this.supportsBinary = false; + } + Transport.call(this, opts); + } + + /** + * Inherits from Transport. + */ + + inherit(Polling, Transport); + + /** + * Transport name. + */ + + Polling.prototype.name = 'polling'; + + /** + * Opens the socket (triggers polling). We write a PING message to determine + * when the transport is open. + * + * @api private + */ + + Polling.prototype.doOpen = function () { + this.poll(); + }; + + /** + * Pauses polling. + * + * @param {Function} callback upon buffers are flushed and transport is paused + * @api private + */ + + Polling.prototype.pause = function (onPause) { + var self = this; + + this.readyState = 'pausing'; + + function pause () { + debug('paused'); + self.readyState = 'paused'; + onPause(); + } + + if (this.polling || !this.writable) { + var total = 0; + + if (this.polling) { + debug('we are currently polling - waiting to pause'); + total++; + this.once('pollComplete', function () { + debug('pre-pause polling complete'); + --total || pause(); + }); + } + + if (!this.writable) { + debug('we are currently writing - waiting to pause'); + total++; + this.once('drain', function () { + debug('pre-pause writing complete'); + --total || pause(); + }); + } + } else { + pause(); + } + }; + + /** + * Starts polling cycle. + * + * @api public + */ + + Polling.prototype.poll = function () { + debug('polling'); + this.polling = true; + this.doPoll(); + this.emit('poll'); + }; + + /** + * Overloads onData to detect payloads. + * + * @api private + */ + + Polling.prototype.onData = function (data) { + var self = this; + debug('polling got data %s', data); + var callback = function (packet, index, total) { + // if its the first message we consider the transport open + if ('opening' === self.readyState) { + self.onOpen(); + } + + // if its a close packet, we close the ongoing requests + if ('close' === packet.type) { + self.onClose(); + return false; + } + + // otherwise bypass onData and handle the message + self.onPacket(packet); + }; + + // decode payload + parser.decodePayload(data, this.socket.binaryType, callback); + + // if an event did not trigger closing + if ('closed' !== this.readyState) { + // if we got data we're not polling + this.polling = false; + this.emit('pollComplete'); + + if ('open' === this.readyState) { + this.poll(); + } else { + debug('ignoring poll - transport state "%s"', this.readyState); + } + } + }; + + /** + * For polling, send a close packet. + * + * @api private + */ + + Polling.prototype.doClose = function () { + var self = this; + + function close () { + debug('writing close packet'); + self.write([{ type: 'close' }]); + } + + if ('open' === this.readyState) { + debug('transport open - closing'); + close(); + } else { + // in case we're trying to close while + // handshaking is in progress (GH-164) + debug('transport not open - deferring close'); + this.once('open', close); + } + }; + + /** + * Writes a packets payload. + * + * @param {Array} data packets + * @param {Function} drain callback + * @api private + */ + + Polling.prototype.write = function (packets) { + var self = this; + this.writable = false; + var callbackfn = function () { + self.writable = true; + self.emit('drain'); + }; + + parser.encodePayload(packets, this.supportsBinary, function (data) { + self.doWrite(data, callbackfn); + }); + }; + + /** + * Generates uri for connection. + * + * @api private + */ + + Polling.prototype.uri = function () { + var query = this.query || {}; + var schema = this.secure ? 'https' : 'http'; + var port = ''; + + // cache busting is forced + if (false !== this.timestampRequests) { + query[this.timestampParam] = yeast(); + } + + if (!this.supportsBinary && !query.sid) { + query.b64 = 1; + } + + query = parseqs.encode(query); + + // avoid port if default for schema + if (this.port && (('https' === schema && this.port !== 443) || + ('http' === schema && this.port !== 80))) { + port = ':' + this.port; + } + + // prepend ? to query + if (query.length) { + query = '?' + query; + } + + var ipv6 = this.hostname.indexOf(':') !== -1; + return schema + '://' + (ipv6 ? '[' + this.hostname + ']' : this.hostname) + port + this.path + query; + }; + + +/***/ }, +/* 22 */ +/***/ function(module, exports, __webpack_require__) { + + /** + * Module dependencies. + */ + + var parser = __webpack_require__(23); + var Emitter = __webpack_require__(31); + + /** + * Module exports. + */ + + module.exports = Transport; + + /** + * Transport abstract constructor. + * + * @param {Object} options. + * @api private + */ + + function Transport (opts) { + this.path = opts.path; + this.hostname = opts.hostname; + this.port = opts.port; + this.secure = opts.secure; + this.query = opts.query; + this.timestampParam = opts.timestampParam; + this.timestampRequests = opts.timestampRequests; + this.readyState = ''; + this.agent = opts.agent || false; + this.socket = opts.socket; + this.enablesXDR = opts.enablesXDR; + + // SSL options for Node.js client + this.pfx = opts.pfx; + this.key = opts.key; + this.passphrase = opts.passphrase; + this.cert = opts.cert; + this.ca = opts.ca; + this.ciphers = opts.ciphers; + this.rejectUnauthorized = opts.rejectUnauthorized; + + // other options for Node.js client + this.extraHeaders = opts.extraHeaders; + } + + /** + * Mix in `Emitter`. + */ + + Emitter(Transport.prototype); + + /** + * Emits an error. + * + * @param {String} str + * @return {Transport} for chaining + * @api public + */ + + Transport.prototype.onError = function (msg, desc) { + var err = new Error(msg); + err.type = 'TransportError'; + err.description = desc; + this.emit('error', err); + return this; + }; + + /** + * Opens the transport. + * + * @api public + */ + + Transport.prototype.open = function () { + if ('closed' === this.readyState || '' === this.readyState) { + this.readyState = 'opening'; + this.doOpen(); + } + + return this; + }; + + /** + * Closes the transport. + * + * @api private + */ + + Transport.prototype.close = function () { + if ('opening' === this.readyState || 'open' === this.readyState) { + this.doClose(); + this.onClose(); + } + + return this; + }; + + /** + * Sends multiple packets. + * + * @param {Array} packets + * @api private + */ + + Transport.prototype.send = function (packets) { + if ('open' === this.readyState) { + this.write(packets); + } else { + throw new Error('Transport not open'); + } + }; + + /** + * Called upon open + * + * @api private + */ + + Transport.prototype.onOpen = function () { + this.readyState = 'open'; + this.writable = true; + this.emit('open'); + }; + + /** + * Called with data. + * + * @param {String} data + * @api private + */ + + Transport.prototype.onData = function (data) { + var packet = parser.decodePacket(data, this.socket.binaryType); + this.onPacket(packet); + }; + + /** + * Called with a decoded packet. + */ + + Transport.prototype.onPacket = function (packet) { + this.emit('packet', packet); + }; + + /** + * Called upon close. + * + * @api private + */ + + Transport.prototype.onClose = function () { + this.readyState = 'closed'; + this.emit('close'); + }; + + +/***/ }, +/* 23 */ +/***/ function(module, exports, __webpack_require__) { + + /* WEBPACK VAR INJECTION */(function(global) {/** + * Module dependencies. + */ + + var keys = __webpack_require__(24); + var hasBinary = __webpack_require__(25); + var sliceBuffer = __webpack_require__(26); + var after = __webpack_require__(27); + var utf8 = __webpack_require__(28); + + var base64encoder; + if (global && global.ArrayBuffer) { + base64encoder = __webpack_require__(29); + } + + /** + * Check if we are running an android browser. That requires us to use + * ArrayBuffer with polling transports... + * + * http://ghinda.net/jpeg-blob-ajax-android/ + */ + + var isAndroid = typeof navigator !== 'undefined' && /Android/i.test(navigator.userAgent); + + /** + * Check if we are running in PhantomJS. + * Uploading a Blob with PhantomJS does not work correctly, as reported here: + * https://github.com/ariya/phantomjs/issues/11395 + * @type boolean + */ + var isPhantomJS = typeof navigator !== 'undefined' && /PhantomJS/i.test(navigator.userAgent); + + /** + * When true, avoids using Blobs to encode payloads. + * @type boolean + */ + var dontSendBlobs = isAndroid || isPhantomJS; + + /** + * Current protocol version. + */ + + exports.protocol = 3; + + /** + * Packet types. + */ + + var packets = exports.packets = { + open: 0 // non-ws + , close: 1 // non-ws + , ping: 2 + , pong: 3 + , message: 4 + , upgrade: 5 + , noop: 6 + }; + + var packetslist = keys(packets); + + /** + * Premade error packet. + */ + + var err = { type: 'error', data: 'parser error' }; + + /** + * Create a blob api even for blob builder when vendor prefixes exist + */ + + var Blob = __webpack_require__(30); + + /** + * Encodes a packet. + * + * [ ] + * + * Example: + * + * 5hello world + * 3 + * 4 + * + * Binary is encoded in an identical principle + * + * @api private + */ + + exports.encodePacket = function (packet, supportsBinary, utf8encode, callback) { + if ('function' == typeof supportsBinary) { + callback = supportsBinary; + supportsBinary = false; + } + + if ('function' == typeof utf8encode) { + callback = utf8encode; + utf8encode = null; + } + + var data = (packet.data === undefined) + ? undefined + : packet.data.buffer || packet.data; + + if (global.ArrayBuffer && data instanceof ArrayBuffer) { + return encodeArrayBuffer(packet, supportsBinary, callback); + } else if (Blob && data instanceof global.Blob) { + return encodeBlob(packet, supportsBinary, callback); + } + + // might be an object with { base64: true, data: dataAsBase64String } + if (data && data.base64) { + return encodeBase64Object(packet, callback); + } + + // Sending data as a utf-8 string + var encoded = packets[packet.type]; + + // data fragment is optional + if (undefined !== packet.data) { + encoded += utf8encode ? utf8.encode(String(packet.data)) : String(packet.data); + } + + return callback('' + encoded); + + }; + + function encodeBase64Object(packet, callback) { + // packet data is an object { base64: true, data: dataAsBase64String } + var message = 'b' + exports.packets[packet.type] + packet.data.data; + return callback(message); + } + + /** + * Encode packet helpers for binary types + */ + + function encodeArrayBuffer(packet, supportsBinary, callback) { + if (!supportsBinary) { + return exports.encodeBase64Packet(packet, callback); + } + + var data = packet.data; + var contentArray = new Uint8Array(data); + var resultBuffer = new Uint8Array(1 + data.byteLength); + + resultBuffer[0] = packets[packet.type]; + for (var i = 0; i < contentArray.length; i++) { + resultBuffer[i+1] = contentArray[i]; + } + + return callback(resultBuffer.buffer); + } + + function encodeBlobAsArrayBuffer(packet, supportsBinary, callback) { + if (!supportsBinary) { + return exports.encodeBase64Packet(packet, callback); + } + + var fr = new FileReader(); + fr.onload = function() { + packet.data = fr.result; + exports.encodePacket(packet, supportsBinary, true, callback); + }; + return fr.readAsArrayBuffer(packet.data); + } + + function encodeBlob(packet, supportsBinary, callback) { + if (!supportsBinary) { + return exports.encodeBase64Packet(packet, callback); + } + + if (dontSendBlobs) { + return encodeBlobAsArrayBuffer(packet, supportsBinary, callback); + } + + var length = new Uint8Array(1); + length[0] = packets[packet.type]; + var blob = new Blob([length.buffer, packet.data]); + + return callback(blob); + } + + /** + * Encodes a packet with binary data in a base64 string + * + * @param {Object} packet, has `type` and `data` + * @return {String} base64 encoded message + */ + + exports.encodeBase64Packet = function(packet, callback) { + var message = 'b' + exports.packets[packet.type]; + if (Blob && packet.data instanceof global.Blob) { + var fr = new FileReader(); + fr.onload = function() { + var b64 = fr.result.split(',')[1]; + callback(message + b64); + }; + return fr.readAsDataURL(packet.data); + } + + var b64data; + try { + b64data = String.fromCharCode.apply(null, new Uint8Array(packet.data)); + } catch (e) { + // iPhone Safari doesn't let you apply with typed arrays + var typed = new Uint8Array(packet.data); + var basic = new Array(typed.length); + for (var i = 0; i < typed.length; i++) { + basic[i] = typed[i]; + } + b64data = String.fromCharCode.apply(null, basic); + } + message += global.btoa(b64data); + return callback(message); + }; + + /** + * Decodes a packet. Changes format to Blob if requested. + * + * @return {Object} with `type` and `data` (if any) + * @api private + */ + + exports.decodePacket = function (data, binaryType, utf8decode) { + if (data === undefined) { + return err; + } + // String data + if (typeof data == 'string') { + if (data.charAt(0) == 'b') { + return exports.decodeBase64Packet(data.substr(1), binaryType); + } + + if (utf8decode) { + data = tryDecode(data); + if (data === false) { + return err; + } + } + var type = data.charAt(0); + + if (Number(type) != type || !packetslist[type]) { + return err; + } + + if (data.length > 1) { + return { type: packetslist[type], data: data.substring(1) }; + } else { + return { type: packetslist[type] }; + } + } + + var asArray = new Uint8Array(data); + var type = asArray[0]; + var rest = sliceBuffer(data, 1); + if (Blob && binaryType === 'blob') { + rest = new Blob([rest]); + } + return { type: packetslist[type], data: rest }; + }; + + function tryDecode(data) { + try { + data = utf8.decode(data); + } catch (e) { + return false; + } + return data; + } + + /** + * Decodes a packet encoded in a base64 string + * + * @param {String} base64 encoded message + * @return {Object} with `type` and `data` (if any) + */ + + exports.decodeBase64Packet = function(msg, binaryType) { + var type = packetslist[msg.charAt(0)]; + if (!base64encoder) { + return { type: type, data: { base64: true, data: msg.substr(1) } }; + } + + var data = base64encoder.decode(msg.substr(1)); + + if (binaryType === 'blob' && Blob) { + data = new Blob([data]); + } + + return { type: type, data: data }; + }; + + /** + * Encodes multiple messages (payload). + * + * :data + * + * Example: + * + * 11:hello world2:hi + * + * If any contents are binary, they will be encoded as base64 strings. Base64 + * encoded strings are marked with a b before the length specifier + * + * @param {Array} packets + * @api private + */ + + exports.encodePayload = function (packets, supportsBinary, callback) { + if (typeof supportsBinary == 'function') { + callback = supportsBinary; + supportsBinary = null; + } + + var isBinary = hasBinary(packets); + + if (supportsBinary && isBinary) { + if (Blob && !dontSendBlobs) { + return exports.encodePayloadAsBlob(packets, callback); + } + + return exports.encodePayloadAsArrayBuffer(packets, callback); + } + + if (!packets.length) { + return callback('0:'); + } + + function setLengthHeader(message) { + return message.length + ':' + message; + } + + function encodeOne(packet, doneCallback) { + exports.encodePacket(packet, !isBinary ? false : supportsBinary, true, function(message) { + doneCallback(null, setLengthHeader(message)); + }); + } + + map(packets, encodeOne, function(err, results) { + return callback(results.join('')); + }); + }; + + /** + * Async array map using after + */ + + function map(ary, each, done) { + var result = new Array(ary.length); + var next = after(ary.length, done); + + var eachWithIndex = function(i, el, cb) { + each(el, function(error, msg) { + result[i] = msg; + cb(error, result); + }); + }; + + for (var i = 0; i < ary.length; i++) { + eachWithIndex(i, ary[i], next); + } + } + + /* + * Decodes data when a payload is maybe expected. Possible binary contents are + * decoded from their base64 representation + * + * @param {String} data, callback method + * @api public + */ + + exports.decodePayload = function (data, binaryType, callback) { + if (typeof data != 'string') { + return exports.decodePayloadAsBinary(data, binaryType, callback); + } + + if (typeof binaryType === 'function') { + callback = binaryType; + binaryType = null; + } + + var packet; + if (data == '') { + // parser error - ignoring payload + return callback(err, 0, 1); + } + + var length = '' + , n, msg; + + for (var i = 0, l = data.length; i < l; i++) { + var chr = data.charAt(i); + + if (':' != chr) { + length += chr; + } else { + if ('' == length || (length != (n = Number(length)))) { + // parser error - ignoring payload + return callback(err, 0, 1); + } + + msg = data.substr(i + 1, n); + + if (length != msg.length) { + // parser error - ignoring payload + return callback(err, 0, 1); + } + + if (msg.length) { + packet = exports.decodePacket(msg, binaryType, true); + + if (err.type == packet.type && err.data == packet.data) { + // parser error in individual packet - ignoring payload + return callback(err, 0, 1); + } + + var ret = callback(packet, i + n, l); + if (false === ret) return; + } + + // advance cursor + i += n; + length = ''; + } + } + + if (length != '') { + // parser error - ignoring payload + return callback(err, 0, 1); + } + + }; + + /** + * Encodes multiple messages (payload) as binary. + * + * <1 = binary, 0 = string>[...] + * + * Example: + * 1 3 255 1 2 3, if the binary contents are interpreted as 8 bit integers + * + * @param {Array} packets + * @return {ArrayBuffer} encoded payload + * @api private + */ + + exports.encodePayloadAsArrayBuffer = function(packets, callback) { + if (!packets.length) { + return callback(new ArrayBuffer(0)); + } + + function encodeOne(packet, doneCallback) { + exports.encodePacket(packet, true, true, function(data) { + return doneCallback(null, data); + }); + } + + map(packets, encodeOne, function(err, encodedPackets) { + var totalLength = encodedPackets.reduce(function(acc, p) { + var len; + if (typeof p === 'string'){ + len = p.length; + } else { + len = p.byteLength; + } + return acc + len.toString().length + len + 2; // string/binary identifier + separator = 2 + }, 0); + + var resultArray = new Uint8Array(totalLength); + + var bufferIndex = 0; + encodedPackets.forEach(function(p) { + var isString = typeof p === 'string'; + var ab = p; + if (isString) { + var view = new Uint8Array(p.length); + for (var i = 0; i < p.length; i++) { + view[i] = p.charCodeAt(i); + } + ab = view.buffer; + } + + if (isString) { // not true binary + resultArray[bufferIndex++] = 0; + } else { // true binary + resultArray[bufferIndex++] = 1; + } + + var lenStr = ab.byteLength.toString(); + for (var i = 0; i < lenStr.length; i++) { + resultArray[bufferIndex++] = parseInt(lenStr[i]); + } + resultArray[bufferIndex++] = 255; + + var view = new Uint8Array(ab); + for (var i = 0; i < view.length; i++) { + resultArray[bufferIndex++] = view[i]; + } + }); + + return callback(resultArray.buffer); + }); + }; + + /** + * Encode as Blob + */ + + exports.encodePayloadAsBlob = function(packets, callback) { + function encodeOne(packet, doneCallback) { + exports.encodePacket(packet, true, true, function(encoded) { + var binaryIdentifier = new Uint8Array(1); + binaryIdentifier[0] = 1; + if (typeof encoded === 'string') { + var view = new Uint8Array(encoded.length); + for (var i = 0; i < encoded.length; i++) { + view[i] = encoded.charCodeAt(i); + } + encoded = view.buffer; + binaryIdentifier[0] = 0; + } + + var len = (encoded instanceof ArrayBuffer) + ? encoded.byteLength + : encoded.size; + + var lenStr = len.toString(); + var lengthAry = new Uint8Array(lenStr.length + 1); + for (var i = 0; i < lenStr.length; i++) { + lengthAry[i] = parseInt(lenStr[i]); + } + lengthAry[lenStr.length] = 255; + + if (Blob) { + var blob = new Blob([binaryIdentifier.buffer, lengthAry.buffer, encoded]); + doneCallback(null, blob); + } + }); + } + + map(packets, encodeOne, function(err, results) { + return callback(new Blob(results)); + }); + }; + + /* + * Decodes data when a payload is maybe expected. Strings are decoded by + * interpreting each byte as a key code for entries marked to start with 0. See + * description of encodePayloadAsBinary + * + * @param {ArrayBuffer} data, callback method + * @api public + */ + + exports.decodePayloadAsBinary = function (data, binaryType, callback) { + if (typeof binaryType === 'function') { + callback = binaryType; + binaryType = null; + } + + var bufferTail = data; + var buffers = []; + + var numberTooLong = false; + while (bufferTail.byteLength > 0) { + var tailArray = new Uint8Array(bufferTail); + var isString = tailArray[0] === 0; + var msgLength = ''; + + for (var i = 1; ; i++) { + if (tailArray[i] == 255) break; + + if (msgLength.length > 310) { + numberTooLong = true; + break; + } + + msgLength += tailArray[i]; + } + + if(numberTooLong) return callback(err, 0, 1); + + bufferTail = sliceBuffer(bufferTail, 2 + msgLength.length); + msgLength = parseInt(msgLength); + + var msg = sliceBuffer(bufferTail, 0, msgLength); + if (isString) { + try { + msg = String.fromCharCode.apply(null, new Uint8Array(msg)); + } catch (e) { + // iPhone Safari doesn't let you apply to typed arrays + var typed = new Uint8Array(msg); + msg = ''; + for (var i = 0; i < typed.length; i++) { + msg += String.fromCharCode(typed[i]); + } + } + } + + buffers.push(msg); + bufferTail = sliceBuffer(bufferTail, msgLength); + } + + var total = buffers.length; + buffers.forEach(function(buffer, i) { + callback(exports.decodePacket(buffer, binaryType, true), i, total); + }); + }; + + /* WEBPACK VAR INJECTION */}.call(exports, (function() { return this; }()))) + +/***/ }, +/* 24 */ +/***/ function(module, exports) { + + + /** + * Gets the keys for an object. + * + * @return {Array} keys + * @api private + */ + + module.exports = Object.keys || function keys (obj){ + var arr = []; + var has = Object.prototype.hasOwnProperty; + + for (var i in obj) { + if (has.call(obj, i)) { + arr.push(i); + } + } + return arr; + }; + + +/***/ }, +/* 25 */ +/***/ function(module, exports, __webpack_require__) { + + /* WEBPACK VAR INJECTION */(function(global) { + /* + * Module requirements. + */ + + var isArray = __webpack_require__(9); + + /** + * Module exports. + */ + + module.exports = hasBinary; + + /** + * Checks for binary data. + * + * Right now only Buffer and ArrayBuffer are supported.. + * + * @param {Object} anything + * @api public + */ + + function hasBinary(data) { + + function _hasBinary(obj) { + if (!obj) return false; + + if ( (global.Buffer && global.Buffer.isBuffer(obj)) || + (global.ArrayBuffer && obj instanceof ArrayBuffer) || + (global.Blob && obj instanceof Blob) || + (global.File && obj instanceof File) + ) { + return true; + } + + if (isArray(obj)) { + for (var i = 0; i < obj.length; i++) { + if (_hasBinary(obj[i])) { + return true; + } + } + } else if (obj && 'object' == typeof obj) { + if (obj.toJSON) { + obj = obj.toJSON(); + } + + for (var key in obj) { + if (Object.prototype.hasOwnProperty.call(obj, key) && _hasBinary(obj[key])) { + return true; + } + } + } + + return false; + } + + return _hasBinary(data); + } + + /* WEBPACK VAR INJECTION */}.call(exports, (function() { return this; }()))) + +/***/ }, +/* 26 */ +/***/ function(module, exports) { + + /** + * An abstraction for slicing an arraybuffer even when + * ArrayBuffer.prototype.slice is not supported + * + * @api public + */ + + module.exports = function(arraybuffer, start, end) { + var bytes = arraybuffer.byteLength; + start = start || 0; + end = end || bytes; + + if (arraybuffer.slice) { return arraybuffer.slice(start, end); } + + if (start < 0) { start += bytes; } + if (end < 0) { end += bytes; } + if (end > bytes) { end = bytes; } + + if (start >= bytes || start >= end || bytes === 0) { + return new ArrayBuffer(0); + } + + var abv = new Uint8Array(arraybuffer); + var result = new Uint8Array(end - start); + for (var i = start, ii = 0; i < end; i++, ii++) { + result[ii] = abv[i]; + } + return result.buffer; + }; + + +/***/ }, +/* 27 */ +/***/ function(module, exports) { + + module.exports = after + + function after(count, callback, err_cb) { + var bail = false + err_cb = err_cb || noop + proxy.count = count + + return (count === 0) ? callback() : proxy + + function proxy(err, result) { + if (proxy.count <= 0) { + throw new Error('after called too many times') + } + --proxy.count + + // after first error, rest are passed to err_cb + if (err) { + bail = true + callback(err) + // future error callbacks will go to error handler + callback = err_cb + } else if (proxy.count === 0 && !bail) { + callback(null, result) + } + } + } + + function noop() {} + + +/***/ }, +/* 28 */ +/***/ function(module, exports, __webpack_require__) { + + var __WEBPACK_AMD_DEFINE_RESULT__;/* WEBPACK VAR INJECTION */(function(module, global) {/*! https://mths.be/wtf8 v1.0.0 by @mathias */ + ;(function(root) { + + // Detect free variables `exports` + var freeExports = typeof exports == 'object' && exports; + + // Detect free variable `module` + var freeModule = typeof module == 'object' && module && + module.exports == freeExports && module; + + // Detect free variable `global`, from Node.js or Browserified code, + // and use it as `root` + var freeGlobal = typeof global == 'object' && global; + if (freeGlobal.global === freeGlobal || freeGlobal.window === freeGlobal) { + root = freeGlobal; + } + + /*--------------------------------------------------------------------------*/ + + var stringFromCharCode = String.fromCharCode; + + // Taken from https://mths.be/punycode + function ucs2decode(string) { + var output = []; + var counter = 0; + var length = string.length; + var value; + var extra; + while (counter < length) { + value = string.charCodeAt(counter++); + if (value >= 0xD800 && value <= 0xDBFF && counter < length) { + // high surrogate, and there is a next character + extra = string.charCodeAt(counter++); + if ((extra & 0xFC00) == 0xDC00) { // low surrogate + output.push(((value & 0x3FF) << 10) + (extra & 0x3FF) + 0x10000); + } else { + // unmatched surrogate; only append this code unit, in case the next + // code unit is the high surrogate of a surrogate pair + output.push(value); + counter--; + } + } else { + output.push(value); + } + } + return output; + } + + // Taken from https://mths.be/punycode + function ucs2encode(array) { + var length = array.length; + var index = -1; + var value; + var output = ''; + while (++index < length) { + value = array[index]; + if (value > 0xFFFF) { + value -= 0x10000; + output += stringFromCharCode(value >>> 10 & 0x3FF | 0xD800); + value = 0xDC00 | value & 0x3FF; + } + output += stringFromCharCode(value); + } + return output; + } + + /*--------------------------------------------------------------------------*/ + + function createByte(codePoint, shift) { + return stringFromCharCode(((codePoint >> shift) & 0x3F) | 0x80); + } + + function encodeCodePoint(codePoint) { + if ((codePoint & 0xFFFFFF80) == 0) { // 1-byte sequence + return stringFromCharCode(codePoint); + } + var symbol = ''; + if ((codePoint & 0xFFFFF800) == 0) { // 2-byte sequence + symbol = stringFromCharCode(((codePoint >> 6) & 0x1F) | 0xC0); + } + else if ((codePoint & 0xFFFF0000) == 0) { // 3-byte sequence + symbol = stringFromCharCode(((codePoint >> 12) & 0x0F) | 0xE0); + symbol += createByte(codePoint, 6); + } + else if ((codePoint & 0xFFE00000) == 0) { // 4-byte sequence + symbol = stringFromCharCode(((codePoint >> 18) & 0x07) | 0xF0); + symbol += createByte(codePoint, 12); + symbol += createByte(codePoint, 6); + } + symbol += stringFromCharCode((codePoint & 0x3F) | 0x80); + return symbol; + } + + function wtf8encode(string) { + var codePoints = ucs2decode(string); + var length = codePoints.length; + var index = -1; + var codePoint; + var byteString = ''; + while (++index < length) { + codePoint = codePoints[index]; + byteString += encodeCodePoint(codePoint); + } + return byteString; + } + + /*--------------------------------------------------------------------------*/ + + function readContinuationByte() { + if (byteIndex >= byteCount) { + throw Error('Invalid byte index'); + } + + var continuationByte = byteArray[byteIndex] & 0xFF; + byteIndex++; + + if ((continuationByte & 0xC0) == 0x80) { + return continuationByte & 0x3F; + } + + // If we end up here, it’s not a continuation byte. + throw Error('Invalid continuation byte'); + } + + function decodeSymbol() { + var byte1; + var byte2; + var byte3; + var byte4; + var codePoint; + + if (byteIndex > byteCount) { + throw Error('Invalid byte index'); + } + + if (byteIndex == byteCount) { + return false; + } + + // Read the first byte. + byte1 = byteArray[byteIndex] & 0xFF; + byteIndex++; + + // 1-byte sequence (no continuation bytes) + if ((byte1 & 0x80) == 0) { + return byte1; + } + + // 2-byte sequence + if ((byte1 & 0xE0) == 0xC0) { + var byte2 = readContinuationByte(); + codePoint = ((byte1 & 0x1F) << 6) | byte2; + if (codePoint >= 0x80) { + return codePoint; + } else { + throw Error('Invalid continuation byte'); + } + } + + // 3-byte sequence (may include unpaired surrogates) + if ((byte1 & 0xF0) == 0xE0) { + byte2 = readContinuationByte(); + byte3 = readContinuationByte(); + codePoint = ((byte1 & 0x0F) << 12) | (byte2 << 6) | byte3; + if (codePoint >= 0x0800) { + return codePoint; + } else { + throw Error('Invalid continuation byte'); + } + } + + // 4-byte sequence + if ((byte1 & 0xF8) == 0xF0) { + byte2 = readContinuationByte(); + byte3 = readContinuationByte(); + byte4 = readContinuationByte(); + codePoint = ((byte1 & 0x0F) << 0x12) | (byte2 << 0x0C) | + (byte3 << 0x06) | byte4; + if (codePoint >= 0x010000 && codePoint <= 0x10FFFF) { + return codePoint; + } + } + + throw Error('Invalid WTF-8 detected'); + } + + var byteArray; + var byteCount; + var byteIndex; + function wtf8decode(byteString) { + byteArray = ucs2decode(byteString); + byteCount = byteArray.length; + byteIndex = 0; + var codePoints = []; + var tmp; + while ((tmp = decodeSymbol()) !== false) { + codePoints.push(tmp); + } + return ucs2encode(codePoints); + } + + /*--------------------------------------------------------------------------*/ + + var wtf8 = { + 'version': '1.0.0', + 'encode': wtf8encode, + 'decode': wtf8decode + }; + + // Some AMD build optimizers, like r.js, check for specific condition patterns + // like the following: + if ( + true + ) { + !(__WEBPACK_AMD_DEFINE_RESULT__ = function() { + return wtf8; + }.call(exports, __webpack_require__, exports, module), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__)); + } else if (freeExports && !freeExports.nodeType) { + if (freeModule) { // in Node.js or RingoJS v0.8.0+ + freeModule.exports = wtf8; + } else { // in Narwhal or RingoJS v0.7.0- + var object = {}; + var hasOwnProperty = object.hasOwnProperty; + for (var key in wtf8) { + hasOwnProperty.call(wtf8, key) && (freeExports[key] = wtf8[key]); + } + } + } else { // in Rhino or a web browser + root.wtf8 = wtf8; + } + + }(this)); + + /* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(8)(module), (function() { return this; }()))) + +/***/ }, +/* 29 */ +/***/ function(module, exports) { + + /* + * base64-arraybuffer + * https://github.com/niklasvh/base64-arraybuffer + * + * Copyright (c) 2012 Niklas von Hertzen + * Licensed under the MIT license. + */ + (function(){ + "use strict"; + + var chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + + // Use a lookup table to find the index. + var lookup = new Uint8Array(256); + for (var i = 0; i < chars.length; i++) { + lookup[chars.charCodeAt(i)] = i; + } + + exports.encode = function(arraybuffer) { + var bytes = new Uint8Array(arraybuffer), + i, len = bytes.length, base64 = ""; + + for (i = 0; i < len; i+=3) { + base64 += chars[bytes[i] >> 2]; + base64 += chars[((bytes[i] & 3) << 4) | (bytes[i + 1] >> 4)]; + base64 += chars[((bytes[i + 1] & 15) << 2) | (bytes[i + 2] >> 6)]; + base64 += chars[bytes[i + 2] & 63]; + } + + if ((len % 3) === 2) { + base64 = base64.substring(0, base64.length - 1) + "="; + } else if (len % 3 === 1) { + base64 = base64.substring(0, base64.length - 2) + "=="; + } + + return base64; + }; + + exports.decode = function(base64) { + var bufferLength = base64.length * 0.75, + len = base64.length, i, p = 0, + encoded1, encoded2, encoded3, encoded4; + + if (base64[base64.length - 1] === "=") { + bufferLength--; + if (base64[base64.length - 2] === "=") { + bufferLength--; + } + } + + var arraybuffer = new ArrayBuffer(bufferLength), + bytes = new Uint8Array(arraybuffer); + + for (i = 0; i < len; i+=4) { + encoded1 = lookup[base64.charCodeAt(i)]; + encoded2 = lookup[base64.charCodeAt(i+1)]; + encoded3 = lookup[base64.charCodeAt(i+2)]; + encoded4 = lookup[base64.charCodeAt(i+3)]; + + bytes[p++] = (encoded1 << 2) | (encoded2 >> 4); + bytes[p++] = ((encoded2 & 15) << 4) | (encoded3 >> 2); + bytes[p++] = ((encoded3 & 3) << 6) | (encoded4 & 63); + } + + return arraybuffer; + }; + })(); + + +/***/ }, +/* 30 */ +/***/ function(module, exports) { + + /* WEBPACK VAR INJECTION */(function(global) {/** + * Create a blob builder even when vendor prefixes exist + */ + + var BlobBuilder = global.BlobBuilder + || global.WebKitBlobBuilder + || global.MSBlobBuilder + || global.MozBlobBuilder; + + /** + * Check if Blob constructor is supported + */ + + var blobSupported = (function() { + try { + var a = new Blob(['hi']); + return a.size === 2; + } catch(e) { + return false; + } + })(); + + /** + * Check if Blob constructor supports ArrayBufferViews + * Fails in Safari 6, so we need to map to ArrayBuffers there. + */ + + var blobSupportsArrayBufferView = blobSupported && (function() { + try { + var b = new Blob([new Uint8Array([1,2])]); + return b.size === 2; + } catch(e) { + return false; + } + })(); + + /** + * Check if BlobBuilder is supported + */ + + var blobBuilderSupported = BlobBuilder + && BlobBuilder.prototype.append + && BlobBuilder.prototype.getBlob; + + /** + * Helper function that maps ArrayBufferViews to ArrayBuffers + * Used by BlobBuilder constructor and old browsers that didn't + * support it in the Blob constructor. + */ + + function mapArrayBufferViews(ary) { + for (var i = 0; i < ary.length; i++) { + var chunk = ary[i]; + if (chunk.buffer instanceof ArrayBuffer) { + var buf = chunk.buffer; + + // if this is a subarray, make a copy so we only + // include the subarray region from the underlying buffer + if (chunk.byteLength !== buf.byteLength) { + var copy = new Uint8Array(chunk.byteLength); + copy.set(new Uint8Array(buf, chunk.byteOffset, chunk.byteLength)); + buf = copy.buffer; + } + + ary[i] = buf; + } + } + } + + function BlobBuilderConstructor(ary, options) { + options = options || {}; + + var bb = new BlobBuilder(); + mapArrayBufferViews(ary); + + for (var i = 0; i < ary.length; i++) { + bb.append(ary[i]); + } + + return (options.type) ? bb.getBlob(options.type) : bb.getBlob(); + }; + + function BlobConstructor(ary, options) { + mapArrayBufferViews(ary); + return new Blob(ary, options || {}); + }; + + module.exports = (function() { + if (blobSupported) { + return blobSupportsArrayBufferView ? global.Blob : BlobConstructor; + } else if (blobBuilderSupported) { + return BlobBuilderConstructor; + } else { + return undefined; + } + })(); + + /* WEBPACK VAR INJECTION */}.call(exports, (function() { return this; }()))) + +/***/ }, +/* 31 */ +/***/ function(module, exports) { + + + /** + * Expose `Emitter`. + */ + + module.exports = Emitter; + + /** + * Initialize a new `Emitter`. + * + * @api public + */ + + function Emitter(obj) { + if (obj) return mixin(obj); + }; + + /** + * Mixin the emitter properties. + * + * @param {Object} obj + * @return {Object} + * @api private + */ + + function mixin(obj) { + for (var key in Emitter.prototype) { + obj[key] = Emitter.prototype[key]; + } + return obj; + } + + /** + * Listen on the given `event` with `fn`. + * + * @param {String} event + * @param {Function} fn + * @return {Emitter} + * @api public + */ + + Emitter.prototype.on = + Emitter.prototype.addEventListener = function(event, fn){ + this._callbacks = this._callbacks || {}; + (this._callbacks[event] = this._callbacks[event] || []) + .push(fn); + return this; + }; + + /** + * Adds an `event` listener that will be invoked a single + * time then automatically removed. + * + * @param {String} event + * @param {Function} fn + * @return {Emitter} + * @api public + */ + + Emitter.prototype.once = function(event, fn){ + var self = this; + this._callbacks = this._callbacks || {}; + + function on() { + self.off(event, on); + fn.apply(this, arguments); + } + + on.fn = fn; + this.on(event, on); + return this; + }; + + /** + * Remove the given callback for `event` or all + * registered callbacks. + * + * @param {String} event + * @param {Function} fn + * @return {Emitter} + * @api public + */ + + Emitter.prototype.off = + Emitter.prototype.removeListener = + Emitter.prototype.removeAllListeners = + Emitter.prototype.removeEventListener = function(event, fn){ + this._callbacks = this._callbacks || {}; + + // all + if (0 == arguments.length) { + this._callbacks = {}; + return this; + } + + // specific event + var callbacks = this._callbacks[event]; + if (!callbacks) return this; + + // remove all handlers + if (1 == arguments.length) { + delete this._callbacks[event]; + return this; + } + + // remove specific handler + var cb; + for (var i = 0; i < callbacks.length; i++) { + cb = callbacks[i]; + if (cb === fn || cb.fn === fn) { + callbacks.splice(i, 1); + break; + } + } + return this; + }; + + /** + * Emit `event` with the given args. + * + * @param {String} event + * @param {Mixed} ... + * @return {Emitter} + */ + + Emitter.prototype.emit = function(event){ + this._callbacks = this._callbacks || {}; + var args = [].slice.call(arguments, 1) + , callbacks = this._callbacks[event]; + + if (callbacks) { + callbacks = callbacks.slice(0); + for (var i = 0, len = callbacks.length; i < len; ++i) { + callbacks[i].apply(this, args); + } + } + + return this; + }; + + /** + * Return array of callbacks for `event`. + * + * @param {String} event + * @return {Array} + * @api public + */ + + Emitter.prototype.listeners = function(event){ + this._callbacks = this._callbacks || {}; + return this._callbacks[event] || []; + }; + + /** + * Check if this emitter has `event` handlers. + * + * @param {String} event + * @return {Boolean} + * @api public + */ + + Emitter.prototype.hasListeners = function(event){ + return !! this.listeners(event).length; + }; + + +/***/ }, +/* 32 */ +/***/ function(module, exports) { + + /** + * Compiles a querystring + * Returns string representation of the object + * + * @param {Object} + * @api private + */ + + exports.encode = function (obj) { + var str = ''; + + for (var i in obj) { + if (obj.hasOwnProperty(i)) { + if (str.length) str += '&'; + str += encodeURIComponent(i) + '=' + encodeURIComponent(obj[i]); + } + } + + return str; + }; + + /** + * Parses a simple querystring into an object + * + * @param {String} qs + * @api private + */ + + exports.decode = function(qs){ + var qry = {}; + var pairs = qs.split('&'); + for (var i = 0, l = pairs.length; i < l; i++) { + var pair = pairs[i].split('='); + qry[decodeURIComponent(pair[0])] = decodeURIComponent(pair[1]); + } + return qry; + }; + + +/***/ }, +/* 33 */ +/***/ function(module, exports) { + + + module.exports = function(a, b){ + var fn = function(){}; + fn.prototype = b.prototype; + a.prototype = new fn; + a.prototype.constructor = a; + }; + +/***/ }, +/* 34 */ +/***/ function(module, exports) { + + 'use strict'; + + var alphabet = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-_'.split('') + , length = 64 + , map = {} + , seed = 0 + , i = 0 + , prev; + + /** + * Return a string representing the specified number. + * + * @param {Number} num The number to convert. + * @returns {String} The string representation of the number. + * @api public + */ + function encode(num) { + var encoded = ''; + + do { + encoded = alphabet[num % length] + encoded; + num = Math.floor(num / length); + } while (num > 0); + + return encoded; + } + + /** + * Return the integer value specified by the given string. + * + * @param {String} str The string to convert. + * @returns {Number} The integer value represented by the string. + * @api public + */ + function decode(str) { + var decoded = 0; + + for (i = 0; i < str.length; i++) { + decoded = decoded * length + map[str.charAt(i)]; + } + + return decoded; + } + + /** + * Yeast: A tiny growing id generator. + * + * @returns {String} A unique id. + * @api public + */ + function yeast() { + var now = encode(+new Date()); + + if (now !== prev) return seed = 0, prev = now; + return now +'.'+ encode(seed++); + } + + // + // Map each character to its index. + // + for (; i < length; i++) map[alphabet[i]] = i; + + // + // Expose the `yeast`, `encode` and `decode` functions. + // + yeast.encode = encode; + yeast.decode = decode; + module.exports = yeast; + + +/***/ }, +/* 35 */ +/***/ function(module, exports, __webpack_require__) { + + /* WEBPACK VAR INJECTION */(function(global) { + /** + * Module requirements. + */ + + var Polling = __webpack_require__(21); + var inherit = __webpack_require__(33); + + /** + * Module exports. + */ + + module.exports = JSONPPolling; + + /** + * Cached regular expressions. + */ + + var rNewline = /\n/g; + var rEscapedNewline = /\\n/g; + + /** + * Global JSONP callbacks. + */ + + var callbacks; + + /** + * Noop. + */ + + function empty () { } + + /** + * JSONP Polling constructor. + * + * @param {Object} opts. + * @api public + */ + + function JSONPPolling (opts) { + Polling.call(this, opts); + + this.query = this.query || {}; + + // define global callbacks array if not present + // we do this here (lazily) to avoid unneeded global pollution + if (!callbacks) { + // we need to consider multiple engines in the same page + if (!global.___eio) global.___eio = []; + callbacks = global.___eio; + } + + // callback identifier + this.index = callbacks.length; + + // add callback to jsonp global + var self = this; + callbacks.push(function (msg) { + self.onData(msg); + }); + + // append to query string + this.query.j = this.index; + + // prevent spurious errors from being emitted when the window is unloaded + if (global.document && global.addEventListener) { + global.addEventListener('beforeunload', function () { + if (self.script) self.script.onerror = empty; + }, false); + } + } + + /** + * Inherits from Polling. + */ + + inherit(JSONPPolling, Polling); + + /* + * JSONP only supports binary as base64 encoded strings + */ + + JSONPPolling.prototype.supportsBinary = false; + + /** + * Closes the socket. + * + * @api private + */ + + JSONPPolling.prototype.doClose = function () { + if (this.script) { + this.script.parentNode.removeChild(this.script); + this.script = null; + } + + if (this.form) { + this.form.parentNode.removeChild(this.form); + this.form = null; + this.iframe = null; + } + + Polling.prototype.doClose.call(this); + }; + + /** + * Starts a poll cycle. + * + * @api private + */ + + JSONPPolling.prototype.doPoll = function () { + var self = this; + var script = document.createElement('script'); + + if (this.script) { + this.script.parentNode.removeChild(this.script); + this.script = null; + } + + script.async = true; + script.src = this.uri(); + script.onerror = function (e) { + self.onError('jsonp poll error', e); + }; + + var insertAt = document.getElementsByTagName('script')[0]; + if (insertAt) { + insertAt.parentNode.insertBefore(script, insertAt); + } else { + (document.head || document.body).appendChild(script); + } + this.script = script; + + var isUAgecko = 'undefined' !== typeof navigator && /gecko/i.test(navigator.userAgent); + + if (isUAgecko) { + setTimeout(function () { + var iframe = document.createElement('iframe'); + document.body.appendChild(iframe); + document.body.removeChild(iframe); + }, 100); + } + }; + + /** + * Writes with a hidden iframe. + * + * @param {String} data to send + * @param {Function} called upon flush. + * @api private + */ + + JSONPPolling.prototype.doWrite = function (data, fn) { + var self = this; + + if (!this.form) { + var form = document.createElement('form'); + var area = document.createElement('textarea'); + var id = this.iframeId = 'eio_iframe_' + this.index; + var iframe; + + form.className = 'socketio'; + form.style.position = 'absolute'; + form.style.top = '-1000px'; + form.style.left = '-1000px'; + form.target = id; + form.method = 'POST'; + form.setAttribute('accept-charset', 'utf-8'); + area.name = 'd'; + form.appendChild(area); + document.body.appendChild(form); + + this.form = form; + this.area = area; + } + + this.form.action = this.uri(); + + function complete () { + initIframe(); + fn(); + } + + function initIframe () { + if (self.iframe) { + try { + self.form.removeChild(self.iframe); + } catch (e) { + self.onError('jsonp polling iframe removal error', e); + } + } + + try { + // ie6 dynamic iframes with target="" support (thanks Chris Lambacher) + var html = '