2016-09-29 12:55:49 +00:00
|
|
|
|
using System;
|
2016-10-10 18:18:28 +00:00
|
|
|
|
using System.Collections.Generic;
|
2017-05-22 04:54:02 +00:00
|
|
|
|
using System.IO;
|
2016-09-29 12:55:49 +00:00
|
|
|
|
using System.Threading;
|
2016-09-25 18:39:13 +00:00
|
|
|
|
using System.Threading.Tasks;
|
|
|
|
|
using MediaBrowser.Model.Dto;
|
2017-05-22 04:54:02 +00:00
|
|
|
|
using MediaBrowser.Model.IO;
|
|
|
|
|
using MediaBrowser.Model.System;
|
2016-09-25 18:39:13 +00:00
|
|
|
|
|
|
|
|
|
namespace MediaBrowser.Controller.LiveTv
|
|
|
|
|
{
|
|
|
|
|
public class LiveStream
|
|
|
|
|
{
|
|
|
|
|
public MediaSourceInfo OriginalMediaSource { get; set; }
|
2016-09-29 12:55:49 +00:00
|
|
|
|
public MediaSourceInfo OpenedMediaSource { get; set; }
|
2017-05-22 04:54:02 +00:00
|
|
|
|
public int ConsumerCount
|
|
|
|
|
{
|
2016-10-10 18:18:28 +00:00
|
|
|
|
get { return SharedStreamIds.Count; }
|
|
|
|
|
}
|
2016-09-29 12:55:49 +00:00
|
|
|
|
public ITunerHost TunerHost { get; set; }
|
|
|
|
|
public string OriginalStreamId { get; set; }
|
2016-09-30 06:50:06 +00:00
|
|
|
|
public bool EnableStreamSharing { get; set; }
|
2016-10-07 15:08:13 +00:00
|
|
|
|
public string UniqueId = Guid.NewGuid().ToString("N");
|
2016-09-25 18:39:13 +00:00
|
|
|
|
|
2017-05-22 04:54:02 +00:00
|
|
|
|
public List<string> SharedStreamIds = new List<string>();
|
|
|
|
|
protected readonly IEnvironmentInfo Environment;
|
|
|
|
|
protected readonly IFileSystem FileSystem;
|
|
|
|
|
const int StreamCopyToBufferSize = 81920;
|
2016-10-10 18:18:28 +00:00
|
|
|
|
|
2017-05-22 04:54:02 +00:00
|
|
|
|
public LiveStream(MediaSourceInfo mediaSource, IEnvironmentInfo environment, IFileSystem fileSystem)
|
2016-09-25 18:39:13 +00:00
|
|
|
|
{
|
|
|
|
|
OriginalMediaSource = mediaSource;
|
2017-05-22 04:54:02 +00:00
|
|
|
|
Environment = environment;
|
|
|
|
|
FileSystem = fileSystem;
|
2016-09-29 12:55:49 +00:00
|
|
|
|
OpenedMediaSource = mediaSource;
|
2016-09-30 06:50:06 +00:00
|
|
|
|
EnableStreamSharing = true;
|
2016-09-25 18:39:13 +00:00
|
|
|
|
}
|
|
|
|
|
|
2016-10-10 18:18:28 +00:00
|
|
|
|
public Task Open(CancellationToken cancellationToken)
|
2016-09-29 12:55:49 +00:00
|
|
|
|
{
|
2016-10-10 18:18:28 +00:00
|
|
|
|
return OpenInternal(cancellationToken);
|
2016-09-29 12:55:49 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
protected virtual Task OpenInternal(CancellationToken cancellationToken)
|
2016-09-25 18:39:13 +00:00
|
|
|
|
{
|
|
|
|
|
return Task.FromResult(true);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public virtual Task Close()
|
|
|
|
|
{
|
|
|
|
|
return Task.FromResult(true);
|
|
|
|
|
}
|
2017-05-22 04:54:02 +00:00
|
|
|
|
|
|
|
|
|
private Stream GetInputStream(string path, long startPosition, bool allowAsyncFileRead)
|
|
|
|
|
{
|
|
|
|
|
var fileOpenOptions = startPosition > 0
|
|
|
|
|
? FileOpenOptions.RandomAccess
|
|
|
|
|
: FileOpenOptions.SequentialScan;
|
|
|
|
|
|
|
|
|
|
if (allowAsyncFileRead)
|
|
|
|
|
{
|
|
|
|
|
fileOpenOptions |= FileOpenOptions.Asynchronous;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return FileSystem.GetFileStream(path, FileOpenMode.Open, FileAccessMode.Read, FileShareMode.ReadWrite, fileOpenOptions);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
protected async Task DeleteTempFile(string path, int retryCount = 0)
|
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
FileSystem.DeleteFile(path);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
catch
|
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (retryCount > 20)
|
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
await Task.Delay(500).ConfigureAwait(false);
|
|
|
|
|
await DeleteTempFile(path, retryCount + 1).ConfigureAwait(false);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
protected async Task CopyFileTo(string path, bool allowEndOfFile, Stream outputStream, CancellationToken cancellationToken)
|
|
|
|
|
{
|
|
|
|
|
var eofCount = 0;
|
|
|
|
|
|
|
|
|
|
long startPosition = -25000;
|
|
|
|
|
if (startPosition < 0)
|
|
|
|
|
{
|
|
|
|
|
var length = FileSystem.GetFileInfo(path).Length;
|
|
|
|
|
startPosition = Math.Max(length - startPosition, 0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// use non-async filestream along with read due to https://github.com/dotnet/corefx/issues/6039
|
|
|
|
|
var allowAsyncFileRead = Environment.OperatingSystem != OperatingSystem.Windows;
|
|
|
|
|
|
|
|
|
|
using (var inputStream = GetInputStream(path, startPosition, allowAsyncFileRead))
|
|
|
|
|
{
|
|
|
|
|
if (startPosition > 0)
|
|
|
|
|
{
|
|
|
|
|
inputStream.Position = startPosition;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
while (eofCount < 20 || !allowEndOfFile)
|
|
|
|
|
{
|
|
|
|
|
int bytesRead;
|
|
|
|
|
if (allowAsyncFileRead)
|
|
|
|
|
{
|
|
|
|
|
bytesRead = await CopyToInternalAsync(inputStream, outputStream, cancellationToken).ConfigureAwait(false);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
bytesRead = await CopyToInternalAsyncWithSyncRead(inputStream, outputStream, cancellationToken).ConfigureAwait(false);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//var position = fs.Position;
|
|
|
|
|
//_logger.Debug("Streamed {0} bytes to position {1} from file {2}", bytesRead, position, path);
|
|
|
|
|
|
|
|
|
|
if (bytesRead == 0)
|
|
|
|
|
{
|
|
|
|
|
eofCount++;
|
|
|
|
|
await Task.Delay(100, cancellationToken).ConfigureAwait(false);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
eofCount = 0;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private async Task<int> CopyToInternalAsyncWithSyncRead(Stream source, Stream destination, CancellationToken cancellationToken)
|
|
|
|
|
{
|
|
|
|
|
var array = new byte[StreamCopyToBufferSize];
|
|
|
|
|
int bytesRead;
|
|
|
|
|
int totalBytesRead = 0;
|
|
|
|
|
|
|
|
|
|
while ((bytesRead = source.Read(array, 0, array.Length)) != 0)
|
|
|
|
|
{
|
|
|
|
|
var bytesToWrite = bytesRead;
|
|
|
|
|
|
|
|
|
|
if (bytesToWrite > 0)
|
|
|
|
|
{
|
|
|
|
|
await destination.WriteAsync(array, 0, Convert.ToInt32(bytesToWrite), cancellationToken).ConfigureAwait(false);
|
|
|
|
|
|
|
|
|
|
totalBytesRead += bytesRead;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return totalBytesRead;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private async Task<int> CopyToInternalAsync(Stream source, Stream destination, CancellationToken cancellationToken)
|
|
|
|
|
{
|
|
|
|
|
var array = new byte[StreamCopyToBufferSize];
|
|
|
|
|
int bytesRead;
|
|
|
|
|
int totalBytesRead = 0;
|
|
|
|
|
|
|
|
|
|
while ((bytesRead = await source.ReadAsync(array, 0, array.Length, cancellationToken).ConfigureAwait(false)) != 0)
|
|
|
|
|
{
|
|
|
|
|
var bytesToWrite = bytesRead;
|
|
|
|
|
|
|
|
|
|
if (bytesToWrite > 0)
|
|
|
|
|
{
|
|
|
|
|
await destination.WriteAsync(array, 0, Convert.ToInt32(bytesToWrite), cancellationToken).ConfigureAwait(false);
|
|
|
|
|
|
|
|
|
|
totalBytesRead += bytesRead;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return totalBytesRead;
|
|
|
|
|
}
|
2016-09-25 18:39:13 +00:00
|
|
|
|
}
|
|
|
|
|
}
|