Skip to content

Commit

Permalink
Merge pull request #41 from SimformSolutionsPvtLtd/develop
Browse files Browse the repository at this point in the history
Release v2.0.0
  • Loading branch information
mukesh-simform authored Jun 10, 2024
2 parents 62e8d0a + ffab2a3 commit 1e657f3
Show file tree
Hide file tree
Showing 17 changed files with 188 additions and 101 deletions.
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -129,12 +129,15 @@ You can check out the full example at [Example](./example/src/App.tsx).
| path\* | - ||| string | Used for `static` type. It is the resource path of an audio source file. |
| candleSpace | 2 ||| number | Space between two candlesticks of waveform |
| candleWidth | 5 ||| number | Width of single candlestick of waveform |
| candleHeightScale | 3 ||| number | Scaling height of candlestick of waveform |
| containerStyle | - ||| `StyleProp<ViewStyle>` | style of the container |
| waveColor | #545454 ||| string | color of candlestick of waveform |
| scrubColor | #7b7b7b ||| string | color of candlestick of waveform which has played |
| onPlayerStateChange | - ||| ( playerState : PlayerState ) => void | callback function, which returns player state whenever player state changes. |
| onPanStateChange | - ||| ( panMoving : boolean ) => void | callback function which returns boolean indicating whether audio seeking is active or not. |
| onRecorderStateChange | - ||| ( recorderState : RecorderState ) => void | callback function which returns the recorder state whenever the recorder state changes. Check RecorderState for more details |
| onCurrentProgressChange | - ||| ( currentProgress : number, songDuration: number ) => void | callback function, which returns current progress of audio and total song duration. |
| onChangeWaveformLoadState | - ||| ( state : boolean ) => void | callback function which returns the loading state of waveform candlestick. |
| onError | - ||| ( error : Error ) => void | callback function which returns the error for static audio waveform |

##### Know more about [ViewStyle](https://reactnative.dev/docs/view-style-props), [PlayerState](#playerstate), and [RecorderState](#recorderstate)
Expand Down Expand Up @@ -318,6 +321,8 @@ To use example app you need to first run below command
cd example && npx react-native-asset
```

> Note: If link-assets-manifest.json file already exists then make sure to delete that before running npx react-native-asset command.
This command will add our example audio sample files to the iOS bundle so that we can access them inside the iOS app.

```sh
Expand Down
11 changes: 6 additions & 5 deletions android/src/main/java/com/audiowaveform/AudioPlayer.kt
Original file line number Diff line number Diff line change
Expand Up @@ -47,27 +47,28 @@ class AudioPlayer(
}
}
if (state == Player.STATE_ENDED) {
val args: MutableMap<String, Any?> = HashMap()
val args: WritableMap = Arguments.createMap()
when (finishMode) {
FinishMode.Loop -> {
player.seekTo(0)
player.play()
args[Constants.finishType] = 0
args.putInt(Constants.finishType, 0)
}
FinishMode.Pause -> {
player.seekTo(0)
player.playWhenReady = false
stopListening()
args[Constants.finishType] = 1
args.putInt(Constants.finishType, 1)
}
else -> {
player.stop()
player.release()
stopListening()
args[Constants.finishType] = 2
args.putInt(Constants.finishType, 2)
}
}
args[Constants.playerKey] = key
args.putString(Constants.playerKey, key)
appContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)?.emit("onDidFinishPlayingAudio", args)
}
}
}
Expand Down
67 changes: 48 additions & 19 deletions android/src/main/java/com/audiowaveform/AudioRecorder.kt
Original file line number Diff line number Diff line change
Expand Up @@ -54,15 +54,34 @@ class AudioRecorder {

fun getDecibel(recorder: MediaRecorder?): Double? {
if (useLegacyNormalization) {
val db = 20 * log10((recorder?.maxAmplitude?.toDouble() ?: (0.0 / 32768.0)))
if (db == Double.NEGATIVE_INFINITY) {
Log.e(Constants.LOG_TAG, "Microphone might be turned off")
} else {
return db
if (recorder != null) {
try {
val db = 20 * log10((recorder?.maxAmplitude?.toDouble() ?: (0.0 / 32768.0)))
if (db == Double.NEGATIVE_INFINITY) {
Log.e(Constants.LOG_TAG, "Microphone might be turned off")
} else {
return db
}
return db;
} catch (e: IllegalStateException) {
e.printStackTrace()
return null
}
}
else {
return null
}
return db;
} else {
return recorder?.maxAmplitude?.toDouble() ?: 0.0
if (recorder != null) {
try {
return recorder?.maxAmplitude?.toDouble() ?: 0.0
} catch (e: IllegalStateException) {
e.printStackTrace()
return null
}
} else {
return null
}
}
}

Expand All @@ -72,21 +91,29 @@ class AudioRecorder {
encoder: Int,
outputFormat: Int,
sampleRate: Int,
bitRate: Int?,
bitRate: Int,
promise: Promise
) {
if (recorder == null) {
promise.reject("RECORDER_NULL", "MediaRecorder instance is null")
return
}

recorder?.apply {
setAudioSource(MediaRecorder.AudioSource.MIC)
setOutputFormat(getOutputFormat(outputFormat))
setAudioEncoder(getEncoder(encoder))
setAudioSamplingRate(sampleRate)
if (bitRate != null) {
setAudioEncodingBitRate(bitRate)
}
setOutputFile(path)
try {
setAudioSource(MediaRecorder.AudioSource.MIC)
setOutputFormat(getOutputFormat(outputFormat))
setAudioEncoder(getEncoder(encoder))
setAudioSamplingRate(sampleRate)
if (bitRate != null) {
setAudioEncodingBitRate(bitRate)
}
setOutputFile(path)
prepare()
promise.resolve(true)
} catch (e: IllegalArgumentException) {
Log.e(Constants.LOG_TAG, "Invalid MediaRecorder configuration", e)
promise.reject("CONFIGURATION_ERROR", "Invalid MediaRecorder configuration: ${e.message}")
} catch (e: IOException) {
Log.e(Constants.LOG_TAG, "Failed to stop initialize recorder")
}
Expand All @@ -103,10 +130,10 @@ class AudioRecorder {
val tempArrayForCommunication : MutableList<String> = mutableListOf()
val duration = getDuration(path)
tempArrayForCommunication.add(path)
tempArrayForCommunication.add(duration)
tempArrayForCommunication.add(duration.toString())
promise.resolve(Arguments.fromList(tempArrayForCommunication))
} catch (e: IllegalStateException) {
Log.e(Constants.LOG_TAG, "Failed to stop recording")
Log.e(Constants.LOG_TAG, "Failed to stop recording",e)
}
}

Expand All @@ -127,7 +154,9 @@ class AudioRecorder {
fun startRecorder(recorder: MediaRecorder?, useLegacy: Boolean, promise: Promise) {
try {
useLegacyNormalization = useLegacy
recorder?.start()
recorder?.apply {
start()
}
promise.resolve(true)
} catch (e: IllegalStateException) {
Log.e(Constants.LOG_TAG, "Failed to start recording")
Expand Down
61 changes: 47 additions & 14 deletions android/src/main/java/com/audiowaveform/AudioWaveformModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ class AudioWaveformModule(context: ReactApplicationContext): ReactContextBaseJav
private var path: String? = null
private var outputFormat: Int = 0
private var sampleRate: Int = 44100
private var bitRate: Int? = null
private var bitRate: Int = 128000
private val handler = Handler(Looper.getMainLooper())

companion object {
Expand All @@ -51,8 +51,8 @@ class AudioWaveformModule(context: ReactApplicationContext): ReactContextBaseJav
}

@ReactMethod
fun initRecorder(promise: Promise) {
checkPathAndInitialiseRecorder(encoder, outputFormat, sampleRate, bitRate, promise)
fun initRecorder(obj: ReadableMap?, promise: Promise) {
checkPathAndInitialiseRecorder(encoder, outputFormat, sampleRate, bitRate, promise, obj)
}

@ReactMethod
Expand All @@ -62,7 +62,7 @@ class AudioWaveformModule(context: ReactApplicationContext): ReactContextBaseJav

@ReactMethod
fun startRecording(obj: ReadableMap?, promise: Promise) {
initRecorder(promise)
initRecorder(obj, promise)
val useLegacyNormalization = true
audioRecorder.startRecorder(recorder, useLegacyNormalization, promise)
startEmittingRecorderValue()
Expand All @@ -84,9 +84,15 @@ class AudioWaveformModule(context: ReactApplicationContext): ReactContextBaseJav

@ReactMethod
fun stopRecording(promise: Promise) {
if (audioRecorder == null || recorder == null || path == null) {
promise.reject("STOP_RECORDING_ERROR", "Recording resources not properly initialized")
return
}

audioRecorder.stopRecording(recorder, path!!, promise)
stopEmittingRecorderValue()
recorder = null
path = null
}

@ReactMethod
Expand Down Expand Up @@ -231,10 +237,12 @@ class AudioWaveformModule(context: ReactApplicationContext): ReactContextBaseJav
key = playerKey,
extractorCallBack = object : ExtractorCallBack {
override fun onProgress(value: Float) {
if (value == 1.0F) {
val tempArrayForCommunication : MutableList<MutableList<Float>> = mutableListOf()
extractors[playerKey]?.sampleData?.let { tempArrayForCommunication.add(it) }
promise.resolve(Arguments.fromList(tempArrayForCommunication))
if (value == 1.0F) {
extractors[playerKey]?.sampleData?.let { data ->
val normalizedData = normalizeWaveformData(data, 0.12f)
val tempArrayForCommunication: MutableList<MutableList<Float>> = mutableListOf(normalizedData)
promise.resolve(Arguments.fromList(tempArrayForCommunication))
}
}
}
},
Expand All @@ -244,6 +252,16 @@ class AudioWaveformModule(context: ReactApplicationContext): ReactContextBaseJav
extractors[playerKey]?.stop()
}

private fun normalizeWaveformData(data: MutableList<Float>, scale: Float = 0.25f, threshold: Float = 0.01f): MutableList<Float> {
val filteredData = data.filter { kotlin.math.abs(it) >= threshold }
val maxAmplitude = filteredData.maxOrNull() ?: 1.0f
return if (maxAmplitude > 0) {
data.map { if (kotlin.math.abs(it) < threshold) 0.0f else (it / maxAmplitude) * scale }.toMutableList()
} else {
data
}
}

private fun getUpdateFrequency(frequency: Int?): UpdateFrequency {
if (frequency == 2) {
return UpdateFrequency.High
Expand All @@ -257,9 +275,24 @@ class AudioWaveformModule(context: ReactApplicationContext): ReactContextBaseJav
encoder: Int,
outputFormat: Int,
sampleRate: Int,
bitRate: Int?,
promise: Promise
bitRate: Int,
promise: Promise,
obj: ReadableMap?
) {

var sampleRateVal = sampleRate.toInt();
var bitRateVal = bitRate.toInt();

if(obj != null) {
if(obj.hasKey(Constants.bitRate)){
bitRateVal = obj.getInt(Constants.bitRate);
}

if(obj.hasKey(Constants.sampleRate)){
sampleRateVal = obj.getInt(Constants.sampleRate);
}
}

try {
recorder = MediaRecorder()
} catch (e: Exception) {
Expand All @@ -278,8 +311,8 @@ class AudioWaveformModule(context: ReactApplicationContext): ReactContextBaseJav
recorder,
encoder,
outputFormat,
sampleRate,
bitRate,
sampleRateVal,
bitRateVal,
promise,
)
} catch (e: IOException) {
Expand All @@ -291,8 +324,8 @@ class AudioWaveformModule(context: ReactApplicationContext): ReactContextBaseJav
recorder,
encoder,
outputFormat,
sampleRate,
bitRate,
sampleRateVal,
bitRateVal,
promise,
)
}
Expand Down
2 changes: 2 additions & 0 deletions android/src/main/java/com/audiowaveform/Utils.kt
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ object Constants {
const val waveformData = "waveformData"
const val updateFrequency = "updateFrequency"
const val currentDecibel = "currentDecibel"
const val bitRate = "bitRate"
const val sampleRate = "sampleRate"
}

enum class FinishMode(val value:Int) {
Expand Down
2 changes: 2 additions & 0 deletions example/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ DerivedData
*.ipa
*.xcuserstate
ios/.xcode.env.local
ios/link-assets-manifest.json

# Android/IntelliJ
#
Expand All @@ -31,6 +32,7 @@ local.properties
*.iml
*.hprof
.cxx/
android/link-assets-manifest.json

# node.js
#
Expand Down
25 changes: 0 additions & 25 deletions example/android/link-assets-manifest.json

This file was deleted.

25 changes: 0 additions & 25 deletions example/ios/link-assets-manifest.json

This file was deleted.

Loading

0 comments on commit 1e657f3

Please sign in to comment.