Skip to content

Commit

Permalink
6.13.5 commit
Browse files Browse the repository at this point in the history
  • Loading branch information
XilinJia committed Nov 5, 2024
1 parent 2d614ad commit a0d6557
Show file tree
Hide file tree
Showing 15 changed files with 360 additions and 258 deletions.
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,12 @@ An open source podcast instrument, attuned to Puccini ![Puccini](./images/Puccin
[<img src="./images/external/amazon.png" alt="Amazon" height="40">](https://www.amazon.com/%E8%B4%BE%E8%A5%BF%E6%9E%97-Podcini-R/dp/B0D9WR8P13)

#### Podcini.R 6.10 allows creating synthetic podcast and shelving any episdes to any synthetic podcasts
#### Podcini.R version 6.5 as a major step forward brings YouTube contents in the app. Channels can be searched, received from share, subscribed. Podcasts, playlists as well as single media from Youtube and YT Music can be shared to Podcini. For more see the Youtube section below or the changelogs
#### Podcini.R 6.5 as a major step forward brings YouTube contents in the app. Channels can be searched, received from share, subscribed. Podcasts, playlists as well as single media from Youtube and YT Music can be shared to Podcini. For more see the Youtube section below or the changelogs
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.11.5, Podcini.R is back to be built to target SDK 35 (Android 15), but requests for permission for unrestricted background activities for uninterrupted background play of a playlist. For more see [this issue](https://github.com/XilinJia/Podcini/issues/88)
#### If you intend to sync with through a server, be cautious as it's not well tested with Podcini. Welcome any ideas and contribution on this.
#### 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 @@ -141,6 +142,7 @@ While podcast subscriptions' OPML files (from AntennaPod or any other sources) c

* Youtube channels can be searched in podcast search view, can also be shared from other apps (such as Youtube) to Podcini
* Youtube channels can be subscribed as normal podcasts
* When subscribing to a Youtube channel, tabs can be chosen to form separate podcasts
* Playlists and podcasts on Youtube or Youtube Music can be shared to Podcini, and then can be subscribed in similar fashion as the channels
* Single media from Youtube or Youtube Music can also be shared from other apps, can be accepted as including video or audio only, are added to synthetic podcasts such as "Youtube Syndicate"
* 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
Expand Down
4 changes: 2 additions & 2 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ android {
vectorDrawables.useSupportLibrary false
vectorDrawables.generatedDensities = []

versionCode 3020291
versionName "6.13.4"
versionCode 3020292
versionName "6.13.5"

applicationId "ac.mdiq.podcini.R"
def commit = ""
Expand Down
298 changes: 190 additions & 108 deletions app/src/main/kotlin/ac/mdiq/podcini/net/feed/FeedBuilder.kt

Large diffs are not rendered by default.

65 changes: 44 additions & 21 deletions app/src/main/kotlin/ac/mdiq/podcini/net/feed/FeedUpdateManager.kt
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import ac.mdiq.vista.extractor.Vista
import ac.mdiq.vista.extractor.channel.ChannelInfo
import ac.mdiq.vista.extractor.channel.tabs.ChannelTabInfo
import ac.mdiq.vista.extractor.exceptions.ExtractionException
import ac.mdiq.vista.extractor.playlist.PlaylistInfo
import ac.mdiq.vista.extractor.stream.StreamInfoItem
import android.Manifest
import android.app.Notification
Expand All @@ -52,6 +53,7 @@ import io.realm.kotlin.types.RealmList
import org.xml.sax.SAXException
import java.io.File
import java.io.IOException
import java.net.URL
import java.util.*
import java.util.concurrent.Callable
import java.util.concurrent.TimeUnit
Expand Down Expand Up @@ -233,29 +235,50 @@ object FeedUpdateManager {
}
}
private fun refreshYoutubeFeed(feed: Feed) {
if (feed.downloadUrl.isNullOrEmpty()) return
val url = feed.downloadUrl
try {
val service = try { Vista.getService("YouTube") } catch (e: ExtractionException) { throw ExtractionException("YouTube service not found") }
val channelInfo = ChannelInfo.getInfo(service, feed.downloadUrl!!)
Logd(TAG, "refreshYoutubeFeed channelInfo: $channelInfo ${channelInfo.tabs.size}")
if (channelInfo.tabs.isEmpty()) return
try {
val channelTabInfo = ChannelTabInfo.getInfo(service, channelInfo.tabs.first())
Logd(TAG, "refreshYoutubeFeed result1: $channelTabInfo ${channelTabInfo.relatedItems.size}")
val service = try { Vista.getService("YouTube")
} catch (e: ExtractionException) { throw ExtractionException("YouTube service not found") }

val uURL = URL(url)
if (uURL.path.startsWith("/channel")) {
val channelInfo = ChannelInfo.getInfo(service, url!!)
Logd(TAG, "refreshYoutubeFeed channelInfo: $channelInfo ${channelInfo.tabs.size}")
if (channelInfo.tabs.isEmpty()) return
try {
val channelTabInfo = ChannelTabInfo.getInfo(service, channelInfo.tabs.first())
Logd(TAG, "refreshYoutubeFeed result1: $channelTabInfo ${channelTabInfo.relatedItems.size}")
val eList: RealmList<Episode> = realmListOf()
for (r in channelTabInfo.relatedItems) eList.add(episodeFromStreamInfoItem(r as StreamInfoItem))
val feed_ = Feed(url, null)
feed_.type = Feed.FeedType.YOUTUBE.name
feed_.hasVideoMedia = true
feed_.title = channelInfo.name
feed_.fileUrl = File(feedfilePath, getFeedfileName(feed_)).toString()
feed_.description = channelInfo.description
feed_.author = channelInfo.parentChannelName
feed_.imageUrl = if (channelInfo.avatars.isNotEmpty()) channelInfo.avatars.first().url else null
feed_.episodes = eList
Feeds.updateFeed(applicationContext, feed_, false)
} catch (e: Throwable) { Logd(TAG, "refreshYoutubeFeed channel error1 ${e.message}") }
} else if (uURL.path.startsWith("/playlist")) {
val playlistInfo = PlaylistInfo.getInfo(Vista.getService(0), url) ?: return
val eList: RealmList<Episode> = realmListOf()
for (r in channelTabInfo.relatedItems) {
eList.add(episodeFromStreamInfoItem(r as StreamInfoItem))
}
val feed_ = Feed(feed.downloadUrl, null)
feed_.type = Feed.FeedType.YOUTUBE.name
feed_.hasVideoMedia = true
feed_.title = channelInfo.name
feed_.fileUrl = File(feedfilePath, getFeedfileName(feed_)).toString()
feed_.description = channelInfo.description
feed_.author = channelInfo.parentChannelName
feed_.imageUrl = if (channelInfo.avatars.isNotEmpty()) channelInfo.avatars.first().url else null
feed_.episodes = eList
Feeds.updateFeed(applicationContext, feed_, false)
} catch (e: Throwable) { Logd(TAG, "refreshYoutubeFeed error1 ${e.message}") }
try {
for (r in playlistInfo.relatedItems) eList.add(episodeFromStreamInfoItem(r))
val feed_ = Feed(url, null)
feed_.type = Feed.FeedType.YOUTUBE.name
feed_.hasVideoMedia = true
feed_.title = playlistInfo.name
feed_.fileUrl = File(feedfilePath, getFeedfileName(feed_)).toString()
feed_.description = playlistInfo.description?.content ?: ""
feed_.author = playlistInfo.uploaderName
feed_.imageUrl = if (playlistInfo.thumbnails.isNotEmpty()) playlistInfo.thumbnails.first().url else null
feed_.episodes = eList
Feeds.updateFeed(applicationContext, feed_, false)
} catch (e: Throwable) { Logd(TAG, "refreshYoutubeFeed playlist error1 ${e.message}") }
}
} catch (e: Throwable) { Logd(TAG, "refreshYoutubeFeed error ${e.message}") }
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@ import ac.mdiq.podcini.util.FlowEvent
import ac.mdiq.podcini.util.Logd
import android.Manifest
import android.annotation.SuppressLint
import android.app.AppOpsManager
import android.content.ComponentName
import android.content.Context
import android.content.DialogInterface
Expand All @@ -60,16 +59,13 @@ import android.widget.EditText
import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.ActionBarDrawerToggle
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import androidx.core.graphics.Insets
import androidx.core.view.ViewCompat
import androidx.core.view.WindowCompat
import androidx.core.view.WindowInsetsCompat
import androidx.drawerlayout.widget.DrawerLayout
import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope

import androidx.media3.session.MediaController
import androidx.media3.session.SessionToken
import androidx.recyclerview.widget.RecyclerView
Expand Down
4 changes: 1 addition & 3 deletions app/src/main/kotlin/ac/mdiq/podcini/ui/compose/EpisodesVM.kt
Original file line number Diff line number Diff line change
Expand Up @@ -986,9 +986,7 @@ fun ConfirmAddYoutubeEpisode(sharedUrls: List<String>, showDialog: Boolean, onDi
}
withContext(Dispatchers.Main) { onDismissRequest() }
}
}) {
Text("Confirm")
}
}) { Text("Confirm") }
} else CircularProgressIndicator(progress = { 0.6f }, strokeWidth = 4.dp, modifier = Modifier.padding(start = 20.dp, end = 20.dp).width(30.dp).height(30.dp))
}
}
Expand Down
78 changes: 28 additions & 50 deletions app/src/main/kotlin/ac/mdiq/podcini/ui/compose/Feeds.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package ac.mdiq.podcini.ui.compose
import ac.mdiq.podcini.R
import ac.mdiq.podcini.net.feed.FeedBuilder
import ac.mdiq.podcini.net.feed.discovery.PodcastSearchResult
import ac.mdiq.podcini.storage.database.Feeds
import ac.mdiq.podcini.storage.database.Feeds.deleteFeedSync
import ac.mdiq.podcini.storage.database.RealmDB.upsert
import ac.mdiq.podcini.storage.database.RealmDB.upsertBlk
Expand Down Expand Up @@ -55,10 +54,7 @@ fun ChooseRatingDialog(selected: List<Feed>, onDismissRequest: () -> Unit) {
Column(modifier = Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(16.dp)) {
for (rating in Rating.entries.reversed()) {
Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.padding(4.dp).clickable {
for (item in selected) {
// Feeds.setRating(item, rating.code)
upsertBlk(item) { it.rating = rating.code }
}
for (item in selected) upsertBlk(item) { it.rating = rating.code }
onDismissRequest()
}) {
Icon(imageVector = ImageVector.vectorResource(id = rating.res), "")
Expand All @@ -85,8 +81,7 @@ fun RemoveFeedDialog(feeds: List<Feed>, onDismissRequest: () -> Unit, callback:
Column(modifier = Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(16.dp)) {
Text(message)
Text(stringResource(R.string.feed_delete_reason_msg))
BasicTextField(value = textState, onValueChange = { textState = it },
textStyle = TextStyle(fontSize = 16.sp, color = textColor),
BasicTextField(value = textState, onValueChange = { textState = it }, textStyle = TextStyle(fontSize = 16.sp, color = textColor),
modifier = Modifier.fillMaxWidth().height(100.dp).padding(start = 10.dp, end = 10.dp, bottom = 10.dp).border(1.dp, MaterialTheme.colorScheme.primary, MaterialTheme.shapes.small)
)
Button(onClick = {
Expand Down Expand Up @@ -119,9 +114,7 @@ fun RemoveFeedDialog(feeds: List<Feed>, onDismissRequest: () -> Unit, callback:
} catch (e: Throwable) { Log.e("RemoveFeedDialog", Log.getStackTraceString(e)) }
}
onDismissRequest()
}) {
Text("Confirm")
}
}) { Text("Confirm") }
}
}
}
Expand All @@ -130,41 +123,43 @@ fun RemoveFeedDialog(feeds: List<Feed>, onDismissRequest: () -> Unit, callback:
@OptIn(ExperimentalFoundationApi::class)
@Composable
fun OnlineFeedItem(activity: MainActivity, feed: PodcastSearchResult, log: SubscriptionLog? = null) {
// var showYTChannelDialog by remember { mutableStateOf(false) }
// if (showYTChannelDialog) feedBuilder.ConfirmYTChannelTabsDialog(onDismissRequest = {showYTChannelDialog = false}) {feed, map -> handleFeed(feed, map)}

val showSubscribeDialog = remember { mutableStateOf(false) }
@Composable
fun confirmSubscribe(feed: PodcastSearchResult, showDialog: Boolean, onDismissRequest: () -> Unit) {
if (showDialog) {
Dialog(onDismissRequest = { onDismissRequest() }) {
Card(modifier = Modifier.wrapContentSize(align = Alignment.Center).padding(16.dp),
shape = RoundedCornerShape(16.dp)) {
Card(modifier = Modifier.wrapContentSize(align = Alignment.Center).padding(16.dp), shape = RoundedCornerShape(16.dp)) {
val textColor = MaterialTheme.colorScheme.onSurface
Column(modifier = Modifier.padding(16.dp), verticalArrangement = Arrangement.Center) {
Text("Subscribe: \"${feed.title}\" ?")
Text("Subscribe: \"${feed.title}\" ?", color = textColor, modifier = Modifier.padding(bottom = 10.dp))
Button(onClick = {
CoroutineScope(Dispatchers.IO).launch {
if (feed.feedUrl != null) {
val feedBuilder = FeedBuilder(activity) { message, details ->
Logd("OnineFeedItem", "Subscribe error: $message \n $details")
}
val feedBuilder = FeedBuilder(activity) { message, details -> Logd("OnineFeedItem", "Subscribe error: $message \n $details") }
feedBuilder.feedSource = feed.source
feedBuilder.startFeedBuilding(feed.feedUrl,
"",
"") { feed, _ -> feedBuilder.subscribe(feed) }
val url = feed.feedUrl
if (feedBuilder.isYoutube(url)) {
if (feedBuilder.isYoutubeChannel()) {
val nTabs = feedBuilder.youtubeChannelValidTabs()
feedBuilder.buildYTChannel(0, "") { feed, _ -> feedBuilder.subscribe(feed) }
// if (nTabs > 1) showYTChannelDialog = true
// else feedBuilder.buildYTChannel(0, "") { feed, map -> feedBuilder.subscribe(feed) }
} else feedBuilder.buildYTPlaylist { feed, _ -> feedBuilder.subscribe(feed) }
} else feedBuilder.buildPodcast(url, "", "") { feed, _ -> feedBuilder.subscribe(feed) }
}
}
onDismissRequest()
}) {
Text("Confirm")
}
}) { Text("Confirm") }
}
}
}
}
}
if (showSubscribeDialog.value) {
confirmSubscribe(feed, showSubscribeDialog.value, onDismissRequest = {
showSubscribeDialog.value = false
})
}
if (showSubscribeDialog.value) confirmSubscribe(feed, showSubscribeDialog.value, onDismissRequest = { showSubscribeDialog.value = false })

val context = LocalContext.current
Column(Modifier.padding(start = 10.dp, end = 10.dp, top = 4.dp, bottom = 4.dp).combinedClickable(
onClick = {
Expand All @@ -180,11 +175,7 @@ fun OnlineFeedItem(activity: MainActivity, feed: PodcastSearchResult, log: Subsc
}
}, onLongClick = { showSubscribeDialog.value = true })) {
val textColor = MaterialTheme.colorScheme.onSurface
Text(feed.title,
color = textColor,
maxLines = 2,
overflow = TextOverflow.Ellipsis,
modifier = Modifier.padding(bottom = 4.dp))
Text(feed.title, color = textColor, maxLines = 2, overflow = TextOverflow.Ellipsis, modifier = Modifier.padding(bottom = 4.dp))
Row {
ConstraintLayout(modifier = Modifier.width(56.dp).height(56.dp)) {
val (imgvCover, checkMark) = createRefs()
Expand All @@ -196,7 +187,6 @@ fun OnlineFeedItem(activity: MainActivity, feed: PodcastSearchResult, log: Subsc
bottom.linkTo(parent.bottom)
start.linkTo(parent.start)
})

if (feed.feedId > 0 || log != null) {
Logd("OnlineFeedItem", "${feed.feedId} $log")
val alpha = 1.0f
Expand All @@ -215,26 +205,14 @@ fun OnlineFeedItem(activity: MainActivity, feed: PodcastSearchResult, log: Subsc
feed.feedUrl != null && !feed.feedUrl.contains("itunes.apple.com") -> feed.feedUrl
else -> ""
}
if (authorText.isNotEmpty()) Text(authorText,
color = textColor,
style = MaterialTheme.typography.bodyMedium)
if (feed.subscriberCount > 0) Text(MiscFormatter.formatNumber(feed.subscriberCount) + " subscribers",
color = textColor,
style = MaterialTheme.typography.bodyMedium)
if (authorText.isNotEmpty()) Text(authorText, color = textColor, style = MaterialTheme.typography.bodyMedium)
if (feed.subscriberCount > 0) Text(MiscFormatter.formatNumber(feed.subscriberCount) + " subscribers", color = textColor, style = MaterialTheme.typography.bodyMedium)
Row {
if (feed.count != null && feed.count > 0) Text(feed.count.toString() + " episodes",
color = textColor,
style = MaterialTheme.typography.bodyMedium)
if (feed.count != null && feed.count > 0) Text(feed.count.toString() + " episodes", color = textColor, style = MaterialTheme.typography.bodyMedium)
Spacer(Modifier.weight(1f))
if (feed.update != null) Text(feed.update,
color = textColor,
style = MaterialTheme.typography.bodyMedium)
if (feed.update != null) Text(feed.update, color = textColor, style = MaterialTheme.typography.bodyMedium)
}
Text(feed.source + ": " + feed.feedUrl,
color = textColor,
maxLines = 2,
overflow = TextOverflow.Ellipsis,
style = MaterialTheme.typography.labelSmall)
Text(feed.source + ": " + feed.feedUrl, color = textColor, maxLines = 2, overflow = TextOverflow.Ellipsis, style = MaterialTheme.typography.labelSmall)
}
}
}
Expand Down
Loading

0 comments on commit a0d6557

Please sign in to comment.