Skip to content
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
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ class EmbyAPIClient(val context: Context, baseUrl: String = "http://localhost:80
val cache = Cache(cacheDir, cacheSize.toLong())

val okClient = RetrofitUrlManager.getInstance().with(OkHttpClient.Builder())
logger.level = HttpLoggingInterceptor.Level.BODY
logger.level = HttpLoggingInterceptor.Level.BASIC
okClient.addInterceptor(logger)
okClient.cache(cache)

Expand Down
9 changes: 4 additions & 5 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ moshiKotlinVersion = "1.15.2"
retrofitVersion = "2.9.0"
jodaTimeVersion = "2.10.9.1"
timberVersion = "5.0.1"
exoplayerVersion = "2.19.1"
media3Version = "1.9.0"
simpleXmlVersion = "2.7.1"

junitVersion = "4.13.2"
Expand Down Expand Up @@ -80,9 +80,9 @@ androidx-percentlayout = { module = "androidx.percentlayout:percentlayout", vers
androidx-recycler-view = { module = "androidx.recyclerview:recyclerview", version.ref = "androidxRecyclerViewVersion"}
commons-io = { module = "commons-io:commons-io", version.ref = "commonsIo" }
commons-lang3 = { module = "org.apache.commons:commons-lang3", version.ref = "commonsLang3Version" }
exoplayer-core = { module = "com.google.android.exoplayer:exoplayer-core", version.ref = "exoplayerVersion"}
exoplayer-ui = { module = "com.google.android.exoplayer:exoplayer-ui", version.ref = "exoplayerVersion"}
exoplayer-okhttp = { module = "com.google.android.exoplayer:extension-okhttp", version.ref = "exoplayerVersion"}
androidx-media3-exoplayer = { module = "androidx.media3:media3-exoplayer", version.ref = "media3Version" }
androidx-media3-ui = { module = "androidx.media3:media3-ui", version.ref = "media3Version" }
androidx-media3-datasource-okhttp = { module = "androidx.media3:media3-datasource-okhttp", version.ref = "media3Version" }
firebase-analytics = { module = "com.google.firebase:firebase-analytics" }
firebase-bom = { module = "com.google.firebase:firebase-bom", version.ref = "firebaseBomVersion" }
firebase-crashlytics = { module = "com.google.firebase:firebase-crashlytics" }
Expand Down Expand Up @@ -147,4 +147,3 @@ androidx-lifecycle-process = { group = "androidx.lifecycle", name = "lifecycle-p
ksp = { id = "com.google.devtools.ksp", version.ref = "kotlinKSPVersion" }
android-application = { id = "com.android.application", version.ref = "agpVersion" }
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }

Original file line number Diff line number Diff line change
Expand Up @@ -425,6 +425,14 @@ class JellyfinAPIClient(val context: Context, baseUrl: String = "http://localhos
return result.isSuccessful
}

override fun progress(
key: String,
offset: String,
playSessionId: String?
): Boolean {
TODO("Not yet implemented")
}

override fun createMediaTagURL(resourceType: String, resourceName: String, identifier: String): String {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
Expand Down Expand Up @@ -475,14 +483,15 @@ class JellyfinAPIClient(val context: Context, baseUrl: String = "http://localhos
return "$baseUrl/Users/${user.userId}/Images/Primary?Width=$width&Height=$height"
}

override fun startPlaying(itemId: String) {
override fun startPlaying(itemId: String): String? {
if (userId == null) {
userId = fetchUserId()
}

val call = usersService.startPlaying(headerMap(), userId!!, itemId)

call.execute()
return UUID.randomUUID().toString()
}
Comment on lines +486 to 495
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

find . -name "JellyfinAPIClient.kt" -type f

Repository: NineWorlds/serenity-android

Length of output: 165


🏁 Script executed:

head -n 500 ./jellyfin-lib/src/main/kotlin/us/nineworlds/serenity/jellyfin/server/api/JellyfinAPIClient.kt | tail -n 100

Repository: NineWorlds/serenity-android

Length of output: 3551


🏁 Script executed:

rg -nP --type=kt "fun startPlaying\s*\(" --context=3 jellyfin-lib/

Repository: NineWorlds/serenity-android

Length of output: 97


🏁 Script executed:

rg -n "fun startPlaying" --context=3 jellyfin-lib/

Repository: NineWorlds/serenity-android

Length of output: 1886


🏁 Script executed:

sed -n '120,130p' jellyfin-lib/src/main/kotlin/us/nineworlds/serenity/jellyfin/server/api/JellyfinUsersService.kt

Repository: NineWorlds/serenity-android

Length of output: 450


🏁 Script executed:

rg -n "fun progress" jellyfin-lib/src/main/kotlin/us/nineworlds/serenity/jellyfin/server/api/JellyfinUsersService.kt -A 5

Repository: NineWorlds/serenity-android

Length of output: 336


🏁 Script executed:

rg -n "playSessionId\|PlaySessionId" jellyfin-lib/src/main/kotlin/us/nineworlds/serenity/jellyfin/server/api/ -A 2 -B 2

Repository: NineWorlds/serenity-android

Length of output: 53


🏁 Script executed:

rg -rn "playSessionId\|PlaySessionId" jellyfin-lib/

Repository: NineWorlds/serenity-android

Length of output: 53


🏁 Script executed:

rg -n "startPlaying" jellyfin-lib/src/main/kotlin/ -B 2 -A 2

Repository: NineWorlds/serenity-android

Length of output: 1936


🏁 Script executed:

sed -n '425,445p' ./jellyfin-lib/src/main/kotlin/us/nineworlds/serenity/jellyfin/server/api/JellyfinAPIClient.kt

Repository: NineWorlds/serenity-android

Length of output: 845


🏁 Script executed:

sed -n '460,480p' ./jellyfin-lib/src/main/kotlin/us/nineworlds/serenity/jellyfin/server/api/JellyfinAPIClient.kt

Repository: NineWorlds/serenity-android

Length of output: 975


🏁 Script executed:

rg -n "fun progress" jellyfin-lib/src/main/kotlin/us/nineworlds/serenity/jellyfin/server/api/JellyfinUsersService.kt -A 8

Repository: NineWorlds/serenity-android

Length of output: 414


🏁 Script executed:

sed -n '100,140p' ./jellyfin-lib/src/main/kotlin/us/nineworlds/serenity/jellyfin/server/api/JellyfinUsersService.kt

Repository: NineWorlds/serenity-android

Length of output: 1392


🏁 Script executed:

rg -n "PlaySessionId" ./jellyfin-lib/src/main/kotlin/ -B 3 -A 3

Repository: NineWorlds/serenity-android

Length of output: 1072


🏁 Script executed:

rg -n "startPlaying" ./jellyfin-lib/ --type-list -A 1 | head -50

Repository: NineWorlds/serenity-android

Length of output: 460


🏁 Script executed:

rg -n "startPlaying" ./jellyfin-lib/ -A 1 -B 1

Repository: NineWorlds/serenity-android

Length of output: 1258


Generate UUID before the API call and add error handling.

The current implementation has two critical issues:

  1. UUID timing: The UUID is generated on line 494 after call.execute(), which means it's never sent to the server. If this session ID is needed for tracking, it must be generated before the API call.

  2. Missing error handling: Unlike similar methods (watched, unwatched), the execute() result is not checked. The method should verify success before returning the session ID.

Note: The usersService.startPlaying() endpoint does not accept a playSessionId query parameter. The progress() overload that accepts playSessionId is currently marked TODO("Not yet implemented"), so those must be addressed separately. For now, focus on generating the UUID before the call and properly handling the API response:

 override fun startPlaying(itemId: String): String? {
     if (userId == null) {
         userId = fetchUserId()
     }
+    
+    val playSessionId = UUID.randomUUID().toString()
 
-    val call = usersService.startPlaying(headerMap(), userId!!, itemId)
+    val call = usersService.startPlaying(headerMap(), userId!!, itemId)
 
-    call.execute()
-    return UUID.randomUUID().toString()
+    val result = call.execute()
+    return if (result.isSuccessful) playSessionId else null
 }
🤖 Prompt for AI Agents
In
jellyfin-lib/src/main/kotlin/us/nineworlds/serenity/jellyfin/server/api/JellyfinAPIClient.kt
around lines 486–495, generate the play session UUID before making the
usersService.startPlaying call, then pass it along if the request supports it
(or keep it locally for tracking), and call execute() into a val response; check
response.isSuccessful and only return the UUID on success; on failure log or
handle the error and return null. Ensure userId is fetched the same way as
before and avoid generating the UUID after the network call.


override fun stopPlaying(itemId: String, offset: Long) {
Expand Down
13 changes: 3 additions & 10 deletions serenity-app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -122,16 +122,9 @@ dependencies {
implementation(libs.firebase.crashlytics)

implementation(libs.github.glide.okhttp)
implementation(libs.exoplayer.core) {
exclude(module = "support-annotations")
}
implementation(libs.exoplayer.ui) {
exclude(module = "support-annotations")
}

implementation(libs.exoplayer.okhttp) {
exclude(module = "support-annotations")
}
implementation(libs.androidx.media3.exoplayer)
implementation(libs.androidx.media3.ui)
implementation(libs.androidx.media3.datasource.okhttp)

implementation(libs.okhttp) {
exclude(group = "com.android.support")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,10 @@ import android.preference.PreferenceManager
import androidx.lifecycle.ProcessLifecycleOwner
import androidx.lifecycle.lifecycleScope
import androidx.localbroadcastmanager.content.LocalBroadcastManager
import com.google.android.exoplayer2.database.ExoDatabaseProvider
import com.google.android.exoplayer2.upstream.cache.LeastRecentlyUsedCacheEvictor
import com.google.android.exoplayer2.upstream.cache.SimpleCache
import androidx.media3.common.util.UnstableApi
import androidx.media3.database.ExoDatabaseProvider
import androidx.media3.datasource.cache.LeastRecentlyUsedCacheEvictor
import androidx.media3.datasource.cache.SimpleCache
import com.google.firebase.analytics.FirebaseAnalytics
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
Expand Down Expand Up @@ -99,14 +100,16 @@ open class SerenityApplication : Application() {
Toothpick.inject(this, scope)
}

@UnstableApi
override fun onCreate() {
super.onCreate()
init()
setDefaultPreferences()
discoverServers()
MediaCodecInfoUtil.logAvailableCodecs()

val leastRecentlyUsedCacheEvictor = LeastRecentlyUsedCacheEvictor((200 * 1024 * 1024).toLong())
val leastRecentlyUsedCacheEvictor =
LeastRecentlyUsedCacheEvictor((200 * 1024 * 1024).toLong())
val exoDatabaseProvider = ExoDatabaseProvider(this)
simpleCache = SimpleCache(cacheDir, leastRecentlyUsedCacheEvictor, exoDatabaseProvider)
Comment on lines +111 to 114
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Call simpleCache.release() for proper resource cleanup, but not in onTerminate().

SimpleCache requires calling .release() to properly clean up database resources. However, onTerminate() is only for testing and is not called in production. Consider releasing the cache during app lifecycle events that are reliably called, such as onTrimMemory() or when the last activity is destroyed, to ensure cleanup happens in production scenarios.

🤖 Prompt for AI Agents
In serenity-app/src/main/java/us/nineworlds/serenity/SerenityApplication.kt
around lines 111 to 114, SimpleCache is created but not released; calling
simpleCache.release() in onTerminate() is insufficient because onTerminate() is
not called in production. Add reliable cleanup: implement
Application.ActivityLifecycleCallbacks to track activity count (or override
onTrimMemory) and call simpleCache.release() when the app is
backgrounded/trimmed to a critical level (e.g., TRIM_MEMORY_COMPLETE/UiHidden)
or when the last activity is destroyed; ensure you null out the simpleCache
reference after release, guard against multiple releases, and wrap release in a
try/catch to avoid crashing.

}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,15 @@
import android.content.pm.PackageManager;
import android.os.Build;

import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.audio.AudioCapabilities;
import androidx.annotation.OptIn;
import androidx.media3.common.C;
import androidx.media3.common.MimeTypes;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.exoplayer.audio.AudioCapabilities;
import androidx.media3.exoplayer.mediacodec.MediaCodecInfo;
import androidx.media3.exoplayer.mediacodec.MediaCodecUtil;

import java.util.List;

public class AndroidHelper {

Expand Down Expand Up @@ -65,20 +72,47 @@ public boolean isBravia() {
return Build.MODEL.toLowerCase().contains(ANDROID_BRAVIA_MODEL.toLowerCase());
}

public boolean enableTunneling() {
return false;
}
/**
* Checks if the device supports tunneling for common video formats (HEVC or AVC).
* @return true if tunneling is supported for either H.265 or H.264.
*/
@OptIn(markerClass = UnstableApi.class)
public boolean enableTunneling() {
// Tunneling is practically only useful/stable on API 24+ (Android 7.0)
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
return false;
}
return isTunnelingSupported(MimeTypes.VIDEO_H265)
|| isTunnelingSupported(MimeTypes.VIDEO_H264);
}


public boolean isBeyondTV() {
return Build.MODEL.toLowerCase().contains(ANDROID_TCL_BEYONDTV5.toLowerCase()) || Build.MODEL.toLowerCase().contains("smart tv");
@OptIn(markerClass = UnstableApi.class)
public boolean isTunnelingSupported(String mimeType) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
return false;
}

try {
List<MediaCodecInfo> decoderInfos = MediaCodecUtil.getDecoderInfos(mimeType, false, false);
for (MediaCodecInfo info : decoderInfos) {
if (info.capabilities != null && info.capabilities.isFeatureSupported(android.media.MediaCodecInfo.CodecCapabilities.FEATURE_TunneledPlayback)) {
return true;
}
}
} catch (Exception e) {
return false;
}
Comment on lines +96 to +105
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Silent exception handling hinders debugging.

The broad catch (Exception e) silently swallows all errors without logging. If MediaCodecUtil throws exceptions due to device issues or unsupported codecs, you'll have no visibility into the failure cause.

🔎 Suggested improvement: Add logging for exceptions
+import android.util.Log;
+
 // ... in method body
     try {
       List<MediaCodecInfo> decoderInfos = MediaCodecUtil.getDecoderInfos(mimeType, false, false);
       for (MediaCodecInfo info : decoderInfos) {
         if (info.capabilities != null && info.capabilities.isFeatureSupported(android.media.MediaCodecInfo.CodecCapabilities.FEATURE_TunneledPlayback)) {
           return true;
         }
       }
     } catch (Exception e) {
+      Log.w("AndroidHelper", "Failed to check tunneling support for " + mimeType, e);
       return false;
     }

Alternatively, narrow the catch to MediaCodecUtil.DecoderQueryException if you only expect that specific exception type.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
try {
List<MediaCodecInfo> decoderInfos = MediaCodecUtil.getDecoderInfos(mimeType, false, false);
for (MediaCodecInfo info : decoderInfos) {
if (info.capabilities != null && info.capabilities.isFeatureSupported(android.media.MediaCodecInfo.CodecCapabilities.FEATURE_TunneledPlayback)) {
return true;
}
}
} catch (Exception e) {
return false;
}
try {
List<MediaCodecInfo> decoderInfos = MediaCodecUtil.getDecoderInfos(mimeType, false, false);
for (MediaCodecInfo info : decoderInfos) {
if (info.capabilities != null && info.capabilities.isFeatureSupported(android.media.MediaCodecInfo.CodecCapabilities.FEATURE_TunneledPlayback)) {
return true;
}
}
} catch (Exception e) {
Log.w("AndroidHelper", "Failed to check tunneling support for " + mimeType, e);
return false;
}
🤖 Prompt for AI Agents
In
serenity-app/src/main/java/us/nineworlds/serenity/core/util/AndroidHelper.java
around lines 96-105, the current catch block swallows all exceptions which hides
failures from MediaCodecUtil; change the catch to either catch the specific
MediaCodecUtil.DecoderQueryException (if available) or keep Exception but log
the exception before returning false (use the class TAG or Android Log.e with a
short message and the exception) so the error is visible for debugging while
preserving the existing false return behavior.

return false;
}

public boolean isLeanbackSupported() {
final PackageManager pm = context.getPackageManager();
return pm.hasSystemFeature(ANDROID_SOFTWARE_LEANBACK);
}

public boolean isAudioPassthroughSupported(String codec) {
@OptIn(markerClass = UnstableApi.class)
public boolean isAudioPassthroughSupported(String codec) {
AudioCapabilities audioCapabilities = AudioCapabilities.getCapabilities(context);
int encoding = 0;
switch (codec.toLowerCase()) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
package us.nineworlds.serenity.injection.modules;

import com.google.android.exoplayer2.trackselection.TrackSelector;
import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.HttpDataSource;

import androidx.annotation.OptIn;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.datasource.DataSource;
import androidx.media3.datasource.HttpDataSource;
import androidx.media3.exoplayer.trackselection.TrackSelector;

import toothpick.config.Module;
import us.nineworlds.serenity.injection.modules.providers.DataSourceFactoryProvider;
Expand All @@ -14,7 +17,8 @@

public class ExoplayerVideoModule extends Module {

public ExoplayerVideoModule() {
@OptIn(markerClass = UnstableApi.class)
public ExoplayerVideoModule() {
super();


Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
package us.nineworlds.serenity.injection.modules.providers

import android.content.Context
import com.google.android.exoplayer2.upstream.DataSource
import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter
import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory
import com.google.android.exoplayer2.upstream.HttpDataSource
import com.google.android.exoplayer2.upstream.cache.CacheDataSource
import androidx.annotation.OptIn
import androidx.media3.common.util.UnstableApi
import androidx.media3.datasource.DataSource
import androidx.media3.datasource.DefaultDataSourceFactory
import androidx.media3.datasource.HttpDataSource
import androidx.media3.datasource.cache.CacheDataSource
import androidx.media3.exoplayer.upstream.DefaultBandwidthMeter
import us.nineworlds.serenity.SerenityApplication
import us.nineworlds.serenity.common.android.injection.ApplicationContext
import javax.inject.Inject
Expand All @@ -18,9 +20,12 @@ class DataSourceFactoryProvider : Provider<DataSource.Factory> {

@Inject
lateinit var httpDataSourceFactory: HttpDataSource.Factory

@OptIn(UnstableApi::class)
override fun get(): DataSource.Factory {
val bandwidthMeter = DefaultBandwidthMeter.getSingletonInstance(context)
val defaultDataSourceFactory = DefaultDataSourceFactory(context, bandwidthMeter, httpDataSourceFactory)
val defaultDataSourceFactory =
DefaultDataSourceFactory(context, bandwidthMeter, httpDataSourceFactory)
return CacheDataSource.Factory()
.setCache(SerenityApplication.simpleCache)
.setUpstreamDataSourceFactory(defaultDataSourceFactory)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@

import android.content.Context;

import com.google.android.exoplayer2.trackselection.AdaptiveTrackSelection;
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector;
import com.google.android.exoplayer2.trackselection.MappingTrackSelector;

import androidx.media3.common.util.UnstableApi;
import androidx.media3.exoplayer.trackselection.AdaptiveTrackSelection;
import androidx.media3.exoplayer.trackselection.DefaultTrackSelector;
import androidx.media3.exoplayer.trackselection.MappingTrackSelector;
Comment on lines +5 to +9
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Convert file to Kotlin and verify Media3 API compliance.

The import migration from ExoPlayer v2 to AndroidX.Media3 is correct. However, this file should be converted to Kotlin per the coding guidelines that require Kotlin as the primary language. When converting, use @OptIn annotation for unstable Media3 APIs at the method level in Kotlin, rather than the @UnstableApi annotation used in Java. The DefaultTrackSelector constructor requires Context context, TrackSelectionParameters parameters, and ExoTrackSelection.Factory trackSelectionFactory—verify that the provider passes the correct parameters. Toothpick usage with @Inject is appropriate and can be migrated to Kotlin incrementally while maintaining compatibility.

🤖 Prompt for AI Agents
In
serenity-app/src/main/java/us/nineworlds/serenity/injection/modules/providers/DefaultMappingTrackSelectorProvider.java
around lines 5–9, convert this Java provider to Kotlin and update it to use
Media3 APIs correctly: rewrite the class as a Kotlin provider, annotate the
method that constructs the DefaultTrackSelector with @OptIn(UnstableApi::class)
(at method level) instead of using the Java @UnstableApi, and ensure you call
the DefaultTrackSelector constructor with the correct parameters (Context,
TrackSelectionParameters, and
ExoTrackSelection.Factory/AdaptiveTrackSelection.Factory as required by the
Media3 version you target) passing in the current context, appropriate
TrackSelectionParameters, and a proper trackSelectionFactory; keep the Toothpick
@Inject wiring equivalent in Kotlin to maintain DI compatibility and migrate
incrementally if needed.


import javax.inject.Inject;
import javax.inject.Provider;
Expand All @@ -17,7 +19,8 @@ public class DefaultMappingTrackSelectorProvider implements Provider<MappingTrac
@ApplicationContext
Context context;

@Override public MappingTrackSelector get() {
@UnstableApi
@Override public MappingTrackSelector get() {
return new DefaultTrackSelector(context, new AdaptiveTrackSelection.Factory());
}
}
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
package us.nineworlds.serenity.injection.modules.providers;

import com.google.android.exoplayer2.trackselection.MappingTrackSelector;

import androidx.media3.common.util.UnstableApi;
import androidx.media3.exoplayer.trackselection.MappingTrackSelector;

import javax.inject.Inject;
import javax.inject.Provider;

import us.nineworlds.serenity.ui.video.player.EventLogger;

@UnstableApi
public class EventLoggerProvider implements Provider<EventLogger> {

@Inject MappingTrackSelector mappingTrackSelector;
@Inject
MappingTrackSelector mappingTrackSelector;

@Override public EventLogger get() {
return new EventLogger(mappingTrackSelector);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
package us.nineworlds.serenity.injection.modules.providers;

import com.google.android.exoplayer2.ext.okhttp.OkHttpDataSourceFactory;
import com.google.android.exoplayer2.upstream.HttpDataSource;

import androidx.media3.datasource.HttpDataSource;
import androidx.media3.datasource.okhttp.OkHttpDataSource;

import javax.inject.Inject;
import javax.inject.Provider;
Expand All @@ -15,6 +16,6 @@ public class HttpDataSourceFactoryProvider implements Provider<HttpDataSource.Fa
@Inject OkHttpClient okHttpClient;

@Override public HttpDataSource.Factory get() {
return new OkHttpDataSourceFactory(okHttpClient, userAgent, null, null);
return new OkHttpDataSource.Factory(okHttpClient).setUserAgent(userAgent);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,24 +18,27 @@
import android.os.SystemClock;
import android.util.Log;

import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.audio.AudioRendererEventListener;
import com.google.android.exoplayer2.decoder.DecoderCounters;
import com.google.android.exoplayer2.metadata.Metadata;
import com.google.android.exoplayer2.metadata.MetadataOutput;
import com.google.android.exoplayer2.metadata.emsg.EventMessage;
import com.google.android.exoplayer2.metadata.id3.ApicFrame;
import com.google.android.exoplayer2.metadata.id3.CommentFrame;
import com.google.android.exoplayer2.metadata.id3.GeobFrame;
import com.google.android.exoplayer2.metadata.id3.Id3Frame;
import com.google.android.exoplayer2.metadata.id3.PrivFrame;
import com.google.android.exoplayer2.metadata.id3.TextInformationFrame;
import com.google.android.exoplayer2.metadata.id3.UrlLinkFrame;
import com.google.android.exoplayer2.source.MediaSourceEventListener;
import com.google.android.exoplayer2.trackselection.MappingTrackSelector;
import com.google.android.exoplayer2.video.VideoRendererEventListener;
import androidx.annotation.Nullable;
import androidx.media3.common.C;
import androidx.media3.common.Format;
import androidx.media3.common.Player;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.exoplayer.audio.AudioRendererEventListener;
import androidx.media3.exoplayer.DecoderCounters;
import androidx.media3.exoplayer.DecoderReuseEvaluation;
import androidx.media3.common.Metadata;
import androidx.media3.exoplayer.metadata.MetadataOutput;
import androidx.media3.extractor.metadata.emsg.EventMessage;
import androidx.media3.extractor.metadata.id3.ApicFrame;
import androidx.media3.extractor.metadata.id3.CommentFrame;
import androidx.media3.extractor.metadata.id3.GeobFrame;
import androidx.media3.extractor.metadata.id3.Id3Frame;
import androidx.media3.extractor.metadata.id3.PrivFrame;
import androidx.media3.extractor.metadata.id3.TextInformationFrame;
import androidx.media3.extractor.metadata.id3.UrlLinkFrame;
import androidx.media3.exoplayer.source.MediaSourceEventListener;
import androidx.media3.exoplayer.trackselection.MappingTrackSelector;
import androidx.media3.exoplayer.video.VideoRendererEventListener;

import java.text.NumberFormat;
import java.util.Locale;
Expand All @@ -49,6 +52,7 @@
/**
* Logs player events using {@link Log}.
*/
@UnstableApi
public final class EventLogger implements AudioRendererEventListener, VideoRendererEventListener,
MediaSourceEventListener, MetadataOutput {

Expand Down Expand Up @@ -93,7 +97,7 @@ public void onAudioDecoderInitialized(String decoderName, long elapsedRealtimeMs
logger.debug("audioDecoderInitialized [" + getSessionTimeString() + ", " + decoderName + "]");
}

@Override public void onAudioInputFormatChanged(Format format) {
@Override public void onAudioInputFormatChanged(Format format, @Nullable DecoderReuseEvaluation decoderReuseEvaluation) {
logger.debug("audioFormatChanged [" + getSessionTimeString() + ", " + Format.toLogString(format) + "]");
}

Expand All @@ -112,7 +116,7 @@ public void onVideoDecoderInitialized(String decoderName, long elapsedRealtimeMs
logger.debug("videoDecoderInitialized [" + getSessionTimeString() + ", " + decoderName + "]");
}

@Override public void onVideoInputFormatChanged(Format format) {
@Override public void onVideoInputFormatChanged(Format format, @Nullable DecoderReuseEvaluation decoderReuseEvaluation) {
logger.debug("videoFormatChanged [" + getSessionTimeString() + ", " + Format.toLogString(format) + "]");
}

Expand Down
Loading