aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--MediaBrowser.Api/Playback/BaseStreamingService.cs31
-rw-r--r--MediaBrowser.Api/Playback/MediaInfoService.cs160
-rw-r--r--MediaBrowser.Api/Playback/StreamRequest.cs3
-rw-r--r--MediaBrowser.Api/Playback/StreamState.cs4
-rw-r--r--MediaBrowser.Controller/Library/IMediaSourceManager.cs25
-rw-r--r--MediaBrowser.Controller/Library/IMediaSourceProvider.cs8
-rw-r--r--MediaBrowser.Controller/LiveTv/ILiveTvManager.cs16
-rw-r--r--MediaBrowser.Model/Dto/MediaSourceInfo.cs7
-rw-r--r--MediaBrowser.Server.Implementations/Channels/ChannelDynamicMediaSourceProvider.cs4
-rw-r--r--MediaBrowser.Server.Implementations/Library/MediaSourceManager.cs155
-rw-r--r--MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs27
-rw-r--r--MediaBrowser.Server.Implementations/LiveTv/LiveTvMediaSourceProvider.cs51
-rw-r--r--MediaBrowser.Server.Implementations/Sync/SyncedMediaSourceProvider.cs8
13 files changed, 377 insertions, 122 deletions
diff --git a/MediaBrowser.Api/Playback/BaseStreamingService.cs b/MediaBrowser.Api/Playback/BaseStreamingService.cs
index 435bda2c4..2e7c9a5a7 100644
--- a/MediaBrowser.Api/Playback/BaseStreamingService.cs
+++ b/MediaBrowser.Api/Playback/BaseStreamingService.cs
@@ -5,7 +5,6 @@ using MediaBrowser.Controller.Devices;
using MediaBrowser.Controller.Dlna;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
-using MediaBrowser.Controller.LiveTv;
using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Dlna;
@@ -937,7 +936,7 @@ namespace MediaBrowser.Api.Playback
if (state.MediaSource.RequiresOpening)
{
- var mediaSource = await MediaSourceManager.OpenMediaSource(state.MediaSource.OpenKey, cancellationTokenSource.Token)
+ var mediaSource = await MediaSourceManager.OpenLiveStream(state.MediaSource.OpenToken, false, cancellationTokenSource.Token)
.ConfigureAwait(false);
AttachMediaSourceInfo(state, mediaSource, state.VideoRequest, state.RequestedUrl);
@@ -946,9 +945,11 @@ namespace MediaBrowser.Api.Playback
{
TryStreamCopy(state, state.VideoRequest);
}
+ }
- // TODO: This is only needed for live tv
- await Task.Delay(1500, cancellationTokenSource.Token).ConfigureAwait(false);
+ if (state.MediaSource.BufferMs.HasValue)
+ {
+ await Task.Delay(state.MediaSource.BufferMs.Value, cancellationTokenSource.Token).ConfigureAwait(false);
}
}
@@ -1616,12 +1617,20 @@ namespace MediaBrowser.Api.Playback
var archivable = item as IArchivable;
state.IsInputArchive = archivable != null && archivable.IsArchive;
- var mediaSources = await MediaSourceManager.GetPlayackMediaSources(request.Id, false, cancellationToken).ConfigureAwait(false);
+ MediaSourceInfo mediaSource = null;
+ if (string.IsNullOrWhiteSpace(request.LiveStreamId))
+ {
+ var mediaSources = await MediaSourceManager.GetPlayackMediaSources(request.Id, false, cancellationToken).ConfigureAwait(false);
+
+ mediaSource = string.IsNullOrEmpty(request.MediaSourceId)
+ ? mediaSources.First()
+ : mediaSources.First(i => string.Equals(i.Id, request.MediaSourceId));
+ }
+ else
+ {
+ mediaSource = await MediaSourceManager.GetLiveStream(request.LiveStreamId, cancellationToken).ConfigureAwait(false);
+ }
- var mediaSource = string.IsNullOrEmpty(request.MediaSourceId)
- ? mediaSources.First()
- : mediaSources.First(i => string.Equals(i.Id, request.MediaSourceId));
-
var videoRequest = request as VideoStreamRequest;
AttachMediaSourceInfo(state, mediaSource, videoRequest, url);
@@ -1699,7 +1708,7 @@ namespace MediaBrowser.Api.Playback
state.ReadInputAtNativeFramerate = mediaSource.ReadAtNativeFramerate;
state.RunTimeTicks = mediaSource.RunTimeTicks;
state.RemoteHttpHeaders = mediaSource.RequiredHttpHeaders;
-
+
if (mediaSource.VideoType.HasValue)
{
state.VideoType = mediaSource.VideoType.Value;
@@ -1713,7 +1722,7 @@ namespace MediaBrowser.Api.Playback
{
state.InputTimestamp = mediaSource.Timestamp.Value;
}
-
+
state.InputProtocol = mediaSource.Protocol;
state.MediaPath = mediaSource.Path;
state.RunTimeTicks = mediaSource.RunTimeTicks;
diff --git a/MediaBrowser.Api/Playback/MediaInfoService.cs b/MediaBrowser.Api/Playback/MediaInfoService.cs
index cef8a34e5..d954c5b19 100644
--- a/MediaBrowser.Api/Playback/MediaInfoService.cs
+++ b/MediaBrowser.Api/Playback/MediaInfoService.cs
@@ -45,6 +45,9 @@ namespace MediaBrowser.Api.Playback
[ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")]
public string UserId { get; set; }
+ [ApiMember(Name = "MaxStreamingBitrate", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "POST")]
+ public int? MaxStreamingBitrate { get; set; }
+
[ApiMember(Name = "StartTimeTicks", Description = "Optional. Specify a starting offset, in ticks. 1 tick = 10000 ms", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "POST")]
public long? StartTimeTicks { get; set; }
@@ -58,6 +61,20 @@ namespace MediaBrowser.Api.Playback
public string MediaSourceId { get; set; }
}
+ [Route("/MediaSources/Open", "POST", Summary = "Opens a media source")]
+ public class OpenMediaSource : IReturn<MediaSourceInfo>
+ {
+ [ApiMember(Name = "OpenToken", Description = "OpenToken", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
+ public string OpenToken { get; set; }
+ }
+
+ [Route("/MediaSources/Close", "POST", Summary = "Closes a media source")]
+ public class CloseMediaSource : IReturnVoid
+ {
+ [ApiMember(Name = "LiveStreamId", Description = "LiveStreamId", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
+ public string LiveStreamId { get; set; }
+ }
+
[Authenticated]
public class MediaInfoService : BaseApiService
{
@@ -84,6 +101,18 @@ namespace MediaBrowser.Api.Playback
return ToOptimizedResult(result);
}
+ public async Task<object> Post(OpenMediaSource request)
+ {
+ var result = await _mediaSourceManager.OpenLiveStream(request.OpenToken, false, CancellationToken.None).ConfigureAwait(false);
+ return ToOptimizedResult(result);
+ }
+
+ public void Post(CloseMediaSource request)
+ {
+ var task = _mediaSourceManager.CloseLiveStream(request.LiveStreamId, CancellationToken.None);
+ Task.WaitAll(task);
+ }
+
public async Task<object> Post(GetPostedPlaybackInfo request)
{
var info = await GetPlaybackInfo(request.Id, request.UserId, request.MediaSourceId).ConfigureAwait(false);
@@ -102,7 +131,7 @@ namespace MediaBrowser.Api.Playback
if (profile != null)
{
var mediaSourceId = request.MediaSourceId;
- SetDeviceSpecificData(request.Id, info, profile, authInfo, null, request.StartTimeTicks ?? 0, mediaSourceId, request.AudioStreamIndex, request.SubtitleStreamIndex);
+ SetDeviceSpecificData(request.Id, info, profile, authInfo, request.MaxStreamingBitrate, request.StartTimeTicks ?? 0, mediaSourceId, request.AudioStreamIndex, request.SubtitleStreamIndex);
}
return ToOptimizedResult(info);
@@ -158,81 +187,94 @@ namespace MediaBrowser.Api.Playback
int? audioStreamIndex,
int? subtitleStreamIndex)
{
- var streamBuilder = new StreamBuilder();
-
var item = _libraryManager.GetItemById(itemId);
foreach (var mediaSource in result.MediaSources)
{
- var options = new VideoOptions
- {
- MediaSources = new List<MediaSourceInfo> { mediaSource },
- Context = EncodingContext.Streaming,
- DeviceId = auth.DeviceId,
- ItemId = item.Id.ToString("N"),
- Profile = profile,
- MaxBitrate = maxBitrate
- };
-
- if (string.Equals(mediaSourceId, mediaSource.Id, StringComparison.OrdinalIgnoreCase))
- {
- options.MediaSourceId = mediaSourceId;
- options.AudioStreamIndex = audioStreamIndex;
- options.SubtitleStreamIndex = subtitleStreamIndex;
- }
+ SetDeviceSpecificData(item, mediaSource, profile, auth, maxBitrate, startTimeTicks, mediaSourceId, audioStreamIndex, subtitleStreamIndex);
+ }
- if (mediaSource.SupportsDirectPlay)
- {
- var supportsDirectStream = mediaSource.SupportsDirectStream;
+ SortMediaSources(result);
+ }
- // Dummy this up to fool StreamBuilder
- mediaSource.SupportsDirectStream = true;
+ private void SetDeviceSpecificData(BaseItem item,
+ MediaSourceInfo mediaSource,
+ DeviceProfile profile,
+ AuthorizationInfo auth,
+ int? maxBitrate,
+ long startTimeTicks,
+ string mediaSourceId,
+ int? audioStreamIndex,
+ int? subtitleStreamIndex)
+ {
+ var streamBuilder = new StreamBuilder();
- // The MediaSource supports direct stream, now test to see if the client supports it
- var streamInfo = string.Equals(item.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase) ?
- streamBuilder.BuildAudioItem(options) :
- streamBuilder.BuildVideoItem(options);
+ var options = new VideoOptions
+ {
+ MediaSources = new List<MediaSourceInfo> { mediaSource },
+ Context = EncodingContext.Streaming,
+ DeviceId = auth.DeviceId,
+ ItemId = item.Id.ToString("N"),
+ Profile = profile,
+ MaxBitrate = maxBitrate
+ };
+
+ if (string.Equals(mediaSourceId, mediaSource.Id, StringComparison.OrdinalIgnoreCase))
+ {
+ options.MediaSourceId = mediaSourceId;
+ options.AudioStreamIndex = audioStreamIndex;
+ options.SubtitleStreamIndex = subtitleStreamIndex;
+ }
- if (streamInfo == null || !streamInfo.IsDirectStream)
- {
- mediaSource.SupportsDirectPlay = false;
- }
+ if (mediaSource.SupportsDirectPlay)
+ {
+ var supportsDirectStream = mediaSource.SupportsDirectStream;
- // Set this back to what it was
- mediaSource.SupportsDirectStream = supportsDirectStream;
- }
+ // Dummy this up to fool StreamBuilder
+ mediaSource.SupportsDirectStream = true;
- if (mediaSource.SupportsDirectStream)
+ // The MediaSource supports direct stream, now test to see if the client supports it
+ var streamInfo = string.Equals(item.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase) ?
+ streamBuilder.BuildAudioItem(options) :
+ streamBuilder.BuildVideoItem(options);
+
+ if (streamInfo == null || !streamInfo.IsDirectStream)
{
- // The MediaSource supports direct stream, now test to see if the client supports it
- var streamInfo = string.Equals(item.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase) ?
- streamBuilder.BuildAudioItem(options) :
- streamBuilder.BuildVideoItem(options);
-
- if (streamInfo == null || !streamInfo.IsDirectStream)
- {
- mediaSource.SupportsDirectStream = false;
- }
+ mediaSource.SupportsDirectPlay = false;
}
- if (mediaSource.SupportsTranscoding)
+ // Set this back to what it was
+ mediaSource.SupportsDirectStream = supportsDirectStream;
+ }
+
+ if (mediaSource.SupportsDirectStream)
+ {
+ // The MediaSource supports direct stream, now test to see if the client supports it
+ var streamInfo = string.Equals(item.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase) ?
+ streamBuilder.BuildAudioItem(options) :
+ streamBuilder.BuildVideoItem(options);
+
+ if (streamInfo == null || !streamInfo.IsDirectStream)
{
- // The MediaSource supports direct stream, now test to see if the client supports it
- var streamInfo = string.Equals(item.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase) ?
- streamBuilder.BuildAudioItem(options) :
- streamBuilder.BuildVideoItem(options);
-
- if (streamInfo != null && streamInfo.PlayMethod == PlayMethod.Transcode)
- {
- streamInfo.StartPositionTicks = startTimeTicks;
- mediaSource.TranscodingUrl = streamInfo.ToUrl("-", auth.Token).Substring(1);
- mediaSource.TranscodingContainer = streamInfo.Container;
- mediaSource.TranscodingSubProtocol = streamInfo.SubProtocol;
- }
+ mediaSource.SupportsDirectStream = false;
}
}
- SortMediaSources(result);
+ if (mediaSource.SupportsTranscoding)
+ {
+ // The MediaSource supports direct stream, now test to see if the client supports it
+ var streamInfo = string.Equals(item.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase) ?
+ streamBuilder.BuildAudioItem(options) :
+ streamBuilder.BuildVideoItem(options);
+
+ if (streamInfo != null && streamInfo.PlayMethod == PlayMethod.Transcode)
+ {
+ streamInfo.StartPositionTicks = startTimeTicks;
+ mediaSource.TranscodingUrl = streamInfo.ToUrl("-", auth.Token).Substring(1);
+ mediaSource.TranscodingContainer = streamInfo.Container;
+ mediaSource.TranscodingSubProtocol = streamInfo.SubProtocol;
+ }
+ }
}
private void SortMediaSources(PlaybackInfoResponse result)
diff --git a/MediaBrowser.Api/Playback/StreamRequest.cs b/MediaBrowser.Api/Playback/StreamRequest.cs
index b52260b50..7ed4fcd96 100644
--- a/MediaBrowser.Api/Playback/StreamRequest.cs
+++ b/MediaBrowser.Api/Playback/StreamRequest.cs
@@ -72,8 +72,7 @@ namespace MediaBrowser.Api.Playback
public string Params { get; set; }
public string ClientTime { get; set; }
public string StreamId { get; set; }
-
- public string TranscodingJobId { get; set; }
+ public string LiveStreamId { get; set; }
}
public class VideoStreamRequest : StreamRequest
diff --git a/MediaBrowser.Api/Playback/StreamState.cs b/MediaBrowser.Api/Playback/StreamState.cs
index 37f2c7702..b097f3b6a 100644
--- a/MediaBrowser.Api/Playback/StreamState.cs
+++ b/MediaBrowser.Api/Playback/StreamState.cs
@@ -182,11 +182,11 @@ namespace MediaBrowser.Api.Playback
private async void DisposeLiveStream()
{
- if (MediaSource.RequiresClosing)
+ if (MediaSource.RequiresClosing && string.IsNullOrWhiteSpace(Request.LiveStreamId))
{
try
{
- await _mediaSourceManager.CloseMediaSource(MediaSource.CloseKey, CancellationToken.None).ConfigureAwait(false);
+ await _mediaSourceManager.CloseLiveStream(MediaSource.LiveStreamId, CancellationToken.None).ConfigureAwait(false);
}
catch (Exception ex)
{
diff --git a/MediaBrowser.Controller/Library/IMediaSourceManager.cs b/MediaBrowser.Controller/Library/IMediaSourceManager.cs
index fda17aa27..292205c03 100644
--- a/MediaBrowser.Controller/Library/IMediaSourceManager.cs
+++ b/MediaBrowser.Controller/Library/IMediaSourceManager.cs
@@ -84,17 +84,34 @@ namespace MediaBrowser.Controller.Library
/// <summary>
/// Opens the media source.
/// </summary>
- /// <param name="openKey">The open key.</param>
+ /// <param name="openToken">The open token.</param>
+ /// <param name="enableAutoClose">if set to <c>true</c> [enable automatic close].</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task&lt;MediaSourceInfo&gt;.</returns>
- Task<MediaSourceInfo> OpenMediaSource(string openKey, CancellationToken cancellationToken);
+ Task<MediaSourceInfo> OpenLiveStream(string openToken, bool enableAutoClose, CancellationToken cancellationToken);
+
+ /// <summary>
+ /// Gets the live stream.
+ /// </summary>
+ /// <param name="id">The identifier.</param>
+ /// <param name="cancellationToken">The cancellation token.</param>
+ /// <returns>Task&lt;MediaSourceInfo&gt;.</returns>
+ Task<MediaSourceInfo> GetLiveStream(string id, CancellationToken cancellationToken);
+
+ /// <summary>
+ /// Pings the media source.
+ /// </summary>
+ /// <param name="id">The live stream identifier.</param>
+ /// <param name="cancellationToken">The cancellation token.</param>
+ /// <returns>Task.</returns>
+ Task PingLiveStream(string id, CancellationToken cancellationToken);
/// <summary>
/// Closes the media source.
/// </summary>
- /// <param name="closeKey">The close key.</param>
+ /// <param name="id">The live stream identifier.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task.</returns>
- Task CloseMediaSource(string closeKey, CancellationToken cancellationToken);
+ Task CloseLiveStream(string id, CancellationToken cancellationToken);
}
}
diff --git a/MediaBrowser.Controller/Library/IMediaSourceProvider.cs b/MediaBrowser.Controller/Library/IMediaSourceProvider.cs
index c5f5b5401..5b033af4a 100644
--- a/MediaBrowser.Controller/Library/IMediaSourceProvider.cs
+++ b/MediaBrowser.Controller/Library/IMediaSourceProvider.cs
@@ -19,17 +19,17 @@ namespace MediaBrowser.Controller.Library
/// <summary>
/// Opens the media source.
/// </summary>
- /// <param name="openKey">The open key.</param>
+ /// <param name="openToken">The open token.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task&lt;MediaSourceInfo&gt;.</returns>
- Task<MediaSourceInfo> OpenMediaSource(string openKey, CancellationToken cancellationToken);
+ Task<MediaSourceInfo> OpenMediaSource(string openToken, CancellationToken cancellationToken);
/// <summary>
/// Closes the media source.
/// </summary>
- /// <param name="closeKey">The close key.</param>
+ /// <param name="liveStreamId">The live stream identifier.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task.</returns>
- Task CloseMediaSource(string closeKey, CancellationToken cancellationToken);
+ Task CloseMediaSource(string liveStreamId, CancellationToken cancellationToken);
}
}
diff --git a/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs b/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs
index 0b58a9232..d5b5d92a6 100644
--- a/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs
+++ b/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs
@@ -301,5 +301,21 @@ namespace MediaBrowser.Controller.LiveTv
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task&lt;QueryResult&lt;BaseItem&gt;&gt;.</returns>
Task<QueryResult<BaseItem>> GetInternalRecordings(RecordingQuery query, CancellationToken cancellationToken);
+
+ /// <summary>
+ /// Gets the recording media sources.
+ /// </summary>
+ /// <param name="id">The identifier.</param>
+ /// <param name="cancellationToken">The cancellation token.</param>
+ /// <returns>Task&lt;IEnumerable&lt;MediaSourceInfo&gt;&gt;.</returns>
+ Task<IEnumerable<MediaSourceInfo>> GetRecordingMediaSources(string id, CancellationToken cancellationToken);
+
+ /// <summary>
+ /// Gets the channel media sources.
+ /// </summary>
+ /// <param name="id">The identifier.</param>
+ /// <param name="cancellationToken">The cancellation token.</param>
+ /// <returns>Task&lt;IEnumerable&lt;MediaSourceInfo&gt;&gt;.</returns>
+ Task<IEnumerable<MediaSourceInfo>> GetChannelMediaSources(string id, CancellationToken cancellationToken);
}
}
diff --git a/MediaBrowser.Model/Dto/MediaSourceInfo.cs b/MediaBrowser.Model/Dto/MediaSourceInfo.cs
index 92af8d671..3b4513724 100644
--- a/MediaBrowser.Model/Dto/MediaSourceInfo.cs
+++ b/MediaBrowser.Model/Dto/MediaSourceInfo.cs
@@ -27,10 +27,11 @@ namespace MediaBrowser.Model.Dto
public bool SupportsDirectPlay { get; set; }
public bool RequiresOpening { get; set; }
- public string OpenKey { get; set; }
+ public string OpenToken { get; set; }
public bool RequiresClosing { get; set; }
- public string CloseKey { get; set; }
-
+ public string LiveStreamId { get; set; }
+ public int? BufferMs { get; set; }
+
public VideoType? VideoType { get; set; }
public IsoType? IsoType { get; set; }
diff --git a/MediaBrowser.Server.Implementations/Channels/ChannelDynamicMediaSourceProvider.cs b/MediaBrowser.Server.Implementations/Channels/ChannelDynamicMediaSourceProvider.cs
index 6a7163bb3..dac3a80f2 100644
--- a/MediaBrowser.Server.Implementations/Channels/ChannelDynamicMediaSourceProvider.cs
+++ b/MediaBrowser.Server.Implementations/Channels/ChannelDynamicMediaSourceProvider.cs
@@ -30,12 +30,12 @@ namespace MediaBrowser.Server.Implementations.Channels
return Task.FromResult<IEnumerable<MediaSourceInfo>>(new List<MediaSourceInfo>());
}
- public Task<MediaSourceInfo> OpenMediaSource(string openKey, CancellationToken cancellationToken)
+ public Task<MediaSourceInfo> OpenMediaSource(string openToken, CancellationToken cancellationToken)
{
throw new NotImplementedException();
}
- public Task CloseMediaSource(string closeKey, CancellationToken cancellationToken)
+ public Task CloseMediaSource(string liveStreamId, CancellationToken cancellationToken)
{
throw new NotImplementedException();
}
diff --git a/MediaBrowser.Server.Implementations/Library/MediaSourceManager.cs b/MediaBrowser.Server.Implementations/Library/MediaSourceManager.cs
index 40cf240d7..3dbcf4aad 100644
--- a/MediaBrowser.Server.Implementations/Library/MediaSourceManager.cs
+++ b/MediaBrowser.Server.Implementations/Library/MediaSourceManager.cs
@@ -207,14 +207,14 @@ namespace MediaBrowser.Server.Implementations.Library
{
var prefix = provider.GetType().FullName.GetMD5().ToString("N") + "|";
- if (!string.IsNullOrWhiteSpace(mediaSource.OpenKey) && !mediaSource.OpenKey.StartsWith(prefix, StringComparison.OrdinalIgnoreCase))
+ if (!string.IsNullOrWhiteSpace(mediaSource.OpenToken) && !mediaSource.OpenToken.StartsWith(prefix, StringComparison.OrdinalIgnoreCase))
{
- mediaSource.OpenKey = prefix + mediaSource.OpenKey;
+ mediaSource.OpenToken = prefix + mediaSource.OpenToken;
}
- if (!string.IsNullOrWhiteSpace(mediaSource.CloseKey) && !mediaSource.CloseKey.StartsWith(prefix, StringComparison.OrdinalIgnoreCase))
+ if (!string.IsNullOrWhiteSpace(mediaSource.LiveStreamId) && !mediaSource.LiveStreamId.StartsWith(prefix, StringComparison.OrdinalIgnoreCase))
{
- mediaSource.CloseKey = prefix + mediaSource.CloseKey;
+ mediaSource.LiveStreamId = prefix + mediaSource.LiveStreamId;
}
}
@@ -314,24 +314,41 @@ namespace MediaBrowser.Server.Implementations.Library
return GetStaticMediaSources(item, enablePathSubstitution).FirstOrDefault(i => string.Equals(i.Id, mediaSourceId, StringComparison.OrdinalIgnoreCase));
}
- private readonly ConcurrentDictionary<string, string> _openStreams =
- new ConcurrentDictionary<string, string>();
+ private readonly ConcurrentDictionary<string, LiveStreamInfo> _openStreams = new ConcurrentDictionary<string, LiveStreamInfo>();
private readonly SemaphoreSlim _liveStreamSemaphore = new SemaphoreSlim(1, 1);
- public async Task<MediaSourceInfo> OpenMediaSource(string openKey, CancellationToken cancellationToken)
+
+ public async Task<MediaSourceInfo> OpenLiveStream(string openToken, bool enableAutoClose, CancellationToken cancellationToken)
{
await _liveStreamSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
try
{
- var tuple = GetProvider(openKey);
+ var tuple = GetProvider(openToken);
var provider = tuple.Item1;
var mediaSource = await provider.OpenMediaSource(tuple.Item2, cancellationToken).ConfigureAwait(false);
SetKeyProperties(provider, mediaSource);
- _openStreams.AddOrUpdate(mediaSource.CloseKey, mediaSource.CloseKey, (key, i) => mediaSource.CloseKey);
-
+ var info = new LiveStreamInfo
+ {
+ Date = DateTime.UtcNow,
+ EnableCloseTimer = enableAutoClose,
+ Id = mediaSource.LiveStreamId,
+ MediaSource = mediaSource
+ };
+ _openStreams.AddOrUpdate(mediaSource.LiveStreamId, info, (key, i) => info);
+
+ if (enableAutoClose)
+ {
+ StartCloseTimer();
+ }
+
+ if (!string.IsNullOrWhiteSpace(mediaSource.TranscodingUrl))
+ {
+ mediaSource.TranscodingUrl += "&LiveStreamId=" + mediaSource.LiveStreamId;
+ }
+
return mediaSource;
}
finally
@@ -340,18 +357,70 @@ namespace MediaBrowser.Server.Implementations.Library
}
}
- public async Task CloseMediaSource(string closeKey, CancellationToken cancellationToken)
+ public async Task<MediaSourceInfo> GetLiveStream(string id, CancellationToken cancellationToken)
+ {
+ await _liveStreamSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
+
+ try
+ {
+ LiveStreamInfo info;
+ if (_openStreams.TryGetValue(id, out info))
+ {
+ return info.MediaSource;
+ }
+ else
+ {
+ throw new ResourceNotFoundException();
+ }
+ }
+ finally
+ {
+ _liveStreamSemaphore.Release();
+ }
+ }
+
+ public async Task PingLiveStream(string id, CancellationToken cancellationToken)
+ {
+ await _liveStreamSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
+
+ try
+ {
+ LiveStreamInfo info;
+ if (_openStreams.TryGetValue(id, out info))
+ {
+ info.Date = DateTime.UtcNow;
+ }
+ else
+ {
+ _logger.Error("Failed to update MediaSource timestamp for {0}", id);
+ }
+ }
+ finally
+ {
+ _liveStreamSemaphore.Release();
+ }
+ }
+
+ public async Task CloseLiveStream(string id, CancellationToken cancellationToken)
{
await _liveStreamSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
try
{
- var tuple = GetProvider(closeKey);
+ var tuple = GetProvider(id);
- await tuple.Item1.OpenMediaSource(tuple.Item2, cancellationToken).ConfigureAwait(false);
+ await tuple.Item1.CloseMediaSource(tuple.Item2, cancellationToken).ConfigureAwait(false);
- string removedKey;
- _openStreams.TryRemove(closeKey, out removedKey);
+ LiveStreamInfo removed;
+ if (_openStreams.TryRemove(id, out removed))
+ {
+ removed.Closed = true;
+ }
+
+ if (_openStreams.Count == 0)
+ {
+ StopCloseTimer();
+ }
}
finally
{
@@ -368,11 +437,56 @@ namespace MediaBrowser.Server.Implementations.Library
return new Tuple<IMediaSourceProvider, string>(provider, keys[1]);
}
+ private Timer _closeTimer;
+ private readonly TimeSpan _openStreamMaxAge = TimeSpan.FromSeconds(40);
+
+ private void StartCloseTimer()
+ {
+ StopCloseTimer();
+
+ _closeTimer = new Timer(CloseTimerCallback, null, _openStreamMaxAge, _openStreamMaxAge);
+ }
+
+ private void StopCloseTimer()
+ {
+ var timer = _closeTimer;
+
+ if (timer != null)
+ {
+ _closeTimer = null;
+ timer.Dispose();
+ }
+ }
+
+ private async void CloseTimerCallback(object state)
+ {
+ var infos = _openStreams
+ .Values
+ .Where(i => i.EnableCloseTimer && (DateTime.UtcNow - i.Date) > _openStreamMaxAge)
+ .ToList();
+
+ foreach (var info in infos)
+ {
+ if (!info.Closed)
+ {
+ try
+ {
+ await CloseLiveStream(info.Id, CancellationToken.None).ConfigureAwait(false);
+ }
+ catch (Exception ex)
+ {
+ _logger.ErrorException("Error closing media source", ex);
+ }
+ }
+ }
+ }
+
/// <summary>
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
/// </summary>
public void Dispose()
{
+ StopCloseTimer();
Dispose(true);
}
@@ -389,7 +503,7 @@ namespace MediaBrowser.Server.Implementations.Library
{
foreach (var key in _openStreams.Keys.ToList())
{
- var task = CloseMediaSource(key, CancellationToken.None);
+ var task = CloseLiveStream(key, CancellationToken.None);
Task.WaitAll(task);
}
@@ -398,5 +512,14 @@ namespace MediaBrowser.Server.Implementations.Library
}
}
}
+
+ private class LiveStreamInfo
+ {
+ public DateTime Date;
+ public bool EnableCloseTimer;
+ public string Id;
+ public bool Closed;
+ public MediaSourceInfo MediaSource;
+ }
}
}
diff --git a/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs b/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs
index 202a051e3..86b31e0e5 100644
--- a/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs
+++ b/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs
@@ -313,6 +313,22 @@ namespace MediaBrowser.Server.Implementations.LiveTv
return await GetLiveStream(id, true, cancellationToken).ConfigureAwait(false);
}
+ public async Task<IEnumerable<MediaSourceInfo>> GetRecordingMediaSources(string id, CancellationToken cancellationToken)
+ {
+ var item = await GetInternalRecording(id, cancellationToken).ConfigureAwait(false);
+ var service = GetService(item);
+
+ return await service.GetRecordingStreamMediaSources(id, cancellationToken).ConfigureAwait(false);
+ }
+
+ public async Task<IEnumerable<MediaSourceInfo>> GetChannelMediaSources(string id, CancellationToken cancellationToken)
+ {
+ var item = GetInternalChannel(id);
+ var service = GetService(item);
+
+ return await service.GetChannelStreamMediaSources(id, cancellationToken).ConfigureAwait(false);
+ }
+
private ILiveTvService GetService(ILiveTvItem item)
{
return GetService(item.ServiceName);
@@ -330,7 +346,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv
try
{
MediaSourceInfo info;
- var isVideo = true;
+ bool isVideo;
if (isChannel)
{
@@ -340,7 +356,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv
_logger.Info("Opening channel stream from {0}, external channel Id: {1}", service.Name, channel.ExternalId);
info = await service.GetChannelStream(channel.ExternalId, null, cancellationToken).ConfigureAwait(false);
info.RequiresClosing = true;
- info.CloseKey = info.Id;
+ info.LiveStreamId = info.Id;
}
else
{
@@ -351,7 +367,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv
_logger.Info("Opening recording stream from {0}, external recording Id: {1}", service.Name, recording.RecordingInfo.Id);
info = await service.GetRecordingStream(recording.RecordingInfo.Id, null, cancellationToken).ConfigureAwait(false);
info.RequiresClosing = true;
- info.CloseKey = info.Id;
+ info.LiveStreamId = info.Id;
}
_logger.Info("Live stream info: {0}", _jsonSerializer.SerializeToString(info));
@@ -393,7 +409,10 @@ namespace MediaBrowser.Server.Implementations.LiveTv
{
Type = MediaStreamType.Video,
// Set the index to -1 because we don't know the exact index of the video stream within the container
- Index = -1
+ Index = -1,
+
+ // Set to true if unknown to enable deinterlacing
+ IsInterlaced = true
},
new MediaStream
{
diff --git a/MediaBrowser.Server.Implementations/LiveTv/LiveTvMediaSourceProvider.cs b/MediaBrowser.Server.Implementations/LiveTv/LiveTvMediaSourceProvider.cs
index 186bc499d..5de4cf499 100644
--- a/MediaBrowser.Server.Implementations/LiveTv/LiveTvMediaSourceProvider.cs
+++ b/MediaBrowser.Server.Implementations/LiveTv/LiveTvMediaSourceProvider.cs
@@ -2,6 +2,8 @@
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.LiveTv;
using MediaBrowser.Model.Dto;
+using MediaBrowser.Model.Logging;
+using MediaBrowser.Model.Serialization;
using System;
using System.Collections.Generic;
using System.Linq;
@@ -13,10 +15,14 @@ namespace MediaBrowser.Server.Implementations.LiveTv
public class LiveTvMediaSourceProvider : IMediaSourceProvider
{
private readonly ILiveTvManager _liveTvManager;
+ private readonly IJsonSerializer _jsonSerializer;
+ private readonly ILogger _logger;
- public LiveTvMediaSourceProvider(ILiveTvManager liveTvManager)
+ public LiveTvMediaSourceProvider(ILiveTvManager liveTvManager, IJsonSerializer jsonSerializer, ILogManager logManager)
{
_liveTvManager = liveTvManager;
+ _jsonSerializer = jsonSerializer;
+ _logger = logManager.GetLogger(GetType().Name);
}
public Task<IEnumerable<MediaSourceInfo>> GetMediaSources(IHasMediaSources item, CancellationToken cancellationToken)
@@ -38,28 +44,51 @@ namespace MediaBrowser.Server.Implementations.LiveTv
private async Task<IEnumerable<MediaSourceInfo>> GetMediaSourcesInternal(ILiveTvItem item, CancellationToken cancellationToken)
{
- var hasMediaSources = (IHasMediaSources)item;
+ IEnumerable<MediaSourceInfo> sources;
- var sources = hasMediaSources.GetMediaSources(false)
- .ToList();
+ try
+ {
+ if (item is ILiveTvRecording)
+ {
+ sources = await _liveTvManager.GetRecordingMediaSources(item.Id.ToString("N"), cancellationToken)
+ .ConfigureAwait(false);
+ }
+ else
+ {
+ sources = await _liveTvManager.GetChannelMediaSources(item.Id.ToString("N"), cancellationToken)
+ .ConfigureAwait(false);
+ }
+ }
+ catch (NotImplementedException)
+ {
+ var hasMediaSources = (IHasMediaSources)item;
+
+ sources = hasMediaSources.GetMediaSources(false)
+ .ToList();
+ }
- foreach (var source in sources)
+ var list = sources.ToList();
+
+ foreach (var source in list)
{
source.Type = MediaSourceType.Default;
source.RequiresOpening = true;
+ source.BufferMs = source.BufferMs ?? 1500;
var openKeys = new List<string>();
openKeys.Add(item.GetType().Name);
openKeys.Add(item.Id.ToString("N"));
- source.OpenKey = string.Join("|", openKeys.ToArray());
+ source.OpenToken = string.Join("|", openKeys.ToArray());
}
- return sources;
+ _logger.Debug("MediaSources: {0}", _jsonSerializer.SerializeToString(list));
+
+ return list;
}
- public async Task<MediaSourceInfo> OpenMediaSource(string openKey, CancellationToken cancellationToken)
+ public async Task<MediaSourceInfo> OpenMediaSource(string openToken, CancellationToken cancellationToken)
{
- var keys = openKey.Split(new[] { '|' }, 2);
+ var keys = openToken.Split(new[] { '|' }, 2);
if (string.Equals(keys[0], typeof(LiveTvChannel).Name, StringComparison.OrdinalIgnoreCase))
{
@@ -69,9 +98,9 @@ namespace MediaBrowser.Server.Implementations.LiveTv
return await _liveTvManager.GetRecordingStream(keys[1], cancellationToken).ConfigureAwait(false);
}
- public Task CloseMediaSource(string closeKey, CancellationToken cancellationToken)
+ public Task CloseMediaSource(string liveStreamId, CancellationToken cancellationToken)
{
- return _liveTvManager.CloseLiveStream(closeKey, cancellationToken);
+ return _liveTvManager.CloseLiveStream(liveStreamId, cancellationToken);
}
}
}
diff --git a/MediaBrowser.Server.Implementations/Sync/SyncedMediaSourceProvider.cs b/MediaBrowser.Server.Implementations/Sync/SyncedMediaSourceProvider.cs
index 25a52fb95..1c17b9993 100644
--- a/MediaBrowser.Server.Implementations/Sync/SyncedMediaSourceProvider.cs
+++ b/MediaBrowser.Server.Implementations/Sync/SyncedMediaSourceProvider.cs
@@ -90,13 +90,13 @@ namespace MediaBrowser.Server.Implementations.Sync
keyList.Add(provider.GetType().FullName.GetMD5().ToString("N"));
keyList.Add(target.Id.GetMD5().ToString("N"));
keyList.Add(item.Id);
- mediaSource.OpenKey = string.Join("|", keyList.ToArray());
+ mediaSource.OpenToken = string.Join("|", keyList.ToArray());
}
}
- public async Task<MediaSourceInfo> OpenMediaSource(string openKey, CancellationToken cancellationToken)
+ public async Task<MediaSourceInfo> OpenMediaSource(string openToken, CancellationToken cancellationToken)
{
- var openKeys = openKey.Split(new[] { '|' }, 3);
+ var openKeys = openToken.Split(new[] { '|' }, 3);
var provider = _syncManager.ServerSyncProviders
.FirstOrDefault(i => string.Equals(openKeys[0], i.GetType().FullName.GetMD5().ToString("N"), StringComparison.OrdinalIgnoreCase));
@@ -133,7 +133,7 @@ namespace MediaBrowser.Server.Implementations.Sync
mediaSource.SupportsTranscoding = false;
}
- public Task CloseMediaSource(string closeKey, CancellationToken cancellationToken)
+ public Task CloseMediaSource(string liveStreamId, CancellationToken cancellationToken)
{
throw new NotImplementedException();
}