6.5.2 commit

This commit is contained in:
Xilin Jia 2024-09-03 06:58:12 +01:00
parent 0299b53c8c
commit c29eb0bf1b
17 changed files with 102 additions and 124 deletions

View File

@ -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 {

View File

@ -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

View File

@ -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"

View File

@ -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 {

View File

@ -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())
} }
} }

View File

@ -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()

View File

@ -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)

View File

@ -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
} }
} }

View File

@ -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!!)

View File

@ -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)

View File

@ -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)

View File

@ -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
} }
} }

View File

@ -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

View File

@ -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
} }

View File

@ -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)

View File

@ -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

View File

@ -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