Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[i227] fix crash in audio player after relogin #5120

Merged
merged 3 commits into from
Dec 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading