Skip to content

Commit

Permalink
5.4.0 commit
Browse files Browse the repository at this point in the history
  • Loading branch information
XilinJia committed May 19, 2024
1 parent ae37642 commit 36e1823
Show file tree
Hide file tree
Showing 139 changed files with 868 additions and 695 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ Compared to AntennaPod this project:
2. Plays in `AudioOffloadMode`, kind to device battery,
3. Is purely `Kotlin` based and mono-modular,
4. Targets Android 14 with updated dependencies,
5. Outfits with Viewbinding, Coil replacing Glide, coroutines replacing RxJava, and SharedFlow replacing EventBus,
5. Outfits with Viewbinding, Coil replacing Glide, coroutines replacing RxJava and threads, and SharedFlow replacing EventBus,
6. Boasts new UI's including streamlined drawer, subscriptions view and player controller,
7. Accepts podcast as well as plain RSS and YouTube feeds,
8. Offers Readability and Text-to-Speech for RSS contents,
Expand Down
4 changes: 2 additions & 2 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -159,8 +159,8 @@ android {
// Version code schema (not used):
// "1.2.3-beta4" -> 1020304
// "1.2.3" -> 1020395
versionCode 3020146
versionName "5.3.1"
versionCode 3020147
versionName "5.4.0"

def commit = ""
try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import ac.mdiq.podcini.preferences.UserPreferences
import de.test.podcini.EspressoTestUtils
import de.test.podcini.IgnoreOnCi
import de.test.podcini.ui.UITestUtils
import kotlinx.coroutines.runBlocking
import org.awaitility.Awaitility
import org.hamcrest.Matchers
import org.junit.*
Expand Down Expand Up @@ -166,7 +167,7 @@ class PlaybackTest {
fun testStartLocal() {
uiTestUtils!!.addLocalFeedData(true)
activityTestRule.launchActivity(Intent())
clearQueue().get()
runBlocking { clearQueue().join() }
startLocalPlayback()
}

Expand All @@ -175,7 +176,7 @@ class PlaybackTest {
fun testPlayingItemAddsToQueue() {
uiTestUtils!!.addLocalFeedData(true)
activityTestRule.launchActivity(Intent())
clearQueue().get()
runBlocking { clearQueue().join() }
val queue = getQueue()
Assert.assertEquals(0, queue.size.toLong())
startLocalPlayback()
Expand All @@ -188,7 +189,7 @@ class PlaybackTest {
setContinuousPlaybackPreference(false)
uiTestUtils!!.addLocalFeedData(true)
activityTestRule.launchActivity(Intent())
clearQueue().get()
runBlocking { clearQueue().join() }
startLocalPlayback()
}

Expand Down Expand Up @@ -261,10 +262,9 @@ class PlaybackTest {
protected fun replayEpisodeCheck(followQueue: Boolean) {
setContinuousPlaybackPreference(followQueue)
uiTestUtils!!.addLocalFeedData(true)
clearQueue().get()
runBlocking { clearQueue().join() }
activityTestRule.launchActivity(Intent())
val episodes = getEpisodes(0, 10,
unfiltered(), SortOrder.DATE_NEW_OLD)
val episodes = getEpisodes(0, 10, unfiltered(), SortOrder.DATE_NEW_OLD)

startLocalPlayback()
val media = episodes[0].media
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -147,12 +147,12 @@ class NavigationDrawerTest {
val hidden = hiddenDrawerItems
Assert.assertEquals(2, hidden!!.size.toLong())
Assert.assertTrue(hidden.contains(AllEpisodesFragment.TAG))
Assert.assertTrue(hidden.contains(PlaybackHistoryFragment.TAG))
Assert.assertTrue(hidden.contains(HistoryFragment.TAG))
}

@Test
fun testDrawerPreferencesUnhideSomeElements() {
var hidden = listOf(PlaybackHistoryFragment.TAG, DownloadsFragment.TAG)
var hidden = listOf(HistoryFragment.TAG, DownloadsFragment.TAG)
hiddenDrawerItems = hidden
activityRule.launchActivity(Intent())
openNavDrawer()
Expand All @@ -166,7 +166,7 @@ class NavigationDrawerTest {
hidden = hiddenDrawerItems?.filterNotNull()?: listOf()
Assert.assertEquals(2, hidden.size.toLong())
Assert.assertTrue(hidden.contains(QueueFragment.TAG))
Assert.assertTrue(hidden.contains(PlaybackHistoryFragment.TAG))
Assert.assertTrue(hidden.contains(HistoryFragment.TAG))
}


Expand Down
12 changes: 9 additions & 3 deletions app/src/main/java/ac/mdiq/podcini/PodciniApp.kt
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ import com.google.android.material.color.DynamicColors
import com.joanzapata.iconify.Iconify
import com.joanzapata.iconify.fonts.FontAwesomeModule
import com.joanzapata.iconify.fonts.MaterialModule
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext

/** Main application class. */
class PodciniApp : Application() {
Expand All @@ -42,9 +45,12 @@ class PodciniApp : Application() {

singleton = this

ClientConfigurator.initialize(this)
PreferenceUpgrader.checkUpgrades(this)

runBlocking {
withContext(Dispatchers.IO) {
ClientConfigurator.initialize(this@PodciniApp)
PreferenceUpgrader.checkUpgrades(this@PodciniApp)
}
}
Iconify.with(FontAwesomeModule())
Iconify.with(MaterialModule())

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import android.util.Log
import ac.mdiq.podcini.net.download.service.PodciniHttpClient.getHttpClient
import ac.mdiq.podcini.storage.model.feed.Feed
import ac.mdiq.podcini.util.Logd
import android.content.SharedPreferences
import okhttp3.CacheControl
import okhttp3.OkHttpClient
import okhttp3.Request
Expand Down Expand Up @@ -81,6 +82,11 @@ class ItunesTopListLoader(private val context: Context) {
const val COUNTRY_CODE_UNSET: String = "99"
private const val NUM_LOADED = 25

var prefs: SharedPreferences? = null
fun getSharedPrefs(context: Context) {
if (prefs == null) prefs = context.getSharedPreferences(PREFS, Context.MODE_PRIVATE)
}

private fun removeSubscribed(suggestedPodcasts: List<PodcastSearchResult>, subscribedFeeds: List<Feed>, limit: Int): List<PodcastSearchResult> {
val subscribedPodcastsSet: MutableSet<String> = HashSet()
for (subscribedFeed in subscribedFeeds) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import androidx.work.Worker
import androidx.work.WorkerParameters
import com.google.common.util.concurrent.Futures
import com.google.common.util.concurrent.ListenableFuture
import kotlinx.coroutines.runBlocking
import org.apache.commons.io.FileUtils
import java.io.File
import java.io.IOException
Expand Down Expand Up @@ -115,7 +116,7 @@ class EpisodeDownloadWorker(context: Context, params: WorkerParameters) : Worker
if (dest.exists()) {
media.file_url = request.destination
try {
DBWriter.persistFeedMedia(media).get()
runBlocking { DBWriter.persistFeedMedia(media).join() }
} catch (e: Exception) {
Log.e(TAG, "ExecutionException in writeFileUrl: " + e.message)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import android.content.Context
import android.media.MediaMetadataRetriever
import android.util.Log
import androidx.media3.common.util.UnstableApi
import kotlinx.coroutines.runBlocking
import java.io.File
import java.util.concurrent.ExecutionException

Expand Down Expand Up @@ -60,15 +61,15 @@ class MediaDownloadedHandler(private val context: Context, var updatedStatus: Do
val item = media.item

try {
DBWriter.persistFeedMedia(media).get()
runBlocking { DBWriter.persistFeedMedia(media).join() }

// we've received the media, we don't want to autodownload it again
if (item != null) {
item.disableAutoDownload()
// setFeedItem() signals (via EventBus) that the item has been updated,
// so we do it after the enclosing media has been updated above,
// to ensure subscribers will get the updated FeedMedia as well
DBWriter.persistFeedItem(item).get()
runBlocking { DBWriter.persistFeedItem(item).join() }
if (broadcastUnreadStateUpdate) EventFlow.postEvent(FlowEvent.UnreadItemsUpdateEvent())
}
} catch (e: InterruptedException) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ class CompositeX509TrustManager(private val trustManagers: List<X509TrustManager
override fun getAcceptedIssuers(): Array<X509Certificate> {
val certificates: MutableList<X509Certificate> = ArrayList()
for (trustManager in trustManagers) {
// TODO: appears time consuming
certificates.addAll(listOf(*trustManager.acceptedIssuers))
}
return certificates.toTypedArray<X509Certificate>()
Expand Down
15 changes: 1 addition & 14 deletions app/src/main/java/ac/mdiq/podcini/net/sync/SyncService.kt
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ open class SyncService(context: Context, params: WorkerParameters) : Worker(cont
@UnstableApi override fun doWork(): Result {
Logd(TAG, "doWork() called")
val activeSyncProvider = getActiveSyncProvider() ?: return Result.failure()
Logd(TAG, "doWork() got syn provider")

SynchronizationSettings.updateLastSynchronizationAttempt()
setCurrentlyActive(true)
Expand Down Expand Up @@ -165,15 +166,6 @@ open class SyncService(context: Context, params: WorkerParameters) : Worker(cont
return@launch
}
scope.cancel()
// try {
// while (true) {
// Thread.sleep(1000)
// val event = EventBus.getDefault().getStickyEvent(FlowEvent.FeedUpdateRunningEvent::class.java)
// if (event == null || !event.isFeedUpdateRunning) return
// }
// } catch (e: InterruptedException) {
// e.printStackTrace()
// }
}

fun getEpisodeActions(syncServiceImpl: ISyncService) : Pair<Long, Long> {
Expand Down Expand Up @@ -315,11 +307,6 @@ open class SyncService(context: Context, params: WorkerParameters) : Worker(cont
if (selectedService == null) return null

return when (selectedService) {
// SynchronizationProviderViewData.WIFI -> {
//// if (hosturl != null) WifiImplSyncService(hosturl!!, hostport)
//// else null
// null
// }
SynchronizationProviderViewData.GPODDER_NET -> GpodnetService(getHttpClient(), hosturl, deviceID?:"", username?:"", password?:"")
SynchronizationProviderViewData.NEXTCLOUD_GPODDER -> NextcloudSyncService(getHttpClient(), hosturl, username?:"", password?:"")
}
Expand Down
15 changes: 12 additions & 3 deletions app/src/main/java/ac/mdiq/podcini/playback/PlaybackController.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import ac.mdiq.podcini.playback.service.PlaybackService
import ac.mdiq.podcini.playback.service.PlaybackService.LocalBinder
import ac.mdiq.podcini.playback.service.PlaybackServiceConstants
import ac.mdiq.podcini.preferences.PlaybackPreferences
import ac.mdiq.podcini.preferences.PlaybackPreferences.Companion.loadPlayableFromPreferences
import ac.mdiq.podcini.preferences.UserPreferences
import ac.mdiq.podcini.storage.DBWriter
import ac.mdiq.podcini.storage.model.feed.FeedMedia
Expand All @@ -24,8 +25,11 @@ import android.view.SurfaceHolder
import androidx.fragment.app.FragmentActivity
import androidx.lifecycle.lifecycleScope
import androidx.media3.common.util.UnstableApi
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext

/**
* Communicates with the playback service. GUI classes should use this class to
Expand Down Expand Up @@ -73,6 +77,7 @@ abstract class PlaybackController(private val activity: FragmentActivity) {
private fun procFlowEvents() {
activity.lifecycleScope.launch {
EventFlow.events.collectLatest { event ->
Logd(TAG, "Received event: $event")
when (event) {
is FlowEvent.PlaybackServiceEvent -> if (event.action == FlowEvent.PlaybackServiceEvent.Action.SERVICE_STARTED) init()
else -> {}
Expand Down Expand Up @@ -305,9 +310,13 @@ abstract class PlaybackController(private val activity: FragmentActivity) {

fun getMedia(): Playable? {
if (media == null && playbackService != null) media = playbackService!!.mPlayerInfo.playable
if (media == null) media = PlaybackPreferences.createInstanceFromPreferences(activity)

return media
if (media != null) return media
return runBlocking {
media = withContext(Dispatchers.IO) {
loadPlayableFromPreferences()
}
media
}
}

fun seekTo(time: Int) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import ac.mdiq.podcini.playback.base.MediaPlayerBase
import ac.mdiq.podcini.playback.base.PlayerStatus
import ac.mdiq.podcini.playback.base.RewindAfterPauseUtils
import ac.mdiq.podcini.preferences.UserPreferences
import ac.mdiq.podcini.storage.DBWriter.ioScope
import ac.mdiq.podcini.storage.model.feed.FeedMedia
import ac.mdiq.podcini.storage.model.playback.MediaType
import ac.mdiq.podcini.storage.model.playback.Playable
Expand Down Expand Up @@ -159,15 +160,15 @@ class LocalMediaPlayer(context: Context, callback: MediaPlayerCallback) : MediaP
private fun setDataSource(m: MediaMetadata, s: String, user: String?, password: String?) {
Logd(TAG, "setDataSource: $s")

val httpDataSourceFactory = OkHttpDataSource.Factory(PodciniHttpClient.getHttpClient() as okhttp3.Call.Factory)
.setUserAgent(ClientConfig.USER_AGENT)
if (httpDataSourceFactory == null)
httpDataSourceFactory = OkHttpDataSource.Factory(PodciniHttpClient.getHttpClient() as okhttp3.Call.Factory).setUserAgent(ClientConfig.USER_AGENT)

if (!user.isNullOrEmpty() && !password.isNullOrEmpty()) {
val requestProperties = HashMap<String, String>()
requestProperties["Authorization"] = HttpCredentialEncoder.encode(user, password, "ISO-8859-1")
httpDataSourceFactory.setDefaultRequestProperties(requestProperties)
httpDataSourceFactory!!.setDefaultRequestProperties(requestProperties)
}
val dataSourceFactory: DataSource.Factory = DefaultDataSourceFactory(context, null, httpDataSourceFactory)
val dataSourceFactory: DataSource.Factory = DefaultDataSourceFactory(context, null, httpDataSourceFactory!!)
val extractorsFactory = DefaultExtractorsFactory()
extractorsFactory.setConstantBitrateSeekingEnabled(true)
extractorsFactory.setMp3ExtractorFlags(Mp3Extractor.FLAG_DISABLE_ID3_METADATA)
Expand Down Expand Up @@ -282,6 +283,7 @@ class LocalMediaPlayer(context: Context, callback: MediaPlayerCallback) : MediaP
}
else -> {
val localMediaurl = this.playable!!.getLocalMediaUrl()
// TODO: File(localMediaurl).canRead() time consuming
if (!localMediaurl.isNullOrEmpty() && File(localMediaurl).canRead()) setDataSource(metadata, localMediaurl, null, null)
else throw IOException("Unable to read local file $localMediaurl")
}
Expand Down Expand Up @@ -603,8 +605,7 @@ class LocalMediaPlayer(context: Context, callback: MediaPlayerCallback) : MediaP
* invalid values.
*/
override fun getVideoSize(): Pair<Int, Int>? {
if (status != PlayerStatus.ERROR && mediaType == MediaType.VIDEO)
videoSize = Pair(videoWidth, videoHeight)
if (status != PlayerStatus.ERROR && mediaType == MediaType.VIDEO) videoSize = Pair(videoWidth, videoHeight)
return videoSize
}

Expand Down Expand Up @@ -664,6 +665,12 @@ class LocalMediaPlayer(context: Context, callback: MediaPlayerCallback) : MediaP
init {
mediaType = MediaType.UNKNOWN

if (httpDataSourceFactory == null) {
ioScope.launch {
httpDataSourceFactory = OkHttpDataSource.Factory(PodciniHttpClient.getHttpClient() as okhttp3.Call.Factory)
.setUserAgent(ClientConfig.USER_AGENT)
}
}
if (exoPlayer == null) {
setupPlayerListener()
createStaticPlayer(context)
Expand Down Expand Up @@ -837,6 +844,8 @@ class LocalMediaPlayer(context: Context, callback: MediaPlayerCallback) : MediaP
const val BUFFERING_ENDED: Int = -2
const val ERROR_CODE_OFFSET: Int = 1000

private var httpDataSourceFactory: OkHttpDataSource.Factory? = null

private var trackSelector: DefaultTrackSelector? = null
var exoPlayer: ExoPlayer? = null

Expand Down Expand Up @@ -897,6 +906,7 @@ class LocalMediaPlayer(context: Context, callback: MediaPlayerCallback) : MediaP
audioErrorListener = null
bufferingUpdateListener = null
loudnessEnhancer = null
httpDataSourceFactory = null
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import ac.mdiq.podcini.playback.cast.CastPsmp
import ac.mdiq.podcini.playback.cast.CastStateListener
import ac.mdiq.podcini.playback.service.PlaybackServiceTaskManager.PSTMCallback
import ac.mdiq.podcini.preferences.PlaybackPreferences.Companion.clearCurrentlyPlayingTemporaryPlaybackSpeed
import ac.mdiq.podcini.preferences.PlaybackPreferences.Companion.createInstanceFromPreferences
import ac.mdiq.podcini.preferences.PlaybackPreferences.Companion.loadPlayableFromPreferences
import ac.mdiq.podcini.preferences.PlaybackPreferences.Companion.currentEpisodeIsVideo
import ac.mdiq.podcini.preferences.PlaybackPreferences.Companion.currentlyPlayingFeedMediaId
import ac.mdiq.podcini.preferences.PlaybackPreferences.Companion.currentlyPlayingTemporaryPlaybackSpeed
Expand Down Expand Up @@ -612,7 +612,7 @@ class PlaybackService : MediaSessionService() {
scope.launch {
try {
val playable = withContext(Dispatchers.IO) {
createInstanceFromPreferences(applicationContext)
loadPlayableFromPreferences()
}
withContext(Dispatchers.Main) {
startPlaying(playable, false)
Expand Down Expand Up @@ -783,6 +783,7 @@ class PlaybackService : MediaSessionService() {
private fun procFlowEvents() {
scope.launch {
EventFlow.events.collectLatest { event ->
Logd(TAG, "Received event: $event")
when (event) {
is FlowEvent.PlayerErrorEvent -> playerError(event)
is FlowEvent.BufferUpdateEvent -> bufferUpdate(event)
Expand Down
Loading

0 comments on commit 36e1823

Please sign in to comment.