mirror of
				https://github.com/jellyfin/jellyfin.git
				synced 2025-10-31 10:37:22 -04:00 
			
		
		
		
	Merge pull request #2573 from YouKnowBlom/add-hls-fields
Add codecs, resolution and frame-rate fields to HLS master playlist
This commit is contained in:
		
						commit
						7f7a686ea6
					
				| @ -720,22 +720,203 @@ namespace MediaBrowser.Api.Playback.Hls | |||||||
|             //return state.VideoRequest.VideoBitRate.HasValue; |             //return state.VideoRequest.VideoBitRate.HasValue; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|  |         /// <summary> | ||||||
|  |         /// Get the H.26X level of the output video stream. | ||||||
|  |         /// </summary> | ||||||
|  |         /// <param name="state">StreamState of the current stream.</param> | ||||||
|  |         /// <returns>H.26X level of the output video stream.</returns> | ||||||
|  |         private int? GetOutputVideoCodecLevel(StreamState state) | ||||||
|  |         { | ||||||
|  |             string levelString; | ||||||
|  |             if (string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase) | ||||||
|  |                 && state.VideoStream.Level.HasValue) | ||||||
|  |             { | ||||||
|  |                 levelString = state.VideoStream?.Level.ToString(); | ||||||
|  |             } | ||||||
|  |             else | ||||||
|  |             { | ||||||
|  |                 levelString = state.GetRequestedLevel(state.ActualOutputVideoCodec); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             if (int.TryParse(levelString, NumberStyles.Integer, CultureInfo.InvariantCulture, out var parsedLevel)) | ||||||
|  |             { | ||||||
|  |                 return parsedLevel; | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             return null; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         /// <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, string, int)"/> | ||||||
|  |         /// <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, string codec, int level) | ||||||
|  |         { | ||||||
|  |             if (level == 0) | ||||||
|  |             { | ||||||
|  |                 // This is 0 when there's no requested H.26X level in the device profile | ||||||
|  |                 // and the source is not encoded in H.26X | ||||||
|  |                 Logger.LogError("Got invalid H.26X level when building CODECS field for HLS master playlist"); | ||||||
|  |                 return string.Empty; | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             if (string.Equals(codec, "h264", StringComparison.OrdinalIgnoreCase)) | ||||||
|  |             { | ||||||
|  |                 string profile = state.GetRequestedProfiles("h264").FirstOrDefault(); | ||||||
|  | 
 | ||||||
|  |                 return HlsCodecStringFactory.GetH264String(profile, level); | ||||||
|  |             } | ||||||
|  |             else if (string.Equals(codec, "h265", StringComparison.OrdinalIgnoreCase) | ||||||
|  |                     || string.Equals(codec, "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, string, int)"/> | ||||||
|  |         /// <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; | ||||||
|  |             int? videoCodecLevel = GetOutputVideoCodecLevel(state); | ||||||
|  |             if (!string.IsNullOrEmpty(state.ActualOutputVideoCodec) && videoCodecLevel.HasValue) | ||||||
|  |             { | ||||||
|  |                 videoCodecs = GetPlaylistVideoCodecs(state, state.ActualOutputVideoCodec, videoCodecLevel.Value); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             // Audio | ||||||
|  |             string audioCodecs = string.Empty; | ||||||
|  |             if (!string.IsNullOrEmpty(state.ActualOutputAudioCodec)) | ||||||
|  |             { | ||||||
|  |                 audioCodecs = GetPlaylistAudioCodecs(state); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             StringBuilder codecs = new StringBuilder(); | ||||||
|  | 
 | ||||||
|  |             codecs.Append(videoCodecs) | ||||||
|  |                 .Append(',') | ||||||
|  |                 .Append(audioCodecs); | ||||||
|  | 
 | ||||||
|  |             if (codecs.Length > 1) | ||||||
|  |             { | ||||||
|  |                 builder.Append(",CODECS=\"") | ||||||
|  |                     .Append(codecs) | ||||||
|  |                     .Append('"'); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         /// <summary> | ||||||
|  |         /// Appends a FRAME-RATE field containing the framerate of the output stream. | ||||||
|  |         /// </summary> | ||||||
|  |         /// <seealso cref="AppendPlaylist(StringBuilder, StreamState, string, int, string)"/> | ||||||
|  |         /// <param name="builder">StringBuilder to append the field to.</param> | ||||||
|  |         /// <param name="state">StreamState of the current stream.</param> | ||||||
|  |         private void AppendPlaylistFramerateField(StringBuilder builder, StreamState state) | ||||||
|  |         { | ||||||
|  |             double? framerate = null; | ||||||
|  |             if (state.TargetFramerate.HasValue) | ||||||
|  |             { | ||||||
|  |                 framerate = Math.Round(state.TargetFramerate.GetValueOrDefault(), 3); | ||||||
|  |             } | ||||||
|  |             else if (state.VideoStream.RealFrameRate.HasValue) | ||||||
|  |             { | ||||||
|  |                 framerate = Math.Round(state.VideoStream.RealFrameRate.GetValueOrDefault(), 3); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             if (framerate.HasValue) | ||||||
|  |             { | ||||||
|  |                 builder.Append(",FRAME-RATE=\"") | ||||||
|  |                     .Append(framerate.Value) | ||||||
|  |                     .Append('"'); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         /// <summary> | ||||||
|  |         /// Appends a RESOLUTION field containing the resolution of the output stream. | ||||||
|  |         /// </summary> | ||||||
|  |         /// <seealso cref="AppendPlaylist(StringBuilder, StreamState, string, int, string)"/> | ||||||
|  |         /// <param name="builder">StringBuilder to append the field to.</param> | ||||||
|  |         /// <param name="state">StreamState of the current stream.</param> | ||||||
|  |         private void AppendPlaylistResolutionField(StringBuilder builder, StreamState state) | ||||||
|  |         { | ||||||
|  |             if (state.OutputWidth.HasValue && state.OutputHeight.HasValue) | ||||||
|  |             { | ||||||
|  |                 builder.Append(",RESOLUTION=\"") | ||||||
|  |                     .Append(state.OutputWidth.GetValueOrDefault()) | ||||||
|  |                     .Append('x') | ||||||
|  |                     .Append(state.OutputHeight.GetValueOrDefault()) | ||||||
|  |                     .Append('"'); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|         private void AppendPlaylist(StringBuilder builder, StreamState state, string url, int bitrate, string subtitleGroup) |         private void AppendPlaylist(StringBuilder builder, StreamState state, string url, int bitrate, string subtitleGroup) | ||||||
|         { |         { | ||||||
|             var header = "#EXT-X-STREAM-INF:BANDWIDTH=" + bitrate.ToString(CultureInfo.InvariantCulture) + ",AVERAGE-BANDWIDTH=" + bitrate.ToString(CultureInfo.InvariantCulture); |             builder.Append("#EXT-X-STREAM-INF:BANDWIDTH=") | ||||||
|  |                 .Append(bitrate.ToString(CultureInfo.InvariantCulture)) | ||||||
|  |                 .Append(",AVERAGE-BANDWIDTH=") | ||||||
|  |                 .Append(bitrate.ToString(CultureInfo.InvariantCulture)); | ||||||
| 
 | 
 | ||||||
|             // tvos wants resolution, codecs, framerate |             AppendPlaylistCodecsField(builder, state); | ||||||
|             //if (state.TargetFramerate.HasValue) | 
 | ||||||
|             //{ |             AppendPlaylistResolutionField(builder, state); | ||||||
|             //    header += string.Format(",FRAME-RATE=\"{0}\"", state.TargetFramerate.Value.ToString(CultureInfo.InvariantCulture)); | 
 | ||||||
|             //} |             AppendPlaylistFramerateField(builder, state); | ||||||
| 
 | 
 | ||||||
|             if (!string.IsNullOrWhiteSpace(subtitleGroup)) |             if (!string.IsNullOrWhiteSpace(subtitleGroup)) | ||||||
|             { |             { | ||||||
|                 header += string.Format(",SUBTITLES=\"{0}\"", subtitleGroup); |                 builder.Append(",SUBTITLES=\"") | ||||||
|  |                     .Append(subtitleGroup) | ||||||
|  |                     .Append('"'); | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             builder.AppendLine(header); |             builder.Append(Environment.NewLine); | ||||||
|             builder.AppendLine(url); |             builder.AppendLine(url); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|  | |||||||
							
								
								
									
										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…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user