diff --git a/app/mobile/src/context/useRingContext.hook.js b/app/mobile/src/context/useRingContext.hook.js
index 7f53f7d3..3c3d727f 100644
--- a/app/mobile/src/context/useRingContext.hook.js
+++ b/app/mobile/src/context/useRingContext.hook.js
@@ -6,6 +6,18 @@ import { keepCall } from 'api/keepCall';
import { removeCall } from 'api/removeCall';
import { removeContactCall } from 'api/removeContactCall';
+import {
+ ScreenCapturePickerView,
+ RTCPeerConnection,
+ RTCIceCandidate,
+ RTCSessionDescription,
+ RTCView,
+ MediaStream,
+ MediaStreamTrack,
+ mediaDevices,
+ registerGlobals
+} from 'react-native-webrtc';
+
export function useRingContext() {
const [state, setState] = useState({
ringing: new Map(),
@@ -31,6 +43,7 @@ export function useRingContext() {
const accessAudio = useRef(false);
const videoTrack = useRef();
const audioTrack = useRef();
+ const candidates = useRef([]);
const iceServers = [
{
@@ -96,6 +109,182 @@ export function useRingContext() {
}
},
accept: async (cardId, callId, contactNode, contactToken, calleeToken) => {
+ if (calling.current) {
+ throw new Error("active session");
+ }
+
+ const key = `${cardId}:${callId}`
+ const call = ringing.current.get(key);
+ if (call) {
+ call.status = 'accepted'
+ ringing.current.set(key, call);
+ updateState({ ringing: ringing.current });
+
+ // connect signal socket
+ candidates.current = [];
+ calling.current = { state: "connecting", callId, contactNode, contactToken, host: false };
+ updateState({ callStatus: "connecting", cardId, remoteVideo: false, remoteAudio: false });
+
+ 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);
+ console.log("ICE NEGOTIATION", event);
+ console.log("ICE NEGOTIATION", event);
+ console.log("ICE NEGOTIATION", event);
+ console.log("ICE NEGOTIATION", event);
+ console.log("ICE NEGOTIATION", event);
+ console.log("ICE NEGOTIATION", event);
+ console.log("ICE NEGOTIATION", event);
+ console.log("ICE NEGOTIATION", event);
+ console.log("ICE NEGOTIATION", event);
+ console.log("ICE NEGOTIATION", event);
+ console.log("ICE NEGOTIATION", event);
+ console.log("ICE NEGOTIATION", event);
+ } );
+ pc.current.addEventListener( 'signalingstatechange', event => {
+ console.log("ICE SIGNALING", event);
+ } );
+ pc.current.addEventListener( 'track', event => {
+
+ console.log("ICE TRACK", event.track);
+ if (stream.current == null) {
+console.log("NEW STREAM.");
+console.log("NEW STREAM.");
+console.log("NEW STREAM.");
+console.log("NEW STREAM.");
+console.log("NEW STREAM.");
+console.log("NEW STREAM.");
+console.log("NEW STREAM.");
+console.log("NEW STREAM.");
+console.log("NEW STREAM.");
+ stream.current = new MediaStream();
+ updateState({ remoteStream: stream.current });
+ }
+ stream.current.addTrack(event.track, stream.current);
+ } );
+
+
+
+
+ ws.current = createWebsocket(`wss://${contactNode}/signal`);
+ ws.current.onmessage = async (ev) => {
+ // handle messages [impolite]
+ try {
+ const signal = JSON.parse(ev.data);
+ if (signal.status === 'closed') {
+ ws.current.close();
+ }
+ else if (signal.description) {
+console.log("DESCRIPTION", signal.description);
+
+
+
+ stream.current = null;
+ if (signal.description.type === 'offer' && pc.current.signalingState !== 'stable') {
+console.log("IGNORING OFFER!");
+ return; //rudely ignore
+ }
+
+ const offer = new RTCSessionDescription(signal.description);
+ await pc.current.setRemoteDescription(offer);
+
+ if (signal.description.type === 'offer') {
+ const answer = await pc.current.createAnswer();
+ await pc.current.setLocalDescription(answer);
+ ws.current.send(JSON.stringify({ description: answer }));
+ }
+
+console.log("STATE:", pc.current.signalingState);
+
+ const adding = candidates.current;
+ candidates.current = [];
+ for (let i = 0; i < adding.length; i++) {
+ try {
+ const candidate = new RTCIceCandidate(adding[i]);
+ await pc.current.addIceCandidate(candidate);
+ console.log("success:", adding[i]);
+ }
+ catch (err) {
+ console.log(err);
+ console.log(adding[i]);
+ }
+ };
+ }
+ else if (signal.candidate) {
+ if (pc.current.remoteDescription == null) {
+ candidates.current.push(signal.candidate);
+ return;
+ }
+ const candidate = new RTCIceCandidate(signal.candidate);
+ await pc.current.addIceCandidate(candidate);
+ }
+ }
+ catch (err) {
+ console.log(err);
+ }
+ }
+ ws.current.onclose = (e) => {
+ // update state to disconnected
+ pc.current.close();
+ 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 = async () => {
+ calling.current.state = "connected"
+ updateState({ callStatus: "connected" });
+ ws.current.send(JSON.stringify({ AppToken: calleeToken }))
+
+ try {
+ const constraints = {
+ mandatory: {
+ OfferToReceiveAudio: true,
+ OfferToReceiveVideo: true,
+ VoiceActivityDetection: true
+ }
+ };
+ const offer = await pc.current.createOffer(constraints);
+ await pc.current.setLocalDescription(offer);
+ ws.current.send(JSON.stringify({ description: offer }));
+console.log("OPENING OFFER");
+console.log("OPENING OFFER");
+console.log("OPENING OFFER");
+console.log("OPENING OFFER");
+console.log("OPENING OFFER");
+console.log("OPENING OFFER");
+console.log("OPENING OFFER");
+console.log("OPENING OFFER");
+
+ }
+ catch(err) {
+ console.log(err);
+ }
+ }
+ ws.current.error = (e) => {
+ console.log(e)
+ ws.current.close();
+ }
+
+ }
},
end: async () => {
},
diff --git a/app/mobile/src/session/Session.jsx b/app/mobile/src/session/Session.jsx
index b2c6db44..6469bdc2 100644
--- a/app/mobile/src/session/Session.jsx
+++ b/app/mobile/src/session/Session.jsx
@@ -25,6 +25,7 @@ import { ProfileIcon } from './profileIcon/ProfileIcon';
import { CardsIcon } from './cardsIcon/CardsIcon';
import { Logo } from 'utils/Logo';
import splash from 'images/session.png';
+import { RTCView } from 'react-native-webrtc';
const ConversationStack = createStackNavigator();
const ProfileStack = createStackNavigator();
@@ -323,13 +324,13 @@ export function Session() {
{ label }
- actions.ignore(cardId, callId)}>
+ actions.ignore({ cardId, callId })}>
- actions.decline(cardId, contactNode, contactToken, callId)}>
+ actions.decline({ cardId, contactNode, contactToken, callId })}>
- actions.accept(cardId, callId, contactNode, contactToken, calleeToken)}>
+ actions.accept({ cardId, callId, contactNode, contactToken, calleeToken })}>
@@ -410,7 +411,7 @@ export function Session() {
0}
+ visible={ringing.length > 0 && state.callStatus == null}
supportedOrientations={['portrait', 'landscape']}
>
@@ -419,6 +420,28 @@ export function Session() {
+
+
+
+ { state.remoteStream && (
+
+
+
+ )}
+ { JSON.stringify(state.callStatus == null) }
+
+
+
);
}
diff --git a/app/mobile/src/session/Session.styled.js b/app/mobile/src/session/Session.styled.js
index 2f36f45e..e8878c55 100644
--- a/app/mobile/src/session/Session.styled.js
+++ b/app/mobile/src/session/Session.styled.js
@@ -186,5 +186,26 @@ export const styles = StyleSheet.create({
padding: 6,
marginLeft: 4,
},
+ callBase: {
+ display: 'flex',
+ width: '100%',
+ height: '100%',
+ alignItems: 'center',
+ justifyContent: 'center',
+ backgroundColor: 'rgba(52, 52, 52, 0.8)'
+ },
+ callFrame: {
+ backgroundColor: Colors.formBackground,
+ padding: 16,
+ width: '90%',
+ maxWidth: 400,
+ borderRadius: 4,
+ },
+ callRemote: {
+ width: 256,
+ height: 256,
+ backgroundColor: 'yellow',
+ },
+
});
diff --git a/app/mobile/src/session/useSession.hook.js b/app/mobile/src/session/useSession.hook.js
index a6dcceb4..4231d9a1 100644
--- a/app/mobile/src/session/useSession.hook.js
+++ b/app/mobile/src/session/useSession.hook.js
@@ -9,13 +9,21 @@ import { RingContext } from 'context/RingContext';
export function useSession() {
const [state, setState] = useState({
- ringing: [],
tabbled: null,
subWidth: '50%',
baseWidth: '50%',
cardId: null,
converstaionId: null,
firstRun: null,
+ ringing: [],
+ callStatus: null,
+ callLogo: null,
+ localStream: null,
+ localVideo: false,
+ localAudio: false,
+ remoteStream: null,
+ remoteVideo: false,
+ remoteAudio: false,
});
const ring = useContext(RingContext);
@@ -46,8 +54,15 @@ export function useSession() {
}
});
+ let callLogo = null;
+ const contact = card.state.cards.get(ring.state.cardId);
+ if (contact) {
+ const { imageSet } = contact.card?.profile || {};
+ callLogo = imageSet ? card.actions.getCardImageUrl(ring.state.cardId) : null;
+ }
+
const { callStatus, localStream, localVideo, localAudio, remoteStream, remoteVideo, remoteAudio } = ring.state;
- updateState({ ringing, callStatus, localStream, localVideo, localAudio, remoteStream, remoteVideo, remoteAudio });
+ updateState({ ringing, callStatus, callLogo, localStream, localVideo, localAudio, remoteStream, remoteVideo, remoteAudio });
}, [ring.state]);
useEffect(() => {
@@ -81,16 +96,32 @@ export function useSession() {
updateState({ firstRun: false });
store.actions.setFirstRun();
},
- ignore: async (cardId, callId) => {
- await ring.actions.ignore(cardId, callId);
+ ignore: (call) => {
+ ring.actions.ignore(call.cardId, call.callId);
},
- decline: async (cardId, contactNode, contactToken, callId) => {
+ decline: async (call) => {
+ const { cardId, contactNode, contactToken, callId } = call;
await ring.actions.decline(cardId, contactNode, contactToken, callId);
},
- accept: async (cardId, callId, contactNode, contactToken, calleeToken) => {
+ accept: async (call) => {
+ const { cardId, callId, contactNode, contactToken, calleeToken } = call;
await ring.actions.accept(cardId, callId, contactNode, contactToken, calleeToken);
},
-
+ end: async () => {
+ await ring.actions.end();
+ },
+ enableVideo: async () => {
+ await ring.actions.enableVideo();
+ },
+ disableVideo: async () => {
+ await ring.actions.disableVideo();
+ },
+ enableAudio: async () => {
+ await ring.actions.enableAudio();
+ },
+ disableAudio: async () => {
+ await ring.actions.disableAudio();
+ },
};
return { state, actions };