< Summary - Jellyfin

Information
Class: Emby.Server.Implementations.Library.UserDataManager
Assembly: Emby.Server.Implementations
File(s): /srv/git/jellyfin/Emby.Server.Implementations/Library/UserDataManager.cs
Line coverage
28%
Covered lines: 67
Uncovered lines: 168
Coverable lines: 235
Total lines: 526
Line coverage: 28.5%
Branch coverage
21%
Covered branches: 27
Total branches: 128
Branch coverage: 21%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Coverage history

Coverage history 0 25 50 75 100 3/26/2026 - 12:14:14 AM Line coverage: 15.9% (26/163) Branch coverage: 5.8% (4/68) Total lines: 3685/4/2026 - 12:15:16 AM Line coverage: 32.9% (57/173) Branch coverage: 25.6% (21/82) Total lines: 3895/20/2026 - 12:15:44 AM Line coverage: 32.9% (57/173) Branch coverage: 24.3% (20/82) Total lines: 3897/6/2026 - 12:16:28 AM Line coverage: 28.5% (67/235) Branch coverage: 21% (27/128) Total lines: 526 3/26/2026 - 12:14:14 AM Line coverage: 15.9% (26/163) Branch coverage: 5.8% (4/68) Total lines: 3685/4/2026 - 12:15:16 AM Line coverage: 32.9% (57/173) Branch coverage: 25.6% (21/82) Total lines: 3895/20/2026 - 12:15:44 AM Line coverage: 32.9% (57/173) Branch coverage: 24.3% (20/82) Total lines: 3897/6/2026 - 12:16:28 AM Line coverage: 28.5% (67/235) Branch coverage: 21% (27/128) Total lines: 526

Coverage delta

Coverage delta 20 -20

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
SaveUserData(...)0%4260%
SaveUserData(...)0%272160%
Map(...)100%210%
Map(...)100%210%
GetUserDataBatch(...)70.83%252488.23%
GetResumeUserData(...)100%11100%
GetResumeUserDataBatch(...)15.78%7773820%
GetCacheKey(...)100%11100%
GetUserData(...)50%44100%
GetUserDataDto(...)100%210%
GetUserDataDto(...)50%4485.71%
GetUserItemDataDto(...)100%11100%
UpdatePlayState(...)0%930300%
ResetPlaybackStreamSelections(...)0%4260%

File(s)

/srv/git/jellyfin/Emby.Server.Implementations/Library/UserDataManager.cs

#LineLine coverage
 1#pragma warning disable RS0030 // Do not use banned APIs
 2
 3using System;
 4using System.Collections.Generic;
 5using System.Globalization;
 6using System.Linq;
 7using System.Threading;
 8using BitFaster.Caching.Lru;
 9using Jellyfin.Database.Implementations;
 10using Jellyfin.Database.Implementations.Entities;
 11using MediaBrowser.Controller.Configuration;
 12using MediaBrowser.Controller.Dto;
 13using MediaBrowser.Controller.Entities;
 14using MediaBrowser.Controller.Library;
 15using MediaBrowser.Model.Dto;
 16using MediaBrowser.Model.Entities;
 17using Microsoft.EntityFrameworkCore;
 18using AudioBook = MediaBrowser.Controller.Entities.AudioBook;
 19using Book = MediaBrowser.Controller.Entities.Book;
 20
 21namespace Emby.Server.Implementations.Library
 22{
 23    /// <summary>
 24    /// Class UserDataManager.
 25    /// </summary>
 26    public class UserDataManager : IUserDataManager
 27    {
 28        private readonly IServerConfigurationManager _config;
 29        private readonly IDbContextFactory<JellyfinDbContext> _repository;
 30        private readonly FastConcurrentLru<string, UserItemData> _cache;
 31
 32        /// <summary>
 33        /// Initializes a new instance of the <see cref="UserDataManager"/> class.
 34        /// </summary>
 35        /// <param name="config">Instance of the <see cref="IServerConfigurationManager"/> interface.</param>
 36        /// <param name="repository">Instance of the <see cref="IDbContextFactory{JellyfinDbContext}"/> interface.</para
 37        public UserDataManager(
 38            IServerConfigurationManager config,
 39            IDbContextFactory<JellyfinDbContext> repository)
 40        {
 2241            _config = config;
 2242            _repository = repository;
 2243            _cache = new FastConcurrentLru<string, UserItemData>(Environment.ProcessorCount, _config.Configuration.Cache
 2244        }
 45
 46        /// <inheritdoc />
 47        public event EventHandler<UserDataSaveEventArgs>? UserDataSaved;
 48
 49        /// <inheritdoc />
 50        public void SaveUserData(User user, BaseItem item, UserItemData userData, UserDataSaveReason reason, Cancellatio
 51        {
 052            ArgumentNullException.ThrowIfNull(userData);
 53
 054            ArgumentNullException.ThrowIfNull(item);
 55
 056            cancellationToken.ThrowIfCancellationRequested();
 57
 058            var keys = item.GetUserDataKeys();
 59
 060            using var dbContext = _repository.CreateDbContext();
 061            using var transaction = dbContext.Database.BeginTransaction();
 62
 063            foreach (var key in keys)
 64            {
 065                userData.Key = key;
 066                var userDataEntry = Map(userData, user.Id, item.Id);
 067                if (dbContext.UserData.Any(f => f.ItemId == userDataEntry.ItemId && f.UserId == userDataEntry.UserId && 
 68                {
 069                    dbContext.UserData.Attach(userDataEntry).State = EntityState.Modified;
 70                }
 71                else
 72                {
 073                    dbContext.UserData.Add(userDataEntry);
 74                }
 75            }
 76
 077            dbContext.SaveChanges();
 078            transaction.Commit();
 79
 080            var userId = user.InternalId;
 081            var cacheKey = GetCacheKey(userId, item.Id);
 082            _cache.AddOrUpdate(cacheKey, userData);
 083            item.UserData = dbContext.UserData.Where(e => e.ItemId == item.Id).AsNoTracking().ToArray(); // rehydrate th
 84
 085            UserDataSaved?.Invoke(this, new UserDataSaveEventArgs
 086            {
 087                Keys = keys,
 088                UserData = userData,
 089                SaveReason = reason,
 090                UserId = user.Id,
 091                Item = item
 092            });
 093        }
 94
 95        /// <inheritdoc />
 96        public void SaveUserData(User user, BaseItem item, UpdateUserItemDataDto userDataDto, UserDataSaveReason reason)
 97        {
 098            ArgumentNullException.ThrowIfNull(user);
 099            ArgumentNullException.ThrowIfNull(item);
 0100            ArgumentNullException.ThrowIfNull(userDataDto);
 101
 0102            var userData = GetUserData(user, item) ?? throw new InvalidOperationException("UserData should not be null."
 103
 0104            if (userDataDto.PlaybackPositionTicks.HasValue)
 105            {
 0106                userData.PlaybackPositionTicks = userDataDto.PlaybackPositionTicks.Value;
 107            }
 108
 0109            if (userDataDto.PlayCount.HasValue)
 110            {
 0111                userData.PlayCount = userDataDto.PlayCount.Value;
 112            }
 113
 0114            if (userDataDto.IsFavorite.HasValue)
 115            {
 0116                userData.IsFavorite = userDataDto.IsFavorite.Value;
 117            }
 118
 0119            if (userDataDto.Likes.HasValue)
 120            {
 0121                userData.Likes = userDataDto.Likes.Value;
 122            }
 123
 0124            if (userDataDto.Played.HasValue)
 125            {
 0126                userData.Played = userDataDto.Played.Value;
 127            }
 128
 0129            if (userDataDto.LastPlayedDate.HasValue)
 130            {
 0131                userData.LastPlayedDate = userDataDto.LastPlayedDate.Value;
 132            }
 133
 0134            if (userDataDto.Rating.HasValue)
 135            {
 0136                userData.Rating = userDataDto.Rating.Value;
 137            }
 138
 0139            SaveUserData(user, item, userData, reason, CancellationToken.None);
 0140        }
 141
 142        private UserData Map(UserItemData dto, Guid userId, Guid itemId)
 143        {
 0144            return new UserData()
 0145            {
 0146                ItemId = itemId,
 0147                CustomDataKey = dto.Key,
 0148                Item = null,
 0149                User = null,
 0150                AudioStreamIndex = dto.AudioStreamIndex,
 0151                IsFavorite = dto.IsFavorite,
 0152                LastPlayedDate = dto.LastPlayedDate,
 0153                Likes = dto.Likes,
 0154                PlaybackPositionTicks = dto.PlaybackPositionTicks,
 0155                PlayCount = dto.PlayCount,
 0156                Played = dto.Played,
 0157                Rating = dto.Rating,
 0158                UserId = userId,
 0159                SubtitleStreamIndex = dto.SubtitleStreamIndex,
 0160            };
 161        }
 162
 163        private static UserItemData Map(UserData dto)
 164        {
 0165            return new UserItemData()
 0166            {
 0167                Key = dto.CustomDataKey!,
 0168                AudioStreamIndex = dto.AudioStreamIndex,
 0169                IsFavorite = dto.IsFavorite,
 0170                LastPlayedDate = dto.LastPlayedDate,
 0171                Likes = dto.Likes,
 0172                PlaybackPositionTicks = dto.PlaybackPositionTicks,
 0173                PlayCount = dto.PlayCount,
 0174                Played = dto.Played,
 0175                Rating = dto.Rating,
 0176                SubtitleStreamIndex = dto.SubtitleStreamIndex,
 0177            };
 178        }
 179
 180        /// <inheritdoc />
 181        public Dictionary<Guid, UserItemData> GetUserDataBatch(IReadOnlyList<BaseItem> items, User user)
 182        {
 4183            var result = new Dictionary<Guid, UserItemData>(items.Count);
 4184            var itemsNeedingQuery = new List<(BaseItem Item, List<string> Keys)>();
 185
 14186            foreach (var item in items)
 187            {
 3188                var cacheKey = GetCacheKey(user.InternalId, item.Id);
 3189                if (_cache.TryGet(cacheKey, out var cachedData))
 190                {
 2191                    result[item.Id] = cachedData;
 192                }
 193                else
 194                {
 1195                    var userData = item.UserData?.Where(e => e.UserId.Equals(user.Id)).Select(Map).FirstOrDefault();
 1196                    if (userData is not null)
 197                    {
 0198                        result[item.Id] = userData;
 0199                        _cache.AddOrUpdate(cacheKey, userData);
 200                    }
 201                    else
 202                    {
 1203                        var keys = item.GetUserDataKeys();
 1204                        itemsNeedingQuery.Add((item, keys));
 205                    }
 206                }
 207            }
 208
 4209            if (itemsNeedingQuery.Count == 0)
 210            {
 3211                return result;
 212            }
 213
 214            // Build a single query for all missing items
 1215            var allItemIds = itemsNeedingQuery.Select(x => x.Item.Id).ToList();
 1216            var allKeys = itemsNeedingQuery.SelectMany(x => x.Keys).Distinct().ToList();
 1217            if (allKeys.Count > 0)
 218            {
 1219                using var context = _repository.CreateDbContext();
 1220                var userDataArray = context.UserData
 1221                    .AsNoTracking()
 1222                    .Where(e => e.UserId.Equals(user.Id))
 1223                    .WhereOneOrMany(allItemIds, e => e.ItemId)
 1224                    .WhereOneOrMany(allKeys, e => e.CustomDataKey)
 1225                    .ToArray();
 226
 1227                var userDataByItem = userDataArray.GroupBy(e => e.ItemId).ToDictionary(g => g.Key, g => g.ToArray());
 4228                foreach (var (item, keys) in itemsNeedingQuery)
 229                {
 230                    UserItemData userData;
 1231                    if (userDataByItem.TryGetValue(item.Id, out var itemUserData) && itemUserData.Length > 0)
 232                    {
 0233                        var directDataReference = itemUserData.FirstOrDefault(e => e.CustomDataKey == item.Id.ToString("
 0234                        userData = directDataReference is not null ? Map(directDataReference) : Map(itemUserData.First()
 235                    }
 236                    else
 237                    {
 1238                        userData = new UserItemData { Key = keys.Count > 0 ? keys[0] : string.Empty };
 239                    }
 240
 1241                    result[item.Id] = userData;
 1242                    var cacheKey = GetCacheKey(user.InternalId, item.Id);
 1243                    _cache.AddOrUpdate(cacheKey, userData);
 244                }
 245            }
 246
 1247            return result;
 248        }
 249
 250        /// <inheritdoc />
 251        public VersionResumeData? GetResumeUserData(User user, BaseItem item)
 252        {
 6253            return GetResumeUserDataBatch([item], user).GetValueOrDefault(item.Id);
 254        }
 255
 256        /// <inheritdoc />
 257        public IReadOnlyDictionary<Guid, VersionResumeData> GetResumeUserDataBatch(IReadOnlyList<BaseItem> items, User u
 258        {
 10259            ArgumentNullException.ThrowIfNull(user);
 260
 10261            var result = new Dictionary<Guid, VersionResumeData>();
 262
 263            // Candidate primaries: a directly queried version (PrimaryVersionId set) keeps its own data.
 264            // Linked alternates are already known in memory; only the local-alternate existence check
 265            // would otherwise hit the database (one query per item via Video.HasLocalAlternateVersions),
 266            // so collect those ids and resolve them all in a single query below.
 10267            List<Video>? candidates = null;
 10268            List<Guid>? localProbeIds = null;
 38269            foreach (var item in items)
 270            {
 9271                if (item is not Video video || video.PrimaryVersionId.HasValue)
 272                {
 273                    continue;
 274                }
 275
 0276                (candidates ??= []).Add(video);
 277
 0278                if (video.LinkedAlternateVersions.Length == 0)
 279                {
 0280                    (localProbeIds ??= []).Add(video.Id);
 281                }
 282            }
 283
 10284            if (candidates is null)
 285            {
 10286                return result;
 287            }
 288
 0289            HashSet<Guid>? withLocalAlternates = null;
 0290            if (localProbeIds is not null)
 291            {
 0292                using var dbContext = _repository.CreateDbContext();
 0293                withLocalAlternates = dbContext.LinkedChildren
 0294                    .Where(lc => lc.ChildType == Jellyfin.Database.Implementations.Entities.LinkedChildType.LocalAlterna
 0295                        && localProbeIds.Contains(lc.ParentId))
 0296                    .Select(lc => lc.ParentId)
 0297                    .Distinct()
 0298                    .ToHashSet();
 299            }
 300
 0301            List<(Guid PrimaryId, IReadOnlyList<Video> Versions)>? versionGroups = null;
 0302            List<BaseItem>? allVersions = null;
 303
 0304            foreach (var video in candidates)
 305            {
 306                // Only items that actually have alternate versions aggregate over them.
 0307                if (video.LinkedAlternateVersions.Length == 0
 0308                    && (withLocalAlternates is null || !withLocalAlternates.Contains(video.Id)))
 309                {
 310                    continue;
 311                }
 312
 0313                var versions = video.GetAllVersions();
 0314                if (versions.Count < 2)
 315                {
 316                    continue;
 317                }
 318
 0319                (versionGroups ??= []).Add((video.Id, versions));
 0320                (allVersions ??= []).AddRange(versions);
 321            }
 322
 0323            if (versionGroups is null)
 324            {
 0325                return result;
 326            }
 327
 0328            var userDataByVersion = GetUserDataBatch(allVersions!.DistinctBy(i => i.Id).ToList(), user);
 329
 0330            foreach (var (primaryId, versions) in versionGroups)
 331            {
 332                // Consider both in-progress and completed versions so a finished alternate still marks the primary as p
 0333                var resumeVersion = VersionPlaybackSelector.SelectMostRecentlyPlayed(
 0334                    versions,
 0335                    version => userDataByVersion.GetValueOrDefault(version.Id),
 0336                    data => data.PlaybackPositionTicks > 0 || data.Played);
 337
 0338                if (resumeVersion is not null)
 339                {
 0340                    result[primaryId] = new VersionResumeData(resumeVersion.Id, userDataByVersion[resumeVersion.Id]);
 341                }
 342            }
 343
 0344            return result;
 345        }
 346
 347        /// <summary>
 348        /// Gets the internal key.
 349        /// </summary>
 350        /// <returns>System.String.</returns>
 351        private static string GetCacheKey(long internalUserId, Guid itemId)
 352        {
 4353            return internalUserId.ToString(CultureInfo.InvariantCulture) + "-" + itemId.ToString("N", CultureInfo.Invari
 354        }
 355
 356        /// <inheritdoc />
 357        public UserItemData? GetUserData(User user, BaseItem item)
 358        {
 6359            return item.UserData?.Where(e => e.UserId.Equals(user.Id)).Select(Map).FirstOrDefault() ?? new UserItemData(
 6360            {
 6361                Key = item.GetUserDataKeys()[0],
 6362            };
 363        }
 364
 365        /// <inheritdoc />
 366        public UserItemDataDto? GetUserDataDto(BaseItem item, User user)
 0367            => GetUserDataDto(item, null, user, new DtoOptions());
 368
 369        /// <inheritdoc />
 370        public UserItemDataDto? GetUserDataDto(BaseItem item, BaseItemDto? itemDto, User user, DtoOptions options)
 371        {
 6372            var userData = GetUserData(user, item);
 6373            if (userData is null)
 374            {
 0375                return null;
 376            }
 377
 6378            var dto = GetUserItemDataDto(userData, item.Id);
 379
 6380            item.FillUserDataDtoValues(dto, userData, itemDto, user, options);
 381
 382            // For an item with alternate versions, surface the most recently played version's resume point.
 6383            GetResumeUserData(user, item)?.ApplyTo(dto);
 384
 6385            return dto;
 386        }
 387
 388        /// <summary>
 389        /// Converts a UserItemData to a DTOUserItemData.
 390        /// </summary>
 391        /// <param name="data">The data.</param>
 392        /// <param name="itemId">The reference key to an Item.</param>
 393        /// <returns>DtoUserItemData.</returns>
 394        /// <exception cref="ArgumentNullException"><paramref name="data"/> is <c>null</c>.</exception>
 395        private UserItemDataDto GetUserItemDataDto(UserItemData data, Guid itemId)
 396        {
 6397            ArgumentNullException.ThrowIfNull(data);
 398
 6399            return new UserItemDataDto
 6400            {
 6401                IsFavorite = data.IsFavorite,
 6402                Likes = data.Likes,
 6403                PlaybackPositionTicks = data.PlaybackPositionTicks,
 6404                PlayCount = data.PlayCount,
 6405                Rating = data.Rating,
 6406                Played = data.Played,
 6407                LastPlayedDate = data.LastPlayedDate,
 6408                ItemId = itemId,
 6409                Key = data.Key
 6410            };
 411        }
 412
 413        /// <inheritdoc />
 414        public bool UpdatePlayState(BaseItem item, UserItemData data, long? reportedPositionTicks)
 415        {
 0416            var playedToCompletion = false;
 417
 0418            var runtimeTicks = item.GetRunTimeTicksForPlayState();
 419
 0420            var positionTicks = reportedPositionTicks ?? runtimeTicks;
 0421            var hasRuntime = runtimeTicks > 0;
 422
 423            // If a position has been reported, and if we know the duration
 0424            if (positionTicks > 0 && hasRuntime && item is not AudioBook && item is not Book)
 425            {
 0426                var pctIn = decimal.Divide(positionTicks, runtimeTicks) * 100;
 427
 0428                if (pctIn < _config.Configuration.MinResumePct)
 429                {
 430                    // ignore progress during the beginning
 0431                    positionTicks = 0;
 432                }
 0433                else if (pctIn > _config.Configuration.MaxResumePct || positionTicks >= (runtimeTicks - TimeSpan.TicksPe
 434                {
 435                    // mark as completed close to the end
 0436                    positionTicks = 0;
 0437                    data.Played = playedToCompletion = true;
 438                }
 439                else
 440                {
 441                    // Enforce MinResumeDuration
 0442                    var durationSeconds = TimeSpan.FromTicks(runtimeTicks).TotalSeconds;
 0443                    if (durationSeconds < _config.Configuration.MinResumeDurationSeconds)
 444                    {
 0445                        positionTicks = 0;
 0446                        data.Played = playedToCompletion = true;
 447                    }
 448                }
 449            }
 0450            else if (positionTicks > 0 && hasRuntime && item is AudioBook)
 451            {
 0452                var playbackPositionInMinutes = TimeSpan.FromTicks(positionTicks).TotalMinutes;
 0453                var remainingTimeInMinutes = TimeSpan.FromTicks(runtimeTicks - positionTicks).TotalMinutes;
 454
 0455                if (playbackPositionInMinutes < _config.Configuration.MinAudiobookResume)
 456                {
 457                    // ignore progress during the beginning
 0458                    positionTicks = 0;
 459                }
 0460                else if (remainingTimeInMinutes < _config.Configuration.MaxAudiobookResume || positionTicks >= runtimeTi
 461                {
 462                    // mark as completed close to the end
 0463                    positionTicks = 0;
 0464                    data.Played = playedToCompletion = true;
 465                }
 466            }
 0467            else if (!hasRuntime)
 468            {
 469                // If we don't know the runtime we'll just have to assume it was fully played
 0470                data.Played = playedToCompletion = true;
 0471                positionTicks = 0;
 472            }
 473
 0474            if (!item.SupportsPlayedStatus)
 475            {
 0476                positionTicks = 0;
 0477                data.Played = false;
 478            }
 479
 0480            if (!item.SupportsPositionTicksResume)
 481            {
 0482                positionTicks = 0;
 483            }
 484
 0485            data.PlaybackPositionTicks = positionTicks;
 486
 0487            return playedToCompletion;
 488        }
 489
 490        /// <inheritdoc />
 491        public void ResetPlaybackStreamSelections(User user, BaseItem item)
 492        {
 0493            ArgumentNullException.ThrowIfNull(user);
 0494            ArgumentNullException.ThrowIfNull(item);
 495
 0496            using var dbContext = _repository.CreateDbContext();
 0497            var rows = dbContext.UserData
 0498                .Where(e => e.ItemId == item.Id && e.UserId == user.Id
 0499                            && (e.AudioStreamIndex != null || e.SubtitleStreamIndex != null))
 0500                .ToList();
 501
 0502            if (rows.Count == 0)
 503            {
 0504                return;
 505            }
 506
 0507            foreach (var row in rows)
 508            {
 0509                row.AudioStreamIndex = null;
 0510                row.SubtitleStreamIndex = null;
 511            }
 512
 0513            dbContext.SaveChanges();
 514
 0515            var cacheKey = GetCacheKey(user.InternalId, item.Id);
 0516            if (_cache.TryGet(cacheKey, out var cached))
 517            {
 0518                cached.AudioStreamIndex = null;
 0519                cached.SubtitleStreamIndex = null;
 0520                _cache.AddOrUpdate(cacheKey, cached);
 521            }
 522
 0523            item.UserData = dbContext.UserData.Where(e => e.ItemId == item.Id).AsNoTracking().ToArray();
 0524        }
 525    }
 526}

Methods/Properties

.ctor(MediaBrowser.Controller.Configuration.IServerConfigurationManager,Microsoft.EntityFrameworkCore.IDbContextFactory`1<Jellyfin.Database.Implementations.JellyfinDbContext>)
SaveUserData(Jellyfin.Database.Implementations.Entities.User,MediaBrowser.Controller.Entities.BaseItem,MediaBrowser.Controller.Entities.UserItemData,MediaBrowser.Model.Entities.UserDataSaveReason,System.Threading.CancellationToken)
SaveUserData(Jellyfin.Database.Implementations.Entities.User,MediaBrowser.Controller.Entities.BaseItem,MediaBrowser.Model.Dto.UpdateUserItemDataDto,MediaBrowser.Model.Entities.UserDataSaveReason)
Map(MediaBrowser.Controller.Entities.UserItemData,System.Guid,System.Guid)
Map(Jellyfin.Database.Implementations.Entities.UserData)
GetUserDataBatch(System.Collections.Generic.IReadOnlyList`1<MediaBrowser.Controller.Entities.BaseItem>,Jellyfin.Database.Implementations.Entities.User)
GetResumeUserData(Jellyfin.Database.Implementations.Entities.User,MediaBrowser.Controller.Entities.BaseItem)
GetResumeUserDataBatch(System.Collections.Generic.IReadOnlyList`1<MediaBrowser.Controller.Entities.BaseItem>,Jellyfin.Database.Implementations.Entities.User)
GetCacheKey(System.Int64,System.Guid)
GetUserData(Jellyfin.Database.Implementations.Entities.User,MediaBrowser.Controller.Entities.BaseItem)
GetUserDataDto(MediaBrowser.Controller.Entities.BaseItem,Jellyfin.Database.Implementations.Entities.User)
GetUserDataDto(MediaBrowser.Controller.Entities.BaseItem,MediaBrowser.Model.Dto.BaseItemDto,Jellyfin.Database.Implementations.Entities.User,MediaBrowser.Controller.Dto.DtoOptions)
GetUserItemDataDto(MediaBrowser.Controller.Entities.UserItemData,System.Guid)
UpdatePlayState(MediaBrowser.Controller.Entities.BaseItem,MediaBrowser.Controller.Entities.UserItemData,System.Nullable`1<System.Int64>)
ResetPlaybackStreamSelections(Jellyfin.Database.Implementations.Entities.User,MediaBrowser.Controller.Entities.BaseItem)