Skip to content

Commit

Permalink
4.6.0 commit
Browse files Browse the repository at this point in the history
  • Loading branch information
XilinJia committed Apr 5, 2024
1 parent dce4f39 commit f7adf60
Show file tree
Hide file tree
Showing 21 changed files with 260 additions and 66 deletions.
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ Compared to AntennaPod this project:

Some drastic changes are made in the project since version 4.0. There is now a whole new interface of the Subscriptions page showing only the feeds with tags as filters, no longer having tags as folders in the page. And the default page of the app is changed to the Subscriptions page. Feed list are no longer shown in the drawer. Access to statistics is in the drawer. Alongside, the Home, Echo and Inbox pages are removed from the project. OnlineFeedView activity is stripped down to only receive externally shared feeds. Also, viewbindings are enabled for most views, the project becomes mono-modular, containing only the app module.

Even so, the database remains backward compatible, and AntennaPod's db can be easily imported.

Other notable features and changes include:

* A more convenient player control displayed on all pages
Expand Down Expand Up @@ -48,8 +50,10 @@ Other notable features and changes include:
* Subscriptions view has sorting by "Unread publication date"
* Feed info view offers a link for direct search of feeds related to author
* More info about feeds are shown in the online search view
* Ability to open podcast from webpage address
* Online feed info display is handled in similar ways as any local feed, and offers options to subscribe or view episodes
* Online feed episodes can be freely played (streamed) without a subscription
* Youtube channels are accepted from external share or paste of address in podcast search view, and can be subscribed as a normal podcast, though video play is handled externally
* usesCleartextTraffic (for non-secure content transmission) is now disabled in the project
* Various efficiency improvements, including removal of
* redundant media loadings and ui updates
Expand Down
4 changes: 2 additions & 2 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -149,8 +149,8 @@ android {
// Version code schema (not used):
// "1.2.3-beta4" -> 1020304
// "1.2.3" -> 1020395
versionCode 3020124
versionName "4.5.4"
versionCode 3020125
versionName "4.6.0"

def commit = ""
try {
Expand Down
4 changes: 4 additions & 0 deletions app/src/main/java/ac/mdiq/podcini/feed/parser/SyndHandler.kt
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,10 @@ class SyndHandler(feed: Feed, type: TypeGetter.Type) : DefaultHandler() {
state.namespaces[uri] = Itunes()
Log.d(TAG, "Recognized ITunes namespace")
}
uri == YouTube.NSURI && prefix == YouTube.NSTAG -> {
state.namespaces[uri] = YouTube()
Log.d(TAG, "Recognized YouTube namespace")
}
uri == SimpleChapters.NSURI && prefix.matches(SimpleChapters.NSTAG.toRegex()) -> {
state.namespaces[uri] = SimpleChapters()
Log.d(TAG, "Recognized SimpleChapters namespace")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import org.xml.sax.Attributes

class Atom : Namespace() {
override fun handleElementStart(localName: String, state: HandlerState, attributes: Attributes): SyndElement {
Log.d(TAG, "handleElementStart $localName")
when {
ENTRY == localName -> {
state.currentItem = FeedItem()
Expand Down Expand Up @@ -111,6 +112,7 @@ class Atom : Namespace() {
}

override fun handleElementEnd(localName: String, state: HandlerState) {
Log.d(TAG, "handleElementEnd $localName")
if (ENTRY == localName) {
if (state.currentItem != null &&
state.tempObjects.containsKey(Itunes.DURATION)) {
Expand Down
11 changes: 7 additions & 4 deletions app/src/main/java/ac/mdiq/podcini/feed/parser/namespace/Media.kt
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,9 @@ import java.util.concurrent.TimeUnit
/** Processes tags from the http://search.yahoo.com/mrss/ namespace. */
class Media : Namespace() {
override fun handleElementStart(localName: String, state: HandlerState, attributes: Attributes): SyndElement {
when {
CONTENT == localName -> {
Log.d(TAG, "handleElementStart $localName")
when (localName) {
CONTENT -> {
val url: String? = attributes.getValue(DOWNLOAD_URL)
val defaultStr: String? = attributes.getValue(DEFAULT)
val medium: String? = attributes.getValue(MEDIUM)
Expand Down Expand Up @@ -72,6 +73,7 @@ class Media : Namespace() {
Log.e(TAG, "Duration \"$durationStr\" could not be parsed")
}
}
Log.d(TAG, "handleElementStart creating media: ${state.currentItem?.title} $url $size $mimeType")
val media = FeedMedia(state.currentItem, url, size, mimeType)
if (durationMs > 0) {
media.setDuration( durationMs)
Expand All @@ -83,7 +85,7 @@ class Media : Namespace() {
}
}
}
IMAGE == localName -> {
IMAGE -> {
val url: String? = attributes.getValue(IMAGE_URL)
if (url != null) {
when {
Expand All @@ -98,7 +100,7 @@ class Media : Namespace() {
}
}
}
DESCRIPTION == localName -> {
DESCRIPTION -> {
val type: String? = attributes.getValue(DESCRIPTION_TYPE)
return AtomText(localName, this, type)
}
Expand All @@ -107,6 +109,7 @@ class Media : Namespace() {
}

override fun handleElementEnd(localName: String, state: HandlerState) {
Log.d(TAG, "handleElementEnd $localName")
if (DESCRIPTION == localName) {
val content = state.contentBuf.toString()
state.currentItem?.setDescriptionIfLonger(content)
Expand Down
93 changes: 93 additions & 0 deletions app/src/main/java/ac/mdiq/podcini/feed/parser/namespace/YouTube.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
package ac.mdiq.podcini.feed.parser.namespace

import ac.mdiq.podcini.feed.parser.HandlerState
import android.util.Log
import androidx.core.text.HtmlCompat
import ac.mdiq.podcini.feed.parser.element.SyndElement
import ac.mdiq.podcini.feed.parser.util.DurationParser.inMillis
import org.xml.sax.Attributes

class YouTube : Namespace() {
override fun handleElementStart(localName: String, state: HandlerState, attributes: Attributes): SyndElement {
Log.d(TAG, "handleElementStart $localName")
if (IMAGE == localName) {
val url: String? = attributes.getValue(IMAGE_HREF)

if (state.currentItem != null) {
state.currentItem!!.imageUrl = url
} else {
// this is the feed image
// prefer to all other images
if (!url.isNullOrEmpty()) {
state.feed.imageUrl = url
}
}
}
return SyndElement(localName, this)
}

override fun handleElementEnd(localName: String, state: HandlerState) {
Log.d(TAG, "handleElementEnd $localName")
if (state.contentBuf == null) {
return
}

val content = state.contentBuf.toString()
val contentFromHtml = HtmlCompat.fromHtml(content, HtmlCompat.FROM_HTML_MODE_COMPACT).toString()
if (content.isEmpty()) {
return
}

when {
AUTHOR == localName && state.tagstack.size <= 3 -> {
state.feed.author = contentFromHtml
}
DURATION == localName -> {
try {
val durationMs = inMillis(content)
state.tempObjects[DURATION] = durationMs.toInt()
} catch (e: NumberFormatException) {
Log.e(NSTAG, String.format("Duration '%s' could not be parsed", content))
}
}
SUBTITLE == localName -> {
when {
state.currentItem != null && state.currentItem?.description.isNullOrEmpty() -> {
state.currentItem!!.setDescriptionIfLonger(content)
}
state.feed.description.isNullOrEmpty() -> {
state.feed.description = content
}
}
}
SUMMARY == localName -> {
when {
state.currentItem != null -> {
state.currentItem!!.setDescriptionIfLonger(content)
}
Rss20.CHANNEL == state.secondTag.name -> {
state.feed.description = content
}
}
}
NEW_FEED_URL == localName && content.trim { it <= ' ' }.startsWith("http") -> {
state.redirectUrl = content.trim { it <= ' ' }
}
}
}

companion object {
const val TAG: String = "NSYouTube"
const val NSTAG: String = "yt"
const val NSURI: String = "http://www.youtube.com/xml/schemas/2015"

private const val IMAGE = "thumbnail"
private const val IMAGE_HREF = "href"

private const val AUTHOR = "author"
const val DURATION: String = "duration"
private const val SUBTITLE = "subtitle"
private const val SUMMARY = "summary"
private const val NEW_FEED_URL = "new-feed-url"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ object MimeTypeUtils {
))

val VIDEO_FILE_EXTENSIONS: Set<String> = HashSet(mutableListOf(
"3gp", "mkv", "mp4", "ogg", "ogv", "ogx", "webm"
"3gp", "mkv", "mp4", "ogg", "ogv", "ogx", "webm", "swf"
))

@JvmStatic
Expand All @@ -36,8 +36,8 @@ object MimeTypeUtils {
return if (type == null) {
false
} else {
(type.startsWith("audio/")
|| type.startsWith("video/")) || type == "application/ogg" || type == "application/octet-stream"
type.startsWith("audio/") || type.startsWith("video/") ||
type == "application/ogg" || type == "application/octet-stream" || type == "application/x-shockwave-flash"
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,6 @@ class TypeGetter {
reader = createReader(feed)
xpp.setInput(reader)
var eventType = xpp.eventType
// TODO: need to check about handling webpage
// return return Type.ATOM

while (eventType != XmlPullParser.END_DOCUMENT) {
if (eventType == XmlPullParser.START_TAG) {
when (val tag = xpp.name) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ object DownloadRequestCreator {
}
Log.d(TAG, "Requesting download feed from url " + feed.download_url)

val username = if ((feed.preferences != null)) feed.preferences!!.username else null
val password = if ((feed.preferences != null)) feed.preferences!!.password else null
val username = feed.preferences?.username
val password = feed.preferences?.password

return DownloadRequest.Builder(dest.toString(), feed)
.withAuthentication(username, password)
Expand All @@ -36,10 +36,11 @@ object DownloadRequestCreator {

@JvmStatic
fun create(media: FeedMedia): DownloadRequest.Builder {
val partiallyDownloadedFileExists = media.file_url != null && File(media.file_url!!).exists()
val pdFile = if (media.file_url != null) File(media.file_url!!) else null
val partiallyDownloadedFileExists = pdFile?.exists() ?: false
var dest: File
dest = if (partiallyDownloadedFileExists) {
File(media.file_url!!)
pdFile!!
} else {
File(getMediafilePath(media), getMediafilename(media))
}
Expand All @@ -49,8 +50,8 @@ object DownloadRequestCreator {
}
Log.d(TAG, "Requesting download media from url " + media.download_url)

val username = if ((media.item?.feed?.preferences != null)) media.item!!.feed!!.preferences!!.username else null
val password = if ((media.item?.feed?.preferences != null)) media.item!!.feed!!.preferences!!.password else null
val username = media.item?.feed?.preferences?.username
val password = media.item?.feed?.preferences?.password

return DownloadRequest.Builder(dest.toString(), media).withAuthentication(username, password)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ class EpisodeDownloadWorker(context: Context, params: WorkerParameters) : Worker
return retry3times()
}

Log.e(TAG, "Download failed")
Log.e(TAG, "Download failed ${request.title} ${status.reason}")
DBWriter.addDownloadStatus(status)
if (status.reason == DownloadError.ERROR_FORBIDDEN || status.reason == DownloadError.ERROR_NOT_FOUND || status.reason == DownloadError.ERROR_UNAUTHORIZED || status.reason == DownloadError.ERROR_IO_BLOCKED) {
// Fail fast, these are probably unrecoverable
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ class MediaDownloadedHandler(private val context: Context, var updatedStatus: Do
Log.d(TAG, "Invalid file duration: $durationStr")
} catch (e: Exception) {
Log.e(TAG, "Get duration failed", e)
media.setDuration(30000)
}

val item = media.item
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
package ac.mdiq.podcini.storage.model.playback

enum class MediaType {
AUDIO, VIDEO, UNKNOWN;
AUDIO, VIDEO, FLASH, UNKNOWN;

companion object {
private val AUDIO_APPLICATION_MIME_STRINGS: Set<String> = HashSet(mutableListOf(
"application/ogg",
"application/opus",
"application/x-flac"
))
private val VIDEO_APPLICATION_MIME_STRINGS: Set<String> = HashSet(mutableListOf(
"application/x-shockwave-flash"
))

fun fromMimeType(mimeType: String?): MediaType {
return when {
Expand All @@ -24,6 +27,9 @@ enum class MediaType {
AUDIO_APPLICATION_MIME_STRINGS.contains(mimeType) -> {
AUDIO
}
VIDEO_APPLICATION_MIME_STRINGS.contains(mimeType) -> {
FLASH
}
else -> UNKNOWN
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@ class OnlineFeedViewActivity : AppCompatActivity() {

if (!feedUrl.isNullOrBlank() && !feedUrl.startsWith("http")) {
val uri = Uri.parse(feedUrl)
feedUrl = URLDecoder.decode(uri.getQueryParameter("url"), "UTF-8")
val urlString = uri?.getQueryParameter("url")
if (urlString != null) feedUrl = URLDecoder.decode(urlString, "UTF-8")
}

if (feedUrl == null) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
package ac.mdiq.podcini.ui.adapter.actionbutton

import ac.mdiq.podcini.net.download.serviceinterface.DownloadServiceInterface
import ac.mdiq.podcini.preferences.UserPreferences.isStreamOverDownload
import ac.mdiq.podcini.storage.model.feed.FeedItem
import ac.mdiq.podcini.storage.model.playback.MediaType
import ac.mdiq.podcini.util.PlaybackStatus.isCurrentlyPlaying
import android.content.Context
import android.view.View
import android.widget.ImageView
import androidx.media3.common.util.UnstableApi
import ac.mdiq.podcini.util.PlaybackStatus.isCurrentlyPlaying
import ac.mdiq.podcini.storage.model.feed.FeedItem
import ac.mdiq.podcini.net.download.serviceinterface.DownloadServiceInterface
import ac.mdiq.podcini.preferences.UserPreferences.isStreamOverDownload
import ac.mdiq.podcini.storage.model.playback.RemoteMedia

abstract class ItemActionButton internal constructor(@JvmField var item: FeedItem) {
abstract fun getLabel(): Int
Expand Down Expand Up @@ -36,6 +36,9 @@ abstract class ItemActionButton internal constructor(@JvmField var item: FeedIte
else -> DownloadServiceInterface.get()?.isDownloadingEpisode(media.download_url!!)?:false
}
return when {
media.getMediaType() == MediaType.FLASH -> {
VisitWebsiteActionButton(item)
}
isCurrentlyPlaying(media) -> {
PauseActionButton(item)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import ac.mdiq.podcini.preferences.UserPreferences
import ac.mdiq.podcini.storage.DBReader
import ac.mdiq.podcini.storage.model.feed.FeedItem
import ac.mdiq.podcini.storage.model.feed.FeedMedia
import ac.mdiq.podcini.storage.model.playback.MediaType
import ac.mdiq.podcini.ui.activity.MainActivity
import ac.mdiq.podcini.ui.adapter.actionbutton.*
import ac.mdiq.podcini.ui.common.CircularProgressBar
Expand Down Expand Up @@ -343,6 +344,9 @@ class EpisodeInfoFragment : Fragment(), Toolbar.OnMenuItemClickListener {
}
if (item != null) {
actionButton1 = when {
media.getMediaType() == MediaType.FLASH -> {
VisitWebsiteActionButton(item!!)
}
PlaybackStatus.isCurrentlyPlaying(media) -> {
PauseActionButton(item!!)
}
Expand All @@ -357,6 +361,9 @@ class EpisodeInfoFragment : Fragment(), Toolbar.OnMenuItemClickListener {
}
}
actionButton2 = when {
media.getMediaType() == MediaType.FLASH -> {
VisitWebsiteActionButton(item!!)
}
dls != null && media.download_url != null && dls.isDownloadingEpisode(media.download_url!!) -> {
CancelDownloadActionButton(item!!)
}
Expand All @@ -367,6 +374,7 @@ class EpisodeInfoFragment : Fragment(), Toolbar.OnMenuItemClickListener {
DeleteActionButton(item!!)
}
}
// if (actionButton2 != null && media.getMediaType() == MediaType.FLASH) actionButton2!!.visibility = View.GONE
}
}

Expand Down
Loading

0 comments on commit f7adf60

Please sign in to comment.