added live channel playback

This commit is contained in:
Luke Pulverenti 2014-01-02 23:58:22 -05:00
parent a5be2523c5
commit ede84702d1
8 changed files with 167 additions and 31 deletions

View File

@ -349,25 +349,41 @@ namespace MediaBrowser.Api
// Also don't cache video
if (!hasExitedSuccessfully || job.StartTimeTicks.HasValue || job.IsVideo)
{
Logger.Info("Deleting partial stream file(s) {0}", job.Path);
DeletePartialStreamFiles(job.Path, job.Type, 0, 1500);
}
}
await Task.Delay(1500).ConfigureAwait(false);
private async void DeletePartialStreamFiles(string path, TranscodingJobType jobType, int retryCount, int delayMs)
{
if (retryCount >= 5)
{
return;
}
Logger.Info("Deleting partial stream file(s) {0}", path);
await Task.Delay(delayMs).ConfigureAwait(false);
try
{
if (job.Type == TranscodingJobType.Progressive)
if (jobType == TranscodingJobType.Progressive)
{
DeleteProgressivePartialStreamFiles(job.Path);
DeleteProgressivePartialStreamFiles(path);
}
else
{
DeleteHlsPartialStreamFiles(job.Path);
DeleteHlsPartialStreamFiles(path);
}
}
catch (IOException ex)
{
Logger.ErrorException("Error deleting partial stream file(s) {0}", ex, job.Path);
Logger.ErrorException("Error deleting partial stream file(s) {0}", ex, path);
DeletePartialStreamFiles(path, jobType, retryCount + 1, 500);
}
catch (Exception ex)
{
Logger.ErrorException("Error deleting partial stream file(s) {0}", ex, path);
}
}

View File

@ -97,6 +97,7 @@
<Compile Include="NotificationsService.cs" />
<Compile Include="PackageReviewService.cs" />
<Compile Include="PackageService.cs" />
<Compile Include="Playback\EndlessStreamCopy.cs" />
<Compile Include="Playback\Hls\AudioHlsService.cs" />
<Compile Include="Playback\Hls\BaseHlsService.cs" />
<Compile Include="Playback\Hls\HlsSegmentResponseFilter.cs" />

View File

@ -13,6 +13,7 @@ using MediaBrowser.Model.Drawing;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.LiveTv;
using System;
using System.Collections.Generic;
using System.Diagnostics;
@ -539,8 +540,8 @@ namespace MediaBrowser.Api.Playback
/// <returns>System.String.</returns>
protected string GetProbeSizeArgument(string mediaPath, bool isVideo, VideoType? videoType, IsoType? isoType)
{
var type = !isVideo ? MediaEncoderHelpers.GetInputType(mediaPath, null, null) :
MediaEncoderHelpers.GetInputType(mediaPath, videoType, isoType);
var type = !isVideo ? MediaEncoderHelpers.GetInputType(null, null) :
MediaEncoderHelpers.GetInputType(videoType, isoType);
return MediaEncoder.GetProbeSizeArgument(type);
}
@ -654,6 +655,11 @@ namespace MediaBrowser.Api.Playback
/// <returns>System.String.</returns>
protected string GetInputArgument(StreamState state)
{
if (state.SendInputOverStandardInput)
{
return "-";
}
var type = InputType.AudioFile;
var inputPath = new[] { state.MediaPath };
@ -705,7 +711,9 @@ namespace MediaBrowser.Api.Playback
Arguments = GetCommandLineArguments(outputPath, state, true),
WindowStyle = ProcessWindowStyle.Hidden,
ErrorDialog = false
ErrorDialog = false,
RedirectStandardInput = state.SendInputOverStandardInput
},
EnableRaisingEvents = true
@ -738,6 +746,11 @@ namespace MediaBrowser.Api.Playback
throw;
}
if (state.SendInputOverStandardInput)
{
StreamToStandardInput(process, state);
}
// MUST read both stdout and stderr asynchronously or a deadlock may occurr
process.BeginOutputReadLine();
@ -763,6 +776,34 @@ namespace MediaBrowser.Api.Playback
}
}
private async void StreamToStandardInput(Process process, StreamState state)
{
state.StandardInputCancellationTokenSource = new CancellationTokenSource();
try
{
await StreamToStandardInputInternal(process, state).ConfigureAwait(false);
}
catch (OperationCanceledException)
{
Logger.Debug("Stream to standard input closed normally.");
}
catch (Exception ex)
{
Logger.ErrorException("Error writing to standard input", ex);
}
}
private async Task StreamToStandardInputInternal(Process process, StreamState state)
{
state.StandardInputCancellationTokenSource = new CancellationTokenSource();
using (var fileStream = FileSystem.GetFileStream(state.MediaPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, true))
{
await new EndlessStreamCopy().CopyStream(fileStream, process.StandardInput.BaseStream, state.StandardInputCancellationTokenSource.Token).ConfigureAwait(false);
}
}
protected int? GetVideoBitrateParam(StreamState state)
{
return state.VideoRequest.VideoBitRate;
@ -831,6 +872,11 @@ namespace MediaBrowser.Api.Playback
state.IsoMount = null;
}
if (state.StandardInputCancellationTokenSource != null)
{
state.StandardInputCancellationTokenSource.Cancel();
}
var outputFilePath = GetOutputFilePath(state);
state.LogFileStream.Dispose();
@ -903,6 +949,7 @@ namespace MediaBrowser.Api.Playback
}
itemId = recording.Id;
state.SendInputOverStandardInput = recording.RecordingInfo.Status == RecordingStatus.InProgress;
}
else if (string.Equals(request.Type, "Channel", StringComparison.OrdinalIgnoreCase))
{
@ -926,6 +973,7 @@ namespace MediaBrowser.Api.Playback
}
itemId = channel.Id;
state.SendInputOverStandardInput = true;
}
else
{

View File

@ -0,0 +1,32 @@
using System.IO;
using System.Threading;
using System.Threading.Tasks;
namespace MediaBrowser.Api.Playback
{
public class EndlessStreamCopy
{
public async Task CopyStream(Stream source, Stream target, CancellationToken cancellationToken)
{
long position = 0;
while (!cancellationToken.IsCancellationRequested)
{
await source.CopyToAsync(target, 81920, cancellationToken).ConfigureAwait(false);
var fsPosition = source.Position;
var bytesRead = fsPosition - position;
//Logger.Debug("Streamed {0} bytes from file {1}", bytesRead, path);
if (bytesRead == 0)
{
await Task.Delay(100, cancellationToken).ConfigureAwait(false);
}
position = fsPosition;
}
}
}
}

View File

@ -1,4 +1,5 @@
using MediaBrowser.Model.Entities;
using System.Threading;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.IO;
using System.Collections.Generic;
using System.IO;
@ -47,5 +48,9 @@ namespace MediaBrowser.Api.Playback
public List<string> PlayableStreamFileNames { get; set; }
public bool HasMediaStreams { get; set; }
public bool SendInputOverStandardInput { get; set; }
public CancellationTokenSource StandardInputCancellationTokenSource { get; set; }
}
}

View File

@ -81,11 +81,10 @@ namespace MediaBrowser.Controller.MediaInfo
/// <summary>
/// Gets the type of the input.
/// </summary>
/// <param name="path">The path.</param>
/// <param name="videoType">Type of the video.</param>
/// <param name="isoType">Type of the iso.</param>
/// <returns>InputType.</returns>
public static InputType GetInputType(string path, VideoType? videoType, IsoType? isoType)
public static InputType GetInputType(VideoType? videoType, IsoType? isoType)
{
var type = InputType.AudioFile;

View File

@ -79,8 +79,11 @@ namespace MediaBrowser.Server.Implementations.LiveTv
if (user != null)
{
// Avoid implicitly captured closure
var currentUser = user;
channels = channels
.Where(i => i.IsParentalAllowed(user))
.Where(i => i.IsParentalAllowed(currentUser))
.OrderBy(i =>
{
double number = 0;
@ -419,7 +422,6 @@ namespace MediaBrowser.Server.Implementations.LiveTv
var allChannelsList = allChannels.ToList();
var list = new List<LiveTvChannel>();
var programs = new List<LiveTvProgram>();
var numComplete = 0;
@ -429,13 +431,6 @@ namespace MediaBrowser.Server.Implementations.LiveTv
{
var item = await GetChannel(channelInfo.Item2, channelInfo.Item1, cancellationToken).ConfigureAwait(false);
var channelPrograms = await service.GetProgramsAsync(channelInfo.Item2.Id, cancellationToken).ConfigureAwait(false);
var programTasks = channelPrograms.Select(program => GetProgram(program, item.ChannelInfo.ChannelType, service.Name, cancellationToken));
var programEntities = await Task.WhenAll(programTasks).ConfigureAwait(false);
programs.AddRange(programEntities);
list.Add(item);
}
catch (OperationCanceledException)
@ -451,11 +446,45 @@ namespace MediaBrowser.Server.Implementations.LiveTv
double percent = numComplete;
percent /= allChannelsList.Count;
progress.Report(5 * percent + 10);
}
_channels = list.ToDictionary(i => i.Id);
progress.Report(15);
numComplete = 0;
var programs = new List<LiveTvProgram>();
foreach (var item in list)
{
// Avoid implicitly captured closure
var currentChannel = item;
try
{
var channelPrograms = await service.GetProgramsAsync(currentChannel.ChannelInfo.Id, cancellationToken).ConfigureAwait(false);
var programTasks = channelPrograms.Select(program => GetProgram(program, currentChannel.ChannelInfo.ChannelType, service.Name, cancellationToken));
var programEntities = await Task.WhenAll(programTasks).ConfigureAwait(false);
programs.AddRange(programEntities);
}
catch (OperationCanceledException)
{
throw;
}
catch (Exception ex)
{
_logger.ErrorException("Error getting programs for channel {0}", ex, currentChannel.Name);
}
numComplete++;
double percent = numComplete;
percent /= allChannelsList.Count;
progress.Report(90 * percent + 10);
}
_programs = programs.ToDictionary(i => i.Id);
_channels = list.ToDictionary(i => i.Id);
}
private async Task<IEnumerable<Tuple<string, ChannelInfo>>> GetChannels(ILiveTvService service, CancellationToken cancellationToken)

View File

@ -127,6 +127,9 @@
<Content Include="dashboard-ui\css\images\icons\subtitles.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="dashboard-ui\css\images\icons\tv.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="dashboard-ui\css\images\icons\volumedown.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
@ -136,6 +139,9 @@
<Content Include="dashboard-ui\css\images\items\detail\tv.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="dashboard-ui\css\images\media\tvflyout.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="dashboard-ui\css\livetv.css">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>