2014-02-13 16:38:43 +00:00
using MediaBrowser.Common.IO ;
2013-03-04 05:43:06 +00:00
using MediaBrowser.Common.Net ;
2013-12-15 19:39:21 +00:00
using MediaBrowser.Controller.Configuration ;
2013-09-18 18:49:06 +00:00
using MediaBrowser.Controller.Drawing ;
2013-09-04 17:02:19 +00:00
using MediaBrowser.Controller.Dto ;
2013-02-27 20:25:45 +00:00
using MediaBrowser.Controller.Library ;
2013-12-21 18:37:34 +00:00
using MediaBrowser.Controller.LiveTv ;
2014-01-12 06:31:21 +00:00
using MediaBrowser.Controller.MediaInfo ;
2013-06-18 19:16:27 +00:00
using MediaBrowser.Controller.Persistence ;
2013-02-27 04:19:05 +00:00
using MediaBrowser.Model.Dto ;
2013-09-01 13:13:11 +00:00
using MediaBrowser.Model.IO ;
2014-02-13 16:38:43 +00:00
using System ;
2013-03-24 02:45:00 +00:00
using System.Collections.Generic ;
2013-02-27 04:19:05 +00:00
using System.IO ;
2013-12-21 18:37:34 +00:00
using System.Threading ;
2013-02-27 04:19:05 +00:00
using System.Threading.Tasks ;
2014-02-19 05:25:00 +00:00
using ServiceStack.Web ;
2013-02-27 04:19:05 +00:00
namespace MediaBrowser.Api.Playback.Progressive
{
/// <summary>
/// Class BaseProgressiveStreamingService
/// </summary>
public abstract class BaseProgressiveStreamingService : BaseStreamingService
{
2013-09-18 18:49:06 +00:00
protected readonly IImageProcessor ImageProcessor ;
2014-02-13 16:38:43 +00:00
protected readonly IHttpClient HttpClient ;
2013-09-04 17:02:19 +00:00
2014-02-13 16:38:43 +00:00
protected BaseProgressiveStreamingService ( IServerConfigurationManager serverConfig , IUserManager userManager , ILibraryManager libraryManager , IIsoManager isoManager , IMediaEncoder mediaEncoder , IDtoService dtoService , IFileSystem fileSystem , IItemRepository itemRepository , ILiveTvManager liveTvManager , IImageProcessor imageProcessor , IHttpClient httpClient )
2013-12-21 18:37:34 +00:00
: base ( serverConfig , userManager , libraryManager , isoManager , mediaEncoder , dtoService , fileSystem , itemRepository , liveTvManager )
2013-02-27 04:19:05 +00:00
{
2013-09-18 18:49:06 +00:00
ImageProcessor = imageProcessor ;
2014-02-13 16:38:43 +00:00
HttpClient = httpClient ;
2013-02-27 04:19:05 +00:00
}
/// <summary>
/// Gets the output file extension.
/// </summary>
/// <param name="state">The state.</param>
/// <returns>System.String.</returns>
protected override string GetOutputFileExtension ( StreamState state )
{
var ext = base . GetOutputFileExtension ( state ) ;
if ( ! string . IsNullOrEmpty ( ext ) )
{
return ext ;
}
2013-03-09 02:34:54 +00:00
var videoRequest = state . Request as VideoStreamRequest ;
2013-02-27 04:19:05 +00:00
// Try to infer based on the desired video codec
2013-03-09 02:34:54 +00:00
if ( videoRequest ! = null & & videoRequest . VideoCodec . HasValue )
2013-02-27 04:19:05 +00:00
{
2013-12-19 21:51:32 +00:00
if ( state . IsInputVideo )
2013-02-27 04:19:05 +00:00
{
2013-03-09 02:34:54 +00:00
switch ( videoRequest . VideoCodec . Value )
2013-02-27 04:19:05 +00:00
{
case VideoCodecs . H264 :
return ".ts" ;
case VideoCodecs . Theora :
return ".ogv" ;
case VideoCodecs . Vpx :
return ".webm" ;
case VideoCodecs . Wmv :
return ".asf" ;
}
}
}
// Try to infer based on the desired audio codec
if ( state . Request . AudioCodec . HasValue )
{
2013-12-19 21:51:32 +00:00
if ( ! state . IsInputVideo )
2013-02-27 04:19:05 +00:00
{
switch ( state . Request . AudioCodec . Value )
{
case AudioCodecs . Aac :
return ".aac" ;
case AudioCodecs . Mp3 :
return ".mp3" ;
case AudioCodecs . Vorbis :
return ".ogg" ;
case AudioCodecs . Wma :
return ".wma" ;
}
}
}
return null ;
}
2013-03-14 01:46:27 +00:00
/// <summary>
/// Adds the dlna headers.
/// </summary>
2013-03-24 02:45:00 +00:00
/// <param name="state">The state.</param>
/// <param name="responseHeaders">The response headers.</param>
2013-04-03 12:03:37 +00:00
/// <param name="isStaticallyStreamed">if set to <c>true</c> [is statically streamed].</param>
2013-03-24 02:45:00 +00:00
/// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns>
2013-04-03 12:03:37 +00:00
private void AddDlnaHeaders ( StreamState state , IDictionary < string , string > responseHeaders , bool isStaticallyStreamed )
2013-03-14 01:46:27 +00:00
{
2013-12-07 15:52:38 +00:00
var timeSeek = GetHeader ( "TimeSeekRange.dlna.org" ) ;
2013-03-14 01:46:27 +00:00
2013-03-23 04:04:36 +00:00
if ( ! string . IsNullOrEmpty ( timeSeek ) )
2013-03-14 01:46:27 +00:00
{
2013-03-24 02:45:00 +00:00
ResultFactory . ThrowError ( 406 , "Time seek not supported during encoding." , responseHeaders ) ;
return ;
2013-03-14 01:46:27 +00:00
}
2013-12-07 15:52:38 +00:00
var transferMode = GetHeader ( "transferMode.dlna.org" ) ;
2013-03-24 02:45:00 +00:00
responseHeaders [ "transferMode.dlna.org" ] = string . IsNullOrEmpty ( transferMode ) ? "Streaming" : transferMode ;
2014-01-10 13:52:01 +00:00
responseHeaders [ "realTimeInfo.dlna.org" ] = "DLNA.ORG_TLAG=*" ;
2013-03-14 01:46:27 +00:00
var contentFeatures = string . Empty ;
var extension = GetOutputFileExtension ( state ) ;
2013-04-03 12:03:37 +00:00
// first bit means Time based seek supported, second byte range seek supported (not sure about the order now), so 01 = only byte seek, 10 = time based, 11 = both, 00 = none
2013-04-10 15:32:09 +00:00
var orgOp = isStaticallyStreamed ? ";DLNA.ORG_OP=01" : ";DLNA.ORG_OP=00" ;
2013-04-03 12:03:37 +00:00
// 0 = native, 1 = transcoded
2013-04-10 15:32:09 +00:00
var orgCi = isStaticallyStreamed ? ";DLNA.ORG_CI=0" : ";DLNA.ORG_CI=1" ;
2013-04-03 12:03:37 +00:00
2013-04-10 15:32:09 +00:00
const string dlnaflags = ";DLNA.ORG_FLAGS=01500000000000000000000000000000" ;
2013-04-03 12:03:37 +00:00
2014-01-10 13:52:01 +00:00
if ( string . Equals ( extension , ".mp3" , StringComparison . OrdinalIgnoreCase ) )
{
contentFeatures = "DLNA.ORG_PN=MP3" ;
}
else if ( string . Equals ( extension , ".aac" , StringComparison . OrdinalIgnoreCase ) )
{
contentFeatures = "DLNA.ORG_PN=AAC_ISO" ;
}
else if ( string . Equals ( extension , ".wma" , StringComparison . OrdinalIgnoreCase ) )
{
contentFeatures = "DLNA.ORG_PN=WMABASE" ;
}
else if ( string . Equals ( extension , ".avi" , StringComparison . OrdinalIgnoreCase ) )
{
contentFeatures = "DLNA.ORG_PN=AVI" ;
}
2014-02-05 06:51:28 +00:00
else if ( string . Equals ( extension , ".mkv" , StringComparison . OrdinalIgnoreCase ) )
{
contentFeatures = "DLNA.ORG_PN=MATROSKA" ;
}
else if ( string . Equals ( extension , ".mp4" , StringComparison . OrdinalIgnoreCase ) )
{
contentFeatures = "DLNA.ORG_PN=AVC_MP4_MP_HD_720p_AAC" ;
}
2014-02-19 08:33:03 +00:00
else if ( string . Equals ( extension , ".mpeg" , StringComparison . OrdinalIgnoreCase ) )
{
contentFeatures = "DLNA.ORG_PN=MPEG_PS_PAL" ;
}
else if ( string . Equals ( extension , ".ts" , StringComparison . OrdinalIgnoreCase ) )
{
contentFeatures = "DLNA.ORG_PN=MPEG_PS_PAL" ;
}
2013-11-28 18:27:29 +00:00
//else if (string.Equals(extension, ".wmv", StringComparison.OrdinalIgnoreCase))
//{
// contentFeatures = "DLNA.ORG_PN=WMVHIGH_BASE";
//}
//else if (string.Equals(extension, ".asf", StringComparison.OrdinalIgnoreCase))
//{
// // ??
// contentFeatures = "DLNA.ORG_PN=WMVHIGH_BASE";
//}
2014-02-13 16:38:43 +00:00
2013-03-14 01:46:27 +00:00
if ( ! string . IsNullOrEmpty ( contentFeatures ) )
{
2014-02-02 13:36:31 +00:00
responseHeaders [ "contentFeatures.dlna.org" ] = ( contentFeatures + orgOp + orgCi + dlnaflags ) . Trim ( ';' ) ;
2013-03-14 01:46:27 +00:00
}
2014-01-11 05:49:18 +00:00
foreach ( var item in responseHeaders )
{
Request . Response . AddHeader ( item . Key , item . Value ) ;
}
2013-03-14 01:46:27 +00:00
}
2013-02-27 04:19:05 +00:00
/// <summary>
/// Gets the type of the transcoding job.
/// </summary>
/// <value>The type of the transcoding job.</value>
protected override TranscodingJobType TranscodingJobType
{
get { return TranscodingJobType . Progressive ; }
}
/// <summary>
/// Processes the request.
/// </summary>
/// <param name="request">The request.</param>
2013-03-13 03:57:54 +00:00
/// <param name="isHeadRequest">if set to <c>true</c> [is head request].</param>
2013-02-27 04:19:05 +00:00
/// <returns>Task.</returns>
2013-03-13 03:57:54 +00:00
protected object ProcessRequest ( StreamRequest request , bool isHeadRequest )
2013-02-27 04:19:05 +00:00
{
2013-12-21 18:37:34 +00:00
var state = GetState ( request , CancellationToken . None ) . Result ;
2013-02-27 04:19:05 +00:00
2013-03-24 02:45:00 +00:00
var responseHeaders = new Dictionary < string , string > ( ) ;
2013-12-19 21:51:32 +00:00
if ( request . Static & & state . IsRemote )
2013-05-17 18:05:49 +00:00
{
2014-02-03 17:44:13 +00:00
AddDlnaHeaders ( state , responseHeaders , true ) ;
2014-02-13 16:38:43 +00:00
2013-12-19 21:51:32 +00:00
return GetStaticRemoteStreamResult ( state . MediaPath , responseHeaders , isHeadRequest ) . Result ;
2013-05-17 18:05:49 +00:00
}
2013-04-03 12:03:37 +00:00
var outputPath = GetOutputFilePath ( state ) ;
var outputPathExists = File . Exists ( outputPath ) ;
2013-11-28 18:27:29 +00:00
var isStatic = request . Static | |
( outputPathExists & & ! ApiEntryPoint . Instance . HasActiveTranscodingJob ( outputPath , TranscodingJobType . Progressive ) ) ;
2013-04-03 12:03:37 +00:00
2013-11-28 18:27:29 +00:00
AddDlnaHeaders ( state , responseHeaders , isStatic ) ;
2013-03-14 01:46:27 +00:00
2013-02-27 04:19:05 +00:00
if ( request . Static )
{
2013-12-19 21:51:32 +00:00
return ResultFactory . GetStaticFileResult ( Request , state . MediaPath , FileShare . Read , responseHeaders , isHeadRequest ) ;
2013-02-27 04:19:05 +00:00
}
2013-04-03 12:03:37 +00:00
if ( outputPathExists & & ! ApiEntryPoint . Instance . HasActiveTranscodingJob ( outputPath , TranscodingJobType . Progressive ) )
2013-02-27 04:19:05 +00:00
{
2013-12-07 15:52:38 +00:00
return ResultFactory . GetStaticFileResult ( Request , outputPath , FileShare . Read , responseHeaders , isHeadRequest ) ;
2013-02-27 04:19:05 +00:00
}
2013-03-24 02:45:00 +00:00
return GetStreamResult ( state , responseHeaders , isHeadRequest ) . Result ;
2013-02-27 04:19:05 +00:00
}
2013-05-17 18:05:49 +00:00
/// <summary>
/// Gets the static remote stream result.
/// </summary>
2013-12-19 21:51:32 +00:00
/// <param name="mediaPath">The media path.</param>
2013-05-17 18:05:49 +00:00
/// <param name="responseHeaders">The response headers.</param>
/// <param name="isHeadRequest">if set to <c>true</c> [is head request].</param>
/// <returns>Task{System.Object}.</returns>
2013-12-19 21:51:32 +00:00
private async Task < object > GetStaticRemoteStreamResult ( string mediaPath , Dictionary < string , string > responseHeaders , bool isHeadRequest )
2013-05-17 18:05:49 +00:00
{
2014-02-14 04:00:13 +00:00
var options = new HttpRequestOptions
2013-05-17 18:05:49 +00:00
{
2014-02-13 16:38:43 +00:00
Url = mediaPath ,
UserAgent = GetUserAgent ( mediaPath ) ,
BufferContent = false
2014-02-14 04:00:13 +00:00
} ;
2013-05-17 18:05:49 +00:00
2014-02-14 04:00:13 +00:00
var response = await HttpClient . GetResponse ( options ) . ConfigureAwait ( false ) ;
2013-05-17 18:05:49 +00:00
2014-02-14 04:00:13 +00:00
responseHeaders [ "Accept-Ranges" ] = "none" ;
2013-05-17 18:05:49 +00:00
2014-02-19 05:21:03 +00:00
var length = response . Headers [ "Content-Length" ] ;
if ( ! string . IsNullOrEmpty ( length ) )
{
responseHeaders [ "Content-Length" ] = length ;
}
2014-02-13 16:38:43 +00:00
if ( isHeadRequest )
{
using ( response . Content )
2013-05-17 18:05:49 +00:00
{
2014-02-14 04:00:13 +00:00
return ResultFactory . GetResult ( new byte [ ] { } , response . ContentType , responseHeaders ) ;
2013-05-17 18:05:49 +00:00
}
2014-02-13 16:38:43 +00:00
}
2013-05-17 18:05:49 +00:00
2014-02-13 16:38:43 +00:00
var result = new StaticRemoteStreamWriter ( response ) ;
2013-05-17 18:05:49 +00:00
2014-02-13 16:38:43 +00:00
result . Options [ "Content-Type" ] = response . ContentType ;
2013-05-17 18:05:49 +00:00
2014-02-13 16:38:43 +00:00
// Add the response headers to the result object
foreach ( var header in responseHeaders )
{
result . Options [ header . Key ] = header . Value ;
2013-05-17 18:05:49 +00:00
}
2014-02-13 16:38:43 +00:00
return result ;
2013-05-17 18:05:49 +00:00
}
2013-02-27 04:19:05 +00:00
/// <summary>
/// Gets the stream result.
/// </summary>
/// <param name="state">The state.</param>
2013-03-24 02:45:00 +00:00
/// <param name="responseHeaders">The response headers.</param>
2013-03-13 03:57:54 +00:00
/// <param name="isHeadRequest">if set to <c>true</c> [is head request].</param>
2013-02-27 04:19:05 +00:00
/// <returns>Task{System.Object}.</returns>
2013-03-27 03:27:03 +00:00
private async Task < object > GetStreamResult ( StreamState state , IDictionary < string , string > responseHeaders , bool isHeadRequest )
2013-02-27 04:19:05 +00:00
{
// Use the command line args with a dummy playlist path
var outputPath = GetOutputFilePath ( state ) ;
2014-02-19 05:21:03 +00:00
responseHeaders [ "Accept-Ranges" ] = "none" ;
2013-03-24 02:45:00 +00:00
var contentType = MimeTypes . GetMimeType ( outputPath ) ;
2013-02-27 04:44:41 +00:00
2013-03-13 03:57:54 +00:00
// Headers only
if ( isHeadRequest )
{
2014-02-19 05:25:00 +00:00
var streamResult = ResultFactory . GetResult ( new byte [ ] { } , contentType , responseHeaders ) ;
var hasOptions = streamResult as IHasOptions ;
if ( hasOptions ! = null )
{
if ( hasOptions . Options . ContainsKey ( "Content-Length" ) )
{
hasOptions . Options . Remove ( "Content-Length" ) ;
}
}
return streamResult ;
2013-03-13 03:57:54 +00:00
}
2013-02-27 04:19:05 +00:00
if ( ! File . Exists ( outputPath ) )
{
2013-04-10 15:32:09 +00:00
await StartFfMpeg ( state , outputPath ) . ConfigureAwait ( false ) ;
2013-02-27 04:19:05 +00:00
}
else
{
2013-03-08 19:14:09 +00:00
ApiEntryPoint . Instance . OnTranscodeBeginRequest ( outputPath , TranscodingJobType . Progressive ) ;
2013-02-27 04:19:05 +00:00
}
2013-10-31 14:03:23 +00:00
var result = new ProgressiveStreamWriter ( outputPath , Logger , FileSystem ) ;
2013-03-24 02:45:00 +00:00
result . Options [ "Content-Type" ] = contentType ;
// Add the response headers to the result object
foreach ( var item in responseHeaders )
{
result . Options [ item . Key ] = item . Value ;
}
return result ;
2013-02-27 04:19:05 +00:00
}
}
}