Skip to content

Commit 1fe56f9

Browse files
chore(docs): update code docs
1 parent 67b8d96 commit 1fe56f9

5 files changed

Lines changed: 151 additions & 55 deletions

File tree

docs/data/search.json

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

docs/module-deviceCapabilities.html

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

docs/module-items.html

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

docs/source_api_items.bs.html

Lines changed: 57 additions & 6 deletions
Large diffs are not rendered by default.

docs/source_utils_deviceCapabilities.bs.html

Lines changed: 91 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -446,8 +446,15 @@
446446
' ========================================
447447
' VIDEO CODEC DETECTION (per container)
448448
' ========================================
449-
450-
transcodingContainers = ["ts", "mp4"]
449+
'
450+
' Container preference order: mp4 (HLS-fmp4) first, then ts (HLS-mpegts). This
451+
' matches the official Jellyfin Roku app and the original jellyfin-roku fork parent.
452+
' The order matters because Jellyfin's StreamBuilder picks FirstOrDefault when
453+
' profile ranks tie — mp4-first unlocks the wider HLS audio target set
454+
' (alac/flac/opus/dts/truehd in addition to aac/ac3/eac3/mp3) for surround
455+
' transcoding scenarios where the user's hardware passthrough is detected only
456+
' for a codec that's TS-incompatible (issue #573).
457+
transcodingContainers = ["mp4", "ts"]
451458
containerVideoCodecs = {}
452459

453460
for each container in transcodingContainers
@@ -496,54 +503,23 @@
496503
allSurroundCodecs = sixChannelPassthruCodecs
497504
end if
498505

499-
' Build optimal audio codec list for transcoding
500-
' Order: AAC (stereo), passthrough surround, multichannel decode, stereo fallbacks
506+
' Build per-container audio codec list. The construction logic is in the pure helper
507+
' buildVideoTranscodingAudioCodecsForContainer so it can be exhaustively unit-tested
508+
' against a full matrix of hardware shapes (eac3+ac3 passthru, dts-only, truehd-only,
509+
' stereo-only). The helper applies the per-container HLS-target filter that prevents
510+
' issue #573 at the construction site.
501511
for each container in transcodingContainers
502-
audioCodecList = []
503-
504-
' 1. AAC always first (efficient for stereo). On multichannel + passthru playback, all
505-
' stereo-output codecs (including AAC) are stripped at playback time by
506-
' optimizeAudioCodecListForSource (see items.bs).
507-
audioCodecList.push("aac")
508-
509-
' 2. Add surround passthrough codecs if supported (in preference order)
510-
if allSurroundCodecs.count() > 0
511-
' Apply user's preferred codec ordering
512-
if isValid(preferredCodec) and preferredCodec <> "" and preferredCodec <> "auto"
513-
' Preferred codec first (if supported)
514-
if arrayHasValue(allSurroundCodecs, preferredCodec)
515-
audioCodecList.push(preferredCodec)
516-
end if
517-
518-
' Then other surround codecs in priority order: eac3 > ac3 > dts
519-
surroundPriority = ["eac3", "ac3", "dts"]
520-
for each codec in surroundPriority
521-
if arrayHasValue(allSurroundCodecs, codec) and codec <> preferredCodec
522-
audioCodecList.push(codec)
523-
end if
524-
end for
525-
else
526-
' Auto mode: use default priority order (eac3 > ac3 > dts)
527-
surroundPriority = ["eac3", "ac3", "dts"]
528-
for each codec in surroundPriority
529-
if arrayHasValue(allSurroundCodecs, codec)
530-
audioCodecList.push(codec)
531-
end if
532-
end for
533-
end if
534-
end if
535-
536-
' 3. Add stereo fallback codecs if device supports them (MP3 most compatible, then lossless as fallbacks)
537-
stereoFallbacks = ["mp3", "flac", "alac", "pcm"]
538-
for each codec in stereoFallbacks
539-
if not arrayHasValue(audioCodecList, codec)
540-
' Validate device can decode this codec at 2 channels in this container
541-
if di.CanDecodeAudio({ Codec: codec, ChCnt: 2, Container: container }).Result
542-
audioCodecList.push(codec)
543-
end if
512+
' Detect which stereo fallback codecs the device can decode at 2ch in this container.
513+
' Pre-computing this lets the construction helper stay pure.
514+
decodableStereoFallbacks = []
515+
for each codec in ["mp3", "flac", "alac", "opus"]
516+
if di.CanDecodeAudio({ Codec: codec, ChCnt: 2, Container: container }).Result
517+
decodableStereoFallbacks.push(codec)
544518
end if
545519
end for
546520

521+
audioCodecList = buildVideoTranscodingAudioCodecsForContainer(container, allSurroundCodecs, preferredCodec, decodableStereoFallbacks)
522+
547523
' Create single profile per container
548524
profile = {
549525
"Container": container,
@@ -570,6 +546,75 @@
570546
return transcodingProfiles
571547
end function
572548

549+
' Mirror of Jellyfin server's _supportedHlsAudioCodecs* from
550+
' MediaBrowser.Model/Dlna/StreamBuilder.cs. Codecs outside these per-container sets
551+
' get silently stripped by the server's HLS-codec filter when building the
552+
' TranscodingUrl, leaving an empty AudioCodec field that triggers the server's
553+
' InferAudioCodec("m3u8") fallback chain (issue #573). Listing only codecs the
554+
' server will actually use as HLS transcode targets keeps the profile honest and
555+
' prevents the bug class at the construction site.
556+
function getHlsTranscodeTargetsForContainer(container as string) as object
557+
if container = "ts"
558+
return ["aac", "ac3", "eac3", "mp3"]
559+
end if
560+
if container = "mp4"
561+
return ["aac", "ac3", "eac3", "mp3", "alac", "flac", "opus", "dts", "truehd"]
562+
end if
563+
return []
564+
end function
565+
566+
' Build the AudioCodec list for a single HLS video transcoding profile container.
567+
' Pure function — all inputs explicit so the full hardware matrix can be unit-tested
568+
' without mocking roDeviceInfo or m.global.
569+
'
570+
' Ordering rules (leftmost is the server's first-choice transcode target):
571+
' 1. AAC (universal Roku decode, in both ts and mp4 server target sets)
572+
' 2. User's preferredCodec if set, device-passthru-supported, and server-target
573+
' 3. Other surround codecs in priority order (eac3 > ac3 > dts > truehd), each
574+
' gated by device passthru AND server-target for this container
575+
' 4. Stereo fallback codecs the device decodes (mp3 > flac > alac > opus), each
576+
' gated by server-target for this container
577+
'
578+
' @param container - "ts" or "mp4"
579+
' @param allSurroundCodecs - array of surround codec names with device passthrough
580+
' @param preferredCodec - user's playbackPreferredMultichannelCodec value, or "auto" / ""
581+
' @param decodableStereoFallbacks - stereo fallback codec names the device can decode
582+
' at 2ch in this container (subset of mp3/flac/alac/opus)
583+
' @returns array of codec name strings in preference order
584+
function buildVideoTranscodingAudioCodecsForContainer(container as string, allSurroundCodecs as object, preferredCodec as string, decodableStereoFallbacks as object) as object
585+
serverTargets = getHlsTranscodeTargetsForContainer(container)
586+
if serverTargets.count() = 0 then return []
587+
588+
surroundPriority = ["eac3", "ac3", "dts", "truehd"]
589+
audioCodecList = []
590+
591+
' 1. AAC always first
592+
audioCodecList.push("aac")
593+
594+
' 2. User's preferred surround codec, if both device-supported and server-supported
595+
if isValid(preferredCodec) and preferredCodec <> "" and preferredCodec <> "auto"
596+
if arrayHasValue(allSurroundCodecs, preferredCodec) and arrayHasValue(serverTargets, preferredCodec)
597+
audioCodecList.push(preferredCodec)
598+
end if
599+
end if
600+
601+
' 3. Other surround codecs in priority order
602+
for each codec in surroundPriority
603+
if not arrayHasValue(audioCodecList, codec) and arrayHasValue(allSurroundCodecs, codec) and arrayHasValue(serverTargets, codec)
604+
audioCodecList.push(codec)
605+
end if
606+
end for
607+
608+
' 4. Stereo fallbacks — caller already verified device decode capability
609+
for each codec in decodableStereoFallbacks
610+
if not arrayHasValue(audioCodecList, codec) and arrayHasValue(serverTargets, codec)
611+
audioCodecList.push(codec)
612+
end if
613+
end for
614+
615+
return audioCodecList
616+
end function
617+
573618
function getContainerProfiles() as object
574619
containerProfiles = []
575620

0 commit comments

Comments
 (0)