update stream generation

This commit is contained in:
Luke Pulverenti
2015-03-29 00:56:39 -04:00
parent bd2ea703e3
commit 578dec0c71
13 changed files with 387 additions and 132 deletions
@@ -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,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
{
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();
}