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