Skip to content

Commit 2c3282e

Browse files
Merge pull request #157 from SimformSolutionsPvtLtd/develop
Release v2.1.3
2 parents 3052c87 + 8476e83 commit 2c3282e

File tree

6 files changed

+128
-15
lines changed

6 files changed

+128
-15
lines changed

android/build.gradle

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,4 +91,7 @@ dependencies {
9191
// Android Audio Media3 Dependency
9292
implementation 'androidx.media3:media3-exoplayer:1.3.1'
9393
implementation 'androidx.media3:media3-common:1.3.1'
94+
95+
implementation "androidx.media3:media3-session:1.3.1"
96+
implementation 'androidx.media3:media3-ui:1.3.1'
9497
}

android/src/main/java/com/audiowaveform/AudioPlayer.kt

Lines changed: 99 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
package com.audiowaveform
22

3+
import android.content.Context
4+
import android.media.AudioAttributes
5+
import android.media.AudioFocusRequest
6+
import android.media.AudioManager
37
import android.net.Uri
8+
import android.os.Build
49
import android.os.CountDownTimer
510
import com.facebook.react.bridge.Arguments
611
import com.facebook.react.bridge.Promise
@@ -18,13 +23,93 @@ class AudioPlayer(
1823
) {
1924
private val appContext = context
2025
private lateinit var player: ExoPlayer
26+
private var audioManager: AudioManager = appContext.getSystemService(Context.AUDIO_SERVICE) as AudioManager
27+
private var audioFocusRequest: AudioFocusRequest? = null
2128
private var playerListener: Player.Listener? = null
2229
private var isPlayerPrepared: Boolean = false
2330
private var finishMode = FinishMode.Stop
2431
private val key = playerKey
2532
private var updateFrequency = UpdateFrequency.Low
2633
private lateinit var audioPlaybackListener: CountDownTimer
2734
private var isComponentMounted = true // Flag to track mounting status
35+
private var isAudioFocusGranted=false
36+
37+
init {
38+
// Set up the audio focus request
39+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
40+
audioFocusRequest = AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN)
41+
.setAudioAttributes(
42+
AudioAttributes.Builder()
43+
.setUsage(AudioAttributes.USAGE_MEDIA)
44+
.setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
45+
.build()
46+
).setAcceptsDelayedFocusGain(true)
47+
.setOnAudioFocusChangeListener { focusChange ->
48+
handleAudioFocusChange(focusChange)
49+
}
50+
.build()
51+
}
52+
}
53+
54+
private fun handleAudioFocusChange(focusChange: Int) {
55+
when (focusChange) {
56+
AudioManager.AUDIOFOCUS_GAIN -> {
57+
// Audio focus granted; resume playback if necessary
58+
if (!player.isPlaying) {
59+
player.play()
60+
}
61+
player.volume = 1.0f // Restore full volume
62+
}
63+
AudioManager.AUDIOFOCUS_LOSS -> {
64+
// Permanent loss of audio focus; pause playback
65+
if (player.isPlaying) {
66+
val args: WritableMap = Arguments.createMap()
67+
stopListening()
68+
player.pause()
69+
abandonAudioFocus()
70+
args.putInt(Constants.finishType, 1)
71+
args.putString(Constants.playerKey, key)
72+
appContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)?.emit("onDidFinishPlayingAudio", args)
73+
}
74+
}
75+
AudioManager.AUDIOFOCUS_LOSS_TRANSIENT -> {
76+
// Temporary loss of audio focus; pause playback
77+
if (player.isPlaying) {
78+
player.pause()
79+
}
80+
}
81+
AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK -> {
82+
// Temporarily loss of audio focus; but can continue playing at a lower volume.
83+
player.volume = 0.2f
84+
}
85+
}
86+
}
87+
88+
private fun requestAudioFocus(): Boolean {
89+
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
90+
audioFocusRequest?.let {
91+
val result = audioManager.requestAudioFocus(it)
92+
isAudioFocusGranted = result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED
93+
return isAudioFocusGranted
94+
} ?: false
95+
} else {
96+
val result = audioManager.requestAudioFocus(
97+
{ focusChange -> handleAudioFocusChange(focusChange) },
98+
AudioManager.STREAM_MUSIC,
99+
AudioManager.AUDIOFOCUS_GAIN
100+
)
101+
isAudioFocusGranted = result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED
102+
return isAudioFocusGranted
103+
}
104+
}
105+
106+
private fun abandonAudioFocus() {
107+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
108+
audioManager.abandonAudioFocusRequest(audioFocusRequest!!)
109+
}
110+
isAudioFocusGranted = false
111+
}
112+
28113

29114
fun markPlayerAsUnmounted() {
30115
isComponentMounted = false
@@ -134,12 +219,15 @@ class AudioPlayer(
134219
this.finishMode = FinishMode.Stop
135220
}
136221

137-
validateAndSetPlaybackSpeed(player, speed)
138-
139-
player.playWhenReady = true
140-
player.play()
141-
promise.resolve(true)
142-
startListening(promise)
222+
validateAndSetPlaybackSpeed(player, speed)
223+
if (requestAudioFocus()) {
224+
player.playWhenReady = true
225+
player.play()
226+
promise.resolve(true)
227+
startListening(promise)}
228+
else {
229+
promise.reject("AudioFocusError", "Failed to gain audio focus")
230+
}
143231
} catch (e: Exception) {
144232
promise.reject("Can not start the player", e.toString())
145233
}
@@ -153,15 +241,17 @@ class AudioPlayer(
153241
isPlayerPrepared = false
154242
player.stop()
155243
player.release()
244+
abandonAudioFocus()
156245
}
157246

158-
fun pause(promise: Promise) {
247+
fun pause(promise: Promise?) {
159248
try {
160249
stopListening()
161250
player.pause()
162-
promise.resolve(true)
251+
abandonAudioFocus()
252+
promise?.resolve(true)
163253
} catch (e: Exception) {
164-
promise.reject("Failed to pause the player", e.toString())
254+
promise?.reject("Failed to pause the player", e.toString())
165255
}
166256

167257
}

android/src/main/java/com/audiowaveform/AudioRecorder.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -178,9 +178,10 @@ class AudioRecorder {
178178
fun pauseRecording(recorder: MediaRecorder?, promise: Promise) {
179179
try {
180180
recorder?.pause()
181-
promise.resolve(false)
181+
promise.resolve(true)
182182
} catch (e: IllegalStateException) {
183183
Log.e(Constants.LOG_TAG, "Failed to pause recording")
184+
promise.resolve(false)
184185
}
185186
}
186187

ios/AudioRecorder.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,8 @@ public class AudioRecorder: NSObject, AVAudioRecorderDelegate{
4545
AVEncoderAudioQualityKey: AVAudioQuality.high.rawValue
4646
]
4747

48-
let options: AVAudioSession.CategoryOptions = [.defaultToSpeaker, .allowBluetooth]
49-
48+
let options: AVAudioSession.CategoryOptions = [.defaultToSpeaker, .allowBluetooth, .mixWithOthers]
49+
5050
if (path == nil) {
5151
guard let newPath = self.createAudioRecordPath(fileNameFormat: fileNameFormat) else {
5252
reject(Constants.audioWaveforms, "Failed to initialise file URL", nil)

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@simform_solutions/react-native-audio-waveform",
3-
"version": "2.1.2",
3+
"version": "2.1.3",
44
"description": "A React Native component to show audio waveform with ease in react native application",
55
"main": "lib/index",
66
"types": "lib/index.d.ts",

src/components/Waveform/Waveform.tsx

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ export const Waveform = forwardRef<IWaveformRef, IWaveform>((props, ref) => {
6868
const scrollRef = useRef<ScrollView>(null);
6969
const isLayoutCalculated = useRef<boolean>(false);
7070
const isAutoPaused = useRef<boolean>(false);
71+
const isAudioPlaying = useRef<boolean>(false);
7172
const [waveform, setWaveform] = useState<number[]>([]);
7273
const [viewLayout, setViewLayout] = useState<LayoutRectangle | null>(null);
7374
const [seekPosition, setSeekPosition] = useState<NativeTouchEvent | null>(
@@ -215,6 +216,7 @@ export const Waveform = forwardRef<IWaveformRef, IWaveform>((props, ref) => {
215216
const result = await stopPlayer({
216217
playerKey: `PlayerFor${path}`,
217218
});
219+
isAudioPlaying.current = false;
218220
if (!isNil(result) && result) {
219221
if (resetProgress) {
220222
setCurrentProgress(0);
@@ -240,6 +242,7 @@ export const Waveform = forwardRef<IWaveformRef, IWaveform>((props, ref) => {
240242
const startPlayerAction = async (args?: IStartPlayerRef) => {
241243
if (mode === 'static') {
242244
try {
245+
isAudioPlaying.current = true;
243246
if (playerState === PlayerState.stopped) {
244247
await preparePlayerForPath(currentProgress);
245248
}
@@ -278,6 +281,7 @@ export const Waveform = forwardRef<IWaveformRef, IWaveform>((props, ref) => {
278281
const pausePlayerAction = async (changePlayerState = true) => {
279282
if (mode === 'static') {
280283
try {
284+
isAudioPlaying.current = false;
281285
const pause = await pausePlayer({
282286
playerKey: `PlayerFor${path}`,
283287
});
@@ -474,8 +478,9 @@ export const Waveform = forwardRef<IWaveformRef, IWaveform>((props, ref) => {
474478
const tracePlayerState = onDidFinishPlayingAudio(async data => {
475479
if (data.playerKey === `PlayerFor${path}`) {
476480
if (data.finishType === FinishMode.stop) {
477-
setPlayerState(PlayerState.stopped);
478-
setCurrentProgress(0);
481+
stopPlayerAction();
482+
} else if (data.finishType === FinishMode.pause) {
483+
setPlayerState(PlayerState.paused);
479484
}
480485
}
481486
});
@@ -600,6 +605,20 @@ export const Waveform = forwardRef<IWaveformRef, IWaveform>((props, ref) => {
600605
}
601606
}, [currentProgress, songDuration, onCurrentProgressChange]);
602607

608+
/* Ensure that the audio player is released (or stopped) once the song's duration is determined,
609+
especially if the audio is not playing immediately after loading */
610+
useEffect(() => {
611+
if (
612+
songDuration !== 0 &&
613+
mode === 'static' &&
614+
isAudioPlaying.current !== true
615+
) {
616+
isAudioPlaying.current = false;
617+
stopPlayerAction(false);
618+
}
619+
// eslint-disable-next-line react-hooks/exhaustive-deps
620+
}, [songDuration]);
621+
603622
useImperativeHandle(ref, () => ({
604623
startPlayer: startPlayerAction,
605624
stopPlayer: stopPlayerAction,

0 commit comments

Comments
 (0)