aboutsummaryrefslogtreecommitdiff
path: root/MediaBrowser.Api
diff options
context:
space:
mode:
Diffstat (limited to 'MediaBrowser.Api')
-rw-r--r--MediaBrowser.Api/ApiEntryPoint.cs752
-rw-r--r--MediaBrowser.Api/BaseApiService.cs24
-rw-r--r--MediaBrowser.Api/BasePeriodicWebSocketListener.cs1
-rw-r--r--MediaBrowser.Api/ChannelService.cs23
-rw-r--r--MediaBrowser.Api/ConfigurationService.cs5
-rw-r--r--MediaBrowser.Api/Devices/DeviceService.cs3
-rw-r--r--MediaBrowser.Api/DisplayPreferencesService.cs4
-rw-r--r--MediaBrowser.Api/Dlna/DlnaServerService.cs37
-rw-r--r--MediaBrowser.Api/Dlna/DlnaService.cs9
-rw-r--r--MediaBrowser.Api/EnvironmentService.cs11
-rw-r--r--MediaBrowser.Api/FilterService.cs6
-rw-r--r--MediaBrowser.Api/GamesService.cs73
-rw-r--r--MediaBrowser.Api/IHasItemFields.cs4
-rw-r--r--MediaBrowser.Api/Images/ImageService.cs42
-rw-r--r--MediaBrowser.Api/Images/RemoteImageService.cs10
-rw-r--r--MediaBrowser.Api/ItemUpdateService.cs38
-rw-r--r--MediaBrowser.Api/Library/LibraryService.cs47
-rw-r--r--MediaBrowser.Api/LiveTv/LiveTvService.cs42
-rw-r--r--MediaBrowser.Api/LiveTv/ProgressiveFileCopier.cs (renamed from MediaBrowser.Api/Playback/Progressive/ProgressiveStreamWriter.cs)107
-rw-r--r--MediaBrowser.Api/LocalizationService.cs17
-rw-r--r--MediaBrowser.Api/MediaBrowser.Api.csproj19
-rw-r--r--MediaBrowser.Api/Movies/CollectionService.cs9
-rw-r--r--MediaBrowser.Api/Movies/MoviesService.cs58
-rw-r--r--MediaBrowser.Api/Movies/TrailersService.cs3
-rw-r--r--MediaBrowser.Api/Music/AlbumsService.cs17
-rw-r--r--MediaBrowser.Api/Music/InstantMixService.cs33
-rw-r--r--MediaBrowser.Api/NotificationsService.cs2
-rw-r--r--MediaBrowser.Api/PackageReviewService.cs162
-rw-r--r--MediaBrowser.Api/PackageService.cs27
-rw-r--r--MediaBrowser.Api/Playback/BaseStreamingService.cs1024
-rw-r--r--MediaBrowser.Api/Playback/Hls/BaseHlsService.cs331
-rw-r--r--MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs970
-rw-r--r--MediaBrowser.Api/Playback/Hls/HlsSegmentService.cs163
-rw-r--r--MediaBrowser.Api/Playback/Hls/VideoHlsService.cs137
-rw-r--r--MediaBrowser.Api/Playback/MediaInfoService.cs591
-rw-r--r--MediaBrowser.Api/Playback/Progressive/AudioService.cs67
-rw-r--r--MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs429
-rw-r--r--MediaBrowser.Api/Playback/Progressive/VideoService.cs100
-rw-r--r--MediaBrowser.Api/Playback/StaticRemoteStreamWriter.cs47
-rw-r--r--MediaBrowser.Api/Playback/StreamRequest.cs63
-rw-r--r--MediaBrowser.Api/Playback/StreamState.cs259
-rw-r--r--MediaBrowser.Api/Playback/TranscodingThrottler.cs176
-rw-r--r--MediaBrowser.Api/Playback/UniversalAudioService.cs339
-rw-r--r--MediaBrowser.Api/PlaylistService.cs14
-rw-r--r--MediaBrowser.Api/PluginService.cs11
-rw-r--r--MediaBrowser.Api/Reports/Data/ReportBuilder.cs8
-rw-r--r--MediaBrowser.Api/Reports/ReportsService.cs10
-rw-r--r--MediaBrowser.Api/ScheduledTasks/ScheduledTaskService.cs4
-rw-r--r--MediaBrowser.Api/SearchService.cs15
-rw-r--r--MediaBrowser.Api/Session/SessionsService.cs17
-rw-r--r--MediaBrowser.Api/SimilarItemsHelper.cs15
-rw-r--r--MediaBrowser.Api/Social/SharingService.cs5
-rw-r--r--MediaBrowser.Api/StartupWizardService.cs3
-rw-r--r--MediaBrowser.Api/Subtitles/SubtitleService.cs6
-rw-r--r--MediaBrowser.Api/SuggestionsService.cs14
-rw-r--r--MediaBrowser.Api/System/SystemService.cs22
-rw-r--r--MediaBrowser.Api/TestService.cs77
-rw-r--r--MediaBrowser.Api/TvShowsService.cs105
-rw-r--r--MediaBrowser.Api/UserLibrary/ArtistsService.cs2
-rw-r--r--MediaBrowser.Api/UserLibrary/BaseItemsByNameService.cs33
-rw-r--r--MediaBrowser.Api/UserLibrary/BaseItemsRequest.cs53
-rw-r--r--MediaBrowser.Api/UserLibrary/GameGenresService.cs2
-rw-r--r--MediaBrowser.Api/UserLibrary/GenresService.cs2
-rw-r--r--MediaBrowser.Api/UserLibrary/ItemsService.cs43
-rw-r--r--MediaBrowser.Api/UserLibrary/MusicGenresService.cs2
-rw-r--r--MediaBrowser.Api/UserLibrary/PersonsService.cs12
-rw-r--r--MediaBrowser.Api/UserLibrary/PlaystateService.cs451
-rw-r--r--MediaBrowser.Api/UserLibrary/StudiosService.cs6
-rw-r--r--MediaBrowser.Api/UserLibrary/UserLibraryService.cs57
-rw-r--r--MediaBrowser.Api/UserLibrary/UserViewsService.cs21
-rw-r--r--MediaBrowser.Api/UserLibrary/YearsService.cs6
-rw-r--r--MediaBrowser.Api/UserService.cs63
-rw-r--r--MediaBrowser.Api/VideosService.cs13
73 files changed, 598 insertions, 6775 deletions
diff --git a/MediaBrowser.Api/ApiEntryPoint.cs b/MediaBrowser.Api/ApiEntryPoint.cs
index 34d0bd413..04cef60bf 100644
--- a/MediaBrowser.Api/ApiEntryPoint.cs
+++ b/MediaBrowser.Api/ApiEntryPoint.cs
@@ -1,27 +1,15 @@
-using MediaBrowser.Api.Playback;
-using MediaBrowser.Common.Configuration;
+using System;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.Net;
using MediaBrowser.Controller.Plugins;
using MediaBrowser.Controller.Session;
-using MediaBrowser.Model.Configuration;
-using MediaBrowser.Model.Logging;
-using MediaBrowser.Model.Session;
-using System;
-using System.Collections.Concurrent;
-using System.Collections.Generic;
-using System.Diagnostics;
-using System.IO;
-using System.Linq;
-using System.Threading;
-using System.Threading.Tasks;
-
-using MediaBrowser.Controller.MediaEncoding;
-using MediaBrowser.Controller.Net;
using MediaBrowser.Model.Diagnostics;
using MediaBrowser.Model.IO;
-using MediaBrowser.Model.Dto;
+using MediaBrowser.Model.Logging;
using MediaBrowser.Model.Threading;
+using System.Collections.Generic;
+using System.Threading;
namespace MediaBrowser.Api
{
@@ -53,11 +41,6 @@ namespace MediaBrowser.Api
public readonly ITimerFactory TimerFactory;
public readonly IProcessFactory ProcessFactory;
- /// <summary>
- /// The active transcoding jobs
- /// </summary>
- private readonly List<TranscodingJob> _activeTranscodingJobs = new List<TranscodingJob>();
-
private readonly Dictionary<string, SemaphoreSlim> _transcodingLocks =
new Dictionary<string, SemaphoreSlim>();
@@ -81,742 +64,31 @@ namespace MediaBrowser.Api
ResultFactory = resultFactory;
Instance = this;
- _sessionManager.PlaybackProgress += _sessionManager_PlaybackProgress;
- _sessionManager.PlaybackStart += _sessionManager_PlaybackStart;
}
- public SemaphoreSlim GetTranscodingLock(string outputPath)
+ public static string[] Split(string value, char separator, bool removeEmpty)
{
- lock (_transcodingLocks)
+ if (string.IsNullOrWhiteSpace(value))
{
- SemaphoreSlim result;
- if (!_transcodingLocks.TryGetValue(outputPath, out result))
- {
- result = new SemaphoreSlim(1, 1);
- _transcodingLocks[outputPath] = result;
- }
-
- return result;
+ return new string[] { };
}
- }
- private void _sessionManager_PlaybackStart(object sender, PlaybackProgressEventArgs e)
- {
- if (!string.IsNullOrWhiteSpace(e.PlaySessionId))
+ if (removeEmpty)
{
- PingTranscodingJob(e.PlaySessionId, e.IsPaused);
+ return value.Split(new[] { separator }, StringSplitOptions.RemoveEmptyEntries);
}
- }
- void _sessionManager_PlaybackProgress(object sender, PlaybackProgressEventArgs e)
- {
- if (!string.IsNullOrWhiteSpace(e.PlaySessionId))
- {
- PingTranscodingJob(e.PlaySessionId, e.IsPaused);
- }
+ return value.Split(separator);
}
- /// <summary>
- /// Runs this instance.
- /// </summary>
public void Run()
{
- try
- {
- DeleteEncodedMediaCache();
- }
- catch (FileNotFoundException)
- {
- // Don't clutter the log
- }
- catch (IOException)
- {
- // Don't clutter the log
- }
- catch (Exception ex)
- {
- Logger.ErrorException("Error deleting encoded media cache", ex);
- }
- }
-
- public EncodingOptions GetEncodingOptions()
- {
- return _config.GetConfiguration<EncodingOptions>("encoding");
- }
-
- /// <summary>
- /// Deletes the encoded media cache.
- /// </summary>
- private void DeleteEncodedMediaCache()
- {
- var path = _config.ApplicationPaths.TranscodingTempPath;
-
- foreach (var file in _fileSystem.GetFilePaths(path, true)
- .ToList())
- {
- _fileSystem.DeleteFile(file);
- }
+
}
- /// <summary>
- /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
- /// </summary>
public void Dispose()
{
- Dispose(true);
GC.SuppressFinalize(this);
}
-
- /// <summary>
- /// Releases unmanaged and - optionally - managed resources.
- /// </summary>
- /// <param name="dispose"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
- protected virtual void Dispose(bool dispose)
- {
- var list = _activeTranscodingJobs.ToList();
- var jobCount = list.Count;
-
- Parallel.ForEach(list, j => KillTranscodingJob(j, false, path => true));
-
- // Try to allow for some time to kill the ffmpeg processes and delete the partial stream files
- if (jobCount > 0)
- {
- var task = Task.Delay(1000);
- Task.WaitAll(task);
- }
- }
-
- /// <summary>
- /// Called when [transcode beginning].
- /// </summary>
- /// <param name="path">The path.</param>
- /// <param name="playSessionId">The play session identifier.</param>
- /// <param name="liveStreamId">The live stream identifier.</param>
- /// <param name="transcodingJobId">The transcoding job identifier.</param>
- /// <param name="type">The type.</param>
- /// <param name="process">The process.</param>
- /// <param name="deviceId">The device id.</param>
- /// <param name="state">The state.</param>
- /// <param name="cancellationTokenSource">The cancellation token source.</param>
- /// <returns>TranscodingJob.</returns>
- public TranscodingJob OnTranscodeBeginning(string path,
- string playSessionId,
- string liveStreamId,
- string transcodingJobId,
- TranscodingJobType type,
- IProcess process,
- string deviceId,
- StreamState state,
- CancellationTokenSource cancellationTokenSource)
- {
- lock (_activeTranscodingJobs)
- {
- var job = new TranscodingJob(Logger, TimerFactory)
- {
- Type = type,
- Path = path,
- Process = process,
- ActiveRequestCount = 1,
- DeviceId = deviceId,
- CancellationTokenSource = cancellationTokenSource,
- Id = transcodingJobId,
- PlaySessionId = playSessionId,
- LiveStreamId = liveStreamId,
- MediaSource = state.MediaSource
- };
-
- _activeTranscodingJobs.Add(job);
-
- ReportTranscodingProgress(job, state, null, null, null, null, null);
-
- return job;
- }
- }
-
- public void ReportTranscodingProgress(TranscodingJob job, StreamState state, TimeSpan? transcodingPosition, float? framerate, double? percentComplete, long? bytesTranscoded, int? bitRate)
- {
- var ticks = transcodingPosition.HasValue ? transcodingPosition.Value.Ticks : (long?)null;
-
- if (job != null)
- {
- job.Framerate = framerate;
- job.CompletionPercentage = percentComplete;
- job.TranscodingPositionTicks = ticks;
- job.BytesTranscoded = bytesTranscoded;
- job.BitRate = bitRate;
- }
-
- var deviceId = state.Request.DeviceId;
-
- if (!string.IsNullOrWhiteSpace(deviceId))
- {
- var audioCodec = state.ActualOutputAudioCodec;
- var videoCodec = state.ActualOutputVideoCodec;
-
- _sessionManager.ReportTranscodingInfo(deviceId, new TranscodingInfo
- {
- Bitrate = bitRate ?? state.TotalOutputBitrate,
- AudioCodec = audioCodec,
- VideoCodec = videoCodec,
- Container = state.OutputContainer,
- Framerate = framerate,
- CompletionPercentage = percentComplete,
- Width = state.OutputWidth,
- Height = state.OutputHeight,
- AudioChannels = state.OutputAudioChannels,
- IsAudioDirect = string.Equals(state.OutputAudioCodec, "copy", StringComparison.OrdinalIgnoreCase),
- IsVideoDirect = string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase),
- TranscodeReasons = state.TranscodeReasons
- });
- }
- }
-
- /// <summary>
- /// <summary>
- /// The progressive
- /// </summary>
- /// Called when [transcode failed to start].
- /// </summary>
- /// <param name="path">The path.</param>
- /// <param name="type">The type.</param>
- /// <param name="state">The state.</param>
- public void OnTranscodeFailedToStart(string path, TranscodingJobType type, StreamState state)
- {
- lock (_activeTranscodingJobs)
- {
- var job = _activeTranscodingJobs.FirstOrDefault(j => j.Type == type && string.Equals(j.Path, path, StringComparison.OrdinalIgnoreCase));
-
- if (job != null)
- {
- _activeTranscodingJobs.Remove(job);
- }
- }
-
- lock (_transcodingLocks)
- {
- _transcodingLocks.Remove(path);
- }
-
- if (!string.IsNullOrWhiteSpace(state.Request.DeviceId))
- {
- _sessionManager.ClearTranscodingInfo(state.Request.DeviceId);
- }
- }
-
- /// <summary>
- /// Determines whether [has active transcoding job] [the specified path].
- /// </summary>
- /// <param name="path">The path.</param>
- /// <param name="type">The type.</param>
- /// <returns><c>true</c> if [has active transcoding job] [the specified path]; otherwise, <c>false</c>.</returns>
- public bool HasActiveTranscodingJob(string path, TranscodingJobType type)
- {
- return GetTranscodingJob(path, type) != null;
- }
-
- public TranscodingJob GetTranscodingJob(string path, TranscodingJobType type)
- {
- lock (_activeTranscodingJobs)
- {
- return _activeTranscodingJobs.FirstOrDefault(j => j.Type == type && string.Equals(j.Path, path, StringComparison.OrdinalIgnoreCase));
- }
- }
-
- public TranscodingJob GetTranscodingJob(string playSessionId)
- {
- lock (_activeTranscodingJobs)
- {
- return _activeTranscodingJobs.FirstOrDefault(j => string.Equals(j.PlaySessionId, playSessionId, StringComparison.OrdinalIgnoreCase));
- }
- }
-
- /// <summary>
- /// Called when [transcode begin request].
- /// </summary>
- /// <param name="path">The path.</param>
- /// <param name="type">The type.</param>
- public TranscodingJob OnTranscodeBeginRequest(string path, TranscodingJobType type)
- {
- lock (_activeTranscodingJobs)
- {
- var job = _activeTranscodingJobs.FirstOrDefault(j => j.Type == type && string.Equals(j.Path, path, StringComparison.OrdinalIgnoreCase));
-
- if (job == null)
- {
- return null;
- }
-
- OnTranscodeBeginRequest(job);
-
- return job;
- }
- }
-
- public void OnTranscodeBeginRequest(TranscodingJob job)
- {
- job.ActiveRequestCount++;
-
- if (string.IsNullOrWhiteSpace(job.PlaySessionId) || job.Type == TranscodingJobType.Progressive)
- {
- job.StopKillTimer();
- }
- }
-
- public void OnTranscodeEndRequest(TranscodingJob job)
- {
- job.ActiveRequestCount--;
- //Logger.Debug("OnTranscodeEndRequest job.ActiveRequestCount={0}", job.ActiveRequestCount);
- if (job.ActiveRequestCount <= 0)
- {
- PingTimer(job, false);
- }
- }
- internal void PingTranscodingJob(string playSessionId, bool? isUserPaused)
- {
- if (string.IsNullOrEmpty(playSessionId))
- {
- throw new ArgumentNullException("playSessionId");
- }
-
- //Logger.Debug("PingTranscodingJob PlaySessionId={0} isUsedPaused: {1}", playSessionId, isUserPaused);
-
- List<TranscodingJob> jobs;
-
- lock (_activeTranscodingJobs)
- {
- // This is really only needed for HLS.
- // Progressive streams can stop on their own reliably
- jobs = _activeTranscodingJobs.Where(j => string.Equals(playSessionId, j.PlaySessionId, StringComparison.OrdinalIgnoreCase)).ToList();
- }
-
- foreach (var job in jobs)
- {
- if (isUserPaused.HasValue)
- {
- //Logger.Debug("Setting job.IsUserPaused to {0}. jobId: {1}", isUserPaused, job.Id);
- job.IsUserPaused = isUserPaused.Value;
- }
- PingTimer(job, true);
- }
- }
-
- private void PingTimer(TranscodingJob job, bool isProgressCheckIn)
- {
- if (job.HasExited)
- {
- job.StopKillTimer();
- return;
- }
-
- var timerDuration = 10000;
-
- if (job.Type != TranscodingJobType.Progressive)
- {
- timerDuration = 60000;
- }
-
- job.PingTimeout = timerDuration;
- job.LastPingDate = DateTime.UtcNow;
-
- // Don't start the timer for playback checkins with progressive streaming
- if (job.Type != TranscodingJobType.Progressive || !isProgressCheckIn)
- {
- job.StartKillTimer(OnTranscodeKillTimerStopped);
- }
- else
- {
- job.ChangeKillTimerIfStarted();
- }
- }
-
- /// <summary>
- /// Called when [transcode kill timer stopped].
- /// </summary>
- /// <param name="state">The state.</param>
- private void OnTranscodeKillTimerStopped(object state)
- {
- var job = (TranscodingJob)state;
-
- if (!job.HasExited && job.Type != TranscodingJobType.Progressive)
- {
- var timeSinceLastPing = (DateTime.UtcNow - job.LastPingDate).TotalMilliseconds;
-
- if (timeSinceLastPing < job.PingTimeout)
- {
- job.StartKillTimer(OnTranscodeKillTimerStopped, job.PingTimeout);
- return;
- }
- }
-
- Logger.Info("Transcoding kill timer stopped for JobId {0} PlaySessionId {1}. Killing transcoding", job.Id, job.PlaySessionId);
-
- KillTranscodingJob(job, true, path => true);
- }
-
- /// <summary>
- /// Kills the single transcoding job.
- /// </summary>
- /// <param name="deviceId">The device id.</param>
- /// <param name="playSessionId">The play session identifier.</param>
- /// <param name="deleteFiles">The delete files.</param>
- /// <returns>Task.</returns>
- internal void KillTranscodingJobs(string deviceId, string playSessionId, Func<string, bool> deleteFiles)
- {
- KillTranscodingJobs(j =>
- {
- if (!string.IsNullOrWhiteSpace(playSessionId))
- {
- return string.Equals(playSessionId, j.PlaySessionId, StringComparison.OrdinalIgnoreCase);
- }
-
- return string.Equals(deviceId, j.DeviceId, StringComparison.OrdinalIgnoreCase);
-
- }, deleteFiles);
- }
-
- /// <summary>
- /// Kills the transcoding jobs.
- /// </summary>
- /// <param name="killJob">The kill job.</param>
- /// <param name="deleteFiles">The delete files.</param>
- /// <returns>Task.</returns>
- private void KillTranscodingJobs(Func<TranscodingJob, bool> killJob, Func<string, bool> deleteFiles)
- {
- var jobs = new List<TranscodingJob>();
-
- lock (_activeTranscodingJobs)
- {
- // This is really only needed for HLS.
- // Progressive streams can stop on their own reliably
- jobs.AddRange(_activeTranscodingJobs.Where(killJob));
- }
-
- if (jobs.Count == 0)
- {
- return;
- }
-
- foreach (var job in jobs)
- {
- KillTranscodingJob(job, false, deleteFiles);
- }
- }
-
- /// <summary>
- /// Kills the transcoding job.
- /// </summary>
- /// <param name="job">The job.</param>
- /// <param name="closeLiveStream">if set to <c>true</c> [close live stream].</param>
- /// <param name="delete">The delete.</param>
- private async void KillTranscodingJob(TranscodingJob job, bool closeLiveStream, Func<string, bool> delete)
- {
- job.DisposeKillTimer();
-
- Logger.Debug("KillTranscodingJob - JobId {0} PlaySessionId {1}. Killing transcoding", job.Id, job.PlaySessionId);
-
- lock (_activeTranscodingJobs)
- {
- _activeTranscodingJobs.Remove(job);
-
- if (!job.CancellationTokenSource.IsCancellationRequested)
- {
- job.CancellationTokenSource.Cancel();
- }
- }
-
- lock (_transcodingLocks)
- {
- _transcodingLocks.Remove(job.Path);
- }
-
- lock (job.ProcessLock)
- {
- if (job.TranscodingThrottler != null)
- {
- job.TranscodingThrottler.Stop();
- }
-
- var process = job.Process;
-
- var hasExited = job.HasExited;
-
- if (!hasExited)
- {
- try
- {
- Logger.Info("Stopping ffmpeg process with q command for {0}", job.Path);
-
- //process.Kill();
- process.StandardInput.WriteLine("q");
-
- // Need to wait because killing is asynchronous
- if (!process.WaitForExit(5000))
- {
- Logger.Info("Killing ffmpeg process for {0}", job.Path);
- process.Kill();
- }
- }
- catch (Exception ex)
- {
- Logger.ErrorException("Error killing transcoding job for {0}", ex, job.Path);
- }
- }
- }
-
- if (delete(job.Path))
- {
- DeletePartialStreamFiles(job.Path, job.Type, 0, 1500);
- }
-
- if (closeLiveStream && !string.IsNullOrWhiteSpace(job.LiveStreamId))
- {
- try
- {
- await _mediaSourceManager.CloseLiveStream(job.LiveStreamId).ConfigureAwait(false);
- }
- catch (Exception ex)
- {
- Logger.ErrorException("Error closing live stream for {0}", ex, job.Path);
- }
- }
- }
-
- private async void DeletePartialStreamFiles(string path, TranscodingJobType jobType, int retryCount, int delayMs)
- {
- if (retryCount >= 10)
- {
- return;
- }
-
- Logger.Info("Deleting partial stream file(s) {0}", path);
-
- await Task.Delay(delayMs).ConfigureAwait(false);
-
- try
- {
- if (jobType == TranscodingJobType.Progressive)
- {
- DeleteProgressivePartialStreamFiles(path);
- }
- else
- {
- DeleteHlsPartialStreamFiles(path);
- }
- }
- catch (FileNotFoundException)
- {
-
- }
- catch (IOException)
- {
- //Logger.ErrorException("Error deleting partial stream file(s) {0}", ex, path);
-
- DeletePartialStreamFiles(path, jobType, retryCount + 1, 500);
- }
- catch
- {
- //Logger.ErrorException("Error deleting partial stream file(s) {0}", ex, path);
- }
- }
-
- /// <summary>
- /// Deletes the progressive partial stream files.
- /// </summary>
- /// <param name="outputFilePath">The output file path.</param>
- private void DeleteProgressivePartialStreamFiles(string outputFilePath)
- {
- _fileSystem.DeleteFile(outputFilePath);
- }
-
- /// <summary>
- /// Deletes the HLS partial stream files.
- /// </summary>
- /// <param name="outputFilePath">The output file path.</param>
- private void DeleteHlsPartialStreamFiles(string outputFilePath)
- {
- var directory = _fileSystem.GetDirectoryName(outputFilePath);
- var name = Path.GetFileNameWithoutExtension(outputFilePath);
-
- var filesToDelete = _fileSystem.GetFilePaths(directory)
- .Where(f => f.IndexOf(name, StringComparison.OrdinalIgnoreCase) != -1)
- .ToList();
-
- Exception e = null;
-
- foreach (var file in filesToDelete)
- {
- try
- {
- //Logger.Debug("Deleting HLS file {0}", file);
- _fileSystem.DeleteFile(file);
- }
- catch (FileNotFoundException)
- {
-
- }
- catch (IOException ex)
- {
- e = ex;
- //Logger.ErrorException("Error deleting HLS file {0}", ex, file);
- }
- }
-
- if (e != null)
- {
- throw e;
- }
- }
- }
-
- /// <summary>
- /// Class TranscodingJob
- /// </summary>
- public class TranscodingJob
- {
- /// <summary>
- /// Gets or sets the play session identifier.
- /// </summary>
- /// <value>The play session identifier.</value>
- public string PlaySessionId { get; set; }
- /// <summary>
- /// Gets or sets the live stream identifier.
- /// </summary>
- /// <value>The live stream identifier.</value>
- public string LiveStreamId { get; set; }
-
- public bool IsLiveOutput { get; set; }
-
- /// <summary>
- /// Gets or sets the path.
- /// </summary>
- /// <value>The path.</value>
- public MediaSourceInfo MediaSource { get; set; }
- public string Path { get; set; }
- /// <summary>
- /// Gets or sets the type.
- /// </summary>
- /// <value>The type.</value>
- public TranscodingJobType Type { get; set; }
- /// <summary>
- /// Gets or sets the process.
- /// </summary>
- /// <value>The process.</value>
- public IProcess Process { get; set; }
- public ILogger Logger { get; private set; }
- /// <summary>
- /// Gets or sets the active request count.
- /// </summary>
- /// <value>The active request count.</value>
- public int ActiveRequestCount { get; set; }
- /// <summary>
- /// Gets or sets the kill timer.
- /// </summary>
- /// <value>The kill timer.</value>
- private ITimer KillTimer { get; set; }
-
- private readonly ITimerFactory _timerFactory;
-
- public string DeviceId { get; set; }
-
- public CancellationTokenSource CancellationTokenSource { get; set; }
-
- public object ProcessLock = new object();
-
- public bool HasExited { get; set; }
- public bool IsUserPaused { get; set; }
-
- public string Id { get; set; }
-
- public float? Framerate { get; set; }
- public double? CompletionPercentage { get; set; }
-
- public long? BytesDownloaded { get; set; }
- public long? BytesTranscoded { get; set; }
- public int? BitRate { get; set; }
-
- public long? TranscodingPositionTicks { get; set; }
- public long? DownloadPositionTicks { get; set; }
-
- public TranscodingThrottler TranscodingThrottler { get; set; }
-
- private readonly object _timerLock = new object();
-
- public DateTime LastPingDate { get; set; }
- public int PingTimeout { get; set; }
-
- public TranscodingJob(ILogger logger, ITimerFactory timerFactory)
- {
- Logger = logger;
- _timerFactory = timerFactory;
- }
-
- public void StopKillTimer()
- {
- lock (_timerLock)
- {
- if (KillTimer != null)
- {
- KillTimer.Change(Timeout.Infinite, Timeout.Infinite);
- }
- }
- }
-
- public void DisposeKillTimer()
- {
- lock (_timerLock)
- {
- if (KillTimer != null)
- {
- KillTimer.Dispose();
- KillTimer = null;
- }
- }
- }
-
- public void StartKillTimer(Action<object> callback)
- {
- StartKillTimer(callback, PingTimeout);
- }
-
- public void StartKillTimer(Action<object> callback, int intervalMs)
- {
- if (HasExited)
- {
- return;
- }
-
- lock (_timerLock)
- {
- if (KillTimer == null)
- {
- //Logger.Debug("Starting kill timer at {0}ms. JobId {1} PlaySessionId {2}", intervalMs, Id, PlaySessionId);
- KillTimer = _timerFactory.Create(callback, this, intervalMs, Timeout.Infinite);
- }
- else
- {
- //Logger.Debug("Changing kill timer to {0}ms. JobId {1} PlaySessionId {2}", intervalMs, Id, PlaySessionId);
- KillTimer.Change(intervalMs, Timeout.Infinite);
- }
- }
- }
-
- public void ChangeKillTimerIfStarted()
- {
- if (HasExited)
- {
- return;
- }
-
- lock (_timerLock)
- {
- if (KillTimer != null)
- {
- var intervalMs = PingTimeout;
-
- //Logger.Debug("Changing kill timer to {0}ms. JobId {1} PlaySessionId {2}", intervalMs, Id, PlaySessionId);
- KillTimer.Change(intervalMs, Timeout.Infinite);
- }
- }
- }
}
}
diff --git a/MediaBrowser.Api/BaseApiService.cs b/MediaBrowser.Api/BaseApiService.cs
index d3cc18d4b..1629d49b4 100644
--- a/MediaBrowser.Api/BaseApiService.cs
+++ b/MediaBrowser.Api/BaseApiService.cs
@@ -11,6 +11,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using MediaBrowser.Model.Services;
+using MediaBrowser.Model.Extensions;
namespace MediaBrowser.Api
{
@@ -54,6 +55,17 @@ namespace MediaBrowser.Api
return Request.Headers[name];
}
+ private static readonly string[] EmptyStringArray = new string[] { };
+ public static string[] SplitValue(string value, char delim)
+ {
+ if (string.IsNullOrWhiteSpace(value))
+ {
+ return EmptyStringArray;
+ }
+
+ return value.Split(new[] { delim }, StringSplitOptions.RemoveEmptyEntries);
+ }
+
/// <summary>
/// To the optimized result.
/// </summary>
@@ -128,7 +140,7 @@ namespace MediaBrowser.Api
var hasFields = request as IHasItemFields;
if (hasFields != null)
{
- options.Fields = hasFields.GetItemFields().ToList();
+ options.Fields = hasFields.GetItemFields();
}
var client = authInfo.Client ?? string.Empty;
@@ -137,7 +149,9 @@ namespace MediaBrowser.Api
client.IndexOf("media center", StringComparison.OrdinalIgnoreCase) != -1 ||
client.IndexOf("classic", StringComparison.OrdinalIgnoreCase) != -1)
{
- options.Fields.Add(Model.Querying.ItemFields.RecursiveItemCount);
+ var list = options.Fields.ToList();
+ list.Add(Model.Querying.ItemFields.RecursiveItemCount);
+ options.Fields = list.ToArray(list.Count);
}
if (client.IndexOf("kodi", StringComparison.OrdinalIgnoreCase) != -1 ||
@@ -148,7 +162,9 @@ namespace MediaBrowser.Api
client.IndexOf("samsung", StringComparison.OrdinalIgnoreCase) != -1 ||
client.IndexOf("androidtv", StringComparison.OrdinalIgnoreCase) != -1)
{
- options.Fields.Add(Model.Querying.ItemFields.ChildCount);
+ var list = options.Fields.ToList();
+ list.Add(Model.Querying.ItemFields.ChildCount);
+ options.Fields = list.ToArray(list.Count);
}
var hasDtoOptions = request as IHasDtoOptions;
@@ -167,7 +183,7 @@ namespace MediaBrowser.Api
if (!string.IsNullOrWhiteSpace(hasDtoOptions.EnableImageTypes))
{
- options.ImageTypes = (hasDtoOptions.EnableImageTypes ?? string.Empty).Split(',').Where(i => !string.IsNullOrWhiteSpace(i)).Select(v => (ImageType)Enum.Parse(typeof(ImageType), v, true)).ToList();
+ options.ImageTypes = (hasDtoOptions.EnableImageTypes ?? string.Empty).Split(',').Where(i => !string.IsNullOrWhiteSpace(i)).Select(v => (ImageType)Enum.Parse(typeof(ImageType), v, true)).ToArray();
}
}
diff --git a/MediaBrowser.Api/BasePeriodicWebSocketListener.cs b/MediaBrowser.Api/BasePeriodicWebSocketListener.cs
index 8004d7e9b..c7a9d97ba 100644
--- a/MediaBrowser.Api/BasePeriodicWebSocketListener.cs
+++ b/MediaBrowser.Api/BasePeriodicWebSocketListener.cs
@@ -314,6 +314,7 @@ namespace MediaBrowser.Api
public void Dispose()
{
Dispose(true);
+ GC.SuppressFinalize(this);
}
}
diff --git a/MediaBrowser.Api/ChannelService.cs b/MediaBrowser.Api/ChannelService.cs
index bce1e6682..d64bf7ec7 100644
--- a/MediaBrowser.Api/ChannelService.cs
+++ b/MediaBrowser.Api/ChannelService.cs
@@ -9,6 +9,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
+using MediaBrowser.Api.UserLibrary;
using MediaBrowser.Model.Services;
namespace MediaBrowser.Api
@@ -55,7 +56,7 @@ namespace MediaBrowser.Api
}
[Route("/Channels/Features", "GET", Summary = "Gets features for a channel")]
- public class GetAllChannelFeatures : IReturn<List<ChannelFeatures>>
+ public class GetAllChannelFeatures : IReturn<ChannelFeatures[]>
{
}
@@ -90,7 +91,7 @@ namespace MediaBrowser.Api
public int? Limit { get; set; }
[ApiMember(Name = "SortOrder", Description = "Sort Order - Ascending,Descending", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
- public SortOrder? SortOrder { get; set; }
+ public string SortOrder { get; set; }
[ApiMember(Name = "Filters", Description = "Optional. Specify additional filters to apply. This allows multiple, comma delimeted. Options: IsFolder, IsNotFolder, IsUnplayed, IsPlayed, IsFavorite, IsResumable, Likes, Dislikes", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
public string Filters { get; set; }
@@ -116,6 +117,15 @@ namespace MediaBrowser.Api
return val.Split(',').Select(v => (ItemFilter)Enum.Parse(typeof(ItemFilter), v, true));
}
+
+ /// <summary>
+ /// Gets the order by.
+ /// </summary>
+ /// <returns>IEnumerable{ItemSortBy}.</returns>
+ public Tuple<string, SortOrder>[] GetOrderBy()
+ {
+ return BaseItemsRequest.GetOrderBy(SortBy, SortOrder);
+ }
}
[Route("/Channels/Items/Latest", "GET", Summary = "Gets channel items")]
@@ -187,7 +197,7 @@ namespace MediaBrowser.Api
public object Get(GetAllChannelFeatures request)
{
- var result = _channelManager.GetAllChannelFeatures().ToList();
+ var result = _channelManager.GetAllChannelFeatures();
return ToOptimizedResult(result);
}
@@ -228,10 +238,9 @@ namespace MediaBrowser.Api
UserId = request.UserId,
ChannelId = request.Id,
FolderId = request.FolderId,
- SortOrder = request.SortOrder,
- SortBy = (request.SortBy ?? string.Empty).Split(',').Where(i => !string.IsNullOrWhiteSpace(i)).ToArray(),
+ OrderBy = request.GetOrderBy(),
Filters = request.GetFilters().ToArray(),
- Fields = request.GetItemFields().ToArray()
+ Fields = request.GetItemFields()
}, CancellationToken.None).ConfigureAwait(false);
@@ -247,7 +256,7 @@ namespace MediaBrowser.Api
ChannelIds = (request.ChannelIds ?? string.Empty).Split(',').Where(i => !string.IsNullOrWhiteSpace(i)).ToArray(),
UserId = request.UserId,
Filters = request.GetFilters().ToArray(),
- Fields = request.GetItemFields().ToList()
+ Fields = request.GetItemFields()
}, CancellationToken.None).ConfigureAwait(false);
diff --git a/MediaBrowser.Api/ConfigurationService.cs b/MediaBrowser.Api/ConfigurationService.cs
index 8d5f46962..643ecd9c8 100644
--- a/MediaBrowser.Api/ConfigurationService.cs
+++ b/MediaBrowser.Api/ConfigurationService.cs
@@ -6,7 +6,6 @@ using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Serialization;
using System.Collections.Generic;
using System.IO;
-using System.Linq;
using System.Threading.Tasks;
using MediaBrowser.Controller.IO;
@@ -62,7 +61,7 @@ namespace MediaBrowser.Api
[Route("/System/Configuration/MetadataPlugins", "GET", Summary = "Gets all available metadata plugins")]
[Authenticated(Roles = "Admin")]
- public class GetMetadataPlugins : IReturn<List<MetadataPluginSummary>>
+ public class GetMetadataPlugins : IReturn<MetadataPluginSummary[]>
{
}
@@ -170,7 +169,7 @@ namespace MediaBrowser.Api
public object Get(GetMetadataPlugins request)
{
- return ToOptimizedSerializedResultUsingCache(_providerManager.GetAllMetadataPlugins().ToList());
+ return ToOptimizedSerializedResultUsingCache(_providerManager.GetAllMetadataPlugins());
}
}
}
diff --git a/MediaBrowser.Api/Devices/DeviceService.cs b/MediaBrowser.Api/Devices/DeviceService.cs
index 544960f5f..012f0ddb2 100644
--- a/MediaBrowser.Api/Devices/DeviceService.cs
+++ b/MediaBrowser.Api/Devices/DeviceService.cs
@@ -1,5 +1,4 @@
using System;
-using System.Linq;
using MediaBrowser.Controller.Devices;
using MediaBrowser.Controller.Net;
using MediaBrowser.Model.Devices;
@@ -143,7 +142,7 @@ namespace MediaBrowser.Api.Devices
}
else
{
- var file = Request.Files.First();
+ var file = Request.Files.Length == 0 ? null : Request.Files[0];
var task = _deviceManager.AcceptCameraUpload(deviceId, file.InputStream, new LocalFileInfo
{
diff --git a/MediaBrowser.Api/DisplayPreferencesService.cs b/MediaBrowser.Api/DisplayPreferencesService.cs
index 5a21fc9f4..4f8cc5255 100644
--- a/MediaBrowser.Api/DisplayPreferencesService.cs
+++ b/MediaBrowser.Api/DisplayPreferencesService.cs
@@ -88,9 +88,7 @@ namespace MediaBrowser.Api
// Serialize to json and then back so that the core doesn't see the request dto type
var displayPreferences = _jsonSerializer.DeserializeFromString<DisplayPreferences>(_jsonSerializer.SerializeToString(request));
- var task = _displayPreferencesManager.SaveDisplayPreferences(displayPreferences, request.UserId, request.Client, CancellationToken.None);
-
- Task.WaitAll(task);
+ _displayPreferencesManager.SaveDisplayPreferences(displayPreferences, request.UserId, request.Client, CancellationToken.None);
}
}
}
diff --git a/MediaBrowser.Api/Dlna/DlnaServerService.cs b/MediaBrowser.Api/Dlna/DlnaServerService.cs
index fc8c0edf6..cbef6e5b3 100644
--- a/MediaBrowser.Api/Dlna/DlnaServerService.cs
+++ b/MediaBrowser.Api/Dlna/DlnaServerService.cs
@@ -3,7 +3,6 @@ using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
-using System.Linq;
using System.Threading.Tasks;
using MediaBrowser.Controller.IO;
@@ -71,24 +70,27 @@ namespace MediaBrowser.Api.Dlna
public Stream RequestStream { get; set; }
}
- [Route("/Dlna/{UuId}/mediareceiverregistrar/events", Summary = "Processes an event subscription request")]
+ [Route("/Dlna/{UuId}/mediareceiverregistrar/events", "SUBSCRIBE", Summary = "Processes an event subscription request")]
+ [Route("/Dlna/{UuId}/mediareceiverregistrar/events", "UNSUBSCRIBE", Summary = "Processes an event subscription request")]
public class ProcessMediaReceiverRegistrarEventRequest
{
- [ApiMember(Name = "UuId", Description = "Server UuId", IsRequired = false, DataType = "string", ParameterType = "path", Verb = "SUBSCRIBE,POST")]
+ [ApiMember(Name = "UuId", Description = "Server UuId", IsRequired = false, DataType = "string", ParameterType = "path", Verb = "SUBSCRIBE,UNSUBSCRIBE")]
public string UuId { get; set; }
}
- [Route("/Dlna/{UuId}/contentdirectory/events", Summary = "Processes an event subscription request")]
+ [Route("/Dlna/{UuId}/contentdirectory/events", "SUBSCRIBE", Summary = "Processes an event subscription request")]
+ [Route("/Dlna/{UuId}/contentdirectory/events", "UNSUBSCRIBE", Summary = "Processes an event subscription request")]
public class ProcessContentDirectoryEventRequest
{
- [ApiMember(Name = "UuId", Description = "Server UuId", IsRequired = false, DataType = "string", ParameterType = "path", Verb = "SUBSCRIBE,POST")]
+ [ApiMember(Name = "UuId", Description = "Server UuId", IsRequired = false, DataType = "string", ParameterType = "path", Verb = "SUBSCRIBE,UNSUBSCRIBE")]
public string UuId { get; set; }
}
- [Route("/Dlna/{UuId}/connectionmanager/events", Summary = "Processes an event subscription request")]
+ [Route("/Dlna/{UuId}/connectionmanager/events", "SUBSCRIBE", Summary = "Processes an event subscription request")]
+ [Route("/Dlna/{UuId}/connectionmanager/events", "UNSUBSCRIBE", Summary = "Processes an event subscription request")]
public class ProcessConnectionManagerEventRequest
{
- [ApiMember(Name = "UuId", Description = "Server UuId", IsRequired = false, DataType = "string", ParameterType = "path", Verb = "SUBSCRIBE,POST")]
+ [ApiMember(Name = "UuId", Description = "Server UuId", IsRequired = false, DataType = "string", ParameterType = "path", Verb = "SUBSCRIBE,UNSUBSCRIBE")]
public string UuId { get; set; }
}
@@ -201,17 +203,32 @@ namespace MediaBrowser.Api.Dlna
}
}
- public object Any(ProcessContentDirectoryEventRequest request)
+ public object Subscribe(ProcessContentDirectoryEventRequest request)
{
return ProcessEventRequest(_contentDirectory);
}
- public object Any(ProcessConnectionManagerEventRequest request)
+ public object Subscribe(ProcessConnectionManagerEventRequest request)
{
return ProcessEventRequest(_connectionManager);
}
- public object Any(ProcessMediaReceiverRegistrarEventRequest request)
+ public object Subscribe(ProcessMediaReceiverRegistrarEventRequest request)
+ {
+ return ProcessEventRequest(_mediaReceiverRegistrar);
+ }
+
+ public object Unsubscribe(ProcessContentDirectoryEventRequest request)
+ {
+ return ProcessEventRequest(_contentDirectory);
+ }
+
+ public object Unsubscribe(ProcessConnectionManagerEventRequest request)
+ {
+ return ProcessEventRequest(_connectionManager);
+ }
+
+ public object Unsubscribe(ProcessMediaReceiverRegistrarEventRequest request)
{
return ProcessEventRequest(_mediaReceiverRegistrar);
}
diff --git a/MediaBrowser.Api/Dlna/DlnaService.cs b/MediaBrowser.Api/Dlna/DlnaService.cs
index ecb54bf5c..4dd71f446 100644
--- a/MediaBrowser.Api/Dlna/DlnaService.cs
+++ b/MediaBrowser.Api/Dlna/DlnaService.cs
@@ -1,14 +1,13 @@
-using MediaBrowser.Controller.Dlna;
+using System.Linq;
+using MediaBrowser.Controller.Dlna;
using MediaBrowser.Controller.Net;
using MediaBrowser.Model.Dlna;
-using System.Collections.Generic;
-using System.Linq;
using MediaBrowser.Model.Services;
namespace MediaBrowser.Api.Dlna
{
[Route("/Dlna/ProfileInfos", "GET", Summary = "Gets a list of profiles")]
- public class GetProfileInfos : IReturn<List<DeviceProfileInfo>>
+ public class GetProfileInfos : IReturn<DeviceProfileInfo[]>
{
}
@@ -53,7 +52,7 @@ namespace MediaBrowser.Api.Dlna
public object Get(GetProfileInfos request)
{
- var result = _dlnaManager.GetProfileInfos().ToList();
+ var result = _dlnaManager.GetProfileInfos().ToArray();
return ToOptimizedResult(result);
}
diff --git a/MediaBrowser.Api/EnvironmentService.cs b/MediaBrowser.Api/EnvironmentService.cs
index 9764a71db..400169ac5 100644
--- a/MediaBrowser.Api/EnvironmentService.cs
+++ b/MediaBrowser.Api/EnvironmentService.cs
@@ -226,7 +226,7 @@ namespace MediaBrowser.Api
return ToOptimizedSerializedResultUsingCache(GetNetworkShares(path).OrderBy(i => i.Path).ToList());
}
- return ToOptimizedSerializedResultUsingCache(GetFileSystemEntries(request).OrderBy(i => i.Path).ToList());
+ return ToOptimizedSerializedResultUsingCache(GetFileSystemEntries(request).ToList());
}
public object Get(GetNetworkShares request)
@@ -271,9 +271,7 @@ namespace MediaBrowser.Api
/// <returns>System.Object.</returns>
public object Get(GetNetworkDevices request)
{
- var result = _networkManager.GetNetworkDevices()
- .OrderBy(i => i.Path)
- .ToList();
+ var result = _networkManager.GetNetworkDevices().ToList();
return ToOptimizedSerializedResultUsingCache(result);
}
@@ -300,8 +298,7 @@ namespace MediaBrowser.Api
/// <returns>IEnumerable{FileSystemEntryInfo}.</returns>
private IEnumerable<FileSystemEntryInfo> GetFileSystemEntries(GetDirectoryContents request)
{
- // using EnumerateFileSystemInfos doesn't handle reparse points (symlinks)
- var entries = _fileSystem.GetFileSystemEntries(request.Path).Where(i =>
+ var entries = _fileSystem.GetFileSystemEntries(request.Path).OrderBy(i => i.FullName).Where(i =>
{
if (!request.IncludeHidden && i.IsHidden)
{
@@ -329,7 +326,7 @@ namespace MediaBrowser.Api
Path = f.FullName,
Type = f.IsDirectory ? FileSystemEntryType.Directory : FileSystemEntryType.File
- }).ToList();
+ });
}
public object Get(GetParentPath request)
diff --git a/MediaBrowser.Api/FilterService.cs b/MediaBrowser.Api/FilterService.cs
index a1f891506..52b274653 100644
--- a/MediaBrowser.Api/FilterService.cs
+++ b/MediaBrowser.Api/FilterService.cs
@@ -61,9 +61,9 @@ namespace MediaBrowser.Api
user == null ? _libraryManager.RootFolder : user.RootFolder :
parentItem;
- var result = ((Folder)item).GetItems(GetItemsQuery(request, user));
+ var result = ((Folder)item).GetItemList(GetItemsQuery(request, user));
- return ToOptimizedResult(GetFilters(result.Items));
+ return ToOptimizedResult(GetFilters(result));
}
private QueryFilters GetFilters(BaseItem[] items)
@@ -108,7 +108,7 @@ namespace MediaBrowser.Api
EnableTotalRecordCount = false,
DtoOptions = new Controller.Dto.DtoOptions
{
- Fields = new List<ItemFields> { ItemFields.Genres, ItemFields.Tags },
+ Fields = new ItemFields[] { ItemFields.Genres, ItemFields.Tags },
EnableImages = false,
EnableUserData = false
}
diff --git a/MediaBrowser.Api/GamesService.cs b/MediaBrowser.Api/GamesService.cs
index 2d161ccfd..6c48b732f 100644
--- a/MediaBrowser.Api/GamesService.cs
+++ b/MediaBrowser.Api/GamesService.cs
@@ -12,6 +12,7 @@ using System.Linq;
using System.Threading.Tasks;
using MediaBrowser.Model.Querying;
using MediaBrowser.Model.Services;
+using MediaBrowser.Model.Extensions;
namespace MediaBrowser.Api
{
@@ -27,21 +28,7 @@ namespace MediaBrowser.Api
/// Class GetGameSystemSummaries
/// </summary>
[Route("/Games/SystemSummaries", "GET", Summary = "Finds games similar to a given game.")]
- public class GetGameSystemSummaries : IReturn<List<GameSystemSummary>>
- {
- /// <summary>
- /// Gets or sets the user id.
- /// </summary>
- /// <value>The user id.</value>
- [ApiMember(Name = "UserId", Description = "Optional. Filter by user id", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
- public string UserId { get; set; }
- }
-
- /// <summary>
- /// Class GetGameSystemSummaries
- /// </summary>
- [Route("/Games/PlayerIndex", "GET", Summary = "Gets an index of players (1-x) and the number of games listed under each")]
- public class GetPlayerIndex : IReturn<List<ItemIndex>>
+ public class GetGameSystemSummaries : IReturn<GameSystemSummary[]>
{
/// <summary>
/// Gets or sets the user id.
@@ -116,47 +103,17 @@ namespace MediaBrowser.Api
EnableImages = false
}
};
- var gameSystems = _libraryManager.GetItemList(query)
- .Cast<GameSystem>()
- .ToList();
- var result = gameSystems
+ var result = _libraryManager.GetItemList(query)
+ .Cast<GameSystem>()
.Select(i => GetSummary(i, user))
- .ToList();
+ .ToArray();
return ToOptimizedSerializedResultUsingCache(result);
}
private static readonly CultureInfo UsCulture = new CultureInfo("en-US");
- public object Get(GetPlayerIndex request)
- {
- var user = request.UserId == null ? null : _userManager.GetUserById(request.UserId);
- var query = new InternalItemsQuery(user)
- {
- IncludeItemTypes = new[] { typeof(Game).Name },
- DtoOptions = new DtoOptions(false)
- {
- EnableImages = false
- }
- };
- var games = _libraryManager.GetItemList(query)
- .Cast<Game>()
- .ToList();
-
- var lookup = games
- .ToLookup(i => i.PlayersSupported ?? -1)
- .OrderBy(i => i.Key)
- .Select(i => new ItemIndex
- {
- ItemCount = i.Count(),
- Name = i.Key == -1 ? string.Empty : i.Key.ToString(UsCulture)
- })
- .ToList();
-
- return ToOptimizedSerializedResultUsingCache(lookup);
- }
-
/// <summary>
/// Gets the summary.
/// </summary>
@@ -171,7 +128,7 @@ namespace MediaBrowser.Api
DisplayName = system.Name
};
- var items = user == null ?
+ var items = user == null ?
system.GetRecursiveChildren(i => i is Game) :
system.GetRecursiveChildren(user, new InternalItemsQuery(user)
{
@@ -182,15 +139,15 @@ namespace MediaBrowser.Api
}
});
- var games = items.Cast<Game>().ToList();
+ var games = items.Cast<Game>().ToArray();
summary.ClientInstalledGameCount = games.Count(i => i.IsPlaceHolder);
- summary.GameCount = games.Count;
+ summary.GameCount = games.Length;
summary.GameFileExtensions = games.Where(i => !i.IsPlaceHolder).Select(i => Path.GetExtension(i.Path))
.Distinct(StringComparer.OrdinalIgnoreCase)
- .ToList();
+ .ToArray();
return summary;
}
@@ -200,14 +157,14 @@ namespace MediaBrowser.Api
/// </summary>
/// <param name="request">The request.</param>
/// <returns>System.Object.</returns>
- public async Task<object> Get(GetSimilarGames request)
+ public object Get(GetSimilarGames request)
{
- var result = await GetSimilarItemsResult(request).ConfigureAwait(false);
+ var result = GetSimilarItemsResult(request);
return ToOptimizedSerializedResultUsingCache(result);
}
- private async Task<QueryResult<BaseItemDto>> GetSimilarItemsResult(BaseGetSimilarItemsFromItem request)
+ private QueryResult<BaseItemDto> GetSimilarItemsResult(BaseGetSimilarItemsFromItem request)
{
var user = !string.IsNullOrWhiteSpace(request.UserId) ? _userManager.GetUserById(request.UserId) : null;
@@ -227,11 +184,13 @@ namespace MediaBrowser.Api
SimilarTo = item,
DtoOptions = dtoOptions
- }).ToList();
+ });
+
+ var returnList = _dtoService.GetBaseItemDtos(itemsResult, dtoOptions, user);
var result = new QueryResult<BaseItemDto>
{
- Items = (await _dtoService.GetBaseItemDtos(itemsResult, dtoOptions, user).ConfigureAwait(false)).ToArray(),
+ Items = returnList,
TotalRecordCount = itemsResult.Count
};
diff --git a/MediaBrowser.Api/IHasItemFields.cs b/MediaBrowser.Api/IHasItemFields.cs
index 36303c889..0b3919985 100644
--- a/MediaBrowser.Api/IHasItemFields.cs
+++ b/MediaBrowser.Api/IHasItemFields.cs
@@ -27,7 +27,7 @@ namespace MediaBrowser.Api
/// </summary>
/// <param name="request">The request.</param>
/// <returns>IEnumerable{ItemFields}.</returns>
- public static IEnumerable<ItemFields> GetItemFields(this IHasItemFields request)
+ public static ItemFields[] GetItemFields(this IHasItemFields request)
{
var val = request.Fields;
@@ -46,7 +46,7 @@ namespace MediaBrowser.Api
}
return null;
- }).Where(i => i.HasValue).Select(i => i.Value);
+ }).Where(i => i.HasValue).Select(i => i.Value).ToArray();
}
}
}
diff --git a/MediaBrowser.Api/Images/ImageService.cs b/MediaBrowser.Api/Images/ImageService.cs
index f6c97e091..69d4a4ab4 100644
--- a/MediaBrowser.Api/Images/ImageService.cs
+++ b/MediaBrowser.Api/Images/ImageService.cs
@@ -279,13 +279,16 @@ namespace MediaBrowser.Api.Images
var itemImages = item.ImageInfos;
- foreach (var image in itemImages.Where(i => !item.AllowsMultipleImages(i.Type)))
+ foreach (var image in itemImages)
{
- var info = GetImageInfo(item, image, null);
-
- if (info != null)
+ if (!item.AllowsMultipleImages(image.Type))
{
- list.Add(info);
+ var info = GetImageInfo(item, image, null);
+
+ if (info != null)
+ {
+ list.Add(info);
+ }
}
}
@@ -312,7 +315,7 @@ namespace MediaBrowser.Api.Images
return list;
}
- private ImageInfo GetImageInfo(IHasImages item, ItemImageInfo info, int? imageIndex)
+ private ImageInfo GetImageInfo(IHasMetadata item, ItemImageInfo info, int? imageIndex)
{
try
{
@@ -507,7 +510,7 @@ namespace MediaBrowser.Api.Images
/// <param name="currentIndex">Index of the current.</param>
/// <param name="newIndex">The new index.</param>
/// <returns>Task.</returns>
- private Task UpdateItemIndex(IHasImages item, ImageType type, int currentIndex, int newIndex)
+ private Task UpdateItemIndex(IHasMetadata item, ImageType type, int currentIndex, int newIndex)
{
return item.SwapImages(type, currentIndex, newIndex);
}
@@ -520,7 +523,7 @@ namespace MediaBrowser.Api.Images
/// <param name="isHeadRequest">if set to <c>true</c> [is head request].</param>
/// <returns>System.Object.</returns>
/// <exception cref="ResourceNotFoundException"></exception>
- public Task<object> GetImage(ImageRequest request, IHasImages item, bool isHeadRequest)
+ public Task<object> GetImage(ImageRequest request, IHasMetadata item, bool isHeadRequest)
{
if (request.PercentPlayed.HasValue)
{
@@ -553,20 +556,7 @@ namespace MediaBrowser.Api.Images
throw new ResourceNotFoundException(string.Format("{0} does not have an image of type {1}", item.Name, request.Type));
}
- var supportedImageEnhancers = request.EnableImageEnhancers ? _imageProcessor.ImageEnhancers.Where(i =>
- {
- try
- {
- return i.Supports(item, request.Type);
- }
- catch (Exception ex)
- {
- Logger.ErrorException("Error in image enhancer: {0}", ex, i.GetType().Name);
-
- return false;
- }
-
- }).ToList() : new List<IImageEnhancer>();
+ var supportedImageEnhancers = request.EnableImageEnhancers ? _imageProcessor.GetSupportedEnhancers(item, request.Type) : new List<IImageEnhancer>();
var cropwhitespace = request.Type == ImageType.Logo ||
request.Type == ImageType.Art
@@ -603,7 +593,7 @@ namespace MediaBrowser.Api.Images
isHeadRequest);
}
- private async Task<object> GetImageResult(IHasImages item,
+ private async Task<object> GetImageResult(IHasMetadata item,
ImageRequest request,
ItemImageInfo image,
bool cropwhitespace,
@@ -649,9 +639,7 @@ namespace MediaBrowser.Api.Images
IsHeadRequest = isHeadRequest,
Path = imageResult.Item1,
- // Sometimes imagemagick keeps a hold on the file briefly even after it's done writing to it.
- // I'd rather do this than add a delay after saving the file
- FileShare = FileShareMode.ReadWrite
+ FileShare = FileShareMode.Read
}).ConfigureAwait(false);
}
@@ -749,7 +737,7 @@ namespace MediaBrowser.Api.Images
/// <param name="request">The request.</param>
/// <param name="item">The item.</param>
/// <returns>System.String.</returns>
- private ItemImageInfo GetImageInfo(ImageRequest request, IHasImages item)
+ private ItemImageInfo GetImageInfo(ImageRequest request, IHasMetadata item)
{
var index = request.Index ?? 0;
diff --git a/MediaBrowser.Api/Images/RemoteImageService.cs b/MediaBrowser.Api/Images/RemoteImageService.cs
index e4f3fd3d7..3512a526b 100644
--- a/MediaBrowser.Api/Images/RemoteImageService.cs
+++ b/MediaBrowser.Api/Images/RemoteImageService.cs
@@ -150,7 +150,7 @@ namespace MediaBrowser.Api.Images
}, CancellationToken.None).ConfigureAwait(false);
- var imagesList = images.ToList();
+ var imagesList = images.ToArray();
var allProviders = _providerManager.GetRemoteImageProviderInfo(item);
@@ -161,22 +161,22 @@ namespace MediaBrowser.Api.Images
var result = new RemoteImageResult
{
- TotalRecordCount = imagesList.Count,
+ TotalRecordCount = imagesList.Length,
Providers = allProviders.Select(i => i.Name)
.Distinct(StringComparer.OrdinalIgnoreCase)
- .ToList()
+ .ToArray()
};
if (request.StartIndex.HasValue)
{
imagesList = imagesList.Skip(request.StartIndex.Value)
- .ToList();
+ .ToArray();
}
if (request.Limit.HasValue)
{
imagesList = imagesList.Take(request.Limit.Value)
- .ToList();
+ .ToArray();
}
result.Images = imagesList;
diff --git a/MediaBrowser.Api/ItemUpdateService.cs b/MediaBrowser.Api/ItemUpdateService.cs
index 5325df325..9e83cf680 100644
--- a/MediaBrowser.Api/ItemUpdateService.cs
+++ b/MediaBrowser.Api/ItemUpdateService.cs
@@ -64,10 +64,10 @@ namespace MediaBrowser.Api
var info = new MetadataEditorInfo
{
- ParentalRatingOptions = _localizationManager.GetParentalRatings().ToList(),
- ExternalIdInfos = _providerManager.GetExternalIdInfos(item).ToList(),
- Countries = _localizationManager.GetCountries().ToList(),
- Cultures = _localizationManager.GetCultures().ToList()
+ ParentalRatingOptions = _localizationManager.GetParentalRatings(),
+ ExternalIdInfos = _providerManager.GetExternalIdInfos(item).ToArray(),
+ Countries = _localizationManager.GetCountries(),
+ Cultures = _localizationManager.GetCultures()
};
if (!item.IsVirtualItem && !(item is ICollectionFolder) && !(item is UserView) && !(item is AggregateFolder) && !(item is LiveTvChannel) && !(item is IItemByName) &&
@@ -78,14 +78,14 @@ namespace MediaBrowser.Api
if (string.IsNullOrWhiteSpace(inheritedContentType) || !string.IsNullOrWhiteSpace(configuredContentType))
{
- info.ContentTypeOptions = GetContentTypeOptions(true);
+ info.ContentTypeOptions = GetContentTypeOptions(true).ToArray();
info.ContentType = configuredContentType;
if (string.IsNullOrWhiteSpace(inheritedContentType) || string.Equals(inheritedContentType, CollectionType.TvShows, StringComparison.OrdinalIgnoreCase))
{
info.ContentTypeOptions = info.ContentTypeOptions
.Where(i => string.IsNullOrWhiteSpace(i.Value) || string.Equals(i.Value, CollectionType.TvShows, StringComparison.OrdinalIgnoreCase))
- .ToList();
+ .ToArray();
}
}
}
@@ -209,7 +209,7 @@ namespace MediaBrowser.Api
// Do this first so that metadata savers can pull the updates from the database.
if (request.People != null)
{
- await _libraryManager.UpdatePeople(item, request.People.Select(x => new PersonInfo { Name = x.Name, Role = x.Role, Type = x.Type }).ToList());
+ _libraryManager.UpdatePeople(item, request.People.Select(x => new PersonInfo { Name = x.Name, Role = x.Role, Type = x.Type }).ToList());
}
UpdateItem(request, item);
@@ -242,7 +242,6 @@ namespace MediaBrowser.Api
item.CriticRating = request.CriticRating;
- item.DisplayMediaType = request.DisplayMediaType;
item.CommunityRating = request.CommunityRating;
item.HomePageUrl = request.HomePageUrl;
item.IndexNumber = request.IndexNumber;
@@ -270,7 +269,7 @@ namespace MediaBrowser.Api
if (request.Studios != null)
{
- item.Studios = request.Studios.Select(x => x.Name).ToList();
+ item.Studios = request.Studios.Select(x => x.Name).ToArray();
}
if (request.DateCreated.HasValue)
@@ -286,7 +285,7 @@ namespace MediaBrowser.Api
if (request.ProductionLocations != null)
{
- item.ProductionLocations = request.ProductionLocations.ToList();
+ item.ProductionLocations = request.ProductionLocations;
}
item.PreferredMetadataCountryCode = request.PreferredMetadataCountryCode;
@@ -333,13 +332,6 @@ namespace MediaBrowser.Api
video.Video3DFormat = request.Video3DFormat;
}
- var game = item as Game;
-
- if (game != null)
- {
- game.PlayersSupported = request.Players;
- }
-
if (request.AlbumArtists != null)
{
var hasAlbumArtists = item as IHasAlbumArtist;
@@ -348,7 +340,7 @@ namespace MediaBrowser.Api
hasAlbumArtists.AlbumArtists = request
.AlbumArtists
.Select(i => i.Name)
- .ToList();
+ .ToArray();
}
}
@@ -360,7 +352,7 @@ namespace MediaBrowser.Api
hasArtists.Artists = request
.ArtistItems
.Select(i => i.Name)
- .ToList();
+ .ToArray();
}
}
@@ -380,8 +372,12 @@ namespace MediaBrowser.Api
if (series != null)
{
series.Status = GetSeriesStatus(request);
- series.AirDays = request.AirDays;
- series.AirTime = request.AirTime;
+
+ if (request.AirDays != null)
+ {
+ series.AirDays = request.AirDays;
+ series.AirTime = request.AirTime;
+ }
}
}
diff --git a/MediaBrowser.Api/Library/LibraryService.cs b/MediaBrowser.Api/Library/LibraryService.cs
index 3bb119cba..6152ea20b 100644
--- a/MediaBrowser.Api/Library/LibraryService.cs
+++ b/MediaBrowser.Api/Library/LibraryService.cs
@@ -29,6 +29,7 @@ using MediaBrowser.Model.Globalization;
using MediaBrowser.Model.Services;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.Progress;
+using MediaBrowser.Model.Extensions;
namespace MediaBrowser.Api.Library
{
@@ -226,7 +227,7 @@ namespace MediaBrowser.Api.Library
[Route("/Library/MediaFolders", "GET", Summary = "Gets all user media folders.")]
[Authenticated]
- public class GetMediaFolders : IReturn<ItemsResult>
+ public class GetMediaFolders : IReturn<QueryResult<BaseItemDto>>
{
[ApiMember(Name = "IsHidden", Description = "Optional. Filter by folders that are marked hidden, or not.", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "GET")]
public bool? IsHidden { get; set; }
@@ -399,7 +400,7 @@ namespace MediaBrowser.Api.Library
});
}
- return new ItemsResult();
+ return new QueryResult<BaseItemDto>();
}
public object Get(GetMediaFolders request)
@@ -415,7 +416,7 @@ namespace MediaBrowser.Api.Library
var dtoOptions = GetDtoOptions(_authContext, request);
- var result = new ItemsResult
+ var result = new QueryResult<BaseItemDto>
{
TotalRecordCount = items.Count,
@@ -460,22 +461,22 @@ namespace MediaBrowser.Api.Library
EnableImages = false
}
- }).ToArray();
+ });
if (!string.IsNullOrWhiteSpace(request.ImdbId))
{
- movies = movies.Where(i => string.Equals(request.ImdbId, i.GetProviderId(MetadataProviders.Imdb), StringComparison.OrdinalIgnoreCase)).ToArray();
+ movies = movies.Where(i => string.Equals(request.ImdbId, i.GetProviderId(MetadataProviders.Imdb), StringComparison.OrdinalIgnoreCase)).ToList();
}
else if (!string.IsNullOrWhiteSpace(request.TmdbId))
{
- movies = movies.Where(i => string.Equals(request.TmdbId, i.GetProviderId(MetadataProviders.Tmdb), StringComparison.OrdinalIgnoreCase)).ToArray();
+ movies = movies.Where(i => string.Equals(request.TmdbId, i.GetProviderId(MetadataProviders.Tmdb), StringComparison.OrdinalIgnoreCase)).ToList();
}
else
{
- movies = new BaseItem[] { };
+ movies = new List<BaseItem>();
}
- if (movies.Length > 0)
+ if (movies.Count > 0)
{
foreach (var item in movies)
{
@@ -517,25 +518,34 @@ namespace MediaBrowser.Api.Library
LogDownload(item, user, auth);
}
+ var path = item.Path;
+
+ // Quotes are valid in linux. They'll possibly cause issues here
+ var filename = (Path.GetFileName(path) ?? string.Empty).Replace("\"", string.Empty);
+ if (!string.IsNullOrWhiteSpace(filename))
+ {
+ headers["Content-Disposition"] = "attachment; filename=\"" + filename + "\"";
+ }
+
return ResultFactory.GetStaticFileResult(Request, new StaticFileResultOptions
{
- Path = item.Path,
+ Path = path,
ResponseHeaders = headers
});
}
- private async void LogDownload(BaseItem item, User user, AuthorizationInfo auth)
+ private void LogDownload(BaseItem item, User user, AuthorizationInfo auth)
{
try
{
- await _activityManager.Create(new ActivityLogEntry
+ _activityManager.Create(new ActivityLogEntry
{
Name = string.Format(_localization.GetLocalizedString("UserDownloadingItemWithValues"), user.Name, item.Name),
Type = "UserDownloadingContent",
ShortOverview = string.Format(_localization.GetLocalizedString("AppDeviceValues"), auth.Client, auth.Device),
UserId = auth.UserId
- }).ConfigureAwait(false);
+ });
}
catch
{
@@ -614,7 +624,7 @@ namespace MediaBrowser.Api.Library
parent = parent.GetParent();
}
- return baseItemDtos.ToList();
+ return baseItemDtos;
}
private BaseItem TranslateParentItem(BaseItem item, User user)
@@ -732,7 +742,8 @@ namespace MediaBrowser.Api.Library
{
DeleteFileLocation = true
});
- }).ToArray();
+
+ }).ToArray(ids.Length);
Task.WaitAll(tasks);
}
@@ -758,7 +769,7 @@ namespace MediaBrowser.Api.Library
{
var reviews = _itemRepo.GetCriticReviews(new Guid(request.Id));
- var reviewsArray = reviews.ToArray();
+ var reviewsArray = reviews.ToArray(reviews.Count);
var result = new QueryResult<ItemReview>
{
@@ -833,7 +844,7 @@ namespace MediaBrowser.Api.Library
throw new ResourceNotFoundException("Item not found.");
}
- while (item.ThemeSongIds.Count == 0 && request.InheritFromParent && item.GetParent() != null)
+ while (item.ThemeSongIds.Length == 0 && request.InheritFromParent && item.GetParent() != null)
{
item = item.GetParent();
}
@@ -882,7 +893,7 @@ namespace MediaBrowser.Api.Library
throw new ResourceNotFoundException("Item not found.");
}
- while (item.ThemeVideoIds.Count == 0 && request.InheritFromParent && item.GetParent() != null)
+ while (item.ThemeVideoIds.Length == 0 && request.InheritFromParent && item.GetParent() != null)
{
item = item.GetParent();
}
@@ -913,7 +924,7 @@ namespace MediaBrowser.Api.Library
: request.IncludeItemTypes.Split(',');
var user = !string.IsNullOrWhiteSpace(request.UserId) ? _userManager.GetUserById(request.UserId) : null;
-
+
var query = new InternalItemsQuery(user)
{
IncludeItemTypes = includeTypes,
diff --git a/MediaBrowser.Api/LiveTv/LiveTvService.cs b/MediaBrowser.Api/LiveTv/LiveTvService.cs
index 837a0f6a6..36bcee913 100644
--- a/MediaBrowser.Api/LiveTv/LiveTvService.cs
+++ b/MediaBrowser.Api/LiveTv/LiveTvService.cs
@@ -15,14 +15,14 @@ using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
+using MediaBrowser.Api.UserLibrary;
using MediaBrowser.Model.IO;
-using MediaBrowser.Api.Playback.Progressive;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities.TV;
-using MediaBrowser.Controller.IO;
using MediaBrowser.Model.Services;
using MediaBrowser.Model.System;
+using MediaBrowser.Model.Extensions;
namespace MediaBrowser.Api.LiveTv
{
@@ -374,7 +374,7 @@ namespace MediaBrowser.Api.LiveTv
public string SortBy { get; set; }
[ApiMember(Name = "SortOrder", Description = "Sort Order - Ascending,Descending", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
- public SortOrder? SortOrder { get; set; }
+ public string SortOrder { get; set; }
[ApiMember(Name = "Genres", Description = "The genres to return guide information for.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET,POST")]
public string Genres { get; set; }
@@ -649,7 +649,7 @@ namespace MediaBrowser.Api.LiveTv
{
public List<TunerChannelMapping> TunerChannels { get; set; }
public List<NameIdPair> ProviderChannels { get; set; }
- public List<NameValuePair> Mappings { get; set; }
+ public NameValuePair[] Mappings { get; set; }
public string ProviderName { get; set; }
}
@@ -734,7 +734,7 @@ namespace MediaBrowser.Api.LiveTv
outputHeaders["Content-Type"] = Model.Net.MimeTypes.GetMimeType(path);
- return new ProgressiveFileCopier(_fileSystem, path, outputHeaders, null, Logger, _environment, CancellationToken.None)
+ return new ProgressiveFileCopier(_fileSystem, path, outputHeaders, Logger, _environment, CancellationToken.None)
{
AllowEndOfFile = false
};
@@ -753,7 +753,7 @@ namespace MediaBrowser.Api.LiveTv
outputHeaders["Content-Type"] = Model.Net.MimeTypes.GetMimeType("file." + request.Container);
- return new ProgressiveFileCopier(directStreamProvider, outputHeaders, null, Logger, _environment, CancellationToken.None)
+ return new ProgressiveFileCopier(directStreamProvider, outputHeaders, Logger, _environment, CancellationToken.None)
{
AllowEndOfFile = false
};
@@ -790,7 +790,7 @@ namespace MediaBrowser.Api.LiveTv
var providerChannels = await _liveTvManager.GetChannelsFromListingsProviderData(request.ProviderId, CancellationToken.None)
.ConfigureAwait(false);
- var mappings = listingsProviderInfo.ChannelMappings.ToList();
+ var mappings = listingsProviderInfo.ChannelMappings;
var result = new ChannelMappingOptions
{
@@ -853,6 +853,8 @@ namespace MediaBrowser.Api.LiveTv
public async Task<object> Post(AddTunerHost request)
{
+ request.EnableNewHdhrChannelIds = true;
+
var result = await _liveTvManager.SaveTunerHost(request).ConfigureAwait(false);
return ToOptimizedResult(result);
}
@@ -861,7 +863,7 @@ namespace MediaBrowser.Api.LiveTv
{
var config = GetConfiguration();
- config.TunerHosts = config.TunerHosts.Where(i => !string.Equals(request.Id, i.Id, StringComparison.OrdinalIgnoreCase)).ToList();
+ config.TunerHosts = config.TunerHosts.Where(i => !string.Equals(request.Id, i.Id, StringComparison.OrdinalIgnoreCase)).ToArray();
_config.SaveConfiguration("livetv", config);
}
@@ -921,7 +923,7 @@ namespace MediaBrowser.Api.LiveTv
options.AddCurrentProgram = request.AddCurrentProgram;
- var returnArray = (await _dtoService.GetBaseItemDtos(channelResult.Items, options, user).ConfigureAwait(false)).ToArray();
+ var returnArray = _dtoService.GetBaseItemDtos(channelResult.Items, options, user);
var result = new QueryResult<BaseItemDto>
{
@@ -934,10 +936,13 @@ namespace MediaBrowser.Api.LiveTv
private void RemoveFields(DtoOptions options)
{
- options.Fields.Remove(ItemFields.CanDelete);
- options.Fields.Remove(ItemFields.CanDownload);
- options.Fields.Remove(ItemFields.DisplayPreferencesId);
- options.Fields.Remove(ItemFields.Etag);
+ var fields = options.Fields.ToList();
+
+ fields.Remove(ItemFields.CanDelete);
+ fields.Remove(ItemFields.CanDownload);
+ fields.Remove(ItemFields.DisplayPreferencesId);
+ fields.Remove(ItemFields.Etag);
+ options.Fields = fields.ToArray(fields.Count);
}
public object Get(GetChannel request)
@@ -962,7 +967,7 @@ namespace MediaBrowser.Api.LiveTv
{
var query = new ProgramQuery
{
- ChannelIds = (request.ChannelIds ?? string.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).ToArray(),
+ ChannelIds = ApiEntryPoint.Split(request.ChannelIds, ',', true),
UserId = request.UserId,
HasAired = request.HasAired,
EnableTotalRecordCount = request.EnableTotalRecordCount
@@ -990,8 +995,7 @@ namespace MediaBrowser.Api.LiveTv
query.StartIndex = request.StartIndex;
query.Limit = request.Limit;
- query.SortBy = (request.SortBy ?? String.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
- query.SortOrder = request.SortOrder;
+ query.OrderBy = BaseItemsRequest.GetOrderBy(request.SortBy, request.SortOrder);
query.IsNews = request.IsNews;
query.IsMovie = request.IsMovie;
query.IsSeries = request.IsSeries;
@@ -1070,12 +1074,12 @@ namespace MediaBrowser.Api.LiveTv
return ToOptimizedResult(result);
}
- public async Task<object> Get(GetRecordingSeries request)
+ public object Get(GetRecordingSeries request)
{
var options = GetDtoOptions(_authContext, request);
options.DeviceId = _authContext.GetAuthorizationInfo(Request).DeviceId;
- var result = await _liveTvManager.GetRecordingSeries(new RecordingQuery
+ var result = _liveTvManager.GetRecordingSeries(new RecordingQuery
{
ChannelId = request.ChannelId,
UserId = request.UserId,
@@ -1087,7 +1091,7 @@ namespace MediaBrowser.Api.LiveTv
IsInProgress = request.IsInProgress,
EnableTotalRecordCount = request.EnableTotalRecordCount
- }, options, CancellationToken.None).ConfigureAwait(false);
+ }, options, CancellationToken.None);
return ToOptimizedResult(result);
}
diff --git a/MediaBrowser.Api/Playback/Progressive/ProgressiveStreamWriter.cs b/MediaBrowser.Api/LiveTv/ProgressiveFileCopier.cs
index d84d889fa..9ce109fc4 100644
--- a/MediaBrowser.Api/Playback/Progressive/ProgressiveStreamWriter.cs
+++ b/MediaBrowser.Api/LiveTv/ProgressiveFileCopier.cs
@@ -1,23 +1,19 @@
-using MediaBrowser.Model.Logging;
-using System;
+using System;
+using System.Collections.Generic;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
-using MediaBrowser.Model.IO;
-using MediaBrowser.Controller.Net;
-using System.Collections.Generic;
-
-using MediaBrowser.Controller.IO;
using MediaBrowser.Controller.Library;
+using MediaBrowser.Model.IO;
+using MediaBrowser.Model.Logging;
using MediaBrowser.Model.Services;
using MediaBrowser.Model.System;
-namespace MediaBrowser.Api.Playback.Progressive
+namespace MediaBrowser.Api.LiveTv
{
public class ProgressiveFileCopier : IAsyncStreamWriter, IHasHeaders
{
private readonly IFileSystem _fileSystem;
- private readonly TranscodingJob _job;
private readonly ILogger _logger;
private readonly string _path;
private readonly CancellationToken _cancellationToken;
@@ -32,22 +28,20 @@ namespace MediaBrowser.Api.Playback.Progressive
private readonly IDirectStreamProvider _directStreamProvider;
private readonly IEnvironmentInfo _environment;
- public ProgressiveFileCopier(IFileSystem fileSystem, string path, Dictionary<string, string> outputHeaders, TranscodingJob job, ILogger logger, IEnvironmentInfo environment, CancellationToken cancellationToken)
+ public ProgressiveFileCopier(IFileSystem fileSystem, string path, Dictionary<string, string> outputHeaders, ILogger logger, IEnvironmentInfo environment, CancellationToken cancellationToken)
{
_fileSystem = fileSystem;
_path = path;
_outputHeaders = outputHeaders;
- _job = job;
_logger = logger;
_cancellationToken = cancellationToken;
_environment = environment;
}
- public ProgressiveFileCopier(IDirectStreamProvider directStreamProvider, Dictionary<string, string> outputHeaders, TranscodingJob job, ILogger logger, IEnvironmentInfo environment, CancellationToken cancellationToken)
+ public ProgressiveFileCopier(IDirectStreamProvider directStreamProvider, Dictionary<string, string> outputHeaders, ILogger logger, IEnvironmentInfo environment, CancellationToken cancellationToken)
{
_directStreamProvider = directStreamProvider;
_outputHeaders = outputHeaders;
- _job = job;
_logger = logger;
_cancellationToken = cancellationToken;
_environment = environment;
@@ -77,61 +71,48 @@ namespace MediaBrowser.Api.Playback.Progressive
{
cancellationToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, _cancellationToken).Token;
- try
+ if (_directStreamProvider != null)
{
- if (_directStreamProvider != null)
- {
- await _directStreamProvider.CopyToAsync(outputStream, cancellationToken).ConfigureAwait(false);
- return;
- }
+ await _directStreamProvider.CopyToAsync(outputStream, cancellationToken).ConfigureAwait(false);
+ return;
+ }
- var eofCount = 0;
+ var eofCount = 0;
- // use non-async filestream along with read due to https://github.com/dotnet/corefx/issues/6039
- var allowAsyncFileRead = _environment.OperatingSystem != OperatingSystem.Windows;
+ // use non-async filestream along with read due to https://github.com/dotnet/corefx/issues/6039
+ var allowAsyncFileRead = _environment.OperatingSystem != MediaBrowser.Model.System.OperatingSystem.Windows;
- using (var inputStream = GetInputStream(allowAsyncFileRead))
+ using (var inputStream = GetInputStream(allowAsyncFileRead))
+ {
+ if (StartPosition > 0)
{
- if (StartPosition > 0)
+ inputStream.Position = StartPosition;
+ }
+
+ while (eofCount < 20 || !AllowEndOfFile)
+ {
+ int bytesRead;
+ if (allowAsyncFileRead)
+ {
+ bytesRead = await CopyToInternalAsync(inputStream, outputStream, cancellationToken).ConfigureAwait(false);
+ }
+ else
{
- inputStream.Position = StartPosition;
+ bytesRead = await CopyToInternalAsyncWithSyncRead(inputStream, outputStream, cancellationToken).ConfigureAwait(false);
}
- while (eofCount < 20 || !AllowEndOfFile)
+ //var position = fs.Position;
+ //_logger.Debug("Streamed {0} bytes to position {1} from file {2}", bytesRead, position, path);
+
+ if (bytesRead == 0)
{
- int bytesRead;
- if (allowAsyncFileRead)
- {
- bytesRead = await CopyToInternalAsync(inputStream, outputStream, cancellationToken).ConfigureAwait(false);
- }
- else
- {
- bytesRead = await CopyToInternalAsyncWithSyncRead(inputStream, outputStream, cancellationToken).ConfigureAwait(false);
- }
-
- //var position = fs.Position;
- //_logger.Debug("Streamed {0} bytes to position {1} from file {2}", bytesRead, position, path);
-
- if (bytesRead == 0)
- {
- if (_job == null || _job.HasExited)
- {
- eofCount++;
- }
- await Task.Delay(100, cancellationToken).ConfigureAwait(false);
- }
- else
- {
- eofCount = 0;
- }
+ eofCount++;
+ await Task.Delay(100, cancellationToken).ConfigureAwait(false);
+ }
+ else
+ {
+ eofCount = 0;
}
- }
- }
- finally
- {
- if (_job != null)
- {
- ApiEntryPoint.Instance.OnTranscodeEndRequest(_job);
}
}
}
@@ -152,11 +133,6 @@ namespace MediaBrowser.Api.Playback.Progressive
_bytesWritten += bytesRead;
totalBytesRead += bytesRead;
-
- if (_job != null)
- {
- _job.BytesDownloaded = Math.Max(_job.BytesDownloaded ?? _bytesWritten, _bytesWritten);
- }
}
}
@@ -179,11 +155,6 @@ namespace MediaBrowser.Api.Playback.Progressive
_bytesWritten += bytesRead;
totalBytesRead += bytesRead;
-
- if (_job != null)
- {
- _job.BytesDownloaded = Math.Max(_job.BytesDownloaded ?? _bytesWritten, _bytesWritten);
- }
}
}
diff --git a/MediaBrowser.Api/LocalizationService.cs b/MediaBrowser.Api/LocalizationService.cs
index 90b963149..56d32fc37 100644
--- a/MediaBrowser.Api/LocalizationService.cs
+++ b/MediaBrowser.Api/LocalizationService.cs
@@ -2,7 +2,6 @@
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Globalization;
using System.Collections.Generic;
-using System.Linq;
using MediaBrowser.Model.Services;
namespace MediaBrowser.Api
@@ -11,7 +10,7 @@ namespace MediaBrowser.Api
/// Class GetCultures
/// </summary>
[Route("/Localization/Cultures", "GET", Summary = "Gets known cultures")]
- public class GetCultures : IReturn<List<CultureDto>>
+ public class GetCultures : IReturn<CultureDto[]>
{
}
@@ -19,7 +18,7 @@ namespace MediaBrowser.Api
/// Class GetCountries
/// </summary>
[Route("/Localization/Countries", "GET", Summary = "Gets known countries")]
- public class GetCountries : IReturn<List<CountryInfo>>
+ public class GetCountries : IReturn<CountryInfo[]>
{
}
@@ -27,7 +26,7 @@ namespace MediaBrowser.Api
/// Class ParentalRatings
/// </summary>
[Route("/Localization/ParentalRatings", "GET", Summary = "Gets known parental ratings")]
- public class GetParentalRatings : IReturn<List<ParentalRating>>
+ public class GetParentalRatings : IReturn<ParentalRating[]>
{
}
@@ -35,7 +34,7 @@ namespace MediaBrowser.Api
/// Class ParentalRatings
/// </summary>
[Route("/Localization/Options", "GET", Summary = "Gets localization options")]
- public class GetLocalizationOptions : IReturn<List<LocalizatonOption>>
+ public class GetLocalizationOptions : IReturn<LocalizatonOption[]>
{
}
@@ -66,14 +65,14 @@ namespace MediaBrowser.Api
/// <returns>System.Object.</returns>
public object Get(GetParentalRatings request)
{
- var result = _localization.GetParentalRatings().ToList();
+ var result = _localization.GetParentalRatings();
return ToOptimizedResult(result);
}
public object Get(GetLocalizationOptions request)
{
- var result = _localization.GetLocalizationOptions().ToList();
+ var result = _localization.GetLocalizationOptions();
return ToOptimizedResult(result);
}
@@ -85,7 +84,7 @@ namespace MediaBrowser.Api
/// <returns>System.Object.</returns>
public object Get(GetCountries request)
{
- var result = _localization.GetCountries().ToList();
+ var result = _localization.GetCountries();
return ToOptimizedResult(result);
}
@@ -97,7 +96,7 @@ namespace MediaBrowser.Api
/// <returns>System.Object.</returns>
public object Get(GetCultures request)
{
- var result = _localization.GetCultures().ToList();
+ var result = _localization.GetCultures();
return ToOptimizedResult(result);
}
diff --git a/MediaBrowser.Api/MediaBrowser.Api.csproj b/MediaBrowser.Api/MediaBrowser.Api.csproj
index 88889e5e7..602a697bf 100644
--- a/MediaBrowser.Api/MediaBrowser.Api.csproj
+++ b/MediaBrowser.Api/MediaBrowser.Api.csproj
@@ -48,9 +48,7 @@
<Compile Include="Dlna\DlnaService.cs" />
<Compile Include="FilterService.cs" />
<Compile Include="IHasDtoOptions.cs" />
- <Compile Include="Playback\MediaInfoService.cs" />
- <Compile Include="Playback\TranscodingThrottler.cs" />
- <Compile Include="Playback\UniversalAudioService.cs" />
+ <Compile Include="LiveTv\ProgressiveFileCopier.cs" />
<Compile Include="PlaylistService.cs" />
<Compile Include="Reports\Activities\ReportActivitiesBuilder.cs" />
<Compile Include="Reports\Common\HeaderActivitiesMetadata.cs" />
@@ -99,20 +97,7 @@
<Compile Include="Movies\MoviesService.cs" />
<Compile Include="NewsService.cs" />
<Compile Include="NotificationsService.cs" />
- <Compile Include="PackageReviewService.cs" />
<Compile Include="PackageService.cs" />
- <Compile Include="Playback\Hls\BaseHlsService.cs" />
- <Compile Include="Playback\Hls\DynamicHlsService.cs" />
- <Compile Include="Playback\Hls\HlsSegmentService.cs" />
- <Compile Include="Playback\Hls\VideoHlsService.cs" />
- <Compile Include="Playback\Progressive\AudioService.cs" />
- <Compile Include="Playback\Progressive\BaseProgressiveStreamingService.cs" />
- <Compile Include="Playback\BaseStreamingService.cs" />
- <Compile Include="Playback\Progressive\ProgressiveStreamWriter.cs" />
- <Compile Include="Playback\StaticRemoteStreamWriter.cs" />
- <Compile Include="Playback\StreamRequest.cs" />
- <Compile Include="Playback\StreamState.cs" />
- <Compile Include="Playback\Progressive\VideoService.cs" />
<Compile Include="PluginService.cs" />
<Compile Include="Images\RemoteImageService.cs" />
<Compile Include="ScheduledTasks\ScheduledTaskService.cs" />
@@ -126,7 +111,6 @@
<Compile Include="System\ActivityLogWebSocketListener.cs" />
<Compile Include="System\SystemService.cs" />
<Compile Include="Movies\TrailersService.cs" />
- <Compile Include="TestService.cs" />
<Compile Include="TvShowsService.cs" />
<Compile Include="UserLibrary\ArtistsService.cs" />
<Compile Include="UserLibrary\BaseItemsByNameService.cs" />
@@ -136,7 +120,6 @@
<Compile Include="UserLibrary\ItemsService.cs" />
<Compile Include="UserLibrary\MusicGenresService.cs" />
<Compile Include="UserLibrary\PersonsService.cs" />
- <Compile Include="UserLibrary\PlaystateService.cs" />
<Compile Include="UserLibrary\StudiosService.cs" />
<Compile Include="UserLibrary\UserLibraryService.cs" />
<Compile Include="UserLibrary\UserViewsService.cs" />
diff --git a/MediaBrowser.Api/Movies/CollectionService.cs b/MediaBrowser.Api/Movies/CollectionService.cs
index 917a3bc0b..c63712f4c 100644
--- a/MediaBrowser.Api/Movies/CollectionService.cs
+++ b/MediaBrowser.Api/Movies/CollectionService.cs
@@ -4,7 +4,6 @@ using MediaBrowser.Controller.Net;
using MediaBrowser.Model.Collections;
using System;
using System.Collections.Generic;
-using System.Linq;
using System.Threading.Tasks;
using MediaBrowser.Model.Services;
@@ -71,8 +70,8 @@ namespace MediaBrowser.Api.Movies
IsLocked = request.IsLocked,
Name = request.Name,
ParentId = parentId,
- ItemIdList = (request.Ids ?? string.Empty).Split(',').Where(i => !string.IsNullOrWhiteSpace(i)).Select(i => new Guid(i)).ToList(),
- UserIds = new List<Guid> { new Guid(userId) }
+ ItemIdList = SplitValue(request.Ids, ','),
+ UserIds = new string[] { userId }
}).ConfigureAwait(false);
@@ -88,14 +87,14 @@ namespace MediaBrowser.Api.Movies
public void Post(AddToCollection request)
{
- var task = _collectionManager.AddToCollection(new Guid(request.Id), request.Ids.Split(',').Select(i => new Guid(i)));
+ var task = _collectionManager.AddToCollection(new Guid(request.Id), SplitValue(request.Ids, ','));
Task.WaitAll(task);
}
public void Delete(RemoveFromCollection request)
{
- var task = _collectionManager.RemoveFromCollection(new Guid(request.Id), request.Ids.Split(',').Select(i => new Guid(i)));
+ var task = _collectionManager.RemoveFromCollection(new Guid(request.Id), SplitValue(request.Ids, ','));
Task.WaitAll(task);
}
diff --git a/MediaBrowser.Api/Movies/MoviesService.cs b/MediaBrowser.Api/Movies/MoviesService.cs
index e20fa2cca..254c93b33 100644
--- a/MediaBrowser.Api/Movies/MoviesService.cs
+++ b/MediaBrowser.Api/Movies/MoviesService.cs
@@ -113,16 +113,16 @@ namespace MediaBrowser.Api.Movies
/// </summary>
/// <param name="request">The request.</param>
/// <returns>System.Object.</returns>
- public async Task<object> Get(GetSimilarMovies request)
+ public object Get(GetSimilarMovies request)
{
- var result = await GetSimilarItemsResult(request).ConfigureAwait(false);
+ var result = GetSimilarItemsResult(request);
return ToOptimizedSerializedResultUsingCache(result);
}
- public async Task<object> Get(GetSimilarTrailers request)
+ public object Get(GetSimilarTrailers request)
{
- var result = await GetSimilarItemsResult(request).ConfigureAwait(false);
+ var result = GetSimilarItemsResult(request);
return ToOptimizedSerializedResultUsingCache(result);
}
@@ -138,7 +138,7 @@ namespace MediaBrowser.Api.Movies
return ToOptimizedResult(result);
}
- private async Task<QueryResult<BaseItemDto>> GetSimilarItemsResult(BaseGetSimilarItemsFromItem request)
+ private QueryResult<BaseItemDto> GetSimilarItemsResult(BaseGetSimilarItemsFromItem request)
{
var user = !string.IsNullOrWhiteSpace(request.UserId) ? _userManager.GetUserById(request.UserId) : null;
@@ -158,17 +158,19 @@ namespace MediaBrowser.Api.Movies
var itemsResult = _libraryManager.GetItemList(new InternalItemsQuery(user)
{
Limit = request.Limit,
- IncludeItemTypes = itemTypes.ToArray(),
+ IncludeItemTypes = itemTypes.ToArray(itemTypes.Count),
IsMovie = true,
SimilarTo = item,
EnableGroupByMetadataKey = true,
DtoOptions = dtoOptions
- }).ToList();
+ });
+
+ var returnList = _dtoService.GetBaseItemDtos(itemsResult, dtoOptions, user);
var result = new QueryResult<BaseItemDto>
{
- Items = (await _dtoService.GetBaseItemDtos(itemsResult, dtoOptions, user).ConfigureAwait(false)).ToArray(),
+ Items = returnList,
TotalRecordCount = itemsResult.Count
};
@@ -191,8 +193,7 @@ namespace MediaBrowser.Api.Movies
//typeof(LiveTvProgram).Name
},
// IsMovie = true
- SortBy = new[] { ItemSortBy.DatePlayed, ItemSortBy.Random },
- SortOrder = SortOrder.Descending,
+ OrderBy = new[] { ItemSortBy.DatePlayed, ItemSortBy.Random }.Select(i => new Tuple<string, SortOrder>(i, SortOrder.Descending)).ToArray(),
Limit = 7,
ParentId = parentIdGuid,
Recursive = true,
@@ -200,7 +201,7 @@ namespace MediaBrowser.Api.Movies
DtoOptions = dtoOptions
};
- var recentlyPlayedMovies = _libraryManager.GetItemList(query).ToList();
+ var recentlyPlayedMovies = _libraryManager.GetItemList(query);
var itemTypes = new List<string> { typeof(Movie).Name };
if (_config.Configuration.EnableExternalContentInSuggestions)
@@ -211,19 +212,18 @@ namespace MediaBrowser.Api.Movies
var likedMovies = _libraryManager.GetItemList(new InternalItemsQuery(user)
{
- IncludeItemTypes = itemTypes.ToArray(),
+ IncludeItemTypes = itemTypes.ToArray(itemTypes.Count),
IsMovie = true,
- SortBy = new[] { ItemSortBy.Random },
- SortOrder = SortOrder.Descending,
+ OrderBy = new[] { ItemSortBy.Random }.Select(i => new Tuple<string, SortOrder>(i, SortOrder.Descending)).ToArray(),
Limit = 10,
IsFavoriteOrLiked = true,
- ExcludeItemIds = recentlyPlayedMovies.Select(i => i.Id.ToString("N")).ToArray(),
+ ExcludeItemIds = recentlyPlayedMovies.Select(i => i.Id.ToString("N")).ToArray(recentlyPlayedMovies.Count),
EnableGroupByMetadataKey = true,
ParentId = parentIdGuid,
Recursive = true,
DtoOptions = dtoOptions
- }).ToList();
+ });
var mostRecentMovies = recentlyPlayedMovies.Take(6).ToList();
// Get recently played directors
@@ -300,7 +300,7 @@ namespace MediaBrowser.Api.Movies
// Account for duplicates by imdb id, since the database doesn't support this yet
Limit = itemLimit + 2,
PersonTypes = new[] { PersonType.Director },
- IncludeItemTypes = itemTypes.ToArray(),
+ IncludeItemTypes = itemTypes.ToArray(itemTypes.Count),
IsMovie = true,
EnableGroupByMetadataKey = true,
DtoOptions = dtoOptions
@@ -311,12 +311,14 @@ namespace MediaBrowser.Api.Movies
if (items.Count > 0)
{
+ var returnItems = _dtoService.GetBaseItemDtos(items, dtoOptions, user);
+
yield return new RecommendationDto
{
BaselineItemName = name,
CategoryId = name.GetMD5().ToString("N"),
RecommendationType = type,
- Items = _dtoService.GetBaseItemDtos(items, dtoOptions, user).Result.ToArray()
+ Items = returnItems
};
}
}
@@ -338,7 +340,7 @@ namespace MediaBrowser.Api.Movies
Person = name,
// Account for duplicates by imdb id, since the database doesn't support this yet
Limit = itemLimit + 2,
- IncludeItemTypes = itemTypes.ToArray(),
+ IncludeItemTypes = itemTypes.ToArray(itemTypes.Count),
IsMovie = true,
EnableGroupByMetadataKey = true,
DtoOptions = dtoOptions
@@ -349,12 +351,14 @@ namespace MediaBrowser.Api.Movies
if (items.Count > 0)
{
+ var returnItems = _dtoService.GetBaseItemDtos(items, dtoOptions, user);
+
yield return new RecommendationDto
{
BaselineItemName = name,
CategoryId = name.GetMD5().ToString("N"),
RecommendationType = type,
- Items = _dtoService.GetBaseItemDtos(items, dtoOptions, user).Result.ToArray()
+ Items = returnItems
};
}
}
@@ -374,28 +378,30 @@ namespace MediaBrowser.Api.Movies
var similar = _libraryManager.GetItemList(new InternalItemsQuery(user)
{
Limit = itemLimit,
- IncludeItemTypes = itemTypes.ToArray(),
+ IncludeItemTypes = itemTypes.ToArray(itemTypes.Count),
IsMovie = true,
SimilarTo = item,
EnableGroupByMetadataKey = true,
DtoOptions = dtoOptions
- }).ToList();
+ });
if (similar.Count > 0)
{
+ var returnItems = _dtoService.GetBaseItemDtos(similar, dtoOptions, user);
+
yield return new RecommendationDto
{
BaselineItemName = item.Name,
CategoryId = item.Id.ToString("N"),
RecommendationType = type,
- Items = _dtoService.GetBaseItemDtos(similar, dtoOptions, user).Result.ToArray()
+ Items = returnItems
};
}
}
}
- private IEnumerable<string> GetActors(IEnumerable<BaseItem> items)
+ private IEnumerable<string> GetActors(List<BaseItem> items)
{
var people = _libraryManager.GetPeople(new InternalPeopleQuery
{
@@ -414,11 +420,11 @@ namespace MediaBrowser.Api.Movies
.DistinctNames();
}
- private IEnumerable<string> GetDirectors(IEnumerable<BaseItem> items)
+ private IEnumerable<string> GetDirectors(List<BaseItem> items)
{
var people = _libraryManager.GetPeople(new InternalPeopleQuery
{
- PersonTypes = new List<string>
+ PersonTypes = new string[]
{
PersonType.Director
}
diff --git a/MediaBrowser.Api/Movies/TrailersService.cs b/MediaBrowser.Api/Movies/TrailersService.cs
index eb5365ab8..45b07712f 100644
--- a/MediaBrowser.Api/Movies/TrailersService.cs
+++ b/MediaBrowser.Api/Movies/TrailersService.cs
@@ -4,6 +4,7 @@ using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Net;
using MediaBrowser.Model.Querying;
using MediaBrowser.Controller.Collections;
+using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Globalization;
using MediaBrowser.Model.Serialization;
using MediaBrowser.Model.Services;
@@ -11,7 +12,7 @@ using MediaBrowser.Model.Services;
namespace MediaBrowser.Api.Movies
{
[Route("/Trailers", "GET", Summary = "Finds movies and trailers similar to a given trailer.")]
- public class Getrailers : BaseItemsRequest, IReturn<ItemsResult>
+ public class Getrailers : BaseItemsRequest, IReturn<QueryResult<BaseItemDto>>
{
}
diff --git a/MediaBrowser.Api/Music/AlbumsService.cs b/MediaBrowser.Api/Music/AlbumsService.cs
index bc7ae2be2..d7986fa50 100644
--- a/MediaBrowser.Api/Music/AlbumsService.cs
+++ b/MediaBrowser.Api/Music/AlbumsService.cs
@@ -7,7 +7,6 @@ using MediaBrowser.Controller.Persistence;
using System;
using System.Collections.Generic;
using System.Linq;
-using System.Threading.Tasks;
using MediaBrowser.Model.Services;
namespace MediaBrowser.Api.Music
@@ -52,43 +51,43 @@ namespace MediaBrowser.Api.Music
_authContext = authContext;
}
- public async Task<object> Get(GetSimilarArtists request)
+ public object Get(GetSimilarArtists request)
{
var dtoOptions = GetDtoOptions(_authContext, request);
- var result = await SimilarItemsHelper.GetSimilarItemsResult(dtoOptions, _userManager,
+ var result = SimilarItemsHelper.GetSimilarItemsResult(dtoOptions, _userManager,
_itemRepo,
_libraryManager,
_userDataRepository,
_dtoService,
Logger,
request, new[] { typeof(MusicArtist) },
- SimilarItemsHelper.GetSimiliarityScore).ConfigureAwait(false);
+ SimilarItemsHelper.GetSimiliarityScore);
return ToOptimizedSerializedResultUsingCache(result);
}
-
+
/// <summary>
/// Gets the specified request.
/// </summary>
/// <param name="request">The request.</param>
/// <returns>System.Object.</returns>
- public async Task<object> Get(GetSimilarAlbums request)
+ public object Get(GetSimilarAlbums request)
{
var dtoOptions = GetDtoOptions(_authContext, request);
- var result = await SimilarItemsHelper.GetSimilarItemsResult(dtoOptions, _userManager,
+ var result = SimilarItemsHelper.GetSimilarItemsResult(dtoOptions, _userManager,
_itemRepo,
_libraryManager,
_userDataRepository,
_dtoService,
Logger,
request, new[] { typeof(MusicAlbum) },
- GetAlbumSimilarityScore).ConfigureAwait(false);
+ GetAlbumSimilarityScore);
return ToOptimizedSerializedResultUsingCache(result);
}
-
+
/// <summary>
/// Gets the album similarity score.
/// </summary>
diff --git a/MediaBrowser.Api/Music/InstantMixService.cs b/MediaBrowser.Api/Music/InstantMixService.cs
index 3cb29de07..8a18298f1 100644
--- a/MediaBrowser.Api/Music/InstantMixService.cs
+++ b/MediaBrowser.Api/Music/InstantMixService.cs
@@ -8,7 +8,9 @@ using MediaBrowser.Model.Querying;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
+using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Services;
+using MediaBrowser.Model.Extensions;
namespace MediaBrowser.Api.Music
{
@@ -79,7 +81,7 @@ namespace MediaBrowser.Api.Music
_authContext = authContext;
}
- public Task<object> Get(GetInstantMixFromItem request)
+ public object Get(GetInstantMixFromItem request)
{
var item = _libraryManager.GetItemById(request.Id);
@@ -92,7 +94,7 @@ namespace MediaBrowser.Api.Music
return GetResult(items, user, request, dtoOptions);
}
- public Task<object> Get(GetInstantMixFromArtistId request)
+ public object Get(GetInstantMixFromArtistId request)
{
var item = _libraryManager.GetItemById(request.Id);
@@ -105,7 +107,7 @@ namespace MediaBrowser.Api.Music
return GetResult(items, user, request, dtoOptions);
}
- public Task<object> Get(GetInstantMixFromMusicGenreId request)
+ public object Get(GetInstantMixFromMusicGenreId request)
{
var item = _libraryManager.GetItemById(request.Id);
@@ -118,7 +120,7 @@ namespace MediaBrowser.Api.Music
return GetResult(items, user, request, dtoOptions);
}
- public Task<object> Get(GetInstantMixFromSong request)
+ public object Get(GetInstantMixFromSong request)
{
var item = _libraryManager.GetItemById(request.Id);
@@ -131,7 +133,7 @@ namespace MediaBrowser.Api.Music
return GetResult(items, user, request, dtoOptions);
}
- public Task<object> Get(GetInstantMixFromAlbum request)
+ public object Get(GetInstantMixFromAlbum request)
{
var album = _libraryManager.GetItemById(request.Id);
@@ -144,7 +146,7 @@ namespace MediaBrowser.Api.Music
return GetResult(items, user, request, dtoOptions);
}
- public Task<object> Get(GetInstantMixFromPlaylist request)
+ public object Get(GetInstantMixFromPlaylist request)
{
var playlist = (Playlist)_libraryManager.GetItemById(request.Id);
@@ -157,7 +159,7 @@ namespace MediaBrowser.Api.Music
return GetResult(items, user, request, dtoOptions);
}
- public Task<object> Get(GetInstantMixFromMusicGenre request)
+ public object Get(GetInstantMixFromMusicGenre request)
{
var user = _userManager.GetUserById(request.UserId);
@@ -168,7 +170,7 @@ namespace MediaBrowser.Api.Music
return GetResult(items, user, request, dtoOptions);
}
- public Task<object> Get(GetInstantMixFromArtist request)
+ public object Get(GetInstantMixFromArtist request)
{
var user = _userManager.GetUserById(request.UserId);
var artist = _libraryManager.GetArtist(request.Name, new DtoOptions(false));
@@ -180,16 +182,23 @@ namespace MediaBrowser.Api.Music
return GetResult(items, user, request, dtoOptions);
}
- private async Task<object> GetResult(IEnumerable<Audio> items, User user, BaseGetSimilarItems request, DtoOptions dtoOptions)
+ private object GetResult(List<BaseItem> items, User user, BaseGetSimilarItems request, DtoOptions dtoOptions)
{
- var list = items.ToList();
+ var list = items;
- var result = new ItemsResult
+ var result = new QueryResult<BaseItemDto>
{
TotalRecordCount = list.Count
};
- result.Items = (await _dtoService.GetBaseItemDtos(list.Take(request.Limit ?? list.Count), dtoOptions, user).ConfigureAwait(false)).ToArray();
+ if (request.Limit.HasValue)
+ {
+ list = list.Take(request.Limit.Value).ToList();
+ }
+
+ var returnList = _dtoService.GetBaseItemDtos(list, dtoOptions, user);
+
+ result.Items = returnList;
return ToOptimizedResult(result);
}
diff --git a/MediaBrowser.Api/NotificationsService.cs b/MediaBrowser.Api/NotificationsService.cs
index 58e413cef..4876351fc 100644
--- a/MediaBrowser.Api/NotificationsService.cs
+++ b/MediaBrowser.Api/NotificationsService.cs
@@ -99,7 +99,7 @@ namespace MediaBrowser.Api
public object Get(GetNotificationTypes request)
{
- var result = _notificationManager.GetNotificationTypes().ToList();
+ var result = _notificationManager.GetNotificationTypes();
return ToOptimizedResult(result);
}
diff --git a/MediaBrowser.Api/PackageReviewService.cs b/MediaBrowser.Api/PackageReviewService.cs
deleted file mode 100644
index baf1adc19..000000000
--- a/MediaBrowser.Api/PackageReviewService.cs
+++ /dev/null
@@ -1,162 +0,0 @@
-using MediaBrowser.Common.Net;
-using MediaBrowser.Controller;
-using MediaBrowser.Controller.Net;
-using MediaBrowser.Model.Entities;
-using MediaBrowser.Model.Serialization;
-using System.Collections.Generic;
-using System.Globalization;
-using System.Net;
-using System.Threading;
-using System.Threading.Tasks;
-using MediaBrowser.Model.Services;
-
-namespace MediaBrowser.Api
-{
- /// <summary>
- /// Class InstallPackage
- /// </summary>
- [Route("/Packages/Reviews/{Id}", "POST", Summary = "Creates or updates a package review")]
- public class CreateReviewRequest : IReturnVoid
- {
- /// <summary>
- /// Gets or sets the Id.
- /// </summary>
- /// <value>The Id.</value>
- [ApiMember(Name = "Id", Description = "Package Id", IsRequired = true, DataType = "int", ParameterType = "path", Verb = "POST")]
- public int Id { get; set; }
-
- /// <summary>
- /// Gets or sets the rating.
- /// </summary>
- /// <value>The review.</value>
- [ApiMember(Name = "Rating", Description = "The rating value (1-5)", IsRequired = true, DataType = "int", ParameterType = "query", Verb = "POST")]
- public int Rating { get; set; }
-
- /// <summary>
- /// Gets or sets the recommend value.
- /// </summary>
- /// <value>Whether or not this review recommends this item.</value>
- [ApiMember(Name = "Recommend", Description = "Whether or not this review recommends this item", IsRequired = true, DataType = "bool", ParameterType = "query", Verb = "POST")]
- public bool Recommend { get; set; }
-
- /// <summary>
- /// Gets or sets the title.
- /// </summary>
- /// <value>The title.</value>
- [ApiMember(Name = "Title", Description = "Optional short description of review.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")]
- public string Title { get; set; }
-
- /// <summary>
- /// Gets or sets the full review.
- /// </summary>
- /// <value>The full review.</value>
- [ApiMember(Name = "Review", Description = "Optional full review.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")]
- public string Review { get; set; }
- }
-
- /// <summary>
- /// Class InstallPackage
- /// </summary>
- [Route("/Packages/{Id}/Reviews", "GET", Summary = "Gets reviews for a package")]
- public class ReviewRequest : IReturn<List<PackageReviewInfo>>
- {
- /// <summary>
- /// Gets or sets the Id.
- /// </summary>
- /// <value>The Id.</value>
- [ApiMember(Name = "Id", Description = "Package Id", IsRequired = true, DataType = "int", ParameterType = "path", Verb = "GET")]
- public int Id { get; set; }
-
- /// <summary>
- /// Gets or sets the max rating.
- /// </summary>
- /// <value>The max rating.</value>
- [ApiMember(Name = "MaxRating", Description = "Retrieve only reviews less than or equal to this", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
- public int MaxRating { get; set; }
-
- /// <summary>
- /// Gets or sets the min rating.
- /// </summary>
- /// <value>The max rating.</value>
- [ApiMember(Name = "MinRating", Description = "Retrieve only reviews greator than or equal to this", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
- public int MinRating { get; set; }
-
- /// <summary>
- /// Only retrieve reviews with at least a short review.
- /// </summary>
- /// <value>True if should only get reviews with a title.</value>
- [ApiMember(Name = "ForceTitle", Description = "Whether or not to restrict results to those with a title", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
- public bool ForceTitle { get; set; }
-
- /// <summary>
- /// Gets or sets the limit for the query.
- /// </summary>
- /// <value>The max rating.</value>
- [ApiMember(Name = "Limit", Description = "Limit the result to this many reviews (ordered by latest)", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
- public int Limit { get; set; }
-
- }
-
- [Authenticated]
- public class PackageReviewService : BaseApiService
- {
- private readonly IHttpClient _httpClient;
- private readonly IJsonSerializer _serializer;
- private const string MbAdminUrl = "https://www.mb3admin.com/admin/";
- private readonly IServerApplicationHost _appHost;
-
- public PackageReviewService(IHttpClient httpClient, IJsonSerializer serializer, IServerApplicationHost appHost)
- {
- _httpClient = httpClient;
- _serializer = serializer;
- _appHost = appHost;
- }
-
- public async Task<object> Get(ReviewRequest request)
- {
- var parms = "?id=" + request.Id;
-
- if (request.MaxRating > 0)
- {
- parms += "&max=" + request.MaxRating;
- }
- if (request.MinRating > 0)
- {
- parms += "&min=" + request.MinRating;
- }
- if (request.MinRating > 0)
- {
- parms += "&limit=" + request.Limit;
- }
- if (request.ForceTitle)
- {
- parms += "&title=true";
- }
-
- using (var result = await _httpClient.Get(MbAdminUrl + "/service/packageReview/retrieve" + parms, CancellationToken.None)
- .ConfigureAwait(false))
- {
- var reviews = _serializer.DeserializeFromStream<List<PackageReviewInfo>>(result);
-
- return ToOptimizedResult(reviews);
- }
- }
-
- public void Post(CreateReviewRequest request)
- {
- var reviewText = WebUtility.HtmlEncode(request.Review ?? string.Empty);
- var title = WebUtility.HtmlEncode(request.Title ?? string.Empty);
-
- var review = new Dictionary<string, string>
- { { "id", request.Id.ToString(CultureInfo.InvariantCulture) },
- { "mac", _appHost.SystemId },
- { "rating", request.Rating.ToString(CultureInfo.InvariantCulture) },
- { "recommend", request.Recommend.ToString() },
- { "title", title },
- { "review", reviewText },
- };
-
- Task.WaitAll(_httpClient.Post(MbAdminUrl + "/service/packageReview/update", review, CancellationToken.None));
- }
- }
-}
diff --git a/MediaBrowser.Api/PackageService.cs b/MediaBrowser.Api/PackageService.cs
index 64424795f..79dda8702 100644
--- a/MediaBrowser.Api/PackageService.cs
+++ b/MediaBrowser.Api/PackageService.cs
@@ -40,7 +40,7 @@ namespace MediaBrowser.Api
/// </summary>
[Route("/Packages", "GET", Summary = "Gets available packages")]
[Authenticated]
- public class GetPackages : IReturn<List<PackageInfo>>
+ public class GetPackages : IReturn<PackageInfo[]>
{
/// <summary>
/// Gets or sets the name.
@@ -66,7 +66,7 @@ namespace MediaBrowser.Api
/// </summary>
[Route("/Packages/Updates", "GET", Summary = "Gets available package updates for currently installed packages")]
[Authenticated(Roles = "Admin")]
- public class GetPackageVersionUpdates : IReturn<List<PackageVersionInfo>>
+ public class GetPackageVersionUpdates : IReturn<PackageVersionInfo[]>
{
/// <summary>
/// Gets or sets the name.
@@ -148,24 +148,26 @@ namespace MediaBrowser.Api
/// <returns>System.Object.</returns>
public object Get(GetPackageVersionUpdates request)
{
- var result = new List<PackageVersionInfo>();
+ PackageVersionInfo[] result = null;
if (string.Equals(request.PackageType, "UserInstalled", StringComparison.OrdinalIgnoreCase) || string.Equals(request.PackageType, "All", StringComparison.OrdinalIgnoreCase))
{
- result.AddRange(_installationManager.GetAvailablePluginUpdates(_appHost.ApplicationVersion, false, CancellationToken.None).Result.ToList());
+ result = _installationManager.GetAvailablePluginUpdates(_appHost.ApplicationVersion, false, CancellationToken.None).Result.ToArray();
}
- else if (string.Equals(request.PackageType, "System", StringComparison.OrdinalIgnoreCase) || string.Equals(request.PackageType, "All", StringComparison.OrdinalIgnoreCase))
+ else if (string.Equals(request.PackageType, "System", StringComparison.OrdinalIgnoreCase) ||
+ string.Equals(request.PackageType, "All", StringComparison.OrdinalIgnoreCase))
{
- var updateCheckResult = _appHost.CheckForApplicationUpdate(CancellationToken.None, new SimpleProgress<double>()).Result;
+ var updateCheckResult = _appHost
+ .CheckForApplicationUpdate(CancellationToken.None, new SimpleProgress<double>()).Result;
if (updateCheckResult.IsUpdateAvailable)
{
- result.Add(updateCheckResult.Package);
+ result = new PackageVersionInfo[] {updateCheckResult.Package};
}
}
- return ToOptimizedResult(result);
+ return ToOptimizedResult(result ?? new PackageVersionInfo[] { });
}
/// <summary>
@@ -176,10 +178,9 @@ namespace MediaBrowser.Api
public object Get(GetPackage request)
{
var packages = _installationManager.GetAvailablePackages(CancellationToken.None, applicationVersion: _appHost.ApplicationVersion).Result;
- var list = packages.ToList();
- var result = list.FirstOrDefault(p => string.Equals(p.guid, request.AssemblyGuid ?? "none", StringComparison.OrdinalIgnoreCase))
- ?? list.FirstOrDefault(p => p.name.Equals(request.Name, StringComparison.OrdinalIgnoreCase));
+ var result = packages.FirstOrDefault(p => string.Equals(p.guid, request.AssemblyGuid ?? "none", StringComparison.OrdinalIgnoreCase))
+ ?? packages.FirstOrDefault(p => p.name.Equals(request.Name, StringComparison.OrdinalIgnoreCase));
return ToOptimizedResult(result);
}
@@ -191,7 +192,7 @@ namespace MediaBrowser.Api
/// <returns>System.Object.</returns>
public async Task<object> Get(GetPackages request)
{
- var packages = await _installationManager.GetAvailablePackages(CancellationToken.None, false, request.PackageType, _appHost.ApplicationVersion).ConfigureAwait(false);
+ IEnumerable<PackageInfo> packages = await _installationManager.GetAvailablePackages(CancellationToken.None, false, request.PackageType, _appHost.ApplicationVersion).ConfigureAwait(false);
if (!string.IsNullOrEmpty(request.TargetSystems))
{
@@ -215,7 +216,7 @@ namespace MediaBrowser.Api
packages = packages.Where(p => p.enableInAppStore == request.IsAppStoreEnabled.Value);
}
- return ToOptimizedResult(packages.ToList());
+ return ToOptimizedResult(packages.ToArray());
}
/// <summary>
diff --git a/MediaBrowser.Api/Playback/BaseStreamingService.cs b/MediaBrowser.Api/Playback/BaseStreamingService.cs
deleted file mode 100644
index c300fcce3..000000000
--- a/MediaBrowser.Api/Playback/BaseStreamingService.cs
+++ /dev/null
@@ -1,1024 +0,0 @@
-using MediaBrowser.Common.Extensions;
-using MediaBrowser.Controller.Configuration;
-using MediaBrowser.Controller.Devices;
-using MediaBrowser.Controller.Dlna;
-using MediaBrowser.Controller.Library;
-using MediaBrowser.Controller.MediaEncoding;
-using MediaBrowser.Model.Dlna;
-using MediaBrowser.Model.Dto;
-using MediaBrowser.Model.Entities;
-using MediaBrowser.Model.Extensions;
-using MediaBrowser.Model.IO;
-using MediaBrowser.Model.MediaInfo;
-using MediaBrowser.Model.Serialization;
-using System;
-using System.Collections.Generic;
-using System.Globalization;
-using System.IO;
-using System.Linq;
-using System.Text;
-using System.Threading;
-using System.Threading.Tasks;
-using MediaBrowser.Common.Net;
-using MediaBrowser.Controller;
-using MediaBrowser.Controller.Net;
-using MediaBrowser.Model.Configuration;
-using MediaBrowser.Model.Diagnostics;
-
-namespace MediaBrowser.Api.Playback
-{
- /// <summary>
- /// Class BaseStreamingService
- /// </summary>
- public abstract class BaseStreamingService : BaseApiService
- {
- /// <summary>
- /// Gets or sets the application paths.
- /// </summary>
- /// <value>The application paths.</value>
- protected IServerConfigurationManager ServerConfigurationManager { get; private set; }
-
- /// <summary>
- /// Gets or sets the user manager.
- /// </summary>
- /// <value>The user manager.</value>
- protected IUserManager UserManager { get; private set; }
-
- /// <summary>
- /// Gets or sets the library manager.
- /// </summary>
- /// <value>The library manager.</value>
- protected ILibraryManager LibraryManager { get; private set; }
-
- /// <summary>
- /// Gets or sets the iso manager.
- /// </summary>
- /// <value>The iso manager.</value>
- protected IIsoManager IsoManager { get; private set; }
-
- /// <summary>
- /// Gets or sets the media encoder.
- /// </summary>
- /// <value>The media encoder.</value>
- protected IMediaEncoder MediaEncoder { get; private set; }
-
- protected IFileSystem FileSystem { get; private set; }
-
- protected IDlnaManager DlnaManager { get; private set; }
- protected IDeviceManager DeviceManager { get; private set; }
- protected ISubtitleEncoder SubtitleEncoder { get; private set; }
- protected IMediaSourceManager MediaSourceManager { get; private set; }
- protected IZipClient ZipClient { get; private set; }
- protected IJsonSerializer JsonSerializer { get; private set; }
-
- public static IServerApplicationHost AppHost;
- public static IHttpClient HttpClient;
- protected IAuthorizationContext AuthorizationContext { get; private set; }
-
- protected EncodingHelper EncodingHelper { get; set; }
-
- /// <summary>
- /// Initializes a new instance of the <see cref="BaseStreamingService" /> class.
- /// </summary>
- protected BaseStreamingService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, IDlnaManager dlnaManager, ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager, IMediaSourceManager mediaSourceManager, IZipClient zipClient, IJsonSerializer jsonSerializer, IAuthorizationContext authorizationContext)
- {
- JsonSerializer = jsonSerializer;
- AuthorizationContext = authorizationContext;
- ZipClient = zipClient;
- MediaSourceManager = mediaSourceManager;
- DeviceManager = deviceManager;
- SubtitleEncoder = subtitleEncoder;
- DlnaManager = dlnaManager;
- FileSystem = fileSystem;
- ServerConfigurationManager = serverConfig;
- UserManager = userManager;
- LibraryManager = libraryManager;
- IsoManager = isoManager;
- MediaEncoder = mediaEncoder;
- EncodingHelper = new EncodingHelper(MediaEncoder, serverConfig, FileSystem, SubtitleEncoder);
- }
-
- /// <summary>
- /// Gets the command line arguments.
- /// </summary>
- protected abstract string GetCommandLineArguments(string outputPath, EncodingOptions encodingOptions, StreamState state, bool isEncoding);
-
- /// <summary>
- /// Gets the type of the transcoding job.
- /// </summary>
- /// <value>The type of the transcoding job.</value>
- protected abstract TranscodingJobType TranscodingJobType { get; }
-
- /// <summary>
- /// Gets the output file extension.
- /// </summary>
- /// <param name="state">The state.</param>
- /// <returns>System.String.</returns>
- protected virtual string GetOutputFileExtension(StreamState state)
- {
- return Path.GetExtension(state.RequestedUrl);
- }
-
- /// <summary>
- /// Gets the output file path.
- /// </summary>
- private string GetOutputFilePath(StreamState state, EncodingOptions encodingOptions, string outputFileExtension)
- {
- var folder = ServerConfigurationManager.ApplicationPaths.TranscodingTempPath;
-
- var data = GetCommandLineArguments("dummy\\dummy", encodingOptions, state, false);
-
- data += "-" + (state.Request.DeviceId ?? string.Empty);
- data += "-" + (state.Request.PlaySessionId ?? string.Empty);
-
- var dataHash = data.GetMD5().ToString("N");
-
- if (EnableOutputInSubFolder)
- {
- return Path.Combine(folder, dataHash, dataHash + (outputFileExtension ?? string.Empty).ToLower());
- }
-
- return Path.Combine(folder, dataHash + (outputFileExtension ?? string.Empty).ToLower());
- }
-
- protected virtual bool EnableOutputInSubFolder
- {
- get { return false; }
- }
-
- protected readonly CultureInfo UsCulture = new CultureInfo("en-US");
-
- protected virtual string GetDefaultH264Preset()
- {
- return "superfast";
- }
-
- private async Task AcquireResources(StreamState state, CancellationTokenSource cancellationTokenSource)
- {
- if (state.VideoType == VideoType.Iso && state.IsoType.HasValue && IsoManager.CanMount(state.MediaPath))
- {
- state.IsoMount = await IsoManager.Mount(state.MediaPath, cancellationTokenSource.Token).ConfigureAwait(false);
- }
-
- if (state.MediaSource.RequiresOpening && string.IsNullOrWhiteSpace(state.Request.LiveStreamId))
- {
- var liveStreamResponse = await MediaSourceManager.OpenLiveStream(new LiveStreamRequest
- {
- OpenToken = state.MediaSource.OpenToken
-
- }, cancellationTokenSource.Token).ConfigureAwait(false);
-
- EncodingHelper.AttachMediaSourceInfo(state, liveStreamResponse.MediaSource, state.RequestedUrl);
-
- if (state.VideoRequest != null)
- {
- EncodingHelper.TryStreamCopy(state);
- }
- }
-
- if (state.MediaSource.BufferMs.HasValue)
- {
- await Task.Delay(state.MediaSource.BufferMs.Value, cancellationTokenSource.Token).ConfigureAwait(false);
- }
- }
-
- /// <summary>
- /// Starts the FFMPEG.
- /// </summary>
- /// <param name="state">The state.</param>
- /// <param name="outputPath">The output path.</param>
- /// <param name="cancellationTokenSource">The cancellation token source.</param>
- /// <param name="workingDirectory">The working directory.</param>
- /// <returns>Task.</returns>
- protected async Task<TranscodingJob> StartFfMpeg(StreamState state,
- string outputPath,
- CancellationTokenSource cancellationTokenSource,
- string workingDirectory = null)
- {
- FileSystem.CreateDirectory(FileSystem.GetDirectoryName(outputPath));
-
- await AcquireResources(state, cancellationTokenSource).ConfigureAwait(false);
-
- if (state.VideoRequest != null && !string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase))
- {
- var auth = AuthorizationContext.GetAuthorizationInfo(Request);
- if (!string.IsNullOrWhiteSpace(auth.UserId))
- {
- var user = UserManager.GetUserById(auth.UserId);
- if (!user.Policy.EnableVideoPlaybackTranscoding)
- {
- ApiEntryPoint.Instance.OnTranscodeFailedToStart(outputPath, TranscodingJobType, state);
-
- throw new ArgumentException("User does not have access to video transcoding");
- }
- }
- }
-
- var encodingOptions = ApiEntryPoint.Instance.GetEncodingOptions();
-
- var transcodingId = Guid.NewGuid().ToString("N");
- var commandLineArgs = GetCommandLineArguments(outputPath, encodingOptions, state, true);
-
- var process = ApiEntryPoint.Instance.ProcessFactory.Create(new ProcessOptions
- {
- CreateNoWindow = true,
- UseShellExecute = false,
-
- // Must consume both stdout and stderr or deadlocks may occur
- //RedirectStandardOutput = true,
- RedirectStandardError = true,
- RedirectStandardInput = true,
-
- FileName = MediaEncoder.EncoderPath,
- Arguments = commandLineArgs,
-
- IsHidden = true,
- ErrorDialog = false,
- EnableRaisingEvents = true,
- WorkingDirectory = !string.IsNullOrWhiteSpace(workingDirectory) ? workingDirectory : null
- });
-
- var transcodingJob = ApiEntryPoint.Instance.OnTranscodeBeginning(outputPath,
- state.Request.PlaySessionId,
- state.MediaSource.LiveStreamId,
- transcodingId,
- TranscodingJobType,
- process,
- state.Request.DeviceId,
- state,
- cancellationTokenSource);
-
- var commandLineLogMessage = process.StartInfo.FileName + " " + process.StartInfo.Arguments;
- Logger.Info(commandLineLogMessage);
-
- var logFilePrefix = "ffmpeg-transcode";
- if (state.VideoRequest != null && string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase) && string.Equals(state.OutputAudioCodec, "copy", StringComparison.OrdinalIgnoreCase))
- {
- logFilePrefix = "ffmpeg-directstream";
- }
- else if (state.VideoRequest != null && string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase))
- {
- logFilePrefix = "ffmpeg-remux";
- }
-
- var logFilePath = Path.Combine(ServerConfigurationManager.ApplicationPaths.LogDirectoryPath, logFilePrefix + "-" + Guid.NewGuid() + ".txt");
- FileSystem.CreateDirectory(FileSystem.GetDirectoryName(logFilePath));
-
- // FFMpeg writes debug/error info to stderr. This is useful when debugging so let's put it in the log directory.
- state.LogFileStream = FileSystem.GetFileStream(logFilePath, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read, true);
-
- var commandLineLogMessageBytes = Encoding.UTF8.GetBytes(Request.AbsoluteUri + Environment.NewLine + Environment.NewLine + JsonSerializer.SerializeToString(state.MediaSource) + Environment.NewLine + Environment.NewLine + commandLineLogMessage + Environment.NewLine + Environment.NewLine);
- await state.LogFileStream.WriteAsync(commandLineLogMessageBytes, 0, commandLineLogMessageBytes.Length, cancellationTokenSource.Token).ConfigureAwait(false);
-
- process.Exited += (sender, args) => OnFfMpegProcessExited(process, transcodingJob, state);
-
- try
- {
- process.Start();
- }
- catch (Exception ex)
- {
- Logger.ErrorException("Error starting ffmpeg", ex);
-
- ApiEntryPoint.Instance.OnTranscodeFailedToStart(outputPath, TranscodingJobType, state);
-
- throw;
- }
-
- // MUST read both stdout and stderr asynchronously or a deadlock may occurr
- //process.BeginOutputReadLine();
-
- state.TranscodingJob = transcodingJob;
-
- // Important - don't await the log task or we won't be able to kill ffmpeg when the user stops playback
- new JobLogger(Logger).StartStreamingLog(state, process.StandardError.BaseStream, state.LogFileStream);
-
- // Wait for the file to exist before proceeeding
- while (!FileSystem.FileExists(state.WaitForPath ?? outputPath) && !transcodingJob.HasExited)
- {
- await Task.Delay(100, cancellationTokenSource.Token).ConfigureAwait(false);
- }
-
- if (state.IsInputVideo && transcodingJob.Type == TranscodingJobType.Progressive && !transcodingJob.HasExited)
- {
- await Task.Delay(1000, cancellationTokenSource.Token).ConfigureAwait(false);
-
- if (state.ReadInputAtNativeFramerate && !transcodingJob.HasExited)
- {
- await Task.Delay(1500, cancellationTokenSource.Token).ConfigureAwait(false);
- }
- }
-
- if (!transcodingJob.HasExited)
- {
- StartThrottler(state, transcodingJob);
- }
-
- return transcodingJob;
- }
-
- private void StartThrottler(StreamState state, TranscodingJob transcodingJob)
- {
- if (EnableThrottling(state))
- {
- transcodingJob.TranscodingThrottler = state.TranscodingThrottler = new TranscodingThrottler(transcodingJob, Logger, ServerConfigurationManager, ApiEntryPoint.Instance.TimerFactory, FileSystem);
- state.TranscodingThrottler.Start();
- }
- }
-
- private bool EnableThrottling(StreamState state)
- {
- return false;
- //// do not use throttling with hardware encoders
- //return state.InputProtocol == MediaProtocol.File &&
- // state.RunTimeTicks.HasValue &&
- // state.RunTimeTicks.Value >= TimeSpan.FromMinutes(5).Ticks &&
- // state.IsInputVideo &&
- // state.VideoType == VideoType.VideoFile &&
- // !string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase) &&
- // string.Equals(GetVideoEncoder(state), "libx264", StringComparison.OrdinalIgnoreCase);
- }
-
- /// <summary>
- /// Processes the exited.
- /// </summary>
- /// <param name="process">The process.</param>
- /// <param name="job">The job.</param>
- /// <param name="state">The state.</param>
- private void OnFfMpegProcessExited(IProcess process, TranscodingJob job, StreamState state)
- {
- if (job != null)
- {
- job.HasExited = true;
- }
-
- Logger.Debug("Disposing stream resources");
- state.Dispose();
-
- try
- {
- Logger.Info("FFMpeg exited with code {0}", process.ExitCode);
- }
- catch
- {
- 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);
- //}
- }
-
- /// <summary>
- /// Parses the parameters.
- /// </summary>
- /// <param name="request">The request.</param>
- private void ParseParams(StreamRequest request)
- {
- var vals = request.Params.Split(';');
-
- var videoRequest = request as VideoStreamRequest;
-
- for (var i = 0; i < vals.Length; i++)
- {
- var val = vals[i];
-
- if (string.IsNullOrWhiteSpace(val))
- {
- continue;
- }
-
- if (i == 0)
- {
- request.DeviceProfileId = val;
- }
- else if (i == 1)
- {
- request.DeviceId = val;
- }
- else if (i == 2)
- {
- request.MediaSourceId = val;
- }
- else if (i == 3)
- {
- request.Static = string.Equals("true", val, StringComparison.OrdinalIgnoreCase);
- }
- else if (i == 4)
- {
- if (videoRequest != null)
- {
- videoRequest.VideoCodec = val;
- }
- }
- else if (i == 5)
- {
- request.AudioCodec = val;
- }
- else if (i == 6)
- {
- if (videoRequest != null)
- {
- videoRequest.AudioStreamIndex = int.Parse(val, UsCulture);
- }
- }
- else if (i == 7)
- {
- if (videoRequest != null)
- {
- videoRequest.SubtitleStreamIndex = int.Parse(val, UsCulture);
- }
- }
- else if (i == 8)
- {
- if (videoRequest != null)
- {
- videoRequest.VideoBitRate = int.Parse(val, UsCulture);
- }
- }
- else if (i == 9)
- {
- request.AudioBitRate = int.Parse(val, UsCulture);
- }
- else if (i == 10)
- {
- request.MaxAudioChannels = int.Parse(val, UsCulture);
- }
- else if (i == 11)
- {
- if (videoRequest != null)
- {
- videoRequest.MaxFramerate = float.Parse(val, UsCulture);
- }
- }
- else if (i == 12)
- {
- if (videoRequest != null)
- {
- videoRequest.MaxWidth = int.Parse(val, UsCulture);
- }
- }
- else if (i == 13)
- {
- if (videoRequest != null)
- {
- videoRequest.MaxHeight = int.Parse(val, UsCulture);
- }
- }
- else if (i == 14)
- {
- request.StartTimeTicks = long.Parse(val, UsCulture);
- }
- else if (i == 15)
- {
- if (videoRequest != null)
- {
- videoRequest.Level = val;
- }
- }
- else if (i == 16)
- {
- if (videoRequest != null)
- {
- videoRequest.MaxRefFrames = int.Parse(val, UsCulture);
- }
- }
- else if (i == 17)
- {
- if (videoRequest != null)
- {
- videoRequest.MaxVideoBitDepth = int.Parse(val, UsCulture);
- }
- }
- else if (i == 18)
- {
- if (videoRequest != null)
- {
- videoRequest.Profile = val;
- }
- }
- else if (i == 19)
- {
- // cabac no longer used
- }
- else if (i == 20)
- {
- request.PlaySessionId = val;
- }
- else if (i == 21)
- {
- // api_key
- }
- else if (i == 22)
- {
- request.LiveStreamId = val;
- }
- else if (i == 23)
- {
- // Duplicating ItemId because of MediaMonkey
- }
- else if (i == 24)
- {
- if (videoRequest != null)
- {
- videoRequest.CopyTimestamps = string.Equals("true", val, StringComparison.OrdinalIgnoreCase);
- }
- }
- else if (i == 25)
- {
- if (!string.IsNullOrWhiteSpace(val) && videoRequest != null)
- {
- SubtitleDeliveryMethod method;
- if (Enum.TryParse(val, out method))
- {
- videoRequest.SubtitleMethod = method;
- }
- }
- }
- else if (i == 26)
- {
- request.TranscodingMaxAudioChannels = int.Parse(val, UsCulture);
- }
- else if (i == 27)
- {
- if (videoRequest != null)
- {
- videoRequest.EnableSubtitlesInManifest = string.Equals("true", val, StringComparison.OrdinalIgnoreCase);
- }
- }
- else if (i == 28)
- {
- request.Tag = val;
- }
- else if (i == 29)
- {
- if (videoRequest != null)
- {
- videoRequest.RequireAvc = string.Equals("true", val, StringComparison.OrdinalIgnoreCase);
- }
- }
- else if (i == 30)
- {
- request.SubtitleCodec = val;
- }
- else if (i == 31)
- {
- if (videoRequest != null)
- {
- videoRequest.RequireNonAnamorphic = string.Equals("true", val, StringComparison.OrdinalIgnoreCase);
- }
- }
- else if (i == 32)
- {
- if (videoRequest != null)
- {
- videoRequest.DeInterlace = string.Equals("true", val, StringComparison.OrdinalIgnoreCase);
- }
- }
- else if (i == 33)
- {
- request.TranscodeReasons = val;
- }
- }
- }
-
- /// <summary>
- /// Parses the dlna headers.
- /// </summary>
- /// <param name="request">The request.</param>
- private void ParseDlnaHeaders(StreamRequest request)
- {
- if (!request.StartTimeTicks.HasValue)
- {
- var timeSeek = GetHeader("TimeSeekRange.dlna.org");
-
- request.StartTimeTicks = ParseTimeSeekHeader(timeSeek);
- }
- }
-
- /// <summary>
- /// Parses the time seek header.
- /// </summary>
- private long? ParseTimeSeekHeader(string value)
- {
- if (string.IsNullOrWhiteSpace(value))
- {
- return null;
- }
-
- if (value.IndexOf("npt=", StringComparison.OrdinalIgnoreCase) != 0)
- {
- throw new ArgumentException("Invalid timeseek header");
- }
- value = value.Substring(4).Split(new[] { '-' }, 2)[0];
-
- if (value.IndexOf(':') == -1)
- {
- // Parses npt times in the format of '417.33'
- double seconds;
- if (double.TryParse(value, NumberStyles.Any, UsCulture, out seconds))
- {
- return TimeSpan.FromSeconds(seconds).Ticks;
- }
-
- throw new ArgumentException("Invalid timeseek header");
- }
-
- // Parses npt times in the format of '10:19:25.7'
- var tokens = value.Split(new[] { ':' }, 3);
- double secondsSum = 0;
- var timeFactor = 3600;
-
- foreach (var time in tokens)
- {
- double digit;
- if (double.TryParse(time, NumberStyles.Any, UsCulture, out digit))
- {
- secondsSum += digit * timeFactor;
- }
- else
- {
- throw new ArgumentException("Invalid timeseek header");
- }
- timeFactor /= 60;
- }
- return TimeSpan.FromSeconds(secondsSum).Ticks;
- }
-
- /// <summary>
- /// Gets the state.
- /// </summary>
- /// <param name="request">The request.</param>
- /// <param name="cancellationToken">The cancellation token.</param>
- /// <returns>StreamState.</returns>
- protected async Task<StreamState> GetState(StreamRequest request, CancellationToken cancellationToken)
- {
- ParseDlnaHeaders(request);
-
- if (!string.IsNullOrWhiteSpace(request.Params))
- {
- ParseParams(request);
- }
-
- var url = Request.PathInfo;
-
- if (string.IsNullOrEmpty(request.AudioCodec))
- {
- request.AudioCodec = EncodingHelper.InferAudioCodec(url);
- }
-
- var enableDlnaHeaders = !string.IsNullOrWhiteSpace(request.Params) /*||
- string.Equals(Request.Headers.Get("GetContentFeatures.DLNA.ORG"), "1", StringComparison.OrdinalIgnoreCase)*/;
-
- var state = new StreamState(MediaSourceManager, Logger, TranscodingJobType)
- {
- Request = request,
- RequestedUrl = url,
- UserAgent = Request.UserAgent,
- EnableDlnaHeaders = enableDlnaHeaders
- };
-
- var auth = AuthorizationContext.GetAuthorizationInfo(Request);
- if (!string.IsNullOrWhiteSpace(auth.UserId))
- {
- state.User = UserManager.GetUserById(auth.UserId);
- }
-
- //if ((Request.UserAgent ?? string.Empty).IndexOf("iphone", StringComparison.OrdinalIgnoreCase) != -1 ||
- // (Request.UserAgent ?? string.Empty).IndexOf("ipad", StringComparison.OrdinalIgnoreCase) != -1 ||
- // (Request.UserAgent ?? string.Empty).IndexOf("ipod", StringComparison.OrdinalIgnoreCase) != -1)
- //{
- // state.SegmentLength = 6;
- //}
-
- if (state.VideoRequest != null)
- {
- if (!string.IsNullOrWhiteSpace(state.VideoRequest.VideoCodec))
- {
- state.SupportedVideoCodecs = state.VideoRequest.VideoCodec.Split(',').Where(i => !string.IsNullOrWhiteSpace(i)).ToList();
- state.VideoRequest.VideoCodec = state.SupportedVideoCodecs.FirstOrDefault();
- }
- }
-
- if (!string.IsNullOrWhiteSpace(request.AudioCodec))
- {
- state.SupportedAudioCodecs = request.AudioCodec.Split(',').Where(i => !string.IsNullOrWhiteSpace(i)).ToList();
- state.Request.AudioCodec = state.SupportedAudioCodecs.FirstOrDefault(i => MediaEncoder.CanEncodeToAudioCodec(i))
- ?? state.SupportedAudioCodecs.FirstOrDefault();
- }
-
- if (!string.IsNullOrWhiteSpace(request.SubtitleCodec))
- {
- state.SupportedSubtitleCodecs = request.SubtitleCodec.Split(',').Where(i => !string.IsNullOrWhiteSpace(i)).ToList();
- state.Request.SubtitleCodec = state.SupportedSubtitleCodecs.FirstOrDefault(i => MediaEncoder.CanEncodeToSubtitleCodec(i))
- ?? state.SupportedSubtitleCodecs.FirstOrDefault();
- }
-
- var item = LibraryManager.GetItemById(request.Id);
-
- state.IsInputVideo = string.Equals(item.MediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase);
-
- //var primaryImage = item.GetImageInfo(ImageType.Primary, 0) ??
- // item.Parents.Select(i => i.GetImageInfo(ImageType.Primary, 0)).FirstOrDefault(i => i != null);
- //if (primaryImage != null)
- //{
- // state.AlbumCoverPath = primaryImage.Path;
- //}
-
- MediaSourceInfo mediaSource = null;
- if (string.IsNullOrWhiteSpace(request.LiveStreamId))
- {
- TranscodingJob currentJob = !string.IsNullOrWhiteSpace(request.PlaySessionId) ?
- ApiEntryPoint.Instance.GetTranscodingJob(request.PlaySessionId)
- : null;
-
- if (currentJob != null)
- {
- mediaSource = currentJob.MediaSource;
- }
-
- if (mediaSource == null)
- {
- var mediaSources = (await MediaSourceManager.GetPlayackMediaSources(request.Id, null, false, new[] { MediaType.Audio, MediaType.Video }, cancellationToken).ConfigureAwait(false)).ToList();
-
- mediaSource = string.IsNullOrEmpty(request.MediaSourceId)
- ? mediaSources.First()
- : mediaSources.FirstOrDefault(i => string.Equals(i.Id, request.MediaSourceId));
-
- if (mediaSource == null && string.Equals(request.Id, request.MediaSourceId, StringComparison.OrdinalIgnoreCase))
- {
- mediaSource = mediaSources.First();
- }
- }
- }
- else
- {
- var liveStreamInfo = await MediaSourceManager.GetLiveStreamWithDirectStreamProvider(request.LiveStreamId, cancellationToken).ConfigureAwait(false);
- mediaSource = liveStreamInfo.Item1;
- state.DirectStreamProvider = liveStreamInfo.Item2;
- }
-
- var videoRequest = request as VideoStreamRequest;
-
- EncodingHelper.AttachMediaSourceInfo(state, mediaSource, url);
-
- var container = Path.GetExtension(state.RequestedUrl);
-
- if (string.IsNullOrEmpty(container))
- {
- container = request.Container;
- }
-
- if (string.IsNullOrEmpty(container))
- {
- container = request.Static ?
- state.InputContainer :
- GetOutputFileExtension(state);
- }
-
- state.OutputContainer = (container ?? string.Empty).TrimStart('.');
-
- state.OutputAudioBitrate = EncodingHelper.GetAudioBitrateParam(state.Request, state.AudioStream);
-
- state.OutputAudioCodec = state.Request.AudioCodec;
-
- state.OutputAudioChannels = EncodingHelper.GetNumAudioChannelsParam(state.Request, state.AudioStream, state.OutputAudioCodec);
-
- if (videoRequest != null)
- {
- state.OutputVideoCodec = state.VideoRequest.VideoCodec;
- state.OutputVideoBitrate = EncodingHelper.GetVideoBitrateParamValue(state.VideoRequest, state.VideoStream, state.OutputVideoCodec);
-
- if (videoRequest != null)
- {
- EncodingHelper.TryStreamCopy(state);
- }
-
- if (state.OutputVideoBitrate.HasValue && !string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase))
- {
- var resolution = ResolutionNormalizer.Normalize(
- state.VideoStream == null ? (int?)null : state.VideoStream.BitRate,
- state.OutputVideoBitrate.Value,
- state.VideoStream == null ? null : state.VideoStream.Codec,
- state.OutputVideoCodec,
- videoRequest.MaxWidth,
- videoRequest.MaxHeight);
-
- videoRequest.MaxWidth = resolution.MaxWidth;
- videoRequest.MaxHeight = resolution.MaxHeight;
- }
-
- ApplyDeviceProfileSettings(state);
- }
- else
- {
- ApplyDeviceProfileSettings(state);
- }
-
- var ext = string.IsNullOrWhiteSpace(state.OutputContainer)
- ? GetOutputFileExtension(state)
- : ("." + state.OutputContainer);
-
- var encodingOptions = ApiEntryPoint.Instance.GetEncodingOptions();
-
- state.OutputFilePath = GetOutputFilePath(state, encodingOptions, ext);
-
- return state;
- }
-
- private void ApplyDeviceProfileSettings(StreamState state)
- {
- var headers = Request.Headers.ToDictionary();
-
- if (!string.IsNullOrWhiteSpace(state.Request.DeviceProfileId))
- {
- state.DeviceProfile = DlnaManager.GetProfile(state.Request.DeviceProfileId);
- }
- else
- {
- if (!string.IsNullOrWhiteSpace(state.Request.DeviceId))
- {
- var caps = DeviceManager.GetCapabilities(state.Request.DeviceId);
-
- if (caps != null)
- {
- state.DeviceProfile = caps.DeviceProfile;
- }
- else
- {
- state.DeviceProfile = DlnaManager.GetProfile(headers);
- }
- }
- }
-
- var profile = state.DeviceProfile;
-
- if (profile == null)
- {
- // Don't use settings from the default profile.
- // Only use a specific profile if it was requested.
- return;
- }
-
- var audioCodec = state.ActualOutputAudioCodec;
- var videoCodec = state.ActualOutputVideoCodec;
-
- var mediaProfile = state.VideoRequest == null ?
- profile.GetAudioMediaProfile(state.OutputContainer, audioCodec, state.OutputAudioChannels, state.OutputAudioBitrate, state.OutputAudioSampleRate, state.OutputAudioBitDepth) :
- profile.GetVideoMediaProfile(state.OutputContainer,
- audioCodec,
- videoCodec,
- state.OutputWidth,
- state.OutputHeight,
- state.TargetVideoBitDepth,
- state.OutputVideoBitrate,
- state.TargetVideoProfile,
- state.TargetVideoLevel,
- state.TargetFramerate,
- state.TargetPacketLength,
- state.TargetTimestamp,
- state.IsTargetAnamorphic,
- state.IsTargetInterlaced,
- state.TargetRefFrames,
- state.TargetVideoStreamCount,
- state.TargetAudioStreamCount,
- state.TargetVideoCodecTag,
- state.IsTargetAVC);
-
- if (mediaProfile != null)
- {
- state.MimeType = mediaProfile.MimeType;
- }
-
- if (!state.Request.Static)
- {
- var transcodingProfile = state.VideoRequest == null ?
- profile.GetAudioTranscodingProfile(state.OutputContainer, audioCodec) :
- profile.GetVideoTranscodingProfile(state.OutputContainer, audioCodec, videoCodec);
-
- if (transcodingProfile != null)
- {
- state.EstimateContentLength = transcodingProfile.EstimateContentLength;
- state.EnableMpegtsM2TsMode = transcodingProfile.EnableMpegtsM2TsMode;
- state.TranscodeSeekInfo = transcodingProfile.TranscodeSeekInfo;
-
- if (state.VideoRequest != null)
- {
- state.VideoRequest.CopyTimestamps = transcodingProfile.CopyTimestamps;
- state.VideoRequest.EnableSubtitlesInManifest = transcodingProfile.EnableSubtitlesInManifest;
- }
- }
- }
- }
-
- /// <summary>
- /// Adds the dlna headers.
- /// </summary>
- /// <param name="state">The state.</param>
- /// <param name="responseHeaders">The response headers.</param>
- /// <param name="isStaticallyStreamed">if set to <c>true</c> [is statically streamed].</param>
- /// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns>
- protected void AddDlnaHeaders(StreamState state, IDictionary<string, string> responseHeaders, bool isStaticallyStreamed)
- {
- if (!state.EnableDlnaHeaders)
- {
- return;
- }
-
- var profile = state.DeviceProfile;
-
- var transferMode = GetHeader("transferMode.dlna.org");
- responseHeaders["transferMode.dlna.org"] = string.IsNullOrEmpty(transferMode) ? "Streaming" : transferMode;
- responseHeaders["realTimeInfo.dlna.org"] = "DLNA.ORG_TLAG=*";
-
- if (string.Equals(GetHeader("getMediaInfo.sec"), "1", StringComparison.OrdinalIgnoreCase))
- {
- if (state.RunTimeTicks.HasValue)
- {
- var ms = TimeSpan.FromTicks(state.RunTimeTicks.Value).TotalMilliseconds;
- responseHeaders["MediaInfo.sec"] = string.Format("SEC_Duration={0};", Convert.ToInt32(ms).ToString(CultureInfo.InvariantCulture));
- }
- }
-
- if (state.RunTimeTicks.HasValue && !isStaticallyStreamed && profile != null)
- {
- AddTimeSeekResponseHeaders(state, responseHeaders);
- }
-
- if (profile == null)
- {
- profile = DlnaManager.GetDefaultProfile();
- }
-
- var audioCodec = state.ActualOutputAudioCodec;
-
- if (state.VideoRequest == null)
- {
- responseHeaders["contentFeatures.dlna.org"] = new ContentFeatureBuilder(profile)
- .BuildAudioHeader(
- state.OutputContainer,
- audioCodec,
- state.OutputAudioBitrate,
- state.OutputAudioSampleRate,
- state.OutputAudioChannels,
- state.OutputAudioBitDepth,
- isStaticallyStreamed,
- state.RunTimeTicks,
- state.TranscodeSeekInfo
- );
- }
- else
- {
- var videoCodec = state.ActualOutputVideoCodec;
-
- responseHeaders["contentFeatures.dlna.org"] = new ContentFeatureBuilder(profile)
- .BuildVideoHeader(
- state.OutputContainer,
- videoCodec,
- audioCodec,
- state.OutputWidth,
- state.OutputHeight,
- state.TargetVideoBitDepth,
- state.OutputVideoBitrate,
- state.TargetTimestamp,
- isStaticallyStreamed,
- state.RunTimeTicks,
- state.TargetVideoProfile,
- state.TargetVideoLevel,
- state.TargetFramerate,
- state.TargetPacketLength,
- state.TranscodeSeekInfo,
- state.IsTargetAnamorphic,
- state.IsTargetInterlaced,
- state.TargetRefFrames,
- state.TargetVideoStreamCount,
- state.TargetAudioStreamCount,
- state.TargetVideoCodecTag,
- state.IsTargetAVC
-
- ).FirstOrDefault() ?? string.Empty;
- }
-
- foreach (var item in responseHeaders)
- {
- Request.Response.AddHeader(item.Key, item.Value);
- }
- }
-
- private void AddTimeSeekResponseHeaders(StreamState state, IDictionary<string, string> responseHeaders)
- {
- var runtimeSeconds = TimeSpan.FromTicks(state.RunTimeTicks.Value).TotalSeconds.ToString(UsCulture);
- var startSeconds = TimeSpan.FromTicks(state.Request.StartTimeTicks ?? 0).TotalSeconds.ToString(UsCulture);
-
- responseHeaders["TimeSeekRange.dlna.org"] = string.Format("npt={0}-{1}/{1}", startSeconds, runtimeSeconds);
- responseHeaders["X-AvailableSeekRange"] = string.Format("1 npt={0}-{1}", startSeconds, runtimeSeconds);
- }
- }
-}
diff --git a/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs b/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs
deleted file mode 100644
index 83157c703..000000000
--- a/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs
+++ /dev/null
@@ -1,331 +0,0 @@
-using MediaBrowser.Controller.Configuration;
-using MediaBrowser.Controller.Devices;
-using MediaBrowser.Controller.Dlna;
-using MediaBrowser.Controller.Library;
-using MediaBrowser.Controller.MediaEncoding;
-using MediaBrowser.Model.Extensions;
-using MediaBrowser.Model.IO;
-using MediaBrowser.Model.Net;
-using MediaBrowser.Model.Serialization;
-using System;
-using System.Collections.Generic;
-using System.IO;
-using System.Text;
-using System.Threading;
-using System.Threading.Tasks;
-using MediaBrowser.Controller.Net;
-using MediaBrowser.Model.Configuration;
-
-namespace MediaBrowser.Api.Playback.Hls
-{
- /// <summary>
- /// Class BaseHlsService
- /// </summary>
- public abstract class BaseHlsService : BaseStreamingService
- {
- /// <summary>
- /// Gets the audio arguments.
- /// </summary>
- protected abstract string GetAudioArguments(StreamState state, EncodingOptions encodingOptions);
-
- /// <summary>
- /// Gets the video arguments.
- /// </summary>
- protected abstract string GetVideoArguments(StreamState state, EncodingOptions encodingOptions);
-
- /// <summary>
- /// Gets the segment file extension.
- /// </summary>
- protected string GetSegmentFileExtension(StreamRequest request)
- {
- var segmentContainer = request.SegmentContainer;
- if (!string.IsNullOrWhiteSpace(segmentContainer))
- {
- return "." + segmentContainer;
- }
-
- return ".ts";
- }
-
- /// <summary>
- /// Gets the type of the transcoding job.
- /// </summary>
- /// <value>The type of the transcoding job.</value>
- protected override TranscodingJobType TranscodingJobType
- {
- get { return TranscodingJobType.Hls; }
- }
-
- /// <summary>
- /// Processes the request.
- /// </summary>
- /// <param name="request">The request.</param>
- /// <param name="isLive">if set to <c>true</c> [is live].</param>
- /// <returns>System.Object.</returns>
- protected async Task<object> ProcessRequest(StreamRequest request, bool isLive)
- {
- return await ProcessRequestAsync(request, isLive).ConfigureAwait(false);
- }
-
- /// <summary>
- /// Processes the request async.
- /// </summary>
- /// <param name="request">The request.</param>
- /// <param name="isLive">if set to <c>true</c> [is live].</param>
- /// <returns>Task{System.Object}.</returns>
- /// <exception cref="ArgumentException">A video bitrate is required
- /// or
- /// An audio bitrate is required</exception>
- private async Task<object> ProcessRequestAsync(StreamRequest request, bool isLive)
- {
- var cancellationTokenSource = new CancellationTokenSource();
-
- var state = await GetState(request, cancellationTokenSource.Token).ConfigureAwait(false);
-
- TranscodingJob job = null;
- var playlist = state.OutputFilePath;
-
- if (!FileSystem.FileExists(playlist))
- {
- var transcodingLock = ApiEntryPoint.Instance.GetTranscodingLock(playlist);
- await transcodingLock.WaitAsync(cancellationTokenSource.Token).ConfigureAwait(false);
- try
- {
- if (!FileSystem.FileExists(playlist))
- {
- // If the playlist doesn't already exist, startup ffmpeg
- try
- {
- job = await StartFfMpeg(state, playlist, cancellationTokenSource).ConfigureAwait(false);
- job.IsLiveOutput = isLive;
- }
- catch
- {
- state.Dispose();
- throw;
- }
-
- var minSegments = state.MinSegments;
- if (minSegments > 0)
- {
- await WaitForMinimumSegmentCount(playlist, minSegments, cancellationTokenSource.Token).ConfigureAwait(false);
- }
- }
- }
- finally
- {
- transcodingLock.Release();
- }
- }
-
- if (isLive)
- {
- job = job ?? ApiEntryPoint.Instance.OnTranscodeBeginRequest(playlist, TranscodingJobType);
-
- if (job != null)
- {
- ApiEntryPoint.Instance.OnTranscodeEndRequest(job);
- }
- return ResultFactory.GetResult(GetLivePlaylistText(playlist, state.SegmentLength), MimeTypes.GetMimeType("playlist.m3u8"), new Dictionary<string, string>());
- }
-
- var audioBitrate = state.OutputAudioBitrate ?? 0;
- var videoBitrate = state.OutputVideoBitrate ?? 0;
-
- var baselineStreamBitrate = 64000;
-
- var playlistText = GetMasterPlaylistFileText(playlist, videoBitrate + audioBitrate, baselineStreamBitrate);
-
- job = job ?? ApiEntryPoint.Instance.OnTranscodeBeginRequest(playlist, TranscodingJobType);
-
- if (job != null)
- {
- ApiEntryPoint.Instance.OnTranscodeEndRequest(job);
- }
-
- return ResultFactory.GetResult(playlistText, MimeTypes.GetMimeType("playlist.m3u8"), new Dictionary<string, string>());
- }
-
- private string GetLivePlaylistText(string path, int segmentLength)
- {
- using (var stream = FileSystem.GetFileStream(path, FileOpenMode.Open, FileAccessMode.Read, FileShareMode.ReadWrite))
- {
- using (var reader = new StreamReader(stream))
- {
- var text = reader.ReadToEnd();
-
- text = text.Replace("#EXTM3U", "#EXTM3U\n#EXT-X-PLAYLIST-TYPE:EVENT");
-
- var newDuration = "#EXT-X-TARGETDURATION:" + segmentLength.ToString(UsCulture);
-
- text = text.Replace("#EXT-X-TARGETDURATION:" + (segmentLength - 1).ToString(UsCulture), newDuration, StringComparison.OrdinalIgnoreCase);
- //text = text.Replace("#EXT-X-TARGETDURATION:" + (segmentLength + 1).ToString(UsCulture), newDuration, StringComparison.OrdinalIgnoreCase);
-
- return text;
- }
- }
- }
-
- private string GetMasterPlaylistFileText(string firstPlaylist, int bitrate, int baselineStreamBitrate)
- {
- var builder = new StringBuilder();
-
- builder.AppendLine("#EXTM3U");
-
- // Pad a little to satisfy the apple hls validator
- var paddedBitrate = Convert.ToInt32(bitrate * 1.15);
-
- // Main stream
- builder.AppendLine("#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=" + paddedBitrate.ToString(UsCulture));
- var playlistUrl = "hls/" + Path.GetFileName(firstPlaylist).Replace(".m3u8", "/stream.m3u8");
- builder.AppendLine(playlistUrl);
-
- return builder.ToString();
- }
-
- protected virtual async Task WaitForMinimumSegmentCount(string playlist, int segmentCount, CancellationToken cancellationToken)
- {
- Logger.Debug("Waiting for {0} segments in {1}", segmentCount, playlist);
-
- while (!cancellationToken.IsCancellationRequested)
- {
- try
- {
- // Need to use FileShareMode.ReadWrite because we're reading the file at the same time it's being written
- using (var fileStream = GetPlaylistFileStream(playlist))
- {
- using (var reader = new StreamReader(fileStream))
- {
- var count = 0;
-
- while (!reader.EndOfStream)
- {
- var line = reader.ReadLine();
-
- if (line.IndexOf("#EXTINF:", StringComparison.OrdinalIgnoreCase) != -1)
- {
- count++;
- if (count >= segmentCount)
- {
- Logger.Debug("Finished waiting for {0} segments in {1}", segmentCount, playlist);
- return;
- }
- }
- }
- await Task.Delay(100, cancellationToken).ConfigureAwait(false);
- }
- }
- }
- catch (IOException)
- {
- // May get an error if the file is locked
- }
-
- await Task.Delay(50, cancellationToken).ConfigureAwait(false);
- }
- }
-
- protected Stream GetPlaylistFileStream(string path)
- {
- var tmpPath = path + ".tmp";
- tmpPath = path;
-
- try
- {
- return FileSystem.GetFileStream(tmpPath, FileOpenMode.Open, FileAccessMode.Read, FileShareMode.ReadWrite, FileOpenOptions.SequentialScan);
- }
- catch (IOException)
- {
- return FileSystem.GetFileStream(path, FileOpenMode.Open, FileAccessMode.Read, FileShareMode.ReadWrite, FileOpenOptions.SequentialScan);
- }
- }
-
- protected override string GetCommandLineArguments(string outputPath, EncodingOptions encodingOptions, StreamState state, bool isEncoding)
- {
- var itsOffsetMs = 0;
-
- var itsOffset = itsOffsetMs == 0 ? string.Empty : string.Format("-itsoffset {0} ", TimeSpan.FromMilliseconds(itsOffsetMs).TotalSeconds.ToString(UsCulture));
-
- var threads = EncodingHelper.GetNumberOfThreads(state, encodingOptions, false);
-
- var inputModifier = EncodingHelper.GetInputModifier(state, encodingOptions);
-
- // If isEncoding is true we're actually starting ffmpeg
- var startNumberParam = isEncoding ? GetStartNumber(state).ToString(UsCulture) : "0";
-
- var baseUrlParam = string.Empty;
-
- if (state.Request is GetLiveHlsStream)
- {
- baseUrlParam = string.Format(" -hls_base_url \"{0}/\"",
- "hls/" + Path.GetFileNameWithoutExtension(outputPath));
- }
-
- var useGenericSegmenter = true;
- if (useGenericSegmenter)
- {
- var outputTsArg = Path.Combine(FileSystem.GetDirectoryName(outputPath), Path.GetFileNameWithoutExtension(outputPath)) + "%d" + GetSegmentFileExtension(state.Request);
-
- var timeDeltaParam = String.Empty;
-
- var segmentFormat = GetSegmentFileExtension(state.Request).TrimStart('.');
- if (string.Equals(segmentFormat, "ts", StringComparison.OrdinalIgnoreCase))
- {
- segmentFormat = "mpegts";
- }
-
- baseUrlParam = string.Format("\"{0}/\"", "hls/" + Path.GetFileNameWithoutExtension(outputPath));
-
- return string.Format("{0} {1} -map_metadata -1 -map_chapters -1 -threads {2} {3} {4} {5} -f segment -max_delay 5000000 -avoid_negative_ts disabled -start_at_zero -segment_time {6} {10} -individual_header_trailer 0 -segment_format {11} -segment_list_entry_prefix {12} -segment_list_type m3u8 -segment_start_number {7} -segment_list \"{8}\" -y \"{9}\"",
- inputModifier,
- EncodingHelper.GetInputArgument(state, encodingOptions),
- threads,
- EncodingHelper.GetMapArgs(state),
- GetVideoArguments(state, encodingOptions),
- GetAudioArguments(state, encodingOptions),
- state.SegmentLength.ToString(UsCulture),
- startNumberParam,
- outputPath,
- outputTsArg,
- timeDeltaParam,
- segmentFormat,
- baseUrlParam
- ).Trim();
- }
-
- // add when stream copying?
- // -avoid_negative_ts make_zero -fflags +genpts
-
- var args = string.Format("{0} {1} {2} -map_metadata -1 -map_chapters -1 -threads {3} {4} {5} -max_delay 5000000 -avoid_negative_ts disabled -start_at_zero {6} -hls_time {7} -individual_header_trailer 0 -start_number {8} -hls_list_size {9}{10} -y \"{11}\"",
- itsOffset,
- inputModifier,
- EncodingHelper.GetInputArgument(state, encodingOptions),
- threads,
- EncodingHelper.GetMapArgs(state),
- GetVideoArguments(state, encodingOptions),
- GetAudioArguments(state, encodingOptions),
- state.SegmentLength.ToString(UsCulture),
- startNumberParam,
- state.HlsListSize.ToString(UsCulture),
- baseUrlParam,
- outputPath
- ).Trim();
-
- return args;
- }
-
- protected override string GetDefaultH264Preset()
- {
- return "veryfast";
- }
-
- protected virtual int GetStartNumber(StreamState state)
- {
- return 0;
- }
-
- public BaseHlsService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, IDlnaManager dlnaManager, ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager, IMediaSourceManager mediaSourceManager, IZipClient zipClient, IJsonSerializer jsonSerializer, IAuthorizationContext authorizationContext) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, fileSystem, dlnaManager, subtitleEncoder, deviceManager, mediaSourceManager, zipClient, jsonSerializer, authorizationContext)
- {
- }
- }
-} \ No newline at end of file
diff --git a/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs b/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs
deleted file mode 100644
index 6744fbd92..000000000
--- a/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs
+++ /dev/null
@@ -1,970 +0,0 @@
-using MediaBrowser.Common.Net;
-using MediaBrowser.Controller.Configuration;
-using MediaBrowser.Controller.Devices;
-using MediaBrowser.Controller.Dlna;
-using MediaBrowser.Controller.Library;
-using MediaBrowser.Controller.MediaEncoding;
-using MediaBrowser.Controller.Net;
-using MediaBrowser.Model.Dlna;
-using MediaBrowser.Model.Entities;
-using MediaBrowser.Model.Extensions;
-using MediaBrowser.Model.IO;
-using MediaBrowser.Model.Serialization;
-using System;
-using System.Collections.Generic;
-using System.Globalization;
-using System.IO;
-using System.Linq;
-using System.Text;
-using System.Threading;
-using System.Threading.Tasks;
-using MediaBrowser.Model.Configuration;
-using MediaBrowser.Model.Services;
-using MimeTypes = MediaBrowser.Model.Net.MimeTypes;
-
-namespace MediaBrowser.Api.Playback.Hls
-{
- /// <summary>
- /// Options is needed for chromecast. Threw Head in there since it's related
- /// </summary>
- [Route("/Videos/{Id}/master.m3u8", "GET", Summary = "Gets a video stream using HTTP live streaming.")]
- [Route("/Videos/{Id}/master.m3u8", "HEAD", Summary = "Gets a video stream using HTTP live streaming.")]
- public class GetMasterHlsVideoPlaylist : VideoStreamRequest, IMasterHlsRequest
- {
- public bool EnableAdaptiveBitrateStreaming { get; set; }
-
- public GetMasterHlsVideoPlaylist()
- {
- EnableAdaptiveBitrateStreaming = true;
- }
- }
-
- [Route("/Audio/{Id}/master.m3u8", "GET", Summary = "Gets an audio stream using HTTP live streaming.")]
- [Route("/Audio/{Id}/master.m3u8", "HEAD", Summary = "Gets an audio stream using HTTP live streaming.")]
- public class GetMasterHlsAudioPlaylist : StreamRequest, IMasterHlsRequest
- {
- public bool EnableAdaptiveBitrateStreaming { get; set; }
-
- public GetMasterHlsAudioPlaylist()
- {
- EnableAdaptiveBitrateStreaming = true;
- }
- }
-
- public interface IMasterHlsRequest
- {
- bool EnableAdaptiveBitrateStreaming { get; set; }
- }
-
- [Route("/Videos/{Id}/main.m3u8", "GET", Summary = "Gets a video stream using HTTP live streaming.")]
- public class GetVariantHlsVideoPlaylist : VideoStreamRequest
- {
- }
-
- [Route("/Audio/{Id}/main.m3u8", "GET", Summary = "Gets an audio stream using HTTP live streaming.")]
- public class GetVariantHlsAudioPlaylist : StreamRequest
- {
- }
-
- [Route("/Videos/{Id}/hls1/{PlaylistId}/{SegmentId}.{SegmentContainer}", "GET")]
- public class GetHlsVideoSegment : VideoStreamRequest
- {
- public string PlaylistId { get; set; }
-
- /// <summary>
- /// Gets or sets the segment id.
- /// </summary>
- /// <value>The segment id.</value>
- public string SegmentId { get; set; }
- }
-
- [Route("/Audio/{Id}/hls1/{PlaylistId}/{SegmentId}.{SegmentContainer}", "GET")]
- public class GetHlsAudioSegment : StreamRequest
- {
- public string PlaylistId { get; set; }
-
- /// <summary>
- /// Gets or sets the segment id.
- /// </summary>
- /// <value>The segment id.</value>
- public string SegmentId { get; set; }
- }
-
- [Authenticated]
- public class DynamicHlsService : BaseHlsService
- {
-
- public DynamicHlsService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, IDlnaManager dlnaManager, ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager, IMediaSourceManager mediaSourceManager, IZipClient zipClient, IJsonSerializer jsonSerializer, IAuthorizationContext authorizationContext, INetworkManager networkManager) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, fileSystem, dlnaManager, subtitleEncoder, deviceManager, mediaSourceManager, zipClient, jsonSerializer, authorizationContext)
- {
- NetworkManager = networkManager;
- }
-
- protected INetworkManager NetworkManager { get; private set; }
-
- public Task<object> Get(GetMasterHlsVideoPlaylist request)
- {
- return GetMasterPlaylistInternal(request, "GET");
- }
-
- public Task<object> Head(GetMasterHlsVideoPlaylist request)
- {
- return GetMasterPlaylistInternal(request, "HEAD");
- }
-
- public Task<object> Get(GetMasterHlsAudioPlaylist request)
- {
- return GetMasterPlaylistInternal(request, "GET");
- }
-
- public Task<object> Head(GetMasterHlsAudioPlaylist request)
- {
- return GetMasterPlaylistInternal(request, "HEAD");
- }
-
- public Task<object> Get(GetVariantHlsVideoPlaylist request)
- {
- return GetVariantPlaylistInternal(request, true, "main");
- }
-
- public Task<object> Get(GetVariantHlsAudioPlaylist request)
- {
- return GetVariantPlaylistInternal(request, false, "main");
- }
-
- public Task<object> Get(GetHlsVideoSegment request)
- {
- return GetDynamicSegment(request, request.SegmentId);
- }
-
- public Task<object> Get(GetHlsAudioSegment request)
- {
- return GetDynamicSegment(request, request.SegmentId);
- }
-
- private async Task<object> GetDynamicSegment(StreamRequest request, string segmentId)
- {
- if ((request.StartTimeTicks ?? 0) > 0)
- {
- throw new ArgumentException("StartTimeTicks is not allowed.");
- }
-
- var cancellationTokenSource = new CancellationTokenSource();
- var cancellationToken = cancellationTokenSource.Token;
-
- var requestedIndex = int.Parse(segmentId, NumberStyles.Integer, UsCulture);
-
- var state = await GetState(request, cancellationToken).ConfigureAwait(false);
-
- var playlistPath = Path.ChangeExtension(state.OutputFilePath, ".m3u8");
-
- var segmentPath = GetSegmentPath(state, playlistPath, requestedIndex);
-
- var segmentExtension = GetSegmentFileExtension(state.Request);
-
- TranscodingJob job = null;
-
- if (FileSystem.FileExists(segmentPath))
- {
- job = ApiEntryPoint.Instance.OnTranscodeBeginRequest(playlistPath, TranscodingJobType);
- return await GetSegmentResult(state, playlistPath, segmentPath, segmentExtension, requestedIndex, job, cancellationToken).ConfigureAwait(false);
- }
-
- var transcodingLock = ApiEntryPoint.Instance.GetTranscodingLock(playlistPath);
- await transcodingLock.WaitAsync(cancellationTokenSource.Token).ConfigureAwait(false);
- var released = false;
- var startTranscoding = false;
-
- try
- {
- if (FileSystem.FileExists(segmentPath))
- {
- job = ApiEntryPoint.Instance.OnTranscodeBeginRequest(playlistPath, TranscodingJobType);
- transcodingLock.Release();
- released = true;
- return await GetSegmentResult(state, playlistPath, segmentPath, segmentExtension, requestedIndex, job, cancellationToken).ConfigureAwait(false);
- }
- else
- {
- var currentTranscodingIndex = GetCurrentTranscodingIndex(playlistPath, segmentExtension);
- var segmentGapRequiringTranscodingChange = 24 / state.SegmentLength;
-
- if (currentTranscodingIndex == null)
- {
- Logger.Debug("Starting transcoding because currentTranscodingIndex=null");
- startTranscoding = true;
- }
- else if (requestedIndex < currentTranscodingIndex.Value)
- {
- Logger.Debug("Starting transcoding because requestedIndex={0} and currentTranscodingIndex={1}", requestedIndex, currentTranscodingIndex);
- startTranscoding = true;
- }
- else if (requestedIndex - currentTranscodingIndex.Value > segmentGapRequiringTranscodingChange)
- {
- Logger.Debug("Starting transcoding because segmentGap is {0} and max allowed gap is {1}. requestedIndex={2}", requestedIndex - currentTranscodingIndex.Value, segmentGapRequiringTranscodingChange, requestedIndex);
- startTranscoding = true;
- }
- if (startTranscoding)
- {
- // If the playlist doesn't already exist, startup ffmpeg
- try
- {
- ApiEntryPoint.Instance.KillTranscodingJobs(request.DeviceId, request.PlaySessionId, p => false);
-
- if (currentTranscodingIndex.HasValue)
- {
- DeleteLastFile(playlistPath, segmentExtension, 0);
- }
-
- request.StartTimeTicks = GetStartPositionTicks(state, requestedIndex);
-
- job = await StartFfMpeg(state, playlistPath, cancellationTokenSource).ConfigureAwait(false);
- }
- catch
- {
- state.Dispose();
- throw;
- }
-
- //await WaitForMinimumSegmentCount(playlistPath, 1, cancellationTokenSource.Token).ConfigureAwait(false);
- }
- else
- {
- job = ApiEntryPoint.Instance.OnTranscodeBeginRequest(playlistPath, TranscodingJobType);
- if (job.TranscodingThrottler != null)
- {
- job.TranscodingThrottler.UnpauseTranscoding();
- }
- }
- }
- }
- finally
- {
- if (!released)
- {
- transcodingLock.Release();
- }
- }
-
- //Logger.Info("waiting for {0}", segmentPath);
- //while (!File.Exists(segmentPath))
- //{
- // await Task.Delay(50, cancellationToken).ConfigureAwait(false);
- //}
-
- Logger.Info("returning {0}", segmentPath);
- job = job ?? ApiEntryPoint.Instance.OnTranscodeBeginRequest(playlistPath, TranscodingJobType);
- return await GetSegmentResult(state, playlistPath, segmentPath, segmentExtension, requestedIndex, job, cancellationToken).ConfigureAwait(false);
- }
-
- private const int BufferSize = 81920;
-
- private long GetStartPositionTicks(StreamState state, int requestedIndex)
- {
- double startSeconds = 0;
- var lengths = GetSegmentLengths(state);
-
- if (requestedIndex >= lengths.Length)
- {
- var msg = string.Format("Invalid segment index requested: {0} - Segment count: {1}", requestedIndex, lengths.Length);
- throw new ArgumentException(msg);
- }
-
- for (var i = 0; i < requestedIndex; i++)
- {
- startSeconds += lengths[i];
- }
-
- var position = TimeSpan.FromSeconds(startSeconds).Ticks;
- return position;
- }
-
- private long GetEndPositionTicks(StreamState state, int requestedIndex)
- {
- double startSeconds = 0;
- var lengths = GetSegmentLengths(state);
-
- if (requestedIndex >= lengths.Length)
- {
- var msg = string.Format("Invalid segment index requested: {0} - Segment count: {1}", requestedIndex, lengths.Length);
- throw new ArgumentException(msg);
- }
-
- for (var i = 0; i <= requestedIndex; i++)
- {
- startSeconds += lengths[i];
- }
-
- var position = TimeSpan.FromSeconds(startSeconds).Ticks;
- return position;
- }
-
- private double[] GetSegmentLengths(StreamState state)
- {
- var result = new List<double>();
-
- var ticks = state.RunTimeTicks ?? 0;
-
- var segmentLengthTicks = TimeSpan.FromSeconds(state.SegmentLength).Ticks;
-
- while (ticks > 0)
- {
- var length = ticks >= segmentLengthTicks ? segmentLengthTicks : ticks;
-
- result.Add(TimeSpan.FromTicks(length).TotalSeconds);
-
- ticks -= length;
- }
-
- return result.ToArray();
- }
-
- public int? GetCurrentTranscodingIndex(string playlist, string segmentExtension)
- {
- var job = ApiEntryPoint.Instance.GetTranscodingJob(playlist, TranscodingJobType);
-
- if (job == null || job.HasExited)
- {
- return null;
- }
-
- var file = GetLastTranscodingFile(playlist, segmentExtension, FileSystem);
-
- if (file == null)
- {
- return null;
- }
-
- var playlistFilename = Path.GetFileNameWithoutExtension(playlist);
-
- var indexString = Path.GetFileNameWithoutExtension(file.Name).Substring(playlistFilename.Length);
-
- return int.Parse(indexString, NumberStyles.Integer, UsCulture);
- }
-
- private void DeleteLastFile(string playlistPath, string segmentExtension, int retryCount)
- {
- var file = GetLastTranscodingFile(playlistPath, segmentExtension, FileSystem);
-
- if (file != null)
- {
- DeleteFile(file.FullName, retryCount);
- }
- }
-
- private void DeleteFile(string path, int retryCount)
- {
- if (retryCount >= 5)
- {
- return;
- }
-
- Logger.Debug("Deleting partial HLS file {0}", path);
-
- try
- {
- FileSystem.DeleteFile(path);
- }
- catch (IOException ex)
- {
- Logger.ErrorException("Error deleting partial stream file(s) {0}", ex, path);
-
- var task = Task.Delay(100);
- Task.WaitAll(task);
- DeleteFile(path, retryCount + 1);
- }
- catch (Exception ex)
- {
- Logger.ErrorException("Error deleting partial stream file(s) {0}", ex, path);
- }
- }
-
- private static FileSystemMetadata GetLastTranscodingFile(string playlist, string segmentExtension, IFileSystem fileSystem)
- {
- var folder = fileSystem.GetDirectoryName(playlist);
-
- var filePrefix = Path.GetFileNameWithoutExtension(playlist) ?? string.Empty;
-
- try
- {
- return fileSystem.GetFiles(folder, new[] { segmentExtension }, true, false)
- .Where(i => Path.GetFileNameWithoutExtension(i.Name).StartsWith(filePrefix, StringComparison.OrdinalIgnoreCase))
- .OrderByDescending(fileSystem.GetLastWriteTimeUtc)
- .FirstOrDefault();
- }
- catch (IOException)
- {
- return null;
- }
- }
-
- protected override int GetStartNumber(StreamState state)
- {
- return GetStartNumber(state.VideoRequest);
- }
-
- private int GetStartNumber(VideoStreamRequest request)
- {
- var segmentId = "0";
-
- var segmentRequest = request as GetHlsVideoSegment;
- if (segmentRequest != null)
- {
- segmentId = segmentRequest.SegmentId;
- }
-
- return int.Parse(segmentId, NumberStyles.Integer, UsCulture);
- }
-
- private string GetSegmentPath(StreamState state, string playlist, int index)
- {
- var folder = FileSystem.GetDirectoryName(playlist);
-
- var filename = Path.GetFileNameWithoutExtension(playlist);
-
- return Path.Combine(folder, filename + index.ToString(UsCulture) + GetSegmentFileExtension(state.Request));
- }
-
- private async Task<object> GetSegmentResult(StreamState state,
- string playlistPath,
- string segmentPath,
- string segmentExtension,
- int segmentIndex,
- TranscodingJob transcodingJob,
- CancellationToken cancellationToken)
- {
- var segmentFileExists = FileSystem.FileExists(segmentPath);
-
- // If all transcoding has completed, just return immediately
- if (transcodingJob != null && transcodingJob.HasExited && segmentFileExists)
- {
- return await GetSegmentResult(state, segmentPath, segmentIndex, transcodingJob).ConfigureAwait(false);
- }
-
- if (segmentFileExists)
- {
- var currentTranscodingIndex = GetCurrentTranscodingIndex(playlistPath, segmentExtension);
-
- // If requested segment is less than transcoding position, we can't transcode backwards, so assume it's ready
- if (segmentIndex < currentTranscodingIndex)
- {
- return await GetSegmentResult(state, segmentPath, segmentIndex, transcodingJob).ConfigureAwait(false);
- }
- }
-
- var segmentFilename = Path.GetFileName(segmentPath);
-
- while (!cancellationToken.IsCancellationRequested)
- {
- try
- {
- var text = FileSystem.ReadAllText(playlistPath, Encoding.UTF8);
-
- // If it appears in the playlist, it's done
- if (text.IndexOf(segmentFilename, StringComparison.OrdinalIgnoreCase) != -1)
- {
- if (!segmentFileExists)
- {
- segmentFileExists = FileSystem.FileExists(segmentPath);
- }
- if (segmentFileExists)
- {
- return await GetSegmentResult(state, segmentPath, segmentIndex, transcodingJob).ConfigureAwait(false);
- }
- //break;
- }
- }
- catch (IOException)
- {
- // May get an error if the file is locked
- }
-
- await Task.Delay(100, cancellationToken).ConfigureAwait(false);
- }
-
- cancellationToken.ThrowIfCancellationRequested();
- return await GetSegmentResult(state, segmentPath, segmentIndex, transcodingJob).ConfigureAwait(false);
- }
-
- private Task<object> GetSegmentResult(StreamState state, string segmentPath, int index, TranscodingJob transcodingJob)
- {
- var segmentEndingPositionTicks = GetEndPositionTicks(state, index);
-
- return ResultFactory.GetStaticFileResult(Request, new StaticFileResultOptions
- {
- Path = segmentPath,
- FileShare = FileShareMode.ReadWrite,
- OnComplete = () =>
- {
- if (transcodingJob != null)
- {
- transcodingJob.DownloadPositionTicks = Math.Max(transcodingJob.DownloadPositionTicks ?? segmentEndingPositionTicks, segmentEndingPositionTicks);
- ApiEntryPoint.Instance.OnTranscodeEndRequest(transcodingJob);
- }
- }
- });
- }
-
- private async Task<object> GetMasterPlaylistInternal(StreamRequest request, string method)
- {
- var state = await GetState(request, CancellationToken.None).ConfigureAwait(false);
-
- if (string.IsNullOrEmpty(request.MediaSourceId))
- {
- throw new ArgumentException("MediaSourceId is required");
- }
-
- var playlistText = string.Empty;
-
- if (string.Equals(method, "GET", StringComparison.OrdinalIgnoreCase))
- {
- var audioBitrate = state.OutputAudioBitrate ?? 0;
- var videoBitrate = state.OutputVideoBitrate ?? 0;
-
- playlistText = GetMasterPlaylistFileText(state, videoBitrate + audioBitrate);
- }
-
- return ResultFactory.GetResult(playlistText, MimeTypes.GetMimeType("playlist.m3u8"), new Dictionary<string, string>());
- }
-
- private string GetMasterPlaylistFileText(StreamState state, int totalBitrate)
- {
- var builder = new StringBuilder();
-
- builder.AppendLine("#EXTM3U");
-
- var isLiveStream = state.IsSegmentedLiveStream;
-
- var queryStringIndex = Request.RawUrl.IndexOf('?');
- var queryString = queryStringIndex == -1 ? string.Empty : Request.RawUrl.Substring(queryStringIndex);
-
- // from universal audio service
- if (queryString.IndexOf("SegmentContainer", StringComparison.OrdinalIgnoreCase) == -1 && !string.IsNullOrWhiteSpace(state.Request.SegmentContainer))
- {
- queryString += "&SegmentContainer=" + state.Request.SegmentContainer;
- }
- // from universal audio service
- if (!string.IsNullOrWhiteSpace(state.Request.TranscodeReasons) && queryString.IndexOf("TranscodeReasons=", StringComparison.OrdinalIgnoreCase) == -1)
- {
- queryString += "&TranscodeReasons=" + state.Request.TranscodeReasons;
- }
-
- // Main stream
- var playlistUrl = isLiveStream ? "live.m3u8" : "main.m3u8";
-
- playlistUrl += queryString;
-
- var request = state.Request;
-
- var subtitleStreams = state.MediaSource
- .MediaStreams
- .Where(i => i.IsTextSubtitleStream)
- .ToList();
-
- var subtitleGroup = subtitleStreams.Count > 0 &&
- request is GetMasterHlsVideoPlaylist &&
- (state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Hls || state.VideoRequest.EnableSubtitlesInManifest) ?
- "subs" :
- null;
-
- // If we're burning in subtitles then don't add additional subs to the manifest
- if (state.SubtitleStream != null && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode)
- {
- subtitleGroup = null;
- }
-
- if (!string.IsNullOrWhiteSpace(subtitleGroup))
- {
- AddSubtitles(state, subtitleStreams, builder);
- }
-
- AppendPlaylist(builder, state, playlistUrl, totalBitrate, subtitleGroup);
-
- if (EnableAdaptiveBitrateStreaming(state, isLiveStream))
- {
- var requestedVideoBitrate = state.VideoRequest == null ? 0 : state.VideoRequest.VideoBitRate ?? 0;
-
- // By default, vary by just 200k
- var variation = GetBitrateVariation(totalBitrate);
-
- var newBitrate = totalBitrate - variation;
- var variantUrl = ReplaceBitrate(playlistUrl, requestedVideoBitrate, requestedVideoBitrate - variation);
- AppendPlaylist(builder, state, variantUrl, newBitrate, subtitleGroup);
-
- variation *= 2;
- newBitrate = totalBitrate - variation;
- variantUrl = ReplaceBitrate(playlistUrl, requestedVideoBitrate, requestedVideoBitrate - variation);
- AppendPlaylist(builder, state, variantUrl, newBitrate, subtitleGroup);
- }
-
- return builder.ToString();
- }
-
- private string ReplaceBitrate(string url, int oldValue, int newValue)
- {
- return url.Replace(
- "videobitrate=" + oldValue.ToString(UsCulture),
- "videobitrate=" + newValue.ToString(UsCulture),
- StringComparison.OrdinalIgnoreCase);
- }
-
- private void AddSubtitles(StreamState state, IEnumerable<MediaStream> subtitles, StringBuilder builder)
- {
- var selectedIndex = state.SubtitleStream == null || state.SubtitleDeliveryMethod != SubtitleDeliveryMethod.Hls ? (int?)null : state.SubtitleStream.Index;
-
- foreach (var stream in subtitles)
- {
- const string format = "#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID=\"subs\",NAME=\"{0}\",DEFAULT={1},FORCED={2},AUTOSELECT=YES,URI=\"{3}\",LANGUAGE=\"{4}\"";
-
- var name = stream.DisplayTitle;
-
- var isDefault = selectedIndex.HasValue && selectedIndex.Value == stream.Index;
- var isForced = stream.IsForced;
-
- var url = string.Format("{0}/Subtitles/{1}/subtitles.m3u8?SegmentLength={2}&api_key={3}",
- state.Request.MediaSourceId,
- stream.Index.ToString(UsCulture),
- 30.ToString(UsCulture),
- AuthorizationContext.GetAuthorizationInfo(Request).Token);
-
- var line = string.Format(format,
- name,
- isDefault ? "YES" : "NO",
- isForced ? "YES" : "NO",
- url,
- stream.Language ?? "Unknown");
-
- builder.AppendLine(line);
- }
- }
-
- private bool EnableAdaptiveBitrateStreaming(StreamState state, bool isLiveStream)
- {
- // Within the local network this will likely do more harm than good.
- if (Request.IsLocal || NetworkManager.IsInLocalNetwork(Request.RemoteIp))
- {
- return false;
- }
-
- var request = state.Request as IMasterHlsRequest;
- if (request != null && !request.EnableAdaptiveBitrateStreaming)
- {
- return false;
- }
-
- if (isLiveStream || string.IsNullOrWhiteSpace(state.MediaPath))
- {
- // Opening live streams is so slow it's not even worth it
- return false;
- }
-
- if (string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase))
- {
- return false;
- }
-
- if (string.Equals(state.OutputAudioCodec, "copy", StringComparison.OrdinalIgnoreCase))
- {
- return false;
- }
-
- if (!state.IsOutputVideo)
- {
- return false;
- }
-
- // Having problems in android
- return false;
- //return state.VideoRequest.VideoBitRate.HasValue;
- }
-
- private void AppendPlaylist(StringBuilder builder, StreamState state, string url, int bitrate, string subtitleGroup)
- {
- var header = "#EXT-X-STREAM-INF:BANDWIDTH=" + bitrate.ToString(UsCulture) + ",AVERAGE-BANDWIDTH=" + bitrate.ToString(UsCulture);
-
- // tvos wants resolution, codecs, framerate
- //if (state.TargetFramerate.HasValue)
- //{
- // header += string.Format(",FRAME-RATE=\"{0}\"", state.TargetFramerate.Value.ToString(CultureInfo.InvariantCulture));
- //}
-
- if (!string.IsNullOrWhiteSpace(subtitleGroup))
- {
- header += string.Format(",SUBTITLES=\"{0}\"", subtitleGroup);
- }
-
- builder.AppendLine(header);
- builder.AppendLine(url);
- }
-
- private int GetBitrateVariation(int bitrate)
- {
- // By default, vary by just 50k
- var variation = 50000;
-
- 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;
- }
- else if (bitrate >= 600000)
- {
- variation = 200000;
- }
- else if (bitrate >= 400000)
- {
- variation = 100000;
- }
-
- return variation;
- }
-
- private async Task<object> GetVariantPlaylistInternal(StreamRequest request, bool isOutputVideo, string name)
- {
- var state = await GetState(request, CancellationToken.None).ConfigureAwait(false);
-
- var segmentLengths = GetSegmentLengths(state);
-
- var builder = new StringBuilder();
-
- builder.AppendLine("#EXTM3U");
- builder.AppendLine("#EXT-X-PLAYLIST-TYPE:VOD");
- builder.AppendLine("#EXT-X-VERSION:3");
- builder.AppendLine("#EXT-X-TARGETDURATION:" + Math.Ceiling(segmentLengths.Length > 0 ? segmentLengths.Max() : state.SegmentLength).ToString(UsCulture));
- builder.AppendLine("#EXT-X-MEDIA-SEQUENCE:0");
-
- var queryStringIndex = Request.RawUrl.IndexOf('?');
- var queryString = queryStringIndex == -1 ? string.Empty : Request.RawUrl.Substring(queryStringIndex);
-
- //if ((Request.UserAgent ?? string.Empty).IndexOf("roku", StringComparison.OrdinalIgnoreCase) != -1)
- //{
- // queryString = string.Empty;
- //}
-
- var index = 0;
-
- foreach (var length in segmentLengths)
- {
- builder.AppendLine("#EXTINF:" + length.ToString("0.0000", UsCulture) + ", nodesc");
-
- builder.AppendLine(string.Format("hls1/{0}/{1}{2}{3}",
-
- name,
- index.ToString(UsCulture),
- GetSegmentFileExtension(request),
- queryString));
-
- index++;
- }
-
- builder.AppendLine("#EXT-X-ENDLIST");
-
- var playlistText = builder.ToString();
-
- return ResultFactory.GetResult(playlistText, MimeTypes.GetMimeType("playlist.m3u8"), new Dictionary<string, string>());
- }
-
- protected override string GetAudioArguments(StreamState state, EncodingOptions encodingOptions)
- {
- var audioCodec = EncodingHelper.GetAudioEncoder(state);
-
- if (!state.IsOutputVideo)
- {
- if (string.Equals(audioCodec, "copy", StringComparison.OrdinalIgnoreCase))
- {
- return "-acodec copy";
- }
-
- var audioTranscodeParams = new List<string>();
-
- audioTranscodeParams.Add("-acodec " + audioCodec);
-
- if (state.OutputAudioBitrate.HasValue)
- {
- audioTranscodeParams.Add("-ab " + state.OutputAudioBitrate.Value.ToString(UsCulture));
- }
-
- if (state.OutputAudioChannels.HasValue)
- {
- audioTranscodeParams.Add("-ac " + state.OutputAudioChannels.Value.ToString(UsCulture));
- }
-
- if (state.OutputAudioSampleRate.HasValue)
- {
- audioTranscodeParams.Add("-ar " + state.OutputAudioSampleRate.Value.ToString(UsCulture));
- }
-
- audioTranscodeParams.Add("-vn");
- return string.Join(" ", audioTranscodeParams.ToArray());
- }
-
- if (string.Equals(audioCodec, "copy", StringComparison.OrdinalIgnoreCase))
- {
- var videoCodec = EncodingHelper.GetVideoEncoder(state, encodingOptions);
-
- if (string.Equals(videoCodec, "copy", StringComparison.OrdinalIgnoreCase) && state.EnableBreakOnNonKeyFrames(videoCodec))
- {
- return "-codec:a:0 copy -copypriorss:a:0 0";
- }
-
- return "-codec:a:0 copy";
- }
-
- var args = "-codec:a:0 " + audioCodec;
-
- var channels = state.OutputAudioChannels;
-
- if (channels.HasValue)
- {
- args += " -ac " + channels.Value;
- }
-
- var bitrate = state.OutputAudioBitrate;
-
- if (bitrate.HasValue)
- {
- args += " -ab " + bitrate.Value.ToString(UsCulture);
- }
-
- if (state.OutputAudioSampleRate.HasValue)
- {
- args += " -ar " + state.OutputAudioSampleRate.Value.ToString(UsCulture);
- }
-
- args += " " + EncodingHelper.GetAudioFilterParam(state, encodingOptions, true);
-
- return args;
- }
-
- protected override string GetVideoArguments(StreamState state, EncodingOptions encodingOptions)
- {
- if (!state.IsOutputVideo)
- {
- return string.Empty;
- }
-
- var codec = EncodingHelper.GetVideoEncoder(state, encodingOptions);
-
- var args = "-codec:v:0 " + codec;
-
- if (state.EnableMpegtsM2TsMode)
- {
- args += " -mpegts_m2ts_mode 1";
- }
-
- // See if we can save come cpu cycles by avoiding encoding
- if (string.Equals(codec, "copy", StringComparison.OrdinalIgnoreCase))
- {
- if (state.VideoStream != null && EncodingHelper.IsH264(state.VideoStream) && !string.Equals(state.VideoStream.NalLengthSize, "0", StringComparison.OrdinalIgnoreCase))
- {
- args += " -bsf:v h264_mp4toannexb";
- }
-
- //args += " -flags -global_header";
- }
- else
- {
- var keyFrameArg = string.Format(" -force_key_frames \"expr:gte(t,n_forced*{0})\"",
- state.SegmentLength.ToString(UsCulture));
-
- var hasGraphicalSubs = state.SubtitleStream != null && !state.SubtitleStream.IsTextSubtitleStream && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode;
-
- args += " " + EncodingHelper.GetVideoQualityParam(state, codec, encodingOptions, GetDefaultH264Preset()) + keyFrameArg;
-
- //args += " -mixed-refs 0 -refs 3 -x264opts b_pyramid=0:weightb=0:weightp=0";
-
- // Add resolution params, if specified
- if (!hasGraphicalSubs)
- {
- args += EncodingHelper.GetOutputSizeParam(state, codec, true);
- }
-
- // This is for internal graphical subs
- if (hasGraphicalSubs)
- {
- args += EncodingHelper.GetGraphicalSubtitleParam(state, codec);
- }
-
- //args += " -flags -global_header";
- }
-
- if (args.IndexOf("-copyts", StringComparison.OrdinalIgnoreCase) == -1)
- {
- args += " -copyts";
- }
-
- if (!string.IsNullOrEmpty(state.OutputVideoSync))
- {
- args += " -vsync " + state.OutputVideoSync;
- }
-
- args += EncodingHelper.GetOutputFFlags(state);
-
- return args;
- }
-
- protected override string GetCommandLineArguments(string outputPath, EncodingOptions encodingOptions, StreamState state, bool isEncoding)
- {
- var threads = EncodingHelper.GetNumberOfThreads(state, encodingOptions, false);
-
- var inputModifier = EncodingHelper.GetInputModifier(state, encodingOptions);
-
- // If isEncoding is true we're actually starting ffmpeg
- var startNumber = GetStartNumber(state);
- var startNumberParam = isEncoding ? startNumber.ToString(UsCulture) : "0";
-
- var mapArgs = state.IsOutputVideo ? EncodingHelper.GetMapArgs(state) : string.Empty;
-
- var outputTsArg = Path.Combine(FileSystem.GetDirectoryName(outputPath), Path.GetFileNameWithoutExtension(outputPath)) + "%d" + GetSegmentFileExtension(state.Request);
-
- var timeDeltaParam = String.Empty;
-
- if (isEncoding && startNumber > 0)
- {
- var startTime = state.SegmentLength * startNumber;
- timeDeltaParam = string.Format("-segment_time_delta -{0}", startTime);
- }
-
- var segmentFormat = GetSegmentFileExtension(state.Request).TrimStart('.');
- if (string.Equals(segmentFormat, "ts", StringComparison.OrdinalIgnoreCase))
- {
- segmentFormat = "mpegts";
- }
-
- var videoCodec = EncodingHelper.GetVideoEncoder(state, encodingOptions);
- var breakOnNonKeyFrames = state.EnableBreakOnNonKeyFrames(videoCodec);
-
- var breakOnNonKeyFramesArg = breakOnNonKeyFrames ? " -break_non_keyframes 1" : "";
-
- return string.Format("{0} {1} -map_metadata -1 -map_chapters -1 -threads {2} {3} {4} {5} -f segment -max_delay 5000000 -avoid_negative_ts disabled -start_at_zero -segment_time {6} {10} -individual_header_trailer 0{12} -segment_format {11} -segment_list_type m3u8 -segment_start_number {7} -segment_list \"{8}\" -y \"{9}\"",
- inputModifier,
- EncodingHelper.GetInputArgument(state, encodingOptions),
- threads,
- mapArgs,
- GetVideoArguments(state, encodingOptions),
- GetAudioArguments(state, encodingOptions),
- state.SegmentLength.ToString(UsCulture),
- startNumberParam,
- outputPath,
- outputTsArg,
- timeDeltaParam,
- segmentFormat,
- breakOnNonKeyFramesArg
- ).Trim();
- }
- }
-} \ No newline at end of file
diff --git a/MediaBrowser.Api/Playback/Hls/HlsSegmentService.cs b/MediaBrowser.Api/Playback/Hls/HlsSegmentService.cs
deleted file mode 100644
index 52cc02528..000000000
--- a/MediaBrowser.Api/Playback/Hls/HlsSegmentService.cs
+++ /dev/null
@@ -1,163 +0,0 @@
-using MediaBrowser.Controller;
-using MediaBrowser.Controller.Configuration;
-using MediaBrowser.Controller.Net;
-using System;
-using System.IO;
-using System.Linq;
-using System.Threading.Tasks;
-
-using MediaBrowser.Controller.MediaEncoding;
-using MediaBrowser.Model.IO;
-using MediaBrowser.Model.Services;
-
-namespace MediaBrowser.Api.Playback.Hls
-{
- /// <summary>
- /// Class GetHlsAudioSegment
- /// </summary>
- // Can't require authentication just yet due to seeing some requests come from Chrome without full query string
- //[Authenticated]
- [Route("/Audio/{Id}/hls/{SegmentId}/stream.mp3", "GET")]
- [Route("/Audio/{Id}/hls/{SegmentId}/stream.aac", "GET")]
- public class GetHlsAudioSegmentLegacy
- {
- // TODO: Deprecate with new iOS app
-
- /// <summary>
- /// Gets or sets the id.
- /// </summary>
- /// <value>The id.</value>
- public string Id { get; set; }
-
- /// <summary>
- /// Gets or sets the segment id.
- /// </summary>
- /// <value>The segment id.</value>
- public string SegmentId { get; set; }
- }
-
- /// <summary>
- /// Class GetHlsVideoSegment
- /// </summary>
- [Route("/Videos/{Id}/hls/{PlaylistId}/stream.m3u8", "GET")]
- [Authenticated]
- public class GetHlsPlaylistLegacy
- {
- // TODO: Deprecate with new iOS app
-
- /// <summary>
- /// Gets or sets the id.
- /// </summary>
- /// <value>The id.</value>
- public string Id { get; set; }
-
- public string PlaylistId { get; set; }
- }
-
- [Route("/Videos/ActiveEncodings", "DELETE")]
- [Authenticated]
- public class StopEncodingProcess
- {
- [ApiMember(Name = "DeviceId", Description = "The device id of the client requesting. Used to stop encoding processes when needed.", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "DELETE")]
- public string DeviceId { get; set; }
-
- [ApiMember(Name = "PlaySessionId", Description = "The play session id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "DELETE")]
- public string PlaySessionId { get; set; }
- }
-
- /// <summary>
- /// Class GetHlsVideoSegment
- /// </summary>
- // Can't require authentication just yet due to seeing some requests come from Chrome without full query string
- //[Authenticated]
- [Route("/Videos/{Id}/hls/{PlaylistId}/{SegmentId}.{SegmentContainer}", "GET")]
- public class GetHlsVideoSegmentLegacy : VideoStreamRequest
- {
- public string PlaylistId { get; set; }
-
- /// <summary>
- /// Gets or sets the segment id.
- /// </summary>
- /// <value>The segment id.</value>
- public string SegmentId { get; set; }
- }
-
- public class HlsSegmentService : BaseApiService
- {
- private readonly IServerApplicationPaths _appPaths;
- private readonly IServerConfigurationManager _config;
- private readonly IFileSystem _fileSystem;
-
- public HlsSegmentService(IServerApplicationPaths appPaths, IServerConfigurationManager config, IFileSystem fileSystem)
- {
- _appPaths = appPaths;
- _config = config;
- _fileSystem = fileSystem;
- }
-
- public Task<object> Get(GetHlsPlaylistLegacy request)
- {
- var file = request.PlaylistId + Path.GetExtension(Request.PathInfo);
- file = Path.Combine(_appPaths.TranscodingTempPath, file);
-
- return GetFileResult(file, file);
- }
-
- public void Delete(StopEncodingProcess request)
- {
- ApiEntryPoint.Instance.KillTranscodingJobs(request.DeviceId, request.PlaySessionId, path => true);
- }
-
- /// <summary>
- /// Gets the specified request.
- /// </summary>
- /// <param name="request">The request.</param>
- /// <returns>System.Object.</returns>
- public Task<object> Get(GetHlsVideoSegmentLegacy request)
- {
- var file = request.SegmentId + Path.GetExtension(Request.PathInfo);
-
- var transcodeFolderPath = _config.ApplicationPaths.TranscodingTempPath;
- file = Path.Combine(transcodeFolderPath, file);
-
- var normalizedPlaylistId = request.PlaylistId;
-
- var playlistPath = _fileSystem.GetFilePaths(transcodeFolderPath)
- .FirstOrDefault(i => string.Equals(Path.GetExtension(i), ".m3u8", StringComparison.OrdinalIgnoreCase) && i.IndexOf(normalizedPlaylistId, StringComparison.OrdinalIgnoreCase) != -1);
-
- return GetFileResult(file, playlistPath);
- }
-
- /// <summary>
- /// Gets the specified request.
- /// </summary>
- /// <param name="request">The request.</param>
- /// <returns>System.Object.</returns>
- public Task<object> Get(GetHlsAudioSegmentLegacy request)
- {
- // TODO: Deprecate with new iOS app
- var file = request.SegmentId + Path.GetExtension(Request.PathInfo);
- file = Path.Combine(_appPaths.TranscodingTempPath, file);
-
- return ResultFactory.GetStaticFileResult(Request, file, FileShareMode.ReadWrite);
- }
-
- private Task<object> GetFileResult(string path, string playlistPath)
- {
- var transcodingJob = ApiEntryPoint.Instance.OnTranscodeBeginRequest(playlistPath, TranscodingJobType.Hls);
-
- return ResultFactory.GetStaticFileResult(Request, new StaticFileResultOptions
- {
- Path = path,
- FileShare = FileShareMode.ReadWrite,
- OnComplete = () =>
- {
- if (transcodingJob != null)
- {
- ApiEntryPoint.Instance.OnTranscodeEndRequest(transcodingJob);
- }
- }
- });
- }
- }
-}
diff --git a/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs b/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs
deleted file mode 100644
index 9b3c8a08f..000000000
--- a/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs
+++ /dev/null
@@ -1,137 +0,0 @@
-using MediaBrowser.Controller.Configuration;
-using MediaBrowser.Controller.Devices;
-using MediaBrowser.Controller.Dlna;
-using MediaBrowser.Controller.Library;
-using MediaBrowser.Controller.MediaEncoding;
-using MediaBrowser.Model.IO;
-using MediaBrowser.Model.Serialization;
-using System;
-using MediaBrowser.Controller.Net;
-using MediaBrowser.Model.Configuration;
-using MediaBrowser.Model.Dlna;
-using MediaBrowser.Model.Services;
-
-namespace MediaBrowser.Api.Playback.Hls
-{
- [Route("/Videos/{Id}/live.m3u8", "GET")]
- public class GetLiveHlsStream : VideoStreamRequest
- {
- }
-
- /// <summary>
- /// Class VideoHlsService
- /// </summary>
- [Authenticated]
- public class VideoHlsService : BaseHlsService
- {
- public object Get(GetLiveHlsStream request)
- {
- return ProcessRequest(request, true);
- }
-
- /// <summary>
- /// Gets the audio arguments.
- /// </summary>
- protected override string GetAudioArguments(StreamState state, EncodingOptions encodingOptions)
- {
- var codec = EncodingHelper.GetAudioEncoder(state);
-
- if (string.Equals(codec, "copy", StringComparison.OrdinalIgnoreCase))
- {
- return "-codec:a:0 copy";
- }
-
- var args = "-codec:a:0 " + codec;
-
- var channels = state.OutputAudioChannels;
-
- if (channels.HasValue)
- {
- args += " -ac " + channels.Value;
- }
-
- var bitrate = state.OutputAudioBitrate;
-
- if (bitrate.HasValue)
- {
- args += " -ab " + bitrate.Value.ToString(UsCulture);
- }
-
- if (state.OutputAudioSampleRate.HasValue)
- {
- args += " -ar " + state.OutputAudioSampleRate.Value.ToString(UsCulture);
- }
-
- args += " " + EncodingHelper.GetAudioFilterParam(state, encodingOptions, true);
-
- return args;
- }
-
- /// <summary>
- /// Gets the video arguments.
- /// </summary>
- protected override string GetVideoArguments(StreamState state, EncodingOptions encodingOptions)
- {
- if (!state.IsOutputVideo)
- {
- return string.Empty;
- }
-
- var codec = EncodingHelper.GetVideoEncoder(state, encodingOptions);
-
- var args = "-codec:v:0 " + codec;
-
- if (state.EnableMpegtsM2TsMode)
- {
- args += " -mpegts_m2ts_mode 1";
- }
-
- // See if we can save come cpu cycles by avoiding encoding
- if (codec.Equals("copy", StringComparison.OrdinalIgnoreCase))
- {
- // if h264_mp4toannexb is ever added, do not use it for live tv
- if (state.VideoStream != null && EncodingHelper.IsH264(state.VideoStream) &&
- !string.Equals(state.VideoStream.NalLengthSize, "0", StringComparison.OrdinalIgnoreCase))
- {
- args += " -bsf:v h264_mp4toannexb";
- }
- }
- else
- {
- var keyFrameArg = string.Format(" -force_key_frames \"expr:gte(t,n_forced*{0})\"",
- state.SegmentLength.ToString(UsCulture));
-
- var hasGraphicalSubs = state.SubtitleStream != null && !state.SubtitleStream.IsTextSubtitleStream && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode;
-
- args += " " + EncodingHelper.GetVideoQualityParam(state, codec, encodingOptions, GetDefaultH264Preset()) + keyFrameArg;
-
- // Add resolution params, if specified
- if (!hasGraphicalSubs)
- {
- args += EncodingHelper.GetOutputSizeParam(state, codec);
- }
-
- // This is for internal graphical subs
- if (hasGraphicalSubs)
- {
- args += EncodingHelper.GetGraphicalSubtitleParam(state, codec);
- }
- }
-
- args += " -flags -global_header";
-
- if (!string.IsNullOrEmpty(state.OutputVideoSync))
- {
- args += " -vsync " + state.OutputVideoSync;
- }
-
- args += EncodingHelper.GetOutputFFlags(state);
-
- return args;
- }
-
- public VideoHlsService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, IDlnaManager dlnaManager, ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager, IMediaSourceManager mediaSourceManager, IZipClient zipClient, IJsonSerializer jsonSerializer, IAuthorizationContext authorizationContext) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, fileSystem, dlnaManager, subtitleEncoder, deviceManager, mediaSourceManager, zipClient, jsonSerializer, authorizationContext)
- {
- }
- }
-}
diff --git a/MediaBrowser.Api/Playback/MediaInfoService.cs b/MediaBrowser.Api/Playback/MediaInfoService.cs
deleted file mode 100644
index 536236f5f..000000000
--- a/MediaBrowser.Api/Playback/MediaInfoService.cs
+++ /dev/null
@@ -1,591 +0,0 @@
-using MediaBrowser.Common.Net;
-using MediaBrowser.Controller.Configuration;
-using MediaBrowser.Controller.Devices;
-using MediaBrowser.Controller.Entities;
-using MediaBrowser.Controller.Library;
-using MediaBrowser.Controller.Net;
-using MediaBrowser.Model.Dlna;
-using MediaBrowser.Model.Dto;
-using MediaBrowser.Model.Entities;
-using MediaBrowser.Model.MediaInfo;
-using MediaBrowser.Model.Session;
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Threading;
-using System.Threading.Tasks;
-using MediaBrowser.Controller.Entities.Audio;
-using MediaBrowser.Controller.MediaEncoding;
-using MediaBrowser.Model.Serialization;
-using MediaBrowser.Model.Services;
-
-namespace MediaBrowser.Api.Playback
-{
- [Route("/Items/{Id}/PlaybackInfo", "GET", Summary = "Gets live playback media info for an item")]
- public class GetPlaybackInfo : IReturn<PlaybackInfoResponse>
- {
- [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
- public string Id { get; set; }
-
- [ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")]
- public string UserId { get; set; }
- }
-
- [Route("/Items/{Id}/PlaybackInfo", "POST", Summary = "Gets live playback media info for an item")]
- public class GetPostedPlaybackInfo : PlaybackInfoRequest, IReturn<PlaybackInfoResponse>
- {
- }
-
- [Route("/LiveStreams/Open", "POST", Summary = "Opens a media source")]
- public class OpenMediaSource : LiveStreamRequest, IReturn<LiveStreamResponse>
- {
- }
-
- [Route("/LiveStreams/Close", "POST", Summary = "Closes a media source")]
- public class CloseMediaSource : IReturnVoid
- {
- [ApiMember(Name = "LiveStreamId", Description = "LiveStreamId", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
- public string LiveStreamId { get; set; }
- }
-
- [Route("/Playback/BitrateTest", "GET")]
- public class GetBitrateTestBytes
- {
- [ApiMember(Name = "Size", Description = "Size", IsRequired = true, DataType = "int", ParameterType = "query", Verb = "GET")]
- public long Size { get; set; }
-
- public GetBitrateTestBytes()
- {
- // 100k
- Size = 102400;
- }
- }
-
- [Authenticated]
- public class MediaInfoService : BaseApiService
- {
- private readonly IMediaSourceManager _mediaSourceManager;
- private readonly IDeviceManager _deviceManager;
- private readonly ILibraryManager _libraryManager;
- private readonly IServerConfigurationManager _config;
- private readonly INetworkManager _networkManager;
- private readonly IMediaEncoder _mediaEncoder;
- private readonly IUserManager _userManager;
- private readonly IJsonSerializer _json;
- private readonly IAuthorizationContext _authContext;
-
- public MediaInfoService(IMediaSourceManager mediaSourceManager, IDeviceManager deviceManager, ILibraryManager libraryManager, IServerConfigurationManager config, INetworkManager networkManager, IMediaEncoder mediaEncoder, IUserManager userManager, IJsonSerializer json, IAuthorizationContext authContext)
- {
- _mediaSourceManager = mediaSourceManager;
- _deviceManager = deviceManager;
- _libraryManager = libraryManager;
- _config = config;
- _networkManager = networkManager;
- _mediaEncoder = mediaEncoder;
- _userManager = userManager;
- _json = json;
- _authContext = authContext;
- }
-
- public object Get(GetBitrateTestBytes request)
- {
- var bytes = new byte[request.Size];
-
- for (var i = 0; i < bytes.Length; i++)
- {
- bytes[i] = 0;
- }
-
- return ResultFactory.GetResult(bytes, "application/octet-stream");
- }
-
- public async Task<object> Get(GetPlaybackInfo request)
- {
- var result = await GetPlaybackInfo(request.Id, request.UserId, new[] { MediaType.Audio, MediaType.Video }).ConfigureAwait(false);
- return ToOptimizedResult(result);
- }
-
- public async Task<object> Post(OpenMediaSource request)
- {
- var result = await OpenMediaSource(request).ConfigureAwait(false);
-
- return ToOptimizedResult(result);
- }
-
- private async Task<LiveStreamResponse> OpenMediaSource(OpenMediaSource request)
- {
- var authInfo = _authContext.GetAuthorizationInfo(Request);
-
- var result = await _mediaSourceManager.OpenLiveStream(request, CancellationToken.None).ConfigureAwait(false);
-
- var profile = request.DeviceProfile;
- if (profile == null)
- {
- var caps = _deviceManager.GetCapabilities(authInfo.DeviceId);
- if (caps != null)
- {
- profile = caps.DeviceProfile;
- }
- }
-
- if (profile != null)
- {
- var item = _libraryManager.GetItemById(request.ItemId);
-
- SetDeviceSpecificData(item, result.MediaSource, profile, authInfo, request.MaxStreamingBitrate,
- request.StartTimeTicks ?? 0, result.MediaSource.Id, request.AudioStreamIndex,
- request.SubtitleStreamIndex, request.MaxAudioChannels, request.PlaySessionId, request.UserId, request.EnableDirectPlay, true, request.EnableDirectStream, true, true, true);
- }
- else
- {
- if (!string.IsNullOrWhiteSpace(result.MediaSource.TranscodingUrl))
- {
- result.MediaSource.TranscodingUrl += "&LiveStreamId=" + result.MediaSource.LiveStreamId;
- }
- }
-
- return result;
- }
-
- public void Post(CloseMediaSource request)
- {
- var task = _mediaSourceManager.CloseLiveStream(request.LiveStreamId);
- Task.WaitAll(task);
- }
-
- public async Task<PlaybackInfoResponse> GetPlaybackInfo(GetPostedPlaybackInfo request)
- {
- var authInfo = _authContext.GetAuthorizationInfo(Request);
-
- var profile = request.DeviceProfile;
-
- //Logger.Info("GetPostedPlaybackInfo profile: {0}", _json.SerializeToString(profile));
-
- if (profile == null)
- {
- var caps = _deviceManager.GetCapabilities(authInfo.DeviceId);
- if (caps != null)
- {
- profile = caps.DeviceProfile;
- }
- }
-
- var info = await GetPlaybackInfo(request.Id, request.UserId, new[] { MediaType.Audio, MediaType.Video }, request.MediaSourceId, request.LiveStreamId).ConfigureAwait(false);
-
- if (profile != null)
- {
- var mediaSourceId = request.MediaSourceId;
-
- SetDeviceSpecificData(request.Id, info, profile, authInfo, request.MaxStreamingBitrate ?? profile.MaxStreamingBitrate, request.StartTimeTicks ?? 0, mediaSourceId, request.AudioStreamIndex, request.SubtitleStreamIndex, request.MaxAudioChannels, request.UserId, request.EnableDirectPlay, true, request.EnableDirectStream, request.EnableTranscoding, request.AllowVideoStreamCopy, request.AllowAudioStreamCopy);
- }
-
- if (request.AutoOpenLiveStream)
- {
- var mediaSource = string.IsNullOrWhiteSpace(request.MediaSourceId) ? info.MediaSources.FirstOrDefault() : info.MediaSources.FirstOrDefault(i => string.Equals(i.Id, request.MediaSourceId, StringComparison.Ordinal));
-
- if (mediaSource != null && mediaSource.RequiresOpening && string.IsNullOrWhiteSpace(mediaSource.LiveStreamId))
- {
- var openStreamResult = await OpenMediaSource(new OpenMediaSource
- {
- AudioStreamIndex = request.AudioStreamIndex,
- DeviceProfile = request.DeviceProfile,
- EnableDirectPlay = request.EnableDirectPlay,
- EnableDirectStream = request.EnableDirectStream,
- ItemId = request.Id,
- MaxAudioChannels = request.MaxAudioChannels,
- MaxStreamingBitrate = request.MaxStreamingBitrate,
- PlaySessionId = info.PlaySessionId,
- StartTimeTicks = request.StartTimeTicks,
- SubtitleStreamIndex = request.SubtitleStreamIndex,
- UserId = request.UserId,
- OpenToken = mediaSource.OpenToken,
- EnableMediaProbe = request.EnableMediaProbe
-
- }).ConfigureAwait(false);
-
- info.MediaSources = new List<MediaSourceInfo> { openStreamResult.MediaSource };
- }
- }
-
- return info;
- }
-
- public async Task<object> Post(GetPostedPlaybackInfo request)
- {
- var result = await GetPlaybackInfo(request).ConfigureAwait(false);
-
- return ToOptimizedResult(result);
- }
-
- private T Clone<T>(T obj)
- {
- // Since we're going to be setting properties on MediaSourceInfos that come out of _mediaSourceManager, we should clone it
- // Should we move this directly into MediaSourceManager?
-
- var json = _json.SerializeToString(obj);
- return _json.DeserializeFromString<T>(json);
- }
-
- private async Task<PlaybackInfoResponse> GetPlaybackInfo(string id, string userId, string[] supportedLiveMediaTypes, string mediaSourceId = null, string liveStreamId = null)
- {
- var result = new PlaybackInfoResponse();
-
- if (string.IsNullOrWhiteSpace(liveStreamId))
- {
- IEnumerable<MediaSourceInfo> mediaSources;
- try
- {
- mediaSources = await _mediaSourceManager.GetPlayackMediaSources(id, userId, true, supportedLiveMediaTypes, CancellationToken.None).ConfigureAwait(false);
- }
- catch (PlaybackException ex)
- {
- mediaSources = new List<MediaSourceInfo>();
- result.ErrorCode = ex.ErrorCode;
- }
-
- result.MediaSources = mediaSources.ToList();
-
- if (!string.IsNullOrWhiteSpace(mediaSourceId))
- {
- result.MediaSources = result.MediaSources
- .Where(i => string.Equals(i.Id, mediaSourceId, StringComparison.OrdinalIgnoreCase))
- .ToList();
- }
- }
- else
- {
- var mediaSource = await _mediaSourceManager.GetLiveStream(liveStreamId, CancellationToken.None).ConfigureAwait(false);
-
- result.MediaSources = new List<MediaSourceInfo> { mediaSource };
- }
-
- if (result.MediaSources.Count == 0)
- {
- if (!result.ErrorCode.HasValue)
- {
- result.ErrorCode = PlaybackErrorCode.NoCompatibleStream;
- }
- }
- else
- {
- result.MediaSources = Clone(result.MediaSources);
-
- result.PlaySessionId = Guid.NewGuid().ToString("N");
- }
-
- return result;
- }
-
- private void SetDeviceSpecificData(string itemId,
- PlaybackInfoResponse result,
- DeviceProfile profile,
- AuthorizationInfo auth,
- long? maxBitrate,
- long startTimeTicks,
- string mediaSourceId,
- int? audioStreamIndex,
- int? subtitleStreamIndex,
- int? maxAudioChannels,
- string userId,
- bool enableDirectPlay,
- bool forceDirectPlayRemoteMediaSource,
- bool enableDirectStream,
- bool enableTranscoding,
- bool allowVideoStreamCopy,
- bool allowAudioStreamCopy)
- {
- var item = _libraryManager.GetItemById(itemId);
-
- foreach (var mediaSource in result.MediaSources)
- {
- SetDeviceSpecificData(item, mediaSource, profile, auth, maxBitrate, startTimeTicks, mediaSourceId, audioStreamIndex, subtitleStreamIndex, maxAudioChannels, result.PlaySessionId, userId, enableDirectPlay, forceDirectPlayRemoteMediaSource, enableDirectStream, enableTranscoding, allowVideoStreamCopy, allowAudioStreamCopy);
- }
-
- SortMediaSources(result, maxBitrate);
- }
-
- private void SetDeviceSpecificData(BaseItem item,
- MediaSourceInfo mediaSource,
- DeviceProfile profile,
- AuthorizationInfo auth,
- long? maxBitrate,
- long startTimeTicks,
- string mediaSourceId,
- int? audioStreamIndex,
- int? subtitleStreamIndex,
- int? maxAudioChannels,
- string playSessionId,
- string userId,
- bool enableDirectPlay,
- bool forceDirectPlayRemoteMediaSource,
- bool enableDirectStream,
- bool enableTranscoding,
- bool allowVideoStreamCopy,
- bool allowAudioStreamCopy)
- {
- var streamBuilder = new StreamBuilder(_mediaEncoder, Logger);
-
- var options = new VideoOptions
- {
- MediaSources = new List<MediaSourceInfo> { mediaSource },
- Context = EncodingContext.Streaming,
- DeviceId = auth.DeviceId,
- ItemId = item.Id.ToString("N"),
- Profile = profile,
- MaxAudioChannels = maxAudioChannels
- };
-
- if (string.Equals(mediaSourceId, mediaSource.Id, StringComparison.OrdinalIgnoreCase))
- {
- options.MediaSourceId = mediaSourceId;
- options.AudioStreamIndex = audioStreamIndex;
- options.SubtitleStreamIndex = subtitleStreamIndex;
- }
-
- var user = _userManager.GetUserById(userId);
-
- if (!enableDirectPlay)
- {
- mediaSource.SupportsDirectPlay = false;
- }
- if (!enableDirectStream)
- {
- mediaSource.SupportsDirectStream = false;
- }
- if (!enableTranscoding)
- {
- mediaSource.SupportsTranscoding = false;
- }
-
- if (item is Audio)
- {
- Logger.Info("User policy for {0}. EnableAudioPlaybackTranscoding: {1}", user.Name, user.Policy.EnableAudioPlaybackTranscoding);
- }
- else
- {
- Logger.Info("User policy for {0}. EnablePlaybackRemuxing: {1} EnableVideoPlaybackTranscoding: {2} EnableAudioPlaybackTranscoding: {3}",
- user.Name,
- user.Policy.EnablePlaybackRemuxing,
- user.Policy.EnableVideoPlaybackTranscoding,
- user.Policy.EnableAudioPlaybackTranscoding);
- }
-
- if (mediaSource.SupportsDirectPlay)
- {
- if (mediaSource.IsRemote && forceDirectPlayRemoteMediaSource)
- {
- }
- else
- {
- var supportsDirectStream = mediaSource.SupportsDirectStream;
-
- // Dummy this up to fool StreamBuilder
- mediaSource.SupportsDirectStream = true;
- options.MaxBitrate = maxBitrate;
-
- if (item is Audio)
- {
- if (!user.Policy.EnableAudioPlaybackTranscoding)
- {
- options.ForceDirectPlay = true;
- }
- }
- else if (item is Video)
- {
- if (!user.Policy.EnableAudioPlaybackTranscoding && !user.Policy.EnableVideoPlaybackTranscoding && !user.Policy.EnablePlaybackRemuxing)
- {
- options.ForceDirectPlay = true;
- }
- }
-
- // The MediaSource supports direct stream, now test to see if the client supports it
- var streamInfo = string.Equals(item.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase) ?
- streamBuilder.BuildAudioItem(options) :
- streamBuilder.BuildVideoItem(options);
-
- if (streamInfo == null || !streamInfo.IsDirectStream)
- {
- mediaSource.SupportsDirectPlay = false;
- }
-
- // Set this back to what it was
- mediaSource.SupportsDirectStream = supportsDirectStream;
-
- if (streamInfo != null)
- {
- SetDeviceSpecificSubtitleInfo(streamInfo, mediaSource, auth.Token);
- }
- }
- }
-
- if (mediaSource.SupportsDirectStream)
- {
- options.MaxBitrate = GetMaxBitrate(maxBitrate);
-
- if (item is Audio)
- {
- if (!user.Policy.EnableAudioPlaybackTranscoding)
- {
- options.ForceDirectStream = true;
- }
- }
- else if (item is Video)
- {
- if (!user.Policy.EnableAudioPlaybackTranscoding && !user.Policy.EnableVideoPlaybackTranscoding && !user.Policy.EnablePlaybackRemuxing)
- {
- options.ForceDirectStream = true;
- }
- }
-
- // The MediaSource supports direct stream, now test to see if the client supports it
- var streamInfo = string.Equals(item.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase) ?
- streamBuilder.BuildAudioItem(options) :
- streamBuilder.BuildVideoItem(options);
-
- if (streamInfo == null || !streamInfo.IsDirectStream)
- {
- mediaSource.SupportsDirectStream = false;
- }
-
- if (streamInfo != null)
- {
- SetDeviceSpecificSubtitleInfo(streamInfo, mediaSource, auth.Token);
- }
- }
-
- if (mediaSource.SupportsTranscoding)
- {
- options.MaxBitrate = GetMaxBitrate(maxBitrate);
-
- // The MediaSource supports direct stream, now test to see if the client supports it
- var streamInfo = string.Equals(item.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase) ?
- streamBuilder.BuildAudioItem(options) :
- streamBuilder.BuildVideoItem(options);
-
- if (streamInfo != null)
- {
- streamInfo.PlaySessionId = playSessionId;
-
- if (streamInfo.PlayMethod == PlayMethod.Transcode)
- {
- streamInfo.StartPositionTicks = startTimeTicks;
- mediaSource.TranscodingUrl = streamInfo.ToUrl("-", auth.Token).TrimStart('-');
-
- if (!allowVideoStreamCopy)
- {
- mediaSource.TranscodingUrl += "&allowVideoStreamCopy=false";
- }
- if (!allowAudioStreamCopy)
- {
- mediaSource.TranscodingUrl += "&allowAudioStreamCopy=false";
- }
- mediaSource.TranscodingContainer = streamInfo.Container;
- mediaSource.TranscodingSubProtocol = streamInfo.SubProtocol;
- }
-
- // Do this after the above so that StartPositionTicks is set
- SetDeviceSpecificSubtitleInfo(streamInfo, mediaSource, auth.Token);
- }
- }
- }
-
- private long? GetMaxBitrate(long? clientMaxBitrate)
- {
- var maxBitrate = clientMaxBitrate;
- var remoteClientMaxBitrate = _config.Configuration.RemoteClientBitrateLimit;
-
- if (remoteClientMaxBitrate > 0)
- {
- var isInLocalNetwork = _networkManager.IsInLocalNetwork(Request.RemoteIp);
-
- Logger.Info("RemoteClientBitrateLimit: {0}, RemoteIp: {1}, IsInLocalNetwork: {2}", remoteClientMaxBitrate, Request.RemoteIp, isInLocalNetwork);
- if (!isInLocalNetwork)
- {
- maxBitrate = Math.Min(maxBitrate ?? remoteClientMaxBitrate, remoteClientMaxBitrate);
- }
- }
-
- return maxBitrate;
- }
-
- private void SetDeviceSpecificSubtitleInfo(StreamInfo info, MediaSourceInfo mediaSource, string accessToken)
- {
- var profiles = info.GetSubtitleProfiles(false, "-", accessToken);
- mediaSource.DefaultSubtitleStreamIndex = info.SubtitleStreamIndex;
-
- mediaSource.TranscodeReasons = info.TranscodeReasons;
-
- foreach (var profile in profiles)
- {
- foreach (var stream in mediaSource.MediaStreams)
- {
- if (stream.Type == MediaStreamType.Subtitle && stream.Index == profile.Index)
- {
- stream.DeliveryMethod = profile.DeliveryMethod;
-
- if (profile.DeliveryMethod == SubtitleDeliveryMethod.External)
- {
- stream.DeliveryUrl = profile.Url.TrimStart('-');
- stream.IsExternalUrl = profile.IsExternalUrl;
- }
- }
- }
- }
- }
-
- private void SortMediaSources(PlaybackInfoResponse result, long? maxBitrate)
- {
- var originalList = result.MediaSources.ToList();
-
- result.MediaSources = result.MediaSources.OrderBy(i =>
- {
- // Nothing beats direct playing a file
- if (i.SupportsDirectPlay && i.Protocol == MediaProtocol.File)
- {
- return 0;
- }
-
- return 1;
-
- }).ThenBy(i =>
- {
- // Let's assume direct streaming a file is just as desirable as direct playing a remote url
- if (i.SupportsDirectPlay || i.SupportsDirectStream)
- {
- return 0;
- }
-
- return 1;
-
- }).ThenBy(i =>
- {
- switch (i.Protocol)
- {
- case MediaProtocol.File:
- return 0;
- default:
- return 1;
- }
-
- }).ThenBy(i =>
- {
- if (maxBitrate.HasValue)
- {
- if (i.Bitrate.HasValue)
- {
- if (i.Bitrate.Value <= maxBitrate.Value)
- {
- return 0;
- }
-
- return 2;
- }
- }
-
- return 1;
-
- }).ThenBy(originalList.IndexOf)
- .ToList();
- }
- }
-}
diff --git a/MediaBrowser.Api/Playback/Progressive/AudioService.cs b/MediaBrowser.Api/Playback/Progressive/AudioService.cs
deleted file mode 100644
index 44e096dd7..000000000
--- a/MediaBrowser.Api/Playback/Progressive/AudioService.cs
+++ /dev/null
@@ -1,67 +0,0 @@
-using MediaBrowser.Common.Net;
-using MediaBrowser.Controller.Configuration;
-using MediaBrowser.Controller.Devices;
-using MediaBrowser.Controller.Dlna;
-using MediaBrowser.Controller.Drawing;
-using MediaBrowser.Controller.Library;
-using MediaBrowser.Controller.MediaEncoding;
-using MediaBrowser.Model.IO;
-using MediaBrowser.Model.Serialization;
-using System.Collections.Generic;
-using System.Threading.Tasks;
-using MediaBrowser.Controller.Net;
-using MediaBrowser.Model.Configuration;
-using MediaBrowser.Model.IO;
-using MediaBrowser.Model.Services;
-using MediaBrowser.Model.System;
-
-namespace MediaBrowser.Api.Playback.Progressive
-{
- /// <summary>
- /// Class GetAudioStream
- /// </summary>
- [Route("/Audio/{Id}/stream.{Container}", "GET", Summary = "Gets an audio stream")]
- [Route("/Audio/{Id}/stream", "GET", Summary = "Gets an audio stream")]
- [Route("/Audio/{Id}/stream.{Container}", "HEAD", Summary = "Gets an audio stream")]
- [Route("/Audio/{Id}/stream", "HEAD", Summary = "Gets an audio stream")]
- public class GetAudioStream : StreamRequest
- {
- }
-
- /// <summary>
- /// Class AudioService
- /// </summary>
- // TODO: In order to autheneticate this in the future, Dlna playback will require updating
- //[Authenticated]
- public class AudioService : BaseProgressiveStreamingService
- {
- public AudioService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, IDlnaManager dlnaManager, ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager, IMediaSourceManager mediaSourceManager, IZipClient zipClient, IJsonSerializer jsonSerializer, IAuthorizationContext authorizationContext, IImageProcessor imageProcessor, IEnvironmentInfo environmentInfo) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, fileSystem, dlnaManager, subtitleEncoder, deviceManager, mediaSourceManager, zipClient, jsonSerializer, authorizationContext, imageProcessor, environmentInfo)
- {
- }
-
- /// <summary>
- /// Gets the specified request.
- /// </summary>
- /// <param name="request">The request.</param>
- /// <returns>System.Object.</returns>
- public Task<object> Get(GetAudioStream request)
- {
- return ProcessRequest(request, false);
- }
-
- /// <summary>
- /// Gets the specified request.
- /// </summary>
- /// <param name="request">The request.</param>
- /// <returns>System.Object.</returns>
- public Task<object> Head(GetAudioStream request)
- {
- return ProcessRequest(request, true);
- }
-
- protected override string GetCommandLineArguments(string outputPath, EncodingOptions encodingOptions, StreamState state, bool isEncoding)
- {
- return EncodingHelper.GetProgressiveAudioFullCommandLine(state, encodingOptions, outputPath);
- }
- }
-}
diff --git a/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs b/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs
deleted file mode 100644
index db5c78a2f..000000000
--- a/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs
+++ /dev/null
@@ -1,429 +0,0 @@
-using MediaBrowser.Common.Net;
-using MediaBrowser.Controller.Configuration;
-using MediaBrowser.Controller.Devices;
-using MediaBrowser.Controller.Dlna;
-using MediaBrowser.Controller.Drawing;
-using MediaBrowser.Controller.Library;
-using MediaBrowser.Controller.MediaEncoding;
-using MediaBrowser.Controller.Net;
-using MediaBrowser.Model.IO;
-using MediaBrowser.Model.MediaInfo;
-using MediaBrowser.Model.Serialization;
-using System;
-using System.Collections.Generic;
-using System.Globalization;
-using System.IO;
-using System.Threading;
-using System.Threading.Tasks;
-using MediaBrowser.Model.Services;
-using MediaBrowser.Model.System;
-
-namespace MediaBrowser.Api.Playback.Progressive
-{
- /// <summary>
- /// Class BaseProgressiveStreamingService
- /// </summary>
- public abstract class BaseProgressiveStreamingService : BaseStreamingService
- {
- protected readonly IImageProcessor ImageProcessor;
- protected readonly IEnvironmentInfo EnvironmentInfo;
-
- public BaseProgressiveStreamingService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, IDlnaManager dlnaManager, ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager, IMediaSourceManager mediaSourceManager, IZipClient zipClient, IJsonSerializer jsonSerializer, IAuthorizationContext authorizationContext, IImageProcessor imageProcessor, IEnvironmentInfo environmentInfo) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, fileSystem, dlnaManager, subtitleEncoder, deviceManager, mediaSourceManager, zipClient, jsonSerializer, authorizationContext)
- {
- ImageProcessor = imageProcessor;
- EnvironmentInfo = environmentInfo;
- }
-
- /// <summary>
- /// Gets the output file extension.
- /// </summary>
- /// <param name="state">The state.</param>
- /// <returns>System.String.</returns>
- protected override string GetOutputFileExtension(StreamState state)
- {
- var ext = base.GetOutputFileExtension(state);
-
- if (!string.IsNullOrEmpty(ext))
- {
- return ext;
- }
-
- var isVideoRequest = state.VideoRequest != null;
-
- // Try to infer based on the desired video codec
- if (isVideoRequest)
- {
- var videoCodec = state.VideoRequest.VideoCodec;
-
- if (string.Equals(videoCodec, "h264", StringComparison.OrdinalIgnoreCase))
- {
- return ".ts";
- }
- if (string.Equals(videoCodec, "theora", StringComparison.OrdinalIgnoreCase))
- {
- return ".ogv";
- }
- if (string.Equals(videoCodec, "vpx", StringComparison.OrdinalIgnoreCase))
- {
- return ".webm";
- }
- if (string.Equals(videoCodec, "wmv", StringComparison.OrdinalIgnoreCase))
- {
- return ".asf";
- }
- }
-
- // Try to infer based on the desired audio codec
- if (!isVideoRequest)
- {
- var audioCodec = state.Request.AudioCodec;
-
- if (string.Equals("aac", audioCodec, StringComparison.OrdinalIgnoreCase))
- {
- return ".aac";
- }
- if (string.Equals("mp3", audioCodec, StringComparison.OrdinalIgnoreCase))
- {
- return ".mp3";
- }
- if (string.Equals("vorbis", audioCodec, StringComparison.OrdinalIgnoreCase))
- {
- return ".ogg";
- }
- if (string.Equals("wma", audioCodec, StringComparison.OrdinalIgnoreCase))
- {
- return ".wma";
- }
- }
-
- return null;
- }
-
- /// <summary>
- /// Gets the type of the transcoding job.
- /// </summary>
- /// <value>The type of the transcoding job.</value>
- protected override TranscodingJobType TranscodingJobType
- {
- get { return TranscodingJobType.Progressive; }
- }
-
- /// <summary>
- /// Processes the request.
- /// </summary>
- /// <param name="request">The request.</param>
- /// <param name="isHeadRequest">if set to <c>true</c> [is head request].</param>
- /// <returns>Task.</returns>
- protected async Task<object> ProcessRequest(StreamRequest request, bool isHeadRequest)
- {
- var cancellationTokenSource = new CancellationTokenSource();
-
- var state = await GetState(request, cancellationTokenSource.Token).ConfigureAwait(false);
-
- var responseHeaders = new Dictionary<string, string>();
-
- if (request.Static && state.DirectStreamProvider != null)
- {
- AddDlnaHeaders(state, responseHeaders, true);
-
- using (state)
- {
- var outputHeaders = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
-
- // TODO: Don't hardcode this
- outputHeaders["Content-Type"] = MediaBrowser.Model.Net.MimeTypes.GetMimeType("file.ts");
-
- return new ProgressiveFileCopier(state.DirectStreamProvider, outputHeaders, null, Logger, EnvironmentInfo, CancellationToken.None)
- {
- AllowEndOfFile = false
- };
- }
- }
-
- // Static remote stream
- if (request.Static && state.InputProtocol == MediaProtocol.Http)
- {
- AddDlnaHeaders(state, responseHeaders, true);
-
- using (state)
- {
- return await GetStaticRemoteStreamResult(state, responseHeaders, isHeadRequest, cancellationTokenSource).ConfigureAwait(false);
- }
- }
-
- if (request.Static && state.InputProtocol != MediaProtocol.File)
- {
- throw new ArgumentException(string.Format("Input protocol {0} cannot be streamed statically.", state.InputProtocol));
- }
-
- var outputPath = state.OutputFilePath;
- var outputPathExists = FileSystem.FileExists(outputPath);
-
- var transcodingJob = ApiEntryPoint.Instance.GetTranscodingJob(outputPath, TranscodingJobType.Progressive);
- var isTranscodeCached = outputPathExists && transcodingJob != null;
-
- AddDlnaHeaders(state, responseHeaders, request.Static || isTranscodeCached);
-
- // Static stream
- if (request.Static)
- {
- var contentType = state.GetMimeType(state.MediaPath);
-
- using (state)
- {
- if (state.MediaSource.IsInfiniteStream)
- {
- var outputHeaders = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
-
- outputHeaders["Content-Type"] = contentType;
-
- return new ProgressiveFileCopier(FileSystem, state.MediaPath, outputHeaders, null, Logger, EnvironmentInfo, CancellationToken.None)
- {
- AllowEndOfFile = false
- };
- }
-
- TimeSpan? cacheDuration = null;
-
- if (!string.IsNullOrEmpty(request.Tag))
- {
- cacheDuration = TimeSpan.FromDays(365);
- }
-
- return await ResultFactory.GetStaticFileResult(Request, new StaticFileResultOptions
- {
- ResponseHeaders = responseHeaders,
- ContentType = contentType,
- IsHeadRequest = isHeadRequest,
- Path = state.MediaPath,
- CacheDuration = cacheDuration
-
- }).ConfigureAwait(false);
- }
- }
-
- //// Not static but transcode cache file exists
- //if (isTranscodeCached && state.VideoRequest == null)
- //{
- // var contentType = state.GetMimeType(outputPath);
-
- // try
- // {
- // if (transcodingJob != null)
- // {
- // ApiEntryPoint.Instance.OnTranscodeBeginRequest(transcodingJob);
- // }
-
- // return await ResultFactory.GetStaticFileResult(Request, new StaticFileResultOptions
- // {
- // ResponseHeaders = responseHeaders,
- // ContentType = contentType,
- // IsHeadRequest = isHeadRequest,
- // Path = outputPath,
- // FileShare = FileShareMode.ReadWrite,
- // OnComplete = () =>
- // {
- // if (transcodingJob != null)
- // {
- // ApiEntryPoint.Instance.OnTranscodeEndRequest(transcodingJob);
- // }
- // }
-
- // }).ConfigureAwait(false);
- // }
- // finally
- // {
- // state.Dispose();
- // }
- //}
-
- // Need to start ffmpeg
- try
- {
- return await GetStreamResult(state, responseHeaders, isHeadRequest, cancellationTokenSource).ConfigureAwait(false);
- }
- catch
- {
- state.Dispose();
-
- throw;
- }
- }
-
- /// <summary>
- /// Gets the static remote stream result.
- /// </summary>
- /// <param name="state">The state.</param>
- /// <param name="responseHeaders">The response headers.</param>
- /// <param name="isHeadRequest">if set to <c>true</c> [is head request].</param>
- /// <param name="cancellationTokenSource">The cancellation token source.</param>
- /// <returns>Task{System.Object}.</returns>
- private async Task<object> GetStaticRemoteStreamResult(StreamState state, Dictionary<string, string> responseHeaders, bool isHeadRequest, CancellationTokenSource cancellationTokenSource)
- {
- string useragent = null;
- state.RemoteHttpHeaders.TryGetValue("User-Agent", out useragent);
-
- var trySupportSeek = false;
-
- var options = new HttpRequestOptions
- {
- Url = state.MediaPath,
- UserAgent = useragent,
- BufferContent = false,
- CancellationToken = cancellationTokenSource.Token
- };
-
- if (trySupportSeek)
- {
- if (!string.IsNullOrWhiteSpace(Request.QueryString["Range"]))
- {
- options.RequestHeaders["Range"] = Request.QueryString["Range"];
- }
- }
- var response = await HttpClient.GetResponse(options).ConfigureAwait(false);
-
- if (trySupportSeek)
- {
- foreach (var name in new[] { "Content-Range", "Accept-Ranges" })
- {
- var val = response.Headers[name];
- if (!string.IsNullOrWhiteSpace(val))
- {
- responseHeaders[name] = val;
- }
- }
- }
- else
- {
- responseHeaders["Accept-Ranges"] = "none";
- }
-
- // Seeing cases of -1 here
- if (response.ContentLength.HasValue && response.ContentLength.Value >= 0)
- {
- responseHeaders["Content-Length"] = response.ContentLength.Value.ToString(UsCulture);
- }
-
- if (isHeadRequest)
- {
- using (response)
- {
- return ResultFactory.GetResult(new byte[] { }, response.ContentType, responseHeaders);
- }
- }
-
- var result = new StaticRemoteStreamWriter(response);
-
- result.Headers["Content-Type"] = response.ContentType;
-
- // Add the response headers to the result object
- foreach (var header in responseHeaders)
- {
- result.Headers[header.Key] = header.Value;
- }
-
- return result;
- }
-
- /// <summary>
- /// Gets the stream result.
- /// </summary>
- /// <param name="state">The state.</param>
- /// <param name="responseHeaders">The response headers.</param>
- /// <param name="isHeadRequest">if set to <c>true</c> [is head request].</param>
- /// <param name="cancellationTokenSource">The cancellation token source.</param>
- /// <returns>Task{System.Object}.</returns>
- private async Task<object> GetStreamResult(StreamState state, IDictionary<string, string> responseHeaders, bool isHeadRequest, CancellationTokenSource cancellationTokenSource)
- {
- // Use the command line args with a dummy playlist path
- var outputPath = state.OutputFilePath;
-
- responseHeaders["Accept-Ranges"] = "none";
-
- var contentType = state.GetMimeType(outputPath);
-
- // TODO: The isHeadRequest is only here because ServiceStack will add Content-Length=0 to the response
- // What we really want to do is hunt that down and remove that
- var contentLength = state.EstimateContentLength || isHeadRequest ? GetEstimatedContentLength(state) : null;
-
- if (contentLength.HasValue)
- {
- responseHeaders["Content-Length"] = contentLength.Value.ToString(UsCulture);
- }
-
- // Headers only
- if (isHeadRequest)
- {
- var streamResult = ResultFactory.GetResult(new byte[] { }, contentType, responseHeaders);
-
- var hasHeaders = streamResult as IHasHeaders;
- if (hasHeaders != null)
- {
- if (contentLength.HasValue)
- {
- hasHeaders.Headers["Content-Length"] = contentLength.Value.ToString(CultureInfo.InvariantCulture);
- }
- else
- {
- if (hasHeaders.Headers.ContainsKey("Content-Length"))
- {
- hasHeaders.Headers.Remove("Content-Length");
- }
- }
- }
-
- return streamResult;
- }
-
- var transcodingLock = ApiEntryPoint.Instance.GetTranscodingLock(outputPath);
- await transcodingLock.WaitAsync(cancellationTokenSource.Token).ConfigureAwait(false);
- try
- {
- TranscodingJob job;
-
- if (!FileSystem.FileExists(outputPath))
- {
- job = await StartFfMpeg(state, outputPath, cancellationTokenSource).ConfigureAwait(false);
- }
- else
- {
- job = ApiEntryPoint.Instance.OnTranscodeBeginRequest(outputPath, TranscodingJobType.Progressive);
- state.Dispose();
- }
-
- var outputHeaders = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
-
- outputHeaders["Content-Type"] = contentType;
-
- // Add the response headers to the result object
- foreach (var item in responseHeaders)
- {
- outputHeaders[item.Key] = item.Value;
- }
-
- return new ProgressiveFileCopier(FileSystem, outputPath, outputHeaders, job, Logger, EnvironmentInfo, CancellationToken.None);
- }
- finally
- {
- transcodingLock.Release();
- }
- }
-
- /// <summary>
- /// Gets the length of the estimated content.
- /// </summary>
- /// <param name="state">The state.</param>
- /// <returns>System.Nullable{System.Int64}.</returns>
- private long? GetEstimatedContentLength(StreamState state)
- {
- var totalBitrate = state.TotalOutputBitrate ?? 0;
-
- if (totalBitrate > 0 && state.RunTimeTicks.HasValue)
- {
- return Convert.ToInt64(totalBitrate * TimeSpan.FromTicks(state.RunTimeTicks.Value).TotalSeconds / 8);
- }
-
- return null;
- }
- }
-}
diff --git a/MediaBrowser.Api/Playback/Progressive/VideoService.cs b/MediaBrowser.Api/Playback/Progressive/VideoService.cs
deleted file mode 100644
index a41b4cbf5..000000000
--- a/MediaBrowser.Api/Playback/Progressive/VideoService.cs
+++ /dev/null
@@ -1,100 +0,0 @@
-using MediaBrowser.Controller.Configuration;
-using MediaBrowser.Controller.Devices;
-using MediaBrowser.Controller.Dlna;
-using MediaBrowser.Controller.Drawing;
-using MediaBrowser.Controller.Library;
-using MediaBrowser.Controller.MediaEncoding;
-using MediaBrowser.Model.IO;
-using MediaBrowser.Model.Serialization;
-using System.Threading.Tasks;
-using MediaBrowser.Controller.Net;
-using MediaBrowser.Model.Configuration;
-using MediaBrowser.Model.Services;
-using MediaBrowser.Model.System;
-
-namespace MediaBrowser.Api.Playback.Progressive
-{
- /// <summary>
- /// Class GetVideoStream
- /// </summary>
- [Route("/Videos/{Id}/stream.mpegts", "GET")]
- [Route("/Videos/{Id}/stream.ts", "GET")]
- [Route("/Videos/{Id}/stream.webm", "GET")]
- [Route("/Videos/{Id}/stream.asf", "GET")]
- [Route("/Videos/{Id}/stream.wmv", "GET")]
- [Route("/Videos/{Id}/stream.ogv", "GET")]
- [Route("/Videos/{Id}/stream.mp4", "GET")]
- [Route("/Videos/{Id}/stream.m4v", "GET")]
- [Route("/Videos/{Id}/stream.mkv", "GET")]
- [Route("/Videos/{Id}/stream.mpeg", "GET")]
- [Route("/Videos/{Id}/stream.mpg", "GET")]
- [Route("/Videos/{Id}/stream.avi", "GET")]
- [Route("/Videos/{Id}/stream.m2ts", "GET")]
- [Route("/Videos/{Id}/stream.3gp", "GET")]
- [Route("/Videos/{Id}/stream.wmv", "GET")]
- [Route("/Videos/{Id}/stream.wtv", "GET")]
- [Route("/Videos/{Id}/stream.mov", "GET")]
- [Route("/Videos/{Id}/stream.iso", "GET")]
- [Route("/Videos/{Id}/stream.flv", "GET")]
- [Route("/Videos/{Id}/stream", "GET")]
- [Route("/Videos/{Id}/stream.ts", "HEAD")]
- [Route("/Videos/{Id}/stream.webm", "HEAD")]
- [Route("/Videos/{Id}/stream.asf", "HEAD")]
- [Route("/Videos/{Id}/stream.wmv", "HEAD")]
- [Route("/Videos/{Id}/stream.ogv", "HEAD")]
- [Route("/Videos/{Id}/stream.mp4", "HEAD")]
- [Route("/Videos/{Id}/stream.m4v", "HEAD")]
- [Route("/Videos/{Id}/stream.mkv", "HEAD")]
- [Route("/Videos/{Id}/stream.mpeg", "HEAD")]
- [Route("/Videos/{Id}/stream.mpg", "HEAD")]
- [Route("/Videos/{Id}/stream.avi", "HEAD")]
- [Route("/Videos/{Id}/stream.3gp", "HEAD")]
- [Route("/Videos/{Id}/stream.wmv", "HEAD")]
- [Route("/Videos/{Id}/stream.wtv", "HEAD")]
- [Route("/Videos/{Id}/stream.m2ts", "HEAD")]
- [Route("/Videos/{Id}/stream.mov", "HEAD")]
- [Route("/Videos/{Id}/stream.iso", "HEAD")]
- [Route("/Videos/{Id}/stream.flv", "HEAD")]
- [Route("/Videos/{Id}/stream", "HEAD")]
- public class GetVideoStream : VideoStreamRequest
- {
-
- }
-
- /// <summary>
- /// Class VideoService
- /// </summary>
- // TODO: In order to autheneticate this in the future, Dlna playback will require updating
- //[Authenticated]
- public class VideoService : BaseProgressiveStreamingService
- {
- public VideoService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, IDlnaManager dlnaManager, ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager, IMediaSourceManager mediaSourceManager, IZipClient zipClient, IJsonSerializer jsonSerializer, IAuthorizationContext authorizationContext, IImageProcessor imageProcessor, IEnvironmentInfo environmentInfo) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, fileSystem, dlnaManager, subtitleEncoder, deviceManager, mediaSourceManager, zipClient, jsonSerializer, authorizationContext, imageProcessor, environmentInfo)
- {
- }
-
- /// <summary>
- /// Gets the specified request.
- /// </summary>
- /// <param name="request">The request.</param>
- /// <returns>System.Object.</returns>
- public Task<object> Get(GetVideoStream request)
- {
- return ProcessRequest(request, false);
- }
-
- /// <summary>
- /// Heads the specified request.
- /// </summary>
- /// <param name="request">The request.</param>
- /// <returns>System.Object.</returns>
- public Task<object> Head(GetVideoStream request)
- {
- return ProcessRequest(request, true);
- }
-
- protected override string GetCommandLineArguments(string outputPath, EncodingOptions encodingOptions, StreamState state, bool isEncoding)
- {
- return EncodingHelper.GetProgressiveVideoFullCommandLine(state, encodingOptions, outputPath, GetDefaultH264Preset());
- }
- }
-} \ No newline at end of file
diff --git a/MediaBrowser.Api/Playback/StaticRemoteStreamWriter.cs b/MediaBrowser.Api/Playback/StaticRemoteStreamWriter.cs
deleted file mode 100644
index 6bb3b6b80..000000000
--- a/MediaBrowser.Api/Playback/StaticRemoteStreamWriter.cs
+++ /dev/null
@@ -1,47 +0,0 @@
-using MediaBrowser.Common.Net;
-using System.Collections.Generic;
-using System.IO;
-using System.Threading;
-using System.Threading.Tasks;
-using MediaBrowser.Model.Services;
-
-namespace MediaBrowser.Api.Playback
-{
- /// <summary>
- /// Class StaticRemoteStreamWriter
- /// </summary>
- public class StaticRemoteStreamWriter : IAsyncStreamWriter, IHasHeaders
- {
- /// <summary>
- /// The _input stream
- /// </summary>
- private readonly HttpResponseInfo _response;
-
- /// <summary>
- /// The _options
- /// </summary>
- private readonly IDictionary<string, string> _options = new Dictionary<string, string>();
-
- public StaticRemoteStreamWriter(HttpResponseInfo response)
- {
- _response = response;
- }
-
- /// <summary>
- /// Gets the options.
- /// </summary>
- /// <value>The options.</value>
- public IDictionary<string, string> Headers
- {
- get { return _options; }
- }
-
- public async Task WriteToAsync(Stream responseStream, CancellationToken cancellationToken)
- {
- using (_response)
- {
- await _response.Content.CopyToAsync(responseStream, 81920, cancellationToken).ConfigureAwait(false);
- }
- }
- }
-}
diff --git a/MediaBrowser.Api/Playback/StreamRequest.cs b/MediaBrowser.Api/Playback/StreamRequest.cs
deleted file mode 100644
index 176b68f37..000000000
--- a/MediaBrowser.Api/Playback/StreamRequest.cs
+++ /dev/null
@@ -1,63 +0,0 @@
-using MediaBrowser.Controller.MediaEncoding;
-using MediaBrowser.Model.Dlna;
-using MediaBrowser.Model.Services;
-
-namespace MediaBrowser.Api.Playback
-{
- /// <summary>
- /// Class StreamRequest
- /// </summary>
- public class StreamRequest : BaseEncodingJobOptions
- {
- /// <summary>
- /// Gets or sets the id.
- /// </summary>
- /// <value>The id.</value>
- [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
- public string Id { get; set; }
-
- [ApiMember(Name = "MediaSourceId", Description = "The media version id, if playing an alternate version", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
- public string MediaSourceId { get; set; }
-
- [ApiMember(Name = "DeviceId", Description = "The device id of the client requesting. Used to stop encoding processes when needed.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
- public string DeviceId { get; set; }
-
- [ApiMember(Name = "Container", Description = "Container", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
- public string Container { get; set; }
-
- /// <summary>
- /// Gets or sets the audio codec.
- /// </summary>
- /// <value>The audio codec.</value>
- [ApiMember(Name = "AudioCodec", Description = "Optional. Specify a audio codec to encode to, e.g. mp3. If omitted the server will auto-select using the url's extension. Options: aac, mp3, vorbis, wma.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
- public string AudioCodec { get; set; }
-
- [ApiMember(Name = "DeviceProfileId", Description = "Optional. The dlna device profile id to utilize.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
- public string DeviceProfileId { get; set; }
-
- public string Params { get; set; }
- public string PlaySessionId { get; set; }
- public string Tag { get; set; }
- public string SegmentContainer { get; set; }
-
- public int? SegmentLength { get; set; }
- public int? MinSegments { get; set; }
- }
-
- public class VideoStreamRequest : StreamRequest
- {
- /// <summary>
- /// Gets a value indicating whether this instance has fixed resolution.
- /// </summary>
- /// <value><c>true</c> if this instance has fixed resolution; otherwise, <c>false</c>.</value>
- public bool HasFixedResolution
- {
- get
- {
- return Width.HasValue || Height.HasValue;
- }
- }
-
- public bool EnableSubtitlesInManifest { get; set; }
- }
-}
diff --git a/MediaBrowser.Api/Playback/StreamState.cs b/MediaBrowser.Api/Playback/StreamState.cs
deleted file mode 100644
index eecc12432..000000000
--- a/MediaBrowser.Api/Playback/StreamState.cs
+++ /dev/null
@@ -1,259 +0,0 @@
-using MediaBrowser.Controller.Library;
-using MediaBrowser.Model.Dlna;
-using MediaBrowser.Model.Drawing;
-using MediaBrowser.Model.Dto;
-using MediaBrowser.Model.Entities;
-using MediaBrowser.Model.IO;
-using MediaBrowser.Model.Logging;
-using MediaBrowser.Model.MediaInfo;
-using MediaBrowser.Model.Net;
-using System;
-using System.Collections.Generic;
-using System.Globalization;
-using System.IO;
-using System.Linq;
-using System.Threading;
-using MediaBrowser.Controller.MediaEncoding;
-
-namespace MediaBrowser.Api.Playback
-{
- public class StreamState : EncodingJobInfo, IDisposable
- {
- private readonly ILogger _logger;
- private readonly IMediaSourceManager _mediaSourceManager;
-
- public string RequestedUrl { get; set; }
-
- public StreamRequest Request
- {
- get { return (StreamRequest)BaseRequest; }
- set
- {
- BaseRequest = value;
-
- IsVideoRequest = VideoRequest != null;
- }
- }
-
- public TranscodingThrottler TranscodingThrottler { get; set; }
-
- public VideoStreamRequest VideoRequest
- {
- get { return Request as VideoStreamRequest; }
- }
-
- /// <summary>
- /// Gets or sets the log file stream.
- /// </summary>
- /// <value>The log file stream.</value>
- public Stream LogFileStream { get; set; }
- public IDirectStreamProvider DirectStreamProvider { get; set; }
-
- public string WaitForPath { get; set; }
-
- public bool IsOutputVideo
- {
- get { return Request is VideoStreamRequest; }
- }
-
- public int SegmentLength
- {
- get
- {
- if (Request.SegmentLength.HasValue)
- {
- return Request.SegmentLength.Value;
- }
-
- if (string.Equals(OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase))
- {
- var userAgent = UserAgent ?? string.Empty;
-
- if (userAgent.IndexOf("AppleTV", StringComparison.OrdinalIgnoreCase) != -1 ||
- userAgent.IndexOf("cfnetwork", StringComparison.OrdinalIgnoreCase) != -1 ||
- userAgent.IndexOf("ipad", StringComparison.OrdinalIgnoreCase) != -1 ||
- userAgent.IndexOf("iphone", StringComparison.OrdinalIgnoreCase) != -1 ||
- userAgent.IndexOf("ipod", StringComparison.OrdinalIgnoreCase) != -1)
- {
- if (IsSegmentedLiveStream)
- {
- return 6;
- }
-
- return 6;
- }
-
- if (IsSegmentedLiveStream)
- {
- return 3;
- }
- return 6;
- }
-
- return 3;
- }
- }
-
- public int MinSegments
- {
- get
- {
- if (Request.MinSegments.HasValue)
- {
- return Request.MinSegments.Value;
- }
-
- return SegmentLength >= 10 ? 2 : 3;
- }
- }
-
- public int HlsListSize
- {
- get
- {
- return 0;
- }
- }
-
- public string UserAgent { get; set; }
-
- public StreamState(IMediaSourceManager mediaSourceManager, ILogger logger, TranscodingJobType transcodingType)
- : base(logger, transcodingType)
- {
- _mediaSourceManager = mediaSourceManager;
- _logger = logger;
- }
-
- public string MimeType { get; set; }
-
- public bool EstimateContentLength { get; set; }
- public TranscodeSeekInfo TranscodeSeekInfo { get; set; }
-
- public long? EncodingDurationTicks { get; set; }
-
- public string GetMimeType(string outputPath)
- {
- if (!string.IsNullOrEmpty(MimeType))
- {
- return MimeType;
- }
-
- return MimeTypes.GetMimeType(outputPath);
- }
-
- public bool EnableDlnaHeaders { get; set; }
-
- public void Dispose()
- {
- DisposeTranscodingThrottler();
- DisposeLiveStream();
- DisposeLogStream();
- DisposeIsoMount();
-
- TranscodingJob = null;
- }
-
- private void DisposeLogStream()
- {
- if (LogFileStream != null)
- {
- try
- {
- LogFileStream.Dispose();
- }
- catch (Exception ex)
- {
- _logger.ErrorException("Error disposing log stream", ex);
- }
-
- LogFileStream = null;
- }
- }
-
- private void DisposeTranscodingThrottler()
- {
- if (TranscodingThrottler != null)
- {
- try
- {
- TranscodingThrottler.Dispose();
- }
- catch (Exception ex)
- {
- _logger.ErrorException("Error disposing TranscodingThrottler", ex);
- }
-
- TranscodingThrottler = null;
- }
- }
-
- private async void DisposeLiveStream()
- {
- if (MediaSource.RequiresClosing && string.IsNullOrWhiteSpace(Request.LiveStreamId) && !string.IsNullOrWhiteSpace(MediaSource.LiveStreamId))
- {
- try
- {
- await _mediaSourceManager.CloseLiveStream(MediaSource.LiveStreamId).ConfigureAwait(false);
- }
- catch (Exception ex)
- {
- _logger.ErrorException("Error closing media source", ex);
- }
- }
- }
-
- public string OutputFilePath { get; set; }
-
- public string ActualOutputVideoCodec
- {
- get
- {
- var codec = OutputVideoCodec;
-
- if (string.Equals(codec, "copy", StringComparison.OrdinalIgnoreCase))
- {
- var stream = VideoStream;
-
- if (stream != null)
- {
- return stream.Codec;
- }
-
- return null;
- }
-
- return codec;
- }
- }
-
- public string ActualOutputAudioCodec
- {
- get
- {
- var codec = OutputAudioCodec;
-
- if (string.Equals(codec, "copy", StringComparison.OrdinalIgnoreCase))
- {
- var stream = AudioStream;
-
- if (stream != null)
- {
- return stream.Codec;
- }
-
- return null;
- }
-
- return codec;
- }
- }
-
- public DeviceProfile DeviceProfile { get; set; }
-
- public TranscodingJob TranscodingJob;
- public override void ReportTranscodingProgress(TimeSpan? transcodingPosition, float? framerate, double? percentComplete, long? bytesTranscoded, int? bitRate)
- {
- ApiEntryPoint.Instance.ReportTranscodingProgress(TranscodingJob, this, transcodingPosition, framerate, percentComplete, bytesTranscoded, bitRate);
- }
- }
-}
diff --git a/MediaBrowser.Api/Playback/TranscodingThrottler.cs b/MediaBrowser.Api/Playback/TranscodingThrottler.cs
deleted file mode 100644
index c42d0c3e4..000000000
--- a/MediaBrowser.Api/Playback/TranscodingThrottler.cs
+++ /dev/null
@@ -1,176 +0,0 @@
-using MediaBrowser.Common.Configuration;
-using MediaBrowser.Model.Configuration;
-using MediaBrowser.Model.Logging;
-using System;
-using MediaBrowser.Model.IO;
-using MediaBrowser.Model.Threading;
-
-namespace MediaBrowser.Api.Playback
-{
- public class TranscodingThrottler : IDisposable
- {
- private readonly TranscodingJob _job;
- private readonly ILogger _logger;
- private ITimer _timer;
- private bool _isPaused;
- private readonly IConfigurationManager _config;
- private readonly ITimerFactory _timerFactory;
- private readonly IFileSystem _fileSystem;
-
- public TranscodingThrottler(TranscodingJob job, ILogger logger, IConfigurationManager config, ITimerFactory timerFactory, IFileSystem fileSystem)
- {
- _job = job;
- _logger = logger;
- _config = config;
- _timerFactory = timerFactory;
- _fileSystem = fileSystem;
- }
-
- private EncodingOptions GetOptions()
- {
- return _config.GetConfiguration<EncodingOptions>("encoding");
- }
-
- public void Start()
- {
- _timer = _timerFactory.Create(TimerCallback, null, 5000, 5000);
- }
-
- private void TimerCallback(object state)
- {
- if (_job.HasExited)
- {
- DisposeTimer();
- return;
- }
-
- var options = GetOptions();
-
- if (options.EnableThrottling && IsThrottleAllowed(_job, options.ThrottleDelaySeconds))
- {
- PauseTranscoding();
- }
- else
- {
- UnpauseTranscoding();
- }
- }
-
- private void PauseTranscoding()
- {
- if (!_isPaused)
- {
- _logger.Debug("Sending pause command to ffmpeg");
-
- try
- {
- _job.Process.StandardInput.Write("c");
- _isPaused = true;
- }
- catch (Exception ex)
- {
- _logger.ErrorException("Error pausing transcoding", ex);
- }
- }
- }
-
- public void UnpauseTranscoding()
- {
- if (_isPaused)
- {
- _logger.Debug("Sending unpause command to ffmpeg");
-
- try
- {
- _job.Process.StandardInput.WriteLine();
- _isPaused = false;
- }
- catch (Exception ex)
- {
- _logger.ErrorException("Error unpausing transcoding", ex);
- }
- }
- }
-
- private bool IsThrottleAllowed(TranscodingJob job, int thresholdSeconds)
- {
- var bytesDownloaded = job.BytesDownloaded ?? 0;
- var transcodingPositionTicks = job.TranscodingPositionTicks ?? 0;
- var downloadPositionTicks = job.DownloadPositionTicks ?? 0;
-
- var path = job.Path;
- var gapLengthInTicks = TimeSpan.FromSeconds(thresholdSeconds).Ticks;
-
- if (downloadPositionTicks > 0 && transcodingPositionTicks > 0)
- {
- // HLS - time-based consideration
-
- var targetGap = gapLengthInTicks;
- var gap = transcodingPositionTicks - downloadPositionTicks;
-
- if (gap < targetGap)
- {
- //_logger.Debug("Not throttling transcoder gap {0} target gap {1}", gap, targetGap);
- return false;
- }
-
- //_logger.Debug("Throttling transcoder gap {0} target gap {1}", gap, targetGap);
- return true;
- }
-
- if (bytesDownloaded > 0 && transcodingPositionTicks > 0)
- {
- // Progressive Streaming - byte-based consideration
-
- try
- {
- var bytesTranscoded = job.BytesTranscoded ?? _fileSystem.GetFileInfo(path).Length;
-
- // Estimate the bytes the transcoder should be ahead
- double gapFactor = gapLengthInTicks;
- gapFactor /= transcodingPositionTicks;
- var targetGap = bytesTranscoded * gapFactor;
-
- var gap = bytesTranscoded - bytesDownloaded;
-
- if (gap < targetGap)
- {
- //_logger.Debug("Not throttling transcoder gap {0} target gap {1} bytes downloaded {2}", gap, targetGap, bytesDownloaded);
- return false;
- }
-
- //_logger.Debug("Throttling transcoder gap {0} target gap {1} bytes downloaded {2}", gap, targetGap, bytesDownloaded);
- return true;
- }
- catch
- {
- //_logger.Error("Error getting output size");
- return false;
- }
- }
-
- //_logger.Debug("No throttle data for " + path);
- return false;
- }
-
- public void Stop()
- {
- DisposeTimer();
- UnpauseTranscoding();
- }
-
- public void Dispose()
- {
- DisposeTimer();
- }
-
- private void DisposeTimer()
- {
- if (_timer != null)
- {
- _timer.Dispose();
- _timer = null;
- }
- }
- }
-}
diff --git a/MediaBrowser.Api/Playback/UniversalAudioService.cs b/MediaBrowser.Api/Playback/UniversalAudioService.cs
deleted file mode 100644
index 118bf5246..000000000
--- a/MediaBrowser.Api/Playback/UniversalAudioService.cs
+++ /dev/null
@@ -1,339 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Globalization;
-using System.Linq;
-using System.Threading.Tasks;
-using MediaBrowser.Api.Playback.Hls;
-using MediaBrowser.Api.Playback.Progressive;
-using MediaBrowser.Common.Net;
-using MediaBrowser.Controller.Configuration;
-using MediaBrowser.Controller.Devices;
-using MediaBrowser.Controller.Dlna;
-using MediaBrowser.Controller.Drawing;
-using MediaBrowser.Controller.Library;
-using MediaBrowser.Controller.MediaEncoding;
-using MediaBrowser.Controller.Net;
-using MediaBrowser.Model.Dlna;
-using MediaBrowser.Model.IO;
-using MediaBrowser.Model.MediaInfo;
-using MediaBrowser.Model.Serialization;
-using MediaBrowser.Model.Services;
-using MediaBrowser.Model.System;
-
-namespace MediaBrowser.Api.Playback
-{
- public class BaseUniversalRequest
- {
- /// <summary>
- /// Gets or sets the id.
- /// </summary>
- /// <value>The id.</value>
- [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
- public string Id { get; set; }
-
- [ApiMember(Name = "MediaSourceId", Description = "The media version id, if playing an alternate version", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
- public string MediaSourceId { get; set; }
-
- [ApiMember(Name = "DeviceId", Description = "The device id of the client requesting. Used to stop encoding processes when needed.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
- public string DeviceId { get; set; }
-
- public string UserId { get; set; }
- public string AudioCodec { get; set; }
- public string Container { get; set; }
-
- public int? MaxAudioChannels { get; set; }
- public int? TranscodingAudioChannels { get; set; }
-
- public long? MaxStreamingBitrate { get; set; }
-
- [ApiMember(Name = "StartTimeTicks", Description = "Optional. Specify a starting offset, in ticks. 1 tick = 10000 ms", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
- public long? StartTimeTicks { get; set; }
-
- public string TranscodingContainer { get; set; }
- public string TranscodingProtocol { get; set; }
- public int? MaxAudioSampleRate { get; set; }
- public int? MaxAudioBitDepth { get; set; }
-
- public bool EnableRedirection { get; set; }
- public bool EnableRemoteMedia { get; set; }
- public bool BreakOnNonKeyFrames { get; set; }
-
- public BaseUniversalRequest()
- {
- EnableRedirection = true;
- }
- }
-
- [Route("/Audio/{Id}/universal.{Container}", "GET", Summary = "Gets an audio stream")]
- [Route("/Audio/{Id}/universal", "GET", Summary = "Gets an audio stream")]
- [Route("/Audio/{Id}/universal.{Container}", "HEAD", Summary = "Gets an audio stream")]
- [Route("/Audio/{Id}/universal", "HEAD", Summary = "Gets an audio stream")]
- public class GetUniversalAudioStream : BaseUniversalRequest
- {
- }
-
- [Authenticated]
- public class UniversalAudioService : BaseApiService
- {
- public UniversalAudioService(IServerConfigurationManager serverConfigurationManager, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, IDlnaManager dlnaManager, IDeviceManager deviceManager, ISubtitleEncoder subtitleEncoder, IMediaSourceManager mediaSourceManager, IZipClient zipClient, IJsonSerializer jsonSerializer, IAuthorizationContext authorizationContext, IImageProcessor imageProcessor, INetworkManager networkManager, IEnvironmentInfo environmentInfo)
- {
- ServerConfigurationManager = serverConfigurationManager;
- UserManager = userManager;
- LibraryManager = libraryManager;
- IsoManager = isoManager;
- MediaEncoder = mediaEncoder;
- FileSystem = fileSystem;
- DlnaManager = dlnaManager;
- DeviceManager = deviceManager;
- SubtitleEncoder = subtitleEncoder;
- MediaSourceManager = mediaSourceManager;
- ZipClient = zipClient;
- JsonSerializer = jsonSerializer;
- AuthorizationContext = authorizationContext;
- ImageProcessor = imageProcessor;
- NetworkManager = networkManager;
- EnvironmentInfo = environmentInfo;
- }
-
- protected IServerConfigurationManager ServerConfigurationManager { get; private set; }
- protected IUserManager UserManager { get; private set; }
- protected ILibraryManager LibraryManager { get; private set; }
- protected IIsoManager IsoManager { get; private set; }
- protected IMediaEncoder MediaEncoder { get; private set; }
- protected IFileSystem FileSystem { get; private set; }
- protected IDlnaManager DlnaManager { get; private set; }
- protected IDeviceManager DeviceManager { get; private set; }
- protected ISubtitleEncoder SubtitleEncoder { get; private set; }
- protected IMediaSourceManager MediaSourceManager { get; private set; }
- protected IZipClient ZipClient { get; private set; }
- protected IJsonSerializer JsonSerializer { get; private set; }
- protected IAuthorizationContext AuthorizationContext { get; private set; }
- protected IImageProcessor ImageProcessor { get; private set; }
- protected INetworkManager NetworkManager { get; private set; }
- protected IEnvironmentInfo EnvironmentInfo { get; private set; }
-
- public Task<object> Get(GetUniversalAudioStream request)
- {
- return GetUniversalStream(request, false);
- }
-
- public Task<object> Head(GetUniversalAudioStream request)
- {
- return GetUniversalStream(request, true);
- }
-
- private DeviceProfile GetDeviceProfile(GetUniversalAudioStream request)
- {
- var deviceProfile = new DeviceProfile();
-
- var directPlayProfiles = new List<DirectPlayProfile>();
-
- directPlayProfiles.Add(new DirectPlayProfile
- {
- Type = DlnaProfileType.Audio,
- Container = request.Container
- });
-
- deviceProfile.DirectPlayProfiles = directPlayProfiles.ToArray();
-
- deviceProfile.TranscodingProfiles = new[]
- {
- new TranscodingProfile
- {
- Type = DlnaProfileType.Audio,
- Context = EncodingContext.Streaming,
- Container = request.TranscodingContainer,
- AudioCodec = request.AudioCodec,
- Protocol = request.TranscodingProtocol,
- BreakOnNonKeyFrames = request.BreakOnNonKeyFrames,
- MaxAudioChannels = request.TranscodingAudioChannels.HasValue ? request.TranscodingAudioChannels.Value.ToString(CultureInfo.InvariantCulture) : null
- }
- };
-
- var codecProfiles = new List<CodecProfile>();
- var conditions = new List<ProfileCondition>();
-
- if (request.MaxAudioSampleRate.HasValue)
- {
- // codec profile
- conditions.Add(new ProfileCondition
- {
- Condition = ProfileConditionType.LessThanEqual,
- IsRequired = false,
- Property = ProfileConditionValue.AudioSampleRate,
- Value = request.MaxAudioSampleRate.Value.ToString(CultureInfo.InvariantCulture)
- });
- }
-
- if (request.MaxAudioBitDepth.HasValue)
- {
- // codec profile
- conditions.Add(new ProfileCondition
- {
- Condition = ProfileConditionType.LessThanEqual,
- IsRequired = false,
- Property = ProfileConditionValue.AudioBitDepth,
- Value = request.MaxAudioBitDepth.Value.ToString(CultureInfo.InvariantCulture)
- });
- }
-
- if (request.MaxAudioChannels.HasValue)
- {
- // codec profile
- conditions.Add(new ProfileCondition
- {
- Condition = ProfileConditionType.LessThanEqual,
- IsRequired = false,
- Property = ProfileConditionValue.AudioChannels,
- Value = request.MaxAudioChannels.Value.ToString(CultureInfo.InvariantCulture)
- });
- }
-
- if (conditions.Count > 0)
- {
- // codec profile
- codecProfiles.Add(new CodecProfile
- {
- Type = CodecType.Audio,
- Container = request.Container,
- Conditions = conditions.ToArray()
- });
- }
-
- deviceProfile.CodecProfiles = codecProfiles.ToArray();
-
- return deviceProfile;
- }
-
- private async Task<object> GetUniversalStream(GetUniversalAudioStream request, bool isHeadRequest)
- {
- var deviceProfile = GetDeviceProfile(request);
-
- AuthorizationContext.GetAuthorizationInfo(Request).DeviceId = request.DeviceId;
-
- var mediaInfoService = new MediaInfoService(MediaSourceManager, DeviceManager, LibraryManager, ServerConfigurationManager, NetworkManager, MediaEncoder, UserManager, JsonSerializer, AuthorizationContext)
- {
- Request = Request
- };
-
- var playbackInfoResult = await mediaInfoService.GetPlaybackInfo(new GetPostedPlaybackInfo
- {
- Id = request.Id,
- MaxAudioChannels = request.MaxAudioChannels,
- MaxStreamingBitrate = request.MaxStreamingBitrate,
- StartTimeTicks = request.StartTimeTicks,
- UserId = request.UserId,
- DeviceProfile = deviceProfile,
- MediaSourceId = request.MediaSourceId
-
- }).ConfigureAwait(false);
-
- var mediaSource = playbackInfoResult.MediaSources[0];
-
- if (mediaSource.SupportsDirectPlay && mediaSource.Protocol == MediaProtocol.Http)
- {
- if (request.EnableRedirection)
- {
- if (mediaSource.IsRemote && request.EnableRemoteMedia)
- {
- return ResultFactory.GetRedirectResult(mediaSource.Path);
- }
- }
- }
-
- var isStatic = mediaSource.SupportsDirectStream;
-
- if (!isStatic && string.Equals(mediaSource.TranscodingSubProtocol, "hls", StringComparison.OrdinalIgnoreCase))
- {
- var service = new DynamicHlsService(ServerConfigurationManager,
- UserManager,
- LibraryManager,
- IsoManager,
- MediaEncoder,
- FileSystem,
- DlnaManager,
- SubtitleEncoder,
- DeviceManager,
- MediaSourceManager,
- ZipClient,
- JsonSerializer,
- AuthorizationContext,
- NetworkManager)
- {
- Request = Request
- };
-
- var transcodingProfile = deviceProfile.TranscodingProfiles[0];
-
- var newRequest = new GetMasterHlsAudioPlaylist
- {
- AudioBitRate = isStatic ? (int?)null : Convert.ToInt32(Math.Min(request.MaxStreamingBitrate ?? 192000, int.MaxValue)),
- AudioCodec = transcodingProfile.AudioCodec,
- Container = ".m3u8",
- DeviceId = request.DeviceId,
- Id = request.Id,
- MaxAudioChannels = request.MaxAudioChannels,
- MediaSourceId = mediaSource.Id,
- PlaySessionId = playbackInfoResult.PlaySessionId,
- StartTimeTicks = request.StartTimeTicks,
- Static = isStatic,
- SegmentContainer = request.TranscodingContainer,
- AudioSampleRate = request.MaxAudioSampleRate,
- MaxAudioBitDepth = request.MaxAudioBitDepth,
- BreakOnNonKeyFrames = transcodingProfile.BreakOnNonKeyFrames,
- TranscodeReasons = mediaSource.TranscodeReasons == null ? null : string.Join(",", mediaSource.TranscodeReasons.Select(i => i.ToString()).ToArray())
- };
-
- if (isHeadRequest)
- {
- return await service.Head(newRequest).ConfigureAwait(false);
- }
- return await service.Get(newRequest).ConfigureAwait(false);
- }
- else
- {
- var service = new AudioService(ServerConfigurationManager,
- UserManager,
- LibraryManager,
- IsoManager,
- MediaEncoder,
- FileSystem,
- DlnaManager,
- SubtitleEncoder,
- DeviceManager,
- MediaSourceManager,
- ZipClient,
- JsonSerializer,
- AuthorizationContext,
- ImageProcessor,
- EnvironmentInfo)
- {
- Request = Request
- };
-
- var newRequest = new GetAudioStream
- {
- AudioBitRate = isStatic ? (int?)null : Convert.ToInt32(Math.Min(request.MaxStreamingBitrate ?? 192000, int.MaxValue)),
- AudioCodec = request.AudioCodec,
- Container = isStatic ? null : ("." + mediaSource.TranscodingContainer),
- DeviceId = request.DeviceId,
- Id = request.Id,
- MaxAudioChannels = request.MaxAudioChannels,
- MediaSourceId = mediaSource.Id,
- PlaySessionId = playbackInfoResult.PlaySessionId,
- StartTimeTicks = request.StartTimeTicks,
- Static = isStatic,
- AudioSampleRate = request.MaxAudioSampleRate,
- MaxAudioBitDepth = request.MaxAudioBitDepth,
- TranscodeReasons = mediaSource.TranscodeReasons == null ? null : string.Join(",", mediaSource.TranscodeReasons.Select(i => i.ToString()).ToArray())
- };
-
- if (isHeadRequest)
- {
- return await service.Head(newRequest).ConfigureAwait(false);
- }
- return await service.Get(newRequest).ConfigureAwait(false);
- }
- }
- }
-}
diff --git a/MediaBrowser.Api/PlaylistService.cs b/MediaBrowser.Api/PlaylistService.cs
index 9f37bb70a..226678021 100644
--- a/MediaBrowser.Api/PlaylistService.cs
+++ b/MediaBrowser.Api/PlaylistService.cs
@@ -1,13 +1,14 @@
-using MediaBrowser.Controller.Dto;
+using System.Linq;
+using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Net;
using MediaBrowser.Controller.Playlists;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Playlists;
using MediaBrowser.Model.Querying;
-using System.Linq;
using System.Threading.Tasks;
using MediaBrowser.Model.Services;
+using MediaBrowser.Model.Extensions;
namespace MediaBrowser.Api
{
@@ -148,7 +149,7 @@ namespace MediaBrowser.Api
var result = await _playlistManager.CreatePlaylist(new PlaylistCreationRequest
{
Name = request.Name,
- ItemIdList = (request.Ids ?? string.Empty).Split(',').Where(i => !string.IsNullOrWhiteSpace(i)).ToList(),
+ ItemIdList = SplitValue(request.Ids, ','),
UserId = request.UserId,
MediaType = request.MediaType
@@ -171,7 +172,7 @@ namespace MediaBrowser.Api
Task.WaitAll(task);
}
- public async Task<object> Get(GetPlaylistItems request)
+ public object Get(GetPlaylistItems request)
{
var playlist = (Playlist)_libraryManager.GetItemById(request.Id);
var user = !string.IsNullOrWhiteSpace(request.UserId) ? _userManager.GetUserById(request.UserId) : null;
@@ -192,8 +193,7 @@ namespace MediaBrowser.Api
var dtoOptions = GetDtoOptions(_authContext, request);
- var dtos = (await _dtoService.GetBaseItemDtos(items.Select(i => i.Item2), dtoOptions, user).ConfigureAwait(false))
- .ToArray();
+ var dtos = _dtoService.GetBaseItemDtos(items.Select(i => i.Item2).ToList(), dtoOptions, user);
var index = 0;
foreach (var item in dtos)
@@ -202,7 +202,7 @@ namespace MediaBrowser.Api
index++;
}
- var result = new ItemsResult
+ var result = new QueryResult<BaseItemDto>
{
Items = dtos,
TotalRecordCount = count
diff --git a/MediaBrowser.Api/PluginService.cs b/MediaBrowser.Api/PluginService.cs
index eb95224b7..f6efe15e6 100644
--- a/MediaBrowser.Api/PluginService.cs
+++ b/MediaBrowser.Api/PluginService.cs
@@ -23,7 +23,7 @@ namespace MediaBrowser.Api
/// </summary>
[Route("/Plugins", "GET", Summary = "Gets a list of currently installed plugins")]
[Authenticated]
- public class GetPlugins : IReturn<List<PluginInfo>>
+ public class GetPlugins : IReturn<PluginInfo[]>
{
public bool? IsAppStoreEnabled { get; set; }
}
@@ -195,14 +195,13 @@ namespace MediaBrowser.Api
/// <returns>System.Object.</returns>
public async Task<object> Get(GetPlugins request)
{
- var result = _appHost.Plugins.OrderBy(p => p.Name).Select(p => p.GetPluginInfo()).ToList();
+ var result = _appHost.Plugins.OrderBy(p => p.Name).Select(p => p.GetPluginInfo()).ToArray();
var requireAppStoreEnabled = request.IsAppStoreEnabled.HasValue && request.IsAppStoreEnabled.Value;
// Don't fail just on account of image url's
try
{
- var packages = (await _installationManager.GetAvailablePackagesWithoutRegistrationInfo(CancellationToken.None))
- .ToList();
+ var packages = (await _installationManager.GetAvailablePackagesWithoutRegistrationInfo(CancellationToken.None));
foreach (var plugin in result)
{
@@ -223,7 +222,7 @@ namespace MediaBrowser.Api
return pkg != null && pkg.enableInAppStore;
})
- .ToList();
+ .ToArray();
}
}
catch
@@ -232,7 +231,7 @@ namespace MediaBrowser.Api
// Play it safe here
if (requireAppStoreEnabled)
{
- result = new List<PluginInfo>();
+ result = new PluginInfo[] { };
}
}
diff --git a/MediaBrowser.Api/Reports/Data/ReportBuilder.cs b/MediaBrowser.Api/Reports/Data/ReportBuilder.cs
index 9c3dde6a4..6a1502c7e 100644
--- a/MediaBrowser.Api/Reports/Data/ReportBuilder.cs
+++ b/MediaBrowser.Api/Reports/Data/ReportBuilder.cs
@@ -458,7 +458,7 @@ namespace MediaBrowser.Api.Reports
break;
case HeaderMetadata.Network:
- option.Column = (i, r) => this.GetListAsString(i.Studios);
+ option.Column = (i, r) => this.GetListAsString(i.Studios.ToList());
option.ItemID = (i) => this.GetStudioID(i.Studios.FirstOrDefault());
option.Header.ItemViewType = ItemViewType.ItemByNameDetails;
option.Header.SortField = "Studio,SortName";
@@ -513,7 +513,7 @@ namespace MediaBrowser.Api.Reports
internalHeader = HeaderMetadata.AlbumArtist;
break;
case HeaderMetadata.AudioAlbumArtist:
- option.Column = (i, r) => this.GetListAsString(this.GetObject<Audio, List<string>>(i, (x) => x.AlbumArtists));
+ option.Column = (i, r) => this.GetListAsString(this.GetObject<Audio, List<string>>(i, (x) => x.AlbumArtists.ToList()));
option.Header.SortField = "AlbumArtist,Album,SortName";
internalHeader = HeaderMetadata.AlbumArtist;
break;
@@ -533,7 +533,7 @@ namespace MediaBrowser.Api.Reports
break;
case HeaderMetadata.Tracks:
- option.Column = (i, r) => this.GetObject<MusicAlbum, List<Audio>>(i, (x) => x.Tracks.ToList(), new List<Audio>()).Count();
+ option.Column = (i, r) => this.GetObject<MusicAlbum, List<Audio>>(i, (x) => x.Tracks.Cast<Audio>().ToList(), new List<Audio>()).Count();
break;
case HeaderMetadata.Audio:
@@ -613,7 +613,7 @@ namespace MediaBrowser.Api.Reports
HasImageTagsPrimary = item.ImageInfos != null && item.ImageInfos.Count(n => n.Type == ImageType.Primary) > 0,
HasImageTagsBackdrop = item.ImageInfos != null && item.ImageInfos.Count(n => n.Type == ImageType.Backdrop) > 0,
HasImageTagsLogo = item.ImageInfos != null && item.ImageInfos.Count(n => n.Type == ImageType.Logo) > 0,
- HasSpecials = hasSpecialFeatures != null ? hasSpecialFeatures.SpecialFeatureIds.Count > 0 : false,
+ HasSpecials = hasSpecialFeatures != null ? hasSpecialFeatures.SpecialFeatureIds.Length > 0 : false,
HasSubtitles = video != null ? video.HasSubtitles : false,
RowType = ReportHelper.GetRowType(item.GetClientTypeName())
};
diff --git a/MediaBrowser.Api/Reports/ReportsService.cs b/MediaBrowser.Api/Reports/ReportsService.cs
index d4201e73c..a100b91e6 100644
--- a/MediaBrowser.Api/Reports/ReportsService.cs
+++ b/MediaBrowser.Api/Reports/ReportsService.cs
@@ -176,14 +176,12 @@ namespace MediaBrowser.Api.Reports
IncludeItemTypes = request.GetIncludeItemTypes(),
ExcludeItemTypes = request.GetExcludeItemTypes(),
Recursive = request.Recursive,
- SortBy = request.GetOrderBy(),
- SortOrder = request.SortOrder ?? SortOrder.Ascending,
+ OrderBy = request.GetOrderBy(),
IsFavorite = request.IsFavorite,
Limit = request.Limit,
StartIndex = request.StartIndex,
IsMissing = request.IsMissing,
- IsVirtualUnaired = request.IsVirtualUnaired,
IsUnaired = request.IsUnaired,
CollapseBoxSetItems = request.CollapseBoxSetItems,
NameLessThan = request.NameLessThan,
@@ -283,12 +281,6 @@ namespace MediaBrowser.Api.Reports
query.SeriesStatuses = request.SeriesStatus.Split(',').Select(d => (SeriesStatus)Enum.Parse(typeof(SeriesStatus), d, true)).ToArray();
}
- // Filter by Series AirDays
- if (!string.IsNullOrEmpty(request.AirDays))
- {
- query.AirDays = request.AirDays.Split(',').Select(d => (DayOfWeek)Enum.Parse(typeof(DayOfWeek), d, true)).ToArray();
- }
-
// ExcludeLocationTypes
if (!string.IsNullOrEmpty(request.ExcludeLocationTypes))
{
diff --git a/MediaBrowser.Api/ScheduledTasks/ScheduledTaskService.cs b/MediaBrowser.Api/ScheduledTasks/ScheduledTaskService.cs
index e8ad9ea95..abe3e5407 100644
--- a/MediaBrowser.Api/ScheduledTasks/ScheduledTaskService.cs
+++ b/MediaBrowser.Api/ScheduledTasks/ScheduledTaskService.cs
@@ -27,7 +27,7 @@ namespace MediaBrowser.Api.ScheduledTasks
/// Class GetScheduledTasks
/// </summary>
[Route("/ScheduledTasks", "GET", Summary = "Gets scheduled tasks")]
- public class GetScheduledTasks : IReturn<List<TaskInfo>>
+ public class GetScheduledTasks : IReturn<TaskInfo[]>
{
[ApiMember(Name = "IsHidden", Description = "Optional filter tasks that are hidden, or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
public bool? IsHidden { get; set; }
@@ -158,7 +158,7 @@ namespace MediaBrowser.Api.ScheduledTasks
var infos = result
.Select(ScheduledTaskHelpers.GetTaskInfo)
- .ToList();
+ .ToArray();
return ToOptimizedResult(infos);
}
diff --git a/MediaBrowser.Api/SearchService.cs b/MediaBrowser.Api/SearchService.cs
index 80a703313..ad79ea57b 100644
--- a/MediaBrowser.Api/SearchService.cs
+++ b/MediaBrowser.Api/SearchService.cs
@@ -1,4 +1,5 @@
-using MediaBrowser.Controller.Drawing;
+using System.Linq;
+using MediaBrowser.Controller.Drawing;
using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Audio;
@@ -7,7 +8,6 @@ using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Net;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Search;
-using System.Linq;
using System.Threading.Tasks;
using MediaBrowser.Controller.LiveTv;
using MediaBrowser.Model.Services;
@@ -159,9 +159,9 @@ namespace MediaBrowser.Api
IncludeStudios = request.IncludeStudios,
StartIndex = request.StartIndex,
UserId = request.UserId,
- IncludeItemTypes = (request.IncludeItemTypes ?? string.Empty).Split(',').Where(i => !string.IsNullOrWhiteSpace(i)).ToArray(),
- ExcludeItemTypes = (request.ExcludeItemTypes ?? string.Empty).Split(',').Where(i => !string.IsNullOrWhiteSpace(i)).ToArray(),
- MediaTypes = (request.MediaTypes ?? string.Empty).Split(',').Where(i => !string.IsNullOrWhiteSpace(i)).ToArray(),
+ IncludeItemTypes = ApiEntryPoint.Split(request.IncludeItemTypes, ',', true),
+ ExcludeItemTypes = ApiEntryPoint.Split(request.ExcludeItemTypes, ',', true),
+ MediaTypes = ApiEntryPoint.Split(request.MediaTypes, ',', true),
ParentId = request.ParentId,
IsKids = request.IsKids,
@@ -198,7 +198,6 @@ namespace MediaBrowser.Api
Type = item.GetClientTypeName(),
MediaType = item.MediaType,
MatchedTerm = hintInfo.MatchedTerm,
- DisplayMediaType = item.DisplayMediaType,
RunTimeTicks = item.RunTimeTicks,
ProductionYear = item.ProductionYear,
ChannelId = item.ChannelId,
@@ -241,7 +240,7 @@ namespace MediaBrowser.Api
if (album != null)
{
- result.Artists = album.Artists.ToArray();
+ result.Artists = album.Artists;
result.AlbumArtist = album.AlbumArtist;
}
@@ -251,7 +250,7 @@ namespace MediaBrowser.Api
{
result.Album = song.Album;
result.AlbumArtist = song.AlbumArtists.FirstOrDefault();
- result.Artists = song.Artists.ToArray();
+ result.Artists = song.Artists;
}
if (!string.IsNullOrWhiteSpace(item.ChannelId))
diff --git a/MediaBrowser.Api/Session/SessionsService.cs b/MediaBrowser.Api/Session/SessionsService.cs
index 358d09c18..8f54b591e 100644
--- a/MediaBrowser.Api/Session/SessionsService.cs
+++ b/MediaBrowser.Api/Session/SessionsService.cs
@@ -18,7 +18,7 @@ namespace MediaBrowser.Api.Session
/// </summary>
[Route("/Sessions", "GET", Summary = "Gets a list of sessions")]
[Authenticated]
- public class GetSessions : IReturn<List<SessionInfoDto>>
+ public class GetSessions : IReturn<SessionInfoDto[]>
{
[ApiMember(Name = "ControllableByUserId", Description = "Optional. Filter by sessions that a given user is allowed to remote control.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
public string ControllableByUserId { get; set; }
@@ -313,14 +313,13 @@ namespace MediaBrowser.Api.Session
public void Delete(RevokeKey request)
{
- var task = _sessionManager.RevokeToken(request.Key);
+ _sessionManager.RevokeToken(request.Key);
- Task.WaitAll(task);
}
public void Post(CreateKey request)
{
- var task = _authRepo.Create(new AuthenticationInfo
+ _authRepo.Create(new AuthenticationInfo
{
AppName = request.App,
IsActive = true,
@@ -328,8 +327,6 @@ namespace MediaBrowser.Api.Session
DateCreated = DateTime.UtcNow
}, CancellationToken.None);
-
- Task.WaitAll(task);
}
public void Post(ReportSessionEnded request)
@@ -396,7 +393,7 @@ namespace MediaBrowser.Api.Session
});
}
- return ToOptimizedResult(result.Select(_sessionManager.GetSessionInfoDto).ToList());
+ return ToOptimizedResult(result.Select(_sessionManager.GetSessionInfoDto).ToArray());
}
public void Post(SendPlaystateCommand request)
@@ -477,7 +474,7 @@ namespace MediaBrowser.Api.Session
{
var command = new PlayRequest
{
- ItemIds = request.ItemIds.Split(',').ToArray(),
+ ItemIds = request.ItemIds.Split(','),
PlayCommand = request.PlayCommand,
StartPositionTicks = request.StartPositionTicks
@@ -532,9 +529,9 @@ namespace MediaBrowser.Api.Session
}
_sessionManager.ReportCapabilities(request.Id, new ClientCapabilities
{
- PlayableMediaTypes = (request.PlayableMediaTypes ?? string.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).ToList(),
+ PlayableMediaTypes = SplitValue(request.PlayableMediaTypes, ','),
- SupportedCommands = (request.SupportedCommands ?? string.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).ToList(),
+ SupportedCommands = SplitValue(request.SupportedCommands, ','),
SupportsMediaControl = request.SupportsMediaControl,
diff --git a/MediaBrowser.Api/SimilarItemsHelper.cs b/MediaBrowser.Api/SimilarItemsHelper.cs
index 93aad0ef0..0b5eaa476 100644
--- a/MediaBrowser.Api/SimilarItemsHelper.cs
+++ b/MediaBrowser.Api/SimilarItemsHelper.cs
@@ -11,6 +11,7 @@ using System.Linq;
using System.Threading.Tasks;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Services;
+using MediaBrowser.Model.Extensions;
namespace MediaBrowser.Api
{
@@ -29,7 +30,7 @@ namespace MediaBrowser.Api
public string ExcludeArtistIds { get; set; }
}
- public class BaseGetSimilarItems : IReturn<ItemsResult>, IHasDtoOptions
+ public class BaseGetSimilarItems : IReturn<QueryResult<BaseItemDto>>, IHasDtoOptions
{
[ApiMember(Name = "EnableImages", Description = "Optional, include image information in output", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "GET")]
public bool? EnableImages { get; set; }
@@ -70,7 +71,7 @@ namespace MediaBrowser.Api
/// </summary>
public static class SimilarItemsHelper
{
- internal static async Task<QueryResult<BaseItemDto>> GetSimilarItemsResult(DtoOptions dtoOptions, IUserManager userManager, IItemRepository itemRepository, ILibraryManager libraryManager, IUserDataManager userDataRepository, IDtoService dtoService, ILogger logger, BaseGetSimilarItemsFromItem request, Type[] includeTypes, Func<BaseItem, List<PersonInfo>, List<PersonInfo>, BaseItem, int> getSimilarityScore)
+ internal static QueryResult<BaseItemDto> GetSimilarItemsResult(DtoOptions dtoOptions, IUserManager userManager, IItemRepository itemRepository, ILibraryManager libraryManager, IUserDataManager userDataRepository, IDtoService dtoService, ILogger logger, BaseGetSimilarItemsFromItem request, Type[] includeTypes, Func<BaseItem, List<PersonInfo>, List<PersonInfo>, BaseItem, int> getSimilarityScore)
{
var user = !string.IsNullOrWhiteSpace(request.UserId) ? userManager.GetUserById(request.UserId) : null;
@@ -80,7 +81,7 @@ namespace MediaBrowser.Api
var query = new InternalItemsQuery(user)
{
- IncludeItemTypes = includeTypes.Select(i => i.Name).ToArray(),
+ IncludeItemTypes = includeTypes.Select(i => i.Name).ToArray(includeTypes.Length),
Recursive = true,
DtoOptions = dtoOptions
};
@@ -96,18 +97,18 @@ namespace MediaBrowser.Api
var items = GetSimilaritems(item, libraryManager, inputItems, getSimilarityScore)
.ToList();
- IEnumerable<BaseItem> returnItems = items;
+ List<BaseItem> returnItems = items;
if (request.Limit.HasValue)
{
- returnItems = returnItems.Take(request.Limit.Value);
+ returnItems = returnItems.Take(request.Limit.Value).ToList();
}
- var dtos = await dtoService.GetBaseItemDtos(returnItems, dtoOptions, user).ConfigureAwait(false);
+ var dtos = dtoService.GetBaseItemDtos(returnItems, dtoOptions, user);
return new QueryResult<BaseItemDto>
{
- Items = dtos.ToArray(),
+ Items = dtos,
TotalRecordCount = items.Count
};
diff --git a/MediaBrowser.Api/Social/SharingService.cs b/MediaBrowser.Api/Social/SharingService.cs
index 37941bd4a..4f10667b7 100644
--- a/MediaBrowser.Api/Social/SharingService.cs
+++ b/MediaBrowser.Api/Social/SharingService.cs
@@ -121,8 +121,7 @@ namespace MediaBrowser.Api.Social
public void Delete(DeleteShare request)
{
- var task = _sharingManager.DeleteShare(request.Id);
- Task.WaitAll(task);
+ _sharingManager.DeleteShare(request.Id);
}
public async Task<object> Get(GetShareImage request)
@@ -157,7 +156,7 @@ namespace MediaBrowser.Api.Social
}
catch
{
-
+
}
}
diff --git a/MediaBrowser.Api/StartupWizardService.cs b/MediaBrowser.Api/StartupWizardService.cs
index cd56b69bd..7a75aeb4b 100644
--- a/MediaBrowser.Api/StartupWizardService.cs
+++ b/MediaBrowser.Api/StartupWizardService.cs
@@ -95,7 +95,6 @@ namespace MediaBrowser.Api
config.EnableStandaloneMusicKeys = true;
config.EnableCaseSensitiveItemIds = true;
config.SkipDeserializationForBasicTypes = true;
- config.SkipDeserializationForAudio = true;
config.EnableLocalizedGuids = true;
config.EnableSimpleArtistDetection = true;
config.EnableNormalizedItemByNameIds = true;
@@ -126,7 +125,7 @@ namespace MediaBrowser.Api
var user = _userManager.Users.First();
user.Name = request.Name;
- await _userManager.UpdateUser(user).ConfigureAwait(false);
+ _userManager.UpdateUser(user);
var result = new UpdateStartupUserResult();
diff --git a/MediaBrowser.Api/Subtitles/SubtitleService.cs b/MediaBrowser.Api/Subtitles/SubtitleService.cs
index 645aacdec..4d4b4cb27 100644
--- a/MediaBrowser.Api/Subtitles/SubtitleService.cs
+++ b/MediaBrowser.Api/Subtitles/SubtitleService.cs
@@ -14,8 +14,6 @@ using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
-
-using MediaBrowser.Controller.IO;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.Services;
using MimeTypes = MediaBrowser.Model.Net.MimeTypes;
@@ -39,7 +37,7 @@ namespace MediaBrowser.Api.Subtitles
[Route("/Items/{Id}/RemoteSearch/Subtitles/{Language}", "GET")]
[Authenticated]
- public class SearchRemoteSubtitles : IReturn<List<RemoteSubtitleInfo>>
+ public class SearchRemoteSubtitles : IReturn<RemoteSubtitleInfo[]>
{
[ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
public string Id { get; set; }
@@ -52,7 +50,7 @@ namespace MediaBrowser.Api.Subtitles
[Route("/Items/{Id}/RemoteSearch/Subtitles/Providers", "GET")]
[Authenticated]
- public class GetSubtitleProviders : IReturn<List<SubtitleProviderInfo>>
+ public class GetSubtitleProviders : IReturn<SubtitleProviderInfo[]>
{
[ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
public string Id { get; set; }
diff --git a/MediaBrowser.Api/SuggestionsService.cs b/MediaBrowser.Api/SuggestionsService.cs
index 99411ffdc..3b918d8a2 100644
--- a/MediaBrowser.Api/SuggestionsService.cs
+++ b/MediaBrowser.Api/SuggestionsService.cs
@@ -8,6 +8,8 @@ using System;
using System.Linq;
using System.Threading.Tasks;
using MediaBrowser.Controller.Library;
+using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.Extensions;
namespace MediaBrowser.Api
{
@@ -47,21 +49,21 @@ namespace MediaBrowser.Api
_libraryManager = libraryManager;
}
- public async Task<object> Get(GetSuggestedItems request)
+ public object Get(GetSuggestedItems request)
{
- var result = await GetResultItems(request).ConfigureAwait(false);
+ var result = GetResultItems(request);
return ToOptimizedResult(result);
}
- private async Task<QueryResult<BaseItemDto>> GetResultItems(GetSuggestedItems request)
+ private QueryResult<BaseItemDto> GetResultItems(GetSuggestedItems request)
{
var user = !string.IsNullOrWhiteSpace(request.UserId) ? _userManager.GetUserById(request.UserId) : null;
var dtoOptions = GetDtoOptions(_authContext, request);
var result = GetItems(request, user, dtoOptions);
- var dtoList = await _dtoService.GetBaseItemDtos(result.Items, dtoOptions, user).ConfigureAwait(false);
+ var dtoList = _dtoService.GetBaseItemDtos(result.Items, dtoOptions, user);
if (dtoList == null)
{
@@ -71,7 +73,7 @@ namespace MediaBrowser.Api
return new QueryResult<BaseItemDto>
{
TotalRecordCount = result.TotalRecordCount,
- Items = dtoList.ToArray()
+ Items = dtoList
};
}
@@ -79,7 +81,7 @@ namespace MediaBrowser.Api
{
return _libraryManager.GetItemsResult(new InternalItemsQuery(user)
{
- SortBy = new string[] { ItemSortBy.Random },
+ OrderBy = new[] { ItemSortBy.Random }.Select(i => new Tuple<string, SortOrder>(i, SortOrder.Descending)).ToArray(),
MediaTypes = request.GetMediaTypes(),
IncludeItemTypes = request.GetIncludeItemTypes(),
IsVirtualItem = false,
diff --git a/MediaBrowser.Api/System/SystemService.cs b/MediaBrowser.Api/System/SystemService.cs
index a9cec0914..37613c1c8 100644
--- a/MediaBrowser.Api/System/SystemService.cs
+++ b/MediaBrowser.Api/System/SystemService.cs
@@ -43,7 +43,7 @@ namespace MediaBrowser.Api.System
/// Class RestartApplication
/// </summary>
[Route("/System/Restart", "POST", Summary = "Restarts the application, if needed")]
- [Authenticated(Roles = "Admin")]
+ [Authenticated(Roles = "Admin", AllowLocal = true)]
public class RestartApplication
{
}
@@ -52,15 +52,14 @@ namespace MediaBrowser.Api.System
/// This is currently not authenticated because the uninstaller needs to be able to shutdown the server.
/// </summary>
[Route("/System/Shutdown", "POST", Summary = "Shuts down the application")]
+ [Authenticated(Roles = "Admin", AllowLocal = true)]
public class ShutdownApplication
{
- // TODO: This is not currently authenticated due to uninstaller
- // Improve later
}
[Route("/System/Logs", "GET", Summary = "Gets a list of available server log files")]
[Authenticated(Roles = "Admin")]
- public class GetServerLogs : IReturn<List<LogFile>>
+ public class GetServerLogs : IReturn<LogFile[]>
{
}
@@ -118,16 +117,15 @@ namespace MediaBrowser.Api.System
public object Get(GetServerLogs request)
{
- List<FileSystemMetadata> files;
+ IEnumerable<FileSystemMetadata> files;
try
{
- files = _fileSystem.GetFiles(_appPaths.LogDirectoryPath, new[] { ".txt" }, true, false)
- .ToList();
+ files = _fileSystem.GetFiles(_appPaths.LogDirectoryPath, new[] { ".txt" }, true, false);
}
catch (IOException)
{
- files = new List<FileSystemMetadata>();
+ files = new FileSystemMetadata[] { };
}
var result = files.Select(i => new LogFile
@@ -140,7 +138,7 @@ namespace MediaBrowser.Api.System
}).OrderByDescending(i => i.DateModified)
.ThenByDescending(i => i.DateCreated)
.ThenBy(i => i.Name)
- .ToList();
+ .ToArray();
return ToOptimizedResult(result);
}
@@ -150,6 +148,12 @@ namespace MediaBrowser.Api.System
var file = _fileSystem.GetFiles(_appPaths.LogDirectoryPath)
.First(i => string.Equals(i.Name, request.Name, StringComparison.OrdinalIgnoreCase));
+ // For older files, assume fully static
+ if (file.LastWriteTimeUtc < DateTime.UtcNow.AddHours(-1))
+ {
+ return ResultFactory.GetStaticFileResult(Request, file.FullName, FileShareMode.Read);
+ }
+
return ResultFactory.GetStaticFileResult(Request, file.FullName, FileShareMode.ReadWrite);
}
diff --git a/MediaBrowser.Api/TestService.cs b/MediaBrowser.Api/TestService.cs
deleted file mode 100644
index 5340b816c..000000000
--- a/MediaBrowser.Api/TestService.cs
+++ /dev/null
@@ -1,77 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.IO;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-using MediaBrowser.Model.Services;
-
-namespace MediaBrowser.Api
-{
- [Route("/Test/String", "GET")]
- public class GetString
- {
- }
-
- [Route("/Test/OptimizedString", "GET")]
- public class GetOptimizedString
- {
- }
-
- [Route("/Test/Bytes", "GET")]
- public class GetBytes
- {
- }
-
- [Route("/Test/OptimizedBytes", "GET")]
- public class GetOptimizedBytes
- {
- }
-
- [Route("/Test/Stream", "GET")]
- public class GetStream
- {
- }
-
- [Route("/Test/OptimizedStream", "GET")]
- public class GetOptimizedStream
- {
- }
-
- [Route("/Test/BytesWithContentType", "GET")]
- public class GetBytesWithContentType
- {
- }
-
- public class TestService : BaseApiService
- {
- public object Get(GetString request)
- {
- return "Welcome to Emby!";
- }
- public object Get(GetOptimizedString request)
- {
- return ToOptimizedResult("Welcome to Emby!");
- }
- public object Get(GetBytes request)
- {
- return Encoding.UTF8.GetBytes("Welcome to Emby!");
- }
- public object Get(GetOptimizedBytes request)
- {
- return ToOptimizedResult(Encoding.UTF8.GetBytes("Welcome to Emby!"));
- }
- public object Get(GetBytesWithContentType request)
- {
- return ApiEntryPoint.Instance.ResultFactory.GetResult(Encoding.UTF8.GetBytes("Welcome to Emby!"), "text/html");
- }
- public object Get(GetStream request)
- {
- return new MemoryStream(Encoding.UTF8.GetBytes("Welcome to Emby!"));
- }
- public object Get(GetOptimizedStream request)
- {
- return ToOptimizedResult(new MemoryStream(Encoding.UTF8.GetBytes("Welcome to Emby!")));
- }
- }
-}
diff --git a/MediaBrowser.Api/TvShowsService.cs b/MediaBrowser.Api/TvShowsService.cs
index 5a6004760..fd81a9a3e 100644
--- a/MediaBrowser.Api/TvShowsService.cs
+++ b/MediaBrowser.Api/TvShowsService.cs
@@ -21,7 +21,7 @@ namespace MediaBrowser.Api
/// Class GetNextUpEpisodes
/// </summary>
[Route("/Shows/NextUp", "GET", Summary = "Gets a list of next up episodes")]
- public class GetNextUpEpisodes : IReturn<ItemsResult>, IHasDtoOptions
+ public class GetNextUpEpisodes : IReturn<QueryResult<BaseItemDto>>, IHasDtoOptions
{
/// <summary>
/// Gets or sets the user id.
@@ -81,7 +81,7 @@ namespace MediaBrowser.Api
}
[Route("/Shows/Upcoming", "GET", Summary = "Gets a list of upcoming episodes")]
- public class GetUpcomingEpisodes : IReturn<ItemsResult>, IHasDtoOptions
+ public class GetUpcomingEpisodes : IReturn<QueryResult<BaseItemDto>>, IHasDtoOptions
{
/// <summary>
/// Gets or sets the user id.
@@ -137,7 +137,7 @@ namespace MediaBrowser.Api
}
[Route("/Shows/{Id}/Episodes", "GET", Summary = "Gets episodes for a tv season")]
- public class GetEpisodes : IReturn<ItemsResult>, IHasItemFields, IHasDtoOptions
+ public class GetEpisodes : IReturn<QueryResult<BaseItemDto>>, IHasItemFields, IHasDtoOptions
{
/// <summary>
/// Gets or sets the user id.
@@ -165,9 +165,6 @@ namespace MediaBrowser.Api
[ApiMember(Name = "IsMissing", Description = "Optional filter by items that are missing episodes or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
public bool? IsMissing { get; set; }
- [ApiMember(Name = "IsVirtualUnaired", Description = "Optional filter by items that are virtual unaired episodes or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
- public bool? IsVirtualUnaired { get; set; }
-
[ApiMember(Name = "AdjacentTo", Description = "Optional. Return items that are siblings of a supplied item.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
public string AdjacentTo { get; set; }
@@ -199,10 +196,16 @@ namespace MediaBrowser.Api
[ApiMember(Name = "EnableUserData", Description = "Optional, include user data", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "GET")]
public bool? EnableUserData { get; set; }
+
+ [ApiMember(Name = "SortBy", Description = "Optional. Specify one or more sort orders, comma delimeted. Options: Album, AlbumArtist, Artist, Budget, CommunityRating, CriticRating, DateCreated, DatePlayed, PlayCount, PremiereDate, ProductionYear, SortName, Random, Revenue, Runtime", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
+ public string SortBy { get; set; }
+
+ [ApiMember(Name = "SortOrder", Description = "Sort Order - Ascending,Descending", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
+ public SortOrder? SortOrder { get; set; }
}
[Route("/Shows/{Id}/Seasons", "GET", Summary = "Gets seasons for a tv series")]
- public class GetSeasons : IReturn<ItemsResult>, IHasItemFields, IHasDtoOptions
+ public class GetSeasons : IReturn<QueryResult<BaseItemDto>>, IHasItemFields, IHasDtoOptions
{
/// <summary>
/// Gets or sets the user id.
@@ -227,9 +230,6 @@ namespace MediaBrowser.Api
[ApiMember(Name = "IsMissing", Description = "Optional filter by items that are missing episodes or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
public bool? IsMissing { get; set; }
- [ApiMember(Name = "IsVirtualUnaired", Description = "Optional filter by items that are virtual unaired episodes or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
- public bool? IsVirtualUnaired { get; set; }
-
[ApiMember(Name = "AdjacentTo", Description = "Optional. Return items that are siblings of a supplied item.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
public string AdjacentTo { get; set; }
@@ -293,14 +293,14 @@ namespace MediaBrowser.Api
/// </summary>
/// <param name="request">The request.</param>
/// <returns>System.Object.</returns>
- public async Task<object> Get(GetSimilarShows request)
+ public object Get(GetSimilarShows request)
{
- var result = await GetSimilarItemsResult(request).ConfigureAwait(false);
+ var result = GetSimilarItemsResult(request);
return ToOptimizedSerializedResultUsingCache(result);
}
- private async Task<QueryResult<BaseItemDto>> GetSimilarItemsResult(BaseGetSimilarItemsFromItem request)
+ private QueryResult<BaseItemDto> GetSimilarItemsResult(BaseGetSimilarItemsFromItem request)
{
var user = !string.IsNullOrWhiteSpace(request.UserId) ? _userManager.GetUserById(request.UserId) : null;
@@ -320,11 +320,13 @@ namespace MediaBrowser.Api
SimilarTo = item,
DtoOptions = dtoOptions
- }).ToList();
+ });
+
+ var returnList = _dtoService.GetBaseItemDtos(itemsResult, dtoOptions, user);
var result = new QueryResult<BaseItemDto>
{
- Items = (await _dtoService.GetBaseItemDtos(itemsResult, dtoOptions, user).ConfigureAwait(false)).ToArray(),
+ Items = returnList,
TotalRecordCount = itemsResult.Count
};
@@ -332,7 +334,7 @@ namespace MediaBrowser.Api
return result;
}
- public async Task<object> Get(GetUpcomingEpisodes request)
+ public object Get(GetUpcomingEpisodes request)
{
var user = _userManager.GetUserById(request.UserId);
@@ -345,8 +347,7 @@ namespace MediaBrowser.Api
var itemsResult = _libraryManager.GetItemList(new InternalItemsQuery(user)
{
IncludeItemTypes = new[] { typeof(Episode).Name },
- SortBy = new[] { "PremiereDate", "AirTime", "SortName" },
- SortOrder = SortOrder.Ascending,
+ OrderBy = new[] { ItemSortBy.PremiereDate, ItemSortBy.SortName }.Select(i => new Tuple<string, SortOrder>(i, SortOrder.Ascending)).ToArray(),
MinPremiereDate = minPremiereDate,
StartIndex = request.StartIndex,
Limit = request.Limit,
@@ -354,11 +355,11 @@ namespace MediaBrowser.Api
Recursive = true,
DtoOptions = options
- }).ToList();
+ });
- var returnItems = (await _dtoService.GetBaseItemDtos(itemsResult, options, user).ConfigureAwait(false)).ToArray();
+ var returnItems = _dtoService.GetBaseItemDtos(itemsResult, options, user);
- var result = new ItemsResult
+ var result = new QueryResult<BaseItemDto>
{
TotalRecordCount = itemsResult.Count,
Items = returnItems
@@ -372,7 +373,7 @@ namespace MediaBrowser.Api
/// </summary>
/// <param name="request">The request.</param>
/// <returns>System.Object.</returns>
- public async Task<object> Get(GetNextUpEpisodes request)
+ public object Get(GetNextUpEpisodes request)
{
var options = GetDtoOptions(_authContext, request);
@@ -388,9 +389,9 @@ namespace MediaBrowser.Api
var user = _userManager.GetUserById(request.UserId);
- var returnItems = (await _dtoService.GetBaseItemDtos(result.Items, options, user).ConfigureAwait(false)).ToArray();
+ var returnItems = _dtoService.GetBaseItemDtos(result.Items, options, user);
- return ToOptimizedSerializedResultUsingCache(new ItemsResult
+ return ToOptimizedSerializedResultUsingCache(new QueryResult<BaseItemDto>
{
TotalRecordCount = result.TotalRecordCount,
Items = returnItems
@@ -421,7 +422,7 @@ namespace MediaBrowser.Api
return items;
}
- public async Task<object> Get(GetSeasons request)
+ public object Get(GetSeasons request)
{
var user = _userManager.GetUserById(request.UserId);
@@ -432,21 +433,19 @@ namespace MediaBrowser.Api
throw new ResourceNotFoundException("Series not found");
}
- var seasons = (series.GetItems(new InternalItemsQuery(user)
+ var seasons = (series.GetItemList(new InternalItemsQuery(user)
{
IsMissing = request.IsMissing,
- IsVirtualUnaired = request.IsVirtualUnaired,
IsSpecialSeason = request.IsSpecialSeason,
AdjacentTo = request.AdjacentTo
- })).Items.OfType<Season>();
+ }));
var dtoOptions = GetDtoOptions(_authContext, request);
- var returnItems = (await _dtoService.GetBaseItemDtos(seasons, dtoOptions, user).ConfigureAwait(false))
- .ToArray();
+ var returnItems = _dtoService.GetBaseItemDtos(seasons, dtoOptions, user);
- return new ItemsResult
+ return new QueryResult<BaseItemDto>
{
TotalRecordCount = returnItems.Length,
Items = returnItems
@@ -463,11 +462,11 @@ namespace MediaBrowser.Api
return null;
}
- public async Task<object> Get(GetEpisodes request)
+ public object Get(GetEpisodes request)
{
var user = _userManager.GetUserById(request.UserId);
- IEnumerable<Episode> episodes;
+ List<BaseItem> episodes;
var dtoOptions = GetDtoOptions(_authContext, request);
@@ -495,11 +494,11 @@ namespace MediaBrowser.Api
if (season == null)
{
- episodes = new List<Episode>();
+ episodes = new List<BaseItem>();
}
else
{
- episodes = season.GetEpisodes(user, dtoOptions);
+ episodes = ((Season)season).GetEpisodes(user, dtoOptions);
}
}
else
@@ -511,46 +510,44 @@ namespace MediaBrowser.Api
throw new ResourceNotFoundException("Series not found");
}
- episodes = series.GetEpisodes(user, dtoOptions);
+ episodes = series.GetEpisodes(user, dtoOptions).ToList();
}
// Filter after the fact in case the ui doesn't want them
if (request.IsMissing.HasValue)
{
var val = request.IsMissing.Value;
- episodes = episodes.Where(i => i.IsMissingEpisode == val);
- }
-
- // Filter after the fact in case the ui doesn't want them
- if (request.IsVirtualUnaired.HasValue)
- {
- var val = request.IsVirtualUnaired.Value;
- episodes = episodes.Where(i => i.IsVirtualUnaired == val);
+ episodes = episodes.Where(i => ((Episode)i).IsMissingEpisode == val).ToList();
}
if (!string.IsNullOrWhiteSpace(request.StartItemId))
{
- episodes = episodes.SkipWhile(i => !string.Equals(i.Id.ToString("N"), request.StartItemId, StringComparison.OrdinalIgnoreCase));
+ episodes = episodes.SkipWhile(i => !string.Equals(i.Id.ToString("N"), request.StartItemId, StringComparison.OrdinalIgnoreCase)).ToList();
}
- IEnumerable<BaseItem> returnItems = episodes;
-
// This must be the last filter
if (!string.IsNullOrEmpty(request.AdjacentTo))
{
- returnItems = UserViewBuilder.FilterForAdjacency(returnItems, request.AdjacentTo);
+ episodes = UserViewBuilder.FilterForAdjacency(episodes, request.AdjacentTo).ToList();
}
- var returnList = returnItems.ToList();
+ if (string.Equals(request.SortBy, ItemSortBy.Random, StringComparison.OrdinalIgnoreCase))
+ {
+ episodes = episodes.OrderBy(i => Guid.NewGuid()).ToList();
+ }
+
+ var returnItems = episodes;
- var pagedItems = ApplyPaging(returnList, request.StartIndex, request.Limit);
+ if (request.StartIndex.HasValue || request.Limit.HasValue)
+ {
+ returnItems = ApplyPaging(episodes, request.StartIndex, request.Limit).ToList();
+ }
- var dtos = (await _dtoService.GetBaseItemDtos(pagedItems, dtoOptions, user).ConfigureAwait(false))
- .ToArray();
+ var dtos = _dtoService.GetBaseItemDtos(returnItems, dtoOptions, user);
- return new ItemsResult
+ return new QueryResult<BaseItemDto>
{
- TotalRecordCount = returnList.Count,
+ TotalRecordCount = episodes.Count,
Items = dtos
};
}
diff --git a/MediaBrowser.Api/UserLibrary/ArtistsService.cs b/MediaBrowser.Api/UserLibrary/ArtistsService.cs
index 7c1519e9f..4018759d9 100644
--- a/MediaBrowser.Api/UserLibrary/ArtistsService.cs
+++ b/MediaBrowser.Api/UserLibrary/ArtistsService.cs
@@ -132,7 +132,7 @@ namespace MediaBrowser.Api.UserLibrary
/// <param name="request">The request.</param>
/// <param name="items">The items.</param>
/// <returns>IEnumerable{Tuple{System.StringFunc{System.Int32}}}.</returns>
- protected override IEnumerable<BaseItem> GetAllItems(GetItemsByName request, IEnumerable<BaseItem> items)
+ protected override IEnumerable<BaseItem> GetAllItems(GetItemsByName request, IList<BaseItem> items)
{
throw new NotImplementedException();
}
diff --git a/MediaBrowser.Api/UserLibrary/BaseItemsByNameService.cs b/MediaBrowser.Api/UserLibrary/BaseItemsByNameService.cs
index 24d0a7d52..ed0c4069b 100644
--- a/MediaBrowser.Api/UserLibrary/BaseItemsByNameService.cs
+++ b/MediaBrowser.Api/UserLibrary/BaseItemsByNameService.cs
@@ -10,6 +10,7 @@ using System.Linq;
using MediaBrowser.Controller.Net;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Services;
+using MediaBrowser.Model.Extensions;
namespace MediaBrowser.Api.UserLibrary
{
@@ -87,7 +88,7 @@ namespace MediaBrowser.Api.UserLibrary
return null;
}
- protected ItemsResult GetResultSlim(GetItemsByName request)
+ protected QueryResult<BaseItemDto> GetResultSlim(GetItemsByName request)
{
var dtoOptions = GetDtoOptions(AuthorizationContext, request);
@@ -208,9 +209,9 @@ namespace MediaBrowser.Api.UserLibrary
return dto;
});
- return new ItemsResult
+ return new QueryResult<BaseItemDto>
{
- Items = dtos.ToArray(),
+ Items = dtos.ToArray(result.Items.Length),
TotalRecordCount = result.TotalRecordCount
};
}
@@ -239,7 +240,7 @@ namespace MediaBrowser.Api.UserLibrary
/// </summary>
/// <param name="request">The request.</param>
/// <returns>Task{ItemsResult}.</returns>
- protected ItemsResult GetResult(GetItemsByName request)
+ protected QueryResult<BaseItemDto> GetResult(GetItemsByName request)
{
var dtoOptions = GetDtoOptions(AuthorizationContext, request);
@@ -256,7 +257,7 @@ namespace MediaBrowser.Api.UserLibrary
parentItem = string.IsNullOrEmpty(request.ParentId) ? LibraryManager.RootFolder : LibraryManager.GetItemById(request.ParentId);
}
- IEnumerable<BaseItem> items;
+ IList<BaseItem> items;
var excludeItemTypes = request.GetExcludeItemTypes();
var includeItemTypes = request.GetIncludeItemTypes();
@@ -279,32 +280,32 @@ namespace MediaBrowser.Api.UserLibrary
if (!string.IsNullOrWhiteSpace(request.UserId))
{
items = request.Recursive ?
- folder.GetRecursiveChildren(user, query) :
- folder.GetChildren(user, true).Where(filter);
+ folder.GetRecursiveChildren(user, query).ToList() :
+ folder.GetChildren(user, true).Where(filter).ToList();
}
else
{
items = request.Recursive ?
folder.GetRecursiveChildren(filter) :
- folder.Children.Where(filter);
+ folder.Children.Where(filter).ToList();
}
}
else
{
- items = new[] { parentItem }.Where(filter);
+ items = new[] { parentItem }.Where(filter).ToList();
}
var extractedItems = GetAllItems(request, items);
var filteredItems = FilterItems(request, extractedItems, user);
- filteredItems = LibraryManager.Sort(filteredItems, user, request.GetOrderBy(), request.SortOrder ?? SortOrder.Ascending);
+ filteredItems = LibraryManager.Sort(filteredItems, user, request.GetOrderBy());
var ibnItemsArray = filteredItems.ToList();
IEnumerable<BaseItem> ibnItems = ibnItemsArray;
- var result = new ItemsResult
+ var result = new QueryResult<BaseItemDto>
{
TotalRecordCount = ibnItemsArray.Count
};
@@ -356,13 +357,13 @@ namespace MediaBrowser.Api.UserLibrary
items = items.Where(i => string.Compare(request.NameLessThan, i.SortName, StringComparison.CurrentCultureIgnoreCase) == 1);
}
- var imageTypes = request.GetImageTypes().ToList();
- if (imageTypes.Count > 0)
+ var imageTypes = request.GetImageTypes();
+ if (imageTypes.Length > 0)
{
items = items.Where(item => imageTypes.Any(item.HasImage));
}
- var filters = request.GetFilters().ToList();
+ var filters = request.GetFilters();
if (filters.Contains(ItemFilter.Dislikes))
{
@@ -499,13 +500,13 @@ namespace MediaBrowser.Api.UserLibrary
/// <param name="request">The request.</param>
/// <param name="items">The items.</param>
/// <returns>IEnumerable{Task{`0}}.</returns>
- protected abstract IEnumerable<BaseItem> GetAllItems(GetItemsByName request, IEnumerable<BaseItem> items);
+ protected abstract IEnumerable<BaseItem> GetAllItems(GetItemsByName request, IList<BaseItem> items);
}
/// <summary>
/// Class GetItemsByName
/// </summary>
- public class GetItemsByName : BaseItemsRequest, IReturn<ItemsResult>
+ public class GetItemsByName : BaseItemsRequest, IReturn<QueryResult<BaseItemDto>>
{
public GetItemsByName()
{
diff --git a/MediaBrowser.Api/UserLibrary/BaseItemsRequest.cs b/MediaBrowser.Api/UserLibrary/BaseItemsRequest.cs
index 3415d01f1..88d080db5 100644
--- a/MediaBrowser.Api/UserLibrary/BaseItemsRequest.cs
+++ b/MediaBrowser.Api/UserLibrary/BaseItemsRequest.cs
@@ -70,9 +70,6 @@ namespace MediaBrowser.Api.UserLibrary
[ApiMember(Name = "IsUnaired", Description = "Optional filter by items that are unaired episodes or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
public bool? IsUnaired { get; set; }
- [ApiMember(Name = "IsVirtualUnaired", Description = "Optional filter by items that are virtual unaired episodes or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
- public bool? IsVirtualUnaired { get; set; }
-
[ApiMember(Name = "MinCommunityRating", Description = "Optional filter by minimum community rating.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
public double? MinCommunityRating { get; set; }
@@ -139,7 +136,7 @@ namespace MediaBrowser.Api.UserLibrary
/// </summary>
/// <value>The sort order.</value>
[ApiMember(Name = "SortOrder", Description = "Sort Order - Ascending,Descending", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
- public SortOrder? SortOrder { get; set; }
+ public string SortOrder { get; set; }
/// <summary>
/// Specify this to localize the search to a specific item or folder. Omit to use the root.
@@ -300,13 +297,6 @@ namespace MediaBrowser.Api.UserLibrary
public string VideoTypes { get; set; }
/// <summary>
- /// Gets or sets the air days.
- /// </summary>
- /// <value>The air days.</value>
- [ApiMember(Name = "AirDays", Description = "Optional filter by Series Air Days. Allows multiple, comma delimeted.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
- public string AirDays { get; set; }
-
- /// <summary>
/// Gets or sets the user id.
/// </summary>
/// <value>The user id.</value>
@@ -445,7 +435,7 @@ namespace MediaBrowser.Api.UserLibrary
/// Gets the filters.
/// </summary>
/// <returns>IEnumerable{ItemFilter}.</returns>
- public IEnumerable<ItemFilter> GetFilters()
+ public ItemFilter[] GetFilters()
{
var val = Filters;
@@ -454,14 +444,14 @@ namespace MediaBrowser.Api.UserLibrary
return new ItemFilter[] { };
}
- return val.Split(',').Select(v => (ItemFilter)Enum.Parse(typeof(ItemFilter), v, true));
+ return val.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Select(v => (ItemFilter)Enum.Parse(typeof(ItemFilter), v, true)).ToArray();
}
/// <summary>
/// Gets the image types.
/// </summary>
/// <returns>IEnumerable{ImageType}.</returns>
- public IEnumerable<ImageType> GetImageTypes()
+ public ImageType[] GetImageTypes()
{
var val = ImageTypes;
@@ -470,23 +460,48 @@ namespace MediaBrowser.Api.UserLibrary
return new ImageType[] { };
}
- return val.Split(',').Select(v => (ImageType)Enum.Parse(typeof(ImageType), v, true));
+ return val.Split(',').Select(v => (ImageType)Enum.Parse(typeof(ImageType), v, true)).ToArray();
}
/// <summary>
/// Gets the order by.
/// </summary>
/// <returns>IEnumerable{ItemSortBy}.</returns>
- public string[] GetOrderBy()
+ public Tuple<string, SortOrder>[] GetOrderBy()
{
- var val = SortBy;
+ return GetOrderBy(SortBy, SortOrder);
+ }
+
+ public static Tuple<string, SortOrder>[] GetOrderBy(string sortBy, string requestedSortOrder)
+ {
+ var val = sortBy;
if (string.IsNullOrEmpty(val))
{
- return new string[] { };
+ return new Tuple<string, Model.Entities.SortOrder>[] { };
+ }
+
+ var vals = val.Split(',');
+ if (string.IsNullOrWhiteSpace(requestedSortOrder))
+ {
+ requestedSortOrder = "Ascending";
+ }
+
+ var sortOrders = requestedSortOrder.Split(',');
+
+ var result = new Tuple<string, Model.Entities.SortOrder>[vals.Length];
+
+ for (var i = 0; i < vals.Length; i++)
+ {
+ var sortOrderIndex = sortOrders.Length > i ? i : 0;
+
+ var sortOrderValue = sortOrders.Length > sortOrderIndex ? sortOrders[sortOrderIndex] : null;
+ var sortOrder = string.Equals(sortOrderValue, "Descending", StringComparison.OrdinalIgnoreCase) ? MediaBrowser.Model.Entities.SortOrder.Descending : MediaBrowser.Model.Entities.SortOrder.Ascending;
+
+ result[i] = new Tuple<string, Model.Entities.SortOrder>(vals[i], sortOrder);
}
- return val.Split(',');
+ return result;
}
}
}
diff --git a/MediaBrowser.Api/UserLibrary/GameGenresService.cs b/MediaBrowser.Api/UserLibrary/GameGenresService.cs
index 56730c1b2..0b2ca4daf 100644
--- a/MediaBrowser.Api/UserLibrary/GameGenresService.cs
+++ b/MediaBrowser.Api/UserLibrary/GameGenresService.cs
@@ -93,7 +93,7 @@ namespace MediaBrowser.Api.UserLibrary
/// <param name="request">The request.</param>
/// <param name="items">The items.</param>
/// <returns>IEnumerable{Tuple{System.StringFunc{System.Int32}}}.</returns>
- protected override IEnumerable<BaseItem> GetAllItems(GetItemsByName request, IEnumerable<BaseItem> items)
+ protected override IEnumerable<BaseItem> GetAllItems(GetItemsByName request, IList<BaseItem> items)
{
throw new NotImplementedException();
}
diff --git a/MediaBrowser.Api/UserLibrary/GenresService.cs b/MediaBrowser.Api/UserLibrary/GenresService.cs
index fc387e5e3..d913f52d9 100644
--- a/MediaBrowser.Api/UserLibrary/GenresService.cs
+++ b/MediaBrowser.Api/UserLibrary/GenresService.cs
@@ -115,7 +115,7 @@ namespace MediaBrowser.Api.UserLibrary
/// <param name="request">The request.</param>
/// <param name="items">The items.</param>
/// <returns>IEnumerable{Tuple{System.StringFunc{System.Int32}}}.</returns>
- protected override IEnumerable<BaseItem> GetAllItems(GetItemsByName request, IEnumerable<BaseItem> items)
+ protected override IEnumerable<BaseItem> GetAllItems(GetItemsByName request, IList<BaseItem> items)
{
throw new NotImplementedException();
}
diff --git a/MediaBrowser.Api/UserLibrary/ItemsService.cs b/MediaBrowser.Api/UserLibrary/ItemsService.cs
index 01e1ce769..fb48f65e4 100644
--- a/MediaBrowser.Api/UserLibrary/ItemsService.cs
+++ b/MediaBrowser.Api/UserLibrary/ItemsService.cs
@@ -10,8 +10,10 @@ using System.Globalization;
using System.Linq;
using System.Threading.Tasks;
using MediaBrowser.Controller.Entities.Audio;
+using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Globalization;
using MediaBrowser.Model.Services;
+using MediaBrowser.Model.Extensions;
namespace MediaBrowser.Api.UserLibrary
{
@@ -20,7 +22,7 @@ namespace MediaBrowser.Api.UserLibrary
/// </summary>
[Route("/Items", "GET", Summary = "Gets items based on a query.")]
[Route("/Users/{UserId}/Items", "GET", Summary = "Gets items based on a query.")]
- public class GetItems : BaseItemsRequest, IReturn<ItemsResult>
+ public class GetItems : BaseItemsRequest, IReturn<QueryResult<BaseItemDto>>
{
}
@@ -82,14 +84,14 @@ namespace MediaBrowser.Api.UserLibrary
/// </summary>
/// <param name="request">The request.</param>
/// <returns>System.Object.</returns>
- public async Task<object> Get(GetItems request)
+ public object Get(GetItems request)
{
if (request == null)
{
throw new ArgumentNullException("request");
}
- var result = await GetItems(request).ConfigureAwait(false);
+ var result = GetItems(request);
return ToOptimizedSerializedResultUsingCache(result);
}
@@ -98,8 +100,7 @@ namespace MediaBrowser.Api.UserLibrary
/// Gets the items.
/// </summary>
/// <param name="request">The request.</param>
- /// <returns>Task{ItemsResult}.</returns>
- private async Task<ItemsResult> GetItems(GetItems request)
+ private QueryResult<BaseItemDto> GetItems(GetItems request)
{
var user = !string.IsNullOrWhiteSpace(request.UserId) ? _userManager.GetUserById(request.UserId) : null;
@@ -117,17 +118,17 @@ namespace MediaBrowser.Api.UserLibrary
throw new InvalidOperationException("GetItemsToSerialize result.Items returned null");
}
- var dtoList = await _dtoService.GetBaseItemDtos(result.Items, dtoOptions, user).ConfigureAwait(false);
+ var dtoList = _dtoService.GetBaseItemDtos(result.Items, dtoOptions, user);
if (dtoList == null)
{
throw new InvalidOperationException("GetBaseItemDtos returned null");
}
- return new ItemsResult
+ return new QueryResult<BaseItemDto>
{
TotalRecordCount = result.TotalRecordCount,
- Items = dtoList.ToArray()
+ Items = dtoList
};
}
@@ -179,9 +180,7 @@ namespace MediaBrowser.Api.UserLibrary
return folder.GetItems(GetItemsQuery(request, dtoOptions, user));
}
- IEnumerable<BaseItem> items = folder.GetChildren(user, true);
-
- var itemsArray = items.ToArray();
+ var itemsArray = folder.GetChildren(user, true).ToArray();
return new QueryResult<BaseItem>
{
@@ -199,14 +198,12 @@ namespace MediaBrowser.Api.UserLibrary
IncludeItemTypes = request.GetIncludeItemTypes(),
ExcludeItemTypes = request.GetExcludeItemTypes(),
Recursive = request.Recursive,
- SortBy = request.GetOrderBy(),
- SortOrder = request.SortOrder ?? SortOrder.Ascending,
+ OrderBy = request.GetOrderBy(),
IsFavorite = request.IsFavorite,
Limit = request.Limit,
StartIndex = request.StartIndex,
IsMissing = request.IsMissing,
- IsVirtualUnaired = request.IsVirtualUnaired,
IsUnaired = request.IsUnaired,
CollapseBoxSetItems = request.CollapseBoxSetItems,
NameLessThan = request.NameLessThan,
@@ -238,8 +235,8 @@ namespace MediaBrowser.Api.UserLibrary
PersonIds = request.GetPersonIds(),
PersonTypes = request.GetPersonTypes(),
Years = request.GetYears(),
- ImageTypes = request.GetImageTypes().ToArray(),
- VideoTypes = request.GetVideoTypes().ToArray(),
+ ImageTypes = request.GetImageTypes(),
+ VideoTypes = request.GetVideoTypes(),
AdjacentTo = request.AdjacentTo,
ItemIds = request.GetItemIds(),
MinPlayers = request.MinPlayers,
@@ -319,12 +316,6 @@ namespace MediaBrowser.Api.UserLibrary
query.SeriesStatuses = request.SeriesStatus.Split(',').Select(d => (SeriesStatus)Enum.Parse(typeof(SeriesStatus), d, true)).ToArray();
}
- // Filter by Series AirDays
- if (!string.IsNullOrEmpty(request.AirDays))
- {
- query.AirDays = request.AirDays.Split(',').Select(d => (DayOfWeek)Enum.Parse(typeof(DayOfWeek), d, true)).ToArray();
- }
-
// ExcludeLocationTypes
if (!string.IsNullOrEmpty(request.ExcludeLocationTypes))
{
@@ -338,13 +329,11 @@ namespace MediaBrowser.Api.UserLibrary
if (!string.IsNullOrEmpty(request.LocationTypes))
{
var requestedLocationTypes =
- request.LocationTypes.Split(',')
- .Select(d => (LocationType)Enum.Parse(typeof(LocationType), d, true))
- .ToList();
+ request.LocationTypes.Split(',');
- if (requestedLocationTypes.Count > 0 && requestedLocationTypes.Count < 4)
+ if (requestedLocationTypes.Length > 0 && requestedLocationTypes.Length < 4)
{
- query.IsVirtualItem = requestedLocationTypes.Contains(LocationType.Virtual);
+ query.IsVirtualItem = requestedLocationTypes.Contains(LocationType.Virtual.ToString());
}
}
diff --git a/MediaBrowser.Api/UserLibrary/MusicGenresService.cs b/MediaBrowser.Api/UserLibrary/MusicGenresService.cs
index d1d4aa634..36dc773d4 100644
--- a/MediaBrowser.Api/UserLibrary/MusicGenresService.cs
+++ b/MediaBrowser.Api/UserLibrary/MusicGenresService.cs
@@ -94,7 +94,7 @@ namespace MediaBrowser.Api.UserLibrary
/// <param name="request">The request.</param>
/// <param name="items">The items.</param>
/// <returns>IEnumerable{Tuple{System.StringFunc{System.Int32}}}.</returns>
- protected override IEnumerable<BaseItem> GetAllItems(GetItemsByName request, IEnumerable<BaseItem> items)
+ protected override IEnumerable<BaseItem> GetAllItems(GetItemsByName request, IList<BaseItem> items)
{
throw new NotImplementedException();
}
diff --git a/MediaBrowser.Api/UserLibrary/PersonsService.cs b/MediaBrowser.Api/UserLibrary/PersonsService.cs
index 21f416025..9417447d8 100644
--- a/MediaBrowser.Api/UserLibrary/PersonsService.cs
+++ b/MediaBrowser.Api/UserLibrary/PersonsService.cs
@@ -96,15 +96,13 @@ namespace MediaBrowser.Api.UserLibrary
/// <param name="request">The request.</param>
/// <param name="items">The items.</param>
/// <returns>IEnumerable{Tuple{System.StringFunc{System.Int32}}}.</returns>
- protected override IEnumerable<BaseItem> GetAllItems(GetItemsByName request, IEnumerable<BaseItem> items)
+ protected override IEnumerable<BaseItem> GetAllItems(GetItemsByName request, IList<BaseItem> items)
{
var inputPersonTypes = ((GetPersons)request).PersonTypes;
var personTypes = string.IsNullOrEmpty(inputPersonTypes) ? new string[] { } : inputPersonTypes.Split(',');
- var itemsList = items.ToList();
-
// Either get all people, or all people filtered by a specific person type
- var allPeople = GetAllPeople(itemsList, personTypes);
+ var allPeople = GetAllPeople(items, personTypes);
return allPeople
.Select(i => i.Name)
@@ -132,13 +130,13 @@ namespace MediaBrowser.Api.UserLibrary
/// <param name="itemsList">The items list.</param>
/// <param name="personTypes">The person types.</param>
/// <returns>IEnumerable{PersonInfo}.</returns>
- private IEnumerable<PersonInfo> GetAllPeople(IEnumerable<BaseItem> itemsList, IEnumerable<string> personTypes)
+ private IEnumerable<PersonInfo> GetAllPeople(IList<BaseItem> itemsList, string[] personTypes)
{
- var allIds = itemsList.Select(i => i.Id).ToList();
+ var allIds = itemsList.Select(i => i.Id).ToArray();
var allPeople = LibraryManager.GetPeople(new InternalPeopleQuery
{
- PersonTypes = personTypes.ToList()
+ PersonTypes = personTypes
});
return allPeople.Where(i => allIds.Contains(i.ItemId)).OrderBy(p => p.SortOrder ?? int.MaxValue).ThenBy(p => p.Type);
diff --git a/MediaBrowser.Api/UserLibrary/PlaystateService.cs b/MediaBrowser.Api/UserLibrary/PlaystateService.cs
deleted file mode 100644
index 98b4a5d5d..000000000
--- a/MediaBrowser.Api/UserLibrary/PlaystateService.cs
+++ /dev/null
@@ -1,451 +0,0 @@
-using MediaBrowser.Controller.Entities;
-using MediaBrowser.Controller.Library;
-using MediaBrowser.Controller.Net;
-using MediaBrowser.Controller.Session;
-using MediaBrowser.Model.Dto;
-using MediaBrowser.Model.Session;
-using System;
-using System.Globalization;
-using System.Linq;
-using System.Threading.Tasks;
-using MediaBrowser.Model.Services;
-
-namespace MediaBrowser.Api.UserLibrary
-{
- /// <summary>
- /// Class MarkPlayedItem
- /// </summary>
- [Route("/Users/{UserId}/PlayedItems/{Id}", "POST", Summary = "Marks an item as played")]
- public class MarkPlayedItem : IReturn<UserItemDataDto>
- {
- /// <summary>
- /// Gets or sets the user id.
- /// </summary>
- /// <value>The user id.</value>
- [ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
- public string UserId { get; set; }
-
- [ApiMember(Name = "DatePlayed", Description = "The date the item was played (if any). Format = yyyyMMddHHmmss", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")]
- public string DatePlayed { get; set; }
-
- /// <summary>
- /// Gets or sets the id.
- /// </summary>
- /// <value>The id.</value>
- [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
- public string Id { get; set; }
- }
-
- /// <summary>
- /// Class MarkUnplayedItem
- /// </summary>
- [Route("/Users/{UserId}/PlayedItems/{Id}", "DELETE", Summary = "Marks an item as unplayed")]
- public class MarkUnplayedItem : IReturn<UserItemDataDto>
- {
- /// <summary>
- /// Gets or sets the user id.
- /// </summary>
- /// <value>The user id.</value>
- [ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "DELETE")]
- public string UserId { get; set; }
-
- /// <summary>
- /// Gets or sets the id.
- /// </summary>
- /// <value>The id.</value>
- [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "DELETE")]
- public string Id { get; set; }
- }
-
- [Route("/Sessions/Playing", "POST", Summary = "Reports playback has started within a session")]
- public class ReportPlaybackStart : PlaybackStartInfo, IReturnVoid
- {
- }
-
- [Route("/Sessions/Playing/Progress", "POST", Summary = "Reports playback progress within a session")]
- public class ReportPlaybackProgress : PlaybackProgressInfo, IReturnVoid
- {
- }
-
- [Route("/Sessions/Playing/Ping", "POST", Summary = "Pings a playback session")]
- public class PingPlaybackSession : IReturnVoid
- {
- [ApiMember(Name = "PlaySessionId", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")]
- public string PlaySessionId { get; set; }
- }
-
- [Route("/Sessions/Playing/Stopped", "POST", Summary = "Reports playback has stopped within a session")]
- public class ReportPlaybackStopped : PlaybackStopInfo, IReturnVoid
- {
- }
-
- /// <summary>
- /// Class OnPlaybackStart
- /// </summary>
- [Route("/Users/{UserId}/PlayingItems/{Id}", "POST", Summary = "Reports that a user has begun playing an item")]
- public class OnPlaybackStart : IReturnVoid
- {
- /// <summary>
- /// Gets or sets the user id.
- /// </summary>
- /// <value>The user id.</value>
- [ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
- public string UserId { get; set; }
-
- /// <summary>
- /// Gets or sets the id.
- /// </summary>
- /// <value>The id.</value>
- [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
- public string Id { get; set; }
-
- [ApiMember(Name = "MediaSourceId", Description = "The id of the MediaSource", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")]
- public string MediaSourceId { get; set; }
-
- /// <summary>
- /// Gets or sets a value indicating whether this <see cref="UpdateUserItemRating" /> is likes.
- /// </summary>
- /// <value><c>true</c> if likes; otherwise, <c>false</c>.</value>
- [ApiMember(Name = "CanSeek", Description = "Indicates if the client can seek", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "POST")]
- public bool CanSeek { get; set; }
-
- [ApiMember(Name = "AudioStreamIndex", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "POST")]
- public int? AudioStreamIndex { get; set; }
-
- [ApiMember(Name = "SubtitleStreamIndex", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "POST")]
- public int? SubtitleStreamIndex { get; set; }
-
- [ApiMember(Name = "PlayMethod", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")]
- public PlayMethod PlayMethod { get; set; }
-
- [ApiMember(Name = "LiveStreamId", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")]
- public string LiveStreamId { get; set; }
-
- [ApiMember(Name = "PlaySessionId", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")]
- public string PlaySessionId { get; set; }
- }
-
- /// <summary>
- /// Class OnPlaybackProgress
- /// </summary>
- [Route("/Users/{UserId}/PlayingItems/{Id}/Progress", "POST", Summary = "Reports a user's playback progress")]
- public class OnPlaybackProgress : IReturnVoid
- {
- /// <summary>
- /// Gets or sets the user id.
- /// </summary>
- /// <value>The user id.</value>
- [ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
- public string UserId { get; set; }
-
- /// <summary>
- /// Gets or sets the id.
- /// </summary>
- /// <value>The id.</value>
- [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
- public string Id { get; set; }
-
- [ApiMember(Name = "MediaSourceId", Description = "The id of the MediaSource", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")]
- public string MediaSourceId { get; set; }
-
- /// <summary>
- /// Gets or sets the position ticks.
- /// </summary>
- /// <value>The position ticks.</value>
- [ApiMember(Name = "PositionTicks", Description = "Optional. The current position, in ticks. 1 tick = 10000 ms", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "POST")]
- public long? PositionTicks { get; set; }
-
- [ApiMember(Name = "IsPaused", Description = "Indicates if the player is paused.", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "POST")]
- public bool IsPaused { get; set; }
-
- [ApiMember(Name = "IsMuted", Description = "Indicates if the player is muted.", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "POST")]
- public bool IsMuted { get; set; }
-
- [ApiMember(Name = "AudioStreamIndex", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "POST")]
- public int? AudioStreamIndex { get; set; }
-
- [ApiMember(Name = "SubtitleStreamIndex", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "POST")]
- public int? SubtitleStreamIndex { get; set; }
-
- [ApiMember(Name = "VolumeLevel", Description = "Scale of 0-100", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "POST")]
- public int? VolumeLevel { get; set; }
-
- [ApiMember(Name = "PlayMethod", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")]
- public PlayMethod PlayMethod { get; set; }
-
- [ApiMember(Name = "LiveStreamId", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")]
- public string LiveStreamId { get; set; }
-
- [ApiMember(Name = "PlaySessionId", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")]
- public string PlaySessionId { get; set; }
-
- [ApiMember(Name = "RepeatMode", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")]
- public RepeatMode RepeatMode { get; set; }
- }
-
- /// <summary>
- /// Class OnPlaybackStopped
- /// </summary>
- [Route("/Users/{UserId}/PlayingItems/{Id}", "DELETE", Summary = "Reports that a user has stopped playing an item")]
- public class OnPlaybackStopped : IReturnVoid
- {
- /// <summary>
- /// Gets or sets the user id.
- /// </summary>
- /// <value>The user id.</value>
- [ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "DELETE")]
- public string UserId { get; set; }
-
- /// <summary>
- /// Gets or sets the id.
- /// </summary>
- /// <value>The id.</value>
- [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "DELETE")]
- public string Id { get; set; }
-
- [ApiMember(Name = "MediaSourceId", Description = "The id of the MediaSource", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "DELETE")]
- public string MediaSourceId { get; set; }
-
- [ApiMember(Name = "NextMediaType", Description = "The next media type that will play", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "DELETE")]
- public string NextMediaType { get; set; }
-
- /// <summary>
- /// Gets or sets the position ticks.
- /// </summary>
- /// <value>The position ticks.</value>
- [ApiMember(Name = "PositionTicks", Description = "Optional. The position, in ticks, where playback stopped. 1 tick = 10000 ms", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "DELETE")]
- public long? PositionTicks { get; set; }
-
- [ApiMember(Name = "LiveStreamId", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")]
- public string LiveStreamId { get; set; }
-
- [ApiMember(Name = "PlaySessionId", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")]
- public string PlaySessionId { get; set; }
- }
-
- [Authenticated]
- public class PlaystateService : BaseApiService
- {
- private readonly IUserManager _userManager;
- private readonly IUserDataManager _userDataRepository;
- private readonly ILibraryManager _libraryManager;
- private readonly ISessionManager _sessionManager;
- private readonly ISessionContext _sessionContext;
- private readonly IAuthorizationContext _authContext;
-
- public PlaystateService(IUserManager userManager, IUserDataManager userDataRepository, ILibraryManager libraryManager, ISessionManager sessionManager, ISessionContext sessionContext, IAuthorizationContext authContext)
- {
- _userManager = userManager;
- _userDataRepository = userDataRepository;
- _libraryManager = libraryManager;
- _sessionManager = sessionManager;
- _sessionContext = sessionContext;
- _authContext = authContext;
- }
-
- /// <summary>
- /// Posts the specified request.
- /// </summary>
- /// <param name="request">The request.</param>
- public async Task<object> Post(MarkPlayedItem request)
- {
- var result = await MarkPlayed(request).ConfigureAwait(false);
-
- return ToOptimizedResult(result);
- }
-
- private async Task<UserItemDataDto> MarkPlayed(MarkPlayedItem request)
- {
- var user = _userManager.GetUserById(request.UserId);
-
- DateTime? datePlayed = null;
-
- if (!string.IsNullOrEmpty(request.DatePlayed))
- {
- datePlayed = DateTime.ParseExact(request.DatePlayed, "yyyyMMddHHmmss", CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal);
- }
-
- var session = await GetSession(_sessionContext).ConfigureAwait(false);
-
- var dto = await UpdatePlayedStatus(user, request.Id, true, datePlayed).ConfigureAwait(false);
-
- foreach (var additionalUserInfo in session.AdditionalUsers)
- {
- var additionalUser = _userManager.GetUserById(additionalUserInfo.UserId);
-
- await UpdatePlayedStatus(additionalUser, request.Id, true, datePlayed).ConfigureAwait(false);
- }
-
- return dto;
- }
-
- private PlayMethod ValidatePlayMethod(PlayMethod method, string playSessionId)
- {
- if (method == PlayMethod.Transcode)
- {
- var job = string.IsNullOrWhiteSpace(playSessionId) ? null : ApiEntryPoint.Instance.GetTranscodingJob(playSessionId);
- if (job == null)
- {
- return PlayMethod.DirectPlay;
- }
- }
-
- return method;
- }
-
- /// <summary>
- /// Posts the specified request.
- /// </summary>
- /// <param name="request">The request.</param>
- public void Post(OnPlaybackStart request)
- {
- Post(new ReportPlaybackStart
- {
- CanSeek = request.CanSeek,
- ItemId = request.Id,
- MediaSourceId = request.MediaSourceId,
- AudioStreamIndex = request.AudioStreamIndex,
- SubtitleStreamIndex = request.SubtitleStreamIndex,
- PlayMethod = request.PlayMethod,
- PlaySessionId = request.PlaySessionId,
- LiveStreamId = request.LiveStreamId
- });
- }
-
- public void Post(ReportPlaybackStart request)
- {
- request.PlayMethod = ValidatePlayMethod(request.PlayMethod, request.PlaySessionId);
-
- request.SessionId = GetSession(_sessionContext).Result.Id;
-
- var task = _sessionManager.OnPlaybackStart(request);
-
- Task.WaitAll(task);
- }
-
- /// <summary>
- /// Posts the specified request.
- /// </summary>
- /// <param name="request">The request.</param>
- public void Post(OnPlaybackProgress request)
- {
- Post(new ReportPlaybackProgress
- {
- ItemId = request.Id,
- PositionTicks = request.PositionTicks,
- IsMuted = request.IsMuted,
- IsPaused = request.IsPaused,
- MediaSourceId = request.MediaSourceId,
- AudioStreamIndex = request.AudioStreamIndex,
- SubtitleStreamIndex = request.SubtitleStreamIndex,
- VolumeLevel = request.VolumeLevel,
- PlayMethod = request.PlayMethod,
- PlaySessionId = request.PlaySessionId,
- LiveStreamId = request.LiveStreamId,
- RepeatMode = request.RepeatMode
- });
- }
-
- public void Post(ReportPlaybackProgress request)
- {
- request.PlayMethod = ValidatePlayMethod(request.PlayMethod, request.PlaySessionId);
-
- request.SessionId = GetSession(_sessionContext).Result.Id;
-
- var task = _sessionManager.OnPlaybackProgress(request);
-
- Task.WaitAll(task);
- }
-
- public void Post(PingPlaybackSession request)
- {
- ApiEntryPoint.Instance.PingTranscodingJob(request.PlaySessionId, null);
- }
-
- /// <summary>
- /// Posts the specified request.
- /// </summary>
- /// <param name="request">The request.</param>
- public void Delete(OnPlaybackStopped request)
- {
- Post(new ReportPlaybackStopped
- {
- ItemId = request.Id,
- PositionTicks = request.PositionTicks,
- MediaSourceId = request.MediaSourceId,
- PlaySessionId = request.PlaySessionId,
- LiveStreamId = request.LiveStreamId,
- NextMediaType = request.NextMediaType
- });
- }
-
- public void Post(ReportPlaybackStopped request)
- {
- Logger.Debug("ReportPlaybackStopped PlaySessionId: {0}", request.PlaySessionId ?? string.Empty);
-
- if (!string.IsNullOrWhiteSpace(request.PlaySessionId))
- {
- ApiEntryPoint.Instance.KillTranscodingJobs(_authContext.GetAuthorizationInfo(Request).DeviceId, request.PlaySessionId, s => true);
- }
-
- request.SessionId = GetSession(_sessionContext).Result.Id;
-
- var task = _sessionManager.OnPlaybackStopped(request);
-
- Task.WaitAll(task);
- }
-
- /// <summary>
- /// Deletes the specified request.
- /// </summary>
- /// <param name="request">The request.</param>
- public object Delete(MarkUnplayedItem request)
- {
- var task = MarkUnplayed(request);
-
- return ToOptimizedResult(task.Result);
- }
-
- private async Task<UserItemDataDto> MarkUnplayed(MarkUnplayedItem request)
- {
- var user = _userManager.GetUserById(request.UserId);
-
- var session = await GetSession(_sessionContext).ConfigureAwait(false);
-
- var dto = await UpdatePlayedStatus(user, request.Id, false, null).ConfigureAwait(false);
-
- foreach (var additionalUserInfo in session.AdditionalUsers)
- {
- var additionalUser = _userManager.GetUserById(additionalUserInfo.UserId);
-
- await UpdatePlayedStatus(additionalUser, request.Id, false, null).ConfigureAwait(false);
- }
-
- return dto;
- }
-
- /// <summary>
- /// Updates the played status.
- /// </summary>
- /// <param name="user">The user.</param>
- /// <param name="itemId">The item id.</param>
- /// <param name="wasPlayed">if set to <c>true</c> [was played].</param>
- /// <param name="datePlayed">The date played.</param>
- /// <returns>Task.</returns>
- private async Task<UserItemDataDto> UpdatePlayedStatus(User user, string itemId, bool wasPlayed, DateTime? datePlayed)
- {
- var item = _libraryManager.GetItemById(itemId);
-
- if (wasPlayed)
- {
- await item.MarkPlayed(user, datePlayed, true).ConfigureAwait(false);
- }
- else
- {
- await item.MarkUnplayed(user).ConfigureAwait(false);
- }
-
- return _userDataRepository.GetUserDataDto(item, user);
- }
- }
-} \ No newline at end of file
diff --git a/MediaBrowser.Api/UserLibrary/StudiosService.cs b/MediaBrowser.Api/UserLibrary/StudiosService.cs
index 7ac1264e8..f10cccbb1 100644
--- a/MediaBrowser.Api/UserLibrary/StudiosService.cs
+++ b/MediaBrowser.Api/UserLibrary/StudiosService.cs
@@ -103,11 +103,9 @@ namespace MediaBrowser.Api.UserLibrary
/// <param name="request">The request.</param>
/// <param name="items">The items.</param>
/// <returns>IEnumerable{Tuple{System.StringFunc{System.Int32}}}.</returns>
- protected override IEnumerable<BaseItem> GetAllItems(GetItemsByName request, IEnumerable<BaseItem> items)
+ protected override IEnumerable<BaseItem> GetAllItems(GetItemsByName request, IList<BaseItem> items)
{
- var itemsList = items.Where(i => i.Studios != null).ToList();
-
- return itemsList
+ return items
.SelectMany(i => i.Studios)
.DistinctNames()
.Select(name => LibraryManager.GetStudio(name));
diff --git a/MediaBrowser.Api/UserLibrary/UserLibraryService.cs b/MediaBrowser.Api/UserLibrary/UserLibraryService.cs
index 4bb3de882..1bbc740c0 100644
--- a/MediaBrowser.Api/UserLibrary/UserLibraryService.cs
+++ b/MediaBrowser.Api/UserLibrary/UserLibraryService.cs
@@ -12,7 +12,6 @@ using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Controller.Entities.Audio;
-using MediaBrowser.Controller.IO;
using MediaBrowser.Model.IO;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Services;
@@ -58,7 +57,7 @@ namespace MediaBrowser.Api.UserLibrary
/// Class GetIntros
/// </summary>
[Route("/Users/{UserId}/Items/{Id}/Intros", "GET", Summary = "Gets intros to play before the main media item plays")]
- public class GetIntros : IReturn<ItemsResult>
+ public class GetIntros : IReturn<QueryResult<BaseItemDto>>
{
/// <summary>
/// Gets or sets the user id.
@@ -170,7 +169,7 @@ namespace MediaBrowser.Api.UserLibrary
/// Class GetLocalTrailers
/// </summary>
[Route("/Users/{UserId}/Items/{Id}/LocalTrailers", "GET", Summary = "Gets local trailers for an item")]
- public class GetLocalTrailers : IReturn<List<BaseItemDto>>
+ public class GetLocalTrailers : IReturn<BaseItemDto[]>
{
/// <summary>
/// Gets or sets the user id.
@@ -191,7 +190,7 @@ namespace MediaBrowser.Api.UserLibrary
/// Class GetSpecialFeatures
/// </summary>
[Route("/Users/{UserId}/Items/{Id}/SpecialFeatures", "GET", Summary = "Gets special features for an item")]
- public class GetSpecialFeatures : IReturn<List<BaseItemDto>>
+ public class GetSpecialFeatures : IReturn<BaseItemDto[]>
{
/// <summary>
/// Gets or sets the user id.
@@ -209,7 +208,7 @@ namespace MediaBrowser.Api.UserLibrary
}
[Route("/Users/{UserId}/Items/Latest", "GET", Summary = "Gets latest media")]
- public class GetLatestMedia : IReturn<List<BaseItemDto>>, IHasDtoOptions
+ public class GetLatestMedia : IReturn<BaseItemDto[]>, IHasDtoOptions
{
/// <summary>
/// Gets or sets the user id.
@@ -312,7 +311,7 @@ namespace MediaBrowser.Api.UserLibrary
var list = _userViewManager.GetLatestItems(new LatestItemsQuery
{
GroupItems = request.GroupItems,
- IncludeItemTypes = (request.IncludeItemTypes ?? string.Empty).Split(',').Where(i => !string.IsNullOrWhiteSpace(i)).ToArray(),
+ IncludeItemTypes = ApiEntryPoint.Split(request.IncludeItemTypes, ',', true),
IsPlayed = request.IsPlayed,
Limit = request.Limit,
ParentId = request.ParentId,
@@ -337,10 +336,10 @@ namespace MediaBrowser.Api.UserLibrary
return dto;
});
- return ToOptimizedResult(dtos.ToList());
+ return ToOptimizedResult(dtos.ToArray());
}
- private List<BaseItemDto> GetAsync(GetSpecialFeatures request)
+ private BaseItemDto[] GetAsync(GetSpecialFeatures request)
{
var user = _userManager.GetUserById(request.UserId);
@@ -363,7 +362,7 @@ namespace MediaBrowser.Api.UserLibrary
.Where(i => i.ParentIndexNumber.HasValue && i.ParentIndexNumber.Value == 0)
.Select(i => _dtoService.GetBaseItemDto(i, dtoOptions, currentUser));
- return dtos.ToList();
+ return dtos.ToArray();
}
var movie = item as IHasSpecialFeatures;
@@ -378,10 +377,10 @@ namespace MediaBrowser.Api.UserLibrary
.OrderBy(i => i.SortName)
.Select(i => _dtoService.GetBaseItemDto(i, dtoOptions, user, item));
- return dtos.ToList();
+ return dtos.ToArray();
}
- return new List<BaseItemDto>();
+ return new BaseItemDto[] { };
}
/// <summary>
@@ -395,19 +394,24 @@ namespace MediaBrowser.Api.UserLibrary
var item = string.IsNullOrEmpty(request.Id) ? user.RootFolder : _libraryManager.GetItemById(request.Id);
- var trailerIds = new List<Guid>();
+ List<Guid> trailerIds = null;
var hasTrailers = item as IHasTrailers;
if (hasTrailers != null)
{
trailerIds = hasTrailers.GetTrailerIds();
}
+ else
+ {
+ trailerIds = new List<Guid>();
+ }
var dtoOptions = GetDtoOptions(_authContext, request);
var dtos = trailerIds
.Select(_libraryManager.GetItemById)
- .Select(i => _dtoService.GetBaseItemDto(i, dtoOptions, user, item));
+ .Select(i => _dtoService.GetBaseItemDto(i, dtoOptions, user, item))
+ .ToArray();
return ToOptimizedSerializedResultUsingCache(dtos);
}
@@ -486,10 +490,9 @@ namespace MediaBrowser.Api.UserLibrary
var dtoOptions = GetDtoOptions(_authContext, request);
- var dtos = items.Select(i => _dtoService.GetBaseItemDto(i, dtoOptions, user))
- .ToArray();
+ var dtos = items.Select(i => _dtoService.GetBaseItemDto(i, dtoOptions, user)).ToArray();
- var result = new ItemsResult
+ var result = new QueryResult<BaseItemDto>
{
Items = dtos,
TotalRecordCount = dtos.Length
@@ -502,9 +505,9 @@ namespace MediaBrowser.Api.UserLibrary
/// Posts the specified request.
/// </summary>
/// <param name="request">The request.</param>
- public async Task<object> Post(MarkFavoriteItem request)
+ public object Post(MarkFavoriteItem request)
{
- var dto = await MarkFavorite(request.UserId, request.Id, true).ConfigureAwait(false);
+ var dto = MarkFavorite(request.UserId, request.Id, true);
return ToOptimizedResult(dto);
}
@@ -515,7 +518,7 @@ namespace MediaBrowser.Api.UserLibrary
/// <param name="request">The request.</param>
public object Delete(UnmarkFavoriteItem request)
{
- var dto = MarkFavorite(request.UserId, request.Id, false).Result;
+ var dto = MarkFavorite(request.UserId, request.Id, false);
return ToOptimizedResult(dto);
}
@@ -526,8 +529,7 @@ namespace MediaBrowser.Api.UserLibrary
/// <param name="userId">The user id.</param>
/// <param name="itemId">The item id.</param>
/// <param name="isFavorite">if set to <c>true</c> [is favorite].</param>
- /// <returns>Task{UserItemDataDto}.</returns>
- private async Task<UserItemDataDto> MarkFavorite(string userId, string itemId, bool isFavorite)
+ private UserItemDataDto MarkFavorite(string userId, string itemId, bool isFavorite)
{
var user = _userManager.GetUserById(userId);
@@ -539,7 +541,7 @@ namespace MediaBrowser.Api.UserLibrary
// Set favorite status
data.IsFavorite = isFavorite;
- await _userDataRepository.SaveUserData(user.Id, item, data, UserDataSaveReason.UpdateUserRating, CancellationToken.None).ConfigureAwait(false);
+ _userDataRepository.SaveUserData(user.Id, item, data, UserDataSaveReason.UpdateUserRating, CancellationToken.None);
return _userDataRepository.GetUserDataDto(item, user);
}
@@ -550,7 +552,7 @@ namespace MediaBrowser.Api.UserLibrary
/// <param name="request">The request.</param>
public object Delete(DeleteUserItemRating request)
{
- var dto = UpdateUserItemRating(request.UserId, request.Id, null).Result;
+ var dto = UpdateUserItemRating(request.UserId, request.Id, null);
return ToOptimizedResult(dto);
}
@@ -559,9 +561,9 @@ namespace MediaBrowser.Api.UserLibrary
/// Posts the specified request.
/// </summary>
/// <param name="request">The request.</param>
- public async Task<object> Post(UpdateUserItemRating request)
+ public object Post(UpdateUserItemRating request)
{
- var dto = await UpdateUserItemRating(request.UserId, request.Id, request.Likes).ConfigureAwait(false);
+ var dto = UpdateUserItemRating(request.UserId, request.Id, request.Likes);
return ToOptimizedResult(dto);
}
@@ -572,8 +574,7 @@ namespace MediaBrowser.Api.UserLibrary
/// <param name="userId">The user id.</param>
/// <param name="itemId">The item id.</param>
/// <param name="likes">if set to <c>true</c> [likes].</param>
- /// <returns>Task{UserItemDataDto}.</returns>
- private async Task<UserItemDataDto> UpdateUserItemRating(string userId, string itemId, bool? likes)
+ private UserItemDataDto UpdateUserItemRating(string userId, string itemId, bool? likes)
{
var user = _userManager.GetUserById(userId);
@@ -584,7 +585,7 @@ namespace MediaBrowser.Api.UserLibrary
data.Likes = likes;
- await _userDataRepository.SaveUserData(user.Id, item, data, UserDataSaveReason.UpdateUserRating, CancellationToken.None).ConfigureAwait(false);
+ _userDataRepository.SaveUserData(user.Id, item, data, UserDataSaveReason.UpdateUserRating, CancellationToken.None);
return _userDataRepository.GetUserDataDto(item, user);
}
diff --git a/MediaBrowser.Api/UserLibrary/UserViewsService.cs b/MediaBrowser.Api/UserLibrary/UserViewsService.cs
index 3ed5166a4..096157e47 100644
--- a/MediaBrowser.Api/UserLibrary/UserViewsService.cs
+++ b/MediaBrowser.Api/UserLibrary/UserViewsService.cs
@@ -12,6 +12,7 @@ using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Controller.Net;
using MediaBrowser.Model.Services;
+using MediaBrowser.Model.Extensions;
namespace MediaBrowser.Api.UserLibrary
{
@@ -32,7 +33,7 @@ namespace MediaBrowser.Api.UserLibrary
}
[Route("/Users/{UserId}/GroupingOptions", "GET")]
- public class GetGroupingOptions : IReturn<List<SpecialViewOption>>
+ public class GetGroupingOptions : IReturn<SpecialViewOption[]>
{
/// <summary>
/// Gets or sets the user id.
@@ -84,10 +85,13 @@ namespace MediaBrowser.Api.UserLibrary
var folders = await _userViewManager.GetUserViews(query, CancellationToken.None).ConfigureAwait(false);
var dtoOptions = GetDtoOptions(_authContext, request);
- dtoOptions.Fields.Add(ItemFields.PrimaryImageAspectRatio);
- dtoOptions.Fields.Add(ItemFields.DisplayPreferencesId);
- dtoOptions.Fields.Remove(ItemFields.SyncInfo);
- dtoOptions.Fields.Remove(ItemFields.BasicSyncInfo);
+ var fields = dtoOptions.Fields.ToList();
+
+ fields.Add(ItemFields.PrimaryImageAspectRatio);
+ fields.Add(ItemFields.DisplayPreferencesId);
+ fields.Remove(ItemFields.SyncInfo);
+ fields.Remove(ItemFields.BasicSyncInfo);
+ dtoOptions.Fields = fields.ToArray(fields.Count);
var user = _userManager.GetUserById(request.UserId);
@@ -107,13 +111,10 @@ namespace MediaBrowser.Api.UserLibrary
{
var user = _userManager.GetUserById(request.UserId);
- var views = user.RootFolder
+ var list = user.RootFolder
.GetChildren(user, true)
.OfType<Folder>()
.Where(UserView.IsEligibleForGrouping)
- .ToList();
-
- var list = views
.Select(i => new SpecialViewOption
{
Name = i.Name,
@@ -121,7 +122,7 @@ namespace MediaBrowser.Api.UserLibrary
})
.OrderBy(i => i.Name)
- .ToList();
+ .ToArray();
return ToOptimizedResult(list);
}
diff --git a/MediaBrowser.Api/UserLibrary/YearsService.cs b/MediaBrowser.Api/UserLibrary/YearsService.cs
index 1059b72cb..db622a9b3 100644
--- a/MediaBrowser.Api/UserLibrary/YearsService.cs
+++ b/MediaBrowser.Api/UserLibrary/YearsService.cs
@@ -96,11 +96,9 @@ namespace MediaBrowser.Api.UserLibrary
/// <param name="request">The request.</param>
/// <param name="items">The items.</param>
/// <returns>IEnumerable{Tuple{System.StringFunc{System.Int32}}}.</returns>
- protected override IEnumerable<BaseItem> GetAllItems(GetItemsByName request, IEnumerable<BaseItem> items)
+ protected override IEnumerable<BaseItem> GetAllItems(GetItemsByName request, IList<BaseItem> items)
{
- var itemsList = items.Where(i => i.ProductionYear != null).ToList();
-
- return itemsList
+ return items
.Select(i => i.ProductionYear ?? 0)
.Where(i => i > 0)
.Distinct()
diff --git a/MediaBrowser.Api/UserService.cs b/MediaBrowser.Api/UserService.cs
index 49b7f6c15..512356b43 100644
--- a/MediaBrowser.Api/UserService.cs
+++ b/MediaBrowser.Api/UserService.cs
@@ -22,7 +22,7 @@ namespace MediaBrowser.Api
/// </summary>
[Route("/Users", "GET", Summary = "Gets a list of users")]
[Authenticated]
- public class GetUsers : IReturn<List<UserDto>>
+ public class GetUsers : IReturn<UserDto[]>
{
[ApiMember(Name = "IsHidden", Description = "Optional filter by IsHidden=true or false", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
public bool? IsHidden { get; set; }
@@ -35,7 +35,7 @@ namespace MediaBrowser.Api
}
[Route("/Users/Public", "GET", Summary = "Gets a list of publicly visible users for display on a login screen.")]
- public class GetPublicUsers : IReturn<List<UserDto>>
+ public class GetPublicUsers : IReturn<UserDto[]>
{
}
@@ -329,7 +329,7 @@ namespace MediaBrowser.Api
var result = users
.OrderBy(u => u.Name)
.Select(i => _userManager.GetUserDto(i, Request.RemoteIp))
- .ToList();
+ .ToArray();
return ToOptimizedResult(result);
}
@@ -387,7 +387,7 @@ namespace MediaBrowser.Api
throw new ResourceNotFoundException("User not found");
}
- await _sessionMananger.RevokeUserTokens(user.Id.ToString("N"), null).ConfigureAwait(false);
+ _sessionMananger.RevokeUserTokens(user.Id.ToString("N"), null);
await _userManager.DeleteUser(user).ConfigureAwait(false);
}
@@ -455,7 +455,7 @@ namespace MediaBrowser.Api
if (request.ResetPassword)
{
- await _userManager.ResetPassword(user).ConfigureAwait(false);
+ _userManager.ResetPassword(user);
}
else
{
@@ -466,24 +466,18 @@ namespace MediaBrowser.Api
throw new ArgumentException("Invalid user or password entered.");
}
- await _userManager.ChangePassword(user, request.NewPassword).ConfigureAwait(false);
+ _userManager.ChangePassword(user, request.NewPassword);
var currentToken = _authContext.GetAuthorizationInfo(Request).Token;
- await _sessionMananger.RevokeUserTokens(user.Id.ToString("N"), currentToken).ConfigureAwait(false);
+ _sessionMananger.RevokeUserTokens(user.Id.ToString("N"), currentToken);
}
}
public void Post(UpdateUserEasyPassword request)
{
- var task = PostAsync(request);
- Task.WaitAll(task);
- }
-
- public async Task PostAsync(UpdateUserEasyPassword request)
- {
AssertCanUpdateUser(_authContext, _userManager, request.Id, true);
-
+
var user = _userManager.GetUserById(request.Id);
if (user == null)
@@ -493,11 +487,11 @@ namespace MediaBrowser.Api
if (request.ResetPassword)
{
- await _userManager.ResetEasyPassword(user).ConfigureAwait(false);
+ _userManager.ResetEasyPassword(user);
}
else
{
- await _userManager.ChangeEasyPassword(user, request.NewPassword).ConfigureAwait(false);
+ _userManager.ChangeEasyPassword(user, request.NewPassword);
}
}
@@ -507,13 +501,6 @@ namespace MediaBrowser.Api
/// <param name="request">The request.</param>
public void Post(UpdateUser request)
{
- var task = PostAsync(request);
-
- Task.WaitAll(task);
- }
-
- public async Task PostAsync(UpdateUser request)
- {
// We need to parse this manually because we told service stack not to with IRequiresRequestStream
// https://code.google.com/p/servicestack/source/browse/trunk/Common/ServiceStack.Text/ServiceStack.Text/Controller/PathInfo.cs
var id = GetPathValue(1);
@@ -524,13 +511,18 @@ namespace MediaBrowser.Api
var user = _userManager.GetUserById(id);
- var task = string.Equals(user.Name, dtoUser.Name, StringComparison.Ordinal) ?
- _userManager.UpdateUser(user) :
- _userManager.RenameUser(user, dtoUser.Name);
+ if (string.Equals(user.Name, dtoUser.Name, StringComparison.Ordinal))
+ {
+ _userManager.UpdateUser(user);
+ }
+ else
+ {
+ var task = _userManager.RenameUser(user, dtoUser.Name);
- await task.ConfigureAwait(false);
+ Task.WaitAll(task);
+ }
- await _userManager.UpdateConfiguration(dtoUser.Id, dtoUser.Configuration);
+ _userManager.UpdateConfiguration(dtoUser.Id, dtoUser.Configuration);
}
/// <summary>
@@ -570,21 +562,14 @@ namespace MediaBrowser.Api
{
AssertCanUpdateUser(_authContext, _userManager, request.Id, false);
- var task = _userManager.UpdateConfiguration(request.Id, request);
+ _userManager.UpdateConfiguration(request.Id, request);
- Task.WaitAll(task);
}
public void Post(UpdateUserPolicy request)
{
- var task = UpdateUserPolicy(request);
- Task.WaitAll(task);
- }
-
- private async Task UpdateUserPolicy(UpdateUserPolicy request)
- {
var user = _userManager.GetUserById(request.Id);
-
+
// If removing admin access
if (!request.IsAdministrator && user.Policy.IsAdministrator)
{
@@ -609,10 +594,10 @@ namespace MediaBrowser.Api
}
var currentToken = _authContext.GetAuthorizationInfo(Request).Token;
- await _sessionMananger.RevokeUserTokens(user.Id.ToString("N"), currentToken).ConfigureAwait(false);
+ _sessionMananger.RevokeUserTokens(user.Id.ToString("N"), currentToken);
}
- await _userManager.UpdateUserPolicy(request.Id, request).ConfigureAwait(false);
+ _userManager.UpdateUserPolicy(request.Id, request);
}
}
}
diff --git a/MediaBrowser.Api/VideosService.cs b/MediaBrowser.Api/VideosService.cs
index 729b50c1f..3bd0497f7 100644
--- a/MediaBrowser.Api/VideosService.cs
+++ b/MediaBrowser.Api/VideosService.cs
@@ -19,7 +19,7 @@ namespace MediaBrowser.Api
{
[Route("/Videos/{Id}/AdditionalParts", "GET", Summary = "Gets additional parts for a video.")]
[Authenticated]
- public class GetAdditionalParts : IReturn<ItemsResult>
+ public class GetAdditionalParts : IReturn<QueryResult<BaseItemDto>>
{
[ApiMember(Name = "UserId", Description = "Optional. Filter by user id, and attach user data", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
public string UserId { get; set; }
@@ -99,7 +99,7 @@ namespace MediaBrowser.Api
items = new BaseItemDto[] { };
}
- var result = new ItemsResult
+ var result = new QueryResult<BaseItemDto>
{
Items = items,
TotalRecordCount = items.Length
@@ -126,7 +126,7 @@ namespace MediaBrowser.Api
await link.UpdateToRepository(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false);
}
- video.LinkedAlternateVersions.Clear();
+ video.LinkedAlternateVersions = Video.EmptyLinkedChildArray;
await video.UpdateToRepository(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false);
}
@@ -140,7 +140,6 @@ namespace MediaBrowser.Api
public async Task PostAsync(MergeVersions request)
{
var items = request.Ids.Split(',')
- .Select(i => new Guid(i))
.Select(i => _libraryManager.GetItemById(i))
.OfType<Video>()
.ToList();
@@ -185,19 +184,23 @@ namespace MediaBrowser.Api
}).First();
}
+ var list = primaryVersion.LinkedAlternateVersions.ToList();
+
foreach (var item in items.Where(i => i.Id != primaryVersion.Id))
{
item.PrimaryVersionId = primaryVersion.Id.ToString("N");
await item.UpdateToRepository(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false);
- primaryVersion.LinkedAlternateVersions.Add(new LinkedChild
+ list.Add(new LinkedChild
{
Path = item.Path,
ItemId = item.Id
});
}
+ primaryVersion.LinkedAlternateVersions = list.ToArray();
+
await primaryVersion.UpdateToRepository(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false);
}
}