|
|
|
@ -9,13 +9,10 @@ using MediaBrowser.Model.Logging;
|
|
|
|
|
using MediaBrowser.Model.MediaInfo;
|
|
|
|
|
using MediaBrowser.Model.Serialization;
|
|
|
|
|
using System;
|
|
|
|
|
using System.Collections.Concurrent;
|
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
using System.Linq;
|
|
|
|
|
using System.Threading;
|
|
|
|
|
using System.Threading.Tasks;
|
|
|
|
|
using MediaBrowser.Common.IO;
|
|
|
|
|
using MediaBrowser.Controller.IO;
|
|
|
|
|
using MediaBrowser.Model.IO;
|
|
|
|
|
using MediaBrowser.Model.Configuration;
|
|
|
|
|
using MediaBrowser.Model.Threading;
|
|
|
|
@ -365,12 +362,10 @@ namespace Emby.Server.Implementations.Library
|
|
|
|
|
private readonly Dictionary<string, LiveStreamInfo> _openStreams = new Dictionary<string, LiveStreamInfo>(StringComparer.OrdinalIgnoreCase);
|
|
|
|
|
private readonly SemaphoreSlim _liveStreamSemaphore = new SemaphoreSlim(1, 1);
|
|
|
|
|
|
|
|
|
|
public async Task<LiveStreamResponse> OpenLiveStream(LiveStreamRequest request, bool enableAutoClose, CancellationToken cancellationToken)
|
|
|
|
|
public async Task<LiveStreamResponse> OpenLiveStream(LiveStreamRequest request, CancellationToken cancellationToken)
|
|
|
|
|
{
|
|
|
|
|
await _liveStreamSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
|
|
|
|
|
|
|
|
|
|
enableAutoClose = false;
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
var tuple = GetProvider(request.OpenToken);
|
|
|
|
@ -389,8 +384,6 @@ namespace Emby.Server.Implementations.Library
|
|
|
|
|
|
|
|
|
|
var info = new LiveStreamInfo
|
|
|
|
|
{
|
|
|
|
|
Date = DateTime.UtcNow,
|
|
|
|
|
EnableCloseTimer = enableAutoClose,
|
|
|
|
|
Id = mediaSource.LiveStreamId,
|
|
|
|
|
MediaSource = mediaSource,
|
|
|
|
|
DirectStreamProvider = mediaSourceTuple.Item2
|
|
|
|
@ -398,11 +391,6 @@ namespace Emby.Server.Implementations.Library
|
|
|
|
|
|
|
|
|
|
_openStreams[mediaSource.LiveStreamId] = info;
|
|
|
|
|
|
|
|
|
|
if (enableAutoClose)
|
|
|
|
|
{
|
|
|
|
|
StartCloseTimer();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var json = _jsonSerializer.SerializeToString(mediaSource);
|
|
|
|
|
_logger.Debug("Live stream opened: " + json);
|
|
|
|
|
var clone = _jsonSerializer.DeserializeFromString<MediaSourceInfo>(json);
|
|
|
|
@ -462,28 +450,6 @@ namespace Emby.Server.Implementations.Library
|
|
|
|
|
return result.Item1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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 ping live stream {0}", id);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
finally
|
|
|
|
|
{
|
|
|
|
|
_liveStreamSemaphore.Release();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private async Task CloseLiveStreamWithProvider(IMediaSourceProvider provider, string streamId)
|
|
|
|
|
{
|
|
|
|
|
_logger.Info("Closing live stream {0} with provider {1}", streamId, provider.GetType().Name);
|
|
|
|
@ -525,11 +491,6 @@ namespace Emby.Server.Implementations.Library
|
|
|
|
|
|
|
|
|
|
await CloseLiveStreamWithProvider(tuple.Item1, tuple.Item2).ConfigureAwait(false);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (_openStreams.Count == 0)
|
|
|
|
|
{
|
|
|
|
|
StopCloseTimer();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
finally
|
|
|
|
@ -558,66 +519,11 @@ namespace Emby.Server.Implementations.Library
|
|
|
|
|
return new Tuple<IMediaSourceProvider, string>(provider, keyId);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private ITimer _closeTimer;
|
|
|
|
|
private readonly TimeSpan _openStreamMaxAge = TimeSpan.FromSeconds(180);
|
|
|
|
|
|
|
|
|
|
private void StartCloseTimer()
|
|
|
|
|
{
|
|
|
|
|
StopCloseTimer();
|
|
|
|
|
|
|
|
|
|
_closeTimer = _timerFactory.Create(CloseTimerCallback, null, _openStreamMaxAge, _openStreamMaxAge);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void StopCloseTimer()
|
|
|
|
|
{
|
|
|
|
|
var timer = _closeTimer;
|
|
|
|
|
|
|
|
|
|
if (timer != null)
|
|
|
|
|
{
|
|
|
|
|
_closeTimer = null;
|
|
|
|
|
timer.Dispose();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private async void CloseTimerCallback(object state)
|
|
|
|
|
{
|
|
|
|
|
List<LiveStreamInfo> infos;
|
|
|
|
|
await _liveStreamSemaphore.WaitAsync().ConfigureAwait(false);
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
infos = _openStreams
|
|
|
|
|
.Values
|
|
|
|
|
.Where(i => i.EnableCloseTimer && DateTime.UtcNow - i.Date > _openStreamMaxAge)
|
|
|
|
|
.ToList();
|
|
|
|
|
}
|
|
|
|
|
finally
|
|
|
|
|
{
|
|
|
|
|
_liveStreamSemaphore.Release();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
foreach (var info in infos)
|
|
|
|
|
{
|
|
|
|
|
if (!info.Closed)
|
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
await CloseLiveStream(info.Id).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);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@ -644,8 +550,6 @@ namespace Emby.Server.Implementations.Library
|
|
|
|
|
|
|
|
|
|
private class LiveStreamInfo
|
|
|
|
|
{
|
|
|
|
|
public DateTime Date;
|
|
|
|
|
public bool EnableCloseTimer;
|
|
|
|
|
public string Id;
|
|
|
|
|
public bool Closed;
|
|
|
|
|
public MediaSourceInfo MediaSource;
|
|
|
|
|