fixes #859 - Support adaptive bitrate streaming

This commit is contained in:
Luke Pulverenti 2014-06-30 13:40:46 -04:00
parent f526a07edd
commit 8ae316a2f3
11 changed files with 122 additions and 98 deletions

View File

@ -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.InputProtocol = mediaSource.Protocol;
state.MediaPath = source.Path; state.MediaPath = mediaSource.Path;
state.RunTimeTicks = item.RunTimeTicks; state.RunTimeTicks = item.RunTimeTicks;
state.RemoteHttpHeaders = source.RequiredHttpHeaders; state.RemoteHttpHeaders = mediaSource.RequiredHttpHeaders;
mediaStreams = source.MediaStreams; 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);

View File

@ -119,9 +119,8 @@ namespace MediaBrowser.Api.Playback.Hls
} }
} }
int audioBitrate; var audioBitrate = state.OutputAudioBitrate ?? 0;
int videoBitrate; var videoBitrate = state.OutputVideoBitrate ?? 0;
GetPlaylistBitrates(state, out audioBitrate, out videoBitrate);
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();

View File

@ -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; var audioBitrate = state.OutputAudioBitrate ?? 0;
int videoBitrate; var videoBitrate = state.OutputVideoBitrate ?? 0;
GetPlaylistBitrates(state, out audioBitrate, out videoBitrate);
var appendBaselineStream = false; var playlistText = GetMasterPlaylistFileText(state, videoBitrate + audioBitrate);
var baselineStreamBitrate = 64000;
var hlsVideoRequest = state.VideoRequest as GetMasterHlsVideoStream;
if (hlsVideoRequest != null)
{
appendBaselineStream = hlsVideoRequest.AppendBaselineStream;
baselineStreamBitrate = hlsVideoRequest.BaselineStreamAudioBitRate ?? baselineStreamBitrate;
}
var playlistText = GetMasterPlaylistFileText(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;

View File

@ -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();
} }

View File

@ -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)

View File

@ -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)
{ {

View File

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

View File

@ -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;

View File

@ -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)
{ {

View File

@ -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" />

View File

@ -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();