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]
Timestamp=2024,8,2,9,3,22.216
Timestamp=2024,8,10,7,39,7.985
Version=4
ViewMode=1
[Settings]
HiddenFilesShown=true

View File

@ -4,7 +4,7 @@ plugins {
id 'org.jetbrains.kotlin.android'
alias(libs.plugins.compose.compiler)
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'
@ -31,8 +31,8 @@ android {
testApplicationId "ac.mdiq.podcini.tests"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
versionCode 3020230
versionName "6.3.6"
versionCode 3020231
versionName "6.3.7"
applicationId "ac.mdiq.podcini.R"
def commit = ""
@ -102,23 +102,11 @@ android {
namespace "ac.mdiq.podcini"
lint {
lintConfig = file("lint.xml")
// disable "GradleDependency"
checkReleaseBuilds false
checkDependencies true
warningsAsErrors true
abortOnError 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',
'ExportedReceiver', 'VectorDrawableCompat', 'NestedWeights', 'Overdraw', 'TextFields',
'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.MediaPlayerInfo
import ac.mdiq.podcini.playback.base.MediaPlayerCallback
import ac.mdiq.podcini.playback.base.PlayerStatus
import ac.mdiq.podcini.playback.service.LocalMediaPlayer
import ac.mdiq.podcini.storage.model.*
@ -798,6 +799,72 @@ class MediaPlayerBaseTest {
}
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 {
private const val PLAYABLE_DEST_URL = "psmptestfile.mp3"
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.LogsAndStats
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.model.DownloadResult
import ac.mdiq.podcini.storage.model.Episode
@ -60,7 +61,6 @@ class DownloadServiceInterfaceImpl : DownloadServiceInterface() {
workRequest.setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST)
if (ignoreConstraints) workRequest.setConstraints(Builder().setRequiredNetworkType(NetworkType.CONNECTED).build())
else workRequest.setConstraints(constraints)
if (item.media?.downloadUrl != null)
WorkManager.getInstance(context).enqueueUniqueWork(item.media!!.downloadUrl!!, ExistingWorkPolicy.KEEP, workRequest.build())
}
@ -144,7 +144,7 @@ class DownloadServiceInterfaceImpl : DownloadServiceInterface() {
Logd(TAG, "starting doWork")
ClientConfigurator.initialize(applicationContext)
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) {
Log.e(TAG, "media is null for mediaId: $mediaId")
return Result.failure()
@ -220,8 +220,13 @@ class DownloadServiceInterfaceImpl : DownloadServiceInterface() {
}
if (dest.exists()) {
try {
media.setfileUrlOrNull(request.destination)
Episodes.persistEpisodeMedia(media)
var episode = realm.query(Episode::class).query("id == ${media.id}").first().find()
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) {
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 {
@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) {
Log.e(TAG, "Could not find downloaded media object in database")
return
}
// media.setDownloaded modifies played state
var item = media.episodeOrFetch()
val broadcastUnreadStateUpdate = item?.isNew == true
// media.downloaded = true
media.setIsDownloaded()
// item = media.episodeOrFetch()
Logd(TAG, "media.episode.isNew: ${item?.isNew} ${item?.playState}")
media.setfileUrlOrNull(request.destination)
if (request.destination != null) media.size = File(request.destination).length()
media.checkEmbeddedPicture(false) // enforce check
// check if file has chapters
if (item?.chapters.isNullOrEmpty()) media.setChapters(ChapterUtils.loadChaptersFromMediaFile(media, context))
if (item?.podcastIndexChapterUrl != null) ChapterUtils.loadChaptersFromUrl(item.podcastIndexChapterUrl!!, false)
// Get duration
val broadcastUnreadStateUpdate = item.isNew
item = upsertBlk(item) {
it.media?.setIsDownloaded()
it.media?.setfileUrlOrNull(request.destination)
if (request.destination != null) it.media?.size = File(request.destination).length()
it.media?.checkEmbeddedPicture(false) // enforce check
if (it.chapters.isEmpty()) it.media?.setChapters(ChapterUtils.loadChaptersFromMediaFile(it.media!!, context))
if (it.podcastIndexChapterUrl != null) ChapterUtils.loadChaptersFromUrl(it.podcastIndexChapterUrl!!, false)
var durationStr: String? = null
try {
MediaMetadataRetrieverCompat().use { mmr ->
mmr.setDataSource(media.fileUrl)
if (it.media != null) mmr.setDataSource(it.media!!.fileUrl)
durationStr = mmr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION)
if (durationStr != null) media.setDuration(durationStr!!.toInt())
Logd(TAG, "Duration of file is " + media.getDuration())
if (durationStr != null) it.media?.setDuration(durationStr!!.toInt())
}
} catch (e: NumberFormatException) {
Logd(TAG, "Invalid file duration: $durationStr")
} catch (e: Exception) {
Log.e(TAG, "Get duration failed", e)
media.setDuration(30000)
it.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))
Logd(TAG, "persisting episode downloaded ${item.title} ${item.media?.fileUrl} ${item.media?.downloaded} ${item.isNew}")
// setFeedItem() signals that the item has been updated,
// so we do it after the enclosing media has been updated above,
// to ensure subscribers will get the updated EpisodeMedia as well
// Episodes.persistEpisode(item)
// TODO: should use different event?
if (broadcastUnreadStateUpdate) EventFlow.postEvent(FlowEvent.EpisodePlayedEvent(item))
}
} catch (e: InterruptedException) {
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) {
val action = EpisodeAction.Builder(item, EpisodeAction.DOWNLOAD)
.currentTimestamp()
.build()
if (needSynch()) {
Logd(TAG, "enqueue synch")
val action = EpisodeAction.Builder(item, EpisodeAction.DOWNLOAD).currentTimestamp().build()
SynchronizationQueueSink.enqueueEpisodeActionIfSyncActive(context, action)
}
}
companion object {
private val TAG: String = MediaDownloadedHandler::class.simpleName ?: "Anonymous"
Logd(TAG, "media.episode.isNew: ${item.isNew} ${item.playState}")
}
}
companion object {
private val TAG: String = EpisodeDownloadWorker::class.simpleName ?: "Anonymous"
private val notificationProgress: MutableMap<String, Int> = HashMap()
}
}
}

View File

@ -143,15 +143,13 @@ class HttpDownloader(request: DownloadRequest) : Downloader(request) {
} catch (e: IOException) {
Log.e(TAG, Log.getStackTraceString(e))
}
if (cancelled) {
onCancelled()
} else {
if (cancelled) onCancelled()
else {
// 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
when {
!isGzip && downloadRequest.size != DownloadResult.SIZE_UNKNOWN.toLong() && downloadRequest.soFar != downloadRequest.size -> {
onFail(DownloadError.ERROR_IO_WRONG_SIZE,
"Download completed but size: ${downloadRequest.soFar} does not equal expected size ${downloadRequest.size}")
onFail(DownloadError.ERROR_IO_WRONG_SIZE, "Download completed but size: ${downloadRequest.soFar} does not equal expected size ${downloadRequest.size}")
return
}
downloadRequest.size > 0 && downloadRequest.soFar == 0L -> {
@ -208,9 +206,7 @@ class HttpDownloader(request: DownloadRequest) : Downloader(request) {
Log.e(TAG, e.toString())
if (e.message != null && e.message!!.contains("PROTOCOL_ERROR")) {
// Apparently some servers announce they support SPDY but then actually don't.
httpClient = httpClient.newBuilder()
.protocols(listOf(Protocol.HTTP_1_1))
.build()
httpClient = httpClient.newBuilder().protocols(listOf(Protocol.HTTP_1_1)).build()
return httpClient.newCall(httpReq.build()).execute()
} else {
throw e

View File

@ -289,8 +289,6 @@ open class SyncService(context: Context, params: WorkerParameters) : Worker(cont
updatedItems.add(result.second)
}
removeFromQueue(*updatedItems.toTypedArray())
// loadAdditionalFeedItemListData(updatedItems)
// persistEpisodes(updatedItems)
runOnIOScope {
for (episode in updatedItems) {
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.getEpisodes
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.EpisodeFilter
import ac.mdiq.podcini.storage.utils.EpisodeUtil.hasAlmostEnded
@ -315,7 +317,7 @@ import kotlin.math.min
override fun processEpisodeAction(action: EpisodeAction): Pair<Long, Episode>? {
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) {
Logd(TAG, "Unknown feed item: $action")
return null
@ -328,23 +330,23 @@ import kotlin.math.min
var idRemove: Long? = null
Logd(TAG, "processEpisodeAction ${feedItem.media!!.getLastPlayedTime()} ${(action.timestamp?.time?:0L)} ${action.position} ${feedItem.title}")
if (feedItem.media!!.getLastPlayedTime() < (action.timestamp?.time?:0L)) {
feedItem.media!!.startPosition = action.started * 1000
feedItem.media!!.setPosition(action.position * 1000)
feedItem.media!!.playedDuration = action.playedDuration * 1000
feedItem.media!!.setLastPlayedTime(action.timestamp!!.time)
feedItem.isFavorite = action.isFavorite
feedItem.playState = action.playState
if (hasAlmostEnded(feedItem.media!!)) {
feedItem = upsertBlk(feedItem) {
it.media!!.startPosition = action.started * 1000
it.media!!.setPosition(action.position * 1000)
it.media!!.playedDuration = action.playedDuration * 1000
it.media!!.setLastPlayedTime(action.timestamp!!.time)
it.isFavorite = action.isFavorite
it.playState = action.playState
if (hasAlmostEnded(it.media!!)) {
Logd(TAG, "Marking as played")
feedItem.setPlayed(true)
feedItem.media!!.setPosition(0)
idRemove = feedItem.id
it.setPlayed(true)
it.media!!.setPosition(0)
idRemove = it.id
} else Logd(TAG, "Setting position")
// persistFeedMediaPlaybackInfo(feedItem.media)
persistEpisode(feedItem)
}
EventFlow.postEvent(FlowEvent.EpisodeEvent.updated(feedItem))
} 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() {

View File

@ -48,12 +48,8 @@ object InTheatre {
CoroutineScope(Dispatchers.IO).launch {
Logd(TAG, "starting curQueue")
var curQueue_ = realm.query(PlayQueue::class).sort("updated", Sort.DESCENDING).first().find()
if (curQueue_ != null) {
curQueue = curQueue_
// 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")
if (curQueue_ != null) curQueue = curQueue_
else {
for (i in 0..4) {
curQueue_ = PlayQueue()
if (i == 0) {
@ -72,7 +68,6 @@ object InTheatre {
Logd(TAG, "starting curState")
var curState_ = realm.query(CurrentState::class).first().find()
// if (curState_ != null) curState = unmanaged(curState_)
if (curState_ != null) curState = curState_
else {
Logd(TAG, "creating new curState")

View File

@ -262,7 +262,7 @@ abstract class MediaPlayerBase protected constructor(protected val context: Cont
*/
@Synchronized
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
status = newStatus
if (newMedia != null) setPlayable(newMedia)

View File

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

View File

@ -136,9 +136,8 @@ class PlaybackService : MediaSessionService() {
val status = intent.getStringExtra("media_connection_status")
val isConnectedToCar = "media_connected" == status
Logd(TAG, "Received Auto Connection update: $status")
if (!isConnectedToCar) {
Logd(TAG, "Car was unplugged during playback.")
} else {
if (!isConnectedToCar) Logd(TAG, "Car was unplugged during playback.")
else {
val playerStatus = MediaPlayerBase.status
when (playerStatus) {
PlayerStatus.PAUSED, PlayerStatus.PREPARED -> mPlayer?.resume()
@ -229,8 +228,7 @@ class PlaybackService : MediaSessionService() {
Log.d(TAG, "statusChanged called ${newInfo?.playerStatus}")
if (newInfo != null) {
when (newInfo.playerStatus) {
PlayerStatus.INITIALIZED ->
if (mPlayer != null) writeMediaPlaying(mPlayer!!.playerInfo.playable, mPlayer!!.playerInfo.playerStatus)
PlayerStatus.INITIALIZED -> if (mPlayer != null) writeMediaPlaying(mPlayer!!.playerInfo.playable, mPlayer!!.playerInfo.playerStatus)
PlayerStatus.PREPARED -> {
if (mPlayer != null) writeMediaPlaying(mPlayer!!.playerInfo.playable, mPlayer!!.playerInfo.playerStatus)
if (newInfo.playable != null) taskManager.startChapterLoader(newInfo.playable!!)
@ -382,8 +380,7 @@ class PlaybackService : MediaSessionService() {
val i = EpisodeUtil.indexOfItemWithId(eList, item.id)
Logd(TAG, "getNextInQueue current i: $i curIndexInQueue: $curIndexInQueue")
if (i < 0) {
if (curIndexInQueue >= 0 && curIndexInQueue < eList.size) j = curIndexInQueue
else j = eList.size-1
j = if (curIndexInQueue >= 0 && curIndexInQueue < eList.size) curIndexInQueue else eList.size-1
} else if (i < eList.size-1) j = i+1
Logd(TAG, "getNextInQueue next j: $j")
@ -411,17 +408,15 @@ class PlaybackService : MediaSessionService() {
EventFlow.postEvent(FlowEvent.PlayEvent(nextItem))
return if (nextItem.media == null) null else unmanaged(nextItem.media!!)
}
// only used in test
override fun findMedia(url: String): Playable? {
val item = getEpisodeByGuidOrUrl(null, url)
return item?.media
}
override fun onPlaybackEnded(mediaType: MediaType?, stopPlaying: Boolean) {
Logd(TAG, "onPlaybackEnded mediaType: $mediaType stopPlaying: $stopPlaying")
clearCurTempSpeed()
if (stopPlaying) taskManager.cancelPositionSaver()
if (mediaType == null) sendNotificationBroadcast(NOTIFICATION_TYPE_PLAYBACK_END, 0)
else sendNotificationBroadcast(NOTIFICATION_TYPE_RELOAD,
when {
@ -694,9 +689,15 @@ class PlaybackService : MediaSessionService() {
val keycode = intent?.getIntExtra(MediaButtonReceiver.EXTRA_KEYCODE, -1) ?: -1
val customAction = intent?.getStringExtra(MediaButtonReceiver.EXTRA_CUSTOM_ACTION)
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
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) {
Log.e(TAG, "onStartCommand PlaybackService was started with no arguments, return")
return START_NOT_STICKY
@ -797,7 +798,7 @@ class PlaybackService : MediaSessionService() {
* Handles media button events. return: keycode was handled
*/
private fun handleKeycode(keycode: Int, notificationButton: Boolean): Boolean {
Logd(TAG, "Handling keycode: $keycode")
Log.d(TAG, "Handling keycode: $keycode")
val info = mPlayer?.playerInfo
val status = info?.playerStatus
when (keycode) {
@ -1064,12 +1065,6 @@ class PlaybackService : MediaSessionService() {
playable.setLastPlayedTime(System.currentTimeMillis())
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()
if (item != null) {
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.ui.activity.OpmlImportActivity
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 android.app.Activity.RESULT_OK
import android.app.ProgressDialog
@ -934,29 +932,31 @@ class ImportExportPreferencesFragment : PreferenceFragmentCompat() {
val action = readFromJsonObject(jsonAction) ?: continue
Logd(TAG, "processing action: $action")
val result = processEpisodeAction(action) ?: continue
upsertBlk(result.second) {}
// upsertBlk(result.second) {}
}
}
private fun processEpisodeAction(action: EpisodeAction): Pair<Long, Episode>? {
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) {
Logd(TAG, "Feed item has no media: $action")
return null
}
var idRemove = 0L
feedItem.media!!.startPosition = action.started * 1000
feedItem.media!!.setPosition(action.position * 1000)
feedItem.media!!.playedDuration = action.playedDuration * 1000
feedItem.media!!.setLastPlayedTime(action.timestamp!!.time)
feedItem.isFavorite = action.isFavorite
feedItem.playState = action.playState
if (hasAlmostEnded(feedItem.media!!)) {
feedItem = upsertBlk(feedItem) {
it.media!!.startPosition = action.started * 1000
it.media!!.setPosition(action.position * 1000)
it.media!!.playedDuration = action.playedDuration * 1000
it.media!!.setLastPlayedTime(action.timestamp!!.time)
it.isFavorite = action.isFavorite
it.playState = action.playState
if (hasAlmostEnded(it.media!!)) {
Logd(TAG, "Marking as played: $action")
feedItem.setPlayed(true)
feedItem.media!!.setPosition(0)
idRemove = feedItem.id
it.setPlayed(true)
it.media!!.setPosition(0)
idRemove = it.id
} else Logd(TAG, "Setting position: $action")
}
return Pair(idRemove, feedItem)
}
}

View File

@ -80,16 +80,18 @@ object Episodes {
* @return The FeedItem or null if the FeedItem could not be found.
* 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")
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()
if (!copy) return episode
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")
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
}
@ -217,6 +219,7 @@ object Episodes {
// }
// }
// only used in tests
fun persistEpisodeMedia(media: EpisodeMedia) : Job {
Logd(TAG, "persistEpisodeMedia called")
return runOnIOScope {

View File

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

View File

@ -1,11 +1,11 @@
package ac.mdiq.podcini.ui.dialog
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.RealmDB.realm
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.Feed
import ac.mdiq.podcini.util.error.DownloadErrorLabel.from
import ac.mdiq.podcini.util.event.EventFlow
import ac.mdiq.podcini.util.event.FlowEvent
@ -23,7 +23,7 @@ class DownloadLogDetailsDialog(context: Context, status: DownloadResult) : Mater
var url = "unknown"
when (status.feedfileType) {
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?:""
}
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.net.download.DownloadError
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.RealmDB.realm
import ac.mdiq.podcini.storage.database.RealmDB.runOnIOScope
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.Feed
import ac.mdiq.podcini.storage.utils.DownloadResultComparator
import ac.mdiq.podcini.ui.actions.actionbutton.DownloadActionButton
import ac.mdiq.podcini.ui.activity.MainActivity
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.event.EventFlow
import ac.mdiq.podcini.util.event.FlowEvent
import ac.mdiq.podcini.storage.utils.DownloadResultComparator
import android.app.Activity
import android.content.Context
import android.os.Bundle
@ -237,17 +237,12 @@ class DownloadLogFragment : BottomSheetDialogFragment(), OnItemClickListener, To
})
}
EpisodeMedia.FEEDFILETYPE_FEEDMEDIA -> {
holder.secondaryActionButton.setOnClickListener(View.OnClickListener {
holder.secondaryActionButton.setOnClickListener {
holder.secondaryActionButton.visibility = View.INVISIBLE
val media: EpisodeMedia? = getEpisodeMedia(status.feedfileId)
if (media == null) {
Log.e(TAG, "Could not find feed media for feed id: " + status.feedfileId)
return@OnClickListener
}
val item_ = media.episodeOrFetch()
val item_ = realm.query(Episode::class).query("id == $0", status.feedfileId).first().find()
if (item_ != null) DownloadActionButton(item_).onClick(context)
(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 ->
adapter.selectedItems.let {
EpisodeMultiSelectHandler((activity as MainActivity), actionItem.id).handleAction(it.filterIsInstance<Episode>())
EpisodeMultiSelectHandler((activity as MainActivity), actionItem.id).handleAction(it)
}
adapter.endSelectMode()
true

View File

@ -4,7 +4,7 @@ buildscript {
mavenCentral()
gradlePluginPortal()
}
ext.kotlin_version = '2.0.0'
ext.kotlin_version = '2.0.10'
dependencies {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
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
* 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