diff --git a/MediaBrowser.Api/MediaBrowser.Api.csproj b/MediaBrowser.Api/MediaBrowser.Api.csproj
index f9dfadd1f9..f4b68810b8 100644
--- a/MediaBrowser.Api/MediaBrowser.Api.csproj
+++ b/MediaBrowser.Api/MediaBrowser.Api.csproj
@@ -101,6 +101,7 @@
+
diff --git a/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs b/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs
index b162d582d8..736d2122d1 100644
--- a/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs
+++ b/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs
@@ -117,15 +117,7 @@ namespace MediaBrowser.Api.Playback.Hls
if (isPlaylistNewlyCreated)
{
- var minimumSegmentCount = 3;
- var quality = GetQualitySetting();
-
- if (quality == EncodingQuality.HighSpeed || quality == EncodingQuality.HighQuality)
- {
- minimumSegmentCount = 2;
- }
-
- await WaitForMinimumSegmentCount(playlist, minimumSegmentCount).ConfigureAwait(false);
+ await WaitForMinimumSegmentCount(playlist, GetSegmentWait()).ConfigureAwait(false);
}
int audioBitrate;
@@ -154,6 +146,23 @@ namespace MediaBrowser.Api.Playback.Hls
}
}
+ ///
+ /// Gets the segment wait.
+ ///
+ /// System.Int32.
+ protected int GetSegmentWait()
+ {
+ var minimumSegmentCount = 3;
+ var quality = GetQualitySetting();
+
+ if (quality == EncodingQuality.HighSpeed || quality == EncodingQuality.HighQuality)
+ {
+ minimumSegmentCount = 2;
+ }
+
+ return minimumSegmentCount;
+ }
+
///
/// Gets the playlist bitrates.
///
@@ -210,7 +219,7 @@ namespace MediaBrowser.Api.Playback.Hls
return builder.ToString();
}
- private async Task WaitForMinimumSegmentCount(string playlist, int segmentCount)
+ protected async Task WaitForMinimumSegmentCount(string playlist, int segmentCount)
{
while (true)
{
@@ -273,8 +282,11 @@ namespace MediaBrowser.Api.Playback.Hls
var threads = GetNumberOfThreads(false);
var inputModifier = GetInputModifier(state);
+
+ // If performSubtitleConversions is true we're actually starting ffmpeg
+ var startNumberParam = performSubtitleConversions ? GetStartNumber(state).ToString(UsCulture) : "0";
- var args = string.Format("{0} {1} -i {2}{3} -map_metadata -1 -threads {4} {5} {6} -sc_threshold 0 {7} -hls_time {8} -start_number 0 -hls_list_size 1440 \"{9}\"",
+ var args = string.Format("{0} {1} -i {2}{3} -map_metadata -1 -threads {4} {5} {6} -sc_threshold 0 {7} -hls_time {8} -start_number {9} -hls_list_size 1440 \"{10}\"",
itsOffset,
inputModifier,
GetInputArgument(state),
@@ -284,6 +296,7 @@ namespace MediaBrowser.Api.Playback.Hls
GetVideoArguments(state, performSubtitleConversions),
GetAudioArguments(state),
state.SegmentLength.ToString(UsCulture),
+ startNumberParam,
outputPath
).Trim();
@@ -295,10 +308,11 @@ namespace MediaBrowser.Api.Playback.Hls
var bitrate = hlsVideoRequest.BaselineStreamAudioBitRate ?? 64000;
- var lowBitrateParams = string.Format(" -threads {0} -vn -codec:a:0 libmp3lame -ac 2 -ab {1} -hls_time {2} -start_number 0 -hls_list_size 1440 \"{3}\"",
+ var lowBitrateParams = string.Format(" -threads {0} -vn -codec:a:0 libmp3lame -ac 2 -ab {1} -hls_time {2} -start_number {3} -hls_list_size 1440 \"{4}\"",
threads,
bitrate / 2,
state.SegmentLength.ToString(UsCulture),
+ startNumberParam,
lowBitratePath);
args += " " + lowBitrateParams;
@@ -307,5 +321,10 @@ namespace MediaBrowser.Api.Playback.Hls
return args;
}
+
+ protected virtual int GetStartNumber(StreamState state)
+ {
+ return 0;
+ }
}
}
diff --git a/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs b/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs
new file mode 100644
index 0000000000..f064a13c6b
--- /dev/null
+++ b/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs
@@ -0,0 +1,356 @@
+using MediaBrowser.Common.Extensions;
+using MediaBrowser.Common.IO;
+using MediaBrowser.Controller.Configuration;
+using MediaBrowser.Controller.Dto;
+using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.LiveTv;
+using MediaBrowser.Controller.MediaInfo;
+using MediaBrowser.Controller.Persistence;
+using MediaBrowser.Model.Dto;
+using MediaBrowser.Model.IO;
+using ServiceStack;
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.IO;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace MediaBrowser.Api.Playback.Hls
+{
+ [Route("/Videos/{Id}/master.m3u8", "GET")]
+ [Api(Description = "Gets a video stream using HTTP live streaming.")]
+ public class GetMasterHlsVideoStream : VideoStreamRequest
+ {
+ [ApiMember(Name = "BaselineStreamAudioBitRate", Description = "Optional. Specify the audio bitrate for the baseline stream.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
+ public int? BaselineStreamAudioBitRate { get; set; }
+
+ [ApiMember(Name = "AppendBaselineStream", Description = "Optional. Whether or not to include a baseline audio-only stream in the master playlist.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
+ public bool AppendBaselineStream { get; set; }
+ }
+
+ [Route("/Videos/{Id}/main.m3u8", "GET")]
+ [Api(Description = "Gets a video stream using HTTP live streaming.")]
+ public class GetMainHlsVideoStream : VideoStreamRequest
+ {
+ }
+
+ [Route("/Videos/{Id}/baseline.m3u8", "GET")]
+ [Api(Description = "Gets a video stream using HTTP live streaming.")]
+ public class GetBaselineHlsVideoStream : VideoStreamRequest
+ {
+ }
+
+ ///
+ /// Class GetHlsVideoSegment
+ ///
+ [Route("/Videos/{Id}/hlsdynamic/{PlaylistId}/{SegmentId}.ts", "GET")]
+ [Api(Description = "Gets an Http live streaming segment file. Internal use only.")]
+ public class GetDynamicHlsVideoSegment : VideoStreamRequest
+ {
+ public string PlaylistId { get; set; }
+
+ ///
+ /// Gets or sets the segment id.
+ ///
+ /// The segment id.
+ public string SegmentId { get; set; }
+ }
+
+ public class DynamicHlsService : BaseHlsService
+ {
+ public DynamicHlsService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IDtoService dtoService, IFileSystem fileSystem, IItemRepository itemRepository, ILiveTvManager liveTvManager)
+ : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, dtoService, fileSystem, itemRepository, liveTvManager)
+ {
+ }
+
+ protected override string GetOutputFilePath(StreamState state)
+ {
+ var folder = (state.MediaPath + state.Request.DeviceId).GetMD5().ToString("N");
+
+ folder = Path.Combine(ServerConfigurationManager.ApplicationPaths.TranscodingTempPath, folder);
+
+ var outputFileExtension = GetOutputFileExtension(state);
+
+ return Path.Combine(folder, GetCommandLineArguments("dummy\\dummy", state, false).GetMD5() + (outputFileExtension ?? string.Empty).ToLower());
+ }
+
+ public object Get(GetMasterHlsVideoStream request)
+ {
+ var result = GetAsync(request).Result;
+
+ return result;
+ }
+
+ public object Get(GetDynamicHlsVideoSegment request)
+ {
+ if (string.Equals("baseline", request.PlaylistId, StringComparison.OrdinalIgnoreCase))
+ {
+ return GetDynamicSegment(request, false).Result;
+ }
+
+ return GetDynamicSegment(request, true).Result;
+ }
+
+ private async Task