diff --git a/package-lock.json b/package-lock.json index 02c4f5a..deb9566 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,6 +11,7 @@ "@reduxjs/toolkit": "^1.9.7", "axios": "^1.6.0", "dayjs": "^1.11.10", + "peerjs": "^1.5.2", "react": "^18.2.0", "react-dom": "^18.2.0", "react-redux": "^8.1.3", @@ -410,6 +411,78 @@ "node": ">=6.9.0" } }, + "node_modules/@cbor-extract/cbor-extract-darwin-arm64": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@cbor-extract/cbor-extract-darwin-arm64/-/cbor-extract-darwin-arm64-2.2.0.tgz", + "integrity": "sha512-P7swiOAdF7aSi0H+tHtHtr6zrpF3aAq/W9FXx5HektRvLTM2O89xCyXF3pk7pLc7QpaY7AoaE8UowVf9QBdh3w==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@cbor-extract/cbor-extract-darwin-x64": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@cbor-extract/cbor-extract-darwin-x64/-/cbor-extract-darwin-x64-2.2.0.tgz", + "integrity": "sha512-1liF6fgowph0JxBbYnAS7ZlqNYLf000Qnj4KjqPNW4GViKrEql2MgZnAsExhY9LSy8dnvA4C0qHEBgPrll0z0w==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@cbor-extract/cbor-extract-linux-arm": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@cbor-extract/cbor-extract-linux-arm/-/cbor-extract-linux-arm-2.2.0.tgz", + "integrity": "sha512-QeBcBXk964zOytiedMPQNZr7sg0TNavZeuUCD6ON4vEOU/25+pLhNN6EDIKJ9VLTKaZ7K7EaAriyYQ1NQ05s/Q==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@cbor-extract/cbor-extract-linux-arm64": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@cbor-extract/cbor-extract-linux-arm64/-/cbor-extract-linux-arm64-2.2.0.tgz", + "integrity": "sha512-rQvhNmDuhjTVXSPFLolmQ47/ydGOFXtbR7+wgkSY0bdOxCFept1hvg59uiLPT2fVDuJFuEy16EImo5tE2x3RsQ==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@cbor-extract/cbor-extract-linux-x64": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@cbor-extract/cbor-extract-linux-x64/-/cbor-extract-linux-x64-2.2.0.tgz", + "integrity": "sha512-cWLAWtT3kNLHSvP4RKDzSTX9o0wvQEEAj4SKvhWuOVZxiDAeQazr9A+PSiRILK1VYMLeDml89ohxCnUNQNQNCw==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@cbor-extract/cbor-extract-win32-x64": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@cbor-extract/cbor-extract-win32-x64/-/cbor-extract-win32-x64-2.2.0.tgz", + "integrity": "sha512-l2M+Z8DO2vbvADOBNLbbh9y5ST1RY5sqkWOg/58GkUPBYou/cuNZ68SGQ644f1CvZ8kcOxyZtw06+dxWHIoN/w==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "win32" + ] + }, "node_modules/@esbuild/android-arm": { "version": "0.18.20", "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.18.20.tgz", @@ -914,6 +987,14 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@msgpack/msgpack": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/@msgpack/msgpack/-/msgpack-2.8.0.tgz", + "integrity": "sha512-h9u4u/jiIRKbq25PM+zymTyW6bhTzELvOoUd+AvYriWOAKpLGnIamaET3pnHYoI5iYphAHBI4ayx0MehR+VVPQ==", + "engines": { + "node": ">= 10" + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -1482,6 +1563,35 @@ } ] }, + "node_modules/cbor-extract": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cbor-extract/-/cbor-extract-2.2.0.tgz", + "integrity": "sha512-Ig1zM66BjLfTXpNgKpvBePq271BPOvu8MR0Jl080yG7Jsl+wAZunfrwiwA+9ruzm/WEdIV5QF/bjDZTqyAIVHA==", + "hasInstallScript": true, + "optional": true, + "dependencies": { + "node-gyp-build-optional-packages": "5.1.1" + }, + "bin": { + "download-cbor-prebuilds": "bin/download-prebuilds.js" + }, + "optionalDependencies": { + "@cbor-extract/cbor-extract-darwin-arm64": "2.2.0", + "@cbor-extract/cbor-extract-darwin-x64": "2.2.0", + "@cbor-extract/cbor-extract-linux-arm": "2.2.0", + "@cbor-extract/cbor-extract-linux-arm64": "2.2.0", + "@cbor-extract/cbor-extract-linux-x64": "2.2.0", + "@cbor-extract/cbor-extract-win32-x64": "2.2.0" + } + }, + "node_modules/cbor-x": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/cbor-x/-/cbor-x-1.5.4.tgz", + "integrity": "sha512-PVKILDn+Rf6MRhhcyzGXi5eizn1i0i3F8Fe6UMMxXBnWkalq9+C5+VTmlIjAYM4iF2IYF2N+zToqAfYOp+3rfw==", + "optionalDependencies": { + "cbor-extract": "^2.1.1" + } + }, "node_modules/chalk": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", @@ -1604,6 +1714,15 @@ "node": ">=0.4.0" } }, + "node_modules/detect-libc": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.2.tgz", + "integrity": "sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw==", + "optional": true, + "engines": { + "node": ">=8" + } + }, "node_modules/dir-glob": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", @@ -1955,6 +2074,11 @@ "node": ">=0.10.0" } }, + "node_modules/eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==" + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -2678,6 +2802,20 @@ "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", "dev": true }, + "node_modules/node-gyp-build-optional-packages": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/node-gyp-build-optional-packages/-/node-gyp-build-optional-packages-5.1.1.tgz", + "integrity": "sha512-+P72GAjVAbTxjjwUmwjVrqrdZROD4nf8KgpBoDxqXXTiYZZt/ud60dE5yvCSr9lRO8e8yv6kgJIC0K0PfZFVQw==", + "optional": true, + "dependencies": { + "detect-libc": "^2.0.1" + }, + "bin": { + "node-gyp-build-optional-packages": "bin.js", + "node-gyp-build-optional-packages-optional": "optional.js", + "node-gyp-build-optional-packages-test": "build-test.js" + } + }, "node_modules/node-releases": { "version": "2.0.13", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.13.tgz", @@ -2806,6 +2944,37 @@ "node": ">=8" } }, + "node_modules/peerjs": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/peerjs/-/peerjs-1.5.2.tgz", + "integrity": "sha512-pPrtNwPyWJHRPxy2y+rHcdlrG8UwUBB1nl+3Yj6r7FLwcbBpcB2NvGNvLvcrxAVGGGX9fsdA5VT5zBKTZcm1DQ==", + "dependencies": { + "@msgpack/msgpack": "^2.8.0", + "cbor-x": "1.5.4", + "eventemitter3": "^4.0.7", + "peerjs-js-binarypack": "^2.1.0", + "webrtc-adapter": "^8.0.0" + }, + "engines": { + "node": ">= 14" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/peer" + } + }, + "node_modules/peerjs-js-binarypack": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/peerjs-js-binarypack/-/peerjs-js-binarypack-2.1.0.tgz", + "integrity": "sha512-YIwCC+pTzp3Bi8jPI9UFKO0t0SLo6xALnHkiNt/iUFmUUZG0fEEmEyFKvjsDKweiFitzHRyhuh6NvyJZ4nNxMg==", + "engines": { + "node": ">= 14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/peer" + } + }, "node_modules/picocolors": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", @@ -3216,6 +3385,11 @@ "loose-envify": "^1.1.0" } }, + "node_modules/sdp": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/sdp/-/sdp-3.2.0.tgz", + "integrity": "sha512-d7wDPgDV3DDiqulJjKiV2865wKsJ34YI+NDREbm+FySq6WuKOikwyNQcm+doLAZ1O6ltdO0SeKle2xMpN3Brgw==" + }, "node_modules/semver": { "version": "7.5.4", "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", @@ -3535,6 +3709,18 @@ } } }, + "node_modules/webrtc-adapter": { + "version": "8.2.3", + "resolved": "https://registry.npmjs.org/webrtc-adapter/-/webrtc-adapter-8.2.3.tgz", + "integrity": "sha512-gnmRz++suzmvxtp3ehQts6s2JtAGPuDPjA1F3a9ckNpG1kYdYuHWYpazoAnL9FS5/B21tKlhkorbdCXat0+4xQ==", + "dependencies": { + "sdp": "^3.2.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", diff --git a/package.json b/package.json index 033b8d0..ad22b17 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,7 @@ "@reduxjs/toolkit": "^1.9.7", "axios": "^1.6.0", "dayjs": "^1.11.10", + "peerjs": "^1.5.2", "react": "^18.2.0", "react-dom": "^18.2.0", "react-redux": "^8.1.3", diff --git a/public/app-icons/peer-call-icon.svg b/public/app-icons/peer-call-icon.svg new file mode 100644 index 0000000..ea755bc --- /dev/null +++ b/public/app-icons/peer-call-icon.svg @@ -0,0 +1,9 @@ + + + + + + + + \ No newline at end of file diff --git a/src/Ubuntu.tsx b/src/Ubuntu.tsx index d5b25f1..659594f 100644 --- a/src/Ubuntu.tsx +++ b/src/Ubuntu.tsx @@ -5,11 +5,12 @@ import AppBar from "./components/AppBar/AppBar.tsx"; import Desktop from "./components/Desktop/Desktop.tsx"; import IMAGES from "./components/AppBar/Images.ts"; import { AppConfigType } from "./components/Desktop/AppConfigType.ts"; -import { DefaultApp, Settings } from "./components/Apps/Apps.ts"; +import { DefaultApp, Settings, PeerCall } from "./components/Apps/Apps.ts"; function Ubuntu() { const [apps, setApps] = useState([ { name: "Firefox", icon: IMAGES.firefox, app: DefaultApp }, + { name: "PeerCall", icon: IMAGES.peerCall, app: PeerCall }, { name: "LibreOffice", icon: IMAGES.libreOffice, app: DefaultApp }, { name: "Rhythmbox", icon: IMAGES.rhythmBox, app: DefaultApp }, { name: "Settings", icon: IMAGES.settings, app: Settings }, diff --git a/src/components/AppBar/Images.ts b/src/components/AppBar/Images.ts index 1f0cc4b..c4819b7 100644 --- a/src/components/AppBar/Images.ts +++ b/src/components/AppBar/Images.ts @@ -1,4 +1,5 @@ const IMAGES = { + peerCall: new URL("/app-icons/peer-call-icon.svg", import.meta.url).href, firefox: new URL("/app-icons/firefox-icon.png", import.meta.url).href, libreOffice: new URL("/app-icons/libre-office-icon.png", import.meta.url).href, rhythmBox: new URL("/app-icons/rhythmbox-icon.png", import.meta.url).href, diff --git a/src/components/Apps/Apps.ts b/src/components/Apps/Apps.ts index 6d26752..d353850 100644 --- a/src/components/Apps/Apps.ts +++ b/src/components/Apps/Apps.ts @@ -1,5 +1,6 @@ import Settings from "./Settings/Settings.tsx"; import DefaultApp from "./Default/DefaultApp.tsx"; +import PeerCall from "./PeerCall/peerCall.tsx"; -export { Settings, DefaultApp }; \ No newline at end of file +export { Settings, DefaultApp, PeerCall }; \ No newline at end of file diff --git a/src/components/Apps/PeerCall/peerCall.css b/src/components/Apps/PeerCall/peerCall.css new file mode 100644 index 0000000..5eda015 --- /dev/null +++ b/src/components/Apps/PeerCall/peerCall.css @@ -0,0 +1,41 @@ +#peer-call{ + height: 100%; +} + +#peer-call-menu{ + position: absolute; + left: 50%; + top: 50%; + transform: translate(-50%, -50%); + display: flex; + flex-direction: column; +} + +#remote-video { + display: none; + position: absolute; + max-width: 100%; + height: calc(100% - 35px); + top: 35px; + left: 50%; + transform: translate(-50%, 0); + z-index: 1; +} + +#local-video { + display: none; + position: absolute; + bottom: 0; + left: 0; + width: 25%; + z-index: 2; + transform: scaleX(-1); +} + +#end-call{ + position: absolute; + bottom: 1rem; + left: 50%; + transform: translate(-50%, 0); + z-index: 3; +} \ No newline at end of file diff --git a/src/components/Apps/PeerCall/peerCall.tsx b/src/components/Apps/PeerCall/peerCall.tsx new file mode 100644 index 0000000..40b2a2b --- /dev/null +++ b/src/components/Apps/PeerCall/peerCall.tsx @@ -0,0 +1,118 @@ +import { useRef, useState } from "react"; +import { MediaConnection, Peer } from "peerjs"; +import "./peerCall.css"; + +export default function PeerCall() { + const [callId, setCallId] = useState(""); + const [peerId, setPeerId] = useState(""); + const [peer, setPeer] = useState(); + const [call, setCall] = useState(); + const localVideo = useRef(null); + const remoteVideo = useRef(null); + + function startPeer() { + const peer = new Peer(peerId, { debug: 3 }); + peer.on("open", function(id) { + setPeerId(id); + setPeer(peer); + peer.on("call", (call) => { + if (window.confirm(`Accept call from ${call.peer}?`)) { + navigator.mediaDevices + .getUserMedia({ video: true, audio: true }) + .then((stream) => { + if (localVideo.current) { + localVideo.current.style.display = "block"; + localVideo.current.srcObject = stream; + localVideo.current.play(); + call.answer(stream); + call.on("stream", (remoteStream) => { + if (remoteVideo.current) { + remoteVideo.current.style.display = "block"; + remoteVideo.current.srcObject = remoteStream; + remoteVideo.current.play(); + } + }); + setCall(call); + setCallId(call.peer); + call.on("close", () => { + endCall(); + }); + + } + }) + .catch((err) => { + console.log("Failed to get local stream:", err); + }); + } else { + call.close(); + } + }); + }); + } + + async function startCall() { + const stream = await navigator.mediaDevices.getUserMedia({ + video: true, + audio: true + }); + if (localVideo.current) { + localVideo.current.style.display = "block"; + localVideo.current.srcObject = stream; + localVideo.current.play(); + } + + if (peer) { + const call = peer.call(callId, stream); + call.on("stream", (stream) => { + if (remoteVideo.current) { + remoteVideo.current.style.display = "block"; + remoteVideo.current.srcObject = stream; + remoteVideo.current.play(); + } + }); + call.on("error", (err) => { + console.log(err); + }); + call.on("close", () => { + endCall(); + }); + + setCall(call); + } + } + + function endCall() { + if (localVideo.current) localVideo.current.style.display = "none"; + if (remoteVideo.current) remoteVideo.current.style.display = "none"; + if (!call) return; + if (call) { + call.localStream.getTracks().forEach(value => value.stop()); + } + try { + call.close(); + } catch { + } + setCall(undefined); + } + + return ( +
+
+

Peer-Call

+ setPeerId(e.target.value)} + disabled={peer?.open} /> + +
+ setCallId(e.target.value)} + disabled={call?.open || !peer?.open} /> + +
+ <> + +
+ ); +} \ No newline at end of file diff --git a/src/components/Desktop/Desktop.tsx b/src/components/Desktop/Desktop.tsx index df65ff4..06fdd3f 100644 --- a/src/components/Desktop/Desktop.tsx +++ b/src/components/Desktop/Desktop.tsx @@ -103,7 +103,6 @@ function Desktop({ openedAppConfigs, setOpenedAppConfigs }: DesktopProps) { width: (desktopSelectPos.width + "px"), display: (desktopSelectPos.width + desktopSelectPos.height > 0 ? "block" : "none") }} /> - { openedAppConfigs.map((appConfig, i) => ( )) } - ); }