Skip to content

Commit 063f98d

Browse files
committed
6.6.3 commit
1 parent 86f0cb9 commit 063f98d

File tree

14 files changed

+172
-87
lines changed

14 files changed

+172
-87
lines changed

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@ An open source podcast instrument, attuned to Puccini ![Puccini](./images/Puccin
1212
[<img src="./images/external/getItf-droid.png" alt="F-Droid" height="50">](https://f-droid.org/packages/ac.mdiq.podcini.R/)
1313
[<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)
1414

15-
#### Podcini.R 6.6 introduces the powerful feature of synthetic podcasts, first enables the receiving/handling shared single media from Youtube, for more see the changelogs.
16-
#### Podcini.R version 6.5 as a major step forward brings YouTube channels in the app. They can be searched, subscribed and played from within Podcini. For more see the changelogs
15+
#### Podcini.R 6.6 introduces the powerful feature of synthetic podcasts, enables the receiving/handling shared single media as well as playlist from Youtube, for more see the changelogs.
16+
#### Podcini.R version 6.5 as a major step forward brings YouTube channels in the app. They can be searched, received from share, subscribed and played from within Podcini. For more see the changelogs
1717
#### If you are migrating from Podcini version 5, please read the migrationTo5.md file for migration instructions.
1818
#### For Podcini to show up on car's HUD with Android Auto, please read AnroidAuto.md for instructions.
1919

@@ -28,7 +28,7 @@ Compared to AntennaPod this project:
2828
5. Boasts new UI's including streamlined drawer, subscriptions view and player controller,
2929
6. Supports multiple, virtual and circular play queues associable to any podcast
3030
7. Auto-download is governed by policy and limit settings of individual feed
31-
8. Features synthetic podcasts while supporting normal podcasts, Youtube channels, Youtube media and plain RSS,
31+
8. Features synthetic podcasts while supporting normal podcasts, Youtube channels, Youtube playlists, Youtube media and plain RSS,
3232
9. Offers Readability and Text-to-Speech for RSS contents,s
3333
10. Features `instant sync` across devices without a server.
3434

app/build.gradle

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,8 @@ android {
3131
testApplicationId "ac.mdiq.podcini.tests"
3232
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
3333

34-
versionCode 3020247
35-
versionName "6.6.2"
34+
versionCode 3020248
35+
versionName "6.6.3"
3636

3737
applicationId "ac.mdiq.podcini.R"
3838
def commit = ""

app/src/main/kotlin/ac/mdiq/podcini/playback/service/PlaybackService.kt

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -67,10 +67,8 @@ import ac.mdiq.podcini.util.IntentUtils.sendLocalBroadcast
6767
import ac.mdiq.podcini.util.Logd
6868
import ac.mdiq.podcini.util.config.ClientConfig
6969
import ac.mdiq.vista.extractor.MediaFormat
70-
import ac.mdiq.vista.extractor.Vista
7170
import ac.mdiq.vista.extractor.stream.AudioStream
7271
import ac.mdiq.vista.extractor.stream.DeliveryMethod
73-
import ac.mdiq.vista.extractor.stream.StreamInfo
7472
import ac.mdiq.vista.extractor.stream.VideoStream
7573
import android.annotation.SuppressLint
7674
import android.app.NotificationManager
@@ -1429,8 +1427,8 @@ class PlaybackService : MediaLibraryService() {
14291427
if (media.episode?.feed?.type == Feed.FeedType.YOUTUBE.name) {
14301428
Logd(TAG, "setDataSource1 setting for YouTube source")
14311429
try {
1432-
val vService = Vista.getService(0)
1433-
val streamInfo = StreamInfo.getInfo(vService, url)
1430+
// val vService = Vista.getService(0)
1431+
val streamInfo = media.episode!!.streamInfo ?: return
14341432
val audioStreamsList = getFilteredAudioStreams(streamInfo.audioStreams)
14351433
Logd(TAG, "setDataSource1 audioStreamsList ${audioStreamsList.size}")
14361434
val audioIndex = if (isNetworkRestricted && prefLowQualityMedia) 0 else audioStreamsList.size - 1

app/src/main/kotlin/ac/mdiq/podcini/storage/database/Episodes.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import ac.mdiq.podcini.util.IntentUtils.sendLocalBroadcast
2929
import ac.mdiq.podcini.util.Logd
3030
import ac.mdiq.podcini.util.EventFlow
3131
import ac.mdiq.podcini.util.FlowEvent
32+
import ac.mdiq.vista.extractor.playlist.PlaylistInfo
3233
import ac.mdiq.vista.extractor.stream.StreamInfo
3334
import ac.mdiq.vista.extractor.stream.StreamInfoItem
3435
import android.app.backup.BackupManager
@@ -295,7 +296,7 @@ object Episodes {
295296
val e = Episode()
296297
e.link = item.url
297298
e.title = item.name
298-
e.description = item.shortDescription
299+
e.description = "Short: ${item.shortDescription}"
299300
e.imageUrl = item.thumbnails.first().url
300301
e.setPubDate(item.uploadDate?.date()?.time)
301302
val m = EpisodeMedia(e, item.url, 0, "video/*")

app/src/main/kotlin/ac/mdiq/podcini/storage/model/Episode.kt

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ package ac.mdiq.podcini.storage.model
22

33
import ac.mdiq.podcini.storage.database.Feeds.getFeed
44
import ac.mdiq.podcini.storage.database.RealmDB.unmanaged
5+
import ac.mdiq.vista.extractor.Vista
6+
import ac.mdiq.vista.extractor.stream.StreamInfo
57
import io.realm.kotlin.ext.realmListOf
68
import io.realm.kotlin.ext.realmSetOf
79
import io.realm.kotlin.types.RealmList
@@ -121,6 +123,16 @@ class Episode : RealmObject {
121123
else -> null
122124
}
123125

126+
@Ignore
127+
var streamInfo: StreamInfo? = null
128+
get() {
129+
if (field == null) {
130+
if (media?.downloadUrl == null) return null
131+
field = StreamInfo.getInfo(Vista.getService(0), media!!.downloadUrl!!)
132+
}
133+
return field
134+
}
135+
124136
constructor() {
125137
this.playState = PlayState.UNPLAYED.code
126138
}

app/src/main/kotlin/ac/mdiq/podcini/ui/activity/ShareReceiverActivity.kt

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import ac.mdiq.podcini.storage.database.Feeds.addToYoutubeSyndicate
66
import ac.mdiq.podcini.ui.compose.CustomTheme
77
import ac.mdiq.podcini.util.Logd
88
import ac.mdiq.vista.extractor.Vista
9+
import ac.mdiq.vista.extractor.playlist.PlaylistInfo
910
import ac.mdiq.vista.extractor.stream.StreamInfo
1011
import android.content.DialogInterface
1112
import android.content.Intent
@@ -32,36 +33,36 @@ import kotlinx.coroutines.launch
3233
import java.net.URLDecoder
3334

3435
class ShareReceiverActivity : AppCompatActivity() {
35-
var feedUrl: String? = null
36+
private var sharedUrl: String? = null
3637

3738
@OptIn(UnstableApi::class) override fun onCreate(savedInstanceState: Bundle?) {
3839
super.onCreate(savedInstanceState)
3940

4041
when {
41-
intent.hasExtra(ARG_FEEDURL) -> feedUrl = intent.getStringExtra(ARG_FEEDURL)
42-
intent.action == Intent.ACTION_SEND -> feedUrl = intent.getStringExtra(Intent.EXTRA_TEXT)
43-
intent.action == Intent.ACTION_VIEW -> feedUrl = intent.dataString
42+
intent.hasExtra(ARG_FEEDURL) -> sharedUrl = intent.getStringExtra(ARG_FEEDURL)
43+
intent.action == Intent.ACTION_SEND -> sharedUrl = intent.getStringExtra(Intent.EXTRA_TEXT)
44+
intent.action == Intent.ACTION_VIEW -> sharedUrl = intent.dataString
4445
}
45-
if (feedUrl.isNullOrBlank()) {
46+
if (sharedUrl.isNullOrBlank()) {
4647
Log.e(TAG, "feedUrl is empty or null.")
4748
showNoPodcastFoundError()
4849
return
4950
}
50-
if (!feedUrl!!.startsWith("http")) {
51-
val uri = Uri.parse(feedUrl)
51+
if (!sharedUrl!!.startsWith("http")) {
52+
val uri = Uri.parse(sharedUrl)
5253
val urlString = uri?.getQueryParameter("url")
53-
if (urlString != null) feedUrl = URLDecoder.decode(urlString, "UTF-8")
54+
if (urlString != null) sharedUrl = URLDecoder.decode(urlString, "UTF-8")
5455
}
55-
Logd(TAG, "feedUrl: $feedUrl")
56+
Logd(TAG, "feedUrl: $sharedUrl")
5657
when {
5758
// plain text
58-
feedUrl!!.matches(Regex("^[^\\s<>/]+\$")) -> {
59-
val intent = MainActivity.showOnlineSearch(this, feedUrl!!)
59+
sharedUrl!!.matches(Regex("^[^\\s<>/]+\$")) -> {
60+
val intent = MainActivity.showOnlineSearch(this, sharedUrl!!)
6061
startActivity(intent)
6162
finish()
6263
}
6364
// Youtube media
64-
feedUrl!!.startsWith("https://youtube.com/watch?") -> {
65+
sharedUrl!!.startsWith("https://youtube.com/watch?") -> {
6566
Logd(TAG, "got youtube media")
6667
setContent {
6768
val showDialog = remember { mutableStateOf(true) }
@@ -73,9 +74,10 @@ class ShareReceiverActivity : AppCompatActivity() {
7374
}
7475
}
7576
}
77+
// podcast or Youtube channel, Youtube playlist, or other?
7678
else -> {
77-
Logd(TAG, "Activity was started with url $feedUrl")
78-
val intent = MainActivity.showOnlineFeed(this, feedUrl!!)
79+
Logd(TAG, "Activity was started with url $sharedUrl")
80+
val intent = MainActivity.showOnlineFeed(this, sharedUrl!!)
7981
// intent.putExtra(MainActivity.Extras.started_from_share.name, getIntent().getBooleanExtra(MainActivity.Extras.started_from_share.name, false))
8082
startActivity(intent)
8183
finish()
@@ -111,7 +113,7 @@ class ShareReceiverActivity : AppCompatActivity() {
111113
}
112114
Button(onClick = {
113115
CoroutineScope(Dispatchers.IO).launch {
114-
val info = StreamInfo.getInfo(Vista.getService(0), feedUrl!!)
116+
val info = StreamInfo.getInfo(Vista.getService(0), sharedUrl!!)
115117
Logd(TAG, "info: $info")
116118
val episode = episodeFromStreamInfo(info)
117119
Logd(TAG, "episode: $episode")

app/src/main/kotlin/ac/mdiq/podcini/ui/activity/VideoplayerActivity.kt

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import ac.mdiq.podcini.preferences.UserPreferences.setShowRemainTimeSetting
2626
import ac.mdiq.podcini.preferences.UserPreferences.shouldShowRemainingTime
2727
import ac.mdiq.podcini.preferences.UserPreferences.videoPlayMode
2828
import ac.mdiq.podcini.storage.database.Episodes.setFavorite
29+
import ac.mdiq.podcini.storage.database.RealmDB.upsert
2930
import ac.mdiq.podcini.storage.model.Episode
3031
import ac.mdiq.podcini.storage.model.EpisodeMedia
3132
import ac.mdiq.podcini.storage.model.Playable
@@ -470,7 +471,7 @@ class VideoplayerActivity : CastEnabledActivity() {
470471

471472
private var itemsLoaded = false
472473
private var episode: Episode? = null
473-
private var webviewData: String? = null
474+
private var webviewData: String = ""
474475
private var webvDescription: ShownotesWebView? = null
475476

476477
var destroyingDueToReload = false
@@ -541,7 +542,8 @@ class VideoplayerActivity : CastEnabledActivity() {
541542
return root
542543
}
543544

544-
@OptIn(UnstableApi::class) private fun newStatusHandler(): ServiceStatusHandler {
545+
@OptIn(UnstableApi::class)
546+
private fun newStatusHandler(): ServiceStatusHandler {
545547
return object : ServiceStatusHandler(requireActivity()) {
546548
override fun updatePlayButton(showPlay: Boolean) {
547549
Logd(TAG, "updatePlayButtonShowsPlay called")
@@ -686,10 +688,21 @@ class VideoplayerActivity : CastEnabledActivity() {
686688
lifecycleScope.launch {
687689
try {
688690
episode = withContext(Dispatchers.IO) {
689-
val feedItem = (curMedia as? EpisodeMedia)?.episodeOrFetch()
691+
var feedItem = (curMedia as? EpisodeMedia)?.episodeOrFetch()
690692
if (feedItem != null) {
691693
val duration = feedItem.media?.getDuration() ?: Int.MAX_VALUE
692-
webviewData = ShownotesCleaner(requireContext()).processShownotes(feedItem.description ?: "", duration)
694+
val url = feedItem.media?.downloadUrl
695+
val shownotesCleaner = ShownotesCleaner(requireContext())
696+
if (url?.contains("youtube.com") == true && feedItem.description?.startsWith("Short:") == true) {
697+
Logd(TAG, "getting extended description: ${feedItem.title}")
698+
try {
699+
val info = feedItem.streamInfo
700+
if (info?.description?.content != null) {
701+
feedItem = upsert(feedItem) { it.description = info.description?.content }
702+
webviewData = shownotesCleaner.processShownotes(info.description!!.content, duration)
703+
} else webviewData = shownotesCleaner.processShownotes(episode!!.description ?: "", duration)
704+
} catch (e: Exception) { Logd(TAG, "StreamInfo error: ${e.message}") }
705+
} else webviewData = shownotesCleaner.processShownotes(episode!!.description ?: "", duration)
693706
}
694707
feedItem
695708
}
@@ -702,7 +715,7 @@ class VideoplayerActivity : CastEnabledActivity() {
702715
invalidateOptionsMenu(activity)
703716
}
704717
}
705-
if (webviewData != null && !itemsLoaded) webvDescription?.loadDataWithBaseURL("https://127.0.0.1", webviewData!!,
718+
if (!itemsLoaded) webvDescription?.loadDataWithBaseURL("https://127.0.0.1", webviewData,
706719
"text/html", "utf-8", "about:blank")
707720
itemsLoaded = true
708721
}
@@ -743,21 +756,18 @@ class VideoplayerActivity : CastEnabledActivity() {
743756
SkipPreferenceDialog.showSkipPreference(requireContext(), SkipPreferenceDialog.SkipDirection.SKIP_REWIND, null)
744757
true
745758
}
746-
Logd(TAG, "setupView 1")
747759
binding.playButton.setIsVideoScreen(true)
748760
binding.playButton.setOnClickListener { onPlayPause() }
749761
binding.fastForwardButton.setOnClickListener { onFastForward() }
750762
binding.fastForwardButton.setOnLongClickListener {
751763
SkipPreferenceDialog.showSkipPreference(requireContext(), SkipPreferenceDialog.SkipDirection.SKIP_FORWARD, null)
752764
false
753765
}
754-
Logd(TAG, "setupView 2")
755766
// To suppress touches directly below the slider
756767
binding.bottomControlsContainer.setOnTouchListener { _: View?, _: MotionEvent? -> true }
757768
binding.videoView.holder.addCallback(surfaceHolderCallback)
758769
setupVideoControlsToggler()
759770
binding.videoPlayerContainer.setOnTouchListener(onVideoviewTouched)
760-
Logd(TAG, "setupView 2")
761771
webvDescription = binding.webvDescription
762772
// webvDescription.setTimecodeSelectedListener { time: Int? ->
763773
// val cMedia = getMedia
@@ -775,7 +785,8 @@ class VideoplayerActivity : CastEnabledActivity() {
775785
(activity as? VideoplayerActivity)?.switchToAudioOnly = true
776786
(activity as? VideoplayerActivity)?.finish()
777787
}
778-
Logd(TAG, "setupView 4")
788+
if (!itemsLoaded) webvDescription?.loadDataWithBaseURL("https://127.0.0.1", webviewData,
789+
"text/html", "utf-8", "about:blank")
779790
}
780791

781792
fun toggleVideoControlsVisibility() {

app/src/main/kotlin/ac/mdiq/podcini/ui/adapter/EpisodesAdapter.kt

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -266,7 +266,8 @@ open class EpisodesAdapter(mainActivity: MainActivity, var refreshFragPosCallbac
266266

267267
private var itemLoaded = false
268268
private var episode: Episode? = null // managed
269-
private var webviewData: String? = null
269+
270+
private var webviewData: String = ""
270271

271272
private lateinit var shownotesCleaner: ShownotesCleaner
272273
private lateinit var toolbar: MaterialToolbar
@@ -337,6 +338,7 @@ open class EpisodesAdapter(mainActivity: MainActivity, var refreshFragPosCallbac
337338
}
338339
})
339340
shownotesCleaner = ShownotesCleaner(requireContext())
341+
onFragmentLoaded()
340342
load()
341343
return binding.root
342344
}
@@ -434,8 +436,8 @@ open class EpisodesAdapter(mainActivity: MainActivity, var refreshFragPosCallbac
434436
}
435437

436438
@UnstableApi private fun onFragmentLoaded() {
437-
if (webviewData != null && !itemLoaded)
438-
webvDescription.loadDataWithBaseURL("https://127.0.0.1", webviewData!!, "text/html", "utf-8", "about:blank")
439+
if (!itemLoaded)
440+
webvDescription.loadDataWithBaseURL("https://127.0.0.1", webviewData, "text/html", "utf-8", "about:blank")
439441
// if (item?.link != null) binding.webView.loadUrl(item!!.link!!)
440442
updateAppearance()
441443
}
@@ -652,19 +654,27 @@ open class EpisodesAdapter(mainActivity: MainActivity, var refreshFragPosCallbac
652654
withContext(Dispatchers.IO) {
653655
if (episode != null) {
654656
val duration = episode!!.media?.getDuration() ?: Int.MAX_VALUE
655-
webviewData = shownotesCleaner.processShownotes(episode!!.description ?: "", duration)
657+
Logd(TAG, "description: ${episode?.description}")
658+
val url = episode!!.media?.downloadUrl
659+
if (url?.contains("youtube.com") == true && episode!!.description?.startsWith("Short:") == true) {
660+
Logd(TAG, "getting extended description: ${episode!!.title}")
661+
try {
662+
val info = episode!!.streamInfo
663+
if (info?.description?.content != null) {
664+
episode = upsert(episode!!) { it.description = info.description?.content }
665+
webviewData = shownotesCleaner.processShownotes(info.description!!.content, duration)
666+
} else webviewData = shownotesCleaner.processShownotes(episode!!.description ?: "", duration)
667+
} catch (e: Exception) { Logd(TAG, "StreamInfo error: ${e.message}") }
668+
} else webviewData = shownotesCleaner.processShownotes(episode!!.description ?: "", duration)
656669
}
657670
}
658671
withContext(Dispatchers.Main) {
659672
binding.progbarLoading.visibility = View.GONE
660673
onFragmentLoaded()
661674
itemLoaded = true
662675
}
663-
} catch (e: Throwable) {
664-
Log.e(TAG, Log.getStackTraceString(e))
665-
} finally {
666-
loadItemsRunning = false
667-
}
676+
} catch (e: Throwable) { Log.e(TAG, Log.getStackTraceString(e))
677+
} finally { loadItemsRunning = false }
668678
}
669679
}
670680
}

0 commit comments

Comments
 (0)