mid mobile webrtc integration

This commit is contained in:
balzack 2023-03-29 13:07:20 -07:00
parent ee474b5f73
commit 99e1aa88f5
4 changed files with 275 additions and 11 deletions

View File

@ -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 () => {
},

View File

@ -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() {
<View key={key} style={styles.ringEntry}>
<Logo src={img} width={40} height={40} radius={4} />
<Text style={styles.ringName} numberOfLines={1} ellipsizeMode={'tail'}>{ label }</Text>
<TouchableOpacity style={styles.ringIgnore} onPress={() => actions.ignore(cardId, callId)}>
<TouchableOpacity style={styles.ringIgnore} onPress={() => actions.ignore({ cardId, callId })}>
<MatIcons name={'eye-off-outline'} size={20} color={Colors.text} />
</TouchableOpacity>
<TouchableOpacity style={styles.ringDecline} onPress={() => actions.decline(cardId, contactNode, contactToken, callId)}>
<TouchableOpacity style={styles.ringDecline} onPress={() => actions.decline({ cardId, contactNode, contactToken, callId })}>
<MatIcons name={'phone-hangup'} size={20} color={Colors.alert} />
</TouchableOpacity>
<TouchableOpacity style={styles.ringAccept} onPress={() => actions.accept(cardId, callId, contactNode, contactToken, calleeToken)}>
<TouchableOpacity style={styles.ringAccept} onPress={() => actions.accept({ cardId, callId, contactNode, contactToken, calleeToken })}>
<MatIcons name={'phone'} size={20} color={Colors.primary} />
</TouchableOpacity>
</View>
@ -410,7 +411,7 @@ export function Session() {
<Modal
animationType="fade"
transparent={true}
visible={ringing.length > 0}
visible={ringing.length > 0 && state.callStatus == null}
supportedOrientations={['portrait', 'landscape']}
>
<View style={styles.ringBase}>
@ -419,6 +420,28 @@ export function Session() {
</View>
</View>
</Modal>
<Modal
animationType="fade"
transparent={true}
visible={state.callStatus != null}
supportedOrientations={['portrait', 'landscape']}
>
<View style={styles.callBase}>
<View style={styles.callFrame}>
{ state.remoteStream && (
<View style={styles.callRemote}>
<RTCView
mirror={true}
objectFit={'cover'}
streamURL={state.remoteStream.toURL()}
zOrder={0}
/>
</View>
)}
<Text>{ JSON.stringify(state.callStatus == null) }</Text>
</View>
</View>
</Modal>
</NavigationContainer>
);
}

View File

@ -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',
},
});

View File

@ -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 };