connecting rtc peer

This commit is contained in:
Roland Osborne 2023-03-24 14:46:44 -07:00
parent 7a36d4843d
commit d5ada715af
3 changed files with 152 additions and 23 deletions

View File

@ -11,7 +11,7 @@ var bridgeRelay BridgeRelay;
const BridgeKeepAlive = 15
type BridgeStatus struct {
status string
Status string `json:"status"`
}
type Bridge struct {
@ -28,13 +28,13 @@ type Bridge struct {
type BridgeRelay struct {
sync sync.Mutex
bridges []Bridge
bridges []*Bridge
}
func (s *BridgeRelay) AddBridge(accountId uint, callId string, callerToken string, calleeToken string) {
s.sync.Lock()
defer s.sync.Unlock()
bridge := Bridge{
bridge := &Bridge{
accountId: accountId,
callId: callId,
expires: time.Now().Unix() + (BridgeKeepAlive * 3),
@ -45,8 +45,8 @@ func (s *BridgeRelay) AddBridge(accountId uint, callId string, callerToken strin
s.bridges = append(s.bridges, bridge)
}
func setStatus(bridge Bridge, status string) {
msg, _ := json.Marshal(BridgeStatus{ status: status })
func setStatus(bridge *Bridge, status string) {
msg, _ := json.Marshal(BridgeStatus{ Status: status })
if bridge.caller != nil {
if err := bridge.caller.WriteMessage(websocket.TextMessage, msg); err != nil {
LogMsg("failed to notify bridge status");
@ -63,7 +63,7 @@ func (s *BridgeRelay) KeepAlive(accountId uint, callId string) {
s.sync.Lock()
defer s.sync.Unlock()
now := time.Now().Unix()
var bridges []Bridge
var bridges []*Bridge
for _, bridge := range s.bridges {
if bridge.expires > now {
bridges = append(bridges, bridge)
@ -90,7 +90,7 @@ func (s *BridgeRelay) KeepAlive(accountId uint, callId string) {
func (s *BridgeRelay) RemoveBridge(accountId uint, callId string, cardId string) {
s.sync.Lock()
defer s.sync.Unlock()
var bridges []Bridge
var bridges []*Bridge
for _, bridge := range s.bridges {
if bridge.callId == callId && bridge.accountId == accountId && (bridge.cardId == cardId || cardId == "") {
setStatus(bridge, "closed");

View File

@ -13,10 +13,12 @@ export function useRingContext() {
});
const access = useRef(null);
const EXPIRE = 3000000
const EXPIRE = 3000
const RING = 2000
const ringing = useRef(new Map());
const calling = useRef(null);
const ws = useRef(null);
const pc = useRef(null);
const updateState = (value) => {
setState((s) => ({ ...s, ...value }))
@ -43,7 +45,7 @@ export function useRingContext() {
updateState({ ringing: ringing.current });
setTimeout(() => {
updateState({ ringing: ringing.current });
}, 3000);
}, EXPIRE);
},
ignore: (cardId, callId) => {
const key = `${cardId}:${callId}`
@ -78,20 +80,73 @@ export function useRingContext() {
// connect signal socket
calling.current = { state: "connecting", callId, contactNode, contactToken, host: false };
updateState({ callStatus: "connecting" });
// form peer connection
pc.current = new RTCPeerConnection();
pc.current.ontrack = ({streams: [stream]}) => {
console.log("ON TRACK");
};
pc.current.onicecandidate = ({candidate}) => {
ws.current.send(JSON.stringify({ candidate }));
};
pc.current.onnegotiationneeded = async () => {
if (calling.current.state === 'connected') {
const offer = await pc.current.createOffer();
if (pc.current.signalingState !== 'stable') {
return;
}
await pc.current.setLocalDescription(offer);
ws.current.send(JSON.stringify({ description: pc.current.localDescription }));
}
};
const stream = await whiteNoise();
pc.current.addTransceiver(stream.getTracks()[0], {streams: [stream]});
ws.current = createWebsocket(`wss://${contactNode}/signal`);
ws.current.onmessage = (ev) => {
ws.current.onmessage = async (ev) => {
// handle messages [impolite]
console.log(ev);
try {
const signal = JSON.parse(ev.data);
if (signal.description) {
if (signal.description.type === 'offer' && pc.current.signalingState !== 'stable') {
return; //rudely ignore
}
await pc.current.setRemoteDescription(signal.description);
if (signal.description.type === 'offer') {
const answer = await pc.current.createAnswer();
await pc.current.setLocalDescription(answer);
ws.current.send(JSON.stringify({ description: pc.current.localDescription }));
}
}
else if (signal.candidate) {
await pc.current.addIceCandidate(signal.candidate);
}
console.log(signal);
}
catch (err) {
console.log(err);
}
}
ws.current.onclose = (e) => {
// update state to disconnected
pc.current.close();
calling.current = null;
updateState({ calling: null });
}
ws.current.onopen = () => {
ws.current.onopen = async () => {
calling.current.state = "connected"
updateState({ callStatus: "connected" });
ws.current.send(JSON.stringify({ AppToken: calleeToken }))
try {
const offer = await pc.current.createOffer();
await pc.current.setLocalDescription(offer);
ws.current.send(JSON.stringify({ description: pc.current.localDescription }));
}
catch(err) {
console.log(err);
}
}
ws.current.error = (e) => {
console.log(e)
@ -124,10 +179,16 @@ export function useRingContext() {
// create call
const call = await addCall(access.current, cardId);
const { callId, keepAlive, callerToken, calleeToken } = call;
const { id, keepAlive, callerToken, calleeToken } = call;
try {
await addContactRing(contactNode, contactToken, { index, callId: id, calleeToken });
}
catch (err) {
console.log(err);
}
const aliveInterval = setInterval(async () => {
try {
await keepCall(access.current, call.callId);
await keepCall(access.current, id);
}
catch (err) {
console.log(err);
@ -136,23 +197,74 @@ export function useRingContext() {
let index = 0;
const ringInterval = setInterval(async () => {
try {
await addContactRing(contactNode, contactToken, { index, callId, calleeToken });
await addContactRing(contactNode, contactToken, { index, callId: id, calleeToken });
index += 1;
}
catch (err) {
console.log(err);
}
}, 3000);
calling.current = { state: "connecting", callId, host: true };
}, RING);
calling.current = { state: "connecting", callId: id, host: true };
updateState({ callStatus: "connecting" });
// form peer connection
pc.current = new RTCPeerConnection();
pc.current.ontrack = ({streams: [stream]}) => {
console.log("ON TRACK");
};
pc.current.onicecandidate = ({candidate}) => {
ws.current.send(JSON.stringify({ candidate }));
};
pc.current.onnegotiationneeded = async () => {
if (calling.current.state === 'connected') {
const offer = await pc.current.createOffer();
if (pc.current.signalingState !== 'stable') {
return;
}
await pc.current.setLocalDescription(offer);
ws.current.send(JSON.stringify({ description: pc.current.localDescription }));
}
};
const stream = await whiteNoise();
pc.current.addTransceiver(stream.getTracks()[0], {streams: [stream]});
const protocol = window.location.protocol === 'http:' ? 'ws://' : 'wss://';
ws.current = createWebsocket(`${protocol}${window.location.host}/signal`);
ws.current.onmessage = (ev) => {
ws.current.onmessage = async (ev) => {
// handle messages [polite]
// on connected stop ringing
console.log(ev);
try {
const signal = JSON.parse(ev.data);
if (signal.status) {
if (calling.current.state !== 'connected' && signal.status === 'connected') {
clearInterval(ringInterval);
calling.current.state = 'connected';
updateState({ callStatus: "connected" });
}
}
else if (signal.description) {
if (signal.description.type === 'offer' && pc.current.signalingState !== 'stable') {
await pc.current.setLocalDescription({ type: "rollback" });
}
await pc.current.setRemoteDescription(signal.description);
if (signal.description.type === 'offer') {
const answer = await pc.current.createAnswer();
await pc.current.setLocalDescription(answer);
ws.current.send(JSON.stringify({ description: pc.current.localDescription }));
}
}
else if (signal.candidate) {
await pc.current.addIceCandidate(signal.candidate);
}
console.log(signal);
}
catch (err) {
console.log(err);
}
}
ws.current.onclose = (e) => {
pc.current.close();
clearInterval(ringInterval);
clearInterval(aliveInterval);
calling.current = null;
@ -173,4 +285,18 @@ export function useRingContext() {
return { state, actions }
}
function whiteNoise() {
const canvas = Object.assign(document.createElement("canvas"), {width: 320, height: 240});
const ctx = canvas.getContext('2d');
ctx.fillRect(0, 0, 320, 240);
const p = ctx.getImageData(0, 0, 320, 240);
requestAnimationFrame(function draw(){
for (var i = 0; i < p.data.length; i++) {
p.data[i++] = p.data[i++] = p.data[i++] = Math.random() * 255;
}
ctx.putImageData(p, 0, 0);
requestAnimationFrame(draw);
});
return canvas.captureStream();
}

View File

@ -49,9 +49,11 @@ export function useSession() {
if (call.expires > expired && !call.status) {
const { callId, cardId, calleeToken } = call;
const contact = card.state.cards.get(cardId);
const { imageSet, name, handle, node } = contact.data.cardProfile || {};
const { imageSet, name, handle, node, guid } = contact.data.cardProfile || {};
const { token } = contact.data.cardDetail;
const contactToken = `${guid}.${token}`;
const img = imageSet ? card.actions.getCardImageUrl(cardId) : 'avatar';
ringing.push({ cardId, img, name, handle, node, callId, calleeToken });
ringing.push({ cardId, img, name, handle, contactNode: node, callId, contactToken, calleeToken });
}
});
updateState({ ringing });
@ -144,7 +146,8 @@ export function useSession() {
ring.actions.decline(call.cardId, call.callId);
},
accept: (call) => {
ring.actions.accept(call.cardId, call.callId);
const { cardId, callId, contactNode, contactToken, calleeToken } = call;
ring.actions.accept(cardId, callId, contactNode, contactToken, calleeToken);
},
};