using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Session; using MediaBrowser.Model.Events; using MediaBrowser.Model.Logging; using MediaBrowser.Model.Serialization; using MediaBrowser.Model.Session; using System; using System.Collections.Specialized; using System.Globalization; using System.Linq; using System.Threading.Tasks; namespace MediaBrowser.Server.Implementations.Session { /// /// Class SessionWebSocketListener /// public class SessionWebSocketListener : IWebSocketListener, IDisposable { /// /// The _true task result /// private readonly Task _trueTaskResult = Task.FromResult(true); /// /// The _session manager /// private readonly ISessionManager _sessionManager; /// /// The _logger /// private readonly ILogger _logger; /// /// The _dto service /// private readonly IJsonSerializer _json; private readonly IHttpServer _httpServer; private readonly IServerManager _serverManager; /// /// Initializes a new instance of the class. /// /// The session manager. /// The log manager. /// The json. /// The HTTP server. /// The server manager. public SessionWebSocketListener(ISessionManager sessionManager, ILogManager logManager, IJsonSerializer json, IHttpServer httpServer, IServerManager serverManager) { _sessionManager = sessionManager; _logger = logManager.GetLogger(GetType().Name); _json = json; _httpServer = httpServer; _serverManager = serverManager; httpServer.WebSocketConnecting += _httpServer_WebSocketConnecting; serverManager.WebSocketConnected += _serverManager_WebSocketConnected; } async void _serverManager_WebSocketConnected(object sender, GenericEventArgs e) { var session = await GetSession(e.Argument.QueryString, e.Argument.RemoteEndPoint).ConfigureAwait(false); if (session != null) { var controller = session.SessionController as WebSocketController; if (controller == null) { controller = new WebSocketController(session, _logger, _sessionManager); } controller.AddWebSocket(e.Argument); session.SessionController = controller; } else { _logger.Warn("Unable to determine session based on url: {0}", e.Argument.Url); } } async void _httpServer_WebSocketConnecting(object sender, WebSocketConnectingEventArgs e) { //var token = e.QueryString["api_key"]; //if (!string.IsNullOrWhiteSpace(token)) //{ // try // { // var session = await GetSession(e.QueryString, e.Endpoint).ConfigureAwait(false); // if (session == null) // { // e.AllowConnection = false; // } // } // catch (Exception ex) // { // _logger.ErrorException("Error getting session info", ex); // } //} } private Task GetSession(NameValueCollection queryString, string remoteEndpoint) { if (queryString == null) { throw new ArgumentNullException("queryString"); } var token = queryString["api_key"]; if (string.IsNullOrWhiteSpace(token)) { return Task.FromResult(null); } var deviceId = queryString["deviceId"]; return _sessionManager.GetSessionByAuthenticationToken(token, deviceId, remoteEndpoint); } public void Dispose() { _httpServer.WebSocketConnecting -= _httpServer_WebSocketConnecting; _serverManager.WebSocketConnected -= _serverManager_WebSocketConnected; } /// /// Processes the message. /// /// The message. /// Task. public Task ProcessMessage(WebSocketMessageInfo message) { if (string.Equals(message.MessageType, "Identity", StringComparison.OrdinalIgnoreCase)) { ProcessIdentityMessage(message); } else if (string.Equals(message.MessageType, "Context", StringComparison.OrdinalIgnoreCase)) { ProcessContextMessage(message); } else if (string.Equals(message.MessageType, "PlaybackStart", StringComparison.OrdinalIgnoreCase)) { OnPlaybackStart(message); } else if (string.Equals(message.MessageType, "PlaybackProgress", StringComparison.OrdinalIgnoreCase)) { OnPlaybackProgress(message); } else if (string.Equals(message.MessageType, "PlaybackStopped", StringComparison.OrdinalIgnoreCase)) { OnPlaybackStopped(message); } else if (string.Equals(message.MessageType, "ReportPlaybackStart", StringComparison.OrdinalIgnoreCase)) { ReportPlaybackStart(message); } else if (string.Equals(message.MessageType, "ReportPlaybackProgress", StringComparison.OrdinalIgnoreCase)) { ReportPlaybackProgress(message); } else if (string.Equals(message.MessageType, "ReportPlaybackStopped", StringComparison.OrdinalIgnoreCase)) { ReportPlaybackStopped(message); } return _trueTaskResult; } /// /// Processes the identity message. /// /// The message. private async void ProcessIdentityMessage(WebSocketMessageInfo message) { _logger.Debug("Received Identity message: " + message.Data); var vals = message.Data.Split('|'); if (vals.Length < 3) { _logger.Error("Client sent invalid identity message."); return; } var client = vals[0]; var deviceId = vals[1]; var version = vals[2]; var deviceName = vals.Length > 3 ? vals[3] : string.Empty; var session = _sessionManager.GetSession(deviceId, client, version); if (session == null && !string.IsNullOrEmpty(deviceName)) { _logger.Debug("Logging session activity"); session = await _sessionManager.LogSessionActivity(client, version, deviceId, deviceName, message.Connection.RemoteEndPoint, null).ConfigureAwait(false); } if (session != null) { var controller = session.SessionController as WebSocketController; if (controller == null) { controller = new WebSocketController(session, _logger, _sessionManager); } controller.AddWebSocket(message.Connection); session.SessionController = controller; } else { _logger.Warn("Unable to determine session based on identity message: {0}", message.Data); } } /// /// Processes the context message. /// /// The message. private void ProcessContextMessage(WebSocketMessageInfo message) { var session = GetSessionFromMessage(message); if (session != null) { var vals = message.Data.Split('|'); _sessionManager.ReportNowViewingItem(session.Id, vals[1]); } } /// /// Gets the session from message. /// /// The message. /// SessionInfo. private SessionInfo GetSessionFromMessage(WebSocketMessageInfo message) { var result = _sessionManager.Sessions.FirstOrDefault(i => { var controller = i.SessionController as WebSocketController; if (controller != null) { if (controller.Sockets.Any(s => s.Id == message.Connection.Id)) { return true; } } return false; }); if (result == null) { _logger.Error("Unable to find session based on web socket message"); } return result; } private readonly CultureInfo _usCulture = new CultureInfo("en-US"); /// /// Reports the playback start. /// /// The message. private void OnPlaybackStart(WebSocketMessageInfo message) { _logger.Debug("Received PlaybackStart message"); var session = GetSessionFromMessage(message); if (session != null && session.UserId.HasValue) { var vals = message.Data.Split('|'); var itemId = vals[0]; var queueableMediaTypes = string.Empty; var canSeek = true; if (vals.Length > 1) { canSeek = string.Equals(vals[1], "true", StringComparison.OrdinalIgnoreCase); } if (vals.Length > 2) { queueableMediaTypes = vals[2]; } var info = new PlaybackStartInfo { CanSeek = canSeek, ItemId = itemId, SessionId = session.Id, QueueableMediaTypes = queueableMediaTypes.Split(',').ToList() }; if (vals.Length > 3) { info.MediaSourceId = vals[3]; } if (vals.Length > 4 && !string.IsNullOrWhiteSpace(vals[4])) { info.AudioStreamIndex = int.Parse(vals[4], _usCulture); } if (vals.Length > 5 && !string.IsNullOrWhiteSpace(vals[5])) { info.SubtitleStreamIndex = int.Parse(vals[5], _usCulture); } _sessionManager.OnPlaybackStart(info); } } private void ReportPlaybackStart(WebSocketMessageInfo message) { _logger.Debug("Received ReportPlaybackStart message"); var session = GetSessionFromMessage(message); if (session != null && session.UserId.HasValue) { var info = _json.DeserializeFromString(message.Data); info.SessionId = session.Id; _sessionManager.OnPlaybackStart(info); } } private void ReportPlaybackProgress(WebSocketMessageInfo message) { //_logger.Debug("Received ReportPlaybackProgress message"); var session = GetSessionFromMessage(message); if (session != null && session.UserId.HasValue) { var info = _json.DeserializeFromString(message.Data); info.SessionId = session.Id; _sessionManager.OnPlaybackProgress(info); } } /// /// Reports the playback progress. /// /// The message. private void OnPlaybackProgress(WebSocketMessageInfo message) { var session = GetSessionFromMessage(message); if (session != null && session.UserId.HasValue) { var vals = message.Data.Split('|'); var itemId = vals[0]; long? positionTicks = null; if (vals.Length > 1) { long pos; if (long.TryParse(vals[1], out pos)) { positionTicks = pos; } } var isPaused = vals.Length > 2 && string.Equals(vals[2], "true", StringComparison.OrdinalIgnoreCase); var isMuted = vals.Length > 3 && string.Equals(vals[3], "true", StringComparison.OrdinalIgnoreCase); var info = new PlaybackProgressInfo { ItemId = itemId, PositionTicks = positionTicks, IsMuted = isMuted, IsPaused = isPaused, SessionId = session.Id }; if (vals.Length > 4) { info.MediaSourceId = vals[4]; } if (vals.Length > 5 && !string.IsNullOrWhiteSpace(vals[5])) { info.VolumeLevel = int.Parse(vals[5], _usCulture); } if (vals.Length > 5 && !string.IsNullOrWhiteSpace(vals[6])) { info.AudioStreamIndex = int.Parse(vals[6], _usCulture); } if (vals.Length > 7 && !string.IsNullOrWhiteSpace(vals[7])) { info.SubtitleStreamIndex = int.Parse(vals[7], _usCulture); } _sessionManager.OnPlaybackProgress(info); } } private void ReportPlaybackStopped(WebSocketMessageInfo message) { _logger.Debug("Received ReportPlaybackStopped message"); var session = GetSessionFromMessage(message); if (session != null && session.UserId.HasValue) { var info = _json.DeserializeFromString(message.Data); info.SessionId = session.Id; _sessionManager.OnPlaybackStopped(info); } } /// /// Reports the playback stopped. /// /// The message. private void OnPlaybackStopped(WebSocketMessageInfo message) { _logger.Debug("Received PlaybackStopped message"); var session = GetSessionFromMessage(message); if (session != null && session.UserId.HasValue) { var vals = message.Data.Split('|'); var itemId = vals[0]; long? positionTicks = null; if (vals.Length > 1) { long pos; if (long.TryParse(vals[1], out pos)) { positionTicks = pos; } } var info = new PlaybackStopInfo { ItemId = itemId, PositionTicks = positionTicks, SessionId = session.Id }; if (vals.Length > 2) { info.MediaSourceId = vals[2]; } _sessionManager.OnPlaybackStopped(info); } } } }