diff --git a/Screenbox.Core/Common/ServiceHelpers.cs b/Screenbox.Core/Common/ServiceHelpers.cs index 9e085cc3c..883b25e87 100644 --- a/Screenbox.Core/Common/ServiceHelpers.cs +++ b/Screenbox.Core/Common/ServiceHelpers.cs @@ -1,14 +1,31 @@ using Microsoft.Extensions.DependencyInjection; +using Screenbox.Core.Contexts; using Screenbox.Core.Factories; using Screenbox.Core.Helpers; using Screenbox.Core.Services; using Screenbox.Core.ViewModels; namespace Screenbox.Core; + public static class ServiceHelpers { public static void PopulateCoreServices(ServiceCollection services) { + // Contexts + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + // View models services.AddTransient(); services.AddTransient(); @@ -38,9 +55,9 @@ public static void PopulateCoreServices(ServiceCollection services) services.AddTransient(); services.AddTransient(); services.AddTransient(); - services.AddSingleton(); // Shared between many pages - services.AddSingleton(); // Avoid thread lock - services.AddSingleton(); // Global playlist + services.AddTransient(); + services.AddTransient(); + services.AddTransient(); // Misc services.AddTransient(); diff --git a/Screenbox.Core/Contexts/AlbumFactoryContext.cs b/Screenbox.Core/Contexts/AlbumFactoryContext.cs new file mode 100644 index 000000000..24fcfb60f --- /dev/null +++ b/Screenbox.Core/Contexts/AlbumFactoryContext.cs @@ -0,0 +1,12 @@ +#nullable enable + +using System.Collections.Generic; +using Screenbox.Core.ViewModels; + +namespace Screenbox.Core.Contexts; + +public sealed class AlbumFactoryContext +{ + public Dictionary Albums { get; } = new(); + public AlbumViewModel? UnknownAlbum { get; set; } +} diff --git a/Screenbox.Core/Contexts/ArtistFactoryContext.cs b/Screenbox.Core/Contexts/ArtistFactoryContext.cs new file mode 100644 index 000000000..5d9729a31 --- /dev/null +++ b/Screenbox.Core/Contexts/ArtistFactoryContext.cs @@ -0,0 +1,12 @@ +#nullable enable + +using System.Collections.Generic; +using Screenbox.Core.ViewModels; + +namespace Screenbox.Core.Contexts; + +public sealed class ArtistFactoryContext +{ + public Dictionary Artists { get; } = new(); + public ArtistViewModel? UnknownArtist { get; set; } +} diff --git a/Screenbox.Core/Contexts/CastContext.cs b/Screenbox.Core/Contexts/CastContext.cs new file mode 100644 index 000000000..3bb43f2ba --- /dev/null +++ b/Screenbox.Core/Contexts/CastContext.cs @@ -0,0 +1,13 @@ +#nullable enable + +using System.Collections.Generic; +using LibVLCSharp.Shared; +using Screenbox.Core.Models; + +namespace Screenbox.Core.Contexts; + +public sealed class CastContext +{ + public List Renderers { get; } = new(); + public RendererDiscoverer? Discoverer { get; set; } +} diff --git a/Screenbox.Core/Contexts/LastPositionContext.cs b/Screenbox.Core/Contexts/LastPositionContext.cs new file mode 100644 index 000000000..a0428164c --- /dev/null +++ b/Screenbox.Core/Contexts/LastPositionContext.cs @@ -0,0 +1,15 @@ +#nullable enable + +using System; +using System.Collections.Generic; +using Screenbox.Core.Models; + +namespace Screenbox.Core.Contexts; + +public sealed class LastPositionContext +{ + public DateTimeOffset LastUpdated { get; set; } + public List LastPositions { get; set; } = new(65); + public MediaLastPosition? UpdateCache { get; set; } + public string? RemoveCache { get; set; } +} diff --git a/Screenbox.Core/Contexts/LibVlcContext.cs b/Screenbox.Core/Contexts/LibVlcContext.cs new file mode 100644 index 000000000..175f55c1e --- /dev/null +++ b/Screenbox.Core/Contexts/LibVlcContext.cs @@ -0,0 +1,13 @@ +#nullable enable + +using LibVLCSharp.Shared; +using Screenbox.Core.Playback; + +namespace Screenbox.Core.Contexts; + +public sealed class LibVlcContext +{ + public VlcMediaPlayer? MediaPlayer { get; set; } + public LibVLC? LibVlc { get; set; } + public bool UseFutureAccessList { get; set; } = true; +} diff --git a/Screenbox.Core/Contexts/LibraryContext.cs b/Screenbox.Core/Contexts/LibraryContext.cs new file mode 100644 index 000000000..0898ca255 --- /dev/null +++ b/Screenbox.Core/Contexts/LibraryContext.cs @@ -0,0 +1,31 @@ +#nullable enable + +using System.Collections.Generic; +using System.Threading; +using Screenbox.Core.ViewModels; +using Windows.Devices.Enumeration; +using Windows.Storage; +using Windows.Storage.Search; +using Windows.System; + +namespace Screenbox.Core.Contexts; + +public sealed class LibraryContext +{ + public StorageLibrary? MusicLibrary { get; set; } + public StorageLibrary? VideosLibrary { get; set; } + public bool IsLoadingVideos { get; set; } + public bool IsLoadingMusic { get; set; } + public StorageFileQueryResult? MusicLibraryQueryResult { get; set; } + public StorageFileQueryResult? VideosLibraryQueryResult { get; set; } + public List Songs { get; set; } = new(); + public List Videos { get; set; } = new(); + public CancellationTokenSource? MusicFetchCancellation { get; set; } + public CancellationTokenSource? VideosFetchCancellation { get; set; } + public bool MusicChangeTrackerAvailable { get; set; } + public bool VideosChangeTrackerAvailable { get; set; } + public DispatcherQueueTimer? MusicRefreshTimer { get; set; } + public DispatcherQueueTimer? VideosRefreshTimer { get; set; } + public DispatcherQueueTimer? StorageDeviceRefreshTimer { get; set; } + public DeviceWatcher? PortableStorageDeviceWatcher { get; set; } +} diff --git a/Screenbox.Core/Contexts/MediaListContext.cs b/Screenbox.Core/Contexts/MediaListContext.cs new file mode 100644 index 000000000..df565cd79 --- /dev/null +++ b/Screenbox.Core/Contexts/MediaListContext.cs @@ -0,0 +1,26 @@ +#nullable enable + +using System.Collections.Generic; +using System.Threading; +using Screenbox.Core.Models; +using Screenbox.Core.Playback; +using Screenbox.Core.ViewModels; +using Windows.Media; +using Windows.Storage.Search; + +namespace Screenbox.Core.Contexts; + +public sealed class MediaListContext +{ + public Playlist Playlist { get; set; } = new(); + public List MediaBuffer { get; set; } = new(); + public IMediaPlayer? MediaPlayer { get; set; } + public object? DelayPlay { get; set; } + public bool DeferCollectionChanged { get; set; } + public StorageFileQueryResult? NeighboringFilesQuery { get; set; } + public CancellationTokenSource? PlayFilesCancellation { get; set; } + public MediaPlaybackAutoRepeatMode RepeatMode { get; set; } + public bool ShuffleMode { get; set; } + public MediaViewModel? CurrentItem { get; set; } + public int CurrentIndex { get; set; } = -1; +} diff --git a/Screenbox.Core/Contexts/MediaViewModelFactoryContext.cs b/Screenbox.Core/Contexts/MediaViewModelFactoryContext.cs new file mode 100644 index 000000000..e349cc6ad --- /dev/null +++ b/Screenbox.Core/Contexts/MediaViewModelFactoryContext.cs @@ -0,0 +1,13 @@ +#nullable enable + +using System; +using System.Collections.Generic; +using Screenbox.Core.ViewModels; + +namespace Screenbox.Core.Contexts; + +public sealed class MediaViewModelFactoryContext +{ + public Dictionary> References { get; } = new(); + public int ReferencesCleanUpThreshold { get; set; } = 1000; +} diff --git a/Screenbox.Core/Contexts/NavigationContext.cs b/Screenbox.Core/Contexts/NavigationContext.cs new file mode 100644 index 000000000..fb70e7585 --- /dev/null +++ b/Screenbox.Core/Contexts/NavigationContext.cs @@ -0,0 +1,18 @@ +#nullable enable + +using System; +using System.Collections.Generic; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Controls; + +namespace Screenbox.Core.Contexts; + +public sealed class NavigationContext +{ + public Dictionary NavigationStates { get; } = new(); + public Dictionary PageStates { get; } = new(); + public NavigationViewDisplayMode NavigationViewDisplayMode { get; set; } + public Thickness ScrollBarMargin { get; set; } + public Thickness FooterBottomPaddingMargin { get; set; } + public double FooterBottomPaddingHeight { get; set; } +} diff --git a/Screenbox.Core/Contexts/NotificationContext.cs b/Screenbox.Core/Contexts/NotificationContext.cs new file mode 100644 index 000000000..aab3d6963 --- /dev/null +++ b/Screenbox.Core/Contexts/NotificationContext.cs @@ -0,0 +1,8 @@ +#nullable enable + +namespace Screenbox.Core.Contexts; + +public sealed class NotificationContext +{ + public string? ProgressTitle { get; set; } +} diff --git a/Screenbox.Core/Contexts/TransportControlsContext.cs b/Screenbox.Core/Contexts/TransportControlsContext.cs new file mode 100644 index 000000000..5cfa67e1a --- /dev/null +++ b/Screenbox.Core/Contexts/TransportControlsContext.cs @@ -0,0 +1,10 @@ +#nullable enable + +using System; + +namespace Screenbox.Core.Contexts; + +public sealed class TransportControlsContext +{ + public DateTime LastUpdated { get; set; } = DateTime.MinValue; +} diff --git a/Screenbox.Core/Contexts/VolumeContext.cs b/Screenbox.Core/Contexts/VolumeContext.cs new file mode 100644 index 000000000..150d9178b --- /dev/null +++ b/Screenbox.Core/Contexts/VolumeContext.cs @@ -0,0 +1,14 @@ +#nullable enable + +using Screenbox.Core.Playback; + +namespace Screenbox.Core.Contexts; + +public sealed class VolumeContext +{ + public int MaxVolume { get; set; } + public int Volume { get; set; } + public bool IsMute { get; set; } + public IMediaPlayer? MediaPlayer { get; set; } + public bool IsInitialized { get; set; } +} diff --git a/Screenbox.Core/Contexts/WindowContext.cs b/Screenbox.Core/Contexts/WindowContext.cs new file mode 100644 index 000000000..5dda1fb57 --- /dev/null +++ b/Screenbox.Core/Contexts/WindowContext.cs @@ -0,0 +1,12 @@ +#nullable enable + +using Screenbox.Core.Enums; +using Windows.UI.Core; + +namespace Screenbox.Core.Contexts; + +public sealed class WindowContext +{ + public CoreCursor? Cursor { get; set; } + public WindowViewMode ViewMode { get; set; } +} diff --git a/Screenbox.Core/Factories/AlbumViewModelFactory.cs b/Screenbox.Core/Factories/AlbumViewModelFactory.cs index 9bfefd0f3..d47f3591f 100644 --- a/Screenbox.Core/Factories/AlbumViewModelFactory.cs +++ b/Screenbox.Core/Factories/AlbumViewModelFactory.cs @@ -1,5 +1,6 @@ -#nullable enable +#nullable enable +using Screenbox.Core.Contexts; using Screenbox.Core.Enums; using Screenbox.Core.Services; using Screenbox.Core.ViewModels; @@ -12,19 +13,17 @@ namespace Screenbox.Core.Factories { public sealed class AlbumViewModelFactory { - public AlbumViewModel UnknownAlbum { get; } + public AlbumViewModel UnknownAlbum => State.UnknownAlbum!; - public IReadOnlyCollection AllAlbums { get; } - - private readonly Dictionary _allAlbums; + public IReadOnlyCollection AllAlbums => State.Albums.Values; private readonly IResourceService _resourceService; + private readonly AlbumFactoryContext State; - public AlbumViewModelFactory(IResourceService resourceService) + public AlbumViewModelFactory(IResourceService resourceService, AlbumFactoryContext state) { _resourceService = resourceService; - UnknownAlbum = new AlbumViewModel(resourceService.GetString(ResourceName.UnknownAlbum), resourceService.GetString(ResourceName.UnknownArtist)); - _allAlbums = new Dictionary(); - AllAlbums = _allAlbums.Values; + State = state; + State.UnknownAlbum ??= new AlbumViewModel(resourceService.GetString(ResourceName.UnknownAlbum), resourceService.GetString(ResourceName.UnknownArtist)); } public AlbumViewModel GetAlbumFromName(string albumName, string artistName) @@ -37,7 +36,7 @@ public AlbumViewModel GetAlbumFromName(string albumName, string artistName) string albumKey = albumName.Trim().ToLower(CultureInfo.CurrentUICulture); string artistKey = artistName.Trim().ToLower(CultureInfo.CurrentUICulture); string key = GetAlbumKey(albumKey, artistKey); - return _allAlbums.GetValueOrDefault(key, UnknownAlbum); + return State.Albums.GetValueOrDefault(key, UnknownAlbum); } public AlbumViewModel AddSongToAlbum(MediaViewModel song, string albumName, string artistName, uint year) @@ -68,7 +67,7 @@ public AlbumViewModel AddSongToAlbum(MediaViewModel song, string albumName, stri album.RelatedSongs.Add(song); UpdateAlbumDateAdded(album, song); - return _allAlbums[key] = album; + return State.Albums[key] = album; } public void Remove(MediaViewModel song) @@ -81,18 +80,18 @@ public void Remove(MediaViewModel song) { string albumKey = album.Name.Trim().ToLower(CultureInfo.CurrentUICulture); string artistKey = album.ArtistName.Trim().ToLower(CultureInfo.CurrentUICulture); - _allAlbums.Remove(GetAlbumKey(albumKey, artistKey)); + State.Albums.Remove(GetAlbumKey(albumKey, artistKey)); } } public void Compact() { List albumKeysToRemove = - _allAlbums.Where(p => p.Value.RelatedSongs.Count == 0).Select(p => p.Key).ToList(); + State.Albums.Where(p => p.Value.RelatedSongs.Count == 0).Select(p => p.Key).ToList(); foreach (string albumKey in albumKeysToRemove) { - _allAlbums.Remove(albumKey); + State.Albums.Remove(albumKey); } } @@ -106,7 +105,7 @@ public void Clear() UnknownAlbum.RelatedSongs.Clear(); UnknownAlbum.DateAdded = default; - foreach ((string _, AlbumViewModel album) in _allAlbums) + foreach ((string _, AlbumViewModel album) in State.Albums) { foreach (MediaViewModel media in album.RelatedSongs) { @@ -114,7 +113,7 @@ public void Clear() } } - _allAlbums.Clear(); + State.Albums.Clear(); } private static void UpdateAlbumDateAdded(AlbumViewModel album, MediaViewModel song) diff --git a/Screenbox.Core/Factories/ArtistViewModelFactory.cs b/Screenbox.Core/Factories/ArtistViewModelFactory.cs index 0807eea44..de8316b5c 100644 --- a/Screenbox.Core/Factories/ArtistViewModelFactory.cs +++ b/Screenbox.Core/Factories/ArtistViewModelFactory.cs @@ -1,5 +1,6 @@ -#nullable enable +#nullable enable +using Screenbox.Core.Contexts; using Screenbox.Core.Enums; using Screenbox.Core.Services; using Screenbox.Core.ViewModels; @@ -13,19 +14,16 @@ namespace Screenbox.Core.Factories { public sealed class ArtistViewModelFactory { - public ArtistViewModel UnknownArtist { get; } - - public IReadOnlyCollection AllArtists { get; } - - private readonly Dictionary _allArtists; + public ArtistViewModel UnknownArtist => State.UnknownArtist!; + public IReadOnlyCollection AllArtists => State.Artists.Values; private static readonly string[] ArtistNameSeparators = { ",", ", ", "; " }; + private readonly ArtistFactoryContext State; - public ArtistViewModelFactory(IResourceService resourceService) + public ArtistViewModelFactory(IResourceService resourceService, ArtistFactoryContext state) { - _allArtists = new Dictionary(); - AllArtists = _allArtists.Values; - UnknownArtist = new ArtistViewModel(resourceService.GetString(ResourceName.UnknownArtist)); + State = state; + State.UnknownArtist ??= new ArtistViewModel(resourceService.GetString(ResourceName.UnknownArtist)); } public ArtistViewModel[] ParseArtists(string artist) @@ -72,7 +70,7 @@ public ArtistViewModel GetArtistFromName(string artistName) return UnknownArtist; string key = artistName.Trim().ToLower(CultureInfo.CurrentUICulture); - return _allArtists.GetValueOrDefault(key, UnknownArtist); + return State.Artists.GetValueOrDefault(key, UnknownArtist); } public ArtistViewModel AddSongToArtist(MediaViewModel song, string artistName) @@ -93,7 +91,7 @@ public ArtistViewModel AddSongToArtist(MediaViewModel song, string artistName) string key = artistName.Trim().ToLower(CultureInfo.CurrentUICulture); artist = new ArtistViewModel(artistName); artist.RelatedSongs.Add(song); - return _allArtists[key] = artist; + return State.Artists[key] = artist; } public void Remove(MediaViewModel song) @@ -104,7 +102,7 @@ public void Remove(MediaViewModel song) if (artist.RelatedSongs.Count == 0) { string artistKey = artist.Name.Trim().ToLower(CultureInfo.CurrentUICulture); - _allArtists.Remove(artistKey); + State.Artists.Remove(artistKey); } } @@ -114,11 +112,11 @@ public void Remove(MediaViewModel song) public void Compact() { List albumKeysToRemove = - _allArtists.Where(p => p.Value.RelatedSongs.Count == 0).Select(p => p.Key).ToList(); + State.Artists.Where(p => p.Value.RelatedSongs.Count == 0).Select(p => p.Key).ToList(); foreach (string albumKey in albumKeysToRemove) { - _allArtists.Remove(albumKey); + State.Artists.Remove(albumKey); } } @@ -131,7 +129,7 @@ public void Clear() UnknownArtist.RelatedSongs.Clear(); - foreach ((string _, ArtistViewModel artist) in _allArtists) + foreach ((string _, ArtistViewModel artist) in State.Artists) { foreach (MediaViewModel media in artist.RelatedSongs) { @@ -139,7 +137,7 @@ public void Clear() } } - _allArtists.Clear(); + State.Artists.Clear(); } } } diff --git a/Screenbox.Core/Factories/MediaViewModelFactory.cs b/Screenbox.Core/Factories/MediaViewModelFactory.cs index ad4fa7885..2e79b7f36 100644 --- a/Screenbox.Core/Factories/MediaViewModelFactory.cs +++ b/Screenbox.Core/Factories/MediaViewModelFactory.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Linq; using LibVLCSharp.Shared; +using Screenbox.Core.Contexts; using Screenbox.Core.Playback; using Screenbox.Core.Services; using Windows.Storage; @@ -14,12 +15,12 @@ namespace Screenbox.Core.Factories; public sealed class MediaViewModelFactory { private readonly LibVlcService _libVlcService; - private readonly Dictionary> _references = new(); - private int _referencesCleanUpThreshold = 1000; + private readonly MediaViewModelFactoryContext State; - public MediaViewModelFactory(LibVlcService libVlcService) + public MediaViewModelFactory(LibVlcService libVlcService, MediaViewModelFactoryContext state) { _libVlcService = libVlcService; + State = state; } public MediaViewModel GetTransient(StorageFile file) @@ -52,7 +53,7 @@ public MediaViewModel GetTransient(Media media) public MediaViewModel GetSingleton(StorageFile file) { string id = file.Path; - if (_references.TryGetValue(id, out WeakReference reference) && + if (State.References.TryGetValue(id, out WeakReference reference) && reference.TryGetTarget(out MediaViewModel instance)) { // Prefer storage file source @@ -69,7 +70,7 @@ public MediaViewModel GetSingleton(StorageFile file) instance = new MediaViewModel(_libVlcService, file); if (!string.IsNullOrEmpty(id)) { - _references[id] = new WeakReference(instance); + State.References[id] = new WeakReference(instance); CleanUpStaleReferences(); } @@ -79,14 +80,14 @@ public MediaViewModel GetSingleton(StorageFile file) public MediaViewModel GetSingleton(Uri uri) { string id = uri.OriginalString; - if (_references.TryGetValue(id, out WeakReference reference) && + if (State.References.TryGetValue(id, out WeakReference reference) && reference.TryGetTarget(out MediaViewModel instance)) return instance; // No existing reference, create new instance instance = new MediaViewModel(_libVlcService, uri); if (!string.IsNullOrEmpty(id)) { - _references[id] = new WeakReference(instance); + State.References[id] = new WeakReference(instance); CleanUpStaleReferences(); } @@ -95,15 +96,15 @@ public MediaViewModel GetSingleton(Uri uri) private void CleanUpStaleReferences() { - if (_references.Count < _referencesCleanUpThreshold) return; - string[] keysToRemove = _references + if (State.References.Count < State.ReferencesCleanUpThreshold) return; + string[] keysToRemove = State.References .Where(pair => !pair.Value.TryGetTarget(out MediaViewModel _)) .Select(pair => pair.Key).ToArray(); foreach (string key in keysToRemove) { - _references.Remove(key); + State.References.Remove(key); } - _referencesCleanUpThreshold = Math.Max(_references.Count * 2, _referencesCleanUpThreshold); + State.ReferencesCleanUpThreshold = Math.Max(State.References.Count * 2, State.ReferencesCleanUpThreshold); } } diff --git a/Screenbox.Core/Helpers/LastPositionTracker.cs b/Screenbox.Core/Helpers/LastPositionTracker.cs index e612b85db..945c542d6 100644 --- a/Screenbox.Core/Helpers/LastPositionTracker.cs +++ b/Screenbox.Core/Helpers/LastPositionTracker.cs @@ -2,6 +2,7 @@ using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Messaging; +using Screenbox.Core.Contexts; using Screenbox.Core.Messages; using Screenbox.Core.Models; using Screenbox.Core.Services; @@ -20,18 +21,21 @@ public sealed class LastPositionTracker : ObservableRecipient, private const int Capacity = 64; private const string SaveFileName = "last_positions.bin"; - public bool IsLoaded => LastUpdated != default; + public bool IsLoaded => State.LastUpdated != default; - public DateTimeOffset LastUpdated { get; private set; } + public DateTimeOffset LastUpdated + { + get => State.LastUpdated; + private set => State.LastUpdated = value; + } private readonly IFilesService _filesService; - private List _lastPositions = new(Capacity + 1); - private MediaLastPosition? _updateCache; - private string? _removeCache; + private readonly LastPositionContext State; - public LastPositionTracker(IFilesService filesService) + public LastPositionTracker(IFilesService filesService, LastPositionContext state) { _filesService = filesService; + State = state; IsActive = true; } @@ -44,32 +48,32 @@ public void Receive(SuspendingMessage message) public void UpdateLastPosition(string location, TimeSpan position) { LastUpdated = DateTimeOffset.Now; - _removeCache = null; - MediaLastPosition? item = _updateCache; + State.RemoveCache = null; + MediaLastPosition? item = State.UpdateCache; if (item?.Location == location) { item.Position = position; - if (_lastPositions.FirstOrDefault() != item) + if (State.LastPositions.FirstOrDefault() != item) { - int index = _lastPositions.IndexOf(item); + int index = State.LastPositions.IndexOf(item); if (index >= 0) { - _lastPositions.RemoveAt(index); + State.LastPositions.RemoveAt(index); } - _lastPositions.Insert(0, item); + State.LastPositions.Insert(0, item); } } else { - item = _lastPositions.Find(x => x.Location == location); + item = State.LastPositions.Find(x => x.Location == location); if (item == null) { item = new MediaLastPosition(location, position); - _lastPositions.Insert(0, item); - if (_lastPositions.Count > Capacity) + State.LastPositions.Insert(0, item); + if (State.LastPositions.Count > Capacity) { - _lastPositions.RemoveAt(Capacity); + State.LastPositions.RemoveAt(Capacity); } } else @@ -78,27 +82,27 @@ public void UpdateLastPosition(string location, TimeSpan position) } } - _updateCache = item; + State.UpdateCache = item; } public TimeSpan GetPosition(string location) { - return _lastPositions.Find(x => x.Location == location)?.Position ?? TimeSpan.Zero; + return State.LastPositions.Find(x => x.Location == location)?.Position ?? TimeSpan.Zero; } public void RemovePosition(string location) { LastUpdated = DateTimeOffset.Now; - if (_removeCache == location) return; - _lastPositions.RemoveAll(x => x.Location == location); - _removeCache = location; + if (State.RemoveCache == location) return; + State.LastPositions.RemoveAll(x => x.Location == location); + State.RemoveCache = location; } public async Task SaveToDiskAsync() { try { - await _filesService.SaveToDiskAsync(ApplicationData.Current.TemporaryFolder, SaveFileName, _lastPositions.ToList()); + await _filesService.SaveToDiskAsync(ApplicationData.Current.TemporaryFolder, SaveFileName, State.LastPositions.ToList()); } catch (FileLoadException) { @@ -113,7 +117,7 @@ public async Task LoadFromDiskAsync() List lastPositions = await _filesService.LoadFromDiskAsync>(ApplicationData.Current.TemporaryFolder, SaveFileName); lastPositions.Capacity = Capacity; - _lastPositions = lastPositions; + State.LastPositions = lastPositions; LastUpdated = DateTimeOffset.UtcNow; } catch (FileNotFoundException) diff --git a/Screenbox.Core/Models/MediaLastPosition.cs b/Screenbox.Core/Models/MediaLastPosition.cs index 4dd0ee089..f059c6f7d 100644 --- a/Screenbox.Core/Models/MediaLastPosition.cs +++ b/Screenbox.Core/Models/MediaLastPosition.cs @@ -4,7 +4,7 @@ namespace Screenbox.Core.Models { [ProtoContract] - internal record MediaLastPosition(string Location, TimeSpan Position) + public record MediaLastPosition(string Location, TimeSpan Position) { [ProtoMember(1)] public string Location { get; set; } = Location; [ProtoMember(2)] public TimeSpan Position { get; set; } = Position; diff --git a/Screenbox.Core/Screenbox.Core.csproj b/Screenbox.Core/Screenbox.Core.csproj index f843e4706..34b82ac79 100644 --- a/Screenbox.Core/Screenbox.Core.csproj +++ b/Screenbox.Core/Screenbox.Core.csproj @@ -127,6 +127,19 @@ + + + + + + + + + + + + + diff --git a/Screenbox.Core/Services/CastService.cs b/Screenbox.Core/Services/CastService.cs index e3b5b977c..142979280 100644 --- a/Screenbox.Core/Services/CastService.cs +++ b/Screenbox.Core/Services/CastService.cs @@ -1,11 +1,11 @@ #nullable enable +using System; using CommunityToolkit.Diagnostics; using LibVLCSharp.Shared; +using Screenbox.Core.Contexts; using Screenbox.Core.Events; using Screenbox.Core.Models; -using System; -using System.Collections.Generic; namespace Screenbox.Core.Services { @@ -15,13 +15,12 @@ public sealed class CastService : ICastService public event EventHandler? RendererLost; private readonly LibVlcService _libVlcService; - private readonly List _renderers; - private RendererDiscoverer? _discoverer; + private readonly CastContext State; - public CastService(LibVlcService libVlcService) + public CastService(LibVlcService libVlcService, CastContext state) { _libVlcService = libVlcService; - _renderers = new List(); + State = state; } public bool SetActiveRenderer(Renderer? renderer) @@ -34,39 +33,39 @@ public bool Start() Stop(); LibVLC? libVlc = _libVlcService.LibVlc; Guard.IsNotNull(libVlc, nameof(libVlc)); - _discoverer = new RendererDiscoverer(libVlc); - _discoverer.ItemAdded += DiscovererOnItemAdded; - _discoverer.ItemDeleted += DiscovererOnItemDeleted; - return _discoverer.Start(); + State.Discoverer = new RendererDiscoverer(libVlc); + State.Discoverer.ItemAdded += DiscovererOnItemAdded; + State.Discoverer.ItemDeleted += DiscovererOnItemDeleted; + return State.Discoverer.Start(); } public void Stop() { - if (_discoverer == null) return; - _discoverer.Stop(); - _discoverer.ItemAdded -= DiscovererOnItemAdded; - _discoverer.ItemDeleted -= DiscovererOnItemDeleted; - _discoverer.Dispose(); - _discoverer = null; - foreach (Renderer renderer in _renderers) + if (State.Discoverer == null) return; + State.Discoverer.Stop(); + State.Discoverer.ItemAdded -= DiscovererOnItemAdded; + State.Discoverer.ItemDeleted -= DiscovererOnItemDeleted; + State.Discoverer.Dispose(); + State.Discoverer = null; + foreach (Renderer renderer in State.Renderers) { renderer.Dispose(); } - _renderers.Clear(); + State.Renderers.Clear(); } private void DiscovererOnItemAdded(object sender, RendererDiscovererItemAddedEventArgs e) { - Guard.IsNotNull(_discoverer, nameof(_discoverer)); + Guard.IsNotNull(State.Discoverer, nameof(State.Discoverer)); Renderer renderer = new(e.RendererItem); - _renderers.Add(renderer); + State.Renderers.Add(renderer); RendererFound?.Invoke(this, new RendererFoundEventArgs(renderer)); } private void DiscovererOnItemDeleted(object sender, RendererDiscovererItemDeletedEventArgs e) { - Renderer? item = _renderers.Find(r => r.Target == e.RendererItem); + Renderer? item = State.Renderers.Find(r => r.Target == e.RendererItem); if (item != null) { RendererLost?.Invoke(this, new RendererLostEventArgs(item)); diff --git a/Screenbox.Core/Services/LibVlcService.cs b/Screenbox.Core/Services/LibVlcService.cs index de1165007..9d10953c4 100644 --- a/Screenbox.Core/Services/LibVlcService.cs +++ b/Screenbox.Core/Services/LibVlcService.cs @@ -2,6 +2,7 @@ using CommunityToolkit.Diagnostics; using LibVLCSharp.Shared; +using Screenbox.Core.Contexts; using Screenbox.Core.Playback; using System; using System.Collections.Generic; @@ -11,42 +12,48 @@ namespace Screenbox.Core.Services { - public sealed class LibVlcService : IDisposable - { - public VlcMediaPlayer? MediaPlayer { get; private set; } +public sealed class LibVlcService : IDisposable +{ + public VlcMediaPlayer? MediaPlayer => _libVlcState.MediaPlayer; - public LibVLC? LibVlc { get; private set; } + public LibVLC? LibVlc => _libVlcState.LibVlc; - private readonly NotificationService _notificationService; - private readonly bool _useFal; + private readonly NotificationService _notificationService; + private readonly LibVlcContext _libVlcState; + private bool UseFutureAccessList + { + get => _libVlcState.UseFutureAccessList; + set => _libVlcState.UseFutureAccessList = value; + } - public LibVlcService(INotificationService notificationService) - { - _notificationService = (NotificationService)notificationService; + public LibVlcService(INotificationService notificationService, LibVlcContext libVlcState) + { + _notificationService = (NotificationService)notificationService; + _libVlcState = libVlcState; - // FutureAccessList is preferred because it can handle network StorageFiles - // If FutureAccessList is somehow unavailable, SharedStorageAccessManager will be the fallback - _useFal = true; + // FutureAccessList is preferred because it can handle network StorageFiles + // If FutureAccessList is somehow unavailable, SharedStorageAccessManager will be the fallback + UseFutureAccessList = true; - try - { - // Clear FA periodically because of 1000 items limit - StorageApplicationPermissions.FutureAccessList.Clear(); - } - catch (Exception) // FileNotFoundException - { - // FutureAccessList is not available - _useFal = false; - } + try + { + // Clear FA periodically because of 1000 items limit + StorageApplicationPermissions.FutureAccessList.Clear(); } - - public VlcMediaPlayer Initialize(string[] swapChainOptions) + catch (Exception) // FileNotFoundException { - LibVLC lib = InitializeLibVlc(swapChainOptions); - LibVlc = lib; - MediaPlayer = new VlcMediaPlayer(lib); - return MediaPlayer; + // FutureAccessList is not available + UseFutureAccessList = false; } + } + + public VlcMediaPlayer Initialize(string[] swapChainOptions) + { + LibVLC lib = InitializeLibVlc(swapChainOptions); + _libVlcState.LibVlc = lib; + _libVlcState.MediaPlayer = new VlcMediaPlayer(lib); + return _libVlcState.MediaPlayer; + } public Media CreateMedia(object source, params string[] options) { @@ -75,17 +82,17 @@ private Media CreateMedia(IStorageFile file, params string[] options) { Guard.IsNotNull(LibVlc, nameof(LibVlc)); LibVLC libVlc = LibVlc; - if (file is StorageFile storageFile && - storageFile.Provider.Id.Equals("network", StringComparison.OrdinalIgnoreCase) && - !string.IsNullOrEmpty(storageFile.Path)) + if (file is StorageFile storageFile && + storageFile.Provider.Id.Equals("network", StringComparison.OrdinalIgnoreCase) && + !string.IsNullOrEmpty(storageFile.Path)) { // Optimization for network files. Avoid having to deal with WinRT quirks. return CreateMedia(new Uri(storageFile.Path, UriKind.Absolute), options); } - string token = _useFal - ? StorageApplicationPermissions.FutureAccessList.Add(file, "media") - : SharedStorageAccessManager.AddFile(file); + string token = UseFutureAccessList + ? StorageApplicationPermissions.FutureAccessList.Add(file, "media") + : SharedStorageAccessManager.AddFile(file); string mrl = "winrt://" + token; return new Media(libVlc, mrl, FromType.FromLocation, options); } @@ -105,10 +112,10 @@ public void DisposeMedia(Media media) string token = mrl.Substring(8); try { - if (_useFal) - { - StorageApplicationPermissions.FutureAccessList.Remove(token); - } + if (UseFutureAccessList) + { + StorageApplicationPermissions.FutureAccessList.Remove(token); + } else { SharedStorageAccessManager.RemoveFile(token); @@ -147,10 +154,12 @@ private LibVLC InitializeLibVlc(string[] swapChainOptions) return libVlc; } - public void Dispose() - { - MediaPlayer?.Close(); - LibVlc?.Dispose(); - } + public void Dispose() + { + MediaPlayer?.Close(); + LibVlc?.Dispose(); + _libVlcState.MediaPlayer = null; + _libVlcState.LibVlc = null; } } +} diff --git a/Screenbox.Core/Services/LibraryService.cs b/Screenbox.Core/Services/LibraryService.cs index 2b7754eb2..aeaf136b1 100644 --- a/Screenbox.Core/Services/LibraryService.cs +++ b/Screenbox.Core/Services/LibraryService.cs @@ -1,15 +1,16 @@ #nullable enable -using CommunityToolkit.WinUI; -using Screenbox.Core.Factories; -using Screenbox.Core.Helpers; -using Screenbox.Core.Models; -using Screenbox.Core.ViewModels; using System; using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; +using CommunityToolkit.WinUI; +using Screenbox.Core.Contexts; +using Screenbox.Core.Factories; +using Screenbox.Core.Helpers; +using Screenbox.Core.Models; +using Screenbox.Core.ViewModels; using Windows.Devices.Enumeration; using Windows.Foundation; using Windows.Foundation.Metadata; @@ -27,10 +28,29 @@ public sealed class LibraryService : ILibraryService public event TypedEventHandler? MusicLibraryContentChanged; public event TypedEventHandler? VideosLibraryContentChanged; - public StorageLibrary? MusicLibrary { get; private set; } - public StorageLibrary? VideosLibrary { get; private set; } - public bool IsLoadingVideos { get; private set; } - public bool IsLoadingMusic { get; private set; } + public StorageLibrary? MusicLibrary + { + get => State.MusicLibrary; + private set => State.MusicLibrary = value; + } + + public StorageLibrary? VideosLibrary + { + get => State.VideosLibrary; + private set => State.VideosLibrary = value; + } + + public bool IsLoadingVideos + { + get => State.IsLoadingVideos; + private set => State.IsLoadingVideos = value; + } + + public bool IsLoadingMusic + { + get => State.IsLoadingMusic; + private set => State.IsLoadingMusic = value; + } private bool UseIndexer => _settingsService.UseIndexer; private bool SearchRemovableStorage => _settingsService.SearchRemovableStorage && SystemInformation.IsXbox; @@ -40,41 +60,75 @@ public sealed class LibraryService : ILibraryService private readonly MediaViewModelFactory _mediaFactory; private readonly AlbumViewModelFactory _albumFactory; private readonly ArtistViewModelFactory _artistFactory; + private readonly LibraryContext State; private readonly DispatcherQueueTimer _musicRefreshTimer; private readonly DispatcherQueueTimer _videosRefreshTimer; private readonly DispatcherQueueTimer _storageDeviceRefreshTimer; private readonly DeviceWatcher? _portableStorageDeviceWatcher; + private StorageFileQueryResult? MusicLibraryQueryResult + { + get => State.MusicLibraryQueryResult; + set => State.MusicLibraryQueryResult = value; + } - private StorageFileQueryResult? _musicLibraryQueryResult; - private StorageFileQueryResult? _videosLibraryQueryResult; - private List _songs; - private List _videos; - private CancellationTokenSource? _musicFetchCts; - private CancellationTokenSource? _videosFetchCts; - private bool _musicChangeTrackerAvailable; - private bool _videosChangeTrackerAvailable; + private StorageFileQueryResult? VideosLibraryQueryResult + { + get => State.VideosLibraryQueryResult; + set => State.VideosLibraryQueryResult = value; + } + + private CancellationTokenSource? MusicFetchCancellation + { + get => State.MusicFetchCancellation; + set => State.MusicFetchCancellation = value; + } + + private CancellationTokenSource? VideosFetchCancellation + { + get => State.VideosFetchCancellation; + set => State.VideosFetchCancellation = value; + } + + private bool MusicChangeTrackerAvailable + { + get => State.MusicChangeTrackerAvailable; + set => State.MusicChangeTrackerAvailable = value; + } + + private bool VideosChangeTrackerAvailable + { + get => State.VideosChangeTrackerAvailable; + set => State.VideosChangeTrackerAvailable = value; + } private const string SongsCacheFileName = "songs.bin"; private const string VideoCacheFileName = "videos.bin"; public LibraryService(ISettingsService settingsService, IFilesService filesService, - MediaViewModelFactory mediaFactory, AlbumViewModelFactory albumFactory, ArtistViewModelFactory artistFactory) + MediaViewModelFactory mediaFactory, AlbumViewModelFactory albumFactory, ArtistViewModelFactory artistFactory, + LibraryContext state) { _settingsService = settingsService; _filesService = filesService; _mediaFactory = mediaFactory; _albumFactory = albumFactory; _artistFactory = artistFactory; + State = state; DispatcherQueue dispatcherQueue = DispatcherQueue.GetForCurrentThread(); - _musicRefreshTimer = dispatcherQueue.CreateTimer(); - _videosRefreshTimer = dispatcherQueue.CreateTimer(); - _storageDeviceRefreshTimer = dispatcherQueue.CreateTimer(); - _songs = new List(); - _videos = new List(); + _musicRefreshTimer = State.MusicRefreshTimer ??= dispatcherQueue.CreateTimer(); + _videosRefreshTimer = State.VideosRefreshTimer ??= dispatcherQueue.CreateTimer(); + _storageDeviceRefreshTimer = State.StorageDeviceRefreshTimer ??= dispatcherQueue.CreateTimer(); if (SystemInformation.IsXbox) { - _portableStorageDeviceWatcher = DeviceInformation.CreateWatcher(DeviceClass.PortableStorageDevice); + if (State.PortableStorageDeviceWatcher != null) + { + State.PortableStorageDeviceWatcher.Removed -= OnPortableStorageDeviceChanged; + State.PortableStorageDeviceWatcher.Updated -= OnPortableStorageDeviceChanged; + } + + _portableStorageDeviceWatcher = State.PortableStorageDeviceWatcher ?? DeviceInformation.CreateWatcher(DeviceClass.PortableStorageDevice); + State.PortableStorageDeviceWatcher = _portableStorageDeviceWatcher; _portableStorageDeviceWatcher.Removed += OnPortableStorageDeviceChanged; _portableStorageDeviceWatcher.Updated += OnPortableStorageDeviceChanged; } @@ -87,7 +141,7 @@ public async Task InitializeMusicLibraryAsync() try { MusicLibrary.ChangeTracker.Enable(); - _musicChangeTrackerAvailable = true; + MusicChangeTrackerAvailable = true; } catch (Exception) { @@ -104,7 +158,7 @@ public async Task InitializeVideosLibraryAsync() try { VideosLibrary.ChangeTracker.Enable(); - _videosChangeTrackerAvailable = true; + VideosChangeTrackerAvailable = true; } catch (Exception) { @@ -116,20 +170,20 @@ public async Task InitializeVideosLibraryAsync() public MusicLibraryFetchResult GetMusicFetchResult() { - return new MusicLibraryFetchResult(_songs.AsReadOnly(), _albumFactory.AllAlbums.ToList(), _artistFactory.AllArtists.ToList(), + return new MusicLibraryFetchResult(State.Songs.AsReadOnly(), _albumFactory.AllAlbums.ToList(), _artistFactory.AllArtists.ToList(), _albumFactory.UnknownAlbum, _artistFactory.UnknownArtist); } public IReadOnlyList GetVideosFetchResult() { - return _videos.AsReadOnly(); + return State.Videos.AsReadOnly(); } public async Task FetchMusicAsync(bool useCache = true) { - _musicFetchCts?.Cancel(); + MusicFetchCancellation?.Cancel(); using CancellationTokenSource cts = new(); - _musicFetchCts = cts; + MusicFetchCancellation = cts; try { await InitializeMusicLibraryAsync(); @@ -142,15 +196,15 @@ public async Task FetchMusicAsync(bool useCache = true) } finally { - _musicFetchCts = null; + MusicFetchCancellation = null; } } public async Task FetchVideosAsync(bool useCache = true) { - _videosFetchCts?.Cancel(); + VideosFetchCancellation?.Cancel(); using CancellationTokenSource cts = new(); - _videosFetchCts = cts; + VideosFetchCancellation = cts; try { await InitializeVideosLibraryAsync(); @@ -163,7 +217,7 @@ public async Task FetchVideosAsync(bool useCache = true) } finally { - _videosFetchCts = null; + VideosFetchCancellation = null; } } @@ -181,14 +235,14 @@ public void RemoveMedia(MediaViewModel media) } media.Artists = Array.Empty(); - _songs.Remove(media); - _videos.Remove(media); + State.Songs.Remove(media); + State.Videos.Remove(media); } private async Task CacheSongsAsync(CancellationToken cancellationToken) { var folderPaths = MusicLibrary!.Folders.Select(f => f.Path).ToList(); - var records = _songs.Select(song => + var records = State.Songs.Select(song => new PersistentMediaRecord(song.Name, song.Location, song.MediaInfo.MusicProperties, song.DateAdded)).ToList(); var libraryCache = new PersistentStorageLibrary { @@ -209,7 +263,7 @@ private async Task CacheSongsAsync(CancellationToken cancellationToken) private async Task CacheVideosAsync(CancellationToken cancellationToken) { var folderPaths = VideosLibrary!.Folders.Select(f => f.Path).ToList(); - List records = _videos.Select(video => + List records = State.Videos.Select(video => new PersistentMediaRecord(video.Name, video.Location, video.MediaInfo.VideoProperties, video.DateAdded)).ToList(); var libraryCache = new PersistentStorageLibrary() { @@ -276,6 +330,7 @@ private async Task FetchMusicCancelableAsync(bool useCache, CancellationToken ca await KnownFolders.RequestAccessAsync(KnownFolderId.MusicLibrary); var libraryQuery = GetMusicLibraryQuery(); List songs = new(); + List previousSongs = State.Songs.ToList(); if (useCache) { var libraryCache = await LoadStorageLibraryCacheAsync(SongsCacheFileName); @@ -285,7 +340,7 @@ private async Task FetchMusicCancelableAsync(bool useCache, CancellationToken ca hasCache = !AreLibraryPathsChanged(libraryCache.FolderPaths, MusicLibrary); // Update cache with changes from library tracker. Invalidate cache if needed. - if (hasCache && _musicChangeTrackerAvailable) + if (hasCache && MusicChangeTrackerAvailable) { try { @@ -305,7 +360,6 @@ private async Task FetchMusicCancelableAsync(bool useCache, CancellationToken ca // Recrawl the library if there is no cache or cache is invalidated if (!hasCache) { - _songs = songs; songs.Clear(); _albumFactory.Clear(); _artistFactory.Clear(); @@ -324,14 +378,14 @@ private async Task FetchMusicCancelableAsync(bool useCache, CancellationToken ca } } - if (songs != _songs) + if (!songs.SequenceEqual(previousSongs)) { // Ensure only songs not in the library has IsFromLibrary = false - _songs.ForEach(song => song.IsFromLibrary = false); + previousSongs.ForEach(song => song.IsFromLibrary = false); } songs.ForEach(song => song.IsFromLibrary = true); - CleanOutdatedSongs(); + CleanOutdatedSongs(previousSongs.Except(songs)); // Populate Album and Artists for each song foreach (MediaViewModel song in songs) @@ -351,11 +405,11 @@ private async Task FetchMusicCancelableAsync(bool useCache, CancellationToken ca } } - _songs = songs; + State.Songs = songs; MusicLibraryContentChanged?.Invoke(this, EventArgs.Empty); await CacheSongsAsync(cancellationToken); - if (hasCache && _musicChangeTrackerAvailable && changeReader != null) + if (hasCache && MusicChangeTrackerAvailable && changeReader != null) { await changeReader.AcceptChangesAsync(); } @@ -396,7 +450,7 @@ private async Task FetchVideosCancelableAsync(bool useCache, CancellationToken c hasCache = !AreLibraryPathsChanged(libraryCache.FolderPaths, VideosLibrary); // Update cache with changes from library tracker. Invalidate cache if needed. - if (hasCache && _videosChangeTrackerAvailable) + if (hasCache && VideosChangeTrackerAvailable) { try { @@ -416,7 +470,7 @@ private async Task FetchVideosCancelableAsync(bool useCache, CancellationToken c // Recrawl the library if there is no cache or cache is invalidated if (!hasCache) { - _videos = videos; + State.Videos = videos; videos.Clear(); await BatchFetchMediaAsync(libraryQuery, videos, cancellationToken); @@ -443,11 +497,13 @@ private async Task FetchVideosCancelableAsync(bool useCache, CancellationToken c } } - _videos = videos; + var stateVideos = State.Videos; + stateVideos.Clear(); + stateVideos.AddRange(videos); VideosLibraryContentChanged?.Invoke(this, EventArgs.Empty); await CacheVideosAsync(cancellationToken); - if (hasCache && _videosChangeTrackerAvailable && changeReader != null) + if (hasCache && VideosChangeTrackerAvailable && changeReader != null) { await changeReader.AcceptChangesAsync(); } @@ -480,9 +536,8 @@ private async Task BatchFetchMediaAsync(StorageFileQueryResult queryResult, List /// /// Clean up songs that are no longer from the library /// - private void CleanOutdatedSongs() + private void CleanOutdatedSongs(IEnumerable outdatedSongs) { - List outdatedSongs = _songs.Where(song => !song.IsFromLibrary).ToList(); foreach (MediaViewModel song in outdatedSongs) { if (song.Album != null) @@ -506,7 +561,7 @@ private void CleanOutdatedSongs() private StorageFileQueryResult GetMusicLibraryQuery() { - StorageFileQueryResult? libraryQuery = _musicLibraryQueryResult; + StorageFileQueryResult? libraryQuery = MusicLibraryQueryResult; if (libraryQuery != null && ShouldUpdateQuery(libraryQuery, UseIndexer)) { @@ -520,13 +575,13 @@ private StorageFileQueryResult GetMusicLibraryQuery() libraryQuery.ContentsChanged += OnMusicLibraryContentChanged; } - _musicLibraryQueryResult = libraryQuery; + MusicLibraryQueryResult = libraryQuery; return libraryQuery; } private StorageFileQueryResult GetVideosLibraryQuery() { - StorageFileQueryResult? libraryQuery = _videosLibraryQueryResult; + StorageFileQueryResult? libraryQuery = VideosLibraryQueryResult; if (libraryQuery != null && ShouldUpdateQuery(libraryQuery, UseIndexer)) { @@ -540,7 +595,7 @@ private StorageFileQueryResult GetVideosLibraryQuery() libraryQuery.ContentsChanged += OnVideosLibraryContentChanged; } - _videosLibraryQueryResult = libraryQuery; + VideosLibraryQueryResult = libraryQuery; return libraryQuery; } @@ -691,7 +746,8 @@ async void FetchAction() { // pass } - }; + } + ; // Delay fetch due to query result not yet updated at this time _musicRefreshTimer.Debounce(FetchAction, TimeSpan.FromMilliseconds(1000)); } diff --git a/Screenbox.Core/Services/NotificationService.cs b/Screenbox.Core/Services/NotificationService.cs index 0847b0569..9640f8dae 100644 --- a/Screenbox.Core/Services/NotificationService.cs +++ b/Screenbox.Core/Services/NotificationService.cs @@ -4,6 +4,7 @@ using System.Threading; using System.Threading.Tasks; using LibVLCSharp.Shared; +using Screenbox.Core.Contexts; using Screenbox.Core.Enums; using Screenbox.Core.Events; using Windows.UI.Xaml.Controls; @@ -16,13 +17,13 @@ public sealed class NotificationService : INotificationService public event EventHandler? ProgressUpdated; - private string? _progressTitle; - private readonly Func _vlcLoginDialogFactory; + private readonly NotificationContext State; - public NotificationService(Func vlcLoginDialogFactory) + public NotificationService(Func vlcLoginDialogFactory, NotificationContext state) { _vlcLoginDialogFactory = vlcLoginDialogFactory; + State = state; } public void RaiseNotification(NotificationLevel level, string title, string message) @@ -57,14 +58,14 @@ private Task DisplayProgress(Dialog dialog, string? title, string? text, bool in return Task.Run(() => { if (token.IsCancellationRequested) return; - _progressTitle = title; + State.ProgressTitle = title; var eventArgs = new ProgressUpdatedEventArgs(title, text, indeterminate, position); ProgressUpdated?.Invoke(this, eventArgs); }, token); } private Task UpdateProgress(Dialog dialog, float position, string? text) => - DisplayProgress(dialog, _progressTitle, text, false, position, null, CancellationToken.None); + DisplayProgress(dialog, State.ProgressTitle, text, false, position, null, CancellationToken.None); private async Task DisplayLoginDialog(Dialog dialog, string? title, string? text, string? defaultUsername, bool askStore, CancellationToken token) { diff --git a/Screenbox.Core/Services/SystemMediaTransportControlsService.cs b/Screenbox.Core/Services/SystemMediaTransportControlsService.cs index ff6d87606..8c4f1130e 100644 --- a/Screenbox.Core/Services/SystemMediaTransportControlsService.cs +++ b/Screenbox.Core/Services/SystemMediaTransportControlsService.cs @@ -1,8 +1,9 @@ #nullable enable -using Screenbox.Core.Helpers; using System; using System.Threading.Tasks; +using Screenbox.Core.Contexts; +using Screenbox.Core.Helpers; using Windows.ApplicationModel; using Windows.Media; using Windows.Media.Playback; @@ -15,10 +16,11 @@ public sealed class SystemMediaTransportControlsService : ISystemMediaTransportC { public SystemMediaTransportControls TransportControls { get; } - private DateTime _lastUpdated; + private readonly TransportControlsContext State; - public SystemMediaTransportControlsService() + public SystemMediaTransportControlsService(TransportControlsContext state) { + State = state; TransportControls = SystemMediaTransportControls.GetForCurrentView(); TransportControls.IsEnabled = true; TransportControls.IsPlayEnabled = true; @@ -27,8 +29,7 @@ public SystemMediaTransportControlsService() TransportControls.AutoRepeatMode = MediaPlaybackAutoRepeatMode.None; TransportControls.PlaybackStatus = MediaPlaybackStatus.Closed; TransportControls.DisplayUpdater.ClearAll(); - - _lastUpdated = DateTime.MinValue; + State.LastUpdated = DateTime.MinValue; } public async Task UpdateTransportControlsDisplayAsync(MediaViewModel? item) @@ -81,8 +82,8 @@ public async Task UpdateTransportControlsDisplayAsync(MediaViewModel? item) public void UpdatePlaybackPosition(TimeSpan position, TimeSpan startTime, TimeSpan endTime) { - if (DateTime.Now - _lastUpdated < TimeSpan.FromSeconds(5)) return; - _lastUpdated = DateTime.Now; + if (DateTime.Now - State.LastUpdated < TimeSpan.FromSeconds(5)) return; + State.LastUpdated = DateTime.Now; SystemMediaTransportControlsTimelineProperties timelineProps = new() { StartTime = startTime, diff --git a/Screenbox.Core/Services/WindowService.cs b/Screenbox.Core/Services/WindowService.cs index 03a995a81..eff76f526 100644 --- a/Screenbox.Core/Services/WindowService.cs +++ b/Screenbox.Core/Services/WindowService.cs @@ -1,5 +1,6 @@ #nullable enable +using Screenbox.Core.Contexts; using Screenbox.Core.Enums; using Screenbox.Core.Events; using System; @@ -19,23 +20,23 @@ public sealed class WindowService : IWindowService public WindowViewMode ViewMode { - get => _viewMode; + get => State.ViewMode; private set { - WindowViewMode oldValue = _viewMode; + WindowViewMode oldValue = State.ViewMode; if (oldValue != value) { - _viewMode = value; + State.ViewMode = value; ViewModeChanged?.Invoke(this, new ViewModeChangedEventArgs(value, oldValue)); } } } - private CoreCursor? _cursor; - private WindowViewMode _viewMode; + private readonly WindowContext State; - public WindowService() + public WindowService(WindowContext state) { + State = state; Window.Current.SizeChanged += OnWindowSizeChanged; } @@ -156,7 +157,7 @@ public void HideCursor() CoreWindow? coreWindow = Window.Current.CoreWindow; if (coreWindow.PointerCursor?.Type == CoreCursorType.Arrow) { - _cursor = coreWindow.PointerCursor; + State.Cursor = coreWindow.PointerCursor; coreWindow.PointerCursor = null; } } @@ -164,7 +165,7 @@ public void HideCursor() public void ShowCursor() { CoreWindow? coreWindow = Window.Current.CoreWindow; - coreWindow.PointerCursor ??= _cursor; + coreWindow.PointerCursor ??= State.Cursor; } } } diff --git a/Screenbox.Core/ViewModels/CommonViewModel.cs b/Screenbox.Core/ViewModels/CommonViewModel.cs index 163d497e3..1531c1260 100644 --- a/Screenbox.Core/ViewModels/CommonViewModel.cs +++ b/Screenbox.Core/ViewModels/CommonViewModel.cs @@ -7,6 +7,7 @@ using CommunityToolkit.Mvvm.Input; using CommunityToolkit.Mvvm.Messaging; using CommunityToolkit.Mvvm.Messaging.Messages; +using Screenbox.Core.Contexts; using Screenbox.Core.Enums; using Screenbox.Core.Helpers; using Screenbox.Core.Messages; @@ -22,7 +23,7 @@ public sealed partial class CommonViewModel : ObservableRecipient, IRecipient>, IRecipient> { - public Dictionary NavigationStates { get; } + public Dictionary NavigationStates => NavigationState.NavigationStates; public bool IsAdvancedModeEnabled => _settingsService.AdvancedMode; @@ -31,24 +32,30 @@ public sealed partial class CommonViewModel : ObservableRecipient, [ObservableProperty] private Thickness _footerBottomPaddingMargin; [ObservableProperty] private double _footerBottomPaddingHeight; + private readonly NavigationContext NavigationState; private readonly INavigationService _navigationService; private readonly IFilesService _filesService; private readonly IResourceService _resourceService; private readonly ISettingsService _settingsService; - private readonly Dictionary _pageStates; public CommonViewModel(INavigationService navigationService, IFilesService filesService, IResourceService resourceService, - ISettingsService settingsService) + ISettingsService settingsService, + NavigationContext navigationState) { _navigationService = navigationService; _filesService = filesService; _resourceService = resourceService; _settingsService = settingsService; - _navigationViewDisplayMode = Messenger.Send(); - NavigationStates = new Dictionary(); - _pageStates = new Dictionary(); + NavigationState = navigationState; + _navigationViewDisplayMode = NavigationState.NavigationViewDisplayMode == default + ? Messenger.Send() + : NavigationState.NavigationViewDisplayMode; + NavigationState.NavigationViewDisplayMode = _navigationViewDisplayMode; + _scrollBarMargin = NavigationState.ScrollBarMargin; + _footerBottomPaddingMargin = NavigationState.FooterBottomPaddingMargin; + _footerBottomPaddingHeight = NavigationState.FooterBottomPaddingHeight; // Activate the view model's messenger IsActive = true; @@ -85,12 +92,12 @@ public void Receive(PropertyChangedMessage message) public void SavePageState(object state, string pageTypeName, int backStackDepth) { - _pageStates[pageTypeName + backStackDepth] = state; + NavigationState.PageStates[pageTypeName + backStackDepth] = state; } public bool TryGetPageState(string pageTypeName, int backStackDepth, out object state) { - return _pageStates.TryGetValue(pageTypeName + backStackDepth, out state); + return NavigationState.PageStates.TryGetValue(pageTypeName + backStackDepth, out state); } [RelayCommand] @@ -136,5 +143,25 @@ private async Task OpenFilesAsync() _resourceService.GetString(ResourceName.FailedToOpenFilesNotificationTitle), e.Message)); } } + + partial void OnNavigationViewDisplayModeChanged(NavigationViewDisplayMode value) + { + NavigationState.NavigationViewDisplayMode = value; + } + + partial void OnScrollBarMarginChanged(Thickness value) + { + NavigationState.ScrollBarMargin = value; + } + + partial void OnFooterBottomPaddingMarginChanged(Thickness value) + { + NavigationState.FooterBottomPaddingMargin = value; + } + + partial void OnFooterBottomPaddingHeightChanged(double value) + { + NavigationState.FooterBottomPaddingHeight = value; + } } } diff --git a/Screenbox.Core/ViewModels/MediaListViewModel.cs b/Screenbox.Core/ViewModels/MediaListViewModel.cs index 6dd797a14..787617122 100644 --- a/Screenbox.Core/ViewModels/MediaListViewModel.cs +++ b/Screenbox.Core/ViewModels/MediaListViewModel.cs @@ -10,6 +10,7 @@ using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; using CommunityToolkit.Mvvm.Messaging; +using Screenbox.Core.Contexts; using Screenbox.Core.Factories; using Screenbox.Core.Helpers; using Screenbox.Core.Messages; @@ -61,15 +62,50 @@ public sealed partial class MediaListViewModel : ObservableRecipient, private readonly ISettingsService _settingsService; private readonly ISystemMediaTransportControlsService _transportControlsService; private readonly MediaViewModelFactory _mediaFactory; + private readonly MediaListContext State; // ViewModel state - private Playlist _playlist; - private List _mediaBuffer = new(); - private IMediaPlayer? _mediaPlayer; - private object? _delayPlay; - private bool _deferCollectionChanged; // Optimization to avoid excessive updates on collection changed events - private StorageFileQueryResult? _neighboringFilesQuery; - private CancellationTokenSource? _playFilesCts; + private Playlist Playlist + { + get => State.Playlist; + set => State.Playlist = value; + } + + private List MediaBuffer + { + get => State.MediaBuffer; + set => State.MediaBuffer = value; + } + + private IMediaPlayer? MediaPlayer + { + get => State.MediaPlayer; + set => State.MediaPlayer = value; + } + + private object? DelayPlay + { + get => State.DelayPlay; + set => State.DelayPlay = value; + } + + private bool DeferCollectionChanged + { + get => State.DeferCollectionChanged; + set => State.DeferCollectionChanged = value; + } + + private StorageFileQueryResult? NeighboringFilesQuery + { + get => State.NeighboringFilesQuery; + set => State.NeighboringFilesQuery = value; + } + + private CancellationTokenSource? PlayFilesCts + { + get => State.PlayFilesCancellation; + set => State.PlayFilesCancellation = value; + } private readonly DispatcherQueue _dispatcherQueue; public MediaListViewModel( @@ -79,7 +115,8 @@ public MediaListViewModel( IFilesService filesService, ISettingsService settingsService, ISystemMediaTransportControlsService transportControlsService, - MediaViewModelFactory mediaFactory) + MediaViewModelFactory mediaFactory, + MediaListContext state) { _playlistService = playlistService; _playbackControlService = playbackControlService; @@ -88,16 +125,23 @@ public MediaListViewModel( _settingsService = settingsService; _transportControlsService = transportControlsService; _mediaFactory = mediaFactory; + State = state; _dispatcherQueue = DispatcherQueue.GetForCurrentThread(); // Initialize UI collections - Items = new ObservableCollection(); + Playlist = State.Playlist ??= new Playlist(); + Items = new ObservableCollection(Playlist.Items); Items.CollectionChanged += OnCollectionChanged; // Initialize state - _playlist = new Playlist(); - _repeatMode = settingsService.PersistentRepeatMode; - _currentIndex = -1; + _repeatMode = State.RepeatMode == default ? settingsService.PersistentRepeatMode : State.RepeatMode; + _shuffleMode = State.ShuffleMode; + _currentItem = State.CurrentItem; + _currentIndex = State.CurrentIndex >= 0 ? State.CurrentIndex : -1; + State.RepeatMode = _repeatMode; + State.ShuffleMode = _shuffleMode; + State.CurrentItem = _currentItem; + State.CurrentIndex = _currentIndex; // Setup transport controls _transportControlsService.TransportControls.ButtonPressed += TransportControlsOnButtonPressed; @@ -114,19 +158,19 @@ public async void Receive(PlayFilesMessage message) { var files = message.Value; await ParseAndPlayAsync(files); - _neighboringFilesQuery = message.NeighboringFilesQuery; + NeighboringFilesQuery = message.NeighboringFilesQuery; // Enqueue neighboring files if needed - if (_playlist.Items.Count == 1 && _settingsService.EnqueueAllFilesInFolder) + if (Playlist.Items.Count == 1 && _settingsService.EnqueueAllFilesInFolder) { - if (_neighboringFilesQuery == null && files[0] is StorageFile file) + if (NeighboringFilesQuery == null && files[0] is StorageFile file) { - _neighboringFilesQuery ??= await _filesService.GetNeighboringFilesQueryAsync(file); + NeighboringFilesQuery ??= await _filesService.GetNeighboringFilesQueryAsync(file); } - if (_neighboringFilesQuery != null) + if (NeighboringFilesQuery != null) { - var updatedPlaylist = await EnqueueNeighboringFilesAsync(_playlist, _neighboringFilesQuery); + var updatedPlaylist = await EnqueueNeighboringFilesAsync(Playlist, NeighboringFilesQuery); LoadFromPlaylist(updatedPlaylist); } } @@ -140,7 +184,7 @@ public void Receive(ClearPlaylistMessage message) public void Receive(QueuePlaylistMessage message) { // Perform some clean ups as we assume new playlist - _neighboringFilesQuery = null; + NeighboringFilesQuery = null; ShuffleMode = false; // Load and play the new playlist @@ -156,14 +200,14 @@ public void Receive(QueuePlaylistMessage message) public void Receive(PlaylistRequestMessage message) { - message.Reply(new Playlist(_playlist)); + message.Reply(new Playlist(Playlist)); } public async void Receive(PlayMediaMessage message) { - if (_mediaPlayer == null) + if (MediaPlayer == null) { - _delayPlay = message.Value; + DelayPlay = message.Value; return; } @@ -180,25 +224,32 @@ public async void Receive(PlayMediaMessage message) public void Receive(MediaPlayerChangedMessage message) { - _mediaPlayer = message.Value; - _mediaPlayer.MediaFailed += OnMediaFailed; - _mediaPlayer.MediaEnded += OnEndReached; - _mediaPlayer.PlaybackStateChanged += OnPlaybackStateChanged; + if (MediaPlayer != null) + { + MediaPlayer.MediaFailed -= OnMediaFailed; + MediaPlayer.MediaEnded -= OnEndReached; + MediaPlayer.PlaybackStateChanged -= OnPlaybackStateChanged; + } + + MediaPlayer = message.Value; + MediaPlayer.MediaFailed += OnMediaFailed; + MediaPlayer.MediaEnded += OnEndReached; + MediaPlayer.PlaybackStateChanged += OnPlaybackStateChanged; - if (_delayPlay != null) + if (DelayPlay != null) { async void SetPlayQueue() { - if (_delayPlay is MediaViewModel media && Items.Contains(media)) + if (DelayPlay is MediaViewModel media && Items.Contains(media)) { PlaySingle(media); } else { ClearPlaylist(); - await ParseAndPlayAsync(_delayPlay); + await ParseAndPlayAsync(DelayPlay); } - _delayPlay = null; + DelayPlay = null; } _dispatcherQueue.TryEnqueue(SetPlayQueue); @@ -211,9 +262,9 @@ async void SetPlayQueue() partial void OnCurrentItemChanging(MediaViewModel? value) { - if (_mediaPlayer != null) + if (MediaPlayer != null) { - _mediaPlayer.PlaybackItem = value?.Item.Value; + MediaPlayer.PlaybackItem = value?.Item.Value; } if (CurrentItem != null) @@ -226,13 +277,16 @@ partial void OnCurrentItemChanging(MediaViewModel? value) { value.IsMediaActive = true; CurrentIndex = Items.IndexOf(value); - _playlist.CurrentIndex = CurrentIndex; + Playlist.CurrentIndex = CurrentIndex; } else { CurrentIndex = -1; - _playlist.CurrentIndex = -1; + Playlist.CurrentIndex = -1; } + + State.CurrentIndex = CurrentIndex; + State.CurrentItem = value; } async partial void OnCurrentItemChanged(MediaViewModel? value) @@ -254,10 +308,12 @@ await Task.WhenAll( _transportControlsService.UpdateTransportControlsDisplayAsync(value), UpdateMediaBufferAsync() ); + State.CurrentItem = value; } partial void OnRepeatModeChanged(MediaPlaybackAutoRepeatMode value) { + State.RepeatMode = value; Messenger.Send(new RepeatModeChangedMessage(value)); _transportControlsService.TransportControls.AutoRepeatMode = value; _settingsService.PersistentRepeatMode = value; @@ -265,21 +321,22 @@ partial void OnRepeatModeChanged(MediaPlaybackAutoRepeatMode value) partial void OnShuffleModeChanged(bool value) { + State.ShuffleMode = value; Playlist playlist; if (value) { - playlist = _playlistService.ShufflePlaylist(_playlist, CurrentIndex); + playlist = _playlistService.ShufflePlaylist(Playlist, CurrentIndex); } else { - if (_playlist.ShuffleBackup != null) + if (Playlist.ShuffleBackup != null) { - playlist = _playlistService.RestoreFromShuffle(_playlist); + playlist = _playlistService.RestoreFromShuffle(Playlist); } else { // No backup - just reshuffle - playlist = _playlistService.ShufflePlaylist(_playlist, CurrentIndex); + playlist = _playlistService.ShufflePlaylist(Playlist, CurrentIndex); playlist.ShuffleMode = false; playlist.ShuffleBackup = null; } @@ -295,15 +352,15 @@ partial void OnShuffleModeChanged(bool value) [RelayCommand] private void PlaySingle(MediaViewModel vm) { - if (_mediaPlayer == null) + if (MediaPlayer == null) { - _delayPlay = vm; + DelayPlay = vm; return; } CurrentItem = vm; - _mediaPlayer.PlaybackItem = vm.Item.Value; - _mediaPlayer.Play(); + MediaPlayer.PlaybackItem = vm.Item.Value; + MediaPlayer.Play(); } [RelayCommand] @@ -316,15 +373,15 @@ private void Clear() private bool CanNext() { - return _playbackControlService.CanNext(_playlist, RepeatMode, hasNeighbor: _neighboringFilesQuery != null); + return _playbackControlService.CanNext(Playlist, RepeatMode, hasNeighbor: NeighboringFilesQuery != null); } [RelayCommand(CanExecute = nameof(CanNext))] private async Task NextAsync() { - var playlist = _playlist; - var result = playlist.Items.Count == 1 && _neighboringFilesQuery != null - ? await _playbackControlService.GetNeighboringNextAsync(playlist, _neighboringFilesQuery) + var playlist = Playlist; + var result = playlist.Items.Count == 1 && NeighboringFilesQuery != null + ? await _playbackControlService.GetNeighboringNextAsync(playlist, NeighboringFilesQuery) : _playbackControlService.GetNext(playlist, RepeatMode); if (result != null) @@ -341,29 +398,29 @@ private async Task NextAsync() private bool CanPrevious() { - return _playbackControlService.CanPrevious(_playlist, RepeatMode, hasNeighbor: _neighboringFilesQuery != null); + return _playbackControlService.CanPrevious(Playlist, RepeatMode, hasNeighbor: NeighboringFilesQuery != null); } [RelayCommand(CanExecute = nameof(CanPrevious))] private async Task PreviousAsync() { - if (_mediaPlayer == null) return; + if (MediaPlayer == null) return; void SetPositionToStart() { - _mediaPlayer.Position = TimeSpan.Zero; + MediaPlayer.Position = TimeSpan.Zero; } // If playing and position > 5 seconds, restart current track - if (_mediaPlayer.PlaybackState == MediaPlaybackState.Playing && - _mediaPlayer.Position > TimeSpan.FromSeconds(5)) + if (MediaPlayer.PlaybackState == MediaPlaybackState.Playing && + MediaPlayer.Position > TimeSpan.FromSeconds(5)) { _dispatcherQueue.TryEnqueue(SetPositionToStart); return; } - var playlist = _playlist; - var result = playlist.Items.Count == 1 && _neighboringFilesQuery != null - ? await _playbackControlService.GetNeighboringPreviousAsync(playlist, _neighboringFilesQuery) + var playlist = Playlist; + var result = playlist.Items.Count == 1 && NeighboringFilesQuery != null + ? await _playbackControlService.GetNeighboringPreviousAsync(playlist, NeighboringFilesQuery) : _playbackControlService.GetPrevious(playlist, RepeatMode); if (result != null) @@ -434,11 +491,11 @@ private async Task AddToRecent(object? source) private async Task EnqueueNeighboringFilesAsync(Playlist playlist, StorageFileQueryResult neighboringFilesQuery) { - _playFilesCts?.Cancel(); + PlayFilesCts?.Cancel(); using var cts = new CancellationTokenSource(); try { - _playFilesCts = cts; + PlayFilesCts = cts; var updatedPlaylist = await _playlistService.AddNeighboringFilesAsync(playlist, neighboringFilesQuery, cts.Token); return updatedPlaylist; } @@ -452,7 +509,7 @@ private async Task EnqueueNeighboringFilesAsync(Playlist playlist, Sto } finally { - _playFilesCts = null; + PlayFilesCts = null; } return playlist; @@ -462,8 +519,8 @@ private void LoadFromPlaylist(Playlist playlist) { try { - _deferCollectionChanged = true; - _playlist = playlist; + DeferCollectionChanged = true; + Playlist = playlist; // Sync ObservableCollection with mediaList // Only use sync if both lists are small enough to avoid UI freezing @@ -483,12 +540,13 @@ private void LoadFromPlaylist(Playlist playlist) // Update current item CurrentItem = playlist.CurrentItem; CurrentIndex = CurrentItem != null ? Items.IndexOf(CurrentItem) : -1; + State.CurrentIndex = CurrentIndex; NextCommand.NotifyCanExecuteChanged(); PreviousCommand.NotifyCanExecuteChanged(); } finally { - _deferCollectionChanged = false; + DeferCollectionChanged = false; } } @@ -501,32 +559,32 @@ private void ClearPlaylist() try { - _deferCollectionChanged = true; + DeferCollectionChanged = true; Items.Clear(); - _playlist = new Playlist(); - _neighboringFilesQuery = null; + Playlist = new Playlist(); + NeighboringFilesQuery = null; ShuffleMode = false; } finally { - _deferCollectionChanged = false; + DeferCollectionChanged = false; } } private async Task UpdateMediaBufferAsync() { - var bufferIndices = _playlistService.GetMediaBufferIndices(_playlist.CurrentIndex, _playlist.Items.Count, RepeatMode); - var newBuffer = bufferIndices.Select(i => _playlist.Items[i]).ToList(); + var bufferIndices = _playlistService.GetMediaBufferIndices(Playlist.CurrentIndex, Playlist.Items.Count, RepeatMode); + var newBuffer = bufferIndices.Select(i => Playlist.Items[i]).ToList(); - var toLoad = newBuffer.Except(_mediaBuffer); - var toClean = _mediaBuffer.Except(newBuffer); + var toLoad = newBuffer.Except(MediaBuffer); + var toClean = MediaBuffer.Except(newBuffer); foreach (var media in toClean) { media.Clean(); } - _mediaBuffer = newBuffer; + MediaBuffer = newBuffer; await Task.WhenAll(toLoad.Select(x => x.LoadThumbnailAsync())); } @@ -618,7 +676,7 @@ private void OnEndReached(IMediaPlayer sender, object? args) { _dispatcherQueue.TryEnqueue(() => { - var playlist = _playlist; + var playlist = Playlist; var result = _playbackControlService.HandleMediaEnded(playlist, RepeatMode); if (result != null) { @@ -663,20 +721,21 @@ private void TransportControlsOnAutoRepeatModeChangeRequested(SystemMediaTranspo private void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) { - if (_deferCollectionChanged) return; + if (DeferCollectionChanged) return; CurrentIndex = CurrentItem != null ? Items.IndexOf(CurrentItem) : -1; - _playlist = new Playlist(CurrentIndex, Items, _playlist); + State.CurrentIndex = CurrentIndex; + Playlist = new Playlist(CurrentIndex, Items, Playlist); NextCommand.NotifyCanExecuteChanged(); PreviousCommand.NotifyCanExecuteChanged(); if (Items.Count <= 1) { - _playlist.ShuffleBackup = null; + Playlist.ShuffleBackup = null; return; } // Update shuffle backup if shuffling is enabled - ShuffleBackup? backup = _playlist.ShuffleBackup; + ShuffleBackup? backup = Playlist.ShuffleBackup; if (ShuffleMode && backup != null) { switch (e.Action) @@ -704,14 +763,14 @@ private void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs { if (!backup.Removals.Remove((MediaViewModel)item)) { - _playlist.ShuffleBackup = null; + Playlist.ShuffleBackup = null; break; } } break; case NotifyCollectionChangedAction.Reset: - _playlist.ShuffleBackup = null; + Playlist.ShuffleBackup = null; break; } diff --git a/Screenbox.Core/ViewModels/VolumeViewModel.cs b/Screenbox.Core/ViewModels/VolumeViewModel.cs index c8989d09a..14545a47a 100644 --- a/Screenbox.Core/ViewModels/VolumeViewModel.cs +++ b/Screenbox.Core/ViewModels/VolumeViewModel.cs @@ -3,6 +3,7 @@ using System; using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Messaging; +using Screenbox.Core.Contexts; using Screenbox.Core.Messages; using Screenbox.Core.Playback; using Screenbox.Core.Services; @@ -18,16 +19,31 @@ public sealed partial class VolumeViewModel : ObservableRecipient, [ObservableProperty] private int _maxVolume; [ObservableProperty] private int _volume; [ObservableProperty] private bool _isMute; - private IMediaPlayer? _mediaPlayer; + private readonly VolumeContext VolumeState; private readonly DispatcherQueue _dispatcherQueue; private readonly ISettingsService _settingsService; - public VolumeViewModel(ISettingsService settingsService) + public VolumeViewModel(ISettingsService settingsService, VolumeContext volumeState) { _settingsService = settingsService; - _volume = settingsService.PersistentVolume; - _maxVolume = settingsService.MaxVolume; - _isMute = _volume == 0; + VolumeState = volumeState; + if (VolumeState.IsInitialized) + { + _volume = VolumeState.Volume; + _maxVolume = VolumeState.MaxVolume; + _isMute = VolumeState.IsMute; + } + else + { + _volume = settingsService.PersistentVolume; + _maxVolume = settingsService.MaxVolume; + _isMute = _volume == 0; + VolumeState.IsInitialized = true; + } + + VolumeState.Volume = _volume; + VolumeState.MaxVolume = _maxVolume; + VolumeState.IsMute = _isMute; _dispatcherQueue = DispatcherQueue.GetForCurrentThread(); // View model doesn't receive any messages @@ -36,9 +52,20 @@ public VolumeViewModel(ISettingsService settingsService) public void Receive(MediaPlayerChangedMessage message) { - _mediaPlayer = message.Value; - _mediaPlayer.VolumeChanged += OnVolumeChanged; - _mediaPlayer.IsMutedChanged += OnIsMutedChanged; + if (VolumeState.MediaPlayer != null) + { + VolumeState.MediaPlayer.VolumeChanged -= OnVolumeChanged; + VolumeState.MediaPlayer.IsMutedChanged -= OnIsMutedChanged; + } + + VolumeState.MediaPlayer = message.Value; + if (VolumeState.MediaPlayer == null) + { + return; + } + + VolumeState.MediaPlayer.VolumeChanged += OnVolumeChanged; + VolumeState.MediaPlayer.IsMutedChanged += OnIsMutedChanged; } public void Receive(SettingsChangedMessage message) @@ -55,18 +82,19 @@ public void Receive(ChangeVolumeRequestMessage message) partial void OnVolumeChanged(int value) { - if (_mediaPlayer == null) return; + VolumeState.Volume = value; + if (VolumeState.MediaPlayer == null) return; double newValue = value / 100d; - // bool stayMute = IsMute && newValue - _mediaPlayer.Volume < 0.005; - _mediaPlayer.Volume = newValue; + VolumeState.MediaPlayer.Volume = newValue; if (value > 0) IsMute = false; _settingsService.PersistentVolume = value; } partial void OnIsMuteChanged(bool value) { - if (_mediaPlayer == null) return; - _mediaPlayer.IsMuted = value; + VolumeState.IsMute = value; + if (VolumeState.MediaPlayer == null) return; + VolumeState.MediaPlayer.IsMuted = value; } private void OnVolumeChanged(IMediaPlayer sender, object? args) @@ -96,5 +124,10 @@ public void SetVolume(int value, bool isOffset = false) { Volume = Math.Clamp(isOffset ? Volume + value : value, 0, MaxVolume); } + + partial void OnMaxVolumeChanged(int value) + { + VolumeState.MaxVolume = value; + } } }