diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 90269d98..a5abe386 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -96,6 +96,7 @@ dependencies { implementation(libs.androidx.hilt.navigation.compose) implementation(libs.hilt.android) implementation(libs.androidx.media3.session) + implementation(libs.androidx.media3.datasource.okhttp) kapt(libs.hilt.android.compiler) implementation(libs.androidx.core.ktx) diff --git a/app/src/main/java/org/grakovne/lissen/channel/common/ApiClient.kt b/app/src/main/java/org/grakovne/lissen/channel/common/ApiClient.kt index 935e9d1a..c8949654 100644 --- a/app/src/main/java/org/grakovne/lissen/channel/common/ApiClient.kt +++ b/app/src/main/java/org/grakovne/lissen/channel/common/ApiClient.kt @@ -4,6 +4,7 @@ import okhttp3.Interceptor import okhttp3.OkHttpClient import okhttp3.Request import okhttp3.logging.HttpLoggingInterceptor +import org.grakovne.lissen.common.withTrustedCertificates import org.grakovne.lissen.domain.connection.ServerRequestHeader import retrofit2.Retrofit import retrofit2.converter.gson.GsonConverterFactory @@ -15,7 +16,9 @@ class ApiClient( token: String? = null ) { - private val httpClient = OkHttpClient.Builder() + private val httpClient = OkHttpClient + .Builder() + .withTrustedCertificates() .addInterceptor( HttpLoggingInterceptor().apply { level = HttpLoggingInterceptor.Level.NONE diff --git a/app/src/main/java/org/grakovne/lissen/channel/common/BinaryApiClient.kt b/app/src/main/java/org/grakovne/lissen/channel/common/BinaryApiClient.kt index 27cc8ffb..ac5dd8b8 100644 --- a/app/src/main/java/org/grakovne/lissen/channel/common/BinaryApiClient.kt +++ b/app/src/main/java/org/grakovne/lissen/channel/common/BinaryApiClient.kt @@ -3,6 +3,7 @@ package org.grakovne.lissen.channel.common import okhttp3.Interceptor import okhttp3.OkHttpClient import okhttp3.logging.HttpLoggingInterceptor +import org.grakovne.lissen.common.withTrustedCertificates import org.grakovne.lissen.domain.connection.ServerRequestHeader import retrofit2.Retrofit import java.util.concurrent.TimeUnit @@ -13,7 +14,9 @@ class BinaryApiClient( token: String ) { - private val httpClient = OkHttpClient.Builder() + private val httpClient = OkHttpClient + .Builder() + .withTrustedCertificates() .addInterceptor( HttpLoggingInterceptor().apply { level = HttpLoggingInterceptor.Level.NONE diff --git a/app/src/main/java/org/grakovne/lissen/common/CertificateExtension.kt b/app/src/main/java/org/grakovne/lissen/common/CertificateExtension.kt new file mode 100644 index 00000000..c9801f3b --- /dev/null +++ b/app/src/main/java/org/grakovne/lissen/common/CertificateExtension.kt @@ -0,0 +1,36 @@ +package org.grakovne.lissen.common + +import okhttp3.OkHttpClient +import java.security.KeyStore +import javax.net.ssl.SSLContext +import javax.net.ssl.TrustManagerFactory +import javax.net.ssl.TrustManagerFactory.getInstance +import javax.net.ssl.X509TrustManager + +fun OkHttpClient.Builder.withTrustedCertificates(): OkHttpClient.Builder { + return try { + val trustManager = getSystemTrustManager() + val sslContext = getSystemSSLContext(trustManager) + this.sslSocketFactory(sslContext.socketFactory, trustManager) + + this + } catch (ex: Exception) { + this + } +} + +private fun getSystemTrustManager(): X509TrustManager { + val keyStore = KeyStore.getInstance("AndroidCAStore") + keyStore.load(null) + + val trustManagerFactory = getInstance(TrustManagerFactory.getDefaultAlgorithm()) + trustManagerFactory.init(keyStore) + + return trustManagerFactory.trustManagers.first { it is X509TrustManager } as X509TrustManager +} + +private fun getSystemSSLContext(trustManager: X509TrustManager): SSLContext { + val sslContext = SSLContext.getInstance("TLS") + sslContext.init(null, arrayOf(trustManager), null) + return sslContext +} diff --git a/app/src/main/java/org/grakovne/lissen/playback/service/PlaybackService.kt b/app/src/main/java/org/grakovne/lissen/playback/service/PlaybackService.kt index d11d0429..1a643337 100644 --- a/app/src/main/java/org/grakovne/lissen/playback/service/PlaybackService.kt +++ b/app/src/main/java/org/grakovne/lissen/playback/service/PlaybackService.kt @@ -9,7 +9,7 @@ import androidx.media3.common.MediaMetadata import androidx.media3.common.MediaMetadata.PICTURE_TYPE_FRONT_COVER import androidx.media3.common.util.UnstableApi import androidx.media3.datasource.DefaultDataSource -import androidx.media3.datasource.DefaultHttpDataSource +import androidx.media3.datasource.okhttp.OkHttpDataSource import androidx.media3.exoplayer.ExoPlayer import androidx.media3.exoplayer.source.ProgressiveMediaSource import androidx.media3.session.MediaSession @@ -22,7 +22,10 @@ import kotlinx.coroutines.awaitAll import kotlinx.coroutines.cancel import kotlinx.coroutines.launch import kotlinx.coroutines.withContext +import okhttp3.OkHttpClient +import okhttp3.logging.HttpLoggingInterceptor import org.grakovne.lissen.channel.audiobookshelf.common.api.RequestHeadersProvider +import org.grakovne.lissen.common.withTrustedCertificates import org.grakovne.lissen.content.LissenMediaProvider import org.grakovne.lissen.domain.BookFile import org.grakovne.lissen.domain.DetailedItem @@ -228,19 +231,33 @@ class PlaybackService : MediaSessionService() { progress: MediaProgress? ) = seek(chapters, progress?.currentTime) + private fun createOkHttpClient(): OkHttpClient { + return OkHttpClient + .Builder() + .withTrustedCertificates() + .addInterceptor( + HttpLoggingInterceptor().apply { + level = HttpLoggingInterceptor.Level.NONE + } + ) + .build() + } + @OptIn(UnstableApi::class) private fun buildDataSourceFactory(): DefaultDataSource.Factory { val requestHeaders = requestHeadersProvider .fetchRequestHeaders() .associate { it.name to it.value } - val networkDatasourceFactory = DefaultHttpDataSource - .Factory() + val okHttpClient = createOkHttpClient() + + val okHttpDataSourceFactory = OkHttpDataSource + .Factory(okHttpClient) .setDefaultRequestProperties(requestHeaders) return DefaultDataSource.Factory( baseContext, - networkDatasourceFactory + okHttpDataSourceFactory ) } diff --git a/app/src/main/java/org/grakovne/lissen/viewmodel/PlayerViewModel.kt b/app/src/main/java/org/grakovne/lissen/viewmodel/PlayerViewModel.kt index d861bf77..17de0f94 100644 --- a/app/src/main/java/org/grakovne/lissen/viewmodel/PlayerViewModel.kt +++ b/app/src/main/java/org/grakovne/lissen/viewmodel/PlayerViewModel.kt @@ -1,5 +1,6 @@ package org.grakovne.lissen.viewmodel +import android.util.Log import androidx.lifecycle.LiveData import androidx.lifecycle.MediatorLiveData import androidx.lifecycle.MutableLiveData @@ -111,9 +112,17 @@ class PlayerViewModel @Inject constructor( } fun seekTo(chapterPosition: Double) { - val absolutePosition = currentChapterIndex.value - ?.let { chapterIndex -> book.value?.chapters?.get(chapterIndex)?.start } - ?.let { it + chapterPosition } ?: return + val currentIndex = currentChapterIndex.value ?: return + + if (currentIndex < 0) { + Log.w(TAG, "Unable seek to $chapterPosition because there is no chapter") + return + } + + val absolutePosition = currentIndex + .let { chapterIndex -> book.value?.chapters?.get(chapterIndex)?.start } + ?.let { it + chapterPosition } + ?: return mediaRepository.seekTo(absolutePosition) } @@ -164,7 +173,7 @@ class PlayerViewModel @Inject constructor( } companion object { - + private const val TAG = "PlayerViewModel" private const val CURRENT_TRACK_REPLAY_THRESHOLD = 5 } } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 1aa31095..6b42de69 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -31,6 +31,7 @@ roomRuntime = "2.6.1" rules = "1.6.1" runtimeLivedata = "1.7.5" media3Session = "1.4.1" +media3DatasourceOkhttp = "1.4.1" [libraries] accompanist-systemuicontroller = { module = "com.google.accompanist:accompanist-systemuicontroller", version.ref = "accompanistSystemuicontroller" } @@ -80,6 +81,7 @@ material3 = { module = "androidx.compose.material3:material3", version.ref = "ma okhttp = { module = "com.squareup.okhttp3:okhttp", version.ref = "okhttp" } retrofit = { module = "com.squareup.retrofit2:retrofit", version.ref = "retrofit" } androidx-media3-session = { group = "androidx.media3", name = "media3-session", version.ref = "media3Session" } +androidx-media3-datasource-okhttp = { group = "androidx.media3", name = "media3-datasource-okhttp", version.ref = "media3DatasourceOkhttp" } [plugins] android-application = { id = "com.android.application", version.ref = "agp" }