Skip to content

Commit e51ccd7

Browse files
committed
Persist Peek volume setting across sessions
This commit implements volume persistence for the Peek utility, so that when users adjust the volume slider for audio/video files, the volume level is remembered and applied to subsequent files. Changes: - Add MediaVolume property to PeekProperties (DoubleProperty, default 1.0) - Add MediaVolume to IUserSettings interface with getter/setter - Implement MediaVolume in UserSettings with save-to-settings logic - Add MediaVolume binding from MainWindow to FilePreview to AudioControl - Add VolumeChanged events in AudioControl and FilePreview - Track user volume changes and bubble up to MainWindowViewModel - Handle volume changes for both audio (AudioControl) and video (MediaPlayerElement) Fixes #31810
1 parent 52f2561 commit e51ccd7

File tree

9 files changed

+178
-1
lines changed

9 files changed

+178
-1
lines changed

src/modules/peek/Peek.FilePreviewer/Controls/AudioControl.xaml.cs

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// The Microsoft Corporation licenses this file to you under the MIT license.
33
// See the LICENSE file in the project root for more information.
44

5+
using System;
56
using Microsoft.UI.Xaml;
67
using Microsoft.UI.Xaml.Controls;
78
using Peek.FilePreviewer.Previewers.MediaPreviewer.Models;
@@ -10,6 +11,10 @@ namespace Peek.FilePreviewer.Controls
1011
{
1112
public sealed partial class AudioControl : UserControl
1213
{
14+
public event EventHandler<double>? VolumeChanged;
15+
16+
private bool _isSettingVolume;
17+
1318
public static readonly DependencyProperty SourceProperty = DependencyProperty.Register(
1419
nameof(Source),
1520
typeof(AudioPreviewData),
@@ -22,6 +27,12 @@ public sealed partial class AudioControl : UserControl
2227
typeof(AudioControl),
2328
new PropertyMetadata(null));
2429

30+
public static readonly DependencyProperty MediaVolumeProperty = DependencyProperty.Register(
31+
nameof(MediaVolume),
32+
typeof(double),
33+
typeof(AudioControl),
34+
new PropertyMetadata(1.0, new PropertyChangedCallback((d, e) => ((AudioControl)d).MediaVolumePropertyChanged())));
35+
2536
public AudioPreviewData? Source
2637
{
2738
get { return (AudioPreviewData)GetValue(SourceProperty); }
@@ -34,9 +45,27 @@ public string ToolTipText
3445
set { SetValue(ToolTipTextProperty, value); }
3546
}
3647

48+
public double MediaVolume
49+
{
50+
get { return (double)GetValue(MediaVolumeProperty); }
51+
set { SetValue(MediaVolumeProperty, value); }
52+
}
53+
3754
public AudioControl()
3855
{
3956
this.InitializeComponent();
57+
PlayerElement.MediaPlayer.VolumeChanged += MediaPlayer_VolumeChanged;
58+
}
59+
60+
private void MediaPlayer_VolumeChanged(Windows.Media.Playback.MediaPlayer sender, object args)
61+
{
62+
if (!_isSettingVolume)
63+
{
64+
DispatcherQueue.TryEnqueue(() =>
65+
{
66+
VolumeChanged?.Invoke(this, sender.Volume);
67+
});
68+
}
4069
}
4170

4271
private void SourcePropertyChanged()
@@ -46,6 +75,32 @@ private void SourcePropertyChanged()
4675
PlayerElement.MediaPlayer.Pause();
4776
PlayerElement.MediaPlayer.Source = null;
4877
}
78+
else
79+
{
80+
// Apply saved volume when source changes
81+
ApplyVolume();
82+
}
83+
}
84+
85+
private void MediaVolumePropertyChanged()
86+
{
87+
ApplyVolume();
88+
}
89+
90+
private void ApplyVolume()
91+
{
92+
if (PlayerElement.MediaPlayer != null)
93+
{
94+
_isSettingVolume = true;
95+
try
96+
{
97+
PlayerElement.MediaPlayer.Volume = MediaVolume;
98+
}
99+
finally
100+
{
101+
_isSettingVolume = false;
102+
}
103+
}
49104
}
50105

51106
private void KeyboardAccelerator_Space_Invoked(Microsoft.UI.Xaml.Input.KeyboardAccelerator sender, Microsoft.UI.Xaml.Input.KeyboardAcceleratorInvokedEventArgs args)

src/modules/peek/Peek.FilePreviewer/FilePreview.xaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@
7777

7878
<controls:AudioControl
7979
x:Name="AudioPreview"
80+
MediaVolume="{x:Bind MediaVolume, Mode=OneWay}"
8081
Source="{x:Bind AudioPreviewer.Preview, Mode=OneWay}"
8182
ToolTipText="{x:Bind InfoTooltip, Mode=OneWay}"
8283
Visibility="{x:Bind IsPreviewVisible(AudioPreviewer, Previewer.State), Mode=OneWay}" />

src/modules/peek/Peek.FilePreviewer/FilePreview.xaml.cs

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ public sealed partial class FilePreview : UserControl, IDisposable
3434

3535
public event EventHandler<PreviewSizeChangedArgs>? PreviewSizeChanged;
3636

37+
public event EventHandler<double>? MediaVolumeChanged;
38+
3739
public static readonly DependencyProperty ItemProperty =
3840
DependencyProperty.Register(
3941
nameof(Item),
@@ -48,6 +50,13 @@ public sealed partial class FilePreview : UserControl, IDisposable
4850
typeof(FilePreview),
4951
new PropertyMetadata(false, async (d, e) => await ((FilePreview)d).OnScalingFactorPropertyChanged()));
5052

53+
public static readonly DependencyProperty MediaVolumeProperty =
54+
DependencyProperty.Register(
55+
nameof(MediaVolume),
56+
typeof(double),
57+
typeof(FilePreview),
58+
new PropertyMetadata(1.0, (d, e) => ((FilePreview)d).OnMediaVolumePropertyChanged()));
59+
5160
[ObservableProperty]
5261
private int numberOfFiles;
5362

@@ -71,16 +80,43 @@ public sealed partial class FilePreview : UserControl, IDisposable
7180

7281
private CancellationTokenSource _cancellationTokenSource = new();
7382

83+
private bool _isSettingVolume;
84+
7485
public FilePreview()
7586
{
7687
InitializeComponent();
88+
89+
// Subscribe to video player volume changes
90+
VideoPreview.MediaPlayer.VolumeChanged += MediaPlayer_VolumeChanged;
91+
92+
// Subscribe to audio player volume changes
93+
AudioPreview.VolumeChanged += AudioPreview_VolumeChanged;
7794
}
7895

7996
public void Dispose()
8097
{
98+
VideoPreview.MediaPlayer.VolumeChanged -= MediaPlayer_VolumeChanged;
99+
AudioPreview.VolumeChanged -= AudioPreview_VolumeChanged;
81100
_cancellationTokenSource.Dispose();
82101
}
83102

103+
private void MediaPlayer_VolumeChanged(Windows.Media.Playback.MediaPlayer sender, object args)
104+
{
105+
// Only notify if the change was made by user (not by us setting the volume)
106+
if (!_isSettingVolume)
107+
{
108+
DispatcherQueue.TryEnqueue(() =>
109+
{
110+
MediaVolumeChanged?.Invoke(this, sender.Volume);
111+
});
112+
}
113+
}
114+
115+
private void AudioPreview_VolumeChanged(object? sender, double volume)
116+
{
117+
MediaVolumeChanged?.Invoke(this, volume);
118+
}
119+
84120
private async void Previewer_PropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e)
85121
{
86122
// Fallback on DefaultPreviewer if we fail to load the correct Preview
@@ -139,6 +175,12 @@ public double ScalingFactor
139175
}
140176
}
141177

178+
public double MediaVolume
179+
{
180+
get => (double)GetValue(MediaVolumeProperty);
181+
set => SetValue(MediaVolumeProperty, value);
182+
}
183+
142184
public bool MatchPreviewState(PreviewState? value, PreviewState stateToMatch)
143185
{
144186
return value == stateToMatch;
@@ -218,6 +260,30 @@ private async Task OnScalingFactorPropertyChanged()
218260
await UpdatePreviewSizeAsync(_cancellationTokenSource.Token);
219261
}
220262

263+
private void OnMediaVolumePropertyChanged()
264+
{
265+
ApplyMediaVolume();
266+
}
267+
268+
private void ApplyMediaVolume()
269+
{
270+
var volume = MediaVolume;
271+
_isSettingVolume = true;
272+
try
273+
{
274+
if (VideoPreview.MediaPlayer != null)
275+
{
276+
VideoPreview.MediaPlayer.Volume = volume;
277+
}
278+
279+
AudioPreview.MediaVolume = volume;
280+
}
281+
finally
282+
{
283+
_isSettingVolume = false;
284+
}
285+
}
286+
221287
private async Task UpdatePreviewSizeAsync(CancellationToken cancellationToken)
222288
{
223289
if (Previewer != null)

src/modules/peek/Peek.UI/MainWindowViewModel.cs

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,21 @@ public int DisplayItemCount
110110
[ObservableProperty]
111111
private bool _isErrorVisible = false;
112112

113+
private IUserSettings UserSettings { get; }
114+
115+
public double MediaVolume
116+
{
117+
get => UserSettings.MediaVolume;
118+
set
119+
{
120+
if (UserSettings.MediaVolume != value)
121+
{
122+
UserSettings.MediaVolume = value;
123+
OnPropertyChanged();
124+
}
125+
}
126+
}
127+
113128
private enum NavigationDirection
114129
{
115130
Forwards,
@@ -126,9 +141,10 @@ private enum NavigationDirection
126141

127142
private DispatcherTimer NavigationThrottleTimer { get; set; } = new();
128143

129-
public MainWindowViewModel(NeighboringItemsQuery query)
144+
public MainWindowViewModel(NeighboringItemsQuery query, IUserSettings userSettings)
130145
{
131146
NeighboringItemsQuery = query;
147+
UserSettings = userSettings;
132148
WindowTitle = _defaultWindowTitle;
133149

134150
NavigationThrottleTimer.Tick += NavigationThrottleTimer_Tick;

src/modules/peek/Peek.UI/PeekXAML/MainWindow.xaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,11 @@
4646
NumberOfFiles="{x:Bind ViewModel.DisplayItemCount, Mode=OneWay}" />
4747

4848
<fp:FilePreview
49+
x:Name="FilePreviewer"
4950
Grid.Row="1"
5051
Item="{x:Bind ViewModel.CurrentItem, Mode=OneWay}"
52+
MediaVolume="{x:Bind ViewModel.MediaVolume, Mode=OneWay}"
53+
MediaVolumeChanged="FilePreviewer_MediaVolumeChanged"
5154
NumberOfFiles="{x:Bind ViewModel.DisplayItemCount, Mode=OneWay}"
5255
PreviewSizeChanged="FilePreviewer_PreviewSizeChanged"
5356
ScalingFactor="{x:Bind ViewModel.ScalingFactor, Mode=OneWay}" />

src/modules/peek/Peek.UI/PeekXAML/MainWindow.xaml.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -284,6 +284,11 @@ private void AppWindow_Closing(AppWindow sender, AppWindowClosingEventArgs args)
284284
Uninitialize();
285285
}
286286

287+
private void FilePreviewer_MediaVolumeChanged(object? sender, double volume)
288+
{
289+
ViewModel.MediaVolume = volume;
290+
}
291+
287292
private bool IsNewSingleSelectedItem(SelectedItem selectedItem)
288293
{
289294
try

src/modules/peek/Peek.UI/Services/IUserSettings.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,7 @@ public interface IUserSettings
99
public bool CloseAfterLosingFocus { get; }
1010

1111
public bool ConfirmFileDelete { get; set; }
12+
13+
public double MediaVolume { get; set; }
1214
}
1315
}

src/modules/peek/Peek.UI/Services/UserSettings.cs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ private PeekSettings Settings
3838
_settings = value;
3939
CloseAfterLosingFocus = _settings.Properties.CloseAfterLosingFocus.Value;
4040
ConfirmFileDelete = _settings.Properties.ConfirmFileDelete.Value;
41+
_mediaVolume = _settings.Properties.MediaVolume.Value;
4142
}
4243
}
4344
}
@@ -49,6 +50,8 @@ private PeekSettings Settings
4950

5051
private bool _confirmFileDelete;
5152

53+
private double _mediaVolume = 1.0;
54+
5255
/// <summary>
5356
/// Gets or sets a value indicating whether the user is prompted before a file is recycled.
5457
/// </summary>
@@ -75,6 +78,29 @@ public bool ConfirmFileDelete
7578
}
7679
}
7780

81+
/// <summary>
82+
/// Gets or sets the volume level for media playback (0.0 to 1.0).
83+
/// </summary>
84+
public double MediaVolume
85+
{
86+
get => _mediaVolume;
87+
set
88+
{
89+
// Clamp value between 0 and 1
90+
var clampedValue = Math.Max(0.0, Math.Min(1.0, value));
91+
if (Math.Abs(_mediaVolume - clampedValue) > 0.001)
92+
{
93+
_mediaVolume = clampedValue;
94+
95+
lock (_settingsLock)
96+
{
97+
_settings.Properties.MediaVolume.Value = _mediaVolume;
98+
_settingsUtils.SaveSettings(_settings.ToJsonString(), PeekModuleName);
99+
}
100+
}
101+
}
102+
}
103+
78104
public UserSettings()
79105
{
80106
_settingsUtils = new SettingsUtils();

src/settings-ui/Settings.UI.Library/PeekProperties.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ public PeekProperties()
2020
CloseAfterLosingFocus = new BoolProperty(false);
2121
ConfirmFileDelete = new BoolProperty(true);
2222
EnableSpaceToActivate = new BoolProperty(true); // Toggle is ON by default for new users. No impact on existing users.
23+
MediaVolume = new DoubleProperty(1.0); // Default volume is 100%
2324
}
2425

2526
public HotkeySettings ActivationShortcut { get; set; }
@@ -32,6 +33,8 @@ public PeekProperties()
3233

3334
public BoolProperty EnableSpaceToActivate { get; set; }
3435

36+
public DoubleProperty MediaVolume { get; set; }
37+
3538
public override string ToString() => JsonSerializer.Serialize(this, SettingsSerializationContext.Default.PeekProperties);
3639
}
3740
}

0 commit comments

Comments
 (0)