users in channels, kindof
parent
e775e4a240
commit
c0289d92e3
|
|
@ -21,7 +21,9 @@
|
|||
"eslint-plugin-react": "^7.30.1",
|
||||
"express": "^4.18.1",
|
||||
"get-port": "^6.1.2",
|
||||
"local-storage": "^2.0.0",
|
||||
"mysql": "^2.18.1",
|
||||
"peerjs": "^1.4.6",
|
||||
"qrcode": "^1.5.1",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
|
|
@ -928,6 +930,14 @@
|
|||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/@swc/helpers": {
|
||||
"version": "0.3.17",
|
||||
"resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.3.17.tgz",
|
||||
"integrity": "sha512-tb7Iu+oZ+zWJZ3HJqwx8oNwSDIU440hmVMDPhpACWQWnrZHK99Bxs70gT1L2dnr5Hg50ZRWEFkQCAnOVVV0z1Q==",
|
||||
"dependencies": {
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@szmarczak/http-timer": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-1.1.2.tgz",
|
||||
|
|
@ -4929,6 +4939,11 @@
|
|||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/eventemitter3": {
|
||||
"version": "4.0.7",
|
||||
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz",
|
||||
"integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw=="
|
||||
},
|
||||
"node_modules/execa": {
|
||||
"version": "5.1.1",
|
||||
"resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz",
|
||||
|
|
@ -6929,6 +6944,11 @@
|
|||
"url": "https://github.com/sponsors/antfu"
|
||||
}
|
||||
},
|
||||
"node_modules/local-storage": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/local-storage/-/local-storage-2.0.0.tgz",
|
||||
"integrity": "sha512-/0sRoeijw7yr/igbVVygDuq6dlYCmtsuTmmpnweVlVtl/s10pf5BCq8LWBxW/AMyFJ3MhMUuggMZiYlx6qr9tw=="
|
||||
},
|
||||
"node_modules/locate-path": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz",
|
||||
|
|
@ -8084,6 +8104,29 @@
|
|||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/peerjs": {
|
||||
"version": "1.4.6",
|
||||
"resolved": "https://registry.npmjs.org/peerjs/-/peerjs-1.4.6.tgz",
|
||||
"integrity": "sha512-0XA105/9yBFGxfpyCjlI1bcBiyPmXHs8+UDvO2j1WGnY+FXilMn35+P/3t8HzKnUnR1SX0PRkDSk8kM17ciNxA==",
|
||||
"dependencies": {
|
||||
"@swc/helpers": "^0.3.13",
|
||||
"eventemitter3": "^4.0.7",
|
||||
"peerjs-js-binarypack": "1.0.1",
|
||||
"webrtc-adapter": "^7.7.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/peer"
|
||||
}
|
||||
},
|
||||
"node_modules/peerjs-js-binarypack": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/peerjs-js-binarypack/-/peerjs-js-binarypack-1.0.1.tgz",
|
||||
"integrity": "sha512-N6aeia3NhdpV7kiGxJV5xQiZZCVEEVjRz2T2C6UZQiBkHWHzUv/oWA4myQLcwBwO8LUoR1KWW5oStvwVesmfCg=="
|
||||
},
|
||||
"node_modules/pend": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz",
|
||||
|
|
@ -9044,6 +9087,18 @@
|
|||
"fsevents": "~2.3.2"
|
||||
}
|
||||
},
|
||||
"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/run-async": {
|
||||
"version": "2.4.1",
|
||||
"resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz",
|
||||
|
|
@ -9115,6 +9170,11 @@
|
|||
"loose-envify": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"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": "7.3.7",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz",
|
||||
|
|
@ -9932,10 +9992,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/tslib": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz",
|
||||
"integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==",
|
||||
"devOptional": true
|
||||
"version": "2.4.0",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz",
|
||||
"integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ=="
|
||||
},
|
||||
"node_modules/tsutils": {
|
||||
"version": "3.21.0",
|
||||
|
|
@ -10574,6 +10633,19 @@
|
|||
"integrity": "sha512-5NUqC2JquIL2pBAAo/VfBP6KuGkHIZQXW/lNKupLPfhViwh8wNsu0BObtl09yuKZszeEUfbXz8xhrHvSG16Nqw==",
|
||||
"dev": true
|
||||
},
|
||||
"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/whatwg-encoding": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz",
|
||||
|
|
@ -11545,6 +11617,14 @@
|
|||
"resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.14.0.tgz",
|
||||
"integrity": "sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ=="
|
||||
},
|
||||
"@swc/helpers": {
|
||||
"version": "0.3.17",
|
||||
"resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.3.17.tgz",
|
||||
"integrity": "sha512-tb7Iu+oZ+zWJZ3HJqwx8oNwSDIU440hmVMDPhpACWQWnrZHK99Bxs70gT1L2dnr5Hg50ZRWEFkQCAnOVVV0z1Q==",
|
||||
"requires": {
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"@szmarczak/http-timer": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-1.1.2.tgz",
|
||||
|
|
@ -14519,6 +14599,11 @@
|
|||
"resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
|
||||
"integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg=="
|
||||
},
|
||||
"eventemitter3": {
|
||||
"version": "4.0.7",
|
||||
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz",
|
||||
"integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw=="
|
||||
},
|
||||
"execa": {
|
||||
"version": "5.1.1",
|
||||
"resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz",
|
||||
|
|
@ -16032,6 +16117,11 @@
|
|||
"integrity": "sha512-mlERgSPrbxU3BP4qBqAvvwlgW4MTg78iwJdGGnv7kibKjWcJksrG3t6LB5lXI93wXRDvG4NpUgJFmTG4T6rdrg==",
|
||||
"dev": true
|
||||
},
|
||||
"local-storage": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/local-storage/-/local-storage-2.0.0.tgz",
|
||||
"integrity": "sha512-/0sRoeijw7yr/igbVVygDuq6dlYCmtsuTmmpnweVlVtl/s10pf5BCq8LWBxW/AMyFJ3MhMUuggMZiYlx6qr9tw=="
|
||||
},
|
||||
"locate-path": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz",
|
||||
|
|
@ -16889,6 +16979,22 @@
|
|||
"integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==",
|
||||
"dev": true
|
||||
},
|
||||
"peerjs": {
|
||||
"version": "1.4.6",
|
||||
"resolved": "https://registry.npmjs.org/peerjs/-/peerjs-1.4.6.tgz",
|
||||
"integrity": "sha512-0XA105/9yBFGxfpyCjlI1bcBiyPmXHs8+UDvO2j1WGnY+FXilMn35+P/3t8HzKnUnR1SX0PRkDSk8kM17ciNxA==",
|
||||
"requires": {
|
||||
"@swc/helpers": "^0.3.13",
|
||||
"eventemitter3": "^4.0.7",
|
||||
"peerjs-js-binarypack": "1.0.1",
|
||||
"webrtc-adapter": "^7.7.1"
|
||||
}
|
||||
},
|
||||
"peerjs-js-binarypack": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/peerjs-js-binarypack/-/peerjs-js-binarypack-1.0.1.tgz",
|
||||
"integrity": "sha512-N6aeia3NhdpV7kiGxJV5xQiZZCVEEVjRz2T2C6UZQiBkHWHzUv/oWA4myQLcwBwO8LUoR1KWW5oStvwVesmfCg=="
|
||||
},
|
||||
"pend": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz",
|
||||
|
|
@ -17599,6 +17705,14 @@
|
|||
"fsevents": "~2.3.2"
|
||||
}
|
||||
},
|
||||
"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"
|
||||
}
|
||||
},
|
||||
"run-async": {
|
||||
"version": "2.4.1",
|
||||
"resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz",
|
||||
|
|
@ -17653,6 +17767,11 @@
|
|||
"loose-envify": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"sdp": {
|
||||
"version": "2.12.0",
|
||||
"resolved": "https://registry.npmjs.org/sdp/-/sdp-2.12.0.tgz",
|
||||
"integrity": "sha512-jhXqQAQVM+8Xj5EjJGVweuEzgtGWb3tmEEpl3CLP3cStInSbVHSg0QWOGQzNq8pSID4JkpeV2mPqlMDLrm0/Vw=="
|
||||
},
|
||||
"semver": {
|
||||
"version": "7.3.7",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz",
|
||||
|
|
@ -18283,10 +18402,9 @@
|
|||
}
|
||||
},
|
||||
"tslib": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz",
|
||||
"integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==",
|
||||
"devOptional": true
|
||||
"version": "2.4.0",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz",
|
||||
"integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ=="
|
||||
},
|
||||
"tsutils": {
|
||||
"version": "3.21.0",
|
||||
|
|
@ -18717,6 +18835,15 @@
|
|||
"integrity": "sha512-5NUqC2JquIL2pBAAo/VfBP6KuGkHIZQXW/lNKupLPfhViwh8wNsu0BObtl09yuKZszeEUfbXz8xhrHvSG16Nqw==",
|
||||
"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"
|
||||
}
|
||||
},
|
||||
"whatwg-encoding": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz",
|
||||
|
|
|
|||
|
|
@ -69,7 +69,9 @@
|
|||
"eslint-plugin-react": "^7.30.1",
|
||||
"express": "^4.18.1",
|
||||
"get-port": "^6.1.2",
|
||||
"local-storage": "^2.0.0",
|
||||
"mysql": "^2.18.1",
|
||||
"peerjs": "^1.4.6",
|
||||
"qrcode": "^1.5.1",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
|
|
|
|||
|
|
@ -2,8 +2,10 @@ import { useContext, useEffect } from "react";
|
|||
import ServerConnection from "./components/ServerConnection";
|
||||
import Sidebar from "./components/Sidebar";
|
||||
import TwoPanel from "./components/TwoPanel";
|
||||
import Voice from "./components/Voice";
|
||||
import { SettingsContext } from "./contexts/EphemeralState/EphemeralState";
|
||||
import useHomeServer from "./contexts/PersistentState/useHomeServerNative";
|
||||
import useChannel from "./hooks/useChannel";
|
||||
import useClientId from "./hooks/useClientId";
|
||||
import useSessionToken from "./hooks/useSessionToken";
|
||||
import Channels from "./pages/Channels";
|
||||
|
|
@ -22,6 +24,7 @@ export default function Router(props: RouterProps) {
|
|||
const { sessionToken } = useSessionToken();
|
||||
const { homeServer } = useHomeServer();
|
||||
const { isSettingsOpen, closeSettings } = useContext(SettingsContext);
|
||||
const { voice, text } = useChannel();
|
||||
|
||||
const configured =
|
||||
homeServer !== null &&
|
||||
|
|
@ -43,7 +46,27 @@ export default function Router(props: RouterProps) {
|
|||
sidebar={300}
|
||||
>
|
||||
<Sidebar></Sidebar>
|
||||
<Chat></Chat>
|
||||
{voice ? (
|
||||
<div style={{
|
||||
height: '100%',
|
||||
width: '100%'
|
||||
}}>
|
||||
<div style={{
|
||||
height: '50%',
|
||||
width: '100%'
|
||||
}}>
|
||||
<Voice></Voice>
|
||||
</div>
|
||||
<div style={{
|
||||
height: '50%',
|
||||
width: '100%'
|
||||
}}>
|
||||
<Chat></Chat>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<Chat></Chat>
|
||||
)}
|
||||
</TwoPanel>
|
||||
)}
|
||||
</ServerConnection>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,37 @@
|
|||
import { useCallback, useContext } from "react"
|
||||
import { PeerContext } from "../contexts/EphemeralState/PeerState";
|
||||
import useChannel from "../hooks/useChannel";
|
||||
import { useApi } from "../lib/useApi";
|
||||
|
||||
|
||||
|
||||
export default function Voice(props: any) {
|
||||
const { uid } = props;
|
||||
const { connected, peerId } = useContext(PeerContext);
|
||||
const { channel } = useChannel();
|
||||
|
||||
const { send } = useApi({
|
||||
|
||||
});
|
||||
|
||||
const joinCall = useCallback(() => {
|
||||
if(peerId === null || connected === false) return;
|
||||
send('voice:join', { peerId, channelId: channel })
|
||||
}, [connected, peerId, channel])
|
||||
|
||||
return <div style={{
|
||||
width: '100%',
|
||||
maxWidth: '500px',
|
||||
margin: '0px auto',
|
||||
}}>
|
||||
<fieldset>
|
||||
<legend>Peer Info</legend>
|
||||
connected: {connected ? 'true' : 'false'}<br></br>
|
||||
PeerId: {peerId}<br></br>
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<legend>Actions</legend>
|
||||
<button onClick={joinCall}>Join Call</button>
|
||||
</fieldset>
|
||||
</div>
|
||||
}
|
||||
|
|
@ -1,11 +1,19 @@
|
|||
import { createContext, useState, useMemo, useEffect } from "react";
|
||||
import UserMediaState from "./UserMediaState";
|
||||
import PeerState from "./PeerState";
|
||||
|
||||
export type ChannelType = 'text' | 'voice';
|
||||
|
||||
export const ChannelContext = createContext<{
|
||||
channel: string | null,
|
||||
setChannel: (uid: string) => void
|
||||
text: boolean,
|
||||
voice: boolean,
|
||||
setChannel: (uid: string, type: ChannelType) => void
|
||||
}>({
|
||||
channel: null,
|
||||
setChannel: () => {},
|
||||
text: false,
|
||||
voice: false,
|
||||
});
|
||||
export const TransparencyContext = createContext<(transparent: boolean) => void>(() => {});
|
||||
export const SettingsContext = createContext<{
|
||||
|
|
@ -24,12 +32,38 @@ export default function EphemeralState(props: {
|
|||
}) {
|
||||
|
||||
const [channel, setChannel] = useState<string | null>(null);
|
||||
const [voice, setVoice] = useState(false);
|
||||
const [text, setText] = useState(false);
|
||||
const [transparent, setTransparent] = useState(false);
|
||||
|
||||
const [settings, setSettings] = useState(false);
|
||||
|
||||
const channelContextValue = useMemo(() => {
|
||||
return { channel, setChannel };
|
||||
return {
|
||||
channel,
|
||||
setChannel: (uid: string, channelType: ChannelType) => {
|
||||
setChannel(uid);
|
||||
switch(channelType) {
|
||||
case 'text': {
|
||||
setVoice(false);
|
||||
setText(true);
|
||||
break;
|
||||
}
|
||||
case 'voice': {
|
||||
setVoice(true);
|
||||
setText(false);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
setVoice(false);
|
||||
setText(false);
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
voice,
|
||||
text
|
||||
};
|
||||
}, [channel, setChannel]);
|
||||
|
||||
useEffect(() => {
|
||||
|
|
@ -46,7 +80,11 @@ export default function EphemeralState(props: {
|
|||
closeSettings: () => setSettings(false),
|
||||
isSettingsOpen: settings,
|
||||
}}>
|
||||
{props.children}
|
||||
<UserMediaState>
|
||||
<PeerState>
|
||||
{props.children}
|
||||
</PeerState>
|
||||
</UserMediaState>
|
||||
</SettingsContext.Provider>
|
||||
</TransparencyContext.Provider>
|
||||
</ChannelContext.Provider>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,82 @@
|
|||
import { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState } from "react";
|
||||
import { Peer, MediaConnection } from "peerjs";
|
||||
import { UserMediaContext } from "./UserMediaState";
|
||||
|
||||
export const PeerContext = createContext<{
|
||||
connected: boolean;
|
||||
peerId: string | null
|
||||
}>({
|
||||
connected: false,
|
||||
peerId: null
|
||||
});
|
||||
|
||||
function useCurrent<T>(thing: T) {
|
||||
const thingRef = useRef<T>(thing);
|
||||
|
||||
useEffect(() => {
|
||||
thingRef.current = thing;
|
||||
}, [thing]);
|
||||
|
||||
return thingRef.current;
|
||||
}
|
||||
|
||||
export default function PeerState(props: any) {
|
||||
|
||||
const { mediaStream } = useContext(UserMediaContext);
|
||||
// TODO ability to disable until needed
|
||||
// const [enabled, setEnabled] = useState(true);
|
||||
const [connected, setConnected] = useState(false);
|
||||
const [peer, setPeer] = useState<Peer | null>(null);
|
||||
const [peerId, setPeerId] = useState<string | null>(null);
|
||||
const [incomingCalls, setIncomingCalls] = useState<MediaConnection[]>([]);
|
||||
|
||||
const addCall = useCurrent(useCallback((call: MediaConnection) => {
|
||||
// HACK lookout for possible timing issues here.
|
||||
// if we get two incomming calls before a re-render
|
||||
// then our state could be out of date?!
|
||||
// a possible solution is to cache the
|
||||
// append to the list, and if the cache and
|
||||
// state disagree, add to the cache, and set state
|
||||
// to the cached value.
|
||||
setIncomingCalls([...incomingCalls, call]);
|
||||
}, [incomingCalls]))
|
||||
|
||||
useEffect(() => {
|
||||
if(connected) return;
|
||||
|
||||
const peer = new Peer();
|
||||
setPeer(peer);
|
||||
|
||||
peer.on('open', (id: string) => {
|
||||
setConnected(true);
|
||||
setPeerId(id);
|
||||
});
|
||||
|
||||
peer.on('close', () => {
|
||||
setConnected(false);
|
||||
setPeerId(null);
|
||||
setPeer(null);
|
||||
});
|
||||
|
||||
peer.on('call', (call: MediaConnection) => {
|
||||
addCall(call);
|
||||
});
|
||||
}, [connected]);
|
||||
|
||||
const dial = useCallback((id: string) => {
|
||||
if(peer === null) return;
|
||||
if(mediaStream === null) return;
|
||||
peer.call(id, mediaStream);
|
||||
}, [peer, mediaStream])
|
||||
|
||||
const value = useMemo(() => ({
|
||||
connected,
|
||||
peerId,
|
||||
}), [connected, peerId]);
|
||||
|
||||
return <PeerContext.Provider value={value}>
|
||||
{props.children}
|
||||
</PeerContext.Provider>
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
import { createContext, useCallback, useMemo, useState } from "react";
|
||||
|
||||
export const UserMediaContext = createContext<{
|
||||
enabled: boolean;
|
||||
mediaStream: MediaStream | null;
|
||||
enable: () => void;
|
||||
disable: () => void;
|
||||
}>({
|
||||
enabled: false,
|
||||
mediaStream: null,
|
||||
enable: () => {},
|
||||
disable: () => {},
|
||||
});
|
||||
|
||||
export default function UserMediaState(props: any) {
|
||||
|
||||
const [mediaStream, setMediaStream] = useState<MediaStream | null>(null);
|
||||
const [enabled, setEnabled] = useState(false);
|
||||
|
||||
const enable = useCallback(async () => {
|
||||
const newStream = await navigator.mediaDevices.getUserMedia({
|
||||
audio: true,
|
||||
video: false,
|
||||
});
|
||||
|
||||
setMediaStream(newStream);
|
||||
setEnabled(true);
|
||||
}, []);
|
||||
|
||||
const disable = useCallback(async () => {
|
||||
if(mediaStream === null) return;
|
||||
|
||||
for(const track of mediaStream?.getTracks()) {
|
||||
track.stop();
|
||||
}
|
||||
|
||||
setMediaStream(null);
|
||||
setEnabled(false);
|
||||
}, [mediaStream])
|
||||
|
||||
const value = useMemo(() => ({
|
||||
enabled: false,
|
||||
mediaStream: null,
|
||||
enable,
|
||||
disable
|
||||
}), []);
|
||||
|
||||
|
||||
|
||||
return <UserMediaContext.Provider value={value}>
|
||||
{props.children}
|
||||
</UserMediaContext.Provider>
|
||||
}
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
import { createContext, useContext, useEffect, useMemo, useState } from "react";
|
||||
import { PeerContext } from "./PeerState";
|
||||
import { useApi } from "/@/lib/useApi";
|
||||
|
||||
interface RemotePeer {
|
||||
mediaStream: MediaStream | null;
|
||||
peerId: string;
|
||||
}
|
||||
|
||||
export const VoiceChannelContext = createContext<{
|
||||
voiceChannelId: string | null;
|
||||
setVoiceChannelId: (channelId: string | null) => void
|
||||
}>({
|
||||
voiceChannelId: null,
|
||||
setVoiceChannelId: () => {}
|
||||
});
|
||||
|
||||
export default function VoiceChannelState(props: any) {
|
||||
|
||||
const [voiceChannelId, setVoiceChannelId] = useState<string | null>(null);
|
||||
const { peerId, incommingCalls } = useContext(PeerContext);
|
||||
const [peers, setPeers] = useState<RemotePeer[]>([]);
|
||||
|
||||
const { send } = useApi({
|
||||
'voice:list'() {
|
||||
|
||||
}
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
}, [voiceChannelId])
|
||||
|
||||
|
||||
const value = useMemo(() => ({
|
||||
voiceChannelId,
|
||||
setVoiceChannelId,
|
||||
}), [voiceChannelId, setVoiceChannelId])
|
||||
|
||||
return <VoiceChannelContext.Provider value={value}>
|
||||
{props.children}
|
||||
</VoiceChannelContext.Provider>
|
||||
}
|
||||
|
|
@ -4,6 +4,25 @@ export function connectApi(url: string) {
|
|||
let connectionAttempts = 0;
|
||||
let destroy = false;
|
||||
let routers: any[] = [];
|
||||
let keepalive: NodeJS.Timer | null = null;
|
||||
|
||||
function startKeepalive() {
|
||||
keepalive = setInterval(() => {
|
||||
if(socket !== null) {
|
||||
socket.send(JSON.stringify({
|
||||
action: 'up',
|
||||
data: {}
|
||||
}))
|
||||
} else {
|
||||
stopKeepalive();
|
||||
}
|
||||
}, 30_000);
|
||||
}
|
||||
|
||||
function stopKeepalive() {
|
||||
if(keepalive !== null)
|
||||
clearInterval(keepalive);
|
||||
}
|
||||
|
||||
const connect = async () => {
|
||||
try {
|
||||
|
|
@ -25,23 +44,26 @@ export function connectApi(url: string) {
|
|||
if(socket === null) return;
|
||||
connectionAttempts = 0;
|
||||
console.log('connected to', url);
|
||||
startKeepalive();
|
||||
// socket.send('Hello Server!');
|
||||
});
|
||||
|
||||
|
||||
socket.addEventListener('message', (event) => {
|
||||
const {action, data} = JSON.parse(event.data);
|
||||
// console.debug('[IN]', action, data);
|
||||
const routeFound = routers
|
||||
.map(router => router(action, data))
|
||||
.reduce((a, b) => a + b, 0);
|
||||
if(routeFound === 0) {
|
||||
if(routeFound === 0 && action !== 'up') {
|
||||
console.warn(`route <${action}> not found`);
|
||||
}
|
||||
});
|
||||
|
||||
socket.addEventListener('close', () => {
|
||||
if(destroy) return;
|
||||
stopKeepalive();
|
||||
socket = null;
|
||||
if(destroy) return;
|
||||
|
||||
connect();
|
||||
});
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,7 +1,14 @@
|
|||
import * as preload from '#preload';
|
||||
|
||||
// console.log('#preload', preload);
|
||||
|
||||
function ls(key: string, value?: string) {
|
||||
if(value === undefined) {
|
||||
return localStorage.getItem(key);
|
||||
} else {
|
||||
localStorage.setItem(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
const functions: any = (function() {
|
||||
const electron = !!preload.versions;
|
||||
const cordova = 'cordova' in globalThis;
|
||||
|
|
@ -13,17 +20,23 @@ const functions: any = (function() {
|
|||
let homeServer: string | null = null;
|
||||
return {
|
||||
getClientId() {
|
||||
return cid;
|
||||
return ls('clientId');
|
||||
},
|
||||
setClientId(id: any) {
|
||||
cid = id;
|
||||
ls('clientId', id);
|
||||
},
|
||||
getHomeServer() {
|
||||
return homeServer;
|
||||
return ls('homeServer');
|
||||
},
|
||||
setHomeServer(str: string) {
|
||||
homeServer = str;
|
||||
}
|
||||
ls('homeServer', str);
|
||||
},
|
||||
getSessionToken() {
|
||||
return ls('sessionToken');
|
||||
},
|
||||
setSessionToken(str: string) {
|
||||
ls('sessionToken', str);
|
||||
},
|
||||
};
|
||||
}
|
||||
})();
|
||||
|
|
|
|||
|
|
@ -1,18 +1,54 @@
|
|||
import { CgHashtag } from "react-icons/cg";
|
||||
import { MdVolumeUp } from "react-icons/md";
|
||||
import { BsQuestionLg } from 'react-icons/bs';
|
||||
import { ChannelType } from "../contexts/EphemeralState/EphemeralState";
|
||||
import useChannel from "../hooks/useChannel";
|
||||
import useHover from "../hooks/useHover";
|
||||
import { useApi } from "../lib/useApi";
|
||||
import { useContext, useEffect, useState } from "react";
|
||||
import { VoiceChannelContext } from "../contexts/EphemeralState/VoiceChannelState";
|
||||
|
||||
interface ChannelProps {
|
||||
unread: number;
|
||||
uid: string;
|
||||
name: string;
|
||||
type: ChannelType;
|
||||
}
|
||||
|
||||
export default function Channel(props: ChannelProps) {
|
||||
const { channel, setChannel } = useChannel();
|
||||
const { unread, uid, name } = props;
|
||||
const { unread, uid, name, type } = props;
|
||||
const [ref, hover] = useHover<HTMLDivElement>();
|
||||
const selected = channel === uid;
|
||||
const { voiceChannelId } = useContext(VoiceChannelContext);
|
||||
|
||||
const [participants, setParticipants] = useState<any[]>([]);
|
||||
|
||||
const { send } = useApi({
|
||||
'voice:join'(data: any) {
|
||||
if(type !== 'voice' || data.channelId !== uid) return;
|
||||
setParticipants([...participants, {
|
||||
clientId: data.clientId,
|
||||
peerId: data.peerId,
|
||||
channelId: data.channelId
|
||||
}])
|
||||
console.log('JOIN', data);
|
||||
},
|
||||
'voice:list'(data: any) {
|
||||
if(type !== 'voice') return;
|
||||
console.log('CURRENTS', data);
|
||||
},
|
||||
'voice:leave'(data: any) {
|
||||
console.log(data);
|
||||
},
|
||||
}, [uid, type, participants])
|
||||
|
||||
useEffect(() => {
|
||||
if(type !== 'voice') return;
|
||||
|
||||
setParticipants([]);
|
||||
send('voice:list', { uid });
|
||||
}, [uid]);
|
||||
|
||||
return (
|
||||
<div
|
||||
|
|
@ -30,19 +66,41 @@ export default function Channel(props: ChannelProps) {
|
|||
transition: 'background 300ms, color 300ms',
|
||||
}}
|
||||
onClick={() => {
|
||||
setChannel(uid);
|
||||
setChannel(uid, type);
|
||||
}}
|
||||
ref={ref}
|
||||
>
|
||||
<CgHashtag color={
|
||||
selected ? 'var(--neutral-9)' :
|
||||
hover ? 'var(--neutral-7)' :
|
||||
'var(--neutral-7)'
|
||||
} size={24} style={{
|
||||
margin: '4px',
|
||||
transition: 'background 300ms, color 300ms',
|
||||
transform:'skew(-5deg, 0deg)',
|
||||
}}></CgHashtag>
|
||||
{(type === 'text') ? (
|
||||
<CgHashtag color={
|
||||
selected ? 'var(--neutral-9)' :
|
||||
hover ? 'var(--neutral-7)' :
|
||||
'var(--neutral-7)'
|
||||
} size={24} style={{
|
||||
margin: '4px',
|
||||
transition: 'background 300ms, color 300ms',
|
||||
transform:'skew(-5deg, 0deg)',
|
||||
}}></CgHashtag>
|
||||
) : ((type === 'voice') ? (
|
||||
<MdVolumeUp color={
|
||||
selected ? 'var(--neutral-9)' :
|
||||
hover ? 'var(--neutral-7)' :
|
||||
'var(--neutral-7)'
|
||||
} size={24} style={{
|
||||
margin: '4px',
|
||||
transition: 'background 300ms, color 300ms',
|
||||
transform:'skew(20deg, 0deg)',
|
||||
}}></MdVolumeUp>
|
||||
) : (
|
||||
<BsQuestionLg color={
|
||||
selected ? 'var(--neutral-9)' :
|
||||
hover ? 'var(--neutral-7)' :
|
||||
'var(--neutral-7)'
|
||||
} size={24} style={{
|
||||
margin: '4px',
|
||||
transition: 'background 300ms, color 300ms',
|
||||
transform:'skew(20deg, 0deg)',
|
||||
}}></BsQuestionLg>
|
||||
))}
|
||||
<div style={{
|
||||
lineHeight: '32px',
|
||||
color: selected ? 'var(--neutral-9)' :
|
||||
|
|
@ -66,6 +124,12 @@ export default function Channel(props: ChannelProps) {
|
|||
marginLeft: '8px',
|
||||
fontSize: '10px',
|
||||
}} href="#" onClick={() => {}}>Delete</a> */}
|
||||
<br></br>
|
||||
{participants.map(participant => (
|
||||
<div key={participant.clientId}>
|
||||
{participant.clientId}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
@ -10,10 +10,12 @@ import useChannel from '../hooks/useChannel';
|
|||
import useClientId from '../hooks/useClientId';
|
||||
import useHomeServer from '../contexts/PersistentState/useHomeServerNative';
|
||||
import Channel from './Channel';
|
||||
import { ChannelType } from '../contexts/EphemeralState/EphemeralState';
|
||||
|
||||
interface IChannel {
|
||||
uid: string;
|
||||
name: string;
|
||||
type: ChannelType;
|
||||
}
|
||||
|
||||
interface IUnreads {
|
||||
|
|
@ -25,12 +27,12 @@ export default function Channels() {
|
|||
const [channels, setChannels] = useState<IChannel[]>([]);
|
||||
const [unreads, setUnreads] = useState<IUnreads>({});
|
||||
|
||||
const { channel, setChannel } = useChannel()
|
||||
const { clientId } = useClientId()
|
||||
const { channel, setChannel } = useChannel();
|
||||
const { clientId } = useClientId();
|
||||
|
||||
const { send } = useApi({
|
||||
'channels:list'(data: IChannel[]) {
|
||||
setChannels(data);
|
||||
'channels:list'(data: any) {
|
||||
setChannels(data.channels);
|
||||
},
|
||||
'channel:add'(channel: IChannel) {
|
||||
setChannels([...channels, channel]);
|
||||
|
|
@ -53,7 +55,7 @@ export default function Channels() {
|
|||
useEffect(() => {
|
||||
if(channels.length === 0) return;
|
||||
if(channel !== null) return;
|
||||
setChannel(channels[0].uid);
|
||||
setChannel(channels[1].uid, channels[1].type);
|
||||
}, [channel, channels]);
|
||||
|
||||
useEffect(() => {
|
||||
|
|
@ -66,7 +68,7 @@ export default function Channels() {
|
|||
|
||||
useEffect(() => {
|
||||
if(clientId === null) return;
|
||||
send('client:get', { clientId });
|
||||
// send('client:get', { clientId });
|
||||
}, [clientId]);
|
||||
|
||||
const textbox = useRef<HTMLInputElement>(null);
|
||||
|
|
@ -90,6 +92,7 @@ export default function Channels() {
|
|||
<Channel
|
||||
key={c.uid}
|
||||
uid={c.uid}
|
||||
type={c.type}
|
||||
unread={unreads[c.uid] ?? 0}
|
||||
name={c.name}
|
||||
></Channel>
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ const config = {
|
|||
},
|
||||
base: '',
|
||||
server: {
|
||||
host: true,
|
||||
fs: {
|
||||
strict: true,
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import router from './lib/router';
|
||||
import { expose } from './lib/WebSocketServer';
|
||||
import { expose, reply } from './lib/WebSocketServer';
|
||||
|
||||
import message from './routers/message';
|
||||
import channel from './routers/channel';
|
||||
|
|
@ -8,7 +8,9 @@ import totp from './routers/totp';
|
|||
|
||||
const api = router({
|
||||
up() {
|
||||
console.log(Date.now());
|
||||
return reply({
|
||||
time: Date.now()
|
||||
});
|
||||
},
|
||||
message: message,
|
||||
messages: message,
|
||||
|
|
@ -19,6 +21,7 @@ const api = router({
|
|||
totp: totp,
|
||||
session: session,
|
||||
sessions: session,
|
||||
voice: voice
|
||||
});
|
||||
|
||||
expose(api, 3000);
|
||||
|
|
@ -27,6 +30,7 @@ expose(api, 3000);
|
|||
|
||||
import { update } from './db/migrate';
|
||||
import session from './routers/session';
|
||||
import voice from './routers/voice';
|
||||
|
||||
try {
|
||||
update();
|
||||
|
|
|
|||
|
|
@ -5,10 +5,21 @@ import add from '../db/snippets/channel/new.sql';
|
|||
import { broadcast, reply } from '../lib/WebSocketServer';
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
export const mockVoiceChannels = [
|
||||
{
|
||||
uid: v4(),
|
||||
name: 'Voice Test',
|
||||
type: 'voice'
|
||||
}
|
||||
]
|
||||
|
||||
export default router({
|
||||
async list() {
|
||||
const res = await query(list);
|
||||
return reply(res ?? undefined);
|
||||
if(res === null) return;
|
||||
return reply({
|
||||
channels: [...(res.map(v => ({...v, type: 'text'}))), ...mockVoiceChannels]
|
||||
});
|
||||
},
|
||||
async add(channel: any) {
|
||||
const name = channel.name;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,62 @@
|
|||
import router from "../lib/router";
|
||||
import { broadcast, reply } from "../lib/WebSocketServer";
|
||||
|
||||
function filterInPlace<T>(a: T[], condition: (v: T, i: number, a: T[]) => boolean) {
|
||||
let i = 0, j = 0;
|
||||
|
||||
let copy = [...a];
|
||||
let removed = [];
|
||||
|
||||
while (i < a.length) {
|
||||
const val = a[i];
|
||||
if (condition(val, i, copy)) a[j++] = val;
|
||||
else removed.push(val);
|
||||
i++;
|
||||
}
|
||||
|
||||
a.length = j;
|
||||
return removed;
|
||||
}
|
||||
|
||||
interface ClientChannelRelationship {
|
||||
clientId: string;
|
||||
channelId: string;
|
||||
peerId: string;
|
||||
}
|
||||
|
||||
const participants: ClientChannelRelationship[] = [];
|
||||
|
||||
export default router({
|
||||
async join(data: any) {
|
||||
const { $clientId, channelId, peerId } = data;
|
||||
// TODO validate channel exists
|
||||
if(participants
|
||||
.filter(v => v.clientId === $clientId)
|
||||
.length !== 0) {
|
||||
// TODO REMOVE USER FROM THIS PLACE
|
||||
}
|
||||
|
||||
const user_channel = {
|
||||
clientId: $clientId,
|
||||
peerId,
|
||||
channelId
|
||||
};
|
||||
|
||||
participants.push(user_channel);
|
||||
|
||||
return broadcast(user_channel)
|
||||
},
|
||||
async list(data: any) {
|
||||
const { uid } = data;
|
||||
return reply({
|
||||
uid,
|
||||
participants: participants.filter(v => uid === v.channelId)
|
||||
});
|
||||
},
|
||||
async leave(data: any) {
|
||||
const { $clientId } = data;
|
||||
const removed = filterInPlace(participants, (v) => v.clientId !== $clientId);
|
||||
console.log('removed', removed);
|
||||
return broadcast(removed[0]);
|
||||
},
|
||||
})
|
||||
|
|
@ -8,11 +8,31 @@
|
|||
#tracks {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
grid-auto-flow: column;
|
||||
grid-auto-flow: row;
|
||||
}
|
||||
#tracks fieldset {
|
||||
width: '50%'
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="tracks"></div>
|
||||
<fieldset>
|
||||
<legend>Call</legend>
|
||||
<div id="call_status"></div>
|
||||
|
||||
|
||||
<label>Audio</label>
|
||||
<select id="in_audio">
|
||||
</select>
|
||||
<br />
|
||||
|
||||
<label>Video</label>
|
||||
<select id="in_video">
|
||||
</select>
|
||||
<br />
|
||||
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<legend>Peer Connection</legend>
|
||||
<div>Peer ID: <span id="pid"></span></div>
|
||||
|
|
@ -31,25 +51,8 @@
|
|||
<div id="in_status"></div>
|
||||
<button disabled type="submit" id="answer_btn">ANSWER</button>
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<legend>Call</legend>
|
||||
<div id="call_status"></div>
|
||||
|
||||
|
||||
<label>Audio</label>
|
||||
<select id="in_audio">
|
||||
</select>
|
||||
<br />
|
||||
|
||||
<label>Video</label>
|
||||
<select id="in_video">
|
||||
</select>
|
||||
<br />
|
||||
|
||||
</fieldset>
|
||||
|
||||
<div id="tracks"></div>
|
||||
|
||||
<script>
|
||||
const eStatus = document.getElementById('status');
|
||||
const ePid = document.getElementById('pid');
|
||||
|
|
@ -70,6 +73,19 @@
|
|||
let incommingCall = null;
|
||||
let currentVideoTrackId = null;
|
||||
let currentAudioTrackId = null;
|
||||
let selfBoxRemove = null;
|
||||
let ctx = null;
|
||||
|
||||
setInterval(() => {
|
||||
if(ctx === null) return;
|
||||
ctx.fillStyle = '#000000';
|
||||
ctx.fillRect(0, 0, 160, 90);
|
||||
setTimeout(() => {
|
||||
if(ctx === null) return;
|
||||
ctx.fillStyle = 'red';
|
||||
ctx.fillRect(0, 0, 160, 90);
|
||||
}, 100);
|
||||
}, 200);
|
||||
|
||||
function closeLocalMediaStream() {
|
||||
for(const track of localMediaStream.getTracks()) {
|
||||
|
|
@ -78,6 +94,7 @@
|
|||
}
|
||||
|
||||
async function createLocalMediaStream() {
|
||||
console.log('creating stream');
|
||||
if(localMediaStream !== null) {
|
||||
closeLocalMediaStream();
|
||||
}
|
||||
|
|
@ -100,22 +117,22 @@
|
|||
width: 160,
|
||||
height: 90
|
||||
});
|
||||
canvas.getContext("2d").fillRect(0, 0, 160, 90);
|
||||
ctx = canvas.getContext("2d");
|
||||
const blankStream = canvas.captureStream();
|
||||
const videoTrack = blankStream.getVideoTracks()[0];
|
||||
console.log('empty tracks', blankStream, videoTrack);
|
||||
localMediaStream.addTrack(videoTrack);
|
||||
}
|
||||
|
||||
updateOutgoing();
|
||||
};
|
||||
|
||||
console.dir(eAudioOptions);
|
||||
|
||||
async function updateOutgoing() {
|
||||
await createLocalMediaStream();
|
||||
for(const peerId in connections) {
|
||||
const { call } = connections[peerId];
|
||||
const { call, completed } = connections[peerId];
|
||||
if(!completed) continue;
|
||||
|
||||
if(call.peerConnection === undefined) debugger;
|
||||
|
||||
const audioTrack = localMediaStream.getTracks()
|
||||
.filter(track => track.kind === 'audio')[0];
|
||||
const videoTrack = localMediaStream.getTracks()
|
||||
|
|
@ -128,12 +145,21 @@
|
|||
call.peerConnection.getSenders()
|
||||
.filter(sender => sender.track.kind === 'video')[0]
|
||||
.replaceTrack(videoTrack)
|
||||
}
|
||||
|
||||
selfBoxRemove?.();
|
||||
|
||||
if(Object.keys(connections.filter(c => c.completed)).length === 0) {
|
||||
selfBoxRemove = addVideoBox(localMediaStream, 'Self', () => {
|
||||
alert('DISCO ALL CONNS');
|
||||
}).remove;
|
||||
} else {
|
||||
selfBoxRemove = null;
|
||||
}
|
||||
}
|
||||
|
||||
eAudioOptions.onchange = updateOutgoing;
|
||||
eVideoOptions.onchange = updateOutgoing;
|
||||
eAudioOptions.onchange = createLocalMediaStream;
|
||||
eVideoOptions.onchange = createLocalMediaStream;
|
||||
|
||||
// get permissions and enumerate devices
|
||||
(async function() {
|
||||
|
|
@ -145,8 +171,7 @@
|
|||
track.stop();
|
||||
}
|
||||
const devices = await navigator.mediaDevices.enumerateDevices();
|
||||
console.log(devices);
|
||||
|
||||
|
||||
for(const device of devices.filter(v => v.kind === 'audioinput')) {
|
||||
const elem = document.createElement('option');
|
||||
elem.innerText = device.label;
|
||||
|
|
@ -236,24 +261,7 @@
|
|||
onCall(call.peer);
|
||||
}
|
||||
|
||||
function onCall(peerId) {
|
||||
const { call, conn, mediaStream, completed } = connections[peerId];
|
||||
if(!call || !conn || !mediaStream) return;
|
||||
if(completed) return;
|
||||
connections[peerId].completed = true;
|
||||
|
||||
console.log('fully connected!');
|
||||
|
||||
eOutgoingStatus.innerText = 'Inactive';
|
||||
eCallBtn.disabled = false;
|
||||
eCallPid.value = '';
|
||||
eIncommingStatus.innerText = '';
|
||||
eAnswerBtn.disabled = true;
|
||||
|
||||
// while(eTracks.firstChild) {
|
||||
// eTracks.removeChild(eTracks.firstChild);
|
||||
// }
|
||||
|
||||
function addVideoBox(stream, name, end) {
|
||||
const root = document.createElement('fieldset');
|
||||
const elem = document.createElement('video');
|
||||
const legend = document.createElement('legend');
|
||||
|
|
@ -262,15 +270,12 @@
|
|||
elem.autoplay = true;
|
||||
elem.controls = true;
|
||||
elem.style.width = '100%';
|
||||
elem.srcObject = mediaStream;
|
||||
elem.srcObject = stream;
|
||||
|
||||
legend.innerText = peerId;
|
||||
legend.innerText = name;
|
||||
|
||||
endBtn.innerText = "END";
|
||||
endBtn.addEventListener('click', () => {
|
||||
conn.close();
|
||||
call.close();
|
||||
});
|
||||
endBtn.addEventListener('click', end);
|
||||
|
||||
// root.appendChild(document.createElement('br'));
|
||||
root.appendChild(legend);
|
||||
|
|
@ -280,14 +285,44 @@
|
|||
|
||||
eTracks.appendChild(root);
|
||||
|
||||
conn.on('close', () => {
|
||||
while(root.firstChild) {
|
||||
root.removeChild(root.firstChild);
|
||||
return {
|
||||
remove() {
|
||||
while(root.firstChild) {
|
||||
root.removeChild(root.firstChild);
|
||||
}
|
||||
root.remove();
|
||||
}
|
||||
root.remove();
|
||||
delete connections[peerId];
|
||||
if(Object.keys(connections).length === 0) closeLocalMediaStream();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function onCall(peerId) {
|
||||
const { call, conn, mediaStream, completed } = connections[peerId];
|
||||
if(!call || !conn || !mediaStream) return;
|
||||
if(completed) return;
|
||||
connections[peerId].completed = true;
|
||||
|
||||
eOutgoingStatus.innerText = 'Inactive';
|
||||
eCallBtn.disabled = false;
|
||||
eCallPid.value = '';
|
||||
eIncommingStatus.innerText = '';
|
||||
eAnswerBtn.disabled = true;
|
||||
}
|
||||
|
||||
function updateRemoteBoxes() {
|
||||
for(const peerId in connections.filter(c => c.completed)) {
|
||||
const { mediaStream, call, conn } = connections[peerId];
|
||||
|
||||
const { remove } = addVideoBox(mediaStream, peerId.split('-')[0], () => {
|
||||
conn.close();
|
||||
call.close();
|
||||
});
|
||||
|
||||
conn.on('close', () => {
|
||||
remove();
|
||||
delete connections[peerId];
|
||||
if(Object.keys(connections).length === 0) closeLocalMediaStream();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// eEndBtn.addEventListener('click', () => {
|
||||
|
|
|
|||
Reference in New Issue