Ver código fonte

implement modular media sources

Luke Pulverenti 10 anos atrás
pai
commit
bd2ea703e3
42 arquivos alterados com 697 adições e 366 exclusões
  1. 71 184
      MediaBrowser.Api/Playback/BaseStreamingService.cs
  2. 3 3
      MediaBrowser.Api/Playback/Dash/MpegDashService.cs
  3. 3 3
      MediaBrowser.Api/Playback/Hls/BaseHlsService.cs
  4. 6 5
      MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs
  5. 1 1
      MediaBrowser.Api/Playback/Hls/VideoHlsService.cs
  6. 76 32
      MediaBrowser.Api/Playback/MediaInfoService.cs
  7. 3 3
      MediaBrowser.Api/Playback/Progressive/AudioService.cs
  8. 1 1
      MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs
  9. 3 3
      MediaBrowser.Api/Playback/Progressive/VideoService.cs
  10. 9 14
      MediaBrowser.Api/Playback/StreamState.cs
  11. 1 1
      MediaBrowser.Api/Subtitles/SubtitleService.cs
  12. 11 5
      MediaBrowser.Controller/Channels/ChannelAudioItem.cs
  13. 11 5
      MediaBrowser.Controller/Channels/ChannelVideoItem.cs
  14. 3 3
      MediaBrowser.Controller/Channels/IChannelManager.cs
  15. 24 0
      MediaBrowser.Controller/Library/IMediaSourceManager.cs
  16. 16 0
      MediaBrowser.Controller/Library/IMediaSourceProvider.cs
  17. 3 1
      MediaBrowser.Controller/LiveTv/ILiveTvItem.cs
  18. 19 2
      MediaBrowser.Controller/LiveTv/LiveTvAudioRecording.cs
  19. 1 1
      MediaBrowser.Controller/LiveTv/LiveTvChannel.cs
  20. 20 3
      MediaBrowser.Controller/LiveTv/LiveTvVideoRecording.cs
  21. 1 1
      MediaBrowser.Dlna/PlayTo/PlayToController.cs
  22. 22 2
      MediaBrowser.Model/Dlna/StreamBuilder.cs
  23. 26 1
      MediaBrowser.Model/Dlna/SubtitleProfile.cs
  24. 5 0
      MediaBrowser.Model/Dto/MediaSourceInfo.cs
  25. 1 1
      MediaBrowser.Model/Dto/MediaSourceType.cs
  26. 5 0
      MediaBrowser.Model/Entities/MediaStream.cs
  27. 0 2
      MediaBrowser.Model/MediaInfo/PlaybackInfoRequest.cs
  28. 1 1
      MediaBrowser.Providers/MediaInfo/AudioImageProvider.cs
  29. 6 8
      MediaBrowser.Server.Implementations/Channels/ChannelDownloadScheduledTask.cs
  30. 43 0
      MediaBrowser.Server.Implementations/Channels/ChannelDynamicMediaSourceProvider.cs
  31. 27 20
      MediaBrowser.Server.Implementations/Channels/ChannelManager.cs
  32. 126 22
      MediaBrowser.Server.Implementations/Library/MediaSourceManager.cs
  33. 5 4
      MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs
  34. 77 0
      MediaBrowser.Server.Implementations/LiveTv/LiveTvMediaSourceProvider.cs
  35. 2 0
      MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj
  36. 1 0
      MediaBrowser.Server.Implementations/Sync/MediaSync.cs
  37. 52 22
      MediaBrowser.Server.Implementations/Sync/SyncedMediaSourceProvider.cs
  38. 1 1
      MediaBrowser.Server.Startup.Common/ApplicationHost.cs
  39. 2 2
      Nuget/MediaBrowser.Common.Internal.nuspec
  40. 3 3
      Nuget/MediaBrowser.Common.nuspec
  41. 3 3
      Nuget/MediaBrowser.Model.Signed.nuspec
  42. 3 3
      Nuget/MediaBrowser.Server.Core.nuspec

+ 71 - 184
MediaBrowser.Api/Playback/BaseStreamingService.cs

@@ -1,6 +1,5 @@
 using MediaBrowser.Common.Extensions;
 using MediaBrowser.Common.IO;
-using MediaBrowser.Controller.Channels;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Devices;
 using MediaBrowser.Controller.Dlna;
@@ -65,7 +64,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 +73,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 +92,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,7 +124,7 @@ 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);
@@ -719,8 +715,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 +893,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 +910,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 +935,20 @@ 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 streamInfo = await LiveTvManager.GetChannelStream(state.Request.Id, cancellationTokenSource.Token).ConfigureAwait(false);
+                var mediaSource = await MediaSourceManager.OpenMediaSource(state.MediaSource.OpenKey, cancellationTokenSource.Token)
+                            .ConfigureAwait(false);
 
-                    state.LiveTvStreamId = streamInfo.Id;
+                AttachMediaSourceInfo(state, mediaSource, state.VideoRequest, state.RequestedUrl);
 
-                    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;
-                }
-
-                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";
-                    }
-                }
+                // TODO: This is only needed for live tv
+                await Task.Delay(1500, cancellationTokenSource.Token).ConfigureAwait(false);
             }
         }
 
@@ -1017,7 +970,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)
             {
@@ -1644,7 +1597,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 +1611,20 @@ 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)
-            {
-                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;
-
-                // Just to prevent this from being null and causing other methods to fail
-                state.MediaPath = string.Empty;
-            }
-            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;
-                    }
-                }
-
-            }
+            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));
+            
             var videoRequest = request as VideoStreamRequest;
 
-            AttachMediaStreamInfo(state, mediaStreams, videoRequest, url);
+            AttachMediaSourceInfo(state, mediaSource, videoRequest, url);
 
             var container = Path.GetExtension(state.RequestedUrl);
 
@@ -1801,15 +1665,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 +1673,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 +1722,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 +1760,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)

+ 3 - 3
MediaBrowser.Api/Playback/Dash/MpegDashService.cs

@@ -54,7 +54,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;
         }
@@ -447,7 +447,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 +461,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),

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

@@ -22,7 +22,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)
         {
         }
 
@@ -212,7 +212,7 @@ namespace MediaBrowser.Api.Playback.Hls
             }
         }
 
-        protected override string GetCommandLineArguments(string outputPath, string transcodingJobId, StreamState state, bool isEncoding)
+        protected override string GetCommandLineArguments(string outputPath, StreamState state, bool isEncoding)
         {
             var hlsVideoRequest = state.VideoRequest as GetHlsVideoStream;
 
@@ -240,7 +240,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),

+ 6 - 5
MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs

@@ -62,7 +62,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;
         }
@@ -414,7 +414,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 +685,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);
 
@@ -699,7 +700,7 @@ namespace MediaBrowser.Api.Playback.Hls
 
                 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}\"",
                     inputModifier,
-                    GetInputArgument(transcodingJobId, state),
+                    GetInputArgument(state),
                     threads,
                     GetMapArgs(state),
                     GetVideoArguments(state),
@@ -713,7 +714,7 @@ namespace MediaBrowser.Api.Playback.Hls
 
             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}\"",
                             inputModifier,
-                            GetInputArgument(transcodingJobId, state),
+                            GetInputArgument(state),
                             threads,
                             GetMapArgs(state),
                             GetVideoArguments(state),

+ 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)
         {
         }
 

+ 76 - 32
MediaBrowser.Api/Playback/MediaInfoService.cs

@@ -4,6 +4,7 @@ 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;
@@ -38,20 +39,23 @@ namespace MediaBrowser.Api.Playback
     [Route("/Items/{Id}/PlaybackInfo", "POST", Summary = "Gets live playback media info for an item")]
     public class GetPostedPlaybackInfo : PlaybackInfoRequest, IReturn<PlaybackInfoResponse>
     {
-        [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
+        [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
         public string Id { get; set; }
 
-        [ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")]
+        [ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")]
         public string UserId { get; set; }
 
-        [ApiMember(Name = "StartTimeTicks", Description = "Optional. Specify a starting offset, in ticks. 1 tick = 10000 ms", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
+        [ApiMember(Name = "StartTimeTicks", Description = "Optional. Specify a starting offset, in ticks. 1 tick = 10000 ms", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "POST")]
         public long? StartTimeTicks { get; set; }
 
-        [ApiMember(Name = "AudioStreamIndex", Description = "Optional. The index of the audio stream to use. If omitted the first audio stream will be used.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
+        [ApiMember(Name = "AudioStreamIndex", Description = "Optional. The index of the audio stream to use. If omitted the first audio stream will be used.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "POST")]
         public int? AudioStreamIndex { get; set; }
 
-        [ApiMember(Name = "SubtitleStreamIndex", Description = "Optional. The index of the subtitle stream to use. If omitted no subtitles will be used.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
+        [ApiMember(Name = "SubtitleStreamIndex", Description = "Optional. The index of the subtitle stream to use. If omitted no subtitles will be used.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "POST")]
         public int? SubtitleStreamIndex { get; set; }
+
+        [ApiMember(Name = "MediaSourceId", Description = "The media version id, if playing an alternate version", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
+        public string MediaSourceId { get; set; }
     }
 
     [Authenticated]
@@ -82,7 +86,7 @@ namespace MediaBrowser.Api.Playback
 
         public async Task<object> Post(GetPostedPlaybackInfo request)
         {
-            var info = await GetPlaybackInfo(request.Id, request.UserId, request.MediaSource).ConfigureAwait(false);
+            var info = await GetPlaybackInfo(request.Id, request.UserId, request.MediaSourceId).ConfigureAwait(false);
             var authInfo = AuthorizationContext.GetAuthorizationInfo(Request);
 
             var profile = request.DeviceProfile;
@@ -97,36 +101,36 @@ namespace MediaBrowser.Api.Playback
 
             if (profile != null)
             {
-                var mediaSourceId = request.MediaSource == null ? null : request.MediaSource.Id;
+                var mediaSourceId = request.MediaSourceId;
                 SetDeviceSpecificData(request.Id, info, profile, authInfo, null, request.StartTimeTicks ?? 0, mediaSourceId, request.AudioStreamIndex, request.SubtitleStreamIndex);
             }
 
             return ToOptimizedResult(info);
         }
 
-        private async Task<PlaybackInfoResponse> GetPlaybackInfo(string id, string userId, MediaSourceInfo mediaSource = null)
+        private async Task<PlaybackInfoResponse> GetPlaybackInfo(string id, string userId, string mediaSourceId = null)
         {
             var result = new PlaybackInfoResponse();
 
-            if (mediaSource == null)
+            IEnumerable<MediaSourceInfo> mediaSources;
+
+            try
             {
-                IEnumerable<MediaSourceInfo> mediaSources;
+                mediaSources = await _mediaSourceManager.GetPlayackMediaSources(id, userId, true, CancellationToken.None).ConfigureAwait(false);
+            }
+            catch (PlaybackException ex)
+            {
+                mediaSources = new List<MediaSourceInfo>();
+                result.ErrorCode = ex.ErrorCode;
+            }
 
-                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();
 
-                result.MediaSources = mediaSources.ToList();
-            }
-            else
+            if (!string.IsNullOrWhiteSpace(mediaSourceId))
             {
-                result.MediaSources = new List<MediaSourceInfo> { mediaSource };
+                result.MediaSources = result.MediaSources
+                    .Where(i => string.Equals(i.Id, mediaSourceId, StringComparison.OrdinalIgnoreCase))
+                    .ToList();
             }
 
             if (result.MediaSources.Count == 0)
@@ -185,9 +189,9 @@ namespace MediaBrowser.Api.Playback
                     mediaSource.SupportsDirectStream = true;
 
                     // The MediaSource supports direct stream, now test to see if the client supports it
-                    var streamInfo = item is Video ?
-                        streamBuilder.BuildVideoItem(options) :
-                        streamBuilder.BuildAudioItem(options);
+                    var streamInfo = string.Equals(item.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase) ?
+                        streamBuilder.BuildAudioItem(options) :
+                        streamBuilder.BuildVideoItem(options);
 
                     if (streamInfo == null || !streamInfo.IsDirectStream)
                     {
@@ -201,9 +205,9 @@ namespace MediaBrowser.Api.Playback
                 if (mediaSource.SupportsDirectStream)
                 {
                     // The MediaSource supports direct stream, now test to see if the client supports it
-                    var streamInfo = item is Video ?
-                        streamBuilder.BuildVideoItem(options) :
-                        streamBuilder.BuildAudioItem(options);
+                    var streamInfo = string.Equals(item.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase) ?
+                        streamBuilder.BuildAudioItem(options) :
+                        streamBuilder.BuildVideoItem(options);
 
                     if (streamInfo == null || !streamInfo.IsDirectStream)
                     {
@@ -214,9 +218,9 @@ namespace MediaBrowser.Api.Playback
                 if (mediaSource.SupportsTranscoding)
                 {
                     // The MediaSource supports direct stream, now test to see if the client supports it
-                    var streamInfo = item is Video ?
-                        streamBuilder.BuildVideoItem(options) :
-                        streamBuilder.BuildAudioItem(options);
+                    var streamInfo = string.Equals(item.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase) ?
+                        streamBuilder.BuildAudioItem(options) :
+                        streamBuilder.BuildVideoItem(options);
 
                     if (streamInfo != null && streamInfo.PlayMethod == PlayMethod.Transcode)
                     {
@@ -227,6 +231,46 @@ namespace MediaBrowser.Api.Playback
                     }
                 }
             }
+
+            SortMediaSources(result);
+        }
+
+        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),

+ 9 - 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)
             {
                 try
                 {
-                    await _liveTvManager.CloseLiveStream(LiveTvStreamId, CancellationToken.None).ConfigureAwait(false);
+                    await _mediaSourceManager.CloseMediaSource(MediaSource.CloseKey, CancellationToken.None).ConfigureAwait(false);
                 }
                 catch (Exception ex)
                 {
-                    _logger.ErrorException("Error closing live tv stream", ex);
+                    _logger.ErrorException("Error closing media source", ex);
                 }
             }
         }

+ 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

+ 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;
         }

+ 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.

+ 24 - 0
MediaBrowser.Controller/Library/IMediaSourceManager.cs

@@ -64,6 +64,14 @@ namespace MediaBrowser.Controller.Library
         /// <returns>IEnumerable&lt;MediaSourceInfo&gt;.</returns>
         IEnumerable<MediaSourceInfo> GetStaticMediaSources(IHasMediaSources item, bool enablePathSubstitution, User user);
 
+        /// <summary>
+        /// Gets the static media sources.
+        /// </summary>
+        /// <param name="item">The item.</param>
+        /// <param name="enablePathSubstitution">if set to <c>true</c> [enable path substitution].</param>
+        /// <returns>IEnumerable&lt;MediaSourceInfo&gt;.</returns>
+        IEnumerable<MediaSourceInfo> GetStaticMediaSources(IHasMediaSources item, bool enablePathSubstitution);
+        
         /// <summary>
         /// Gets the static media source.
         /// </summary>
@@ -72,5 +80,21 @@ 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="openKey">The open key.</param>
+        /// <param name="cancellationToken">The cancellation token.</param>
+        /// <returns>Task&lt;MediaSourceInfo&gt;.</returns>
+        Task<MediaSourceInfo> OpenMediaSource(string openKey, CancellationToken cancellationToken);
+
+        /// <summary>
+        /// Closes the media source.
+        /// </summary>
+        /// <param name="closeKey">The close key.</param>
+        /// <param name="cancellationToken">The cancellation token.</param>
+        /// <returns>Task.</returns>
+        Task CloseMediaSource(string closeKey, 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="openKey">The open key.</param>
+        /// <param name="cancellationToken">The cancellation token.</param>
+        /// <returns>Task&lt;MediaSourceInfo&gt;.</returns>
+        Task<MediaSourceInfo> OpenMediaSource(string openKey, CancellationToken cancellationToken);
+
+        /// <summary>
+        /// Closes the media source.
+        /// </summary>
+        /// <param name="closeKey">The close key.</param>
+        /// <param name="cancellationToken">The cancellation token.</param>
+        /// <returns>Task.</returns>
+        Task CloseMediaSource(string closeKey, 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; }
     }
 }

+ 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;
+        }
     }
 }

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

@@ -127,7 +127,7 @@ namespace MediaBrowser.Controller.LiveTv
                 Name = Name,
                 Path = Path,
                 RunTimeTicks = RunTimeTicks,
-                Type = MediaSourceType.Default
+                Type = MediaSourceType.Placeholder
             };
 
             list.Add(info);

+ 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;
+        }
     }
 }

+ 1 - 1
MediaBrowser.Dlna/PlayTo/PlayToController.cs

@@ -476,7 +476,7 @@ namespace MediaBrowser.Dlna.PlayTo
             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);

+ 22 - 2
MediaBrowser.Model/Dlna/StreamBuilder.cs

@@ -239,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;
@@ -263,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);
                 }
@@ -293,7 +303,7 @@ namespace MediaBrowser.Model.Dlna
             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)
@@ -604,6 +614,11 @@ namespace MediaBrowser.Model.Dlna
             // Look for an external profile that matches the stream type (text/graphical)
             foreach (SubtitleProfile profile in subtitleProfiles)
             {
+                if (!profile.SupportsLanguage(subtitleStream.Language))
+                {
+                    continue;
+                }
+
                 if (profile.Method == SubtitleDeliveryMethod.External && subtitleStream.IsTextSubtitleStream == MediaStream.IsTextFormat(profile.Format))
                 {
                     if (subtitleStream.SupportsExternalStream)
@@ -621,6 +636,11 @@ namespace MediaBrowser.Model.Dlna
 
             foreach (SubtitleProfile profile in subtitleProfiles)
             {
+                if (!profile.SupportsLanguage(subtitleStream.Language))
+                {
+                    continue;
+                }
+
                 if (profile.Method == SubtitleDeliveryMethod.Embed && subtitleStream.IsTextSubtitleStream == MediaStream.IsTextFormat(profile.Format))
                 {
                     return profile;

+ 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);
+        }
     }
 }

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

@@ -26,6 +26,11 @@ namespace MediaBrowser.Model.Dto
         public bool SupportsDirectStream { get; set; }
         public bool SupportsDirectPlay { get; set; }
 
+        public bool RequiresOpening { get; set; }
+        public string OpenKey { get; set; }
+        public bool RequiresClosing { get; set; }
+        public string CloseKey { get; set; }
+   
         public VideoType? VideoType { get; set; }
 
         public IsoType? IsoType { get; set; }

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

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

+ 5 - 0
MediaBrowser.Model/Entities/MediaStream.cs

@@ -141,6 +141,11 @@ namespace MediaBrowser.Model.Entities
             {
                 if (Type != MediaStreamType.Subtitle) return false;
 
+                if (string.IsNullOrEmpty(Codec) && !IsExternal)
+                {
+                    return false;
+                }
+
                 return IsTextFormat(Codec);
             }
         }

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

@@ -1,11 +1,9 @@
 using MediaBrowser.Model.Dlna;
-using MediaBrowser.Model.Dto;
 
 namespace MediaBrowser.Model.MediaInfo
 {
     public class PlaybackInfoRequest
     {
         public DeviceProfile DeviceProfile { get; set; }
-        public MediaSourceInfo MediaSource { get; set; }
     }
 }

+ 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";

+ 6 - 8
MediaBrowser.Server.Implementations/Channels/ChannelDownloadScheduledTask.cs

@@ -169,7 +169,7 @@ namespace MediaBrowser.Server.Implementations.Channels
 
             foreach (var item in result.Items)
             {
-                var channelItem = (IChannelItem)item;
+                var channelItem = (IChannelMediaItem)item;
 
                 var channelFeatures = _manager.GetChannelFeatures(channelItem.ChannelId);
 
@@ -179,7 +179,7 @@ namespace MediaBrowser.Server.Implementations.Channels
                     {
                         try
                         {
-                            await DownloadChannelItem(item, options, cancellationToken, path);
+                            await DownloadChannelItem(channelItem, options, cancellationToken, path);
                         }
                         catch (OperationCanceledException)
                         {
@@ -210,13 +210,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 +237,9 @@ namespace MediaBrowser.Server.Implementations.Channels
                 }
             }
 
-            var channelItem = (IChannelMediaItem)item;
+            var destination = Path.Combine(path, item.ChannelId, itemId);
 
-            var destination = Path.Combine(path, channelItem.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);

+ 43 - 0
MediaBrowser.Server.Implementations/Channels/ChannelDynamicMediaSourceProvider.cs

@@ -0,0 +1,43 @@
+using MediaBrowser.Controller.Channels;
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Library;
+using MediaBrowser.Model.Dto;
+using System;
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace MediaBrowser.Server.Implementations.Channels
+{
+    public class ChannelDynamicMediaSourceProvider : IMediaSourceProvider
+    {
+        private readonly ChannelManager _channelManager;
+
+        public ChannelDynamicMediaSourceProvider(IChannelManager channelManager)
+        {
+            _channelManager = (ChannelManager)channelManager;
+        }
+
+        public Task<IEnumerable<MediaSourceInfo>> GetMediaSources(IHasMediaSources item, CancellationToken cancellationToken)
+        {
+            var channelItem = item as IChannelMediaItem;
+
+            if (channelItem != null)
+            {
+                return _channelManager.GetDynamicMediaSources(channelItem, cancellationToken);
+            }
+
+            return Task.FromResult<IEnumerable<MediaSourceInfo>>(new List<MediaSourceInfo>());
+        }
+
+        public Task<MediaSourceInfo> OpenMediaSource(string openKey, CancellationToken cancellationToken)
+        {
+            throw new NotImplementedException();
+        }
+
+        public Task CloseMediaSource(string closeKey, CancellationToken cancellationToken)
+        {
+            throw new NotImplementedException();
+        }
+    }
+}

+ 27 - 20
MediaBrowser.Server.Implementations/Channels/ChannelManager.cs

@@ -241,10 +241,25 @@ namespace MediaBrowser.Server.Implementations.Channels
             return item;
         }
 
-        public async Task<IEnumerable<MediaSourceInfo>> GetChannelItemMediaSources(string id, bool includeDynamicSources, CancellationToken cancellationToken)
+        public async Task<IEnumerable<MediaSourceInfo>> GetStaticMediaSources(IChannelMediaItem item, bool includeCachedVersions, CancellationToken cancellationToken)
         {
-            var item = (IChannelMediaItem)_libraryManager.GetItemById(id);
+            IEnumerable<ChannelMediaInfo> results = item.ChannelMediaSources;
 
+            var sources = SortMediaInfoResults(results)
+                .Select(i => GetMediaSource(item, i))
+                .ToList();
+
+            if (includeCachedVersions)
+            {
+                var cachedVersions = GetCachedChannelItemMediaSources(item);
+                sources.InsertRange(0, cachedVersions);
+            }
+
+            return sources.Where(IsValidMediaSource);
+        }
+
+        public async Task<IEnumerable<MediaSourceInfo>> GetDynamicMediaSources(IChannelMediaItem item, CancellationToken cancellationToken)
+        {
             var channel = GetChannel(item.ChannelId);
             var channelPlugin = GetChannelProvider(channel);
 
@@ -252,24 +267,25 @@ namespace MediaBrowser.Server.Implementations.Channels
 
             IEnumerable<ChannelMediaInfo> results;
 
-            if (requiresCallback != null && includeDynamicSources)
+            if (requiresCallback != null)
             {
                 results = await GetChannelItemMediaSourcesInternal(requiresCallback, item.ExternalId, cancellationToken)
-                            .ConfigureAwait(false);
+                    .ConfigureAwait(false);
             }
             else
             {
-                results = item.ChannelMediaSources;
+                results = new List<ChannelMediaInfo>();
             }
 
-            var sources = SortMediaInfoResults(results).Select(i => GetMediaSource(item, i))
+            var list = SortMediaInfoResults(results)
+                .Select(i => GetMediaSource(item, i))
+                .Where(IsValidMediaSource)
                 .ToList();
 
             var cachedVersions = GetCachedChannelItemMediaSources(item);
+            list.InsertRange(0, cachedVersions);
 
-            sources.InsertRange(0, cachedVersions);
-
-            return sources.Where(IsValidMediaSource);
+            return list;
         }
 
         private readonly ConcurrentDictionary<string, Tuple<DateTime, List<ChannelMediaInfo>>> _channelItemMediaInfo =
@@ -297,14 +313,7 @@ namespace MediaBrowser.Server.Implementations.Channels
             return list;
         }
 
-        public IEnumerable<MediaSourceInfo> GetCachedChannelItemMediaSources(string id)
-        {
-            var item = (IChannelMediaItem)_libraryManager.GetItemById(id);
-
-            return GetCachedChannelItemMediaSources(item);
-        }
-
-        public IEnumerable<MediaSourceInfo> GetCachedChannelItemMediaSources(IChannelMediaItem item)
+        private IEnumerable<MediaSourceInfo> GetCachedChannelItemMediaSources(IChannelMediaItem item)
         {
             var filenamePrefix = item.Id.ToString("N");
             var parentPath = Path.Combine(ChannelDownloadPath, item.ChannelId);
@@ -339,7 +348,6 @@ namespace MediaBrowser.Server.Implementations.Channels
 
                             if (source != null)
                             {
-                                source.Type = MediaSourceType.Cache;
                                 return new[] { source };
                             }
                         }
@@ -1408,8 +1416,7 @@ namespace MediaBrowser.Server.Implementations.Channels
         public async Task DownloadChannelItem(IChannelMediaItem item, string destination,
             IProgress<double> progress, CancellationToken cancellationToken)
         {
-            var itemId = item.Id.ToString("N");
-            var sources = await GetChannelItemMediaSources(itemId, true, cancellationToken)
+            var sources = await GetDynamicMediaSources(item, cancellationToken)
                 .ConfigureAwait(false);
 
             var list = sources.Where(i => i.Protocol == MediaProtocol.Http).ToList();

+ 126 - 22
MediaBrowser.Server.Implementations/Library/MediaSourceManager.cs

@@ -1,4 +1,5 @@
-using MediaBrowser.Controller.Channels;
+using System.Collections.Concurrent;
+using MediaBrowser.Common.Extensions;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.MediaEncoding;
@@ -13,25 +14,24 @@ using System.IO;
 using System.Linq;
 using System.Threading;
 using System.Threading.Tasks;
+using MediaBrowser.Server.Implementations.LiveTv;
 
 namespace MediaBrowser.Server.Implementations.Library
 {
-    public class MediaSourceManager : IMediaSourceManager
+    public class MediaSourceManager : IMediaSourceManager, IDisposable
     {
         private readonly IItemRepository _itemRepo;
         private readonly IUserManager _userManager;
         private readonly ILibraryManager _libraryManager;
-        private readonly IChannelManager _channelManager;
 
         private IMediaSourceProvider[] _providers;
         private readonly ILogger _logger;
 
-        public MediaSourceManager(IItemRepository itemRepo, IUserManager userManager, ILibraryManager libraryManager, IChannelManager channelManager, ILogger logger)
+        public MediaSourceManager(IItemRepository itemRepo, IUserManager userManager, ILibraryManager libraryManager, ILogger logger)
         {
             _itemRepo = itemRepo;
             _userManager = userManager;
             _libraryManager = libraryManager;
-            _channelManager = channelManager;
             _logger = logger;
         }
 
@@ -133,24 +133,15 @@ namespace MediaBrowser.Server.Implementations.Library
             IEnumerable<MediaSourceInfo> mediaSources;
 
             var hasMediaSources = (IHasMediaSources)item;
-            var channelItem = item as IChannelMediaItem;
 
-            if (channelItem != null)
+            if (string.IsNullOrWhiteSpace(userId))
             {
-                mediaSources = await _channelManager.GetChannelItemMediaSources(id, true, cancellationToken)
-                        .ConfigureAwait(false);
+                mediaSources = hasMediaSources.GetMediaSources(enablePathSubstitution);
             }
             else
             {
-                if (string.IsNullOrWhiteSpace(userId))
-                {
-                    mediaSources = hasMediaSources.GetMediaSources(enablePathSubstitution);
-                }
-                else
-                {
-                    var user = _userManager.GetUserById(userId);
-                    mediaSources = GetStaticMediaSources(hasMediaSources, enablePathSubstitution, user);
-                }
+                var user = _userManager.GetUserById(userId);
+                mediaSources = GetStaticMediaSources(hasMediaSources, enablePathSubstitution, user);
             }
 
             var dynamicMediaSources = await GetDynamicMediaSources(hasMediaSources, cancellationToken).ConfigureAwait(false);
@@ -161,11 +152,16 @@ namespace MediaBrowser.Server.Implementations.Library
 
             foreach (var source in dynamicMediaSources)
             {
-                source.SupportsTranscoding = false;
-
                 if (source.Protocol == MediaProtocol.File)
                 {
                     source.SupportsDirectStream = File.Exists(source.Path);
+
+                    // TODO: Path substitution
+                }
+                else if (source.Protocol == MediaProtocol.Http)
+                {
+                    // TODO: Allow this when the source is plain http, e.g. not HLS or Mpeg Dash
+                    source.SupportsDirectStream = false;
                 }
                 else
                 {
@@ -175,7 +171,7 @@ namespace MediaBrowser.Server.Implementations.Library
                 list.Add(source);
             }
 
-            return SortMediaSources(list);
+            return SortMediaSources(list).Where(i => i.Type != MediaSourceType.Placeholder);
         }
 
         private async Task<IEnumerable<MediaSourceInfo>> GetDynamicMediaSources(IHasMediaSources item, CancellationToken cancellationToken)
@@ -190,7 +186,15 @@ namespace MediaBrowser.Server.Implementations.Library
         {
             try
             {
-                return await provider.GetMediaSources(item, cancellationToken).ConfigureAwait(false);
+                var sources = await provider.GetMediaSources(item, cancellationToken).ConfigureAwait(false);
+                var list = sources.ToList();
+
+                foreach (var mediaSource in list)
+                {
+                    SetKeyProperties(provider, mediaSource);
+                }
+
+                return list;
             }
             catch (Exception ex)
             {
@@ -199,6 +203,21 @@ namespace MediaBrowser.Server.Implementations.Library
             }
         }
 
+        private void SetKeyProperties(IMediaSourceProvider provider, MediaSourceInfo mediaSource)
+        {
+            var prefix = provider.GetType().FullName.GetMD5().ToString("N") + "|";
+
+            if (!string.IsNullOrWhiteSpace(mediaSource.OpenKey) && !mediaSource.OpenKey.StartsWith(prefix, StringComparison.OrdinalIgnoreCase))
+            {
+                mediaSource.OpenKey = prefix + mediaSource.OpenKey;
+            }
+
+            if (!string.IsNullOrWhiteSpace(mediaSource.CloseKey) && !mediaSource.CloseKey.StartsWith(prefix, StringComparison.OrdinalIgnoreCase))
+            {
+                mediaSource.CloseKey = prefix + mediaSource.CloseKey;
+            }
+        }
+
         public Task<IEnumerable<MediaSourceInfo>> GetPlayackMediaSources(string id, bool enablePathSubstitution, CancellationToken cancellationToken)
         {
             return GetPlayackMediaSources(id, null, enablePathSubstitution, cancellationToken);
@@ -294,5 +313,90 @@ namespace MediaBrowser.Server.Implementations.Library
         {
             return GetStaticMediaSources(item, enablePathSubstitution).FirstOrDefault(i => string.Equals(i.Id, mediaSourceId, StringComparison.OrdinalIgnoreCase));
         }
+
+        private readonly ConcurrentDictionary<string, string> _openStreams =
+         new ConcurrentDictionary<string, string>();
+        private readonly SemaphoreSlim _liveStreamSemaphore = new SemaphoreSlim(1, 1);
+        public async Task<MediaSourceInfo> OpenMediaSource(string openKey, CancellationToken cancellationToken)
+        {
+            await _liveStreamSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
+
+            try
+            {
+                var tuple = GetProvider(openKey);
+                var provider = tuple.Item1;
+
+                var mediaSource = await provider.OpenMediaSource(tuple.Item2, cancellationToken).ConfigureAwait(false);
+
+                SetKeyProperties(provider, mediaSource);
+
+                _openStreams.AddOrUpdate(mediaSource.CloseKey, mediaSource.CloseKey, (key, i) => mediaSource.CloseKey);
+                
+                return mediaSource;
+            }
+            finally
+            {
+                _liveStreamSemaphore.Release();
+            }
+        }
+
+        public async Task CloseMediaSource(string closeKey, CancellationToken cancellationToken)
+        {
+            await _liveStreamSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
+
+            try
+            {
+                var tuple = GetProvider(closeKey);
+
+                await tuple.Item1.OpenMediaSource(tuple.Item2, cancellationToken).ConfigureAwait(false);
+
+                string removedKey;
+                _openStreams.TryRemove(closeKey, out removedKey);
+            }
+            finally
+            {
+                _liveStreamSemaphore.Release();
+            }
+        }
+
+        private Tuple<IMediaSourceProvider, string> GetProvider(string key)
+        {
+            var keys = key.Split(new[] { '|' }, 2);
+
+            var provider = _providers.FirstOrDefault(i => string.Equals(i.GetType().FullName.GetMD5().ToString("N"), keys[0], StringComparison.OrdinalIgnoreCase));
+
+            return new Tuple<IMediaSourceProvider, string>(provider, keys[1]);
+        }
+
+        /// <summary>
+        /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
+        /// </summary>
+        public void Dispose()
+        {
+            Dispose(true);
+        }
+
+        private readonly object _disposeLock = new object();
+        /// <summary>
+        /// Releases unmanaged and - optionally - managed resources.
+        /// </summary>
+        /// <param name="dispose"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
+        protected virtual void Dispose(bool dispose)
+        {
+            if (dispose)
+            {
+                lock (_disposeLock)
+                {
+                    foreach (var key in _openStreams.Keys.ToList())
+                    {
+                        var task = CloseMediaSource(key, CancellationToken.None);
+
+                        Task.WaitAll(task);
+                    }
+
+                    _openStreams.Clear();
+                }
+            }
+        }
     }
 }

+ 5 - 4
MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs

@@ -1,10 +1,8 @@
-using System.Globalization;
-using MediaBrowser.Common;
+using MediaBrowser.Common;
 using MediaBrowser.Common.Configuration;
 using MediaBrowser.Common.Extensions;
 using MediaBrowser.Common.Progress;
 using MediaBrowser.Common.ScheduledTasks;
-using MediaBrowser.Controller.Channels;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Drawing;
 using MediaBrowser.Controller.Dto;
@@ -15,7 +13,6 @@ using MediaBrowser.Controller.Localization;
 using MediaBrowser.Controller.Persistence;
 using MediaBrowser.Controller.Providers;
 using MediaBrowser.Controller.Sorting;
-using MediaBrowser.Model.Dlna;
 using MediaBrowser.Model.Dto;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.LiveTv;
@@ -342,6 +339,8 @@ namespace MediaBrowser.Server.Implementations.LiveTv
                     var service = GetService(channel);
                     _logger.Info("Opening channel stream from {0}, external channel Id: {1}", service.Name, channel.ExternalId);
                     info = await service.GetChannelStream(channel.ExternalId, null, cancellationToken).ConfigureAwait(false);
+                    info.RequiresClosing = true;
+                    info.CloseKey = info.Id;
                 }
                 else
                 {
@@ -351,6 +350,8 @@ namespace MediaBrowser.Server.Implementations.LiveTv
 
                     _logger.Info("Opening recording stream from {0}, external recording Id: {1}", service.Name, recording.RecordingInfo.Id);
                     info = await service.GetRecordingStream(recording.RecordingInfo.Id, null, cancellationToken).ConfigureAwait(false);
+                    info.RequiresClosing = true;
+                    info.CloseKey = info.Id;
                 }
 
                 _logger.Info("Live stream info: {0}", _jsonSerializer.SerializeToString(info));

+ 77 - 0
MediaBrowser.Server.Implementations/LiveTv/LiveTvMediaSourceProvider.cs

@@ -0,0 +1,77 @@
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.LiveTv;
+using MediaBrowser.Model.Dto;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace MediaBrowser.Server.Implementations.LiveTv
+{
+    public class LiveTvMediaSourceProvider : IMediaSourceProvider
+    {
+        private readonly ILiveTvManager _liveTvManager;
+
+        public LiveTvMediaSourceProvider(ILiveTvManager liveTvManager)
+        {
+            _liveTvManager = liveTvManager;
+        }
+
+        public Task<IEnumerable<MediaSourceInfo>> GetMediaSources(IHasMediaSources item, CancellationToken cancellationToken)
+        {
+            var channelItem = item as ILiveTvItem;
+
+            if (channelItem != null)
+            {
+                var hasMetadata = (IHasMetadata)channelItem;
+
+                if (string.IsNullOrWhiteSpace(hasMetadata.Path))
+                {
+                    return GetMediaSourcesInternal(channelItem, cancellationToken);
+                }
+            }
+
+            return Task.FromResult<IEnumerable<MediaSourceInfo>>(new List<MediaSourceInfo>());
+        }
+
+        private async Task<IEnumerable<MediaSourceInfo>> GetMediaSourcesInternal(ILiveTvItem item, CancellationToken cancellationToken)
+        {
+            var hasMediaSources = (IHasMediaSources)item;
+
+            var sources = hasMediaSources.GetMediaSources(false)
+                .ToList();
+
+            foreach (var source in sources)
+            {
+                source.Type = MediaSourceType.Default;
+                source.RequiresOpening = true;
+
+                var openKeys = new List<string>();
+                openKeys.Add(item.GetType().Name);
+                openKeys.Add(item.Id.ToString("N"));
+                source.OpenKey = string.Join("|", openKeys.ToArray());
+            }
+
+            return sources;
+        }
+
+        public async Task<MediaSourceInfo> OpenMediaSource(string openKey, CancellationToken cancellationToken)
+        {
+            var keys = openKey.Split(new[] { '|' }, 2);
+
+            if (string.Equals(keys[0], typeof(LiveTvChannel).Name, StringComparison.OrdinalIgnoreCase))
+            {
+                return await _liveTvManager.GetChannelStream(keys[1], cancellationToken).ConfigureAwait(false);
+            }
+
+            return await _liveTvManager.GetRecordingStream(keys[1], cancellationToken).ConfigureAwait(false);
+        }
+
+        public Task CloseMediaSource(string closeKey, CancellationToken cancellationToken)
+        {
+            return _liveTvManager.CloseLiveStream(closeKey, cancellationToken);
+        }
+    }
+}

+ 2 - 0
MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj

@@ -111,6 +111,7 @@
     <Compile Include="Branding\BrandingConfigurationFactory.cs" />
     <Compile Include="Channels\ChannelConfigurations.cs" />
     <Compile Include="Channels\ChannelDownloadScheduledTask.cs" />
+    <Compile Include="Channels\ChannelDynamicMediaSourceProvider.cs" />
     <Compile Include="Channels\ChannelImageProvider.cs" />
     <Compile Include="Channels\ChannelItemImageProvider.cs" />
     <Compile Include="Channels\ChannelManager.cs" />
@@ -225,6 +226,7 @@
     <Compile Include="LiveTv\LiveTvConfigurationFactory.cs" />
     <Compile Include="LiveTv\LiveTvDtoService.cs" />
     <Compile Include="LiveTv\LiveTvManager.cs" />
+    <Compile Include="LiveTv\LiveTvMediaSourceProvider.cs" />
     <Compile Include="LiveTv\ProgramImageProvider.cs" />
     <Compile Include="LiveTv\RecordingImageProvider.cs" />
     <Compile Include="LiveTv\RefreshChannelsScheduledTask.cs" />

+ 1 - 0
MediaBrowser.Server.Implementations/Sync/MediaSync.cs

@@ -161,6 +161,7 @@ namespace MediaBrowser.Server.Implementations.Sync
                     {
                         mediaSource.Path = sendFileResult.Path;
                         mediaSource.Protocol = sendFileResult.Protocol;
+                        mediaSource.RequiredHttpHeaders = sendFileResult.RequiredHttpHeaders;
                         mediaSource.SupportsTranscoding = false;
                     }
                 }

+ 52 - 22
MediaBrowser.Server.Implementations/Sync/SyncedMediaSourceProvider.cs

@@ -1,4 +1,5 @@
-using MediaBrowser.Controller;
+using MediaBrowser.Common.Extensions;
+using MediaBrowser.Controller;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Sync;
@@ -61,7 +62,7 @@ namespace MediaBrowser.Server.Implementations.Sync
                         {
                             foreach (var mediaSource in localItem.Item.MediaSources)
                             {
-                                await TryAddMediaSource(list, localItem, mediaSource, syncProvider, syncTarget, cancellationToken).ConfigureAwait(false);
+                                AddMediaSource(list, localItem, mediaSource, syncProvider, syncTarget);
                             }
                         }
                     }
@@ -71,41 +72,70 @@ namespace MediaBrowser.Server.Implementations.Sync
             return list;
         }
 
-        private async Task TryAddMediaSource(List<MediaSourceInfo> list,
+        private void AddMediaSource(List<MediaSourceInfo> list,
             LocalItem item,
             MediaSourceInfo mediaSource,
             IServerSyncProvider provider,
-            SyncTarget target,
-            CancellationToken cancellationToken)
+            SyncTarget target)
         {
+            SetStaticMediaSourceInfo(item, mediaSource);
+
             var requiresDynamicAccess = provider as IHasDynamicAccess;
 
-            if (requiresDynamicAccess == null)
+            if (requiresDynamicAccess != null)
             {
-                list.Add(mediaSource);
-                return;
+                mediaSource.RequiresOpening = true;
+
+                var keyList = new List<string>();
+                keyList.Add(provider.GetType().FullName.GetMD5().ToString("N"));
+                keyList.Add(target.Id.GetMD5().ToString("N"));
+                keyList.Add(item.Id);
+                mediaSource.OpenKey = string.Join("|", keyList.ToArray());
             }
+        }
 
-            try
-            {
-                var dynamicInfo = await requiresDynamicAccess.GetSyncedFileInfo(item.LocalPath, target, cancellationToken).ConfigureAwait(false);
+        public async Task<MediaSourceInfo> OpenMediaSource(string openKey, CancellationToken cancellationToken)
+        {
+            var openKeys = openKey.Split(new[] { '|' }, 3);
 
-                foreach (var stream in mediaSource.MediaStreams)
-                {
-                    var dynamicStreamInfo = await requiresDynamicAccess.GetSyncedFileInfo(stream.ExternalId, target, cancellationToken).ConfigureAwait(false);
+            var provider = _syncManager.ServerSyncProviders
+                .FirstOrDefault(i => string.Equals(openKeys[0], i.GetType().FullName.GetMD5().ToString("N"), StringComparison.OrdinalIgnoreCase));
 
-                    stream.Path = dynamicStreamInfo.Path;
-                }
+            var target = provider.GetAllSyncTargets()
+                .FirstOrDefault(i => string.Equals(openKeys[1], i.Id.GetMD5().ToString("N"), StringComparison.OrdinalIgnoreCase));
 
-                mediaSource.Path = dynamicInfo.Path;
-                mediaSource.Protocol = dynamicInfo.Protocol;
+            var dataProvider = _syncManager.GetDataProvider(provider, target);
+            var localItem = await dataProvider.Get(target, openKeys[2]).ConfigureAwait(false);
 
-                list.Add(mediaSource);
-            }
-            catch (Exception ex)
+            var requiresDynamicAccess = (IHasDynamicAccess)provider;
+            var dynamicInfo = await requiresDynamicAccess.GetSyncedFileInfo(localItem.LocalPath, target, cancellationToken).ConfigureAwait(false);
+
+            var mediaSource = localItem.Item.MediaSources.First();
+            SetStaticMediaSourceInfo(localItem, mediaSource);
+
+            foreach (var stream in mediaSource.MediaStreams)
             {
-                _logger.ErrorException("Error getting dynamic media source info", ex);
+                var dynamicStreamInfo = await requiresDynamicAccess.GetSyncedFileInfo(stream.ExternalId, target, cancellationToken).ConfigureAwait(false);
+
+                stream.Path = dynamicStreamInfo.Path;
             }
+
+            mediaSource.Path = dynamicInfo.Path;
+            mediaSource.Protocol = dynamicInfo.Protocol;
+            mediaSource.RequiredHttpHeaders = dynamicInfo.RequiredHttpHeaders;
+
+            return mediaSource;
+        }
+
+        private void SetStaticMediaSourceInfo(LocalItem item, MediaSourceInfo mediaSource)
+        {
+            mediaSource.Id = item.Id;
+            mediaSource.SupportsTranscoding = false;
+        }
+
+        public Task CloseMediaSource(string closeKey, CancellationToken cancellationToken)
+        {
+            throw new NotImplementedException();
         }
     }
 }

+ 1 - 1
MediaBrowser.Server.Startup.Common/ApplicationHost.cs

@@ -472,7 +472,7 @@ namespace MediaBrowser.Server.Startup.Common
             ChannelManager = new ChannelManager(UserManager, DtoService, LibraryManager, LogManager.GetLogger("ChannelManager"), ServerConfigurationManager, FileSystemManager, UserDataManager, JsonSerializer, LocalizationManager, HttpClient);
             RegisterSingleInstance(ChannelManager);
 
-            MediaSourceManager = new MediaSourceManager(ItemRepository, UserManager, LibraryManager, ChannelManager, LogManager.GetLogger("MediaSourceManager"));
+            MediaSourceManager = new MediaSourceManager(ItemRepository, UserManager, LibraryManager, LogManager.GetLogger("MediaSourceManager"));
             RegisterSingleInstance(MediaSourceManager);
 
             SessionManager = new SessionManager(UserDataManager, LogManager.GetLogger("SessionManager"), UserRepository, LibraryManager, UserManager, musicManager, DtoService, ImageProcessor, JsonSerializer, this, HttpClient, AuthenticationRepository, DeviceManager, MediaSourceManager);

+ 2 - 2
Nuget/MediaBrowser.Common.Internal.nuspec

@@ -9,8 +9,8 @@
         <projectUrl>https://github.com/MediaBrowser/MediaBrowser</projectUrl>
         <iconUrl>http://www.mb3admin.com/images/mb3icons1-1.png</iconUrl>
         <requireLicenseAcceptance>false</requireLicenseAcceptance>
-        <description>Contains common components shared by Media Browser Theater and Media Browser Server. Not intended for plugin developer consumption.</description>
-        <copyright>Copyright © Media Browser 2013</copyright>
+        <description>Contains common components shared by Emby Theater and Emby Server. Not intended for plugin developer consumption.</description>
+        <copyright>Copyright © Emby 2013</copyright>
         <dependencies>
             <dependency id="MediaBrowser.Common" version="3.0.603" />
             <dependency id="NLog" version="3.2.0.0" />

+ 3 - 3
Nuget/MediaBrowser.Common.nuspec

@@ -4,13 +4,13 @@
         <id>MediaBrowser.Common</id>
         <version>3.0.603</version>
         <title>MediaBrowser.Common</title>
-        <authors>Media Browser Team</authors>
+        <authors>Emby Team</authors>
         <owners>ebr,Luke,scottisafool</owners>
         <projectUrl>https://github.com/MediaBrowser/MediaBrowser</projectUrl>
         <iconUrl>http://www.mb3admin.com/images/mb3icons1-1.png</iconUrl>
         <requireLicenseAcceptance>false</requireLicenseAcceptance>
-        <description>Contains common model objects and interfaces used by all Media Browser solutions.</description>
-        <copyright>Copyright © Media Browser 2013</copyright>
+        <description>Contains common model objects and interfaces used by all Emby solutions.</description>
+        <copyright>Copyright © Emby 2013</copyright>
     </metadata>
     <files>
         <file src="dlls\net35\MediaBrowser.Model.dll" target="lib\net35\MediaBrowser.Model.dll" />

+ 3 - 3
Nuget/MediaBrowser.Model.Signed.nuspec

@@ -4,13 +4,13 @@
         <id>MediaBrowser.Model.Signed</id>
         <version>3.0.603</version>
         <title>MediaBrowser.Model - Signed Edition</title>
-        <authors>Media Browser Team</authors>
+        <authors>Emby Team</authors>
         <owners>ebr,Luke,scottisafool</owners>
         <projectUrl>https://github.com/MediaBrowser/MediaBrowser</projectUrl>
         <iconUrl>http://www.mb3admin.com/images/mb3icons1-1.png</iconUrl>
         <requireLicenseAcceptance>false</requireLicenseAcceptance>
-        <description>Contains common model objects and interfaces used by all Media Browser solutions.</description>
-        <copyright>Copyright © Media Browser 2013</copyright>
+        <description>Contains common model objects and interfaces used by all Emby solutions.</description>
+        <copyright>Copyright © Emby 2013</copyright>
         <dependencies>
         </dependencies>
     </metadata>

+ 3 - 3
Nuget/MediaBrowser.Server.Core.nuspec

@@ -4,13 +4,13 @@
         <id>MediaBrowser.Server.Core</id>
         <version>3.0.603</version>
         <title>Media Browser.Server.Core</title>
-        <authors>Media Browser Team</authors>
+        <authors>Emby Team</authors>
         <owners>ebr,Luke,scottisafool</owners>
         <projectUrl>https://github.com/MediaBrowser/MediaBrowser</projectUrl>
 		<iconUrl>http://www.mb3admin.com/images/mb3icons1-1.png</iconUrl>
         <requireLicenseAcceptance>false</requireLicenseAcceptance>
-        <description>Contains core components required to build plugins for Media Browser Server.</description>
-        <copyright>Copyright © Media Browser 2013</copyright>
+        <description>Contains core components required to build plugins for Emby Server.</description>
+        <copyright>Copyright © Emby 2013</copyright>
         <dependencies>
             <dependency id="MediaBrowser.Common" version="3.0.603" />
         </dependencies>