mirror of
https://github.com/jellyfin/jellyfin.git
synced 2025-07-09 03:04:24 -04:00
commit
66ad1699e2
@ -37,11 +37,14 @@ namespace MediaBrowser.Api
|
|||||||
|
|
||||||
private readonly ISessionManager _sessionManager;
|
private readonly ISessionManager _sessionManager;
|
||||||
|
|
||||||
|
public readonly SemaphoreSlim TranscodingStartLock = new SemaphoreSlim(1, 1);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="ApiEntryPoint" /> class.
|
/// Initializes a new instance of the <see cref="ApiEntryPoint" /> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="logger">The logger.</param>
|
/// <param name="logger">The logger.</param>
|
||||||
/// <param name="appPaths">The application paths.</param>
|
/// <param name="appPaths">The application paths.</param>
|
||||||
|
/// <param name="sessionManager">The session manager.</param>
|
||||||
public ApiEntryPoint(ILogger logger, IServerApplicationPaths appPaths, ISessionManager sessionManager)
|
public ApiEntryPoint(ILogger logger, IServerApplicationPaths appPaths, ISessionManager sessionManager)
|
||||||
{
|
{
|
||||||
Logger = logger;
|
Logger = logger;
|
||||||
@ -99,7 +102,7 @@ namespace MediaBrowser.Api
|
|||||||
{
|
{
|
||||||
var jobCount = _activeTranscodingJobs.Count;
|
var jobCount = _activeTranscodingJobs.Count;
|
||||||
|
|
||||||
Parallel.ForEach(_activeTranscodingJobs.ToList(), j => KillTranscodingJob(j, true));
|
Parallel.ForEach(_activeTranscodingJobs.ToList(), j => KillTranscodingJob(j, path => true));
|
||||||
|
|
||||||
// Try to allow for some time to kill the ffmpeg processes and delete the partial stream files
|
// Try to allow for some time to kill the ffmpeg processes and delete the partial stream files
|
||||||
if (jobCount > 0)
|
if (jobCount > 0)
|
||||||
@ -119,14 +122,12 @@ namespace MediaBrowser.Api
|
|||||||
/// <param name="path">The path.</param>
|
/// <param name="path">The path.</param>
|
||||||
/// <param name="type">The type.</param>
|
/// <param name="type">The type.</param>
|
||||||
/// <param name="process">The process.</param>
|
/// <param name="process">The process.</param>
|
||||||
/// <param name="startTimeTicks">The start time ticks.</param>
|
|
||||||
/// <param name="deviceId">The device id.</param>
|
/// <param name="deviceId">The device id.</param>
|
||||||
/// <param name="state">The state.</param>
|
/// <param name="state">The state.</param>
|
||||||
/// <param name="cancellationTokenSource">The cancellation token source.</param>
|
/// <param name="cancellationTokenSource">The cancellation token source.</param>
|
||||||
public void OnTranscodeBeginning(string path,
|
public void OnTranscodeBeginning(string path,
|
||||||
TranscodingJobType type,
|
TranscodingJobType type,
|
||||||
Process process,
|
Process process,
|
||||||
long? startTimeTicks,
|
|
||||||
string deviceId,
|
string deviceId,
|
||||||
StreamState state,
|
StreamState state,
|
||||||
CancellationTokenSource cancellationTokenSource)
|
CancellationTokenSource cancellationTokenSource)
|
||||||
@ -139,7 +140,6 @@ namespace MediaBrowser.Api
|
|||||||
Path = path,
|
Path = path,
|
||||||
Process = process,
|
Process = process,
|
||||||
ActiveRequestCount = 1,
|
ActiveRequestCount = 1,
|
||||||
StartTimeTicks = startTimeTicks,
|
|
||||||
DeviceId = deviceId,
|
DeviceId = deviceId,
|
||||||
CancellationTokenSource = cancellationTokenSource
|
CancellationTokenSource = cancellationTokenSource
|
||||||
});
|
});
|
||||||
@ -214,10 +214,15 @@ namespace MediaBrowser.Api
|
|||||||
/// <param name="type">The type.</param>
|
/// <param name="type">The type.</param>
|
||||||
/// <returns><c>true</c> if [has active transcoding job] [the specified path]; otherwise, <c>false</c>.</returns>
|
/// <returns><c>true</c> if [has active transcoding job] [the specified path]; otherwise, <c>false</c>.</returns>
|
||||||
public bool HasActiveTranscodingJob(string path, TranscodingJobType type)
|
public bool HasActiveTranscodingJob(string path, TranscodingJobType type)
|
||||||
|
{
|
||||||
|
return GetTranscodingJob(path, type) != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TranscodingJob GetTranscodingJob(string path, TranscodingJobType type)
|
||||||
{
|
{
|
||||||
lock (_activeTranscodingJobs)
|
lock (_activeTranscodingJobs)
|
||||||
{
|
{
|
||||||
return _activeTranscodingJobs.Any(j => j.Type == type && j.Path.Equals(path, StringComparison.OrdinalIgnoreCase));
|
return _activeTranscodingJobs.FirstOrDefault(j => j.Type == type && j.Path.Equals(path, StringComparison.OrdinalIgnoreCase));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -290,34 +295,70 @@ namespace MediaBrowser.Api
|
|||||||
{
|
{
|
||||||
var job = (TranscodingJob)state;
|
var job = (TranscodingJob)state;
|
||||||
|
|
||||||
KillTranscodingJob(job, true);
|
KillTranscodingJob(job, path => true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Kills the single transcoding job.
|
/// Kills the single transcoding job.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="deviceId">The device id.</param>
|
/// <param name="deviceId">The device id.</param>
|
||||||
/// <param name="deleteFiles">if set to <c>true</c> [delete files].</param>
|
/// <param name="deleteFiles">The delete files.</param>
|
||||||
|
/// <param name="acquireLock">if set to <c>true</c> [acquire lock].</param>
|
||||||
|
/// <returns>Task.</returns>
|
||||||
|
/// <exception cref="ArgumentNullException">deviceId</exception>
|
||||||
/// <exception cref="System.ArgumentNullException">sourcePath</exception>
|
/// <exception cref="System.ArgumentNullException">sourcePath</exception>
|
||||||
internal void KillTranscodingJobs(string deviceId, bool deleteFiles)
|
internal Task KillTranscodingJobs(string deviceId, Func<string, bool> deleteFiles, bool acquireLock)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(deviceId))
|
if (string.IsNullOrEmpty(deviceId))
|
||||||
{
|
{
|
||||||
throw new ArgumentNullException("deviceId");
|
throw new ArgumentNullException("deviceId");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return KillTranscodingJobs(j => string.Equals(deviceId, j.DeviceId, StringComparison.OrdinalIgnoreCase), deleteFiles, acquireLock);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Kills the transcoding jobs.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="killJob">The kill job.</param>
|
||||||
|
/// <param name="deleteFiles">The delete files.</param>
|
||||||
|
/// <param name="acquireLock">if set to <c>true</c> [acquire lock].</param>
|
||||||
|
/// <returns>Task.</returns>
|
||||||
|
/// <exception cref="System.ArgumentNullException">deviceId</exception>
|
||||||
|
internal async Task KillTranscodingJobs(Func<TranscodingJob,bool> killJob, Func<string, bool> deleteFiles, bool acquireLock)
|
||||||
|
{
|
||||||
var jobs = new List<TranscodingJob>();
|
var jobs = new List<TranscodingJob>();
|
||||||
|
|
||||||
lock (_activeTranscodingJobs)
|
lock (_activeTranscodingJobs)
|
||||||
{
|
{
|
||||||
// This is really only needed for HLS.
|
// This is really only needed for HLS.
|
||||||
// Progressive streams can stop on their own reliably
|
// Progressive streams can stop on their own reliably
|
||||||
jobs.AddRange(_activeTranscodingJobs.Where(i => string.Equals(deviceId, i.DeviceId, StringComparison.OrdinalIgnoreCase)));
|
jobs.AddRange(_activeTranscodingJobs.Where(killJob));
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var job in jobs)
|
if (jobs.Count == 0)
|
||||||
{
|
{
|
||||||
KillTranscodingJob(job, deleteFiles);
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (acquireLock)
|
||||||
|
{
|
||||||
|
await TranscodingStartLock.WaitAsync(CancellationToken.None).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
foreach (var job in jobs)
|
||||||
|
{
|
||||||
|
KillTranscodingJob(job, deleteFiles);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if (acquireLock)
|
||||||
|
{
|
||||||
|
TranscodingStartLock.Release();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -325,8 +366,8 @@ namespace MediaBrowser.Api
|
|||||||
/// Kills the transcoding job.
|
/// Kills the transcoding job.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="job">The job.</param>
|
/// <param name="job">The job.</param>
|
||||||
/// <param name="deleteFiles">if set to <c>true</c> [delete files].</param>
|
/// <param name="delete">The delete.</param>
|
||||||
private void KillTranscodingJob(TranscodingJob job, bool deleteFiles)
|
private void KillTranscodingJob(TranscodingJob job, Func<string, bool> delete)
|
||||||
{
|
{
|
||||||
lock (_activeTranscodingJobs)
|
lock (_activeTranscodingJobs)
|
||||||
{
|
{
|
||||||
@ -378,7 +419,7 @@ namespace MediaBrowser.Api
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (deleteFiles)
|
if (delete(job.Path))
|
||||||
{
|
{
|
||||||
DeletePartialStreamFiles(job.Path, job.Type, 0, 1500);
|
DeletePartialStreamFiles(job.Path, job.Type, 0, 1500);
|
||||||
}
|
}
|
||||||
@ -386,7 +427,7 @@ namespace MediaBrowser.Api
|
|||||||
|
|
||||||
private async void DeletePartialStreamFiles(string path, TranscodingJobType jobType, int retryCount, int delayMs)
|
private async void DeletePartialStreamFiles(string path, TranscodingJobType jobType, int retryCount, int delayMs)
|
||||||
{
|
{
|
||||||
if (retryCount >= 8)
|
if (retryCount >= 10)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -440,6 +481,8 @@ namespace MediaBrowser.Api
|
|||||||
.Where(f => f.IndexOf(name, StringComparison.OrdinalIgnoreCase) != -1)
|
.Where(f => f.IndexOf(name, StringComparison.OrdinalIgnoreCase) != -1)
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
|
Exception e = null;
|
||||||
|
|
||||||
foreach (var file in filesToDelete)
|
foreach (var file in filesToDelete)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@ -449,9 +492,15 @@ namespace MediaBrowser.Api
|
|||||||
}
|
}
|
||||||
catch (IOException ex)
|
catch (IOException ex)
|
||||||
{
|
{
|
||||||
|
e = ex;
|
||||||
Logger.ErrorException("Error deleting HLS file {0}", ex, file);
|
Logger.ErrorException("Error deleting HLS file {0}", ex, file);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (e != null)
|
||||||
|
{
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -486,12 +535,13 @@ namespace MediaBrowser.Api
|
|||||||
/// <value>The kill timer.</value>
|
/// <value>The kill timer.</value>
|
||||||
public Timer KillTimer { get; set; }
|
public Timer KillTimer { get; set; }
|
||||||
|
|
||||||
public long? StartTimeTicks { get; set; }
|
|
||||||
public string DeviceId { get; set; }
|
public string DeviceId { get; set; }
|
||||||
|
|
||||||
public CancellationTokenSource CancellationTokenSource { get; set; }
|
public CancellationTokenSource CancellationTokenSource { get; set; }
|
||||||
|
|
||||||
public object ProcessLock = new object();
|
public object ProcessLock = new object();
|
||||||
|
|
||||||
|
public bool HasExited { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
using MediaBrowser.Common.IO;
|
using MediaBrowser.Common.IO;
|
||||||
|
using MediaBrowser.Controller.Net;
|
||||||
using MediaBrowser.Controller.Themes;
|
using MediaBrowser.Controller.Themes;
|
||||||
using MediaBrowser.Model.Themes;
|
using MediaBrowser.Model.Themes;
|
||||||
using ServiceStack;
|
using ServiceStack;
|
||||||
@ -47,6 +48,7 @@ namespace MediaBrowser.Api
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Authenticated]
|
||||||
public class AppThemeService : BaseApiService
|
public class AppThemeService : BaseApiService
|
||||||
{
|
{
|
||||||
private readonly IAppThemeManager _themeManager;
|
private readonly IAppThemeManager _themeManager;
|
||||||
@ -92,7 +94,7 @@ namespace MediaBrowser.Api
|
|||||||
|
|
||||||
var contentType = MimeTypes.GetMimeType(info.Path);
|
var contentType = MimeTypes.GetMimeType(info.Path);
|
||||||
|
|
||||||
return ToCachedResult(cacheGuid, info.DateModified, cacheDuration, () => _fileSystem.GetFileStream(info.Path, FileMode.Open, FileAccess.Read, FileShare.Read), contentType);
|
return ResultFactory.GetCachedResult(Request, cacheGuid, null, cacheDuration, () => _fileSystem.GetFileStream(info.Path, FileMode.Open, FileAccess.Read, FileShare.Read), contentType);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,190 +0,0 @@
|
|||||||
using MediaBrowser.Controller.Entities;
|
|
||||||
using MediaBrowser.Controller.Library;
|
|
||||||
using MediaBrowser.Controller.Session;
|
|
||||||
using MediaBrowser.Model.Logging;
|
|
||||||
using ServiceStack.Web;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
|
|
||||||
namespace MediaBrowser.Api
|
|
||||||
{
|
|
||||||
public class AuthorizationRequestFilterAttribute : Attribute, IHasRequestFilter
|
|
||||||
{
|
|
||||||
//This property will be resolved by the IoC container
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the user manager.
|
|
||||||
/// </summary>
|
|
||||||
/// <value>The user manager.</value>
|
|
||||||
public IUserManager UserManager { get; set; }
|
|
||||||
|
|
||||||
public ISessionManager SessionManager { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the logger.
|
|
||||||
/// </summary>
|
|
||||||
/// <value>The logger.</value>
|
|
||||||
public ILogger Logger { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The request filter is executed before the service.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="request">The http request wrapper</param>
|
|
||||||
/// <param name="response">The http response wrapper</param>
|
|
||||||
/// <param name="requestDto">The request DTO</param>
|
|
||||||
public void RequestFilter(IRequest request, IResponse response, object requestDto)
|
|
||||||
{
|
|
||||||
//This code is executed before the service
|
|
||||||
var auth = GetAuthorizationDictionary(request);
|
|
||||||
|
|
||||||
if (auth != null)
|
|
||||||
{
|
|
||||||
User user = null;
|
|
||||||
|
|
||||||
if (auth.ContainsKey("UserId"))
|
|
||||||
{
|
|
||||||
var userId = auth["UserId"];
|
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(userId))
|
|
||||||
{
|
|
||||||
user = UserManager.GetUserById(new Guid(userId));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
string deviceId;
|
|
||||||
string device;
|
|
||||||
string client;
|
|
||||||
string version;
|
|
||||||
|
|
||||||
auth.TryGetValue("DeviceId", out deviceId);
|
|
||||||
auth.TryGetValue("Device", out device);
|
|
||||||
auth.TryGetValue("Client", out client);
|
|
||||||
auth.TryGetValue("Version", out version);
|
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(client) && !string.IsNullOrEmpty(deviceId) && !string.IsNullOrEmpty(device) && !string.IsNullOrEmpty(version))
|
|
||||||
{
|
|
||||||
var remoteEndPoint = request.RemoteIp;
|
|
||||||
|
|
||||||
SessionManager.LogSessionActivity(client, version, deviceId, device, remoteEndPoint, user);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the auth.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="httpReq">The HTTP req.</param>
|
|
||||||
/// <returns>Dictionary{System.StringSystem.String}.</returns>
|
|
||||||
private static Dictionary<string, string> GetAuthorizationDictionary(IRequest httpReq)
|
|
||||||
{
|
|
||||||
var auth = httpReq.Headers["Authorization"];
|
|
||||||
|
|
||||||
return GetAuthorization(auth);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static User GetCurrentUser(IRequest httpReq, IUserManager userManager)
|
|
||||||
{
|
|
||||||
var info = GetAuthorization(httpReq);
|
|
||||||
|
|
||||||
return string.IsNullOrEmpty(info.UserId) ? null :
|
|
||||||
userManager.GetUserById(new Guid(info.UserId));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the authorization.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="httpReq">The HTTP req.</param>
|
|
||||||
/// <returns>Dictionary{System.StringSystem.String}.</returns>
|
|
||||||
public static AuthorizationInfo GetAuthorization(IRequest httpReq)
|
|
||||||
{
|
|
||||||
var auth = GetAuthorizationDictionary(httpReq);
|
|
||||||
|
|
||||||
string userId = null;
|
|
||||||
string deviceId = null;
|
|
||||||
string device = null;
|
|
||||||
string client = null;
|
|
||||||
string version = null;
|
|
||||||
|
|
||||||
if (auth != null)
|
|
||||||
{
|
|
||||||
auth.TryGetValue("UserId", out userId);
|
|
||||||
auth.TryGetValue("DeviceId", out deviceId);
|
|
||||||
auth.TryGetValue("Device", out device);
|
|
||||||
auth.TryGetValue("Client", out client);
|
|
||||||
auth.TryGetValue("Version", out version);
|
|
||||||
}
|
|
||||||
|
|
||||||
return new AuthorizationInfo
|
|
||||||
{
|
|
||||||
Client = client,
|
|
||||||
Device = device,
|
|
||||||
DeviceId = deviceId,
|
|
||||||
UserId = userId,
|
|
||||||
Version = version
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the authorization.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="authorizationHeader">The authorization header.</param>
|
|
||||||
/// <returns>Dictionary{System.StringSystem.String}.</returns>
|
|
||||||
private static Dictionary<string, string> GetAuthorization(string authorizationHeader)
|
|
||||||
{
|
|
||||||
if (authorizationHeader == null) return null;
|
|
||||||
|
|
||||||
var parts = authorizationHeader.Split(' ');
|
|
||||||
|
|
||||||
// There should be at least to parts
|
|
||||||
if (parts.Length < 2) return null;
|
|
||||||
|
|
||||||
// It has to be a digest request
|
|
||||||
if (!string.Equals(parts[0], "MediaBrowser", StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove uptil the first space
|
|
||||||
authorizationHeader = authorizationHeader.Substring(authorizationHeader.IndexOf(' '));
|
|
||||||
parts = authorizationHeader.Split(',');
|
|
||||||
|
|
||||||
var result = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
|
||||||
|
|
||||||
foreach (var item in parts)
|
|
||||||
{
|
|
||||||
var param = item.Trim().Split(new[] { '=' }, 2);
|
|
||||||
result.Add(param[0], param[1].Trim(new[] { '"' }));
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// A new shallow copy of this filter is used on every request.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>IHasRequestFilter.</returns>
|
|
||||||
public IHasRequestFilter Copy()
|
|
||||||
{
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Order in which Request Filters are executed.
|
|
||||||
/// <0 Executed before global request filters
|
|
||||||
/// >0 Executed after global request filters
|
|
||||||
/// </summary>
|
|
||||||
/// <value>The priority.</value>
|
|
||||||
public int Priority
|
|
||||||
{
|
|
||||||
get { return 0; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class AuthorizationInfo
|
|
||||||
{
|
|
||||||
public string UserId;
|
|
||||||
public string DeviceId;
|
|
||||||
public string Device;
|
|
||||||
public string Client;
|
|
||||||
public string Version;
|
|
||||||
}
|
|
||||||
}
|
|
@ -14,8 +14,7 @@ namespace MediaBrowser.Api
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Class BaseApiService
|
/// Class BaseApiService
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[AuthorizationRequestFilter]
|
public class BaseApiService : IHasResultFactory, IRestfulService, IHasSession
|
||||||
public class BaseApiService : IHasResultFactory, IRestfulService
|
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the logger.
|
/// Gets or sets the logger.
|
||||||
@ -35,6 +34,8 @@ namespace MediaBrowser.Api
|
|||||||
/// <value>The request context.</value>
|
/// <value>The request context.</value>
|
||||||
public IRequest Request { get; set; }
|
public IRequest Request { get; set; }
|
||||||
|
|
||||||
|
public ISessionContext SessionContext { get; set; }
|
||||||
|
|
||||||
public string GetHeader(string name)
|
public string GetHeader(string name)
|
||||||
{
|
{
|
||||||
return Request.Headers[name];
|
return Request.Headers[name];
|
||||||
@ -82,33 +83,18 @@ namespace MediaBrowser.Api
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the session.
|
/// Gets the session.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="sessionManager">The session manager.</param>
|
|
||||||
/// <returns>SessionInfo.</returns>
|
/// <returns>SessionInfo.</returns>
|
||||||
protected SessionInfo GetSession(ISessionManager sessionManager)
|
/// <exception cref="System.ArgumentException">Session not found.</exception>
|
||||||
|
protected SessionInfo GetSession()
|
||||||
{
|
{
|
||||||
var auth = AuthorizationRequestFilterAttribute.GetAuthorization(Request);
|
var session = SessionContext.GetSession(Request);
|
||||||
|
|
||||||
return sessionManager.Sessions.First(i => string.Equals(i.DeviceId, auth.DeviceId) &&
|
if (session == null)
|
||||||
string.Equals(i.Client, auth.Client) &&
|
{
|
||||||
string.Equals(i.ApplicationVersion, auth.Version));
|
throw new ArgumentException("Session not found.");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
return session;
|
||||||
/// To the cached result.
|
|
||||||
/// </summary>
|
|
||||||
/// <typeparam name="T"></typeparam>
|
|
||||||
/// <param name="cacheKey">The cache key.</param>
|
|
||||||
/// <param name="lastDateModified">The last date modified.</param>
|
|
||||||
/// <param name="cacheDuration">Duration of the cache.</param>
|
|
||||||
/// <param name="factoryFn">The factory fn.</param>
|
|
||||||
/// <param name="contentType">Type of the content.</param>
|
|
||||||
/// <param name="responseHeaders">The response headers.</param>
|
|
||||||
/// <returns>System.Object.</returns>
|
|
||||||
/// <exception cref="System.ArgumentNullException">cacheKey</exception>
|
|
||||||
protected object ToCachedResult<T>(Guid cacheKey, DateTime? lastDateModified, TimeSpan? cacheDuration, Func<T> factoryFn, string contentType, IDictionary<string,string> responseHeaders = null)
|
|
||||||
where T : class
|
|
||||||
{
|
|
||||||
return ResultFactory.GetCachedResult(Request, cacheKey, lastDateModified, cacheDuration, factoryFn, contentType, responseHeaders);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -121,7 +107,7 @@ namespace MediaBrowser.Api
|
|||||||
return ResultFactory.GetStaticFileResult(Request, path);
|
return ResultFactory.GetStaticFileResult(Request, path);
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly char[] _dashReplaceChars = new[] { '?', '/' };
|
private readonly char[] _dashReplaceChars = { '?', '/' };
|
||||||
private const char SlugChar = '-';
|
private const char SlugChar = '-';
|
||||||
|
|
||||||
protected MusicArtist GetArtist(string name, ILibraryManager libraryManager)
|
protected MusicArtist GetArtist(string name, ILibraryManager libraryManager)
|
||||||
@ -154,7 +140,7 @@ namespace MediaBrowser.Api
|
|||||||
return libraryManager.GetPerson(DeSlugPersonName(name, libraryManager));
|
return libraryManager.GetPerson(DeSlugPersonName(name, libraryManager));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected IList<BaseItem> GetAllLibraryItems(Guid? userId, IUserManager userManager, ILibraryManager libraryManager, string parentId = null)
|
protected IEnumerable<BaseItem> GetAllLibraryItems(Guid? userId, IUserManager userManager, ILibraryManager libraryManager, string parentId = null)
|
||||||
{
|
{
|
||||||
if (!string.IsNullOrEmpty(parentId))
|
if (!string.IsNullOrEmpty(parentId))
|
||||||
{
|
{
|
||||||
@ -164,7 +150,12 @@ namespace MediaBrowser.Api
|
|||||||
{
|
{
|
||||||
var user = userManager.GetUserById(userId.Value);
|
var user = userManager.GetUserById(userId.Value);
|
||||||
|
|
||||||
return folder.GetRecursiveChildren(user).ToList();
|
if (user == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentException("User not found");
|
||||||
|
}
|
||||||
|
|
||||||
|
return folder.GetRecursiveChildren(user);
|
||||||
}
|
}
|
||||||
|
|
||||||
return folder.GetRecursiveChildren();
|
return folder.GetRecursiveChildren();
|
||||||
@ -173,7 +164,12 @@ namespace MediaBrowser.Api
|
|||||||
{
|
{
|
||||||
var user = userManager.GetUserById(userId.Value);
|
var user = userManager.GetUserById(userId.Value);
|
||||||
|
|
||||||
return userManager.GetUserById(userId.Value).RootFolder.GetRecursiveChildren(user, null);
|
if (user == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentException("User not found");
|
||||||
|
}
|
||||||
|
|
||||||
|
return userManager.GetUserById(userId.Value).RootFolder.GetRecursiveChildren(user);
|
||||||
}
|
}
|
||||||
|
|
||||||
return libraryManager.RootFolder.GetRecursiveChildren();
|
return libraryManager.RootFolder.GetRecursiveChildren();
|
||||||
@ -234,7 +230,8 @@ namespace MediaBrowser.Api
|
|||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
|
|
||||||
return libraryManager.RootFolder.GetRecursiveChildren(i => i is Game)
|
return libraryManager.RootFolder.GetRecursiveChildren()
|
||||||
|
.OfType<Game>()
|
||||||
.SelectMany(i => i.Genres)
|
.SelectMany(i => i.Genres)
|
||||||
.Distinct(StringComparer.OrdinalIgnoreCase)
|
.Distinct(StringComparer.OrdinalIgnoreCase)
|
||||||
.FirstOrDefault(i =>
|
.FirstOrDefault(i =>
|
||||||
|
28
MediaBrowser.Api/BrandingService.cs
Normal file
28
MediaBrowser.Api/BrandingService.cs
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
using MediaBrowser.Common.Configuration;
|
||||||
|
using MediaBrowser.Model.Branding;
|
||||||
|
using ServiceStack;
|
||||||
|
|
||||||
|
namespace MediaBrowser.Api
|
||||||
|
{
|
||||||
|
[Route("/Branding/Configuration", "GET", Summary = "Gets branding configuration")]
|
||||||
|
public class GetBrandingOptions : IReturn<BrandingOptions>
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public class BrandingService : BaseApiService
|
||||||
|
{
|
||||||
|
private readonly IConfigurationManager _config;
|
||||||
|
|
||||||
|
public BrandingService(IConfigurationManager config)
|
||||||
|
{
|
||||||
|
_config = config;
|
||||||
|
}
|
||||||
|
|
||||||
|
public object Get(GetBrandingOptions request)
|
||||||
|
{
|
||||||
|
var result = _config.GetConfiguration<BrandingOptions>("branding");
|
||||||
|
|
||||||
|
return ToOptimizedResult(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,4 +1,5 @@
|
|||||||
using MediaBrowser.Controller.Channels;
|
using MediaBrowser.Controller.Channels;
|
||||||
|
using MediaBrowser.Controller.Net;
|
||||||
using MediaBrowser.Model.Channels;
|
using MediaBrowser.Model.Channels;
|
||||||
using MediaBrowser.Model.Dto;
|
using MediaBrowser.Model.Dto;
|
||||||
using MediaBrowser.Model.Entities;
|
using MediaBrowser.Model.Entities;
|
||||||
@ -8,6 +9,7 @@ using System;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace MediaBrowser.Api
|
namespace MediaBrowser.Api
|
||||||
{
|
{
|
||||||
@ -173,6 +175,7 @@ namespace MediaBrowser.Api
|
|||||||
public string UserId { get; set; }
|
public string UserId { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Authenticated]
|
||||||
public class ChannelService : BaseApiService
|
public class ChannelService : BaseApiService
|
||||||
{
|
{
|
||||||
private readonly IChannelManager _channelManager;
|
private readonly IChannelManager _channelManager;
|
||||||
@ -196,14 +199,14 @@ namespace MediaBrowser.Api
|
|||||||
return ToOptimizedResult(result);
|
return ToOptimizedResult(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
public object Get(GetChannelFolder request)
|
public async Task<object> Get(GetChannelFolder request)
|
||||||
{
|
{
|
||||||
return ToOptimizedResult(_channelManager.GetChannelFolder(request.UserId, CancellationToken.None).Result);
|
return ToOptimizedResult(await _channelManager.GetChannelFolder(request.UserId, CancellationToken.None).ConfigureAwait(false));
|
||||||
}
|
}
|
||||||
|
|
||||||
public object Get(GetChannels request)
|
public async Task<object> Get(GetChannels request)
|
||||||
{
|
{
|
||||||
var result = _channelManager.GetChannels(new ChannelQuery
|
var result = await _channelManager.GetChannels(new ChannelQuery
|
||||||
{
|
{
|
||||||
Limit = request.Limit,
|
Limit = request.Limit,
|
||||||
StartIndex = request.StartIndex,
|
StartIndex = request.StartIndex,
|
||||||
@ -211,14 +214,14 @@ namespace MediaBrowser.Api
|
|||||||
SupportsLatestItems = request.SupportsLatestItems,
|
SupportsLatestItems = request.SupportsLatestItems,
|
||||||
IsFavorite = request.IsFavorite
|
IsFavorite = request.IsFavorite
|
||||||
|
|
||||||
}, CancellationToken.None).Result;
|
}, CancellationToken.None).ConfigureAwait(false);
|
||||||
|
|
||||||
return ToOptimizedResult(result);
|
return ToOptimizedResult(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
public object Get(GetChannelItems request)
|
public async Task<object> Get(GetChannelItems request)
|
||||||
{
|
{
|
||||||
var result = _channelManager.GetChannelItems(new ChannelItemQuery
|
var result = await _channelManager.GetChannelItems(new ChannelItemQuery
|
||||||
{
|
{
|
||||||
Limit = request.Limit,
|
Limit = request.Limit,
|
||||||
StartIndex = request.StartIndex,
|
StartIndex = request.StartIndex,
|
||||||
@ -228,16 +231,16 @@ namespace MediaBrowser.Api
|
|||||||
SortOrder = request.SortOrder,
|
SortOrder = request.SortOrder,
|
||||||
SortBy = (request.SortBy ?? string.Empty).Split(',').Where(i => !string.IsNullOrWhiteSpace(i)).ToArray(),
|
SortBy = (request.SortBy ?? string.Empty).Split(',').Where(i => !string.IsNullOrWhiteSpace(i)).ToArray(),
|
||||||
Filters = request.GetFilters().ToArray(),
|
Filters = request.GetFilters().ToArray(),
|
||||||
Fields = request.GetItemFields().ToList()
|
Fields = request.GetItemFields().ToArray()
|
||||||
|
|
||||||
}, CancellationToken.None).Result;
|
}, CancellationToken.None).ConfigureAwait(false);
|
||||||
|
|
||||||
return ToOptimizedResult(result);
|
return ToOptimizedResult(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
public object Get(GetLatestChannelItems request)
|
public async Task<object> Get(GetLatestChannelItems request)
|
||||||
{
|
{
|
||||||
var result = _channelManager.GetLatestChannelItems(new AllChannelMediaQuery
|
var result = await _channelManager.GetLatestChannelItems(new AllChannelMediaQuery
|
||||||
{
|
{
|
||||||
Limit = request.Limit,
|
Limit = request.Limit,
|
||||||
StartIndex = request.StartIndex,
|
StartIndex = request.StartIndex,
|
||||||
@ -246,7 +249,7 @@ namespace MediaBrowser.Api
|
|||||||
Filters = request.GetFilters().ToArray(),
|
Filters = request.GetFilters().ToArray(),
|
||||||
Fields = request.GetItemFields().ToList()
|
Fields = request.GetItemFields().ToList()
|
||||||
|
|
||||||
}, CancellationToken.None).Result;
|
}, CancellationToken.None).ConfigureAwait(false);
|
||||||
|
|
||||||
return ToOptimizedResult(result);
|
return ToOptimizedResult(result);
|
||||||
}
|
}
|
||||||
|
@ -1,15 +1,17 @@
|
|||||||
using MediaBrowser.Common.Extensions;
|
using MediaBrowser.Common.Extensions;
|
||||||
using MediaBrowser.Common.IO;
|
using MediaBrowser.Common.IO;
|
||||||
using MediaBrowser.Controller.Configuration;
|
using MediaBrowser.Controller.Configuration;
|
||||||
using MediaBrowser.Controller.Entities;
|
using MediaBrowser.Controller.Library;
|
||||||
using MediaBrowser.Controller.Entities.Movies;
|
using MediaBrowser.Controller.Net;
|
||||||
using MediaBrowser.Controller.Entities.TV;
|
|
||||||
using MediaBrowser.Controller.Providers;
|
using MediaBrowser.Controller.Providers;
|
||||||
using MediaBrowser.Model.Configuration;
|
using MediaBrowser.Model.Configuration;
|
||||||
using MediaBrowser.Model.Serialization;
|
using MediaBrowser.Model.Serialization;
|
||||||
using ServiceStack;
|
using ServiceStack;
|
||||||
|
using ServiceStack.Text.Controller;
|
||||||
|
using ServiceStack.Web;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
|
||||||
namespace MediaBrowser.Api
|
namespace MediaBrowser.Api
|
||||||
@ -18,35 +20,58 @@ namespace MediaBrowser.Api
|
|||||||
/// Class GetConfiguration
|
/// Class GetConfiguration
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Route("/System/Configuration", "GET", Summary = "Gets application configuration")]
|
[Route("/System/Configuration", "GET", Summary = "Gets application configuration")]
|
||||||
|
[Authenticated]
|
||||||
public class GetConfiguration : IReturn<ServerConfiguration>
|
public class GetConfiguration : IReturn<ServerConfiguration>
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Route("/System/Configuration/{Key}", "GET", Summary = "Gets a named configuration")]
|
||||||
|
[Authenticated]
|
||||||
|
public class GetNamedConfiguration
|
||||||
|
{
|
||||||
|
[ApiMember(Name = "Key", Description = "Key", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
|
||||||
|
public string Key { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Class UpdateConfiguration
|
/// Class UpdateConfiguration
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Route("/System/Configuration", "POST", Summary = "Updates application configuration")]
|
[Route("/System/Configuration", "POST", Summary = "Updates application configuration")]
|
||||||
|
[Authenticated]
|
||||||
public class UpdateConfiguration : ServerConfiguration, IReturnVoid
|
public class UpdateConfiguration : ServerConfiguration, IReturnVoid
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Route("/System/Configuration/{Key}", "POST", Summary = "Updates named configuration")]
|
||||||
|
[Authenticated]
|
||||||
|
public class UpdateNamedConfiguration : IReturnVoid, IRequiresRequestStream
|
||||||
|
{
|
||||||
|
[ApiMember(Name = "Key", Description = "Key", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
|
||||||
|
public string Key { get; set; }
|
||||||
|
|
||||||
|
public Stream RequestStream { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
[Route("/System/Configuration/MetadataOptions/Default", "GET", Summary = "Gets a default MetadataOptions object")]
|
[Route("/System/Configuration/MetadataOptions/Default", "GET", Summary = "Gets a default MetadataOptions object")]
|
||||||
|
[Authenticated]
|
||||||
public class GetDefaultMetadataOptions : IReturn<MetadataOptions>
|
public class GetDefaultMetadataOptions : IReturn<MetadataOptions>
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Route("/System/Configuration/MetadataPlugins", "GET", Summary = "Gets all available metadata plugins")]
|
[Route("/System/Configuration/MetadataPlugins", "GET", Summary = "Gets all available metadata plugins")]
|
||||||
|
[Authenticated]
|
||||||
public class GetMetadataPlugins : IReturn<List<MetadataPluginSummary>>
|
public class GetMetadataPlugins : IReturn<List<MetadataPluginSummary>>
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Route("/System/Configuration/VideoImageExtraction", "POST", Summary = "Updates image extraction for all types")]
|
[Route("/System/Configuration/MetadataPlugins/Autoset", "POST")]
|
||||||
public class UpdateVideoImageExtraction : IReturnVoid
|
[Authenticated]
|
||||||
|
public class AutoSetMetadataOptions : IReturnVoid
|
||||||
{
|
{
|
||||||
public bool Enabled { get; set; }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public class ConfigurationService : BaseApiService
|
public class ConfigurationService : BaseApiService
|
||||||
@ -63,13 +88,15 @@ namespace MediaBrowser.Api
|
|||||||
|
|
||||||
private readonly IFileSystem _fileSystem;
|
private readonly IFileSystem _fileSystem;
|
||||||
private readonly IProviderManager _providerManager;
|
private readonly IProviderManager _providerManager;
|
||||||
|
private readonly ILibraryManager _libraryManager;
|
||||||
|
|
||||||
public ConfigurationService(IJsonSerializer jsonSerializer, IServerConfigurationManager configurationManager, IFileSystem fileSystem, IProviderManager providerManager)
|
public ConfigurationService(IJsonSerializer jsonSerializer, IServerConfigurationManager configurationManager, IFileSystem fileSystem, IProviderManager providerManager, ILibraryManager libraryManager)
|
||||||
{
|
{
|
||||||
_jsonSerializer = jsonSerializer;
|
_jsonSerializer = jsonSerializer;
|
||||||
_configurationManager = configurationManager;
|
_configurationManager = configurationManager;
|
||||||
_fileSystem = fileSystem;
|
_fileSystem = fileSystem;
|
||||||
_providerManager = providerManager;
|
_providerManager = providerManager;
|
||||||
|
_libraryManager = libraryManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -88,6 +115,60 @@ namespace MediaBrowser.Api
|
|||||||
return ToOptimizedResultUsingCache(cacheKey, dateModified, null, () => _configurationManager.Configuration);
|
return ToOptimizedResultUsingCache(cacheKey, dateModified, null, () => _configurationManager.Configuration);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public object Get(GetNamedConfiguration request)
|
||||||
|
{
|
||||||
|
var result = _configurationManager.GetConfiguration(request.Key);
|
||||||
|
|
||||||
|
return ToOptimizedResult(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
const string XbmcMetadata = "Xbmc Nfo";
|
||||||
|
const string MediaBrowserMetadata = "Media Browser Xml";
|
||||||
|
|
||||||
|
public void Post(AutoSetMetadataOptions request)
|
||||||
|
{
|
||||||
|
var service = AutoDetectMetadataService();
|
||||||
|
|
||||||
|
Logger.Info("Setting preferred metadata format to " + service);
|
||||||
|
|
||||||
|
var serviceToDisable = string.Equals(service, XbmcMetadata) ?
|
||||||
|
MediaBrowserMetadata :
|
||||||
|
XbmcMetadata;
|
||||||
|
|
||||||
|
_configurationManager.DisableMetadataService(serviceToDisable);
|
||||||
|
_configurationManager.SaveConfiguration();
|
||||||
|
}
|
||||||
|
|
||||||
|
private string AutoDetectMetadataService()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var paths = _libraryManager.GetDefaultVirtualFolders()
|
||||||
|
.SelectMany(i => i.Locations)
|
||||||
|
.Distinct(StringComparer.OrdinalIgnoreCase)
|
||||||
|
.Select(i => new DirectoryInfo(i))
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
if (paths.SelectMany(i => i.EnumerateFiles("*.xml", SearchOption.AllDirectories))
|
||||||
|
.Any())
|
||||||
|
{
|
||||||
|
return XbmcMetadata;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (paths.SelectMany(i => i.EnumerateFiles("*.xml", SearchOption.AllDirectories))
|
||||||
|
.Any(i => string.Equals(i.Name, "series.xml", StringComparison.OrdinalIgnoreCase) || string.Equals(i.Name, "movie.xml", StringComparison.OrdinalIgnoreCase)))
|
||||||
|
{
|
||||||
|
return MediaBrowserMetadata;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return XbmcMetadata;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Posts the specified configuraiton.
|
/// Posts the specified configuraiton.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -95,7 +176,6 @@ namespace MediaBrowser.Api
|
|||||||
public void Post(UpdateConfiguration request)
|
public void Post(UpdateConfiguration request)
|
||||||
{
|
{
|
||||||
// Silly, but we need to serialize and deserialize or the XmlSerializer will write the xml with an element name of UpdateConfiguration
|
// Silly, but we need to serialize and deserialize or the XmlSerializer will write the xml with an element name of UpdateConfiguration
|
||||||
|
|
||||||
var json = _jsonSerializer.SerializeToString(request);
|
var json = _jsonSerializer.SerializeToString(request);
|
||||||
|
|
||||||
var config = _jsonSerializer.DeserializeFromString<ServerConfiguration>(json);
|
var config = _jsonSerializer.DeserializeFromString<ServerConfiguration>(json);
|
||||||
@ -103,6 +183,17 @@ namespace MediaBrowser.Api
|
|||||||
_configurationManager.ReplaceConfiguration(config);
|
_configurationManager.ReplaceConfiguration(config);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void Post(UpdateNamedConfiguration request)
|
||||||
|
{
|
||||||
|
var pathInfo = PathInfo.Parse(Request.PathInfo);
|
||||||
|
var key = pathInfo.GetArgumentValue<string>(2);
|
||||||
|
|
||||||
|
var configurationType = _configurationManager.GetConfigurationType(key);
|
||||||
|
var configuration = _jsonSerializer.DeserializeFromStream(request.RequestStream, configurationType);
|
||||||
|
|
||||||
|
_configurationManager.SaveConfiguration(key, configuration);
|
||||||
|
}
|
||||||
|
|
||||||
public object Get(GetDefaultMetadataOptions request)
|
public object Get(GetDefaultMetadataOptions request)
|
||||||
{
|
{
|
||||||
return ToOptimizedSerializedResultUsingCache(new MetadataOptions());
|
return ToOptimizedSerializedResultUsingCache(new MetadataOptions());
|
||||||
@ -112,71 +203,5 @@ namespace MediaBrowser.Api
|
|||||||
{
|
{
|
||||||
return ToOptimizedSerializedResultUsingCache(_providerManager.GetAllMetadataPlugins().ToList());
|
return ToOptimizedSerializedResultUsingCache(_providerManager.GetAllMetadataPlugins().ToList());
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// This is a temporary method used until image settings get broken out.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="request"></param>
|
|
||||||
public void Post(UpdateVideoImageExtraction request)
|
|
||||||
{
|
|
||||||
var config = _configurationManager.Configuration;
|
|
||||||
|
|
||||||
EnableImageExtractionForType(typeof(Movie), config, request.Enabled);
|
|
||||||
EnableImageExtractionForType(typeof(Episode), config, request.Enabled);
|
|
||||||
EnableImageExtractionForType(typeof(AdultVideo), config, request.Enabled);
|
|
||||||
EnableImageExtractionForType(typeof(MusicVideo), config, request.Enabled);
|
|
||||||
EnableImageExtractionForType(typeof(Video), config, request.Enabled);
|
|
||||||
EnableImageExtractionForType(typeof(Trailer), config, request.Enabled);
|
|
||||||
|
|
||||||
_configurationManager.SaveConfiguration();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void EnableImageExtractionForType(Type type, ServerConfiguration config, bool enabled)
|
|
||||||
{
|
|
||||||
var options = GetMetadataOptions(type, config);
|
|
||||||
|
|
||||||
const string imageProviderName = "Screen Grabber";
|
|
||||||
|
|
||||||
var contains = options.DisabledImageFetchers.Contains(imageProviderName, StringComparer.OrdinalIgnoreCase);
|
|
||||||
|
|
||||||
if (!enabled && !contains)
|
|
||||||
{
|
|
||||||
var list = options.DisabledImageFetchers.ToList();
|
|
||||||
|
|
||||||
list.Add(imageProviderName);
|
|
||||||
|
|
||||||
options.DisabledImageFetchers = list.ToArray();
|
|
||||||
}
|
|
||||||
else if (enabled && contains)
|
|
||||||
{
|
|
||||||
var list = options.DisabledImageFetchers.ToList();
|
|
||||||
|
|
||||||
list.Remove(imageProviderName);
|
|
||||||
|
|
||||||
options.DisabledImageFetchers = list.ToArray();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private MetadataOptions GetMetadataOptions(Type type, ServerConfiguration config)
|
|
||||||
{
|
|
||||||
var options = config.MetadataOptions
|
|
||||||
.FirstOrDefault(i => string.Equals(i.ItemType, type.Name, StringComparison.OrdinalIgnoreCase));
|
|
||||||
|
|
||||||
if (options == null)
|
|
||||||
{
|
|
||||||
var list = config.MetadataOptions.ToList();
|
|
||||||
|
|
||||||
options = new MetadataOptions
|
|
||||||
{
|
|
||||||
ItemType = type.Name
|
|
||||||
};
|
|
||||||
|
|
||||||
list.Add(options);
|
|
||||||
|
|
||||||
config.MetadataOptions = list.ToArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
return options;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,671 +0,0 @@
|
|||||||
using MediaBrowser.Controller.Drawing;
|
|
||||||
using MediaBrowser.Controller.Dto;
|
|
||||||
using MediaBrowser.Controller.Entities;
|
|
||||||
using MediaBrowser.Controller.Entities.Audio;
|
|
||||||
using MediaBrowser.Controller.Entities.Movies;
|
|
||||||
using MediaBrowser.Controller.Entities.TV;
|
|
||||||
using MediaBrowser.Controller.Library;
|
|
||||||
using MediaBrowser.Controller.Persistence;
|
|
||||||
using MediaBrowser.Model.Entities;
|
|
||||||
using MediaBrowser.Model.Logging;
|
|
||||||
using MediaBrowser.Model.Querying;
|
|
||||||
using ServiceStack;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
|
|
||||||
namespace MediaBrowser.Api.DefaultTheme
|
|
||||||
{
|
|
||||||
[Route("/MBT/DefaultTheme/Games", "GET")]
|
|
||||||
public class GetGamesView : IReturn<GamesView>
|
|
||||||
{
|
|
||||||
[ApiMember(Name = "UserId", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")]
|
|
||||||
public Guid UserId { get; set; }
|
|
||||||
|
|
||||||
[ApiMember(Name = "RecentlyPlayedGamesLimit", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
|
|
||||||
public int RecentlyPlayedGamesLimit { get; set; }
|
|
||||||
|
|
||||||
public string ParentId { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
[Route("/MBT/DefaultTheme/TV", "GET")]
|
|
||||||
public class GetTvView : IReturn<TvView>
|
|
||||||
{
|
|
||||||
[ApiMember(Name = "UserId", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")]
|
|
||||||
public Guid UserId { get; set; }
|
|
||||||
|
|
||||||
[ApiMember(Name = "ComedyGenre", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
|
|
||||||
public string ComedyGenre { get; set; }
|
|
||||||
|
|
||||||
[ApiMember(Name = "RomanceGenre", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
|
|
||||||
public string RomanceGenre { get; set; }
|
|
||||||
|
|
||||||
[ApiMember(Name = "TopCommunityRating", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
|
|
||||||
public double TopCommunityRating { get; set; }
|
|
||||||
|
|
||||||
[ApiMember(Name = "NextUpEpisodeLimit", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
|
|
||||||
public int NextUpEpisodeLimit { get; set; }
|
|
||||||
|
|
||||||
[ApiMember(Name = "ResumableEpisodeLimit", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
|
|
||||||
public int ResumableEpisodeLimit { get; set; }
|
|
||||||
|
|
||||||
[ApiMember(Name = "LatestEpisodeLimit", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
|
|
||||||
public int LatestEpisodeLimit { get; set; }
|
|
||||||
|
|
||||||
public string ParentId { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
[Route("/MBT/DefaultTheme/Movies", "GET")]
|
|
||||||
public class GetMovieView : IReturn<MoviesView>
|
|
||||||
{
|
|
||||||
[ApiMember(Name = "UserId", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")]
|
|
||||||
public Guid UserId { get; set; }
|
|
||||||
|
|
||||||
[ApiMember(Name = "FamilyGenre", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
|
|
||||||
public string FamilyGenre { get; set; }
|
|
||||||
|
|
||||||
[ApiMember(Name = "ComedyGenre", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
|
|
||||||
public string ComedyGenre { get; set; }
|
|
||||||
|
|
||||||
[ApiMember(Name = "RomanceGenre", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
|
|
||||||
public string RomanceGenre { get; set; }
|
|
||||||
|
|
||||||
[ApiMember(Name = "LatestMoviesLimit", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
|
|
||||||
public int LatestMoviesLimit { get; set; }
|
|
||||||
|
|
||||||
[ApiMember(Name = "LatestTrailersLimit", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
|
|
||||||
public int LatestTrailersLimit { get; set; }
|
|
||||||
|
|
||||||
public string ParentId { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
[Route("/MBT/DefaultTheme/Favorites", "GET")]
|
|
||||||
public class GetFavoritesView : IReturn<FavoritesView>
|
|
||||||
{
|
|
||||||
[ApiMember(Name = "UserId", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")]
|
|
||||||
public Guid UserId { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class DefaultThemeService : BaseApiService
|
|
||||||
{
|
|
||||||
private readonly IUserManager _userManager;
|
|
||||||
private readonly IDtoService _dtoService;
|
|
||||||
private readonly ILogger _logger;
|
|
||||||
private readonly ILibraryManager _libraryManager;
|
|
||||||
private readonly IUserDataManager _userDataManager;
|
|
||||||
|
|
||||||
private readonly IImageProcessor _imageProcessor;
|
|
||||||
private readonly IItemRepository _itemRepo;
|
|
||||||
|
|
||||||
public DefaultThemeService(IUserManager userManager, IDtoService dtoService, ILogger logger, ILibraryManager libraryManager, IImageProcessor imageProcessor, IUserDataManager userDataManager, IItemRepository itemRepo)
|
|
||||||
{
|
|
||||||
_userManager = userManager;
|
|
||||||
_dtoService = dtoService;
|
|
||||||
_logger = logger;
|
|
||||||
_libraryManager = libraryManager;
|
|
||||||
_imageProcessor = imageProcessor;
|
|
||||||
_userDataManager = userDataManager;
|
|
||||||
_itemRepo = itemRepo;
|
|
||||||
}
|
|
||||||
|
|
||||||
public object Get(GetFavoritesView request)
|
|
||||||
{
|
|
||||||
var user = _userManager.GetUserById(request.UserId);
|
|
||||||
|
|
||||||
var allItems = user.RootFolder.GetRecursiveChildren(user)
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
var allFavoriteItems = allItems.Where(i => _userDataManager.GetUserData(user.Id, i.GetUserDataKey()).IsFavorite)
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
var itemsWithImages = allFavoriteItems.Where(i => !string.IsNullOrEmpty(i.PrimaryImagePath))
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
var itemsWithBackdrops = allFavoriteItems.Where(i => i.GetImages(ImageType.Backdrop).Any())
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
var view = new FavoritesView();
|
|
||||||
|
|
||||||
var fields = new List<ItemFields>();
|
|
||||||
|
|
||||||
view.BackdropItems = FilterItemsForBackdropDisplay(itemsWithBackdrops)
|
|
||||||
.Randomize("backdrop")
|
|
||||||
.Take(10)
|
|
||||||
.Select(i => _dtoService.GetBaseItemDto(i, fields, user))
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
var spotlightItems = itemsWithBackdrops.Randomize("spotlight")
|
|
||||||
.Take(10)
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
view.SpotlightItems = spotlightItems
|
|
||||||
.Select(i => _dtoService.GetBaseItemDto(i, fields, user))
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
fields.Add(ItemFields.PrimaryImageAspectRatio);
|
|
||||||
|
|
||||||
view.Albums = itemsWithImages
|
|
||||||
.OfType<MusicAlbum>()
|
|
||||||
.Randomize()
|
|
||||||
.Take(4)
|
|
||||||
.Select(i => _dtoService.GetBaseItemDto(i, fields, user))
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
view.Books = itemsWithImages
|
|
||||||
.OfType<Book>()
|
|
||||||
.Randomize()
|
|
||||||
.Take(6)
|
|
||||||
.Select(i => _dtoService.GetBaseItemDto(i, fields, user))
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
view.Episodes = itemsWithImages
|
|
||||||
.OfType<Episode>()
|
|
||||||
.Randomize()
|
|
||||||
.Take(6)
|
|
||||||
.Select(i => _dtoService.GetBaseItemDto(i, fields, user))
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
view.Games = itemsWithImages
|
|
||||||
.OfType<Game>()
|
|
||||||
.Randomize()
|
|
||||||
.Take(6)
|
|
||||||
.Select(i => _dtoService.GetBaseItemDto(i, fields, user))
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
view.Movies = itemsWithImages
|
|
||||||
.OfType<Movie>()
|
|
||||||
.Randomize()
|
|
||||||
.Take(6)
|
|
||||||
.Select(i => _dtoService.GetBaseItemDto(i, fields, user))
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
view.Series = itemsWithImages
|
|
||||||
.OfType<Series>()
|
|
||||||
.Randomize()
|
|
||||||
.Take(6)
|
|
||||||
.Select(i => _dtoService.GetBaseItemDto(i, fields, user))
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
view.Songs = itemsWithImages
|
|
||||||
.OfType<Audio>()
|
|
||||||
.Randomize()
|
|
||||||
.Take(4)
|
|
||||||
.Select(i => _dtoService.GetBaseItemDto(i, fields, user))
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
view.MiniSpotlights = itemsWithBackdrops
|
|
||||||
.Except(spotlightItems)
|
|
||||||
.Randomize()
|
|
||||||
.Take(5)
|
|
||||||
.Select(i => _dtoService.GetBaseItemDto(i, fields, user))
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
var artists = allItems.OfType<Audio>()
|
|
||||||
.SelectMany(i => i.AllArtists)
|
|
||||||
.Distinct(StringComparer.OrdinalIgnoreCase)
|
|
||||||
.Randomize()
|
|
||||||
.Select(i =>
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
return _libraryManager.GetArtist(i);
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.Where(i => i != null && _userDataManager.GetUserData(user.Id, i.GetUserDataKey()).IsFavorite)
|
|
||||||
.Take(4)
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
view.Artists = artists
|
|
||||||
.Select(i => _dtoService.GetBaseItemDto(i, fields, user))
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
return ToOptimizedSerializedResultUsingCache(view);
|
|
||||||
}
|
|
||||||
|
|
||||||
public object Get(GetGamesView request)
|
|
||||||
{
|
|
||||||
var user = _userManager.GetUserById(request.UserId);
|
|
||||||
|
|
||||||
var items = GetAllLibraryItems(user.Id, _userManager, _libraryManager, request.ParentId).Where(i => i is Game || i is GameSystem)
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
var gamesWithImages = items.OfType<Game>().Where(i => !string.IsNullOrEmpty(i.PrimaryImagePath)).ToList();
|
|
||||||
|
|
||||||
var itemsWithBackdrops = FilterItemsForBackdropDisplay(items.Where(i => i.GetImages(ImageType.Backdrop).Any())).ToList();
|
|
||||||
|
|
||||||
var gamesWithBackdrops = itemsWithBackdrops.OfType<Game>().ToList();
|
|
||||||
|
|
||||||
var view = new GamesView();
|
|
||||||
|
|
||||||
var fields = new List<ItemFields>();
|
|
||||||
|
|
||||||
view.GameSystems = items
|
|
||||||
.OfType<GameSystem>()
|
|
||||||
.OrderBy(i => i.SortName)
|
|
||||||
.Select(i => _dtoService.GetBaseItemDto(i, fields, user))
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
var currentUserId = user.Id;
|
|
||||||
view.RecentlyPlayedGames = gamesWithImages
|
|
||||||
.OrderByDescending(i => _userDataManager.GetUserData(currentUserId, i.GetUserDataKey()).LastPlayedDate ?? DateTime.MinValue)
|
|
||||||
.Take(request.RecentlyPlayedGamesLimit)
|
|
||||||
.Select(i => _dtoService.GetBaseItemDto(i, fields, user))
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
view.BackdropItems = gamesWithBackdrops
|
|
||||||
.OrderBy(i => Guid.NewGuid())
|
|
||||||
.Take(10)
|
|
||||||
.Select(i => _dtoService.GetBaseItemDto(i, fields, user))
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
view.SpotlightItems = gamesWithBackdrops
|
|
||||||
.OrderBy(i => Guid.NewGuid())
|
|
||||||
.Take(10)
|
|
||||||
.Select(i => _dtoService.GetBaseItemDto(i, fields, user))
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
view.MultiPlayerItems = gamesWithImages
|
|
||||||
.Where(i => i.PlayersSupported.HasValue && i.PlayersSupported.Value > 1)
|
|
||||||
.Randomize()
|
|
||||||
.Select(i => GetItemStub(i, ImageType.Primary))
|
|
||||||
.Where(i => i != null)
|
|
||||||
.Take(1)
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
return ToOptimizedSerializedResultUsingCache(view);
|
|
||||||
}
|
|
||||||
|
|
||||||
public object Get(GetTvView request)
|
|
||||||
{
|
|
||||||
var romanceGenres = request.RomanceGenre.Split(',').ToDictionary(i => i, StringComparer.OrdinalIgnoreCase);
|
|
||||||
var comedyGenres = request.ComedyGenre.Split(',').ToDictionary(i => i, StringComparer.OrdinalIgnoreCase);
|
|
||||||
|
|
||||||
var user = _userManager.GetUserById(request.UserId);
|
|
||||||
|
|
||||||
var series = GetAllLibraryItems(user.Id, _userManager, _libraryManager, request.ParentId)
|
|
||||||
.OfType<Series>()
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
var seriesWithBackdrops = series.Where(i => i.GetImages(ImageType.Backdrop).Any()).ToList();
|
|
||||||
|
|
||||||
var view = new TvView();
|
|
||||||
|
|
||||||
var fields = new List<ItemFields>();
|
|
||||||
|
|
||||||
var seriesWithBestBackdrops = FilterItemsForBackdropDisplay(seriesWithBackdrops).ToList();
|
|
||||||
|
|
||||||
view.BackdropItems = seriesWithBestBackdrops
|
|
||||||
.OrderBy(i => Guid.NewGuid())
|
|
||||||
.Take(10)
|
|
||||||
.AsParallel()
|
|
||||||
.Select(i => _dtoService.GetBaseItemDto(i, fields, user))
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
view.ShowsItems = series
|
|
||||||
.Where(i => i.GetImages(ImageType.Backdrop).Any())
|
|
||||||
.Randomize("all")
|
|
||||||
.Select(i => GetItemStub(i, ImageType.Backdrop))
|
|
||||||
.Where(i => i != null)
|
|
||||||
.Take(1)
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
view.RomanceItems = seriesWithBackdrops
|
|
||||||
.Where(i => i.Genres.Any(romanceGenres.ContainsKey))
|
|
||||||
.Randomize("romance")
|
|
||||||
.Select(i => GetItemStub(i, ImageType.Backdrop))
|
|
||||||
.Where(i => i != null)
|
|
||||||
.Take(1)
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
view.ComedyItems = seriesWithBackdrops
|
|
||||||
.Where(i => i.Genres.Any(comedyGenres.ContainsKey))
|
|
||||||
.Randomize("comedy")
|
|
||||||
.Select(i => GetItemStub(i, ImageType.Backdrop))
|
|
||||||
.Where(i => i != null)
|
|
||||||
.Take(1)
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
var spotlightSeries = seriesWithBestBackdrops
|
|
||||||
.Where(i => i.CommunityRating.HasValue && i.CommunityRating >= 8.5)
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
if (spotlightSeries.Count < 20)
|
|
||||||
{
|
|
||||||
spotlightSeries = seriesWithBestBackdrops;
|
|
||||||
}
|
|
||||||
|
|
||||||
spotlightSeries = spotlightSeries
|
|
||||||
.OrderBy(i => Guid.NewGuid())
|
|
||||||
.Take(10)
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
view.SpotlightItems = spotlightSeries
|
|
||||||
.AsParallel()
|
|
||||||
.Select(i => _dtoService.GetBaseItemDto(i, fields, user))
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
var miniSpotlightItems = seriesWithBackdrops
|
|
||||||
.Except(spotlightSeries.OfType<Series>())
|
|
||||||
.Where(i => i.CommunityRating.HasValue && i.CommunityRating >= 8)
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
if (miniSpotlightItems.Count < 15)
|
|
||||||
{
|
|
||||||
miniSpotlightItems = seriesWithBackdrops;
|
|
||||||
}
|
|
||||||
|
|
||||||
view.MiniSpotlights = miniSpotlightItems
|
|
||||||
.Randomize("minispotlight")
|
|
||||||
.Take(5)
|
|
||||||
.Select(i => _dtoService.GetBaseItemDto(i, fields, user))
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
var nextUpEpisodes = new TvShowsService(_userManager, _userDataManager, _libraryManager, _itemRepo, _dtoService)
|
|
||||||
.GetNextUpEpisodes(new GetNextUpEpisodes { UserId = user.Id }, series)
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
fields.Add(ItemFields.PrimaryImageAspectRatio);
|
|
||||||
|
|
||||||
view.NextUpEpisodes = nextUpEpisodes
|
|
||||||
.Take(request.NextUpEpisodeLimit)
|
|
||||||
.Select(i => _dtoService.GetBaseItemDto(i, fields, user))
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
view.SeriesIdsInProgress = nextUpEpisodes.Select(i => i.Series.Id.ToString("N")).ToList();
|
|
||||||
|
|
||||||
// Avoid implicitly captured closure
|
|
||||||
var currentUser1 = user;
|
|
||||||
|
|
||||||
var ownedEpisodes = series
|
|
||||||
.SelectMany(i => i.GetRecursiveChildren(currentUser1, j => j.LocationType != LocationType.Virtual))
|
|
||||||
.OfType<Episode>()
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
// Avoid implicitly captured closure
|
|
||||||
var currentUser = user;
|
|
||||||
|
|
||||||
view.LatestEpisodes = ownedEpisodes
|
|
||||||
.OrderByDescending(i => i.DateCreated)
|
|
||||||
.Where(i => !_userDataManager.GetUserData(currentUser.Id, i.GetUserDataKey()).Played)
|
|
||||||
.Take(request.LatestEpisodeLimit)
|
|
||||||
.Select(i => _dtoService.GetBaseItemDto(i, fields, user))
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
view.ResumableEpisodes = ownedEpisodes
|
|
||||||
.Where(i => _userDataManager.GetUserData(currentUser.Id, i.GetUserDataKey()).PlaybackPositionTicks > 0)
|
|
||||||
.OrderByDescending(i => _userDataManager.GetUserData(currentUser.Id, i.GetUserDataKey()).LastPlayedDate ?? DateTime.MinValue)
|
|
||||||
.Take(request.ResumableEpisodeLimit)
|
|
||||||
.Select(i => _dtoService.GetBaseItemDto(i, fields, user))
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
return ToOptimizedSerializedResultUsingCache(view);
|
|
||||||
}
|
|
||||||
|
|
||||||
public object Get(GetMovieView request)
|
|
||||||
{
|
|
||||||
var user = _userManager.GetUserById(request.UserId);
|
|
||||||
|
|
||||||
var items = GetAllLibraryItems(user.Id, _userManager, _libraryManager, request.ParentId)
|
|
||||||
.Where(i => i is Movie || i is Trailer || i is BoxSet)
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
var view = new MoviesView();
|
|
||||||
|
|
||||||
var movies = items.OfType<Movie>()
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
var trailers = items.OfType<Trailer>()
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
var hdMovies = movies.Where(i => i.IsHD).ToList();
|
|
||||||
|
|
||||||
var familyGenres = request.FamilyGenre.Split(',').ToDictionary(i => i, StringComparer.OrdinalIgnoreCase);
|
|
||||||
|
|
||||||
var familyMovies = movies.Where(i => i.Genres.Any(familyGenres.ContainsKey)).ToList();
|
|
||||||
|
|
||||||
view.HDMoviePercentage = 100 * hdMovies.Count;
|
|
||||||
view.HDMoviePercentage /= movies.Count;
|
|
||||||
|
|
||||||
view.FamilyMoviePercentage = 100 * familyMovies.Count;
|
|
||||||
view.FamilyMoviePercentage /= movies.Count;
|
|
||||||
|
|
||||||
var moviesWithBackdrops = movies
|
|
||||||
.Where(i => i.GetImages(ImageType.Backdrop).Any())
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
var fields = new List<ItemFields>();
|
|
||||||
|
|
||||||
var itemsWithTopBackdrops = FilterItemsForBackdropDisplay(moviesWithBackdrops).ToList();
|
|
||||||
|
|
||||||
view.BackdropItems = itemsWithTopBackdrops
|
|
||||||
.OrderBy(i => Guid.NewGuid())
|
|
||||||
.Take(10)
|
|
||||||
.AsParallel()
|
|
||||||
.Select(i => _dtoService.GetBaseItemDto(i, fields, user))
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
view.MovieItems = moviesWithBackdrops
|
|
||||||
.Randomize("all")
|
|
||||||
.Select(i => GetItemStub(i, ImageType.Backdrop))
|
|
||||||
.Where(i => i != null)
|
|
||||||
.Take(1)
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
view.TrailerItems = trailers
|
|
||||||
.Where(i => !string.IsNullOrEmpty(i.PrimaryImagePath))
|
|
||||||
.Randomize()
|
|
||||||
.Select(i => GetItemStub(i, ImageType.Primary))
|
|
||||||
.Where(i => i != null)
|
|
||||||
.Take(1)
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
view.BoxSetItems = items
|
|
||||||
.OfType<BoxSet>()
|
|
||||||
.Where(i => i.GetImages(ImageType.Backdrop).Any())
|
|
||||||
.Randomize()
|
|
||||||
.Select(i => GetItemStub(i, ImageType.Backdrop))
|
|
||||||
.Where(i => i != null)
|
|
||||||
.Take(1)
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
view.ThreeDItems = moviesWithBackdrops
|
|
||||||
.Where(i => i.Is3D)
|
|
||||||
.Randomize("3d")
|
|
||||||
.Select(i => GetItemStub(i, ImageType.Backdrop))
|
|
||||||
.Where(i => i != null)
|
|
||||||
.Take(1)
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
var romanceGenres = request.RomanceGenre.Split(',').ToDictionary(i => i, StringComparer.OrdinalIgnoreCase);
|
|
||||||
var comedyGenres = request.ComedyGenre.Split(',').ToDictionary(i => i, StringComparer.OrdinalIgnoreCase);
|
|
||||||
|
|
||||||
view.RomanceItems = moviesWithBackdrops
|
|
||||||
.Where(i => i.Genres.Any(romanceGenres.ContainsKey))
|
|
||||||
.Randomize("romance")
|
|
||||||
.Select(i => GetItemStub(i, ImageType.Backdrop))
|
|
||||||
.Where(i => i != null)
|
|
||||||
.Take(1)
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
view.ComedyItems = moviesWithBackdrops
|
|
||||||
.Where(i => i.Genres.Any(comedyGenres.ContainsKey))
|
|
||||||
.Randomize("comedy")
|
|
||||||
.Select(i => GetItemStub(i, ImageType.Backdrop))
|
|
||||||
.Where(i => i != null)
|
|
||||||
.Take(1)
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
view.HDItems = hdMovies
|
|
||||||
.Where(i => i.GetImages(ImageType.Backdrop).Any())
|
|
||||||
.Randomize("hd")
|
|
||||||
.Select(i => GetItemStub(i, ImageType.Backdrop))
|
|
||||||
.Where(i => i != null)
|
|
||||||
.Take(1)
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
view.FamilyMovies = familyMovies
|
|
||||||
.Where(i => i.GetImages(ImageType.Backdrop).Any())
|
|
||||||
.Randomize("family")
|
|
||||||
.Select(i => GetItemStub(i, ImageType.Backdrop))
|
|
||||||
.Where(i => i != null)
|
|
||||||
.Take(1)
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
var currentUserId = user.Id;
|
|
||||||
var spotlightItems = itemsWithTopBackdrops
|
|
||||||
.Where(i => i.CommunityRating.HasValue && i.CommunityRating >= 8)
|
|
||||||
.Where(i => !_userDataManager.GetUserData(currentUserId, i.GetUserDataKey()).Played)
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
if (spotlightItems.Count < 20)
|
|
||||||
{
|
|
||||||
spotlightItems = itemsWithTopBackdrops;
|
|
||||||
}
|
|
||||||
|
|
||||||
spotlightItems = spotlightItems
|
|
||||||
.OrderBy(i => Guid.NewGuid())
|
|
||||||
.Take(10)
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
view.SpotlightItems = spotlightItems
|
|
||||||
.AsParallel()
|
|
||||||
.Select(i => _dtoService.GetBaseItemDto(i, fields, user))
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
var miniSpotlightItems = moviesWithBackdrops
|
|
||||||
.Except(spotlightItems)
|
|
||||||
.Where(i => i.CommunityRating.HasValue && i.CommunityRating >= 7.5)
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
if (miniSpotlightItems.Count < 15)
|
|
||||||
{
|
|
||||||
miniSpotlightItems = itemsWithTopBackdrops;
|
|
||||||
}
|
|
||||||
|
|
||||||
miniSpotlightItems = miniSpotlightItems
|
|
||||||
.Randomize("minispotlight")
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
// Avoid implicitly captured closure
|
|
||||||
miniSpotlightItems.InsertRange(0, moviesWithBackdrops
|
|
||||||
.Where(i => _userDataManager.GetUserData(currentUserId, i.GetUserDataKey()).PlaybackPositionTicks > 0)
|
|
||||||
.OrderByDescending(i => _userDataManager.GetUserData(currentUserId, i.GetUserDataKey()).LastPlayedDate ?? DateTime.MaxValue)
|
|
||||||
.Take(3));
|
|
||||||
|
|
||||||
view.MiniSpotlights = miniSpotlightItems
|
|
||||||
.Take(3)
|
|
||||||
.Select(i => _dtoService.GetBaseItemDto(i, fields, user))
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
// Avoid implicitly captured closure
|
|
||||||
var currentUserId1 = user.Id;
|
|
||||||
|
|
||||||
view.LatestMovies = movies
|
|
||||||
.OrderByDescending(i => i.DateCreated)
|
|
||||||
.Where(i => !_userDataManager.GetUserData(currentUserId1, i.GetUserDataKey()).Played)
|
|
||||||
.Take(request.LatestMoviesLimit)
|
|
||||||
.Select(i => _dtoService.GetBaseItemDto(i, fields, user))
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
view.LatestTrailers = trailers
|
|
||||||
.OrderByDescending(i => i.DateCreated)
|
|
||||||
.Where(i => !_userDataManager.GetUserData(currentUserId1, i.GetUserDataKey()).Played)
|
|
||||||
.Take(request.LatestTrailersLimit)
|
|
||||||
.Select(i => _dtoService.GetBaseItemDto(i, fields, user))
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
return ToOptimizedSerializedResultUsingCache(view);
|
|
||||||
}
|
|
||||||
|
|
||||||
private IEnumerable<BaseItem> FilterItemsForBackdropDisplay(IEnumerable<BaseItem> items)
|
|
||||||
{
|
|
||||||
var tuples = items
|
|
||||||
.Select(i => new Tuple<BaseItem, double>(i, GetResolution(i, ImageType.Backdrop, 0)))
|
|
||||||
.Where(i => i.Item2 > 0)
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
var topItems = tuples
|
|
||||||
.Where(i => i.Item2 >= 1920)
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
if (topItems.Count >= 10)
|
|
||||||
{
|
|
||||||
return topItems.Select(i => i.Item1);
|
|
||||||
}
|
|
||||||
|
|
||||||
return tuples.Select(i => i.Item1);
|
|
||||||
}
|
|
||||||
|
|
||||||
private double GetResolution(BaseItem item, ImageType type, int index)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var info = item.GetImageInfo(type, index);
|
|
||||||
|
|
||||||
var size = _imageProcessor.GetImageSize(info.Path, info.DateModified);
|
|
||||||
|
|
||||||
return size.Width;
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private ItemStub GetItemStub(BaseItem item, ImageType imageType)
|
|
||||||
{
|
|
||||||
var stub = new ItemStub
|
|
||||||
{
|
|
||||||
Id = _dtoService.GetDtoId(item),
|
|
||||||
Name = item.Name,
|
|
||||||
ImageType = imageType
|
|
||||||
};
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var tag = _imageProcessor.GetImageCacheTag(item, imageType);
|
|
||||||
|
|
||||||
if (tag != null)
|
|
||||||
{
|
|
||||||
stub.ImageTag = tag;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
_logger.ErrorException("Error getting image tag for {0}", ex, item.Path);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return stub;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static class RandomExtension
|
|
||||||
{
|
|
||||||
public static IEnumerable<T> Randomize<T>(this IEnumerable<T> sequence, string type = "none")
|
|
||||||
where T : BaseItem
|
|
||||||
{
|
|
||||||
var hour = DateTime.Now.Hour + DateTime.Now.Day + 2;
|
|
||||||
|
|
||||||
var typeCode = type.GetHashCode();
|
|
||||||
|
|
||||||
return sequence.OrderBy(i =>
|
|
||||||
{
|
|
||||||
var val = i.Id.GetHashCode() + i.Genres.Count + i.People.Count + (i.ProductionYear ?? 0) + i.DateCreated.Minute + i.DateModified.Minute + typeCode;
|
|
||||||
|
|
||||||
return val % hour;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public static IEnumerable<string> Randomize(this IEnumerable<string> sequence)
|
|
||||||
{
|
|
||||||
var hour = DateTime.Now.Hour + 2;
|
|
||||||
|
|
||||||
return sequence.OrderBy(i => i.GetHashCode() % hour);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,83 +0,0 @@
|
|||||||
using MediaBrowser.Model.Dto;
|
|
||||||
using MediaBrowser.Model.Entities;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
|
|
||||||
namespace MediaBrowser.Api.DefaultTheme
|
|
||||||
{
|
|
||||||
public class ItemStub
|
|
||||||
{
|
|
||||||
public string Name { get; set; }
|
|
||||||
public string Id { get; set; }
|
|
||||||
public string ImageTag { get; set; }
|
|
||||||
public ImageType ImageType { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class MoviesView : BaseView
|
|
||||||
{
|
|
||||||
public List<ItemStub> MovieItems { get; set; }
|
|
||||||
|
|
||||||
public List<ItemStub> BoxSetItems { get; set; }
|
|
||||||
public List<ItemStub> TrailerItems { get; set; }
|
|
||||||
public List<ItemStub> HDItems { get; set; }
|
|
||||||
public List<ItemStub> ThreeDItems { get; set; }
|
|
||||||
|
|
||||||
public List<ItemStub> FamilyMovies { get; set; }
|
|
||||||
|
|
||||||
public List<ItemStub> RomanceItems { get; set; }
|
|
||||||
public List<ItemStub> ComedyItems { get; set; }
|
|
||||||
|
|
||||||
public double FamilyMoviePercentage { get; set; }
|
|
||||||
|
|
||||||
public double HDMoviePercentage { get; set; }
|
|
||||||
|
|
||||||
public List<BaseItemDto> LatestTrailers { get; set; }
|
|
||||||
public List<BaseItemDto> LatestMovies { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class TvView : BaseView
|
|
||||||
{
|
|
||||||
public List<ItemStub> ShowsItems { get; set; }
|
|
||||||
|
|
||||||
public List<ItemStub> RomanceItems { get; set; }
|
|
||||||
public List<ItemStub> ComedyItems { get; set; }
|
|
||||||
|
|
||||||
public List<string> SeriesIdsInProgress { get; set; }
|
|
||||||
|
|
||||||
public List<BaseItemDto> LatestEpisodes { get; set; }
|
|
||||||
public List<BaseItemDto> NextUpEpisodes { get; set; }
|
|
||||||
public List<BaseItemDto> ResumableEpisodes { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class ItemByNameInfo
|
|
||||||
{
|
|
||||||
public string Name { get; set; }
|
|
||||||
public int ItemCount { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class GamesView : BaseView
|
|
||||||
{
|
|
||||||
public List<ItemStub> MultiPlayerItems { get; set; }
|
|
||||||
public List<BaseItemDto> GameSystems { get; set; }
|
|
||||||
public List<BaseItemDto> RecentlyPlayedGames { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class BaseView
|
|
||||||
{
|
|
||||||
public List<BaseItemDto> BackdropItems { get; set; }
|
|
||||||
public List<BaseItemDto> SpotlightItems { get; set; }
|
|
||||||
public List<BaseItemDto> MiniSpotlights { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class FavoritesView : BaseView
|
|
||||||
{
|
|
||||||
public List<BaseItemDto> Movies { get; set; }
|
|
||||||
public List<BaseItemDto> Series { get; set; }
|
|
||||||
public List<BaseItemDto> Episodes { get; set; }
|
|
||||||
public List<BaseItemDto> Games { get; set; }
|
|
||||||
public List<BaseItemDto> Books { get; set; }
|
|
||||||
public List<BaseItemDto> Albums { get; set; }
|
|
||||||
public List<BaseItemDto> Songs { get; set; }
|
|
||||||
public List<BaseItemDto> Artists { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,4 +1,5 @@
|
|||||||
using MediaBrowser.Controller.Persistence;
|
using MediaBrowser.Controller.Net;
|
||||||
|
using MediaBrowser.Controller.Persistence;
|
||||||
using MediaBrowser.Model.Entities;
|
using MediaBrowser.Model.Entities;
|
||||||
using MediaBrowser.Model.Serialization;
|
using MediaBrowser.Model.Serialization;
|
||||||
using ServiceStack;
|
using ServiceStack;
|
||||||
@ -48,6 +49,7 @@ namespace MediaBrowser.Api
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Class DisplayPreferencesService
|
/// Class DisplayPreferencesService
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[Authenticated]
|
||||||
public class DisplayPreferencesService : BaseApiService
|
public class DisplayPreferencesService : BaseApiService
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
using MediaBrowser.Controller.Dlna;
|
using MediaBrowser.Common.Configuration;
|
||||||
|
using MediaBrowser.Controller.Dlna;
|
||||||
|
using MediaBrowser.Model.Configuration;
|
||||||
using ServiceStack;
|
using ServiceStack;
|
||||||
using ServiceStack.Text.Controller;
|
using ServiceStack.Text.Controller;
|
||||||
using ServiceStack.Web;
|
using ServiceStack.Web;
|
||||||
@ -76,11 +78,14 @@ namespace MediaBrowser.Api.Dlna
|
|||||||
private readonly IContentDirectory _contentDirectory;
|
private readonly IContentDirectory _contentDirectory;
|
||||||
private readonly IConnectionManager _connectionManager;
|
private readonly IConnectionManager _connectionManager;
|
||||||
|
|
||||||
public DlnaServerService(IDlnaManager dlnaManager, IContentDirectory contentDirectory, IConnectionManager connectionManager)
|
private readonly IConfigurationManager _config;
|
||||||
|
|
||||||
|
public DlnaServerService(IDlnaManager dlnaManager, IContentDirectory contentDirectory, IConnectionManager connectionManager, IConfigurationManager config)
|
||||||
{
|
{
|
||||||
_dlnaManager = dlnaManager;
|
_dlnaManager = dlnaManager;
|
||||||
_contentDirectory = contentDirectory;
|
_contentDirectory = contentDirectory;
|
||||||
_connectionManager = connectionManager;
|
_connectionManager = connectionManager;
|
||||||
|
_config = config;
|
||||||
}
|
}
|
||||||
|
|
||||||
public object Get(GetDescriptionXml request)
|
public object Get(GetDescriptionXml request)
|
||||||
@ -104,16 +109,16 @@ namespace MediaBrowser.Api.Dlna
|
|||||||
return ResultFactory.GetResult(xml, "text/xml");
|
return ResultFactory.GetResult(xml, "text/xml");
|
||||||
}
|
}
|
||||||
|
|
||||||
public object Post(ProcessContentDirectoryControlRequest request)
|
public async Task<object> Post(ProcessContentDirectoryControlRequest request)
|
||||||
{
|
{
|
||||||
var response = PostAsync(request.RequestStream, _contentDirectory).Result;
|
var response = await PostAsync(request.RequestStream, _contentDirectory).ConfigureAwait(false);
|
||||||
|
|
||||||
return ResultFactory.GetResult(response.Xml, "text/xml");
|
return ResultFactory.GetResult(response.Xml, "text/xml");
|
||||||
}
|
}
|
||||||
|
|
||||||
public object Post(ProcessConnectionManagerControlRequest request)
|
public async Task<object> Post(ProcessConnectionManagerControlRequest request)
|
||||||
{
|
{
|
||||||
var response = PostAsync(request.RequestStream, _connectionManager).Result;
|
var response = await PostAsync(request.RequestStream, _connectionManager).ConfigureAwait(false);
|
||||||
|
|
||||||
return ResultFactory.GetResult(response.Xml, "text/xml");
|
return ResultFactory.GetResult(response.Xml, "text/xml");
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
using MediaBrowser.Controller.Dlna;
|
using MediaBrowser.Controller.Dlna;
|
||||||
|
using MediaBrowser.Controller.Net;
|
||||||
using MediaBrowser.Model.Dlna;
|
using MediaBrowser.Model.Dlna;
|
||||||
using ServiceStack;
|
using ServiceStack;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
@ -42,6 +43,7 @@ namespace MediaBrowser.Api.Dlna
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Authenticated]
|
||||||
public class DlnaService : BaseApiService
|
public class DlnaService : BaseApiService
|
||||||
{
|
{
|
||||||
private readonly IDlnaManager _dlnaManager;
|
private readonly IDlnaManager _dlnaManager;
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
using MediaBrowser.Common.Net;
|
using MediaBrowser.Common.Net;
|
||||||
|
using MediaBrowser.Controller.Net;
|
||||||
using MediaBrowser.Model.IO;
|
using MediaBrowser.Model.IO;
|
||||||
using MediaBrowser.Model.Net;
|
using MediaBrowser.Model.Net;
|
||||||
using ServiceStack;
|
using ServiceStack;
|
||||||
@ -86,6 +87,7 @@ namespace MediaBrowser.Api
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Class EnvironmentService
|
/// Class EnvironmentService
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[Authenticated]
|
||||||
public class EnvironmentService : BaseApiService
|
public class EnvironmentService : BaseApiService
|
||||||
{
|
{
|
||||||
const char UncSeparator = '\\';
|
const char UncSeparator = '\\';
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
using MediaBrowser.Controller.Dto;
|
using MediaBrowser.Controller.Dto;
|
||||||
using MediaBrowser.Controller.Entities;
|
using MediaBrowser.Controller.Entities;
|
||||||
using MediaBrowser.Controller.Library;
|
using MediaBrowser.Controller.Library;
|
||||||
|
using MediaBrowser.Controller.Net;
|
||||||
using MediaBrowser.Controller.Persistence;
|
using MediaBrowser.Controller.Persistence;
|
||||||
using MediaBrowser.Model.Dto;
|
using MediaBrowser.Model.Dto;
|
||||||
using ServiceStack;
|
using ServiceStack;
|
||||||
@ -51,6 +52,7 @@ namespace MediaBrowser.Api
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Class GamesService
|
/// Class GamesService
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[Authenticated]
|
||||||
public class GamesService : BaseApiService
|
public class GamesService : BaseApiService
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
using MediaBrowser.Common.Extensions;
|
using MediaBrowser.Common.Extensions;
|
||||||
|
using MediaBrowser.Common.IO;
|
||||||
using MediaBrowser.Controller;
|
using MediaBrowser.Controller;
|
||||||
using MediaBrowser.Controller.Entities;
|
using MediaBrowser.Controller.Entities;
|
||||||
using MediaBrowser.Model.Dto;
|
using MediaBrowser.Model.Dto;
|
||||||
@ -100,13 +101,16 @@ namespace MediaBrowser.Api.Images
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private readonly IServerApplicationPaths _appPaths;
|
private readonly IServerApplicationPaths _appPaths;
|
||||||
|
|
||||||
|
private readonly IFileSystem _fileSystem;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="ImageByNameService" /> class.
|
/// Initializes a new instance of the <see cref="ImageByNameService" /> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="appPaths">The app paths.</param>
|
/// <param name="appPaths">The app paths.</param>
|
||||||
public ImageByNameService(IServerApplicationPaths appPaths)
|
public ImageByNameService(IServerApplicationPaths appPaths, IFileSystem fileSystem)
|
||||||
{
|
{
|
||||||
_appPaths = appPaths;
|
_appPaths = appPaths;
|
||||||
|
_fileSystem = fileSystem;
|
||||||
}
|
}
|
||||||
|
|
||||||
public object Get(GetMediaInfoImages request)
|
public object Get(GetMediaInfoImages request)
|
||||||
@ -133,7 +137,7 @@ namespace MediaBrowser.Api.Images
|
|||||||
.Where(i => BaseItem.SupportedImageExtensions.Contains(i.Extension, StringComparer.Ordinal))
|
.Where(i => BaseItem.SupportedImageExtensions.Contains(i.Extension, StringComparer.Ordinal))
|
||||||
.Select(i => new ImageByNameInfo
|
.Select(i => new ImageByNameInfo
|
||||||
{
|
{
|
||||||
Name = Path.GetFileNameWithoutExtension(i.FullName),
|
Name = _fileSystem.GetFileNameWithoutExtension(i),
|
||||||
FileLength = i.Length,
|
FileLength = i.Length,
|
||||||
|
|
||||||
// For themeable images, use the Theme property
|
// For themeable images, use the Theme property
|
||||||
|
@ -3,6 +3,7 @@ using MediaBrowser.Common.IO;
|
|||||||
using MediaBrowser.Controller.Drawing;
|
using MediaBrowser.Controller.Drawing;
|
||||||
using MediaBrowser.Controller.Entities;
|
using MediaBrowser.Controller.Entities;
|
||||||
using MediaBrowser.Controller.Library;
|
using MediaBrowser.Controller.Library;
|
||||||
|
using MediaBrowser.Controller.Net;
|
||||||
using MediaBrowser.Controller.Persistence;
|
using MediaBrowser.Controller.Persistence;
|
||||||
using MediaBrowser.Controller.Providers;
|
using MediaBrowser.Controller.Providers;
|
||||||
using MediaBrowser.Model.Drawing;
|
using MediaBrowser.Model.Drawing;
|
||||||
@ -13,7 +14,6 @@ using ServiceStack.Text.Controller;
|
|||||||
using ServiceStack.Web;
|
using ServiceStack.Web;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Globalization;
|
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
@ -26,6 +26,7 @@ namespace MediaBrowser.Api.Images
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
[Route("/Items/{Id}/Images", "GET")]
|
[Route("/Items/{Id}/Images", "GET")]
|
||||||
[Api(Description = "Gets information about an item's images")]
|
[Api(Description = "Gets information about an item's images")]
|
||||||
|
[Authenticated]
|
||||||
public class GetItemImageInfos : IReturn<List<ImageInfo>>
|
public class GetItemImageInfos : IReturn<List<ImageInfo>>
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -38,6 +39,8 @@ namespace MediaBrowser.Api.Images
|
|||||||
|
|
||||||
[Route("/Items/{Id}/Images/{Type}", "GET")]
|
[Route("/Items/{Id}/Images/{Type}", "GET")]
|
||||||
[Route("/Items/{Id}/Images/{Type}/{Index}", "GET")]
|
[Route("/Items/{Id}/Images/{Type}/{Index}", "GET")]
|
||||||
|
[Route("/Items/{Id}/Images/{Type}/{Index}/{Tag}/{Format}/{MaxWidth}/{MaxHeight}", "GET")]
|
||||||
|
[Route("/Items/{Id}/Images/{Type}/{Index}/{Tag}/{Format}/{MaxWidth}/{MaxHeight}", "HEAD")]
|
||||||
[Api(Description = "Gets an item image")]
|
[Api(Description = "Gets an item image")]
|
||||||
public class GetItemImage : ImageRequest
|
public class GetItemImage : ImageRequest
|
||||||
{
|
{
|
||||||
@ -47,8 +50,6 @@ namespace MediaBrowser.Api.Images
|
|||||||
/// <value>The id.</value>
|
/// <value>The id.</value>
|
||||||
[ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
|
[ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
|
||||||
public string Id { get; set; }
|
public string Id { get; set; }
|
||||||
|
|
||||||
public string Params { get; set; }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -56,6 +57,7 @@ namespace MediaBrowser.Api.Images
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
[Route("/Items/{Id}/Images/{Type}/{Index}/Index", "POST")]
|
[Route("/Items/{Id}/Images/{Type}/{Index}/Index", "POST")]
|
||||||
[Api(Description = "Updates the index for an item image")]
|
[Api(Description = "Updates the index for an item image")]
|
||||||
|
[Authenticated]
|
||||||
public class UpdateItemImageIndex : IReturnVoid
|
public class UpdateItemImageIndex : IReturnVoid
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -137,6 +139,7 @@ namespace MediaBrowser.Api.Images
|
|||||||
[Route("/Items/{Id}/Images/{Type}", "DELETE")]
|
[Route("/Items/{Id}/Images/{Type}", "DELETE")]
|
||||||
[Route("/Items/{Id}/Images/{Type}/{Index}", "DELETE")]
|
[Route("/Items/{Id}/Images/{Type}/{Index}", "DELETE")]
|
||||||
[Api(Description = "Deletes an item image")]
|
[Api(Description = "Deletes an item image")]
|
||||||
|
[Authenticated]
|
||||||
public class DeleteItemImage : DeleteImageRequest, IReturnVoid
|
public class DeleteItemImage : DeleteImageRequest, IReturnVoid
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -153,6 +156,7 @@ namespace MediaBrowser.Api.Images
|
|||||||
[Route("/Users/{Id}/Images/{Type}", "DELETE")]
|
[Route("/Users/{Id}/Images/{Type}", "DELETE")]
|
||||||
[Route("/Users/{Id}/Images/{Type}/{Index}", "DELETE")]
|
[Route("/Users/{Id}/Images/{Type}/{Index}", "DELETE")]
|
||||||
[Api(Description = "Deletes a user image")]
|
[Api(Description = "Deletes a user image")]
|
||||||
|
[Authenticated]
|
||||||
public class DeleteUserImage : DeleteImageRequest, IReturnVoid
|
public class DeleteUserImage : DeleteImageRequest, IReturnVoid
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -169,6 +173,7 @@ namespace MediaBrowser.Api.Images
|
|||||||
[Route("/Users/{Id}/Images/{Type}", "POST")]
|
[Route("/Users/{Id}/Images/{Type}", "POST")]
|
||||||
[Route("/Users/{Id}/Images/{Type}/{Index}", "POST")]
|
[Route("/Users/{Id}/Images/{Type}/{Index}", "POST")]
|
||||||
[Api(Description = "Posts a user image")]
|
[Api(Description = "Posts a user image")]
|
||||||
|
[Authenticated]
|
||||||
public class PostUserImage : DeleteImageRequest, IRequiresRequestStream, IReturnVoid
|
public class PostUserImage : DeleteImageRequest, IRequiresRequestStream, IReturnVoid
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -191,6 +196,7 @@ namespace MediaBrowser.Api.Images
|
|||||||
[Route("/Items/{Id}/Images/{Type}", "POST")]
|
[Route("/Items/{Id}/Images/{Type}", "POST")]
|
||||||
[Route("/Items/{Id}/Images/{Type}/{Index}", "POST")]
|
[Route("/Items/{Id}/Images/{Type}/{Index}", "POST")]
|
||||||
[Api(Description = "Posts an item image")]
|
[Api(Description = "Posts an item image")]
|
||||||
|
[Authenticated]
|
||||||
public class PostItemImage : DeleteImageRequest, IRequiresRequestStream, IReturnVoid
|
public class PostItemImage : DeleteImageRequest, IRequiresRequestStream, IReturnVoid
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -359,45 +365,21 @@ namespace MediaBrowser.Api.Images
|
|||||||
_libraryManager.RootFolder :
|
_libraryManager.RootFolder :
|
||||||
_libraryManager.GetItemById(request.Id);
|
_libraryManager.GetItemById(request.Id);
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(request.Params))
|
return GetImage(request, item, false);
|
||||||
{
|
|
||||||
ParseOptions(request, request.Params);
|
|
||||||
}
|
|
||||||
|
|
||||||
return GetImage(request, item);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly CultureInfo _usCulture = new CultureInfo("en-US");
|
/// <summary>
|
||||||
private void ParseOptions(ImageRequest request, string options)
|
/// Gets the specified request.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="request">The request.</param>
|
||||||
|
/// <returns>System.Object.</returns>
|
||||||
|
public object Head(GetItemImage request)
|
||||||
{
|
{
|
||||||
var vals = options.Split(';');
|
var item = string.IsNullOrEmpty(request.Id) ?
|
||||||
|
_libraryManager.RootFolder :
|
||||||
|
_libraryManager.GetItemById(request.Id);
|
||||||
|
|
||||||
for (var i = 0; i < vals.Length; i++)
|
return GetImage(request, item, true);
|
||||||
{
|
|
||||||
var val = vals[i];
|
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(val))
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (i == 0)
|
|
||||||
{
|
|
||||||
request.Tag = val;
|
|
||||||
}
|
|
||||||
else if (i == 1)
|
|
||||||
{
|
|
||||||
request.Format = (ImageOutputFormat)Enum.Parse(typeof(ImageOutputFormat), val, true);
|
|
||||||
}
|
|
||||||
else if (i == 2)
|
|
||||||
{
|
|
||||||
request.MaxWidth = int.Parse(val, _usCulture);
|
|
||||||
}
|
|
||||||
else if (i == 3)
|
|
||||||
{
|
|
||||||
request.MaxHeight = int.Parse(val, _usCulture);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -409,7 +391,7 @@ namespace MediaBrowser.Api.Images
|
|||||||
{
|
{
|
||||||
var item = _userManager.Users.First(i => i.Id == request.Id);
|
var item = _userManager.Users.First(i => i.Id == request.Id);
|
||||||
|
|
||||||
return GetImage(request, item);
|
return GetImage(request, item, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public object Get(GetItemByNameImage request)
|
public object Get(GetItemByNameImage request)
|
||||||
@ -419,7 +401,7 @@ namespace MediaBrowser.Api.Images
|
|||||||
|
|
||||||
var item = GetItemByName(request.Name, type, _libraryManager);
|
var item = GetItemByName(request.Name, type, _libraryManager);
|
||||||
|
|
||||||
return GetImage(request, item);
|
return GetImage(request, item, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -516,10 +498,10 @@ namespace MediaBrowser.Api.Images
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="request">The request.</param>
|
/// <param name="request">The request.</param>
|
||||||
/// <param name="item">The item.</param>
|
/// <param name="item">The item.</param>
|
||||||
|
/// <param name="isHeadRequest">if set to <c>true</c> [is head request].</param>
|
||||||
/// <returns>System.Object.</returns>
|
/// <returns>System.Object.</returns>
|
||||||
/// <exception cref="ResourceNotFoundException">
|
/// <exception cref="ResourceNotFoundException"></exception>
|
||||||
/// </exception>
|
public object GetImage(ImageRequest request, IHasImages item, bool isHeadRequest)
|
||||||
public object GetImage(ImageRequest request, IHasImages item)
|
|
||||||
{
|
{
|
||||||
var imageInfo = GetImageInfo(request, item);
|
var imageInfo = GetImageInfo(request, item);
|
||||||
|
|
||||||
@ -528,9 +510,6 @@ namespace MediaBrowser.Api.Images
|
|||||||
throw new ResourceNotFoundException(string.Format("{0} does not have an image of type {1}", item.Name, request.Type));
|
throw new ResourceNotFoundException(string.Format("{0} does not have an image of type {1}", item.Name, request.Type));
|
||||||
}
|
}
|
||||||
|
|
||||||
// See if we can avoid a file system lookup by looking for the file in ResolveArgs
|
|
||||||
var originalFileImageDateModified = imageInfo.DateModified;
|
|
||||||
|
|
||||||
var supportedImageEnhancers = request.EnableImageEnhancers ? _imageProcessor.ImageEnhancers.Where(i =>
|
var supportedImageEnhancers = request.EnableImageEnhancers ? _imageProcessor.ImageEnhancers.Where(i =>
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@ -557,25 +536,68 @@ namespace MediaBrowser.Api.Images
|
|||||||
cacheDuration = TimeSpan.FromDays(365);
|
cacheDuration = TimeSpan.FromDays(365);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Avoid implicitly captured closure
|
|
||||||
var currentItem = item;
|
|
||||||
var currentRequest = request;
|
|
||||||
|
|
||||||
var responseHeaders = new Dictionary<string, string>
|
var responseHeaders = new Dictionary<string, string>
|
||||||
{
|
{
|
||||||
{"transferMode.dlna.org", "Interactive"},
|
{"transferMode.dlna.org", "Interactive"},
|
||||||
{"realTimeInfo.dlna.org", "DLNA.ORG_TLAG=*"}
|
{"realTimeInfo.dlna.org", "DLNA.ORG_TLAG=*"}
|
||||||
};
|
};
|
||||||
|
|
||||||
return ToCachedResult(cacheGuid, originalFileImageDateModified, cacheDuration, () => new ImageWriter
|
return GetImageResult(item,
|
||||||
{
|
request,
|
||||||
Item = currentItem,
|
imageInfo,
|
||||||
Request = currentRequest,
|
supportedImageEnhancers,
|
||||||
Enhancers = supportedImageEnhancers,
|
contentType,
|
||||||
Image = imageInfo,
|
cacheDuration,
|
||||||
ImageProcessor = _imageProcessor
|
responseHeaders,
|
||||||
|
isHeadRequest)
|
||||||
|
.Result;
|
||||||
|
}
|
||||||
|
|
||||||
}, contentType, responseHeaders);
|
private async Task<object> GetImageResult(IHasImages item,
|
||||||
|
ImageRequest request,
|
||||||
|
ItemImageInfo image,
|
||||||
|
List<IImageEnhancer> enhancers,
|
||||||
|
string contentType,
|
||||||
|
TimeSpan? cacheDuration,
|
||||||
|
IDictionary<string, string> headers,
|
||||||
|
bool isHeadRequest)
|
||||||
|
{
|
||||||
|
var cropwhitespace = request.Type == ImageType.Logo || request.Type == ImageType.Art;
|
||||||
|
|
||||||
|
if (request.CropWhitespace.HasValue)
|
||||||
|
{
|
||||||
|
cropwhitespace = request.CropWhitespace.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
var options = new ImageProcessingOptions
|
||||||
|
{
|
||||||
|
CropWhiteSpace = cropwhitespace,
|
||||||
|
Enhancers = enhancers,
|
||||||
|
Height = request.Height,
|
||||||
|
ImageIndex = request.Index ?? 0,
|
||||||
|
Image = image,
|
||||||
|
Item = item,
|
||||||
|
MaxHeight = request.MaxHeight,
|
||||||
|
MaxWidth = request.MaxWidth,
|
||||||
|
Quality = request.Quality,
|
||||||
|
Width = request.Width,
|
||||||
|
OutputFormat = request.Format,
|
||||||
|
AddPlayedIndicator = request.AddPlayedIndicator,
|
||||||
|
PercentPlayed = request.PercentPlayed,
|
||||||
|
UnplayedCount = request.UnplayedCount,
|
||||||
|
BackgroundColor = request.BackgroundColor
|
||||||
|
};
|
||||||
|
|
||||||
|
var file = await _imageProcessor.ProcessImage(options).ConfigureAwait(false);
|
||||||
|
|
||||||
|
return ResultFactory.GetStaticFileResult(Request, new StaticFileResultOptions
|
||||||
|
{
|
||||||
|
CacheDuration = cacheDuration,
|
||||||
|
ResponseHeaders = headers,
|
||||||
|
ContentType = contentType,
|
||||||
|
IsHeadRequest = isHeadRequest,
|
||||||
|
Path = file
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private string GetMimeType(ImageOutputFormat format, string path)
|
private string GetMimeType(ImageOutputFormat format, string path)
|
||||||
@ -596,6 +618,10 @@ namespace MediaBrowser.Api.Images
|
|||||||
{
|
{
|
||||||
return Common.Net.MimeTypes.GetMimeType("i.png");
|
return Common.Net.MimeTypes.GetMimeType("i.png");
|
||||||
}
|
}
|
||||||
|
if (format == ImageOutputFormat.Webp)
|
||||||
|
{
|
||||||
|
return Common.Net.MimeTypes.GetMimeType("i.webp");
|
||||||
|
}
|
||||||
|
|
||||||
return Common.Net.MimeTypes.GetMimeType(path);
|
return Common.Net.MimeTypes.GetMimeType(path);
|
||||||
}
|
}
|
||||||
|
@ -1,96 +0,0 @@
|
|||||||
using MediaBrowser.Controller.Drawing;
|
|
||||||
using MediaBrowser.Controller.Entities;
|
|
||||||
using MediaBrowser.Controller.Providers;
|
|
||||||
using MediaBrowser.Model.Entities;
|
|
||||||
using ServiceStack.Web;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace MediaBrowser.Api.Images
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Class ImageWriter
|
|
||||||
/// </summary>
|
|
||||||
public class ImageWriter : IStreamWriter, IHasOptions
|
|
||||||
{
|
|
||||||
public List<IImageEnhancer> Enhancers;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the request.
|
|
||||||
/// </summary>
|
|
||||||
/// <value>The request.</value>
|
|
||||||
public ImageRequest Request { get; set; }
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the item.
|
|
||||||
/// </summary>
|
|
||||||
/// <value>The item.</value>
|
|
||||||
public IHasImages Item { get; set; }
|
|
||||||
/// <summary>
|
|
||||||
/// The original image date modified
|
|
||||||
/// </summary>
|
|
||||||
public ItemImageInfo Image;
|
|
||||||
|
|
||||||
public IImageProcessor ImageProcessor { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The _options
|
|
||||||
/// </summary>
|
|
||||||
private readonly IDictionary<string, string> _options = new Dictionary<string, string>();
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the options.
|
|
||||||
/// </summary>
|
|
||||||
/// <value>The options.</value>
|
|
||||||
public IDictionary<string, string> Options
|
|
||||||
{
|
|
||||||
get { return _options; }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Writes to.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="responseStream">The response stream.</param>
|
|
||||||
public void WriteTo(Stream responseStream)
|
|
||||||
{
|
|
||||||
var task = WriteToAsync(responseStream);
|
|
||||||
|
|
||||||
Task.WaitAll(task);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Writes to async.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="responseStream">The response stream.</param>
|
|
||||||
/// <returns>Task.</returns>
|
|
||||||
private Task WriteToAsync(Stream responseStream)
|
|
||||||
{
|
|
||||||
var cropwhitespace = Request.Type == ImageType.Logo || Request.Type == ImageType.Art;
|
|
||||||
|
|
||||||
if (Request.CropWhitespace.HasValue)
|
|
||||||
{
|
|
||||||
cropwhitespace = Request.CropWhitespace.Value;
|
|
||||||
}
|
|
||||||
|
|
||||||
var options = new ImageProcessingOptions
|
|
||||||
{
|
|
||||||
CropWhiteSpace = cropwhitespace,
|
|
||||||
Enhancers = Enhancers,
|
|
||||||
Height = Request.Height,
|
|
||||||
ImageIndex = Request.Index ?? 0,
|
|
||||||
Image = Image,
|
|
||||||
Item = Item,
|
|
||||||
MaxHeight = Request.MaxHeight,
|
|
||||||
MaxWidth = Request.MaxWidth,
|
|
||||||
Quality = Request.Quality,
|
|
||||||
Width = Request.Width,
|
|
||||||
OutputFormat = Request.Format,
|
|
||||||
AddPlayedIndicator = Request.AddPlayedIndicator,
|
|
||||||
PercentPlayed = Request.PercentPlayed,
|
|
||||||
UnplayedCount = Request.UnplayedCount,
|
|
||||||
BackgroundColor = Request.BackgroundColor
|
|
||||||
};
|
|
||||||
|
|
||||||
return ImageProcessor.ProcessImage(options, responseStream);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -199,35 +199,33 @@ namespace MediaBrowser.Api.Images
|
|||||||
return _providerManager.GetRemoteImageProviderInfo(item).ToList();
|
return _providerManager.GetRemoteImageProviderInfo(item).ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
public object Get(GetRemoteImages request)
|
public async Task<object> Get(GetRemoteImages request)
|
||||||
{
|
{
|
||||||
var item = _libraryManager.GetItemById(request.Id);
|
var item = _libraryManager.GetItemById(request.Id);
|
||||||
|
|
||||||
var result = GetRemoteImageResult(item, request);
|
return await GetRemoteImageResult(item, request).ConfigureAwait(false);
|
||||||
|
|
||||||
return ToOptimizedSerializedResultUsingCache(result);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public object Get(GetItemByNameRemoteImages request)
|
public async Task<object> Get(GetItemByNameRemoteImages request)
|
||||||
{
|
{
|
||||||
var pathInfo = PathInfo.Parse(Request.PathInfo);
|
var pathInfo = PathInfo.Parse(Request.PathInfo);
|
||||||
var type = pathInfo.GetArgumentValue<string>(0);
|
var type = pathInfo.GetArgumentValue<string>(0);
|
||||||
|
|
||||||
var item = GetItemByName(request.Name, type, _libraryManager);
|
var item = GetItemByName(request.Name, type, _libraryManager);
|
||||||
|
|
||||||
return GetRemoteImageResult(item, request);
|
return await GetRemoteImageResult(item, request).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private RemoteImageResult GetRemoteImageResult(BaseItem item, BaseRemoteImageRequest request)
|
private async Task<RemoteImageResult> GetRemoteImageResult(BaseItem item, BaseRemoteImageRequest request)
|
||||||
{
|
{
|
||||||
var images = _providerManager.GetAvailableRemoteImages(item, new RemoteImageQuery
|
var images = await _providerManager.GetAvailableRemoteImages(item, new RemoteImageQuery
|
||||||
{
|
{
|
||||||
ProviderName = request.ProviderName,
|
ProviderName = request.ProviderName,
|
||||||
IncludeAllLanguages = request.IncludeAllLanguages,
|
IncludeAllLanguages = request.IncludeAllLanguages,
|
||||||
IncludeDisabledProviders = true,
|
IncludeDisabledProviders = true,
|
||||||
ImageType = request.Type
|
ImageType = request.Type
|
||||||
|
|
||||||
}, CancellationToken.None).Result;
|
}, CancellationToken.None).ConfigureAwait(false);
|
||||||
|
|
||||||
var imagesList = images.ToList();
|
var imagesList = images.ToList();
|
||||||
|
|
||||||
@ -308,17 +306,10 @@ namespace MediaBrowser.Api.Images
|
|||||||
/// <returns>System.Object.</returns>
|
/// <returns>System.Object.</returns>
|
||||||
public object Get(GetRemoteImage request)
|
public object Get(GetRemoteImage request)
|
||||||
{
|
{
|
||||||
var task = GetRemoteImage(request);
|
return GetAsync(request).Result;
|
||||||
|
|
||||||
return task.Result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
public async Task<object> GetAsync(GetRemoteImage request)
|
||||||
/// Gets the remote image.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="request">The request.</param>
|
|
||||||
/// <returns>Task{System.Object}.</returns>
|
|
||||||
private async Task<object> GetRemoteImage(GetRemoteImage request)
|
|
||||||
{
|
{
|
||||||
var urlHash = request.ImageUrl.GetMD5();
|
var urlHash = request.ImageUrl.GetMD5();
|
||||||
var pointerCachePath = GetFullCachePath(urlHash.ToString());
|
var pointerCachePath = GetFullCachePath(urlHash.ToString());
|
||||||
|
@ -6,6 +6,7 @@ using MediaBrowser.Controller.Entities.Audio;
|
|||||||
using MediaBrowser.Controller.Entities.Movies;
|
using MediaBrowser.Controller.Entities.Movies;
|
||||||
using MediaBrowser.Controller.Entities.TV;
|
using MediaBrowser.Controller.Entities.TV;
|
||||||
using MediaBrowser.Controller.Library;
|
using MediaBrowser.Controller.Library;
|
||||||
|
using MediaBrowser.Controller.Net;
|
||||||
using MediaBrowser.Controller.Providers;
|
using MediaBrowser.Controller.Providers;
|
||||||
using MediaBrowser.Model.Entities;
|
using MediaBrowser.Model.Entities;
|
||||||
using MediaBrowser.Model.Providers;
|
using MediaBrowser.Model.Providers;
|
||||||
@ -21,6 +22,7 @@ namespace MediaBrowser.Api
|
|||||||
{
|
{
|
||||||
[Route("/Items/{Id}/ExternalIdInfos", "GET")]
|
[Route("/Items/{Id}/ExternalIdInfos", "GET")]
|
||||||
[Api(Description = "Gets external id infos for an item")]
|
[Api(Description = "Gets external id infos for an item")]
|
||||||
|
[Authenticated]
|
||||||
public class GetExternalIdInfos : IReturn<List<ExternalIdInfo>>
|
public class GetExternalIdInfos : IReturn<List<ExternalIdInfo>>
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -33,54 +35,63 @@ namespace MediaBrowser.Api
|
|||||||
|
|
||||||
[Route("/Items/RemoteSearch/Movie", "POST")]
|
[Route("/Items/RemoteSearch/Movie", "POST")]
|
||||||
[Api(Description = "Gets external id infos for an item")]
|
[Api(Description = "Gets external id infos for an item")]
|
||||||
|
[Authenticated]
|
||||||
public class GetMovieRemoteSearchResults : RemoteSearchQuery<MovieInfo>, IReturn<List<RemoteSearchResult>>
|
public class GetMovieRemoteSearchResults : RemoteSearchQuery<MovieInfo>, IReturn<List<RemoteSearchResult>>
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
[Route("/Items/RemoteSearch/Trailer", "POST")]
|
[Route("/Items/RemoteSearch/Trailer", "POST")]
|
||||||
[Api(Description = "Gets external id infos for an item")]
|
[Api(Description = "Gets external id infos for an item")]
|
||||||
|
[Authenticated]
|
||||||
public class GetTrailerRemoteSearchResults : RemoteSearchQuery<TrailerInfo>, IReturn<List<RemoteSearchResult>>
|
public class GetTrailerRemoteSearchResults : RemoteSearchQuery<TrailerInfo>, IReturn<List<RemoteSearchResult>>
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
[Route("/Items/RemoteSearch/AdultVideo", "POST")]
|
[Route("/Items/RemoteSearch/AdultVideo", "POST")]
|
||||||
[Api(Description = "Gets external id infos for an item")]
|
[Api(Description = "Gets external id infos for an item")]
|
||||||
|
[Authenticated]
|
||||||
public class GetAdultVideoRemoteSearchResults : RemoteSearchQuery<ItemLookupInfo>, IReturn<List<RemoteSearchResult>>
|
public class GetAdultVideoRemoteSearchResults : RemoteSearchQuery<ItemLookupInfo>, IReturn<List<RemoteSearchResult>>
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
[Route("/Items/RemoteSearch/Series", "POST")]
|
[Route("/Items/RemoteSearch/Series", "POST")]
|
||||||
[Api(Description = "Gets external id infos for an item")]
|
[Api(Description = "Gets external id infos for an item")]
|
||||||
|
[Authenticated]
|
||||||
public class GetSeriesRemoteSearchResults : RemoteSearchQuery<SeriesInfo>, IReturn<List<RemoteSearchResult>>
|
public class GetSeriesRemoteSearchResults : RemoteSearchQuery<SeriesInfo>, IReturn<List<RemoteSearchResult>>
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
[Route("/Items/RemoteSearch/Game", "POST")]
|
[Route("/Items/RemoteSearch/Game", "POST")]
|
||||||
[Api(Description = "Gets external id infos for an item")]
|
[Api(Description = "Gets external id infos for an item")]
|
||||||
|
[Authenticated]
|
||||||
public class GetGameRemoteSearchResults : RemoteSearchQuery<GameInfo>, IReturn<List<RemoteSearchResult>>
|
public class GetGameRemoteSearchResults : RemoteSearchQuery<GameInfo>, IReturn<List<RemoteSearchResult>>
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
[Route("/Items/RemoteSearch/BoxSet", "POST")]
|
[Route("/Items/RemoteSearch/BoxSet", "POST")]
|
||||||
[Api(Description = "Gets external id infos for an item")]
|
[Api(Description = "Gets external id infos for an item")]
|
||||||
|
[Authenticated]
|
||||||
public class GetBoxSetRemoteSearchResults : RemoteSearchQuery<BoxSetInfo>, IReturn<List<RemoteSearchResult>>
|
public class GetBoxSetRemoteSearchResults : RemoteSearchQuery<BoxSetInfo>, IReturn<List<RemoteSearchResult>>
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
[Route("/Items/RemoteSearch/MusicArtist", "POST")]
|
[Route("/Items/RemoteSearch/MusicArtist", "POST")]
|
||||||
[Api(Description = "Gets external id infos for an item")]
|
[Api(Description = "Gets external id infos for an item")]
|
||||||
|
[Authenticated]
|
||||||
public class GetMusicArtistRemoteSearchResults : RemoteSearchQuery<ArtistInfo>, IReturn<List<RemoteSearchResult>>
|
public class GetMusicArtistRemoteSearchResults : RemoteSearchQuery<ArtistInfo>, IReturn<List<RemoteSearchResult>>
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
[Route("/Items/RemoteSearch/MusicAlbum", "POST")]
|
[Route("/Items/RemoteSearch/MusicAlbum", "POST")]
|
||||||
[Api(Description = "Gets external id infos for an item")]
|
[Api(Description = "Gets external id infos for an item")]
|
||||||
|
[Authenticated]
|
||||||
public class GetMusicAlbumRemoteSearchResults : RemoteSearchQuery<AlbumInfo>, IReturn<List<RemoteSearchResult>>
|
public class GetMusicAlbumRemoteSearchResults : RemoteSearchQuery<AlbumInfo>, IReturn<List<RemoteSearchResult>>
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
[Route("/Items/RemoteSearch/Person", "POST")]
|
[Route("/Items/RemoteSearch/Person", "POST")]
|
||||||
[Api(Description = "Gets external id infos for an item")]
|
[Api(Description = "Gets external id infos for an item")]
|
||||||
|
[Authenticated]
|
||||||
public class GetPersonRemoteSearchResults : RemoteSearchQuery<PersonLookupInfo>, IReturn<List<RemoteSearchResult>>
|
public class GetPersonRemoteSearchResults : RemoteSearchQuery<PersonLookupInfo>, IReturn<List<RemoteSearchResult>>
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
@ -98,6 +109,7 @@ namespace MediaBrowser.Api
|
|||||||
|
|
||||||
[Route("/Items/RemoteSearch/Apply/{Id}", "POST")]
|
[Route("/Items/RemoteSearch/Apply/{Id}", "POST")]
|
||||||
[Api(Description = "Applies search criteria to an item and refreshes metadata")]
|
[Api(Description = "Applies search criteria to an item and refreshes metadata")]
|
||||||
|
[Authenticated]
|
||||||
public class ApplySearchCriteria : RemoteSearchResult, IReturnVoid
|
public class ApplySearchCriteria : RemoteSearchResult, IReturnVoid
|
||||||
{
|
{
|
||||||
[ApiMember(Name = "Id", Description = "The item id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")]
|
[ApiMember(Name = "Id", Description = "The item id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")]
|
||||||
@ -212,16 +224,23 @@ namespace MediaBrowser.Api
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var task = item.RefreshMetadata(new MetadataRefreshOptions
|
var service = new ItemRefreshService(_libraryManager)
|
||||||
{
|
{
|
||||||
|
Logger = Logger,
|
||||||
|
Request = Request,
|
||||||
|
ResultFactory = ResultFactory,
|
||||||
|
SessionContext = SessionContext
|
||||||
|
};
|
||||||
|
|
||||||
|
service.Post(new RefreshItem
|
||||||
|
{
|
||||||
|
Id = request.Id,
|
||||||
MetadataRefreshMode = MetadataRefreshMode.FullRefresh,
|
MetadataRefreshMode = MetadataRefreshMode.FullRefresh,
|
||||||
ImageRefreshMode = ImageRefreshMode.FullRefresh,
|
ImageRefreshMode = ImageRefreshMode.FullRefresh,
|
||||||
ReplaceAllMetadata = true,
|
ReplaceAllMetadata = true,
|
||||||
ReplaceAllImages = true
|
ReplaceAllImages = true,
|
||||||
|
Recursive = true
|
||||||
}, CancellationToken.None);
|
});
|
||||||
|
|
||||||
Task.WaitAll(task);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
using MediaBrowser.Controller.Entities;
|
using MediaBrowser.Controller.Entities;
|
||||||
using MediaBrowser.Controller.Entities.Audio;
|
using MediaBrowser.Controller.Entities.Audio;
|
||||||
using MediaBrowser.Controller.Library;
|
using MediaBrowser.Controller.Library;
|
||||||
|
using MediaBrowser.Controller.Net;
|
||||||
using MediaBrowser.Controller.Providers;
|
using MediaBrowser.Controller.Providers;
|
||||||
using ServiceStack;
|
using ServiceStack;
|
||||||
using System;
|
using System;
|
||||||
@ -12,10 +13,16 @@ namespace MediaBrowser.Api
|
|||||||
{
|
{
|
||||||
public class BaseRefreshRequest : IReturnVoid
|
public class BaseRefreshRequest : IReturnVoid
|
||||||
{
|
{
|
||||||
[ApiMember(Name = "Forced", Description = "Indicates if a normal or forced refresh should occur.", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "POST")]
|
[ApiMember(Name = "MetadataRefreshMode", Description = "Specifies the metadata refresh mode", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "POST")]
|
||||||
public bool Forced { get; set; }
|
public MetadataRefreshMode MetadataRefreshMode { get; set; }
|
||||||
|
|
||||||
[ApiMember(Name = "ReplaceAllImages", Description = "Determines if images should be replaced during the refresh.", IsRequired = true, DataType = "boolean", ParameterType = "query", Verb = "POST")]
|
[ApiMember(Name = "ImageRefreshMode", Description = "Specifies the image refresh mode", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "POST")]
|
||||||
|
public ImageRefreshMode ImageRefreshMode { get; set; }
|
||||||
|
|
||||||
|
[ApiMember(Name = "ReplaceAllMetadata", Description = "Determines if metadata should be replaced. Only applicable if mode is FullRefresh", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "POST")]
|
||||||
|
public bool ReplaceAllMetadata { get; set; }
|
||||||
|
|
||||||
|
[ApiMember(Name = "ReplaceAllImages", Description = "Determines if images should be replaced. Only applicable if mode is FullRefresh", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "POST")]
|
||||||
public bool ReplaceAllImages { get; set; }
|
public bool ReplaceAllImages { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -30,6 +37,7 @@ namespace MediaBrowser.Api
|
|||||||
public string Id { get; set; }
|
public string Id { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Authenticated]
|
||||||
public class ItemRefreshService : BaseApiService
|
public class ItemRefreshService : BaseApiService
|
||||||
{
|
{
|
||||||
private readonly ILibraryManager _libraryManager;
|
private readonly ILibraryManager _libraryManager;
|
||||||
@ -146,10 +154,11 @@ namespace MediaBrowser.Api
|
|||||||
{
|
{
|
||||||
return new MetadataRefreshOptions
|
return new MetadataRefreshOptions
|
||||||
{
|
{
|
||||||
MetadataRefreshMode = MetadataRefreshMode.FullRefresh,
|
MetadataRefreshMode = request.MetadataRefreshMode,
|
||||||
ImageRefreshMode = ImageRefreshMode.FullRefresh,
|
ImageRefreshMode = request.ImageRefreshMode,
|
||||||
ReplaceAllMetadata = request.Forced,
|
ReplaceAllImages = request.ReplaceAllImages,
|
||||||
ReplaceAllImages = request.ReplaceAllImages
|
ReplaceAllMetadata = request.ReplaceAllMetadata,
|
||||||
|
ForceSave = true
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,12 @@
|
|||||||
using System.Collections.Generic;
|
using MediaBrowser.Controller.Entities;
|
||||||
using MediaBrowser.Controller.Entities;
|
|
||||||
using MediaBrowser.Controller.Entities.Audio;
|
using MediaBrowser.Controller.Entities.Audio;
|
||||||
using MediaBrowser.Controller.Entities.TV;
|
using MediaBrowser.Controller.Entities.TV;
|
||||||
using MediaBrowser.Controller.Library;
|
using MediaBrowser.Controller.Library;
|
||||||
|
using MediaBrowser.Controller.Net;
|
||||||
using MediaBrowser.Model.Dto;
|
using MediaBrowser.Model.Dto;
|
||||||
using ServiceStack;
|
using ServiceStack;
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
@ -19,6 +20,7 @@ namespace MediaBrowser.Api
|
|||||||
public string ItemId { get; set; }
|
public string ItemId { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Authenticated]
|
||||||
public class ItemUpdateService : BaseApiService
|
public class ItemUpdateService : BaseApiService
|
||||||
{
|
{
|
||||||
private readonly ILibraryManager _libraryManager;
|
private readonly ILibraryManager _libraryManager;
|
||||||
@ -63,6 +65,11 @@ namespace MediaBrowser.Api
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private DateTime NormalizeDateTime(DateTime val)
|
||||||
|
{
|
||||||
|
return DateTime.SpecifyKind(val, DateTimeKind.Utc);
|
||||||
|
}
|
||||||
|
|
||||||
private void UpdateItem(BaseItemDto request, BaseItem item)
|
private void UpdateItem(BaseItemDto request, BaseItem item)
|
||||||
{
|
{
|
||||||
item.Name = request.Name;
|
item.Name = request.Name;
|
||||||
@ -108,6 +115,12 @@ namespace MediaBrowser.Api
|
|||||||
hasTags.Tags = request.Tags;
|
hasTags.Tags = request.Tags;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var hasTaglines = item as IHasTaglines;
|
||||||
|
if (hasTaglines != null)
|
||||||
|
{
|
||||||
|
hasTaglines.Taglines = request.Taglines;
|
||||||
|
}
|
||||||
|
|
||||||
var hasShortOverview = item as IHasShortOverview;
|
var hasShortOverview = item as IHasShortOverview;
|
||||||
if (hasShortOverview != null)
|
if (hasShortOverview != null)
|
||||||
{
|
{
|
||||||
@ -132,11 +145,11 @@ namespace MediaBrowser.Api
|
|||||||
|
|
||||||
if (request.DateCreated.HasValue)
|
if (request.DateCreated.HasValue)
|
||||||
{
|
{
|
||||||
item.DateCreated = request.DateCreated.Value.ToUniversalTime();
|
item.DateCreated = NormalizeDateTime(request.DateCreated.Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
item.EndDate = request.EndDate.HasValue ? request.EndDate.Value.ToUniversalTime() : (DateTime?)null;
|
item.EndDate = request.EndDate.HasValue ? NormalizeDateTime(request.EndDate.Value) : (DateTime?)null;
|
||||||
item.PremiereDate = request.PremiereDate.HasValue ? request.PremiereDate.Value.ToUniversalTime() : (DateTime?)null;
|
item.PremiereDate = request.PremiereDate.HasValue ? NormalizeDateTime(request.PremiereDate.Value) : (DateTime?)null;
|
||||||
item.ProductionYear = request.ProductionYear;
|
item.ProductionYear = request.ProductionYear;
|
||||||
item.OfficialRating = request.OfficialRating;
|
item.OfficialRating = request.OfficialRating;
|
||||||
item.CustomRating = request.CustomRating;
|
item.CustomRating = request.CustomRating;
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
using MediaBrowser.Controller.Chapters;
|
using MediaBrowser.Controller.Chapters;
|
||||||
|
using MediaBrowser.Controller.Net;
|
||||||
using ServiceStack;
|
using ServiceStack;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
|
||||||
@ -9,6 +10,7 @@ namespace MediaBrowser.Api.Library
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Authenticated]
|
||||||
public class ChapterService : BaseApiService
|
public class ChapterService : BaseApiService
|
||||||
{
|
{
|
||||||
private readonly IChapterManager _chapterManager;
|
private readonly IChapterManager _chapterManager;
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
using MediaBrowser.Controller.FileOrganization;
|
using MediaBrowser.Controller.FileOrganization;
|
||||||
|
using MediaBrowser.Controller.Net;
|
||||||
using MediaBrowser.Model.FileOrganization;
|
using MediaBrowser.Model.FileOrganization;
|
||||||
using MediaBrowser.Model.Querying;
|
using MediaBrowser.Model.Querying;
|
||||||
using ServiceStack;
|
using ServiceStack;
|
||||||
@ -78,6 +79,7 @@ namespace MediaBrowser.Api.Library
|
|||||||
public bool RememberCorrection { get; set; }
|
public bool RememberCorrection { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Authenticated]
|
||||||
public class FileOrganizationService : BaseApiService
|
public class FileOrganizationService : BaseApiService
|
||||||
{
|
{
|
||||||
private readonly IFileOrganizationService _iFileOrganizationService;
|
private readonly IFileOrganizationService _iFileOrganizationService;
|
||||||
|
@ -65,7 +65,7 @@ namespace MediaBrowser.Api.Library
|
|||||||
var rootFolderPath = appPaths.DefaultUserViewsPath;
|
var rootFolderPath = appPaths.DefaultUserViewsPath;
|
||||||
var virtualFolderPath = Path.Combine(rootFolderPath, virtualFolderName);
|
var virtualFolderPath = Path.Combine(rootFolderPath, virtualFolderName);
|
||||||
|
|
||||||
var shortcutFilename = Path.GetFileNameWithoutExtension(path);
|
var shortcutFilename = fileSystem.GetFileNameWithoutExtension(path);
|
||||||
|
|
||||||
var lnk = Path.Combine(virtualFolderPath, shortcutFilename + ShortcutFileExtension);
|
var lnk = Path.Combine(virtualFolderPath, shortcutFilename + ShortcutFileExtension);
|
||||||
|
|
||||||
|
@ -5,6 +5,7 @@ using MediaBrowser.Controller.Entities.Audio;
|
|||||||
using MediaBrowser.Controller.Entities.Movies;
|
using MediaBrowser.Controller.Entities.Movies;
|
||||||
using MediaBrowser.Controller.Entities.TV;
|
using MediaBrowser.Controller.Entities.TV;
|
||||||
using MediaBrowser.Controller.Library;
|
using MediaBrowser.Controller.Library;
|
||||||
|
using MediaBrowser.Controller.Net;
|
||||||
using MediaBrowser.Controller.Persistence;
|
using MediaBrowser.Controller.Persistence;
|
||||||
using MediaBrowser.Controller.Session;
|
using MediaBrowser.Controller.Session;
|
||||||
using MediaBrowser.Model.Dto;
|
using MediaBrowser.Model.Dto;
|
||||||
@ -210,20 +211,23 @@ namespace MediaBrowser.Api.Library
|
|||||||
[Api(Description = "Gets all user media folders.")]
|
[Api(Description = "Gets all user media folders.")]
|
||||||
public class GetMediaFolders : IReturn<ItemsResult>
|
public class GetMediaFolders : IReturn<ItemsResult>
|
||||||
{
|
{
|
||||||
|
[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; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Route("/Library/Series/Added", "POST")]
|
||||||
[Route("/Library/Series/Updated", "POST")]
|
[Route("/Library/Series/Updated", "POST")]
|
||||||
[Api(Description = "Reports that new episodes of a series have been added by an external source")]
|
[Api(Description = "Reports that new episodes of a series have been added by an external source")]
|
||||||
public class PostUpdatedSeries : IReturnVoid
|
public class PostUpdatedSeries : IReturnVoid
|
||||||
{
|
{
|
||||||
[ApiMember(Name = "TvdbId", Description = "Tvdb Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
|
[ApiMember(Name = "TvdbId", Description = "Tvdb Id", IsRequired = false, DataType = "string", ParameterType = "path", Verb = "GET")]
|
||||||
public string TvdbId { get; set; }
|
public string TvdbId { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Class LibraryService
|
/// Class LibraryService
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[Authenticated]
|
||||||
public class LibraryService : BaseApiService
|
public class LibraryService : BaseApiService
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -258,6 +262,13 @@ namespace MediaBrowser.Api.Library
|
|||||||
{
|
{
|
||||||
var items = _libraryManager.GetUserRootFolder().Children.OrderBy(i => i.SortName).ToList();
|
var items = _libraryManager.GetUserRootFolder().Children.OrderBy(i => i.SortName).ToList();
|
||||||
|
|
||||||
|
if (request.IsHidden.HasValue)
|
||||||
|
{
|
||||||
|
var val = request.IsHidden.Value;
|
||||||
|
|
||||||
|
items = items.Where(i => i.IsHidden == val).ToList();
|
||||||
|
}
|
||||||
|
|
||||||
// Get everything
|
// Get everything
|
||||||
var fields = Enum.GetNames(typeof(ItemFields))
|
var fields = Enum.GetNames(typeof(ItemFields))
|
||||||
.Select(i => (ItemFields)Enum.Parse(typeof(ItemFields), i, true))
|
.Select(i => (ItemFields)Enum.Parse(typeof(ItemFields), i, true))
|
||||||
@ -273,6 +284,11 @@ namespace MediaBrowser.Api.Library
|
|||||||
return ToOptimizedResult(result);
|
return ToOptimizedResult(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void Post(PostUpdatedSeries request)
|
||||||
|
{
|
||||||
|
Task.Run(() => _libraryManager.ValidateMediaLibrary(new Progress<double>(), CancellationToken.None));
|
||||||
|
}
|
||||||
|
|
||||||
public object Get(GetFile request)
|
public object Get(GetFile request)
|
||||||
{
|
{
|
||||||
var item = _libraryManager.GetItemById(request.Id);
|
var item = _libraryManager.GetItemById(request.Id);
|
||||||
@ -451,24 +467,12 @@ namespace MediaBrowser.Api.Library
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="request">The request.</param>
|
/// <param name="request">The request.</param>
|
||||||
public void Delete(DeleteItem request)
|
public void Delete(DeleteItem request)
|
||||||
{
|
|
||||||
var task = DeleteItem(request);
|
|
||||||
|
|
||||||
Task.WaitAll(task);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Task DeleteItem(DeleteItem request)
|
|
||||||
{
|
{
|
||||||
var item = _libraryManager.GetItemById(request.Id);
|
var item = _libraryManager.GetItemById(request.Id);
|
||||||
|
|
||||||
var session = GetSession(_sessionManager);
|
var task = _libraryManager.DeleteItem(item);
|
||||||
|
|
||||||
if (!session.UserId.HasValue || !_userManager.GetUserById(session.UserId.Value).Configuration.EnableContentDeletion)
|
Task.WaitAll(task);
|
||||||
{
|
|
||||||
throw new UnauthorizedAccessException("This operation requires a logged in user with delete access.");
|
|
||||||
}
|
|
||||||
|
|
||||||
return _libraryManager.DeleteItem(item);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
using MediaBrowser.Common.IO;
|
using MediaBrowser.Common.IO;
|
||||||
using MediaBrowser.Controller;
|
using MediaBrowser.Controller;
|
||||||
using MediaBrowser.Controller.Library;
|
using MediaBrowser.Controller.Library;
|
||||||
|
using MediaBrowser.Controller.Net;
|
||||||
using MediaBrowser.Model.Entities;
|
using MediaBrowser.Model.Entities;
|
||||||
using MediaBrowser.Model.Logging;
|
using MediaBrowser.Model.Logging;
|
||||||
using ServiceStack;
|
using ServiceStack;
|
||||||
@ -131,35 +132,10 @@ namespace MediaBrowser.Api.Library
|
|||||||
public bool RefreshLibrary { get; set; }
|
public bool RefreshLibrary { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
[Route("/Library/Downloaded", "POST")]
|
|
||||||
public class ReportContentDownloaded : IReturnVoid
|
|
||||||
{
|
|
||||||
[ApiMember(Name = "Path", Description = "The path being downloaded to.", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")]
|
|
||||||
public string Path { get; set; }
|
|
||||||
|
|
||||||
[ApiMember(Name = "ImageUrl", Description = "Optional thumbnail image url of the content.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")]
|
|
||||||
public string ImageUrl { get; set; }
|
|
||||||
|
|
||||||
[ApiMember(Name = "Name", Description = "The name of the content.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")]
|
|
||||||
public string Name { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
[Route("/Library/Downloading", "POST")]
|
|
||||||
public class ReportContentDownloading : IReturnVoid
|
|
||||||
{
|
|
||||||
[ApiMember(Name = "Path", Description = "The path being downloaded to.", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")]
|
|
||||||
public string Path { get; set; }
|
|
||||||
|
|
||||||
[ApiMember(Name = "ImageUrl", Description = "Optional thumbnail image url of the content.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")]
|
|
||||||
public string ImageUrl { get; set; }
|
|
||||||
|
|
||||||
[ApiMember(Name = "Name", Description = "The name of the content.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")]
|
|
||||||
public string Name { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Class LibraryStructureService
|
/// Class LibraryStructureService
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[Authenticated]
|
||||||
public class LibraryStructureService : BaseApiService
|
public class LibraryStructureService : BaseApiService
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
using MediaBrowser.Controller.Library;
|
using MediaBrowser.Controller.Library;
|
||||||
using MediaBrowser.Controller.LiveTv;
|
using MediaBrowser.Controller.LiveTv;
|
||||||
|
using MediaBrowser.Controller.Net;
|
||||||
using MediaBrowser.Model.Dto;
|
using MediaBrowser.Model.Dto;
|
||||||
using MediaBrowser.Model.Entities;
|
using MediaBrowser.Model.Entities;
|
||||||
using MediaBrowser.Model.LiveTv;
|
using MediaBrowser.Model.LiveTv;
|
||||||
@ -267,6 +268,7 @@ namespace MediaBrowser.Api.LiveTv
|
|||||||
public string UserId { get; set; }
|
public string UserId { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Authenticated]
|
||||||
public class LiveTvService : BaseApiService
|
public class LiveTvService : BaseApiService
|
||||||
{
|
{
|
||||||
private readonly ILiveTvManager _liveTvManager;
|
private readonly ILiveTvManager _liveTvManager;
|
||||||
@ -280,7 +282,7 @@ namespace MediaBrowser.Api.LiveTv
|
|||||||
|
|
||||||
private void AssertUserCanManageLiveTv()
|
private void AssertUserCanManageLiveTv()
|
||||||
{
|
{
|
||||||
var user = AuthorizationRequestFilterAttribute.GetCurrentUser(Request, _userManager);
|
var user = SessionContext.GetUser(Request);
|
||||||
|
|
||||||
if (user == null)
|
if (user == null)
|
||||||
{
|
{
|
||||||
@ -293,16 +295,16 @@ namespace MediaBrowser.Api.LiveTv
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public object Get(GetLiveTvInfo request)
|
public async Task<object> Get(GetLiveTvInfo request)
|
||||||
{
|
{
|
||||||
var info = _liveTvManager.GetLiveTvInfo(CancellationToken.None).Result;
|
var info = await _liveTvManager.GetLiveTvInfo(CancellationToken.None).ConfigureAwait(false);
|
||||||
|
|
||||||
return ToOptimizedSerializedResultUsingCache(info);
|
return ToOptimizedSerializedResultUsingCache(info);
|
||||||
}
|
}
|
||||||
|
|
||||||
public object Get(GetChannels request)
|
public async Task<object> Get(GetChannels request)
|
||||||
{
|
{
|
||||||
var result = _liveTvManager.GetChannels(new LiveTvChannelQuery
|
var result = await _liveTvManager.GetChannels(new LiveTvChannelQuery
|
||||||
{
|
{
|
||||||
ChannelType = request.Type,
|
ChannelType = request.Type,
|
||||||
UserId = request.UserId,
|
UserId = request.UserId,
|
||||||
@ -312,26 +314,26 @@ namespace MediaBrowser.Api.LiveTv
|
|||||||
IsLiked = request.IsLiked,
|
IsLiked = request.IsLiked,
|
||||||
IsDisliked = request.IsDisliked
|
IsDisliked = request.IsDisliked
|
||||||
|
|
||||||
}, CancellationToken.None).Result;
|
}, CancellationToken.None).ConfigureAwait(false);
|
||||||
|
|
||||||
return ToOptimizedSerializedResultUsingCache(result);
|
return ToOptimizedSerializedResultUsingCache(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
public object Get(GetChannel request)
|
public async Task<object> Get(GetChannel request)
|
||||||
{
|
{
|
||||||
var user = string.IsNullOrEmpty(request.UserId) ? null : _userManager.GetUserById(new Guid(request.UserId));
|
var user = string.IsNullOrEmpty(request.UserId) ? null : _userManager.GetUserById(new Guid(request.UserId));
|
||||||
|
|
||||||
var result = _liveTvManager.GetChannel(request.Id, CancellationToken.None, user).Result;
|
var result = await _liveTvManager.GetChannel(request.Id, CancellationToken.None, user).ConfigureAwait(false);
|
||||||
|
|
||||||
return ToOptimizedSerializedResultUsingCache(result);
|
return ToOptimizedSerializedResultUsingCache(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
public object Get(GetLiveTvFolder request)
|
public async Task<object> Get(GetLiveTvFolder request)
|
||||||
{
|
{
|
||||||
return ToOptimizedResult(_liveTvManager.GetLiveTvFolder(request.UserId, CancellationToken.None).Result);
|
return ToOptimizedResult(await _liveTvManager.GetLiveTvFolder(request.UserId, CancellationToken.None).ConfigureAwait(false));
|
||||||
}
|
}
|
||||||
|
|
||||||
public object Get(GetPrograms request)
|
public async Task<object> Get(GetPrograms request)
|
||||||
{
|
{
|
||||||
var query = new ProgramQuery
|
var query = new ProgramQuery
|
||||||
{
|
{
|
||||||
@ -359,12 +361,12 @@ namespace MediaBrowser.Api.LiveTv
|
|||||||
query.MaxEndDate = DateTime.Parse(request.MaxEndDate, null, DateTimeStyles.RoundtripKind).ToUniversalTime();
|
query.MaxEndDate = DateTime.Parse(request.MaxEndDate, null, DateTimeStyles.RoundtripKind).ToUniversalTime();
|
||||||
}
|
}
|
||||||
|
|
||||||
var result = _liveTvManager.GetPrograms(query, CancellationToken.None).Result;
|
var result = await _liveTvManager.GetPrograms(query, CancellationToken.None).ConfigureAwait(false);
|
||||||
|
|
||||||
return ToOptimizedSerializedResultUsingCache(result);
|
return ToOptimizedSerializedResultUsingCache(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
public object Get(GetRecommendedPrograms request)
|
public async Task<object> Get(GetRecommendedPrograms request)
|
||||||
{
|
{
|
||||||
var query = new RecommendedProgramQuery
|
var query = new RecommendedProgramQuery
|
||||||
{
|
{
|
||||||
@ -374,7 +376,7 @@ namespace MediaBrowser.Api.LiveTv
|
|||||||
HasAired = request.HasAired
|
HasAired = request.HasAired
|
||||||
};
|
};
|
||||||
|
|
||||||
var result = _liveTvManager.GetRecommendedPrograms(query, CancellationToken.None).Result;
|
var result = await _liveTvManager.GetRecommendedPrograms(query, CancellationToken.None).ConfigureAwait(false);
|
||||||
|
|
||||||
return ToOptimizedSerializedResultUsingCache(result);
|
return ToOptimizedSerializedResultUsingCache(result);
|
||||||
}
|
}
|
||||||
@ -384,9 +386,9 @@ namespace MediaBrowser.Api.LiveTv
|
|||||||
return Get(request);
|
return Get(request);
|
||||||
}
|
}
|
||||||
|
|
||||||
public object Get(GetRecordings request)
|
public async Task<object> Get(GetRecordings request)
|
||||||
{
|
{
|
||||||
var result = _liveTvManager.GetRecordings(new RecordingQuery
|
var result = await _liveTvManager.GetRecordings(new RecordingQuery
|
||||||
{
|
{
|
||||||
ChannelId = request.ChannelId,
|
ChannelId = request.ChannelId,
|
||||||
UserId = request.UserId,
|
UserId = request.UserId,
|
||||||
@ -397,35 +399,35 @@ namespace MediaBrowser.Api.LiveTv
|
|||||||
SeriesTimerId = request.SeriesTimerId,
|
SeriesTimerId = request.SeriesTimerId,
|
||||||
IsInProgress = request.IsInProgress
|
IsInProgress = request.IsInProgress
|
||||||
|
|
||||||
}, CancellationToken.None).Result;
|
}, CancellationToken.None).ConfigureAwait(false);
|
||||||
|
|
||||||
return ToOptimizedSerializedResultUsingCache(result);
|
return ToOptimizedSerializedResultUsingCache(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
public object Get(GetRecording request)
|
public async Task<object> Get(GetRecording request)
|
||||||
{
|
{
|
||||||
var user = string.IsNullOrEmpty(request.UserId) ? null : _userManager.GetUserById(new Guid(request.UserId));
|
var user = string.IsNullOrEmpty(request.UserId) ? null : _userManager.GetUserById(new Guid(request.UserId));
|
||||||
|
|
||||||
var result = _liveTvManager.GetRecording(request.Id, CancellationToken.None, user).Result;
|
var result = await _liveTvManager.GetRecording(request.Id, CancellationToken.None, user).ConfigureAwait(false);
|
||||||
|
|
||||||
return ToOptimizedSerializedResultUsingCache(result);
|
return ToOptimizedSerializedResultUsingCache(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
public object Get(GetTimer request)
|
public async Task<object> Get(GetTimer request)
|
||||||
{
|
{
|
||||||
var result = _liveTvManager.GetTimer(request.Id, CancellationToken.None).Result;
|
var result = await _liveTvManager.GetTimer(request.Id, CancellationToken.None).ConfigureAwait(false);
|
||||||
|
|
||||||
return ToOptimizedSerializedResultUsingCache(result);
|
return ToOptimizedSerializedResultUsingCache(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
public object Get(GetTimers request)
|
public async Task<object> Get(GetTimers request)
|
||||||
{
|
{
|
||||||
var result = _liveTvManager.GetTimers(new TimerQuery
|
var result = await _liveTvManager.GetTimers(new TimerQuery
|
||||||
{
|
{
|
||||||
ChannelId = request.ChannelId,
|
ChannelId = request.ChannelId,
|
||||||
SeriesTimerId = request.SeriesTimerId
|
SeriesTimerId = request.SeriesTimerId
|
||||||
|
|
||||||
}, CancellationToken.None).Result;
|
}, CancellationToken.None).ConfigureAwait(false);
|
||||||
|
|
||||||
return ToOptimizedSerializedResultUsingCache(result);
|
return ToOptimizedSerializedResultUsingCache(result);
|
||||||
}
|
}
|
||||||
@ -457,21 +459,21 @@ namespace MediaBrowser.Api.LiveTv
|
|||||||
Task.WaitAll(task);
|
Task.WaitAll(task);
|
||||||
}
|
}
|
||||||
|
|
||||||
public object Get(GetSeriesTimers request)
|
public async Task<object> Get(GetSeriesTimers request)
|
||||||
{
|
{
|
||||||
var result = _liveTvManager.GetSeriesTimers(new SeriesTimerQuery
|
var result = await _liveTvManager.GetSeriesTimers(new SeriesTimerQuery
|
||||||
{
|
{
|
||||||
SortOrder = request.SortOrder,
|
SortOrder = request.SortOrder,
|
||||||
SortBy = request.SortBy
|
SortBy = request.SortBy
|
||||||
|
|
||||||
}, CancellationToken.None).Result;
|
}, CancellationToken.None).ConfigureAwait(false);
|
||||||
|
|
||||||
return ToOptimizedSerializedResultUsingCache(result);
|
return ToOptimizedSerializedResultUsingCache(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
public object Get(GetSeriesTimer request)
|
public async Task<object> Get(GetSeriesTimer request)
|
||||||
{
|
{
|
||||||
var result = _liveTvManager.GetSeriesTimer(request.Id, CancellationToken.None).Result;
|
var result = await _liveTvManager.GetSeriesTimer(request.Id, CancellationToken.None).ConfigureAwait(false);
|
||||||
|
|
||||||
return ToOptimizedSerializedResultUsingCache(result);
|
return ToOptimizedSerializedResultUsingCache(result);
|
||||||
}
|
}
|
||||||
@ -494,27 +496,27 @@ namespace MediaBrowser.Api.LiveTv
|
|||||||
Task.WaitAll(task);
|
Task.WaitAll(task);
|
||||||
}
|
}
|
||||||
|
|
||||||
public object Get(GetDefaultTimer request)
|
public async Task<object> Get(GetDefaultTimer request)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(request.ProgramId))
|
if (string.IsNullOrEmpty(request.ProgramId))
|
||||||
{
|
{
|
||||||
var result = _liveTvManager.GetNewTimerDefaults(CancellationToken.None).Result;
|
var result = await _liveTvManager.GetNewTimerDefaults(CancellationToken.None).ConfigureAwait(false);
|
||||||
|
|
||||||
return ToOptimizedSerializedResultUsingCache(result);
|
return ToOptimizedSerializedResultUsingCache(result);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var result = _liveTvManager.GetNewTimerDefaults(request.ProgramId, CancellationToken.None).Result;
|
var result = await _liveTvManager.GetNewTimerDefaults(request.ProgramId, CancellationToken.None).ConfigureAwait(false);
|
||||||
|
|
||||||
return ToOptimizedSerializedResultUsingCache(result);
|
return ToOptimizedSerializedResultUsingCache(result);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public object Get(GetProgram request)
|
public async Task<object> Get(GetProgram request)
|
||||||
{
|
{
|
||||||
var user = string.IsNullOrEmpty(request.UserId) ? null : _userManager.GetUserById(new Guid(request.UserId));
|
var user = string.IsNullOrEmpty(request.UserId) ? null : _userManager.GetUserById(new Guid(request.UserId));
|
||||||
|
|
||||||
var result = _liveTvManager.GetProgram(request.Id, CancellationToken.None, user).Result;
|
var result = await _liveTvManager.GetProgram(request.Id, CancellationToken.None, user).ConfigureAwait(false);
|
||||||
|
|
||||||
return ToOptimizedSerializedResultUsingCache(result);
|
return ToOptimizedSerializedResultUsingCache(result);
|
||||||
}
|
}
|
||||||
@ -537,23 +539,23 @@ namespace MediaBrowser.Api.LiveTv
|
|||||||
Task.WaitAll(task);
|
Task.WaitAll(task);
|
||||||
}
|
}
|
||||||
|
|
||||||
public object Get(GetRecordingGroups request)
|
public async Task<object> Get(GetRecordingGroups request)
|
||||||
{
|
{
|
||||||
var result = _liveTvManager.GetRecordingGroups(new RecordingGroupQuery
|
var result = await _liveTvManager.GetRecordingGroups(new RecordingGroupQuery
|
||||||
{
|
{
|
||||||
UserId = request.UserId
|
UserId = request.UserId
|
||||||
|
|
||||||
}, CancellationToken.None).Result;
|
}, CancellationToken.None).ConfigureAwait(false);
|
||||||
|
|
||||||
return ToOptimizedSerializedResultUsingCache(result);
|
return ToOptimizedSerializedResultUsingCache(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
public object Get(GetRecordingGroup request)
|
public async Task<object> Get(GetRecordingGroup request)
|
||||||
{
|
{
|
||||||
var result = _liveTvManager.GetRecordingGroups(new RecordingGroupQuery
|
var result = await _liveTvManager.GetRecordingGroups(new RecordingGroupQuery
|
||||||
{
|
{
|
||||||
|
|
||||||
}, CancellationToken.None).Result;
|
}, CancellationToken.None).ConfigureAwait(false);
|
||||||
|
|
||||||
var group = result.Items.FirstOrDefault(i => string.Equals(i.Id, request.Id, StringComparison.OrdinalIgnoreCase));
|
var group = result.Items.FirstOrDefault(i => string.Equals(i.Id, request.Id, StringComparison.OrdinalIgnoreCase));
|
||||||
|
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
using MediaBrowser.Controller.Localization;
|
using MediaBrowser.Controller.Localization;
|
||||||
|
using MediaBrowser.Controller.Net;
|
||||||
using MediaBrowser.Model.Entities;
|
using MediaBrowser.Model.Entities;
|
||||||
using MediaBrowser.Model.Globalization;
|
using MediaBrowser.Model.Globalization;
|
||||||
using ServiceStack;
|
using ServiceStack;
|
||||||
@ -42,6 +43,7 @@ namespace MediaBrowser.Api
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Class CulturesService
|
/// Class CulturesService
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[Authenticated]
|
||||||
public class LocalizationService : BaseApiService
|
public class LocalizationService : BaseApiService
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -49,6 +49,9 @@
|
|||||||
<RunPostBuildEvent>Always</RunPostBuildEvent>
|
<RunPostBuildEvent>Always</RunPostBuildEvent>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<Reference Include="MoreLinq">
|
||||||
|
<HintPath>..\packages\morelinq.1.0.16006\lib\net35\MoreLinq.dll</HintPath>
|
||||||
|
</Reference>
|
||||||
<Reference Include="System" />
|
<Reference Include="System" />
|
||||||
<Reference Include="System.Core" />
|
<Reference Include="System.Core" />
|
||||||
<Reference Include="Microsoft.CSharp" />
|
<Reference Include="Microsoft.CSharp" />
|
||||||
@ -65,27 +68,25 @@
|
|||||||
<Compile Include="..\SharedVersion.cs">
|
<Compile Include="..\SharedVersion.cs">
|
||||||
<Link>Properties\SharedVersion.cs</Link>
|
<Link>Properties\SharedVersion.cs</Link>
|
||||||
</Compile>
|
</Compile>
|
||||||
|
<Compile Include="BrandingService.cs" />
|
||||||
<Compile Include="ChannelService.cs" />
|
<Compile Include="ChannelService.cs" />
|
||||||
<Compile Include="Dlna\DlnaServerService.cs" />
|
<Compile Include="Dlna\DlnaServerService.cs" />
|
||||||
<Compile Include="Dlna\DlnaService.cs" />
|
<Compile Include="Dlna\DlnaService.cs" />
|
||||||
<Compile Include="Library\ChapterService.cs" />
|
<Compile Include="Library\ChapterService.cs" />
|
||||||
<Compile Include="Library\SubtitleService.cs" />
|
<Compile Include="PlaylistService.cs" />
|
||||||
|
<Compile Include="Subtitles\SubtitleService.cs" />
|
||||||
<Compile Include="Movies\CollectionService.cs" />
|
<Compile Include="Movies\CollectionService.cs" />
|
||||||
<Compile Include="Music\AlbumsService.cs" />
|
<Compile Include="Music\AlbumsService.cs" />
|
||||||
<Compile Include="AppThemeService.cs" />
|
<Compile Include="AppThemeService.cs" />
|
||||||
<Compile Include="BaseApiService.cs" />
|
<Compile Include="BaseApiService.cs" />
|
||||||
<Compile Include="ConfigurationService.cs" />
|
<Compile Include="ConfigurationService.cs" />
|
||||||
<Compile Include="DefaultTheme\DefaultThemeService.cs" />
|
|
||||||
<Compile Include="DefaultTheme\Models.cs" />
|
|
||||||
<Compile Include="DisplayPreferencesService.cs" />
|
<Compile Include="DisplayPreferencesService.cs" />
|
||||||
<Compile Include="EnvironmentService.cs" />
|
<Compile Include="EnvironmentService.cs" />
|
||||||
<Compile Include="AuthorizationRequestFilterAttribute.cs" />
|
|
||||||
<Compile Include="GamesService.cs" />
|
<Compile Include="GamesService.cs" />
|
||||||
<Compile Include="IHasItemFields.cs" />
|
<Compile Include="IHasItemFields.cs" />
|
||||||
<Compile Include="Images\ImageByNameService.cs" />
|
<Compile Include="Images\ImageByNameService.cs" />
|
||||||
<Compile Include="Images\ImageRequest.cs" />
|
<Compile Include="Images\ImageRequest.cs" />
|
||||||
<Compile Include="Images\ImageService.cs" />
|
<Compile Include="Images\ImageService.cs" />
|
||||||
<Compile Include="Images\ImageWriter.cs" />
|
|
||||||
<Compile Include="Music\InstantMixService.cs" />
|
<Compile Include="Music\InstantMixService.cs" />
|
||||||
<Compile Include="ItemLookupService.cs" />
|
<Compile Include="ItemLookupService.cs" />
|
||||||
<Compile Include="ItemRefreshService.cs" />
|
<Compile Include="ItemRefreshService.cs" />
|
||||||
@ -101,7 +102,7 @@
|
|||||||
<Compile Include="NotificationsService.cs" />
|
<Compile Include="NotificationsService.cs" />
|
||||||
<Compile Include="PackageReviewService.cs" />
|
<Compile Include="PackageReviewService.cs" />
|
||||||
<Compile Include="PackageService.cs" />
|
<Compile Include="PackageService.cs" />
|
||||||
<Compile Include="Playback\EndlessStreamCopy.cs" />
|
<Compile Include="Playback\BifService.cs" />
|
||||||
<Compile Include="Playback\Hls\BaseHlsService.cs" />
|
<Compile Include="Playback\Hls\BaseHlsService.cs" />
|
||||||
<Compile Include="Playback\Hls\DynamicHlsService.cs" />
|
<Compile Include="Playback\Hls\DynamicHlsService.cs" />
|
||||||
<Compile Include="Playback\Hls\HlsSegmentService.cs" />
|
<Compile Include="Playback\Hls\HlsSegmentService.cs" />
|
||||||
@ -120,9 +121,12 @@
|
|||||||
<Compile Include="ScheduledTasks\ScheduledTasksWebSocketListener.cs" />
|
<Compile Include="ScheduledTasks\ScheduledTasksWebSocketListener.cs" />
|
||||||
<Compile Include="ApiEntryPoint.cs" />
|
<Compile Include="ApiEntryPoint.cs" />
|
||||||
<Compile Include="SearchService.cs" />
|
<Compile Include="SearchService.cs" />
|
||||||
<Compile Include="SessionsService.cs" />
|
<Compile Include="Session\SessionsService.cs" />
|
||||||
<Compile Include="SimilarItemsHelper.cs" />
|
<Compile Include="SimilarItemsHelper.cs" />
|
||||||
<Compile Include="SystemService.cs" />
|
<Compile Include="Sync\SyncService.cs" />
|
||||||
|
<Compile Include="System\ActivityLogService.cs" />
|
||||||
|
<Compile Include="System\ActivityLogWebSocketListener.cs" />
|
||||||
|
<Compile Include="System\SystemService.cs" />
|
||||||
<Compile Include="Movies\TrailersService.cs" />
|
<Compile Include="Movies\TrailersService.cs" />
|
||||||
<Compile Include="TvShowsService.cs" />
|
<Compile Include="TvShowsService.cs" />
|
||||||
<Compile Include="UserLibrary\ArtistsService.cs" />
|
<Compile Include="UserLibrary\ArtistsService.cs" />
|
||||||
@ -133,15 +137,15 @@
|
|||||||
<Compile Include="UserLibrary\ItemsService.cs" />
|
<Compile Include="UserLibrary\ItemsService.cs" />
|
||||||
<Compile Include="UserLibrary\MusicGenresService.cs" />
|
<Compile Include="UserLibrary\MusicGenresService.cs" />
|
||||||
<Compile Include="UserLibrary\PersonsService.cs" />
|
<Compile Include="UserLibrary\PersonsService.cs" />
|
||||||
|
<Compile Include="UserLibrary\PlaystateService.cs" />
|
||||||
<Compile Include="UserLibrary\StudiosService.cs" />
|
<Compile Include="UserLibrary\StudiosService.cs" />
|
||||||
<Compile Include="UserLibrary\UserLibraryService.cs" />
|
<Compile Include="UserLibrary\UserLibraryService.cs" />
|
||||||
<Compile Include="UserLibrary\YearsService.cs" />
|
<Compile Include="UserLibrary\YearsService.cs" />
|
||||||
<Compile Include="UserService.cs" />
|
<Compile Include="UserService.cs" />
|
||||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||||
<Compile Include="VideosService.cs" />
|
<Compile Include="VideosService.cs" />
|
||||||
<Compile Include="WebSocket\LogFileWebSocketListener.cs" />
|
<Compile Include="Session\SessionInfoWebSocketListener.cs" />
|
||||||
<Compile Include="WebSocket\SessionInfoWebSocketListener.cs" />
|
<Compile Include="System\SystemInfoWebSocketListener.cs" />
|
||||||
<Compile Include="WebSocket\SystemInfoWebSocketListener.cs" />
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\MediaBrowser.Common\MediaBrowser.Common.csproj">
|
<ProjectReference Include="..\MediaBrowser.Common\MediaBrowser.Common.csproj">
|
||||||
@ -160,13 +164,12 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<None Include="packages.config" />
|
<None Include="packages.config" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup />
|
|
||||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<PostBuildEvent>
|
<PostBuildEvent>
|
||||||
</PostBuildEvent>
|
</PostBuildEvent>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<Import Project="$(SolutionDir)\.nuget\nuget.targets" Condition=" '$(ConfigurationName)' != 'Release Mono' " />
|
<Import Project="$(SolutionDir)\.nuget\NuGet.targets" />
|
||||||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
||||||
Other similar extension points exist, see Microsoft.Common.targets.
|
Other similar extension points exist, see Microsoft.Common.targets.
|
||||||
<Target Name="BeforeBuild">
|
<Target Name="BeforeBuild">
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
using MediaBrowser.Controller.Collections;
|
using MediaBrowser.Controller.Collections;
|
||||||
using MediaBrowser.Controller.Dto;
|
using MediaBrowser.Controller.Dto;
|
||||||
|
using MediaBrowser.Controller.Net;
|
||||||
|
using MediaBrowser.Model.Collections;
|
||||||
using MediaBrowser.Model.Querying;
|
using MediaBrowser.Model.Querying;
|
||||||
using ServiceStack;
|
using ServiceStack;
|
||||||
using System;
|
using System;
|
||||||
@ -45,6 +47,7 @@ namespace MediaBrowser.Api.Movies
|
|||||||
public Guid Id { get; set; }
|
public Guid Id { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Authenticated]
|
||||||
public class CollectionService : BaseApiService
|
public class CollectionService : BaseApiService
|
||||||
{
|
{
|
||||||
private readonly ICollectionManager _collectionManager;
|
private readonly ICollectionManager _collectionManager;
|
||||||
@ -56,17 +59,16 @@ namespace MediaBrowser.Api.Movies
|
|||||||
_dtoService = dtoService;
|
_dtoService = dtoService;
|
||||||
}
|
}
|
||||||
|
|
||||||
public object Post(CreateCollection request)
|
public async Task<object> Post(CreateCollection request)
|
||||||
{
|
{
|
||||||
var task = _collectionManager.CreateCollection(new CollectionCreationOptions
|
var item = await _collectionManager.CreateCollection(new CollectionCreationOptions
|
||||||
{
|
{
|
||||||
IsLocked = request.IsLocked,
|
IsLocked = request.IsLocked,
|
||||||
Name = request.Name,
|
Name = request.Name,
|
||||||
ParentId = request.ParentId,
|
ParentId = request.ParentId,
|
||||||
ItemIdList = (request.Ids ?? string.Empty).Split(',').Where(i => !string.IsNullOrWhiteSpace(i)).Select(i => new Guid(i)).ToList()
|
ItemIdList = (request.Ids ?? string.Empty).Split(',').Where(i => !string.IsNullOrWhiteSpace(i)).Select(i => new Guid(i)).ToList()
|
||||||
});
|
|
||||||
|
|
||||||
var item = task.Result;
|
}).ConfigureAwait(false);
|
||||||
|
|
||||||
var dto = _dtoService.GetBaseItemDto(item, new List<ItemFields>());
|
var dto = _dtoService.GetBaseItemDto(item, new List<ItemFields>());
|
||||||
|
|
||||||
@ -90,9 +92,4 @@ namespace MediaBrowser.Api.Movies
|
|||||||
Task.WaitAll(task);
|
Task.WaitAll(task);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class CollectionCreationResult
|
|
||||||
{
|
|
||||||
public string Id { get; set; }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@ using MediaBrowser.Controller.Dto;
|
|||||||
using MediaBrowser.Controller.Entities;
|
using MediaBrowser.Controller.Entities;
|
||||||
using MediaBrowser.Controller.Entities.Movies;
|
using MediaBrowser.Controller.Entities.Movies;
|
||||||
using MediaBrowser.Controller.Library;
|
using MediaBrowser.Controller.Library;
|
||||||
|
using MediaBrowser.Controller.Net;
|
||||||
using MediaBrowser.Controller.Persistence;
|
using MediaBrowser.Controller.Persistence;
|
||||||
using MediaBrowser.Model.Dto;
|
using MediaBrowser.Model.Dto;
|
||||||
using MediaBrowser.Model.Entities;
|
using MediaBrowser.Model.Entities;
|
||||||
@ -64,6 +65,7 @@ namespace MediaBrowser.Api.Movies
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Class MoviesService
|
/// Class MoviesService
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[Authenticated]
|
||||||
public class MoviesService : BaseApiService
|
public class MoviesService : BaseApiService
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
using MediaBrowser.Controller.Entities;
|
using MediaBrowser.Controller.Entities;
|
||||||
using MediaBrowser.Controller.Entities.Movies;
|
using MediaBrowser.Controller.Entities.Movies;
|
||||||
using MediaBrowser.Controller.Library;
|
using MediaBrowser.Controller.Library;
|
||||||
|
using MediaBrowser.Controller.Net;
|
||||||
using MediaBrowser.Controller.Persistence;
|
using MediaBrowser.Controller.Persistence;
|
||||||
using ServiceStack;
|
using ServiceStack;
|
||||||
|
|
||||||
@ -18,6 +19,7 @@ namespace MediaBrowser.Api.Movies
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Class TrailersService
|
/// Class TrailersService
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[Authenticated]
|
||||||
public class TrailersService : BaseApiService
|
public class TrailersService : BaseApiService
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -2,10 +2,10 @@
|
|||||||
using MediaBrowser.Controller.Entities;
|
using MediaBrowser.Controller.Entities;
|
||||||
using MediaBrowser.Controller.Entities.Audio;
|
using MediaBrowser.Controller.Entities.Audio;
|
||||||
using MediaBrowser.Controller.Library;
|
using MediaBrowser.Controller.Library;
|
||||||
|
using MediaBrowser.Controller.Net;
|
||||||
using MediaBrowser.Controller.Persistence;
|
using MediaBrowser.Controller.Persistence;
|
||||||
using ServiceStack;
|
using ServiceStack;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
|
||||||
namespace MediaBrowser.Api.Music
|
namespace MediaBrowser.Api.Music
|
||||||
@ -15,6 +15,7 @@ namespace MediaBrowser.Api.Music
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Authenticated]
|
||||||
public class AlbumsService : BaseApiService
|
public class AlbumsService : BaseApiService
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
using MediaBrowser.Controller.Entities;
|
using MediaBrowser.Controller.Entities;
|
||||||
using MediaBrowser.Controller.Entities.Audio;
|
using MediaBrowser.Controller.Entities.Audio;
|
||||||
using MediaBrowser.Controller.Library;
|
using MediaBrowser.Controller.Library;
|
||||||
|
using MediaBrowser.Controller.Net;
|
||||||
using MediaBrowser.Model.Querying;
|
using MediaBrowser.Model.Querying;
|
||||||
using ServiceStack;
|
using ServiceStack;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
@ -33,6 +34,21 @@ namespace MediaBrowser.Api.Music
|
|||||||
public string Name { get; set; }
|
public string Name { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Route("/Artists/InstantMix", "GET", Summary = "Creates an instant playlist based on a given artist")]
|
||||||
|
public class GetInstantMixFromArtistId : BaseGetSimilarItems
|
||||||
|
{
|
||||||
|
[ApiMember(Name = "Id", Description = "The artist Id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")]
|
||||||
|
public string Id { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
[Route("/MusicGenres/InstantMix", "GET", Summary = "Creates an instant playlist based on a music genre")]
|
||||||
|
public class GetInstantMixFromMusicGenreId : BaseGetSimilarItems
|
||||||
|
{
|
||||||
|
[ApiMember(Name = "Id", Description = "The genre Id", IsRequired = true, DataType = "string", ParameterType = "querypath", Verb = "GET")]
|
||||||
|
public string Id { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
[Authenticated]
|
||||||
public class InstantMixService : BaseApiService
|
public class InstantMixService : BaseApiService
|
||||||
{
|
{
|
||||||
private readonly IUserManager _userManager;
|
private readonly IUserManager _userManager;
|
||||||
@ -49,6 +65,28 @@ namespace MediaBrowser.Api.Music
|
|||||||
_libraryManager = libraryManager;
|
_libraryManager = libraryManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public object Get(GetInstantMixFromArtistId request)
|
||||||
|
{
|
||||||
|
var item = (MusicArtist)_libraryManager.GetItemById(request.Id);
|
||||||
|
|
||||||
|
var user = _userManager.GetUserById(request.UserId.Value);
|
||||||
|
|
||||||
|
var items = _musicManager.GetInstantMixFromArtist(item.Name, user);
|
||||||
|
|
||||||
|
return GetResult(items, user, request);
|
||||||
|
}
|
||||||
|
|
||||||
|
public object Get(GetInstantMixFromMusicGenreId request)
|
||||||
|
{
|
||||||
|
var item = (MusicGenre)_libraryManager.GetItemById(request.Id);
|
||||||
|
|
||||||
|
var user = _userManager.GetUserById(request.UserId.Value);
|
||||||
|
|
||||||
|
var items = _musicManager.GetInstantMixFromGenres(new[] { item.Name }, user);
|
||||||
|
|
||||||
|
return GetResult(items, user, request);
|
||||||
|
}
|
||||||
|
|
||||||
public object Get(GetInstantMixFromSong request)
|
public object Get(GetInstantMixFromSong request)
|
||||||
{
|
{
|
||||||
var item = (Audio)_libraryManager.GetItemById(request.Id);
|
var item = (Audio)_libraryManager.GetItemById(request.Id);
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
using MediaBrowser.Controller.Library;
|
using MediaBrowser.Controller.Library;
|
||||||
|
using MediaBrowser.Controller.Net;
|
||||||
using MediaBrowser.Controller.Notifications;
|
using MediaBrowser.Controller.Notifications;
|
||||||
using MediaBrowser.Model.Notifications;
|
using MediaBrowser.Model.Notifications;
|
||||||
using ServiceStack;
|
using ServiceStack;
|
||||||
@ -82,6 +83,7 @@ namespace MediaBrowser.Api
|
|||||||
public string Ids { get; set; }
|
public string Ids { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Authenticated]
|
||||||
public class NotificationsService : BaseApiService
|
public class NotificationsService : BaseApiService
|
||||||
{
|
{
|
||||||
private readonly INotificationsRepository _notificationsRepo;
|
private readonly INotificationsRepository _notificationsRepo;
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
using MediaBrowser.Common.Constants;
|
using MediaBrowser.Common.Constants;
|
||||||
using MediaBrowser.Common.Net;
|
using MediaBrowser.Common.Net;
|
||||||
|
using MediaBrowser.Controller.Net;
|
||||||
using MediaBrowser.Model.Entities;
|
using MediaBrowser.Model.Entities;
|
||||||
using MediaBrowser.Model.Serialization;
|
using MediaBrowser.Model.Serialization;
|
||||||
using ServiceStack;
|
using ServiceStack;
|
||||||
@ -96,6 +97,7 @@ namespace MediaBrowser.Api
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Authenticated]
|
||||||
public class PackageReviewService : BaseApiService
|
public class PackageReviewService : BaseApiService
|
||||||
{
|
{
|
||||||
private readonly IHttpClient _httpClient;
|
private readonly IHttpClient _httpClient;
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
using MediaBrowser.Common;
|
using MediaBrowser.Common;
|
||||||
using MediaBrowser.Common.Extensions;
|
using MediaBrowser.Common.Extensions;
|
||||||
using MediaBrowser.Common.Updates;
|
using MediaBrowser.Common.Updates;
|
||||||
|
using MediaBrowser.Controller.Net;
|
||||||
using MediaBrowser.Model.Updates;
|
using MediaBrowser.Model.Updates;
|
||||||
using ServiceStack;
|
using ServiceStack;
|
||||||
using System;
|
using System;
|
||||||
@ -121,6 +122,7 @@ namespace MediaBrowser.Api
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Class PackageService
|
/// Class PackageService
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[Authenticated]
|
||||||
public class PackageService : BaseApiService
|
public class PackageService : BaseApiService
|
||||||
{
|
{
|
||||||
private readonly IInstallationManager _installationManager;
|
private readonly IInstallationManager _installationManager;
|
||||||
|
@ -13,7 +13,6 @@ using MediaBrowser.Model.Drawing;
|
|||||||
using MediaBrowser.Model.Dto;
|
using MediaBrowser.Model.Dto;
|
||||||
using MediaBrowser.Model.Entities;
|
using MediaBrowser.Model.Entities;
|
||||||
using MediaBrowser.Model.IO;
|
using MediaBrowser.Model.IO;
|
||||||
using MediaBrowser.Model.Library;
|
|
||||||
using MediaBrowser.Model.LiveTv;
|
using MediaBrowser.Model.LiveTv;
|
||||||
using MediaBrowser.Model.MediaInfo;
|
using MediaBrowser.Model.MediaInfo;
|
||||||
using System;
|
using System;
|
||||||
@ -123,7 +122,11 @@ namespace MediaBrowser.Api.Playback
|
|||||||
|
|
||||||
var outputFileExtension = GetOutputFileExtension(state);
|
var outputFileExtension = GetOutputFileExtension(state);
|
||||||
|
|
||||||
return Path.Combine(folder, GetCommandLineArguments("dummy\\dummy", state, false).GetMD5() + (outputFileExtension ?? string.Empty).ToLower());
|
var data = GetCommandLineArguments("dummy\\dummy", state, false);
|
||||||
|
|
||||||
|
data += "-" + (state.Request.DeviceId ?? string.Empty);
|
||||||
|
|
||||||
|
return Path.Combine(folder, data.GetMD5().ToString("N") + (outputFileExtension ?? string.Empty).ToLower());
|
||||||
}
|
}
|
||||||
|
|
||||||
protected readonly CultureInfo UsCulture = new CultureInfo("en-US");
|
protected readonly CultureInfo UsCulture = new CultureInfo("en-US");
|
||||||
@ -138,14 +141,9 @@ namespace MediaBrowser.Api.Playback
|
|||||||
{
|
{
|
||||||
var time = request.StartTimeTicks;
|
var time = request.StartTimeTicks;
|
||||||
|
|
||||||
if (time.HasValue)
|
if (time.HasValue && time.Value > 0)
|
||||||
{
|
{
|
||||||
var seconds = TimeSpan.FromTicks(time.Value).TotalSeconds;
|
return string.Format("-ss {0}", MediaEncoder.GetTimeParameter(time.Value));
|
||||||
|
|
||||||
if (seconds > 0)
|
|
||||||
{
|
|
||||||
return string.Format("-ss {0}", seconds.ToString(UsCulture));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return string.Empty;
|
return string.Empty;
|
||||||
@ -319,7 +317,7 @@ namespace MediaBrowser.Api.Playback
|
|||||||
switch (qualitySetting)
|
switch (qualitySetting)
|
||||||
{
|
{
|
||||||
case EncodingQuality.HighSpeed:
|
case EncodingQuality.HighSpeed:
|
||||||
param = "-preset ultrafast";
|
param = "-preset superfast";
|
||||||
break;
|
break;
|
||||||
case EncodingQuality.HighQuality:
|
case EncodingQuality.HighQuality:
|
||||||
param = "-preset superfast";
|
param = "-preset superfast";
|
||||||
@ -350,16 +348,16 @@ namespace MediaBrowser.Api.Playback
|
|||||||
var profileScore = 0;
|
var profileScore = 0;
|
||||||
|
|
||||||
string crf;
|
string crf;
|
||||||
|
var qmin = "0";
|
||||||
|
var qmax = "50";
|
||||||
|
|
||||||
switch (qualitySetting)
|
switch (qualitySetting)
|
||||||
{
|
{
|
||||||
case EncodingQuality.HighSpeed:
|
case EncodingQuality.HighSpeed:
|
||||||
crf = "12";
|
crf = "10";
|
||||||
profileScore = 2;
|
|
||||||
break;
|
break;
|
||||||
case EncodingQuality.HighQuality:
|
case EncodingQuality.HighQuality:
|
||||||
crf = "8";
|
crf = "6";
|
||||||
profileScore = 1;
|
|
||||||
break;
|
break;
|
||||||
case EncodingQuality.MaxQuality:
|
case EncodingQuality.MaxQuality:
|
||||||
crf = "4";
|
crf = "4";
|
||||||
@ -371,14 +369,17 @@ namespace MediaBrowser.Api.Playback
|
|||||||
if (isVc1)
|
if (isVc1)
|
||||||
{
|
{
|
||||||
profileScore++;
|
profileScore++;
|
||||||
// Max of 2
|
|
||||||
profileScore = Math.Min(profileScore, 2);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Max of 2
|
||||||
|
profileScore = Math.Min(profileScore, 2);
|
||||||
|
|
||||||
// http://www.webmproject.org/docs/encoder-parameters/
|
// http://www.webmproject.org/docs/encoder-parameters/
|
||||||
param = string.Format("-speed 16 -quality good -profile:v {0} -slices 8 -crf {1}",
|
param = string.Format("-speed 16 -quality good -profile:v {0} -slices 8 -crf {1} -qmin {2} -qmax {3}",
|
||||||
profileScore.ToString(UsCulture),
|
profileScore.ToString(UsCulture),
|
||||||
crf);
|
crf,
|
||||||
|
qmin,
|
||||||
|
qmax);
|
||||||
}
|
}
|
||||||
|
|
||||||
else if (string.Equals(videoCodec, "mpeg4", StringComparison.OrdinalIgnoreCase))
|
else if (string.Equals(videoCodec, "mpeg4", StringComparison.OrdinalIgnoreCase))
|
||||||
@ -469,11 +470,11 @@ namespace MediaBrowser.Api.Playback
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="state">The state.</param>
|
/// <param name="state">The state.</param>
|
||||||
/// <param name="outputVideoCodec">The output video codec.</param>
|
/// <param name="outputVideoCodec">The output video codec.</param>
|
||||||
/// <param name="cancellationToken">The cancellation token.</param>
|
/// <param name="allowTimeStampCopy">if set to <c>true</c> [allow time stamp copy].</param>
|
||||||
/// <returns>System.String.</returns>
|
/// <returns>System.String.</returns>
|
||||||
protected string GetOutputSizeParam(StreamState state,
|
protected string GetOutputSizeParam(StreamState state,
|
||||||
string outputVideoCodec,
|
string outputVideoCodec,
|
||||||
CancellationToken cancellationToken)
|
bool allowTimeStampCopy = true)
|
||||||
{
|
{
|
||||||
// http://sonnati.wordpress.com/2012/10/19/ffmpeg-the-swiss-army-knife-of-internet-streaming-part-vi/
|
// http://sonnati.wordpress.com/2012/10/19/ffmpeg-the-swiss-army-knife-of-internet-streaming-part-vi/
|
||||||
|
|
||||||
@ -562,11 +563,14 @@ namespace MediaBrowser.Api.Playback
|
|||||||
|
|
||||||
if (state.SubtitleStream != null && state.SubtitleStream.IsTextSubtitleStream)
|
if (state.SubtitleStream != null && state.SubtitleStream.IsTextSubtitleStream)
|
||||||
{
|
{
|
||||||
var subParam = GetTextSubtitleParam(state, cancellationToken);
|
var subParam = GetTextSubtitleParam(state);
|
||||||
|
|
||||||
filters.Add(subParam);
|
filters.Add(subParam);
|
||||||
|
|
||||||
output += " -copyts";
|
if (allowTimeStampCopy)
|
||||||
|
{
|
||||||
|
output += " -copyts";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (filters.Count > 0)
|
if (filters.Count > 0)
|
||||||
@ -581,12 +585,10 @@ namespace MediaBrowser.Api.Playback
|
|||||||
/// Gets the text subtitle param.
|
/// Gets the text subtitle param.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="state">The state.</param>
|
/// <param name="state">The state.</param>
|
||||||
/// <param name="cancellationToken">The cancellation token.</param>
|
|
||||||
/// <returns>System.String.</returns>
|
/// <returns>System.String.</returns>
|
||||||
protected string GetTextSubtitleParam(StreamState state,
|
protected string GetTextSubtitleParam(StreamState state)
|
||||||
CancellationToken cancellationToken)
|
|
||||||
{
|
{
|
||||||
var seconds = TimeSpan.FromTicks(state.Request.StartTimeTicks ?? 0).TotalSeconds;
|
var seconds = Math.Round(TimeSpan.FromTicks(state.Request.StartTimeTicks ?? 0).TotalSeconds);
|
||||||
|
|
||||||
if (state.SubtitleStream.IsExternal)
|
if (state.SubtitleStream.IsExternal)
|
||||||
{
|
{
|
||||||
@ -604,17 +606,17 @@ namespace MediaBrowser.Api.Playback
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Perhaps also use original_size=1920x800
|
// TODO: Perhaps also use original_size=1920x800 ??
|
||||||
return string.Format("subtitles=filename='{0}'{1},setpts=PTS -{2}/TB",
|
return string.Format("subtitles=filename='{0}'{1},setpts=PTS -{2}/TB",
|
||||||
subtitlePath.Replace('\\', '/').Replace(":/", "\\:/"),
|
subtitlePath.Replace('\\', '/').Replace(":/", "\\:/"),
|
||||||
charsetParam,
|
charsetParam,
|
||||||
Math.Round(seconds).ToString(UsCulture));
|
seconds.ToString(UsCulture));
|
||||||
}
|
}
|
||||||
|
|
||||||
return string.Format("subtitles='{0}:si={1}',setpts=PTS -{2}/TB",
|
return string.Format("subtitles='{0}:si={1}',setpts=PTS -{2}/TB",
|
||||||
state.MediaPath.Replace('\\', '/').Replace(":/", "\\:/"),
|
state.MediaPath.Replace('\\', '/').Replace(":/", "\\:/"),
|
||||||
state.InternalSubtitleStreamOffset.ToString(UsCulture),
|
state.InternalSubtitleStreamOffset.ToString(UsCulture),
|
||||||
Math.Round(seconds).ToString(UsCulture));
|
seconds.ToString(UsCulture));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -623,7 +625,7 @@ namespace MediaBrowser.Api.Playback
|
|||||||
/// <param name="state">The state.</param>
|
/// <param name="state">The state.</param>
|
||||||
/// <param name="outputVideoCodec">The output video codec.</param>
|
/// <param name="outputVideoCodec">The output video codec.</param>
|
||||||
/// <returns>System.String.</returns>
|
/// <returns>System.String.</returns>
|
||||||
protected string GetInternalGraphicalSubtitleParam(StreamState state, string outputVideoCodec)
|
protected string GetGraphicalSubtitleParam(StreamState state, string outputVideoCodec)
|
||||||
{
|
{
|
||||||
var outputSizeParam = string.Empty;
|
var outputSizeParam = string.Empty;
|
||||||
|
|
||||||
@ -632,7 +634,7 @@ namespace MediaBrowser.Api.Playback
|
|||||||
// Add resolution params, if specified
|
// Add resolution params, if specified
|
||||||
if (request.Width.HasValue || request.Height.HasValue || request.MaxHeight.HasValue || request.MaxWidth.HasValue)
|
if (request.Width.HasValue || request.Height.HasValue || request.MaxHeight.HasValue || request.MaxWidth.HasValue)
|
||||||
{
|
{
|
||||||
outputSizeParam = GetOutputSizeParam(state, outputVideoCodec, CancellationToken.None).TrimEnd('"');
|
outputSizeParam = GetOutputSizeParam(state, outputVideoCodec).TrimEnd('"');
|
||||||
outputSizeParam = "," + outputSizeParam.Substring(outputSizeParam.IndexOf("scale", StringComparison.OrdinalIgnoreCase));
|
outputSizeParam = "," + outputSizeParam.Substring(outputSizeParam.IndexOf("scale", StringComparison.OrdinalIgnoreCase));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -772,6 +774,11 @@ namespace MediaBrowser.Api.Playback
|
|||||||
return "copy";
|
return "copy";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected virtual bool SupportsThrottling
|
||||||
|
{
|
||||||
|
get { return false; }
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the input argument.
|
/// Gets the input argument.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -779,6 +786,19 @@ namespace MediaBrowser.Api.Playback
|
|||||||
/// <returns>System.String.</returns>
|
/// <returns>System.String.</returns>
|
||||||
protected string GetInputArgument(StreamState state)
|
protected string GetInputArgument(StreamState state)
|
||||||
{
|
{
|
||||||
|
if (state.InputProtocol == MediaProtocol.File &&
|
||||||
|
state.RunTimeTicks.HasValue &&
|
||||||
|
state.VideoType == VideoType.VideoFile &&
|
||||||
|
!string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
if (state.RunTimeTicks.Value >= TimeSpan.FromMinutes(5).Ticks && state.IsInputVideo)
|
||||||
|
{
|
||||||
|
var url = "http://localhost:" + ServerConfigurationManager.Configuration.HttpServerPortNumber.ToString(UsCulture) + "/mediabrowser/videos/" + state.Request.Id + "/stream?static=true&Throttle=true&mediaSourceId=" + state.Request.MediaSourceId;
|
||||||
|
|
||||||
|
return string.Format("\"{0}\"", url);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var protocol = state.InputProtocol;
|
var protocol = state.InputProtocol;
|
||||||
|
|
||||||
var inputPath = new[] { state.MediaPath };
|
var inputPath = new[] { state.MediaPath };
|
||||||
@ -794,6 +814,81 @@ namespace MediaBrowser.Api.Playback
|
|||||||
return MediaEncoder.GetInputArgument(inputPath, protocol);
|
return MediaEncoder.GetInputArgument(inputPath, protocol);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 (string.IsNullOrEmpty(state.MediaPath))
|
||||||
|
{
|
||||||
|
var checkCodecs = false;
|
||||||
|
|
||||||
|
if (string.Equals(state.ItemType, typeof(LiveTvChannel).Name))
|
||||||
|
{
|
||||||
|
var streamInfo = await LiveTvManager.GetChannelStream(state.Request.Id, cancellationTokenSource.Token).ConfigureAwait(false);
|
||||||
|
|
||||||
|
state.LiveTvStreamId = streamInfo.Id;
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(streamInfo.Path))
|
||||||
|
{
|
||||||
|
state.MediaPath = streamInfo.Path;
|
||||||
|
state.InputProtocol = MediaProtocol.File;
|
||||||
|
|
||||||
|
await Task.Delay(1500, cancellationTokenSource.Token).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
else if (!string.IsNullOrEmpty(streamInfo.Url))
|
||||||
|
{
|
||||||
|
state.MediaPath = streamInfo.Url;
|
||||||
|
state.InputProtocol = MediaProtocol.Http;
|
||||||
|
}
|
||||||
|
|
||||||
|
AttachMediaStreamInfo(state, streamInfo.MediaStreams, state.VideoRequest, state.RequestedUrl);
|
||||||
|
checkCodecs = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
else if (string.Equals(state.ItemType, typeof(LiveTvVideoRecording).Name) ||
|
||||||
|
string.Equals(state.ItemType, typeof(LiveTvAudioRecording).Name))
|
||||||
|
{
|
||||||
|
var streamInfo = await LiveTvManager.GetRecordingStream(state.Request.Id, cancellationTokenSource.Token).ConfigureAwait(false);
|
||||||
|
|
||||||
|
state.LiveTvStreamId = streamInfo.Id;
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(streamInfo.Path))
|
||||||
|
{
|
||||||
|
state.MediaPath = streamInfo.Path;
|
||||||
|
state.InputProtocol = MediaProtocol.File;
|
||||||
|
|
||||||
|
await Task.Delay(1500, cancellationTokenSource.Token).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
else if (!string.IsNullOrEmpty(streamInfo.Url))
|
||||||
|
{
|
||||||
|
state.MediaPath = streamInfo.Url;
|
||||||
|
state.InputProtocol = MediaProtocol.Http;
|
||||||
|
}
|
||||||
|
|
||||||
|
AttachMediaStreamInfo(state, streamInfo.MediaStreams, state.VideoRequest, state.RequestedUrl);
|
||||||
|
checkCodecs = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
var videoRequest = state.VideoRequest;
|
||||||
|
|
||||||
|
if (videoRequest != null && checkCodecs)
|
||||||
|
{
|
||||||
|
if (state.VideoStream != null && CanStreamCopyVideo(videoRequest, state.VideoStream))
|
||||||
|
{
|
||||||
|
state.OutputVideoCodec = "copy";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state.AudioStream != null && CanStreamCopyAudio(videoRequest, state.AudioStream, state.SupportedAudioCodecs))
|
||||||
|
{
|
||||||
|
state.OutputAudioCodec = "copy";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Starts the FFMPEG.
|
/// Starts the FFMPEG.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -811,10 +906,7 @@ namespace MediaBrowser.Api.Playback
|
|||||||
|
|
||||||
Directory.CreateDirectory(Path.GetDirectoryName(outputPath));
|
Directory.CreateDirectory(Path.GetDirectoryName(outputPath));
|
||||||
|
|
||||||
if (state.VideoType == VideoType.Iso && state.IsoType.HasValue && IsoManager.CanMount(state.MediaPath))
|
await AcquireResources(state, cancellationTokenSource).ConfigureAwait(false);
|
||||||
{
|
|
||||||
state.IsoMount = await IsoManager.Mount(state.MediaPath, cancellationTokenSource.Token).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
var commandLineArgs = GetCommandLineArguments(outputPath, state, true);
|
var commandLineArgs = GetCommandLineArguments(outputPath, state, true);
|
||||||
|
|
||||||
@ -849,7 +941,6 @@ namespace MediaBrowser.Api.Playback
|
|||||||
ApiEntryPoint.Instance.OnTranscodeBeginning(outputPath,
|
ApiEntryPoint.Instance.OnTranscodeBeginning(outputPath,
|
||||||
TranscodingJobType,
|
TranscodingJobType,
|
||||||
process,
|
process,
|
||||||
state.Request.StartTimeTicks,
|
|
||||||
state.Request.DeviceId,
|
state.Request.DeviceId,
|
||||||
state,
|
state,
|
||||||
cancellationTokenSource);
|
cancellationTokenSource);
|
||||||
@ -866,7 +957,7 @@ namespace MediaBrowser.Api.Playback
|
|||||||
var commandLineLogMessageBytes = Encoding.UTF8.GetBytes(commandLineLogMessage + Environment.NewLine + Environment.NewLine);
|
var commandLineLogMessageBytes = Encoding.UTF8.GetBytes(commandLineLogMessage + Environment.NewLine + Environment.NewLine);
|
||||||
await state.LogFileStream.WriteAsync(commandLineLogMessageBytes, 0, commandLineLogMessageBytes.Length, cancellationTokenSource.Token).ConfigureAwait(false);
|
await state.LogFileStream.WriteAsync(commandLineLogMessageBytes, 0, commandLineLogMessageBytes.Length, cancellationTokenSource.Token).ConfigureAwait(false);
|
||||||
|
|
||||||
process.Exited += (sender, args) => OnFfMpegProcessExited(process, state);
|
process.Exited += (sender, args) => OnFfMpegProcessExited(process, state, outputPath);
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@ -892,18 +983,6 @@ namespace MediaBrowser.Api.Playback
|
|||||||
{
|
{
|
||||||
await Task.Delay(100, cancellationTokenSource.Token).ConfigureAwait(false);
|
await Task.Delay(100, cancellationTokenSource.Token).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Allow a small amount of time to buffer a little
|
|
||||||
if (state.IsInputVideo)
|
|
||||||
{
|
|
||||||
await Task.Delay(500, cancellationTokenSource.Token).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
// This is arbitrary, but add a little buffer time when internet streaming
|
|
||||||
if (state.InputProtocol != MediaProtocol.File)
|
|
||||||
{
|
|
||||||
await Task.Delay(3000, cancellationTokenSource.Token).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async void StartStreamingLog(StreamState state, Stream source, Stream target)
|
private async void StartStreamingLog(StreamState state, Stream source, Stream target)
|
||||||
@ -1061,7 +1140,8 @@ namespace MediaBrowser.Api.Playback
|
|||||||
// Make sure we don't request a bitrate higher than the source
|
// Make sure we don't request a bitrate higher than the source
|
||||||
var currentBitrate = audioStream == null ? request.AudioBitRate.Value : audioStream.BitRate ?? request.AudioBitRate.Value;
|
var currentBitrate = audioStream == null ? request.AudioBitRate.Value : audioStream.BitRate ?? request.AudioBitRate.Value;
|
||||||
|
|
||||||
return Math.Min(currentBitrate, request.AudioBitRate.Value);
|
return request.AudioBitRate.Value;
|
||||||
|
//return Math.Min(currentBitrate, request.AudioBitRate.Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
@ -1091,8 +1171,16 @@ namespace MediaBrowser.Api.Playback
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="process">The process.</param>
|
/// <param name="process">The process.</param>
|
||||||
/// <param name="state">The state.</param>
|
/// <param name="state">The state.</param>
|
||||||
private void OnFfMpegProcessExited(Process process, StreamState state)
|
/// <param name="outputPath">The output path.</param>
|
||||||
|
private void OnFfMpegProcessExited(Process process, StreamState state, string outputPath)
|
||||||
{
|
{
|
||||||
|
var job = ApiEntryPoint.Instance.GetTranscodingJob(outputPath, TranscodingJobType);
|
||||||
|
|
||||||
|
if (job != null)
|
||||||
|
{
|
||||||
|
job.HasExited = true;
|
||||||
|
}
|
||||||
|
|
||||||
Logger.Debug("Disposing stream resources");
|
Logger.Debug("Disposing stream resources");
|
||||||
state.Dispose();
|
state.Dispose();
|
||||||
|
|
||||||
@ -1126,13 +1214,13 @@ namespace MediaBrowser.Api.Playback
|
|||||||
return state.VideoRequest.Framerate.Value;
|
return state.VideoRequest.Framerate.Value;
|
||||||
}
|
}
|
||||||
|
|
||||||
var maxrate = state.VideoRequest.MaxFramerate ?? 23.97602;
|
var maxrate = state.VideoRequest.MaxFramerate;
|
||||||
|
|
||||||
if (state.VideoStream != null)
|
if (maxrate.HasValue && state.VideoStream != null)
|
||||||
{
|
{
|
||||||
var contentRate = state.VideoStream.AverageFrameRate ?? state.VideoStream.RealFrameRate;
|
var contentRate = state.VideoStream.AverageFrameRate ?? state.VideoStream.RealFrameRate;
|
||||||
|
|
||||||
if (contentRate.HasValue && contentRate.Value > maxrate)
|
if (contentRate.HasValue && contentRate.Value > maxrate.Value)
|
||||||
{
|
{
|
||||||
return maxrate;
|
return maxrate;
|
||||||
}
|
}
|
||||||
@ -1330,8 +1418,6 @@ namespace MediaBrowser.Api.Playback
|
|||||||
ParseParams(request);
|
ParseParams(request);
|
||||||
}
|
}
|
||||||
|
|
||||||
var user = AuthorizationRequestFilterAttribute.GetCurrentUser(Request, UserManager);
|
|
||||||
|
|
||||||
var url = Request.PathInfo;
|
var url = Request.PathInfo;
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(request.AudioCodec))
|
if (string.IsNullOrEmpty(request.AudioCodec))
|
||||||
@ -1353,13 +1439,10 @@ namespace MediaBrowser.Api.Playback
|
|||||||
|
|
||||||
var item = LibraryManager.GetItemById(request.Id);
|
var item = LibraryManager.GetItemById(request.Id);
|
||||||
|
|
||||||
if (user != null && item.GetPlayAccess(user) != PlayAccess.Full)
|
|
||||||
{
|
|
||||||
throw new ArgumentException(string.Format("{0} is not allowed to play media.", user.Name));
|
|
||||||
}
|
|
||||||
|
|
||||||
List<MediaStream> mediaStreams = null;
|
List<MediaStream> mediaStreams = null;
|
||||||
|
|
||||||
|
state.ItemType = item.GetType().Name;
|
||||||
|
|
||||||
if (item is ILiveTvRecording)
|
if (item is ILiveTvRecording)
|
||||||
{
|
{
|
||||||
var recording = await LiveTvManager.GetInternalRecording(request.Id, cancellationToken).ConfigureAwait(false);
|
var recording = await LiveTvManager.GetInternalRecording(request.Id, cancellationToken).ConfigureAwait(false);
|
||||||
@ -1376,16 +1459,8 @@ namespace MediaBrowser.Api.Playback
|
|||||||
|
|
||||||
mediaStreams = source.MediaStreams;
|
mediaStreams = source.MediaStreams;
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(path) && string.IsNullOrWhiteSpace(mediaUrl))
|
// Just to prevent this from being null and causing other methods to fail
|
||||||
{
|
state.MediaPath = string.Empty;
|
||||||
var streamInfo = await LiveTvManager.GetRecordingStream(request.Id, cancellationToken).ConfigureAwait(false);
|
|
||||||
|
|
||||||
state.LiveTvStreamId = streamInfo.Id;
|
|
||||||
mediaStreams = streamInfo.MediaStreams;
|
|
||||||
|
|
||||||
path = streamInfo.Path;
|
|
||||||
mediaUrl = streamInfo.Url;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(path))
|
if (!string.IsNullOrEmpty(path))
|
||||||
{
|
{
|
||||||
@ -1397,17 +1472,20 @@ namespace MediaBrowser.Api.Playback
|
|||||||
state.MediaPath = mediaUrl;
|
state.MediaPath = mediaUrl;
|
||||||
state.InputProtocol = MediaProtocol.Http;
|
state.InputProtocol = MediaProtocol.Http;
|
||||||
}
|
}
|
||||||
|
else
|
||||||
state.RunTimeTicks = recording.RunTimeTicks;
|
{
|
||||||
|
// No media info, so this is probably needed
|
||||||
|
state.DeInterlace = true;
|
||||||
|
}
|
||||||
|
|
||||||
if (recording.RecordingInfo.Status == RecordingStatus.InProgress)
|
if (recording.RecordingInfo.Status == RecordingStatus.InProgress)
|
||||||
{
|
{
|
||||||
await Task.Delay(1000, cancellationToken).ConfigureAwait(false);
|
state.ReadInputAtNativeFramerate = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
state.ReadInputAtNativeFramerate = recording.RecordingInfo.Status == RecordingStatus.InProgress;
|
state.RunTimeTicks = recording.RunTimeTicks;
|
||||||
|
|
||||||
state.OutputAudioSync = "1000";
|
state.OutputAudioSync = "1000";
|
||||||
state.DeInterlace = true;
|
|
||||||
state.InputVideoSync = "-1";
|
state.InputVideoSync = "-1";
|
||||||
state.InputAudioSync = "1";
|
state.InputAudioSync = "1";
|
||||||
state.InputContainer = recording.Container;
|
state.InputContainer = recording.Container;
|
||||||
@ -1418,40 +1496,27 @@ namespace MediaBrowser.Api.Playback
|
|||||||
|
|
||||||
state.VideoType = VideoType.VideoFile;
|
state.VideoType = VideoType.VideoFile;
|
||||||
state.IsInputVideo = string.Equals(channel.MediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase);
|
state.IsInputVideo = string.Equals(channel.MediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase);
|
||||||
|
mediaStreams = new List<MediaStream>();
|
||||||
var streamInfo = await LiveTvManager.GetChannelStream(request.Id, cancellationToken).ConfigureAwait(false);
|
|
||||||
|
|
||||||
state.LiveTvStreamId = streamInfo.Id;
|
|
||||||
mediaStreams = streamInfo.MediaStreams;
|
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(streamInfo.Path))
|
|
||||||
{
|
|
||||||
state.MediaPath = streamInfo.Path;
|
|
||||||
state.InputProtocol = MediaProtocol.File;
|
|
||||||
|
|
||||||
await Task.Delay(1000, cancellationToken).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
else if (!string.IsNullOrEmpty(streamInfo.Url))
|
|
||||||
{
|
|
||||||
state.MediaPath = streamInfo.Url;
|
|
||||||
state.InputProtocol = MediaProtocol.Http;
|
|
||||||
}
|
|
||||||
|
|
||||||
state.ReadInputAtNativeFramerate = true;
|
state.ReadInputAtNativeFramerate = true;
|
||||||
state.OutputAudioSync = "1000";
|
state.OutputAudioSync = "1000";
|
||||||
state.DeInterlace = true;
|
state.DeInterlace = true;
|
||||||
state.InputVideoSync = "-1";
|
state.InputVideoSync = "-1";
|
||||||
state.InputAudioSync = "1";
|
state.InputAudioSync = "1";
|
||||||
|
|
||||||
|
// Just to prevent this from being null and causing other methods to fail
|
||||||
|
state.MediaPath = string.Empty;
|
||||||
}
|
}
|
||||||
else if (item is IChannelMediaItem)
|
else if (item is IChannelMediaItem)
|
||||||
{
|
{
|
||||||
var source = await GetChannelMediaInfo(request.Id, request.MediaSourceId, cancellationToken).ConfigureAwait(false);
|
var mediaSource = await GetChannelMediaInfo(request.Id, request.MediaSourceId, cancellationToken).ConfigureAwait(false);
|
||||||
state.IsInputVideo = string.Equals(item.MediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase);
|
state.IsInputVideo = string.Equals(item.MediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase);
|
||||||
state.InputProtocol = source.Protocol;
|
state.InputProtocol = mediaSource.Protocol;
|
||||||
state.MediaPath = source.Path;
|
state.MediaPath = mediaSource.Path;
|
||||||
state.RunTimeTicks = item.RunTimeTicks;
|
state.RunTimeTicks = item.RunTimeTicks;
|
||||||
state.RemoteHttpHeaders = source.RequiredHttpHeaders;
|
state.RemoteHttpHeaders = mediaSource.RequiredHttpHeaders;
|
||||||
mediaStreams = source.MediaStreams;
|
state.InputBitrate = mediaSource.Bitrate;
|
||||||
|
mediaStreams = mediaSource.MediaStreams;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -1465,6 +1530,7 @@ namespace MediaBrowser.Api.Playback
|
|||||||
state.MediaPath = mediaSource.Path;
|
state.MediaPath = mediaSource.Path;
|
||||||
state.InputProtocol = mediaSource.Protocol;
|
state.InputProtocol = mediaSource.Protocol;
|
||||||
state.InputContainer = mediaSource.Container;
|
state.InputContainer = mediaSource.Container;
|
||||||
|
state.InputBitrate = mediaSource.Bitrate;
|
||||||
|
|
||||||
if (item is Video)
|
if (item is Video)
|
||||||
{
|
{
|
||||||
@ -1488,16 +1554,23 @@ namespace MediaBrowser.Api.Playback
|
|||||||
state.RunTimeTicks = mediaSource.RunTimeTicks;
|
state.RunTimeTicks = mediaSource.RunTimeTicks;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (string.Equals(state.InputContainer, "wtv", StringComparison.OrdinalIgnoreCase))
|
// If it's a wtv and we don't have media info, we will probably need to deinterlace
|
||||||
|
if (string.Equals(state.InputContainer, "wtv", StringComparison.OrdinalIgnoreCase) &&
|
||||||
|
mediaStreams.Count == 0)
|
||||||
{
|
{
|
||||||
state.DeInterlace = true;
|
state.DeInterlace = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (state.InputProtocol == MediaProtocol.Rtmp)
|
||||||
|
{
|
||||||
|
state.ReadInputAtNativeFramerate = true;
|
||||||
|
}
|
||||||
|
|
||||||
var videoRequest = request as VideoStreamRequest;
|
var videoRequest = request as VideoStreamRequest;
|
||||||
|
|
||||||
AttachMediaStreamInfo(state, mediaStreams, videoRequest, url);
|
AttachMediaStreamInfo(state, mediaStreams, videoRequest, url);
|
||||||
|
|
||||||
state.SegmentLength = state.ReadInputAtNativeFramerate ? 5 : 10;
|
state.SegmentLength = state.ReadInputAtNativeFramerate ? 5 : 7;
|
||||||
state.HlsListSize = state.ReadInputAtNativeFramerate ? 100 : 1440;
|
state.HlsListSize = state.ReadInputAtNativeFramerate ? 100 : 1440;
|
||||||
|
|
||||||
var container = Path.GetExtension(state.RequestedUrl);
|
var container = Path.GetExtension(state.RequestedUrl);
|
||||||
@ -1574,6 +1647,8 @@ namespace MediaBrowser.Api.Playback
|
|||||||
{
|
{
|
||||||
state.AudioStream = GetMediaStream(mediaStreams, null, MediaStreamType.Audio, true);
|
state.AudioStream = GetMediaStream(mediaStreams, null, MediaStreamType.Audio, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
state.AllMediaStreams = mediaStreams;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<MediaSourceInfo> GetChannelMediaInfo(string id,
|
private async Task<MediaSourceInfo> GetChannelMediaInfo(string id,
|
||||||
@ -1609,7 +1684,10 @@ namespace MediaBrowser.Api.Playback
|
|||||||
// Can't stream copy if we're burning in subtitles
|
// Can't stream copy if we're burning in subtitles
|
||||||
if (request.SubtitleStreamIndex.HasValue)
|
if (request.SubtitleStreamIndex.HasValue)
|
||||||
{
|
{
|
||||||
return false;
|
if (request.SubtitleMethod == SubtitleDeliveryMethod.Encode)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Source and target codecs must match
|
// Source and target codecs must match
|
||||||
@ -1886,7 +1964,8 @@ namespace MediaBrowser.Api.Playback
|
|||||||
state.TargetPacketLength,
|
state.TargetPacketLength,
|
||||||
state.TranscodeSeekInfo,
|
state.TranscodeSeekInfo,
|
||||||
state.IsTargetAnamorphic
|
state.IsTargetAnamorphic
|
||||||
);
|
|
||||||
|
).FirstOrDefault() ?? string.Empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var item in responseHeaders)
|
foreach (var item in responseHeaders)
|
||||||
@ -1911,12 +1990,6 @@ namespace MediaBrowser.Api.Playback
|
|||||||
/// <param name="videoRequest">The video request.</param>
|
/// <param name="videoRequest">The video request.</param>
|
||||||
private void EnforceResolutionLimit(StreamState state, VideoStreamRequest videoRequest)
|
private void EnforceResolutionLimit(StreamState state, VideoStreamRequest videoRequest)
|
||||||
{
|
{
|
||||||
// If enabled, allow whatever the client asks for
|
|
||||||
if (ServerConfigurationManager.Configuration.AllowVideoUpscaling)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Switch the incoming params to be ceilings rather than fixed values
|
// Switch the incoming params to be ceilings rather than fixed values
|
||||||
videoRequest.MaxWidth = videoRequest.MaxWidth ?? videoRequest.Width;
|
videoRequest.MaxWidth = videoRequest.MaxWidth ?? videoRequest.Width;
|
||||||
videoRequest.MaxHeight = videoRequest.MaxHeight ?? videoRequest.Height;
|
videoRequest.MaxHeight = videoRequest.MaxHeight ?? videoRequest.Height;
|
||||||
@ -1925,7 +1998,7 @@ namespace MediaBrowser.Api.Playback
|
|||||||
videoRequest.Height = null;
|
videoRequest.Height = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected string GetInputModifier(StreamState state)
|
protected string GetInputModifier(StreamState state, bool genPts = true)
|
||||||
{
|
{
|
||||||
var inputModifier = string.Empty;
|
var inputModifier = string.Empty;
|
||||||
|
|
||||||
@ -1945,9 +2018,9 @@ namespace MediaBrowser.Api.Playback
|
|||||||
inputModifier += " " + GetFastSeekCommandLineParameter(state.Request);
|
inputModifier += " " + GetFastSeekCommandLineParameter(state.Request);
|
||||||
inputModifier = inputModifier.Trim();
|
inputModifier = inputModifier.Trim();
|
||||||
|
|
||||||
if (state.VideoRequest != null)
|
if (state.VideoRequest != null && genPts)
|
||||||
{
|
{
|
||||||
inputModifier += " -fflags genpts";
|
inputModifier += " -fflags +genpts";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(state.InputAudioSync))
|
if (!string.IsNullOrEmpty(state.InputAudioSync))
|
||||||
|
186
MediaBrowser.Api/Playback/BifService.cs
Normal file
186
MediaBrowser.Api/Playback/BifService.cs
Normal file
@ -0,0 +1,186 @@
|
|||||||
|
using MediaBrowser.Common.IO;
|
||||||
|
using MediaBrowser.Controller;
|
||||||
|
using MediaBrowser.Controller.Entities;
|
||||||
|
using MediaBrowser.Controller.Library;
|
||||||
|
using MediaBrowser.Controller.MediaEncoding;
|
||||||
|
using ServiceStack;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Concurrent;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace MediaBrowser.Api.Playback
|
||||||
|
{
|
||||||
|
[Route("/Videos/{Id}/index.bif", "GET")]
|
||||||
|
public class GetBifFile
|
||||||
|
{
|
||||||
|
[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 = "MaxWidth", Description = "Optional. The maximum horizontal resolution of the encoded video.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
|
||||||
|
public int? MaxWidth { get; set; }
|
||||||
|
|
||||||
|
[ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
|
||||||
|
public string Id { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class BifService : BaseApiService
|
||||||
|
{
|
||||||
|
private readonly IServerApplicationPaths _appPaths;
|
||||||
|
private readonly ILibraryManager _libraryManager;
|
||||||
|
private readonly IMediaEncoder _mediaEncoder;
|
||||||
|
private readonly IFileSystem _fileSystem;
|
||||||
|
|
||||||
|
public BifService(IServerApplicationPaths appPaths, ILibraryManager libraryManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem)
|
||||||
|
{
|
||||||
|
_appPaths = appPaths;
|
||||||
|
_libraryManager = libraryManager;
|
||||||
|
_mediaEncoder = mediaEncoder;
|
||||||
|
_fileSystem = fileSystem;
|
||||||
|
}
|
||||||
|
|
||||||
|
public object Get(GetBifFile request)
|
||||||
|
{
|
||||||
|
return ToStaticFileResult(GetBifFile(request).Result);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<string> GetBifFile(GetBifFile request)
|
||||||
|
{
|
||||||
|
var widthVal = request.MaxWidth.HasValue ? request.MaxWidth.Value.ToString(CultureInfo.InvariantCulture) : string.Empty;
|
||||||
|
|
||||||
|
var item = _libraryManager.GetItemById(request.Id);
|
||||||
|
var mediaSources = ((IHasMediaSources)item).GetMediaSources(false).ToList();
|
||||||
|
var mediaSource = mediaSources.FirstOrDefault(i => string.Equals(i.Id, request.MediaSourceId)) ?? mediaSources.First();
|
||||||
|
|
||||||
|
var path = Path.Combine(_appPaths.ImageCachePath, "bif", request.Id, request.MediaSourceId, widthVal, "index.bif");
|
||||||
|
|
||||||
|
if (File.Exists(path))
|
||||||
|
{
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
|
var protocol = mediaSource.Protocol;
|
||||||
|
|
||||||
|
var inputPath = MediaEncoderHelpers.GetInputArgument(mediaSource.Path, protocol, null, mediaSource.PlayableStreamFileNames);
|
||||||
|
|
||||||
|
var semaphore = GetLock(path);
|
||||||
|
|
||||||
|
await semaphore.WaitAsync().ConfigureAwait(false);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (File.Exists(path))
|
||||||
|
{
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
|
await _mediaEncoder.ExtractVideoImagesOnInterval(inputPath, protocol, mediaSource.Video3DFormat,
|
||||||
|
TimeSpan.FromSeconds(10), Path.GetDirectoryName(path), "img_", request.MaxWidth, CancellationToken.None)
|
||||||
|
.ConfigureAwait(false);
|
||||||
|
|
||||||
|
var images = new DirectoryInfo(Path.GetDirectoryName(path))
|
||||||
|
.EnumerateFiles()
|
||||||
|
.Where(img => string.Equals(img.Extension, ".jpg", StringComparison.Ordinal))
|
||||||
|
.OrderBy(i => i.FullName)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
using (var fs = _fileSystem.GetFileStream(path, FileMode.Create, FileAccess.Write, FileShare.Read, true))
|
||||||
|
{
|
||||||
|
var magicNumber = new byte[] { 0x89, 0x42, 0x49, 0x46, 0x0d, 0x0a, 0x1a, 0x0a };
|
||||||
|
await fs.WriteAsync(magicNumber, 0, magicNumber.Length);
|
||||||
|
|
||||||
|
// version
|
||||||
|
var bytes = GetBytes(0);
|
||||||
|
await fs.WriteAsync(bytes, 0, bytes.Length);
|
||||||
|
|
||||||
|
// image count
|
||||||
|
bytes = GetBytes(images.Count);
|
||||||
|
await fs.WriteAsync(bytes, 0, bytes.Length);
|
||||||
|
|
||||||
|
// interval in ms
|
||||||
|
bytes = GetBytes(10000);
|
||||||
|
await fs.WriteAsync(bytes, 0, bytes.Length);
|
||||||
|
|
||||||
|
// reserved
|
||||||
|
for (var i = 20; i <= 63; i++)
|
||||||
|
{
|
||||||
|
bytes = new byte[] { 0x00 };
|
||||||
|
await fs.WriteAsync(bytes, 0, bytes.Length);
|
||||||
|
}
|
||||||
|
|
||||||
|
// write the bif index
|
||||||
|
var index = 0;
|
||||||
|
long imageOffset = 64 + (8 * images.Count) + 8;
|
||||||
|
|
||||||
|
foreach (var img in images)
|
||||||
|
{
|
||||||
|
bytes = GetBytes(index);
|
||||||
|
await fs.WriteAsync(bytes, 0, bytes.Length);
|
||||||
|
|
||||||
|
bytes = GetBytes(imageOffset);
|
||||||
|
await fs.WriteAsync(bytes, 0, bytes.Length);
|
||||||
|
|
||||||
|
imageOffset += img.Length;
|
||||||
|
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
|
||||||
|
bytes = new byte[] { 0xff, 0xff, 0xff, 0xff };
|
||||||
|
await fs.WriteAsync(bytes, 0, bytes.Length);
|
||||||
|
|
||||||
|
bytes = GetBytes(imageOffset);
|
||||||
|
await fs.WriteAsync(bytes, 0, bytes.Length);
|
||||||
|
|
||||||
|
// write the images
|
||||||
|
foreach (var img in images)
|
||||||
|
{
|
||||||
|
using (var imgStream = _fileSystem.GetFileStream(img.FullName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, true))
|
||||||
|
{
|
||||||
|
await imgStream.CopyToAsync(fs).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
semaphore.Release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private byte[] GetBytes(int value)
|
||||||
|
{
|
||||||
|
byte[] bytes = BitConverter.GetBytes(value);
|
||||||
|
if (BitConverter.IsLittleEndian)
|
||||||
|
Array.Reverse(bytes);
|
||||||
|
return bytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
private byte[] GetBytes(long value)
|
||||||
|
{
|
||||||
|
var intVal = Convert.ToInt32(value);
|
||||||
|
return GetBytes(intVal);
|
||||||
|
|
||||||
|
//byte[] bytes = BitConverter.GetBytes(value);
|
||||||
|
//if (BitConverter.IsLittleEndian)
|
||||||
|
// Array.Reverse(bytes);
|
||||||
|
//return bytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static readonly ConcurrentDictionary<string, SemaphoreSlim> SemaphoreLocks = new ConcurrentDictionary<string, SemaphoreSlim>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the lock.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="filename">The filename.</param>
|
||||||
|
/// <returns>System.Object.</returns>
|
||||||
|
private static SemaphoreSlim GetLock(string filename)
|
||||||
|
{
|
||||||
|
return SemaphoreLocks.GetOrAdd(filename, key => new SemaphoreSlim(1, 1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,32 +0,0 @@
|
|||||||
using System.IO;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace MediaBrowser.Api.Playback
|
|
||||||
{
|
|
||||||
public class EndlessStreamCopy
|
|
||||||
{
|
|
||||||
public async Task CopyStream(Stream source, Stream target, CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
long position = 0;
|
|
||||||
|
|
||||||
while (!cancellationToken.IsCancellationRequested)
|
|
||||||
{
|
|
||||||
await source.CopyToAsync(target, 81920, cancellationToken).ConfigureAwait(false);
|
|
||||||
|
|
||||||
var fsPosition = source.Position;
|
|
||||||
|
|
||||||
var bytesRead = fsPosition - position;
|
|
||||||
|
|
||||||
//Logger.Debug("Streamed {0} bytes from file {1}", bytesRead, path);
|
|
||||||
|
|
||||||
if (bytesRead == 0)
|
|
||||||
{
|
|
||||||
await Task.Delay(100, cancellationToken).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
position = fsPosition;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,14 +1,11 @@
|
|||||||
using MediaBrowser.Common.Extensions;
|
using MediaBrowser.Common.IO;
|
||||||
using MediaBrowser.Common.IO;
|
|
||||||
using MediaBrowser.Common.Net;
|
using MediaBrowser.Common.Net;
|
||||||
using MediaBrowser.Controller.Channels;
|
using MediaBrowser.Controller.Channels;
|
||||||
using MediaBrowser.Controller.Configuration;
|
using MediaBrowser.Controller.Configuration;
|
||||||
using MediaBrowser.Controller.Dlna;
|
using MediaBrowser.Controller.Dlna;
|
||||||
using MediaBrowser.Controller.Dto;
|
|
||||||
using MediaBrowser.Controller.Library;
|
using MediaBrowser.Controller.Library;
|
||||||
using MediaBrowser.Controller.LiveTv;
|
using MediaBrowser.Controller.LiveTv;
|
||||||
using MediaBrowser.Controller.MediaEncoding;
|
using MediaBrowser.Controller.MediaEncoding;
|
||||||
using MediaBrowser.Controller.Persistence;
|
|
||||||
using MediaBrowser.Model.Configuration;
|
using MediaBrowser.Model.Configuration;
|
||||||
using MediaBrowser.Model.IO;
|
using MediaBrowser.Model.IO;
|
||||||
using System;
|
using System;
|
||||||
@ -25,7 +22,8 @@ namespace MediaBrowser.Api.Playback.Hls
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public abstract class BaseHlsService : BaseStreamingService
|
public abstract class BaseHlsService : BaseStreamingService
|
||||||
{
|
{
|
||||||
protected BaseHlsService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, ILiveTvManager liveTvManager, IDlnaManager dlnaManager, IChannelManager channelManager, ISubtitleEncoder subtitleEncoder) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, fileSystem, liveTvManager, dlnaManager, channelManager, subtitleEncoder)
|
protected BaseHlsService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, ILiveTvManager liveTvManager, IDlnaManager dlnaManager, IChannelManager channelManager, ISubtitleEncoder subtitleEncoder)
|
||||||
|
: base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, fileSystem, liveTvManager, dlnaManager, channelManager, subtitleEncoder)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -62,29 +60,33 @@ namespace MediaBrowser.Api.Playback.Hls
|
|||||||
/// Processes the request.
|
/// Processes the request.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="request">The request.</param>
|
/// <param name="request">The request.</param>
|
||||||
|
/// <param name="isLive">if set to <c>true</c> [is live].</param>
|
||||||
/// <returns>System.Object.</returns>
|
/// <returns>System.Object.</returns>
|
||||||
protected object ProcessRequest(StreamRequest request)
|
protected object ProcessRequest(StreamRequest request, bool isLive)
|
||||||
{
|
{
|
||||||
return ProcessRequestAsync(request).Result;
|
return ProcessRequestAsync(request, isLive).Result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static readonly SemaphoreSlim FfmpegStartLock = new SemaphoreSlim(1, 1);
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Processes the request async.
|
/// Processes the request async.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="request">The request.</param>
|
/// <param name="request">The request.</param>
|
||||||
|
/// <param name="isLive">if set to <c>true</c> [is live].</param>
|
||||||
/// <returns>Task{System.Object}.</returns>
|
/// <returns>Task{System.Object}.</returns>
|
||||||
/// <exception cref="ArgumentException">
|
/// <exception cref="ArgumentException">A video bitrate is required
|
||||||
/// A video bitrate is required
|
|
||||||
/// or
|
/// or
|
||||||
/// An audio bitrate is required
|
/// An audio bitrate is required</exception>
|
||||||
/// </exception>
|
private async Task<object> ProcessRequestAsync(StreamRequest request, bool isLive)
|
||||||
private async Task<object> ProcessRequestAsync(StreamRequest request)
|
|
||||||
{
|
{
|
||||||
var cancellationTokenSource = new CancellationTokenSource();
|
var cancellationTokenSource = new CancellationTokenSource();
|
||||||
|
|
||||||
var state = await GetState(request, cancellationTokenSource.Token).ConfigureAwait(false);
|
var state = await GetState(request, cancellationTokenSource.Token).ConfigureAwait(false);
|
||||||
|
|
||||||
|
if (isLive)
|
||||||
|
{
|
||||||
|
state.Request.StartTimeTicks = null;
|
||||||
|
}
|
||||||
|
|
||||||
var playlist = state.OutputFilePath;
|
var playlist = state.OutputFilePath;
|
||||||
|
|
||||||
if (File.Exists(playlist))
|
if (File.Exists(playlist))
|
||||||
@ -93,7 +95,7 @@ namespace MediaBrowser.Api.Playback.Hls
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
await FfmpegStartLock.WaitAsync(cancellationTokenSource.Token).ConfigureAwait(false);
|
await ApiEntryPoint.Instance.TranscodingStartLock.WaitAsync(cancellationTokenSource.Token).ConfigureAwait(false);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (File.Exists(playlist))
|
if (File.Exists(playlist))
|
||||||
@ -113,18 +115,34 @@ namespace MediaBrowser.Api.Playback.Hls
|
|||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
|
|
||||||
await WaitForMinimumSegmentCount(playlist, GetSegmentWait(), cancellationTokenSource.Token).ConfigureAwait(false);
|
var waitCount = isLive ? 1 : GetSegmentWait();
|
||||||
|
await WaitForMinimumSegmentCount(playlist, waitCount, cancellationTokenSource.Token).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
FfmpegStartLock.Release();
|
ApiEntryPoint.Instance.TranscodingStartLock.Release();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int audioBitrate;
|
if (isLive)
|
||||||
int videoBitrate;
|
{
|
||||||
GetPlaylistBitrates(state, out audioBitrate, out videoBitrate);
|
//var file = request.PlaylistId + Path.GetExtension(Request.PathInfo);
|
||||||
|
|
||||||
|
//file = Path.Combine(ServerConfigurationManager.ApplicationPaths.TranscodingTempPath, file);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return ResultFactory.GetStaticFileResult(Request, playlist, FileShare.ReadWrite);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
ApiEntryPoint.Instance.OnTranscodeEndRequest(playlist, TranscodingJobType.Hls);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var audioBitrate = state.OutputAudioBitrate ?? 0;
|
||||||
|
var videoBitrate = state.OutputVideoBitrate ?? 0;
|
||||||
|
|
||||||
var appendBaselineStream = false;
|
var appendBaselineStream = false;
|
||||||
var baselineStreamBitrate = 64000;
|
var baselineStreamBitrate = 64000;
|
||||||
@ -165,37 +183,6 @@ namespace MediaBrowser.Api.Playback.Hls
|
|||||||
return minimumSegmentCount;
|
return minimumSegmentCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the playlist bitrates.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="state">The state.</param>
|
|
||||||
/// <param name="audioBitrate">The audio bitrate.</param>
|
|
||||||
/// <param name="videoBitrate">The video bitrate.</param>
|
|
||||||
protected void GetPlaylistBitrates(StreamState state, out int audioBitrate, out int videoBitrate)
|
|
||||||
{
|
|
||||||
var audioBitrateParam = state.OutputAudioBitrate;
|
|
||||||
var videoBitrateParam = state.OutputVideoBitrate;
|
|
||||||
|
|
||||||
if (!audioBitrateParam.HasValue)
|
|
||||||
{
|
|
||||||
if (state.AudioStream != null)
|
|
||||||
{
|
|
||||||
audioBitrateParam = state.AudioStream.BitRate;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!videoBitrateParam.HasValue)
|
|
||||||
{
|
|
||||||
if (state.VideoStream != null)
|
|
||||||
{
|
|
||||||
videoBitrateParam = state.VideoStream.BitRate;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
audioBitrate = audioBitrateParam ?? 0;
|
|
||||||
videoBitrate = videoBitrateParam ?? 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
private string GetMasterPlaylistFileText(string firstPlaylist, int bitrate, bool includeBaselineStream, int baselineStreamBitrate)
|
private string GetMasterPlaylistFileText(string firstPlaylist, int bitrate, bool includeBaselineStream, int baselineStreamBitrate)
|
||||||
{
|
{
|
||||||
var builder = new StringBuilder();
|
var builder = new StringBuilder();
|
||||||
@ -223,49 +210,37 @@ namespace MediaBrowser.Api.Playback.Hls
|
|||||||
|
|
||||||
protected async Task WaitForMinimumSegmentCount(string playlist, int segmentCount, CancellationToken cancellationToken)
|
protected async Task WaitForMinimumSegmentCount(string playlist, int segmentCount, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
Logger.Debug("Waiting for {0} segments in {1}", segmentCount, playlist);
|
||||||
|
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
cancellationToken.ThrowIfCancellationRequested();
|
|
||||||
|
|
||||||
string fileText;
|
|
||||||
|
|
||||||
// Need to use FileShare.ReadWrite because we're reading the file at the same time it's being written
|
// Need to use FileShare.ReadWrite because we're reading the file at the same time it's being written
|
||||||
using (var fileStream = FileSystem.GetFileStream(playlist, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, true))
|
using (var fileStream = FileSystem.GetFileStream(playlist, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, true))
|
||||||
{
|
{
|
||||||
using (var reader = new StreamReader(fileStream))
|
using (var reader = new StreamReader(fileStream))
|
||||||
{
|
{
|
||||||
fileText = await reader.ReadToEndAsync().ConfigureAwait(false);
|
var count = 0;
|
||||||
|
|
||||||
|
while (!reader.EndOfStream)
|
||||||
|
{
|
||||||
|
var line = await reader.ReadLineAsync().ConfigureAwait(false);
|
||||||
|
|
||||||
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (CountStringOccurrences(fileText, "#EXTINF:") >= segmentCount)
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
await Task.Delay(25, cancellationToken).ConfigureAwait(false);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Count occurrences of strings.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="text">The text.</param>
|
|
||||||
/// <param name="pattern">The pattern.</param>
|
|
||||||
/// <returns>System.Int32.</returns>
|
|
||||||
private static int CountStringOccurrences(string text, string pattern)
|
|
||||||
{
|
|
||||||
// Loop through all instances of the string 'text'.
|
|
||||||
var count = 0;
|
|
||||||
var i = 0;
|
|
||||||
while ((i = text.IndexOf(pattern, i, StringComparison.OrdinalIgnoreCase)) != -1)
|
|
||||||
{
|
|
||||||
i += pattern.Length;
|
|
||||||
count++;
|
|
||||||
}
|
|
||||||
return count;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the command line arguments.
|
/// Gets the command line arguments.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -279,7 +254,7 @@ namespace MediaBrowser.Api.Playback.Hls
|
|||||||
|
|
||||||
var itsOffsetMs = hlsVideoRequest == null
|
var itsOffsetMs = hlsVideoRequest == null
|
||||||
? 0
|
? 0
|
||||||
: ((GetHlsVideoStream)state.VideoRequest).TimeStampOffsetMs;
|
: hlsVideoRequest.TimeStampOffsetMs;
|
||||||
|
|
||||||
var itsOffset = itsOffsetMs == 0 ? string.Empty : string.Format("-itsoffset {0} ", TimeSpan.FromMilliseconds(itsOffsetMs).TotalSeconds.ToString(UsCulture));
|
var itsOffset = itsOffsetMs == 0 ? string.Empty : string.Format("-itsoffset {0} ", TimeSpan.FromMilliseconds(itsOffsetMs).TotalSeconds.ToString(UsCulture));
|
||||||
|
|
||||||
@ -290,7 +265,15 @@ namespace MediaBrowser.Api.Playback.Hls
|
|||||||
// If isEncoding is true we're actually starting ffmpeg
|
// If isEncoding is true we're actually starting ffmpeg
|
||||||
var startNumberParam = isEncoding ? GetStartNumber(state).ToString(UsCulture) : "0";
|
var startNumberParam = isEncoding ? GetStartNumber(state).ToString(UsCulture) : "0";
|
||||||
|
|
||||||
var args = string.Format("{0} {1} -i {2} -map_metadata -1 -threads {3} {4} {5} -sc_threshold 0 {6} -hls_time {7} -start_number {8} -hls_list_size {9} -y \"{10}\"",
|
var baseUrlParam = string.Empty;
|
||||||
|
|
||||||
|
if (state.Request is GetLiveHlsStream)
|
||||||
|
{
|
||||||
|
baseUrlParam = string.Format(" -hls_base_url \"{0}/\"",
|
||||||
|
"hls/" + Path.GetFileNameWithoutExtension(outputPath));
|
||||||
|
}
|
||||||
|
|
||||||
|
var args = string.Format("{0} {1} -i {2} -map_metadata -1 -threads {3} {4} {5} -sc_threshold 0 {6} -hls_time {7} -start_number {8} -hls_list_size {9}{10} -y \"{11}\"",
|
||||||
itsOffset,
|
itsOffset,
|
||||||
inputModifier,
|
inputModifier,
|
||||||
GetInputArgument(state),
|
GetInputArgument(state),
|
||||||
@ -301,6 +284,7 @@ namespace MediaBrowser.Api.Playback.Hls
|
|||||||
state.SegmentLength.ToString(UsCulture),
|
state.SegmentLength.ToString(UsCulture),
|
||||||
startNumberParam,
|
startNumberParam,
|
||||||
state.HlsListSize.ToString(UsCulture),
|
state.HlsListSize.ToString(UsCulture),
|
||||||
|
baseUrlParam,
|
||||||
outputPath
|
outputPath
|
||||||
).Trim();
|
).Trim();
|
||||||
|
|
||||||
|
@ -1,45 +1,46 @@
|
|||||||
using MediaBrowser.Common.IO;
|
using MediaBrowser.Common.Extensions;
|
||||||
|
using MediaBrowser.Common.IO;
|
||||||
using MediaBrowser.Controller.Channels;
|
using MediaBrowser.Controller.Channels;
|
||||||
using MediaBrowser.Controller.Configuration;
|
using MediaBrowser.Controller.Configuration;
|
||||||
using MediaBrowser.Controller.Dlna;
|
using MediaBrowser.Controller.Dlna;
|
||||||
using MediaBrowser.Controller.Library;
|
using MediaBrowser.Controller.Library;
|
||||||
using MediaBrowser.Controller.LiveTv;
|
using MediaBrowser.Controller.LiveTv;
|
||||||
using MediaBrowser.Controller.MediaEncoding;
|
using MediaBrowser.Controller.MediaEncoding;
|
||||||
|
using MediaBrowser.Model.Dlna;
|
||||||
|
using MediaBrowser.Model.Entities;
|
||||||
using MediaBrowser.Model.IO;
|
using MediaBrowser.Model.IO;
|
||||||
using ServiceStack;
|
using ServiceStack;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace MediaBrowser.Api.Playback.Hls
|
namespace MediaBrowser.Api.Playback.Hls
|
||||||
{
|
{
|
||||||
[Route("/Videos/{Id}/master.m3u8", "GET")]
|
/// <summary>
|
||||||
[Api(Description = "Gets a video stream using HTTP live streaming.")]
|
/// 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 GetMasterHlsVideoStream : VideoStreamRequest
|
public class GetMasterHlsVideoStream : VideoStreamRequest
|
||||||
{
|
{
|
||||||
[ApiMember(Name = "BaselineStreamAudioBitRate", Description = "Optional. Specify the audio bitrate for the baseline stream.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
|
public bool EnableAdaptiveBitrateStreaming { get; set; }
|
||||||
public int? BaselineStreamAudioBitRate { get; set; }
|
|
||||||
|
|
||||||
[ApiMember(Name = "AppendBaselineStream", Description = "Optional. Whether or not to include a baseline audio-only stream in the master playlist.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
|
public GetMasterHlsVideoStream()
|
||||||
public bool AppendBaselineStream { get; set; }
|
{
|
||||||
|
EnableAdaptiveBitrateStreaming = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Route("/Videos/{Id}/main.m3u8", "GET")]
|
[Route("/Videos/{Id}/main.m3u8", "GET", Summary = "Gets a video stream using HTTP live streaming.")]
|
||||||
[Api(Description = "Gets a video stream using HTTP live streaming.")]
|
|
||||||
public class GetMainHlsVideoStream : VideoStreamRequest
|
public class GetMainHlsVideoStream : VideoStreamRequest
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
[Route("/Videos/{Id}/baseline.m3u8", "GET")]
|
|
||||||
[Api(Description = "Gets a video stream using HTTP live streaming.")]
|
|
||||||
public class GetBaselineHlsVideoStream : VideoStreamRequest
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Class GetHlsVideoSegment
|
/// Class GetHlsVideoSegment
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -58,34 +59,48 @@ namespace MediaBrowser.Api.Playback.Hls
|
|||||||
|
|
||||||
public class DynamicHlsService : BaseHlsService
|
public class DynamicHlsService : BaseHlsService
|
||||||
{
|
{
|
||||||
public DynamicHlsService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, ILiveTvManager liveTvManager, IDlnaManager dlnaManager, IChannelManager channelManager, ISubtitleEncoder subtitleEncoder) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, fileSystem, liveTvManager, dlnaManager, channelManager, subtitleEncoder)
|
public DynamicHlsService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, ILiveTvManager liveTvManager, IDlnaManager dlnaManager, IChannelManager channelManager, ISubtitleEncoder subtitleEncoder)
|
||||||
|
: base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, fileSystem, liveTvManager, dlnaManager, channelManager, subtitleEncoder)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public object Get(GetMasterHlsVideoStream request)
|
public object Get(GetMasterHlsVideoStream request)
|
||||||
{
|
{
|
||||||
var result = GetAsync(request).Result;
|
var result = GetAsync(request, "GET").Result;
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public object Head(GetMasterHlsVideoStream request)
|
||||||
|
{
|
||||||
|
var result = GetAsync(request, "HEAD").Result;
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public object Get(GetMainHlsVideoStream request)
|
||||||
|
{
|
||||||
|
var result = GetPlaylistAsync(request, "main").Result;
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
public object Get(GetDynamicHlsVideoSegment request)
|
public object Get(GetDynamicHlsVideoSegment request)
|
||||||
{
|
{
|
||||||
if (string.Equals("baseline", request.PlaylistId, StringComparison.OrdinalIgnoreCase))
|
return GetDynamicSegment(request, request.SegmentId).Result;
|
||||||
{
|
|
||||||
return GetDynamicSegment(request, false).Result;
|
|
||||||
}
|
|
||||||
|
|
||||||
return GetDynamicSegment(request, true).Result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static readonly SemaphoreSlim FfmpegStartLock = new SemaphoreSlim(1, 1);
|
private async Task<object> GetDynamicSegment(VideoStreamRequest request, string segmentId)
|
||||||
private async Task<object> GetDynamicSegment(GetDynamicHlsVideoSegment request, bool isMain)
|
|
||||||
{
|
{
|
||||||
|
if ((request.StartTimeTicks ?? 0) > 0)
|
||||||
|
{
|
||||||
|
throw new ArgumentException("StartTimeTicks is not allowed.");
|
||||||
|
}
|
||||||
|
|
||||||
var cancellationTokenSource = new CancellationTokenSource();
|
var cancellationTokenSource = new CancellationTokenSource();
|
||||||
var cancellationToken = cancellationTokenSource.Token;
|
var cancellationToken = cancellationTokenSource.Token;
|
||||||
|
|
||||||
var index = int.Parse(request.SegmentId, NumberStyles.Integer, UsCulture);
|
var index = int.Parse(segmentId, NumberStyles.Integer, UsCulture);
|
||||||
|
|
||||||
var state = await GetState(request, cancellationToken).ConfigureAwait(false);
|
var state = await GetState(request, cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
@ -96,25 +111,35 @@ namespace MediaBrowser.Api.Playback.Hls
|
|||||||
if (File.Exists(segmentPath))
|
if (File.Exists(segmentPath))
|
||||||
{
|
{
|
||||||
ApiEntryPoint.Instance.OnTranscodeBeginRequest(playlistPath, TranscodingJobType.Hls);
|
ApiEntryPoint.Instance.OnTranscodeBeginRequest(playlistPath, TranscodingJobType.Hls);
|
||||||
return GetSegementResult(segmentPath);
|
return await GetSegmentResult(playlistPath, segmentPath, index, cancellationToken).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
await FfmpegStartLock.WaitAsync(cancellationTokenSource.Token).ConfigureAwait(false);
|
await ApiEntryPoint.Instance.TranscodingStartLock.WaitAsync(cancellationTokenSource.Token).ConfigureAwait(false);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (File.Exists(segmentPath))
|
if (File.Exists(segmentPath))
|
||||||
{
|
{
|
||||||
ApiEntryPoint.Instance.OnTranscodeBeginRequest(playlistPath, TranscodingJobType.Hls);
|
ApiEntryPoint.Instance.OnTranscodeBeginRequest(playlistPath, TranscodingJobType.Hls);
|
||||||
return GetSegementResult(segmentPath);
|
return await GetSegmentResult(playlistPath, segmentPath, index, cancellationToken).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (index == 0)
|
var currentTranscodingIndex = GetCurrentTranscodingIndex(playlistPath);
|
||||||
|
|
||||||
|
if (currentTranscodingIndex == null || index < currentTranscodingIndex.Value || (index - currentTranscodingIndex.Value) > 4)
|
||||||
{
|
{
|
||||||
// If the playlist doesn't already exist, startup ffmpeg
|
// If the playlist doesn't already exist, startup ffmpeg
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
ApiEntryPoint.Instance.KillTranscodingJobs(state.Request.DeviceId, false);
|
await ApiEntryPoint.Instance.KillTranscodingJobs(j => j.Type == TranscodingJobType.Hls && string.Equals(j.DeviceId, request.DeviceId, StringComparison.OrdinalIgnoreCase), p => !string.Equals(p, playlistPath, StringComparison.OrdinalIgnoreCase), false).ConfigureAwait(false);
|
||||||
|
|
||||||
|
if (currentTranscodingIndex.HasValue)
|
||||||
|
{
|
||||||
|
DeleteLastFile(playlistPath, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
var startSeconds = index * state.SegmentLength;
|
||||||
|
request.StartTimeTicks = TimeSpan.FromSeconds(startSeconds).Ticks;
|
||||||
|
|
||||||
await StartFfMpeg(state, playlistPath, cancellationTokenSource).ConfigureAwait(false);
|
await StartFfMpeg(state, playlistPath, cancellationTokenSource).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
@ -124,13 +149,13 @@ namespace MediaBrowser.Api.Playback.Hls
|
|||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
|
|
||||||
await WaitForMinimumSegmentCount(playlistPath, 2, cancellationTokenSource.Token).ConfigureAwait(false);
|
await WaitForMinimumSegmentCount(playlistPath, 1, cancellationTokenSource.Token).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
FfmpegStartLock.Release();
|
ApiEntryPoint.Instance.TranscodingStartLock.Release();
|
||||||
}
|
}
|
||||||
|
|
||||||
Logger.Info("waiting for {0}", segmentPath);
|
Logger.Info("waiting for {0}", segmentPath);
|
||||||
@ -140,14 +165,88 @@ namespace MediaBrowser.Api.Playback.Hls
|
|||||||
}
|
}
|
||||||
|
|
||||||
Logger.Info("returning {0}", segmentPath);
|
Logger.Info("returning {0}", segmentPath);
|
||||||
return GetSegementResult(segmentPath);
|
return await GetSegmentResult(playlistPath, segmentPath, index, cancellationToken).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int? GetCurrentTranscodingIndex(string playlist)
|
||||||
|
{
|
||||||
|
var file = GetLastTranscodingFile(playlist, 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 path, int retryCount)
|
||||||
|
{
|
||||||
|
if (retryCount >= 5)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var file = GetLastTranscodingFile(path, FileSystem);
|
||||||
|
|
||||||
|
if (file != null)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
File.Delete(file.FullName);
|
||||||
|
}
|
||||||
|
catch (IOException ex)
|
||||||
|
{
|
||||||
|
Logger.ErrorException("Error deleting partial stream file(s) {0}", ex, file.FullName);
|
||||||
|
|
||||||
|
Thread.Sleep(100);
|
||||||
|
DeleteLastFile(path, retryCount + 1);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logger.ErrorException("Error deleting partial stream file(s) {0}", ex, file.FullName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static FileInfo GetLastTranscodingFile(string playlist, IFileSystem fileSystem)
|
||||||
|
{
|
||||||
|
var folder = Path.GetDirectoryName(playlist);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return new DirectoryInfo(folder)
|
||||||
|
.EnumerateFiles("*", SearchOption.TopDirectoryOnly)
|
||||||
|
.Where(i => string.Equals(i.Extension, ".ts", StringComparison.OrdinalIgnoreCase))
|
||||||
|
.OrderByDescending(fileSystem.GetLastWriteTimeUtc)
|
||||||
|
.FirstOrDefault();
|
||||||
|
}
|
||||||
|
catch (DirectoryNotFoundException)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override int GetStartNumber(StreamState state)
|
protected override int GetStartNumber(StreamState state)
|
||||||
{
|
{
|
||||||
var request = (GetDynamicHlsVideoSegment) state.Request;
|
return GetStartNumber(state.VideoRequest);
|
||||||
|
}
|
||||||
|
|
||||||
return int.Parse(request.SegmentId, NumberStyles.Integer, UsCulture);
|
private int GetStartNumber(VideoStreamRequest request)
|
||||||
|
{
|
||||||
|
var segmentId = "0";
|
||||||
|
|
||||||
|
var segmentRequest = request as GetDynamicHlsVideoSegment;
|
||||||
|
if (segmentRequest != null)
|
||||||
|
{
|
||||||
|
segmentId = segmentRequest.SegmentId;
|
||||||
|
}
|
||||||
|
|
||||||
|
return int.Parse(segmentId, NumberStyles.Integer, UsCulture);
|
||||||
}
|
}
|
||||||
|
|
||||||
private string GetSegmentPath(string playlist, int index)
|
private string GetSegmentPath(string playlist, int index)
|
||||||
@ -159,75 +258,262 @@ namespace MediaBrowser.Api.Playback.Hls
|
|||||||
return Path.Combine(folder, filename + index.ToString(UsCulture) + ".ts");
|
return Path.Combine(folder, filename + index.ToString(UsCulture) + ".ts");
|
||||||
}
|
}
|
||||||
|
|
||||||
private object GetSegementResult(string path)
|
private async Task<object> GetSegmentResult(string playlistPath, string segmentPath, int segmentIndex, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
// TODO: Handle if it's currently being written to
|
// If all transcoding has completed, just return immediately
|
||||||
return ResultFactory.GetStaticFileResult(Request, path, FileShare.ReadWrite);
|
if (!IsTranscoding(playlistPath))
|
||||||
|
{
|
||||||
|
return ResultFactory.GetStaticFileResult(Request, segmentPath, FileShare.ReadWrite);
|
||||||
|
}
|
||||||
|
|
||||||
|
var segmentFilename = Path.GetFileName(segmentPath);
|
||||||
|
|
||||||
|
using (var fileStream = FileSystem.GetFileStream(playlistPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, true))
|
||||||
|
{
|
||||||
|
using (var reader = new StreamReader(fileStream))
|
||||||
|
{
|
||||||
|
var text = await reader.ReadToEndAsync().ConfigureAwait(false);
|
||||||
|
|
||||||
|
// If it appears in the playlist, it's done
|
||||||
|
if (text.IndexOf(segmentFilename, StringComparison.OrdinalIgnoreCase) != -1)
|
||||||
|
{
|
||||||
|
return ResultFactory.GetStaticFileResult(Request, segmentPath, FileShare.ReadWrite);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// if a different file is encoding, it's done
|
||||||
|
//var currentTranscodingIndex = GetCurrentTranscodingIndex(playlistPath);
|
||||||
|
//if (currentTranscodingIndex > segmentIndex)
|
||||||
|
//{
|
||||||
|
// return ResultFactory.GetStaticFileResult(Request, segmentPath, FileShare.ReadWrite);
|
||||||
|
//}
|
||||||
|
|
||||||
|
// Wait for the file to stop being written to, then stream it
|
||||||
|
var length = new FileInfo(segmentPath).Length;
|
||||||
|
var eofCount = 0;
|
||||||
|
|
||||||
|
while (eofCount < 10)
|
||||||
|
{
|
||||||
|
var info = new FileInfo(segmentPath);
|
||||||
|
|
||||||
|
if (!info.Exists)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
var newLength = info.Length;
|
||||||
|
|
||||||
|
if (newLength == length)
|
||||||
|
{
|
||||||
|
eofCount++;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
eofCount = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
length = newLength;
|
||||||
|
await Task.Delay(100, cancellationToken).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ResultFactory.GetStaticFileResult(Request, segmentPath, FileShare.ReadWrite);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<object> GetAsync(GetMasterHlsVideoStream request)
|
private bool IsTranscoding(string playlistPath)
|
||||||
|
{
|
||||||
|
var job = ApiEntryPoint.Instance.GetTranscodingJob(playlistPath, TranscodingJobType);
|
||||||
|
|
||||||
|
return job != null && !job.HasExited;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<object> GetAsync(GetMasterHlsVideoStream request, string method)
|
||||||
{
|
{
|
||||||
var state = await GetState(request, CancellationToken.None).ConfigureAwait(false);
|
var state = await GetState(request, CancellationToken.None).ConfigureAwait(false);
|
||||||
|
|
||||||
int audioBitrate;
|
if (string.Equals(request.AudioCodec, "copy", StringComparison.OrdinalIgnoreCase))
|
||||||
int videoBitrate;
|
|
||||||
GetPlaylistBitrates(state, out audioBitrate, out videoBitrate);
|
|
||||||
|
|
||||||
var appendBaselineStream = false;
|
|
||||||
var baselineStreamBitrate = 64000;
|
|
||||||
|
|
||||||
var hlsVideoRequest = state.VideoRequest as GetMasterHlsVideoStream;
|
|
||||||
if (hlsVideoRequest != null)
|
|
||||||
{
|
{
|
||||||
appendBaselineStream = hlsVideoRequest.AppendBaselineStream;
|
throw new ArgumentException("Audio codec copy is not allowed here.");
|
||||||
baselineStreamBitrate = hlsVideoRequest.BaselineStreamAudioBitRate ?? baselineStreamBitrate;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var playlistText = GetMasterPlaylistFileText(videoBitrate + audioBitrate, appendBaselineStream, baselineStreamBitrate);
|
if (string.Equals(request.VideoCodec, "copy", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Video codec copy is not allowed here.");
|
||||||
|
}
|
||||||
|
|
||||||
|
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, Common.Net.MimeTypes.GetMimeType("playlist.m3u8"), new Dictionary<string, string>());
|
return ResultFactory.GetResult(playlistText, Common.Net.MimeTypes.GetMimeType("playlist.m3u8"), new Dictionary<string, string>());
|
||||||
}
|
}
|
||||||
|
|
||||||
private string GetMasterPlaylistFileText(int bitrate, bool includeBaselineStream, int baselineStreamBitrate)
|
private string GetMasterPlaylistFileText(StreamState state, int totalBitrate)
|
||||||
{
|
{
|
||||||
var builder = new StringBuilder();
|
var builder = new StringBuilder();
|
||||||
|
|
||||||
builder.AppendLine("#EXTM3U");
|
builder.AppendLine("#EXTM3U");
|
||||||
|
|
||||||
// Pad a little to satisfy the apple hls validator
|
|
||||||
var paddedBitrate = Convert.ToInt32(bitrate * 1.05);
|
|
||||||
|
|
||||||
var queryStringIndex = Request.RawUrl.IndexOf('?');
|
var queryStringIndex = Request.RawUrl.IndexOf('?');
|
||||||
var queryString = queryStringIndex == -1 ? string.Empty : Request.RawUrl.Substring(queryStringIndex);
|
var queryString = queryStringIndex == -1 ? string.Empty : Request.RawUrl.Substring(queryStringIndex);
|
||||||
|
|
||||||
// Main stream
|
// Main stream
|
||||||
builder.AppendLine("#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=" + paddedBitrate.ToString(UsCulture));
|
var playlistUrl = (state.RunTimeTicks ?? 0) > 0 ? "main.m3u8" : "live.m3u8";
|
||||||
var playlistUrl = "main.m3u8" + queryString;
|
playlistUrl += queryString;
|
||||||
builder.AppendLine(playlistUrl);
|
|
||||||
|
|
||||||
// Low bitrate stream
|
var request = (GetMasterHlsVideoStream)state.Request;
|
||||||
if (includeBaselineStream)
|
|
||||||
|
var subtitleStreams = state.AllMediaStreams
|
||||||
|
.Where(i => i.IsTextSubtitleStream)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
var subtitleGroup = subtitleStreams.Count > 0 && request.SubtitleMethod == SubtitleDeliveryMethod.Hls ?
|
||||||
|
"subs" :
|
||||||
|
null;
|
||||||
|
|
||||||
|
AppendPlaylist(builder, playlistUrl, totalBitrate, subtitleGroup);
|
||||||
|
|
||||||
|
if (EnableAdaptiveBitrateStreaming(state))
|
||||||
{
|
{
|
||||||
builder.AppendLine("#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=" + baselineStreamBitrate.ToString(UsCulture));
|
var requestedVideoBitrate = state.VideoRequest.VideoBitRate.Value;
|
||||||
playlistUrl = "baseline.m3u8" + queryString;
|
|
||||||
builder.AppendLine(playlistUrl);
|
// By default, vary by just 200k
|
||||||
|
var variation = GetBitrateVariation(totalBitrate);
|
||||||
|
|
||||||
|
var newBitrate = totalBitrate - variation;
|
||||||
|
var variantUrl = ReplaceBitrate(playlistUrl, requestedVideoBitrate, (requestedVideoBitrate - variation));
|
||||||
|
AppendPlaylist(builder, variantUrl, newBitrate, subtitleGroup);
|
||||||
|
|
||||||
|
variation *= 2;
|
||||||
|
newBitrate = totalBitrate - variation;
|
||||||
|
variantUrl = ReplaceBitrate(playlistUrl, requestedVideoBitrate, (requestedVideoBitrate - variation));
|
||||||
|
AppendPlaylist(builder, variantUrl, newBitrate, subtitleGroup);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(subtitleGroup))
|
||||||
|
{
|
||||||
|
AddSubtitles(state, subtitleStreams, builder);
|
||||||
}
|
}
|
||||||
|
|
||||||
return builder.ToString();
|
return builder.ToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
public object Get(GetMainHlsVideoStream request)
|
private string ReplaceBitrate(string url, int oldValue, int newValue)
|
||||||
{
|
{
|
||||||
var result = GetPlaylistAsync(request, "main").Result;
|
return url.Replace(
|
||||||
|
"videobitrate=" + oldValue.ToString(UsCulture),
|
||||||
return result;
|
"videobitrate=" + newValue.ToString(UsCulture),
|
||||||
|
StringComparison.OrdinalIgnoreCase);
|
||||||
}
|
}
|
||||||
|
|
||||||
public object Get(GetBaselineHlsVideoStream request)
|
private void AddSubtitles(StreamState state, IEnumerable<MediaStream> subtitles, StringBuilder builder)
|
||||||
{
|
{
|
||||||
var result = GetPlaylistAsync(request, "baseline").Result;
|
var selectedIndex = state.SubtitleStream == null ? (int?)null : state.SubtitleStream.Index;
|
||||||
|
|
||||||
return result;
|
foreach (var stream in subtitles)
|
||||||
|
{
|
||||||
|
const string format = "#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID=\"subs\",NAME=\"{0}\",DEFAULT={1},FORCED={2},URI=\"{3}\",LANGUAGE=\"{4}\"";
|
||||||
|
|
||||||
|
var name = stream.Language;
|
||||||
|
|
||||||
|
var isDefault = selectedIndex.HasValue && selectedIndex.Value == stream.Index;
|
||||||
|
var isForced = stream.IsForced;
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(name)) name = stream.Codec ?? "Unknown";
|
||||||
|
|
||||||
|
var url = string.Format("{0}/Subtitles/{1}/subtitles.m3u8?SegmentLength={2}",
|
||||||
|
state.Request.MediaSourceId,
|
||||||
|
stream.Index.ToString(UsCulture),
|
||||||
|
30.ToString(UsCulture));
|
||||||
|
|
||||||
|
var line = string.Format(format,
|
||||||
|
name,
|
||||||
|
isDefault ? "YES" : "NO",
|
||||||
|
isForced ? "YES" : "NO",
|
||||||
|
url,
|
||||||
|
stream.Language ?? "Unknown");
|
||||||
|
|
||||||
|
builder.AppendLine(line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool EnableAdaptiveBitrateStreaming(StreamState state)
|
||||||
|
{
|
||||||
|
var request = state.Request as GetMasterHlsVideoStream;
|
||||||
|
|
||||||
|
if (request != null && !request.EnableAdaptiveBitrateStreaming)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(state.MediaPath))
|
||||||
|
{
|
||||||
|
// Opening live streams is so slow it's not even worth it
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return state.VideoRequest.VideoBitRate.HasValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AppendPlaylist(StringBuilder builder, string url, int bitrate, string subtitleGroup)
|
||||||
|
{
|
||||||
|
var header = "#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=" + bitrate.ToString(UsCulture);
|
||||||
|
|
||||||
|
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> GetPlaylistAsync(VideoStreamRequest request, string name)
|
private async Task<object> GetPlaylistAsync(VideoStreamRequest request, string name)
|
||||||
@ -240,6 +526,7 @@ namespace MediaBrowser.Api.Playback.Hls
|
|||||||
builder.AppendLine("#EXT-X-VERSION:3");
|
builder.AppendLine("#EXT-X-VERSION:3");
|
||||||
builder.AppendLine("#EXT-X-TARGETDURATION:" + state.SegmentLength.ToString(UsCulture));
|
builder.AppendLine("#EXT-X-TARGETDURATION:" + state.SegmentLength.ToString(UsCulture));
|
||||||
builder.AppendLine("#EXT-X-MEDIA-SEQUENCE:0");
|
builder.AppendLine("#EXT-X-MEDIA-SEQUENCE:0");
|
||||||
|
builder.AppendLine("#EXT-X-ALLOW-CACHE:NO");
|
||||||
|
|
||||||
var queryStringIndex = Request.RawUrl.IndexOf('?');
|
var queryStringIndex = Request.RawUrl.IndexOf('?');
|
||||||
var queryString = queryStringIndex == -1 ? string.Empty : Request.RawUrl.Substring(queryStringIndex);
|
var queryString = queryStringIndex == -1 ? string.Empty : Request.RawUrl.Substring(queryStringIndex);
|
||||||
@ -252,7 +539,7 @@ namespace MediaBrowser.Api.Playback.Hls
|
|||||||
{
|
{
|
||||||
var length = seconds >= state.SegmentLength ? state.SegmentLength : seconds;
|
var length = seconds >= state.SegmentLength ? state.SegmentLength : seconds;
|
||||||
|
|
||||||
builder.AppendLine("#EXTINF:" + length.ToString(UsCulture));
|
builder.AppendLine("#EXTINF:" + length.ToString(UsCulture) + ",");
|
||||||
|
|
||||||
builder.AppendLine(string.Format("hlsdynamic/{0}/{1}.ts{2}",
|
builder.AppendLine(string.Format("hlsdynamic/{0}/{1}.ts{2}",
|
||||||
|
|
||||||
@ -312,9 +599,8 @@ namespace MediaBrowser.Api.Playback.Hls
|
|||||||
return IsH264(state.VideoStream) ? "-codec:v:0 copy -bsf h264_mp4toannexb" : "-codec:v:0 copy";
|
return IsH264(state.VideoStream) ? "-codec:v:0 copy -bsf h264_mp4toannexb" : "-codec:v:0 copy";
|
||||||
}
|
}
|
||||||
|
|
||||||
var keyFrameArg = state.ReadInputAtNativeFramerate ?
|
var keyFrameArg = string.Format(" -force_key_frames expr:gte(t,n_forced*{0})",
|
||||||
" -force_key_frames expr:if(isnan(prev_forced_t),gte(t,.1),gte(t,prev_forced_t+1))" :
|
state.SegmentLength.ToString(UsCulture));
|
||||||
" -force_key_frames expr:if(isnan(prev_forced_t),gte(t,.1),gte(t,prev_forced_t+5))";
|
|
||||||
|
|
||||||
var hasGraphicalSubs = state.SubtitleStream != null && !state.SubtitleStream.IsTextSubtitleStream;
|
var hasGraphicalSubs = state.SubtitleStream != null && !state.SubtitleStream.IsTextSubtitleStream;
|
||||||
|
|
||||||
@ -323,18 +609,50 @@ namespace MediaBrowser.Api.Playback.Hls
|
|||||||
// Add resolution params, if specified
|
// Add resolution params, if specified
|
||||||
if (!hasGraphicalSubs)
|
if (!hasGraphicalSubs)
|
||||||
{
|
{
|
||||||
args += GetOutputSizeParam(state, codec, CancellationToken.None);
|
args += GetOutputSizeParam(state, codec, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
// This is for internal graphical subs
|
// This is for internal graphical subs
|
||||||
if (hasGraphicalSubs)
|
if (hasGraphicalSubs)
|
||||||
{
|
{
|
||||||
args += GetInternalGraphicalSubtitleParam(state, codec);
|
args += GetGraphicalSubtitleParam(state, codec);
|
||||||
}
|
}
|
||||||
|
|
||||||
return args;
|
return args;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the command line arguments.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="outputPath">The output path.</param>
|
||||||
|
/// <param name="state">The state.</param>
|
||||||
|
/// <param name="isEncoding">if set to <c>true</c> [is encoding].</param>
|
||||||
|
/// <returns>System.String.</returns>
|
||||||
|
protected override string GetCommandLineArguments(string outputPath, StreamState state, bool isEncoding)
|
||||||
|
{
|
||||||
|
var threads = GetNumberOfThreads(state, false);
|
||||||
|
|
||||||
|
var inputModifier = GetInputModifier(state);
|
||||||
|
|
||||||
|
// If isEncoding is true we're actually starting ffmpeg
|
||||||
|
var startNumberParam = isEncoding ? GetStartNumber(state).ToString(UsCulture) : "0";
|
||||||
|
|
||||||
|
var args = string.Format("{0} -i {1} -map_metadata -1 -threads {2} {3} {4} -copyts -flags -global_header {5} -hls_time {6} -start_number {7} -hls_list_size {8} -y \"{9}\"",
|
||||||
|
inputModifier,
|
||||||
|
GetInputArgument(state),
|
||||||
|
threads,
|
||||||
|
GetMapArgs(state),
|
||||||
|
GetVideoArguments(state),
|
||||||
|
GetAudioArguments(state),
|
||||||
|
state.SegmentLength.ToString(UsCulture),
|
||||||
|
startNumberParam,
|
||||||
|
state.HlsListSize.ToString(UsCulture),
|
||||||
|
outputPath
|
||||||
|
).Trim();
|
||||||
|
|
||||||
|
return args;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the segment file extension.
|
/// Gets the segment file extension.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -344,5 +662,13 @@ namespace MediaBrowser.Api.Playback.Hls
|
|||||||
{
|
{
|
||||||
return ".ts";
|
return ".ts";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override TranscodingJobType TranscodingJobType
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return TranscodingJobType.Hls;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -74,7 +74,9 @@ namespace MediaBrowser.Api.Playback.Hls
|
|||||||
|
|
||||||
public void Delete(StopEncodingProcess request)
|
public void Delete(StopEncodingProcess request)
|
||||||
{
|
{
|
||||||
ApiEntryPoint.Instance.KillTranscodingJobs(request.DeviceId, true);
|
var task = ApiEntryPoint.Instance.KillTranscodingJobs(request.DeviceId, path => true, true);
|
||||||
|
|
||||||
|
Task.WaitAll(task);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -10,7 +10,6 @@ using ServiceStack;
|
|||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace MediaBrowser.Api.Playback.Hls
|
namespace MediaBrowser.Api.Playback.Hls
|
||||||
@ -32,6 +31,12 @@ namespace MediaBrowser.Api.Playback.Hls
|
|||||||
public int TimeStampOffsetMs { get; set; }
|
public int TimeStampOffsetMs { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Route("/Videos/{Id}/live.m3u8", "GET")]
|
||||||
|
[Api(Description = "Gets a video stream using HTTP live streaming.")]
|
||||||
|
public class GetLiveHlsStream : VideoStreamRequest
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Class GetHlsVideoSegment
|
/// Class GetHlsVideoSegment
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -105,7 +110,12 @@ namespace MediaBrowser.Api.Playback.Hls
|
|||||||
/// <returns>System.Object.</returns>
|
/// <returns>System.Object.</returns>
|
||||||
public object Get(GetHlsVideoStream request)
|
public object Get(GetHlsVideoStream request)
|
||||||
{
|
{
|
||||||
return ProcessRequest(request);
|
return ProcessRequest(request, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public object Get(GetLiveHlsStream request)
|
||||||
|
{
|
||||||
|
return ProcessRequest(request, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -159,9 +169,8 @@ namespace MediaBrowser.Api.Playback.Hls
|
|||||||
return IsH264(state.VideoStream) ? "-codec:v:0 copy -bsf h264_mp4toannexb" : "-codec:v:0 copy";
|
return IsH264(state.VideoStream) ? "-codec:v:0 copy -bsf h264_mp4toannexb" : "-codec:v:0 copy";
|
||||||
}
|
}
|
||||||
|
|
||||||
var keyFrameArg = state.ReadInputAtNativeFramerate ?
|
var keyFrameArg = string.Format(" -force_key_frames expr:gte(t,n_forced*{0})",
|
||||||
" -force_key_frames expr:if(isnan(prev_forced_t),gte(t,.1),gte(t,prev_forced_t+1))" :
|
state.SegmentLength.ToString(UsCulture));
|
||||||
" -force_key_frames expr:if(isnan(prev_forced_t),gte(t,.1),gte(t,prev_forced_t+5))";
|
|
||||||
|
|
||||||
var hasGraphicalSubs = state.SubtitleStream != null && !state.SubtitleStream.IsTextSubtitleStream;
|
var hasGraphicalSubs = state.SubtitleStream != null && !state.SubtitleStream.IsTextSubtitleStream;
|
||||||
|
|
||||||
@ -170,13 +179,13 @@ namespace MediaBrowser.Api.Playback.Hls
|
|||||||
// Add resolution params, if specified
|
// Add resolution params, if specified
|
||||||
if (!hasGraphicalSubs)
|
if (!hasGraphicalSubs)
|
||||||
{
|
{
|
||||||
args += GetOutputSizeParam(state, codec, CancellationToken.None);
|
args += GetOutputSizeParam(state, codec);
|
||||||
}
|
}
|
||||||
|
|
||||||
// This is for internal graphical subs
|
// This is for internal graphical subs
|
||||||
if (hasGraphicalSubs)
|
if (hasGraphicalSubs)
|
||||||
{
|
{
|
||||||
args += GetInternalGraphicalSubtitleParam(state, codec);
|
args += GetGraphicalSubtitleParam(state, codec);
|
||||||
}
|
}
|
||||||
|
|
||||||
return args;
|
return args;
|
||||||
|
@ -7,6 +7,7 @@ using MediaBrowser.Controller.Drawing;
|
|||||||
using MediaBrowser.Controller.Library;
|
using MediaBrowser.Controller.Library;
|
||||||
using MediaBrowser.Controller.LiveTv;
|
using MediaBrowser.Controller.LiveTv;
|
||||||
using MediaBrowser.Controller.MediaEncoding;
|
using MediaBrowser.Controller.MediaEncoding;
|
||||||
|
using MediaBrowser.Controller.Net;
|
||||||
using MediaBrowser.Model.IO;
|
using MediaBrowser.Model.IO;
|
||||||
using MediaBrowser.Model.MediaInfo;
|
using MediaBrowser.Model.MediaInfo;
|
||||||
using ServiceStack.Web;
|
using ServiceStack.Web;
|
||||||
@ -26,7 +27,8 @@ namespace MediaBrowser.Api.Playback.Progressive
|
|||||||
protected readonly IImageProcessor ImageProcessor;
|
protected readonly IImageProcessor ImageProcessor;
|
||||||
protected readonly IHttpClient HttpClient;
|
protected readonly IHttpClient HttpClient;
|
||||||
|
|
||||||
protected BaseProgressiveStreamingService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, ILiveTvManager liveTvManager, IDlnaManager dlnaManager, IChannelManager channelManager, ISubtitleEncoder subtitleEncoder, IImageProcessor imageProcessor, IHttpClient httpClient) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, fileSystem, liveTvManager, dlnaManager, channelManager, subtitleEncoder)
|
protected BaseProgressiveStreamingService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, ILiveTvManager liveTvManager, IDlnaManager dlnaManager, IChannelManager channelManager, ISubtitleEncoder subtitleEncoder, IImageProcessor imageProcessor, IHttpClient httpClient)
|
||||||
|
: base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, fileSystem, liveTvManager, dlnaManager, channelManager, subtitleEncoder)
|
||||||
{
|
{
|
||||||
ImageProcessor = imageProcessor;
|
ImageProcessor = imageProcessor;
|
||||||
HttpClient = httpClient;
|
HttpClient = httpClient;
|
||||||
@ -53,22 +55,22 @@ namespace MediaBrowser.Api.Playback.Progressive
|
|||||||
{
|
{
|
||||||
var videoCodec = state.VideoRequest.VideoCodec;
|
var videoCodec = state.VideoRequest.VideoCodec;
|
||||||
|
|
||||||
if (string.Equals(videoCodec, "h264", StringComparison.OrdinalIgnoreCase))
|
if (string.Equals(videoCodec, "h264", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
return ".ts";
|
return ".ts";
|
||||||
}
|
}
|
||||||
if (string.Equals(videoCodec, "theora", StringComparison.OrdinalIgnoreCase))
|
if (string.Equals(videoCodec, "theora", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
return ".ogv";
|
return ".ogv";
|
||||||
}
|
}
|
||||||
if (string.Equals(videoCodec, "vpx", StringComparison.OrdinalIgnoreCase))
|
if (string.Equals(videoCodec, "vpx", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
return ".webm";
|
return ".webm";
|
||||||
}
|
}
|
||||||
if (string.Equals(videoCodec, "wmv", StringComparison.OrdinalIgnoreCase))
|
if (string.Equals(videoCodec, "wmv", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
return ".asf";
|
return ".asf";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try to infer based on the desired audio codec
|
// Try to infer based on the desired audio codec
|
||||||
@ -114,7 +116,9 @@ namespace MediaBrowser.Api.Playback.Progressive
|
|||||||
/// <returns>Task.</returns>
|
/// <returns>Task.</returns>
|
||||||
protected object ProcessRequest(StreamRequest request, bool isHeadRequest)
|
protected object ProcessRequest(StreamRequest request, bool isHeadRequest)
|
||||||
{
|
{
|
||||||
var state = GetState(request, CancellationToken.None).Result;
|
var cancellationTokenSource = new CancellationTokenSource();
|
||||||
|
|
||||||
|
var state = GetState(request, cancellationTokenSource.Token).Result;
|
||||||
|
|
||||||
var responseHeaders = new Dictionary<string, string>();
|
var responseHeaders = new Dictionary<string, string>();
|
||||||
|
|
||||||
@ -123,13 +127,9 @@ namespace MediaBrowser.Api.Playback.Progressive
|
|||||||
{
|
{
|
||||||
AddDlnaHeaders(state, responseHeaders, true);
|
AddDlnaHeaders(state, responseHeaders, true);
|
||||||
|
|
||||||
try
|
using (state)
|
||||||
{
|
{
|
||||||
return GetStaticRemoteStreamResult(state, responseHeaders, isHeadRequest).Result;
|
return GetStaticRemoteStreamResult(state, responseHeaders, isHeadRequest, cancellationTokenSource).Result;
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
state.Dispose();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -151,13 +151,24 @@ namespace MediaBrowser.Api.Playback.Progressive
|
|||||||
{
|
{
|
||||||
var contentType = state.GetMimeType(state.MediaPath);
|
var contentType = state.GetMimeType(state.MediaPath);
|
||||||
|
|
||||||
try
|
using (state)
|
||||||
{
|
{
|
||||||
return ResultFactory.GetStaticFileResult(Request, state.MediaPath, contentType, FileShare.Read, responseHeaders, isHeadRequest);
|
var throttleLimit = state.InputBitrate.HasValue ? (state.InputBitrate.Value / 8) : 0;
|
||||||
}
|
|
||||||
finally
|
return ResultFactory.GetStaticFileResult(Request, new StaticFileResultOptions
|
||||||
{
|
{
|
||||||
state.Dispose();
|
ResponseHeaders = responseHeaders,
|
||||||
|
ContentType = contentType,
|
||||||
|
IsHeadRequest = isHeadRequest,
|
||||||
|
Path = state.MediaPath,
|
||||||
|
Throttle = request.Throttle,
|
||||||
|
|
||||||
|
// Pad by 20% to play it safe
|
||||||
|
ThrottleLimit = Convert.ToInt64(1.2 * throttleLimit),
|
||||||
|
|
||||||
|
// Three minutes
|
||||||
|
MinThrottlePosition = throttleLimit * 180
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -168,7 +179,13 @@ namespace MediaBrowser.Api.Playback.Progressive
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
return ResultFactory.GetStaticFileResult(Request, outputPath, contentType, FileShare.Read, responseHeaders, isHeadRequest);
|
return ResultFactory.GetStaticFileResult(Request, new StaticFileResultOptions
|
||||||
|
{
|
||||||
|
ResponseHeaders = responseHeaders,
|
||||||
|
ContentType = contentType,
|
||||||
|
IsHeadRequest = isHeadRequest,
|
||||||
|
Path = outputPath
|
||||||
|
});
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
@ -179,7 +196,7 @@ namespace MediaBrowser.Api.Playback.Progressive
|
|||||||
// Need to start ffmpeg
|
// Need to start ffmpeg
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
return GetStreamResult(state, responseHeaders, isHeadRequest).Result;
|
return GetStreamResult(state, responseHeaders, isHeadRequest, cancellationTokenSource).Result;
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
@ -195,8 +212,9 @@ namespace MediaBrowser.Api.Playback.Progressive
|
|||||||
/// <param name="state">The state.</param>
|
/// <param name="state">The state.</param>
|
||||||
/// <param name="responseHeaders">The response headers.</param>
|
/// <param name="responseHeaders">The response headers.</param>
|
||||||
/// <param name="isHeadRequest">if set to <c>true</c> [is head request].</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>
|
/// <returns>Task{System.Object}.</returns>
|
||||||
private async Task<object> GetStaticRemoteStreamResult(StreamState state, Dictionary<string, string> responseHeaders, bool isHeadRequest)
|
private async Task<object> GetStaticRemoteStreamResult(StreamState state, Dictionary<string, string> responseHeaders, bool isHeadRequest, CancellationTokenSource cancellationTokenSource)
|
||||||
{
|
{
|
||||||
string useragent = null;
|
string useragent = null;
|
||||||
state.RemoteHttpHeaders.TryGetValue("User-Agent", out useragent);
|
state.RemoteHttpHeaders.TryGetValue("User-Agent", out useragent);
|
||||||
@ -205,7 +223,8 @@ namespace MediaBrowser.Api.Playback.Progressive
|
|||||||
{
|
{
|
||||||
Url = state.MediaPath,
|
Url = state.MediaPath,
|
||||||
UserAgent = useragent,
|
UserAgent = useragent,
|
||||||
BufferContent = false
|
BufferContent = false,
|
||||||
|
CancellationToken = cancellationTokenSource.Token
|
||||||
};
|
};
|
||||||
|
|
||||||
var response = await HttpClient.GetResponse(options).ConfigureAwait(false);
|
var response = await HttpClient.GetResponse(options).ConfigureAwait(false);
|
||||||
@ -246,8 +265,9 @@ namespace MediaBrowser.Api.Playback.Progressive
|
|||||||
/// <param name="state">The state.</param>
|
/// <param name="state">The state.</param>
|
||||||
/// <param name="responseHeaders">The response headers.</param>
|
/// <param name="responseHeaders">The response headers.</param>
|
||||||
/// <param name="isHeadRequest">if set to <c>true</c> [is head request].</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>
|
/// <returns>Task{System.Object}.</returns>
|
||||||
private async Task<object> GetStreamResult(StreamState state, IDictionary<string, string> responseHeaders, bool isHeadRequest)
|
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
|
// Use the command line args with a dummy playlist path
|
||||||
var outputPath = state.OutputFilePath;
|
var outputPath = state.OutputFilePath;
|
||||||
@ -283,27 +303,36 @@ namespace MediaBrowser.Api.Playback.Progressive
|
|||||||
return streamResult;
|
return streamResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!File.Exists(outputPath))
|
await ApiEntryPoint.Instance.TranscodingStartLock.WaitAsync(cancellationTokenSource.Token).ConfigureAwait(false);
|
||||||
|
try
|
||||||
{
|
{
|
||||||
await StartFfMpeg(state, outputPath, new CancellationTokenSource()).ConfigureAwait(false);
|
if (!File.Exists(outputPath))
|
||||||
|
{
|
||||||
|
await StartFfMpeg(state, outputPath, cancellationTokenSource).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ApiEntryPoint.Instance.OnTranscodeBeginRequest(outputPath, TranscodingJobType.Progressive);
|
||||||
|
state.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
var job = ApiEntryPoint.Instance.GetTranscodingJob(outputPath, TranscodingJobType.Progressive);
|
||||||
|
var result = new ProgressiveStreamWriter(outputPath, Logger, FileSystem, job);
|
||||||
|
|
||||||
|
result.Options["Content-Type"] = contentType;
|
||||||
|
|
||||||
|
// Add the response headers to the result object
|
||||||
|
foreach (var item in responseHeaders)
|
||||||
|
{
|
||||||
|
result.Options[item.Key] = item.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
else
|
finally
|
||||||
{
|
{
|
||||||
ApiEntryPoint.Instance.OnTranscodeBeginRequest(outputPath, TranscodingJobType.Progressive);
|
ApiEntryPoint.Instance.TranscodingStartLock.Release();
|
||||||
state.Dispose();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var result = new ProgressiveStreamWriter(outputPath, Logger, FileSystem);
|
|
||||||
|
|
||||||
result.Options["Content-Type"] = contentType;
|
|
||||||
|
|
||||||
// Add the response headers to the result object
|
|
||||||
foreach (var item in responseHeaders)
|
|
||||||
{
|
|
||||||
result.Options[item.Key] = item.Value;
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
using MediaBrowser.Common.IO;
|
using System;
|
||||||
|
using MediaBrowser.Common.IO;
|
||||||
using MediaBrowser.Model.Logging;
|
using MediaBrowser.Model.Logging;
|
||||||
using ServiceStack.Web;
|
using ServiceStack.Web;
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
@ -13,6 +13,7 @@ namespace MediaBrowser.Api.Playback.Progressive
|
|||||||
private string Path { get; set; }
|
private string Path { get; set; }
|
||||||
private ILogger Logger { get; set; }
|
private ILogger Logger { get; set; }
|
||||||
private readonly IFileSystem _fileSystem;
|
private readonly IFileSystem _fileSystem;
|
||||||
|
private readonly TranscodingJob _job;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The _options
|
/// The _options
|
||||||
@ -33,11 +34,12 @@ namespace MediaBrowser.Api.Playback.Progressive
|
|||||||
/// <param name="path">The path.</param>
|
/// <param name="path">The path.</param>
|
||||||
/// <param name="logger">The logger.</param>
|
/// <param name="logger">The logger.</param>
|
||||||
/// <param name="fileSystem">The file system.</param>
|
/// <param name="fileSystem">The file system.</param>
|
||||||
public ProgressiveStreamWriter(string path, ILogger logger, IFileSystem fileSystem)
|
public ProgressiveStreamWriter(string path, ILogger logger, IFileSystem fileSystem, TranscodingJob job)
|
||||||
{
|
{
|
||||||
Path = path;
|
Path = path;
|
||||||
Logger = logger;
|
Logger = logger;
|
||||||
_fileSystem = fileSystem;
|
_fileSystem = fileSystem;
|
||||||
|
_job = job;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -60,11 +62,12 @@ namespace MediaBrowser.Api.Playback.Progressive
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await StreamFile(Path, responseStream).ConfigureAwait(false);
|
await new ProgressiveFileCopier(_fileSystem, _job)
|
||||||
|
.StreamFile(Path, responseStream).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
catch
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Logger.Error("Error streaming media. The client has most likely disconnected or transcoding has failed.");
|
Logger.ErrorException("Error streaming media. The client has most likely disconnected or transcoding has failed.", ex);
|
||||||
|
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
@ -73,14 +76,20 @@ namespace MediaBrowser.Api.Playback.Progressive
|
|||||||
ApiEntryPoint.Instance.OnTranscodeEndRequest(Path, TranscodingJobType.Progressive);
|
ApiEntryPoint.Instance.OnTranscodeEndRequest(Path, TranscodingJobType.Progressive);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
public class ProgressiveFileCopier
|
||||||
/// Streams the file.
|
{
|
||||||
/// </summary>
|
private readonly IFileSystem _fileSystem;
|
||||||
/// <param name="path">The path.</param>
|
private readonly TranscodingJob _job;
|
||||||
/// <param name="outputStream">The output stream.</param>
|
|
||||||
/// <returns>Task{System.Boolean}.</returns>
|
public ProgressiveFileCopier(IFileSystem fileSystem, TranscodingJob job)
|
||||||
private async Task StreamFile(string path, Stream outputStream)
|
{
|
||||||
|
_fileSystem = fileSystem;
|
||||||
|
_job = job;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task StreamFile(string path, Stream outputStream)
|
||||||
{
|
{
|
||||||
var eofCount = 0;
|
var eofCount = 0;
|
||||||
long position = 0;
|
long position = 0;
|
||||||
@ -99,7 +108,10 @@ namespace MediaBrowser.Api.Playback.Progressive
|
|||||||
|
|
||||||
if (bytesRead == 0)
|
if (bytesRead == 0)
|
||||||
{
|
{
|
||||||
eofCount++;
|
if (_job == null || _job.HasExited)
|
||||||
|
{
|
||||||
|
eofCount++;
|
||||||
|
}
|
||||||
await Task.Delay(100).ConfigureAwait(false);
|
await Task.Delay(100).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -11,7 +11,6 @@ using MediaBrowser.Model.IO;
|
|||||||
using ServiceStack;
|
using ServiceStack;
|
||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Threading;
|
|
||||||
|
|
||||||
namespace MediaBrowser.Api.Playback.Progressive
|
namespace MediaBrowser.Api.Playback.Progressive
|
||||||
{
|
{
|
||||||
@ -144,7 +143,8 @@ namespace MediaBrowser.Api.Playback.Progressive
|
|||||||
return state.VideoStream != null && IsH264(state.VideoStream) ? args + " -bsf h264_mp4toannexb" : args;
|
return state.VideoStream != null && IsH264(state.VideoStream) ? args + " -bsf h264_mp4toannexb" : args;
|
||||||
}
|
}
|
||||||
|
|
||||||
const string keyFrameArg = " -force_key_frames expr:if(isnan(prev_forced_t),gte(t,.1),gte(t,prev_forced_t+5))";
|
var keyFrameArg = string.Format(" -force_key_frames expr:gte(t,n_forced*{0})",
|
||||||
|
5.ToString(UsCulture));
|
||||||
|
|
||||||
args += keyFrameArg;
|
args += keyFrameArg;
|
||||||
|
|
||||||
@ -153,7 +153,7 @@ namespace MediaBrowser.Api.Playback.Progressive
|
|||||||
// Add resolution params, if specified
|
// Add resolution params, if specified
|
||||||
if (!hasGraphicalSubs)
|
if (!hasGraphicalSubs)
|
||||||
{
|
{
|
||||||
args += GetOutputSizeParam(state, codec, CancellationToken.None);
|
args += GetOutputSizeParam(state, codec);
|
||||||
}
|
}
|
||||||
|
|
||||||
var qualityParam = GetVideoQualityParam(state, codec, false);
|
var qualityParam = GetVideoQualityParam(state, codec, false);
|
||||||
@ -166,7 +166,7 @@ namespace MediaBrowser.Api.Playback.Progressive
|
|||||||
// This is for internal graphical subs
|
// This is for internal graphical subs
|
||||||
if (hasGraphicalSubs)
|
if (hasGraphicalSubs)
|
||||||
{
|
{
|
||||||
args += GetInternalGraphicalSubtitleParam(state, codec);
|
args += GetGraphicalSubtitleParam(state, codec);
|
||||||
}
|
}
|
||||||
|
|
||||||
return args;
|
return args;
|
||||||
@ -210,7 +210,7 @@ namespace MediaBrowser.Api.Playback.Progressive
|
|||||||
args += " -ab " + bitrate.Value.ToString(UsCulture);
|
args += " -ab " + bitrate.Value.ToString(UsCulture);
|
||||||
}
|
}
|
||||||
|
|
||||||
args += " " + GetAudioFilterParam(state, true);
|
args += " " + GetAudioFilterParam(state, false);
|
||||||
|
|
||||||
return args;
|
return args;
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
using ServiceStack;
|
using MediaBrowser.Model.Dlna;
|
||||||
|
using ServiceStack;
|
||||||
|
|
||||||
namespace MediaBrowser.Api.Playback
|
namespace MediaBrowser.Api.Playback
|
||||||
{
|
{
|
||||||
@ -69,6 +70,8 @@ namespace MediaBrowser.Api.Playback
|
|||||||
public string DeviceProfileId { get; set; }
|
public string DeviceProfileId { get; set; }
|
||||||
|
|
||||||
public string Params { get; set; }
|
public string Params { get; set; }
|
||||||
|
|
||||||
|
public bool Throttle { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class VideoStreamRequest : StreamRequest
|
public class VideoStreamRequest : StreamRequest
|
||||||
@ -160,6 +163,9 @@ namespace MediaBrowser.Api.Playback
|
|||||||
[ApiMember(Name = "Level", Description = "Optional. Specify a level for the h264 profile, e.g. 3, 3.1.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
|
[ApiMember(Name = "Level", Description = "Optional. Specify a level for the h264 profile, e.g. 3, 3.1.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
|
||||||
public string Level { get; set; }
|
public string Level { get; set; }
|
||||||
|
|
||||||
|
[ApiMember(Name = "SubtitleDeliveryMethod", Description = "Optional. Specify the subtitle delivery method.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
|
||||||
|
public SubtitleDeliveryMethod SubtitleMethod { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a value indicating whether this instance has fixed resolution.
|
/// Gets a value indicating whether this instance has fixed resolution.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -38,6 +38,8 @@ namespace MediaBrowser.Api.Playback
|
|||||||
|
|
||||||
public string InputContainer { get; set; }
|
public string InputContainer { get; set; }
|
||||||
|
|
||||||
|
public List<MediaStream> AllMediaStreams { get; set; }
|
||||||
|
|
||||||
public MediaStream AudioStream { get; set; }
|
public MediaStream AudioStream { get; set; }
|
||||||
public MediaStream VideoStream { get; set; }
|
public MediaStream VideoStream { get; set; }
|
||||||
public MediaStream SubtitleStream { get; set; }
|
public MediaStream SubtitleStream { get; set; }
|
||||||
@ -66,6 +68,8 @@ namespace MediaBrowser.Api.Playback
|
|||||||
|
|
||||||
public long? RunTimeTicks;
|
public long? RunTimeTicks;
|
||||||
|
|
||||||
|
public long? InputBitrate { get; set; }
|
||||||
|
|
||||||
public string OutputAudioSync = "1";
|
public string OutputAudioSync = "1";
|
||||||
public string OutputVideoSync = "vfr";
|
public string OutputVideoSync = "vfr";
|
||||||
|
|
||||||
@ -78,6 +82,7 @@ namespace MediaBrowser.Api.Playback
|
|||||||
SupportedAudioCodecs = new List<string>();
|
SupportedAudioCodecs = new List<string>();
|
||||||
PlayableStreamFileNames = new List<string>();
|
PlayableStreamFileNames = new List<string>();
|
||||||
RemoteHttpHeaders = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
RemoteHttpHeaders = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
AllMediaStreams = new List<MediaStream>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public string InputAudioSync { get; set; }
|
public string InputAudioSync { get; set; }
|
||||||
@ -94,6 +99,10 @@ namespace MediaBrowser.Api.Playback
|
|||||||
public bool EnableMpegtsM2TsMode { get; set; }
|
public bool EnableMpegtsM2TsMode { get; set; }
|
||||||
public TranscodeSeekInfo TranscodeSeekInfo { get; set; }
|
public TranscodeSeekInfo TranscodeSeekInfo { get; set; }
|
||||||
|
|
||||||
|
public long? EncodingDurationTicks { get; set; }
|
||||||
|
|
||||||
|
public string ItemType { get; set; }
|
||||||
|
|
||||||
public string GetMimeType(string outputPath)
|
public string GetMimeType(string outputPath)
|
||||||
{
|
{
|
||||||
if (!string.IsNullOrEmpty(MimeType))
|
if (!string.IsNullOrEmpty(MimeType))
|
||||||
|
176
MediaBrowser.Api/PlaylistService.cs
Normal file
176
MediaBrowser.Api/PlaylistService.cs
Normal file
@ -0,0 +1,176 @@
|
|||||||
|
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 ServiceStack;
|
||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace MediaBrowser.Api
|
||||||
|
{
|
||||||
|
[Route("/Playlists", "POST", Summary = "Creates a new playlist")]
|
||||||
|
public class CreatePlaylist : IReturn<PlaylistCreationResult>
|
||||||
|
{
|
||||||
|
[ApiMember(Name = "Name", Description = "The name of the new playlist.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")]
|
||||||
|
public string Name { get; set; }
|
||||||
|
|
||||||
|
[ApiMember(Name = "Ids", Description = "Item Ids to add to the playlist", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST", AllowMultiple = true)]
|
||||||
|
public string Ids { get; set; }
|
||||||
|
|
||||||
|
[ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
|
||||||
|
public string UserId { get; set; }
|
||||||
|
|
||||||
|
[ApiMember(Name = "MediaType", Description = "The playlist media type", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
|
||||||
|
public string MediaType { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
[Route("/Playlists/{Id}/Items", "POST", Summary = "Adds items to a playlist")]
|
||||||
|
public class AddToPlaylist : IReturnVoid
|
||||||
|
{
|
||||||
|
[ApiMember(Name = "Ids", Description = "Item id, comma delimited", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")]
|
||||||
|
public string Ids { get; set; }
|
||||||
|
|
||||||
|
[ApiMember(Name = "Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
|
||||||
|
public string Id { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the user id.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The user id.</value>
|
||||||
|
[ApiMember(Name = "UserId", Description = "User Id", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
|
||||||
|
public string UserId { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
[Route("/Playlists/{Id}/Items", "DELETE", Summary = "Removes items from a playlist")]
|
||||||
|
public class RemoveFromPlaylist : IReturnVoid
|
||||||
|
{
|
||||||
|
[ApiMember(Name = "Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "DELETE")]
|
||||||
|
public string Id { get; set; }
|
||||||
|
|
||||||
|
[ApiMember(Name = "EntryIds", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "DELETE")]
|
||||||
|
public string EntryIds { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
[Route("/Playlists/{Id}/Items", "GET", Summary = "Gets the original items of a playlist")]
|
||||||
|
public class GetPlaylistItems : IReturn<QueryResult<BaseItemDto>>, IHasItemFields
|
||||||
|
{
|
||||||
|
[ApiMember(Name = "Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "DELETE")]
|
||||||
|
public string Id { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the user id.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The user id.</value>
|
||||||
|
[ApiMember(Name = "UserId", Description = "User Id", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
|
||||||
|
public string UserId { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Skips over a given number of items within the results. Use for paging.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The start index.</value>
|
||||||
|
[ApiMember(Name = "StartIndex", Description = "Optional. The record index to start at. All items with a lower index will be dropped from the results.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
|
||||||
|
public int? StartIndex { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The maximum number of items to return
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The limit.</value>
|
||||||
|
[ApiMember(Name = "Limit", Description = "Optional. The maximum number of records to return", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
|
||||||
|
public int? Limit { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Fields to return within the items, in addition to basic information
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The fields.</value>
|
||||||
|
[ApiMember(Name = "Fields", Description = "Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimeted. Options: Budget, Chapters, CriticRatingSummary, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
|
||||||
|
public string Fields { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
[Authenticated]
|
||||||
|
public class PlaylistService : BaseApiService
|
||||||
|
{
|
||||||
|
private readonly IPlaylistManager _playlistManager;
|
||||||
|
private readonly IDtoService _dtoService;
|
||||||
|
private readonly IUserManager _userManager;
|
||||||
|
private readonly ILibraryManager _libraryManager;
|
||||||
|
|
||||||
|
public PlaylistService(IDtoService dtoService, IPlaylistManager playlistManager, IUserManager userManager, ILibraryManager libraryManager)
|
||||||
|
{
|
||||||
|
_dtoService = dtoService;
|
||||||
|
_playlistManager = playlistManager;
|
||||||
|
_userManager = userManager;
|
||||||
|
_libraryManager = libraryManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<object> Post(CreatePlaylist request)
|
||||||
|
{
|
||||||
|
var result = await _playlistManager.CreatePlaylist(new PlaylistCreationRequest
|
||||||
|
{
|
||||||
|
Name = request.Name,
|
||||||
|
ItemIdList = (request.Ids ?? string.Empty).Split(',').Where(i => !string.IsNullOrWhiteSpace(i)).ToList(),
|
||||||
|
UserId = request.UserId,
|
||||||
|
MediaType = request.MediaType
|
||||||
|
|
||||||
|
}).ConfigureAwait(false);
|
||||||
|
|
||||||
|
return ToOptimizedResult(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Post(AddToPlaylist request)
|
||||||
|
{
|
||||||
|
var task = _playlistManager.AddToPlaylist(request.Id, request.Ids.Split(','), request.UserId);
|
||||||
|
|
||||||
|
Task.WaitAll(task);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Delete(RemoveFromPlaylist request)
|
||||||
|
{
|
||||||
|
var task = _playlistManager.RemoveFromPlaylist(request.Id, request.EntryIds.Split(','));
|
||||||
|
|
||||||
|
Task.WaitAll(task);
|
||||||
|
}
|
||||||
|
|
||||||
|
public object Get(GetPlaylistItems request)
|
||||||
|
{
|
||||||
|
var playlist = (Playlist)_libraryManager.GetItemById(request.Id);
|
||||||
|
var user = !string.IsNullOrWhiteSpace(request.UserId) ? _userManager.GetUserById(new Guid(request.UserId)) : null;
|
||||||
|
|
||||||
|
var items = playlist.GetManageableItems().ToArray();
|
||||||
|
|
||||||
|
var count = items.Length;
|
||||||
|
|
||||||
|
if (request.StartIndex.HasValue)
|
||||||
|
{
|
||||||
|
items = items.Skip(request.StartIndex.Value).ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (request.Limit.HasValue)
|
||||||
|
{
|
||||||
|
items = items.Take(request.Limit.Value).ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
var dtos = items
|
||||||
|
.Select(i => _dtoService.GetBaseItemDto(i.Item2, request.GetItemFields().ToList(), user))
|
||||||
|
.ToArray();
|
||||||
|
|
||||||
|
var index = 0;
|
||||||
|
foreach (var item in dtos)
|
||||||
|
{
|
||||||
|
item.PlaylistItemId = items[index].Item1.Id;
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
|
||||||
|
var result = new ItemsResult
|
||||||
|
{
|
||||||
|
Items = dtos,
|
||||||
|
TotalRecordCount = count
|
||||||
|
};
|
||||||
|
|
||||||
|
return ToOptimizedResult(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -2,6 +2,7 @@
|
|||||||
using MediaBrowser.Common.Extensions;
|
using MediaBrowser.Common.Extensions;
|
||||||
using MediaBrowser.Common.Security;
|
using MediaBrowser.Common.Security;
|
||||||
using MediaBrowser.Common.Updates;
|
using MediaBrowser.Common.Updates;
|
||||||
|
using MediaBrowser.Controller.Net;
|
||||||
using MediaBrowser.Model.Entities;
|
using MediaBrowser.Model.Entities;
|
||||||
using MediaBrowser.Model.Plugins;
|
using MediaBrowser.Model.Plugins;
|
||||||
using MediaBrowser.Model.Serialization;
|
using MediaBrowser.Model.Serialization;
|
||||||
@ -100,6 +101,7 @@ namespace MediaBrowser.Api
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Class PluginsService
|
/// Class PluginsService
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[Authenticated]
|
||||||
public class PluginService : BaseApiService
|
public class PluginService : BaseApiService
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
using MediaBrowser.Common.Extensions;
|
using MediaBrowser.Common.Extensions;
|
||||||
using MediaBrowser.Common.ScheduledTasks;
|
using MediaBrowser.Common.ScheduledTasks;
|
||||||
|
using MediaBrowser.Controller.Net;
|
||||||
using MediaBrowser.Model.Tasks;
|
using MediaBrowser.Model.Tasks;
|
||||||
using ServiceStack;
|
using ServiceStack;
|
||||||
using ServiceStack.Text.Controller;
|
using ServiceStack.Text.Controller;
|
||||||
@ -78,6 +79,7 @@ namespace MediaBrowser.Api.ScheduledTasks
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Class ScheduledTasksService
|
/// Class ScheduledTasksService
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[Authenticated]
|
||||||
public class ScheduledTaskService : BaseApiService
|
public class ScheduledTaskService : BaseApiService
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -4,6 +4,7 @@ using MediaBrowser.Controller.Entities;
|
|||||||
using MediaBrowser.Controller.Entities.Audio;
|
using MediaBrowser.Controller.Entities.Audio;
|
||||||
using MediaBrowser.Controller.Entities.TV;
|
using MediaBrowser.Controller.Entities.TV;
|
||||||
using MediaBrowser.Controller.Library;
|
using MediaBrowser.Controller.Library;
|
||||||
|
using MediaBrowser.Controller.Net;
|
||||||
using MediaBrowser.Model.Entities;
|
using MediaBrowser.Model.Entities;
|
||||||
using MediaBrowser.Model.Search;
|
using MediaBrowser.Model.Search;
|
||||||
using ServiceStack;
|
using ServiceStack;
|
||||||
@ -79,6 +80,7 @@ namespace MediaBrowser.Api
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Class SearchService
|
/// Class SearchService
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[Authenticated]
|
||||||
public class SearchService : BaseApiService
|
public class SearchService : BaseApiService
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -109,9 +111,9 @@ namespace MediaBrowser.Api
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="request">The request.</param>
|
/// <param name="request">The request.</param>
|
||||||
/// <returns>System.Object.</returns>
|
/// <returns>System.Object.</returns>
|
||||||
public object Get(GetSearchHints request)
|
public async Task<object> Get(GetSearchHints request)
|
||||||
{
|
{
|
||||||
var result = GetSearchHintsAsync(request).Result;
|
var result = await GetSearchHintsAsync(request).ConfigureAwait(false);
|
||||||
|
|
||||||
return ToOptimizedSerializedResultUsingCache(result);
|
return ToOptimizedSerializedResultUsingCache(result);
|
||||||
}
|
}
|
||||||
@ -192,14 +194,14 @@ namespace MediaBrowser.Api
|
|||||||
{
|
{
|
||||||
result.Series = season.Series.Name;
|
result.Series = season.Series.Name;
|
||||||
|
|
||||||
result.EpisodeCount = season.GetRecursiveChildren(i => i is Episode).Count;
|
result.EpisodeCount = season.GetRecursiveChildren().Count(i => i is Episode);
|
||||||
}
|
}
|
||||||
|
|
||||||
var series = item as Series;
|
var series = item as Series;
|
||||||
|
|
||||||
if (series != null)
|
if (series != null)
|
||||||
{
|
{
|
||||||
result.EpisodeCount = series.GetRecursiveChildren(i => i is Episode).Count;
|
result.EpisodeCount = series.GetRecursiveChildren().Count(i => i is Episode);
|
||||||
}
|
}
|
||||||
|
|
||||||
var album = item as MusicAlbum;
|
var album = item as MusicAlbum;
|
||||||
|
@ -7,7 +7,7 @@ using System.Collections.Generic;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace MediaBrowser.Api.WebSocket
|
namespace MediaBrowser.Api.Session
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Class SessionInfoWebSocketListener
|
/// Class SessionInfoWebSocketListener
|
@ -1,4 +1,6 @@
|
|||||||
using MediaBrowser.Controller.Library;
|
using MediaBrowser.Controller.Library;
|
||||||
|
using MediaBrowser.Controller.Net;
|
||||||
|
using MediaBrowser.Controller.Security;
|
||||||
using MediaBrowser.Controller.Session;
|
using MediaBrowser.Controller.Session;
|
||||||
using MediaBrowser.Model.Session;
|
using MediaBrowser.Model.Session;
|
||||||
using ServiceStack;
|
using ServiceStack;
|
||||||
@ -8,12 +10,13 @@ using System.Linq;
|
|||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace MediaBrowser.Api
|
namespace MediaBrowser.Api.Session
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Class GetSessions
|
/// Class GetSessions
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Route("/Sessions", "GET", Summary = "Gets a list of sessions")]
|
[Route("/Sessions", "GET", Summary = "Gets a list of sessions")]
|
||||||
|
[Authenticated]
|
||||||
public class GetSessions : IReturn<List<SessionInfoDto>>
|
public class GetSessions : IReturn<List<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")]
|
[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")]
|
||||||
@ -27,6 +30,7 @@ namespace MediaBrowser.Api
|
|||||||
/// Class DisplayContent
|
/// Class DisplayContent
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Route("/Sessions/{Id}/Viewing", "POST", Summary = "Instructs a session to browse to an item or view")]
|
[Route("/Sessions/{Id}/Viewing", "POST", Summary = "Instructs a session to browse to an item or view")]
|
||||||
|
[Authenticated]
|
||||||
public class DisplayContent : IReturnVoid
|
public class DisplayContent : IReturnVoid
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -59,6 +63,7 @@ namespace MediaBrowser.Api
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Route("/Sessions/{Id}/Playing", "POST", Summary = "Instructs a session to play an item")]
|
[Route("/Sessions/{Id}/Playing", "POST", Summary = "Instructs a session to play an item")]
|
||||||
|
[Authenticated]
|
||||||
public class Play : IReturnVoid
|
public class Play : IReturnVoid
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -91,6 +96,7 @@ namespace MediaBrowser.Api
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Route("/Sessions/{Id}/Playing/{Command}", "POST", Summary = "Issues a playstate command to a client")]
|
[Route("/Sessions/{Id}/Playing/{Command}", "POST", Summary = "Issues a playstate command to a client")]
|
||||||
|
[Authenticated]
|
||||||
public class SendPlaystateCommand : IReturnVoid
|
public class SendPlaystateCommand : IReturnVoid
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -115,6 +121,7 @@ namespace MediaBrowser.Api
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Route("/Sessions/{Id}/System/{Command}", "POST", Summary = "Issues a system command to a client")]
|
[Route("/Sessions/{Id}/System/{Command}", "POST", Summary = "Issues a system command to a client")]
|
||||||
|
[Authenticated]
|
||||||
public class SendSystemCommand : IReturnVoid
|
public class SendSystemCommand : IReturnVoid
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -133,6 +140,7 @@ namespace MediaBrowser.Api
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Route("/Sessions/{Id}/Command/{Command}", "POST", Summary = "Issues a system command to a client")]
|
[Route("/Sessions/{Id}/Command/{Command}", "POST", Summary = "Issues a system command to a client")]
|
||||||
|
[Authenticated]
|
||||||
public class SendGeneralCommand : IReturnVoid
|
public class SendGeneralCommand : IReturnVoid
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -151,6 +159,7 @@ namespace MediaBrowser.Api
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Route("/Sessions/{Id}/Command", "POST", Summary = "Issues a system command to a client")]
|
[Route("/Sessions/{Id}/Command", "POST", Summary = "Issues a system command to a client")]
|
||||||
|
[Authenticated]
|
||||||
public class SendFullGeneralCommand : GeneralCommand, IReturnVoid
|
public class SendFullGeneralCommand : GeneralCommand, IReturnVoid
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -162,6 +171,7 @@ namespace MediaBrowser.Api
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Route("/Sessions/{Id}/Message", "POST", Summary = "Issues a command to a client to display a message to the user")]
|
[Route("/Sessions/{Id}/Message", "POST", Summary = "Issues a command to a client to display a message to the user")]
|
||||||
|
[Authenticated]
|
||||||
public class SendMessageCommand : IReturnVoid
|
public class SendMessageCommand : IReturnVoid
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -182,6 +192,7 @@ namespace MediaBrowser.Api
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Route("/Sessions/{Id}/Users/{UserId}", "POST", Summary = "Adds an additional user to a session")]
|
[Route("/Sessions/{Id}/Users/{UserId}", "POST", Summary = "Adds an additional user to a session")]
|
||||||
|
[Authenticated]
|
||||||
public class AddUserToSession : IReturnVoid
|
public class AddUserToSession : IReturnVoid
|
||||||
{
|
{
|
||||||
[ApiMember(Name = "Id", Description = "Session Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
|
[ApiMember(Name = "Id", Description = "Session Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
|
||||||
@ -192,6 +203,7 @@ namespace MediaBrowser.Api
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Route("/Sessions/{Id}/Users/{UserId}", "DELETE", Summary = "Removes an additional user from a session")]
|
[Route("/Sessions/{Id}/Users/{UserId}", "DELETE", Summary = "Removes an additional user from a session")]
|
||||||
|
[Authenticated]
|
||||||
public class RemoveUserFromSession : IReturnVoid
|
public class RemoveUserFromSession : IReturnVoid
|
||||||
{
|
{
|
||||||
[ApiMember(Name = "Id", Description = "Session Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
|
[ApiMember(Name = "Id", Description = "Session Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
|
||||||
@ -202,7 +214,7 @@ namespace MediaBrowser.Api
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Route("/Sessions/Capabilities", "POST", Summary = "Updates capabilities for a device")]
|
[Route("/Sessions/Capabilities", "POST", Summary = "Updates capabilities for a device")]
|
||||||
[Route("/Sessions/{Id}/Capabilities", "POST", Summary = "Updates capabilities for a device")]
|
[Authenticated]
|
||||||
public class PostCapabilities : IReturnVoid
|
public class PostCapabilities : IReturnVoid
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -225,6 +237,30 @@ namespace MediaBrowser.Api
|
|||||||
public bool SupportsMediaControl { get; set; }
|
public bool SupportsMediaControl { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Route("/Sessions/Logout", "POST", Summary = "Reports that a session has ended")]
|
||||||
|
public class ReportSessionEnded : IReturnVoid
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
[Route("/Auth/Keys", "GET")]
|
||||||
|
public class GetApiKeys
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
[Route("/Auth/Keys/{Key}", "DELETE")]
|
||||||
|
public class RevokeKey
|
||||||
|
{
|
||||||
|
[ApiMember(Name = "Key", Description = "Auth Key", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
|
||||||
|
public string Key { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
[Route("/Auth/Keys", "POST")]
|
||||||
|
public class CreateKey
|
||||||
|
{
|
||||||
|
[ApiMember(Name = "App", Description = "App", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")]
|
||||||
|
public string App { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Class SessionsService
|
/// Class SessionsService
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -236,16 +272,60 @@ namespace MediaBrowser.Api
|
|||||||
private readonly ISessionManager _sessionManager;
|
private readonly ISessionManager _sessionManager;
|
||||||
|
|
||||||
private readonly IUserManager _userManager;
|
private readonly IUserManager _userManager;
|
||||||
|
private readonly IAuthorizationContext _authContext;
|
||||||
|
private readonly IAuthenticationRepository _authRepo;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="SessionsService" /> class.
|
/// Initializes a new instance of the <see cref="SessionsService" /> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="sessionManager">The session manager.</param>
|
/// <param name="sessionManager">The session manager.</param>
|
||||||
/// <param name="userManager">The user manager.</param>
|
/// <param name="userManager">The user manager.</param>
|
||||||
public SessionsService(ISessionManager sessionManager, IUserManager userManager)
|
/// <param name="authContext">The authentication context.</param>
|
||||||
|
/// <param name="authRepo">The authentication repo.</param>
|
||||||
|
public SessionsService(ISessionManager sessionManager, IUserManager userManager, IAuthorizationContext authContext, IAuthenticationRepository authRepo)
|
||||||
{
|
{
|
||||||
_sessionManager = sessionManager;
|
_sessionManager = sessionManager;
|
||||||
_userManager = userManager;
|
_userManager = userManager;
|
||||||
|
_authContext = authContext;
|
||||||
|
_authRepo = authRepo;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Delete(RevokeKey request)
|
||||||
|
{
|
||||||
|
var task = _sessionManager.RevokeToken(request.Key);
|
||||||
|
|
||||||
|
Task.WaitAll(task);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Post(CreateKey request)
|
||||||
|
{
|
||||||
|
var task = _authRepo.Create(new AuthenticationInfo
|
||||||
|
{
|
||||||
|
AppName = request.App,
|
||||||
|
IsActive = true,
|
||||||
|
AccessToken = Guid.NewGuid().ToString("N"),
|
||||||
|
DateCreated = DateTime.UtcNow
|
||||||
|
|
||||||
|
}, CancellationToken.None);
|
||||||
|
|
||||||
|
Task.WaitAll(task);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Post(ReportSessionEnded request)
|
||||||
|
{
|
||||||
|
var auth = _authContext.GetAuthorizationInfo(Request);
|
||||||
|
|
||||||
|
_sessionManager.Logout(auth.Token);
|
||||||
|
}
|
||||||
|
|
||||||
|
public object Get(GetApiKeys request)
|
||||||
|
{
|
||||||
|
var result = _authRepo.Get(new AuthenticationInfoQuery
|
||||||
|
{
|
||||||
|
IsActive = true
|
||||||
|
});
|
||||||
|
|
||||||
|
return ToOptimizedResult(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -315,21 +395,24 @@ namespace MediaBrowser.Api
|
|||||||
public void Post(SendSystemCommand request)
|
public void Post(SendSystemCommand request)
|
||||||
{
|
{
|
||||||
GeneralCommandType commandType;
|
GeneralCommandType commandType;
|
||||||
|
var name = request.Command;
|
||||||
|
|
||||||
if (Enum.TryParse(request.Command, true, out commandType))
|
if (Enum.TryParse(name, true, out commandType))
|
||||||
{
|
{
|
||||||
var currentSession = GetSession();
|
name = commandType.ToString();
|
||||||
|
|
||||||
var command = new GeneralCommand
|
|
||||||
{
|
|
||||||
Name = commandType.ToString(),
|
|
||||||
ControllingUserId = currentSession.UserId.HasValue ? currentSession.UserId.Value.ToString("N") : null
|
|
||||||
};
|
|
||||||
|
|
||||||
var task = _sessionManager.SendGeneralCommand(currentSession.Id, request.Id, command, CancellationToken.None);
|
|
||||||
|
|
||||||
Task.WaitAll(task);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var currentSession = GetSession();
|
||||||
|
|
||||||
|
var command = new GeneralCommand
|
||||||
|
{
|
||||||
|
Name = name,
|
||||||
|
ControllingUserId = currentSession.UserId.HasValue ? currentSession.UserId.Value.ToString("N") : null
|
||||||
|
};
|
||||||
|
|
||||||
|
var task = _sessionManager.SendGeneralCommand(currentSession.Id, request.Id, command, CancellationToken.None);
|
||||||
|
|
||||||
|
Task.WaitAll(task);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -422,14 +505,5 @@ namespace MediaBrowser.Api
|
|||||||
MessageCallbackUrl = request.MessageCallbackUrl
|
MessageCallbackUrl = request.MessageCallbackUrl
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private SessionInfo GetSession()
|
|
||||||
{
|
|
||||||
var auth = AuthorizationRequestFilterAttribute.GetAuthorization(Request);
|
|
||||||
|
|
||||||
return _sessionManager.Sessions.First(i => string.Equals(i.DeviceId, auth.DeviceId) &&
|
|
||||||
string.Equals(i.Client, auth.Client) &&
|
|
||||||
string.Equals(i.ApplicationVersion, auth.Version));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -78,8 +78,8 @@ namespace MediaBrowser.Api
|
|||||||
var fields = request.GetItemFields().ToList();
|
var fields = request.GetItemFields().ToList();
|
||||||
|
|
||||||
var inputItems = user == null
|
var inputItems = user == null
|
||||||
? libraryManager.RootFolder.GetRecursiveChildren(i => i.Id != item.Id)
|
? libraryManager.RootFolder.GetRecursiveChildren().Where(i => i.Id != item.Id)
|
||||||
: user.RootFolder.GetRecursiveChildren(user, i => i.Id != item.Id);
|
: user.RootFolder.GetRecursiveChildren(user).Where(i => i.Id != item.Id);
|
||||||
|
|
||||||
var items = GetSimilaritems(item, inputItems.Where(includeInSearch), getSimilarityScore)
|
var items = GetSimilaritems(item, inputItems.Where(includeInSearch), getSimilarityScore)
|
||||||
.ToList();
|
.ToList();
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
using MediaBrowser.Controller.Entities;
|
using MediaBrowser.Controller.Entities;
|
||||||
using MediaBrowser.Controller.Library;
|
using MediaBrowser.Controller.Library;
|
||||||
using MediaBrowser.Controller.MediaEncoding;
|
using MediaBrowser.Controller.MediaEncoding;
|
||||||
|
using MediaBrowser.Controller.Net;
|
||||||
using MediaBrowser.Controller.Providers;
|
using MediaBrowser.Controller.Providers;
|
||||||
using MediaBrowser.Controller.Subtitles;
|
using MediaBrowser.Controller.Subtitles;
|
||||||
using MediaBrowser.Model.Entities;
|
using MediaBrowser.Model.Entities;
|
||||||
@ -8,14 +9,70 @@ using MediaBrowser.Model.Providers;
|
|||||||
using ServiceStack;
|
using ServiceStack;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace MediaBrowser.Api.Library
|
namespace MediaBrowser.Api.Subtitles
|
||||||
{
|
{
|
||||||
[Route("/Videos/{Id}/{MediaSourceId}/Subtitles/{Index}/Stream.{Format}", "GET", Summary = "Gets subtitles in a specified format (vtt).")]
|
[Route("/Videos/{Id}/Subtitles/{Index}", "DELETE", Summary = "Deletes an external subtitle file")]
|
||||||
|
[Authenticated]
|
||||||
|
public class DeleteSubtitle
|
||||||
|
{
|
||||||
|
/// <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 = "Index", Description = "The subtitle stream index", IsRequired = true, DataType = "int", ParameterType = "path", Verb = "DELETE")]
|
||||||
|
public int Index { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
[Route("/Items/{Id}/RemoteSearch/Subtitles/{Language}", "GET")]
|
||||||
|
[Authenticated]
|
||||||
|
public class SearchRemoteSubtitles : IReturn<List<RemoteSubtitleInfo>>
|
||||||
|
{
|
||||||
|
[ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
|
||||||
|
public string Id { get; set; }
|
||||||
|
|
||||||
|
[ApiMember(Name = "Language", Description = "Language", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
|
||||||
|
public string Language { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
[Route("/Items/{Id}/RemoteSearch/Subtitles/Providers", "GET")]
|
||||||
|
[Authenticated]
|
||||||
|
public class GetSubtitleProviders : IReturn<List<SubtitleProviderInfo>>
|
||||||
|
{
|
||||||
|
[ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
|
||||||
|
public string Id { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
[Route("/Items/{Id}/RemoteSearch/Subtitles/{SubtitleId}", "POST")]
|
||||||
|
[Authenticated]
|
||||||
|
public class DownloadRemoteSubtitles : IReturnVoid
|
||||||
|
{
|
||||||
|
[ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
|
||||||
|
public string Id { get; set; }
|
||||||
|
|
||||||
|
[ApiMember(Name = "SubtitleId", Description = "SubtitleId", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
|
||||||
|
public string SubtitleId { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
[Route("/Providers/Subtitles/Subtitles/{Id}", "GET")]
|
||||||
|
[Authenticated]
|
||||||
|
public class GetRemoteSubtitles : IReturnVoid
|
||||||
|
{
|
||||||
|
[ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
|
||||||
|
public string Id { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
[Route("/Videos/{Id}/{MediaSourceId}/Subtitles/{Index}/Stream.{Format}", "GET", Summary = "Gets subtitles in a specified format.")]
|
||||||
|
[Route("/Videos/{Id}/{MediaSourceId}/Subtitles/{Index}/{StartPositionTicks}/Stream.{Format}", "GET", Summary = "Gets subtitles in a specified format.")]
|
||||||
public class GetSubtitle
|
public class GetSubtitle
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -36,54 +93,29 @@ namespace MediaBrowser.Api.Library
|
|||||||
|
|
||||||
[ApiMember(Name = "StartPositionTicks", Description = "StartPositionTicks", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
|
[ApiMember(Name = "StartPositionTicks", Description = "StartPositionTicks", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
|
||||||
public long StartPositionTicks { get; set; }
|
public long StartPositionTicks { get; set; }
|
||||||
|
|
||||||
|
[ApiMember(Name = "EndPositionTicks", Description = "EndPositionTicks", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
|
||||||
|
public long? EndPositionTicks { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
[Route("/Videos/{Id}/Subtitles/{Index}", "DELETE", Summary = "Deletes an external subtitle file")]
|
[Route("/Videos/{Id}/{MediaSourceId}/Subtitles/{Index}/subtitles.m3u8", "GET", Summary = "Gets an HLS subtitle playlist.")]
|
||||||
public class DeleteSubtitle
|
public class GetSubtitlePlaylist
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the id.
|
/// Gets or sets the id.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value>The id.</value>
|
/// <value>The id.</value>
|
||||||
[ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "DELETE")]
|
[ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
|
||||||
public string Id { get; set; }
|
public string Id { get; set; }
|
||||||
|
|
||||||
[ApiMember(Name = "Index", Description = "The subtitle stream index", IsRequired = true, DataType = "int", ParameterType = "path", Verb = "DELETE")]
|
[ApiMember(Name = "MediaSourceId", Description = "MediaSourceId", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
|
||||||
|
public string MediaSourceId { get; set; }
|
||||||
|
|
||||||
|
[ApiMember(Name = "Index", Description = "The subtitle stream index", IsRequired = true, DataType = "int", ParameterType = "path", Verb = "GET")]
|
||||||
public int Index { get; set; }
|
public int Index { get; set; }
|
||||||
}
|
|
||||||
|
|
||||||
[Route("/Items/{Id}/RemoteSearch/Subtitles/{Language}", "GET")]
|
[ApiMember(Name = "SegmentLength", Description = "The subtitle srgment length", IsRequired = true, DataType = "int", ParameterType = "path", Verb = "GET")]
|
||||||
public class SearchRemoteSubtitles : IReturn<List<RemoteSubtitleInfo>>
|
public int SegmentLength { get; set; }
|
||||||
{
|
|
||||||
[ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
|
|
||||||
public string Id { get; set; }
|
|
||||||
|
|
||||||
[ApiMember(Name = "Language", Description = "Language", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
|
|
||||||
public string Language { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
[Route("/Items/{Id}/RemoteSearch/Subtitles/Providers", "GET")]
|
|
||||||
public class GetSubtitleProviders : IReturn<List<SubtitleProviderInfo>>
|
|
||||||
{
|
|
||||||
[ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
|
|
||||||
public string Id { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
[Route("/Items/{Id}/RemoteSearch/Subtitles/{SubtitleId}", "POST")]
|
|
||||||
public class DownloadRemoteSubtitles : IReturnVoid
|
|
||||||
{
|
|
||||||
[ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
|
|
||||||
public string Id { get; set; }
|
|
||||||
|
|
||||||
[ApiMember(Name = "SubtitleId", Description = "SubtitleId", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
|
|
||||||
public string SubtitleId { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
[Route("/Providers/Subtitles/Subtitles/{Id}", "GET")]
|
|
||||||
public class GetRemoteSubtitles : IReturnVoid
|
|
||||||
{
|
|
||||||
[ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
|
|
||||||
public string Id { get; set; }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public class SubtitleService : BaseApiService
|
public class SubtitleService : BaseApiService
|
||||||
@ -99,16 +131,59 @@ namespace MediaBrowser.Api.Library
|
|||||||
_subtitleEncoder = subtitleEncoder;
|
_subtitleEncoder = subtitleEncoder;
|
||||||
}
|
}
|
||||||
|
|
||||||
public object Get(SearchRemoteSubtitles request)
|
public object Get(GetSubtitlePlaylist request)
|
||||||
{
|
{
|
||||||
var video = (Video)_libraryManager.GetItemById(request.Id);
|
var item = (Video)_libraryManager.GetItemById(new Guid(request.Id));
|
||||||
|
|
||||||
var response = _subtitleManager.SearchSubtitles(video, request.Language, CancellationToken.None).Result;
|
var mediaSource = item.GetMediaSources(false)
|
||||||
|
.First(i => string.Equals(i.Id, request.MediaSourceId ?? request.Id));
|
||||||
|
|
||||||
return ToOptimizedResult(response);
|
var builder = new StringBuilder();
|
||||||
|
|
||||||
|
var runtime = mediaSource.RunTimeTicks ?? -1;
|
||||||
|
|
||||||
|
if (runtime <= 0)
|
||||||
|
{
|
||||||
|
throw new ArgumentException("HLS Subtitles are not supported for this media.");
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.AppendLine("#EXTM3U");
|
||||||
|
builder.AppendLine("#EXT-X-TARGETDURATION:" + request.SegmentLength.ToString(CultureInfo.InvariantCulture));
|
||||||
|
builder.AppendLine("#EXT-X-VERSION:3");
|
||||||
|
builder.AppendLine("#EXT-X-MEDIA-SEQUENCE:0");
|
||||||
|
|
||||||
|
long positionTicks = 0;
|
||||||
|
var segmentLengthTicks = TimeSpan.FromSeconds(request.SegmentLength).Ticks;
|
||||||
|
|
||||||
|
while (positionTicks < runtime)
|
||||||
|
{
|
||||||
|
var remaining = runtime - positionTicks;
|
||||||
|
var lengthTicks = Math.Min(remaining, segmentLengthTicks);
|
||||||
|
|
||||||
|
builder.AppendLine("#EXTINF:" + TimeSpan.FromTicks(lengthTicks).TotalSeconds.ToString(CultureInfo.InvariantCulture));
|
||||||
|
|
||||||
|
var endPositionTicks = Math.Min(runtime, positionTicks + segmentLengthTicks);
|
||||||
|
|
||||||
|
var url = string.Format("stream.srt?StartPositionTicks={0}&EndPositionTicks={1}",
|
||||||
|
positionTicks.ToString(CultureInfo.InvariantCulture),
|
||||||
|
endPositionTicks.ToString(CultureInfo.InvariantCulture));
|
||||||
|
|
||||||
|
builder.AppendLine(url);
|
||||||
|
|
||||||
|
positionTicks += segmentLengthTicks;
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.AppendLine("#EXT-X-ENDLIST");
|
||||||
|
|
||||||
|
return ResultFactory.GetResult(builder.ToString(), Common.Net.MimeTypes.GetMimeType("playlist.m3u8"), new Dictionary<string, string>());
|
||||||
}
|
}
|
||||||
|
|
||||||
public object Get(GetSubtitle request)
|
public object Get(GetSubtitle request)
|
||||||
{
|
{
|
||||||
|
if (string.Equals(request.Format, "js", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
request.Format = "json";
|
||||||
|
}
|
||||||
if (string.IsNullOrEmpty(request.Format))
|
if (string.IsNullOrEmpty(request.Format))
|
||||||
{
|
{
|
||||||
var item = (Video)_libraryManager.GetItemById(new Guid(request.Id));
|
var item = (Video)_libraryManager.GetItemById(new Guid(request.Id));
|
||||||
@ -134,9 +209,19 @@ namespace MediaBrowser.Api.Library
|
|||||||
request.Index,
|
request.Index,
|
||||||
request.Format,
|
request.Format,
|
||||||
request.StartPositionTicks,
|
request.StartPositionTicks,
|
||||||
|
request.EndPositionTicks,
|
||||||
CancellationToken.None).ConfigureAwait(false);
|
CancellationToken.None).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public object Get(SearchRemoteSubtitles request)
|
||||||
|
{
|
||||||
|
var video = (Video)_libraryManager.GetItemById(request.Id);
|
||||||
|
|
||||||
|
var response = _subtitleManager.SearchSubtitles(video, request.Language, CancellationToken.None).Result;
|
||||||
|
|
||||||
|
return ToOptimizedResult(response);
|
||||||
|
}
|
||||||
|
|
||||||
public void Delete(DeleteSubtitle request)
|
public void Delete(DeleteSubtitle request)
|
||||||
{
|
{
|
||||||
var task = _subtitleManager.DeleteSubtitles(request.Id, request.Index);
|
var task = _subtitleManager.DeleteSubtitles(request.Id, request.Index);
|
104
MediaBrowser.Api/Sync/SyncService.cs
Normal file
104
MediaBrowser.Api/Sync/SyncService.cs
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
using MediaBrowser.Controller.Net;
|
||||||
|
using MediaBrowser.Controller.Sync;
|
||||||
|
using MediaBrowser.Model.Querying;
|
||||||
|
using MediaBrowser.Model.Sync;
|
||||||
|
using ServiceStack;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace MediaBrowser.Api.Sync
|
||||||
|
{
|
||||||
|
[Route("/Sync/Jobs/{Id}", "DELETE", Summary = "Cancels a sync job.")]
|
||||||
|
public class CancelSyncJob : IReturnVoid
|
||||||
|
{
|
||||||
|
[ApiMember(Name = "Id", Description = "Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
|
||||||
|
public string Id { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
[Route("/Sync/Jobs/{Id}", "GET", Summary = "Gets a sync job.")]
|
||||||
|
public class GetSyncJob : IReturn<SyncJob>
|
||||||
|
{
|
||||||
|
[ApiMember(Name = "Id", Description = "Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
|
||||||
|
public string Id { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
[Route("/Sync/Jobs", "GET", Summary = "Gets sync jobs.")]
|
||||||
|
public class GetSyncJobs : IReturn<QueryResult<SyncJob>>
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Skips over a given number of items within the results. Use for paging.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The start index.</value>
|
||||||
|
[ApiMember(Name = "StartIndex", Description = "Optional. The record index to start at. All items with a lower index will be dropped from the results.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
|
||||||
|
public int? StartIndex { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The maximum number of items to return
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The limit.</value>
|
||||||
|
[ApiMember(Name = "Limit", Description = "Optional. The maximum number of records to return", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
|
||||||
|
public int? Limit { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
[Route("/Sync/Jobs", "POST", Summary = "Gets sync jobs.")]
|
||||||
|
public class CreateSyncJob : SyncJobRequest, IReturn<SyncJobCreationResult>
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
[Route("/Sync/Targets", "GET", Summary = "Gets a list of available sync targets.")]
|
||||||
|
public class GetSyncTarget : IReturn<List<SyncTarget>>
|
||||||
|
{
|
||||||
|
[ApiMember(Name = "UserId", Description = "UserId", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")]
|
||||||
|
public string UserId { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
[Authenticated]
|
||||||
|
public class SyncService : BaseApiService
|
||||||
|
{
|
||||||
|
private readonly ISyncManager _syncManager;
|
||||||
|
|
||||||
|
public SyncService(ISyncManager syncManager)
|
||||||
|
{
|
||||||
|
_syncManager = syncManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
public object Get(GetSyncTarget request)
|
||||||
|
{
|
||||||
|
var result = _syncManager.GetSyncTargets(request.UserId);
|
||||||
|
|
||||||
|
return ToOptimizedResult(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
public object Get(GetSyncJobs request)
|
||||||
|
{
|
||||||
|
var result = _syncManager.GetJobs(new SyncJobQuery
|
||||||
|
{
|
||||||
|
StartIndex = request.StartIndex,
|
||||||
|
Limit = request.Limit
|
||||||
|
});
|
||||||
|
|
||||||
|
return ToOptimizedResult(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
public object Get(GetSyncJob request)
|
||||||
|
{
|
||||||
|
var result = _syncManager.GetJob(request.Id);
|
||||||
|
|
||||||
|
return ToOptimizedResult(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Delete(CancelSyncJob request)
|
||||||
|
{
|
||||||
|
var task = _syncManager.CancelJob(request.Id);
|
||||||
|
|
||||||
|
Task.WaitAll(task);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<object> Post(CreateSyncJob request)
|
||||||
|
{
|
||||||
|
var result = await _syncManager.CreateJob(request).ConfigureAwait(false);
|
||||||
|
|
||||||
|
return ToOptimizedResult(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
53
MediaBrowser.Api/System/ActivityLogService.cs
Normal file
53
MediaBrowser.Api/System/ActivityLogService.cs
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
using MediaBrowser.Controller.Activity;
|
||||||
|
using MediaBrowser.Controller.Net;
|
||||||
|
using MediaBrowser.Model.Activity;
|
||||||
|
using MediaBrowser.Model.Querying;
|
||||||
|
using ServiceStack;
|
||||||
|
using System;
|
||||||
|
using System.Globalization;
|
||||||
|
|
||||||
|
namespace MediaBrowser.Api.System
|
||||||
|
{
|
||||||
|
[Route("/System/ActivityLog/Entries", "GET", Summary = "Gets activity log entries")]
|
||||||
|
public class GetActivityLogs : IReturn<QueryResult<ActivityLogEntry>>
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Skips over a given number of items within the results. Use for paging.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The start index.</value>
|
||||||
|
[ApiMember(Name = "StartIndex", Description = "Optional. The record index to start at. All items with a lower index will be dropped from the results.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
|
||||||
|
public int? StartIndex { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The maximum number of items to return
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The limit.</value>
|
||||||
|
[ApiMember(Name = "Limit", Description = "Optional. The maximum number of records to return", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
|
||||||
|
public int? Limit { get; set; }
|
||||||
|
|
||||||
|
[ApiMember(Name = "MinDate", Description = "Optional. The minimum date. Format = ISO", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")]
|
||||||
|
public string MinDate { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
[Authenticated]
|
||||||
|
public class ActivityLogService : BaseApiService
|
||||||
|
{
|
||||||
|
private readonly IActivityManager _activityManager;
|
||||||
|
|
||||||
|
public ActivityLogService(IActivityManager activityManager)
|
||||||
|
{
|
||||||
|
_activityManager = activityManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
public object Get(GetActivityLogs request)
|
||||||
|
{
|
||||||
|
DateTime? minDate = string.IsNullOrWhiteSpace(request.MinDate) ?
|
||||||
|
(DateTime?)null :
|
||||||
|
DateTime.Parse(request.MinDate, null, DateTimeStyles.RoundtripKind).ToUniversalTime();
|
||||||
|
|
||||||
|
var result = _activityManager.GetActivityLogEntries(minDate, request.StartIndex, request.Limit);
|
||||||
|
|
||||||
|
return ToOptimizedResult(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
67
MediaBrowser.Api/System/ActivityLogWebSocketListener.cs
Normal file
67
MediaBrowser.Api/System/ActivityLogWebSocketListener.cs
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
using MediaBrowser.Common.Net;
|
||||||
|
using MediaBrowser.Controller.Activity;
|
||||||
|
using MediaBrowser.Model.Activity;
|
||||||
|
using MediaBrowser.Model.Events;
|
||||||
|
using MediaBrowser.Model.Logging;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace MediaBrowser.Api.System
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Class SessionInfoWebSocketListener
|
||||||
|
/// </summary>
|
||||||
|
class ActivityLogWebSocketListener : BasePeriodicWebSocketListener<List<ActivityLogEntry>, WebSocketListenerState>
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the name.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The name.</value>
|
||||||
|
protected override string Name
|
||||||
|
{
|
||||||
|
get { return "ActivityLogEntry"; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The _kernel
|
||||||
|
/// </summary>
|
||||||
|
private readonly IActivityManager _activityManager;
|
||||||
|
|
||||||
|
public ActivityLogWebSocketListener(ILogger logger, IActivityManager activityManager)
|
||||||
|
: base(logger)
|
||||||
|
{
|
||||||
|
_activityManager = activityManager;
|
||||||
|
_activityManager.EntryCreated += _activityManager_EntryCreated;
|
||||||
|
}
|
||||||
|
|
||||||
|
void _activityManager_EntryCreated(object sender, GenericEventArgs<ActivityLogEntry> e)
|
||||||
|
{
|
||||||
|
SendData(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the data to send.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="state">The state.</param>
|
||||||
|
/// <returns>Task{SystemInfo}.</returns>
|
||||||
|
protected override Task<List<ActivityLogEntry>> GetDataToSend(WebSocketListenerState state)
|
||||||
|
{
|
||||||
|
return Task.FromResult(new List<ActivityLogEntry>());
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override bool SendOnTimer
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Dispose(bool dispose)
|
||||||
|
{
|
||||||
|
_activityManager.EntryCreated -= _activityManager_EntryCreated;
|
||||||
|
|
||||||
|
base.Dispose(dispose);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -4,7 +4,7 @@ using MediaBrowser.Model.Logging;
|
|||||||
using MediaBrowser.Model.System;
|
using MediaBrowser.Model.System;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace MediaBrowser.Api.WebSocket
|
namespace MediaBrowser.Api.System
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Class SystemInfoWebSocketListener
|
/// Class SystemInfoWebSocketListener
|
205
MediaBrowser.Api/System/SystemService.cs
Normal file
205
MediaBrowser.Api/System/SystemService.cs
Normal file
@ -0,0 +1,205 @@
|
|||||||
|
using MediaBrowser.Common.Configuration;
|
||||||
|
using MediaBrowser.Common.IO;
|
||||||
|
using MediaBrowser.Common.Net;
|
||||||
|
using MediaBrowser.Controller;
|
||||||
|
using MediaBrowser.Controller.Net;
|
||||||
|
using MediaBrowser.Model.System;
|
||||||
|
using ServiceStack;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace MediaBrowser.Api.System
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Class GetSystemInfo
|
||||||
|
/// </summary>
|
||||||
|
[Route("/System/Info", "GET", Summary = "Gets information about the server")]
|
||||||
|
[Authenticated]
|
||||||
|
public class GetSystemInfo : IReturn<SystemInfo>
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
[Route("/System/Info/Public", "GET", Summary = "Gets public information about the server")]
|
||||||
|
public class GetPublicSystemInfo : IReturn<PublicSystemInfo>
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Class RestartApplication
|
||||||
|
/// </summary>
|
||||||
|
[Route("/System/Restart", "POST", Summary = "Restarts the application, if needed")]
|
||||||
|
[Authenticated]
|
||||||
|
public class RestartApplication
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 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")]
|
||||||
|
public class ShutdownApplication
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
[Route("/System/Logs", "GET", Summary = "Gets a list of available server log files")]
|
||||||
|
[Authenticated]
|
||||||
|
public class GetServerLogs : IReturn<List<LogFile>>
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
[Route("/System/Endpoint", "GET", Summary = "Gets information about the request endpoint")]
|
||||||
|
[Authenticated]
|
||||||
|
public class GetEndpointInfo : IReturn<EndpointInfo>
|
||||||
|
{
|
||||||
|
public string Endpoint { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
[Route("/System/Logs/Log", "GET", Summary = "Gets a log file")]
|
||||||
|
public class GetLogFile
|
||||||
|
{
|
||||||
|
[ApiMember(Name = "Name", Description = "The log file name.", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
|
||||||
|
public string Name { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Class SystemInfoService
|
||||||
|
/// </summary>
|
||||||
|
public class SystemService : BaseApiService
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The _app host
|
||||||
|
/// </summary>
|
||||||
|
private readonly IServerApplicationHost _appHost;
|
||||||
|
private readonly IApplicationPaths _appPaths;
|
||||||
|
private readonly IFileSystem _fileSystem;
|
||||||
|
|
||||||
|
private readonly INetworkManager _network;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="SystemService" /> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="appHost">The app host.</param>
|
||||||
|
/// <param name="appPaths">The application paths.</param>
|
||||||
|
/// <param name="fileSystem">The file system.</param>
|
||||||
|
/// <exception cref="ArgumentNullException">jsonSerializer</exception>
|
||||||
|
public SystemService(IServerApplicationHost appHost, IApplicationPaths appPaths, IFileSystem fileSystem, INetworkManager network)
|
||||||
|
{
|
||||||
|
_appHost = appHost;
|
||||||
|
_appPaths = appPaths;
|
||||||
|
_fileSystem = fileSystem;
|
||||||
|
_network = network;
|
||||||
|
}
|
||||||
|
|
||||||
|
public object Get(GetServerLogs request)
|
||||||
|
{
|
||||||
|
List<FileInfo> files;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
files = new DirectoryInfo(_appPaths.LogDirectoryPath)
|
||||||
|
.EnumerateFiles("*", SearchOption.AllDirectories)
|
||||||
|
.Where(i => string.Equals(i.Extension, ".txt", global::System.StringComparison.OrdinalIgnoreCase))
|
||||||
|
.ToList();
|
||||||
|
}
|
||||||
|
catch (DirectoryNotFoundException)
|
||||||
|
{
|
||||||
|
files = new List<FileInfo>();
|
||||||
|
}
|
||||||
|
|
||||||
|
var result = files.Select(i => new LogFile
|
||||||
|
{
|
||||||
|
DateCreated = _fileSystem.GetCreationTimeUtc(i),
|
||||||
|
DateModified = _fileSystem.GetLastWriteTimeUtc(i),
|
||||||
|
Name = i.Name,
|
||||||
|
Size = i.Length
|
||||||
|
|
||||||
|
}).OrderByDescending(i => i.DateModified)
|
||||||
|
.ThenByDescending(i => i.DateCreated)
|
||||||
|
.ThenBy(i => i.Name)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
return ToOptimizedResult(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
public object Get(GetLogFile request)
|
||||||
|
{
|
||||||
|
var file = new DirectoryInfo(_appPaths.LogDirectoryPath)
|
||||||
|
.EnumerateFiles("*", SearchOption.AllDirectories)
|
||||||
|
.First(i => string.Equals(i.Name, request.Name, global::System.StringComparison.OrdinalIgnoreCase));
|
||||||
|
|
||||||
|
return ResultFactory.GetStaticFileResult(Request, file.FullName, FileShare.ReadWrite);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the specified request.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="request">The request.</param>
|
||||||
|
/// <returns>System.Object.</returns>
|
||||||
|
public object Get(GetSystemInfo request)
|
||||||
|
{
|
||||||
|
var result = _appHost.GetSystemInfo();
|
||||||
|
|
||||||
|
return ToOptimizedResult(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
public object Get(GetPublicSystemInfo request)
|
||||||
|
{
|
||||||
|
var result = _appHost.GetSystemInfo();
|
||||||
|
|
||||||
|
var publicInfo = new PublicSystemInfo
|
||||||
|
{
|
||||||
|
Id = result.Id,
|
||||||
|
ServerName = result.ServerName,
|
||||||
|
Version = result.Version
|
||||||
|
};
|
||||||
|
|
||||||
|
return ToOptimizedResult(publicInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Posts the specified request.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="request">The request.</param>
|
||||||
|
public void Post(RestartApplication request)
|
||||||
|
{
|
||||||
|
Task.Run(async () =>
|
||||||
|
{
|
||||||
|
await Task.Delay(100).ConfigureAwait(false);
|
||||||
|
await _appHost.Restart().ConfigureAwait(false);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Posts the specified request.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="request">The request.</param>
|
||||||
|
public void Post(ShutdownApplication request)
|
||||||
|
{
|
||||||
|
Task.Run(async () =>
|
||||||
|
{
|
||||||
|
await Task.Delay(100).ConfigureAwait(false);
|
||||||
|
await _appHost.Shutdown().ConfigureAwait(false);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public object Get(GetEndpointInfo request)
|
||||||
|
{
|
||||||
|
return ToOptimizedResult(new EndpointInfo
|
||||||
|
{
|
||||||
|
IsLocal = Request.IsLocal,
|
||||||
|
IsInNetwork = _network.IsInLocalNetwork(request.Endpoint ?? Request.RemoteIp)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class EndpointInfo
|
||||||
|
{
|
||||||
|
public bool IsLocal { get; set; }
|
||||||
|
public bool IsInNetwork { get; set; }
|
||||||
|
}
|
||||||
|
}
|
@ -1,90 +0,0 @@
|
|||||||
using MediaBrowser.Controller;
|
|
||||||
using MediaBrowser.Model.System;
|
|
||||||
using ServiceStack;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace MediaBrowser.Api
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Class GetSystemInfo
|
|
||||||
/// </summary>
|
|
||||||
[Route("/System/Info", "GET", Summary = "Gets information about the server")]
|
|
||||||
public class GetSystemInfo : IReturn<SystemInfo>
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Class RestartApplication
|
|
||||||
/// </summary>
|
|
||||||
[Route("/System/Restart", "POST", Summary = "Restarts the application, if needed")]
|
|
||||||
public class RestartApplication
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
[Route("/System/Shutdown", "POST", Summary = "Shuts down the application")]
|
|
||||||
public class ShutdownApplication
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Class SystemInfoService
|
|
||||||
/// </summary>
|
|
||||||
public class SystemService : BaseApiService
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// The _app host
|
|
||||||
/// </summary>
|
|
||||||
private readonly IServerApplicationHost _appHost;
|
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Initializes a new instance of the <see cref="SystemService" /> class.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="appHost">The app host.</param>
|
|
||||||
/// <exception cref="System.ArgumentNullException">jsonSerializer</exception>
|
|
||||||
public SystemService(IServerApplicationHost appHost)
|
|
||||||
{
|
|
||||||
_appHost = appHost;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the specified request.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="request">The request.</param>
|
|
||||||
/// <returns>System.Object.</returns>
|
|
||||||
public object Get(GetSystemInfo request)
|
|
||||||
{
|
|
||||||
var result = _appHost.GetSystemInfo();
|
|
||||||
|
|
||||||
return ToOptimizedResult(result);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Posts the specified request.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="request">The request.</param>
|
|
||||||
public void Post(RestartApplication request)
|
|
||||||
{
|
|
||||||
Task.Run(async () =>
|
|
||||||
{
|
|
||||||
await Task.Delay(100).ConfigureAwait(false);
|
|
||||||
await _appHost.Restart().ConfigureAwait(false);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Posts the specified request.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="request">The request.</param>
|
|
||||||
public void Post(ShutdownApplication request)
|
|
||||||
{
|
|
||||||
Task.Run(async () =>
|
|
||||||
{
|
|
||||||
await Task.Delay(100).ConfigureAwait(false);
|
|
||||||
await _appHost.Shutdown().ConfigureAwait(false);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
@ -4,6 +4,7 @@ using MediaBrowser.Controller.Dto;
|
|||||||
using MediaBrowser.Controller.Entities;
|
using MediaBrowser.Controller.Entities;
|
||||||
using MediaBrowser.Controller.Entities.TV;
|
using MediaBrowser.Controller.Entities.TV;
|
||||||
using MediaBrowser.Controller.Library;
|
using MediaBrowser.Controller.Library;
|
||||||
|
using MediaBrowser.Controller.Net;
|
||||||
using MediaBrowser.Controller.Persistence;
|
using MediaBrowser.Controller.Persistence;
|
||||||
using MediaBrowser.Model.Entities;
|
using MediaBrowser.Model.Entities;
|
||||||
using MediaBrowser.Model.Querying;
|
using MediaBrowser.Model.Querying;
|
||||||
@ -175,6 +176,7 @@ namespace MediaBrowser.Api
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Class TvShowsService
|
/// Class TvShowsService
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[Authenticated]
|
||||||
public class TvShowsService : BaseApiService
|
public class TvShowsService : BaseApiService
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
using MediaBrowser.Controller.Entities;
|
using MediaBrowser.Controller.Entities;
|
||||||
using MediaBrowser.Controller.Entities.Audio;
|
using MediaBrowser.Controller.Entities.Audio;
|
||||||
using MediaBrowser.Controller.Library;
|
using MediaBrowser.Controller.Library;
|
||||||
|
using MediaBrowser.Controller.Net;
|
||||||
using MediaBrowser.Controller.Persistence;
|
using MediaBrowser.Controller.Persistence;
|
||||||
using MediaBrowser.Model.Dto;
|
using MediaBrowser.Model.Dto;
|
||||||
using MediaBrowser.Model.Querying;
|
using MediaBrowser.Model.Querying;
|
||||||
@ -46,6 +47,7 @@ namespace MediaBrowser.Api.UserLibrary
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Class ArtistsService
|
/// Class ArtistsService
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[Authenticated]
|
||||||
public class ArtistsService : BaseItemsByNameService<MusicArtist>
|
public class ArtistsService : BaseItemsByNameService<MusicArtist>
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -88,10 +90,10 @@ namespace MediaBrowser.Api.UserLibrary
|
|||||||
{
|
{
|
||||||
var user = UserManager.GetUserById(request.UserId.Value);
|
var user = UserManager.GetUserById(request.UserId.Value);
|
||||||
|
|
||||||
return DtoService.GetItemByNameDto(item, fields.ToList(), user);
|
return DtoService.GetBaseItemDto(item, fields.ToList(), user);
|
||||||
}
|
}
|
||||||
|
|
||||||
return DtoService.GetItemByNameDto(item, fields.ToList());
|
return DtoService.GetBaseItemDto(item, fields.ToList());
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
using System.Collections.Generic;
|
using MediaBrowser.Model.Entities;
|
||||||
using System.Linq;
|
|
||||||
using MediaBrowser.Model.Entities;
|
|
||||||
using MediaBrowser.Model.Querying;
|
using MediaBrowser.Model.Querying;
|
||||||
using ServiceStack;
|
using ServiceStack;
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
namespace MediaBrowser.Api.UserLibrary
|
namespace MediaBrowser.Api.UserLibrary
|
||||||
{
|
{
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
using MediaBrowser.Controller.Dto;
|
using MediaBrowser.Controller.Dto;
|
||||||
using MediaBrowser.Controller.Entities;
|
using MediaBrowser.Controller.Entities;
|
||||||
using MediaBrowser.Controller.Library;
|
using MediaBrowser.Controller.Library;
|
||||||
|
using MediaBrowser.Controller.Net;
|
||||||
using MediaBrowser.Controller.Persistence;
|
using MediaBrowser.Controller.Persistence;
|
||||||
using MediaBrowser.Model.Dto;
|
using MediaBrowser.Model.Dto;
|
||||||
using MediaBrowser.Model.Entities;
|
using MediaBrowser.Model.Entities;
|
||||||
@ -41,6 +42,7 @@ namespace MediaBrowser.Api.UserLibrary
|
|||||||
public Guid? UserId { get; set; }
|
public Guid? UserId { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Authenticated]
|
||||||
public class GameGenresService : BaseItemsByNameService<GameGenre>
|
public class GameGenresService : BaseItemsByNameService<GameGenre>
|
||||||
{
|
{
|
||||||
public GameGenresService(IUserManager userManager, ILibraryManager libraryManager, IUserDataManager userDataRepository, IItemRepository itemRepo, IDtoService dtoService)
|
public GameGenresService(IUserManager userManager, ILibraryManager libraryManager, IUserDataManager userDataRepository, IItemRepository itemRepo, IDtoService dtoService)
|
||||||
@ -76,10 +78,10 @@ namespace MediaBrowser.Api.UserLibrary
|
|||||||
{
|
{
|
||||||
var user = UserManager.GetUserById(request.UserId.Value);
|
var user = UserManager.GetUserById(request.UserId.Value);
|
||||||
|
|
||||||
return DtoService.GetItemByNameDto(item, fields.ToList(), user);
|
return DtoService.GetBaseItemDto(item, fields.ToList(), user);
|
||||||
}
|
}
|
||||||
|
|
||||||
return DtoService.GetItemByNameDto(item, fields.ToList());
|
return DtoService.GetBaseItemDto(item, fields.ToList());
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
using MediaBrowser.Controller.Entities;
|
using MediaBrowser.Controller.Entities;
|
||||||
using MediaBrowser.Controller.Entities.Audio;
|
using MediaBrowser.Controller.Entities.Audio;
|
||||||
using MediaBrowser.Controller.Library;
|
using MediaBrowser.Controller.Library;
|
||||||
|
using MediaBrowser.Controller.Net;
|
||||||
using MediaBrowser.Controller.Persistence;
|
using MediaBrowser.Controller.Persistence;
|
||||||
using MediaBrowser.Model.Dto;
|
using MediaBrowser.Model.Dto;
|
||||||
using MediaBrowser.Model.Querying;
|
using MediaBrowser.Model.Querying;
|
||||||
@ -46,6 +47,7 @@ namespace MediaBrowser.Api.UserLibrary
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Class GenresService
|
/// Class GenresService
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[Authenticated]
|
||||||
public class GenresService : BaseItemsByNameService<Genre>
|
public class GenresService : BaseItemsByNameService<Genre>
|
||||||
{
|
{
|
||||||
public GenresService(IUserManager userManager, ILibraryManager libraryManager, IUserDataManager userDataRepository, IItemRepository itemRepo, IDtoService dtoService)
|
public GenresService(IUserManager userManager, ILibraryManager libraryManager, IUserDataManager userDataRepository, IItemRepository itemRepo, IDtoService dtoService)
|
||||||
@ -81,10 +83,10 @@ namespace MediaBrowser.Api.UserLibrary
|
|||||||
{
|
{
|
||||||
var user = UserManager.GetUserById(request.UserId.Value);
|
var user = UserManager.GetUserById(request.UserId.Value);
|
||||||
|
|
||||||
return DtoService.GetItemByNameDto(item, fields.ToList(), user);
|
return DtoService.GetBaseItemDto(item, fields.ToList(), user);
|
||||||
}
|
}
|
||||||
|
|
||||||
return DtoService.GetItemByNameDto(item, fields.ToList());
|
return DtoService.GetBaseItemDto(item, fields.ToList());
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -6,6 +6,7 @@ using MediaBrowser.Controller.Entities.Movies;
|
|||||||
using MediaBrowser.Controller.Entities.TV;
|
using MediaBrowser.Controller.Entities.TV;
|
||||||
using MediaBrowser.Controller.Library;
|
using MediaBrowser.Controller.Library;
|
||||||
using MediaBrowser.Controller.Localization;
|
using MediaBrowser.Controller.Localization;
|
||||||
|
using MediaBrowser.Controller.Net;
|
||||||
using MediaBrowser.Controller.Providers;
|
using MediaBrowser.Controller.Providers;
|
||||||
using MediaBrowser.Model.Entities;
|
using MediaBrowser.Model.Entities;
|
||||||
using MediaBrowser.Model.Querying;
|
using MediaBrowser.Model.Querying;
|
||||||
@ -246,6 +247,7 @@ namespace MediaBrowser.Api.UserLibrary
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Class ItemsService
|
/// Class ItemsService
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[Authenticated]
|
||||||
public class ItemsService : BaseApiService
|
public class ItemsService : BaseApiService
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -1428,7 +1430,7 @@ namespace MediaBrowser.Api.UserLibrary
|
|||||||
nextId = list[index + 1].Id;
|
nextId = list[index + 1].Id;
|
||||||
}
|
}
|
||||||
|
|
||||||
return list.Where(i => i.Id == previousId || i.Id == nextId);
|
return list.Where(i => i.Id == previousId || i.Id == nextId || i.Id == adjacentToIdGuid);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
using MediaBrowser.Controller.Entities;
|
using MediaBrowser.Controller.Entities;
|
||||||
using MediaBrowser.Controller.Entities.Audio;
|
using MediaBrowser.Controller.Entities.Audio;
|
||||||
using MediaBrowser.Controller.Library;
|
using MediaBrowser.Controller.Library;
|
||||||
|
using MediaBrowser.Controller.Net;
|
||||||
using MediaBrowser.Controller.Persistence;
|
using MediaBrowser.Controller.Persistence;
|
||||||
using MediaBrowser.Model.Dto;
|
using MediaBrowser.Model.Dto;
|
||||||
using MediaBrowser.Model.Querying;
|
using MediaBrowser.Model.Querying;
|
||||||
@ -41,6 +42,7 @@ namespace MediaBrowser.Api.UserLibrary
|
|||||||
public Guid? UserId { get; set; }
|
public Guid? UserId { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Authenticated]
|
||||||
public class MusicGenresService : BaseItemsByNameService<MusicGenre>
|
public class MusicGenresService : BaseItemsByNameService<MusicGenre>
|
||||||
{
|
{
|
||||||
public MusicGenresService(IUserManager userManager, ILibraryManager libraryManager, IUserDataManager userDataRepository, IItemRepository itemRepo, IDtoService dtoService)
|
public MusicGenresService(IUserManager userManager, ILibraryManager libraryManager, IUserDataManager userDataRepository, IItemRepository itemRepo, IDtoService dtoService)
|
||||||
@ -76,10 +78,10 @@ namespace MediaBrowser.Api.UserLibrary
|
|||||||
{
|
{
|
||||||
var user = UserManager.GetUserById(request.UserId.Value);
|
var user = UserManager.GetUserById(request.UserId.Value);
|
||||||
|
|
||||||
return DtoService.GetItemByNameDto(item, fields.ToList(), user);
|
return DtoService.GetBaseItemDto(item, fields.ToList(), user);
|
||||||
}
|
}
|
||||||
|
|
||||||
return DtoService.GetItemByNameDto(item, fields.ToList());
|
return DtoService.GetBaseItemDto(item, fields.ToList());
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
using MediaBrowser.Controller.Dto;
|
using MediaBrowser.Controller.Dto;
|
||||||
using MediaBrowser.Controller.Entities;
|
using MediaBrowser.Controller.Entities;
|
||||||
using MediaBrowser.Controller.Library;
|
using MediaBrowser.Controller.Library;
|
||||||
|
using MediaBrowser.Controller.Net;
|
||||||
using MediaBrowser.Controller.Persistence;
|
using MediaBrowser.Controller.Persistence;
|
||||||
using MediaBrowser.Model.Dto;
|
using MediaBrowser.Model.Dto;
|
||||||
using MediaBrowser.Model.Querying;
|
using MediaBrowser.Model.Querying;
|
||||||
@ -51,6 +52,7 @@ namespace MediaBrowser.Api.UserLibrary
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Class PersonsService
|
/// Class PersonsService
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[Authenticated]
|
||||||
public class PersonsService : BaseItemsByNameService<Person>
|
public class PersonsService : BaseItemsByNameService<Person>
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -93,10 +95,10 @@ namespace MediaBrowser.Api.UserLibrary
|
|||||||
{
|
{
|
||||||
var user = UserManager.GetUserById(request.UserId.Value);
|
var user = UserManager.GetUserById(request.UserId.Value);
|
||||||
|
|
||||||
return DtoService.GetItemByNameDto(item, fields.ToList(), user);
|
return DtoService.GetBaseItemDto(item, fields.ToList(), user);
|
||||||
}
|
}
|
||||||
|
|
||||||
return DtoService.GetItemByNameDto(item, fields.ToList());
|
return DtoService.GetBaseItemDto(item, fields.ToList());
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
388
MediaBrowser.Api/UserLibrary/PlaystateService.cs
Normal file
388
MediaBrowser.Api/UserLibrary/PlaystateService.cs
Normal file
@ -0,0 +1,388 @@
|
|||||||
|
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 ServiceStack;
|
||||||
|
using System;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace MediaBrowser.Api.UserLibrary
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Class MarkPlayedItem
|
||||||
|
/// </summary>
|
||||||
|
[Route("/Users/{UserId}/PlayedItems/{Id}", "POST")]
|
||||||
|
[Api(Description = "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 Guid 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")]
|
||||||
|
[Api(Description = "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 Guid 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")]
|
||||||
|
[Api(Description = "Reports playback has started within a session")]
|
||||||
|
public class ReportPlaybackStart : PlaybackStartInfo, IReturnVoid
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
[Route("/Sessions/Playing/Progress", "POST")]
|
||||||
|
[Api(Description = "Reports playback progress within a session")]
|
||||||
|
public class ReportPlaybackProgress : PlaybackProgressInfo, IReturnVoid
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
[Route("/Sessions/Playing/Stopped", "POST")]
|
||||||
|
[Api(Description = "Reports playback has stopped within a session")]
|
||||||
|
public class ReportPlaybackStopped : PlaybackStopInfo, IReturnVoid
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Class OnPlaybackStart
|
||||||
|
/// </summary>
|
||||||
|
[Route("/Users/{UserId}/PlayingItems/{Id}", "POST")]
|
||||||
|
[Api(Description = "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 Guid 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; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the id.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The id.</value>
|
||||||
|
[ApiMember(Name = "QueueableMediaTypes", Description = "A list of media types that can be queued from this item, comma delimited. Audio,Video,Book,Game", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST", AllowMultiple = true)]
|
||||||
|
public string QueueableMediaTypes { 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; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Class OnPlaybackProgress
|
||||||
|
/// </summary>
|
||||||
|
[Route("/Users/{UserId}/PlayingItems/{Id}/Progress", "POST")]
|
||||||
|
[Api(Description = "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 Guid 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; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Class OnPlaybackStopped
|
||||||
|
/// </summary>
|
||||||
|
[Route("/Users/{UserId}/PlayingItems/{Id}", "DELETE")]
|
||||||
|
[Api(Description = "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 Guid 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; }
|
||||||
|
|
||||||
|
/// <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; }
|
||||||
|
}
|
||||||
|
|
||||||
|
[Authenticated]
|
||||||
|
public class PlaystateService : BaseApiService
|
||||||
|
{
|
||||||
|
private readonly IUserManager _userManager;
|
||||||
|
private readonly IUserDataManager _userDataRepository;
|
||||||
|
private readonly ILibraryManager _libraryManager;
|
||||||
|
private readonly ISessionManager _sessionManager;
|
||||||
|
|
||||||
|
public PlaystateService(IUserManager userManager, IUserDataManager userDataRepository, ILibraryManager libraryManager, ISessionManager sessionManager)
|
||||||
|
{
|
||||||
|
_userManager = userManager;
|
||||||
|
_userDataRepository = userDataRepository;
|
||||||
|
_libraryManager = libraryManager;
|
||||||
|
_sessionManager = sessionManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Posts the specified request.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="request">The request.</param>
|
||||||
|
public object Post(MarkPlayedItem request)
|
||||||
|
{
|
||||||
|
var result = MarkPlayed(request).Result;
|
||||||
|
|
||||||
|
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 = GetSession();
|
||||||
|
|
||||||
|
var dto = await UpdatePlayedStatus(user, request.Id, true, datePlayed).ConfigureAwait(false);
|
||||||
|
|
||||||
|
foreach (var additionalUserInfo in session.AdditionalUsers)
|
||||||
|
{
|
||||||
|
var additionalUser = _userManager.GetUserById(new Guid(additionalUserInfo.UserId));
|
||||||
|
|
||||||
|
await UpdatePlayedStatus(additionalUser, request.Id, true, datePlayed).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
return dto;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Posts the specified request.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="request">The request.</param>
|
||||||
|
public void Post(OnPlaybackStart request)
|
||||||
|
{
|
||||||
|
var queueableMediaTypes = (request.QueueableMediaTypes ?? string.Empty);
|
||||||
|
|
||||||
|
Post(new ReportPlaybackStart
|
||||||
|
{
|
||||||
|
CanSeek = request.CanSeek,
|
||||||
|
ItemId = request.Id,
|
||||||
|
QueueableMediaTypes = queueableMediaTypes.Split(',').ToList(),
|
||||||
|
MediaSourceId = request.MediaSourceId,
|
||||||
|
AudioStreamIndex = request.AudioStreamIndex,
|
||||||
|
SubtitleStreamIndex = request.SubtitleStreamIndex
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Post(ReportPlaybackStart request)
|
||||||
|
{
|
||||||
|
request.SessionId = GetSession().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
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Post(ReportPlaybackProgress request)
|
||||||
|
{
|
||||||
|
request.SessionId = GetSession().Id;
|
||||||
|
|
||||||
|
var task = _sessionManager.OnPlaybackProgress(request);
|
||||||
|
|
||||||
|
Task.WaitAll(task);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <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
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Post(ReportPlaybackStopped request)
|
||||||
|
{
|
||||||
|
request.SessionId = GetSession().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 = GetSession();
|
||||||
|
|
||||||
|
var dto = await UpdatePlayedStatus(user, request.Id, false, null).ConfigureAwait(false);
|
||||||
|
|
||||||
|
foreach (var additionalUserInfo in session.AdditionalUsers)
|
||||||
|
{
|
||||||
|
var additionalUser = _userManager.GetUserById(new Guid(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, _userDataRepository).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
await item.MarkUnplayed(user, _userDataRepository).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
return _userDataRepository.GetUserDataDto(item, user);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,7 @@
|
|||||||
using MediaBrowser.Controller.Dto;
|
using MediaBrowser.Controller.Dto;
|
||||||
using MediaBrowser.Controller.Entities;
|
using MediaBrowser.Controller.Entities;
|
||||||
using MediaBrowser.Controller.Library;
|
using MediaBrowser.Controller.Library;
|
||||||
|
using MediaBrowser.Controller.Net;
|
||||||
using MediaBrowser.Controller.Persistence;
|
using MediaBrowser.Controller.Persistence;
|
||||||
using MediaBrowser.Model.Dto;
|
using MediaBrowser.Model.Dto;
|
||||||
using MediaBrowser.Model.Querying;
|
using MediaBrowser.Model.Querying;
|
||||||
@ -43,6 +44,7 @@ namespace MediaBrowser.Api.UserLibrary
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Class StudiosService
|
/// Class StudiosService
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[Authenticated]
|
||||||
public class StudiosService : BaseItemsByNameService<Studio>
|
public class StudiosService : BaseItemsByNameService<Studio>
|
||||||
{
|
{
|
||||||
public StudiosService(IUserManager userManager, ILibraryManager libraryManager, IUserDataManager userDataRepository, IItemRepository itemRepo, IDtoService dtoService)
|
public StudiosService(IUserManager userManager, ILibraryManager libraryManager, IUserDataManager userDataRepository, IItemRepository itemRepo, IDtoService dtoService)
|
||||||
@ -78,10 +80,10 @@ namespace MediaBrowser.Api.UserLibrary
|
|||||||
{
|
{
|
||||||
var user = UserManager.GetUserById(request.UserId.Value);
|
var user = UserManager.GetUserById(request.UserId.Value);
|
||||||
|
|
||||||
return DtoService.GetItemByNameDto(item, fields.ToList(), user);
|
return DtoService.GetBaseItemDto(item, fields.ToList(), user);
|
||||||
}
|
}
|
||||||
|
|
||||||
return DtoService.GetItemByNameDto(item, fields.ToList());
|
return DtoService.GetBaseItemDto(item, fields.ToList());
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -3,16 +3,14 @@ using MediaBrowser.Controller.Entities;
|
|||||||
using MediaBrowser.Controller.Entities.Movies;
|
using MediaBrowser.Controller.Entities.Movies;
|
||||||
using MediaBrowser.Controller.Entities.TV;
|
using MediaBrowser.Controller.Entities.TV;
|
||||||
using MediaBrowser.Controller.Library;
|
using MediaBrowser.Controller.Library;
|
||||||
using MediaBrowser.Controller.Session;
|
using MediaBrowser.Controller.Net;
|
||||||
using MediaBrowser.Model.Dto;
|
using MediaBrowser.Model.Dto;
|
||||||
using MediaBrowser.Model.Entities;
|
using MediaBrowser.Model.Entities;
|
||||||
using MediaBrowser.Model.Library;
|
using MediaBrowser.Model.Library;
|
||||||
using MediaBrowser.Model.Querying;
|
using MediaBrowser.Model.Querying;
|
||||||
using MediaBrowser.Model.Session;
|
|
||||||
using ServiceStack;
|
using ServiceStack;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Globalization;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
@ -187,195 +185,6 @@ namespace MediaBrowser.Api.UserLibrary
|
|||||||
public bool Likes { get; set; }
|
public bool Likes { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Class MarkPlayedItem
|
|
||||||
/// </summary>
|
|
||||||
[Route("/Users/{UserId}/PlayedItems/{Id}", "POST")]
|
|
||||||
[Api(Description = "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 Guid 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")]
|
|
||||||
[Api(Description = "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 Guid 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")]
|
|
||||||
[Api(Description = "Reports playback has started within a session")]
|
|
||||||
public class ReportPlaybackStart : PlaybackStartInfo, IReturnVoid
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
[Route("/Sessions/Playing/Progress", "POST")]
|
|
||||||
[Api(Description = "Reports playback progress within a session")]
|
|
||||||
public class ReportPlaybackProgress : PlaybackProgressInfo, IReturnVoid
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
[Route("/Sessions/Playing/Stopped", "POST")]
|
|
||||||
[Api(Description = "Reports playback has stopped within a session")]
|
|
||||||
public class ReportPlaybackStopped : PlaybackStopInfo, IReturnVoid
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Class OnPlaybackStart
|
|
||||||
/// </summary>
|
|
||||||
[Route("/Users/{UserId}/PlayingItems/{Id}", "POST")]
|
|
||||||
[Api(Description = "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 Guid 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; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the id.
|
|
||||||
/// </summary>
|
|
||||||
/// <value>The id.</value>
|
|
||||||
[ApiMember(Name = "QueueableMediaTypes", Description = "A list of media types that can be queued from this item, comma delimited. Audio,Video,Book,Game", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST", AllowMultiple = true)]
|
|
||||||
public string QueueableMediaTypes { 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; }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Class OnPlaybackProgress
|
|
||||||
/// </summary>
|
|
||||||
[Route("/Users/{UserId}/PlayingItems/{Id}/Progress", "POST")]
|
|
||||||
[Api(Description = "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 Guid 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; }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Class OnPlaybackStopped
|
|
||||||
/// </summary>
|
|
||||||
[Route("/Users/{UserId}/PlayingItems/{Id}", "DELETE")]
|
|
||||||
[Api(Description = "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 Guid 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; }
|
|
||||||
|
|
||||||
/// <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; }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Class GetLocalTrailers
|
/// Class GetLocalTrailers
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -420,28 +229,54 @@ namespace MediaBrowser.Api.UserLibrary
|
|||||||
public string Id { get; set; }
|
public string Id { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Route("/Users/{UserId}/Items/Latest", "GET", Summary = "Gets latest media")]
|
||||||
|
public class GetLatestMedia : IReturn<List<BaseItemDto>>, IHasItemFields
|
||||||
|
{
|
||||||
|
/// <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 = "GET")]
|
||||||
|
public Guid UserId { get; set; }
|
||||||
|
|
||||||
|
[ApiMember(Name = "Limit", Description = "Limit", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
|
||||||
|
public int Limit { get; set; }
|
||||||
|
|
||||||
|
[ApiMember(Name = "ParentId", Description = "Specify this to localize the search to a specific item or folder. Omit to use the root", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
|
||||||
|
public string ParentId { get; set; }
|
||||||
|
|
||||||
|
[ApiMember(Name = "Fields", Description = "Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimeted. Options: Budget, Chapters, CriticRatingSummary, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
|
||||||
|
public string Fields { get; set; }
|
||||||
|
|
||||||
|
[ApiMember(Name = "IncludeItemTypes", Description = "Optional. If specified, results will be filtered based on item type. This allows multiple, comma delimeted.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
|
||||||
|
public string IncludeItemTypes { get; set; }
|
||||||
|
|
||||||
|
[ApiMember(Name = "IsFolder", Description = "Filter by items that are folders, or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
|
||||||
|
public bool? IsFolder { get; set; }
|
||||||
|
|
||||||
|
[ApiMember(Name = "IsPlayed", Description = "Filter by items that are played, or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
|
||||||
|
public bool? IsPlayed { get; set; }
|
||||||
|
|
||||||
|
[ApiMember(Name = "GroupItems", Description = "Whether or not to group items into a parent container.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
|
||||||
|
public bool GroupItems { get; set; }
|
||||||
|
|
||||||
|
public GetLatestMedia()
|
||||||
|
{
|
||||||
|
Limit = 20;
|
||||||
|
GroupItems = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Class UserLibraryService
|
/// Class UserLibraryService
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[Authenticated]
|
||||||
public class UserLibraryService : BaseApiService
|
public class UserLibraryService : BaseApiService
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// The _user manager
|
|
||||||
/// </summary>
|
|
||||||
private readonly IUserManager _userManager;
|
private readonly IUserManager _userManager;
|
||||||
/// <summary>
|
|
||||||
/// The _user data repository
|
|
||||||
/// </summary>
|
|
||||||
private readonly IUserDataManager _userDataRepository;
|
private readonly IUserDataManager _userDataRepository;
|
||||||
/// <summary>
|
|
||||||
/// The _library manager
|
|
||||||
/// </summary>
|
|
||||||
private readonly ILibraryManager _libraryManager;
|
private readonly ILibraryManager _libraryManager;
|
||||||
|
|
||||||
private readonly ISessionManager _sessionManager;
|
|
||||||
private readonly IDtoService _dtoService;
|
private readonly IDtoService _dtoService;
|
||||||
|
|
||||||
private readonly IUserViewManager _userViewManager;
|
private readonly IUserViewManager _userViewManager;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -450,15 +285,14 @@ namespace MediaBrowser.Api.UserLibrary
|
|||||||
/// <param name="userManager">The user manager.</param>
|
/// <param name="userManager">The user manager.</param>
|
||||||
/// <param name="libraryManager">The library manager.</param>
|
/// <param name="libraryManager">The library manager.</param>
|
||||||
/// <param name="userDataRepository">The user data repository.</param>
|
/// <param name="userDataRepository">The user data repository.</param>
|
||||||
/// <param name="sessionManager">The session manager.</param>
|
|
||||||
/// <param name="dtoService">The dto service.</param>
|
/// <param name="dtoService">The dto service.</param>
|
||||||
|
/// <param name="userViewManager">The user view manager.</param>
|
||||||
/// <exception cref="System.ArgumentNullException">jsonSerializer</exception>
|
/// <exception cref="System.ArgumentNullException">jsonSerializer</exception>
|
||||||
public UserLibraryService(IUserManager userManager, ILibraryManager libraryManager, IUserDataManager userDataRepository, ISessionManager sessionManager, IDtoService dtoService, IUserViewManager userViewManager)
|
public UserLibraryService(IUserManager userManager, ILibraryManager libraryManager, IUserDataManager userDataRepository, IDtoService dtoService, IUserViewManager userViewManager)
|
||||||
{
|
{
|
||||||
_userManager = userManager;
|
_userManager = userManager;
|
||||||
_libraryManager = libraryManager;
|
_libraryManager = libraryManager;
|
||||||
_userDataRepository = userDataRepository;
|
_userDataRepository = userDataRepository;
|
||||||
_sessionManager = sessionManager;
|
|
||||||
_dtoService = dtoService;
|
_dtoService = dtoService;
|
||||||
_userViewManager = userViewManager;
|
_userViewManager = userViewManager;
|
||||||
}
|
}
|
||||||
@ -475,7 +309,98 @@ namespace MediaBrowser.Api.UserLibrary
|
|||||||
return ToOptimizedSerializedResultUsingCache(result);
|
return ToOptimizedSerializedResultUsingCache(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
public object Get(GetUserViews request)
|
public object Get(GetLatestMedia request)
|
||||||
|
{
|
||||||
|
var user = _userManager.GetUserById(request.UserId);
|
||||||
|
|
||||||
|
// Avoid implicitly captured closure
|
||||||
|
var libraryItems = GetAllLibraryItems(request.UserId, _userManager, _libraryManager, request.ParentId)
|
||||||
|
.OrderByDescending(i => i.DateCreated)
|
||||||
|
.Where(i => i.LocationType != LocationType.Virtual);
|
||||||
|
|
||||||
|
|
||||||
|
//if (request.IsFolder.HasValue)
|
||||||
|
//{
|
||||||
|
//var val = request.IsFolder.Value;
|
||||||
|
libraryItems = libraryItems.Where(f => f.IsFolder == false);
|
||||||
|
//}
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(request.IncludeItemTypes))
|
||||||
|
{
|
||||||
|
var vals = request.IncludeItemTypes.Split(',');
|
||||||
|
libraryItems = libraryItems.Where(f => vals.Contains(f.GetType().Name, StringComparer.OrdinalIgnoreCase));
|
||||||
|
}
|
||||||
|
|
||||||
|
var currentUser = user;
|
||||||
|
|
||||||
|
if (request.IsPlayed.HasValue)
|
||||||
|
{
|
||||||
|
var takeLimit = request.Limit * 20;
|
||||||
|
|
||||||
|
var val = request.IsPlayed.Value;
|
||||||
|
libraryItems = libraryItems.Where(f => f.IsPlayed(currentUser) == val)
|
||||||
|
.Take(takeLimit);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Avoid implicitly captured closure
|
||||||
|
var items = libraryItems
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
var list = new List<Tuple<BaseItem, List<BaseItem>>>();
|
||||||
|
|
||||||
|
foreach (var item in items)
|
||||||
|
{
|
||||||
|
// Only grab the index container for media
|
||||||
|
var container = item.IsFolder || !request.GroupItems ? null : item.LatestItemsIndexContainer;
|
||||||
|
|
||||||
|
if (container == null)
|
||||||
|
{
|
||||||
|
list.Add(new Tuple<BaseItem, List<BaseItem>>(null, new List<BaseItem> { item }));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var current = list.FirstOrDefault(i => i.Item1 != null && i.Item1.Id == container.Id);
|
||||||
|
|
||||||
|
if (current != null)
|
||||||
|
{
|
||||||
|
current.Item2.Add(item);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
list.Add(new Tuple<BaseItem, List<BaseItem>>(container, new List<BaseItem> { item }));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (list.Count >= request.Limit)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var fields = request.GetItemFields().ToList();
|
||||||
|
|
||||||
|
var dtos = list.Select(i =>
|
||||||
|
{
|
||||||
|
var item = i.Item2[0];
|
||||||
|
var childCount = 0;
|
||||||
|
|
||||||
|
if (i.Item1 != null && i.Item2.Count > 0)
|
||||||
|
{
|
||||||
|
item = i.Item1;
|
||||||
|
childCount = i.Item2.Count;
|
||||||
|
}
|
||||||
|
|
||||||
|
var dto = _dtoService.GetBaseItemDto(item, fields, user);
|
||||||
|
|
||||||
|
dto.ChildCount = childCount;
|
||||||
|
|
||||||
|
return dto;
|
||||||
|
});
|
||||||
|
|
||||||
|
return ToOptimizedResult(dtos.ToList());
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<object> Get(GetUserViews request)
|
||||||
{
|
{
|
||||||
var user = _userManager.GetUserById(new Guid(request.UserId));
|
var user = _userManager.GetUserById(new Guid(request.UserId));
|
||||||
|
|
||||||
@ -493,10 +418,9 @@ namespace MediaBrowser.Api.UserLibrary
|
|||||||
query.IncludeExternalContent = request.IncludeExternalContent.Value;
|
query.IncludeExternalContent = request.IncludeExternalContent.Value;
|
||||||
}
|
}
|
||||||
|
|
||||||
var folders = _userViewManager.GetUserViews(query, CancellationToken.None).Result;
|
var folders = await _userViewManager.GetUserViews(query, CancellationToken.None).ConfigureAwait(false);
|
||||||
|
|
||||||
var dtos = folders.OrderBy(i => i.SortName)
|
var dtos = folders.Select(i => _dtoService.GetBaseItemDto(i, fields, user))
|
||||||
.Select(i => _dtoService.GetBaseItemDto(i, fields, user))
|
|
||||||
.ToArray();
|
.ToArray();
|
||||||
|
|
||||||
var result = new QueryResult<BaseItemDto>
|
var result = new QueryResult<BaseItemDto>
|
||||||
@ -541,7 +465,8 @@ namespace MediaBrowser.Api.UserLibrary
|
|||||||
if (series != null)
|
if (series != null)
|
||||||
{
|
{
|
||||||
var dtos = series
|
var dtos = series
|
||||||
.GetRecursiveChildren(i => i is Episode && i.ParentIndexNumber.HasValue && i.ParentIndexNumber.Value == 0)
|
.GetRecursiveChildren()
|
||||||
|
.Where(i => i is Episode && i.ParentIndexNumber.HasValue && i.ParentIndexNumber.Value == 0)
|
||||||
.OrderBy(i =>
|
.OrderBy(i =>
|
||||||
{
|
{
|
||||||
if (i.PremiereDate.HasValue)
|
if (i.PremiereDate.HasValue)
|
||||||
@ -714,9 +639,7 @@ namespace MediaBrowser.Api.UserLibrary
|
|||||||
|
|
||||||
await _userDataRepository.SaveUserData(user.Id, item, data, UserDataSaveReason.UpdateUserRating, CancellationToken.None).ConfigureAwait(false);
|
await _userDataRepository.SaveUserData(user.Id, item, data, UserDataSaveReason.UpdateUserRating, CancellationToken.None).ConfigureAwait(false);
|
||||||
|
|
||||||
data = _userDataRepository.GetUserData(user.Id, key);
|
return _userDataRepository.GetUserDataDto(item, user);
|
||||||
|
|
||||||
return _dtoService.GetUserItemDataDto(data);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -763,177 +686,7 @@ namespace MediaBrowser.Api.UserLibrary
|
|||||||
|
|
||||||
await _userDataRepository.SaveUserData(user.Id, item, data, UserDataSaveReason.UpdateUserRating, CancellationToken.None).ConfigureAwait(false);
|
await _userDataRepository.SaveUserData(user.Id, item, data, UserDataSaveReason.UpdateUserRating, CancellationToken.None).ConfigureAwait(false);
|
||||||
|
|
||||||
data = _userDataRepository.GetUserData(user.Id, key);
|
return _userDataRepository.GetUserDataDto(item, user);
|
||||||
|
|
||||||
return _dtoService.GetUserItemDataDto(data);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Posts the specified request.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="request">The request.</param>
|
|
||||||
public object Post(MarkPlayedItem request)
|
|
||||||
{
|
|
||||||
var result = MarkPlayed(request).Result;
|
|
||||||
|
|
||||||
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 = GetSession(_sessionManager);
|
|
||||||
|
|
||||||
var dto = await UpdatePlayedStatus(user, request.Id, true, datePlayed).ConfigureAwait(false);
|
|
||||||
|
|
||||||
foreach (var additionalUserInfo in session.AdditionalUsers)
|
|
||||||
{
|
|
||||||
var additionalUser = _userManager.GetUserById(new Guid(additionalUserInfo.UserId));
|
|
||||||
|
|
||||||
await UpdatePlayedStatus(additionalUser, request.Id, true, datePlayed).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
return dto;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Posts the specified request.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="request">The request.</param>
|
|
||||||
public void Post(OnPlaybackStart request)
|
|
||||||
{
|
|
||||||
var queueableMediaTypes = (request.QueueableMediaTypes ?? string.Empty);
|
|
||||||
|
|
||||||
Post(new ReportPlaybackStart
|
|
||||||
{
|
|
||||||
CanSeek = request.CanSeek,
|
|
||||||
ItemId = request.Id,
|
|
||||||
QueueableMediaTypes = queueableMediaTypes.Split(',').ToList(),
|
|
||||||
MediaSourceId = request.MediaSourceId,
|
|
||||||
AudioStreamIndex = request.AudioStreamIndex,
|
|
||||||
SubtitleStreamIndex = request.SubtitleStreamIndex
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Post(ReportPlaybackStart request)
|
|
||||||
{
|
|
||||||
request.SessionId = GetSession(_sessionManager).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
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Post(ReportPlaybackProgress request)
|
|
||||||
{
|
|
||||||
request.SessionId = GetSession(_sessionManager).Id;
|
|
||||||
|
|
||||||
var task = _sessionManager.OnPlaybackProgress(request);
|
|
||||||
|
|
||||||
Task.WaitAll(task);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <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
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Post(ReportPlaybackStopped request)
|
|
||||||
{
|
|
||||||
request.SessionId = GetSession(_sessionManager).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 = GetSession(_sessionManager);
|
|
||||||
|
|
||||||
var dto = await UpdatePlayedStatus(user, request.Id, false, null).ConfigureAwait(false);
|
|
||||||
|
|
||||||
foreach (var additionalUserInfo in session.AdditionalUsers)
|
|
||||||
{
|
|
||||||
var additionalUser = _userManager.GetUserById(new Guid(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, _userDataRepository).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
await item.MarkUnplayed(user, _userDataRepository).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
return _dtoService.GetUserItemDataDto(_userDataRepository.GetUserData(user.Id, item.GetUserDataKey()));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
using MediaBrowser.Controller.Dto;
|
using MediaBrowser.Controller.Dto;
|
||||||
using MediaBrowser.Controller.Entities;
|
using MediaBrowser.Controller.Entities;
|
||||||
using MediaBrowser.Controller.Library;
|
using MediaBrowser.Controller.Library;
|
||||||
|
using MediaBrowser.Controller.Net;
|
||||||
using MediaBrowser.Controller.Persistence;
|
using MediaBrowser.Controller.Persistence;
|
||||||
using MediaBrowser.Model.Dto;
|
using MediaBrowser.Model.Dto;
|
||||||
using MediaBrowser.Model.Querying;
|
using MediaBrowser.Model.Querying;
|
||||||
@ -43,6 +44,7 @@ namespace MediaBrowser.Api.UserLibrary
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Class YearsService
|
/// Class YearsService
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[Authenticated]
|
||||||
public class YearsService : BaseItemsByNameService<Year>
|
public class YearsService : BaseItemsByNameService<Year>
|
||||||
{
|
{
|
||||||
public YearsService(IUserManager userManager, ILibraryManager libraryManager, IUserDataManager userDataRepository, IItemRepository itemRepo, IDtoService dtoService)
|
public YearsService(IUserManager userManager, ILibraryManager libraryManager, IUserDataManager userDataRepository, IItemRepository itemRepo, IDtoService dtoService)
|
||||||
@ -78,10 +80,10 @@ namespace MediaBrowser.Api.UserLibrary
|
|||||||
{
|
{
|
||||||
var user = UserManager.GetUserById(request.UserId.Value);
|
var user = UserManager.GetUserById(request.UserId.Value);
|
||||||
|
|
||||||
return DtoService.GetItemByNameDto(item, fields.ToList(), user);
|
return DtoService.GetBaseItemDto(item, fields.ToList(), user);
|
||||||
}
|
}
|
||||||
|
|
||||||
return DtoService.GetItemByNameDto(item, fields.ToList());
|
return DtoService.GetBaseItemDto(item, fields.ToList());
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
using MediaBrowser.Common.Extensions;
|
using MediaBrowser.Common.Extensions;
|
||||||
|
using MediaBrowser.Common.Net;
|
||||||
|
using MediaBrowser.Controller.Configuration;
|
||||||
using MediaBrowser.Controller.Dto;
|
using MediaBrowser.Controller.Dto;
|
||||||
using MediaBrowser.Controller.Library;
|
using MediaBrowser.Controller.Library;
|
||||||
|
using MediaBrowser.Controller.Net;
|
||||||
using MediaBrowser.Controller.Session;
|
using MediaBrowser.Controller.Session;
|
||||||
using MediaBrowser.Model.Dto;
|
using MediaBrowser.Model.Dto;
|
||||||
using MediaBrowser.Model.Serialization;
|
|
||||||
using MediaBrowser.Model.Users;
|
using MediaBrowser.Model.Users;
|
||||||
using ServiceStack;
|
using ServiceStack;
|
||||||
using ServiceStack.Text.Controller;
|
using ServiceStack.Text.Controller;
|
||||||
@ -18,6 +20,7 @@ namespace MediaBrowser.Api
|
|||||||
/// Class GetUsers
|
/// Class GetUsers
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Route("/Users", "GET", Summary = "Gets a list of users")]
|
[Route("/Users", "GET", Summary = "Gets a list of users")]
|
||||||
|
[Authenticated]
|
||||||
public class GetUsers : IReturn<List<UserDto>>
|
public class GetUsers : IReturn<List<UserDto>>
|
||||||
{
|
{
|
||||||
[ApiMember(Name = "IsHidden", Description = "Optional filter by IsHidden=true or false", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
|
[ApiMember(Name = "IsHidden", Description = "Optional filter by IsHidden=true or false", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
|
||||||
@ -36,6 +39,7 @@ namespace MediaBrowser.Api
|
|||||||
/// Class GetUser
|
/// Class GetUser
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Route("/Users/{Id}", "GET", Summary = "Gets a user by Id")]
|
[Route("/Users/{Id}", "GET", Summary = "Gets a user by Id")]
|
||||||
|
[Authenticated]
|
||||||
public class GetUser : IReturn<UserDto>
|
public class GetUser : IReturn<UserDto>
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -50,6 +54,7 @@ namespace MediaBrowser.Api
|
|||||||
/// Class DeleteUser
|
/// Class DeleteUser
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Route("/Users/{Id}", "DELETE", Summary = "Deletes a user")]
|
[Route("/Users/{Id}", "DELETE", Summary = "Deletes a user")]
|
||||||
|
[Authenticated]
|
||||||
public class DeleteUser : IReturnVoid
|
public class DeleteUser : IReturnVoid
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -106,6 +111,7 @@ namespace MediaBrowser.Api
|
|||||||
/// Class UpdateUserPassword
|
/// Class UpdateUserPassword
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Route("/Users/{Id}/Password", "POST", Summary = "Updates a user's password")]
|
[Route("/Users/{Id}/Password", "POST", Summary = "Updates a user's password")]
|
||||||
|
[Authenticated]
|
||||||
public class UpdateUserPassword : IReturnVoid
|
public class UpdateUserPassword : IReturnVoid
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -137,6 +143,7 @@ namespace MediaBrowser.Api
|
|||||||
/// Class UpdateUser
|
/// Class UpdateUser
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Route("/Users/{Id}", "POST", Summary = "Updates a user")]
|
[Route("/Users/{Id}", "POST", Summary = "Updates a user")]
|
||||||
|
[Authenticated]
|
||||||
public class UpdateUser : UserDto, IReturnVoid
|
public class UpdateUser : UserDto, IReturnVoid
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
@ -145,6 +152,7 @@ namespace MediaBrowser.Api
|
|||||||
/// Class CreateUser
|
/// Class CreateUser
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Route("/Users", "POST", Summary = "Creates a user")]
|
[Route("/Users", "POST", Summary = "Creates a user")]
|
||||||
|
[Authenticated]
|
||||||
public class CreateUser : UserDto, IReturn<UserDto>
|
public class CreateUser : UserDto, IReturn<UserDto>
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
@ -152,48 +160,68 @@ namespace MediaBrowser.Api
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Class UsersService
|
/// Class UsersService
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class UserService : BaseApiService
|
public class UserService : BaseApiService, IHasAuthorization
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// The _XML serializer
|
|
||||||
/// </summary>
|
|
||||||
private readonly IXmlSerializer _xmlSerializer;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The _user manager
|
/// The _user manager
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private readonly IUserManager _userManager;
|
private readonly IUserManager _userManager;
|
||||||
private readonly IDtoService _dtoService;
|
private readonly IDtoService _dtoService;
|
||||||
private readonly ISessionManager _sessionMananger;
|
private readonly ISessionManager _sessionMananger;
|
||||||
|
private readonly IServerConfigurationManager _config;
|
||||||
|
private readonly INetworkManager _networkManager;
|
||||||
|
|
||||||
|
public IAuthorizationContext AuthorizationContext { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="UserService" /> class.
|
/// Initializes a new instance of the <see cref="UserService" /> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="xmlSerializer">The XML serializer.</param>
|
|
||||||
/// <param name="userManager">The user manager.</param>
|
/// <param name="userManager">The user manager.</param>
|
||||||
/// <param name="dtoService">The dto service.</param>
|
/// <param name="dtoService">The dto service.</param>
|
||||||
|
/// <param name="sessionMananger">The session mananger.</param>
|
||||||
/// <exception cref="System.ArgumentNullException">xmlSerializer</exception>
|
/// <exception cref="System.ArgumentNullException">xmlSerializer</exception>
|
||||||
public UserService(IXmlSerializer xmlSerializer, IUserManager userManager, IDtoService dtoService, ISessionManager sessionMananger)
|
public UserService(IUserManager userManager, IDtoService dtoService, ISessionManager sessionMananger, IServerConfigurationManager config, INetworkManager networkManager)
|
||||||
: base()
|
|
||||||
{
|
{
|
||||||
if (xmlSerializer == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException("xmlSerializer");
|
|
||||||
}
|
|
||||||
|
|
||||||
_xmlSerializer = xmlSerializer;
|
|
||||||
_userManager = userManager;
|
_userManager = userManager;
|
||||||
_dtoService = dtoService;
|
_dtoService = dtoService;
|
||||||
_sessionMananger = sessionMananger;
|
_sessionMananger = sessionMananger;
|
||||||
|
_config = config;
|
||||||
|
_networkManager = networkManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
public object Get(GetPublicUsers request)
|
public object Get(GetPublicUsers request)
|
||||||
{
|
{
|
||||||
|
var authInfo = AuthorizationContext.GetAuthorizationInfo(Request);
|
||||||
|
var isDashboard = string.Equals(authInfo.Client, "Dashboard", StringComparison.OrdinalIgnoreCase);
|
||||||
|
|
||||||
|
if ((Request.IsLocal && isDashboard) ||
|
||||||
|
!_config.Configuration.IsStartupWizardCompleted)
|
||||||
|
{
|
||||||
|
return Get(new GetUsers
|
||||||
|
{
|
||||||
|
IsDisabled = false
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Uncomment this once all clients can handle an empty user list.
|
||||||
return Get(new GetUsers
|
return Get(new GetUsers
|
||||||
{
|
{
|
||||||
IsHidden = false,
|
IsHidden = false,
|
||||||
IsDisabled = false
|
IsDisabled = false
|
||||||
});
|
});
|
||||||
|
|
||||||
|
//// TODO: Add or is authenticated
|
||||||
|
//if (Request.IsLocal || IsInLocalNetwork(Request.RemoteIp))
|
||||||
|
//{
|
||||||
|
// return Get(new GetUsers
|
||||||
|
// {
|
||||||
|
// IsHidden = false,
|
||||||
|
// IsDisabled = false
|
||||||
|
// });
|
||||||
|
//}
|
||||||
|
|
||||||
|
//// Return empty when external
|
||||||
|
//return ToOptimizedResult(new List<UserDto>());
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -217,7 +245,7 @@ namespace MediaBrowser.Api
|
|||||||
|
|
||||||
var result = users
|
var result = users
|
||||||
.OrderBy(u => u.Name)
|
.OrderBy(u => u.Name)
|
||||||
.Select(_dtoService.GetUserDto)
|
.Select(i => _userManager.GetUserDto(i, Request.RemoteIp))
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
return ToOptimizedSerializedResultUsingCache(result);
|
return ToOptimizedSerializedResultUsingCache(result);
|
||||||
@ -237,7 +265,7 @@ namespace MediaBrowser.Api
|
|||||||
throw new ResourceNotFoundException("User not found");
|
throw new ResourceNotFoundException("User not found");
|
||||||
}
|
}
|
||||||
|
|
||||||
var result = _dtoService.GetUserDto(user);
|
var result = _userManager.GetUserDto(user, Request.RemoteIp);
|
||||||
|
|
||||||
return ToOptimizedSerializedResultUsingCache(result);
|
return ToOptimizedSerializedResultUsingCache(result);
|
||||||
}
|
}
|
||||||
@ -247,6 +275,13 @@ namespace MediaBrowser.Api
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="request">The request.</param>
|
/// <param name="request">The request.</param>
|
||||||
public void Delete(DeleteUser request)
|
public void Delete(DeleteUser request)
|
||||||
|
{
|
||||||
|
var task = DeleteAsync(request);
|
||||||
|
|
||||||
|
Task.WaitAll(task);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task DeleteAsync(DeleteUser request)
|
||||||
{
|
{
|
||||||
var user = _userManager.GetUserById(request.Id);
|
var user = _userManager.GetUserById(request.Id);
|
||||||
|
|
||||||
@ -255,9 +290,8 @@ namespace MediaBrowser.Api
|
|||||||
throw new ResourceNotFoundException("User not found");
|
throw new ResourceNotFoundException("User not found");
|
||||||
}
|
}
|
||||||
|
|
||||||
var task = _userManager.DeleteUser(user);
|
await _sessionMananger.RevokeUserTokens(user.Id.ToString("N")).ConfigureAwait(false);
|
||||||
|
await _userManager.DeleteUser(user).ConfigureAwait(false);
|
||||||
Task.WaitAll(task);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -265,28 +299,6 @@ namespace MediaBrowser.Api
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="request">The request.</param>
|
/// <param name="request">The request.</param>
|
||||||
public object Post(AuthenticateUser request)
|
public object Post(AuthenticateUser request)
|
||||||
{
|
|
||||||
// No response needed. Will throw an exception on failure.
|
|
||||||
var result = AuthenticateUser(request).Result;
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
public object Post(AuthenticateUserByName request)
|
|
||||||
{
|
|
||||||
var user = _userManager.Users.FirstOrDefault(i => string.Equals(request.Username, i.Name, StringComparison.OrdinalIgnoreCase));
|
|
||||||
|
|
||||||
if (user == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentException(string.Format("User {0} not found.", request.Username));
|
|
||||||
}
|
|
||||||
|
|
||||||
var result = AuthenticateUser(new AuthenticateUser { Id = user.Id, Password = request.Password }).Result;
|
|
||||||
|
|
||||||
return ToOptimizedResult(result);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task<AuthenticationResult> AuthenticateUser(AuthenticateUser request)
|
|
||||||
{
|
{
|
||||||
var user = _userManager.GetUserById(request.Id);
|
var user = _userManager.GetUserById(request.Id);
|
||||||
|
|
||||||
@ -295,38 +307,47 @@ namespace MediaBrowser.Api
|
|||||||
throw new ResourceNotFoundException("User not found");
|
throw new ResourceNotFoundException("User not found");
|
||||||
}
|
}
|
||||||
|
|
||||||
var auth = AuthorizationRequestFilterAttribute.GetAuthorization(Request);
|
return Post(new AuthenticateUserByName
|
||||||
|
|
||||||
// Login in the old way if the header is missing
|
|
||||||
if (string.IsNullOrEmpty(auth.Client) ||
|
|
||||||
string.IsNullOrEmpty(auth.Device) ||
|
|
||||||
string.IsNullOrEmpty(auth.DeviceId) ||
|
|
||||||
string.IsNullOrEmpty(auth.Version))
|
|
||||||
{
|
{
|
||||||
var success = await _userManager.AuthenticateUser(user, request.Password).ConfigureAwait(false);
|
Username = user.Name,
|
||||||
|
Password = request.Password
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (!success)
|
public async Task<object> Post(AuthenticateUserByName request)
|
||||||
{
|
{
|
||||||
// Unauthorized
|
var auth = AuthorizationContext.GetAuthorizationInfo(Request);
|
||||||
throw new UnauthorizedAccessException("Invalid user or password entered.");
|
|
||||||
}
|
|
||||||
|
|
||||||
return new AuthenticationResult
|
if (string.IsNullOrWhiteSpace(auth.Client))
|
||||||
{
|
{
|
||||||
User = _dtoService.GetUserDto(user)
|
auth.Client = "Unknown app";
|
||||||
};
|
}
|
||||||
|
if (string.IsNullOrWhiteSpace(auth.Device))
|
||||||
|
{
|
||||||
|
auth.Device = "Unknown device";
|
||||||
|
}
|
||||||
|
if (string.IsNullOrWhiteSpace(auth.Version))
|
||||||
|
{
|
||||||
|
auth.Version = "Unknown version";
|
||||||
|
}
|
||||||
|
if (string.IsNullOrWhiteSpace(auth.DeviceId))
|
||||||
|
{
|
||||||
|
auth.DeviceId = "Unknown device id";
|
||||||
}
|
}
|
||||||
|
|
||||||
var session = await _sessionMananger.AuthenticateNewSession(user, request.Password, auth.Client, auth.Version,
|
var result = await _sessionMananger.AuthenticateNewSession(new AuthenticationRequest
|
||||||
auth.DeviceId, auth.Device, Request.RemoteIp).ConfigureAwait(false);
|
|
||||||
|
|
||||||
var result = new AuthenticationResult
|
|
||||||
{
|
{
|
||||||
User = _dtoService.GetUserDto(user),
|
App = auth.Client,
|
||||||
SessionInfo = _sessionMananger.GetSessionInfoDto(session)
|
AppVersion = auth.Version,
|
||||||
};
|
DeviceId = auth.DeviceId,
|
||||||
|
DeviceName = auth.Device,
|
||||||
|
Password = request.Password,
|
||||||
|
RemoteEndPoint = Request.RemoteIp,
|
||||||
|
Username = request.Username
|
||||||
|
|
||||||
return result;
|
}, Request.IsLocal).ConfigureAwait(false);
|
||||||
|
|
||||||
|
return ToOptimizedResult(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -334,6 +355,12 @@ namespace MediaBrowser.Api
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="request">The request.</param>
|
/// <param name="request">The request.</param>
|
||||||
public void Post(UpdateUserPassword request)
|
public void Post(UpdateUserPassword request)
|
||||||
|
{
|
||||||
|
var task = PostAsync(request);
|
||||||
|
Task.WaitAll(task);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task PostAsync(UpdateUserPassword request)
|
||||||
{
|
{
|
||||||
var user = _userManager.GetUserById(request.Id);
|
var user = _userManager.GetUserById(request.Id);
|
||||||
|
|
||||||
@ -344,22 +371,18 @@ namespace MediaBrowser.Api
|
|||||||
|
|
||||||
if (request.ResetPassword)
|
if (request.ResetPassword)
|
||||||
{
|
{
|
||||||
var task = _userManager.ResetPassword(user);
|
await _userManager.ResetPassword(user).ConfigureAwait(false);
|
||||||
|
|
||||||
Task.WaitAll(task);
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var success = _userManager.AuthenticateUser(user, request.CurrentPassword).Result;
|
var success = await _userManager.AuthenticateUser(user.Name, request.CurrentPassword, Request.RemoteIp).ConfigureAwait(false);
|
||||||
|
|
||||||
if (!success)
|
if (!success)
|
||||||
{
|
{
|
||||||
throw new UnauthorizedAccessException("Invalid user or password entered.");
|
throw new ArgumentException("Invalid user or password entered.");
|
||||||
}
|
}
|
||||||
|
|
||||||
var task = _userManager.ChangePassword(user, request.NewPassword);
|
await _userManager.ChangePassword(user, request.NewPassword).ConfigureAwait(false);
|
||||||
|
|
||||||
Task.WaitAll(task);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -368,6 +391,13 @@ namespace MediaBrowser.Api
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="request">The request.</param>
|
/// <param name="request">The request.</param>
|
||||||
public void Post(UpdateUser request)
|
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
|
// 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
|
// https://code.google.com/p/servicestack/source/browse/trunk/Common/ServiceStack.Text/ServiceStack.Text/Controller/PathInfo.cs
|
||||||
@ -400,11 +430,15 @@ namespace MediaBrowser.Api
|
|||||||
{
|
{
|
||||||
throw new ArgumentException("There must be at least one enabled user in the system.");
|
throw new ArgumentException("There must be at least one enabled user in the system.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await _sessionMananger.RevokeUserTokens(user.Id.ToString("N")).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
var task = user.Name.Equals(dtoUser.Name, StringComparison.Ordinal) ? _userManager.UpdateUser(user) : _userManager.RenameUser(user, dtoUser.Name);
|
var task = user.Name.Equals(dtoUser.Name, StringComparison.Ordinal) ?
|
||||||
|
_userManager.UpdateUser(user) :
|
||||||
|
_userManager.RenameUser(user, dtoUser.Name);
|
||||||
|
|
||||||
Task.WaitAll(task);
|
await task.ConfigureAwait(false);
|
||||||
|
|
||||||
user.UpdateConfiguration(dtoUser.Configuration);
|
user.UpdateConfiguration(dtoUser.Configuration);
|
||||||
}
|
}
|
||||||
@ -422,7 +456,7 @@ namespace MediaBrowser.Api
|
|||||||
|
|
||||||
newUser.UpdateConfiguration(dtoUser.Configuration);
|
newUser.UpdateConfiguration(dtoUser.Configuration);
|
||||||
|
|
||||||
var result = _dtoService.GetUserDto(newUser);
|
var result = _userManager.GetUserDto(newUser, Request.RemoteIp);
|
||||||
|
|
||||||
return ToOptimizedResult(result);
|
return ToOptimizedResult(result);
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@ using MediaBrowser.Controller.Configuration;
|
|||||||
using MediaBrowser.Controller.Dto;
|
using MediaBrowser.Controller.Dto;
|
||||||
using MediaBrowser.Controller.Entities;
|
using MediaBrowser.Controller.Entities;
|
||||||
using MediaBrowser.Controller.Library;
|
using MediaBrowser.Controller.Library;
|
||||||
|
using MediaBrowser.Controller.Net;
|
||||||
using MediaBrowser.Controller.Persistence;
|
using MediaBrowser.Controller.Persistence;
|
||||||
using MediaBrowser.Model.Querying;
|
using MediaBrowser.Model.Querying;
|
||||||
using ServiceStack;
|
using ServiceStack;
|
||||||
@ -41,6 +42,7 @@ namespace MediaBrowser.Api
|
|||||||
public string Ids { get; set; }
|
public string Ids { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Authenticated]
|
||||||
public class VideosService : BaseApiService
|
public class VideosService : BaseApiService
|
||||||
{
|
{
|
||||||
private readonly ILibraryManager _libraryManager;
|
private readonly ILibraryManager _libraryManager;
|
||||||
@ -97,12 +99,12 @@ namespace MediaBrowser.Api
|
|||||||
|
|
||||||
public void Delete(DeleteAlternateSources request)
|
public void Delete(DeleteAlternateSources request)
|
||||||
{
|
{
|
||||||
var task = RemoveAlternateVersions(request);
|
var task = DeleteAsync(request);
|
||||||
|
|
||||||
Task.WaitAll(task);
|
Task.WaitAll(task);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task RemoveAlternateVersions(DeleteAlternateSources request)
|
public async Task DeleteAsync(DeleteAlternateSources request)
|
||||||
{
|
{
|
||||||
var video = (Video)_libraryManager.GetItemById(request.Id);
|
var video = (Video)_libraryManager.GetItemById(request.Id);
|
||||||
|
|
||||||
@ -119,12 +121,12 @@ namespace MediaBrowser.Api
|
|||||||
|
|
||||||
public void Post(MergeVersions request)
|
public void Post(MergeVersions request)
|
||||||
{
|
{
|
||||||
var task = MergeVersions(request);
|
var task = PostAsync(request);
|
||||||
|
|
||||||
Task.WaitAll(task);
|
Task.WaitAll(task);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task MergeVersions(MergeVersions request)
|
public async Task PostAsync(MergeVersions request)
|
||||||
{
|
{
|
||||||
var items = request.Ids.Split(',')
|
var items = request.Ids.Split(',')
|
||||||
.Select(i => new Guid(i))
|
.Select(i => new Guid(i))
|
||||||
@ -170,12 +172,12 @@ namespace MediaBrowser.Api
|
|||||||
return 0;
|
return 0;
|
||||||
})
|
})
|
||||||
.ThenByDescending(i =>
|
.ThenByDescending(i =>
|
||||||
{
|
{
|
||||||
var stream = i.GetDefaultVideoStream();
|
var stream = i.GetDefaultVideoStream();
|
||||||
|
|
||||||
return stream == null || stream.Width == null ? 0 : stream.Width.Value;
|
return stream == null || stream.Width == null ? 0 : stream.Width.Value;
|
||||||
|
|
||||||
}).First();
|
}).First();
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var item in videos.Where(i => i.Id != primaryVersion.Id))
|
foreach (var item in videos.Where(i => i.Id != primaryVersion.Id))
|
||||||
|
@ -1,149 +0,0 @@
|
|||||||
using MediaBrowser.Common.IO;
|
|
||||||
using MediaBrowser.Common.Net;
|
|
||||||
using MediaBrowser.Controller.IO;
|
|
||||||
using MediaBrowser.Model.Logging;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace MediaBrowser.Api.WebSocket
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Class ScheduledTasksWebSocketListener
|
|
||||||
/// </summary>
|
|
||||||
public class LogFileWebSocketListener : BasePeriodicWebSocketListener<IEnumerable<string>, LogFileWebSocketState>
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the name.
|
|
||||||
/// </summary>
|
|
||||||
/// <value>The name.</value>
|
|
||||||
protected override string Name
|
|
||||||
{
|
|
||||||
get { return "LogFile"; }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The _kernel
|
|
||||||
/// </summary>
|
|
||||||
private readonly ILogManager _logManager;
|
|
||||||
private readonly IFileSystem _fileSystem;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Initializes a new instance of the <see cref="LogFileWebSocketListener" /> class.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="logger">The logger.</param>
|
|
||||||
/// <param name="logManager">The log manager.</param>
|
|
||||||
public LogFileWebSocketListener(ILogger logger, ILogManager logManager, IFileSystem fileSystem)
|
|
||||||
: base(logger)
|
|
||||||
{
|
|
||||||
_logManager = logManager;
|
|
||||||
_fileSystem = fileSystem;
|
|
||||||
_logManager.LoggerLoaded += kernel_LoggerLoaded;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the data to send.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="state">The state.</param>
|
|
||||||
/// <returns>IEnumerable{System.String}.</returns>
|
|
||||||
protected override async Task<IEnumerable<string>> GetDataToSend(LogFileWebSocketState state)
|
|
||||||
{
|
|
||||||
if (!string.Equals(_logManager.LogFilePath, state.LastLogFilePath))
|
|
||||||
{
|
|
||||||
state.LastLogFilePath = _logManager.LogFilePath;
|
|
||||||
state.StartLine = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
var lines = await GetLogLines(state.LastLogFilePath, state.StartLine, _fileSystem).ConfigureAwait(false);
|
|
||||||
|
|
||||||
state.StartLine += lines.Count;
|
|
||||||
|
|
||||||
return lines;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <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 override void Dispose(bool dispose)
|
|
||||||
{
|
|
||||||
if (dispose)
|
|
||||||
{
|
|
||||||
_logManager.LoggerLoaded -= kernel_LoggerLoaded;
|
|
||||||
}
|
|
||||||
base.Dispose(dispose);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Handles the LoggerLoaded event of the kernel control.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="sender">The source of the event.</param>
|
|
||||||
/// <param name="e">The <see cref="EventArgs" /> instance containing the event data.</param>
|
|
||||||
void kernel_LoggerLoaded(object sender, EventArgs e)
|
|
||||||
{
|
|
||||||
// Reset the startline for each connection whenever the logger reloads
|
|
||||||
lock (ActiveConnections)
|
|
||||||
{
|
|
||||||
foreach (var connection in ActiveConnections)
|
|
||||||
{
|
|
||||||
connection.Item4.StartLine = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the log lines.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="logFilePath">The log file path.</param>
|
|
||||||
/// <param name="startLine">The start line.</param>
|
|
||||||
/// <returns>Task{IEnumerable{System.String}}.</returns>
|
|
||||||
internal static async Task<List<string>> GetLogLines(string logFilePath, int startLine, IFileSystem fileSystem)
|
|
||||||
{
|
|
||||||
var lines = new List<string>();
|
|
||||||
|
|
||||||
using (var fs = fileSystem.GetFileStream(logFilePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, true))
|
|
||||||
{
|
|
||||||
using (var reader = new StreamReader(fs))
|
|
||||||
{
|
|
||||||
while (!reader.EndOfStream)
|
|
||||||
{
|
|
||||||
var line = await reader.ReadLineAsync().ConfigureAwait(false);
|
|
||||||
|
|
||||||
if (line.IndexOf("Info -", StringComparison.OrdinalIgnoreCase) != -1 ||
|
|
||||||
line.IndexOf("Warn -", StringComparison.OrdinalIgnoreCase) != -1 ||
|
|
||||||
line.IndexOf("Error -", StringComparison.OrdinalIgnoreCase) != -1)
|
|
||||||
{
|
|
||||||
lines.Add(line);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (startLine > 0)
|
|
||||||
{
|
|
||||||
lines = lines.Skip(startLine).ToList();
|
|
||||||
}
|
|
||||||
|
|
||||||
return lines;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Class LogFileWebSocketState
|
|
||||||
/// </summary>
|
|
||||||
public class LogFileWebSocketState : WebSocketListenerState
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the last log file path.
|
|
||||||
/// </summary>
|
|
||||||
/// <value>The last log file path.</value>
|
|
||||||
public string LastLogFilePath { get; set; }
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the start line.
|
|
||||||
/// </summary>
|
|
||||||
/// <value>The start line.</value>
|
|
||||||
public int StartLine { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
@ -211,6 +211,8 @@ namespace MediaBrowser.Common.Implementations
|
|||||||
JsonSerializer = CreateJsonSerializer();
|
JsonSerializer = CreateJsonSerializer();
|
||||||
|
|
||||||
Logger = LogManager.GetLogger("App");
|
Logger = LogManager.GetLogger("App");
|
||||||
|
OnLoggerLoaded(true);
|
||||||
|
LogManager.LoggerLoaded += (s, e) => OnLoggerLoaded(false);
|
||||||
|
|
||||||
IsFirstRun = !ConfigurationManager.CommonConfiguration.IsStartupWizardCompleted;
|
IsFirstRun = !ConfigurationManager.CommonConfiguration.IsStartupWizardCompleted;
|
||||||
progress.Report(2);
|
progress.Report(2);
|
||||||
@ -219,16 +221,11 @@ namespace MediaBrowser.Common.Implementations
|
|||||||
? LogSeverity.Debug
|
? LogSeverity.Debug
|
||||||
: LogSeverity.Info;
|
: LogSeverity.Info;
|
||||||
|
|
||||||
// Put the app config in the log for troubleshooting purposes
|
|
||||||
Logger.LogMultiline("Application Configuration:", LogSeverity.Info, new StringBuilder(JsonSerializer.SerializeToString(ConfigurationManager.CommonConfiguration)));
|
|
||||||
|
|
||||||
progress.Report(3);
|
progress.Report(3);
|
||||||
|
|
||||||
DiscoverTypes();
|
DiscoverTypes();
|
||||||
progress.Report(14);
|
progress.Report(14);
|
||||||
|
|
||||||
Logger.Info("Version {0} initializing", ApplicationVersion);
|
|
||||||
|
|
||||||
SetHttpLimit();
|
SetHttpLimit();
|
||||||
progress.Report(15);
|
progress.Report(15);
|
||||||
|
|
||||||
@ -245,6 +242,47 @@ namespace MediaBrowser.Common.Implementations
|
|||||||
progress.Report(100);
|
progress.Report(100);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected virtual void OnLoggerLoaded(bool isFirstLoad)
|
||||||
|
{
|
||||||
|
Logger.Info("Application version: {0}", ApplicationVersion);
|
||||||
|
|
||||||
|
if (!isFirstLoad)
|
||||||
|
{
|
||||||
|
LogEnvironmentInfo(Logger, ApplicationPaths);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Put the app config in the log for troubleshooting purposes
|
||||||
|
Logger.LogMultiline("Application configuration:", LogSeverity.Info, new StringBuilder(JsonSerializer.SerializeToString(ConfigurationManager.CommonConfiguration)));
|
||||||
|
|
||||||
|
if (Plugins != null)
|
||||||
|
{
|
||||||
|
var pluginBuilder = new StringBuilder();
|
||||||
|
|
||||||
|
foreach (var plugin in Plugins)
|
||||||
|
{
|
||||||
|
pluginBuilder.AppendLine(string.Format("{0} {1}", plugin.Name, plugin.Version));
|
||||||
|
}
|
||||||
|
|
||||||
|
Logger.LogMultiline("Plugins:", LogSeverity.Info, pluginBuilder);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void LogEnvironmentInfo(ILogger logger, IApplicationPaths appPaths)
|
||||||
|
{
|
||||||
|
logger.Info("Command line: {0}", string.Join(" ", Environment.GetCommandLineArgs()));
|
||||||
|
|
||||||
|
logger.Info("Server: {0}", Environment.MachineName);
|
||||||
|
logger.Info("Operating system: {0}", Environment.OSVersion.ToString());
|
||||||
|
logger.Info("Processor count: {0}", Environment.ProcessorCount);
|
||||||
|
logger.Info("64-Bit OS: {0}", Environment.Is64BitOperatingSystem);
|
||||||
|
logger.Info("64-Bit Process: {0}", Environment.Is64BitProcess);
|
||||||
|
logger.Info("Program data path: {0}", appPaths.ProgramDataPath);
|
||||||
|
|
||||||
|
logger.Info("Application Path: {0}", appPaths.ApplicationPath);
|
||||||
|
|
||||||
|
logger.Info("*** When reporting issues please include the entire log file. ***".ToUpper());
|
||||||
|
}
|
||||||
|
|
||||||
protected virtual IJsonSerializer CreateJsonSerializer()
|
protected virtual IJsonSerializer CreateJsonSerializer()
|
||||||
{
|
{
|
||||||
return new JsonSerializer();
|
return new JsonSerializer();
|
||||||
@ -342,6 +380,7 @@ namespace MediaBrowser.Common.Implementations
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
protected virtual void FindParts()
|
protected virtual void FindParts()
|
||||||
{
|
{
|
||||||
|
ConfigurationManager.AddParts(GetExports<IConfigurationFactory>());
|
||||||
Plugins = GetExports<IPlugin>();
|
Plugins = GetExports<IPlugin>();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -393,7 +432,7 @@ namespace MediaBrowser.Common.Implementations
|
|||||||
HttpClient = new HttpClientManager.HttpClientManager(ApplicationPaths, Logger, FileSystemManager, ConfigurationManager);
|
HttpClient = new HttpClientManager.HttpClientManager(ApplicationPaths, Logger, FileSystemManager, ConfigurationManager);
|
||||||
RegisterSingleInstance(HttpClient);
|
RegisterSingleInstance(HttpClient);
|
||||||
|
|
||||||
NetworkManager = CreateNetworkManager();
|
NetworkManager = CreateNetworkManager(LogManager.GetLogger("NetworkManager"));
|
||||||
RegisterSingleInstance(NetworkManager);
|
RegisterSingleInstance(NetworkManager);
|
||||||
|
|
||||||
SecurityManager = new PluginSecurityManager(this, HttpClient, JsonSerializer, ApplicationPaths, NetworkManager, LogManager);
|
SecurityManager = new PluginSecurityManager(this, HttpClient, JsonSerializer, ApplicationPaths, NetworkManager, LogManager);
|
||||||
@ -461,7 +500,7 @@ namespace MediaBrowser.Common.Implementations
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected abstract INetworkManager CreateNetworkManager();
|
protected abstract INetworkManager CreateNetworkManager(ILogger logger);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates an instance of type and resolves all constructor dependancies
|
/// Creates an instance of type and resolves all constructor dependancies
|
||||||
@ -631,6 +670,7 @@ namespace MediaBrowser.Common.Implementations
|
|||||||
return parts;
|
return parts;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Version _version;
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the current application version
|
/// Gets the current application version
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -639,7 +679,7 @@ namespace MediaBrowser.Common.Implementations
|
|||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
return GetType().Assembly.GetName().Version;
|
return _version ?? (_version = GetType().Assembly.GetName().Version);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,10 +1,13 @@
|
|||||||
using System.IO;
|
using MediaBrowser.Common.Configuration;
|
||||||
using MediaBrowser.Common.Configuration;
|
|
||||||
using MediaBrowser.Common.Events;
|
using MediaBrowser.Common.Events;
|
||||||
using MediaBrowser.Model.Configuration;
|
using MediaBrowser.Model.Configuration;
|
||||||
using MediaBrowser.Model.Logging;
|
using MediaBrowser.Model.Logging;
|
||||||
using MediaBrowser.Model.Serialization;
|
using MediaBrowser.Model.Serialization;
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Concurrent;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
|
||||||
namespace MediaBrowser.Common.Implementations.Configuration
|
namespace MediaBrowser.Common.Implementations.Configuration
|
||||||
@ -25,6 +28,16 @@ namespace MediaBrowser.Common.Implementations.Configuration
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public event EventHandler<EventArgs> ConfigurationUpdated;
|
public event EventHandler<EventArgs> ConfigurationUpdated;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Occurs when [configuration updating].
|
||||||
|
/// </summary>
|
||||||
|
public event EventHandler<ConfigurationUpdateEventArgs> NamedConfigurationUpdating;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Occurs when [named configuration updated].
|
||||||
|
/// </summary>
|
||||||
|
public event EventHandler<ConfigurationUpdateEventArgs> NamedConfigurationUpdated;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the logger.
|
/// Gets the logger.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -74,6 +87,9 @@ namespace MediaBrowser.Common.Implementations.Configuration
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private ConfigurationStore[] _configurationStores = {};
|
||||||
|
private IConfigurationFactory[] _configurationFactories;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="BaseConfigurationManager" /> class.
|
/// Initializes a new instance of the <see cref="BaseConfigurationManager" /> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -89,10 +105,14 @@ namespace MediaBrowser.Common.Implementations.Configuration
|
|||||||
UpdateCachePath();
|
UpdateCachePath();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
public void AddParts(IEnumerable<IConfigurationFactory> factories)
|
||||||
/// The _save lock
|
{
|
||||||
/// </summary>
|
_configurationFactories = factories.ToArray();
|
||||||
private readonly object _configurationSaveLock = new object();
|
|
||||||
|
_configurationStores = _configurationFactories
|
||||||
|
.SelectMany(i => i.GetConfigurations())
|
||||||
|
.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Saves the configuration.
|
/// Saves the configuration.
|
||||||
@ -103,7 +123,7 @@ namespace MediaBrowser.Common.Implementations.Configuration
|
|||||||
|
|
||||||
Directory.CreateDirectory(Path.GetDirectoryName(path));
|
Directory.CreateDirectory(Path.GetDirectoryName(path));
|
||||||
|
|
||||||
lock (_configurationSaveLock)
|
lock (_configurationSyncLock)
|
||||||
{
|
{
|
||||||
XmlSerializer.SerializeToFile(CommonConfiguration, path);
|
XmlSerializer.SerializeToFile(CommonConfiguration, path);
|
||||||
}
|
}
|
||||||
@ -168,5 +188,70 @@ namespace MediaBrowser.Common.Implementations.Configuration
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private readonly ConcurrentDictionary<string, object> _configurations = new ConcurrentDictionary<string, object>();
|
||||||
|
|
||||||
|
private string GetConfigurationFile(string key)
|
||||||
|
{
|
||||||
|
return Path.Combine(CommonApplicationPaths.ConfigurationDirectoryPath, key.ToLower() + ".xml");
|
||||||
|
}
|
||||||
|
|
||||||
|
public object GetConfiguration(string key)
|
||||||
|
{
|
||||||
|
return _configurations.GetOrAdd(key, k =>
|
||||||
|
{
|
||||||
|
var file = GetConfigurationFile(key);
|
||||||
|
|
||||||
|
var configurationType = _configurationStores
|
||||||
|
.First(i => string.Equals(i.Key, key, StringComparison.OrdinalIgnoreCase))
|
||||||
|
.ConfigurationType;
|
||||||
|
|
||||||
|
lock (_configurationSyncLock)
|
||||||
|
{
|
||||||
|
return ConfigurationHelper.GetXmlConfiguration(configurationType, file, XmlSerializer);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SaveConfiguration(string key, object configuration)
|
||||||
|
{
|
||||||
|
var configurationType = GetConfigurationType(key);
|
||||||
|
|
||||||
|
if (configuration.GetType() != configurationType)
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Expected configuration type is " + configurationType.Name);
|
||||||
|
}
|
||||||
|
|
||||||
|
EventHelper.FireEventIfNotNull(NamedConfigurationUpdating, this, new ConfigurationUpdateEventArgs
|
||||||
|
{
|
||||||
|
Key = key,
|
||||||
|
NewConfiguration = configuration
|
||||||
|
|
||||||
|
}, Logger);
|
||||||
|
|
||||||
|
_configurations.AddOrUpdate(key, configuration, (k, v) => configuration);
|
||||||
|
|
||||||
|
var path = GetConfigurationFile(key);
|
||||||
|
Directory.CreateDirectory(Path.GetDirectoryName(path));
|
||||||
|
|
||||||
|
lock (_configurationSyncLock)
|
||||||
|
{
|
||||||
|
XmlSerializer.SerializeToFile(configuration, path);
|
||||||
|
}
|
||||||
|
|
||||||
|
EventHelper.FireEventIfNotNull(NamedConfigurationUpdated, this, new ConfigurationUpdateEventArgs
|
||||||
|
{
|
||||||
|
Key = key,
|
||||||
|
NewConfiguration = configuration
|
||||||
|
|
||||||
|
}, Logger);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Type GetConfigurationType(string key)
|
||||||
|
{
|
||||||
|
return _configurationStores
|
||||||
|
.First(i => string.Equals(i.Key, key, StringComparison.OrdinalIgnoreCase))
|
||||||
|
.ConfigurationType;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -107,7 +107,6 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager
|
|||||||
return client;
|
return client;
|
||||||
}
|
}
|
||||||
|
|
||||||
private PropertyInfo _httpBehaviorPropertyInfo;
|
|
||||||
private WebRequest GetRequest(HttpRequestOptions options, string method, bool enableHttpCompression)
|
private WebRequest GetRequest(HttpRequestOptions options, string method, bool enableHttpCompression)
|
||||||
{
|
{
|
||||||
var request = (HttpWebRequest)WebRequest.Create(options.Url);
|
var request = (HttpWebRequest)WebRequest.Create(options.Url);
|
||||||
@ -118,7 +117,11 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager
|
|||||||
|
|
||||||
request.CachePolicy = new RequestCachePolicy(RequestCacheLevel.BypassCache);
|
request.CachePolicy = new RequestCachePolicy(RequestCacheLevel.BypassCache);
|
||||||
|
|
||||||
request.KeepAlive = options.EnableKeepAlive;
|
if (options.EnableKeepAlive)
|
||||||
|
{
|
||||||
|
request.KeepAlive = true;
|
||||||
|
}
|
||||||
|
|
||||||
request.Method = method;
|
request.Method = method;
|
||||||
request.Pipelined = true;
|
request.Pipelined = true;
|
||||||
request.Timeout = 20000;
|
request.Timeout = 20000;
|
||||||
@ -133,21 +136,6 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager
|
|||||||
request.Referer = options.Referer;
|
request.Referer = options.Referer;
|
||||||
}
|
}
|
||||||
|
|
||||||
#if !__MonoCS__
|
|
||||||
if (options.EnableKeepAlive)
|
|
||||||
{
|
|
||||||
// This is a hack to prevent KeepAlive from getting disabled internally by the HttpWebRequest
|
|
||||||
// May need to remove this for mono
|
|
||||||
var sp = request.ServicePoint;
|
|
||||||
if (_httpBehaviorPropertyInfo == null)
|
|
||||||
{
|
|
||||||
_httpBehaviorPropertyInfo = sp.GetType().GetProperty("HttpBehaviour", BindingFlags.Instance | BindingFlags.NonPublic);
|
|
||||||
}
|
|
||||||
|
|
||||||
_httpBehaviorPropertyInfo.SetValue(sp, (byte)0, null);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
return request;
|
return request;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -367,5 +367,20 @@ namespace MediaBrowser.Common.Implementations.IO
|
|||||||
|
|
||||||
return newPath;
|
return newPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public string GetFileNameWithoutExtension(FileSystemInfo info)
|
||||||
|
{
|
||||||
|
if (info is DirectoryInfo)
|
||||||
|
{
|
||||||
|
return info.Name;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Path.GetFileNameWithoutExtension(info.FullName);
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetFileNameWithoutExtension(string path)
|
||||||
|
{
|
||||||
|
return Path.GetFileNameWithoutExtension(path);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -48,17 +48,17 @@
|
|||||||
<RunPostBuildEvent>Always</RunPostBuildEvent>
|
<RunPostBuildEvent>Always</RunPostBuildEvent>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Reference Include="NLog, Version=3.0.0.0, Culture=neutral, PublicKeyToken=5120e14c03d0593c, processorArchitecture=MSIL">
|
<Reference Include="NLog, Version=3.1.0.0, Culture=neutral, PublicKeyToken=5120e14c03d0593c, processorArchitecture=MSIL">
|
||||||
<SpecificVersion>False</SpecificVersion>
|
<SpecificVersion>False</SpecificVersion>
|
||||||
<HintPath>..\packages\NLog.3.0.0.0\lib\net45\NLog.dll</HintPath>
|
<HintPath>..\packages\NLog.3.1.0.0\lib\net45\NLog.dll</HintPath>
|
||||||
</Reference>
|
</Reference>
|
||||||
<Reference Include="SimpleInjector, Version=2.5.0.0, Culture=neutral, PublicKeyToken=984cb50dea722e99, processorArchitecture=MSIL">
|
<Reference Include="SimpleInjector, Version=2.5.0.0, Culture=neutral, PublicKeyToken=984cb50dea722e99, processorArchitecture=MSIL">
|
||||||
<SpecificVersion>False</SpecificVersion>
|
<SpecificVersion>False</SpecificVersion>
|
||||||
<HintPath>..\packages\SimpleInjector.2.5.0\lib\net45\SimpleInjector.dll</HintPath>
|
<HintPath>..\packages\SimpleInjector.2.5.2\lib\net45\SimpleInjector.dll</HintPath>
|
||||||
</Reference>
|
</Reference>
|
||||||
<Reference Include="SimpleInjector.Diagnostics, Version=2.5.0.0, Culture=neutral, PublicKeyToken=984cb50dea722e99, processorArchitecture=MSIL">
|
<Reference Include="SimpleInjector.Diagnostics, Version=2.5.2.0, Culture=neutral, PublicKeyToken=984cb50dea722e99, processorArchitecture=MSIL">
|
||||||
<SpecificVersion>False</SpecificVersion>
|
<SpecificVersion>False</SpecificVersion>
|
||||||
<HintPath>..\packages\SimpleInjector.2.5.0\lib\net45\SimpleInjector.Diagnostics.dll</HintPath>
|
<HintPath>..\packages\SimpleInjector.2.5.2\lib\net45\SimpleInjector.Diagnostics.dll</HintPath>
|
||||||
</Reference>
|
</Reference>
|
||||||
<Reference Include="System" />
|
<Reference Include="System" />
|
||||||
<Reference Include="System.Configuration" />
|
<Reference Include="System.Configuration" />
|
||||||
@ -122,7 +122,7 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup />
|
<ItemGroup />
|
||||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||||
<Import Project="$(SolutionDir)\.nuget\nuget.targets" Condition=" '$(ConfigurationName)' != 'Release Mono' " />
|
<Import Project="$(SolutionDir)\.nuget\NuGet.targets" />
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<PostBuildEvent Condition=" '$(ConfigurationName)' != 'Release Mono' ">if '$(ConfigurationName)' == 'Release' (
|
<PostBuildEvent Condition=" '$(ConfigurationName)' != 'Release Mono' ">if '$(ConfigurationName)' == 'Release' (
|
||||||
xcopy "$(TargetPath)" "$(SolutionDir)\Nuget\dlls\" /y /d /r /i
|
xcopy "$(TargetPath)" "$(SolutionDir)\Nuget\dlls\" /y /d /r /i
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
using System;
|
using MediaBrowser.Model.Logging;
|
||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
@ -10,6 +11,13 @@ namespace MediaBrowser.Common.Implementations.Networking
|
|||||||
{
|
{
|
||||||
public abstract class BaseNetworkManager
|
public abstract class BaseNetworkManager
|
||||||
{
|
{
|
||||||
|
protected ILogger Logger { get; private set; }
|
||||||
|
|
||||||
|
protected BaseNetworkManager(ILogger logger)
|
||||||
|
{
|
||||||
|
Logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the machine's local ip address
|
/// Gets the machine's local ip address
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -26,6 +34,81 @@ namespace MediaBrowser.Common.Implementations.Networking
|
|||||||
return GetLocalIpAddressesFallback();
|
return GetLocalIpAddressesFallback();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool IsInLocalNetwork(string endpoint)
|
||||||
|
{
|
||||||
|
return IsInLocalNetworkInternal(endpoint, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsInLocalNetworkInternal(string endpoint, bool resolveHost)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(endpoint))
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException("endpoint");
|
||||||
|
}
|
||||||
|
|
||||||
|
const int lengthMatch = 4;
|
||||||
|
|
||||||
|
if (endpoint.Length >= lengthMatch)
|
||||||
|
{
|
||||||
|
var prefix = endpoint.Substring(0, lengthMatch);
|
||||||
|
|
||||||
|
if (GetLocalIpAddresses()
|
||||||
|
.Any(i => i.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Private address space:
|
||||||
|
// http://en.wikipedia.org/wiki/Private_network
|
||||||
|
|
||||||
|
var isPrivate =
|
||||||
|
|
||||||
|
// If url was requested with computer name, we may see this
|
||||||
|
endpoint.IndexOf("::", StringComparison.OrdinalIgnoreCase) != -1 ||
|
||||||
|
|
||||||
|
endpoint.StartsWith("10.", StringComparison.OrdinalIgnoreCase) ||
|
||||||
|
endpoint.StartsWith("192.", StringComparison.OrdinalIgnoreCase) ||
|
||||||
|
endpoint.StartsWith("172.", StringComparison.OrdinalIgnoreCase) ||
|
||||||
|
endpoint.StartsWith("169.", StringComparison.OrdinalIgnoreCase);
|
||||||
|
|
||||||
|
if (isPrivate)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
IPAddress address;
|
||||||
|
if (resolveHost && !IPAddress.TryParse(endpoint, out address))
|
||||||
|
{
|
||||||
|
var host = new Uri(endpoint).DnsSafeHost;
|
||||||
|
|
||||||
|
Logger.Debug("Resolving host {0}", host);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
address = GetIpAddresses(host).FirstOrDefault();
|
||||||
|
|
||||||
|
if (address != null)
|
||||||
|
{
|
||||||
|
Logger.Debug("{0} resolved to {1}", host, address);
|
||||||
|
|
||||||
|
return IsInLocalNetworkInternal(address.ToString(), false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logger.ErrorException("Error resovling hostname {0}", ex, host);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerable<IPAddress> GetIpAddresses(string hostName)
|
||||||
|
{
|
||||||
|
return Dns.GetHostAddresses(hostName);
|
||||||
|
}
|
||||||
|
|
||||||
private IEnumerable<IPAddress> GetIPsDefault()
|
private IEnumerable<IPAddress> GetIPsDefault()
|
||||||
{
|
{
|
||||||
foreach (var adapter in NetworkInterface.GetAllNetworkInterfaces())
|
foreach (var adapter in NetworkInterface.GetAllNetworkInterfaces())
|
||||||
|
@ -547,6 +547,7 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks
|
|||||||
if (ex != null)
|
if (ex != null)
|
||||||
{
|
{
|
||||||
result.ErrorMessage = ex.Message;
|
result.ErrorMessage = ex.Message;
|
||||||
|
result.LongErrorMessage = ex.StackTrace;
|
||||||
}
|
}
|
||||||
|
|
||||||
var path = GetHistoryFilePath();
|
var path = GetHistoryFilePath();
|
||||||
|
@ -46,7 +46,7 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks.Tasks
|
|||||||
return new ITaskTrigger[] {
|
return new ITaskTrigger[] {
|
||||||
|
|
||||||
// At startup
|
// At startup
|
||||||
new StartupTrigger (),
|
new StartupTrigger {DelayMs = 60000},
|
||||||
|
|
||||||
// Every so often
|
// Every so often
|
||||||
new IntervalTrigger { Interval = TimeSpan.FromHours(24)}
|
new IntervalTrigger { Interval = TimeSpan.FromHours(24)}
|
||||||
|
@ -43,7 +43,7 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks.Tasks
|
|||||||
return new ITaskTrigger[] {
|
return new ITaskTrigger[] {
|
||||||
|
|
||||||
// At startup
|
// At startup
|
||||||
new StartupTrigger (),
|
new StartupTrigger {DelayMs = 30000},
|
||||||
|
|
||||||
// Every so often
|
// Every so often
|
||||||
new IntervalTrigger { Interval = TimeSpan.FromHours(24)}
|
new IntervalTrigger { Interval = TimeSpan.FromHours(24)}
|
||||||
|
@ -41,7 +41,7 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks.Tasks
|
|||||||
return new ITaskTrigger[] {
|
return new ITaskTrigger[] {
|
||||||
|
|
||||||
// At startup
|
// At startup
|
||||||
new StartupTrigger (),
|
new StartupTrigger(),
|
||||||
|
|
||||||
// Every so often
|
// Every so often
|
||||||
new IntervalTrigger { Interval = TimeSpan.FromHours(24)}
|
new IntervalTrigger { Interval = TimeSpan.FromHours(24)}
|
||||||
|
@ -52,7 +52,7 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks.Tasks
|
|||||||
return new ITaskTrigger[] {
|
return new ITaskTrigger[] {
|
||||||
|
|
||||||
// At startup
|
// At startup
|
||||||
new StartupTrigger (),
|
new StartupTrigger(),
|
||||||
|
|
||||||
// Every so often
|
// Every so often
|
||||||
new IntervalTrigger { Interval = TimeSpan.FromHours(24)}
|
new IntervalTrigger { Interval = TimeSpan.FromHours(24)}
|
||||||
|
@ -26,16 +26,13 @@ namespace MediaBrowser.Common.Implementations.Security
|
|||||||
|
|
||||||
var mac = _networkManager.GetMacAddress();
|
var mac = _networkManager.GetMacAddress();
|
||||||
|
|
||||||
var plugins = string.Join("|", _applicationHost.Plugins.Select(i => i.Name).ToArray());
|
|
||||||
|
|
||||||
var data = new Dictionary<string, string>
|
var data = new Dictionary<string, string>
|
||||||
{
|
{
|
||||||
{ "feature", _applicationHost.Name },
|
{ "feature", _applicationHost.Name },
|
||||||
{ "mac", mac },
|
{ "mac", mac },
|
||||||
{ "ver", _applicationHost.ApplicationVersion.ToString() },
|
{ "ver", _applicationHost.ApplicationVersion.ToString() },
|
||||||
{ "platform", Environment.OSVersion.VersionString },
|
{ "platform", Environment.OSVersion.VersionString },
|
||||||
{ "isservice", _applicationHost.IsRunningAsService.ToString().ToLower()},
|
{ "isservice", _applicationHost.IsRunningAsService.ToString().ToLower()}
|
||||||
{ "plugins", plugins}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return _httpClient.Post(Constants.Constants.MbAdminUrl + "service/registration/ping", data, cancellationToken);
|
return _httpClient.Post(Constants.Constants.MbAdminUrl + "service/registration/ping", data, cancellationToken);
|
||||||
|
@ -210,25 +210,5 @@ namespace MediaBrowser.Common.Implementations.Serialization
|
|||||||
|
|
||||||
return ServiceStack.Text.JsonSerializer.SerializeToString(obj, obj.GetType());
|
return ServiceStack.Text.JsonSerializer.SerializeToString(obj, obj.GetType());
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Serializes to bytes.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="obj">The obj.</param>
|
|
||||||
/// <returns>System.Byte[][].</returns>
|
|
||||||
/// <exception cref="System.ArgumentNullException">obj</exception>
|
|
||||||
public byte[] SerializeToBytes(object obj)
|
|
||||||
{
|
|
||||||
if (obj == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException("obj");
|
|
||||||
}
|
|
||||||
|
|
||||||
using (var stream = new MemoryStream())
|
|
||||||
{
|
|
||||||
SerializeToStream(obj, stream);
|
|
||||||
return stream.ToArray();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -91,20 +91,5 @@ namespace MediaBrowser.Common.Implementations.Serialization
|
|||||||
return DeserializeFromStream(type, stream);
|
return DeserializeFromStream(type, stream);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Serializes to bytes.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="obj">The obj.</param>
|
|
||||||
/// <returns>System.Byte[][].</returns>
|
|
||||||
public byte[] SerializeToBytes(object obj)
|
|
||||||
{
|
|
||||||
using (var stream = new MemoryStream())
|
|
||||||
{
|
|
||||||
SerializeToStream(obj, stream);
|
|
||||||
|
|
||||||
return stream.ToArray();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -68,7 +68,7 @@ namespace MediaBrowser.Common.Implementations.Updates
|
|||||||
/// <param name="newVersion">The new version.</param>
|
/// <param name="newVersion">The new version.</param>
|
||||||
private void OnPluginUpdated(IPlugin plugin, PackageVersionInfo newVersion)
|
private void OnPluginUpdated(IPlugin plugin, PackageVersionInfo newVersion)
|
||||||
{
|
{
|
||||||
_logger.Info("Plugin updated: {0} {1} {2}", newVersion.name, newVersion.version, newVersion.classification);
|
_logger.Info("Plugin updated: {0} {1} {2}", newVersion.name, newVersion.versionStr ?? string.Empty, newVersion.classification);
|
||||||
|
|
||||||
EventHelper.FireEventIfNotNull(PluginUpdated, this, new GenericEventArgs<Tuple<IPlugin, PackageVersionInfo>> { Argument = new Tuple<IPlugin, PackageVersionInfo>(plugin, newVersion) }, _logger);
|
EventHelper.FireEventIfNotNull(PluginUpdated, this, new GenericEventArgs<Tuple<IPlugin, PackageVersionInfo>> { Argument = new Tuple<IPlugin, PackageVersionInfo>(plugin, newVersion) }, _logger);
|
||||||
|
|
||||||
@ -87,7 +87,7 @@ namespace MediaBrowser.Common.Implementations.Updates
|
|||||||
/// <param name="package">The package.</param>
|
/// <param name="package">The package.</param>
|
||||||
private void OnPluginInstalled(PackageVersionInfo package)
|
private void OnPluginInstalled(PackageVersionInfo package)
|
||||||
{
|
{
|
||||||
_logger.Info("New plugin installed: {0} {1} {2}", package.name, package.version, package.classification);
|
_logger.Info("New plugin installed: {0} {1} {2}", package.name, package.versionStr ?? string.Empty, package.classification);
|
||||||
|
|
||||||
EventHelper.FireEventIfNotNull(PluginInstalled, this, new GenericEventArgs<PackageVersionInfo> { Argument = package }, _logger);
|
EventHelper.FireEventIfNotNull(PluginInstalled, this, new GenericEventArgs<PackageVersionInfo> { Argument = package }, _logger);
|
||||||
|
|
||||||
@ -133,6 +133,16 @@ namespace MediaBrowser.Common.Implementations.Updates
|
|||||||
_logger = logger;
|
_logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Version GetPackageVersion(PackageVersionInfo version)
|
||||||
|
{
|
||||||
|
return new Version(ValueOrDefault(version.versionStr, "0.0.0.1"));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string ValueOrDefault(string str, string def)
|
||||||
|
{
|
||||||
|
return string.IsNullOrEmpty(str) ? def : str;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets all available packages.
|
/// Gets all available packages.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -167,10 +177,20 @@ namespace MediaBrowser.Common.Implementations.Updates
|
|||||||
{
|
{
|
||||||
if (_lastPackageListResult != null)
|
if (_lastPackageListResult != null)
|
||||||
{
|
{
|
||||||
// Let dev users get results more often for testing purposes
|
TimeSpan cacheLength;
|
||||||
var cacheLength = _config.CommonConfiguration.SystemUpdateLevel == PackageVersionClass.Dev
|
|
||||||
? TimeSpan.FromMinutes(3)
|
switch (_config.CommonConfiguration.SystemUpdateLevel)
|
||||||
: TimeSpan.FromHours(6);
|
{
|
||||||
|
case PackageVersionClass.Beta:
|
||||||
|
cacheLength = TimeSpan.FromMinutes(30);
|
||||||
|
break;
|
||||||
|
case PackageVersionClass.Dev:
|
||||||
|
cacheLength = TimeSpan.FromMinutes(3);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
cacheLength = TimeSpan.FromHours(6);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
if ((DateTime.UtcNow - _lastPackageListResult.Item2) < cacheLength)
|
if ((DateTime.UtcNow - _lastPackageListResult.Item2) < cacheLength)
|
||||||
{
|
{
|
||||||
@ -197,7 +217,7 @@ namespace MediaBrowser.Common.Implementations.Updates
|
|||||||
foreach (var package in packages)
|
foreach (var package in packages)
|
||||||
{
|
{
|
||||||
package.versions = package.versions.Where(v => !string.IsNullOrWhiteSpace(v.sourceUrl))
|
package.versions = package.versions.Where(v => !string.IsNullOrWhiteSpace(v.sourceUrl))
|
||||||
.OrderByDescending(v => v.version).ToList();
|
.OrderByDescending(GetPackageVersion).ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove packages with no versions
|
// Remove packages with no versions
|
||||||
@ -211,7 +231,7 @@ namespace MediaBrowser.Common.Implementations.Updates
|
|||||||
foreach (var package in packages)
|
foreach (var package in packages)
|
||||||
{
|
{
|
||||||
package.versions = package.versions.Where(v => !string.IsNullOrWhiteSpace(v.sourceUrl))
|
package.versions = package.versions.Where(v => !string.IsNullOrWhiteSpace(v.sourceUrl))
|
||||||
.OrderByDescending(v => v.version).ToList();
|
.OrderByDescending(GetPackageVersion).ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (packageType.HasValue)
|
if (packageType.HasValue)
|
||||||
@ -272,7 +292,7 @@ namespace MediaBrowser.Common.Implementations.Updates
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return package.versions.FirstOrDefault(v => v.version.Equals(version) && v.classification == classification);
|
return package.versions.FirstOrDefault(v => GetPackageVersion(v).Equals(version) && v.classification == classification);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -309,7 +329,7 @@ namespace MediaBrowser.Common.Implementations.Updates
|
|||||||
}
|
}
|
||||||
|
|
||||||
return package.versions
|
return package.versions
|
||||||
.OrderByDescending(v => v.version)
|
.OrderByDescending(GetPackageVersion)
|
||||||
.FirstOrDefault(v => v.classification <= classification && IsPackageVersionUpToDate(v, currentServerVersion));
|
.FirstOrDefault(v => v.classification <= classification && IsPackageVersionUpToDate(v, currentServerVersion));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -338,7 +358,7 @@ namespace MediaBrowser.Common.Implementations.Updates
|
|||||||
{
|
{
|
||||||
var latestPluginInfo = GetLatestCompatibleVersion(catalog, p.Name, p.Id.ToString(), applicationVersion, _config.CommonConfiguration.SystemUpdateLevel);
|
var latestPluginInfo = GetLatestCompatibleVersion(catalog, p.Name, p.Id.ToString(), applicationVersion, _config.CommonConfiguration.SystemUpdateLevel);
|
||||||
|
|
||||||
return latestPluginInfo != null && latestPluginInfo.version != null && latestPluginInfo.version > p.Version ? latestPluginInfo : null;
|
return latestPluginInfo != null && GetPackageVersion(latestPluginInfo) > p.Version ? latestPluginInfo : null;
|
||||||
|
|
||||||
}).Where(i => i != null).ToList();
|
}).Where(i => i != null).ToList();
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<packages>
|
<packages>
|
||||||
<package id="NLog" version="3.0.0.0" targetFramework="net45" />
|
<package id="NLog" version="3.1.0.0" targetFramework="net45" />
|
||||||
<package id="sharpcompress" version="0.10.2" targetFramework="net45" />
|
<package id="sharpcompress" version="0.10.2" targetFramework="net45" />
|
||||||
<package id="SimpleInjector" version="2.5.0" targetFramework="net45" />
|
<package id="SimpleInjector" version="2.5.2" targetFramework="net45" />
|
||||||
</packages>
|
</packages>
|
@ -36,22 +36,26 @@ namespace MediaBrowser.Common.Configuration
|
|||||||
configuration = Activator.CreateInstance(type);
|
configuration = Activator.CreateInstance(type);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Take the object we just got and serialize it back to bytes
|
using (var stream = new MemoryStream())
|
||||||
var newBytes = xmlSerializer.SerializeToBytes(configuration);
|
|
||||||
|
|
||||||
// If the file didn't exist before, or if something has changed, re-save
|
|
||||||
if (buffer == null || !buffer.SequenceEqual(newBytes))
|
|
||||||
{
|
{
|
||||||
Directory.CreateDirectory(Path.GetDirectoryName(path));
|
xmlSerializer.SerializeToStream(configuration, stream);
|
||||||
|
|
||||||
// Save it after load in case we got new items
|
// Take the object we just got and serialize it back to bytes
|
||||||
File.WriteAllBytes(path, newBytes);
|
var newBytes = stream.ToArray();
|
||||||
|
|
||||||
|
// If the file didn't exist before, or if something has changed, re-save
|
||||||
|
if (buffer == null || !buffer.SequenceEqual(newBytes))
|
||||||
|
{
|
||||||
|
Directory.CreateDirectory(Path.GetDirectoryName(path));
|
||||||
|
|
||||||
|
// Save it after load in case we got new items
|
||||||
|
File.WriteAllBytes(path, newBytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
return configuration;
|
||||||
}
|
}
|
||||||
|
|
||||||
return configuration;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Reads an xml configuration file from the file system
|
/// Reads an xml configuration file from the file system
|
||||||
/// It will immediately save the configuration after loading it, just
|
/// It will immediately save the configuration after loading it, just
|
||||||
|
@ -0,0 +1,18 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace MediaBrowser.Common.Configuration
|
||||||
|
{
|
||||||
|
public class ConfigurationUpdateEventArgs : EventArgs
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the key.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The key.</value>
|
||||||
|
public string Key { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the new configuration.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The new configuration.</value>
|
||||||
|
public object NewConfiguration { get; set; }
|
||||||
|
}
|
||||||
|
}
|
17
MediaBrowser.Common/Configuration/IConfigurationFactory.cs
Normal file
17
MediaBrowser.Common/Configuration/IConfigurationFactory.cs
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace MediaBrowser.Common.Configuration
|
||||||
|
{
|
||||||
|
public interface IConfigurationFactory
|
||||||
|
{
|
||||||
|
IEnumerable<ConfigurationStore> GetConfigurations();
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ConfigurationStore
|
||||||
|
{
|
||||||
|
public string Key { get; set; }
|
||||||
|
|
||||||
|
public Type ConfigurationType { get; set; }
|
||||||
|
}
|
||||||
|
}
|
@ -1,15 +1,26 @@
|
|||||||
using MediaBrowser.Model.Configuration;
|
using MediaBrowser.Model.Configuration;
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
namespace MediaBrowser.Common.Configuration
|
namespace MediaBrowser.Common.Configuration
|
||||||
{
|
{
|
||||||
public interface IConfigurationManager
|
public interface IConfigurationManager
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Occurs when [configuration updating].
|
||||||
|
/// </summary>
|
||||||
|
event EventHandler<ConfigurationUpdateEventArgs> NamedConfigurationUpdating;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Occurs when [configuration updated].
|
/// Occurs when [configuration updated].
|
||||||
/// </summary>
|
/// </summary>
|
||||||
event EventHandler<EventArgs> ConfigurationUpdated;
|
event EventHandler<EventArgs> ConfigurationUpdated;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Occurs when [named configuration updated].
|
||||||
|
/// </summary>
|
||||||
|
event EventHandler<ConfigurationUpdateEventArgs> NamedConfigurationUpdated;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the application paths.
|
/// Gets or sets the application paths.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -32,5 +43,40 @@ namespace MediaBrowser.Common.Configuration
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="newConfiguration">The new configuration.</param>
|
/// <param name="newConfiguration">The new configuration.</param>
|
||||||
void ReplaceConfiguration(BaseApplicationConfiguration newConfiguration);
|
void ReplaceConfiguration(BaseApplicationConfiguration newConfiguration);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the configuration.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="key">The key.</param>
|
||||||
|
/// <returns>System.Object.</returns>
|
||||||
|
object GetConfiguration(string key);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the type of the configuration.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="key">The key.</param>
|
||||||
|
/// <returns>Type.</returns>
|
||||||
|
Type GetConfigurationType(string key);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Saves the configuration.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="key">The key.</param>
|
||||||
|
/// <param name="configuration">The configuration.</param>
|
||||||
|
void SaveConfiguration(string key, object configuration);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds the parts.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="factories">The factories.</param>
|
||||||
|
void AddParts(IEnumerable<IConfigurationFactory> factories);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class ConfigurationManagerExtensions
|
||||||
|
{
|
||||||
|
public static T GetConfiguration<T>(this IConfigurationManager manager, string key)
|
||||||
|
{
|
||||||
|
return (T)manager.GetConfiguration(key);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -112,5 +112,19 @@ namespace MediaBrowser.Common.IO
|
|||||||
/// <param name="to">To.</param>
|
/// <param name="to">To.</param>
|
||||||
/// <returns>System.String.</returns>
|
/// <returns>System.String.</returns>
|
||||||
string SubstitutePath(string path, string from, string to);
|
string SubstitutePath(string path, string from, string to);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the file name without extension.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="info">The information.</param>
|
||||||
|
/// <returns>System.String.</returns>
|
||||||
|
string GetFileNameWithoutExtension(FileSystemInfo info);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the file name without extension.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="path">The path.</param>
|
||||||
|
/// <returns>System.String.</returns>
|
||||||
|
string GetFileNameWithoutExtension(string path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -55,7 +55,9 @@
|
|||||||
<Link>Properties\SharedVersion.cs</Link>
|
<Link>Properties\SharedVersion.cs</Link>
|
||||||
</Compile>
|
</Compile>
|
||||||
<Compile Include="Configuration\ConfigurationHelper.cs" />
|
<Compile Include="Configuration\ConfigurationHelper.cs" />
|
||||||
|
<Compile Include="Configuration\ConfigurationUpdateEventArgs.cs" />
|
||||||
<Compile Include="Configuration\IConfigurationManager.cs" />
|
<Compile Include="Configuration\IConfigurationManager.cs" />
|
||||||
|
<Compile Include="Configuration\IConfigurationFactory.cs" />
|
||||||
<Compile Include="Constants\Constants.cs" />
|
<Compile Include="Constants\Constants.cs" />
|
||||||
<Compile Include="Events\EventHelper.cs" />
|
<Compile Include="Events\EventHelper.cs" />
|
||||||
<Compile Include="Extensions\BaseExtensions.cs" />
|
<Compile Include="Extensions\BaseExtensions.cs" />
|
||||||
@ -76,7 +78,6 @@
|
|||||||
<Compile Include="Net\INetworkManager.cs" />
|
<Compile Include="Net\INetworkManager.cs" />
|
||||||
<Compile Include="Net\IWebSocket.cs" />
|
<Compile Include="Net\IWebSocket.cs" />
|
||||||
<Compile Include="Net\IWebSocketConnection.cs" />
|
<Compile Include="Net\IWebSocketConnection.cs" />
|
||||||
<Compile Include="Net\IWebSocketServer.cs" />
|
|
||||||
<Compile Include="Net\MimeTypes.cs" />
|
<Compile Include="Net\MimeTypes.cs" />
|
||||||
<Compile Include="Net\WebSocketConnectEventArgs.cs" />
|
<Compile Include="Net\WebSocketConnectEventArgs.cs" />
|
||||||
<Compile Include="Net\WebSocketMessageInfo.cs" />
|
<Compile Include="Net\WebSocketMessageInfo.cs" />
|
||||||
@ -115,7 +116,7 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup />
|
<ItemGroup />
|
||||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||||
<Import Project="$(SolutionDir)\.nuget\nuget.targets" Condition=" '$(ConfigurationName)' != 'Release Mono' " />
|
<Import Project="$(SolutionDir)\.nuget\NuGet.targets" />
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<PostBuildEvent Condition=" '$(ConfigurationName)' != 'Release Mono' ">if '$(ConfigurationName)' == 'Release' (
|
<PostBuildEvent Condition=" '$(ConfigurationName)' != 'Release Mono' ">if '$(ConfigurationName)' == 'Release' (
|
||||||
xcopy "$(TargetPath)" "$(SolutionDir)\Nuget\dlls\" /y /d /r /i
|
xcopy "$(TargetPath)" "$(SolutionDir)\Nuget\dlls\" /y /d /r /i
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user