-
-
Notifications
You must be signed in to change notification settings - Fork 119
refactor: break up and refactor play queue components for clearer separation #676
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
Replaced `IMediaParsingService` with `IPlaylistFactory` and `PlaylistFactory` to centralize playlist creation and parsing logic. Refactored the `Playlist` class to support cloning properties from a reference playlist and added constructors for enhanced flexibility. Introduced `PlaylistCreateResult` to encapsulate playlist creation results. Updated `PlaybackControlService`, `MediaListViewModel`, and `PlaylistService` to use `IPlaylistFactory` for playlist operations. Removed `IMediaParsingService` and its implementation. Adjusted dependency injection to register the new factory. Enabled nullable reference types in several files and performed general code cleanup. Updated `Screenbox.Core.csproj` to reflect file additions and removals.
Renamed IPlaylistFactory to IMediaListFactory and updated its methods to return NextMediaList instead of Playlist. Replaced PlaylistFactory with MediaListFactory to align with the new interface.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
This PR refactors the media playlist components to improve code organization and maintainability by separating concerns and applying proper MVVM architecture patterns.
- Renames components from "Playlist" to "PlayQueue" for clearer UI terminology
- Extracts business logic from
MediaListViewModelinto dedicated stateless services (IPlaylistService,IPlaybackControlService,IMediaListFactory) - Introduces domain models (
Playlist,PlaybackNavigationResult,NextMediaList) to separate data from presentation logic
Reviewed Changes
Copilot reviewed 22 out of 22 changed files in this pull request and generated 9 comments.
Show a summary per file
| File | Description |
|---|---|
| Screenbox/Screenbox.csproj | Updates project references from PlaylistView to PlayQueueControl |
| Screenbox/Pages/PlayerPage.xaml.cs | Updates references to use renamed PlayQueue control |
| Screenbox/Pages/PlayerPage.xaml | Updates XAML to use PlayQueueControl instead of PlaylistView |
| Screenbox/Pages/PlayQueuePage.xaml.cs | Removes unused import and updates control references |
| Screenbox/Pages/PlayQueuePage.xaml | Updates binding references to use PlayQueue control |
| Screenbox/Controls/PlayQueueControl.xaml.cs | Renames class from PlaylistView to PlayQueueControl and updates ViewModel type |
| Screenbox/Controls/PlayQueueControl.xaml | Updates class reference and event handler names |
| Screenbox.Core/ViewModels/PlayQueueViewModel.cs | Renames class from PlaylistViewModel to PlayQueueViewModel |
| Screenbox.Core/ViewModels/MediaListViewModel.cs | Major refactor to use injected services and domain models instead of inline business logic |
| Screenbox.Core/Services/PlaylistService.cs | New stateless service for playlist operations like shuffling and neighboring file management |
| Screenbox.Core/Services/PlaybackControlService.cs | New service handling playback navigation logic |
| Screenbox.Core/Services/IPlaylistService.cs | Interface for playlist operations |
| Screenbox.Core/Services/IPlaybackControlService.cs | Interface for playback control operations |
| Screenbox.Core/Screenbox.Core.csproj | Adds new service and model files to project |
| Screenbox.Core/Models/ShuffleBackup.cs | Extracted model for shuffle backup data |
| Screenbox.Core/Models/Playlist.cs | New domain model representing playlist data |
| Screenbox.Core/Models/PlaybackNavigationResult.cs | Model for playback navigation results |
| Screenbox.Core/Models/NextMediaList.cs | Model for media list parsing results |
| Screenbox.Core/Helpers/CollectionExtensions.cs | Minor import reordering |
| Screenbox.Core/Factories/MediaListFactory.cs | Extracted factory for media list creation and parsing |
| Screenbox.Core/Factories/IMediaListFactory.cs | Interface for media list factory |
| Screenbox.Core/Common/ServiceHelpers.cs | Updates service registration for renamed and new services |
Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.
| if (_delayPlay is MediaViewModel media && Items.Contains(media)) | ||
| { | ||
| PlaySingle(media); | ||
| await TryEnqueueAndPlayPlaylistAsync(media); | ||
| } | ||
| else | ||
| { | ||
| ClearPlaylist(); | ||
| await EnqueueAndPlay(_delayPlay); | ||
| } | ||
| _delayPlay = null; |
Copilot
AI
Sep 27, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Async lambda in TryEnqueue can cause unhandled exceptions. The async operations inside the lambda should be wrapped in try-catch blocks or the lambda should be fire-and-forget with proper error handling.
| if (_delayPlay is MediaViewModel media && Items.Contains(media)) | |
| { | |
| PlaySingle(media); | |
| await TryEnqueueAndPlayPlaylistAsync(media); | |
| } | |
| else | |
| { | |
| ClearPlaylist(); | |
| await EnqueueAndPlay(_delayPlay); | |
| } | |
| _delayPlay = null; | |
| try | |
| { | |
| if (_delayPlay is MediaViewModel media && Items.Contains(media)) | |
| { | |
| PlaySingle(media); | |
| await TryEnqueueAndPlayPlaylistAsync(media); | |
| } | |
| else | |
| { | |
| ClearPlaylist(); | |
| await EnqueueAndPlay(_delayPlay); | |
| } | |
| _delayPlay = null; | |
| } | |
| catch (Exception ex) | |
| { | |
| SentrySdk.CaptureException(ex); | |
| } |
| switch (args.Button) | ||
| { | ||
| case SystemMediaTransportControlsButton.Next: | ||
| await NextAsync(); | ||
| break; | ||
| case SystemMediaTransportControlsButton.Previous: | ||
| await PreviousAsync(); | ||
| break; | ||
| } |
Copilot
AI
Sep 27, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Async lambda in TryEnqueue lacks error handling. Exceptions from NextAsync() or PreviousAsync() could become unhandled exceptions.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
Copilot reviewed 22 out of 22 changed files in this pull request and generated 4 comments.
Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.
| _mediaPlayer.PlaybackItem = vm.Item.Value; | ||
| _mediaPlayer.Play(); |
Copilot
AI
Sep 29, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Potential null reference exception if _mediaPlayer is null. The null check at line 308 guards against this, but the assignment should be inside that check or use null-conditional operator.
| _mediaPlayer.PlaybackItem = vm.Item.Value; | |
| _mediaPlayer.Play(); | |
| _mediaPlayer!.PlaybackItem = vm.Item.Value; | |
| _mediaPlayer?.Play(); |
|
|
||
| public Playlist RestoreFromShuffle(Playlist playlist) | ||
| { | ||
| Guard.IsNotNull(playlist.ShuffleBackup, nameof(playlist.ShuffleBackup)); |
Copilot
AI
Sep 29, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using Guard.IsNotNull with nameof(playlist.ShuffleBackup) will throw an exception with the property name rather than the parameter name. Consider using a more descriptive message or checking the condition explicitly and throwing a more meaningful exception.
| Guard.IsNotNull(playlist.ShuffleBackup, nameof(playlist.ShuffleBackup)); | |
| if (playlist.ShuffleBackup is null) | |
| throw new InvalidOperationException("Cannot restore from shuffle: ShuffleBackup is null. Ensure the playlist was previously shuffled."); |
Updated `PlaybackNavigationResult` to enforce non-nullable `NextItem`, simplifying its structure and removing unused properties and constructors. Updated `IPlaybackControlService` and `PlaybackControlService` methods to return nullable `PlaybackNavigationResult?` instead of creating instances with `null` values. Simplified logic in `MediaListViewModel` to handle nullable results consistently.
|
@United600 Can you help me test this PR? It's a huge refactor that is needed for the persistent playlist feature in #514. I have done some testing on my end, but I just want to make sure there is no regression. |
Of course, I'd be happy to. Are there any specific actions or features I should pay special attention to? |
|
It seems that opening files directly from the explorer doesn't work. Gravacao.2025-09-29.192435.mp4Reordering the play queue also leads to unexpected behavior, and possible locks (and crashes) when clicking the Previous button. Gravacao.2025-09-29.193300.mp4 |
Introduced `MediaViewModelFactory` in `MediaListViewModel` to short circut media list parsing whenever possible. Replaced `UpdateItemsFromPlaylist` with `LoadFromPlaylist` and `SaveToPlaylist` methods, separating UI syncing and playlist state management. Consolidated file handling logic into a new `ParseAndPlayAsync` method, unifying media type handling. Added `CreatePlaylistAndPlay` to reduce duplication. Enhanced shuffle handling with robust backup updates in `OnCollectionChanged`. Updated navigation methods (`NextAsync`, `PreviousAsync`, `OnEndReached`) to save playlist state before actions. Removed redundant methods (`HandleSingleFileAsync`, `HandleMultipleFilesAsync`, `EnqueueAndPlay`).
Consolidated playlist-related functionality into a single `Playlist` class by removing the redundant `PlaylistInfo` class. Updated all references to use the new `Playlist` structure. - Merged `PlaylistInfo` properties (`CurrentIndex`, `ShuffleMode`, `LastUpdated`) into `Playlist`. - Updated `MessengerExtensions` and `PlaylistRequestMessage` to use `Playlist`. - Refactored `IPlaybackControlService` and `PlaybackControlService` to handle neighboring files explicitly. - Removed `NeighboringFilesQuery` from `Playlist` and passed it as a parameter where needed. - Refactored `MediaListViewModel` and other view models to align with the new `Playlist` structure. - Updated `PlaylistService` to simplify shuffle and restore logic.
Refactored `RestoreFromShuffle` in `PlaylistService` to simplify playlist restoration using a constructor. Introduced `_deferCollectionChanged` in `MediaListViewModel` to optimize bulk collection updates and avoid excessive notifications. Updated `LoadFromPlaylist` to handle large playlists efficiently by clearing and repopulating the `Items` collection when necessary. Modified `ClearPlaylist` to defer collection change notifications during clearing. Replaced direct `_playlist` assignments with local variables and consistent use of `LoadFromPlaylist` for better clarity and reduced side effects. Added a guard clause in `OnCollectionChanged` to skip processing during deferred updates. Cleaned up `MediaViewModel` by removing unused imports and commented-out code. Commented out the `Name` assignment in `UpdateSource` for potential future adjustments.
|
@United600 Can you help review this PR again? I have fixed the issues you mentioned. (And pretty much refactored the refactor.) The focus is on how the player queue behaves and anything that interacts with it. I have covered the following cases:
Let me know if you spot any issues. |
|
So far no issues 👌. I think this is a good opportunity to switch to file-scoped namespaces in the modified files. Since you’re already working on this area, here are some additional improvements to consider:
Gravacao.2025-10-15.224631.mp4
Gravacao.2025-10-15.224919.mp4 |
|
The two issues you mentioned also exist in the latest version. I have some ideas on how to fix them, but I will create separate PRs for them. If there is no regression, I will go ahead and merge this PR then, since it's already too big. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
Copilot reviewed 31 out of 31 changed files in this pull request and generated 4 comments.
Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.
Fair enough. I only mentioned it because it stood out while I was testing the changes. |
|
One regression slipped through 😢. Gravacao.2025-10-17.192426.mp4 |
#697 should fix this |
* Initial plan * feat: pass NeighboringFilesQuery through PlaylistCurrentItemChangedMessage to fix subtitle auto-loading Co-authored-by: huynhsontung <31434093+huynhsontung@users.noreply.github.com> * refactor: use ToArray() instead of ToList() for better performance Co-authored-by: huynhsontung <31434093+huynhsontung@users.noreply.github.com> * neighborhood query should be set before active media is changed * better subtitle file name matching logic --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: huynhsontung <31434093+huynhsontung@users.noreply.github.com> Co-authored-by: Tung Huynh <tung75605@gmail.com>
MediaListViewModelfrom a monolithic class into a clean MVVM architecture, separating business logic, domain models, and presentation layer for maintainability and testability.IPlaylistService,IPlaybackControlService,IMediaListFactory) to encapsulate playlist operations, navigation logic, and media parsing, all injected via dependency injection.