|  | @@ -5,6 +5,7 @@ using MediaBrowser.Controller.Configuration;
 | 
	
		
			
				|  |  |  using MediaBrowser.Controller.Dto;
 | 
	
		
			
				|  |  |  using MediaBrowser.Controller.Entities;
 | 
	
		
			
				|  |  |  using MediaBrowser.Controller.Library;
 | 
	
		
			
				|  |  | +using MediaBrowser.Controller.LiveTv;
 | 
	
		
			
				|  |  |  using MediaBrowser.Controller.MediaInfo;
 | 
	
		
			
				|  |  |  using MediaBrowser.Controller.Persistence;
 | 
	
		
			
				|  |  |  using MediaBrowser.Model.Configuration;
 | 
	
	
		
			
				|  | @@ -62,6 +63,7 @@ namespace MediaBrowser.Api.Playback
 | 
	
		
			
				|  |  |          protected IFileSystem FileSystem { get; private set; }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |          protected IItemRepository ItemRepository { get; private set; }
 | 
	
		
			
				|  |  | +        protected ILiveTvManager LiveTvManager { get; private set; }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |          /// <summary>
 | 
	
		
			
				|  |  |          /// Initializes a new instance of the <see cref="BaseStreamingService" /> class.
 | 
	
	
		
			
				|  | @@ -74,8 +76,9 @@ namespace MediaBrowser.Api.Playback
 | 
	
		
			
				|  |  |          /// <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)
 | 
	
		
			
				|  |  | +        protected BaseStreamingService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IDtoService dtoService, IFileSystem fileSystem, IItemRepository itemRepository, ILiveTvManager liveTvManager)
 | 
	
		
			
				|  |  |          {
 | 
	
		
			
				|  |  | +            LiveTvManager = liveTvManager;
 | 
	
		
			
				|  |  |              ItemRepository = itemRepository;
 | 
	
		
			
				|  |  |              FileSystem = fileSystem;
 | 
	
		
			
				|  |  |              DtoService = dtoService;
 | 
	
	
		
			
				|  | @@ -194,6 +197,10 @@ namespace MediaBrowser.Api.Playback
 | 
	
		
			
				|  |  |              {
 | 
	
		
			
				|  |  |                  args += string.Format("-map 0:{0}", state.VideoStream.Index);
 | 
	
		
			
				|  |  |              }
 | 
	
		
			
				|  |  | +            else if (!state.HasMediaStreams)
 | 
	
		
			
				|  |  | +            {
 | 
	
		
			
				|  |  | +                args += string.Format("-map 0:{0}", 0);
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  |              else
 | 
	
		
			
				|  |  |              {
 | 
	
		
			
				|  |  |                  args += "-map -0:v";
 | 
	
	
		
			
				|  | @@ -203,6 +210,10 @@ namespace MediaBrowser.Api.Playback
 | 
	
		
			
				|  |  |              {
 | 
	
		
			
				|  |  |                  args += string.Format(" -map 0:{0}", state.AudioStream.Index);
 | 
	
		
			
				|  |  |              }
 | 
	
		
			
				|  |  | +            else if (!state.HasMediaStreams)
 | 
	
		
			
				|  |  | +            {
 | 
	
		
			
				|  |  | +                args += string.Format(" -map 0:{0}", 1);
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |              else
 | 
	
		
			
				|  |  |              {
 | 
	
	
		
			
				|  | @@ -336,7 +347,10 @@ namespace MediaBrowser.Api.Playback
 | 
	
		
			
				|  |  |              // If fixed dimensions were supplied
 | 
	
		
			
				|  |  |              if (request.Width.HasValue && request.Height.HasValue)
 | 
	
		
			
				|  |  |              {
 | 
	
		
			
				|  |  | -                return string.Format(" -vf \"scale=trunc({0}/2)*2:trunc({1}/2)*2{2}\"", request.Width.Value, request.Height.Value, assSubtitleParam);
 | 
	
		
			
				|  |  | +                var widthParam = request.Width.Value.ToString(UsCulture);
 | 
	
		
			
				|  |  | +                var heightParam = request.Height.Value.ToString(UsCulture);
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +                return string.Format(" -vf \"scale=trunc({0}/2)*2:trunc({1}/2)*2{2}\"", widthParam, heightParam, assSubtitleParam);
 | 
	
		
			
				|  |  |              }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |              var isH264Output = outputVideoCodec.Equals("libx264", StringComparison.OrdinalIgnoreCase);
 | 
	
	
		
			
				|  | @@ -344,33 +358,41 @@ namespace MediaBrowser.Api.Playback
 | 
	
		
			
				|  |  |              // If a fixed width was requested
 | 
	
		
			
				|  |  |              if (request.Width.HasValue)
 | 
	
		
			
				|  |  |              {
 | 
	
		
			
				|  |  | +                var widthParam = request.Width.Value.ToString(UsCulture);
 | 
	
		
			
				|  |  | +                
 | 
	
		
			
				|  |  |                  return isH264Output ?
 | 
	
		
			
				|  |  | -                    string.Format(" -vf \"scale={0}:trunc(ow/a/2)*2{1}\"", request.Width.Value, assSubtitleParam) :
 | 
	
		
			
				|  |  | -                    string.Format(" -vf \"scale={0}:-1{1}\"", request.Width.Value, assSubtitleParam);
 | 
	
		
			
				|  |  | +                    string.Format(" -vf \"scale={0}:trunc(ow/a/2)*2{1}\"", widthParam, assSubtitleParam) :
 | 
	
		
			
				|  |  | +                    string.Format(" -vf \"scale={0}:-1{1}\"", widthParam, assSubtitleParam);
 | 
	
		
			
				|  |  |              }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |              // If a fixed height was requested
 | 
	
		
			
				|  |  |              if (request.Height.HasValue)
 | 
	
		
			
				|  |  |              {
 | 
	
		
			
				|  |  | +                var heightParam = request.Height.Value.ToString(UsCulture);
 | 
	
		
			
				|  |  | +                
 | 
	
		
			
				|  |  |                  return isH264Output ?
 | 
	
		
			
				|  |  | -                    string.Format(" -vf \"scale=trunc(oh*a*2)/2:{0}{1}\"", request.Height.Value, assSubtitleParam) :
 | 
	
		
			
				|  |  | -                    string.Format(" -vf \"scale=-1:{0}{1}\"", request.Height.Value, assSubtitleParam);
 | 
	
		
			
				|  |  | +                    string.Format(" -vf \"scale=trunc(oh*a*2)/2:{0}{1}\"", heightParam, assSubtitleParam) :
 | 
	
		
			
				|  |  | +                    string.Format(" -vf \"scale=-1:{0}{1}\"", heightParam, assSubtitleParam);
 | 
	
		
			
				|  |  |              }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |              // If a max width was requested
 | 
	
		
			
				|  |  |              if (request.MaxWidth.HasValue && (!request.MaxHeight.HasValue || state.VideoStream == null))
 | 
	
		
			
				|  |  |              {
 | 
	
		
			
				|  |  | +                var maxWidthParam = request.MaxWidth.Value.ToString(UsCulture);
 | 
	
		
			
				|  |  | +                
 | 
	
		
			
				|  |  |                  return isH264Output ?
 | 
	
		
			
				|  |  | -                    string.Format(" -vf \"scale=min(iw\\,{0}):trunc(ow/a/2)*2{1}\"", request.MaxWidth.Value, assSubtitleParam) :
 | 
	
		
			
				|  |  | -                    string.Format(" -vf \"scale=min(iw\\,{0}):-1{1}\"", request.MaxWidth.Value, assSubtitleParam);
 | 
	
		
			
				|  |  | +                    string.Format(" -vf \"scale=min(iw\\,{0}):trunc(ow/a/2)*2{1}\"", maxWidthParam, assSubtitleParam) :
 | 
	
		
			
				|  |  | +                    string.Format(" -vf \"scale=min(iw\\,{0}):-1{1}\"", maxWidthParam, assSubtitleParam);
 | 
	
		
			
				|  |  |              }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |              // If a max height was requested
 | 
	
		
			
				|  |  |              if (request.MaxHeight.HasValue && (!request.MaxWidth.HasValue || state.VideoStream == null))
 | 
	
		
			
				|  |  |              {
 | 
	
		
			
				|  |  | +                var maxHeightParam = request.MaxHeight.Value.ToString(UsCulture);
 | 
	
		
			
				|  |  | +                
 | 
	
		
			
				|  |  |                  return isH264Output ?
 | 
	
		
			
				|  |  | -                    string.Format(" -vf \"scale=trunc(oh*a*2)/2:min(ih\\,{0}){1}\"", request.MaxHeight.Value, assSubtitleParam) :
 | 
	
		
			
				|  |  | -                    string.Format(" -vf \"scale=-1:min(ih\\,{0}){1}\"", request.MaxHeight.Value, assSubtitleParam);
 | 
	
		
			
				|  |  | +                    string.Format(" -vf \"scale=trunc(oh*a*2)/2:min(ih\\,{0}){1}\"", maxHeightParam, assSubtitleParam) :
 | 
	
		
			
				|  |  | +                    string.Format(" -vf \"scale=-1:min(ih\\,{0}){1}\"", maxHeightParam, assSubtitleParam);
 | 
	
		
			
				|  |  |              }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |              if (state.VideoStream == null)
 | 
	
	
		
			
				|  | @@ -390,7 +412,10 @@ namespace MediaBrowser.Api.Playback
 | 
	
		
			
				|  |  |              // If we're encoding with libx264, it can't handle odd numbered widths or heights, so we'll have to fix that
 | 
	
		
			
				|  |  |              if (isH264Output)
 | 
	
		
			
				|  |  |              {
 | 
	
		
			
				|  |  | -                return string.Format(" -vf \"scale=trunc({0}/2)*2:trunc({1}/2)*2{2}\"", outputSize.Width, outputSize.Height, assSubtitleParam);
 | 
	
		
			
				|  |  | +                var widthParam = outputSize.Width.ToString(UsCulture);
 | 
	
		
			
				|  |  | +                var heightParam = outputSize.Height.ToString(UsCulture);
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +                return string.Format(" -vf \"scale=trunc({0}/2)*2:trunc({1}/2)*2{2}\"", widthParam, heightParam, assSubtitleParam);
 | 
	
		
			
				|  |  |              }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |              // Otherwise use -vf scale since ffmpeg will ensure internally that the aspect ratio is preserved
 | 
	
	
		
			
				|  | @@ -823,11 +848,10 @@ namespace MediaBrowser.Api.Playback
 | 
	
		
			
				|  |  |          /// Gets the state.
 | 
	
		
			
				|  |  |          /// </summary>
 | 
	
		
			
				|  |  |          /// <param name="request">The request.</param>
 | 
	
		
			
				|  |  | +        /// <param name="cancellationToken">The cancellation token.</param>
 | 
	
		
			
				|  |  |          /// <returns>StreamState.</returns>
 | 
	
		
			
				|  |  | -        protected StreamState GetState(StreamRequest request)
 | 
	
		
			
				|  |  | +        protected async Task<StreamState> GetState(StreamRequest request, CancellationToken cancellationToken)
 | 
	
		
			
				|  |  |          {
 | 
	
		
			
				|  |  | -            var item = DtoService.GetItemByDtoId(request.Id);
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  |              var url = Request.PathInfo;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |              if (!request.AudioCodec.HasValue)
 | 
	
	
		
			
				|  | @@ -838,22 +862,48 @@ namespace MediaBrowser.Api.Playback
 | 
	
		
			
				|  |  |              var state = new StreamState
 | 
	
		
			
				|  |  |              {
 | 
	
		
			
				|  |  |                  Request = request,
 | 
	
		
			
				|  |  | -                RequestedUrl = url,
 | 
	
		
			
				|  |  | -                MediaPath = item.Path,
 | 
	
		
			
				|  |  | -                IsRemote = item.LocationType == LocationType.Remote
 | 
	
		
			
				|  |  | +                RequestedUrl = url
 | 
	
		
			
				|  |  |              };
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -            var video = item as Video;
 | 
	
		
			
				|  |  | +            BaseItem item;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +            if (string.Equals(request.Type, "Recording", StringComparison.OrdinalIgnoreCase))
 | 
	
		
			
				|  |  | +            {
 | 
	
		
			
				|  |  | +                var recording = await LiveTvManager.GetInternalRecording(request.Id, cancellationToken).ConfigureAwait(false);
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +                state.VideoType = VideoType.VideoFile;
 | 
	
		
			
				|  |  | +                state.IsInputVideo = string.Equals(recording.MediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase);
 | 
	
		
			
				|  |  | +                state.PlayableStreamFileNames = new List<string>();
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +                if (!string.IsNullOrEmpty(recording.RecordingInfo.Path) && File.Exists(recording.RecordingInfo.Path))
 | 
	
		
			
				|  |  | +                {
 | 
	
		
			
				|  |  | +                    state.MediaPath = recording.RecordingInfo.Path;
 | 
	
		
			
				|  |  | +                    state.IsRemote = false;
 | 
	
		
			
				|  |  | +                }
 | 
	
		
			
				|  |  | +                else if (!string.IsNullOrEmpty(recording.RecordingInfo.Url))
 | 
	
		
			
				|  |  | +                {
 | 
	
		
			
				|  |  | +                    state.MediaPath = recording.RecordingInfo.Url;
 | 
	
		
			
				|  |  | +                    state.IsRemote = true;
 | 
	
		
			
				|  |  | +                }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -            if (video != null)
 | 
	
		
			
				|  |  | +                item = recording;
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  | +            else
 | 
	
		
			
				|  |  |              {
 | 
	
		
			
				|  |  | -                state.IsInputVideo = true;
 | 
	
		
			
				|  |  | -                state.VideoType = video.VideoType;
 | 
	
		
			
				|  |  | -                state.IsoType = video.IsoType;
 | 
	
		
			
				|  |  | +                item = DtoService.GetItemByDtoId(request.Id);
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -                state.PlayableStreamFileNames = video.PlayableStreamFileNames == null
 | 
	
		
			
				|  |  | -                    ? new List<string>()
 | 
	
		
			
				|  |  | -                    : video.PlayableStreamFileNames.ToList();
 | 
	
		
			
				|  |  | +                var video = item as Video;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +                if (video != null)
 | 
	
		
			
				|  |  | +                {
 | 
	
		
			
				|  |  | +                    state.IsInputVideo = true;
 | 
	
		
			
				|  |  | +                    state.VideoType = video.VideoType;
 | 
	
		
			
				|  |  | +                    state.IsoType = video.IsoType;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +                    state.PlayableStreamFileNames = video.PlayableStreamFileNames == null
 | 
	
		
			
				|  |  | +                        ? new List<string>()
 | 
	
		
			
				|  |  | +                        : video.PlayableStreamFileNames.ToList();
 | 
	
		
			
				|  |  | +                }
 | 
	
		
			
				|  |  |              }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |              var videoRequest = request as VideoStreamRequest;
 | 
	
	
		
			
				|  | @@ -880,6 +930,8 @@ namespace MediaBrowser.Api.Playback
 | 
	
		
			
				|  |  |                  state.AudioStream = GetMediaStream(mediaStreams, null, MediaStreamType.Audio, true);
 | 
	
		
			
				|  |  |              }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +            state.HasMediaStreams = mediaStreams.Count > 0;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |              return state;
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  |  
 |