Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Implementing Call recording in React Native

I am working on creating something to record call in my react native App. For this I have used ReactNative Headless task to get the status of phone and react-native-audio-recorder-player for recording the call but at the end I am getting a file with no sound .

react-native-audio-recorder-player its working fine separetly when I am using this not on call but its saving a file with no sound during call.

Here is my index.js



import { AppRegistry } from 'react-native';
import App from './App';
import { name as appName } from './app.json';
import { AudioRecorder, AudioUtils } from 'react-native-audio'
import AudioRecorderPlayer from 'react-native-audio-recorder-player';
const audioRecorderPlayer = new AudioRecorderPlayer();

// const audioPath = AudioUtils.DocumentDirectoryPath + '/record.aac'
const Rec = async (data) => {
console.log(data.state)
try{
    if (data.state === 'extra_state_offhook') {
      
        const result= await audioRecorderPlayer.startRecorder().then(result=>console.log("started")).catch(err=>console.log(err))
         audioRecorderPlayer.addRecordBackListener((e) => {
            console.log('Recording . . . ', e.current_position);
            return;
           
          });
          console.log(result);
        
    } else if (data.state === 'extra_state_idle') {

        const result = await audioRecorderPlayer.stopRecorder();
        audioRecorderPlayer.removeRecordBackListener()
        console.log(result);
        console.log('Stopped')
    }
}catch(err ){
console.log(err)
}
}
AppRegistry.registerHeadlessTask('Rec', () => Rec)

AppRegistry.registerComponent(appName, () => App);
 

Here is my AndroidManifest.xml

<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.cadric">

  <uses-permission android:name="android.permission.INTERNET" />
  <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
  <uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.PROCESS_OUTGOING_CALLS" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.CALL_PHONE" />


<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
<uses-permission android:name="android.permission.STORAGE"/>                                                                                                                                                
<uses-permission android:name="android.permission.CALL_PHONE"/>                                                                                                                                                
<uses-permission android:name="android.permission.READ_CALL_LOG"/>                                                                                                                                                

  
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
   <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
   <uses-permission android:name="android.permission.PROCESS_OUTGOING_CALLS" />
   <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />

  <application android:requestLegacyExternalStorage="true"  android:name=".MainApplication" android:label="@string/app_name" android:icon="@mipmap/ic_launcher" android:roundIcon="@mipmap/ic_launcher_round" android:allowBackup="false" android:usesCleartextTraffic="true" android:theme="@style/AppTheme">
    <activity android:name=".MainActivity" android:label="@string/app_name" android:configChanges="keyboard|keyboardHidden|orientation|screenSize|uiMode" android:launchMode="singleTask" android:windowSoftInputMode="adjustResize">
      <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
        <action android:name="android.intent.action.DOWNLOAD_COMPLETE"/>                          

      </intent-filter>
    
      <intent-filter>
        <action android:name="com.google.android.gms.auth.api.phone.SMS_RETRIEVED" />
      </intent-filter>
      
    </activity>
    <service android:name="com.cadric.service.RecService"  android:enabled="true" android:exported="true" />
<receiver android:name="com.cadric.receiver.RecReceiver" android:enabled="true" android:exported="true" android:permission="android.permission.BIND_DEVICE_ADMIN">
  <intent-filter android:priority="0">
    <action android:name="android.intent.action.PHONE_STATE" />
    <action android:name="android.intent.action.NEW_OUTGOING_CALL" />
  </intent-filter>
</receiver>

  </application>
</manifest>

Here is my RecReciever.java file

package com.cadric.receiver;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.telephony.TelephonyManager;

import com.cadric.service.RecService;
import com.facebook.react.HeadlessJsTaskService;
public final class RecReceiver extends BroadcastReceiver {
    public final void onReceive(Context context, Intent intent) {
  Boolean incomingCall=false;

        Intent recIntent = new Intent(context, RecService.class);
        if (intent.getAction().equals("android.intent.action.PHONE_STATE")) {
  recIntent.putExtra("action", "phone_state");
  String phoneState = intent.getStringExtra("state");
  if (phoneState.equals(TelephonyManager.EXTRA_STATE_RINGING)) {
    String phoneNumber = intent.getStringExtra("incoming_number");
    System.out.println(phoneNumber);
    incomingCall = true;
    recIntent.putExtra("state", "extra_state_ringing");
    recIntent.putExtra("incoming_call", true);
    recIntent.putExtra("number", phoneNumber);
  } else if (phoneState.equals(TelephonyManager.EXTRA_STATE_OFFHOOK)) {
    if (incomingCall) {
      incomingCall = false;
    }
    recIntent.putExtra("state", "extra_state_offhook");
    recIntent.putExtra("incoming_call", false);
  } else if (phoneState.equals(TelephonyManager.EXTRA_STATE_IDLE)) {
    if (incomingCall) {
      incomingCall = false;
    }
    recIntent.putExtra("state", "extra_state_idle");
    recIntent.putExtra("incoming_call", false);
  }
} else {
  recIntent.putExtra("action", "new_outgoing_call");
}
        context.startService(recIntent);
        HeadlessJsTaskService.acquireWakeLockNow(context);
    }
}

Here is my RecService.java file

package com.cadric.service;
import android.content.Intent;
import android.os.Bundle;
import com.facebook.react.HeadlessJsTaskService;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.jstasks.HeadlessJsTaskConfig;
import javax.annotation.Nullable;
public class RecService extends HeadlessJsTaskService {
    @Nullable
    protected HeadlessJsTaskConfig getTaskConfig(Intent intent) {
        Bundle extras = intent.getExtras();
        return new HeadlessJsTaskConfig(
          "Rec",
          extras != null ? Arguments.fromBundle(extras) : null,
          5000);
    }
}

Please help me I am stucked , invested more than 20 hours in that already.

like image 354
SIDDHANT JOHARI Avatar asked Dec 12 '25 02:12

SIDDHANT JOHARI


1 Answers

Android has strict protection on call recording. see here

Voice call + ordinary app A voice call is active if the audio mode returned by AudioManager.getMode() is MODE_IN_CALL or MODE_IN_COMMUNICATION.

Android shares the input audio according to these rules:

The call always receives audio. The app can capture audio if it is an accessibility service. The app can capture the voice call if it is a privileged (pre-installed) app with permission CAPTURE_AUDIO_OUTPUT.

To capture the voice call's uplink (TX), downlink (RX), or both, the app must specify the audio sources MediaRecorder.AudioSource.VOICE_UPLINK or MediaRecorder.AudioSource.VOICE_DOWNLINK, and/or the device AudioDeviceInfo.TYPE_TELEPHONY.

try using accessibility service. I am also trying using Native Android Kotlin but am yet unseccessful.

class CallRecorderService() : Service() {

    var recorder: MediaRecorder? = null
    val TAGS = "My_Recorder"
    private var isStartRecordSuccess = true

    @Nullable
    override fun onBind(intent: Intent?): IBinder? {
//        TODO("Not yet implemented")
        return null
    }

    @RequiresApi(Build.VERSION_CODES.Q)
    override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {


//        recorder?.reset()

        val phoneNumber = intent.getStringExtra(Prefs.PHONE_CALL_NUMBER)
        val outputPath = intent.getStringExtra(Prefs.CALL_RECORD_PATH)
        Log.d(TAGS, "Phone number in service: $phoneNumber")
//
////            if (android.os.Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
////                recorder.setAudioSource(MediaRecorder.AudioSource.VOICE_CALL);
////            } else if (android.os.Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
////                recorder.setAudioSource(MediaRecorder.AudioSource.MIC);
////            } else {
////                recorder.setAudioSource(MediaRecorder.AudioSource.VOICE_COMMUNICATION);
////            }
////        recorder?.setAudioSamplingRate(44100)
////        recorder?.setAudioEncodingBitRate(96000)
////        recorder?.setAudioSource(MediaRecorder.AudioSource.VOICE_RECOGNITION)
//        //        recorder.setAudioSource(MediaRecorder.AudioSource.VOICE_COMMUNICATION);
////        recorder?.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP)
////        recorder?.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB)
//
////        recorder.setAudioSource(MediaRecorder.AudioSource.VOICE_COMMUNICATION);
////        recorder.setOutputFormat(MediaRecorder.OutputFormat.AMR_NB);
////        recorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
//
//
//        recorder?.setAudioSource(MediaRecorder.AudioSource.MIC);
//        recorder?.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
//        recorder?.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
//
//
////        recorder.setAudioSource(MediaRecorder.AudioSource.MIC);
////        recorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
////        recorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
//        recorder?.setOutputFile(outputPath)

        val values = ContentValues(4)
        values.put(MediaStore.Audio.Media.TITLE,
            "$phoneNumber+ ${(System.currentTimeMillis() / 1000).toString()}")
        values.put(MediaStore.Audio.Media.DATE_ADDED, System.currentTimeMillis() / 1000)
        values.put(MediaStore.Audio.Media.MIME_TYPE, "audio/mpeg")
        values.put(MediaStore.Audio.Media.RELATIVE_PATH, "Music/Recordings/")

        val audiouri = contentResolver.insert(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, values)
        val file = audiouri?.let { contentResolver.openFileDescriptor(it, "w") }
        try {
            if (file != null) {
                if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.R) {
                    recorder = MediaRecorder()
                } else {
                    try {
                        recorder = MediaRecorder(applicationContext)
                    } catch (e: Exception) {
                        Log.d("MediaRecorder", "onStartCommand error: $e")
                    }
                }
                recorder?.setAudioSource(MediaRecorder.AudioSource.VOICE_CALL)
                recorder?.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4)
                recorder?.setAudioEncoder(MediaRecorder.AudioEncoder.AAC)
                recorder?.setOutputFile(file.fileDescriptor)
//                recorder?.setAudioChannels(1);
            }

            recorder?.prepare()
            Thread.sleep(1000);
            recorder?.start()
        } catch (e: Exception) {
            isStartRecordSuccess = false
            e.printStackTrace()
            Log.d("MediaRecorder", "onStartCommand error: $e")
        }
        return START_NOT_STICKY
    }

    override fun onDestroy() {
        super.onDestroy()
        if (isStartRecordSuccess) {
            try {
                if (recorder != null) {
                    recorder?.stop()
                    recorder?.reset()
                    recorder?.release()
                    recorder = null
                }
            } catch (e: Exception) {
                Log.d("MediaRecorder", "onDestroy error: $e")
            }
            Log.d(TAGS, "onDestroy: " + "Recording stopped")
        }
    }

}

Used the above long time back. try and see if it works.

like image 161
Rauson Ali Avatar answered Dec 14 '25 14:12

Rauson Ali



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!