lint: klint standard
This commit is contained in:
parent
062781a43d
commit
633ee6b8c4
3 changed files with 636 additions and 438 deletions
|
@ -30,7 +30,6 @@ import org.jellyfin.sdk.model.api.EncodingContext
|
||||||
import org.jellyfin.sdk.model.api.MediaStreamType
|
import org.jellyfin.sdk.model.api.MediaStreamType
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
import kotlin.Exception
|
|
||||||
import kotlin.math.ceil
|
import kotlin.math.ceil
|
||||||
import dev.jdtech.jellyfin.core.R as CoreR
|
import dev.jdtech.jellyfin.core.R as CoreR
|
||||||
|
|
||||||
|
@ -48,9 +47,11 @@ class DownloaderImpl(
|
||||||
storageIndex: Int,
|
storageIndex: Int,
|
||||||
): Pair<Long, UiText?> {
|
): Pair<Long, UiText?> {
|
||||||
try {
|
try {
|
||||||
val source = jellyfinRepository.getMediaSources(item.id, true).first { it.id == sourceId }
|
val source =
|
||||||
|
jellyfinRepository.getMediaSources(item.id, true).first { it.id == sourceId }
|
||||||
val intro = jellyfinRepository.getIntroTimestamps(item.id)
|
val intro = jellyfinRepository.getIntroTimestamps(item.id)
|
||||||
val trickplayInfo = if (item is FindroidSources) {
|
val trickplayInfo =
|
||||||
|
if (item is FindroidSources) {
|
||||||
item.trickplayInfo?.get(sourceId)
|
item.trickplayInfo?.get(sourceId)
|
||||||
} else {
|
} else {
|
||||||
null
|
null
|
||||||
|
@ -85,9 +86,12 @@ class DownloaderImpl(
|
||||||
database.insertIntro(intro.toIntroDto(item.id))
|
database.insertIntro(intro.toIntroDto(item.id))
|
||||||
}
|
}
|
||||||
if (appPreferences.downloadQuality != "Original") {
|
if (appPreferences.downloadQuality != "Original") {
|
||||||
downloadEmbeddedMediaStreams(item, source,storageIndex)
|
downloadEmbeddedMediaStreams(item, source, storageIndex)
|
||||||
val transcodingUrl =getTranscodedUrl(item.id,appPreferences.downloadQuality!!)
|
val transcodingUrl =
|
||||||
val request = DownloadManager.Request(transcodingUrl)
|
getTranscodedUrl(item.id, appPreferences.downloadQuality!!)
|
||||||
|
val request =
|
||||||
|
DownloadManager
|
||||||
|
.Request(transcodingUrl)
|
||||||
.setTitle(item.name)
|
.setTitle(item.name)
|
||||||
.setAllowedOverMetered(appPreferences.downloadOverMobileData)
|
.setAllowedOverMetered(appPreferences.downloadOverMobileData)
|
||||||
.setAllowedOverRoaming(appPreferences.downloadWhenRoaming)
|
.setAllowedOverRoaming(appPreferences.downloadWhenRoaming)
|
||||||
|
@ -96,8 +100,10 @@ class DownloaderImpl(
|
||||||
val downloadId = downloadManager.enqueue(request)
|
val downloadId = downloadManager.enqueue(request)
|
||||||
database.setSourceDownloadId(source.id, downloadId)
|
database.setSourceDownloadId(source.id, downloadId)
|
||||||
return Pair(downloadId, null)
|
return Pair(downloadId, null)
|
||||||
}else {
|
} else {
|
||||||
val request = DownloadManager.Request(source.path.toUri())
|
val request =
|
||||||
|
DownloadManager
|
||||||
|
.Request(source.path.toUri())
|
||||||
.setTitle(item.name)
|
.setTitle(item.name)
|
||||||
.setAllowedOverMetered(appPreferences.downloadOverMobileData)
|
.setAllowedOverMetered(appPreferences.downloadOverMobileData)
|
||||||
.setAllowedOverRoaming(appPreferences.downloadWhenRoaming)
|
.setAllowedOverRoaming(appPreferences.downloadWhenRoaming)
|
||||||
|
@ -111,7 +117,8 @@ class DownloaderImpl(
|
||||||
|
|
||||||
is FindroidEpisode -> {
|
is FindroidEpisode -> {
|
||||||
database.insertShow(
|
database.insertShow(
|
||||||
jellyfinRepository.getShow(item.seriesId)
|
jellyfinRepository
|
||||||
|
.getShow(item.seriesId)
|
||||||
.toFindroidShowDto(appPreferences.currentServer!!),
|
.toFindroidShowDto(appPreferences.currentServer!!),
|
||||||
)
|
)
|
||||||
database.insertSeason(
|
database.insertSeason(
|
||||||
|
@ -128,9 +135,12 @@ class DownloaderImpl(
|
||||||
database.insertIntro(intro.toIntroDto(item.id))
|
database.insertIntro(intro.toIntroDto(item.id))
|
||||||
}
|
}
|
||||||
if (appPreferences.downloadQuality != "Original") {
|
if (appPreferences.downloadQuality != "Original") {
|
||||||
downloadEmbeddedMediaStreams(item, source,storageIndex)
|
downloadEmbeddedMediaStreams(item, source, storageIndex)
|
||||||
val transcodingUrl = getTranscodedUrl(item.id, appPreferences.downloadQuality!!)
|
val transcodingUrl =
|
||||||
val request = DownloadManager.Request(transcodingUrl)
|
getTranscodedUrl(item.id, appPreferences.downloadQuality!!)
|
||||||
|
val request =
|
||||||
|
DownloadManager
|
||||||
|
.Request(transcodingUrl)
|
||||||
.setTitle(item.name)
|
.setTitle(item.name)
|
||||||
.setAllowedOverMetered(appPreferences.downloadOverMobileData)
|
.setAllowedOverMetered(appPreferences.downloadOverMobileData)
|
||||||
.setAllowedOverRoaming(appPreferences.downloadWhenRoaming)
|
.setAllowedOverRoaming(appPreferences.downloadWhenRoaming)
|
||||||
|
@ -139,8 +149,10 @@ class DownloaderImpl(
|
||||||
val downloadId = downloadManager.enqueue(request)
|
val downloadId = downloadManager.enqueue(request)
|
||||||
database.setSourceDownloadId(source.id, downloadId)
|
database.setSourceDownloadId(source.id, downloadId)
|
||||||
return Pair(downloadId, null)
|
return Pair(downloadId, null)
|
||||||
}else {
|
} else {
|
||||||
val request = DownloadManager.Request(source.path.toUri())
|
val request =
|
||||||
|
DownloadManager
|
||||||
|
.Request(source.path.toUri())
|
||||||
.setTitle(item.name)
|
.setTitle(item.name)
|
||||||
.setAllowedOverMetered(appPreferences.downloadOverMobileData)
|
.setAllowedOverMetered(appPreferences.downloadOverMobileData)
|
||||||
.setAllowedOverRoaming(appPreferences.downloadWhenRoaming)
|
.setAllowedOverRoaming(appPreferences.downloadWhenRoaming)
|
||||||
|
@ -157,24 +169,41 @@ class DownloaderImpl(
|
||||||
try {
|
try {
|
||||||
val source = jellyfinRepository.getMediaSources(item.id).first { it.id == sourceId }
|
val source = jellyfinRepository.getMediaSources(item.id).first { it.id == sourceId }
|
||||||
deleteItem(item, source)
|
deleteItem(item, source)
|
||||||
} catch (_: Exception) {}
|
} catch (_: Exception) {
|
||||||
|
}
|
||||||
|
|
||||||
return Pair(-1, if (e.message != null) UiText.DynamicString(e.message!!) else UiText.StringResource(CoreR.string.unknown_error))
|
return Pair(
|
||||||
|
-1,
|
||||||
|
if (e.message != null) {
|
||||||
|
UiText.DynamicString(e.message!!)
|
||||||
|
} else {
|
||||||
|
UiText.StringResource(
|
||||||
|
CoreR.string.unknown_error,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun cancelDownload(item: FindroidItem, source: FindroidSource) {
|
override suspend fun cancelDownload(
|
||||||
|
item: FindroidItem,
|
||||||
|
source: FindroidSource,
|
||||||
|
) {
|
||||||
if (source.downloadId != null) {
|
if (source.downloadId != null) {
|
||||||
downloadManager.remove(source.downloadId!!)
|
downloadManager.remove(source.downloadId!!)
|
||||||
}
|
}
|
||||||
deleteItem(item, source)
|
deleteItem(item, source)
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun deleteItem(item: FindroidItem, source: FindroidSource) {
|
override suspend fun deleteItem(
|
||||||
|
item: FindroidItem,
|
||||||
|
source: FindroidSource,
|
||||||
|
) {
|
||||||
when (item) {
|
when (item) {
|
||||||
is FindroidMovie -> {
|
is FindroidMovie -> {
|
||||||
database.deleteMovie(item.id)
|
database.deleteMovie(item.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
is FindroidEpisode -> {
|
is FindroidEpisode -> {
|
||||||
database.deleteEpisode(item.id)
|
database.deleteEpisode(item.id)
|
||||||
val remainingEpisodes = database.getEpisodesBySeasonId(item.seasonId)
|
val remainingEpisodes = database.getEpisodesBySeasonId(item.seasonId)
|
||||||
|
@ -212,23 +241,29 @@ class DownloaderImpl(
|
||||||
if (downloadId == null) {
|
if (downloadId == null) {
|
||||||
return Pair(downloadStatus, progress)
|
return Pair(downloadStatus, progress)
|
||||||
}
|
}
|
||||||
val query = DownloadManager.Query()
|
val query =
|
||||||
|
DownloadManager
|
||||||
|
.Query()
|
||||||
.setFilterById(downloadId)
|
.setFilterById(downloadId)
|
||||||
val cursor = downloadManager.query(query)
|
val cursor = downloadManager.query(query)
|
||||||
if (cursor.moveToFirst()) {
|
if (cursor.moveToFirst()) {
|
||||||
downloadStatus = cursor.getInt(
|
downloadStatus =
|
||||||
|
cursor.getInt(
|
||||||
cursor.getColumnIndexOrThrow(
|
cursor.getColumnIndexOrThrow(
|
||||||
DownloadManager.COLUMN_STATUS,
|
DownloadManager.COLUMN_STATUS,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
when (downloadStatus) {
|
when (downloadStatus) {
|
||||||
DownloadManager.STATUS_RUNNING -> {
|
DownloadManager.STATUS_RUNNING -> {
|
||||||
val totalBytes = cursor.getLong(cursor.getColumnIndexOrThrow(DownloadManager.COLUMN_TOTAL_SIZE_BYTES))
|
val totalBytes =
|
||||||
|
cursor.getLong(cursor.getColumnIndexOrThrow(DownloadManager.COLUMN_TOTAL_SIZE_BYTES))
|
||||||
if (totalBytes > 0) {
|
if (totalBytes > 0) {
|
||||||
val downloadedBytes = cursor.getLong(cursor.getColumnIndexOrThrow(DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR))
|
val downloadedBytes =
|
||||||
|
cursor.getLong(cursor.getColumnIndexOrThrow(DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR))
|
||||||
progress = downloadedBytes.times(100).div(totalBytes).toInt()
|
progress = downloadedBytes.times(100).div(totalBytes).toInt()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
DownloadManager.STATUS_SUCCESSFUL -> {
|
DownloadManager.STATUS_SUCCESSFUL -> {
|
||||||
progress = 100
|
progress = 100
|
||||||
}
|
}
|
||||||
|
@ -247,9 +282,23 @@ class DownloaderImpl(
|
||||||
val storageLocation = context.getExternalFilesDirs(null)[storageIndex]
|
val storageLocation = context.getExternalFilesDirs(null)[storageIndex]
|
||||||
for (mediaStream in source.mediaStreams.filter { it.isExternal }) {
|
for (mediaStream in source.mediaStreams.filter { it.isExternal }) {
|
||||||
val id = UUID.randomUUID()
|
val id = UUID.randomUUID()
|
||||||
val streamPath = Uri.fromFile(File(storageLocation, "downloads/${item.id}.${source.id}.$id.download"))
|
val streamPath =
|
||||||
database.insertMediaStream(mediaStream.toFindroidMediaStreamDto(id, source.id, streamPath.path.orEmpty()))
|
Uri.fromFile(
|
||||||
val request = DownloadManager.Request(Uri.parse(mediaStream.path))
|
File(
|
||||||
|
storageLocation,
|
||||||
|
"downloads/${item.id}.${source.id}.$id.download",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
database.insertMediaStream(
|
||||||
|
mediaStream.toFindroidMediaStreamDto(
|
||||||
|
id,
|
||||||
|
source.id,
|
||||||
|
streamPath.path.orEmpty(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
val request =
|
||||||
|
DownloadManager
|
||||||
|
.Request(Uri.parse(mediaStream.path))
|
||||||
.setTitle(mediaStream.title)
|
.setTitle(mediaStream.title)
|
||||||
.setAllowedOverMetered(appPreferences.downloadOverMobileData)
|
.setAllowedOverMetered(appPreferences.downloadOverMobileData)
|
||||||
.setAllowedOverRoaming(appPreferences.downloadWhenRoaming)
|
.setAllowedOverRoaming(appPreferences.downloadWhenRoaming)
|
||||||
|
@ -263,30 +312,34 @@ class DownloaderImpl(
|
||||||
private fun downloadEmbeddedMediaStreams(
|
private fun downloadEmbeddedMediaStreams(
|
||||||
item: FindroidItem,
|
item: FindroidItem,
|
||||||
source: FindroidSource,
|
source: FindroidSource,
|
||||||
storageIndex: Int = 0
|
storageIndex: Int = 0,
|
||||||
) {
|
) {
|
||||||
val storageLocation = context.getExternalFilesDirs(null)[storageIndex]
|
val storageLocation = context.getExternalFilesDirs(null)[storageIndex]
|
||||||
val subtitleStreams = source.mediaStreams.filter { !it.isExternal && it.type == MediaStreamType.SUBTITLE && it.path != null }
|
val subtitleStreams =
|
||||||
|
source.mediaStreams.filter { !it.isExternal && it.type == MediaStreamType.SUBTITLE && it.path != null }
|
||||||
for (mediaStream in subtitleStreams) {
|
for (mediaStream in subtitleStreams) {
|
||||||
var deliveryUrl = mediaStream.path!!
|
var deliveryUrl = mediaStream.path!!
|
||||||
if (mediaStream.codec == "webvtt") {
|
if (mediaStream.codec == "webvtt") {
|
||||||
deliveryUrl = deliveryUrl.replace("Stream.srt", "Stream.vtt")
|
deliveryUrl = deliveryUrl.replace("Stream.srt", "Stream.vtt")
|
||||||
}
|
}
|
||||||
val id = UUID.randomUUID()
|
val id = UUID.randomUUID()
|
||||||
val streamPath = Uri.fromFile(
|
val streamPath =
|
||||||
|
Uri.fromFile(
|
||||||
File(
|
File(
|
||||||
storageLocation,
|
storageLocation,
|
||||||
"downloads/${item.id}.${source.id}.$id.download"
|
"downloads/${item.id}.${source.id}.$id.download",
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
database.insertMediaStream(
|
database.insertMediaStream(
|
||||||
mediaStream.toFindroidMediaStreamDto(
|
mediaStream.toFindroidMediaStreamDto(
|
||||||
id,
|
id,
|
||||||
source.id,
|
source.id,
|
||||||
streamPath.path.orEmpty()
|
streamPath.path.orEmpty(),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
)
|
val request =
|
||||||
val request = DownloadManager.Request(Uri.parse(deliveryUrl))
|
DownloadManager
|
||||||
|
.Request(Uri.parse(deliveryUrl))
|
||||||
.setTitle(mediaStream.title)
|
.setTitle(mediaStream.title)
|
||||||
.setAllowedOverMetered(appPreferences.downloadOverMobileData)
|
.setAllowedOverMetered(appPreferences.downloadOverMobileData)
|
||||||
.setAllowedOverRoaming(appPreferences.downloadWhenRoaming)
|
.setAllowedOverRoaming(appPreferences.downloadWhenRoaming)
|
||||||
|
@ -298,16 +351,21 @@ class DownloaderImpl(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private suspend fun downloadTrickplayData(
|
private suspend fun downloadTrickplayData(
|
||||||
itemId: UUID,
|
itemId: UUID,
|
||||||
sourceId: String,
|
sourceId: String,
|
||||||
trickplayInfo: FindroidTrickplayInfo,
|
trickplayInfo: FindroidTrickplayInfo,
|
||||||
) {
|
) {
|
||||||
val maxIndex = ceil(trickplayInfo.thumbnailCount.toDouble().div(trickplayInfo.tileWidth * trickplayInfo.tileHeight)).toInt()
|
val maxIndex =
|
||||||
|
ceil(
|
||||||
|
trickplayInfo.thumbnailCount
|
||||||
|
.toDouble()
|
||||||
|
.div(trickplayInfo.tileWidth * trickplayInfo.tileHeight),
|
||||||
|
).toInt()
|
||||||
val byteArrays = mutableListOf<ByteArray>()
|
val byteArrays = mutableListOf<ByteArray>()
|
||||||
for (i in 0..maxIndex) {
|
for (i in 0..maxIndex) {
|
||||||
jellyfinRepository.getTrickplayData(
|
jellyfinRepository
|
||||||
|
.getTrickplayData(
|
||||||
itemId,
|
itemId,
|
||||||
trickplayInfo.width,
|
trickplayInfo.width,
|
||||||
i,
|
i,
|
||||||
|
@ -333,8 +391,12 @@ class DownloaderImpl(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun getTranscodedUrl(itemId: UUID, quality: String): Uri? {
|
private suspend fun getTranscodedUrl(
|
||||||
val maxBitrate = when (quality) {
|
itemId: UUID,
|
||||||
|
quality: String,
|
||||||
|
): Uri? {
|
||||||
|
val maxBitrate =
|
||||||
|
when (quality) {
|
||||||
"720p" -> 2000000 // 2 Mbps
|
"720p" -> 2000000 // 2 Mbps
|
||||||
"480p" -> 1000000 // 1 Mbps
|
"480p" -> 1000000 // 1 Mbps
|
||||||
"360p" -> 800000 // 800Kbps
|
"360p" -> 800000 // 800Kbps
|
||||||
|
@ -342,13 +404,25 @@ class DownloaderImpl(
|
||||||
}
|
}
|
||||||
|
|
||||||
return try {
|
return try {
|
||||||
|
val deviceProfile =
|
||||||
val deviceProfile = jellyfinRepository.buildDeviceProfile(maxBitrate,"mkv", EncodingContext.STATIC)
|
jellyfinRepository.buildDeviceProfile(maxBitrate, "mkv", EncodingContext.STATIC)
|
||||||
val playbackInfo = jellyfinRepository.getPostedPlaybackInfo(itemId,false,deviceProfile,maxBitrate)
|
val playbackInfo =
|
||||||
val mediaSourceId = playbackInfo.content.mediaSources.firstOrNull()?.id!!
|
jellyfinRepository.getPostedPlaybackInfo(itemId, false, deviceProfile, maxBitrate)
|
||||||
|
val mediaSourceId =
|
||||||
|
playbackInfo.content.mediaSources
|
||||||
|
.firstOrNull()
|
||||||
|
?.id!!
|
||||||
val playSessionId = playbackInfo.content.playSessionId!!
|
val playSessionId = playbackInfo.content.playSessionId!!
|
||||||
val deviceId = jellyfinRepository.getDeviceId()
|
val deviceId = jellyfinRepository.getDeviceId()
|
||||||
val downloadUrl = jellyfinRepository.getVideoStreambyContainerUrl(itemId, deviceId, mediaSourceId, playSessionId, maxBitrate, "ts")
|
val downloadUrl =
|
||||||
|
jellyfinRepository.getVideoStreambyContainerUrl(
|
||||||
|
itemId,
|
||||||
|
deviceId,
|
||||||
|
mediaSourceId,
|
||||||
|
playSessionId,
|
||||||
|
maxBitrate,
|
||||||
|
"ts",
|
||||||
|
)
|
||||||
|
|
||||||
val transcodeUri = buildTranscodeUri(downloadUrl, maxBitrate, quality)
|
val transcodeUri = buildTranscodeUri(downloadUrl, maxBitrate, quality)
|
||||||
transcodeUri
|
transcodeUri
|
||||||
|
@ -361,15 +435,18 @@ class DownloaderImpl(
|
||||||
private fun buildTranscodeUri(
|
private fun buildTranscodeUri(
|
||||||
transcodingUrl: String,
|
transcodingUrl: String,
|
||||||
maxBitrate: Int,
|
maxBitrate: Int,
|
||||||
quality: String
|
quality: String,
|
||||||
): Uri {
|
): Uri {
|
||||||
val resolution = when (quality) {
|
val resolution =
|
||||||
|
when (quality) {
|
||||||
"720p" -> "720"
|
"720p" -> "720"
|
||||||
"480p" -> "480"
|
"480p" -> "480"
|
||||||
"360p" -> "360"
|
"360p" -> "360"
|
||||||
else -> "720"
|
else -> "720"
|
||||||
}
|
}
|
||||||
return Uri.parse(transcodingUrl).buildUpon()
|
return Uri
|
||||||
|
.parse(transcodingUrl)
|
||||||
|
.buildUpon()
|
||||||
.appendQueryParameter("MaxVideoHeight", resolution)
|
.appendQueryParameter("MaxVideoHeight", resolution)
|
||||||
.appendQueryParameter("MaxVideoBitRate", maxBitrate.toString())
|
.appendQueryParameter("MaxVideoBitRate", maxBitrate.toString())
|
||||||
.appendQueryParameter("subtitleMethod", "External")
|
.appendQueryParameter("subtitleMethod", "External")
|
||||||
|
|
|
@ -68,53 +68,68 @@ class JellyfinRepositoryImpl(
|
||||||
private val database: ServerDatabaseDao,
|
private val database: ServerDatabaseDao,
|
||||||
private val appPreferences: AppPreferences,
|
private val appPreferences: AppPreferences,
|
||||||
) : JellyfinRepository {
|
) : JellyfinRepository {
|
||||||
override suspend fun getPublicSystemInfo(): PublicSystemInfo = withContext(Dispatchers.IO) {
|
override suspend fun getPublicSystemInfo(): PublicSystemInfo =
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
jellyfinApi.systemApi.getPublicSystemInfo().content
|
jellyfinApi.systemApi.getPublicSystemInfo().content
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun getUserViews(): List<BaseItemDto> = withContext(Dispatchers.IO) {
|
override suspend fun getUserViews(): List<BaseItemDto> =
|
||||||
jellyfinApi.viewsApi.getUserViews(jellyfinApi.userId!!).content.items.orEmpty()
|
withContext(Dispatchers.IO) {
|
||||||
|
jellyfinApi.viewsApi
|
||||||
|
.getUserViews(jellyfinApi.userId!!)
|
||||||
|
.content.items
|
||||||
|
.orEmpty()
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun getItem(itemId: UUID): BaseItemDto = withContext(Dispatchers.IO) {
|
override suspend fun getItem(itemId: UUID): BaseItemDto =
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
jellyfinApi.userLibraryApi.getItem(itemId, jellyfinApi.userId!!).content
|
jellyfinApi.userLibraryApi.getItem(itemId, jellyfinApi.userId!!).content
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun getEpisode(itemId: UUID): FindroidEpisode =
|
override suspend fun getEpisode(itemId: UUID): FindroidEpisode =
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
jellyfinApi.userLibraryApi.getItem(
|
jellyfinApi.userLibraryApi
|
||||||
|
.getItem(
|
||||||
itemId,
|
itemId,
|
||||||
jellyfinApi.userId!!,
|
jellyfinApi.userId!!,
|
||||||
).content.toFindroidEpisode(this@JellyfinRepositoryImpl, database)!!
|
).content
|
||||||
|
.toFindroidEpisode(this@JellyfinRepositoryImpl, database)!!
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun getMovie(itemId: UUID): FindroidMovie =
|
override suspend fun getMovie(itemId: UUID): FindroidMovie =
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
jellyfinApi.userLibraryApi.getItem(
|
jellyfinApi.userLibraryApi
|
||||||
|
.getItem(
|
||||||
itemId,
|
itemId,
|
||||||
jellyfinApi.userId!!,
|
jellyfinApi.userId!!,
|
||||||
).content.toFindroidMovie(this@JellyfinRepositoryImpl, database)
|
).content
|
||||||
|
.toFindroidMovie(this@JellyfinRepositoryImpl, database)
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun getShow(itemId: UUID): FindroidShow =
|
override suspend fun getShow(itemId: UUID): FindroidShow =
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
jellyfinApi.userLibraryApi.getItem(
|
jellyfinApi.userLibraryApi
|
||||||
|
.getItem(
|
||||||
itemId,
|
itemId,
|
||||||
jellyfinApi.userId!!,
|
jellyfinApi.userId!!,
|
||||||
).content.toFindroidShow(this@JellyfinRepositoryImpl)
|
).content
|
||||||
|
.toFindroidShow(this@JellyfinRepositoryImpl)
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun getSeason(itemId: UUID): FindroidSeason =
|
override suspend fun getSeason(itemId: UUID): FindroidSeason =
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
jellyfinApi.userLibraryApi.getItem(
|
jellyfinApi.userLibraryApi
|
||||||
|
.getItem(
|
||||||
itemId,
|
itemId,
|
||||||
jellyfinApi.userId!!,
|
jellyfinApi.userId!!,
|
||||||
).content.toFindroidSeason(this@JellyfinRepositoryImpl)
|
).content
|
||||||
|
.toFindroidSeason(this@JellyfinRepositoryImpl)
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun getLibraries(): List<FindroidCollection> =
|
override suspend fun getLibraries(): List<FindroidCollection> =
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
jellyfinApi.itemsApi.getItems(
|
jellyfinApi.itemsApi
|
||||||
|
.getItems(
|
||||||
jellyfinApi.userId!!,
|
jellyfinApi.userId!!,
|
||||||
).content.items
|
).content.items
|
||||||
.orEmpty()
|
.orEmpty()
|
||||||
|
@ -131,7 +146,8 @@ class JellyfinRepositoryImpl(
|
||||||
limit: Int?,
|
limit: Int?,
|
||||||
): List<FindroidItem> =
|
): List<FindroidItem> =
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
jellyfinApi.itemsApi.getItems(
|
jellyfinApi.itemsApi
|
||||||
|
.getItems(
|
||||||
jellyfinApi.userId!!,
|
jellyfinApi.userId!!,
|
||||||
parentId = parentId,
|
parentId = parentId,
|
||||||
includeItemTypes = includeTypes,
|
includeItemTypes = includeTypes,
|
||||||
|
@ -151,9 +167,10 @@ class JellyfinRepositoryImpl(
|
||||||
recursive: Boolean,
|
recursive: Boolean,
|
||||||
sortBy: SortBy,
|
sortBy: SortBy,
|
||||||
sortOrder: SortOrder,
|
sortOrder: SortOrder,
|
||||||
): Flow<PagingData<FindroidItem>> {
|
): Flow<PagingData<FindroidItem>> =
|
||||||
return Pager(
|
Pager(
|
||||||
config = PagingConfig(
|
config =
|
||||||
|
PagingConfig(
|
||||||
pageSize = 10,
|
pageSize = 10,
|
||||||
maxSize = 100,
|
maxSize = 100,
|
||||||
enablePlaceholders = false,
|
enablePlaceholders = false,
|
||||||
|
@ -169,14 +186,15 @@ class JellyfinRepositoryImpl(
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
).flow
|
).flow
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun getPersonItems(
|
override suspend fun getPersonItems(
|
||||||
personIds: List<UUID>,
|
personIds: List<UUID>,
|
||||||
includeTypes: List<BaseItemKind>?,
|
includeTypes: List<BaseItemKind>?,
|
||||||
recursive: Boolean,
|
recursive: Boolean,
|
||||||
): List<FindroidItem> = withContext(Dispatchers.IO) {
|
): List<FindroidItem> =
|
||||||
jellyfinApi.itemsApi.getItems(
|
withContext(Dispatchers.IO) {
|
||||||
|
jellyfinApi.itemsApi
|
||||||
|
.getItems(
|
||||||
jellyfinApi.userId!!,
|
jellyfinApi.userId!!,
|
||||||
personIds = personIds,
|
personIds = personIds,
|
||||||
includeItemTypes = includeTypes,
|
includeItemTypes = includeTypes,
|
||||||
|
@ -190,10 +208,12 @@ class JellyfinRepositoryImpl(
|
||||||
|
|
||||||
override suspend fun getFavoriteItems(): List<FindroidItem> =
|
override suspend fun getFavoriteItems(): List<FindroidItem> =
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
jellyfinApi.itemsApi.getItems(
|
jellyfinApi.itemsApi
|
||||||
|
.getItems(
|
||||||
jellyfinApi.userId!!,
|
jellyfinApi.userId!!,
|
||||||
filters = listOf(ItemFilter.IS_FAVORITE),
|
filters = listOf(ItemFilter.IS_FAVORITE),
|
||||||
includeItemTypes = listOf(
|
includeItemTypes =
|
||||||
|
listOf(
|
||||||
BaseItemKind.MOVIE,
|
BaseItemKind.MOVIE,
|
||||||
BaseItemKind.SERIES,
|
BaseItemKind.SERIES,
|
||||||
BaseItemKind.EPISODE,
|
BaseItemKind.EPISODE,
|
||||||
|
@ -206,10 +226,12 @@ class JellyfinRepositoryImpl(
|
||||||
|
|
||||||
override suspend fun getSearchItems(searchQuery: String): List<FindroidItem> =
|
override suspend fun getSearchItems(searchQuery: String): List<FindroidItem> =
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
jellyfinApi.itemsApi.getItems(
|
jellyfinApi.itemsApi
|
||||||
|
.getItems(
|
||||||
jellyfinApi.userId!!,
|
jellyfinApi.userId!!,
|
||||||
searchTerm = searchQuery,
|
searchTerm = searchQuery,
|
||||||
includeItemTypes = listOf(
|
includeItemTypes =
|
||||||
|
listOf(
|
||||||
BaseItemKind.MOVIE,
|
BaseItemKind.MOVIE,
|
||||||
BaseItemKind.SERIES,
|
BaseItemKind.SERIES,
|
||||||
BaseItemKind.EPISODE,
|
BaseItemKind.EPISODE,
|
||||||
|
@ -221,12 +243,15 @@ class JellyfinRepositoryImpl(
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun getResumeItems(): List<FindroidItem> {
|
override suspend fun getResumeItems(): List<FindroidItem> {
|
||||||
val items = withContext(Dispatchers.IO) {
|
val items =
|
||||||
jellyfinApi.itemsApi.getResumeItems(
|
withContext(Dispatchers.IO) {
|
||||||
|
jellyfinApi.itemsApi
|
||||||
|
.getResumeItems(
|
||||||
jellyfinApi.userId!!,
|
jellyfinApi.userId!!,
|
||||||
limit = 12,
|
limit = 12,
|
||||||
includeItemTypes = listOf(BaseItemKind.MOVIE, BaseItemKind.EPISODE),
|
includeItemTypes = listOf(BaseItemKind.MOVIE, BaseItemKind.EPISODE),
|
||||||
).content.items.orEmpty()
|
).content.items
|
||||||
|
.orEmpty()
|
||||||
}
|
}
|
||||||
return items.mapNotNull {
|
return items.mapNotNull {
|
||||||
it.toFindroidItem(this, database)
|
it.toFindroidItem(this, database)
|
||||||
|
@ -234,8 +259,10 @@ class JellyfinRepositoryImpl(
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun getLatestMedia(parentId: UUID): List<FindroidItem> {
|
override suspend fun getLatestMedia(parentId: UUID): List<FindroidItem> {
|
||||||
val items = withContext(Dispatchers.IO) {
|
val items =
|
||||||
jellyfinApi.userLibraryApi.getLatestMedia(
|
withContext(Dispatchers.IO) {
|
||||||
|
jellyfinApi.userLibraryApi
|
||||||
|
.getLatestMedia(
|
||||||
jellyfinApi.userId!!,
|
jellyfinApi.userId!!,
|
||||||
parentId = parentId,
|
parentId = parentId,
|
||||||
limit = 16,
|
limit = 16,
|
||||||
|
@ -246,10 +273,15 @@ class JellyfinRepositoryImpl(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun getSeasons(seriesId: UUID, offline: Boolean): List<FindroidSeason> =
|
override suspend fun getSeasons(
|
||||||
|
seriesId: UUID,
|
||||||
|
offline: Boolean,
|
||||||
|
): List<FindroidSeason> =
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
if (!offline) {
|
if (!offline) {
|
||||||
jellyfinApi.showsApi.getSeasons(seriesId, jellyfinApi.userId!!).content.items
|
jellyfinApi.showsApi
|
||||||
|
.getSeasons(seriesId, jellyfinApi.userId!!)
|
||||||
|
.content.items
|
||||||
.orEmpty()
|
.orEmpty()
|
||||||
.map { it.toFindroidSeason(this@JellyfinRepositoryImpl) }
|
.map { it.toFindroidSeason(this@JellyfinRepositoryImpl) }
|
||||||
} else {
|
} else {
|
||||||
|
@ -259,7 +291,8 @@ class JellyfinRepositoryImpl(
|
||||||
|
|
||||||
override suspend fun getNextUp(seriesId: UUID?): List<FindroidEpisode> =
|
override suspend fun getNextUp(seriesId: UUID?): List<FindroidEpisode> =
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
jellyfinApi.showsApi.getNextUp(
|
jellyfinApi.showsApi
|
||||||
|
.getNextUp(
|
||||||
jellyfinApi.userId!!,
|
jellyfinApi.userId!!,
|
||||||
limit = 24,
|
limit = 24,
|
||||||
seriesId = seriesId,
|
seriesId = seriesId,
|
||||||
|
@ -279,7 +312,8 @@ class JellyfinRepositoryImpl(
|
||||||
): List<FindroidEpisode> =
|
): List<FindroidEpisode> =
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
if (!offline) {
|
if (!offline) {
|
||||||
jellyfinApi.showsApi.getEpisodes(
|
jellyfinApi.showsApi
|
||||||
|
.getEpisodes(
|
||||||
seriesId,
|
seriesId,
|
||||||
jellyfinApi.userId!!,
|
jellyfinApi.userId!!,
|
||||||
seasonId = seasonId,
|
seasonId = seasonId,
|
||||||
|
@ -294,33 +328,41 @@ class JellyfinRepositoryImpl(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun getMediaSources(itemId: UUID, includePath: Boolean): List<FindroidSource> =
|
override suspend fun getMediaSources(
|
||||||
|
itemId: UUID,
|
||||||
|
includePath: Boolean,
|
||||||
|
): List<FindroidSource> =
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
val sources = mutableListOf<FindroidSource>()
|
val sources = mutableListOf<FindroidSource>()
|
||||||
sources.addAll(
|
sources.addAll(
|
||||||
jellyfinApi.mediaInfoApi.getPostedPlaybackInfo(
|
jellyfinApi.mediaInfoApi
|
||||||
|
.getPostedPlaybackInfo(
|
||||||
itemId,
|
itemId,
|
||||||
PlaybackInfoDto(
|
PlaybackInfoDto(
|
||||||
userId = jellyfinApi.userId!!,
|
userId = jellyfinApi.userId!!,
|
||||||
deviceProfile = DeviceProfile(
|
deviceProfile =
|
||||||
|
DeviceProfile(
|
||||||
name = "Direct play all",
|
name = "Direct play all",
|
||||||
maxStaticBitrate = 1_000_000_000,
|
maxStaticBitrate = 1_000_000_000,
|
||||||
maxStreamingBitrate = 1_000_000_000,
|
maxStreamingBitrate = 1_000_000_000,
|
||||||
codecProfiles = emptyList(),
|
codecProfiles = emptyList(),
|
||||||
containerProfiles = emptyList(),
|
containerProfiles = emptyList(),
|
||||||
directPlayProfiles = listOf(
|
directPlayProfiles =
|
||||||
|
listOf(
|
||||||
DirectPlayProfile(type = DlnaProfileType.VIDEO),
|
DirectPlayProfile(type = DlnaProfileType.VIDEO),
|
||||||
DirectPlayProfile(type = DlnaProfileType.AUDIO),
|
DirectPlayProfile(type = DlnaProfileType.AUDIO),
|
||||||
),
|
),
|
||||||
transcodingProfiles = emptyList(),
|
transcodingProfiles = emptyList(),
|
||||||
subtitleProfiles = listOf(
|
subtitleProfiles =
|
||||||
|
listOf(
|
||||||
SubtitleProfile("srt", SubtitleDeliveryMethod.EXTERNAL),
|
SubtitleProfile("srt", SubtitleDeliveryMethod.EXTERNAL),
|
||||||
SubtitleProfile("ass", SubtitleDeliveryMethod.EXTERNAL),
|
SubtitleProfile("ass", SubtitleDeliveryMethod.EXTERNAL),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
maxStreamingBitrate = 1_000_000_000,
|
maxStreamingBitrate = 1_000_000_000,
|
||||||
),
|
),
|
||||||
).content.mediaSources.map {
|
).content.mediaSources
|
||||||
|
.map {
|
||||||
it.toFindroidSource(
|
it.toFindroidSource(
|
||||||
this@JellyfinRepositoryImpl,
|
this@JellyfinRepositoryImpl,
|
||||||
itemId,
|
itemId,
|
||||||
|
@ -334,14 +376,18 @@ class JellyfinRepositoryImpl(
|
||||||
sources
|
sources
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun getStreamUrl(itemId: UUID, mediaSourceId: String, playSessionId: String?): String =
|
override suspend fun getStreamUrl(
|
||||||
|
itemId: UUID,
|
||||||
|
mediaSourceId: String,
|
||||||
|
playSessionId: String?,
|
||||||
|
): String =
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
try {
|
try {
|
||||||
jellyfinApi.videosApi.getVideoStreamUrl(
|
jellyfinApi.videosApi.getVideoStreamUrl(
|
||||||
itemId,
|
itemId,
|
||||||
static = true,
|
static = true,
|
||||||
mediaSourceId = mediaSourceId,
|
mediaSourceId = mediaSourceId,
|
||||||
playSessionId = playSessionId
|
playSessionId = playSessionId,
|
||||||
)
|
)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Timber.e(e)
|
Timber.e(e)
|
||||||
|
@ -362,7 +408,8 @@ class JellyfinRepositoryImpl(
|
||||||
pathParameters["itemId"] = itemId
|
pathParameters["itemId"] = itemId
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return@withContext jellyfinApi.api.get<Intro>(
|
return@withContext jellyfinApi.api
|
||||||
|
.get<Intro>(
|
||||||
"/Episode/{itemId}/IntroTimestamps/v1",
|
"/Episode/{itemId}/IntroTimestamps/v1",
|
||||||
pathParameters,
|
pathParameters,
|
||||||
).content
|
).content
|
||||||
|
@ -371,7 +418,11 @@ class JellyfinRepositoryImpl(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun getTrickplayData(itemId: UUID, width: Int, index: Int): ByteArray? =
|
override suspend fun getTrickplayData(
|
||||||
|
itemId: UUID,
|
||||||
|
width: Int,
|
||||||
|
index: Int,
|
||||||
|
): ByteArray? =
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
try {
|
try {
|
||||||
try {
|
try {
|
||||||
|
@ -379,9 +430,13 @@ class JellyfinRepositoryImpl(
|
||||||
if (sources != null) {
|
if (sources != null) {
|
||||||
return@withContext File(sources.first(), index.toString()).readBytes()
|
return@withContext File(sources.first(), index.toString()).readBytes()
|
||||||
}
|
}
|
||||||
} catch (_: Exception) { }
|
} catch (_: Exception) {
|
||||||
|
}
|
||||||
|
|
||||||
return@withContext jellyfinApi.trickplayApi.getTrickplayTileImage(itemId, width, index).content.toByteArray()
|
return@withContext jellyfinApi.trickplayApi
|
||||||
|
.getTrickplayTileImage(itemId, width, index)
|
||||||
|
.content
|
||||||
|
.toByteArray()
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
return@withContext null
|
return@withContext null
|
||||||
}
|
}
|
||||||
|
@ -392,7 +447,8 @@ class JellyfinRepositoryImpl(
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
jellyfinApi.sessionApi.postCapabilities(
|
jellyfinApi.sessionApi.postCapabilities(
|
||||||
playableMediaTypes = listOf(MediaType.VIDEO),
|
playableMediaTypes = listOf(MediaType.VIDEO),
|
||||||
supportedCommands = listOf(
|
supportedCommands =
|
||||||
|
listOf(
|
||||||
GeneralCommandType.VOLUME_UP,
|
GeneralCommandType.VOLUME_UP,
|
||||||
GeneralCommandType.VOLUME_DOWN,
|
GeneralCommandType.VOLUME_DOWN,
|
||||||
GeneralCommandType.TOGGLE_MUTE,
|
GeneralCommandType.TOGGLE_MUTE,
|
||||||
|
@ -528,57 +584,66 @@ class JellyfinRepositoryImpl(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun getUserConfiguration(): UserConfiguration = withContext(Dispatchers.IO) {
|
override suspend fun getUserConfiguration(): UserConfiguration =
|
||||||
jellyfinApi.userApi.getCurrentUser().content.configuration!!
|
withContext(Dispatchers.IO) {
|
||||||
|
jellyfinApi.userApi
|
||||||
|
.getCurrentUser()
|
||||||
|
.content.configuration!!
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun getDownloads(): List<FindroidItem> =
|
override suspend fun getDownloads(): List<FindroidItem> =
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
val items = mutableListOf<FindroidItem>()
|
val items = mutableListOf<FindroidItem>()
|
||||||
items.addAll(
|
items.addAll(
|
||||||
database.getMoviesByServerId(appPreferences.currentServer!!)
|
database
|
||||||
|
.getMoviesByServerId(appPreferences.currentServer!!)
|
||||||
.map { it.toFindroidMovie(database, jellyfinApi.userId!!) },
|
.map { it.toFindroidMovie(database, jellyfinApi.userId!!) },
|
||||||
)
|
)
|
||||||
items.addAll(
|
items.addAll(
|
||||||
database.getShowsByServerId(appPreferences.currentServer!!)
|
database
|
||||||
|
.getShowsByServerId(appPreferences.currentServer!!)
|
||||||
.map { it.toFindroidShow(database, jellyfinApi.userId!!) },
|
.map { it.toFindroidShow(database, jellyfinApi.userId!!) },
|
||||||
)
|
)
|
||||||
items
|
items
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getUserId(): UUID {
|
override fun getUserId(): UUID = jellyfinApi.userId!!
|
||||||
return jellyfinApi.userId!!
|
|
||||||
}
|
|
||||||
|
|
||||||
|
override suspend fun getVideoTranscodeBitRate(transcodeResolution: Int): Pair<Int, Int> =
|
||||||
override suspend fun getVideoTranscodeBitRate(transcodeResolution: Int): Pair<Int, Int> {
|
when (transcodeResolution) {
|
||||||
return when (transcodeResolution) {
|
|
||||||
1080 -> 8000000 to 384000 // Adjusted for personal can be other values
|
1080 -> 8000000 to 384000 // Adjusted for personal can be other values
|
||||||
720 -> 2000000 to 384000 // 720p
|
720 -> 2000000 to 384000 // 720p
|
||||||
480 -> 1000000 to 384000 // 480p
|
480 -> 1000000 to 384000 // 480p
|
||||||
360 -> 800000 to 128000 // 360p
|
360 -> 800000 to 128000 // 360p
|
||||||
else -> 12000000 to 384000 // its adaptive but setting max here
|
else -> 12000000 to 384000 // its adaptive but setting max here
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun buildDeviceProfile(maxBitrate: Int, container: String, context: EncodingContext): DeviceProfile {
|
override suspend fun buildDeviceProfile(
|
||||||
val deviceProfile = ClientCapabilitiesDto(
|
maxBitrate: Int,
|
||||||
|
container: String,
|
||||||
|
context: EncodingContext,
|
||||||
|
): DeviceProfile {
|
||||||
|
val deviceProfile =
|
||||||
|
ClientCapabilitiesDto(
|
||||||
supportedCommands = emptyList(),
|
supportedCommands = emptyList(),
|
||||||
playableMediaTypes = emptyList(),
|
playableMediaTypes = emptyList(),
|
||||||
supportsMediaControl = true,
|
supportsMediaControl = true,
|
||||||
supportsPersistentIdentifier = true,
|
supportsPersistentIdentifier = true,
|
||||||
deviceProfile = DeviceProfile(
|
deviceProfile =
|
||||||
|
DeviceProfile(
|
||||||
name = "AnanasUser",
|
name = "AnanasUser",
|
||||||
id = getUserId().toString(),
|
id = getUserId().toString(),
|
||||||
maxStaticBitrate = maxBitrate,
|
maxStaticBitrate = maxBitrate,
|
||||||
maxStreamingBitrate = maxBitrate,
|
maxStreamingBitrate = maxBitrate,
|
||||||
codecProfiles = emptyList(),
|
codecProfiles = emptyList(),
|
||||||
containerProfiles = listOf(),
|
containerProfiles = listOf(),
|
||||||
directPlayProfiles = listOf(
|
directPlayProfiles =
|
||||||
|
listOf(
|
||||||
DirectPlayProfile(type = DlnaProfileType.VIDEO),
|
DirectPlayProfile(type = DlnaProfileType.VIDEO),
|
||||||
DirectPlayProfile(type = DlnaProfileType.AUDIO),
|
DirectPlayProfile(type = DlnaProfileType.AUDIO),
|
||||||
),
|
),
|
||||||
transcodingProfiles = listOf(
|
transcodingProfiles =
|
||||||
|
listOf(
|
||||||
TranscodingProfile(
|
TranscodingProfile(
|
||||||
container = container,
|
container = container,
|
||||||
context = context,
|
context = context,
|
||||||
|
@ -586,20 +651,22 @@ class JellyfinRepositoryImpl(
|
||||||
audioCodec = "aac,ac3,eac3",
|
audioCodec = "aac,ac3,eac3",
|
||||||
videoCodec = "hevc,h264",
|
videoCodec = "hevc,h264",
|
||||||
type = DlnaProfileType.VIDEO,
|
type = DlnaProfileType.VIDEO,
|
||||||
conditions = listOf(
|
conditions =
|
||||||
|
listOf(
|
||||||
ProfileCondition(
|
ProfileCondition(
|
||||||
condition = ProfileConditionType.LESS_THAN_EQUAL,
|
condition = ProfileConditionType.LESS_THAN_EQUAL,
|
||||||
property = ProfileConditionValue.VIDEO_BITRATE,
|
property = ProfileConditionValue.VIDEO_BITRATE,
|
||||||
value = "8000000",
|
value = "8000000",
|
||||||
isRequired = true,
|
isRequired = true,
|
||||||
)
|
),
|
||||||
),
|
),
|
||||||
copyTimestamps = true,
|
copyTimestamps = true,
|
||||||
enableSubtitlesInManifest = true,
|
enableSubtitlesInManifest = true,
|
||||||
transcodeSeekInfo = TranscodeSeekInfo.AUTO,
|
transcodeSeekInfo = TranscodeSeekInfo.AUTO,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
subtitleProfiles = listOf(
|
subtitleProfiles =
|
||||||
|
listOf(
|
||||||
SubtitleProfile("srt", SubtitleDeliveryMethod.EXTERNAL),
|
SubtitleProfile("srt", SubtitleDeliveryMethod.EXTERNAL),
|
||||||
SubtitleProfile("ass", SubtitleDeliveryMethod.EXTERNAL),
|
SubtitleProfile("ass", SubtitleDeliveryMethod.EXTERNAL),
|
||||||
SubtitleProfile("sub", SubtitleDeliveryMethod.EXTERNAL),
|
SubtitleProfile("sub", SubtitleDeliveryMethod.EXTERNAL),
|
||||||
|
@ -607,16 +674,21 @@ class JellyfinRepositoryImpl(
|
||||||
SubtitleProfile("ssa", SubtitleDeliveryMethod.EXTERNAL),
|
SubtitleProfile("ssa", SubtitleDeliveryMethod.EXTERNAL),
|
||||||
SubtitleProfile("pgs", SubtitleDeliveryMethod.EXTERNAL),
|
SubtitleProfile("pgs", SubtitleDeliveryMethod.EXTERNAL),
|
||||||
SubtitleProfile("dvb_teletext", SubtitleDeliveryMethod.EXTERNAL),
|
SubtitleProfile("dvb_teletext", SubtitleDeliveryMethod.EXTERNAL),
|
||||||
SubtitleProfile("dvd_subtitle", SubtitleDeliveryMethod.EXTERNAL)
|
SubtitleProfile("dvd_subtitle", SubtitleDeliveryMethod.EXTERNAL),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
)
|
|
||||||
)
|
)
|
||||||
return deviceProfile.deviceProfile!!
|
return deviceProfile.deviceProfile!!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override suspend fun getPostedPlaybackInfo(
|
||||||
override suspend fun getPostedPlaybackInfo(itemId: UUID ,enableDirectStream: Boolean ,deviceProfile: DeviceProfile ,maxBitrate: Int): Response<PlaybackInfoResponse> {
|
itemId: UUID,
|
||||||
val playbackInfo = jellyfinApi.mediaInfoApi.getPostedPlaybackInfo(
|
enableDirectStream: Boolean,
|
||||||
|
deviceProfile: DeviceProfile,
|
||||||
|
maxBitrate: Int,
|
||||||
|
): Response<PlaybackInfoResponse> {
|
||||||
|
val playbackInfo =
|
||||||
|
jellyfinApi.mediaInfoApi.getPostedPlaybackInfo(
|
||||||
itemId = itemId,
|
itemId = itemId,
|
||||||
PlaybackInfoDto(
|
PlaybackInfoDto(
|
||||||
userId = jellyfinApi.userId!!,
|
userId = jellyfinApi.userId!!,
|
||||||
|
@ -628,13 +700,21 @@ class JellyfinRepositoryImpl(
|
||||||
allowAudioStreamCopy = true,
|
allowAudioStreamCopy = true,
|
||||||
allowVideoStreamCopy = true,
|
allowVideoStreamCopy = true,
|
||||||
maxStreamingBitrate = maxBitrate,
|
maxStreamingBitrate = maxBitrate,
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
return playbackInfo
|
return playbackInfo
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun getVideoStreambyContainerUrl(itemId: UUID, deviceId: String, mediaSourceId: String, playSessionId: String, videoBitrate: Int, container: String): String {
|
override suspend fun getVideoStreambyContainerUrl(
|
||||||
val url = jellyfinApi.videosApi.getVideoStreamByContainerUrl(
|
itemId: UUID,
|
||||||
|
deviceId: String,
|
||||||
|
mediaSourceId: String,
|
||||||
|
playSessionId: String,
|
||||||
|
videoBitrate: Int,
|
||||||
|
container: String,
|
||||||
|
): String {
|
||||||
|
val url =
|
||||||
|
jellyfinApi.videosApi.getVideoStreamByContainerUrl(
|
||||||
itemId,
|
itemId,
|
||||||
static = false,
|
static = false,
|
||||||
deviceId = deviceId,
|
deviceId = deviceId,
|
||||||
|
@ -647,14 +727,21 @@ class JellyfinRepositoryImpl(
|
||||||
container = container,
|
container = container,
|
||||||
startTimeTicks = 0,
|
startTimeTicks = 0,
|
||||||
copyTimestamps = true,
|
copyTimestamps = true,
|
||||||
subtitleMethod = SubtitleDeliveryMethod.EXTERNAL
|
subtitleMethod = SubtitleDeliveryMethod.EXTERNAL,
|
||||||
)
|
)
|
||||||
return url
|
return url
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun getTranscodedVideoStream(itemId: UUID, deviceId: String, mediaSourceId: String, playSessionId: String, videoBitrate: Int): String {
|
override suspend fun getTranscodedVideoStream(
|
||||||
|
itemId: UUID,
|
||||||
|
deviceId: String,
|
||||||
|
mediaSourceId: String,
|
||||||
|
playSessionId: String,
|
||||||
|
videoBitrate: Int,
|
||||||
|
): String {
|
||||||
val isAuto = videoBitrate == 12000000
|
val isAuto = videoBitrate == 12000000
|
||||||
val url = if (!isAuto) {
|
val url =
|
||||||
|
if (!isAuto) {
|
||||||
jellyfinApi.api.dynamicHlsApi.getMasterHlsVideoPlaylistUrl(
|
jellyfinApi.api.dynamicHlsApi.getMasterHlsVideoPlaylistUrl(
|
||||||
itemId,
|
itemId,
|
||||||
static = false,
|
static = false,
|
||||||
|
@ -663,7 +750,7 @@ class JellyfinRepositoryImpl(
|
||||||
playSessionId = playSessionId,
|
playSessionId = playSessionId,
|
||||||
videoBitRate = videoBitrate,
|
videoBitRate = videoBitrate,
|
||||||
enableAdaptiveBitrateStreaming = false,
|
enableAdaptiveBitrateStreaming = false,
|
||||||
audioBitRate = 384000, //could also be passed with audioBitrate but i preferred not as its not much data anyways
|
audioBitRate = 384000, // could also be passed with audioBitrate but i preferred not as its not much data anyways
|
||||||
videoCodec = "hevc,h264",
|
videoCodec = "hevc,h264",
|
||||||
audioCodec = "aac,ac3,eac3",
|
audioCodec = "aac,ac3,eac3",
|
||||||
startTimeTicks = 0,
|
startTimeTicks = 0,
|
||||||
|
@ -694,20 +781,18 @@ class JellyfinRepositoryImpl(
|
||||||
return url
|
return url
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
override suspend fun getDeviceId(): String {
|
override suspend fun getDeviceId(): String {
|
||||||
val devices = jellyfinApi.devicesApi.getDevices(getUserId())
|
val devices = jellyfinApi.devicesApi.getDevices(getUserId())
|
||||||
return devices.content.items?.firstOrNull()?.id!!
|
return devices.content.items
|
||||||
|
?.firstOrNull()
|
||||||
|
?.id!!
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun stopEncodingProcess(playSessionId: String) {
|
override suspend fun stopEncodingProcess(playSessionId: String) {
|
||||||
val deviceId = getDeviceId()
|
val deviceId = getDeviceId()
|
||||||
jellyfinApi.api.hlsSegmentApi.stopEncodingProcess(
|
jellyfinApi.api.hlsSegmentApi.stopEncodingProcess(
|
||||||
deviceId = deviceId,
|
deviceId = deviceId,
|
||||||
playSessionId = playSessionId
|
playSessionId = playSessionId,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -42,14 +42,9 @@ class JellyfinRepositoryOfflineImpl(
|
||||||
private val database: ServerDatabaseDao,
|
private val database: ServerDatabaseDao,
|
||||||
private val appPreferences: AppPreferences,
|
private val appPreferences: AppPreferences,
|
||||||
) : JellyfinRepository {
|
) : JellyfinRepository {
|
||||||
|
override suspend fun getPublicSystemInfo(): PublicSystemInfo = throw Exception("System info not available in offline mode")
|
||||||
|
|
||||||
override suspend fun getPublicSystemInfo(): PublicSystemInfo {
|
override suspend fun getUserViews(): List<BaseItemDto> = emptyList()
|
||||||
throw Exception("System info not available in offline mode")
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun getUserViews(): List<BaseItemDto> {
|
|
||||||
return emptyList()
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun getItem(itemId: UUID): BaseItemDto {
|
override suspend fun getItem(itemId: UUID): BaseItemDto {
|
||||||
TODO("Not yet implemented")
|
TODO("Not yet implemented")
|
||||||
|
@ -113,36 +108,67 @@ class JellyfinRepositoryOfflineImpl(
|
||||||
TODO("Not yet implemented")
|
TODO("Not yet implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun getSearchItems(searchQuery: String): List<FindroidItem> {
|
override suspend fun getSearchItems(searchQuery: String): List<FindroidItem> =
|
||||||
return withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
val movies = database.searchMovies(appPreferences.currentServer!!, searchQuery).map { it.toFindroidMovie(database, jellyfinApi.userId!!) }
|
val movies =
|
||||||
val shows = database.searchShows(appPreferences.currentServer!!, searchQuery).map { it.toFindroidShow(database, jellyfinApi.userId!!) }
|
database.searchMovies(appPreferences.currentServer!!, searchQuery).map {
|
||||||
val episodes = database.searchEpisodes(appPreferences.currentServer!!, searchQuery).map { it.toFindroidEpisode(database, jellyfinApi.userId!!) }
|
it.toFindroidMovie(
|
||||||
|
database,
|
||||||
|
@Suppress("ktlint:standard:max-line-length")
|
||||||
|
jellyfinApi.userId!!,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
val shows =
|
||||||
|
database
|
||||||
|
.searchShows(
|
||||||
|
appPreferences.currentServer!!,
|
||||||
|
searchQuery,
|
||||||
|
).map { it.toFindroidShow(database, jellyfinApi.userId!!) }
|
||||||
|
val episodes =
|
||||||
|
database.searchEpisodes(appPreferences.currentServer!!, searchQuery).map {
|
||||||
|
it.toFindroidEpisode(database, jellyfinApi.userId!!)
|
||||||
|
}
|
||||||
movies + shows + episodes
|
movies + shows + episodes
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun getResumeItems(): List<FindroidItem> {
|
override suspend fun getResumeItems(): List<FindroidItem> =
|
||||||
return withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
val movies = database.getMoviesByServerId(appPreferences.currentServer!!).map { it.toFindroidMovie(database, jellyfinApi.userId!!) }.filter { it.playbackPositionTicks > 0 }
|
val movies =
|
||||||
val episodes = database.getEpisodesByServerId(appPreferences.currentServer!!).map { it.toFindroidEpisode(database, jellyfinApi.userId!!) }.filter { it.playbackPositionTicks > 0 }
|
database
|
||||||
|
.getMoviesByServerId(
|
||||||
|
appPreferences.currentServer!!,
|
||||||
|
).map { it.toFindroidMovie(database, jellyfinApi.userId!!) }
|
||||||
|
.filter {
|
||||||
|
it.playbackPositionTicks >
|
||||||
|
0
|
||||||
|
}
|
||||||
|
val episodes =
|
||||||
|
database
|
||||||
|
.getEpisodesByServerId(
|
||||||
|
appPreferences.currentServer!!,
|
||||||
|
).map { it.toFindroidEpisode(database, jellyfinApi.userId!!) }
|
||||||
|
.filter {
|
||||||
|
it.playbackPositionTicks >
|
||||||
|
0
|
||||||
|
}
|
||||||
movies + episodes
|
movies + episodes
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun getLatestMedia(parentId: UUID): List<FindroidItem> {
|
override suspend fun getLatestMedia(parentId: UUID): List<FindroidItem> = emptyList()
|
||||||
return emptyList()
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun getSeasons(seriesId: UUID, offline: Boolean): List<FindroidSeason> =
|
override suspend fun getSeasons(
|
||||||
|
seriesId: UUID,
|
||||||
|
offline: Boolean,
|
||||||
|
): List<FindroidSeason> =
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
database.getSeasonsByShowId(seriesId).map { it.toFindroidSeason(database, jellyfinApi.userId!!) }
|
database.getSeasonsByShowId(seriesId).map { it.toFindroidSeason(database, jellyfinApi.userId!!) }
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun getNextUp(seriesId: UUID?): List<FindroidEpisode> {
|
override suspend fun getNextUp(seriesId: UUID?): List<FindroidEpisode> =
|
||||||
return withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
val result = mutableListOf<FindroidEpisode>()
|
val result = mutableListOf<FindroidEpisode>()
|
||||||
val shows = database.getShowsByServerId(appPreferences.currentServer!!).filter {
|
val shows =
|
||||||
|
database.getShowsByServerId(appPreferences.currentServer!!).filter {
|
||||||
if (seriesId != null) it.id == seriesId else true
|
if (seriesId != null) it.id == seriesId else true
|
||||||
}
|
}
|
||||||
for (show in shows) {
|
for (show in shows) {
|
||||||
|
@ -156,7 +182,6 @@ class JellyfinRepositoryOfflineImpl(
|
||||||
}
|
}
|
||||||
result.filter { it.playbackPositionTicks == 0L }
|
result.filter { it.playbackPositionTicks == 0L }
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun getEpisodes(
|
override suspend fun getEpisodes(
|
||||||
seriesId: UUID,
|
seriesId: UUID,
|
||||||
|
@ -172,12 +197,19 @@ class JellyfinRepositoryOfflineImpl(
|
||||||
items
|
items
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun getMediaSources(itemId: UUID, includePath: Boolean): List<FindroidSource> =
|
override suspend fun getMediaSources(
|
||||||
|
itemId: UUID,
|
||||||
|
includePath: Boolean,
|
||||||
|
): List<FindroidSource> =
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
database.getSources(itemId).map { it.toFindroidSource(database) }
|
database.getSources(itemId).map { it.toFindroidSource(database) }
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun getStreamUrl(itemId: UUID, mediaSourceId: String, playSessionId: String?): String {
|
override suspend fun getStreamUrl(
|
||||||
|
itemId: UUID,
|
||||||
|
mediaSourceId: String,
|
||||||
|
playSessionId: String?,
|
||||||
|
): String {
|
||||||
TODO("Not yet implemented")
|
TODO("Not yet implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -186,7 +218,11 @@ class JellyfinRepositoryOfflineImpl(
|
||||||
database.getIntro(itemId)?.toIntro()
|
database.getIntro(itemId)?.toIntro()
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun getTrickplayData(itemId: UUID, width: Int, index: Int): ByteArray? =
|
override suspend fun getTrickplayData(
|
||||||
|
itemId: UUID,
|
||||||
|
width: Int,
|
||||||
|
index: Int,
|
||||||
|
): ByteArray? =
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
try {
|
try {
|
||||||
val sources = File(context.filesDir, "trickplay/$itemId").listFiles() ?: return@withContext null
|
val sources = File(context.filesDir, "trickplay/$itemId").listFiles() ?: return@withContext null
|
||||||
|
@ -200,7 +236,11 @@ class JellyfinRepositoryOfflineImpl(
|
||||||
|
|
||||||
override suspend fun postPlaybackStart(itemId: UUID) {}
|
override suspend fun postPlaybackStart(itemId: UUID) {}
|
||||||
|
|
||||||
override suspend fun postPlaybackStop(itemId: UUID, positionTicks: Long, playedPercentage: Int) {
|
override suspend fun postPlaybackStop(
|
||||||
|
itemId: UUID,
|
||||||
|
positionTicks: Long,
|
||||||
|
playedPercentage: Int,
|
||||||
|
) {
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
when {
|
when {
|
||||||
playedPercentage < 10 -> {
|
playedPercentage < 10 -> {
|
||||||
|
@ -260,35 +300,31 @@ class JellyfinRepositoryOfflineImpl(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getBaseUrl(): String {
|
override fun getBaseUrl(): String = ""
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun updateDeviceName(name: String) {
|
override suspend fun updateDeviceName(name: String) {
|
||||||
TODO("Not yet implemented")
|
TODO("Not yet implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun getUserConfiguration(): UserConfiguration? {
|
override suspend fun getUserConfiguration(): UserConfiguration? = null
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun getDownloads(): List<FindroidItem> =
|
override suspend fun getDownloads(): List<FindroidItem> =
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
val items = mutableListOf<FindroidItem>()
|
val items = mutableListOf<FindroidItem>()
|
||||||
items.addAll(
|
items.addAll(
|
||||||
database.getMoviesByServerId(appPreferences.currentServer!!)
|
database
|
||||||
|
.getMoviesByServerId(appPreferences.currentServer!!)
|
||||||
.map { it.toFindroidMovie(database, jellyfinApi.userId!!) },
|
.map { it.toFindroidMovie(database, jellyfinApi.userId!!) },
|
||||||
)
|
)
|
||||||
items.addAll(
|
items.addAll(
|
||||||
database.getShowsByServerId(appPreferences.currentServer!!)
|
database
|
||||||
|
.getShowsByServerId(appPreferences.currentServer!!)
|
||||||
.map { it.toFindroidShow(database, jellyfinApi.userId!!) },
|
.map { it.toFindroidShow(database, jellyfinApi.userId!!) },
|
||||||
)
|
)
|
||||||
items
|
items
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getUserId(): UUID {
|
override fun getUserId(): UUID = jellyfinApi.userId!!
|
||||||
return jellyfinApi.userId!!
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun getDeviceId(): String {
|
override suspend fun getDeviceId(): String {
|
||||||
TODO("Not yet implemented")
|
TODO("Not yet implemented")
|
||||||
|
@ -301,7 +337,7 @@ class JellyfinRepositoryOfflineImpl(
|
||||||
override suspend fun buildDeviceProfile(
|
override suspend fun buildDeviceProfile(
|
||||||
maxBitrate: Int,
|
maxBitrate: Int,
|
||||||
container: String,
|
container: String,
|
||||||
context: EncodingContext
|
context: EncodingContext,
|
||||||
): DeviceProfile {
|
): DeviceProfile {
|
||||||
TODO("Not yet implemented")
|
TODO("Not yet implemented")
|
||||||
}
|
}
|
||||||
|
@ -312,7 +348,7 @@ class JellyfinRepositoryOfflineImpl(
|
||||||
mediaSourceId: String,
|
mediaSourceId: String,
|
||||||
playSessionId: String,
|
playSessionId: String,
|
||||||
videoBitrate: Int,
|
videoBitrate: Int,
|
||||||
container: String
|
container: String,
|
||||||
): String {
|
): String {
|
||||||
TODO("Not yet implemented")
|
TODO("Not yet implemented")
|
||||||
}
|
}
|
||||||
|
@ -322,7 +358,7 @@ class JellyfinRepositoryOfflineImpl(
|
||||||
deviceId: String,
|
deviceId: String,
|
||||||
mediaSourceId: String,
|
mediaSourceId: String,
|
||||||
playSessionId: String,
|
playSessionId: String,
|
||||||
videoBitrate: Int
|
videoBitrate: Int,
|
||||||
): String {
|
): String {
|
||||||
TODO("Not yet implemented")
|
TODO("Not yet implemented")
|
||||||
}
|
}
|
||||||
|
@ -331,7 +367,7 @@ class JellyfinRepositoryOfflineImpl(
|
||||||
itemId: UUID,
|
itemId: UUID,
|
||||||
enableDirectStream: Boolean,
|
enableDirectStream: Boolean,
|
||||||
deviceProfile: DeviceProfile,
|
deviceProfile: DeviceProfile,
|
||||||
maxBitrate: Int
|
maxBitrate: Int,
|
||||||
): Response<PlaybackInfoResponse> {
|
): Response<PlaybackInfoResponse> {
|
||||||
TODO("Not yet implemented")
|
TODO("Not yet implemented")
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue