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;
|
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);
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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)
|
||||||
{
|
{
|
||||||
|
|
|
@ -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 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;
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
{
|
{
|
||||||
|
|
|
@ -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" />
|
||||||
|
|
|
@ -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();
|
||||||
|
|
Loading…
Reference in New Issue
Block a user