Browse Source

Merging in latest dev

T. Adams 10 years ago
parent
commit
abf12569ba
100 changed files with 1914 additions and 918 deletions
  1. 58 20
      MediaBrowser.Api/ApiEntryPoint.cs
  2. 1 1
      MediaBrowser.Api/ConfigurationService.cs
  3. 9 1
      MediaBrowser.Api/LiveTv/LiveTvService.cs
  4. 91 185
      MediaBrowser.Api/Playback/BaseStreamingService.cs
  5. 5 17
      MediaBrowser.Api/Playback/Dash/MpegDashService.cs
  6. 18 5
      MediaBrowser.Api/Playback/Hls/BaseHlsService.cs
  7. 10 10
      MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs
  8. 3 3
      MediaBrowser.Api/Playback/Hls/HlsSegmentService.cs
  9. 1 1
      MediaBrowser.Api/Playback/Hls/VideoHlsService.cs
  10. 316 19
      MediaBrowser.Api/Playback/MediaInfoService.cs
  11. 3 3
      MediaBrowser.Api/Playback/Progressive/AudioService.cs
  12. 1 1
      MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs
  13. 3 3
      MediaBrowser.Api/Playback/Progressive/VideoService.cs
  14. 2 3
      MediaBrowser.Api/Playback/StreamRequest.cs
  15. 45 14
      MediaBrowser.Api/Playback/StreamState.cs
  16. 18 8
      MediaBrowser.Api/Playback/TranscodingThrottler.cs
  17. 1 1
      MediaBrowser.Api/Subtitles/SubtitleService.cs
  18. 1 2
      MediaBrowser.Api/UserLibrary/BaseItemsByNameService.cs
  19. 10 0
      MediaBrowser.Api/UserLibrary/PlaystateService.cs
  20. 8 0
      MediaBrowser.Api/UserLibrary/UserLibraryService.cs
  21. 21 11
      MediaBrowser.Common.Implementations/HttpClientManager/HttpClientManager.cs
  22. 20 17
      MediaBrowser.Common.Implementations/ScheduledTasks/TaskManager.cs
  23. 2 13
      MediaBrowser.Controller/Channels/Channel.cs
  24. 11 5
      MediaBrowser.Controller/Channels/ChannelAudioItem.cs
  25. 5 1
      MediaBrowser.Controller/Channels/ChannelMediaInfo.cs
  26. 11 5
      MediaBrowser.Controller/Channels/ChannelVideoItem.cs
  27. 3 3
      MediaBrowser.Controller/Channels/IChannelManager.cs
  28. 0 29
      MediaBrowser.Controller/Entities/BaseItem.cs
  29. 2 15
      MediaBrowser.Controller/Entities/Folder.cs
  30. 0 3
      MediaBrowser.Controller/Entities/LinkedChild.cs
  31. 2 1
      MediaBrowser.Controller/Entities/Video.cs
  32. 36 2
      MediaBrowser.Controller/Library/IMediaSourceManager.cs
  33. 16 0
      MediaBrowser.Controller/Library/IMediaSourceProvider.cs
  34. 3 1
      MediaBrowser.Controller/LiveTv/ILiveTvItem.cs
  35. 16 0
      MediaBrowser.Controller/LiveTv/ILiveTvManager.cs
  36. 19 2
      MediaBrowser.Controller/LiveTv/LiveTvAudioRecording.cs
  37. 10 1
      MediaBrowser.Controller/LiveTv/LiveTvChannel.cs
  38. 6 0
      MediaBrowser.Controller/LiveTv/LiveTvProgram.cs
  39. 20 3
      MediaBrowser.Controller/LiveTv/LiveTvVideoRecording.cs
  40. 2 1
      MediaBrowser.Controller/MediaBrowser.Controller.csproj
  41. 5 3
      MediaBrowser.Controller/MediaEncoding/ISubtitleEncoder.cs
  42. 73 4
      MediaBrowser.Controller/MediaEncoding/MediaStreamSelector.cs
  43. 3 4
      MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs
  44. 1 13
      MediaBrowser.Controller/Providers/BaseItemXmlParser.cs
  45. 18 0
      MediaBrowser.Controller/Sync/IHasDynamicAccess.cs
  46. 1 10
      MediaBrowser.Controller/Sync/IServerSyncProvider.cs
  47. 16 0
      MediaBrowser.Controller/Sync/ISyncDataProvider.cs
  48. 12 1
      MediaBrowser.Controller/Sync/SyncedFileInfo.cs
  49. 13 6
      MediaBrowser.Dlna/Didl/DidlBuilder.cs
  50. 6 4
      MediaBrowser.Dlna/PlayTo/PlayToController.cs
  51. 1 1
      MediaBrowser.LocalMetadata/BaseXmlProvider.cs
  52. 5 6
      MediaBrowser.LocalMetadata/Images/LocalImageProvider.cs
  53. 0 5
      MediaBrowser.LocalMetadata/Savers/XmlSaverHelpers.cs
  54. 1 1
      MediaBrowser.MediaEncoding/Encoder/AudioEncoder.cs
  55. 18 65
      MediaBrowser.MediaEncoding/Encoder/BaseEncoder.cs
  56. 45 9
      MediaBrowser.MediaEncoding/Encoder/EncodingJob.cs
  57. 69 112
      MediaBrowser.MediaEncoding/Encoder/EncodingJobFactory.cs
  58. 0 4
      MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs
  59. 7 4
      MediaBrowser.MediaEncoding/Encoder/VideoEncoder.cs
  60. 71 38
      MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs
  61. 14 2
      MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj
  62. 14 2
      MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj
  63. 9 3
      MediaBrowser.Model/ApiClient/IApiClient.cs
  64. 6 0
      MediaBrowser.Model/ApiClient/IConnectionManager.cs
  65. 16 5
      MediaBrowser.Model/ApiClient/ServerCredentials.cs
  66. 6 2
      MediaBrowser.Model/ApiClient/ServerInfo.cs
  67. 4 0
      MediaBrowser.Model/Configuration/EncodingOptions.cs
  68. 2 3
      MediaBrowser.Model/Configuration/ServerConfiguration.cs
  69. 2 7
      MediaBrowser.Model/Configuration/UserConfiguration.cs
  70. 11 2
      MediaBrowser.Model/Dlna/ConditionProcessor.cs
  71. 6 2
      MediaBrowser.Model/Dlna/ContentFeatureBuilder.cs
  72. 4 2
      MediaBrowser.Model/Dlna/DeviceProfile.cs
  73. 4 1
      MediaBrowser.Model/Dlna/ProfileConditionValue.cs
  74. 99 35
      MediaBrowser.Model/Dlna/StreamBuilder.cs
  75. 204 109
      MediaBrowser.Model/Dlna/StreamInfo.cs
  76. 47 0
      MediaBrowser.Model/Dlna/StreamInfoSorter.cs
  77. 26 1
      MediaBrowser.Model/Dlna/SubtitleProfile.cs
  78. 2 0
      MediaBrowser.Model/Dlna/SubtitleStreamInfo.cs
  79. 47 0
      MediaBrowser.Model/Dto/MediaSourceInfo.cs
  80. 1 1
      MediaBrowser.Model/Dto/MediaSourceType.cs
  81. 11 0
      MediaBrowser.Model/Dto/NameValuePair.cs
  82. 35 1
      MediaBrowser.Model/Entities/MediaStream.cs
  83. 6 0
      MediaBrowser.Model/LiveTv/ProgramQuery.cs
  84. 5 0
      MediaBrowser.Model/LiveTv/RecommendedProgramQuery.cs
  85. 5 1
      MediaBrowser.Model/MediaBrowser.Model.csproj
  86. 36 0
      MediaBrowser.Model/MediaInfo/LiveStreamRequest.cs
  87. 9 0
      MediaBrowser.Model/MediaInfo/LiveStreamResponse.cs
  88. 25 0
      MediaBrowser.Model/MediaInfo/PlaybackInfoRequest.cs
  89. 5 5
      MediaBrowser.Model/MediaInfo/PlaybackInfoResponse.cs
  90. 3 2
      MediaBrowser.Model/Notifications/NotificationOptions.cs
  91. 10 0
      MediaBrowser.Model/Querying/ItemQuery.cs
  92. 1 0
      MediaBrowser.Model/Querying/ItemSortBy.cs
  93. 10 0
      MediaBrowser.Model/Session/PlaybackProgressInfo.cs
  94. 10 0
      MediaBrowser.Model/Session/PlaybackStopInfo.cs
  95. 11 0
      MediaBrowser.Model/Sync/LocalItem.cs
  96. 1 0
      MediaBrowser.Model/Sync/SyncDataRequest.cs
  97. 1 2
      MediaBrowser.Model/Users/UserPolicy.cs
  98. 1 1
      MediaBrowser.Providers/MediaInfo/AudioImageProvider.cs
  99. 5 4
      MediaBrowser.Providers/MediaInfo/SubtitleScheduledTask.cs
  100. 26 25
      MediaBrowser.Server.Implementations/Channels/ChannelDownloadScheduledTask.cs

+ 58 - 20
MediaBrowser.Api/ApiEntryPoint.cs

@@ -132,7 +132,7 @@ namespace MediaBrowser.Api
         /// Called when [transcode beginning].
         /// </summary>
         /// <param name="path">The path.</param>
-        /// <param name="streamId">The stream identifier.</param>
+        /// <param name="playSessionId">The play session identifier.</param>
         /// <param name="transcodingJobId">The transcoding job identifier.</param>
         /// <param name="type">The type.</param>
         /// <param name="process">The process.</param>
@@ -141,7 +141,7 @@ namespace MediaBrowser.Api
         /// <param name="cancellationTokenSource">The cancellation token source.</param>
         /// <returns>TranscodingJob.</returns>
         public TranscodingJob OnTranscodeBeginning(string path,
-            string streamId,
+            string playSessionId,
             string transcodingJobId,
             TranscodingJobType type,
             Process process,
@@ -160,7 +160,7 @@ namespace MediaBrowser.Api
                     DeviceId = deviceId,
                     CancellationTokenSource = cancellationTokenSource,
                     Id = transcodingJobId,
-                    StreamId = streamId
+                    PlaySessionId = playSessionId
                 };
 
                 _activeTranscodingJobs.Add(job);
@@ -187,7 +187,7 @@ namespace MediaBrowser.Api
 
             if (!string.IsNullOrWhiteSpace(deviceId))
             {
-                var audioCodec = state.ActualOutputVideoCodec;
+                var audioCodec = state.ActualOutputAudioCodec;
                 var videoCodec = state.ActualOutputVideoCodec;
 
                 _sessionManager.ReportTranscodingInfo(deviceId, new TranscodingInfo
@@ -286,27 +286,65 @@ namespace MediaBrowser.Api
 
             job.DisposeKillTimer();
         }
-        
+
         public void OnTranscodeEndRequest(TranscodingJob job)
         {
             job.ActiveRequestCount--;
 
             if (job.ActiveRequestCount == 0)
             {
-                // TODO: Lower this hls timeout
-                var timerDuration = job.Type == TranscodingJobType.Progressive ?
-                    1000 :
-                    14400000;
+                PingTimer(job, true);
+            }
+        }
+        internal void PingTranscodingJob(string deviceId, string playSessionId)
+        {
+            if (string.IsNullOrEmpty(deviceId))
+            {
+                throw new ArgumentNullException("deviceId");
+            }
 
-                if (job.KillTimer == null)
+            var jobs = new List<TranscodingJob>();
+
+            lock (_activeTranscodingJobs)
+            {
+                // This is really only needed for HLS. 
+                // Progressive streams can stop on their own reliably
+                jobs = jobs.Where(j =>
                 {
-                    job.KillTimer = new Timer(OnTranscodeKillTimerStopped, job, timerDuration, Timeout.Infinite);
-                }
-                else
+                    if (string.Equals(deviceId, j.DeviceId, StringComparison.OrdinalIgnoreCase))
+                    {
+                        return string.Equals(playSessionId, j.PlaySessionId, StringComparison.OrdinalIgnoreCase);
+                    }
+
+                    return false;
+
+                }).ToList();
+            }
+
+            foreach (var job in jobs)
+            {
+                PingTimer(job, false);
+            }
+        }
+
+        private void PingTimer(TranscodingJob job, bool startTimerIfNeeded)
+        {
+            // TODO: Lower this hls timeout
+            var timerDuration = job.Type == TranscodingJobType.Progressive ?
+                1000 :
+                1800000;
+
+            if (job.KillTimer == null)
+            {
+                if (startTimerIfNeeded)
                 {
-                    job.KillTimer.Change(timerDuration, Timeout.Infinite);
+                    job.KillTimer = new Timer(OnTranscodeKillTimerStopped, job, timerDuration, Timeout.Infinite);
                 }
             }
+            else
+            {
+                job.KillTimer.Change(timerDuration, Timeout.Infinite);
+            }
         }
 
         /// <summary>
@@ -324,10 +362,10 @@ namespace MediaBrowser.Api
         /// Kills the single transcoding job.
         /// </summary>
         /// <param name="deviceId">The device id.</param>
-        /// <param name="streamId">The stream identifier.</param>
+        /// <param name="playSessionId">The play session identifier.</param>
         /// <param name="deleteFiles">The delete files.</param>
         /// <returns>Task.</returns>
-        internal void KillTranscodingJobs(string deviceId, string streamId, Func<string, bool> deleteFiles)
+        internal void KillTranscodingJobs(string deviceId, string playSessionId, Func<string, bool> deleteFiles)
         {
             if (string.IsNullOrEmpty(deviceId))
             {
@@ -338,7 +376,7 @@ namespace MediaBrowser.Api
             {
                 if (string.Equals(deviceId, j.DeviceId, StringComparison.OrdinalIgnoreCase))
                 {
-                    return string.IsNullOrWhiteSpace(streamId) || string.Equals(streamId, j.StreamId, StringComparison.OrdinalIgnoreCase);
+                    return string.IsNullOrWhiteSpace(playSessionId) || string.Equals(playSessionId, j.PlaySessionId, StringComparison.OrdinalIgnoreCase);
                 }
 
                 return false;
@@ -539,10 +577,10 @@ namespace MediaBrowser.Api
     public class TranscodingJob
     {
         /// <summary>
-        /// Gets or sets the stream identifier.
+        /// Gets or sets the play session identifier.
         /// </summary>
-        /// <value>The stream identifier.</value>
-        public string StreamId { get; set; }
+        /// <value>The play session identifier.</value>
+        public string PlaySessionId { get; set; }
         /// <summary>
         /// Gets or sets the path.
         /// </summary>

+ 1 - 1
MediaBrowser.Api/ConfigurationService.cs

@@ -123,7 +123,7 @@ namespace MediaBrowser.Api
 
         public void Post(AutoSetMetadataOptions request)
         {
-            _configurationManager.DisableMetadataService("Media Browser Xml");
+            _configurationManager.DisableMetadataService("Emby Xml");
             _configurationManager.SaveConfiguration();
         }
 

+ 9 - 1
MediaBrowser.Api/LiveTv/LiveTvService.cs

@@ -186,6 +186,9 @@ namespace MediaBrowser.Api.LiveTv
         [ApiMember(Name = "IsMovie", Description = "Optional filter for movies.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET,POST")]
         public bool? IsMovie { get; set; }
 
+        [ApiMember(Name = "IsSports", Description = "Optional filter for sports.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET,POST")]
+        public bool? IsSports { get; set; }
+
         [ApiMember(Name = "StartIndex", Description = "Optional. The record index to start at. All items with a lower index will be dropped from the results.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
         public int? StartIndex { get; set; }
 
@@ -218,6 +221,9 @@ namespace MediaBrowser.Api.LiveTv
         [ApiMember(Name = "HasAired", Description = "Optional. Filter by programs that have completed airing, or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
         public bool? HasAired { get; set; }
 
+        [ApiMember(Name = "IsSports", Description = "Optional filter for sports.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET,POST")]
+        public bool? IsSports { get; set; }
+
         [ApiMember(Name = "IsMovie", Description = "Optional filter for movies.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
         public bool? IsMovie { get; set; }
     }
@@ -422,6 +428,7 @@ namespace MediaBrowser.Api.LiveTv
             query.SortBy = (request.SortBy ?? String.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
             query.SortOrder = request.SortOrder;
             query.IsMovie = request.IsMovie;
+            query.IsSports = request.IsSports;
             query.Genres = (request.Genres ?? String.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
 
             var result = await _liveTvManager.GetPrograms(query, CancellationToken.None).ConfigureAwait(false);
@@ -437,7 +444,8 @@ namespace MediaBrowser.Api.LiveTv
                 IsAiring = request.IsAiring,
                 Limit = request.Limit,
                 HasAired = request.HasAired,
-                IsMovie = request.IsMovie
+                IsMovie = request.IsMovie,
+                IsSports = request.IsSports
             };
 
             var result = await _liveTvManager.GetRecommendedPrograms(query, CancellationToken.None).ConfigureAwait(false);

+ 91 - 185
MediaBrowser.Api/Playback/BaseStreamingService.cs

@@ -1,12 +1,10 @@
 using MediaBrowser.Common.Extensions;
 using MediaBrowser.Common.IO;
-using MediaBrowser.Controller.Channels;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Devices;
 using MediaBrowser.Controller.Dlna;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Library;
-using MediaBrowser.Controller.LiveTv;
 using MediaBrowser.Controller.MediaEncoding;
 using MediaBrowser.Model.Configuration;
 using MediaBrowser.Model.Dlna;
@@ -65,7 +63,6 @@ namespace MediaBrowser.Api.Playback
 
         protected IFileSystem FileSystem { get; private set; }
 
-        protected ILiveTvManager LiveTvManager { get; private set; }
         protected IDlnaManager DlnaManager { get; private set; }
         protected IDeviceManager DeviceManager { get; private set; }
         protected ISubtitleEncoder SubtitleEncoder { get; private set; }
@@ -75,14 +72,13 @@ namespace MediaBrowser.Api.Playback
         /// <summary>
         /// Initializes a new instance of the <see cref="BaseStreamingService" /> class.
         /// </summary>
-        protected BaseStreamingService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, ILiveTvManager liveTvManager, IDlnaManager dlnaManager, ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager, IMediaSourceManager mediaSourceManager, IZipClient zipClient)
+        protected BaseStreamingService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, IDlnaManager dlnaManager, ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager, IMediaSourceManager mediaSourceManager, IZipClient zipClient)
         {
             ZipClient = zipClient;
             MediaSourceManager = mediaSourceManager;
             DeviceManager = deviceManager;
             SubtitleEncoder = subtitleEncoder;
             DlnaManager = dlnaManager;
-            LiveTvManager = liveTvManager;
             FileSystem = fileSystem;
             ServerConfigurationManager = serverConfig;
             UserManager = userManager;
@@ -95,11 +91,10 @@ namespace MediaBrowser.Api.Playback
         /// Gets the command line arguments.
         /// </summary>
         /// <param name="outputPath">The output path.</param>
-        /// <param name="transcodingJobId">The transcoding job identifier.</param>
         /// <param name="state">The state.</param>
         /// <param name="isEncoding">if set to <c>true</c> [is encoding].</param>
         /// <returns>System.String.</returns>
-        protected abstract string GetCommandLineArguments(string outputPath, string transcodingJobId, StreamState state, bool isEncoding);
+        protected abstract string GetCommandLineArguments(string outputPath, StreamState state, bool isEncoding);
 
         /// <summary>
         /// Gets the type of the transcoding job.
@@ -128,10 +123,10 @@ namespace MediaBrowser.Api.Playback
 
             var outputFileExtension = GetOutputFileExtension(state);
 
-            var data = GetCommandLineArguments("dummy\\dummy", "dummyTranscodingId", state, false);
+            var data = GetCommandLineArguments("dummy\\dummy", state, false);
 
             data += "-" + (state.Request.DeviceId ?? string.Empty);
-            data += "-" + (state.Request.StreamId ?? string.Empty);
+            data += "-" + (state.Request.PlaySessionId ?? string.Empty);
             data += "-" + (state.Request.ClientTime ?? string.Empty);
 
             var dataHash = data.GetMD5().ToString("N");
@@ -704,7 +699,7 @@ namespace MediaBrowser.Api.Playback
 
                 if (!string.IsNullOrEmpty(state.SubtitleStream.Language))
                 {
-                    var charenc = SubtitleEncoder.GetSubtitleFileCharacterSet(subtitlePath);
+                    var charenc = SubtitleEncoder.GetSubtitleFileCharacterSet(subtitlePath, state.MediaSource.Protocol, CancellationToken.None).Result;
 
                     if (!string.IsNullOrEmpty(charenc))
                     {
@@ -719,8 +714,10 @@ namespace MediaBrowser.Api.Playback
                     seconds.ToString(UsCulture));
             }
 
+            var mediaPath = state.MediaPath ?? string.Empty;
+
             return string.Format("subtitles='{0}:si={1}',setpts=PTS -{2}/TB",
-                state.MediaPath.Replace('\\', '/').Replace(":/", "\\:/"),
+                mediaPath.Replace('\\', '/').Replace(":/", "\\:/"),
                 state.InternalSubtitleStreamOffset.ToString(UsCulture),
                 seconds.ToString(UsCulture));
         }
@@ -895,12 +892,11 @@ namespace MediaBrowser.Api.Playback
         /// <summary>
         /// Gets the input argument.
         /// </summary>
-        /// <param name="transcodingJobId">The transcoding job identifier.</param>
         /// <param name="state">The state.</param>
         /// <returns>System.String.</returns>
-        protected string GetInputArgument(string transcodingJobId, StreamState state)
+        protected string GetInputArgument(StreamState state)
         {
-            var arg = "-i " + GetInputPathArgument(transcodingJobId, state);
+            var arg = "-i " + GetInputPathArgument(state);
 
             if (state.SubtitleStream != null)
             {
@@ -913,27 +909,18 @@ namespace MediaBrowser.Api.Playback
             return arg;
         }
 
-        private string GetInputPathArgument(string transcodingJobId, StreamState state)
+        private string GetInputPathArgument(StreamState state)
         {
-            //if (state.InputProtocol == MediaProtocol.File &&
-            //   state.RunTimeTicks.HasValue &&
-            //   state.VideoType == VideoType.VideoFile &&
-            //   !string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase))
-            //{
-            //    if (state.RunTimeTicks.Value >= TimeSpan.FromMinutes(5).Ticks && state.IsInputVideo)
-            //    {
-            //    }
-            //}
-
             var protocol = state.InputProtocol;
+            var mediaPath = state.MediaPath ?? string.Empty;
 
-            var inputPath = new[] { state.MediaPath };
+            var inputPath = new[] { mediaPath };
 
             if (state.IsInputVideo)
             {
                 if (!(state.VideoType == VideoType.Iso && state.IsoMount == null))
                 {
-                    inputPath = MediaEncoderHelpers.GetInputArgument(state.MediaPath, state.InputProtocol, state.IsoMount, state.PlayableStreamFileNames);
+                    inputPath = MediaEncoderHelpers.GetInputArgument(mediaPath, state.InputProtocol, state.IsoMount, state.PlayableStreamFileNames);
                 }
             }
 
@@ -947,55 +934,25 @@ namespace MediaBrowser.Api.Playback
                 state.IsoMount = await IsoManager.Mount(state.MediaPath, cancellationTokenSource.Token).ConfigureAwait(false);
             }
 
-            if (string.IsNullOrEmpty(state.MediaPath))
+            if (state.MediaSource.RequiresOpening)
             {
-                var checkCodecs = false;
-
-                if (string.Equals(state.ItemType, typeof(LiveTvChannel).Name))
+                var liveStreamResponse = await MediaSourceManager.OpenLiveStream(new LiveStreamRequest
                 {
-                    var streamInfo = await LiveTvManager.GetChannelStream(state.Request.Id, cancellationTokenSource.Token).ConfigureAwait(false);
-
-                    state.LiveTvStreamId = streamInfo.Id;
+                    OpenToken = state.MediaSource.OpenToken
 
-                    state.MediaPath = streamInfo.Path;
-                    state.InputProtocol = streamInfo.Protocol;
+                }, false, cancellationTokenSource.Token).ConfigureAwait(false);
 
-                    await Task.Delay(1500, cancellationTokenSource.Token).ConfigureAwait(false);
-
-                    AttachMediaStreamInfo(state, streamInfo, state.VideoRequest, state.RequestedUrl);
-                    checkCodecs = true;
-                }
+                AttachMediaSourceInfo(state, liveStreamResponse.MediaSource, state.VideoRequest, state.RequestedUrl);
 
-                else if (string.Equals(state.ItemType, typeof(LiveTvVideoRecording).Name) ||
-                    string.Equals(state.ItemType, typeof(LiveTvAudioRecording).Name))
+                if (state.VideoRequest != null)
                 {
-                    var streamInfo = await LiveTvManager.GetRecordingStream(state.Request.Id, cancellationTokenSource.Token).ConfigureAwait(false);
-
-                    state.LiveTvStreamId = streamInfo.Id;
-
-                    state.MediaPath = streamInfo.Path;
-                    state.InputProtocol = streamInfo.Protocol;
-
-                    await Task.Delay(1500, cancellationTokenSource.Token).ConfigureAwait(false);
-
-                    AttachMediaStreamInfo(state, streamInfo, state.VideoRequest, state.RequestedUrl);
-                    checkCodecs = true;
+                    TryStreamCopy(state, state.VideoRequest);
                 }
+            }
 
-                var videoRequest = state.VideoRequest;
-
-                if (videoRequest != null && checkCodecs)
-                {
-                    if (state.VideoStream != null && CanStreamCopyVideo(videoRequest, state.VideoStream))
-                    {
-                        state.OutputVideoCodec = "copy";
-                    }
-
-                    if (state.AudioStream != null && CanStreamCopyAudio(videoRequest, state.AudioStream, state.SupportedAudioCodecs))
-                    {
-                        state.OutputAudioCodec = "copy";
-                    }
-                }
+            if (state.MediaSource.BufferMs.HasValue)
+            {
+                await Task.Delay(state.MediaSource.BufferMs.Value, cancellationTokenSource.Token).ConfigureAwait(false);
             }
         }
 
@@ -1017,7 +974,7 @@ namespace MediaBrowser.Api.Playback
             await AcquireResources(state, cancellationTokenSource).ConfigureAwait(false);
 
             var transcodingId = Guid.NewGuid().ToString("N");
-            var commandLineArgs = GetCommandLineArguments(outputPath, transcodingId, state, true);
+            var commandLineArgs = GetCommandLineArguments(outputPath, state, true);
 
             if (ApiEntryPoint.Instance.GetEncodingOptions().EnableDebugLogging)
             {
@@ -1052,7 +1009,7 @@ namespace MediaBrowser.Api.Playback
             }
 
             var transcodingJob = ApiEntryPoint.Instance.OnTranscodeBeginning(outputPath,
-                state.Request.StreamId,
+                state.Request.PlaySessionId,
                 transcodingId,
                 TranscodingJobType,
                 process,
@@ -1123,7 +1080,7 @@ namespace MediaBrowser.Api.Playback
             {
                 if (state.RunTimeTicks.Value >= TimeSpan.FromMinutes(5).Ticks && state.IsInputVideo)
                 {
-                    transcodingJob.TranscodingThrottler = state.TranscodingThrottler = new TranscodingThrottler(transcodingJob, Logger);
+                    transcodingJob.TranscodingThrottler = state.TranscodingThrottler = new TranscodingThrottler(transcodingJob, Logger, ServerConfigurationManager);
                     state.TranscodingThrottler.Start();
                 }
             }
@@ -1554,7 +1511,11 @@ namespace MediaBrowser.Api.Playback
                 }
                 else if (i == 21)
                 {
-                    request.StreamId = val;
+                    request.PlaySessionId = val;
+                }
+                else if (i == 22)
+                {
+                    request.LiveStreamId = val;
                 }
             }
         }
@@ -1644,7 +1605,7 @@ namespace MediaBrowser.Api.Playback
                 request.AudioCodec = InferAudioCodec(url);
             }
 
-            var state = new StreamState(LiveTvManager, Logger)
+            var state = new StreamState(MediaSourceManager, Logger)
             {
                 Request = request,
                 RequestedUrl = url
@@ -1658,109 +1619,28 @@ namespace MediaBrowser.Api.Playback
 
             var item = LibraryManager.GetItemById(request.Id);
 
-            List<MediaStream> mediaStreams = null;
+            state.IsInputVideo = string.Equals(item.MediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase);
 
-            state.ItemType = item.GetType().Name;
-            state.ItemId = item.Id.ToString("N");
             var archivable = item as IArchivable;
             state.IsInputArchive = archivable != null && archivable.IsArchive;
 
-            if (item is ILiveTvRecording)
-            {
-                var recording = await LiveTvManager.GetInternalRecording(request.Id, cancellationToken).ConfigureAwait(false);
-
-                state.VideoType = VideoType.VideoFile;
-                state.IsInputVideo = string.Equals(recording.MediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase);
-
-                var path = recording.RecordingInfo.Path;
-                var mediaUrl = recording.RecordingInfo.Url;
-
-                var source = string.IsNullOrEmpty(request.MediaSourceId)
-                    ? recording.GetMediaSources(false).First()
-                    : MediaSourceManager.GetStaticMediaSource(recording, request.MediaSourceId, false);
-
-                mediaStreams = source.MediaStreams;
-
-                // Just to prevent this from being null and causing other methods to fail
-                state.MediaPath = string.Empty;
-
-                if (!string.IsNullOrEmpty(path))
-                {
-                    state.MediaPath = path;
-                    state.InputProtocol = MediaProtocol.File;
-                }
-                else if (!string.IsNullOrEmpty(mediaUrl))
-                {
-                    state.MediaPath = mediaUrl;
-                    state.InputProtocol = MediaProtocol.Http;
-                }
-
-                state.RunTimeTicks = recording.RunTimeTicks;
-                state.DeInterlace = true;
-                state.OutputAudioSync = "1000";
-                state.InputVideoSync = "-1";
-                state.InputAudioSync = "1";
-                state.InputContainer = recording.Container;
-                state.ReadInputAtNativeFramerate = source.ReadAtNativeFramerate;
-            }
-            else if (item is LiveTvChannel)
+            MediaSourceInfo mediaSource = null;
+            if (string.IsNullOrWhiteSpace(request.LiveStreamId))
             {
-                var channel = LiveTvManager.GetInternalChannel(request.Id);
-
-                state.VideoType = VideoType.VideoFile;
-                state.IsInputVideo = string.Equals(channel.MediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase);
-                mediaStreams = new List<MediaStream>();
-
-                state.DeInterlace = true;
+                var mediaSources = await MediaSourceManager.GetPlayackMediaSources(request.Id, false, cancellationToken).ConfigureAwait(false);
 
-                // Just to prevent this from being null and causing other methods to fail
-                state.MediaPath = string.Empty;
+                mediaSource = string.IsNullOrEmpty(request.MediaSourceId)
+                   ? mediaSources.First()
+                   : mediaSources.First(i => string.Equals(i.Id, request.MediaSourceId));
             }
             else
             {
-                var mediaSources = await MediaSourceManager.GetPlayackMediaSources(request.Id, false, cancellationToken).ConfigureAwait(false);
-
-                var mediaSource = string.IsNullOrEmpty(request.MediaSourceId)
-                    ? mediaSources.First()
-                    : mediaSources.First(i => string.Equals(i.Id, request.MediaSourceId));
-
-                mediaStreams = mediaSource.MediaStreams;
-
-                state.MediaPath = mediaSource.Path;
-                state.InputProtocol = mediaSource.Protocol;
-                state.InputContainer = mediaSource.Container;
-                state.InputFileSize = mediaSource.Size;
-                state.InputBitrate = mediaSource.Bitrate;
-                state.ReadInputAtNativeFramerate = mediaSource.ReadAtNativeFramerate;
-                state.RunTimeTicks = mediaSource.RunTimeTicks;
-                state.RemoteHttpHeaders = mediaSource.RequiredHttpHeaders;
-
-                var video = item as Video;
-
-                if (video != null)
-                {
-                    state.IsInputVideo = true;
-
-                    if (mediaSource.VideoType.HasValue)
-                    {
-                        state.VideoType = mediaSource.VideoType.Value;
-                    }
-
-                    state.IsoType = mediaSource.IsoType;
-
-                    state.PlayableStreamFileNames = mediaSource.PlayableStreamFileNames.ToList();
-
-                    if (mediaSource.Timestamp.HasValue)
-                    {
-                        state.InputTimestamp = mediaSource.Timestamp.Value;
-                    }
-                }
-
+                mediaSource = await MediaSourceManager.GetLiveStream(request.LiveStreamId, cancellationToken).ConfigureAwait(false);
             }
 
             var videoRequest = request as VideoStreamRequest;
 
-            AttachMediaStreamInfo(state, mediaStreams, videoRequest, url);
+            AttachMediaSourceInfo(state, mediaSource, videoRequest, url);
 
             var container = Path.GetExtension(state.RequestedUrl);
 
@@ -1801,15 +1681,7 @@ namespace MediaBrowser.Api.Playback
 
             if (videoRequest != null)
             {
-                if (state.VideoStream != null && CanStreamCopyVideo(videoRequest, state.VideoStream))
-                {
-                    state.OutputVideoCodec = "copy";
-                }
-
-                if (state.AudioStream != null && CanStreamCopyAudio(videoRequest, state.AudioStream, state.SupportedAudioCodecs))
-                {
-                    state.OutputAudioCodec = "copy";
-                }
+                TryStreamCopy(state, videoRequest);
             }
 
             state.OutputFilePath = GetOutputFilePath(state);
@@ -1817,11 +1689,47 @@ namespace MediaBrowser.Api.Playback
             return state;
         }
 
-        private void AttachMediaStreamInfo(StreamState state,
+        private void TryStreamCopy(StreamState state, VideoStreamRequest videoRequest)
+        {
+            if (state.VideoStream != null && CanStreamCopyVideo(videoRequest, state.VideoStream))
+            {
+                state.OutputVideoCodec = "copy";
+            }
+
+            if (state.AudioStream != null && CanStreamCopyAudio(videoRequest, state.AudioStream, state.SupportedAudioCodecs))
+            {
+                state.OutputAudioCodec = "copy";
+            }
+        }
+
+        private void AttachMediaSourceInfo(StreamState state,
           MediaSourceInfo mediaSource,
           VideoStreamRequest videoRequest,
           string requestedUrl)
         {
+            state.MediaPath = mediaSource.Path;
+            state.InputProtocol = mediaSource.Protocol;
+            state.InputContainer = mediaSource.Container;
+            state.InputFileSize = mediaSource.Size;
+            state.InputBitrate = mediaSource.Bitrate;
+            state.ReadInputAtNativeFramerate = mediaSource.ReadAtNativeFramerate;
+            state.RunTimeTicks = mediaSource.RunTimeTicks;
+            state.RemoteHttpHeaders = mediaSource.RequiredHttpHeaders;
+
+            if (mediaSource.VideoType.HasValue)
+            {
+                state.VideoType = mediaSource.VideoType.Value;
+            }
+
+            state.IsoType = mediaSource.IsoType;
+
+            state.PlayableStreamFileNames = mediaSource.PlayableStreamFileNames.ToList();
+
+            if (mediaSource.Timestamp.HasValue)
+            {
+                state.InputTimestamp = mediaSource.Timestamp.Value;
+            }
+
             state.InputProtocol = mediaSource.Protocol;
             state.MediaPath = mediaSource.Path;
             state.RunTimeTicks = mediaSource.RunTimeTicks;
@@ -1830,21 +1738,16 @@ namespace MediaBrowser.Api.Playback
             state.InputFileSize = mediaSource.Size;
             state.ReadInputAtNativeFramerate = mediaSource.ReadAtNativeFramerate;
 
-            if (state.ReadInputAtNativeFramerate)
+            if (state.ReadInputAtNativeFramerate ||
+                mediaSource.Protocol == MediaProtocol.File && string.Equals(mediaSource.Container, "wtv", StringComparison.OrdinalIgnoreCase))
             {
                 state.OutputAudioSync = "1000";
                 state.InputVideoSync = "-1";
                 state.InputAudioSync = "1";
             }
 
-            AttachMediaStreamInfo(state, mediaSource.MediaStreams, videoRequest, requestedUrl);
-        }
+            var mediaStreams = mediaSource.MediaStreams;
 
-        private void AttachMediaStreamInfo(StreamState state,
-            List<MediaStream> mediaStreams,
-            VideoStreamRequest videoRequest,
-            string requestedUrl)
-        {
             if (videoRequest != null)
             {
                 if (string.IsNullOrEmpty(videoRequest.VideoCodec))
@@ -1873,7 +1776,7 @@ namespace MediaBrowser.Api.Playback
                 state.AudioStream = GetMediaStream(mediaStreams, null, MediaStreamType.Audio, true);
             }
 
-            state.AllMediaStreams = mediaStreams;
+            state.MediaSource = mediaSource;
         }
 
         private bool CanStreamCopyVideo(VideoStreamRequest request, MediaStream videoStream)
@@ -2109,7 +2012,6 @@ namespace MediaBrowser.Api.Playback
             }
 
             var audioCodec = state.ActualOutputAudioCodec;
-
             var videoCodec = state.ActualOutputVideoCodec;
 
             var mediaProfile = state.VideoRequest == null ?
@@ -2130,7 +2032,9 @@ namespace MediaBrowser.Api.Playback
                 state.TargetTimestamp,
                 state.IsTargetAnamorphic,
                 state.IsTargetCabac,
-                state.TargetRefFrames);
+                state.TargetRefFrames,
+                state.TargetVideoStreamCount,
+                state.TargetAudioStreamCount);
 
             if (mediaProfile != null)
             {
@@ -2215,7 +2119,9 @@ namespace MediaBrowser.Api.Playback
                     state.TranscodeSeekInfo,
                     state.IsTargetAnamorphic,
                     state.IsTargetCabac,
-                    state.TargetRefFrames
+                    state.TargetRefFrames,
+                    state.TargetVideoStreamCount,
+                    state.TargetAudioStreamCount
 
                     ).FirstOrDefault() ?? string.Empty;
             }

+ 5 - 17
MediaBrowser.Api/Playback/Dash/MpegDashService.cs

@@ -5,7 +5,6 @@ using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Devices;
 using MediaBrowser.Controller.Dlna;
 using MediaBrowser.Controller.Library;
-using MediaBrowser.Controller.LiveTv;
 using MediaBrowser.Controller.MediaEncoding;
 using MediaBrowser.Controller.Net;
 using MediaBrowser.Model.IO;
@@ -54,7 +53,7 @@ namespace MediaBrowser.Api.Playback.Dash
 
     public class MpegDashService : BaseHlsService
     {
-        public MpegDashService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, ILiveTvManager liveTvManager, IDlnaManager dlnaManager, ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager, IMediaSourceManager mediaSourceManager, IZipClient zipClient, INetworkManager networkManager) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, fileSystem, liveTvManager, dlnaManager, subtitleEncoder, deviceManager, mediaSourceManager, zipClient)
+        public MpegDashService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, IDlnaManager dlnaManager, ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager, IMediaSourceManager mediaSourceManager, IZipClient zipClient, INetworkManager networkManager) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, fileSystem, dlnaManager, subtitleEncoder, deviceManager, mediaSourceManager, zipClient)
         {
             NetworkManager = networkManager;
         }
@@ -160,7 +159,7 @@ namespace MediaBrowser.Api.Playback.Dash
                             // If the playlist doesn't already exist, startup ffmpeg
                             try
                             {
-                                ApiEntryPoint.Instance.KillTranscodingJobs(request.DeviceId, request.StreamId, p => false);
+                                ApiEntryPoint.Instance.KillTranscodingJobs(request.DeviceId, request.PlaySessionId, p => false);
 
                                 if (currentTranscodingIndex.HasValue)
                                 {
@@ -447,7 +446,7 @@ namespace MediaBrowser.Api.Playback.Dash
             return args;
         }
 
-        protected override string GetCommandLineArguments(string outputPath, string transcodingJobId, StreamState state, bool isEncoding)
+        protected override string GetCommandLineArguments(string outputPath, StreamState state, bool isEncoding)
         {
             // test url http://192.168.1.2:8096/videos/233e8905d559a8f230db9bffd2ac9d6d/master.mpd?mediasourceid=233e8905d559a8f230db9bffd2ac9d6d&videocodec=h264&audiocodec=aac&maxwidth=1280&videobitrate=500000&audiobitrate=128000&profile=baseline&level=3
             // Good info on i-frames http://blog.streamroot.io/encode-multi-bitrate-videos-mpeg-dash-mse-based-media-players/
@@ -461,7 +460,7 @@ namespace MediaBrowser.Api.Playback.Dash
 
             var args = string.Format("{0} {1} -map_metadata -1 -threads {2} {3} {4} -copyts {5} -f dash -init_seg_name \"{6}\" -media_seg_name \"{7}\" -use_template 0 -use_timeline 1 -min_seg_duration {8} -y \"{9}\"",
                 inputModifier,
-                GetInputArgument(transcodingJobId, state),
+                GetInputArgument(state),
                 threads,
                 GetMapArgs(state),
                 GetVideoArguments(state),
@@ -518,25 +517,14 @@ namespace MediaBrowser.Api.Playback.Dash
 
         private async Task WaitForSegment(string playlist, string segment, CancellationToken cancellationToken)
         {
-            var tmpPath = playlist + ".tmp";
-
             var segmentFilename = Path.GetFileName(segment);
 
             Logger.Debug("Waiting for {0} in {1}", segmentFilename, playlist);
 
             while (true)
             {
-                FileStream fileStream;
-                try
-                {
-                    fileStream = FileSystem.GetFileStream(tmpPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, true);
-                }
-                catch (IOException)
-                {
-                    fileStream = FileSystem.GetFileStream(playlist, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, true);
-                }
                 // Need to use FileShare.ReadWrite because we're reading the file at the same time it's being written
-                using (fileStream)
+                using (var fileStream = GetPlaylistFileStream(playlist))
                 {
                     using (var reader = new StreamReader(fileStream))
                     {

+ 18 - 5
MediaBrowser.Api/Playback/Hls/BaseHlsService.cs

@@ -3,7 +3,6 @@ using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Devices;
 using MediaBrowser.Controller.Dlna;
 using MediaBrowser.Controller.Library;
-using MediaBrowser.Controller.LiveTv;
 using MediaBrowser.Controller.MediaEncoding;
 using MediaBrowser.Model.Extensions;
 using MediaBrowser.Model.IO;
@@ -22,7 +21,7 @@ namespace MediaBrowser.Api.Playback.Hls
     /// </summary>
     public abstract class BaseHlsService : BaseStreamingService
     {
-        protected BaseHlsService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, ILiveTvManager liveTvManager, IDlnaManager dlnaManager, ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager, IMediaSourceManager mediaSourceManager, IZipClient zipClient) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, fileSystem, liveTvManager, dlnaManager, subtitleEncoder, deviceManager, mediaSourceManager, zipClient)
+        protected BaseHlsService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, IDlnaManager dlnaManager, ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager, IMediaSourceManager mediaSourceManager, IZipClient zipClient) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, fileSystem, dlnaManager, subtitleEncoder, deviceManager, mediaSourceManager, zipClient)
         {
         }
 
@@ -186,7 +185,7 @@ namespace MediaBrowser.Api.Playback.Hls
             while (true)
             {
                 // Need to use FileShare.ReadWrite because we're reading the file at the same time it's being written
-                using (var fileStream = FileSystem.GetFileStream(playlist, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, true))
+                using (var fileStream = GetPlaylistFileStream(playlist))
                 {
                     using (var reader = new StreamReader(fileStream))
                     {
@@ -212,7 +211,21 @@ namespace MediaBrowser.Api.Playback.Hls
             }
         }
 
-        protected override string GetCommandLineArguments(string outputPath, string transcodingJobId, StreamState state, bool isEncoding)
+        protected Stream GetPlaylistFileStream(string path)
+        {
+            var tmpPath = path + ".tmp";
+
+            try
+            {
+                return FileSystem.GetFileStream(tmpPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, true);
+            }
+            catch (IOException)
+            {
+                return FileSystem.GetFileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, true);
+            }
+        }
+
+        protected override string GetCommandLineArguments(string outputPath, StreamState state, bool isEncoding)
         {
             var hlsVideoRequest = state.VideoRequest as GetHlsVideoStream;
 
@@ -240,7 +253,7 @@ namespace MediaBrowser.Api.Playback.Hls
             var args = string.Format("{0} {1} {2} -map_metadata -1 -threads {3} {4} {5} -sc_threshold 0 {6} -hls_time {7} -start_number {8} -hls_list_size {9}{10} -y \"{11}\"",
                 itsOffset,
                 inputModifier,
-                GetInputArgument(transcodingJobId, state),
+                GetInputArgument(state),
                 threads,
                 GetMapArgs(state),
                 GetVideoArguments(state),

+ 10 - 10
MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs

@@ -4,7 +4,6 @@ using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Devices;
 using MediaBrowser.Controller.Dlna;
 using MediaBrowser.Controller.Library;
-using MediaBrowser.Controller.LiveTv;
 using MediaBrowser.Controller.MediaEncoding;
 using MediaBrowser.Controller.Net;
 using MediaBrowser.Model.Dlna;
@@ -62,7 +61,7 @@ namespace MediaBrowser.Api.Playback.Hls
 
     public class DynamicHlsService : BaseHlsService
     {
-        public DynamicHlsService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, ILiveTvManager liveTvManager, IDlnaManager dlnaManager, ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager, IMediaSourceManager mediaSourceManager, IZipClient zipClient, INetworkManager networkManager) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, fileSystem, liveTvManager, dlnaManager, subtitleEncoder, deviceManager, mediaSourceManager, zipClient)
+        public DynamicHlsService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, IDlnaManager dlnaManager, ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager, IMediaSourceManager mediaSourceManager, IZipClient zipClient, INetworkManager networkManager) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, fileSystem, dlnaManager, subtitleEncoder, deviceManager, mediaSourceManager, zipClient)
         {
             NetworkManager = networkManager;
         }
@@ -135,7 +134,7 @@ namespace MediaBrowser.Api.Playback.Hls
                         // If the playlist doesn't already exist, startup ffmpeg
                         try
                         {
-                            ApiEntryPoint.Instance.KillTranscodingJobs(request.DeviceId, request.StreamId, p => false);
+                            ApiEntryPoint.Instance.KillTranscodingJobs(request.DeviceId, request.PlaySessionId, p => false);
 
                             if (currentTranscodingIndex.HasValue)
                             {
@@ -300,7 +299,7 @@ namespace MediaBrowser.Api.Playback.Hls
 
             var segmentFilename = Path.GetFileName(segmentPath);
 
-            using (var fileStream = FileSystem.GetFileStream(playlistPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, true))
+            using (var fileStream = GetPlaylistFileStream(playlistPath))
             {
                 using (var reader = new StreamReader(fileStream))
                 {
@@ -414,7 +413,8 @@ namespace MediaBrowser.Api.Playback.Hls
 
             var request = (GetMasterHlsVideoStream)state.Request;
 
-            var subtitleStreams = state.AllMediaStreams
+            var subtitleStreams = state.MediaSource
+                .MediaStreams
                 .Where(i => i.IsTextSubtitleStream)
                 .ToList();
 
@@ -684,7 +684,7 @@ namespace MediaBrowser.Api.Playback.Hls
             return args;
         }
 
-        protected override string GetCommandLineArguments(string outputPath, string transcodingJobId, StreamState state, bool isEncoding)
+        protected override string GetCommandLineArguments(string outputPath, StreamState state, bool isEncoding)
         {
             var threads = GetNumberOfThreads(state, false);
 
@@ -697,9 +697,9 @@ namespace MediaBrowser.Api.Playback.Hls
             {
                 var outputTsArg = Path.Combine(Path.GetDirectoryName(outputPath), Path.GetFileNameWithoutExtension(outputPath)) + "%d.ts";
 
-                return string.Format("{0} {1} -map_metadata -1 -threads {2} {3} {4} -copyts -flags -global_header {5} -f segment -segment_time {6} -segment_start_number {7} -segment_list \"{8}\" -y \"{9}\"",
+                return string.Format("{0} {1} -map_metadata -1 -threads {2} {3} {4} -flags -global_header -sc_threshold 0 {5} -f segment -segment_time {6} -segment_start_number {7} -segment_list \"{8}\" -y \"{9}\"",
                     inputModifier,
-                    GetInputArgument(transcodingJobId, state),
+                    GetInputArgument(state),
                     threads,
                     GetMapArgs(state),
                     GetVideoArguments(state),
@@ -711,9 +711,9 @@ namespace MediaBrowser.Api.Playback.Hls
                     ).Trim();
             }
 
-            return string.Format("{0} {1} -map_metadata -1 -threads {2} {3} {4} -copyts -flags -global_header {5} -hls_time {6} -start_number {7} -hls_list_size {8} -y \"{9}\"",
+            return string.Format("{0} {1} -map_metadata -1 -threads {2} {3} {4} -flags -global_header -sc_threshold 0 {5} -hls_time {6} -start_number {7} -hls_list_size {8} -y \"{9}\"",
                             inputModifier,
-                            GetInputArgument(transcodingJobId, state),
+                            GetInputArgument(state),
                             threads,
                             GetMapArgs(state),
                             GetVideoArguments(state),

+ 3 - 3
MediaBrowser.Api/Playback/Hls/HlsSegmentService.cs

@@ -54,8 +54,8 @@ namespace MediaBrowser.Api.Playback.Hls
         [ApiMember(Name = "DeviceId", Description = "The device id of the client requesting. Used to stop encoding processes when needed.", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "DELETE")]
         public string DeviceId { get; set; }
 
-        [ApiMember(Name = "StreamId", Description = "The stream id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "DELETE")]
-        public string StreamId { get; set; }
+        [ApiMember(Name = "PlaySessionId", Description = "The play session id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "DELETE")]
+        public string PlaySessionId { get; set; }
     }
 
     /// <summary>
@@ -95,7 +95,7 @@ namespace MediaBrowser.Api.Playback.Hls
 
         public void Delete(StopEncodingProcess request)
         {
-            ApiEntryPoint.Instance.KillTranscodingJobs(request.DeviceId, request.StreamId, path => true);
+            ApiEntryPoint.Instance.KillTranscodingJobs(request.DeviceId, request.PlaySessionId, path => true);
         }
 
         /// <summary>

+ 1 - 1
MediaBrowser.Api/Playback/Hls/VideoHlsService.cs

@@ -42,7 +42,7 @@ namespace MediaBrowser.Api.Playback.Hls
     /// </summary>
     public class VideoHlsService : BaseHlsService
     {
-        public VideoHlsService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, ILiveTvManager liveTvManager, IDlnaManager dlnaManager, ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager, IMediaSourceManager mediaSourceManager, IZipClient zipClient) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, fileSystem, liveTvManager, dlnaManager, subtitleEncoder, deviceManager, mediaSourceManager, zipClient)
+        public VideoHlsService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, IDlnaManager dlnaManager, ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager, IMediaSourceManager mediaSourceManager, IZipClient zipClient) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, fileSystem, dlnaManager, subtitleEncoder, deviceManager, mediaSourceManager, zipClient)
         {
         }
 

+ 316 - 19
MediaBrowser.Api/Playback/MediaInfoService.cs

@@ -1,8 +1,12 @@
-using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.Devices;
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Net;
 using MediaBrowser.Model.Dlna;
 using MediaBrowser.Model.Dto;
+using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.MediaInfo;
+using MediaBrowser.Model.Session;
 using ServiceStack;
 using System;
 using System.Collections.Generic;
@@ -13,7 +17,7 @@ using System.Threading.Tasks;
 namespace MediaBrowser.Api.Playback
 {
     [Route("/Items/{Id}/MediaInfo", "GET", Summary = "Gets live playback media info for an item")]
-    public class GetLiveMediaInfo : IReturn<LiveMediaInfoResult>
+    public class GetLiveMediaInfo : IReturn<PlaybackInfoResponse>
     {
         [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
         public string Id { get; set; }
@@ -23,7 +27,7 @@ namespace MediaBrowser.Api.Playback
     }
 
     [Route("/Items/{Id}/PlaybackInfo", "GET", Summary = "Gets live playback media info for an item")]
-    public class GetPlaybackInfo : IReturn<LiveMediaInfoResult>
+    public class GetPlaybackInfo : IReturn<PlaybackInfoResponse>
     {
         [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
         public string Id { get; set; }
@@ -32,45 +36,338 @@ namespace MediaBrowser.Api.Playback
         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>
+    {
+    }
+
+    [Route("/LiveStreams/Open", "POST", Summary = "Opens a media source")]
+    public class OpenMediaSource : LiveStreamRequest, IReturn<LiveStreamResponse>
+    {
+    }
+
+    [Route("/LiveStreams/Close", "POST", Summary = "Closes a media source")]
+    public class CloseMediaSource : IReturnVoid
+    {
+        [ApiMember(Name = "LiveStreamId", Description = "LiveStreamId", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
+        public string LiveStreamId { get; set; }
+    }
+
     [Authenticated]
     public class MediaInfoService : BaseApiService
     {
         private readonly IMediaSourceManager _mediaSourceManager;
+        private readonly IDeviceManager _deviceManager;
+        private readonly ILibraryManager _libraryManager;
 
-        public MediaInfoService(IMediaSourceManager mediaSourceManager)
+        public MediaInfoService(IMediaSourceManager mediaSourceManager, IDeviceManager deviceManager, ILibraryManager libraryManager)
         {
             _mediaSourceManager = mediaSourceManager;
+            _deviceManager = deviceManager;
+            _libraryManager = libraryManager;
         }
 
-        public Task<object> Get(GetPlaybackInfo request)
+        public async Task<object> Get(GetPlaybackInfo request)
         {
-            return GetPlaybackInfo(request.Id, request.UserId);
+            var result = await GetPlaybackInfo(request.Id, request.UserId).ConfigureAwait(false);
+            return ToOptimizedResult(result);
         }
 
-        public Task<object> Get(GetLiveMediaInfo request)
+        public async Task<object> Get(GetLiveMediaInfo request)
         {
-            return GetPlaybackInfo(request.Id, request.UserId);
+            var result = await GetPlaybackInfo(request.Id, request.UserId).ConfigureAwait(false);
+            return ToOptimizedResult(result);
         }
 
-        private async Task<object> GetPlaybackInfo(string id, string userId)
+        public async Task<object> Post(OpenMediaSource request)
         {
-            IEnumerable<MediaSourceInfo> mediaSources;
-            var result = new LiveMediaInfoResult();
+            var authInfo = AuthorizationContext.GetAuthorizationInfo(Request);
+
+            var result = await _mediaSourceManager.OpenLiveStream(request, false, CancellationToken.None).ConfigureAwait(false);
 
-            try
+            var profile = request.DeviceProfile;
+            if (profile == null)
             {
-                mediaSources = await _mediaSourceManager.GetPlayackMediaSources(id, userId, true, CancellationToken.None).ConfigureAwait(false);
+                var caps = _deviceManager.GetCapabilities(authInfo.DeviceId);
+                if (caps != null)
+                {
+                    profile = caps.DeviceProfile;
+                }
             }
-            catch (PlaybackException ex)
+
+            if (profile != null)
             {
-                mediaSources = new List<MediaSourceInfo>();
-                result.ErrorCode = ex.ErrorCode;
-            }
+                var item = _libraryManager.GetItemById(request.ItemId);
 
-            result.MediaSources = mediaSources.ToList();
-            result.StreamId = Guid.NewGuid().ToString("N");
+                SetDeviceSpecificData(item, result.MediaSource, profile, authInfo, request.MaxStreamingBitrate,
+                    request.StartTimeTicks ?? 0, result.MediaSource.Id, request.AudioStreamIndex,
+                    request.SubtitleStreamIndex, request.PlaySessionId);
+            }
+            else
+            {
+                if (!string.IsNullOrWhiteSpace(result.MediaSource.TranscodingUrl))
+                {
+                    result.MediaSource.TranscodingUrl += "&LiveStreamId=" + result.MediaSource.LiveStreamId;
+                }
+            }
 
             return ToOptimizedResult(result);
         }
+
+        public void Post(CloseMediaSource request)
+        {
+            var task = _mediaSourceManager.CloseLiveStream(request.LiveStreamId, CancellationToken.None);
+            Task.WaitAll(task);
+        }
+
+        public async Task<object> Post(GetPostedPlaybackInfo request)
+        {
+            var info = await GetPlaybackInfo(request.Id, request.UserId, request.MediaSourceId, request.LiveStreamId).ConfigureAwait(false);
+            var authInfo = AuthorizationContext.GetAuthorizationInfo(Request);
+
+            var profile = request.DeviceProfile;
+            if (profile == null)
+            {
+                var caps = _deviceManager.GetCapabilities(authInfo.DeviceId);
+                if (caps != null)
+                {
+                    profile = caps.DeviceProfile;
+                }
+            }
+
+            if (profile != null)
+            {
+                var mediaSourceId = request.MediaSourceId;
+                SetDeviceSpecificData(request.Id, info, profile, authInfo, request.MaxStreamingBitrate, request.StartTimeTicks ?? 0, mediaSourceId, request.AudioStreamIndex, request.SubtitleStreamIndex);
+            }
+
+            return ToOptimizedResult(info);
+        }
+
+        private async Task<PlaybackInfoResponse> GetPlaybackInfo(string id, string userId, string mediaSourceId = null, string liveStreamId = null)
+        {
+            var result = new PlaybackInfoResponse();
+
+            if (string.IsNullOrWhiteSpace(liveStreamId))
+            {
+                IEnumerable<MediaSourceInfo> mediaSources;
+                try
+                {
+                    mediaSources = await _mediaSourceManager.GetPlayackMediaSources(id, userId, true, CancellationToken.None).ConfigureAwait(false);
+                }
+                catch (PlaybackException ex)
+                {
+                    mediaSources = new List<MediaSourceInfo>();
+                    result.ErrorCode = ex.ErrorCode;
+                }
+
+                result.MediaSources = mediaSources.ToList();
+
+                if (!string.IsNullOrWhiteSpace(mediaSourceId))
+                {
+                    result.MediaSources = result.MediaSources
+                        .Where(i => string.Equals(i.Id, mediaSourceId, StringComparison.OrdinalIgnoreCase))
+                        .ToList();
+                }
+            }
+            else
+            {
+                var mediaSource = await _mediaSourceManager.GetLiveStream(liveStreamId, CancellationToken.None).ConfigureAwait(false);
+
+                result.MediaSources = new List<MediaSourceInfo> { mediaSource };
+            }
+
+            if (result.MediaSources.Count == 0)
+            {
+                if (!result.ErrorCode.HasValue)
+                {
+                    result.ErrorCode = PlaybackErrorCode.NoCompatibleStream;
+                }
+            }
+            else
+            {
+                result.PlaySessionId = Guid.NewGuid().ToString("N");
+            }
+
+            return result;
+        }
+
+        private void SetDeviceSpecificData(string itemId,
+            PlaybackInfoResponse result,
+            DeviceProfile profile,
+            AuthorizationInfo auth,
+            int? maxBitrate,
+            long startTimeTicks,
+            string mediaSourceId,
+            int? audioStreamIndex,
+            int? subtitleStreamIndex)
+        {
+            var item = _libraryManager.GetItemById(itemId);
+
+            foreach (var mediaSource in result.MediaSources)
+            {
+                SetDeviceSpecificData(item, mediaSource, profile, auth, maxBitrate, startTimeTicks, mediaSourceId, audioStreamIndex, subtitleStreamIndex, result.PlaySessionId);
+            }
+
+            SortMediaSources(result);
+        }
+
+        private void SetDeviceSpecificData(BaseItem item,
+            MediaSourceInfo mediaSource,
+            DeviceProfile profile,
+            AuthorizationInfo auth,
+            int? maxBitrate,
+            long startTimeTicks,
+            string mediaSourceId,
+            int? audioStreamIndex,
+            int? subtitleStreamIndex,
+            string playSessionId)
+        {
+            var streamBuilder = new StreamBuilder();
+
+            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;
+            }
+
+            if (mediaSource.SupportsDirectPlay)
+            {
+                var supportsDirectStream = mediaSource.SupportsDirectStream;
+
+                // Dummy this up to fool StreamBuilder
+                mediaSource.SupportsDirectStream = true;
+
+                // 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)
+                {
+                    mediaSource.SupportsDirectPlay = false;
+                }
+
+                // Set this back to what it was
+                mediaSource.SupportsDirectStream = supportsDirectStream;
+
+                if (streamInfo != null)
+                {
+                    SetDeviceSpecificSubtitleInfo(streamInfo, mediaSource, auth.Token);
+                }
+            }
+
+            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)
+                {
+                    mediaSource.SupportsDirectStream = false;
+                }
+
+                if (streamInfo != null)
+                {
+                    SetDeviceSpecificSubtitleInfo(streamInfo, mediaSource, auth.Token);
+                }
+            }
+
+            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.PlaySessionId = playSessionId;
+                    SetDeviceSpecificSubtitleInfo(streamInfo, mediaSource, auth.Token);
+                }
+
+                if (streamInfo != null && streamInfo.PlayMethod == PlayMethod.Transcode)
+                {
+                    streamInfo.StartPositionTicks = startTimeTicks;
+                    mediaSource.TranscodingUrl = streamInfo.ToUrl("-", auth.Token).TrimStart('-');
+                    mediaSource.TranscodingContainer = streamInfo.Container;
+                    mediaSource.TranscodingSubProtocol = streamInfo.SubProtocol;
+                }
+            }
+        }
+
+        private void SetDeviceSpecificSubtitleInfo(StreamInfo info, MediaSourceInfo mediaSource, string accessToken)
+        {
+            var profiles = info.GetSubtitleProfiles(false, "-", accessToken);
+            mediaSource.DefaultSubtitleStreamIndex = info.SubtitleStreamIndex;
+
+            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('-');
+                            stream.IsExternalUrl = profile.IsExternalUrl;
+                        }
+                    }
+                }
+            }
+        }
+
+        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();
+        }
     }
 }

+ 3 - 3
MediaBrowser.Api/Playback/Progressive/AudioService.cs

@@ -31,7 +31,7 @@ namespace MediaBrowser.Api.Playback.Progressive
     /// </summary>
     public class AudioService : BaseProgressiveStreamingService
     {
-        public AudioService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, ILiveTvManager liveTvManager, IDlnaManager dlnaManager, ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager, IMediaSourceManager mediaSourceManager, IZipClient zipClient, IImageProcessor imageProcessor, IHttpClient httpClient) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, fileSystem, liveTvManager, dlnaManager, subtitleEncoder, deviceManager, mediaSourceManager, zipClient, imageProcessor, httpClient)
+        public AudioService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, IDlnaManager dlnaManager, ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager, IMediaSourceManager mediaSourceManager, IZipClient zipClient, IImageProcessor imageProcessor, IHttpClient httpClient) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, fileSystem, dlnaManager, subtitleEncoder, deviceManager, mediaSourceManager, zipClient, imageProcessor, httpClient)
         {
         }
 
@@ -55,7 +55,7 @@ namespace MediaBrowser.Api.Playback.Progressive
             return ProcessRequest(request, true);
         }
 
-        protected override string GetCommandLineArguments(string outputPath, string transcodingJobId, StreamState state, bool isEncoding)
+        protected override string GetCommandLineArguments(string outputPath, StreamState state, bool isEncoding)
         {
             var audioTranscodeParams = new List<string>();
 
@@ -84,7 +84,7 @@ namespace MediaBrowser.Api.Playback.Progressive
 
             return string.Format("{0} {1} -threads {2}{3} {4} -id3v2_version 3 -write_id3v1 1 -y \"{5}\"",
                 inputModifier,
-                GetInputArgument(transcodingJobId, state),
+                GetInputArgument(state),
                 threads,
                 vn,
                 string.Join(" ", audioTranscodeParams.ToArray()),

+ 1 - 1
MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs

@@ -27,7 +27,7 @@ namespace MediaBrowser.Api.Playback.Progressive
         protected readonly IImageProcessor ImageProcessor;
         protected readonly IHttpClient HttpClient;
 
-        protected BaseProgressiveStreamingService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, ILiveTvManager liveTvManager, IDlnaManager dlnaManager, ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager, IMediaSourceManager mediaSourceManager, IZipClient zipClient, IImageProcessor imageProcessor, IHttpClient httpClient) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, fileSystem, liveTvManager, dlnaManager, subtitleEncoder, deviceManager, mediaSourceManager, zipClient)
+        protected BaseProgressiveStreamingService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, IDlnaManager dlnaManager, ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager, IMediaSourceManager mediaSourceManager, IZipClient zipClient, IImageProcessor imageProcessor, IHttpClient httpClient) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, fileSystem, dlnaManager, subtitleEncoder, deviceManager, mediaSourceManager, zipClient)
         {
             ImageProcessor = imageProcessor;
             HttpClient = httpClient;

+ 3 - 3
MediaBrowser.Api/Playback/Progressive/VideoService.cs

@@ -62,7 +62,7 @@ namespace MediaBrowser.Api.Playback.Progressive
     /// </summary>
     public class VideoService : BaseProgressiveStreamingService
     {
-        public VideoService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, ILiveTvManager liveTvManager, IDlnaManager dlnaManager, ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager, IMediaSourceManager mediaSourceManager, IZipClient zipClient, IImageProcessor imageProcessor, IHttpClient httpClient) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, fileSystem, liveTvManager, dlnaManager, subtitleEncoder, deviceManager, mediaSourceManager, zipClient, imageProcessor, httpClient)
+        public VideoService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, IDlnaManager dlnaManager, ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager, IMediaSourceManager mediaSourceManager, IZipClient zipClient, IImageProcessor imageProcessor, IHttpClient httpClient) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, fileSystem, dlnaManager, subtitleEncoder, deviceManager, mediaSourceManager, zipClient, imageProcessor, httpClient)
         {
         }
 
@@ -86,7 +86,7 @@ namespace MediaBrowser.Api.Playback.Progressive
             return ProcessRequest(request, true);
         }
 
-        protected override string GetCommandLineArguments(string outputPath, string transcodingJobId, StreamState state, bool isEncoding)
+        protected override string GetCommandLineArguments(string outputPath, StreamState state, bool isEncoding)
         {
             // Get the output codec name
             var videoCodec = state.OutputVideoCodec;
@@ -106,7 +106,7 @@ namespace MediaBrowser.Api.Playback.Progressive
 
             return string.Format("{0} {1}{2} {3} {4} -map_metadata -1 -threads {5} {6}{7} -y \"{8}\"",
                 inputModifier,
-                GetInputArgument(transcodingJobId, state),
+                GetInputArgument(state),
                 keyFrame,
                 GetMapArgs(state),
                 GetVideoArguments(state, videoCodec),

+ 2 - 3
MediaBrowser.Api/Playback/StreamRequest.cs

@@ -71,9 +71,8 @@ namespace MediaBrowser.Api.Playback
 
         public string Params { get; set; }
         public string ClientTime { get; set; }
-        public string StreamId { get; set; }
-
-        public string TranscodingJobId { get; set; }
+        public string PlaySessionId { get; set; }
+        public string LiveStreamId { get; set; }
     }
 
     public class VideoStreamRequest : StreamRequest

+ 45 - 14
MediaBrowser.Api/Playback/StreamState.cs

@@ -1,6 +1,7 @@
-using MediaBrowser.Controller.LiveTv;
+using MediaBrowser.Controller.Library;
 using MediaBrowser.Model.Dlna;
 using MediaBrowser.Model.Drawing;
+using MediaBrowser.Model.Dto;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.IO;
 using MediaBrowser.Model.Logging;
@@ -17,7 +18,7 @@ namespace MediaBrowser.Api.Playback
     public class StreamState : IDisposable
     {
         private readonly ILogger _logger;
-        private readonly ILiveTvManager _liveTvManager;
+        private readonly IMediaSourceManager _mediaSourceManager;
 
         public string RequestedUrl { get; set; }
 
@@ -39,7 +40,7 @@ namespace MediaBrowser.Api.Playback
 
         public string InputContainer { get; set; }
 
-        public List<MediaStream> AllMediaStreams { get; set; }
+        public MediaSourceInfo MediaSource { get; set; }
         
         public MediaStream AudioStream { get; set; }
         public MediaStream VideoStream { get; set; }
@@ -64,8 +65,6 @@ namespace MediaBrowser.Api.Playback
 
         public List<string> PlayableStreamFileNames { get; set; }
 
-        public string LiveTvStreamId { get; set; }
-
         public int SegmentLength = 3;
         public bool EnableGenericHlsSegmenter = false;
         public int HlsListSize
@@ -86,14 +85,13 @@ namespace MediaBrowser.Api.Playback
 
         public List<string> SupportedAudioCodecs { get; set; }
 
-        public StreamState(ILiveTvManager liveTvManager, ILogger logger)
+        public StreamState(IMediaSourceManager mediaSourceManager, ILogger logger)
         {
-            _liveTvManager = liveTvManager;
+            _mediaSourceManager = mediaSourceManager;
             _logger = logger;
             SupportedAudioCodecs = new List<string>();
             PlayableStreamFileNames = new List<string>();
             RemoteHttpHeaders = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
-            AllMediaStreams = new List<MediaStream>();
         }
 
         public string InputAudioSync { get; set; }
@@ -113,9 +111,6 @@ namespace MediaBrowser.Api.Playback
 
         public long? EncodingDurationTicks { get; set; }
 
-        public string ItemType { get; set; }
-        public string ItemId { get; set; }
-
         public string GetMimeType(string outputPath)
         {
             if (!string.IsNullOrEmpty(MimeType))
@@ -187,15 +182,15 @@ namespace MediaBrowser.Api.Playback
 
         private async void DisposeLiveStream()
         {
-            if (!string.IsNullOrEmpty(LiveTvStreamId))
+            if (MediaSource.RequiresClosing && string.IsNullOrWhiteSpace(Request.LiveStreamId))
             {
                 try
                 {
-                    await _liveTvManager.CloseLiveStream(LiveTvStreamId, CancellationToken.None).ConfigureAwait(false);
+                    await _mediaSourceManager.CloseLiveStream(MediaSource.LiveStreamId, CancellationToken.None).ConfigureAwait(false);
                 }
                 catch (Exception ex)
                 {
-                    _logger.ErrorException("Error closing live tv stream", ex);
+                    _logger.ErrorException("Error closing media source", ex);
                 }
             }
         }
@@ -351,6 +346,42 @@ namespace MediaBrowser.Api.Playback
             }
         }
 
+        public int? TargetVideoStreamCount
+        {
+            get
+            {
+                if (Request.Static)
+                {
+                    return GetMediaStreamCount(MediaStreamType.Video, int.MaxValue);
+                }
+                return GetMediaStreamCount(MediaStreamType.Video, 1);
+            }
+        }
+
+        public int? TargetAudioStreamCount
+        {
+            get
+            {
+                if (Request.Static)
+                {
+                    return GetMediaStreamCount(MediaStreamType.Audio, int.MaxValue);
+                }
+                return GetMediaStreamCount(MediaStreamType.Audio, 1);
+            }
+        }
+
+        private int? GetMediaStreamCount(MediaStreamType type, int limit)
+        {
+            var count = MediaSource.GetStreamCount(type);
+
+            if (count.HasValue)
+            {
+                count = Math.Min(count.Value, limit);
+            }
+
+            return count;
+        }
+
         /// <summary>
         /// Predicts the audio sample rate that will be in the output stream
         /// </summary>

+ 18 - 8
MediaBrowser.Api/Playback/TranscodingThrottler.cs

@@ -1,4 +1,6 @@
-using MediaBrowser.Model.Logging;
+using MediaBrowser.Common.Configuration;
+using MediaBrowser.Model.Configuration;
+using MediaBrowser.Model.Logging;
 using System;
 using System.IO;
 using System.Threading;
@@ -11,13 +13,18 @@ namespace MediaBrowser.Api.Playback
         private readonly ILogger _logger;
         private Timer _timer;
         private bool _isPaused;
+        private readonly IConfigurationManager _config;
 
-        private readonly long _gapLengthInTicks = TimeSpan.FromMinutes(2).Ticks;
-
-        public TranscodingThrottler(TranscodingJob job, ILogger logger)
+        public TranscodingThrottler(TranscodingJob job, ILogger logger, IConfigurationManager config)
         {
             _job = job;
             _logger = logger;
+            _config = config;
+        }
+
+        private EncodingOptions GetOptions()
+        {
+            return _config.GetConfiguration<EncodingOptions>("encoding");
         }
 
         public void Start()
@@ -33,7 +40,9 @@ namespace MediaBrowser.Api.Playback
                 return;
             }
 
-            if (IsThrottleAllowed(_job))
+            var options = GetOptions();
+
+            if (/*options.EnableThrottling &&*/ IsThrottleAllowed(_job, options.ThrottleThresholdSeconds))
             {
                 PauseTranscoding();
             }
@@ -79,19 +88,20 @@ namespace MediaBrowser.Api.Playback
             }
         }
 
-        private bool IsThrottleAllowed(TranscodingJob job)
+        private bool IsThrottleAllowed(TranscodingJob job, int thresholdSeconds)
         {
             var bytesDownloaded = job.BytesDownloaded ?? 0;
             var transcodingPositionTicks = job.TranscodingPositionTicks ?? 0;
             var downloadPositionTicks = job.DownloadPositionTicks ?? 0;
 
             var path = job.Path;
+            var gapLengthInTicks = TimeSpan.FromSeconds(thresholdSeconds).Ticks;
 
             if (downloadPositionTicks > 0 && transcodingPositionTicks > 0)
             {
                 // HLS - time-based consideration
 
-                var targetGap = _gapLengthInTicks;
+                var targetGap = gapLengthInTicks;
                 var gap = transcodingPositionTicks - downloadPositionTicks;
 
                 if (gap < targetGap)
@@ -113,7 +123,7 @@ namespace MediaBrowser.Api.Playback
                     var bytesTranscoded = job.BytesTranscoded ?? new FileInfo(path).Length;
 
                     // Estimate the bytes the transcoder should be ahead
-                    double gapFactor = _gapLengthInTicks;
+                    double gapFactor = gapLengthInTicks;
                     gapFactor /= transcodingPositionTicks;
                     var targetGap = bytesTranscoded * gapFactor;
 

+ 1 - 1
MediaBrowser.Api/Subtitles/SubtitleService.cs

@@ -192,7 +192,7 @@ namespace MediaBrowser.Api.Subtitles
             {
                 var item = (Video)_libraryManager.GetItemById(new Guid(request.Id));
 
-                var mediaSource = item.GetMediaSources(false)
+                var mediaSource = _mediaSourceManager.GetStaticMediaSources(item, false, null)
                     .First(i => string.Equals(i.Id, request.MediaSourceId ?? request.Id));
 
                 var subtitleStream = mediaSource.MediaStreams

+ 1 - 2
MediaBrowser.Api/UserLibrary/BaseItemsByNameService.cs

@@ -142,7 +142,7 @@ namespace MediaBrowser.Api.UserLibrary
             }
 
             IEnumerable<Tuple<TItemType, List<BaseItem>>> tuples;
-            if (dtoOptions.Fields.Contains(ItemFields.ItemCounts) || true)
+            if (dtoOptions.Fields.Contains(ItemFields.ItemCounts))
             {
                 tuples = ibnItems.Select(i => new Tuple<TItemType, List<BaseItem>>(i, i.GetTaggedItems(libraryItems).ToList()));
             }
@@ -177,7 +177,6 @@ namespace MediaBrowser.Api.UserLibrary
                 return true;
             }
 
-            return true;
             return options.Fields.Contains(ItemFields.ItemCounts);
         }
 

+ 10 - 0
MediaBrowser.Api/UserLibrary/PlaystateService.cs

@@ -294,6 +294,11 @@ namespace MediaBrowser.Api.UserLibrary
 
         public void Post(ReportPlaybackProgress request)
         {
+            if (!string.IsNullOrWhiteSpace(request.PlaySessionId))
+            {
+                ApiEntryPoint.Instance.PingTranscodingJob(AuthorizationContext.GetAuthorizationInfo(Request).DeviceId, request.PlaySessionId);
+            }
+
             request.SessionId = GetSession().Result.Id;
 
             var task = _sessionManager.OnPlaybackProgress(request);
@@ -317,6 +322,11 @@ namespace MediaBrowser.Api.UserLibrary
 
         public void Post(ReportPlaybackStopped request)
         {
+            if (!string.IsNullOrWhiteSpace(request.PlaySessionId))
+            {
+                ApiEntryPoint.Instance.KillTranscodingJobs(AuthorizationContext.GetAuthorizationInfo(Request).DeviceId, request.PlaySessionId, s => true);
+            }
+
             request.SessionId = GetSession().Result.Id;
 
             var task = _sessionManager.OnPlaybackStopped(request);

+ 8 - 0
MediaBrowser.Api/UserLibrary/UserLibraryService.cs

@@ -304,6 +304,14 @@ namespace MediaBrowser.Api.UserLibrary
         {
             var user = _userManager.GetUserById(request.UserId);
 
+            if (!request.IsPlayed.HasValue)
+            {
+                if (user.Configuration.HidePlayedInLatest)
+                {
+                    request.IsPlayed = false;
+                }
+            }
+
             var list = _userViewManager.GetLatestItems(new LatestItemsQuery
             {
                 GroupItems = request.GroupItems,

+ 21 - 11
MediaBrowser.Common.Implementations/HttpClientManager/HttpClientManager.cs

@@ -107,30 +107,40 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager
 
         private WebRequest GetRequest(HttpRequestOptions options, string method, bool enableHttpCompression)
         {
-            var request = (HttpWebRequest)WebRequest.Create(options.Url);
+            var request = WebRequest.Create(options.Url);
+            var httpWebRequest = request as HttpWebRequest;
 
-            AddRequestHeaders(request, options);
+            if (httpWebRequest != null)
+            {
+                AddRequestHeaders(httpWebRequest, options);
 
-            request.AutomaticDecompression = enableHttpCompression ? DecompressionMethods.Deflate : DecompressionMethods.None;
+                httpWebRequest.AutomaticDecompression = enableHttpCompression ? DecompressionMethods.Deflate : DecompressionMethods.None;
+            }
 
             request.CachePolicy = new RequestCachePolicy(RequestCacheLevel.BypassCache);
 
-            if (options.EnableKeepAlive)
+            if (httpWebRequest != null)
             {
-                request.KeepAlive = true;
+                if (options.EnableKeepAlive)
+                {
+                    httpWebRequest.KeepAlive = true;
+                }
             }
 
             request.Method = method;
             request.Timeout = options.TimeoutMs;
 
-            if (!string.IsNullOrEmpty(options.Host))
+            if (httpWebRequest != null)
             {
-                request.Host = options.Host;
-            }
+                if (!string.IsNullOrEmpty(options.Host))
+                {
+                    httpWebRequest.Host = options.Host;
+                }
 
-            if (!string.IsNullOrEmpty(options.Referer))
-            {
-                request.Referer = options.Referer;
+                if (!string.IsNullOrEmpty(options.Referer))
+                {
+                    httpWebRequest.Referer = options.Referer;
+                }
             }
 
             //request.ServicePoint.BindIPEndPointDelegate = BindIPEndPointCallback;

+ 20 - 17
MediaBrowser.Common.Implementations/ScheduledTasks/TaskManager.cs

@@ -6,6 +6,7 @@ using MediaBrowser.Model.Logging;
 using MediaBrowser.Model.Serialization;
 using MediaBrowser.Model.Tasks;
 using System;
+using System.Collections.Concurrent;
 using System.Collections.Generic;
 using System.Linq;
 using System.Threading.Tasks;
@@ -29,7 +30,8 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks
         /// <summary>
         /// The _task queue
         /// </summary>
-        private readonly SortedDictionary<Type, TaskExecutionOptions> _taskQueue = new SortedDictionary<Type, TaskExecutionOptions>();
+        private readonly ConcurrentQueue<Tuple<Type, TaskExecutionOptions>> _taskQueue =
+            new ConcurrentQueue<Tuple<Type, TaskExecutionOptions>>();
 
         /// <summary>
         /// Gets or sets the json serializer.
@@ -136,25 +138,17 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks
         {
             var type = task.ScheduledTask.GetType();
 
+            Logger.Info("Queueing task {0}", type.Name);
+
             lock (_taskQueue)
             {
-                // If it's idle just execute immediately
                 if (task.State == TaskState.Idle)
                 {
                     Execute(task, options);
                     return;
                 }
 
-                if (!_taskQueue.ContainsKey(type))
-                {
-                    Logger.Info("Queueing task {0}", type.Name);
-                    _taskQueue.Add(type, options);
-                }
-                else
-                {
-                    _taskQueue[type] = options;
-                    Logger.Info("Task already queued: {0}", type.Name);
-                }
+                _taskQueue.Enqueue(new Tuple<Type, TaskExecutionOptions>(type, options));
             }
         }
 
@@ -241,15 +235,24 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks
             // Execute queued tasks
             lock (_taskQueue)
             {
-                foreach (var enqueuedType in _taskQueue.ToList())
+                var list = new List<Tuple<Type, TaskExecutionOptions>>();
+
+                Tuple<Type, TaskExecutionOptions> item;
+                while (_taskQueue.TryDequeue(out item))
                 {
-                    var scheduledTask = ScheduledTasks.First(t => t.ScheduledTask.GetType() == enqueuedType.Key);
+                    if (list.All(i => i.Item1 != item.Item1))
+                    {
+                        list.Add(item);
+                    }
+                }
+
+                foreach (var enqueuedType in list)
+                {
+                    var scheduledTask = ScheduledTasks.First(t => t.ScheduledTask.GetType() == enqueuedType.Item1);
 
                     if (scheduledTask.State == TaskState.Idle)
                     {
-                        Execute(scheduledTask, enqueuedType.Value);
-
-                        _taskQueue.Remove(enqueuedType.Key);
+                        Execute(scheduledTask, enqueuedType.Item2);
                     }
                 }
             }

+ 2 - 13
MediaBrowser.Controller/Channels/Channel.cs

@@ -5,7 +5,6 @@ using System;
 using System.Linq;
 using System.Threading;
 using System.Threading.Tasks;
-using MediaBrowser.Model.Users;
 
 namespace MediaBrowser.Controller.Channels
 {
@@ -15,19 +14,9 @@ namespace MediaBrowser.Controller.Channels
 
         public override bool IsVisible(User user)
         {
-            if (user.Policy.BlockedChannels != null)
+            if (!user.Policy.EnableAllChannels && !user.Policy.EnabledChannels.Contains(Id.ToString("N"), StringComparer.OrdinalIgnoreCase))
             {
-                if (user.Policy.BlockedChannels.Contains(Id.ToString("N"), StringComparer.OrdinalIgnoreCase))
-                {
-                    return false;
-                }
-            }
-            else
-            {
-                if (!user.Policy.EnableAllChannels && !user.Policy.EnabledChannels.Contains(Id.ToString("N"), StringComparer.OrdinalIgnoreCase))
-                {
-                    return false;
-                }
+                return false;
             }
             
             return base.IsVisible(user);

+ 11 - 5
MediaBrowser.Controller/Channels/ChannelAudioItem.cs

@@ -75,17 +75,23 @@ namespace MediaBrowser.Controller.Channels
 
         public override IEnumerable<MediaSourceInfo> GetMediaSources(bool enablePathSubstitution)
         {
-            var list = base.GetMediaSources(enablePathSubstitution).ToList();
-
-            var sources = ChannelManager.GetChannelItemMediaSources(Id.ToString("N"), false, CancellationToken.None)
-                    .Result.ToList();
+            var sources = ChannelManager.GetStaticMediaSources(this, false, CancellationToken.None)
+                       .Result.ToList();
 
             if (sources.Count > 0)
             {
                 return sources;
             }
 
-            list.InsertRange(0, sources);
+            var list = base.GetMediaSources(enablePathSubstitution).ToList();
+
+            foreach (var mediaSource in list)
+            {
+                if (string.IsNullOrWhiteSpace(mediaSource.Path))
+                {
+                    mediaSource.Type = MediaSourceType.Placeholder;
+                }
+            }
 
             return list;
         }

+ 5 - 1
MediaBrowser.Controller/Channels/ChannelMediaInfo.cs

@@ -38,6 +38,7 @@ namespace MediaBrowser.Controller.Channels
         public string Id { get; set; }
 
         public bool ReadAtNativeFramerate { get; set; }
+        public bool SupportsDirectPlay { get; set; }
 
         public ChannelMediaInfo()
         {
@@ -45,6 +46,7 @@ namespace MediaBrowser.Controller.Channels
 
             // This is most common
             Protocol = MediaProtocol.Http;
+            SupportsDirectPlay = true;
         }
 
         public MediaSourceInfo ToMediaSource()
@@ -62,7 +64,9 @@ namespace MediaBrowser.Controller.Channels
                 RunTimeTicks = RunTimeTicks,
                 Name = id,
                 Id = id,
-                ReadAtNativeFramerate = ReadAtNativeFramerate
+                ReadAtNativeFramerate = ReadAtNativeFramerate,
+                SupportsDirectStream = Protocol == MediaProtocol.File || Protocol == MediaProtocol.Http,
+                SupportsDirectPlay = SupportsDirectPlay
             };
 
             var bitrate = (AudioBitrate ?? 0) + (VideoBitrate ?? 0);

+ 11 - 5
MediaBrowser.Controller/Channels/ChannelVideoItem.cs

@@ -90,17 +90,23 @@ namespace MediaBrowser.Controller.Channels
 
         public override IEnumerable<MediaSourceInfo> GetMediaSources(bool enablePathSubstitution)
         {
-            var list = base.GetMediaSources(enablePathSubstitution).ToList();
-
-            var sources = ChannelManager.GetChannelItemMediaSources(Id.ToString("N"), false, CancellationToken.None)
-                    .Result.ToList();
+            var sources = ChannelManager.GetStaticMediaSources(this, false, CancellationToken.None)
+                       .Result.ToList();
 
             if (sources.Count > 0)
             {
                 return sources;
             }
 
-            list.InsertRange(0, sources);
+            var list = base.GetMediaSources(enablePathSubstitution).ToList();
+
+            foreach (var mediaSource in list)
+            {
+                if (string.IsNullOrWhiteSpace(mediaSource.Path))
+                {
+                    mediaSource.Type = MediaSourceType.Placeholder;
+                }
+            }
 
             return list;
         }

+ 3 - 3
MediaBrowser.Controller/Channels/IChannelManager.cs

@@ -112,11 +112,11 @@ namespace MediaBrowser.Controller.Channels
         /// <summary>
         /// Gets the channel item media sources.
         /// </summary>
-        /// <param name="id">The identifier.</param>
-        /// <param name="includeDynamicSources">if set to <c>true</c> [include dynamic sources].</param>
+        /// <param name="item">The item.</param>
+        /// <param name="includeCachedVersions">if set to <c>true</c> [include cached versions].</param>
         /// <param name="cancellationToken">The cancellation token.</param>
         /// <returns>Task{IEnumerable{MediaSourceInfo}}.</returns>
-        Task<IEnumerable<MediaSourceInfo>> GetChannelItemMediaSources(string id, bool includeDynamicSources, CancellationToken cancellationToken);
+        Task<IEnumerable<MediaSourceInfo>> GetStaticMediaSources(IChannelMediaItem item, bool includeCachedVersions, CancellationToken cancellationToken);
 
         /// <summary>
         /// Gets the channel folder.

+ 0 - 29
MediaBrowser.Controller/Entities/BaseItem.cs

@@ -1219,18 +1219,6 @@ namespace MediaBrowser.Controller.Entities
 
         private BaseItem FindLinkedChild(LinkedChild info)
         {
-            if (!string.IsNullOrWhiteSpace(info.ItemName))
-            {
-                if (string.Equals(info.ItemType, "musicgenre", StringComparison.OrdinalIgnoreCase))
-                {
-                    return LibraryManager.GetMusicGenre(info.ItemName);
-                }
-                if (string.Equals(info.ItemType, "musicartist", StringComparison.OrdinalIgnoreCase))
-                {
-                    return LibraryManager.GetArtist(info.ItemName);
-                }
-            }
-
             if (!string.IsNullOrEmpty(info.Path))
             {
                 var itemByPath = LibraryManager.RootFolder.FindByPath(info.Path);
@@ -1243,23 +1231,6 @@ namespace MediaBrowser.Controller.Entities
                 return itemByPath;
             }
 
-            if (!string.IsNullOrWhiteSpace(info.ItemName) && !string.IsNullOrWhiteSpace(info.ItemType))
-            {
-                return LibraryManager.RootFolder.GetRecursiveChildren(i =>
-                {
-                    if (string.Equals(i.Name, info.ItemName, StringComparison.OrdinalIgnoreCase))
-                    {
-                        if (string.Equals(i.GetType().Name, info.ItemType, StringComparison.OrdinalIgnoreCase))
-                        {
-                            return true;
-                        }
-                    }
-
-                    return false;
-
-                }).FirstOrDefault();
-            }
-
             return null;
         }
 

+ 2 - 15
MediaBrowser.Controller/Entities/Folder.cs

@@ -334,22 +334,9 @@ namespace MediaBrowser.Controller.Entities
         {
             if (this is ICollectionFolder && !(this is BasePluginFolder))
             {
-                if (user.Policy.BlockedMediaFolders != null)
+                if (!user.Policy.EnableAllFolders && !user.Policy.EnabledFolders.Contains(Id.ToString("N"), StringComparer.OrdinalIgnoreCase))
                 {
-                    if (user.Policy.BlockedMediaFolders.Contains(Id.ToString("N"), StringComparer.OrdinalIgnoreCase) ||
-
-                        // Backwards compatibility
-                        user.Policy.BlockedMediaFolders.Contains(Name, StringComparer.OrdinalIgnoreCase))
-                    {
-                        return false;
-                    }
-                }
-                else
-                {
-                    if (!user.Policy.EnableAllFolders && !user.Policy.EnabledFolders.Contains(Id.ToString("N"), StringComparer.OrdinalIgnoreCase))
-                    {
-                        return false;
-                    }
+                    return false;
                 }
             }
 

+ 0 - 3
MediaBrowser.Controller/Entities/LinkedChild.cs

@@ -9,9 +9,6 @@ namespace MediaBrowser.Controller.Entities
         public string Path { get; set; }
         public LinkedChildType Type { get; set; }
 
-        public string ItemName { get; set; }
-        public string ItemType { get; set; }
-
         [IgnoreDataMember]
         public string Id { get; set; }
 

+ 2 - 1
MediaBrowser.Controller/Entities/Video.cs

@@ -501,7 +501,8 @@ namespace MediaBrowser.Controller.Entities
                 Formats = (i.FormatName ?? string.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).ToList(),
                 Timestamp = i.Timestamp,
                 Type = type,
-                PlayableStreamFileNames = i.PlayableStreamFileNames.ToList()
+                PlayableStreamFileNames = i.PlayableStreamFileNames.ToList(),
+                SupportsDirectStream = i.VideoType == VideoType.VideoFile
             };
 
             if (i.IsShortcut)

+ 36 - 2
MediaBrowser.Controller/Library/IMediaSourceManager.cs

@@ -2,6 +2,7 @@
 using MediaBrowser.Controller.Persistence;
 using MediaBrowser.Model.Dto;
 using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.MediaInfo;
 using System;
 using System.Collections.Generic;
 using System.Threading;
@@ -62,8 +63,8 @@ namespace MediaBrowser.Controller.Library
         /// <param name="enablePathSubstitution">if set to <c>true</c> [enable path substitution].</param>
         /// <param name="user">The user.</param>
         /// <returns>IEnumerable&lt;MediaSourceInfo&gt;.</returns>
-        IEnumerable<MediaSourceInfo> GetStaticMediaSources(IHasMediaSources item, bool enablePathSubstitution, User user);
-
+        IEnumerable<MediaSourceInfo> GetStaticMediaSources(IHasMediaSources item, bool enablePathSubstitution, User user = null);
+        
         /// <summary>
         /// Gets the static media source.
         /// </summary>
@@ -72,5 +73,38 @@ namespace MediaBrowser.Controller.Library
         /// <param name="enablePathSubstitution">if set to <c>true</c> [enable path substitution].</param>
         /// <returns>MediaSourceInfo.</returns>
         MediaSourceInfo GetStaticMediaSource(IHasMediaSources item, string mediaSourceId, bool enablePathSubstitution);
+
+        /// <summary>
+        /// Opens the media source.
+        /// </summary>
+        /// <param name="request">The request.</param>
+        /// <param name="enableAutoClose">if set to <c>true</c> [enable automatic close].</param>
+        /// <param name="cancellationToken">The cancellation token.</param>
+        /// <returns>Task&lt;MediaSourceInfo&gt;.</returns>
+        Task<LiveStreamResponse> OpenLiveStream(LiveStreamRequest request, bool enableAutoClose, CancellationToken cancellationToken);
+
+        /// <summary>
+        /// Gets the live stream.
+        /// </summary>
+        /// <param name="id">The identifier.</param>
+        /// <param name="cancellationToken">The cancellation token.</param>
+        /// <returns>Task&lt;MediaSourceInfo&gt;.</returns>
+        Task<MediaSourceInfo> GetLiveStream(string id, CancellationToken cancellationToken);
+        
+        /// <summary>
+        /// Pings the media source.
+        /// </summary>
+        /// <param name="id">The live stream identifier.</param>
+        /// <param name="cancellationToken">The cancellation token.</param>
+        /// <returns>Task.</returns>
+        Task PingLiveStream(string id, CancellationToken cancellationToken);
+
+        /// <summary>
+        /// Closes the media source.
+        /// </summary>
+        /// <param name="id">The live stream identifier.</param>
+        /// <param name="cancellationToken">The cancellation token.</param>
+        /// <returns>Task.</returns>
+        Task CloseLiveStream(string id, CancellationToken cancellationToken);
     }
 }

+ 16 - 0
MediaBrowser.Controller/Library/IMediaSourceProvider.cs

@@ -15,5 +15,21 @@ namespace MediaBrowser.Controller.Library
         /// <param name="cancellationToken">The cancellation token.</param>
         /// <returns>Task&lt;IEnumerable&lt;MediaSourceInfo&gt;&gt;.</returns>
         Task<IEnumerable<MediaSourceInfo>> GetMediaSources(IHasMediaSources item, CancellationToken cancellationToken);
+
+        /// <summary>
+        /// Opens the media source.
+        /// </summary>
+        /// <param name="openToken">The open token.</param>
+        /// <param name="cancellationToken">The cancellation token.</param>
+        /// <returns>Task&lt;MediaSourceInfo&gt;.</returns>
+        Task<MediaSourceInfo> OpenMediaSource(string openToken, CancellationToken cancellationToken);
+
+        /// <summary>
+        /// Closes the media source.
+        /// </summary>
+        /// <param name="liveStreamId">The live stream identifier.</param>
+        /// <param name="cancellationToken">The cancellation token.</param>
+        /// <returns>Task.</returns>
+        Task CloseMediaSource(string liveStreamId, CancellationToken cancellationToken);
     }
 }

+ 3 - 1
MediaBrowser.Controller/LiveTv/ILiveTvItem.cs

@@ -1,8 +1,10 @@
-
+using System;
+
 namespace MediaBrowser.Controller.LiveTv
 {
     public interface ILiveTvItem
     {
+        Guid Id { get; }
         string ServiceName { get; set; }
     }
 }

+ 16 - 0
MediaBrowser.Controller/LiveTv/ILiveTvManager.cs

@@ -301,5 +301,21 @@ namespace MediaBrowser.Controller.LiveTv
         /// <param name="cancellationToken">The cancellation token.</param>
         /// <returns>Task&lt;QueryResult&lt;BaseItem&gt;&gt;.</returns>
         Task<QueryResult<BaseItem>> GetInternalRecordings(RecordingQuery query, CancellationToken cancellationToken);
+
+        /// <summary>
+        /// Gets the recording media sources.
+        /// </summary>
+        /// <param name="id">The identifier.</param>
+        /// <param name="cancellationToken">The cancellation token.</param>
+        /// <returns>Task&lt;IEnumerable&lt;MediaSourceInfo&gt;&gt;.</returns>
+        Task<IEnumerable<MediaSourceInfo>> GetRecordingMediaSources(string id, CancellationToken cancellationToken);
+
+        /// <summary>
+        /// Gets the channel media sources.
+        /// </summary>
+        /// <param name="id">The identifier.</param>
+        /// <param name="cancellationToken">The cancellation token.</param>
+        /// <returns>Task&lt;IEnumerable&lt;MediaSourceInfo&gt;&gt;.</returns>
+        Task<IEnumerable<MediaSourceInfo>> GetChannelMediaSources(string id, CancellationToken cancellationToken);
     }
 }

+ 19 - 2
MediaBrowser.Controller/LiveTv/LiveTvAudioRecording.cs

@@ -1,10 +1,12 @@
-using System.Runtime.Serialization;
-using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities.Audio;
 using MediaBrowser.Model.Configuration;
+using MediaBrowser.Model.Dto;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Users;
+using System.Collections.Generic;
 using System.Linq;
+using System.Runtime.Serialization;
 
 namespace MediaBrowser.Controller.LiveTv
 {
@@ -99,5 +101,20 @@ namespace MediaBrowser.Controller.LiveTv
         {
             return user.Policy.EnableLiveTvManagement;
         }
+
+        public override IEnumerable<MediaSourceInfo> GetMediaSources(bool enablePathSubstitution)
+        {
+            var list = base.GetMediaSources(enablePathSubstitution).ToList();
+
+            foreach (var mediaSource in list)
+            {
+                if (string.IsNullOrWhiteSpace(mediaSource.Path))
+                {
+                    mediaSource.Type = MediaSourceType.Placeholder;
+                }
+            }
+
+            return list;
+        }
     }
 }

+ 10 - 1
MediaBrowser.Controller/LiveTv/LiveTvChannel.cs

@@ -82,6 +82,15 @@ namespace MediaBrowser.Controller.LiveTv
         /// <value><c>null</c> if [has image] contains no value, <c>true</c> if [has image]; otherwise, <c>false</c>.</value>
         public bool? HasProviderImage { get; set; }
 
+        public override LocationType LocationType
+        {
+            get
+            {
+                // TODO: This should be removed
+                return LocationType.Remote;
+            }
+        }
+
         protected override string CreateSortName()
         {
             double number = 0;
@@ -127,7 +136,7 @@ namespace MediaBrowser.Controller.LiveTv
                 Name = Name,
                 Path = Path,
                 RunTimeTicks = RunTimeTicks,
-                Type = MediaSourceType.Default
+                Type = MediaSourceType.Placeholder
             };
 
             list.Add(info);

+ 6 - 0
MediaBrowser.Controller/LiveTv/LiveTvProgram.cs

@@ -34,6 +34,12 @@ namespace MediaBrowser.Controller.LiveTv
         /// <value>The channel identifier.</value>
         public string ExternalChannelId { get; set; }
 
+        /// <summary>
+        /// Gets or sets the original air date.
+        /// </summary>
+        /// <value>The original air date.</value>
+        public DateTime? OriginalAirDate { get; set; }
+        
         /// <summary>
         /// Gets or sets the type of the channel.
         /// </summary>

+ 20 - 3
MediaBrowser.Controller/LiveTv/LiveTvVideoRecording.cs

@@ -1,9 +1,11 @@
-using System.Runtime.Serialization;
-using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Entities;
 using MediaBrowser.Model.Configuration;
+using MediaBrowser.Model.Dto;
 using MediaBrowser.Model.Entities;
-using System.Linq;
 using MediaBrowser.Model.Users;
+using System.Collections.Generic;
+using System.Linq;
+using System.Runtime.Serialization;
 
 namespace MediaBrowser.Controller.LiveTv
 {
@@ -97,5 +99,20 @@ namespace MediaBrowser.Controller.LiveTv
         {
             return user.Policy.EnableLiveTvManagement;
         }
+
+        public override IEnumerable<MediaSourceInfo> GetMediaSources(bool enablePathSubstitution)
+        {
+            var list = base.GetMediaSources(enablePathSubstitution).ToList();
+
+            foreach (var mediaSource in list)
+            {
+                if (string.IsNullOrWhiteSpace(mediaSource.Path))
+                {
+                    mediaSource.Type = MediaSourceType.Placeholder;
+                }
+            }
+
+            return list;
+        }
     }
 }

+ 2 - 1
MediaBrowser.Controller/MediaBrowser.Controller.csproj

@@ -393,12 +393,13 @@
     <Compile Include="Subtitles\SubtitleDownloadEventArgs.cs" />
     <Compile Include="Subtitles\SubtitleResponse.cs" />
     <Compile Include="Subtitles\SubtitleSearchRequest.cs" />
+    <Compile Include="Sync\IHasDynamicAccess.cs" />
     <Compile Include="Sync\IServerSyncProvider.cs" />
     <Compile Include="Sync\ISyncDataProvider.cs" />
     <Compile Include="Sync\ISyncManager.cs" />
     <Compile Include="Sync\ISyncProvider.cs" />
     <Compile Include="Sync\ISyncRepository.cs" />
-    <Compile Include="Sync\SendFileResult.cs" />
+    <Compile Include="Sync\SyncedFileInfo.cs" />
     <Compile Include="Themes\IAppThemeManager.cs" />
     <Compile Include="Themes\InternalThemeImage.cs" />
     <Compile Include="TV\ITVSeriesManager.cs" />

+ 5 - 3
MediaBrowser.Controller/MediaEncoding/ISubtitleEncoder.cs

@@ -1,4 +1,5 @@
-using System.IO;
+using MediaBrowser.Model.MediaInfo;
+using System.IO;
 using System.Threading;
 using System.Threading.Tasks;
 
@@ -47,8 +48,9 @@ namespace MediaBrowser.Controller.MediaEncoding
         /// Gets the subtitle language encoding parameter.
         /// </summary>
         /// <param name="path">The path.</param>
+        /// <param name="protocol">The protocol.</param>
+        /// <param name="cancellationToken">The cancellation token.</param>
         /// <returns>System.String.</returns>
-        string GetSubtitleFileCharacterSet(string path);
-
+        Task<string> GetSubtitleFileCharacterSet(string path, MediaProtocol protocol, CancellationToken cancellationToken);
     }
 }

+ 73 - 4
MediaBrowser.Controller/MediaEncoding/MediaStreamSelector.cs

@@ -41,8 +41,6 @@ namespace MediaBrowser.Controller.MediaEncoding
             streams = GetSortedStreams(streams, MediaStreamType.Subtitle, preferredLanguages)
                 .ToList();
 
-            var full = streams.Where(s => !s.IsForced);
-
             MediaStream stream = null;
 
             if (mode == SubtitlePlaybackMode.None)
@@ -55,13 +53,13 @@ namespace MediaBrowser.Controller.MediaEncoding
                 // if the audio language is not understood by the user, load their preferred subs, if there are any
                 if (!ContainsOrdinal(preferredLanguages, audioTrackLanguage))
                 {
-                    stream = full.FirstOrDefault(s => ContainsOrdinal(preferredLanguages, s.Language));
+                    stream = streams.Where(s => !s.IsForced).FirstOrDefault(s => ContainsOrdinal(preferredLanguages, s.Language));
                 }
             }
             else if (mode == SubtitlePlaybackMode.Always)
             {
                 // always load the most suitable full subtitles
-                stream = full.FirstOrDefault();
+                stream = streams.FirstOrDefault(s => !s.IsForced);
             }
 
             // load forced subs if we have found no suitable full subtitles
@@ -97,6 +95,77 @@ namespace MediaBrowser.Controller.MediaEncoding
                  .ThenBy(i => i.Index);
         }
 
+        public static void SetSubtitleStreamScores(List<MediaStream> streams,
+            List<string> preferredLanguages,
+            SubtitlePlaybackMode mode,
+            string audioTrackLanguage)
+        {
+            if (mode == SubtitlePlaybackMode.None)
+            {
+                return;
+            }
+
+            streams = GetSortedStreams(streams, MediaStreamType.Subtitle, preferredLanguages)
+                .ToList();
+
+            var filteredStreams = new List<MediaStream>();
+
+            if (mode == SubtitlePlaybackMode.Default)
+            {
+                // if the audio language is not understood by the user, load their preferred subs, if there are any
+                if (!ContainsOrdinal(preferredLanguages, audioTrackLanguage))
+                {
+                    filteredStreams = streams.Where(s => !s.IsForced && ContainsOrdinal(preferredLanguages, s.Language))
+                        .ToList();
+                }
+            }
+            else if (mode == SubtitlePlaybackMode.Always)
+            {
+                // always load the most suitable full subtitles
+                filteredStreams = streams.Where(s => !s.IsForced)
+                    .ToList();
+            }
+
+            // load forced subs if we have found no suitable full subtitles
+            if (filteredStreams.Count == 0)
+            {
+                filteredStreams = streams
+                    .Where(s => s.IsForced && string.Equals(s.Language, audioTrackLanguage, StringComparison.OrdinalIgnoreCase))
+                    .ToList();
+            }
+
+            foreach (var stream in filteredStreams)
+            {
+                stream.Score = GetSubtitleScore(stream, preferredLanguages);
+            }
+        }
+
+        private static int GetSubtitleScore(MediaStream stream, List<string> languagePreferences)
+        {
+            var values = new List<int>();
+
+            var index = languagePreferences.FindIndex(l => string.Equals(stream.Language, l, StringComparison.OrdinalIgnoreCase));
+
+            values.Add(index == -1 ? 0 : 100 - index);
+
+            values.Add(stream.IsDefault ? 1 : 0);
+            values.Add(stream.SupportsExternalStream ? 1 : 0);
+            values.Add(stream.IsTextSubtitleStream ? 1 : 0);
+            values.Add(stream.IsExternal ? 1 : 0);
+
+            values.Reverse();
+            var scale = 1;
+            var score = 0;
+
+            foreach (var value in values)
+            {
+                score += scale * (value + 1);
+                scale *= 10;
+            }
+
+            return score;
+        }
+
         private static int GetBooleanOrderBy(bool value)
         {
             return value ? 0 : 1;

+ 3 - 4
MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs

@@ -1,5 +1,4 @@
-using MediaBrowser.Common.Net;
-using MediaBrowser.Model.Logging;
+using MediaBrowser.Model.Logging;
 using MediaBrowser.Model.Net;
 using System;
 using System.Collections.Generic;
@@ -75,12 +74,12 @@ namespace MediaBrowser.Controller.Net
                 throw new ArgumentNullException("message");
             }
 
-            if (message.MessageType.Equals(Name + "Start", StringComparison.OrdinalIgnoreCase))
+            if (string.Equals(message.MessageType, Name + "Start", StringComparison.OrdinalIgnoreCase))
             {
                 Start(message);
             }
 
-            if (message.MessageType.Equals(Name + "Stop", StringComparison.OrdinalIgnoreCase))
+            if (string.Equals(message.MessageType, Name + "Stop", StringComparison.OrdinalIgnoreCase))
             {
                 Stop(message);
             }

+ 1 - 13
MediaBrowser.Controller/Providers/BaseItemXmlParser.cs

@@ -1404,24 +1404,12 @@ namespace MediaBrowser.Controller.Providers
                 {
                     switch (reader.Name)
                     {
-                        case "Name":
-                            {
-                                linkedItem.ItemName = reader.ReadElementContentAsString();
-                                break;
-                            }
-
                         case "Path":
                             {
                                 linkedItem.Path = reader.ReadElementContentAsString();
                                 break;
                             }
 
-                        case "Type":
-                            {
-                                linkedItem.ItemType = reader.ReadElementContentAsString();
-                                break;
-                            }
-
                         default:
                             reader.Skip();
                             break;
@@ -1435,7 +1423,7 @@ namespace MediaBrowser.Controller.Providers
                 return linkedItem;
             }
 
-            return string.IsNullOrWhiteSpace(linkedItem.ItemName) || string.IsNullOrWhiteSpace(linkedItem.ItemType) ? null : linkedItem;
+            return null;
         }
 
 

+ 18 - 0
MediaBrowser.Controller/Sync/IHasDynamicAccess.cs

@@ -0,0 +1,18 @@
+using MediaBrowser.Model.Sync;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace MediaBrowser.Controller.Sync
+{
+    public interface IHasDynamicAccess
+    {
+        /// <summary>
+        /// Gets the synced file information.
+        /// </summary>
+        /// <param name="remotePath">The remote path.</param>
+        /// <param name="target">The target.</param>
+        /// <param name="cancellationToken">The cancellation token.</param>
+        /// <returns>Task&lt;SyncedFileInfo&gt;.</returns>
+        Task<SyncedFileInfo> GetSyncedFileInfo(string remotePath, SyncTarget target, CancellationToken cancellationToken);
+    }
+}

+ 1 - 10
MediaBrowser.Controller/Sync/IServerSyncProvider.cs

@@ -18,7 +18,7 @@ namespace MediaBrowser.Controller.Sync
         /// <param name="progress">The progress.</param>
         /// <param name="cancellationToken">The cancellation token.</param>
         /// <returns>Task.</returns>
-        Task<SendFileResult> SendFile(Stream stream, string remotePath, SyncTarget target, IProgress<double> progress, CancellationToken cancellationToken);
+        Task<SyncedFileInfo> SendFile(Stream stream, string remotePath, SyncTarget target, IProgress<double> progress, CancellationToken cancellationToken);
 
         /// <summary>
         /// Deletes the file.
@@ -54,14 +54,5 @@ namespace MediaBrowser.Controller.Sync
         /// <param name="target">The target.</param>
         /// <returns>System.String.</returns>
         string GetParentDirectoryPath(string path, SyncTarget target);
-
-        /// <summary>
-        /// Gets the file system entries.
-        /// </summary>
-        /// <param name="path">The path.</param>
-        /// <param name="target">The target.</param>
-        /// <param name="cancellationToken">The cancellation token.</param>
-        /// <returns>Task&lt;List&lt;DeviceFileInfo&gt;&gt;.</returns>
-        Task<List<DeviceFileInfo>> GetFileSystemEntries(string path, SyncTarget target, CancellationToken cancellationToken);
     }
 }

+ 16 - 0
MediaBrowser.Controller/Sync/ISyncDataProvider.cs

@@ -14,6 +14,14 @@ namespace MediaBrowser.Controller.Sync
         /// <returns>Task&lt;List&lt;System.String&gt;&gt;.</returns>
         Task<List<string>> GetServerItemIds(SyncTarget target, string serverId);
 
+        /// <summary>
+        /// Gets the synchronize job item ids.
+        /// </summary>
+        /// <param name="target">The target.</param>
+        /// <param name="serverId">The server identifier.</param>
+        /// <returns>Task&lt;List&lt;System.String&gt;&gt;.</returns>
+        Task<List<string>> GetSyncJobItemIds(SyncTarget target, string serverId);
+        
         /// <summary>
         /// Adds the or update.
         /// </summary>
@@ -46,5 +54,13 @@ namespace MediaBrowser.Controller.Sync
         /// <param name="itemId">The item identifier.</param>
         /// <returns>Task&lt;LocalItem&gt;.</returns>
         Task<List<LocalItem>> GetCachedItems(SyncTarget target, string serverId, string itemId);
+        /// <summary>
+        /// Gets the cached items by synchronize job item identifier.
+        /// </summary>
+        /// <param name="target">The target.</param>
+        /// <param name="serverId">The server identifier.</param>
+        /// <param name="syncJobItemId">The synchronize job item identifier.</param>
+        /// <returns>Task&lt;List&lt;LocalItem&gt;&gt;.</returns>
+        Task<List<LocalItem>> GetCachedItemsBySyncJobItemId(SyncTarget target, string serverId, string syncJobItemId);
     }
 }

+ 12 - 1
MediaBrowser.Controller/Sync/SendFileResult.cs → MediaBrowser.Controller/Sync/SyncedFileInfo.cs

@@ -1,8 +1,9 @@
 using MediaBrowser.Model.MediaInfo;
+using System.Collections.Generic;
 
 namespace MediaBrowser.Controller.Sync
 {
-    public class SendFileResult
+    public class SyncedFileInfo
     {
         /// <summary>
         /// Gets or sets the path.
@@ -14,5 +15,15 @@ namespace MediaBrowser.Controller.Sync
         /// </summary>
         /// <value>The protocol.</value>
         public MediaProtocol Protocol { get; set; }
+        /// <summary>
+        /// Gets or sets the required HTTP headers.
+        /// </summary>
+        /// <value>The required HTTP headers.</value>
+        public Dictionary<string, string> RequiredHttpHeaders { get; set; }
+
+        public SyncedFileInfo()
+        {
+            RequiredHttpHeaders = new Dictionary<string, string>();
+        }
     }
 }

+ 13 - 6
MediaBrowser.Dlna/Didl/DidlBuilder.cs

@@ -124,7 +124,7 @@ namespace MediaBrowser.Dlna.Didl
         {
             if (streamInfo == null)
             {
-                var sources = _user == null ? video.GetMediaSources(true).ToList() : _mediaSourceManager.GetStaticMediaSources(video, true, _user).ToList();
+                var sources = _mediaSourceManager.GetStaticMediaSources(video, true, _user).ToList();
 
                 streamInfo = new StreamBuilder().BuildVideoItem(new VideoOptions
                 {
@@ -158,16 +158,21 @@ namespace MediaBrowser.Dlna.Didl
                 streamInfo.TranscodeSeekInfo,
                 streamInfo.IsTargetAnamorphic,
                 streamInfo.IsTargetCabac,
-                streamInfo.TargetRefFrames);
+                streamInfo.TargetRefFrames,
+                streamInfo.TargetVideoStreamCount,
+                streamInfo.TargetAudioStreamCount);
 
             foreach (var contentFeature in contentFeatureList)
             {
                 AddVideoResource(container, video, deviceId, filter, contentFeature, streamInfo);
             }
 
-            foreach (var subtitle in streamInfo.GetExternalSubtitles(_serverAddress, _accessToken, false))
+            foreach (var subtitle in streamInfo.GetSubtitleProfiles(false, _serverAddress, _accessToken))
             {
-                AddSubtitleElement(container, subtitle);
+                if (subtitle.DeliveryMethod == SubtitleDeliveryMethod.External)
+                {
+                    AddSubtitleElement(container, subtitle);
+                }
             }
         }
 
@@ -280,7 +285,9 @@ namespace MediaBrowser.Dlna.Didl
                 streamInfo.TargetTimestamp,
                 streamInfo.IsTargetAnamorphic,
                 streamInfo.IsTargetCabac,
-                streamInfo.TargetRefFrames);
+                streamInfo.TargetRefFrames,
+                streamInfo.TargetVideoStreamCount,
+                streamInfo.TargetAudioStreamCount);
 
             var filename = url.Substring(0, url.IndexOf('?'));
 
@@ -344,7 +351,7 @@ namespace MediaBrowser.Dlna.Didl
 
             if (streamInfo == null)
             {
-                var sources = _user == null ? audio.GetMediaSources(true).ToList() : _mediaSourceManager.GetStaticMediaSources(audio, true, _user).ToList();
+                var sources = _mediaSourceManager.GetStaticMediaSources(audio, true, _user).ToList();
 
                 streamInfo = new StreamBuilder().BuildAudioItem(new AudioOptions
                {

+ 6 - 4
MediaBrowser.Dlna/PlayTo/PlayToController.cs

@@ -467,16 +467,16 @@ namespace MediaBrowser.Dlna.PlayTo
 
             var profile = _dlnaManager.GetProfile(deviceInfo.ToDeviceIdentification()) ??
                 _dlnaManager.GetDefaultProfile();
-
+            
             var hasMediaSources = item as IHasMediaSources;
             var mediaSources = hasMediaSources != null
-                ? (user == null ? hasMediaSources.GetMediaSources(true) : _mediaSourceManager.GetStaticMediaSources(hasMediaSources, true, user)).ToList()
+                ? (_mediaSourceManager.GetStaticMediaSources(hasMediaSources, true, user)).ToList()
                 : new List<MediaSourceInfo>();
 
             var playlistItem = GetPlaylistItem(item, mediaSources, profile, _session.DeviceId, mediaSourceId, audioStreamIndex, subtitleStreamIndex);
             playlistItem.StreamInfo.StartPositionTicks = startPostionTicks;
 
-            playlistItem.StreamUrl = playlistItem.StreamInfo.ToUrl(_serverAddress, _accessToken);
+            playlistItem.StreamUrl = playlistItem.StreamInfo.ToDlnaUrl(_serverAddress, _accessToken);
 
             var itemXml = new DidlBuilder(profile, user, _imageProcessor, _serverAddress, _accessToken, _userDataManager, _localization, _mediaSourceManager)
                 .GetItemDidl(item, null, _session.DeviceId, new Filter(), playlistItem.StreamInfo);
@@ -526,7 +526,9 @@ namespace MediaBrowser.Dlna.PlayTo
                     streamInfo.TranscodeSeekInfo,
                     streamInfo.IsTargetAnamorphic,
                     streamInfo.IsTargetCabac,
-                    streamInfo.TargetRefFrames);
+                    streamInfo.TargetRefFrames,
+                    streamInfo.TargetVideoStreamCount,
+                    streamInfo.TargetAudioStreamCount);
 
                 return list.FirstOrDefault();
             }

+ 1 - 1
MediaBrowser.LocalMetadata/BaseXmlProvider.cs

@@ -92,7 +92,7 @@ namespace MediaBrowser.LocalMetadata
         {
             get
             {
-                return "Media Browser Xml";
+                return "Emby Xml";
             }
         }
         

+ 5 - 6
MediaBrowser.LocalMetadata/Images/LocalImageProvider.cs

@@ -186,18 +186,17 @@ namespace MediaBrowser.LocalMetadata.Images
                 names.Add("movie");
             }
 
-            foreach (var name in names)
-            {
-                AddImage(files, images, imagePrefix + name, ImageType.Primary);
-            }
-
             var fileNameWithoutExtension = item.FileNameWithoutExtension;
-
             if (!string.IsNullOrEmpty(fileNameWithoutExtension))
             {
                 AddImage(files, images, fileNameWithoutExtension, ImageType.Primary);
             }
 
+            foreach (var name in names)
+            {
+                AddImage(files, images, imagePrefix + name, ImageType.Primary);
+            }
+
             if (!isInMixedFolder)
             {
                 foreach (var name in names)

+ 0 - 5
MediaBrowser.LocalMetadata/Savers/XmlSaverHelpers.cs

@@ -756,11 +756,6 @@ namespace MediaBrowser.LocalMetadata.Savers
             {
                 builder.Append("<" + singularNodeName + ">");
 
-                if (!string.IsNullOrWhiteSpace(link.ItemType))
-                {
-                    builder.Append("<Type>" + SecurityElement.Escape(link.ItemType) + "</Type>");
-                }
-
                 if (!string.IsNullOrWhiteSpace(link.Path))
                 {
                     builder.Append("<Path>" + SecurityElement.Escape((link.Path)) + "</Path>");

+ 1 - 1
MediaBrowser.MediaEncoding/Encoder/AudioEncoder.cs

@@ -14,7 +14,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
 {
     public class AudioEncoder : BaseEncoder
     {
-        public AudioEncoder(MediaEncoder mediaEncoder, ILogger logger, IServerConfigurationManager configurationManager, IFileSystem fileSystem, ILiveTvManager liveTvManager, IIsoManager isoManager, ILibraryManager libraryManager, IChannelManager channelManager, ISessionManager sessionManager, ISubtitleEncoder subtitleEncoder, IMediaSourceManager mediaSourceManager) : base(mediaEncoder, logger, configurationManager, fileSystem, liveTvManager, isoManager, libraryManager, channelManager, sessionManager, subtitleEncoder, mediaSourceManager)
+        public AudioEncoder(MediaEncoder mediaEncoder, ILogger logger, IServerConfigurationManager configurationManager, IFileSystem fileSystem, IIsoManager isoManager, ILibraryManager libraryManager, ISessionManager sessionManager, ISubtitleEncoder subtitleEncoder, IMediaSourceManager mediaSourceManager) : base(mediaEncoder, logger, configurationManager, fileSystem, isoManager, libraryManager, sessionManager, subtitleEncoder, mediaSourceManager)
         {
         }
 

+ 18 - 65
MediaBrowser.MediaEncoding/Encoder/BaseEncoder.cs

@@ -1,6 +1,5 @@
 using MediaBrowser.Common.Configuration;
 using MediaBrowser.Common.IO;
-using MediaBrowser.Controller.Channels;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.LiveTv;
@@ -14,6 +13,7 @@ using MediaBrowser.Model.Dto;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.IO;
 using MediaBrowser.Model.Logging;
+using MediaBrowser.Model.MediaInfo;
 using System;
 using System.Collections.Generic;
 using System.Diagnostics;
@@ -31,10 +31,8 @@ namespace MediaBrowser.MediaEncoding.Encoder
         protected readonly ILogger Logger;
         protected readonly IServerConfigurationManager ConfigurationManager;
         protected readonly IFileSystem FileSystem;
-        protected readonly ILiveTvManager LiveTvManager;
         protected readonly IIsoManager IsoManager;
         protected readonly ILibraryManager LibraryManager;
-        protected readonly IChannelManager ChannelManager;
         protected readonly ISessionManager SessionManager;
         protected readonly ISubtitleEncoder SubtitleEncoder;
         protected readonly IMediaSourceManager MediaSourceManager;
@@ -45,20 +43,18 @@ namespace MediaBrowser.MediaEncoding.Encoder
             ILogger logger,
             IServerConfigurationManager configurationManager,
             IFileSystem fileSystem,
-            ILiveTvManager liveTvManager,
             IIsoManager isoManager,
             ILibraryManager libraryManager,
-            IChannelManager channelManager,
-            ISessionManager sessionManager, ISubtitleEncoder subtitleEncoder, IMediaSourceManager mediaSourceManager)
+            ISessionManager sessionManager, 
+            ISubtitleEncoder subtitleEncoder, 
+            IMediaSourceManager mediaSourceManager)
         {
             MediaEncoder = mediaEncoder;
             Logger = logger;
             ConfigurationManager = configurationManager;
             FileSystem = fileSystem;
-            LiveTvManager = liveTvManager;
             IsoManager = isoManager;
             LibraryManager = libraryManager;
-            ChannelManager = channelManager;
             SessionManager = sessionManager;
             SubtitleEncoder = subtitleEncoder;
             MediaSourceManager = mediaSourceManager;
@@ -68,7 +64,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
             IProgress<double> progress,
             CancellationToken cancellationToken)
         {
-            var encodingJob = await new EncodingJobFactory(Logger, LiveTvManager, LibraryManager, ChannelManager, MediaSourceManager)
+            var encodingJob = await new EncodingJobFactory(Logger, LibraryManager, MediaSourceManager)
                 .CreateJob(options, IsVideoEncoder, progress, cancellationToken).ConfigureAwait(false);
 
             encodingJob.OutputFilePath = GetOutputFilePath(encodingJob);
@@ -477,53 +473,25 @@ namespace MediaBrowser.MediaEncoding.Encoder
                 state.IsoMount = await IsoManager.Mount(state.MediaPath, cancellationToken).ConfigureAwait(false);
             }
 
-            if (string.IsNullOrEmpty(state.MediaPath))
+            if (state.MediaSource.RequiresOpening)
             {
-                var checkCodecs = false;
-
-                if (string.Equals(state.ItemType, typeof(LiveTvChannel).Name))
+                var liveStreamResponse = await MediaSourceManager.OpenLiveStream(new LiveStreamRequest
                 {
-                    var streamInfo = await LiveTvManager.GetChannelStream(state.Options.ItemId, cancellationToken).ConfigureAwait(false);
-
-                    state.LiveTvStreamId = streamInfo.Id;
-
-                    state.MediaPath = streamInfo.Path;
-                    state.InputProtocol = streamInfo.Protocol;
+                    OpenToken = state.MediaSource.OpenToken
 
-                    await Task.Delay(1500, cancellationToken).ConfigureAwait(false);
+                }, false, cancellationToken).ConfigureAwait(false);
 
-                    AttachMediaStreamInfo(state, streamInfo, state.Options);
-                    checkCodecs = true;
-                }
+                AttachMediaStreamInfo(state, liveStreamResponse.MediaSource, state.Options);
 
-                else if (string.Equals(state.ItemType, typeof(LiveTvVideoRecording).Name) ||
-                    string.Equals(state.ItemType, typeof(LiveTvAudioRecording).Name))
+                if (state.IsVideoRequest)
                 {
-                    var streamInfo = await LiveTvManager.GetRecordingStream(state.Options.ItemId, cancellationToken).ConfigureAwait(false);
-
-                    state.LiveTvStreamId = streamInfo.Id;
-
-                    state.MediaPath = streamInfo.Path;
-                    state.InputProtocol = streamInfo.Protocol;
-
-                    await Task.Delay(1500, cancellationToken).ConfigureAwait(false);
-
-                    AttachMediaStreamInfo(state, streamInfo, state.Options);
-                    checkCodecs = true;
+                    EncodingJobFactory.TryStreamCopy(state, state.Options);
                 }
+            }
 
-                if (state.IsVideoRequest && checkCodecs)
-                {
-                    if (state.VideoStream != null && EncodingJobFactory.CanStreamCopyVideo(state.Options, state.VideoStream))
-                    {
-                        state.OutputVideoCodec = "copy";
-                    }
-
-                    if (state.AudioStream != null && EncodingJobFactory.CanStreamCopyAudio(state.Options, state.AudioStream, state.SupportedAudioCodecs))
-                    {
-                        state.OutputAudioCodec = "copy";
-                    }
-                }
+            if (state.MediaSource.BufferMs.HasValue)
+            {
+                await Task.Delay(state.MediaSource.BufferMs.Value, cancellationToken).ConfigureAwait(false);
             }
         }
 
@@ -531,22 +499,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
           MediaSourceInfo mediaSource,
           EncodingJobOptions videoRequest)
         {
-            state.InputProtocol = mediaSource.Protocol;
-            state.MediaPath = mediaSource.Path;
-            state.RunTimeTicks = mediaSource.RunTimeTicks;
-            state.RemoteHttpHeaders = mediaSource.RequiredHttpHeaders;
-            state.InputBitrate = mediaSource.Bitrate;
-            state.InputFileSize = mediaSource.Size;
-            state.ReadInputAtNativeFramerate = mediaSource.ReadAtNativeFramerate;
-
-            if (state.ReadInputAtNativeFramerate)
-            {
-                state.OutputAudioSync = "1000";
-                state.InputVideoSync = "-1";
-                state.InputAudioSync = "1";
-            }
-
-            EncodingJobFactory.AttachMediaStreamInfo(state, mediaSource.MediaStreams, videoRequest);
+            EncodingJobFactory.AttachMediaStreamInfo(state, mediaSource, videoRequest);
         }
 
         /// <summary>
@@ -998,7 +951,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
 
                 if (!string.IsNullOrEmpty(state.SubtitleStream.Language))
                 {
-                    var charenc = SubtitleEncoder.GetSubtitleFileCharacterSet(subtitlePath);
+                    var charenc = SubtitleEncoder.GetSubtitleFileCharacterSet(subtitlePath, state.MediaSource.Protocol, CancellationToken.None).Result;
 
                     if (!string.IsNullOrEmpty(charenc))
                     {

+ 45 - 9
MediaBrowser.MediaEncoding/Encoder/EncodingJob.cs

@@ -1,7 +1,8 @@
-using MediaBrowser.Controller.LiveTv;
+using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.MediaEncoding;
 using MediaBrowser.Model.Dlna;
 using MediaBrowser.Model.Drawing;
+using MediaBrowser.Model.Dto;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.IO;
 using MediaBrowser.Model.Logging;
@@ -26,7 +27,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
 
         public EncodingJobOptions Options { get; set; }
         public string InputContainer { get; set; }
-        public List<MediaStream> AllMediaStreams { get; set; }
+        public MediaSourceInfo MediaSource { get; set; }
         public MediaStream AudioStream { get; set; }
         public MediaStream VideoStream { get; set; }
         public MediaStream SubtitleStream { get; set; }
@@ -76,12 +77,12 @@ namespace MediaBrowser.MediaEncoding.Encoder
         }
 
         private readonly ILogger _logger;
-        private readonly ILiveTvManager _liveTvManager;
+        private readonly IMediaSourceManager _mediaSourceManager;
 
-        public EncodingJob(ILogger logger, ILiveTvManager liveTvManager)
+        public EncodingJob(ILogger logger, IMediaSourceManager mediaSourceManager)
         {
             _logger = logger;
-            _liveTvManager = liveTvManager;
+            _mediaSourceManager = mediaSourceManager;
             Id = Guid.NewGuid().ToString("N");
 
             RemoteHttpHeaders = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
@@ -89,7 +90,6 @@ namespace MediaBrowser.MediaEncoding.Encoder
             SupportedAudioCodecs = new List<string>();
             PlayableStreamFileNames = new List<string>();
             RemoteHttpHeaders = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
-            AllMediaStreams = new List<MediaStream>();
             TaskCompletionSource = new TaskCompletionSource<bool>();
         }
 
@@ -136,15 +136,15 @@ namespace MediaBrowser.MediaEncoding.Encoder
 
         private async void DisposeLiveStream()
         {
-            if (!string.IsNullOrEmpty(LiveTvStreamId))
+            if (MediaSource.RequiresClosing)
             {
                 try
                 {
-                    await _liveTvManager.CloseLiveStream(LiveTvStreamId, CancellationToken.None).ConfigureAwait(false);
+                    await _mediaSourceManager.CloseLiveStream(MediaSource.LiveStreamId, CancellationToken.None).ConfigureAwait(false);
                 }
                 catch (Exception ex)
                 {
-                    _logger.ErrorException("Error closing live tv stream", ex);
+                    _logger.ErrorException("Error closing media source", ex);
                 }
             }
         }
@@ -394,6 +394,42 @@ namespace MediaBrowser.MediaEncoding.Encoder
             }
         }
 
+        public int? TargetVideoStreamCount
+        {
+            get
+            {
+                if (Options.Static)
+                {
+                    return GetMediaStreamCount(MediaStreamType.Video, int.MaxValue);
+                }
+                return GetMediaStreamCount(MediaStreamType.Video, 1);
+            }
+        }
+
+        public int? TargetAudioStreamCount
+        {
+            get
+            {
+                if (Options.Static)
+                {
+                    return GetMediaStreamCount(MediaStreamType.Audio, int.MaxValue);
+                }
+                return GetMediaStreamCount(MediaStreamType.Audio, 1);
+            }
+        }
+
+        private int? GetMediaStreamCount(MediaStreamType type, int limit)
+        {
+            var count = MediaSource.GetStreamCount(type);
+
+            if (count.HasValue)
+            {
+                count = Math.Min(count.Value, limit);
+            }
+
+            return count;
+        }
+
         public void ReportTranscodingProgress(TimeSpan? transcodingPosition, float? framerate, double? percentComplete, long? bytesTranscoded)
         {
             var ticks = transcodingPosition.HasValue ? transcodingPosition.Value.Ticks : (long?)null;

+ 69 - 112
MediaBrowser.MediaEncoding/Encoder/EncodingJobFactory.cs

@@ -1,9 +1,10 @@
-using MediaBrowser.Controller.Channels;
+using System.IO;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.LiveTv;
 using MediaBrowser.Controller.MediaEncoding;
 using MediaBrowser.Model.Dlna;
+using MediaBrowser.Model.Dto;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Logging;
 using MediaBrowser.Model.MediaInfo;
@@ -19,19 +20,15 @@ namespace MediaBrowser.MediaEncoding.Encoder
     public class EncodingJobFactory
     {
         private readonly ILogger _logger;
-        private readonly ILiveTvManager _liveTvManager;
         private readonly ILibraryManager _libraryManager;
-        private readonly IChannelManager _channelManager;
         private readonly IMediaSourceManager _mediaSourceManager;
 
         protected static readonly CultureInfo UsCulture = new CultureInfo("en-US");
         
-        public EncodingJobFactory(ILogger logger, ILiveTvManager liveTvManager, ILibraryManager libraryManager, IChannelManager channelManager, IMediaSourceManager mediaSourceManager)
+        public EncodingJobFactory(ILogger logger, ILibraryManager libraryManager, IMediaSourceManager mediaSourceManager)
         {
             _logger = logger;
-            _liveTvManager = liveTvManager;
             _libraryManager = libraryManager;
-            _channelManager = channelManager;
             _mediaSourceManager = mediaSourceManager;
         }
 
@@ -42,9 +39,9 @@ namespace MediaBrowser.MediaEncoding.Encoder
             if (string.IsNullOrEmpty(request.AudioCodec))
             {
                 request.AudioCodec = InferAudioCodec(request.OutputContainer);
-            } 
-            
-            var state = new EncodingJob(_logger, _liveTvManager)
+            }
+
+            var state = new EncodingJob(_logger, _mediaSourceManager)
             {
                 Options = options,
                 IsVideoRequest = isVideoRequest,
@@ -58,106 +55,17 @@ namespace MediaBrowser.MediaEncoding.Encoder
             }
 
             var item = _libraryManager.GetItemById(request.ItemId);
-
-            List<MediaStream> mediaStreams = null;
-
             state.ItemType = item.GetType().Name;
 
-            if (item is ILiveTvRecording)
-            {
-                var recording = await _liveTvManager.GetInternalRecording(request.ItemId, cancellationToken).ConfigureAwait(false);
-
-                state.VideoType = VideoType.VideoFile;
-                state.IsInputVideo = string.Equals(recording.MediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase);
-
-                var path = recording.RecordingInfo.Path;
-                var mediaUrl = recording.RecordingInfo.Url;
-                
-                var source = string.IsNullOrEmpty(request.MediaSourceId)
-                    ? recording.GetMediaSources(false).First()
-                    : _mediaSourceManager.GetStaticMediaSource(recording, request.MediaSourceId, false);
-
-                mediaStreams = source.MediaStreams;
-
-                // Just to prevent this from being null and causing other methods to fail
-                state.MediaPath = string.Empty;
-
-                if (!string.IsNullOrEmpty(path))
-                {
-                    state.MediaPath = path;
-                    state.InputProtocol = MediaProtocol.File;
-                }
-                else if (!string.IsNullOrEmpty(mediaUrl))
-                {
-                    state.MediaPath = mediaUrl;
-                    state.InputProtocol = MediaProtocol.Http;
-                }
-
-                state.RunTimeTicks = recording.RunTimeTicks;
-                state.DeInterlace = true;
-                state.OutputAudioSync = "1000";
-                state.InputVideoSync = "-1";
-                state.InputAudioSync = "1";
-                state.InputContainer = recording.Container;
-                state.ReadInputAtNativeFramerate = source.ReadAtNativeFramerate;
-            }
-            else if (item is LiveTvChannel)
-            {
-                var channel = _liveTvManager.GetInternalChannel(request.ItemId);
-
-                state.VideoType = VideoType.VideoFile;
-                state.IsInputVideo = string.Equals(channel.MediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase);
-                mediaStreams = new List<MediaStream>();
-
-                state.DeInterlace = true;
-
-                // Just to prevent this from being null and causing other methods to fail
-                state.MediaPath = string.Empty;
-            }
-            else
-            {
-                var mediaSources = await _mediaSourceManager.GetPlayackMediaSources(request.ItemId, false, cancellationToken).ConfigureAwait(false);
+            state.IsInputVideo = string.Equals(item.MediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase);
 
-                var mediaSource = string.IsNullOrEmpty(request.MediaSourceId)
-                    ? mediaSources.First()
-                    : mediaSources.First(i => string.Equals(i.Id, request.MediaSourceId));
-
-                mediaStreams = mediaSource.MediaStreams;
-
-                state.MediaPath = mediaSource.Path;
-                state.InputProtocol = mediaSource.Protocol;
-                state.InputContainer = mediaSource.Container;
-                state.InputFileSize = mediaSource.Size;
-                state.InputBitrate = mediaSource.Bitrate;
-                state.ReadInputAtNativeFramerate = mediaSource.ReadAtNativeFramerate;
-                state.RunTimeTicks = mediaSource.RunTimeTicks;
-                state.RemoteHttpHeaders = mediaSource.RequiredHttpHeaders;
-
-                var video = item as Video;
-
-                if (video != null)
-                {
-                    state.IsInputVideo = true;
-
-                    if (mediaSource.VideoType.HasValue)
-                    {
-                        state.VideoType = mediaSource.VideoType.Value;
-                    }
+            var mediaSources = await _mediaSourceManager.GetPlayackMediaSources(request.ItemId, false, cancellationToken).ConfigureAwait(false);
 
-                    state.IsoType = mediaSource.IsoType;
+            var mediaSource = string.IsNullOrEmpty(request.MediaSourceId)
+               ? mediaSources.First()
+               : mediaSources.First(i => string.Equals(i.Id, request.MediaSourceId));
 
-                    state.PlayableStreamFileNames = mediaSource.PlayableStreamFileNames.ToList();
-
-                    if (mediaSource.Timestamp.HasValue)
-                    {
-                        state.InputTimestamp = mediaSource.Timestamp.Value;
-                    }
-                }
-
-                state.RunTimeTicks = mediaSource.RunTimeTicks;
-            }
-
-            AttachMediaStreamInfo(state, mediaStreams, request);
+            AttachMediaStreamInfo(state, mediaSource, options);
 
             state.OutputAudioBitrate = GetAudioBitrateParam(request, state.AudioStream);
             state.OutputAudioSampleRate = request.AudioSampleRate;
@@ -185,26 +93,73 @@ namespace MediaBrowser.MediaEncoding.Encoder
 
             ApplyDeviceProfileSettings(state);
 
-            if (isVideoRequest)
+            TryStreamCopy(state, request);
+
+            return state;
+        }
+
+        internal static void TryStreamCopy(EncodingJob state,
+            EncodingJobOptions videoRequest)
+        {
+            if (state.IsVideoRequest)
             {
-                if (state.VideoStream != null && CanStreamCopyVideo(request, state.VideoStream))
+                if (state.VideoStream != null && CanStreamCopyVideo(videoRequest, state.VideoStream))
                 {
                     state.OutputVideoCodec = "copy";
                 }
 
-                if (state.AudioStream != null && CanStreamCopyAudio(request, state.AudioStream, state.SupportedAudioCodecs))
+                if (state.AudioStream != null && CanStreamCopyAudio(videoRequest, state.AudioStream, state.SupportedAudioCodecs))
                 {
                     state.OutputAudioCodec = "copy";
                 }
             }
-
-            return state;
         }
 
         internal static void AttachMediaStreamInfo(EncodingJob state,
-            List<MediaStream> mediaStreams,
+            MediaSourceInfo mediaSource,
             EncodingJobOptions videoRequest)
         {
+            state.MediaPath = mediaSource.Path;
+            state.InputProtocol = mediaSource.Protocol;
+            state.InputContainer = mediaSource.Container;
+            state.InputFileSize = mediaSource.Size;
+            state.InputBitrate = mediaSource.Bitrate;
+            state.ReadInputAtNativeFramerate = mediaSource.ReadAtNativeFramerate;
+            state.RunTimeTicks = mediaSource.RunTimeTicks;
+            state.RemoteHttpHeaders = mediaSource.RequiredHttpHeaders;
+
+            if (mediaSource.VideoType.HasValue)
+            {
+                state.VideoType = mediaSource.VideoType.Value;
+            }
+
+            state.IsoType = mediaSource.IsoType;
+
+            state.PlayableStreamFileNames = mediaSource.PlayableStreamFileNames.ToList();
+
+            if (mediaSource.Timestamp.HasValue)
+            {
+                state.InputTimestamp = mediaSource.Timestamp.Value;
+            }
+
+            state.InputProtocol = mediaSource.Protocol;
+            state.MediaPath = mediaSource.Path;
+            state.RunTimeTicks = mediaSource.RunTimeTicks;
+            state.RemoteHttpHeaders = mediaSource.RequiredHttpHeaders;
+            state.InputBitrate = mediaSource.Bitrate;
+            state.InputFileSize = mediaSource.Size;
+            state.ReadInputAtNativeFramerate = mediaSource.ReadAtNativeFramerate;
+
+            if (state.ReadInputAtNativeFramerate ||
+                mediaSource.Protocol == MediaProtocol.File && string.Equals(mediaSource.Container, "wtv", StringComparison.OrdinalIgnoreCase))
+            {
+                state.OutputAudioSync = "1000";
+                state.InputVideoSync = "-1";
+                state.InputAudioSync = "1";
+            }
+
+            var mediaStreams = mediaSource.MediaStreams;
+
             if (videoRequest != null)
             {
                 if (string.IsNullOrEmpty(videoRequest.VideoCodec))
@@ -233,7 +188,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
                 state.AudioStream = GetMediaStream(mediaStreams, null, MediaStreamType.Audio, true);
             }
 
-            state.AllMediaStreams = mediaStreams;
+            state.MediaSource = mediaSource;
         }
 
         /// <summary>
@@ -771,7 +726,9 @@ namespace MediaBrowser.MediaEncoding.Encoder
                 state.TargetTimestamp,
                 state.IsTargetAnamorphic,
                 state.IsTargetCabac,
-                state.TargetRefFrames);
+                state.TargetRefFrames,
+                state.TargetVideoStreamCount,
+                state.TargetAudioStreamCount);
 
             if (mediaProfile != null)
             {

+ 0 - 4
MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs

@@ -577,10 +577,8 @@ namespace MediaBrowser.MediaEncoding.Encoder
                 _logger,
                 ConfigurationManager,
                 FileSystem,
-                LiveTvManager,
                 IsoManager,
                 LibraryManager,
-                ChannelManager,
                 SessionManager,
                 SubtitleEncoder(),
                 MediaSourceManager())
@@ -599,10 +597,8 @@ namespace MediaBrowser.MediaEncoding.Encoder
                 _logger,
                 ConfigurationManager,
                 FileSystem,
-                LiveTvManager,
                 IsoManager,
                 LibraryManager,
-                ChannelManager,
                 SessionManager,
                 SubtitleEncoder(),
                 MediaSourceManager())

+ 7 - 4
MediaBrowser.MediaEncoding/Encoder/VideoEncoder.cs

@@ -15,7 +15,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
 {
     public class VideoEncoder : BaseEncoder
     {
-        public VideoEncoder(MediaEncoder mediaEncoder, ILogger logger, IServerConfigurationManager configurationManager, IFileSystem fileSystem, ILiveTvManager liveTvManager, IIsoManager isoManager, ILibraryManager libraryManager, IChannelManager channelManager, ISessionManager sessionManager, ISubtitleEncoder subtitleEncoder, IMediaSourceManager mediaSourceManager) : base(mediaEncoder, logger, configurationManager, fileSystem, liveTvManager, isoManager, libraryManager, channelManager, sessionManager, subtitleEncoder, mediaSourceManager)
+        public VideoEncoder(MediaEncoder mediaEncoder, ILogger logger, IServerConfigurationManager configurationManager, IFileSystem fileSystem, IIsoManager isoManager, ILibraryManager libraryManager, ISessionManager sessionManager, ISubtitleEncoder subtitleEncoder, IMediaSourceManager mediaSourceManager) : base(mediaEncoder, logger, configurationManager, fileSystem, isoManager, libraryManager, sessionManager, subtitleEncoder, mediaSourceManager)
         {
         }
 
@@ -73,10 +73,13 @@ namespace MediaBrowser.MediaEncoding.Encoder
                     args;
             }
 
-            var keyFrameArg = string.Format(" -force_key_frames expr:gte(t,n_forced*{0})",
-                5.ToString(UsCulture));
+            if (state.Options.Context == EncodingContext.Streaming)
+            {
+                var keyFrameArg = string.Format(" -force_key_frames expr:gte(t,n_forced*{0})",
+                    5.ToString(UsCulture));
 
-            args += keyFrameArg;
+                args += keyFrameArg;
+            }
 
             var hasGraphicalSubs = state.SubtitleStream != null && !state.SubtitleStream.IsTextSubtitleStream;
 

+ 71 - 38
MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs

@@ -1,6 +1,7 @@
 using MediaBrowser.Common.Configuration;
 using MediaBrowser.Common.Extensions;
 using MediaBrowser.Common.IO;
+using MediaBrowser.Common.Net;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.MediaEncoding;
@@ -29,8 +30,10 @@ namespace MediaBrowser.MediaEncoding.Subtitles
         private readonly IFileSystem _fileSystem;
         private readonly IMediaEncoder _mediaEncoder;
         private readonly IJsonSerializer _json;
+        private readonly IHttpClient _httpClient;
+        private readonly IMediaSourceManager _mediaSourceManager;
 
-        public SubtitleEncoder(ILibraryManager libraryManager, ILogger logger, IApplicationPaths appPaths, IFileSystem fileSystem, IMediaEncoder mediaEncoder, IJsonSerializer json)
+        public SubtitleEncoder(ILibraryManager libraryManager, ILogger logger, IApplicationPaths appPaths, IFileSystem fileSystem, IMediaEncoder mediaEncoder, IJsonSerializer json, IHttpClient httpClient, IMediaSourceManager mediaSourceManager)
         {
             _libraryManager = libraryManager;
             _logger = logger;
@@ -38,6 +41,8 @@ namespace MediaBrowser.MediaEncoding.Subtitles
             _fileSystem = fileSystem;
             _mediaEncoder = mediaEncoder;
             _json = json;
+            _httpClient = httpClient;
+            _mediaSourceManager = mediaSourceManager;
         }
 
         private string SubtitleCachePath
@@ -127,9 +132,9 @@ namespace MediaBrowser.MediaEncoding.Subtitles
             int subtitleStreamIndex,
             CancellationToken cancellationToken)
         {
-            var item = (Video)_libraryManager.GetItemById(new Guid(itemId));
+            var mediaSources = await _mediaSourceManager.GetPlayackMediaSources(itemId, false, cancellationToken).ConfigureAwait(false);
 
-            var mediaSource = item.GetMediaSources(false)
+            var mediaSource = mediaSources
                 .First(i => string.Equals(i.Id, mediaSourceId));
 
             var subtitleStream = mediaSource.MediaStreams
@@ -149,20 +154,20 @@ namespace MediaBrowser.MediaEncoding.Subtitles
 
             var fileInfo = await GetReadableFile(mediaSource.Path, inputFiles, mediaSource.Protocol, subtitleStream, cancellationToken).ConfigureAwait(false);
 
-            var stream = await GetSubtitleStream(fileInfo.Item1, fileInfo.Item3).ConfigureAwait(false);
+            var stream = await GetSubtitleStream(fileInfo.Item1, fileInfo.Item2, fileInfo.Item4, cancellationToken).ConfigureAwait(false);
 
-            return new Tuple<Stream, string>(stream, fileInfo.Item2);
+            return new Tuple<Stream, string>(stream, fileInfo.Item3);
         }
 
-        private async Task<Stream> GetSubtitleStream(string path, bool requiresCharset)
+        private async Task<Stream> GetSubtitleStream(string path, MediaProtocol protocol, bool requiresCharset, CancellationToken cancellationToken)
         {
             if (requiresCharset)
             {
-                var charset = GetSubtitleFileCharacterSet(path);
+                var charset = await GetSubtitleFileCharacterSet(path, protocol, cancellationToken).ConfigureAwait(false);
 
                 if (!string.IsNullOrEmpty(charset))
                 {
-                    using (var fs = _fileSystem.GetFileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, true))
+                    using (var fs = await GetStream(path, protocol, cancellationToken).ConfigureAwait(false))
                     {
                         using (var reader = new StreamReader(fs, GetEncoding(charset)))
                         {
@@ -196,7 +201,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
             }
         }
 
-        private async Task<Tuple<string, string, bool>> GetReadableFile(string mediaPath,
+        private async Task<Tuple<string, MediaProtocol, string, bool>> GetReadableFile(string mediaPath,
             string[] inputFiles,
             MediaProtocol protocol,
             MediaStream subtitleStream,
@@ -228,12 +233,12 @@ namespace MediaBrowser.MediaEncoding.Subtitles
                 }
 
                 // Extract    
-                var outputPath = GetSubtitleCachePath(mediaPath, subtitleStream.Index, "." + outputFormat);
+                var outputPath = GetSubtitleCachePath(mediaPath, protocol, subtitleStream.Index, "." + outputFormat);
 
                 await ExtractTextSubtitle(inputFiles, protocol, subtitleStream.Index, outputCodec, outputPath, cancellationToken)
                         .ConfigureAwait(false);
 
-                return new Tuple<string, string, bool>(outputPath, outputFormat, false);
+                return new Tuple<string, MediaProtocol, string, bool>(outputPath, MediaProtocol.File, outputFormat, false);
             }
 
             var currentFormat = (Path.GetExtension(subtitleStream.Path) ?? subtitleStream.Codec)
@@ -242,14 +247,14 @@ namespace MediaBrowser.MediaEncoding.Subtitles
             if (GetReader(currentFormat, false) == null)
             {
                 // Convert    
-                var outputPath = GetSubtitleCachePath(mediaPath, subtitleStream.Index, ".srt");
+                var outputPath = GetSubtitleCachePath(mediaPath, protocol, subtitleStream.Index, ".srt");
 
-                await ConvertTextSubtitleToSrt(subtitleStream.Path, outputPath, cancellationToken).ConfigureAwait(false);
+                await ConvertTextSubtitleToSrt(subtitleStream.Path, protocol, outputPath, cancellationToken).ConfigureAwait(false);
 
-                return new Tuple<string, string, bool>(outputPath, "srt", true);
+                return new Tuple<string, MediaProtocol, string, bool>(outputPath, MediaProtocol.File, "srt", true);
             }
 
-            return new Tuple<string, string, bool>(subtitleStream.Path, currentFormat, true);
+            return new Tuple<string, MediaProtocol, string, bool>(subtitleStream.Path, protocol, currentFormat, true);
         }
 
         private async Task<SubtitleTrackInfo> GetTrackInfo(Stream stream,
@@ -336,10 +341,11 @@ namespace MediaBrowser.MediaEncoding.Subtitles
         /// Converts the text subtitle to SRT.
         /// </summary>
         /// <param name="inputPath">The input path.</param>
+        /// <param name="inputProtocol">The input protocol.</param>
         /// <param name="outputPath">The output path.</param>
         /// <param name="cancellationToken">The cancellation token.</param>
         /// <returns>Task.</returns>
-        public async Task ConvertTextSubtitleToSrt(string inputPath, string outputPath, CancellationToken cancellationToken)
+        private async Task ConvertTextSubtitleToSrt(string inputPath, MediaProtocol inputProtocol, string outputPath, CancellationToken cancellationToken)
         {
             var semaphore = GetLock(outputPath);
 
@@ -349,7 +355,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
             {
                 if (!File.Exists(outputPath))
                 {
-                    await ConvertTextSubtitleToSrtInternal(inputPath, outputPath).ConfigureAwait(false);
+                    await ConvertTextSubtitleToSrtInternal(inputPath, inputProtocol, outputPath, cancellationToken).ConfigureAwait(false);
                 }
             }
             finally
@@ -362,13 +368,17 @@ namespace MediaBrowser.MediaEncoding.Subtitles
         /// Converts the text subtitle to SRT internal.
         /// </summary>
         /// <param name="inputPath">The input path.</param>
+        /// <param name="inputProtocol">The input protocol.</param>
         /// <param name="outputPath">The output path.</param>
+        /// <param name="cancellationToken">The cancellation token.</param>
         /// <returns>Task.</returns>
-        /// <exception cref="System.ArgumentNullException">inputPath
+        /// <exception cref="System.ArgumentNullException">
+        /// inputPath
         /// or
-        /// outputPath</exception>
+        /// outputPath
+        /// </exception>
         /// <exception cref="System.ApplicationException"></exception>
-        private async Task ConvertTextSubtitleToSrtInternal(string inputPath, string outputPath)
+        private async Task ConvertTextSubtitleToSrtInternal(string inputPath, MediaProtocol inputProtocol, string outputPath, CancellationToken cancellationToken)
         {
             if (string.IsNullOrEmpty(inputPath))
             {
@@ -382,7 +392,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
 
             Directory.CreateDirectory(Path.GetDirectoryName(outputPath));
 
-            var encodingParam = GetSubtitleFileCharacterSet(inputPath);
+            var encodingParam = await GetSubtitleFileCharacterSet(inputPath, inputProtocol, cancellationToken).ConfigureAwait(false);
 
             if (!string.IsNullOrEmpty(encodingParam))
             {
@@ -688,32 +698,41 @@ namespace MediaBrowser.MediaEncoding.Subtitles
             }
         }
 
-        private string GetSubtitleCachePath(string mediaPath, int subtitleStreamIndex, string outputSubtitleExtension)
+        private string GetSubtitleCachePath(string mediaPath, MediaProtocol protocol, int subtitleStreamIndex, string outputSubtitleExtension)
         {
-            var ticksParam = string.Empty;
+            if (protocol == MediaProtocol.File)
+            {
+                var ticksParam = string.Empty;
+
+                var date = _fileSystem.GetLastWriteTimeUtc(mediaPath);
 
-            var date = _fileSystem.GetLastWriteTimeUtc(mediaPath);
+                var filename = (mediaPath + "_" + subtitleStreamIndex.ToString(CultureInfo.InvariantCulture) + "_" + date.Ticks.ToString(CultureInfo.InvariantCulture) + ticksParam).GetMD5() + outputSubtitleExtension;
 
-            var filename = (mediaPath + "_" + subtitleStreamIndex.ToString(CultureInfo.InvariantCulture) + "_" + date.Ticks.ToString(CultureInfo.InvariantCulture) + ticksParam).GetMD5() + outputSubtitleExtension;
+                var prefix = filename.Substring(0, 1);
+
+                return Path.Combine(SubtitleCachePath, prefix, filename);
+            }
+            else
+            {
+                var filename = (mediaPath + "_" + subtitleStreamIndex.ToString(CultureInfo.InvariantCulture)).GetMD5() + outputSubtitleExtension;
 
-            var prefix = filename.Substring(0, 1);
+                var prefix = filename.Substring(0, 1);
 
-            return Path.Combine(SubtitleCachePath, prefix, filename);
+                return Path.Combine(SubtitleCachePath, prefix, filename);
+            }
         }
 
-        /// <summary>
-        /// Gets the subtitle language encoding param.
-        /// </summary>
-        /// <param name="path">The path.</param>
-        /// <returns>System.String.</returns>
-        public string GetSubtitleFileCharacterSet(string path)
+        public async Task<string> GetSubtitleFileCharacterSet(string path, MediaProtocol protocol, CancellationToken cancellationToken)
         {
-            if (GetFileEncoding(path).Equals(Encoding.UTF8))
+            if (protocol == MediaProtocol.File)
             {
-                return string.Empty;
+                if (GetFileEncoding(path).Equals(Encoding.UTF8))
+                {
+                    return string.Empty;
+                }
             }
 
-            var charset = DetectCharset(path);
+            var charset = await DetectCharset(path, protocol, cancellationToken).ConfigureAwait(false);
 
             if (!string.IsNullOrWhiteSpace(charset))
             {
@@ -769,11 +788,11 @@ namespace MediaBrowser.MediaEncoding.Subtitles
             }
         }
 
-        private string DetectCharset(string path)
+        private async Task<string> DetectCharset(string path, MediaProtocol protocol, CancellationToken cancellationToken)
         {
             try
             {
-                using (var file = _fileSystem.GetFileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
+                using (var file = await GetStream(path, protocol, cancellationToken).ConfigureAwait(false))
                 {
                     var detector = new CharsetDetector();
                     detector.Feed(file);
@@ -819,5 +838,19 @@ namespace MediaBrowser.MediaEncoding.Subtitles
             // It's ok - anything aside from utf is ok since that's what we're looking for
             return Encoding.Default;
         }
+
+        private async Task<Stream> GetStream(string path, MediaProtocol protocol, CancellationToken cancellationToken)
+        {
+            if (protocol == MediaProtocol.Http)
+            {
+                return await _httpClient.Get(path, cancellationToken).ConfigureAwait(false);
+            }
+            if (protocol == MediaProtocol.File)
+            {
+                return _fileSystem.GetFileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
+            }
+
+            throw new ArgumentOutOfRangeException("protocol");
+        }
     }
 }

+ 14 - 2
MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj

@@ -413,6 +413,9 @@
     <Compile Include="..\MediaBrowser.Model\Dlna\StreamInfo.cs">
       <Link>Dlna\StreamInfo.cs</Link>
     </Compile>
+    <Compile Include="..\MediaBrowser.Model\Dlna\StreamInfoSorter.cs">
+      <Link>Dlna\StreamInfoSorter.cs</Link>
+    </Compile>
     <Compile Include="..\MediaBrowser.Model\Dlna\SubtitleDeliveryMethod.cs">
       <Link>Dlna\SubtitleDeliveryMethod.cs</Link>
     </Compile>
@@ -800,12 +803,21 @@
     <Compile Include="..\MediaBrowser.Model\MediaInfo\IBlurayExaminer.cs">
       <Link>MediaInfo\IBlurayExaminer.cs</Link>
     </Compile>
-    <Compile Include="..\MediaBrowser.Model\MediaInfo\LiveMediaInfoResult.cs">
-      <Link>MediaInfo\LiveMediaInfoResult.cs</Link>
+    <Compile Include="..\MediaBrowser.Model\MediaInfo\LiveStreamRequest.cs">
+      <Link>MediaInfo\LiveStreamRequest.cs</Link>
+    </Compile>
+    <Compile Include="..\MediaBrowser.Model\MediaInfo\LiveStreamResponse.cs">
+      <Link>MediaInfo\LiveStreamResponse.cs</Link>
     </Compile>
     <Compile Include="..\MediaBrowser.Model\MediaInfo\MediaProtocol.cs">
       <Link>MediaInfo\MediaProtocol.cs</Link>
     </Compile>
+    <Compile Include="..\MediaBrowser.Model\MediaInfo\PlaybackInfoRequest.cs">
+      <Link>MediaInfo\PlaybackInfoRequest.cs</Link>
+    </Compile>
+    <Compile Include="..\MediaBrowser.Model\MediaInfo\PlaybackInfoResponse.cs">
+      <Link>MediaInfo\PlaybackInfoResponse.cs</Link>
+    </Compile>
     <Compile Include="..\MediaBrowser.Model\MediaInfo\SubtitleFormat.cs">
       <Link>MediaInfo\SubtitleFormat.cs</Link>
     </Compile>

+ 14 - 2
MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj

@@ -378,6 +378,9 @@
     <Compile Include="..\MediaBrowser.Model\Dlna\StreamInfo.cs">
       <Link>Dlna\StreamInfo.cs</Link>
     </Compile>
+    <Compile Include="..\MediaBrowser.Model\Dlna\StreamInfoSorter.cs">
+      <Link>Dlna\StreamInfoSorter.cs</Link>
+    </Compile>
     <Compile Include="..\MediaBrowser.Model\Dlna\SubtitleDeliveryMethod.cs">
       <Link>Dlna\SubtitleDeliveryMethod.cs</Link>
     </Compile>
@@ -756,12 +759,21 @@
     <Compile Include="..\MediaBrowser.Model\MediaInfo\IBlurayExaminer.cs">
       <Link>MediaInfo\IBlurayExaminer.cs</Link>
     </Compile>
-    <Compile Include="..\MediaBrowser.Model\MediaInfo\LiveMediaInfoResult.cs">
-      <Link>MediaInfo\LiveMediaInfoResult.cs</Link>
+    <Compile Include="..\MediaBrowser.Model\MediaInfo\LiveStreamRequest.cs">
+      <Link>MediaInfo\LiveStreamRequest.cs</Link>
+    </Compile>
+    <Compile Include="..\MediaBrowser.Model\MediaInfo\LiveStreamResponse.cs">
+      <Link>MediaInfo\LiveStreamResponse.cs</Link>
     </Compile>
     <Compile Include="..\MediaBrowser.Model\MediaInfo\MediaProtocol.cs">
       <Link>MediaInfo\MediaProtocol.cs</Link>
     </Compile>
+    <Compile Include="..\MediaBrowser.Model\MediaInfo\PlaybackInfoRequest.cs">
+      <Link>MediaInfo\PlaybackInfoRequest.cs</Link>
+    </Compile>
+    <Compile Include="..\MediaBrowser.Model\MediaInfo\PlaybackInfoResponse.cs">
+      <Link>MediaInfo\PlaybackInfoResponse.cs</Link>
+    </Compile>
     <Compile Include="..\MediaBrowser.Model\MediaInfo\SubtitleFormat.cs">
       <Link>MediaInfo\SubtitleFormat.cs</Link>
     </Compile>

+ 9 - 3
MediaBrowser.Model/ApiClient/IApiClient.cs

@@ -248,10 +248,9 @@ namespace MediaBrowser.Model.ApiClient
         /// <summary>
         /// Gets the playback information.
         /// </summary>
-        /// <param name="itemId">The item identifier.</param>
-        /// <param name="userId">The user identifier.</param>
+        /// <param name="request">The request.</param>
         /// <returns>Task&lt;LiveMediaInfoResult&gt;.</returns>
-        Task<LiveMediaInfoResult> GetPlaybackInfo(string itemId, string userId);
+        Task<PlaybackInfoResponse> GetPlaybackInfo(PlaybackInfoRequest request);
 
         /// <summary>
         /// Gets the users async.
@@ -1486,5 +1485,12 @@ namespace MediaBrowser.Model.ApiClient
         /// <param name="query">The query.</param>
         /// <returns>Task&lt;List&lt;RecommendationDto&gt;&gt;.</returns>
         Task<List<RecommendationDto>> GetMovieRecommendations(MovieRecommendationQuery query);
+        /// <summary>
+        /// Opens the live stream.
+        /// </summary>
+        /// <param name="request">The request.</param>
+        /// <param name="cancellationToken">The cancellation token.</param>
+        /// <returns>Task&lt;LiveStreamResponse&gt;.</returns>
+        Task<LiveStreamResponse> OpenLiveStream(LiveStreamRequest request, CancellationToken cancellationToken);
     }
 }

+ 6 - 0
MediaBrowser.Model/ApiClient/IConnectionManager.cs

@@ -172,5 +172,11 @@ namespace MediaBrowser.Model.ApiClient
         /// <param name="rememberCredentials">if set to <c>true</c> [remember credentials].</param>
         /// <returns>Task.</returns>
         Task AuthenticateOffline(UserDto user, string password, bool rememberCredentials);
+
+        /// <summary>
+        /// Gets the offline users.
+        /// </summary>
+        /// <returns>Task&lt;List&lt;UserDto&gt;&gt;.</returns>
+        Task<List<UserDto>> GetOfflineUsers();
     }
 }

+ 16 - 5
MediaBrowser.Model/ApiClient/ServerCredentials.cs

@@ -1,7 +1,6 @@
 using MediaBrowser.Model.Extensions;
 using System;
 using System.Collections.Generic;
-using System.Linq;
 
 namespace MediaBrowser.Model.ApiClient
 {
@@ -24,7 +23,12 @@ namespace MediaBrowser.Model.ApiClient
                 throw new ArgumentNullException("server");
             }
 
-            var list = Servers.ToList();
+            // Clone the existing list of servers
+            var list = new List<ServerInfo>();
+            foreach (ServerInfo serverInfo in Servers)
+            {
+                list.Add(serverInfo);
+            }
 
             var index = FindIndex(list, server.Id);
 
@@ -32,8 +36,11 @@ namespace MediaBrowser.Model.ApiClient
             {
                 var existing = list[index];
 
-                // Merge the data
-                existing.DateLastAccessed = new[] { existing.DateLastAccessed, server.DateLastAccessed }.Max();
+                // Take the most recent DateLastAccessed
+                if (server.DateLastAccessed > existing.DateLastAccessed)
+                {
+                    existing.DateLastAccessed = server.DateLastAccessed;
+                }
 
                 existing.UserLinkType = server.UserLinkType;
 
@@ -64,7 +71,11 @@ namespace MediaBrowser.Model.ApiClient
                 }
                 if (server.WakeOnLanInfos != null && server.WakeOnLanInfos.Count > 0)
                 {
-                    existing.WakeOnLanInfos = server.WakeOnLanInfos.ToList();
+                    existing.WakeOnLanInfos = new List<WakeOnLanInfo>();
+                    foreach (WakeOnLanInfo info in server.WakeOnLanInfos)
+                    {
+                        existing.WakeOnLanInfos.Add(info);
+                    }
                 }
                 if (server.LastConnectionMode.HasValue)
                 {

+ 6 - 2
MediaBrowser.Model/ApiClient/ServerInfo.cs

@@ -3,7 +3,6 @@ using MediaBrowser.Model.Extensions;
 using MediaBrowser.Model.System;
 using System;
 using System.Collections.Generic;
-using System.Linq;
 
 namespace MediaBrowser.Model.ApiClient
 {
@@ -83,7 +82,12 @@ namespace MediaBrowser.Model.ApiClient
                 throw new ArgumentNullException("user");
             }
 
-            var list = Users.ToList();
+            // Clone the existing list of users
+            var list = new List<ServerUserInfo>();
+            foreach (ServerUserInfo serverUserInfo in Users)
+            {
+                list.Add(serverUserInfo);
+            }
 
             var index = FindIndex(list, user.Id);
 

+ 4 - 0
MediaBrowser.Model/Configuration/EncodingOptions.cs

@@ -8,12 +8,16 @@ namespace MediaBrowser.Model.Configuration
         public double DownMixAudioBoost { get; set; }
         public string H264Encoder { get; set; }
         public bool EnableDebugLogging { get; set; }
+        public bool EnableThrottling { get; set; }
+        public int ThrottleThresholdSeconds { get; set; }
 
         public EncodingOptions()
         {
             H264Encoder = "libx264";
             DownMixAudioBoost = 2;
             EncodingQuality = EncodingQuality.Auto;
+            EnableThrottling = true;
+            ThrottleThresholdSeconds = 120;
         }
     }
 }

+ 2 - 3
MediaBrowser.Model/Configuration/ServerConfiguration.cs

@@ -200,7 +200,7 @@ namespace MediaBrowser.Model.Configuration
         public PeopleMetadataOptions PeopleMetadataOptions { get; set; }
         public bool FindInternetTrailers { get; set; }
 
-        public string[] InsecureApps8 { get; set; }
+        public string[] InsecureApps9 { get; set; }
 
         public bool SaveMetadataHidden { get; set; }
 
@@ -257,7 +257,7 @@ namespace MediaBrowser.Model.Configuration
 
             PeopleMetadataOptions = new PeopleMetadataOptions();
 
-            InsecureApps8 = new[]
+            InsecureApps9 = new[]
             {
                 "Chromecast",
                 "iOS",
@@ -266,7 +266,6 @@ namespace MediaBrowser.Model.Configuration
                 "Media Portal",
                 "iPad",
                 "iPhone",
-                "Roku",
                 "Windows Phone"
             };
 

+ 2 - 7
MediaBrowser.Model/Configuration/UserConfiguration.cs

@@ -6,12 +6,6 @@ namespace MediaBrowser.Model.Configuration
     /// </summary>
     public class UserConfiguration
     {
-        /// <summary>
-        /// Gets or sets a value indicating whether this instance is administrator.
-        /// </summary>
-        /// <value><c>true</c> if this instance is administrator; otherwise, <c>false</c>.</value>
-        public bool IsAdministrator { get; set; }
-
         /// <summary>
         /// Gets or sets the audio language preference.
         /// </summary>
@@ -53,13 +47,14 @@ namespace MediaBrowser.Model.Configuration
 
         public string[] LatestItemsExcludes { get; set; }
 
-        public bool HasMigratedToPolicy { get; set; }
+        public bool HidePlayedInLatest { get; set; }
 
         /// <summary>
         /// Initializes a new instance of the <see cref="UserConfiguration" /> class.
         /// </summary>
         public UserConfiguration()
         {
+            HidePlayedInLatest = true;
             PlayDefaultAudioTrack = true;
 
             LatestItemsExcludes = new string[] { };

+ 11 - 2
MediaBrowser.Model/Dlna/ConditionProcessor.cs

@@ -20,7 +20,9 @@ namespace MediaBrowser.Model.Dlna
             TransportStreamTimestamp? timestamp,
             bool? isAnamorphic,
             bool? isCabac,
-            int? refFrames)
+            int? refFrames,
+            int? numVideoStreams,
+            int? numAudioStreams)
         {
             switch (condition.Property)
             {
@@ -56,6 +58,10 @@ namespace MediaBrowser.Model.Dlna
                     return IsConditionSatisfied(condition, width);
                 case ProfileConditionValue.RefFrames:
                     return IsConditionSatisfied(condition, refFrames);
+                case ProfileConditionValue.NumAudioStreams:
+                    return IsConditionSatisfied(condition, numAudioStreams);
+                case ProfileConditionValue.NumVideoStreams:
+                    return IsConditionSatisfied(condition, numVideoStreams);
                 case ProfileConditionValue.VideoTimestamp:
                     return IsConditionSatisfied(condition, timestamp);
                 default:
@@ -92,7 +98,8 @@ namespace MediaBrowser.Model.Dlna
         public bool IsVideoAudioConditionSatisfied(ProfileCondition condition, 
             int? audioChannels, 
             int? audioBitrate,
-            string audioProfile)
+            string audioProfile,
+            bool? isSecondaryTrack)
         {
             switch (condition.Property)
             {
@@ -102,6 +109,8 @@ namespace MediaBrowser.Model.Dlna
                     return IsConditionSatisfied(condition, audioBitrate);
                 case ProfileConditionValue.AudioChannels:
                     return IsConditionSatisfied(condition, audioChannels);
+                case ProfileConditionValue.IsSecondaryAudio:
+                    return IsConditionSatisfied(condition, isSecondaryTrack);
                 default:
                     throw new ArgumentException("Unexpected condition on audio file: " + condition.Property);
             }

+ 6 - 2
MediaBrowser.Model/Dlna/ContentFeatureBuilder.cs

@@ -117,7 +117,9 @@ namespace MediaBrowser.Model.Dlna
             TranscodeSeekInfo transcodeSeekInfo,
             bool? isAnamorphic,
             bool? isCabac,
-            int? refFrames)
+            int? refFrames,
+            int? numVideoStreams,
+            int? numAudioStreams)
         {
             // 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
             string orgOp = ";DLNA.ORG_OP=" + DlnaMaps.GetOrgOpValue(runtimeTicks.HasValue, isDirectStream, transcodeSeekInfo);
@@ -158,7 +160,9 @@ namespace MediaBrowser.Model.Dlna
                 timestamp,
                 isAnamorphic,
                 isCabac,
-                refFrames);
+                refFrames,
+                numVideoStreams,
+                numAudioStreams);
 
             List<string> orgPnValues = new List<string>();
 

+ 4 - 2
MediaBrowser.Model/Dlna/DeviceProfile.cs

@@ -281,7 +281,9 @@ namespace MediaBrowser.Model.Dlna
             TransportStreamTimestamp timestamp,
             bool? isAnamorphic,
             bool? isCabac,
-            int? refFrames)
+            int? refFrames,
+            int? numVideoStreams,
+            int? numAudioStreams)
         {
             container = StringHelper.TrimStart((container ?? string.Empty), '.');
 
@@ -315,7 +317,7 @@ namespace MediaBrowser.Model.Dlna
                 var anyOff = false;
                 foreach (ProfileCondition c in i.Conditions)
                 {
-                    if (!conditionProcessor.IsVideoConditionSatisfied(c, audioBitrate, audioChannels, width, height, bitDepth, videoBitrate, videoProfile, videoLevel, videoFramerate, packetLength, timestamp, isAnamorphic, isCabac, refFrames))
+                    if (!conditionProcessor.IsVideoConditionSatisfied(c, audioBitrate, audioChannels, width, height, bitDepth, videoBitrate, videoProfile, videoLevel, videoFramerate, packetLength, timestamp, isAnamorphic, isCabac, refFrames, numVideoStreams, numAudioStreams))
                     {
                         anyOff = true;
                         break;

+ 4 - 1
MediaBrowser.Model/Dlna/ProfileConditionValue.cs

@@ -17,6 +17,9 @@
         VideoTimestamp = 12,
         IsAnamorphic = 13,
         RefFrames = 14,
-        IsCabac = 15
+        IsCabac = 15,
+        NumAudioStreams = 16,
+        NumVideoStreams = 17,
+        IsSecondaryAudio
     }
 }

+ 99 - 35
MediaBrowser.Model/Dlna/StreamBuilder.cs

@@ -89,38 +89,14 @@ namespace MediaBrowser.Model.Dlna
 
         private StreamInfo GetOptimalStream(List<StreamInfo> streams)
         {
-            // Grab the first one that can be direct streamed
-            // If that doesn't produce anything, just take the first
-            foreach (StreamInfo i in streams)
-            {
-                if (i.PlayMethod == PlayMethod.DirectPlay && i.MediaSource.Protocol == MediaProtocol.File)
-                {
-                    return i;
-                }
-            }
-            foreach (StreamInfo i in streams)
-            {
-                if (i.PlayMethod == PlayMethod.DirectPlay)
-                {
-                    return i;
-                }
-            }
-            foreach (StreamInfo i in streams)
-            {
-                if (i.PlayMethod == PlayMethod.DirectStream)
-                {
-                    return i;
-                }
-            }
+            streams = StreamInfoSorter.SortMediaSources(streams);
 
             foreach (StreamInfo stream in streams)
             {
                 return stream;
             }
 
-            PlaybackException error = new PlaybackException();
-            error.ErrorCode = PlaybackErrorCode.NoCompatibleStream;
-            throw error;
+            return null;
         }
 
         private StreamInfo BuildAudioItem(MediaSourceInfo item, AudioOptions options)
@@ -221,7 +197,7 @@ namespace MediaBrowser.Model.Dlna
                 playlistItem.EstimateContentLength = transcodingProfile.EstimateContentLength;
                 playlistItem.Container = transcodingProfile.Container;
                 playlistItem.AudioCodec = transcodingProfile.AudioCodec;
-                playlistItem.Protocol = transcodingProfile.Protocol;
+                playlistItem.SubProtocol = transcodingProfile.Protocol;
 
                 List<CodecProfile> audioCodecProfiles = new List<CodecProfile>();
                 foreach (CodecProfile i in options.Profile.CodecProfiles)
@@ -263,6 +239,16 @@ namespace MediaBrowser.Model.Dlna
             return playlistItem;
         }
 
+        private int? GetBitrateForDirectPlayCheck(MediaSourceInfo item, AudioOptions options)
+        {
+            if (item.Protocol == MediaProtocol.File)
+            {
+                return options.Profile.MaxStaticBitrate;
+            }
+
+            return options.GetMaxBitrate();
+        }
+
         private List<PlayMethod> GetAudioDirectPlayMethods(MediaSourceInfo item, MediaStream audioStream, AudioOptions options)
         {
             DirectPlayProfile directPlayProfile = null;
@@ -287,7 +273,7 @@ namespace MediaBrowser.Model.Dlna
                 
                 // The profile describes what the device supports
                 // If device requirements are satisfied then allow both direct stream and direct play
-                if (IsAudioEligibleForDirectPlay(item, options.Profile.MaxStaticBitrate))
+                if (IsAudioEligibleForDirectPlay(item, GetBitrateForDirectPlayCheck(item, options)))
                 {
                     playMethods.Add(PlayMethod.DirectPlay);
                 }
@@ -296,6 +282,49 @@ namespace MediaBrowser.Model.Dlna
             return playMethods;
         }
 
+        private int? GetDefaultSubtitleStreamIndex(MediaSourceInfo item, SubtitleProfile[] subtitleProfiles)
+        {
+            int highestScore = -1;
+
+            foreach (MediaStream stream in item.MediaStreams)
+            {
+                if (stream.Type == MediaStreamType.Subtitle && stream.Score.HasValue)
+                {
+                    if (stream.Score.Value > highestScore)
+                    {
+                        highestScore = stream.Score.Value;
+                    }
+                }    
+            }
+
+            List<MediaStream> topStreams = new List<MediaStream>();
+            foreach (MediaStream stream in item.MediaStreams)
+            {
+                if (stream.Type == MediaStreamType.Subtitle && stream.Score.HasValue && stream.Score.Value == highestScore)
+                {
+                    topStreams.Add(stream);
+                }
+            }
+
+            // If multiple streams have an equal score, try to pick the most efficient one
+            if (topStreams.Count > 1)
+            {
+                foreach (MediaStream stream in topStreams)
+                {
+                    foreach (SubtitleProfile profile in subtitleProfiles)
+                    {
+                        if (profile.Method == SubtitleDeliveryMethod.External && StringHelper.EqualsIgnoreCase(profile.Format, stream.Codec))
+                        {
+                            return stream.Index;
+                        }
+                    }
+                }
+            }
+
+            // If no optimization panned out, just use the original default
+            return item.DefaultSubtitleStreamIndex;
+        }
+
         private StreamInfo BuildVideoItem(MediaSourceInfo item, VideoOptions options)
         {
             StreamInfo playlistItem = new StreamInfo
@@ -308,16 +337,20 @@ namespace MediaBrowser.Model.Dlna
                 DeviceProfile = options.Profile
             };
 
-            playlistItem.SubtitleStreamIndex = options.SubtitleStreamIndex ?? item.DefaultSubtitleStreamIndex;
+            playlistItem.SubtitleStreamIndex = options.SubtitleStreamIndex ?? GetDefaultSubtitleStreamIndex(item, options.Profile.SubtitleProfiles);
             MediaStream subtitleStream = playlistItem.SubtitleStreamIndex.HasValue ? item.GetMediaStream(MediaStreamType.Subtitle, playlistItem.SubtitleStreamIndex.Value) : null;
 
             MediaStream audioStream = item.GetDefaultAudioStream(options.AudioStreamIndex ?? item.DefaultAudioStreamIndex);
-            int? audioStreamIndex = audioStream == null ? (int?)null : audioStream.Index;
+            int? audioStreamIndex = null;
+            if (audioStream != null)
+            {
+                audioStreamIndex = audioStream.Index;
+            }
 
             MediaStream videoStream = item.VideoStream;
 
             // TODO: This doesn't accout for situation of device being able to handle media bitrate, but wifi connection not fast enough
-            bool isEligibleForDirectPlay = IsEligibleForDirectPlay(item, options.Profile.MaxStaticBitrate, subtitleStream, options);
+            bool isEligibleForDirectPlay = IsEligibleForDirectPlay(item, GetBitrateForDirectPlayCheck(item, options), subtitleStream, options);
             bool isEligibleForDirectStream = IsEligibleForDirectPlay(item, options.GetMaxBitrate(), subtitleStream, options);
 
             if (isEligibleForDirectPlay || isEligibleForDirectStream)
@@ -374,7 +407,7 @@ namespace MediaBrowser.Model.Dlna
                 playlistItem.TranscodeSeekInfo = transcodingProfile.TranscodeSeekInfo;
                 playlistItem.AudioCodec = transcodingProfile.AudioCodec.Split(',')[0];
                 playlistItem.VideoCodec = transcodingProfile.VideoCodec;
-                playlistItem.Protocol = transcodingProfile.Protocol;
+                playlistItem.SubProtocol = transcodingProfile.Protocol;
                 playlistItem.AudioStreamIndex = audioStreamIndex;
 
                 List<ProfileCondition> videoTranscodingConditions = new List<ProfileCondition>();
@@ -509,10 +542,13 @@ namespace MediaBrowser.Model.Dlna
             int? packetLength = videoStream == null ? null : videoStream.PacketLength;
             int? refFrames = videoStream == null ? null : videoStream.RefFrames;
 
+            int? numAudioStreams = mediaSource.GetStreamCount(MediaStreamType.Audio);
+            int? numVideoStreams = mediaSource.GetStreamCount(MediaStreamType.Video);
+
             // Check container conditions
             foreach (ProfileCondition i in conditions)
             {
-                if (!conditionProcessor.IsVideoConditionSatisfied(i, audioBitrate, audioChannels, width, height, bitDepth, videoBitrate, videoProfile, videoLevel, videoFramerate, packetLength, timestamp, isAnamorphic, isCabac, refFrames))
+                if (!conditionProcessor.IsVideoConditionSatisfied(i, audioBitrate, audioChannels, width, height, bitDepth, videoBitrate, videoProfile, videoLevel, videoFramerate, packetLength, timestamp, isAnamorphic, isCabac, refFrames, numVideoStreams, numAudioStreams))
                 {
                     return null;
                 }
@@ -539,7 +575,7 @@ namespace MediaBrowser.Model.Dlna
 
             foreach (ProfileCondition i in conditions)
             {
-                if (!conditionProcessor.IsVideoConditionSatisfied(i, audioBitrate, audioChannels, width, height, bitDepth, videoBitrate, videoProfile, videoLevel, videoFramerate, packetLength, timestamp, isAnamorphic, isCabac, refFrames))
+                if (!conditionProcessor.IsVideoConditionSatisfied(i, audioBitrate, audioChannels, width, height, bitDepth, videoBitrate, videoProfile, videoLevel, videoFramerate, packetLength, timestamp, isAnamorphic, isCabac, refFrames, numVideoStreams, numAudioStreams))
                 {
                     return null;
                 }
@@ -568,7 +604,8 @@ namespace MediaBrowser.Model.Dlna
 
                 foreach (ProfileCondition i in conditions)
                 {
-                    if (!conditionProcessor.IsVideoAudioConditionSatisfied(i, audioChannels, audioBitrate, audioProfile))
+                    bool? isSecondaryAudio = audioStream == null ? null : mediaSource.IsSecondaryAudio(audioStream);
+                    if (!conditionProcessor.IsVideoAudioConditionSatisfied(i, audioChannels, audioBitrate, audioProfile, isSecondaryAudio))
                     {
                         return null;
                     }
@@ -628,8 +665,20 @@ namespace MediaBrowser.Model.Dlna
             // Look for an external profile that matches the stream type (text/graphical)
             foreach (SubtitleProfile profile in subtitleProfiles)
             {
+                bool requiresConversion = !StringHelper.EqualsIgnoreCase(subtitleStream.Codec, profile.Format);
+
+                if (!profile.SupportsLanguage(subtitleStream.Language))
+                {
+                    continue;
+                }
+
                 if (profile.Method == SubtitleDeliveryMethod.External && subtitleStream.IsTextSubtitleStream == MediaStream.IsTextFormat(profile.Format))
                 {
+                    if (!requiresConversion)
+                    {
+                        return profile;
+                    }
+
                     if (subtitleStream.SupportsExternalStream)
                     {
                         return profile;
@@ -645,8 +694,20 @@ namespace MediaBrowser.Model.Dlna
 
             foreach (SubtitleProfile profile in subtitleProfiles)
             {
+                bool requiresConversion = !StringHelper.EqualsIgnoreCase(subtitleStream.Codec, profile.Format);
+
+                if (!profile.SupportsLanguage(subtitleStream.Language))
+                {
+                    continue;
+                }
+
                 if (profile.Method == SubtitleDeliveryMethod.Embed && subtitleStream.IsTextSubtitleStream == MediaStream.IsTextFormat(profile.Format))
                 {
+                    if (!requiresConversion)
+                    {
+                        return profile;
+                    }
+
                     return profile;
                 }
             }
@@ -756,6 +817,9 @@ namespace MediaBrowser.Model.Dlna
                     case ProfileConditionValue.AudioProfile:
                     case ProfileConditionValue.Has64BitOffsets:
                     case ProfileConditionValue.PacketLength:
+                    case ProfileConditionValue.NumAudioStreams:
+                    case ProfileConditionValue.NumVideoStreams:
+                    case ProfileConditionValue.IsSecondaryAudio:
                     case ProfileConditionValue.VideoTimestamp:
                         {
                             // Not supported yet

+ 204 - 109
MediaBrowser.Model/Dlna/StreamInfo.cs

@@ -24,7 +24,7 @@ namespace MediaBrowser.Model.Dlna
 
         public string Container { get; set; }
 
-        public string Protocol { get; set; }
+        public string SubProtocol { get; set; }
 
         public long StartPositionTicks { get; set; }
 
@@ -51,7 +51,7 @@ namespace MediaBrowser.Model.Dlna
 
         public int? MaxVideoBitDepth { get; set; }
         public int? MaxRefFrames { get; set; }
-        
+
         public float? MaxFramerate { get; set; }
 
         public DeviceProfile DeviceProfile { get; set; }
@@ -69,7 +69,7 @@ namespace MediaBrowser.Model.Dlna
         public SubtitleDeliveryMethod SubtitleDeliveryMethod { get; set; }
         public string SubtitleFormat { get; set; }
 
-        public LiveMediaInfoResult PlaybackInfo { get; set; }
+        public string PlaySessionId { get; set; }
 
         public string MediaSourceId
         {
@@ -81,7 +81,8 @@ namespace MediaBrowser.Model.Dlna
 
         public bool IsDirectStream
         {
-            get { 
+            get
+            {
                 return PlayMethod == PlayMethod.DirectStream ||
                     PlayMethod == PlayMethod.DirectPlay;
             }
@@ -89,7 +90,47 @@ namespace MediaBrowser.Model.Dlna
 
         public string ToUrl(string baseUrl, string accessToken)
         {
-            return ToDlnaUrl(baseUrl, accessToken);
+            if (PlayMethod == PlayMethod.DirectPlay)
+            {
+                return MediaSource.Path;
+            }
+
+            if (string.IsNullOrEmpty(baseUrl))
+            {
+                throw new ArgumentNullException(baseUrl);
+            }
+
+            List<string> list = new List<string>();
+            foreach (NameValuePair pair in BuildParams(this, accessToken))
+            {
+                if (string.IsNullOrEmpty(pair.Value))
+                {
+                    continue;
+                }
+
+                // Try to keep the url clean by omitting defaults
+                if (StringHelper.EqualsIgnoreCase(pair.Name, "StartTimeTicks") &&
+                    StringHelper.EqualsIgnoreCase(pair.Value, "0"))
+                {
+                    continue;
+                }
+                if (StringHelper.EqualsIgnoreCase(pair.Name, "SubtitleStreamIndex") &&
+                    StringHelper.EqualsIgnoreCase(pair.Value, "-1"))
+                {
+                    continue;
+                }
+                if (StringHelper.EqualsIgnoreCase(pair.Name, "Static") &&
+                    StringHelper.EqualsIgnoreCase(pair.Value, "false"))
+                {
+                    continue;
+                }
+
+                list.Add(string.Format("{0}={1}", pair.Name, pair.Value));
+            }
+
+            string queryString = string.Join("&", list.ToArray());
+
+            return GetUrl(baseUrl, queryString);
         }
 
         public string ToDlnaUrl(string baseUrl, string accessToken)
@@ -99,115 +140,124 @@ namespace MediaBrowser.Model.Dlna
                 return MediaSource.Path;
             }
 
+            string dlnaCommand = BuildDlnaParam(this, accessToken);
+            return GetUrl(baseUrl, dlnaCommand);
+        }
+
+        private string GetUrl(string baseUrl, string queryString)
+        {
             if (string.IsNullOrEmpty(baseUrl))
             {
                 throw new ArgumentNullException(baseUrl);
             }
 
-            string dlnaCommand = BuildDlnaParam(this);
-
             string extension = string.IsNullOrEmpty(Container) ? string.Empty : "." + Container;
 
             baseUrl = baseUrl.TrimEnd('/');
 
             if (MediaType == DlnaProfileType.Audio)
             {
-                return string.Format("{0}/audio/{1}/stream{2}?{3}", baseUrl, ItemId, extension, dlnaCommand);
+                return string.Format("{0}/audio/{1}/stream{2}?{3}", baseUrl, ItemId, extension, queryString);
             }
 
-            if (StringHelper.EqualsIgnoreCase(Protocol, "hls"))
+            if (StringHelper.EqualsIgnoreCase(SubProtocol, "hls"))
             {
-                return string.Format("{0}/videos/{1}/master.m3u8?{2}", baseUrl, ItemId, dlnaCommand);
+                return string.Format("{0}/videos/{1}/master.m3u8?{2}", baseUrl, ItemId, queryString);
             }
 
-            return string.Format("{0}/videos/{1}/stream{2}?{3}", baseUrl, ItemId, extension, dlnaCommand);
+            return string.Format("{0}/videos/{1}/stream{2}?{3}", baseUrl, ItemId, extension, queryString);
         }
 
-        private static string BuildDlnaParam(StreamInfo item)
+        private static string BuildDlnaParam(StreamInfo item, string accessToken)
         {
-            List<string> list = new List<string>
-            {
-                item.DeviceProfileId ?? string.Empty,
-                item.DeviceId ?? string.Empty,
-                item.MediaSourceId ?? string.Empty,
-                (item.IsDirectStream).ToString().ToLower(),
-                item.VideoCodec ?? string.Empty,
-                item.AudioCodec ?? string.Empty,
-                item.AudioStreamIndex.HasValue ? StringHelper.ToStringCultureInvariant(item.AudioStreamIndex.Value) : string.Empty,
-                item.SubtitleStreamIndex.HasValue && item.SubtitleDeliveryMethod != SubtitleDeliveryMethod.External ? StringHelper.ToStringCultureInvariant(item.SubtitleStreamIndex.Value) : string.Empty,
-                item.VideoBitrate.HasValue ? StringHelper.ToStringCultureInvariant(item.VideoBitrate.Value) : string.Empty,
-                item.AudioBitrate.HasValue ? StringHelper.ToStringCultureInvariant(item.AudioBitrate.Value) : string.Empty,
-                item.MaxAudioChannels.HasValue ? StringHelper.ToStringCultureInvariant(item.MaxAudioChannels.Value) : string.Empty,
-                item.MaxFramerate.HasValue ? StringHelper.ToStringCultureInvariant(item.MaxFramerate.Value) : string.Empty,
-                item.MaxWidth.HasValue ? StringHelper.ToStringCultureInvariant(item.MaxWidth.Value) : string.Empty,
-                item.MaxHeight.HasValue ? StringHelper.ToStringCultureInvariant(item.MaxHeight.Value) : string.Empty,
-                StringHelper.ToStringCultureInvariant(item.StartPositionTicks),
-                item.VideoLevel.HasValue ? StringHelper.ToStringCultureInvariant(item.VideoLevel.Value) : string.Empty
-            };
+            List<string> list = new List<string>();
 
-            list.Add(item.IsDirectStream ? string.Empty : DateTime.UtcNow.Ticks.ToString(CultureInfo.InvariantCulture));
-            list.Add(item.MaxRefFrames.HasValue ? StringHelper.ToStringCultureInvariant(item.MaxRefFrames.Value) : string.Empty);
-            list.Add(item.MaxVideoBitDepth.HasValue ? StringHelper.ToStringCultureInvariant(item.MaxVideoBitDepth.Value) : string.Empty);
-            list.Add(item.VideoProfile ?? string.Empty);
-            list.Add(item.Cabac.HasValue ? item.Cabac.Value.ToString() : string.Empty);
+            foreach (NameValuePair pair in BuildParams(item, accessToken))
+            {
+                list.Add(pair.Value);
+            }
 
-            string streamId = item.PlaybackInfo == null ? null : item.PlaybackInfo.StreamId;
-            list.Add(streamId ?? string.Empty);
-            
             return string.Format("Params={0}", string.Join(";", list.ToArray()));
         }
 
-        public List<SubtitleStreamInfo> GetExternalSubtitles(bool includeSelectedTrackOnly)
+        private static List<NameValuePair> BuildParams(StreamInfo item, string accessToken)
         {
-            List<SubtitleStreamInfo> list = new List<SubtitleStreamInfo>();
+            List<NameValuePair> list = new List<NameValuePair>();
 
-            // First add the selected track
-            if (SubtitleStreamIndex.HasValue)
-            {
-                foreach (MediaStream stream in MediaSource.MediaStreams)
-                {
-                    if (stream.Type == MediaStreamType.Subtitle && stream.Index == SubtitleStreamIndex.Value)
-                    {
-                        SubtitleStreamInfo info = GetSubtitleStreamInfo(stream);
+            list.Add(new NameValuePair("DeviceProfileId", item.DeviceProfileId ?? string.Empty));
+            list.Add(new NameValuePair("DeviceId", item.DeviceId ?? string.Empty));
+            list.Add(new NameValuePair("MediaSourceId", item.MediaSourceId ?? string.Empty));
+            list.Add(new NameValuePair("Static", (item.IsDirectStream).ToString().ToLower()));
+            list.Add(new NameValuePair("VideoCodec", item.VideoCodec ?? string.Empty));
+            list.Add(new NameValuePair("AudioCodec", item.AudioCodec ?? string.Empty));
+            list.Add(new NameValuePair("AudioStreamIndex", item.AudioStreamIndex.HasValue ? StringHelper.ToStringCultureInvariant(item.AudioStreamIndex.Value) : string.Empty));
+            list.Add(new NameValuePair("SubtitleStreamIndex", item.SubtitleStreamIndex.HasValue && item.SubtitleDeliveryMethod != SubtitleDeliveryMethod.External ? StringHelper.ToStringCultureInvariant(item.SubtitleStreamIndex.Value) : string.Empty));
+            list.Add(new NameValuePair("VideoBitrate", item.VideoBitrate.HasValue ? StringHelper.ToStringCultureInvariant(item.VideoBitrate.Value) : string.Empty));
+            list.Add(new NameValuePair("AudioBitrate", item.AudioBitrate.HasValue ? StringHelper.ToStringCultureInvariant(item.AudioBitrate.Value) : string.Empty));
+            list.Add(new NameValuePair("MaxAudioChannels", item.MaxAudioChannels.HasValue ? StringHelper.ToStringCultureInvariant(item.MaxAudioChannels.Value) : string.Empty));
+            list.Add(new NameValuePair("MaxFramerate", item.MaxFramerate.HasValue ? StringHelper.ToStringCultureInvariant(item.MaxFramerate.Value) : string.Empty));
+            list.Add(new NameValuePair("MaxWidth", item.MaxWidth.HasValue ? StringHelper.ToStringCultureInvariant(item.MaxWidth.Value) : string.Empty));
+            list.Add(new NameValuePair("MaxHeight", item.MaxHeight.HasValue ? StringHelper.ToStringCultureInvariant(item.MaxHeight.Value) : string.Empty));
 
-                        if (info != null)
-                        {
-                            list.Add(info);
-                        }
-                    }
-                }
+            if (StringHelper.EqualsIgnoreCase(item.SubProtocol, "hls"))
+            {
+                list.Add(new NameValuePair("StartTimeTicks", string.Empty));
             }
-
-            if (!includeSelectedTrackOnly)
+            else
             {
-                foreach (MediaStream stream in MediaSource.MediaStreams)
-                {
-                    if (stream.Type == MediaStreamType.Subtitle && (!SubtitleStreamIndex.HasValue || stream.Index != SubtitleStreamIndex.Value))
-                    {
-                        SubtitleStreamInfo info = GetSubtitleStreamInfo(stream);
-
-                        if (info != null)
-                        {
-                            list.Add(info);
-                        }
-                    }
-                }
+                list.Add(new NameValuePair("StartTimeTicks", StringHelper.ToStringCultureInvariant(item.StartPositionTicks)));
             }
 
+            list.Add(new NameValuePair("Level", item.VideoLevel.HasValue ? StringHelper.ToStringCultureInvariant(item.VideoLevel.Value) : string.Empty));
+
+            list.Add(new NameValuePair("ClientTime", item.IsDirectStream ? string.Empty : DateTime.UtcNow.Ticks.ToString(CultureInfo.InvariantCulture)));
+            list.Add(new NameValuePair("MaxRefFrames", item.MaxRefFrames.HasValue ? StringHelper.ToStringCultureInvariant(item.MaxRefFrames.Value) : string.Empty));
+            list.Add(new NameValuePair("MaxVideoBitDepth", item.MaxVideoBitDepth.HasValue ? StringHelper.ToStringCultureInvariant(item.MaxVideoBitDepth.Value) : string.Empty));
+            list.Add(new NameValuePair("Profile", item.VideoProfile ?? string.Empty));
+            list.Add(new NameValuePair("Cabac", item.Cabac.HasValue ? item.Cabac.Value.ToString() : string.Empty));
+
+            list.Add(new NameValuePair("PlaySessionId", item.PlaySessionId ?? string.Empty));
+            list.Add(new NameValuePair("api_key", accessToken ?? string.Empty));
+
+            string liveStreamId = item.MediaSource == null ? null : item.MediaSource.LiveStreamId;
+            list.Add(new NameValuePair("LiveStreamId", liveStreamId ?? string.Empty));
+
             return list;
         }
 
-        public List<SubtitleStreamInfo> GetExternalSubtitles(string baseUrl, string accessToken, bool includeSelectedTrackOnly)
+        public List<SubtitleStreamInfo> GetExternalSubtitles(bool includeSelectedTrackOnly, string baseUrl, string accessToken)
         {
-            if (string.IsNullOrEmpty(baseUrl))
+            return GetExternalSubtitles(includeSelectedTrackOnly, false, baseUrl, accessToken);
+        }
+
+        public List<SubtitleStreamInfo> GetExternalSubtitles(bool includeSelectedTrackOnly, bool enableAllProfiles, string baseUrl, string accessToken)
+        {
+            List<SubtitleStreamInfo> list = GetSubtitleProfiles(includeSelectedTrackOnly, enableAllProfiles, baseUrl, accessToken);
+            List<SubtitleStreamInfo> newList = new List<SubtitleStreamInfo>();
+
+            // First add the selected track
+            foreach (SubtitleStreamInfo stream in list)
             {
-                throw new ArgumentNullException(baseUrl);
+                if (stream.DeliveryMethod == SubtitleDeliveryMethod.External)
+                {
+                    newList.Add(stream);
+                }
             }
 
+            return newList;
+        }
+
+        public List<SubtitleStreamInfo> GetSubtitleProfiles(bool includeSelectedTrackOnly, string baseUrl, string accessToken)
+        {
+            return GetSubtitleProfiles(includeSelectedTrackOnly, false, baseUrl, accessToken);
+        }
+
+        public List<SubtitleStreamInfo> GetSubtitleProfiles(bool includeSelectedTrackOnly, bool enableAllProfiles, string baseUrl, string accessToken)
+        {
             List<SubtitleStreamInfo> list = new List<SubtitleStreamInfo>();
 
             // HLS will preserve timestamps so we can just grab the full subtitle stream
-            long startPositionTicks = StringHelper.EqualsIgnoreCase(Protocol, "hls")
+            long startPositionTicks = StringHelper.EqualsIgnoreCase(SubProtocol, "hls")
                 ? 0
                 : StartPositionTicks;
 
@@ -218,12 +268,7 @@ namespace MediaBrowser.Model.Dlna
                 {
                     if (stream.Type == MediaStreamType.Subtitle && stream.Index == SubtitleStreamIndex.Value)
                     {
-                        SubtitleStreamInfo info = GetSubtitleStreamInfo(stream, baseUrl, accessToken, startPositionTicks);
-
-                        if (info != null)
-                        {
-                            list.Add(info);
-                        }
+                        AddSubtitleProfiles(list, stream, enableAllProfiles, baseUrl, accessToken, startPositionTicks);
                     }
                 }
             }
@@ -234,54 +279,68 @@ namespace MediaBrowser.Model.Dlna
                 {
                     if (stream.Type == MediaStreamType.Subtitle && (!SubtitleStreamIndex.HasValue || stream.Index != SubtitleStreamIndex.Value))
                     {
-                        SubtitleStreamInfo info = GetSubtitleStreamInfo(stream, baseUrl, accessToken, startPositionTicks);
-
-                        if (info != null)
-                        {
-                            list.Add(info);
-                        }
+                        AddSubtitleProfiles(list, stream, enableAllProfiles, baseUrl, accessToken, startPositionTicks);
                     }
                 }
             }
-            
+
             return list;
         }
 
-        private SubtitleStreamInfo GetSubtitleStreamInfo(MediaStream stream, string baseUrl, string accessToken, long startPositionTicks)
+        private void AddSubtitleProfiles(List<SubtitleStreamInfo> list, MediaStream stream, bool enableAllProfiles, string baseUrl, string accessToken, long startPositionTicks)
         {
-            SubtitleStreamInfo info = GetSubtitleStreamInfo(stream);
-
-            if (info != null)
+            if (enableAllProfiles)
             {
-                info.Url = string.Format("{0}/Videos/{1}/{2}/Subtitles/{3}/{4}/Stream.{5}",
-                    baseUrl,
-                    ItemId,
-                    MediaSourceId,
-                    StringHelper.ToStringCultureInvariant(stream.Index),
-                    StringHelper.ToStringCultureInvariant(startPositionTicks),
-                    SubtitleFormat);
+                foreach (SubtitleProfile profile in DeviceProfile.SubtitleProfiles)
+                {
+                    SubtitleStreamInfo info = GetSubtitleStreamInfo(stream, baseUrl, accessToken, startPositionTicks, new[] { profile });
+
+                    list.Add(info);
+                }
             }
+            else
+            {
+                SubtitleStreamInfo info = GetSubtitleStreamInfo(stream, baseUrl, accessToken, startPositionTicks, DeviceProfile.SubtitleProfiles);
 
-            return info;
+                list.Add(info);
+            }
         }
 
-        private SubtitleStreamInfo GetSubtitleStreamInfo(MediaStream stream)
+        private SubtitleStreamInfo GetSubtitleStreamInfo(MediaStream stream, string baseUrl, string accessToken, long startPositionTicks, SubtitleProfile[] subtitleProfiles)
         {
-            SubtitleProfile subtitleProfile = StreamBuilder.GetSubtitleProfile(stream, DeviceProfile.SubtitleProfiles, Context);
-
-            if (subtitleProfile.Method != SubtitleDeliveryMethod.External)
-            {
-                return null;
-            }
-
-            return new SubtitleStreamInfo
+            SubtitleProfile subtitleProfile = StreamBuilder.GetSubtitleProfile(stream, subtitleProfiles, Context);
+            SubtitleStreamInfo info = new SubtitleStreamInfo
             {
                 IsForced = stream.IsForced,
                 Language = stream.Language,
                 Name = stream.Language ?? "Unknown",
-                Format = SubtitleFormat,
-                Index = stream.Index
+                Format = subtitleProfile.Format,
+                Index = stream.Index,
+                DeliveryMethod = subtitleProfile.Method
             };
+
+            if (info.DeliveryMethod == SubtitleDeliveryMethod.External)
+            {
+                if (MediaSource.Protocol == MediaProtocol.File || !StringHelper.EqualsIgnoreCase(stream.Codec, subtitleProfile.Format))
+                {
+                    info.Url = string.Format("{0}/Videos/{1}/{2}/Subtitles/{3}/{4}/Stream.{5}",
+                        baseUrl,
+                        ItemId,
+                        MediaSourceId,
+                        StringHelper.ToStringCultureInvariant(stream.Index),
+                        StringHelper.ToStringCultureInvariant(startPositionTicks),
+                        subtitleProfile.Format);
+
+                    info.IsExternalUrl = false;
+                }
+                else
+                {
+                    info.Url = stream.Path;
+                    info.IsExternalUrl = true;
+                }
+            }
+
+            return info;
         }
 
         /// <summary>
@@ -613,6 +672,42 @@ namespace MediaBrowser.Model.Dlna
             }
         }
 
+        public int? TargetVideoStreamCount
+        {
+            get
+            {
+                if (IsDirectStream)
+                {
+                    return GetMediaStreamCount(MediaStreamType.Video, int.MaxValue);
+                }
+                return GetMediaStreamCount(MediaStreamType.Video, 1);
+            }
+        }
+
+        public int? TargetAudioStreamCount
+        {
+            get
+            {
+                if (IsDirectStream)
+                {
+                    return GetMediaStreamCount(MediaStreamType.Audio, int.MaxValue);
+                }
+                return GetMediaStreamCount(MediaStreamType.Audio, 1);
+            }
+        }
+
+        private int? GetMediaStreamCount(MediaStreamType type, int limit)
+        {
+            var count = MediaSource.GetStreamCount(type);
+
+            if (count.HasValue)
+            {
+                count = Math.Min(count.Value, limit);
+            }
+
+            return count;
+        }
+
         public List<MediaStream> GetSelectableAudioStreams()
         {
             return GetSelectableStreams(MediaStreamType.Audio);

+ 47 - 0
MediaBrowser.Model/Dlna/StreamInfoSorter.cs

@@ -0,0 +1,47 @@
+using MediaBrowser.Model.MediaInfo;
+using MediaBrowser.Model.Session;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace MediaBrowser.Model.Dlna
+{
+    public class StreamInfoSorter
+    {
+        public static List<StreamInfo> SortMediaSources(List<StreamInfo> streams)
+        {
+            return streams.OrderBy(i =>
+            {
+                // Nothing beats direct playing a file
+                if (i.PlayMethod == PlayMethod.DirectPlay && i.MediaSource.Protocol == MediaProtocol.File)
+                {
+                    return 0;
+                }
+
+                return 1;
+
+            }).ThenBy(i =>
+            {
+                switch (i.PlayMethod)
+                {
+                    // Let's assume direct streaming a file is just as desirable as direct playing a remote url
+                    case PlayMethod.DirectStream:
+                    case PlayMethod.DirectPlay:
+                        return 0;
+                    default:
+                        return 1;
+                }
+
+            }).ThenBy(i =>
+            {
+                switch (i.MediaSource.Protocol)
+                {
+                    case MediaProtocol.File:
+                        return 0;
+                    default:
+                        return 1;
+                }
+
+            }).ToList();
+        }
+    }
+}

+ 26 - 1
MediaBrowser.Model/Dlna/SubtitleProfile.cs

@@ -1,4 +1,6 @@
-using System.Xml.Serialization;
+using MediaBrowser.Model.Extensions;
+using System.Collections.Generic;
+using System.Xml.Serialization;
 
 namespace MediaBrowser.Model.Dlna
 {
@@ -13,5 +15,28 @@ namespace MediaBrowser.Model.Dlna
         [XmlAttribute("didlMode")]
         public string DidlMode { get; set; }
 
+        [XmlAttribute("language")]
+        public string Language { get; set; }
+
+        public List<string> GetLanguages()
+        {
+            List<string> list = new List<string>();
+            foreach (string i in (Language ?? string.Empty).Split(','))
+            {
+                if (!string.IsNullOrEmpty(i)) list.Add(i);
+            }
+            return list;
+        }
+
+        public bool SupportsLanguage(string language)
+        {
+            if (string.IsNullOrEmpty(language))
+            {
+                language = "und";
+            }
+
+            List<string> languages = GetLanguages();
+            return languages.Count == 0 || ListHelper.ContainsIgnoreCase(languages, language);
+        }
     }
 }

+ 2 - 0
MediaBrowser.Model/Dlna/SubtitleStreamInfo.cs

@@ -8,5 +8,7 @@ namespace MediaBrowser.Model.Dlna
         public bool IsForced { get; set; }
         public string Format { get; set; }
         public int Index { get; set; }
+        public SubtitleDeliveryMethod DeliveryMethod { get; set; }
+        public bool IsExternalUrl { get; set; }
     }
 }

+ 47 - 0
MediaBrowser.Model/Dto/MediaSourceInfo.cs

@@ -24,6 +24,13 @@ namespace MediaBrowser.Model.Dto
         public bool ReadAtNativeFramerate { get; set; }
         public bool SupportsTranscoding { get; set; }
         public bool SupportsDirectStream { get; set; }
+        public bool SupportsDirectPlay { get; set; }
+
+        public bool RequiresOpening { get; set; }
+        public string OpenToken { get; set; }
+        public bool RequiresClosing { get; set; }
+        public string LiveStreamId { get; set; }
+        public int? BufferMs { get; set; }
 
         public VideoType? VideoType { get; set; }
 
@@ -41,6 +48,10 @@ namespace MediaBrowser.Model.Dto
         public TransportStreamTimestamp? Timestamp { get; set; }
         public Dictionary<string, string> RequiredHttpHeaders { get; set; }
 
+        public string TranscodingUrl { get; set; }
+        public string TranscodingSubProtocol { get; set; }
+        public string TranscodingContainer { get; set; }
+
         public MediaSourceInfo()
         {
             Formats = new List<string>();
@@ -49,6 +60,7 @@ namespace MediaBrowser.Model.Dto
             PlayableStreamFileNames = new List<string>();
             SupportsTranscoding = true;
             SupportsDirectStream = true;
+            SupportsDirectPlay = true;
         }
 
         public int? DefaultAudioStreamIndex { get; set; }
@@ -123,5 +135,40 @@ namespace MediaBrowser.Model.Dto
 
             return null;
         }
+
+        public int? GetStreamCount(MediaStreamType type)
+        {
+            int numMatches = 0;
+            int numStreams = 0;
+
+            foreach (MediaStream i in MediaStreams)
+            {
+                numStreams++;
+                if (i.Type == type)
+                {
+                    numMatches++;
+                }
+            }
+
+            if (numStreams == 0)
+            {
+                return null;
+            }
+
+            return numMatches;
+        }
+
+        public bool? IsSecondaryAudio(MediaStream stream)
+        {
+            foreach (MediaStream currentStream in MediaStreams)
+            {
+                if (currentStream.Type == MediaStreamType.Audio)
+                {
+                    return currentStream.Index != stream.Index;
+                }
+            }
+
+            return null;
+        }
     }
 }

+ 1 - 1
MediaBrowser.Model/Dto/MediaSourceType.cs

@@ -4,6 +4,6 @@ namespace MediaBrowser.Model.Dto
     {
         Default = 0,
         Grouping = 1,
-        Cache = 2
+        Placeholder = 2
     }
 }

+ 11 - 0
MediaBrowser.Model/Dto/NameValuePair.cs

@@ -3,6 +3,17 @@ namespace MediaBrowser.Model.Dto
 {
     public class NameValuePair
     {
+        public NameValuePair()
+        {
+            
+        }
+
+        public NameValuePair(string name, string value)
+        {
+            Name = name;
+            Value = value;
+        }
+
         /// <summary>
         /// Gets or sets the name.
         /// </summary>

+ 35 - 1
MediaBrowser.Model/Entities/MediaStream.cs

@@ -1,4 +1,5 @@
-using MediaBrowser.Model.Extensions;
+using MediaBrowser.Model.Dlna;
+using MediaBrowser.Model.Extensions;
 using System.Diagnostics;
 
 namespace MediaBrowser.Model.Entities
@@ -129,18 +130,45 @@ namespace MediaBrowser.Model.Entities
         /// <value>The index.</value>
         public int Index { get; set; }
 
+        /// <summary>
+        /// Gets or sets the score.
+        /// </summary>
+        /// <value>The score.</value>
+        public int? Score { get; set; }
+
         /// <summary>
         /// Gets or sets a value indicating whether this instance is external.
         /// </summary>
         /// <value><c>true</c> if this instance is external; otherwise, <c>false</c>.</value>
         public bool IsExternal { get; set; }
 
+        /// <summary>
+        /// Gets or sets the method.
+        /// </summary>
+        /// <value>The method.</value>
+        public SubtitleDeliveryMethod? DeliveryMethod { get; set; }
+        /// <summary>
+        /// Gets or sets the delivery URL.
+        /// </summary>
+        /// <value>The delivery URL.</value>
+        public string DeliveryUrl { get; set; }
+        /// <summary>
+        /// Gets or sets a value indicating whether this instance is external URL.
+        /// </summary>
+        /// <value><c>null</c> if [is external URL] contains no value, <c>true</c> if [is external URL]; otherwise, <c>false</c>.</value>
+        public bool? IsExternalUrl { get; set; }
+
         public bool IsTextSubtitleStream
         {
             get
             {
                 if (Type != MediaStreamType.Subtitle) return false;
 
+                if (string.IsNullOrEmpty(Codec) && !IsExternal)
+                {
+                    return false;
+                }
+
                 return IsTextFormat(Codec);
             }
         }
@@ -168,6 +196,12 @@ namespace MediaBrowser.Model.Entities
         /// <value>The filename.</value>
         public string Path { get; set; }
 
+        /// <summary>
+        /// Gets or sets the external identifier.
+        /// </summary>
+        /// <value>The external identifier.</value>
+        public string ExternalId { get; set; }
+
         /// <summary>
         /// Gets or sets the pixel format.
         /// </summary>

+ 6 - 0
MediaBrowser.Model/LiveTv/ProgramQuery.cs

@@ -53,6 +53,12 @@ namespace MediaBrowser.Model.LiveTv
         /// <remarks>If set to null, all programs will be returned</remarks>
         public bool? IsMovie { get; set; }
 
+        /// <summary>
+        /// Gets or sets a value indicating whether this instance is sports.
+        /// </summary>
+        /// <value><c>null</c> if [is sports] contains no value, <c>true</c> if [is sports]; otherwise, <c>false</c>.</value>
+        public bool? IsSports { get; set; }
+        
         /// <summary>
         /// Skips over a given number of items within the results. Use for paging.
         /// </summary>

+ 5 - 0
MediaBrowser.Model/LiveTv/RecommendedProgramQuery.cs

@@ -31,5 +31,10 @@
         /// </summary>
         /// <value><c>null</c> if [is movie] contains no value, <c>true</c> if [is movie]; otherwise, <c>false</c>.</value>
         public bool? IsMovie { get; set; }
+        /// <summary>
+        /// Gets or sets a value indicating whether this instance is sports.
+        /// </summary>
+        /// <value><c>null</c> if [is sports] contains no value, <c>true</c> if [is sports]; otherwise, <c>false</c>.</value>
+        public bool? IsSports { get; set; }
     }
 }

+ 5 - 1
MediaBrowser.Model/MediaBrowser.Model.csproj

@@ -126,6 +126,7 @@
     <Compile Include="Devices\DevicesOptions.cs" />
     <Compile Include="Dlna\EncodingContext.cs" />
     <Compile Include="Dlna\ILocalPlayer.cs" />
+    <Compile Include="Dlna\StreamInfoSorter.cs" />
     <Compile Include="Dlna\NullLocalPlayer.cs" />
     <Compile Include="Dlna\PlaybackErrorCode.cs" />
     <Compile Include="Dlna\PlaybackException.cs" />
@@ -140,7 +141,10 @@
     <Compile Include="Dto\MetadataEditorInfo.cs" />
     <Compile Include="Dto\NameIdPair.cs" />
     <Compile Include="Dto\NameValuePair.cs" />
-    <Compile Include="MediaInfo\LiveMediaInfoResult.cs" />
+    <Compile Include="MediaInfo\LiveStreamRequest.cs" />
+    <Compile Include="MediaInfo\LiveStreamResponse.cs" />
+    <Compile Include="MediaInfo\PlaybackInfoRequest.cs" />
+    <Compile Include="MediaInfo\PlaybackInfoResponse.cs" />
     <Compile Include="Dto\MediaSourceType.cs" />
     <Compile Include="Configuration\DynamicDayOfWeek.cs" />
     <Compile Include="Entities\ExtraType.cs" />

+ 36 - 0
MediaBrowser.Model/MediaInfo/LiveStreamRequest.cs

@@ -0,0 +1,36 @@
+using MediaBrowser.Model.Dlna;
+
+namespace MediaBrowser.Model.MediaInfo
+{
+    public class LiveStreamRequest
+    {
+        public string OpenToken { get; set; }
+        public string UserId { get; set; }
+        public string PlaySessionId { get; set; }
+        public int? MaxStreamingBitrate { get; set; }
+        public long? StartTimeTicks { get; set; }
+        public int? AudioStreamIndex { get; set; }
+        public int? SubtitleStreamIndex { get; set; }
+        public string ItemId { get; set; }
+        public DeviceProfile DeviceProfile { get; set; }
+
+        public LiveStreamRequest()
+        {
+
+        }
+
+        public LiveStreamRequest(AudioOptions options)
+        {
+            MaxStreamingBitrate = options.MaxBitrate;
+            ItemId = options.ItemId;
+            DeviceProfile = options.Profile;
+
+            VideoOptions videoOptions = options as VideoOptions;
+            if (videoOptions != null)
+            {
+                AudioStreamIndex = videoOptions.AudioStreamIndex;
+                SubtitleStreamIndex = videoOptions.SubtitleStreamIndex;
+            }
+        }
+    }
+}

+ 9 - 0
MediaBrowser.Model/MediaInfo/LiveStreamResponse.cs

@@ -0,0 +1,9 @@
+using MediaBrowser.Model.Dto;
+
+namespace MediaBrowser.Model.MediaInfo
+{
+    public class LiveStreamResponse
+    {
+        public MediaSourceInfo MediaSource { get; set; }
+    }
+}

+ 25 - 0
MediaBrowser.Model/MediaInfo/PlaybackInfoRequest.cs

@@ -0,0 +1,25 @@
+using MediaBrowser.Model.Dlna;
+
+namespace MediaBrowser.Model.MediaInfo
+{
+    public class PlaybackInfoRequest
+    {
+        public string Id { get; set; }
+
+        public string UserId { get; set; }
+
+        public int? MaxStreamingBitrate { get; set; }
+
+        public long? StartTimeTicks { get; set; }
+
+        public int? AudioStreamIndex { get; set; }
+
+        public int? SubtitleStreamIndex { get; set; }
+
+        public string MediaSourceId { get; set; }
+
+        public string LiveStreamId { get; set; }
+        
+        public DeviceProfile DeviceProfile { get; set; }
+    }
+}

+ 5 - 5
MediaBrowser.Model/MediaInfo/LiveMediaInfoResult.cs → MediaBrowser.Model/MediaInfo/PlaybackInfoResponse.cs

@@ -4,7 +4,7 @@ using System.Collections.Generic;
 
 namespace MediaBrowser.Model.MediaInfo
 {
-    public class LiveMediaInfoResult
+    public class PlaybackInfoResponse
     {
         /// <summary>
         /// Gets or sets the media sources.
@@ -13,10 +13,10 @@ namespace MediaBrowser.Model.MediaInfo
         public List<MediaSourceInfo> MediaSources { get; set; }
 
         /// <summary>
-        /// Gets or sets the stream identifier.
+        /// Gets or sets the play session identifier.
         /// </summary>
-        /// <value>The stream identifier.</value>
-        public string StreamId { get; set; }
+        /// <value>The play session identifier.</value>
+        public string PlaySessionId { get; set; }
 
         /// <summary>
         /// Gets or sets the error code.
@@ -24,7 +24,7 @@ namespace MediaBrowser.Model.MediaInfo
         /// <value>The error code.</value>
         public PlaybackErrorCode? ErrorCode { get; set; }
 
-        public LiveMediaInfoResult()
+        public PlaybackInfoResponse()
         {
             MediaSources = new List<MediaSourceInfo>();
         }

+ 3 - 2
MediaBrowser.Model/Notifications/NotificationOptions.cs

@@ -1,5 +1,6 @@
 using MediaBrowser.Model.Configuration;
 using MediaBrowser.Model.Extensions;
+using MediaBrowser.Model.Users;
 
 namespace MediaBrowser.Model.Notifications
 {
@@ -106,7 +107,7 @@ namespace MediaBrowser.Model.Notifications
                    !ListHelper.ContainsIgnoreCase(opt.DisabledMonitorUsers, userId);
         }
 
-        public bool IsEnabledToSendToUser(string type, string userId, UserConfiguration userConfig)
+        public bool IsEnabledToSendToUser(string type, string userId, UserPolicy userPolicy)
         {
             NotificationOption opt = GetOptions(type);
 
@@ -117,7 +118,7 @@ namespace MediaBrowser.Model.Notifications
                     return true;
                 }
 
-                if (opt.SendToUserMode == SendToUserType.Admins && userConfig.IsAdministrator)
+                if (opt.SendToUserMode == SendToUserType.Admins && userPolicy.IsAdministrator)
                 {
                     return true;
                 }

+ 10 - 0
MediaBrowser.Model/Querying/ItemQuery.cs

@@ -281,6 +281,13 @@ namespace MediaBrowser.Model.Querying
         public int? ImageTypeLimit { get; set; }
         public ImageType[] EnableImageTypes { get; set; }
 
+        [Obsolete]
+        public string[] Artists { get; set; }
+        [Obsolete]
+        public string[] Studios { get; set; }
+        [Obsolete]
+        public string Person { get; set; }
+
         /// <summary>
         /// Initializes a new instance of the <see cref="ItemQuery" /> class.
         /// </summary>
@@ -299,6 +306,9 @@ namespace MediaBrowser.Model.Querying
 
             VideoTypes = new VideoType[] { };
 
+            Artists = new string[] { };
+            Studios = new string[] { };
+            
             Genres = new string[] { };
             StudioIds = new string[] { };
             IncludeItemTypes = new string[] { };

+ 1 - 0
MediaBrowser.Model/Querying/ItemSortBy.cs

@@ -43,6 +43,7 @@ namespace MediaBrowser.Model.Querying
         /// The premiere date
         /// </summary>
         public const string PremiereDate = "PremiereDate";
+        public const string StartDate = "StartDate";
         /// <summary>
         /// The sort name
         /// </summary>

+ 10 - 0
MediaBrowser.Model/Session/PlaybackProgressInfo.cs

@@ -78,5 +78,15 @@ namespace MediaBrowser.Model.Session
         /// </summary>
         /// <value>The play method.</value>
         public PlayMethod PlayMethod { get; set; }
+        /// <summary>
+        /// Gets or sets the live stream identifier.
+        /// </summary>
+        /// <value>The live stream identifier.</value>
+        public string LiveStreamId { get; set; }
+        /// <summary>
+        /// Gets or sets the play session identifier.
+        /// </summary>
+        /// <value>The play session identifier.</value>
+        public string PlaySessionId { get; set; }
     }
 }

+ 10 - 0
MediaBrowser.Model/Session/PlaybackStopInfo.cs

@@ -36,5 +36,15 @@ namespace MediaBrowser.Model.Session
         /// </summary>
         /// <value>The position ticks.</value>
         public long? PositionTicks { get; set; }
+        /// <summary>
+        /// Gets or sets the live stream identifier.
+        /// </summary>
+        /// <value>The live stream identifier.</value>
+        public string LiveStreamId { get; set; }
+        /// <summary>
+        /// Gets or sets the play session identifier.
+        /// </summary>
+        /// <value>The play session identifier.</value>
+        public string PlaySessionId { get; set; }
     }
 }

+ 11 - 0
MediaBrowser.Model/Sync/LocalItem.cs

@@ -31,13 +31,24 @@ namespace MediaBrowser.Model.Sync
         /// <value>The item identifier.</value>
         public string ItemId { get; set; }
         /// <summary>
+        /// Gets or sets the synchronize job item identifier.
+        /// </summary>
+        /// <value>The synchronize job item identifier.</value>
+        public string SyncJobItemId { get; set; }
+        /// <summary>
         /// Gets or sets the user ids with access.
         /// </summary>
         /// <value>The user ids with access.</value>
         public List<string> UserIdsWithAccess { get; set; }
+        /// <summary>
+        /// Gets or sets the additional files.
+        /// </summary>
+        /// <value>The additional files.</value>
+        public List<string> AdditionalFiles { get; set; }
 
         public LocalItem()
         {
+            AdditionalFiles = new List<string>();
             UserIdsWithAccess = new List<string>();
         }
     }

+ 1 - 0
MediaBrowser.Model/Sync/SyncDataRequest.cs

@@ -6,6 +6,7 @@ namespace MediaBrowser.Model.Sync
     {
         public List<string> LocalItemIds { get; set; }
         public List<string> OfflineUserIds { get; set; }
+        public List<string> SyncJobItemIds { get; set; }
 
         public string TargetId { get; set; }
 

+ 1 - 2
MediaBrowser.Model/Users/UserPolicy.cs

@@ -32,8 +32,6 @@ namespace MediaBrowser.Model.Users
         public bool EnableUserPreferenceAccess { get; set; }
         public AccessSchedule[] AccessSchedules { get; set; }
         public UnratedItem[] BlockUnratedItems { get; set; }
-        public string[] BlockedMediaFolders { get; set; }
-        public string[] BlockedChannels { get; set; }
         public bool EnableRemoteControlOfOtherUsers { get; set; }
         public bool EnableSharedDeviceControl { get; set; }
 
@@ -63,6 +61,7 @@ namespace MediaBrowser.Model.Users
         
         public UserPolicy()
         {
+            EnableSync = true;
             EnableLiveTvManagement = true;
             EnableMediaPlayback = true;
             EnableLiveTvAccess = true;

+ 1 - 1
MediaBrowser.Providers/MediaInfo/AudioImageProvider.cs

@@ -96,7 +96,7 @@ namespace MediaBrowser.Providers.MediaInfo
             var album = item.Parent as MusicAlbum;
 
             var filename = item.Album ?? string.Empty;
-            filename += item.Artists.FirstOrDefault() ?? string.Empty;
+            filename += string.Join(",", item.Artists.ToArray());
             filename += album == null ? item.Id.ToString("N") + "_primary" + item.DateModified.Ticks : album.Id.ToString("N") + album.DateModified.Ticks + "_primary";
 
             filename = filename.GetMD5() + ".jpg";

+ 5 - 4
MediaBrowser.Providers/MediaInfo/SubtitleScheduledTask.cs

@@ -6,15 +6,14 @@ using MediaBrowser.Controller.Entities.Movies;
 using MediaBrowser.Controller.Entities.TV;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Subtitles;
-using MediaBrowser.Model.Configuration;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Logging;
+using MediaBrowser.Model.Providers;
 using System;
 using System.Collections.Generic;
 using System.Linq;
 using System.Threading;
 using System.Threading.Tasks;
-using MediaBrowser.Model.Providers;
 
 namespace MediaBrowser.Providers.MediaInfo
 {
@@ -23,14 +22,16 @@ namespace MediaBrowser.Providers.MediaInfo
         private readonly ILibraryManager _libraryManager;
         private readonly IServerConfigurationManager _config;
         private readonly ISubtitleManager _subtitleManager;
+        private readonly IMediaSourceManager _mediaSourceManager;
         private readonly ILogger _logger;
 
-        public SubtitleScheduledTask(ILibraryManager libraryManager, IServerConfigurationManager config, ISubtitleManager subtitleManager, ILogger logger)
+        public SubtitleScheduledTask(ILibraryManager libraryManager, IServerConfigurationManager config, ISubtitleManager subtitleManager, ILogger logger, IMediaSourceManager mediaSourceManager)
         {
             _libraryManager = libraryManager;
             _config = config;
             _subtitleManager = subtitleManager;
             _logger = logger;
+            _mediaSourceManager = mediaSourceManager;
         }
 
         public string Name
@@ -107,7 +108,7 @@ namespace MediaBrowser.Providers.MediaInfo
                 (options.DownloadMovieSubtitles &&
                 video is Movie))
             {
-                var mediaStreams = video.GetMediaSources(false).First().MediaStreams;
+                var mediaStreams = _mediaSourceManager.GetStaticMediaSources(video, false).First().MediaStreams;
 
                 var downloadedLanguages = await new SubtitleDownloader(_logger,
                     _subtitleManager)

+ 26 - 25
MediaBrowser.Server.Implementations/Channels/ChannelDownloadScheduledTask.cs

@@ -169,29 +169,32 @@ namespace MediaBrowser.Server.Implementations.Channels
 
             foreach (var item in result.Items)
             {
-                var channelItem = (IChannelItem)item;
+                var channelItem = item as IChannelMediaItem;
 
-                var channelFeatures = _manager.GetChannelFeatures(channelItem.ChannelId);
-
-                if (channelFeatures.SupportsContentDownloading)
+                if (channelItem != null)
                 {
-                    if (options.DownloadingChannels.Contains(channelItem.ChannelId))
+                    var channelFeatures = _manager.GetChannelFeatures(channelItem.ChannelId);
+
+                    if (channelFeatures.SupportsContentDownloading)
                     {
-                        try
+                        if (options.DownloadingChannels.Contains(channelItem.ChannelId))
                         {
-                            await DownloadChannelItem(item, options, cancellationToken, path);
-                        }
-                        catch (OperationCanceledException)
-                        {
-                            break;
-                        }
-                        catch (ChannelDownloadException)
-                        {
-                            // Logged at lower levels
-                        }
-                        catch (Exception ex)
-                        {
-                            _logger.ErrorException("Error downloading channel content for {0}", ex, item.Name);
+                            try
+                            {
+                                await DownloadChannelItem(channelItem, options, cancellationToken, path);
+                            }
+                            catch (OperationCanceledException)
+                            {
+                                break;
+                            }
+                            catch (ChannelDownloadException)
+                            {
+                                // Logged at lower levels
+                            }
+                            catch (Exception ex)
+                            {
+                                _logger.ErrorException("Error downloading channel content for {0}", ex, item.Name);
+                            }
                         }
                     }
                 }
@@ -210,13 +213,13 @@ namespace MediaBrowser.Server.Implementations.Channels
             return channelOptions.DownloadSizeLimit;
         }
 
-        private async Task DownloadChannelItem(BaseItem item,
+        private async Task DownloadChannelItem(IChannelMediaItem item,
             ChannelOptions channelOptions,
             CancellationToken cancellationToken,
             string path)
         {
             var itemId = item.Id.ToString("N");
-            var sources = await _manager.GetChannelItemMediaSources(itemId, false, cancellationToken)
+            var sources = await _manager.GetStaticMediaSources(item, true, cancellationToken)
                 .ConfigureAwait(false);
 
             var cachedVersions = sources.Where(i => i.Protocol == MediaProtocol.File).ToList();
@@ -237,11 +240,9 @@ namespace MediaBrowser.Server.Implementations.Channels
                 }
             }
 
-            var channelItem = (IChannelMediaItem)item;
-
-            var destination = Path.Combine(path, channelItem.ChannelId, itemId);
+            var destination = Path.Combine(path, item.ChannelId, itemId);
 
-            await _manager.DownloadChannelItem(channelItem, destination, new Progress<double>(), cancellationToken)
+            await _manager.DownloadChannelItem(item, destination, new Progress<double>(), cancellationToken)
                     .ConfigureAwait(false);
 
             await RefreshMediaSourceItem(destination, cancellationToken).ConfigureAwait(false);

Some files were not shown because too many files changed in this diff