2013-05-17 18:05:49 +00:00
using System.Net ;
using System.Net.Cache ;
using System.Net.Http ;
using MediaBrowser.Api.Images ;
2013-03-27 03:27:03 +00:00
using MediaBrowser.Common.IO ;
2013-04-07 20:55:05 +00:00
using MediaBrowser.Common.MediaInfo ;
2013-03-04 05:43:06 +00:00
using MediaBrowser.Common.Net ;
2013-02-27 04:44:41 +00:00
using MediaBrowser.Controller ;
2013-02-27 04:19:05 +00:00
using MediaBrowser.Controller.Entities ;
using MediaBrowser.Controller.Entities.Audio ;
2013-02-27 20:25:45 +00:00
using MediaBrowser.Controller.Library ;
2013-02-27 04:19:05 +00:00
using MediaBrowser.Model.Dto ;
2013-04-07 20:55:05 +00:00
using MediaBrowser.Model.Entities ;
2013-03-24 02:45:00 +00:00
using System ;
using System.Collections.Generic ;
2013-02-27 04:19:05 +00:00
using System.IO ;
using System.Threading.Tasks ;
namespace MediaBrowser.Api.Playback.Progressive
{
/// <summary>
/// Class BaseProgressiveStreamingService
/// </summary>
public abstract class BaseProgressiveStreamingService : BaseStreamingService
{
2013-04-07 20:55:05 +00:00
protected BaseProgressiveStreamingService ( IServerApplicationPaths appPaths , IUserManager userManager , ILibraryManager libraryManager , IIsoManager isoManager , IMediaEncoder mediaEncoder ) :
base ( appPaths , userManager , libraryManager , isoManager , mediaEncoder )
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
{
var video = state . Item as Video ;
if ( video ! = null )
{
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 )
{
var audio = state . Item as Audio ;
if ( audio ! = null )
{
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-03-23 04:04:36 +00:00
var timeSeek = RequestContext . 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-03-23 04:04:36 +00:00
var transferMode = RequestContext . GetHeader ( "transferMode.dlna.org" ) ;
2013-03-24 02:45:00 +00:00
responseHeaders [ "transferMode.dlna.org" ] = string . IsNullOrEmpty ( transferMode ) ? "Streaming" : transferMode ;
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
2013-03-14 01:46:27 +00:00
if ( string . Equals ( extension , ".mp3" , StringComparison . OrdinalIgnoreCase ) )
{
2013-04-03 12:03:37 +00:00
contentFeatures = "DLNA.ORG_PN=MP3" ;
2013-03-14 01:46:27 +00:00
}
else if ( string . Equals ( extension , ".aac" , StringComparison . OrdinalIgnoreCase ) )
{
2013-04-03 12:03:37 +00:00
contentFeatures = "DLNA.ORG_PN=AAC_ISO" ;
2013-03-14 01:46:27 +00:00
}
else if ( string . Equals ( extension , ".wma" , StringComparison . OrdinalIgnoreCase ) )
{
2013-04-03 12:03:37 +00:00
contentFeatures = "DLNA.ORG_PN=WMABASE" ;
2013-03-14 01:46:27 +00:00
}
else if ( string . Equals ( extension , ".avi" , StringComparison . OrdinalIgnoreCase ) )
{
2013-04-03 12:03:37 +00:00
contentFeatures = "DLNA.ORG_PN=AVI" ;
2013-03-14 01:46:27 +00:00
}
else if ( string . Equals ( extension , ".mp4" , StringComparison . OrdinalIgnoreCase ) )
{
2013-04-10 04:38:04 +00:00
contentFeatures = "DLNA.ORG_PN=MPEG4_P2_SP_AAC" ;
2013-03-14 01:46:27 +00:00
}
else if ( string . Equals ( extension , ".mpeg" , StringComparison . OrdinalIgnoreCase ) )
{
2013-04-03 12:03:37 +00:00
contentFeatures = "DLNA.ORG_PN=MPEG_PS_PAL" ;
2013-03-14 01:46:27 +00:00
}
else if ( string . Equals ( extension , ".wmv" , StringComparison . OrdinalIgnoreCase ) )
{
2013-04-03 12:03:37 +00:00
contentFeatures = "DLNA.ORG_PN=WMVHIGH_BASE" ;
2013-03-14 01:46:27 +00:00
}
else if ( string . Equals ( extension , ".asf" , StringComparison . OrdinalIgnoreCase ) )
{
2013-04-03 12:03:37 +00:00
// ??
contentFeatures = "DLNA.ORG_PN=WMVHIGH_BASE" ;
2013-03-14 01:46:27 +00:00
}
else if ( string . Equals ( extension , ".mkv" , StringComparison . OrdinalIgnoreCase ) )
{
2013-04-03 12:03:37 +00:00
// ??
contentFeatures = "" ;
2013-03-14 01:46:27 +00:00
}
if ( ! string . IsNullOrEmpty ( contentFeatures ) )
{
2013-04-10 15:32:09 +00:00
responseHeaders [ "ContentFeatures.DLNA.ORG" ] = ( contentFeatures + orgOp + orgCi + dlnaflags ) . Trim ( ';' ) ;
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
{
var state = GetState ( request ) ;
2013-03-27 03:27:03 +00:00
if ( request . AlbumArt )
{
return GetAlbumArtResponse ( state ) ;
}
2013-03-24 02:45:00 +00:00
var responseHeaders = new Dictionary < string , string > ( ) ;
2013-05-17 18:05:49 +00:00
if ( request . Static & & state . Item . LocationType = = LocationType . Remote )
{
return GetStaticRemoteStreamResult ( state . Item , responseHeaders , isHeadRequest ) . Result ;
}
2013-04-03 12:03:37 +00:00
var outputPath = GetOutputFilePath ( state ) ;
var outputPathExists = File . Exists ( outputPath ) ;
var isStatic = request . Static | |
( outputPathExists & & ! ApiEntryPoint . Instance . HasActiveTranscodingJob ( outputPath , TranscodingJobType . Progressive ) ) ;
AddDlnaHeaders ( state , responseHeaders , isStatic ) ;
2013-03-14 01:46:27 +00:00
2013-02-27 04:19:05 +00:00
if ( request . Static )
{
2013-03-24 02:45:00 +00:00
return ResultFactory . GetStaticFileResult ( RequestContext , state . Item . Path , 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-03-24 02:45:00 +00:00
return ResultFactory . GetStaticFileResult ( RequestContext , outputPath , 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>
/// <param name="item">The item.</param>
/// <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>
private async Task < object > GetStaticRemoteStreamResult ( BaseItem item , Dictionary < string , string > responseHeaders , bool isHeadRequest )
{
responseHeaders [ "Accept-Ranges" ] = "none" ;
var httpClient = new HttpClient ( new WebRequestHandler
{
CachePolicy = new RequestCachePolicy ( RequestCacheLevel . BypassCache ) ,
AutomaticDecompression = DecompressionMethods . None
} ) ;
using ( var message = new HttpRequestMessage ( HttpMethod . Get , item . Path ) )
{
var useragent = GetUserAgent ( item ) ;
if ( ! string . IsNullOrEmpty ( useragent ) )
{
message . Headers . Add ( "User-Agent" , useragent ) ;
}
var response = await httpClient . SendAsync ( message , HttpCompletionOption . ResponseHeadersRead ) . ConfigureAwait ( false ) ;
response . EnsureSuccessStatusCode ( ) ;
var contentType = response . Content . Headers . ContentType . MediaType ;
// Headers only
if ( isHeadRequest )
{
response . Dispose ( ) ;
2013-05-17 19:18:54 +00:00
httpClient . Dispose ( ) ;
2013-05-17 18:05:49 +00:00
return ResultFactory . GetResult ( null , contentType , responseHeaders ) ;
}
var result = new StaticRemoteStreamWriter ( response , httpClient ) ;
result . Options [ "Content-Type" ] = contentType ;
// Add the response headers to the result object
foreach ( var header in responseHeaders )
{
result . Options [ header . Key ] = header . Value ;
}
return result ;
}
}
2013-03-27 03:27:03 +00:00
/// <summary>
/// Gets the album art response.
/// </summary>
/// <param name="state">The state.</param>
/// <returns>System.Object.</returns>
private object GetAlbumArtResponse ( StreamState state )
{
var request = new GetItemImage
{
MaxWidth = 800 ,
MaxHeight = 800 ,
Type = ImageType . Primary ,
Id = state . Item . Id . ToString ( )
} ;
// Try and find some image to return
if ( ! state . Item . HasImage ( ImageType . Primary ) )
{
if ( state . Item . HasImage ( ImageType . Backdrop ) )
{
request . Type = ImageType . Backdrop ;
}
else if ( state . Item . HasImage ( ImageType . Thumb ) )
{
request . Type = ImageType . Thumb ;
}
else if ( state . Item . HasImage ( ImageType . Logo ) )
{
request . Type = ImageType . Logo ;
}
}
2013-05-05 04:49:49 +00:00
return new ImageService ( UserManager , LibraryManager , ApplicationPaths , null )
2013-03-27 03:27:03 +00:00
{
Logger = Logger ,
RequestContext = RequestContext ,
ResultFactory = ResultFactory
} . Get ( request ) ;
}
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 ) ;
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 )
{
2013-03-24 02:45:00 +00:00
responseHeaders [ "Accept-Ranges" ] = "none" ;
return ResultFactory . GetResult ( null , contentType , responseHeaders ) ;
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-03-24 02:45:00 +00:00
var result = new ProgressiveStreamWriter ( outputPath , state , Logger ) ;
result . Options [ "Accept-Ranges" ] = "none" ;
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
}
/// <summary>
/// Deletes the partial stream files.
/// </summary>
/// <param name="outputFilePath">The output file path.</param>
protected override void DeletePartialStreamFiles ( string outputFilePath )
{
File . Delete ( outputFilePath ) ;
}
}
}