From b3e5a2d9050ec6c8517083fd9a3f000293537378 Mon Sep 17 00:00:00 2001 From: Thomas Jarrand Date: Tue, 12 Jan 2021 00:14:59 +0100 Subject: [PATCH 1/6] WIP --- package-lock.json | 55 ++++++++++++++- package.json | 3 +- src/client/components/Controls.js | 8 ++- src/client/components/Player.js | 22 ++++++ src/client/components/Socket.js | 28 ++++++++ src/client/components/video/Video.js | 17 ++++- src/client/container.js | 2 + src/client/peer/Distributor.js | 88 +++++++++++++++++++++++ src/client/peer/PeerManager.js | 67 ++++++++++++++++++ src/client/peer/Spectator.js | 100 +++++++++++++++++++++++++++ src/client/scenes/Room.js | 2 +- src/client/service/Api.js | 15 ++++ src/client/store/player.js | 6 +- src/client/translations/fr.json | 3 +- src/events/index.js | 4 ++ src/server/core/Server.js | 2 +- src/server/room/Room.js | 34 +++++++++ 17 files changed, 443 insertions(+), 13 deletions(-) create mode 100644 src/client/peer/Distributor.js create mode 100644 src/client/peer/PeerManager.js create mode 100644 src/client/peer/Spectator.js diff --git a/package-lock.json b/package-lock.json index dfcea74..e108eb1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,7 +18,8 @@ "redux": "^4.1.0", "redux-logger": "^3.0.6", "reset-css": "^5.0.1", - "tom32i-event-emitter.js": "^2.0.5" + "tom32i-event-emitter.js": "^2.0.5", + "webrtc-adapter": "^7.7.0" }, "devDependencies": { "@babel/core": "^7.14.0", @@ -5494,6 +5495,18 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/rtcpeerconnection-shim": { + "version": "1.2.15", + "resolved": "https://registry.npmjs.org/rtcpeerconnection-shim/-/rtcpeerconnection-shim-1.2.15.tgz", + "integrity": "sha512-C6DxhXt7bssQ1nHb154lqeL0SXz5Dx4RczXZu2Aa/L1NJFnEVDxFwCBo3fqtuljhHIGceg5JKBV4XJ0gW5JKyw==", + "dependencies": { + "sdp": "^2.6.0" + }, + "engines": { + "node": ">=6.0.0", + "npm": ">=3.10.0" + } + }, "node_modules/safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", @@ -5578,6 +5591,11 @@ "url": "https://opencollective.com/webpack" } }, + "node_modules/sdp": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/sdp/-/sdp-2.12.0.tgz", + "integrity": "sha512-jhXqQAQVM+8Xj5EjJGVweuEzgtGWb3tmEEpl3CLP3cStInSbVHSg0QWOGQzNq8pSID4JkpeV2mPqlMDLrm0/Vw==" + }, "node_modules/semver": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", @@ -6439,6 +6457,19 @@ "url": "https://opencollective.com/webpack" } }, + "node_modules/webrtc-adapter": { + "version": "7.7.1", + "resolved": "https://registry.npmjs.org/webrtc-adapter/-/webrtc-adapter-7.7.1.tgz", + "integrity": "sha512-TbrbBmiQBL9n0/5bvDdORc6ZfRY/Z7JnEj+EYOD1ghseZdpJ+nF2yx14k3LgQKc7JZnG7HAcL+zHnY25So9d7A==", + "dependencies": { + "rtcpeerconnection-shim": "^1.2.15", + "sdp": "^2.12.0" + }, + "engines": { + "node": ">=6.0.0", + "npm": ">=3.10.0" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -10530,6 +10561,14 @@ "glob": "^7.1.3" } }, + "rtcpeerconnection-shim": { + "version": "1.2.15", + "resolved": "https://registry.npmjs.org/rtcpeerconnection-shim/-/rtcpeerconnection-shim-1.2.15.tgz", + "integrity": "sha512-C6DxhXt7bssQ1nHb154lqeL0SXz5Dx4RczXZu2Aa/L1NJFnEVDxFwCBo3fqtuljhHIGceg5JKBV4XJ0gW5JKyw==", + "requires": { + "sdp": "^2.6.0" + } + }, "safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", @@ -10577,6 +10616,11 @@ "ajv-keywords": "^3.5.2" } }, + "sdp": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/sdp/-/sdp-2.12.0.tgz", + "integrity": "sha512-jhXqQAQVM+8Xj5EjJGVweuEzgtGWb3tmEEpl3CLP3cStInSbVHSg0QWOGQzNq8pSID4JkpeV2mPqlMDLrm0/Vw==" + }, "semver": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", @@ -11199,6 +11243,15 @@ "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==", "dev": true }, + "webrtc-adapter": { + "version": "7.7.1", + "resolved": "https://registry.npmjs.org/webrtc-adapter/-/webrtc-adapter-7.7.1.tgz", + "integrity": "sha512-TbrbBmiQBL9n0/5bvDdORc6ZfRY/Z7JnEj+EYOD1ghseZdpJ+nF2yx14k3LgQKc7JZnG7HAcL+zHnY25So9d7A==", + "requires": { + "rtcpeerconnection-shim": "^1.2.15", + "sdp": "^2.12.0" + } + }, "which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", diff --git a/package.json b/package.json index 8e310cb..b6672e1 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,8 @@ "redux": "^4.1.0", "redux-logger": "^3.0.6", "reset-css": "^5.0.1", - "tom32i-event-emitter.js": "^2.0.5" + "tom32i-event-emitter.js": "^2.0.5", + "webrtc-adapter": "^7.7.0" }, "devDependencies": { "@babel/core": "^7.14.0", diff --git a/src/client/components/Controls.js b/src/client/components/Controls.js index 8328744..5118a73 100644 --- a/src/client/components/Controls.js +++ b/src/client/components/Controls.js @@ -13,6 +13,11 @@ class Controls extends Component { onStop: PropTypes.func.isRequired, onBackward: PropTypes.func.isRequired, onForward: PropTypes.func.isRequired, + onStream: PropTypes.func, + }; + + static defaultProps = { + onStream: null, }; constructor(props) { @@ -102,7 +107,7 @@ class Controls extends Component { } render() { - const { playing, loaded, onStop, onBackward, onForward } = this.props; + const { playing, loaded, onStop, onBackward, onForward, onStream } = this.props; const playIcon = loaded ? playing ? 'icon-pause' : 'icon-play' : 'icon-loader'; const screenIcon = this.fullscreen ? 'icon-minimise' : 'icon-maximise'; @@ -110,6 +115,7 @@ class Controls extends Component {
diff --git a/src/client/components/Socket.js b/src/client/components/Socket.js index 23f752f..5fbf8bb 100644 --- a/src/client/components/Socket.js +++ b/src/client/components/Socket.js @@ -38,11 +38,15 @@ class Socket extends Component { super(props); this.api = get('api'); + this.peer = get('peer'); this.onError = this.onError.bind(this); this.onVideoFile = this.onVideoFile.bind(this); this.onVideoUrl = this.onVideoUrl.bind(this); this.onVideoYoutube = this.onVideoYoutube.bind(this); + this.onPeerOffer = this.onPeerOffer.bind(this); + this.onPeerAnswer = this.onPeerAnswer.bind(this); + this.onPeerCandidate = this.onPeerCandidate.bind(this); } componentDidMount() { @@ -65,6 +69,9 @@ class Socket extends Component { this.api.addEventListener('video:file', this.onVideoFile); this.api.addEventListener('video:url', this.onVideoUrl); this.api.addEventListener('video:youtube', this.onVideoYoutube); + this.api.addEventListener('peer:offer', this.onPeerOffer); + this.api.addEventListener('peer:answer', this.onPeerAnswer); + this.api.addEventListener('peer:candidate', this.onPeerCandidate); } componentWillUnmount() { @@ -85,6 +92,9 @@ class Socket extends Component { this.api.removeEventListener('video:file', this.onVideoFile); this.api.removeEventListener('video:url', this.onVideoUrl); this.api.removeEventListener('video:youtube', this.onVideoYoutube); + this.api.removeEventListener('peer:offer', this.onPeerOffer); + this.api.removeEventListener('peer:answer', this.onPeerAnswer); + this.api.removeEventListener('peer:candidate', this.onPeerCandidate); // Close connection this.api.leave(); @@ -123,6 +133,24 @@ class Socket extends Component { this.props.onVideo('file', name); } + onPeerOffer(event) { + const description = event.detail; + + this.peer.spectate(description); + + this.props.onVideo('peer'); + } + + onPeerAnswer(event) { + const description = event.detail; + this.peer.distributor.answer(description); + } + + onPeerCandidate(event) { + const candidate = JSON.parse(event.detail); + this.peer.candidate(candidate); + } + /** * On error * diff --git a/src/client/components/video/Video.js b/src/client/components/video/Video.js index 65e4ece..34a9d6b 100644 --- a/src/client/components/video/Video.js +++ b/src/client/components/video/Video.js @@ -4,7 +4,7 @@ import Subtitles from '@client/components/Subtitles'; export default class Video extends Component { static propTypes = { - src: PropTypes.string.isRequired, + src: PropTypes.string, playing: PropTypes.bool.isRequired, time: PropTypes.number.isRequired, authorized: PropTypes.bool.isRequired, @@ -25,6 +25,7 @@ export default class Video extends Component { static defaultProps = { preload: 'auto', + src: null, }; constructor(props) { @@ -105,6 +106,18 @@ export default class Video extends Component { } } + captureStream() { + if (typeof this.element.mozCaptureStream === 'function') { + return this.element.mozCaptureStream(); + } + + if (typeof this.element.captureStream === 'function') { + return this.element.captureStream(); + } + + return null; + } + onAuthorized() { if (!this.props.authorized) { this.props.setAuthorized(true); @@ -157,7 +170,7 @@ export default class Video extends Component { return (