mirror of
https://github.com/Royce551/FRESHMusicPlayer.git
synced 2025-01-22 10:51:52 -05:00
WPF: beginnings of queue stashing Avalonia: beginnings of MPRIS
This commit is contained in:
parent
9867003f24
commit
a94ce2abed
9 changed files with 301 additions and 1 deletions
|
@ -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>
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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}");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
|
|
|
@ -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; }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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="<" 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"/>
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue