Skip to content

Commit

Permalink
[i227] fix crash in audio player after relogin (#5120)
Browse files Browse the repository at this point in the history
* [i227] fix crash in audio player after relogin

* [i227] add CHANGELOG
  • Loading branch information
kanat authored Dec 20, 2023
1 parent 93b9186 commit 7511e17
Show file tree
Hide file tree
Showing 9 changed files with 222 additions and 133 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

## stream-chat-android-client
### 🐞 Fixed
- Fixed crash in `StreamMediaPlayer` when playing audio after re-login. [#5120](https://github.com/GetStream/stream-chat-android/pull/5120)

### ⬆️ Improved

Expand Down
28 changes: 0 additions & 28 deletions stream-chat-android-client/api/stream-chat-android-client.api
Original file line number Diff line number Diff line change
Expand Up @@ -508,34 +508,6 @@ public final class io/getstream/chat/android/client/api/models/WatchChannelReque
public fun withWatchers (II)Lio/getstream/chat/android/client/api/models/WatchChannelRequest;
}

public abstract interface class io/getstream/chat/android/client/audio/NativeMediaPlayer {
public static final field Companion Lio/getstream/chat/android/client/audio/NativeMediaPlayer$Companion;
public static final field MEDIA_ERROR_IO I
public static final field MEDIA_ERROR_MALFORMED I
public static final field MEDIA_ERROR_NOT_VALID_FOR_PROGRESSIVE_PLAYBACK I
public static final field MEDIA_ERROR_SERVER_DIED I
public static final field MEDIA_ERROR_SYSTEM I
public static final field MEDIA_ERROR_TIMED_OUT I
public static final field MEDIA_ERROR_UNKNOWN I
public static final field MEDIA_ERROR_UNSUPPORTED I
public abstract fun getCurrentPosition ()I
public abstract fun getDuration ()I
public abstract fun getSpeed ()F
public abstract fun pause ()V
public abstract fun prepare ()V
public abstract fun prepareAsync ()V
public abstract fun release ()V
public abstract fun reset ()V
public abstract fun seekTo (I)V
public abstract fun setDataSource (Ljava/lang/String;)V
public abstract fun setOnCompletionListener (Lkotlin/jvm/functions/Function0;)V
public abstract fun setOnErrorListener (Lkotlin/jvm/functions/Function3;)V
public abstract fun setOnPreparedListener (Lkotlin/jvm/functions/Function0;)V
public abstract fun setSpeed (F)V
public abstract fun start ()V
public abstract fun stop ()V
}

public final class io/getstream/chat/android/client/audio/NativeMediaPlayer$Companion {
public static final field MEDIA_ERROR_IO I
public static final field MEDIA_ERROR_MALFORMED I
Expand Down
1 change: 0 additions & 1 deletion stream-chat-android-client/detekt-baseline.xml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@
<ID>MaxLineLength:BaseChatModule.kt$BaseChatModule$NetworkStateProvider(userScope, appContext.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager)</ID>
<ID>MaxLineLength:Message.kt$*</ID>
<ID>MaxLineLength:Reaction.kt$*</ID>
<ID>MaxLineLength:StreamAudioPlayer.kt$StreamMediaPlayer$logger.v { "[pollProgress] #3; finalPosition: $finalPosition($currentPosition), prevPosition: $prevPosition" }</ID>
<ID>MaxLineLength:StringExtensionsKtTest.kt$StringExtensionsKtTest.Companion$"https://us-east.stream-io-cdn.com/1/images/IMAGE_NAME.jpg?Key-Pair-Id=SODHGWNRLG&amp;Policy=akIjUneI9Kmbds2&amp;Signature=dsnIjJ8-gfdgihih8-GkhdfgfdGFG32--KHJDFj349sfsdf~SFDf2~Fsdfgrg3~kjnooi23Jig-Kjoih34iW~k7Jbe2~Jnk33j-Fsiniiz2~Sfj23iJihn-Jinfnsiw2kS"</ID>
<ID>MaxLineLength:StringExtensionsKtTest.kt$StringExtensionsKtTest.Companion$"https://us-east.stream-io-cdn.com/1/images/IMAGE_NAME.jpg?Key-Pair-Id=SODHGWNRLG&amp;Policy=akIjUneI9Kmbds2&amp;Signature=dsnIjJ8-gfdgihih8-GkhdfgfdGFG32--KHJDFj349sfsdf~SFDf2~Fsdfgrg3~kjnooi23Jig-Kjoih34iW~k7Jbe2~Jnk33j-Fsiniiz2~Sfj23iJihn-Jinfnsiw2kS&amp;oh=$originalHeight&amp;ow=$originalWidth"</ID>
<ID>MaxLineLength:WaveformExtractor.kt$WaveformExtractor$logger.v { "[handle16bit] this.totalSamples: $totalSamples, sampleRate: $sampleRate, duration: $durationInSeconds" }</ID>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3212,14 +3212,14 @@ internal constructor(
val appSettingsManager = AppSettingManager(module.api())

val audioPlayer: AudioPlayer = StreamMediaPlayer(
mediaPlayer = NativeMediaPlayerImpl(
mediaPlayer = NativeMediaPlayerImpl {
MediaPlayer().apply {
AudioAttributes.Builder()
.setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
.build()
.let(this::setAudioAttributes)
},
),
}
},
userScope = userScope,
isMarshmallowOrHigher = { Build.VERSION.SDK_INT >= Build.VERSION_CODES.M },
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,11 @@ package io.getstream.chat.android.client.audio
import android.media.MediaPlayer
import android.os.Build
import androidx.annotation.RequiresApi
import io.getstream.chat.android.core.internal.InternalStreamChatApi
import io.getstream.log.taggedLogger
import java.io.IOException

@InternalStreamChatApi
public interface NativeMediaPlayer {

public companion object {
Expand Down Expand Up @@ -77,6 +80,13 @@ public interface NativeMediaPlayer {
@set:Throws(IllegalStateException::class, IllegalArgumentException::class)
public var speed: Float

/**
* Gets the current player state.
*
* @return the current position in milliseconds
*/
public val state: NativeMediaPlayerState

/**
* Gets the current playback position.
*
Expand Down Expand Up @@ -202,7 +212,7 @@ public interface NativeMediaPlayer {
* Returning false, or not having an OnErrorListener at all, will
* cause the OnCompletionListener to be called.
*/
public fun setOnErrorListener(listener: (mp: NativeMediaPlayer, what: Int, extra: Int) -> Boolean)
public fun setOnErrorListener(listener: (what: Int, extra: Int) -> Boolean)

/**
* Register a callback to be invoked when the media source is ready
Expand All @@ -213,10 +223,53 @@ public interface NativeMediaPlayer {
public fun setOnPreparedListener(listener: () -> Unit)
}

@InternalStreamChatApi
public enum class NativeMediaPlayerState {
IDLE,
INITIALIZED,
PREPARING,
PREPARED,
STARTED,
PAUSED,
STOPPED,
PLAYBACK_COMPLETED,
END,
ERROR,
}

internal class NativeMediaPlayerImpl(
private val mediaPlayer: MediaPlayer,
private val builder: () -> MediaPlayer,
) : NativeMediaPlayer {

companion object {
private const val DEBUG = false
}

private val logger by taggedLogger("Chat:NativeMediaPlayer")

private var _mediaPlayer: MediaPlayer? = null
set(value) {
if (DEBUG) logger.i { "[setMediaPlayerInstance] instance: $value" }
field = value
}

private val mediaPlayer: MediaPlayer get() {
return _mediaPlayer ?: builder().also {
_mediaPlayer = it.setupListeners()
state = NativeMediaPlayerState.IDLE
}
}

private var onCompletionListener: (() -> Unit)? = null
private var onErrorListener: ((what: Int, extra: Int) -> Boolean)? = null
private var onPreparedListener: (() -> Unit)? = null

override var state: NativeMediaPlayerState = NativeMediaPlayerState.END
set(value) {
if (DEBUG) logger.d { "[setMediaPlayerState] state: $value <= $field" }
field = value
}

override var speed: Float
@RequiresApi(Build.VERSION_CODES.M)
@Throws(IllegalStateException::class)
Expand All @@ -225,6 +278,7 @@ internal class NativeMediaPlayerImpl(
@RequiresApi(Build.VERSION_CODES.M)
@Throws(IllegalStateException::class, IllegalArgumentException::class)
set(value) {
if (DEBUG) logger.d { "[setSpeed] speed: $value" }
mediaPlayer.playbackParams = mediaPlayer.playbackParams.setSpeed(value)
}
override val currentPosition: Int
Expand All @@ -239,45 +293,98 @@ internal class NativeMediaPlayerImpl(
SecurityException::class,
IllegalStateException::class,
)
override fun setDataSource(path: String) = mediaPlayer.setDataSource(path)
override fun setDataSource(path: String) {
if (DEBUG) logger.d { "[setDataSource] path: $path" }
mediaPlayer.setDataSource(path)
state = NativeMediaPlayerState.INITIALIZED
}

@Throws(IllegalStateException::class)
override fun prepareAsync() = mediaPlayer.prepareAsync()
override fun prepareAsync() {
if (DEBUG) logger.d { "[prepareAsync] no args" }
mediaPlayer.prepareAsync()
state = NativeMediaPlayerState.PREPARING
}

@Throws(IOException::class, IllegalStateException::class)
override fun prepare() = mediaPlayer.prepare()
override fun prepare() {
if (DEBUG) logger.d { "[prepare] no args" }
mediaPlayer.prepare()
state = NativeMediaPlayerState.PREPARED
}

@Throws(IllegalStateException::class)
override fun seekTo(msec: Int) = mediaPlayer.seekTo(msec)
override fun seekTo(msec: Int) {
if (DEBUG) logger.d { "[seekTo] msec: $msec" }
mediaPlayer.seekTo(msec)
}

@Throws(IllegalStateException::class)
override fun start() = mediaPlayer.start()
override fun start() {
if (DEBUG) logger.d { "[start] no args" }
mediaPlayer.start()
state = NativeMediaPlayerState.STARTED
}

@Throws(IllegalStateException::class)
override fun pause() = mediaPlayer.pause()
override fun pause() {
if (DEBUG) logger.d { "[pause] no args" }
mediaPlayer.pause()
state = NativeMediaPlayerState.PAUSED
}

@Throws(IllegalStateException::class)
override fun stop() = mediaPlayer.stop()
override fun stop() {
if (DEBUG) logger.d { "[stop] no args" }
mediaPlayer.stop()
state = NativeMediaPlayerState.STOPPED
}

override fun reset() = mediaPlayer.reset()
override fun reset() {
if (DEBUG) logger.d { "[reset] no args" }
mediaPlayer.reset()
state = NativeMediaPlayerState.IDLE
}

override fun release() = mediaPlayer.release()
override fun release() {
if (DEBUG) logger.d { "[release] no args" }
mediaPlayer.release()
state = NativeMediaPlayerState.END
_mediaPlayer = null
}

override fun setOnPreparedListener(listener: () -> Unit) {
mediaPlayer.setOnPreparedListener {
listener()
}
if (DEBUG) logger.d { "[setOnPreparedListener] listener: $listener" }
this.onPreparedListener = listener
}

override fun setOnCompletionListener(listener: () -> Unit) {
mediaPlayer.setOnCompletionListener {
listener()
}
if (DEBUG) logger.d { "[setOnCompletionListener] listener: $listener" }
this.onCompletionListener = listener
}

override fun setOnErrorListener(listener: (mp: NativeMediaPlayer, what: Int, extra: Int) -> Boolean) {
mediaPlayer.setOnErrorListener { mp, what, extra ->
listener(NativeMediaPlayerImpl(mp), what, extra)
override fun setOnErrorListener(listener: (what: Int, extra: Int) -> Boolean) {
if (DEBUG) logger.d { "[setOnErrorListener] listener: $listener" }
this.onErrorListener = listener
}

private fun MediaPlayer.setupListeners(): MediaPlayer {
setOnErrorListener { _, what, extra ->
if (DEBUG) logger.e { "[onError] what: $what, extra: $extra" }
state = NativeMediaPlayerState.ERROR
_mediaPlayer = null
onErrorListener?.invoke(what, extra) ?: false
}
setOnPreparedListener {
if (DEBUG) logger.d { "[onPrepared] no args" }
state = NativeMediaPlayerState.PREPARED
onPreparedListener?.invoke()
}
setOnCompletionListener {
if (DEBUG) logger.d { "[onCompletion] no args" }
state = NativeMediaPlayerState.PLAYBACK_COMPLETED
onCompletionListener?.invoke()
}
return this
}
}
Loading

0 comments on commit 7511e17

Please sign in to comment.