Vitalik
8 months ago
31 changed files with 422 additions and 350 deletions
@ -1,44 +0,0 @@ |
|||||||
import { socketEvents } from '@/shared' |
|
||||||
import { CallMod, callDataStoreHelper, useCallDataStore } from '../hooks' |
|
||||||
import { RTCSessionDescription } from 'react-native-webrtc' |
|
||||||
// import inCallManager from 'react-native-incall-manager'
|
|
||||||
|
|
||||||
class CallEventsListenter { |
|
||||||
constructor() { |
|
||||||
this.init() |
|
||||||
} |
|
||||||
|
|
||||||
private init() { |
|
||||||
this.initCallAnswered() |
|
||||||
} |
|
||||||
|
|
||||||
private initCallAnswered() { |
|
||||||
socketEvents.on('call/answered', data => { |
|
||||||
console.log('ANSWERED') |
|
||||||
callDataStoreHelper |
|
||||||
.peerConnection() |
|
||||||
.setRemoteDescription( |
|
||||||
new RTCSessionDescription(data.rtcMessage), |
|
||||||
) |
|
||||||
useCallDataStore.getState().changeMod(CallMod.Speaking) |
|
||||||
|
|
||||||
this.proccessIceCandidates() |
|
||||||
// inCallManager.stopRingback()
|
|
||||||
}) |
|
||||||
} |
|
||||||
|
|
||||||
private proccessIceCandidates() { |
|
||||||
const existIcecandidates = useCallDataStore.getState().icecandidates |
|
||||||
|
|
||||||
if (existIcecandidates.length) { |
|
||||||
existIcecandidates.map(candidate => |
|
||||||
useCallDataStore |
|
||||||
.getState() |
|
||||||
.peerConnection.addIceCandidate(candidate), |
|
||||||
) |
|
||||||
useCallDataStore.getState().cleanIcecandidates() |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
export const callEventsListener = new CallEventsListenter() |
|
@ -0,0 +1,22 @@ |
|||||||
|
import { |
||||||
|
CallDataStore, |
||||||
|
CallFromStore, |
||||||
|
ICallStreamsStore, |
||||||
|
useCallDataStore, |
||||||
|
useCallFromStore, |
||||||
|
useCallsStream, |
||||||
|
} from '../hooks' |
||||||
|
|
||||||
|
export class CallRoot { |
||||||
|
protected get store(): CallDataStore { |
||||||
|
return useCallDataStore.getState() |
||||||
|
} |
||||||
|
|
||||||
|
protected get storeFrom(): CallFromStore { |
||||||
|
return useCallFromStore.getState() |
||||||
|
} |
||||||
|
|
||||||
|
protected get streamsStore(): ICallStreamsStore { |
||||||
|
return useCallsStream.getState() |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,10 @@ |
|||||||
|
import { CallDataStore, useCallDataStore } from '../hooks' |
||||||
|
import { CallRoot } from './call-root.service' |
||||||
|
|
||||||
|
class CallUtilityService extends CallRoot { |
||||||
|
public peerConnection() { |
||||||
|
return this.store.peerConnection |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
export const callUtilityService = new CallUtilityService() |
@ -0,0 +1,68 @@ |
|||||||
|
import { NavigationService } from '@/services/system' |
||||||
|
import { AcceptCall, StartCall } from '../core' |
||||||
|
import { callsStreamsCtr, useCallDataStore, useCallFromStore } from '../hooks' |
||||||
|
import { CallRoot } from './call-root.service' |
||||||
|
import { peerConnectionService } from './peer-connection.service' |
||||||
|
import { RouteKey } from '@/shared' |
||||||
|
import { StopCall } from '../core/stop-call' |
||||||
|
import { cancelCallReq } from '@/api/calls/requests' |
||||||
|
import { RTCSessionDescription } from 'react-native-webrtc' |
||||||
|
|
||||||
|
class CallService extends CallRoot { |
||||||
|
public async proccesIncome(data: any) { |
||||||
|
const peerConnection = await peerConnectionService.createIns() |
||||||
|
|
||||||
|
useCallDataStore |
||||||
|
.getState() |
||||||
|
.incomeCall( |
||||||
|
data.callerId, |
||||||
|
data.rtcMessage, |
||||||
|
data.callId, |
||||||
|
peerConnection, |
||||||
|
) |
||||||
|
|
||||||
|
peerConnection.setRemoteDescription( |
||||||
|
new RTCSessionDescription(data.rtcMessage), |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
public async processAccept() { |
||||||
|
new AcceptCall(useCallDataStore.getState).accept() |
||||||
|
} |
||||||
|
|
||||||
|
public async processStart(targetUserId: number) { |
||||||
|
try { |
||||||
|
const peerConnection = await peerConnectionService.createIns() |
||||||
|
useCallDataStore.getState().startCall(targetUserId, peerConnection) |
||||||
|
NavigationService.navigate(RouteKey.Call, {}) |
||||||
|
|
||||||
|
new StartCall( |
||||||
|
useCallDataStore.getState, |
||||||
|
useCallFromStore.getState, |
||||||
|
).start() |
||||||
|
} catch (e) { |
||||||
|
console.log(e) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public async stop() { |
||||||
|
new StopCall(useCallDataStore.getState, callsStreamsCtr).stop() |
||||||
|
} |
||||||
|
|
||||||
|
public async finishCall() { |
||||||
|
useCallDataStore.setState(useCallDataStore.getInitialState()) |
||||||
|
NavigationService.goBack() |
||||||
|
} |
||||||
|
|
||||||
|
public async cancel() { |
||||||
|
await cancelCallReq({ |
||||||
|
callId: useCallDataStore.getState().callId, |
||||||
|
}).catch(console.log) |
||||||
|
|
||||||
|
new StopCall(useCallDataStore.getState, callsStreamsCtr).stop() |
||||||
|
useCallDataStore.setState(useCallDataStore.getInitialState()) |
||||||
|
NavigationService.goBack() |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
export const callService = new CallService() |
@ -0,0 +1,78 @@ |
|||||||
|
import { getIncomeDataReq } from '@/api/calls/requests' |
||||||
|
import RNCallKeep from 'react-native-callkeep' |
||||||
|
|
||||||
|
class CallKeepService { |
||||||
|
public register() { |
||||||
|
this.init() |
||||||
|
this.initListeners() |
||||||
|
} |
||||||
|
|
||||||
|
public unregister() { |
||||||
|
this.removeListeners() |
||||||
|
} |
||||||
|
|
||||||
|
private init() { |
||||||
|
RNCallKeep.setup({ |
||||||
|
ios: { |
||||||
|
appName: 'TaskMe', |
||||||
|
includesCallsInRecents: false, |
||||||
|
maximumCallGroups: '1', |
||||||
|
supportsVideo: false, |
||||||
|
}, |
||||||
|
android: { |
||||||
|
alertTitle: 'Permissions required', |
||||||
|
alertDescription: |
||||||
|
'This application needs to access your phone accounts', |
||||||
|
cancelButton: 'Cancel', |
||||||
|
okButton: 'ok', |
||||||
|
imageName: 'phone_account_icon', |
||||||
|
additionalPermissions: [], |
||||||
|
foregroundService: { |
||||||
|
channelId: 'com.company.my', |
||||||
|
channelName: 'Foreground service for my app', |
||||||
|
notificationTitle: 'My app is running on background', |
||||||
|
notificationIcon: |
||||||
|
'Path to the resource icon of the notification', |
||||||
|
}, |
||||||
|
}, |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
private initListeners() { |
||||||
|
RNCallKeep.addEventListener('answerCall', this.onAnswer.bind(this)) |
||||||
|
RNCallKeep.addEventListener('endCall', this.onEnd.bind(this)) |
||||||
|
RNCallKeep.addEventListener( |
||||||
|
'didPerformSetMutedCallAction', |
||||||
|
this.changeMuted.bind(this), |
||||||
|
) |
||||||
|
} |
||||||
|
private removeListeners() { |
||||||
|
RNCallKeep.clearInitialEvents() |
||||||
|
RNCallKeep.removeEventListener('answerCall') |
||||||
|
RNCallKeep.removeEventListener('endCall') |
||||||
|
RNCallKeep.removeEventListener('didPerformSetMutedCallAction') |
||||||
|
} |
||||||
|
|
||||||
|
private async onAnswer(data: any) { |
||||||
|
try { |
||||||
|
console.log('ON ANSWER', data) |
||||||
|
getIncomeDataReq(data.callUUID) |
||||||
|
} catch (e) { |
||||||
|
console.log('Error on answer', e) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private async onEnd() { |
||||||
|
console.log('onend') |
||||||
|
} |
||||||
|
|
||||||
|
private async changeMuted() { |
||||||
|
console.log('changemuted') |
||||||
|
} |
||||||
|
|
||||||
|
public async stop() { |
||||||
|
RNCallKeep.endCall('') |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
export const callKeepService = new CallKeepService() |
@ -0,0 +1,77 @@ |
|||||||
|
import { RouteKey, socketEvents } from '@/shared' |
||||||
|
import { CallRoot } from './call-root.service' |
||||||
|
import { CallMod } from '../hooks' |
||||||
|
import { callService } from './call.service' |
||||||
|
import { NavigationService } from '@/services/system' |
||||||
|
import { Alert } from 'react-native' |
||||||
|
import { RTCIceCandidate, RTCSessionDescription } from 'react-native-webrtc' |
||||||
|
|
||||||
|
class CallsEventsService extends CallRoot { |
||||||
|
constructor() { |
||||||
|
super() |
||||||
|
this.init() |
||||||
|
} |
||||||
|
|
||||||
|
private init() { |
||||||
|
socketEvents.on('call/answered', data => { |
||||||
|
this.store.peerConnection.setRemoteDescription( |
||||||
|
new RTCSessionDescription(data.rtcMessage), |
||||||
|
) |
||||||
|
this.store.changeMod(CallMod.Speaking) |
||||||
|
this.proccessIceCandidates() |
||||||
|
}) |
||||||
|
socketEvents.on('call/new', this.proccessNewCall.bind(this)) |
||||||
|
socketEvents.on( |
||||||
|
'call/ICEcandidate', |
||||||
|
this.proccessIceCandidateEvent.bind(this), |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
private proccessIceCandidates() { |
||||||
|
const existIcecandidates = this.store.icecandidates |
||||||
|
|
||||||
|
if (existIcecandidates.length) { |
||||||
|
existIcecandidates.map(candidate => |
||||||
|
this.store.peerConnection.addIceCandidate(candidate), |
||||||
|
) |
||||||
|
this.store.cleanIcecandidates() |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private async proccessNewCall(data) { |
||||||
|
console.log('procces new call', data) |
||||||
|
try { |
||||||
|
this.storeFrom.put(data.from.title, data.from.avatarImageUrl) |
||||||
|
|
||||||
|
await callService.proccesIncome(data) |
||||||
|
|
||||||
|
setTimeout(() => { |
||||||
|
callService.processAccept() |
||||||
|
NavigationService.navigate(RouteKey.Call, {}) |
||||||
|
}, 100) |
||||||
|
} catch (e) { |
||||||
|
console.log('e', e) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private proccessIceCandidateEvent(data) { |
||||||
|
try { |
||||||
|
const candidates = data.candidates |
||||||
|
const peerConnection = this.store.peerConnection |
||||||
|
|
||||||
|
candidates.map(it => { |
||||||
|
const item = new RTCIceCandidate(it) |
||||||
|
|
||||||
|
if (peerConnection) { |
||||||
|
peerConnection.addIceCandidate(item) |
||||||
|
} else { |
||||||
|
this.store.addIcecanidate(item) |
||||||
|
} |
||||||
|
}) |
||||||
|
} catch (e) { |
||||||
|
console.log(e) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
export const callsEventsService = new CallsEventsService() |
@ -0,0 +1,6 @@ |
|||||||
|
export * from './call-root.service' |
||||||
|
export * from './call-utility.service' |
||||||
|
export * from './call.service' |
||||||
|
export * from './callkeep.service' |
||||||
|
export * from './calls-events.service' |
||||||
|
export * from './peer-connection.service' |
@ -0,0 +1,66 @@ |
|||||||
|
import { MediaStream, RTCPeerConnection } from 'react-native-webrtc' |
||||||
|
import { iceServers } from '../configs' |
||||||
|
import { CallRoot } from './call-root.service' |
||||||
|
import { iceCandidateReq } from '@/api/calls/requests' |
||||||
|
import { |
||||||
|
CallMod, |
||||||
|
callsStreamsCtr, |
||||||
|
initCallsMediaDevices, |
||||||
|
useCallDataStore, |
||||||
|
} from '../hooks' |
||||||
|
import { StopCall } from '../core/stop-call' |
||||||
|
|
||||||
|
class PeerConnectionService extends CallRoot { |
||||||
|
public async createIns() { |
||||||
|
const peerConnection = new RTCPeerConnection({ |
||||||
|
iceServers: iceServers, |
||||||
|
}) |
||||||
|
|
||||||
|
await initCallsMediaDevices(peerConnection) |
||||||
|
|
||||||
|
await this.addListeners(peerConnection) |
||||||
|
|
||||||
|
return peerConnection |
||||||
|
} |
||||||
|
|
||||||
|
private async addListeners(peerConnection: RTCPeerConnection) { |
||||||
|
peerConnection.addEventListener('track', event => { |
||||||
|
try { |
||||||
|
const existremoteStream = this.streamsStore.remoteStream |
||||||
|
const remoteMediaStream = existremoteStream || new MediaStream() |
||||||
|
remoteMediaStream.addTrack(event.track) |
||||||
|
this.streamsStore.setRemoteStream(remoteMediaStream) |
||||||
|
} catch (e) { |
||||||
|
console.log(e) |
||||||
|
} |
||||||
|
}) |
||||||
|
|
||||||
|
peerConnection.addEventListener('icecandidate', event => { |
||||||
|
if (event.candidate === null) { |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
iceCandidateReq({ |
||||||
|
targetUserId: this.store.targetUserId, |
||||||
|
candidates: [event.candidate], |
||||||
|
}) |
||||||
|
}) |
||||||
|
|
||||||
|
peerConnection.addEventListener('iceconnectionstatechange', event => { |
||||||
|
this.store.setConnectedStatus(peerConnection.iceConnectionState) |
||||||
|
|
||||||
|
if (peerConnection.iceConnectionState === 'closed') { |
||||||
|
this.store.changeMod(CallMod.Finished) |
||||||
|
} |
||||||
|
|
||||||
|
if ( |
||||||
|
peerConnection.iceConnectionState === 'disconnected' || |
||||||
|
peerConnection.iceConnectionState === 'failed' |
||||||
|
) { |
||||||
|
new StopCall(useCallDataStore.getState, callsStreamsCtr).stop() |
||||||
|
} |
||||||
|
}) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
export const peerConnectionService = new PeerConnectionService() |
@ -1,27 +0,0 @@ |
|||||||
import RNCallKeep from 'react-native-callkeep' |
|
||||||
|
|
||||||
const options = { |
|
||||||
ios: { |
|
||||||
appName: 'TaskMe', |
|
||||||
}, |
|
||||||
android: { |
|
||||||
alertTitle: 'Permissions required', |
|
||||||
alertDescription: |
|
||||||
'This application needs to access your phone accounts', |
|
||||||
cancelButton: 'Cancel', |
|
||||||
okButton: 'ok', |
|
||||||
imageName: 'phone_account_icon', |
|
||||||
additionalPermissions: [], |
|
||||||
// Required to get audio in background when using Android 11
|
|
||||||
foregroundService: { |
|
||||||
channelId: 'com.company.my', |
|
||||||
channelName: 'Foreground service for my app', |
|
||||||
notificationTitle: 'My app is running on background', |
|
||||||
notificationIcon: 'Path to the resource icon of the notification', |
|
||||||
}, |
|
||||||
}, |
|
||||||
} |
|
||||||
|
|
||||||
RNCallKeep.setup(options).then(accepted => { |
|
||||||
console.log('accepted', accepted) |
|
||||||
}) |
|
Loading…
Reference in new issue