6.3.7 commit
This commit is contained in:
parent
4ccf66d5a1
commit
7a49a11f54
|
@ -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
|
||||||
|
|
|
@ -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',
|
||||||
|
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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) {
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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
|
||||||
|
|
|
@ -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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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) {}
|
||||||
|
|
|
@ -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() {
|
||||||
|
|
|
@ -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")
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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++
|
||||||
|
|
|
@ -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 -> {
|
||||||
|
|
|
@ -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)
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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'
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
Loading…
Reference in New Issue