From 219a7d7cfe8b88e85af7f588a1d8148dbf8d410d Mon Sep 17 00:00:00 2001 From: Isira Seneviratne Date: Wed, 18 Jan 2023 07:01:06 +0530 Subject: [PATCH] Use Kotlinx Serialization with stream data. --- .../github/libretube/api/RetrofitInstance.kt | 25 +++++++--- .../libretube/api/obj/ChapterSegment.kt | 4 +- .../github/libretube/api/obj/PipedStream.kt | 4 +- .../github/libretube/api/obj/PreviewFrames.kt | 4 +- .../github/libretube/api/obj/StreamItem.kt | 4 +- .../com/github/libretube/api/obj/Streams.kt | 45 +++++++++--------- .../com/github/libretube/api/obj/Subtitle.kt | 4 +- .../com/github/libretube/db/DatabaseHelper.kt | 4 +- .../libretube/extensions/ToStreamItem.kt | 2 +- .../libretube/services/DownloadService.kt | 20 ++++---- .../libretube/ui/dialogs/DownloadDialog.kt | 8 ++-- .../libretube/ui/fragments/PlayerFragment.kt | 46 ++++++++----------- .../com/github/libretube/util/TextUtils.kt | 14 ++---- 13 files changed, 91 insertions(+), 93 deletions(-) diff --git a/app/src/main/java/com/github/libretube/api/RetrofitInstance.kt b/app/src/main/java/com/github/libretube/api/RetrofitInstance.kt index 03f69cee7..2d95c2b52 100644 --- a/app/src/main/java/com/github/libretube/api/RetrofitInstance.kt +++ b/app/src/main/java/com/github/libretube/api/RetrofitInstance.kt @@ -3,40 +3,51 @@ package com.github.libretube.api import com.github.libretube.constants.PIPED_API_URL import com.github.libretube.constants.PreferenceKeys import com.github.libretube.util.PreferenceHelper +import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory +import kotlinx.serialization.json.Json +import okhttp3.MediaType.Companion.toMediaType import retrofit2.Retrofit import retrofit2.converter.jackson.JacksonConverterFactory +import retrofit2.create object RetrofitInstance { lateinit var url: String lateinit var authUrl: String val lazyMgr = resettableManager() - val jacksonConverterFactory = JacksonConverterFactory.create() + private val jacksonConverterFactory = JacksonConverterFactory.create() + private val json = Json { + ignoreUnknownKeys = true + } + private val kotlinxConverterFactory = json.asConverterFactory("application/json".toMediaType()) - val api: PipedApi by resettableLazy(lazyMgr) { + val api by resettableLazy(lazyMgr) { Retrofit.Builder() .baseUrl(url) .callFactory(CronetHelper.callFactory) + .addConverterFactory(kotlinxConverterFactory) .addConverterFactory(jacksonConverterFactory) .build() - .create(PipedApi::class.java) + .create() } - val authApi: PipedApi by resettableLazy(lazyMgr) { + val authApi by resettableLazy(lazyMgr) { Retrofit.Builder() .baseUrl(authUrl) .callFactory(CronetHelper.callFactory) + .addConverterFactory(kotlinxConverterFactory) .addConverterFactory(jacksonConverterFactory) .build() - .create(PipedApi::class.java) + .create() } - val externalApi: ExternalApi by resettableLazy(lazyMgr) { + val externalApi by resettableLazy(lazyMgr) { Retrofit.Builder() .baseUrl(url) .callFactory(CronetHelper.callFactory) + .addConverterFactory(kotlinxConverterFactory) .addConverterFactory(jacksonConverterFactory) .build() - .create(ExternalApi::class.java) + .create() } /** diff --git a/app/src/main/java/com/github/libretube/api/obj/ChapterSegment.kt b/app/src/main/java/com/github/libretube/api/obj/ChapterSegment.kt index 6fbdfed00..a5fb281dd 100644 --- a/app/src/main/java/com/github/libretube/api/obj/ChapterSegment.kt +++ b/app/src/main/java/com/github/libretube/api/obj/ChapterSegment.kt @@ -1,8 +1,8 @@ package com.github.libretube.api.obj -import com.fasterxml.jackson.annotation.JsonIgnoreProperties +import kotlinx.serialization.Serializable -@JsonIgnoreProperties(ignoreUnknown = true) +@Serializable data class ChapterSegment( var title: String? = null, var image: String? = null, diff --git a/app/src/main/java/com/github/libretube/api/obj/PipedStream.kt b/app/src/main/java/com/github/libretube/api/obj/PipedStream.kt index 58de468e8..bb071e5c6 100644 --- a/app/src/main/java/com/github/libretube/api/obj/PipedStream.kt +++ b/app/src/main/java/com/github/libretube/api/obj/PipedStream.kt @@ -1,8 +1,8 @@ package com.github.libretube.api.obj -import com.fasterxml.jackson.annotation.JsonIgnoreProperties +import kotlinx.serialization.Serializable -@JsonIgnoreProperties(ignoreUnknown = true) +@Serializable data class PipedStream( var url: String? = null, var format: String? = null, diff --git a/app/src/main/java/com/github/libretube/api/obj/PreviewFrames.kt b/app/src/main/java/com/github/libretube/api/obj/PreviewFrames.kt index db7a051d4..48966998e 100644 --- a/app/src/main/java/com/github/libretube/api/obj/PreviewFrames.kt +++ b/app/src/main/java/com/github/libretube/api/obj/PreviewFrames.kt @@ -1,8 +1,8 @@ package com.github.libretube.api.obj -import com.fasterxml.jackson.annotation.JsonIgnoreProperties +import kotlinx.serialization.Serializable -@JsonIgnoreProperties(ignoreUnknown = true) +@Serializable data class PreviewFrames( val urls: List? = null, val frameWidth: Int? = null, diff --git a/app/src/main/java/com/github/libretube/api/obj/StreamItem.kt b/app/src/main/java/com/github/libretube/api/obj/StreamItem.kt index 9363fbabd..db4b18c17 100644 --- a/app/src/main/java/com/github/libretube/api/obj/StreamItem.kt +++ b/app/src/main/java/com/github/libretube/api/obj/StreamItem.kt @@ -1,8 +1,8 @@ package com.github.libretube.api.obj -import com.fasterxml.jackson.annotation.JsonIgnoreProperties +import kotlinx.serialization.Serializable -@JsonIgnoreProperties(ignoreUnknown = true) +@Serializable data class StreamItem( var url: String? = null, val type: String? = null, diff --git a/app/src/main/java/com/github/libretube/api/obj/Streams.kt b/app/src/main/java/com/github/libretube/api/obj/Streams.kt index f4c561b22..6f4f8ce85 100644 --- a/app/src/main/java/com/github/libretube/api/obj/Streams.kt +++ b/app/src/main/java/com/github/libretube/api/obj/Streams.kt @@ -1,31 +1,32 @@ package com.github.libretube.api.obj -import com.fasterxml.jackson.annotation.JsonIgnoreProperties +import kotlinx.datetime.LocalDate +import kotlinx.serialization.Serializable -@JsonIgnoreProperties(ignoreUnknown = true) +@Serializable data class Streams( - val title: String? = null, - val description: String? = null, - val uploadDate: String? = null, - val uploader: String? = null, - val uploaderUrl: String? = null, - val uploaderAvatar: String? = null, - val thumbnailUrl: String? = null, + val title: String, + val description: String, + val uploadDate: LocalDate, + val uploader: String, + val uploaderUrl: String, + val uploaderAvatar: String, + val thumbnailUrl: String, val hls: String? = null, val dash: String? = null, val lbryId: String? = null, - val uploaderVerified: Boolean? = null, - val duration: Long? = null, - val views: Long? = null, - val likes: Long? = null, - val dislikes: Long? = null, - val audioStreams: List? = null, - val videoStreams: List? = null, - val relatedStreams: List? = null, - val subtitles: List? = null, - val livestream: Boolean? = null, + val uploaderVerified: Boolean, + val duration: Long, + val views: Long = 0, + val likes: Long = 0, + val dislikes: Long = 0, + val audioStreams: List = emptyList(), + val videoStreams: List = emptyList(), + val relatedStreams: List = emptyList(), + val subtitles: List = emptyList(), + val livestream: Boolean = false, val proxyUrl: String? = null, - val chapters: List? = null, - val uploaderSubscriberCount: Long? = null, - val previewFrames: List? = null + val chapters: List = emptyList(), + val uploaderSubscriberCount: Long = 0, + val previewFrames: List = emptyList() ) diff --git a/app/src/main/java/com/github/libretube/api/obj/Subtitle.kt b/app/src/main/java/com/github/libretube/api/obj/Subtitle.kt index 74454e802..91b6760cd 100644 --- a/app/src/main/java/com/github/libretube/api/obj/Subtitle.kt +++ b/app/src/main/java/com/github/libretube/api/obj/Subtitle.kt @@ -1,8 +1,8 @@ package com.github.libretube.api.obj -import com.fasterxml.jackson.annotation.JsonIgnoreProperties +import kotlinx.serialization.Serializable -@JsonIgnoreProperties(ignoreUnknown = true) +@Serializable data class Subtitle( val url: String? = null, val mimeType: String? = null, diff --git a/app/src/main/java/com/github/libretube/db/DatabaseHelper.kt b/app/src/main/java/com/github/libretube/db/DatabaseHelper.kt index c6fdf259f..cbaf5e6f4 100644 --- a/app/src/main/java/com/github/libretube/db/DatabaseHelper.kt +++ b/app/src/main/java/com/github/libretube/db/DatabaseHelper.kt @@ -16,9 +16,9 @@ object DatabaseHelper { val watchHistoryItem = WatchHistoryItem( videoId, streams.title, - streams.uploadDate, + streams.uploadDate.toString(), streams.uploader, - streams.uploaderUrl!!.toID(), + streams.uploaderUrl.toID(), streams.uploaderAvatar, streams.thumbnailUrl, streams.duration diff --git a/app/src/main/java/com/github/libretube/extensions/ToStreamItem.kt b/app/src/main/java/com/github/libretube/extensions/ToStreamItem.kt index 2be7bbde4..7f1e718b7 100644 --- a/app/src/main/java/com/github/libretube/extensions/ToStreamItem.kt +++ b/app/src/main/java/com/github/libretube/extensions/ToStreamItem.kt @@ -13,7 +13,7 @@ fun Streams.toStreamItem(videoId: String): StreamItem { uploaderName = uploader, uploaderUrl = uploaderUrl, uploaderAvatar = uploaderAvatar, - uploadedDate = uploadDate, + uploadedDate = uploadDate.toString(), uploaded = null, duration = duration, views = views, diff --git a/app/src/main/java/com/github/libretube/services/DownloadService.kt b/app/src/main/java/com/github/libretube/services/DownloadService.kt index b35fae1a5..64a75c03a 100644 --- a/app/src/main/java/com/github/libretube/services/DownloadService.kt +++ b/app/src/main/java/com/github/libretube/services/DownloadService.kt @@ -102,21 +102,19 @@ class DownloadService : Service() { Database.downloadDao().insertDownload( Download( videoId = videoId, - title = streams.title ?: "", + title = streams.title, thumbnailPath = thumbnailTargetFile.absolutePath, - description = streams.description ?: "", - uploadDate = streams.uploadDate, - uploader = streams.uploader ?: "" + description = streams.description, + uploadDate = streams.uploadDate.toString(), + uploader = streams.uploader ) ) } - streams.thumbnailUrl?.let { url -> - ImageHelper.downloadImage( - this@DownloadService, - url, - thumbnailTargetFile.absolutePath - ) - } + ImageHelper.downloadImage( + this@DownloadService, + streams.thumbnailUrl, + thumbnailTargetFile.absolutePath + ) val downloadItems = streams.toDownloadItems( videoId, diff --git a/app/src/main/java/com/github/libretube/ui/dialogs/DownloadDialog.kt b/app/src/main/java/com/github/libretube/ui/dialogs/DownloadDialog.kt index 0a0ac45cf..e165e1f47 100644 --- a/app/src/main/java/com/github/libretube/ui/dialogs/DownloadDialog.kt +++ b/app/src/main/java/com/github/libretube/ui/dialogs/DownloadDialog.kt @@ -74,7 +74,7 @@ class DownloadDialog( } private fun initDownloadOptions(streams: Streams) { - binding.fileName.setText(streams.title.toString()) + binding.fileName.setText(streams.title) val vidName = arrayListOf() @@ -82,7 +82,7 @@ class DownloadDialog( vidName.add(getString(R.string.no_video)) // add all available video streams - for (vid in streams.videoStreams!!) { + for (vid in streams.videoStreams) { if (vid.url != null) { val name = vid.quality + " " + vid.format vidName.add(name) @@ -95,7 +95,7 @@ class DownloadDialog( audioName.add(getString(R.string.no_audio)) // add all available audio streams - for (audio in streams.audioStreams!!) { + for (audio in streams.audioStreams) { if (audio.url != null) { val name = audio.quality + " " + audio.format audioName.add(name) @@ -108,7 +108,7 @@ class DownloadDialog( subtitleName.add(getString(R.string.no_subtitle)) // add all available subtitles - for (subtitle in streams.subtitles!!) { + for (subtitle in streams.subtitles) { if (subtitle.url != null) { subtitleName.add(subtitle.name.toString()) } diff --git a/app/src/main/java/com/github/libretube/ui/fragments/PlayerFragment.kt b/app/src/main/java/com/github/libretube/ui/fragments/PlayerFragment.kt index d18cb97ab..4f468efb2 100644 --- a/app/src/main/java/com/github/libretube/ui/fragments/PlayerFragment.kt +++ b/app/src/main/java/com/github/libretube/ui/fragments/PlayerFragment.kt @@ -116,6 +116,7 @@ import kotlin.math.abs import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch +import kotlinx.datetime.LocalDate import org.chromium.net.CronetEngine import retrofit2.HttpException @@ -682,9 +683,7 @@ class PlayerFragment : BaseFragment(), OnlinePlayerOptions { } else { PlayingQueue.updateCurrent(streams.toStreamItem(videoId!!)) if (PlayerHelper.autoInsertRelatedVideos) { - PlayingQueue.add( - *streams.relatedStreams.orEmpty().toTypedArray() - ) + PlayingQueue.add(*streams.relatedStreams.toTypedArray()) } } } @@ -773,8 +772,7 @@ class PlayerFragment : BaseFragment(), OnlinePlayerOptions { playerBinding.liveDiff.text = "-$diffText" } // call the function again after 100ms - handler - .postDelayed(this@PlayerFragment::refreshLiveStatus, 100) + handler.postDelayed(this@PlayerFragment::refreshLiveStatus, 100) } // seek to saved watch position if available @@ -794,7 +792,7 @@ class PlayerFragment : BaseFragment(), OnlinePlayerOptions { return } // position is almost the end of the video => don't seek, start from beginning - if (position != null && position < streams.duration!! * 1000 * 0.9) { + if (position != null && position < streams.duration * 1000 * 0.9) { exoPlayer.seekTo(position) } } @@ -828,7 +826,7 @@ class PlayerFragment : BaseFragment(), OnlinePlayerOptions { playerBinding.exoProgress.setPlayer(exoPlayer) } - private fun localizedDate(date: String?): String? { + private fun localizedDate(date: LocalDate): String { val locale = ConfigurationCompat.getLocales(resources.configuration)[0]!! return TextUtils.localizeDate(date, locale) } @@ -870,12 +868,12 @@ class PlayerFragment : BaseFragment(), OnlinePlayerOptions { playerChannelSubCount.text = context?.getString( R.string.subscribers, - streams.uploaderSubscriberCount?.formatShort() + streams.uploaderSubscriberCount.formatShort() ) } // duration that's not greater than 0 indicates that the video is live - if (streams.duration!! <= 0) { + if (streams.duration <= 0) { isLive = true handleLiveVideo() } @@ -883,10 +881,8 @@ class PlayerFragment : BaseFragment(), OnlinePlayerOptions { playerBinding.exoTitle.text = streams.title // init the chapters recyclerview - if (streams.chapters != null) { - chapters = streams.chapters.orEmpty() - initializeChapters() - } + chapters = streams.chapters + initializeChapters() // Listener for play and pause icon change exoPlayer.addListener(object : Player.Listener { @@ -972,7 +968,7 @@ class PlayerFragment : BaseFragment(), OnlinePlayerOptions { }) binding.relPlayerDownload.setOnClickListener { - if (streams.duration!! <= 0) { + if (streams.duration <= 0) { Toast.makeText(context, R.string.cannotDownload, Toast.LENGTH_SHORT).show() } else if (!DownloadService.IS_DOWNLOAD_RUNNING) { val newFragment = DownloadDialog(videoId!!) @@ -995,7 +991,7 @@ class PlayerFragment : BaseFragment(), OnlinePlayerOptions { } initializeRelatedVideos(streams.relatedStreams) // set video description - val description = streams.description!! + val description = streams.description // detect whether the description is html formatted if (description.contains("<") && description.contains(">")) { @@ -1016,7 +1012,7 @@ class PlayerFragment : BaseFragment(), OnlinePlayerOptions { // update the subscribed state binding.playerSubscribe.setupSubscriptionButton( - this.streams.uploaderUrl?.toID(), + this.streams.uploaderUrl.toID(), this.streams.uploader ) @@ -1228,9 +1224,9 @@ class PlayerFragment : BaseFragment(), OnlinePlayerOptions { private fun setResolutionAndSubtitles() { // create a list of subtitles subtitles = mutableListOf() - val subtitlesNamesList = mutableListOf(context?.getString(R.string.none)!!) + val subtitlesNamesList = mutableListOf(getString(R.string.none)) val subtitleCodesList = mutableListOf("") - streams.subtitles.orEmpty().forEach { + streams.subtitles.forEach { subtitles.add( SubtitleConfiguration.Builder(it.url!!.toUri()) .setMimeType(it.mimeType!!) // The correct MIME type (required). @@ -1260,7 +1256,7 @@ class PlayerFragment : BaseFragment(), OnlinePlayerOptions { if (defaultResolution != "") setPlayerResolution(defaultResolution.toInt()) if (!PreferenceHelper.getBoolean(PreferenceKeys.USE_HLS_OVER_DASH, false) && - streams.videoStreams.orEmpty().isNotEmpty() + streams.videoStreams.isNotEmpty() ) { val uri = let { streams.dash?.toUri() @@ -1355,16 +1351,14 @@ class PlayerFragment : BaseFragment(), OnlinePlayerOptions { } override fun onCaptionsClicked() { - if (!this@PlayerFragment::streams.isInitialized || - streams.subtitles.isNullOrEmpty() - ) { + if (!this@PlayerFragment::streams.isInitialized || streams.subtitles.isEmpty()) { Toast.makeText(context, R.string.no_subtitles_available, Toast.LENGTH_SHORT).show() return } - val subtitlesNamesList = mutableListOf(context?.getString(R.string.none)!!) + val subtitlesNamesList = mutableListOf(getString(R.string.none)) val subtitleCodesList = mutableListOf("") - streams.subtitles!!.forEach { + streams.subtitles.forEach { subtitlesNamesList += it.name!! subtitleCodesList += it.code!! } @@ -1493,9 +1487,9 @@ class PlayerFragment : BaseFragment(), OnlinePlayerOptions { playerBinding.seekbarPreview.visibility = View.GONE playerBinding.exoProgress.addListener( SeekbarPreviewListener( - streams.previewFrames.orEmpty(), + streams.previewFrames, playerBinding.seekbarPreview, - streams.duration!! * 1000 + streams.duration * 1000 ) ) } diff --git a/app/src/main/java/com/github/libretube/util/TextUtils.kt b/app/src/main/java/com/github/libretube/util/TextUtils.kt index 6d100523d..014751d7f 100644 --- a/app/src/main/java/com/github/libretube/util/TextUtils.kt +++ b/app/src/main/java/com/github/libretube/util/TextUtils.kt @@ -1,10 +1,11 @@ package com.github.libretube.util import java.net.URL -import java.time.LocalDate import java.time.format.DateTimeFormatter import java.time.format.FormatStyle import java.util.* +import kotlinx.datetime.LocalDate +import kotlinx.datetime.toJavaLocalDate object TextUtils { /** @@ -41,15 +42,8 @@ object TextUtils { * @param locale The locale to use, otherwise uses system default * return Localized date string */ - fun localizeDate(date: String?, locale: Locale): String? { - date ?: return null - - // relative time span - if (!date.contains("-")) return date - - val dateObj = LocalDate.parse(date) - + fun localizeDate(date: LocalDate, locale: Locale): String { val formatter = DateTimeFormatter.ofLocalizedDate(FormatStyle.MEDIUM).withLocale(locale) - return dateObj.format(formatter) + return date.toJavaLocalDate().format(formatter) } }