6.5.2 commit
This commit is contained in:
parent
0299b53c8c
commit
c29eb0bf1b
|
@ -31,8 +31,8 @@ android {
|
||||||
testApplicationId "ac.mdiq.podcini.tests"
|
testApplicationId "ac.mdiq.podcini.tests"
|
||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
|
|
||||||
versionCode 3020235
|
versionCode 3020236
|
||||||
versionName "6.5.1"
|
versionName "6.5.2"
|
||||||
|
|
||||||
applicationId "ac.mdiq.podcini.R"
|
applicationId "ac.mdiq.podcini.R"
|
||||||
def commit = ""
|
def commit = ""
|
||||||
|
@ -90,8 +90,8 @@ android {
|
||||||
compose true
|
compose true
|
||||||
}
|
}
|
||||||
|
|
||||||
splits {
|
splits {
|
||||||
abi {
|
abi {
|
||||||
enable true
|
enable true
|
||||||
reset()
|
reset()
|
||||||
include "arm64-v8a" // Specify the ABI you want to split.
|
include "arm64-v8a" // Specify the ABI you want to split.
|
||||||
|
@ -142,11 +142,11 @@ android {
|
||||||
proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard.cfg"
|
proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard.cfg"
|
||||||
resValue "string", "app_name", "Podcini.R"
|
resValue "string", "app_name", "Podcini.R"
|
||||||
resValue "string", "provider_authority", "ac.mdiq.podcini.R.provider"
|
resValue "string", "provider_authority", "ac.mdiq.podcini.R.provider"
|
||||||
|
// debuggable false
|
||||||
vcsInfo.include false
|
vcsInfo.include false
|
||||||
minifyEnabled true
|
minifyEnabled true
|
||||||
shrinkResources true
|
shrinkResources true
|
||||||
signingConfig signingConfigs.releaseConfig
|
signingConfig signingConfigs.releaseConfig
|
||||||
proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard.cfg"
|
|
||||||
}
|
}
|
||||||
debug {
|
debug {
|
||||||
resValue "string", "app_name", "Podcini.R Debug"
|
resValue "string", "app_name", "Podcini.R Debug"
|
||||||
|
@ -161,8 +161,8 @@ android {
|
||||||
def buildType = variant.buildType.name
|
def buildType = variant.buildType.name
|
||||||
def versionName = variant.versionName
|
def versionName = variant.versionName
|
||||||
def flavorName = variant.flavorName ?: ""
|
def flavorName = variant.flavorName ?: ""
|
||||||
def abiName = output.getFilter(com.android.build.OutputFile.ABI) ?: "universal"
|
def abiName = output.getFilter(com.android.build.OutputFile.ABI) ?: ""
|
||||||
outputFileName = "${applicationName}_${flavorName}_${buildType}_${versionName}_${abiName}.apk"
|
outputFileName = "${applicationName}_${buildType}_${flavorName}_${versionName}_${abiName}.apk"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
androidResources {
|
androidResources {
|
||||||
|
|
|
@ -1,17 +1,21 @@
|
||||||
-dontobfuscate
|
-dontobfuscate
|
||||||
-renamesourcefileattribute SourceFile
|
|
||||||
-keepattributes SourceFile,LineNumberTable
|
#-renamesourcefileattribute SourceFile
|
||||||
|
-keepattributes Exceptions, SourceFile,LineNumberTable
|
||||||
-optimizations !code/allocation/variable
|
-optimizations !code/allocation/variable
|
||||||
-optimizationpasses 5
|
-optimizationpasses 5
|
||||||
|
|
||||||
## Rules for VistaGuide
|
## Rules for VistaGuide
|
||||||
|
# -keepclassmembers class ac.mdiq.vista** {*;}
|
||||||
-keep class ac.mdiq.vista.extractor.timeago.patterns.** { *; }
|
-keep class ac.mdiq.vista.extractor.timeago.patterns.** { *; }
|
||||||
-keep class org.mozilla.javascript.** { *; }
|
-keep class org.mozilla.javascript.** { *; }
|
||||||
-keep class org.mozilla.classfile.ClassFileWriter
|
-keep class org.mozilla.classfile.ClassFileWriter
|
||||||
-keep class java.beans.**
|
|
||||||
-dontwarn org.mozilla.javascript.tools.**
|
-dontwarn org.mozilla.javascript.tools.**
|
||||||
|
-keep class java.beans.**
|
||||||
-dontwarn java.beans.**
|
-dontwarn java.beans.**
|
||||||
|
|
||||||
|
-keep class ac.mdiq.vista.settings.notifications.** { *; }
|
||||||
|
|
||||||
-allowaccessmodification
|
-allowaccessmodification
|
||||||
-dontskipnonpubliclibraryclassmembers
|
-dontskipnonpubliclibraryclassmembers
|
||||||
|
|
||||||
|
@ -19,8 +23,6 @@
|
||||||
# Without this, methods only used in tests are removed and break tests.
|
# Without this, methods only used in tests are removed and break tests.
|
||||||
-keep class ac.mdiq.podcini**
|
-keep class ac.mdiq.podcini**
|
||||||
-keepclassmembers class ac.mdiq.podcini** {*;}
|
-keepclassmembers class ac.mdiq.podcini** {*;}
|
||||||
# -keep class de.test.podcini**
|
|
||||||
# -keepclassmembers class de.test.podcini** {*;}
|
|
||||||
|
|
||||||
# Keep methods used in tests.
|
# Keep methods used in tests.
|
||||||
# This is only needed when running tests with proguard enabled.
|
# This is only needed when running tests with proguard enabled.
|
||||||
|
@ -40,9 +42,6 @@
|
||||||
-dontwarn okhttp3.**
|
-dontwarn okhttp3.**
|
||||||
-dontwarn okio.**
|
-dontwarn okio.**
|
||||||
|
|
||||||
# android-iconify
|
|
||||||
#-keep class com.joanzapata.** { *; }
|
|
||||||
|
|
||||||
#### Proguard rules for fyyd client
|
#### Proguard rules for fyyd client
|
||||||
# Retrofit 2.0
|
# Retrofit 2.0
|
||||||
-dontwarn retrofit2.**
|
-dontwarn retrofit2.**
|
||||||
|
@ -62,10 +61,10 @@
|
||||||
####
|
####
|
||||||
|
|
||||||
# awaitility
|
# awaitility
|
||||||
-dontwarn java.beans.BeanInfo
|
# -dontwarn java.beans.BeanInfo
|
||||||
-dontwarn java.beans.Introspector
|
# -dontwarn java.beans.Introspector
|
||||||
-dontwarn java.beans.IntrospectionException
|
# -dontwarn java.beans.IntrospectionException
|
||||||
-dontwarn java.beans.PropertyDescriptor
|
# -dontwarn java.beans.PropertyDescriptor
|
||||||
-dontwarn java.lang.management.ManagementFactory
|
-dontwarn java.lang.management.ManagementFactory
|
||||||
-dontwarn java.lang.management.ThreadInfo
|
-dontwarn java.lang.management.ThreadInfo
|
||||||
-dontwarn java.lang.management.ThreadMXBean
|
-dontwarn java.lang.management.ThreadMXBean
|
||||||
|
|
|
@ -45,8 +45,8 @@
|
||||||
android:supportsRtl="true"
|
android:supportsRtl="true"
|
||||||
android:logo="@mipmap/ic_launcher"
|
android:logo="@mipmap/ic_launcher"
|
||||||
android:resizeableActivity="true"
|
android:resizeableActivity="true"
|
||||||
android:allowAudioPlaybackCapture="true"
|
android:allowAudioPlaybackCapture="true">
|
||||||
android:networkSecurityConfig="@xml/network_security_config">
|
<!-- android:networkSecurityConfig="@xml/network_security_config">-->
|
||||||
|
|
||||||
<service android:name=".playback.service.PlaybackService"
|
<service android:name=".playback.service.PlaybackService"
|
||||||
android:foregroundServiceType="mediaPlayback"
|
android:foregroundServiceType="mediaPlayback"
|
||||||
|
|
|
@ -8,10 +8,12 @@ import ac.mdiq.vista.extractor.downloader.Response
|
||||||
import ac.mdiq.vista.extractor.exceptions.ReCaptchaException
|
import ac.mdiq.vista.extractor.exceptions.ReCaptchaException
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
|
import okhttp3.Cache
|
||||||
import okhttp3.MediaType.Companion.toMediaTypeOrNull
|
import okhttp3.MediaType.Companion.toMediaTypeOrNull
|
||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
import okhttp3.Request.Builder
|
import okhttp3.Request.Builder
|
||||||
import okhttp3.RequestBody
|
import okhttp3.RequestBody
|
||||||
|
import java.io.File
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
|
@ -22,7 +24,7 @@ class VistaDownloaderImpl private constructor(builder: OkHttpClient.Builder) : D
|
||||||
private val mCookies: MutableMap<String, String> = HashMap()
|
private val mCookies: MutableMap<String, String> = HashMap()
|
||||||
private val client: OkHttpClient = builder
|
private val client: OkHttpClient = builder
|
||||||
.readTimeout(30, TimeUnit.SECONDS)
|
.readTimeout(30, TimeUnit.SECONDS)
|
||||||
// .cache(new Cache(new File(context.getExternalCacheDir(), "okhttp"), 16 * 1024 * 1024))
|
// .cache(Cache(File(context.getExternalCacheDir(), "okhttp"), 16 * 1024 * 1024))
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
private fun getCookies(url: String): String {
|
private fun getCookies(url: String): String {
|
||||||
|
@ -107,17 +109,22 @@ class VistaDownloaderImpl private constructor(builder: OkHttpClient.Builder) : D
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val response = client.newCall(requestBuilder.build()).execute()
|
try {
|
||||||
if (response.code == 429) {
|
val response = client.newCall(requestBuilder.build()).execute()
|
||||||
response.close()
|
if (response.code == 429) {
|
||||||
throw ReCaptchaException("reCaptcha Challenge requested", url)
|
response.close()
|
||||||
|
throw ReCaptchaException("reCaptcha Challenge requested", url)
|
||||||
|
}
|
||||||
|
|
||||||
|
val body = response.body
|
||||||
|
val responseBodyToReturn: String? = body?.string()
|
||||||
|
|
||||||
|
val latestUrl = response.request.url.toString()
|
||||||
|
return Response(response.code, response.message, response.headers.toMultimap(), responseBodyToReturn, latestUrl)
|
||||||
|
} catch (e: Throwable) {
|
||||||
|
e.printStackTrace()
|
||||||
|
throw IOException("Something is wrong ${e.message}")
|
||||||
}
|
}
|
||||||
|
|
||||||
val body = response.body
|
|
||||||
val responseBodyToReturn: String? = body?.string()
|
|
||||||
|
|
||||||
val latestUrl = response.request.url.toString()
|
|
||||||
return Response(response.code, response.message, response.headers.toMultimap(), responseBodyToReturn, latestUrl)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
|
@ -101,9 +101,7 @@ object PodciniHttpClient {
|
||||||
if (!proxyConfig!!.username.isNullOrEmpty() && proxyConfig!!.password != null) {
|
if (!proxyConfig!!.username.isNullOrEmpty() && proxyConfig!!.password != null) {
|
||||||
builder.proxyAuthenticator { _: Route?, response: Response ->
|
builder.proxyAuthenticator { _: Route?, response: Response ->
|
||||||
val credentials = basic(proxyConfig!!.username!!, proxyConfig!!.password!!)
|
val credentials = basic(proxyConfig!!.username!!, proxyConfig!!.password!!)
|
||||||
response.request.newBuilder()
|
response.request.newBuilder().header("Proxy-Authorization", credentials).build()
|
||||||
.header("Proxy-Authorization", credentials)
|
|
||||||
.build()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -126,9 +124,7 @@ object PodciniHttpClient {
|
||||||
@Throws(IOException::class)
|
@Throws(IOException::class)
|
||||||
override fun intercept(chain: Chain): Response {
|
override fun intercept(chain: Chain): Response {
|
||||||
TrafficStats.setThreadStatsTag(Thread.currentThread().id.toInt())
|
TrafficStats.setThreadStatsTag(Thread.currentThread().id.toInt())
|
||||||
return chain.proceed(chain.request().newBuilder()
|
return chain.proceed(chain.request().newBuilder().header("User-Agent", ClientConfig.USER_AGENT?:"").build())
|
||||||
.header("User-Agent", ClientConfig.USER_AGENT?:"")
|
|
||||||
.build())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package ac.mdiq.podcini.net.feed.discovery
|
package ac.mdiq.podcini.net.feed.discovery
|
||||||
|
|
||||||
|
import ac.mdiq.podcini.util.Logd
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.async
|
import kotlinx.coroutines.async
|
||||||
|
@ -21,13 +22,9 @@ class CombinedSearcher : PodcastSearcher {
|
||||||
try {
|
try {
|
||||||
val results = searcher.search(query)
|
val results = searcher.search(query)
|
||||||
searchResults[index] = results
|
searchResults[index] = results
|
||||||
} catch (e: Throwable) {
|
} catch (e: Throwable) { Logd(TAG, Log.getStackTraceString(e)) }
|
||||||
Log.d(TAG, Log.getStackTraceString(e))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
} else null
|
||||||
null
|
|
||||||
}
|
|
||||||
}.filterNotNull() // Remove null jobs
|
}.filterNotNull() // Remove null jobs
|
||||||
// Wait for all search jobs to complete
|
// Wait for all search jobs to complete
|
||||||
searchJobs.awaitAll()
|
searchJobs.awaitAll()
|
||||||
|
|
|
@ -391,11 +391,7 @@ class GpodnetService(private val httpClient: OkHttpClient, baseHosturl: String?,
|
||||||
throw GpodnetServiceAuthenticationException("Wrong username or password")
|
throw GpodnetServiceAuthenticationException("Wrong username or password")
|
||||||
} else {
|
} else {
|
||||||
if (BuildConfig.DEBUG) {
|
if (BuildConfig.DEBUG) {
|
||||||
try {
|
try { Logd(TAG, response.body!!.string()) } catch (e: IOException) { e.printStackTrace() }
|
||||||
Logd(TAG, response.body!!.string())
|
|
||||||
} catch (e: IOException) {
|
|
||||||
e.printStackTrace()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (responseCode >= 500) {
|
if (responseCode >= 500) {
|
||||||
throw GpodnetServiceBadStatusCodeException("Gpodder.net is currently unavailable (code " + responseCode + ")", responseCode)
|
throw GpodnetServiceBadStatusCodeException("Gpodder.net is currently unavailable (code " + responseCode + ")", responseCode)
|
||||||
|
|
|
@ -39,7 +39,9 @@ object UrlChecker {
|
||||||
// prepareUrl(removedWebsite)
|
// prepareUrl(removedWebsite)
|
||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
!(lowerCaseUrl.startsWith("http://") || lowerCaseUrl.startsWith("https://")) -> "http://$url"
|
// TODO: test
|
||||||
|
// !(lowerCaseUrl.startsWith("http://") || lowerCaseUrl.startsWith("https://")) -> "http://$url"
|
||||||
|
!(lowerCaseUrl.startsWith("http://") || lowerCaseUrl.startsWith("https://")) -> "https://$url"
|
||||||
else -> url
|
else -> url
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
package ac.mdiq.podcini.playback.service
|
package ac.mdiq.podcini.playback.service
|
||||||
|
|
||||||
import ac.mdiq.podcini.BuildConfig
|
|
||||||
import ac.mdiq.podcini.R
|
import ac.mdiq.podcini.R
|
||||||
import ac.mdiq.podcini.net.download.service.HttpCredentialEncoder
|
import ac.mdiq.podcini.net.download.service.HttpCredentialEncoder
|
||||||
import ac.mdiq.podcini.net.download.service.PodciniHttpClient
|
import ac.mdiq.podcini.net.download.service.PodciniHttpClient
|
||||||
|
@ -20,11 +19,11 @@ import ac.mdiq.podcini.storage.model.Feed
|
||||||
import ac.mdiq.podcini.storage.model.MediaType
|
import ac.mdiq.podcini.storage.model.MediaType
|
||||||
import ac.mdiq.podcini.storage.model.Playable
|
import ac.mdiq.podcini.storage.model.Playable
|
||||||
import ac.mdiq.podcini.storage.utils.EpisodeUtil
|
import ac.mdiq.podcini.storage.utils.EpisodeUtil
|
||||||
import ac.mdiq.podcini.util.Logd
|
|
||||||
import ac.mdiq.podcini.util.config.ClientConfig
|
|
||||||
import ac.mdiq.podcini.util.EventFlow
|
import ac.mdiq.podcini.util.EventFlow
|
||||||
import ac.mdiq.podcini.util.FlowEvent
|
import ac.mdiq.podcini.util.FlowEvent
|
||||||
import ac.mdiq.podcini.util.FlowEvent.PlayEvent.Action
|
import ac.mdiq.podcini.util.FlowEvent.PlayEvent.Action
|
||||||
|
import ac.mdiq.podcini.util.Logd
|
||||||
|
import ac.mdiq.podcini.util.config.ClientConfig
|
||||||
import ac.mdiq.vista.extractor.MediaFormat
|
import ac.mdiq.vista.extractor.MediaFormat
|
||||||
import ac.mdiq.vista.extractor.Vista
|
import ac.mdiq.vista.extractor.Vista
|
||||||
import ac.mdiq.vista.extractor.stream.*
|
import ac.mdiq.vista.extractor.stream.*
|
||||||
|
@ -56,7 +55,6 @@ import androidx.media3.exoplayer.source.ProgressiveMediaSource
|
||||||
import androidx.media3.exoplayer.trackselection.DefaultTrackSelector
|
import androidx.media3.exoplayer.trackselection.DefaultTrackSelector
|
||||||
import androidx.media3.exoplayer.trackselection.DefaultTrackSelector.SelectionOverride
|
import androidx.media3.exoplayer.trackselection.DefaultTrackSelector.SelectionOverride
|
||||||
import androidx.media3.exoplayer.trackselection.ExoTrackSelection
|
import androidx.media3.exoplayer.trackselection.ExoTrackSelection
|
||||||
import androidx.media3.exoplayer.util.EventLogger
|
|
||||||
import androidx.media3.extractor.DefaultExtractorsFactory
|
import androidx.media3.extractor.DefaultExtractorsFactory
|
||||||
import androidx.media3.extractor.mp3.Mp3Extractor
|
import androidx.media3.extractor.mp3.Mp3Extractor
|
||||||
import androidx.media3.ui.DefaultTrackNameProvider
|
import androidx.media3.ui.DefaultTrackNameProvider
|
||||||
|
@ -67,8 +65,6 @@ import java.lang.Runnable
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import java.util.concurrent.CountDownLatch
|
import java.util.concurrent.CountDownLatch
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
import kotlin.collections.ArrayList
|
|
||||||
import kotlin.collections.HashMap
|
|
||||||
import kotlin.concurrent.Volatile
|
import kotlin.concurrent.Volatile
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -186,28 +182,29 @@ class LocalMediaPlayer(context: Context, callback: MediaPlayerCallback) : MediaP
|
||||||
@Throws(IllegalArgumentException::class, IllegalStateException::class)
|
@Throws(IllegalArgumentException::class, IllegalStateException::class)
|
||||||
private fun setDataSource(metadata: MediaMetadata, media: EpisodeMedia) {
|
private fun setDataSource(metadata: MediaMetadata, media: EpisodeMedia) {
|
||||||
val url = media.getStreamUrl() ?: return
|
val url = media.getStreamUrl() ?: return
|
||||||
Logd(TAG, "setDataSource1: $url")
|
|
||||||
val preferences = media.episodeOrFetch()?.feed?.preferences
|
val preferences = media.episodeOrFetch()?.feed?.preferences
|
||||||
val user = preferences?.username
|
val user = preferences?.username
|
||||||
val password = preferences?.password
|
val password = preferences?.password
|
||||||
if (media.episode?.feed?.type == Feed.FeedType.YOUTUBE.name) {
|
if (media.episode?.feed?.type == Feed.FeedType.YOUTUBE.name) {
|
||||||
Logd(TAG, "setDataSource setting for YouTube source")
|
Logd(TAG, "setDataSource1 setting for YouTube source")
|
||||||
try {
|
try {
|
||||||
val streamInfo = StreamInfo.getInfo(Vista.getService(0), url)
|
val vService = Vista.getService(0)
|
||||||
|
val streamInfo = StreamInfo.getInfo(vService, url)
|
||||||
val audioStreamsList = getFilteredAudioStreams(streamInfo.audioStreams)
|
val audioStreamsList = getFilteredAudioStreams(streamInfo.audioStreams)
|
||||||
|
Logd(TAG, "setDataSource1 got ${audioStreamsList.size}")
|
||||||
val audioIndex = if (isNetworkRestricted) 0 else audioStreamsList.size - 1
|
val audioIndex = if (isNetworkRestricted) 0 else audioStreamsList.size - 1
|
||||||
val audioStream = audioStreamsList[audioIndex]
|
val audioStream = audioStreamsList[audioIndex]
|
||||||
|
Logd(TAG, "setDataSource1 use audio quality: ${audioStream.bitrate}")
|
||||||
val aSource = DefaultMediaSourceFactory(context).createMediaSource(MediaItem.Builder().setTag(metadata).setUri(Uri.parse(audioStream.content)).build())
|
val aSource = DefaultMediaSourceFactory(context).createMediaSource(MediaItem.Builder().setTag(metadata).setUri(Uri.parse(audioStream.content)).build())
|
||||||
Logd(TAG, "setDataSource use audio quality: ${audioStream.bitrate}")
|
|
||||||
if (media.episode?.feed?.preferences?.playAudioOnly != true) {
|
if (media.episode?.feed?.preferences?.playAudioOnly != true) {
|
||||||
Logd(TAG, "setDataSource result: $streamInfo")
|
Logd(TAG, "setDataSource1 result: $streamInfo")
|
||||||
Logd(TAG, "setDataSource videoStreams: ${streamInfo.videoStreams.size} videoOnlyStreams: ${streamInfo.videoOnlyStreams.size} audioStreams: ${streamInfo.audioStreams.size}")
|
Logd(TAG, "setDataSource1 videoStreams: ${streamInfo.videoStreams.size} videoOnlyStreams: ${streamInfo.videoOnlyStreams.size} audioStreams: ${streamInfo.audioStreams.size}")
|
||||||
val videoStreamsList = getSortedStreamVideosList(streamInfo.videoStreams, streamInfo.videoOnlyStreams, true, true)
|
val videoStreamsList = getSortedStreamVideosList(streamInfo.videoStreams, streamInfo.videoOnlyStreams, true, true)
|
||||||
val videoIndex = 0
|
val videoIndex = 0
|
||||||
val videoStream = videoStreamsList[videoIndex]
|
val videoStream = videoStreamsList[videoIndex]
|
||||||
Logd(TAG, "setDataSource use video quality: ${videoStream.resolution}")
|
Logd(TAG, "setDataSource1 use video quality: ${videoStream.resolution}")
|
||||||
val vSource = DefaultMediaSourceFactory(context).createMediaSource(MediaItem.Builder().setTag(metadata).setUri(Uri.parse(videoStream.content)).build())
|
val vSource = DefaultMediaSourceFactory(context).createMediaSource(MediaItem.Builder().setTag(metadata).setUri(Uri.parse(videoStream.content)).build())
|
||||||
val mediaSources: MutableList<MediaSource> = java.util.ArrayList()
|
val mediaSources: MutableList<MediaSource> = ArrayList()
|
||||||
mediaSources.add(vSource)
|
mediaSources.add(vSource)
|
||||||
mediaSources.add(aSource)
|
mediaSources.add(aSource)
|
||||||
mediaSource = MergingMediaSource(true, *mediaSources.toTypedArray<MediaSource>())
|
mediaSource = MergingMediaSource(true, *mediaSources.toTypedArray<MediaSource>())
|
||||||
|
@ -215,9 +212,9 @@ class LocalMediaPlayer(context: Context, callback: MediaPlayerCallback) : MediaP
|
||||||
} else mediaSource = aSource
|
} else mediaSource = aSource
|
||||||
mediaItem = mediaSource?.mediaItem
|
mediaItem = mediaSource?.mediaItem
|
||||||
setSourceCredentials(user, password)
|
setSourceCredentials(user, password)
|
||||||
} catch (throwable: Throwable) { Logd(TAG, "setDataSource error: ${throwable.message}") }
|
} catch (throwable: Throwable) { Log.e(TAG, "setDataSource1 error: ${throwable.message}") }
|
||||||
} else {
|
} else {
|
||||||
Logd(TAG, "setDataSource setting for Podcast source")
|
Logd(TAG, "setDataSource1 setting for Podcast source")
|
||||||
setDataSource(metadata, url,user, password)
|
setDataSource(metadata, url,user, password)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -255,15 +252,15 @@ class LocalMediaPlayer(context: Context, callback: MediaPlayerCallback) : MediaP
|
||||||
* streams and normal video streams are available
|
* streams and normal video streams are available
|
||||||
* @return the sorted list
|
* @return the sorted list
|
||||||
*/
|
*/
|
||||||
fun getSortedStreamVideosList(videoStreams: List<VideoStream>?, videoOnlyStreams: List<VideoStream>?, ascendingOrder: Boolean,
|
private fun getSortedStreamVideosList(videoStreams: List<VideoStream>?, videoOnlyStreams: List<VideoStream>?, ascendingOrder: Boolean,
|
||||||
preferVideoOnlyStreams: Boolean): List<VideoStream> {
|
preferVideoOnlyStreams: Boolean): List<VideoStream> {
|
||||||
val videoStreamsOrdered = if (preferVideoOnlyStreams) listOf(videoStreams, videoOnlyStreams) else listOf(videoOnlyStreams, videoStreams)
|
val videoStreamsOrdered = if (preferVideoOnlyStreams) listOf(videoStreams, videoOnlyStreams) else listOf(videoOnlyStreams, videoStreams)
|
||||||
val allInitialStreams = videoStreamsOrdered.filterNotNull().flatMap { it }.toList()
|
val allInitialStreams = videoStreamsOrdered.filterNotNull().flatten().toList()
|
||||||
val comparator = compareBy<VideoStream> { it.resolution.toResolutionValue() }
|
val comparator = compareBy<VideoStream> { it.resolution.toResolutionValue() }
|
||||||
return if (ascendingOrder) allInitialStreams.sortedWith(comparator) else { allInitialStreams.sortedWith(comparator.reversed()) }
|
return if (ascendingOrder) allInitialStreams.sortedWith(comparator) else { allInitialStreams.sortedWith(comparator.reversed()) }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun String.toResolutionValue(): Int {
|
private fun String.toResolutionValue(): Int {
|
||||||
val match = Regex("(\\d+)p|(\\d+)k").find(this)
|
val match = Regex("(\\d+)p|(\\d+)k").find(this)
|
||||||
return when {
|
return when {
|
||||||
match?.groupValues?.get(1) != null -> match.groupValues[1].toInt()
|
match?.groupValues?.get(1) != null -> match.groupValues[1].toInt()
|
||||||
|
@ -272,7 +269,7 @@ class LocalMediaPlayer(context: Context, callback: MediaPlayerCallback) : MediaP
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getFilteredAudioStreams(audioStreams: List<AudioStream>?): List<AudioStream> {
|
private fun getFilteredAudioStreams(audioStreams: List<AudioStream>?): List<AudioStream> {
|
||||||
if (audioStreams == null) return listOf()
|
if (audioStreams == null) return listOf()
|
||||||
val collectedStreams = mutableSetOf<AudioStream>()
|
val collectedStreams = mutableSetOf<AudioStream>()
|
||||||
for (stream in audioStreams) {
|
for (stream in audioStreams) {
|
||||||
|
@ -281,7 +278,7 @@ class LocalMediaPlayer(context: Context, callback: MediaPlayerCallback) : MediaP
|
||||||
continue
|
continue
|
||||||
collectedStreams.add(stream)
|
collectedStreams.add(stream)
|
||||||
}
|
}
|
||||||
return collectedStreams.toList().sortedWith(compareBy<AudioStream> { it.bitrate })
|
return collectedStreams.toList().sortedWith(compareBy { it.bitrate })
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -355,7 +352,8 @@ class LocalMediaPlayer(context: Context, callback: MediaPlayerCallback) : MediaP
|
||||||
if (streamurl != null) {
|
if (streamurl != null) {
|
||||||
val media = curMedia
|
val media = curMedia
|
||||||
if (media is EpisodeMedia) {
|
if (media is EpisodeMedia) {
|
||||||
setDataSource(metadata, media)
|
val deferred = CoroutineScope(Dispatchers.IO).async { setDataSource(metadata, media) }
|
||||||
|
runBlocking { deferred.await() }
|
||||||
// val preferences = media.episodeOrFetch()?.feed?.preferences
|
// val preferences = media.episodeOrFetch()?.feed?.preferences
|
||||||
// setDataSource(metadata, streamurl, preferences?.username, preferences?.password)
|
// setDataSource(metadata, streamurl, preferences?.username, preferences?.password)
|
||||||
} else setDataSource(metadata, streamurl, null, null)
|
} else setDataSource(metadata, streamurl, null, null)
|
||||||
|
@ -381,13 +379,12 @@ class LocalMediaPlayer(context: Context, callback: MediaPlayerCallback) : MediaP
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
setPlayerStatus(PlayerStatus.ERROR, null)
|
setPlayerStatus(PlayerStatus.ERROR, null)
|
||||||
EventFlow.postStickyEvent(FlowEvent.PlayerErrorEvent(e.localizedMessage ?: ""))
|
EventFlow.postStickyEvent(FlowEvent.PlayerErrorEvent(e.localizedMessage ?: ""))
|
||||||
} finally {
|
} finally { }
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun resume() {
|
override fun resume() {
|
||||||
if (status == PlayerStatus.PAUSED || status == PlayerStatus.PREPARED) {
|
if (status == PlayerStatus.PAUSED || status == PlayerStatus.PREPARED) {
|
||||||
Log.d(TAG, "Resuming/Starting playback")
|
Logd(TAG, "Resuming/Starting playback")
|
||||||
acquireWifiLockIfNecessary()
|
acquireWifiLockIfNecessary()
|
||||||
setPlaybackParams(getCurrentPlaybackSpeed(curMedia), UserPreferences.isSkipSilence)
|
setPlaybackParams(getCurrentPlaybackSpeed(curMedia), UserPreferences.isSkipSilence)
|
||||||
setVolume(1.0f, 1.0f)
|
setVolume(1.0f, 1.0f)
|
||||||
|
@ -441,9 +438,7 @@ class LocalMediaPlayer(context: Context, callback: MediaPlayerCallback) : MediaP
|
||||||
releaseWifiLockIfNecessary()
|
releaseWifiLockIfNecessary()
|
||||||
when {
|
when {
|
||||||
curMedia != null -> playMediaObject(curMedia!!, isStreaming, startWhenPrepared.get(), prepareImmediately = false, true)
|
curMedia != null -> playMediaObject(curMedia!!, isStreaming, startWhenPrepared.get(), prepareImmediately = false, true)
|
||||||
else -> {
|
else -> Logd(TAG, "Call to reinit: media and mediaPlayer were null, ignored")
|
||||||
Logd(TAG, "Call to reinit: media and mediaPlayer were null, ignored")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -466,11 +461,7 @@ class LocalMediaPlayer(context: Context, callback: MediaPlayerCallback) : MediaP
|
||||||
PlayerStatus.PLAYING, PlayerStatus.PAUSED, PlayerStatus.PREPARED -> {
|
PlayerStatus.PLAYING, PlayerStatus.PAUSED, PlayerStatus.PREPARED -> {
|
||||||
Logd(TAG, "seekTo() called $t")
|
Logd(TAG, "seekTo() called $t")
|
||||||
if (seekLatch != null && seekLatch!!.count > 0) {
|
if (seekLatch != null && seekLatch!!.count > 0) {
|
||||||
try {
|
try { seekLatch!!.await(3, TimeUnit.SECONDS) } catch (e: InterruptedException) { Log.e(TAG, Log.getStackTraceString(e)) }
|
||||||
seekLatch!!.await(3, TimeUnit.SECONDS)
|
|
||||||
} catch (e: InterruptedException) {
|
|
||||||
Log.e(TAG, Log.getStackTraceString(e))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
seekLatch = CountDownLatch(1)
|
seekLatch = CountDownLatch(1)
|
||||||
statusBeforeSeeking = status
|
statusBeforeSeeking = status
|
||||||
|
@ -479,11 +470,7 @@ class LocalMediaPlayer(context: Context, callback: MediaPlayerCallback) : MediaP
|
||||||
if (curMedia != null) EventFlow.postEvent(FlowEvent.PlaybackPositionEvent(curMedia, t, curMedia!!.getDuration()))
|
if (curMedia != null) EventFlow.postEvent(FlowEvent.PlaybackPositionEvent(curMedia, t, curMedia!!.getDuration()))
|
||||||
audioSeekCompleteListener?.run()
|
audioSeekCompleteListener?.run()
|
||||||
if (statusBeforeSeeking == PlayerStatus.PREPARED) curMedia?.setPosition(t)
|
if (statusBeforeSeeking == PlayerStatus.PREPARED) curMedia?.setPosition(t)
|
||||||
try {
|
try { seekLatch!!.await(3, TimeUnit.SECONDS) } catch (e: InterruptedException) { Log.e(TAG, Log.getStackTraceString(e)) }
|
||||||
seekLatch!!.await(3, TimeUnit.SECONDS)
|
|
||||||
} catch (e: InterruptedException) {
|
|
||||||
Log.e(TAG, Log.getStackTraceString(e))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
PlayerStatus.INITIALIZED -> {
|
PlayerStatus.INITIALIZED -> {
|
||||||
curMedia?.setPosition(t)
|
curMedia?.setPosition(t)
|
||||||
|
@ -729,7 +716,7 @@ class LocalMediaPlayer(context: Context, callback: MediaPlayerCallback) : MediaP
|
||||||
override fun onIsPlayingChanged(isPlaying: Boolean) {
|
override fun onIsPlayingChanged(isPlaying: Boolean) {
|
||||||
val stat = if (isPlaying) PlayerStatus.PLAYING else PlayerStatus.PAUSED
|
val stat = if (isPlaying) PlayerStatus.PLAYING else PlayerStatus.PAUSED
|
||||||
setPlayerStatus(stat, curMedia)
|
setPlayerStatus(stat, curMedia)
|
||||||
Log.d(TAG, "onIsPlayingChanged $isPlaying")
|
Logd(TAG, "onIsPlayingChanged $isPlaying")
|
||||||
}
|
}
|
||||||
override fun onPlayerError(error: PlaybackException) {
|
override fun onPlayerError(error: PlaybackException) {
|
||||||
Logd(TAG, "onPlayerError ${error.message}")
|
Logd(TAG, "onPlayerError ${error.message}")
|
||||||
|
@ -808,7 +795,7 @@ class LocalMediaPlayer(context: Context, callback: MediaPlayerCallback) : MediaP
|
||||||
.setAudioOffloadPreferences(audioOffloadPreferences)
|
.setAudioOffloadPreferences(audioOffloadPreferences)
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
if (BuildConfig.DEBUG) exoPlayer!!.addAnalyticsListener(EventLogger())
|
// if (BuildConfig.DEBUG) exoPlayer!!.addAnalyticsListener(EventLogger())
|
||||||
|
|
||||||
if (exoplayerListener != null) {
|
if (exoplayerListener != null) {
|
||||||
exoPlayer?.removeListener(exoplayerListener!!)
|
exoPlayer?.removeListener(exoplayerListener!!)
|
||||||
|
|
|
@ -19,7 +19,6 @@ import android.util.Log
|
||||||
class PowerConnectionReceiver : BroadcastReceiver() {
|
class PowerConnectionReceiver : BroadcastReceiver() {
|
||||||
@UnstableApi override fun onReceive(context: Context, intent: Intent) {
|
@UnstableApi override fun onReceive(context: Context, intent: Intent) {
|
||||||
val action = intent.action
|
val action = intent.action
|
||||||
|
|
||||||
Log.d(TAG, "onReceive charging intent: $action")
|
Log.d(TAG, "onReceive charging intent: $action")
|
||||||
|
|
||||||
ClientConfigurator.initialize(context)
|
ClientConfigurator.initialize(context)
|
||||||
|
|
|
@ -152,11 +152,11 @@ object Feeds {
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getFeed(feedId: Long, copy: Boolean = false): Feed? {
|
fun getFeed(feedId: Long, copy: Boolean = false): Feed? {
|
||||||
if (BuildConfig.DEBUG) {
|
// if (BuildConfig.DEBUG) {
|
||||||
val stackTrace = Thread.currentThread().stackTrace
|
// val stackTrace = Thread.currentThread().stackTrace
|
||||||
val caller = if (stackTrace.size > 3) stackTrace[3] else null
|
// val caller = if (stackTrace.size > 3) stackTrace[3] else null
|
||||||
Logd(TAG, "${caller?.className}.${caller?.methodName} getFeed called")
|
// Logd(TAG, "${caller?.className}.${caller?.methodName} getFeed called")
|
||||||
}
|
// }
|
||||||
val f = realm.query(Feed::class, "id == $feedId").first().find()
|
val f = realm.query(Feed::class, "id == $feedId").first().find()
|
||||||
return if (f != null) {
|
return if (f != null) {
|
||||||
if (copy) realm.copyFromRealm(f)
|
if (copy) realm.copyFromRealm(f)
|
||||||
|
|
|
@ -689,9 +689,7 @@ import java.util.concurrent.Semaphore
|
||||||
runOnIOScope {
|
runOnIOScope {
|
||||||
val feed_ = realm.query(Feed::class, "id == ${feed.id}").first().find()
|
val feed_ = realm.query(Feed::class, "id == ${feed.id}").first().find()
|
||||||
if (feed_ != null) {
|
if (feed_ != null) {
|
||||||
upsert(feed_) {
|
upsert(feed_) { it.sortOrder = sortOrder }
|
||||||
it.sortOrder = sortOrder
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -737,6 +735,10 @@ import java.util.concurrent.Semaphore
|
||||||
private const val ARGUMENT_FEED_ID = "argument.ac.mdiq.podcini.feed_id"
|
private const val ARGUMENT_FEED_ID = "argument.ac.mdiq.podcini.feed_id"
|
||||||
private const val KEY_UP_ARROW = "up_arrow"
|
private const val KEY_UP_ARROW = "up_arrow"
|
||||||
|
|
||||||
|
var tts: TextToSpeech? = null
|
||||||
|
var ttsReady = false
|
||||||
|
var ttsWorking = false
|
||||||
|
|
||||||
fun newInstance(feedId: Long): FeedEpisodesFragment {
|
fun newInstance(feedId: Long): FeedEpisodesFragment {
|
||||||
val i = FeedEpisodesFragment()
|
val i = FeedEpisodesFragment()
|
||||||
val b = Bundle()
|
val b = Bundle()
|
||||||
|
@ -744,9 +746,5 @@ import java.util.concurrent.Semaphore
|
||||||
i.arguments = b
|
i.arguments = b
|
||||||
return i
|
return i
|
||||||
}
|
}
|
||||||
|
|
||||||
var tts: TextToSpeech? = null
|
|
||||||
var ttsReady = false
|
|
||||||
var ttsWorking = false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -268,6 +268,7 @@ class OnlineFeedViewFragment : Fragment() {
|
||||||
val channelTabInfo = ChannelTabInfo.getInfo(service, channelInfo.tabs.first())
|
val channelTabInfo = ChannelTabInfo.getInfo(service, channelInfo.tabs.first())
|
||||||
Logd(TAG, "startFeedBuilding result1: $channelTabInfo ${channelTabInfo.relatedItems.size}")
|
Logd(TAG, "startFeedBuilding result1: $channelTabInfo ${channelTabInfo.relatedItems.size}")
|
||||||
selectedDownloadUrl = prepareUrl(url)
|
selectedDownloadUrl = prepareUrl(url)
|
||||||
|
// selectedDownloadUrl = url
|
||||||
val feed_ = Feed(selectedDownloadUrl, null)
|
val feed_ = Feed(selectedDownloadUrl, null)
|
||||||
feed_.id = 1234567889L
|
feed_.id = 1234567889L
|
||||||
feed_.type = Feed.FeedType.YOUTUBE.name
|
feed_.type = Feed.FeedType.YOUTUBE.name
|
||||||
|
|
|
@ -403,7 +403,7 @@ import java.util.*
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun onFeedPrefsChanged(event: FlowEvent.FeedPrefsChangeEvent) {
|
private fun onFeedPrefsChanged(event: FlowEvent.FeedPrefsChangeEvent) {
|
||||||
Log.d(TAG,"speedPresetChanged called")
|
Logd(TAG,"speedPresetChanged called")
|
||||||
for (item in queueItems) {
|
for (item in queueItems) {
|
||||||
if (item.feed?.id == event.feed.id) item.feed = null
|
if (item.feed?.id == event.feed.id) item.feed = null
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,18 +31,12 @@ object RatingDialog {
|
||||||
mPreferences = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
|
mPreferences = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
|
||||||
|
|
||||||
val firstDate: Long = mPreferences.getLong(KEY_FIRST_START_DATE, 0)
|
val firstDate: Long = mPreferences.getLong(KEY_FIRST_START_DATE, 0)
|
||||||
if (firstDate == 0L) {
|
if (firstDate == 0L) resetStartDate()
|
||||||
resetStartDate()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun check() {
|
fun check() {
|
||||||
if (shouldShow()) {
|
if (shouldShow()) {
|
||||||
try {
|
try { showInAppReview() } catch (e: Exception) { Log.e(TAG, Log.getStackTraceString(e)) }
|
||||||
showInAppReview()
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Log.e(TAG, Log.getStackTraceString(e))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -58,9 +52,8 @@ object RatingDialog {
|
||||||
val flow: Task<Void?> = manager.launchReviewFlow(context as Activity, reviewInfo)
|
val flow: Task<Void?> = manager.launchReviewFlow(context as Activity, reviewInfo)
|
||||||
flow.addOnCompleteListener { task1: Task<Void?>? ->
|
flow.addOnCompleteListener { task1: Task<Void?>? ->
|
||||||
val previousAttempts: Int = mPreferences.getInt(KEY_NUMBER_OF_REVIEWS, 0)
|
val previousAttempts: Int = mPreferences.getInt(KEY_NUMBER_OF_REVIEWS, 0)
|
||||||
if (previousAttempts >= 3) {
|
if (previousAttempts >= 3) saveRated()
|
||||||
saveRated()
|
else {
|
||||||
} else {
|
|
||||||
resetStartDate()
|
resetStartDate()
|
||||||
mPreferences
|
mPreferences
|
||||||
.edit()
|
.edit()
|
||||||
|
@ -69,14 +62,10 @@ object RatingDialog {
|
||||||
}
|
}
|
||||||
Logd("ReviewDialog", "Successfully finished in-app review")
|
Logd("ReviewDialog", "Successfully finished in-app review")
|
||||||
}
|
}
|
||||||
.addOnFailureListener { error: Exception? ->
|
.addOnFailureListener { error: Exception? -> Logd("ReviewDialog", "failed in reviewing process") }
|
||||||
Logd("ReviewDialog", "failed in reviewing process")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.addOnFailureListener { error: Exception? ->
|
.addOnFailureListener { error: Exception? -> Logd("ReviewDialog", "failed to get in-app review request") }
|
||||||
Logd("ReviewDialog", "failed to get in-app review request")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun rated(): Boolean {
|
private fun rated(): Boolean {
|
||||||
|
@ -99,9 +88,7 @@ object RatingDialog {
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun shouldShow(): Boolean {
|
private fun shouldShow(): Boolean {
|
||||||
if (rated() || BuildConfig.DEBUG) {
|
if (rated() || BuildConfig.DEBUG) return false
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
val now = System.currentTimeMillis()
|
val now = System.currentTimeMillis()
|
||||||
val firstDate: Long = mPreferences.getLong(KEY_FIRST_START_DATE, now)
|
val firstDate: Long = mPreferences.getLong(KEY_FIRST_START_DATE, now)
|
||||||
|
|
|
@ -1,3 +1,8 @@
|
||||||
|
# 6.5.2
|
||||||
|
|
||||||
|
* replace all url of http to https
|
||||||
|
* resolved the nasty issue of Youtube media not properly played in release app
|
||||||
|
|
||||||
# 6.5.1
|
# 6.5.1
|
||||||
|
|
||||||
* further improved behavior in video player, seamless switch among audio-only, window and fullscreen modes, and automatical switch to audio when exit
|
* further improved behavior in video player, seamless switch among audio-only, window and fullscreen modes, and automatical switch to audio when exit
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
Version 6.5.2 brings several changes:
|
||||||
|
|
||||||
|
* replace all url of http to https
|
||||||
|
* resolved the nasty issue of Youtube media not properly played in release app
|
Loading…
Reference in New Issue