|
@@ -3,10 +3,8 @@
|
|
|
#pragma warning disable CS1591
|
|
|
|
|
|
using System;
|
|
|
-using System.Collections.Generic;
|
|
|
using System.Globalization;
|
|
|
using System.IO;
|
|
|
-using System.Linq;
|
|
|
using System.Threading;
|
|
|
using System.Threading.Tasks;
|
|
|
using MediaBrowser.Common.Configuration;
|
|
@@ -22,14 +20,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
|
|
|
{
|
|
|
private readonly IConfigurationManager _configurationManager;
|
|
|
|
|
|
- protected readonly IFileSystem FileSystem;
|
|
|
-
|
|
|
- protected readonly IStreamHelper StreamHelper;
|
|
|
-
|
|
|
- protected string TempFilePath;
|
|
|
- protected readonly ILogger Logger;
|
|
|
- protected readonly CancellationTokenSource LiveStreamCancellationTokenSource = new CancellationTokenSource();
|
|
|
-
|
|
|
public LiveStream(
|
|
|
MediaSourceInfo mediaSource,
|
|
|
TunerHostInfo tuner,
|
|
@@ -57,7 +47,15 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
|
|
|
SetTempFilePath("ts");
|
|
|
}
|
|
|
|
|
|
- protected virtual int EmptyReadLimit => 1000;
|
|
|
+ protected IFileSystem FileSystem { get; }
|
|
|
+
|
|
|
+ protected IStreamHelper StreamHelper { get; }
|
|
|
+
|
|
|
+ protected ILogger Logger { get; }
|
|
|
+
|
|
|
+ protected CancellationTokenSource LiveStreamCancellationTokenSource { get; } = new CancellationTokenSource();
|
|
|
+
|
|
|
+ protected string TempFilePath { get; set; }
|
|
|
|
|
|
public MediaSourceInfo OriginalMediaSource { get; set; }
|
|
|
|
|
@@ -97,121 +95,50 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
|
|
|
return Task.CompletedTask;
|
|
|
}
|
|
|
|
|
|
- protected FileStream GetInputStream(string path, bool allowAsyncFileRead)
|
|
|
+ public Stream GetStream()
|
|
|
+ {
|
|
|
+ var stream = GetInputStream(TempFilePath);
|
|
|
+ bool seekFile = (DateTime.UtcNow - DateOpened).TotalSeconds > 10;
|
|
|
+ if (seekFile)
|
|
|
+ {
|
|
|
+ TrySeek(stream, -20000);
|
|
|
+ }
|
|
|
+
|
|
|
+ return stream;
|
|
|
+ }
|
|
|
+
|
|
|
+ protected FileStream GetInputStream(string path)
|
|
|
=> new FileStream(
|
|
|
path,
|
|
|
FileMode.Open,
|
|
|
FileAccess.Read,
|
|
|
FileShare.ReadWrite,
|
|
|
IODefaults.FileStreamBufferSize,
|
|
|
- allowAsyncFileRead ? FileOptions.SequentialScan | FileOptions.Asynchronous : FileOptions.SequentialScan);
|
|
|
+ FileOptions.SequentialScan | FileOptions.Asynchronous);
|
|
|
|
|
|
- public Task DeleteTempFiles()
|
|
|
- {
|
|
|
- return DeleteTempFiles(GetStreamFilePaths());
|
|
|
- }
|
|
|
-
|
|
|
- protected async Task DeleteTempFiles(IEnumerable<string> paths, int retryCount = 0)
|
|
|
+ protected async Task DeleteTempFiles(string path, int retryCount = 0)
|
|
|
{
|
|
|
if (retryCount == 0)
|
|
|
{
|
|
|
- Logger.LogInformation("Deleting temp files {0}", paths);
|
|
|
- }
|
|
|
-
|
|
|
- var failedFiles = new List<string>();
|
|
|
-
|
|
|
- foreach (var path in paths)
|
|
|
- {
|
|
|
- if (!File.Exists(path))
|
|
|
- {
|
|
|
- continue;
|
|
|
- }
|
|
|
-
|
|
|
- try
|
|
|
- {
|
|
|
- FileSystem.DeleteFile(path);
|
|
|
- }
|
|
|
- catch (Exception ex)
|
|
|
- {
|
|
|
- Logger.LogError(ex, "Error deleting file {path}", path);
|
|
|
- failedFiles.Add(path);
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- if (failedFiles.Count > 0 && retryCount <= 40)
|
|
|
- {
|
|
|
- await Task.Delay(500).ConfigureAwait(false);
|
|
|
- await DeleteTempFiles(failedFiles, retryCount + 1).ConfigureAwait(false);
|
|
|
+ Logger.LogInformation("Deleting temp file {FilePath}", path);
|
|
|
}
|
|
|
- }
|
|
|
-
|
|
|
- protected virtual List<string> GetStreamFilePaths()
|
|
|
- {
|
|
|
- return new List<string> { TempFilePath };
|
|
|
- }
|
|
|
-
|
|
|
- public async Task CopyToAsync(Stream stream, CancellationToken cancellationToken)
|
|
|
- {
|
|
|
- using var linkedCancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, LiveStreamCancellationTokenSource.Token);
|
|
|
- cancellationToken = linkedCancellationTokenSource.Token;
|
|
|
|
|
|
- bool seekFile = (DateTime.UtcNow - DateOpened).TotalSeconds > 10;
|
|
|
-
|
|
|
- var nextFileInfo = GetNextFile(null);
|
|
|
- var nextFile = nextFileInfo.file;
|
|
|
- var isLastFile = nextFileInfo.isLastFile;
|
|
|
-
|
|
|
- var allowAsync = AsyncFile.UseAsyncIO;
|
|
|
- while (!string.IsNullOrEmpty(nextFile))
|
|
|
- {
|
|
|
- var emptyReadLimit = isLastFile ? EmptyReadLimit : 1;
|
|
|
-
|
|
|
- await CopyFile(nextFile, seekFile, emptyReadLimit, allowAsync, stream, cancellationToken).ConfigureAwait(false);
|
|
|
-
|
|
|
- seekFile = false;
|
|
|
- nextFileInfo = GetNextFile(nextFile);
|
|
|
- nextFile = nextFileInfo.file;
|
|
|
- isLastFile = nextFileInfo.isLastFile;
|
|
|
- }
|
|
|
-
|
|
|
- Logger.LogInformation("Live Stream ended.");
|
|
|
- }
|
|
|
-
|
|
|
- private (string file, bool isLastFile) GetNextFile(string currentFile)
|
|
|
- {
|
|
|
- var files = GetStreamFilePaths();
|
|
|
-
|
|
|
- if (string.IsNullOrEmpty(currentFile))
|
|
|
+ try
|
|
|
{
|
|
|
- return (files[^1], true);
|
|
|
+ FileSystem.DeleteFile(path);
|
|
|
}
|
|
|
-
|
|
|
- var nextIndex = files.FindIndex(i => string.Equals(i, currentFile, StringComparison.OrdinalIgnoreCase)) + 1;
|
|
|
-
|
|
|
- var isLastFile = nextIndex == files.Count - 1;
|
|
|
-
|
|
|
- return (files.ElementAtOrDefault(nextIndex), isLastFile);
|
|
|
- }
|
|
|
-
|
|
|
- private async Task CopyFile(string path, bool seekFile, int emptyReadLimit, bool allowAsync, Stream stream, CancellationToken cancellationToken)
|
|
|
- {
|
|
|
- using (var inputStream = GetInputStream(path, allowAsync))
|
|
|
+ catch (Exception ex)
|
|
|
{
|
|
|
- if (seekFile)
|
|
|
+ Logger.LogError(ex, "Error deleting file {FilePath}", path);
|
|
|
+ if (retryCount <= 40)
|
|
|
{
|
|
|
- TrySeek(inputStream, -20000);
|
|
|
+ await Task.Delay(500).ConfigureAwait(false);
|
|
|
+ await DeleteTempFiles(path, retryCount + 1).ConfigureAwait(false);
|
|
|
}
|
|
|
-
|
|
|
- await StreamHelper.CopyToAsync(
|
|
|
- inputStream,
|
|
|
- stream,
|
|
|
- IODefaults.CopyToBufferSize,
|
|
|
- emptyReadLimit,
|
|
|
- cancellationToken).ConfigureAwait(false);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- private void TrySeek(FileStream stream, long offset)
|
|
|
+ private void TrySeek(Stream stream, long offset)
|
|
|
{
|
|
|
if (!stream.CanSeek)
|
|
|
{
|