From 976459d3e8a8b889cebc2cf281e38b0fbc19c9b9 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Tue, 17 Dec 2019 23:15:02 +0100 Subject: Rewrite WebSocket handling code --- .../Session/HttpSessionController.cs | 191 --------------------- .../Session/SessionManager.cs | 13 +- .../Session/SessionWebSocketListener.cs | 36 ++-- .../Session/WebSocketController.cs | 70 ++++---- 4 files changed, 65 insertions(+), 245 deletions(-) delete mode 100644 Emby.Server.Implementations/Session/HttpSessionController.cs (limited to 'Emby.Server.Implementations/Session') diff --git a/Emby.Server.Implementations/Session/HttpSessionController.cs b/Emby.Server.Implementations/Session/HttpSessionController.cs deleted file mode 100644 index dfb81816c..000000000 --- a/Emby.Server.Implementations/Session/HttpSessionController.cs +++ /dev/null @@ -1,191 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Linq; -using System.Net; -using System.Threading; -using System.Threading.Tasks; -using MediaBrowser.Common.Net; -using MediaBrowser.Controller.Session; -using MediaBrowser.Model.Serialization; -using MediaBrowser.Model.Session; - -namespace Emby.Server.Implementations.Session -{ - public class HttpSessionController : ISessionController - { - private readonly IHttpClient _httpClient; - private readonly IJsonSerializer _json; - private readonly ISessionManager _sessionManager; - - public SessionInfo Session { get; private set; } - - private readonly string _postUrl; - - public HttpSessionController(IHttpClient httpClient, - IJsonSerializer json, - SessionInfo session, - string postUrl, ISessionManager sessionManager) - { - _httpClient = httpClient; - _json = json; - Session = session; - _postUrl = postUrl; - _sessionManager = sessionManager; - } - - private string PostUrl => string.Format("http://{0}{1}", Session.RemoteEndPoint, _postUrl); - - public bool IsSessionActive => (DateTime.UtcNow - Session.LastActivityDate).TotalMinutes <= 5; - - public bool SupportsMediaControl => true; - - private Task SendMessage(string name, string messageId, CancellationToken cancellationToken) - { - return SendMessage(name, messageId, new Dictionary(), cancellationToken); - } - - private Task SendMessage(string name, string messageId, Dictionary args, CancellationToken cancellationToken) - { - args["messageId"] = messageId; - var url = PostUrl + "/" + name + ToQueryString(args); - - return SendRequest(new HttpRequestOptions - { - Url = url, - CancellationToken = cancellationToken, - BufferContent = false - }); - } - - private Task SendPlayCommand(PlayRequest command, string messageId, CancellationToken cancellationToken) - { - var dict = new Dictionary(); - - dict["ItemIds"] = string.Join(",", command.ItemIds.Select(i => i.ToString("N", CultureInfo.InvariantCulture)).ToArray()); - - if (command.StartPositionTicks.HasValue) - { - dict["StartPositionTicks"] = command.StartPositionTicks.Value.ToString(CultureInfo.InvariantCulture); - } - if (command.AudioStreamIndex.HasValue) - { - dict["AudioStreamIndex"] = command.AudioStreamIndex.Value.ToString(CultureInfo.InvariantCulture); - } - if (command.SubtitleStreamIndex.HasValue) - { - dict["SubtitleStreamIndex"] = command.SubtitleStreamIndex.Value.ToString(CultureInfo.InvariantCulture); - } - if (command.StartIndex.HasValue) - { - dict["StartIndex"] = command.StartIndex.Value.ToString(CultureInfo.InvariantCulture); - } - if (!string.IsNullOrEmpty(command.MediaSourceId)) - { - dict["MediaSourceId"] = command.MediaSourceId; - } - - return SendMessage(command.PlayCommand.ToString(), messageId, dict, cancellationToken); - } - - private Task SendPlaystateCommand(PlaystateRequest command, string messageId, CancellationToken cancellationToken) - { - var args = new Dictionary(); - - if (command.Command == PlaystateCommand.Seek) - { - if (!command.SeekPositionTicks.HasValue) - { - throw new ArgumentException("SeekPositionTicks cannot be null"); - } - - args["SeekPositionTicks"] = command.SeekPositionTicks.Value.ToString(CultureInfo.InvariantCulture); - } - - return SendMessage(command.Command.ToString(), messageId, args, cancellationToken); - } - - private string[] _supportedMessages = Array.Empty(); - public Task SendMessage(string name, string messageId, T data, ISessionController[] allControllers, CancellationToken cancellationToken) - { - if (!IsSessionActive) - { - return Task.CompletedTask; - } - - if (string.Equals(name, "Play", StringComparison.OrdinalIgnoreCase)) - { - return SendPlayCommand(data as PlayRequest, messageId, cancellationToken); - } - if (string.Equals(name, "PlayState", StringComparison.OrdinalIgnoreCase)) - { - return SendPlaystateCommand(data as PlaystateRequest, messageId, cancellationToken); - } - if (string.Equals(name, "GeneralCommand", StringComparison.OrdinalIgnoreCase)) - { - var command = data as GeneralCommand; - return SendMessage(command.Name, messageId, command.Arguments, cancellationToken); - } - - if (!_supportedMessages.Contains(name, StringComparer.OrdinalIgnoreCase)) - { - return Task.CompletedTask; - } - - var url = PostUrl + "/" + name; - - url += "?messageId=" + messageId; - - var options = new HttpRequestOptions - { - Url = url, - CancellationToken = cancellationToken, - BufferContent = false - }; - - if (data != null) - { - if (typeof(T) == typeof(string)) - { - var str = data as string; - if (!string.IsNullOrEmpty(str)) - { - options.RequestContent = str; - options.RequestContentType = "application/json"; - } - } - else - { - options.RequestContent = _json.SerializeToString(data); - options.RequestContentType = "application/json"; - } - } - - return SendRequest(options); - } - - private async Task SendRequest(HttpRequestOptions options) - { - using (var response = await _httpClient.Post(options).ConfigureAwait(false)) - { - - } - } - - private static string ToQueryString(Dictionary nvc) - { - var array = (from item in nvc - select string.Format("{0}={1}", WebUtility.UrlEncode(item.Key), WebUtility.UrlEncode(item.Value))) - .ToArray(); - - var args = string.Join("&", array); - - if (string.IsNullOrEmpty(args)) - { - return args; - } - - return "?" + args; - } - } -} diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs index b1d513dd4..db00ceeb7 100644 --- a/Emby.Server.Implementations/Session/SessionManager.cs +++ b/Emby.Server.Implementations/Session/SessionManager.cs @@ -463,8 +463,7 @@ namespace Emby.Server.Implementations.Session Client = appName, DeviceId = deviceId, ApplicationVersion = appVersion, - Id = key.GetMD5().ToString("N", CultureInfo.InvariantCulture), - ServerId = _appHost.SystemId + Id = key.GetMD5().ToString("N", CultureInfo.InvariantCulture) }; var username = user?.Name; @@ -1024,12 +1023,12 @@ namespace Emby.Server.Implementations.Session private static async Task SendMessageToSession(SessionInfo session, string name, T data, CancellationToken cancellationToken) { - var controllers = session.SessionControllers.ToArray(); - var messageId = Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture); + var controllers = session.SessionControllers; + var messageId = Guid.NewGuid(); foreach (var controller in controllers) { - await controller.SendMessage(name, messageId, data, controllers, cancellationToken).ConfigureAwait(false); + await controller.SendMessage(name, messageId, data, cancellationToken).ConfigureAwait(false); } } @@ -1037,13 +1036,13 @@ namespace Emby.Server.Implementations.Session { IEnumerable GetTasks() { - var messageId = Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture); + var messageId = Guid.NewGuid(); foreach (var session in sessions) { var controllers = session.SessionControllers; foreach (var controller in controllers) { - yield return controller.SendMessage(name, messageId, data, controllers, cancellationToken); + yield return controller.SendMessage(name, messageId, data, cancellationToken); } } } diff --git a/Emby.Server.Implementations/Session/SessionWebSocketListener.cs b/Emby.Server.Implementations/Session/SessionWebSocketListener.cs index 930f2d35d..13b42698d 100644 --- a/Emby.Server.Implementations/Session/SessionWebSocketListener.cs +++ b/Emby.Server.Implementations/Session/SessionWebSocketListener.cs @@ -3,7 +3,6 @@ using System.Threading.Tasks; using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Session; using MediaBrowser.Model.Events; -using MediaBrowser.Model.Serialization; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; @@ -12,7 +11,7 @@ namespace Emby.Server.Implementations.Session /// /// Class SessionWebSocketListener /// - public class SessionWebSocketListener : IWebSocketListener, IDisposable + public sealed class SessionWebSocketListener : IWebSocketListener, IDisposable { /// /// The _session manager @@ -23,35 +22,34 @@ namespace Emby.Server.Implementations.Session /// The _logger /// private readonly ILogger _logger; - - /// - /// The _dto service - /// - private readonly IJsonSerializer _json; + private readonly ILoggerFactory _loggerFactory; private readonly IHttpServer _httpServer; - /// /// Initializes a new instance of the class. /// + /// The logger. /// The session manager. /// The logger factory. - /// The json. /// The HTTP server. - public SessionWebSocketListener(ISessionManager sessionManager, ILoggerFactory loggerFactory, IJsonSerializer json, IHttpServer httpServer) + public SessionWebSocketListener( + ILogger logger, + ISessionManager sessionManager, + ILoggerFactory loggerFactory, + IHttpServer httpServer) { + _logger = logger; _sessionManager = sessionManager; - _logger = loggerFactory.CreateLogger(GetType().Name); - _json = json; + _loggerFactory = loggerFactory; _httpServer = httpServer; - httpServer.WebSocketConnected += _serverManager_WebSocketConnected; + + httpServer.WebSocketConnected += OnServerManagerWebSocketConnected; } - void _serverManager_WebSocketConnected(object sender, GenericEventArgs e) + private void OnServerManagerWebSocketConnected(object sender, GenericEventArgs e) { - var session = GetSession(e.Argument.QueryString, e.Argument.RemoteEndPoint); - + var session = GetSession(e.Argument.QueryString, e.Argument.RemoteEndPoint.ToString()); if (session != null) { EnsureController(session, e.Argument); @@ -79,9 +77,10 @@ namespace Emby.Server.Implementations.Session return _sessionManager.GetSessionByAuthenticationToken(token, deviceId, remoteEndpoint); } + /// public void Dispose() { - _httpServer.WebSocketConnected -= _serverManager_WebSocketConnected; + _httpServer.WebSocketConnected -= OnServerManagerWebSocketConnected; } /// @@ -94,7 +93,8 @@ namespace Emby.Server.Implementations.Session private void EnsureController(SessionInfo session, IWebSocketConnection connection) { - var controllerInfo = session.EnsureController(s => new WebSocketController(s, _logger, _sessionManager)); + var controllerInfo = session.EnsureController( + s => new WebSocketController(_loggerFactory.CreateLogger(), s, _sessionManager)); var controller = (WebSocketController)controllerInfo.Item1; controller.AddWebSocket(connection); diff --git a/Emby.Server.Implementations/Session/WebSocketController.cs b/Emby.Server.Implementations/Session/WebSocketController.cs index 0d483c55f..c17e67da9 100644 --- a/Emby.Server.Implementations/Session/WebSocketController.cs +++ b/Emby.Server.Implementations/Session/WebSocketController.cs @@ -11,60 +11,64 @@ using Microsoft.Extensions.Logging; namespace Emby.Server.Implementations.Session { - public class WebSocketController : ISessionController, IDisposable + public sealed class WebSocketController : ISessionController, IDisposable { - public SessionInfo Session { get; private set; } - public IReadOnlyList Sockets { get; private set; } - private readonly ILogger _logger; - private readonly ISessionManager _sessionManager; + private readonly SessionInfo _session; - public WebSocketController(SessionInfo session, ILogger logger, ISessionManager sessionManager) + private List _sockets; + private bool _disposed = false; + + public WebSocketController( + ILogger logger, + SessionInfo session, + ISessionManager sessionManager) { - Session = session; _logger = logger; + _session = session; _sessionManager = sessionManager; - Sockets = new List(); + _sockets = new List(); } private bool HasOpenSockets => GetActiveSockets().Any(); + /// public bool SupportsMediaControl => HasOpenSockets; + /// public bool IsSessionActive => HasOpenSockets; private IEnumerable GetActiveSockets() - { - return Sockets - .OrderByDescending(i => i.LastActivityDate) - .Where(i => i.State == WebSocketState.Open); - } + => _sockets.Where(i => i.State == WebSocketState.Open); + /// public void AddWebSocket(IWebSocketConnection connection) { - var sockets = Sockets.ToList(); - sockets.Add(connection); + _logger.LogDebug("Adding websocket to session {Session}", _session.Id); + _sockets.Add(connection); - Sockets = sockets; - - connection.Closed += connection_Closed; + connection.Closed += OnConnectionClosed; } - void connection_Closed(object sender, EventArgs e) + private void OnConnectionClosed(object sender, EventArgs e) { + _logger.LogDebug("Removing websocket from session {Session}", _session.Id); var connection = (IWebSocketConnection)sender; - var sockets = Sockets.ToList(); - sockets.Remove(connection); - - Sockets = sockets; - - _sessionManager.CloseIfNeeded(Session); + _sockets.Remove(connection); + _sessionManager.CloseIfNeeded(_session); + connection.Dispose(); } - public Task SendMessage(string name, string messageId, T data, ISessionController[] allControllers, CancellationToken cancellationToken) + /// + public Task SendMessage( + string name, + Guid messageId, + T data, + CancellationToken cancellationToken) { var socket = GetActiveSockets() + .OrderByDescending(i => i.LastActivityDate) .FirstOrDefault(); if (socket == null) @@ -77,16 +81,24 @@ namespace Emby.Server.Implementations.Session Data = data, MessageType = name, MessageId = messageId - }, cancellationToken); } + /// public void Dispose() { - foreach (var socket in Sockets.ToList()) + if (_disposed) { - socket.Closed -= connection_Closed; + return; } + + foreach (var socket in _sockets) + { + socket.Closed -= OnConnectionClosed; + socket.Dispose(); + } + + _disposed = true; } } } -- cgit v1.2.3 From 5ca68f9623e414b85ddbda1f97895f1b90bd05e0 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Thu, 26 Dec 2019 20:57:46 +0100 Subject: Fix nullref exception and added logging --- .../HttpServer/HttpListenerHost.cs | 17 +++--- .../HttpServer/WebSocketConnection.cs | 63 ++++++++-------------- .../Session/SessionManager.cs | 3 +- .../Session/SessionWebSocketListener.cs | 2 +- .../Session/WebSocketController.cs | 5 +- .../Net/IWebSocketConnection.cs | 16 +++--- 6 files changed, 41 insertions(+), 65 deletions(-) (limited to 'Emby.Server.Implementations/Session') diff --git a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs index 4baf96ab5..ebae4d0b1 100644 --- a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs +++ b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs @@ -518,30 +518,29 @@ namespace Emby.Server.Implementations.HttpServer return; } - var url = context.Request.GetDisplayUrl(); - _logger.LogInformation("WS {Url}. UserAgent: {UserAgent}", url, context.Request.Headers[HeaderNames.UserAgent].ToString()); - try { - var webSocket = await context.WebSockets.AcceptWebSocketAsync(null).ConfigureAwait(false); + _logger.LogInformation("WS Request from {IP}", context.Connection.RemoteIpAddress); + + WebSocket webSocket = await context.WebSockets.AcceptWebSocketAsync().ConfigureAwait(false); var connection = new WebSocketConnection( _loggerFactory.CreateLogger(), webSocket, - context.Connection.RemoteIpAddress) + context.Connection.RemoteIpAddress, + context.Request.Query) { - Url = url, - QueryString = context.Request.Query, OnReceive = ProcessWebSocketMessageReceived }; WebSocketConnected?.Invoke(this, new GenericEventArgs(connection)); await connection.ProcessAsync().ConfigureAwait(false); + _logger.LogInformation("WS closed from {IP}", context.Connection.RemoteIpAddress); } - catch (WebSocketException ex) + catch (Exception ex) // Otherwise ASP.Net will ignore the exception { - _logger.LogError(ex, "ProcessWebSocketRequest error"); + _logger.LogError(ex, "WebSocketRequestHandler error"); if (!context.Response.HasStarted) { context.Response.StatusCode = 500; diff --git a/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs b/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs index b4f420e5d..88974f9ab 100644 --- a/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs +++ b/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs @@ -1,4 +1,6 @@ -using System; +#nullable enable + +using System; using System.Buffers; using System.IO.Pipelines; using System.Net; @@ -39,47 +41,38 @@ namespace Emby.Server.Implementations.HttpServer /// /// Initializes a new instance of the class. /// + /// The logger. /// The socket. /// The remote end point. - /// The logger. - /// socket - public WebSocketConnection(ILogger logger, WebSocket socket, IPAddress remoteEndPoint) + /// The query. + public WebSocketConnection( + ILogger logger, + WebSocket socket, + IPAddress? remoteEndPoint, + IQueryCollection query) { - if (socket == null) - { - throw new ArgumentNullException(nameof(socket)); - } - - if (remoteEndPoint != null) - { - throw new ArgumentNullException(nameof(remoteEndPoint)); - } - - if (logger == null) - { - throw new ArgumentNullException(nameof(logger)); - } - + _logger = logger; _socket = socket; RemoteEndPoint = remoteEndPoint; - _logger = logger; + QueryString = query; _jsonOptions = JsonDefaults.GetOptions(); + LastActivityDate = DateTime.Now; } /// - public event EventHandler Closed; + public event EventHandler? Closed; /// /// Gets or sets the remote end point. /// - public IPAddress RemoteEndPoint { get; private set; } + public IPAddress? RemoteEndPoint { get; } /// /// Gets or sets the receive action. /// /// The receive action. - public Func OnReceive { get; set; } + public Func? OnReceive { get; set; } /// /// Gets the last activity date. @@ -87,17 +80,11 @@ namespace Emby.Server.Implementations.HttpServer /// The last activity date. public DateTime LastActivityDate { get; private set; } - /// - /// Gets or sets the URL. - /// - /// The URL. - public string Url { get; set; } - /// /// Gets or sets the query string. /// /// The query string. - public IQueryCollection QueryString { get; set; } + public IQueryCollection QueryString { get; } /// /// Gets the state. @@ -115,11 +102,6 @@ namespace Emby.Server.Implementations.HttpServer /// message public Task SendAsync(WebSocketMessage message, CancellationToken cancellationToken) { - if (message == null) - { - throw new ArgumentNullException(nameof(message)); - } - var json = JsonSerializer.SerializeToUtf8Bytes(message, _jsonOptions); return _socket.SendAsync(json, WebSocketMessageType.Text, true, cancellationToken); } @@ -140,7 +122,7 @@ namespace Emby.Server.Implementations.HttpServer int bytesRead = receiveresult.Count; if (bytesRead == 0) { - continue; + break; } // Tell the PipeWriter how much was read from the Socket @@ -154,6 +136,8 @@ namespace Emby.Server.Implementations.HttpServer break; } + LastActivityDate = DateTime.UtcNow; + if (receiveresult.EndOfMessage) { await ProcessInternal(pipe.Reader).ConfigureAwait(false); @@ -162,10 +146,7 @@ namespace Emby.Server.Implementations.HttpServer if (_socket.State == WebSocketState.Open) { - await _socket.CloseAsync( - WebSocketCloseStatus.NormalClosure, - string.Empty, // REVIEW: human readable explanation as to why the connection is closed. - cancellationToken).ConfigureAwait(false); + _logger.LogWarning("Stopped reading from websocket before it was closed"); } Closed?.Invoke(this, EventArgs.Empty); @@ -175,8 +156,6 @@ namespace Emby.Server.Implementations.HttpServer private async Task ProcessInternal(PipeReader reader) { - LastActivityDate = DateTime.UtcNow; - if (OnReceive == null) { return; diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs index db00ceeb7..0d5df1dad 100644 --- a/Emby.Server.Implementations/Session/SessionManager.cs +++ b/Emby.Server.Implementations/Session/SessionManager.cs @@ -1726,6 +1726,7 @@ namespace Emby.Server.Implementations.Session string.Equals(i.Client, client)); } + /// public SessionInfo GetSessionByAuthenticationToken(AuthenticationInfo info, string deviceId, string remoteEndpoint, string appVersion) { if (info == null) @@ -1733,7 +1734,7 @@ namespace Emby.Server.Implementations.Session throw new ArgumentNullException(nameof(info)); } - var user = info.UserId.Equals(Guid.Empty) + var user = info.UserId == Guid.Empty ? null : _userManager.GetUserById(info.UserId); diff --git a/Emby.Server.Implementations/Session/SessionWebSocketListener.cs b/Emby.Server.Implementations/Session/SessionWebSocketListener.cs index 13b42698d..d4e4ba1f2 100644 --- a/Emby.Server.Implementations/Session/SessionWebSocketListener.cs +++ b/Emby.Server.Implementations/Session/SessionWebSocketListener.cs @@ -56,7 +56,7 @@ namespace Emby.Server.Implementations.Session } else { - _logger.LogWarning("Unable to determine session based on url: {0}", e.Argument.Url); + _logger.LogWarning("Unable to determine session based on query string: {0}", e.Argument.QueryString); } } diff --git a/Emby.Server.Implementations/Session/WebSocketController.cs b/Emby.Server.Implementations/Session/WebSocketController.cs index c17e67da9..536013c7a 100644 --- a/Emby.Server.Implementations/Session/WebSocketController.cs +++ b/Emby.Server.Implementations/Session/WebSocketController.cs @@ -53,11 +53,12 @@ namespace Emby.Server.Implementations.Session private void OnConnectionClosed(object sender, EventArgs e) { - _logger.LogDebug("Removing websocket from session {Session}", _session.Id); var connection = (IWebSocketConnection)sender; + _logger.LogDebug("Removing websocket from session {Session}", _session.Id); _sockets.Remove(connection); - _sessionManager.CloseIfNeeded(_session); + connection.Closed -= OnConnectionClosed; connection.Dispose(); + _sessionManager.CloseIfNeeded(_session); } /// diff --git a/MediaBrowser.Controller/Net/IWebSocketConnection.cs b/MediaBrowser.Controller/Net/IWebSocketConnection.cs index e2a714d5b..d5555884d 100644 --- a/MediaBrowser.Controller/Net/IWebSocketConnection.cs +++ b/MediaBrowser.Controller/Net/IWebSocketConnection.cs @@ -1,3 +1,5 @@ +#nullable enable + using System; using System.Net; using System.Net.WebSockets; @@ -13,7 +15,7 @@ namespace MediaBrowser.Controller.Net /// /// Occurs when [closed]. /// - event EventHandler Closed; + event EventHandler? Closed; /// /// Gets the last activity date. @@ -21,23 +23,17 @@ namespace MediaBrowser.Controller.Net /// The last activity date. DateTime LastActivityDate { get; } - /// - /// Gets or sets the URL. - /// - /// The URL. - string Url { get; set; } - /// /// Gets or sets the query string. /// /// The query string. - IQueryCollection QueryString { get; set; } + IQueryCollection QueryString { get; } /// /// Gets or sets the receive action. /// /// The receive action. - Func OnReceive { get; set; } + Func? OnReceive { get; set; } /// /// Gets the state. @@ -49,7 +45,7 @@ namespace MediaBrowser.Controller.Net /// Gets the remote end point. /// /// The remote end point. - IPAddress RemoteEndPoint { get; } + IPAddress? RemoteEndPoint { get; } /// /// Sends a message asynchronously. -- cgit v1.2.3 From 4d311870d2f40f67da6df5641b53df637fdee88d Mon Sep 17 00:00:00 2001 From: Bond-009 Date: Fri, 27 Dec 2019 14:42:53 +0100 Subject: Fix websocket handling --- .../HttpServer/WebSocketConnection.cs | 73 +++++++++------------- .../Session/WebSocketController.cs | 2 - .../Net/IWebSocketConnection.cs | 2 +- 3 files changed, 30 insertions(+), 47 deletions(-) (limited to 'Emby.Server.Implementations/Session') diff --git a/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs b/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs index 88974f9ab..913a51217 100644 --- a/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs +++ b/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs @@ -99,7 +99,6 @@ namespace Emby.Server.Implementations.HttpServer /// The message. /// The cancellation token. /// Task. - /// message public Task SendAsync(WebSocketMessage message, CancellationToken cancellationToken) { var json = JsonSerializer.SerializeToUtf8Bytes(message, _jsonOptions); @@ -117,7 +116,6 @@ namespace Emby.Server.Implementations.HttpServer { // Allocate at least 512 bytes from the PipeWriter Memory memory = writer.GetMemory(512); - receiveresult = await _socket.ReceiveAsync(memory, cancellationToken); int bytesRead = receiveresult.Count; if (bytesRead == 0) @@ -144,33 +142,30 @@ namespace Emby.Server.Implementations.HttpServer } } while (_socket.State == WebSocketState.Open && receiveresult.MessageType != WebSocketMessageType.Close); - if (_socket.State == WebSocketState.Open) - { - _logger.LogWarning("Stopped reading from websocket before it was closed"); - } - Closed?.Invoke(this, EventArgs.Empty); - _socket.Dispose(); + await _socket.CloseAsync( + WebSocketCloseStatus.NormalClosure, + string.Empty, + cancellationToken).ConfigureAwait(false); } private async Task ProcessInternal(PipeReader reader) { + ReadResult result = await reader.ReadAsync().ConfigureAwait(false); + ReadOnlySequence buffer = result.Buffer; + if (OnReceive == null) { + // Tell the PipeReader how much of the buffer we have consumed + reader.AdvanceTo(buffer.End); return; } + WebSocketMessage stub; try { - var result = await reader.ReadAsync().ConfigureAwait(false); - if (!result.IsCompleted) - { - return; - } - WebSocketMessage stub; - var buffer = result.Buffer; if (buffer.IsSingleSegment) { stub = JsonSerializer.Deserialize>(buffer.FirstSpan, _jsonOptions); @@ -188,46 +183,36 @@ namespace Emby.Server.Implementations.HttpServer ArrayPool.Shared.Return(buf); } } - - var info = new WebSocketMessageInfo - { - MessageType = stub.MessageType, - Data = stub.Data.ToString(), - Connection = this - }; - - await OnReceive(info).ConfigureAwait(false); } catch (JsonException ex) { + // Tell the PipeReader how much of the buffer we have consumed + reader.AdvanceTo(buffer.End); _logger.LogError(ex, "Error processing web socket message"); + return; } - } - /// - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } + // Tell the PipeReader how much of the buffer we have consumed + reader.AdvanceTo(buffer.End); - /// - /// Releases unmanaged and - optionally - managed resources. - /// - /// true to release both managed and unmanaged resources; false to release only unmanaged resources. - protected virtual void Dispose(bool dispose) - { - if (_disposed) + _logger.LogDebug("WS received message: {@Message}", stub); + + var info = new WebSocketMessageInfo { - return; - } + MessageType = stub.MessageType, + Data = stub.Data?.ToString(), // Data can be null + Connection = this + }; + + _logger.LogDebug("WS message info: {@MessageInfo}", info); - if (dispose) + await OnReceive(info).ConfigureAwait(false); + + // Stop reading if there's no more data coming + if (result.IsCompleted) { - _socket.Dispose(); + return; } - - _disposed = true; } } } diff --git a/Emby.Server.Implementations/Session/WebSocketController.cs b/Emby.Server.Implementations/Session/WebSocketController.cs index 536013c7a..c3c4b716f 100644 --- a/Emby.Server.Implementations/Session/WebSocketController.cs +++ b/Emby.Server.Implementations/Session/WebSocketController.cs @@ -57,7 +57,6 @@ namespace Emby.Server.Implementations.Session _logger.LogDebug("Removing websocket from session {Session}", _session.Id); _sockets.Remove(connection); connection.Closed -= OnConnectionClosed; - connection.Dispose(); _sessionManager.CloseIfNeeded(_session); } @@ -96,7 +95,6 @@ namespace Emby.Server.Implementations.Session foreach (var socket in _sockets) { socket.Closed -= OnConnectionClosed; - socket.Dispose(); } _disposed = true; diff --git a/MediaBrowser.Controller/Net/IWebSocketConnection.cs b/MediaBrowser.Controller/Net/IWebSocketConnection.cs index d5555884d..09e43c683 100644 --- a/MediaBrowser.Controller/Net/IWebSocketConnection.cs +++ b/MediaBrowser.Controller/Net/IWebSocketConnection.cs @@ -10,7 +10,7 @@ using Microsoft.AspNetCore.Http; namespace MediaBrowser.Controller.Net { - public interface IWebSocketConnection : IDisposable + public interface IWebSocketConnection { /// /// Occurs when [closed]. -- cgit v1.2.3 From 407f54e7764a6bfd8e4ccc0df897fac7e8c658b4 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Mon, 13 Jan 2020 20:03:49 +0100 Subject: Style fixes --- .../Session/WebSocketController.cs | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) (limited to 'Emby.Server.Implementations/Session') diff --git a/Emby.Server.Implementations/Session/WebSocketController.cs b/Emby.Server.Implementations/Session/WebSocketController.cs index c3c4b716f..c7ef9b1ce 100644 --- a/Emby.Server.Implementations/Session/WebSocketController.cs +++ b/Emby.Server.Implementations/Session/WebSocketController.cs @@ -1,3 +1,7 @@ +#pragma warning disable CS1591 +#pragma warning disable SA1600 +#nullable enable + using System; using System.Collections.Generic; using System.Linq; @@ -42,7 +46,6 @@ namespace Emby.Server.Implementations.Session private IEnumerable GetActiveSockets() => _sockets.Where(i => i.State == WebSocketState.Open); - /// public void AddWebSocket(IWebSocketConnection connection) { _logger.LogDebug("Adding websocket to session {Session}", _session.Id); @@ -76,12 +79,14 @@ namespace Emby.Server.Implementations.Session return Task.CompletedTask; } - return socket.SendAsync(new WebSocketMessage - { - Data = data, - MessageType = name, - MessageId = messageId - }, cancellationToken); + return socket.SendAsync( + new WebSocketMessage + { + Data = data, + MessageType = name, + MessageId = messageId + }, + cancellationToken); } /// -- cgit v1.2.3 From 53380689ad00f00efc0c1790f1d25d08c95d7f2d Mon Sep 17 00:00:00 2001 From: Mark Monteiro Date: Mon, 13 Apr 2020 13:17:46 -0400 Subject: Return correct status codes for authentication and authorization errors - Use AuthenticatonException to return 401 - Use SecurityException to return 403 - Update existing throws to throw the correct exception for the circumstance --- Emby.Server.Implementations/HttpServer/HttpListenerHost.cs | 5 ++++- .../HttpServer/Security/AuthService.cs | 7 ++++--- Emby.Server.Implementations/Library/UserManager.cs | 11 ++++------- Emby.Server.Implementations/Session/SessionManager.cs | 2 +- 4 files changed, 13 insertions(+), 12 deletions(-) (limited to 'Emby.Server.Implementations/Session') diff --git a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs index 5ae65a4e3..f496ff1ba 100644 --- a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs +++ b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs @@ -14,6 +14,7 @@ using Emby.Server.Implementations.Services; using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Net; using MediaBrowser.Controller; +using MediaBrowser.Controller.Authentication; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Net; using MediaBrowser.Model.Events; @@ -230,7 +231,8 @@ namespace Emby.Server.Implementations.HttpServer switch (ex) { case ArgumentException _: return 400; - case SecurityException _: return 401; + case AuthenticationException _: return 401; + case SecurityException _: return 403; case DirectoryNotFoundException _: case FileNotFoundException _: case ResourceNotFoundException _: return 404; @@ -550,6 +552,7 @@ namespace Emby.Server.Implementations.HttpServer || ex is IOException || ex is OperationCanceledException || ex is SecurityException + || ex is AuthenticationException || ex is FileNotFoundException; await ErrorHandler(ex, httpReq, !ignoreStackTrace, urlToLog).ConfigureAwait(false); } diff --git a/Emby.Server.Implementations/HttpServer/Security/AuthService.cs b/Emby.Server.Implementations/HttpServer/Security/AuthService.cs index 1360a5e0c..256b24924 100644 --- a/Emby.Server.Implementations/HttpServer/Security/AuthService.cs +++ b/Emby.Server.Implementations/HttpServer/Security/AuthService.cs @@ -2,6 +2,7 @@ using System; using System.Linq; +using System.Security.Authentication; using Emby.Server.Implementations.SocketSharp; using MediaBrowser.Common.Net; using MediaBrowser.Controller.Configuration; @@ -68,7 +69,7 @@ namespace Emby.Server.Implementations.HttpServer.Security if (user == null && auth.UserId != Guid.Empty) { - throw new SecurityException("User with Id " + auth.UserId + " not found"); + throw new AuthenticationException("User with Id " + auth.UserId + " not found"); } if (user != null) @@ -212,14 +213,14 @@ namespace Emby.Server.Implementations.HttpServer.Security { if (string.IsNullOrEmpty(token)) { - throw new SecurityException("Access token is required."); + throw new AuthenticationException("Access token is required."); } var info = GetTokenInfo(request); if (info == null) { - throw new SecurityException("Access token is invalid or expired."); + throw new AuthenticationException("Access token is invalid or expired."); } //if (!string.IsNullOrEmpty(info.UserId)) diff --git a/Emby.Server.Implementations/Library/UserManager.cs b/Emby.Server.Implementations/Library/UserManager.cs index 7b17cc913..f92cb6ae6 100644 --- a/Emby.Server.Implementations/Library/UserManager.cs +++ b/Emby.Server.Implementations/Library/UserManager.cs @@ -20,6 +20,7 @@ using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Persistence; using MediaBrowser.Controller.Plugins; using MediaBrowser.Controller.Providers; @@ -324,21 +325,17 @@ namespace Emby.Server.Implementations.Library if (user.Policy.IsDisabled) { - throw new AuthenticationException( - string.Format( - CultureInfo.InvariantCulture, - "The {0} account is currently disabled. Please consult with your administrator.", - user.Name)); + throw new SecurityException($"The {user.Name} account is currently disabled. Please consult with your administrator."); } if (!user.Policy.EnableRemoteAccess && !_networkManager.IsInLocalNetwork(remoteEndPoint)) { - throw new AuthenticationException("Forbidden."); + throw new SecurityException("Forbidden."); } if (!user.IsParentalScheduleAllowed()) { - throw new AuthenticationException("User is not allowed access at this time."); + throw new SecurityException("User is not allowed access at this time."); } // Update LastActivityDate and LastLoginDate, then save diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs index de768333d..c93c02c48 100644 --- a/Emby.Server.Implementations/Session/SessionManager.cs +++ b/Emby.Server.Implementations/Session/SessionManager.cs @@ -1414,7 +1414,7 @@ namespace Emby.Server.Implementations.Session if (user == null) { AuthenticationFailed?.Invoke(this, new GenericEventArgs(request)); - throw new SecurityException("Invalid username or password entered."); + throw new AuthenticationException("Invalid username or password entered."); } if (!string.IsNullOrEmpty(request.DeviceId) -- cgit v1.2.3 From 10c2c62f07fb4088480ff580ab67c1bc10a057a4 Mon Sep 17 00:00:00 2001 From: gion Date: Wed, 1 Apr 2020 17:52:42 +0200 Subject: Implement syncplay backend --- Emby.Server.Implementations/ApplicationHost.cs | 4 + .../Session/SessionManager.cs | 17 + .../Syncplay/SyncplayController.cs | 418 +++++++++++++++++++++ .../Syncplay/SyncplayManager.cs | 248 ++++++++++++ MediaBrowser.Api/Syncplay/SyncplayService.cs | 261 +++++++++++++ MediaBrowser.Controller/Session/ISessionManager.cs | 19 + MediaBrowser.Controller/Syncplay/GroupInfo.cs | 148 ++++++++ MediaBrowser.Controller/Syncplay/GroupMember.cs | 28 ++ .../Syncplay/ISyncplayController.cs | 61 +++ .../Syncplay/ISyncplayManager.cs | 62 +++ MediaBrowser.Model/Syncplay/GroupInfoModel.cs | 38 ++ MediaBrowser.Model/Syncplay/SyncplayCommand.cs | 32 ++ MediaBrowser.Model/Syncplay/SyncplayCommandType.cs | 21 ++ MediaBrowser.Model/Syncplay/SyncplayGroupUpdate.cs | 26 ++ .../Syncplay/SyncplayGroupUpdateType.cs | 41 ++ MediaBrowser.Model/Syncplay/SyncplayRequestInfo.cs | 34 ++ MediaBrowser.Model/Syncplay/SyncplayRequestType.cs | 33 ++ 17 files changed, 1491 insertions(+) create mode 100644 Emby.Server.Implementations/Syncplay/SyncplayController.cs create mode 100644 Emby.Server.Implementations/Syncplay/SyncplayManager.cs create mode 100644 MediaBrowser.Api/Syncplay/SyncplayService.cs create mode 100644 MediaBrowser.Controller/Syncplay/GroupInfo.cs create mode 100644 MediaBrowser.Controller/Syncplay/GroupMember.cs create mode 100644 MediaBrowser.Controller/Syncplay/ISyncplayController.cs create mode 100644 MediaBrowser.Controller/Syncplay/ISyncplayManager.cs create mode 100644 MediaBrowser.Model/Syncplay/GroupInfoModel.cs create mode 100644 MediaBrowser.Model/Syncplay/SyncplayCommand.cs create mode 100644 MediaBrowser.Model/Syncplay/SyncplayCommandType.cs create mode 100644 MediaBrowser.Model/Syncplay/SyncplayGroupUpdate.cs create mode 100644 MediaBrowser.Model/Syncplay/SyncplayGroupUpdateType.cs create mode 100644 MediaBrowser.Model/Syncplay/SyncplayRequestInfo.cs create mode 100644 MediaBrowser.Model/Syncplay/SyncplayRequestType.cs (limited to 'Emby.Server.Implementations/Session') diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 4d906a1bf..8419014c2 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -47,6 +47,7 @@ using Emby.Server.Implementations.Session; using Emby.Server.Implementations.SocketSharp; using Emby.Server.Implementations.TV; using Emby.Server.Implementations.Updates; +using Emby.Server.Implementations.Syncplay; using MediaBrowser.Api; using MediaBrowser.Common; using MediaBrowser.Common.Configuration; @@ -80,6 +81,7 @@ using MediaBrowser.Controller.Session; using MediaBrowser.Controller.Sorting; using MediaBrowser.Controller.Subtitles; using MediaBrowser.Controller.TV; +using MediaBrowser.Controller.Syncplay; using MediaBrowser.LocalMetadata.Savers; using MediaBrowser.MediaEncoding.BdInfo; using MediaBrowser.Model.Activity; @@ -643,6 +645,8 @@ namespace Emby.Server.Implementations serviceCollection.AddSingleton(); + serviceCollection.AddSingleton(); + serviceCollection.AddSingleton(); serviceCollection.AddSingleton(); diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs index c93c02c48..b1519b572 100644 --- a/Emby.Server.Implementations/Session/SessionManager.cs +++ b/Emby.Server.Implementations/Session/SessionManager.cs @@ -25,6 +25,7 @@ using MediaBrowser.Model.Events; using MediaBrowser.Model.Library; using MediaBrowser.Model.Querying; using MediaBrowser.Model.Session; +using MediaBrowser.Model.Syncplay; using Microsoft.Extensions.Logging; namespace Emby.Server.Implementations.Session @@ -1154,6 +1155,22 @@ namespace Emby.Server.Implementations.Session await SendMessageToSession(session, "Play", command, cancellationToken).ConfigureAwait(false); } + /// + public async Task SendSyncplayCommand(string sessionId, SyncplayCommand command, CancellationToken cancellationToken) + { + CheckDisposed(); + var session = GetSessionToRemoteControl(sessionId); + await SendMessageToSession(session, "SyncplayCommand", command, cancellationToken).ConfigureAwait(false); + } + + /// + public async Task SendSyncplayGroupUpdate(string sessionId, SyncplayGroupUpdate command, CancellationToken cancellationToken) + { + CheckDisposed(); + var session = GetSessionToRemoteControl(sessionId); + await SendMessageToSession(session, "SyncplayGroupUpdate", command, cancellationToken).ConfigureAwait(false); + } + private IEnumerable TranslateItemForPlayback(Guid id, User user) { var item = _libraryManager.GetItemById(id); diff --git a/Emby.Server.Implementations/Syncplay/SyncplayController.cs b/Emby.Server.Implementations/Syncplay/SyncplayController.cs new file mode 100644 index 000000000..4a20ceba0 --- /dev/null +++ b/Emby.Server.Implementations/Syncplay/SyncplayController.cs @@ -0,0 +1,418 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using MediaBrowser.Controller.Session; +using MediaBrowser.Controller.Syncplay; +using MediaBrowser.Model.Session; +using MediaBrowser.Model.Syncplay; +using Microsoft.Extensions.Logging; + +namespace Emby.Server.Implementations.Syncplay +{ + /// + /// Class SyncplayController. + /// + public class SyncplayController : ISyncplayController, IDisposable + { + private enum BroadcastType + { + AllGroup = 0, + SingleUser = 1, + AllExceptUser = 2, + AllReady = 3 + } + + /// + /// The logger. + /// + private readonly ILogger _logger; + + /// + /// The session manager. + /// + private readonly ISessionManager _sessionManager; + + /// + /// The syncplay manager. + /// + private readonly ISyncplayManager _syncplayManager; + + /// + /// The group to manage. + /// + private readonly GroupInfo _group = new GroupInfo(); + + /// + public Guid GetGroupId() => _group.GroupId; + + /// + public Guid GetPlayingItemId() => _group.PlayingItem.Id; + + /// + public bool IsGroupEmpty() => _group.IsEmpty(); + + private bool _disposed = false; + + public SyncplayController( + ILogger logger, + ISessionManager sessionManager, + ISyncplayManager syncplayManager) + { + _logger = logger; + _sessionManager = sessionManager; + _syncplayManager = syncplayManager; + } + + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + /// + /// Releases unmanaged and optionally managed resources. + /// + /// true to release both managed and unmanaged resources; false to release only unmanaged resources. + protected virtual void Dispose(bool disposing) + { + if (_disposed) + { + return; + } + + _disposed = true; + } + + // TODO: use this somewhere + private void CheckDisposed() + { + if (_disposed) + { + throw new ObjectDisposedException(GetType().Name); + } + } + + private SessionInfo[] FilterUsers(SessionInfo from, BroadcastType type) + { + if (type == BroadcastType.SingleUser) + { + return new SessionInfo[] { from }; + } + else if (type == BroadcastType.AllGroup) + { + return _group.Partecipants.Values.Select( + user => user.Session + ).ToArray(); + } + else if (type == BroadcastType.AllExceptUser) + { + return _group.Partecipants.Values.Select( + user => user.Session + ).Where( + user => !user.Id.Equals(from.Id) + ).ToArray(); + } + else if (type == BroadcastType.AllReady) + { + return _group.Partecipants.Values.Where( + user => !user.IsBuffering + ).Select( + user => user.Session + ).ToArray(); + } + else + { + return new SessionInfo[] {}; + } + } + + private Task SendGroupUpdate(SessionInfo from, BroadcastType type, SyncplayGroupUpdate message) + { + IEnumerable GetTasks() + { + SessionInfo[] users = FilterUsers(from, type); + foreach (var user in users) + { + yield return _sessionManager.SendSyncplayGroupUpdate(user.Id.ToString(), message, CancellationToken.None); + } + } + + return Task.WhenAll(GetTasks()); + } + + private Task SendCommand(SessionInfo from, BroadcastType type, SyncplayCommand message) + { + IEnumerable GetTasks() + { + SessionInfo[] users = FilterUsers(from, type); + foreach (var user in users) + { + yield return _sessionManager.SendSyncplayCommand(user.Id.ToString(), message, CancellationToken.None); + } + } + + return Task.WhenAll(GetTasks()); + } + + private SyncplayCommand NewSyncplayCommand(SyncplayCommandType type) { + var command = new SyncplayCommand(); + command.GroupId = _group.GroupId.ToString(); + command.Command = type; + command.PositionTicks = _group.PositionTicks; + command.When = _group.LastActivity.ToUniversalTime().ToString("o"); + return command; + } + + private SyncplayGroupUpdate NewSyncplayGroupUpdate(SyncplayGroupUpdateType type, T data) + { + var command = new SyncplayGroupUpdate(); + command.GroupId = _group.GroupId.ToString(); + command.Type = type; + command.Data = data; + return command; + } + + /// + public void InitGroup(SessionInfo user) + { + _group.AddUser(user); + _syncplayManager.MapUserToGroup(user, this); + + _group.PlayingItem = user.FullNowPlayingItem; + _group.IsPaused = true; + _group.PositionTicks = user.PlayState.PositionTicks ??= 0; + _group.LastActivity = DateTime.UtcNow; + + var updateUser = NewSyncplayGroupUpdate(SyncplayGroupUpdateType.GroupJoined, DateTime.UtcNow.ToUniversalTime().ToString("o")); + SendGroupUpdate(user, BroadcastType.SingleUser, updateUser); + var pauseCommand = NewSyncplayCommand(SyncplayCommandType.Pause); + SendCommand(user, BroadcastType.SingleUser, pauseCommand); + } + + /// + public void UserJoin(SessionInfo user) + { + if (user.NowPlayingItem != null && user.NowPlayingItem.Id.Equals(_group.PlayingItem.Id)) + { + _group.AddUser(user); + _syncplayManager.MapUserToGroup(user, this); + + var updateUser = NewSyncplayGroupUpdate(SyncplayGroupUpdateType.GroupJoined, _group.PositionTicks); + SendGroupUpdate(user, BroadcastType.SingleUser, updateUser); + + var updateOthers = NewSyncplayGroupUpdate(SyncplayGroupUpdateType.UserJoined, user.UserName); + SendGroupUpdate(user, BroadcastType.AllExceptUser, updateOthers); + + // Client join and play, syncing will happen client side + if (!_group.IsPaused) + { + var playCommand = NewSyncplayCommand(SyncplayCommandType.Play); + SendCommand(user, BroadcastType.SingleUser, playCommand); + } + else + { + var pauseCommand = NewSyncplayCommand(SyncplayCommandType.Pause); + SendCommand(user, BroadcastType.SingleUser, pauseCommand); + } + } + else + { + var playRequest = new PlayRequest(); + playRequest.ItemIds = new Guid[] { _group.PlayingItem.Id }; + playRequest.StartPositionTicks = _group.PositionTicks; + var update = NewSyncplayGroupUpdate(SyncplayGroupUpdateType.PrepareSession, playRequest); + SendGroupUpdate(user, BroadcastType.SingleUser, update); + } + } + + /// + public void UserLeave(SessionInfo user) + { + _group.RemoveUser(user); + _syncplayManager.UnmapUserFromGroup(user, this); + + var updateUser = NewSyncplayGroupUpdate(SyncplayGroupUpdateType.GroupLeft, _group.PositionTicks); + SendGroupUpdate(user, BroadcastType.SingleUser, updateUser); + + var updateOthers = NewSyncplayGroupUpdate(SyncplayGroupUpdateType.UserLeft, user.UserName); + SendGroupUpdate(user, BroadcastType.AllExceptUser, updateOthers); + } + + /// + public void HandleRequest(SessionInfo user, SyncplayRequestInfo request) + { + if (request.Type.Equals(SyncplayRequestType.Play)) + { + if (_group.IsPaused) + { + var delay = _group.GetHighestPing() * 2; + delay = delay < _group.DefaulPing ? _group.DefaulPing : delay; + + _group.IsPaused = false; + _group.LastActivity = DateTime.UtcNow.AddMilliseconds( + delay + ); + + var command = NewSyncplayCommand(SyncplayCommandType.Play); + SendCommand(user, BroadcastType.AllGroup, command); + } + else + { + // Client got lost + var command = NewSyncplayCommand(SyncplayCommandType.Play); + SendCommand(user, BroadcastType.SingleUser, command); + } + } + else if (request.Type.Equals(SyncplayRequestType.Pause)) + { + if (!_group.IsPaused) + { + _group.IsPaused = true; + var currentTime = DateTime.UtcNow; + var elapsedTime = currentTime - _group.LastActivity; + _group.LastActivity = currentTime; + _group.PositionTicks += elapsedTime.Ticks > 0 ? elapsedTime.Ticks : 0; + + var command = NewSyncplayCommand(SyncplayCommandType.Pause); + SendCommand(user, BroadcastType.AllGroup, command); + } + else + { + var command = NewSyncplayCommand(SyncplayCommandType.Pause); + SendCommand(user, BroadcastType.SingleUser, command); + } + } + else if (request.Type.Equals(SyncplayRequestType.Seek)) + { + // Sanitize PositionTicks + var ticks = request.PositionTicks ??= 0; + ticks = ticks >= 0 ? ticks : 0; + if (_group.PlayingItem.RunTimeTicks != null) + { + var runTimeTicks = _group.PlayingItem.RunTimeTicks ??= 0; + ticks = ticks > runTimeTicks ? runTimeTicks : ticks; + } + + _group.IsPaused = true; + _group.PositionTicks = ticks; + _group.LastActivity = DateTime.UtcNow; + + var command = NewSyncplayCommand(SyncplayCommandType.Seek); + SendCommand(user, BroadcastType.AllGroup, command); + } + // TODO: client does not implement this yet + else if (request.Type.Equals(SyncplayRequestType.Buffering)) + { + if (!_group.IsPaused) + { + _group.IsPaused = true; + var currentTime = DateTime.UtcNow; + var elapsedTime = currentTime - _group.LastActivity; + _group.LastActivity = currentTime; + _group.PositionTicks += elapsedTime.Ticks > 0 ? elapsedTime.Ticks : 0; + + _group.SetBuffering(user, true); + + // Send pause command to all non-buffering users + var command = NewSyncplayCommand(SyncplayCommandType.Pause); + SendCommand(user, BroadcastType.AllReady, command); + + var updateOthers = NewSyncplayGroupUpdate(SyncplayGroupUpdateType.GroupWait, user.UserName); + SendGroupUpdate(user, BroadcastType.AllExceptUser, updateOthers); + } + else + { + var command = NewSyncplayCommand(SyncplayCommandType.Pause); + SendCommand(user, BroadcastType.SingleUser, command); + } + } + // TODO: client does not implement this yet + else if (request.Type.Equals(SyncplayRequestType.BufferingComplete)) + { + if (_group.IsPaused) + { + _group.SetBuffering(user, false); + + if (_group.IsBuffering()) { + // Others are buffering, tell this client to pause when ready + var when = request.When ??= DateTime.UtcNow; + var currentTime = DateTime.UtcNow; + var elapsedTime = currentTime - when; + var clientPosition = TimeSpan.FromTicks(request.PositionTicks ??= 0) + elapsedTime; + var delay = _group.PositionTicks - clientPosition.Ticks; + + var command = NewSyncplayCommand(SyncplayCommandType.Pause); + command.When = currentTime.AddMilliseconds( + delay + ).ToUniversalTime().ToString("o"); + SendCommand(user, BroadcastType.SingleUser, command); + } + else + { + // Let other clients resume as soon as the buffering client catches up + var when = request.When ??= DateTime.UtcNow; + var currentTime = DateTime.UtcNow; + var elapsedTime = currentTime - when; + var clientPosition = TimeSpan.FromTicks(request.PositionTicks ??= 0) + elapsedTime; + var delay = _group.PositionTicks - clientPosition.Ticks; + + _group.IsPaused = false; + + if (delay > _group.GetHighestPing() * 2) + { + // Client that was buffering is recovering, notifying others to resume + _group.LastActivity = currentTime.AddMilliseconds( + delay + ); + var command = NewSyncplayCommand(SyncplayCommandType.Play); + SendCommand(user, BroadcastType.AllExceptUser, command); + } + else + { + // Client, that was buffering, resumed playback but did not update others in time + delay = _group.GetHighestPing() * 2; + delay = delay < _group.DefaulPing ? _group.DefaulPing : delay; + + _group.LastActivity = currentTime.AddMilliseconds( + delay + ); + + var command = NewSyncplayCommand(SyncplayCommandType.Play); + SendCommand(user, BroadcastType.AllGroup, command); + } + } + } + else + { + // Make sure client has latest group state + var command = NewSyncplayCommand(SyncplayCommandType.Play); + SendCommand(user, BroadcastType.SingleUser, command); + } + } + else if (request.Type.Equals(SyncplayRequestType.KeepAlive)) + { + _group.UpdatePing(user, request.Ping ??= _group.DefaulPing); + + var keepAlive = new SyncplayGroupUpdate(); + keepAlive.GroupId = _group.GroupId.ToString(); + keepAlive.Type = SyncplayGroupUpdateType.KeepAlive; + SendGroupUpdate(user, BroadcastType.SingleUser, keepAlive); + } + } + + /// + public GroupInfoView GetInfo() + { + var info = new GroupInfoView(); + info.GroupId = GetGroupId().ToString(); + info.PlayingItemName = _group.PlayingItem.Name; + info.PlayingItemId = _group.PlayingItem.Id.ToString(); + info.PositionTicks = _group.PositionTicks; + info.Partecipants = _group.Partecipants.Values.Select(user => user.Session.UserName).ToArray(); + return info; + } + } +} diff --git a/Emby.Server.Implementations/Syncplay/SyncplayManager.cs b/Emby.Server.Implementations/Syncplay/SyncplayManager.cs new file mode 100644 index 000000000..6bfd6aa9b --- /dev/null +++ b/Emby.Server.Implementations/Syncplay/SyncplayManager.cs @@ -0,0 +1,248 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using Microsoft.Extensions.Logging; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Session; +using MediaBrowser.Controller.Syncplay; +using MediaBrowser.Model.Syncplay; + +namespace Emby.Server.Implementations.Syncplay +{ + /// + /// Class SyncplayManager. + /// + public class SyncplayManager : ISyncplayManager, IDisposable + { + /// + /// The logger. + /// + private readonly ILogger _logger; + + /// + /// The session manager. + /// + private readonly ISessionManager _sessionManager; + + /// + /// The map between users and groups. + /// + private readonly ConcurrentDictionary _userToGroupMap = + new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); + + /// + /// The groups. + /// + private readonly ConcurrentDictionary _groups = + new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); + + private bool _disposed = false; + + public SyncplayManager( + ILogger logger, + ISessionManager sessionManager) + { + _logger = logger; + _sessionManager = sessionManager; + + _sessionManager.SessionEnded += _sessionManager_SessionEnded; + _sessionManager.PlaybackStopped += _sessionManager_PlaybackStopped; + } + + /// + /// Gets all groups. + /// + /// All groups. + public IEnumerable Groups => _groups.Values; + + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + /// + /// Releases unmanaged and optionally managed resources. + /// + /// true to release both managed and unmanaged resources; false to release only unmanaged resources. + protected virtual void Dispose(bool disposing) + { + if (_disposed) + { + return; + } + + _sessionManager.SessionEnded -= _sessionManager_SessionEnded; + _sessionManager.PlaybackStopped -= _sessionManager_PlaybackStopped; + + _disposed = true; + } + + private void CheckDisposed() + { + if (_disposed) + { + throw new ObjectDisposedException(GetType().Name); + } + } + + void _sessionManager_SessionEnded(object sender, SessionEventArgs e) + { + LeaveGroup(e.SessionInfo); + } + + void _sessionManager_PlaybackStopped(object sender, PlaybackStopEventArgs e) + { + LeaveGroup(e.Session); + } + + private bool IsUserInGroup(SessionInfo user) + { + return _userToGroupMap.ContainsKey(user.Id); + } + + private Guid? GetUserGroup(SessionInfo user) + { + ISyncplayController group; + _userToGroupMap.TryGetValue(user.Id, out group); + if (group != null) + { + return group.GetGroupId(); + } + else + { + return null; + } + } + + /// + public void NewGroup(SessionInfo user) + { + if (IsUserInGroup(user)) + { + LeaveGroup(user); + } + + var group = new SyncplayController(_logger, _sessionManager, this); + _groups[group.GetGroupId().ToString()] = group; + + group.InitGroup(user); + } + + /// + public void JoinGroup(SessionInfo user, string groupId) + { + if (IsUserInGroup(user)) + { + if (GetUserGroup(user).Equals(groupId)) return; + LeaveGroup(user); + } + + ISyncplayController group; + _groups.TryGetValue(groupId, out group); + + if (group == null) + { + _logger.LogError("Syncplaymanager JoinGroup: " + groupId + " does not exist."); + + var update = new SyncplayGroupUpdate(); + update.Type = SyncplayGroupUpdateType.NotInGroup; + _sessionManager.SendSyncplayGroupUpdate(user.Id.ToString(), update, CancellationToken.None); + return; + } + group.UserJoin(user); + } + + /// + public void LeaveGroup(SessionInfo user) + { + ISyncplayController group; + _userToGroupMap.TryGetValue(user.Id, out group); + + if (group == null) + { + _logger.LogWarning("Syncplaymanager HandleRequest: " + user.Id + " not in group."); + + var update = new SyncplayGroupUpdate(); + update.Type = SyncplayGroupUpdateType.NotInGroup; + _sessionManager.SendSyncplayGroupUpdate(user.Id.ToString(), update, CancellationToken.None); + return; + } + group.UserLeave(user); + + if (group.IsGroupEmpty()) + { + _groups.Remove(group.GetGroupId().ToString(), out _); + } + } + + /// + public List ListGroups(SessionInfo user) + { + // Filter by playing item if the user is viewing something already + if (user.NowPlayingItem != null) + { + return _groups.Values.Where( + group => group.GetPlayingItemId().Equals(user.FullNowPlayingItem.Id) + ).Select( + group => group.GetInfo() + ).ToList(); + } + // Otherwise show all available groups + else + { + return _groups.Values.Select( + group => group.GetInfo() + ).ToList(); + } + } + + /// + public void HandleRequest(SessionInfo user, SyncplayRequestInfo request) + { + ISyncplayController group; + _userToGroupMap.TryGetValue(user.Id, out group); + + if (group == null) + { + _logger.LogWarning("Syncplaymanager HandleRequest: " + user.Id + " not in group."); + + var update = new SyncplayGroupUpdate(); + update.Type = SyncplayGroupUpdateType.NotInGroup; + _sessionManager.SendSyncplayGroupUpdate(user.Id.ToString(), update, CancellationToken.None); + return; + } + group.HandleRequest(user, request); + } + + /// + public void MapUserToGroup(SessionInfo user, ISyncplayController group) + { + if (IsUserInGroup(user)) + { + throw new InvalidOperationException("User in other group already!"); + } + _userToGroupMap[user.Id] = group; + } + + /// + public void UnmapUserFromGroup(SessionInfo user, ISyncplayController group) + { + if (!IsUserInGroup(user)) + { + throw new InvalidOperationException("User not in any group!"); + } + + ISyncplayController tempGroup; + _userToGroupMap.Remove(user.Id, out tempGroup); + + if (!tempGroup.GetGroupId().Equals(group.GetGroupId())) + { + throw new InvalidOperationException("User was in wrong group!"); + } + } + } +} diff --git a/MediaBrowser.Api/Syncplay/SyncplayService.cs b/MediaBrowser.Api/Syncplay/SyncplayService.cs new file mode 100644 index 000000000..f17cca9ee --- /dev/null +++ b/MediaBrowser.Api/Syncplay/SyncplayService.cs @@ -0,0 +1,261 @@ +using System; +using System.Collections.Generic; +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Net; +using MediaBrowser.Controller.Session; +using MediaBrowser.Controller.Syncplay; +using MediaBrowser.Model.Services; +using MediaBrowser.Model.Syncplay; +using Microsoft.Extensions.Logging; + +namespace MediaBrowser.Api.Syncplay +{ + [Route("/Syncplay/{SessionId}/NewGroup", "POST", Summary = "Create a new Syncplay group")] + [Authenticated] + public class SyncplayNewGroup : IReturnVoid + { + [ApiMember(Name = "SessionId", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] + public string SessionId { get; set; } + } + + [Route("/Syncplay/{SessionId}/JoinGroup", "POST", Summary = "Join an existing Syncplay group")] + [Authenticated] + public class SyncplayJoinGroup : IReturnVoid + { + [ApiMember(Name = "SessionId", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] + public string SessionId { get; set; } + + /// + /// Gets or sets the Group id. + /// + /// The Group id to join. + [ApiMember(Name = "GroupId", Description = "Group Id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")] + public string GroupId { get; set; } + } + + [Route("/Syncplay/{SessionId}/LeaveGroup", "POST", Summary = "Leave joined Syncplay group")] + [Authenticated] + public class SyncplayLeaveGroup : IReturnVoid + { + [ApiMember(Name = "SessionId", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] + public string SessionId { get; set; } + } + + [Route("/Syncplay/{SessionId}/ListGroups", "POST", Summary = "List Syncplay groups playing same item")] + [Authenticated] + public class SyncplayListGroups : IReturnVoid + { + [ApiMember(Name = "SessionId", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] + public string SessionId { get; set; } + } + + [Route("/Syncplay/{SessionId}/PlayRequest", "POST", Summary = "Request play in Syncplay group")] + [Authenticated] + public class SyncplayPlayRequest : IReturnVoid + { + [ApiMember(Name = "SessionId", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] + public string SessionId { get; set; } + } + + [Route("/Syncplay/{SessionId}/PauseRequest", "POST", Summary = "Request pause in Syncplay group")] + [Authenticated] + public class SyncplayPauseRequest : IReturnVoid + { + [ApiMember(Name = "SessionId", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] + public string SessionId { get; set; } + } + + [Route("/Syncplay/{SessionId}/SeekRequest", "POST", Summary = "Request seek in Syncplay group")] + [Authenticated] + public class SyncplaySeekRequest : IReturnVoid + { + [ApiMember(Name = "SessionId", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] + public string SessionId { get; set; } + + [ApiMember(Name = "PositionTicks", IsRequired = true, DataType = "long", ParameterType = "query", Verb = "POST")] + public long PositionTicks { get; set; } + } + + [Route("/Syncplay/{SessionId}/BufferingRequest", "POST", Summary = "Request group wait in Syncplay group while buffering")] + [Authenticated] + public class SyncplayBufferingRequest : IReturnVoid + { + [ApiMember(Name = "SessionId", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] + public string SessionId { get; set; } + + [ApiMember(Name = "When", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")] + public string When { get; set; } + + [ApiMember(Name = "PositionTicks", IsRequired = true, DataType = "long", ParameterType = "query", Verb = "POST")] + public long PositionTicks { get; set; } + + [ApiMember(Name = "Resume", IsRequired = true, DataType = "bool", ParameterType = "query", Verb = "POST")] + public bool Resume { get; set; } + } + + [Route("/Syncplay/{SessionId}/KeepAlive", "POST", Summary = "Keep session alive")] + [Authenticated] + public class SyncplayKeepAlive : IReturnVoid + { + [ApiMember(Name = "SessionId", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] + public string SessionId { get; set; } + + [ApiMember(Name = "Ping", IsRequired = true, DataType = "double", ParameterType = "query", Verb = "POST")] + public double Ping { get; set; } + } + + [Route("/Syncplay/{SessionId}/GetUtcTime", "POST", Summary = "Get UtcTime")] + [Authenticated] + public class SyncplayGetUtcTime : IReturnVoid + { + [ApiMember(Name = "SessionId", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] + public string SessionId { get; set; } + } + + /// + /// Class SyncplayService. + /// + public class SyncplayService : BaseApiService + { + /// + /// The session manager. + /// + private readonly ISessionManager _sessionManager; + + private readonly ISessionContext _sessionContext; + + /// + /// The Syncplay manager. + /// + private readonly ISyncplayManager _syncplayManager; + + public SyncplayService( + ILogger logger, + IServerConfigurationManager serverConfigurationManager, + IHttpResultFactory httpResultFactory, + ISessionManager sessionManager, + ISessionContext sessionContext, + ISyncplayManager syncplayManager) + : base(logger, serverConfigurationManager, httpResultFactory) + { + _sessionManager = sessionManager; + _sessionContext = sessionContext; + _syncplayManager = syncplayManager; + } + + /// + /// Handles the specified request. + /// + /// The request. + public void Post(SyncplayNewGroup request) + { + var currentSession = GetSession(_sessionContext); + _syncplayManager.NewGroup(currentSession); + } + + /// + /// Handles the specified request. + /// + /// The request. + public void Post(SyncplayJoinGroup request) + { + var currentSession = GetSession(_sessionContext); + _syncplayManager.JoinGroup(currentSession, request.GroupId); + } + + /// + /// Handles the specified request. + /// + /// The request. + public void Post(SyncplayLeaveGroup request) + { + var currentSession = GetSession(_sessionContext); + _syncplayManager.LeaveGroup(currentSession); + } + + /// + /// Handles the specified request. + /// + /// The request. + /// The requested list of groups. + public List Post(SyncplayListGroups request) + { + var currentSession = GetSession(_sessionContext); + return _syncplayManager.ListGroups(currentSession); + } + + /// + /// Handles the specified request. + /// + /// The request. + public void Post(SyncplayPlayRequest request) + { + var currentSession = GetSession(_sessionContext); + var syncplayRequest = new SyncplayRequestInfo(); + syncplayRequest.Type = SyncplayRequestType.Play; + _syncplayManager.HandleRequest(currentSession, syncplayRequest); + } + + /// + /// Handles the specified request. + /// + /// The request. + public void Post(SyncplayPauseRequest request) + { + var currentSession = GetSession(_sessionContext); + var syncplayRequest = new SyncplayRequestInfo(); + syncplayRequest.Type = SyncplayRequestType.Pause; + _syncplayManager.HandleRequest(currentSession, syncplayRequest); + } + + /// + /// Handles the specified request. + /// + /// The request. + public void Post(SyncplaySeekRequest request) + { + var currentSession = GetSession(_sessionContext); + var syncplayRequest = new SyncplayRequestInfo(); + syncplayRequest.Type = SyncplayRequestType.Seek; + syncplayRequest.PositionTicks = request.PositionTicks; + _syncplayManager.HandleRequest(currentSession, syncplayRequest); + } + + /// + /// Handles the specified request. + /// + /// The request. + public void Post(SyncplayBufferingRequest request) + { + var currentSession = GetSession(_sessionContext); + var syncplayRequest = new SyncplayRequestInfo(); + syncplayRequest.Type = request.Resume ? SyncplayRequestType.BufferingComplete : SyncplayRequestType.Buffering; + syncplayRequest.When = DateTime.Parse(request.When); + syncplayRequest.PositionTicks = request.PositionTicks; + _syncplayManager.HandleRequest(currentSession, syncplayRequest); + } + + /// + /// Handles the specified request. + /// + /// The request. + public void Post(SyncplayKeepAlive request) + { + var currentSession = GetSession(_sessionContext); + var syncplayRequest = new SyncplayRequestInfo(); + syncplayRequest.Type = SyncplayRequestType.KeepAlive; + syncplayRequest.Ping = Convert.ToInt64(request.Ping); + _syncplayManager.HandleRequest(currentSession, syncplayRequest); + } + + /// + /// Handles the specified request. + /// + /// The request. + /// The current UTC time. + public string Post(SyncplayGetUtcTime request) + { + return DateTime.UtcNow.ToUniversalTime().ToString("o"); + } + } +} diff --git a/MediaBrowser.Controller/Session/ISessionManager.cs b/MediaBrowser.Controller/Session/ISessionManager.cs index 771027103..4bfc0c73f 100644 --- a/MediaBrowser.Controller/Session/ISessionManager.cs +++ b/MediaBrowser.Controller/Session/ISessionManager.cs @@ -9,6 +9,7 @@ using MediaBrowser.Controller.Security; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Events; using MediaBrowser.Model.Session; +using MediaBrowser.Model.Syncplay; namespace MediaBrowser.Controller.Session { @@ -140,6 +141,24 @@ namespace MediaBrowser.Controller.Session /// Task. Task SendPlayCommand(string controllingSessionId, string sessionId, PlayRequest command, CancellationToken cancellationToken); + /// + /// Sends the SyncplayCommand. + /// + /// The session id. + /// The command. + /// The cancellation token. + /// Task. + Task SendSyncplayCommand(string sessionId, SyncplayCommand command, CancellationToken cancellationToken); + + /// + /// Sends the SyncplayGroupUpdate. + /// + /// The session id. + /// The group update. + /// The cancellation token. + /// Task. + Task SendSyncplayGroupUpdate(string sessionId, SyncplayGroupUpdate command, CancellationToken cancellationToken); + /// /// Sends the browse command. /// diff --git a/MediaBrowser.Controller/Syncplay/GroupInfo.cs b/MediaBrowser.Controller/Syncplay/GroupInfo.cs new file mode 100644 index 000000000..d37e8563b --- /dev/null +++ b/MediaBrowser.Controller/Syncplay/GroupInfo.cs @@ -0,0 +1,148 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Session; + +namespace MediaBrowser.Controller.Syncplay +{ + /// + /// Class GroupInfo. + /// + public class GroupInfo + { + /// + /// Default ping value used for users. + /// + public readonly long DefaulPing = 500; + /// + /// Gets or sets the group identifier. + /// + /// The group identifier. + public readonly Guid GroupId = Guid.NewGuid(); + + /// + /// Gets or sets the playing item. + /// + /// The playing item. + public BaseItem PlayingItem { get; set; } + + /// + /// Gets or sets whether playback is paused. + /// + /// Playback is paused. + public bool IsPaused { get; set; } + + /// + /// Gets or sets the position ticks. + /// + /// The position ticks. + public long PositionTicks { get; set; } + + /// + /// Gets or sets the last activity. + /// + /// The last activity. + public DateTime LastActivity { get; set; } + + /// + /// Gets the partecipants. + /// + /// The partecipants. + public readonly ConcurrentDictionary Partecipants = + new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); + + /// + /// Checks if a user is in this group. + /// + /// true if the user is in this group; false otherwise. + public bool ContainsUser(string sessionId) + { + return Partecipants.ContainsKey(sessionId); + } + + /// + /// Adds the user to the group. + /// + /// The session. + public void AddUser(SessionInfo user) + { + if (ContainsUser(user.Id.ToString())) return; + var member = new GroupMember(); + member.Session = user; + member.Ping = DefaulPing; + member.IsBuffering = false; + Partecipants[user.Id.ToString()] = member; + } + + /// + /// Removes the user from the group. + /// + /// The session. + + public void RemoveUser(SessionInfo user) + { + if (!ContainsUser(user.Id.ToString())) return; + GroupMember member; + Partecipants.Remove(user.Id.ToString(), out member); + } + + /// + /// Updates the ping of a user. + /// + /// The session. + /// The ping. + public void UpdatePing(SessionInfo user, long ping) + { + if (!ContainsUser(user.Id.ToString())) return; + Partecipants[user.Id.ToString()].Ping = ping; + } + + /// + /// Gets the highest ping in the group. + /// + /// The highest ping in the group. + public long GetHighestPing() + { + long max = Int64.MinValue; + foreach (var user in Partecipants.Values) + { + max = Math.Max(max, user.Ping); + } + return max; + } + + /// + /// Sets the user's buffering state. + /// + /// The session. + /// The state. + public void SetBuffering(SessionInfo user, bool isBuffering) + { + if (!ContainsUser(user.Id.ToString())) return; + Partecipants[user.Id.ToString()].IsBuffering = isBuffering; + } + + /// + /// Gets the group buffering state. + /// + /// true if there is a user buffering in the group; false otherwise. + public bool IsBuffering() + { + foreach (var user in Partecipants.Values) + { + if (user.IsBuffering) return true; + } + return false; + } + + /// + /// Checks if the group is empty. + /// + /// true if the group is empty; false otherwise. + public bool IsEmpty() + { + return Partecipants.Count == 0; + } + } +} diff --git a/MediaBrowser.Controller/Syncplay/GroupMember.cs b/MediaBrowser.Controller/Syncplay/GroupMember.cs new file mode 100644 index 000000000..7630428d7 --- /dev/null +++ b/MediaBrowser.Controller/Syncplay/GroupMember.cs @@ -0,0 +1,28 @@ +using MediaBrowser.Controller.Session; + +namespace MediaBrowser.Controller.Syncplay +{ + /// + /// Class GroupMember. + /// + public class GroupMember + { + /// + /// Gets or sets whether this member is buffering. + /// + /// true if member is buffering; false otherwise. + public bool IsBuffering { get; set; } + + /// + /// Gets or sets the session. + /// + /// The session. + public SessionInfo Session { get; set; } + + /// + /// Gets or sets the ping. + /// + /// The ping. + public long Ping { get; set; } + } +} diff --git a/MediaBrowser.Controller/Syncplay/ISyncplayController.cs b/MediaBrowser.Controller/Syncplay/ISyncplayController.cs new file mode 100644 index 000000000..c9465b27a --- /dev/null +++ b/MediaBrowser.Controller/Syncplay/ISyncplayController.cs @@ -0,0 +1,61 @@ +using System; +using MediaBrowser.Controller.Session; +using MediaBrowser.Model.Syncplay; + +namespace MediaBrowser.Controller.Syncplay +{ + /// + /// Interface ISyncplayController. + /// + public interface ISyncplayController + { + /// + /// Gets the group id. + /// + /// The group id. + Guid GetGroupId(); + + /// + /// Gets the playing item id. + /// + /// The playing item id. + Guid GetPlayingItemId(); + + /// + /// Checks if the group is empty. + /// + /// If the group is empty. + bool IsGroupEmpty(); + + /// + /// Initializes the group with the user's info. + /// + /// The session. + void InitGroup(SessionInfo user); + + /// + /// Adds the user to the group. + /// + /// The session. + void UserJoin(SessionInfo user); + + /// + /// Removes the user from the group. + /// + /// The session. + void UserLeave(SessionInfo user); + + /// + /// Handles the requested action by the user. + /// + /// The session. + /// The requested action. + void HandleRequest(SessionInfo user, SyncplayRequestInfo request); + + /// + /// Gets the info about the group for the clients. + /// + /// The group info for the clients. + GroupInfoView GetInfo(); + } +} \ No newline at end of file diff --git a/MediaBrowser.Controller/Syncplay/ISyncplayManager.cs b/MediaBrowser.Controller/Syncplay/ISyncplayManager.cs new file mode 100644 index 000000000..ec91ea69d --- /dev/null +++ b/MediaBrowser.Controller/Syncplay/ISyncplayManager.cs @@ -0,0 +1,62 @@ +using System; +using System.Collections.Generic; +using MediaBrowser.Controller.Session; +using MediaBrowser.Model.Syncplay; + +namespace MediaBrowser.Controller.Syncplay +{ + /// + /// Interface ISyncplayManager. + /// + public interface ISyncplayManager + { + /// + /// Creates a new group. + /// + /// The user that's creating the group. + void NewGroup(SessionInfo user); + + /// + /// Adds the user to a group. + /// + /// The session. + /// The group id. + void JoinGroup(SessionInfo user, string groupId); + + /// + /// Removes the user from a group. + /// + /// The session. + void LeaveGroup(SessionInfo user); + + /// + /// Gets list of available groups for a user. + /// + /// The user. + /// The list of available groups. + List ListGroups(SessionInfo user); + + /// + /// Handle a request by a user in a group. + /// + /// The session. + /// The request. + void HandleRequest(SessionInfo user, SyncplayRequestInfo request); + + /// + /// Maps a user to a group. + /// + /// The user. + /// The group. + /// + void MapUserToGroup(SessionInfo user, ISyncplayController group); + + /// + /// Unmaps a user from a group. + /// + /// The user. + /// The group. + /// + void UnmapUserFromGroup(SessionInfo user, ISyncplayController group); + } +} diff --git a/MediaBrowser.Model/Syncplay/GroupInfoModel.cs b/MediaBrowser.Model/Syncplay/GroupInfoModel.cs new file mode 100644 index 000000000..599c0dbfc --- /dev/null +++ b/MediaBrowser.Model/Syncplay/GroupInfoModel.cs @@ -0,0 +1,38 @@ +namespace MediaBrowser.Model.Syncplay +{ + /// + /// Class GroupInfoModel. + /// + public class GroupInfoView + { + /// + /// Gets or sets the group identifier. + /// + /// The group identifier. + public string GroupId { get; set; } + + /// + /// Gets or sets the playing item id. + /// + /// The playing item id. + public string PlayingItemId { get; set; } + + /// + /// Gets or sets the playing item name. + /// + /// The playing item name. + public string PlayingItemName { get; set; } + + /// + /// Gets or sets the position ticks. + /// + /// The position ticks. + public long PositionTicks { get; set; } + + /// + /// Gets or sets the partecipants. + /// + /// The partecipants. + public string[] Partecipants { get; set; } + } +} diff --git a/MediaBrowser.Model/Syncplay/SyncplayCommand.cs b/MediaBrowser.Model/Syncplay/SyncplayCommand.cs new file mode 100644 index 000000000..769316e80 --- /dev/null +++ b/MediaBrowser.Model/Syncplay/SyncplayCommand.cs @@ -0,0 +1,32 @@ +namespace MediaBrowser.Model.Syncplay +{ + /// + /// Class SyncplayCommand. + /// + public class SyncplayCommand + { + /// + /// Gets or sets the group identifier. + /// + /// The group identifier. + public string GroupId { get; set; } + + /// + /// Gets or sets the UTC time when to execute the command. + /// + /// The UTC time when to execute the command. + public string When { get; set; } + + /// + /// Gets or sets the position ticks. + /// + /// The position ticks. + public long? PositionTicks { get; set; } + + /// + /// Gets or sets the command. + /// + /// The command. + public SyncplayCommandType Command { get; set; } + } +} diff --git a/MediaBrowser.Model/Syncplay/SyncplayCommandType.cs b/MediaBrowser.Model/Syncplay/SyncplayCommandType.cs new file mode 100644 index 000000000..87b9ad66d --- /dev/null +++ b/MediaBrowser.Model/Syncplay/SyncplayCommandType.cs @@ -0,0 +1,21 @@ +namespace MediaBrowser.Model.Syncplay +{ + /// + /// Enum SyncplayCommandType. + /// + public enum SyncplayCommandType + { + /// + /// The play command. Instructs users to start playback. + /// + Play = 0, + /// + /// The pause command. Instructs users to pause playback. + /// + Pause = 1, + /// + /// The seek command. Instructs users to seek to a specified time. + /// + Seek = 2 + } +} diff --git a/MediaBrowser.Model/Syncplay/SyncplayGroupUpdate.cs b/MediaBrowser.Model/Syncplay/SyncplayGroupUpdate.cs new file mode 100644 index 000000000..c5c2f3540 --- /dev/null +++ b/MediaBrowser.Model/Syncplay/SyncplayGroupUpdate.cs @@ -0,0 +1,26 @@ +namespace MediaBrowser.Model.Syncplay +{ + /// + /// Class SyncplayGroupUpdate. + /// + public class SyncplayGroupUpdate + { + /// + /// Gets or sets the group identifier. + /// + /// The group identifier. + public string GroupId { get; set; } + + /// + /// Gets or sets the update type. + /// + /// The update type. + public SyncplayGroupUpdateType Type { get; set; } + + /// + /// Gets or sets the data. + /// + /// The data. + public T Data { get; set; } + } +} diff --git a/MediaBrowser.Model/Syncplay/SyncplayGroupUpdateType.cs b/MediaBrowser.Model/Syncplay/SyncplayGroupUpdateType.cs new file mode 100644 index 000000000..c7c5f534d --- /dev/null +++ b/MediaBrowser.Model/Syncplay/SyncplayGroupUpdateType.cs @@ -0,0 +1,41 @@ +namespace MediaBrowser.Model.Syncplay +{ + /// + /// Enum SyncplayGroupUpdateType + /// + public enum SyncplayGroupUpdateType + { + /// + /// The user-joined update. Tells members of a group about a new user. + /// + UserJoined = 0, + /// + /// The user-left update. Tells members of a group that a user left. + /// + UserLeft = 1, + /// + /// The group-joined update. Tells a user that the group has been joined. + /// + GroupJoined = 2, + /// + /// The group-left update. Tells a user that the group has been left. + /// + GroupLeft = 3, + /// + /// The group-wait update. Tells members of the group that a user is buffering. + /// + GroupWait = 4, + /// + /// The prepare-session update. Tells a user to load some content. + /// + PrepareSession = 5, + /// + /// The keep-alive update. An update to keep alive the socket. + /// + KeepAlive = 6, + /// + /// The not-in-group update. Tells a user that no group has been joined. + /// + NotInGroup = 7 + } +} diff --git a/MediaBrowser.Model/Syncplay/SyncplayRequestInfo.cs b/MediaBrowser.Model/Syncplay/SyncplayRequestInfo.cs new file mode 100644 index 000000000..7dba74ae9 --- /dev/null +++ b/MediaBrowser.Model/Syncplay/SyncplayRequestInfo.cs @@ -0,0 +1,34 @@ +using System; + +namespace MediaBrowser.Model.Syncplay +{ + /// + /// Class SyncplayRequestInfo. + /// + public class SyncplayRequestInfo + { + /// + /// Gets or sets the request type. + /// + /// The request type. + public SyncplayRequestType Type; + + /// + /// Gets or sets when the request has been made by the client. + /// + /// The date of the request. + public DateTime? When { get; set; } + + /// + /// Gets or sets the position ticks. + /// + /// The position ticks. + public long? PositionTicks { get; set; } + + /// + /// Gets or sets the ping time. + /// + /// The ping time. + public long? Ping { get; set; } + } +} diff --git a/MediaBrowser.Model/Syncplay/SyncplayRequestType.cs b/MediaBrowser.Model/Syncplay/SyncplayRequestType.cs new file mode 100644 index 000000000..44d7a0af2 --- /dev/null +++ b/MediaBrowser.Model/Syncplay/SyncplayRequestType.cs @@ -0,0 +1,33 @@ +namespace MediaBrowser.Model.Syncplay +{ + /// + /// Enum SyncplayRequestType + /// + public enum SyncplayRequestType + { + /// + /// A user is requesting a play command for the group. + /// + Play = 0, + /// + /// A user is requesting a pause command for the group. + /// + Pause = 1, + /// + /// A user is requesting a seek command for the group. + /// + Seek = 2, + /// + /// A user is signaling that playback is buffering. + /// + Buffering = 3, + /// + /// A user is signaling that playback resumed. + /// + BufferingComplete = 4, + /// + /// A user is reporting its ping. + /// + KeepAlive = 5 + } +} -- cgit v1.2.3 From 84d92ba9cea4fdd97a8d1580e67706dc4577871a Mon Sep 17 00:00:00 2001 From: gion Date: Wed, 15 Apr 2020 18:03:58 +0200 Subject: Check that client is playing the right item Send date when playback command is emitted Rename some classes --- .../Session/SessionManager.cs | 4 +- .../Syncplay/SyncplayController.cs | 80 ++++++++++++---------- .../Syncplay/SyncplayManager.cs | 28 ++++---- MediaBrowser.Api/Syncplay/SyncplayService.cs | 43 +++++++++--- MediaBrowser.Controller/Session/ISessionManager.cs | 4 +- .../Syncplay/ISyncplayController.cs | 5 +- .../Syncplay/ISyncplayManager.cs | 5 +- MediaBrowser.Model/Syncplay/GroupUpdate.cs | 26 +++++++ MediaBrowser.Model/Syncplay/GroupUpdateType.cs | 41 +++++++++++ MediaBrowser.Model/Syncplay/JoinGroupRequest.cs | 22 ++++++ MediaBrowser.Model/Syncplay/PlaybackRequest.cs | 34 +++++++++ MediaBrowser.Model/Syncplay/PlaybackRequestType.cs | 33 +++++++++ MediaBrowser.Model/Syncplay/SendCommand.cs | 38 ++++++++++ MediaBrowser.Model/Syncplay/SendCommandType.cs | 21 ++++++ MediaBrowser.Model/Syncplay/SyncplayCommand.cs | 32 --------- MediaBrowser.Model/Syncplay/SyncplayCommandType.cs | 21 ------ MediaBrowser.Model/Syncplay/SyncplayGroupUpdate.cs | 26 ------- .../Syncplay/SyncplayGroupUpdateType.cs | 41 ----------- MediaBrowser.Model/Syncplay/SyncplayRequestInfo.cs | 34 --------- MediaBrowser.Model/Syncplay/SyncplayRequestType.cs | 33 --------- 20 files changed, 313 insertions(+), 258 deletions(-) create mode 100644 MediaBrowser.Model/Syncplay/GroupUpdate.cs create mode 100644 MediaBrowser.Model/Syncplay/GroupUpdateType.cs create mode 100644 MediaBrowser.Model/Syncplay/JoinGroupRequest.cs create mode 100644 MediaBrowser.Model/Syncplay/PlaybackRequest.cs create mode 100644 MediaBrowser.Model/Syncplay/PlaybackRequestType.cs create mode 100644 MediaBrowser.Model/Syncplay/SendCommand.cs create mode 100644 MediaBrowser.Model/Syncplay/SendCommandType.cs delete mode 100644 MediaBrowser.Model/Syncplay/SyncplayCommand.cs delete mode 100644 MediaBrowser.Model/Syncplay/SyncplayCommandType.cs delete mode 100644 MediaBrowser.Model/Syncplay/SyncplayGroupUpdate.cs delete mode 100644 MediaBrowser.Model/Syncplay/SyncplayGroupUpdateType.cs delete mode 100644 MediaBrowser.Model/Syncplay/SyncplayRequestInfo.cs delete mode 100644 MediaBrowser.Model/Syncplay/SyncplayRequestType.cs (limited to 'Emby.Server.Implementations/Session') diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs index b1519b572..6a64209c1 100644 --- a/Emby.Server.Implementations/Session/SessionManager.cs +++ b/Emby.Server.Implementations/Session/SessionManager.cs @@ -1156,7 +1156,7 @@ namespace Emby.Server.Implementations.Session } /// - public async Task SendSyncplayCommand(string sessionId, SyncplayCommand command, CancellationToken cancellationToken) + public async Task SendSyncplayCommand(string sessionId, SendCommand command, CancellationToken cancellationToken) { CheckDisposed(); var session = GetSessionToRemoteControl(sessionId); @@ -1164,7 +1164,7 @@ namespace Emby.Server.Implementations.Session } /// - public async Task SendSyncplayGroupUpdate(string sessionId, SyncplayGroupUpdate command, CancellationToken cancellationToken) + public async Task SendSyncplayGroupUpdate(string sessionId, GroupUpdate command, CancellationToken cancellationToken) { CheckDisposed(); var session = GetSessionToRemoteControl(sessionId); diff --git a/Emby.Server.Implementations/Syncplay/SyncplayController.cs b/Emby.Server.Implementations/Syncplay/SyncplayController.cs index b156e5a87..fb37b2fb6 100644 --- a/Emby.Server.Implementations/Syncplay/SyncplayController.cs +++ b/Emby.Server.Implementations/Syncplay/SyncplayController.cs @@ -129,7 +129,7 @@ namespace Emby.Server.Implementations.Syncplay } } - private Task SendGroupUpdate(SessionInfo from, BroadcastType type, SyncplayGroupUpdate message) + private Task SendGroupUpdate(SessionInfo from, BroadcastType type, GroupUpdate message) { IEnumerable GetTasks() { @@ -143,7 +143,7 @@ namespace Emby.Server.Implementations.Syncplay return Task.WhenAll(GetTasks()); } - private Task SendCommand(SessionInfo from, BroadcastType type, SyncplayCommand message) + private Task SendCommand(SessionInfo from, BroadcastType type, SendCommand message) { IEnumerable GetTasks() { @@ -157,18 +157,20 @@ namespace Emby.Server.Implementations.Syncplay return Task.WhenAll(GetTasks()); } - private SyncplayCommand NewSyncplayCommand(SyncplayCommandType type) { - var command = new SyncplayCommand(); + private SendCommand NewSyncplayCommand(SendCommandType type) + { + var command = new SendCommand(); command.GroupId = _group.GroupId.ToString(); command.Command = type; command.PositionTicks = _group.PositionTicks; command.When = _group.LastActivity.ToUniversalTime().ToString("o"); + command.EmittedAt = DateTime.UtcNow.ToUniversalTime().ToString("o"); return command; } - private SyncplayGroupUpdate NewSyncplayGroupUpdate(SyncplayGroupUpdateType type, T data) + private GroupUpdate NewSyncplayGroupUpdate(GroupUpdateType type, T data) { - var command = new SyncplayGroupUpdate(); + var command = new GroupUpdate(); command.GroupId = _group.GroupId.ToString(); command.Type = type; command.Data = data; @@ -186,35 +188,37 @@ namespace Emby.Server.Implementations.Syncplay _group.PositionTicks = session.PlayState.PositionTicks ??= 0; _group.LastActivity = DateTime.UtcNow; - var updateSession = NewSyncplayGroupUpdate(SyncplayGroupUpdateType.GroupJoined, DateTime.UtcNow.ToUniversalTime().ToString("o")); + var updateSession = NewSyncplayGroupUpdate(GroupUpdateType.GroupJoined, DateTime.UtcNow.ToUniversalTime().ToString("o")); SendGroupUpdate(session, BroadcastType.SingleSession, updateSession); - var pauseCommand = NewSyncplayCommand(SyncplayCommandType.Pause); + var pauseCommand = NewSyncplayCommand(SendCommandType.Pause); SendCommand(session, BroadcastType.SingleSession, pauseCommand); } /// - public void SessionJoin(SessionInfo session) + public void SessionJoin(SessionInfo session, JoinGroupRequest request) { - if (session.NowPlayingItem != null && session.NowPlayingItem.Id.Equals(_group.PlayingItem.Id)) + if (session.NowPlayingItem != null && + session.NowPlayingItem.Id.Equals(_group.PlayingItem.Id) && + request.PlayingItemId.Equals(_group.PlayingItem.Id)) { _group.AddSession(session); _syncplayManager.MapSessionToGroup(session, this); - var updateSession = NewSyncplayGroupUpdate(SyncplayGroupUpdateType.GroupJoined, _group.PositionTicks); + var updateSession = NewSyncplayGroupUpdate(GroupUpdateType.GroupJoined, DateTime.UtcNow.ToUniversalTime().ToString("o")); SendGroupUpdate(session, BroadcastType.SingleSession, updateSession); - var updateOthers = NewSyncplayGroupUpdate(SyncplayGroupUpdateType.UserJoined, session.UserName); + var updateOthers = NewSyncplayGroupUpdate(GroupUpdateType.UserJoined, session.UserName); SendGroupUpdate(session, BroadcastType.AllExceptSession, updateOthers); // Client join and play, syncing will happen client side if (!_group.IsPaused) { - var playCommand = NewSyncplayCommand(SyncplayCommandType.Play); + var playCommand = NewSyncplayCommand(SendCommandType.Play); SendCommand(session, BroadcastType.SingleSession, playCommand); } else { - var pauseCommand = NewSyncplayCommand(SyncplayCommandType.Pause); + var pauseCommand = NewSyncplayCommand(SendCommandType.Pause); SendCommand(session, BroadcastType.SingleSession, pauseCommand); } } @@ -223,7 +227,7 @@ namespace Emby.Server.Implementations.Syncplay var playRequest = new PlayRequest(); playRequest.ItemIds = new Guid[] { _group.PlayingItem.Id }; playRequest.StartPositionTicks = _group.PositionTicks; - var update = NewSyncplayGroupUpdate(SyncplayGroupUpdateType.PrepareSession, playRequest); + var update = NewSyncplayGroupUpdate(GroupUpdateType.PrepareSession, playRequest); SendGroupUpdate(session, BroadcastType.SingleSession, update); } } @@ -234,17 +238,17 @@ namespace Emby.Server.Implementations.Syncplay _group.RemoveSession(session); _syncplayManager.UnmapSessionFromGroup(session, this); - var updateSession = NewSyncplayGroupUpdate(SyncplayGroupUpdateType.GroupLeft, _group.PositionTicks); + var updateSession = NewSyncplayGroupUpdate(GroupUpdateType.GroupLeft, _group.PositionTicks); SendGroupUpdate(session, BroadcastType.SingleSession, updateSession); - var updateOthers = NewSyncplayGroupUpdate(SyncplayGroupUpdateType.UserLeft, session.UserName); + var updateOthers = NewSyncplayGroupUpdate(GroupUpdateType.UserLeft, session.UserName); SendGroupUpdate(session, BroadcastType.AllExceptSession, updateOthers); } /// - public void HandleRequest(SessionInfo session, SyncplayRequestInfo request) + public void HandleRequest(SessionInfo session, PlaybackRequest request) { - if (request.Type.Equals(SyncplayRequestType.Play)) + if (request.Type.Equals(PlaybackRequestType.Play)) { if (_group.IsPaused) { @@ -256,17 +260,17 @@ namespace Emby.Server.Implementations.Syncplay delay ); - var command = NewSyncplayCommand(SyncplayCommandType.Play); + var command = NewSyncplayCommand(SendCommandType.Play); SendCommand(session, BroadcastType.AllGroup, command); } else { // Client got lost - var command = NewSyncplayCommand(SyncplayCommandType.Play); + var command = NewSyncplayCommand(SendCommandType.Play); SendCommand(session, BroadcastType.SingleSession, command); } } - else if (request.Type.Equals(SyncplayRequestType.Pause)) + else if (request.Type.Equals(PlaybackRequestType.Pause)) { if (!_group.IsPaused) { @@ -276,16 +280,16 @@ namespace Emby.Server.Implementations.Syncplay _group.LastActivity = currentTime; _group.PositionTicks += elapsedTime.Ticks > 0 ? elapsedTime.Ticks : 0; - var command = NewSyncplayCommand(SyncplayCommandType.Pause); + var command = NewSyncplayCommand(SendCommandType.Pause); SendCommand(session, BroadcastType.AllGroup, command); } else { - var command = NewSyncplayCommand(SyncplayCommandType.Pause); + var command = NewSyncplayCommand(SendCommandType.Pause); SendCommand(session, BroadcastType.SingleSession, command); } } - else if (request.Type.Equals(SyncplayRequestType.Seek)) + else if (request.Type.Equals(PlaybackRequestType.Seek)) { // Sanitize PositionTicks var ticks = request.PositionTicks ??= 0; @@ -300,11 +304,11 @@ namespace Emby.Server.Implementations.Syncplay _group.PositionTicks = ticks; _group.LastActivity = DateTime.UtcNow; - var command = NewSyncplayCommand(SyncplayCommandType.Seek); + var command = NewSyncplayCommand(SendCommandType.Seek); SendCommand(session, BroadcastType.AllGroup, command); } // TODO: client does not implement this yet - else if (request.Type.Equals(SyncplayRequestType.Buffering)) + else if (request.Type.Equals(PlaybackRequestType.Buffering)) { if (!_group.IsPaused) { @@ -317,20 +321,20 @@ namespace Emby.Server.Implementations.Syncplay _group.SetBuffering(session, true); // Send pause command to all non-buffering sessions - var command = NewSyncplayCommand(SyncplayCommandType.Pause); + var command = NewSyncplayCommand(SendCommandType.Pause); SendCommand(session, BroadcastType.AllReady, command); - var updateOthers = NewSyncplayGroupUpdate(SyncplayGroupUpdateType.GroupWait, session.UserName); + var updateOthers = NewSyncplayGroupUpdate(GroupUpdateType.GroupWait, session.UserName); SendGroupUpdate(session, BroadcastType.AllExceptSession, updateOthers); } else { - var command = NewSyncplayCommand(SyncplayCommandType.Pause); + var command = NewSyncplayCommand(SendCommandType.Pause); SendCommand(session, BroadcastType.SingleSession, command); } } // TODO: client does not implement this yet - else if (request.Type.Equals(SyncplayRequestType.BufferingComplete)) + else if (request.Type.Equals(PlaybackRequestType.BufferingComplete)) { if (_group.IsPaused) { @@ -344,7 +348,7 @@ namespace Emby.Server.Implementations.Syncplay var clientPosition = TimeSpan.FromTicks(request.PositionTicks ??= 0) + elapsedTime; var delay = _group.PositionTicks - clientPosition.Ticks; - var command = NewSyncplayCommand(SyncplayCommandType.Pause); + var command = NewSyncplayCommand(SendCommandType.Pause); command.When = currentTime.AddMilliseconds( delay ).ToUniversalTime().ToString("o"); @@ -367,7 +371,7 @@ namespace Emby.Server.Implementations.Syncplay _group.LastActivity = currentTime.AddMilliseconds( delay ); - var command = NewSyncplayCommand(SyncplayCommandType.Play); + var command = NewSyncplayCommand(SendCommandType.Play); SendCommand(session, BroadcastType.AllExceptSession, command); } else @@ -380,7 +384,7 @@ namespace Emby.Server.Implementations.Syncplay delay ); - var command = NewSyncplayCommand(SyncplayCommandType.Play); + var command = NewSyncplayCommand(SendCommandType.Play); SendCommand(session, BroadcastType.AllGroup, command); } } @@ -388,17 +392,17 @@ namespace Emby.Server.Implementations.Syncplay else { // Make sure client has latest group state - var command = NewSyncplayCommand(SyncplayCommandType.Play); + var command = NewSyncplayCommand(SendCommandType.Play); SendCommand(session, BroadcastType.SingleSession, command); } } - else if (request.Type.Equals(SyncplayRequestType.KeepAlive)) + else if (request.Type.Equals(PlaybackRequestType.KeepAlive)) { _group.UpdatePing(session, request.Ping ??= _group.DefaulPing); - var keepAlive = new SyncplayGroupUpdate(); + var keepAlive = new GroupUpdate(); keepAlive.GroupId = _group.GroupId.ToString(); - keepAlive.Type = SyncplayGroupUpdateType.KeepAlive; + keepAlive.Type = GroupUpdateType.KeepAlive; SendGroupUpdate(session, BroadcastType.SingleSession, keepAlive); } } diff --git a/Emby.Server.Implementations/Syncplay/SyncplayManager.cs b/Emby.Server.Implementations/Syncplay/SyncplayManager.cs index 5c44326f5..60d70e5fd 100644 --- a/Emby.Server.Implementations/Syncplay/SyncplayManager.cs +++ b/Emby.Server.Implementations/Syncplay/SyncplayManager.cs @@ -166,7 +166,7 @@ namespace Emby.Server.Implementations.Syncplay if (user.Policy.SyncplayAccess != SyncplayAccess.CreateAndJoinGroups) { // TODO: shall an error message be sent back to the client? - return; + throw new ArgumentException("User does not have permission to create groups"); } if (IsSessionInGroup(session)) @@ -181,14 +181,14 @@ namespace Emby.Server.Implementations.Syncplay } /// - public void JoinGroup(SessionInfo session, string groupId) + public void JoinGroup(SessionInfo session, string groupId, JoinGroupRequest request) { var user = _userManager.GetUserById(session.UserId); if (user.Policy.SyncplayAccess == SyncplayAccess.None) { // TODO: shall an error message be sent back to the client? - return; + throw new ArgumentException("User does not have access to syncplay"); } if (IsSessionInGroup(session)) @@ -204,18 +204,18 @@ namespace Emby.Server.Implementations.Syncplay { _logger.LogError("Syncplaymanager JoinGroup: " + groupId + " does not exist."); - var update = new SyncplayGroupUpdate(); - update.Type = SyncplayGroupUpdateType.NotInGroup; + var update = new GroupUpdate(); + update.Type = GroupUpdateType.NotInGroup; _sessionManager.SendSyncplayGroupUpdate(session.Id.ToString(), update, CancellationToken.None); return; } if (!HasAccessToItem(user, group.GetPlayingItemId())) { - return; + throw new ArgumentException("User does not have access to playing item"); } - group.SessionJoin(session); + group.SessionJoin(session, request); } /// @@ -230,8 +230,8 @@ namespace Emby.Server.Implementations.Syncplay { _logger.LogWarning("Syncplaymanager HandleRequest: " + session.Id + " not in group."); - var update = new SyncplayGroupUpdate(); - update.Type = SyncplayGroupUpdateType.NotInGroup; + var update = new GroupUpdate(); + update.Type = GroupUpdateType.NotInGroup; _sessionManager.SendSyncplayGroupUpdate(session.Id.ToString(), update, CancellationToken.None); return; } @@ -276,14 +276,14 @@ namespace Emby.Server.Implementations.Syncplay } /// - public void HandleRequest(SessionInfo session, SyncplayRequestInfo request) + public void HandleRequest(SessionInfo session, PlaybackRequest request) { var user = _userManager.GetUserById(session.UserId); if (user.Policy.SyncplayAccess == SyncplayAccess.None) { // TODO: same as LeaveGroup - return; + throw new ArgumentException("User does not have access to syncplay"); } ISyncplayController group; @@ -293,14 +293,14 @@ namespace Emby.Server.Implementations.Syncplay { _logger.LogWarning("Syncplaymanager HandleRequest: " + session.Id + " not in group."); - var update = new SyncplayGroupUpdate(); - update.Type = SyncplayGroupUpdateType.NotInGroup; + var update = new GroupUpdate(); + update.Type = GroupUpdateType.NotInGroup; _sessionManager.SendSyncplayGroupUpdate(session.Id.ToString(), update, CancellationToken.None); return; } group.HandleRequest(session, request); } - + /// public void MapSessionToGroup(SessionInfo session, ISyncplayController group) { diff --git a/MediaBrowser.Api/Syncplay/SyncplayService.cs b/MediaBrowser.Api/Syncplay/SyncplayService.cs index f17cca9ee..0f9d1b733 100644 --- a/MediaBrowser.Api/Syncplay/SyncplayService.cs +++ b/MediaBrowser.Api/Syncplay/SyncplayService.cs @@ -31,6 +31,13 @@ namespace MediaBrowser.Api.Syncplay /// The Group id to join. [ApiMember(Name = "GroupId", Description = "Group Id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")] public string GroupId { get; set; } + + /// + /// Gets or sets the playing item id. + /// + /// The client's currently playing item id. + [ApiMember(Name = "PlayingItemId", Description = "Client's playing item id", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")] + public string PlayingItemId { get; set; } } [Route("/Syncplay/{SessionId}/LeaveGroup", "POST", Summary = "Leave joined Syncplay group")] @@ -160,7 +167,21 @@ namespace MediaBrowser.Api.Syncplay public void Post(SyncplayJoinGroup request) { var currentSession = GetSession(_sessionContext); - _syncplayManager.JoinGroup(currentSession, request.GroupId); + var joinRequest = new JoinGroupRequest(); + joinRequest.GroupId = Guid.Parse(request.GroupId); + try + { + joinRequest.PlayingItemId = Guid.Parse(request.PlayingItemId); + } + catch (ArgumentNullException) + { + // Do nothing + } + catch (FormatException) + { + // Do nothing + } + _syncplayManager.JoinGroup(currentSession, request.GroupId, joinRequest); } /// @@ -191,8 +212,8 @@ namespace MediaBrowser.Api.Syncplay public void Post(SyncplayPlayRequest request) { var currentSession = GetSession(_sessionContext); - var syncplayRequest = new SyncplayRequestInfo(); - syncplayRequest.Type = SyncplayRequestType.Play; + var syncplayRequest = new PlaybackRequest(); + syncplayRequest.Type = PlaybackRequestType.Play; _syncplayManager.HandleRequest(currentSession, syncplayRequest); } @@ -203,8 +224,8 @@ namespace MediaBrowser.Api.Syncplay public void Post(SyncplayPauseRequest request) { var currentSession = GetSession(_sessionContext); - var syncplayRequest = new SyncplayRequestInfo(); - syncplayRequest.Type = SyncplayRequestType.Pause; + var syncplayRequest = new PlaybackRequest(); + syncplayRequest.Type = PlaybackRequestType.Pause; _syncplayManager.HandleRequest(currentSession, syncplayRequest); } @@ -215,8 +236,8 @@ namespace MediaBrowser.Api.Syncplay public void Post(SyncplaySeekRequest request) { var currentSession = GetSession(_sessionContext); - var syncplayRequest = new SyncplayRequestInfo(); - syncplayRequest.Type = SyncplayRequestType.Seek; + var syncplayRequest = new PlaybackRequest(); + syncplayRequest.Type = PlaybackRequestType.Seek; syncplayRequest.PositionTicks = request.PositionTicks; _syncplayManager.HandleRequest(currentSession, syncplayRequest); } @@ -228,8 +249,8 @@ namespace MediaBrowser.Api.Syncplay public void Post(SyncplayBufferingRequest request) { var currentSession = GetSession(_sessionContext); - var syncplayRequest = new SyncplayRequestInfo(); - syncplayRequest.Type = request.Resume ? SyncplayRequestType.BufferingComplete : SyncplayRequestType.Buffering; + var syncplayRequest = new PlaybackRequest(); + syncplayRequest.Type = request.Resume ? PlaybackRequestType.BufferingComplete : PlaybackRequestType.Buffering; syncplayRequest.When = DateTime.Parse(request.When); syncplayRequest.PositionTicks = request.PositionTicks; _syncplayManager.HandleRequest(currentSession, syncplayRequest); @@ -242,8 +263,8 @@ namespace MediaBrowser.Api.Syncplay public void Post(SyncplayKeepAlive request) { var currentSession = GetSession(_sessionContext); - var syncplayRequest = new SyncplayRequestInfo(); - syncplayRequest.Type = SyncplayRequestType.KeepAlive; + var syncplayRequest = new PlaybackRequest(); + syncplayRequest.Type = PlaybackRequestType.KeepAlive; syncplayRequest.Ping = Convert.ToInt64(request.Ping); _syncplayManager.HandleRequest(currentSession, syncplayRequest); } diff --git a/MediaBrowser.Controller/Session/ISessionManager.cs b/MediaBrowser.Controller/Session/ISessionManager.cs index 4bfc0c73f..39c065b89 100644 --- a/MediaBrowser.Controller/Session/ISessionManager.cs +++ b/MediaBrowser.Controller/Session/ISessionManager.cs @@ -148,7 +148,7 @@ namespace MediaBrowser.Controller.Session /// The command. /// The cancellation token. /// Task. - Task SendSyncplayCommand(string sessionId, SyncplayCommand command, CancellationToken cancellationToken); + Task SendSyncplayCommand(string sessionId, SendCommand command, CancellationToken cancellationToken); /// /// Sends the SyncplayGroupUpdate. @@ -157,7 +157,7 @@ namespace MediaBrowser.Controller.Session /// The group update. /// The cancellation token. /// Task. - Task SendSyncplayGroupUpdate(string sessionId, SyncplayGroupUpdate command, CancellationToken cancellationToken); + Task SendSyncplayGroupUpdate(string sessionId, GroupUpdate command, CancellationToken cancellationToken); /// /// Sends the browse command. diff --git a/MediaBrowser.Controller/Syncplay/ISyncplayController.cs b/MediaBrowser.Controller/Syncplay/ISyncplayController.cs index d35ae3101..5b08eac0a 100644 --- a/MediaBrowser.Controller/Syncplay/ISyncplayController.cs +++ b/MediaBrowser.Controller/Syncplay/ISyncplayController.cs @@ -37,7 +37,8 @@ namespace MediaBrowser.Controller.Syncplay /// Adds the session to the group. /// /// The session. - void SessionJoin(SessionInfo session); + /// The request. + void SessionJoin(SessionInfo session, JoinGroupRequest request); /// /// Removes the session from the group. @@ -50,7 +51,7 @@ namespace MediaBrowser.Controller.Syncplay /// /// The session. /// The requested action. - void HandleRequest(SessionInfo session, SyncplayRequestInfo request); + void HandleRequest(SessionInfo session, PlaybackRequest request); /// /// Gets the info about the group for the clients. diff --git a/MediaBrowser.Controller/Syncplay/ISyncplayManager.cs b/MediaBrowser.Controller/Syncplay/ISyncplayManager.cs index 09920a19f..d0cf8fa9c 100644 --- a/MediaBrowser.Controller/Syncplay/ISyncplayManager.cs +++ b/MediaBrowser.Controller/Syncplay/ISyncplayManager.cs @@ -21,7 +21,8 @@ namespace MediaBrowser.Controller.Syncplay /// /// The session. /// The group id. - void JoinGroup(SessionInfo session, string groupId); + /// The request. + void JoinGroup(SessionInfo session, string groupId, JoinGroupRequest request); /// /// Removes the session from a group. @@ -41,7 +42,7 @@ namespace MediaBrowser.Controller.Syncplay /// /// The session. /// The request. - void HandleRequest(SessionInfo session, SyncplayRequestInfo request); + void HandleRequest(SessionInfo session, PlaybackRequest request); /// /// Maps a session to a group. diff --git a/MediaBrowser.Model/Syncplay/GroupUpdate.cs b/MediaBrowser.Model/Syncplay/GroupUpdate.cs new file mode 100644 index 000000000..cc49e92a9 --- /dev/null +++ b/MediaBrowser.Model/Syncplay/GroupUpdate.cs @@ -0,0 +1,26 @@ +namespace MediaBrowser.Model.Syncplay +{ + /// + /// Class GroupUpdate. + /// + public class GroupUpdate + { + /// + /// Gets or sets the group identifier. + /// + /// The group identifier. + public string GroupId { get; set; } + + /// + /// Gets or sets the update type. + /// + /// The update type. + public GroupUpdateType Type { get; set; } + + /// + /// Gets or sets the data. + /// + /// The data. + public T Data { get; set; } + } +} diff --git a/MediaBrowser.Model/Syncplay/GroupUpdateType.cs b/MediaBrowser.Model/Syncplay/GroupUpdateType.cs new file mode 100644 index 000000000..ceb778b36 --- /dev/null +++ b/MediaBrowser.Model/Syncplay/GroupUpdateType.cs @@ -0,0 +1,41 @@ +namespace MediaBrowser.Model.Syncplay +{ + /// + /// Enum GroupUpdateType + /// + public enum GroupUpdateType + { + /// + /// The user-joined update. Tells members of a group about a new user. + /// + UserJoined = 0, + /// + /// The user-left update. Tells members of a group that a user left. + /// + UserLeft = 1, + /// + /// The group-joined update. Tells a user that the group has been joined. + /// + GroupJoined = 2, + /// + /// The group-left update. Tells a user that the group has been left. + /// + GroupLeft = 3, + /// + /// The group-wait update. Tells members of the group that a user is buffering. + /// + GroupWait = 4, + /// + /// The prepare-session update. Tells a user to load some content. + /// + PrepareSession = 5, + /// + /// The keep-alive update. An update to keep alive the socket. + /// + KeepAlive = 6, + /// + /// The not-in-group update. Tells a user that no group has been joined. + /// + NotInGroup = 7 + } +} diff --git a/MediaBrowser.Model/Syncplay/JoinGroupRequest.cs b/MediaBrowser.Model/Syncplay/JoinGroupRequest.cs new file mode 100644 index 000000000..8d8a2646a --- /dev/null +++ b/MediaBrowser.Model/Syncplay/JoinGroupRequest.cs @@ -0,0 +1,22 @@ +using System; + +namespace MediaBrowser.Model.Syncplay +{ + /// + /// Class JoinGroupRequest. + /// + public class JoinGroupRequest + { + /// + /// Gets or sets the Group id. + /// + /// The Group id to join. + public Guid GroupId { get; set; } + + /// + /// Gets or sets the playing item id. + /// + /// The client's currently playing item id. + public Guid PlayingItemId { get; set; } + } +} diff --git a/MediaBrowser.Model/Syncplay/PlaybackRequest.cs b/MediaBrowser.Model/Syncplay/PlaybackRequest.cs new file mode 100644 index 000000000..cae769db0 --- /dev/null +++ b/MediaBrowser.Model/Syncplay/PlaybackRequest.cs @@ -0,0 +1,34 @@ +using System; + +namespace MediaBrowser.Model.Syncplay +{ + /// + /// Class PlaybackRequest. + /// + public class PlaybackRequest + { + /// + /// Gets or sets the request type. + /// + /// The request type. + public PlaybackRequestType Type; + + /// + /// Gets or sets when the request has been made by the client. + /// + /// The date of the request. + public DateTime? When { get; set; } + + /// + /// Gets or sets the position ticks. + /// + /// The position ticks. + public long? PositionTicks { get; set; } + + /// + /// Gets or sets the ping time. + /// + /// The ping time. + public long? Ping { get; set; } + } +} diff --git a/MediaBrowser.Model/Syncplay/PlaybackRequestType.cs b/MediaBrowser.Model/Syncplay/PlaybackRequestType.cs new file mode 100644 index 000000000..da770736c --- /dev/null +++ b/MediaBrowser.Model/Syncplay/PlaybackRequestType.cs @@ -0,0 +1,33 @@ +namespace MediaBrowser.Model.Syncplay +{ + /// + /// Enum PlaybackRequestType + /// + public enum PlaybackRequestType + { + /// + /// A user is requesting a play command for the group. + /// + Play = 0, + /// + /// A user is requesting a pause command for the group. + /// + Pause = 1, + /// + /// A user is requesting a seek command for the group. + /// + Seek = 2, + /// + /// A user is signaling that playback is buffering. + /// + Buffering = 3, + /// + /// A user is signaling that playback resumed. + /// + BufferingComplete = 4, + /// + /// A user is reporting its ping. + /// + KeepAlive = 5 + } +} diff --git a/MediaBrowser.Model/Syncplay/SendCommand.cs b/MediaBrowser.Model/Syncplay/SendCommand.cs new file mode 100644 index 000000000..d9f391403 --- /dev/null +++ b/MediaBrowser.Model/Syncplay/SendCommand.cs @@ -0,0 +1,38 @@ +namespace MediaBrowser.Model.Syncplay +{ + /// + /// Class SendCommand. + /// + public class SendCommand + { + /// + /// Gets or sets the group identifier. + /// + /// The group identifier. + public string GroupId { get; set; } + + /// + /// Gets or sets the UTC time when to execute the command. + /// + /// The UTC time when to execute the command. + public string When { get; set; } + + /// + /// Gets or sets the position ticks. + /// + /// The position ticks. + public long? PositionTicks { get; set; } + + /// + /// Gets or sets the command. + /// + /// The command. + public SendCommandType Command { get; set; } + + /// + /// Gets or sets the UTC time when this command has been emitted. + /// + /// The UTC time when this command has been emitted. + public string EmittedAt { get; set; } + } +} diff --git a/MediaBrowser.Model/Syncplay/SendCommandType.cs b/MediaBrowser.Model/Syncplay/SendCommandType.cs new file mode 100644 index 000000000..02e4774d0 --- /dev/null +++ b/MediaBrowser.Model/Syncplay/SendCommandType.cs @@ -0,0 +1,21 @@ +namespace MediaBrowser.Model.Syncplay +{ + /// + /// Enum SendCommandType. + /// + public enum SendCommandType + { + /// + /// The play command. Instructs users to start playback. + /// + Play = 0, + /// + /// The pause command. Instructs users to pause playback. + /// + Pause = 1, + /// + /// The seek command. Instructs users to seek to a specified time. + /// + Seek = 2 + } +} diff --git a/MediaBrowser.Model/Syncplay/SyncplayCommand.cs b/MediaBrowser.Model/Syncplay/SyncplayCommand.cs deleted file mode 100644 index 769316e80..000000000 --- a/MediaBrowser.Model/Syncplay/SyncplayCommand.cs +++ /dev/null @@ -1,32 +0,0 @@ -namespace MediaBrowser.Model.Syncplay -{ - /// - /// Class SyncplayCommand. - /// - public class SyncplayCommand - { - /// - /// Gets or sets the group identifier. - /// - /// The group identifier. - public string GroupId { get; set; } - - /// - /// Gets or sets the UTC time when to execute the command. - /// - /// The UTC time when to execute the command. - public string When { get; set; } - - /// - /// Gets or sets the position ticks. - /// - /// The position ticks. - public long? PositionTicks { get; set; } - - /// - /// Gets or sets the command. - /// - /// The command. - public SyncplayCommandType Command { get; set; } - } -} diff --git a/MediaBrowser.Model/Syncplay/SyncplayCommandType.cs b/MediaBrowser.Model/Syncplay/SyncplayCommandType.cs deleted file mode 100644 index 87b9ad66d..000000000 --- a/MediaBrowser.Model/Syncplay/SyncplayCommandType.cs +++ /dev/null @@ -1,21 +0,0 @@ -namespace MediaBrowser.Model.Syncplay -{ - /// - /// Enum SyncplayCommandType. - /// - public enum SyncplayCommandType - { - /// - /// The play command. Instructs users to start playback. - /// - Play = 0, - /// - /// The pause command. Instructs users to pause playback. - /// - Pause = 1, - /// - /// The seek command. Instructs users to seek to a specified time. - /// - Seek = 2 - } -} diff --git a/MediaBrowser.Model/Syncplay/SyncplayGroupUpdate.cs b/MediaBrowser.Model/Syncplay/SyncplayGroupUpdate.cs deleted file mode 100644 index c5c2f3540..000000000 --- a/MediaBrowser.Model/Syncplay/SyncplayGroupUpdate.cs +++ /dev/null @@ -1,26 +0,0 @@ -namespace MediaBrowser.Model.Syncplay -{ - /// - /// Class SyncplayGroupUpdate. - /// - public class SyncplayGroupUpdate - { - /// - /// Gets or sets the group identifier. - /// - /// The group identifier. - public string GroupId { get; set; } - - /// - /// Gets or sets the update type. - /// - /// The update type. - public SyncplayGroupUpdateType Type { get; set; } - - /// - /// Gets or sets the data. - /// - /// The data. - public T Data { get; set; } - } -} diff --git a/MediaBrowser.Model/Syncplay/SyncplayGroupUpdateType.cs b/MediaBrowser.Model/Syncplay/SyncplayGroupUpdateType.cs deleted file mode 100644 index c7c5f534d..000000000 --- a/MediaBrowser.Model/Syncplay/SyncplayGroupUpdateType.cs +++ /dev/null @@ -1,41 +0,0 @@ -namespace MediaBrowser.Model.Syncplay -{ - /// - /// Enum SyncplayGroupUpdateType - /// - public enum SyncplayGroupUpdateType - { - /// - /// The user-joined update. Tells members of a group about a new user. - /// - UserJoined = 0, - /// - /// The user-left update. Tells members of a group that a user left. - /// - UserLeft = 1, - /// - /// The group-joined update. Tells a user that the group has been joined. - /// - GroupJoined = 2, - /// - /// The group-left update. Tells a user that the group has been left. - /// - GroupLeft = 3, - /// - /// The group-wait update. Tells members of the group that a user is buffering. - /// - GroupWait = 4, - /// - /// The prepare-session update. Tells a user to load some content. - /// - PrepareSession = 5, - /// - /// The keep-alive update. An update to keep alive the socket. - /// - KeepAlive = 6, - /// - /// The not-in-group update. Tells a user that no group has been joined. - /// - NotInGroup = 7 - } -} diff --git a/MediaBrowser.Model/Syncplay/SyncplayRequestInfo.cs b/MediaBrowser.Model/Syncplay/SyncplayRequestInfo.cs deleted file mode 100644 index 7dba74ae9..000000000 --- a/MediaBrowser.Model/Syncplay/SyncplayRequestInfo.cs +++ /dev/null @@ -1,34 +0,0 @@ -using System; - -namespace MediaBrowser.Model.Syncplay -{ - /// - /// Class SyncplayRequestInfo. - /// - public class SyncplayRequestInfo - { - /// - /// Gets or sets the request type. - /// - /// The request type. - public SyncplayRequestType Type; - - /// - /// Gets or sets when the request has been made by the client. - /// - /// The date of the request. - public DateTime? When { get; set; } - - /// - /// Gets or sets the position ticks. - /// - /// The position ticks. - public long? PositionTicks { get; set; } - - /// - /// Gets or sets the ping time. - /// - /// The ping time. - public long? Ping { get; set; } - } -} diff --git a/MediaBrowser.Model/Syncplay/SyncplayRequestType.cs b/MediaBrowser.Model/Syncplay/SyncplayRequestType.cs deleted file mode 100644 index 44d7a0af2..000000000 --- a/MediaBrowser.Model/Syncplay/SyncplayRequestType.cs +++ /dev/null @@ -1,33 +0,0 @@ -namespace MediaBrowser.Model.Syncplay -{ - /// - /// Enum SyncplayRequestType - /// - public enum SyncplayRequestType - { - /// - /// A user is requesting a play command for the group. - /// - Play = 0, - /// - /// A user is requesting a pause command for the group. - /// - Pause = 1, - /// - /// A user is requesting a seek command for the group. - /// - Seek = 2, - /// - /// A user is signaling that playback is buffering. - /// - Buffering = 3, - /// - /// A user is signaling that playback resumed. - /// - BufferingComplete = 4, - /// - /// A user is reporting its ping. - /// - KeepAlive = 5 - } -} -- cgit v1.2.3 From aad5058d25b3c295e9ea5b4330dde219034ba8c8 Mon Sep 17 00:00:00 2001 From: gion Date: Fri, 17 Apr 2020 13:47:00 +0200 Subject: Implement KeepAlive for WebSockets --- .../HttpServer/WebSocketConnection.cs | 27 +++- .../Session/SessionWebSocketListener.cs | 156 +++++++++++++++++++++ .../Net/IWebSocketConnection.cs | 6 + 3 files changed, 183 insertions(+), 6 deletions(-) (limited to 'Emby.Server.Implementations/Session') diff --git a/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs b/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs index 2292d86a4..171047e65 100644 --- a/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs +++ b/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs @@ -94,6 +94,9 @@ namespace Emby.Server.Implementations.HttpServer /// The last activity date. public DateTime LastActivityDate { get; private set; } + /// + public DateTime LastKeepAliveDate { get; set; } + /// /// Gets the id. /// @@ -158,11 +161,6 @@ namespace Emby.Server.Implementations.HttpServer return; } - if (OnReceive == null) - { - return; - } - try { var stub = (WebSocketMessage)_jsonSerializer.DeserializeFromString(message, typeof(WebSocketMessage)); @@ -174,7 +172,15 @@ namespace Emby.Server.Implementations.HttpServer Connection = this }; - OnReceive(info); + if (info.MessageType.Equals("KeepAlive", StringComparison.Ordinal)) + { + SendKeepAliveResponse(); + } + + if (OnReceive != null) + { + OnReceive(info); + } } catch (Exception ex) { @@ -233,6 +239,15 @@ namespace Emby.Server.Implementations.HttpServer return _socket.SendAsync(text, true, cancellationToken); } + private Task SendKeepAliveResponse() + { + LastKeepAliveDate = DateTime.UtcNow; + return SendAsync(new WebSocketMessage + { + MessageType = "KeepAlive" + }, CancellationToken.None); + } + /// public void Dispose() { diff --git a/Emby.Server.Implementations/Session/SessionWebSocketListener.cs b/Emby.Server.Implementations/Session/SessionWebSocketListener.cs index 930f2d35d..d8e02ef39 100644 --- a/Emby.Server.Implementations/Session/SessionWebSocketListener.cs +++ b/Emby.Server.Implementations/Session/SessionWebSocketListener.cs @@ -1,8 +1,13 @@ using System; +using System.Collections.Concurrent; +using System.Linq; +using System.Net.WebSockets; +using System.Threading; using System.Threading.Tasks; using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Session; using MediaBrowser.Model.Events; +using MediaBrowser.Model.Net; using MediaBrowser.Model.Serialization; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; @@ -14,6 +19,21 @@ namespace Emby.Server.Implementations.Session /// public class SessionWebSocketListener : IWebSocketListener, IDisposable { + /// + /// The timeout in seconds after which a WebSocket is considered to be lost. + /// + public readonly int WebSocketLostTimeout = 60; + + /// + /// The timer factor; controls the frequency of the timer. + /// + public readonly double TimerFactor = 0.2; + + /// + /// The ForceKeepAlive factor; controls when a ForceKeepAlive is sent. + /// + public readonly double ForceKeepAliveFactor = 0.75; + /// /// The _session manager /// @@ -31,6 +51,15 @@ namespace Emby.Server.Implementations.Session private readonly IHttpServer _httpServer; + /// + /// The KeepAlive timer. + /// + private Timer _keepAliveTimer; + + /// + /// The WebSocket watchlist. + /// + private readonly ConcurrentDictionary _webSockets = new ConcurrentDictionary(); /// /// Initializes a new instance of the class. @@ -55,6 +84,7 @@ namespace Emby.Server.Implementations.Session if (session != null) { EnsureController(session, e.Argument); + KeepAliveWebSocket(e.Argument); } else { @@ -82,6 +112,7 @@ namespace Emby.Server.Implementations.Session public void Dispose() { _httpServer.WebSocketConnected -= _serverManager_WebSocketConnected; + StopKeepAliveTimer(); } /// @@ -99,5 +130,130 @@ namespace Emby.Server.Implementations.Session var controller = (WebSocketController)controllerInfo.Item1; controller.AddWebSocket(connection); } + + /// + /// Called when a WebSocket is closed. + /// + /// The WebSocket. + /// The event arguments. + private void _webSocket_Closed(object sender, EventArgs e) + { + var webSocket = (IWebSocketConnection) sender; + webSocket.Closed -= _webSocket_Closed; + _webSockets.TryRemove(webSocket, out _); + } + + /// + /// Adds a WebSocket to the KeepAlive watchlist. + /// + /// The WebSocket to monitor. + private async void KeepAliveWebSocket(IWebSocketConnection webSocket) + { + _webSockets.TryAdd(webSocket, 0); + webSocket.Closed += _webSocket_Closed; + webSocket.LastKeepAliveDate = DateTime.UtcNow; + + // Notify WebSocket about timeout + try + { + await SendForceKeepAlive(webSocket); + } + catch (WebSocketException exception) + { + _logger.LogDebug(exception, "Error sending ForceKeepAlive message to WebSocket."); + } + + StartKeepAliveTimer(); + } + + /// + /// Starts the KeepAlive timer. + /// + private void StartKeepAliveTimer() + { + if (_keepAliveTimer == null) + { + _keepAliveTimer = new Timer( + KeepAliveSockets, + null, + TimeSpan.FromSeconds(WebSocketLostTimeout * TimerFactor), + TimeSpan.FromSeconds(WebSocketLostTimeout * TimerFactor) + ); + } + } + + /// + /// Stops the KeepAlive timer. + /// + private void StopKeepAliveTimer() + { + if (_keepAliveTimer != null) + { + _keepAliveTimer.Dispose(); + _keepAliveTimer = null; + } + + foreach (var pair in _webSockets) + { + pair.Key.Closed -= _webSocket_Closed; + } + } + + /// + /// Checks status of KeepAlive of WebSockets. + /// + /// The state. + private async void KeepAliveSockets(object state) + { + var inactive = _webSockets.Keys.Where(i => + { + var elapsed = (DateTime.UtcNow - i.LastKeepAliveDate).TotalSeconds; + return (elapsed > WebSocketLostTimeout * ForceKeepAliveFactor) && (elapsed < WebSocketLostTimeout); + }); + var lost = _webSockets.Keys.Where(i => (DateTime.UtcNow - i.LastKeepAliveDate).TotalSeconds >= WebSocketLostTimeout); + + if (inactive.Any()) + { + _logger.LogDebug("Sending ForceKeepAlive message to {0} WebSockets.", inactive.Count()); + } + + foreach (var webSocket in inactive) + { + try + { + await SendForceKeepAlive(webSocket); + } + catch (WebSocketException exception) + { + _logger.LogDebug(exception, "Error sending ForceKeepAlive message to WebSocket."); + lost.Append(webSocket); + } + } + + if (lost.Any()) + { + // TODO: handle lost webSockets + _logger.LogDebug("Lost {0} WebSockets.", lost.Count()); + } + + if (!_webSockets.Any()) + { + StopKeepAliveTimer(); + } + } + + /// + /// Sends a ForceKeepAlive message to a WebSocket. + /// + /// The WebSocket. + /// Task. + private Task SendForceKeepAlive(IWebSocketConnection webSocket) + { + return webSocket.SendAsync(new WebSocketMessage + { + MessageType = "ForceKeepAlive", + Data = WebSocketLostTimeout + }, CancellationToken.None); + } } } diff --git a/MediaBrowser.Controller/Net/IWebSocketConnection.cs b/MediaBrowser.Controller/Net/IWebSocketConnection.cs index 31eb7ccb7..fb766ab57 100644 --- a/MediaBrowser.Controller/Net/IWebSocketConnection.cs +++ b/MediaBrowser.Controller/Net/IWebSocketConnection.cs @@ -26,6 +26,12 @@ namespace MediaBrowser.Controller.Net /// The last activity date. DateTime LastActivityDate { get; } + /// + /// Gets or sets the date of last Keeplive received. + /// + /// The date of last Keeplive received. + public DateTime LastKeepAliveDate { get; set; } + /// /// Gets or sets the URL. /// -- cgit v1.2.3 From 083d3272d09395e2b7d73d886377017573e63686 Mon Sep 17 00:00:00 2001 From: gion Date: Tue, 21 Apr 2020 23:37:37 +0200 Subject: Refactor and other minor changes --- .../HttpServer/WebSocketConnection.cs | 5 +- .../Session/SessionWebSocketListener.cs | 42 +- .../Syncplay/SyncplayController.cs | 485 +++++++++++++-------- .../Syncplay/SyncplayManager.cs | 50 +-- MediaBrowser.Api/Syncplay/SyncplayService.cs | 52 ++- MediaBrowser.Api/Syncplay/TimeSyncService.cs | 1 - MediaBrowser.Controller/Syncplay/GroupInfo.cs | 24 +- .../Syncplay/ISyncplayManager.cs | 4 +- MediaBrowser.Model/Syncplay/GroupInfoModel.cs | 38 -- MediaBrowser.Model/Syncplay/GroupInfoView.cs | 38 ++ MediaBrowser.Model/Syncplay/PlaybackRequestType.cs | 2 +- 11 files changed, 441 insertions(+), 300 deletions(-) delete mode 100644 MediaBrowser.Model/Syncplay/GroupInfoModel.cs create mode 100644 MediaBrowser.Model/Syncplay/GroupInfoView.cs (limited to 'Emby.Server.Implementations/Session') diff --git a/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs b/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs index 171047e65..c819c163a 100644 --- a/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs +++ b/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs @@ -176,10 +176,9 @@ namespace Emby.Server.Implementations.HttpServer { SendKeepAliveResponse(); } - - if (OnReceive != null) + else { - OnReceive(info); + OnReceive?.Invoke(info); } } catch (Exception ex) diff --git a/Emby.Server.Implementations/Session/SessionWebSocketListener.cs b/Emby.Server.Implementations/Session/SessionWebSocketListener.cs index d8e02ef39..b0c6d0aa0 100644 --- a/Emby.Server.Implementations/Session/SessionWebSocketListener.cs +++ b/Emby.Server.Implementations/Session/SessionWebSocketListener.cs @@ -1,3 +1,4 @@ +using System.Collections.Generic; using System; using System.Collections.Concurrent; using System.Linq; @@ -25,7 +26,7 @@ namespace Emby.Server.Implementations.Session public readonly int WebSocketLostTimeout = 60; /// - /// The timer factor; controls the frequency of the timer. + /// The keep-alive timer factor; controls how often the timer will check on the status of the WebSockets. /// public readonly double TimerFactor = 0.2; @@ -136,11 +137,10 @@ namespace Emby.Server.Implementations.Session /// /// The WebSocket. /// The event arguments. - private void _webSocket_Closed(object sender, EventArgs e) + private void OnWebSocketClosed(object sender, EventArgs e) { var webSocket = (IWebSocketConnection) sender; - webSocket.Closed -= _webSocket_Closed; - _webSockets.TryRemove(webSocket, out _); + RemoveWebSocket(webSocket); } /// @@ -149,8 +149,12 @@ namespace Emby.Server.Implementations.Session /// The WebSocket to monitor. private async void KeepAliveWebSocket(IWebSocketConnection webSocket) { - _webSockets.TryAdd(webSocket, 0); - webSocket.Closed += _webSocket_Closed; + if (!_webSockets.TryAdd(webSocket, 0)) + { + _logger.LogWarning("Multiple attempts to keep alive single WebSocket {0}", webSocket); + return; + } + webSocket.Closed += OnWebSocketClosed; webSocket.LastKeepAliveDate = DateTime.UtcNow; // Notify WebSocket about timeout @@ -160,12 +164,22 @@ namespace Emby.Server.Implementations.Session } catch (WebSocketException exception) { - _logger.LogDebug(exception, "Error sending ForceKeepAlive message to WebSocket."); + _logger.LogWarning(exception, "Error sending ForceKeepAlive message to WebSocket."); } StartKeepAliveTimer(); } + /// + /// Removes a WebSocket from the KeepAlive watchlist. + /// + /// The WebSocket to remove. + private void RemoveWebSocket(IWebSocketConnection webSocket) + { + webSocket.Closed -= OnWebSocketClosed; + _webSockets.TryRemove(webSocket, out _); + } + /// /// Starts the KeepAlive timer. /// @@ -195,7 +209,7 @@ namespace Emby.Server.Implementations.Session foreach (var pair in _webSockets) { - pair.Key.Closed -= _webSocket_Closed; + pair.Key.Closed -= OnWebSocketClosed; } } @@ -214,7 +228,7 @@ namespace Emby.Server.Implementations.Session if (inactive.Any()) { - _logger.LogDebug("Sending ForceKeepAlive message to {0} WebSockets.", inactive.Count()); + _logger.LogDebug("Sending ForceKeepAlive message to {0} inactive WebSockets.", inactive.Count()); } foreach (var webSocket in inactive) @@ -225,15 +239,19 @@ namespace Emby.Server.Implementations.Session } catch (WebSocketException exception) { - _logger.LogDebug(exception, "Error sending ForceKeepAlive message to WebSocket."); + _logger.LogInformation(exception, "Error sending ForceKeepAlive message to WebSocket."); lost.Append(webSocket); } } if (lost.Any()) { - // TODO: handle lost webSockets - _logger.LogDebug("Lost {0} WebSockets.", lost.Count()); + _logger.LogInformation("Lost {0} WebSockets.", lost.Count()); + foreach (var webSocket in lost) + { + // TODO: handle session relative to the lost webSocket + RemoveWebSocket(webSocket); + } } if (!_webSockets.Any()) diff --git a/Emby.Server.Implementations/Syncplay/SyncplayController.cs b/Emby.Server.Implementations/Syncplay/SyncplayController.cs index 83b477944..02cf08cd7 100644 --- a/Emby.Server.Implementations/Syncplay/SyncplayController.cs +++ b/Emby.Server.Implementations/Syncplay/SyncplayController.cs @@ -16,11 +16,26 @@ namespace Emby.Server.Implementations.Syncplay /// public class SyncplayController : ISyncplayController, IDisposable { + /// + /// Used to filter the sessions of a group. + /// private enum BroadcastType { + /// + /// All sessions will receive the message. + /// AllGroup = 0, - SingleSession = 1, - AllExceptSession = 2, + /// + /// Only the specified session will receive the message. + /// + CurrentSession = 1, + /// + /// All sessions, except the current one, will receive the message. + /// + AllExceptCurrentSession = 2, + /// + /// Only sessions that are not buffering will receive the message. + /// AllReady = 3 } @@ -95,40 +110,46 @@ namespace Emby.Server.Implementations.Syncplay } } + /// + /// Filters sessions of this group. + /// + /// The current session. + /// The filtering type. + /// The array of sessions matching the filter. private SessionInfo[] FilterSessions(SessionInfo from, BroadcastType type) { - if (type == BroadcastType.SingleSession) - { - return new SessionInfo[] { from }; - } - else if (type == BroadcastType.AllGroup) - { - return _group.Partecipants.Values.Select( - session => session.Session - ).ToArray(); - } - else if (type == BroadcastType.AllExceptSession) - { - return _group.Partecipants.Values.Select( - session => session.Session - ).Where( - session => !session.Id.Equals(from.Id) - ).ToArray(); - } - else if (type == BroadcastType.AllReady) + switch (type) { - return _group.Partecipants.Values.Where( - session => !session.IsBuffering - ).Select( - session => session.Session - ).ToArray(); - } - else - { - return new SessionInfo[] {}; + case BroadcastType.CurrentSession: + return new SessionInfo[] { from }; + case BroadcastType.AllGroup: + return _group.Participants.Values.Select( + session => session.Session + ).ToArray(); + case BroadcastType.AllExceptCurrentSession: + return _group.Participants.Values.Select( + session => session.Session + ).Where( + session => !session.Id.Equals(from.Id) + ).ToArray(); + case BroadcastType.AllReady: + return _group.Participants.Values.Where( + session => !session.IsBuffering + ).Select( + session => session.Session + ).ToArray(); + default: + return new SessionInfo[] { }; } } + /// + /// Sends a GroupUpdate message to the interested sessions. + /// + /// The current session. + /// The filtering type. + /// The message to send. + /// The task. private Task SendGroupUpdate(SessionInfo from, BroadcastType type, GroupUpdate message) { IEnumerable GetTasks() @@ -143,6 +164,13 @@ namespace Emby.Server.Implementations.Syncplay return Task.WhenAll(GetTasks()); } + /// + /// Sends a playback command to the interested sessions. + /// + /// The current session. + /// The filtering type. + /// The message to send. + /// The task. private Task SendCommand(SessionInfo from, BroadcastType type, SendCommand message) { IEnumerable GetTasks() @@ -157,31 +185,44 @@ namespace Emby.Server.Implementations.Syncplay return Task.WhenAll(GetTasks()); } + /// + /// Builds a new playback command with some default values. + /// + /// The command type. + /// The SendCommand. private SendCommand NewSyncplayCommand(SendCommandType type) { - var command = new SendCommand(); - command.GroupId = _group.GroupId.ToString(); - command.Command = type; - command.PositionTicks = _group.PositionTicks; - command.When = _group.LastActivity.ToUniversalTime().ToString("o"); - command.EmittedAt = DateTime.UtcNow.ToUniversalTime().ToString("o"); - return command; + return new SendCommand() + { + GroupId = _group.GroupId.ToString(), + Command = type, + PositionTicks = _group.PositionTicks, + When = _group.LastActivity.ToUniversalTime().ToString("o"), + EmittedAt = DateTime.UtcNow.ToUniversalTime().ToString("o") + }; } + /// + /// Builds a new group update message. + /// + /// The update type. + /// The data to send. + /// The GroupUpdate. private GroupUpdate NewSyncplayGroupUpdate(GroupUpdateType type, T data) { - var command = new GroupUpdate(); - command.GroupId = _group.GroupId.ToString(); - command.Type = type; - command.Data = data; - return command; + return new GroupUpdate() + { + GroupId = _group.GroupId.ToString(), + Type = type, + Data = data + }; } /// public void InitGroup(SessionInfo session) { _group.AddSession(session); - _syncplayManager.MapSessionToGroup(session, this); + _syncplayManager.AddSessionToGroup(session, this); _group.PlayingItem = session.FullNowPlayingItem; _group.IsPaused = true; @@ -189,37 +230,35 @@ namespace Emby.Server.Implementations.Syncplay _group.LastActivity = DateTime.UtcNow; var updateSession = NewSyncplayGroupUpdate(GroupUpdateType.GroupJoined, DateTime.UtcNow.ToUniversalTime().ToString("o")); - SendGroupUpdate(session, BroadcastType.SingleSession, updateSession); + SendGroupUpdate(session, BroadcastType.CurrentSession, updateSession); var pauseCommand = NewSyncplayCommand(SendCommandType.Pause); - SendCommand(session, BroadcastType.SingleSession, pauseCommand); + SendCommand(session, BroadcastType.CurrentSession, pauseCommand); } /// public void SessionJoin(SessionInfo session, JoinGroupRequest request) { - if (session.NowPlayingItem != null && - session.NowPlayingItem.Id.Equals(_group.PlayingItem.Id) && - request.PlayingItemId.Equals(_group.PlayingItem.Id)) + if (session.NowPlayingItem?.Id == _group.PlayingItem.Id && request.PlayingItemId == _group.PlayingItem.Id) { _group.AddSession(session); - _syncplayManager.MapSessionToGroup(session, this); + _syncplayManager.AddSessionToGroup(session, this); var updateSession = NewSyncplayGroupUpdate(GroupUpdateType.GroupJoined, DateTime.UtcNow.ToUniversalTime().ToString("o")); - SendGroupUpdate(session, BroadcastType.SingleSession, updateSession); + SendGroupUpdate(session, BroadcastType.CurrentSession, updateSession); var updateOthers = NewSyncplayGroupUpdate(GroupUpdateType.UserJoined, session.UserName); - SendGroupUpdate(session, BroadcastType.AllExceptSession, updateOthers); + SendGroupUpdate(session, BroadcastType.AllExceptCurrentSession, updateOthers); // Client join and play, syncing will happen client side if (!_group.IsPaused) { var playCommand = NewSyncplayCommand(SendCommandType.Play); - SendCommand(session, BroadcastType.SingleSession, playCommand); + SendCommand(session, BroadcastType.CurrentSession, playCommand); } else { var pauseCommand = NewSyncplayCommand(SendCommandType.Pause); - SendCommand(session, BroadcastType.SingleSession, pauseCommand); + SendCommand(session, BroadcastType.CurrentSession, pauseCommand); } } else @@ -228,7 +267,7 @@ namespace Emby.Server.Implementations.Syncplay playRequest.ItemIds = new Guid[] { _group.PlayingItem.Id }; playRequest.StartPositionTicks = _group.PositionTicks; var update = NewSyncplayGroupUpdate(GroupUpdateType.PrepareSession, playRequest); - SendGroupUpdate(session, BroadcastType.SingleSession, update); + SendGroupUpdate(session, BroadcastType.CurrentSession, update); } } @@ -236,182 +275,250 @@ namespace Emby.Server.Implementations.Syncplay public void SessionLeave(SessionInfo session) { _group.RemoveSession(session); - _syncplayManager.UnmapSessionFromGroup(session, this); + _syncplayManager.RemoveSessionFromGroup(session, this); var updateSession = NewSyncplayGroupUpdate(GroupUpdateType.GroupLeft, _group.PositionTicks); - SendGroupUpdate(session, BroadcastType.SingleSession, updateSession); + SendGroupUpdate(session, BroadcastType.CurrentSession, updateSession); var updateOthers = NewSyncplayGroupUpdate(GroupUpdateType.UserLeft, session.UserName); - SendGroupUpdate(session, BroadcastType.AllExceptSession, updateOthers); + SendGroupUpdate(session, BroadcastType.AllExceptCurrentSession, updateOthers); } /// public void HandleRequest(SessionInfo session, PlaybackRequest request) { - if (request.Type.Equals(PlaybackRequestType.Play)) + // The server's job is to mantain a consistent state to which clients refer to, + // as also to notify clients of state changes. + // The actual syncing of media playback happens client side. + // Clients are aware of the server's time and use it to sync. + switch (request.Type) { - if (_group.IsPaused) - { - var delay = _group.GetHighestPing() * 2; - delay = delay < _group.DefaulPing ? _group.DefaulPing : delay; - - _group.IsPaused = false; - _group.LastActivity = DateTime.UtcNow.AddMilliseconds( - delay - ); + case PlaybackRequestType.Play: + HandlePlayRequest(session, request); + break; + case PlaybackRequestType.Pause: + HandlePauseRequest(session, request); + break; + case PlaybackRequestType.Seek: + HandleSeekRequest(session, request); + break; + case PlaybackRequestType.Buffering: + HandleBufferingRequest(session, request); + break; + case PlaybackRequestType.BufferingDone: + HandleBufferingDoneRequest(session, request); + break; + case PlaybackRequestType.UpdatePing: + HandlePingUpdateRequest(session, request); + break; + } + } - var command = NewSyncplayCommand(SendCommandType.Play); - SendCommand(session, BroadcastType.AllGroup, command); - } - else - { - // Client got lost - var command = NewSyncplayCommand(SendCommandType.Play); - SendCommand(session, BroadcastType.SingleSession, command); - } + /// + /// Handles a play action requested by a session. + /// + /// The session. + /// The play action. + private void HandlePlayRequest(SessionInfo session, PlaybackRequest request) + { + if (_group.IsPaused) + { + // Pick a suitable time that accounts for latency + var delay = _group.GetHighestPing() * 2; + delay = delay < _group.DefaulPing ? _group.DefaulPing : delay; + + // Unpause group and set starting point in future + // Clients will start playback at LastActivity (datetime) from PositionTicks (playback position) + // The added delay does not guarantee, of course, that the command will be received in time + // Playback synchronization will mainly happen client side + _group.IsPaused = false; + _group.LastActivity = DateTime.UtcNow.AddMilliseconds( + delay + ); + + var command = NewSyncplayCommand(SendCommandType.Play); + SendCommand(session, BroadcastType.AllGroup, command); } - else if (request.Type.Equals(PlaybackRequestType.Pause)) + else { - if (!_group.IsPaused) - { - _group.IsPaused = true; - var currentTime = DateTime.UtcNow; - var elapsedTime = currentTime - _group.LastActivity; - _group.LastActivity = currentTime; - _group.PositionTicks += elapsedTime.Ticks > 0 ? elapsedTime.Ticks : 0; + // Client got lost, sending current state + var command = NewSyncplayCommand(SendCommandType.Play); + SendCommand(session, BroadcastType.CurrentSession, command); + } + } - var command = NewSyncplayCommand(SendCommandType.Pause); - SendCommand(session, BroadcastType.AllGroup, command); - } - else - { - var command = NewSyncplayCommand(SendCommandType.Pause); - SendCommand(session, BroadcastType.SingleSession, command); - } + /// + /// Handles a pause action requested by a session. + /// + /// The session. + /// The pause action. + private void HandlePauseRequest(SessionInfo session, PlaybackRequest request) + { + if (!_group.IsPaused) + { + // Pause group and compute the media playback position + _group.IsPaused = true; + var currentTime = DateTime.UtcNow; + var elapsedTime = currentTime - _group.LastActivity; + _group.LastActivity = currentTime; + // Seek only if playback actually started + // (a pause request may be issued during the delay added to account for latency) + _group.PositionTicks += elapsedTime.Ticks > 0 ? elapsedTime.Ticks : 0; + + var command = NewSyncplayCommand(SendCommandType.Pause); + SendCommand(session, BroadcastType.AllGroup, command); } - else if (request.Type.Equals(PlaybackRequestType.Seek)) + else { - // Sanitize PositionTicks - var ticks = request.PositionTicks ??= 0; - ticks = ticks >= 0 ? ticks : 0; - if (_group.PlayingItem.RunTimeTicks != null) - { - var runTimeTicks = _group.PlayingItem.RunTimeTicks ??= 0; - ticks = ticks > runTimeTicks ? runTimeTicks : ticks; - } + // Client got lost, sending current state + var command = NewSyncplayCommand(SendCommandType.Pause); + SendCommand(session, BroadcastType.CurrentSession, command); + } + } + + /// + /// Handles a seek action requested by a session. + /// + /// The session. + /// The seek action. + private void HandleSeekRequest(SessionInfo session, PlaybackRequest request) + { + // Sanitize PositionTicks + var ticks = request.PositionTicks ??= 0; + ticks = ticks >= 0 ? ticks : 0; + if (_group.PlayingItem.RunTimeTicks != null) + { + var runTimeTicks = _group.PlayingItem.RunTimeTicks ??= 0; + ticks = ticks > runTimeTicks ? runTimeTicks : ticks; + } + + // Pause and seek + _group.IsPaused = true; + _group.PositionTicks = ticks; + _group.LastActivity = DateTime.UtcNow; + + var command = NewSyncplayCommand(SendCommandType.Seek); + SendCommand(session, BroadcastType.AllGroup, command); + } + /// + /// Handles a buffering action requested by a session. + /// + /// The session. + /// The buffering action. + private void HandleBufferingRequest(SessionInfo session, PlaybackRequest request) + { + if (!_group.IsPaused) + { + // Pause group and compute the media playback position _group.IsPaused = true; - _group.PositionTicks = ticks; - _group.LastActivity = DateTime.UtcNow; + var currentTime = DateTime.UtcNow; + var elapsedTime = currentTime - _group.LastActivity; + _group.LastActivity = currentTime; + _group.PositionTicks += elapsedTime.Ticks > 0 ? elapsedTime.Ticks : 0; - var command = NewSyncplayCommand(SendCommandType.Seek); - SendCommand(session, BroadcastType.AllGroup, command); + _group.SetBuffering(session, true); + + // Send pause command to all non-buffering sessions + var command = NewSyncplayCommand(SendCommandType.Pause); + SendCommand(session, BroadcastType.AllReady, command); + + var updateOthers = NewSyncplayGroupUpdate(GroupUpdateType.GroupWait, session.UserName); + SendGroupUpdate(session, BroadcastType.AllExceptCurrentSession, updateOthers); } - // TODO: client does not implement this yet - else if (request.Type.Equals(PlaybackRequestType.Buffering)) + else { - if (!_group.IsPaused) - { - _group.IsPaused = true; - var currentTime = DateTime.UtcNow; - var elapsedTime = currentTime - _group.LastActivity; - _group.LastActivity = currentTime; - _group.PositionTicks += elapsedTime.Ticks > 0 ? elapsedTime.Ticks : 0; + // Client got lost, sending current state + var command = NewSyncplayCommand(SendCommandType.Pause); + SendCommand(session, BroadcastType.CurrentSession, command); + } + } - _group.SetBuffering(session, true); + /// + /// Handles a buffering-done action requested by a session. + /// + /// The session. + /// The buffering-done action. + private void HandleBufferingDoneRequest(SessionInfo session, PlaybackRequest request) + { + if (_group.IsPaused) + { + _group.SetBuffering(session, false); - // Send pause command to all non-buffering sessions - var command = NewSyncplayCommand(SendCommandType.Pause); - SendCommand(session, BroadcastType.AllReady, command); + var when = request.When ??= DateTime.UtcNow; + var currentTime = DateTime.UtcNow; + var elapsedTime = currentTime - when; + var clientPosition = TimeSpan.FromTicks(request.PositionTicks ??= 0) + elapsedTime; + var delay = _group.PositionTicks - clientPosition.Ticks; - var updateOthers = NewSyncplayGroupUpdate(GroupUpdateType.GroupWait, session.UserName); - SendGroupUpdate(session, BroadcastType.AllExceptSession, updateOthers); - } - else + if (_group.IsBuffering()) { + // Others are buffering, tell this client to pause when ready var command = NewSyncplayCommand(SendCommandType.Pause); - SendCommand(session, BroadcastType.SingleSession, command); + command.When = currentTime.AddMilliseconds( + delay + ).ToUniversalTime().ToString("o"); + SendCommand(session, BroadcastType.CurrentSession, command); } - } - // TODO: client does not implement this yet - else if (request.Type.Equals(PlaybackRequestType.BufferingComplete)) - { - if (_group.IsPaused) + else { - _group.SetBuffering(session, false); - - if (_group.IsBuffering()) { - // Others are buffering, tell this client to pause when ready - var when = request.When ??= DateTime.UtcNow; - var currentTime = DateTime.UtcNow; - var elapsedTime = currentTime - when; - var clientPosition = TimeSpan.FromTicks(request.PositionTicks ??= 0) + elapsedTime; - var delay = _group.PositionTicks - clientPosition.Ticks; - - var command = NewSyncplayCommand(SendCommandType.Pause); - command.When = currentTime.AddMilliseconds( + // Let other clients resume as soon as the buffering client catches up + _group.IsPaused = false; + + if (delay > _group.GetHighestPing() * 2) + { + // Client that was buffering is recovering, notifying others to resume + _group.LastActivity = currentTime.AddMilliseconds( delay - ).ToUniversalTime().ToString("o"); - SendCommand(session, BroadcastType.SingleSession, command); + ); + var command = NewSyncplayCommand(SendCommandType.Play); + SendCommand(session, BroadcastType.AllExceptCurrentSession, command); } else { - // Let other clients resume as soon as the buffering client catches up - var when = request.When ??= DateTime.UtcNow; - var currentTime = DateTime.UtcNow; - var elapsedTime = currentTime - when; - var clientPosition = TimeSpan.FromTicks(request.PositionTicks ??= 0) + elapsedTime; - var delay = _group.PositionTicks - clientPosition.Ticks; - - _group.IsPaused = false; - - if (delay > _group.GetHighestPing() * 2) - { - // Client that was buffering is recovering, notifying others to resume - _group.LastActivity = currentTime.AddMilliseconds( - delay - ); - var command = NewSyncplayCommand(SendCommandType.Play); - SendCommand(session, BroadcastType.AllExceptSession, command); - } - else - { - // Client, that was buffering, resumed playback but did not update others in time - delay = _group.GetHighestPing() * 2; - delay = delay < _group.DefaulPing ? _group.DefaulPing : delay; - - _group.LastActivity = currentTime.AddMilliseconds( - delay - ); - - var command = NewSyncplayCommand(SendCommandType.Play); - SendCommand(session, BroadcastType.AllGroup, command); - } - } - } - else - { - // Make sure client has latest group state - var command = NewSyncplayCommand(SendCommandType.Play); - SendCommand(session, BroadcastType.SingleSession, command); + // Client, that was buffering, resumed playback but did not update others in time + delay = _group.GetHighestPing() * 2; + delay = delay < _group.DefaulPing ? _group.DefaulPing : delay; + + _group.LastActivity = currentTime.AddMilliseconds( + delay + ); + + var command = NewSyncplayCommand(SendCommandType.Play); + SendCommand(session, BroadcastType.AllGroup, command); + } } } - else if (request.Type.Equals(PlaybackRequestType.UpdatePing)) + else { - _group.UpdatePing(session, request.Ping ??= _group.DefaulPing); + // Group was not waiting, make sure client has latest state + var command = NewSyncplayCommand(SendCommandType.Play); + SendCommand(session, BroadcastType.CurrentSession, command); } } + /// + /// Updates ping of a session. + /// + /// The session. + /// The update. + private void HandlePingUpdateRequest(SessionInfo session, PlaybackRequest request) + { + // Collected pings are used to account for network latency when unpausing playback + _group.UpdatePing(session, request.Ping ??= _group.DefaulPing); + } + /// public GroupInfoView GetInfo() { - var info = new GroupInfoView(); - info.GroupId = GetGroupId().ToString(); - info.PlayingItemName = _group.PlayingItem.Name; - info.PlayingItemId = _group.PlayingItem.Id.ToString(); - info.PositionTicks = _group.PositionTicks; - info.Partecipants = _group.Partecipants.Values.Select(session => session.Session.UserName).ToArray(); - return info; + return new GroupInfoView() + { + GroupId = GetGroupId().ToString(), + PlayingItemName = _group.PlayingItem.Name, + PlayingItemId = _group.PlayingItem.Id.ToString(), + PositionTicks = _group.PositionTicks, + Participants = _group.Participants.Values.Select(session => session.Session.UserName).ToArray() + }; } } } diff --git a/Emby.Server.Implementations/Syncplay/SyncplayManager.cs b/Emby.Server.Implementations/Syncplay/SyncplayManager.cs index 60d70e5fd..e7df8925e 100644 --- a/Emby.Server.Implementations/Syncplay/SyncplayManager.cs +++ b/Emby.Server.Implementations/Syncplay/SyncplayManager.cs @@ -49,7 +49,7 @@ namespace Emby.Server.Implementations.Syncplay /// The groups. /// private readonly ConcurrentDictionary _groups = - new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); + new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); private bool _disposed = false; @@ -64,8 +64,8 @@ namespace Emby.Server.Implementations.Syncplay _sessionManager = sessionManager; _libraryManager = libraryManager; - _sessionManager.SessionEnded += _sessionManager_SessionEnded; - _sessionManager.PlaybackStopped += _sessionManager_PlaybackStopped; + _sessionManager.SessionEnded += OnSessionManagerSessionEnded; + _sessionManager.PlaybackStopped += OnSessionManagerPlaybackStopped; } /// @@ -92,8 +92,8 @@ namespace Emby.Server.Implementations.Syncplay return; } - _sessionManager.SessionEnded -= _sessionManager_SessionEnded; - _sessionManager.PlaybackStopped -= _sessionManager_PlaybackStopped; + _sessionManager.SessionEnded -= OnSessionManagerSessionEnded; + _sessionManager.PlaybackStopped -= OnSessionManagerPlaybackStopped; _disposed = true; } @@ -106,14 +106,14 @@ namespace Emby.Server.Implementations.Syncplay } } - void _sessionManager_SessionEnded(object sender, SessionEventArgs e) + private void OnSessionManagerSessionEnded(object sender, SessionEventArgs e) { var session = e.SessionInfo; if (!IsSessionInGroup(session)) return; LeaveGroup(session); } - void _sessionManager_PlaybackStopped(object sender, PlaybackStopEventArgs e) + private void OnSessionManagerPlaybackStopped(object sender, PlaybackStopEventArgs e) { var session = e.Session; if (!IsSessionInGroup(session)) return; @@ -130,13 +130,13 @@ namespace Emby.Server.Implementations.Syncplay var item = _libraryManager.GetItemById(itemId); var hasParentalRatingAccess = user.Policy.MaxParentalRating.HasValue ? item.InheritedParentalRatingValue <= user.Policy.MaxParentalRating : true; - if (!user.Policy.EnableAllFolders) + if (!user.Policy.EnableAllFolders && hasParentalRatingAccess) { var collections = _libraryManager.GetCollectionFolders(item).Select( folder => folder.Id.ToString("N", CultureInfo.InvariantCulture) ); var intersect = collections.Intersect(user.Policy.EnabledFolders); - return intersect.Count() > 0 && hasParentalRatingAccess; + return intersect.Count() > 0; } else { @@ -165,7 +165,7 @@ namespace Emby.Server.Implementations.Syncplay if (user.Policy.SyncplayAccess != SyncplayAccess.CreateAndJoinGroups) { - // TODO: shall an error message be sent back to the client? + // TODO: report the error to the client throw new ArgumentException("User does not have permission to create groups"); } @@ -187,22 +187,16 @@ namespace Emby.Server.Implementations.Syncplay if (user.Policy.SyncplayAccess == SyncplayAccess.None) { - // TODO: shall an error message be sent back to the client? + // TODO: report the error to the client throw new ArgumentException("User does not have access to syncplay"); } - if (IsSessionInGroup(session)) - { - if (GetSessionGroup(session).Equals(groupId)) return; - LeaveGroup(session); - } - ISyncplayController group; _groups.TryGetValue(groupId, out group); if (group == null) { - _logger.LogError("Syncplaymanager JoinGroup: " + groupId + " does not exist."); + _logger.LogWarning("Syncplaymanager JoinGroup: {0} does not exist.", groupId); var update = new GroupUpdate(); update.Type = GroupUpdateType.NotInGroup; @@ -215,20 +209,26 @@ namespace Emby.Server.Implementations.Syncplay throw new ArgumentException("User does not have access to playing item"); } + if (IsSessionInGroup(session)) + { + if (GetSessionGroup(session).Equals(groupId)) return; + LeaveGroup(session); + } + group.SessionJoin(session, request); } /// public void LeaveGroup(SessionInfo session) { - // TODO: what happens to users that are in a group and get their permissions revoked? + // TODO: determine what happens to users that are in a group and get their permissions revoked ISyncplayController group; _sessionToGroupMap.TryGetValue(session.Id, out group); if (group == null) { - _logger.LogWarning("Syncplaymanager HandleRequest: " + session.Id + " not in group."); + _logger.LogWarning("Syncplaymanager LeaveGroup: {0} does not belong to any group.", session.Id); var update = new GroupUpdate(); update.Type = GroupUpdateType.NotInGroup; @@ -257,9 +257,7 @@ namespace Emby.Server.Implementations.Syncplay if (session.NowPlayingItem != null) { return _groups.Values.Where( - group => HasAccessToItem(user, group.GetPlayingItemId()) - ).Where( - group => group.GetPlayingItemId().Equals(session.FullNowPlayingItem.Id) + group => group.GetPlayingItemId().Equals(session.FullNowPlayingItem.Id) && HasAccessToItem(user, group.GetPlayingItemId()) ).Select( group => group.GetInfo() ).ToList(); @@ -291,7 +289,7 @@ namespace Emby.Server.Implementations.Syncplay if (group == null) { - _logger.LogWarning("Syncplaymanager HandleRequest: " + session.Id + " not in group."); + _logger.LogWarning("Syncplaymanager HandleRequest: {0} not in a group.", session.Id); var update = new GroupUpdate(); update.Type = GroupUpdateType.NotInGroup; @@ -302,7 +300,7 @@ namespace Emby.Server.Implementations.Syncplay } /// - public void MapSessionToGroup(SessionInfo session, ISyncplayController group) + public void AddSessionToGroup(SessionInfo session, ISyncplayController group) { if (IsSessionInGroup(session)) { @@ -312,7 +310,7 @@ namespace Emby.Server.Implementations.Syncplay } /// - public void UnmapSessionFromGroup(SessionInfo session, ISyncplayController group) + public void RemoveSessionFromGroup(SessionInfo session, ISyncplayController group) { if (!IsSessionInGroup(session)) { diff --git a/MediaBrowser.Api/Syncplay/SyncplayService.cs b/MediaBrowser.Api/Syncplay/SyncplayService.cs index af220ed81..2eaf9ce83 100644 --- a/MediaBrowser.Api/Syncplay/SyncplayService.cs +++ b/MediaBrowser.Api/Syncplay/SyncplayService.cs @@ -90,12 +90,20 @@ namespace MediaBrowser.Api.Syncplay [ApiMember(Name = "SessionId", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] public string SessionId { get; set; } + /// + /// Gets or sets the date used to pin PositionTicks in time. + /// + /// The date related to PositionTicks. [ApiMember(Name = "When", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")] public string When { get; set; } [ApiMember(Name = "PositionTicks", IsRequired = true, DataType = "long", ParameterType = "query", Verb = "POST")] public long PositionTicks { get; set; } + /// + /// Gets or sets whether this is a buffering or a buffering-done request. + /// + /// true if buffering is complete; false otherwise. [ApiMember(Name = "Resume", IsRequired = true, DataType = "bool", ParameterType = "query", Verb = "POST")] public bool Resume { get; set; } } @@ -162,8 +170,10 @@ namespace MediaBrowser.Api.Syncplay public void Post(SyncplayJoinGroup request) { var currentSession = GetSession(_sessionContext); - var joinRequest = new JoinGroupRequest(); - joinRequest.GroupId = Guid.Parse(request.GroupId); + var joinRequest = new JoinGroupRequest() + { + GroupId = Guid.Parse(request.GroupId) + }; try { joinRequest.PlayingItemId = Guid.Parse(request.PlayingItemId); @@ -207,8 +217,10 @@ namespace MediaBrowser.Api.Syncplay public void Post(SyncplayPlayRequest request) { var currentSession = GetSession(_sessionContext); - var syncplayRequest = new PlaybackRequest(); - syncplayRequest.Type = PlaybackRequestType.Play; + var syncplayRequest = new PlaybackRequest() + { + Type = PlaybackRequestType.Play + }; _syncplayManager.HandleRequest(currentSession, syncplayRequest); } @@ -219,8 +231,10 @@ namespace MediaBrowser.Api.Syncplay public void Post(SyncplayPauseRequest request) { var currentSession = GetSession(_sessionContext); - var syncplayRequest = new PlaybackRequest(); - syncplayRequest.Type = PlaybackRequestType.Pause; + var syncplayRequest = new PlaybackRequest() + { + Type = PlaybackRequestType.Pause + }; _syncplayManager.HandleRequest(currentSession, syncplayRequest); } @@ -231,9 +245,11 @@ namespace MediaBrowser.Api.Syncplay public void Post(SyncplaySeekRequest request) { var currentSession = GetSession(_sessionContext); - var syncplayRequest = new PlaybackRequest(); - syncplayRequest.Type = PlaybackRequestType.Seek; - syncplayRequest.PositionTicks = request.PositionTicks; + var syncplayRequest = new PlaybackRequest() + { + Type = PlaybackRequestType.Seek, + PositionTicks = request.PositionTicks + }; _syncplayManager.HandleRequest(currentSession, syncplayRequest); } @@ -244,10 +260,12 @@ namespace MediaBrowser.Api.Syncplay public void Post(SyncplayBufferingRequest request) { var currentSession = GetSession(_sessionContext); - var syncplayRequest = new PlaybackRequest(); - syncplayRequest.Type = request.Resume ? PlaybackRequestType.BufferingComplete : PlaybackRequestType.Buffering; - syncplayRequest.When = DateTime.Parse(request.When); - syncplayRequest.PositionTicks = request.PositionTicks; + var syncplayRequest = new PlaybackRequest() + { + Type = request.Resume ? PlaybackRequestType.BufferingDone : PlaybackRequestType.Buffering, + When = DateTime.Parse(request.When), + PositionTicks = request.PositionTicks + }; _syncplayManager.HandleRequest(currentSession, syncplayRequest); } @@ -258,9 +276,11 @@ namespace MediaBrowser.Api.Syncplay public void Post(SyncplayUpdatePing request) { var currentSession = GetSession(_sessionContext); - var syncplayRequest = new PlaybackRequest(); - syncplayRequest.Type = PlaybackRequestType.UpdatePing; - syncplayRequest.Ping = Convert.ToInt64(request.Ping); + var syncplayRequest = new PlaybackRequest() + { + Type = PlaybackRequestType.UpdatePing, + Ping = Convert.ToInt64(request.Ping) + }; _syncplayManager.HandleRequest(currentSession, syncplayRequest); } } diff --git a/MediaBrowser.Api/Syncplay/TimeSyncService.cs b/MediaBrowser.Api/Syncplay/TimeSyncService.cs index a69e0e293..897413015 100644 --- a/MediaBrowser.Api/Syncplay/TimeSyncService.cs +++ b/MediaBrowser.Api/Syncplay/TimeSyncService.cs @@ -54,7 +54,6 @@ namespace MediaBrowser.Api.Syncplay var response = new UtcTimeResponse(); response.RequestReceptionTime = requestReceptionTime; - var currentSession = GetSession(_sessionContext); // Important to keep the following two lines at the end var responseTransmissionTime = DateTime.UtcNow.ToUniversalTime().ToString("o"); diff --git a/MediaBrowser.Controller/Syncplay/GroupInfo.cs b/MediaBrowser.Controller/Syncplay/GroupInfo.cs index 42e85ef86..8e886a2cb 100644 --- a/MediaBrowser.Controller/Syncplay/GroupInfo.cs +++ b/MediaBrowser.Controller/Syncplay/GroupInfo.cs @@ -46,11 +46,11 @@ namespace MediaBrowser.Controller.Syncplay public DateTime LastActivity { get; set; } /// - /// Gets the partecipants. + /// Gets the participants. /// - /// The partecipants. - public readonly ConcurrentDictionary Partecipants = - new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); + /// The participants, or members of the group. + public readonly ConcurrentDictionary Participants = + new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); /// /// Checks if a session is in this group. @@ -58,7 +58,7 @@ namespace MediaBrowser.Controller.Syncplay /// true if the session is in this group; false otherwise. public bool ContainsSession(string sessionId) { - return Partecipants.ContainsKey(sessionId); + return Participants.ContainsKey(sessionId); } /// @@ -72,7 +72,7 @@ namespace MediaBrowser.Controller.Syncplay member.Session = session; member.Ping = DefaulPing; member.IsBuffering = false; - Partecipants[session.Id.ToString()] = member; + Participants[session.Id.ToString()] = member; } /// @@ -84,7 +84,7 @@ namespace MediaBrowser.Controller.Syncplay { if (!ContainsSession(session.Id.ToString())) return; GroupMember member; - Partecipants.Remove(session.Id.ToString(), out member); + Participants.Remove(session.Id.ToString(), out member); } /// @@ -95,7 +95,7 @@ namespace MediaBrowser.Controller.Syncplay public void UpdatePing(SessionInfo session, long ping) { if (!ContainsSession(session.Id.ToString())) return; - Partecipants[session.Id.ToString()].Ping = ping; + Participants[session.Id.ToString()].Ping = ping; } /// @@ -105,7 +105,7 @@ namespace MediaBrowser.Controller.Syncplay public long GetHighestPing() { long max = Int64.MinValue; - foreach (var session in Partecipants.Values) + foreach (var session in Participants.Values) { max = Math.Max(max, session.Ping); } @@ -120,7 +120,7 @@ namespace MediaBrowser.Controller.Syncplay public void SetBuffering(SessionInfo session, bool isBuffering) { if (!ContainsSession(session.Id.ToString())) return; - Partecipants[session.Id.ToString()].IsBuffering = isBuffering; + Participants[session.Id.ToString()].IsBuffering = isBuffering; } /// @@ -129,7 +129,7 @@ namespace MediaBrowser.Controller.Syncplay /// true if there is a session buffering in the group; false otherwise. public bool IsBuffering() { - foreach (var session in Partecipants.Values) + foreach (var session in Participants.Values) { if (session.IsBuffering) return true; } @@ -142,7 +142,7 @@ namespace MediaBrowser.Controller.Syncplay /// true if the group is empty; false otherwise. public bool IsEmpty() { - return Partecipants.Count == 0; + return Participants.Count == 0; } } } diff --git a/MediaBrowser.Controller/Syncplay/ISyncplayManager.cs b/MediaBrowser.Controller/Syncplay/ISyncplayManager.cs index d0cf8fa9c..433d6d8bc 100644 --- a/MediaBrowser.Controller/Syncplay/ISyncplayManager.cs +++ b/MediaBrowser.Controller/Syncplay/ISyncplayManager.cs @@ -50,7 +50,7 @@ namespace MediaBrowser.Controller.Syncplay /// The session. /// The group. /// - void MapSessionToGroup(SessionInfo session, ISyncplayController group); + void AddSessionToGroup(SessionInfo session, ISyncplayController group); /// /// Unmaps a session from a group. @@ -58,6 +58,6 @@ namespace MediaBrowser.Controller.Syncplay /// The session. /// The group. /// - void UnmapSessionFromGroup(SessionInfo session, ISyncplayController group); + void RemoveSessionFromGroup(SessionInfo session, ISyncplayController group); } } diff --git a/MediaBrowser.Model/Syncplay/GroupInfoModel.cs b/MediaBrowser.Model/Syncplay/GroupInfoModel.cs deleted file mode 100644 index 599c0dbfc..000000000 --- a/MediaBrowser.Model/Syncplay/GroupInfoModel.cs +++ /dev/null @@ -1,38 +0,0 @@ -namespace MediaBrowser.Model.Syncplay -{ - /// - /// Class GroupInfoModel. - /// - public class GroupInfoView - { - /// - /// Gets or sets the group identifier. - /// - /// The group identifier. - public string GroupId { get; set; } - - /// - /// Gets or sets the playing item id. - /// - /// The playing item id. - public string PlayingItemId { get; set; } - - /// - /// Gets or sets the playing item name. - /// - /// The playing item name. - public string PlayingItemName { get; set; } - - /// - /// Gets or sets the position ticks. - /// - /// The position ticks. - public long PositionTicks { get; set; } - - /// - /// Gets or sets the partecipants. - /// - /// The partecipants. - public string[] Partecipants { get; set; } - } -} diff --git a/MediaBrowser.Model/Syncplay/GroupInfoView.cs b/MediaBrowser.Model/Syncplay/GroupInfoView.cs new file mode 100644 index 000000000..50ad70630 --- /dev/null +++ b/MediaBrowser.Model/Syncplay/GroupInfoView.cs @@ -0,0 +1,38 @@ +namespace MediaBrowser.Model.Syncplay +{ + /// + /// Class GroupInfoView. + /// + public class GroupInfoView + { + /// + /// Gets or sets the group identifier. + /// + /// The group identifier. + public string GroupId { get; set; } + + /// + /// Gets or sets the playing item id. + /// + /// The playing item id. + public string PlayingItemId { get; set; } + + /// + /// Gets or sets the playing item name. + /// + /// The playing item name. + public string PlayingItemName { get; set; } + + /// + /// Gets or sets the position ticks. + /// + /// The position ticks. + public long PositionTicks { get; set; } + + /// + /// Gets or sets the participants. + /// + /// The participants. + public string[] Participants { get; set; } + } +} diff --git a/MediaBrowser.Model/Syncplay/PlaybackRequestType.cs b/MediaBrowser.Model/Syncplay/PlaybackRequestType.cs index 3d99b2718..b3d49d09e 100644 --- a/MediaBrowser.Model/Syncplay/PlaybackRequestType.cs +++ b/MediaBrowser.Model/Syncplay/PlaybackRequestType.cs @@ -24,7 +24,7 @@ namespace MediaBrowser.Model.Syncplay /// /// A user is signaling that playback resumed. /// - BufferingComplete = 4, + BufferingDone = 4, /// /// A user is reporting its ping. /// -- cgit v1.2.3 From 0b974d09ca08f70d9cd61d4871698956026b7b3b Mon Sep 17 00:00:00 2001 From: gion Date: Tue, 28 Apr 2020 14:12:06 +0200 Subject: Synchronize access to data --- .../Session/SessionWebSocketListener.cs | 186 ++++++++++++++------- .../Syncplay/SyncplayManager.cs | 147 +++++++++------- MediaBrowser.Api/Syncplay/TimeSyncService.cs | 8 +- 3 files changed, 205 insertions(+), 136 deletions(-) (limited to 'Emby.Server.Implementations/Session') diff --git a/Emby.Server.Implementations/Session/SessionWebSocketListener.cs b/Emby.Server.Implementations/Session/SessionWebSocketListener.cs index b0c6d0aa0..7a316b070 100644 --- a/Emby.Server.Implementations/Session/SessionWebSocketListener.cs +++ b/Emby.Server.Implementations/Session/SessionWebSocketListener.cs @@ -1,6 +1,5 @@ -using System.Collections.Generic; using System; -using System.Collections.Concurrent; +using System.Collections.Generic; using System.Linq; using System.Net.WebSockets; using System.Threading; @@ -26,9 +25,9 @@ namespace Emby.Server.Implementations.Session public readonly int WebSocketLostTimeout = 60; /// - /// The keep-alive timer factor; controls how often the timer will check on the status of the WebSockets. + /// The keep-alive interval factor; controls how often the watcher will check on the status of the WebSockets. /// - public readonly double TimerFactor = 0.2; + public readonly double IntervalFactor = 0.2; /// /// The ForceKeepAlive factor; controls when a ForceKeepAlive is sent. @@ -53,14 +52,24 @@ namespace Emby.Server.Implementations.Session private readonly IHttpServer _httpServer; /// - /// The KeepAlive timer. + /// The KeepAlive cancellation token. + /// + private CancellationTokenSource _keepAliveCancellationToken; + + /// + /// Lock used for accesing the KeepAlive cancellation token. /// - private Timer _keepAliveTimer; + private readonly object _keepAliveLock = new object(); /// /// The WebSocket watchlist. /// - private readonly ConcurrentDictionary _webSockets = new ConcurrentDictionary(); + private readonly HashSet _webSockets = new HashSet(); + + /// + /// Lock used for accesing the WebSockets watchlist. + /// + private readonly object _webSocketsLock = new object(); /// /// Initializes a new instance of the class. @@ -113,7 +122,7 @@ namespace Emby.Server.Implementations.Session public void Dispose() { _httpServer.WebSocketConnected -= _serverManager_WebSocketConnected; - StopKeepAliveTimer(); + StopKeepAlive(); } /// @@ -140,6 +149,7 @@ namespace Emby.Server.Implementations.Session private void OnWebSocketClosed(object sender, EventArgs e) { var webSocket = (IWebSocketConnection) sender; + _logger.LogDebug("WebSockets {0} closed.", webSocket); RemoveWebSocket(webSocket); } @@ -147,15 +157,20 @@ namespace Emby.Server.Implementations.Session /// Adds a WebSocket to the KeepAlive watchlist. /// /// The WebSocket to monitor. - private async void KeepAliveWebSocket(IWebSocketConnection webSocket) + private async Task KeepAliveWebSocket(IWebSocketConnection webSocket) { - if (!_webSockets.TryAdd(webSocket, 0)) + lock (_webSocketsLock) { - _logger.LogWarning("Multiple attempts to keep alive single WebSocket {0}", webSocket); - return; + if (!_webSockets.Add(webSocket)) + { + _logger.LogWarning("Multiple attempts to keep alive single WebSocket {0}", webSocket); + return; + } + webSocket.Closed += OnWebSocketClosed; + webSocket.LastKeepAliveDate = DateTime.UtcNow; + + StartKeepAlive(); } - webSocket.Closed += OnWebSocketClosed; - webSocket.LastKeepAliveDate = DateTime.UtcNow; // Notify WebSocket about timeout try @@ -164,10 +179,8 @@ namespace Emby.Server.Implementations.Session } catch (WebSocketException exception) { - _logger.LogWarning(exception, "Error sending ForceKeepAlive message to WebSocket."); + _logger.LogWarning(exception, "Error sending ForceKeepAlive message to WebSocket {0}.", webSocket); } - - StartKeepAliveTimer(); } /// @@ -176,87 +189,130 @@ namespace Emby.Server.Implementations.Session /// The WebSocket to remove. private void RemoveWebSocket(IWebSocketConnection webSocket) { - webSocket.Closed -= OnWebSocketClosed; - _webSockets.TryRemove(webSocket, out _); + lock (_webSocketsLock) + { + if (!_webSockets.Remove(webSocket)) + { + _logger.LogWarning("WebSocket {0} not on watchlist.", webSocket); + } + else + { + webSocket.Closed -= OnWebSocketClosed; + } + } } /// - /// Starts the KeepAlive timer. + /// Starts the KeepAlive watcher. /// - private void StartKeepAliveTimer() + private void StartKeepAlive() { - if (_keepAliveTimer == null) + lock (_keepAliveLock) { - _keepAliveTimer = new Timer( - KeepAliveSockets, - null, - TimeSpan.FromSeconds(WebSocketLostTimeout * TimerFactor), - TimeSpan.FromSeconds(WebSocketLostTimeout * TimerFactor) - ); + if (_keepAliveCancellationToken == null) + { + _keepAliveCancellationToken = new CancellationTokenSource(); + // Start KeepAlive watcher + KeepAliveSockets( + TimeSpan.FromSeconds(WebSocketLostTimeout * IntervalFactor), + _keepAliveCancellationToken.Token); + } } } /// - /// Stops the KeepAlive timer. + /// Stops the KeepAlive watcher. /// - private void StopKeepAliveTimer() + private void StopKeepAlive() { - if (_keepAliveTimer != null) + lock (_keepAliveLock) { - _keepAliveTimer.Dispose(); - _keepAliveTimer = null; + if (_keepAliveCancellationToken != null) + { + _keepAliveCancellationToken.Cancel(); + _keepAliveCancellationToken = null; + } } - foreach (var pair in _webSockets) + lock (_webSocketsLock) { - pair.Key.Closed -= OnWebSocketClosed; + foreach (var webSocket in _webSockets) + { + webSocket.Closed -= OnWebSocketClosed; + } + _webSockets.Clear(); } } /// - /// Checks status of KeepAlive of WebSockets. + /// Checks status of KeepAlive of WebSockets once every the specified interval time. /// - /// The state. - private async void KeepAliveSockets(object state) + /// The interval. + /// The cancellation token. + private async Task KeepAliveSockets(TimeSpan interval, CancellationToken cancellationToken) { - var inactive = _webSockets.Keys.Where(i => + while (true) { - var elapsed = (DateTime.UtcNow - i.LastKeepAliveDate).TotalSeconds; - return (elapsed > WebSocketLostTimeout * ForceKeepAliveFactor) && (elapsed < WebSocketLostTimeout); - }); - var lost = _webSockets.Keys.Where(i => (DateTime.UtcNow - i.LastKeepAliveDate).TotalSeconds >= WebSocketLostTimeout); + _logger.LogDebug("Watching {0} WebSockets.", _webSockets.Count()); - if (inactive.Any()) - { - _logger.LogDebug("Sending ForceKeepAlive message to {0} inactive WebSockets.", inactive.Count()); - } + IEnumerable inactive; + IEnumerable lost; + lock (_webSocketsLock) + { + inactive = _webSockets.Where(i => + { + var elapsed = (DateTime.UtcNow - i.LastKeepAliveDate).TotalSeconds; + return (elapsed > WebSocketLostTimeout * ForceKeepAliveFactor) && (elapsed < WebSocketLostTimeout); + }); + lost = _webSockets.Where(i => (DateTime.UtcNow - i.LastKeepAliveDate).TotalSeconds >= WebSocketLostTimeout); + } - foreach (var webSocket in inactive) - { - try + if (inactive.Any()) { - await SendForceKeepAlive(webSocket); + _logger.LogInformation("Sending ForceKeepAlive message to {0} inactive WebSockets.", inactive.Count()); } - catch (WebSocketException exception) + + foreach (var webSocket in inactive) { - _logger.LogInformation(exception, "Error sending ForceKeepAlive message to WebSocket."); - lost.Append(webSocket); + try + { + await SendForceKeepAlive(webSocket); + } + catch (WebSocketException exception) + { + _logger.LogInformation(exception, "Error sending ForceKeepAlive message to WebSocket."); + lost = lost.Append(webSocket); + } } - } - if (lost.Any()) - { - _logger.LogInformation("Lost {0} WebSockets.", lost.Count()); - foreach (var webSocket in lost) + lock (_webSocketsLock) { - // TODO: handle session relative to the lost webSocket - RemoveWebSocket(webSocket); + if (lost.Any()) + { + _logger.LogInformation("Lost {0} WebSockets.", lost.Count()); + foreach (var webSocket in lost.ToList()) + { + // TODO: handle session relative to the lost webSocket + RemoveWebSocket(webSocket); + } + } + + if (!_webSockets.Any()) + { + StopKeepAlive(); + } } - } - if (!_webSockets.Any()) - { - StopKeepAliveTimer(); + // Wait for next interval + Task task = Task.Delay(interval, cancellationToken); + try + { + await task; + } + catch (TaskCanceledException) + { + return; + } } } diff --git a/Emby.Server.Implementations/Syncplay/SyncplayManager.cs b/Emby.Server.Implementations/Syncplay/SyncplayManager.cs index 5aefd1fd9..eb61da7f3 100644 --- a/Emby.Server.Implementations/Syncplay/SyncplayManager.cs +++ b/Emby.Server.Implementations/Syncplay/SyncplayManager.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Concurrent; using System.Collections.Generic; using System.Globalization; using System.Linq; @@ -42,14 +41,19 @@ namespace Emby.Server.Implementations.Syncplay /// /// The map between sessions and groups. /// - private readonly ConcurrentDictionary _sessionToGroupMap = - new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); + private readonly Dictionary _sessionToGroupMap = + new Dictionary(StringComparer.OrdinalIgnoreCase); /// /// The groups. /// - private readonly ConcurrentDictionary _groups = - new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); + private readonly Dictionary _groups = + new Dictionary(StringComparer.OrdinalIgnoreCase); + + /// + /// Lock used for accesing any group. + /// + private readonly object _groupsLock = new object(); private bool _disposed = false; @@ -175,15 +179,18 @@ namespace Emby.Server.Implementations.Syncplay return; } - if (IsSessionInGroup(session)) + lock (_groupsLock) { - LeaveGroup(session); - } + if (IsSessionInGroup(session)) + { + LeaveGroup(session); + } - var group = new SyncplayController(_logger, _sessionManager, this); - _groups[group.GetGroupId().ToString()] = group; + var group = new SyncplayController(_logger, _sessionManager, this); + _groups[group.GetGroupId().ToString()] = group; - group.InitGroup(session); + group.InitGroup(session); + } } /// @@ -203,67 +210,73 @@ namespace Emby.Server.Implementations.Syncplay return; } - ISyncplayController group; - _groups.TryGetValue(groupId, out group); - - if (group == null) + lock (_groupsLock) { - _logger.LogWarning("Syncplaymanager JoinGroup: {0} tried to join group {0} that does not exist.", session.Id, groupId); + ISyncplayController group; + _groups.TryGetValue(groupId, out group); - var error = new GroupUpdate() + if (group == null) { - Type = GroupUpdateType.GroupNotJoined - }; - _sessionManager.SendSyncplayGroupUpdate(session.Id.ToString(), error, CancellationToken.None); - return; - } + _logger.LogWarning("Syncplaymanager JoinGroup: {0} tried to join group {0} that does not exist.", session.Id, groupId); - if (!HasAccessToItem(user, group.GetPlayingItemId())) - { - _logger.LogWarning("Syncplaymanager JoinGroup: {0} does not have access to {1}.", session.Id, group.GetPlayingItemId()); + var error = new GroupUpdate() + { + Type = GroupUpdateType.GroupNotJoined + }; + _sessionManager.SendSyncplayGroupUpdate(session.Id.ToString(), error, CancellationToken.None); + return; + } - var error = new GroupUpdate() + if (!HasAccessToItem(user, group.GetPlayingItemId())) { - GroupId = group.GetGroupId().ToString(), - Type = GroupUpdateType.LibraryAccessDenied - }; - _sessionManager.SendSyncplayGroupUpdate(session.Id.ToString(), error, CancellationToken.None); - return; - } + _logger.LogWarning("Syncplaymanager JoinGroup: {0} does not have access to {1}.", session.Id, group.GetPlayingItemId()); + + var error = new GroupUpdate() + { + GroupId = group.GetGroupId().ToString(), + Type = GroupUpdateType.LibraryAccessDenied + }; + _sessionManager.SendSyncplayGroupUpdate(session.Id.ToString(), error, CancellationToken.None); + return; + } + + if (IsSessionInGroup(session)) + { + if (GetSessionGroup(session).Equals(groupId)) return; + LeaveGroup(session); + } - if (IsSessionInGroup(session)) - { - if (GetSessionGroup(session).Equals(groupId)) return; - LeaveGroup(session); + group.SessionJoin(session, request); } - - group.SessionJoin(session, request); } /// public void LeaveGroup(SessionInfo session) { // TODO: determine what happens to users that are in a group and get their permissions revoked - - ISyncplayController group; - _sessionToGroupMap.TryGetValue(session.Id, out group); - - if (group == null) + lock (_groupsLock) { - _logger.LogWarning("Syncplaymanager LeaveGroup: {0} does not belong to any group.", session.Id); + ISyncplayController group; + _sessionToGroupMap.TryGetValue(session.Id, out group); - var error = new GroupUpdate() + if (group == null) { - Type = GroupUpdateType.NotInGroup - }; - _sessionManager.SendSyncplayGroupUpdate(session.Id.ToString(), error, CancellationToken.None); - return; - } - group.SessionLeave(session); + _logger.LogWarning("Syncplaymanager LeaveGroup: {0} does not belong to any group.", session.Id); - if (group.IsGroupEmpty()) - { - _groups.Remove(group.GetGroupId().ToString(), out _); + var error = new GroupUpdate() + { + Type = GroupUpdateType.NotInGroup + }; + _sessionManager.SendSyncplayGroupUpdate(session.Id.ToString(), error, CancellationToken.None); + return; + } + + group.SessionLeave(session); + + if (group.IsGroupEmpty()) + { + _groups.Remove(group.GetGroupId().ToString(), out _); + } } } @@ -314,21 +327,25 @@ namespace Emby.Server.Implementations.Syncplay return; } - ISyncplayController group; - _sessionToGroupMap.TryGetValue(session.Id, out group); - - if (group == null) + lock (_groupsLock) { - _logger.LogWarning("Syncplaymanager HandleRequest: {0} does not belong to any group.", session.Id); + ISyncplayController group; + _sessionToGroupMap.TryGetValue(session.Id, out group); - var error = new GroupUpdate() + if (group == null) { - Type = GroupUpdateType.NotInGroup - }; - _sessionManager.SendSyncplayGroupUpdate(session.Id.ToString(), error, CancellationToken.None); - return; + _logger.LogWarning("Syncplaymanager HandleRequest: {0} does not belong to any group.", session.Id); + + var error = new GroupUpdate() + { + Type = GroupUpdateType.NotInGroup + }; + _sessionManager.SendSyncplayGroupUpdate(session.Id.ToString(), error, CancellationToken.None); + return; + } + + group.HandleRequest(session, request); } - group.HandleRequest(session, request); } /// diff --git a/MediaBrowser.Api/Syncplay/TimeSyncService.cs b/MediaBrowser.Api/Syncplay/TimeSyncService.cs index 897413015..930968d9f 100644 --- a/MediaBrowser.Api/Syncplay/TimeSyncService.cs +++ b/MediaBrowser.Api/Syncplay/TimeSyncService.cs @@ -9,7 +9,6 @@ using Microsoft.Extensions.Logging; namespace MediaBrowser.Api.Syncplay { [Route("/GetUtcTime", "GET", Summary = "Get UtcTime")] - [Authenticated] public class GetUtcTime : IReturnVoid { // Nothing @@ -33,13 +32,10 @@ namespace MediaBrowser.Api.Syncplay public TimeSyncService( ILogger logger, IServerConfigurationManager serverConfigurationManager, - IHttpResultFactory httpResultFactory, - ISessionManager sessionManager, - ISessionContext sessionContext) + IHttpResultFactory httpResultFactory) : base(logger, serverConfigurationManager, httpResultFactory) { - _sessionManager = sessionManager; - _sessionContext = sessionContext; + // Do nothing } /// -- cgit v1.2.3 From 3623aafcb60dec4f4f5055046717d895b7597b60 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Sat, 2 May 2020 01:30:04 +0200 Subject: Make SonarCloud happy --- Emby.Server.Implementations/ApplicationHost.cs | 5 +---- .../HttpServer/HttpListenerHost.cs | 25 +--------------------- .../HttpServer/WebSocketConnection.cs | 8 ------- .../Session/WebSocketController.cs | 2 +- MediaBrowser.Controller/Net/IHttpServer.cs | 5 ++--- 5 files changed, 5 insertions(+), 40 deletions(-) (limited to 'Emby.Server.Implementations/Session') diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 6279ce5d0..11fed24f7 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -93,7 +93,6 @@ using MediaBrowser.Model.Serialization; using MediaBrowser.Model.Services; using MediaBrowser.Model.System; using MediaBrowser.Model.Tasks; -using MediaBrowser.Model.Updates; using MediaBrowser.Providers.Chapters; using MediaBrowser.Providers.Manager; using MediaBrowser.Providers.Plugins.TheTvdb; @@ -101,12 +100,10 @@ using MediaBrowser.Providers.Subtitles; using MediaBrowser.WebDashboard.Api; using MediaBrowser.XbmcMetadata.Providers; using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Http.Extensions; -using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; -using OperatingSystem = MediaBrowser.Common.System.OperatingSystem; using Prometheus.DotNetRuntime; +using OperatingSystem = MediaBrowser.Common.System.OperatingSystem; namespace Emby.Server.Implementations { diff --git a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs index e75140d6c..7358c1a81 100644 --- a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs +++ b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs @@ -28,12 +28,11 @@ using Microsoft.AspNetCore.WebUtilities; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; -using Microsoft.Net.Http.Headers; using ServiceStack.Text.Jsv; namespace Emby.Server.Implementations.HttpServer { - public class HttpListenerHost : IHttpServer, IDisposable + public class HttpListenerHost : IHttpServer { /// /// The key for a setting that specifies the default redirect path @@ -699,28 +698,6 @@ namespace Emby.Server.Implementations.HttpServer return _baseUrlPrefix + NormalizeUrlPath(path); } - /// - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - protected virtual void Dispose(bool disposing) - { - if (_disposed) - { - return; - } - - if (disposing) - { - // TODO: - } - - _disposed = true; - } - /// /// Processes the web socket message received. /// diff --git a/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs b/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs index 1af748ebc..095725c50 100644 --- a/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs +++ b/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs @@ -36,8 +36,6 @@ namespace Emby.Server.Implementations.HttpServer /// private readonly WebSocket _socket; - private bool _disposed = false; - /// /// Initializes a new instance of the class. /// @@ -221,12 +219,6 @@ namespace Emby.Server.Implementations.HttpServer }; await OnReceive(info).ConfigureAwait(false); - - // Stop reading if there's no more data coming - if (result.IsCompleted) - { - return; - } } } } diff --git a/Emby.Server.Implementations/Session/WebSocketController.cs b/Emby.Server.Implementations/Session/WebSocketController.cs index c7ef9b1ce..a0274acd2 100644 --- a/Emby.Server.Implementations/Session/WebSocketController.cs +++ b/Emby.Server.Implementations/Session/WebSocketController.cs @@ -21,7 +21,7 @@ namespace Emby.Server.Implementations.Session private readonly ISessionManager _sessionManager; private readonly SessionInfo _session; - private List _sockets; + private readonly List _sockets; private bool _disposed = false; public WebSocketController( diff --git a/MediaBrowser.Controller/Net/IHttpServer.cs b/MediaBrowser.Controller/Net/IHttpServer.cs index 666ac1cfe..f1c441761 100644 --- a/MediaBrowser.Controller/Net/IHttpServer.cs +++ b/MediaBrowser.Controller/Net/IHttpServer.cs @@ -2,15 +2,14 @@ using System; using System.Collections.Generic; using System.Threading.Tasks; using MediaBrowser.Model.Events; -using MediaBrowser.Model.Services; using Microsoft.AspNetCore.Http; namespace MediaBrowser.Controller.Net { /// - /// Interface IHttpServer + /// Interface IHttpServer. /// - public interface IHttpServer : IDisposable + public interface IHttpServer { /// /// Gets the URL prefix. -- cgit v1.2.3 From 6e22e9222b68ad117550c02a8cbce2d65878f50b Mon Sep 17 00:00:00 2001 From: gion Date: Mon, 4 May 2020 19:46:02 +0200 Subject: Fix code issues --- .../HttpServer/WebSocketConnection.cs | 4 +- .../Session/SessionWebSocketListener.cs | 127 +++++++++-------- .../Syncplay/SyncplayController.cs | 158 ++++++++++++--------- .../Syncplay/SyncplayManager.cs | 61 ++++---- MediaBrowser.Api/Syncplay/SyncplayService.cs | 85 +++++++---- MediaBrowser.Api/Syncplay/TimeSyncService.cs | 13 +- MediaBrowser.Controller/Syncplay/GroupInfo.cs | 8 +- .../Syncplay/ISyncplayController.cs | 13 +- .../Syncplay/ISyncplayManager.cs | 16 ++- MediaBrowser.Model/Syncplay/GroupUpdateType.cs | 6 +- MediaBrowser.Model/Syncplay/PlaybackRequest.cs | 2 +- 11 files changed, 281 insertions(+), 212 deletions(-) (limited to 'Emby.Server.Implementations/Session') diff --git a/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs b/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs index c819c163a..4c33ff71b 100644 --- a/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs +++ b/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs @@ -238,10 +238,10 @@ namespace Emby.Server.Implementations.HttpServer return _socket.SendAsync(text, true, cancellationToken); } - private Task SendKeepAliveResponse() + private void SendKeepAliveResponse() { LastKeepAliveDate = DateTime.UtcNow; - return SendAsync(new WebSocketMessage + SendAsync(new WebSocketMessage { MessageType = "KeepAlive" }, CancellationToken.None); diff --git a/Emby.Server.Implementations/Session/SessionWebSocketListener.cs b/Emby.Server.Implementations/Session/SessionWebSocketListener.cs index 7a316b070..d1ee22ea8 100644 --- a/Emby.Server.Implementations/Session/SessionWebSocketListener.cs +++ b/Emby.Server.Implementations/Session/SessionWebSocketListener.cs @@ -84,10 +84,10 @@ namespace Emby.Server.Implementations.Session _logger = loggerFactory.CreateLogger(GetType().Name); _json = json; _httpServer = httpServer; - httpServer.WebSocketConnected += _serverManager_WebSocketConnected; + httpServer.WebSocketConnected += OnServerManagerWebSocketConnected; } - void _serverManager_WebSocketConnected(object sender, GenericEventArgs e) + void OnServerManagerWebSocketConnected(object sender, GenericEventArgs e) { var session = GetSession(e.Argument.QueryString, e.Argument.RemoteEndPoint); @@ -121,7 +121,7 @@ namespace Emby.Server.Implementations.Session public void Dispose() { - _httpServer.WebSocketConnected -= _serverManager_WebSocketConnected; + _httpServer.WebSocketConnected -= OnServerManagerWebSocketConnected; StopKeepAlive(); } @@ -149,7 +149,7 @@ namespace Emby.Server.Implementations.Session private void OnWebSocketClosed(object sender, EventArgs e) { var webSocket = (IWebSocketConnection) sender; - _logger.LogDebug("WebSockets {0} closed.", webSocket); + _logger.LogDebug("WebSocket {0} is closed.", webSocket); RemoveWebSocket(webSocket); } @@ -157,7 +157,7 @@ namespace Emby.Server.Implementations.Session /// Adds a WebSocket to the KeepAlive watchlist. /// /// The WebSocket to monitor. - private async Task KeepAliveWebSocket(IWebSocketConnection webSocket) + private void KeepAliveWebSocket(IWebSocketConnection webSocket) { lock (_webSocketsLock) { @@ -175,11 +175,11 @@ namespace Emby.Server.Implementations.Session // Notify WebSocket about timeout try { - await SendForceKeepAlive(webSocket); + SendForceKeepAlive(webSocket).Wait(); } catch (WebSocketException exception) { - _logger.LogWarning(exception, "Error sending ForceKeepAlive message to WebSocket {0}.", webSocket); + _logger.LogWarning(exception, "Cannot send ForceKeepAlive message to WebSocket {0}.", webSocket); } } @@ -213,7 +213,8 @@ namespace Emby.Server.Implementations.Session { _keepAliveCancellationToken = new CancellationTokenSource(); // Start KeepAlive watcher - KeepAliveSockets( + var task = RepeatAsyncCallbackEvery( + KeepAliveSockets, TimeSpan.FromSeconds(WebSocketLostTimeout * IntervalFactor), _keepAliveCancellationToken.Token); } @@ -245,73 +246,58 @@ namespace Emby.Server.Implementations.Session } /// - /// Checks status of KeepAlive of WebSockets once every the specified interval time. + /// Checks status of KeepAlive of WebSockets. /// - /// The interval. - /// The cancellation token. - private async Task KeepAliveSockets(TimeSpan interval, CancellationToken cancellationToken) + private async Task KeepAliveSockets() { - while (true) + IEnumerable inactive; + IEnumerable lost; + + lock (_webSocketsLock) { - _logger.LogDebug("Watching {0} WebSockets.", _webSockets.Count()); + _logger.LogDebug("Watching {0} WebSockets.", _webSockets.Count); - IEnumerable inactive; - IEnumerable lost; - lock (_webSocketsLock) + inactive = _webSockets.Where(i => { - inactive = _webSockets.Where(i => - { - var elapsed = (DateTime.UtcNow - i.LastKeepAliveDate).TotalSeconds; - return (elapsed > WebSocketLostTimeout * ForceKeepAliveFactor) && (elapsed < WebSocketLostTimeout); - }); - lost = _webSockets.Where(i => (DateTime.UtcNow - i.LastKeepAliveDate).TotalSeconds >= WebSocketLostTimeout); - } + var elapsed = (DateTime.UtcNow - i.LastKeepAliveDate).TotalSeconds; + return (elapsed > WebSocketLostTimeout * ForceKeepAliveFactor) && (elapsed < WebSocketLostTimeout); + }); + lost = _webSockets.Where(i => (DateTime.UtcNow - i.LastKeepAliveDate).TotalSeconds >= WebSocketLostTimeout); + } - if (inactive.Any()) + if (inactive.Any()) + { + _logger.LogInformation("Sending ForceKeepAlive message to {0} inactive WebSockets.", inactive.Count()); + } + + foreach (var webSocket in inactive) + { + try { - _logger.LogInformation("Sending ForceKeepAlive message to {0} inactive WebSockets.", inactive.Count()); + await SendForceKeepAlive(webSocket); } - - foreach (var webSocket in inactive) + catch (WebSocketException exception) { - try - { - await SendForceKeepAlive(webSocket); - } - catch (WebSocketException exception) - { - _logger.LogInformation(exception, "Error sending ForceKeepAlive message to WebSocket."); - lost = lost.Append(webSocket); - } + _logger.LogInformation(exception, "Error sending ForceKeepAlive message to WebSocket."); + lost = lost.Append(webSocket); } + } - lock (_webSocketsLock) + lock (_webSocketsLock) + { + if (lost.Any()) { - if (lost.Any()) - { - _logger.LogInformation("Lost {0} WebSockets.", lost.Count()); - foreach (var webSocket in lost.ToList()) - { - // TODO: handle session relative to the lost webSocket - RemoveWebSocket(webSocket); - } - } - - if (!_webSockets.Any()) + _logger.LogInformation("Lost {0} WebSockets.", lost.Count()); + foreach (var webSocket in lost.ToList()) { - StopKeepAlive(); + // TODO: handle session relative to the lost webSocket + RemoveWebSocket(webSocket); } } - // Wait for next interval - Task task = Task.Delay(interval, cancellationToken); - try + if (!_webSockets.Any()) { - await task; - } - catch (TaskCanceledException) - { - return; + StopKeepAlive(); } } } @@ -329,5 +315,30 @@ namespace Emby.Server.Implementations.Session Data = WebSocketLostTimeout }, CancellationToken.None); } + + /// + /// Runs a given async callback once every specified interval time, until cancelled. + /// + /// The async callback. + /// The interval time. + /// The cancellation token. + /// Task. + private async Task RepeatAsyncCallbackEvery(Func callback, TimeSpan interval, CancellationToken cancellationToken) + { + while (!cancellationToken.IsCancellationRequested) + { + await callback(); + Task task = Task.Delay(interval, cancellationToken); + + try + { + await task; + } + catch (TaskCanceledException) + { + return; + } + } + } } } diff --git a/Emby.Server.Implementations/Syncplay/SyncplayController.cs b/Emby.Server.Implementations/Syncplay/SyncplayController.cs index 02cf08cd7..8cc3d1fac 100644 --- a/Emby.Server.Implementations/Syncplay/SyncplayController.cs +++ b/Emby.Server.Implementations/Syncplay/SyncplayController.cs @@ -7,13 +7,15 @@ using MediaBrowser.Controller.Session; using MediaBrowser.Controller.Syncplay; using MediaBrowser.Model.Session; using MediaBrowser.Model.Syncplay; -using Microsoft.Extensions.Logging; namespace Emby.Server.Implementations.Syncplay { /// /// Class SyncplayController. /// + /// + /// Class is not thread-safe, external locking is required when accessing methods. + /// public class SyncplayController : ISyncplayController, IDisposable { /// @@ -39,11 +41,6 @@ namespace Emby.Server.Implementations.Syncplay AllReady = 3 } - /// - /// The logger. - /// - private readonly ILogger _logger; - /// /// The session manager. /// @@ -71,11 +68,9 @@ namespace Emby.Server.Implementations.Syncplay private bool _disposed = false; public SyncplayController( - ILogger logger, ISessionManager sessionManager, ISyncplayManager syncplayManager) { - _logger = logger; _sessionManager = sessionManager; _syncplayManager = syncplayManager; } @@ -110,6 +105,16 @@ namespace Emby.Server.Implementations.Syncplay } } + /// + /// Converts DateTime to UTC string. + /// + /// The date to convert. + /// The UTC string. + private string DateToUTCString(DateTime date) + { + return date.ToUniversalTime().ToString("o"); + } + /// /// Filters sessions of this group. /// @@ -149,15 +154,16 @@ namespace Emby.Server.Implementations.Syncplay /// The current session. /// The filtering type. /// The message to send. + /// The cancellation token. /// The task. - private Task SendGroupUpdate(SessionInfo from, BroadcastType type, GroupUpdate message) + private Task SendGroupUpdate(SessionInfo from, BroadcastType type, GroupUpdate message, CancellationToken cancellationToken) { IEnumerable GetTasks() { SessionInfo[] sessions = FilterSessions(from, type); foreach (var session in sessions) { - yield return _sessionManager.SendSyncplayGroupUpdate(session.Id.ToString(), message, CancellationToken.None); + yield return _sessionManager.SendSyncplayGroupUpdate(session.Id.ToString(), message, cancellationToken); } } @@ -170,15 +176,16 @@ namespace Emby.Server.Implementations.Syncplay /// The current session. /// The filtering type. /// The message to send. + /// The cancellation token. /// The task. - private Task SendCommand(SessionInfo from, BroadcastType type, SendCommand message) + private Task SendCommand(SessionInfo from, BroadcastType type, SendCommand message, CancellationToken cancellationToken) { IEnumerable GetTasks() { SessionInfo[] sessions = FilterSessions(from, type); foreach (var session in sessions) { - yield return _sessionManager.SendSyncplayCommand(session.Id.ToString(), message, CancellationToken.None); + yield return _sessionManager.SendSyncplayCommand(session.Id.ToString(), message, cancellationToken); } } @@ -197,8 +204,8 @@ namespace Emby.Server.Implementations.Syncplay GroupId = _group.GroupId.ToString(), Command = type, PositionTicks = _group.PositionTicks, - When = _group.LastActivity.ToUniversalTime().ToString("o"), - EmittedAt = DateTime.UtcNow.ToUniversalTime().ToString("o") + When = DateToUTCString(_group.LastActivity), + EmittedAt = DateToUTCString(DateTime.UtcNow) }; } @@ -219,46 +226,46 @@ namespace Emby.Server.Implementations.Syncplay } /// - public void InitGroup(SessionInfo session) + public void InitGroup(SessionInfo session, CancellationToken cancellationToken) { _group.AddSession(session); _syncplayManager.AddSessionToGroup(session, this); _group.PlayingItem = session.FullNowPlayingItem; _group.IsPaused = true; - _group.PositionTicks = session.PlayState.PositionTicks ??= 0; + _group.PositionTicks = session.PlayState.PositionTicks ?? 0; _group.LastActivity = DateTime.UtcNow; - var updateSession = NewSyncplayGroupUpdate(GroupUpdateType.GroupJoined, DateTime.UtcNow.ToUniversalTime().ToString("o")); - SendGroupUpdate(session, BroadcastType.CurrentSession, updateSession); + var updateSession = NewSyncplayGroupUpdate(GroupUpdateType.GroupJoined, DateToUTCString(DateTime.UtcNow)); + SendGroupUpdate(session, BroadcastType.CurrentSession, updateSession, cancellationToken); var pauseCommand = NewSyncplayCommand(SendCommandType.Pause); - SendCommand(session, BroadcastType.CurrentSession, pauseCommand); + SendCommand(session, BroadcastType.CurrentSession, pauseCommand, cancellationToken); } /// - public void SessionJoin(SessionInfo session, JoinGroupRequest request) + public void SessionJoin(SessionInfo session, JoinGroupRequest request, CancellationToken cancellationToken) { if (session.NowPlayingItem?.Id == _group.PlayingItem.Id && request.PlayingItemId == _group.PlayingItem.Id) { _group.AddSession(session); _syncplayManager.AddSessionToGroup(session, this); - var updateSession = NewSyncplayGroupUpdate(GroupUpdateType.GroupJoined, DateTime.UtcNow.ToUniversalTime().ToString("o")); - SendGroupUpdate(session, BroadcastType.CurrentSession, updateSession); + var updateSession = NewSyncplayGroupUpdate(GroupUpdateType.GroupJoined, DateToUTCString(DateTime.UtcNow)); + SendGroupUpdate(session, BroadcastType.CurrentSession, updateSession, cancellationToken); var updateOthers = NewSyncplayGroupUpdate(GroupUpdateType.UserJoined, session.UserName); - SendGroupUpdate(session, BroadcastType.AllExceptCurrentSession, updateOthers); + SendGroupUpdate(session, BroadcastType.AllExceptCurrentSession, updateOthers, cancellationToken); // Client join and play, syncing will happen client side if (!_group.IsPaused) { var playCommand = NewSyncplayCommand(SendCommandType.Play); - SendCommand(session, BroadcastType.CurrentSession, playCommand); + SendCommand(session, BroadcastType.CurrentSession, playCommand, cancellationToken); } else { var pauseCommand = NewSyncplayCommand(SendCommandType.Pause); - SendCommand(session, BroadcastType.CurrentSession, pauseCommand); + SendCommand(session, BroadcastType.CurrentSession, pauseCommand, cancellationToken); } } else @@ -267,25 +274,25 @@ namespace Emby.Server.Implementations.Syncplay playRequest.ItemIds = new Guid[] { _group.PlayingItem.Id }; playRequest.StartPositionTicks = _group.PositionTicks; var update = NewSyncplayGroupUpdate(GroupUpdateType.PrepareSession, playRequest); - SendGroupUpdate(session, BroadcastType.CurrentSession, update); + SendGroupUpdate(session, BroadcastType.CurrentSession, update, cancellationToken); } } /// - public void SessionLeave(SessionInfo session) + public void SessionLeave(SessionInfo session, CancellationToken cancellationToken) { _group.RemoveSession(session); _syncplayManager.RemoveSessionFromGroup(session, this); var updateSession = NewSyncplayGroupUpdate(GroupUpdateType.GroupLeft, _group.PositionTicks); - SendGroupUpdate(session, BroadcastType.CurrentSession, updateSession); + SendGroupUpdate(session, BroadcastType.CurrentSession, updateSession, cancellationToken); var updateOthers = NewSyncplayGroupUpdate(GroupUpdateType.UserLeft, session.UserName); - SendGroupUpdate(session, BroadcastType.AllExceptCurrentSession, updateOthers); + SendGroupUpdate(session, BroadcastType.AllExceptCurrentSession, updateOthers, cancellationToken); } /// - public void HandleRequest(SessionInfo session, PlaybackRequest request) + public void HandleRequest(SessionInfo session, PlaybackRequest request, CancellationToken cancellationToken) { // The server's job is to mantain a consistent state to which clients refer to, // as also to notify clients of state changes. @@ -294,19 +301,19 @@ namespace Emby.Server.Implementations.Syncplay switch (request.Type) { case PlaybackRequestType.Play: - HandlePlayRequest(session, request); + HandlePlayRequest(session, request, cancellationToken); break; case PlaybackRequestType.Pause: - HandlePauseRequest(session, request); + HandlePauseRequest(session, request, cancellationToken); break; case PlaybackRequestType.Seek: - HandleSeekRequest(session, request); + HandleSeekRequest(session, request, cancellationToken); break; case PlaybackRequestType.Buffering: - HandleBufferingRequest(session, request); + HandleBufferingRequest(session, request, cancellationToken); break; case PlaybackRequestType.BufferingDone: - HandleBufferingDoneRequest(session, request); + HandleBufferingDoneRequest(session, request, cancellationToken); break; case PlaybackRequestType.UpdatePing: HandlePingUpdateRequest(session, request); @@ -319,7 +326,8 @@ namespace Emby.Server.Implementations.Syncplay /// /// The session. /// The play action. - private void HandlePlayRequest(SessionInfo session, PlaybackRequest request) + /// The cancellation token. + private void HandlePlayRequest(SessionInfo session, PlaybackRequest request, CancellationToken cancellationToken) { if (_group.IsPaused) { @@ -337,13 +345,13 @@ namespace Emby.Server.Implementations.Syncplay ); var command = NewSyncplayCommand(SendCommandType.Play); - SendCommand(session, BroadcastType.AllGroup, command); + SendCommand(session, BroadcastType.AllGroup, command, cancellationToken); } else { // Client got lost, sending current state var command = NewSyncplayCommand(SendCommandType.Play); - SendCommand(session, BroadcastType.CurrentSession, command); + SendCommand(session, BroadcastType.CurrentSession, command, cancellationToken); } } @@ -352,7 +360,8 @@ namespace Emby.Server.Implementations.Syncplay /// /// The session. /// The pause action. - private void HandlePauseRequest(SessionInfo session, PlaybackRequest request) + /// The cancellation token. + private void HandlePauseRequest(SessionInfo session, PlaybackRequest request, CancellationToken cancellationToken) { if (!_group.IsPaused) { @@ -366,13 +375,13 @@ namespace Emby.Server.Implementations.Syncplay _group.PositionTicks += elapsedTime.Ticks > 0 ? elapsedTime.Ticks : 0; var command = NewSyncplayCommand(SendCommandType.Pause); - SendCommand(session, BroadcastType.AllGroup, command); + SendCommand(session, BroadcastType.AllGroup, command, cancellationToken); } else { // Client got lost, sending current state var command = NewSyncplayCommand(SendCommandType.Pause); - SendCommand(session, BroadcastType.CurrentSession, command); + SendCommand(session, BroadcastType.CurrentSession, command, cancellationToken); } } @@ -381,16 +390,11 @@ namespace Emby.Server.Implementations.Syncplay /// /// The session. /// The seek action. - private void HandleSeekRequest(SessionInfo session, PlaybackRequest request) + /// The cancellation token. + private void HandleSeekRequest(SessionInfo session, PlaybackRequest request, CancellationToken cancellationToken) { // Sanitize PositionTicks - var ticks = request.PositionTicks ??= 0; - ticks = ticks >= 0 ? ticks : 0; - if (_group.PlayingItem.RunTimeTicks != null) - { - var runTimeTicks = _group.PlayingItem.RunTimeTicks ??= 0; - ticks = ticks > runTimeTicks ? runTimeTicks : ticks; - } + var ticks = SanitizePositionTicks(request.PositionTicks); // Pause and seek _group.IsPaused = true; @@ -398,7 +402,7 @@ namespace Emby.Server.Implementations.Syncplay _group.LastActivity = DateTime.UtcNow; var command = NewSyncplayCommand(SendCommandType.Seek); - SendCommand(session, BroadcastType.AllGroup, command); + SendCommand(session, BroadcastType.AllGroup, command, cancellationToken); } /// @@ -406,7 +410,8 @@ namespace Emby.Server.Implementations.Syncplay /// /// The session. /// The buffering action. - private void HandleBufferingRequest(SessionInfo session, PlaybackRequest request) + /// The cancellation token. + private void HandleBufferingRequest(SessionInfo session, PlaybackRequest request, CancellationToken cancellationToken) { if (!_group.IsPaused) { @@ -421,16 +426,16 @@ namespace Emby.Server.Implementations.Syncplay // Send pause command to all non-buffering sessions var command = NewSyncplayCommand(SendCommandType.Pause); - SendCommand(session, BroadcastType.AllReady, command); + SendCommand(session, BroadcastType.AllReady, command, cancellationToken); var updateOthers = NewSyncplayGroupUpdate(GroupUpdateType.GroupWait, session.UserName); - SendGroupUpdate(session, BroadcastType.AllExceptCurrentSession, updateOthers); + SendGroupUpdate(session, BroadcastType.AllExceptCurrentSession, updateOthers, cancellationToken); } else { // Client got lost, sending current state var command = NewSyncplayCommand(SendCommandType.Pause); - SendCommand(session, BroadcastType.CurrentSession, command); + SendCommand(session, BroadcastType.CurrentSession, command, cancellationToken); } } @@ -439,26 +444,28 @@ namespace Emby.Server.Implementations.Syncplay /// /// The session. /// The buffering-done action. - private void HandleBufferingDoneRequest(SessionInfo session, PlaybackRequest request) + /// The cancellation token. + private void HandleBufferingDoneRequest(SessionInfo session, PlaybackRequest request, CancellationToken cancellationToken) { if (_group.IsPaused) { _group.SetBuffering(session, false); - var when = request.When ??= DateTime.UtcNow; + var requestTicks = SanitizePositionTicks(request.PositionTicks); + + var when = request.When ?? DateTime.UtcNow; var currentTime = DateTime.UtcNow; var elapsedTime = currentTime - when; - var clientPosition = TimeSpan.FromTicks(request.PositionTicks ??= 0) + elapsedTime; + var clientPosition = TimeSpan.FromTicks(requestTicks) + elapsedTime; var delay = _group.PositionTicks - clientPosition.Ticks; if (_group.IsBuffering()) { - // Others are buffering, tell this client to pause when ready + // Others are still buffering, tell this client to pause when ready var command = NewSyncplayCommand(SendCommandType.Pause); - command.When = currentTime.AddMilliseconds( - delay - ).ToUniversalTime().ToString("o"); - SendCommand(session, BroadcastType.CurrentSession, command); + var pauseAtTime = currentTime.AddMilliseconds(delay); + command.When = DateToUTCString(pauseAtTime); + SendCommand(session, BroadcastType.CurrentSession, command, cancellationToken); } else { @@ -472,7 +479,7 @@ namespace Emby.Server.Implementations.Syncplay delay ); var command = NewSyncplayCommand(SendCommandType.Play); - SendCommand(session, BroadcastType.AllExceptCurrentSession, command); + SendCommand(session, BroadcastType.AllExceptCurrentSession, command, cancellationToken); } else { @@ -485,7 +492,7 @@ namespace Emby.Server.Implementations.Syncplay ); var command = NewSyncplayCommand(SendCommandType.Play); - SendCommand(session, BroadcastType.AllGroup, command); + SendCommand(session, BroadcastType.AllGroup, command, cancellationToken); } } } @@ -493,8 +500,25 @@ namespace Emby.Server.Implementations.Syncplay { // Group was not waiting, make sure client has latest state var command = NewSyncplayCommand(SendCommandType.Play); - SendCommand(session, BroadcastType.CurrentSession, command); + SendCommand(session, BroadcastType.CurrentSession, command, cancellationToken); + } + } + + /// + /// Sanitizes the PositionTicks, considers the current playing item when available. + /// + /// The PositionTicks. + /// The sanitized PositionTicks. + private long SanitizePositionTicks(long? positionTicks) + { + var ticks = positionTicks ?? 0; + ticks = ticks >= 0 ? ticks : 0; + if (_group.PlayingItem != null) + { + var runTimeTicks = _group.PlayingItem.RunTimeTicks ?? 0; + ticks = ticks > runTimeTicks ? runTimeTicks : ticks; } + return ticks; } /// @@ -505,7 +529,7 @@ namespace Emby.Server.Implementations.Syncplay private void HandlePingUpdateRequest(SessionInfo session, PlaybackRequest request) { // Collected pings are used to account for network latency when unpausing playback - _group.UpdatePing(session, request.Ping ??= _group.DefaulPing); + _group.UpdatePing(session, request.Ping ?? _group.DefaulPing); } /// @@ -517,7 +541,7 @@ namespace Emby.Server.Implementations.Syncplay PlayingItemName = _group.PlayingItem.Name, PlayingItemId = _group.PlayingItem.Id.ToString(), PositionTicks = _group.PositionTicks, - Participants = _group.Participants.Values.Select(session => session.Session.UserName).ToArray() + Participants = _group.Participants.Values.Select(session => session.Session.UserName).Distinct().ToArray() }; } } diff --git a/Emby.Server.Implementations/Syncplay/SyncplayManager.cs b/Emby.Server.Implementations/Syncplay/SyncplayManager.cs index eb61da7f3..7074e2225 100644 --- a/Emby.Server.Implementations/Syncplay/SyncplayManager.cs +++ b/Emby.Server.Implementations/Syncplay/SyncplayManager.cs @@ -114,14 +114,14 @@ namespace Emby.Server.Implementations.Syncplay { var session = e.SessionInfo; if (!IsSessionInGroup(session)) return; - LeaveGroup(session); + LeaveGroup(session, CancellationToken.None); } private void OnSessionManagerPlaybackStopped(object sender, PlaybackStopEventArgs e) { var session = e.Session; if (!IsSessionInGroup(session)) return; - LeaveGroup(session); + LeaveGroup(session, CancellationToken.None); } private bool IsSessionInGroup(SessionInfo session) @@ -132,7 +132,13 @@ namespace Emby.Server.Implementations.Syncplay private bool HasAccessToItem(User user, Guid itemId) { var item = _libraryManager.GetItemById(itemId); - var hasParentalRatingAccess = user.Policy.MaxParentalRating.HasValue ? item.InheritedParentalRatingValue <= user.Policy.MaxParentalRating : true; + + // Check ParentalRating access + var hasParentalRatingAccess = true; + if (user.Policy.MaxParentalRating.HasValue) + { + hasParentalRatingAccess = item.InheritedParentalRatingValue <= user.Policy.MaxParentalRating; + } if (!user.Policy.EnableAllFolders && hasParentalRatingAccess) { @@ -140,7 +146,7 @@ namespace Emby.Server.Implementations.Syncplay folder => folder.Id.ToString("N", CultureInfo.InvariantCulture) ); var intersect = collections.Intersect(user.Policy.EnabledFolders); - return intersect.Count() > 0; + return intersect.Any(); } else { @@ -163,13 +169,13 @@ namespace Emby.Server.Implementations.Syncplay } /// - public void NewGroup(SessionInfo session) + public void NewGroup(SessionInfo session, CancellationToken cancellationToken) { var user = _userManager.GetUserById(session.UserId); if (user.Policy.SyncplayAccess != SyncplayAccess.CreateAndJoinGroups) { - _logger.LogWarning("Syncplaymanager NewGroup: {0} does not have permission to create groups.", session.Id); + _logger.LogWarning("NewGroup: {0} does not have permission to create groups.", session.Id); var error = new GroupUpdate() { @@ -183,24 +189,24 @@ namespace Emby.Server.Implementations.Syncplay { if (IsSessionInGroup(session)) { - LeaveGroup(session); + LeaveGroup(session, cancellationToken); } - var group = new SyncplayController(_logger, _sessionManager, this); + var group = new SyncplayController(_sessionManager, this); _groups[group.GetGroupId().ToString()] = group; - group.InitGroup(session); + group.InitGroup(session, cancellationToken); } } /// - public void JoinGroup(SessionInfo session, string groupId, JoinGroupRequest request) + public void JoinGroup(SessionInfo session, string groupId, JoinGroupRequest request, CancellationToken cancellationToken) { var user = _userManager.GetUserById(session.UserId); if (user.Policy.SyncplayAccess == SyncplayAccess.None) { - _logger.LogWarning("Syncplaymanager JoinGroup: {0} does not have access to Syncplay.", session.Id); + _logger.LogWarning("JoinGroup: {0} does not have access to Syncplay.", session.Id); var error = new GroupUpdate() { @@ -217,11 +223,11 @@ namespace Emby.Server.Implementations.Syncplay if (group == null) { - _logger.LogWarning("Syncplaymanager JoinGroup: {0} tried to join group {0} that does not exist.", session.Id, groupId); + _logger.LogWarning("JoinGroup: {0} tried to join group {0} that does not exist.", session.Id, groupId); var error = new GroupUpdate() { - Type = GroupUpdateType.GroupNotJoined + Type = GroupUpdateType.GroupDoesNotExist }; _sessionManager.SendSyncplayGroupUpdate(session.Id.ToString(), error, CancellationToken.None); return; @@ -229,7 +235,7 @@ namespace Emby.Server.Implementations.Syncplay if (!HasAccessToItem(user, group.GetPlayingItemId())) { - _logger.LogWarning("Syncplaymanager JoinGroup: {0} does not have access to {1}.", session.Id, group.GetPlayingItemId()); + _logger.LogWarning("JoinGroup: {0} does not have access to {1}.", session.Id, group.GetPlayingItemId()); var error = new GroupUpdate() { @@ -243,15 +249,15 @@ namespace Emby.Server.Implementations.Syncplay if (IsSessionInGroup(session)) { if (GetSessionGroup(session).Equals(groupId)) return; - LeaveGroup(session); + LeaveGroup(session, cancellationToken); } - group.SessionJoin(session, request); + group.SessionJoin(session, request, cancellationToken); } } /// - public void LeaveGroup(SessionInfo session) + public void LeaveGroup(SessionInfo session, CancellationToken cancellationToken) { // TODO: determine what happens to users that are in a group and get their permissions revoked lock (_groupsLock) @@ -261,7 +267,7 @@ namespace Emby.Server.Implementations.Syncplay if (group == null) { - _logger.LogWarning("Syncplaymanager LeaveGroup: {0} does not belong to any group.", session.Id); + _logger.LogWarning("LeaveGroup: {0} does not belong to any group.", session.Id); var error = new GroupUpdate() { @@ -271,17 +277,18 @@ namespace Emby.Server.Implementations.Syncplay return; } - group.SessionLeave(session); + group.SessionLeave(session, cancellationToken); if (group.IsGroupEmpty()) { + _logger.LogInformation("LeaveGroup: removing empty group {0}.", group.GetGroupId()); _groups.Remove(group.GetGroupId().ToString(), out _); } } } /// - public List ListGroups(SessionInfo session) + public List ListGroups(SessionInfo session, Guid filterItemId) { var user = _userManager.GetUserById(session.UserId); @@ -290,11 +297,11 @@ namespace Emby.Server.Implementations.Syncplay return new List(); } - // Filter by playing item if the user is viewing something already - if (session.NowPlayingItem != null) + // Filter by item if requested + if (!filterItemId.Equals(Guid.Empty)) { return _groups.Values.Where( - group => group.GetPlayingItemId().Equals(session.FullNowPlayingItem.Id) && HasAccessToItem(user, group.GetPlayingItemId()) + group => group.GetPlayingItemId().Equals(filterItemId) && HasAccessToItem(user, group.GetPlayingItemId()) ).Select( group => group.GetInfo() ).ToList(); @@ -311,13 +318,13 @@ namespace Emby.Server.Implementations.Syncplay } /// - public void HandleRequest(SessionInfo session, PlaybackRequest request) + public void HandleRequest(SessionInfo session, PlaybackRequest request, CancellationToken cancellationToken) { var user = _userManager.GetUserById(session.UserId); if (user.Policy.SyncplayAccess == SyncplayAccess.None) { - _logger.LogWarning("Syncplaymanager HandleRequest: {0} does not have access to Syncplay.", session.Id); + _logger.LogWarning("HandleRequest: {0} does not have access to Syncplay.", session.Id); var error = new GroupUpdate() { @@ -334,7 +341,7 @@ namespace Emby.Server.Implementations.Syncplay if (group == null) { - _logger.LogWarning("Syncplaymanager HandleRequest: {0} does not belong to any group.", session.Id); + _logger.LogWarning("HandleRequest: {0} does not belong to any group.", session.Id); var error = new GroupUpdate() { @@ -344,7 +351,7 @@ namespace Emby.Server.Implementations.Syncplay return; } - group.HandleRequest(session, request); + group.HandleRequest(session, request, cancellationToken); } } diff --git a/MediaBrowser.Api/Syncplay/SyncplayService.cs b/MediaBrowser.Api/Syncplay/SyncplayService.cs index 2eaf9ce83..4b6e16762 100644 --- a/MediaBrowser.Api/Syncplay/SyncplayService.cs +++ b/MediaBrowser.Api/Syncplay/SyncplayService.cs @@ -1,3 +1,4 @@ +using System.Threading; using System; using System.Collections.Generic; using MediaBrowser.Controller.Configuration; @@ -48,12 +49,19 @@ namespace MediaBrowser.Api.Syncplay public string SessionId { get; set; } } - [Route("/Syncplay/{SessionId}/ListGroups", "POST", Summary = "List Syncplay groups playing same item")] + [Route("/Syncplay/{SessionId}/ListGroups", "POST", Summary = "List Syncplay groups")] [Authenticated] public class SyncplayListGroups : IReturnVoid { [ApiMember(Name = "SessionId", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] public string SessionId { get; set; } + + /// + /// Gets or sets the filter item id. + /// + /// The filter item id. + [ApiMember(Name = "FilterItemId", Description = "Filter by item id", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")] + public string FilterItemId { get; set; } } [Route("/Syncplay/{SessionId}/PlayRequest", "POST", Summary = "Request play in Syncplay group")] @@ -104,8 +112,8 @@ namespace MediaBrowser.Api.Syncplay /// Gets or sets whether this is a buffering or a buffering-done request. /// /// true if buffering is complete; false otherwise. - [ApiMember(Name = "Resume", IsRequired = true, DataType = "bool", ParameterType = "query", Verb = "POST")] - public bool Resume { get; set; } + [ApiMember(Name = "BufferingDone", IsRequired = true, DataType = "bool", ParameterType = "query", Verb = "POST")] + public bool BufferingDone { get; set; } } [Route("/Syncplay/{SessionId}/UpdatePing", "POST", Summary = "Update session ping")] @@ -124,11 +132,6 @@ namespace MediaBrowser.Api.Syncplay /// public class SyncplayService : BaseApiService { - /// - /// The session manager. - /// - private readonly ISessionManager _sessionManager; - /// /// The session context. /// @@ -143,12 +146,10 @@ namespace MediaBrowser.Api.Syncplay ILogger logger, IServerConfigurationManager serverConfigurationManager, IHttpResultFactory httpResultFactory, - ISessionManager sessionManager, ISessionContext sessionContext, ISyncplayManager syncplayManager) : base(logger, serverConfigurationManager, httpResultFactory) { - _sessionManager = sessionManager; _sessionContext = sessionContext; _syncplayManager = syncplayManager; } @@ -160,7 +161,7 @@ namespace MediaBrowser.Api.Syncplay public void Post(SyncplayNewGroup request) { var currentSession = GetSession(_sessionContext); - _syncplayManager.NewGroup(currentSession); + _syncplayManager.NewGroup(currentSession, CancellationToken.None); } /// @@ -174,19 +175,27 @@ namespace MediaBrowser.Api.Syncplay { GroupId = Guid.Parse(request.GroupId) }; - try - { - joinRequest.PlayingItemId = Guid.Parse(request.PlayingItemId); - } - catch (ArgumentNullException) - { - // Do nothing - } - catch (FormatException) + + // Both null and empty strings mean that client isn't playing anything + if (!String.IsNullOrEmpty(request.PlayingItemId)) { - // Do nothing + try + { + joinRequest.PlayingItemId = Guid.Parse(request.PlayingItemId); + } + catch (ArgumentNullException) + { + // Should never happen, but just in case + Logger.LogError("JoinGroup: null value for PlayingItemId. Ignoring request."); + return; + } + catch (FormatException) + { + Logger.LogError("JoinGroup: {0} is not a valid format for PlayingItemId. Ignoring request.", request.PlayingItemId); + return; + } } - _syncplayManager.JoinGroup(currentSession, request.GroupId, joinRequest); + _syncplayManager.JoinGroup(currentSession, request.GroupId, joinRequest, CancellationToken.None); } /// @@ -196,7 +205,7 @@ namespace MediaBrowser.Api.Syncplay public void Post(SyncplayLeaveGroup request) { var currentSession = GetSession(_sessionContext); - _syncplayManager.LeaveGroup(currentSession); + _syncplayManager.LeaveGroup(currentSession, CancellationToken.None); } /// @@ -207,7 +216,23 @@ namespace MediaBrowser.Api.Syncplay public List Post(SyncplayListGroups request) { var currentSession = GetSession(_sessionContext); - return _syncplayManager.ListGroups(currentSession); + var filterItemId = Guid.Empty; + if (!String.IsNullOrEmpty(request.FilterItemId)) + { + try + { + filterItemId = Guid.Parse(request.FilterItemId); + } + catch (ArgumentNullException) + { + Logger.LogWarning("ListGroups: null value for FilterItemId. Ignoring filter."); + } + catch (FormatException) + { + Logger.LogWarning("ListGroups: {0} is not a valid format for FilterItemId. Ignoring filter.", request.FilterItemId); + } + } + return _syncplayManager.ListGroups(currentSession, filterItemId); } /// @@ -221,7 +246,7 @@ namespace MediaBrowser.Api.Syncplay { Type = PlaybackRequestType.Play }; - _syncplayManager.HandleRequest(currentSession, syncplayRequest); + _syncplayManager.HandleRequest(currentSession, syncplayRequest, CancellationToken.None); } /// @@ -235,7 +260,7 @@ namespace MediaBrowser.Api.Syncplay { Type = PlaybackRequestType.Pause }; - _syncplayManager.HandleRequest(currentSession, syncplayRequest); + _syncplayManager.HandleRequest(currentSession, syncplayRequest, CancellationToken.None); } /// @@ -250,7 +275,7 @@ namespace MediaBrowser.Api.Syncplay Type = PlaybackRequestType.Seek, PositionTicks = request.PositionTicks }; - _syncplayManager.HandleRequest(currentSession, syncplayRequest); + _syncplayManager.HandleRequest(currentSession, syncplayRequest, CancellationToken.None); } /// @@ -262,11 +287,11 @@ namespace MediaBrowser.Api.Syncplay var currentSession = GetSession(_sessionContext); var syncplayRequest = new PlaybackRequest() { - Type = request.Resume ? PlaybackRequestType.BufferingDone : PlaybackRequestType.Buffering, + Type = request.BufferingDone ? PlaybackRequestType.BufferingDone : PlaybackRequestType.Buffering, When = DateTime.Parse(request.When), PositionTicks = request.PositionTicks }; - _syncplayManager.HandleRequest(currentSession, syncplayRequest); + _syncplayManager.HandleRequest(currentSession, syncplayRequest, CancellationToken.None); } /// @@ -281,7 +306,7 @@ namespace MediaBrowser.Api.Syncplay Type = PlaybackRequestType.UpdatePing, Ping = Convert.ToInt64(request.Ping) }; - _syncplayManager.HandleRequest(currentSession, syncplayRequest); + _syncplayManager.HandleRequest(currentSession, syncplayRequest, CancellationToken.None); } } } diff --git a/MediaBrowser.Api/Syncplay/TimeSyncService.cs b/MediaBrowser.Api/Syncplay/TimeSyncService.cs index 930968d9f..9a26ffd99 100644 --- a/MediaBrowser.Api/Syncplay/TimeSyncService.cs +++ b/MediaBrowser.Api/Syncplay/TimeSyncService.cs @@ -1,7 +1,6 @@ using System; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Net; -using MediaBrowser.Controller.Session; using MediaBrowser.Model.Services; using MediaBrowser.Model.Syncplay; using Microsoft.Extensions.Logging; @@ -19,16 +18,6 @@ namespace MediaBrowser.Api.Syncplay /// public class TimeSyncService : BaseApiService { - /// - /// The session manager. - /// - private readonly ISessionManager _sessionManager; - - /// - /// The session context. - /// - private readonly ISessionContext _sessionContext; - public TimeSyncService( ILogger logger, IServerConfigurationManager serverConfigurationManager, @@ -55,7 +44,7 @@ namespace MediaBrowser.Api.Syncplay var responseTransmissionTime = DateTime.UtcNow.ToUniversalTime().ToString("o"); response.ResponseTransmissionTime = responseTransmissionTime; - // Implementing NTP on such a high level results in this useless + // Implementing NTP on such a high level results in this useless // information being sent. On the other hand it enables future additions. return response; } diff --git a/MediaBrowser.Controller/Syncplay/GroupInfo.cs b/MediaBrowser.Controller/Syncplay/GroupInfo.cs index 8e886a2cb..c01fead83 100644 --- a/MediaBrowser.Controller/Syncplay/GroupInfo.cs +++ b/MediaBrowser.Controller/Syncplay/GroupInfo.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Concurrent; using System.Collections.Generic; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Session; @@ -9,6 +8,9 @@ namespace MediaBrowser.Controller.Syncplay /// /// Class GroupInfo. /// + /// + /// Class is not thread-safe, external locking is required when accessing methods. + /// public class GroupInfo { /// @@ -49,8 +51,8 @@ namespace MediaBrowser.Controller.Syncplay /// Gets the participants. /// /// The participants, or members of the group. - public readonly ConcurrentDictionary Participants = - new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); + public readonly Dictionary Participants = + new Dictionary(StringComparer.OrdinalIgnoreCase); /// /// Checks if a session is in this group. diff --git a/MediaBrowser.Controller/Syncplay/ISyncplayController.cs b/MediaBrowser.Controller/Syncplay/ISyncplayController.cs index 5b08eac0a..34eae4062 100644 --- a/MediaBrowser.Controller/Syncplay/ISyncplayController.cs +++ b/MediaBrowser.Controller/Syncplay/ISyncplayController.cs @@ -1,4 +1,5 @@ using System; +using System.Threading; using MediaBrowser.Controller.Session; using MediaBrowser.Model.Syncplay; @@ -31,27 +32,31 @@ namespace MediaBrowser.Controller.Syncplay /// Initializes the group with the session's info. /// /// The session. - void InitGroup(SessionInfo session); + /// The cancellation token. + void InitGroup(SessionInfo session, CancellationToken cancellationToken); /// /// Adds the session to the group. /// /// The session. /// The request. - void SessionJoin(SessionInfo session, JoinGroupRequest request); + /// The cancellation token. + void SessionJoin(SessionInfo session, JoinGroupRequest request, CancellationToken cancellationToken); /// /// Removes the session from the group. /// /// The session. - void SessionLeave(SessionInfo session); + /// The cancellation token. + void SessionLeave(SessionInfo session, CancellationToken cancellationToken); /// /// Handles the requested action by the session. /// /// The session. /// The requested action. - void HandleRequest(SessionInfo session, PlaybackRequest request); + /// The cancellation token. + void HandleRequest(SessionInfo session, PlaybackRequest request, CancellationToken cancellationToken); /// /// Gets the info about the group for the clients. diff --git a/MediaBrowser.Controller/Syncplay/ISyncplayManager.cs b/MediaBrowser.Controller/Syncplay/ISyncplayManager.cs index 433d6d8bc..fbc208d27 100644 --- a/MediaBrowser.Controller/Syncplay/ISyncplayManager.cs +++ b/MediaBrowser.Controller/Syncplay/ISyncplayManager.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Threading; using MediaBrowser.Controller.Session; using MediaBrowser.Model.Syncplay; @@ -14,7 +15,8 @@ namespace MediaBrowser.Controller.Syncplay /// Creates a new group. /// /// The session that's creating the group. - void NewGroup(SessionInfo session); + /// The cancellation token. + void NewGroup(SessionInfo session, CancellationToken cancellationToken); /// /// Adds the session to a group. @@ -22,27 +24,31 @@ namespace MediaBrowser.Controller.Syncplay /// The session. /// The group id. /// The request. - void JoinGroup(SessionInfo session, string groupId, JoinGroupRequest request); + /// The cancellation token. + void JoinGroup(SessionInfo session, string groupId, JoinGroupRequest request, CancellationToken cancellationToken); /// /// Removes the session from a group. /// /// The session. - void LeaveGroup(SessionInfo session); + /// The cancellation token. + void LeaveGroup(SessionInfo session, CancellationToken cancellationToken); /// /// Gets list of available groups for a session. /// /// The session. + /// The item id to filter by. /// The list of available groups. - List ListGroups(SessionInfo session); + List ListGroups(SessionInfo session, Guid filterItemId); /// /// Handle a request by a session in a group. /// /// The session. /// The request. - void HandleRequest(SessionInfo session, PlaybackRequest request); + /// The cancellation token. + void HandleRequest(SessionInfo session, PlaybackRequest request, CancellationToken cancellationToken); /// /// Maps a session to a group. diff --git a/MediaBrowser.Model/Syncplay/GroupUpdateType.cs b/MediaBrowser.Model/Syncplay/GroupUpdateType.cs index 20e76932d..9f40f9577 100644 --- a/MediaBrowser.Model/Syncplay/GroupUpdateType.cs +++ b/MediaBrowser.Model/Syncplay/GroupUpdateType.cs @@ -30,13 +30,13 @@ namespace MediaBrowser.Model.Syncplay /// PrepareSession, /// - /// The not-in-group error. Tells a user that it doesn't belong to a group. + /// The not-in-group error. Tells a user that they don't belong to a group. /// NotInGroup, /// - /// The group-not-joined error. Sent when a request to join a group fails. + /// The group-does-not-exist error. Sent when trying to join a non-existing group. /// - GroupNotJoined, + GroupDoesNotExist, /// /// The create-group-denied error. Sent when a user tries to create a group without required permissions. /// diff --git a/MediaBrowser.Model/Syncplay/PlaybackRequest.cs b/MediaBrowser.Model/Syncplay/PlaybackRequest.cs index cae769db0..ba97641f6 100644 --- a/MediaBrowser.Model/Syncplay/PlaybackRequest.cs +++ b/MediaBrowser.Model/Syncplay/PlaybackRequest.cs @@ -11,7 +11,7 @@ namespace MediaBrowser.Model.Syncplay /// Gets or sets the request type. /// /// The request type. - public PlaybackRequestType Type; + public PlaybackRequestType Type { get; set; } /// /// Gets or sets when the request has been made by the client. -- cgit v1.2.3 From 8a6ec2fb713cb77e91d2fceea8b4fce8e7d17395 Mon Sep 17 00:00:00 2001 From: gion Date: Wed, 6 May 2020 23:42:53 +0200 Subject: Rename Syncplay to SyncPlay --- Emby.Server.Implementations/ApplicationHost.cs | 6 +- .../Session/SessionManager.cs | 10 +- .../SyncPlay/SyncPlayController.cs | 548 +++++++++++++++++++++ .../SyncPlay/SyncPlayManager.cs | 385 +++++++++++++++ .../Syncplay/SyncplayController.cs | 548 --------------------- .../Syncplay/SyncplayManager.cs | 385 --------------- MediaBrowser.Api/SyncPlay/SyncPlayService.cs | 312 ++++++++++++ MediaBrowser.Api/SyncPlay/TimeSyncService.cs | 52 ++ MediaBrowser.Api/Syncplay/SyncplayService.cs | 312 ------------ MediaBrowser.Api/Syncplay/TimeSyncService.cs | 52 -- MediaBrowser.Controller/Session/ISessionManager.cs | 10 +- MediaBrowser.Controller/SyncPlay/GroupInfo.cs | 150 ++++++ MediaBrowser.Controller/SyncPlay/GroupMember.cs | 28 ++ .../SyncPlay/ISyncPlayController.cs | 67 +++ .../SyncPlay/ISyncPlayManager.cs | 69 +++ MediaBrowser.Controller/Syncplay/GroupInfo.cs | 150 ------ MediaBrowser.Controller/Syncplay/GroupMember.cs | 28 -- .../Syncplay/ISyncplayController.cs | 67 --- .../Syncplay/ISyncplayManager.cs | 69 --- MediaBrowser.Model/Configuration/SyncplayAccess.cs | 6 +- MediaBrowser.Model/SyncPlay/GroupInfoView.cs | 38 ++ MediaBrowser.Model/SyncPlay/GroupUpdate.cs | 26 + MediaBrowser.Model/SyncPlay/GroupUpdateType.cs | 53 ++ MediaBrowser.Model/SyncPlay/JoinGroupRequest.cs | 22 + MediaBrowser.Model/SyncPlay/PlaybackRequest.cs | 34 ++ MediaBrowser.Model/SyncPlay/PlaybackRequestType.cs | 33 ++ MediaBrowser.Model/SyncPlay/SendCommand.cs | 38 ++ MediaBrowser.Model/SyncPlay/SendCommandType.cs | 21 + MediaBrowser.Model/SyncPlay/UtcTimeResponse.cs | 20 + MediaBrowser.Model/Syncplay/GroupInfoView.cs | 38 -- MediaBrowser.Model/Syncplay/GroupUpdate.cs | 26 - MediaBrowser.Model/Syncplay/GroupUpdateType.cs | 53 -- MediaBrowser.Model/Syncplay/JoinGroupRequest.cs | 22 - MediaBrowser.Model/Syncplay/PlaybackRequest.cs | 34 -- MediaBrowser.Model/Syncplay/PlaybackRequestType.cs | 33 -- MediaBrowser.Model/Syncplay/SendCommand.cs | 38 -- MediaBrowser.Model/Syncplay/SendCommandType.cs | 21 - MediaBrowser.Model/Syncplay/UtcTimeResponse.cs | 20 - MediaBrowser.Model/Users/UserPolicy.cs | 8 +- 39 files changed, 1916 insertions(+), 1916 deletions(-) create mode 100644 Emby.Server.Implementations/SyncPlay/SyncPlayController.cs create mode 100644 Emby.Server.Implementations/SyncPlay/SyncPlayManager.cs delete mode 100644 Emby.Server.Implementations/Syncplay/SyncplayController.cs delete mode 100644 Emby.Server.Implementations/Syncplay/SyncplayManager.cs create mode 100644 MediaBrowser.Api/SyncPlay/SyncPlayService.cs create mode 100644 MediaBrowser.Api/SyncPlay/TimeSyncService.cs delete mode 100644 MediaBrowser.Api/Syncplay/SyncplayService.cs delete mode 100644 MediaBrowser.Api/Syncplay/TimeSyncService.cs create mode 100644 MediaBrowser.Controller/SyncPlay/GroupInfo.cs create mode 100644 MediaBrowser.Controller/SyncPlay/GroupMember.cs create mode 100644 MediaBrowser.Controller/SyncPlay/ISyncPlayController.cs create mode 100644 MediaBrowser.Controller/SyncPlay/ISyncPlayManager.cs delete mode 100644 MediaBrowser.Controller/Syncplay/GroupInfo.cs delete mode 100644 MediaBrowser.Controller/Syncplay/GroupMember.cs delete mode 100644 MediaBrowser.Controller/Syncplay/ISyncplayController.cs delete mode 100644 MediaBrowser.Controller/Syncplay/ISyncplayManager.cs create mode 100644 MediaBrowser.Model/SyncPlay/GroupInfoView.cs create mode 100644 MediaBrowser.Model/SyncPlay/GroupUpdate.cs create mode 100644 MediaBrowser.Model/SyncPlay/GroupUpdateType.cs create mode 100644 MediaBrowser.Model/SyncPlay/JoinGroupRequest.cs create mode 100644 MediaBrowser.Model/SyncPlay/PlaybackRequest.cs create mode 100644 MediaBrowser.Model/SyncPlay/PlaybackRequestType.cs create mode 100644 MediaBrowser.Model/SyncPlay/SendCommand.cs create mode 100644 MediaBrowser.Model/SyncPlay/SendCommandType.cs create mode 100644 MediaBrowser.Model/SyncPlay/UtcTimeResponse.cs delete mode 100644 MediaBrowser.Model/Syncplay/GroupInfoView.cs delete mode 100644 MediaBrowser.Model/Syncplay/GroupUpdate.cs delete mode 100644 MediaBrowser.Model/Syncplay/GroupUpdateType.cs delete mode 100644 MediaBrowser.Model/Syncplay/JoinGroupRequest.cs delete mode 100644 MediaBrowser.Model/Syncplay/PlaybackRequest.cs delete mode 100644 MediaBrowser.Model/Syncplay/PlaybackRequestType.cs delete mode 100644 MediaBrowser.Model/Syncplay/SendCommand.cs delete mode 100644 MediaBrowser.Model/Syncplay/SendCommandType.cs delete mode 100644 MediaBrowser.Model/Syncplay/UtcTimeResponse.cs (limited to 'Emby.Server.Implementations/Session') diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 8419014c2..730323c22 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -47,7 +47,7 @@ using Emby.Server.Implementations.Session; using Emby.Server.Implementations.SocketSharp; using Emby.Server.Implementations.TV; using Emby.Server.Implementations.Updates; -using Emby.Server.Implementations.Syncplay; +using Emby.Server.Implementations.SyncPlay; using MediaBrowser.Api; using MediaBrowser.Common; using MediaBrowser.Common.Configuration; @@ -81,7 +81,7 @@ using MediaBrowser.Controller.Session; using MediaBrowser.Controller.Sorting; using MediaBrowser.Controller.Subtitles; using MediaBrowser.Controller.TV; -using MediaBrowser.Controller.Syncplay; +using MediaBrowser.Controller.SyncPlay; using MediaBrowser.LocalMetadata.Savers; using MediaBrowser.MediaEncoding.BdInfo; using MediaBrowser.Model.Activity; @@ -645,7 +645,7 @@ namespace Emby.Server.Implementations serviceCollection.AddSingleton(); - serviceCollection.AddSingleton(); + serviceCollection.AddSingleton(); serviceCollection.AddSingleton(); serviceCollection.AddSingleton(); diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs index 6a64209c1..aab745de4 100644 --- a/Emby.Server.Implementations/Session/SessionManager.cs +++ b/Emby.Server.Implementations/Session/SessionManager.cs @@ -25,7 +25,7 @@ using MediaBrowser.Model.Events; using MediaBrowser.Model.Library; using MediaBrowser.Model.Querying; using MediaBrowser.Model.Session; -using MediaBrowser.Model.Syncplay; +using MediaBrowser.Model.SyncPlay; using Microsoft.Extensions.Logging; namespace Emby.Server.Implementations.Session @@ -1156,19 +1156,19 @@ namespace Emby.Server.Implementations.Session } /// - public async Task SendSyncplayCommand(string sessionId, SendCommand command, CancellationToken cancellationToken) + public async Task SendSyncPlayCommand(string sessionId, SendCommand command, CancellationToken cancellationToken) { CheckDisposed(); var session = GetSessionToRemoteControl(sessionId); - await SendMessageToSession(session, "SyncplayCommand", command, cancellationToken).ConfigureAwait(false); + await SendMessageToSession(session, "SyncPlayCommand", command, cancellationToken).ConfigureAwait(false); } /// - public async Task SendSyncplayGroupUpdate(string sessionId, GroupUpdate command, CancellationToken cancellationToken) + public async Task SendSyncPlayGroupUpdate(string sessionId, GroupUpdate command, CancellationToken cancellationToken) { CheckDisposed(); var session = GetSessionToRemoteControl(sessionId); - await SendMessageToSession(session, "SyncplayGroupUpdate", command, cancellationToken).ConfigureAwait(false); + await SendMessageToSession(session, "SyncPlayGroupUpdate", command, cancellationToken).ConfigureAwait(false); } private IEnumerable TranslateItemForPlayback(Guid id, User user) diff --git a/Emby.Server.Implementations/SyncPlay/SyncPlayController.cs b/Emby.Server.Implementations/SyncPlay/SyncPlayController.cs new file mode 100644 index 000000000..9c9758de1 --- /dev/null +++ b/Emby.Server.Implementations/SyncPlay/SyncPlayController.cs @@ -0,0 +1,548 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using MediaBrowser.Controller.Session; +using MediaBrowser.Controller.SyncPlay; +using MediaBrowser.Model.Session; +using MediaBrowser.Model.SyncPlay; + +namespace Emby.Server.Implementations.SyncPlay +{ + /// + /// Class SyncPlayController. + /// + /// + /// Class is not thread-safe, external locking is required when accessing methods. + /// + public class SyncPlayController : ISyncPlayController, IDisposable + { + /// + /// Used to filter the sessions of a group. + /// + private enum BroadcastType + { + /// + /// All sessions will receive the message. + /// + AllGroup = 0, + /// + /// Only the specified session will receive the message. + /// + CurrentSession = 1, + /// + /// All sessions, except the current one, will receive the message. + /// + AllExceptCurrentSession = 2, + /// + /// Only sessions that are not buffering will receive the message. + /// + AllReady = 3 + } + + /// + /// The session manager. + /// + private readonly ISessionManager _sessionManager; + + /// + /// The SyncPlay manager. + /// + private readonly ISyncPlayManager _syncPlayManager; + + /// + /// The group to manage. + /// + private readonly GroupInfo _group = new GroupInfo(); + + /// + public Guid GetGroupId() => _group.GroupId; + + /// + public Guid GetPlayingItemId() => _group.PlayingItem.Id; + + /// + public bool IsGroupEmpty() => _group.IsEmpty(); + + private bool _disposed = false; + + public SyncPlayController( + ISessionManager sessionManager, + ISyncPlayManager syncPlayManager) + { + _sessionManager = sessionManager; + _syncPlayManager = syncPlayManager; + } + + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + /// + /// Releases unmanaged and optionally managed resources. + /// + /// true to release both managed and unmanaged resources; false to release only unmanaged resources. + protected virtual void Dispose(bool disposing) + { + if (_disposed) + { + return; + } + + _disposed = true; + } + + // TODO: use this somewhere + private void CheckDisposed() + { + if (_disposed) + { + throw new ObjectDisposedException(GetType().Name); + } + } + + /// + /// Converts DateTime to UTC string. + /// + /// The date to convert. + /// The UTC string. + private string DateToUTCString(DateTime date) + { + return date.ToUniversalTime().ToString("o"); + } + + /// + /// Filters sessions of this group. + /// + /// The current session. + /// The filtering type. + /// The array of sessions matching the filter. + private SessionInfo[] FilterSessions(SessionInfo from, BroadcastType type) + { + switch (type) + { + case BroadcastType.CurrentSession: + return new SessionInfo[] { from }; + case BroadcastType.AllGroup: + return _group.Participants.Values.Select( + session => session.Session + ).ToArray(); + case BroadcastType.AllExceptCurrentSession: + return _group.Participants.Values.Select( + session => session.Session + ).Where( + session => !session.Id.Equals(from.Id) + ).ToArray(); + case BroadcastType.AllReady: + return _group.Participants.Values.Where( + session => !session.IsBuffering + ).Select( + session => session.Session + ).ToArray(); + default: + return new SessionInfo[] { }; + } + } + + /// + /// Sends a GroupUpdate message to the interested sessions. + /// + /// The current session. + /// The filtering type. + /// The message to send. + /// The cancellation token. + /// The task. + private Task SendGroupUpdate(SessionInfo from, BroadcastType type, GroupUpdate message, CancellationToken cancellationToken) + { + IEnumerable GetTasks() + { + SessionInfo[] sessions = FilterSessions(from, type); + foreach (var session in sessions) + { + yield return _sessionManager.SendSyncPlayGroupUpdate(session.Id.ToString(), message, cancellationToken); + } + } + + return Task.WhenAll(GetTasks()); + } + + /// + /// Sends a playback command to the interested sessions. + /// + /// The current session. + /// The filtering type. + /// The message to send. + /// The cancellation token. + /// The task. + private Task SendCommand(SessionInfo from, BroadcastType type, SendCommand message, CancellationToken cancellationToken) + { + IEnumerable GetTasks() + { + SessionInfo[] sessions = FilterSessions(from, type); + foreach (var session in sessions) + { + yield return _sessionManager.SendSyncPlayCommand(session.Id.ToString(), message, cancellationToken); + } + } + + return Task.WhenAll(GetTasks()); + } + + /// + /// Builds a new playback command with some default values. + /// + /// The command type. + /// The SendCommand. + private SendCommand NewSyncPlayCommand(SendCommandType type) + { + return new SendCommand() + { + GroupId = _group.GroupId.ToString(), + Command = type, + PositionTicks = _group.PositionTicks, + When = DateToUTCString(_group.LastActivity), + EmittedAt = DateToUTCString(DateTime.UtcNow) + }; + } + + /// + /// Builds a new group update message. + /// + /// The update type. + /// The data to send. + /// The GroupUpdate. + private GroupUpdate NewSyncPlayGroupUpdate(GroupUpdateType type, T data) + { + return new GroupUpdate() + { + GroupId = _group.GroupId.ToString(), + Type = type, + Data = data + }; + } + + /// + public void InitGroup(SessionInfo session, CancellationToken cancellationToken) + { + _group.AddSession(session); + _syncPlayManager.AddSessionToGroup(session, this); + + _group.PlayingItem = session.FullNowPlayingItem; + _group.IsPaused = true; + _group.PositionTicks = session.PlayState.PositionTicks ?? 0; + _group.LastActivity = DateTime.UtcNow; + + var updateSession = NewSyncPlayGroupUpdate(GroupUpdateType.GroupJoined, DateToUTCString(DateTime.UtcNow)); + SendGroupUpdate(session, BroadcastType.CurrentSession, updateSession, cancellationToken); + var pauseCommand = NewSyncPlayCommand(SendCommandType.Pause); + SendCommand(session, BroadcastType.CurrentSession, pauseCommand, cancellationToken); + } + + /// + public void SessionJoin(SessionInfo session, JoinGroupRequest request, CancellationToken cancellationToken) + { + if (session.NowPlayingItem?.Id == _group.PlayingItem.Id && request.PlayingItemId == _group.PlayingItem.Id) + { + _group.AddSession(session); + _syncPlayManager.AddSessionToGroup(session, this); + + var updateSession = NewSyncPlayGroupUpdate(GroupUpdateType.GroupJoined, DateToUTCString(DateTime.UtcNow)); + SendGroupUpdate(session, BroadcastType.CurrentSession, updateSession, cancellationToken); + + var updateOthers = NewSyncPlayGroupUpdate(GroupUpdateType.UserJoined, session.UserName); + SendGroupUpdate(session, BroadcastType.AllExceptCurrentSession, updateOthers, cancellationToken); + + // Client join and play, syncing will happen client side + if (!_group.IsPaused) + { + var playCommand = NewSyncPlayCommand(SendCommandType.Play); + SendCommand(session, BroadcastType.CurrentSession, playCommand, cancellationToken); + } + else + { + var pauseCommand = NewSyncPlayCommand(SendCommandType.Pause); + SendCommand(session, BroadcastType.CurrentSession, pauseCommand, cancellationToken); + } + } + else + { + var playRequest = new PlayRequest(); + playRequest.ItemIds = new Guid[] { _group.PlayingItem.Id }; + playRequest.StartPositionTicks = _group.PositionTicks; + var update = NewSyncPlayGroupUpdate(GroupUpdateType.PrepareSession, playRequest); + SendGroupUpdate(session, BroadcastType.CurrentSession, update, cancellationToken); + } + } + + /// + public void SessionLeave(SessionInfo session, CancellationToken cancellationToken) + { + _group.RemoveSession(session); + _syncPlayManager.RemoveSessionFromGroup(session, this); + + var updateSession = NewSyncPlayGroupUpdate(GroupUpdateType.GroupLeft, _group.PositionTicks); + SendGroupUpdate(session, BroadcastType.CurrentSession, updateSession, cancellationToken); + + var updateOthers = NewSyncPlayGroupUpdate(GroupUpdateType.UserLeft, session.UserName); + SendGroupUpdate(session, BroadcastType.AllExceptCurrentSession, updateOthers, cancellationToken); + } + + /// + public void HandleRequest(SessionInfo session, PlaybackRequest request, CancellationToken cancellationToken) + { + // The server's job is to mantain a consistent state to which clients refer to, + // as also to notify clients of state changes. + // The actual syncing of media playback happens client side. + // Clients are aware of the server's time and use it to sync. + switch (request.Type) + { + case PlaybackRequestType.Play: + HandlePlayRequest(session, request, cancellationToken); + break; + case PlaybackRequestType.Pause: + HandlePauseRequest(session, request, cancellationToken); + break; + case PlaybackRequestType.Seek: + HandleSeekRequest(session, request, cancellationToken); + break; + case PlaybackRequestType.Buffering: + HandleBufferingRequest(session, request, cancellationToken); + break; + case PlaybackRequestType.BufferingDone: + HandleBufferingDoneRequest(session, request, cancellationToken); + break; + case PlaybackRequestType.UpdatePing: + HandlePingUpdateRequest(session, request); + break; + } + } + + /// + /// Handles a play action requested by a session. + /// + /// The session. + /// The play action. + /// The cancellation token. + private void HandlePlayRequest(SessionInfo session, PlaybackRequest request, CancellationToken cancellationToken) + { + if (_group.IsPaused) + { + // Pick a suitable time that accounts for latency + var delay = _group.GetHighestPing() * 2; + delay = delay < _group.DefaulPing ? _group.DefaulPing : delay; + + // Unpause group and set starting point in future + // Clients will start playback at LastActivity (datetime) from PositionTicks (playback position) + // The added delay does not guarantee, of course, that the command will be received in time + // Playback synchronization will mainly happen client side + _group.IsPaused = false; + _group.LastActivity = DateTime.UtcNow.AddMilliseconds( + delay + ); + + var command = NewSyncPlayCommand(SendCommandType.Play); + SendCommand(session, BroadcastType.AllGroup, command, cancellationToken); + } + else + { + // Client got lost, sending current state + var command = NewSyncPlayCommand(SendCommandType.Play); + SendCommand(session, BroadcastType.CurrentSession, command, cancellationToken); + } + } + + /// + /// Handles a pause action requested by a session. + /// + /// The session. + /// The pause action. + /// The cancellation token. + private void HandlePauseRequest(SessionInfo session, PlaybackRequest request, CancellationToken cancellationToken) + { + if (!_group.IsPaused) + { + // Pause group and compute the media playback position + _group.IsPaused = true; + var currentTime = DateTime.UtcNow; + var elapsedTime = currentTime - _group.LastActivity; + _group.LastActivity = currentTime; + // Seek only if playback actually started + // (a pause request may be issued during the delay added to account for latency) + _group.PositionTicks += elapsedTime.Ticks > 0 ? elapsedTime.Ticks : 0; + + var command = NewSyncPlayCommand(SendCommandType.Pause); + SendCommand(session, BroadcastType.AllGroup, command, cancellationToken); + } + else + { + // Client got lost, sending current state + var command = NewSyncPlayCommand(SendCommandType.Pause); + SendCommand(session, BroadcastType.CurrentSession, command, cancellationToken); + } + } + + /// + /// Handles a seek action requested by a session. + /// + /// The session. + /// The seek action. + /// The cancellation token. + private void HandleSeekRequest(SessionInfo session, PlaybackRequest request, CancellationToken cancellationToken) + { + // Sanitize PositionTicks + var ticks = SanitizePositionTicks(request.PositionTicks); + + // Pause and seek + _group.IsPaused = true; + _group.PositionTicks = ticks; + _group.LastActivity = DateTime.UtcNow; + + var command = NewSyncPlayCommand(SendCommandType.Seek); + SendCommand(session, BroadcastType.AllGroup, command, cancellationToken); + } + + /// + /// Handles a buffering action requested by a session. + /// + /// The session. + /// The buffering action. + /// The cancellation token. + private void HandleBufferingRequest(SessionInfo session, PlaybackRequest request, CancellationToken cancellationToken) + { + if (!_group.IsPaused) + { + // Pause group and compute the media playback position + _group.IsPaused = true; + var currentTime = DateTime.UtcNow; + var elapsedTime = currentTime - _group.LastActivity; + _group.LastActivity = currentTime; + _group.PositionTicks += elapsedTime.Ticks > 0 ? elapsedTime.Ticks : 0; + + _group.SetBuffering(session, true); + + // Send pause command to all non-buffering sessions + var command = NewSyncPlayCommand(SendCommandType.Pause); + SendCommand(session, BroadcastType.AllReady, command, cancellationToken); + + var updateOthers = NewSyncPlayGroupUpdate(GroupUpdateType.GroupWait, session.UserName); + SendGroupUpdate(session, BroadcastType.AllExceptCurrentSession, updateOthers, cancellationToken); + } + else + { + // Client got lost, sending current state + var command = NewSyncPlayCommand(SendCommandType.Pause); + SendCommand(session, BroadcastType.CurrentSession, command, cancellationToken); + } + } + + /// + /// Handles a buffering-done action requested by a session. + /// + /// The session. + /// The buffering-done action. + /// The cancellation token. + private void HandleBufferingDoneRequest(SessionInfo session, PlaybackRequest request, CancellationToken cancellationToken) + { + if (_group.IsPaused) + { + _group.SetBuffering(session, false); + + var requestTicks = SanitizePositionTicks(request.PositionTicks); + + var when = request.When ?? DateTime.UtcNow; + var currentTime = DateTime.UtcNow; + var elapsedTime = currentTime - when; + var clientPosition = TimeSpan.FromTicks(requestTicks) + elapsedTime; + var delay = _group.PositionTicks - clientPosition.Ticks; + + if (_group.IsBuffering()) + { + // Others are still buffering, tell this client to pause when ready + var command = NewSyncPlayCommand(SendCommandType.Pause); + var pauseAtTime = currentTime.AddMilliseconds(delay); + command.When = DateToUTCString(pauseAtTime); + SendCommand(session, BroadcastType.CurrentSession, command, cancellationToken); + } + else + { + // Let other clients resume as soon as the buffering client catches up + _group.IsPaused = false; + + if (delay > _group.GetHighestPing() * 2) + { + // Client that was buffering is recovering, notifying others to resume + _group.LastActivity = currentTime.AddMilliseconds( + delay + ); + var command = NewSyncPlayCommand(SendCommandType.Play); + SendCommand(session, BroadcastType.AllExceptCurrentSession, command, cancellationToken); + } + else + { + // Client, that was buffering, resumed playback but did not update others in time + delay = _group.GetHighestPing() * 2; + delay = delay < _group.DefaulPing ? _group.DefaulPing : delay; + + _group.LastActivity = currentTime.AddMilliseconds( + delay + ); + + var command = NewSyncPlayCommand(SendCommandType.Play); + SendCommand(session, BroadcastType.AllGroup, command, cancellationToken); + } + } + } + else + { + // Group was not waiting, make sure client has latest state + var command = NewSyncPlayCommand(SendCommandType.Play); + SendCommand(session, BroadcastType.CurrentSession, command, cancellationToken); + } + } + + /// + /// Sanitizes the PositionTicks, considers the current playing item when available. + /// + /// The PositionTicks. + /// The sanitized PositionTicks. + private long SanitizePositionTicks(long? positionTicks) + { + var ticks = positionTicks ?? 0; + ticks = ticks >= 0 ? ticks : 0; + if (_group.PlayingItem != null) + { + var runTimeTicks = _group.PlayingItem.RunTimeTicks ?? 0; + ticks = ticks > runTimeTicks ? runTimeTicks : ticks; + } + return ticks; + } + + /// + /// Updates ping of a session. + /// + /// The session. + /// The update. + private void HandlePingUpdateRequest(SessionInfo session, PlaybackRequest request) + { + // Collected pings are used to account for network latency when unpausing playback + _group.UpdatePing(session, request.Ping ?? _group.DefaulPing); + } + + /// + public GroupInfoView GetInfo() + { + return new GroupInfoView() + { + GroupId = GetGroupId().ToString(), + PlayingItemName = _group.PlayingItem.Name, + PlayingItemId = _group.PlayingItem.Id.ToString(), + PositionTicks = _group.PositionTicks, + Participants = _group.Participants.Values.Select(session => session.Session.UserName).Distinct().ToArray() + }; + } + } +} diff --git a/Emby.Server.Implementations/SyncPlay/SyncPlayManager.cs b/Emby.Server.Implementations/SyncPlay/SyncPlayManager.cs new file mode 100644 index 000000000..d3197d97b --- /dev/null +++ b/Emby.Server.Implementations/SyncPlay/SyncPlayManager.cs @@ -0,0 +1,385 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Threading; +using Microsoft.Extensions.Logging; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Session; +using MediaBrowser.Controller.SyncPlay; +using MediaBrowser.Model.Configuration; +using MediaBrowser.Model.SyncPlay; + +namespace Emby.Server.Implementations.SyncPlay +{ + /// + /// Class SyncPlayManager. + /// + public class SyncPlayManager : ISyncPlayManager, IDisposable + { + /// + /// The logger. + /// + private readonly ILogger _logger; + + /// + /// The user manager. + /// + private readonly IUserManager _userManager; + + /// + /// The session manager. + /// + private readonly ISessionManager _sessionManager; + + /// + /// The library manager. + /// + private readonly ILibraryManager _libraryManager; + + /// + /// The map between sessions and groups. + /// + private readonly Dictionary _sessionToGroupMap = + new Dictionary(StringComparer.OrdinalIgnoreCase); + + /// + /// The groups. + /// + private readonly Dictionary _groups = + new Dictionary(StringComparer.OrdinalIgnoreCase); + + /// + /// Lock used for accesing any group. + /// + private readonly object _groupsLock = new object(); + + private bool _disposed = false; + + public SyncPlayManager( + ILogger logger, + IUserManager userManager, + ISessionManager sessionManager, + ILibraryManager libraryManager) + { + _logger = logger; + _userManager = userManager; + _sessionManager = sessionManager; + _libraryManager = libraryManager; + + _sessionManager.SessionEnded += OnSessionManagerSessionEnded; + _sessionManager.PlaybackStopped += OnSessionManagerPlaybackStopped; + } + + /// + /// Gets all groups. + /// + /// All groups. + public IEnumerable Groups => _groups.Values; + + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + /// + /// Releases unmanaged and optionally managed resources. + /// + /// true to release both managed and unmanaged resources; false to release only unmanaged resources. + protected virtual void Dispose(bool disposing) + { + if (_disposed) + { + return; + } + + _sessionManager.SessionEnded -= OnSessionManagerSessionEnded; + _sessionManager.PlaybackStopped -= OnSessionManagerPlaybackStopped; + + _disposed = true; + } + + private void CheckDisposed() + { + if (_disposed) + { + throw new ObjectDisposedException(GetType().Name); + } + } + + private void OnSessionManagerSessionEnded(object sender, SessionEventArgs e) + { + var session = e.SessionInfo; + if (!IsSessionInGroup(session)) return; + LeaveGroup(session, CancellationToken.None); + } + + private void OnSessionManagerPlaybackStopped(object sender, PlaybackStopEventArgs e) + { + var session = e.Session; + if (!IsSessionInGroup(session)) return; + LeaveGroup(session, CancellationToken.None); + } + + private bool IsSessionInGroup(SessionInfo session) + { + return _sessionToGroupMap.ContainsKey(session.Id); + } + + private bool HasAccessToItem(User user, Guid itemId) + { + var item = _libraryManager.GetItemById(itemId); + + // Check ParentalRating access + var hasParentalRatingAccess = true; + if (user.Policy.MaxParentalRating.HasValue) + { + hasParentalRatingAccess = item.InheritedParentalRatingValue <= user.Policy.MaxParentalRating; + } + + if (!user.Policy.EnableAllFolders && hasParentalRatingAccess) + { + var collections = _libraryManager.GetCollectionFolders(item).Select( + folder => folder.Id.ToString("N", CultureInfo.InvariantCulture) + ); + var intersect = collections.Intersect(user.Policy.EnabledFolders); + return intersect.Any(); + } + else + { + return hasParentalRatingAccess; + } + } + + private Guid? GetSessionGroup(SessionInfo session) + { + ISyncPlayController group; + _sessionToGroupMap.TryGetValue(session.Id, out group); + if (group != null) + { + return group.GetGroupId(); + } + else + { + return null; + } + } + + /// + public void NewGroup(SessionInfo session, CancellationToken cancellationToken) + { + var user = _userManager.GetUserById(session.UserId); + + if (user.Policy.SyncPlayAccess != SyncPlayAccess.CreateAndJoinGroups) + { + _logger.LogWarning("NewGroup: {0} does not have permission to create groups.", session.Id); + + var error = new GroupUpdate() + { + Type = GroupUpdateType.CreateGroupDenied + }; + _sessionManager.SendSyncPlayGroupUpdate(session.Id.ToString(), error, CancellationToken.None); + return; + } + + lock (_groupsLock) + { + if (IsSessionInGroup(session)) + { + LeaveGroup(session, cancellationToken); + } + + var group = new SyncPlayController(_sessionManager, this); + _groups[group.GetGroupId().ToString()] = group; + + group.InitGroup(session, cancellationToken); + } + } + + /// + public void JoinGroup(SessionInfo session, string groupId, JoinGroupRequest request, CancellationToken cancellationToken) + { + var user = _userManager.GetUserById(session.UserId); + + if (user.Policy.SyncPlayAccess == SyncPlayAccess.None) + { + _logger.LogWarning("JoinGroup: {0} does not have access to SyncPlay.", session.Id); + + var error = new GroupUpdate() + { + Type = GroupUpdateType.JoinGroupDenied + }; + _sessionManager.SendSyncPlayGroupUpdate(session.Id.ToString(), error, CancellationToken.None); + return; + } + + lock (_groupsLock) + { + ISyncPlayController group; + _groups.TryGetValue(groupId, out group); + + if (group == null) + { + _logger.LogWarning("JoinGroup: {0} tried to join group {0} that does not exist.", session.Id, groupId); + + var error = new GroupUpdate() + { + Type = GroupUpdateType.GroupDoesNotExist + }; + _sessionManager.SendSyncPlayGroupUpdate(session.Id.ToString(), error, CancellationToken.None); + return; + } + + if (!HasAccessToItem(user, group.GetPlayingItemId())) + { + _logger.LogWarning("JoinGroup: {0} does not have access to {1}.", session.Id, group.GetPlayingItemId()); + + var error = new GroupUpdate() + { + GroupId = group.GetGroupId().ToString(), + Type = GroupUpdateType.LibraryAccessDenied + }; + _sessionManager.SendSyncPlayGroupUpdate(session.Id.ToString(), error, CancellationToken.None); + return; + } + + if (IsSessionInGroup(session)) + { + if (GetSessionGroup(session).Equals(groupId)) return; + LeaveGroup(session, cancellationToken); + } + + group.SessionJoin(session, request, cancellationToken); + } + } + + /// + public void LeaveGroup(SessionInfo session, CancellationToken cancellationToken) + { + // TODO: determine what happens to users that are in a group and get their permissions revoked + lock (_groupsLock) + { + ISyncPlayController group; + _sessionToGroupMap.TryGetValue(session.Id, out group); + + if (group == null) + { + _logger.LogWarning("LeaveGroup: {0} does not belong to any group.", session.Id); + + var error = new GroupUpdate() + { + Type = GroupUpdateType.NotInGroup + }; + _sessionManager.SendSyncPlayGroupUpdate(session.Id.ToString(), error, CancellationToken.None); + return; + } + + group.SessionLeave(session, cancellationToken); + + if (group.IsGroupEmpty()) + { + _logger.LogInformation("LeaveGroup: removing empty group {0}.", group.GetGroupId()); + _groups.Remove(group.GetGroupId().ToString(), out _); + } + } + } + + /// + public List ListGroups(SessionInfo session, Guid filterItemId) + { + var user = _userManager.GetUserById(session.UserId); + + if (user.Policy.SyncPlayAccess == SyncPlayAccess.None) + { + return new List(); + } + + // Filter by item if requested + if (!filterItemId.Equals(Guid.Empty)) + { + return _groups.Values.Where( + group => group.GetPlayingItemId().Equals(filterItemId) && HasAccessToItem(user, group.GetPlayingItemId()) + ).Select( + group => group.GetInfo() + ).ToList(); + } + // Otherwise show all available groups + else + { + return _groups.Values.Where( + group => HasAccessToItem(user, group.GetPlayingItemId()) + ).Select( + group => group.GetInfo() + ).ToList(); + } + } + + /// + public void HandleRequest(SessionInfo session, PlaybackRequest request, CancellationToken cancellationToken) + { + var user = _userManager.GetUserById(session.UserId); + + if (user.Policy.SyncPlayAccess == SyncPlayAccess.None) + { + _logger.LogWarning("HandleRequest: {0} does not have access to SyncPlay.", session.Id); + + var error = new GroupUpdate() + { + Type = GroupUpdateType.JoinGroupDenied + }; + _sessionManager.SendSyncPlayGroupUpdate(session.Id.ToString(), error, CancellationToken.None); + return; + } + + lock (_groupsLock) + { + ISyncPlayController group; + _sessionToGroupMap.TryGetValue(session.Id, out group); + + if (group == null) + { + _logger.LogWarning("HandleRequest: {0} does not belong to any group.", session.Id); + + var error = new GroupUpdate() + { + Type = GroupUpdateType.NotInGroup + }; + _sessionManager.SendSyncPlayGroupUpdate(session.Id.ToString(), error, CancellationToken.None); + return; + } + + group.HandleRequest(session, request, cancellationToken); + } + } + + /// + public void AddSessionToGroup(SessionInfo session, ISyncPlayController group) + { + if (IsSessionInGroup(session)) + { + throw new InvalidOperationException("Session in other group already!"); + } + _sessionToGroupMap[session.Id] = group; + } + + /// + public void RemoveSessionFromGroup(SessionInfo session, ISyncPlayController group) + { + if (!IsSessionInGroup(session)) + { + throw new InvalidOperationException("Session not in any group!"); + } + + ISyncPlayController tempGroup; + _sessionToGroupMap.Remove(session.Id, out tempGroup); + + if (!tempGroup.GetGroupId().Equals(group.GetGroupId())) + { + throw new InvalidOperationException("Session was in wrong group!"); + } + } + } +} diff --git a/Emby.Server.Implementations/Syncplay/SyncplayController.cs b/Emby.Server.Implementations/Syncplay/SyncplayController.cs deleted file mode 100644 index 8cc3d1fac..000000000 --- a/Emby.Server.Implementations/Syncplay/SyncplayController.cs +++ /dev/null @@ -1,548 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using MediaBrowser.Controller.Session; -using MediaBrowser.Controller.Syncplay; -using MediaBrowser.Model.Session; -using MediaBrowser.Model.Syncplay; - -namespace Emby.Server.Implementations.Syncplay -{ - /// - /// Class SyncplayController. - /// - /// - /// Class is not thread-safe, external locking is required when accessing methods. - /// - public class SyncplayController : ISyncplayController, IDisposable - { - /// - /// Used to filter the sessions of a group. - /// - private enum BroadcastType - { - /// - /// All sessions will receive the message. - /// - AllGroup = 0, - /// - /// Only the specified session will receive the message. - /// - CurrentSession = 1, - /// - /// All sessions, except the current one, will receive the message. - /// - AllExceptCurrentSession = 2, - /// - /// Only sessions that are not buffering will receive the message. - /// - AllReady = 3 - } - - /// - /// The session manager. - /// - private readonly ISessionManager _sessionManager; - - /// - /// The syncplay manager. - /// - private readonly ISyncplayManager _syncplayManager; - - /// - /// The group to manage. - /// - private readonly GroupInfo _group = new GroupInfo(); - - /// - public Guid GetGroupId() => _group.GroupId; - - /// - public Guid GetPlayingItemId() => _group.PlayingItem.Id; - - /// - public bool IsGroupEmpty() => _group.IsEmpty(); - - private bool _disposed = false; - - public SyncplayController( - ISessionManager sessionManager, - ISyncplayManager syncplayManager) - { - _sessionManager = sessionManager; - _syncplayManager = syncplayManager; - } - - /// - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - /// - /// Releases unmanaged and optionally managed resources. - /// - /// true to release both managed and unmanaged resources; false to release only unmanaged resources. - protected virtual void Dispose(bool disposing) - { - if (_disposed) - { - return; - } - - _disposed = true; - } - - // TODO: use this somewhere - private void CheckDisposed() - { - if (_disposed) - { - throw new ObjectDisposedException(GetType().Name); - } - } - - /// - /// Converts DateTime to UTC string. - /// - /// The date to convert. - /// The UTC string. - private string DateToUTCString(DateTime date) - { - return date.ToUniversalTime().ToString("o"); - } - - /// - /// Filters sessions of this group. - /// - /// The current session. - /// The filtering type. - /// The array of sessions matching the filter. - private SessionInfo[] FilterSessions(SessionInfo from, BroadcastType type) - { - switch (type) - { - case BroadcastType.CurrentSession: - return new SessionInfo[] { from }; - case BroadcastType.AllGroup: - return _group.Participants.Values.Select( - session => session.Session - ).ToArray(); - case BroadcastType.AllExceptCurrentSession: - return _group.Participants.Values.Select( - session => session.Session - ).Where( - session => !session.Id.Equals(from.Id) - ).ToArray(); - case BroadcastType.AllReady: - return _group.Participants.Values.Where( - session => !session.IsBuffering - ).Select( - session => session.Session - ).ToArray(); - default: - return new SessionInfo[] { }; - } - } - - /// - /// Sends a GroupUpdate message to the interested sessions. - /// - /// The current session. - /// The filtering type. - /// The message to send. - /// The cancellation token. - /// The task. - private Task SendGroupUpdate(SessionInfo from, BroadcastType type, GroupUpdate message, CancellationToken cancellationToken) - { - IEnumerable GetTasks() - { - SessionInfo[] sessions = FilterSessions(from, type); - foreach (var session in sessions) - { - yield return _sessionManager.SendSyncplayGroupUpdate(session.Id.ToString(), message, cancellationToken); - } - } - - return Task.WhenAll(GetTasks()); - } - - /// - /// Sends a playback command to the interested sessions. - /// - /// The current session. - /// The filtering type. - /// The message to send. - /// The cancellation token. - /// The task. - private Task SendCommand(SessionInfo from, BroadcastType type, SendCommand message, CancellationToken cancellationToken) - { - IEnumerable GetTasks() - { - SessionInfo[] sessions = FilterSessions(from, type); - foreach (var session in sessions) - { - yield return _sessionManager.SendSyncplayCommand(session.Id.ToString(), message, cancellationToken); - } - } - - return Task.WhenAll(GetTasks()); - } - - /// - /// Builds a new playback command with some default values. - /// - /// The command type. - /// The SendCommand. - private SendCommand NewSyncplayCommand(SendCommandType type) - { - return new SendCommand() - { - GroupId = _group.GroupId.ToString(), - Command = type, - PositionTicks = _group.PositionTicks, - When = DateToUTCString(_group.LastActivity), - EmittedAt = DateToUTCString(DateTime.UtcNow) - }; - } - - /// - /// Builds a new group update message. - /// - /// The update type. - /// The data to send. - /// The GroupUpdate. - private GroupUpdate NewSyncplayGroupUpdate(GroupUpdateType type, T data) - { - return new GroupUpdate() - { - GroupId = _group.GroupId.ToString(), - Type = type, - Data = data - }; - } - - /// - public void InitGroup(SessionInfo session, CancellationToken cancellationToken) - { - _group.AddSession(session); - _syncplayManager.AddSessionToGroup(session, this); - - _group.PlayingItem = session.FullNowPlayingItem; - _group.IsPaused = true; - _group.PositionTicks = session.PlayState.PositionTicks ?? 0; - _group.LastActivity = DateTime.UtcNow; - - var updateSession = NewSyncplayGroupUpdate(GroupUpdateType.GroupJoined, DateToUTCString(DateTime.UtcNow)); - SendGroupUpdate(session, BroadcastType.CurrentSession, updateSession, cancellationToken); - var pauseCommand = NewSyncplayCommand(SendCommandType.Pause); - SendCommand(session, BroadcastType.CurrentSession, pauseCommand, cancellationToken); - } - - /// - public void SessionJoin(SessionInfo session, JoinGroupRequest request, CancellationToken cancellationToken) - { - if (session.NowPlayingItem?.Id == _group.PlayingItem.Id && request.PlayingItemId == _group.PlayingItem.Id) - { - _group.AddSession(session); - _syncplayManager.AddSessionToGroup(session, this); - - var updateSession = NewSyncplayGroupUpdate(GroupUpdateType.GroupJoined, DateToUTCString(DateTime.UtcNow)); - SendGroupUpdate(session, BroadcastType.CurrentSession, updateSession, cancellationToken); - - var updateOthers = NewSyncplayGroupUpdate(GroupUpdateType.UserJoined, session.UserName); - SendGroupUpdate(session, BroadcastType.AllExceptCurrentSession, updateOthers, cancellationToken); - - // Client join and play, syncing will happen client side - if (!_group.IsPaused) - { - var playCommand = NewSyncplayCommand(SendCommandType.Play); - SendCommand(session, BroadcastType.CurrentSession, playCommand, cancellationToken); - } - else - { - var pauseCommand = NewSyncplayCommand(SendCommandType.Pause); - SendCommand(session, BroadcastType.CurrentSession, pauseCommand, cancellationToken); - } - } - else - { - var playRequest = new PlayRequest(); - playRequest.ItemIds = new Guid[] { _group.PlayingItem.Id }; - playRequest.StartPositionTicks = _group.PositionTicks; - var update = NewSyncplayGroupUpdate(GroupUpdateType.PrepareSession, playRequest); - SendGroupUpdate(session, BroadcastType.CurrentSession, update, cancellationToken); - } - } - - /// - public void SessionLeave(SessionInfo session, CancellationToken cancellationToken) - { - _group.RemoveSession(session); - _syncplayManager.RemoveSessionFromGroup(session, this); - - var updateSession = NewSyncplayGroupUpdate(GroupUpdateType.GroupLeft, _group.PositionTicks); - SendGroupUpdate(session, BroadcastType.CurrentSession, updateSession, cancellationToken); - - var updateOthers = NewSyncplayGroupUpdate(GroupUpdateType.UserLeft, session.UserName); - SendGroupUpdate(session, BroadcastType.AllExceptCurrentSession, updateOthers, cancellationToken); - } - - /// - public void HandleRequest(SessionInfo session, PlaybackRequest request, CancellationToken cancellationToken) - { - // The server's job is to mantain a consistent state to which clients refer to, - // as also to notify clients of state changes. - // The actual syncing of media playback happens client side. - // Clients are aware of the server's time and use it to sync. - switch (request.Type) - { - case PlaybackRequestType.Play: - HandlePlayRequest(session, request, cancellationToken); - break; - case PlaybackRequestType.Pause: - HandlePauseRequest(session, request, cancellationToken); - break; - case PlaybackRequestType.Seek: - HandleSeekRequest(session, request, cancellationToken); - break; - case PlaybackRequestType.Buffering: - HandleBufferingRequest(session, request, cancellationToken); - break; - case PlaybackRequestType.BufferingDone: - HandleBufferingDoneRequest(session, request, cancellationToken); - break; - case PlaybackRequestType.UpdatePing: - HandlePingUpdateRequest(session, request); - break; - } - } - - /// - /// Handles a play action requested by a session. - /// - /// The session. - /// The play action. - /// The cancellation token. - private void HandlePlayRequest(SessionInfo session, PlaybackRequest request, CancellationToken cancellationToken) - { - if (_group.IsPaused) - { - // Pick a suitable time that accounts for latency - var delay = _group.GetHighestPing() * 2; - delay = delay < _group.DefaulPing ? _group.DefaulPing : delay; - - // Unpause group and set starting point in future - // Clients will start playback at LastActivity (datetime) from PositionTicks (playback position) - // The added delay does not guarantee, of course, that the command will be received in time - // Playback synchronization will mainly happen client side - _group.IsPaused = false; - _group.LastActivity = DateTime.UtcNow.AddMilliseconds( - delay - ); - - var command = NewSyncplayCommand(SendCommandType.Play); - SendCommand(session, BroadcastType.AllGroup, command, cancellationToken); - } - else - { - // Client got lost, sending current state - var command = NewSyncplayCommand(SendCommandType.Play); - SendCommand(session, BroadcastType.CurrentSession, command, cancellationToken); - } - } - - /// - /// Handles a pause action requested by a session. - /// - /// The session. - /// The pause action. - /// The cancellation token. - private void HandlePauseRequest(SessionInfo session, PlaybackRequest request, CancellationToken cancellationToken) - { - if (!_group.IsPaused) - { - // Pause group and compute the media playback position - _group.IsPaused = true; - var currentTime = DateTime.UtcNow; - var elapsedTime = currentTime - _group.LastActivity; - _group.LastActivity = currentTime; - // Seek only if playback actually started - // (a pause request may be issued during the delay added to account for latency) - _group.PositionTicks += elapsedTime.Ticks > 0 ? elapsedTime.Ticks : 0; - - var command = NewSyncplayCommand(SendCommandType.Pause); - SendCommand(session, BroadcastType.AllGroup, command, cancellationToken); - } - else - { - // Client got lost, sending current state - var command = NewSyncplayCommand(SendCommandType.Pause); - SendCommand(session, BroadcastType.CurrentSession, command, cancellationToken); - } - } - - /// - /// Handles a seek action requested by a session. - /// - /// The session. - /// The seek action. - /// The cancellation token. - private void HandleSeekRequest(SessionInfo session, PlaybackRequest request, CancellationToken cancellationToken) - { - // Sanitize PositionTicks - var ticks = SanitizePositionTicks(request.PositionTicks); - - // Pause and seek - _group.IsPaused = true; - _group.PositionTicks = ticks; - _group.LastActivity = DateTime.UtcNow; - - var command = NewSyncplayCommand(SendCommandType.Seek); - SendCommand(session, BroadcastType.AllGroup, command, cancellationToken); - } - - /// - /// Handles a buffering action requested by a session. - /// - /// The session. - /// The buffering action. - /// The cancellation token. - private void HandleBufferingRequest(SessionInfo session, PlaybackRequest request, CancellationToken cancellationToken) - { - if (!_group.IsPaused) - { - // Pause group and compute the media playback position - _group.IsPaused = true; - var currentTime = DateTime.UtcNow; - var elapsedTime = currentTime - _group.LastActivity; - _group.LastActivity = currentTime; - _group.PositionTicks += elapsedTime.Ticks > 0 ? elapsedTime.Ticks : 0; - - _group.SetBuffering(session, true); - - // Send pause command to all non-buffering sessions - var command = NewSyncplayCommand(SendCommandType.Pause); - SendCommand(session, BroadcastType.AllReady, command, cancellationToken); - - var updateOthers = NewSyncplayGroupUpdate(GroupUpdateType.GroupWait, session.UserName); - SendGroupUpdate(session, BroadcastType.AllExceptCurrentSession, updateOthers, cancellationToken); - } - else - { - // Client got lost, sending current state - var command = NewSyncplayCommand(SendCommandType.Pause); - SendCommand(session, BroadcastType.CurrentSession, command, cancellationToken); - } - } - - /// - /// Handles a buffering-done action requested by a session. - /// - /// The session. - /// The buffering-done action. - /// The cancellation token. - private void HandleBufferingDoneRequest(SessionInfo session, PlaybackRequest request, CancellationToken cancellationToken) - { - if (_group.IsPaused) - { - _group.SetBuffering(session, false); - - var requestTicks = SanitizePositionTicks(request.PositionTicks); - - var when = request.When ?? DateTime.UtcNow; - var currentTime = DateTime.UtcNow; - var elapsedTime = currentTime - when; - var clientPosition = TimeSpan.FromTicks(requestTicks) + elapsedTime; - var delay = _group.PositionTicks - clientPosition.Ticks; - - if (_group.IsBuffering()) - { - // Others are still buffering, tell this client to pause when ready - var command = NewSyncplayCommand(SendCommandType.Pause); - var pauseAtTime = currentTime.AddMilliseconds(delay); - command.When = DateToUTCString(pauseAtTime); - SendCommand(session, BroadcastType.CurrentSession, command, cancellationToken); - } - else - { - // Let other clients resume as soon as the buffering client catches up - _group.IsPaused = false; - - if (delay > _group.GetHighestPing() * 2) - { - // Client that was buffering is recovering, notifying others to resume - _group.LastActivity = currentTime.AddMilliseconds( - delay - ); - var command = NewSyncplayCommand(SendCommandType.Play); - SendCommand(session, BroadcastType.AllExceptCurrentSession, command, cancellationToken); - } - else - { - // Client, that was buffering, resumed playback but did not update others in time - delay = _group.GetHighestPing() * 2; - delay = delay < _group.DefaulPing ? _group.DefaulPing : delay; - - _group.LastActivity = currentTime.AddMilliseconds( - delay - ); - - var command = NewSyncplayCommand(SendCommandType.Play); - SendCommand(session, BroadcastType.AllGroup, command, cancellationToken); - } - } - } - else - { - // Group was not waiting, make sure client has latest state - var command = NewSyncplayCommand(SendCommandType.Play); - SendCommand(session, BroadcastType.CurrentSession, command, cancellationToken); - } - } - - /// - /// Sanitizes the PositionTicks, considers the current playing item when available. - /// - /// The PositionTicks. - /// The sanitized PositionTicks. - private long SanitizePositionTicks(long? positionTicks) - { - var ticks = positionTicks ?? 0; - ticks = ticks >= 0 ? ticks : 0; - if (_group.PlayingItem != null) - { - var runTimeTicks = _group.PlayingItem.RunTimeTicks ?? 0; - ticks = ticks > runTimeTicks ? runTimeTicks : ticks; - } - return ticks; - } - - /// - /// Updates ping of a session. - /// - /// The session. - /// The update. - private void HandlePingUpdateRequest(SessionInfo session, PlaybackRequest request) - { - // Collected pings are used to account for network latency when unpausing playback - _group.UpdatePing(session, request.Ping ?? _group.DefaulPing); - } - - /// - public GroupInfoView GetInfo() - { - return new GroupInfoView() - { - GroupId = GetGroupId().ToString(), - PlayingItemName = _group.PlayingItem.Name, - PlayingItemId = _group.PlayingItem.Id.ToString(), - PositionTicks = _group.PositionTicks, - Participants = _group.Participants.Values.Select(session => session.Session.UserName).Distinct().ToArray() - }; - } - } -} diff --git a/Emby.Server.Implementations/Syncplay/SyncplayManager.cs b/Emby.Server.Implementations/Syncplay/SyncplayManager.cs deleted file mode 100644 index 7074e2225..000000000 --- a/Emby.Server.Implementations/Syncplay/SyncplayManager.cs +++ /dev/null @@ -1,385 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Linq; -using System.Threading; -using Microsoft.Extensions.Logging; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.Session; -using MediaBrowser.Controller.Syncplay; -using MediaBrowser.Model.Configuration; -using MediaBrowser.Model.Syncplay; - -namespace Emby.Server.Implementations.Syncplay -{ - /// - /// Class SyncplayManager. - /// - public class SyncplayManager : ISyncplayManager, IDisposable - { - /// - /// The logger. - /// - private readonly ILogger _logger; - - /// - /// The user manager. - /// - private readonly IUserManager _userManager; - - /// - /// The session manager. - /// - private readonly ISessionManager _sessionManager; - - /// - /// The library manager. - /// - private readonly ILibraryManager _libraryManager; - - /// - /// The map between sessions and groups. - /// - private readonly Dictionary _sessionToGroupMap = - new Dictionary(StringComparer.OrdinalIgnoreCase); - - /// - /// The groups. - /// - private readonly Dictionary _groups = - new Dictionary(StringComparer.OrdinalIgnoreCase); - - /// - /// Lock used for accesing any group. - /// - private readonly object _groupsLock = new object(); - - private bool _disposed = false; - - public SyncplayManager( - ILogger logger, - IUserManager userManager, - ISessionManager sessionManager, - ILibraryManager libraryManager) - { - _logger = logger; - _userManager = userManager; - _sessionManager = sessionManager; - _libraryManager = libraryManager; - - _sessionManager.SessionEnded += OnSessionManagerSessionEnded; - _sessionManager.PlaybackStopped += OnSessionManagerPlaybackStopped; - } - - /// - /// Gets all groups. - /// - /// All groups. - public IEnumerable Groups => _groups.Values; - - /// - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - /// - /// Releases unmanaged and optionally managed resources. - /// - /// true to release both managed and unmanaged resources; false to release only unmanaged resources. - protected virtual void Dispose(bool disposing) - { - if (_disposed) - { - return; - } - - _sessionManager.SessionEnded -= OnSessionManagerSessionEnded; - _sessionManager.PlaybackStopped -= OnSessionManagerPlaybackStopped; - - _disposed = true; - } - - private void CheckDisposed() - { - if (_disposed) - { - throw new ObjectDisposedException(GetType().Name); - } - } - - private void OnSessionManagerSessionEnded(object sender, SessionEventArgs e) - { - var session = e.SessionInfo; - if (!IsSessionInGroup(session)) return; - LeaveGroup(session, CancellationToken.None); - } - - private void OnSessionManagerPlaybackStopped(object sender, PlaybackStopEventArgs e) - { - var session = e.Session; - if (!IsSessionInGroup(session)) return; - LeaveGroup(session, CancellationToken.None); - } - - private bool IsSessionInGroup(SessionInfo session) - { - return _sessionToGroupMap.ContainsKey(session.Id); - } - - private bool HasAccessToItem(User user, Guid itemId) - { - var item = _libraryManager.GetItemById(itemId); - - // Check ParentalRating access - var hasParentalRatingAccess = true; - if (user.Policy.MaxParentalRating.HasValue) - { - hasParentalRatingAccess = item.InheritedParentalRatingValue <= user.Policy.MaxParentalRating; - } - - if (!user.Policy.EnableAllFolders && hasParentalRatingAccess) - { - var collections = _libraryManager.GetCollectionFolders(item).Select( - folder => folder.Id.ToString("N", CultureInfo.InvariantCulture) - ); - var intersect = collections.Intersect(user.Policy.EnabledFolders); - return intersect.Any(); - } - else - { - return hasParentalRatingAccess; - } - } - - private Guid? GetSessionGroup(SessionInfo session) - { - ISyncplayController group; - _sessionToGroupMap.TryGetValue(session.Id, out group); - if (group != null) - { - return group.GetGroupId(); - } - else - { - return null; - } - } - - /// - public void NewGroup(SessionInfo session, CancellationToken cancellationToken) - { - var user = _userManager.GetUserById(session.UserId); - - if (user.Policy.SyncplayAccess != SyncplayAccess.CreateAndJoinGroups) - { - _logger.LogWarning("NewGroup: {0} does not have permission to create groups.", session.Id); - - var error = new GroupUpdate() - { - Type = GroupUpdateType.CreateGroupDenied - }; - _sessionManager.SendSyncplayGroupUpdate(session.Id.ToString(), error, CancellationToken.None); - return; - } - - lock (_groupsLock) - { - if (IsSessionInGroup(session)) - { - LeaveGroup(session, cancellationToken); - } - - var group = new SyncplayController(_sessionManager, this); - _groups[group.GetGroupId().ToString()] = group; - - group.InitGroup(session, cancellationToken); - } - } - - /// - public void JoinGroup(SessionInfo session, string groupId, JoinGroupRequest request, CancellationToken cancellationToken) - { - var user = _userManager.GetUserById(session.UserId); - - if (user.Policy.SyncplayAccess == SyncplayAccess.None) - { - _logger.LogWarning("JoinGroup: {0} does not have access to Syncplay.", session.Id); - - var error = new GroupUpdate() - { - Type = GroupUpdateType.JoinGroupDenied - }; - _sessionManager.SendSyncplayGroupUpdate(session.Id.ToString(), error, CancellationToken.None); - return; - } - - lock (_groupsLock) - { - ISyncplayController group; - _groups.TryGetValue(groupId, out group); - - if (group == null) - { - _logger.LogWarning("JoinGroup: {0} tried to join group {0} that does not exist.", session.Id, groupId); - - var error = new GroupUpdate() - { - Type = GroupUpdateType.GroupDoesNotExist - }; - _sessionManager.SendSyncplayGroupUpdate(session.Id.ToString(), error, CancellationToken.None); - return; - } - - if (!HasAccessToItem(user, group.GetPlayingItemId())) - { - _logger.LogWarning("JoinGroup: {0} does not have access to {1}.", session.Id, group.GetPlayingItemId()); - - var error = new GroupUpdate() - { - GroupId = group.GetGroupId().ToString(), - Type = GroupUpdateType.LibraryAccessDenied - }; - _sessionManager.SendSyncplayGroupUpdate(session.Id.ToString(), error, CancellationToken.None); - return; - } - - if (IsSessionInGroup(session)) - { - if (GetSessionGroup(session).Equals(groupId)) return; - LeaveGroup(session, cancellationToken); - } - - group.SessionJoin(session, request, cancellationToken); - } - } - - /// - public void LeaveGroup(SessionInfo session, CancellationToken cancellationToken) - { - // TODO: determine what happens to users that are in a group and get their permissions revoked - lock (_groupsLock) - { - ISyncplayController group; - _sessionToGroupMap.TryGetValue(session.Id, out group); - - if (group == null) - { - _logger.LogWarning("LeaveGroup: {0} does not belong to any group.", session.Id); - - var error = new GroupUpdate() - { - Type = GroupUpdateType.NotInGroup - }; - _sessionManager.SendSyncplayGroupUpdate(session.Id.ToString(), error, CancellationToken.None); - return; - } - - group.SessionLeave(session, cancellationToken); - - if (group.IsGroupEmpty()) - { - _logger.LogInformation("LeaveGroup: removing empty group {0}.", group.GetGroupId()); - _groups.Remove(group.GetGroupId().ToString(), out _); - } - } - } - - /// - public List ListGroups(SessionInfo session, Guid filterItemId) - { - var user = _userManager.GetUserById(session.UserId); - - if (user.Policy.SyncplayAccess == SyncplayAccess.None) - { - return new List(); - } - - // Filter by item if requested - if (!filterItemId.Equals(Guid.Empty)) - { - return _groups.Values.Where( - group => group.GetPlayingItemId().Equals(filterItemId) && HasAccessToItem(user, group.GetPlayingItemId()) - ).Select( - group => group.GetInfo() - ).ToList(); - } - // Otherwise show all available groups - else - { - return _groups.Values.Where( - group => HasAccessToItem(user, group.GetPlayingItemId()) - ).Select( - group => group.GetInfo() - ).ToList(); - } - } - - /// - public void HandleRequest(SessionInfo session, PlaybackRequest request, CancellationToken cancellationToken) - { - var user = _userManager.GetUserById(session.UserId); - - if (user.Policy.SyncplayAccess == SyncplayAccess.None) - { - _logger.LogWarning("HandleRequest: {0} does not have access to Syncplay.", session.Id); - - var error = new GroupUpdate() - { - Type = GroupUpdateType.JoinGroupDenied - }; - _sessionManager.SendSyncplayGroupUpdate(session.Id.ToString(), error, CancellationToken.None); - return; - } - - lock (_groupsLock) - { - ISyncplayController group; - _sessionToGroupMap.TryGetValue(session.Id, out group); - - if (group == null) - { - _logger.LogWarning("HandleRequest: {0} does not belong to any group.", session.Id); - - var error = new GroupUpdate() - { - Type = GroupUpdateType.NotInGroup - }; - _sessionManager.SendSyncplayGroupUpdate(session.Id.ToString(), error, CancellationToken.None); - return; - } - - group.HandleRequest(session, request, cancellationToken); - } - } - - /// - public void AddSessionToGroup(SessionInfo session, ISyncplayController group) - { - if (IsSessionInGroup(session)) - { - throw new InvalidOperationException("Session in other group already!"); - } - _sessionToGroupMap[session.Id] = group; - } - - /// - public void RemoveSessionFromGroup(SessionInfo session, ISyncplayController group) - { - if (!IsSessionInGroup(session)) - { - throw new InvalidOperationException("Session not in any group!"); - } - - ISyncplayController tempGroup; - _sessionToGroupMap.Remove(session.Id, out tempGroup); - - if (!tempGroup.GetGroupId().Equals(group.GetGroupId())) - { - throw new InvalidOperationException("Session was in wrong group!"); - } - } - } -} diff --git a/MediaBrowser.Api/SyncPlay/SyncPlayService.cs b/MediaBrowser.Api/SyncPlay/SyncPlayService.cs new file mode 100644 index 000000000..bcdc833e4 --- /dev/null +++ b/MediaBrowser.Api/SyncPlay/SyncPlayService.cs @@ -0,0 +1,312 @@ +using System.Threading; +using System; +using System.Collections.Generic; +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Net; +using MediaBrowser.Controller.Session; +using MediaBrowser.Controller.SyncPlay; +using MediaBrowser.Model.Services; +using MediaBrowser.Model.SyncPlay; +using Microsoft.Extensions.Logging; + +namespace MediaBrowser.Api.SyncPlay +{ + [Route("/SyncPlay/{SessionId}/NewGroup", "POST", Summary = "Create a new SyncPlay group")] + [Authenticated] + public class SyncPlayNewGroup : IReturnVoid + { + [ApiMember(Name = "SessionId", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] + public string SessionId { get; set; } + } + + [Route("/SyncPlay/{SessionId}/JoinGroup", "POST", Summary = "Join an existing SyncPlay group")] + [Authenticated] + public class SyncPlayJoinGroup : IReturnVoid + { + [ApiMember(Name = "SessionId", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] + public string SessionId { get; set; } + + /// + /// Gets or sets the Group id. + /// + /// The Group id to join. + [ApiMember(Name = "GroupId", Description = "Group Id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")] + public string GroupId { get; set; } + + /// + /// Gets or sets the playing item id. + /// + /// The client's currently playing item id. + [ApiMember(Name = "PlayingItemId", Description = "Client's playing item id", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")] + public string PlayingItemId { get; set; } + } + + [Route("/SyncPlay/{SessionId}/LeaveGroup", "POST", Summary = "Leave joined SyncPlay group")] + [Authenticated] + public class SyncPlayLeaveGroup : IReturnVoid + { + [ApiMember(Name = "SessionId", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] + public string SessionId { get; set; } + } + + [Route("/SyncPlay/{SessionId}/ListGroups", "POST", Summary = "List SyncPlay groups")] + [Authenticated] + public class SyncPlayListGroups : IReturnVoid + { + [ApiMember(Name = "SessionId", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] + public string SessionId { get; set; } + + /// + /// Gets or sets the filter item id. + /// + /// The filter item id. + [ApiMember(Name = "FilterItemId", Description = "Filter by item id", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")] + public string FilterItemId { get; set; } + } + + [Route("/SyncPlay/{SessionId}/PlayRequest", "POST", Summary = "Request play in SyncPlay group")] + [Authenticated] + public class SyncPlayPlayRequest : IReturnVoid + { + [ApiMember(Name = "SessionId", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] + public string SessionId { get; set; } + } + + [Route("/SyncPlay/{SessionId}/PauseRequest", "POST", Summary = "Request pause in SyncPlay group")] + [Authenticated] + public class SyncPlayPauseRequest : IReturnVoid + { + [ApiMember(Name = "SessionId", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] + public string SessionId { get; set; } + } + + [Route("/SyncPlay/{SessionId}/SeekRequest", "POST", Summary = "Request seek in SyncPlay group")] + [Authenticated] + public class SyncPlaySeekRequest : IReturnVoid + { + [ApiMember(Name = "SessionId", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] + public string SessionId { get; set; } + + [ApiMember(Name = "PositionTicks", IsRequired = true, DataType = "long", ParameterType = "query", Verb = "POST")] + public long PositionTicks { get; set; } + } + + [Route("/SyncPlay/{SessionId}/BufferingRequest", "POST", Summary = "Request group wait in SyncPlay group while buffering")] + [Authenticated] + public class SyncPlayBufferingRequest : IReturnVoid + { + [ApiMember(Name = "SessionId", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] + public string SessionId { get; set; } + + /// + /// Gets or sets the date used to pin PositionTicks in time. + /// + /// The date related to PositionTicks. + [ApiMember(Name = "When", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")] + public string When { get; set; } + + [ApiMember(Name = "PositionTicks", IsRequired = true, DataType = "long", ParameterType = "query", Verb = "POST")] + public long PositionTicks { get; set; } + + /// + /// Gets or sets whether this is a buffering or a buffering-done request. + /// + /// true if buffering is complete; false otherwise. + [ApiMember(Name = "BufferingDone", IsRequired = true, DataType = "bool", ParameterType = "query", Verb = "POST")] + public bool BufferingDone { get; set; } + } + + [Route("/SyncPlay/{SessionId}/UpdatePing", "POST", Summary = "Update session ping")] + [Authenticated] + public class SyncPlayUpdatePing : IReturnVoid + { + [ApiMember(Name = "SessionId", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] + public string SessionId { get; set; } + + [ApiMember(Name = "Ping", IsRequired = true, DataType = "double", ParameterType = "query", Verb = "POST")] + public double Ping { get; set; } + } + + /// + /// Class SyncPlayService. + /// + public class SyncPlayService : BaseApiService + { + /// + /// The session context. + /// + private readonly ISessionContext _sessionContext; + + /// + /// The SyncPlay manager. + /// + private readonly ISyncPlayManager _syncPlayManager; + + public SyncPlayService( + ILogger logger, + IServerConfigurationManager serverConfigurationManager, + IHttpResultFactory httpResultFactory, + ISessionContext sessionContext, + ISyncPlayManager syncPlayManager) + : base(logger, serverConfigurationManager, httpResultFactory) + { + _sessionContext = sessionContext; + _syncPlayManager = syncPlayManager; + } + + /// + /// Handles the specified request. + /// + /// The request. + public void Post(SyncPlayNewGroup request) + { + var currentSession = GetSession(_sessionContext); + _syncPlayManager.NewGroup(currentSession, CancellationToken.None); + } + + /// + /// Handles the specified request. + /// + /// The request. + public void Post(SyncPlayJoinGroup request) + { + var currentSession = GetSession(_sessionContext); + var joinRequest = new JoinGroupRequest() + { + GroupId = Guid.Parse(request.GroupId) + }; + + // Both null and empty strings mean that client isn't playing anything + if (!String.IsNullOrEmpty(request.PlayingItemId)) + { + try + { + joinRequest.PlayingItemId = Guid.Parse(request.PlayingItemId); + } + catch (ArgumentNullException) + { + // Should never happen, but just in case + Logger.LogError("JoinGroup: null value for PlayingItemId. Ignoring request."); + return; + } + catch (FormatException) + { + Logger.LogError("JoinGroup: {0} is not a valid format for PlayingItemId. Ignoring request.", request.PlayingItemId); + return; + } + } + _syncPlayManager.JoinGroup(currentSession, request.GroupId, joinRequest, CancellationToken.None); + } + + /// + /// Handles the specified request. + /// + /// The request. + public void Post(SyncPlayLeaveGroup request) + { + var currentSession = GetSession(_sessionContext); + _syncPlayManager.LeaveGroup(currentSession, CancellationToken.None); + } + + /// + /// Handles the specified request. + /// + /// The request. + /// The requested list of groups. + public List Post(SyncPlayListGroups request) + { + var currentSession = GetSession(_sessionContext); + var filterItemId = Guid.Empty; + if (!String.IsNullOrEmpty(request.FilterItemId)) + { + try + { + filterItemId = Guid.Parse(request.FilterItemId); + } + catch (ArgumentNullException) + { + Logger.LogWarning("ListGroups: null value for FilterItemId. Ignoring filter."); + } + catch (FormatException) + { + Logger.LogWarning("ListGroups: {0} is not a valid format for FilterItemId. Ignoring filter.", request.FilterItemId); + } + } + return _syncPlayManager.ListGroups(currentSession, filterItemId); + } + + /// + /// Handles the specified request. + /// + /// The request. + public void Post(SyncPlayPlayRequest request) + { + var currentSession = GetSession(_sessionContext); + var syncPlayRequest = new PlaybackRequest() + { + Type = PlaybackRequestType.Play + }; + _syncPlayManager.HandleRequest(currentSession, syncPlayRequest, CancellationToken.None); + } + + /// + /// Handles the specified request. + /// + /// The request. + public void Post(SyncPlayPauseRequest request) + { + var currentSession = GetSession(_sessionContext); + var syncPlayRequest = new PlaybackRequest() + { + Type = PlaybackRequestType.Pause + }; + _syncPlayManager.HandleRequest(currentSession, syncPlayRequest, CancellationToken.None); + } + + /// + /// Handles the specified request. + /// + /// The request. + public void Post(SyncPlaySeekRequest request) + { + var currentSession = GetSession(_sessionContext); + var syncPlayRequest = new PlaybackRequest() + { + Type = PlaybackRequestType.Seek, + PositionTicks = request.PositionTicks + }; + _syncPlayManager.HandleRequest(currentSession, syncPlayRequest, CancellationToken.None); + } + + /// + /// Handles the specified request. + /// + /// The request. + public void Post(SyncPlayBufferingRequest request) + { + var currentSession = GetSession(_sessionContext); + var syncPlayRequest = new PlaybackRequest() + { + Type = request.BufferingDone ? PlaybackRequestType.BufferingDone : PlaybackRequestType.Buffering, + When = DateTime.Parse(request.When), + PositionTicks = request.PositionTicks + }; + _syncPlayManager.HandleRequest(currentSession, syncPlayRequest, CancellationToken.None); + } + + /// + /// Handles the specified request. + /// + /// The request. + public void Post(SyncPlayUpdatePing request) + { + var currentSession = GetSession(_sessionContext); + var syncPlayRequest = new PlaybackRequest() + { + Type = PlaybackRequestType.UpdatePing, + Ping = Convert.ToInt64(request.Ping) + }; + _syncPlayManager.HandleRequest(currentSession, syncPlayRequest, CancellationToken.None); + } + } +} diff --git a/MediaBrowser.Api/SyncPlay/TimeSyncService.cs b/MediaBrowser.Api/SyncPlay/TimeSyncService.cs new file mode 100644 index 000000000..4a9307e62 --- /dev/null +++ b/MediaBrowser.Api/SyncPlay/TimeSyncService.cs @@ -0,0 +1,52 @@ +using System; +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Net; +using MediaBrowser.Model.Services; +using MediaBrowser.Model.SyncPlay; +using Microsoft.Extensions.Logging; + +namespace MediaBrowser.Api.SyncPlay +{ + [Route("/GetUtcTime", "GET", Summary = "Get UtcTime")] + public class GetUtcTime : IReturnVoid + { + // Nothing + } + + /// + /// Class TimeSyncService. + /// + public class TimeSyncService : BaseApiService + { + public TimeSyncService( + ILogger logger, + IServerConfigurationManager serverConfigurationManager, + IHttpResultFactory httpResultFactory) + : base(logger, serverConfigurationManager, httpResultFactory) + { + // Do nothing + } + + /// + /// Handles the specified request. + /// + /// The request. + /// The current UTC time response. + public UtcTimeResponse Get(GetUtcTime request) + { + // Important to keep the following line at the beginning + var requestReceptionTime = DateTime.UtcNow.ToUniversalTime().ToString("o"); + + var response = new UtcTimeResponse(); + response.RequestReceptionTime = requestReceptionTime; + + // Important to keep the following two lines at the end + var responseTransmissionTime = DateTime.UtcNow.ToUniversalTime().ToString("o"); + response.ResponseTransmissionTime = responseTransmissionTime; + + // Implementing NTP on such a high level results in this useless + // information being sent. On the other hand it enables future additions. + return response; + } + } +} diff --git a/MediaBrowser.Api/Syncplay/SyncplayService.cs b/MediaBrowser.Api/Syncplay/SyncplayService.cs deleted file mode 100644 index 4b6e16762..000000000 --- a/MediaBrowser.Api/Syncplay/SyncplayService.cs +++ /dev/null @@ -1,312 +0,0 @@ -using System.Threading; -using System; -using System.Collections.Generic; -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Net; -using MediaBrowser.Controller.Session; -using MediaBrowser.Controller.Syncplay; -using MediaBrowser.Model.Services; -using MediaBrowser.Model.Syncplay; -using Microsoft.Extensions.Logging; - -namespace MediaBrowser.Api.Syncplay -{ - [Route("/Syncplay/{SessionId}/NewGroup", "POST", Summary = "Create a new Syncplay group")] - [Authenticated] - public class SyncplayNewGroup : IReturnVoid - { - [ApiMember(Name = "SessionId", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public string SessionId { get; set; } - } - - [Route("/Syncplay/{SessionId}/JoinGroup", "POST", Summary = "Join an existing Syncplay group")] - [Authenticated] - public class SyncplayJoinGroup : IReturnVoid - { - [ApiMember(Name = "SessionId", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public string SessionId { get; set; } - - /// - /// Gets or sets the Group id. - /// - /// The Group id to join. - [ApiMember(Name = "GroupId", Description = "Group Id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")] - public string GroupId { get; set; } - - /// - /// Gets or sets the playing item id. - /// - /// The client's currently playing item id. - [ApiMember(Name = "PlayingItemId", Description = "Client's playing item id", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")] - public string PlayingItemId { get; set; } - } - - [Route("/Syncplay/{SessionId}/LeaveGroup", "POST", Summary = "Leave joined Syncplay group")] - [Authenticated] - public class SyncplayLeaveGroup : IReturnVoid - { - [ApiMember(Name = "SessionId", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public string SessionId { get; set; } - } - - [Route("/Syncplay/{SessionId}/ListGroups", "POST", Summary = "List Syncplay groups")] - [Authenticated] - public class SyncplayListGroups : IReturnVoid - { - [ApiMember(Name = "SessionId", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public string SessionId { get; set; } - - /// - /// Gets or sets the filter item id. - /// - /// The filter item id. - [ApiMember(Name = "FilterItemId", Description = "Filter by item id", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")] - public string FilterItemId { get; set; } - } - - [Route("/Syncplay/{SessionId}/PlayRequest", "POST", Summary = "Request play in Syncplay group")] - [Authenticated] - public class SyncplayPlayRequest : IReturnVoid - { - [ApiMember(Name = "SessionId", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public string SessionId { get; set; } - } - - [Route("/Syncplay/{SessionId}/PauseRequest", "POST", Summary = "Request pause in Syncplay group")] - [Authenticated] - public class SyncplayPauseRequest : IReturnVoid - { - [ApiMember(Name = "SessionId", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public string SessionId { get; set; } - } - - [Route("/Syncplay/{SessionId}/SeekRequest", "POST", Summary = "Request seek in Syncplay group")] - [Authenticated] - public class SyncplaySeekRequest : IReturnVoid - { - [ApiMember(Name = "SessionId", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public string SessionId { get; set; } - - [ApiMember(Name = "PositionTicks", IsRequired = true, DataType = "long", ParameterType = "query", Verb = "POST")] - public long PositionTicks { get; set; } - } - - [Route("/Syncplay/{SessionId}/BufferingRequest", "POST", Summary = "Request group wait in Syncplay group while buffering")] - [Authenticated] - public class SyncplayBufferingRequest : IReturnVoid - { - [ApiMember(Name = "SessionId", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public string SessionId { get; set; } - - /// - /// Gets or sets the date used to pin PositionTicks in time. - /// - /// The date related to PositionTicks. - [ApiMember(Name = "When", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")] - public string When { get; set; } - - [ApiMember(Name = "PositionTicks", IsRequired = true, DataType = "long", ParameterType = "query", Verb = "POST")] - public long PositionTicks { get; set; } - - /// - /// Gets or sets whether this is a buffering or a buffering-done request. - /// - /// true if buffering is complete; false otherwise. - [ApiMember(Name = "BufferingDone", IsRequired = true, DataType = "bool", ParameterType = "query", Verb = "POST")] - public bool BufferingDone { get; set; } - } - - [Route("/Syncplay/{SessionId}/UpdatePing", "POST", Summary = "Update session ping")] - [Authenticated] - public class SyncplayUpdatePing : IReturnVoid - { - [ApiMember(Name = "SessionId", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public string SessionId { get; set; } - - [ApiMember(Name = "Ping", IsRequired = true, DataType = "double", ParameterType = "query", Verb = "POST")] - public double Ping { get; set; } - } - - /// - /// Class SyncplayService. - /// - public class SyncplayService : BaseApiService - { - /// - /// The session context. - /// - private readonly ISessionContext _sessionContext; - - /// - /// The Syncplay manager. - /// - private readonly ISyncplayManager _syncplayManager; - - public SyncplayService( - ILogger logger, - IServerConfigurationManager serverConfigurationManager, - IHttpResultFactory httpResultFactory, - ISessionContext sessionContext, - ISyncplayManager syncplayManager) - : base(logger, serverConfigurationManager, httpResultFactory) - { - _sessionContext = sessionContext; - _syncplayManager = syncplayManager; - } - - /// - /// Handles the specified request. - /// - /// The request. - public void Post(SyncplayNewGroup request) - { - var currentSession = GetSession(_sessionContext); - _syncplayManager.NewGroup(currentSession, CancellationToken.None); - } - - /// - /// Handles the specified request. - /// - /// The request. - public void Post(SyncplayJoinGroup request) - { - var currentSession = GetSession(_sessionContext); - var joinRequest = new JoinGroupRequest() - { - GroupId = Guid.Parse(request.GroupId) - }; - - // Both null and empty strings mean that client isn't playing anything - if (!String.IsNullOrEmpty(request.PlayingItemId)) - { - try - { - joinRequest.PlayingItemId = Guid.Parse(request.PlayingItemId); - } - catch (ArgumentNullException) - { - // Should never happen, but just in case - Logger.LogError("JoinGroup: null value for PlayingItemId. Ignoring request."); - return; - } - catch (FormatException) - { - Logger.LogError("JoinGroup: {0} is not a valid format for PlayingItemId. Ignoring request.", request.PlayingItemId); - return; - } - } - _syncplayManager.JoinGroup(currentSession, request.GroupId, joinRequest, CancellationToken.None); - } - - /// - /// Handles the specified request. - /// - /// The request. - public void Post(SyncplayLeaveGroup request) - { - var currentSession = GetSession(_sessionContext); - _syncplayManager.LeaveGroup(currentSession, CancellationToken.None); - } - - /// - /// Handles the specified request. - /// - /// The request. - /// The requested list of groups. - public List Post(SyncplayListGroups request) - { - var currentSession = GetSession(_sessionContext); - var filterItemId = Guid.Empty; - if (!String.IsNullOrEmpty(request.FilterItemId)) - { - try - { - filterItemId = Guid.Parse(request.FilterItemId); - } - catch (ArgumentNullException) - { - Logger.LogWarning("ListGroups: null value for FilterItemId. Ignoring filter."); - } - catch (FormatException) - { - Logger.LogWarning("ListGroups: {0} is not a valid format for FilterItemId. Ignoring filter.", request.FilterItemId); - } - } - return _syncplayManager.ListGroups(currentSession, filterItemId); - } - - /// - /// Handles the specified request. - /// - /// The request. - public void Post(SyncplayPlayRequest request) - { - var currentSession = GetSession(_sessionContext); - var syncplayRequest = new PlaybackRequest() - { - Type = PlaybackRequestType.Play - }; - _syncplayManager.HandleRequest(currentSession, syncplayRequest, CancellationToken.None); - } - - /// - /// Handles the specified request. - /// - /// The request. - public void Post(SyncplayPauseRequest request) - { - var currentSession = GetSession(_sessionContext); - var syncplayRequest = new PlaybackRequest() - { - Type = PlaybackRequestType.Pause - }; - _syncplayManager.HandleRequest(currentSession, syncplayRequest, CancellationToken.None); - } - - /// - /// Handles the specified request. - /// - /// The request. - public void Post(SyncplaySeekRequest request) - { - var currentSession = GetSession(_sessionContext); - var syncplayRequest = new PlaybackRequest() - { - Type = PlaybackRequestType.Seek, - PositionTicks = request.PositionTicks - }; - _syncplayManager.HandleRequest(currentSession, syncplayRequest, CancellationToken.None); - } - - /// - /// Handles the specified request. - /// - /// The request. - public void Post(SyncplayBufferingRequest request) - { - var currentSession = GetSession(_sessionContext); - var syncplayRequest = new PlaybackRequest() - { - Type = request.BufferingDone ? PlaybackRequestType.BufferingDone : PlaybackRequestType.Buffering, - When = DateTime.Parse(request.When), - PositionTicks = request.PositionTicks - }; - _syncplayManager.HandleRequest(currentSession, syncplayRequest, CancellationToken.None); - } - - /// - /// Handles the specified request. - /// - /// The request. - public void Post(SyncplayUpdatePing request) - { - var currentSession = GetSession(_sessionContext); - var syncplayRequest = new PlaybackRequest() - { - Type = PlaybackRequestType.UpdatePing, - Ping = Convert.ToInt64(request.Ping) - }; - _syncplayManager.HandleRequest(currentSession, syncplayRequest, CancellationToken.None); - } - } -} diff --git a/MediaBrowser.Api/Syncplay/TimeSyncService.cs b/MediaBrowser.Api/Syncplay/TimeSyncService.cs deleted file mode 100644 index 9a26ffd99..000000000 --- a/MediaBrowser.Api/Syncplay/TimeSyncService.cs +++ /dev/null @@ -1,52 +0,0 @@ -using System; -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Net; -using MediaBrowser.Model.Services; -using MediaBrowser.Model.Syncplay; -using Microsoft.Extensions.Logging; - -namespace MediaBrowser.Api.Syncplay -{ - [Route("/GetUtcTime", "GET", Summary = "Get UtcTime")] - public class GetUtcTime : IReturnVoid - { - // Nothing - } - - /// - /// Class TimeSyncService. - /// - public class TimeSyncService : BaseApiService - { - public TimeSyncService( - ILogger logger, - IServerConfigurationManager serverConfigurationManager, - IHttpResultFactory httpResultFactory) - : base(logger, serverConfigurationManager, httpResultFactory) - { - // Do nothing - } - - /// - /// Handles the specified request. - /// - /// The request. - /// The current UTC time response. - public UtcTimeResponse Get(GetUtcTime request) - { - // Important to keep the following line at the beginning - var requestReceptionTime = DateTime.UtcNow.ToUniversalTime().ToString("o"); - - var response = new UtcTimeResponse(); - response.RequestReceptionTime = requestReceptionTime; - - // Important to keep the following two lines at the end - var responseTransmissionTime = DateTime.UtcNow.ToUniversalTime().ToString("o"); - response.ResponseTransmissionTime = responseTransmissionTime; - - // Implementing NTP on such a high level results in this useless - // information being sent. On the other hand it enables future additions. - return response; - } - } -} diff --git a/MediaBrowser.Controller/Session/ISessionManager.cs b/MediaBrowser.Controller/Session/ISessionManager.cs index 39c065b89..4c2f834cb 100644 --- a/MediaBrowser.Controller/Session/ISessionManager.cs +++ b/MediaBrowser.Controller/Session/ISessionManager.cs @@ -9,7 +9,7 @@ using MediaBrowser.Controller.Security; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Events; using MediaBrowser.Model.Session; -using MediaBrowser.Model.Syncplay; +using MediaBrowser.Model.SyncPlay; namespace MediaBrowser.Controller.Session { @@ -142,22 +142,22 @@ namespace MediaBrowser.Controller.Session Task SendPlayCommand(string controllingSessionId, string sessionId, PlayRequest command, CancellationToken cancellationToken); /// - /// Sends the SyncplayCommand. + /// Sends the SyncPlayCommand. /// /// The session id. /// The command. /// The cancellation token. /// Task. - Task SendSyncplayCommand(string sessionId, SendCommand command, CancellationToken cancellationToken); + Task SendSyncPlayCommand(string sessionId, SendCommand command, CancellationToken cancellationToken); /// - /// Sends the SyncplayGroupUpdate. + /// Sends the SyncPlayGroupUpdate. /// /// The session id. /// The group update. /// The cancellation token. /// Task. - Task SendSyncplayGroupUpdate(string sessionId, GroupUpdate command, CancellationToken cancellationToken); + Task SendSyncPlayGroupUpdate(string sessionId, GroupUpdate command, CancellationToken cancellationToken); /// /// Sends the browse command. diff --git a/MediaBrowser.Controller/SyncPlay/GroupInfo.cs b/MediaBrowser.Controller/SyncPlay/GroupInfo.cs new file mode 100644 index 000000000..087748de0 --- /dev/null +++ b/MediaBrowser.Controller/SyncPlay/GroupInfo.cs @@ -0,0 +1,150 @@ +using System; +using System.Collections.Generic; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Session; + +namespace MediaBrowser.Controller.SyncPlay +{ + /// + /// Class GroupInfo. + /// + /// + /// Class is not thread-safe, external locking is required when accessing methods. + /// + public class GroupInfo + { + /// + /// Default ping value used for sessions. + /// + public readonly long DefaulPing = 500; + /// + /// Gets or sets the group identifier. + /// + /// The group identifier. + public readonly Guid GroupId = Guid.NewGuid(); + + /// + /// Gets or sets the playing item. + /// + /// The playing item. + public BaseItem PlayingItem { get; set; } + + /// + /// Gets or sets whether playback is paused. + /// + /// Playback is paused. + public bool IsPaused { get; set; } + + /// + /// Gets or sets the position ticks. + /// + /// The position ticks. + public long PositionTicks { get; set; } + + /// + /// Gets or sets the last activity. + /// + /// The last activity. + public DateTime LastActivity { get; set; } + + /// + /// Gets the participants. + /// + /// The participants, or members of the group. + public readonly Dictionary Participants = + new Dictionary(StringComparer.OrdinalIgnoreCase); + + /// + /// Checks if a session is in this group. + /// + /// true if the session is in this group; false otherwise. + public bool ContainsSession(string sessionId) + { + return Participants.ContainsKey(sessionId); + } + + /// + /// Adds the session to the group. + /// + /// The session. + public void AddSession(SessionInfo session) + { + if (ContainsSession(session.Id.ToString())) return; + var member = new GroupMember(); + member.Session = session; + member.Ping = DefaulPing; + member.IsBuffering = false; + Participants[session.Id.ToString()] = member; + } + + /// + /// Removes the session from the group. + /// + /// The session. + + public void RemoveSession(SessionInfo session) + { + if (!ContainsSession(session.Id.ToString())) return; + GroupMember member; + Participants.Remove(session.Id.ToString(), out member); + } + + /// + /// Updates the ping of a session. + /// + /// The session. + /// The ping. + public void UpdatePing(SessionInfo session, long ping) + { + if (!ContainsSession(session.Id.ToString())) return; + Participants[session.Id.ToString()].Ping = ping; + } + + /// + /// Gets the highest ping in the group. + /// + /// The highest ping in the group. + public long GetHighestPing() + { + long max = Int64.MinValue; + foreach (var session in Participants.Values) + { + max = Math.Max(max, session.Ping); + } + return max; + } + + /// + /// Sets the session's buffering state. + /// + /// The session. + /// The state. + public void SetBuffering(SessionInfo session, bool isBuffering) + { + if (!ContainsSession(session.Id.ToString())) return; + Participants[session.Id.ToString()].IsBuffering = isBuffering; + } + + /// + /// Gets the group buffering state. + /// + /// true if there is a session buffering in the group; false otherwise. + public bool IsBuffering() + { + foreach (var session in Participants.Values) + { + if (session.IsBuffering) return true; + } + return false; + } + + /// + /// Checks if the group is empty. + /// + /// true if the group is empty; false otherwise. + public bool IsEmpty() + { + return Participants.Count == 0; + } + } +} diff --git a/MediaBrowser.Controller/SyncPlay/GroupMember.cs b/MediaBrowser.Controller/SyncPlay/GroupMember.cs new file mode 100644 index 000000000..a3975c334 --- /dev/null +++ b/MediaBrowser.Controller/SyncPlay/GroupMember.cs @@ -0,0 +1,28 @@ +using MediaBrowser.Controller.Session; + +namespace MediaBrowser.Controller.SyncPlay +{ + /// + /// Class GroupMember. + /// + public class GroupMember + { + /// + /// Gets or sets whether this member is buffering. + /// + /// true if member is buffering; false otherwise. + public bool IsBuffering { get; set; } + + /// + /// Gets or sets the session. + /// + /// The session. + public SessionInfo Session { get; set; } + + /// + /// Gets or sets the ping. + /// + /// The ping. + public long Ping { get; set; } + } +} diff --git a/MediaBrowser.Controller/SyncPlay/ISyncPlayController.cs b/MediaBrowser.Controller/SyncPlay/ISyncPlayController.cs new file mode 100644 index 000000000..de1fcd259 --- /dev/null +++ b/MediaBrowser.Controller/SyncPlay/ISyncPlayController.cs @@ -0,0 +1,67 @@ +using System; +using System.Threading; +using MediaBrowser.Controller.Session; +using MediaBrowser.Model.SyncPlay; + +namespace MediaBrowser.Controller.SyncPlay +{ + /// + /// Interface ISyncPlayController. + /// + public interface ISyncPlayController + { + /// + /// Gets the group id. + /// + /// The group id. + Guid GetGroupId(); + + /// + /// Gets the playing item id. + /// + /// The playing item id. + Guid GetPlayingItemId(); + + /// + /// Checks if the group is empty. + /// + /// If the group is empty. + bool IsGroupEmpty(); + + /// + /// Initializes the group with the session's info. + /// + /// The session. + /// The cancellation token. + void InitGroup(SessionInfo session, CancellationToken cancellationToken); + + /// + /// Adds the session to the group. + /// + /// The session. + /// The request. + /// The cancellation token. + void SessionJoin(SessionInfo session, JoinGroupRequest request, CancellationToken cancellationToken); + + /// + /// Removes the session from the group. + /// + /// The session. + /// The cancellation token. + void SessionLeave(SessionInfo session, CancellationToken cancellationToken); + + /// + /// Handles the requested action by the session. + /// + /// The session. + /// The requested action. + /// The cancellation token. + void HandleRequest(SessionInfo session, PlaybackRequest request, CancellationToken cancellationToken); + + /// + /// Gets the info about the group for the clients. + /// + /// The group info for the clients. + GroupInfoView GetInfo(); + } +} \ No newline at end of file diff --git a/MediaBrowser.Controller/SyncPlay/ISyncPlayManager.cs b/MediaBrowser.Controller/SyncPlay/ISyncPlayManager.cs new file mode 100644 index 000000000..6c962ec85 --- /dev/null +++ b/MediaBrowser.Controller/SyncPlay/ISyncPlayManager.cs @@ -0,0 +1,69 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using MediaBrowser.Controller.Session; +using MediaBrowser.Model.SyncPlay; + +namespace MediaBrowser.Controller.SyncPlay +{ + /// + /// Interface ISyncPlayManager. + /// + public interface ISyncPlayManager + { + /// + /// Creates a new group. + /// + /// The session that's creating the group. + /// The cancellation token. + void NewGroup(SessionInfo session, CancellationToken cancellationToken); + + /// + /// Adds the session to a group. + /// + /// The session. + /// The group id. + /// The request. + /// The cancellation token. + void JoinGroup(SessionInfo session, string groupId, JoinGroupRequest request, CancellationToken cancellationToken); + + /// + /// Removes the session from a group. + /// + /// The session. + /// The cancellation token. + void LeaveGroup(SessionInfo session, CancellationToken cancellationToken); + + /// + /// Gets list of available groups for a session. + /// + /// The session. + /// The item id to filter by. + /// The list of available groups. + List ListGroups(SessionInfo session, Guid filterItemId); + + /// + /// Handle a request by a session in a group. + /// + /// The session. + /// The request. + /// The cancellation token. + void HandleRequest(SessionInfo session, PlaybackRequest request, CancellationToken cancellationToken); + + /// + /// Maps a session to a group. + /// + /// The session. + /// The group. + /// + void AddSessionToGroup(SessionInfo session, ISyncPlayController group); + + /// + /// Unmaps a session from a group. + /// + /// The session. + /// The group. + /// + void RemoveSessionFromGroup(SessionInfo session, ISyncPlayController group); + } +} diff --git a/MediaBrowser.Controller/Syncplay/GroupInfo.cs b/MediaBrowser.Controller/Syncplay/GroupInfo.cs deleted file mode 100644 index c01fead83..000000000 --- a/MediaBrowser.Controller/Syncplay/GroupInfo.cs +++ /dev/null @@ -1,150 +0,0 @@ -using System; -using System.Collections.Generic; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Session; - -namespace MediaBrowser.Controller.Syncplay -{ - /// - /// Class GroupInfo. - /// - /// - /// Class is not thread-safe, external locking is required when accessing methods. - /// - public class GroupInfo - { - /// - /// Default ping value used for sessions. - /// - public readonly long DefaulPing = 500; - /// - /// Gets or sets the group identifier. - /// - /// The group identifier. - public readonly Guid GroupId = Guid.NewGuid(); - - /// - /// Gets or sets the playing item. - /// - /// The playing item. - public BaseItem PlayingItem { get; set; } - - /// - /// Gets or sets whether playback is paused. - /// - /// Playback is paused. - public bool IsPaused { get; set; } - - /// - /// Gets or sets the position ticks. - /// - /// The position ticks. - public long PositionTicks { get; set; } - - /// - /// Gets or sets the last activity. - /// - /// The last activity. - public DateTime LastActivity { get; set; } - - /// - /// Gets the participants. - /// - /// The participants, or members of the group. - public readonly Dictionary Participants = - new Dictionary(StringComparer.OrdinalIgnoreCase); - - /// - /// Checks if a session is in this group. - /// - /// true if the session is in this group; false otherwise. - public bool ContainsSession(string sessionId) - { - return Participants.ContainsKey(sessionId); - } - - /// - /// Adds the session to the group. - /// - /// The session. - public void AddSession(SessionInfo session) - { - if (ContainsSession(session.Id.ToString())) return; - var member = new GroupMember(); - member.Session = session; - member.Ping = DefaulPing; - member.IsBuffering = false; - Participants[session.Id.ToString()] = member; - } - - /// - /// Removes the session from the group. - /// - /// The session. - - public void RemoveSession(SessionInfo session) - { - if (!ContainsSession(session.Id.ToString())) return; - GroupMember member; - Participants.Remove(session.Id.ToString(), out member); - } - - /// - /// Updates the ping of a session. - /// - /// The session. - /// The ping. - public void UpdatePing(SessionInfo session, long ping) - { - if (!ContainsSession(session.Id.ToString())) return; - Participants[session.Id.ToString()].Ping = ping; - } - - /// - /// Gets the highest ping in the group. - /// - /// The highest ping in the group. - public long GetHighestPing() - { - long max = Int64.MinValue; - foreach (var session in Participants.Values) - { - max = Math.Max(max, session.Ping); - } - return max; - } - - /// - /// Sets the session's buffering state. - /// - /// The session. - /// The state. - public void SetBuffering(SessionInfo session, bool isBuffering) - { - if (!ContainsSession(session.Id.ToString())) return; - Participants[session.Id.ToString()].IsBuffering = isBuffering; - } - - /// - /// Gets the group buffering state. - /// - /// true if there is a session buffering in the group; false otherwise. - public bool IsBuffering() - { - foreach (var session in Participants.Values) - { - if (session.IsBuffering) return true; - } - return false; - } - - /// - /// Checks if the group is empty. - /// - /// true if the group is empty; false otherwise. - public bool IsEmpty() - { - return Participants.Count == 0; - } - } -} diff --git a/MediaBrowser.Controller/Syncplay/GroupMember.cs b/MediaBrowser.Controller/Syncplay/GroupMember.cs deleted file mode 100644 index 7630428d7..000000000 --- a/MediaBrowser.Controller/Syncplay/GroupMember.cs +++ /dev/null @@ -1,28 +0,0 @@ -using MediaBrowser.Controller.Session; - -namespace MediaBrowser.Controller.Syncplay -{ - /// - /// Class GroupMember. - /// - public class GroupMember - { - /// - /// Gets or sets whether this member is buffering. - /// - /// true if member is buffering; false otherwise. - public bool IsBuffering { get; set; } - - /// - /// Gets or sets the session. - /// - /// The session. - public SessionInfo Session { get; set; } - - /// - /// Gets or sets the ping. - /// - /// The ping. - public long Ping { get; set; } - } -} diff --git a/MediaBrowser.Controller/Syncplay/ISyncplayController.cs b/MediaBrowser.Controller/Syncplay/ISyncplayController.cs deleted file mode 100644 index 34eae4062..000000000 --- a/MediaBrowser.Controller/Syncplay/ISyncplayController.cs +++ /dev/null @@ -1,67 +0,0 @@ -using System; -using System.Threading; -using MediaBrowser.Controller.Session; -using MediaBrowser.Model.Syncplay; - -namespace MediaBrowser.Controller.Syncplay -{ - /// - /// Interface ISyncplayController. - /// - public interface ISyncplayController - { - /// - /// Gets the group id. - /// - /// The group id. - Guid GetGroupId(); - - /// - /// Gets the playing item id. - /// - /// The playing item id. - Guid GetPlayingItemId(); - - /// - /// Checks if the group is empty. - /// - /// If the group is empty. - bool IsGroupEmpty(); - - /// - /// Initializes the group with the session's info. - /// - /// The session. - /// The cancellation token. - void InitGroup(SessionInfo session, CancellationToken cancellationToken); - - /// - /// Adds the session to the group. - /// - /// The session. - /// The request. - /// The cancellation token. - void SessionJoin(SessionInfo session, JoinGroupRequest request, CancellationToken cancellationToken); - - /// - /// Removes the session from the group. - /// - /// The session. - /// The cancellation token. - void SessionLeave(SessionInfo session, CancellationToken cancellationToken); - - /// - /// Handles the requested action by the session. - /// - /// The session. - /// The requested action. - /// The cancellation token. - void HandleRequest(SessionInfo session, PlaybackRequest request, CancellationToken cancellationToken); - - /// - /// Gets the info about the group for the clients. - /// - /// The group info for the clients. - GroupInfoView GetInfo(); - } -} \ No newline at end of file diff --git a/MediaBrowser.Controller/Syncplay/ISyncplayManager.cs b/MediaBrowser.Controller/Syncplay/ISyncplayManager.cs deleted file mode 100644 index fbc208d27..000000000 --- a/MediaBrowser.Controller/Syncplay/ISyncplayManager.cs +++ /dev/null @@ -1,69 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Threading; -using MediaBrowser.Controller.Session; -using MediaBrowser.Model.Syncplay; - -namespace MediaBrowser.Controller.Syncplay -{ - /// - /// Interface ISyncplayManager. - /// - public interface ISyncplayManager - { - /// - /// Creates a new group. - /// - /// The session that's creating the group. - /// The cancellation token. - void NewGroup(SessionInfo session, CancellationToken cancellationToken); - - /// - /// Adds the session to a group. - /// - /// The session. - /// The group id. - /// The request. - /// The cancellation token. - void JoinGroup(SessionInfo session, string groupId, JoinGroupRequest request, CancellationToken cancellationToken); - - /// - /// Removes the session from a group. - /// - /// The session. - /// The cancellation token. - void LeaveGroup(SessionInfo session, CancellationToken cancellationToken); - - /// - /// Gets list of available groups for a session. - /// - /// The session. - /// The item id to filter by. - /// The list of available groups. - List ListGroups(SessionInfo session, Guid filterItemId); - - /// - /// Handle a request by a session in a group. - /// - /// The session. - /// The request. - /// The cancellation token. - void HandleRequest(SessionInfo session, PlaybackRequest request, CancellationToken cancellationToken); - - /// - /// Maps a session to a group. - /// - /// The session. - /// The group. - /// - void AddSessionToGroup(SessionInfo session, ISyncplayController group); - - /// - /// Unmaps a session from a group. - /// - /// The session. - /// The group. - /// - void RemoveSessionFromGroup(SessionInfo session, ISyncplayController group); - } -} diff --git a/MediaBrowser.Model/Configuration/SyncplayAccess.cs b/MediaBrowser.Model/Configuration/SyncplayAccess.cs index cddf68c42..d891a8167 100644 --- a/MediaBrowser.Model/Configuration/SyncplayAccess.cs +++ b/MediaBrowser.Model/Configuration/SyncplayAccess.cs @@ -1,9 +1,9 @@ namespace MediaBrowser.Model.Configuration { /// - /// Enum SyncplayAccess. + /// Enum SyncPlayAccess. /// - public enum SyncplayAccess + public enum SyncPlayAccess { /// /// User can create groups and join them. @@ -16,7 +16,7 @@ namespace MediaBrowser.Model.Configuration JoinGroups, /// - /// Syncplay is disabled for the user. + /// SyncPlay is disabled for the user. /// None } diff --git a/MediaBrowser.Model/SyncPlay/GroupInfoView.cs b/MediaBrowser.Model/SyncPlay/GroupInfoView.cs new file mode 100644 index 000000000..7b833506b --- /dev/null +++ b/MediaBrowser.Model/SyncPlay/GroupInfoView.cs @@ -0,0 +1,38 @@ +namespace MediaBrowser.Model.SyncPlay +{ + /// + /// Class GroupInfoView. + /// + public class GroupInfoView + { + /// + /// Gets or sets the group identifier. + /// + /// The group identifier. + public string GroupId { get; set; } + + /// + /// Gets or sets the playing item id. + /// + /// The playing item id. + public string PlayingItemId { get; set; } + + /// + /// Gets or sets the playing item name. + /// + /// The playing item name. + public string PlayingItemName { get; set; } + + /// + /// Gets or sets the position ticks. + /// + /// The position ticks. + public long PositionTicks { get; set; } + + /// + /// Gets or sets the participants. + /// + /// The participants. + public string[] Participants { get; set; } + } +} diff --git a/MediaBrowser.Model/SyncPlay/GroupUpdate.cs b/MediaBrowser.Model/SyncPlay/GroupUpdate.cs new file mode 100644 index 000000000..895702f3d --- /dev/null +++ b/MediaBrowser.Model/SyncPlay/GroupUpdate.cs @@ -0,0 +1,26 @@ +namespace MediaBrowser.Model.SyncPlay +{ + /// + /// Class GroupUpdate. + /// + public class GroupUpdate + { + /// + /// Gets or sets the group identifier. + /// + /// The group identifier. + public string GroupId { get; set; } + + /// + /// Gets or sets the update type. + /// + /// The update type. + public GroupUpdateType Type { get; set; } + + /// + /// Gets or sets the data. + /// + /// The data. + public T Data { get; set; } + } +} diff --git a/MediaBrowser.Model/SyncPlay/GroupUpdateType.cs b/MediaBrowser.Model/SyncPlay/GroupUpdateType.cs new file mode 100644 index 000000000..89d245787 --- /dev/null +++ b/MediaBrowser.Model/SyncPlay/GroupUpdateType.cs @@ -0,0 +1,53 @@ +namespace MediaBrowser.Model.SyncPlay +{ + /// + /// Enum GroupUpdateType. + /// + public enum GroupUpdateType + { + /// + /// The user-joined update. Tells members of a group about a new user. + /// + UserJoined, + /// + /// The user-left update. Tells members of a group that a user left. + /// + UserLeft, + /// + /// The group-joined update. Tells a user that the group has been joined. + /// + GroupJoined, + /// + /// The group-left update. Tells a user that the group has been left. + /// + GroupLeft, + /// + /// The group-wait update. Tells members of the group that a user is buffering. + /// + GroupWait, + /// + /// The prepare-session update. Tells a user to load some content. + /// + PrepareSession, + /// + /// The not-in-group error. Tells a user that they don't belong to a group. + /// + NotInGroup, + /// + /// The group-does-not-exist error. Sent when trying to join a non-existing group. + /// + GroupDoesNotExist, + /// + /// The create-group-denied error. Sent when a user tries to create a group without required permissions. + /// + CreateGroupDenied, + /// + /// The join-group-denied error. Sent when a user tries to join a group without required permissions. + /// + JoinGroupDenied, + /// + /// The library-access-denied error. Sent when a user tries to join a group without required access to the library. + /// + LibraryAccessDenied + } +} diff --git a/MediaBrowser.Model/SyncPlay/JoinGroupRequest.cs b/MediaBrowser.Model/SyncPlay/JoinGroupRequest.cs new file mode 100644 index 000000000..d67b6bd55 --- /dev/null +++ b/MediaBrowser.Model/SyncPlay/JoinGroupRequest.cs @@ -0,0 +1,22 @@ +using System; + +namespace MediaBrowser.Model.SyncPlay +{ + /// + /// Class JoinGroupRequest. + /// + public class JoinGroupRequest + { + /// + /// Gets or sets the Group id. + /// + /// The Group id to join. + public Guid GroupId { get; set; } + + /// + /// Gets or sets the playing item id. + /// + /// The client's currently playing item id. + public Guid PlayingItemId { get; set; } + } +} diff --git a/MediaBrowser.Model/SyncPlay/PlaybackRequest.cs b/MediaBrowser.Model/SyncPlay/PlaybackRequest.cs new file mode 100644 index 000000000..9de23194e --- /dev/null +++ b/MediaBrowser.Model/SyncPlay/PlaybackRequest.cs @@ -0,0 +1,34 @@ +using System; + +namespace MediaBrowser.Model.SyncPlay +{ + /// + /// Class PlaybackRequest. + /// + public class PlaybackRequest + { + /// + /// Gets or sets the request type. + /// + /// The request type. + public PlaybackRequestType Type { get; set; } + + /// + /// Gets or sets when the request has been made by the client. + /// + /// The date of the request. + public DateTime? When { get; set; } + + /// + /// Gets or sets the position ticks. + /// + /// The position ticks. + public long? PositionTicks { get; set; } + + /// + /// Gets or sets the ping time. + /// + /// The ping time. + public long? Ping { get; set; } + } +} diff --git a/MediaBrowser.Model/SyncPlay/PlaybackRequestType.cs b/MediaBrowser.Model/SyncPlay/PlaybackRequestType.cs new file mode 100644 index 000000000..f1e175fde --- /dev/null +++ b/MediaBrowser.Model/SyncPlay/PlaybackRequestType.cs @@ -0,0 +1,33 @@ +namespace MediaBrowser.Model.SyncPlay +{ + /// + /// Enum PlaybackRequestType + /// + public enum PlaybackRequestType + { + /// + /// A user is requesting a play command for the group. + /// + Play = 0, + /// + /// A user is requesting a pause command for the group. + /// + Pause = 1, + /// + /// A user is requesting a seek command for the group. + /// + Seek = 2, + /// + /// A user is signaling that playback is buffering. + /// + Buffering = 3, + /// + /// A user is signaling that playback resumed. + /// + BufferingDone = 4, + /// + /// A user is reporting its ping. + /// + UpdatePing = 5 + } +} diff --git a/MediaBrowser.Model/SyncPlay/SendCommand.cs b/MediaBrowser.Model/SyncPlay/SendCommand.cs new file mode 100644 index 000000000..0f06e381f --- /dev/null +++ b/MediaBrowser.Model/SyncPlay/SendCommand.cs @@ -0,0 +1,38 @@ +namespace MediaBrowser.Model.SyncPlay +{ + /// + /// Class SendCommand. + /// + public class SendCommand + { + /// + /// Gets or sets the group identifier. + /// + /// The group identifier. + public string GroupId { get; set; } + + /// + /// Gets or sets the UTC time when to execute the command. + /// + /// The UTC time when to execute the command. + public string When { get; set; } + + /// + /// Gets or sets the position ticks. + /// + /// The position ticks. + public long? PositionTicks { get; set; } + + /// + /// Gets or sets the command. + /// + /// The command. + public SendCommandType Command { get; set; } + + /// + /// Gets or sets the UTC time when this command has been emitted. + /// + /// The UTC time when this command has been emitted. + public string EmittedAt { get; set; } + } +} diff --git a/MediaBrowser.Model/SyncPlay/SendCommandType.cs b/MediaBrowser.Model/SyncPlay/SendCommandType.cs new file mode 100644 index 000000000..113719871 --- /dev/null +++ b/MediaBrowser.Model/SyncPlay/SendCommandType.cs @@ -0,0 +1,21 @@ +namespace MediaBrowser.Model.SyncPlay +{ + /// + /// Enum SendCommandType. + /// + public enum SendCommandType + { + /// + /// The play command. Instructs users to start playback. + /// + Play = 0, + /// + /// The pause command. Instructs users to pause playback. + /// + Pause = 1, + /// + /// The seek command. Instructs users to seek to a specified time. + /// + Seek = 2 + } +} diff --git a/MediaBrowser.Model/SyncPlay/UtcTimeResponse.cs b/MediaBrowser.Model/SyncPlay/UtcTimeResponse.cs new file mode 100644 index 000000000..0a6036154 --- /dev/null +++ b/MediaBrowser.Model/SyncPlay/UtcTimeResponse.cs @@ -0,0 +1,20 @@ +namespace MediaBrowser.Model.SyncPlay +{ + /// + /// Class UtcTimeResponse. + /// + public class UtcTimeResponse + { + /// + /// Gets or sets the UTC time when request has been received. + /// + /// The UTC time when request has been received. + public string RequestReceptionTime { get; set; } + + /// + /// Gets or sets the UTC time when response has been sent. + /// + /// The UTC time when response has been sent. + public string ResponseTransmissionTime { get; set; } + } +} diff --git a/MediaBrowser.Model/Syncplay/GroupInfoView.cs b/MediaBrowser.Model/Syncplay/GroupInfoView.cs deleted file mode 100644 index 50ad70630..000000000 --- a/MediaBrowser.Model/Syncplay/GroupInfoView.cs +++ /dev/null @@ -1,38 +0,0 @@ -namespace MediaBrowser.Model.Syncplay -{ - /// - /// Class GroupInfoView. - /// - public class GroupInfoView - { - /// - /// Gets or sets the group identifier. - /// - /// The group identifier. - public string GroupId { get; set; } - - /// - /// Gets or sets the playing item id. - /// - /// The playing item id. - public string PlayingItemId { get; set; } - - /// - /// Gets or sets the playing item name. - /// - /// The playing item name. - public string PlayingItemName { get; set; } - - /// - /// Gets or sets the position ticks. - /// - /// The position ticks. - public long PositionTicks { get; set; } - - /// - /// Gets or sets the participants. - /// - /// The participants. - public string[] Participants { get; set; } - } -} diff --git a/MediaBrowser.Model/Syncplay/GroupUpdate.cs b/MediaBrowser.Model/Syncplay/GroupUpdate.cs deleted file mode 100644 index cc49e92a9..000000000 --- a/MediaBrowser.Model/Syncplay/GroupUpdate.cs +++ /dev/null @@ -1,26 +0,0 @@ -namespace MediaBrowser.Model.Syncplay -{ - /// - /// Class GroupUpdate. - /// - public class GroupUpdate - { - /// - /// Gets or sets the group identifier. - /// - /// The group identifier. - public string GroupId { get; set; } - - /// - /// Gets or sets the update type. - /// - /// The update type. - public GroupUpdateType Type { get; set; } - - /// - /// Gets or sets the data. - /// - /// The data. - public T Data { get; set; } - } -} diff --git a/MediaBrowser.Model/Syncplay/GroupUpdateType.cs b/MediaBrowser.Model/Syncplay/GroupUpdateType.cs deleted file mode 100644 index 9f40f9577..000000000 --- a/MediaBrowser.Model/Syncplay/GroupUpdateType.cs +++ /dev/null @@ -1,53 +0,0 @@ -namespace MediaBrowser.Model.Syncplay -{ - /// - /// Enum GroupUpdateType. - /// - public enum GroupUpdateType - { - /// - /// The user-joined update. Tells members of a group about a new user. - /// - UserJoined, - /// - /// The user-left update. Tells members of a group that a user left. - /// - UserLeft, - /// - /// The group-joined update. Tells a user that the group has been joined. - /// - GroupJoined, - /// - /// The group-left update. Tells a user that the group has been left. - /// - GroupLeft, - /// - /// The group-wait update. Tells members of the group that a user is buffering. - /// - GroupWait, - /// - /// The prepare-session update. Tells a user to load some content. - /// - PrepareSession, - /// - /// The not-in-group error. Tells a user that they don't belong to a group. - /// - NotInGroup, - /// - /// The group-does-not-exist error. Sent when trying to join a non-existing group. - /// - GroupDoesNotExist, - /// - /// The create-group-denied error. Sent when a user tries to create a group without required permissions. - /// - CreateGroupDenied, - /// - /// The join-group-denied error. Sent when a user tries to join a group without required permissions. - /// - JoinGroupDenied, - /// - /// The library-access-denied error. Sent when a user tries to join a group without required access to the library. - /// - LibraryAccessDenied - } -} diff --git a/MediaBrowser.Model/Syncplay/JoinGroupRequest.cs b/MediaBrowser.Model/Syncplay/JoinGroupRequest.cs deleted file mode 100644 index 8d8a2646a..000000000 --- a/MediaBrowser.Model/Syncplay/JoinGroupRequest.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System; - -namespace MediaBrowser.Model.Syncplay -{ - /// - /// Class JoinGroupRequest. - /// - public class JoinGroupRequest - { - /// - /// Gets or sets the Group id. - /// - /// The Group id to join. - public Guid GroupId { get; set; } - - /// - /// Gets or sets the playing item id. - /// - /// The client's currently playing item id. - public Guid PlayingItemId { get; set; } - } -} diff --git a/MediaBrowser.Model/Syncplay/PlaybackRequest.cs b/MediaBrowser.Model/Syncplay/PlaybackRequest.cs deleted file mode 100644 index ba97641f6..000000000 --- a/MediaBrowser.Model/Syncplay/PlaybackRequest.cs +++ /dev/null @@ -1,34 +0,0 @@ -using System; - -namespace MediaBrowser.Model.Syncplay -{ - /// - /// Class PlaybackRequest. - /// - public class PlaybackRequest - { - /// - /// Gets or sets the request type. - /// - /// The request type. - public PlaybackRequestType Type { get; set; } - - /// - /// Gets or sets when the request has been made by the client. - /// - /// The date of the request. - public DateTime? When { get; set; } - - /// - /// Gets or sets the position ticks. - /// - /// The position ticks. - public long? PositionTicks { get; set; } - - /// - /// Gets or sets the ping time. - /// - /// The ping time. - public long? Ping { get; set; } - } -} diff --git a/MediaBrowser.Model/Syncplay/PlaybackRequestType.cs b/MediaBrowser.Model/Syncplay/PlaybackRequestType.cs deleted file mode 100644 index b3d49d09e..000000000 --- a/MediaBrowser.Model/Syncplay/PlaybackRequestType.cs +++ /dev/null @@ -1,33 +0,0 @@ -namespace MediaBrowser.Model.Syncplay -{ - /// - /// Enum PlaybackRequestType - /// - public enum PlaybackRequestType - { - /// - /// A user is requesting a play command for the group. - /// - Play = 0, - /// - /// A user is requesting a pause command for the group. - /// - Pause = 1, - /// - /// A user is requesting a seek command for the group. - /// - Seek = 2, - /// - /// A user is signaling that playback is buffering. - /// - Buffering = 3, - /// - /// A user is signaling that playback resumed. - /// - BufferingDone = 4, - /// - /// A user is reporting its ping. - /// - UpdatePing = 5 - } -} diff --git a/MediaBrowser.Model/Syncplay/SendCommand.cs b/MediaBrowser.Model/Syncplay/SendCommand.cs deleted file mode 100644 index d9f391403..000000000 --- a/MediaBrowser.Model/Syncplay/SendCommand.cs +++ /dev/null @@ -1,38 +0,0 @@ -namespace MediaBrowser.Model.Syncplay -{ - /// - /// Class SendCommand. - /// - public class SendCommand - { - /// - /// Gets or sets the group identifier. - /// - /// The group identifier. - public string GroupId { get; set; } - - /// - /// Gets or sets the UTC time when to execute the command. - /// - /// The UTC time when to execute the command. - public string When { get; set; } - - /// - /// Gets or sets the position ticks. - /// - /// The position ticks. - public long? PositionTicks { get; set; } - - /// - /// Gets or sets the command. - /// - /// The command. - public SendCommandType Command { get; set; } - - /// - /// Gets or sets the UTC time when this command has been emitted. - /// - /// The UTC time when this command has been emitted. - public string EmittedAt { get; set; } - } -} diff --git a/MediaBrowser.Model/Syncplay/SendCommandType.cs b/MediaBrowser.Model/Syncplay/SendCommandType.cs deleted file mode 100644 index 02e4774d0..000000000 --- a/MediaBrowser.Model/Syncplay/SendCommandType.cs +++ /dev/null @@ -1,21 +0,0 @@ -namespace MediaBrowser.Model.Syncplay -{ - /// - /// Enum SendCommandType. - /// - public enum SendCommandType - { - /// - /// The play command. Instructs users to start playback. - /// - Play = 0, - /// - /// The pause command. Instructs users to pause playback. - /// - Pause = 1, - /// - /// The seek command. Instructs users to seek to a specified time. - /// - Seek = 2 - } -} diff --git a/MediaBrowser.Model/Syncplay/UtcTimeResponse.cs b/MediaBrowser.Model/Syncplay/UtcTimeResponse.cs deleted file mode 100644 index f7887dc33..000000000 --- a/MediaBrowser.Model/Syncplay/UtcTimeResponse.cs +++ /dev/null @@ -1,20 +0,0 @@ -namespace MediaBrowser.Model.Syncplay -{ - /// - /// Class UtcTimeResponse. - /// - public class UtcTimeResponse - { - /// - /// Gets or sets the UTC time when request has been received. - /// - /// The UTC time when request has been received. - public string RequestReceptionTime { get; set; } - - /// - /// Gets or sets the UTC time when response has been sent. - /// - /// The UTC time when response has been sent. - public string ResponseTransmissionTime { get; set; } - } -} diff --git a/MediaBrowser.Model/Users/UserPolicy.cs b/MediaBrowser.Model/Users/UserPolicy.cs index cf576c358..3e027e831 100644 --- a/MediaBrowser.Model/Users/UserPolicy.cs +++ b/MediaBrowser.Model/Users/UserPolicy.cs @@ -81,10 +81,10 @@ namespace MediaBrowser.Model.Users public string PasswordResetProviderId { get; set; } /// - /// Gets or sets a value indicating what Syncplay features the user can access. + /// Gets or sets a value indicating what SyncPlay features the user can access. /// - /// Access level to Syncplay features. - public SyncplayAccess SyncplayAccess { get; set; } + /// Access level to SyncPlay features. + public SyncPlayAccess SyncPlayAccess { get; set; } public UserPolicy() { @@ -131,7 +131,7 @@ namespace MediaBrowser.Model.Users EnableContentDownloading = true; EnablePublicSharing = true; EnableRemoteAccess = true; - SyncplayAccess = SyncplayAccess.CreateAndJoinGroups; + SyncPlayAccess = SyncPlayAccess.CreateAndJoinGroups; } } } -- cgit v1.2.3 From 5c8cbd4087261f13d003d7d4eab082cbf335b4d4 Mon Sep 17 00:00:00 2001 From: gion Date: Sat, 9 May 2020 14:34:07 +0200 Subject: Fix code issues --- .../Session/SessionWebSocketListener.cs | 21 +++++----- .../SyncPlay/SyncPlayController.cs | 4 +- .../SyncPlay/SyncPlayManager.cs | 28 +++++++++---- MediaBrowser.Api/SyncPlay/SyncPlayService.cs | 47 +++++++++++----------- .../Net/IWebSocketConnection.cs | 2 +- MediaBrowser.Controller/SyncPlay/GroupInfo.cs | 32 +++++++++++---- .../SyncPlay/ISyncPlayManager.cs | 2 +- MediaBrowser.Model/SyncPlay/GroupInfoView.cs | 4 +- 8 files changed, 86 insertions(+), 54 deletions(-) (limited to 'Emby.Server.Implementations/Session') diff --git a/Emby.Server.Implementations/Session/SessionWebSocketListener.cs b/Emby.Server.Implementations/Session/SessionWebSocketListener.cs index d1ee22ea8..3704445ab 100644 --- a/Emby.Server.Implementations/Session/SessionWebSocketListener.cs +++ b/Emby.Server.Implementations/Session/SessionWebSocketListener.cs @@ -22,17 +22,17 @@ namespace Emby.Server.Implementations.Session /// /// The timeout in seconds after which a WebSocket is considered to be lost. /// - public readonly int WebSocketLostTimeout = 60; + public const int WebSocketLostTimeout = 60; /// /// The keep-alive interval factor; controls how often the watcher will check on the status of the WebSockets. /// - public readonly double IntervalFactor = 0.2; + public const float IntervalFactor = 0.2f; /// /// The ForceKeepAlive factor; controls when a ForceKeepAlive is sent. /// - public readonly double ForceKeepAliveFactor = 0.75; + public const float ForceKeepAliveFactor = 0.75f; /// /// The _session manager @@ -213,7 +213,7 @@ namespace Emby.Server.Implementations.Session { _keepAliveCancellationToken = new CancellationTokenSource(); // Start KeepAlive watcher - var task = RepeatAsyncCallbackEvery( + _ = RepeatAsyncCallbackEvery( KeepAliveSockets, TimeSpan.FromSeconds(WebSocketLostTimeout * IntervalFactor), _keepAliveCancellationToken.Token); @@ -241,6 +241,7 @@ namespace Emby.Server.Implementations.Session { webSocket.Closed -= OnWebSocketClosed; } + _webSockets.Clear(); } } @@ -250,8 +251,8 @@ namespace Emby.Server.Implementations.Session /// private async Task KeepAliveSockets() { - IEnumerable inactive; - IEnumerable lost; + List inactive; + List lost; lock (_webSocketsLock) { @@ -261,8 +262,8 @@ namespace Emby.Server.Implementations.Session { var elapsed = (DateTime.UtcNow - i.LastKeepAliveDate).TotalSeconds; return (elapsed > WebSocketLostTimeout * ForceKeepAliveFactor) && (elapsed < WebSocketLostTimeout); - }); - lost = _webSockets.Where(i => (DateTime.UtcNow - i.LastKeepAliveDate).TotalSeconds >= WebSocketLostTimeout); + }).ToList(); + lost = _webSockets.Where(i => (DateTime.UtcNow - i.LastKeepAliveDate).TotalSeconds >= WebSocketLostTimeout).ToList(); } if (inactive.Any()) @@ -279,7 +280,7 @@ namespace Emby.Server.Implementations.Session catch (WebSocketException exception) { _logger.LogInformation(exception, "Error sending ForceKeepAlive message to WebSocket."); - lost = lost.Append(webSocket); + lost.Add(webSocket); } } @@ -288,7 +289,7 @@ namespace Emby.Server.Implementations.Session if (lost.Any()) { _logger.LogInformation("Lost {0} WebSockets.", lost.Count()); - foreach (var webSocket in lost.ToList()) + foreach (var webSocket in lost) { // TODO: handle session relative to the lost webSocket RemoveWebSocket(webSocket); diff --git a/Emby.Server.Implementations/SyncPlay/SyncPlayController.cs b/Emby.Server.Implementations/SyncPlay/SyncPlayController.cs index 9c9758de1..c7bd242a7 100644 --- a/Emby.Server.Implementations/SyncPlay/SyncPlayController.cs +++ b/Emby.Server.Implementations/SyncPlay/SyncPlayController.cs @@ -144,7 +144,7 @@ namespace Emby.Server.Implementations.SyncPlay session => session.Session ).ToArray(); default: - return new SessionInfo[] { }; + return Array.Empty(); } } @@ -541,7 +541,7 @@ namespace Emby.Server.Implementations.SyncPlay PlayingItemName = _group.PlayingItem.Name, PlayingItemId = _group.PlayingItem.Id.ToString(), PositionTicks = _group.PositionTicks, - Participants = _group.Participants.Values.Select(session => session.Session.UserName).Distinct().ToArray() + Participants = _group.Participants.Values.Select(session => session.Session.UserName).Distinct().ToList().AsReadOnly() }; } } diff --git a/Emby.Server.Implementations/SyncPlay/SyncPlayManager.cs b/Emby.Server.Implementations/SyncPlay/SyncPlayManager.cs index d3197d97b..93cec1304 100644 --- a/Emby.Server.Implementations/SyncPlay/SyncPlayManager.cs +++ b/Emby.Server.Implementations/SyncPlay/SyncPlayManager.cs @@ -47,8 +47,8 @@ namespace Emby.Server.Implementations.SyncPlay /// /// The groups. /// - private readonly Dictionary _groups = - new Dictionary(StringComparer.OrdinalIgnoreCase); + private readonly Dictionary _groups = + new Dictionary(); /// /// Lock used for accesing any group. @@ -113,14 +113,22 @@ namespace Emby.Server.Implementations.SyncPlay private void OnSessionManagerSessionEnded(object sender, SessionEventArgs e) { var session = e.SessionInfo; - if (!IsSessionInGroup(session)) return; + if (!IsSessionInGroup(session)) + { + return; + } + LeaveGroup(session, CancellationToken.None); } private void OnSessionManagerPlaybackStopped(object sender, PlaybackStopEventArgs e) { var session = e.Session; - if (!IsSessionInGroup(session)) return; + if (!IsSessionInGroup(session)) + { + return; + } + LeaveGroup(session, CancellationToken.None); } @@ -193,14 +201,14 @@ namespace Emby.Server.Implementations.SyncPlay } var group = new SyncPlayController(_sessionManager, this); - _groups[group.GetGroupId().ToString()] = group; + _groups[group.GetGroupId()] = group; group.InitGroup(session, cancellationToken); } } /// - public void JoinGroup(SessionInfo session, string groupId, JoinGroupRequest request, CancellationToken cancellationToken) + public void JoinGroup(SessionInfo session, Guid groupId, JoinGroupRequest request, CancellationToken cancellationToken) { var user = _userManager.GetUserById(session.UserId); @@ -248,7 +256,11 @@ namespace Emby.Server.Implementations.SyncPlay if (IsSessionInGroup(session)) { - if (GetSessionGroup(session).Equals(groupId)) return; + if (GetSessionGroup(session).Equals(groupId)) + { + return; + } + LeaveGroup(session, cancellationToken); } @@ -282,7 +294,7 @@ namespace Emby.Server.Implementations.SyncPlay if (group.IsGroupEmpty()) { _logger.LogInformation("LeaveGroup: removing empty group {0}.", group.GetGroupId()); - _groups.Remove(group.GetGroupId().ToString(), out _); + _groups.Remove(group.GetGroupId(), out _); } } } diff --git a/MediaBrowser.Api/SyncPlay/SyncPlayService.cs b/MediaBrowser.Api/SyncPlay/SyncPlayService.cs index bcdc833e4..9137faf9f 100644 --- a/MediaBrowser.Api/SyncPlay/SyncPlayService.cs +++ b/MediaBrowser.Api/SyncPlay/SyncPlayService.cs @@ -171,31 +171,35 @@ namespace MediaBrowser.Api.SyncPlay public void Post(SyncPlayJoinGroup request) { var currentSession = GetSession(_sessionContext); - var joinRequest = new JoinGroupRequest() + + Guid groupId; + Guid playingItemId = Guid.Empty; + + var valid = Guid.TryParse(request.GroupId, out groupId); + if (!valid) { - GroupId = Guid.Parse(request.GroupId) - }; + Logger.LogError("JoinGroup: {0} is not a valid format for GroupId. Ignoring request.", request.GroupId); + return; + } // Both null and empty strings mean that client isn't playing anything if (!String.IsNullOrEmpty(request.PlayingItemId)) { - try - { - joinRequest.PlayingItemId = Guid.Parse(request.PlayingItemId); - } - catch (ArgumentNullException) - { - // Should never happen, but just in case - Logger.LogError("JoinGroup: null value for PlayingItemId. Ignoring request."); - return; - } - catch (FormatException) + valid = Guid.TryParse(request.PlayingItemId, out playingItemId); + if (!valid) { Logger.LogError("JoinGroup: {0} is not a valid format for PlayingItemId. Ignoring request.", request.PlayingItemId); return; } } - _syncPlayManager.JoinGroup(currentSession, request.GroupId, joinRequest, CancellationToken.None); + + var joinRequest = new JoinGroupRequest() + { + GroupId = groupId, + PlayingItemId = playingItemId + }; + + _syncPlayManager.JoinGroup(currentSession, groupId, joinRequest, CancellationToken.None); } /// @@ -217,21 +221,16 @@ namespace MediaBrowser.Api.SyncPlay { var currentSession = GetSession(_sessionContext); var filterItemId = Guid.Empty; + if (!String.IsNullOrEmpty(request.FilterItemId)) { - try - { - filterItemId = Guid.Parse(request.FilterItemId); - } - catch (ArgumentNullException) - { - Logger.LogWarning("ListGroups: null value for FilterItemId. Ignoring filter."); - } - catch (FormatException) + var valid = Guid.TryParse(request.FilterItemId, out filterItemId); + if (!valid) { Logger.LogWarning("ListGroups: {0} is not a valid format for FilterItemId. Ignoring filter.", request.FilterItemId); } } + return _syncPlayManager.ListGroups(currentSession, filterItemId); } diff --git a/MediaBrowser.Controller/Net/IWebSocketConnection.cs b/MediaBrowser.Controller/Net/IWebSocketConnection.cs index fb766ab57..b371a59e9 100644 --- a/MediaBrowser.Controller/Net/IWebSocketConnection.cs +++ b/MediaBrowser.Controller/Net/IWebSocketConnection.cs @@ -30,7 +30,7 @@ namespace MediaBrowser.Controller.Net /// Gets or sets the date of last Keeplive received. /// /// The date of last Keeplive received. - public DateTime LastKeepAliveDate { get; set; } + DateTime LastKeepAliveDate { get; set; } /// /// Gets or sets the URL. diff --git a/MediaBrowser.Controller/SyncPlay/GroupInfo.cs b/MediaBrowser.Controller/SyncPlay/GroupInfo.cs index 087748de0..bda49bd1b 100644 --- a/MediaBrowser.Controller/SyncPlay/GroupInfo.cs +++ b/MediaBrowser.Controller/SyncPlay/GroupInfo.cs @@ -69,7 +69,11 @@ namespace MediaBrowser.Controller.SyncPlay /// The session. public void AddSession(SessionInfo session) { - if (ContainsSession(session.Id.ToString())) return; + if (ContainsSession(session.Id.ToString())) + { + return; + } + var member = new GroupMember(); member.Session = session; member.Ping = DefaulPing; @@ -84,9 +88,12 @@ namespace MediaBrowser.Controller.SyncPlay public void RemoveSession(SessionInfo session) { - if (!ContainsSession(session.Id.ToString())) return; - GroupMember member; - Participants.Remove(session.Id.ToString(), out member); + if (!ContainsSession(session.Id.ToString())) + { + return; + } + + Participants.Remove(session.Id.ToString(), out _); } /// @@ -96,7 +103,11 @@ namespace MediaBrowser.Controller.SyncPlay /// The ping. public void UpdatePing(SessionInfo session, long ping) { - if (!ContainsSession(session.Id.ToString())) return; + if (!ContainsSession(session.Id.ToString())) + { + return; + } + Participants[session.Id.ToString()].Ping = ping; } @@ -121,7 +132,11 @@ namespace MediaBrowser.Controller.SyncPlay /// The state. public void SetBuffering(SessionInfo session, bool isBuffering) { - if (!ContainsSession(session.Id.ToString())) return; + if (!ContainsSession(session.Id.ToString())) + { + return; + } + Participants[session.Id.ToString()].IsBuffering = isBuffering; } @@ -133,7 +148,10 @@ namespace MediaBrowser.Controller.SyncPlay { foreach (var session in Participants.Values) { - if (session.IsBuffering) return true; + if (session.IsBuffering) + { + return true; + } } return false; } diff --git a/MediaBrowser.Controller/SyncPlay/ISyncPlayManager.cs b/MediaBrowser.Controller/SyncPlay/ISyncPlayManager.cs index 6c962ec85..006fb687b 100644 --- a/MediaBrowser.Controller/SyncPlay/ISyncPlayManager.cs +++ b/MediaBrowser.Controller/SyncPlay/ISyncPlayManager.cs @@ -25,7 +25,7 @@ namespace MediaBrowser.Controller.SyncPlay /// The group id. /// The request. /// The cancellation token. - void JoinGroup(SessionInfo session, string groupId, JoinGroupRequest request, CancellationToken cancellationToken); + void JoinGroup(SessionInfo session, Guid groupId, JoinGroupRequest request, CancellationToken cancellationToken); /// /// Removes the session from a group. diff --git a/MediaBrowser.Model/SyncPlay/GroupInfoView.cs b/MediaBrowser.Model/SyncPlay/GroupInfoView.cs index 7b833506b..f28ecf16d 100644 --- a/MediaBrowser.Model/SyncPlay/GroupInfoView.cs +++ b/MediaBrowser.Model/SyncPlay/GroupInfoView.cs @@ -1,3 +1,5 @@ +using System.Collections.Generic; + namespace MediaBrowser.Model.SyncPlay { /// @@ -33,6 +35,6 @@ namespace MediaBrowser.Model.SyncPlay /// Gets or sets the participants. /// /// The participants. - public string[] Participants { get; set; } + public IReadOnlyList Participants { get; set; } } } -- cgit v1.2.3 From 8c04049a595df054f491712ed317274566f2d71b Mon Sep 17 00:00:00 2001 From: gion Date: Fri, 15 May 2020 20:06:41 +0200 Subject: Fix some code smells --- .../Session/SessionWebSocketListener.cs | 4 ++-- MediaBrowser.Api/SyncPlay/SyncPlayService.cs | 16 +++++----------- 2 files changed, 7 insertions(+), 13 deletions(-) (limited to 'Emby.Server.Implementations/Session') diff --git a/Emby.Server.Implementations/Session/SessionWebSocketListener.cs b/Emby.Server.Implementations/Session/SessionWebSocketListener.cs index a5293b41c..3af18f681 100644 --- a/Emby.Server.Implementations/Session/SessionWebSocketListener.cs +++ b/Emby.Server.Implementations/Session/SessionWebSocketListener.cs @@ -269,7 +269,7 @@ namespace Emby.Server.Implementations.Session if (inactive.Any()) { - _logger.LogInformation("Sending ForceKeepAlive message to {0} inactive WebSockets.", inactive.Count()); + _logger.LogInformation("Sending ForceKeepAlive message to {0} inactive WebSockets.", inactive.Count); } foreach (var webSocket in inactive) @@ -289,7 +289,7 @@ namespace Emby.Server.Implementations.Session { if (lost.Any()) { - _logger.LogInformation("Lost {0} WebSockets.", lost.Count()); + _logger.LogInformation("Lost {0} WebSockets.", lost.Count); foreach (var webSocket in lost) { // TODO: handle session relative to the lost webSocket diff --git a/MediaBrowser.Api/SyncPlay/SyncPlayService.cs b/MediaBrowser.Api/SyncPlay/SyncPlayService.cs index 8f552ef89..8064ea7dc 100644 --- a/MediaBrowser.Api/SyncPlay/SyncPlayService.cs +++ b/MediaBrowser.Api/SyncPlay/SyncPlayService.cs @@ -182,13 +182,10 @@ namespace MediaBrowser.Api.SyncPlay } // Both null and empty strings mean that client isn't playing anything - if (!String.IsNullOrEmpty(request.PlayingItemId)) + if (!String.IsNullOrEmpty(request.PlayingItemId) && !Guid.TryParse(request.PlayingItemId, out playingItemId)) { - if (!Guid.TryParse(request.PlayingItemId, out playingItemId)) - { - Logger.LogError("JoinGroup: {0} is not a valid format for PlayingItemId. Ignoring request.", request.PlayingItemId); - return; - } + Logger.LogError("JoinGroup: {0} is not a valid format for PlayingItemId. Ignoring request.", request.PlayingItemId); + return; } var joinRequest = new JoinGroupRequest() @@ -220,12 +217,9 @@ namespace MediaBrowser.Api.SyncPlay var currentSession = GetSession(_sessionContext); var filterItemId = Guid.Empty; - if (!String.IsNullOrEmpty(request.FilterItemId)) + if (!String.IsNullOrEmpty(request.FilterItemId) && !Guid.TryParse(request.FilterItemId, out filterItemId)) { - if (!Guid.TryParse(request.FilterItemId, out filterItemId)) - { - Logger.LogWarning("ListGroups: {0} is not a valid format for FilterItemId. Ignoring filter.", request.FilterItemId); - } + Logger.LogWarning("ListGroups: {0} is not a valid format for FilterItemId. Ignoring filter.", request.FilterItemId); } return _syncPlayManager.ListGroups(currentSession, filterItemId); -- cgit v1.2.3 From e42bfc92f3e072a3d51dddce06bc90587e06791c Mon Sep 17 00:00:00 2001 From: gion Date: Tue, 26 May 2020 11:37:52 +0200 Subject: Fix code issues --- .../HttpServer/WebSocketConnection.cs | 6 ++-- .../Session/SessionWebSocketListener.cs | 10 +++--- .../SyncPlay/SyncPlayController.cs | 37 ++-------------------- .../SyncPlay/SyncPlayManager.cs | 1 + MediaBrowser.Api/SyncPlay/SyncPlayService.cs | 4 +-- MediaBrowser.Controller/SyncPlay/GroupInfo.cs | 9 +++--- 6 files changed, 19 insertions(+), 48 deletions(-) (limited to 'Emby.Server.Implementations/Session') diff --git a/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs b/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs index 1f5a7d177..0680c5ffe 100644 --- a/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs +++ b/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs @@ -223,7 +223,7 @@ namespace Emby.Server.Implementations.HttpServer if (info.MessageType.Equals("KeepAlive", StringComparison.Ordinal)) { - SendKeepAliveResponse(); + await SendKeepAliveResponse(); } else { @@ -231,10 +231,10 @@ namespace Emby.Server.Implementations.HttpServer } } - private void SendKeepAliveResponse() + private Task SendKeepAliveResponse() { LastKeepAliveDate = DateTime.UtcNow; - SendAsync(new WebSocketMessage + return SendAsync(new WebSocketMessage { MessageType = "KeepAlive" }, CancellationToken.None); diff --git a/Emby.Server.Implementations/Session/SessionWebSocketListener.cs b/Emby.Server.Implementations/Session/SessionWebSocketListener.cs index 3af18f681..e7b4b0ec3 100644 --- a/Emby.Server.Implementations/Session/SessionWebSocketListener.cs +++ b/Emby.Server.Implementations/Session/SessionWebSocketListener.cs @@ -87,13 +87,13 @@ namespace Emby.Server.Implementations.Session httpServer.WebSocketConnected += OnServerManagerWebSocketConnected; } - private void OnServerManagerWebSocketConnected(object sender, GenericEventArgs e) + private async void OnServerManagerWebSocketConnected(object sender, GenericEventArgs e) { var session = GetSession(e.Argument.QueryString, e.Argument.RemoteEndPoint.ToString()); if (session != null) { EnsureController(session, e.Argument); - KeepAliveWebSocket(e.Argument); + await KeepAliveWebSocket(e.Argument); } else { @@ -149,7 +149,7 @@ namespace Emby.Server.Implementations.Session /// The event arguments. private void OnWebSocketClosed(object sender, EventArgs e) { - var webSocket = (IWebSocketConnection) sender; + var webSocket = (IWebSocketConnection)sender; _logger.LogDebug("WebSocket {0} is closed.", webSocket); RemoveWebSocket(webSocket); } @@ -158,7 +158,7 @@ namespace Emby.Server.Implementations.Session /// Adds a WebSocket to the KeepAlive watchlist. /// /// The WebSocket to monitor. - private void KeepAliveWebSocket(IWebSocketConnection webSocket) + private async Task KeepAliveWebSocket(IWebSocketConnection webSocket) { lock (_webSocketsLock) { @@ -176,7 +176,7 @@ namespace Emby.Server.Implementations.Session // Notify WebSocket about timeout try { - SendForceKeepAlive(webSocket).Wait(); + await SendForceKeepAlive(webSocket); } catch (WebSocketException exception) { diff --git a/Emby.Server.Implementations/SyncPlay/SyncPlayController.cs b/Emby.Server.Implementations/SyncPlay/SyncPlayController.cs index c7bd242a7..d430d4d16 100644 --- a/Emby.Server.Implementations/SyncPlay/SyncPlayController.cs +++ b/Emby.Server.Implementations/SyncPlay/SyncPlayController.cs @@ -16,7 +16,7 @@ namespace Emby.Server.Implementations.SyncPlay /// /// Class is not thread-safe, external locking is required when accessing methods. /// - public class SyncPlayController : ISyncPlayController, IDisposable + public class SyncPlayController : ISyncPlayController { /// /// Used to filter the sessions of a group. @@ -65,8 +65,6 @@ namespace Emby.Server.Implementations.SyncPlay /// public bool IsGroupEmpty() => _group.IsEmpty(); - private bool _disposed = false; - public SyncPlayController( ISessionManager sessionManager, ISyncPlayManager syncPlayManager) @@ -75,36 +73,6 @@ namespace Emby.Server.Implementations.SyncPlay _syncPlayManager = syncPlayManager; } - /// - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - /// - /// Releases unmanaged and optionally managed resources. - /// - /// true to release both managed and unmanaged resources; false to release only unmanaged resources. - protected virtual void Dispose(bool disposing) - { - if (_disposed) - { - return; - } - - _disposed = true; - } - - // TODO: use this somewhere - private void CheckDisposed() - { - if (_disposed) - { - throw new ObjectDisposedException(GetType().Name); - } - } - /// /// Converts DateTime to UTC string. /// @@ -518,6 +486,7 @@ namespace Emby.Server.Implementations.SyncPlay var runTimeTicks = _group.PlayingItem.RunTimeTicks ?? 0; ticks = ticks > runTimeTicks ? runTimeTicks : ticks; } + return ticks; } @@ -541,7 +510,7 @@ namespace Emby.Server.Implementations.SyncPlay PlayingItemName = _group.PlayingItem.Name, PlayingItemId = _group.PlayingItem.Id.ToString(), PositionTicks = _group.PositionTicks, - Participants = _group.Participants.Values.Select(session => session.Session.UserName).Distinct().ToList().AsReadOnly() + Participants = _group.Participants.Values.Select(session => session.Session.UserName).Distinct().ToList() }; } } diff --git a/Emby.Server.Implementations/SyncPlay/SyncPlayManager.cs b/Emby.Server.Implementations/SyncPlay/SyncPlayManager.cs index 93cec1304..1f76dd4e3 100644 --- a/Emby.Server.Implementations/SyncPlay/SyncPlayManager.cs +++ b/Emby.Server.Implementations/SyncPlay/SyncPlayManager.cs @@ -374,6 +374,7 @@ namespace Emby.Server.Implementations.SyncPlay { throw new InvalidOperationException("Session in other group already!"); } + _sessionToGroupMap[session.Id] = group; } diff --git a/MediaBrowser.Api/SyncPlay/SyncPlayService.cs b/MediaBrowser.Api/SyncPlay/SyncPlayService.cs index 8064ea7dc..1e14ea552 100644 --- a/MediaBrowser.Api/SyncPlay/SyncPlayService.cs +++ b/MediaBrowser.Api/SyncPlay/SyncPlayService.cs @@ -182,7 +182,7 @@ namespace MediaBrowser.Api.SyncPlay } // Both null and empty strings mean that client isn't playing anything - if (!String.IsNullOrEmpty(request.PlayingItemId) && !Guid.TryParse(request.PlayingItemId, out playingItemId)) + if (!string.IsNullOrEmpty(request.PlayingItemId) && !Guid.TryParse(request.PlayingItemId, out playingItemId)) { Logger.LogError("JoinGroup: {0} is not a valid format for PlayingItemId. Ignoring request.", request.PlayingItemId); return; @@ -217,7 +217,7 @@ namespace MediaBrowser.Api.SyncPlay var currentSession = GetSession(_sessionContext); var filterItemId = Guid.Empty; - if (!String.IsNullOrEmpty(request.FilterItemId) && !Guid.TryParse(request.FilterItemId, out filterItemId)) + if (!string.IsNullOrEmpty(request.FilterItemId) && !Guid.TryParse(request.FilterItemId, out filterItemId)) { Logger.LogWarning("ListGroups: {0} is not a valid format for FilterItemId. Ignoring filter.", request.FilterItemId); } diff --git a/MediaBrowser.Controller/SyncPlay/GroupInfo.cs b/MediaBrowser.Controller/SyncPlay/GroupInfo.cs index bda49bd1b..28a3ac505 100644 --- a/MediaBrowser.Controller/SyncPlay/GroupInfo.cs +++ b/MediaBrowser.Controller/SyncPlay/GroupInfo.cs @@ -16,12 +16,13 @@ namespace MediaBrowser.Controller.SyncPlay /// /// Default ping value used for sessions. /// - public readonly long DefaulPing = 500; + public long DefaulPing { get; } = 500; + /// /// Gets or sets the group identifier. /// /// The group identifier. - public readonly Guid GroupId = Guid.NewGuid(); + public Guid GroupId { get; } = Guid.NewGuid(); /// /// Gets or sets the playing item. @@ -51,7 +52,7 @@ namespace MediaBrowser.Controller.SyncPlay /// Gets the participants. /// /// The participants, or members of the group. - public readonly Dictionary Participants = + public Dictionary Participants { get; } = new Dictionary(StringComparer.OrdinalIgnoreCase); /// @@ -85,7 +86,6 @@ namespace MediaBrowser.Controller.SyncPlay /// Removes the session from the group. /// /// The session. - public void RemoveSession(SessionInfo session) { if (!ContainsSession(session.Id.ToString())) @@ -153,6 +153,7 @@ namespace MediaBrowser.Controller.SyncPlay return true; } } + return false; } -- cgit v1.2.3 From 4748105dce13c0fe0b4d8fcbf44f26033d314b26 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Fri, 29 May 2020 11:28:19 +0200 Subject: Enable TreatWarningsAsErrors for Jellyfin.Server.Implementations in Release mode --- .../Channels/ChannelManager.cs | 4 +- .../Data/SqliteItemRepository.cs | 5 +- .../Emby.Server.Implementations.csproj | 1 + .../EntryPoints/UdpServerEntryPoint.cs | 3 +- .../HttpServer/HttpResultFactory.cs | 1 + Emby.Server.Implementations/IStartupOptions.cs | 2 + .../Library/DefaultAuthenticationProvider.cs | 38 ---- .../Library/LibraryManager.cs | 8 +- .../LiveTv/LiveTvManager.cs | 2 - .../LiveTv/RefreshChannelsScheduledTask.cs | 2 + .../MediaEncoder/EncodingManager.cs | 2 + Emby.Server.Implementations/Net/SocketFactory.cs | 2 + Emby.Server.Implementations/Net/UdpSocket.cs | 2 + .../Networking/NetworkManager.cs | 2 + .../Playlists/ManualPlaylistsFolder.cs | 2 + .../Playlists/PlaylistImageProvider.cs | 6 +- .../Playlists/PlaylistManager.cs | 2 + Emby.Server.Implementations/ResourceFileManager.cs | 2 + .../ScheduledTasks/ScheduledTaskWorker.cs | 11 +- .../ScheduledTasks/TaskManager.cs | 4 +- .../ScheduledTasks/Tasks/ChapterImagesTask.cs | 7 + .../ScheduledTasks/Tasks/DeleteCacheFileTask.cs | 7 + .../ScheduledTasks/Tasks/DeleteLogFileTask.cs | 9 + .../Tasks/DeleteTranscodeFileTask.cs | 7 + .../ScheduledTasks/Tasks/PeopleValidationTask.cs | 10 +- .../ScheduledTasks/Tasks/PluginUpdateTask.cs | 2 + .../Tasks/RefreshMediaLibraryTask.cs | 17 +- .../ScheduledTasks/Triggers/DailyTrigger.cs | 7 +- .../ScheduledTasks/Triggers/IntervalTrigger.cs | 2 + .../ScheduledTasks/Triggers/StartupTrigger.cs | 6 +- .../ScheduledTasks/Triggers/WeeklyTrigger.cs | 2 + .../Security/AuthenticationRepository.cs | 2 + .../Serialization/JsonSerializer.cs | 5 + Emby.Server.Implementations/Services/HttpResult.cs | 2 + .../Services/RequestHelper.cs | 7 +- .../Services/ResponseHelper.cs | 5 +- .../Services/ServiceController.cs | 2 + .../Services/ServiceExec.cs | 2 + .../Services/ServiceHandler.cs | 2 + .../Services/ServiceMethod.cs | 2 + .../Services/ServicePath.cs | 2 + .../Services/StringMapTypeDeserializer.cs | 2 + .../Services/SwaggerService.cs | 2 + .../Services/UrlExtensions.cs | 2 + .../Session/SessionManager.cs | 2 + .../SocketSharp/HttpFile.cs | 18 -- .../SocketSharp/HttpPostedFile.cs | 198 --------------------- .../SocketSharp/WebSocketSharpRequest.cs | 2 + .../Sorting/AiredEpisodeOrderComparer.cs | 2 + .../Sorting/CommunityRatingComparer.cs | 18 +- .../Sorting/DateLastMediaAddedComparer.cs | 18 +- .../Sorting/IsFavoriteOrLikeComparer.cs | 38 ++-- .../Sorting/IsFolderComparer.cs | 14 +- .../Sorting/IsPlayedComparer.cs | 38 ++-- .../Sorting/IsUnplayedComparer.cs | 38 ++-- .../Sorting/OfficialRatingComparer.cs | 14 +- .../Sorting/SeriesSortNameComparer.cs | 14 +- .../Sorting/StartDateComparer.cs | 19 +- .../Sorting/StudioComparer.cs | 2 + .../SyncPlay/SyncPlayController.cs | 5 + .../SyncPlay/SyncPlayManager.cs | 7 + Emby.Server.Implementations/TV/TVSeriesManager.cs | 7 +- .../Updates/InstallationManager.cs | 4 +- .../UserViews/CollectionFolderImageProvider.cs | 2 + .../UserViews/DynamicImageProvider.cs | 6 +- .../UserViews/FolderImageProvider.cs | 6 +- Jellyfin.Server/Migrations/IMigrationRoutine.cs | 1 - .../Routines/CreateUserLoggingConfigFile.cs | 1 - Jellyfin.Server/Program.cs | 4 +- 69 files changed, 291 insertions(+), 401 deletions(-) delete mode 100644 Emby.Server.Implementations/SocketSharp/HttpFile.cs delete mode 100644 Emby.Server.Implementations/SocketSharp/HttpPostedFile.cs (limited to 'Emby.Server.Implementations/Session') diff --git a/Emby.Server.Implementations/Channels/ChannelManager.cs b/Emby.Server.Implementations/Channels/ChannelManager.cs index 138832fb8..04fe0bacb 100644 --- a/Emby.Server.Implementations/Channels/ChannelManager.cs +++ b/Emby.Server.Implementations/Channels/ChannelManager.cs @@ -46,14 +46,14 @@ namespace Emby.Server.Implementations.Channels new ConcurrentDictionary>>(); private readonly SemaphoreSlim _resourcePool = new SemaphoreSlim(1, 1); - + /// /// Initializes a new instance of the class. /// /// The user manager. /// The dto service. /// The library manager. - /// The logger factory. + /// The logger. /// The server configuration manager. /// The filesystem. /// The user data manager. diff --git a/Emby.Server.Implementations/Data/SqliteItemRepository.cs b/Emby.Server.Implementations/Data/SqliteItemRepository.cs index ca5cd6fdd..58702541e 100644 --- a/Emby.Server.Implementations/Data/SqliteItemRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteItemRepository.cs @@ -1,3 +1,5 @@ +#pragma warning disable CS1591 + using System; using System.Collections.Generic; using System.Globalization; @@ -33,7 +35,7 @@ using SQLitePCL.pretty; namespace Emby.Server.Implementations.Data { /// - /// Class SQLiteItemRepository + /// Class SQLiteItemRepository. /// public class SqliteItemRepository : BaseSqliteRepository, IItemRepository { @@ -1971,6 +1973,7 @@ namespace Emby.Server.Implementations.Data /// Gets the chapter. /// /// The reader. + /// The item. /// ChapterInfo. private ChapterInfo GetChapter(IReadOnlyList reader, BaseItem item) { diff --git a/Emby.Server.Implementations/Emby.Server.Implementations.csproj b/Emby.Server.Implementations/Emby.Server.Implementations.csproj index b69a126b3..279ec3098 100644 --- a/Emby.Server.Implementations/Emby.Server.Implementations.csproj +++ b/Emby.Server.Implementations/Emby.Server.Implementations.csproj @@ -54,6 +54,7 @@ netstandard2.1 false true + true diff --git a/Emby.Server.Implementations/EntryPoints/UdpServerEntryPoint.cs b/Emby.Server.Implementations/EntryPoints/UdpServerEntryPoint.cs index 6929c81f9..5bc1a81aa 100644 --- a/Emby.Server.Implementations/EntryPoints/UdpServerEntryPoint.cs +++ b/Emby.Server.Implementations/EntryPoints/UdpServerEntryPoint.cs @@ -47,10 +47,11 @@ namespace Emby.Server.Implementations.EntryPoints } /// - public async Task RunAsync() + public Task RunAsync() { _udpServer = new UdpServer(_logger, _appHost, _config); _udpServer.Start(PortNumber, _cancellationTokenSource.Token); + return Task.CompletedTask; } /// diff --git a/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs b/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs index 2e9ecc4ae..dd7f753cc 100644 --- a/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs +++ b/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs @@ -56,6 +56,7 @@ namespace Emby.Server.Implementations.HttpServer /// /// Gets the result. /// + /// The request context. /// The content. /// Type of the content. /// The response headers. diff --git a/Emby.Server.Implementations/IStartupOptions.cs b/Emby.Server.Implementations/IStartupOptions.cs index acae702f3..0b9f80538 100644 --- a/Emby.Server.Implementations/IStartupOptions.cs +++ b/Emby.Server.Implementations/IStartupOptions.cs @@ -1,3 +1,5 @@ +#pragma warning disable CS1591 + using System; namespace Emby.Server.Implementations diff --git a/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs b/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs index 52c8facc3..02f150607 100644 --- a/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs +++ b/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs @@ -135,43 +135,5 @@ namespace Emby.Server.Implementations.Library ? null : Hex.Encode(PasswordHash.Parse(user.EasyPassword).Hash); } - - /// - /// Gets the hashed string. - /// - public string GetHashedString(User user, string str) - { - if (string.IsNullOrEmpty(user.Password)) - { - return _cryptographyProvider.CreatePasswordHash(str).ToString(); - } - - // TODO: make use of iterations parameter? - PasswordHash passwordHash = PasswordHash.Parse(user.Password); - var salt = passwordHash.Salt.ToArray(); - return new PasswordHash( - passwordHash.Id, - _cryptographyProvider.ComputeHash( - passwordHash.Id, - Encoding.UTF8.GetBytes(str), - salt), - salt, - passwordHash.Parameters.ToDictionary(x => x.Key, y => y.Value)).ToString(); - } - - public ReadOnlySpan GetHashed(User user, string str) - { - if (string.IsNullOrEmpty(user.Password)) - { - return _cryptographyProvider.CreatePasswordHash(str).Hash; - } - - // TODO: make use of iterations parameter? - PasswordHash passwordHash = PasswordHash.Parse(user.Password); - return _cryptographyProvider.ComputeHash( - passwordHash.Id, - Encoding.UTF8.GetBytes(str), - passwordHash.Salt.ToArray()); - } } } diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs index 0b86b2db7..67a72d313 100644 --- a/Emby.Server.Implementations/Library/LibraryManager.cs +++ b/Emby.Server.Implementations/Library/LibraryManager.cs @@ -50,7 +50,7 @@ using VideoResolver = Emby.Naming.Video.VideoResolver; namespace Emby.Server.Implementations.Library { /// - /// Class LibraryManager + /// Class LibraryManager. /// public class LibraryManager : ILibraryManager { @@ -135,6 +135,12 @@ namespace Emby.Server.Implementations.Library /// The user manager. /// The configuration manager. /// The user data repository. + /// The library monitor. + /// The file system. + /// The provider manager. + /// The userview manager. + /// The media encoder. + /// The item repository. public LibraryManager( IServerApplicationHost appHost, ILogger logger, diff --git a/Emby.Server.Implementations/LiveTv/LiveTvManager.cs b/Emby.Server.Implementations/LiveTv/LiveTvManager.cs index 1b10f2d27..3e48425a2 100644 --- a/Emby.Server.Implementations/LiveTv/LiveTvManager.cs +++ b/Emby.Server.Implementations/LiveTv/LiveTvManager.cs @@ -10,10 +10,8 @@ using Emby.Server.Implementations.Library; using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Progress; -using MediaBrowser.Controller; using MediaBrowser.Controller.Channels; using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Movies; diff --git a/Emby.Server.Implementations/LiveTv/RefreshChannelsScheduledTask.cs b/Emby.Server.Implementations/LiveTv/RefreshChannelsScheduledTask.cs index 1056a33b9..8e7d60a15 100644 --- a/Emby.Server.Implementations/LiveTv/RefreshChannelsScheduledTask.cs +++ b/Emby.Server.Implementations/LiveTv/RefreshChannelsScheduledTask.cs @@ -1,3 +1,5 @@ +#pragma warning disable CS1591 + using System; using System.Collections.Generic; using System.Threading.Tasks; diff --git a/Emby.Server.Implementations/MediaEncoder/EncodingManager.cs b/Emby.Server.Implementations/MediaEncoder/EncodingManager.cs index 677d68b4c..7b7575707 100644 --- a/Emby.Server.Implementations/MediaEncoder/EncodingManager.cs +++ b/Emby.Server.Implementations/MediaEncoder/EncodingManager.cs @@ -1,3 +1,5 @@ +#pragma warning disable CS1591 + using System; using System.Collections.Generic; using System.Globalization; diff --git a/Emby.Server.Implementations/Net/SocketFactory.cs b/Emby.Server.Implementations/Net/SocketFactory.cs index e42ff8496..f347540c7 100644 --- a/Emby.Server.Implementations/Net/SocketFactory.cs +++ b/Emby.Server.Implementations/Net/SocketFactory.cs @@ -1,3 +1,5 @@ +#pragma warning disable CS1591 + using System; using System.Net; using System.Net.Sockets; diff --git a/Emby.Server.Implementations/Net/UdpSocket.cs b/Emby.Server.Implementations/Net/UdpSocket.cs index 211ca6784..848f82d85 100644 --- a/Emby.Server.Implementations/Net/UdpSocket.cs +++ b/Emby.Server.Implementations/Net/UdpSocket.cs @@ -1,3 +1,5 @@ +#pragma warning disable CS1591 + using System; using System.Net; using System.Net.Sockets; diff --git a/Emby.Server.Implementations/Networking/NetworkManager.cs b/Emby.Server.Implementations/Networking/NetworkManager.cs index b3e88b667..d1a28e7a1 100644 --- a/Emby.Server.Implementations/Networking/NetworkManager.cs +++ b/Emby.Server.Implementations/Networking/NetworkManager.cs @@ -1,3 +1,5 @@ +#pragma warning disable CS1591 + using System; using System.Collections.Generic; using System.Globalization; diff --git a/Emby.Server.Implementations/Playlists/ManualPlaylistsFolder.cs b/Emby.Server.Implementations/Playlists/ManualPlaylistsFolder.cs index cd9f7946e..889760586 100644 --- a/Emby.Server.Implementations/Playlists/ManualPlaylistsFolder.cs +++ b/Emby.Server.Implementations/Playlists/ManualPlaylistsFolder.cs @@ -1,3 +1,5 @@ +#pragma warning disable CS1591 + using System.Collections.Generic; using System.Linq; using System.Text.Json.Serialization; diff --git a/Emby.Server.Implementations/Playlists/PlaylistImageProvider.cs b/Emby.Server.Implementations/Playlists/PlaylistImageProvider.cs index bb56d9771..f8a2d9741 100644 --- a/Emby.Server.Implementations/Playlists/PlaylistImageProvider.cs +++ b/Emby.Server.Implementations/Playlists/PlaylistImageProvider.cs @@ -1,3 +1,5 @@ +#pragma warning disable CS1591 + using System.Collections.Generic; using System.Linq; using Emby.Server.Implementations.Images; @@ -32,9 +34,7 @@ namespace Emby.Server.Implementations.Playlists { var subItem = i.Item2; - var episode = subItem as Episode; - - if (episode != null) + if (subItem is Episode episode) { var series = episode.Series; if (series != null && series.HasImage(ImageType.Primary)) diff --git a/Emby.Server.Implementations/Playlists/PlaylistManager.cs b/Emby.Server.Implementations/Playlists/PlaylistManager.cs index 9b1510ac9..d4d1c1ff7 100644 --- a/Emby.Server.Implementations/Playlists/PlaylistManager.cs +++ b/Emby.Server.Implementations/Playlists/PlaylistManager.cs @@ -1,3 +1,5 @@ +#pragma warning disable CS1591 + using System; using System.Collections.Generic; using System.Globalization; diff --git a/Emby.Server.Implementations/ResourceFileManager.cs b/Emby.Server.Implementations/ResourceFileManager.cs index 6eda2b503..d192be921 100644 --- a/Emby.Server.Implementations/ResourceFileManager.cs +++ b/Emby.Server.Implementations/ResourceFileManager.cs @@ -1,3 +1,5 @@ +#pragma warning disable CS1591 + using System; using System.IO; using MediaBrowser.Controller; diff --git a/Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs b/Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs index 5b188d962..dc3e9a607 100644 --- a/Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs +++ b/Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs @@ -1,3 +1,5 @@ +#pragma warning disable CS1591 + using System; using System.Globalization; using System.IO; @@ -51,7 +53,6 @@ namespace Emby.Server.Implementations.ScheduledTasks /// /// The task manager. private ITaskManager TaskManager { get; set; } - private readonly IFileSystem _fileSystem; /// /// Initializes a new instance of the class. @@ -72,24 +73,28 @@ namespace Emby.Server.Implementations.ScheduledTasks /// or /// logger /// - public ScheduledTaskWorker(IScheduledTask scheduledTask, IApplicationPaths applicationPaths, ITaskManager taskManager, IJsonSerializer jsonSerializer, ILogger logger, IFileSystem fileSystem) + public ScheduledTaskWorker(IScheduledTask scheduledTask, IApplicationPaths applicationPaths, ITaskManager taskManager, IJsonSerializer jsonSerializer, ILogger logger) { if (scheduledTask == null) { throw new ArgumentNullException(nameof(scheduledTask)); } + if (applicationPaths == null) { throw new ArgumentNullException(nameof(applicationPaths)); } + if (taskManager == null) { throw new ArgumentNullException(nameof(taskManager)); } + if (jsonSerializer == null) { throw new ArgumentNullException(nameof(jsonSerializer)); } + if (logger == null) { throw new ArgumentNullException(nameof(logger)); @@ -100,7 +105,6 @@ namespace Emby.Server.Implementations.ScheduledTasks TaskManager = taskManager; JsonSerializer = jsonSerializer; Logger = logger; - _fileSystem = fileSystem; InitTriggerEvents(); } @@ -576,6 +580,7 @@ namespace Emby.Server.Implementations.ScheduledTasks /// The start time. /// The end time. /// The status. + /// The exception. private void OnTaskCompleted(DateTime startTime, DateTime endTime, TaskCompletionStatus status, Exception ex) { var elapsedTime = endTime - startTime; diff --git a/Emby.Server.Implementations/ScheduledTasks/TaskManager.cs b/Emby.Server.Implementations/ScheduledTasks/TaskManager.cs index 6ffa581a9..907680239 100644 --- a/Emby.Server.Implementations/ScheduledTasks/TaskManager.cs +++ b/Emby.Server.Implementations/ScheduledTasks/TaskManager.cs @@ -1,3 +1,5 @@ +#pragma warning disable CS1591 + using System; using System.Collections.Concurrent; using System.Collections.Generic; @@ -199,7 +201,7 @@ namespace Emby.Server.Implementations.ScheduledTasks /// The tasks. public void AddTasks(IEnumerable tasks) { - var list = tasks.Select(t => new ScheduledTaskWorker(t, _applicationPaths, this, _jsonSerializer, _logger, _fileSystem)); + var list = tasks.Select(t => new ScheduledTaskWorker(t, _applicationPaths, this, _jsonSerializer, _logger)); ScheduledTasks = ScheduledTasks.Concat(list).ToArray(); } diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/ChapterImagesTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/ChapterImagesTask.cs index ea6a70615..fae049914 100644 --- a/Emby.Server.Implementations/ScheduledTasks/Tasks/ChapterImagesTask.cs +++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/ChapterImagesTask.cs @@ -169,18 +169,25 @@ namespace Emby.Server.Implementations.ScheduledTasks } } + /// public string Name => _localization.GetLocalizedString("TaskRefreshChapterImages"); + /// public string Description => _localization.GetLocalizedString("TaskRefreshChapterImagesDescription"); + /// public string Category => _localization.GetLocalizedString("TasksLibraryCategory"); + /// public string Key => "RefreshChapterImages"; + /// public bool IsHidden => false; + /// public bool IsEnabled => true; + /// public bool IsLogged => true; } } diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteCacheFileTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteCacheFileTask.cs index 9df7c538b..a6c13eaef 100644 --- a/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteCacheFileTask.cs +++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteCacheFileTask.cs @@ -165,18 +165,25 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks } } + /// public string Name => _localization.GetLocalizedString("TaskCleanCache"); + /// public string Description => _localization.GetLocalizedString("TaskCleanCacheDescription"); + /// public string Category => _localization.GetLocalizedString("TasksMaintenanceCategory"); + /// public string Key => "DeleteCacheFiles"; + /// public bool IsHidden => false; + /// public bool IsEnabled => true; + /// public bool IsLogged => true; } } diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteLogFileTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteLogFileTask.cs index 3140aa489..402b39a26 100644 --- a/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteLogFileTask.cs +++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteLogFileTask.cs @@ -28,6 +28,8 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks /// Initializes a new instance of the class. /// /// The configuration manager. + /// The file system. + /// The localization manager. public DeleteLogFileTask(IConfigurationManager configurationManager, IFileSystem fileSystem, ILocalizationManager localization) { ConfigurationManager = configurationManager; @@ -82,18 +84,25 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks return Task.CompletedTask; } + /// public string Name => _localization.GetLocalizedString("TaskCleanLogs"); + /// public string Description => string.Format(_localization.GetLocalizedString("TaskCleanLogsDescription"), ConfigurationManager.CommonConfiguration.LogFileRetentionDays); + /// public string Category => _localization.GetLocalizedString("TasksMaintenanceCategory"); + /// public string Key => "CleanLogFiles"; + /// public bool IsHidden => false; + /// public bool IsEnabled => true; + /// public bool IsLogged => true; } } diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteTranscodeFileTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteTranscodeFileTask.cs index 1d133dcda..0d36b82c0 100644 --- a/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteTranscodeFileTask.cs +++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteTranscodeFileTask.cs @@ -132,18 +132,25 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks } } + /// public string Name => _localization.GetLocalizedString("TaskCleanTranscode"); + /// public string Description => _localization.GetLocalizedString("TaskCleanTranscodeDescription"); + /// public string Category => _localization.GetLocalizedString("TasksMaintenanceCategory"); + /// public string Key => "DeleteTranscodeFiles"; + /// public bool IsHidden => false; + /// public bool IsEnabled => false; + /// public bool IsLogged => true; } } diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/PeopleValidationTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/PeopleValidationTask.cs index 63f867bf6..c384cf4bb 100644 --- a/Emby.Server.Implementations/ScheduledTasks/Tasks/PeopleValidationTask.cs +++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/PeopleValidationTask.cs @@ -1,8 +1,9 @@ +#pragma warning disable CS1591 + using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; -using MediaBrowser.Controller; using MediaBrowser.Controller.Library; using MediaBrowser.Model.Tasks; using MediaBrowser.Model.Globalization; @@ -18,19 +19,16 @@ namespace Emby.Server.Implementations.ScheduledTasks /// The library manager. /// private readonly ILibraryManager _libraryManager; - - private readonly IServerApplicationHost _appHost; private readonly ILocalizationManager _localization; /// /// Initializes a new instance of the class. /// /// The library manager. - /// The server application host - public PeopleValidationTask(ILibraryManager libraryManager, IServerApplicationHost appHost, ILocalizationManager localization) + /// The localization manager. + public PeopleValidationTask(ILibraryManager libraryManager, ILocalizationManager localization) { _libraryManager = libraryManager; - _appHost = appHost; _localization = localization; } diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/PluginUpdateTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/PluginUpdateTask.cs index 6a1afced7..9d9d77538 100644 --- a/Emby.Server.Implementations/ScheduledTasks/Tasks/PluginUpdateTask.cs +++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/PluginUpdateTask.cs @@ -1,3 +1,5 @@ +#pragma warning disable CS1591 + using System; using System.Collections.Generic; using System.IO; diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/RefreshMediaLibraryTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/RefreshMediaLibraryTask.cs index 74cb01444..e470adcf4 100644 --- a/Emby.Server.Implementations/ScheduledTasks/Tasks/RefreshMediaLibraryTask.cs +++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/RefreshMediaLibraryTask.cs @@ -1,9 +1,10 @@ +#pragma warning disable CS1591 + using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using Emby.Server.Implementations.Library; -using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Library; using MediaBrowser.Model.Tasks; using MediaBrowser.Model.Globalization; @@ -16,20 +17,19 @@ namespace Emby.Server.Implementations.ScheduledTasks public class RefreshMediaLibraryTask : IScheduledTask { /// - /// The _library manager + /// The _library manager. /// private readonly ILibraryManager _libraryManager; - private readonly IServerConfigurationManager _config; private readonly ILocalizationManager _localization; /// /// Initializes a new instance of the class. /// /// The library manager. - public RefreshMediaLibraryTask(ILibraryManager libraryManager, IServerConfigurationManager config, ILocalizationManager localization) + /// The localization manager. + public RefreshMediaLibraryTask(ILibraryManager libraryManager, ILocalizationManager localization) { _libraryManager = libraryManager; - _config = config; _localization = localization; } @@ -61,18 +61,25 @@ namespace Emby.Server.Implementations.ScheduledTasks return ((LibraryManager)_libraryManager).ValidateMediaLibraryInternal(progress, cancellationToken); } + /// public string Name => _localization.GetLocalizedString("TaskRefreshLibrary"); + /// public string Description => _localization.GetLocalizedString("TaskRefreshLibraryDescription"); + /// public string Category => _localization.GetLocalizedString("TasksLibraryCategory"); + /// public string Key => "RefreshLibrary"; + /// public bool IsHidden => false; + /// public bool IsEnabled => true; + /// public bool IsLogged => true; } } diff --git a/Emby.Server.Implementations/ScheduledTasks/Triggers/DailyTrigger.cs b/Emby.Server.Implementations/ScheduledTasks/Triggers/DailyTrigger.cs index ea278de0d..c7819d4c0 100644 --- a/Emby.Server.Implementations/ScheduledTasks/Triggers/DailyTrigger.cs +++ b/Emby.Server.Implementations/ScheduledTasks/Triggers/DailyTrigger.cs @@ -31,6 +31,8 @@ namespace Emby.Server.Implementations.ScheduledTasks /// Stars waiting for the trigger action /// /// The last result. + /// The logger. + /// The name of the task. /// if set to true [is application startup]. public void Start(TaskResult lastResult, ILogger logger, string taskName, bool isApplicationStartup) { @@ -77,10 +79,7 @@ namespace Emby.Server.Implementations.ScheduledTasks /// private void OnTriggered() { - if (Triggered != null) - { - Triggered(this, EventArgs.Empty); - } + Triggered?.Invoke(this, EventArgs.Empty); } } } diff --git a/Emby.Server.Implementations/ScheduledTasks/Triggers/IntervalTrigger.cs b/Emby.Server.Implementations/ScheduledTasks/Triggers/IntervalTrigger.cs index 3a34da3af..74cd4ef1e 100644 --- a/Emby.Server.Implementations/ScheduledTasks/Triggers/IntervalTrigger.cs +++ b/Emby.Server.Implementations/ScheduledTasks/Triggers/IntervalTrigger.cs @@ -34,6 +34,8 @@ namespace Emby.Server.Implementations.ScheduledTasks /// Stars waiting for the trigger action /// /// The last result. + /// The logger. + /// The name of the task. /// if set to true [is application startup]. public void Start(TaskResult lastResult, ILogger logger, string taskName, bool isApplicationStartup) { diff --git a/Emby.Server.Implementations/ScheduledTasks/Triggers/StartupTrigger.cs b/Emby.Server.Implementations/ScheduledTasks/Triggers/StartupTrigger.cs index 08ff4f55f..e171a9e9f 100644 --- a/Emby.Server.Implementations/ScheduledTasks/Triggers/StartupTrigger.cs +++ b/Emby.Server.Implementations/ScheduledTasks/Triggers/StartupTrigger.cs @@ -1,3 +1,5 @@ +#pragma warning disable CS1591 + using System; using System.Threading.Tasks; using MediaBrowser.Model.Tasks; @@ -6,7 +8,7 @@ using Microsoft.Extensions.Logging; namespace Emby.Server.Implementations.ScheduledTasks { /// - /// Class StartupTaskTrigger + /// Class StartupTaskTrigger. /// public class StartupTrigger : ITaskTrigger { @@ -26,6 +28,8 @@ namespace Emby.Server.Implementations.ScheduledTasks /// Stars waiting for the trigger action /// /// The last result. + /// The logger. + /// The name of the task. /// if set to true [is application startup]. public async void Start(TaskResult lastResult, ILogger logger, string taskName, bool isApplicationStartup) { diff --git a/Emby.Server.Implementations/ScheduledTasks/Triggers/WeeklyTrigger.cs b/Emby.Server.Implementations/ScheduledTasks/Triggers/WeeklyTrigger.cs index 2a6a7b13c..ad0b57af6 100644 --- a/Emby.Server.Implementations/ScheduledTasks/Triggers/WeeklyTrigger.cs +++ b/Emby.Server.Implementations/ScheduledTasks/Triggers/WeeklyTrigger.cs @@ -37,6 +37,8 @@ namespace Emby.Server.Implementations.ScheduledTasks /// Stars waiting for the trigger action /// /// The last result. + /// The logger. + /// The name of the task. /// if set to true [is application startup]. public void Start(TaskResult lastResult, ILogger logger, string taskName, bool isApplicationStartup) { diff --git a/Emby.Server.Implementations/Security/AuthenticationRepository.cs b/Emby.Server.Implementations/Security/AuthenticationRepository.cs index 4e4029f06..750890ec8 100644 --- a/Emby.Server.Implementations/Security/AuthenticationRepository.cs +++ b/Emby.Server.Implementations/Security/AuthenticationRepository.cs @@ -1,3 +1,5 @@ +#pragma warning disable CS1591 + using System; using System.Collections.Generic; using System.Globalization; diff --git a/Emby.Server.Implementations/Serialization/JsonSerializer.cs b/Emby.Server.Implementations/Serialization/JsonSerializer.cs index bcc814daf..5ec3a735a 100644 --- a/Emby.Server.Implementations/Serialization/JsonSerializer.cs +++ b/Emby.Server.Implementations/Serialization/JsonSerializer.cs @@ -1,3 +1,5 @@ +#pragma warning disable CS1591 + using System; using System.Globalization; using System.IO; @@ -11,6 +13,9 @@ namespace Emby.Server.Implementations.Serialization /// public class JsonSerializer : IJsonSerializer { + /// + /// Initializes a new instance of the class. + /// public JsonSerializer() { ServiceStack.Text.JsConfig.DateHandler = ServiceStack.Text.DateHandler.ISO8601; diff --git a/Emby.Server.Implementations/Services/HttpResult.cs b/Emby.Server.Implementations/Services/HttpResult.cs index 095193828..8ba86f756 100644 --- a/Emby.Server.Implementations/Services/HttpResult.cs +++ b/Emby.Server.Implementations/Services/HttpResult.cs @@ -1,3 +1,5 @@ +#pragma warning disable CS1591 + using System.Collections.Generic; using System.IO; using System.Net; diff --git a/Emby.Server.Implementations/Services/RequestHelper.cs b/Emby.Server.Implementations/Services/RequestHelper.cs index 2563cac99..1f9c7fc22 100644 --- a/Emby.Server.Implementations/Services/RequestHelper.cs +++ b/Emby.Server.Implementations/Services/RequestHelper.cs @@ -1,3 +1,5 @@ +#pragma warning disable CS1591 + using System; using System.IO; using System.Threading.Tasks; @@ -43,10 +45,7 @@ namespace Emby.Server.Implementations.Services private static string GetContentTypeWithoutEncoding(string contentType) { - return contentType == null - ? null - : contentType.Split(';')[0].ToLowerInvariant().Trim(); + return contentType?.Split(';')[0].ToLowerInvariant().Trim(); } - } } diff --git a/Emby.Server.Implementations/Services/ResponseHelper.cs b/Emby.Server.Implementations/Services/ResponseHelper.cs index a566b18dd..f2b1d06f3 100644 --- a/Emby.Server.Implementations/Services/ResponseHelper.cs +++ b/Emby.Server.Implementations/Services/ResponseHelper.cs @@ -1,3 +1,5 @@ +#pragma warning disable CS1591 + using System; using System.Globalization; using System.IO; @@ -43,8 +45,7 @@ namespace Emby.Server.Implementations.Services response.StatusCode = httpResult.Status; } - var responseOptions = result as IHasHeaders; - if (responseOptions != null) + if (result is IHasHeaders responseOptions) { foreach (var responseHeaders in responseOptions.Headers) { diff --git a/Emby.Server.Implementations/Services/ServiceController.cs b/Emby.Server.Implementations/Services/ServiceController.cs index e24a95dbb..ad6015c1c 100644 --- a/Emby.Server.Implementations/Services/ServiceController.cs +++ b/Emby.Server.Implementations/Services/ServiceController.cs @@ -1,3 +1,5 @@ +#pragma warning disable CS1591 + using System; using System.Collections.Generic; using System.Threading.Tasks; diff --git a/Emby.Server.Implementations/Services/ServiceExec.cs b/Emby.Server.Implementations/Services/ServiceExec.cs index 9f5f97028..606f2a240 100644 --- a/Emby.Server.Implementations/Services/ServiceExec.cs +++ b/Emby.Server.Implementations/Services/ServiceExec.cs @@ -1,3 +1,5 @@ +#pragma warning disable CS1591 + using System; using System.Collections.Generic; using System.Linq; diff --git a/Emby.Server.Implementations/Services/ServiceHandler.cs b/Emby.Server.Implementations/Services/ServiceHandler.cs index 934560de3..7f44357e1 100644 --- a/Emby.Server.Implementations/Services/ServiceHandler.cs +++ b/Emby.Server.Implementations/Services/ServiceHandler.cs @@ -1,3 +1,5 @@ +#pragma warning disable CS1591 + using System; using System.Collections.Generic; using System.Reflection; diff --git a/Emby.Server.Implementations/Services/ServiceMethod.cs b/Emby.Server.Implementations/Services/ServiceMethod.cs index 5018bf4a2..59ee5908f 100644 --- a/Emby.Server.Implementations/Services/ServiceMethod.cs +++ b/Emby.Server.Implementations/Services/ServiceMethod.cs @@ -1,3 +1,5 @@ +#pragma warning disable CS1591 + using System; namespace Emby.Server.Implementations.Services diff --git a/Emby.Server.Implementations/Services/ServicePath.cs b/Emby.Server.Implementations/Services/ServicePath.cs index 27c4dcba0..278379a92 100644 --- a/Emby.Server.Implementations/Services/ServicePath.cs +++ b/Emby.Server.Implementations/Services/ServicePath.cs @@ -1,3 +1,5 @@ +#pragma warning disable CS1591 + using System; using System.Collections.Generic; using System.Globalization; diff --git a/Emby.Server.Implementations/Services/StringMapTypeDeserializer.cs b/Emby.Server.Implementations/Services/StringMapTypeDeserializer.cs index 56e23d549..ab22fe019 100644 --- a/Emby.Server.Implementations/Services/StringMapTypeDeserializer.cs +++ b/Emby.Server.Implementations/Services/StringMapTypeDeserializer.cs @@ -1,3 +1,5 @@ +#pragma warning disable CS1591 + using System; using System.Collections.Generic; using System.Reflection; diff --git a/Emby.Server.Implementations/Services/SwaggerService.cs b/Emby.Server.Implementations/Services/SwaggerService.cs index 5177251c3..16142a70d 100644 --- a/Emby.Server.Implementations/Services/SwaggerService.cs +++ b/Emby.Server.Implementations/Services/SwaggerService.cs @@ -1,3 +1,5 @@ +#pragma warning disable CS1591 + using System; using System.Collections.Generic; using System.Linq; diff --git a/Emby.Server.Implementations/Services/UrlExtensions.cs b/Emby.Server.Implementations/Services/UrlExtensions.cs index 483c63ade..e3b6aa197 100644 --- a/Emby.Server.Implementations/Services/UrlExtensions.cs +++ b/Emby.Server.Implementations/Services/UrlExtensions.cs @@ -1,3 +1,5 @@ +#pragma warning disable CS1591 + using System; using MediaBrowser.Common.Extensions; diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs index 2b09a93ef..5c480e842 100644 --- a/Emby.Server.Implementations/Session/SessionManager.cs +++ b/Emby.Server.Implementations/Session/SessionManager.cs @@ -1,3 +1,5 @@ +#pragma warning disable CS1591 + using System; using System.Collections.Concurrent; using System.Collections.Generic; diff --git a/Emby.Server.Implementations/SocketSharp/HttpFile.cs b/Emby.Server.Implementations/SocketSharp/HttpFile.cs deleted file mode 100644 index 120ac50d9..000000000 --- a/Emby.Server.Implementations/SocketSharp/HttpFile.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System.IO; -using MediaBrowser.Model.Services; - -namespace Emby.Server.Implementations.SocketSharp -{ - public class HttpFile : IHttpFile - { - public string Name { get; set; } - - public string FileName { get; set; } - - public long ContentLength { get; set; } - - public string ContentType { get; set; } - - public Stream InputStream { get; set; } - } -} diff --git a/Emby.Server.Implementations/SocketSharp/HttpPostedFile.cs b/Emby.Server.Implementations/SocketSharp/HttpPostedFile.cs deleted file mode 100644 index 7479d8104..000000000 --- a/Emby.Server.Implementations/SocketSharp/HttpPostedFile.cs +++ /dev/null @@ -1,198 +0,0 @@ -using System; -using System.IO; - -public sealed class HttpPostedFile : IDisposable -{ - private string _name; - private string _contentType; - private Stream _stream; - private bool _disposed = false; - - internal HttpPostedFile(string name, string content_type, Stream base_stream, long offset, long length) - { - _name = name; - _contentType = content_type; - _stream = new ReadSubStream(base_stream, offset, length); - } - - public string ContentType => _contentType; - - public int ContentLength => (int)_stream.Length; - - public string FileName => _name; - - public Stream InputStream => _stream; - - /// - /// Releases the unmanaged resources and disposes of the managed resources used. - /// - public void Dispose() - { - if (_disposed) - { - return; - } - - _stream.Dispose(); - _stream = null; - - _name = null; - _contentType = null; - - _disposed = true; - } - - private class ReadSubStream : Stream - { - private Stream _stream; - private long _offset; - private long _end; - private long _position; - - public ReadSubStream(Stream s, long offset, long length) - { - _stream = s; - _offset = offset; - _end = offset + length; - _position = offset; - } - - public override bool CanRead => true; - - public override bool CanSeek => true; - - public override bool CanWrite => false; - - public override long Length => _end - _offset; - - public override long Position - { - get => _position - _offset; - set - { - if (value > Length) - { - throw new ArgumentOutOfRangeException(nameof(value)); - } - - _position = Seek(value, SeekOrigin.Begin); - } - } - - public override void Flush() - { - } - - public override int Read(byte[] buffer, int dest_offset, int count) - { - if (buffer == null) - { - throw new ArgumentNullException(nameof(buffer)); - } - - if (dest_offset < 0) - { - throw new ArgumentOutOfRangeException(nameof(dest_offset), "< 0"); - } - - if (count < 0) - { - throw new ArgumentOutOfRangeException(nameof(count), "< 0"); - } - - int len = buffer.Length; - if (dest_offset > len) - { - throw new ArgumentException("destination offset is beyond array size", nameof(dest_offset)); - } - - // reordered to avoid possible integer overflow - if (dest_offset > len - count) - { - throw new ArgumentException("Reading would overrun buffer", nameof(count)); - } - - if (count > _end - _position) - { - count = (int)(_end - _position); - } - - if (count <= 0) - { - return 0; - } - - _stream.Position = _position; - int result = _stream.Read(buffer, dest_offset, count); - if (result > 0) - { - _position += result; - } - else - { - _position = _end; - } - - return result; - } - - public override int ReadByte() - { - if (_position >= _end) - { - return -1; - } - - _stream.Position = _position; - int result = _stream.ReadByte(); - if (result < 0) - { - _position = _end; - } - else - { - _position++; - } - - return result; - } - - public override long Seek(long d, SeekOrigin origin) - { - long real; - switch (origin) - { - case SeekOrigin.Begin: - real = _offset + d; - break; - case SeekOrigin.End: - real = _end + d; - break; - case SeekOrigin.Current: - real = _position + d; - break; - default: - throw new ArgumentException("Unknown SeekOrigin value", nameof(origin)); - } - - long virt = real - _offset; - if (virt < 0 || virt > Length) - { - throw new ArgumentException("Invalid position", nameof(d)); - } - - _position = _stream.Seek(real, SeekOrigin.Begin); - return _position; - } - - public override void SetLength(long value) - { - throw new NotSupportedException(); - } - - public override void Write(byte[] buffer, int offset, int count) - { - throw new NotSupportedException(); - } - } -} diff --git a/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs b/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs index ee5131c1f..146c84d7c 100644 --- a/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs +++ b/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs @@ -1,3 +1,5 @@ +#pragma warning disable CS1591 + using System; using System.Collections.Generic; using System.IO; diff --git a/Emby.Server.Implementations/Sorting/AiredEpisodeOrderComparer.cs b/Emby.Server.Implementations/Sorting/AiredEpisodeOrderComparer.cs index 16507466f..67e31f7f6 100644 --- a/Emby.Server.Implementations/Sorting/AiredEpisodeOrderComparer.cs +++ b/Emby.Server.Implementations/Sorting/AiredEpisodeOrderComparer.cs @@ -1,3 +1,5 @@ +#pragma warning disable CS1591 + using System; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.TV; diff --git a/Emby.Server.Implementations/Sorting/CommunityRatingComparer.cs b/Emby.Server.Implementations/Sorting/CommunityRatingComparer.cs index 87d3ae2d6..980954ba0 100644 --- a/Emby.Server.Implementations/Sorting/CommunityRatingComparer.cs +++ b/Emby.Server.Implementations/Sorting/CommunityRatingComparer.cs @@ -1,3 +1,5 @@ +#pragma warning disable CS1591 + using System; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Sorting; @@ -7,6 +9,12 @@ namespace Emby.Server.Implementations.Sorting { public class CommunityRatingComparer : IBaseItemComparer { + /// + /// Gets the name. + /// + /// The name. + public string Name => ItemSortBy.CommunityRating; + /// /// Compares the specified x. /// @@ -16,18 +24,16 @@ namespace Emby.Server.Implementations.Sorting public int Compare(BaseItem x, BaseItem y) { if (x == null) + { throw new ArgumentNullException(nameof(x)); + } if (y == null) + { throw new ArgumentNullException(nameof(y)); + } return (x.CommunityRating ?? 0).CompareTo(y.CommunityRating ?? 0); } - - /// - /// Gets the name. - /// - /// The name. - public string Name => ItemSortBy.CommunityRating; } } diff --git a/Emby.Server.Implementations/Sorting/DateLastMediaAddedComparer.cs b/Emby.Server.Implementations/Sorting/DateLastMediaAddedComparer.cs index 623675157..5c1503ed2 100644 --- a/Emby.Server.Implementations/Sorting/DateLastMediaAddedComparer.cs +++ b/Emby.Server.Implementations/Sorting/DateLastMediaAddedComparer.cs @@ -1,3 +1,5 @@ +#pragma warning disable CS1591 + using System; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; @@ -26,6 +28,12 @@ namespace Emby.Server.Implementations.Sorting /// The user data repository. public IUserDataManager UserDataRepository { get; set; } + /// + /// Gets the name. + /// + /// The name. + public string Name => ItemSortBy.DateLastContentAdded; + /// /// Compares the specified x. /// @@ -44,9 +52,7 @@ namespace Emby.Server.Implementations.Sorting /// DateTime. private static DateTime GetDate(BaseItem x) { - var folder = x as Folder; - - if (folder != null) + if (x is Folder folder) { if (folder.DateLastMediaAdded.HasValue) { @@ -56,11 +62,5 @@ namespace Emby.Server.Implementations.Sorting return DateTime.MinValue; } - - /// - /// Gets the name. - /// - /// The name. - public string Name => ItemSortBy.DateLastContentAdded; } } diff --git a/Emby.Server.Implementations/Sorting/IsFavoriteOrLikeComparer.cs b/Emby.Server.Implementations/Sorting/IsFavoriteOrLikeComparer.cs index 66de05a6a..aba14c6ca 100644 --- a/Emby.Server.Implementations/Sorting/IsFavoriteOrLikeComparer.cs +++ b/Emby.Server.Implementations/Sorting/IsFavoriteOrLikeComparer.cs @@ -1,3 +1,5 @@ +#pragma warning disable CS1591 + using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Sorting; @@ -13,6 +15,24 @@ namespace Emby.Server.Implementations.Sorting /// The user. public User User { get; set; } + /// + /// Gets the name. + /// + /// The name. + public string Name => ItemSortBy.IsFavoriteOrLiked; + + /// + /// Gets or sets the user data repository. + /// + /// The user data repository. + public IUserDataManager UserDataRepository { get; set; } + + /// + /// Gets or sets the user manager. + /// + /// The user manager. + public IUserManager UserManager { get; set; } + /// /// Compares the specified x. /// @@ -33,23 +53,5 @@ namespace Emby.Server.Implementations.Sorting { return x.IsFavoriteOrLiked(User) ? 0 : 1; } - - /// - /// Gets the name. - /// - /// The name. - public string Name => ItemSortBy.IsFavoriteOrLiked; - - /// - /// Gets or sets the user data repository. - /// - /// The user data repository. - public IUserDataManager UserDataRepository { get; set; } - - /// - /// Gets or sets the user manager. - /// - /// The user manager. - public IUserManager UserManager { get; set; } } } diff --git a/Emby.Server.Implementations/Sorting/IsFolderComparer.cs b/Emby.Server.Implementations/Sorting/IsFolderComparer.cs index dfaa144cd..a35192eff 100644 --- a/Emby.Server.Implementations/Sorting/IsFolderComparer.cs +++ b/Emby.Server.Implementations/Sorting/IsFolderComparer.cs @@ -1,3 +1,5 @@ +#pragma warning disable CS1591 + using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Sorting; using MediaBrowser.Model.Querying; @@ -6,6 +8,12 @@ namespace Emby.Server.Implementations.Sorting { public class IsFolderComparer : IBaseItemComparer { + /// + /// Gets the name. + /// + /// The name. + public string Name => ItemSortBy.IsFolder; + /// /// Compares the specified x. /// @@ -26,11 +34,5 @@ namespace Emby.Server.Implementations.Sorting { return x.IsFolder ? 0 : 1; } - - /// - /// Gets the name. - /// - /// The name. - public string Name => ItemSortBy.IsFolder; } } diff --git a/Emby.Server.Implementations/Sorting/IsPlayedComparer.cs b/Emby.Server.Implementations/Sorting/IsPlayedComparer.cs index da3f3dd25..39d9bc68e 100644 --- a/Emby.Server.Implementations/Sorting/IsPlayedComparer.cs +++ b/Emby.Server.Implementations/Sorting/IsPlayedComparer.cs @@ -1,3 +1,5 @@ +#pragma warning disable CS1591 + using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Sorting; @@ -13,6 +15,24 @@ namespace Emby.Server.Implementations.Sorting /// The user. public User User { get; set; } + /// + /// Gets the name. + /// + /// The name. + public string Name => ItemSortBy.IsUnplayed; + + /// + /// Gets or sets the user data repository. + /// + /// The user data repository. + public IUserDataManager UserDataRepository { get; set; } + + /// + /// Gets or sets the user manager. + /// + /// The user manager. + public IUserManager UserManager { get; set; } + /// /// Compares the specified x. /// @@ -33,23 +53,5 @@ namespace Emby.Server.Implementations.Sorting { return x.IsPlayed(User) ? 0 : 1; } - - /// - /// Gets the name. - /// - /// The name. - public string Name => ItemSortBy.IsUnplayed; - - /// - /// Gets or sets the user data repository. - /// - /// The user data repository. - public IUserDataManager UserDataRepository { get; set; } - - /// - /// Gets or sets the user manager. - /// - /// The user manager. - public IUserManager UserManager { get; set; } } } diff --git a/Emby.Server.Implementations/Sorting/IsUnplayedComparer.cs b/Emby.Server.Implementations/Sorting/IsUnplayedComparer.cs index d99d0eff2..478df4035 100644 --- a/Emby.Server.Implementations/Sorting/IsUnplayedComparer.cs +++ b/Emby.Server.Implementations/Sorting/IsUnplayedComparer.cs @@ -1,3 +1,5 @@ +#pragma warning disable CS1591 + using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Sorting; @@ -13,6 +15,24 @@ namespace Emby.Server.Implementations.Sorting /// The user. public User User { get; set; } + /// + /// Gets the name. + /// + /// The name. + public string Name => ItemSortBy.IsUnplayed; + + /// + /// Gets or sets the user data repository. + /// + /// The user data repository. + public IUserDataManager UserDataRepository { get; set; } + + /// + /// Gets or sets the user manager. + /// + /// The user manager. + public IUserManager UserManager { get; set; } + /// /// Compares the specified x. /// @@ -33,23 +53,5 @@ namespace Emby.Server.Implementations.Sorting { return x.IsUnplayed(User) ? 0 : 1; } - - /// - /// Gets the name. - /// - /// The name. - public string Name => ItemSortBy.IsUnplayed; - - /// - /// Gets or sets the user data repository. - /// - /// The user data repository. - public IUserDataManager UserDataRepository { get; set; } - - /// - /// Gets or sets the user manager. - /// - /// The user manager. - public IUserManager UserManager { get; set; } } } diff --git a/Emby.Server.Implementations/Sorting/OfficialRatingComparer.cs b/Emby.Server.Implementations/Sorting/OfficialRatingComparer.cs index 7afbd9ff7..76bb798b5 100644 --- a/Emby.Server.Implementations/Sorting/OfficialRatingComparer.cs +++ b/Emby.Server.Implementations/Sorting/OfficialRatingComparer.cs @@ -1,3 +1,5 @@ +#pragma warning disable CS1591 + using System; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Sorting; @@ -15,6 +17,12 @@ namespace Emby.Server.Implementations.Sorting _localization = localization; } + /// + /// Gets the name. + /// + /// The name. + public string Name => ItemSortBy.OfficialRating; + /// /// Compares the specified x. /// @@ -38,11 +46,5 @@ namespace Emby.Server.Implementations.Sorting return levelX.CompareTo(levelY); } - - /// - /// Gets the name. - /// - /// The name. - public string Name => ItemSortBy.OfficialRating; } } diff --git a/Emby.Server.Implementations/Sorting/SeriesSortNameComparer.cs b/Emby.Server.Implementations/Sorting/SeriesSortNameComparer.cs index 504b6d283..b9205ee07 100644 --- a/Emby.Server.Implementations/Sorting/SeriesSortNameComparer.cs +++ b/Emby.Server.Implementations/Sorting/SeriesSortNameComparer.cs @@ -1,3 +1,5 @@ +#pragma warning disable CS1591 + using System; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Sorting; @@ -7,6 +9,12 @@ namespace Emby.Server.Implementations.Sorting { public class SeriesSortNameComparer : IBaseItemComparer { + /// + /// Gets the name. + /// + /// The name. + public string Name => ItemSortBy.SeriesSortName; + /// /// Compares the specified x. /// @@ -18,12 +26,6 @@ namespace Emby.Server.Implementations.Sorting return string.Compare(GetValue(x), GetValue(y), StringComparison.CurrentCultureIgnoreCase); } - /// - /// Gets the name. - /// - /// The name. - public string Name => ItemSortBy.SeriesSortName; - private static string GetValue(BaseItem item) { var hasSeries = item as IHasSeries; diff --git a/Emby.Server.Implementations/Sorting/StartDateComparer.cs b/Emby.Server.Implementations/Sorting/StartDateComparer.cs index aa040fa15..558a3d351 100644 --- a/Emby.Server.Implementations/Sorting/StartDateComparer.cs +++ b/Emby.Server.Implementations/Sorting/StartDateComparer.cs @@ -1,3 +1,5 @@ +#pragma warning disable CS1591 + using System; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.LiveTv; @@ -8,6 +10,12 @@ namespace Emby.Server.Implementations.Sorting { public class StartDateComparer : IBaseItemComparer { + /// + /// Gets the name. + /// + /// The name. + public string Name => ItemSortBy.StartDate; + /// /// Compares the specified x. /// @@ -26,19 +34,12 @@ namespace Emby.Server.Implementations.Sorting /// DateTime. private static DateTime GetDate(BaseItem x) { - var hasStartDate = x as LiveTvProgram; - - if (hasStartDate != null) + if (x is LiveTvProgram hasStartDate) { return hasStartDate.StartDate; } + return DateTime.MinValue; } - - /// - /// Gets the name. - /// - /// The name. - public string Name => ItemSortBy.StartDate; } } diff --git a/Emby.Server.Implementations/Sorting/StudioComparer.cs b/Emby.Server.Implementations/Sorting/StudioComparer.cs index c9ac765c1..5766dc542 100644 --- a/Emby.Server.Implementations/Sorting/StudioComparer.cs +++ b/Emby.Server.Implementations/Sorting/StudioComparer.cs @@ -1,3 +1,5 @@ +#pragma warning disable CS1591 + using System; using System.Linq; using MediaBrowser.Controller.Entities; diff --git a/Emby.Server.Implementations/SyncPlay/SyncPlayController.cs b/Emby.Server.Implementations/SyncPlay/SyncPlayController.cs index d430d4d16..d0812a13f 100644 --- a/Emby.Server.Implementations/SyncPlay/SyncPlayController.cs +++ b/Emby.Server.Implementations/SyncPlay/SyncPlayController.cs @@ -65,6 +65,11 @@ namespace Emby.Server.Implementations.SyncPlay /// public bool IsGroupEmpty() => _group.IsEmpty(); + /// + /// Initializes a new instance of the class. + /// + /// The session manager. + /// The SyncPlay manager. public SyncPlayController( ISessionManager sessionManager, ISyncPlayManager syncPlayManager) diff --git a/Emby.Server.Implementations/SyncPlay/SyncPlayManager.cs b/Emby.Server.Implementations/SyncPlay/SyncPlayManager.cs index 1f76dd4e3..129262e53 100644 --- a/Emby.Server.Implementations/SyncPlay/SyncPlayManager.cs +++ b/Emby.Server.Implementations/SyncPlay/SyncPlayManager.cs @@ -57,6 +57,13 @@ namespace Emby.Server.Implementations.SyncPlay private bool _disposed = false; + /// + /// Initializes a new instance of the class. + /// + /// The logger. + /// The user manager. + /// The session manager. + /// The library manager. public SyncPlayManager( ILogger logger, IUserManager userManager, diff --git a/Emby.Server.Implementations/TV/TVSeriesManager.cs b/Emby.Server.Implementations/TV/TVSeriesManager.cs index 4c2f24e6f..383615f74 100644 --- a/Emby.Server.Implementations/TV/TVSeriesManager.cs +++ b/Emby.Server.Implementations/TV/TVSeriesManager.cs @@ -1,8 +1,9 @@ +#pragma warning disable CS1591 + using System; using System.Collections.Generic; using System.Globalization; using System.Linq; -using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.TV; @@ -18,14 +19,12 @@ namespace Emby.Server.Implementations.TV private readonly IUserManager _userManager; private readonly IUserDataManager _userDataManager; private readonly ILibraryManager _libraryManager; - private readonly IServerConfigurationManager _config; - public TVSeriesManager(IUserManager userManager, IUserDataManager userDataManager, ILibraryManager libraryManager, IServerConfigurationManager config) + public TVSeriesManager(IUserManager userManager, IUserDataManager userDataManager, ILibraryManager libraryManager) { _userManager = userManager; _userDataManager = userDataManager; _libraryManager = libraryManager; - _config = config; } public QueryResult GetNextUp(NextUpQuery request, DtoOptions dtoOptions) diff --git a/Emby.Server.Implementations/Updates/InstallationManager.cs b/Emby.Server.Implementations/Updates/InstallationManager.cs index 0b2309889..4f6a84ef7 100644 --- a/Emby.Server.Implementations/Updates/InstallationManager.cs +++ b/Emby.Server.Implementations/Updates/InstallationManager.cs @@ -1,11 +1,11 @@ +#pragma warning disable CS1591 + using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.IO; using System.Linq; -using System.Net; using System.Net.Http; -using System.Runtime.CompilerServices; using System.Runtime.Serialization; using System.Security.Cryptography; using System.Threading; diff --git a/Emby.Server.Implementations/UserViews/CollectionFolderImageProvider.cs b/Emby.Server.Implementations/UserViews/CollectionFolderImageProvider.cs index a3f3f6cb4..7b7d66ca6 100644 --- a/Emby.Server.Implementations/UserViews/CollectionFolderImageProvider.cs +++ b/Emby.Server.Implementations/UserViews/CollectionFolderImageProvider.cs @@ -1,3 +1,5 @@ +#pragma warning disable CS1591 + using System; using System.Collections.Generic; using System.IO; diff --git a/Emby.Server.Implementations/UserViews/DynamicImageProvider.cs b/Emby.Server.Implementations/UserViews/DynamicImageProvider.cs index 78ac95f85..e7888595f 100644 --- a/Emby.Server.Implementations/UserViews/DynamicImageProvider.cs +++ b/Emby.Server.Implementations/UserViews/DynamicImageProvider.cs @@ -1,3 +1,5 @@ +#pragma warning disable CS1591 + using System; using System.Collections.Generic; using System.IO; @@ -19,13 +21,11 @@ namespace Emby.Server.Implementations.UserViews public class DynamicImageProvider : BaseDynamicImageProvider { private readonly IUserManager _userManager; - private readonly ILibraryManager _libraryManager; - public DynamicImageProvider(IFileSystem fileSystem, IProviderManager providerManager, IApplicationPaths applicationPaths, IImageProcessor imageProcessor, IUserManager userManager, ILibraryManager libraryManager) + public DynamicImageProvider(IFileSystem fileSystem, IProviderManager providerManager, IApplicationPaths applicationPaths, IImageProcessor imageProcessor, IUserManager userManager) : base(fileSystem, providerManager, applicationPaths, imageProcessor) { _userManager = userManager; - _libraryManager = libraryManager; } protected override IReadOnlyList GetItemsWithImages(BaseItem item) diff --git a/Emby.Server.Implementations/UserViews/FolderImageProvider.cs b/Emby.Server.Implementations/UserViews/FolderImageProvider.cs index 4655cd928..58a023638 100644 --- a/Emby.Server.Implementations/UserViews/FolderImageProvider.cs +++ b/Emby.Server.Implementations/UserViews/FolderImageProvider.cs @@ -1,3 +1,5 @@ +#pragma warning disable CS1591 + using System.Collections.Generic; using Emby.Server.Implementations.Images; using MediaBrowser.Common.Configuration; @@ -75,14 +77,14 @@ namespace Emby.Server.Implementations.UserViews return false; } - var folder = item as Folder; - if (folder != null) + if (item is Folder folder) { if (folder.IsTopParent) { return false; } } + return true; //return item.SourceType == SourceType.Library; } diff --git a/Jellyfin.Server/Migrations/IMigrationRoutine.cs b/Jellyfin.Server/Migrations/IMigrationRoutine.cs index b79fdeac0..6b5780a26 100644 --- a/Jellyfin.Server/Migrations/IMigrationRoutine.cs +++ b/Jellyfin.Server/Migrations/IMigrationRoutine.cs @@ -1,5 +1,4 @@ using System; -using Microsoft.Extensions.Logging; namespace Jellyfin.Server.Migrations { diff --git a/Jellyfin.Server/Migrations/Routines/CreateUserLoggingConfigFile.cs b/Jellyfin.Server/Migrations/Routines/CreateUserLoggingConfigFile.cs index 89514c89b..b15e09290 100644 --- a/Jellyfin.Server/Migrations/Routines/CreateUserLoggingConfigFile.cs +++ b/Jellyfin.Server/Migrations/Routines/CreateUserLoggingConfigFile.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.IO; using System.Linq; using MediaBrowser.Common.Configuration; -using Microsoft.Extensions.Logging; using Newtonsoft.Json.Linq; namespace Jellyfin.Server.Migrations.Routines diff --git a/Jellyfin.Server/Program.cs b/Jellyfin.Server/Program.cs index b9895386f..7c693f8c3 100644 --- a/Jellyfin.Server/Program.cs +++ b/Jellyfin.Server/Program.cs @@ -40,12 +40,12 @@ namespace Jellyfin.Server /// /// The name of logging configuration file containing application defaults. /// - public static readonly string LoggingConfigFileDefault = "logging.default.json"; + public const string LoggingConfigFileDefault = "logging.default.json"; /// /// The name of the logging configuration file containing the system-specific override settings. /// - public static readonly string LoggingConfigFileSystem = "logging.json"; + public const string LoggingConfigFileSystem = "logging.json"; private static readonly CancellationTokenSource _tokenSource = new CancellationTokenSource(); private static readonly ILoggerFactory _loggerFactory = new SerilogLoggerFactory(); -- cgit v1.2.3 From 44957c5a9a11fcd6e4567c7d31bc39d79c709068 Mon Sep 17 00:00:00 2001 From: crobibero Date: Fri, 5 Jun 2020 18:15:56 -0600 Subject: Use typed logger where possible --- Emby.Dlna/ConnectionManager/ConnectionManager.cs | 2 +- Emby.Dlna/DlnaManager.cs | 4 ++-- Emby.Dlna/Main/DlnaEntryPoint.cs | 10 ++++++---- Emby.Dlna/PlayTo/PlayToManager.cs | 3 ++- Emby.Dlna/Service/BaseService.cs | 4 ++-- Emby.Drawing/ImageProcessor.cs | 2 +- Emby.Notifications/NotificationEntryPoint.cs | 2 +- Emby.Notifications/NotificationManager.cs | 2 +- Emby.Photos/PhotoProvider.cs | 2 +- .../AppBase/BaseConfigurationManager.cs | 4 ++-- Emby.Server.Implementations/ApplicationHost.cs | 2 +- Emby.Server.Implementations/Browser/BrowserLauncher.cs | 2 +- Emby.Server.Implementations/Channels/ChannelManager.cs | 2 +- .../Channels/RefreshChannelsScheduledTask.cs | 2 +- Emby.Server.Implementations/Collections/CollectionManager.cs | 6 +++--- Emby.Server.Implementations/Data/BaseSqliteRepository.cs | 4 ++-- Emby.Server.Implementations/Data/CleanDatabaseScheduledTask.cs | 2 +- Emby.Server.Implementations/Devices/DeviceId.cs | 4 ++-- Emby.Server.Implementations/Dto/DtoService.cs | 2 +- .../EntryPoints/ExternalPortForwarding.cs | 2 +- .../EntryPoints/LibraryChangedNotifier.cs | 2 +- Emby.Server.Implementations/EntryPoints/RecordingNotifier.cs | 2 +- Emby.Server.Implementations/EntryPoints/UdpServerEntryPoint.cs | 2 +- .../HttpClientManager/HttpClientManager.cs | 2 +- Emby.Server.Implementations/HttpServer/HttpListenerHost.cs | 4 ++-- Emby.Server.Implementations/HttpServer/HttpResultFactory.cs | 4 ++-- Emby.Server.Implementations/HttpServer/Security/AuthService.cs | 2 +- Emby.Server.Implementations/HttpServer/WebSocketConnection.cs | 2 +- Emby.Server.Implementations/IO/LibraryMonitor.cs | 2 +- Emby.Server.Implementations/IO/ManagedFileSystem.cs | 2 +- Emby.Server.Implementations/Library/LibraryManager.cs | 2 +- Emby.Server.Implementations/Library/MediaSourceManager.cs | 2 +- .../Library/Resolvers/Audio/MusicAlbumResolver.cs | 2 +- .../Library/Resolvers/TV/SeasonResolver.cs | 2 +- .../Library/Resolvers/TV/SeriesResolver.cs | 4 ++-- Emby.Server.Implementations/Library/SearchEngine.cs | 2 +- Emby.Server.Implementations/Library/UserDataManager.cs | 2 +- Emby.Server.Implementations/Library/UserManager.cs | 2 +- .../Library/Validators/ArtistsPostScanTask.cs | 4 ++-- .../Library/Validators/ArtistsValidator.cs | 4 ++-- .../Library/Validators/GenresPostScanTask.cs | 4 ++-- .../Library/Validators/GenresValidator.cs | 4 ++-- .../Library/Validators/MusicGenresPostScanTask.cs | 4 ++-- .../Library/Validators/MusicGenresValidator.cs | 4 ++-- .../Library/Validators/StudiosPostScanTask.cs | 4 ++-- .../Library/Validators/StudiosValidator.cs | 4 ++-- Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs | 2 +- Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs | 2 +- .../LiveTv/Listings/XmlTvListingsProvider.cs | 2 +- Emby.Server.Implementations/LiveTv/LiveTvDtoService.cs | 2 +- Emby.Server.Implementations/LiveTv/LiveTvManager.cs | 2 +- .../LiveTv/LiveTvMediaSourceProvider.cs | 2 +- Emby.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs | 4 ++-- Emby.Server.Implementations/LiveTv/TunerHosts/LiveStream.cs | 1 + .../Localization/LocalizationManager.cs | 2 +- Emby.Server.Implementations/MediaEncoder/EncodingManager.cs | 2 +- Emby.Server.Implementations/Networking/NetworkManager.cs | 2 +- Emby.Server.Implementations/Playlists/PlaylistManager.cs | 2 +- Emby.Server.Implementations/ResourceFileManager.cs | 2 +- Emby.Server.Implementations/ScheduledTasks/TaskManager.cs | 2 +- .../ScheduledTasks/Tasks/ChapterImagesTask.cs | 4 ++-- .../ScheduledTasks/Tasks/DeleteCacheFileTask.cs | 2 +- .../ScheduledTasks/Tasks/DeleteTranscodeFileTask.cs | 2 +- .../ScheduledTasks/Tasks/PluginUpdateTask.cs | 2 +- Emby.Server.Implementations/Services/ServiceController.cs | 2 +- Emby.Server.Implementations/Session/SessionManager.cs | 2 +- .../Session/SessionWebSocketListener.cs | 2 +- Emby.Server.Implementations/Session/WebSocketController.cs | 2 +- .../SocketSharp/WebSocketSharpRequest.cs | 2 +- Emby.Server.Implementations/SyncPlay/SyncPlayManager.cs | 2 +- Emby.Server.Implementations/Updates/InstallationManager.cs | 2 +- Jellyfin.Drawing.Skia/SkiaEncoder.cs | 2 +- .../Migrations/Routines/DisableTranscodingThrottling.cs | 2 +- Jellyfin.Server/Migrations/Routines/RemoveDuplicateExtras.cs | 2 +- MediaBrowser.Api/ApiEntryPoint.cs | 2 +- MediaBrowser.Api/Music/AlbumsService.cs | 10 ++++++---- MediaBrowser.Api/SimilarItemsHelper.cs | 2 +- MediaBrowser.Controller/Entities/BaseItem.cs | 3 ++- MediaBrowser.Controller/Entities/UserView.cs | 3 ++- MediaBrowser.Controller/Entities/UserViewBuilder.cs | 4 ++-- MediaBrowser.Controller/IO/FileData.cs | 3 ++- MediaBrowser.Controller/Library/Profiler.cs | 2 +- MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs | 4 ++-- MediaBrowser.Controller/Session/SessionInfo.cs | 1 + .../Images/InternalMetadataFolderImageProvider.cs | 2 +- MediaBrowser.LocalMetadata/Parsers/BaseItemXmlParser.cs | 4 ++-- MediaBrowser.LocalMetadata/Parsers/BoxSetXmlParser.cs | 2 +- MediaBrowser.LocalMetadata/Parsers/PlaylistXmlParser.cs | 2 +- MediaBrowser.LocalMetadata/Providers/BoxSetXmlProvider.cs | 4 ++-- MediaBrowser.LocalMetadata/Providers/PlaylistXmlProvider.cs | 4 ++-- MediaBrowser.LocalMetadata/Savers/BaseXmlSaver.cs | 4 ++-- MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs | 2 +- MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs | 2 +- MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs | 2 +- MediaBrowser.Providers/Manager/ImageSaver.cs | 1 + MediaBrowser.Providers/Manager/MetadataService.cs | 4 ++-- MediaBrowser.Providers/Manager/ProviderManager.cs | 2 +- MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs | 2 +- MediaBrowser.Providers/MediaInfo/SubtitleDownloader.cs | 3 ++- MediaBrowser.Providers/MediaInfo/SubtitleScheduledTask.cs | 2 +- MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs | 2 +- MediaBrowser.Providers/Playlists/PlaylistItemsProvider.cs | 2 +- MediaBrowser.Providers/Plugins/MusicBrainz/AlbumProvider.cs | 2 +- .../Plugins/TheTvdb/TvdbEpisodeImageProvider.cs | 2 +- MediaBrowser.Providers/Plugins/TheTvdb/TvdbEpisodeProvider.cs | 2 +- .../Plugins/TheTvdb/TvdbPersonImageProvider.cs | 2 +- .../Plugins/TheTvdb/TvdbSeasonImageProvider.cs | 2 +- .../Plugins/TheTvdb/TvdbSeriesImageProvider.cs | 2 +- MediaBrowser.Providers/Plugins/TheTvdb/TvdbSeriesProvider.cs | 2 +- MediaBrowser.Providers/Subtitles/SubtitleManager.cs | 2 +- MediaBrowser.Providers/TV/SeriesMetadataService.cs | 3 ++- MediaBrowser.Providers/Tmdb/BoxSets/TmdbBoxSetProvider.cs | 2 +- MediaBrowser.Providers/Tmdb/Movies/TmdbMovieProvider.cs | 2 +- MediaBrowser.Providers/Tmdb/People/TmdbPersonProvider.cs | 2 +- MediaBrowser.Providers/Tmdb/TV/TmdbEpisodeImageProvider.cs | 4 ++-- MediaBrowser.Providers/Tmdb/TV/TmdbEpisodeProvider.cs | 4 ++-- MediaBrowser.Providers/Tmdb/TV/TmdbEpisodeProviderBase.cs | 4 ++-- MediaBrowser.Providers/Tmdb/TV/TmdbSeasonProvider.cs | 6 +++--- MediaBrowser.Providers/Tmdb/TV/TmdbSeriesProvider.cs | 2 +- MediaBrowser.WebDashboard/Api/DashboardService.cs | 2 +- MediaBrowser.XbmcMetadata/EntryPoint.cs | 2 +- MediaBrowser.XbmcMetadata/Providers/AlbumNfoProvider.cs | 2 +- MediaBrowser.XbmcMetadata/Providers/ArtistNfoProvider.cs | 2 +- MediaBrowser.XbmcMetadata/Providers/BaseVideoNfoProvider.cs | 4 ++-- MediaBrowser.XbmcMetadata/Providers/EpisodeNfoProvider.cs | 2 +- MediaBrowser.XbmcMetadata/Providers/SeasonNfoProvider.cs | 2 +- MediaBrowser.XbmcMetadata/Providers/SeriesNfoProvider.cs | 2 +- MediaBrowser.XbmcMetadata/Savers/BaseNfoSaver.cs | 6 +++--- 128 files changed, 179 insertions(+), 166 deletions(-) (limited to 'Emby.Server.Implementations/Session') diff --git a/Emby.Dlna/ConnectionManager/ConnectionManager.cs b/Emby.Dlna/ConnectionManager/ConnectionManager.cs index e32cc11bf..480dd3a79 100644 --- a/Emby.Dlna/ConnectionManager/ConnectionManager.cs +++ b/Emby.Dlna/ConnectionManager/ConnectionManager.cs @@ -18,7 +18,7 @@ namespace Emby.Dlna.ConnectionManager public ConnectionManager( IDlnaManager dlna, IServerConfigurationManager config, - ILogger logger, + ILogger logger, IHttpClient httpClient) : base(logger, httpClient) { diff --git a/Emby.Dlna/DlnaManager.cs b/Emby.Dlna/DlnaManager.cs index 10f881fe7..a85e5c35e 100644 --- a/Emby.Dlna/DlnaManager.cs +++ b/Emby.Dlna/DlnaManager.cs @@ -31,7 +31,7 @@ namespace Emby.Dlna private readonly IApplicationPaths _appPaths; private readonly IXmlSerializer _xmlSerializer; private readonly IFileSystem _fileSystem; - private readonly ILogger _logger; + private readonly ILogger _logger; private readonly IJsonSerializer _jsonSerializer; private readonly IServerApplicationHost _appHost; private static readonly Assembly _assembly = typeof(DlnaManager).Assembly; @@ -49,7 +49,7 @@ namespace Emby.Dlna _xmlSerializer = xmlSerializer; _fileSystem = fileSystem; _appPaths = appPaths; - _logger = loggerFactory.CreateLogger("Dlna"); + _logger = loggerFactory.CreateLogger(); _jsonSerializer = jsonSerializer; _appHost = appHost; } diff --git a/Emby.Dlna/Main/DlnaEntryPoint.cs b/Emby.Dlna/Main/DlnaEntryPoint.cs index bcab4adba..47b235e59 100644 --- a/Emby.Dlna/Main/DlnaEntryPoint.cs +++ b/Emby.Dlna/Main/DlnaEntryPoint.cs @@ -33,7 +33,7 @@ namespace Emby.Dlna.Main public class DlnaEntryPoint : IServerEntryPoint, IRunBeforeStartup { private readonly IServerConfigurationManager _config; - private readonly ILogger _logger; + private readonly ILogger _logger; private readonly IServerApplicationHost _appHost; private PlayToManager _manager; @@ -65,7 +65,8 @@ namespace Emby.Dlna.Main public static DlnaEntryPoint Current; - public DlnaEntryPoint(IServerConfigurationManager config, + public DlnaEntryPoint( + IServerConfigurationManager config, ILoggerFactory loggerFactory, IServerApplicationHost appHost, ISessionManager sessionManager, @@ -99,7 +100,7 @@ namespace Emby.Dlna.Main _mediaEncoder = mediaEncoder; _socketFactory = socketFactory; _networkManager = networkManager; - _logger = loggerFactory.CreateLogger("Dlna"); + _logger = loggerFactory.CreateLogger(); ContentDirectory = new ContentDirectory.ContentDirectory( dlnaManager, @@ -347,7 +348,8 @@ namespace Emby.Dlna.Main try { - _manager = new PlayToManager(_logger, + _manager = new PlayToManager( + _logger, _sessionManager, _libraryManager, _userManager, diff --git a/Emby.Dlna/PlayTo/PlayToManager.cs b/Emby.Dlna/PlayTo/PlayToManager.cs index bbedd1485..9b0339e5d 100644 --- a/Emby.Dlna/PlayTo/PlayToManager.cs +++ b/Emby.Dlna/PlayTo/PlayToManager.cs @@ -184,7 +184,8 @@ namespace Emby.Dlna.PlayTo serverAddress = _appHost.GetLocalApiUrl(info.LocalIpAddress); } - controller = new PlayToController(sessionInfo, + controller = new PlayToController( + sessionInfo, _sessionManager, _libraryManager, _logger, diff --git a/Emby.Dlna/Service/BaseService.cs b/Emby.Dlna/Service/BaseService.cs index 3704bedcd..4ecffa293 100644 --- a/Emby.Dlna/Service/BaseService.cs +++ b/Emby.Dlna/Service/BaseService.cs @@ -12,12 +12,12 @@ namespace Emby.Dlna.Service protected IHttpClient HttpClient; protected ILogger Logger; - protected BaseService(ILogger logger, IHttpClient httpClient) + protected BaseService(ILogger logger, IHttpClient httpClient) { Logger = logger; HttpClient = httpClient; - EventManager = new EventManager(Logger, HttpClient); + EventManager = new EventManager(logger, HttpClient); } public EventSubscriptionResponse CancelEventSubscription(string subscriptionId) diff --git a/Emby.Drawing/ImageProcessor.cs b/Emby.Drawing/ImageProcessor.cs index 47778acac..c9fc8d55a 100644 --- a/Emby.Drawing/ImageProcessor.cs +++ b/Emby.Drawing/ImageProcessor.cs @@ -28,7 +28,7 @@ namespace Emby.Drawing private static readonly HashSet _transparentImageTypes = new HashSet(StringComparer.OrdinalIgnoreCase) { ".png", ".webp", ".gif" }; - private readonly ILogger _logger; + private readonly ILogger _logger; private readonly IFileSystem _fileSystem; private readonly IServerApplicationPaths _appPaths; private readonly IImageEncoder _imageEncoder; diff --git a/Emby.Notifications/NotificationEntryPoint.cs b/Emby.Notifications/NotificationEntryPoint.cs index 869b7407e..b923fd26c 100644 --- a/Emby.Notifications/NotificationEntryPoint.cs +++ b/Emby.Notifications/NotificationEntryPoint.cs @@ -25,7 +25,7 @@ namespace Emby.Notifications /// public class NotificationEntryPoint : IServerEntryPoint { - private readonly ILogger _logger; + private readonly ILogger _logger; private readonly IActivityManager _activityManager; private readonly ILocalizationManager _localization; private readonly INotificationManager _notificationManager; diff --git a/Emby.Notifications/NotificationManager.cs b/Emby.Notifications/NotificationManager.cs index 639a5e1aa..2792a8f0b 100644 --- a/Emby.Notifications/NotificationManager.cs +++ b/Emby.Notifications/NotificationManager.cs @@ -21,7 +21,7 @@ namespace Emby.Notifications /// public class NotificationManager : INotificationManager { - private readonly ILogger _logger; + private readonly ILogger _logger; private readonly IUserManager _userManager; private readonly IServerConfigurationManager _config; diff --git a/Emby.Photos/PhotoProvider.cs b/Emby.Photos/PhotoProvider.cs index 987cb7fb2..58baa1e50 100644 --- a/Emby.Photos/PhotoProvider.cs +++ b/Emby.Photos/PhotoProvider.cs @@ -22,7 +22,7 @@ namespace Emby.Photos /// public class PhotoProvider : ICustomMetadataProvider, IForcedProvider, IHasItemChangeMonitor { - private readonly ILogger _logger; + private readonly ILogger _logger; private readonly IImageProcessor _imageProcessor; // These are causing taglib to hang diff --git a/Emby.Server.Implementations/AppBase/BaseConfigurationManager.cs b/Emby.Server.Implementations/AppBase/BaseConfigurationManager.cs index 080cfbbd1..d4a8268b9 100644 --- a/Emby.Server.Implementations/AppBase/BaseConfigurationManager.cs +++ b/Emby.Server.Implementations/AppBase/BaseConfigurationManager.cs @@ -53,7 +53,7 @@ namespace Emby.Server.Implementations.AppBase CommonApplicationPaths = applicationPaths; XmlSerializer = xmlSerializer; _fileSystem = fileSystem; - Logger = loggerFactory.CreateLogger(GetType().Name); + Logger = loggerFactory.CreateLogger(); UpdateCachePath(); } @@ -83,7 +83,7 @@ namespace Emby.Server.Implementations.AppBase /// Gets the logger. /// /// The logger. - protected ILogger Logger { get; private set; } + protected ILogger Logger { get; private set; } /// /// Gets the XML serializer. diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index be4e05a64..184c6f399 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -173,7 +173,7 @@ namespace Emby.Server.Implementations /// /// Gets the logger. /// - protected ILogger Logger { get; } + protected ILogger Logger { get; } private IPlugin[] _plugins; diff --git a/Emby.Server.Implementations/Browser/BrowserLauncher.cs b/Emby.Server.Implementations/Browser/BrowserLauncher.cs index 7f7c6a0be..7a0294e07 100644 --- a/Emby.Server.Implementations/Browser/BrowserLauncher.cs +++ b/Emby.Server.Implementations/Browser/BrowserLauncher.cs @@ -41,7 +41,7 @@ namespace Emby.Server.Implementations.Browser } catch (Exception ex) { - var logger = appHost.Resolve(); + var logger = appHost.Resolve>(); logger?.LogError(ex, "Failed to open browser window with URL {URL}", relativeUrl); } } diff --git a/Emby.Server.Implementations/Channels/ChannelManager.cs b/Emby.Server.Implementations/Channels/ChannelManager.cs index 04fe0bacb..f9eea66b4 100644 --- a/Emby.Server.Implementations/Channels/ChannelManager.cs +++ b/Emby.Server.Implementations/Channels/ChannelManager.cs @@ -36,7 +36,7 @@ namespace Emby.Server.Implementations.Channels private readonly IUserDataManager _userDataManager; private readonly IDtoService _dtoService; private readonly ILibraryManager _libraryManager; - private readonly ILogger _logger; + private readonly ILogger _logger; private readonly IServerConfigurationManager _config; private readonly IFileSystem _fileSystem; private readonly IJsonSerializer _jsonSerializer; diff --git a/Emby.Server.Implementations/Channels/RefreshChannelsScheduledTask.cs b/Emby.Server.Implementations/Channels/RefreshChannelsScheduledTask.cs index 54b621e25..e5dde48d8 100644 --- a/Emby.Server.Implementations/Channels/RefreshChannelsScheduledTask.cs +++ b/Emby.Server.Implementations/Channels/RefreshChannelsScheduledTask.cs @@ -17,7 +17,7 @@ namespace Emby.Server.Implementations.Channels public class RefreshChannelsScheduledTask : IScheduledTask, IConfigurableScheduledTask { private readonly IChannelManager _channelManager; - private readonly ILogger _logger; + private readonly ILogger _logger; private readonly ILibraryManager _libraryManager; private readonly ILocalizationManager _localization; diff --git a/Emby.Server.Implementations/Collections/CollectionManager.cs b/Emby.Server.Implementations/Collections/CollectionManager.cs index 7c518d483..cba3975cf 100644 --- a/Emby.Server.Implementations/Collections/CollectionManager.cs +++ b/Emby.Server.Implementations/Collections/CollectionManager.cs @@ -29,7 +29,7 @@ namespace Emby.Server.Implementations.Collections private readonly ILibraryManager _libraryManager; private readonly IFileSystem _fileSystem; private readonly ILibraryMonitor _iLibraryMonitor; - private readonly ILogger _logger; + private readonly ILogger _logger; private readonly IProviderManager _providerManager; private readonly ILocalizationManager _localizationManager; private readonly IApplicationPaths _appPaths; @@ -56,7 +56,7 @@ namespace Emby.Server.Implementations.Collections _libraryManager = libraryManager; _fileSystem = fileSystem; _iLibraryMonitor = iLibraryMonitor; - _logger = loggerFactory.CreateLogger(nameof(CollectionManager)); + _logger = loggerFactory.CreateLogger(); _providerManager = providerManager; _localizationManager = localizationManager; _appPaths = appPaths; @@ -370,7 +370,7 @@ namespace Emby.Server.Implementations.Collections { private readonly CollectionManager _collectionManager; private readonly IServerConfigurationManager _config; - private readonly ILogger _logger; + private readonly ILogger _logger; /// /// Initializes a new instance of the class. diff --git a/Emby.Server.Implementations/Data/BaseSqliteRepository.cs b/Emby.Server.Implementations/Data/BaseSqliteRepository.cs index 0654132f4..f816fd54f 100644 --- a/Emby.Server.Implementations/Data/BaseSqliteRepository.cs +++ b/Emby.Server.Implementations/Data/BaseSqliteRepository.cs @@ -17,7 +17,7 @@ namespace Emby.Server.Implementations.Data /// Initializes a new instance of the class. /// /// The logger. - protected BaseSqliteRepository(ILogger logger) + protected BaseSqliteRepository(ILogger logger) { Logger = logger; } @@ -32,7 +32,7 @@ namespace Emby.Server.Implementations.Data /// Gets the logger. /// /// The logger. - protected ILogger Logger { get; } + protected ILogger Logger { get; } /// /// Gets the default connection flags. diff --git a/Emby.Server.Implementations/Data/CleanDatabaseScheduledTask.cs b/Emby.Server.Implementations/Data/CleanDatabaseScheduledTask.cs index 37c678a5d..6c9bcff0f 100644 --- a/Emby.Server.Implementations/Data/CleanDatabaseScheduledTask.cs +++ b/Emby.Server.Implementations/Data/CleanDatabaseScheduledTask.cs @@ -12,7 +12,7 @@ namespace Emby.Server.Implementations.Data public class CleanDatabaseScheduledTask : ILibraryPostScanTask { private readonly ILibraryManager _libraryManager; - private readonly ILogger _logger; + private readonly ILogger _logger; public CleanDatabaseScheduledTask(ILibraryManager libraryManager, ILogger logger) { diff --git a/Emby.Server.Implementations/Devices/DeviceId.cs b/Emby.Server.Implementations/Devices/DeviceId.cs index f0d43e665..fa6ac95fd 100644 --- a/Emby.Server.Implementations/Devices/DeviceId.cs +++ b/Emby.Server.Implementations/Devices/DeviceId.cs @@ -12,7 +12,7 @@ namespace Emby.Server.Implementations.Devices public class DeviceId { private readonly IApplicationPaths _appPaths; - private readonly ILogger _logger; + private readonly ILogger _logger; private readonly object _syncLock = new object(); @@ -90,7 +90,7 @@ namespace Emby.Server.Implementations.Devices public DeviceId(IApplicationPaths appPaths, ILoggerFactory loggerFactory) { _appPaths = appPaths; - _logger = loggerFactory.CreateLogger("SystemId"); + _logger = loggerFactory.CreateLogger(); } public string Value => _id ?? (_id = GetDeviceId()); diff --git a/Emby.Server.Implementations/Dto/DtoService.cs b/Emby.Server.Implementations/Dto/DtoService.cs index 38c4f940d..f9841082c 100644 --- a/Emby.Server.Implementations/Dto/DtoService.cs +++ b/Emby.Server.Implementations/Dto/DtoService.cs @@ -29,7 +29,7 @@ namespace Emby.Server.Implementations.Dto { public class DtoService : IDtoService { - private readonly ILogger _logger; + private readonly ILogger _logger; private readonly ILibraryManager _libraryManager; private readonly IUserDataManager _userDataRepository; private readonly IItemRepository _itemRepo; diff --git a/Emby.Server.Implementations/EntryPoints/ExternalPortForwarding.cs b/Emby.Server.Implementations/EntryPoints/ExternalPortForwarding.cs index 878cee23c..9fce49425 100644 --- a/Emby.Server.Implementations/EntryPoints/ExternalPortForwarding.cs +++ b/Emby.Server.Implementations/EntryPoints/ExternalPortForwarding.cs @@ -23,7 +23,7 @@ namespace Emby.Server.Implementations.EntryPoints public class ExternalPortForwarding : IServerEntryPoint { private readonly IServerApplicationHost _appHost; - private readonly ILogger _logger; + private readonly ILogger _logger; private readonly IServerConfigurationManager _config; private readonly IDeviceDiscovery _deviceDiscovery; diff --git a/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs b/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs index 9bc2b62ec..b9992ae73 100644 --- a/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs +++ b/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs @@ -28,7 +28,7 @@ namespace Emby.Server.Implementations.EntryPoints private readonly ISessionManager _sessionManager; private readonly IUserManager _userManager; - private readonly ILogger _logger; + private readonly ILogger _logger; /// /// The library changed sync lock. diff --git a/Emby.Server.Implementations/EntryPoints/RecordingNotifier.cs b/Emby.Server.Implementations/EntryPoints/RecordingNotifier.cs index 997571a91..b64439802 100644 --- a/Emby.Server.Implementations/EntryPoints/RecordingNotifier.cs +++ b/Emby.Server.Implementations/EntryPoints/RecordingNotifier.cs @@ -17,7 +17,7 @@ namespace Emby.Server.Implementations.EntryPoints private readonly ILiveTvManager _liveTvManager; private readonly ISessionManager _sessionManager; private readonly IUserManager _userManager; - private readonly ILogger _logger; + private readonly ILogger _logger; public RecordingNotifier( ISessionManager sessionManager, diff --git a/Emby.Server.Implementations/EntryPoints/UdpServerEntryPoint.cs b/Emby.Server.Implementations/EntryPoints/UdpServerEntryPoint.cs index 5bc1a81aa..ecdce89ce 100644 --- a/Emby.Server.Implementations/EntryPoints/UdpServerEntryPoint.cs +++ b/Emby.Server.Implementations/EntryPoints/UdpServerEntryPoint.cs @@ -21,7 +21,7 @@ namespace Emby.Server.Implementations.EntryPoints /// /// The logger. /// - private readonly ILogger _logger; + private readonly ILogger _logger; private readonly IServerApplicationHost _appHost; private readonly IConfiguration _config; diff --git a/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs b/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs index d66bb7638..87977494a 100644 --- a/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs +++ b/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs @@ -22,7 +22,7 @@ namespace Emby.Server.Implementations.HttpClientManager /// public class HttpClientManager : IHttpClient { - private readonly ILogger _logger; + private readonly ILogger _logger; private readonly IApplicationPaths _appPaths; private readonly IFileSystem _fileSystem; private readonly IApplicationHost _appHost; diff --git a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs index 7de4f168c..17994d199 100644 --- a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs +++ b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs @@ -41,7 +41,7 @@ namespace Emby.Server.Implementations.HttpServer /// public const string DefaultRedirectKey = "HttpListenerHost:DefaultRedirectPath"; - private readonly ILogger _logger; + private readonly ILogger _logger; private readonly ILoggerFactory _loggerFactory; private readonly IServerConfigurationManager _config; private readonly INetworkManager _networkManager; @@ -397,7 +397,7 @@ namespace Emby.Server.Implementations.HttpServer var response = context.Response; var localPath = context.Request.Path.ToString(); - var req = new WebSocketSharpRequest(request, response, request.Path, _logger); + var req = new WebSocketSharpRequest(request, response, request.Path); return RequestHandler(req, request.GetDisplayUrl(), request.Host.ToString(), localPath, context.RequestAborted); } diff --git a/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs b/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs index 9d5969583..cc4797790 100644 --- a/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs +++ b/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs @@ -37,7 +37,7 @@ namespace Emby.Server.Implementations.HttpServer /// /// The logger. /// - private readonly ILogger _logger; + private readonly ILogger _logger; private readonly IFileSystem _fileSystem; private readonly IJsonSerializer _jsonSerializer; private readonly IStreamHelper _streamHelper; @@ -50,7 +50,7 @@ namespace Emby.Server.Implementations.HttpServer _fileSystem = fileSystem; _jsonSerializer = jsonSerializer; _streamHelper = streamHelper; - _logger = loggerfactory.CreateLogger("HttpResultFactory"); + _logger = loggerfactory.CreateLogger(); } /// diff --git a/Emby.Server.Implementations/HttpServer/Security/AuthService.cs b/Emby.Server.Implementations/HttpServer/Security/AuthService.cs index 256b24924..9bb29586e 100644 --- a/Emby.Server.Implementations/HttpServer/Security/AuthService.cs +++ b/Emby.Server.Implementations/HttpServer/Security/AuthService.cs @@ -45,7 +45,7 @@ namespace Emby.Server.Implementations.HttpServer.Security public User Authenticate(HttpRequest request, IAuthenticationAttributes authAttributes) { - var req = new WebSocketSharpRequest(request, null, request.Path, _logger); + var req = new WebSocketSharpRequest(request, null, request.Path); var user = ValidateUser(req, authAttributes); return user; } diff --git a/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs b/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs index 0680c5ffe..c64d57339 100644 --- a/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs +++ b/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs @@ -24,7 +24,7 @@ namespace Emby.Server.Implementations.HttpServer /// /// The logger. /// - private readonly ILogger _logger; + private readonly ILogger _logger; /// /// The json serializer options. diff --git a/Emby.Server.Implementations/IO/LibraryMonitor.cs b/Emby.Server.Implementations/IO/LibraryMonitor.cs index eb5e190aa..49bca7dac 100644 --- a/Emby.Server.Implementations/IO/LibraryMonitor.cs +++ b/Emby.Server.Implementations/IO/LibraryMonitor.cs @@ -18,7 +18,7 @@ namespace Emby.Server.Implementations.IO { public class LibraryMonitor : ILibraryMonitor { - private readonly ILogger _logger; + private readonly ILogger _logger; private readonly ILibraryManager _libraryManager; private readonly IServerConfigurationManager _configurationManager; private readonly IFileSystem _fileSystem; diff --git a/Emby.Server.Implementations/IO/ManagedFileSystem.cs b/Emby.Server.Implementations/IO/ManagedFileSystem.cs index 7461ec4f1..2bcfc82b6 100644 --- a/Emby.Server.Implementations/IO/ManagedFileSystem.cs +++ b/Emby.Server.Implementations/IO/ManagedFileSystem.cs @@ -20,7 +20,7 @@ namespace Emby.Server.Implementations.IO /// public class ManagedFileSystem : IFileSystem { - protected ILogger Logger; + protected ILogger Logger; private readonly List _shortcutHandlers = new List(); private readonly string _tempPath; diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs index 677030b82..8c6be8e04 100644 --- a/Emby.Server.Implementations/Library/LibraryManager.cs +++ b/Emby.Server.Implementations/Library/LibraryManager.cs @@ -56,7 +56,7 @@ namespace Emby.Server.Implementations.Library /// public class LibraryManager : ILibraryManager { - private readonly ILogger _logger; + private readonly ILogger _logger; private readonly ITaskManager _taskManager; private readonly IUserManager _userManager; private readonly IUserDataManager _userDataRepository; diff --git a/Emby.Server.Implementations/Library/MediaSourceManager.cs b/Emby.Server.Implementations/Library/MediaSourceManager.cs index a5e5981b8..00826c7cb 100644 --- a/Emby.Server.Implementations/Library/MediaSourceManager.cs +++ b/Emby.Server.Implementations/Library/MediaSourceManager.cs @@ -33,7 +33,7 @@ namespace Emby.Server.Implementations.Library private readonly ILibraryManager _libraryManager; private readonly IJsonSerializer _jsonSerializer; private readonly IFileSystem _fileSystem; - private readonly ILogger _logger; + private readonly ILogger _logger; private readonly IUserDataManager _userDataManager; private readonly IMediaEncoder _mediaEncoder; private readonly ILocalizationManager _localizationManager; diff --git a/Emby.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs index 6c9ba7c27..ebfe95d0a 100644 --- a/Emby.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs @@ -109,7 +109,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio IEnumerable list, bool allowSubfolders, IDirectoryService directoryService, - ILogger logger, + ILogger logger, IFileSystem fileSystem, ILibraryManager libraryManager) { diff --git a/Emby.Server.Implementations/Library/Resolvers/TV/SeasonResolver.cs b/Emby.Server.Implementations/Library/Resolvers/TV/SeasonResolver.cs index 18145b7f1..7f8800a64 100644 --- a/Emby.Server.Implementations/Library/Resolvers/TV/SeasonResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/TV/SeasonResolver.cs @@ -16,7 +16,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV private readonly IServerConfigurationManager _config; private readonly ILibraryManager _libraryManager; private readonly ILocalizationManager _localization; - private readonly ILogger _logger; + private readonly ILogger _logger; /// /// Initializes a new instance of the class. diff --git a/Emby.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs b/Emby.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs index dd6bd8ee8..4180dad36 100644 --- a/Emby.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs @@ -20,7 +20,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV public class SeriesResolver : FolderResolver { private readonly IFileSystem _fileSystem; - private readonly ILogger _logger; + private readonly ILogger _logger; private readonly ILibraryManager _libraryManager; /// @@ -119,7 +119,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV IEnumerable fileSystemChildren, IDirectoryService directoryService, IFileSystem fileSystem, - ILogger logger, + ILogger logger, ILibraryManager libraryManager, bool isTvContentType) { diff --git a/Emby.Server.Implementations/Library/SearchEngine.cs b/Emby.Server.Implementations/Library/SearchEngine.cs index 59a77607d..bde77ee8e 100644 --- a/Emby.Server.Implementations/Library/SearchEngine.cs +++ b/Emby.Server.Implementations/Library/SearchEngine.cs @@ -17,7 +17,7 @@ namespace Emby.Server.Implementations.Library { public class SearchEngine : ISearchEngine { - private readonly ILogger _logger; + private readonly ILogger _logger; private readonly ILibraryManager _libraryManager; private readonly IUserManager _userManager; diff --git a/Emby.Server.Implementations/Library/UserDataManager.cs b/Emby.Server.Implementations/Library/UserDataManager.cs index a9772a078..d8694b07d 100644 --- a/Emby.Server.Implementations/Library/UserDataManager.cs +++ b/Emby.Server.Implementations/Library/UserDataManager.cs @@ -26,7 +26,7 @@ namespace Emby.Server.Implementations.Library private readonly ConcurrentDictionary _userData = new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); - private readonly ILogger _logger; + private readonly ILogger _logger; private readonly IServerConfigurationManager _config; private readonly IUserManager _userManager; private readonly IUserDataRepository _repository; diff --git a/Emby.Server.Implementations/Library/UserManager.cs b/Emby.Server.Implementations/Library/UserManager.cs index 140155d0e..531e00666 100644 --- a/Emby.Server.Implementations/Library/UserManager.cs +++ b/Emby.Server.Implementations/Library/UserManager.cs @@ -46,7 +46,7 @@ namespace Emby.Server.Implementations.Library private readonly object _policySyncLock = new object(); private readonly object _configSyncLock = new object(); - private readonly ILogger _logger; + private readonly ILogger _logger; private readonly IUserRepository _userRepository; private readonly IXmlSerializer _xmlSerializer; private readonly IJsonSerializer _jsonSerializer; diff --git a/Emby.Server.Implementations/Library/Validators/ArtistsPostScanTask.cs b/Emby.Server.Implementations/Library/Validators/ArtistsPostScanTask.cs index 2af8ff5cb..d51f9aaa7 100644 --- a/Emby.Server.Implementations/Library/Validators/ArtistsPostScanTask.cs +++ b/Emby.Server.Implementations/Library/Validators/ArtistsPostScanTask.cs @@ -16,7 +16,7 @@ namespace Emby.Server.Implementations.Library.Validators /// The _library manager. /// private readonly ILibraryManager _libraryManager; - private readonly ILogger _logger; + private readonly ILogger _logger; private readonly IItemRepository _itemRepo; /// @@ -27,7 +27,7 @@ namespace Emby.Server.Implementations.Library.Validators /// The item repository. public ArtistsPostScanTask( ILibraryManager libraryManager, - ILogger logger, + ILogger logger, IItemRepository itemRepo) { _libraryManager = libraryManager; diff --git a/Emby.Server.Implementations/Library/Validators/ArtistsValidator.cs b/Emby.Server.Implementations/Library/Validators/ArtistsValidator.cs index 1497f4a3a..8a6bd5e78 100644 --- a/Emby.Server.Implementations/Library/Validators/ArtistsValidator.cs +++ b/Emby.Server.Implementations/Library/Validators/ArtistsValidator.cs @@ -24,7 +24,7 @@ namespace Emby.Server.Implementations.Library.Validators /// /// The logger. /// - private readonly ILogger _logger; + private readonly ILogger _logger; private readonly IItemRepository _itemRepo; /// @@ -33,7 +33,7 @@ namespace Emby.Server.Implementations.Library.Validators /// The library manager. /// The logger. /// The item repository. - public ArtistsValidator(ILibraryManager libraryManager, ILogger logger, IItemRepository itemRepo) + public ArtistsValidator(ILibraryManager libraryManager, ILogger logger, IItemRepository itemRepo) { _libraryManager = libraryManager; _logger = logger; diff --git a/Emby.Server.Implementations/Library/Validators/GenresPostScanTask.cs b/Emby.Server.Implementations/Library/Validators/GenresPostScanTask.cs index 251785dfd..d21d2887b 100644 --- a/Emby.Server.Implementations/Library/Validators/GenresPostScanTask.cs +++ b/Emby.Server.Implementations/Library/Validators/GenresPostScanTask.cs @@ -16,7 +16,7 @@ namespace Emby.Server.Implementations.Library.Validators /// The _library manager. /// private readonly ILibraryManager _libraryManager; - private readonly ILogger _logger; + private readonly ILogger _logger; private readonly IItemRepository _itemRepo; /// @@ -27,7 +27,7 @@ namespace Emby.Server.Implementations.Library.Validators /// The item repository. public GenresPostScanTask( ILibraryManager libraryManager, - ILogger logger, + ILogger logger, IItemRepository itemRepo) { _libraryManager = libraryManager; diff --git a/Emby.Server.Implementations/Library/Validators/GenresValidator.cs b/Emby.Server.Implementations/Library/Validators/GenresValidator.cs index b0cd5f87a..e59c62e23 100644 --- a/Emby.Server.Implementations/Library/Validators/GenresValidator.cs +++ b/Emby.Server.Implementations/Library/Validators/GenresValidator.cs @@ -21,7 +21,7 @@ namespace Emby.Server.Implementations.Library.Validators /// /// The logger. /// - private readonly ILogger _logger; + private readonly ILogger _logger; /// /// Initializes a new instance of the class. @@ -29,7 +29,7 @@ namespace Emby.Server.Implementations.Library.Validators /// The library manager. /// The logger. /// The item repository. - public GenresValidator(ILibraryManager libraryManager, ILogger logger, IItemRepository itemRepo) + public GenresValidator(ILibraryManager libraryManager, ILogger logger, IItemRepository itemRepo) { _libraryManager = libraryManager; _logger = logger; diff --git a/Emby.Server.Implementations/Library/Validators/MusicGenresPostScanTask.cs b/Emby.Server.Implementations/Library/Validators/MusicGenresPostScanTask.cs index 9d8690116..be119866b 100644 --- a/Emby.Server.Implementations/Library/Validators/MusicGenresPostScanTask.cs +++ b/Emby.Server.Implementations/Library/Validators/MusicGenresPostScanTask.cs @@ -16,7 +16,7 @@ namespace Emby.Server.Implementations.Library.Validators /// The library manager. /// private readonly ILibraryManager _libraryManager; - private readonly ILogger _logger; + private readonly ILogger _logger; private readonly IItemRepository _itemRepo; /// @@ -27,7 +27,7 @@ namespace Emby.Server.Implementations.Library.Validators /// The item repository. public MusicGenresPostScanTask( ILibraryManager libraryManager, - ILogger logger, + ILogger logger, IItemRepository itemRepo) { _libraryManager = libraryManager; diff --git a/Emby.Server.Implementations/Library/Validators/MusicGenresValidator.cs b/Emby.Server.Implementations/Library/Validators/MusicGenresValidator.cs index 5ee4ca72e..1ecf4c87c 100644 --- a/Emby.Server.Implementations/Library/Validators/MusicGenresValidator.cs +++ b/Emby.Server.Implementations/Library/Validators/MusicGenresValidator.cs @@ -20,7 +20,7 @@ namespace Emby.Server.Implementations.Library.Validators /// /// The logger. /// - private readonly ILogger _logger; + private readonly ILogger _logger; private readonly IItemRepository _itemRepo; /// @@ -29,7 +29,7 @@ namespace Emby.Server.Implementations.Library.Validators /// The library manager. /// The logger. /// The item repository. - public MusicGenresValidator(ILibraryManager libraryManager, ILogger logger, IItemRepository itemRepo) + public MusicGenresValidator(ILibraryManager libraryManager, ILogger logger, IItemRepository itemRepo) { _libraryManager = libraryManager; _logger = logger; diff --git a/Emby.Server.Implementations/Library/Validators/StudiosPostScanTask.cs b/Emby.Server.Implementations/Library/Validators/StudiosPostScanTask.cs index 2f8f906b9..c682b156b 100644 --- a/Emby.Server.Implementations/Library/Validators/StudiosPostScanTask.cs +++ b/Emby.Server.Implementations/Library/Validators/StudiosPostScanTask.cs @@ -17,7 +17,7 @@ namespace Emby.Server.Implementations.Library.Validators /// private readonly ILibraryManager _libraryManager; - private readonly ILogger _logger; + private readonly ILogger _logger; private readonly IItemRepository _itemRepo; /// @@ -28,7 +28,7 @@ namespace Emby.Server.Implementations.Library.Validators /// The item repository. public StudiosPostScanTask( ILibraryManager libraryManager, - ILogger logger, + ILogger logger, IItemRepository itemRepo) { _libraryManager = libraryManager; diff --git a/Emby.Server.Implementations/Library/Validators/StudiosValidator.cs b/Emby.Server.Implementations/Library/Validators/StudiosValidator.cs index 15e7a0dbb..7a6cd11df 100644 --- a/Emby.Server.Implementations/Library/Validators/StudiosValidator.cs +++ b/Emby.Server.Implementations/Library/Validators/StudiosValidator.cs @@ -24,7 +24,7 @@ namespace Emby.Server.Implementations.Library.Validators /// /// The logger. /// - private readonly ILogger _logger; + private readonly ILogger _logger; /// /// Initializes a new instance of the class. @@ -32,7 +32,7 @@ namespace Emby.Server.Implementations.Library.Validators /// The library manager. /// The logger. /// The item repository. - public StudiosValidator(ILibraryManager libraryManager, ILogger logger, IItemRepository itemRepo) + public StudiosValidator(ILibraryManager libraryManager, ILogger logger, IItemRepository itemRepo) { _libraryManager = libraryManager; _logger = logger; diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs index 5a5dc3329..f9b71939b 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs @@ -46,7 +46,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV private const int TunerDiscoveryDurationMs = 3000; private readonly IServerApplicationHost _appHost; - private readonly ILogger _logger; + private readonly ILogger _logger; private readonly IHttpClient _httpClient; private readonly IServerConfigurationManager _config; private readonly IJsonSerializer _jsonSerializer; diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs index 89b81fd96..02f18060d 100644 --- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs +++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs @@ -24,7 +24,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings { public class SchedulesDirect : IListingsProvider { - private readonly ILogger _logger; + private readonly ILogger _logger; private readonly IJsonSerializer _jsonSerializer; private readonly IHttpClient _httpClient; private readonly SemaphoreSlim _tokenSemaphore = new SemaphoreSlim(1, 1); diff --git a/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs b/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs index 07f8539c5..077b5c7e5 100644 --- a/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs +++ b/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs @@ -26,7 +26,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings { private readonly IServerConfigurationManager _config; private readonly IHttpClient _httpClient; - private readonly ILogger _logger; + private readonly ILogger _logger; private readonly IFileSystem _fileSystem; private readonly IZipClient _zipClient; diff --git a/Emby.Server.Implementations/LiveTv/LiveTvDtoService.cs b/Emby.Server.Implementations/LiveTv/LiveTvDtoService.cs index a59c1090e..49ad73af3 100644 --- a/Emby.Server.Implementations/LiveTv/LiveTvDtoService.cs +++ b/Emby.Server.Implementations/LiveTv/LiveTvDtoService.cs @@ -26,7 +26,7 @@ namespace Emby.Server.Implementations.LiveTv private const string ServiceName = "Emby"; - private readonly ILogger _logger; + private readonly ILogger _logger; private readonly IImageProcessor _imageProcessor; private readonly IDtoService _dtoService; private readonly IApplicationHost _appHost; diff --git a/Emby.Server.Implementations/LiveTv/LiveTvManager.cs b/Emby.Server.Implementations/LiveTv/LiveTvManager.cs index dbd0e6f2e..a88e0f31f 100644 --- a/Emby.Server.Implementations/LiveTv/LiveTvManager.cs +++ b/Emby.Server.Implementations/LiveTv/LiveTvManager.cs @@ -44,7 +44,7 @@ namespace Emby.Server.Implementations.LiveTv private const string EtagKey = "ProgramEtag"; private readonly IServerConfigurationManager _config; - private readonly ILogger _logger; + private readonly ILogger _logger; private readonly IItemRepository _itemRepo; private readonly IUserManager _userManager; private readonly IDtoService _dtoService; diff --git a/Emby.Server.Implementations/LiveTv/LiveTvMediaSourceProvider.cs b/Emby.Server.Implementations/LiveTv/LiveTvMediaSourceProvider.cs index 7f63991d0..f3fc41352 100644 --- a/Emby.Server.Implementations/LiveTv/LiveTvMediaSourceProvider.cs +++ b/Emby.Server.Implementations/LiveTv/LiveTvMediaSourceProvider.cs @@ -23,7 +23,7 @@ namespace Emby.Server.Implementations.LiveTv private const string StreamIdDelimeterString = "_"; private readonly ILiveTvManager _liveTvManager; - private readonly ILogger _logger; + private readonly ILogger _logger; private readonly IMediaSourceManager _mediaSourceManager; private readonly IServerApplicationHost _appHost; diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs index 80ee1ee33..ba3594efd 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs @@ -22,14 +22,14 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts public abstract class BaseTunerHost { protected readonly IServerConfigurationManager Config; - protected readonly ILogger Logger; + protected readonly ILogger Logger; protected IJsonSerializer JsonSerializer; protected readonly IFileSystem FileSystem; private readonly ConcurrentDictionary _channelCache = new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); - protected BaseTunerHost(IServerConfigurationManager config, ILogger logger, IJsonSerializer jsonSerializer, IFileSystem fileSystem) + protected BaseTunerHost(IServerConfigurationManager config, ILogger logger, IJsonSerializer jsonSerializer, IFileSystem fileSystem) { Config = config; Logger = logger; diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/LiveStream.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/LiveStream.cs index 4e4f1d7f6..c3336ca92 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/LiveStream.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/LiveStream.cs @@ -7,6 +7,7 @@ using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; +using Emby.Server.Implementations.Library; using MediaBrowser.Common.Configuration; using MediaBrowser.Controller.Library; using MediaBrowser.Model.Dto; diff --git a/Emby.Server.Implementations/Localization/LocalizationManager.cs b/Emby.Server.Implementations/Localization/LocalizationManager.cs index e2a634e1a..62a23118f 100644 --- a/Emby.Server.Implementations/Localization/LocalizationManager.cs +++ b/Emby.Server.Implementations/Localization/LocalizationManager.cs @@ -25,7 +25,7 @@ namespace Emby.Server.Implementations.Localization private readonly IServerConfigurationManager _configurationManager; private readonly IJsonSerializer _jsonSerializer; - private readonly ILogger _logger; + private readonly ILogger _logger; private readonly Dictionary> _allParentalRatings = new Dictionary>(StringComparer.OrdinalIgnoreCase); diff --git a/Emby.Server.Implementations/MediaEncoder/EncodingManager.cs b/Emby.Server.Implementations/MediaEncoder/EncodingManager.cs index 7b7575707..438bbe24a 100644 --- a/Emby.Server.Implementations/MediaEncoder/EncodingManager.cs +++ b/Emby.Server.Implementations/MediaEncoder/EncodingManager.cs @@ -23,7 +23,7 @@ namespace Emby.Server.Implementations.MediaEncoder { private readonly CultureInfo _usCulture = new CultureInfo("en-US"); private readonly IFileSystem _fileSystem; - private readonly ILogger _logger; + private readonly ILogger _logger; private readonly IMediaEncoder _encoder; private readonly IChapterManager _chapterManager; private readonly ILibraryManager _libraryManager; diff --git a/Emby.Server.Implementations/Networking/NetworkManager.cs b/Emby.Server.Implementations/Networking/NetworkManager.cs index d1a28e7a1..45864bb42 100644 --- a/Emby.Server.Implementations/Networking/NetworkManager.cs +++ b/Emby.Server.Implementations/Networking/NetworkManager.cs @@ -15,7 +15,7 @@ namespace Emby.Server.Implementations.Networking { public class NetworkManager : INetworkManager { - private readonly ILogger _logger; + private readonly ILogger _logger; private IPAddress[] _localIpAddresses; private readonly object _localIpAddressSyncLock = new object(); diff --git a/Emby.Server.Implementations/Playlists/PlaylistManager.cs b/Emby.Server.Implementations/Playlists/PlaylistManager.cs index c51eb0586..8d022d6c4 100644 --- a/Emby.Server.Implementations/Playlists/PlaylistManager.cs +++ b/Emby.Server.Implementations/Playlists/PlaylistManager.cs @@ -29,7 +29,7 @@ namespace Emby.Server.Implementations.Playlists private readonly ILibraryManager _libraryManager; private readonly IFileSystem _fileSystem; private readonly ILibraryMonitor _iLibraryMonitor; - private readonly ILogger _logger; + private readonly ILogger _logger; private readonly IUserManager _userManager; private readonly IProviderManager _providerManager; private readonly IConfiguration _appConfig; diff --git a/Emby.Server.Implementations/ResourceFileManager.cs b/Emby.Server.Implementations/ResourceFileManager.cs index d192be921..22fc62293 100644 --- a/Emby.Server.Implementations/ResourceFileManager.cs +++ b/Emby.Server.Implementations/ResourceFileManager.cs @@ -12,7 +12,7 @@ namespace Emby.Server.Implementations public class ResourceFileManager : IResourceFileManager { private readonly IFileSystem _fileSystem; - private readonly ILogger _logger; + private readonly ILogger _logger; public ResourceFileManager(ILogger logger, IFileSystem fileSystem) { diff --git a/Emby.Server.Implementations/ScheduledTasks/TaskManager.cs b/Emby.Server.Implementations/ScheduledTasks/TaskManager.cs index efefa5506..94220ac5d 100644 --- a/Emby.Server.Implementations/ScheduledTasks/TaskManager.cs +++ b/Emby.Server.Implementations/ScheduledTasks/TaskManager.cs @@ -36,7 +36,7 @@ namespace Emby.Server.Implementations.ScheduledTasks private readonly IJsonSerializer _jsonSerializer; private readonly IApplicationPaths _applicationPaths; - private readonly ILogger _logger; + private readonly ILogger _logger; private readonly IFileSystem _fileSystem; /// diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/ChapterImagesTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/ChapterImagesTask.cs index fae049914..9028222dd 100644 --- a/Emby.Server.Implementations/ScheduledTasks/Tasks/ChapterImagesTask.cs +++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/ChapterImagesTask.cs @@ -27,7 +27,7 @@ namespace Emby.Server.Implementations.ScheduledTasks /// /// The _logger. /// - private readonly ILogger _logger; + private readonly ILogger _logger; /// /// The _library manager. @@ -54,7 +54,7 @@ namespace Emby.Server.Implementations.ScheduledTasks IFileSystem fileSystem, ILocalizationManager localization) { - _logger = loggerFactory.CreateLogger(GetType().Name); + _logger = loggerFactory.CreateLogger(); _libraryManager = libraryManager; _itemRepo = itemRepo; _appPaths = appPaths; diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteCacheFileTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteCacheFileTask.cs index a6c13eaef..966b549b2 100644 --- a/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteCacheFileTask.cs +++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteCacheFileTask.cs @@ -23,7 +23,7 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks /// The application paths. private IApplicationPaths ApplicationPaths { get; set; } - private readonly ILogger _logger; + private readonly ILogger _logger; private readonly IFileSystem _fileSystem; private readonly ILocalizationManager _localization; diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteTranscodeFileTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteTranscodeFileTask.cs index 0d36b82c0..53cf9a0a5 100644 --- a/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteTranscodeFileTask.cs +++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteTranscodeFileTask.cs @@ -17,7 +17,7 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks /// public class DeleteTranscodeFileTask : IScheduledTask, IConfigurableScheduledTask { - private readonly ILogger _logger; + private readonly ILogger _logger; private readonly IConfigurationManager _configurationManager; private readonly IFileSystem _fileSystem; private readonly ILocalizationManager _localization; diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/PluginUpdateTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/PluginUpdateTask.cs index acab3aeea..7388086fb 100644 --- a/Emby.Server.Implementations/ScheduledTasks/Tasks/PluginUpdateTask.cs +++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/PluginUpdateTask.cs @@ -22,7 +22,7 @@ namespace Emby.Server.Implementations.ScheduledTasks /// /// The _logger. /// - private readonly ILogger _logger; + private readonly ILogger _logger; private readonly IInstallationManager _installationManager; private readonly ILocalizationManager _localization; diff --git a/Emby.Server.Implementations/Services/ServiceController.cs b/Emby.Server.Implementations/Services/ServiceController.cs index ad6015c1c..e688278b5 100644 --- a/Emby.Server.Implementations/Services/ServiceController.cs +++ b/Emby.Server.Implementations/Services/ServiceController.cs @@ -15,7 +15,7 @@ namespace Emby.Server.Implementations.Services public class ServiceController { - private readonly ILogger _logger; + private readonly ILogger _logger; /// /// Initializes a new instance of the class. diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs index 5c480e842..506e6739f 100644 --- a/Emby.Server.Implementations/Session/SessionManager.cs +++ b/Emby.Server.Implementations/Session/SessionManager.cs @@ -45,7 +45,7 @@ namespace Emby.Server.Implementations.Session /// /// The logger. /// - private readonly ILogger _logger; + private readonly ILogger _logger; private readonly ILibraryManager _libraryManager; private readonly IUserManager _userManager; diff --git a/Emby.Server.Implementations/Session/SessionWebSocketListener.cs b/Emby.Server.Implementations/Session/SessionWebSocketListener.cs index e7b4b0ec3..ef32c692c 100644 --- a/Emby.Server.Implementations/Session/SessionWebSocketListener.cs +++ b/Emby.Server.Implementations/Session/SessionWebSocketListener.cs @@ -41,7 +41,7 @@ namespace Emby.Server.Implementations.Session /// /// The _logger /// - private readonly ILogger _logger; + private readonly ILogger _logger; private readonly ILoggerFactory _loggerFactory; private readonly IHttpServer _httpServer; diff --git a/Emby.Server.Implementations/Session/WebSocketController.cs b/Emby.Server.Implementations/Session/WebSocketController.cs index a0274acd2..94604ca1e 100644 --- a/Emby.Server.Implementations/Session/WebSocketController.cs +++ b/Emby.Server.Implementations/Session/WebSocketController.cs @@ -17,7 +17,7 @@ namespace Emby.Server.Implementations.Session { public sealed class WebSocketController : ISessionController, IDisposable { - private readonly ILogger _logger; + private readonly ILogger _logger; private readonly ISessionManager _sessionManager; private readonly SessionInfo _session; diff --git a/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs b/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs index aa76901a4..ae1a8d0b7 100644 --- a/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs +++ b/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs @@ -26,7 +26,7 @@ namespace Emby.Server.Implementations.SocketSharp private Dictionary _items; private string _responseContentType; - public WebSocketSharpRequest(HttpRequest httpRequest, HttpResponse httpResponse, string operationName, ILogger logger) + public WebSocketSharpRequest(HttpRequest httpRequest, HttpResponse httpResponse, string operationName) { this.OperationName = operationName; this.Request = httpRequest; diff --git a/Emby.Server.Implementations/SyncPlay/SyncPlayManager.cs b/Emby.Server.Implementations/SyncPlay/SyncPlayManager.cs index 129262e53..1bfc9a9a5 100644 --- a/Emby.Server.Implementations/SyncPlay/SyncPlayManager.cs +++ b/Emby.Server.Implementations/SyncPlay/SyncPlayManager.cs @@ -21,7 +21,7 @@ namespace Emby.Server.Implementations.SyncPlay /// /// The logger. /// - private readonly ILogger _logger; + private readonly ILogger _logger; /// /// The user manager. diff --git a/Emby.Server.Implementations/Updates/InstallationManager.cs b/Emby.Server.Implementations/Updates/InstallationManager.cs index 178f32c31..80326fddf 100644 --- a/Emby.Server.Implementations/Updates/InstallationManager.cs +++ b/Emby.Server.Implementations/Updates/InstallationManager.cs @@ -39,7 +39,7 @@ namespace Emby.Server.Implementations.Updates /// /// The logger. /// - private readonly ILogger _logger; + private readonly ILogger _logger; private readonly IApplicationPaths _appPaths; private readonly IHttpClient _httpClient; private readonly IJsonSerializer _jsonSerializer; diff --git a/Jellyfin.Drawing.Skia/SkiaEncoder.cs b/Jellyfin.Drawing.Skia/SkiaEncoder.cs index ccee5c5b9..ba9a5809f 100644 --- a/Jellyfin.Drawing.Skia/SkiaEncoder.cs +++ b/Jellyfin.Drawing.Skia/SkiaEncoder.cs @@ -21,7 +21,7 @@ namespace Jellyfin.Drawing.Skia private static readonly HashSet _transparentImageTypes = new HashSet(StringComparer.OrdinalIgnoreCase) { ".png", ".gif", ".webp" }; - private readonly ILogger _logger; + private readonly ILogger _logger; private readonly IApplicationPaths _appPaths; /// diff --git a/Jellyfin.Server/Migrations/Routines/DisableTranscodingThrottling.cs b/Jellyfin.Server/Migrations/Routines/DisableTranscodingThrottling.cs index b2e957d5b..c18aa1629 100644 --- a/Jellyfin.Server/Migrations/Routines/DisableTranscodingThrottling.cs +++ b/Jellyfin.Server/Migrations/Routines/DisableTranscodingThrottling.cs @@ -10,7 +10,7 @@ namespace Jellyfin.Server.Migrations.Routines /// internal class DisableTranscodingThrottling : IMigrationRoutine { - private readonly ILogger _logger; + private readonly ILogger _logger; private readonly IConfigurationManager _configManager; public DisableTranscodingThrottling(ILogger logger, IConfigurationManager configManager) diff --git a/Jellyfin.Server/Migrations/Routines/RemoveDuplicateExtras.cs b/Jellyfin.Server/Migrations/Routines/RemoveDuplicateExtras.cs index e95536388..2ebef0241 100644 --- a/Jellyfin.Server/Migrations/Routines/RemoveDuplicateExtras.cs +++ b/Jellyfin.Server/Migrations/Routines/RemoveDuplicateExtras.cs @@ -14,7 +14,7 @@ namespace Jellyfin.Server.Migrations.Routines internal class RemoveDuplicateExtras : IMigrationRoutine { private const string DbFilename = "library.db"; - private readonly ILogger _logger; + private readonly ILogger _logger; private readonly IServerApplicationPaths _paths; public RemoveDuplicateExtras(ILogger logger, IServerApplicationPaths paths) diff --git a/MediaBrowser.Api/ApiEntryPoint.cs b/MediaBrowser.Api/ApiEntryPoint.cs index c7485a2e9..9e651fb19 100644 --- a/MediaBrowser.Api/ApiEntryPoint.cs +++ b/MediaBrowser.Api/ApiEntryPoint.cs @@ -31,7 +31,7 @@ namespace MediaBrowser.Api /// /// The logger. /// - private ILogger _logger; + private ILogger _logger; /// /// The configuration manager. diff --git a/MediaBrowser.Api/Music/AlbumsService.cs b/MediaBrowser.Api/Music/AlbumsService.cs index 58c95d053..f257d1014 100644 --- a/MediaBrowser.Api/Music/AlbumsService.cs +++ b/MediaBrowser.Api/Music/AlbumsService.cs @@ -67,12 +67,13 @@ namespace MediaBrowser.Api.Music { var dtoOptions = GetDtoOptions(_authContext, request); - var result = SimilarItemsHelper.GetSimilarItemsResult(dtoOptions, _userManager, + var result = SimilarItemsHelper.GetSimilarItemsResult( + dtoOptions, + _userManager, _itemRepo, _libraryManager, _userDataRepository, _dtoService, - Logger, request, new[] { typeof(MusicArtist) }, SimilarItemsHelper.GetSimiliarityScore); @@ -88,12 +89,13 @@ namespace MediaBrowser.Api.Music { var dtoOptions = GetDtoOptions(_authContext, request); - var result = SimilarItemsHelper.GetSimilarItemsResult(dtoOptions, _userManager, + var result = SimilarItemsHelper.GetSimilarItemsResult( + dtoOptions, + _userManager, _itemRepo, _libraryManager, _userDataRepository, _dtoService, - Logger, request, new[] { typeof(MusicAlbum) }, GetAlbumSimilarityScore); diff --git a/MediaBrowser.Api/SimilarItemsHelper.cs b/MediaBrowser.Api/SimilarItemsHelper.cs index 44bb24ef2..dcd22280a 100644 --- a/MediaBrowser.Api/SimilarItemsHelper.cs +++ b/MediaBrowser.Api/SimilarItemsHelper.cs @@ -69,7 +69,7 @@ namespace MediaBrowser.Api /// public static class SimilarItemsHelper { - internal static QueryResult GetSimilarItemsResult(DtoOptions dtoOptions, IUserManager userManager, IItemRepository itemRepository, ILibraryManager libraryManager, IUserDataManager userDataRepository, IDtoService dtoService, ILogger logger, BaseGetSimilarItemsFromItem request, Type[] includeTypes, Func, List, BaseItem, int> getSimilarityScore) + internal static QueryResult GetSimilarItemsResult(DtoOptions dtoOptions, IUserManager userManager, IItemRepository itemRepository, ILibraryManager libraryManager, IUserDataManager userDataRepository, IDtoService dtoService, BaseGetSimilarItemsFromItem request, Type[] includeTypes, Func, List, BaseItem, int> getSimilarityScore) { var user = !request.UserId.Equals(Guid.Empty) ? userManager.GetUserById(request.UserId) : null; diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs index f4b71d8bf..2c789e386 100644 --- a/MediaBrowser.Controller/Entities/BaseItem.cs +++ b/MediaBrowser.Controller/Entities/BaseItem.cs @@ -557,7 +557,8 @@ namespace MediaBrowser.Controller.Entities /// /// The logger /// - public static ILogger Logger { get; set; } + public static ILoggerFactory LoggerFactory { get; set; } + public static ILogger Logger { get; set; } public static ILibraryManager LibraryManager { get; set; } public static IServerConfigurationManager ConfigurationManager { get; set; } public static IProviderManager ProviderManager { get; set; } diff --git a/MediaBrowser.Controller/Entities/UserView.cs b/MediaBrowser.Controller/Entities/UserView.cs index 4ce9ec6f8..86cff632c 100644 --- a/MediaBrowser.Controller/Entities/UserView.cs +++ b/MediaBrowser.Controller/Entities/UserView.cs @@ -5,6 +5,7 @@ using System.Text.Json.Serialization; using System.Threading.Tasks; using MediaBrowser.Controller.TV; using MediaBrowser.Model.Querying; +using Microsoft.Extensions.Logging; namespace MediaBrowser.Controller.Entities { @@ -66,7 +67,7 @@ namespace MediaBrowser.Controller.Entities parent = LibraryManager.GetItemById(ParentId) as Folder ?? parent; } - return new UserViewBuilder(UserViewManager, LibraryManager, Logger, UserDataManager, TVSeriesManager, ConfigurationManager) + return new UserViewBuilder(UserViewManager, LibraryManager, LoggerFactory.CreateLogger(), UserDataManager, TVSeriesManager, ConfigurationManager) .GetUserItems(parent, this, CollectionType, query); } diff --git a/MediaBrowser.Controller/Entities/UserViewBuilder.cs b/MediaBrowser.Controller/Entities/UserViewBuilder.cs index 435a1e8da..3d91a0d0e 100644 --- a/MediaBrowser.Controller/Entities/UserViewBuilder.cs +++ b/MediaBrowser.Controller/Entities/UserViewBuilder.cs @@ -17,7 +17,7 @@ namespace MediaBrowser.Controller.Entities { private readonly IUserViewManager _userViewManager; private readonly ILibraryManager _libraryManager; - private readonly ILogger _logger; + private readonly ILogger _logger; private readonly IUserDataManager _userDataManager; private readonly ITVSeriesManager _tvSeriesManager; private readonly IServerConfigurationManager _config; @@ -25,7 +25,7 @@ namespace MediaBrowser.Controller.Entities public UserViewBuilder( IUserViewManager userViewManager, ILibraryManager libraryManager, - ILogger logger, + ILogger logger, IUserDataManager userDataManager, ITVSeriesManager tvSeriesManager, IServerConfigurationManager config) diff --git a/MediaBrowser.Controller/IO/FileData.cs b/MediaBrowser.Controller/IO/FileData.cs index 4bbb60283..666a3f76b 100644 --- a/MediaBrowser.Controller/IO/FileData.cs +++ b/MediaBrowser.Controller/IO/FileData.cs @@ -35,7 +35,8 @@ namespace MediaBrowser.Controller.IO /// if set to true [resolve shortcuts]. /// Dictionary{System.StringFileSystemInfo}. /// path - public static FileSystemMetadata[] GetFilteredFileSystemEntries(IDirectoryService directoryService, + public static FileSystemMetadata[] GetFilteredFileSystemEntries( + IDirectoryService directoryService, string path, IFileSystem fileSystem, IServerApplicationHost appHost, diff --git a/MediaBrowser.Controller/Library/Profiler.cs b/MediaBrowser.Controller/Library/Profiler.cs index 46a97d181..0febef3d3 100644 --- a/MediaBrowser.Controller/Library/Profiler.cs +++ b/MediaBrowser.Controller/Library/Profiler.cs @@ -21,7 +21,7 @@ namespace MediaBrowser.Controller.Library /// /// The _logger /// - private readonly ILogger _logger; + private readonly ILogger _logger; /// /// Initializes a new instance of the class. diff --git a/MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs b/MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs index 5be656bdb..7dca793c6 100644 --- a/MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs +++ b/MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs @@ -40,9 +40,9 @@ namespace MediaBrowser.Controller.Net /// /// The logger /// - protected ILogger Logger; + protected ILogger> Logger; - protected BasePeriodicWebSocketListener(ILogger logger) + protected BasePeriodicWebSocketListener(ILogger> logger) { if (logger == null) { diff --git a/MediaBrowser.Controller/Session/SessionInfo.cs b/MediaBrowser.Controller/Session/SessionInfo.cs index 2ba7c9fec..ecc910872 100644 --- a/MediaBrowser.Controller/Session/SessionInfo.cs +++ b/MediaBrowser.Controller/Session/SessionInfo.cs @@ -3,6 +3,7 @@ using System.Linq; using System.Text.Json.Serialization; using System.Threading; using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Session; using Microsoft.Extensions.Logging; diff --git a/MediaBrowser.LocalMetadata/Images/InternalMetadataFolderImageProvider.cs b/MediaBrowser.LocalMetadata/Images/InternalMetadataFolderImageProvider.cs index 795933ce9..5137e4c68 100644 --- a/MediaBrowser.LocalMetadata/Images/InternalMetadataFolderImageProvider.cs +++ b/MediaBrowser.LocalMetadata/Images/InternalMetadataFolderImageProvider.cs @@ -13,7 +13,7 @@ namespace MediaBrowser.LocalMetadata.Images { private readonly IServerConfigurationManager _config; private readonly IFileSystem _fileSystem; - private readonly ILogger _logger; + private readonly ILogger _logger; public InternalMetadataFolderImageProvider( IServerConfigurationManager config, diff --git a/MediaBrowser.LocalMetadata/Parsers/BaseItemXmlParser.cs b/MediaBrowser.LocalMetadata/Parsers/BaseItemXmlParser.cs index d4b98182f..9621cfa4e 100644 --- a/MediaBrowser.LocalMetadata/Parsers/BaseItemXmlParser.cs +++ b/MediaBrowser.LocalMetadata/Parsers/BaseItemXmlParser.cs @@ -23,7 +23,7 @@ namespace MediaBrowser.LocalMetadata.Parsers /// /// The logger /// - protected ILogger Logger { get; private set; } + protected ILogger> Logger { get; private set; } protected IProviderManager ProviderManager { get; private set; } private Dictionary _validProviderIds; @@ -32,7 +32,7 @@ namespace MediaBrowser.LocalMetadata.Parsers /// Initializes a new instance of the class. /// /// The logger. - public BaseItemXmlParser(ILogger logger, IProviderManager providerManager) + public BaseItemXmlParser(ILogger> logger, IProviderManager providerManager) { Logger = logger; ProviderManager = providerManager; diff --git a/MediaBrowser.LocalMetadata/Parsers/BoxSetXmlParser.cs b/MediaBrowser.LocalMetadata/Parsers/BoxSetXmlParser.cs index 127334625..ca11a079d 100644 --- a/MediaBrowser.LocalMetadata/Parsers/BoxSetXmlParser.cs +++ b/MediaBrowser.LocalMetadata/Parsers/BoxSetXmlParser.cs @@ -85,7 +85,7 @@ namespace MediaBrowser.LocalMetadata.Parsers item.Item.LinkedChildren = list.ToArray(); } - public BoxSetXmlParser(ILogger logger, IProviderManager providerManager) + public BoxSetXmlParser(ILogger logger, IProviderManager providerManager) : base(logger, providerManager) { } diff --git a/MediaBrowser.LocalMetadata/Parsers/PlaylistXmlParser.cs b/MediaBrowser.LocalMetadata/Parsers/PlaylistXmlParser.cs index 5608a0be9..54710cd82 100644 --- a/MediaBrowser.LocalMetadata/Parsers/PlaylistXmlParser.cs +++ b/MediaBrowser.LocalMetadata/Parsers/PlaylistXmlParser.cs @@ -93,7 +93,7 @@ namespace MediaBrowser.LocalMetadata.Parsers item.LinkedChildren = list.ToArray(); } - public PlaylistXmlParser(ILogger logger, IProviderManager providerManager) + public PlaylistXmlParser(ILogger logger, IProviderManager providerManager) : base(logger, providerManager) { } diff --git a/MediaBrowser.LocalMetadata/Providers/BoxSetXmlProvider.cs b/MediaBrowser.LocalMetadata/Providers/BoxSetXmlProvider.cs index b2e3bc9e2..2d115a591 100644 --- a/MediaBrowser.LocalMetadata/Providers/BoxSetXmlProvider.cs +++ b/MediaBrowser.LocalMetadata/Providers/BoxSetXmlProvider.cs @@ -13,10 +13,10 @@ namespace MediaBrowser.LocalMetadata.Providers /// public class BoxSetXmlProvider : BaseXmlProvider { - private readonly ILogger _logger; + private readonly ILogger _logger; private readonly IProviderManager _providerManager; - public BoxSetXmlProvider(IFileSystem fileSystem, ILogger logger, IProviderManager providerManager) + public BoxSetXmlProvider(IFileSystem fileSystem, ILogger logger, IProviderManager providerManager) : base(fileSystem) { _logger = logger; diff --git a/MediaBrowser.LocalMetadata/Providers/PlaylistXmlProvider.cs b/MediaBrowser.LocalMetadata/Providers/PlaylistXmlProvider.cs index df8107bad..d4e2bc8e5 100644 --- a/MediaBrowser.LocalMetadata/Providers/PlaylistXmlProvider.cs +++ b/MediaBrowser.LocalMetadata/Providers/PlaylistXmlProvider.cs @@ -10,12 +10,12 @@ namespace MediaBrowser.LocalMetadata.Providers { public class PlaylistXmlProvider : BaseXmlProvider { - private readonly ILogger _logger; + private readonly ILogger _logger; private readonly IProviderManager _providerManager; public PlaylistXmlProvider( IFileSystem fileSystem, - ILogger logger, + ILogger logger, IProviderManager providerManager) : base(fileSystem) { diff --git a/MediaBrowser.LocalMetadata/Savers/BaseXmlSaver.cs b/MediaBrowser.LocalMetadata/Savers/BaseXmlSaver.cs index ba1d850e3..071902393 100644 --- a/MediaBrowser.LocalMetadata/Savers/BaseXmlSaver.cs +++ b/MediaBrowser.LocalMetadata/Savers/BaseXmlSaver.cs @@ -21,7 +21,7 @@ namespace MediaBrowser.LocalMetadata.Savers { private static readonly CultureInfo UsCulture = new CultureInfo("en-US"); - public BaseXmlSaver(IFileSystem fileSystem, IServerConfigurationManager configurationManager, ILibraryManager libraryManager, IUserManager userManager, IUserDataManager userDataManager, ILogger logger) + public BaseXmlSaver(IFileSystem fileSystem, IServerConfigurationManager configurationManager, ILibraryManager libraryManager, IUserManager userManager, IUserDataManager userDataManager, ILogger logger) { FileSystem = fileSystem; ConfigurationManager = configurationManager; @@ -36,7 +36,7 @@ namespace MediaBrowser.LocalMetadata.Savers protected ILibraryManager LibraryManager { get; private set; } protected IUserManager UserManager { get; private set; } protected IUserDataManager UserDataManager { get; private set; } - protected ILogger Logger { get; private set; } + protected ILogger Logger { get; private set; } public string Name => XmlProviderUtils.Name; diff --git a/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs b/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs index 3f177a9fa..a82f2108f 100644 --- a/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs +++ b/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs @@ -21,7 +21,7 @@ namespace MediaBrowser.MediaEncoding.Attachments { public class AttachmentExtractor : IAttachmentExtractor, IDisposable { - private readonly ILogger _logger; + private readonly ILogger _logger; private readonly IApplicationPaths _appPaths; private readonly IFileSystem _fileSystem; private readonly IMediaEncoder _mediaEncoder; diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs index 1377502dd..26bd202ba 100644 --- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs @@ -34,7 +34,7 @@ namespace MediaBrowser.MediaEncoding.Encoder /// internal const int DefaultImageExtractionTimeout = 5000; - private readonly ILogger _logger; + private readonly ILogger _logger; private readonly IServerConfigurationManager _configurationManager; private readonly IFileSystem _fileSystem; private readonly ILocalizationManager _localization; diff --git a/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs b/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs index ba171295e..f08af6045 100644 --- a/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs +++ b/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs @@ -25,7 +25,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles public class SubtitleEncoder : ISubtitleEncoder { private readonly ILibraryManager _libraryManager; - private readonly ILogger _logger; + private readonly ILogger _logger; private readonly IApplicationPaths _appPaths; private readonly IFileSystem _fileSystem; private readonly IMediaEncoder _mediaEncoder; diff --git a/MediaBrowser.Providers/Manager/ImageSaver.cs b/MediaBrowser.Providers/Manager/ImageSaver.cs index 3ab621ba4..f21ac2d65 100644 --- a/MediaBrowser.Providers/Manager/ImageSaver.cs +++ b/MediaBrowser.Providers/Manager/ImageSaver.cs @@ -15,6 +15,7 @@ using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Entities; using MediaBrowser.Model.IO; using MediaBrowser.Model.Net; +using MediaBrowser.Providers.Tmdb.Models.General; using Microsoft.Extensions.Logging; namespace MediaBrowser.Providers.Manager diff --git a/MediaBrowser.Providers/Manager/MetadataService.cs b/MediaBrowser.Providers/Manager/MetadataService.cs index c49aa407a..2997ba064 100644 --- a/MediaBrowser.Providers/Manager/MetadataService.cs +++ b/MediaBrowser.Providers/Manager/MetadataService.cs @@ -20,12 +20,12 @@ namespace MediaBrowser.Providers.Manager where TIdType : ItemLookupInfo, new() { protected readonly IServerConfigurationManager ServerConfigurationManager; - protected readonly ILogger Logger; + protected readonly ILogger> Logger; protected readonly IProviderManager ProviderManager; protected readonly IFileSystem FileSystem; protected readonly ILibraryManager LibraryManager; - protected MetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IFileSystem fileSystem, ILibraryManager libraryManager) + protected MetadataService(IServerConfigurationManager serverConfigurationManager, ILogger> logger, IProviderManager providerManager, IFileSystem fileSystem, ILibraryManager libraryManager) { ServerConfigurationManager = serverConfigurationManager; Logger = logger; diff --git a/MediaBrowser.Providers/Manager/ProviderManager.cs b/MediaBrowser.Providers/Manager/ProviderManager.cs index 400ec8d29..13a4436e1 100644 --- a/MediaBrowser.Providers/Manager/ProviderManager.cs +++ b/MediaBrowser.Providers/Manager/ProviderManager.cs @@ -36,7 +36,7 @@ namespace MediaBrowser.Providers.Manager /// public class ProviderManager : IProviderManager, IDisposable { - private readonly ILogger _logger; + private readonly ILogger _logger; private readonly IHttpClient _httpClient; private readonly ILibraryMonitor _libraryMonitor; private readonly IFileSystem _fileSystem; diff --git a/MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs b/MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs index 6982568eb..81103575d 100644 --- a/MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs +++ b/MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs @@ -37,7 +37,7 @@ namespace MediaBrowser.Providers.MediaInfo IPreRefreshProvider, IHasItemChangeMonitor { - private readonly ILogger _logger; + private readonly ILogger _logger; private readonly IIsoManager _isoManager; private readonly IMediaEncoder _mediaEncoder; private readonly IItemRepository _itemRepo; diff --git a/MediaBrowser.Providers/MediaInfo/SubtitleDownloader.cs b/MediaBrowser.Providers/MediaInfo/SubtitleDownloader.cs index 77c0e9b4e..f35e82bb5 100644 --- a/MediaBrowser.Providers/MediaInfo/SubtitleDownloader.cs +++ b/MediaBrowser.Providers/MediaInfo/SubtitleDownloader.cs @@ -25,7 +25,8 @@ namespace MediaBrowser.Providers.MediaInfo _subtitleManager = subtitleManager; } - public async Task> DownloadSubtitles(Video video, + public async Task> DownloadSubtitles( + Video video, List mediaStreams, bool skipIfEmbeddedSubtitlesPresent, bool skipIfAudioTrackMatches, diff --git a/MediaBrowser.Providers/MediaInfo/SubtitleScheduledTask.cs b/MediaBrowser.Providers/MediaInfo/SubtitleScheduledTask.cs index 2615f2dbb..fb87030ff 100644 --- a/MediaBrowser.Providers/MediaInfo/SubtitleScheduledTask.cs +++ b/MediaBrowser.Providers/MediaInfo/SubtitleScheduledTask.cs @@ -24,7 +24,7 @@ namespace MediaBrowser.Providers.MediaInfo private readonly IServerConfigurationManager _config; private readonly ISubtitleManager _subtitleManager; private readonly IMediaSourceManager _mediaSourceManager; - private readonly ILogger _logger; + private readonly ILogger _logger; private readonly IJsonSerializer _json; private readonly ILocalizationManager _localization; diff --git a/MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs b/MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs index f40570040..08e503a71 100644 --- a/MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs +++ b/MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs @@ -17,7 +17,7 @@ namespace MediaBrowser.Providers.MediaInfo public class VideoImageProvider : IDynamicImageProvider, IHasOrder { private readonly IMediaEncoder _mediaEncoder; - private readonly ILogger _logger; + private readonly ILogger _logger; private readonly IFileSystem _fileSystem; public VideoImageProvider(IMediaEncoder mediaEncoder, ILogger logger, IFileSystem fileSystem) diff --git a/MediaBrowser.Providers/Playlists/PlaylistItemsProvider.cs b/MediaBrowser.Providers/Playlists/PlaylistItemsProvider.cs index ae837c591..9fc86b213 100644 --- a/MediaBrowser.Providers/Playlists/PlaylistItemsProvider.cs +++ b/MediaBrowser.Providers/Playlists/PlaylistItemsProvider.cs @@ -20,7 +20,7 @@ namespace MediaBrowser.Providers.Playlists IPreRefreshProvider, IHasItemChangeMonitor { - private ILogger _logger; + private readonly ILogger _logger; private IFileSystem _fileSystem; public PlaylistItemsProvider(IFileSystem fileSystem, ILogger logger) diff --git a/MediaBrowser.Providers/Plugins/MusicBrainz/AlbumProvider.cs b/MediaBrowser.Providers/Plugins/MusicBrainz/AlbumProvider.cs index 31cdaf616..9d0d14969 100644 --- a/MediaBrowser.Providers/Plugins/MusicBrainz/AlbumProvider.cs +++ b/MediaBrowser.Providers/Plugins/MusicBrainz/AlbumProvider.cs @@ -42,7 +42,7 @@ namespace MediaBrowser.Providers.Music private readonly IHttpClient _httpClient; private readonly IApplicationHost _appHost; - private readonly ILogger _logger; + private readonly ILogger _logger; private readonly string _musicBrainzBaseUrl; diff --git a/MediaBrowser.Providers/Plugins/TheTvdb/TvdbEpisodeImageProvider.cs b/MediaBrowser.Providers/Plugins/TheTvdb/TvdbEpisodeImageProvider.cs index 6118a9c53..7a6fdc5f4 100644 --- a/MediaBrowser.Providers/Plugins/TheTvdb/TvdbEpisodeImageProvider.cs +++ b/MediaBrowser.Providers/Plugins/TheTvdb/TvdbEpisodeImageProvider.cs @@ -17,7 +17,7 @@ namespace MediaBrowser.Providers.Plugins.TheTvdb public class TvdbEpisodeImageProvider : IRemoteImageProvider { private readonly IHttpClient _httpClient; - private readonly ILogger _logger; + private readonly ILogger _logger; private readonly TvdbClientManager _tvdbClientManager; public TvdbEpisodeImageProvider(IHttpClient httpClient, ILogger logger, TvdbClientManager tvdbClientManager) diff --git a/MediaBrowser.Providers/Plugins/TheTvdb/TvdbEpisodeProvider.cs b/MediaBrowser.Providers/Plugins/TheTvdb/TvdbEpisodeProvider.cs index 08c2a74d2..1f8b82ecd 100644 --- a/MediaBrowser.Providers/Plugins/TheTvdb/TvdbEpisodeProvider.cs +++ b/MediaBrowser.Providers/Plugins/TheTvdb/TvdbEpisodeProvider.cs @@ -21,7 +21,7 @@ namespace MediaBrowser.Providers.Plugins.TheTvdb public class TvdbEpisodeProvider : IRemoteMetadataProvider, IHasOrder { private readonly IHttpClient _httpClient; - private readonly ILogger _logger; + private readonly ILogger _logger; private readonly TvdbClientManager _tvdbClientManager; public TvdbEpisodeProvider(IHttpClient httpClient, ILogger logger, TvdbClientManager tvdbClientManager) diff --git a/MediaBrowser.Providers/Plugins/TheTvdb/TvdbPersonImageProvider.cs b/MediaBrowser.Providers/Plugins/TheTvdb/TvdbPersonImageProvider.cs index c1cdc90e9..961cb5970 100644 --- a/MediaBrowser.Providers/Plugins/TheTvdb/TvdbPersonImageProvider.cs +++ b/MediaBrowser.Providers/Plugins/TheTvdb/TvdbPersonImageProvider.cs @@ -19,7 +19,7 @@ namespace MediaBrowser.Providers.Plugins.TheTvdb public class TvdbPersonImageProvider : IRemoteImageProvider, IHasOrder { private readonly IHttpClient _httpClient; - private readonly ILogger _logger; + private readonly ILogger _logger; private readonly ILibraryManager _libraryManager; private readonly TvdbClientManager _tvdbClientManager; diff --git a/MediaBrowser.Providers/Plugins/TheTvdb/TvdbSeasonImageProvider.cs b/MediaBrowser.Providers/Plugins/TheTvdb/TvdbSeasonImageProvider.cs index a5d183df7..cd22514b4 100644 --- a/MediaBrowser.Providers/Plugins/TheTvdb/TvdbSeasonImageProvider.cs +++ b/MediaBrowser.Providers/Plugins/TheTvdb/TvdbSeasonImageProvider.cs @@ -19,7 +19,7 @@ namespace MediaBrowser.Providers.Plugins.TheTvdb public class TvdbSeasonImageProvider : IRemoteImageProvider, IHasOrder { private readonly IHttpClient _httpClient; - private readonly ILogger _logger; + private readonly ILogger _logger; private readonly TvdbClientManager _tvdbClientManager; public TvdbSeasonImageProvider(IHttpClient httpClient, ILogger logger, TvdbClientManager tvdbClientManager) diff --git a/MediaBrowser.Providers/Plugins/TheTvdb/TvdbSeriesImageProvider.cs b/MediaBrowser.Providers/Plugins/TheTvdb/TvdbSeriesImageProvider.cs index 1bad60756..3fd98b828 100644 --- a/MediaBrowser.Providers/Plugins/TheTvdb/TvdbSeriesImageProvider.cs +++ b/MediaBrowser.Providers/Plugins/TheTvdb/TvdbSeriesImageProvider.cs @@ -19,7 +19,7 @@ namespace MediaBrowser.Providers.Plugins.TheTvdb public class TvdbSeriesImageProvider : IRemoteImageProvider, IHasOrder { private readonly IHttpClient _httpClient; - private readonly ILogger _logger; + private readonly ILogger _logger; private readonly TvdbClientManager _tvdbClientManager; public TvdbSeriesImageProvider(IHttpClient httpClient, ILogger logger, TvdbClientManager tvdbClientManager) diff --git a/MediaBrowser.Providers/Plugins/TheTvdb/TvdbSeriesProvider.cs b/MediaBrowser.Providers/Plugins/TheTvdb/TvdbSeriesProvider.cs index f6cd249f5..35152610a 100644 --- a/MediaBrowser.Providers/Plugins/TheTvdb/TvdbSeriesProvider.cs +++ b/MediaBrowser.Providers/Plugins/TheTvdb/TvdbSeriesProvider.cs @@ -23,7 +23,7 @@ namespace MediaBrowser.Providers.Plugins.TheTvdb { internal static TvdbSeriesProvider Current { get; private set; } private readonly IHttpClient _httpClient; - private readonly ILogger _logger; + private readonly ILogger _logger; private readonly ILibraryManager _libraryManager; private readonly ILocalizationManager _localizationManager; private readonly TvdbClientManager _tvdbClientManager; diff --git a/MediaBrowser.Providers/Subtitles/SubtitleManager.cs b/MediaBrowser.Providers/Subtitles/SubtitleManager.cs index 127d29c04..c6ffc460c 100644 --- a/MediaBrowser.Providers/Subtitles/SubtitleManager.cs +++ b/MediaBrowser.Providers/Subtitles/SubtitleManager.cs @@ -25,7 +25,7 @@ namespace MediaBrowser.Providers.Subtitles { public class SubtitleManager : ISubtitleManager { - private readonly ILogger _logger; + private readonly ILogger _logger; private readonly IFileSystem _fileSystem; private readonly ILibraryMonitor _monitor; private readonly IMediaSourceManager _mediaSourceManager; diff --git a/MediaBrowser.Providers/TV/SeriesMetadataService.cs b/MediaBrowser.Providers/TV/SeriesMetadataService.cs index 5e75a8125..a91d5d707 100644 --- a/MediaBrowser.Providers/TV/SeriesMetadataService.cs +++ b/MediaBrowser.Providers/TV/SeriesMetadataService.cs @@ -42,7 +42,8 @@ namespace MediaBrowser.Providers.TV await seasonProvider.Run(item, cancellationToken).ConfigureAwait(false); // TODO why does it not register this itself omg - var provider = new MissingEpisodeProvider(Logger, + var provider = new MissingEpisodeProvider( + Logger, ServerConfigurationManager, LibraryManager, _localization, diff --git a/MediaBrowser.Providers/Tmdb/BoxSets/TmdbBoxSetProvider.cs b/MediaBrowser.Providers/Tmdb/BoxSets/TmdbBoxSetProvider.cs index dd3783ffb..e17f5efdf 100644 --- a/MediaBrowser.Providers/Tmdb/BoxSets/TmdbBoxSetProvider.cs +++ b/MediaBrowser.Providers/Tmdb/BoxSets/TmdbBoxSetProvider.cs @@ -29,7 +29,7 @@ namespace MediaBrowser.Providers.Tmdb.BoxSets internal static TmdbBoxSetProvider Current; - private readonly ILogger _logger; + private readonly ILogger _logger; private readonly IJsonSerializer _json; private readonly IServerConfigurationManager _config; private readonly IFileSystem _fileSystem; diff --git a/MediaBrowser.Providers/Tmdb/Movies/TmdbMovieProvider.cs b/MediaBrowser.Providers/Tmdb/Movies/TmdbMovieProvider.cs index e2fd5b9e3..3787d4003 100644 --- a/MediaBrowser.Providers/Tmdb/Movies/TmdbMovieProvider.cs +++ b/MediaBrowser.Providers/Tmdb/Movies/TmdbMovieProvider.cs @@ -36,7 +36,7 @@ namespace MediaBrowser.Providers.Tmdb.Movies private readonly IHttpClient _httpClient; private readonly IFileSystem _fileSystem; private readonly IServerConfigurationManager _configurationManager; - private readonly ILogger _logger; + private readonly ILogger _logger; private readonly ILibraryManager _libraryManager; private readonly IApplicationHost _appHost; diff --git a/MediaBrowser.Providers/Tmdb/People/TmdbPersonProvider.cs b/MediaBrowser.Providers/Tmdb/People/TmdbPersonProvider.cs index 588001169..1f09badbc 100644 --- a/MediaBrowser.Providers/Tmdb/People/TmdbPersonProvider.cs +++ b/MediaBrowser.Providers/Tmdb/People/TmdbPersonProvider.cs @@ -35,7 +35,7 @@ namespace MediaBrowser.Providers.Tmdb.People private readonly IFileSystem _fileSystem; private readonly IServerConfigurationManager _configurationManager; private readonly IHttpClient _httpClient; - private readonly ILogger _logger; + private readonly ILogger _logger; public TmdbPersonProvider( IFileSystem fileSystem, diff --git a/MediaBrowser.Providers/Tmdb/TV/TmdbEpisodeImageProvider.cs b/MediaBrowser.Providers/Tmdb/TV/TmdbEpisodeImageProvider.cs index 558c8149e..b0174a587 100644 --- a/MediaBrowser.Providers/Tmdb/TV/TmdbEpisodeImageProvider.cs +++ b/MediaBrowser.Providers/Tmdb/TV/TmdbEpisodeImageProvider.cs @@ -24,8 +24,8 @@ namespace MediaBrowser.Providers.Tmdb.TV IRemoteImageProvider, IHasOrder { - public TmdbEpisodeImageProvider(IHttpClient httpClient, IServerConfigurationManager configurationManager, IJsonSerializer jsonSerializer, IFileSystem fileSystem, ILocalizationManager localization, ILoggerFactory loggerFactory) - : base(httpClient, configurationManager, jsonSerializer, fileSystem, localization, loggerFactory) + public TmdbEpisodeImageProvider(IHttpClient httpClient, IServerConfigurationManager configurationManager, IJsonSerializer jsonSerializer, IFileSystem fileSystem, ILocalizationManager localization, ILogger logger) + : base(httpClient, configurationManager, jsonSerializer, fileSystem, localization, logger) { } public IEnumerable GetSupportedImages(BaseItem item) diff --git a/MediaBrowser.Providers/Tmdb/TV/TmdbEpisodeProvider.cs b/MediaBrowser.Providers/Tmdb/TV/TmdbEpisodeProvider.cs index a17f5d17a..330b9f42b 100644 --- a/MediaBrowser.Providers/Tmdb/TV/TmdbEpisodeProvider.cs +++ b/MediaBrowser.Providers/Tmdb/TV/TmdbEpisodeProvider.cs @@ -25,8 +25,8 @@ namespace MediaBrowser.Providers.Tmdb.TV IRemoteMetadataProvider, IHasOrder { - public TmdbEpisodeProvider(IHttpClient httpClient, IServerConfigurationManager configurationManager, IJsonSerializer jsonSerializer, IFileSystem fileSystem, ILocalizationManager localization, ILoggerFactory loggerFactory) - : base(httpClient, configurationManager, jsonSerializer, fileSystem, localization, loggerFactory) + public TmdbEpisodeProvider(IHttpClient httpClient, IServerConfigurationManager configurationManager, IJsonSerializer jsonSerializer, IFileSystem fileSystem, ILocalizationManager localization, ILogger logger) + : base(httpClient, configurationManager, jsonSerializer, fileSystem, localization, logger) { } public async Task> GetSearchResults(EpisodeInfo searchInfo, CancellationToken cancellationToken) diff --git a/MediaBrowser.Providers/Tmdb/TV/TmdbEpisodeProviderBase.cs b/MediaBrowser.Providers/Tmdb/TV/TmdbEpisodeProviderBase.cs index e87fe9332..3937ebc95 100644 --- a/MediaBrowser.Providers/Tmdb/TV/TmdbEpisodeProviderBase.cs +++ b/MediaBrowser.Providers/Tmdb/TV/TmdbEpisodeProviderBase.cs @@ -24,14 +24,14 @@ namespace MediaBrowser.Providers.Tmdb.TV private readonly ILocalizationManager _localization; private readonly ILogger _logger; - protected TmdbEpisodeProviderBase(IHttpClient httpClient, IServerConfigurationManager configurationManager, IJsonSerializer jsonSerializer, IFileSystem fileSystem, ILocalizationManager localization, ILoggerFactory loggerFactory) + protected TmdbEpisodeProviderBase(IHttpClient httpClient, IServerConfigurationManager configurationManager, IJsonSerializer jsonSerializer, IFileSystem fileSystem, ILocalizationManager localization, ILogger logger) { _httpClient = httpClient; _configurationManager = configurationManager; _jsonSerializer = jsonSerializer; _fileSystem = fileSystem; _localization = localization; - _logger = loggerFactory.CreateLogger(GetType().Name); + _logger = logger; } protected ILogger Logger => _logger; diff --git a/MediaBrowser.Providers/Tmdb/TV/TmdbSeasonProvider.cs b/MediaBrowser.Providers/Tmdb/TV/TmdbSeasonProvider.cs index 5ad331971..832b64941 100644 --- a/MediaBrowser.Providers/Tmdb/TV/TmdbSeasonProvider.cs +++ b/MediaBrowser.Providers/Tmdb/TV/TmdbSeasonProvider.cs @@ -29,18 +29,18 @@ namespace MediaBrowser.Providers.Tmdb.TV private readonly IJsonSerializer _jsonSerializer; private readonly IFileSystem _fileSystem; private readonly ILocalizationManager _localization; - private readonly ILogger _logger; + private readonly ILogger _logger; internal static TmdbSeasonProvider Current { get; private set; } - public TmdbSeasonProvider(IHttpClient httpClient, IServerConfigurationManager configurationManager, IFileSystem fileSystem, ILocalizationManager localization, IJsonSerializer jsonSerializer, ILoggerFactory loggerFactory) + public TmdbSeasonProvider(IHttpClient httpClient, IServerConfigurationManager configurationManager, IFileSystem fileSystem, ILocalizationManager localization, IJsonSerializer jsonSerializer, ILogger logger) { _httpClient = httpClient; _configurationManager = configurationManager; _fileSystem = fileSystem; _localization = localization; _jsonSerializer = jsonSerializer; - _logger = loggerFactory.CreateLogger(GetType().Name); + _logger = logger; Current = this; } diff --git a/MediaBrowser.Providers/Tmdb/TV/TmdbSeriesProvider.cs b/MediaBrowser.Providers/Tmdb/TV/TmdbSeriesProvider.cs index 6e3c26c26..649c5474b 100644 --- a/MediaBrowser.Providers/Tmdb/TV/TmdbSeriesProvider.cs +++ b/MediaBrowser.Providers/Tmdb/TV/TmdbSeriesProvider.cs @@ -31,7 +31,7 @@ namespace MediaBrowser.Providers.Tmdb.TV private readonly IJsonSerializer _jsonSerializer; private readonly IFileSystem _fileSystem; private readonly IServerConfigurationManager _configurationManager; - private readonly ILogger _logger; + private readonly ILogger _logger; private readonly ILocalizationManager _localization; private readonly IHttpClient _httpClient; private readonly ILibraryManager _libraryManager; diff --git a/MediaBrowser.WebDashboard/Api/DashboardService.cs b/MediaBrowser.WebDashboard/Api/DashboardService.cs index 55fc463d0..63cbfd9e4 100644 --- a/MediaBrowser.WebDashboard/Api/DashboardService.cs +++ b/MediaBrowser.WebDashboard/Api/DashboardService.cs @@ -90,7 +90,7 @@ namespace MediaBrowser.WebDashboard.Api /// Gets or sets the logger. /// /// The logger. - private readonly ILogger _logger; + private readonly ILogger _logger; /// /// Gets or sets the HTTP result factory. diff --git a/MediaBrowser.XbmcMetadata/EntryPoint.cs b/MediaBrowser.XbmcMetadata/EntryPoint.cs index 571953b47..11b36285c 100644 --- a/MediaBrowser.XbmcMetadata/EntryPoint.cs +++ b/MediaBrowser.XbmcMetadata/EntryPoint.cs @@ -17,7 +17,7 @@ namespace MediaBrowser.XbmcMetadata public sealed class EntryPoint : IServerEntryPoint { private readonly IUserDataManager _userDataManager; - private readonly ILogger _logger; + private readonly ILogger _logger; private readonly IProviderManager _providerManager; private readonly IConfigurationManager _config; diff --git a/MediaBrowser.XbmcMetadata/Providers/AlbumNfoProvider.cs b/MediaBrowser.XbmcMetadata/Providers/AlbumNfoProvider.cs index 4b1ee4c9c..433a936d9 100644 --- a/MediaBrowser.XbmcMetadata/Providers/AlbumNfoProvider.cs +++ b/MediaBrowser.XbmcMetadata/Providers/AlbumNfoProvider.cs @@ -14,7 +14,7 @@ namespace MediaBrowser.XbmcMetadata.Providers /// public class AlbumNfoProvider : BaseNfoProvider { - private readonly ILogger _logger; + private readonly ILogger _logger; private readonly IConfigurationManager _config; private readonly IProviderManager _providerManager; diff --git a/MediaBrowser.XbmcMetadata/Providers/ArtistNfoProvider.cs b/MediaBrowser.XbmcMetadata/Providers/ArtistNfoProvider.cs index 3bbfa6e83..d69cdc90a 100644 --- a/MediaBrowser.XbmcMetadata/Providers/ArtistNfoProvider.cs +++ b/MediaBrowser.XbmcMetadata/Providers/ArtistNfoProvider.cs @@ -14,7 +14,7 @@ namespace MediaBrowser.XbmcMetadata.Providers /// public class ArtistNfoProvider : BaseNfoProvider { - private readonly ILogger _logger; + private readonly ILogger _logger; private readonly IConfigurationManager _config; private readonly IProviderManager _providerManager; diff --git a/MediaBrowser.XbmcMetadata/Providers/BaseVideoNfoProvider.cs b/MediaBrowser.XbmcMetadata/Providers/BaseVideoNfoProvider.cs index 84c99664a..2b1589d47 100644 --- a/MediaBrowser.XbmcMetadata/Providers/BaseVideoNfoProvider.cs +++ b/MediaBrowser.XbmcMetadata/Providers/BaseVideoNfoProvider.cs @@ -15,12 +15,12 @@ namespace MediaBrowser.XbmcMetadata.Providers public abstract class BaseVideoNfoProvider : BaseNfoProvider where T : Video, new() { - private readonly ILogger _logger; + private readonly ILogger> _logger; private readonly IConfigurationManager _config; private readonly IProviderManager _providerManager; public BaseVideoNfoProvider( - ILogger logger, + ILogger> logger, IFileSystem fileSystem, IConfigurationManager config, IProviderManager providerManager) diff --git a/MediaBrowser.XbmcMetadata/Providers/EpisodeNfoProvider.cs b/MediaBrowser.XbmcMetadata/Providers/EpisodeNfoProvider.cs index b2dc2e38e..26983b1a6 100644 --- a/MediaBrowser.XbmcMetadata/Providers/EpisodeNfoProvider.cs +++ b/MediaBrowser.XbmcMetadata/Providers/EpisodeNfoProvider.cs @@ -14,7 +14,7 @@ namespace MediaBrowser.XbmcMetadata.Providers /// public class EpisodeNfoProvider : BaseNfoProvider { - private readonly ILogger _logger; + private readonly ILogger _logger; private readonly IConfigurationManager _config; private readonly IProviderManager _providerManager; diff --git a/MediaBrowser.XbmcMetadata/Providers/SeasonNfoProvider.cs b/MediaBrowser.XbmcMetadata/Providers/SeasonNfoProvider.cs index 63ddd6025..0603fd0d1 100644 --- a/MediaBrowser.XbmcMetadata/Providers/SeasonNfoProvider.cs +++ b/MediaBrowser.XbmcMetadata/Providers/SeasonNfoProvider.cs @@ -14,7 +14,7 @@ namespace MediaBrowser.XbmcMetadata.Providers /// public class SeasonNfoProvider : BaseNfoProvider { - private readonly ILogger _logger; + private readonly ILogger _logger; private readonly IConfigurationManager _config; private readonly IProviderManager _providerManager; diff --git a/MediaBrowser.XbmcMetadata/Providers/SeriesNfoProvider.cs b/MediaBrowser.XbmcMetadata/Providers/SeriesNfoProvider.cs index d65914542..7e059e0aa 100644 --- a/MediaBrowser.XbmcMetadata/Providers/SeriesNfoProvider.cs +++ b/MediaBrowser.XbmcMetadata/Providers/SeriesNfoProvider.cs @@ -14,7 +14,7 @@ namespace MediaBrowser.XbmcMetadata.Providers /// public class SeriesNfoProvider : BaseNfoProvider { - private readonly ILogger _logger; + private readonly ILogger _logger; private readonly IConfigurationManager _config; private readonly IProviderManager _providerManager; diff --git a/MediaBrowser.XbmcMetadata/Savers/BaseNfoSaver.cs b/MediaBrowser.XbmcMetadata/Savers/BaseNfoSaver.cs index 90e8b4b99..3602ec14c 100644 --- a/MediaBrowser.XbmcMetadata/Savers/BaseNfoSaver.cs +++ b/MediaBrowser.XbmcMetadata/Savers/BaseNfoSaver.cs @@ -105,7 +105,7 @@ namespace MediaBrowser.XbmcMetadata.Savers ILibraryManager libraryManager, IUserManager userManager, IUserDataManager userDataManager, - ILogger logger) + ILogger logger) { Logger = logger; UserDataManager = userDataManager; @@ -125,7 +125,7 @@ namespace MediaBrowser.XbmcMetadata.Savers protected IUserDataManager UserDataManager { get; } - protected ILogger Logger { get; } + protected ILogger Logger { get; } protected ItemUpdateType MinimumUpdateType { @@ -974,7 +974,7 @@ namespace MediaBrowser.XbmcMetadata.Savers => string.Equals(person.Type, type, StringComparison.OrdinalIgnoreCase) || string.Equals(person.Role, type, StringComparison.OrdinalIgnoreCase); - private void AddCustomTags(string path, List xmlTagsUsed, XmlWriter writer, ILogger logger) + private void AddCustomTags(string path, List xmlTagsUsed, XmlWriter writer, ILogger logger) { var settings = new XmlReaderSettings() { -- cgit v1.2.3