mirror of
https://github.com/balzack/databag.git
synced 2025-04-23 01:55:17 +00:00
support accepting calls
This commit is contained in:
parent
e6535a14b6
commit
a9e5c3ff85
@ -16,7 +16,6 @@ export const styles = StyleSheet.create({
|
||||
position: 'absolute',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
backgroundColor: 'rgb(64,64,64)',
|
||||
},
|
||||
inactive: {
|
||||
display: 'none',
|
||||
@ -25,7 +24,13 @@ export const styles = StyleSheet.create({
|
||||
position: 'absolute',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
backgroundColor: 'rgb(64,64,64)',
|
||||
},
|
||||
base: {
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
},
|
||||
container: {
|
||||
width: '100%',
|
||||
@ -33,6 +38,11 @@ export const styles = StyleSheet.create({
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
backgroundColor: 'rgb(64,64,64)',
|
||||
},
|
||||
calls: {
|
||||
borderRadius: 8,
|
||||
overflow: 'hidden',
|
||||
},
|
||||
call: {
|
||||
width: '100%',
|
||||
@ -56,6 +66,11 @@ export const styles = StyleSheet.create({
|
||||
closeIcon: {
|
||||
borderRadius: 8,
|
||||
},
|
||||
circleIcon: {
|
||||
},
|
||||
flipIcon: {
|
||||
transform: [{ rotate: '135deg' }],
|
||||
},
|
||||
name: {
|
||||
fontSize: 28,
|
||||
color: '#aaaaaa',
|
||||
@ -87,4 +102,12 @@ export const styles = StyleSheet.create({
|
||||
height: '100%',
|
||||
backgroundColor: 'rgb(64,64,64)',
|
||||
},
|
||||
card: {
|
||||
width: '100%',
|
||||
height: 48,
|
||||
paddingTop: 8,
|
||||
paddingBottom: 8,
|
||||
paddingLeft: 16,
|
||||
borderBottomWidth: 1,
|
||||
}
|
||||
});
|
||||
|
@ -10,6 +10,7 @@ import FastImage from 'react-native-fast-image'
|
||||
import LinearGradient from 'react-native-linear-gradient';
|
||||
import { Colors } from '../constants/Colors';
|
||||
import { RTCView } from 'react-native-webrtc';
|
||||
import { Card } from '../card/Card';
|
||||
|
||||
export function Calling({ callCard }: { callCard: string }) {
|
||||
const { state, actions } = useCalling();
|
||||
@ -80,6 +81,19 @@ export function Calling({ callCard }: { callCard: string }) {
|
||||
}
|
||||
}
|
||||
|
||||
const accept = async (callId, card) => {
|
||||
if (!connecting) {
|
||||
setConnecting(true);
|
||||
try {
|
||||
await actions.accept(callId, card);
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
setAlert(true);
|
||||
}
|
||||
setConnecting(false);
|
||||
}
|
||||
}
|
||||
|
||||
const alertParams = {
|
||||
title: state.strings.operationFailed,
|
||||
prompt: state.strings.tryAgain,
|
||||
@ -98,17 +112,40 @@ export function Calling({ callCard }: { callCard: string }) {
|
||||
}
|
||||
}, [callCard]);
|
||||
|
||||
const calls = state.calls.map((contact, index) => {
|
||||
const { callId, card } = contact;
|
||||
const { name, handle, node, imageUrl } = card;
|
||||
const ignore = <IconButton key="ignore" style={styles.circleIcon} iconColor="white" containerColor={Colors.pending} icon="eye-off-outline" compact="true" mode="contained" size={24} onPress={()=>{}} />
|
||||
const decline = <IconButton key="decline" style={styles.flipIcon} iconColor="white" containerColor={Colors.offsync} icon="phone-outline" compact="true" mode="contained" size={24} onPress={()=>{}} />
|
||||
const accept = <IconButton key="accept" style={styles.circleIcon} iconColor="white" containerColor={Colors.primary} icon="phone-outline" compact="true" mode="contained" size={24} onPress={()=>actions.accept(callId, card)} />
|
||||
return (
|
||||
<Surface mode="flat" key={index}>
|
||||
<Card containerStyle={styles.card} placeholder={''} imageUrl={imageUrl} name={name} node={node} handle={handle} actions={[ignore, decline, accept]} />
|
||||
</Surface>
|
||||
)
|
||||
});
|
||||
|
||||
const overlap = (width + 128) > height;
|
||||
const frameWidth = width > height ? height : width - 16;
|
||||
const frameHeight = frameWidth;
|
||||
const frameOffset = (height - frameHeight) / 8;
|
||||
const frameOffset = (height - frameHeight) / 4;
|
||||
return (
|
||||
<SafeAreaView style={(connecting || state.calling || state.ringing.length > 0 || alert) ? styles.active : styles.inactive}>
|
||||
<View style={styles.container}>
|
||||
{ connecting && !state.calling && (
|
||||
<View style={(connecting || state.calling || state.calls.length > 0 || alert) ? styles.active : styles.inactive}>
|
||||
{ state.calls.length > 0 && !connecting && !state.calling && (
|
||||
<View style={styles.base}>
|
||||
<BlurView style={styles.blur} />
|
||||
<View style={styles.calls}>
|
||||
{ calls }
|
||||
</View>
|
||||
</View>
|
||||
)}
|
||||
{ connecting && !state.calling && (
|
||||
<View style={styles.container}>
|
||||
<ActivityIndicator size={72} />
|
||||
)}
|
||||
{ state.calling && (
|
||||
</View>
|
||||
)}
|
||||
{ state.calling && (
|
||||
<View style={styles.container}>
|
||||
<View style={{ ...styles.frame, top: frameOffset, width: frameWidth, height: frameHeight }}>
|
||||
<Image
|
||||
style={{ ...styles.image, opacity: state.loaded ? 1 : 0 }}
|
||||
@ -129,58 +166,58 @@ export function Calling({ callCard }: { callCard: string }) {
|
||||
<LinearGradient style={{...styles.overlap, height: '100%', width: 16, left: 0}} start={{x: 1, y: 0}} end={{x: 0, y: 0}} colors={['rgba(64,64,64,0)', 'rgba(64,64,64, 1)']} />
|
||||
)}
|
||||
</View>
|
||||
)}
|
||||
{ state.calling && state.loaded && (
|
||||
<View style={{ ...styles.overlap, top: 16 }}>
|
||||
<View style={{backgroundColor: 'rgba(32,32,32,0.8', borderRadius: 4 }}>
|
||||
{ state.calling.name && (
|
||||
<Text style={styles.name} adjustsFontSizeToFit={true} numberOfLines={1}>{ state.calling.name }</Text>
|
||||
)}
|
||||
{ !state.calling.name && (
|
||||
<Text style={styles.name} adjustsFontSizeToFit={true} numberOfLines={1}>{ `${state.calling.handle}/${state.calling.node}` }</Text>
|
||||
)}
|
||||
</View>
|
||||
</View>
|
||||
)}
|
||||
{ state.calling && state.loaded && (
|
||||
<View style={{ ...styles.overlap, top: 64 }}>
|
||||
<View style={{backgroundColor: 'rgba(32,32,32,0.8', borderRadius: 4 }}>
|
||||
{ state.calling.name && (
|
||||
<Text style={styles.name} adjustsFontSizeToFit={true} numberOfLines={1}>{ state.calling.name }</Text>
|
||||
)}
|
||||
{ !state.calling.name && (
|
||||
<Text style={styles.name} adjustsFontSizeToFit={true} numberOfLines={1}>{ `${state.calling.handle}/${state.calling.node}` }</Text>
|
||||
)}
|
||||
</View>
|
||||
)}
|
||||
{ state.calling && state.loaded && state.remote && (
|
||||
<RTCView
|
||||
style={styles.full}
|
||||
mirror={true}
|
||||
objectFit={'contain'}
|
||||
streamURL={state.remote.toURL()}
|
||||
zOrder={2}
|
||||
/>
|
||||
)}
|
||||
{ state.calling && state.loaded && state.local && !state.remote && (
|
||||
<RTCView
|
||||
style={styles.full}
|
||||
mirror={true}
|
||||
objectFit={'contain'}
|
||||
streamURL={state.local.toURL()}
|
||||
zOrder={2}
|
||||
/>
|
||||
)}
|
||||
{ state.calling && state.loaded && state.local && state.remote && (
|
||||
<RTCView
|
||||
style={styles.box}
|
||||
mirror={true}
|
||||
objectFit={'contain'}
|
||||
streamURL={state.local.toURL()}
|
||||
zOrder={2}
|
||||
/>
|
||||
)}
|
||||
{ state.calling && state.loaded && (
|
||||
<View style={{ ...styles.overlap, bottom: frameOffset }}>
|
||||
<View style={{ paddingTop: 8, paddingBottom: 8, paddingLeft: 16, paddingRight: 16, gap: 16, display: 'flex', flexDirection: 'row', borderRadius: 16, backgroundColor: 'rgba(128,128,128,0.6)' }}>
|
||||
<IconButton style={styles.closeIcon} iconColor="white" containerColor={Colors.primary} icon={state.audioEnabled ? 'microphone' : 'microphone-off'} loading={applyingAudio} disabled={!state.audio} compact="true" mode="contained" size={32} onPress={toggleAudio} />
|
||||
<IconButton style={styles.closeIcon} iconColor="white" containerColor={Colors.primary} icon={state.videoEnabled ? 'video-outline' : 'video-off-outline'} loading={applyingVideo} disabled={!state.video} compact="true" mode="contained" size={32} onPress={toggleVideo} />
|
||||
<IconButton style={styles.closeIcon} iconColor="white" containerColor={Colors.danger} icon="phone-hangup-outline" compact="true" mode="contained" size={32} onPress={end} />
|
||||
</View>
|
||||
</View>
|
||||
)}
|
||||
{ state.calling && state.loaded && state.remote && (
|
||||
<RTCView
|
||||
style={styles.full}
|
||||
mirror={true}
|
||||
objectFit={'contain'}
|
||||
streamURL={state.remote.toURL()}
|
||||
zOrder={2}
|
||||
/>
|
||||
)}
|
||||
{ state.calling && state.loaded && state.local && !state.remote && (
|
||||
<RTCView
|
||||
style={styles.full}
|
||||
mirror={true}
|
||||
objectFit={'contain'}
|
||||
streamURL={state.local.toURL()}
|
||||
zOrder={2}
|
||||
/>
|
||||
)}
|
||||
{ state.calling && state.loaded && state.local && state.remote && (
|
||||
<RTCView
|
||||
style={styles.box}
|
||||
mirror={true}
|
||||
objectFit={'contain'}
|
||||
streamURL={state.local.toURL()}
|
||||
zOrder={2}
|
||||
/>
|
||||
)}
|
||||
{ state.calling && state.loaded && (
|
||||
<View style={{ ...styles.overlap, bottom: frameOffset }}>
|
||||
<View style={{ paddingTop: 8, paddingBottom: 8, paddingLeft: 16, paddingRight: 16, gap: 16, display: 'flex', flexDirection: 'row', borderRadius: 16, backgroundColor: 'rgba(128,128,128,0.6)' }}>
|
||||
<IconButton style={styles.closeIcon} iconColor="white" containerColor={Colors.primary} icon={state.audioEnabled ? 'microphone' : 'microphone-off'} loading={applyingAudio} disabled={!state.audio} compact="true" mode="contained" size={32} onPress={toggleAudio} />
|
||||
<IconButton style={styles.closeIcon} iconColor="white" containerColor={Colors.primary} icon={state.videoEnabled ? 'video-outline' : 'video-off-outline'} loading={applyingVideo} disabled={!state.video} compact="true" mode="contained" size={32} onPress={toggleVideo} />
|
||||
<IconButton style={styles.closeIcon} iconColor="white" containerColor={Colors.danger} icon="phone-hangup-outline" compact="true" mode="contained" size={32} onPress={end} />
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
</View>
|
||||
)}
|
||||
<Confirm show={alert} params={alertParams} />
|
||||
</SafeAreaView>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -112,7 +112,7 @@ export function useCalling() {
|
||||
|
||||
const linkMessage = async (message: any) => {
|
||||
if (call.current) {
|
||||
const { peer, link, candidates, policy } = call.current;
|
||||
const { peer, link, policy } = call.current;
|
||||
try {
|
||||
if (message.description) {
|
||||
const offer = new RTCSessionDescription(message.description);
|
||||
@ -123,11 +123,13 @@ export function useCalling() {
|
||||
link.sendMessage({ description });
|
||||
}
|
||||
|
||||
call.current.candidates = [];
|
||||
for (const candidate of candidates) {
|
||||
await peer.addIceCandidate(candidate);
|
||||
};
|
||||
candidates.length = 0;
|
||||
if (call.current) {
|
||||
const { candidates } = call.current;
|
||||
call.current.candidates = [];
|
||||
for (const candidate of candidates) {
|
||||
await peer.addIceCandidate(candidate);
|
||||
};
|
||||
}
|
||||
} else if (message.candidate) {
|
||||
const candidate = new RTCIceCandidate(message.candidate);
|
||||
if (peer.remoteDescription == null) {
|
||||
@ -189,6 +191,9 @@ export function useCalling() {
|
||||
await linkMessage(data);
|
||||
} else if (type === 'remote_track') {
|
||||
await remoteStream.current.addTrack(data, remoteStream.current);
|
||||
if (data.kind === 'video') {
|
||||
updateState({ remote: remoteStream.current });
|
||||
}
|
||||
} else if (type === 'local_track') {
|
||||
await peerTrack(data);
|
||||
}
|
||||
@ -219,10 +224,6 @@ export function useCalling() {
|
||||
});
|
||||
peerConnection.addEventListener( 'track', event => {
|
||||
updatePeer('remote_track', event.track);
|
||||
|
||||
if (event.track.kind === 'video') {
|
||||
updateState({ remote: remoteStream.current });
|
||||
}
|
||||
});
|
||||
return peerConnection;
|
||||
}
|
||||
@ -263,13 +264,15 @@ export function useCalling() {
|
||||
remoteStream.current = null;
|
||||
updateState({ calling: null, audio: null, video: null, local: null, remote: null });
|
||||
},
|
||||
accept: async (callId: string, call: Call) => {
|
||||
accept: async (callId: string, card: Card) => {
|
||||
if (call.current) {
|
||||
throw new Error('active call in progress');
|
||||
}
|
||||
const { cardId, node } = call;
|
||||
const { cardId, node } = card;
|
||||
const ring = app.state.session.getRing();
|
||||
console.log("ACCEPTING");
|
||||
const link = await ring.accept(cardId, callId, node);
|
||||
console.log("ACCEPTED");
|
||||
const ice = link.getIce();
|
||||
const peer = transmit(ice);
|
||||
const policy = 'impolite';
|
||||
@ -277,7 +280,8 @@ export function useCalling() {
|
||||
call.current = { policy, peer, link, candidates };
|
||||
link.setStatusListener(linkStatus);
|
||||
link.setMessageListener((msg) => updatePeer('message', msg));
|
||||
updateState({ calling: call.card });
|
||||
updateState({ calling: card });
|
||||
console.log("DONE");
|
||||
},
|
||||
call: async (cardId: string) => {
|
||||
if (call.current) {
|
||||
@ -295,7 +299,7 @@ export function useCalling() {
|
||||
const candidates = [];
|
||||
call.current = { policy, peer, link, candidates };
|
||||
link.setStatusListener(linkStatus);
|
||||
link.setMessageListener(linkMessage);
|
||||
link.setMessageListener((msg) => updatePeer('message', msg));
|
||||
updateState({ calling: card });
|
||||
},
|
||||
loaded: (e) => {
|
||||
|
@ -53,7 +53,13 @@ export class LinkModule implements Link {
|
||||
|
||||
public async call(node: string, secure: boolean, token: string, cardId: string, contactNode: string, contactGuid: string, contactToken: string) {
|
||||
const call = await addCall(node, secure, token, cardId);
|
||||
this.cleanup = () => { removeCall(node, secure, token, call.id) };
|
||||
this.cleanup = async () => {
|
||||
try {
|
||||
await removeCall(node, secure, token, call.id)
|
||||
} catch (err) {
|
||||
this.log.error(err);
|
||||
}
|
||||
}
|
||||
|
||||
const { id, keepAlive, calleeToken, callerToken, ice } = call;
|
||||
const insecure = /^(?!0)(?!.*\.$)((1?\d?\d|25[0-5]|2[0-4]\d)(\.|:\d+$|$)){4}$/.test(contactNode);
|
||||
@ -84,7 +90,13 @@ export class LinkModule implements Link {
|
||||
public async join(server: string, access: string, ice: { urls: string; username: string; credential: string }[]) {
|
||||
this.ice = ice;
|
||||
const insecure = /^(?!0)(?!.*\.$)((1?\d?\d|25[0-5]|2[0-4]\d)(\.|:\d+$|$)){4}$/.test(server);
|
||||
this.cleanup = () => { removeContactCall(server, !insecure, access); }
|
||||
this.cleanup = async () => {
|
||||
try {
|
||||
await removeContactCall(server, !insecure, access);
|
||||
} catch (err) {
|
||||
this.log.error(err);
|
||||
}
|
||||
}
|
||||
this.connect(access, server, !insecure);
|
||||
}
|
||||
|
||||
@ -163,8 +175,6 @@ export class LinkModule implements Link {
|
||||
await this.notifyMessage(message);
|
||||
}
|
||||
} catch (err) {
|
||||
console.log("HERE!");
|
||||
this.log.error(err, data);
|
||||
this.log.error('failed to process signal message');
|
||||
this.notifyStatus('error');
|
||||
}
|
||||
|
@ -19,7 +19,6 @@ export class RingModule implements Ring {
|
||||
this.log = log;
|
||||
this.emitter = new EventEmitter();
|
||||
this.calls = new Map<string, { call: Call, expires: number }>();
|
||||
this.ringing = [];
|
||||
this.expire = null;
|
||||
this.closed = false;
|
||||
}
|
||||
@ -58,20 +57,20 @@ export class RingModule implements Ring {
|
||||
public async accept(cardId: string, callId: string, contactNode: string): Promise<Link> {
|
||||
const now = (new Date()).getTime();
|
||||
const id = `${cardId}:${callId}`;
|
||||
const entry = this.ringing.get(id);
|
||||
const entry = this.calls.get(id);
|
||||
if (!entry || entry.expires < now || entry.status !== 'ringing') {
|
||||
throw new Error('invalid ringing entry');
|
||||
}
|
||||
entry.status = 'accepted';
|
||||
this.emitRinging();
|
||||
const link = new LinkModule(this.log);
|
||||
await link.join(contactNode, entry.call.calleeToken, ice);
|
||||
await link.join(contactNode, entry.call.calleeToken, entry.call.ice);
|
||||
return link;
|
||||
}
|
||||
|
||||
public async ignore(cardId: stirng, callId: string): Promise<void> {
|
||||
const id = `${cardId}:${callId}`;
|
||||
const entry = this.ringing.get(id);
|
||||
const entry = this.calls.get(id);
|
||||
if (!entry || entry.expires < now || entry.status !== 'ringing') {
|
||||
throw new Error('invalid ringing entry');
|
||||
}
|
||||
@ -81,7 +80,7 @@ export class RingModule implements Ring {
|
||||
|
||||
public async decline(cardId: string, callId: string, contactNode: string): Promise<void> {
|
||||
const id = `${cardId}:${callId}`;
|
||||
const entry = this.ringing.get(id);
|
||||
const entry = this.calls.get(id);
|
||||
if (!entry || entry.expires < now || entry.status !== 'ringing') {
|
||||
throw new Error('invalid ringing entry');
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user