4.10.1 commit

This commit is contained in:
Xilin Jia 2024-05-06 10:03:28 +01:00
parent b719267656
commit eeea4bfd17
10 changed files with 122 additions and 345 deletions

View File

@ -158,8 +158,8 @@ android {
// Version code schema (not used):
// "1.2.3-beta4" -> 1020304
// "1.2.3" -> 1020395
versionCode 3020138
versionName "4.10.0"
versionCode 3020139
versionName "4.10.1"
def commit = ""
try {

View File

@ -856,7 +856,7 @@ class LocalMediaPlayer(context: Context, callback: PSMPCallback) : MediaPlayerBa
}
companion object {
private const val TAG = "LclPlaybackSvcMPlayer"
private const val TAG = "LocalMediaPlayer"
// from wrapper
const val BUFFERING_STARTED: Int = -1

View File

@ -360,14 +360,12 @@ class PodDBAdapter private constructor() {
}
fun resetPagedFeedPage(feed: Feed) {
val sql = ("UPDATE " + TABLE_NAME_FEEDS + " SET " + KEY_NEXT_PAGE_LINK + "=" + KEY_DOWNLOAD_URL + " WHERE " + KEY_ID + "=" + feed.id)
val sql = ("UPDATE $TABLE_NAME_FEEDS SET $KEY_NEXT_PAGE_LINK=$KEY_DOWNLOAD_URL WHERE $KEY_ID=${feed.id}")
db.execSQL(sql)
}
fun setFeedLastUpdateFailed(feedId: Long, failed: Boolean) {
val sql = ("UPDATE " + TABLE_NAME_FEEDS
+ " SET " + KEY_LAST_UPDATE_FAILED + "=" + (if (failed) "1" else "0")
+ " WHERE " + KEY_ID + "=" + feedId)
val sql = ("UPDATE $TABLE_NAME_FEEDS SET $KEY_LAST_UPDATE_FAILED=${if (failed) "1" else "0"} WHERE $KEY_ID=$feedId")
db.execSQL(sql)
}
@ -431,7 +429,7 @@ class PodDBAdapter private constructor() {
}
fun removeFavoriteItem(item: FeedItem) {
val deleteClause = String.format("DELETE FROM %s WHERE %s=%s AND %s=%s", TABLE_NAME_FAVORITES, KEY_FEEDITEM, item.id, KEY_FEED, item.feedId)
val deleteClause = "DELETE FROM $TABLE_NAME_FAVORITES WHERE $KEY_FEEDITEM=${item.id} AND $KEY_FEED=${item.feedId}"
db.execSQL(deleteClause)
}
@ -485,8 +483,8 @@ class PodDBAdapter private constructor() {
db.beginTransactionNonExclusive()
db.delete(TABLE_NAME_SIMPLECHAPTERS, "$KEY_FEEDITEM IN ($itemIds)", null)
db.delete(TABLE_NAME_DOWNLOAD_LOG, (KEY_FEEDFILETYPE + "=" + FeedMedia.FEEDFILETYPE_FEEDMEDIA)
+ " AND " + KEY_FEEDFILE + " IN (" + mediaIds + ")", null)
db.delete(TABLE_NAME_DOWNLOAD_LOG,
"$KEY_FEEDFILETYPE=${FeedMedia.FEEDFILETYPE_FEEDMEDIA} AND $KEY_FEEDFILE IN ($mediaIds)", null)
db.delete(TABLE_NAME_FEED_MEDIA, "$KEY_ID IN ($mediaIds)", null)
db.delete(TABLE_NAME_FEED_ITEMS, "$KEY_ID IN ($itemIds)", null)
db.setTransactionSuccessful()
@ -588,25 +586,21 @@ class PodDBAdapter private constructor() {
}
fun getTranscriptOfItem(item: FeedItem): Cursor {
val query = ("SELECT " + KEY_TRANSCRIPT + " FROM " + TABLE_NAME_FEED_ITEMS + " WHERE " + KEY_ID + "=" + item.id)
val query = ("SELECT $KEY_TRANSCRIPT FROM $TABLE_NAME_FEED_ITEMS WHERE $KEY_ID=${item.id}")
return db.rawQuery(query, null)
}
fun getSimpleChaptersOfFeedItemCursor(item: FeedItem): Cursor {
return db.query(TABLE_NAME_SIMPLECHAPTERS, null, "$KEY_FEEDITEM=?", arrayOf(item.id.toString()),
null, null, null)
return db.query(TABLE_NAME_SIMPLECHAPTERS, null, "$KEY_FEEDITEM=?", arrayOf(item.id.toString()), null, null, null)
}
fun getDownloadLog(feedFileType: Int, feedFileId: Long): Cursor {
val query = ("SELECT * FROM " + TABLE_NAME_DOWNLOAD_LOG +
" WHERE " + KEY_FEEDFILE + "=" + feedFileId + " AND " + KEY_FEEDFILETYPE + "=" + feedFileType
+ " ORDER BY " + KEY_ID + " DESC")
val query = ("SELECT * FROM $TABLE_NAME_DOWNLOAD_LOG WHERE $KEY_FEEDFILE=$feedFileId AND $KEY_FEEDFILETYPE=$feedFileType ORDER BY $KEY_ID DESC")
return db.rawQuery(query, null)
}
fun getDownloadLogCursor(limit: Int): Cursor {
return db.query(TABLE_NAME_DOWNLOAD_LOG, null, null, null, null,
null, "$KEY_COMPLETION_DATE DESC LIMIT $limit")
return db.query(TABLE_NAME_DOWNLOAD_LOG, null, null, null, null, null, "$KEY_COMPLETION_DATE DESC LIMIT $limit")
}
val queueCursor: Cursor
@ -616,12 +610,7 @@ class PodDBAdapter private constructor() {
* cursor uses the FEEDITEM_SEL_FI_SMALL selection.
*/
get() {
val query = ("SELECT " + KEYS_FEED_ITEM_WITHOUT_DESCRIPTION + ", " + KEYS_FEED_MEDIA
+ " FROM " + TABLE_NAME_QUEUE
+ " INNER JOIN " + TABLE_NAME_FEED_ITEMS
+ " ON " + SELECT_KEY_ITEM_ID + " = " + TABLE_NAME_QUEUE + "." + KEY_FEEDITEM
+ JOIN_FEED_ITEM_AND_MEDIA
+ " ORDER BY " + TABLE_NAME_QUEUE + "." + KEY_ID)
val query = ("SELECT $KEYS_FEED_ITEM_WITHOUT_DESCRIPTION, $KEYS_FEED_MEDIA FROM $TABLE_NAME_QUEUE INNER JOIN $TABLE_NAME_FEED_ITEMS ON $SELECT_KEY_ITEM_ID = $TABLE_NAME_QUEUE.$KEY_FEEDITEM$JOIN_FEED_ITEM_AND_MEDIA ORDER BY $TABLE_NAME_QUEUE.$KEY_ID")
return db.rawQuery(query, null)
}
@ -629,43 +618,19 @@ class PodDBAdapter private constructor() {
get() = db.query(TABLE_NAME_QUEUE, arrayOf(KEY_FEEDITEM), null, null, null, null, "$KEY_ID ASC", null)
fun getNextInQueue(item: FeedItem): Cursor {
val query = ("SELECT " + KEYS_FEED_ITEM_WITHOUT_DESCRIPTION + ", " + KEYS_FEED_MEDIA
+ " FROM " + TABLE_NAME_QUEUE
+ " INNER JOIN " + TABLE_NAME_FEED_ITEMS
+ " ON " + SELECT_KEY_ITEM_ID + " = " + TABLE_NAME_QUEUE + "." + KEY_FEEDITEM
+ JOIN_FEED_ITEM_AND_MEDIA
+ " WHERE Queue.ID > (SELECT Queue.ID FROM Queue WHERE Queue.FeedItem = "
+ item.id
+ ")"
+ " ORDER BY Queue.ID"
+ " LIMIT 1")
val query = ("SELECT $KEYS_FEED_ITEM_WITHOUT_DESCRIPTION, $KEYS_FEED_MEDIA FROM $TABLE_NAME_QUEUE INNER JOIN $TABLE_NAME_FEED_ITEMS ON $SELECT_KEY_ITEM_ID = $TABLE_NAME_QUEUE.$KEY_FEEDITEM$JOIN_FEED_ITEM_AND_MEDIA WHERE Queue.ID > (SELECT Queue.ID FROM Queue WHERE Queue.FeedItem = ${item.id}) ORDER BY Queue.ID LIMIT 1")
return db.rawQuery(query, null)
}
fun getPausedQueueCursor(limit: Int): Cursor {
val hasPositionOrRecentlyPlayed = (TABLE_NAME_FEED_MEDIA + "." + KEY_POSITION + " >= 1000"
+ " OR " + TABLE_NAME_FEED_MEDIA + "." + KEY_LAST_PLAYED_TIME
+ " >= " + (System.currentTimeMillis() - 30000))
val query = ("SELECT " + KEYS_FEED_ITEM_WITHOUT_DESCRIPTION + ", " + KEYS_FEED_MEDIA
+ " FROM " + TABLE_NAME_QUEUE
+ " INNER JOIN " + TABLE_NAME_FEED_ITEMS
+ " ON " + SELECT_KEY_ITEM_ID + " = " + TABLE_NAME_QUEUE + "." + KEY_FEEDITEM
+ JOIN_FEED_ITEM_AND_MEDIA
+ " ORDER BY (CASE WHEN " + hasPositionOrRecentlyPlayed + " THEN "
+ TABLE_NAME_FEED_MEDIA + "." + KEY_LAST_PLAYED_TIME + " ELSE 0 END) DESC , "
+ TABLE_NAME_QUEUE + "." + KEY_ID
+ " LIMIT " + limit)
val hasPositionOrRecentlyPlayed = ("$TABLE_NAME_FEED_MEDIA.$KEY_POSITION >= 1000 OR $TABLE_NAME_FEED_MEDIA.$KEY_LAST_PLAYED_TIME >= ${System.currentTimeMillis() - 30000}")
val query = ("SELECT $KEYS_FEED_ITEM_WITHOUT_DESCRIPTION, $KEYS_FEED_MEDIA FROM $TABLE_NAME_QUEUE INNER JOIN $TABLE_NAME_FEED_ITEMS ON $SELECT_KEY_ITEM_ID = $TABLE_NAME_QUEUE.$KEY_FEEDITEM$JOIN_FEED_ITEM_AND_MEDIA ORDER BY (CASE WHEN $hasPositionOrRecentlyPlayed THEN $TABLE_NAME_FEED_MEDIA.$KEY_LAST_PLAYED_TIME ELSE 0 END) DESC , $TABLE_NAME_QUEUE.$KEY_ID LIMIT $limit")
return db.rawQuery(query, null)
}
fun getFavoritesIdsCursor(offset: Int, limit: Int): Cursor {
// Way faster than selecting all columns
val query = ("SELECT " + TABLE_NAME_FEED_ITEMS + "." + KEY_ID
+ " FROM " + TABLE_NAME_FEED_ITEMS
+ " INNER JOIN " + TABLE_NAME_FAVORITES
+ " ON " + TABLE_NAME_FEED_ITEMS + "." + KEY_ID + " = " + TABLE_NAME_FAVORITES + "." + KEY_FEEDITEM
+ " ORDER BY " + TABLE_NAME_FEED_ITEMS + "." + KEY_PUBDATE + " DESC"
+ " LIMIT " + offset + ", " + limit)
val query = ("SELECT $TABLE_NAME_FEED_ITEMS.$KEY_ID FROM $TABLE_NAME_FEED_ITEMS INNER JOIN $TABLE_NAME_FAVORITES ON $TABLE_NAME_FEED_ITEMS.$KEY_ID = $TABLE_NAME_FAVORITES.$KEY_FEEDITEM ORDER BY $TABLE_NAME_FEED_ITEMS.$KEY_PUBDATE DESC LIMIT $offset, $limit")
return db.rawQuery(query, null)
}
@ -688,28 +653,22 @@ class PodDBAdapter private constructor() {
val orderByQuery = generateFrom(sortOrder)
val filterQuery = generateFrom(filter!!)
val whereClause = if ("" == filterQuery) "" else " WHERE $filterQuery"
val query = (SELECT_FEED_ITEMS_AND_MEDIA + whereClause + "ORDER BY " + orderByQuery + " LIMIT " + offset + ", " + limit)
val query = ("$SELECT_FEED_ITEMS_AND_MEDIA${whereClause}ORDER BY $orderByQuery LIMIT $offset, $limit")
return db.rawQuery(query, null)
}
fun getEpisodeCountCursor(filter: FeedItemFilter?): Cursor {
val filterQuery = generateFrom(filter!!)
val whereClause = if ("" == filterQuery) "" else " WHERE $filterQuery"
val query = ("SELECT count(" + TABLE_NAME_FEED_ITEMS + "." + KEY_ID + ") FROM " + TABLE_NAME_FEED_ITEMS
+ JOIN_FEED_ITEM_AND_MEDIA + whereClause)
val query = ("SELECT count($TABLE_NAME_FEED_ITEMS.$KEY_ID) FROM $TABLE_NAME_FEED_ITEMS$JOIN_FEED_ITEM_AND_MEDIA$whereClause")
return db.rawQuery(query, null)
}
fun getRandomEpisodesCursor(limit: Int, seed: Int): Cursor {
val allItemsRandomOrder = (SELECT_FEED_ITEMS_AND_MEDIA
+ " WHERE (" + KEY_READ + " = " + FeedItem.NEW + " OR " + KEY_READ + " = " + FeedItem.UNPLAYED + ") " // Only from the last two years. Older episodes often contain broken covers and stuff like that
+ " AND " + KEY_PUBDATE + " > " + (System.currentTimeMillis() - 1000L * 3600L * 24L * 356L * 2) // Hide episodes that have been played but not completed
+ " AND (" + KEY_LAST_PLAYED_TIME + " == 0"
+ " OR " + KEY_LAST_PLAYED_TIME + " > " + (System.currentTimeMillis() - 1000L * 3600L) + ")"
+ " ORDER BY " + randomEpisodeNumber(seed))
val query = ("SELECT * FROM (" + allItemsRandomOrder + ")"
+ " GROUP BY " + KEY_FEED
+ " ORDER BY " + randomEpisodeNumber(seed * 3) + " DESC LIMIT " + limit)
// Only from the last two years. Older episodes often contain broken covers and stuff like that
// Hide episodes that have been played but not completed
val allItemsRandomOrder = ("$SELECT_FEED_ITEMS_AND_MEDIA WHERE ($KEY_READ = ${FeedItem.NEW} OR $KEY_READ = ${FeedItem.UNPLAYED}) AND $KEY_PUBDATE > ${System.currentTimeMillis() - 1000L * 3600L * 24L * 356L * 2} AND ($KEY_LAST_PLAYED_TIME == 0 OR $KEY_LAST_PLAYED_TIME > ${System.currentTimeMillis() - 1000L * 3600L}) ORDER BY ${randomEpisodeNumber(seed)}")
val query = ("SELECT * FROM ($allItemsRandomOrder) GROUP BY $KEY_FEED ORDER BY ${randomEpisodeNumber(seed * 3)} DESC LIMIT $limit")
return db.rawQuery(query, null)
}
@ -746,9 +705,7 @@ class PodDBAdapter private constructor() {
}
fun getFeedCursor(id: Long): Cursor {
val query = ("SELECT " + KEYS_FEED
+ " FROM " + TABLE_NAME_FEEDS
+ " WHERE " + SELECT_KEY_FEED_ID + " = " + id)
val query = ("SELECT $KEYS_FEED FROM $TABLE_NAME_FEEDS WHERE $SELECT_KEY_FEED_ID = $id")
return db.rawQuery(query, null)
}
@ -782,88 +739,41 @@ class PodDBAdapter private constructor() {
whereClauseCondition = "$TABLE_NAME_FEED_ITEMS.$KEY_ITEM_IDENTIFIER=$escapedGuid"
}
val query = (SELECT_FEED_ITEMS_AND_MEDIA
+ " INNER JOIN " + TABLE_NAME_FEEDS
+ " ON " + TABLE_NAME_FEED_ITEMS + "." + KEY_FEED + "=" + TABLE_NAME_FEEDS + "." + KEY_ID
+ " WHERE " + whereClauseCondition)
val query = ("$SELECT_FEED_ITEMS_AND_MEDIA INNER JOIN $TABLE_NAME_FEEDS ON $TABLE_NAME_FEED_ITEMS.$KEY_FEED=$TABLE_NAME_FEEDS.$KEY_ID WHERE $whereClauseCondition")
return db.rawQuery(query, null)
}
fun getImageAuthenticationCursor(imageUrl: String?): Cursor {
val downloadUrl = DatabaseUtils.sqlEscapeString(imageUrl)
val query = (""
+ "SELECT " + KEY_USERNAME + "," + KEY_PASSWORD + " FROM " + TABLE_NAME_FEED_ITEMS
+ " INNER JOIN " + TABLE_NAME_FEEDS
+ " ON " + TABLE_NAME_FEED_ITEMS + "." + KEY_FEED + " = " + TABLE_NAME_FEEDS + "." + KEY_ID
+ " WHERE " + TABLE_NAME_FEED_ITEMS + "." + KEY_IMAGE_URL + "=" + downloadUrl
+ " UNION SELECT " + KEY_USERNAME + "," + KEY_PASSWORD + " FROM " + TABLE_NAME_FEEDS
+ " WHERE " + TABLE_NAME_FEEDS + "." + KEY_IMAGE_URL + "=" + downloadUrl)
val query = ("SELECT $KEY_USERNAME,$KEY_PASSWORD FROM $TABLE_NAME_FEED_ITEMS INNER JOIN $TABLE_NAME_FEEDS ON $TABLE_NAME_FEED_ITEMS.$KEY_FEED = $TABLE_NAME_FEEDS.$KEY_ID WHERE $TABLE_NAME_FEED_ITEMS.$KEY_IMAGE_URL=$downloadUrl UNION SELECT $KEY_USERNAME,$KEY_PASSWORD FROM $TABLE_NAME_FEEDS WHERE $TABLE_NAME_FEEDS.$KEY_IMAGE_URL=$downloadUrl")
return db.rawQuery(query, null)
}
val monthlyStatisticsCursor: Cursor
get() {
val query = ("SELECT SUM(" + KEY_PLAYED_DURATION + ") AS total_duration"
+ ", strftime('%m', datetime(" + KEY_LAST_PLAYED_TIME + "/1000, 'unixepoch')) AS month"
+ ", strftime('%Y', datetime(" + KEY_LAST_PLAYED_TIME + "/1000, 'unixepoch')) AS year"
+ " FROM " + TABLE_NAME_FEED_MEDIA
+ " WHERE " + KEY_LAST_PLAYED_TIME + " > 0 AND " + KEY_PLAYED_DURATION + " > 0"
+ " GROUP BY year, month"
+ " ORDER BY year, month")
val query = ("SELECT SUM($KEY_PLAYED_DURATION) AS total_duration, strftime('%m', datetime($KEY_LAST_PLAYED_TIME/1000, 'unixepoch')) AS month, strftime('%Y', datetime($KEY_LAST_PLAYED_TIME/1000, 'unixepoch')) AS year FROM $TABLE_NAME_FEED_MEDIA WHERE $KEY_LAST_PLAYED_TIME > 0 AND $KEY_PLAYED_DURATION > 0 GROUP BY year, month ORDER BY year, month")
return db.rawQuery(query, null)
}
fun getFeedStatisticsCursor(includeMarkedAsPlayed: Boolean, timeFilterFrom: Long, timeFilterTo: Long): Cursor {
val lastPlayedTime = "$TABLE_NAME_FEED_MEDIA.$KEY_LAST_PLAYED_TIME"
var wasStarted = (TABLE_NAME_FEED_MEDIA + "." + KEY_PLAYBACK_COMPLETION_DATE + " > 0"
+ " AND " + TABLE_NAME_FEED_MEDIA + "." + KEY_PLAYED_DURATION + " > 0")
var wasStarted = ("$TABLE_NAME_FEED_MEDIA.$KEY_PLAYBACK_COMPLETION_DATE > 0 AND $TABLE_NAME_FEED_MEDIA.$KEY_PLAYED_DURATION > 0")
if (includeMarkedAsPlayed) {
wasStarted = ("(" + wasStarted + ") OR "
+ TABLE_NAME_FEED_ITEMS + "." + KEY_READ + "=" + FeedItem.PLAYED + " OR "
+ TABLE_NAME_FEED_MEDIA + "." + KEY_POSITION + "> 0")
wasStarted = ("($wasStarted) OR $TABLE_NAME_FEED_ITEMS.$KEY_READ=${FeedItem.PLAYED} OR $TABLE_NAME_FEED_MEDIA.$KEY_POSITION> 0")
}
val timeFilter = ("$lastPlayedTime>=$timeFilterFrom AND $lastPlayedTime<$timeFilterTo")
var playedTime = "$TABLE_NAME_FEED_MEDIA.$KEY_PLAYED_DURATION"
if (includeMarkedAsPlayed) {
playedTime = ("(CASE WHEN " + playedTime + " != 0"
+ " THEN " + playedTime + " ELSE ("
+ "CASE WHEN " + TABLE_NAME_FEED_ITEMS + "." + KEY_READ + "=" + FeedItem.PLAYED
+ " THEN " + TABLE_NAME_FEED_MEDIA + "." + KEY_DURATION + " ELSE 0 END"
+ ") END)")
playedTime = ("(CASE WHEN $playedTime != 0 THEN $playedTime ELSE (CASE WHEN $TABLE_NAME_FEED_ITEMS.$KEY_READ=${FeedItem.PLAYED} THEN $TABLE_NAME_FEED_MEDIA.$KEY_DURATION ELSE 0 END) END)")
}
val query = ("SELECT " + KEYS_FEED + ", "
+ "COUNT(*) AS num_episodes, "
+ "MIN(CASE WHEN " + lastPlayedTime + " > 0"
+ " THEN " + lastPlayedTime + " ELSE " + Long.MAX_VALUE + " END) AS oldest_date, "
+ "SUM(CASE WHEN (" + wasStarted + ") THEN 1 ELSE 0 END) AS episodes_started, "
+ "IFNULL(SUM(CASE WHEN (" + timeFilter + ")"
+ " THEN (" + playedTime + ") ELSE 0 END), 0) AS played_time, "
+ "IFNULL(SUM(" + TABLE_NAME_FEED_MEDIA + "." + KEY_DURATION + "), 0) AS total_time, "
+ "SUM(CASE WHEN " + TABLE_NAME_FEED_MEDIA + "." + KEY_DOWNLOADED + " > 0"
+ " THEN 1 ELSE 0 END) AS num_downloaded, "
+ "SUM(CASE WHEN " + TABLE_NAME_FEED_MEDIA + "." + KEY_DOWNLOADED + " > 0"
+ " THEN " + TABLE_NAME_FEED_MEDIA + "." + KEY_SIZE + " ELSE 0 END) AS download_size"
+ " FROM " + TABLE_NAME_FEED_ITEMS
+ JOIN_FEED_ITEM_AND_MEDIA
+ " INNER JOIN " + TABLE_NAME_FEEDS
+ " ON " + TABLE_NAME_FEED_ITEMS + "." + KEY_FEED + "=" + TABLE_NAME_FEEDS + "." + KEY_ID
+ " GROUP BY " + TABLE_NAME_FEEDS + "." + KEY_ID)
val query = ("SELECT $KEYS_FEED, COUNT(*) AS num_episodes, MIN(CASE WHEN $lastPlayedTime > 0 THEN $lastPlayedTime ELSE ${Long.MAX_VALUE} END) AS oldest_date, SUM(CASE WHEN ($wasStarted) THEN 1 ELSE 0 END) AS episodes_started, IFNULL(SUM(CASE WHEN ($timeFilter) THEN ($playedTime) ELSE 0 END), 0) AS played_time, IFNULL(SUM($TABLE_NAME_FEED_MEDIA.$KEY_DURATION), 0) AS total_time, SUM(CASE WHEN $TABLE_NAME_FEED_MEDIA.$KEY_DOWNLOADED > 0 THEN 1 ELSE 0 END) AS num_downloaded, SUM(CASE WHEN $TABLE_NAME_FEED_MEDIA.$KEY_DOWNLOADED > 0 THEN $TABLE_NAME_FEED_MEDIA.$KEY_SIZE ELSE 0 END) AS download_size FROM $TABLE_NAME_FEED_ITEMS$JOIN_FEED_ITEM_AND_MEDIA INNER JOIN $TABLE_NAME_FEEDS ON $TABLE_NAME_FEED_ITEMS.$KEY_FEED=$TABLE_NAME_FEEDS.$KEY_ID GROUP BY $TABLE_NAME_FEEDS.$KEY_ID")
return db.rawQuery(query, null)
}
fun getTimeBetweenReleaseAndPlayback(timeFilterFrom: Long, timeFilterTo: Long): Cursor {
val from = (" FROM " + TABLE_NAME_FEED_ITEMS
+ JOIN_FEED_ITEM_AND_MEDIA
+ " WHERE " + TABLE_NAME_FEED_MEDIA + "." + KEY_LAST_PLAYED_TIME + ">=" + timeFilterFrom
+ " AND " + TABLE_NAME_FEED_ITEMS + "." + KEY_PUBDATE + ">=" + timeFilterFrom
+ " AND " + TABLE_NAME_FEED_MEDIA + "." + KEY_LAST_PLAYED_TIME + "<" + timeFilterTo)
val query = ("SELECT " + TABLE_NAME_FEED_MEDIA + "." + KEY_LAST_PLAYED_TIME
+ " - " + TABLE_NAME_FEED_ITEMS + "." + KEY_PUBDATE + " AS diff"
+ from
+ " ORDER BY diff ASC"
+ " LIMIT 1"
+ " OFFSET (SELECT count(*)/2 " + from + ")")
val from = (" FROM $TABLE_NAME_FEED_ITEMS$JOIN_FEED_ITEM_AND_MEDIA WHERE $TABLE_NAME_FEED_MEDIA.$KEY_LAST_PLAYED_TIME>=$timeFilterFrom AND $TABLE_NAME_FEED_ITEMS.$KEY_PUBDATE>=$timeFilterFrom AND $TABLE_NAME_FEED_MEDIA.$KEY_LAST_PLAYED_TIME<$timeFilterTo")
val query = ("SELECT $TABLE_NAME_FEED_MEDIA.$KEY_LAST_PLAYED_TIME - $TABLE_NAME_FEED_ITEMS.$KEY_PUBDATE AS diff$from ORDER BY diff ASC LIMIT 1 OFFSET (SELECT count(*)/2 $from)")
return db.rawQuery(query, null)
}
@ -906,12 +816,7 @@ class PodDBAdapter private constructor() {
limitFeeds = "$KEY_FEED IN ($builder) AND "
}
val query = ("SELECT " + KEY_FEED + ", COUNT(" + TABLE_NAME_FEED_ITEMS + "." + KEY_ID + ") AS count "
+ " FROM " + TABLE_NAME_FEED_ITEMS
+ " LEFT JOIN " + TABLE_NAME_FEED_MEDIA + " ON "
+ TABLE_NAME_FEED_ITEMS + "." + KEY_ID + "=" + TABLE_NAME_FEED_MEDIA + "." + KEY_FEEDITEM
+ " WHERE " + limitFeeds + " "
+ whereRead + " GROUP BY " + KEY_FEED)
val query = ("SELECT $KEY_FEED, COUNT($TABLE_NAME_FEED_ITEMS.$KEY_ID) AS count FROM $TABLE_NAME_FEED_ITEMS LEFT JOIN $TABLE_NAME_FEED_MEDIA ON $TABLE_NAME_FEED_ITEMS.$KEY_ID=$TABLE_NAME_FEED_MEDIA.$KEY_FEEDITEM WHERE $limitFeeds $whereRead GROUP BY $KEY_FEED")
val c = db.rawQuery(query, null)
val result: MutableMap<Long, Int> = HashMap()
@ -933,10 +838,7 @@ class PodDBAdapter private constructor() {
val mostRecentItemDates: Map<Long, Long>
get() {
val query = ("SELECT " + KEY_FEED + ","
+ " MAX(" + TABLE_NAME_FEED_ITEMS + "." + KEY_PUBDATE + ") AS most_recent_pubdate"
+ " FROM " + TABLE_NAME_FEED_ITEMS
+ " GROUP BY " + KEY_FEED)
val query = ("SELECT $KEY_FEED, MAX($TABLE_NAME_FEED_ITEMS.$KEY_PUBDATE) AS most_recent_pubdate FROM $TABLE_NAME_FEED_ITEMS GROUP BY $KEY_FEED")
val c = db.rawQuery(query, null)
val result: MutableMap<Long, Long> = HashMap()
@ -953,16 +855,7 @@ class PodDBAdapter private constructor() {
val mostRecentUnreadItemDates: Map<Long, Long>
get() {
// val query = ("SELECT " + KEY_FEED + ","
// + " MAX(" + TABLE_NAME_FEED_ITEMS + "." + KEY_PUBDATE + ") AS most_recent_pubdate"
// + " FROM " + TABLE_NAME_FEED_ITEMS
// + " WHERE " + KEY_READ + " = 0"
// + " GROUP BY " + KEY_FEED)
val query = ("SELECT " + KEY_FEED + ","
+ " MAX(CASE WHEN " + KEY_READ + " != 1 THEN " + KEY_PUBDATE + " ELSE 0 END) AS most_recent_pubdate"
+ " FROM " + TABLE_NAME_FEED_ITEMS
+ " GROUP BY " + KEY_FEED)
val query = ("SELECT $KEY_FEED, MAX(CASE WHEN $KEY_READ != 1 THEN $KEY_PUBDATE ELSE 0 END) AS most_recent_pubdate FROM $TABLE_NAME_FEED_ITEMS GROUP BY $KEY_FEED")
val c = db.rawQuery(query, null)
val result: MutableMap<Long, Long> = HashMap()
@ -1118,8 +1011,7 @@ class PodDBAdapter private constructor() {
Log.w("DBAdapter", "Upgrading from version $oldVersion to $newVersion.")
DBUpgrader.upgrade(db, oldVersion, newVersion)
db.execSQL("DELETE FROM " + TABLE_NAME_DOWNLOAD_LOG + " WHERE "
+ KEY_COMPLETION_DATE + "<" + (System.currentTimeMillis() - 7L * 24L * 3600L * 1000L))
db.execSQL("DELETE FROM $TABLE_NAME_DOWNLOAD_LOG WHERE $KEY_COMPLETION_DATE<${System.currentTimeMillis() - 7L * 24L * 3600L * 1000L}")
}
}
@ -1206,104 +1098,34 @@ class PodDBAdapter private constructor() {
const val TABLE_NAME_FAVORITES: String = "Favorites"
// SQL Statements for creating new tables
private const val TABLE_PRIMARY_KEY = (KEY_ID
+ " INTEGER PRIMARY KEY AUTOINCREMENT ,")
private const val TABLE_PRIMARY_KEY = ("$KEY_ID INTEGER PRIMARY KEY AUTOINCREMENT ,")
private const val CREATE_TABLE_FEEDS = ("CREATE TABLE "
+ TABLE_NAME_FEEDS + " (" + TABLE_PRIMARY_KEY + KEY_TITLE
+ " TEXT," + KEY_CUSTOM_TITLE + " TEXT," + KEY_FILE_URL + " TEXT," + KEY_DOWNLOAD_URL + " TEXT,"
+ KEY_DOWNLOADED + " INTEGER," + KEY_LINK + " TEXT,"
+ KEY_DESCRIPTION + " TEXT," + KEY_PAYMENT_LINK + " TEXT,"
+ KEY_LASTUPDATE + " TEXT," + KEY_LANGUAGE + " TEXT," + KEY_AUTHOR
+ " TEXT," + KEY_IMAGE_URL + " TEXT," + KEY_TYPE + " TEXT,"
+ KEY_FEED_IDENTIFIER + " TEXT," + KEY_AUTO_DOWNLOAD_ENABLED + " INTEGER DEFAULT 1,"
+ KEY_USERNAME + " TEXT,"
+ KEY_PASSWORD + " TEXT,"
+ KEY_INCLUDE_FILTER + " TEXT DEFAULT '',"
+ KEY_EXCLUDE_FILTER + " TEXT DEFAULT '',"
+ KEY_MINIMAL_DURATION_FILTER + " INTEGER DEFAULT -1,"
+ KEY_KEEP_UPDATED + " INTEGER DEFAULT 1,"
+ KEY_IS_PAGED + " INTEGER DEFAULT 0,"
+ KEY_NEXT_PAGE_LINK + " TEXT,"
+ KEY_HIDE + " TEXT,"
+ KEY_SORT_ORDER + " TEXT,"
+ KEY_LAST_UPDATE_FAILED + " INTEGER DEFAULT 0,"
+ KEY_AUTO_DELETE_ACTION + " INTEGER DEFAULT 0,"
+ KEY_FEED_PLAYBACK_SPEED + " REAL DEFAULT " + FeedPreferences.SPEED_USE_GLOBAL + ","
+ KEY_FEED_VOLUME_ADAPTION + " INTEGER DEFAULT 0,"
+ KEY_FEED_TAGS + " TEXT,"
+ KEY_FEED_SKIP_INTRO + " INTEGER DEFAULT 0,"
+ KEY_FEED_SKIP_ENDING + " INTEGER DEFAULT 0,"
+ KEY_EPISODE_NOTIFICATION + " INTEGER DEFAULT 0,"
+ KEY_NEW_EPISODES_ACTION + " INTEGER DEFAULT 0)")
private const val CREATE_TABLE_FEEDS = ("CREATE TABLE $TABLE_NAME_FEEDS ($TABLE_PRIMARY_KEY$KEY_TITLE TEXT,$KEY_CUSTOM_TITLE TEXT,$KEY_FILE_URL TEXT,$KEY_DOWNLOAD_URL TEXT,$KEY_DOWNLOADED INTEGER,$KEY_LINK TEXT,$KEY_DESCRIPTION TEXT,$KEY_PAYMENT_LINK TEXT,$KEY_LASTUPDATE TEXT,$KEY_LANGUAGE TEXT,$KEY_AUTHOR TEXT,$KEY_IMAGE_URL TEXT,$KEY_TYPE TEXT,$KEY_FEED_IDENTIFIER TEXT,$KEY_AUTO_DOWNLOAD_ENABLED INTEGER DEFAULT 1,$KEY_USERNAME TEXT,$KEY_PASSWORD TEXT,$KEY_INCLUDE_FILTER TEXT DEFAULT '',$KEY_EXCLUDE_FILTER TEXT DEFAULT '',$KEY_MINIMAL_DURATION_FILTER INTEGER DEFAULT -1,$KEY_KEEP_UPDATED INTEGER DEFAULT 1,$KEY_IS_PAGED INTEGER DEFAULT 0,$KEY_NEXT_PAGE_LINK TEXT,$KEY_HIDE TEXT,$KEY_SORT_ORDER TEXT,$KEY_LAST_UPDATE_FAILED INTEGER DEFAULT 0,$KEY_AUTO_DELETE_ACTION INTEGER DEFAULT 0,$KEY_FEED_PLAYBACK_SPEED REAL DEFAULT ${FeedPreferences.SPEED_USE_GLOBAL},$KEY_FEED_VOLUME_ADAPTION INTEGER DEFAULT 0,$KEY_FEED_TAGS TEXT,$KEY_FEED_SKIP_INTRO INTEGER DEFAULT 0,$KEY_FEED_SKIP_ENDING INTEGER DEFAULT 0,$KEY_EPISODE_NOTIFICATION INTEGER DEFAULT 0,$KEY_NEW_EPISODES_ACTION INTEGER DEFAULT 0)")
private const val CREATE_TABLE_FEED_ITEMS = ("CREATE TABLE "
+ TABLE_NAME_FEED_ITEMS + " (" + TABLE_PRIMARY_KEY
+ KEY_TITLE + " TEXT," + KEY_PUBDATE + " INTEGER,"
+ KEY_READ + " INTEGER," + KEY_LINK + " TEXT,"
+ KEY_DESCRIPTION + " TEXT," + KEY_TRANSCRIPT + " TEXT,"
+ KEY_PAYMENT_LINK + " TEXT,"
+ KEY_MEDIA + " INTEGER," + KEY_FEED + " INTEGER,"
+ KEY_HAS_CHAPTERS + " INTEGER," + KEY_ITEM_IDENTIFIER + " TEXT,"
+ KEY_IMAGE_URL + " TEXT,"
+ KEY_AUTO_DOWNLOAD_ENABLED + " INTEGER,"
+ KEY_PODCASTINDEX_CHAPTER_URL + " TEXT)")
private const val CREATE_TABLE_FEED_ITEMS = ("CREATE TABLE $TABLE_NAME_FEED_ITEMS ($TABLE_PRIMARY_KEY$KEY_TITLE TEXT,$KEY_PUBDATE INTEGER,$KEY_READ INTEGER,$KEY_LINK TEXT,$KEY_DESCRIPTION TEXT,$KEY_TRANSCRIPT TEXT,$KEY_PAYMENT_LINK TEXT,$KEY_MEDIA INTEGER,$KEY_FEED INTEGER,$KEY_HAS_CHAPTERS INTEGER,$KEY_ITEM_IDENTIFIER TEXT,$KEY_IMAGE_URL TEXT,$KEY_AUTO_DOWNLOAD_ENABLED INTEGER,$KEY_PODCASTINDEX_CHAPTER_URL TEXT)")
private const val CREATE_TABLE_FEED_MEDIA = ("CREATE TABLE "
+ TABLE_NAME_FEED_MEDIA + " (" + TABLE_PRIMARY_KEY + KEY_DURATION
+ " INTEGER," + KEY_FILE_URL + " TEXT," + KEY_DOWNLOAD_URL
+ " TEXT," + KEY_DOWNLOADED + " INTEGER," + KEY_POSITION
+ " INTEGER," + KEY_SIZE + " INTEGER," + KEY_MIME_TYPE + " TEXT,"
+ KEY_PLAYBACK_COMPLETION_DATE + " INTEGER,"
+ KEY_FEEDITEM + " INTEGER,"
+ KEY_PLAYED_DURATION + " INTEGER,"
+ KEY_HAS_EMBEDDED_PICTURE + " INTEGER,"
+ KEY_LAST_PLAYED_TIME + " INTEGER" + ")")
private const val CREATE_TABLE_FEED_MEDIA = ("CREATE TABLE $TABLE_NAME_FEED_MEDIA ($TABLE_PRIMARY_KEY$KEY_DURATION INTEGER,$KEY_FILE_URL TEXT,$KEY_DOWNLOAD_URL TEXT,$KEY_DOWNLOADED INTEGER,$KEY_POSITION INTEGER,$KEY_SIZE INTEGER,$KEY_MIME_TYPE TEXT,$KEY_PLAYBACK_COMPLETION_DATE INTEGER,$KEY_FEEDITEM INTEGER,$KEY_PLAYED_DURATION INTEGER,$KEY_HAS_EMBEDDED_PICTURE INTEGER,$KEY_LAST_PLAYED_TIME INTEGER)")
private const val CREATE_TABLE_DOWNLOAD_LOG = ("CREATE TABLE "
+ TABLE_NAME_DOWNLOAD_LOG + " (" + TABLE_PRIMARY_KEY + KEY_FEEDFILE
+ " INTEGER," + KEY_FEEDFILETYPE + " INTEGER," + KEY_REASON
+ " INTEGER," + KEY_SUCCESSFUL + " INTEGER," + KEY_COMPLETION_DATE
+ " INTEGER," + KEY_REASON_DETAILED + " TEXT,"
+ KEY_DOWNLOADSTATUS_TITLE + " TEXT)")
private const val CREATE_TABLE_DOWNLOAD_LOG = ("CREATE TABLE $TABLE_NAME_DOWNLOAD_LOG ($TABLE_PRIMARY_KEY$KEY_FEEDFILE INTEGER,$KEY_FEEDFILETYPE INTEGER,$KEY_REASON INTEGER,$KEY_SUCCESSFUL INTEGER,$KEY_COMPLETION_DATE INTEGER,$KEY_REASON_DETAILED TEXT,$KEY_DOWNLOADSTATUS_TITLE TEXT)")
private const val CREATE_TABLE_QUEUE = ("CREATE TABLE "
+ TABLE_NAME_QUEUE + "(" + KEY_ID + " INTEGER PRIMARY KEY,"
+ KEY_FEEDITEM + " INTEGER," + KEY_FEED + " INTEGER)")
private const val CREATE_TABLE_QUEUE = ("CREATE TABLE $TABLE_NAME_QUEUE($KEY_ID INTEGER PRIMARY KEY,$KEY_FEEDITEM INTEGER,$KEY_FEED INTEGER)")
private const val CREATE_TABLE_SIMPLECHAPTERS = ("CREATE TABLE "
+ TABLE_NAME_SIMPLECHAPTERS + " (" + TABLE_PRIMARY_KEY + KEY_TITLE
+ " TEXT," + KEY_START + " INTEGER," + KEY_FEEDITEM + " INTEGER,"
+ KEY_LINK + " TEXT," + KEY_IMAGE_URL + " TEXT)")
private const val CREATE_TABLE_SIMPLECHAPTERS = ("CREATE TABLE $TABLE_NAME_SIMPLECHAPTERS ($TABLE_PRIMARY_KEY$KEY_TITLE TEXT,$KEY_START INTEGER,$KEY_FEEDITEM INTEGER,$KEY_LINK TEXT,$KEY_IMAGE_URL TEXT)")
// SQL Statements for creating indexes
const val CREATE_INDEX_FEEDITEMS_FEED: String = ("CREATE INDEX "
+ TABLE_NAME_FEED_ITEMS + "_" + KEY_FEED + " ON " + TABLE_NAME_FEED_ITEMS + " ("
+ KEY_FEED + ")")
const val CREATE_INDEX_FEEDITEMS_FEED: String = ("CREATE INDEX ${TABLE_NAME_FEED_ITEMS}_$KEY_FEED ON $TABLE_NAME_FEED_ITEMS ($KEY_FEED)")
const val CREATE_INDEX_FEEDITEMS_PUBDATE: String = ("CREATE INDEX "
+ TABLE_NAME_FEED_ITEMS + "_" + KEY_PUBDATE + " ON " + TABLE_NAME_FEED_ITEMS + " ("
+ KEY_PUBDATE + ")")
const val CREATE_INDEX_FEEDITEMS_PUBDATE: String = ("CREATE INDEX ${TABLE_NAME_FEED_ITEMS}_$KEY_PUBDATE ON $TABLE_NAME_FEED_ITEMS ($KEY_PUBDATE)")
const val CREATE_INDEX_FEEDITEMS_READ: String = ("CREATE INDEX "
+ TABLE_NAME_FEED_ITEMS + "_" + KEY_READ + " ON " + TABLE_NAME_FEED_ITEMS + " ("
+ KEY_READ + ")")
const val CREATE_INDEX_FEEDITEMS_READ: String = ("CREATE INDEX ${TABLE_NAME_FEED_ITEMS}_$KEY_READ ON $TABLE_NAME_FEED_ITEMS ($KEY_READ)")
const val CREATE_INDEX_QUEUE_FEEDITEM: String = ("CREATE INDEX "
+ TABLE_NAME_QUEUE + "_" + KEY_FEEDITEM + " ON " + TABLE_NAME_QUEUE + " ("
+ KEY_FEEDITEM + ")")
const val CREATE_INDEX_QUEUE_FEEDITEM: String = ("CREATE INDEX ${TABLE_NAME_QUEUE}_$KEY_FEEDITEM ON $TABLE_NAME_QUEUE ($KEY_FEEDITEM)")
const val CREATE_INDEX_FEEDMEDIA_FEEDITEM: String = ("CREATE INDEX "
+ TABLE_NAME_FEED_MEDIA + "_" + KEY_FEEDITEM + " ON " + TABLE_NAME_FEED_MEDIA + " ("
+ KEY_FEEDITEM + ")")
const val CREATE_INDEX_FEEDMEDIA_FEEDITEM: String = ("CREATE INDEX ${TABLE_NAME_FEED_MEDIA}_$KEY_FEEDITEM ON $TABLE_NAME_FEED_MEDIA ($KEY_FEEDITEM)")
const val CREATE_INDEX_SIMPLECHAPTERS_FEEDITEM: String = ("CREATE INDEX "
+ TABLE_NAME_SIMPLECHAPTERS + "_" + KEY_FEEDITEM + " ON " + TABLE_NAME_SIMPLECHAPTERS + " ("
+ KEY_FEEDITEM + ")")
const val CREATE_INDEX_SIMPLECHAPTERS_FEEDITEM: String = ("CREATE INDEX ${TABLE_NAME_SIMPLECHAPTERS}_$KEY_FEEDITEM ON $TABLE_NAME_SIMPLECHAPTERS ($KEY_FEEDITEM)")
const val CREATE_TABLE_FAVORITES: String = ("CREATE TABLE "
+ TABLE_NAME_FAVORITES + "(" + KEY_ID + " INTEGER PRIMARY KEY,"
+ KEY_FEEDITEM + " INTEGER," + KEY_FEED + " INTEGER)")
const val CREATE_TABLE_FAVORITES: String = ("CREATE TABLE $TABLE_NAME_FAVORITES($KEY_ID INTEGER PRIMARY KEY,$KEY_FEEDITEM INTEGER,$KEY_FEED INTEGER)")
/**
* All the tables in the database
@ -1322,82 +1144,16 @@ class PodDBAdapter private constructor() {
const val SELECT_KEY_FEED_ID: String = "feed_id"
private const val KEYS_FEED_ITEM_WITHOUT_DESCRIPTION =
(TABLE_NAME_FEED_ITEMS + "." + KEY_ID + " AS " + SELECT_KEY_ITEM_ID + ", "
+ TABLE_NAME_FEED_ITEMS + "." + KEY_TITLE + ", "
+ TABLE_NAME_FEED_ITEMS + "." + KEY_PUBDATE + ", "
+ TABLE_NAME_FEED_ITEMS + "." + KEY_READ + ", "
+ TABLE_NAME_FEED_ITEMS + "." + KEY_LINK + ", "
+ TABLE_NAME_FEED_ITEMS + "." + KEY_PAYMENT_LINK + ", "
+ TABLE_NAME_FEED_ITEMS + "." + KEY_MEDIA + ", "
+ TABLE_NAME_FEED_ITEMS + "." + KEY_FEED + ", "
+ TABLE_NAME_FEED_ITEMS + "." + KEY_HAS_CHAPTERS + ", "
+ TABLE_NAME_FEED_ITEMS + "." + KEY_ITEM_IDENTIFIER + ", "
+ TABLE_NAME_FEED_ITEMS + "." + KEY_IMAGE_URL + ", "
+ TABLE_NAME_FEED_ITEMS + "." + KEY_AUTO_DOWNLOAD_ENABLED + ", "
+ TABLE_NAME_FEED_ITEMS + "." + KEY_PODCASTINDEX_CHAPTER_URL)
("$TABLE_NAME_FEED_ITEMS.$KEY_ID AS $SELECT_KEY_ITEM_ID, $TABLE_NAME_FEED_ITEMS.$KEY_TITLE, $TABLE_NAME_FEED_ITEMS.$KEY_PUBDATE, $TABLE_NAME_FEED_ITEMS.$KEY_READ, $TABLE_NAME_FEED_ITEMS.$KEY_LINK, $TABLE_NAME_FEED_ITEMS.$KEY_PAYMENT_LINK, $TABLE_NAME_FEED_ITEMS.$KEY_MEDIA, $TABLE_NAME_FEED_ITEMS.$KEY_FEED, $TABLE_NAME_FEED_ITEMS.$KEY_HAS_CHAPTERS, $TABLE_NAME_FEED_ITEMS.$KEY_ITEM_IDENTIFIER, $TABLE_NAME_FEED_ITEMS.$KEY_IMAGE_URL, $TABLE_NAME_FEED_ITEMS.$KEY_AUTO_DOWNLOAD_ENABLED, $TABLE_NAME_FEED_ITEMS.$KEY_PODCASTINDEX_CHAPTER_URL")
private const val KEYS_FEED_MEDIA = (TABLE_NAME_FEED_MEDIA + "." + KEY_ID + " AS " + SELECT_KEY_MEDIA_ID + ", "
+ TABLE_NAME_FEED_MEDIA + "." + KEY_DURATION + ", "
+ TABLE_NAME_FEED_MEDIA + "." + KEY_FILE_URL + ", "
+ TABLE_NAME_FEED_MEDIA + "." + KEY_DOWNLOAD_URL + ", "
+ TABLE_NAME_FEED_MEDIA + "." + KEY_DOWNLOADED + ", "
+ TABLE_NAME_FEED_MEDIA + "." + KEY_POSITION + ", "
+ TABLE_NAME_FEED_MEDIA + "." + KEY_SIZE + ", "
+ TABLE_NAME_FEED_MEDIA + "." + KEY_MIME_TYPE + ", "
+ TABLE_NAME_FEED_MEDIA + "." + KEY_PLAYBACK_COMPLETION_DATE + ", "
+ TABLE_NAME_FEED_MEDIA + "." + KEY_FEEDITEM + ", "
+ TABLE_NAME_FEED_MEDIA + "." + KEY_PLAYED_DURATION + ", "
+ TABLE_NAME_FEED_MEDIA + "." + KEY_HAS_EMBEDDED_PICTURE + ", "
+ TABLE_NAME_FEED_MEDIA + "." + KEY_LAST_PLAYED_TIME)
private const val KEYS_FEED_MEDIA = ("$TABLE_NAME_FEED_MEDIA.$KEY_ID AS $SELECT_KEY_MEDIA_ID, $TABLE_NAME_FEED_MEDIA.$KEY_DURATION, $TABLE_NAME_FEED_MEDIA.$KEY_FILE_URL, $TABLE_NAME_FEED_MEDIA.$KEY_DOWNLOAD_URL, $TABLE_NAME_FEED_MEDIA.$KEY_DOWNLOADED, $TABLE_NAME_FEED_MEDIA.$KEY_POSITION, $TABLE_NAME_FEED_MEDIA.$KEY_SIZE, $TABLE_NAME_FEED_MEDIA.$KEY_MIME_TYPE, $TABLE_NAME_FEED_MEDIA.$KEY_PLAYBACK_COMPLETION_DATE, $TABLE_NAME_FEED_MEDIA.$KEY_FEEDITEM, $TABLE_NAME_FEED_MEDIA.$KEY_PLAYED_DURATION, $TABLE_NAME_FEED_MEDIA.$KEY_HAS_EMBEDDED_PICTURE, $TABLE_NAME_FEED_MEDIA.$KEY_LAST_PLAYED_TIME")
private const val KEYS_FEED = (TABLE_NAME_FEEDS + "." + KEY_ID + " AS " + SELECT_KEY_FEED_ID + ", "
+ TABLE_NAME_FEEDS + "." + KEY_TITLE + ", "
+ TABLE_NAME_FEEDS + "." + KEY_CUSTOM_TITLE + ", "
+ TABLE_NAME_FEEDS + "." + KEY_FILE_URL + ", "
+ TABLE_NAME_FEEDS + "." + KEY_DOWNLOAD_URL + ", "
+ TABLE_NAME_FEEDS + "." + KEY_DOWNLOADED + ", "
+ TABLE_NAME_FEEDS + "." + KEY_LINK + ", "
+ TABLE_NAME_FEEDS + "." + KEY_DESCRIPTION + ", "
+ TABLE_NAME_FEEDS + "." + KEY_PAYMENT_LINK + ", "
+ TABLE_NAME_FEEDS + "." + KEY_LASTUPDATE + ", "
+ TABLE_NAME_FEEDS + "." + KEY_LANGUAGE + ", "
+ TABLE_NAME_FEEDS + "." + KEY_AUTHOR + ", "
+ TABLE_NAME_FEEDS + "." + KEY_IMAGE_URL + ", "
+ TABLE_NAME_FEEDS + "." + KEY_TYPE + ", "
+ TABLE_NAME_FEEDS + "." + KEY_FEED_IDENTIFIER + ", "
+ TABLE_NAME_FEEDS + "." + KEY_IS_PAGED + ", "
+ TABLE_NAME_FEEDS + "." + KEY_NEXT_PAGE_LINK + ", "
+ TABLE_NAME_FEEDS + "." + KEY_LAST_UPDATE_FAILED + ", "
+ TABLE_NAME_FEEDS + "." + KEY_AUTO_DOWNLOAD_ENABLED + ", "
+ TABLE_NAME_FEEDS + "." + KEY_KEEP_UPDATED + ", "
+ TABLE_NAME_FEEDS + "." + KEY_USERNAME + ", "
+ TABLE_NAME_FEEDS + "." + KEY_PASSWORD + ", "
+ TABLE_NAME_FEEDS + "." + KEY_HIDE + ", "
+ TABLE_NAME_FEEDS + "." + KEY_SORT_ORDER + ", "
+ TABLE_NAME_FEEDS + "." + KEY_AUTO_DELETE_ACTION + ", "
+ TABLE_NAME_FEEDS + "." + KEY_FEED_VOLUME_ADAPTION + ", "
+ TABLE_NAME_FEEDS + "." + KEY_INCLUDE_FILTER + ", "
+ TABLE_NAME_FEEDS + "." + KEY_EXCLUDE_FILTER + ", "
+ TABLE_NAME_FEEDS + "." + KEY_MINIMAL_DURATION_FILTER + ", "
+ TABLE_NAME_FEEDS + "." + KEY_FEED_PLAYBACK_SPEED + ", "
+ TABLE_NAME_FEEDS + "." + KEY_FEED_TAGS + ", "
+ TABLE_NAME_FEEDS + "." + KEY_FEED_SKIP_INTRO + ", "
+ TABLE_NAME_FEEDS + "." + KEY_FEED_SKIP_ENDING + ", "
+ TABLE_NAME_FEEDS + "." + KEY_EPISODE_NOTIFICATION + ", "
+ TABLE_NAME_FEEDS + "." + KEY_NEW_EPISODES_ACTION)
private const val KEYS_FEED = ("$TABLE_NAME_FEEDS.$KEY_ID AS $SELECT_KEY_FEED_ID, $TABLE_NAME_FEEDS.$KEY_TITLE, $TABLE_NAME_FEEDS.$KEY_CUSTOM_TITLE, $TABLE_NAME_FEEDS.$KEY_FILE_URL, $TABLE_NAME_FEEDS.$KEY_DOWNLOAD_URL, $TABLE_NAME_FEEDS.$KEY_DOWNLOADED, $TABLE_NAME_FEEDS.$KEY_LINK, $TABLE_NAME_FEEDS.$KEY_DESCRIPTION, $TABLE_NAME_FEEDS.$KEY_PAYMENT_LINK, $TABLE_NAME_FEEDS.$KEY_LASTUPDATE, $TABLE_NAME_FEEDS.$KEY_LANGUAGE, $TABLE_NAME_FEEDS.$KEY_AUTHOR, $TABLE_NAME_FEEDS.$KEY_IMAGE_URL, $TABLE_NAME_FEEDS.$KEY_TYPE, $TABLE_NAME_FEEDS.$KEY_FEED_IDENTIFIER, $TABLE_NAME_FEEDS.$KEY_IS_PAGED, $TABLE_NAME_FEEDS.$KEY_NEXT_PAGE_LINK, $TABLE_NAME_FEEDS.$KEY_LAST_UPDATE_FAILED, $TABLE_NAME_FEEDS.$KEY_AUTO_DOWNLOAD_ENABLED, $TABLE_NAME_FEEDS.$KEY_KEEP_UPDATED, $TABLE_NAME_FEEDS.$KEY_USERNAME, $TABLE_NAME_FEEDS.$KEY_PASSWORD, $TABLE_NAME_FEEDS.$KEY_HIDE, $TABLE_NAME_FEEDS.$KEY_SORT_ORDER, $TABLE_NAME_FEEDS.$KEY_AUTO_DELETE_ACTION, $TABLE_NAME_FEEDS.$KEY_FEED_VOLUME_ADAPTION, $TABLE_NAME_FEEDS.$KEY_INCLUDE_FILTER, $TABLE_NAME_FEEDS.$KEY_EXCLUDE_FILTER, $TABLE_NAME_FEEDS.$KEY_MINIMAL_DURATION_FILTER, $TABLE_NAME_FEEDS.$KEY_FEED_PLAYBACK_SPEED, $TABLE_NAME_FEEDS.$KEY_FEED_TAGS, $TABLE_NAME_FEEDS.$KEY_FEED_SKIP_INTRO, $TABLE_NAME_FEEDS.$KEY_FEED_SKIP_ENDING, $TABLE_NAME_FEEDS.$KEY_EPISODE_NOTIFICATION, $TABLE_NAME_FEEDS.$KEY_NEW_EPISODES_ACTION")
private const val JOIN_FEED_ITEM_AND_MEDIA = (" LEFT JOIN " + TABLE_NAME_FEED_MEDIA
+ " ON " + TABLE_NAME_FEED_ITEMS + "." + KEY_ID + "=" + TABLE_NAME_FEED_MEDIA + "." + KEY_FEEDITEM + " ")
private const val JOIN_FEED_ITEM_AND_MEDIA = (" LEFT JOIN $TABLE_NAME_FEED_MEDIA ON $TABLE_NAME_FEED_ITEMS.$KEY_ID=$TABLE_NAME_FEED_MEDIA.$KEY_FEEDITEM ")
private const val SELECT_FEED_ITEMS_AND_MEDIA_WITH_DESCRIPTION =
("SELECT " + KEYS_FEED_ITEM_WITHOUT_DESCRIPTION + ", " + KEYS_FEED_MEDIA + ", "
+ TABLE_NAME_FEED_ITEMS + "." + KEY_DESCRIPTION + "." + KEY_TRANSCRIPT
+ " FROM " + TABLE_NAME_FEED_ITEMS
+ JOIN_FEED_ITEM_AND_MEDIA)
private const val SELECT_FEED_ITEMS_AND_MEDIA =
("SELECT " + KEYS_FEED_ITEM_WITHOUT_DESCRIPTION + ", " + KEYS_FEED_MEDIA
+ " FROM " + TABLE_NAME_FEED_ITEMS
+ JOIN_FEED_ITEM_AND_MEDIA)
private const val SELECT_FEED_ITEMS_AND_MEDIA_WITH_DESCRIPTION = ("SELECT $KEYS_FEED_ITEM_WITHOUT_DESCRIPTION, $KEYS_FEED_MEDIA, $TABLE_NAME_FEED_ITEMS.$KEY_DESCRIPTION.$KEY_TRANSCRIPT FROM $TABLE_NAME_FEED_ITEMS$JOIN_FEED_ITEM_AND_MEDIA")
private const val SELECT_FEED_ITEMS_AND_MEDIA = ("SELECT $KEYS_FEED_ITEM_WITHOUT_DESCRIPTION, $KEYS_FEED_MEDIA FROM $TABLE_NAME_FEED_ITEMS$JOIN_FEED_ITEM_AND_MEDIA")
private lateinit var context: Context
private var instance: PodDBAdapter? = null

View File

@ -3,7 +3,7 @@ package ac.mdiq.podcini.ui.actions.actionbutton
import ac.mdiq.podcini.R
import ac.mdiq.podcini.net.download.service.DownloadRequestCreator.getMediafilePath
import ac.mdiq.podcini.net.download.service.DownloadRequestCreator.getMediafilename
import ac.mdiq.podcini.playback.AudioMediaOperation.MergeAudios
import ac.mdiq.podcini.util.AudioMediaOperation.mergeAudios
import ac.mdiq.podcini.storage.DBWriter
import ac.mdiq.podcini.storage.DBWriter.persistFeedItem
import ac.mdiq.podcini.storage.model.feed.FeedItem
@ -77,9 +77,7 @@ class TTSActionButton(item: FeedItem) : ItemActionButton(item) {
val result = tts?.setLanguage(Locale(item.feed!!.language!!))
if (result == TextToSpeech.LANG_MISSING_DATA || result == TextToSpeech.LANG_NOT_SUPPORTED) {
Log.w(TAG, "TTS language not supported ${item.feed?.language} $result")
withContext(Dispatchers.Main) {
Toast.makeText(context, context.getString(R.string.language_not_supported_by_tts) + " ${item.feed?.language} $result", Toast.LENGTH_LONG).show()
}
withContext(Dispatchers.Main) { Toast.makeText(context, context.getString(R.string.language_not_supported_by_tts) + " ${item.feed?.language} $result", Toast.LENGTH_LONG).show() }
}
}
@ -117,9 +115,7 @@ class TTSActionButton(item: FeedItem) : ItemActionButton(item) {
status = tts?.synthesizeToFile(chunk, null, tempFile, tempFile.absolutePath) ?: 0
Log.d(TAG, "status: $status chunk: ${chunk.substring(0, min(80, chunk.length))}")
if (status == TextToSpeech.ERROR) {
withContext(Dispatchers.Main) {
Toast.makeText(context, "Error generating audio file $tempFile.absolutePath", Toast.LENGTH_LONG).show()
}
withContext(Dispatchers.Main) { Toast.makeText(context, "Error generating audio file $tempFile.absolutePath", Toast.LENGTH_LONG).show() }
break
}
startIndex += chunkLength
@ -131,7 +127,7 @@ class TTSActionButton(item: FeedItem) : ItemActionButton(item) {
processing = 0.85f
EventBus.getDefault().post(updated(item))
if (status == TextToSpeech.SUCCESS) {
MergeAudios(parts.toTypedArray(), mediaFile.absolutePath, null)
mergeAudios(parts.toTypedArray(), mediaFile.absolutePath, null)
// if (mediaFile.exists()) mediaFile.delete()
// if (!mediaFile.exists()) {
@ -156,7 +152,7 @@ class TTSActionButton(item: FeedItem) : ItemActionButton(item) {
f.delete()
}
ttsWorking = false
} else Toast.makeText(context, R.string.episode_has_no_content, Toast.LENGTH_LONG).show()
} else withContext(Dispatchers.Main) { Toast.makeText(context, R.string.episode_has_no_content, Toast.LENGTH_LONG).show() }
item.setPlayed(false)
processing = 1f

View File

@ -137,7 +137,9 @@ class EpisodeHomeFragment : Fragment() {
val result = tts?.setLanguage(Locale(currentItem!!.feed!!.language!!))
if (result == TextToSpeech.LANG_MISSING_DATA || result == TextToSpeech.LANG_NOT_SUPPORTED) {
Log.w(TAG, "TTS language not supported ${currentItem?.feed?.language}")
Toast.makeText(context, getString(R.string.language_not_supported_by_tts) + " ${currentItem?.feed?.language}", Toast.LENGTH_LONG).show()
requireActivity().runOnUiThread {
Toast.makeText(context, getString(R.string.language_not_supported_by_tts) + " ${currentItem?.feed?.language}", Toast.LENGTH_LONG).show()
}
}
}
ttsReady = true
@ -145,7 +147,7 @@ class EpisodeHomeFragment : Fragment() {
Log.d(TAG, "TTS init success")
} else {
Log.w(TAG, "TTS init failed")
Toast.makeText(context, R.string.tts_init_failed, Toast.LENGTH_LONG).show()
requireActivity().runOnUiThread {Toast.makeText(context, R.string.tts_init_failed, Toast.LENGTH_LONG).show() }
}
}
}
@ -171,9 +173,9 @@ class EpisodeHomeFragment : Fragment() {
// super.onPrepareMenu(menu)
Log.d(TAG, "onPrepareMenu called")
val textSpeech = menu.findItem(R.id.text_speech)
textSpeech?.isVisible = readMode
if (readMode) {
if (ttsPlaying) textSpeech?.setIcon(R.drawable.ic_pause) else textSpeech?.setIcon(R.drawable.ic_play_24dp)
textSpeech?.isVisible = readMode && tts != null
if (textSpeech?.isVisible == true) {
if (ttsPlaying) textSpeech.setIcon(R.drawable.ic_pause) else textSpeech.setIcon(R.drawable.ic_play_24dp)
}
menu.findItem(R.id.share_notes)?.setVisible(readMode)
menu.findItem(R.id.switchJS)?.setVisible(!readMode)
@ -215,7 +217,8 @@ class EpisodeHomeFragment : Fragment() {
}
} else ttsPlaying = false
updateAppearance()
}
} else Toast.makeText(context, R.string.tts_not_available, Toast.LENGTH_LONG).show()
return true
}
R.id.share_notes -> {

View File

@ -195,14 +195,26 @@ class FeedItemlistFragment : Fragment(), AdapterView.OnItemClickListener, Toolba
adapter.endSelectMode()
true
}
return binding.root
}
ioScope.launch {
if (!ttsReady) {
initializeTTS(requireContext())
semaphore.acquire()
private val semaphore = Semaphore(0)
private fun initializeTTS(context: Context) {
Log.d(TAG, "starting TTS")
if (tts == null) {
tts = TextToSpeech(context) { status: Int ->
if (status == TextToSpeech.SUCCESS) {
ttsReady = true
semaphore.release()
Log.d(TAG, "TTS init success")
} else {
Log.w(TAG, "TTS init failed")
requireActivity().runOnUiThread {
Toast.makeText(context, R.string.tts_init_failed, Toast.LENGTH_LONG).show()
}
}
}
}
return binding.root
}
override fun onDestroyView() {
@ -523,7 +535,6 @@ class FeedItemlistFragment : Fragment(), AdapterView.OnItemClickListener, Toolba
@UnstableApi private fun loadItems() {
disposable?.dispose()
disposable = Observable.fromCallable<Feed?> { this.loadData() }
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
@ -531,6 +542,23 @@ class FeedItemlistFragment : Fragment(), AdapterView.OnItemClickListener, Toolba
{ result: Feed? ->
feed = result
Log.d(TAG, "loadItems subscribe called ${feed?.title}")
if (feed != null) {
var hasNonMediaItems = false
for (item in feed!!.items) {
if (item.media == null) {
hasNonMediaItems = true
break
}
}
if (hasNonMediaItems) {
ioScope.launch {
if (!ttsReady) {
initializeTTS(requireContext())
semaphore.acquire()
}
}
}
}
swipeActions.setFilter(feed?.itemFilter)
refreshHeaderView()
binding.progressBar.visibility = View.GONE
@ -649,22 +677,5 @@ class FeedItemlistFragment : Fragment(), AdapterView.OnItemClickListener, Toolba
var tts: TextToSpeech? = null
var ttsReady = false
var ttsWorking = false
val semaphore = Semaphore(0)
fun initializeTTS(context: Context) {
Log.d(TAG, "starting TTS")
if (tts == null) {
tts = TextToSpeech(context) { status: Int ->
if (status == TextToSpeech.SUCCESS) {
ttsReady = true
semaphore.release()
Log.d(TAG, "TTS init success")
} else {
Log.w(TAG, "TTS init failed")
Toast.makeText(context, R.string.tts_init_failed, Toast.LENGTH_LONG).show()
}
}
}
}
}
}

View File

@ -1,4 +1,4 @@
package ac.mdiq.podcini.playback
package ac.mdiq.podcini.util
import java.io.*
import java.nio.ByteBuffer
@ -6,7 +6,7 @@ import java.nio.ByteOrder
// converted to Kotlin from the java file: https://gist.github.com/DrustZ/d3d3fc8fcc1067433db4dd3079f8d187
object AudioMediaOperation {
fun MergeAudios(selection: Array<String>, outpath: String?, callback: OperationCallbacks?) {
fun mergeAudios(selection: Array<String>, outpath: String?, callback: OperationCallbacks?) {
var RECORDER_SAMPLERATE = 0
try {
val amplifyOutputStream = DataOutputStream(BufferedOutputStream(FileOutputStream(outpath)))
@ -145,7 +145,7 @@ object AudioMediaOperation {
}
@Throws(IOException::class)
fun RawToWave(rawfn: String?, wavefn: String?) {
fun rawToWave(rawfn: String, wavefn: String) {
val rawFile = File(rawfn)
val waveFile = File(wavefn)
val rawData = ByteArray(rawFile.length().toInt())
@ -233,8 +233,8 @@ object AudioMediaOperation {
@Throws(IOException::class)
fun writeString(output: DataOutputStream, value: String) {
for (i in 0 until value.length) {
output.write(value[i].code)
for (element in value) {
output.write(element.code)
}
}

View File

@ -61,6 +61,7 @@
<string name="web_content_not_available">Web content is not available</string>
<string name="language_not_supported_by_tts">The language is not supported by TTS</string>
<string name="tts_init_failed">TTS init failed</string>
<string name="tts_not_available">TTS engine not available</string>
<string name="episode_has_no_content">Episode has not content</string>
<string name="notification_permission_text">Since Android 13, top level notification is needed for normal refresh and playback. You may disallow notifications of sub-catergories at your wish.</string>

View File

@ -1,3 +1,8 @@
## 4.10.1
* fixed crash issue when TTS engine is not available on device
* in feed item list view, only start TTS engine when some episodes have no media
## 4.10.0
* fixed media info on notification widget
@ -54,7 +59,7 @@
## 4.9.0
* fixed bug of player always expands when changing audio
* migrated to media3's MediaSession and MediaLibraryService thought no new features added with this. some behavior might change or issues might arise, need to be mindful
* migrated to media3's MediaSession and MediaLibraryService though no new features added with this. some behavior might change or issues might arise, need to be mindful
* when video mode is temporarily audio only, click on image on audio player on a video episode also brings up the normal player detailed view
* added a menu action item in player detailed view to turn to fullscreen video for video episode
* added episode home view accessible right from episode info view. episode home view has two display modes: webpage or reader.

View File

@ -0,0 +1,5 @@
Version 4.10.1 brings several changes:
* fixed crash issue when TTS engine is not available on device
* in feed item list view, only start TTS engine when some episodes have no media