fixes #859 - Support adaptive bitrate streaming
This commit is contained in:
parent
f526a07edd
commit
8ae316a2f3
|
@ -1417,7 +1417,6 @@ namespace MediaBrowser.Api.Playback
|
|||
List<MediaStream> mediaStreams = null;
|
||||
|
||||
state.ItemType = item.GetType().Name;
|
||||
state.ReadInputAtNativeFramerate = true;
|
||||
|
||||
if (item is ILiveTvRecording)
|
||||
{
|
||||
|
@ -1479,6 +1478,7 @@ namespace MediaBrowser.Api.Playback
|
|||
state.IsInputVideo = string.Equals(channel.MediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase);
|
||||
mediaStreams = new List<MediaStream>();
|
||||
|
||||
state.ReadInputAtNativeFramerate = true;
|
||||
state.OutputAudioSync = "1000";
|
||||
state.DeInterlace = true;
|
||||
state.InputVideoSync = "-1";
|
||||
|
@ -1489,13 +1489,13 @@ namespace MediaBrowser.Api.Playback
|
|||
}
|
||||
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.InputProtocol = source.Protocol;
|
||||
state.MediaPath = source.Path;
|
||||
state.InputProtocol = mediaSource.Protocol;
|
||||
state.MediaPath = mediaSource.Path;
|
||||
state.RunTimeTicks = item.RunTimeTicks;
|
||||
state.RemoteHttpHeaders = source.RequiredHttpHeaders;
|
||||
mediaStreams = source.MediaStreams;
|
||||
state.RemoteHttpHeaders = mediaSource.RequiredHttpHeaders;
|
||||
mediaStreams = mediaSource.MediaStreams;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -1539,6 +1539,11 @@ namespace MediaBrowser.Api.Playback
|
|||
state.DeInterlace = true;
|
||||
}
|
||||
|
||||
if (state.InputProtocol == MediaProtocol.Rtmp)
|
||||
{
|
||||
state.ReadInputAtNativeFramerate = true;
|
||||
}
|
||||
|
||||
var videoRequest = request as VideoStreamRequest;
|
||||
|
||||
AttachMediaStreamInfo(state, mediaStreams, videoRequest, url);
|
||||
|
|
|
@ -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 baselineStreamBitrate = 64000;
|
||||
|
@ -162,37 +161,6 @@ namespace MediaBrowser.Api.Playback.Hls
|
|||
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)
|
||||
{
|
||||
var builder = new StringBuilder();
|
||||
|
|
|
@ -304,45 +304,79 @@ namespace MediaBrowser.Api.Playback.Hls
|
|||
{
|
||||
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>());
|
||||
}
|
||||
|
||||
private string GetMasterPlaylistFileText(int bitrate)
|
||||
private string GetMasterPlaylistFileText(StreamState state, int totalBitrate)
|
||||
{
|
||||
var builder = new StringBuilder();
|
||||
|
||||
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 queryString = queryStringIndex == -1 ? string.Empty : Request.RawUrl.Substring(queryStringIndex);
|
||||
|
||||
// Main stream
|
||||
builder.AppendLine("#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=" + paddedBitrate.ToString(UsCulture));
|
||||
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();
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
var result = GetPlaylistAsync(request, "main").Result;
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
using System.Linq;
|
||||
using MediaBrowser.Model.Weather;
|
||||
using MediaBrowser.Model.Weather;
|
||||
using System;
|
||||
|
||||
namespace MediaBrowser.Model.Configuration
|
||||
|
@ -211,7 +210,6 @@ namespace MediaBrowser.Model.Configuration
|
|||
public string[] ManualLoginClients { get; set; }
|
||||
|
||||
public ChannelOptions ChannelOptions { get; set; }
|
||||
|
||||
public ChapterOptions ChapterOptions { get; set; }
|
||||
|
||||
public bool DefaultMetadataSettingsApplied { get; set; }
|
||||
|
@ -274,8 +272,6 @@ namespace MediaBrowser.Model.Configuration
|
|||
|
||||
SubtitleOptions = new SubtitleOptions();
|
||||
|
||||
ChannelOptions = new ChannelOptions();
|
||||
|
||||
LiveTvOptions = new LiveTvOptions();
|
||||
TvFileOrganizationOptions = new TvFileOrganizationOptions();
|
||||
}
|
||||
|
|
|
@ -6,7 +6,6 @@ using MediaBrowser.Controller.Entities.Audio;
|
|||
using MediaBrowser.Controller.MediaEncoding;
|
||||
using MediaBrowser.Controller.Providers;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.IO;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
|
@ -24,40 +23,17 @@ namespace MediaBrowser.Providers.MediaInfo
|
|||
{
|
||||
private readonly ConcurrentDictionary<string, SemaphoreSlim> _locks = new ConcurrentDictionary<string, SemaphoreSlim>();
|
||||
|
||||
private readonly IIsoManager _isoManager;
|
||||
private readonly IMediaEncoder _mediaEncoder;
|
||||
private readonly IServerConfigurationManager _config;
|
||||
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;
|
||||
_config = config;
|
||||
_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)
|
||||
{
|
||||
return new List<ImageType> { ImageType.Primary };
|
||||
|
@ -156,7 +132,7 @@ namespace MediaBrowser.Providers.MediaInfo
|
|||
|
||||
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)
|
||||
|
|
|
@ -98,7 +98,7 @@ namespace MediaBrowser.Providers.MediaInfo
|
|||
|
||||
audio.FormatName = mediaInfo.Format;
|
||||
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)
|
||||
{
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
|
@ -146,9 +146,11 @@ namespace MediaBrowser.Server.Implementations.Channels
|
|||
{
|
||||
var numComplete = 0;
|
||||
|
||||
var options = _config.GetChannelsConfiguration();
|
||||
|
||||
foreach (var item in result.Items)
|
||||
{
|
||||
if (_config.Configuration.ChannelOptions.DownloadingChannels.Contains(item.ChannelId))
|
||||
if (options.DownloadingChannels.Contains(item.ChannelId))
|
||||
{
|
||||
try
|
||||
{
|
||||
|
@ -282,12 +284,14 @@ namespace MediaBrowser.Server.Implementations.Channels
|
|||
|
||||
private void CleanChannelContent(CancellationToken cancellationToken)
|
||||
{
|
||||
if (!_config.Configuration.ChannelOptions.MaxDownloadAge.HasValue)
|
||||
var options = _config.GetChannelsConfiguration();
|
||||
|
||||
if (!options.MaxDownloadAge.HasValue)
|
||||
{
|
||||
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;
|
||||
|
||||
|
|
|
@ -77,9 +77,11 @@ namespace MediaBrowser.Server.Implementations.Channels
|
|||
{
|
||||
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");
|
||||
|
@ -374,7 +376,9 @@ namespace MediaBrowser.Server.Implementations.Channels
|
|||
{
|
||||
var list = channelMediaSources.ToList();
|
||||
|
||||
var width = _config.Configuration.ChannelOptions.PreferredStreamingWidth;
|
||||
var options = _config.GetChannelsConfiguration();
|
||||
|
||||
var width = options.PreferredStreamingWidth;
|
||||
|
||||
if (width.HasValue)
|
||||
{
|
||||
|
|
|
@ -101,6 +101,7 @@
|
|||
<Compile Include="..\SharedVersion.cs">
|
||||
<Link>Properties\SharedVersion.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="Channels\ChannelConfigurations.cs" />
|
||||
<Compile Include="Channels\ChannelDownloadScheduledTask.cs" />
|
||||
<Compile Include="Channels\ChannelImageProvider.cs" />
|
||||
<Compile Include="Channels\ChannelItemImageProvider.cs" />
|
||||
|
|
|
@ -310,6 +310,13 @@ namespace MediaBrowser.ServerApplication
|
|||
saveConfig = true;
|
||||
}
|
||||
|
||||
if (ServerConfigurationManager.Configuration.ChannelOptions != null)
|
||||
{
|
||||
ServerConfigurationManager.SaveConfiguration("channels", ServerConfigurationManager.Configuration.ChannelOptions);
|
||||
ServerConfigurationManager.Configuration.ChannelOptions = null;
|
||||
saveConfig = true;
|
||||
}
|
||||
|
||||
if (saveConfig)
|
||||
{
|
||||
ServerConfigurationManager.SaveConfiguration();
|
||||
|
|
Loading…
Reference in New Issue
Block a user