diff --git a/MediaBrowser.Api/SessionsService.cs b/MediaBrowser.Api/SessionsService.cs index 4554b35fa..0307bc498 100644 --- a/MediaBrowser.Api/SessionsService.cs +++ b/MediaBrowser.Api/SessionsService.cs @@ -130,6 +130,46 @@ namespace MediaBrowser.Api public PlaystateCommand Command { get; set; } } + [Route("/Sessions/{Id}/System/{Command}", "POST")] + [Api(("Issues a system command to a client"))] + public class SendSystemCommand : IReturnVoid + { + /// + /// Gets or sets the id. + /// + /// The id. + [ApiMember(Name = "Id", Description = "Session Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] + public Guid Id { get; set; } + + /// + /// Gets or sets the command. + /// + /// The play command. + [ApiMember(Name = "Command", Description = "The command to send.", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] + public SystemCommand Command { get; set; } + } + + [Route("/Sessions/{Id}/Message", "POST")] + [Api(("Issues a command to a client to display a message to the user"))] + public class SendMessageCommand : IReturnVoid + { + /// + /// Gets or sets the id. + /// + /// The id. + [ApiMember(Name = "Id", Description = "Session Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] + public Guid Id { get; set; } + + [ApiMember(Name = "Text", Description = "The message text.", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")] + public string Text { get; set; } + + [ApiMember(Name = "Header", Description = "The message header.", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")] + public string Header { get; set; } + + [ApiMember(Name = "TimeoutMs", Description = "The message timeout. If omitted the user will have to confirm viewing the message.", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")] + public long? TimeoutMs { get; set; } + } + /// /// Class SessionsService /// @@ -273,6 +313,110 @@ namespace MediaBrowser.Api } } + /// + /// Posts the specified request. + /// + /// The request. + public void Post(SendSystemCommand request) + { + var task = SendSystemCommand(request); + + Task.WaitAll(task); + } + + private async Task SendSystemCommand(SendSystemCommand request) + { + var session = _sessionManager.Sessions.FirstOrDefault(i => i.Id == request.Id); + + if (session == null) + { + throw new ResourceNotFoundException(string.Format("Session {0} not found.", request.Id)); + } + + if (!session.SupportsRemoteControl) + { + throw new ArgumentException(string.Format("Session {0} does not support remote control.", session.Id)); + } + + var socket = session.WebSockets.OrderByDescending(i => i.LastActivityDate).FirstOrDefault(i => i.State == WebSocketState.Open); + + if (socket != null) + { + try + { + await socket.SendAsync(new WebSocketMessage + { + MessageType = "SystemCommand", + Data = request.Command.ToString() + + }, CancellationToken.None).ConfigureAwait(false); + } + catch (Exception ex) + { + Logger.ErrorException("Error sending web socket message", ex); + } + } + else + { + throw new InvalidOperationException("The requested session does not have an open web socket."); + } + } + + /// + /// Posts the specified request. + /// + /// The request. + public void Post(SendMessageCommand request) + { + var task = SendMessageCommand(request); + + Task.WaitAll(task); + } + + private async Task SendMessageCommand(SendMessageCommand request) + { + var session = _sessionManager.Sessions.FirstOrDefault(i => i.Id == request.Id); + + if (session == null) + { + throw new ResourceNotFoundException(string.Format("Session {0} not found.", request.Id)); + } + + if (!session.SupportsRemoteControl) + { + throw new ArgumentException(string.Format("Session {0} does not support remote control.", session.Id)); + } + + var socket = session.WebSockets.OrderByDescending(i => i.LastActivityDate).FirstOrDefault(i => i.State == WebSocketState.Open); + + if (socket != null) + { + try + { + await socket.SendAsync(new WebSocketMessage + { + MessageType = "MessageCommand", + + Data = new MessageCommand + { + Header = request.Header, + TimeoutMs = request.TimeoutMs, + Text = request.Text + } + + }, CancellationToken.None).ConfigureAwait(false); + } + catch (Exception ex) + { + Logger.ErrorException("Error sending web socket message", ex); + } + } + else + { + throw new InvalidOperationException("The requested session does not have an open web socket."); + } + } + /// /// Posts the specified request. /// diff --git a/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj b/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj index 7606f7a94..5a8bd2097 100644 --- a/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj +++ b/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj @@ -314,6 +314,9 @@ Session\BrowseRequest.cs + + Session\MessageCommand.cs + Session\PlayRequest.cs @@ -323,6 +326,9 @@ Session\SessionInfoDto.cs + + Session\SystemCommand.cs + System\SystemInfo.cs diff --git a/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj b/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj index 4c27d74b3..855d65fd5 100644 --- a/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj +++ b/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj @@ -295,6 +295,9 @@ Session\BrowseRequest.cs + + Session\MessageCommand.cs + Session\PlayRequest.cs @@ -304,6 +307,9 @@ Session\SessionInfoDto.cs + + Session\SystemCommand.cs + System\SystemInfo.cs diff --git a/MediaBrowser.Model/MediaBrowser.Model.csproj b/MediaBrowser.Model/MediaBrowser.Model.csproj index 8f1194f7f..280df9fe9 100644 --- a/MediaBrowser.Model/MediaBrowser.Model.csproj +++ b/MediaBrowser.Model/MediaBrowser.Model.csproj @@ -76,6 +76,7 @@ + @@ -113,6 +114,7 @@ + diff --git a/MediaBrowser.Model/Session/MessageCommand.cs b/MediaBrowser.Model/Session/MessageCommand.cs new file mode 100644 index 000000000..5ab580815 --- /dev/null +++ b/MediaBrowser.Model/Session/MessageCommand.cs @@ -0,0 +1,12 @@ + +namespace MediaBrowser.Model.Session +{ + public class MessageCommand + { + public string Header { get; set; } + + public string Text { get; set; } + + public long? TimeoutMs { get; set; } + } +} diff --git a/MediaBrowser.Model/Session/SystemCommand.cs b/MediaBrowser.Model/Session/SystemCommand.cs new file mode 100644 index 000000000..2fcaef6e3 --- /dev/null +++ b/MediaBrowser.Model/Session/SystemCommand.cs @@ -0,0 +1,14 @@ + +namespace MediaBrowser.Model.Session +{ + public enum SystemCommand + { + GoHome, + GoToSettings, + VolumeUp, + VolumeDown, + Mute, + Unmute, + ToggleMute + } +} diff --git a/MediaBrowser.WebDashboard/ApiClient.js b/MediaBrowser.WebDashboard/ApiClient.js index 8a7db8b76..6262d760e 100644 --- a/MediaBrowser.WebDashboard/ApiClient.js +++ b/MediaBrowser.WebDashboard/ApiClient.js @@ -3465,6 +3465,42 @@ MediaBrowser.ApiClient = function ($, navigator, JSON, WebSocket, setTimeout) { }); }; + self.sendSystemCommand = function (sessionId, command) { + + if (!sessionId) { + throw new Error("null sessionId"); + } + + if (!command) { + throw new Error("null command"); + } + + var url = self.getUrl("Sessions/" + sessionId + "/System/" + command); + + return self.ajax({ + type: "POST", + url: url + }); + }; + + self.sendMessageCommand = function (sessionId, options) { + + if (!sessionId) { + throw new Error("null sessionId"); + } + + if (!options) { + throw new Error("null options"); + } + + var url = self.getUrl("Sessions/" + sessionId + "/Message", options); + + return self.ajax({ + type: "POST", + url: url + }); + }; + self.sendPlayStateCommand = function (sessionId, command, options) { if (!sessionId) { diff --git a/MediaBrowser.WebDashboard/packages.config b/MediaBrowser.WebDashboard/packages.config index 11c76e205..9f7b690af 100644 --- a/MediaBrowser.WebDashboard/packages.config +++ b/MediaBrowser.WebDashboard/packages.config @@ -1,6 +1,6 @@  - + \ No newline at end of file