added live channel playback
This commit is contained in:
parent
a5be2523c5
commit
ede84702d1
|
@ -349,26 +349,42 @@ 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;
|
||||
}
|
||||
|
||||
try
|
||||
Logger.Info("Deleting partial stream file(s) {0}", path);
|
||||
|
||||
await Task.Delay(delayMs).ConfigureAwait(false);
|
||||
|
||||
try
|
||||
{
|
||||
if (jobType == TranscodingJobType.Progressive)
|
||||
{
|
||||
if (job.Type == TranscodingJobType.Progressive)
|
||||
{
|
||||
DeleteProgressivePartialStreamFiles(job.Path);
|
||||
}
|
||||
else
|
||||
{
|
||||
DeleteHlsPartialStreamFiles(job.Path);
|
||||
}
|
||||
DeleteProgressivePartialStreamFiles(path);
|
||||
}
|
||||
catch (IOException ex)
|
||||
else
|
||||
{
|
||||
Logger.ErrorException("Error deleting partial stream file(s) {0}", ex, job.Path);
|
||||
DeleteHlsPartialStreamFiles(path);
|
||||
}
|
||||
}
|
||||
catch (IOException ex)
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
@ -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" />
|
||||
|
|
|
@ -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,10 +949,11 @@ namespace MediaBrowser.Api.Playback
|
|||
}
|
||||
|
||||
itemId = recording.Id;
|
||||
state.SendInputOverStandardInput = recording.RecordingInfo.Status == RecordingStatus.InProgress;
|
||||
}
|
||||
else if (string.Equals(request.Type, "Channel", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var channel = LiveTvManager.GetInternalChannel(request.Id);
|
||||
var channel = LiveTvManager.GetInternalChannel(request.Id);
|
||||
|
||||
state.VideoType = VideoType.VideoFile;
|
||||
state.IsInputVideo = string.Equals(channel.MediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase);
|
||||
|
@ -926,6 +973,7 @@ namespace MediaBrowser.Api.Playback
|
|||
}
|
||||
|
||||
itemId = channel.Id;
|
||||
state.SendInputOverStandardInput = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
32
MediaBrowser.Api/Playback/EndlessStreamCopy.cs
Normal file
32
MediaBrowser.Api/Playback/EndlessStreamCopy.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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; }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -77,15 +77,14 @@ namespace MediaBrowser.Controller.MediaInfo
|
|||
.Where(f => !string.IsNullOrEmpty(f))
|
||||
.ToList();
|
||||
}
|
||||
|
||||
|
||||
/// <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;
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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>
|
||||
|
|
Loading…
Reference in New Issue
Block a user