Enhance playback settings management in PLib video library manager. Introduce PlaybackOptions for volume and mute settings, integrating them into AppSettings and IAppSettingsStore. Update VideoPlayerViewModel to persist playback state and adjust UI bindings in VideoPlayerView for volume control. Revise README.md to document new playback settings functionality.

This commit is contained in:
Leonid Pershin
2026-08-09 06:46:33 +03:00
parent b13d0148df
commit a938a48de9
14 changed files with 399 additions and 251 deletions
@@ -1,4 +1,6 @@
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Options;
using NSubstitute;
using PLib.Application.Library;
using PLib.Desktop.Services;
using PLib.Desktop.Settings;
@@ -28,11 +30,13 @@ public sealed class AppSettingsStoreTests : IDisposable
MaxIndexingConcurrency = 8,
MinimumFileSizeInBytes = 2_097_152,
Theme = ThemeMode.Light,
Volume = 0.35,
IsMuted = true,
};
await new JsonAppSettingsStore(_paths).SaveAsync(settings, Token);
await CreateStore().SaveAsync(settings, Token);
var (library, appearance) = Reload();
var (library, appearance, playback) = Reload();
library.Folders.ShouldBe(settings.Folders);
library.ThumbnailWidth.ShouldBe(640);
@@ -40,12 +44,14 @@ public sealed class AppSettingsStoreTests : IDisposable
library.MaxIndexingConcurrency.ShouldBe(8);
library.MinimumFileSizeInBytes.ShouldBe(2_097_152);
appearance.Theme.ShouldBe(ThemeMode.Light);
playback.Volume.ShouldBe(0.35);
playback.IsMuted.ShouldBeTrue();
}
[Fact]
public async Task Removing_a_folder_actually_shortens_the_stored_list()
{
var store = new JsonAppSettingsStore(_paths);
var store = CreateStore();
var settings = Sample with { Folders = [@"C:\a", @"C:\b", @"C:\c"] };
await store.SaveAsync(settings, Token);
@@ -64,7 +70,7 @@ public sealed class AppSettingsStoreTests : IDisposable
"""{ "Library": { "VideoExtensions": [ ".mp4" ] }, "Experimental": { "Flag": true } }""",
Token);
await new JsonAppSettingsStore(_paths).SaveAsync(Sample, Token);
await CreateStore().SaveAsync(Sample, Token);
var configuration = Build();
configuration["Experimental:Flag"].ShouldBe("True");
@@ -93,13 +99,32 @@ public sealed class AppSettingsStoreTests : IDisposable
MaxIndexingConcurrency = 4,
MinimumFileSizeInBytes = 65_536,
Theme = ThemeMode.System,
Volume = 0.8,
IsMuted = false,
};
/// <summary>
/// The store composes <see cref="AppSettings.Current"/> from configuration, which these
/// tests do not exercise — every case here supplies the snapshot it wants to write.
/// </summary>
private JsonAppSettingsStore CreateStore() => new(
_paths,
Monitor(new LibraryOptions()),
Monitor(new AppearanceOptions()),
Monitor(new PlaybackOptions()));
private static IOptionsMonitor<T> Monitor<T>(T value)
{
var monitor = Substitute.For<IOptionsMonitor<T>>();
monitor.CurrentValue.Returns(value);
return monitor;
}
private IConfigurationRoot Build() => new ConfigurationBuilder()
.AddJsonFile(Path.Combine(_paths.DataDirectory, "settings.json"), optional: false)
.Build();
private (LibraryOptions Library, AppearanceOptions Appearance) Reload()
private (LibraryOptions Library, AppearanceOptions Appearance, PlaybackOptions Playback) Reload()
{
var configuration = Build();
@@ -109,7 +134,10 @@ public sealed class AppSettingsStoreTests : IDisposable
var appearance = new AppearanceOptions();
configuration.GetSection(AppearanceOptions.SectionName).Bind(appearance);
return (library, appearance);
var playback = new PlaybackOptions();
configuration.GetSection(PlaybackOptions.SectionName).Bind(playback);
return (library, appearance, playback);
}
private sealed class TempPaths : IAppPaths, IDisposable