So I am trying to create a walkie talkie app like zello in which user can receive audio stream when the app is in background or closed. But when I am trying to connect the audio when the app is opened and putting that in background that part is working. But if I close the app or put it in background other user is not able to send the audio stream.
I am using react-native-background-actions to perform background tasks and mediasoup for webrtc
import React, { useEffect, useState, useRef } from 'react';
import {
SafeAreaView,
View,
StyleSheet,
Dimensions,
TouchableOpacity,
Text,
Platform,
AppState
} from 'react-native';
import { RTCView } from 'react-native-webrtc';
import io from 'socket.io-client';
import { Device } from 'mediasoup-client';
import {
mediaDevices,
MediaStream,
registerGlobals
} from 'react-native-webrtc';
import BackgroundService from 'react-native-background-actions';
import DeviceInfo from 'react-native-device-info';
import messaging from '@react-native-firebase/messaging';
import firebase from '@react-native-firebase/app';
// Register WebRTC globals
registerGlobals();
const firebaseConfig = {
};
// Initialize Firebase
if (!firebase.apps.length) {
try {
firebase.initializeApp(firebaseConfig);
console.log('Firebase initialized successfully');
} catch (error) {
console.error('Firebase initialization error:', error);
}
}
// Background task options
const backgroundOptions = {
taskName: 'SocketConnection',
taskTitle: 'Connection Active',
taskDesc: 'Maintaining connection',
taskIcon: {
name: 'ic_launcher',
type: 'mipmap',
},
color: '#4CAF50',
parameters: {
delay: 5000,
},
};
// Socket configuration
const socketConfig = {
transports: ['websocket'],
secure: true,
withCredentials: true,
reconnection: true,
reconnectionAttempts: Infinity,
reconnectionDelay: 1000,
reconnectionDelayMax: 5000,
timeout: 20000,
pingTimeout: 30000,
pingInterval: 10000,
forceNew: false,
multiplex: false,
};
// Global socket instance
let globalSocket = null;
// Video parameters for encoding
const videoParams = {
encodings: [
{
rid: 'r0',
maxBitrate: 100000,
scalabilityMode: 'S1T3',
},
{
rid: 'r1',
maxBitrate: 300000,
scalabilityMode: 'S1T3',
},
{
rid: 'r2',
maxBitrate: 900000,
scalabilityMode: 'S1T3',
},
],
codecOptions: {
videoGoogleStartBitrate: 1000
}
};
// Audio parameters
const audioParams = {
codecOptions: {
opusStereo: true,
opusDtx: true,
opusFec: true,
opusNack: true,
}
};
const App = () => {
const [localStream, setLocalStream] = useState(null);
const [remoteStreams, setRemoteStreams] = useState(new Map());
const [isInCall, setIsInCall] = useState(false);
const [isCallInitiator, setIsCallInitiator] = useState(false);
const [isBackgroundServiceRunning, setIsBackgroundServiceRunning] = useState(false);
const [socketConnected, setSocketConnected] = useState(false);
const [fcmToken, setFcmToken] = useState(null);
// Refs for persistent values
const deviceRef = useRef(null);
const producerTransportRef = useRef(null);
const audioProducerRef = useRef(null);
const videoProducerRef = useRef(null);
const consumerTransportsRef = useRef([]);
const consumingTransportsRef = useRef([]);
const deviceIdRef = useRef(null);
const getFcmToken = async () => {
try {
if (!messaging().isDeviceRegisteredForRemoteMessages) {
await messaging().registerDeviceForRemoteMessages();
}
const authStatus = await messaging().requestPermission();
const enabled =
authStatus === messaging.AuthorizationStatus.AUTHORIZED ||
authStatus === messaging.AuthorizationStatus.PROVISIONAL;
if (enabled) {
const token = await messaging().getToken();
console.log('FCM Token:', token);
setFcmToken(token);
return token;
} else {
console.log('Authorization status:', authStatus);
}
} catch (error) {
console.error('Error getting FCM token:', error);
}
return null;
};
useEffect(() => {
const initializeApp = async () => {
try {
// Request permissions first (for iOS)
if (Platform.OS === 'ios') {
const authStatus = await messaging().requestPermission();
const enabled =
authStatus === messaging.AuthorizationStatus.AUTHORIZED ||
authStatus === messaging.AuthorizationStatus.PROVISIONAL;
if (!enabled) {
console.log('Push notifications not authorized');
return;
}
}
// Get FCM token
const token = await getFcmToken();
if (token) {
console.log('Successfully retrieved FCM token:', token);
}
// Start background service
startBackgroundService();
} catch (error) {
console.error('Error initializing app:', error);
}
};
initializeApp();
// Token refresh listener
const unsubscribe = messaging().onTokenRefresh(token => {
console.log('New FCM Token:', token);
setFcmToken(token);
});
return () => {
stopBackgroundService();
unsubscribe();
};
}, []);
useEffect(() => {
const getDeviceId = async () => {
deviceIdRef.current = await DeviceInfo.getUniqueId();
console.log(deviceIdRef.current+"device id")
};
getDeviceId();
}, []);
const getSocket = () => {
if (!globalSocket) {
console.log('Creating new socket connection');
globalSocket = io("wss://mydomain/", socketConfig);
setupSocketListeners(globalSocket);
}
return globalSocket;
};
const setupSocketListeners = (socket) => {
if (!socket) return;
socket.on('connect', async () => {
console.log('Connected to server:', socket.id);
setSocketConnected(true);
// Register device with FCM token when socket connects
const fcmToken = await getFcmToken();
socket.emit('registerUser', {
deviceId: deviceIdRef.current,
fcmToken: fcmToken
});
});
socket.on('disconnect', (reason) => {
console.log('Socket disconnected:', reason);
setSocketConnected(false);
});
socket.on('connect_error', (error) => {
console.log('Connection error:', error);
setSocketConnected(false);
});
socket.on('connection-success', ({ socketId }) => {
console.log('Connection success, socket ID:', socketId);
});
socket.on('call-started', async ({ roomName, callerSocketId }) => {
console.log('Call started in room:', roomName);
if (!isInCall) {
handleIncomingCall(roomName, callerSocketId);
}
});
socket.on('call-ended', () => {
if (isInCall) {
endCall();
}
});
socket.on('new-producer', ({ producerId }) => {
console.log('New producer received:', producerId);
signalNewConsumerTransport(producerId);
});
socket.on('producer-closed', ({ remoteProducerId }) => {
console.log('Producer closed:', remoteProducerId);
handleProducerClosed(remoteProducerId);
});
};
const handleIncomingCall = async (roomName, callerSocketId) => {
try {
const stream = await getLocalStream();
if (stream) {
setIsInCall(true);
setIsCallInitiator(false);
joinRoom(stream, roomName);
}
} catch (error) {
console.error('Error handling incoming call:', error);
}
};
// Background task that maintains socket connection
const backgroundTask = async (taskData) => {
await new Promise(async () => {
const checkConnection = () => {
const socket = getSocket();
if (socket && !socket.connected) {
console.log('Background task: Socket disconnected, reconnecting...');
socket.connect();
}
};
// Initial connection check
checkConnection();
// Periodic connection check
const intervalId = setInterval(() => {
checkConnection();
}, 5000);
// Cleanup function
return () => {
clearInterval(intervalId);
};
});
};
const startCall = async () => {
try {
const stream = await getLocalStream();
if (stream) {
setIsInCall(true);
setIsCallInitiator(true);
const roomName = 'default-room';
const socket = getSocket();
socket.emit('startCall', { roomName });
joinRoom(stream, roomName);
}
} catch (error) {
console.error('Error starting call:', error);
}
};
const getLocalStream = async () => {
try {
const stream = await mediaDevices.getUserMedia({
audio: {
echoCancellation: true,
noiseSuppression: true,
autoGainControl: true
},
video: {
width: 1280,
height: 720,
frameRate: 30
}
});
setLocalStream(stream);
return stream;
} catch (error) {
console.error('Error getting local media:', error);
try {
const audioStream = await mediaDevices.getUserMedia({
audio: {
echoCancellation: true,
noiseSuppression: true,
autoGainControl: true
},
video: false
});
setLocalStream(audioStream);
return audioStream;
} catch (audioError) {
console.error('Could not get audio stream either:', audioError);
return null;
}
}
};
const joinRoom = (stream, roomName) => {
console.log('Joining room:', roomName);
const socket = getSocket();
socket.emit('joinRoom', { roomName }, async (data) => {
console.log('Joined room with RTP capabilities:', data.rtpCapabilities);
await createDevice(data.rtpCapabilities, stream);
});
};
const createDevice = async (rtpCapabilities, stream) => {
try {
deviceRef.current = new Device();
await deviceRef.current.load({ routerRtpCapabilities: rtpCapabilities });
console.log('Device RTP Capabilities:', deviceRef.current.rtpCapabilities);
createSendTransport(stream);
} catch (error) {
console.error('Error creating device:', error);
}
};
const createSendTransport = (stream) => {
const socket = getSocket();
socket.emit('createWebRtcTransport', { consumer: false }, ({ params }) => {
if (params.error) {
console.error(params.error);
return;
}
producerTransportRef.current = deviceRef.current.createSendTransport(params);
producerTransportRef.current.on('connect', async ({ dtlsParameters }, callback, errback) => {
try {
await socket.emit('transport-connect', {
dtlsParameters,
});
callback();
} catch (error) {
errback(error);
}
});
producerTransportRef.current.on('produce', async (parameters, callback, errback) => {
try {
await socket.emit('transport-produce', {
kind: parameters.kind,
rtpParameters: parameters.rtpParameters,
appData: parameters.appData,
}, ({ id, producersExist }) => {
callback({ id });
if (producersExist) getProducers();
});
} catch (error) {
errback(error);
}
});
connectSendTransport(stream);
});
};
const connectSendTransport = async (stream) => {
const audioTrack = stream.getAudioTracks()[0];
const videoTrack = stream.getVideoTracks()[0];
if (audioTrack) {
audioProducerRef.current = await producerTransportRef.current.produce({
track: audioTrack,
...audioParams
});
}
if (videoTrack) {
videoProducerRef.current = await producerTransportRef.current.produce({
track: videoTrack,
...videoParams
});
}
};
const getProducers = () => {
const socket = getSocket();
socket.emit('getProducers', producerIds => {
console.log('Got producers:', producerIds);
producerIds.forEach(producerId => {
signalNewConsumerTransport(producerId);
});
});
};
const signalNewConsumerTransport = async (remoteProducerId) => {
if (consumingTransportsRef.current.includes(remoteProducerId)) {
return;
}
consumingTransportsRef.current.push(remoteProducerId);
const socket = getSocket();
await socket.emit('createWebRtcTransport', { consumer: true }, ({ params }) => {
if (params.error) {
console.log('Transport create error:', params.error);
return;
}
let consumerTransport;
try {
consumerTransport = deviceRef.current.createRecvTransport(params);
} catch (error) {
console.error('Error creating consumer transport:', error);
return;
}
consumerTransport.on('connect', async ({ dtlsParameters }, callback, errback) => {
try {
await socket.emit('transport-recv-connect', {
dtlsParameters,
serverConsumerTransportId: params.id,
});
callback();
} catch (error) {
errback(error);
}
});
connectRecvTransport(consumerTransport, remoteProducerId, params.id);
});
};
const connectRecvTransport = async (consumerTransport, remoteProducerId, serverConsumerTransportId) => {
const socket = getSocket();
await socket.emit('consume', {
rtpCapabilities: deviceRef.current.rtpCapabilities,
remoteProducerId,
serverConsumerTransportId,
}, async ({ params }) => {
if (params.error) {
console.log('Cannot consume:', params.error);
return;
}
const consumer = await consumerTransport.consume({
id: params.id,
producerId: params.producerId,
kind: params.kind,
rtpParameters: params.rtpParameters
});
consumerTransportsRef.current = [...consumerTransportsRef.current,
{
consumerTransport,
serverConsumerTransportId: params.id,
producerId: remoteProducerId,
consumer,
},
];
const stream = new MediaStream([consumer.track]);
setRemoteStreams(prev => new Map(prev).set(remoteProducerId, stream));
socket.emit('consumer-resume', { serverConsumerId: params.serverConsumerId });
});
};
const handleProducerClosed = (remoteProducerId) => {
const producerToClose = consumerTransportsRef.current.find(
transportData => transportData.producerId === remoteProducerId
);
if (producerToClose) {
producerToClose.consumerTransport.close();
producerToClose.consumer.close();
consumerTransportsRef.current = consumerTransportsRef.current.filter(
transportData => transportData.producerId !== remoteProducerId
);
setRemoteStreams(prev => {
const newStreams = new Map(prev);
newStreams.delete(remoteProducerId);
return newStreams;
});
}
};
const endCall = () => {
cleanup();
setIsInCall(false);
setIsCallInitiator(false);
if (isCallInitiator) {
const socket = getSocket();
socket.emit('endCall', { roomName: 'default-room' });
}
};
const cleanup = () => {
if (localStream) {
localStream.getTracks().forEach(track => track.stop());
}
if (producerTransportRef.current) {
producerTransportRef.current.close();
}
consumerTransportsRef.current.forEach(({ consumerTransport }) => {
if (consumerTransport) {
consumerTransport.close();
}
});
setLocalStream(null);
setRemoteStreams(new Map());
};
const startBackgroundService = async () => {
if (!isBackgroundServiceRunning) {
try {
await BackgroundService.start(backgroundTask, backgroundOptions);
setIsBackgroundServiceRunning(true);
console.log('Background service started');
} catch (error) {
console.error('Failed to start background service:', error);
}
}
};
const stopBackgroundService = async () => {
if (isBackgroundServiceRunning) {
try {
await BackgroundService.stop();
setIsBackgroundServiceRunning(false);
// Only close socket if we're fully stopping the app
if (globalSocket) {
globalSocket.close();
globalSocket = null;
}
cleanup();
console.log('Background service stopped');
} catch (error) {
console.error('Failed to stop background service:', error);
}
}
};
// Start background service when app starts
// Start background service when app starts
useEffect(() => {
startBackgroundService();
return () => {
stopBackgroundService();
};
}, []);
// Handle app state changes
useEffect(() => {
const subscription = AppState.addEventListener('change', nextAppState => {
if (nextAppState === 'active') {
console.log('App came to foreground');
if (!isBackgroundServiceRunning) {
startBackgroundService();
}
} else if (nextAppState === 'background') {
console.log('App went to background');
}
});
return () => {
subscription.remove();
};
}, [isBackgroundServiceRunning]);
return (
<SafeAreaView style={styles.container}>
<View style={styles.statusContainer}>
<Text style={[styles.statusText, socketConnected ? styles.connected : styles.disconnected]}>
{socketConnected ? 'Connected' : 'Disconnected'}
</Text>
</View>
{!isInCall ? (
<View style={styles.controlsContainer}>
<TouchableOpacity
style={[styles.startButton, !socketConnected && styles.disabledButton]}
onPress={startCall}
disabled={!socketConnected}
>
<Text style={styles.buttonText}>Start Call</Text>
</TouchableOpacity>
</View>
) : (
<>
<View style={styles.videoGrid}>
{localStream && (
<View style={styles.videoContainer}>
<RTCView
streamURL={localStream.toURL()}
style={styles.videoStream}
objectFit="cover"
mirror={true}
/>
</View>
)}
{Array.from(remoteStreams.entries()).map(([producerId, stream]) => (
<View key={producerId} style={styles.videoContainer}>
<RTCView
streamURL={stream.toURL()}
style={styles.videoStream}
objectFit="cover"
/>
</View>
))}
</View>
<View style={styles.controlsContainer}>
<TouchableOpacity
style={styles.endButton}
onPress={endCall}
>
<Text style={styles.buttonText}>End Call</Text>
</TouchableOpacity>
</View>
</>
)}
</SafeAreaView>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#000',
},
videoGrid: {
flex: 1,
flexDirection: 'row',
flexWrap: 'wrap',
justifyContent: 'center',
alignItems: 'center',
},
videoContainer: {
width: Dimensions.get('window').width > 940 ? 360 : 240,
height: Dimensions.get('window').width > 940 ? 270 : 180,
margin: 3,
},
videoStream: {
flex: 1,
backgroundColor: '#333',
},
controlsContainer: {
padding: 20,
alignItems: 'center',
},
serviceControls: {
flexDirection: 'row',
justifyContent: 'space-around',
padding: 10,
backgroundColor: '#111',
},
serviceButton: {
padding: 10,
borderRadius: 15,
width: 150,
alignItems: 'center',
marginHorizontal: 5,
},
startButton: {
backgroundColor: '#4CAF50',
padding: 15,
borderRadius: 25,
width: 200,
alignItems: 'center',
},
endButton: {
backgroundColor: '#F44336',
padding: 15,
borderRadius: 25,
width: 200,
alignItems: 'center',
},
disabledButton: {
backgroundColor: '#666',
},
buttonText: {
color: '#FFF',
fontSize: 16,
fontWeight: 'bold',
},
statusContainer: {
padding: 10,
alignItems: 'center',
backgroundColor: '#111',
},
statusText: {
fontSize: 14,
fontWeight: 'bold',
},
connected: {
color: '#4CAF50',
},
disconnected: {
color: '#F44336',
}
});
export default App;```
Please help me in this