diff --git a/app/mobile/src/context/useRingContext.hook.js b/app/mobile/src/context/useRingContext.hook.js
index c65cb032..119f1830 100644
--- a/app/mobile/src/context/useRingContext.hook.js
+++ b/app/mobile/src/context/useRingContext.hook.js
@@ -65,6 +65,7 @@ export function useRingContext() {
const actions = {
setSession: (token) => {
+
if (access.current) {
throw new Error("invalid ring state");
}
@@ -160,7 +161,7 @@ export function useRingContext() {
stream.current.addTrack(event.track, stream.current);
} );
- const processOffers = async () => {
+ const impolite = async () => {
if (processing.current) {
return;
}
@@ -225,11 +226,10 @@ export function useRingContext() {
}
else if (signal.description) {
offers.current.push(signal.description);
- processOffers();
+ impolite();
}
else if (signal.candidate) {
if (pc.current.remoteDescription == null) {
- console.log("IGNOREING CANDIDATE");
return;
}
const candidate = new RTCIceCandidate(signal.candidate);
@@ -288,7 +288,8 @@ export function useRingContext() {
try {
const { host, callId, contactNode, contactToken } = calling.current;
if (host) {
- await removeCall(access.current, callId);
+ const { server, token } = access.current;
+ await removeCall(server, token, callId);
}
else {
await removeContactCall(contactNode, contactToken, callId);
@@ -308,14 +309,229 @@ export function useRingContext() {
}
},
call: async (cardId, contactNode, contactToken) => {
+ if (calling.current) {
+ throw new Error("active session");
+ }
+
+ // create call
+ const { server, token } = access.current;
+ const call = await addCall(server, token, cardId);
+ 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(server, token, id);
+ }
+ catch (err) {
+ console.log(err);
+ }
+ }, keepAlive * 1000);
+ let index = 0;
+ const ringInterval = setInterval(async () => {
+ try {
+ await addContactRing(contactNode, contactToken, { index, callId: id, calleeToken });
+ index += 1;
+ }
+ catch (err) {
+ console.log(err);
+ }
+ }, RING);
+
+ calling.current = { state: "connecting", callId: id, host: true };
+ updateState({ callStatus: "connecting", cardId, remoteVideo: false, remoteAudio: false });
+
+ // form peer connection
+ pc.current = new RTCPeerConnection({ iceServers });
+ pc.current.addEventListener( 'connectionstatechange', event => {
+ console.log("CONNECTION STATE", event);
+ } );
+ pc.current.addEventListener( 'icecandidate', event => {
+ ws.current.send(JSON.stringify({ candidate: event.candidate }));
+ } );
+ pc.current.addEventListener( 'icecandidateerror', event => {
+ console.log("ICE ERROR");
+ } );
+ pc.current.addEventListener( 'iceconnectionstatechange', event => {
+ console.log("ICE STATE CHANGE", event);
+ } );
+ pc.current.addEventListener( 'negotiationneeded', event => {
+ console.log("ICE NEGOTIATION", event);
+ } );
+ pc.current.addEventListener( 'signalingstatechange', event => {
+ console.log("ICE SIGNALING", event);
+ } );
+ pc.current.addEventListener( 'track', event => {
+ if (stream.current == null) {
+ stream.current = new MediaStream();
+ updateState({ remoteStream: stream.current });
+ }
+ if (event.track.kind === 'audio') {
+ updateState({ remoteAudio: true });
+ }
+ if (event.track.kind === 'video') {
+ updateState({ remoteVideo: true });
+ }
+ stream.current.addTrack(event.track, stream.current);
+ } );
+
+ videoTrack.current = false;
+ audioTrack.current = false;
+ accessVideo.current = false;
+ try {
+ const stream = await mediaDevices.getUserMedia({
+ video: false,
+ audio: true,
+ });
+ accessAudio.current = true;
+ updateState({ localVideo: false, localAudio: true, localStream: stream });
+ for (const track of stream.getTracks()) {
+ if (track.kind === 'audio') {
+ audioTrack.current = track;
+ }
+ if (track.kind === 'video') {
+ videoTrack.current = track;
+ }
+ pc.current.addTrack(track);
+ }
+ }
+ catch (err) {
+ console.log(err);
+ }
+
+ const polite = async () => {
+ if (processing.current) {
+ return;
+ }
+
+ processing.current = true;
+
+ while (offers.current.length > 0) {
+ descriptions = offers.current;
+ offers.current = [];
+
+ for (let i = 0; i < descriptions.length; i++) {
+ const description = descriptions[i];
+ stream.current = null;
+
+ if (description.type === 'offer' && pc.current.signalingState !== 'stable') {
+ const rollback = new RTCSessionDescription({ type: "rollback" });
+ await pc.current.setLocalDescription(reollback);
+ }
+ const offer = new RTCSessionDescription(description);
+ await pc.current.setRemoteDescription(offer);
+ if (description.type === 'offer') {
+ const answer = await pc.current.createAnswer();
+ await pc.current.setLocalDescription(answer);
+ ws.current.send(JSON.stringify({ description: answer }));
+ }
+ }
+ }
+
+ processing.current = false;
+ }
+
+ ws.current = createWebsocket(`wss://${server}/signal`);
+ ws.current.onmessage = async (ev) => {
+ // handle messages [polite]
+ 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" });
+ }
+ if (signal.status === 'closed') {
+ ws.current.close();
+ }
+ }
+ else if (signal.description) {
+ offers.current.push(signal.description);
+ polite();
+ }
+ else if (signal.candidate) {
+ if (pc.current.remoteDescription == null) {
+ return;
+ }
+ const candidate = new RTCIceCandidate(signal.candidate);
+ await pc.current.addIceCandidate(candidate);
+ }
+ }
+ catch (err) {
+ console.log(err);
+ }
+ }
+ ws.current.onclose = (e) => {
+ pc.current.close();
+ clearInterval(ringInterval);
+ clearInterval(aliveInterval);
+ calling.current = null;
+ if (videoTrack.current) {
+ videoTrack.current.stop();
+ videoTrack.current = null;
+ }
+ if (audioTrack.current) {
+ audioTrack.current.stop();
+ audioTrack.current = null;
+ }
+ updateState({ callStatus: null });
+ }
+ ws.current.onopen = () => {
+ calling.current.state = "ringing";
+ updateState({ callStatus: "ringing" });
+ ws.current.send(JSON.stringify({ AppToken: callerToken }))
+ }
+ ws.current.error = (e) => {
+ console.log(e)
+ ws.current.close();
+ }
},
enableVideo: async () => {
+ if (!accessVideo.current) {
+ const stream = await mediaDevices.getUserMedia({
+ video: true,
+ audio: true,
+ });
+ accessVideo.current = true;
+ accessAudio.current = true;
+ updateState({ localStream: stream });
+ for (const track of stream.getTracks()) {
+ if (track.kind === 'audio') {
+ audioTrack.current = track;
+ }
+ if (track.kind === 'video') {
+ videoTrack.current = track;
+ }
+ pc.current.addTrack(track);
+ }
+ }
+ else {
+ videoTrack.current.enabled = true;
+ }
+ updateState({ localVideo: true, localAudio: true });
},
disableVideo: async () => {
+ if (videoTrack.current) {
+ videoTrack.current.enabled = false;
+ }
+ updateState({ localVideo: false });
},
enableAudio: async () => {
+ if (accessAudio.current) {
+ audioTrack.current.enabled = true;
+ updateState({ localAudio: true });
+ }
},
disableAudio: async () => {
+ if (accessAudio.current) {
+ audioTrack.current.enabled = false;
+ updateState({ localAudio: false });
+ }
},
}
diff --git a/app/mobile/src/session/Session.jsx b/app/mobile/src/session/Session.jsx
index a9fda2bf..72db8e75 100644
--- a/app/mobile/src/session/Session.jsx
+++ b/app/mobile/src/session/Session.jsx
@@ -475,12 +475,12 @@ export function Session() {
)}
{ !state.localAudio && (
-
+
)}
{ state.localAudio && (
-
+
)}
diff --git a/app/mobile/src/session/contact/Contact.jsx b/app/mobile/src/session/contact/Contact.jsx
index 6670342a..3046e771 100644
--- a/app/mobile/src/session/contact/Contact.jsx
+++ b/app/mobile/src/session/contact/Contact.jsx
@@ -198,6 +198,10 @@ export function ContactBody({ contact }) {
Report Contact
+
+
+ Call Contact
+
>
)}
{ state.status === 'connecting' && (
diff --git a/app/mobile/src/session/contact/useContact.hook.js b/app/mobile/src/session/contact/useContact.hook.js
index 92af0fe7..df6f2f37 100644
--- a/app/mobile/src/session/contact/useContact.hook.js
+++ b/app/mobile/src/session/contact/useContact.hook.js
@@ -1,5 +1,6 @@
import { useState, useEffect, useRef, useContext } from 'react';
import { CardContext } from 'context/CardContext';
+import { RingContext } from 'context/RingContext';
import { getListingMessage } from 'api/getListingMessage';
import { getListingImageUrl } from 'api/getListingImageUrl';
import { addFlag } from 'api/addFlag';
@@ -22,6 +23,7 @@ export function useContact(contact) {
});
const card = useContext(CardContext);
+ const ring = useContext(RingContext);
const updateState = (value) => {
setState((s) => ({ ...s, ...value }));
@@ -151,6 +153,13 @@ export function useContact(contact) {
resync: () => {
card.actions.resync(contact.card);
},
+ ring: async () => {
+ console.log("calling!!");
+ const contact = card.state.cards.get(state.cardId);
+ const { node, guid } = contact.card?.profile || {}
+ const { token } = contact.card?.detail || {}
+ await ring.actions.call(state.cardId, node, `${guid}.${token}`);
+ },
};
return { state, actions };
diff --git a/app/mobile/src/session/useSession.hook.js b/app/mobile/src/session/useSession.hook.js
index 97e19537..4231d9a1 100644
--- a/app/mobile/src/session/useSession.hook.js
+++ b/app/mobile/src/session/useSession.hook.js
@@ -111,46 +111,16 @@ export function useSession() {
await ring.actions.end();
},
enableVideo: async () => {
- if (!accessVideo.current) {
- const stream = await mediaDevices.getUserMedia({
- video: true,
- audio: true,
- });
- accessVideo.current = true;
- accessAudio.current = true;
- updateState({ localStream: stream });
- for (const track of stream.getTracks()) {
- if (track.kind === 'audio') {
- audioTrack.current = track;
- }
- if (track.kind === 'video') {
- videoTrack.current = track;
- }
- pc.current.addTrack(track);
- }
- }
- else {
- videoTrack.current.enabled = true;
- }
- updateState({ localVideo: true, localAudio: true });
+ await ring.actions.enableVideo();
},
disableVideo: async () => {
- if (videoTrack.current) {
- videoTrack.current.enabled = false;
- }
- updateState({ localVideo: false });
+ await ring.actions.disableVideo();
},
enableAudio: async () => {
- if (accessAudio.current) {
- audioTrack.current.enabled = true;
- updateState({ localAudio: true });
- }
+ await ring.actions.enableAudio();
},
disableAudio: async () => {
- if (accessAudio.current) {
- audioTrack.current.enabled = false;
- updateState({ localAudio: false });
- }
+ await ring.actions.disableAudio();
},
};
diff --git a/net/web/src/session/contact/useContact.hook.js b/net/web/src/session/contact/useContact.hook.js
index 1354c02f..8121e666 100644
--- a/net/web/src/session/contact/useContact.hook.js
+++ b/net/web/src/session/contact/useContact.hook.js
@@ -164,7 +164,6 @@ export function useContact(guid, listing, close) {
const { node, guid } = contact.data.cardProfile;
const { token } = contact.data.cardDetail;
await ring.actions.call(state.cardId, node, `${guid}.${token}`);
- //await addContactRing(node, `${guid}.${token}`, { index: 0, callId: 'abc', calleeToken: '123' });
},
};