Bläddra i källkod

fixes #859 - Support adaptive bitrate streaming

Luke Pulverenti 11 år sedan
förälder
incheckning
8ae316a2f3

+ 11 - 6
MediaBrowser.Api/Playback/BaseStreamingService.cs

@@ -1417,7 +1417,6 @@ namespace MediaBrowser.Api.Playback
             List<MediaStream> mediaStreams = null;
             List<MediaStream> mediaStreams = null;
 
 
             state.ItemType = item.GetType().Name;
             state.ItemType = item.GetType().Name;
-            state.ReadInputAtNativeFramerate = true;
 
 
             if (item is ILiveTvRecording)
             if (item is ILiveTvRecording)
             {
             {
@@ -1479,6 +1478,7 @@ namespace MediaBrowser.Api.Playback
                 state.IsInputVideo = string.Equals(channel.MediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase);
                 state.IsInputVideo = string.Equals(channel.MediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase);
                 mediaStreams = new List<MediaStream>();
                 mediaStreams = new List<MediaStream>();
 
 
+                state.ReadInputAtNativeFramerate = true;
                 state.OutputAudioSync = "1000";
                 state.OutputAudioSync = "1000";
                 state.DeInterlace = true;
                 state.DeInterlace = true;
                 state.InputVideoSync = "-1";
                 state.InputVideoSync = "-1";
@@ -1489,13 +1489,13 @@ namespace MediaBrowser.Api.Playback
             }
             }
             else if (item is IChannelMediaItem)
             else if (item is IChannelMediaItem)
             {
             {
-                var source = await GetChannelMediaInfo(request.Id, request.MediaSourceId, cancellationToken).ConfigureAwait(false);
+                var mediaSource = await GetChannelMediaInfo(request.Id, request.MediaSourceId, cancellationToken).ConfigureAwait(false);
                 state.IsInputVideo = string.Equals(item.MediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase);
                 state.IsInputVideo = string.Equals(item.MediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase);
-                state.InputProtocol = source.Protocol;
-                state.MediaPath = source.Path;
+                state.InputProtocol = mediaSource.Protocol;
+                state.MediaPath = mediaSource.Path;
                 state.RunTimeTicks = item.RunTimeTicks;
                 state.RunTimeTicks = item.RunTimeTicks;
-                state.RemoteHttpHeaders = source.RequiredHttpHeaders;
-                mediaStreams = source.MediaStreams;
+                state.RemoteHttpHeaders = mediaSource.RequiredHttpHeaders;
+                mediaStreams = mediaSource.MediaStreams;
             }
             }
             else
             else
             {
             {
@@ -1539,6 +1539,11 @@ namespace MediaBrowser.Api.Playback
                 state.DeInterlace = true;
                 state.DeInterlace = true;
             }
             }
 
 
+            if (state.InputProtocol == MediaProtocol.Rtmp)
+            {
+                state.ReadInputAtNativeFramerate = true;
+            }
+
             var videoRequest = request as VideoStreamRequest;
             var videoRequest = request as VideoStreamRequest;
 
 
             AttachMediaStreamInfo(state, mediaStreams, videoRequest, url);
             AttachMediaStreamInfo(state, mediaStreams, videoRequest, url);

+ 2 - 34
MediaBrowser.Api/Playback/Hls/BaseHlsService.cs

@@ -119,9 +119,8 @@ namespace MediaBrowser.Api.Playback.Hls
                 }
                 }
             }
             }
 
 
-            int audioBitrate;
-            int videoBitrate;
-            GetPlaylistBitrates(state, out audioBitrate, out videoBitrate);
+            var audioBitrate = state.OutputAudioBitrate ?? 0;
+            var videoBitrate = state.OutputVideoBitrate ?? 0;
 
 
             var appendBaselineStream = false;
             var appendBaselineStream = false;
             var baselineStreamBitrate = 64000;
             var baselineStreamBitrate = 64000;
@@ -162,37 +161,6 @@ namespace MediaBrowser.Api.Playback.Hls
             return minimumSegmentCount;
             return minimumSegmentCount;
         }
         }
 
 
-        /// <summary>
-        /// Gets the playlist bitrates.
-        /// </summary>
-        /// <param name="state">The state.</param>
-        /// <param name="audioBitrate">The audio bitrate.</param>
-        /// <param name="videoBitrate">The video bitrate.</param>
-        protected void GetPlaylistBitrates(StreamState state, out int audioBitrate, out int videoBitrate)
-        {
-            var audioBitrateParam = state.OutputAudioBitrate;
-            var videoBitrateParam = state.OutputVideoBitrate;
-
-            if (!audioBitrateParam.HasValue)
-            {
-                if (state.AudioStream != null)
-                {
-                    audioBitrateParam = state.AudioStream.BitRate;
-                }
-            }
-
-            if (!videoBitrateParam.HasValue)
-            {
-                if (state.VideoStream != null)
-                {
-                    videoBitrateParam = state.VideoStream.BitRate;
-                }
-            }
-
-            audioBitrate = audioBitrateParam ?? 0;
-            videoBitrate = videoBitrateParam ?? 0;
-        }
-
         private string GetMasterPlaylistFileText(string firstPlaylist, int bitrate, bool includeBaselineStream, int baselineStreamBitrate)
         private string GetMasterPlaylistFileText(string firstPlaylist, int bitrate, bool includeBaselineStream, int baselineStreamBitrate)
         {
         {
             var builder = new StringBuilder();
             var builder = new StringBuilder();

+ 54 - 20
MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs

@@ -304,45 +304,79 @@ namespace MediaBrowser.Api.Playback.Hls
         {
         {
             var state = await GetState(request, CancellationToken.None).ConfigureAwait(false);
             var state = await GetState(request, CancellationToken.None).ConfigureAwait(false);
 
 
-            int audioBitrate;
-            int videoBitrate;
-            GetPlaylistBitrates(state, out audioBitrate, out videoBitrate);
+            var audioBitrate = state.OutputAudioBitrate ?? 0;
+            var videoBitrate = state.OutputVideoBitrate ?? 0;
 
 
-            var appendBaselineStream = false;
-            var baselineStreamBitrate = 64000;
-
-            var hlsVideoRequest = state.VideoRequest as GetMasterHlsVideoStream;
-            if (hlsVideoRequest != null)
-            {
-                appendBaselineStream = hlsVideoRequest.AppendBaselineStream;
-                baselineStreamBitrate = hlsVideoRequest.BaselineStreamAudioBitRate ?? baselineStreamBitrate;
-            }
-
-            var playlistText = GetMasterPlaylistFileText(videoBitrate + audioBitrate);
+            var playlistText = GetMasterPlaylistFileText(state, videoBitrate + audioBitrate);
 
 
             return ResultFactory.GetResult(playlistText, Common.Net.MimeTypes.GetMimeType("playlist.m3u8"), new Dictionary<string, string>());
             return ResultFactory.GetResult(playlistText, Common.Net.MimeTypes.GetMimeType("playlist.m3u8"), new Dictionary<string, string>());
         }
         }
 
 
-        private string GetMasterPlaylistFileText(int bitrate)
+        private string GetMasterPlaylistFileText(StreamState state, int totalBitrate)
         {
         {
             var builder = new StringBuilder();
             var builder = new StringBuilder();
 
 
             builder.AppendLine("#EXTM3U");
             builder.AppendLine("#EXTM3U");
 
 
-            // Pad a little to satisfy the apple hls validator
-            var paddedBitrate = Convert.ToInt32(bitrate * 1.05);
-
             var queryStringIndex = Request.RawUrl.IndexOf('?');
             var queryStringIndex = Request.RawUrl.IndexOf('?');
             var queryString = queryStringIndex == -1 ? string.Empty : Request.RawUrl.Substring(queryStringIndex);
             var queryString = queryStringIndex == -1 ? string.Empty : Request.RawUrl.Substring(queryStringIndex);
 
 
             // Main stream
             // Main stream
-            builder.AppendLine("#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=" + paddedBitrate.ToString(UsCulture));
             var playlistUrl = "main.m3u8" + queryString;
             var playlistUrl = "main.m3u8" + queryString;
-            builder.AppendLine(playlistUrl);
+            AppendPlaylist(builder, playlistUrl, totalBitrate);
+
+            if (state.VideoRequest.VideoBitRate.HasValue)
+            {
+                var requestedVideoBitrate = state.VideoRequest.VideoBitRate.Value;
+
+                // By default, vary by just 200k
+                var variation = GetBitrateVariation(totalBitrate);
+
+                var newBitrate = totalBitrate - variation;
+                AppendPlaylist(builder, playlistUrl.Replace(requestedVideoBitrate.ToString(UsCulture), (requestedVideoBitrate - variation).ToString(UsCulture)), newBitrate);
+
+                newBitrate = totalBitrate - (2 * variation);
+                AppendPlaylist(builder, playlistUrl.Replace(requestedVideoBitrate.ToString(UsCulture), (requestedVideoBitrate - (2 * variation)).ToString(UsCulture)), newBitrate);
+            }
 
 
             return builder.ToString();
             return builder.ToString();
         }
         }
 
 
+        private void AppendPlaylist(StringBuilder builder, string url, int bitrate)
+        {
+            builder.AppendLine("#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=" + bitrate.ToString(UsCulture));
+            builder.AppendLine(url);
+        }
+
+        private int GetBitrateVariation(int bitrate)
+        {
+            // By default, vary by just 200k
+            var variation = 200000;
+
+            if (bitrate >= 10000000)
+            {
+                variation = 2000000;
+            }
+            else if (bitrate >= 5000000)
+            {
+                variation = 1500000;
+            }
+            else if (bitrate >= 3000000)
+            {
+                variation = 1000000;
+            }
+            else if (bitrate >= 2000000)
+            {
+                variation = 500000;
+            }
+            else if (bitrate >= 1000000)
+            {
+                variation = 300000;
+            }
+
+            return variation;
+        }
+
         public object Get(GetMainHlsVideoStream request)
         public object Get(GetMainHlsVideoStream request)
         {
         {
             var result = GetPlaylistAsync(request, "main").Result;
             var result = GetPlaylistAsync(request, "main").Result;

+ 1 - 5
MediaBrowser.Model/Configuration/ServerConfiguration.cs

@@ -1,5 +1,4 @@
-using System.Linq;
-using MediaBrowser.Model.Weather;
+using MediaBrowser.Model.Weather;
 using System;
 using System;
 
 
 namespace MediaBrowser.Model.Configuration
 namespace MediaBrowser.Model.Configuration
@@ -211,7 +210,6 @@ namespace MediaBrowser.Model.Configuration
         public string[] ManualLoginClients { get; set; }
         public string[] ManualLoginClients { get; set; }
 
 
         public ChannelOptions ChannelOptions { get; set; }
         public ChannelOptions ChannelOptions { get; set; }
-
         public ChapterOptions ChapterOptions { get; set; }
         public ChapterOptions ChapterOptions { get; set; }
 
 
         public bool DefaultMetadataSettingsApplied { get; set; }
         public bool DefaultMetadataSettingsApplied { get; set; }
@@ -274,8 +272,6 @@ namespace MediaBrowser.Model.Configuration
 
 
             SubtitleOptions = new SubtitleOptions();
             SubtitleOptions = new SubtitleOptions();
 
 
-            ChannelOptions = new ChannelOptions();
-
             LiveTvOptions = new LiveTvOptions();
             LiveTvOptions = new LiveTvOptions();
             TvFileOrganizationOptions = new TvFileOrganizationOptions();
             TvFileOrganizationOptions = new TvFileOrganizationOptions();
         }
         }

+ 2 - 26
MediaBrowser.Providers/MediaInfo/AudioImageProvider.cs

@@ -6,7 +6,6 @@ using MediaBrowser.Controller.Entities.Audio;
 using MediaBrowser.Controller.MediaEncoding;
 using MediaBrowser.Controller.MediaEncoding;
 using MediaBrowser.Controller.Providers;
 using MediaBrowser.Controller.Providers;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Entities;
-using MediaBrowser.Model.IO;
 using System;
 using System;
 using System.Collections.Concurrent;
 using System.Collections.Concurrent;
 using System.Collections.Generic;
 using System.Collections.Generic;
@@ -24,40 +23,17 @@ namespace MediaBrowser.Providers.MediaInfo
     {
     {
         private readonly ConcurrentDictionary<string, SemaphoreSlim> _locks = new ConcurrentDictionary<string, SemaphoreSlim>();
         private readonly ConcurrentDictionary<string, SemaphoreSlim> _locks = new ConcurrentDictionary<string, SemaphoreSlim>();
 
 
-        private readonly IIsoManager _isoManager;
         private readonly IMediaEncoder _mediaEncoder;
         private readonly IMediaEncoder _mediaEncoder;
         private readonly IServerConfigurationManager _config;
         private readonly IServerConfigurationManager _config;
         private readonly IFileSystem _fileSystem;
         private readonly IFileSystem _fileSystem;
 
 
-        public AudioImageProvider(IIsoManager isoManager, IMediaEncoder mediaEncoder, IServerConfigurationManager config, IFileSystem fileSystem)
+        public AudioImageProvider(IMediaEncoder mediaEncoder, IServerConfigurationManager config, IFileSystem fileSystem)
         {
         {
-            _isoManager = isoManager;
             _mediaEncoder = mediaEncoder;
             _mediaEncoder = mediaEncoder;
             _config = config;
             _config = config;
             _fileSystem = fileSystem;
             _fileSystem = fileSystem;
         }
         }
 
 
-        /// <summary>
-        /// The null mount task result
-        /// </summary>
-        protected readonly Task<IIsoMount> NullMountTaskResult = Task.FromResult<IIsoMount>(null);
-
-        /// <summary>
-        /// Mounts the iso if needed.
-        /// </summary>
-        /// <param name="item">The item.</param>
-        /// <param name="cancellationToken">The cancellation token.</param>
-        /// <returns>Task{IIsoMount}.</returns>
-        protected Task<IIsoMount> MountIsoIfNeeded(Video item, CancellationToken cancellationToken)
-        {
-            if (item.VideoType == VideoType.Iso)
-            {
-                return _isoManager.Mount(item.Path, cancellationToken);
-            }
-
-            return NullMountTaskResult;
-        }
-
         public IEnumerable<ImageType> GetSupportedImages(IHasImages item)
         public IEnumerable<ImageType> GetSupportedImages(IHasImages item)
         {
         {
             return new List<ImageType> { ImageType.Primary };
             return new List<ImageType> { ImageType.Primary };
@@ -156,7 +132,7 @@ namespace MediaBrowser.Providers.MediaInfo
 
 
         public bool Supports(IHasImages item)
         public bool Supports(IHasImages item)
         {
         {
-            return item.LocationType == LocationType.FileSystem && item is Audio;
+            return item is Audio;
         }
         }
 
 
         public bool HasChanged(IHasMetadata item, IDirectoryService directoryService, DateTime date)
         public bool HasChanged(IHasMetadata item, IDirectoryService directoryService, DateTime date)

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

@@ -98,7 +98,7 @@ namespace MediaBrowser.Providers.MediaInfo
 
 
             audio.FormatName = mediaInfo.Format;
             audio.FormatName = mediaInfo.Format;
             audio.TotalBitrate = mediaInfo.TotalBitrate;
             audio.TotalBitrate = mediaInfo.TotalBitrate;
-            audio.HasEmbeddedImage = mediaStreams.Any(i => i.Type == MediaStreamType.Video);
+            audio.HasEmbeddedImage = mediaStreams.Any(i => i.Type == MediaStreamType.EmbeddedImage);
 
 
             if (data.streams != null)
             if (data.streams != null)
             {
             {

+ 29 - 0
MediaBrowser.Server.Implementations/Channels/ChannelConfigurations.cs

@@ -0,0 +1,29 @@
+using MediaBrowser.Common.Configuration;
+using MediaBrowser.Model.Configuration;
+using System.Collections.Generic;
+
+namespace MediaBrowser.Server.Implementations.Channels
+{
+    public static class ChannelConfigurationExtension
+    {
+        public static ChannelOptions GetChannelsConfiguration(this IConfigurationManager manager)
+        {
+            return manager.GetConfiguration<ChannelOptions>("channels");
+        }
+    }
+
+    public class ChannelConfigurationFactory : IConfigurationFactory
+    {
+        public IEnumerable<ConfigurationStore> GetConfigurations()
+        {
+            return new List<ConfigurationStore>
+            {
+                new ConfigurationStore
+                {
+                    Key = "channels",
+                    ConfigurationType = typeof (ChannelOptions)
+                }
+            };
+        }
+    }
+}

+ 7 - 3
MediaBrowser.Server.Implementations/Channels/ChannelDownloadScheduledTask.cs

@@ -146,9 +146,11 @@ namespace MediaBrowser.Server.Implementations.Channels
         {
         {
             var numComplete = 0;
             var numComplete = 0;
 
 
+            var options = _config.GetChannelsConfiguration();
+
             foreach (var item in result.Items)
             foreach (var item in result.Items)
             {
             {
-                if (_config.Configuration.ChannelOptions.DownloadingChannels.Contains(item.ChannelId))
+                if (options.DownloadingChannels.Contains(item.ChannelId))
                 {
                 {
                     try
                     try
                     {
                     {
@@ -282,12 +284,14 @@ namespace MediaBrowser.Server.Implementations.Channels
 
 
         private void CleanChannelContent(CancellationToken cancellationToken)
         private void CleanChannelContent(CancellationToken cancellationToken)
         {
         {
-            if (!_config.Configuration.ChannelOptions.MaxDownloadAge.HasValue)
+            var options = _config.GetChannelsConfiguration();
+
+            if (!options.MaxDownloadAge.HasValue)
             {
             {
                 return;
                 return;
             }
             }
 
 
-            var minDateModified = DateTime.UtcNow.AddDays(0 - _config.Configuration.ChannelOptions.MaxDownloadAge.Value);
+            var minDateModified = DateTime.UtcNow.AddDays(0 - options.MaxDownloadAge.Value);
 
 
             var path = _manager.ChannelDownloadPath;
             var path = _manager.ChannelDownloadPath;
 
 

+ 7 - 3
MediaBrowser.Server.Implementations/Channels/ChannelManager.cs

@@ -77,9 +77,11 @@ namespace MediaBrowser.Server.Implementations.Channels
         {
         {
             get
             get
             {
             {
-                if (!string.IsNullOrWhiteSpace(_config.Configuration.ChannelOptions.DownloadPath))
+                var options = _config.GetChannelsConfiguration();
+
+                if (!string.IsNullOrWhiteSpace(options.DownloadPath))
                 {
                 {
-                    return _config.Configuration.ChannelOptions.DownloadPath;
+                    return options.DownloadPath;
                 }
                 }
 
 
                 return Path.Combine(_config.ApplicationPaths.ProgramDataPath, "channels");
                 return Path.Combine(_config.ApplicationPaths.ProgramDataPath, "channels");
@@ -374,7 +376,9 @@ namespace MediaBrowser.Server.Implementations.Channels
         {
         {
             var list = channelMediaSources.ToList();
             var list = channelMediaSources.ToList();
 
 
-            var width = _config.Configuration.ChannelOptions.PreferredStreamingWidth;
+            var options = _config.GetChannelsConfiguration();
+
+            var width = options.PreferredStreamingWidth;
 
 
             if (width.HasValue)
             if (width.HasValue)
             {
             {

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

@@ -101,6 +101,7 @@
     <Compile Include="..\SharedVersion.cs">
     <Compile Include="..\SharedVersion.cs">
       <Link>Properties\SharedVersion.cs</Link>
       <Link>Properties\SharedVersion.cs</Link>
     </Compile>
     </Compile>
+    <Compile Include="Channels\ChannelConfigurations.cs" />
     <Compile Include="Channels\ChannelDownloadScheduledTask.cs" />
     <Compile Include="Channels\ChannelDownloadScheduledTask.cs" />
     <Compile Include="Channels\ChannelImageProvider.cs" />
     <Compile Include="Channels\ChannelImageProvider.cs" />
     <Compile Include="Channels\ChannelItemImageProvider.cs" />
     <Compile Include="Channels\ChannelItemImageProvider.cs" />

+ 7 - 0
MediaBrowser.ServerApplication/ApplicationHost.cs

@@ -310,6 +310,13 @@ namespace MediaBrowser.ServerApplication
                 saveConfig = true;
                 saveConfig = true;
             }
             }
 
 
+            if (ServerConfigurationManager.Configuration.ChannelOptions != null)
+            {
+                ServerConfigurationManager.SaveConfiguration("channels", ServerConfigurationManager.Configuration.ChannelOptions);
+                ServerConfigurationManager.Configuration.ChannelOptions = null;
+                saveConfig = true;
+            }
+
             if (saveConfig)
             if (saveConfig)
             {
             {
                 ServerConfigurationManager.SaveConfiguration();
                 ServerConfigurationManager.SaveConfiguration();