Skip to content

Commit

Permalink
Ensure ExternalPlayer always direct plays
Browse files Browse the repository at this point in the history
Remove now unnecessary device profile, and set high bitrate limit so that we never exceed it.
  • Loading branch information
Maxr1998 committed Aug 31, 2023
1 parent bcf2dab commit f0705a4
Show file tree
Hide file tree
Showing 4 changed files with 17 additions and 90 deletions.
7 changes: 3 additions & 4 deletions app/src/main/assets/native/ExternalPlayerPlugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -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: []
};
}
Expand Down
63 changes: 10 additions & 53 deletions app/src/main/java/org/jellyfin/mobile/bridge/ExternalPlayer.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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

Expand Down Expand Up @@ -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 ->
Expand All @@ -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 ->
Expand Down Expand Up @@ -341,8 +302,4 @@ class ExternalPlayer(
else -> null
}
}

companion object {
const val DEVICE_PROFILE_NAME = "Android External Player"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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<String>, external: Array<String>): List<SubtitleProfile> = ArrayList<SubtitleProfile>().apply {
for (format in embedded) {
add(SubtitleProfile(format = format, method = SubtitleDeliveryMethod.EMBED))
Expand Down Expand Up @@ -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:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<JellyfinMediaSource> {
// Load media source info
val playSessionId: String
Expand All @@ -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,
),
)

Expand Down

0 comments on commit f0705a4

Please sign in to comment.