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