Skip to content

Commit a7e71c1

Browse files
VelikovPetarclaudegithub-actions[bot]andremiongpunto
authored
Merge develop to v7 (30.03.2026) (#6309)
* Improve `Message.createdLocallyAt` creation logic using estimated server time (#6199) * Fix createdLocallyAt using NTP-style server clock offset estimation Co-Authored-By: Claude <noreply@anthropic.com> * Pr remarks * Adjust thread message createdLocallyAt. * Ensure exceedsSyncThreshold is compared against estimated server time (where applicable). * Add max allowed offset. --------- Co-authored-by: Claude <noreply@anthropic.com> * [skip ci] Update SDK sizes * Update README cover image (#6282) * Fix XML image flicker caused by `interceptorCoroutineContext(Dispatchers.IO)` (#6284) Co-authored-by: Claude <noreply@anthropic.com> * [skip ci] Update SDK sizes * AUTOMATION: Version Bump * Fix race condition in plugin resolution during disconnect (#6269) * Update `DependencyResolverTest` to verify error handling when dependency resolution races with disconnection. * Prevent race conditions during disconnects in `ChatClient`. * Handle unresolvable attachments in picker (#6285) - Update `StorageHelper` and `AttachmentMetaDataMapper` to safely handle cases where content URIs (e.g. cloud-backed files) cannot be opened. - Introduce `hasUnresolvedAttachments` state in `AttachmentsPickerViewModel` to track failed attachment resolutions. - Show a toast message in both View-based and Compose attachment pickers when files are unavailable and need to be downloaded to the device. - Add `clearUnresolvedAttachments` to reset the error state after it has been consumed by the UI. - Add unit tests for unresolved attachment scenarios in `AttachmentsPickerViewModelTest`. * [skip ci] Update SDK sizes * Fix wrong message selected on quoted message long click (#6292) * Use type-specific attachment URL fields and deprecate `imagePreviewUrl` (#6280) * Deprecate imagePreviewUrl and use type-specific attachment URL fields Co-Authored-By: Claude <noreply@anthropic.com> * Extract common extensions. --------- Co-authored-by: Claude <noreply@anthropic.com> * Expose optional completion callback for audio recording (#6290) Co-authored-by: Claude <noreply@anthropic.com> * AUTOMATION: Version Bump * AUTOMATION: Clean Detekt Baseline Files (#6299) Co-authored-by: adasiewiczr <17440581+adasiewiczr@users.noreply.github.com> * Add support for intercepting CDN file requests (#6295) * Add new CDN contract. * Add CDN for document files. * Add CDN support for downloading attachments. * Deprecate current CDN methods. * Add progress indicator snackbar. * Add useDocumentGView config flag. * Add file sharing cache handling. * Add file sharing cache handling. * Remove CDNResponse.kt * Add tests * PR remarks * [skip ci] Update SDK sizes * Post-merge clean-up. * Post-merge clean-up. * ApiDump. * Improve attachment URI resolution and error handling in `AttachmentsPickerViewModel` and `AttachmentStorageHelper`. - Add `isUriResolvable` to `StorageHelper` to verify if a content URI can be opened for reading. - Implement `partitionResolvable` in `AttachmentStorageHelper` to separate metadata based on URI accessibility. - Update `AttachmentsPickerViewModel.resolveAndSubmitUris` to exclude inaccessible URIs (e.g., undownloaded cloud files) from the submission. - Ensure `hasUnresolvedAttachments` is correctly set when URIs are inaccessible, independent of file type support. - Add unit tests in `AttachmentStorageHelperTest` and `AttachmentsPickerViewModelTest` to verify partitioning logic and view model state updates. * Handle unresolvable attachments in XML * apiDump. --------- Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: André Mion <andremion@gmail.com> Co-authored-by: Gianmarco <47775302+gpunto@users.noreply.github.com> Co-authored-by: stream-pr-merger[bot] <117762243+stream-pr-merger[bot]@users.noreply.github.com> Co-authored-by: adasiewiczr <17440581+adasiewiczr@users.noreply.github.com>
1 parent 0df0b42 commit a7e71c1

File tree

94 files changed

+3188
-394
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

94 files changed

+3188
-394
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
<p align="center">
44
<a href="https://getstream.io/tutorials/android-chat/">
5-
<img src="/docs/sdk-hero-android.png"/>
5+
<img src="/docs/stream-chat-android-github-cover.png"/>
66
</a>
77
</p>
88

docs/sdk-hero-android.png

-3.18 MB
Binary file not shown.
1.94 MB
Loading

stream-chat-android-client/api/stream-chat-android-client.api

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -266,6 +266,7 @@ public final class io/getstream/chat/android/client/ChatClient$Builder : io/gets
266266
public final fun appVersion (Ljava/lang/String;)Lio/getstream/chat/android/client/ChatClient$Builder;
267267
public final fun baseUrl (Ljava/lang/String;)Lio/getstream/chat/android/client/ChatClient$Builder;
268268
public fun build ()Lio/getstream/chat/android/client/ChatClient;
269+
public final fun cdn (Lio/getstream/chat/android/client/cdn/CDN;)Lio/getstream/chat/android/client/ChatClient$Builder;
269270
public final fun cdnUrl (Ljava/lang/String;)Lio/getstream/chat/android/client/ChatClient$Builder;
270271
public final fun clientDebugger (Lio/getstream/chat/android/client/debugger/ChatClientDebugger;)Lio/getstream/chat/android/client/ChatClient$Builder;
271272
public final fun config (Lio/getstream/chat/android/client/api/ChatClientConfig;)Lio/getstream/chat/android/client/ChatClient$Builder;
@@ -909,6 +910,27 @@ public final class io/getstream/chat/android/client/audio/WaveformExtractorKt {
909910
public static final fun isEof (Landroid/media/MediaCodec$BufferInfo;)Z
910911
}
911912

913+
public abstract interface class io/getstream/chat/android/client/cdn/CDN {
914+
public fun fileRequest (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
915+
public static synthetic fun fileRequest$suspendImpl (Lio/getstream/chat/android/client/cdn/CDN;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
916+
public fun imageRequest (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
917+
public static synthetic fun imageRequest$suspendImpl (Lio/getstream/chat/android/client/cdn/CDN;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
918+
}
919+
920+
public final class io/getstream/chat/android/client/cdn/CDNRequest {
921+
public fun <init> (Ljava/lang/String;Ljava/util/Map;)V
922+
public synthetic fun <init> (Ljava/lang/String;Ljava/util/Map;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
923+
public final fun component1 ()Ljava/lang/String;
924+
public final fun component2 ()Ljava/util/Map;
925+
public final fun copy (Ljava/lang/String;Ljava/util/Map;)Lio/getstream/chat/android/client/cdn/CDNRequest;
926+
public static synthetic fun copy$default (Lio/getstream/chat/android/client/cdn/CDNRequest;Ljava/lang/String;Ljava/util/Map;ILjava/lang/Object;)Lio/getstream/chat/android/client/cdn/CDNRequest;
927+
public fun equals (Ljava/lang/Object;)Z
928+
public final fun getHeaders ()Ljava/util/Map;
929+
public final fun getUrl ()Ljava/lang/String;
930+
public fun hashCode ()I
931+
public fun toString ()Ljava/lang/String;
932+
}
933+
912934
public final class io/getstream/chat/android/client/channel/ChannelClient {
913935
public final fun acceptInvite (Ljava/lang/String;)Lio/getstream/result/call/Call;
914936
public final fun addMembers (Lio/getstream/chat/android/client/query/AddMembersParams;)Lio/getstream/result/call/Call;

stream-chat-android-client/src/main/java/io/getstream/chat/android/client/ChatClient.kt

Lines changed: 63 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,8 @@ import io.getstream.chat.android.client.attachment.prepareForUpload
7474
import io.getstream.chat.android.client.audio.AudioPlayer
7575
import io.getstream.chat.android.client.audio.NativeMediaPlayerImpl
7676
import io.getstream.chat.android.client.audio.StreamAudioPlayer
77+
import io.getstream.chat.android.client.cdn.CDN
78+
import io.getstream.chat.android.client.cdn.internal.StreamMediaDataSource
7779
import io.getstream.chat.android.client.channel.ChannelClient
7880
import io.getstream.chat.android.client.channel.state.ChannelStateLogicProvider
7981
import io.getstream.chat.android.client.clientstate.DisconnectCause
@@ -104,6 +106,7 @@ import io.getstream.chat.android.client.extensions.ATTACHMENT_TYPE_FILE
104106
import io.getstream.chat.android.client.extensions.ATTACHMENT_TYPE_IMAGE
105107
import io.getstream.chat.android.client.extensions.cidToTypeAndId
106108
import io.getstream.chat.android.client.extensions.extractBaseUrl
109+
import io.getstream.chat.android.client.extensions.getCreatedAtOrNull
107110
import io.getstream.chat.android.client.extensions.internal.hasPendingAttachments
108111
import io.getstream.chat.android.client.extensions.internal.isLaterThanDays
109112
import io.getstream.chat.android.client.header.VersionPrefixHeader
@@ -162,6 +165,7 @@ import io.getstream.chat.android.client.user.storage.SharedPreferencesCredential
162165
import io.getstream.chat.android.client.user.storage.UserCredentialStorage
163166
import io.getstream.chat.android.client.utils.ProgressCallback
164167
import io.getstream.chat.android.client.utils.TokenUtils
168+
import io.getstream.chat.android.client.utils.internal.ServerClockOffset
165169
import io.getstream.chat.android.client.utils.mergePartially
166170
import io.getstream.chat.android.client.utils.message.ensureId
167171
import io.getstream.chat.android.client.utils.observable.ChatEventsObservable
@@ -289,9 +293,13 @@ internal constructor(
289293
@InternalStreamChatApi
290294
public val audioPlayer: AudioPlayer,
291295
private val now: () -> Date = ::Date,
296+
@InternalStreamChatApi
297+
public val serverClockOffset: ServerClockOffset,
292298
private val repository: ChatClientRepository,
293299
private val messageReceiptReporter: MessageReceiptReporter,
294300
internal val messageReceiptManager: MessageReceiptManager,
301+
@InternalStreamChatApi
302+
public val cdn: CDN? = null,
295303
) {
296304
private val logger by taggedLogger(TAG)
297305
private val fileManager = StreamFileManager()
@@ -353,6 +361,7 @@ internal constructor(
353361
*
354362
* @see [Plugin]
355363
*/
364+
@Volatile
356365
@InternalStreamChatApi
357366
public var plugins: List<Plugin> = emptyList()
358367

@@ -399,12 +408,16 @@ internal constructor(
399408
@Suppress("ThrowsCount")
400409
internal inline fun <reified P : DependencyResolver, reified T : Any> resolvePluginDependency(): T {
401410
StreamLog.v(TAG) { "[resolvePluginDependency] P: ${P::class.simpleName}, T: ${T::class.simpleName}" }
411+
// Snapshot plugins BEFORE checking initializationState to avoid a race with disconnect().
412+
// disconnect() sets initializationState to NOT_INITIALIZED before clearing plugins,
413+
// so if we snapshot plugins first and then see COMPLETE, the snapshot is guaranteed valid.
414+
val currentPlugins = plugins
402415
val initState = awaitInitializationState(RESOLVE_DEPENDENCY_TIMEOUT)
403416
if (initState != InitializationState.COMPLETE) {
404417
StreamLog.e(TAG) { "[resolvePluginDependency] failed (initializationState is not COMPLETE): $initState " }
405418
throw IllegalStateException("ChatClient::connectUser() must be called before resolving any dependency")
406419
}
407-
val resolver = plugins.find { plugin ->
420+
val resolver = currentPlugins.find { plugin ->
408421
plugin is P
409422
} ?: throw IllegalStateException(
410423
"Plugin '${P::class.qualifiedName}' was not found. Did you init it within ChatClient?",
@@ -1569,9 +1582,9 @@ internal constructor(
15691582

15701583
notifications.onLogout()
15711584
// Set initializationState to NOT_INITIALIZED BEFORE clearing plugins to prevent race condition.
1572-
// This ensures the StatePlugin extension methods don't access the plugin during disconnect.
1585+
// resolvePluginDependency() snapshots plugins before checking state, so if it sees COMPLETE
1586+
// here, the snapshot is guaranteed to still contain the plugins.
15731587
mutableClientState.setInitializationState(InitializationState.NOT_INITIALIZED)
1574-
15751588
plugins.forEach { it.onUserDisconnected() }
15761589
plugins = emptyList()
15771590
userStateService.onLogout()
@@ -2534,16 +2547,34 @@ internal constructor(
25342547

25352548
/**
25362549
* Ensure the message has a [Message.createdLocallyAt] timestamp.
2537-
* If not, set it to the max of the channel's [Channel.lastMessageAt] + 1 millisecond and [now].
2538-
* This ensures that the message appears in the correct order in the channel.
2550+
* If not, set it to the max of the channel's [Channel.lastMessageAt] + 1 millisecond and the
2551+
* estimated server time. Using estimated server time (instead of raw local clock) prevents
2552+
* cross-user ordering issues when the device clock is skewed.
25392553
*/
25402554
private suspend fun Message.ensureCreatedLocallyAt(cid: String): Message {
2541-
val lastMessageAt = repositoryFacade.selectChannel(cid = cid)?.lastMessageAt
2542-
val lastMessageAtPlusOneMillisecond = lastMessageAt?.let {
2543-
Date(it.time + 1)
2555+
val parentId = this.parentId
2556+
if (parentId != null) {
2557+
// Thread reply
2558+
val lastMessage = repositoryFacade.selectMessagesForThread(parentId, limit = 1).lastOrNull()
2559+
val lastMessageAt = lastMessage?.getCreatedAtOrNull()
2560+
val lastMessageAtPlusOneMillisecond = lastMessageAt?.let {
2561+
Date(it.time + 1)
2562+
}
2563+
val createdLocallyAt = max(lastMessageAtPlusOneMillisecond, serverClockOffset.estimatedServerTime())
2564+
return copy(createdLocallyAt = this.createdLocallyAt ?: createdLocallyAt)
2565+
} else {
2566+
// Regular message
2567+
val (type, id) = cid.cidToTypeAndId()
2568+
// Fetch channel lastMessageAt from state, fallback to offline storage
2569+
val channelState = logicRegistry?.channelStateLogic(type, id)?.channelState()
2570+
val lastMessageAt = channelState?.channelData?.value?.lastMessageAt
2571+
?: repositoryFacade.selectChannel(cid = cid)?.lastMessageAt
2572+
val lastMessageAtPlusOneMillisecond = lastMessageAt?.let {
2573+
Date(it.time + 1)
2574+
}
2575+
val createdLocallyAt = max(lastMessageAtPlusOneMillisecond, serverClockOffset.estimatedServerTime())
2576+
return copy(createdLocallyAt = this.createdLocallyAt ?: createdLocallyAt)
25442577
}
2545-
val createdLocallyAt = max(lastMessageAtPlusOneMillisecond, now())
2546-
return copy(createdLocallyAt = this.createdLocallyAt ?: createdLocallyAt)
25472578
}
25482579

25492580
/**
@@ -4608,6 +4639,7 @@ internal constructor(
46084639
private var uploadAttachmentsNetworkType = UploadAttachmentsNetworkType.CONNECTED
46094640
private var fileTransformer: FileTransformer = NoOpFileTransformer
46104641
private var apiModelTransformers: ApiModelTransformers = ApiModelTransformers()
4642+
private var cdn: CDN? = null
46114643
private var appName: String? = null
46124644
private var appVersion: String? = null
46134645

@@ -4736,7 +4768,11 @@ internal constructor(
47364768
*
47374769
* @param shareFileDownloadRequestInterceptor Your [Interceptor] implementation for the share file download
47384770
* call.
4771+
* @deprecated Use [io.getstream.chat.android.client.cdn.CDN] instead. Configure a custom CDN via
4772+
* [io.getstream.chat.android.client.ChatClient.Builder.cdn] to provide headers and transform URLs
4773+
* for all image, file, and download requests.
47394774
*/
4775+
@Deprecated("Use CDN instead. Configure via ChatClient.Builder.cdn().")
47404776
public fun shareFileDownloadRequestInterceptor(shareFileDownloadRequestInterceptor: Interceptor): Builder {
47414777
this.shareFileDownloadRequestInterceptor = shareFileDownloadRequestInterceptor
47424778
return this
@@ -4807,6 +4843,15 @@ internal constructor(
48074843
forceWsUrl = value
48084844
}
48094845

4846+
/**
4847+
* Sets a custom [CDN] implementation to be used by the client.
4848+
*
4849+
* @param cdn The custom CDN implementation.
4850+
*/
4851+
public fun cdn(cdn: CDN): Builder = apply {
4852+
this.cdn = cdn
4853+
}
4854+
48104855
/**
48114856
* Sets the CDN URL to be used by the client.
48124857
*/
@@ -4933,6 +4978,8 @@ internal constructor(
49334978
warmUpReflection()
49344979
}
49354980

4981+
val serverClockOffset = ServerClockOffset()
4982+
49364983
val module =
49374984
ChatModule(
49384985
appContext = appContext,
@@ -4945,19 +4992,22 @@ internal constructor(
49454992
fileUploader = fileUploader,
49464993
sendMessageInterceptor = sendMessageInterceptor,
49474994
shareFileDownloadRequestInterceptor = shareFileDownloadRequestInterceptor,
4995+
cdn = cdn,
49484996
tokenManager = tokenManager,
49494997
customOkHttpClient = customOkHttpClient,
49504998
clientDebugger = clientDebugger,
49514999
lifecycle = lifecycle,
49525000
appName = this.appName,
49535001
appVersion = this.appVersion,
5002+
serverClockOffset = serverClockOffset,
49545003
)
49555004

49565005
val api = module.api()
49575006
val appSettingsManager = AppSettingManager(api)
49585007

5008+
val mediaDataSourceFactory = StreamMediaDataSource.factory(appContext, cdn)
49595009
val audioPlayer: AudioPlayer = StreamAudioPlayer(
4960-
mediaPlayer = NativeMediaPlayerImpl(appContext) {
5010+
mediaPlayer = NativeMediaPlayerImpl(mediaDataSourceFactory) {
49615011
ExoPlayer.Builder(appContext)
49625012
.setAudioAttributes(
49635013
AudioAttributes.Builder()
@@ -4991,6 +5041,7 @@ internal constructor(
49915041
retryPolicy = retryPolicy,
49925042
appSettingsManager = appSettingsManager,
49935043
chatSocket = module.chatSocket,
5044+
serverClockOffset = serverClockOffset,
49945045
pluginFactories = allPluginFactories,
49955046
repositoryFactoryProvider = allPluginFactories
49965047
.filterIsInstance<RepositoryFactory.Provider>()
@@ -5011,6 +5062,7 @@ internal constructor(
50115062
messageReceiptRepository = repository,
50125063
api = api,
50135064
),
5065+
cdn = cdn,
50145066
).apply {
50155067
attachmentsSender = AttachmentsSender(
50165068
context = appContext,

stream-chat-android-client/src/main/java/io/getstream/chat/android/client/api/state/ChatClientStateExtensions.kt

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ import io.getstream.chat.android.client.internal.state.extensions.internal.parse
3636
import io.getstream.chat.android.client.internal.state.extensions.internal.requestsAsState
3737
import io.getstream.chat.android.client.internal.state.plugin.factory.StreamStatePluginFactory
3838
import io.getstream.chat.android.client.internal.state.plugin.internal.StatePlugin
39+
import io.getstream.chat.android.client.utils.attachment.isImage
3940
import io.getstream.chat.android.client.utils.internal.validateCidWithResult
4041
import io.getstream.chat.android.client.utils.message.isEphemeral
4142
import io.getstream.chat.android.core.internal.InternalStreamChatApi
@@ -302,6 +303,12 @@ public fun ChatClient.setMessageForReply(cid: String, message: Message?): Call<U
302303
/**
303304
* Downloads the selected attachment to the "Download" folder in the public external storage directory.
304305
*
306+
* If a [CDN][io.getstream.chat.android.client.cdn.CDN] is configured on this [ChatClient], the download URL
307+
* and headers are transformed via [CDN.imageRequest][io.getstream.chat.android.client.cdn.CDN.imageRequest] (for
308+
* images) or [CDN.fileRequest][io.getstream.chat.android.client.cdn.CDN.fileRequest] (for other files) before
309+
* the download is enqueued. CDN transformations are applied after [generateDownloadUri] and before
310+
* [interceptRequest], so custom interceptors can override CDN headers.
311+
*
305312
* @param context The context used to access the [DownloadManager].
306313
* @param attachment The attachment to download.
307314
* @param generateDownloadUri The function that generates the download URI for the attachment.
@@ -326,13 +333,29 @@ public fun ChatClient.downloadAttachment(
326333
val subPath = attachment.name ?: attachment.title ?: attachment.parseAttachmentNameFromUrl()
327334
?: createAttachmentFallbackName()
328335

329-
logger.d { "Downloading attachment. Name: $subPath, Uri: $uri" }
336+
// Apply CDN transformation if available
337+
val cdnRequest = try {
338+
val cdn = this@downloadAttachment.cdn
339+
val url = uri.toString()
340+
if (attachment.isImage()) cdn?.imageRequest(url) else cdn?.fileRequest(url)
341+
} catch (@Suppress("TooGenericExceptionCaught") e: Exception) {
342+
logger.e(e) { "CDN request failed for attachment. Falling back to original URL." }
343+
null
344+
}
345+
val finalUri = cdnRequest?.url?.let(Uri::parse) ?: uri
346+
347+
logger.d { "Downloading attachment. Name: $subPath, Uri: $finalUri" }
330348

331349
downloadManager.enqueue(
332-
DownloadManager.Request(uri)
350+
DownloadManager.Request(finalUri)
333351
.setTitle(subPath)
334352
.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, subPath)
335353
.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED)
354+
.apply {
355+
cdnRequest?.headers?.forEach { (key, value) ->
356+
addRequestHeader(key, value)
357+
}
358+
}
336359
.apply(interceptRequest),
337360
)
338361
Result.Success(Unit)

stream-chat-android-client/src/main/java/io/getstream/chat/android/client/audio/NativeMediaPlayer.kt

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,13 @@
1616

1717
package io.getstream.chat.android.client.audio
1818

19-
import android.content.Context
2019
import androidx.annotation.OptIn
2120
import androidx.media3.common.MediaItem
2221
import androidx.media3.common.PlaybackException
2322
import androidx.media3.common.PlaybackParameters
2423
import androidx.media3.common.Player
2524
import androidx.media3.common.util.UnstableApi
26-
import androidx.media3.datasource.DefaultDataSource
25+
import androidx.media3.datasource.DataSource
2726
import androidx.media3.exoplayer.ExoPlayer
2827
import androidx.media3.exoplayer.source.MediaSource
2928
import androidx.media3.exoplayer.source.ProgressiveMediaSource
@@ -198,12 +197,12 @@ public enum class NativeMediaPlayerState {
198197
/**
199198
* Default implementation of [NativeMediaPlayer] based on ExoPlayer.
200199
*
201-
* @param context The context.
200+
* @param dataSourceFactory The data source factory used for creating media sources.
202201
* @param builder A builder function to create an [ExoPlayer] instance.
203202
*/
204203
@OptIn(UnstableApi::class)
205204
internal class NativeMediaPlayerImpl(
206-
context: Context,
205+
dataSourceFactory: DataSource.Factory,
207206
private val builder: () -> ExoPlayer,
208207
) : NativeMediaPlayer {
209208

@@ -232,7 +231,7 @@ internal class NativeMediaPlayerImpl(
232231
* For more info see [ExoPlayer Progressive](https://developer.android.com/media/media3/exoplayer/progressive).
233232
*/
234233
private val mediaSourceFactory: MediaSource.Factory = ProgressiveMediaSource.Factory(
235-
DefaultDataSource.Factory(context),
234+
dataSourceFactory,
236235
DefaultExtractorsFactory().setConstantBitrateSeekingEnabled(true),
237236
)
238237

0 commit comments

Comments
 (0)