improve resiliency of recording process

This commit is contained in:
Luke Pulverenti 2016-09-14 12:21:33 -04:00
parent d611d0e717
commit 0c95297269
9 changed files with 99 additions and 43 deletions

View File

@ -205,6 +205,7 @@ namespace MediaBrowser.Api.UserLibrary
private void SetItemCounts(BaseItemDto dto, ItemCounts counts) private void SetItemCounts(BaseItemDto dto, ItemCounts counts)
{ {
dto.ChildCount = counts.ItemCount; dto.ChildCount = counts.ItemCount;
dto.ProgramCount = counts.ProgramCount;
dto.SeriesCount = counts.SeriesCount; dto.SeriesCount = counts.SeriesCount;
dto.EpisodeCount = counts.EpisodeCount; dto.EpisodeCount = counts.EpisodeCount;
dto.MovieCount = counts.MovieCount; dto.MovieCount = counts.MovieCount;

View File

@ -826,6 +826,7 @@ namespace MediaBrowser.Model.Dto
/// </summary> /// </summary>
/// <value>The series count.</value> /// <value>The series count.</value>
public int? SeriesCount { get; set; } public int? SeriesCount { get; set; }
public int? ProgramCount { get; set; }
/// <summary> /// <summary>
/// Gets or sets the episode count. /// Gets or sets the episode count.
/// </summary> /// </summary>

View File

@ -26,6 +26,7 @@
/// <value>The game count.</value> /// <value>The game count.</value>
public int GameCount { get; set; } public int GameCount { get; set; }
public int ArtistCount { get; set; } public int ArtistCount { get; set; }
public int ProgramCount { get; set; }
/// <summary> /// <summary>
/// Gets or sets the game system count. /// Gets or sets the game system count.
/// </summary> /// </summary>

View File

@ -20,6 +20,7 @@ using System.Text.RegularExpressions;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using CommonIO; using CommonIO;
using MediaBrowser.Controller.LiveTv;
using MediaBrowser.Providers.TV; using MediaBrowser.Providers.TV;
namespace MediaBrowser.Providers.Movies namespace MediaBrowser.Providers.Movies
@ -59,30 +60,13 @@ namespace MediaBrowser.Providers.Movies
public bool Supports(IHasImages item) public bool Supports(IHasImages item)
{ {
//var channelItem = item as IChannelMediaItem;
//if (channelItem != null)
//{
// if (channelItem.ContentType == ChannelMediaContentType.Movie)
// {
// return true;
// }
// if (channelItem.ContentType == ChannelMediaContentType.MovieExtra)
// {
// if (channelItem.ExtraType == ExtraType.Trailer)
// {
// return true;
// }
// }
//}
// Supports images for tv movies // Supports images for tv movies
//var tvProgram = item as LiveTvProgram; var tvProgram = item as LiveTvProgram;
//if (tvProgram != null && tvProgram.IsMovie) if (tvProgram != null && tvProgram.IsMovie)
//{ {
// return true; return true;
//} }
return item is Movie || item is BoxSet || item is MusicVideo; return item is Movie || item is BoxSet || item is MusicVideo;
} }

View File

@ -437,6 +437,7 @@ namespace MediaBrowser.Server.Implementations.Dto
dto.TrailerCount = taggedItems.Count(i => i is Trailer); dto.TrailerCount = taggedItems.Count(i => i is Trailer);
dto.MusicVideoCount = taggedItems.Count(i => i is MusicVideo); dto.MusicVideoCount = taggedItems.Count(i => i is MusicVideo);
dto.SeriesCount = taggedItems.Count(i => i is Series); dto.SeriesCount = taggedItems.Count(i => i is Series);
dto.ProgramCount = taggedItems.Count(i => i is LiveTvProgram);
dto.SongCount = taggedItems.Count(i => i is Audio); dto.SongCount = taggedItems.Count(i => i is Audio);
} }

View File

@ -61,11 +61,44 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
cancellationToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, durationToken.Token).Token; cancellationToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, durationToken.Token).Token;
} }
await response.Content.CopyToAsync(output, StreamDefaults.DefaultCopyToBufferSize, cancellationToken).ConfigureAwait(false); await CopyUntilCancelled(response.Content, output, cancellationToken).ConfigureAwait(false);
} }
} }
_logger.Info("Recording completed to file {0}", targetFile); _logger.Info("Recording completed to file {0}", targetFile);
} }
private const int BufferSize = 81920;
public static async Task CopyUntilCancelled(Stream source, Stream target, CancellationToken cancellationToken)
{
while (!cancellationToken.IsCancellationRequested)
{
var bytesRead = await CopyToAsyncInternal(source, target, BufferSize, cancellationToken).ConfigureAwait(false);
//var position = fs.Position;
//_logger.Debug("Streamed {0} bytes to position {1} from file {2}", bytesRead, position, path);
if (bytesRead == 0)
{
await Task.Delay(100).ConfigureAwait(false);
}
}
}
private static async Task<int> CopyToAsyncInternal(Stream source, Stream destination, Int32 bufferSize, CancellationToken cancellationToken)
{
byte[] buffer = new byte[bufferSize];
int bytesRead;
int totalBytesRead = 0;
while ((bytesRead = await source.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false)) != 0)
{
await destination.WriteAsync(buffer, 0, bytesRead, cancellationToken).ConfigureAwait(false);
totalBytesRead += bytesRead;
}
return totalBytesRead;
}
} }
} }

View File

@ -580,7 +580,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
PrePaddingSeconds = Math.Max(config.PrePaddingSeconds, 0), PrePaddingSeconds = Math.Max(config.PrePaddingSeconds, 0),
RecordAnyChannel = true, RecordAnyChannel = true,
RecordAnyTime = true, RecordAnyTime = true,
RecordNewOnly = false, RecordNewOnly = true,
Days = new List<DayOfWeek> Days = new List<DayOfWeek>
{ {
@ -730,6 +730,9 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
return result.Item1; return result.Item1;
} }
catch (FileNotFoundException)
{
}
catch (Exception e) catch (Exception e)
{ {
_logger.ErrorException("Error getting channel stream", e); _logger.ErrorException("Error getting channel stream", e);
@ -751,6 +754,9 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
return new Tuple<MediaSourceInfo, ITunerHost, SemaphoreSlim>(result.Item1, hostInstance, result.Item2); return new Tuple<MediaSourceInfo, ITunerHost, SemaphoreSlim>(result.Item1, hostInstance, result.Item2);
} }
catch (FileNotFoundException)
{
}
catch (Exception e) catch (Exception e)
{ {
_logger.ErrorException("Error getting channel stream", e); _logger.ErrorException("Error getting channel stream", e);
@ -1213,7 +1219,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
} }
// Exclude programs that have already ended // Exclude programs that have already ended
allPrograms = allPrograms.Where(i => i.EndDate > DateTime.UtcNow && i.StartDate > DateTime.UtcNow); allPrograms = allPrograms.Where(i => i.EndDate > DateTime.UtcNow);
allPrograms = GetProgramsForSeries(seriesTimer, allPrograms); allPrograms = GetProgramsForSeries(seriesTimer, allPrograms);

View File

@ -78,21 +78,33 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
var tempfile = Path.Combine(_appPaths.TranscodingTempPath, Guid.NewGuid().ToString("N") + ".ts"); var tempfile = Path.Combine(_appPaths.TranscodingTempPath, Guid.NewGuid().ToString("N") + ".ts");
try await RecordWithTempFile(mediaSource, tempfile, targetFile, duration, onStarted, cancellationToken)
{ .ConfigureAwait(false);
await RecordWithTempFile(mediaSource, tempfile, targetFile, duration, onStarted, cancellationToken) }
.ConfigureAwait(false);
} private async void DeleteTempFile(string path)
finally {
for (var i = 0; i < 10; i++)
{ {
try try
{ {
File.Delete(tempfile); File.Delete(path);
return;
}
catch (FileNotFoundException)
{
return;
}
catch (DirectoryNotFoundException)
{
return;
} }
catch (Exception ex) catch (Exception ex)
{ {
_logger.ErrorException("Error deleting recording temp file", ex); _logger.ErrorException("Error deleting recording temp file", ex);
} }
await Task.Delay(1000).ConfigureAwait(false);
} }
} }
@ -101,7 +113,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
var durationToken = new CancellationTokenSource(duration); var durationToken = new CancellationTokenSource(duration);
cancellationToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, durationToken.Token).Token; cancellationToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, durationToken.Token).Token;
await RecordFromFile(mediaSource, mediaSource.Path, targetFile, duration, onStarted, cancellationToken).ConfigureAwait(false); await RecordFromFile(mediaSource, mediaSource.Path, targetFile, false, duration, onStarted, cancellationToken).ConfigureAwait(false);
_logger.Info("Recording completed to file {0}", targetFile); _logger.Info("Recording completed to file {0}", targetFile);
} }
@ -143,14 +155,21 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
cancellationToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, durationToken.Token).Token; cancellationToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, durationToken.Token).Token;
} }
var tempFileTask = response.Content.CopyToAsync(output, StreamDefaults.DefaultCopyToBufferSize, cancellationToken); var tempFileTask = DirectRecorder.CopyUntilCancelled(response.Content, output, cancellationToken);
// Give the temp file a little time to build up // Give the temp file a little time to build up
await Task.Delay(bufferMs, cancellationToken).ConfigureAwait(false); await Task.Delay(bufferMs, cancellationToken).ConfigureAwait(false);
var recordTask = Task.Run(() => RecordFromFile(mediaSource, tempFile, targetFile, duration, onStarted, cancellationToken), cancellationToken); var recordTask = Task.Run(() => RecordFromFile(mediaSource, tempFile, targetFile, true, duration, onStarted, cancellationToken), CancellationToken.None);
await tempFileTask.ConfigureAwait(false); try
{
await tempFileTask.ConfigureAwait(false);
}
catch (OperationCanceledException)
{
}
await recordTask.ConfigureAwait(false); await recordTask.ConfigureAwait(false);
} }
@ -159,7 +178,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
_logger.Info("Recording completed to file {0}", targetFile); _logger.Info("Recording completed to file {0}", targetFile);
} }
private Task RecordFromFile(MediaSourceInfo mediaSource, string inputFile, string targetFile, TimeSpan duration, Action onStarted, CancellationToken cancellationToken) private Task RecordFromFile(MediaSourceInfo mediaSource, string inputFile, string targetFile, bool deleteInputFileAfterCompletion, TimeSpan duration, Action onStarted, CancellationToken cancellationToken)
{ {
_targetPath = targetFile; _targetPath = targetFile;
_fileSystem.CreateDirectory(Path.GetDirectoryName(targetFile)); _fileSystem.CreateDirectory(Path.GetDirectoryName(targetFile));
@ -200,7 +219,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
var commandLineLogMessageBytes = Encoding.UTF8.GetBytes(_json.SerializeToString(mediaSource) + Environment.NewLine + Environment.NewLine + commandLineLogMessage + Environment.NewLine + Environment.NewLine); var commandLineLogMessageBytes = Encoding.UTF8.GetBytes(_json.SerializeToString(mediaSource) + Environment.NewLine + Environment.NewLine + commandLineLogMessage + Environment.NewLine + Environment.NewLine);
_logFileStream.Write(commandLineLogMessageBytes, 0, commandLineLogMessageBytes.Length); _logFileStream.Write(commandLineLogMessageBytes, 0, commandLineLogMessageBytes.Length);
process.Exited += (sender, args) => OnFfMpegProcessExited(process); process.Exited += (sender, args) => OnFfMpegProcessExited(process, inputFile, deleteInputFileAfterCompletion);
process.Start(); process.Start();
@ -309,8 +328,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
/// <summary> /// <summary>
/// Processes the exited. /// Processes the exited.
/// </summary> /// </summary>
/// <param name="process">The process.</param> private void OnFfMpegProcessExited(Process process, string inputFile, bool deleteInputFileAfterCompletion)
private void OnFfMpegProcessExited(Process process)
{ {
_hasExited = true; _hasExited = true;
@ -336,6 +354,11 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
_logger.Error("FFMpeg recording exited with an error for {0}.", _targetPath); _logger.Error("FFMpeg recording exited with an error for {0}.", _targetPath);
_taskCompletionSource.TrySetException(new Exception(string.Format("Recording for {0} failed", _targetPath))); _taskCompletionSource.TrySetException(new Exception(string.Format("Recording for {0} failed", _targetPath)));
} }
if (deleteInputFileAfterCompletion)
{
DeleteTempFile(inputFile);
}
} }
private void DisposeLogStream() private void DisposeLogStream()

View File

@ -6,6 +6,7 @@ using MediaBrowser.Model.Logging;
using System; using System;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -195,7 +196,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts
} }
else if (streamId.StartsWith(host.Id, StringComparison.OrdinalIgnoreCase)) else if (streamId.StartsWith(host.Id, StringComparison.OrdinalIgnoreCase))
{ {
hostsWithChannel = new List<TunerHostInfo> { host }; hostsWithChannel = new List<TunerHostInfo> {host};
streamId = streamId.Substring(host.Id.Length); streamId = streamId.Substring(host.Id.Length);
break; break;
} }
@ -222,7 +223,8 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts
} }
} }
var stream = await GetChannelStream(host, channelId, streamId, cancellationToken).ConfigureAwait(false); var stream =
await GetChannelStream(host, channelId, streamId, cancellationToken).ConfigureAwait(false);
if (EnableMediaProbing) if (EnableMediaProbing)
{ {
@ -239,6 +241,10 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts
} }
} }
} }
else
{
throw new FileNotFoundException();
}
throw new LiveTvConflictException(); throw new LiveTvConflictException();
} }