diff --git a/Jellyfin.Api/Helpers/DynamicHlsHelper.cs b/Jellyfin.Api/Helpers/DynamicHlsHelper.cs
index f8d89119a..b3878a41e 100644
--- a/Jellyfin.Api/Helpers/DynamicHlsHelper.cs
+++ b/Jellyfin.Api/Helpers/DynamicHlsHelper.cs
@@ -714,6 +714,21 @@ public class DynamicHlsHelper
return HlsCodecStringHelpers.GetAv1String(profile, level, false, bitDepth);
}
+ // VP9 HLS is for video remuxing only, everything is probed from the original video
+ if (string.Equals(codec, "vp9", StringComparison.OrdinalIgnoreCase))
+ {
+ var width = state.VideoStream.Width ?? 0;
+ var height = state.VideoStream.Height ?? 0;
+ var framerate = state.VideoStream.AverageFrameRate ?? 30;
+ var bitDepth = state.VideoStream.BitDepth ?? 8;
+ return HlsCodecStringHelpers.GetVp9String(
+ width,
+ height,
+ state.VideoStream.PixelFormat,
+ framerate,
+ bitDepth);
+ }
+
return string.Empty;
}
diff --git a/Jellyfin.Api/Helpers/HlsCodecStringHelpers.cs b/Jellyfin.Api/Helpers/HlsCodecStringHelpers.cs
index ec67b4c1b..d0bfa1fbe 100644
--- a/Jellyfin.Api/Helpers/HlsCodecStringHelpers.cs
+++ b/Jellyfin.Api/Helpers/HlsCodecStringHelpers.cs
@@ -182,6 +182,68 @@ public static class HlsCodecStringHelpers
return result.ToString();
}
+ ///
+ /// Gets a VP9 codec string.
+ ///
+ /// Video width.
+ /// Video height.
+ /// Video pixel format.
+ /// Video framerate.
+ /// Video bitDepth.
+ /// The VP9 codec string.
+ public static string GetVp9String(int width, int height, string pixelFormat, float framerate, int bitDepth)
+ {
+ // refer: https://www.webmproject.org/vp9/mp4/
+ StringBuilder result = new StringBuilder("vp09", 13);
+
+ var profileString = pixelFormat switch
+ {
+ "yuv420p" => "00",
+ "yuvj420p" => "00",
+ "yuv422p" => "01",
+ "yuv444p" => "01",
+ "yuv420p10le" => "02",
+ "yuv420p12le" => "02",
+ "yuv422p10le" => "03",
+ "yuv422p12le" => "03",
+ "yuv444p10le" => "03",
+ "yuv444p12le" => "03",
+ _ => "00"
+ };
+
+ var lumaPictureSize = width * height;
+ var lumaSampleRate = lumaPictureSize * framerate;
+ var levelString = lumaPictureSize switch
+ {
+ <= 0 => "00",
+ <= 36864 => "10",
+ <= 73728 => "11",
+ <= 122880 => "20",
+ <= 245760 => "21",
+ <= 552960 => "30",
+ <= 983040 => "31",
+ <= 2228224 => lumaSampleRate <= 83558400 ? "40" : "41",
+ <= 8912896 => lumaSampleRate <= 311951360 ? "50" : (lumaSampleRate <= 588251136 ? "51" : "52"),
+ <= 35651584 => lumaSampleRate <= 1176502272 ? "60" : (lumaSampleRate <= 4706009088 ? "61" : "62"),
+ _ => "00" // This should not happen
+ };
+
+ if (bitDepth != 8
+ && bitDepth != 10
+ && bitDepth != 12)
+ {
+ // Default to 8 bits
+ bitDepth = 8;
+ }
+
+ result.Append('.').Append(profileString).Append('.').Append(levelString);
+ var bitDepthD2 = bitDepth.ToString("D2", CultureInfo.InvariantCulture);
+ result.Append('.')
+ .Append(bitDepthD2);
+
+ return result.ToString();
+ }
+
///
/// Gets an AV1 codec string.
///
diff --git a/MediaBrowser.Model/Dlna/StreamBuilder.cs b/MediaBrowser.Model/Dlna/StreamBuilder.cs
index 55d1c3d51..6ac68ba2f 100644
--- a/MediaBrowser.Model/Dlna/StreamBuilder.cs
+++ b/MediaBrowser.Model/Dlna/StreamBuilder.cs
@@ -25,7 +25,7 @@ namespace MediaBrowser.Model.Dlna
private readonly ILogger _logger;
private readonly ITranscoderSupport _transcoderSupport;
- private static readonly string[] _supportedHlsVideoCodecs = new string[] { "h264", "hevc", "av1" };
+ private static readonly string[] _supportedHlsVideoCodecs = new string[] { "h264", "hevc", "vp9", "av1" };
private static readonly string[] _supportedHlsAudioCodecsTs = new string[] { "aac", "ac3", "eac3", "mp3" };
private static readonly string[] _supportedHlsAudioCodecsMp4 = new string[] { "aac", "ac3", "eac3", "mp3", "alac", "flac", "opus", "dca", "truehd" };