1
0

Video is working

Signed-off-by: James Ketrenos <james_eikona@ketrenos.com>
This commit is contained in:
James Ketrenos 2022-03-15 17:31:33 -07:00
parent 7bb7c74234
commit f7e6d919e2
8 changed files with 175 additions and 167 deletions

View File

@ -100,8 +100,8 @@ body {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
justify-content: space-between; justify-content: space-between;
width: 30rem; width: 25rem;
max-width: 30rem; max-width: 25rem;
overflow: hidden; overflow: hidden;
z-index: 5000; z-index: 5000;
} }
@ -114,6 +114,10 @@ body {
.Table .Trade { .Table .Trade {
z-index: 25000; z-index: 25000;
transform-origin: right center;
transform: scale(0.75);
right: 0;
align-self: center;
} }
.Table .Dialogs { .Table .Dialogs {

View File

@ -325,6 +325,7 @@ const Table = () => {
<div className="Sidebar"> <div className="Sidebar">
{ name !== "" && <PlayerList/> } { name !== "" && <PlayerList/> }
{ name !== "" && <Chat/> } { name !== "" && <Chat/> }
{ /* name !== "" && <VideoFeeds/> */ }
{ loaded && <Actions {...{buildActive, setBuildActive}}/> } { loaded && <Actions {...{buildActive, setBuildActive}}/> }
</div> </div>
</div> </div>

View File

@ -1,12 +1,3 @@
.MediaAgent {
position: absolute;
display: none;
top: 0;
left: 0;
z-index: 50000;
flex-direction: column;
}
.MediaControl { .MediaControl {
display: flex; display: flex;
position: relative; position: relative;
@ -15,10 +6,20 @@
align-items: center; align-items: center;
} }
.MediaControl > div { .MediaControl .Video {
display: flex; width: 5rem;
height: 3.75rem;
max-width: 5rem;
max-height: 3.75rem;
background-color: #444;
border-radius: 0.25rem;
border: 1px solid black;
} }
.MediaAgent .Local {
border: 3px solid red; .MediaControl > div {
display: flex;
flex-direction: column;
align-items: center;
margin-right: 0.5rem;
} }

View File

@ -6,27 +6,33 @@ import VolumeOff from '@mui/icons-material/VolumeOff';
import VolumeUp from '@mui/icons-material/VolumeUp'; import VolumeUp from '@mui/icons-material/VolumeUp';
import MicOff from '@mui/icons-material/MicOff'; import MicOff from '@mui/icons-material/MicOff';
import Mic from '@mui/icons-material/Mic'; import Mic from '@mui/icons-material/Mic';
import VideocamOff from '@mui/icons-material/VideocamOff';
import Videocam from '@mui/icons-material/Videocam';
import { GlobalContext } from "./GlobalContext.js"; import { GlobalContext } from "./GlobalContext.js";
const debug = true; const debug = true;
/* Proxy object so we can pass in srcObject to <audio> */ /* Proxy object so we can pass in srcObject to <audio> */
const Audio = ({ srcObject, ...props }) => { const Video = ({ srcObject, local, ...props }) => {
const refAudio = useRef(null); const refVideo = useRef(null);
useEffect(() => { useEffect(() => {
if (!refAudio.current) { if (!refVideo.current) {
return; return;
} }
const ref = refAudio.current; const ref = refVideo.current;
if (debug) console.log('<audio> bind'); if (debug) console.log('<video> bind');
ref.srcObject = srcObject; ref.srcObject = srcObject;
if (local) {
ref.muted = true;
}
return () => { return () => {
if (debug) console.log('<audio> unbind'); if (debug) console.log('<video> unbind');
if (ref) { if (ref) {
ref.srcObject = undefined; ref.srcObject = undefined;
} }
}; };
}, [srcObject]); }, [srcObject, local]);
return <audio ref={refAudio} {...props} />; return <video ref={refVideo} {...props} />;
} }
const MediaAgent = () => { const MediaAgent = () => {
@ -38,20 +44,14 @@ const MediaAgent = () => {
const connection = event.target; const connection = event.target;
if (debug) console.log("ontrack", event); if (debug) console.log("ontrack", event);
let isLocal = true;
for (let key in peers) { for (let key in peers) {
if (peers[key].connection === connection) { if (peers[key].connection === connection) {
isLocal = false;
Object.assign(peers[key].attributes, { Object.assign(peers[key].attributes, {
srcObject: event.streams[0] srcObject: event.streams[0]
}); });
} }
} }
if (isLocal) {
throw new Error('Should not be local!');
}
if (debug) console.log(`media-agent - ontrack - remote`, peers); if (debug) console.log(`media-agent - ontrack - remote`, peers);
setPeers(Object.assign({}, peers)); setPeers(Object.assign({}, peers));
}, [peers, setPeers]); }, [peers, setPeers]);
@ -78,27 +78,9 @@ const MediaAgent = () => {
const connection = new RTCPeerConnection({ const connection = new RTCPeerConnection({
configuration: { configuration: {
offerToReceiveAudio: true, offerToReceiveAudio: true,
offerToReceiveVideo: false offerToReceiveVideo: true
}, },
iceServers: [ iceServers: [ {
/*
{ urls: "stun:stun.l.google.com:19302" },
{ urls: "stun:stun.stunprotocol.org:3478" },
*/
/*
{
urls: "turn:ketrenos.com:3478",
username: "ketra",
credential: "ketran"
},
*/
/*{
urls: "turn:ketrenos.com:3478?transport=tcp",
username: "ketra",
credential: "ketran"
},
*/
{
urls: "turns:ketrenos.com:5349", urls: "turns:ketrenos.com:5349",
username: "ketra", username: "ketra",
credential: "ketran" credential: "ketran"
@ -112,15 +94,12 @@ const MediaAgent = () => {
*/ */
] ]
}, {
/* this will no longer be needed by chrome
* eventually (supposedly), but is necessary
* for now to get firefox to talk to chrome */
//optional: [{DtlsSrtpKeyAgreement: true}]
}); });
peers[peer_id] = { peers[peer_id] = {
connection, connection,
muted: false,
videoOn: true,
attributes: { attributes: {
} }
}; };
@ -206,19 +185,11 @@ const MediaAgent = () => {
} }
const sessionDescription = ({ peer_id, session_description }) => { const sessionDescription = ({ peer_id, session_description }) => {
/**
* Peers exchange session descriptions which contains information
* about their audio / video settings and that sort of stuff. First
* the 'offerer' sends a description to the 'answerer' (with type
* "offer"), then the answerer sends one back (with type "answer").
*/
// console.log('Remote description received: ', peer_id, session_description);
const peer = peers[peer_id]; const peer = peers[peer_id];
if (!peer) { if (!peer) {
console.error(`media-agent - sessionDescription - No peer for ${peer_id}`); console.error(`media-agent - sessionDescription - No peer for ${peer_id}`);
return; return;
} }
// console.log(session_description);
const { connection } = peer; const { connection } = peer;
const desc = new RTCSessionDescription(session_description); const desc = new RTCSessionDescription(session_description);
return connection.setRemoteDescription(desc, () => { return connection.setRemoteDescription(desc, () => {
@ -240,15 +211,12 @@ const MediaAgent = () => {
console.error(`media-agent - sessionDescription - Answer setLocalDescription failed!`); console.error(`media-agent - sessionDescription - Answer setLocalDescription failed!`);
}); });
}, (error) => { }, (error) => {
// console.log("Error creating answer: ", error); console.error(error);
console.error(peer);
}); });
} }
}, (error) => { }, (error) => {
console.log(`media-agent - sessionDescription - setRemoteDescription error: `, error); console.log(`media-agent - sessionDescription - setRemoteDescription error: `, error);
}); });
// console.log("Description Object: ", desc);
}; };
const removePeer = ({peer_id}) => { const removePeer = ({peer_id}) => {
@ -348,7 +316,10 @@ const MediaAgent = () => {
peers[name] = { peers[name] = {
local: true, local: true,
muted: true, muted: true,
videoOn: false,
attributes: { attributes: {
local: true,
srcObject: stream
} }
}; };
} }
@ -381,10 +352,25 @@ const MediaAgent = () => {
navigator.mozGetUserMedia || navigator.mozGetUserMedia ||
navigator.msGetUserMedia); navigator.msGetUserMedia);
return navigator.mediaDevices.getUserMedia({audio: true, video: false})//, "video": true}) return navigator.mediaDevices.getUserMedia({audio: true, video: true})
.then((media) => { /* user accepted access to a/v */ .then((media) => { /* user accepted access to a/v */
console.log("Access granted to audio/video"); console.log("Access granted to audio/video");
setStream(media); media.getVideoTracks().forEach((track) => {
track.applyConstraints({
"video": {
"width": {
"min": 160,
"max": 320
},
"height": {
"min": 120,
"max": 240
}
}
});
});
return media;
}); });
}; };
@ -393,105 +379,108 @@ const MediaAgent = () => {
} }
if (debug) console.log(`media-agent - WebSocket open request. Attempting to create local media.`) if (debug) console.log(`media-agent - WebSocket open request. Attempting to create local media.`)
setup_local_media().then(() => { if (!stream) {
setup_local_media().then((media) => {
/* once the user has given us access to their /* once the user has given us access to their
* microphone/camcorder, join the channel and start peering up */ * microphone/camcorder, join the channel and start peering up */
setStream(media);
join(); join();
}).catch((error) => { /* user denied access to a/v */ }).catch((error) => { /* user denied access to a/v */
console.error(error); console.error(error);
console.log("Access denied for audio/video"); console.log("Access denied for audio/video");
}); });
}, [ws, setStream, name, sendMessage]); }
}, [ws, setStream, stream, peers, name, sendMessage]);
if (!ws) {
return <></>; return <></>;
} }
console.log(`media-agent - render`, peers);
const audioPeers = [];
for (let id in peers) {
const peer = peers[id];
if (peer.local) {
peer.attributes.srcObject = stream;
if (peer.attributes.srcObject) {
peer.attributes.srcObject.getAudioTracks().forEach((track) => {
track.enabled = !peer.muted;
});
}
audioPeers.push(
<Audio
className={peer.local ? 'Local' : 'Remote'}
key={`Peer-${id}`}
autoPlay='autoplay'
controls
muted
{...peer.attributes}/>
);
} else {
if (peer.muted) {
audioPeers.push(
<Audio
className={peer.local ? 'Local' : 'Remote'}
key={`Peer-${id}`}
autoPlay='autoplay'
controls
muted
{...peer.attributes}/>
);
} else {
audioPeers.push(
<Audio
className={peer.local ? 'Local' : 'Remote'}
key={`Peer-${id}`}
autoPlay='autoplay'
controls
{...peer.attributes}/>
);
}
}
}
return <div className="MediaAgent">
{ audioPeers }
</div>;
}
const MediaControl = ({isSelf, peer}) => { const MediaControl = ({isSelf, peer}) => {
const { peers, setPeers } = useContext(GlobalContext); const { peers } = useContext(GlobalContext);
const [control, setControl] = useState(undefined); const [control, setControl] = useState(undefined);
const [media, setMedia] = useState(undefined);
const [muted, setMuted] = useState(undefined);
const [videoOn, setVideoOn] = useState(undefined);
useEffect(() => { useEffect(() => {
setControl(peers[peer]); setControl(peers[peer]);
}, [peer, peers, setControl]); }, [peer, peers, setControl]);
const toggleMute = (event) => { const toggleMute = (event) => {
if (control) { if (debug) console.log(`MediaControl - toggleMute`, !muted);
control.muted = !control.muted; setMuted(!muted);
}
const update = Object.assign({}, peers);
update[peer].muted = control.muted;
if (debug) console.log(`MediaControl - toggleMute`, update);
setPeers(update);
event.stopPropagation(); event.stopPropagation();
} }
const toggleVideo = (event) => {
if (debug) console.log(`MediaControl - toggleVideo`, !videoOn);
setVideoOn(!videoOn);
event.stopPropagation();
}
useEffect(() => {
if (!control) {
setMedia(undefined);
return;
}
setMuted(control.muted);
setVideoOn(control.videoOn);
setMedia(control);
}, [control, setMedia]);
useEffect(() => {
if (!control) {
return;
}
if (control.attributes.srcObject) {
control.attributes.srcObject.getAudioTracks().forEach((track) => {
track.enabled = !muted;
});
}
}, [control, muted]);
useEffect(() => {
if (!control) {
return;
}
if (control.attributes.srcObject) {
control.attributes.srcObject.getVideoTracks().forEach((track) => {
track.enabled = videoOn;
});
}
}, [control, videoOn]);
if (!control) { if (!control) {
return <div className="MediaControl"> return <div className="MediaControl">
<div>
{ isSelf && <MicOff color={'disabled'}/> } { isSelf && <MicOff color={'disabled'}/> }
{ !isSelf && <VolumeOff color={'disabled'}/> } { !isSelf && <VolumeOff color={'disabled'}/> }
<VideocamOff color={'disabled'}/>
</div>
<video className="Video"></video>
</div>; </div>;
} }
return <div className="MediaControl"> return <div className="MediaControl">
<div>
{ isSelf && <div onClick={toggleMute}> { isSelf && <div onClick={toggleMute}>
{ control.muted && <MicOff color={'primary'}/> } { muted && <MicOff color={'primary'}/> }
{ !control.muted && <Mic color={'primary'}/> } { !muted && <Mic color={'primary'}/> }
</div> } </div> }
{ !isSelf && <div onClick={toggleMute}> { !isSelf && <div onClick={toggleMute}>
{ control.muted && <VolumeOff color={'primary'}/> } { muted && <VolumeOff color={'primary'}/> }
{ !control.muted && <VolumeUp color={'primary'}/> } { !muted && <VolumeUp color={'primary'}/> }
</div> } </div> }
<div onClick={toggleVideo}>
{ !videoOn && <VideocamOff color={'primary'}/> }
{ videoOn && <Videocam color={'primary'}/> }
</div>
</div>
{ media && <Video className="Video"
autoPlay='autoplay'
{...media.attributes}/>
}
</div>; </div>;
}; };

View File

@ -27,6 +27,7 @@
display: flex; display: flex;
flex-direction: row; flex-direction: row;
flex-wrap: wrap; flex-wrap: wrap;
justify-content: space-around;
} }
.PlayerList .Unselected > div { .PlayerList .Unselected > div {
@ -35,10 +36,15 @@
align-items: center; align-items: center;
margin: 0.25rem; margin: 0.25rem;
padding: 0.25rem; padding: 0.25rem;
max-width: 8rem;
width: 8rem;
text-overflow: ellipsis;
background-color: #eee;
border-radius: 0.25rem;
} }
.PlayerList .Unselected .Self { .PlayerList .Unselected .Self {
background-color: rgba(255, 255, 0, 0.5); border: 1px solid black;
} }
.PlayerList .PlayerSelector .PlayerColor { .PlayerList .PlayerSelector .PlayerColor {
@ -66,18 +72,24 @@
white-space: nowrap; white-space: nowrap;
} }
.PlayerList .PlayerEntry { .PlayerList .PlayerSelector .PlayerEntry {
display: flex;
flex-direction: column;
text-overflow: ellipsis;
flex: 1 1 0px;
align-items: flex-start;
border: 1px solid rgba(0,0,0,0); border: 1px solid rgba(0,0,0,0);
border-radius: 0.5em; border-radius: 0.25em;
min-width: 10em; min-width: 11em;
max-width: 11rem;
padding: 0.25rem;
} }
.PlayerList .PlayerSelector .PlayerEntry { .PlayerList .PlayerSelector .PlayerEntry > div {
flex: 1 1 0px; display: flex;
align-items: center;
display: inline-flex;
flex-direction: row; flex-direction: row;
min-width: 10em; align-items: center;
align-self: stretch;
} }
.PlayerList .PlayerEntry[data-selectable=true]:hover { .PlayerList .PlayerEntry[data-selectable=true]:hover {
@ -86,12 +98,13 @@
} }
.PlayerList .PlayerEntry[data-selected=true] { .PlayerList .PlayerEntry[data-selected=true] {
background-color: rgba(255, 255, 0, 0.5); border: 1px solid black;
} }
.PlayerList .PlayerEntry .MediaControl { .PlayerList .PlayerSelector .PlayerEntry .MediaControl {
display: flex; display: flex;
justify-self: flex-end; justify-self: flex-end;
align-self: center;
} }

View File

@ -94,9 +94,11 @@ const PlayerList = () => {
className="PlayerEntry" className="PlayerEntry"
onClick={() => { inLobby && selectable && toggleSelected(key) }} onClick={() => { inLobby && selectable && toggleSelected(key) }}
key={`player-${key}`}> key={`player-${key}`}>
<div>
<PlayerColor color={key}/> <PlayerColor color={key}/>
<div className="Name">{name ? name : 'Available' }</div> <div className="Name">{name ? name : 'Available' }</div>
{ name && !item.live && <div className="NoNetwork"></div> } { name && !item.live && <div className="NoNetwork"></div> }
</div>
{ name && item.live && <MediaControl peer={name} isSelf={key === color}/> } { name && item.live && <MediaControl peer={name} isSelf={key === color}/> }
{ !name && <div></div> } { !name && <div></div> }
</div> </div>

View File

@ -1,8 +1,6 @@
.Trade { .Trade {
display: flex; display: flex;
position: absolute; position: absolute;
right: 0;
top: 0;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
margin: 0.25rem; margin: 0.25rem;
@ -11,7 +9,7 @@
.Trade > * { .Trade > * {
max-height: calc(100vh - 2rem); max-height: calc(100vh - 2rem);
overflow: auto; overflow: auto;
width: 40rem; width: 32rem;
display: inline-flex; display: inline-flex;
padding: 0.5rem; padding: 0.5rem;
flex-direction: column; flex-direction: column;

View File

@ -62,7 +62,7 @@ const Winner = ({ winnerDismissed, setWinnerDismissed }) => {
type: 'goto-lobby' type: 'goto-lobby'
})); }));
} }
}, [ws]); }, [ws, winnerDismissed, setWinnerDismissed]);
if (!winner || winnerDismissed) { if (!winner || winnerDismissed) {
return <></>; return <></>;