Skip to content

Commit

Permalink
Handle native Media Transport Controls
Browse files Browse the repository at this point in the history
  • Loading branch information
sabihoshi committed May 4, 2021
1 parent 9acbdb2 commit 84a1c21
Show file tree
Hide file tree
Showing 5 changed files with 121 additions and 37 deletions.
48 changes: 40 additions & 8 deletions GenshinLyreMidiPlayer.WPF/Bootstrapper.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,16 @@
using System.Configuration;
using System;
using System.Configuration;
using System.Diagnostics;
using System.Drawing;
using System.IO;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Documents;
using System.Windows.Navigation;
using Windows.Media;
using Windows.Media.Playback;
using Windows.Storage;
using Windows.Storage.Streams;
using GenshinLyreMidiPlayer.Data;
using GenshinLyreMidiPlayer.Data.Properties;
using GenshinLyreMidiPlayer.WPF.ViewModels;
Expand All @@ -15,7 +22,7 @@ namespace GenshinLyreMidiPlayer.WPF
{
public class Bootstrapper : Bootstrapper<MainWindowViewModel>
{
protected override void OnStart()
public Bootstrapper()
{
// Make Hyperlinks handle themselves
EventManager.RegisterClassHandler(
Expand All @@ -33,16 +40,18 @@ protected override void OnStart()

protected override void ConfigureIoC(IStyletIoCBuilder builder)
{
var level = ConfigurationUserLevel.PerUserRoamingAndLocal;
var config = ConfigurationManager.OpenExeConfiguration(level);

var path = Path.GetDirectoryName(config.FilePath);

if (!Directory.Exists(path))
Directory.CreateDirectory(path);

builder.Bind<LyreContext>().ToFactory(_ =>
{
var config = ConfigurationManager
.OpenExeConfiguration(ConfigurationUserLevel.PerUserRoamingAndLocal);
var path = Path.GetDirectoryName(config.FilePath);
var source = Path.Combine(path!, Settings.Default.SqliteConnection);
if (!Directory.Exists(path)) Directory.CreateDirectory(path);
var options = new DbContextOptionsBuilder<LyreContext>()
.UseSqlite($"Data Source={source}")
.Options;
Expand All @@ -52,6 +61,29 @@ protected override void ConfigureIoC(IStyletIoCBuilder builder)
return db;
});

builder.Bind<MediaPlayer>().ToFactory(_ =>
{
var player = new MediaPlayer();
var controls = player.SystemMediaTransportControls;
controls.IsEnabled = true;
controls.DisplayUpdater.Type = MediaPlaybackType.Music;
Task.Run(async () =>
{
var icon = Path.Combine(path!, "icon.png");
var uri = new Uri("pack://application:,,,/item_windsong_lyre.png");
var resource = Application.GetResourceStream(uri)!.Stream;
Image.FromStream(resource)
.Save(icon);
var file = await StorageFile.GetFileFromPathAsync(icon);
controls.DisplayUpdater.Thumbnail = RandomAccessStreamReference.CreateFromFile(file);
});
return player;
});
}
}
}
7 changes: 6 additions & 1 deletion GenshinLyreMidiPlayer.WPF/GenshinLyreMidiPlayer.WPF.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<UseWPF>true</UseWPF>
<StartupObject>GenshinLyreMidiPlayer.WPF.App</StartupObject>
<ApplicationManifest>app.manifest</ApplicationManifest>
<Version>1.8.3.1</Version>
<Version>1.9.1</Version>
<ApplicationIcon>item_windsong_lyre.ico</ApplicationIcon>
<Nullable>enable</Nullable>
<RepositoryUrl>https://github.com/sabihoshi/GenshinLyreMidiPlayer</RepositoryUrl>
Expand Down Expand Up @@ -39,4 +39,9 @@
<ProjectReference Include="..\GenshinLyreMidiPlayer.Data\GenshinLyreMidiPlayer.Data.csproj" />
</ItemGroup>

<ItemGroup>
<None Remove="item_windsong_lyre.png" />
<Resource Include="item_windsong_lyre.png" />
</ItemGroup>

</Project>
78 changes: 65 additions & 13 deletions GenshinLyreMidiPlayer.WPF/ViewModels/LyrePlayerViewModel.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using Windows.Media;
using Windows.Media.Playback;
using GenshinLyreMidiPlayer.Data.Midi;
using GenshinLyreMidiPlayer.Data.Notification;
using GenshinLyreMidiPlayer.Data.Properties;
Expand All @@ -11,7 +13,7 @@
using Melanchall.DryWetMidi.Tools;
using Stylet;
using StyletIoC;
using static GenshinLyreMidiPlayer.WPF.ViewModels.PlaylistViewModel;
using static Windows.Media.MediaPlaybackAutoRepeatMode;
using MidiFile = GenshinLyreMidiPlayer.Data.Midi.MidiFile;

namespace GenshinLyreMidiPlayer.WPF.ViewModels
Expand All @@ -23,9 +25,10 @@ public class LyrePlayerViewModel : Screen,
{
private static readonly Settings Settings = Settings.Default;
private readonly IEventAggregator _events;
private readonly MediaPlayer _player;
private readonly SettingsPageViewModel _settings;
private readonly OutputDevice _speakers = OutputDevice.GetByName("Microsoft GS Wavetable Synth");
private readonly PlaybackCurrentTimeWatcher _timeWatcher = PlaybackCurrentTimeWatcher.Instance;
private readonly OutputDevice _speakers;
private readonly PlaybackCurrentTimeWatcher _timeWatcher;
private bool _ignoreSliderChange;
private InputDevice? _inputDevice;
private MidiInput? _selectedMidiInput;
Expand All @@ -34,6 +37,9 @@ public class LyrePlayerViewModel : Screen,
public LyrePlayerViewModel(IContainer ioc,
SettingsPageViewModel settings, PlaylistViewModel playlist)
{
_speakers = OutputDevice.GetByName("Microsoft GS Wavetable Synth");
_timeWatcher = PlaybackCurrentTimeWatcher.Instance;

_events = ioc.Get<IEventAggregator>();
_events.Subscribe(this);

Expand All @@ -43,6 +49,14 @@ public LyrePlayerViewModel(IContainer ioc,
SelectedMidiInput = MidiInputs[0];

_timeWatcher.CurrentTimeChanged += OnSongTick;

_player = ioc.Get<MediaPlayer>();

_player.CommandManager.NextReceived += (_, _) => Next();
_player.CommandManager.PreviousReceived += (_, _) => Previous();

_player.CommandManager.PlayReceived += (_, _) => PlayPause();
_player.CommandManager.PauseReceived += (_, _) => PlayPause();
}

public BindableCollection<MidiInput> MidiInputs { get; } = new()
Expand All @@ -56,7 +70,7 @@ public bool CanHitNext
{
get
{
if (Playlist.Loop is LoopState.All or LoopState.Single)
if (Playlist.Loop is List or Track)
return true;

var last = Playlist.GetPlaylist().LastOrDefault();
Expand Down Expand Up @@ -113,6 +127,8 @@ public MidiInput? SelectedMidiInput
}
}

private MusicDisplayProperties Display => _player.SystemMediaTransportControls.DisplayUpdater.MusicProperties;

public Playback? Playback { get; private set; }

public PlaylistViewModel Playlist { get; }
Expand All @@ -123,6 +139,8 @@ public MidiInput? SelectedMidiInput

public string PlayPauseIcon => Playback?.IsRunning ?? false ? PauseIcon : PlayIcon;

private SystemMediaTransportControls Controls => _player.SystemMediaTransportControls;

public TimeSpan CurrentTime => TimeSpan.FromSeconds(SongSlider);

public TimeSpan MaximumTime => Playlist.OpenedFile?.Duration ?? TimeSpan.Zero;
Expand All @@ -143,15 +161,47 @@ public void Handle(MidiFile file)

InitializeTracks();
InitializePlayback();

NotifyOfPropertyChange(() => CanHitNext);
NotifyOfPropertyChange(() => CanHitPrevious);
}

public void Handle(MidiTrack track) { InitializePlayback(); }

public void Handle(SettingsPageViewModel message) { InitializePlayback(); }

public void UpdateButtons()
{
NotifyOfPropertyChange(() => CanHitNext);
NotifyOfPropertyChange(() => CanHitPrevious);
NotifyOfPropertyChange(() => CanHitPlayPause);

NotifyOfPropertyChange(() => PlayPauseIcon);
NotifyOfPropertyChange(() => MaximumTime);

var controls = Controls;

controls.IsPlayEnabled = CanHitPlayPause;
controls.IsPauseEnabled = CanHitPlayPause;

controls.IsNextEnabled = CanHitNext;
controls.IsPreviousEnabled = CanHitPrevious;

controls.PlaybackStatus =
Playlist.OpenedFile is null ? MediaPlaybackStatus.Closed :
Playback is null ? MediaPlaybackStatus.Stopped :
Playback.IsRunning ? MediaPlaybackStatus.Playing :
MediaPlaybackStatus.Paused;

var file = Playlist.OpenedFile;
if (file is not null)
{
var position = $"{file.Position}/{Playlist.GetPlaylist().Count}";

Display.Title = file.Title;
Display.Artist = $"Playing {position} {CurrentTime:mm\\:ss}";
}

controls.DisplayUpdater.Update();
}

private void InitializeTracks()
{
MidiTracks.Clear();
Expand All @@ -163,7 +213,7 @@ private void InitializeTracks()
public async Task OpenFile()
{
await Playlist.OpenFile();
NotifyOfPropertyChange(() => CanHitNext);
UpdateButtons();
}

public void CloseFile()
Expand Down Expand Up @@ -220,17 +270,17 @@ private void InitializePlayback()
_timeWatcher.AddPlayback(Playback, TimeSpanType.Metric);
_timeWatcher.Start();
NotifyOfPropertyChange(() => PlayPauseIcon);
UpdateButtons();
};

Playback.Stopped += (_, _) =>
{
_timeWatcher.Stop();
NotifyOfPropertyChange(() => PlayPauseIcon);
UpdateButtons();
};

NotifyOfPropertyChange(() => MaximumTime);
UpdateButtons();
}

public void Previous()
Expand All @@ -250,7 +300,7 @@ public void Next()
if (next is null)
return;

if (next == Playlist.OpenedFile && Playlist.Loop == LoopState.Single)
if (next == Playlist.OpenedFile && Playlist.Loop == Track)
Handle(next);
else if (next != Playlist.OpenedFile)
Handle(next);
Expand All @@ -267,7 +317,7 @@ public void PlayPause()
Playback.Stop();
else
{
Playback.Loop = Playlist.Loop == LoopState.Single;
Playback.Loop = Playlist.Loop == Track;

var time = (MetricTimeSpan) TimeSpan.FromSeconds(SongSlider);
Playback.PlaybackStart = time;
Expand Down Expand Up @@ -298,6 +348,8 @@ public void OnSongTick(object? sender, PlaybackCurrentTimeChangedEventArgs e)
{
TimeSpan time = (MetricTimeSpan) playbackTime.Time;
MoveSlider(time.TotalSeconds);

UpdateButtons();
}
}

Expand Down
25 changes: 10 additions & 15 deletions GenshinLyreMidiPlayer.WPF/ViewModels/PlaylistViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Media;
using Windows.Media;
using GenshinLyreMidiPlayer.Data;
using GenshinLyreMidiPlayer.Data.Entities;
using GenshinLyreMidiPlayer.WPF.ModernWPF.Errors;
Expand All @@ -11,19 +12,13 @@
using ModernWpf;
using Stylet;
using StyletIoC;
using static Windows.Media.MediaPlaybackAutoRepeatMode;
using MidiFile = GenshinLyreMidiPlayer.Data.Midi.MidiFile;

namespace GenshinLyreMidiPlayer.WPF.ViewModels
{
public class PlaylistViewModel : Screen
{
public enum LoopState
{
None,
Single,
All
}

private readonly IEventAggregator _events;
private readonly IContainer _ioc;

Expand All @@ -39,7 +34,7 @@ public PlaylistViewModel(IContainer ioc, IEventAggregator events)

public bool Shuffle { get; set; }

public LoopState Loop { get; set; }
public MediaPlaybackAutoRepeatMode Loop { get; set; }

public MidiFile? OpenedFile { get; set; }

Expand All @@ -54,9 +49,9 @@ public PlaylistViewModel(IContainer ioc, IEventAggregator events)
public string LoopStateString =>
Loop switch
{
LoopState.None => "\xF5E7",
LoopState.Single => "\xE8ED",
LoopState.All => "\xE8EE"
None => "\xF5E7",
Track => "\xE8ED",
List => "\xE8EE"
};

public void ToggleShuffle()
Expand All @@ -72,17 +67,17 @@ public void ToggleShuffle()
public void ToggleLoop()
{
var loopState = (int) Loop;
var loopStates = Enum.GetValues(typeof(LoopState)).Length;
var loopStates = Enum.GetValues(typeof(MediaPlaybackAutoRepeatMode)).Length;

var newState = (loopState + 1) % loopStates;
Loop = (LoopState) newState;
Loop = (MediaPlaybackAutoRepeatMode) newState;
}

public MidiFile? Next()
{
var playlist = GetPlaylist().ToList();

if (Loop == LoopState.Single)
if (Loop == Track)
return OpenedFile ?? playlist.FirstOrDefault();

var next = playlist.FirstOrDefault();
Expand All @@ -91,7 +86,7 @@ public void ToggleLoop()
{
var current = playlist.IndexOf(OpenedFile) + 1;

if (Loop is LoopState.All)
if (Loop is List)
current %= playlist.Count;

next = playlist.ElementAtOrDefault(current);
Expand Down
Binary file added GenshinLyreMidiPlayer.WPF/item_windsong_lyre.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit 84a1c21

Please sign in to comment.