mirror of
https://github.com/jellyfin/jellyfin.git
synced 2024-09-07 20:08:59 +02:00
Add CODECS field to HLS master playlist
This commit is contained in:
parent
85da15685f
commit
f2858878d1
2 changed files with 236 additions and 0 deletions
|
@ -722,6 +722,114 @@ namespace MediaBrowser.Api.Playback.Hls
|
|||
//return state.VideoRequest.VideoBitRate.HasValue;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a formatted string of the output audio codec, for use in the CODECS field.
|
||||
/// </summary>
|
||||
/// <seealso cref="AppendPlaylistCodecsField(StringBuilder, StreamState)"/>
|
||||
/// <seealso cref="GetPlaylistVideoCodecs(StreamState)"/>
|
||||
/// <param name="state">StreamState of the current stream.</param>
|
||||
/// <returns>Formatted audio codec string.</returns>
|
||||
private string GetPlaylistAudioCodecs(StreamState state)
|
||||
{
|
||||
|
||||
if (string.Equals(state.ActualOutputAudioCodec, "aac", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
string profile = state.GetRequestedProfiles("aac").FirstOrDefault();
|
||||
|
||||
return HlsCodecStringFactory.GetAACString(profile);
|
||||
}
|
||||
else if (string.Equals(state.ActualOutputAudioCodec, "mp3", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return HlsCodecStringFactory.GetMP3String();
|
||||
}
|
||||
else if (string.Equals(state.ActualOutputAudioCodec, "ac3", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return HlsCodecStringFactory.GetAC3String();
|
||||
}
|
||||
else if (string.Equals(state.ActualOutputAudioCodec, "eac3", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return HlsCodecStringFactory.GetEAC3String();
|
||||
}
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a formatted string of the output video codec, for use in the CODECS field.
|
||||
/// </summary>
|
||||
/// <seealso cref="AppendPlaylistCodecsField(StringBuilder, StreamState)"/>
|
||||
/// <seealso cref="GetPlaylistAudioCodecs(StreamState)"/>
|
||||
/// <param name="state">StreamState of the current stream.</param>
|
||||
/// <returns>Formatted video codec string.</returns>
|
||||
private string GetPlaylistVideoCodecs(StreamState state)
|
||||
{
|
||||
int level = Convert.ToInt32(state.GetRequestedLevel(state.ActualOutputVideoCodec));
|
||||
|
||||
if (string.Equals(state.ActualOutputVideoCodec, "h264", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
string profile = state.GetRequestedProfiles("h264").FirstOrDefault();
|
||||
|
||||
return HlsCodecStringFactory.GetH264String(profile, level);
|
||||
}
|
||||
else if (string.Equals(state.ActualOutputVideoCodec, "h265", StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(state.ActualOutputVideoCodec, "hevc", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
string profile = state.GetRequestedProfiles("h265").FirstOrDefault();
|
||||
|
||||
return HlsCodecStringFactory.GetH265String(profile, level);
|
||||
}
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Appends a CODECS field containing formatted strings of
|
||||
/// the active streams output video and audio codecs.
|
||||
/// </summary>
|
||||
/// <seealso cref="AppendPlaylist(StringBuilder, StreamState, string, int, string)"/>
|
||||
/// <seealso cref="GetPlaylistVideoCodecs(StreamState)"/>
|
||||
/// <seealso cref="GetPlaylistAudioCodecs(StreamState)"/>
|
||||
/// <param name="builder">StringBuilder to append the field to.</param>
|
||||
/// <param name="state">StreamState of the current stream.</param>
|
||||
private void AppendPlaylistCodecsField(StringBuilder builder, StreamState state)
|
||||
{
|
||||
// Video
|
||||
string videoCodecs = string.Empty;
|
||||
if (!string.IsNullOrEmpty(state.ActualOutputVideoCodec))
|
||||
{
|
||||
videoCodecs = GetPlaylistVideoCodecs(state);
|
||||
}
|
||||
|
||||
// Audio
|
||||
string audioCodecs = string.Empty;
|
||||
if (!string.IsNullOrEmpty(state.ActualOutputAudioCodec))
|
||||
{
|
||||
audioCodecs = GetPlaylistAudioCodecs(state);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(videoCodecs) || !string.IsNullOrEmpty(audioCodecs))
|
||||
{
|
||||
builder.Append(",CODECS=\"");
|
||||
|
||||
if (!string.IsNullOrEmpty(videoCodecs) && !string.IsNullOrEmpty(audioCodecs))
|
||||
{
|
||||
builder.Append(videoCodecs)
|
||||
.Append(',')
|
||||
.Append(audioCodecs);
|
||||
}
|
||||
else if (!string.IsNullOrEmpty(videoCodecs))
|
||||
{
|
||||
builder.Append(videoCodecs);
|
||||
}
|
||||
else if (!string.IsNullOrEmpty(audioCodecs))
|
||||
{
|
||||
builder.Append(audioCodecs);
|
||||
}
|
||||
|
||||
builder.Append('"');
|
||||
}
|
||||
}
|
||||
|
||||
private void AppendPlaylist(StringBuilder builder, StreamState state, string url, int bitrate, string subtitleGroup)
|
||||
{
|
||||
builder.Append("#EXT-X-STREAM-INF:BANDWIDTH=")
|
||||
|
@ -735,6 +843,8 @@ namespace MediaBrowser.Api.Playback.Hls
|
|||
// header += string.Format(",FRAME-RATE=\"{0}\"", state.TargetFramerate.Value.ToString(CultureInfo.InvariantCulture));
|
||||
//}
|
||||
|
||||
AppendPlaylistCodecsField(builder, state);
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(subtitleGroup))
|
||||
{
|
||||
builder.Append(",SUBTITLES=\"")
|
||||
|
|
126
MediaBrowser.Api/Playback/Hls/HlsCodecStringFactory.cs
Normal file
126
MediaBrowser.Api/Playback/Hls/HlsCodecStringFactory.cs
Normal file
|
@ -0,0 +1,126 @@
|
|||
using System;
|
||||
using System.Text;
|
||||
|
||||
|
||||
namespace MediaBrowser.Api.Playback
|
||||
{
|
||||
/// <summary>
|
||||
/// Get various codec strings for use in HLS playlists.
|
||||
/// </summary>
|
||||
static class HlsCodecStringFactory
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Gets a MP3 codec string.
|
||||
/// </summary>
|
||||
/// <returns>MP3 codec string.</returns>
|
||||
public static string GetMP3String()
|
||||
{
|
||||
return "mp4a.40.34";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets an AAC codec string.
|
||||
/// </summary>
|
||||
/// <param name="profile">AAC profile.</param>
|
||||
/// <returns>AAC codec string.</returns>
|
||||
public static string GetAACString(string profile)
|
||||
{
|
||||
StringBuilder result = new StringBuilder("mp4a", 9);
|
||||
|
||||
if (string.Equals(profile, "HE", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
result.Append(".40.5");
|
||||
}
|
||||
else
|
||||
{
|
||||
// Default to LC if profile is invalid
|
||||
result.Append(".40.2");
|
||||
}
|
||||
|
||||
return result.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a H.264 codec string.
|
||||
/// </summary>
|
||||
/// <param name="profile">H.264 profile.</param>
|
||||
/// <param name="level">H.264 level.</param>
|
||||
/// <returns>H.264 string.</returns>
|
||||
public static string GetH264String(string profile, int level)
|
||||
{
|
||||
StringBuilder result = new StringBuilder("avc1", 11);
|
||||
|
||||
if (string.Equals(profile, "high", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
result.Append(".6400");
|
||||
}
|
||||
else if (string.Equals(profile, "main", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
result.Append(".4D40");
|
||||
}
|
||||
else if (string.Equals(profile, "baseline", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
result.Append(".42E0");
|
||||
}
|
||||
else
|
||||
{
|
||||
// Default to constrained baseline if profile is invalid
|
||||
result.Append(".4240");
|
||||
}
|
||||
|
||||
string levelHex = level.ToString("X2");
|
||||
result.Append(levelHex);
|
||||
|
||||
return result.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a H.265 codec string.
|
||||
/// </summary>
|
||||
/// <param name="profile">H.265 profile.</param>
|
||||
/// <param name="level">H.265 level.</param>
|
||||
/// <returns>H.265 string.</returns>
|
||||
public static string GetH265String(string profile, int level)
|
||||
{
|
||||
// The h265 syntax is a bit of a mystery at the time this comment was written.
|
||||
// This is what I've found through various sources:
|
||||
// FORMAT: [codecTag].[profile].[constraint?].L[level * 30].[UNKNOWN]
|
||||
StringBuilder result = new StringBuilder("hev1", 16);
|
||||
|
||||
if (string.Equals(profile, "main10", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
result.Append(".2.6");
|
||||
}
|
||||
else
|
||||
{
|
||||
// Default to main if profile is invalid
|
||||
result.Append(".1.6");
|
||||
}
|
||||
|
||||
result.Append(".L")
|
||||
.Append(level * 3)
|
||||
.Append(".B0");
|
||||
|
||||
return result.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets an AC-3 codec string.
|
||||
/// </summary>
|
||||
/// <returns>AC-3 codec string.</returns>
|
||||
public static string GetAC3String()
|
||||
{
|
||||
return "mp4a.a5";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets an E-AC-3 codec string.
|
||||
/// </summary>
|
||||
/// <returns>E-AC-3 codec string.</returns>
|
||||
public static string GetEAC3String()
|
||||
{
|
||||
return "mp4a.a6";
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue