Skip to content

Commit

Permalink
Add initial video codec support code
Browse files Browse the repository at this point in the history
  • Loading branch information
nielsvanvelzen committed May 11, 2024
1 parent 4c95258 commit d48c1e6
Show file tree
Hide file tree
Showing 9 changed files with 78 additions and 21 deletions.
6 changes: 5 additions & 1 deletion playback/core/src/main/kotlin/mediastream/MediaStream.kt
Original file line number Diff line number Diff line change
Expand Up @@ -52,4 +52,8 @@ data class MediaStreamAudioTrack(
val sampleRate: Int,
) : MediaStreamTrack

// TODO: Add Video/Subtitle tracks
data class MediaStreamVideoTrack(
override val codec: String,
) : MediaStreamTrack

// TODO: Add subtitle track
4 changes: 2 additions & 2 deletions playback/exoplayer/src/main/kotlin/ExoPlayerBackend.kt
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ import org.jellyfin.playback.core.support.PlaySupportReport
import org.jellyfin.playback.core.ui.PlayerSubtitleView
import org.jellyfin.playback.core.ui.PlayerSurfaceView
import org.jellyfin.playback.exoplayer.support.getPlaySupportReport
import org.jellyfin.playback.exoplayer.support.toFormat
import org.jellyfin.playback.exoplayer.support.toFormats
import timber.log.Timber
import kotlin.time.Duration
import kotlin.time.Duration.Companion.ZERO
Expand Down Expand Up @@ -111,7 +111,7 @@ class ExoPlayerBackend(

override fun supportsStream(
stream: MediaStream
): PlaySupportReport = exoPlayer.getPlaySupportReport(stream.toFormat())
): PlaySupportReport = exoPlayer.getPlaySupportReport(stream.toFormats())

override fun setSurfaceView(surfaceView: PlayerSurfaceView?) {
exoPlayer.setVideoSurfaceView(surfaceView?.surface)
Expand Down
1 change: 1 addition & 0 deletions playback/exoplayer/src/main/kotlin/mapping/container.kt
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ val ffmpegContainerMimeTypes = mapOf(
"flac" to MimeTypes.AUDIO_FLAC,
"flv" to MimeTypes.VIDEO_FLV,
"matroska" to MimeTypes.APPLICATION_MATROSKA,
"mkv" to MimeTypes.APPLICATION_MATROSKA,
"mjpeg" to MimeTypes.VIDEO_MJPEG,
"mpeg" to MimeTypes.AUDIO_MPEG,
"ogg" to MimeTypes.AUDIO_OGG,
Expand Down
21 changes: 19 additions & 2 deletions playback/exoplayer/src/main/kotlin/mapping/video.kt
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,23 @@ fun getFfmpegVideoMimeType(codec: String): String {
?: codec
}

val ffmpegVideoMimeTypes = mapOf<String, String>(
// TODO: Add map
@OptIn(UnstableApi::class)
val ffmpegVideoMimeTypes = mapOf(
"mp4" to MimeTypes.VIDEO_MP4,
"mkv" to MimeTypes.VIDEO_MATROSKA,
"webm" to MimeTypes.VIDEO_WEBM,
"h263" to MimeTypes.VIDEO_H263,
"h254" to MimeTypes.VIDEO_H264,
"h265" to MimeTypes.VIDEO_H265,
"vp8" to MimeTypes.VIDEO_VP8,
"vp9" to MimeTypes.VIDEO_VP9,
"av1" to MimeTypes.VIDEO_AV1,
"mpeg" to MimeTypes.VIDEO_MPEG,
"mp2" to MimeTypes.VIDEO_MPEG2,
"vc1" to MimeTypes.VIDEO_VC1,
"flv" to MimeTypes.VIDEO_FLV,
"ogv" to MimeTypes.VIDEO_OGG,
"avi" to MimeTypes.VIDEO_AVI,
"mjpeg" to MimeTypes.VIDEO_MJPEG,
"rawvideo" to MimeTypes.VIDEO_RAW,
)
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,12 @@ data class ExoPlayerPlaySupportReport(
fun ExoPlayer.getPlaySupportReport(format: Format): ExoPlayerPlaySupportReport =
ExoPlayerPlaySupportReport.fromFlags(supportsFormat(format))

fun ExoPlayer.getPlaySupportReport(formats: Collection<Format>): ExoPlayerPlaySupportReport = formats
.map { format -> supportsFormat(format) }
.reduce { acc, i -> acc and i }
.let { flags -> ExoPlayerPlaySupportReport.fromFlags(flags) }


@OptIn(UnstableApi::class)
fun ExoPlayer.supportsFormat(format: Format): Int {
var capabilities = 0
Expand Down
36 changes: 26 additions & 10 deletions playback/exoplayer/src/main/kotlin/support/mediaStreamToFormat.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,35 @@ import androidx.media3.common.Format
import androidx.media3.common.util.UnstableApi
import org.jellyfin.playback.core.mediastream.MediaStream
import org.jellyfin.playback.core.mediastream.MediaStreamAudioTrack
import org.jellyfin.playback.core.mediastream.MediaStreamVideoTrack
import org.jellyfin.playback.exoplayer.mapping.getFfmpegAudioMimeType
import org.jellyfin.playback.exoplayer.mapping.getFfmpegContainerMimeType
import org.jellyfin.playback.exoplayer.mapping.getFfmpegVideoMimeType

@OptIn(UnstableApi::class)
fun MediaStream.toFormat() = Format.Builder().also { f ->
f.setId(identifier)
f.setContainerMimeType(getFfmpegContainerMimeType(container.format))
fun toFormat(stream: MediaStream, track: MediaStreamAudioTrack) = Format.Builder().also { f ->
f.setId(stream.identifier)
f.setContainerMimeType(getFfmpegContainerMimeType(stream.container.format))

val audioTrack = tracks.filterIsInstance<MediaStreamAudioTrack>().firstOrNull()
if (audioTrack != null) {
f.setSampleMimeType(getFfmpegAudioMimeType(audioTrack.codec))
f.setChannelCount(audioTrack.channels)
f.setAverageBitrate(audioTrack.bitrate)
f.setSampleRate(audioTrack.sampleRate)
}
f.setCodecs(track.codec)
f.setSampleMimeType(getFfmpegAudioMimeType(track.codec))
f.setChannelCount(track.channels)
f.setAverageBitrate(track.bitrate)
f.setSampleRate(track.sampleRate)
}.build()

@OptIn(UnstableApi::class)
fun toFormat(stream: MediaStream, track: MediaStreamVideoTrack) = Format.Builder().also { f ->
f.setId(stream.identifier)
f.setContainerMimeType(getFfmpegContainerMimeType(stream.container.format))

f.setCodecs(track.codec)
f.setSampleMimeType(getFfmpegVideoMimeType(track.codec))
}.build()

fun MediaStream.toFormats() = tracks.map { track ->
when (track) {
is MediaStreamAudioTrack -> toFormat(stream = this, track)
is MediaStreamVideoTrack -> toFormat(stream = this, track)
}
}
15 changes: 12 additions & 3 deletions playback/jellyfin/src/main/kotlin/JellyfinPlugin.kt
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,9 @@ fun jellyfinPlugin(
responseProfiles = emptyList(),
subtitleProfiles = emptyList(),
supportedMediaTypes = "",
// Add at least one transcoding profile so the server returns a value
// for "SupportsTranscoding" based on the user policy
// We don't actually use this profile in the client
// Add at least one transcoding profile for both audio an video so the server returns a
// value for "SupportsTranscoding" based on the user policy. We don't actually use this
// profile in the client
transcodingProfiles = listOf(
TranscodingProfile(
type = DlnaProfileType.AUDIO,
Expand All @@ -36,6 +36,15 @@ fun jellyfinPlugin(
audioCodec = "mp3",
videoCodec = "",
conditions = emptyList()
),
TranscodingProfile(
type = DlnaProfileType.VIDEO,
context = EncodingContext.STREAMING,
protocol = "hls",
container = "ts",
audioCodec = "aac",
videoCodec = "h264",
conditions = emptyList()
)
),
xmlRootAttributes = emptyList(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,8 @@ class VideoMediaStreamResolver(
playSessionId = mediaInfo.playSessionId,
tag = mediaInfo.mediaSource.eTag,
segmentContainer = REMUX_SEGMENT_CONTAINER,
videoCodec = "h264",
audioCodec = "aac",
)
)
}
Expand Down
8 changes: 5 additions & 3 deletions playback/jellyfin/src/main/kotlin/mediastream/tracks.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package org.jellyfin.playback.jellyfin.mediastream

import org.jellyfin.playback.core.mediastream.MediaStreamAudioTrack
import org.jellyfin.playback.core.mediastream.MediaStreamContainer
import org.jellyfin.playback.core.mediastream.MediaStreamVideoTrack
import org.jellyfin.sdk.model.api.MediaStream
import org.jellyfin.sdk.model.api.MediaStreamType

Expand All @@ -16,7 +17,7 @@ fun JellyfinStreamResolver.MediaInfo.getTracks() =

fun MediaStream.getMediaStreamTrack() = when (type) {
MediaStreamType.AUDIO -> getAudioTrack(this)
MediaStreamType.VIDEO -> getVideooTrack(this)
MediaStreamType.VIDEO -> getVideoTrack(this)
MediaStreamType.SUBTITLE -> getSubtitleTrack(this)

// Ignore other track types
Expand All @@ -32,8 +33,9 @@ private fun getAudioTrack(stream: MediaStream) = MediaStreamAudioTrack(
sampleRate = stream.sampleRate ?: 0,
)

// TODO Implement Video track type
private fun getVideooTrack(stream: MediaStream) = null
private fun getVideoTrack(stream: MediaStream) = MediaStreamVideoTrack(
codec = requireNotNull(stream.codec),
)

// TODO Implement Subtitle track type
private fun getSubtitleTrack(stream: MediaStream) = null

0 comments on commit d48c1e6

Please sign in to comment.