aboutsummaryrefslogtreecommitdiff
path: root/src/Jellyfin.LiveTv/TunerHosts/LiveStream.cs
diff options
context:
space:
mode:
Diffstat (limited to 'src/Jellyfin.LiveTv/TunerHosts/LiveStream.cs')
-rw-r--r--src/Jellyfin.LiveTv/TunerHosts/LiveStream.cs176
1 files changed, 176 insertions, 0 deletions
diff --git a/src/Jellyfin.LiveTv/TunerHosts/LiveStream.cs b/src/Jellyfin.LiveTv/TunerHosts/LiveStream.cs
new file mode 100644
index 0000000000..70d8afc5d0
--- /dev/null
+++ b/src/Jellyfin.LiveTv/TunerHosts/LiveStream.cs
@@ -0,0 +1,176 @@
+#nullable disable
+
+#pragma warning disable CA1711
+#pragma warning disable CS1591
+
+using System;
+using System.Globalization;
+using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
+using MediaBrowser.Common.Configuration;
+using MediaBrowser.Controller.Library;
+using MediaBrowser.Model.Dto;
+using MediaBrowser.Model.IO;
+using MediaBrowser.Model.LiveTv;
+using Microsoft.Extensions.Logging;
+
+namespace Jellyfin.LiveTv.TunerHosts
+{
+ public class LiveStream : ILiveStream
+ {
+ private readonly IConfigurationManager _configurationManager;
+
+ public LiveStream(
+ MediaSourceInfo mediaSource,
+ TunerHostInfo tuner,
+ IFileSystem fileSystem,
+ ILogger logger,
+ IConfigurationManager configurationManager,
+ IStreamHelper streamHelper)
+ {
+ OriginalMediaSource = mediaSource;
+ FileSystem = fileSystem;
+ MediaSource = mediaSource;
+ Logger = logger;
+ EnableStreamSharing = true;
+ UniqueId = Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture);
+
+ if (tuner is not null)
+ {
+ TunerHostId = tuner.Id;
+ }
+
+ _configurationManager = configurationManager;
+ StreamHelper = streamHelper;
+
+ ConsumerCount = 1;
+ SetTempFilePath("ts");
+ }
+
+ 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; }
+
+ public MediaSourceInfo MediaSource { get; set; }
+
+ public int ConsumerCount { get; set; }
+
+ public string OriginalStreamId { get; set; }
+
+ public bool EnableStreamSharing { get; set; }
+
+ public string UniqueId { get; }
+
+ public string TunerHostId { get; }
+
+ public DateTime DateOpened { get; protected set; }
+
+ protected void SetTempFilePath(string extension)
+ {
+ TempFilePath = Path.Combine(_configurationManager.GetTranscodePath(), UniqueId + "." + extension);
+ }
+
+ public virtual Task Open(CancellationToken openCancellationToken)
+ {
+ DateOpened = DateTime.UtcNow;
+ return Task.CompletedTask;
+ }
+
+ public async Task Close()
+ {
+ EnableStreamSharing = false;
+
+ Logger.LogInformation("Closing {Type}", GetType().Name);
+
+ await LiveStreamCancellationTokenSource.CancelAsync().ConfigureAwait(false);
+ }
+
+ public Stream GetStream()
+ {
+ var stream = new FileStream(
+ TempFilePath,
+ FileMode.Open,
+ FileAccess.Read,
+ FileShare.ReadWrite,
+ IODefaults.FileStreamBufferSize,
+ FileOptions.SequentialScan | FileOptions.Asynchronous);
+
+ bool seekFile = (DateTime.UtcNow - DateOpened).TotalSeconds > 10;
+ if (seekFile)
+ {
+ TrySeek(stream, -20000);
+ }
+
+ return stream;
+ }
+
+ /// <inheritdoc />
+ public void Dispose()
+ {
+ Dispose(true);
+ GC.SuppressFinalize(this);
+ }
+
+ protected virtual void Dispose(bool dispose)
+ {
+ if (dispose)
+ {
+ LiveStreamCancellationTokenSource?.Dispose();
+ }
+ }
+
+ protected async Task DeleteTempFiles(string path, int retryCount = 0)
+ {
+ if (retryCount == 0)
+ {
+ Logger.LogInformation("Deleting temp file {FilePath}", path);
+ }
+
+ try
+ {
+ FileSystem.DeleteFile(path);
+ }
+ catch (Exception ex)
+ {
+ Logger.LogError(ex, "Error deleting file {FilePath}", path);
+ if (retryCount <= 40)
+ {
+ await Task.Delay(500).ConfigureAwait(false);
+ await DeleteTempFiles(path, retryCount + 1).ConfigureAwait(false);
+ }
+ }
+ }
+
+ private void TrySeek(FileStream stream, long offset)
+ {
+ if (!stream.CanSeek)
+ {
+ return;
+ }
+
+ try
+ {
+ stream.Seek(offset, SeekOrigin.End);
+ }
+ catch (IOException)
+ {
+ }
+ catch (ArgumentException)
+ {
+ }
+ catch (Exception ex)
+ {
+ Logger.LogError(ex, "Error seeking stream");
+ }
+ }
+ }
+}