1
0
peddlers-of-ketran/client/src/MediaControl.js
James Ketrenos 689b498f6b Audio is working
Signed-off-by: James Ketrenos <james_eikona@ketrenos.com>
2022-03-11 14:26:42 -08:00

436 lines
13 KiB
JavaScript

import React, { useState, useEffect, useRef, useCallback,
useContext } from "react";
import "./MediaControl.css";
import VolumeOff from '@mui/icons-material/VolumeOff';
import VolumeUp from '@mui/icons-material/VolumeUp';
import MicOff from '@mui/icons-material/MicOff';
import Mic from '@mui/icons-material/Mic';
import { GlobalContext } from "./GlobalContext.js";
const debug = false;
/* Proxy object so we can pass in srcObject to <audio> */
const Audio = ({ srcObject, ...props }) => {
const refAudio = useRef(null);
useEffect(() => {
if (!refAudio.current) {
return;
}
const ref = refAudio.current;
if (debug) console.log('<audio> bind');
ref.srcObject = srcObject;
return () => {
if (debug) console.log('<audio> unbind');
if (ref) {
ref.srcObject = undefined;
}
};
}, [srcObject]);
return <audio ref={refAudio} {...props} />;
}
const MediaAgent = () => {
const { name, ws, peers, setPeers } = useContext(GlobalContext);
const [stream, setStream] = useState(undefined);
const onTrack = useCallback((event) => {
const connection = event.target;
if (debug) console.log("ontrack", event);
let isLocal = true;
for (let key in peers) {
if (peers[key].connection === connection) {
isLocal = false;
Object.assign(peers[key].attributes, {
srcObject: event.streams[0]
});
}
}
if (isLocal) {
throw new Error('Should not be local!');
}
if (debug) console.log(`MediaAgent - ontrack - remote`, peers);
setPeers(Object.assign({}, peers));
}, [peers, setPeers]);
const refOnTrack = useRef(onTrack);
const onWsMessage = useCallback((event) => {
const data = JSON.parse(event.data);
if (debug) console.log(`MediaAgent - onWsMessage`, peers);
const addPeer = (config) => {
console.log('Signaling server said to add peer:', config);
if (!stream) {
console.log(`No local media stream`);
return;
}
const peer_id = config.peer_id;
if (peer_id in peers) {
return;
}
const connection = new RTCPeerConnection({
/*configuration: {
offerToReceiveAudio: true,
offerToReceiveVideo: false
},*/
iceServers: [ { urls: "stun:stun.l.google.com:19302" } ]
}, {
/* 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] = {
connection,
attributes: {
}
};
if (debug) console.log(`MediaAgent - addPeer - remote`, peers);
setPeers(Object.assign({}, peers));
connection.onicecandidate = (event) => {
if (!event.candidate) {
return;
}
ws.send(JSON.stringify({
type: 'relayICECandidate',
config: {
peer_id: peer_id,
ice_candidate: {
sdpMLineIndex: event.candidate.sdpMLineIndex,
candidate: event.candidate.candidate
}
}
}));
};
connection.ontrack = e => refOnTrack.current(e);;
/* Add our local stream */
connection.addStream(stream);
/* Only one side of the peer connection should create the
* offer, the signaling server picks one to be the offerer.
* The other user will get a 'sessionDescription' event and will
* create an offer, then send back an answer 'sessionDescription'
* to us
*/
if (config.should_create_offer) {
if (debug) console.log("MediaAgent - Creating RTC offer to ", peer_id);
return connection.createOffer()
.then((local_description) => {
if (debug) console.log("Local offer description is: ", local_description);
return connection.setLocalDescription(local_description)
.then(() => {
ws.send(JSON.stringify({
type: 'relaySessionDescription',
config: {
'peer_id': peer_id,
'session_description': local_description
}
}));
if (debug) console.log("Offer setLocalDescription succeeded");
})
.catch((error) => {
console.error("Offer setLocalDescription failed!");
});
})
.catch((error) => {
console.log("Error sending offer: ", error);
});
}
}
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];
if (!peer) {
console.error(`No peer for ${peer_id}`);
return;
}
// console.log(session_description);
const { connection } = peer;
const desc = new RTCSessionDescription(session_description);
return connection.setRemoteDescription(desc, () => {
if (debug) console.log("setRemoteDescription succeeded");
if (session_description.type === "offer") {
if (debug) console.log("Creating answer");
connection.createAnswer((local_description) => {
if (debug) console.log("Answer description is: ", local_description);
connection.setLocalDescription(local_description, () => {
ws.send(JSON.stringify({
type: 'relaySessionDescription',
config: {
peer_id,
session_description: local_description
}
}));
if (debug) console.log("Answer setLocalDescription succeeded");
}, () => {
console.error("Answer setLocalDescription failed!");
});
}, (error) => {
// console.log("Error creating answer: ", error);
console.error(peer);
});
}
}, (error) => {
console.log("setRemoteDescription error: ", error);
});
// console.log("Description Object: ", desc);
};
const removePeer = ({peer_id}) => {
console.log('Signaling server said to remove peer:', peer_id);
if (peer_id in peers) {
if (peers[peer_id].connection) {
peers[peer_id].connnection.close();
}
}
delete peers[peer_id];
if (debug) console.log(`MediaAgent - removePeer - remote or local?`, peers);
setPeers(Object.assign({}, peers));
};
const iceCandidate = ({ peer_id, ice_candidate }) => {
/**
* The offerer will send a number of ICE Candidate blobs to the
* answerer so they can begin trying to find the best path to one
* another on the net.
*/
const peer = peers[peer_id];
if (!peer) {
console.error(`No peer for ${peer_id}`, peers);
return;
}
peer.connection.addIceCandidate(new RTCIceCandidate(ice_candidate))
.then(() => {
if (debug) console.log(`Successfully added Ice Candidate for ${peer_id}`);
})
.catch((error) => {
console.error(error, peer, ice_candidate);
});
};
switch (data.type) {
case 'addPeer': addPeer(data.data); break;
case 'removePeer': removePeer(data.data); break;
case 'iceCandidate': iceCandidate(data.data); break;
case 'sessionDescription': sessionDescription(data.data); break;
default: break;
}
}, [ peers, setPeers, stream, ws, refOnTrack ]);
const refWsMessage = useRef(onWsMessage);
const onWsClose = (event) => {
console.log(`${name} Disconnected from signaling server`);
/* Tear down all of our peer connections and remove all the
* media divs when we disconnect */
for (let peer_id in peers) {
if (peers[peer_id].local) {
continue;
}
peers[peer_id].connection.close();
}
for (let id in peers) {
delete peers[id];
}
if (debug) console.log(`MediaAgent - close - local or remote?`, peers);
setPeers(Object.assign({}, peers));
}
const refWsClose = useRef(onWsClose);
useEffect(() => {
refWsMessage.current = onWsMessage;
refWsClose.current = onWsClose;
refOnTrack.current = onTrack;
});
useEffect(() => {
if (!ws) {
return;
}
const cbMessage = e => refWsMessage.current(e);
ws.addEventListener('message', cbMessage);
const cbClose = e => refWsClose.current(e);
ws.addEventListener('close', cbClose);
return () => {
ws.removeEventListener('message', cbMessage);
ws.removeEventListener('close', cbClose);
}
}, [ws, refWsMessage, refWsClose ]);
useEffect(() => {
if (!name) {
return;
}
let update = false;
if (stream) {
if (!(name in peers)) {
update = true;
peers[name] = {
local: true,
attributes: {
}
};
}
}
for (let key in peers) {
if (peers[key].local && key !== name) {
delete peers[key];
update = true;
}
}
if (update) {
if (debug) console.log(`MediaAgent - Adding local`, peers);
setPeers(Object.assign({}, peers));
}
}, [peers, name, setPeers, stream]);
useEffect(() => {
if (!ws || !name) {
return;
}
const setup_local_media = () => {
/* Ask user for permission to use the computers microphone and/or camera,
* attach it to an <audio> or <video> tag if they give us access. */
console.log("Requesting access to local audio / video inputs");
navigator.getUserMedia = (navigator.getUserMedia ||
navigator.webkitGetUserMedia ||
navigator.mozGetUserMedia ||
navigator.msGetUserMedia);
return navigator.mediaDevices.getUserMedia({audio: true, video: false})//, "video": true})
.then((media) => { /* user accepted access to a/v */
console.log("Access granted to audio/video");
setStream(media);
});
};
const join = () => {
ws.send(JSON.stringify({ type: 'join' }));
}
if (debug) console.log(`MediaAgent - WebSocket open request. Attempting to create local media.`)
setup_local_media().then(() => {
/* once the user has given us access to their
* microphone/camcorder, join the channel and start peering up */
join();
}).catch((error) => { /* user denied access to a/v */
console.error(error);
console.log("Access denied for audio/video");
});
}, [ws, setStream, name]);
if (!ws) {
return <></>;
}
if (debug) console.log(`MediaAgent`, 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 { peers, setPeers } = useContext(GlobalContext);
const [control, setControl] = useState(undefined);
useEffect(() => {
setControl(peers[peer]);
}, [peer, peers, setControl]);
const toggleMute = (event) => {
if (control) {
control.muted = !control.muted;
}
const update = Object.assign({}, peers);
update[peer].muted = control.muted;
if (debug) console.log(`MediaControl - toggleMute`, update);
setPeers(update);
event.stopPropagation();
}
if (!control) {
return <div className="MediaControl">
{ isSelf && <MicOff color={'disabled'}/> }
{ !isSelf && <VolumeOff color={'disabled'}/> }
</div>;
}
return <div className="MediaControl">
{ isSelf && <div onClick={toggleMute}>
{ control.muted && <MicOff color={'primary'}/> }
{ !control.muted && <Mic color={'primary'}/> }
</div> }
{ !isSelf && <div onClick={toggleMute}>
{ control.muted && <VolumeOff color={'primary'}/> }
{ !control.muted && <VolumeUp color={'primary'}/> }
</div> }
</div>;
};
export { MediaControl, MediaAgent };