2015-03-26 16:58:02 +00:00
using MediaBrowser.Controller.Devices ;
using MediaBrowser.Controller.Entities ;
using MediaBrowser.Controller.Library ;
2014-11-19 02:45:12 +00:00
using MediaBrowser.Controller.Net ;
2015-03-16 16:47:14 +00:00
using MediaBrowser.Model.Dlna ;
using MediaBrowser.Model.Dto ;
2015-03-28 20:22:27 +00:00
using MediaBrowser.Model.Entities ;
2014-11-19 02:45:12 +00:00
using MediaBrowser.Model.MediaInfo ;
2015-03-26 16:58:02 +00:00
using MediaBrowser.Model.Session ;
2014-11-19 02:45:12 +00:00
using ServiceStack ;
2015-03-23 17:19:21 +00:00
using System ;
2015-03-16 16:47:14 +00:00
using System.Collections.Generic ;
2014-11-19 02:45:12 +00:00
using System.Linq ;
using System.Threading ;
using System.Threading.Tasks ;
namespace MediaBrowser.Api.Playback
{
2015-03-15 19:22:04 +00:00
[Route("/Items/{Id}/MediaInfo", "GET", Summary = "Gets live playback media info for an item")]
2015-03-26 16:58:02 +00:00
public class GetLiveMediaInfo : IReturn < PlaybackInfoResponse >
2014-11-19 02:45:12 +00:00
{
[ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
public string Id { get ; set ; }
[ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")]
public string UserId { get ; set ; }
}
2015-03-07 22:43:53 +00:00
[Route("/Items/{Id}/PlaybackInfo", "GET", Summary = "Gets live playback media info for an item")]
2015-03-26 16:58:02 +00:00
public class GetPlaybackInfo : IReturn < PlaybackInfoResponse >
{
[ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
public string Id { get ; set ; }
[ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")]
public string UserId { get ; set ; }
}
[Route("/Items/{Id}/PlaybackInfo", "POST", Summary = "Gets live playback media info for an item")]
public class GetPostedPlaybackInfo : PlaybackInfoRequest , IReturn < PlaybackInfoResponse >
2015-03-07 22:43:53 +00:00
{
2015-03-28 20:22:27 +00:00
[ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
2015-03-07 22:43:53 +00:00
public string Id { get ; set ; }
2015-03-28 20:22:27 +00:00
[ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")]
2015-03-07 22:43:53 +00:00
public string UserId { get ; set ; }
2015-03-26 19:18:21 +00:00
2015-03-29 04:56:39 +00:00
[ApiMember(Name = "MaxStreamingBitrate", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "POST")]
public int? MaxStreamingBitrate { get ; set ; }
2015-03-28 20:22:27 +00:00
[ApiMember(Name = "StartTimeTicks", Description = "Optional. Specify a starting offset, in ticks. 1 tick = 10000 ms", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "POST")]
2015-03-26 19:18:21 +00:00
public long? StartTimeTicks { get ; set ; }
2015-03-28 20:22:27 +00:00
[ApiMember(Name = "AudioStreamIndex", Description = "Optional. The index of the audio stream to use. If omitted the first audio stream will be used.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "POST")]
2015-03-26 19:18:21 +00:00
public int? AudioStreamIndex { get ; set ; }
2015-03-28 20:22:27 +00:00
[ApiMember(Name = "SubtitleStreamIndex", Description = "Optional. The index of the subtitle stream to use. If omitted no subtitles will be used.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "POST")]
2015-03-26 19:18:21 +00:00
public int? SubtitleStreamIndex { get ; set ; }
2015-03-28 20:22:27 +00:00
[ApiMember(Name = "MediaSourceId", Description = "The media version id, if playing an alternate version", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
public string MediaSourceId { get ; set ; }
2015-03-07 22:43:53 +00:00
}
2015-03-29 16:45:16 +00:00
[Route("/LiveStreams/Open", "POST", Summary = "Opens a media source")]
public class OpenMediaSource : LiveStreamRequest , IReturn < LiveStreamResponse >
2015-03-29 04:56:39 +00:00
{
}
2015-03-29 16:45:16 +00:00
[Route("/LiveStreams/Close", "POST", Summary = "Closes a media source")]
2015-03-29 04:56:39 +00:00
public class CloseMediaSource : IReturnVoid
{
[ApiMember(Name = "LiveStreamId", Description = "LiveStreamId", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
public string LiveStreamId { get ; set ; }
}
2014-11-19 02:45:12 +00:00
[Authenticated]
public class MediaInfoService : BaseApiService
{
2015-03-07 22:43:53 +00:00
private readonly IMediaSourceManager _mediaSourceManager ;
2015-03-26 16:58:02 +00:00
private readonly IDeviceManager _deviceManager ;
private readonly ILibraryManager _libraryManager ;
2014-11-19 02:45:12 +00:00
2015-03-26 16:58:02 +00:00
public MediaInfoService ( IMediaSourceManager mediaSourceManager , IDeviceManager deviceManager , ILibraryManager libraryManager )
2014-11-19 02:45:12 +00:00
{
2015-03-07 22:43:53 +00:00
_mediaSourceManager = mediaSourceManager ;
2015-03-26 16:58:02 +00:00
_deviceManager = deviceManager ;
_libraryManager = libraryManager ;
}
public async Task < object > Get ( GetPlaybackInfo request )
{
var result = await GetPlaybackInfo ( request . Id , request . UserId ) . ConfigureAwait ( false ) ;
return ToOptimizedResult ( result ) ;
2014-11-19 02:45:12 +00:00
}
2015-03-26 16:58:02 +00:00
public async Task < object > Get ( GetLiveMediaInfo request )
2014-11-19 02:45:12 +00:00
{
2015-03-26 16:58:02 +00:00
var result = await GetPlaybackInfo ( request . Id , request . UserId ) . ConfigureAwait ( false ) ;
return ToOptimizedResult ( result ) ;
2015-03-16 16:47:14 +00:00
}
2014-11-19 02:45:12 +00:00
2015-03-29 04:56:39 +00:00
public async Task < object > Post ( OpenMediaSource request )
{
2015-03-29 16:45:16 +00:00
var authInfo = AuthorizationContext . GetAuthorizationInfo ( Request ) ;
var result = await _mediaSourceManager . OpenLiveStream ( request , false , CancellationToken . None ) . ConfigureAwait ( false ) ;
var profile = request . DeviceProfile ;
if ( profile = = null )
{
var caps = _deviceManager . GetCapabilities ( authInfo . DeviceId ) ;
if ( caps ! = null )
{
profile = caps . DeviceProfile ;
}
}
if ( profile ! = null )
{
var item = _libraryManager . GetItemById ( request . ItemId ) ;
2015-03-29 22:38:32 +00:00
SetDeviceSpecificData ( item , result . MediaSource , profile , authInfo , request . MaxStreamingBitrate ,
request . StartTimeTicks ? ? 0 , result . MediaSource . Id , request . AudioStreamIndex ,
request . SubtitleStreamIndex ) ;
2015-03-29 16:45:16 +00:00
}
2015-03-29 22:38:32 +00:00
else
2015-03-29 16:45:16 +00:00
{
2015-03-29 22:38:32 +00:00
if ( ! string . IsNullOrWhiteSpace ( result . MediaSource . TranscodingUrl ) )
{
result . MediaSource . TranscodingUrl + = "&LiveStreamId=" + result . MediaSource . LiveStreamId ;
}
2015-03-29 16:45:16 +00:00
}
2015-03-29 04:56:39 +00:00
return ToOptimizedResult ( result ) ;
}
public void Post ( CloseMediaSource request )
{
var task = _mediaSourceManager . CloseLiveStream ( request . LiveStreamId , CancellationToken . None ) ;
Task . WaitAll ( task ) ;
}
2015-03-26 16:58:02 +00:00
public async Task < object > Post ( GetPostedPlaybackInfo request )
2015-03-16 16:47:14 +00:00
{
2015-03-28 20:22:27 +00:00
var info = await GetPlaybackInfo ( request . Id , request . UserId , request . MediaSourceId ) . ConfigureAwait ( false ) ;
2015-03-26 16:58:02 +00:00
var authInfo = AuthorizationContext . GetAuthorizationInfo ( Request ) ;
var profile = request . DeviceProfile ;
2015-03-26 23:10:34 +00:00
if ( profile = = null )
{
var caps = _deviceManager . GetCapabilities ( authInfo . DeviceId ) ;
if ( caps ! = null )
{
profile = caps . DeviceProfile ;
}
}
2015-03-26 16:58:02 +00:00
if ( profile ! = null )
{
2015-03-28 20:22:27 +00:00
var mediaSourceId = request . MediaSourceId ;
2015-03-29 04:56:39 +00:00
SetDeviceSpecificData ( request . Id , info , profile , authInfo , request . MaxStreamingBitrate , request . StartTimeTicks ? ? 0 , mediaSourceId , request . AudioStreamIndex , request . SubtitleStreamIndex ) ;
2015-03-26 16:58:02 +00:00
}
return ToOptimizedResult ( info ) ;
2015-03-07 22:43:53 +00:00
}
2014-11-26 04:12:29 +00:00
2015-03-28 20:22:27 +00:00
private async Task < PlaybackInfoResponse > GetPlaybackInfo ( string id , string userId , string mediaSourceId = null )
2015-03-07 22:43:53 +00:00
{
2015-03-26 16:58:02 +00:00
var result = new PlaybackInfoResponse ( ) ;
2014-11-19 02:45:12 +00:00
2015-03-28 20:22:27 +00:00
IEnumerable < MediaSourceInfo > mediaSources ;
try
2014-11-19 02:45:12 +00:00
{
2015-03-28 20:22:27 +00:00
mediaSources = await _mediaSourceManager . GetPlayackMediaSources ( id , userId , true , CancellationToken . None ) . ConfigureAwait ( false ) ;
}
catch ( PlaybackException ex )
{
mediaSources = new List < MediaSourceInfo > ( ) ;
result . ErrorCode = ex . ErrorCode ;
}
2015-03-26 19:18:21 +00:00
2015-03-28 20:22:27 +00:00
result . MediaSources = mediaSources . ToList ( ) ;
2015-03-26 19:18:21 +00:00
2015-03-28 20:22:27 +00:00
if ( ! string . IsNullOrWhiteSpace ( mediaSourceId ) )
2015-03-16 16:47:14 +00:00
{
2015-03-28 20:22:27 +00:00
result . MediaSources = result . MediaSources
. Where ( i = > string . Equals ( i . Id , mediaSourceId , StringComparison . OrdinalIgnoreCase ) )
. ToList ( ) ;
2015-03-16 16:47:14 +00:00
}
2015-03-26 16:58:02 +00:00
if ( result . MediaSources . Count = = 0 )
{
if ( ! result . ErrorCode . HasValue )
{
result . ErrorCode = PlaybackErrorCode . NoCompatibleStream ;
}
}
else
{
2015-03-29 18:31:28 +00:00
result . PlaySessionId = Guid . NewGuid ( ) . ToString ( "N" ) ;
2015-03-26 16:58:02 +00:00
}
return result ;
}
2015-03-29 16:45:16 +00:00
private void SetDeviceSpecificData ( string itemId ,
PlaybackInfoResponse result ,
DeviceProfile profile ,
AuthorizationInfo auth ,
int? maxBitrate ,
2015-03-26 19:18:21 +00:00
long startTimeTicks ,
string mediaSourceId ,
int? audioStreamIndex ,
int? subtitleStreamIndex )
2015-03-26 16:58:02 +00:00
{
var item = _libraryManager . GetItemById ( itemId ) ;
foreach ( var mediaSource in result . MediaSources )
{
2015-03-29 04:56:39 +00:00
SetDeviceSpecificData ( item , mediaSource , profile , auth , maxBitrate , startTimeTicks , mediaSourceId , audioStreamIndex , subtitleStreamIndex ) ;
}
2015-03-26 19:18:21 +00:00
2015-03-29 04:56:39 +00:00
SortMediaSources ( result ) ;
}
2015-03-26 16:58:02 +00:00
2015-03-29 04:56:39 +00:00
private void SetDeviceSpecificData ( BaseItem item ,
MediaSourceInfo mediaSource ,
DeviceProfile profile ,
AuthorizationInfo auth ,
int? maxBitrate ,
long startTimeTicks ,
string mediaSourceId ,
int? audioStreamIndex ,
int? subtitleStreamIndex )
{
var streamBuilder = new StreamBuilder ( ) ;
2015-03-26 19:18:21 +00:00
2015-03-29 04:56:39 +00:00
var options = new VideoOptions
{
MediaSources = new List < MediaSourceInfo > { mediaSource } ,
Context = EncodingContext . Streaming ,
DeviceId = auth . DeviceId ,
ItemId = item . Id . ToString ( "N" ) ,
Profile = profile ,
MaxBitrate = maxBitrate
} ;
if ( string . Equals ( mediaSourceId , mediaSource . Id , StringComparison . OrdinalIgnoreCase ) )
{
options . MediaSourceId = mediaSourceId ;
options . AudioStreamIndex = audioStreamIndex ;
options . SubtitleStreamIndex = subtitleStreamIndex ;
}
2015-03-26 16:58:02 +00:00
2015-03-29 04:56:39 +00:00
if ( mediaSource . SupportsDirectPlay )
{
var supportsDirectStream = mediaSource . SupportsDirectStream ;
2015-03-26 16:58:02 +00:00
2015-03-29 04:56:39 +00:00
// Dummy this up to fool StreamBuilder
mediaSource . SupportsDirectStream = true ;
2015-03-26 16:58:02 +00:00
2015-03-29 04:56:39 +00:00
// The MediaSource supports direct stream, now test to see if the client supports it
var streamInfo = string . Equals ( item . MediaType , MediaType . Audio , StringComparison . OrdinalIgnoreCase ) ?
streamBuilder . BuildAudioItem ( options ) :
streamBuilder . BuildVideoItem ( options ) ;
if ( streamInfo = = null | | ! streamInfo . IsDirectStream )
2015-03-26 16:58:02 +00:00
{
2015-03-29 04:56:39 +00:00
mediaSource . SupportsDirectPlay = false ;
2015-03-26 16:58:02 +00:00
}
2015-03-29 04:56:39 +00:00
// Set this back to what it was
mediaSource . SupportsDirectStream = supportsDirectStream ;
2015-03-30 19:57:37 +00:00
if ( streamInfo ! = null )
{
SetDeviceSpecificSubtitleInfo ( streamInfo , mediaSource , auth . Token ) ;
}
2015-03-29 04:56:39 +00:00
}
if ( mediaSource . SupportsDirectStream )
{
// The MediaSource supports direct stream, now test to see if the client supports it
var streamInfo = string . Equals ( item . MediaType , MediaType . Audio , StringComparison . OrdinalIgnoreCase ) ?
streamBuilder . BuildAudioItem ( options ) :
streamBuilder . BuildVideoItem ( options ) ;
if ( streamInfo = = null | | ! streamInfo . IsDirectStream )
2015-03-26 16:58:02 +00:00
{
2015-03-29 04:56:39 +00:00
mediaSource . SupportsDirectStream = false ;
2015-03-26 16:58:02 +00:00
}
2015-03-30 19:57:37 +00:00
if ( streamInfo ! = null )
{
SetDeviceSpecificSubtitleInfo ( streamInfo , mediaSource , auth . Token ) ;
}
2015-03-26 16:58:02 +00:00
}
2015-03-28 20:22:27 +00:00
2015-03-29 04:56:39 +00:00
if ( mediaSource . SupportsTranscoding )
{
// The MediaSource supports direct stream, now test to see if the client supports it
var streamInfo = string . Equals ( item . MediaType , MediaType . Audio , StringComparison . OrdinalIgnoreCase ) ?
streamBuilder . BuildAudioItem ( options ) :
streamBuilder . BuildVideoItem ( options ) ;
if ( streamInfo ! = null & & streamInfo . PlayMethod = = PlayMethod . Transcode )
{
streamInfo . StartPositionTicks = startTimeTicks ;
2015-03-30 19:57:37 +00:00
mediaSource . TranscodingUrl = streamInfo . ToUrl ( "-" , auth . Token ) . TrimStart ( '-' ) ;
2015-03-29 04:56:39 +00:00
mediaSource . TranscodingContainer = streamInfo . Container ;
mediaSource . TranscodingSubProtocol = streamInfo . SubProtocol ;
}
2015-03-30 19:57:37 +00:00
if ( streamInfo ! = null )
{
SetDeviceSpecificSubtitleInfo ( streamInfo , mediaSource , auth . Token ) ;
}
}
}
private void SetDeviceSpecificSubtitleInfo ( StreamInfo info , MediaSourceInfo mediaSource , string accessToken )
{
var profiles = info . GetSubtitleProfiles ( false , "-" , accessToken ) ;
foreach ( var profile in profiles )
{
foreach ( var stream in mediaSource . MediaStreams )
{
if ( stream . Type = = MediaStreamType . Subtitle & & stream . Index = = profile . Index )
{
stream . DeliveryMethod = profile . DeliveryMethod ;
if ( profile . DeliveryMethod = = SubtitleDeliveryMethod . External )
{
stream . DeliveryUrl = profile . Url . TrimStart ( '-' ) ;
}
}
}
2015-03-29 04:56:39 +00:00
}
2015-03-28 20:22:27 +00:00
}
private void SortMediaSources ( PlaybackInfoResponse result )
{
var originalList = result . MediaSources . ToList ( ) ;
result . MediaSources = result . MediaSources . OrderBy ( i = >
{
// Nothing beats direct playing a file
if ( i . SupportsDirectPlay & & i . Protocol = = MediaProtocol . File )
{
return 0 ;
}
return 1 ;
} ) . ThenBy ( i = >
{
// Let's assume direct streaming a file is just as desirable as direct playing a remote url
if ( i . SupportsDirectPlay | | i . SupportsDirectStream )
{
return 0 ;
}
return 1 ;
} ) . ThenBy ( i = >
{
switch ( i . Protocol )
{
case MediaProtocol . File :
return 0 ;
default :
return 1 ;
}
} ) . ThenBy ( originalList . IndexOf )
. ToList ( ) ;
2014-11-19 02:45:12 +00:00
}
}
}