WPF: beginnings of queue stashing Avalonia: beginnings of MPRIS

This commit is contained in:
Royce551 2021-06-03 20:39:42 -05:00
parent 9867003f24
commit a94ce2abed
9 changed files with 301 additions and 1 deletions

View file

@ -25,6 +25,7 @@
<PackageReference Include="Avalonia.ReactiveUI" Version="0.10.5" />
<PackageReference Include="Avalonia.Svg.Skia" Version="0.10.0" />
<PackageReference Include="FRESHMusicPlayer.Core" Version="1.0.0" />
<PackageReference Include="Tmds.DBus" Version="0.9.1" />
<PackageReference Include="z440.atl.core" Version="3.19.0" />
</ItemGroup>
<ItemGroup>

View file

@ -0,0 +1,39 @@
using ATL;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace FRESHMusicPlayer.Handlers.Integrations
{
/// <summary>
/// Integrates FMP with something that can display current playback info
/// </summary>
public interface IPlaybackIntegration
{
event EventHandler UINeedsUpdate;
/// <summary>
/// Updates the integration with new information
/// </summary>
/// <param name="track">The current track</param>
/// <param name="status">The playback status of the current track</param>
void Update(Track track, PlaybackStatus status);
/// <summary>
/// Prepares the integration to be closed and set to null
/// </summary>
void Dispose();
}
/// <summary>
/// Our own version of WinRT's MediaPlaybackStatus in order to avoid type loading issues on Windows 7.
/// </summary>
public enum PlaybackStatus
{
Changing = 1,
Closed = 0,
Paused = 4,
Playing = 3,
Stopped = 2
}
}

View file

@ -0,0 +1,43 @@
using ATL;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace FRESHMusicPlayer.Handlers.Integrations
{
public class IntegrationHandler
{
public List<IPlaybackIntegration> AllIntegrations { get; private set; } = new();
public event EventHandler UINeedsUpdate;
public void Add(IPlaybackIntegration integration)
{
AllIntegrations.Add(integration);
integration.UINeedsUpdate += Integration_UINeedsUpdate;
}
public void Remove(IPlaybackIntegration integration)
{
integration.Dispose();
AllIntegrations.Remove(integration);
integration.UINeedsUpdate -= Integration_UINeedsUpdate;
}
public void Update(Track track, PlaybackStatus status)
{
foreach (var integration in AllIntegrations)
integration.Update(track, status);
}
public void Dispose()
{
foreach (var integration in AllIntegrations)
integration.Dispose();
}
private void Integration_UINeedsUpdate(object sender, EventArgs e) => UINeedsUpdate?.Invoke(null, EventArgs.Empty);
}
}

View file

@ -0,0 +1,138 @@
using ATL;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Tmds.DBus;
namespace FRESHMusicPlayer.Handlers.Integrations
{
public class MPRISIntegration : IPlaybackIntegration
{
public event EventHandler UINeedsUpdate;
private Player player;
public MPRISIntegration(FRESHMusicPlayer.Player player)
{
this.player = new(player);
Initialize();
}
public async Task Initialize()
{
var server = new ServerConnectionOptions();
using var connection = new Connection(server);
await connection.ConnectAsync();
await connection.RegisterObjectAsync(player);
await connection.RegisterServiceAsync("org.mpris.MediaPlayer2.FRESHMusicPlayer");
}
public void Dispose()
{
}
public void Update(Track track, PlaybackStatus status)
{
}
}
[DBusInterface("org.mpris.MediaPlayer2.Player")]
public interface IPlayer : IDBusObject
{
Task NextAsync();
Task PreviousAsync();
Task PlayPauseAsync();
Task StopAsync();
Task Seek(long offset);
Task SetPosition(ObjectPath trackID, long position);
Task OpenUri();
Task<IDisposable> WatchSeekedAsync(Action<ObjectPath> handler, Action<Exception> onError = null);
Task<IDictionary<string, object>> GetAllAsync();
Task<T> GetAsync<T>(string prop);
Task SetAsync(string prop, object val);
Task<IDisposable> WatchPropertiesAsync(Action<PropertyChanges> handler);
}
#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously
public class Player : IPlayer
{
private FRESHMusicPlayer.Player player;
public Player(FRESHMusicPlayer.Player player)
{
this.player = player;
}
public ObjectPath ObjectPath => new("/org/mpris/MediaPlayer2");
public Task<IDictionary<string, object>> GetAllAsync()
{
throw new NotImplementedException();
}
public Task<T> GetAsync<T>(string prop)
{
throw new NotImplementedException();
}
public async Task NextAsync()
{
player.NextSong();
}
public async Task OpenUri()
{
throw new NotImplementedException();
}
public async Task PlayPauseAsync()
{
if (player.Paused) player.ResumeMusic();
else player.PauseMusic();
}
public async Task PreviousAsync()
{
player.PreviousSong();
}
public async Task Seek(long offset)
{
player.CurrentTime.Add(TimeSpan.FromMilliseconds(offset * 1000));
}
public Task SetAsync(string prop, object val)
{
throw new NotImplementedException();
}
public async Task SetPosition(ObjectPath trackID, long position)
{
// HACK: what do i do with trackID?????
player.CurrentTime = TimeSpan.FromMilliseconds(position * 1000);
}
public async Task StopAsync()
{
player.StopMusic();
}
public Task<IDisposable> WatchPropertiesAsync(Action<PropertyChanges> handler)
{
throw new NotImplementedException();
}
public Task<IDisposable> WatchSeekedAsync(Action<ObjectPath> handler, Action<Exception> onError = null)
{
throw new NotImplementedException();
}
}
#pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously
}

View file

@ -0,0 +1,25 @@
using ATL;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace FRESHMusicPlayer.Handlers.Integrations
{
public class TestIntegration : IPlaybackIntegration
{
public event EventHandler UINeedsUpdate;
public void Dispose()
{
Debug.WriteLine("disposing");
}
public void Update(Track track, PlaybackStatus status)
{
Debug.WriteLine($"{track.Artist} - {track.Title}; Now {status}");
}
}
}

View file

@ -7,6 +7,7 @@ using Avalonia.Media.Imaging;
using Avalonia.Threading;
using FRESHMusicPlayer.Handlers;
using FRESHMusicPlayer.Handlers.Configuration;
using FRESHMusicPlayer.Handlers.Integrations;
using FRESHMusicPlayer.Views;
using LiteDB;
using ReactiveUI;
@ -29,6 +30,7 @@ namespace FRESHMusicPlayer.ViewModels
public Library Library { get; private set; }
public ConfigurationFile Config { get; private set; }
public Track CurrentTrack { get; private set; }
public IntegrationHandler Integrations { get; private set; } = new();
private bool pauseAfterCurrentTrack = false;
@ -48,9 +50,17 @@ namespace FRESHMusicPlayer.ViewModels
StartThings();
var library = new LiteDatabase($"Filename=\"{Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "FRESHMusicPlayer", "database.fdb2")}\";Connection=shared");
Library = new Library(library);
Integrations.UINeedsUpdate += Integrations_UINeedsUpdate;
Integrations.Add(new TestIntegration());
Integrations.Add(new MPRISIntegration(Player));
InitializeLibrary();
}
private void Integrations_UINeedsUpdate(object sender, EventArgs e)
{
throw new NotImplementedException();
}
public const string ProjectName = "FRESHMusicPlayer Cross-Platform Edition™ Beta 7";
private string windowTitle = ProjectName;
public string WindowTitle
@ -72,6 +82,7 @@ namespace FRESHMusicPlayer.ViewModels
CoverArt = null;
WindowTitle = ProjectName;
ProgressTimer.Stop();
Integrations.Update(CurrentTrack, PlaybackStatus.Stopped);
}
private async void ProgressTimer_Elapsed(object sender, ElapsedEventArgs e) => await Dispatcher.UIThread.InvokeAsync(() => ProgressTick());
@ -95,6 +106,8 @@ namespace FRESHMusicPlayer.ViewModels
this.RaisePropertyChanged(nameof(TotalTimeSeconds));
WindowTitle = $"{CurrentTrack.Artist} - {CurrentTrack.Title} | {ProjectName}";
ProgressTimer.Start();
Integrations.Update(CurrentTrack, PlaybackStatus.Playing);
await Task.Delay(1000); // temporary, to avoid fuckery with Bass backend
this.RaisePropertyChanged(nameof(TotalTime));
this.RaisePropertyChanged(nameof(TotalTimeSeconds));
@ -168,11 +181,13 @@ namespace FRESHMusicPlayer.ViewModels
{
Player.ResumeMusic();
Paused = false;
Integrations.Update(CurrentTrack, PlaybackStatus.Playing);
}
else
{
Player.PauseMusic();
Paused = true;
Integrations.Update(CurrentTrack, PlaybackStatus.Paused);
}
}
public void ShuffleCommand()
@ -347,6 +362,7 @@ namespace FRESHMusicPlayer.ViewModels
public async void CloseThings()
{
Library?.Database.Dispose();
Integrations.Dispose();
Config.Volume = Volume;
Config.CurrentTab = SelectedTab;
if (Player.FileLoaded)

View file

@ -30,5 +30,24 @@ namespace FRESHMusicPlayer.Handlers
Type = NotificationType.Success
});
}
public List<DatabaseQueue> GetAllQueues()
{
return Database.GetCollection<DatabaseQueue>("queues").Query().ToList();
}
public DatabaseQueue CreateQueue(List<string> queue, int position)
{
var newQueue = new DatabaseQueue { Queue = queue, QueuePosition = position };
Database.GetCollection<DatabaseQueue>("queues").Insert(newQueue);
return newQueue;
}
}
public class DatabaseQueue
{
public int DatabaseQueueId { get; set; }
public List<string> Queue { get; set; }
public int QueuePosition { get; set; }
}
}

View file

@ -26,7 +26,16 @@
<RowDefinition Height="Auto" MinHeight="29"/>
</Grid.RowDefinitions>
<ListBox x:Name="QueueList" ScrollViewer.HorizontalScrollBarVisibility="Disabled" HorizontalContentAlignment="Stretch" VirtualizingPanel.IsVirtualizing="False" Background="{StaticResource SecondaryColor}" Grid.Row="1" BorderBrush="{x:Null}" Grid.ColumnSpan="3"/>
<TextBlock TextWrapping="Wrap" Text="{x:Static resx:Resources.QUEUEMANAGEMENT_QUEUEHEADER}" Foreground="{StaticResource PrimaryTextColor}" FontWeight="Bold" FontSize="18" Margin="5,5,2,14" Grid.ColumnSpan="3"/>
<DockPanel Grid.ColumnSpan="3" LastChildFill="False" Margin="5,5,2,14">
<TextBlock DockPanel.Dock="Left" TextWrapping="Wrap" Text="{x:Static resx:Resources.QUEUEMANAGEMENT_QUEUEHEADER}" Foreground="{StaticResource PrimaryTextColor}" FontWeight="Bold" FontSize="18" />
<Button x:Name="NextQueueButton" DockPanel.Dock="Right" Content=">" Click="NextQueueButton_Click"/>
<TextBlock x:Name="CurrentQueueIndicator" DockPanel.Dock="Right" Text="0/10" Foreground="{StaticResource PrimaryTextColor}" VerticalAlignment="Center" Margin="10,0"/>
<Button x:Name="PreviousQueueButton" DockPanel.Dock="Right" Content="&lt;" Click="PreviousQueueButton_Click"/>
</DockPanel>
<TextBlock x:Name="RemainingTimeLabel" Text="{x:Static resx:Resources.QUEUEMANAGEMENT_REMAININGTIME}" Foreground="{StaticResource PrimaryTextColor}" FontSize="12" Margin="5,29,5,0" Grid.ColumnSpan="3"/>
<Button x:Name="AddTrackButton" Content="{x:Static resx:Resources.QUEUEMANAGEMENT_ADDTRACK}" Margin="2,5,2,5" Grid.Row="2" FontSize="12" Height="19" Grid.Column="0" VerticalAlignment="Top" Click="AddTrackButton_Click"/>
<Button x:Name="AddPlaylistButton" Content="{x:Static resx:Resources.QUEUEMANAGEMENT_ADDPLAYLIST}" Margin="2,5,2,5" Grid.Row="2" FontSize="12" Grid.Column="1" Height="19" VerticalAlignment="Top" Click="AddPlaylist_Click"/>

View file

@ -130,5 +130,15 @@ namespace FRESHMusicPlayer.Pages
{
InterfaceUtils.DoDragDrop((string[])e.Data.GetData(DataFormats.FileDrop), window.Player, window.Library, import: false, clearqueue: false);
}
private void PreviousQueueButton_Click(object sender, RoutedEventArgs e)
{
}
private void NextQueueButton_Click(object sender, RoutedEventArgs e)
{
}
}
}