Skip to content

Commit

Permalink
6.9.0 commit
Browse files Browse the repository at this point in the history
  • Loading branch information
XilinJia committed Oct 6, 2024
1 parent 13a4f8c commit 72f28ce
Show file tree
Hide file tree
Showing 49 changed files with 1,388 additions and 1,437 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ An open source podcast instrument, attuned to Puccini ![Puccini](./images/Puccin
That means finally: [Nessun dorma](https://www.youtube.com/watch?v=cWc7vYjgnTs)
#### For Podcini to show up on car's HUD with Android Auto, please read AnroidAuto.md for instructions.
#### If you need to cast to an external speaker, you should install the "play" apk, not the "free" apk, that's about the difference between the two.
#### Since version 6.8.5, Podcini.R is built to target SDK 30 (Android 11), though built with SDK 35 and tested on Android 14. This is to counter 2-year old Google issue ForegroundServiceStartNotAllowedException. For more see [this issue](https://github.com/XilinJia/Podcini/issues/88)
#### If you are migrating from Podcini version 5, please read the migrationTo5.md file for migration instructions.

This project was developed from a fork of [AntennaPod](<https://github.com/AntennaPod/AntennaPod>) as of Feb 5 2024.
Expand Down Expand Up @@ -138,6 +139,7 @@ While podcast subscriptions' OPML files (from AntennaPod or any other sources) c
* All the media from Youtube or Youtube Music can be played (only streamed) with video in fullscreen and in window modes or in audio only mode in the background
* These media are played with the lowest video quality and highest audio quality
* If a subscription is set for "audio only", then only audio stream is fetched at play time for every media in the subscription
* accepted host names include: youtube.com, www.youtube.com, m.youtube.com, music.youtube.com, and youtu.be

### Instant (or Wifi) sync

Expand Down
4 changes: 2 additions & 2 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@ android {
testApplicationId "ac.mdiq.podcini.tests"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"

versionCode 3020264
versionName "6.8.7"
versionCode 3020265
versionName "6.9.0"

applicationId "ac.mdiq.podcini.R"
def commit = ""
Expand Down
110 changes: 63 additions & 47 deletions app/src/main/kotlin/ac/mdiq/podcini/net/feed/FeedBuilder.kt
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,7 @@ import android.content.Context
import android.util.Log
import io.realm.kotlin.ext.realmListOf
import io.realm.kotlin.types.RealmList
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import kotlinx.coroutines.*
import org.jsoup.Jsoup
import java.io.File
import java.io.IOException
Expand All @@ -50,43 +47,51 @@ class FeedBuilder(val context: Context, val showError: (String?, String)->Unit)
val service = try { Vista.getService("YouTube") } catch (e: ExtractionException) { throw ExtractionException("YouTube service not found") }
selectedDownloadUrl = prepareUrl(url)
val feed_ = Feed(selectedDownloadUrl, null)
feed_.isBuilding = true
feed_.id = Feed.newId()
feed_.type = Feed.FeedType.YOUTUBE.name
feed_.hasVideoMedia = true
feed_.fileUrl = File(feedfilePath, getFeedfileName(feed_)).toString()
val eList: RealmList<Episode> = realmListOf()
val eList: MutableList<Episode> = mutableListOf()

if (url.startsWith("https://youtube.com/playlist?") || url.startsWith("https://music.youtube.com/playlist?")) {
val uURL = URL(url)
// if (url.startsWith("https://youtube.com/playlist?") || url.startsWith("https://music.youtube.com/playlist?")) {
if (uURL.path.startsWith("/playlist") || uURL.path.startsWith("/playlist")) {
val playlistInfo = PlaylistInfo.getInfo(Vista.getService(0), url) ?: return@launch
feed_.title = playlistInfo.name
feed_.description = playlistInfo.description?.content ?: ""
feed_.author = playlistInfo.uploaderName
feed_.imageUrl = if (playlistInfo.thumbnails.isNotEmpty()) playlistInfo.thumbnails.first().url else null
feed_.episodes = realmListOf()
var infoItems = playlistInfo.relatedItems
var nextPage = playlistInfo.nextPage
Logd(TAG, "infoItems: ${infoItems.size}")
while (infoItems.isNotEmpty()) {
for (r in infoItems) {
Logd(TAG, "startFeedBuilding relatedItem: $r")
if (r.infoType != InfoItem.InfoType.STREAM) continue
val e = episodeFromStreamInfoItem(r)
e.feed = feed_
e.feedId = feed_.id
eList.add(e)
}
if (nextPage == null || eList.size > 500) break
try {
val page = PlaylistInfo.getMoreItems(service, url, nextPage) ?: break
nextPage = page.nextPage
infoItems = page.items
Logd(TAG, "more infoItems: ${infoItems.size}")
} catch (e: Throwable) {
Logd(TAG, "PlaylistInfo.getMoreItems error: ${e.message}")
withContext(Dispatchers.Main) { showError(e.message, "") }
break
CoroutineScope(Dispatchers.IO).launch {
while (infoItems.isNotEmpty()) {
eList.clear()
for (r in infoItems) {
Logd(TAG, "startFeedBuilding relatedItem: $r")
if (r.infoType != InfoItem.InfoType.STREAM) continue
val e = episodeFromStreamInfoItem(r)
e.feed = feed_
e.feedId = feed_.id
eList.add(e)
}
if (nextPage == null || feed_.episodes.size > 1000) break
try {
val page = PlaylistInfo.getMoreItems(service, url, nextPage) ?: break
nextPage = page.nextPage
infoItems = page.items
Logd(TAG, "more infoItems: ${infoItems.size}")
} catch (e: Throwable) {
Logd(TAG, "PlaylistInfo.getMoreItems error: ${e.message}")
withContext(Dispatchers.Main) { showError(e.message, "") }
break
}
feed_.episodes.addAll(eList)
}
feed_.isBuilding = false
}
feed_.episodes = eList
withContext(Dispatchers.Main) { handleFeed(feed_, mapOf()) }
} else {
val channelInfo = ChannelInfo.getInfo(service, url)
Expand All @@ -102,32 +107,37 @@ class FeedBuilder(val context: Context, val showError: (String?, String)->Unit)
feed_.description = channelInfo.description
feed_.author = channelInfo.parentChannelName
feed_.imageUrl = if (channelInfo.avatars.isNotEmpty()) channelInfo.avatars.first().url else null
feed_.episodes = realmListOf()

var infoItems = channelTabInfo.relatedItems
var nextPage = channelTabInfo.nextPage
Logd(TAG, "infoItems: ${infoItems.size}")
while (infoItems.isNotEmpty()) {
for (r in infoItems) {
Logd(TAG, "startFeedBuilding relatedItem: $r")
if (r.infoType != InfoItem.InfoType.STREAM) continue
val e = episodeFromStreamInfoItem(r as StreamInfoItem)
e.feed = feed_
e.feedId = feed_.id
eList.add(e)
}
if (nextPage == null || eList.size > 200) break
try {
val page = ChannelTabInfo.getMoreItems(service, channelInfo.tabs.first(), nextPage)
nextPage = page.nextPage
infoItems = page.items
Logd(TAG, "more infoItems: ${infoItems.size}")
} catch (e: Throwable) {
Logd(TAG, "ChannelTabInfo.getMoreItems error: ${e.message}")
withContext(Dispatchers.Main) { showError(e.message, "") }
break
CoroutineScope(Dispatchers.IO).launch {
while (infoItems.isNotEmpty()) {
eList.clear()
for (r in infoItems) {
Logd(TAG, "startFeedBuilding relatedItem: $r")
if (r.infoType != InfoItem.InfoType.STREAM) continue
val e = episodeFromStreamInfoItem(r as StreamInfoItem)
e.feed = feed_
e.feedId = feed_.id
eList.add(e)
}
if (nextPage == null || feed_.episodes.size > 1000) break
try {
val page = ChannelTabInfo.getMoreItems(service, channelInfo.tabs.first(), nextPage)
nextPage = page.nextPage
infoItems = page.items
Logd(TAG, "more infoItems: ${infoItems.size}")
} catch (e: Throwable) {
Logd(TAG, "ChannelTabInfo.getMoreItems error: ${e.message}")
withContext(Dispatchers.Main) { showError(e.message, "") }
break
}
feed_.episodes.addAll(eList)
}
feed_.isBuilding = false
}
feed_.episodes = eList
withContext(Dispatchers.Main) { handleFeed(feed_, mapOf()) }
} catch (e: Throwable) {
Logd(TAG, "startFeedBuilding error1 ${e.message}")
Expand Down Expand Up @@ -202,8 +212,11 @@ class FeedBuilder(val context: Context, val showError: (String?, String)->Unit)
val destinationFile = File(destination)
return try {
val feed = Feed(selectedDownloadUrl, null)
feed.isBuilding = true
feed.fileUrl = destination
FeedHandler().parseFeed(feed)
val result = FeedHandler().parseFeed(feed)
feed.isBuilding = false
result
} catch (e: FeedHandler.UnsupportedFeedtypeException) {
Logd(TAG, "Unsupported feed type detected")
if ("html".equals(e.rootElement, ignoreCase = true)) {
Expand Down Expand Up @@ -250,6 +263,9 @@ class FeedBuilder(val context: Context, val showError: (String?, String)->Unit)
}

fun subscribe(feed: Feed) {
while (feed.isBuilding) {
runBlocking { delay(200) }
}
feed.id = 0L
for (item in feed.episodes) {
item.id = 0L
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ package ac.mdiq.podcini.net.feed.discovery

import ac.mdiq.podcini.net.sync.gpoddernet.model.GpodnetPodcast
import ac.mdiq.vista.extractor.channel.ChannelInfoItem
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableLongStateOf
import androidx.compose.runtime.setValue
import de.mfietz.fyydlin.SearchHit
import org.json.JSONException
import org.json.JSONObject
Expand All @@ -18,6 +21,9 @@ class PodcastSearchResult private constructor(
val subscriberCount: Int,
val source: String) {

// feedId will be positive if already subscribed
var feedId by mutableLongStateOf(0L)

companion object {
fun dummy(): PodcastSearchResult {
return PodcastSearchResult("", "", "", "", 0, "", -1, "dummy")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ class FeedHandler {
// val tg = TypeGetter()
val type = getType(feed)
val handler = SyndHandler(feed, type)

if (feed.fileUrl != null) {
val factory = SAXParserFactory.newInstance()
factory.isNamespaceAware = true
Expand Down
4 changes: 4 additions & 0 deletions app/src/main/kotlin/ac/mdiq/podcini/storage/database/Feeds.kt
Original file line number Diff line number Diff line change
Expand Up @@ -436,6 +436,7 @@ object Feeds {
feed.preferences!!.queueId = -2L
feed.preferences!!.videoModePolicy = if (video) VideoMode.WINDOW_VIEW else VideoMode.AUDIO_ONLY
upsertBlk(feed) {}
EventFlow.postEvent(FlowEvent.FeedListEvent(FlowEvent.FeedListEvent.Action.ADDED))
return feed
}

Expand All @@ -452,6 +453,7 @@ object Feeds {
upsertBlk(episode) {}
feed.episodes.add(episode)
upsertBlk(feed) {}
EventFlow.postStickyEvent(FlowEvent.FeedUpdatingEvent(false))
}

private fun getMiscSyndicate(): Feed {
Expand All @@ -470,6 +472,7 @@ object Feeds {
feed.preferences!!.queueId = -2L
// feed.preferences!!.videoModePolicy = if (video) VideoMode.WINDOW_VIEW else VideoMode.AUDIO_ONLY
upsertBlk(feed) {}
EventFlow.postEvent(FlowEvent.FeedListEvent(FlowEvent.FeedListEvent.Action.ADDED))
return feed
}

Expand All @@ -485,6 +488,7 @@ object Feeds {
upsertBlk(episode) {}
feed.episodes.add(episode)
upsertBlk(feed) {}
EventFlow.postStickyEvent(FlowEvent.FeedUpdatingEvent(false))
}

/**
Expand Down
32 changes: 0 additions & 32 deletions app/src/main/kotlin/ac/mdiq/podcini/storage/model/Episode.kt
Original file line number Diff line number Diff line change
Expand Up @@ -135,15 +135,9 @@ class Episode : RealmObject {
return field
}

@Ignore
val downloadState = mutableIntStateOf(if (media?.downloaded == true) DownloadStatus.State.COMPLETED.ordinal else DownloadStatus.State.UNKNOWN.ordinal)

@Ignore
val isRemote = mutableStateOf(false)

@Ignore
val stopMonitoring = mutableStateOf(false)

constructor() {
this.playState = PlayState.UNPLAYED.code
}
Expand All @@ -162,13 +156,6 @@ class Episode : RealmObject {
this.feed = feed
}

fun copyStates(other: Episode) {
// inQueueState.value = other.inQueueState.value
// isPlayingState.value = other.isPlayingState.value
downloadState.value = other.downloadState.value
stopMonitoring.value = other.stopMonitoring.value
}

fun updateFromOther(other: Episode) {
if (other.imageUrl != null) this.imageUrl = other.imageUrl
if (other.title != null) title = other.title
Expand Down Expand Up @@ -296,10 +283,6 @@ class Episode : RealmObject {
if (isFavorite != other.isFavorite) return false
if (isInProgress != other.isInProgress) return false
if (isDownloaded != other.isDownloaded) return false
// if (inQueueState != other.inQueueState) return false
// if (isPlayingState != other.isPlayingState) return false
if (downloadState != other.downloadState) return false
if (stopMonitoring != other.stopMonitoring) return false

return true
}
Expand All @@ -324,24 +307,9 @@ class Episode : RealmObject {
result = 31 * result + isFavorite.hashCode()
result = 31 * result + isInProgress.hashCode()
result = 31 * result + isDownloaded.hashCode()
// result = 31 * result + inQueueState.hashCode()
// result = 31 * result + isPlayingState.hashCode()
result = 31 * result + downloadState.hashCode()
result = 31 * result + stopMonitoring.hashCode()
return result
}

// override fun equals(other: Any?): Boolean {
// if (this === other) return true
// if (other !is Episode) return false
// return id == other.id
// }
//
// override fun hashCode(): Int {
// val result = (id xor (id ushr 32)).toInt()
// return result
// }

enum class PlayState(val code: Int) {
UNSPECIFIED(-2),
NEW(-1),
Expand Down
6 changes: 6 additions & 0 deletions app/src/main/kotlin/ac/mdiq/podcini/storage/model/Feed.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ import ac.mdiq.podcini.storage.database.RealmDB.realm
import ac.mdiq.podcini.storage.model.FeedFunding.Companion.extractPaymentLinks
import ac.mdiq.podcini.storage.model.EpisodeSortOrder.Companion.fromCode
import ac.mdiq.podcini.storage.utils.EpisodesPermutors.getPermutor
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import io.realm.kotlin.ext.realmListOf
import io.realm.kotlin.types.RealmList
import io.realm.kotlin.types.RealmObject
Expand Down Expand Up @@ -141,6 +144,9 @@ class Feed : RealmObject {
@Ignore
var sortInfo: String = ""

@Ignore
var isBuilding by mutableStateOf(false)

/**
* This constructor is used for test purposes.
*/
Expand Down
Loading

0 comments on commit 72f28ce

Please sign in to comment.