jellyfin-server/Emby.Server.Implementations/LiveTv/TunerHosts/LiveStream.cs

222 lines
6.9 KiB
C#
Raw Normal View History

using System;
2017-09-28 17:02:49 +00:00
using System.Collections.Generic;
using System.Globalization;
2017-09-28 17:02:49 +00:00
using System.IO;
using System.Linq;
2017-09-28 17:02:49 +00:00
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Controller;
using MediaBrowser.Controller.Library;
2017-09-28 17:02:49 +00:00
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.LiveTv;
using Microsoft.Extensions.Logging;
2017-09-28 17:02:49 +00:00
namespace Emby.Server.Implementations.LiveTv.TunerHosts
{
public class LiveStream : ILiveStream
{
public MediaSourceInfo OriginalMediaSource { get; set; }
2018-09-12 17:26:21 +00:00
public MediaSourceInfo MediaSource { get; set; }
public int ConsumerCount { get; set; }
2017-09-28 17:02:49 +00:00
public string OriginalStreamId { get; set; }
public bool EnableStreamSharing { get; set; }
public string UniqueId { get; }
2017-09-28 17:02:49 +00:00
protected readonly IFileSystem FileSystem;
2017-11-14 07:41:21 +00:00
protected readonly IServerApplicationPaths AppPaths;
2017-09-28 17:02:49 +00:00
2018-09-12 17:26:21 +00:00
protected string TempFilePath;
2017-09-28 17:02:49 +00:00
protected readonly ILogger Logger;
2017-10-14 06:52:56 +00:00
protected readonly CancellationTokenSource LiveStreamCancellationTokenSource = new CancellationTokenSource();
2017-09-28 17:02:49 +00:00
public string TunerHostId { get; }
2018-09-12 17:26:21 +00:00
public DateTime DateOpened { get; protected set; }
public LiveStream(MediaSourceInfo mediaSource, TunerHostInfo tuner, IFileSystem fileSystem, ILogger logger, IServerApplicationPaths appPaths)
2017-09-28 17:02:49 +00:00
{
OriginalMediaSource = mediaSource;
FileSystem = fileSystem;
2018-09-12 17:26:21 +00:00
MediaSource = mediaSource;
2017-09-28 17:02:49 +00:00
Logger = logger;
EnableStreamSharing = true;
UniqueId = Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture);
2018-09-12 17:26:21 +00:00
if (tuner != null)
{
TunerHostId = tuner.Id;
}
2017-11-14 07:41:21 +00:00
AppPaths = appPaths;
2018-09-12 17:26:21 +00:00
ConsumerCount = 1;
2017-11-14 07:41:21 +00:00
SetTempFilePath("ts");
}
protected void SetTempFilePath(string extension)
{
TempFilePath = Path.Combine(AppPaths.GetTranscodingTempPath(), UniqueId + "." + extension);
2017-09-28 17:02:49 +00:00
}
2017-10-23 19:14:11 +00:00
public virtual Task Open(CancellationToken openCancellationToken)
2017-09-28 17:02:49 +00:00
{
2018-09-12 17:26:21 +00:00
DateOpened = DateTime.UtcNow;
return Task.CompletedTask;
2017-09-28 17:02:49 +00:00
}
2018-09-12 17:26:21 +00:00
public Task Close()
{
EnableStreamSharing = false;
Logger.LogInformation("Closing " + GetType().Name);
2018-09-12 17:26:21 +00:00
LiveStreamCancellationTokenSource.Cancel();
return Task.CompletedTask;
}
2017-09-28 17:02:49 +00:00
protected Stream GetInputStream(string path, bool allowAsyncFileRead)
{
var fileOpenOptions = FileOpenOptions.SequentialScan;
if (allowAsyncFileRead)
{
fileOpenOptions |= FileOpenOptions.Asynchronous;
}
return FileSystem.GetFileStream(path, FileOpenMode.Open, FileAccessMode.Read, FileShareMode.ReadWrite, fileOpenOptions);
}
2018-09-12 17:26:21 +00:00
public Task DeleteTempFiles()
{
return DeleteTempFiles(GetStreamFilePaths());
}
protected async Task DeleteTempFiles(IEnumerable<string> paths, int retryCount = 0)
2017-09-28 17:02:49 +00:00
{
2017-10-23 19:14:11 +00:00
if (retryCount == 0)
{
Logger.LogInformation("Deleting temp files {0}", paths);
2017-10-23 19:14:11 +00:00
}
2018-09-12 17:26:21 +00:00
var failedFiles = new List<string>();
2017-09-28 17:02:49 +00:00
2018-09-12 17:26:21 +00:00
foreach (var path in paths)
{
if (!File.Exists(path))
2018-09-12 17:26:21 +00:00
{
continue;
2018-09-12 17:26:21 +00:00
}
try
2018-09-12 17:26:21 +00:00
{
FileSystem.DeleteFile(path);
2018-09-12 17:26:21 +00:00
}
catch (Exception ex)
{
2018-12-20 12:11:26 +00:00
Logger.LogError(ex, "Error deleting file {path}", path);
2018-09-12 17:26:21 +00:00
failedFiles.Add(path);
}
2017-09-28 17:02:49 +00:00
}
2018-09-12 17:26:21 +00:00
if (failedFiles.Count > 0 && retryCount <= 40)
2017-09-28 17:02:49 +00:00
{
2018-09-12 17:26:21 +00:00
await Task.Delay(500).ConfigureAwait(false);
await DeleteTempFiles(failedFiles, retryCount + 1).ConfigureAwait(false);
2017-09-28 17:02:49 +00:00
}
2018-09-12 17:26:21 +00:00
}
2017-09-28 17:02:49 +00:00
2018-09-12 17:26:21 +00:00
protected virtual List<string> GetStreamFilePaths()
{
return new List<string> { TempFilePath };
2017-09-28 17:02:49 +00:00
}
public async Task CopyToAsync(Stream stream, CancellationToken cancellationToken)
{
2017-10-14 06:52:56 +00:00
cancellationToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, LiveStreamCancellationTokenSource.Token).Token;
// use non-async filestream on windows along with read due to https://github.com/dotnet/corefx/issues/6039
var allowAsync = Environment.OSVersion.Platform != PlatformID.Win32NT;
2017-09-28 17:02:49 +00:00
2018-09-12 17:26:21 +00:00
bool seekFile = (DateTime.UtcNow - DateOpened).TotalSeconds > 10;
var nextFileInfo = GetNextFile(null);
var nextFile = nextFileInfo.Item1;
var isLastFile = nextFileInfo.Item2;
while (!string.IsNullOrEmpty(nextFile))
2017-09-28 17:02:49 +00:00
{
2018-09-12 17:26:21 +00:00
var emptyReadLimit = isLastFile ? EmptyReadLimit : 1;
await CopyFile(nextFile, seekFile, emptyReadLimit, allowAsync, stream, cancellationToken).ConfigureAwait(false);
2017-09-28 17:02:49 +00:00
2018-09-12 17:26:21 +00:00
seekFile = false;
nextFileInfo = GetNextFile(nextFile);
nextFile = nextFileInfo.Item1;
isLastFile = nextFileInfo.Item2;
2017-09-28 17:02:49 +00:00
}
2018-09-12 17:26:21 +00:00
Logger.LogInformation("Live Stream ended.");
2017-09-28 17:02:49 +00:00
}
private (string file, bool isLastFile) GetNextFile(string currentFile)
2017-09-28 17:02:49 +00:00
{
2018-09-12 17:26:21 +00:00
var files = GetStreamFilePaths();
2017-10-14 06:52:56 +00:00
2018-09-12 17:26:21 +00:00
if (string.IsNullOrEmpty(currentFile))
2017-09-28 17:02:49 +00:00
{
return (files.Last(), true);
2018-09-12 17:26:21 +00:00
}
var nextIndex = files.FindIndex(i => string.Equals(i, currentFile, StringComparison.OrdinalIgnoreCase)) + 1;
var isLastFile = nextIndex == files.Count - 1;
2017-09-28 17:02:49 +00:00
return (files.ElementAtOrDefault(nextIndex), isLastFile);
2018-09-12 17:26:21 +00:00
}
private async Task CopyFile(string path, bool seekFile, int emptyReadLimit, bool allowAsync, Stream stream, CancellationToken cancellationToken)
{
using (var inputStream = (FileStream)GetInputStream(path, allowAsync))
{
if (seekFile)
2017-10-14 06:52:56 +00:00
{
2018-09-12 17:26:21 +00:00
TrySeek(inputStream, -20000);
2017-10-14 06:52:56 +00:00
}
2018-09-12 17:26:21 +00:00
await ApplicationHost.StreamHelper.CopyToAsync(inputStream, stream, 81920, emptyReadLimit, cancellationToken).ConfigureAwait(false);
}
}
2017-09-28 17:02:49 +00:00
protected virtual int EmptyReadLimit => 1000;
2017-09-28 17:02:49 +00:00
private void TrySeek(FileStream stream, long offset)
{
if (!stream.CanSeek)
{
return;
}
2017-09-28 17:02:49 +00:00
try
{
stream.Seek(offset, SeekOrigin.End);
2017-10-05 18:10:46 +00:00
}
catch (IOException)
{
2017-09-28 17:02:49 +00:00
}
catch (ArgumentException)
{
}
catch (Exception ex)
{
2018-12-20 12:11:26 +00:00
Logger.LogError(ex, "Error seeking stream");
2017-09-28 17:02:49 +00:00
}
}
}
}