Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix and rewrite audio and subtitle track selection when transcoding #931

Merged
merged 2 commits into from
Dec 27, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import org.jellyfin.sdk.api.operations.VideosApi
import org.jellyfin.sdk.model.api.DeviceProfile
import org.jellyfin.sdk.model.api.MediaStream
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
Expand Down Expand Up @@ -85,7 +86,9 @@ class ExternalPlayer(
@JavascriptInterface
fun initPlayer(args: String) {
val playOptions = PlayOptions.fromJson(JSONObject(args))
val itemId = playOptions?.run { mediaSourceId ?: ids.firstOrNull() }
val itemId = playOptions?.run {
ids.firstOrNull() ?: mediaSourceId?.toUUIDOrNull() // fallback if ids is empty
}
if (playOptions == null || itemId == null) {
context.toast(R.string.player_error_invalid_play_options)
return
Expand All @@ -94,6 +97,7 @@ class ExternalPlayer(
coroutinesScope.launch {
mediaSourceResolver.resolveMediaSource(
itemId = itemId,
mediaSourceId = playOptions.mediaSourceId,
deviceProfile = externalPlayerProfile,
startTimeTicks = playOptions.startPositionTicks,
audioStreamIndex = playOptions.audioStreamIndex,
Expand Down
56 changes: 35 additions & 21 deletions app/src/main/java/org/jellyfin/mobile/player/PlayerViewModel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import com.google.android.exoplayer2.analytics.DefaultAnalyticsCollector
import com.google.android.exoplayer2.mediacodec.MediaCodecDecoderException
import com.google.android.exoplayer2.mediacodec.MediaCodecInfo
import com.google.android.exoplayer2.mediacodec.MediaCodecSelector
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector
import com.google.android.exoplayer2.util.Clock
import com.google.android.exoplayer2.util.EventLogger
import com.google.android.exoplayer2.util.MimeTypes
Expand All @@ -40,6 +41,7 @@ import org.jellyfin.mobile.player.queue.QueueManager
import org.jellyfin.mobile.player.source.JellyfinMediaSource
import org.jellyfin.mobile.player.ui.DecoderType
import org.jellyfin.mobile.player.ui.DisplayPreferences
import org.jellyfin.mobile.player.ui.PlayState
import org.jellyfin.mobile.utils.Constants
import org.jellyfin.mobile.utils.Constants.SUPPORTED_VIDEO_PLAYER_PLAYBACK_ACTIONS
import org.jellyfin.mobile.utils.applyDefaultAudioAttributes
Expand Down Expand Up @@ -84,7 +86,8 @@ class PlayerViewModel(application: Application) : AndroidViewModel(application),
val notificationHelper: PlayerNotificationHelper by lazy { PlayerNotificationHelper(this) }

// Media source handling
val mediaQueueManager = QueueManager(this)
private val trackSelector = DefaultTrackSelector(getApplication())
val mediaQueueManager = QueueManager(this, trackSelector)
val mediaSourceOrNull: JellyfinMediaSource?
get() = mediaQueueManager.mediaQueue.value?.jellyfinMediaSource

Expand Down Expand Up @@ -213,7 +216,7 @@ class PlayerViewModel(application: Application) : AndroidViewModel(application),
}
_player.value = ExoPlayer.Builder(getApplication(), renderersFactory, get()).apply {
setUsePlatformDiagnostics(false)
setTrackSelector(mediaQueueManager.trackSelector)
setTrackSelector(trackSelector)
setAnalyticsCollector(analyticsCollector)
}.build().apply {
addListener(this@PlayerViewModel)
Expand Down Expand Up @@ -365,18 +368,22 @@ class PlayerViewModel(application: Application) : AndroidViewModel(application),
}

// Stop active encoding if transcoding
if (mediaSource.playMethod == PlayMethod.TRANSCODE) {
hlsSegmentApi.stopEncodingProcess(
deviceId = apiClient.deviceInfo.id,
playSessionId = mediaSource.playSessionId,
)
}
stopTranscoding(mediaSource)
} catch (e: ApiClientException) {
Timber.e(e, "Failed to report playback stop")
}
}
}

suspend fun stopTranscoding(mediaSource: JellyfinMediaSource) {
if (mediaSource.playMethod == PlayMethod.TRANSCODE) {
hlsSegmentApi.stopEncodingProcess(
deviceId = apiClient.deviceInfo.id,
playSessionId = mediaSource.playSessionId,
)
}
}

// Player controls

fun play() {
Expand Down Expand Up @@ -418,29 +425,36 @@ class PlayerViewModel(application: Application) : AndroidViewModel(application),
}
}

fun getStateAndPause(): PlayState? {
val player = playerOrNull ?: return null

val playWhenReady = player.playWhenReady
player.pause()
val position = player.contentPosition

return PlayState(playWhenReady, position)
}

/**
* @see QueueManager.selectAudioTrack
*/
fun selectAudioTrack(streamIndex: Int): Boolean = mediaQueueManager.selectAudioTrack(streamIndex).also { success ->
if (success) playerOrNull?.logTracks(analyticsCollector)
suspend fun selectAudioTrack(streamIndex: Int): Boolean {
return mediaQueueManager.selectAudioTrack(streamIndex).also { success ->
if (success) playerOrNull?.logTracks(analyticsCollector)
}
}

/**
* @see QueueManager.selectSubtitle
*/
fun selectSubtitle(streamIndex: Int): Boolean = mediaQueueManager.selectSubtitle(streamIndex).also { success ->
if (success) playerOrNull?.logTracks(analyticsCollector)
suspend fun selectSubtitle(streamIndex: Int): Boolean {
return mediaQueueManager.selectSubtitle(streamIndex).also { success ->
if (success) playerOrNull?.logTracks(analyticsCollector)
}
}

fun changeBitrate(bitrate: Int?) {
val player = playerOrNull ?: return

val playWhenReady = player.playWhenReady
pause()
val position = player.contentPosition
viewModelScope.launch {
mediaQueueManager.changeBitrate(bitrate, position, playWhenReady)
}
suspend fun changeBitrate(bitrate: Int?): Boolean {
return mediaQueueManager.changeBitrate(bitrate)
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ import java.util.UUID

@Parcelize
data class PlayOptions(
val mediaSourceId: UUID?,
val ids: List<UUID>,
val mediaSourceId: String?,
val startIndex: Int,
val startPositionTicks: Long?,
val audioStreamIndex: Int?,
Expand All @@ -22,14 +22,14 @@ data class PlayOptions(
companion object {
fun fromJson(json: JSONObject): PlayOptions? = try {
PlayOptions(
mediaSourceId = json.optString("mediaSourceId").toUUIDOrNull(),
ids = json.optJSONArray("ids")?.let { array ->
ArrayList<UUID>().apply {
for (i in 0 until array.size) {
array.getString(i).toUUIDOrNull()?.let(this::add)
}
}
} ?: emptyList(),
mediaSourceId = json.optString("mediaSourceId"),
startIndex = json.optInt("startIndex"),
startPositionTicks = json.optLong("startPositionTicks").takeIf { it > 0 },
audioStreamIndex = json.optString("audioStreamIndex").toIntOrNull(),
Expand Down
Loading