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())