6.3.7 commit

This commit is contained in:
Xilin Jia 2024-08-14 08:10:52 +01:00
parent 4ccf66d5a1
commit 7a49a11f54
22 changed files with 203 additions and 297 deletions

View File

@ -1,4 +1,7 @@
[Dolphin] [Dolphin]
Timestamp=2024,8,2,9,3,22.216 Timestamp=2024,8,10,7,39,7.985
Version=4 Version=4
ViewMode=1 ViewMode=1
[Settings]
HiddenFilesShown=true

View File

@ -4,7 +4,7 @@ plugins {
id 'org.jetbrains.kotlin.android' id 'org.jetbrains.kotlin.android'
alias(libs.plugins.compose.compiler) alias(libs.plugins.compose.compiler)
id 'io.realm.kotlin' id 'io.realm.kotlin'
id('com.github.triplet.play') version '3.8.3' apply false id('com.github.triplet.play') version '3.9.0' apply false
} }
//apply plugin: 'org.jetbrains.compose' //apply plugin: 'org.jetbrains.compose'
@ -31,8 +31,8 @@ android {
testApplicationId "ac.mdiq.podcini.tests" testApplicationId "ac.mdiq.podcini.tests"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
versionCode 3020230 versionCode 3020231
versionName "6.3.6" versionName "6.3.7"
applicationId "ac.mdiq.podcini.R" applicationId "ac.mdiq.podcini.R"
def commit = "" def commit = ""
@ -102,23 +102,11 @@ android {
namespace "ac.mdiq.podcini" namespace "ac.mdiq.podcini"
lint { lint {
lintConfig = file("lint.xml") lintConfig = file("lint.xml")
// disable "GradleDependency"
checkReleaseBuilds false checkReleaseBuilds false
checkDependencies true checkDependencies true
warningsAsErrors true warningsAsErrors true
abortOnError true abortOnError true
checkGeneratedSources = true checkGeneratedSources = true
// checkOnly += ['NewApi', 'InlinedApi', 'Performance', 'DuplicateIds']
// disable += ['TypographyDashes', 'TypographyQuotes', 'ObsoleteLintCustomCheck', 'CheckResult', 'UnusedAttribute', 'BatteryLife', 'InflateParams',
// 'RestrictedApi', 'TrustAllX509TrustManager', 'ExportedReceiver', 'VectorDrawableCompat',
// 'StaticFieldLeak', 'UseCompoundDrawables', 'NestedWeights', 'Overdraw', 'UselessParent', 'TextFields',
// 'AlwaysShowAction', 'Autofill', 'ClickableViewAccessibility', 'ContentDescription',
// 'KeyboardInaccessibleWidget', 'LabelFor', 'SetTextI18n', 'HardcodedText', 'RelativeOverlap',
// 'RtlCompat', 'RtlHardcoded', 'MissingMediaBrowserServiceIntentFilter', 'VectorPath',
// 'InvalidPeriodicWorkRequestInterval', 'NotifyDataSetChanged', 'RtlEnabled']
disable += ['TypographyDashes', 'TypographyQuotes', 'ObsoleteLintCustomCheck', 'BatteryLife', disable += ['TypographyDashes', 'TypographyQuotes', 'ObsoleteLintCustomCheck', 'BatteryLife',
'ExportedReceiver', 'VectorDrawableCompat', 'NestedWeights', 'Overdraw', 'TextFields', 'ExportedReceiver', 'VectorDrawableCompat', 'NestedWeights', 'Overdraw', 'TextFields',
'AlwaysShowAction', 'Autofill', 'ClickableViewAccessibility', 'ContentDescription', 'AlwaysShowAction', 'Autofill', 'ClickableViewAccessibility', 'ContentDescription',

View File

@ -1,83 +0,0 @@
package de.test.podcini.service.playback
import ac.mdiq.podcini.storage.model.MediaType
import ac.mdiq.podcini.storage.model.Playable
import ac.mdiq.podcini.playback.base.MediaPlayerBase.MediaPlayerInfo
import ac.mdiq.podcini.playback.base.MediaPlayerCallback
class CancelableMediaPlayerCallback(private val originalCallback: MediaPlayerCallback) : MediaPlayerCallback {
private var isCancelled = false
fun cancel() {
isCancelled = true
}
override fun statusChanged(newInfo: MediaPlayerInfo?) {
if (isCancelled) {
return
}
originalCallback.statusChanged(newInfo)
}
override fun shouldStop() {
if (isCancelled) return
// originalCallback.shouldStop()
}
override fun onMediaChanged(reloadUI: Boolean) {
if (isCancelled) {
return
}
originalCallback.onMediaChanged(reloadUI)
}
override fun onPostPlayback(media: Playable?, ended: Boolean, skipped: Boolean, playingNext: Boolean) {
if (isCancelled) {
return
}
originalCallback.onPostPlayback(media, ended, skipped, playingNext)
}
override fun onPlaybackStart(playable: Playable, position: Int) {
if (isCancelled) {
return
}
originalCallback.onPlaybackStart(playable, position)
}
override fun onPlaybackPause(playable: Playable?, position: Int) {
if (isCancelled) {
return
}
originalCallback.onPlaybackPause(playable, position)
}
override fun getNextInQueue(currentMedia: Playable?): Playable? {
if (isCancelled) {
return null
}
return originalCallback.getNextInQueue(currentMedia)
}
override fun findMedia(url: String): Playable? {
if (isCancelled) {
return null
}
return originalCallback.findMedia(url)
}
override fun onPlaybackEnded(mediaType: MediaType?, stopPlaying: Boolean) {
if (isCancelled) {
return
}
originalCallback.onPlaybackEnded(mediaType, stopPlaying)
}
override fun ensureMediaInfoLoaded(media: Playable) {
if (isCancelled) {
return
}
originalCallback.ensureMediaInfoLoaded(media)
}
}

View File

@ -1,40 +0,0 @@
package de.test.podcini.service.playback
import ac.mdiq.podcini.storage.model.MediaType
import ac.mdiq.podcini.storage.model.Playable
import ac.mdiq.podcini.playback.base.MediaPlayerBase.MediaPlayerInfo
import ac.mdiq.podcini.playback.base.MediaPlayerCallback
open class DefaultMediaPlayerCallback : MediaPlayerCallback {
override fun statusChanged(newInfo: MediaPlayerInfo?) {
}
override fun shouldStop() {
}
override fun onMediaChanged(reloadUI: Boolean) {
}
override fun onPostPlayback(media: Playable?, ended: Boolean, skipped: Boolean, playingNext: Boolean) {
}
override fun onPlaybackStart(playable: Playable, position: Int) {
}
override fun onPlaybackPause(playable: Playable?, position: Int) {
}
override fun getNextInQueue(currentMedia: Playable?): Playable? {
return null
}
override fun findMedia(url: String): Playable? {
return null
}
override fun onPlaybackEnded(mediaType: MediaType?, stopPlaying: Boolean) {
}
override fun ensureMediaInfoLoaded(media: Playable) {
}
}

View File

@ -2,6 +2,7 @@ package de.test.podcini.service.playback
import ac.mdiq.podcini.playback.base.MediaPlayerBase import ac.mdiq.podcini.playback.base.MediaPlayerBase
import ac.mdiq.podcini.playback.base.MediaPlayerBase.MediaPlayerInfo import ac.mdiq.podcini.playback.base.MediaPlayerBase.MediaPlayerInfo
import ac.mdiq.podcini.playback.base.MediaPlayerCallback
import ac.mdiq.podcini.playback.base.PlayerStatus import ac.mdiq.podcini.playback.base.PlayerStatus
import ac.mdiq.podcini.playback.service.LocalMediaPlayer import ac.mdiq.podcini.playback.service.LocalMediaPlayer
import ac.mdiq.podcini.storage.model.* import ac.mdiq.podcini.storage.model.*
@ -798,6 +799,72 @@ class MediaPlayerBaseTest {
} }
private class UnexpectedStateChange(status: PlayerStatus) : AssertionFailedError("Unexpected state change: $status") private class UnexpectedStateChange(status: PlayerStatus) : AssertionFailedError("Unexpected state change: $status")
open class DefaultMediaPlayerCallback : MediaPlayerCallback {
override fun statusChanged(newInfo: MediaPlayerInfo?) {}
override fun shouldStop() {}
override fun onMediaChanged(reloadUI: Boolean) {}
override fun onPostPlayback(media: Playable?, ended: Boolean, skipped: Boolean, playingNext: Boolean) {}
override fun onPlaybackStart(playable: Playable, position: Int) {}
override fun onPlaybackPause(playable: Playable?, position: Int) {}
override fun getNextInQueue(currentMedia: Playable?): Playable? {
return null
}
override fun findMedia(url: String): Playable? {
return null
}
override fun onPlaybackEnded(mediaType: MediaType?, stopPlaying: Boolean) {}
override fun ensureMediaInfoLoaded(media: Playable) {}
}
class CancelableMediaPlayerCallback(private val originalCallback: MediaPlayerCallback) : MediaPlayerCallback {
private var isCancelled = false
fun cancel() {
isCancelled = true
}
override fun statusChanged(newInfo: MediaPlayerInfo?) {
if (isCancelled) return
originalCallback.statusChanged(newInfo)
}
override fun shouldStop() {
if (isCancelled) return
// originalCallback.shouldStop()
}
override fun onMediaChanged(reloadUI: Boolean) {
if (isCancelled) return
originalCallback.onMediaChanged(reloadUI)
}
override fun onPostPlayback(media: Playable?, ended: Boolean, skipped: Boolean, playingNext: Boolean) {
if (isCancelled) return
originalCallback.onPostPlayback(media, ended, skipped, playingNext)
}
override fun onPlaybackStart(playable: Playable, position: Int) {
if (isCancelled) return
originalCallback.onPlaybackStart(playable, position)
}
override fun onPlaybackPause(playable: Playable?, position: Int) {
if (isCancelled) return
originalCallback.onPlaybackPause(playable, position)
}
override fun getNextInQueue(currentMedia: Playable?): Playable? {
if (isCancelled) return null
return originalCallback.getNextInQueue(currentMedia)
}
override fun findMedia(url: String): Playable? {
if (isCancelled) return null
return originalCallback.findMedia(url)
}
override fun onPlaybackEnded(mediaType: MediaType?, stopPlaying: Boolean) {
if (isCancelled) return
originalCallback.onPlaybackEnded(mediaType, stopPlaying)
}
override fun ensureMediaInfoLoaded(media: Playable) {
if (isCancelled) return
originalCallback.ensureMediaInfoLoaded(media)
}
}
companion object { companion object {
private const val PLAYABLE_DEST_URL = "psmptestfile.mp3" private const val PLAYABLE_DEST_URL = "psmptestfile.mp3"
private const val LATCH_TIMEOUT_SECONDS = 3 private const val LATCH_TIMEOUT_SECONDS = 3

View File

@ -14,6 +14,7 @@ import ac.mdiq.podcini.preferences.UserPreferences.appPrefs
import ac.mdiq.podcini.storage.database.Episodes import ac.mdiq.podcini.storage.database.Episodes
import ac.mdiq.podcini.storage.database.LogsAndStats import ac.mdiq.podcini.storage.database.LogsAndStats
import ac.mdiq.podcini.storage.database.Queues import ac.mdiq.podcini.storage.database.Queues
import ac.mdiq.podcini.storage.database.RealmDB.realm
import ac.mdiq.podcini.storage.database.RealmDB.upsertBlk import ac.mdiq.podcini.storage.database.RealmDB.upsertBlk
import ac.mdiq.podcini.storage.model.DownloadResult import ac.mdiq.podcini.storage.model.DownloadResult
import ac.mdiq.podcini.storage.model.Episode import ac.mdiq.podcini.storage.model.Episode
@ -60,7 +61,6 @@ class DownloadServiceInterfaceImpl : DownloadServiceInterface() {
workRequest.setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST) workRequest.setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST)
if (ignoreConstraints) workRequest.setConstraints(Builder().setRequiredNetworkType(NetworkType.CONNECTED).build()) if (ignoreConstraints) workRequest.setConstraints(Builder().setRequiredNetworkType(NetworkType.CONNECTED).build())
else workRequest.setConstraints(constraints) else workRequest.setConstraints(constraints)
if (item.media?.downloadUrl != null) if (item.media?.downloadUrl != null)
WorkManager.getInstance(context).enqueueUniqueWork(item.media!!.downloadUrl!!, ExistingWorkPolicy.KEEP, workRequest.build()) WorkManager.getInstance(context).enqueueUniqueWork(item.media!!.downloadUrl!!, ExistingWorkPolicy.KEEP, workRequest.build())
} }
@ -144,7 +144,7 @@ class DownloadServiceInterfaceImpl : DownloadServiceInterface() {
Logd(TAG, "starting doWork") Logd(TAG, "starting doWork")
ClientConfigurator.initialize(applicationContext) ClientConfigurator.initialize(applicationContext)
val mediaId = inputData.getLong(WORK_DATA_MEDIA_ID, 0) val mediaId = inputData.getLong(WORK_DATA_MEDIA_ID, 0)
val media = Episodes.getEpisodeMedia(mediaId) val media = realm.query(EpisodeMedia::class).query("id == $0", mediaId).first().find()
if (media == null) { if (media == null) {
Log.e(TAG, "media is null for mediaId: $mediaId") Log.e(TAG, "media is null for mediaId: $mediaId")
return Result.failure() return Result.failure()
@ -220,8 +220,13 @@ class DownloadServiceInterfaceImpl : DownloadServiceInterface() {
} }
if (dest.exists()) { if (dest.exists()) {
try { try {
media.setfileUrlOrNull(request.destination) var episode = realm.query(Episode::class).query("id == ${media.id}").first().find()
Episodes.persistEpisodeMedia(media) if (episode != null) {
episode = upsertBlk(episode) {
it.media?.setfileUrlOrNull(request.destination)
}
EventFlow.postEvent(FlowEvent.EpisodeMediaEvent.updated(episode))
} else Log.e(TAG, "performDownload media.episode is null")
} catch (e: Exception) { } catch (e: Exception) {
Log.e(TAG, "performDownload Exception in writeFileUrl: " + e.message) Log.e(TAG, "performDownload Exception in writeFileUrl: " + e.message)
} }
@ -340,79 +345,53 @@ class DownloadServiceInterfaceImpl : DownloadServiceInterface() {
class MediaDownloadedHandler(private val context: Context, var updatedStatus: DownloadResult, private val request: DownloadRequest) : Runnable { class MediaDownloadedHandler(private val context: Context, var updatedStatus: DownloadResult, private val request: DownloadRequest) : Runnable {
@UnstableApi override fun run() { @UnstableApi override fun run() {
val media = Episodes.getEpisodeMedia(request.feedfileId) var item = realm.query(Episode::class).query("id == ${request.feedfileId}").first().find()
if (item == null) {
Log.e(TAG, "Could not find downloaded episode object in database")
return
}
val media = item.media
if (media == null) { if (media == null) {
Log.e(TAG, "Could not find downloaded media object in database") Log.e(TAG, "Could not find downloaded media object in database")
return return
} }
// media.setDownloaded modifies played state val broadcastUnreadStateUpdate = item.isNew
var item = media.episodeOrFetch() item = upsertBlk(item) {
val broadcastUnreadStateUpdate = item?.isNew == true it.media?.setIsDownloaded()
// media.downloaded = true it.media?.setfileUrlOrNull(request.destination)
media.setIsDownloaded() if (request.destination != null) it.media?.size = File(request.destination).length()
// item = media.episodeOrFetch() it.media?.checkEmbeddedPicture(false) // enforce check
Logd(TAG, "media.episode.isNew: ${item?.isNew} ${item?.playState}") if (it.chapters.isEmpty()) it.media?.setChapters(ChapterUtils.loadChaptersFromMediaFile(it.media!!, context))
media.setfileUrlOrNull(request.destination) if (it.podcastIndexChapterUrl != null) ChapterUtils.loadChaptersFromUrl(it.podcastIndexChapterUrl!!, false)
if (request.destination != null) media.size = File(request.destination).length() var durationStr: String? = null
media.checkEmbeddedPicture(false) // enforce check try {
// check if file has chapters MediaMetadataRetrieverCompat().use { mmr ->
if (item?.chapters.isNullOrEmpty()) media.setChapters(ChapterUtils.loadChaptersFromMediaFile(media, context)) if (it.media != null) mmr.setDataSource(it.media!!.fileUrl)
if (item?.podcastIndexChapterUrl != null) ChapterUtils.loadChaptersFromUrl(item.podcastIndexChapterUrl!!, false) durationStr = mmr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION)
// Get duration if (durationStr != null) it.media?.setDuration(durationStr!!.toInt())
var durationStr: String? = null
try {
MediaMetadataRetrieverCompat().use { mmr ->
mmr.setDataSource(media.fileUrl)
durationStr = mmr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION)
if (durationStr != null) media.setDuration(durationStr!!.toInt())
Logd(TAG, "Duration of file is " + media.getDuration())
}
} catch (e: NumberFormatException) {
Logd(TAG, "Invalid file duration: $durationStr")
} catch (e: Exception) {
Log.e(TAG, "Get duration failed", e)
media.setDuration(30000)
}
// val item = media.episodeOrFetch()
item?.media = media
try {
// we've received the media, we don't want to autodownload it again
if (item != null) {
item = upsertBlk(item) {
it.disableAutoDownload()
} }
EventFlow.postEvent(FlowEvent.EpisodeEvent.updated(item)) } catch (e: NumberFormatException) {
Logd(TAG, "persisting episode downloaded ${item.title} ${item.media?.fileUrl} ${item.media?.downloaded} ${item.isNew}") Logd(TAG, "Invalid file duration: $durationStr")
// setFeedItem() signals that the item has been updated, } catch (e: Exception) {
// so we do it after the enclosing media has been updated above, Log.e(TAG, "Get duration failed", e)
// to ensure subscribers will get the updated EpisodeMedia as well it.media?.setDuration(30000)
// Episodes.persistEpisode(item)
// TODO: should use different event?
if (broadcastUnreadStateUpdate) EventFlow.postEvent(FlowEvent.EpisodePlayedEvent(item))
} }
} catch (e: InterruptedException) { it.disableAutoDownload()
Log.e(TAG, "MediaHandlerThread was interrupted")
} catch (e: ExecutionException) {
Log.e(TAG, "ExecutionException in MediaHandlerThread: " + e.message)
updatedStatus = DownloadResult(media.id, media.getEpisodeTitle(), DownloadError.ERROR_DB_ACCESS_ERROR, false, e.message?:"")
} }
if (needSynch() && item != null) { EventFlow.postEvent(FlowEvent.EpisodeEvent.updated(item))
val action = EpisodeAction.Builder(item, EpisodeAction.DOWNLOAD) // TODO: should use different event?
.currentTimestamp() if (broadcastUnreadStateUpdate) EventFlow.postEvent(FlowEvent.EpisodePlayedEvent(item))
.build() if (needSynch()) {
Logd(TAG, "enqueue synch")
val action = EpisodeAction.Builder(item, EpisodeAction.DOWNLOAD).currentTimestamp().build()
SynchronizationQueueSink.enqueueEpisodeActionIfSyncActive(context, action) SynchronizationQueueSink.enqueueEpisodeActionIfSyncActive(context, action)
} }
} Logd(TAG, "media.episode.isNew: ${item.isNew} ${item.playState}")
companion object {
private val TAG: String = MediaDownloadedHandler::class.simpleName ?: "Anonymous"
} }
} }
companion object { companion object {
private val TAG: String = EpisodeDownloadWorker::class.simpleName ?: "Anonymous"
private val notificationProgress: MutableMap<String, Int> = HashMap() private val notificationProgress: MutableMap<String, Int> = HashMap()
} }
} }
} }

View File

@ -143,15 +143,13 @@ class HttpDownloader(request: DownloadRequest) : Downloader(request) {
} catch (e: IOException) { } catch (e: IOException) {
Log.e(TAG, Log.getStackTraceString(e)) Log.e(TAG, Log.getStackTraceString(e))
} }
if (cancelled) { if (cancelled) onCancelled()
onCancelled() else {
} else {
// check if size specified in the response header is the same as the size of the // check if size specified in the response header is the same as the size of the
// written file. This check cannot be made if compression was used // written file. This check cannot be made if compression was used
when { when {
!isGzip && downloadRequest.size != DownloadResult.SIZE_UNKNOWN.toLong() && downloadRequest.soFar != downloadRequest.size -> { !isGzip && downloadRequest.size != DownloadResult.SIZE_UNKNOWN.toLong() && downloadRequest.soFar != downloadRequest.size -> {
onFail(DownloadError.ERROR_IO_WRONG_SIZE, onFail(DownloadError.ERROR_IO_WRONG_SIZE, "Download completed but size: ${downloadRequest.soFar} does not equal expected size ${downloadRequest.size}")
"Download completed but size: ${downloadRequest.soFar} does not equal expected size ${downloadRequest.size}")
return return
} }
downloadRequest.size > 0 && downloadRequest.soFar == 0L -> { downloadRequest.size > 0 && downloadRequest.soFar == 0L -> {
@ -208,9 +206,7 @@ class HttpDownloader(request: DownloadRequest) : Downloader(request) {
Log.e(TAG, e.toString()) Log.e(TAG, e.toString())
if (e.message != null && e.message!!.contains("PROTOCOL_ERROR")) { if (e.message != null && e.message!!.contains("PROTOCOL_ERROR")) {
// Apparently some servers announce they support SPDY but then actually don't. // Apparently some servers announce they support SPDY but then actually don't.
httpClient = httpClient.newBuilder() httpClient = httpClient.newBuilder().protocols(listOf(Protocol.HTTP_1_1)).build()
.protocols(listOf(Protocol.HTTP_1_1))
.build()
return httpClient.newCall(httpReq.build()).execute() return httpClient.newCall(httpReq.build()).execute()
} else { } else {
throw e throw e

View File

@ -289,8 +289,6 @@ open class SyncService(context: Context, params: WorkerParameters) : Worker(cont
updatedItems.add(result.second) updatedItems.add(result.second)
} }
removeFromQueue(*updatedItems.toTypedArray()) removeFromQueue(*updatedItems.toTypedArray())
// loadAdditionalFeedItemListData(updatedItems)
// persistEpisodes(updatedItems)
runOnIOScope { runOnIOScope {
for (episode in updatedItems) { for (episode in updatedItems) {
upsert(episode) {} upsert(episode) {}

View File

@ -10,6 +10,8 @@ import ac.mdiq.podcini.net.sync.model.EpisodeAction.Companion.readFromJsonObject
import ac.mdiq.podcini.storage.database.Episodes.getEpisodeByGuidOrUrl import ac.mdiq.podcini.storage.database.Episodes.getEpisodeByGuidOrUrl
import ac.mdiq.podcini.storage.database.Episodes.getEpisodes import ac.mdiq.podcini.storage.database.Episodes.getEpisodes
import ac.mdiq.podcini.storage.database.Episodes.persistEpisode import ac.mdiq.podcini.storage.database.Episodes.persistEpisode
import ac.mdiq.podcini.storage.database.RealmDB.upsert
import ac.mdiq.podcini.storage.database.RealmDB.upsertBlk
import ac.mdiq.podcini.storage.model.Episode import ac.mdiq.podcini.storage.model.Episode
import ac.mdiq.podcini.storage.model.EpisodeFilter import ac.mdiq.podcini.storage.model.EpisodeFilter
import ac.mdiq.podcini.storage.utils.EpisodeUtil.hasAlmostEnded import ac.mdiq.podcini.storage.utils.EpisodeUtil.hasAlmostEnded
@ -315,7 +317,7 @@ import kotlin.math.min
override fun processEpisodeAction(action: EpisodeAction): Pair<Long, Episode>? { override fun processEpisodeAction(action: EpisodeAction): Pair<Long, Episode>? {
val guid = if (isValidGuid(action.guid)) action.guid else null val guid = if (isValidGuid(action.guid)) action.guid else null
val feedItem = getEpisodeByGuidOrUrl(guid, action.episode?:"") var feedItem = getEpisodeByGuidOrUrl(guid, action.episode?:"", false)
if (feedItem == null) { if (feedItem == null) {
Logd(TAG, "Unknown feed item: $action") Logd(TAG, "Unknown feed item: $action")
return null return null
@ -328,23 +330,23 @@ import kotlin.math.min
var idRemove: Long? = null var idRemove: Long? = null
Logd(TAG, "processEpisodeAction ${feedItem.media!!.getLastPlayedTime()} ${(action.timestamp?.time?:0L)} ${action.position} ${feedItem.title}") Logd(TAG, "processEpisodeAction ${feedItem.media!!.getLastPlayedTime()} ${(action.timestamp?.time?:0L)} ${action.position} ${feedItem.title}")
if (feedItem.media!!.getLastPlayedTime() < (action.timestamp?.time?:0L)) { if (feedItem.media!!.getLastPlayedTime() < (action.timestamp?.time?:0L)) {
feedItem.media!!.startPosition = action.started * 1000 feedItem = upsertBlk(feedItem) {
feedItem.media!!.setPosition(action.position * 1000) it.media!!.startPosition = action.started * 1000
feedItem.media!!.playedDuration = action.playedDuration * 1000 it.media!!.setPosition(action.position * 1000)
feedItem.media!!.setLastPlayedTime(action.timestamp!!.time) it.media!!.playedDuration = action.playedDuration * 1000
feedItem.isFavorite = action.isFavorite it.media!!.setLastPlayedTime(action.timestamp!!.time)
feedItem.playState = action.playState it.isFavorite = action.isFavorite
if (hasAlmostEnded(feedItem.media!!)) { it.playState = action.playState
Logd(TAG, "Marking as played") if (hasAlmostEnded(it.media!!)) {
feedItem.setPlayed(true) Logd(TAG, "Marking as played")
feedItem.media!!.setPosition(0) it.setPlayed(true)
idRemove = feedItem.id it.media!!.setPosition(0)
} else Logd(TAG, "Setting position") idRemove = it.id
// persistFeedMediaPlaybackInfo(feedItem.media) } else Logd(TAG, "Setting position")
persistEpisode(feedItem) }
EventFlow.postEvent(FlowEvent.EpisodeEvent.updated(feedItem))
} else Logd(TAG, "local is newer, no change") } else Logd(TAG, "local is newer, no change")
return if (idRemove != null) Pair(idRemove!!, feedItem) else null
return if (idRemove != null) Pair(idRemove, feedItem) else null
} }
override fun logout() { override fun logout() {

View File

@ -48,12 +48,8 @@ object InTheatre {
CoroutineScope(Dispatchers.IO).launch { CoroutineScope(Dispatchers.IO).launch {
Logd(TAG, "starting curQueue") Logd(TAG, "starting curQueue")
var curQueue_ = realm.query(PlayQueue::class).sort("updated", Sort.DESCENDING).first().find() var curQueue_ = realm.query(PlayQueue::class).sort("updated", Sort.DESCENDING).first().find()
if (curQueue_ != null) { if (curQueue_ != null) curQueue = curQueue_
curQueue = curQueue_ else {
// curQueue.episodes.addAll(realm.copyFromRealm(realm.query(Episode::class, "id IN $0", curQueue.episodeIds)
// .find().sortedBy { curQueue.episodeIds.indexOf(it.id) }))
} else {
Logd(TAG, "creating new curQueue")
for (i in 0..4) { for (i in 0..4) {
curQueue_ = PlayQueue() curQueue_ = PlayQueue()
if (i == 0) { if (i == 0) {
@ -72,7 +68,6 @@ object InTheatre {
Logd(TAG, "starting curState") Logd(TAG, "starting curState")
var curState_ = realm.query(CurrentState::class).first().find() var curState_ = realm.query(CurrentState::class).first().find()
// if (curState_ != null) curState = unmanaged(curState_)
if (curState_ != null) curState = curState_ if (curState_ != null) curState = curState_
else { else {
Logd(TAG, "creating new curState") Logd(TAG, "creating new curState")

View File

@ -262,7 +262,7 @@ abstract class MediaPlayerBase protected constructor(protected val context: Cont
*/ */
@Synchronized @Synchronized
protected fun setPlayerStatus(newStatus: PlayerStatus, newMedia: Playable?, position: Int = Playable.INVALID_TIME) { protected fun setPlayerStatus(newStatus: PlayerStatus, newMedia: Playable?, position: Int = Playable.INVALID_TIME) {
Logd(TAG, this.javaClass.simpleName + ": Setting player status to " + newStatus) Log.d(TAG, "${this.javaClass.simpleName}: Setting player status to $newStatus")
this.oldStatus = status this.oldStatus = status
status = newStatus status = newStatus
if (newMedia != null) setPlayable(newMedia) if (newMedia != null) setPlayable(newMedia)

View File

@ -307,7 +307,7 @@ class LocalMediaPlayer(context: Context, callback: MediaPlayerCallback) : MediaP
override fun resume() { override fun resume() {
if (status == PlayerStatus.PAUSED || status == PlayerStatus.PREPARED) { if (status == PlayerStatus.PAUSED || status == PlayerStatus.PREPARED) {
Logd(TAG, "Resuming/Starting playback") Log.d(TAG, "Resuming/Starting playback")
acquireWifiLockIfNecessary() acquireWifiLockIfNecessary()
setPlaybackParams(getCurrentPlaybackSpeed(curMedia), UserPreferences.isSkipSilence) setPlaybackParams(getCurrentPlaybackSpeed(curMedia), UserPreferences.isSkipSilence)
setVolume(1.0f, 1.0f) setVolume(1.0f, 1.0f)
@ -646,13 +646,11 @@ class LocalMediaPlayer(context: Context, callback: MediaPlayerCallback) : MediaP
else -> bufferingUpdateListener?.accept(BUFFERING_ENDED) else -> bufferingUpdateListener?.accept(BUFFERING_ENDED)
} }
} }
override fun onIsPlayingChanged(isPlaying: Boolean) { override fun onIsPlayingChanged(isPlaying: Boolean) {
val stat = if (isPlaying) PlayerStatus.PLAYING else PlayerStatus.PAUSED val stat = if (isPlaying) PlayerStatus.PLAYING else PlayerStatus.PAUSED
setPlayerStatus(stat, curMedia) setPlayerStatus(stat, curMedia)
Logd(TAG, "onIsPlayingChanged $isPlaying") Log.d(TAG, "onIsPlayingChanged $isPlaying")
} }
override fun onPlayerError(error: PlaybackException) { override fun onPlayerError(error: PlaybackException) {
Logd(TAG, "onPlayerError ${error.message}") Logd(TAG, "onPlayerError ${error.message}")
if (wasDownloadBlocked(error)) audioErrorListener?.accept(context.getString(R.string.download_error_blocked)) if (wasDownloadBlocked(error)) audioErrorListener?.accept(context.getString(R.string.download_error_blocked))
@ -663,12 +661,10 @@ class LocalMediaPlayer(context: Context, callback: MediaPlayerCallback) : MediaP
audioErrorListener?.accept((if (cause != null) cause.message else error.message) ?:"no message") audioErrorListener?.accept((if (cause != null) cause.message else error.message) ?:"no message")
} }
} }
override fun onPositionDiscontinuity(oldPosition: PositionInfo, newPosition: PositionInfo, reason: @DiscontinuityReason Int) { override fun onPositionDiscontinuity(oldPosition: PositionInfo, newPosition: PositionInfo, reason: @DiscontinuityReason Int) {
Logd(TAG, "onPositionDiscontinuity $oldPosition $newPosition $reason") Logd(TAG, "onPositionDiscontinuity $oldPosition $newPosition $reason")
if (reason == DISCONTINUITY_REASON_SEEK) audioSeekCompleteListener?.run() if (reason == DISCONTINUITY_REASON_SEEK) audioSeekCompleteListener?.run()
} }
override fun onAudioSessionIdChanged(audioSessionId: Int) { override fun onAudioSessionIdChanged(audioSessionId: Int) {
Logd(TAG, "onAudioSessionIdChanged $audioSessionId") Logd(TAG, "onAudioSessionIdChanged $audioSessionId")
initLoudnessEnhancer(audioSessionId) initLoudnessEnhancer(audioSessionId)

View File

@ -136,9 +136,8 @@ class PlaybackService : MediaSessionService() {
val status = intent.getStringExtra("media_connection_status") val status = intent.getStringExtra("media_connection_status")
val isConnectedToCar = "media_connected" == status val isConnectedToCar = "media_connected" == status
Logd(TAG, "Received Auto Connection update: $status") Logd(TAG, "Received Auto Connection update: $status")
if (!isConnectedToCar) { if (!isConnectedToCar) Logd(TAG, "Car was unplugged during playback.")
Logd(TAG, "Car was unplugged during playback.") else {
} else {
val playerStatus = MediaPlayerBase.status val playerStatus = MediaPlayerBase.status
when (playerStatus) { when (playerStatus) {
PlayerStatus.PAUSED, PlayerStatus.PREPARED -> mPlayer?.resume() PlayerStatus.PAUSED, PlayerStatus.PREPARED -> mPlayer?.resume()
@ -229,8 +228,7 @@ class PlaybackService : MediaSessionService() {
Log.d(TAG, "statusChanged called ${newInfo?.playerStatus}") Log.d(TAG, "statusChanged called ${newInfo?.playerStatus}")
if (newInfo != null) { if (newInfo != null) {
when (newInfo.playerStatus) { when (newInfo.playerStatus) {
PlayerStatus.INITIALIZED -> PlayerStatus.INITIALIZED -> if (mPlayer != null) writeMediaPlaying(mPlayer!!.playerInfo.playable, mPlayer!!.playerInfo.playerStatus)
if (mPlayer != null) writeMediaPlaying(mPlayer!!.playerInfo.playable, mPlayer!!.playerInfo.playerStatus)
PlayerStatus.PREPARED -> { PlayerStatus.PREPARED -> {
if (mPlayer != null) writeMediaPlaying(mPlayer!!.playerInfo.playable, mPlayer!!.playerInfo.playerStatus) if (mPlayer != null) writeMediaPlaying(mPlayer!!.playerInfo.playable, mPlayer!!.playerInfo.playerStatus)
if (newInfo.playable != null) taskManager.startChapterLoader(newInfo.playable!!) if (newInfo.playable != null) taskManager.startChapterLoader(newInfo.playable!!)
@ -382,8 +380,7 @@ class PlaybackService : MediaSessionService() {
val i = EpisodeUtil.indexOfItemWithId(eList, item.id) val i = EpisodeUtil.indexOfItemWithId(eList, item.id)
Logd(TAG, "getNextInQueue current i: $i curIndexInQueue: $curIndexInQueue") Logd(TAG, "getNextInQueue current i: $i curIndexInQueue: $curIndexInQueue")
if (i < 0) { if (i < 0) {
if (curIndexInQueue >= 0 && curIndexInQueue < eList.size) j = curIndexInQueue j = if (curIndexInQueue >= 0 && curIndexInQueue < eList.size) curIndexInQueue else eList.size-1
else j = eList.size-1
} else if (i < eList.size-1) j = i+1 } else if (i < eList.size-1) j = i+1
Logd(TAG, "getNextInQueue next j: $j") Logd(TAG, "getNextInQueue next j: $j")
@ -411,17 +408,15 @@ class PlaybackService : MediaSessionService() {
EventFlow.postEvent(FlowEvent.PlayEvent(nextItem)) EventFlow.postEvent(FlowEvent.PlayEvent(nextItem))
return if (nextItem.media == null) null else unmanaged(nextItem.media!!) return if (nextItem.media == null) null else unmanaged(nextItem.media!!)
} }
// only used in test
override fun findMedia(url: String): Playable? { override fun findMedia(url: String): Playable? {
val item = getEpisodeByGuidOrUrl(null, url) val item = getEpisodeByGuidOrUrl(null, url)
return item?.media return item?.media
} }
override fun onPlaybackEnded(mediaType: MediaType?, stopPlaying: Boolean) { override fun onPlaybackEnded(mediaType: MediaType?, stopPlaying: Boolean) {
Logd(TAG, "onPlaybackEnded mediaType: $mediaType stopPlaying: $stopPlaying") Logd(TAG, "onPlaybackEnded mediaType: $mediaType stopPlaying: $stopPlaying")
clearCurTempSpeed() clearCurTempSpeed()
if (stopPlaying) taskManager.cancelPositionSaver() if (stopPlaying) taskManager.cancelPositionSaver()
if (mediaType == null) sendNotificationBroadcast(NOTIFICATION_TYPE_PLAYBACK_END, 0) if (mediaType == null) sendNotificationBroadcast(NOTIFICATION_TYPE_PLAYBACK_END, 0)
else sendNotificationBroadcast(NOTIFICATION_TYPE_RELOAD, else sendNotificationBroadcast(NOTIFICATION_TYPE_RELOAD,
when { when {
@ -694,9 +689,15 @@ class PlaybackService : MediaSessionService() {
val keycode = intent?.getIntExtra(MediaButtonReceiver.EXTRA_KEYCODE, -1) ?: -1 val keycode = intent?.getIntExtra(MediaButtonReceiver.EXTRA_KEYCODE, -1) ?: -1
val customAction = intent?.getStringExtra(MediaButtonReceiver.EXTRA_CUSTOM_ACTION) val customAction = intent?.getStringExtra(MediaButtonReceiver.EXTRA_CUSTOM_ACTION)
val hardwareButton = intent?.getBooleanExtra(MediaButtonReceiver.EXTRA_HARDWAREBUTTON, false) ?: false val hardwareButton = intent?.getBooleanExtra(MediaButtonReceiver.EXTRA_HARDWAREBUTTON, false) ?: false
val keyEvent: KeyEvent? = if (Build.VERSION.SDK_INT >= VERSION_CODES.TIRAMISU) {
intent?.getParcelableExtra(EXTRA_KEY_EVENT, KeyEvent::class.java)
} else {
@Suppress("DEPRECATION")
intent?.getParcelableExtra(EXTRA_KEY_EVENT)
}
val playable = curMedia val playable = curMedia
Log.d(TAG, "onStartCommand flags=$flags startId=$startId keycode=$keycode customAction=$customAction hardwareButton=$hardwareButton action=${intent?.action.toString()} ${playable?.getEpisodeTitle()}") Log.d(TAG, "onStartCommand flags=$flags startId=$startId keycode=$keycode keyEvent=$keyEvent customAction=$customAction hardwareButton=$hardwareButton action=${intent?.action.toString()} ${playable?.getEpisodeTitle()}")
if (keycode == -1 && playable == null && customAction == null) { if (keycode == -1 && playable == null && customAction == null) {
Log.e(TAG, "onStartCommand PlaybackService was started with no arguments, return") Log.e(TAG, "onStartCommand PlaybackService was started with no arguments, return")
return START_NOT_STICKY return START_NOT_STICKY
@ -797,7 +798,7 @@ class PlaybackService : MediaSessionService() {
* Handles media button events. return: keycode was handled * Handles media button events. return: keycode was handled
*/ */
private fun handleKeycode(keycode: Int, notificationButton: Boolean): Boolean { private fun handleKeycode(keycode: Int, notificationButton: Boolean): Boolean {
Logd(TAG, "Handling keycode: $keycode") Log.d(TAG, "Handling keycode: $keycode")
val info = mPlayer?.playerInfo val info = mPlayer?.playerInfo
val status = info?.playerStatus val status = info?.playerStatus
when (keycode) { when (keycode) {
@ -1064,12 +1065,6 @@ class PlaybackService : MediaSessionService() {
playable.setLastPlayedTime(System.currentTimeMillis()) playable.setLastPlayedTime(System.currentTimeMillis())
if (playable is EpisodeMedia) { if (playable is EpisodeMedia) {
// val item = playable.episodeOrFetch()
// if (item != null && item.isNew) item.playState = Episode.UNPLAYED
// if (playable.startPosition >= 0 && playable.getPosition() > playable.startPosition)
// playable.playedDuration = (playable.playedDurationWhenStarted + playable.getPosition() - playable.startPosition)
// persistEpisode(item, true)
var item = realm.query(Episode::class, "id == ${playable.id}").first().find() var item = realm.query(Episode::class, "id == ${playable.id}").first().find()
if (item != null) { if (item != null) {
item = upsertBlk(item) { item = upsertBlk(item) {

View File

@ -23,8 +23,6 @@ import ac.mdiq.podcini.storage.utils.FileNameGenerator.generateFileName
import ac.mdiq.podcini.storage.utils.FilesUtils.getDataFolder import ac.mdiq.podcini.storage.utils.FilesUtils.getDataFolder
import ac.mdiq.podcini.ui.activity.OpmlImportActivity import ac.mdiq.podcini.ui.activity.OpmlImportActivity
import ac.mdiq.podcini.ui.activity.PreferenceActivity import ac.mdiq.podcini.ui.activity.PreferenceActivity
import ac.mdiq.podcini.ui.fragment.SubscriptionsFragment
import ac.mdiq.podcini.ui.fragment.SubscriptionsFragment.Companion
import ac.mdiq.podcini.util.Logd import ac.mdiq.podcini.util.Logd
import android.app.Activity.RESULT_OK import android.app.Activity.RESULT_OK
import android.app.ProgressDialog import android.app.ProgressDialog
@ -934,29 +932,31 @@ class ImportExportPreferencesFragment : PreferenceFragmentCompat() {
val action = readFromJsonObject(jsonAction) ?: continue val action = readFromJsonObject(jsonAction) ?: continue
Logd(TAG, "processing action: $action") Logd(TAG, "processing action: $action")
val result = processEpisodeAction(action) ?: continue val result = processEpisodeAction(action) ?: continue
upsertBlk(result.second) {} // upsertBlk(result.second) {}
} }
} }
private fun processEpisodeAction(action: EpisodeAction): Pair<Long, Episode>? { private fun processEpisodeAction(action: EpisodeAction): Pair<Long, Episode>? {
val guid = if (isValidGuid(action.guid)) action.guid else null val guid = if (isValidGuid(action.guid)) action.guid else null
val feedItem = getEpisodeByGuidOrUrl(guid, action.episode?:"") ?: return null var feedItem = getEpisodeByGuidOrUrl(guid, action.episode?:"", false) ?: return null
if (feedItem.media == null) { if (feedItem.media == null) {
Logd(TAG, "Feed item has no media: $action") Logd(TAG, "Feed item has no media: $action")
return null return null
} }
var idRemove = 0L var idRemove = 0L
feedItem.media!!.startPosition = action.started * 1000 feedItem = upsertBlk(feedItem) {
feedItem.media!!.setPosition(action.position * 1000) it.media!!.startPosition = action.started * 1000
feedItem.media!!.playedDuration = action.playedDuration * 1000 it.media!!.setPosition(action.position * 1000)
feedItem.media!!.setLastPlayedTime(action.timestamp!!.time) it.media!!.playedDuration = action.playedDuration * 1000
feedItem.isFavorite = action.isFavorite it.media!!.setLastPlayedTime(action.timestamp!!.time)
feedItem.playState = action.playState it.isFavorite = action.isFavorite
if (hasAlmostEnded(feedItem.media!!)) { it.playState = action.playState
Logd(TAG, "Marking as played: $action") if (hasAlmostEnded(it.media!!)) {
feedItem.setPlayed(true) Logd(TAG, "Marking as played: $action")
feedItem.media!!.setPosition(0) it.setPlayed(true)
idRemove = feedItem.id it.media!!.setPosition(0)
} else Logd(TAG, "Setting position: $action") idRemove = it.id
} else Logd(TAG, "Setting position: $action")
}
return Pair(idRemove, feedItem) return Pair(idRemove, feedItem)
} }
} }

View File

@ -80,16 +80,18 @@ object Episodes {
* @return The FeedItem or null if the FeedItem could not be found. * @return The FeedItem or null if the FeedItem could not be found.
* Does NOT load additional attributes like feed or queue state. * Does NOT load additional attributes like feed or queue state.
*/ */
fun getEpisodeByGuidOrUrl(guid: String?, episodeUrl: String): Episode? { fun getEpisodeByGuidOrUrl(guid: String?, episodeUrl: String, copy: Boolean = true): Episode? {
Logd(TAG, "getEpisodeByGuidOrUrl called $guid $episodeUrl") Logd(TAG, "getEpisodeByGuidOrUrl called $guid $episodeUrl")
val episode = if (guid != null) realm.query(Episode::class).query("identifier == $0", guid).first().find() val episode = if (guid != null) realm.query(Episode::class).query("identifier == $0", guid).first().find()
else realm.query(Episode::class).query("media.downloadUrl == $0", episodeUrl).first().find() else realm.query(Episode::class).query("media.downloadUrl == $0", episodeUrl).first().find()
if (!copy) return episode
return if (episode != null) realm.copyFromRealm(episode) else null return if (episode != null) realm.copyFromRealm(episode) else null
} }
fun getEpisodeMedia(mediaId: Long): EpisodeMedia? { fun getEpisodeMedia(mediaId: Long, copy: Boolean = true): EpisodeMedia? {
Logd(TAG, "getEpisodeMedia called $mediaId") Logd(TAG, "getEpisodeMedia called $mediaId")
val media = realm.query(EpisodeMedia::class).query("id == $0", mediaId).first().find() val media = realm.query(EpisodeMedia::class).query("id == $0", mediaId).first().find()
if (!copy) return media
return if (media != null) realm.copyFromRealm(media) else null return if (media != null) realm.copyFromRealm(media) else null
} }
@ -217,6 +219,7 @@ object Episodes {
// } // }
// } // }
// only used in tests
fun persistEpisodeMedia(media: EpisodeMedia) : Job { fun persistEpisodeMedia(media: EpisodeMedia) : Job {
Logd(TAG, "persistEpisodeMedia called") Logd(TAG, "persistEpisodeMedia called")
return runOnIOScope { return runOnIOScope {

View File

@ -148,15 +148,14 @@ object Queues {
suspend fun addToQueueSync(markAsUnplayed: Boolean, episode: Episode, queue_: PlayQueue? = null) { suspend fun addToQueueSync(markAsUnplayed: Boolean, episode: Episode, queue_: PlayQueue? = null) {
Logd(TAG, "addToQueueSync( ... ) called") Logd(TAG, "addToQueueSync( ... ) called")
val queue = queue_ ?: curQueue val queue = queue_ ?: curQueue
if (queue.episodeIds.contains(episode.id)) return
val currentlyPlaying = curMedia val currentlyPlaying = curMedia
val positionCalculator = EnqueuePositionPolicy(enqueueLocation) val positionCalculator = EnqueuePositionPolicy(enqueueLocation)
var insertPosition = positionCalculator.calcPosition(queue.episodes, currentlyPlaying) var insertPosition = positionCalculator.calcPosition(queue.episodes, currentlyPlaying)
Logd(TAG, "addToQueueSync insertPosition: $insertPosition") Logd(TAG, "addToQueueSync insertPosition: $insertPosition")
if (queue.episodeIds.contains(episode.id)) return
val queueNew = upsert(queue) { val queueNew = upsert(queue) {
if (!it.episodeIds.contains(episode.id)) it.episodeIds.add(insertPosition, episode.id) if (!it.episodeIds.contains(episode.id)) it.episodeIds.add(insertPosition, episode.id)
insertPosition++ insertPosition++

View File

@ -1,11 +1,11 @@
package ac.mdiq.podcini.ui.dialog package ac.mdiq.podcini.ui.dialog
import ac.mdiq.podcini.R import ac.mdiq.podcini.R
import ac.mdiq.podcini.storage.database.Episodes.getEpisodeMedia
import ac.mdiq.podcini.storage.database.Feeds.getFeed import ac.mdiq.podcini.storage.database.Feeds.getFeed
import ac.mdiq.podcini.storage.database.RealmDB.realm
import ac.mdiq.podcini.storage.model.DownloadResult import ac.mdiq.podcini.storage.model.DownloadResult
import ac.mdiq.podcini.storage.model.Feed
import ac.mdiq.podcini.storage.model.EpisodeMedia import ac.mdiq.podcini.storage.model.EpisodeMedia
import ac.mdiq.podcini.storage.model.Feed
import ac.mdiq.podcini.util.error.DownloadErrorLabel.from import ac.mdiq.podcini.util.error.DownloadErrorLabel.from
import ac.mdiq.podcini.util.event.EventFlow import ac.mdiq.podcini.util.event.EventFlow
import ac.mdiq.podcini.util.event.FlowEvent import ac.mdiq.podcini.util.event.FlowEvent
@ -23,7 +23,7 @@ class DownloadLogDetailsDialog(context: Context, status: DownloadResult) : Mater
var url = "unknown" var url = "unknown"
when (status.feedfileType) { when (status.feedfileType) {
EpisodeMedia.FEEDFILETYPE_FEEDMEDIA -> { EpisodeMedia.FEEDFILETYPE_FEEDMEDIA -> {
val media = getEpisodeMedia(status.feedfileId) val media = realm.query(EpisodeMedia::class).query("id == $0", status.feedfileId).first().find()
if (media != null) url = media.downloadUrl?:"" if (media != null) url = media.downloadUrl?:""
} }
Feed.FEEDFILETYPE_FEED -> { Feed.FEEDFILETYPE_FEED -> {

View File

@ -5,13 +5,14 @@ import ac.mdiq.podcini.databinding.DownloadLogFragmentBinding
import ac.mdiq.podcini.databinding.DownloadlogItemBinding import ac.mdiq.podcini.databinding.DownloadlogItemBinding
import ac.mdiq.podcini.net.download.DownloadError import ac.mdiq.podcini.net.download.DownloadError
import ac.mdiq.podcini.net.feed.FeedUpdateManager import ac.mdiq.podcini.net.feed.FeedUpdateManager
import ac.mdiq.podcini.storage.database.Episodes.getEpisodeMedia
import ac.mdiq.podcini.storage.database.Feeds.getFeed import ac.mdiq.podcini.storage.database.Feeds.getFeed
import ac.mdiq.podcini.storage.database.RealmDB.realm import ac.mdiq.podcini.storage.database.RealmDB.realm
import ac.mdiq.podcini.storage.database.RealmDB.runOnIOScope import ac.mdiq.podcini.storage.database.RealmDB.runOnIOScope
import ac.mdiq.podcini.storage.model.DownloadResult import ac.mdiq.podcini.storage.model.DownloadResult
import ac.mdiq.podcini.storage.model.Episode
import ac.mdiq.podcini.storage.model.EpisodeMedia import ac.mdiq.podcini.storage.model.EpisodeMedia
import ac.mdiq.podcini.storage.model.Feed import ac.mdiq.podcini.storage.model.Feed
import ac.mdiq.podcini.storage.utils.DownloadResultComparator
import ac.mdiq.podcini.ui.actions.actionbutton.DownloadActionButton import ac.mdiq.podcini.ui.actions.actionbutton.DownloadActionButton
import ac.mdiq.podcini.ui.activity.MainActivity import ac.mdiq.podcini.ui.activity.MainActivity
import ac.mdiq.podcini.ui.dialog.DownloadLogDetailsDialog import ac.mdiq.podcini.ui.dialog.DownloadLogDetailsDialog
@ -21,7 +22,6 @@ import ac.mdiq.podcini.util.Logd
import ac.mdiq.podcini.util.error.DownloadErrorLabel import ac.mdiq.podcini.util.error.DownloadErrorLabel
import ac.mdiq.podcini.util.event.EventFlow import ac.mdiq.podcini.util.event.EventFlow
import ac.mdiq.podcini.util.event.FlowEvent import ac.mdiq.podcini.util.event.FlowEvent
import ac.mdiq.podcini.storage.utils.DownloadResultComparator
import android.app.Activity import android.app.Activity
import android.content.Context import android.content.Context
import android.os.Bundle import android.os.Bundle
@ -237,17 +237,12 @@ class DownloadLogFragment : BottomSheetDialogFragment(), OnItemClickListener, To
}) })
} }
EpisodeMedia.FEEDFILETYPE_FEEDMEDIA -> { EpisodeMedia.FEEDFILETYPE_FEEDMEDIA -> {
holder.secondaryActionButton.setOnClickListener(View.OnClickListener { holder.secondaryActionButton.setOnClickListener {
holder.secondaryActionButton.visibility = View.INVISIBLE holder.secondaryActionButton.visibility = View.INVISIBLE
val media: EpisodeMedia? = getEpisodeMedia(status.feedfileId) val item_ = realm.query(Episode::class).query("id == $0", status.feedfileId).first().find()
if (media == null) {
Log.e(TAG, "Could not find feed media for feed id: " + status.feedfileId)
return@OnClickListener
}
val item_ = media.episodeOrFetch()
if (item_ != null) DownloadActionButton(item_).onClick(context) if (item_ != null) DownloadActionButton(item_).onClick(context)
(context as MainActivity).showSnackbarAbovePlayer(R.string.status_downloading_label, Toast.LENGTH_SHORT) (context as MainActivity).showSnackbarAbovePlayer(R.string.status_downloading_label, Toast.LENGTH_SHORT)
}) }
} }
} }
} }

View File

@ -136,7 +136,7 @@ import java.util.*
}) })
speedDialView.setOnActionSelectedListener { actionItem: SpeedDialActionItem -> speedDialView.setOnActionSelectedListener { actionItem: SpeedDialActionItem ->
adapter.selectedItems.let { adapter.selectedItems.let {
EpisodeMultiSelectHandler((activity as MainActivity), actionItem.id).handleAction(it.filterIsInstance<Episode>()) EpisodeMultiSelectHandler((activity as MainActivity), actionItem.id).handleAction(it)
} }
adapter.endSelectMode() adapter.endSelectMode()
true true

View File

@ -4,7 +4,7 @@ buildscript {
mavenCentral() mavenCentral()
gradlePluginPortal() gradlePluginPortal()
} }
ext.kotlin_version = '2.0.0' ext.kotlin_version = '2.0.10'
dependencies { dependencies {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath 'com.android.tools.build:gradle:8.5.2' classpath 'com.android.tools.build:gradle:8.5.2'

View File

@ -1,3 +1,10 @@
# 6.3.7
* inlined some DB writes of Episodes in some routines
* enhanced DB writes in download routine, fixed a write error
* added a couple more Log.d statements in hope for tracking down the mysterious random playing
* Kotlin upped to 2.0.10
# 6.3.6 # 6.3.6
* upgraded gradle to 8.9 and Android Gradle Plugin to 8.5.2 * upgraded gradle to 8.9 and Android Gradle Plugin to 8.5.2

View File

@ -0,0 +1,6 @@
Version 6.3.7 brings several changes:
* inlined some DB writes of Episodes in some routines
* enhanced DB writes in download routine, fixed a write error
* added a couple more Log.d statements in hope for tracking down the mysterious random playing
* Kotlin upped to 2.0.10