using System.Globalization; using MediaBrowser.Common.Net; using MediaBrowser.Controller; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Session; using MediaBrowser.Model.Logging; using System; using System.Linq; using System.Threading.Tasks; namespace MediaBrowser.Server.Implementations.Session { /// /// Class SessionWebSocketListener /// public class SessionWebSocketListener : IWebSocketListener { /// /// 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 IDtoService _dtoService; private readonly IServerApplicationHost _appHost; /// /// Initializes a new instance of the class. /// /// The session manager. /// The log manager. /// The dto service. public SessionWebSocketListener(ISessionManager sessionManager, ILogManager logManager, IDtoService dtoService, IServerApplicationHost appHost) { _sessionManager = sessionManager; _logger = logManager.GetLogger(GetType().Name); _dtoService = dtoService; _appHost = appHost; } /// /// 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)) { ReportPlaybackStart(message); } else if (string.Equals(message.MessageType, "PlaybackProgress", StringComparison.OrdinalIgnoreCase)) { ReportPlaybackProgress(message); } else if (string.Equals(message.MessageType, "PlaybackStopped", 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.Sessions .FirstOrDefault(i => string.Equals(i.DeviceId, deviceId) && string.Equals(i.Client, client) && string.Equals(i.ApplicationVersion, version)); if (session == null && !string.IsNullOrEmpty(deviceName)) { _logger.Debug("Logging session activity"); await _sessionManager.LogSessionActivity(client, version, deviceId, deviceName, message.Connection.RemoteEndPoint, null).ConfigureAwait(false); session = _sessionManager.Sessions .FirstOrDefault(i => string.Equals(i.DeviceId, deviceId) && string.Equals(i.Client, client) && string.Equals(i.ApplicationVersion, version)); } if (session != null) { var controller = session.SessionController as WebSocketController; if (controller == null) { controller = new WebSocketController(session, _appHost); } controller.Sockets.Add(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('|'); session.NowViewingItemType = vals[0]; session.NowViewingItemId = vals[1]; session.NowViewingItemName = vals[2]; session.NowViewingContext = vals.Length > 3 ? vals[3] : null; } } /// /// 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 ReportPlaybackStart(WebSocketMessageInfo message) { _logger.Debug("Received PlaybackStart message"); var session = GetSessionFromMessage(message); if (session != null && session.UserId.HasValue) { var vals = message.Data.Split('|'); var item = _dtoService.GetItemByDtoId(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 PlaybackInfo { CanSeek = canSeek, Item = item, 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); } } /// /// Reports the playback progress. /// /// The message. private void ReportPlaybackProgress(WebSocketMessageInfo message) { var session = GetSessionFromMessage(message); if (session != null && session.UserId.HasValue) { var vals = message.Data.Split('|'); var item = _dtoService.GetItemByDtoId(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 { Item = item, 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); } } /// /// Reports the playback stopped. /// /// The message. private void ReportPlaybackStopped(WebSocketMessageInfo message) { _logger.Debug("Received PlaybackStopped message"); var session = GetSessionFromMessage(message); if (session != null && session.UserId.HasValue) { var vals = message.Data.Split('|'); var item = _dtoService.GetItemByDtoId(vals[0]); long? positionTicks = null; if (vals.Length > 1) { long pos; if (long.TryParse(vals[1], out pos)) { positionTicks = pos; } } var info = new PlaybackStopInfo { Item = item, PositionTicks = positionTicks, SessionId = session.Id }; if (vals.Length > 2) { info.MediaSourceId = vals[2]; } _sessionManager.OnPlaybackStopped(info); } } } }