diff --git a/MediaBrowser.Api/ApiEntryPoint.cs b/MediaBrowser.Api/ApiEntryPoint.cs index f71180754..3e9a0926b 100644 --- a/MediaBrowser.Api/ApiEntryPoint.cs +++ b/MediaBrowser.Api/ApiEntryPoint.cs @@ -3,15 +3,14 @@ using MediaBrowser.Controller; using MediaBrowser.Controller.Plugins; using MediaBrowser.Controller.Session; using MediaBrowser.Model.Logging; +using MediaBrowser.Model.Session; using System; using System.Collections.Generic; -using System.ComponentModel; using System.Diagnostics; using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; -using MediaBrowser.Model.Session; namespace MediaBrowser.Api { @@ -100,7 +99,7 @@ namespace MediaBrowser.Api { var jobCount = _activeTranscodingJobs.Count; - Parallel.ForEach(_activeTranscodingJobs.ToList(), KillTranscodingJob); + Parallel.ForEach(_activeTranscodingJobs.ToList(), j => KillTranscodingJob(j, true)); // Try to allow for some time to kill the ffmpeg processes and delete the partial stream files if (jobCount > 0) @@ -291,16 +290,16 @@ namespace MediaBrowser.Api { var job = (TranscodingJob)state; - KillTranscodingJob(job); + KillTranscodingJob(job, true); } /// /// Kills the single transcoding job. /// /// The device id. - /// if set to true [is video]. + /// if set to true [delete files]. /// sourcePath - internal void KillTranscodingJobs(string deviceId, bool isVideo) + internal void KillTranscodingJobs(string deviceId, bool deleteFiles) { if (string.IsNullOrEmpty(deviceId)) { @@ -318,7 +317,7 @@ namespace MediaBrowser.Api foreach (var job in jobs) { - KillTranscodingJob(job); + KillTranscodingJob(job, deleteFiles); } } @@ -326,7 +325,8 @@ namespace MediaBrowser.Api /// Kills the transcoding job. /// /// The job. - private void KillTranscodingJob(TranscodingJob job) + /// if set to true [delete files]. + private void KillTranscodingJob(TranscodingJob job, bool deleteFiles) { lock (_activeTranscodingJobs) { @@ -344,48 +344,44 @@ namespace MediaBrowser.Api } } - var process = job.Process; - - var hasExited = true; - - try + lock (job.ProcessLock) { - hasExited = process.HasExited; - } - catch (Exception ex) - { - Logger.ErrorException("Error determining if ffmpeg process has exited for {0}", ex, job.Path); - } + var process = job.Process; + + var hasExited = true; - if (!hasExited) - { try { - Logger.Info("Killing ffmpeg process for {0}", job.Path); + hasExited = process.HasExited; + } + catch (Exception ex) + { + Logger.ErrorException("Error determining if ffmpeg process has exited for {0}", ex, job.Path); + } - process.Kill(); + if (!hasExited) + { + try + { + Logger.Info("Killing ffmpeg process for {0}", job.Path); - // Need to wait because killing is asynchronous - process.WaitForExit(5000); - } - catch (Win32Exception ex) - { - Logger.ErrorException("Error killing transcoding job for {0}", ex, job.Path); - } - catch (InvalidOperationException ex) - { - Logger.ErrorException("Error killing transcoding job for {0}", ex, job.Path); - } - catch (NotSupportedException ex) - { - Logger.ErrorException("Error killing transcoding job for {0}", ex, job.Path); + //process.Kill(); + process.StandardInput.WriteLine("q"); + + // Need to wait because killing is asynchronous + process.WaitForExit(5000); + } + catch (Exception ex) + { + Logger.ErrorException("Error killing transcoding job for {0}", ex, job.Path); + } } } - // Dispose the process - process.Dispose(); - - DeletePartialStreamFiles(job.Path, job.Type, 0, 1500); + if (deleteFiles) + { + DeletePartialStreamFiles(job.Path, job.Type, 0, 1500); + } } private async void DeletePartialStreamFiles(string path, TranscodingJobType jobType, int retryCount, int delayMs) @@ -494,6 +490,8 @@ namespace MediaBrowser.Api public string DeviceId { get; set; } public CancellationTokenSource CancellationTokenSource { get; set; } + + public object ProcessLock = new object(); } /// diff --git a/MediaBrowser.Api/Playback/BaseStreamingService.cs b/MediaBrowser.Api/Playback/BaseStreamingService.cs index d2369c410..0ecc5d9d1 100644 --- a/MediaBrowser.Api/Playback/BaseStreamingService.cs +++ b/MediaBrowser.Api/Playback/BaseStreamingService.cs @@ -816,6 +816,7 @@ namespace MediaBrowser.Api.Playback // Must consume both stdout and stderr or deadlocks may occur RedirectStandardOutput = true, RedirectStandardError = true, + RedirectStandardInput = true, FileName = MediaEncoder.EncoderPath, WorkingDirectory = Path.GetDirectoryName(MediaEncoder.EncoderPath), @@ -1073,8 +1074,9 @@ namespace MediaBrowser.Api.Playback /// /// The process. /// The state. - protected void OnFfMpegProcessExited(Process process, StreamState state) + private void OnFfMpegProcessExited(Process process, StreamState state) { + Logger.Debug("Disposing stream resources"); state.Dispose(); try @@ -1083,8 +1085,19 @@ namespace MediaBrowser.Api.Playback } catch { - Logger.Info("FFMpeg exited with an error."); + Logger.Error("FFMpeg exited with an error."); } + + // This causes on exited to be called twice: + //try + //{ + // // Dispose the process + // process.Dispose(); + //} + //catch (Exception ex) + //{ + // Logger.ErrorException("Error disposing ffmpeg.", ex); + //} } protected double? GetFramerateParam(StreamState state) diff --git a/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs b/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs index 5de0709fd..39163a103 100644 --- a/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs +++ b/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs @@ -83,7 +83,7 @@ namespace MediaBrowser.Api.Playback.Hls { var cancellationTokenSource = new CancellationTokenSource(); - var state = GetState(request, cancellationTokenSource.Token).Result; + var state = await GetState(request, cancellationTokenSource.Token).ConfigureAwait(false); var playlist = state.OutputFilePath; @@ -154,7 +154,7 @@ namespace MediaBrowser.Api.Playback.Hls /// System.Int32. protected int GetSegmentWait() { - var minimumSegmentCount = 3; + var minimumSegmentCount = 2; var quality = GetQualitySetting(); if (quality == EncodingQuality.HighSpeed || quality == EncodingQuality.HighQuality) diff --git a/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs b/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs index 13e858aa5..bb547bbff 100644 --- a/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs +++ b/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs @@ -1,13 +1,10 @@ using MediaBrowser.Common.IO; -using MediaBrowser.Common.Net; using MediaBrowser.Controller.Channels; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Dlna; -using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.LiveTv; using MediaBrowser.Controller.MediaEncoding; -using MediaBrowser.Controller.Persistence; using MediaBrowser.Model.IO; using ServiceStack; using System; @@ -17,7 +14,6 @@ using System.IO; using System.Text; using System.Threading; using System.Threading.Tasks; -using MimeTypes = ServiceStack.MimeTypes; namespace MediaBrowser.Api.Playback.Hls { @@ -83,29 +79,75 @@ namespace MediaBrowser.Api.Playback.Hls return GetDynamicSegment(request, true).Result; } + private static readonly SemaphoreSlim FfmpegStartLock = new SemaphoreSlim(1, 1); private async Task GetDynamicSegment(GetDynamicHlsVideoSegment request, bool isMain) { + var cancellationTokenSource = new CancellationTokenSource(); + var cancellationToken = cancellationTokenSource.Token; + var index = int.Parse(request.SegmentId, NumberStyles.Integer, UsCulture); - var state = await GetState(request, CancellationToken.None).ConfigureAwait(false); + var state = await GetState(request, cancellationToken).ConfigureAwait(false); var playlistPath = Path.ChangeExtension(state.OutputFilePath, ".m3u8"); - var path = GetSegmentPath(playlistPath, index); + var segmentPath = GetSegmentPath(playlistPath, index); - if (File.Exists(path)) + if (File.Exists(segmentPath)) { - return GetSegementResult(path); + ApiEntryPoint.Instance.OnTranscodeBeginRequest(playlistPath, TranscodingJobType.Hls); + return GetSegementResult(segmentPath); } - if (!File.Exists(playlistPath)) + await FfmpegStartLock.WaitAsync(cancellationTokenSource.Token).ConfigureAwait(false); + try { - await StartFfMpeg(state, playlistPath, new CancellationTokenSource()).ConfigureAwait(false); + if (File.Exists(segmentPath)) + { + ApiEntryPoint.Instance.OnTranscodeBeginRequest(playlistPath, TranscodingJobType.Hls); + return GetSegementResult(segmentPath); + } + else + { + if (index == 0) + { + // If the playlist doesn't already exist, startup ffmpeg + try + { + ApiEntryPoint.Instance.KillTranscodingJobs(state.Request.DeviceId, false); - await WaitForMinimumSegmentCount(playlistPath, GetSegmentWait(), CancellationToken.None).ConfigureAwait(false); + await StartFfMpeg(state, playlistPath, cancellationTokenSource).ConfigureAwait(false); + } + catch + { + state.Dispose(); + throw; + } + + await WaitForMinimumSegmentCount(playlistPath, 2, cancellationTokenSource.Token).ConfigureAwait(false); + } + } + } + finally + { + FfmpegStartLock.Release(); } - return GetSegementResult(path); + Logger.Info("waiting for {0}", segmentPath); + while (!File.Exists(segmentPath)) + { + await Task.Delay(50, cancellationToken).ConfigureAwait(false); + } + + Logger.Info("returning {0}", segmentPath); + return GetSegementResult(segmentPath); + } + + protected override int GetStartNumber(StreamState state) + { + var request = (GetDynamicHlsVideoSegment) state.Request; + + return int.Parse(request.SegmentId, NumberStyles.Integer, UsCulture); } private string GetSegmentPath(string playlist, int index) @@ -120,7 +162,7 @@ namespace MediaBrowser.Api.Playback.Hls private object GetSegementResult(string path) { // TODO: Handle if it's currently being written to - return ResultFactory.GetStaticFileResult(Request, path); + return ResultFactory.GetStaticFileResult(Request, path, FileShare.ReadWrite); } private async Task GetAsync(GetMasterHlsVideoStream request) @@ -143,7 +185,7 @@ namespace MediaBrowser.Api.Playback.Hls var playlistText = GetMasterPlaylistFileText(videoBitrate + audioBitrate, appendBaselineStream, baselineStreamBitrate); - return ResultFactory.GetResult(playlistText, MimeTypes.GetMimeType("playlist.m3u8"), new Dictionary()); + return ResultFactory.GetResult(playlistText, Common.Net.MimeTypes.GetMimeType("playlist.m3u8"), new Dictionary()); } private string GetMasterPlaylistFileText(int bitrate, bool includeBaselineStream, int baselineStreamBitrate) @@ -226,7 +268,7 @@ namespace MediaBrowser.Api.Playback.Hls var playlistText = builder.ToString(); - return ResultFactory.GetResult(playlistText, MimeTypes.GetMimeType("playlist.m3u8"), new Dictionary()); + return ResultFactory.GetResult(playlistText, Common.Net.MimeTypes.GetMimeType("playlist.m3u8"), new Dictionary()); } protected override string GetAudioArguments(StreamState state) @@ -274,7 +316,9 @@ namespace MediaBrowser.Api.Playback.Hls return IsH264(state.VideoStream) ? "-codec:v:0 copy -bsf h264_mp4toannexb" : "-codec:v:0 copy"; } - const string keyFrameArg = " -force_key_frames expr:if(isnan(prev_forced_t),gte(t,.1),gte(t,prev_forced_t+5))"; + var keyFrameArg = state.ReadInputAtNativeFramerate ? + " -force_key_frames expr:if(isnan(prev_forced_t),gte(t,.1),gte(t,prev_forced_t+1))" : + " -force_key_frames expr:if(isnan(prev_forced_t),gte(t,.1),gte(t,prev_forced_t+5))"; var hasGraphicalSubs = state.SubtitleStream != null && !state.SubtitleStream.IsTextSubtitleStream; diff --git a/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs b/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs index 337cd88f2..d65d1030c 100644 --- a/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs +++ b/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs @@ -1,19 +1,16 @@ -using System.Threading; using MediaBrowser.Common.IO; -using MediaBrowser.Common.Net; using MediaBrowser.Controller.Channels; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Dlna; -using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.LiveTv; using MediaBrowser.Controller.MediaEncoding; -using MediaBrowser.Controller.Persistence; using MediaBrowser.Model.IO; using ServiceStack; using System; using System.IO; using System.Linq; +using System.Threading; using System.Threading.Tasks; namespace MediaBrowser.Api.Playback.Hls diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs index 6cb7e8ce3..5ee119e13 100644 --- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs @@ -134,7 +134,7 @@ namespace MediaBrowser.MediaEncoding.Encoder /// System.String. public string GetProbeSizeArgument(string[] inputFiles, MediaProtocol protocol) { - return EncodingUtils.GetProbeSizeArgument(inputFiles.Length > 0); + return EncodingUtils.GetProbeSizeArgument(inputFiles.Length > 1); } /// diff --git a/MediaBrowser.Model/Dlna/StreamBuilder.cs b/MediaBrowser.Model/Dlna/StreamBuilder.cs index 4f7f6277e..5828ded99 100644 --- a/MediaBrowser.Model/Dlna/StreamBuilder.cs +++ b/MediaBrowser.Model/Dlna/StreamBuilder.cs @@ -54,12 +54,14 @@ namespace MediaBrowser.Model.Dlna // Avoid implicitly captured closure string mediaSourceId = options.MediaSourceId; - mediaSources = new List(); + var newMediaSources = new List(); foreach (MediaSourceInfo i in mediaSources) { if (StringHelper.EqualsIgnoreCase(i.Id, mediaSourceId)) - mediaSources.Add(i); + newMediaSources.Add(i); } + + mediaSources = newMediaSources; } List streams = new List(); diff --git a/MediaBrowser.Server.Implementations/Channels/ChannelManager.cs b/MediaBrowser.Server.Implementations/Channels/ChannelManager.cs index e9df5e52c..98e7593cf 100644 --- a/MediaBrowser.Server.Implementations/Channels/ChannelManager.cs +++ b/MediaBrowser.Server.Implementations/Channels/ChannelManager.cs @@ -1,4 +1,5 @@ -using MediaBrowser.Common.Extensions; +using System.Collections.Concurrent; +using MediaBrowser.Common.Extensions; using MediaBrowser.Common.IO; using MediaBrowser.Controller.Channels; using MediaBrowser.Controller.Configuration; @@ -23,7 +24,7 @@ using System.Threading.Tasks; namespace MediaBrowser.Server.Implementations.Channels { - public class ChannelManager : IChannelManager + public class ChannelManager : IChannelManager, IDisposable { private IChannel[] _channels; private IChannelFactory[] _factories; @@ -39,6 +40,9 @@ namespace MediaBrowser.Server.Implementations.Channels private readonly IJsonSerializer _jsonSerializer; private readonly ILocalizationManager _localization; + private readonly ConcurrentDictionary _refreshedItems = new ConcurrentDictionary(); + + private Timer _refreshTimer; public ChannelManager(IUserManager userManager, IDtoService dtoService, ILibraryManager libraryManager, ILogger logger, IServerConfigurationManager config, IFileSystem fileSystem, IUserDataManager userDataManager, IJsonSerializer jsonSerializer, ILocalizationManager localization) { @@ -51,6 +55,8 @@ namespace MediaBrowser.Server.Implementations.Channels _userDataManager = userDataManager; _jsonSerializer = jsonSerializer; _localization = localization; + + _refreshTimer = new Timer(s => _refreshedItems.Clear(), null, TimeSpan.FromHours(3), TimeSpan.FromHours(3)); } private TimeSpan CacheLength @@ -203,8 +209,8 @@ namespace MediaBrowser.Server.Implementations.Channels if (requiresCallback != null) { - results = await requiresCallback.GetChannelItemMediaInfo(item.ExternalId, cancellationToken) - .ConfigureAwait(false); + results = await GetChannelItemMediaSourcesInternal(requiresCallback, item.ExternalId, cancellationToken) + .ConfigureAwait(false); } else { @@ -221,6 +227,31 @@ namespace MediaBrowser.Server.Implementations.Channels return sources; } + private readonly ConcurrentDictionary>> _channelItemMediaInfo = + new ConcurrentDictionary>>(); + + private async Task> GetChannelItemMediaSourcesInternal(IRequiresMediaInfoCallback channel, string id, CancellationToken cancellationToken) + { + Tuple> cachedInfo; + + if (_channelItemMediaInfo.TryGetValue(id, out cachedInfo)) + { + if ((DateTime.UtcNow - cachedInfo.Item1).TotalMinutes < 5) + { + return cachedInfo.Item2; + } + } + + var mediaInfo = await channel.GetChannelItemMediaInfo(id, cancellationToken) + .ConfigureAwait(false); + var list = mediaInfo.ToList(); + + var item2 = new Tuple>(DateTime.UtcNow, list); + _channelItemMediaInfo.AddOrUpdate(id, item2, (key, oldValue) => item2); + + return list; + } + public IEnumerable GetCachedChannelItemMediaSources(string id) { var item = (IChannelMediaItem)_libraryManager.GetItemById(id); @@ -515,11 +546,7 @@ namespace MediaBrowser.Server.Implementations.Channels { try { - var result = await indexable.GetLatestMedia(new ChannelLatestMediaSearch - { - UserId = userId - - }, cancellationToken).ConfigureAwait(false); + var result = await GetLatestItems(indexable, i, userId, cancellationToken).ConfigureAwait(false); var resultItems = result.ToList(); @@ -585,6 +612,65 @@ namespace MediaBrowser.Server.Implementations.Channels }; } + private async Task> GetLatestItems(ISupportsLatestMedia indexable, IChannel channel, string userId, CancellationToken cancellationToken) + { + var cacheLength = TimeSpan.FromHours(12); + var cachePath = GetChannelDataCachePath(channel, userId, "channelmanager-latest", null, false); + + try + { + if (_fileSystem.GetLastWriteTimeUtc(cachePath).Add(cacheLength) > DateTime.UtcNow) + { + return _jsonSerializer.DeserializeFromFile>(cachePath); + } + } + catch (FileNotFoundException) + { + + } + catch (DirectoryNotFoundException) + { + + } + + await _resourcePool.WaitAsync(cancellationToken).ConfigureAwait(false); + + try + { + try + { + if (_fileSystem.GetLastWriteTimeUtc(cachePath).Add(cacheLength) > DateTime.UtcNow) + { + return _jsonSerializer.DeserializeFromFile>(cachePath); + } + } + catch (FileNotFoundException) + { + + } + catch (DirectoryNotFoundException) + { + + } + + var result = await indexable.GetLatestMedia(new ChannelLatestMediaSearch + { + UserId = userId + + }, cancellationToken).ConfigureAwait(false); + + var resultItems = result.ToList(); + + CacheResponse(resultItems, cachePath); + + return resultItems; + } + finally + { + _resourcePool.Release(); + } + } + public async Task> GetAllMedia(AllChannelMediaQuery query, CancellationToken cancellationToken) { var user = string.IsNullOrWhiteSpace(query.UserId) @@ -614,11 +700,7 @@ namespace MediaBrowser.Server.Implementations.Channels { try { - var result = await indexable.GetAllMedia(new InternalAllChannelMediaQuery - { - UserId = userId - - }, cancellationToken).ConfigureAwait(false); + var result = await GetAllItems(indexable, i, userId, cancellationToken).ConfigureAwait(false); return new Tuple(i, result); } @@ -677,6 +759,63 @@ namespace MediaBrowser.Server.Implementations.Channels }; } + private async Task GetAllItems(IIndexableChannel indexable, IChannel channel, string userId, CancellationToken cancellationToken) + { + var cacheLength = TimeSpan.FromHours(12); + var cachePath = GetChannelDataCachePath(channel, userId, "channelmanager-allitems", null, false); + + try + { + if (_fileSystem.GetLastWriteTimeUtc(cachePath).Add(cacheLength) > DateTime.UtcNow) + { + return _jsonSerializer.DeserializeFromFile(cachePath); + } + } + catch (FileNotFoundException) + { + + } + catch (DirectoryNotFoundException) + { + + } + + await _resourcePool.WaitAsync(cancellationToken).ConfigureAwait(false); + + try + { + try + { + if (_fileSystem.GetLastWriteTimeUtc(cachePath).Add(cacheLength) > DateTime.UtcNow) + { + return _jsonSerializer.DeserializeFromFile(cachePath); + } + } + catch (FileNotFoundException) + { + + } + catch (DirectoryNotFoundException) + { + + } + + var result = await indexable.GetAllMedia(new InternalAllChannelMediaQuery + { + UserId = userId + + }, cancellationToken).ConfigureAwait(false); + + CacheResponse(result, cachePath); + + return result; + } + finally + { + _resourcePool.Release(); + } + } + public async Task> GetChannelItems(ChannelItemQuery query, CancellationToken cancellationToken) { var queryChannelId = query.ChannelId; @@ -764,11 +903,9 @@ namespace MediaBrowser.Server.Implementations.Channels { if (!startIndex.HasValue && !limit.HasValue) { - var channelItemResult = _jsonSerializer.DeserializeFromFile(cachePath); - if (_fileSystem.GetLastWriteTimeUtc(cachePath).Add(cacheLength) > DateTime.UtcNow) { - return channelItemResult; + return _jsonSerializer.DeserializeFromFile(cachePath); } } } @@ -789,11 +926,9 @@ namespace MediaBrowser.Server.Implementations.Channels { if (!startIndex.HasValue && !limit.HasValue) { - var channelItemResult = _jsonSerializer.DeserializeFromFile(cachePath); - if (_fileSystem.GetLastWriteTimeUtc(cachePath).Add(cacheLength) > DateTime.UtcNow) { - return channelItemResult; + return _jsonSerializer.DeserializeFromFile(cachePath); } } } @@ -837,7 +972,7 @@ namespace MediaBrowser.Server.Implementations.Channels } } - private void CacheResponse(ChannelItemResult result, string path) + private void CacheResponse(object result, string path) { try { @@ -993,8 +1128,8 @@ namespace MediaBrowser.Server.Implementations.Channels item.ProductionYear = info.ProductionYear; item.ProviderIds = info.ProviderIds; - item.DateCreated = info.DateCreated.HasValue ? - info.DateCreated.Value : + item.DateCreated = info.DateCreated.HasValue ? + info.DateCreated.Value : DateTime.UtcNow; } @@ -1042,14 +1177,14 @@ namespace MediaBrowser.Server.Implementations.Channels private async Task RefreshIfNeeded(BaseItem program, CancellationToken cancellationToken) { - //if (_refreshedPrograms.ContainsKey(program.Id)) + if (_refreshedItems.ContainsKey(program.Id)) { - //return; + return; } await program.RefreshMetadata(cancellationToken).ConfigureAwait(false); - //_refreshedPrograms.TryAdd(program.Id, true); + _refreshedItems.TryAdd(program.Id, true); } internal IChannel GetChannelProvider(Channel channel) @@ -1155,5 +1290,14 @@ namespace MediaBrowser.Server.Implementations.Channels var name = _localization.GetLocalizedString("ViewTypeChannels"); return await _libraryManager.GetNamedView(name, "channels", "zz_" + name, cancellationToken).ConfigureAwait(false); } + + public void Dispose() + { + if (_refreshTimer != null) + { + _refreshTimer.Dispose(); + _refreshTimer = null; + } + } } } diff --git a/MediaBrowser.Server.Implementations/Library/LibraryManager.cs b/MediaBrowser.Server.Implementations/Library/LibraryManager.cs index c6a632fec..521a6f843 100644 --- a/MediaBrowser.Server.Implementations/Library/LibraryManager.cs +++ b/MediaBrowser.Server.Implementations/Library/LibraryManager.cs @@ -1497,7 +1497,7 @@ namespace MediaBrowser.Server.Implementations.Library public async Task GetNamedView(string name, string type, string sortName, CancellationToken cancellationToken) { - var id = "namedview_2_" + name; + var id = "namedview_3_" + name; var guid = id.GetMD5(); var item = GetItemById(guid) as UserView; @@ -1506,7 +1506,7 @@ namespace MediaBrowser.Server.Implementations.Library { var path = Path.Combine(ConfigurationManager.ApplicationPaths.ItemsByNamePath, "views", - _fileSystem.GetValidFilename(name)); + _fileSystem.GetValidFilename(type)); Directory.CreateDirectory(Path.GetDirectoryName(path)); diff --git a/MediaBrowser.ServerApplication/MainStartup.cs b/MediaBrowser.ServerApplication/MainStartup.cs index 4f143aaf7..3ea45bc4f 100644 --- a/MediaBrowser.ServerApplication/MainStartup.cs +++ b/MediaBrowser.ServerApplication/MainStartup.cs @@ -527,18 +527,27 @@ namespace MediaBrowser.ServerApplication if (!_isRunningAsService) { - _logger.Info("Executing windows forms restart"); + _logger.Info("Hiding server notify icon"); _serverNotifyIcon.Visible = false; - Application.Restart(); - ShutdownWindowsApplication(); + _logger.Info("Executing windows forms restart"); + //Application.Restart(); + Process.Start(_appHost.ServerConfigurationManager.ApplicationPaths.ApplicationPath); + + _logger.Info("Calling Application.Exit"); + Environment.Exit(0); } } private static void ShutdownWindowsApplication() { + _logger.Info("Hiding server notify icon"); _serverNotifyIcon.Visible = false; + + _logger.Info("Calling Application.Exit"); Application.Exit(); + + _logger.Info("Calling ApplicationTaskCompletionSource.SetResult"); ApplicationTaskCompletionSource.SetResult(true); } diff --git a/MediaBrowser.WebDashboard/Api/DashboardService.cs b/MediaBrowser.WebDashboard/Api/DashboardService.cs index aaa930055..661a53ed9 100644 --- a/MediaBrowser.WebDashboard/Api/DashboardService.cs +++ b/MediaBrowser.WebDashboard/Api/DashboardService.cs @@ -311,6 +311,7 @@ namespace MediaBrowser.WebDashboard.Api /// Modifies the HTML by adding common meta tags, css and js. /// /// The source stream. + /// The user identifier. /// The localization culture. /// Task{Stream}. private async Task ModifyHtml(Stream sourceStream, string localizationCulture) @@ -373,8 +374,7 @@ namespace MediaBrowser.WebDashboard.Api sb.Append(""); sb.Append(""); sb.Append(""); - //sb.Append(""); - //sb.Append(""); + sb.Append(""); //sb.Append(""); sb.Append(""); diff --git a/Nuget/MediaBrowser.Common.Internal.nuspec b/Nuget/MediaBrowser.Common.Internal.nuspec index 810544f7e..99dacdd72 100644 --- a/Nuget/MediaBrowser.Common.Internal.nuspec +++ b/Nuget/MediaBrowser.Common.Internal.nuspec @@ -2,7 +2,7 @@ MediaBrowser.Common.Internal - 3.0.408 + 3.0.409 MediaBrowser.Common.Internal Luke ebr,Luke,scottisafool @@ -12,7 +12,7 @@ Contains common components shared by Media Browser Theater and Media Browser Server. Not intended for plugin developer consumption. Copyright © Media Browser 2013 - + diff --git a/Nuget/MediaBrowser.Common.nuspec b/Nuget/MediaBrowser.Common.nuspec index 99b5dd6db..86e212977 100644 --- a/Nuget/MediaBrowser.Common.nuspec +++ b/Nuget/MediaBrowser.Common.nuspec @@ -2,7 +2,7 @@ MediaBrowser.Common - 3.0.408 + 3.0.409 MediaBrowser.Common Media Browser Team ebr,Luke,scottisafool diff --git a/Nuget/MediaBrowser.Server.Core.nuspec b/Nuget/MediaBrowser.Server.Core.nuspec index 5b45d9840..445368523 100644 --- a/Nuget/MediaBrowser.Server.Core.nuspec +++ b/Nuget/MediaBrowser.Server.Core.nuspec @@ -2,7 +2,7 @@ MediaBrowser.Server.Core - 3.0.408 + 3.0.409 Media Browser.Server.Core Media Browser Team ebr,Luke,scottisafool @@ -12,7 +12,7 @@ Contains core components required to build plugins for Media Browser Server. Copyright © Media Browser 2013 - +