Skip to content

Commit

Permalink
Preloading & Small improvements:
Browse files Browse the repository at this point in the history
 - Added the ability to preload a cue to a given time
 - Fixed StartTime related bugs in SoundCues
 - Changed behaviour of pause/unpause to make it global
 - Added (readonly) keyboard shortcut list to the settings window
 - Other minor changes
  • Loading branch information
space928 committed Feb 20, 2024
1 parent 7f90b46 commit 989760d
Show file tree
Hide file tree
Showing 8 changed files with 197 additions and 39 deletions.
2 changes: 1 addition & 1 deletion QPlayer/QPlayer.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<Nullable>enable</Nullable>
<UseWPF>true</UseWPF>
<Title>QPlayer</Title>
<Version>1.3.2-alpha</Version>
<Version>1.3.3-beta</Version>
<Authors>Thomas Mathieson</Authors>
<Company>Thomas Mathieson</Company>
<Copyright>©️ Thomas Mathieson 2024</Copyright>
Expand Down
31 changes: 30 additions & 1 deletion QPlayer/ViewModels/CueViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ public abstract class CueViewModel : ObservableObject, IConvertibleModel<Cue, Cu
[Reactive] public MainViewModel? MainViewModel => mainViewModel;
[Reactive] public bool IsSelected => mainViewModel?.SelectedCue == this;
[Reactive] public CueState State { get; set; }
[Reactive] public TimeSpan PlaybackTime { get; set; }
[Reactive] public virtual TimeSpan PlaybackTime { get; set; }
[Reactive][ReactiveDependency(nameof(LoopMode))]
public bool UseLoopCount => LoopMode == LoopMode.Looped || LoopMode == LoopMode.LoopedInfinite;
[Reactive]
Expand Down Expand Up @@ -165,6 +165,9 @@ private void MainViewModel_PropertyChanged(object? sender, System.ComponentModel
}

#region Command Handlers
/// <summary>
/// Starts this cues after it's delay has elapsed.
/// </summary>
public virtual void DelayedGo()
{
if (Delay == TimeSpan.Zero)
Expand All @@ -181,6 +184,9 @@ public virtual void DelayedGo()
mainViewModel?.ActiveCues.Add(this);
}

/// <summary>
/// Starts this cue immediately.
/// </summary>
public virtual void Go()
{
if (Duration == TimeSpan.Zero)
Expand All @@ -190,19 +196,42 @@ public virtual void Go()
mainViewModel?.ActiveCues.Add(this);
}

/// <summary>
/// Pauses this cue. It can be resumed again by calling Go.
///
/// Not all cues support pausing. For unsupported cues, this should Stop().
/// </summary>
public virtual void Pause()
{
goTimer.Stop();
State = CueState.Paused;
}

/// <summary>
/// Stops this cue immediately.
/// </summary>
public virtual void Stop()
{
goTimer.Stop();
State = CueState.Ready;
mainViewModel?.ActiveCues.Remove(this);
}

/// <summary>
/// Sets the playback time of the cue to the given time, and puts it in the paused state.
/// </summary>
/// <param name="startTime">the time to start the cue at.</param>
public virtual void Preload(TimeSpan startTime)
{
if(State == CueState.Ready || State == CueState.Paused)
{
PlaybackTime = startTime;
State = CueState.Paused;
OnPropertyChanged(nameof(PlaybackTimeString));
OnPropertyChanged(nameof(PlaybackTimeStringShort));
}
}

public void SelectExecute()
{
if(mainViewModel != null)
Expand Down
27 changes: 19 additions & 8 deletions QPlayer/ViewModels/SoundCueViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,16 @@ public class SoundCueViewModel : CueViewModel, IConvertibleModel<Cue, CueViewMod
[Reactive] public string Path { get; set; } = string.Empty;
[Reactive] public TimeSpan StartTime { get; set; }
[Reactive] public TimeSpan PlaybackDuration { get; set; } = TimeSpan.Zero;
[Reactive] public override TimeSpan Duration => PlaybackDuration == TimeSpan.Zero ? audioFile?.TotalTime ?? TimeSpan.Zero : PlaybackDuration;
[Reactive] public override TimeSpan Duration => PlaybackDuration == TimeSpan.Zero ? (audioFile?.TotalTime ?? TimeSpan.Zero) - StartTime : PlaybackDuration;
[Reactive] public override TimeSpan PlaybackTime
{
get => IsAudioFileValid ? audioFile.CurrentTime - StartTime : TimeSpan.Zero;
set
{
if(IsAudioFileValid)
audioFile.CurrentTime = value + StartTime;
}
}
[Reactive] public float Volume { get; set; }
[Reactive] public float FadeIn { get; set; }
[Reactive] public float FadeOut { get; set; }
Expand Down Expand Up @@ -59,6 +68,11 @@ public SoundCueViewModel(MainViewModel mainViewModel) : base(mainViewModel)
if (audioFile != null)
audioFile.Volume = Volume;
break;
case nameof(StartTime):
OnPropertyChanged(nameof(Duration));
OnPropertyChanged(nameof(PlaybackTimeString));
OnPropertyChanged(nameof(PlaybackTimeStringShort));
break;
}
};
}
Expand Down Expand Up @@ -89,10 +103,9 @@ private void FadeOutTimer_Elapsed(object? sender, ElapsedEventArgs e)
private void AudioProgressUpdater_Elapsed(object? sender, ElapsedEventArgs e)
{
synchronizationContext?.Post((x) => {
if (!IsAudioFileValid || mainViewModel == null)
return;

PlaybackTime = audioFile.CurrentTime;
OnPropertyChanged(nameof(PlaybackTime));
OnPropertyChanged(nameof(PlaybackTimeString));
OnPropertyChanged(nameof(PlaybackTimeStringShort));
}, null);
}

Expand Down Expand Up @@ -146,7 +159,7 @@ public override void Go()
audioProgressUpdater.Start();
if (FadeOut > 0)
{
double fadeOutTime = (Duration - TimeSpan.FromSeconds(FadeOut) - audioFile.CurrentTime).TotalMilliseconds;
double fadeOutTime = (Duration - TimeSpan.FromSeconds(FadeOut) - audioFile.CurrentTime - StartTime).TotalMilliseconds;
if (fadeOutTime <= int.MaxValue)
{
fadeOutTimer.Interval = fadeOutTime;
Expand Down Expand Up @@ -217,7 +230,6 @@ private void StopAudio()
if (!IsAudioFileValid || fadeInOutProvider == null || mainViewModel == null)
return;
mainViewModel.AudioPlaybackManager.StopSound(fadeInOutProvider);
audioFile.CurrentTime = StartTime;
PlaybackTime = TimeSpan.Zero;
audioProgressUpdater.Stop();
fadeOutTimer.Stop();
Expand Down Expand Up @@ -254,7 +266,6 @@ private void LoadAudioFile()
try
{
audioFile = new(path);
audioFile.CurrentTime = StartTime;
OnPropertyChanged(nameof(Duration));
PlaybackTime = TimeSpan.Zero;
// For some reason these don't get raised automatically...
Expand Down
32 changes: 27 additions & 5 deletions QPlayer/ViewModels/ViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ [Reactive] public CueViewModel? SelectedCue {

[Reactive] public ProjectSettingsViewModel ProjectSettings { get; private set; }

[Reactive] public TimeSpan PreloadTime { get; set; }

[Reactive] public RelayCommand NewProjectCommand { get; private set; }
[Reactive] public RelayCommand OpenProjectCommand { get; private set; }
[Reactive] public RelayCommand SaveProjectCommand { get; private set; }
Expand All @@ -62,9 +64,11 @@ [Reactive] public CueViewModel? SelectedCue {

[Reactive] public RelayCommand GoCommand { get; private set; }
[Reactive] public RelayCommand PauseCommand { get; private set; }
[Reactive] public RelayCommand UnpauseCommand { get; private set; }
[Reactive] public RelayCommand StopCommand { get; private set; }
[Reactive] public RelayCommand UpCommand { get; private set; }
[Reactive] public RelayCommand DownCommand { get; private set; }
[Reactive] public RelayCommand PreloadCommand { get; private set; }

[Reactive] public string? ProjectFilePath { get; private set; }

Expand Down Expand Up @@ -141,7 +145,7 @@ public MainViewModel()
audioPlaybackManager = new(this);

// Bind commands
OpenLogCommand = new(OpenLogExecute, () => logWindow == null);
OpenLogCommand = new(OpenLogExecute);
NewProjectCommand = new(NewProjectExecute);
OpenProjectCommand = new(OpenProjectExecute);
SaveProjectCommand = new(SaveProjectExecute);
Expand All @@ -161,9 +165,11 @@ public MainViewModel()

GoCommand = new(GoExecute);
PauseCommand = new(PauseExecute);
UnpauseCommand = new(UnpauseExecute);
StopCommand = new(StopExecute);
UpCommand = new(() => SelectedCueInd--);
DownCommand = new(() => SelectedCueInd++);
PreloadCommand = new(PreloadExecute);

slowUpdateTimer = new(TimeSpan.FromMilliseconds(250));
slowUpdateTimer.AutoReset = true;
Expand Down Expand Up @@ -258,6 +264,12 @@ public void OpenProjectExecute()

public void OpenLogExecute()
{
if(logWindow != null)
{
logWindow.Activate();
return;
}

logWindow = new(this);
//logWindow.Owner = ((Window)e.Source);
//Log("Opening log...");
Expand Down Expand Up @@ -309,10 +321,15 @@ public void GoExecute()

public void PauseExecute()
{
if (SelectedCue != null)
{
SelectedCue.Pause();
}
for (int i = ActiveCues.Count - 1; i >= 0; i--)
ActiveCues[i].Pause();
}

public void UnpauseExecute()
{
for (int i = ActiveCues.Count - 1; i >= 0; i--)
if (ActiveCues[i].State == CueState.Paused)
ActiveCues[i].Go();
}

public void StopExecute()
Expand All @@ -323,6 +340,11 @@ public void StopExecute()
AudioPlaybackManager.StopAllSounds();
}

public void PreloadExecute()
{
SelectedCue?.Preload(PreloadTime);
}

public void MoveCueUpExecute()
{
if (SelectedCue == null || SelectedCueInd <= 0)
Expand Down
5 changes: 4 additions & 1 deletion QPlayer/Views/AboutWindow.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@
xmlns:vm="clr-namespace:QPlayer.ViewModels"
d:DataContext="{d:DesignInstance Type=vm:MainViewModel}"
mc:Ignorable="d"
Title="QPlayer - About" Height="300" Width="350" Style="{DynamicResource CustomWindowStyle}" ResizeMode="NoResize" WindowStartupLocation="CenterScreen">
Title="QPlayer - About" Height="300" Width="350"
Style="{DynamicResource CustomWindowStyle}"
ResizeMode="NoResize" WindowStartupLocation="CenterScreen"
Icon="{StaticResource IconImage}">
<Grid Margin="10,10,10,10">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100"/>
Expand Down
6 changes: 4 additions & 2 deletions QPlayer/Views/LogWindow.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:QPlayer.Views"
xmlns:vm="clr-namespace:QPlayer.ViewModels"
d:DataContext="{d:DesignInstance Type=vm:ViewModel}"
d:DataContext="{d:DesignInstance Type=vm:MainViewModel}"
mc:Ignorable="d"
Title="QPlayer - Log Window" Height="450" Width="800" Style="{DynamicResource CustomWindowStyle}">
Title="QPlayer - Log Window" Height="450" Width="800"
Style="{DynamicResource CustomWindowStyle}"
Icon="{StaticResource IconImage}">
<Grid>
<ListBox ItemsSource="{Binding LogList}" FontSize="11" FontFamily="Cascadia Mono" x:Name="LogListBox" ScrollViewer.ScrollChanged="ScrollViewer_OnScrollChanged"/>
</Grid>
Expand Down
53 changes: 36 additions & 17 deletions QPlayer/Views/MainWindow.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
<KeyBinding Key="Esc" Command="{Binding StopCommand}"/>
<KeyBinding Key="Backspace" Command="{Binding PauseCommand}"/>
<KeyBinding Key="OemOpenBrackets" Command="{Binding PauseCommand}"/>
<KeyBinding Key="OemCloseBrackets" Command="{Binding GoCommand}"/>
<KeyBinding Key="OemCloseBrackets" Command="{Binding UnpauseCommand}"/>
<KeyBinding Key="Up" Command="{Binding UpCommand}"/>
<KeyBinding Key="Down" Command="{Binding DownCommand}"/>
<KeyBinding Key="Delete" Command="{Binding DeleteCueCommand}"/>
Expand Down Expand Up @@ -73,40 +73,59 @@
<ColumnDefinition Width="*" MinWidth="20"/>
<ColumnDefinition Width="250" MinWidth="20"/>
</Grid.ColumnDefinitions>
<DockPanel Grid.Column="0">
<Button Content="GO" Command="{Binding GoCommand}"
<Grid Grid.Column="0">
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition MaxHeight="85"/>
</Grid.RowDefinitions>
<DockPanel Grid.Row="0">
<Button Content="GO" Command="{Binding GoCommand}"
Height="70"
FontSize="28" FontWeight="Bold"
Background="#FF135F1B" BorderBrush="#FF032707"
Style="{DynamicResource SlightlyRoundButton}"
ToolTip="Advances to the next cue (Space)"
DockPanel.Dock="Top"/>
<Button Content="Stop" Command="{Binding StopCommand}"
<Button Content="Stop" Command="{Binding StopCommand}"
Height="40"
FontSize="16" FontWeight="Bold"
Background="#FF5F1313" BorderBrush="#FF4C0707"
Style="{DynamicResource SlightlyRoundButton}"
ToolTip="Stops all active cues (Esc)"
DockPanel.Dock="Top"/>
<Label Content="{Binding Clock}"
<Label Content="{Binding Clock}"
HorizontalAlignment="Center"
FontStretch="Expanded" FontFamily="Consolas" FontSize="16" FontWeight="Bold"
BorderBrush="{DynamicResource ComboBox.Static.Background}" BorderThickness="2"
DockPanel.Dock="Top"/>
<Separator DockPanel.Dock="Top"/>
<Label Content="Active Cues" Background="{DynamicResource AREghZyBrush.Primary.2.Background.Static}"
<Separator DockPanel.Dock="Top"/>
<Label Content="Active Cues" Background="{DynamicResource AREghZyBrush.Primary.2.Background.Static}"
FontWeight="Bold" Margin="5,0,0,0"
DockPanel.Dock="Top"/>
<ScrollViewer VerticalScrollBarVisibility="Auto" DockPanel.Dock="Top">
<ItemsControl ItemsSource="{Binding ActiveCues}" Margin="2,0,2,0">
<ItemsControl.ItemTemplate>
<DataTemplate>
<local:ActiveCueControl/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
</DockPanel>
<ScrollViewer VerticalScrollBarVisibility="Auto" DockPanel.Dock="Top">
<ItemsControl ItemsSource="{Binding ActiveCues}" Margin="2,0,2,0">
<ItemsControl.ItemTemplate>
<DataTemplate>
<local:ActiveCueControl/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
</DockPanel>
<StackPanel Grid.Row="1">
<Label Content="Preload Cue" Background="{DynamicResource AREghZyBrush.Primary.2.Background.Static}"
FontWeight="Bold" Margin="5,0,0,0"/>
<DockPanel>
<Label Content="Preload Time"/>
<TextBox Text="{Binding PreloadTime,
Converter={StaticResource TimeSpanStringConverter},
ConverterParameter=False,
UpdateSourceTrigger=PropertyChanged}"
DockPanel.Dock="Right" Margin="8,2,2,2"/>
</DockPanel>
<Button Content="Preload Cue" Command="{Binding PreloadCommand}" Margin="2" ToolTip="Loads the selected cue to given time"/>
</StackPanel>
</Grid>
<GridSplitter Grid.Column="1" HorizontalAlignment="Left" Margin="0,0,0,0" Width="5" ResizeBehavior="PreviousAndCurrent"/>
<GridSplitter Grid.Column="2" HorizontalAlignment="Left" Margin="0,0,0,0" Width="5" ResizeBehavior="PreviousAndCurrent"/>
<ScrollViewer Grid.Column="1" Margin="5,5,0,0"
Expand Down
Loading

0 comments on commit 989760d

Please sign in to comment.