aboutsummaryrefslogtreecommitdiff
path: root/MediaBrowser.Api/Playback
diff options
context:
space:
mode:
Diffstat (limited to 'MediaBrowser.Api/Playback')
-rw-r--r--MediaBrowser.Api/Playback/BaseStreamingService.cs440
-rw-r--r--MediaBrowser.Api/Playback/Hls/BaseHlsService.cs76
-rw-r--r--MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs237
-rw-r--r--MediaBrowser.Api/Playback/Hls/HlsCodecStringFactory.cs126
-rw-r--r--MediaBrowser.Api/Playback/Hls/HlsSegmentService.cs6
-rw-r--r--MediaBrowser.Api/Playback/Hls/VideoHlsService.cs4
-rw-r--r--MediaBrowser.Api/Playback/MediaInfoService.cs69
-rw-r--r--MediaBrowser.Api/Playback/Progressive/AudioService.cs6
-rw-r--r--MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs12
-rw-r--r--MediaBrowser.Api/Playback/Progressive/ProgressiveStreamWriter.cs6
-rw-r--r--MediaBrowser.Api/Playback/Progressive/VideoService.cs5
-rw-r--r--MediaBrowser.Api/Playback/StaticRemoteStreamWriter.cs6
-rw-r--r--MediaBrowser.Api/Playback/StreamRequest.cs6
-rw-r--r--MediaBrowser.Api/Playback/StreamState.cs2
-rw-r--r--MediaBrowser.Api/Playback/UniversalAudioService.cs34
15 files changed, 665 insertions, 370 deletions
diff --git a/MediaBrowser.Api/Playback/BaseStreamingService.cs b/MediaBrowser.Api/Playback/BaseStreamingService.cs
index 5029ce0bb..84ed5dcac 100644
--- a/MediaBrowser.Api/Playback/BaseStreamingService.cs
+++ b/MediaBrowser.Api/Playback/BaseStreamingService.cs
@@ -7,6 +7,7 @@ using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
+using Jellyfin.Data.Enums;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller.Configuration;
@@ -27,7 +28,7 @@ using Microsoft.Extensions.Logging;
namespace MediaBrowser.Api.Playback
{
/// <summary>
- /// Class BaseStreamingService
+ /// Class BaseStreamingService.
/// </summary>
public abstract class BaseStreamingService : BaseApiService
{
@@ -81,7 +82,7 @@ namespace MediaBrowser.Api.Playback
/// Initializes a new instance of the <see cref="BaseStreamingService" /> class.
/// </summary>
protected BaseStreamingService(
- ILogger logger,
+ ILogger<BaseStreamingService> logger,
IServerConfigurationManager serverConfigurationManager,
IHttpResultFactory httpResultFactory,
IUserManager userManager,
@@ -134,15 +135,12 @@ namespace MediaBrowser.Api.Playback
var data = $"{state.MediaPath}-{state.UserAgent}-{state.Request.DeviceId}-{state.Request.PlaySessionId}";
var filename = data.GetMD5().ToString("N", CultureInfo.InvariantCulture);
- var ext = outputFileExtension.ToLowerInvariant();
+ var ext = outputFileExtension?.ToLowerInvariant();
var folder = ServerConfigurationManager.GetTranscodePath();
- if (EnableOutputInSubFolder)
- {
- return Path.Combine(folder, filename, filename + ext);
- }
-
- return Path.Combine(folder, filename + ext);
+ return EnableOutputInSubFolder
+ ? Path.Combine(folder, filename, filename + ext)
+ : Path.Combine(folder, filename + ext);
}
protected virtual string GetDefaultEncoderPreset()
@@ -196,10 +194,10 @@ namespace MediaBrowser.Api.Playback
await AcquireResources(state, cancellationTokenSource).ConfigureAwait(false);
- if (state.VideoRequest != null && !string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase))
+ if (state.VideoRequest != null && !EncodingHelper.IsCopyCodec(state.OutputVideoCodec))
{
var auth = AuthorizationContext.GetAuthorizationInfo(Request);
- if (auth.User != null && !auth.User.Policy.EnableVideoPlaybackTranscoding)
+ if (auth.User != null && !auth.User.HasPermission(PermissionKind.EnableVideoPlaybackTranscoding))
{
ApiEntryPoint.Instance.OnTranscodeFailedToStart(outputPath, TranscodingJobType, state);
@@ -218,7 +216,7 @@ namespace MediaBrowser.Api.Playback
UseShellExecute = false,
// Must consume both stdout and stderr or deadlocks may occur
- //RedirectStandardOutput = true,
+ // RedirectStandardOutput = true,
RedirectStandardError = true,
RedirectStandardInput = true,
@@ -246,16 +244,10 @@ namespace MediaBrowser.Api.Playback
var logFilePrefix = "ffmpeg-transcode";
if (state.VideoRequest != null
- && string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase))
+ && EncodingHelper.IsCopyCodec(state.OutputVideoCodec))
{
- if (string.Equals(state.OutputAudioCodec, "copy", StringComparison.OrdinalIgnoreCase))
- {
- logFilePrefix = "ffmpeg-remux";
- }
- else
- {
- logFilePrefix = "ffmpeg-directstream";
- }
+ logFilePrefix = EncodingHelper.IsCopyCodec(state.OutputAudioCodec)
+ ? "ffmpeg-remux" : "ffmpeg-directstream";
}
var logFilePath = Path.Combine(ServerConfigurationManager.ApplicationPaths.LogDirectoryPath, logFilePrefix + "-" + Guid.NewGuid() + ".txt");
@@ -311,6 +303,7 @@ namespace MediaBrowser.Api.Playback
{
StartThrottler(state, transcodingJob);
}
+
Logger.LogDebug("StartFfMpeg() finished successfully");
return transcodingJob;
@@ -330,15 +323,16 @@ namespace MediaBrowser.Api.Playback
var encodingOptions = ServerConfigurationManager.GetEncodingOptions();
// enable throttling when NOT using hardware acceleration
- if (encodingOptions.HardwareAccelerationType == string.Empty)
+ if (string.IsNullOrEmpty(encodingOptions.HardwareAccelerationType))
{
return state.InputProtocol == MediaProtocol.File &&
state.RunTimeTicks.HasValue &&
state.RunTimeTicks.Value >= TimeSpan.FromMinutes(5).Ticks &&
state.IsInputVideo &&
state.VideoType == VideoType.VideoFile &&
- !string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase);
+ !EncodingHelper.IsCopyCodec(state.OutputVideoCodec);
}
+
return false;
}
@@ -389,195 +383,181 @@ namespace MediaBrowser.Api.Playback
continue;
}
- if (i == 0)
- {
- request.DeviceProfileId = val;
- }
- else if (i == 1)
- {
- request.DeviceId = val;
- }
- else if (i == 2)
- {
- request.MediaSourceId = val;
- }
- else if (i == 3)
- {
- request.Static = string.Equals("true", val, StringComparison.OrdinalIgnoreCase);
- }
- else if (i == 4)
- {
- if (videoRequest != null)
- {
- videoRequest.VideoCodec = val;
- }
- }
- else if (i == 5)
- {
- request.AudioCodec = val;
- }
- else if (i == 6)
- {
- if (videoRequest != null)
- {
- videoRequest.AudioStreamIndex = int.Parse(val, CultureInfo.InvariantCulture);
- }
- }
- else if (i == 7)
- {
- if (videoRequest != null)
- {
- videoRequest.SubtitleStreamIndex = int.Parse(val, CultureInfo.InvariantCulture);
- }
- }
- else if (i == 8)
- {
- if (videoRequest != null)
- {
- videoRequest.VideoBitRate = int.Parse(val, CultureInfo.InvariantCulture);
- }
- }
- else if (i == 9)
- {
- request.AudioBitRate = int.Parse(val, CultureInfo.InvariantCulture);
- }
- else if (i == 10)
- {
- request.MaxAudioChannels = int.Parse(val, CultureInfo.InvariantCulture);
- }
- else if (i == 11)
- {
- if (videoRequest != null)
- {
- videoRequest.MaxFramerate = float.Parse(val, CultureInfo.InvariantCulture);
- }
- }
- else if (i == 12)
- {
- if (videoRequest != null)
- {
- videoRequest.MaxWidth = int.Parse(val, CultureInfo.InvariantCulture);
- }
- }
- else if (i == 13)
- {
- if (videoRequest != null)
- {
- videoRequest.MaxHeight = int.Parse(val, CultureInfo.InvariantCulture);
- }
- }
- else if (i == 14)
- {
- request.StartTimeTicks = long.Parse(val, CultureInfo.InvariantCulture);
- }
- else if (i == 15)
- {
- if (videoRequest != null)
- {
- videoRequest.Level = val;
- }
- }
- else if (i == 16)
- {
- if (videoRequest != null)
- {
- videoRequest.MaxRefFrames = int.Parse(val, CultureInfo.InvariantCulture);
- }
- }
- else if (i == 17)
- {
- if (videoRequest != null)
- {
- videoRequest.MaxVideoBitDepth = int.Parse(val, CultureInfo.InvariantCulture);
- }
- }
- else if (i == 18)
- {
- if (videoRequest != null)
- {
- videoRequest.Profile = val;
- }
- }
- else if (i == 19)
- {
- // cabac no longer used
- }
- else if (i == 20)
- {
- request.PlaySessionId = val;
- }
- else if (i == 21)
- {
- // api_key
- }
- else if (i == 22)
- {
- request.LiveStreamId = val;
- }
- else if (i == 23)
- {
- // Duplicating ItemId because of MediaMonkey
- }
- else if (i == 24)
- {
- if (videoRequest != null)
- {
- videoRequest.CopyTimestamps = string.Equals("true", val, StringComparison.OrdinalIgnoreCase);
- }
- }
- else if (i == 25)
- {
- if (!string.IsNullOrWhiteSpace(val) && videoRequest != null)
- {
- if (Enum.TryParse(val, out SubtitleDeliveryMethod method))
+ switch (i)
+ {
+ case 0:
+ request.DeviceProfileId = val;
+ break;
+ case 1:
+ request.DeviceId = val;
+ break;
+ case 2:
+ request.MediaSourceId = val;
+ break;
+ case 3:
+ request.Static = string.Equals("true", val, StringComparison.OrdinalIgnoreCase);
+ break;
+ case 4:
+ if (videoRequest != null)
{
- videoRequest.SubtitleMethod = method;
+ videoRequest.VideoCodec = val;
}
- }
- }
- else if (i == 26)
- {
- request.TranscodingMaxAudioChannels = int.Parse(val, CultureInfo.InvariantCulture);
- }
- else if (i == 27)
- {
- if (videoRequest != null)
- {
- videoRequest.EnableSubtitlesInManifest = string.Equals("true", val, StringComparison.OrdinalIgnoreCase);
- }
- }
- else if (i == 28)
- {
- request.Tag = val;
- }
- else if (i == 29)
- {
- if (videoRequest != null)
- {
- videoRequest.RequireAvc = string.Equals("true", val, StringComparison.OrdinalIgnoreCase);
- }
- }
- else if (i == 30)
- {
- request.SubtitleCodec = val;
- }
- else if (i == 31)
- {
- if (videoRequest != null)
- {
- videoRequest.RequireNonAnamorphic = string.Equals("true", val, StringComparison.OrdinalIgnoreCase);
- }
- }
- else if (i == 32)
- {
- if (videoRequest != null)
- {
- videoRequest.DeInterlace = string.Equals("true", val, StringComparison.OrdinalIgnoreCase);
- }
- }
- else if (i == 33)
- {
- request.TranscodeReasons = val;
+
+ break;
+ case 5:
+ request.AudioCodec = val;
+ break;
+ case 6:
+ if (videoRequest != null)
+ {
+ videoRequest.AudioStreamIndex = int.Parse(val, CultureInfo.InvariantCulture);
+ }
+
+ break;
+ case 7:
+ if (videoRequest != null)
+ {
+ videoRequest.SubtitleStreamIndex = int.Parse(val, CultureInfo.InvariantCulture);
+ }
+
+ break;
+ case 8:
+ if (videoRequest != null)
+ {
+ videoRequest.VideoBitRate = int.Parse(val, CultureInfo.InvariantCulture);
+ }
+
+ break;
+ case 9:
+ request.AudioBitRate = int.Parse(val, CultureInfo.InvariantCulture);
+ break;
+ case 10:
+ request.MaxAudioChannels = int.Parse(val, CultureInfo.InvariantCulture);
+ break;
+ case 11:
+ if (videoRequest != null)
+ {
+ videoRequest.MaxFramerate = float.Parse(val, CultureInfo.InvariantCulture);
+ }
+
+ break;
+ case 12:
+ if (videoRequest != null)
+ {
+ videoRequest.MaxWidth = int.Parse(val, CultureInfo.InvariantCulture);
+ }
+
+ break;
+ case 13:
+ if (videoRequest != null)
+ {
+ videoRequest.MaxHeight = int.Parse(val, CultureInfo.InvariantCulture);
+ }
+
+ break;
+ case 14:
+ request.StartTimeTicks = long.Parse(val, CultureInfo.InvariantCulture);
+ break;
+ case 15:
+ if (videoRequest != null)
+ {
+ videoRequest.Level = val;
+ }
+
+ break;
+ case 16:
+ if (videoRequest != null)
+ {
+ videoRequest.MaxRefFrames = int.Parse(val, CultureInfo.InvariantCulture);
+ }
+
+ break;
+ case 17:
+ if (videoRequest != null)
+ {
+ videoRequest.MaxVideoBitDepth = int.Parse(val, CultureInfo.InvariantCulture);
+ }
+
+ break;
+ case 18:
+ if (videoRequest != null)
+ {
+ videoRequest.Profile = val;
+ }
+
+ break;
+ case 19:
+ // cabac no longer used
+ break;
+ case 20:
+ request.PlaySessionId = val;
+ break;
+ case 21:
+ // api_key
+ break;
+ case 22:
+ request.LiveStreamId = val;
+ break;
+ case 23:
+ // Duplicating ItemId because of MediaMonkey
+ break;
+ case 24:
+ if (videoRequest != null)
+ {
+ videoRequest.CopyTimestamps = string.Equals("true", val, StringComparison.OrdinalIgnoreCase);
+ }
+
+ break;
+ case 25:
+ if (!string.IsNullOrWhiteSpace(val) && videoRequest != null)
+ {
+ if (Enum.TryParse(val, out SubtitleDeliveryMethod method))
+ {
+ videoRequest.SubtitleMethod = method;
+ }
+ }
+
+ break;
+ case 26:
+ request.TranscodingMaxAudioChannels = int.Parse(val, CultureInfo.InvariantCulture);
+ break;
+ case 27:
+ if (videoRequest != null)
+ {
+ videoRequest.EnableSubtitlesInManifest = string.Equals("true", val, StringComparison.OrdinalIgnoreCase);
+ }
+
+ break;
+ case 28:
+ request.Tag = val;
+ break;
+ case 29:
+ if (videoRequest != null)
+ {
+ videoRequest.RequireAvc = string.Equals("true", val, StringComparison.OrdinalIgnoreCase);
+ }
+
+ break;
+ case 30:
+ request.SubtitleCodec = val;
+ break;
+ case 31:
+ if (videoRequest != null)
+ {
+ videoRequest.RequireNonAnamorphic = string.Equals("true", val, StringComparison.OrdinalIgnoreCase);
+ }
+
+ break;
+ case 32:
+ if (videoRequest != null)
+ {
+ videoRequest.DeInterlace = string.Equals("true", val, StringComparison.OrdinalIgnoreCase);
+ }
+
+ break;
+ case 33:
+ request.TranscodeReasons = val;
+ break;
}
}
}
@@ -629,15 +609,11 @@ namespace MediaBrowser.Api.Playback
{
throw new ArgumentException("Invalid timeseek header");
}
+
int index = value.IndexOf('-');
- if (index == -1)
- {
- value = value.Substring(Npt.Length);
- }
- else
- {
- value = value.Substring(Npt.Length, index - Npt.Length);
- }
+ value = index == -1
+ ? value.Substring(Npt.Length)
+ : value.Substring(Npt.Length, index - Npt.Length);
if (value.IndexOf(':') == -1)
{
@@ -665,8 +641,10 @@ namespace MediaBrowser.Api.Playback
{
throw new ArgumentException("Invalid timeseek header");
}
+
timeFactor /= 60;
}
+
return TimeSpan.FromSeconds(secondsSum).Ticks;
}
@@ -711,7 +689,7 @@ namespace MediaBrowser.Api.Playback
state.User = UserManager.GetUserById(auth.UserId);
}
- //if ((Request.UserAgent ?? string.Empty).IndexOf("iphone", StringComparison.OrdinalIgnoreCase) != -1 ||
+ // if ((Request.UserAgent ?? string.Empty).IndexOf("iphone", StringComparison.OrdinalIgnoreCase) != -1 ||
// (Request.UserAgent ?? string.Empty).IndexOf("ipad", StringComparison.OrdinalIgnoreCase) != -1 ||
// (Request.UserAgent ?? string.Empty).IndexOf("ipod", StringComparison.OrdinalIgnoreCase) != -1)
//{
@@ -742,9 +720,9 @@ namespace MediaBrowser.Api.Playback
state.IsInputVideo = string.Equals(item.MediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase);
- //var primaryImage = item.GetImageInfo(ImageType.Primary, 0) ??
+ // var primaryImage = item.GetImageInfo(ImageType.Primary, 0) ??
// item.Parents.Select(i => i.GetImageInfo(ImageType.Primary, 0)).FirstOrDefault(i => i != null);
- //if (primaryImage != null)
+ // if (primaryImage != null)
//{
// state.AlbumCoverPath = primaryImage.Path;
//}
@@ -818,7 +796,7 @@ namespace MediaBrowser.Api.Playback
EncodingHelper.TryStreamCopy(state);
}
- if (state.OutputVideoBitrate.HasValue && !string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase))
+ if (state.OutputVideoBitrate.HasValue && !EncodingHelper.IsCopyCodec(state.OutputVideoCodec))
{
var resolution = ResolutionNormalizer.Normalize(
state.VideoStream?.BitRate,
@@ -856,21 +834,11 @@ namespace MediaBrowser.Api.Playback
{
state.DeviceProfile = DlnaManager.GetProfile(state.Request.DeviceProfileId);
}
- else
+ else if (!string.IsNullOrWhiteSpace(state.Request.DeviceId))
{
- if (!string.IsNullOrWhiteSpace(state.Request.DeviceId))
- {
- var caps = DeviceManager.GetCapabilities(state.Request.DeviceId);
+ var caps = DeviceManager.GetCapabilities(state.Request.DeviceId);
- if (caps != null)
- {
- state.DeviceProfile = caps.DeviceProfile;
- }
- else
- {
- state.DeviceProfile = DlnaManager.GetProfile(headers);
- }
- }
+ state.DeviceProfile = caps == null ? DlnaManager.GetProfile(headers) : caps.DeviceProfile;
}
var profile = state.DeviceProfile;
@@ -921,7 +889,7 @@ namespace MediaBrowser.Api.Playback
if (transcodingProfile != null)
{
state.EstimateContentLength = transcodingProfile.EstimateContentLength;
- //state.EnableMpegtsM2TsMode = transcodingProfile.EnableMpegtsM2TsMode;
+ // state.EnableMpegtsM2TsMode = transcodingProfile.EnableMpegtsM2TsMode;
state.TranscodeSeekInfo = transcodingProfile.TranscodeSeekInfo;
if (state.VideoRequest != null)
diff --git a/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs b/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs
index 0cbfe4bdf..418cd92b3 100644
--- a/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs
+++ b/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs
@@ -20,12 +20,12 @@ using Microsoft.Extensions.Logging;
namespace MediaBrowser.Api.Playback.Hls
{
/// <summary>
- /// Class BaseHlsService
+ /// Class BaseHlsService.
/// </summary>
public abstract class BaseHlsService : BaseStreamingService
{
public BaseHlsService(
- ILogger logger,
+ ILogger<BaseHlsService> logger,
IServerConfigurationManager serverConfigurationManager,
IHttpResultFactory httpResultFactory,
IUserManager userManager,
@@ -140,12 +140,13 @@ namespace MediaBrowser.Api.Playback.Hls
if (isLive)
{
- job = job ?? ApiEntryPoint.Instance.OnTranscodeBeginRequest(playlist, TranscodingJobType);
+ job ??= ApiEntryPoint.Instance.OnTranscodeBeginRequest(playlist, TranscodingJobType);
if (job != null)
{
ApiEntryPoint.Instance.OnTranscodeEndRequest(job);
}
+
return ResultFactory.GetResult(GetLivePlaylistText(playlist, state.SegmentLength), MimeTypes.GetMimeType("playlist.m3u8"), new Dictionary<string, string>());
}
@@ -156,7 +157,7 @@ namespace MediaBrowser.Api.Playback.Hls
var playlistText = GetMasterPlaylistFileText(playlist, videoBitrate + audioBitrate, baselineStreamBitrate);
- job = job ?? ApiEntryPoint.Instance.OnTranscodeBeginRequest(playlist, TranscodingJobType);
+ job ??= ApiEntryPoint.Instance.OnTranscodeBeginRequest(playlist, TranscodingJobType);
if (job != null)
{
@@ -168,22 +169,19 @@ namespace MediaBrowser.Api.Playback.Hls
private string GetLivePlaylistText(string path, int segmentLength)
{
- using (var stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
- {
- using (var reader = new StreamReader(stream))
- {
- var text = reader.ReadToEnd();
+ using var stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
+ using var reader = new StreamReader(stream);
- text = text.Replace("#EXTM3U", "#EXTM3U\n#EXT-X-PLAYLIST-TYPE:EVENT");
+ var text = reader.ReadToEnd();
- var newDuration = "#EXT-X-TARGETDURATION:" + segmentLength.ToString(CultureInfo.InvariantCulture);
+ text = text.Replace("#EXTM3U", "#EXTM3U\n#EXT-X-PLAYLIST-TYPE:EVENT");
- text = text.Replace("#EXT-X-TARGETDURATION:" + (segmentLength - 1).ToString(CultureInfo.InvariantCulture), newDuration, StringComparison.OrdinalIgnoreCase);
- //text = text.Replace("#EXT-X-TARGETDURATION:" + (segmentLength + 1).ToString(CultureInfo.InvariantCulture), newDuration, StringComparison.OrdinalIgnoreCase);
+ var newDuration = "#EXT-X-TARGETDURATION:" + segmentLength.ToString(CultureInfo.InvariantCulture);
- return text;
- }
- }
+ text = text.Replace("#EXT-X-TARGETDURATION:" + (segmentLength - 1).ToString(CultureInfo.InvariantCulture), newDuration, StringComparison.OrdinalIgnoreCase);
+ // text = text.Replace("#EXT-X-TARGETDURATION:" + (segmentLength + 1).ToString(CultureInfo.InvariantCulture), newDuration, StringComparison.OrdinalIgnoreCase);
+
+ return text;
}
private string GetMasterPlaylistFileText(string firstPlaylist, int bitrate, int baselineStreamBitrate)
@@ -212,29 +210,29 @@ namespace MediaBrowser.Api.Playback.Hls
try
{
// Need to use FileShare.ReadWrite because we're reading the file at the same time it's being written
- using (var fileStream = GetPlaylistFileStream(playlist))
+ var fileStream = GetPlaylistFileStream(playlist);
+ await using (fileStream.ConfigureAwait(false))
{
- using (var reader = new StreamReader(fileStream))
+ using var reader = new StreamReader(fileStream);
+ var count = 0;
+
+ while (!reader.EndOfStream)
{
- var count = 0;
+ var line = await reader.ReadLineAsync().ConfigureAwait(false);
- while (!reader.EndOfStream)
+ if (line.IndexOf("#EXTINF:", StringComparison.OrdinalIgnoreCase) != -1)
{
- var line = reader.ReadLine();
-
- if (line.IndexOf("#EXTINF:", StringComparison.OrdinalIgnoreCase) != -1)
+ count++;
+ if (count >= segmentCount)
{
- count++;
- if (count >= segmentCount)
- {
- Logger.LogDebug("Finished waiting for {0} segments in {1}", segmentCount, playlist);
- return;
- }
+ Logger.LogDebug("Finished waiting for {0} segments in {1}", segmentCount, playlist);
+ return;
}
}
- await Task.Delay(100, cancellationToken).ConfigureAwait(false);
}
}
+
+ await Task.Delay(100, cancellationToken).ConfigureAwait(false);
}
catch (IOException)
{
@@ -247,17 +245,13 @@ namespace MediaBrowser.Api.Playback.Hls
protected Stream GetPlaylistFileStream(string path)
{
- var tmpPath = path + ".tmp";
- tmpPath = path;
-
- try
- {
- return new FileStream(tmpPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, IODefaults.FileStreamBufferSize, FileOptions.SequentialScan);
- }
- catch (IOException)
- {
- return new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, IODefaults.FileStreamBufferSize, FileOptions.SequentialScan);
- }
+ return new FileStream(
+ path,
+ FileMode.Open,
+ FileAccess.Read,
+ FileShare.ReadWrite,
+ IODefaults.FileStreamBufferSize,
+ FileOptions.SequentialScan);
}
protected override string GetCommandLineArguments(string outputPath, EncodingOptions encodingOptions, StreamState state, bool isEncoding)
diff --git a/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs b/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs
index 3348a3187..fe5f980b1 100644
--- a/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs
+++ b/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs
@@ -94,7 +94,7 @@ namespace MediaBrowser.Api.Playback.Hls
public class DynamicHlsService : BaseHlsService
{
public DynamicHlsService(
- ILogger logger,
+ ILogger<DynamicHlsService> logger,
IServerConfigurationManager serverConfigurationManager,
IHttpResultFactory httpResultFactory,
IUserManager userManager,
@@ -234,6 +234,7 @@ namespace MediaBrowser.Api.Playback.Hls
Logger.LogDebug("Starting transcoding because segmentGap is {0} and max allowed gap is {1}. requestedIndex={2}", requestedIndex - currentTranscodingIndex.Value, segmentGapRequiringTranscodingChange, requestedIndex);
startTranscoding = true;
}
+
if (startTranscoding)
{
// If the playlist doesn't already exist, startup ffmpeg
@@ -257,7 +258,7 @@ namespace MediaBrowser.Api.Playback.Hls
throw;
}
- //await WaitForMinimumSegmentCount(playlistPath, 1, cancellationTokenSource.Token).ConfigureAwait(false);
+ // await WaitForMinimumSegmentCount(playlistPath, 1, cancellationTokenSource.Token).ConfigureAwait(false);
}
else
{
@@ -277,14 +278,14 @@ namespace MediaBrowser.Api.Playback.Hls
}
}
- //Logger.LogInformation("waiting for {0}", segmentPath);
- //while (!File.Exists(segmentPath))
+ // Logger.LogInformation("waiting for {0}", segmentPath);
+ // while (!File.Exists(segmentPath))
//{
// await Task.Delay(50, cancellationToken).ConfigureAwait(false);
//}
Logger.LogDebug("returning {0} [general case]", segmentPath);
- job = job ?? ApiEntryPoint.Instance.OnTranscodeBeginRequest(playlistPath, TranscodingJobType);
+ job ??= ApiEntryPoint.Instance.OnTranscodeBeginRequest(playlistPath, TranscodingJobType);
return await GetSegmentResult(state, playlistPath, segmentPath, segmentExtension, requestedIndex, job, cancellationToken).ConfigureAwait(false);
}
@@ -438,8 +439,7 @@ namespace MediaBrowser.Api.Playback.Hls
{
var segmentId = "0";
- var segmentRequest = request as GetHlsVideoSegment;
- if (segmentRequest != null)
+ if (request is GetHlsVideoSegment segmentRequest)
{
segmentId = segmentRequest.SegmentId;
}
@@ -519,6 +519,7 @@ namespace MediaBrowser.Api.Playback.Hls
{
Logger.LogDebug("serving {0} as it's on disk and transcoding stopped", segmentPath);
}
+
cancellationToken.ThrowIfCancellationRequested();
}
else
@@ -690,8 +691,7 @@ namespace MediaBrowser.Api.Playback.Hls
return false;
}
- var request = state.Request as IMasterHlsRequest;
- if (request != null && !request.EnableAdaptiveBitrateStreaming)
+ if (state.Request is IMasterHlsRequest request && !request.EnableAdaptiveBitrateStreaming)
{
return false;
}
@@ -702,12 +702,12 @@ namespace MediaBrowser.Api.Playback.Hls
return false;
}
- if (string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase))
+ if (EncodingHelper.IsCopyCodec(state.OutputVideoCodec))
{
return false;
}
- if (string.Equals(state.OutputAudioCodec, "copy", StringComparison.OrdinalIgnoreCase))
+ if (EncodingHelper.IsCopyCodec(state.OutputAudioCodec))
{
return false;
}
@@ -719,25 +719,204 @@ namespace MediaBrowser.Api.Playback.Hls
// Having problems in android
return false;
- //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 (EncodingHelper.IsCopyCodec(state.OutputVideoCodec)
+ && 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 != null)
+ {
+ framerate = Math.Round(state.VideoStream.RealFrameRate.GetValueOrDefault(), 3);
+ }
+
+ if (framerate.HasValue)
+ {
+ builder.Append(",FRAME-RATE=")
+ .Append(framerate.Value);
+ }
+ }
+
+ /// <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());
+ }
}
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
- //if (state.TargetFramerate.HasValue)
- //{
- // header += string.Format(",FRAME-RATE=\"{0}\"", state.TargetFramerate.Value.ToString(CultureInfo.InvariantCulture));
- //}
+ AppendPlaylistCodecsField(builder, state);
+
+ AppendPlaylistResolutionField(builder, state);
+
+ AppendPlaylistFramerateField(builder, state);
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);
}
@@ -795,7 +974,7 @@ namespace MediaBrowser.Api.Playback.Hls
var queryStringIndex = Request.RawUrl.IndexOf('?');
var queryString = queryStringIndex == -1 ? string.Empty : Request.RawUrl.Substring(queryStringIndex);
- //if ((Request.UserAgent ?? string.Empty).IndexOf("roku", StringComparison.OrdinalIgnoreCase) != -1)
+ // if ((Request.UserAgent ?? string.Empty).IndexOf("roku", StringComparison.OrdinalIgnoreCase) != -1)
//{
// queryString = string.Empty;
//}
@@ -829,7 +1008,7 @@ namespace MediaBrowser.Api.Playback.Hls
if (!state.IsOutputVideo)
{
- if (string.Equals(audioCodec, "copy", StringComparison.OrdinalIgnoreCase))
+ if (EncodingHelper.IsCopyCodec(audioCodec))
{
return "-acodec copy";
}
@@ -857,11 +1036,11 @@ namespace MediaBrowser.Api.Playback.Hls
return string.Join(" ", audioTranscodeParams.ToArray());
}
- if (string.Equals(audioCodec, "copy", StringComparison.OrdinalIgnoreCase))
+ if (EncodingHelper.IsCopyCodec(audioCodec))
{
var videoCodec = EncodingHelper.GetVideoEncoder(state, encodingOptions);
- if (string.Equals(videoCodec, "copy", StringComparison.OrdinalIgnoreCase) && state.EnableBreakOnNonKeyFrames(videoCodec))
+ if (EncodingHelper.IsCopyCodec(videoCodec) && state.EnableBreakOnNonKeyFrames(videoCodec))
{
return "-codec:a:0 copy -copypriorss:a:0 0";
}
@@ -912,7 +1091,7 @@ namespace MediaBrowser.Api.Playback.Hls
// }
// See if we can save come cpu cycles by avoiding encoding
- if (string.Equals(codec, "copy", StringComparison.OrdinalIgnoreCase))
+ if (EncodingHelper.IsCopyCodec(codec))
{
if (state.VideoStream != null && !string.Equals(state.VideoStream.NalLengthSize, "0", StringComparison.OrdinalIgnoreCase))
{
@@ -923,7 +1102,7 @@ namespace MediaBrowser.Api.Playback.Hls
}
}
- //args += " -flags -global_header";
+ // args += " -flags -global_header";
}
else
{
@@ -936,7 +1115,7 @@ namespace MediaBrowser.Api.Playback.Hls
var framerate = state.VideoStream?.RealFrameRate;
- if (framerate != null && framerate.HasValue)
+ if (framerate.HasValue)
{
// This is to make sure keyframe interval is limited to our segment,
// as forcing keyframes is not enough.
@@ -965,7 +1144,7 @@ namespace MediaBrowser.Api.Playback.Hls
args += " " + keyFrameArg + gopArg;
}
- //args += " -mixed-refs 0 -refs 3 -x264opts b_pyramid=0:weightb=0:weightp=0";
+ // args += " -mixed-refs 0 -refs 3 -x264opts b_pyramid=0:weightb=0:weightp=0";
var hasGraphicalSubs = state.SubtitleStream != null && !state.SubtitleStream.IsTextSubtitleStream && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode;
@@ -987,7 +1166,7 @@ namespace MediaBrowser.Api.Playback.Hls
args += " -start_at_zero";
}
- //args += " -flags -global_header";
+ // args += " -flags -global_header";
}
if (!string.IsNullOrEmpty(state.OutputVideoSync))
diff --git a/MediaBrowser.Api/Playback/Hls/HlsCodecStringFactory.cs b/MediaBrowser.Api/Playback/Hls/HlsCodecStringFactory.cs
new file mode 100644
index 000000000..3bbb77a65
--- /dev/null
+++ b/MediaBrowser.Api/Playback/Hls/HlsCodecStringFactory.cs
@@ -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";
+ }
+ }
+}
diff --git a/MediaBrowser.Api/Playback/Hls/HlsSegmentService.cs b/MediaBrowser.Api/Playback/Hls/HlsSegmentService.cs
index 87ccde2e0..8a3d00283 100644
--- a/MediaBrowser.Api/Playback/Hls/HlsSegmentService.cs
+++ b/MediaBrowser.Api/Playback/Hls/HlsSegmentService.cs
@@ -13,7 +13,7 @@ using Microsoft.Extensions.Logging;
namespace MediaBrowser.Api.Playback.Hls
{
/// <summary>
- /// Class GetHlsAudioSegment
+ /// Class GetHlsAudioSegment.
/// </summary>
// Can't require authentication just yet due to seeing some requests come from Chrome without full query string
//[Authenticated]
@@ -37,7 +37,7 @@ namespace MediaBrowser.Api.Playback.Hls
}
/// <summary>
- /// Class GetHlsVideoSegment
+ /// Class GetHlsVideoSegment.
/// </summary>
[Route("/Videos/{Id}/hls/{PlaylistId}/stream.m3u8", "GET")]
[Authenticated]
@@ -66,7 +66,7 @@ namespace MediaBrowser.Api.Playback.Hls
}
/// <summary>
- /// Class GetHlsVideoSegment
+ /// Class GetHlsVideoSegment.
/// </summary>
// Can't require authentication just yet due to seeing some requests come from Chrome without full query string
//[Authenticated]
diff --git a/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs b/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs
index d1c53c1c1..9562f9953 100644
--- a/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs
+++ b/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs
@@ -22,7 +22,7 @@ namespace MediaBrowser.Api.Playback.Hls
}
/// <summary>
- /// Class VideoHlsService
+ /// Class VideoHlsService.
/// </summary>
[Authenticated]
public class VideoHlsService : BaseHlsService
@@ -72,7 +72,7 @@ namespace MediaBrowser.Api.Playback.Hls
{
var codec = EncodingHelper.GetAudioEncoder(state);
- if (string.Equals(codec, "copy", StringComparison.OrdinalIgnoreCase))
+ if (EncodingHelper.IsCopyCodec(codec))
{
return "-codec:a:0 copy";
}
diff --git a/MediaBrowser.Api/Playback/MediaInfoService.cs b/MediaBrowser.Api/Playback/MediaInfoService.cs
index d74ec3ca6..b7ca1a031 100644
--- a/MediaBrowser.Api/Playback/MediaInfoService.cs
+++ b/MediaBrowser.Api/Playback/MediaInfoService.cs
@@ -9,6 +9,7 @@ using System.Linq;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
+using Jellyfin.Data.Enums;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Devices;
@@ -79,7 +80,7 @@ namespace MediaBrowser.Api.Playback
private readonly IAuthorizationContext _authContext;
public MediaInfoService(
- ILogger logger,
+ ILogger<MediaInfoService> logger,
IServerConfigurationManager serverConfigurationManager,
IHttpResultFactory httpResultFactory,
IMediaSourceManager mediaSourceManager,
@@ -234,7 +235,7 @@ namespace MediaBrowser.Api.Playback
OpenToken = mediaSource.OpenToken
}).ConfigureAwait(false);
- info.MediaSources = new MediaSourceInfo[] { openStreamResult.MediaSource };
+ info.MediaSources = new[] { openStreamResult.MediaSource };
}
}
@@ -289,7 +290,7 @@ namespace MediaBrowser.Api.Playback
{
var mediaSource = await _mediaSourceManager.GetLiveStream(liveStreamId, CancellationToken.None).ConfigureAwait(false);
- mediaSources = new MediaSourceInfo[] { mediaSource };
+ mediaSources = new[] { mediaSource };
}
if (mediaSources.Length == 0)
@@ -366,7 +367,7 @@ namespace MediaBrowser.Api.Playback
var options = new VideoOptions
{
- MediaSources = new MediaSourceInfo[] { mediaSource },
+ MediaSources = new[] { mediaSource },
Context = EncodingContext.Streaming,
DeviceId = auth.DeviceId,
ItemId = item.Id,
@@ -400,21 +401,24 @@ namespace MediaBrowser.Api.Playback
if (item is Audio)
{
- Logger.LogInformation("User policy for {0}. EnableAudioPlaybackTranscoding: {1}", user.Name, user.Policy.EnableAudioPlaybackTranscoding);
+ Logger.LogInformation(
+ "User policy for {0}. EnableAudioPlaybackTranscoding: {1}",
+ user.Username,
+ user.HasPermission(PermissionKind.EnableAudioPlaybackTranscoding));
}
else
{
Logger.LogInformation("User policy for {0}. EnablePlaybackRemuxing: {1} EnableVideoPlaybackTranscoding: {2} EnableAudioPlaybackTranscoding: {3}",
- user.Name,
- user.Policy.EnablePlaybackRemuxing,
- user.Policy.EnableVideoPlaybackTranscoding,
- user.Policy.EnableAudioPlaybackTranscoding);
+ user.Username,
+ user.HasPermission(PermissionKind.EnablePlaybackRemuxing),
+ user.HasPermission(PermissionKind.EnableVideoPlaybackTranscoding),
+ user.HasPermission(PermissionKind.EnableAudioPlaybackTranscoding));
}
// Beginning of Playback Determination: Attempt DirectPlay first
if (mediaSource.SupportsDirectPlay)
{
- if (mediaSource.IsRemote && user.Policy.ForceRemoteSourceTranscoding)
+ if (mediaSource.IsRemote && user.HasPermission(PermissionKind.ForceRemoteSourceTranscoding))
{
mediaSource.SupportsDirectPlay = false;
}
@@ -428,14 +432,16 @@ namespace MediaBrowser.Api.Playback
if (item is Audio)
{
- if (!user.Policy.EnableAudioPlaybackTranscoding)
+ if (!user.HasPermission(PermissionKind.EnableAudioPlaybackTranscoding))
{
options.ForceDirectPlay = true;
}
}
else if (item is Video)
{
- if (!user.Policy.EnableAudioPlaybackTranscoding && !user.Policy.EnableVideoPlaybackTranscoding && !user.Policy.EnablePlaybackRemuxing)
+ if (!user.HasPermission(PermissionKind.EnableAudioPlaybackTranscoding)
+ && !user.HasPermission(PermissionKind.EnableVideoPlaybackTranscoding)
+ && !user.HasPermission(PermissionKind.EnablePlaybackRemuxing))
{
options.ForceDirectPlay = true;
}
@@ -463,7 +469,7 @@ namespace MediaBrowser.Api.Playback
if (mediaSource.SupportsDirectStream)
{
- if (mediaSource.IsRemote && user.Policy.ForceRemoteSourceTranscoding)
+ if (mediaSource.IsRemote && user.HasPermission(PermissionKind.ForceRemoteSourceTranscoding))
{
mediaSource.SupportsDirectStream = false;
}
@@ -473,14 +479,16 @@ namespace MediaBrowser.Api.Playback
if (item is Audio)
{
- if (!user.Policy.EnableAudioPlaybackTranscoding)
+ if (!user.HasPermission(PermissionKind.EnableAudioPlaybackTranscoding))
{
options.ForceDirectStream = true;
}
}
else if (item is Video)
{
- if (!user.Policy.EnableAudioPlaybackTranscoding && !user.Policy.EnableVideoPlaybackTranscoding && !user.Policy.EnablePlaybackRemuxing)
+ if (!user.HasPermission(PermissionKind.EnableAudioPlaybackTranscoding)
+ && !user.HasPermission(PermissionKind.EnableVideoPlaybackTranscoding)
+ && !user.HasPermission(PermissionKind.EnablePlaybackRemuxing))
{
options.ForceDirectStream = true;
}
@@ -512,7 +520,7 @@ namespace MediaBrowser.Api.Playback
? streamBuilder.BuildAudioItem(options)
: streamBuilder.BuildVideoItem(options);
- if (mediaSource.IsRemote && user.Policy.ForceRemoteSourceTranscoding)
+ if (mediaSource.IsRemote && user.HasPermission(PermissionKind.ForceRemoteSourceTranscoding))
{
if (streamInfo != null)
{
@@ -520,10 +528,7 @@ namespace MediaBrowser.Api.Playback
streamInfo.StartPositionTicks = startTimeTicks;
mediaSource.TranscodingUrl = streamInfo.ToUrl("-", auth.Token).TrimStart('-');
mediaSource.TranscodingUrl += "&allowVideoStreamCopy=false";
- if (!allowAudioStreamCopy)
- {
- mediaSource.TranscodingUrl += "&allowAudioStreamCopy=false";
- }
+ mediaSource.TranscodingUrl += "&allowAudioStreamCopy=false";
mediaSource.TranscodingContainer = streamInfo.Container;
mediaSource.TranscodingSubProtocol = streamInfo.SubProtocol;
@@ -546,10 +551,12 @@ namespace MediaBrowser.Api.Playback
{
mediaSource.TranscodingUrl += "&allowVideoStreamCopy=false";
}
+
if (!allowAudioStreamCopy)
{
mediaSource.TranscodingUrl += "&allowAudioStreamCopy=false";
}
+
mediaSource.TranscodingContainer = streamInfo.Container;
mediaSource.TranscodingSubProtocol = streamInfo.SubProtocol;
}
@@ -572,18 +579,17 @@ namespace MediaBrowser.Api.Playback
{
attachment.DeliveryUrl = string.Format(
CultureInfo.InvariantCulture,
- "{0}/Videos/{1}/{2}/Attachments/{3}",
- ServerConfigurationManager.Configuration.BaseUrl,
+ "/Videos/{0}/{1}/Attachments/{2}",
item.Id,
mediaSource.Id,
attachment.Index);
}
}
- private long? GetMaxBitrate(long? clientMaxBitrate, User user)
+ private long? GetMaxBitrate(long? clientMaxBitrate, Jellyfin.Data.Entities.User user)
{
var maxBitrate = clientMaxBitrate;
- var remoteClientMaxBitrate = user == null ? 0 : user.Policy.RemoteClientBitrateLimit;
+ var remoteClientMaxBitrate = user?.RemoteClientBitrateLimit ?? 0;
if (remoteClientMaxBitrate <= 0)
{
@@ -642,7 +648,6 @@ namespace MediaBrowser.Api.Playback
}
return 1;
-
}).ThenBy(i =>
{
// Let's assume direct streaming a file is just as desirable as direct playing a remote url
@@ -652,7 +657,6 @@ namespace MediaBrowser.Api.Playback
}
return 1;
-
}).ThenBy(i =>
{
return i.Protocol switch
@@ -662,21 +666,12 @@ namespace MediaBrowser.Api.Playback
};
}).ThenBy(i =>
{
- if (maxBitrate.HasValue)
+ if (maxBitrate.HasValue && i.Bitrate.HasValue)
{
- if (i.Bitrate.HasValue)
- {
- if (i.Bitrate.Value <= maxBitrate.Value)
- {
- return 0;
- }
-
- return 2;
- }
+ return i.Bitrate.Value <= maxBitrate.Value ? 0 : 2;
}
return 1;
-
}).ThenBy(originalList.IndexOf)
.ToArray();
}
diff --git a/MediaBrowser.Api/Playback/Progressive/AudioService.cs b/MediaBrowser.Api/Playback/Progressive/AudioService.cs
index 8d1e3a3f2..d51787df2 100644
--- a/MediaBrowser.Api/Playback/Progressive/AudioService.cs
+++ b/MediaBrowser.Api/Playback/Progressive/AudioService.cs
@@ -15,7 +15,7 @@ using Microsoft.Extensions.Logging;
namespace MediaBrowser.Api.Playback.Progressive
{
/// <summary>
- /// Class GetAudioStream
+ /// Class GetAudioStream.
/// </summary>
[Route("/Audio/{Id}/stream.{Container}", "GET", Summary = "Gets an audio stream")]
[Route("/Audio/{Id}/stream", "GET", Summary = "Gets an audio stream")]
@@ -26,14 +26,14 @@ namespace MediaBrowser.Api.Playback.Progressive
}
/// <summary>
- /// Class AudioService
+ /// Class AudioService.
/// </summary>
// TODO: In order to autheneticate this in the future, Dlna playback will require updating
//[Authenticated]
public class AudioService : BaseProgressiveStreamingService
{
public AudioService(
- ILogger logger,
+ ILogger<AudioService> logger,
IServerConfigurationManager serverConfigurationManager,
IHttpResultFactory httpResultFactory,
IHttpClient httpClient,
diff --git a/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs b/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs
index ed68219c9..2ebf0e420 100644
--- a/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs
+++ b/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs
@@ -21,14 +21,14 @@ using Microsoft.Net.Http.Headers;
namespace MediaBrowser.Api.Playback.Progressive
{
/// <summary>
- /// Class BaseProgressiveStreamingService
+ /// Class BaseProgressiveStreamingService.
/// </summary>
public abstract class BaseProgressiveStreamingService : BaseStreamingService
{
protected IHttpClient HttpClient { get; private set; }
public BaseProgressiveStreamingService(
- ILogger logger,
+ ILogger<BaseProgressiveStreamingService> logger,
IServerConfigurationManager serverConfigurationManager,
IHttpResultFactory httpResultFactory,
IHttpClient httpClient,
@@ -88,14 +88,17 @@ namespace MediaBrowser.Api.Playback.Progressive
{
return ".ts";
}
+
if (string.Equals(videoCodec, "theora", StringComparison.OrdinalIgnoreCase))
{
return ".ogv";
}
+
if (string.Equals(videoCodec, "vpx", StringComparison.OrdinalIgnoreCase))
{
return ".webm";
}
+
if (string.Equals(videoCodec, "wmv", StringComparison.OrdinalIgnoreCase))
{
return ".asf";
@@ -111,14 +114,17 @@ namespace MediaBrowser.Api.Playback.Progressive
{
return ".aac";
}
+
if (string.Equals("mp3", audioCodec, StringComparison.OrdinalIgnoreCase))
{
return ".mp3";
}
+
if (string.Equals("vorbis", audioCodec, StringComparison.OrdinalIgnoreCase))
{
return ".ogg";
}
+
if (string.Equals("wma", audioCodec, StringComparison.OrdinalIgnoreCase))
{
return ".wma";
@@ -231,7 +237,7 @@ namespace MediaBrowser.Api.Playback.Progressive
}
//// Not static but transcode cache file exists
- //if (isTranscodeCached && state.VideoRequest == null)
+ // if (isTranscodeCached && state.VideoRequest == null)
//{
// var contentType = state.GetMimeType(outputPath);
diff --git a/MediaBrowser.Api/Playback/Progressive/ProgressiveStreamWriter.cs b/MediaBrowser.Api/Playback/Progressive/ProgressiveStreamWriter.cs
index a53b848f9..b70fff128 100644
--- a/MediaBrowser.Api/Playback/Progressive/ProgressiveStreamWriter.cs
+++ b/MediaBrowser.Api/Playback/Progressive/ProgressiveStreamWriter.cs
@@ -23,6 +23,7 @@ namespace MediaBrowser.Api.Playback.Progressive
private long _bytesWritten = 0;
public long StartPosition { get; set; }
+
public bool AllowEndOfFile = true;
private readonly IDirectStreamProvider _directStreamProvider;
@@ -96,8 +97,8 @@ namespace MediaBrowser.Api.Playback.Progressive
bytesRead = await CopyToInternalAsyncWithSyncRead(inputStream, outputStream, cancellationToken).ConfigureAwait(false);
}
- //var position = fs.Position;
- //_logger.LogDebug("Streamed {0} bytes to position {1} from file {2}", bytesRead, position, path);
+ // var position = fs.Position;
+ // _logger.LogDebug("Streamed {0} bytes to position {1} from file {2}", bytesRead, position, path);
if (bytesRead == 0)
{
@@ -105,6 +106,7 @@ namespace MediaBrowser.Api.Playback.Progressive
{
eofCount++;
}
+
await Task.Delay(100, cancellationToken).ConfigureAwait(false);
}
else
diff --git a/MediaBrowser.Api/Playback/Progressive/VideoService.cs b/MediaBrowser.Api/Playback/Progressive/VideoService.cs
index 4de81655c..c3f6b905c 100644
--- a/MediaBrowser.Api/Playback/Progressive/VideoService.cs
+++ b/MediaBrowser.Api/Playback/Progressive/VideoService.cs
@@ -15,7 +15,7 @@ using Microsoft.Extensions.Logging;
namespace MediaBrowser.Api.Playback.Progressive
{
/// <summary>
- /// Class GetVideoStream
+ /// Class GetVideoStream.
/// </summary>
[Route("/Videos/{Id}/stream.mpegts", "GET")]
[Route("/Videos/{Id}/stream.ts", "GET")]
@@ -59,11 +59,10 @@ namespace MediaBrowser.Api.Playback.Progressive
[Route("/Videos/{Id}/stream", "HEAD")]
public class GetVideoStream : VideoStreamRequest
{
-
}
/// <summary>
- /// Class VideoService
+ /// Class VideoService.
/// </summary>
// TODO: In order to autheneticate this in the future, Dlna playback will require updating
//[Authenticated]
diff --git a/MediaBrowser.Api/Playback/StaticRemoteStreamWriter.cs b/MediaBrowser.Api/Playback/StaticRemoteStreamWriter.cs
index 3b8b29995..7e2e337ad 100644
--- a/MediaBrowser.Api/Playback/StaticRemoteStreamWriter.cs
+++ b/MediaBrowser.Api/Playback/StaticRemoteStreamWriter.cs
@@ -8,17 +8,17 @@ using MediaBrowser.Model.Services;
namespace MediaBrowser.Api.Playback
{
/// <summary>
- /// Class StaticRemoteStreamWriter
+ /// Class StaticRemoteStreamWriter.
/// </summary>
public class StaticRemoteStreamWriter : IAsyncStreamWriter, IHasHeaders
{
/// <summary>
- /// The _input stream
+ /// The _input stream.
/// </summary>
private readonly HttpResponseInfo _response;
/// <summary>
- /// The _options
+ /// The _options.
/// </summary>
private readonly IDictionary<string, string> _options = new Dictionary<string, string>();
diff --git a/MediaBrowser.Api/Playback/StreamRequest.cs b/MediaBrowser.Api/Playback/StreamRequest.cs
index 9ba8eda91..67c334e48 100644
--- a/MediaBrowser.Api/Playback/StreamRequest.cs
+++ b/MediaBrowser.Api/Playback/StreamRequest.cs
@@ -4,7 +4,7 @@ using MediaBrowser.Model.Services;
namespace MediaBrowser.Api.Playback
{
/// <summary>
- /// Class StreamRequest
+ /// Class StreamRequest.
/// </summary>
public class StreamRequest : BaseEncodingJobOptions
{
@@ -12,11 +12,15 @@ namespace MediaBrowser.Api.Playback
public string DeviceProfileId { get; set; }
public string Params { get; set; }
+
public string PlaySessionId { get; set; }
+
public string Tag { get; set; }
+
public string SegmentContainer { get; set; }
public int? SegmentLength { get; set; }
+
public int? MinSegments { get; set; }
}
diff --git a/MediaBrowser.Api/Playback/StreamState.cs b/MediaBrowser.Api/Playback/StreamState.cs
index d5d2f58c0..c244b0033 100644
--- a/MediaBrowser.Api/Playback/StreamState.cs
+++ b/MediaBrowser.Api/Playback/StreamState.cs
@@ -42,7 +42,7 @@ namespace MediaBrowser.Api.Playback
return Request.SegmentLength.Value;
}
- if (string.Equals(OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase))
+ if (EncodingHelper.IsCopyCodec(OutputVideoCodec))
{
var userAgent = UserAgent ?? string.Empty;
diff --git a/MediaBrowser.Api/Playback/UniversalAudioService.cs b/MediaBrowser.Api/Playback/UniversalAudioService.cs
index cbf981dfe..d5d78cf37 100644
--- a/MediaBrowser.Api/Playback/UniversalAudioService.cs
+++ b/MediaBrowser.Api/Playback/UniversalAudioService.cs
@@ -37,10 +37,13 @@ namespace MediaBrowser.Api.Playback
public string DeviceId { get; set; }
public Guid UserId { get; set; }
+
public string AudioCodec { get; set; }
+
public string Container { get; set; }
public int? MaxAudioChannels { get; set; }
+
public int? TranscodingAudioChannels { get; set; }
public long? MaxStreamingBitrate { get; set; }
@@ -49,12 +52,17 @@ namespace MediaBrowser.Api.Playback
public long? StartTimeTicks { get; set; }
public string TranscodingContainer { get; set; }
+
public string TranscodingProtocol { get; set; }
+
public int? MaxAudioSampleRate { get; set; }
+
public int? MaxAudioBitDepth { get; set; }
public bool EnableRedirection { get; set; }
+
public bool EnableRemoteMedia { get; set; }
+
public bool BreakOnNonKeyFrames { get; set; }
public BaseUniversalRequest()
@@ -75,9 +83,11 @@ namespace MediaBrowser.Api.Playback
public class UniversalAudioService : BaseApiService
{
private readonly EncodingHelper _encodingHelper;
+ private readonly ILoggerFactory _loggerFactory;
public UniversalAudioService(
ILogger<UniversalAudioService> logger,
+ ILoggerFactory loggerFactory,
IServerConfigurationManager serverConfigurationManager,
IHttpResultFactory httpResultFactory,
IHttpClient httpClient,
@@ -108,19 +118,31 @@ namespace MediaBrowser.Api.Playback
AuthorizationContext = authorizationContext;
NetworkManager = networkManager;
_encodingHelper = encodingHelper;
+ _loggerFactory = loggerFactory;
}
protected IHttpClient HttpClient { get; private set; }
+
protected IUserManager UserManager { get; private set; }
+
protected ILibraryManager LibraryManager { get; private set; }
+
protected IIsoManager IsoManager { get; private set; }
+
protected IMediaEncoder MediaEncoder { get; private set; }
+
protected IFileSystem FileSystem { get; private set; }
+
protected IDlnaManager DlnaManager { get; private set; }
+
protected IDeviceManager DeviceManager { get; private set; }
+
protected IMediaSourceManager MediaSourceManager { get; private set; }
+
protected IJsonSerializer JsonSerializer { get; private set; }
+
protected IAuthorizationContext AuthorizationContext { get; private set; }
+
protected INetworkManager NetworkManager { get; private set; }
public Task<object> Get(GetUniversalAudioStream request)
@@ -167,7 +189,7 @@ namespace MediaBrowser.Api.Playback
AudioCodec = request.AudioCodec,
Protocol = request.TranscodingProtocol,
BreakOnNonKeyFrames = request.BreakOnNonKeyFrames,
- MaxAudioChannels = request.TranscodingAudioChannels.HasValue ? request.TranscodingAudioChannels.Value.ToString(CultureInfo.InvariantCulture) : null
+ MaxAudioChannels = request.TranscodingAudioChannels?.ToString(CultureInfo.InvariantCulture)
}
};
@@ -233,7 +255,7 @@ namespace MediaBrowser.Api.Playback
AuthorizationContext.GetAuthorizationInfo(Request).DeviceId = request.DeviceId;
var mediaInfoService = new MediaInfoService(
- Logger,
+ _loggerFactory.CreateLogger<MediaInfoService>(),
ServerConfigurationManager,
ResultFactory,
MediaSourceManager,
@@ -256,7 +278,6 @@ namespace MediaBrowser.Api.Playback
UserId = request.UserId,
DeviceProfile = deviceProfile,
MediaSourceId = request.MediaSourceId
-
}).ConfigureAwait(false);
var mediaSource = playbackInfoResult.MediaSources[0];
@@ -277,7 +298,7 @@ namespace MediaBrowser.Api.Playback
if (!isStatic && string.Equals(mediaSource.TranscodingSubProtocol, "hls", StringComparison.OrdinalIgnoreCase))
{
var service = new DynamicHlsService(
- Logger,
+ _loggerFactory.CreateLogger<DynamicHlsService>(),
ServerConfigurationManager,
ResultFactory,
UserManager,
@@ -300,7 +321,7 @@ namespace MediaBrowser.Api.Playback
// hls segment container can only be mpegts or fmp4 per ffmpeg documentation
// TODO: remove this when we switch back to the segment muxer
- var supportedHLSContainers = new string[] { "mpegts", "fmp4" };
+ var supportedHLSContainers = new[] { "mpegts", "fmp4" };
var newRequest = new GetMasterHlsAudioPlaylist
{
@@ -326,12 +347,13 @@ namespace MediaBrowser.Api.Playback
{
return await service.Head(newRequest).ConfigureAwait(false);
}
+
return await service.Get(newRequest).ConfigureAwait(false);
}
else
{
var service = new AudioService(
- Logger,
+ _loggerFactory.CreateLogger<AudioService>(),
ServerConfigurationManager,
ResultFactory,
HttpClient,