Improve offline playback (#68)
* Fix download playback tracking bug * Remove unused permission * Add overview text to downloadmetadata * Add visual indicator of whether item is downloaded * Use downloaded item when available * Fix "null" overview text in download metadata * Fix crash when playing downloaded file with mpv * Clean up Co-authored-by: jarnedemeulemeester <jarnedemeulemeester@gmail.com>
This commit is contained in:
parent
8c5d0bebf0
commit
598c11f299
14 changed files with 141 additions and 92 deletions
27
app/src/debug/res/drawable/ic_download_filled.xml
Normal file
27
app/src/debug/res/drawable/ic_download_filled.xml
Normal file
|
@ -0,0 +1,27 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:pathData="M21,15v4a2,2 0,0 1,-2 2H5a2,2 0,0 1,-2 -2v-4"
|
||||
android:strokeLineJoin="round"
|
||||
android:strokeWidth="2"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="@color/red"
|
||||
android:strokeLineCap="round"/>
|
||||
<path
|
||||
android:pathData="M7,10l5,5l5,-5"
|
||||
android:strokeLineJoin="round"
|
||||
android:strokeWidth="2"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="@color/red"
|
||||
android:strokeLineCap="round"/>
|
||||
<path
|
||||
android:pathData="M12,15L12,3"
|
||||
android:strokeLineJoin="round"
|
||||
android:strokeWidth="2"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="@color/red"
|
||||
android:strokeLineCap="round"/>
|
||||
</vector>
|
|
@ -3,8 +3,6 @@
|
|||
package="dev.jdtech.jellyfin">
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
|
||||
android:maxSdkVersion="28" />
|
||||
|
||||
<uses-feature android:name="android.software.leanback" android:required="false" />
|
||||
<uses-feature android:name="android.hardware.touchscreen" android:required="false" />
|
||||
|
|
|
@ -13,6 +13,7 @@ import com.google.android.material.bottomnavigation.BottomNavigationView
|
|||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import dev.jdtech.jellyfin.databinding.ActivityMainAppBinding
|
||||
import dev.jdtech.jellyfin.fragments.HomeFragmentDirections
|
||||
import dev.jdtech.jellyfin.utils.loadDownloadLocation
|
||||
import dev.jdtech.jellyfin.viewmodels.MainViewModel
|
||||
|
||||
@AndroidEntryPoint
|
||||
|
@ -57,6 +58,8 @@ class MainActivity : AppCompatActivity() {
|
|||
if (destination.id == R.id.about_libraries_dest) binding.mainToolbar.title = getString(R.string.app_info)
|
||||
}
|
||||
|
||||
loadDownloadLocation(applicationContext)
|
||||
|
||||
viewModel.navigateToAddServer.observe(this, {
|
||||
if (it) {
|
||||
navController.navigate(HomeFragmentDirections.actionHomeFragmentToAddServerFragment())
|
||||
|
|
|
@ -94,6 +94,15 @@ class EpisodeBottomSheetFragment : BottomSheetDialogFragment() {
|
|||
binding.favoriteButton.setImageResource(drawable)
|
||||
})
|
||||
|
||||
viewModel.downloaded.observe(viewLifecycleOwner, {
|
||||
val drawable = when (it) {
|
||||
true -> R.drawable.ic_download_filled
|
||||
false -> R.drawable.ic_download
|
||||
}
|
||||
|
||||
binding.downloadButton.setImageResource(drawable)
|
||||
})
|
||||
|
||||
viewModel.downloadEpisode.observe(viewLifecycleOwner, {
|
||||
if (it) {
|
||||
requestDownload(Uri.parse(viewModel.downloadRequestItem.uri), viewModel.downloadRequestItem, this)
|
||||
|
|
|
@ -129,6 +129,15 @@ class MediaInfoFragment : Fragment() {
|
|||
binding.favoriteButton.setImageResource(drawable)
|
||||
})
|
||||
|
||||
viewModel.downloaded.observe(viewLifecycleOwner, {
|
||||
val drawable = when (it) {
|
||||
true -> R.drawable.ic_download_filled
|
||||
false -> R.drawable.ic_download
|
||||
}
|
||||
|
||||
binding.downloadButton.setImageResource(drawable)
|
||||
})
|
||||
|
||||
binding.trailerButton.setOnClickListener {
|
||||
if (viewModel.item.value?.remoteTrailers.isNullOrEmpty()) return@setOnClickListener
|
||||
val intent = Intent(
|
||||
|
|
|
@ -14,5 +14,7 @@ data class DownloadMetadata(
|
|||
val indexNumber: Int? = null,
|
||||
val playbackPosition: Long? = null,
|
||||
val playedPercentage: Double? = null,
|
||||
val seriesId: UUID? = null
|
||||
val seriesId: UUID? = null,
|
||||
val played: Boolean? = null,
|
||||
val overview: String? = null
|
||||
) : Parcelable
|
|
@ -1,17 +1,11 @@
|
|||
package dev.jdtech.jellyfin.utils
|
||||
|
||||
import android.Manifest
|
||||
import android.app.DownloadManager
|
||||
import android.content.Context
|
||||
import android.content.pm.PackageManager
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Environment
|
||||
import androidx.core.app.ActivityCompat
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.content.getSystemService
|
||||
import androidx.fragment.app.Fragment
|
||||
import dev.jdtech.jellyfin.R
|
||||
import dev.jdtech.jellyfin.models.DownloadMetadata
|
||||
import dev.jdtech.jellyfin.models.DownloadRequestItem
|
||||
import dev.jdtech.jellyfin.models.PlayerItem
|
||||
|
@ -22,46 +16,9 @@ import timber.log.Timber
|
|||
import java.io.File
|
||||
import java.util.UUID
|
||||
|
||||
var defaultStorage: File? = null
|
||||
|
||||
fun requestDownload(uri: Uri, downloadRequestItem: DownloadRequestItem, context: Fragment) {
|
||||
// Storage permission for downloads isn't necessary from Android 10 onwards
|
||||
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.P) {
|
||||
@Suppress("MagicNumber")
|
||||
Timber.d("REQUESTING PERMISSION")
|
||||
|
||||
if (ContextCompat.checkSelfPermission(
|
||||
context.requireActivity(),
|
||||
Manifest.permission.WRITE_EXTERNAL_STORAGE
|
||||
) != PackageManager.PERMISSION_GRANTED
|
||||
) {
|
||||
if (ActivityCompat.shouldShowRequestPermissionRationale(
|
||||
context.requireActivity(),
|
||||
Manifest.permission.WRITE_EXTERNAL_STORAGE
|
||||
)
|
||||
) {
|
||||
ActivityCompat.requestPermissions(
|
||||
context.requireActivity(),
|
||||
arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE), 1
|
||||
)
|
||||
} else {
|
||||
ActivityCompat.requestPermissions(
|
||||
context.requireActivity(),
|
||||
arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE), 1
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
val granted = ContextCompat.checkSelfPermission(
|
||||
context.requireActivity(),
|
||||
Manifest.permission.WRITE_EXTERNAL_STORAGE
|
||||
) == PackageManager.PERMISSION_GRANTED
|
||||
|
||||
if (!granted) {
|
||||
context.requireContext().toast(R.string.download_no_storage_permission)
|
||||
return
|
||||
}
|
||||
}
|
||||
val defaultStorage = getDownloadLocation(context.requireContext())
|
||||
Timber.d(defaultStorage.toString())
|
||||
val downloadRequest = DownloadManager.Request(uri)
|
||||
.setTitle(downloadRequestItem.metadata.name)
|
||||
.setDescription("Downloading")
|
||||
|
@ -75,16 +32,13 @@ fun requestDownload(uri: Uri, downloadRequestItem: DownloadRequestItem, context:
|
|||
)
|
||||
.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED)
|
||||
if (!File(defaultStorage, downloadRequestItem.itemId.toString()).exists())
|
||||
downloadFile(downloadRequest, 1, context.requireContext())
|
||||
downloadFile(downloadRequest, context.requireContext())
|
||||
createMetadataFile(
|
||||
downloadRequestItem.metadata,
|
||||
downloadRequestItem.itemId,
|
||||
context.requireContext()
|
||||
)
|
||||
downloadRequestItem.itemId)
|
||||
}
|
||||
|
||||
private fun createMetadataFile(metadata: DownloadMetadata, itemId: UUID, context: Context) {
|
||||
val defaultStorage = getDownloadLocation(context)
|
||||
private fun createMetadataFile(metadata: DownloadMetadata, itemId: UUID) {
|
||||
val metadataFile = File(defaultStorage, "${itemId}.metadata")
|
||||
|
||||
metadataFile.writeText("") //This might be necessary to make sure that the metadata file is empty
|
||||
|
@ -100,6 +54,8 @@ private fun createMetadataFile(metadata: DownloadMetadata, itemId: UUID, context
|
|||
out.println(metadata.playbackPosition.toString())
|
||||
out.println(metadata.playedPercentage.toString())
|
||||
out.println(metadata.seriesId.toString())
|
||||
out.println(metadata.played.toString())
|
||||
out.println(if (metadata.overview != null) metadata.overview.replace("\n", "\\n") else "")
|
||||
}
|
||||
} else if (metadata.type == "Movie") {
|
||||
metadataFile.printWriter().use { out ->
|
||||
|
@ -108,13 +64,14 @@ private fun createMetadataFile(metadata: DownloadMetadata, itemId: UUID, context
|
|||
out.println(metadata.name.toString())
|
||||
out.println(metadata.playbackPosition.toString())
|
||||
out.println(metadata.playedPercentage.toString())
|
||||
out.println(metadata.played.toString())
|
||||
out.println(if (metadata.overview != null) metadata.overview.replace("\n", "\\n") else "")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private fun downloadFile(request: DownloadManager.Request, downloadMethod: Int, context: Context) {
|
||||
require(downloadMethod >= 0) { "Download method hasn't been set" }
|
||||
private fun downloadFile(request: DownloadManager.Request, context: Context) {
|
||||
request.apply {
|
||||
setAllowedOverMetered(false)
|
||||
setAllowedOverRoaming(false)
|
||||
|
@ -122,13 +79,12 @@ private fun downloadFile(request: DownloadManager.Request, downloadMethod: Int,
|
|||
context.getSystemService<DownloadManager>()?.enqueue(request)
|
||||
}
|
||||
|
||||
private fun getDownloadLocation(context: Context): File? {
|
||||
return context.getExternalFilesDir(Environment.DIRECTORY_MOVIES)
|
||||
fun loadDownloadLocation(context: Context) {
|
||||
defaultStorage = context.getExternalFilesDir(Environment.DIRECTORY_MOVIES)
|
||||
}
|
||||
|
||||
fun loadDownloadedEpisodes(context: Context): List<PlayerItem> {
|
||||
fun loadDownloadedEpisodes(): List<PlayerItem> {
|
||||
val items = mutableListOf<PlayerItem>()
|
||||
val defaultStorage = getDownloadLocation(context)
|
||||
defaultStorage?.walk()?.forEach {
|
||||
if (it.isFile && it.extension == "") {
|
||||
try {
|
||||
|
@ -154,6 +110,29 @@ fun loadDownloadedEpisodes(context: Context): List<PlayerItem> {
|
|||
return items.toList()
|
||||
}
|
||||
|
||||
fun itemIsDownloaded(itemId: UUID): Boolean {
|
||||
val file = File(defaultStorage!!, itemId.toString())
|
||||
if (file.isFile && file.extension == "") {
|
||||
if (File(defaultStorage, "${itemId}.metadata").exists()){
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
fun getDownloadPlayerItem(itemId: UUID): PlayerItem? {
|
||||
val file = File(defaultStorage!!, itemId.toString())
|
||||
try{
|
||||
val metadataFile = File(defaultStorage, "${file.name}.metadata").readLines()
|
||||
val metadata = parseMetadataFile(metadataFile)
|
||||
return PlayerItem(metadata.name, UUID.fromString(file.name), "", metadata.playbackPosition!!, file.absolutePath, metadata)
|
||||
} catch (e: Exception) {
|
||||
file.delete()
|
||||
Timber.e(e)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
fun deleteDownloadedEpisode(uri: String) {
|
||||
try {
|
||||
File(uri).delete()
|
||||
|
@ -175,7 +154,7 @@ fun postDownloadPlaybackProgress(uri: String, playbackPosition: Long, playedPerc
|
|||
metadataArray[3] = playbackPosition.toString()
|
||||
metadataArray[4] = playedPercentage.toString()
|
||||
}
|
||||
|
||||
Timber.d("PLAYEDPERCENTAGE $playedPercentage")
|
||||
metadataFile.writeText("") //This might be necessary to make sure that the metadata file is empty
|
||||
metadataFile.printWriter().use { out ->
|
||||
metadataArray.forEach {
|
||||
|
@ -204,7 +183,8 @@ fun downloadMetadataToBaseItemDto(metadata: DownloadMetadata): BaseItemDto {
|
|||
parentIndexNumber = metadata.parentIndexNumber,
|
||||
indexNumber = metadata.indexNumber,
|
||||
userData = userData,
|
||||
seriesId = metadata.seriesId
|
||||
seriesId = metadata.seriesId,
|
||||
overview = metadata.overview
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -218,7 +198,9 @@ fun baseItemDtoToDownloadMetadata(item: BaseItemDto): DownloadMetadata {
|
|||
indexNumber = item.indexNumber,
|
||||
playbackPosition = item.userData?.playbackPositionTicks ?: 0,
|
||||
playedPercentage = item.userData?.playedPercentage,
|
||||
seriesId = item.seriesId
|
||||
seriesId = item.seriesId,
|
||||
played = item.userData?.played,
|
||||
overview = item.overview
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -237,7 +219,9 @@ fun parseMetadataFile(metadataFile: List<String>): DownloadMetadata {
|
|||
} else {
|
||||
metadataFile[7].toDouble()
|
||||
},
|
||||
seriesId = UUID.fromString(metadataFile[8])
|
||||
seriesId = UUID.fromString(metadataFile[8]),
|
||||
played = metadataFile[9].toBoolean(),
|
||||
overview = metadataFile[10].replace("\\n", "\n")
|
||||
)
|
||||
} else {
|
||||
return DownloadMetadata(
|
||||
|
@ -250,12 +234,14 @@ fun parseMetadataFile(metadataFile: List<String>): DownloadMetadata {
|
|||
} else {
|
||||
metadataFile[4].toDouble()
|
||||
},
|
||||
played = metadataFile[5].toBoolean(),
|
||||
overview = metadataFile[6].replace("\\n", "\n")
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun syncPlaybackProgress(jellyfinRepository: JellyfinRepository, context: Context) {
|
||||
val items = loadDownloadedEpisodes(context)
|
||||
suspend fun syncPlaybackProgress(jellyfinRepository: JellyfinRepository) {
|
||||
val items = loadDownloadedEpisodes()
|
||||
items.forEach() {
|
||||
try {
|
||||
val localPlaybackProgress = it.metadata?.playbackPosition
|
||||
|
@ -268,6 +254,10 @@ suspend fun syncPlaybackProgress(jellyfinRepository: JellyfinRepository, context
|
|||
var playbackProgress: Long = 0
|
||||
var playedPercentage = 0.0
|
||||
|
||||
if (it.metadata?.played == true || item.userData?.played == true){
|
||||
return@forEach
|
||||
}
|
||||
|
||||
if (localPlaybackProgress != null) {
|
||||
if (localPlaybackProgress > playbackProgress) {
|
||||
playbackProgress = localPlaybackProgress
|
||||
|
|
|
@ -1,28 +1,18 @@
|
|||
package dev.jdtech.jellyfin.viewmodels
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Application
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import dev.jdtech.jellyfin.models.DownloadSection
|
||||
import dev.jdtech.jellyfin.repository.JellyfinRepository
|
||||
import dev.jdtech.jellyfin.utils.loadDownloadedEpisodes
|
||||
import dev.jdtech.jellyfin.utils.postDownloadPlaybackProgress
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber
|
||||
import java.util.*
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltViewModel
|
||||
class DownloadViewModel
|
||||
@Inject
|
||||
constructor(
|
||||
private val application: Application,
|
||||
) : ViewModel() {
|
||||
class DownloadViewModel : ViewModel() {
|
||||
private val _downloadSections = MutableLiveData<List<DownloadSection>>()
|
||||
val downloadSections: LiveData<List<DownloadSection>> = _downloadSections
|
||||
|
||||
|
@ -42,7 +32,7 @@ constructor(
|
|||
_finishedLoading.value = false
|
||||
viewModelScope.launch {
|
||||
try {
|
||||
val items = loadDownloadedEpisodes(application)
|
||||
val items = loadDownloadedEpisodes()
|
||||
if (items.isEmpty()) {
|
||||
_downloadSections.value = listOf()
|
||||
_finishedLoading.value = true
|
||||
|
|
|
@ -13,6 +13,7 @@ import dev.jdtech.jellyfin.repository.JellyfinRepository
|
|||
import dev.jdtech.jellyfin.utils.baseItemDtoToDownloadMetadata
|
||||
import dev.jdtech.jellyfin.utils.deleteDownloadedEpisode
|
||||
import dev.jdtech.jellyfin.utils.downloadMetadataToBaseItemDto
|
||||
import dev.jdtech.jellyfin.utils.itemIsDownloaded
|
||||
import kotlinx.coroutines.launch
|
||||
import org.jellyfin.sdk.model.api.BaseItemDto
|
||||
import org.jellyfin.sdk.model.api.ItemFields
|
||||
|
@ -46,6 +47,9 @@ constructor(
|
|||
private val _favorite = MutableLiveData<Boolean>()
|
||||
val favorite: LiveData<Boolean> = _favorite
|
||||
|
||||
private val _downloaded = MutableLiveData<Boolean>()
|
||||
val downloaded: LiveData<Boolean> = _downloaded
|
||||
|
||||
private val _downloadEpisode = MutableLiveData<Boolean>()
|
||||
val downloadEpisode: LiveData<Boolean> = _downloadEpisode
|
||||
|
||||
|
@ -56,6 +60,7 @@ constructor(
|
|||
fun loadEpisode(episodeId: UUID) {
|
||||
viewModelScope.launch {
|
||||
try {
|
||||
_downloaded.value = itemIsDownloaded(episodeId)
|
||||
val item = jellyfinRepository.getItem(episodeId)
|
||||
_item.value = item
|
||||
_runTime.value = "${item.runTimeTicks?.div(600000000)} min"
|
||||
|
@ -129,5 +134,6 @@ constructor(
|
|||
|
||||
fun doneDownloadEpisode() {
|
||||
_downloadEpisode.value = false
|
||||
_downloaded.value = true
|
||||
}
|
||||
}
|
|
@ -27,7 +27,7 @@ import javax.inject.Inject
|
|||
|
||||
@HiltViewModel
|
||||
class HomeViewModel @Inject internal constructor(
|
||||
private val application: Application,
|
||||
application: Application,
|
||||
private val repository: JellyfinRepository
|
||||
) : ViewModel() {
|
||||
|
||||
|
@ -67,7 +67,7 @@ class HomeViewModel @Inject internal constructor(
|
|||
views.postValue(updated)
|
||||
|
||||
withContext(Dispatchers.Default) {
|
||||
syncPlaybackProgress(repository, application)
|
||||
syncPlaybackProgress(repository)
|
||||
}
|
||||
state.tryEmit(Loading(inProgress = false))
|
||||
} catch (e: Exception) {
|
||||
|
|
|
@ -12,6 +12,7 @@ import dev.jdtech.jellyfin.repository.JellyfinRepository
|
|||
import dev.jdtech.jellyfin.utils.baseItemDtoToDownloadMetadata
|
||||
import dev.jdtech.jellyfin.utils.deleteDownloadedEpisode
|
||||
import dev.jdtech.jellyfin.utils.downloadMetadataToBaseItemDto
|
||||
import dev.jdtech.jellyfin.utils.itemIsDownloaded
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
|
@ -61,6 +62,9 @@ constructor(private val jellyfinRepository: JellyfinRepository) : ViewModel() {
|
|||
private val _favorite = MutableLiveData<Boolean>()
|
||||
val favorite: LiveData<Boolean> = _favorite
|
||||
|
||||
private val _downloaded = MutableLiveData<Boolean>()
|
||||
val downloaded: LiveData<Boolean> = _downloaded
|
||||
|
||||
private val _error = MutableLiveData<String>()
|
||||
val error: LiveData<String> = _error
|
||||
|
||||
|
@ -75,6 +79,7 @@ constructor(private val jellyfinRepository: JellyfinRepository) : ViewModel() {
|
|||
_error.value = null
|
||||
viewModelScope.launch {
|
||||
try {
|
||||
_downloaded.value = itemIsDownloaded(itemId)
|
||||
_item.value = jellyfinRepository.getItem(itemId)
|
||||
_actors.value = getActors(_item.value!!)
|
||||
_director.value = getDirector(_item.value!!)
|
||||
|
@ -201,5 +206,6 @@ constructor(private val jellyfinRepository: JellyfinRepository) : ViewModel() {
|
|||
|
||||
fun doneDownloadMedia() {
|
||||
_downloadMedia.value = false
|
||||
_downloaded.value = true
|
||||
}
|
||||
}
|
|
@ -107,6 +107,7 @@ constructor(
|
|||
item.mediaSourceUri.isNotEmpty() -> item.mediaSourceUri
|
||||
else -> jellyfinRepository.getStreamUrl(item.itemId, item.mediaSourceId)
|
||||
}
|
||||
playFromDownloads = item.mediaSourceUri.isNotEmpty()
|
||||
|
||||
Timber.d("Stream url: $streamUrl")
|
||||
val mediaItem =
|
||||
|
@ -156,6 +157,9 @@ constructor(
|
|||
override fun run() {
|
||||
viewModelScope.launch {
|
||||
if (player.currentMediaItem != null) {
|
||||
if(playFromDownloads){
|
||||
postDownloadPlaybackProgress(items[0].mediaSourceUri, player.currentPosition, (player.currentPosition.toDouble()/player.duration.toDouble()).times(100)) //TODO Automatically use the correct item
|
||||
}
|
||||
try {
|
||||
jellyfinRepository.postPlaybackProgress(
|
||||
UUID.fromString(player.currentMediaItem!!.mediaId),
|
||||
|
@ -165,9 +169,6 @@ constructor(
|
|||
} catch (e: Exception) {
|
||||
Timber.e(e)
|
||||
}
|
||||
if(playFromDownloads){
|
||||
postDownloadPlaybackProgress(items[0].mediaSourceUri, player.currentPosition, (player.currentPosition.toDouble()/player.duration.toDouble()).times(100)) //TODO Automaticcaly use the correct item
|
||||
}
|
||||
}
|
||||
}
|
||||
handler.postDelayed(this, 2000)
|
||||
|
|
|
@ -6,6 +6,8 @@ import androidx.lifecycle.viewModelScope
|
|||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import dev.jdtech.jellyfin.models.PlayerItem
|
||||
import dev.jdtech.jellyfin.repository.JellyfinRepository
|
||||
import dev.jdtech.jellyfin.utils.getDownloadPlayerItem
|
||||
import dev.jdtech.jellyfin.utils.itemIsDownloaded
|
||||
import kotlinx.coroutines.channels.BufferOverflow
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.collect
|
||||
|
@ -35,8 +37,15 @@ class PlayerViewModel @Inject internal constructor(
|
|||
fun loadPlayerItems(
|
||||
item: BaseItemDto,
|
||||
mediaSourceIndex: Int = 0,
|
||||
onVersionSelectRequired: () -> Unit = { Unit }
|
||||
onVersionSelectRequired: () -> Unit = { }
|
||||
) {
|
||||
if (itemIsDownloaded(item.id)) {
|
||||
val playerItem = getDownloadPlayerItem(item.id)
|
||||
if (playerItem != null) {
|
||||
loadOfflinePlayerItems(playerItem)
|
||||
return
|
||||
}
|
||||
}
|
||||
Timber.d("Loading player items for item ${item.id}")
|
||||
if (item.mediaSources.orEmpty().size > 1) {
|
||||
onVersionSelectRequired()
|
||||
|
|
|
@ -79,7 +79,6 @@
|
|||
<string name="force_software_decoding">Force software decoding</string>
|
||||
<string name="force_software_decoding_summary">Disable hardware decoding and use software decoding. Can be useful if hardware decoding gives weird artifacts.</string>
|
||||
<string name="download_button_description">Download</string>
|
||||
<string name="download_no_storage_permission">Cannot download files without storage permissions</string>
|
||||
<string name="delete_button_description">Delete</string>
|
||||
<string name="person_detail_title">Person Detail</string>
|
||||
<string name="error_getting_person_id">Detail unavailable</string>
|
||||
|
|
Loading…
Reference in a new issue