Skip to content
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
### 🐞 Fixed

### ⬆️ Improved
- Use `ExoPlayer` instead of `MediaPlayer` for audio message playback. [#5980](https://github.com/GetStream/stream-chat-android/pull/5980)

### ✅ Added

Expand Down
1 change: 1 addition & 0 deletions stream-chat-android-client/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ dependencies {
implementation(libs.kotlin.reflect)
implementation(libs.androidx.core.ktx)
implementation(libs.androidx.lifecycle.process)
implementation(libs.androidx.media3.exoplayer)
implementation(libs.androidx.work)
implementation(libs.kotlinx.coroutines.core)
implementation(libs.kotlinx.coroutines.android)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,15 @@
package io.getstream.chat.android.client

import android.content.Context
import android.media.AudioAttributes
import android.media.MediaPlayer
import android.os.Build
import android.util.Log
import androidx.annotation.CheckResult
import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.ProcessLifecycleOwner
import androidx.media3.common.AudioAttributes
import androidx.media3.common.C.AUDIO_CONTENT_TYPE_MUSIC
import androidx.media3.exoplayer.ExoPlayer
import io.getstream.chat.android.client.ChatClient.Companion.MAX_COOLDOWN_TIME_SECONDS
import io.getstream.chat.android.client.api.ChatApi
import io.getstream.chat.android.client.api.ChatClientConfig
Expand Down Expand Up @@ -69,7 +69,7 @@ import io.getstream.chat.android.client.api2.model.dto.DownstreamUserDto
import io.getstream.chat.android.client.attachment.AttachmentsSender
import io.getstream.chat.android.client.audio.AudioPlayer
import io.getstream.chat.android.client.audio.NativeMediaPlayerImpl
import io.getstream.chat.android.client.audio.StreamMediaPlayer
import io.getstream.chat.android.client.audio.StreamAudioPlayer
import io.getstream.chat.android.client.channel.ChannelClient
import io.getstream.chat.android.client.channel.state.ChannelStateLogicProvider
import io.getstream.chat.android.client.clientstate.DisconnectCause
Expand Down Expand Up @@ -984,7 +984,7 @@ internal constructor(
* @param file The image file that needs to be uploaded.
* @param callback The callback to track progress.
*
* @return Executable async [Call] which completes with [Result] containing an instance of [UploadedImage]
* @return Executable async [Call] which completes with [Result] containing an instance of [UploadedFile]
* if the image was successfully uploaded.
*
* @see FileUploader
Expand Down Expand Up @@ -1108,7 +1108,6 @@ internal constructor(
* @see FileUploader
*/
@CheckResult
@JvmOverloads
public fun deleteImage(
url: String,
): Call<Unit> = api.deleteImage(url)
Expand Down Expand Up @@ -4602,8 +4601,8 @@ internal constructor(
}

/**
* Debug requests using [ApiRequestsAnalyser]. Use this to debug your requests. This shouldn't be enabled in
* release builds as it uses a memory cache.
* Debug requests using [io.getstream.chat.android.client.plugins.requests.ApiRequestsAnalyser]. Use this to
* debug your requests. This shouldn't be enabled in release builds as it uses a memory cache.
*/
public fun debugRequests(shouldDebug: Boolean): Builder = apply {
this.debugRequests = shouldDebug
Expand Down Expand Up @@ -4719,17 +4718,18 @@ internal constructor(

val appSettingsManager = AppSettingManager(module.api())

val audioPlayer: AudioPlayer = StreamMediaPlayer(
mediaPlayer = NativeMediaPlayerImpl {
MediaPlayer().apply {
AudioAttributes.Builder()
.setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
.build()
.let(this::setAudioAttributes)
}
val audioPlayer: AudioPlayer = StreamAudioPlayer(
mediaPlayer = NativeMediaPlayerImpl(appContext) {
ExoPlayer.Builder(appContext)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm a bit worried about the size bump of the SDK. Some companies pay attention to that.

Can we do it in a way to check if the dependency exists at runtime and decide whether to use ExoPlayer based on that?

I'm thinking, maybe we can add ExoPlayer as compileOnly instead of implementation and then check if the class exists like:

private boolean isExoPlayerAvailable() {
    try {
        Class.forName("androidx.media3.exoplayer.ExoPlayer");
        return true;
    } catch (ClassNotFoundException e) {
        return false;
    }
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

and if it does not? Fallback to MediaPlayer?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it adds unnecessary complexity to already complex chat. I think we need to live with the 1.9MBs.

@VelikovPetar why has offline increased for 2MB?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The offline module includes the client as a dependency, so its size has increased transitively.
The ui modules are not affected, because they already include the exoplayer dependency (for video playback).

Another (a bit more complex) solution would be to move the AudioPlayer up to the ui modules, where we already have the exoplayer library. Currently the ChatClient only accesses the AudioPlayer after a logout for cleanup, but with some effort we can ensure proper clean-up in the components where the AudioPlayer is used. The AudioPlayer API is already internal (with the exception of some now unused constants) so there won't be any breaking changes.
But such approach of course would introduce bigger complexity in the wiring of the player, because now can just access it as chatClient.audioPlayer, if we move it upwards, we would need to handle its instantiation better.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The correct way to avoid the 2MB increase is I think to have the contract live in chat and default MediaPlayer implementation.
Then as a separate module include the ExoPlayer backed implementation of said contract. You can then supply an external StreamMediaPlayer implementation of whichever player you choose.

However this goes against our goal of reducing integration complexity and dependencies. I think including exo player is not an optional dependency when MediaPlayer is buggy. We've had several issues with this already. And in the end we are just going to tell people to include the dependency anyway and they will still suffer the 2MB size increase.

Copy link

@PratimMallick PratimMallick Nov 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hey @VelikovPetar - Maybe try this

Instead of includign the entire exoplayer dep implementation(libs.androidx.media3.exoplayer)

We can include its modules that we need as of now

media3-exoplayer-core -> this one is always required

Apart from this, we only need the type of streaming we are supporting :

The other modules are :

media3-exoplayer-dash -> For DASH (MPEG-DASH) streaming
media3-exoplayer-hls -> For HLS streaming
media3-exoplayer-smoothstreaming -> For SmoothStreaming
media3-exoplayer-rtsp -> For RTSP playback
media3-exoplayer-transformer -> For media editing/transcoding
media3-ui -> For default UI components (optional)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We checked this, but unfortunately the legacy exoplayer-core has now become the new media3-exoplayer(and there is no media3-exoplayer-core), so we are already using the minimal required dependencies for the exoplayer.

.setAudioAttributes(
AudioAttributes.Builder()
.setContentType(AUDIO_CONTENT_TYPE_MUSIC)
.build(),
true,
)
.build()
},
userScope = userScope,
isMarshmallowOrHigher = Build.VERSION.SDK_INT >= Build.VERSION_CODES.M,
)

return ChatClient(
Expand Down Expand Up @@ -4878,7 +4878,6 @@ internal constructor(
*/
@Throws(IllegalStateException::class)
@JvmStatic
@JvmOverloads
public fun handlePushMessage(pushMessage: PushMessage) {
ensureClientInitialized().run {
val type = pushMessage.type.orEmpty()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ public interface AudioPlayer {
* Plays an audio track with sourceUrl.
*
* @param sourceUrl the URL of the audio track
* @param hash the identifier of the audio track
* @param audioHash the identifier of the audio track
*/
public fun play(sourceUrl: String, audioHash: Int)

Expand Down
Loading
Loading