close ffmpeg more gracefully

This commit is contained in:
Luke Pulverenti 2014-06-20 00:50:30 -04:00
parent e666fee20d
commit 4398393783
14 changed files with 313 additions and 106 deletions

View File

@ -3,15 +3,14 @@ using MediaBrowser.Controller;
using MediaBrowser.Controller.Plugins; using MediaBrowser.Controller.Plugins;
using MediaBrowser.Controller.Session; using MediaBrowser.Controller.Session;
using MediaBrowser.Model.Logging; using MediaBrowser.Model.Logging;
using MediaBrowser.Model.Session;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics; using System.Diagnostics;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using MediaBrowser.Model.Session;
namespace MediaBrowser.Api namespace MediaBrowser.Api
{ {
@ -100,7 +99,7 @@ namespace MediaBrowser.Api
{ {
var jobCount = _activeTranscodingJobs.Count; 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 // Try to allow for some time to kill the ffmpeg processes and delete the partial stream files
if (jobCount > 0) if (jobCount > 0)
@ -291,16 +290,16 @@ namespace MediaBrowser.Api
{ {
var job = (TranscodingJob)state; var job = (TranscodingJob)state;
KillTranscodingJob(job); KillTranscodingJob(job, true);
} }
/// <summary> /// <summary>
/// Kills the single transcoding job. /// Kills the single transcoding job.
/// </summary> /// </summary>
/// <param name="deviceId">The device id.</param> /// <param name="deviceId">The device id.</param>
/// <param name="isVideo">if set to <c>true</c> [is video].</param> /// <param name="deleteFiles">if set to <c>true</c> [delete files].</param>
/// <exception cref="System.ArgumentNullException">sourcePath</exception> /// <exception cref="System.ArgumentNullException">sourcePath</exception>
internal void KillTranscodingJobs(string deviceId, bool isVideo) internal void KillTranscodingJobs(string deviceId, bool deleteFiles)
{ {
if (string.IsNullOrEmpty(deviceId)) if (string.IsNullOrEmpty(deviceId))
{ {
@ -318,7 +317,7 @@ namespace MediaBrowser.Api
foreach (var job in jobs) foreach (var job in jobs)
{ {
KillTranscodingJob(job); KillTranscodingJob(job, deleteFiles);
} }
} }
@ -326,7 +325,8 @@ namespace MediaBrowser.Api
/// Kills the transcoding job. /// Kills the transcoding job.
/// </summary> /// </summary>
/// <param name="job">The job.</param> /// <param name="job">The job.</param>
private void KillTranscodingJob(TranscodingJob job) /// <param name="deleteFiles">if set to <c>true</c> [delete files].</param>
private void KillTranscodingJob(TranscodingJob job, bool deleteFiles)
{ {
lock (_activeTranscodingJobs) lock (_activeTranscodingJobs)
{ {
@ -344,48 +344,44 @@ namespace MediaBrowser.Api
} }
} }
var process = job.Process; lock (job.ProcessLock)
var hasExited = true;
try
{ {
hasExited = process.HasExited; var process = job.Process;
}
catch (Exception ex) var hasExited = true;
{
Logger.ErrorException("Error determining if ffmpeg process has exited for {0}", ex, job.Path);
}
if (!hasExited)
{
try 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.Kill();
process.WaitForExit(5000); process.StandardInput.WriteLine("q");
}
catch (Win32Exception ex) // Need to wait because killing is asynchronous
{ process.WaitForExit(5000);
Logger.ErrorException("Error killing transcoding job for {0}", ex, job.Path); }
} catch (Exception ex)
catch (InvalidOperationException ex) {
{ Logger.ErrorException("Error killing transcoding job for {0}", ex, job.Path);
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);
} }
} }
// Dispose the process if (deleteFiles)
process.Dispose(); {
DeletePartialStreamFiles(job.Path, job.Type, 0, 1500);
DeletePartialStreamFiles(job.Path, job.Type, 0, 1500); }
} }
private async void DeletePartialStreamFiles(string path, TranscodingJobType jobType, int retryCount, int delayMs) 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 string DeviceId { get; set; }
public CancellationTokenSource CancellationTokenSource { get; set; } public CancellationTokenSource CancellationTokenSource { get; set; }
public object ProcessLock = new object();
} }
/// <summary> /// <summary>

View File

@ -816,6 +816,7 @@ namespace MediaBrowser.Api.Playback
// Must consume both stdout and stderr or deadlocks may occur // Must consume both stdout and stderr or deadlocks may occur
RedirectStandardOutput = true, RedirectStandardOutput = true,
RedirectStandardError = true, RedirectStandardError = true,
RedirectStandardInput = true,
FileName = MediaEncoder.EncoderPath, FileName = MediaEncoder.EncoderPath,
WorkingDirectory = Path.GetDirectoryName(MediaEncoder.EncoderPath), WorkingDirectory = Path.GetDirectoryName(MediaEncoder.EncoderPath),
@ -1073,8 +1074,9 @@ namespace MediaBrowser.Api.Playback
/// </summary> /// </summary>
/// <param name="process">The process.</param> /// <param name="process">The process.</param>
/// <param name="state">The state.</param> /// <param name="state">The state.</param>
protected void OnFfMpegProcessExited(Process process, StreamState state) private void OnFfMpegProcessExited(Process process, StreamState state)
{ {
Logger.Debug("Disposing stream resources");
state.Dispose(); state.Dispose();
try try
@ -1083,8 +1085,19 @@ namespace MediaBrowser.Api.Playback
} }
catch 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) protected double? GetFramerateParam(StreamState state)

View File

@ -83,7 +83,7 @@ namespace MediaBrowser.Api.Playback.Hls
{ {
var cancellationTokenSource = new CancellationTokenSource(); var cancellationTokenSource = new CancellationTokenSource();
var state = GetState(request, cancellationTokenSource.Token).Result; var state = await GetState(request, cancellationTokenSource.Token).ConfigureAwait(false);
var playlist = state.OutputFilePath; var playlist = state.OutputFilePath;
@ -154,7 +154,7 @@ namespace MediaBrowser.Api.Playback.Hls
/// <returns>System.Int32.</returns> /// <returns>System.Int32.</returns>
protected int GetSegmentWait() protected int GetSegmentWait()
{ {
var minimumSegmentCount = 3; var minimumSegmentCount = 2;
var quality = GetQualitySetting(); var quality = GetQualitySetting();
if (quality == EncodingQuality.HighSpeed || quality == EncodingQuality.HighQuality) if (quality == EncodingQuality.HighSpeed || quality == EncodingQuality.HighQuality)

View File

@ -1,13 +1,10 @@
using MediaBrowser.Common.IO; using MediaBrowser.Common.IO;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Channels; using MediaBrowser.Controller.Channels;
using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Dlna; using MediaBrowser.Controller.Dlna;
using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.LiveTv; using MediaBrowser.Controller.LiveTv;
using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Controller.Persistence;
using MediaBrowser.Model.IO; using MediaBrowser.Model.IO;
using ServiceStack; using ServiceStack;
using System; using System;
@ -17,7 +14,6 @@ using System.IO;
using System.Text; using System.Text;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using MimeTypes = ServiceStack.MimeTypes;
namespace MediaBrowser.Api.Playback.Hls namespace MediaBrowser.Api.Playback.Hls
{ {
@ -83,29 +79,75 @@ namespace MediaBrowser.Api.Playback.Hls
return GetDynamicSegment(request, true).Result; return GetDynamicSegment(request, true).Result;
} }
private static readonly SemaphoreSlim FfmpegStartLock = new SemaphoreSlim(1, 1);
private async Task<object> GetDynamicSegment(GetDynamicHlsVideoSegment request, bool isMain) private async Task<object> GetDynamicSegment(GetDynamicHlsVideoSegment request, bool isMain)
{ {
var cancellationTokenSource = new CancellationTokenSource();
var cancellationToken = cancellationTokenSource.Token;
var index = int.Parse(request.SegmentId, NumberStyles.Integer, UsCulture); 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 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) private string GetSegmentPath(string playlist, int index)
@ -120,7 +162,7 @@ namespace MediaBrowser.Api.Playback.Hls
private object GetSegementResult(string path) private object GetSegementResult(string path)
{ {
// TODO: Handle if it's currently being written to // TODO: Handle if it's currently being written to
return ResultFactory.GetStaticFileResult(Request, path); return ResultFactory.GetStaticFileResult(Request, path, FileShare.ReadWrite);
} }
private async Task<object> GetAsync(GetMasterHlsVideoStream request) private async Task<object> GetAsync(GetMasterHlsVideoStream request)
@ -143,7 +185,7 @@ namespace MediaBrowser.Api.Playback.Hls
var playlistText = GetMasterPlaylistFileText(videoBitrate + audioBitrate, appendBaselineStream, baselineStreamBitrate); var playlistText = GetMasterPlaylistFileText(videoBitrate + audioBitrate, appendBaselineStream, baselineStreamBitrate);
return ResultFactory.GetResult(playlistText, 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, bool includeBaselineStream, int baselineStreamBitrate) private string GetMasterPlaylistFileText(int bitrate, bool includeBaselineStream, int baselineStreamBitrate)
@ -226,7 +268,7 @@ namespace MediaBrowser.Api.Playback.Hls
var playlistText = builder.ToString(); var playlistText = builder.ToString();
return ResultFactory.GetResult(playlistText, MimeTypes.GetMimeType("playlist.m3u8"), new Dictionary<string, string>()); return ResultFactory.GetResult(playlistText, Common.Net.MimeTypes.GetMimeType("playlist.m3u8"), new Dictionary<string, string>());
} }
protected override string GetAudioArguments(StreamState state) 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"; 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; var hasGraphicalSubs = state.SubtitleStream != null && !state.SubtitleStream.IsTextSubtitleStream;

View File

@ -1,19 +1,16 @@
using System.Threading;
using MediaBrowser.Common.IO; using MediaBrowser.Common.IO;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Channels; using MediaBrowser.Controller.Channels;
using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Dlna; using MediaBrowser.Controller.Dlna;
using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.LiveTv; using MediaBrowser.Controller.LiveTv;
using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Controller.Persistence;
using MediaBrowser.Model.IO; using MediaBrowser.Model.IO;
using ServiceStack; using ServiceStack;
using System; using System;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace MediaBrowser.Api.Playback.Hls namespace MediaBrowser.Api.Playback.Hls

View File

@ -134,7 +134,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
/// <returns>System.String.</returns> /// <returns>System.String.</returns>
public string GetProbeSizeArgument(string[] inputFiles, MediaProtocol protocol) public string GetProbeSizeArgument(string[] inputFiles, MediaProtocol protocol)
{ {
return EncodingUtils.GetProbeSizeArgument(inputFiles.Length > 0); return EncodingUtils.GetProbeSizeArgument(inputFiles.Length > 1);
} }
/// <summary> /// <summary>

View File

@ -54,12 +54,14 @@ namespace MediaBrowser.Model.Dlna
// Avoid implicitly captured closure // Avoid implicitly captured closure
string mediaSourceId = options.MediaSourceId; string mediaSourceId = options.MediaSourceId;
mediaSources = new List<MediaSourceInfo>(); var newMediaSources = new List<MediaSourceInfo>();
foreach (MediaSourceInfo i in mediaSources) foreach (MediaSourceInfo i in mediaSources)
{ {
if (StringHelper.EqualsIgnoreCase(i.Id, mediaSourceId)) if (StringHelper.EqualsIgnoreCase(i.Id, mediaSourceId))
mediaSources.Add(i); newMediaSources.Add(i);
} }
mediaSources = newMediaSources;
} }
List<StreamInfo> streams = new List<StreamInfo>(); List<StreamInfo> streams = new List<StreamInfo>();

View File

@ -1,4 +1,5 @@
using MediaBrowser.Common.Extensions; using System.Collections.Concurrent;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.IO; using MediaBrowser.Common.IO;
using MediaBrowser.Controller.Channels; using MediaBrowser.Controller.Channels;
using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Configuration;
@ -23,7 +24,7 @@ using System.Threading.Tasks;
namespace MediaBrowser.Server.Implementations.Channels namespace MediaBrowser.Server.Implementations.Channels
{ {
public class ChannelManager : IChannelManager public class ChannelManager : IChannelManager, IDisposable
{ {
private IChannel[] _channels; private IChannel[] _channels;
private IChannelFactory[] _factories; private IChannelFactory[] _factories;
@ -39,6 +40,9 @@ namespace MediaBrowser.Server.Implementations.Channels
private readonly IJsonSerializer _jsonSerializer; private readonly IJsonSerializer _jsonSerializer;
private readonly ILocalizationManager _localization; private readonly ILocalizationManager _localization;
private readonly ConcurrentDictionary<Guid, bool> _refreshedItems = new ConcurrentDictionary<Guid, bool>();
private Timer _refreshTimer;
public ChannelManager(IUserManager userManager, IDtoService dtoService, ILibraryManager libraryManager, ILogger logger, IServerConfigurationManager config, IFileSystem fileSystem, IUserDataManager userDataManager, IJsonSerializer jsonSerializer, ILocalizationManager localization) 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; _userDataManager = userDataManager;
_jsonSerializer = jsonSerializer; _jsonSerializer = jsonSerializer;
_localization = localization; _localization = localization;
_refreshTimer = new Timer(s => _refreshedItems.Clear(), null, TimeSpan.FromHours(3), TimeSpan.FromHours(3));
} }
private TimeSpan CacheLength private TimeSpan CacheLength
@ -203,8 +209,8 @@ namespace MediaBrowser.Server.Implementations.Channels
if (requiresCallback != null) if (requiresCallback != null)
{ {
results = await requiresCallback.GetChannelItemMediaInfo(item.ExternalId, cancellationToken) results = await GetChannelItemMediaSourcesInternal(requiresCallback, item.ExternalId, cancellationToken)
.ConfigureAwait(false); .ConfigureAwait(false);
} }
else else
{ {
@ -221,6 +227,31 @@ namespace MediaBrowser.Server.Implementations.Channels
return sources; return sources;
} }
private readonly ConcurrentDictionary<string, Tuple<DateTime, List<ChannelMediaInfo>>> _channelItemMediaInfo =
new ConcurrentDictionary<string, Tuple<DateTime, List<ChannelMediaInfo>>>();
private async Task<IEnumerable<ChannelMediaInfo>> GetChannelItemMediaSourcesInternal(IRequiresMediaInfoCallback channel, string id, CancellationToken cancellationToken)
{
Tuple<DateTime, List<ChannelMediaInfo>> 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, List<ChannelMediaInfo>>(DateTime.UtcNow, list);
_channelItemMediaInfo.AddOrUpdate(id, item2, (key, oldValue) => item2);
return list;
}
public IEnumerable<MediaSourceInfo> GetCachedChannelItemMediaSources(string id) public IEnumerable<MediaSourceInfo> GetCachedChannelItemMediaSources(string id)
{ {
var item = (IChannelMediaItem)_libraryManager.GetItemById(id); var item = (IChannelMediaItem)_libraryManager.GetItemById(id);
@ -515,11 +546,7 @@ namespace MediaBrowser.Server.Implementations.Channels
{ {
try try
{ {
var result = await indexable.GetLatestMedia(new ChannelLatestMediaSearch var result = await GetLatestItems(indexable, i, userId, cancellationToken).ConfigureAwait(false);
{
UserId = userId
}, cancellationToken).ConfigureAwait(false);
var resultItems = result.ToList(); var resultItems = result.ToList();
@ -585,6 +612,65 @@ namespace MediaBrowser.Server.Implementations.Channels
}; };
} }
private async Task<IEnumerable<ChannelItemInfo>> 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<List<ChannelItemInfo>>(cachePath);
}
}
catch (FileNotFoundException)
{
}
catch (DirectoryNotFoundException)
{
}
await _resourcePool.WaitAsync(cancellationToken).ConfigureAwait(false);
try
{
try
{
if (_fileSystem.GetLastWriteTimeUtc(cachePath).Add(cacheLength) > DateTime.UtcNow)
{
return _jsonSerializer.DeserializeFromFile<List<ChannelItemInfo>>(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<QueryResult<BaseItemDto>> GetAllMedia(AllChannelMediaQuery query, CancellationToken cancellationToken) public async Task<QueryResult<BaseItemDto>> GetAllMedia(AllChannelMediaQuery query, CancellationToken cancellationToken)
{ {
var user = string.IsNullOrWhiteSpace(query.UserId) var user = string.IsNullOrWhiteSpace(query.UserId)
@ -614,11 +700,7 @@ namespace MediaBrowser.Server.Implementations.Channels
{ {
try try
{ {
var result = await indexable.GetAllMedia(new InternalAllChannelMediaQuery var result = await GetAllItems(indexable, i, userId, cancellationToken).ConfigureAwait(false);
{
UserId = userId
}, cancellationToken).ConfigureAwait(false);
return new Tuple<IChannel, ChannelItemResult>(i, result); return new Tuple<IChannel, ChannelItemResult>(i, result);
} }
@ -677,6 +759,63 @@ namespace MediaBrowser.Server.Implementations.Channels
}; };
} }
private async Task<ChannelItemResult> 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<ChannelItemResult>(cachePath);
}
}
catch (FileNotFoundException)
{
}
catch (DirectoryNotFoundException)
{
}
await _resourcePool.WaitAsync(cancellationToken).ConfigureAwait(false);
try
{
try
{
if (_fileSystem.GetLastWriteTimeUtc(cachePath).Add(cacheLength) > DateTime.UtcNow)
{
return _jsonSerializer.DeserializeFromFile<ChannelItemResult>(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<QueryResult<BaseItemDto>> GetChannelItems(ChannelItemQuery query, CancellationToken cancellationToken) public async Task<QueryResult<BaseItemDto>> GetChannelItems(ChannelItemQuery query, CancellationToken cancellationToken)
{ {
var queryChannelId = query.ChannelId; var queryChannelId = query.ChannelId;
@ -764,11 +903,9 @@ namespace MediaBrowser.Server.Implementations.Channels
{ {
if (!startIndex.HasValue && !limit.HasValue) if (!startIndex.HasValue && !limit.HasValue)
{ {
var channelItemResult = _jsonSerializer.DeserializeFromFile<ChannelItemResult>(cachePath);
if (_fileSystem.GetLastWriteTimeUtc(cachePath).Add(cacheLength) > DateTime.UtcNow) if (_fileSystem.GetLastWriteTimeUtc(cachePath).Add(cacheLength) > DateTime.UtcNow)
{ {
return channelItemResult; return _jsonSerializer.DeserializeFromFile<ChannelItemResult>(cachePath);
} }
} }
} }
@ -789,11 +926,9 @@ namespace MediaBrowser.Server.Implementations.Channels
{ {
if (!startIndex.HasValue && !limit.HasValue) if (!startIndex.HasValue && !limit.HasValue)
{ {
var channelItemResult = _jsonSerializer.DeserializeFromFile<ChannelItemResult>(cachePath);
if (_fileSystem.GetLastWriteTimeUtc(cachePath).Add(cacheLength) > DateTime.UtcNow) if (_fileSystem.GetLastWriteTimeUtc(cachePath).Add(cacheLength) > DateTime.UtcNow)
{ {
return channelItemResult; return _jsonSerializer.DeserializeFromFile<ChannelItemResult>(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 try
{ {
@ -1042,14 +1177,14 @@ namespace MediaBrowser.Server.Implementations.Channels
private async Task RefreshIfNeeded(BaseItem program, CancellationToken cancellationToken) 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); await program.RefreshMetadata(cancellationToken).ConfigureAwait(false);
//_refreshedPrograms.TryAdd(program.Id, true); _refreshedItems.TryAdd(program.Id, true);
} }
internal IChannel GetChannelProvider(Channel channel) internal IChannel GetChannelProvider(Channel channel)
@ -1155,5 +1290,14 @@ namespace MediaBrowser.Server.Implementations.Channels
var name = _localization.GetLocalizedString("ViewTypeChannels"); var name = _localization.GetLocalizedString("ViewTypeChannels");
return await _libraryManager.GetNamedView(name, "channels", "zz_" + name, cancellationToken).ConfigureAwait(false); return await _libraryManager.GetNamedView(name, "channels", "zz_" + name, cancellationToken).ConfigureAwait(false);
} }
public void Dispose()
{
if (_refreshTimer != null)
{
_refreshTimer.Dispose();
_refreshTimer = null;
}
}
} }
} }

View File

@ -1497,7 +1497,7 @@ namespace MediaBrowser.Server.Implementations.Library
public async Task<UserView> GetNamedView(string name, string type, string sortName, CancellationToken cancellationToken) public async Task<UserView> GetNamedView(string name, string type, string sortName, CancellationToken cancellationToken)
{ {
var id = "namedview_2_" + name; var id = "namedview_3_" + name;
var guid = id.GetMD5(); var guid = id.GetMD5();
var item = GetItemById(guid) as UserView; var item = GetItemById(guid) as UserView;
@ -1506,7 +1506,7 @@ namespace MediaBrowser.Server.Implementations.Library
{ {
var path = Path.Combine(ConfigurationManager.ApplicationPaths.ItemsByNamePath, var path = Path.Combine(ConfigurationManager.ApplicationPaths.ItemsByNamePath,
"views", "views",
_fileSystem.GetValidFilename(name)); _fileSystem.GetValidFilename(type));
Directory.CreateDirectory(Path.GetDirectoryName(path)); Directory.CreateDirectory(Path.GetDirectoryName(path));

View File

@ -527,18 +527,27 @@ namespace MediaBrowser.ServerApplication
if (!_isRunningAsService) if (!_isRunningAsService)
{ {
_logger.Info("Executing windows forms restart"); _logger.Info("Hiding server notify icon");
_serverNotifyIcon.Visible = false; _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() private static void ShutdownWindowsApplication()
{ {
_logger.Info("Hiding server notify icon");
_serverNotifyIcon.Visible = false; _serverNotifyIcon.Visible = false;
_logger.Info("Calling Application.Exit");
Application.Exit(); Application.Exit();
_logger.Info("Calling ApplicationTaskCompletionSource.SetResult");
ApplicationTaskCompletionSource.SetResult(true); ApplicationTaskCompletionSource.SetResult(true);
} }

View File

@ -311,6 +311,7 @@ namespace MediaBrowser.WebDashboard.Api
/// Modifies the HTML by adding common meta tags, css and js. /// Modifies the HTML by adding common meta tags, css and js.
/// </summary> /// </summary>
/// <param name="sourceStream">The source stream.</param> /// <param name="sourceStream">The source stream.</param>
/// <param name="userId">The user identifier.</param>
/// <param name="localizationCulture">The localization culture.</param> /// <param name="localizationCulture">The localization culture.</param>
/// <returns>Task{Stream}.</returns> /// <returns>Task{Stream}.</returns>
private async Task<Stream> ModifyHtml(Stream sourceStream, string localizationCulture) private async Task<Stream> ModifyHtml(Stream sourceStream, string localizationCulture)
@ -373,8 +374,7 @@ namespace MediaBrowser.WebDashboard.Api
sb.Append("<meta name=\"viewport\" content=\"width=device-width, initial-scale=1, user-scalable=no\">"); sb.Append("<meta name=\"viewport\" content=\"width=device-width, initial-scale=1, user-scalable=no\">");
sb.Append("<meta name=\"apple-mobile-web-app-capable\" content=\"yes\">"); sb.Append("<meta name=\"apple-mobile-web-app-capable\" content=\"yes\">");
sb.Append("<meta name=\"mobile-web-app-capable\" content=\"yes\">"); sb.Append("<meta name=\"mobile-web-app-capable\" content=\"yes\">");
//sb.Append("<meta name=\"application-name\" content=\"Media Browser\">"); sb.Append("<meta name=\"application-name\" content=\"Media Browser\">");
//sb.Append("<meta name=\"msapplication-config\" content=\"config.xml\">");
//sb.Append("<meta name=\"apple-mobile-web-app-status-bar-style\" content=\"black-translucent\">"); //sb.Append("<meta name=\"apple-mobile-web-app-status-bar-style\" content=\"black-translucent\">");
sb.Append("<link rel=\"icon\" sizes=\"114x114\" href=\"css/images/touchicon114.png\" />"); sb.Append("<link rel=\"icon\" sizes=\"114x114\" href=\"css/images/touchicon114.png\" />");

View File

@ -2,7 +2,7 @@
<package xmlns="http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd"> <package xmlns="http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd">
<metadata> <metadata>
<id>MediaBrowser.Common.Internal</id> <id>MediaBrowser.Common.Internal</id>
<version>3.0.408</version> <version>3.0.409</version>
<title>MediaBrowser.Common.Internal</title> <title>MediaBrowser.Common.Internal</title>
<authors>Luke</authors> <authors>Luke</authors>
<owners>ebr,Luke,scottisafool</owners> <owners>ebr,Luke,scottisafool</owners>
@ -12,7 +12,7 @@
<description>Contains common components shared by Media Browser Theater and Media Browser Server. Not intended for plugin developer consumption.</description> <description>Contains common components shared by Media Browser Theater and Media Browser Server. Not intended for plugin developer consumption.</description>
<copyright>Copyright © Media Browser 2013</copyright> <copyright>Copyright © Media Browser 2013</copyright>
<dependencies> <dependencies>
<dependency id="MediaBrowser.Common" version="3.0.408" /> <dependency id="MediaBrowser.Common" version="3.0.409" />
<dependency id="NLog" version="2.1.0" /> <dependency id="NLog" version="2.1.0" />
<dependency id="SimpleInjector" version="2.5.0" /> <dependency id="SimpleInjector" version="2.5.0" />
<dependency id="sharpcompress" version="0.10.2" /> <dependency id="sharpcompress" version="0.10.2" />

View File

@ -2,7 +2,7 @@
<package xmlns="http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd"> <package xmlns="http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd">
<metadata> <metadata>
<id>MediaBrowser.Common</id> <id>MediaBrowser.Common</id>
<version>3.0.408</version> <version>3.0.409</version>
<title>MediaBrowser.Common</title> <title>MediaBrowser.Common</title>
<authors>Media Browser Team</authors> <authors>Media Browser Team</authors>
<owners>ebr,Luke,scottisafool</owners> <owners>ebr,Luke,scottisafool</owners>

View File

@ -2,7 +2,7 @@
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd"> <package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
<metadata> <metadata>
<id>MediaBrowser.Server.Core</id> <id>MediaBrowser.Server.Core</id>
<version>3.0.408</version> <version>3.0.409</version>
<title>Media Browser.Server.Core</title> <title>Media Browser.Server.Core</title>
<authors>Media Browser Team</authors> <authors>Media Browser Team</authors>
<owners>ebr,Luke,scottisafool</owners> <owners>ebr,Luke,scottisafool</owners>
@ -12,7 +12,7 @@
<description>Contains core components required to build plugins for Media Browser Server.</description> <description>Contains core components required to build plugins for Media Browser Server.</description>
<copyright>Copyright © Media Browser 2013</copyright> <copyright>Copyright © Media Browser 2013</copyright>
<dependencies> <dependencies>
<dependency id="MediaBrowser.Common" version="3.0.408" /> <dependency id="MediaBrowser.Common" version="3.0.409" />
</dependencies> </dependencies>
</metadata> </metadata>
<files> <files>