2021-05-20 19:28:18 +00:00
#nullable disable
2020-05-29 09:28:19 +00:00
#pragma warning disable CS1591
2019-01-13 19:22:24 +00:00
using System ;
using System.Collections.Concurrent ;
using System.Collections.Generic ;
using System.Globalization ;
using System.Linq ;
using System.Threading ;
using System.Threading.Tasks ;
2020-05-15 21:24:01 +00:00
using Jellyfin.Data.Entities ;
2021-04-10 20:17:36 +00:00
using Jellyfin.Data.Entities.Security ;
2020-05-13 02:10:35 +00:00
using Jellyfin.Data.Enums ;
2020-08-14 00:48:28 +00:00
using Jellyfin.Data.Events ;
2021-05-21 03:56:59 +00:00
using Jellyfin.Data.Queries ;
2021-06-19 16:02:33 +00:00
using Jellyfin.Extensions ;
2019-01-06 20:50:43 +00:00
using MediaBrowser.Common.Events ;
2013-09-24 15:42:30 +00:00
using MediaBrowser.Common.Extensions ;
2014-05-18 19:58:42 +00:00
using MediaBrowser.Controller ;
2019-01-13 19:22:24 +00:00
using MediaBrowser.Controller.Authentication ;
2023-05-01 18:11:22 +00:00
using MediaBrowser.Controller.Configuration ;
2014-10-11 20:38:13 +00:00
using MediaBrowser.Controller.Devices ;
2014-04-06 17:53:23 +00:00
using MediaBrowser.Controller.Drawing ;
using MediaBrowser.Controller.Dto ;
2013-05-09 17:38:02 +00:00
using MediaBrowser.Controller.Entities ;
2020-08-15 19:55:31 +00:00
using MediaBrowser.Controller.Events ;
2023-06-08 08:36:04 +00:00
using MediaBrowser.Controller.Events.Authentication ;
2020-08-15 19:55:31 +00:00
using MediaBrowser.Controller.Events.Session ;
2013-05-09 17:38:02 +00:00
using MediaBrowser.Controller.Library ;
2019-01-13 19:22:24 +00:00
using MediaBrowser.Controller.Net ;
2013-05-09 17:38:02 +00:00
using MediaBrowser.Controller.Session ;
2015-03-29 16:45:16 +00:00
using MediaBrowser.Model.Dto ;
2013-10-02 17:23:10 +00:00
using MediaBrowser.Model.Entities ;
2014-02-21 05:35:56 +00:00
using MediaBrowser.Model.Library ;
2019-01-13 19:22:24 +00:00
using MediaBrowser.Model.Querying ;
2013-09-24 15:42:30 +00:00
using MediaBrowser.Model.Session ;
2020-05-06 21:42:53 +00:00
using MediaBrowser.Model.SyncPlay ;
2020-06-11 23:45:31 +00:00
using Microsoft.EntityFrameworkCore ;
2023-09-23 01:10:49 +00:00
using Microsoft.Extensions.Hosting ;
2019-01-13 19:22:24 +00:00
using Microsoft.Extensions.Logging ;
2020-05-15 21:24:01 +00:00
using Episode = MediaBrowser . Controller . Entities . TV . Episode ;
2013-05-09 17:38:02 +00:00
2016-11-03 23:35:19 +00:00
namespace Emby.Server.Implementations.Session
2013-05-09 17:38:02 +00:00
{
2013-05-10 12:18:07 +00:00
/// <summary>
2020-01-16 23:19:58 +00:00
/// Class SessionManager.
2013-05-10 12:18:07 +00:00
/// </summary>
2023-09-23 01:10:49 +00:00
public sealed class SessionManager : ISessionManager , IAsyncDisposable
2013-05-09 17:38:02 +00:00
{
2016-08-25 00:12:15 +00:00
private readonly IUserDataManager _userDataManager ;
2023-05-01 18:11:22 +00:00
private readonly IServerConfigurationManager _config ;
2020-06-06 00:15:56 +00:00
private readonly ILogger < SessionManager > _logger ;
2020-08-15 19:55:31 +00:00
private readonly IEventManager _eventManager ;
2013-10-11 13:47:38 +00:00
private readonly ILibraryManager _libraryManager ;
2014-01-04 02:35:41 +00:00
private readonly IUserManager _userManager ;
2014-03-30 16:49:40 +00:00
private readonly IMusicManager _musicManager ;
2014-04-06 17:53:23 +00:00
private readonly IDtoService _dtoService ;
private readonly IImageProcessor _imageProcessor ;
2015-02-07 21:03:09 +00:00
private readonly IMediaSourceManager _mediaSourceManager ;
2014-05-18 19:58:42 +00:00
private readonly IServerApplicationHost _appHost ;
2014-10-11 20:38:13 +00:00
private readonly IDeviceManager _deviceManager ;
2023-09-23 01:10:49 +00:00
private readonly CancellationTokenRegistration _shutdownCallback ;
private readonly ConcurrentDictionary < string , SessionInfo > _activeConnections
= new ( StringComparer . OrdinalIgnoreCase ) ;
2013-05-09 17:38:02 +00:00
2020-01-16 23:19:58 +00:00
private Timer _idleTimer ;
2023-05-01 14:24:15 +00:00
private Timer _inactiveTimer ;
2013-05-09 17:38:02 +00:00
2020-01-16 23:19:58 +00:00
private DtoOptions _itemInfoDtoOptions ;
private bool _disposed = false ;
2014-04-06 17:53:23 +00:00
2019-01-17 22:55:05 +00:00
public SessionManager (
2020-01-16 23:19:58 +00:00
ILogger < SessionManager > logger ,
2020-08-15 19:55:31 +00:00
IEventManager eventManager ,
2019-01-17 22:55:05 +00:00
IUserDataManager userDataManager ,
2023-05-01 18:11:22 +00:00
IServerConfigurationManager config ,
2019-01-17 22:55:05 +00:00
ILibraryManager libraryManager ,
IUserManager userManager ,
IMusicManager musicManager ,
IDtoService dtoService ,
IImageProcessor imageProcessor ,
IServerApplicationHost appHost ,
IDeviceManager deviceManager ,
2023-09-23 01:10:49 +00:00
IMediaSourceManager mediaSourceManager ,
IHostApplicationLifetime hostApplicationLifetime )
2013-05-09 17:38:02 +00:00
{
2020-01-16 23:19:58 +00:00
_logger = logger ;
2020-08-15 19:55:31 +00:00
_eventManager = eventManager ;
2016-08-25 00:12:15 +00:00
_userDataManager = userDataManager ;
2023-05-01 18:11:22 +00:00
_config = config ;
2013-10-25 14:19:03 +00:00
_libraryManager = libraryManager ;
2014-01-04 02:35:41 +00:00
_userManager = userManager ;
2014-04-02 21:55:19 +00:00
_musicManager = musicManager ;
2014-04-06 17:53:23 +00:00
_dtoService = dtoService ;
_imageProcessor = imageProcessor ;
2014-05-18 19:58:42 +00:00
_appHost = appHost ;
2014-10-11 20:38:13 +00:00
_deviceManager = deviceManager ;
2015-02-07 21:03:09 +00:00
_mediaSourceManager = mediaSourceManager ;
2023-09-23 01:10:49 +00:00
_shutdownCallback = hostApplicationLifetime . ApplicationStopping . Register ( OnApplicationStopping ) ;
2020-01-16 23:19:58 +00:00
2019-03-13 21:32:52 +00:00
_deviceManager . DeviceOptionsUpdated + = OnDeviceManagerDeviceOptionsUpdated ;
2014-10-13 20:14:53 +00:00
}
2020-01-16 23:19:58 +00:00
/// <summary>
2020-02-06 12:03:02 +00:00
/// Occurs when playback has started.
2020-01-16 23:19:58 +00:00
/// </summary>
public event EventHandler < PlaybackProgressEventArgs > PlaybackStart ;
/// <summary>
2020-02-06 12:03:02 +00:00
/// Occurs when playback has progressed.
2020-01-16 23:19:58 +00:00
/// </summary>
public event EventHandler < PlaybackProgressEventArgs > PlaybackProgress ;
/// <summary>
2020-02-06 12:03:02 +00:00
/// Occurs when playback has stopped.
2020-01-16 23:19:58 +00:00
/// </summary>
public event EventHandler < PlaybackStopEventArgs > PlaybackStopped ;
/// <inheritdoc />
public event EventHandler < SessionEventArgs > SessionStarted ;
/// <inheritdoc />
public event EventHandler < SessionEventArgs > CapabilitiesChanged ;
/// <inheritdoc />
public event EventHandler < SessionEventArgs > SessionEnded ;
/// <inheritdoc />
public event EventHandler < SessionEventArgs > SessionActivity ;
2020-12-07 00:04:48 +00:00
/// <inheritdoc />
public event EventHandler < SessionEventArgs > SessionControllerConnected ;
2020-01-16 23:19:58 +00:00
/// <summary>
/// Gets all connections.
/// </summary>
/// <value>All connections.</value>
public IEnumerable < SessionInfo > Sessions = > _activeConnections . Values . OrderByDescending ( c = > c . LastActivityDate ) ;
2019-03-13 21:32:52 +00:00
private void OnDeviceManagerDeviceOptionsUpdated ( object sender , GenericEventArgs < Tuple < string , DeviceOptions > > e )
2014-10-13 20:14:53 +00:00
{
foreach ( var session in Sessions )
{
2019-03-13 21:32:52 +00:00
if ( string . Equals ( session . DeviceId , e . Argument . Item1 , StringComparison . Ordinal ) )
2014-10-13 20:14:53 +00:00
{
2018-09-12 17:26:21 +00:00
if ( ! string . IsNullOrWhiteSpace ( e . Argument . Item2 . CustomName ) )
{
session . HasCustomDeviceName = true ;
session . DeviceName = e . Argument . Item2 . CustomName ;
}
else
{
session . HasCustomDeviceName = false ;
}
2014-10-13 20:14:53 +00:00
}
}
2013-05-09 17:38:02 +00:00
}
2020-01-16 23:19:58 +00:00
private void CheckDisposed ( )
2013-12-25 14:39:46 +00:00
{
2024-04-12 23:45:01 +00:00
ObjectDisposedException . ThrowIf ( _disposed , this ) ;
2013-12-25 14:39:46 +00:00
}
2014-04-06 17:53:23 +00:00
private void OnSessionStarted ( SessionInfo info )
{
2018-09-12 17:26:21 +00:00
if ( ! string . IsNullOrEmpty ( info . DeviceId ) )
2014-06-04 03:34:36 +00:00
{
2020-11-19 14:38:54 +00:00
var capabilities = _deviceManager . GetCapabilities ( info . DeviceId ) ;
2014-06-04 03:34:36 +00:00
2022-12-05 14:01:13 +00:00
if ( capabilities is not null )
2014-06-04 03:34:36 +00:00
{
ReportCapabilities ( info , capabilities , false ) ;
}
}
2014-04-06 17:53:23 +00:00
2020-08-15 19:55:31 +00:00
_eventManager . Publish ( new SessionStartedEventArgs ( info ) ) ;
2019-03-13 21:32:52 +00:00
EventHelper . QueueEventIfNotNull (
SessionStarted ,
this ,
new SessionEventArgs
{
SessionInfo = info
} ,
_logger ) ;
2018-09-12 17:26:21 +00:00
}
2024-01-14 15:50:09 +00:00
private async ValueTask OnSessionEnded ( SessionInfo info )
2018-09-12 17:26:21 +00:00
{
2019-03-13 21:32:52 +00:00
EventHelper . QueueEventIfNotNull (
SessionEnded ,
this ,
new SessionEventArgs
{
SessionInfo = info
} ,
_logger ) ;
2014-04-06 17:53:23 +00:00
2020-08-15 19:55:31 +00:00
_eventManager . Publish ( new SessionEndedEventArgs ( info ) ) ;
2024-01-14 15:50:09 +00:00
await info . DisposeAsync ( ) . ConfigureAwait ( false ) ;
2018-09-12 17:26:21 +00:00
}
2014-04-06 17:53:23 +00:00
2020-01-16 23:19:58 +00:00
/// <inheritdoc />
2021-09-03 16:46:34 +00:00
public void UpdateDeviceName ( string sessionId , string reportedDeviceName )
2018-09-12 17:26:21 +00:00
{
var session = GetSession ( sessionId ) ;
2022-12-05 14:01:13 +00:00
if ( session is not null )
2018-09-12 17:26:21 +00:00
{
2021-09-03 16:46:34 +00:00
session . DeviceName = reportedDeviceName ;
2018-09-12 17:26:21 +00:00
}
2014-04-06 17:53:23 +00:00
}
2013-05-09 17:38:02 +00:00
/// <summary>
/// Logs the user activity.
/// </summary>
2015-03-15 01:42:09 +00:00
/// <param name="appName">Type of the client.</param>
2013-07-09 16:11:16 +00:00
/// <param name="appVersion">The app version.</param>
2013-05-09 17:38:02 +00:00
/// <param name="deviceId">The device id.</param>
/// <param name="deviceName">Name of the device.</param>
2013-12-25 14:39:46 +00:00
/// <param name="remoteEndPoint">The remote end point.</param>
2013-05-09 17:38:02 +00:00
/// <param name="user">The user.</param>
2019-03-13 21:32:52 +00:00
/// <returns>SessionInfo.</returns>
2021-04-10 20:57:25 +00:00
public async Task < SessionInfo > LogSessionActivity (
2019-03-13 21:32:52 +00:00
string appName ,
2014-07-08 01:41:03 +00:00
string appVersion ,
string deviceId ,
string deviceName ,
string remoteEndPoint ,
User user )
2013-05-09 17:38:02 +00:00
{
2018-09-12 17:26:21 +00:00
CheckDisposed ( ) ;
2022-10-13 17:08:00 +00:00
ArgumentException . ThrowIfNullOrEmpty ( appName ) ;
ArgumentException . ThrowIfNullOrEmpty ( appVersion ) ;
ArgumentException . ThrowIfNullOrEmpty ( deviceId ) ;
2013-07-09 16:11:16 +00:00
2013-05-09 17:38:02 +00:00
var activityDate = DateTime . UtcNow ;
2024-08-05 14:58:22 +00:00
var session = GetSessionInfo ( appName , appVersion , deviceId , deviceName , remoteEndPoint , user ) ;
2015-03-21 16:10:02 +00:00
var lastActivityDate = session . LastActivityDate ;
2013-07-09 16:11:16 +00:00
session . LastActivityDate = activityDate ;
2013-05-09 17:38:02 +00:00
2022-12-05 14:01:13 +00:00
if ( user is not null )
2013-05-09 17:38:02 +00:00
{
2015-03-21 16:10:02 +00:00
var userLastActivityDate = user . LastActivityDate ? ? DateTime . MinValue ;
2013-05-09 17:38:02 +00:00
2016-10-02 04:31:47 +00:00
if ( ( activityDate - userLastActivityDate ) . TotalSeconds > 60 )
2015-03-21 16:10:02 +00:00
{
2020-06-11 23:45:31 +00:00
try
{
user . LastActivityDate = activityDate ;
2021-06-26 01:13:38 +00:00
await _userManager . UpdateUserAsync ( user ) . ConfigureAwait ( false ) ;
2020-06-11 23:45:31 +00:00
}
catch ( DbUpdateConcurrencyException e )
{
2020-07-07 22:20:17 +00:00
_logger . LogDebug ( e , "Error updating user's last activity date." ) ;
2020-06-11 23:45:31 +00:00
}
2015-03-21 16:10:02 +00:00
}
}
2013-05-09 17:38:02 +00:00
2015-03-21 16:10:02 +00:00
if ( ( activityDate - lastActivityDate ) . TotalSeconds > 10 )
2013-05-09 17:38:02 +00:00
{
2019-03-13 21:32:52 +00:00
SessionActivity ? . Invoke (
this ,
new SessionEventArgs
{
SessionInfo = session
} ) ;
2015-03-21 16:10:02 +00:00
}
2013-07-09 16:11:16 +00:00
return session ;
2013-05-09 17:38:02 +00:00
}
2020-12-07 00:04:48 +00:00
/// <inheritdoc />
2021-09-03 16:46:34 +00:00
public void OnSessionControllerConnected ( SessionInfo session )
2020-12-07 00:04:48 +00:00
{
EventHelper . QueueEventIfNotNull (
SessionControllerConnected ,
this ,
new SessionEventArgs
{
2021-09-03 16:46:34 +00:00
SessionInfo = session
2020-12-07 00:04:48 +00:00
} ,
_logger ) ;
}
2020-01-16 23:19:58 +00:00
/// <inheritdoc />
2022-05-29 14:49:50 +00:00
public async Task CloseIfNeededAsync ( SessionInfo session )
2014-03-10 02:33:32 +00:00
{
2018-09-12 17:26:21 +00:00
if ( ! session . SessionControllers . Any ( i = > i . IsSessionActive ) )
2014-03-10 02:33:32 +00:00
{
2018-09-12 17:26:21 +00:00
var key = GetSessionKey ( session . Client , session . DeviceId ) ;
2014-03-10 02:33:32 +00:00
2020-01-16 23:19:58 +00:00
_activeConnections . TryRemove ( key , out _ ) ;
2022-05-29 14:49:50 +00:00
if ( ! string . IsNullOrEmpty ( session . PlayState ? . LiveStreamId ) )
{
await _mediaSourceManager . CloseLiveStream ( session . PlayState . LiveStreamId ) . ConfigureAwait ( false ) ;
}
2014-03-10 02:33:32 +00:00
2024-01-14 15:50:09 +00:00
await OnSessionEnded ( session ) . ConfigureAwait ( false ) ;
2014-03-10 02:33:32 +00:00
}
2018-09-12 17:26:21 +00:00
}
2020-01-16 23:19:58 +00:00
/// <inheritdoc />
2024-01-14 15:50:09 +00:00
public async ValueTask ReportSessionEnded ( string sessionId )
2018-09-12 17:26:21 +00:00
{
CheckDisposed ( ) ;
var session = GetSession ( sessionId , false ) ;
2022-12-05 14:01:13 +00:00
if ( session is not null )
2014-03-10 02:33:32 +00:00
{
2018-09-12 17:26:21 +00:00
var key = GetSessionKey ( session . Client , session . DeviceId ) ;
2020-01-16 23:19:58 +00:00
_activeConnections . TryRemove ( key , out _ ) ;
2018-09-12 17:26:21 +00:00
2024-01-14 15:50:09 +00:00
await OnSessionEnded ( session ) . ConfigureAwait ( false ) ;
2014-03-10 02:33:32 +00:00
}
}
2018-09-12 17:26:21 +00:00
private Task < MediaSourceInfo > GetMediaSource ( BaseItem item , string mediaSourceId , string liveStreamId )
2015-03-29 16:45:16 +00:00
{
2016-09-18 20:38:38 +00:00
return _mediaSourceManager . GetMediaSource ( item , mediaSourceId , liveStreamId , false , CancellationToken . None ) ;
2015-03-29 16:45:16 +00:00
}
2013-05-09 17:38:02 +00:00
/// <summary>
/// Updates the now playing item id.
/// </summary>
2019-03-13 21:32:52 +00:00
/// <returns>Task.</returns>
2017-04-17 19:01:16 +00:00
private async Task UpdateNowPlayingItem ( SessionInfo session , PlaybackProgressInfo info , BaseItem libraryItem , bool updateLastCheckInTime )
2013-05-09 17:38:02 +00:00
{
2018-09-12 17:26:21 +00:00
if ( string . IsNullOrEmpty ( info . MediaSourceId ) )
2014-03-22 03:35:03 +00:00
{
2019-02-28 22:22:57 +00:00
info . MediaSourceId = info . ItemId . ToString ( "N" , CultureInfo . InvariantCulture ) ;
2014-03-22 03:35:03 +00:00
}
2014-04-16 02:17:48 +00:00
2024-01-17 15:51:39 +00:00
if ( ! info . ItemId . IsEmpty ( ) & & info . Item is null & & libraryItem is not null )
2014-03-22 03:35:03 +00:00
{
2015-03-29 16:45:16 +00:00
var current = session . NowPlayingItem ;
2015-01-17 04:29:53 +00:00
2022-12-05 14:00:20 +00:00
if ( current is null | | ! info . ItemId . Equals ( current . Id ) )
2015-01-17 04:29:53 +00:00
{
2015-03-29 16:45:16 +00:00
var runtimeTicks = libraryItem . RunTimeTicks ;
2015-03-17 04:08:09 +00:00
2015-04-02 16:24:59 +00:00
MediaSourceInfo mediaSource = null ;
2020-01-16 23:19:58 +00:00
if ( libraryItem is IHasMediaSources )
2015-03-29 16:45:16 +00:00
{
2018-09-12 17:26:21 +00:00
mediaSource = await GetMediaSource ( libraryItem , info . MediaSourceId , info . LiveStreamId ) . ConfigureAwait ( false ) ;
2015-04-02 16:24:59 +00:00
2022-12-05 14:01:13 +00:00
if ( mediaSource is not null )
2015-04-02 16:24:59 +00:00
{
runtimeTicks = mediaSource . RunTimeTicks ;
}
2015-03-29 16:45:16 +00:00
}
2014-05-10 17:28:03 +00:00
2017-06-09 19:26:54 +00:00
info . Item = GetItemInfo ( libraryItem , mediaSource ) ;
2015-03-29 16:45:16 +00:00
info . Item . RunTimeTicks = runtimeTicks ;
2014-05-10 17:28:03 +00:00
}
else
{
info . Item = current ;
}
2014-03-22 03:35:03 +00:00
}
2014-04-16 02:17:48 +00:00
session . NowPlayingItem = info . Item ;
session . LastActivityDate = DateTime . UtcNow ;
2017-04-17 19:01:16 +00:00
if ( updateLastCheckInTime )
{
session . LastPlaybackCheckIn = DateTime . UtcNow ;
}
2014-04-16 02:17:48 +00:00
2023-05-14 13:05:03 +00:00
if ( info . IsPaused & & session . LastPausedDate is null )
2023-05-01 14:24:15 +00:00
{
session . LastPausedDate = DateTime . UtcNow ;
}
else if ( ! info . IsPaused )
{
session . LastPausedDate = null ;
}
2014-04-22 17:25:54 +00:00
session . PlayState . IsPaused = info . IsPaused ;
session . PlayState . PositionTicks = info . PositionTicks ;
session . PlayState . MediaSourceId = info . MediaSourceId ;
2022-05-29 14:49:50 +00:00
session . PlayState . LiveStreamId = info . LiveStreamId ;
2014-04-16 02:17:48 +00:00
session . PlayState . CanSeek = info . CanSeek ;
session . PlayState . IsMuted = info . IsMuted ;
session . PlayState . VolumeLevel = info . VolumeLevel ;
session . PlayState . AudioStreamIndex = info . AudioStreamIndex ;
session . PlayState . SubtitleStreamIndex = info . SubtitleStreamIndex ;
2014-04-18 05:03:01 +00:00
session . PlayState . PlayMethod = info . PlayMethod ;
2015-07-30 14:34:46 +00:00
session . PlayState . RepeatMode = info . RepeatMode ;
2024-02-09 19:41:32 +00:00
session . PlayState . PlaybackOrder = info . PlaybackOrder ;
2018-09-12 17:26:21 +00:00
session . PlaylistItemId = info . PlaylistItemId ;
var nowPlayingQueue = info . NowPlayingQueue ;
2022-01-14 15:12:45 +00:00
if ( nowPlayingQueue ? . Length > 0 )
2018-09-12 17:26:21 +00:00
{
session . NowPlayingQueue = nowPlayingQueue ;
2021-05-17 11:33:37 +00:00
2024-04-30 19:32:59 +00:00
var itemIds = Array . ConvertAll ( nowPlayingQueue , queue = > queue . Id ) ;
2022-01-14 15:12:45 +00:00
session . NowPlayingQueueFullItems = _dtoService . GetBaseItemDtos (
_libraryManager . GetItemList ( new InternalItemsQuery { ItemIds = itemIds } ) ,
new DtoOptions ( true ) ) ;
}
2013-05-09 17:38:02 +00:00
}
/// <summary>
/// Removes the now playing item id.
/// </summary>
2013-07-09 16:11:16 +00:00
/// <param name="session">The session.</param>
2014-04-16 02:17:48 +00:00
private void RemoveNowPlayingItem ( SessionInfo session )
2013-05-09 17:38:02 +00:00
{
2014-04-16 02:17:48 +00:00
session . NowPlayingItem = null ;
session . PlayState = new PlayerStateInfo ( ) ;
2014-06-06 00:39:02 +00:00
if ( ! string . IsNullOrEmpty ( session . DeviceId ) )
{
ClearTranscodingInfo ( session . DeviceId ) ;
}
2013-05-09 17:38:02 +00:00
}
2019-01-06 20:50:43 +00:00
private static string GetSessionKey ( string appName , string deviceId )
2020-01-16 23:19:58 +00:00
= > appName + deviceId ;
2014-03-10 02:33:32 +00:00
2013-05-09 17:38:02 +00:00
/// <summary>
/// Gets the connection.
/// </summary>
2015-03-15 01:42:09 +00:00
/// <param name="appName">Type of the client.</param>
2013-07-09 16:11:16 +00:00
/// <param name="appVersion">The app version.</param>
2013-05-09 17:38:02 +00:00
/// <param name="deviceId">The device id.</param>
/// <param name="deviceName">Name of the device.</param>
2013-12-25 14:39:46 +00:00
/// <param name="remoteEndPoint">The remote end point.</param>
2015-03-09 19:40:03 +00:00
/// <param name="user">The user.</param>
2013-05-09 17:38:02 +00:00
/// <returns>SessionInfo.</returns>
2024-08-05 14:58:22 +00:00
private SessionInfo GetSessionInfo (
2020-05-13 02:10:35 +00:00
string appName ,
string appVersion ,
string deviceId ,
string deviceName ,
string remoteEndPoint ,
2020-05-15 21:24:01 +00:00
User user )
2013-05-09 17:38:02 +00:00
{
2018-09-12 17:26:21 +00:00
CheckDisposed ( ) ;
2022-10-13 17:08:00 +00:00
ArgumentException . ThrowIfNullOrEmpty ( deviceId ) ;
2020-01-16 23:19:58 +00:00
2015-03-15 01:42:09 +00:00
var key = GetSessionKey ( appName , deviceId ) ;
2013-05-09 17:38:02 +00:00
2018-09-12 17:26:21 +00:00
CheckDisposed ( ) ;
2014-03-10 02:33:32 +00:00
2021-04-10 20:57:25 +00:00
if ( ! _activeConnections . TryGetValue ( key , out var sessionInfo ) )
{
2024-08-05 14:58:22 +00:00
sessionInfo = CreateSession ( key , appName , appVersion , deviceId , deviceName , remoteEndPoint , user ) ;
2024-03-18 19:55:18 +00:00
_activeConnections [ key ] = sessionInfo ;
2021-04-10 20:57:25 +00:00
}
2014-08-10 22:13:17 +00:00
2020-05-13 02:10:35 +00:00
sessionInfo . UserId = user ? . Id ? ? Guid . Empty ;
sessionInfo . UserName = user ? . Username ;
2022-12-05 14:00:20 +00:00
sessionInfo . UserPrimaryImageTag = user ? . ProfileImage is null ? null : GetImageCacheTag ( user ) ;
2018-09-12 17:26:21 +00:00
sessionInfo . RemoteEndPoint = remoteEndPoint ;
sessionInfo . Client = appName ;
2014-04-06 17:53:23 +00:00
2018-09-12 17:26:21 +00:00
if ( ! sessionInfo . HasCustomDeviceName | | string . IsNullOrEmpty ( sessionInfo . DeviceName ) )
{
sessionInfo . DeviceName = deviceName ;
}
2014-10-11 20:38:13 +00:00
2018-09-12 17:26:21 +00:00
sessionInfo . ApplicationVersion = appVersion ;
2014-03-10 02:33:32 +00:00
2022-12-05 14:00:20 +00:00
if ( user is null )
2018-09-12 17:26:21 +00:00
{
2019-03-13 21:32:52 +00:00
sessionInfo . AdditionalUsers = Array . Empty < SessionUserInfo > ( ) ;
2018-09-12 17:26:21 +00:00
}
2014-10-13 20:14:53 +00:00
2018-09-12 17:26:21 +00:00
return sessionInfo ;
}
2015-03-29 16:45:16 +00:00
2024-08-05 14:58:22 +00:00
private SessionInfo CreateSession (
2020-05-13 02:10:35 +00:00
string key ,
string appName ,
string appVersion ,
string deviceId ,
string deviceName ,
string remoteEndPoint ,
2020-05-15 21:24:01 +00:00
User user )
2018-09-12 17:26:21 +00:00
{
var sessionInfo = new SessionInfo ( this , _logger )
{
Client = appName ,
DeviceId = deviceId ,
ApplicationVersion = appVersion ,
2020-06-24 21:09:15 +00:00
Id = key . GetMD5 ( ) . ToString ( "N" , CultureInfo . InvariantCulture ) ,
ServerId = _appHost . SystemId
2018-09-12 17:26:21 +00:00
} ;
2014-10-13 20:14:53 +00:00
2020-05-13 02:10:35 +00:00
var username = user ? . Username ;
2014-03-10 02:33:32 +00:00
2019-03-13 21:32:52 +00:00
sessionInfo . UserId = user ? . Id ? ? Guid . Empty ;
2018-09-12 17:26:21 +00:00
sessionInfo . UserName = username ;
2022-12-05 14:00:20 +00:00
sessionInfo . UserPrimaryImageTag = user ? . ProfileImage is null ? null : GetImageCacheTag ( user ) ;
2018-09-12 17:26:21 +00:00
sessionInfo . RemoteEndPoint = remoteEndPoint ;
2014-01-04 02:35:41 +00:00
2018-09-12 17:26:21 +00:00
if ( string . IsNullOrEmpty ( deviceName ) )
{
deviceName = "Network Device" ;
}
2014-03-10 02:33:32 +00:00
2024-08-05 14:58:22 +00:00
var deviceOptions = _deviceManager . GetDeviceOptions ( deviceId ) ;
2018-09-12 17:26:21 +00:00
if ( string . IsNullOrEmpty ( deviceOptions . CustomName ) )
{
sessionInfo . DeviceName = deviceName ;
2014-03-10 02:33:32 +00:00
}
2018-09-12 17:26:21 +00:00
else
2013-12-25 14:39:46 +00:00
{
2018-09-12 17:26:21 +00:00
sessionInfo . DeviceName = deviceOptions . CustomName ;
sessionInfo . HasCustomDeviceName = true ;
2013-12-25 14:39:46 +00:00
}
2018-09-12 17:26:21 +00:00
OnSessionStarted ( sessionInfo ) ;
return sessionInfo ;
2013-05-09 17:38:02 +00:00
}
2014-01-04 02:35:41 +00:00
private List < User > GetUsers ( SessionInfo session )
{
var users = new List < User > ( ) ;
2024-01-17 15:51:39 +00:00
if ( session . UserId . IsEmpty ( ) )
2014-01-04 02:35:41 +00:00
{
2022-02-21 13:15:09 +00:00
return users ;
}
2014-01-04 02:35:41 +00:00
2022-02-21 13:15:09 +00:00
var user = _userManager . GetUserById ( session . UserId ) ;
2014-01-04 02:35:41 +00:00
2022-12-05 14:00:20 +00:00
if ( user is null )
2022-02-21 13:15:09 +00:00
{
throw new InvalidOperationException ( "User not found" ) ;
2014-01-04 02:35:41 +00:00
}
2022-02-21 13:15:09 +00:00
users . Add ( user ) ;
users . AddRange ( session . AdditionalUsers
. Select ( i = > _userManager . GetUserById ( i . UserId ) )
2022-12-05 14:01:13 +00:00
. Where ( i = > i is not null ) ) ;
2022-02-21 13:15:09 +00:00
2014-01-04 02:35:41 +00:00
return users ;
}
2023-05-01 14:24:15 +00:00
private void StartCheckTimers ( )
2014-10-23 04:26:01 +00:00
{
2020-05-13 02:10:35 +00:00
_idleTimer ? ? = new Timer ( CheckForIdlePlayback , null , TimeSpan . FromMinutes ( 5 ) , TimeSpan . FromMinutes ( 5 ) ) ;
2023-05-01 18:11:22 +00:00
if ( _config . Configuration . InactiveSessionThreshold > 0 )
{
_inactiveTimer ? ? = new Timer ( CheckForInactiveSteams , null , TimeSpan . FromMinutes ( 1 ) , TimeSpan . FromMinutes ( 1 ) ) ;
}
else
{
StopInactiveCheckTimer ( ) ;
}
2014-10-23 04:26:01 +00:00
}
2019-03-13 21:32:52 +00:00
2014-10-23 04:26:01 +00:00
private void StopIdleCheckTimer ( )
{
2022-12-05 14:01:13 +00:00
if ( _idleTimer is not null )
2014-10-23 04:26:01 +00:00
{
_idleTimer . Dispose ( ) ;
_idleTimer = null ;
}
}
2023-05-01 14:24:15 +00:00
private void StopInactiveCheckTimer ( )
{
if ( _inactiveTimer is not null )
{
_inactiveTimer . Dispose ( ) ;
_inactiveTimer = null ;
}
}
2014-10-23 04:26:01 +00:00
private async void CheckForIdlePlayback ( object state )
{
2022-12-05 14:01:13 +00:00
var playingSessions = Sessions . Where ( i = > i . NowPlayingItem is not null )
2014-10-23 04:26:01 +00:00
. ToList ( ) ;
if ( playingSessions . Count > 0 )
{
var idle = playingSessions
. Where ( i = > ( DateTime . UtcNow - i . LastPlaybackCheckIn ) . TotalMinutes > 5 )
. ToList ( ) ;
foreach ( var session in idle )
{
2018-12-13 13:18:25 +00:00
_logger . LogDebug ( "Session {0} has gone idle while playing" , session . Id ) ;
2014-10-23 04:26:01 +00:00
try
{
await OnPlaybackStopped ( new PlaybackStopInfo
{
Item = session . NowPlayingItem ,
2022-12-05 14:00:20 +00:00
ItemId = session . NowPlayingItem is null ? Guid . Empty : session . NowPlayingItem . Id ,
2014-10-23 04:26:01 +00:00
SessionId = session . Id ,
2019-03-13 21:32:52 +00:00
MediaSourceId = session . PlayState ? . MediaSourceId ,
PositionTicks = session . PlayState ? . PositionTicks
} ) . ConfigureAwait ( false ) ;
2014-10-23 04:26:01 +00:00
}
catch ( Exception ex )
{
2023-04-06 17:05:05 +00:00
_logger . LogDebug ( ex , "Error calling OnPlaybackStopped" ) ;
2014-10-23 04:26:01 +00:00
}
}
}
2023-05-01 14:24:15 +00:00
else
{
StopIdleCheckTimer ( ) ;
}
}
private async void CheckForInactiveSteams ( object state )
{
2023-10-09 17:15:25 +00:00
var inactiveSessions = Sessions . Where ( i = >
2023-05-14 13:05:03 +00:00
i . NowPlayingItem is not null
& & i . PlayState . IsPaused
2023-10-09 17:15:25 +00:00
& & ( DateTime . UtcNow - i . LastPausedDate ) . Value . TotalMinutes > _config . Configuration . InactiveSessionThreshold ) ;
2023-05-01 14:24:15 +00:00
2023-10-09 17:15:25 +00:00
foreach ( var session in inactiveSessions )
2023-05-01 14:24:15 +00:00
{
2023-10-09 17:15:25 +00:00
_logger . LogDebug ( "Session {Session} has been inactive for {InactiveTime} minutes. Stopping it." , session . Id , _config . Configuration . InactiveSessionThreshold ) ;
2023-05-01 14:24:15 +00:00
2023-10-09 17:15:25 +00:00
try
2023-05-01 14:24:15 +00:00
{
2023-10-09 17:15:25 +00:00
await SendPlaystateCommand (
session . Id ,
session . Id ,
new PlaystateRequest ( )
{
Command = PlaystateCommand . Stop ,
ControllingUserId = session . UserId . ToString ( ) ,
SeekPositionTicks = session . PlayState ? . PositionTicks
} ,
CancellationToken . None ) . ConfigureAwait ( true ) ;
}
catch ( Exception ex )
{
_logger . LogDebug ( ex , "Error calling SendPlaystateCommand for stopping inactive session {Session}." , session . Id ) ;
2023-05-01 14:24:15 +00:00
}
}
2014-10-23 04:26:01 +00:00
2023-10-09 17:15:25 +00:00
bool playingSessions = Sessions . Any ( i = > i . NowPlayingItem is not null ) ;
if ( ! playingSessions )
2014-10-23 04:26:01 +00:00
{
2023-05-01 14:24:15 +00:00
StopInactiveCheckTimer ( ) ;
2014-10-23 04:26:01 +00:00
}
}
2018-09-12 17:26:21 +00:00
private BaseItem GetNowPlayingItem ( SessionInfo session , Guid itemId )
2016-11-12 04:02:22 +00:00
{
var item = session . FullNowPlayingItem ;
2022-12-05 14:01:13 +00:00
if ( item is not null & & item . Id . Equals ( itemId ) )
2016-11-12 04:02:22 +00:00
{
return item ;
}
item = _libraryManager . GetItemById ( itemId ) ;
session . FullNowPlayingItem = item ;
return item ;
}
2013-05-09 17:38:02 +00:00
/// <summary>
2020-01-16 23:19:58 +00:00
/// Used to report that playback has started for an item.
2013-05-09 17:38:02 +00:00
/// </summary>
2013-09-24 15:08:51 +00:00
/// <param name="info">The info.</param>
2013-07-09 16:11:16 +00:00
/// <returns>Task.</returns>
2020-01-16 23:19:58 +00:00
/// <exception cref="ArgumentNullException"><c>info</c> is <c>null</c>.</exception>
2014-04-16 02:17:48 +00:00
public async Task OnPlaybackStart ( PlaybackStartInfo info )
2013-05-09 17:38:02 +00:00
{
2018-09-12 17:26:21 +00:00
CheckDisposed ( ) ;
2022-10-06 18:21:23 +00:00
ArgumentNullException . ThrowIfNull ( info ) ;
2013-09-24 15:08:51 +00:00
2014-04-16 02:17:48 +00:00
var session = GetSession ( info . SessionId ) ;
2013-07-09 16:11:16 +00:00
2024-01-17 15:51:39 +00:00
var libraryItem = info . ItemId . IsEmpty ( )
2014-04-16 02:17:48 +00:00
? null
2016-11-12 04:02:22 +00:00
: GetNowPlayingItem ( session , info . ItemId ) ;
2014-03-22 03:35:03 +00:00
2017-04-17 19:01:16 +00:00
await UpdateNowPlayingItem ( session , info , libraryItem , true ) . ConfigureAwait ( false ) ;
2013-05-09 17:38:02 +00:00
2014-06-06 04:56:47 +00:00
if ( ! string . IsNullOrEmpty ( session . DeviceId ) & & info . PlayMethod ! = PlayMethod . Transcode )
2014-06-06 00:39:02 +00:00
{
ClearTranscodingInfo ( session . DeviceId ) ;
}
2019-02-05 08:49:46 +00:00
session . StartAutomaticProgress ( info ) ;
2013-09-24 15:08:51 +00:00
2014-01-04 02:35:41 +00:00
var users = GetUsers ( session ) ;
2013-05-17 18:05:49 +00:00
2022-12-05 14:01:13 +00:00
if ( libraryItem is not null )
2013-05-22 17:58:49 +00:00
{
2014-04-16 02:17:48 +00:00
foreach ( var user in users )
{
2018-09-12 17:26:21 +00:00
OnPlaybackStart ( user , libraryItem ) ;
2014-04-16 02:17:48 +00:00
}
2013-05-22 17:58:49 +00:00
}
2020-10-06 06:14:56 +00:00
var eventArgs = new PlaybackStartEventArgs
2020-08-15 19:55:31 +00:00
{
Item = libraryItem ,
Users = users ,
MediaSourceId = info . MediaSourceId ,
MediaInfo = info . Item ,
DeviceName = session . DeviceName ,
ClientName = session . Client ,
DeviceId = session . DeviceId ,
2022-04-16 12:03:05 +00:00
Session = session ,
PlaybackPositionTicks = info . PositionTicks ,
PlaySessionId = info . PlaySessionId
2020-08-15 19:55:31 +00:00
} ;
await _eventManager . PublishAsync ( eventArgs ) . ConfigureAwait ( false ) ;
2013-05-09 17:38:02 +00:00
// Nothing to save here
// Fire events to inform plugins
2019-03-13 21:32:52 +00:00
EventHelper . QueueEventIfNotNull (
PlaybackStart ,
this ,
2020-08-15 19:55:31 +00:00
eventArgs ,
2019-03-13 21:32:52 +00:00
_logger ) ;
2014-04-12 17:27:53 +00:00
2023-05-01 14:24:15 +00:00
StartCheckTimers ( ) ;
2013-05-09 17:38:02 +00:00
}
2014-01-04 02:35:41 +00:00
/// <summary>
/// Called when [playback start].
/// </summary>
2019-01-06 20:50:43 +00:00
/// <param name="user">The user object.</param>
2014-01-04 02:35:41 +00:00
/// <param name="item">The item.</param>
2018-09-12 17:26:21 +00:00
private void OnPlaybackStart ( User user , BaseItem item )
2014-01-04 02:35:41 +00:00
{
2018-09-12 17:26:21 +00:00
var data = _userDataManager . GetUserData ( user , item ) ;
2014-01-04 02:35:41 +00:00
data . PlayCount + + ;
data . LastPlayedDate = DateTime . UtcNow ;
2019-12-14 02:36:06 +00:00
if ( item . SupportsPlayedStatus & & ! item . SupportsPositionTicksResume )
2014-01-04 02:35:41 +00:00
{
2019-12-14 02:36:06 +00:00
data . Played = true ;
2014-01-04 02:35:41 +00:00
}
2016-10-11 06:46:59 +00:00
else
{
data . Played = false ;
}
2014-01-04 02:35:41 +00:00
2018-09-12 17:26:21 +00:00
_userDataManager . SaveUserData ( user , item , data , UserDataSaveReason . PlaybackStart , CancellationToken . None ) ;
2014-01-04 02:35:41 +00:00
}
2020-01-16 23:19:58 +00:00
/// <inheritdoc />
2017-04-17 19:01:16 +00:00
public Task OnPlaybackProgress ( PlaybackProgressInfo info )
{
return OnPlaybackProgress ( info , false ) ;
}
2013-05-09 17:38:02 +00:00
/// <summary>
2020-01-16 23:19:58 +00:00
/// Used to report playback progress for an item.
2013-05-09 17:38:02 +00:00
/// </summary>
2021-10-02 18:01:57 +00:00
/// <param name="info">The playback progress info.</param>
/// <param name="isAutomated">Whether this is an automated update.</param>
2019-03-13 21:32:52 +00:00
/// <returns>Task.</returns>
2017-04-17 19:01:16 +00:00
public async Task OnPlaybackProgress ( PlaybackProgressInfo info , bool isAutomated )
2013-05-09 17:38:02 +00:00
{
2018-09-12 17:26:21 +00:00
CheckDisposed ( ) ;
2022-10-06 18:21:23 +00:00
ArgumentNullException . ThrowIfNull ( info ) ;
2013-05-09 17:38:02 +00:00
2014-04-16 02:17:48 +00:00
var session = GetSession ( info . SessionId ) ;
2014-03-22 03:35:03 +00:00
2024-01-17 15:51:39 +00:00
var libraryItem = info . ItemId . IsEmpty ( )
2014-04-16 02:17:48 +00:00
? null
2016-11-12 04:02:22 +00:00
: GetNowPlayingItem ( session , info . ItemId ) ;
2014-04-06 17:53:23 +00:00
2017-04-17 19:01:16 +00:00
await UpdateNowPlayingItem ( session , info , libraryItem , ! isAutomated ) . ConfigureAwait ( false ) ;
2013-05-09 17:38:02 +00:00
2022-05-24 13:59:13 +00:00
if ( ! string . IsNullOrEmpty ( session . DeviceId ) & & info . PlayMethod ! = PlayMethod . Transcode )
{
ClearTranscodingInfo ( session . DeviceId ) ;
}
2014-01-04 02:35:41 +00:00
var users = GetUsers ( session ) ;
2013-07-09 16:11:16 +00:00
2018-09-12 17:26:21 +00:00
// only update saved user data on actual check-ins, not automated ones
2022-12-05 14:01:13 +00:00
if ( libraryItem is not null & & ! isAutomated )
2013-05-09 17:38:02 +00:00
{
2014-04-16 02:17:48 +00:00
foreach ( var user in users )
{
2017-08-27 00:32:33 +00:00
OnPlaybackProgress ( user , libraryItem , info ) ;
2014-04-16 02:17:48 +00:00
}
2013-05-09 17:38:02 +00:00
}
2020-08-15 19:55:31 +00:00
var eventArgs = new PlaybackProgressEventArgs
{
Item = libraryItem ,
Users = users ,
PlaybackPositionTicks = session . PlayState . PositionTicks ,
MediaSourceId = session . PlayState . MediaSourceId ,
MediaInfo = info . Item ,
DeviceName = session . DeviceName ,
ClientName = session . Client ,
DeviceId = session . DeviceId ,
IsPaused = info . IsPaused ,
PlaySessionId = info . PlaySessionId ,
IsAutomated = isAutomated ,
Session = session
} ;
await _eventManager . PublishAsync ( eventArgs ) . ConfigureAwait ( false ) ;
PlaybackProgress ? . Invoke ( this , eventArgs ) ;
2014-10-23 04:26:01 +00:00
2017-04-17 20:33:07 +00:00
if ( ! isAutomated )
{
2019-02-05 08:49:46 +00:00
session . StartAutomaticProgress ( info ) ;
2017-04-17 20:33:07 +00:00
}
2023-05-01 14:24:15 +00:00
StartCheckTimers ( ) ;
2013-05-09 17:38:02 +00:00
}
2017-08-27 00:32:33 +00:00
private void OnPlaybackProgress ( User user , BaseItem item , PlaybackProgressInfo info )
2014-01-04 02:35:41 +00:00
{
2018-09-12 17:26:21 +00:00
var data = _userDataManager . GetUserData ( user , item ) ;
2014-01-04 02:35:41 +00:00
2016-02-20 23:06:57 +00:00
var positionTicks = info . PositionTicks ;
2018-09-12 17:26:21 +00:00
var changed = false ;
2014-01-04 02:35:41 +00:00
if ( positionTicks . HasValue )
{
2016-08-25 00:12:15 +00:00
_userDataManager . UpdatePlayState ( item , data , positionTicks . Value ) ;
2018-09-12 17:26:21 +00:00
changed = true ;
}
2014-01-04 02:35:41 +00:00
2018-09-12 17:26:21 +00:00
var tracksChanged = UpdatePlaybackSettings ( user , info , data ) ;
if ( ! tracksChanged )
{
changed = true ;
}
2016-02-20 23:06:57 +00:00
2018-09-12 17:26:21 +00:00
if ( changed )
{
_userDataManager . SaveUserData ( user , item , data , UserDataSaveReason . PlaybackProgress , CancellationToken . None ) ;
2014-01-04 02:35:41 +00:00
}
}
2019-01-06 20:50:43 +00:00
private static bool UpdatePlaybackSettings ( User user , PlaybackProgressInfo info , UserItemData data )
2016-02-20 23:06:57 +00:00
{
2018-09-12 17:26:21 +00:00
var changed = false ;
2020-05-13 02:10:35 +00:00
if ( user . RememberAudioSelections )
2016-02-21 21:15:36 +00:00
{
2018-09-12 17:26:21 +00:00
if ( data . AudioStreamIndex ! = info . AudioStreamIndex )
{
data . AudioStreamIndex = info . AudioStreamIndex ;
changed = true ;
}
2016-02-21 21:15:36 +00:00
}
else
{
2018-09-12 17:26:21 +00:00
if ( data . AudioStreamIndex . HasValue )
{
data . AudioStreamIndex = null ;
changed = true ;
}
2016-02-21 21:15:36 +00:00
}
2020-05-13 02:10:35 +00:00
if ( user . RememberSubtitleSelections )
2016-02-21 21:15:36 +00:00
{
2018-09-12 17:26:21 +00:00
if ( data . SubtitleStreamIndex ! = info . SubtitleStreamIndex )
{
data . SubtitleStreamIndex = info . SubtitleStreamIndex ;
changed = true ;
}
2016-02-21 21:15:36 +00:00
}
else
{
2018-09-12 17:26:21 +00:00
if ( data . SubtitleStreamIndex . HasValue )
{
data . SubtitleStreamIndex = null ;
changed = true ;
}
2016-02-21 21:15:36 +00:00
}
2018-09-12 17:26:21 +00:00
return changed ;
2016-02-20 23:06:57 +00:00
}
2013-05-09 17:38:02 +00:00
/// <summary>
2020-06-15 22:37:52 +00:00
/// Used to report that playback has ended for an item.
2013-05-09 17:38:02 +00:00
/// </summary>
2013-09-30 15:05:07 +00:00
/// <param name="info">The info.</param>
2013-05-09 17:38:02 +00:00
/// <returns>Task.</returns>
2020-07-24 14:37:54 +00:00
/// <exception cref="ArgumentNullException"><c>info</c> is <c>null</c>.</exception>
/// <exception cref="ArgumentOutOfRangeException"><c>info.PositionTicks</c> is <c>null</c> or negative.</exception>
2014-04-16 02:17:48 +00:00
public async Task OnPlaybackStopped ( PlaybackStopInfo info )
2013-05-09 17:38:02 +00:00
{
2018-09-12 17:26:21 +00:00
CheckDisposed ( ) ;
2022-10-06 18:21:23 +00:00
ArgumentNullException . ThrowIfNull ( info ) ;
2013-05-09 17:38:02 +00:00
2013-09-30 15:05:07 +00:00
if ( info . PositionTicks . HasValue & & info . PositionTicks . Value < 0 )
2013-07-18 12:00:57 +00:00
{
2019-01-13 19:22:24 +00:00
throw new ArgumentOutOfRangeException ( nameof ( info ) , "The PlaybackStopInfo's PositionTicks was negative." ) ;
2013-07-18 12:00:57 +00:00
}
2013-09-24 15:42:30 +00:00
2014-04-16 02:17:48 +00:00
var session = GetSession ( info . SessionId ) ;
2017-04-17 18:40:42 +00:00
session . StopAutomaticProgress ( ) ;
2024-01-17 15:51:39 +00:00
var libraryItem = info . ItemId . IsEmpty ( )
2014-04-16 02:17:48 +00:00
? null
2016-11-12 04:02:22 +00:00
: GetNowPlayingItem ( session , info . ItemId ) ;
2013-07-09 16:11:16 +00:00
2014-04-16 02:17:48 +00:00
// Normalize
2018-09-12 17:26:21 +00:00
if ( string . IsNullOrEmpty ( info . MediaSourceId ) )
2014-04-16 02:17:48 +00:00
{
2019-02-28 22:22:57 +00:00
info . MediaSourceId = info . ItemId . ToString ( "N" , CultureInfo . InvariantCulture ) ;
2014-04-16 02:17:48 +00:00
}
2013-05-09 17:38:02 +00:00
2024-01-17 15:51:39 +00:00
if ( ! info . ItemId . IsEmpty ( ) & & info . Item is null & & libraryItem is not null )
2014-07-05 05:21:13 +00:00
{
var current = session . NowPlayingItem ;
2022-12-05 14:00:20 +00:00
if ( current is null | | ! info . ItemId . Equals ( current . Id ) )
2014-07-05 05:21:13 +00:00
{
2015-04-02 16:24:59 +00:00
MediaSourceInfo mediaSource = null ;
2020-01-16 23:19:58 +00:00
if ( libraryItem is IHasMediaSources )
2015-04-02 16:24:59 +00:00
{
2018-09-12 17:26:21 +00:00
mediaSource = await GetMediaSource ( libraryItem , info . MediaSourceId , info . LiveStreamId ) . ConfigureAwait ( false ) ;
2015-04-02 16:24:59 +00:00
}
2015-03-29 16:45:16 +00:00
2017-06-09 19:26:54 +00:00
info . Item = GetItemInfo ( libraryItem , mediaSource ) ;
2014-07-05 05:21:13 +00:00
}
else
{
info . Item = current ;
}
}
2022-12-05 14:01:13 +00:00
if ( info . Item is not null )
2016-12-01 05:46:32 +00:00
{
var msString = info . PositionTicks . HasValue ? ( info . PositionTicks . Value / 10000 ) . ToString ( CultureInfo . InvariantCulture ) : "unknown" ;
2019-03-13 21:32:52 +00:00
_logger . LogInformation (
"Playback stopped reported by app {0} {1} playing {2}. Stopped at {3} ms" ,
2016-12-01 05:46:32 +00:00
session . Client ,
session . ApplicationVersion ,
info . Item . Name ,
msString ) ;
}
2022-12-05 14:01:13 +00:00
if ( info . NowPlayingQueue is not null )
2018-09-12 17:26:21 +00:00
{
session . NowPlayingQueue = info . NowPlayingQueue ;
}
session . PlaylistItemId = info . PlaylistItemId ;
2014-04-16 02:17:48 +00:00
RemoveNowPlayingItem ( session ) ;
2013-05-09 17:38:02 +00:00
2014-01-04 02:35:41 +00:00
var users = GetUsers ( session ) ;
var playedToCompletion = false ;
2014-04-16 02:17:48 +00:00
2022-12-05 14:01:13 +00:00
if ( libraryItem is not null )
2014-01-04 02:35:41 +00:00
{
2014-04-16 02:17:48 +00:00
foreach ( var user in users )
{
2018-09-12 17:26:21 +00:00
playedToCompletion = OnPlaybackStopped ( user , libraryItem , info . PositionTicks , info . Failed ) ;
2014-04-16 02:17:48 +00:00
}
}
2014-03-22 03:35:03 +00:00
2018-09-12 17:26:21 +00:00
if ( ! string . IsNullOrEmpty ( info . LiveStreamId ) )
2015-03-29 18:16:40 +00:00
{
try
{
2016-09-29 12:55:49 +00:00
await _mediaSourceManager . CloseLiveStream ( info . LiveStreamId ) . ConfigureAwait ( false ) ;
2015-03-29 18:16:40 +00:00
}
catch ( Exception ex )
{
2023-04-06 17:05:05 +00:00
_logger . LogError ( ex , "Error closing live stream" ) ;
2015-03-29 18:16:40 +00:00
}
}
2020-08-15 19:55:31 +00:00
var eventArgs = new PlaybackStopEventArgs
{
Item = libraryItem ,
Users = users ,
PlaybackPositionTicks = info . PositionTicks ,
PlayedToCompletion = playedToCompletion ,
MediaSourceId = info . MediaSourceId ,
MediaInfo = info . Item ,
DeviceName = session . DeviceName ,
ClientName = session . Client ,
DeviceId = session . DeviceId ,
2022-04-16 12:03:05 +00:00
Session = session ,
PlaySessionId = info . PlaySessionId
2020-08-15 19:55:31 +00:00
} ;
await _eventManager . PublishAsync ( eventArgs ) . ConfigureAwait ( false ) ;
EventHelper . QueueEventIfNotNull ( PlaybackStopped , this , eventArgs , _logger ) ;
2014-01-04 02:35:41 +00:00
}
2013-09-24 15:42:30 +00:00
2018-09-12 17:26:21 +00:00
private bool OnPlaybackStopped ( User user , BaseItem item , long? positionTicks , bool playbackFailed )
2014-01-04 02:35:41 +00:00
{
2023-10-03 14:31:55 +00:00
if ( playbackFailed )
2013-05-09 17:38:02 +00:00
{
2023-10-03 14:31:55 +00:00
return false ;
}
2013-05-09 17:38:02 +00:00
2023-10-03 14:31:55 +00:00
var data = _userDataManager . GetUserData ( user , item ) ;
bool playedToCompletion ;
if ( positionTicks . HasValue )
{
playedToCompletion = _userDataManager . UpdatePlayState ( item , data , positionTicks . Value ) ;
2016-03-07 04:56:45 +00:00
}
2023-10-03 14:31:55 +00:00
else
{
// If the client isn't able to report this, then we'll just have to make an assumption
data . PlayCount + + ;
data . Played = item . SupportsPlayedStatus ;
data . PlaybackPositionTicks = 0 ;
playedToCompletion = true ;
}
_userDataManager . SaveUserData ( user , item , data , UserDataSaveReason . PlaybackFinished , CancellationToken . None ) ;
2013-12-30 17:18:18 +00:00
2014-01-04 02:35:41 +00:00
return playedToCompletion ;
2013-05-09 17:38:02 +00:00
}
2014-03-29 18:20:42 +00:00
2013-09-24 15:42:30 +00:00
/// <summary>
2014-01-04 02:35:41 +00:00
/// Gets the session.
2013-09-24 15:42:30 +00:00
/// </summary>
2014-01-04 02:35:41 +00:00
/// <param name="sessionId">The session identifier.</param>
2014-06-01 04:11:04 +00:00
/// <param name="throwOnMissing">if set to <c>true</c> [throw on missing].</param>
2013-09-24 15:42:30 +00:00
/// <returns>SessionInfo.</returns>
2020-01-16 23:19:58 +00:00
/// <exception cref="ResourceNotFoundException">
/// No session with an Id equal to <c>sessionId</c> was found
/// and <c>throwOnMissing</c> is <c>true</c>.
/// </exception>
2014-06-01 04:11:04 +00:00
private SessionInfo GetSession ( string sessionId , bool throwOnMissing = true )
2013-09-24 15:42:30 +00:00
{
2019-03-13 21:32:52 +00:00
var session = Sessions . FirstOrDefault ( i = > string . Equals ( i . Id , sessionId , StringComparison . Ordinal ) ) ;
2022-12-05 14:00:20 +00:00
if ( session is null & & throwOnMissing )
2013-09-24 15:42:30 +00:00
{
2020-01-16 23:19:58 +00:00
throw new ResourceNotFoundException (
string . Format ( CultureInfo . InvariantCulture , "Session {0} not found." , sessionId ) ) ;
2013-09-24 15:42:30 +00:00
}
2014-01-04 02:35:41 +00:00
return session ;
}
2015-03-02 05:16:29 +00:00
private SessionInfo GetSessionToRemoteControl ( string sessionId )
{
// Accept either device id or session id
2019-03-13 21:32:52 +00:00
var session = Sessions . FirstOrDefault ( i = > string . Equals ( i . Id , sessionId , StringComparison . Ordinal ) ) ;
2015-03-02 05:16:29 +00:00
2022-12-05 14:00:20 +00:00
if ( session is null )
2015-03-02 05:16:29 +00:00
{
2020-01-16 23:19:58 +00:00
throw new ResourceNotFoundException (
string . Format ( CultureInfo . InvariantCulture , "Session {0} not found." , sessionId ) ) ;
2015-03-02 05:16:29 +00:00
}
return session ;
}
2020-01-16 23:19:58 +00:00
/// <inheritdoc />
2014-04-16 02:17:48 +00:00
public Task SendMessageCommand ( string controllingSessionId , string sessionId , MessageCommand command , CancellationToken cancellationToken )
2013-09-24 15:42:30 +00:00
{
2018-09-12 17:26:21 +00:00
CheckDisposed ( ) ;
2014-05-08 20:09:53 +00:00
var generalCommand = new GeneralCommand
{
2020-09-21 14:53:00 +00:00
Name = GeneralCommandType . DisplayMessage
2014-05-08 20:09:53 +00:00
} ;
2013-09-24 15:42:30 +00:00
2014-05-08 20:09:53 +00:00
generalCommand . Arguments [ "Header" ] = command . Header ;
generalCommand . Arguments [ "Text" ] = command . Text ;
2014-03-29 18:20:42 +00:00
2014-05-08 20:09:53 +00:00
if ( command . TimeoutMs . HasValue )
{
generalCommand . Arguments [ "TimeoutMs" ] = command . TimeoutMs . Value . ToString ( CultureInfo . InvariantCulture ) ;
}
return SendGeneralCommand ( controllingSessionId , sessionId , generalCommand , cancellationToken ) ;
2013-09-24 15:42:30 +00:00
}
2013-09-24 19:54:42 +00:00
2020-01-16 23:19:58 +00:00
/// <inheritdoc />
2014-04-16 02:17:48 +00:00
public Task SendGeneralCommand ( string controllingSessionId , string sessionId , GeneralCommand command , CancellationToken cancellationToken )
2013-09-24 19:54:42 +00:00
{
2018-09-12 17:26:21 +00:00
CheckDisposed ( ) ;
2015-03-02 05:16:29 +00:00
var session = GetSessionToRemoteControl ( sessionId ) ;
2013-09-24 19:54:42 +00:00
2018-09-12 17:26:21 +00:00
if ( ! string . IsNullOrEmpty ( controllingSessionId ) )
2017-02-20 07:04:03 +00:00
{
var controllingSession = GetSession ( controllingSessionId ) ;
AssertCanControl ( session , controllingSession ) ;
}
2014-03-29 18:20:42 +00:00
2020-09-25 07:25:59 +00:00
return SendMessageToSession ( session , SessionMessageType . GeneralCommand , command , cancellationToken ) ;
2018-09-12 17:26:21 +00:00
}
2020-09-25 07:25:59 +00:00
private static async Task SendMessageToSession < T > ( SessionInfo session , SessionMessageType name , T data , CancellationToken cancellationToken )
2018-09-12 17:26:21 +00:00
{
2019-12-17 22:15:02 +00:00
var controllers = session . SessionControllers ;
var messageId = Guid . NewGuid ( ) ;
2018-09-12 17:26:21 +00:00
foreach ( var controller in controllers )
{
2019-12-17 22:15:02 +00:00
await controller . SendMessage ( name , messageId , data , cancellationToken ) . ConfigureAwait ( false ) ;
2018-09-12 17:26:21 +00:00
}
2013-09-24 19:54:42 +00:00
}
2020-09-25 07:25:59 +00:00
private static Task SendMessageToSessions < T > ( IEnumerable < SessionInfo > sessions , SessionMessageType name , T data , CancellationToken cancellationToken )
2019-03-27 15:07:08 +00:00
{
IEnumerable < Task > GetTasks ( )
{
2019-12-17 22:15:02 +00:00
var messageId = Guid . NewGuid ( ) ;
2019-03-27 15:07:08 +00:00
foreach ( var session in sessions )
{
var controllers = session . SessionControllers ;
foreach ( var controller in controllers )
{
2019-12-17 22:15:02 +00:00
yield return controller . SendMessage ( name , messageId , data , cancellationToken ) ;
2019-03-27 15:07:08 +00:00
}
}
}
return Task . WhenAll ( GetTasks ( ) ) ;
}
2020-01-16 23:19:58 +00:00
/// <inheritdoc />
2016-06-19 06:18:29 +00:00
public async Task SendPlayCommand ( string controllingSessionId , string sessionId , PlayRequest command , CancellationToken cancellationToken )
2013-09-24 19:54:42 +00:00
{
2018-09-12 17:26:21 +00:00
CheckDisposed ( ) ;
2015-03-02 05:16:29 +00:00
var session = GetSessionToRemoteControl ( sessionId ) ;
2013-09-24 19:54:42 +00:00
2024-01-17 15:51:39 +00:00
var user = session . UserId . IsEmpty ( ) ? null : _userManager . GetUserById ( session . UserId ) ;
2014-03-29 18:20:42 +00:00
2014-03-30 16:49:40 +00:00
List < BaseItem > items ;
if ( command . PlayCommand = = PlayCommand . PlayInstantMix )
{
items = command . ItemIds . SelectMany ( i = > TranslateItemForInstantMix ( i , user ) )
. ToList ( ) ;
command . PlayCommand = PlayCommand . PlayNow ;
}
else
{
2016-06-19 06:18:29 +00:00
var list = new List < BaseItem > ( ) ;
foreach ( var itemId in command . ItemIds )
{
2017-05-26 06:48:54 +00:00
var subItems = TranslateItemForPlayback ( itemId , user ) ;
2016-06-19 06:18:29 +00:00
list . AddRange ( subItems ) ;
}
2016-12-01 05:46:32 +00:00
2017-11-09 20:58:09 +00:00
items = list ;
2014-03-30 16:49:40 +00:00
}
2014-02-21 05:35:56 +00:00
2014-03-29 18:20:42 +00:00
if ( command . PlayCommand = = PlayCommand . PlayShuffle )
2014-01-23 02:19:04 +00:00
{
2019-10-20 14:08:40 +00:00
items . Shuffle ( ) ;
2014-03-29 18:20:42 +00:00
command . PlayCommand = PlayCommand . PlayNow ;
}
2018-12-28 15:48:26 +00:00
command . ItemIds = items . Select ( i = > i . Id ) . ToArray ( ) ;
2014-01-23 02:19:04 +00:00
2022-12-05 14:01:13 +00:00
if ( user is not null )
2014-03-29 18:20:42 +00:00
{
2014-02-21 05:35:56 +00:00
if ( items . Any ( i = > i . GetPlayAccess ( user ) ! = PlayAccess . Full ) )
2014-01-23 02:19:04 +00:00
{
2020-01-16 23:19:58 +00:00
throw new ArgumentException (
2020-05-13 02:10:35 +00:00
string . Format ( CultureInfo . InvariantCulture , "{0} is not allowed to play media." , user . Username ) ) ;
2014-01-23 02:19:04 +00:00
}
}
2022-12-05 14:01:13 +00:00
if ( user is not null
2019-10-20 14:08:40 +00:00
& & command . ItemIds . Length = = 1
2020-05-13 02:10:35 +00:00
& & user . EnableNextEpisodeAutoPlay
2019-10-20 14:08:40 +00:00
& & _libraryManager . GetItemById ( command . ItemIds [ 0 ] ) is Episode episode )
2016-02-26 14:50:58 +00:00
{
2019-10-20 14:08:40 +00:00
var series = episode . Series ;
2022-12-05 14:01:13 +00:00
if ( series is not null )
2016-02-26 14:50:58 +00:00
{
2019-10-20 14:08:40 +00:00
var episodes = series . GetEpisodes (
user ,
new DtoOptions ( false )
{
EnableImages = false
2024-06-01 22:40:59 +00:00
} ,
user . DisplayMissingEpisodes )
2019-10-20 14:08:40 +00:00
. Where ( i = > ! i . IsVirtualItem )
2022-02-21 13:15:09 +00:00
. SkipWhile ( i = > ! i . Id . Equals ( episode . Id ) )
2019-10-20 14:08:40 +00:00
. ToList ( ) ;
if ( episodes . Count > 0 )
2016-02-26 14:50:58 +00:00
{
2019-10-20 14:08:40 +00:00
command . ItemIds = episodes . Select ( i = > i . Id ) . ToArray ( ) ;
2016-02-26 14:50:58 +00:00
}
}
}
2018-09-12 17:26:21 +00:00
if ( ! string . IsNullOrEmpty ( controllingSessionId ) )
2014-03-16 04:23:58 +00:00
{
2017-02-20 07:04:03 +00:00
var controllingSession = GetSession ( controllingSessionId ) ;
AssertCanControl ( session , controllingSession ) ;
2024-01-17 15:51:39 +00:00
if ( ! controllingSession . UserId . IsEmpty ( ) )
2017-02-20 07:04:03 +00:00
{
2018-09-12 17:26:21 +00:00
command . ControllingUserId = controllingSession . UserId ;
2017-02-20 07:04:03 +00:00
}
2014-03-16 04:23:58 +00:00
}
2020-09-25 07:25:59 +00:00
await SendMessageToSession ( session , SessionMessageType . Play , command , cancellationToken ) . ConfigureAwait ( false ) ;
2013-09-24 19:54:42 +00:00
}
2020-04-01 15:52:42 +00:00
/// <inheritdoc />
2021-03-28 11:25:40 +00:00
public async Task SendSyncPlayCommand ( string sessionId , SendCommand command , CancellationToken cancellationToken )
2020-04-01 15:52:42 +00:00
{
CheckDisposed ( ) ;
2021-03-28 11:25:40 +00:00
var session = GetSession ( sessionId ) ;
2020-09-25 07:25:59 +00:00
await SendMessageToSession ( session , SessionMessageType . SyncPlayCommand , command , cancellationToken ) . ConfigureAwait ( false ) ;
2020-04-01 15:52:42 +00:00
}
/// <inheritdoc />
2021-03-28 11:25:40 +00:00
public async Task SendSyncPlayGroupUpdate < T > ( string sessionId , GroupUpdate < T > command , CancellationToken cancellationToken )
2020-04-01 15:52:42 +00:00
{
CheckDisposed ( ) ;
2021-03-28 11:25:40 +00:00
var session = GetSession ( sessionId ) ;
2020-09-25 07:25:59 +00:00
await SendMessageToSession ( session , SessionMessageType . SyncPlayGroupUpdate , command , cancellationToken ) . ConfigureAwait ( false ) ;
2020-04-01 15:52:42 +00:00
}
2019-02-26 19:47:23 +00:00
private IEnumerable < BaseItem > TranslateItemForPlayback ( Guid id , User user )
2014-03-29 18:20:42 +00:00
{
2015-07-05 18:34:52 +00:00
var item = _libraryManager . GetItemById ( id ) ;
2022-12-05 14:00:20 +00:00
if ( item is null )
2015-07-05 18:34:52 +00:00
{
2022-08-15 10:48:34 +00:00
_logger . LogError ( "A non-existent item Id {0} was passed into TranslateItemForPlayback" , id ) ;
2019-10-20 14:08:40 +00:00
return Array . Empty < BaseItem > ( ) ;
2015-07-05 18:34:52 +00:00
}
2014-03-29 18:20:42 +00:00
2019-03-13 21:32:52 +00:00
if ( item is IItemByName byName )
2015-01-26 16:47:15 +00:00
{
2018-09-12 17:26:21 +00:00
return byName . GetTaggedItems ( new InternalItemsQuery ( user )
2016-05-07 17:47:41 +00:00
{
IsFolder = false ,
2017-05-21 07:25:49 +00:00
Recursive = true ,
DtoOptions = new DtoOptions ( false )
{
2017-05-24 19:12:55 +00:00
EnableImages = false ,
2020-05-13 02:10:35 +00:00
Fields = new [ ]
2017-05-24 19:12:55 +00:00
{
ItemFields . SortName
}
2017-11-09 20:58:09 +00:00
} ,
2018-09-12 17:26:21 +00:00
IsVirtualItem = false ,
2019-10-20 14:08:40 +00:00
OrderBy = new [ ] { ( ItemSortBy . SortName , SortOrder . Ascending ) }
2016-05-07 17:47:41 +00:00
} ) ;
2015-01-26 16:47:15 +00:00
}
2015-03-09 19:40:03 +00:00
2014-03-29 18:20:42 +00:00
if ( item . IsFolder )
{
var folder = ( Folder ) item ;
2018-09-12 17:26:21 +00:00
return folder . GetItemList ( new InternalItemsQuery ( user )
2016-05-07 17:47:41 +00:00
{
Recursive = true ,
2017-05-21 07:25:49 +00:00
IsFolder = false ,
DtoOptions = new DtoOptions ( false )
{
2017-05-24 19:12:55 +00:00
EnableImages = false ,
2017-08-19 19:43:35 +00:00
Fields = new ItemFields [ ]
2017-05-24 19:12:55 +00:00
{
ItemFields . SortName
}
2017-11-09 20:58:09 +00:00
} ,
2018-09-12 17:26:21 +00:00
IsVirtualItem = false ,
2019-10-20 14:08:40 +00:00
OrderBy = new [ ] { ( ItemSortBy . SortName , SortOrder . Ascending ) }
2017-05-26 06:48:54 +00:00
} ) ;
2014-03-29 18:20:42 +00:00
}
2019-10-20 14:08:40 +00:00
return new [ ] { item } ;
2014-03-29 18:20:42 +00:00
}
2024-08-05 14:58:22 +00:00
private List < BaseItem > TranslateItemForInstantMix ( Guid id , User user )
2014-03-30 16:49:40 +00:00
{
2015-01-26 16:47:15 +00:00
var item = _libraryManager . GetItemById ( id ) ;
2014-03-30 16:49:40 +00:00
2022-12-05 14:00:20 +00:00
if ( item is null )
2015-07-05 18:34:52 +00:00
{
2019-03-13 21:32:52 +00:00
_logger . LogError ( "A non-existent item Id {0} was passed into TranslateItemForInstantMix" , id ) ;
2015-07-05 18:34:52 +00:00
return new List < BaseItem > ( ) ;
}
2024-08-05 14:58:22 +00:00
return _musicManager . GetInstantMixFromItem ( item , user , new DtoOptions ( false ) { EnableImages = false } ) . ToList ( ) ;
2014-03-30 16:49:40 +00:00
}
2020-01-16 23:19:58 +00:00
/// <inheritdoc />
2014-04-16 02:17:48 +00:00
public Task SendBrowseCommand ( string controllingSessionId , string sessionId , BrowseRequest command , CancellationToken cancellationToken )
2013-09-24 19:54:42 +00:00
{
2014-04-13 17:27:13 +00:00
var generalCommand = new GeneralCommand
{
2020-09-21 14:53:00 +00:00
Name = GeneralCommandType . DisplayContent ,
2019-03-13 21:32:52 +00:00
Arguments =
{
["ItemId"] = command . ItemId ,
["ItemName"] = command . ItemName ,
2021-12-12 02:31:30 +00:00
["ItemType"] = command . ItemType . ToString ( )
2019-03-13 21:32:52 +00:00
}
2014-04-13 17:27:13 +00:00
} ;
2013-09-24 19:54:42 +00:00
2014-04-13 17:27:13 +00:00
return SendGeneralCommand ( controllingSessionId , sessionId , generalCommand , cancellationToken ) ;
2013-09-24 19:54:42 +00:00
}
2020-01-16 23:19:58 +00:00
/// <inheritdoc />
2014-04-16 02:17:48 +00:00
public Task SendPlaystateCommand ( string controllingSessionId , string sessionId , PlaystateRequest command , CancellationToken cancellationToken )
2013-09-24 19:54:42 +00:00
{
2018-09-12 17:26:21 +00:00
CheckDisposed ( ) ;
2015-03-02 05:16:29 +00:00
var session = GetSessionToRemoteControl ( sessionId ) ;
2013-09-24 19:54:42 +00:00
2018-09-12 17:26:21 +00:00
if ( ! string . IsNullOrEmpty ( controllingSessionId ) )
2014-03-16 04:23:58 +00:00
{
2017-02-20 07:04:03 +00:00
var controllingSession = GetSession ( controllingSessionId ) ;
AssertCanControl ( session , controllingSession ) ;
2024-01-17 15:51:39 +00:00
if ( ! controllingSession . UserId . IsEmpty ( ) )
2017-02-20 07:04:03 +00:00
{
2019-02-28 22:22:57 +00:00
command . ControllingUserId = controllingSession . UserId . ToString ( "N" , CultureInfo . InvariantCulture ) ;
2017-02-20 07:04:03 +00:00
}
2014-03-16 04:23:58 +00:00
}
2021-01-05 15:06:55 +00:00
return SendMessageToSession ( session , SessionMessageType . Playstate , command , cancellationToken ) ;
2013-10-03 01:22:50 +00:00
}
2019-03-27 15:13:36 +00:00
private static void AssertCanControl ( SessionInfo session , SessionInfo controllingSession )
2014-03-16 16:15:10 +00:00
{
2022-10-06 18:21:23 +00:00
ArgumentNullException . ThrowIfNull ( session ) ;
2019-03-27 15:13:36 +00:00
2022-10-06 18:21:23 +00:00
ArgumentNullException . ThrowIfNull ( controllingSession ) ;
2014-03-16 16:15:10 +00:00
}
2013-10-03 18:02:23 +00:00
/// <summary>
/// Sends the restart required message.
/// </summary>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task.</returns>
2019-03-27 15:13:36 +00:00
public Task SendRestartRequiredNotification ( CancellationToken cancellationToken )
2013-10-03 18:02:23 +00:00
{
2018-09-12 17:26:21 +00:00
CheckDisposed ( ) ;
2020-09-25 07:25:59 +00:00
return SendMessageToSessions ( Sessions , SessionMessageType . RestartRequired , string . Empty , cancellationToken ) ;
2013-10-03 18:02:23 +00:00
}
2014-01-04 02:35:41 +00:00
/// <summary>
/// Adds the additional user.
/// </summary>
/// <param name="sessionId">The session identifier.</param>
/// <param name="userId">The user identifier.</param>
2019-01-13 20:37:13 +00:00
/// <exception cref="UnauthorizedAccessException">Cannot modify additional users without authenticating first.</exception>
/// <exception cref="ArgumentException">The requested user is already the primary user of the session.</exception>
2018-09-12 17:26:21 +00:00
public void AddAdditionalUser ( string sessionId , Guid userId )
2014-01-04 02:35:41 +00:00
{
2018-09-12 17:26:21 +00:00
CheckDisposed ( ) ;
2014-01-04 02:35:41 +00:00
var session = GetSession ( sessionId ) ;
2022-02-21 13:15:09 +00:00
if ( session . UserId . Equals ( userId ) )
2014-01-04 02:35:41 +00:00
{
throw new ArgumentException ( "The requested user is already the primary user of the session." ) ;
}
2022-02-21 13:15:09 +00:00
if ( session . AdditionalUsers . All ( i = > ! i . UserId . Equals ( userId ) ) )
2014-01-04 02:35:41 +00:00
{
var user = _userManager . GetUserById ( userId ) ;
2024-04-30 19:32:59 +00:00
var newUser = new SessionUserInfo
2014-01-04 02:35:41 +00:00
{
2015-05-29 23:51:33 +00:00
UserId = userId ,
2020-05-13 02:10:35 +00:00
UserName = user . Username
2024-04-30 19:32:59 +00:00
} ;
2017-08-19 19:43:35 +00:00
2024-04-30 19:32:59 +00:00
session . AdditionalUsers = [ . . session . AdditionalUsers , newUser ] ;
2014-01-04 02:35:41 +00:00
}
}
/// <summary>
/// Removes the additional user.
/// </summary>
/// <param name="sessionId">The session identifier.</param>
/// <param name="userId">The user identifier.</param>
2019-01-13 20:37:13 +00:00
/// <exception cref="UnauthorizedAccessException">Cannot modify additional users without authenticating first.</exception>
/// <exception cref="ArgumentException">The requested user is already the primary user of the session.</exception>
2018-09-12 17:26:21 +00:00
public void RemoveAdditionalUser ( string sessionId , Guid userId )
2014-01-04 02:35:41 +00:00
{
2018-09-12 17:26:21 +00:00
CheckDisposed ( ) ;
2014-01-04 02:35:41 +00:00
var session = GetSession ( sessionId ) ;
2018-09-12 17:26:21 +00:00
if ( session . UserId . Equals ( userId ) )
2014-01-04 02:35:41 +00:00
{
throw new ArgumentException ( "The requested user is already the primary user of the session." ) ;
}
2018-09-12 17:26:21 +00:00
var user = session . AdditionalUsers . FirstOrDefault ( i = > i . UserId . Equals ( userId ) ) ;
2014-01-04 02:35:41 +00:00
2022-12-05 14:01:13 +00:00
if ( user is not null )
2014-01-04 02:35:41 +00:00
{
2017-08-19 19:43:35 +00:00
var list = session . AdditionalUsers . ToList ( ) ;
list . Remove ( user ) ;
2018-12-28 15:48:26 +00:00
session . AdditionalUsers = list . ToArray ( ) ;
2014-01-04 02:35:41 +00:00
}
}
2014-01-11 23:07:56 +00:00
/// <summary>
/// Authenticates the new session.
/// </summary>
2021-06-24 03:07:08 +00:00
/// <param name="request">The authenticationrequest.</param>
/// <returns>The authentication result.</returns>
2016-02-21 06:25:25 +00:00
public Task < AuthenticationResult > AuthenticateNewSession ( AuthenticationRequest request )
{
return AuthenticateNewSessionInternal ( request , true ) ;
}
2021-06-24 03:07:08 +00:00
/// <summary>
/// Directly authenticates the session without enforcing password.
/// </summary>
/// <param name="request">The authentication request.</param>
/// <returns>The authentication result.</returns>
public Task < AuthenticationResult > AuthenticateDirect ( AuthenticationRequest request )
2020-04-15 19:28:42 +00:00
{
return AuthenticateNewSessionInternal ( request , false ) ;
2016-02-21 06:25:25 +00:00
}
2023-10-30 21:31:13 +00:00
internal async Task < AuthenticationResult > AuthenticateNewSessionInternal ( AuthenticationRequest request , bool enforcePassword )
2014-01-11 23:07:56 +00:00
{
2018-09-12 17:26:21 +00:00
CheckDisposed ( ) ;
2023-10-30 21:31:13 +00:00
ArgumentException . ThrowIfNullOrEmpty ( request . App ) ;
ArgumentException . ThrowIfNullOrEmpty ( request . DeviceId ) ;
ArgumentException . ThrowIfNullOrEmpty ( request . DeviceName ) ;
ArgumentException . ThrowIfNullOrEmpty ( request . AppVersion ) ;
2016-09-14 21:34:19 +00:00
User user = null ;
2024-01-17 15:51:39 +00:00
if ( ! request . UserId . IsEmpty ( ) )
2016-09-14 21:34:19 +00:00
{
2019-06-09 20:08:01 +00:00
user = _userManager . GetUserById ( request . UserId ) ;
2016-09-14 21:34:19 +00:00
}
2021-05-05 11:51:14 +00:00
user ? ? = _userManager . GetUserByName ( request . Username ) ;
2014-10-16 03:26:39 +00:00
2020-03-29 20:57:13 +00:00
if ( enforcePassword )
{
user = await _userManager . AuthenticateUser (
request . Username ,
request . Password ,
request . RemoteEndPoint ,
true ) . ConfigureAwait ( false ) ;
}
2022-12-05 14:00:20 +00:00
if ( user is null )
2014-12-29 20:18:48 +00:00
{
2023-07-29 11:50:55 +00:00
await _eventManager . PublishAsync ( new AuthenticationRequestEventArgs ( request ) ) . ConfigureAwait ( false ) ;
2020-04-13 17:17:46 +00:00
throw new AuthenticationException ( "Invalid username or password entered." ) ;
2014-12-29 20:18:48 +00:00
}
2020-02-01 15:09:18 +00:00
if ( ! string . IsNullOrEmpty ( request . DeviceId )
& & ! _deviceManager . CanAccessDevice ( user , request . DeviceId ) )
2019-12-13 14:27:12 +00:00
{
2020-02-01 15:09:18 +00:00
throw new SecurityException ( "User is not allowed access from this device." ) ;
2019-12-13 14:27:12 +00:00
}
2020-10-04 20:57:48 +00:00
int sessionsCount = Sessions . Count ( i = > i . UserId . Equals ( user . Id ) ) ;
2020-10-04 15:50:00 +00:00
int maxActiveSessions = user . MaxActiveSessions ;
2020-10-04 17:30:21 +00:00
_logger . LogInformation ( "Current/Max sessions for user {User}: {Sessions}/{Max}" , user . Username , sessionsCount , maxActiveSessions ) ;
2020-10-04 18:06:20 +00:00
if ( maxActiveSessions > = 1 & & sessionsCount > = maxActiveSessions )
2020-10-04 15:50:00 +00:00
{
2020-10-04 17:29:18 +00:00
throw new SecurityException ( "User is at their maximum number of sessions." ) ;
2020-10-04 15:50:00 +00:00
}
2021-05-21 03:56:59 +00:00
var token = await GetAuthorizationToken ( user , request . DeviceId , request . App , request . AppVersion , request . DeviceName ) . ConfigureAwait ( false ) ;
2014-07-08 01:41:03 +00:00
2021-04-10 20:57:25 +00:00
var session = await LogSessionActivity (
2019-03-13 21:32:52 +00:00
request . App ,
2014-08-10 22:13:17 +00:00
request . AppVersion ,
request . DeviceId ,
request . DeviceName ,
request . RemoteEndPoint ,
2021-04-10 20:57:25 +00:00
user ) . ConfigureAwait ( false ) ;
2014-10-11 20:38:13 +00:00
2018-09-12 17:26:21 +00:00
var returnResult = new AuthenticationResult
2014-07-02 18:34:08 +00:00
{
2014-08-15 16:35:41 +00:00
User = _userManager . GetUserDto ( user , request . RemoteEndPoint ) ,
2018-09-12 17:26:21 +00:00
SessionInfo = session ,
2014-07-27 22:01:29 +00:00
AccessToken = token ,
2014-09-06 17:46:09 +00:00
ServerId = _appHost . SystemId
2014-07-02 18:34:08 +00:00
} ;
2016-05-07 17:47:41 +00:00
2023-07-29 11:50:55 +00:00
await _eventManager . PublishAsync ( new AuthenticationResultEventArgs ( returnResult ) ) . ConfigureAwait ( false ) ;
2018-09-12 17:26:21 +00:00
return returnResult ;
}
2016-05-07 17:47:41 +00:00
2023-10-30 21:31:13 +00:00
internal async Task < string > GetAuthorizationToken ( User user , string deviceId , string app , string appVersion , string deviceName )
2014-07-08 01:41:03 +00:00
{
2023-10-30 21:31:13 +00:00
// This should be validated above, but if it isn't don't delete all tokens.
ArgumentException . ThrowIfNullOrEmpty ( deviceId ) ;
2024-08-05 14:58:22 +00:00
var existing = _deviceManager . GetDevices (
2021-05-21 03:56:59 +00:00
new DeviceQuery
2020-01-16 23:19:58 +00:00
{
DeviceId = deviceId ,
2023-09-11 14:49:01 +00:00
UserId = user . Id
2024-08-05 14:58:22 +00:00
} ) . Items ;
2018-09-12 17:26:21 +00:00
2023-09-11 14:49:01 +00:00
foreach ( var auth in existing )
2014-07-08 01:41:03 +00:00
{
2023-09-11 14:49:01 +00:00
try
{
// Logout any existing sessions for the user on this device
await Logout ( auth ) . ConfigureAwait ( false ) ;
}
catch ( Exception ex )
{
_logger . LogError ( ex , "Error while logging out existing session." ) ;
}
2014-07-08 01:41:03 +00:00
}
2018-12-13 13:18:25 +00:00
_logger . LogInformation ( "Creating new access token for user {0}" , user . Id ) ;
2021-05-21 03:56:59 +00:00
var device = await _deviceManager . CreateDevice ( new Device ( user . Id , app , appVersion , deviceName , deviceId ) ) . ConfigureAwait ( false ) ;
2014-07-08 01:41:03 +00:00
2021-05-21 03:56:59 +00:00
return device . AccessToken ;
2014-07-08 01:41:03 +00:00
}
2020-01-16 23:19:58 +00:00
/// <inheritdoc />
2021-05-21 03:56:59 +00:00
public async Task Logout ( string accessToken )
2014-07-08 01:41:03 +00:00
{
2018-09-12 17:26:21 +00:00
CheckDisposed ( ) ;
2022-10-13 17:08:00 +00:00
ArgumentException . ThrowIfNullOrEmpty ( accessToken ) ;
2014-07-08 01:41:03 +00:00
2024-08-05 14:58:22 +00:00
var existing = _deviceManager . GetDevices (
2021-05-21 03:56:59 +00:00
new DeviceQuery
2020-01-16 23:19:58 +00:00
{
Limit = 1 ,
AccessToken = accessToken
2024-08-05 14:58:22 +00:00
} ) . Items ;
2014-07-08 01:41:03 +00:00
2020-01-16 23:19:58 +00:00
if ( existing . Count > 0 )
2014-07-08 01:41:03 +00:00
{
2021-05-21 03:56:59 +00:00
await Logout ( existing [ 0 ] ) . ConfigureAwait ( false ) ;
2018-09-12 17:26:21 +00:00
}
}
2014-07-08 01:41:03 +00:00
2020-01-16 23:19:58 +00:00
/// <inheritdoc />
2021-09-03 16:46:34 +00:00
public async Task Logout ( Device device )
2018-09-12 17:26:21 +00:00
{
CheckDisposed ( ) ;
2014-07-08 01:41:03 +00:00
2021-09-03 16:46:34 +00:00
_logger . LogInformation ( "Logging out access token {0}" , device . AccessToken ) ;
2018-09-12 17:26:21 +00:00
2021-09-03 16:46:34 +00:00
await _deviceManager . DeleteDevice ( device ) . ConfigureAwait ( false ) ;
2014-07-08 01:41:03 +00:00
2018-09-12 17:26:21 +00:00
var sessions = Sessions
2021-09-03 16:46:34 +00:00
. Where ( i = > string . Equals ( i . DeviceId , device . DeviceId , StringComparison . OrdinalIgnoreCase ) )
2018-09-12 17:26:21 +00:00
. ToList ( ) ;
foreach ( var session in sessions )
{
try
2014-07-08 01:41:03 +00:00
{
2024-01-14 15:50:09 +00:00
await ReportSessionEnded ( session . Id ) . ConfigureAwait ( false ) ;
2018-09-12 17:26:21 +00:00
}
catch ( Exception ex )
{
2021-09-03 16:46:34 +00:00
_logger . LogError ( ex , "Error reporting session ended" ) ;
2014-07-08 01:41:03 +00:00
}
}
}
2020-01-16 23:19:58 +00:00
/// <inheritdoc />
2021-05-21 03:56:59 +00:00
public async Task RevokeUserTokens ( Guid userId , string currentAccessToken )
2014-07-08 01:41:03 +00:00
{
2018-09-12 17:26:21 +00:00
CheckDisposed ( ) ;
2024-08-05 14:58:22 +00:00
var existing = _deviceManager . GetDevices ( new DeviceQuery
2014-07-08 01:41:03 +00:00
{
UserId = userId
2024-08-05 14:58:22 +00:00
} ) ;
2014-07-08 01:41:03 +00:00
foreach ( var info in existing . Items )
{
2016-06-05 20:39:37 +00:00
if ( ! string . Equals ( currentAccessToken , info . AccessToken , StringComparison . OrdinalIgnoreCase ) )
{
2021-05-21 03:56:59 +00:00
await Logout ( info ) . ConfigureAwait ( false ) ;
2016-06-05 20:39:37 +00:00
}
2014-07-08 01:41:03 +00:00
}
}
2014-07-12 02:31:08 +00:00
2014-03-21 03:31:40 +00:00
/// <summary>
/// Reports the capabilities.
/// </summary>
/// <param name="sessionId">The session identifier.</param>
/// <param name="capabilities">The capabilities.</param>
2014-10-11 20:38:13 +00:00
public void ReportCapabilities ( string sessionId , ClientCapabilities capabilities )
2014-03-21 03:31:40 +00:00
{
2018-09-12 17:26:21 +00:00
CheckDisposed ( ) ;
2014-03-21 03:31:40 +00:00
var session = GetSession ( sessionId ) ;
2014-06-04 03:34:36 +00:00
ReportCapabilities ( session , capabilities , true ) ;
}
2019-03-13 21:32:52 +00:00
private void ReportCapabilities (
SessionInfo session ,
2014-10-11 20:38:13 +00:00
ClientCapabilities capabilities ,
2014-06-04 03:34:36 +00:00
bool saveCapabilities )
{
2015-02-19 19:21:03 +00:00
session . Capabilities = capabilities ;
2014-05-14 00:46:45 +00:00
2018-09-12 17:26:21 +00:00
if ( saveCapabilities )
2014-05-14 00:46:45 +00:00
{
2019-03-13 21:32:52 +00:00
CapabilitiesChanged ? . Invoke (
this ,
new SessionEventArgs
{
SessionInfo = session
} ) ;
2014-06-04 03:34:36 +00:00
2020-11-19 14:38:54 +00:00
_deviceManager . SaveCapabilities ( session . DeviceId , capabilities ) ;
2014-06-04 03:34:36 +00:00
}
}
2014-04-06 17:53:23 +00:00
/// <summary>
2020-01-16 23:19:58 +00:00
/// Converts a BaseItem to a BaseItemInfo.
2014-04-06 17:53:23 +00:00
/// </summary>
2017-06-09 19:26:54 +00:00
private BaseItemDto GetItemInfo ( BaseItem item , MediaSourceInfo mediaSource )
2014-04-06 17:53:23 +00:00
{
2022-10-06 18:21:23 +00:00
ArgumentNullException . ThrowIfNull ( item ) ;
2014-04-06 17:53:23 +00:00
2017-06-09 19:26:54 +00:00
var dtoOptions = _itemInfoDtoOptions ;
2014-04-06 17:53:23 +00:00
2022-12-05 14:00:20 +00:00
if ( _itemInfoDtoOptions is null )
2014-04-06 17:53:23 +00:00
{
2017-06-09 19:26:54 +00:00
dtoOptions = new DtoOptions
2014-04-06 17:53:23 +00:00
{
2017-06-09 19:26:54 +00:00
AddProgramRecordingInfo = false
} ;
2017-08-19 19:43:35 +00:00
var fields = dtoOptions . Fields . ToList ( ) ;
fields . Remove ( ItemFields . CanDelete ) ;
fields . Remove ( ItemFields . CanDownload ) ;
fields . Remove ( ItemFields . ChildCount ) ;
fields . Remove ( ItemFields . CustomRating ) ;
fields . Remove ( ItemFields . DateLastMediaAdded ) ;
fields . Remove ( ItemFields . DateLastRefreshed ) ;
fields . Remove ( ItemFields . DateLastSaved ) ;
fields . Remove ( ItemFields . DisplayPreferencesId ) ;
fields . Remove ( ItemFields . Etag ) ;
fields . Remove ( ItemFields . InheritedParentalRatingValue ) ;
fields . Remove ( ItemFields . ItemCounts ) ;
fields . Remove ( ItemFields . MediaSourceCount ) ;
fields . Remove ( ItemFields . MediaStreams ) ;
fields . Remove ( ItemFields . MediaSources ) ;
fields . Remove ( ItemFields . People ) ;
fields . Remove ( ItemFields . PlayAccess ) ;
fields . Remove ( ItemFields . People ) ;
fields . Remove ( ItemFields . ProductionLocations ) ;
fields . Remove ( ItemFields . RecursiveItemCount ) ;
fields . Remove ( ItemFields . RemoteTrailers ) ;
fields . Remove ( ItemFields . SeasonUserData ) ;
fields . Remove ( ItemFields . Settings ) ;
fields . Remove ( ItemFields . SortName ) ;
fields . Remove ( ItemFields . Tags ) ;
2018-09-12 17:26:21 +00:00
fields . Remove ( ItemFields . ExtraIds ) ;
2017-08-19 19:43:35 +00:00
2018-12-28 15:48:26 +00:00
dtoOptions . Fields = fields . ToArray ( ) ;
2017-06-09 19:26:54 +00:00
_itemInfoDtoOptions = dtoOptions ;
}
var info = _dtoService . GetBaseItemDto ( item , dtoOptions ) ;
2014-05-08 05:04:39 +00:00
2022-12-05 14:01:13 +00:00
if ( mediaSource is not null )
2014-05-08 05:04:39 +00:00
{
2017-08-19 19:43:35 +00:00
info . MediaStreams = mediaSource . MediaStreams . ToArray ( ) ;
2014-05-08 05:04:39 +00:00
}
2014-04-06 17:53:23 +00:00
return info ;
}
2020-05-15 21:24:01 +00:00
private string GetImageCacheTag ( User user )
2014-04-06 17:53:23 +00:00
{
try
{
2020-05-13 02:10:35 +00:00
return _imageProcessor . GetImageCacheTag ( user ) ;
2014-04-06 17:53:23 +00:00
}
2020-05-13 02:10:35 +00:00
catch ( Exception e )
2014-04-06 17:53:23 +00:00
{
2020-05-13 02:10:35 +00:00
_logger . LogError ( e , "Error getting image information for profile image" ) ;
2014-04-06 17:53:23 +00:00
return null ;
}
}
2020-01-16 23:19:58 +00:00
/// <inheritdoc />
2014-05-01 03:24:55 +00:00
public void ReportNowViewingItem ( string sessionId , string itemId )
2014-04-16 02:17:48 +00:00
{
2022-10-13 17:08:00 +00:00
ArgumentException . ThrowIfNullOrEmpty ( itemId ) ;
2016-05-30 16:08:46 +00:00
2020-01-16 23:19:58 +00:00
var item = _libraryManager . GetItemById ( new Guid ( itemId ) ) ;
2020-02-01 15:16:11 +00:00
var session = GetSession ( sessionId ) ;
2014-04-16 02:17:48 +00:00
2021-04-01 14:42:00 +00:00
session . NowViewingItem = GetItemInfo ( item , null ) ;
2014-04-16 02:17:48 +00:00
}
2014-06-06 00:39:02 +00:00
2020-01-16 23:19:58 +00:00
/// <inheritdoc />
2014-06-06 00:39:02 +00:00
public void ReportTranscodingInfo ( string deviceId , TranscodingInfo info )
{
2020-02-01 15:07:46 +00:00
var session = Sessions . FirstOrDefault ( i = >
string . Equals ( i . DeviceId , deviceId , StringComparison . OrdinalIgnoreCase ) ) ;
2014-06-06 00:39:02 +00:00
2022-12-05 14:01:13 +00:00
if ( session is not null )
2014-06-06 00:39:02 +00:00
{
session . TranscodingInfo = info ;
}
}
2020-01-16 23:19:58 +00:00
/// <inheritdoc />
2014-06-06 00:39:02 +00:00
public void ClearTranscodingInfo ( string deviceId )
{
ReportTranscodingInfo ( deviceId , null ) ;
}
2014-06-25 15:12:39 +00:00
2020-01-16 23:19:58 +00:00
/// <inheritdoc />
2014-06-25 15:12:39 +00:00
public SessionInfo GetSession ( string deviceId , string client , string version )
{
2020-02-01 15:07:46 +00:00
return Sessions . FirstOrDefault ( i = >
string . Equals ( i . DeviceId , deviceId , StringComparison . OrdinalIgnoreCase )
& & string . Equals ( i . Client , client , StringComparison . OrdinalIgnoreCase ) ) ;
2014-06-25 15:12:39 +00:00
}
2015-01-13 03:46:44 +00:00
2020-01-16 23:19:58 +00:00
/// <inheritdoc />
2021-05-21 03:56:59 +00:00
public Task < SessionInfo > GetSessionByAuthenticationToken ( Device info , string deviceId , string remoteEndpoint , string appVersion )
2015-03-09 19:40:03 +00:00
{
2022-10-06 18:21:23 +00:00
ArgumentNullException . ThrowIfNull ( info ) ;
2015-03-09 19:40:03 +00:00
2024-01-17 15:51:39 +00:00
var user = info . UserId . IsEmpty ( )
2015-03-09 19:40:03 +00:00
? null
: _userManager . GetUserById ( info . UserId ) ;
2018-09-12 17:26:21 +00:00
appVersion = string . IsNullOrEmpty ( appVersion )
2015-03-15 01:42:09 +00:00
? info . AppVersion
2015-03-09 19:40:03 +00:00
: appVersion ;
2015-03-13 01:55:22 +00:00
var deviceName = info . DeviceName ;
2015-03-15 01:42:09 +00:00
var appName = info . AppName ;
2015-03-13 01:55:22 +00:00
2018-09-12 17:26:21 +00:00
if ( string . IsNullOrEmpty ( deviceId ) )
2015-03-13 01:55:22 +00:00
{
deviceId = info . DeviceId ;
}
2015-03-28 18:55:00 +00:00
// Prevent argument exception
2018-09-12 17:26:21 +00:00
if ( string . IsNullOrEmpty ( appVersion ) )
2015-03-28 18:55:00 +00:00
{
appVersion = "1" ;
}
2015-03-21 16:10:02 +00:00
return LogSessionActivity ( appName , appVersion , deviceId , deviceName , remoteEndpoint , user ) ;
2015-03-09 19:40:03 +00:00
}
2020-01-16 23:19:58 +00:00
/// <inheritdoc />
2021-05-21 03:56:59 +00:00
public async Task < SessionInfo > GetSessionByAuthenticationToken ( string token , string deviceId , string remoteEndpoint )
2015-03-08 19:48:30 +00:00
{
2024-08-05 14:58:22 +00:00
var items = _deviceManager . GetDevices ( new DeviceQuery
2015-03-08 19:48:30 +00:00
{
2020-01-16 23:19:58 +00:00
AccessToken = token ,
Limit = 1
2024-08-05 14:58:22 +00:00
} ) . Items ;
2015-03-08 19:48:30 +00:00
2020-01-16 23:19:58 +00:00
if ( items . Count = = 0 )
2015-03-09 19:40:03 +00:00
{
2018-09-12 17:26:21 +00:00
return null ;
2015-03-09 19:40:03 +00:00
}
2021-05-21 03:56:59 +00:00
return await GetSessionByAuthenticationToken ( items [ 0 ] , deviceId , remoteEndpoint , null ) . ConfigureAwait ( false ) ;
2015-03-08 19:48:30 +00:00
}
2020-01-16 23:19:58 +00:00
/// <inheritdoc />
2020-09-25 07:25:59 +00:00
public Task SendMessageToAdminSessions < T > ( SessionMessageType name , T data , CancellationToken cancellationToken )
2016-08-19 01:00:04 +00:00
{
2018-09-12 17:26:21 +00:00
CheckDisposed ( ) ;
2020-05-13 02:10:35 +00:00
var adminUserIds = _userManager . Users
. Where ( i = > i . HasPermission ( PermissionKind . IsAdministrator ) )
. Select ( i = > i . Id )
. ToList ( ) ;
2016-08-19 01:00:04 +00:00
2016-08-20 18:43:13 +00:00
return SendMessageToUserSessions ( adminUserIds , name , data , cancellationToken ) ;
2016-08-19 01:00:04 +00:00
}
2020-01-16 23:19:58 +00:00
/// <inheritdoc />
2020-09-25 07:25:59 +00:00
public Task SendMessageToUserSessions < T > ( List < Guid > userIds , SessionMessageType name , Func < T > dataFn , CancellationToken cancellationToken )
2018-09-12 17:26:21 +00:00
{
CheckDisposed ( ) ;
var sessions = Sessions . Where ( i = > userIds . Any ( i . ContainsUser ) ) . ToList ( ) ;
if ( sessions . Count = = 0 )
{
return Task . CompletedTask ;
}
2020-01-16 23:19:58 +00:00
return SendMessageToSessions ( sessions , name , dataFn ( ) , cancellationToken ) ;
2018-09-12 17:26:21 +00:00
}
2020-01-16 23:19:58 +00:00
/// <inheritdoc />
2020-09-25 07:25:59 +00:00
public Task SendMessageToUserSessions < T > ( List < Guid > userIds , SessionMessageType name , T data , CancellationToken cancellationToken )
2018-09-12 17:26:21 +00:00
{
CheckDisposed ( ) ;
2019-03-25 20:27:03 +00:00
var sessions = Sessions . Where ( i = > userIds . Any ( i . ContainsUser ) ) ;
2019-03-27 15:07:08 +00:00
return SendMessageToSessions ( sessions , name , data , cancellationToken ) ;
2018-09-12 17:26:21 +00:00
}
2020-01-16 23:19:58 +00:00
/// <inheritdoc />
2020-09-25 07:25:59 +00:00
public Task SendMessageToUserDeviceSessions < T > ( string deviceId , SessionMessageType name , T data , CancellationToken cancellationToken )
2015-01-13 03:46:44 +00:00
{
2018-09-12 17:26:21 +00:00
CheckDisposed ( ) ;
2019-03-25 20:27:03 +00:00
var sessions = Sessions . Where ( i = > string . Equals ( i . DeviceId , deviceId , StringComparison . OrdinalIgnoreCase ) ) ;
2019-02-28 22:22:57 +00:00
2019-03-27 15:07:08 +00:00
return SendMessageToSessions ( sessions , name , data , cancellationToken ) ;
2015-01-28 04:04:26 +00:00
}
2023-09-23 01:10:49 +00:00
/// <inheritdoc />
public async ValueTask DisposeAsync ( )
{
if ( _disposed )
{
return ;
}
foreach ( var session in _activeConnections . Values )
{
await session . DisposeAsync ( ) . ConfigureAwait ( false ) ;
}
if ( _idleTimer is not null )
{
await _idleTimer . DisposeAsync ( ) . ConfigureAwait ( false ) ;
_idleTimer = null ;
}
2023-10-08 11:49:35 +00:00
if ( _inactiveTimer is not null )
2023-10-07 19:18:21 +00:00
{
await _inactiveTimer . DisposeAsync ( ) . ConfigureAwait ( false ) ;
_inactiveTimer = null ;
}
2023-09-23 01:10:49 +00:00
await _shutdownCallback . DisposeAsync ( ) . ConfigureAwait ( false ) ;
_deviceManager . DeviceOptionsUpdated - = OnDeviceManagerDeviceOptionsUpdated ;
_disposed = true ;
}
private async void OnApplicationStopping ( )
{
_logger . LogInformation ( "Sending shutdown notifications" ) ;
try
{
var messageType = _appHost . ShouldRestart ? SessionMessageType . ServerRestarting : SessionMessageType . ServerShuttingDown ;
await SendMessageToSessions ( Sessions , messageType , string . Empty , CancellationToken . None ) . ConfigureAwait ( false ) ;
}
catch ( Exception ex )
{
_logger . LogError ( ex , "Error sending server shutdown notifications" ) ;
}
// Close open websockets to allow Kestrel to shut down cleanly
foreach ( var session in _activeConnections . Values )
{
await session . DisposeAsync ( ) . ConfigureAwait ( false ) ;
}
2023-09-29 16:43:49 +00:00
_activeConnections . Clear ( ) ;
2023-09-23 01:10:49 +00:00
}
2013-05-09 17:38:02 +00:00
}
2018-12-15 15:30:38 +00:00
}