@ -1,5 +1,6 @@
using MediaBrowser.Common.Extensions ;
using MediaBrowser.Common.IO ;
using MediaBrowser.Common.Net ;
using MediaBrowser.Controller.Channels ;
using MediaBrowser.Controller.Configuration ;
using MediaBrowser.Controller.Dlna ;
@ -13,6 +14,7 @@ using MediaBrowser.Controller.Persistence;
using MediaBrowser.Model.Configuration ;
using MediaBrowser.Model.Dlna ;
using MediaBrowser.Model.Drawing ;
using MediaBrowser.Model.Dto ;
using MediaBrowser.Model.Entities ;
using MediaBrowser.Model.IO ;
using MediaBrowser.Model.Library ;
@ -72,20 +74,14 @@ namespace MediaBrowser.Api.Playback
protected ILiveTvManager LiveTvManager { get ; private set ; }
protected IDlnaManager DlnaManager { get ; private set ; }
protected IChannelManager ChannelManager { get ; private set ; }
protected IHttpClient HttpClient { get ; private set ; }
/// <summary>
/// Initializes a new instance of the <see cref="BaseStreamingService" /> class.
/// </summary>
/// <param name="serverConfig">The server configuration.</param>
/// <param name="userManager">The user manager.</param>
/// <param name="libraryManager">The library manager.</param>
/// <param name="isoManager">The iso manager.</param>
/// <param name="mediaEncoder">The media encoder.</param>
/// <param name="dtoService">The dto service.</param>
/// <param name="fileSystem">The file system.</param>
/// <param name="itemRepository">The item repository.</param>
protected BaseStreamingService ( IServerConfigurationManager serverConfig , IUserManager userManager , ILibraryManager libraryManager , IIsoManager isoManager , IMediaEncoder mediaEncoder , IDtoService dtoService , IFileSystem fileSystem , IItemRepository itemRepository , ILiveTvManager liveTvManager , IEncodingManager encodingManager , IDlnaManager dlnaManager , IChannelManager channelManager )
protected BaseStreamingService ( IServerConfigurationManager serverConfig , IUserManager userManager , ILibraryManager libraryManager , IIsoManager isoManager , IMediaEncoder mediaEncoder , IDtoService dtoService , IFileSystem fileSystem , IItemRepository itemRepository , ILiveTvManager liveTvManager , IEncodingManager encodingManager , IDlnaManager dlnaManager , IChannelManager channelManager , IHttpClient httpClient )
{
HttpClient = httpClient ;
ChannelManager = channelManager ;
DlnaManager = dlnaManager ;
EncodingManager = encodingManager ;
@ -483,8 +479,12 @@ namespace MediaBrowser.Api.Playback
/// <param name="state">The state.</param>
/// <param name="outputVideoCodec">The output video codec.</param>
/// <param name="performTextSubtitleConversion">if set to <c>true</c> [perform text subtitle conversion].</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>System.String.</returns>
protected string GetOutputSizeParam ( StreamState state , string outputVideoCodec , bool performTextSubtitleConversion )
protected string GetOutputSizeParam ( StreamState state ,
string outputVideoCodec ,
bool performTextSubtitleConversion ,
CancellationToken cancellationToken )
{
// http://sonnati.wordpress.com/2012/10/19/ffmpeg-the-swiss-army-knife-of-internet-streaming-part-vi/
@ -496,7 +496,7 @@ namespace MediaBrowser.Api.Playback
if ( state . SubtitleStream ! = null & & ! state . SubtitleStream . IsGraphicalSubtitleStream )
{
assSubtitleParam = GetTextSubtitleParam ( state , performTextSubtitleConversion );
assSubtitleParam = GetTextSubtitleParam ( state , performTextSubtitleConversion , cancellationToken );
copyTsParam = " -copyts" ;
}
@ -592,11 +592,15 @@ namespace MediaBrowser.Api.Playback
/// </summary>
/// <param name="state">The state.</param>
/// <param name="performConversion">if set to <c>true</c> [perform conversion].</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>System.String.</returns>
protected string GetTextSubtitleParam ( StreamState state , bool performConversion )
protected string GetTextSubtitleParam ( StreamState state ,
bool performConversion ,
CancellationToken cancellationToken )
{
var path = state . SubtitleStream . IsExternal ? GetConvertedAssPath ( state . MediaPath , state . SubtitleStream , performConversion ) :
GetExtractedAssPath ( state , performConversion ) ;
var path = state . SubtitleStream . IsExternal ?
GetConvertedAssPath ( state . SubtitleStream , performConversion , cancellationToken ) :
GetExtractedAssPath ( state , performConversion , cancellationToken ) ;
if ( string . IsNullOrEmpty ( path ) )
{
@ -615,8 +619,11 @@ namespace MediaBrowser.Api.Playback
/// </summary>
/// <param name="state">The state.</param>
/// <param name="performConversion">if set to <c>true</c> [perform conversion].</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>System.String.</returns>
private string GetExtractedAssPath ( StreamState state , bool performConversion )
private string GetExtractedAssPath ( StreamState state ,
bool performConversion ,
CancellationToken cancellationToken )
{
var path = EncodingManager . GetSubtitleCachePath ( state . MediaPath , state . SubtitleStream . Index , ".ass" ) ;
@ -636,7 +643,7 @@ namespace MediaBrowser.Api.Playback
// See https://lists.ffmpeg.org/pipermail/ffmpeg-cvslog/2013-April/063616.html
var isAssSubtitle = string . Equals ( state . SubtitleStream . Codec , "ass" , StringComparison . OrdinalIgnoreCase ) | | string . Equals ( state . SubtitleStream . Codec , "ssa" , StringComparison . OrdinalIgnoreCase ) ;
var task = MediaEncoder . ExtractTextSubtitle ( inputPath , type , state . SubtitleStream . Index , isAssSubtitle , path , CancellationToken. None ) ;
var task = MediaEncoder . ExtractTextSubtitle ( inputPath , type , state . SubtitleStream . Index , isAssSubtitle , path , cancellationToken ) ;
Task . WaitAll ( task ) ;
}
@ -652,11 +659,13 @@ namespace MediaBrowser.Api.Playback
/// <summary>
/// Gets the converted ass path.
/// </summary>
/// <param name="mediaPath">The media path.</param>
/// <param name="subtitleStream">The subtitle stream.</param>
/// <param name="performConversion">if set to <c>true</c> [perform conversion].</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>System.String.</returns>
private string GetConvertedAssPath ( string mediaPath , MediaStream subtitleStream , bool performConversion )
private string GetConvertedAssPath ( MediaStream subtitleStream ,
bool performConversion ,
CancellationToken cancellationToken )
{
var path = EncodingManager . GetSubtitleCachePath ( subtitleStream . Path , ".ass" ) ;
@ -668,7 +677,7 @@ namespace MediaBrowser.Api.Playback
Directory . CreateDirectory ( parentPath ) ;
var task = MediaEncoder . ConvertTextSubtitleToAss ( subtitleStream . Path , path , subtitleStream . Language , CancellationToken. None ) ;
var task = MediaEncoder . ConvertTextSubtitleToAss ( subtitleStream . Path , path , subtitleStream . Language , cancellationToken ) ;
Task . WaitAll ( task ) ;
}
@ -696,7 +705,7 @@ namespace MediaBrowser.Api.Playback
// Add resolution params, if specified
if ( request . Width . HasValue | | request . Height . HasValue | | request . MaxHeight . HasValue | | request . MaxWidth . HasValue )
{
outputSizeParam = GetOutputSizeParam ( state , outputVideoCodec , false ). TrimEnd ( '"' ) ;
outputSizeParam = GetOutputSizeParam ( state , outputVideoCodec , false , CancellationToken . None ). TrimEnd ( '"' ) ;
outputSizeParam = "," + outputSizeParam . Substring ( outputSizeParam . IndexOf ( "scale" , StringComparison . OrdinalIgnoreCase ) ) ;
}
@ -842,7 +851,7 @@ namespace MediaBrowser.Api.Playback
/// <returns>System.String.</returns>
protected string GetInputArgument ( StreamState state )
{
var type = InputType. File ;
var type = state. IsRemote ? InputType . Url : InputType. File ;
var inputPath = new [ ] { state . MediaPath } ;
@ -862,8 +871,10 @@ namespace MediaBrowser.Api.Playback
/// </summary>
/// <param name="state">The state.</param>
/// <param name="outputPath">The output path.</param>
/// <param name="cancellationTokenSource">The cancellation token source.</param>
/// <returns>Task.</returns>
protected async Task StartFfMpeg ( StreamState state , string outputPath )
/// <exception cref="System.InvalidOperationException">ffmpeg was not found at + MediaEncoder.EncoderPath</exception>
protected async Task StartFfMpeg ( StreamState state , string outputPath , CancellationTokenSource cancellationTokenSource )
{
if ( ! File . Exists ( MediaEncoder . EncoderPath ) )
{
@ -874,7 +885,7 @@ namespace MediaBrowser.Api.Playback
if ( state . IsInputVideo & & state . VideoType = = VideoType . Iso & & state . IsoType . HasValue & & IsoManager . CanMount ( state . MediaPath ) )
{
state . IsoMount = await IsoManager . Mount ( state . MediaPath , CancellationToken. None ) . ConfigureAwait ( false ) ;
state . IsoMount = await IsoManager . Mount ( state . MediaPath , cancellationTokenSource. Token ) . ConfigureAwait ( false ) ;
}
var commandLineArgs = GetCommandLineArguments ( outputPath , state , true ) ;
@ -906,7 +917,7 @@ namespace MediaBrowser.Api.Playback
EnableRaisingEvents = true
} ;
ApiEntryPoint . Instance . OnTranscodeBeginning ( outputPath , TranscodingJobType , process , state . Request . StartTimeTicks , state . MediaPath , state . Request . DeviceId );
ApiEntryPoint . Instance . OnTranscodeBeginning ( outputPath , TranscodingJobType , process , state . Request . StartTimeTicks , state . MediaPath , state . Request . DeviceId , cancellationTokenSource );
var commandLineLogMessage = process . StartInfo . FileName + " " + process . StartInfo . Arguments ;
Logger . Info ( commandLineLogMessage ) ;
@ -918,7 +929,7 @@ namespace MediaBrowser.Api.Playback
state . LogFileStream = FileSystem . GetFileStream ( logFilePath , FileMode . Create , FileAccess . Write , FileShare . Read , true ) ;
var commandLineLogMessageBytes = Encoding . UTF8 . GetBytes ( commandLineLogMessage + Environment . NewLine + Environment . NewLine ) ;
await state . LogFileStream . WriteAsync ( commandLineLogMessageBytes , 0 , commandLineLogMessageBytes . Length ). ConfigureAwait ( false ) ;
await state . LogFileStream . WriteAsync ( commandLineLogMessageBytes , 0 , commandLineLogMessageBytes . Length , cancellationTokenSource . Token ). ConfigureAwait ( false ) ;
process . Exited + = ( sender , args ) = > OnFfMpegProcessExited ( process , state ) ;
@ -946,19 +957,19 @@ namespace MediaBrowser.Api.Playback
// Wait for the file to exist before proceeeding
while ( ! File . Exists ( outputPath ) )
{
await Task . Delay ( 100 ). ConfigureAwait ( false ) ;
await Task . Delay ( 100 , cancellationTokenSource . Token ). ConfigureAwait ( false ) ;
}
// Allow a small amount of time to buffer a little
if ( state . IsInputVideo )
{
await Task . Delay ( 500 ). ConfigureAwait ( false ) ;
await Task . Delay ( 500 , cancellationTokenSource . Token ). ConfigureAwait ( false ) ;
}
// This is arbitrary, but add a little buffer time when internet streaming
if ( state . IsRemote )
{
await Task . Delay ( 3000 ). ConfigureAwait ( false ) ;
await Task . Delay ( 3000 , cancellationTokenSource . Token ). ConfigureAwait ( false ) ;
}
}
@ -1050,13 +1061,19 @@ namespace MediaBrowser.Api.Playback
/// <summary>
/// Gets the user agent param.
/// </summary>
/// <param name=" path">The path .</param>
/// <param name=" state">The state .</param>
/// <returns>System.String.</returns>
private string GetUserAgentParam ( string path )
private string GetUserAgentParam ( StreamState state )
{
var useragent = GetUserAgent ( path ) ;
string useragent ;
state . RemoteHttpHeaders . TryGetValue ( "User-Agent" , out useragent ) ;
if ( ! string . IsNullOrEmpty ( useragent ) )
if ( string . IsNullOrWhiteSpace ( useragent ) )
{
useragent = GetUserAgent ( state . MediaPath ) ;
}
if ( ! string . IsNullOrWhiteSpace ( useragent ) )
{
return "-user-agent \"" + useragent + "\"" ;
}
@ -1337,9 +1354,7 @@ namespace MediaBrowser.Api.Playback
state . Request . AudioCodec = state . SupportedAudioCodecs . FirstOrDefault ( ) ;
}
var item = string . IsNullOrEmpty ( request . MediaSourceId ) ?
LibraryManager . GetItemById ( request . Id ) :
LibraryManager . GetItemById ( request . MediaSourceId ) ;
var item = LibraryManager . GetItemById ( request . Id ) ;
if ( user ! = null & & item . GetPlayAccess ( user ) ! = PlayAccess . Full )
{
@ -1427,19 +1442,24 @@ namespace MediaBrowser.Api.Playback
}
else if ( item is IChannelMediaItem )
{
var source = await GetChannelMediaInfo ( request . Id , CancellationToken. None ) . ConfigureAwait ( false ) ;
var source = await GetChannelMediaInfo ( request . Id , request. MediaSourceId , cancellationToken ) . ConfigureAwait ( false ) ;
state . IsInputVideo = string . Equals ( item . MediaType , MediaType . Video , StringComparison . OrdinalIgnoreCase ) ;
state . IsRemote = source . Is Remote;
state . IsRemote = source . LocationType = = LocationType . Remote;
state . MediaPath = source . Path ;
state . RunTimeTicks = item . RunTimeTicks ;
mediaStreams = GetMediaStreams ( source ) . ToList ( ) ;
state . RemoteHttpHeaders = source . RequiredHttpHeaders ;
mediaStreams = source . MediaStreams ;
}
else
{
state . MediaPath = item . Path ;
state . IsRemote = item . LocationType = = LocationType . Remote ;
var mediaSource = string . IsNullOrWhiteSpace ( request . MediaSourceId )
? item
: LibraryManager . GetItemById ( request . MediaSourceId ) ;
state . MediaPath = mediaSource . Path ;
state . IsRemote = mediaSource . LocationType = = LocationType . Remote ;
var video = item as Video ;
var video = mediaSource as Video ;
if ( video ! = null )
{
@ -1461,20 +1481,20 @@ namespace MediaBrowser.Api.Playback
state . InputContainer = video . Container ;
}
var audio = item as Audio ;
var audio = mediaSource as Audio ;
if ( audio ! = null )
{
state . InputContainer = audio . Container ;
}
state . RunTimeTicks = item . RunTimeTicks ;
state . RunTimeTicks = mediaSource . RunTimeTicks ;
}
var videoRequest = request as VideoStreamRequest ;
mediaStreams = mediaStreams ? ? ItemRepository . GetMediaStreams ( new MediaStreamQuery
{
ItemId = item . Id
ItemId = new Guid ( string . IsNullOrWhiteSpace ( request . MediaSourceId ) ? request . Id : request . MediaSourceId )
} ) . ToList ( ) ;
@ -1545,65 +1565,32 @@ namespace MediaBrowser.Api.Playback
}
}
private IEnumerable < MediaStream > GetMediaStreams ( ChannelMediaInfo info )
{
var list = new List < MediaStream > ( ) ;
if ( ! string . IsNullOrWhiteSpace ( info . VideoCodec ) & &
! string . IsNullOrWhiteSpace ( info . AudioCodec ) )
{
list . Add ( new MediaStream
{
Type = MediaStreamType . Video ,
Width = info . Width ,
RealFrameRate = info . Framerate ,
Profile = info . VideoProfile ,
Level = info . VideoLevel ,
Index = - 1 ,
Height = info . Height ,
Codec = info . VideoCodec ,
BitRate = info . VideoBitrate ,
AverageFrameRate = info . Framerate
} ) ;
list . Add ( new MediaStream
{
Type = MediaStreamType . Audio ,
Index = - 1 ,
Codec = info . AudioCodec ,
BitRate = info . AudioBitrate ,
Channels = info . AudioChannels ,
SampleRate = info . AudioSampleRate
} ) ;
}
return list ;
}
private async Task < ChannelMediaInfo > GetChannelMediaInfo ( string id , CancellationToken cancellationToken )
private async Task < MediaSourceInfo > GetChannelMediaInfo ( string id ,
string mediaSourceId ,
CancellationToken cancellationToken )
{
var channelMediaSources = await ChannelManager . GetChannelItemMediaSources ( id , cancellationToken )
. ConfigureAwait ( false ) ;
var list = channelMediaSources . ToList ( ) ;
var preferredWidth = ServerConfigurationManager . Configuration . ChannelOptions . PreferredStreamingWidth ;
if ( preferredWidth . HasValue )
if ( ! string . IsNullOrWhiteSpace ( mediaSourceId ) )
{
var val = preferredWidth . Value ;
var source = list
. FirstOrDefault ( i = > string . Equals ( mediaSourceId , i . Id ) ) ;
return list
. OrderBy ( i = > Math . Abs ( i . Width ? ? 0 - val ) )
. ThenByDescending ( i = > i . Width ? ? 0 )
. ThenBy ( list . IndexOf )
. First ( ) ;
if ( source ! = null )
{
return source ;
}
Logger . Warn ( "Invalid channel MediaSourceId requested, defaulting to first. Item: {0}. Requested MediaSourceId: {1}." ,
id ,
mediaSourceId
) ;
}
return list
. OrderByDescending ( i = > i . Width ? ? 0 )
. ThenBy ( list . IndexOf )
. First ( ) ;
return list . First ( ) ;
}
private bool CanStreamCopyVideo ( VideoStreamRequest request , MediaStream videoStream )
@ -1932,7 +1919,11 @@ namespace MediaBrowser.Api.Playback
inputModifier + = " " + probeSize ;
inputModifier = inputModifier . Trim ( ) ;
inputModifier + = " " + GetUserAgentParam ( state . MediaPath ) ;
if ( state . IsRemote )
{
inputModifier + = " " + GetUserAgentParam ( state ) ;
}
inputModifier = inputModifier . Trim ( ) ;
inputModifier + = " " + GetFastSeekCommandLineParameter ( state . Request ) ;