update stream generation
This commit is contained in:
parent
bd2ea703e3
commit
578dec0c71
|
@ -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,11 +1617,19 @@ 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);
|
||||
|
||||
var mediaSource = string.IsNullOrEmpty(request.MediaSourceId)
|
||||
? mediaSources.First()
|
||||
: mediaSources.First(i => string.Equals(i.Id, request.MediaSourceId));
|
||||
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 videoRequest = request as VideoStreamRequest;
|
||||
|
||||
|
|
|
@ -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,83 +187,96 @@ 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;
|
||||
}
|
||||
|
||||
if (mediaSource.SupportsDirectPlay)
|
||||
{
|
||||
var supportsDirectStream = mediaSource.SupportsDirectStream;
|
||||
|
||||
// Dummy this up to fool StreamBuilder
|
||||
mediaSource.SupportsDirectStream = true;
|
||||
|
||||
// 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.SupportsDirectPlay = false;
|
||||
}
|
||||
|
||||
// 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)
|
||||
{
|
||||
mediaSource.SupportsDirectStream = false;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
SetDeviceSpecificData(item, mediaSource, profile, auth, maxBitrate, startTimeTicks, mediaSourceId, audioStreamIndex, subtitleStreamIndex);
|
||||
}
|
||||
|
||||
SortMediaSources(result);
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
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 (mediaSource.SupportsDirectPlay)
|
||||
{
|
||||
var supportsDirectStream = mediaSource.SupportsDirectStream;
|
||||
|
||||
// Dummy this up to fool StreamBuilder
|
||||
mediaSource.SupportsDirectStream = true;
|
||||
|
||||
// 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.SupportsDirectPlay = false;
|
||||
}
|
||||
|
||||
// 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)
|
||||
{
|
||||
mediaSource.SupportsDirectStream = false;
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
var originalList = result.MediaSources.ToList();
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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<MediaSourceInfo>.</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<MediaSourceInfo>.</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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<MediaSourceInfo>.</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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -301,5 +301,21 @@ namespace MediaBrowser.Controller.LiveTv
|
|||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>Task<QueryResult<BaseItem>>.</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<IEnumerable<MediaSourceInfo>>.</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<IEnumerable<MediaSourceInfo>>.</returns>
|
||||
Task<IEnumerable<MediaSourceInfo>> GetChannelMediaSources(string id, CancellationToken cancellationToken);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,9 +27,10 @@ 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; }
|
||||
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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,23 +314,40 @@ 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;
|
||||
}
|
||||
|
@ -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
|
||||
{
|
||||
var tuple = GetProvider(closeKey);
|
||||
LiveStreamInfo info;
|
||||
if (_openStreams.TryGetValue(id, out info))
|
||||
{
|
||||
return info.MediaSource;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ResourceNotFoundException();
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_liveStreamSemaphore.Release();
|
||||
}
|
||||
}
|
||||
|
||||
await tuple.Item1.OpenMediaSource(tuple.Item2, cancellationToken).ConfigureAwait(false);
|
||||
public async Task PingLiveStream(string id, CancellationToken cancellationToken)
|
||||
{
|
||||
await _liveStreamSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
string removedKey;
|
||||
_openStreams.TryRemove(closeKey, out removedKey);
|
||||
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(id);
|
||||
|
||||
await tuple.Item1.CloseMediaSource(tuple.Item2, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
{
|
||||
|
|
|
@ -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;
|
||||
|
||||
foreach (var source in sources)
|
||||
sources = hasMediaSources.GetMediaSources(false)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user