< Summary - Jellyfin

Information
Class: MediaBrowser.Controller.SyncPlay.Queue.PlayQueueManager
Assembly: MediaBrowser.Controller
File(s): /srv/git/jellyfin/MediaBrowser.Controller/SyncPlay/Queue/PlayQueueManager.cs
Line coverage
51%
Covered lines: 95
Uncovered lines: 88
Coverable lines: 183
Total lines: 572
Line coverage: 51.9%
Branch coverage
42%
Covered branches: 33
Total branches: 78
Branch coverage: 42.3%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Coverage history

Coverage history 0 25 50 75 100 5/12/2026 - 12:15:57 AM Line coverage: 0% (0/174) Branch coverage: 0% (0/70) Total lines: 5506/19/2026 - 12:16:12 AM Line coverage: 14.9% (26/174) Branch coverage: 5.7% (4/70) Total lines: 5507/3/2026 - 12:15:32 AM Line coverage: 17.2% (30/174) Branch coverage: 8.5% (6/70) Total lines: 5507/26/2026 - 12:17:51 AM Line coverage: 43.1% (79/183) Branch coverage: 34.2% (26/76) Total lines: 56910/29/2026 - 12:17:11 AM Line coverage: 51.9% (95/183) Branch coverage: 42.3% (33/78) Total lines: 572 5/12/2026 - 12:15:57 AM Line coverage: 0% (0/174) Branch coverage: 0% (0/70) Total lines: 5506/19/2026 - 12:16:12 AM Line coverage: 14.9% (26/174) Branch coverage: 5.7% (4/70) Total lines: 5507/3/2026 - 12:15:32 AM Line coverage: 17.2% (30/174) Branch coverage: 8.5% (6/70) Total lines: 5507/26/2026 - 12:17:51 AM Line coverage: 43.1% (79/183) Branch coverage: 34.2% (26/76) Total lines: 56910/29/2026 - 12:17:11 AM Line coverage: 51.9% (95/183) Branch coverage: 42.3% (33/78) Total lines: 572

Coverage delta

Coverage delta 26 -26

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor()100%11100%
IsItemPlaying()100%11100%
GetPlaylist()100%11100%
SetPlaylist(...)50%2277.77%
Queue(...)0%620%
ShufflePlaylist()75%4472.22%
RestoreSortedPlaylist()75%44100%
ClearPlaylist(...)0%4260%
QueueNext(...)0%620%
GetPlayingItemPlaylistId()100%22100%
GetPlayingItemId()50%22100%
SetPlayingItemById(...)100%210%
SetPlayingItemByPlaylistId(...)100%11100%
SetPlayingItemByIndex(...)100%44100%
RemoveFromPlaylist(...)70%101094.44%
MovePlaylistItem(...)0%620%
Reset()100%11100%
SetRepeatMode(...)100%11100%
SetShuffleMode(...)100%22100%
ToggleShuffleMode()0%620%
GetNextItemPlaylistId()0%110100%
Next()12.5%47815.38%
Previous()12.5%47815.38%
CreateQueueItemsFromArray(...)100%22100%
GetPlaylistInternal()100%22100%
GetPlayingItem()100%44100%

File(s)

/srv/git/jellyfin/MediaBrowser.Controller/SyncPlay/Queue/PlayQueueManager.cs

#LineLine coverage
 1#nullable disable
 2
 3using System;
 4using System.Collections.Generic;
 5using System.Linq;
 6using Jellyfin.Extensions;
 7using MediaBrowser.Model.SyncPlay;
 8
 9namespace MediaBrowser.Controller.SyncPlay.Queue
 10{
 11    /// <summary>
 12    /// Class PlayQueueManager.
 13    /// </summary>
 14    public class PlayQueueManager
 15    {
 16        /// <summary>
 17        /// Placeholder index for when no item is playing.
 18        /// </summary>
 19        /// <value>The no-playing item index.</value>
 20        private const int NoPlayingItemIndex = -1;
 21
 22        /// <summary>
 23        /// The sorted playlist.
 24        /// </summary>
 25        /// <value>The sorted playlist, or play queue of the group.</value>
 3726        private List<SyncPlayQueueItem> _sortedPlaylist = new List<SyncPlayQueueItem>();
 27
 28        /// <summary>
 29        /// The shuffled playlist.
 30        /// </summary>
 31        /// <value>The shuffled playlist, or play queue of the group.</value>
 3732        private List<SyncPlayQueueItem> _shuffledPlaylist = new List<SyncPlayQueueItem>();
 33
 34        /// <summary>
 35        /// Initializes a new instance of the <see cref="PlayQueueManager" /> class.
 36        /// </summary>
 37        public PlayQueueManager()
 38        {
 3739            Reset();
 3740        }
 41
 42        /// <summary>
 43        /// Gets the playing item index.
 44        /// </summary>
 45        /// <value>The playing item index.</value>
 46        public int PlayingItemIndex { get; private set; }
 47
 48        /// <summary>
 49        /// Gets the last time the queue has been changed.
 50        /// </summary>
 51        /// <value>The last time the queue has been changed.</value>
 52        public DateTime LastChange { get; private set; }
 53
 54        /// <summary>
 55        /// Gets the shuffle mode.
 56        /// </summary>
 57        /// <value>The shuffle mode.</value>
 58        public GroupShuffleMode ShuffleMode { get; private set; } = GroupShuffleMode.Sorted;
 59
 60        /// <summary>
 61        /// Gets the repeat mode.
 62        /// </summary>
 63        /// <value>The repeat mode.</value>
 64        public GroupRepeatMode RepeatMode { get; private set; } = GroupRepeatMode.RepeatNone;
 65
 66        /// <summary>
 67        /// Checks if an item is playing.
 68        /// </summary>
 69        /// <returns><c>true</c> if an item is playing; <c>false</c> otherwise.</returns>
 70        public bool IsItemPlaying()
 71        {
 1172            return PlayingItemIndex != NoPlayingItemIndex;
 73        }
 74
 75        /// <summary>
 76        /// Gets the current playlist considering the shuffle mode.
 77        /// </summary>
 78        /// <returns>The playlist.</returns>
 79        public IReadOnlyList<SyncPlayQueueItem> GetPlaylist()
 80        {
 9881            return GetPlaylistInternal();
 82        }
 83
 84        /// <summary>
 85        /// Sets a new playlist. Playing item is reset.
 86        /// </summary>
 87        /// <param name="items">The new items of the playlist.</param>
 88        public void SetPlaylist(IReadOnlyList<Guid> items)
 89        {
 2790            _sortedPlaylist.Clear();
 2791            _shuffledPlaylist.Clear();
 92
 2793            _sortedPlaylist = CreateQueueItemsFromArray(items);
 2794            if (ShuffleMode.Equals(GroupShuffleMode.Shuffle))
 95            {
 096                _shuffledPlaylist = new List<SyncPlayQueueItem>(_sortedPlaylist);
 097                _shuffledPlaylist.Shuffle();
 98            }
 99
 27100            PlayingItemIndex = NoPlayingItemIndex;
 27101            LastChange = DateTime.UtcNow;
 27102        }
 103
 104        /// <summary>
 105        /// Appends new items to the playlist. The specified order is maintained.
 106        /// </summary>
 107        /// <param name="items">The items to add to the playlist.</param>
 108        public void Queue(IReadOnlyList<Guid> items)
 109        {
 0110            var newItems = CreateQueueItemsFromArray(items);
 111
 0112            _sortedPlaylist.AddRange(newItems);
 0113            if (ShuffleMode.Equals(GroupShuffleMode.Shuffle))
 114            {
 0115                _shuffledPlaylist.AddRange(newItems);
 116            }
 117
 0118            LastChange = DateTime.UtcNow;
 0119        }
 120
 121        /// <summary>
 122        /// Shuffles the playlist. Shuffle mode is changed. The playlist gets re-shuffled if already shuffled.
 123        /// </summary>
 124        public void ShufflePlaylist()
 125        {
 2126            if (PlayingItemIndex == NoPlayingItemIndex)
 127            {
 1128                _shuffledPlaylist = new List<SyncPlayQueueItem>(_sortedPlaylist);
 1129                _shuffledPlaylist.Shuffle();
 130            }
 1131            else if (ShuffleMode.Equals(GroupShuffleMode.Sorted))
 132            {
 133                // First time shuffle.
 1134                var playingItem = _sortedPlaylist[PlayingItemIndex];
 1135                _shuffledPlaylist = new List<SyncPlayQueueItem>(_sortedPlaylist);
 1136                _shuffledPlaylist.RemoveAt(PlayingItemIndex);
 1137                _shuffledPlaylist.Shuffle();
 1138                _shuffledPlaylist.Insert(0, playingItem);
 1139                PlayingItemIndex = 0;
 140            }
 141            else
 142            {
 143                // Re-shuffle playlist.
 0144                var playingItem = _shuffledPlaylist[PlayingItemIndex];
 0145                _shuffledPlaylist.RemoveAt(PlayingItemIndex);
 0146                _shuffledPlaylist.Shuffle();
 0147                _shuffledPlaylist.Insert(0, playingItem);
 0148                PlayingItemIndex = 0;
 149            }
 150
 2151            ShuffleMode = GroupShuffleMode.Shuffle;
 2152            LastChange = DateTime.UtcNow;
 2153        }
 154
 155        /// <summary>
 156        /// Resets the playlist to sorted mode. Shuffle mode is changed.
 157        /// </summary>
 158        public void RestoreSortedPlaylist()
 159        {
 160            // The shuffled playlist is only populated while the shuffle mode is active, so there is
 161            // nothing to map back when the playlist is already sorted. Guarding on its contents keeps
 162            // a redundant request for the sorted mode from indexing an empty list.
 3163            if (PlayingItemIndex != NoPlayingItemIndex && _shuffledPlaylist.Count > 0)
 164            {
 1165                var playingItem = _shuffledPlaylist[PlayingItemIndex];
 1166                PlayingItemIndex = _sortedPlaylist.IndexOf(playingItem);
 167            }
 168
 3169            _shuffledPlaylist.Clear();
 170
 3171            ShuffleMode = GroupShuffleMode.Sorted;
 3172            LastChange = DateTime.UtcNow;
 3173        }
 174
 175        /// <summary>
 176        /// Clears the playlist. Shuffle mode is preserved.
 177        /// </summary>
 178        /// <param name="clearPlayingItem">Whether to remove the playing item as well.</param>
 179        public void ClearPlaylist(bool clearPlayingItem)
 180        {
 0181            var playingItem = GetPlayingItem();
 0182            _sortedPlaylist.Clear();
 0183            _shuffledPlaylist.Clear();
 0184            LastChange = DateTime.UtcNow;
 185
 0186            if (!clearPlayingItem && playingItem is not null)
 187            {
 0188                _sortedPlaylist.Add(playingItem);
 0189                if (ShuffleMode.Equals(GroupShuffleMode.Shuffle))
 190                {
 0191                    _shuffledPlaylist.Add(playingItem);
 192                }
 193
 0194                PlayingItemIndex = 0;
 195            }
 196            else
 197            {
 0198                PlayingItemIndex = NoPlayingItemIndex;
 199            }
 0200        }
 201
 202        /// <summary>
 203        /// Adds new items to the playlist right after the playing item. The specified order is maintained.
 204        /// </summary>
 205        /// <param name="items">The items to add to the playlist.</param>
 206        public void QueueNext(IReadOnlyList<Guid> items)
 207        {
 0208            var newItems = CreateQueueItemsFromArray(items);
 209
 0210            if (ShuffleMode.Equals(GroupShuffleMode.Shuffle))
 211            {
 0212                var playingItem = GetPlayingItem();
 0213                var sortedPlayingItemIndex = _sortedPlaylist.IndexOf(playingItem);
 214                // Append items to sorted and shuffled playlist as they are.
 0215                _sortedPlaylist.InsertRange(sortedPlayingItemIndex + 1, newItems);
 0216                _shuffledPlaylist.InsertRange(PlayingItemIndex + 1, newItems);
 217            }
 218            else
 219            {
 0220                _sortedPlaylist.InsertRange(PlayingItemIndex + 1, newItems);
 221            }
 222
 0223            LastChange = DateTime.UtcNow;
 0224        }
 225
 226        /// <summary>
 227        /// Gets playlist identifier of the playing item, if any.
 228        /// </summary>
 229        /// <returns>The playlist identifier of the playing item.</returns>
 230        public Guid GetPlayingItemPlaylistId()
 231        {
 84232            var playingItem = GetPlayingItem();
 84233            return playingItem?.PlaylistItemId ?? Guid.Empty;
 234        }
 235
 236        /// <summary>
 237        /// Gets the playing item identifier, if any.
 238        /// </summary>
 239        /// <returns>The playing item identifier.</returns>
 240        public Guid GetPlayingItemId()
 241        {
 24242            var playingItem = GetPlayingItem();
 24243            return playingItem?.ItemId ?? Guid.Empty;
 244        }
 245
 246        /// <summary>
 247        /// Sets the playing item using its identifier. If not in the playlist, the playing item is reset.
 248        /// </summary>
 249        /// <param name="itemId">The new playing item identifier.</param>
 250        public void SetPlayingItemById(Guid itemId)
 251        {
 0252            var playlist = GetPlaylistInternal();
 0253            PlayingItemIndex = playlist.FindIndex(item => item.ItemId.Equals(itemId));
 0254            LastChange = DateTime.UtcNow;
 0255        }
 256
 257        /// <summary>
 258        /// Sets the playing item using its playlist identifier. If not in the playlist, the playing item is reset.
 259        /// </summary>
 260        /// <param name="playlistItemId">The new playing item identifier.</param>
 261        /// <returns><c>true</c> if playing item has been set; <c>false</c> if item is not in the playlist.</returns>
 262        public bool SetPlayingItemByPlaylistId(Guid playlistItemId)
 263        {
 2264            var playlist = GetPlaylistInternal();
 2265            PlayingItemIndex = playlist.FindIndex(item => item.PlaylistItemId.Equals(playlistItemId));
 2266            LastChange = DateTime.UtcNow;
 267
 2268            return PlayingItemIndex != NoPlayingItemIndex;
 269        }
 270
 271        /// <summary>
 272        /// Sets the playing item using its position. If not in range, the playing item is reset.
 273        /// </summary>
 274        /// <param name="playlistIndex">The new playing item index.</param>
 275        public void SetPlayingItemByIndex(int playlistIndex)
 276        {
 25277            var playlist = GetPlaylistInternal();
 25278            if (playlistIndex < 0 || playlistIndex >= playlist.Count)
 279            {
 3280                PlayingItemIndex = NoPlayingItemIndex;
 281            }
 282            else
 283            {
 22284                PlayingItemIndex = playlistIndex;
 285            }
 286
 25287            LastChange = DateTime.UtcNow;
 25288        }
 289
 290        /// <summary>
 291        /// Removes items from the playlist. If not removed, the playing item is preserved.
 292        /// </summary>
 293        /// <param name="playlistItemIds">The items to remove.</param>
 294        /// <returns><c>true</c> if playing item got removed; <c>false</c> otherwise.</returns>
 295        public bool RemoveFromPlaylist(IReadOnlyList<Guid> playlistItemIds)
 296        {
 5297            var playingItem = GetPlayingItem();
 298
 299            // Removed items that precede the playing item shift its index as well.
 5300            var removedBeforePlayingItem = 0;
 5301            if (playingItem is not null)
 302            {
 5303                removedBeforePlayingItem = GetPlaylistInternal()
 5304                    .Take(PlayingItemIndex)
 5305                    .Count(item => playlistItemIds.Contains(item.PlaylistItemId));
 306            }
 307
 5308            _sortedPlaylist.RemoveAll(item => playlistItemIds.Contains(item.PlaylistItemId));
 5309            _shuffledPlaylist.RemoveAll(item => playlistItemIds.Contains(item.PlaylistItemId));
 310
 5311            LastChange = DateTime.UtcNow;
 312
 5313            if (playingItem is not null)
 314            {
 5315                if (playlistItemIds.Contains(playingItem.PlaylistItemId))
 316                {
 317                    // Playing item has been removed, picking previous item.
 4318                    PlayingItemIndex -= removedBeforePlayingItem + 1;
 4319                    if (PlayingItemIndex < 0)
 320                    {
 321                        // Was first element, picking next if available.
 322                        // Default to no playing item otherwise.
 1323                        PlayingItemIndex = GetPlaylistInternal().Count > 0 ? 0 : NoPlayingItemIndex;
 324                    }
 325
 4326                    return true;
 327                }
 328
 329                // Restoring playing item.
 1330                SetPlayingItemByPlaylistId(playingItem.PlaylistItemId);
 1331                return false;
 332            }
 333
 0334            return false;
 335        }
 336
 337        /// <summary>
 338        /// Moves an item in the playlist to another position.
 339        /// </summary>
 340        /// <param name="playlistItemId">The item to move.</param>
 341        /// <param name="newIndex">The new position.</param>
 342        /// <returns><c>true</c> if the item has been moved; <c>false</c> otherwise.</returns>
 343        public bool MovePlaylistItem(Guid playlistItemId, int newIndex)
 344        {
 0345            var playlist = GetPlaylistInternal();
 0346            var playingItem = GetPlayingItem();
 347
 0348            var oldIndex = playlist.FindIndex(item => item.PlaylistItemId.Equals(playlistItemId));
 0349            if (oldIndex < 0)
 350            {
 0351                return false;
 352            }
 353
 0354            var queueItem = playlist[oldIndex];
 0355            playlist.RemoveAt(oldIndex);
 0356            newIndex = Math.Clamp(newIndex, 0, playlist.Count);
 0357            playlist.Insert(newIndex, queueItem);
 358
 0359            LastChange = DateTime.UtcNow;
 0360            PlayingItemIndex = playlist.IndexOf(playingItem);
 0361            return true;
 362        }
 363
 364        /// <summary>
 365        /// Resets the playlist to its initial state.
 366        /// </summary>
 367        public void Reset()
 368        {
 53369            _sortedPlaylist.Clear();
 53370            _shuffledPlaylist.Clear();
 53371            PlayingItemIndex = NoPlayingItemIndex;
 53372            ShuffleMode = GroupShuffleMode.Sorted;
 53373            RepeatMode = GroupRepeatMode.RepeatNone;
 53374            LastChange = DateTime.UtcNow;
 53375        }
 376
 377        /// <summary>
 378        /// Sets the repeat mode.
 379        /// </summary>
 380        /// <param name="mode">The new mode.</param>
 381        public void SetRepeatMode(GroupRepeatMode mode)
 382        {
 6383            RepeatMode = mode;
 6384            LastChange = DateTime.UtcNow;
 6385        }
 386
 387        /// <summary>
 388        /// Sets the shuffle mode.
 389        /// </summary>
 390        /// <param name="mode">The new mode.</param>
 391        public void SetShuffleMode(GroupShuffleMode mode)
 392        {
 5393            if (mode.Equals(GroupShuffleMode.Shuffle))
 394            {
 2395                ShufflePlaylist();
 396            }
 397            else
 398            {
 3399                RestoreSortedPlaylist();
 400            }
 3401        }
 402
 403        /// <summary>
 404        /// Toggles the shuffle mode between sorted and shuffled.
 405        /// </summary>
 406        public void ToggleShuffleMode()
 407        {
 0408            if (ShuffleMode.Equals(GroupShuffleMode.Sorted))
 409            {
 0410                ShufflePlaylist();
 411            }
 412            else
 413            {
 0414                RestoreSortedPlaylist();
 415            }
 0416        }
 417
 418        /// <summary>
 419        /// Gets the next item in the playlist considering repeat mode and shuffle mode.
 420        /// </summary>
 421        /// <returns>The next item in the playlist.</returns>
 422        public SyncPlayQueueItem GetNextItemPlaylistId()
 423        {
 424            int newIndex;
 0425            var playlist = GetPlaylistInternal();
 426
 0427            switch (RepeatMode)
 428            {
 429                case GroupRepeatMode.RepeatOne:
 0430                    newIndex = PlayingItemIndex;
 0431                    break;
 432                case GroupRepeatMode.RepeatAll:
 0433                    newIndex = PlayingItemIndex + 1;
 0434                    if (newIndex >= playlist.Count)
 435                    {
 0436                        newIndex = 0;
 437                    }
 438
 0439                    break;
 440                default:
 0441                    newIndex = PlayingItemIndex + 1;
 442                    break;
 443            }
 444
 0445            if (newIndex < 0 || newIndex >= playlist.Count)
 446            {
 0447                return null;
 448            }
 449
 0450            return playlist[newIndex];
 451        }
 452
 453        /// <summary>
 454        /// Sets the next item in the queue as playing item.
 455        /// </summary>
 456        /// <returns><c>true</c> if the playing item changed; <c>false</c> otherwise.</returns>
 457        public bool Next()
 458        {
 3459            if (GetPlaylistInternal().Count == 0)
 460            {
 3461                return false;
 462            }
 463
 0464            if (RepeatMode.Equals(GroupRepeatMode.RepeatOne))
 465            {
 0466                LastChange = DateTime.UtcNow;
 0467                return true;
 468            }
 469
 0470            PlayingItemIndex++;
 0471            if (PlayingItemIndex >= _sortedPlaylist.Count)
 472            {
 0473                if (RepeatMode.Equals(GroupRepeatMode.RepeatAll))
 474                {
 0475                    PlayingItemIndex = 0;
 476                }
 477                else
 478                {
 0479                    PlayingItemIndex = _sortedPlaylist.Count - 1;
 0480                    return false;
 481                }
 482            }
 483
 0484            LastChange = DateTime.UtcNow;
 0485            return true;
 486        }
 487
 488        /// <summary>
 489        /// Sets the previous item in the queue as playing item.
 490        /// </summary>
 491        /// <returns><c>true</c> if the playing item changed; <c>false</c> otherwise.</returns>
 492        public bool Previous()
 493        {
 3494            if (GetPlaylistInternal().Count == 0)
 495            {
 3496                return false;
 497            }
 498
 0499            if (RepeatMode.Equals(GroupRepeatMode.RepeatOne))
 500            {
 0501                LastChange = DateTime.UtcNow;
 0502                return true;
 503            }
 504
 0505            PlayingItemIndex--;
 0506            if (PlayingItemIndex < 0)
 507            {
 0508                if (RepeatMode.Equals(GroupRepeatMode.RepeatAll))
 509                {
 0510                    PlayingItemIndex = _sortedPlaylist.Count - 1;
 511                }
 512                else
 513                {
 0514                    PlayingItemIndex = 0;
 0515                    return false;
 516                }
 517            }
 518
 0519            LastChange = DateTime.UtcNow;
 0520            return true;
 521        }
 522
 523        /// <summary>
 524        /// Creates a list from the array of items. Each item is given an unique playlist identifier.
 525        /// </summary>
 526        /// <returns>The list of queue items.</returns>
 527        private List<SyncPlayQueueItem> CreateQueueItemsFromArray(IReadOnlyList<Guid> items)
 528        {
 27529            var list = new List<SyncPlayQueueItem>();
 154530            foreach (var item in items)
 531            {
 50532                var queueItem = new SyncPlayQueueItem(item);
 50533                list.Add(queueItem);
 534            }
 535
 27536            return list;
 537        }
 538
 539        /// <summary>
 540        /// Gets the current playlist considering the shuffle mode.
 541        /// </summary>
 542        /// <returns>The playlist.</returns>
 543        private List<SyncPlayQueueItem> GetPlaylistInternal()
 544        {
 137545            if (ShuffleMode.Equals(GroupShuffleMode.Shuffle))
 546            {
 4547                return _shuffledPlaylist;
 548            }
 549
 133550            return _sortedPlaylist;
 551        }
 552
 553        /// <summary>
 554        /// Gets the current playing item, depending on the shuffle mode.
 555        /// </summary>
 556        /// <returns>The playing item.</returns>
 557        private SyncPlayQueueItem GetPlayingItem()
 558        {
 113559            if (PlayingItemIndex == NoPlayingItemIndex)
 560            {
 44561                return null;
 562            }
 563
 69564            if (ShuffleMode.Equals(GroupShuffleMode.Shuffle))
 565            {
 2566                return _shuffledPlaylist[PlayingItemIndex];
 567            }
 568
 67569            return _sortedPlaylist[PlayingItemIndex];
 570        }
 571    }
 572}