From f0705a4929ae602e31e42d8db0ba3340ac311b0d Mon Sep 17 00:00:00 2001 From: Maxr1998 Date: Thu, 31 Aug 2023 13:01:17 +0200 Subject: [PATCH] Ensure ExternalPlayer always direct plays Remove now unnecessary device profile, and set high bitrate limit so that we never exceed it. --- .../assets/native/ExternalPlayerPlugin.js | 7 +-- .../jellyfin/mobile/bridge/ExternalPlayer.kt | 63 +++---------------- .../deviceprofile/DeviceProfileBuilder.kt | 30 --------- .../player/source/MediaSourceResolver.kt | 7 ++- 4 files changed, 17 insertions(+), 90 deletions(-) diff --git a/app/src/main/assets/native/ExternalPlayerPlugin.js b/app/src/main/assets/native/ExternalPlayerPlugin.js index 77ba1cbc60..b7473481b1 100644 --- a/app/src/main/assets/native/ExternalPlayerPlugin.js +++ b/app/src/main/assets/native/ExternalPlayerPlugin.js @@ -141,12 +141,11 @@ export class ExternalPlayerPlugin { async getDeviceProfile() { return { Name: 'Android External Player Stub', - MaxStreamingBitrate: 100000000, - MaxStaticBitrate: 100000000, - MusicStreamingTranscodingBitrate: 320000, + MaxStreamingBitrate: 1000000000, + MaxStaticBitrate: 1000000000, DirectPlayProfiles: [{Type: 'Video'}, {Type: 'Audio'}], CodecProfiles: [], - SubtitleProfiles: JSON.parse(this._externalPlayer.getSubtitleProfiles()), + SubtitleProfiles: [{Method: 'Drop'}], TranscodingProfiles: [] }; } diff --git a/app/src/main/java/org/jellyfin/mobile/bridge/ExternalPlayer.kt b/app/src/main/java/org/jellyfin/mobile/bridge/ExternalPlayer.kt index 4c61a1bfa6..a6608559f9 100644 --- a/app/src/main/java/org/jellyfin/mobile/bridge/ExternalPlayer.kt +++ b/app/src/main/java/org/jellyfin/mobile/bridge/ExternalPlayer.kt @@ -16,7 +16,6 @@ import kotlinx.coroutines.launch import org.jellyfin.mobile.R import org.jellyfin.mobile.app.AppPreferences import org.jellyfin.mobile.player.PlayerException -import org.jellyfin.mobile.player.deviceprofile.DeviceProfileBuilder import org.jellyfin.mobile.player.interaction.PlayOptions import org.jellyfin.mobile.player.source.ExternalSubtitleStream import org.jellyfin.mobile.player.source.JellyfinMediaSource @@ -30,10 +29,7 @@ import org.jellyfin.mobile.webapp.WebappFunctionChannel import org.jellyfin.sdk.api.client.ApiClient import org.jellyfin.sdk.api.client.extensions.videosApi import org.jellyfin.sdk.api.operations.VideosApi -import org.jellyfin.sdk.model.api.DeviceProfile -import org.jellyfin.sdk.model.api.PlayMethod import org.jellyfin.sdk.model.serializer.toUUIDOrNull -import org.json.JSONArray import org.json.JSONObject import org.koin.core.component.KoinComponent import org.koin.core.component.get @@ -50,8 +46,6 @@ class ExternalPlayer( private val appPreferences: AppPreferences by inject() private val webappFunctionChannel: WebappFunctionChannel by inject() private val mediaSourceResolver: MediaSourceResolver by inject() - private val deviceProfileBuilder: DeviceProfileBuilder by inject() - private val externalPlayerProfile: DeviceProfile = deviceProfileBuilder.getExternalPlayerProfile() private val apiClient: ApiClient = get() private val videosApi: VideosApi = apiClient.videosApi @@ -95,14 +89,15 @@ class ExternalPlayer( } coroutinesScope.launch { + // Resolve media source to query info about external (subtitle) streams mediaSourceResolver.resolveMediaSource( itemId = itemId, mediaSourceId = playOptions.mediaSourceId, - deviceProfile = externalPlayerProfile, startTimeTicks = playOptions.startPositionTicks, audioStreamIndex = playOptions.audioStreamIndex, subtitleStreamIndex = playOptions.subtitleStreamIndex, - maxStreamingBitrate = null, + maxStreamingBitrate = Int.MAX_VALUE, // ensure we always direct play + autoOpenLiveStream = false, ).onSuccess { jellyfinMediaSource -> playMediaSource(playOptions, jellyfinMediaSource) }.onFailure { error -> @@ -116,48 +111,14 @@ class ExternalPlayer( } } - @JavascriptInterface - fun getSubtitleProfiles(): String { - val subtitleProfiles = JSONArray() - externalPlayerProfile.subtitleProfiles.forEach { profile -> - subtitleProfiles.put( - JSONObject().apply { - put("method", profile.method) - put("format", profile.format) - }, - ) - } - return subtitleProfiles.toString() - } - - @Suppress("LongMethod") private fun playMediaSource(playOptions: PlayOptions, source: JellyfinMediaSource) { - val sourceInfo = source.sourceInfo - val url = when (source.playMethod) { - PlayMethod.DIRECT_PLAY -> { - videosApi.getVideoStreamUrl( - itemId = source.itemId, - static = true, - mediaSourceId = source.id, - playSessionId = source.playSessionId, - ) - } - PlayMethod.DIRECT_STREAM -> { - val container = requireNotNull(sourceInfo.container) { "Missing direct stream container" } - videosApi.getVideoStreamByContainerUrl( - itemId = source.itemId, - container = container, - mediaSourceId = source.id, - playSessionId = source.playSessionId, - ) - } - PlayMethod.TRANSCODE -> { - Timber.d("Transcoding is not supported for External Player, ignoring…") - notifyEvent(Constants.EVENT_CANCELED) - context.toast(R.string.external_player_invalid_play_method, Toast.LENGTH_LONG) - return - } - } + // Create direct play URL + val url = videosApi.getVideoStreamUrl( + itemId = source.itemId, + static = true, + mediaSourceId = source.id, + playSessionId = source.playSessionId, + ) // Select correct subtitle val selectedSubtitleStream = playOptions.subtitleStreamIndex?.let { index -> @@ -341,8 +302,4 @@ class ExternalPlayer( else -> null } } - - companion object { - const val DEVICE_PROFILE_NAME = "Android External Player" - } } diff --git a/app/src/main/java/org/jellyfin/mobile/player/deviceprofile/DeviceProfileBuilder.kt b/app/src/main/java/org/jellyfin/mobile/player/deviceprofile/DeviceProfileBuilder.kt index 1b2ec9d111..b184011d6d 100644 --- a/app/src/main/java/org/jellyfin/mobile/player/deviceprofile/DeviceProfileBuilder.kt +++ b/app/src/main/java/org/jellyfin/mobile/player/deviceprofile/DeviceProfileBuilder.kt @@ -2,7 +2,6 @@ package org.jellyfin.mobile.player.deviceprofile import android.media.MediaCodecList import org.jellyfin.mobile.app.AppPreferences -import org.jellyfin.mobile.bridge.ExternalPlayer import org.jellyfin.mobile.utils.Constants import org.jellyfin.sdk.model.api.CodecProfile import org.jellyfin.sdk.model.api.ContainerProfile @@ -193,32 +192,6 @@ class DeviceProfileBuilder( ) } - fun getExternalPlayerProfile(): DeviceProfile = DeviceProfile( - name = ExternalPlayer.DEVICE_PROFILE_NAME, - directPlayProfiles = listOf( - DirectPlayProfile(type = DlnaProfileType.VIDEO), - DirectPlayProfile(type = DlnaProfileType.AUDIO), - ), - transcodingProfiles = emptyList(), - containerProfiles = emptyList(), - codecProfiles = emptyList(), - subtitleProfiles = getSubtitleProfiles(EXTERNAL_PLAYER_SUBTITLES, EXTERNAL_PLAYER_SUBTITLES), - maxStreamingBitrate = MAX_STREAMING_BITRATE, - - // TODO: remove redundant defaults after API/SDK is fixed - timelineOffsetSeconds = 0, - enableAlbumArtInDidl = false, - enableSingleAlbumArtLimit = false, - enableSingleSubtitleLimit = false, - supportedMediaTypes = "", - requiresPlainFolders = false, - requiresPlainVideoItems = false, - enableMsMediaReceiverRegistrar = false, - ignoreTranscodeByteRangeRequests = false, - xmlRootAttributes = emptyList(), - responseProfiles = emptyList(), - ) - private fun getSubtitleProfiles(embedded: Array, external: Array): List = ArrayList().apply { for (format in embedded) { add(SubtitleProfile(format = format, method = SubtitleDeliveryMethod.EMBED)) @@ -323,9 +296,6 @@ class DeviceProfileBuilder( private val EXO_EMBEDDED_SUBTITLES = arrayOf("dvbsub", "pgssub", "srt", "subrip", "ttml") private val EXO_EXTERNAL_SUBTITLES = arrayOf("srt", "subrip", "ttml", "vtt", "webvtt") private val SUBTITLES_SSA = arrayOf("ssa", "ass") - private val EXTERNAL_PLAYER_SUBTITLES = arrayOf( - "ass", "idx", "pgssub", "smi", "smil", "srt", "ssa", "sub", "subrip", "ttml", "vtt", "webvtt", - ) /** * Taken from Jellyfin Web: diff --git a/app/src/main/java/org/jellyfin/mobile/player/source/MediaSourceResolver.kt b/app/src/main/java/org/jellyfin/mobile/player/source/MediaSourceResolver.kt index 1de7a816d6..180aab093b 100644 --- a/app/src/main/java/org/jellyfin/mobile/player/source/MediaSourceResolver.kt +++ b/app/src/main/java/org/jellyfin/mobile/player/source/MediaSourceResolver.kt @@ -21,11 +21,12 @@ class MediaSourceResolver(private val apiClient: ApiClient) { suspend fun resolveMediaSource( itemId: UUID, mediaSourceId: String? = null, - deviceProfile: DeviceProfile, + deviceProfile: DeviceProfile? = null, maxStreamingBitrate: Int? = null, startTimeTicks: Long? = null, audioStreamIndex: Int? = null, subtitleStreamIndex: Int? = null, + autoOpenLiveStream: Boolean = true, ): Result { // Load media source info val playSessionId: String @@ -39,11 +40,11 @@ class MediaSourceResolver(private val apiClient: ApiClient) { // https://github.com/jellyfin/jellyfin/blob/9a35fd673203cfaf0098138b2768750f4818b3ab/Jellyfin.Api/Helpers/MediaInfoHelper.cs#L196-L201 mediaSourceId = mediaSourceId ?: itemId.toString().replace("-", ""), deviceProfile = deviceProfile, - maxStreamingBitrate = maxStreamingBitrate ?: deviceProfile.maxStreamingBitrate, + maxStreamingBitrate = maxStreamingBitrate, startTimeTicks = startTimeTicks, audioStreamIndex = audioStreamIndex, subtitleStreamIndex = subtitleStreamIndex, - autoOpenLiveStream = true, + autoOpenLiveStream = autoOpenLiveStream, ), )