diff --git a/MediaBrowser.Controller/Persistence/IItemRepository.cs b/MediaBrowser.Controller/Persistence/IItemRepository.cs index 2ef8b70f05..78138999c5 100644 --- a/MediaBrowser.Controller/Persistence/IItemRepository.cs +++ b/MediaBrowser.Controller/Persistence/IItemRepository.cs @@ -154,7 +154,7 @@ namespace MediaBrowser.Controller.Persistence /// /// The query. /// List<BaseItem>. - IEnumerable GetItemList(InternalItemsQuery query); + List GetItemList(InternalItemsQuery query); /// /// Updates the inherited values. diff --git a/MediaBrowser.Dlna/Profiles/LgTvProfile.cs b/MediaBrowser.Dlna/Profiles/LgTvProfile.cs index 4ecfd3c5b9..202ea76fbf 100644 --- a/MediaBrowser.Dlna/Profiles/LgTvProfile.cs +++ b/MediaBrowser.Dlna/Profiles/LgTvProfile.cs @@ -189,6 +189,15 @@ namespace MediaBrowser.Dlna.Profiles } } }; + + SubtitleProfiles = new[] + { + new SubtitleProfile + { + Format = "srt", + Method = SubtitleDeliveryMethod.External + } + }; } } } diff --git a/MediaBrowser.Dlna/Profiles/Xml/LG Smart TV.xml b/MediaBrowser.Dlna/Profiles/Xml/LG Smart TV.xml index 2c6d10e997..1147aa2993 100644 --- a/MediaBrowser.Dlna/Profiles/Xml/LG Smart TV.xml +++ b/MediaBrowser.Dlna/Profiles/Xml/LG Smart TV.xml @@ -77,5 +77,7 @@ - + + + \ No newline at end of file diff --git a/MediaBrowser.MediaEncoding/Encoder/VideoEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/VideoEncoder.cs index 07711301df..82a9668218 100644 --- a/MediaBrowser.MediaEncoding/Encoder/VideoEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/VideoEncoder.cs @@ -94,7 +94,7 @@ namespace MediaBrowser.MediaEncoding.Encoder // Add resolution params, if specified if (!hasGraphicalSubs) { - args += GetOutputSizeParam(state, videoCodec); + args += await GetOutputSizeParam(state, videoCodec).ConfigureAwait(false); } var qualityParam = GetVideoQualityParam(state, videoCodec); diff --git a/MediaBrowser.Server.Implementations/Dto/DtoService.cs b/MediaBrowser.Server.Implementations/Dto/DtoService.cs index a4cf90e5b0..f21dd27ff0 100644 --- a/MediaBrowser.Server.Implementations/Dto/DtoService.cs +++ b/MediaBrowser.Server.Implementations/Dto/DtoService.cs @@ -1306,7 +1306,7 @@ namespace MediaBrowser.Server.Implementations.Dto ItemIds = new[] { item.Id.ToString("N") } }); - dto.ArtistItems = artistItems.Items + dto.AlbumArtists = artistItems.Items .Select(i => { var artist = i.Item1; diff --git a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs index 1b43ff7c19..29c1d1c9a1 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs @@ -1059,6 +1059,9 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV if (recordingStatus == RecordingStatus.Completed) { + timer.Status = RecordingStatus.Completed; + _timerProvider.AddOrUpdate(timer); + OnSuccessfulRecording(info.IsSeries, recordPath); _timerProvider.Delete(timer); } @@ -1067,7 +1070,9 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV const int retryIntervalSeconds = 60; _logger.Info("Retrying recording in {0} seconds.", retryIntervalSeconds); - _timerProvider.StartTimer(timer, TimeSpan.FromSeconds(retryIntervalSeconds)); + timer.Status = RecordingStatus.New; + timer.StartDate = DateTime.UtcNow.AddSeconds(retryIntervalSeconds); + _timerProvider.AddOrUpdate(timer); } else { @@ -1119,7 +1124,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV if (regInfo.IsValid) { - return new EncodedRecorder(_logger, _fileSystem, _mediaEncoder, _config.ApplicationPaths, _jsonSerializer, config); + return new EncodedRecorder(_logger, _fileSystem, _mediaEncoder, _config.ApplicationPaths, _jsonSerializer, config, _httpClient); } } diff --git a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs index 13158d3461..6704dc1396 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs @@ -8,7 +8,9 @@ using System.Text; using System.Threading; using System.Threading.Tasks; using CommonIO; -using MediaBrowser.Common.Configuration; +using MediaBrowser.Common.IO; +using MediaBrowser.Common.Net; +using MediaBrowser.Controller; using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; @@ -22,8 +24,9 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV { private readonly ILogger _logger; private readonly IFileSystem _fileSystem; + private readonly IHttpClient _httpClient; private readonly IMediaEncoder _mediaEncoder; - private readonly IApplicationPaths _appPaths; + private readonly IServerApplicationPaths _appPaths; private readonly LiveTvOptions _liveTvOptions; private bool _hasExited; private Stream _logFileStream; @@ -32,7 +35,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV private readonly IJsonSerializer _json; private readonly TaskCompletionSource _taskCompletionSource = new TaskCompletionSource(); - public EncodedRecorder(ILogger logger, IFileSystem fileSystem, IMediaEncoder mediaEncoder, IApplicationPaths appPaths, IJsonSerializer json, LiveTvOptions liveTvOptions) + public EncodedRecorder(ILogger logger, IFileSystem fileSystem, IMediaEncoder mediaEncoder, IServerApplicationPaths appPaths, IJsonSerializer json, LiveTvOptions liveTvOptions, IHttpClient httpClient) { _logger = logger; _fileSystem = fileSystem; @@ -40,6 +43,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV _appPaths = appPaths; _json = json; _liveTvOptions = liveTvOptions; + _httpClient = httpClient; } public string GetOutputPath(MediaSourceInfo mediaSource, string targetFile) @@ -49,20 +53,73 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV public async Task Record(MediaSourceInfo mediaSource, string targetFile, TimeSpan duration, Action onStarted, CancellationToken cancellationToken) { - if (mediaSource.RunTimeTicks.HasValue) + var tempfile = Path.Combine(_appPaths.TranscodingTempPath, Guid.NewGuid().ToString("N") + ".ts"); + + try { - // The media source already has a fixed duration - // But add another stop 1 minute later just in case the recording gets stuck for any reason - var durationToken = new CancellationTokenSource(duration.Add(TimeSpan.FromMinutes(1))); - cancellationToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, durationToken.Token).Token; + await RecordInternal(mediaSource, tempfile, targetFile, duration, onStarted, cancellationToken) + .ConfigureAwait(false); } - else + finally + { + File.Delete(tempfile); + } + } + + public async Task RecordInternal(MediaSourceInfo mediaSource, string tempFile, string targetFile, TimeSpan duration, Action onStarted, CancellationToken cancellationToken) + { + var httpRequestOptions = new HttpRequestOptions() { - // The media source if infinite so we need to handle stopping ourselves - var durationToken = new CancellationTokenSource(duration); - cancellationToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, durationToken.Token).Token; + Url = mediaSource.Path + }; + + httpRequestOptions.BufferContent = false; + + using (var response = await _httpClient.SendAsync(httpRequestOptions, "GET").ConfigureAwait(false)) + { + _logger.Info("Opened recording stream from tuner provider"); + + Directory.CreateDirectory(Path.GetDirectoryName(tempFile)); + + using (var output = _fileSystem.GetFileStream(tempFile, FileMode.Create, FileAccess.Write, FileShare.Read)) + { + //onStarted(); + + _logger.Info("Copying recording stream to file {0}", tempFile); + + var bufferMs = 5000; + + if (mediaSource.RunTimeTicks.HasValue) + { + // The media source already has a fixed duration + // But add another stop 1 minute later just in case the recording gets stuck for any reason + var durationToken = new CancellationTokenSource(duration.Add(TimeSpan.FromMinutes(1))); + cancellationToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, durationToken.Token).Token; + } + else + { + // The media source if infinite so we need to handle stopping ourselves + var durationToken = new CancellationTokenSource(duration.Add(TimeSpan.FromMilliseconds(bufferMs))); + cancellationToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, durationToken.Token).Token; + } + + var tempFileTask = response.Content.CopyToAsync(output, StreamDefaults.DefaultCopyToBufferSize, cancellationToken); + + // Give the temp file a little time to build up + await Task.Delay(bufferMs, cancellationToken).ConfigureAwait(false); + + await RecordFromFile(mediaSource, tempFile, targetFile, onStarted, cancellationToken) + .ConfigureAwait(false); + + await tempFileTask.ConfigureAwait(false); + } } + _logger.Info("Recording completed to file {0}", targetFile); + } + + private async Task RecordFromFile(MediaSourceInfo mediaSource, string inputFile, string targetFile, Action onStarted, CancellationToken cancellationToken) + { _targetPath = targetFile; _fileSystem.CreateDirectory(Path.GetDirectoryName(targetFile)); @@ -79,7 +136,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV RedirectStandardInput = true, FileName = _mediaEncoder.EncoderPath, - Arguments = GetCommandLineArgs(mediaSource, targetFile, duration), + Arguments = GetCommandLineArgs(mediaSource, inputFile, targetFile), WindowStyle = ProcessWindowStyle.Hidden, ErrorDialog = false @@ -119,7 +176,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV await _taskCompletionSource.Task.ConfigureAwait(false); } - private string GetCommandLineArgs(MediaSourceInfo mediaSource, string targetFile, TimeSpan duration) + private string GetCommandLineArgs(MediaSourceInfo mediaSource, string inputTempFile, string targetFile) { string videoArgs; if (EncodeVideo(mediaSource)) @@ -135,14 +192,14 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV videoArgs = "-codec:v:0 copy"; } - var commandLineArgs = "-fflags +genpts -async 1 -vsync -1 -re -i \"{0}\" -t {4} -sn {2} -map_metadata -1 -threads 0 {3} -y \"{1}\""; + var commandLineArgs = "-fflags +genpts -async 1 -vsync -1 -re -i \"{0}\" -sn {2} -map_metadata -1 -threads 0 {3} -y \"{1}\""; if (mediaSource.ReadAtNativeFramerate) { commandLineArgs = "-re " + commandLineArgs; } - commandLineArgs = string.Format(commandLineArgs, mediaSource.Path, targetFile, videoArgs, GetAudioArgs(mediaSource), _mediaEncoder.GetTimeParameter(duration.Ticks)); + commandLineArgs = string.Format(commandLineArgs, inputTempFile, targetFile, videoArgs, GetAudioArgs(mediaSource)); return commandLineArgs; } diff --git a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/TimerManager.cs b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/TimerManager.cs index 5d462f1069..bcad80447c 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/TimerManager.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/TimerManager.cs @@ -10,6 +10,7 @@ using System.Linq; using System.Threading; using CommonIO; using MediaBrowser.Controller.Power; +using MediaBrowser.Model.LiveTv; namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV { @@ -85,6 +86,11 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV private void AddTimer(TimerInfo item) { + if (item.Status == RecordingStatus.Completed) + { + return; + } + var startDate = RecordingHelper.GetStartTime(item); var now = DateTime.UtcNow; @@ -117,15 +123,15 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV } } - public void StartTimer(TimerInfo item, TimeSpan length) + public void StartTimer(TimerInfo item, TimeSpan dueTime) { StopTimer(item); - var timer = new Timer(TimerCallback, item.Id, length, TimeSpan.Zero); + var timer = new Timer(TimerCallback, item.Id, dueTime, TimeSpan.Zero); if (_timers.TryAdd(item.Id, timer)) { - _logger.Info("Creating recording timer for {0}, {1}. Timer will fire in {2} minutes", item.Id, item.Name, length.TotalMinutes.ToString(CultureInfo.InvariantCulture)); + _logger.Info("Creating recording timer for {0}, {1}. Timer will fire in {2} minutes", item.Id, item.Name, dueTime.TotalMinutes.ToString(CultureInfo.InvariantCulture)); } else { diff --git a/MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs b/MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs index 006cb2edf0..e98bb49d69 100644 --- a/MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs +++ b/MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs @@ -1742,7 +1742,7 @@ namespace MediaBrowser.Server.Implementations.Persistence return " from TypedBaseItems A"; } - public IEnumerable GetItemList(InternalItemsQuery query) + public List GetItemList(InternalItemsQuery query) { if (query == null) { @@ -1842,6 +1842,16 @@ namespace MediaBrowser.Server.Implementations.Persistence CheckDisposed(); + if (!query.EnableTotalRecordCount || (!query.Limit.HasValue && (query.StartIndex ?? 0) == 0)) + { + var list = GetItemList(query); + return new QueryResult + { + Items = list.ToArray(), + TotalRecordCount = list.Count + }; + } + var now = DateTime.UtcNow; using (var cmd = _connection.CreateCommand()) @@ -2196,6 +2206,16 @@ namespace MediaBrowser.Server.Implementations.Persistence CheckDisposed(); + if (!query.EnableTotalRecordCount || (!query.Limit.HasValue && (query.StartIndex ?? 0) == 0)) + { + var list = GetItemIdsList(query); + return new QueryResult + { + Items = list.ToArray(), + TotalRecordCount = list.Count + }; + } + var now = DateTime.UtcNow; using (var cmd = _connection.CreateCommand())