Add media playback functionality to PLib video library manager. Integrate MediaPlayer.Controls and VideoLAN.LibVLC for enhanced video playback capabilities. Update MainWindow and ViewModels to manage video player state and commands, allowing users to play videos within the application. Enhance README.md to document new player features and requirements.
This commit is contained in:
@@ -0,0 +1,126 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Controls.Primitives;
|
||||
using Avalonia.Input;
|
||||
using Avalonia.Interactivity;
|
||||
using MediaPlayer.Controls;
|
||||
using PLib.Desktop.ViewModels;
|
||||
|
||||
namespace PLib.Desktop.Views;
|
||||
|
||||
/// <summary>
|
||||
/// Transport controls for the media page.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Playback state lives on <see cref="GpuMediaPlayer"/> itself — position, duration and
|
||||
/// whether it is playing are all its own properties, and seeking is a method call. Mirroring
|
||||
/// that into the view model would buy nothing but a second copy to keep in sync, so this
|
||||
/// code-behind wires the buttons straight to the control. Everything the page knows about
|
||||
/// the video — title, path, the commands around it — stays in the view model.
|
||||
/// </remarks>
|
||||
public sealed partial class VideoPlayerView : UserControl
|
||||
{
|
||||
/// <summary>True while the user is dragging the seek bar, so playback must not fight them.</summary>
|
||||
private bool _isScrubbing;
|
||||
|
||||
public VideoPlayerView()
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
PlayPauseButton.Click += OnPlayPause;
|
||||
MuteButton.Click += OnToggleMute;
|
||||
|
||||
// Tunnelled: the Slider's own handlers mark these as handled on the way back up.
|
||||
Seek.AddHandler(PointerPressedEvent, OnScrubStarted, RoutingStrategies.Tunnel);
|
||||
Seek.AddHandler(PointerReleasedEvent, OnScrubFinished, RoutingStrategies.Tunnel);
|
||||
|
||||
VolumeSlider.PropertyChanged += OnVolumeChanged;
|
||||
|
||||
Player.GetObservable(GpuMediaPlayer.PositionProperty).Subscribe(OnPositionChanged);
|
||||
Player.GetObservable(GpuMediaPlayer.DurationProperty).Subscribe(OnDurationChanged);
|
||||
Player.GetObservable(GpuMediaPlayer.IsPlayingProperty).Subscribe(OnIsPlayingChanged);
|
||||
Player.GetObservable(GpuMediaPlayer.LastErrorProperty).Subscribe(OnErrorChanged);
|
||||
|
||||
}
|
||||
|
||||
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
|
||||
{
|
||||
base.OnDetachedFromVisualTree(e);
|
||||
|
||||
// Leaving the page has to stop the decoder; nothing else will.
|
||||
Player.Stop();
|
||||
}
|
||||
|
||||
private void OnPlayPause(object? sender, RoutedEventArgs e)
|
||||
{
|
||||
if (Player.IsPlaying)
|
||||
{
|
||||
Player.Pause();
|
||||
}
|
||||
else
|
||||
{
|
||||
Player.Play();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnToggleMute(object? sender, RoutedEventArgs e) => Player.IsMuted = !Player.IsMuted;
|
||||
|
||||
private void OnScrubStarted(object? sender, PointerPressedEventArgs e) => _isScrubbing = true;
|
||||
|
||||
private void OnScrubFinished(object? sender, PointerReleasedEventArgs e)
|
||||
{
|
||||
_isScrubbing = false;
|
||||
Player.Seek(TimeSpan.FromSeconds(Seek.Value));
|
||||
}
|
||||
|
||||
private void OnVolumeChanged(object? sender, AvaloniaPropertyChangedEventArgs e)
|
||||
{
|
||||
if (e.Property != RangeBase.ValueProperty)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Player.Volume = VolumeSlider.Value;
|
||||
MuteIcon.Kind = VolumeIconFor(VolumeSlider.Value, Player.IsMuted);
|
||||
}
|
||||
|
||||
private void OnPositionChanged(TimeSpan position)
|
||||
{
|
||||
PositionText.Text = DisplayText.Duration(position);
|
||||
|
||||
if (!_isScrubbing)
|
||||
{
|
||||
Seek.Value = position.TotalSeconds;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnDurationChanged(TimeSpan duration)
|
||||
{
|
||||
DurationText.Text = DisplayText.Duration(duration);
|
||||
|
||||
// A zero maximum would pin the thumb to the left and swallow every seek.
|
||||
Seek.Maximum = duration > TimeSpan.Zero ? duration.TotalSeconds : 1;
|
||||
Seek.IsEnabled = duration > TimeSpan.Zero;
|
||||
}
|
||||
|
||||
private void OnIsPlayingChanged(bool isPlaying) =>
|
||||
PlayPauseIcon.Kind = isPlaying
|
||||
? Material.Icons.MaterialIconKind.Pause
|
||||
: Material.Icons.MaterialIconKind.Play;
|
||||
|
||||
private void OnErrorChanged(string? error)
|
||||
{
|
||||
ErrorText.Text = string.IsNullOrWhiteSpace(error)
|
||||
? null
|
||||
: $"Не удалось воспроизвести файл: {error}";
|
||||
|
||||
ErrorText.IsVisible = !string.IsNullOrWhiteSpace(error);
|
||||
}
|
||||
|
||||
private static Material.Icons.MaterialIconKind VolumeIconFor(double volume, bool isMuted) =>
|
||||
isMuted || volume <= 0.001
|
||||
? Material.Icons.MaterialIconKind.VolumeOff
|
||||
: volume < 0.5
|
||||
? Material.Icons.MaterialIconKind.VolumeMedium
|
||||
: Material.Icons.MaterialIconKind.VolumeHigh;
|
||||
}
|
||||
Reference in New Issue
Block a user