Handling media-routes/sensors/events during a audio/video chat on React Native
The purpose of this module is to handle actions/events during a phone call (audio/video) on react-native
, ex:
- manage devices events like wired-headset plugged, proximity sensors and expose to javascript.
- automatically route audio to proper device based on events and platform API.
- toggle speaker or microphone on/off, toggle flash light on/off (not implemented yes)
- play ringtone/ringback/dtmftone
basically, it is a telecommunication module which handle most of requirements when making/receiving/talking to a call.
This module is desinged to work with react-native-webrtc you can find demo here: https://github.com/oney/RCTWebRTCDemo
-
since
2.1.0
, you should useRN 40+
and upgrade your xcode to supportswift 3
.
after upgrading xcode,Edit -> Convert -> To Current Swift Syntax
to invokeSwift Migration Assistant
see Migrating to Swift 2.3 or Swift 3 from Swift 2.2 -
for old RN versions (RN < 0.40) please use version
1.5.4
( Swift 2.2~2.3 )
from npm package: npm install react-native-incall-manager
from git package: npm install git://github.com/zxcpoiu/react-native-incall-manager.git
===================================================
After install, you can use rnpm
(npm install rnpm -g
) to link android.
use rnpm link react-native-incall-manager
to link or manually if you like.
optional sound files on android if you want to use bundled ringtone/ringback/busytone sound instead of system sound
We use android support library v4 to check/request permissions.
You should add compile "com.android.support:support-v4:23.0.1"
in $your_project/android/app/build.gradle
dependencies on android.
put files in android/app/src/main/res/raw
and rename file correspond to sound type:
incallmanager_busytone.mp3
incallmanager_ringback.mp3
incallmanager_ringtone.mp3
on android, as long as your file extension supported by android, this module will load it.
===================================================
since ios part written in swift and it doesn't support static library yet.
before that, you should add this project manually:
-
Add files in to your project:
- Open your project in xcode
- find your_project directory under your project's xcodeproject root. ( it's a sub-directoory, not root xcodeproject itself )
- you can do either:
- directly drag your node_modules/react-native-incall-manager/ios/RNInCallManager/ into it.
- right click on your_project directory,
add files
to your project and addnode_modules/react-native-incall-manager/ios/RNInCallManager/
- on the pou-up window, uncheck
Copy items if needed
and selectAdded folders: Create groups
then add it. you will see a new directory namedRNInCallmanager under your_project
directory.
-
Setup Objective-C Bridging Header:
- click your
project's xcodeproject root
, go tobuild setting
and searchObjective-C Bridging Header
- set you header location, the default path is:
ReactNativeProjectRoot/ios/
, in this case, you should set../node_modules/react-native-incall-manager/ios/RNInCallManager/RNInCallManager-Bridging-Header.h
- click your
optional sound files on android if you want to use bundled ringtone/ringback/busytone sound instead of system sound
- add files into your_project directory under your project's xcodeproject root. ( or drag into it as described above. )
- check
copy file if needed
- make sure filename correspond to sound type:
incallmanager_busytone.mp3
incallmanager_ringback.mp3
incallmanager_ringtone.mp3
on ios, we only support mp3 files currently.
This module implement a basic handle logic automatically, just:
import InCallManager from 'react-native-incall-manager';
// --- start manager when the chat start based on logics of your app
// On Call Established:
InCallManager.start({media: 'audio'}); // audio/video, default: audio
// ... it will also register and emit events ...
// --- On Call Hangup:
InCallManager.stop();
// ... it will also remote event listeners ...
if you want to use ringback:
// ringback is basically for OUTGOING call. and is part of start().
InCallManager.start({media: 'audio', ringback: '_BUNDLE_'}); // or _DEFAULT_ or _DTMF_
//when callee answered, you MUST stop ringback explicitly:
InCallManager.stopRingback();
if you want to use busytone:
// busytone is basically for OUTGOING call. and is part of stop()
// If the call failed or callee are busing,
// you may want to stop the call and play busytone
InCallManager.stop({busytone: '_DTMF_'}); // or _BUNDLE_ or _DEFAULT_
if you want to use ringtone:
// ringtone is basically for INCOMING call. it's independent to start() and stop()
// if you receiving an incoming call, before user pick up,
// you may want to play ringtone to notify user.
InCallManager.startRingtone('_BUNDLE_'); // or _DEFAULT_ or system filename with extension
// when user pickup
InCallManager.stopRingtone();
InCallManager.start();
// or user hangup
InCallManager.stopRingtone();
InCallManager.stop();
also can interact with events if you want: see API section.
import { DeviceEventEmitter } from 'react-native';
DeviceEventEmitter.addListener('Proximity', function (data) {
// --- do something with events
});
since version 1.2.0, two functions and a property were added:
// --- function
async checkRecordPermission() // return promise
async requestRecordPermission() // return promise
// --- property
recordPermission = 'unknow' or 'granted' or 'denied', default is 'unknow'
After incall-manager initialized, it will check current state of record permission and set to recordPermission
property.
so you can just write below code in your ComponentDidMount
like:
if (InCallManager.recordPermission !== 'granted') {
InCallManager.requestRecordPermission()
.then((requestedRecordPermissionResult) => {
console.log("InCallManager.requestRecordPermission() requestedRecordPermissionResult: ", requestedRecordPermissionResult);
})
.catch((err) => {
console.log("InCallManager.requestRecordPermission() catch: ", err);
});
}
We use android support library v4 to check/request permissions.
You should add compile "com.android.support:support-v4:23.0.1"
in $your_project/android/app/build.gradle
dependencies on android.
NOTE for android:
React Native does not officially support api 23 currently ( it is on api 22 now. see: RN known issues) and android supports request permission at runtime since api 23, so it will always return 'granted' immediately after calling checkRecordPermission()
or requestRecordPermission()
.
If you really need the functionality, you can do the following to make them work but at your own risk:
( I've tested it though, but who knows :) )
Step 1: change your targetSdkVersion
to 23 in $your_project/android/app/build.gradle
Step 2: override onRequestPermissionsResult
in your MainActivity.java
like:
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
InCallManagerPackage.onRequestPermissionsResult(requestCode, permissions, grantResults);
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
then you can test it on android 6 now.
Another thing you should know is:
If you change targetSdkVersion to 23, the red box
which React Native used to display errors in development mode requires permission Draw Over Other Apps
.
So in development mode, you should manually grant permission in app settings
on your device or declare android.permission.SYSTEM_ALERT_WINDOW
in your manifest.
You don't have to do this in release mode since there are no red box.
checkout this awesome project: react-native-android-permissions by @lucasferreira for more information.
on start:
- store current settings, set KeepScreenOn flag = true, and register some event listeners.
- if media type is
audio
, route voice to earpiece, otherwise route to speaker. - audio will enable proximity sensor which is disabled by default if media=video
- when proximity detect user closed to screen, turn off screen to avoid accident touch and route voice to earpiece.
- when newly external device plugged, such as wired-headset, route audio to external device.
- optional play ringback
on stop:
- set KeepScreenOn flag = false, remote event listeners, restore original user settings.
- optional play busytone
you can custom behavior use API/events exposed by this module. see API
section.
note: ios only supports auto
currently.
Methods
Method | android | ios | description |
---|---|---|---|
start({media: ?string, auto: ?boolean, ringback: ?string} ) |
π | π | start incall manager. ringback accept non-empty string or it won't play default: {media:'audio', auto: true, ringback: ''} |
stop({busytone: ?string} ) |
π | π | stop incall manager busytone accept non-empty string or it won't play default: {busytone: ''} |
turnScreenOn() | π | π‘ | force turn screen on |
turnScreenOff() | π | π‘ | force turn screen off |
setKeepScreenOn(enable: ?boolean ) |
π | π | set KeepScreenOn flag = true or false default: false |
setSpeakerphoneOn(enable: ?boolean ) |
π | π‘ | toggle speaker ON/OFF once. but not force default: false |
setForceSpeakerphoneOn(flag: ?boolean ) |
π | π | true -> force speaker on false -> force speaker off null -> use default behavior according to media type default: null |
setMicrophoneMute(enable: ?boolean ) |
π | π‘ | mute/unmute micophone default: false p.s. if you use webrtc, you can just use track.enabled = false to mute |
async checkRecordPermission() | π | π | check record permission without promt. return Promise. see about permission section above |
async requestRecordPermission() | π | π | request record permission to user. return Promise. see about permission section above |
async getAudioUriJS() | π | π | get audio Uri path. this would be useful when you want to pass Uri into another module. |
startRingtone(ringtone: string, ?vibrate_pattern: array, ?ios_category: string, ?seconds: number ) |
π | π | play ringtone. ringtone : 'DEFAULT' or 'BUNDLE'vibrate_pattern : same as RN, but does not support repeatios_category : ios only, if you want to use specific audio categoryseconds : android only, specify how long do you want to play rather than play once nor repeat. in sec. |
stopRingtone() | π | π | stop play ringtone if previous started via startRingtone() |
stopRingback() | π | π | stop play ringback if previous started via start() |
Events
Event | android | ios | description |
---|---|---|---|
'Proximity' | π | π | proximity sensor detected changes. data: {'isNear': boolean} |
'WiredHeadset' | π | π | fire when wired headset plug/unplug data: {'isPlugged': boolean, 'hasMic': boolean, 'deviceName': string } |
'NoisyAudio' | π | π‘ | see andriod doc. data: null |
'MediaButton' | π | π‘ | when external device controler pressed button. see android doc data: {'eventText': string, 'eventCode': number } |
'onAudioFocusChange' | π | π‘ | see andriod doc data: {'eventText': string, 'eventCode': number } |
NOTE: platform OS always has the final decision, so some toggle api may not work in some case be care when customize your own behavior
ISC License ( functionality equivalent to MIT License )
I'm not expert neither on ios nor android, any suggestions, pull request, corrections are really appreciated and welcome.