230 lines
6.8 KiB
C#
230 lines
6.8 KiB
C#
using System;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using Avalonia.Controls;
|
|
using Avalonia.Input;
|
|
using Avalonia.Markup.Xaml;
|
|
using Avalonia.Threading;
|
|
using LibVLCSharp.Avalonia;
|
|
using LibVLCSharp.Shared;
|
|
using StreamPlayer.Desktop.Models;
|
|
using StreamPlayer.Desktop.Services;
|
|
|
|
namespace StreamPlayer.Desktop.Views;
|
|
|
|
public partial class PlayerWindow : Window
|
|
{
|
|
private readonly StreamChannel _channel;
|
|
private readonly StreamUrlResolver _resolver = new();
|
|
private readonly CancellationTokenSource _lifetimeCts = new();
|
|
private LibVLC? _libVlc;
|
|
private MediaPlayer? _mediaPlayer;
|
|
private bool _overlayVisible = true;
|
|
|
|
private TextBlock? _channelNameText;
|
|
private Border? _loadingOverlay;
|
|
private Border? _errorOverlay;
|
|
private TextBlock? _statusText;
|
|
private VideoView? _videoView;
|
|
private TextBlock? _errorText;
|
|
private Border? _overlay;
|
|
|
|
public PlayerWindow() : this(new StreamChannel("Canal", string.Empty))
|
|
{
|
|
}
|
|
|
|
public PlayerWindow(StreamChannel channel)
|
|
{
|
|
_channel = channel;
|
|
InitializeComponent();
|
|
if (_channelNameText != null)
|
|
{
|
|
_channelNameText.Text = channel.Name;
|
|
}
|
|
Title = $"Reproduciendo {channel.Name}";
|
|
}
|
|
|
|
protected override void OnOpened(EventArgs e)
|
|
{
|
|
base.OnOpened(e);
|
|
_ = LoadAndPlayAsync();
|
|
}
|
|
|
|
private async Task LoadAndPlayAsync()
|
|
{
|
|
try
|
|
{
|
|
await Dispatcher.UIThread.InvokeAsync(() =>
|
|
{
|
|
if (_loadingOverlay != null)
|
|
{
|
|
_loadingOverlay.IsVisible = true;
|
|
}
|
|
if (_errorOverlay != null)
|
|
{
|
|
_errorOverlay.IsVisible = false;
|
|
}
|
|
if (_statusText != null)
|
|
{
|
|
_statusText.Text = "Resolviendo stream…";
|
|
}
|
|
});
|
|
|
|
string resolved = await _resolver.ResolveAsync(_channel.PageUrl, _lifetimeCts.Token);
|
|
await Dispatcher.UIThread.InvokeAsync(() => StartPlayback(resolved));
|
|
}
|
|
catch (OperationCanceledException)
|
|
{
|
|
// ignored
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
await Dispatcher.UIThread.InvokeAsync(() => ShowError($"No se pudo iniciar el stream: {ex.Message}"));
|
|
}
|
|
}
|
|
|
|
private void StartPlayback(string streamUrl)
|
|
{
|
|
ReleasePlayer();
|
|
_libVlc = new LibVLC();
|
|
_mediaPlayer = new MediaPlayer(_libVlc);
|
|
_mediaPlayer.Playing += MediaPlayerOnPlaying;
|
|
_mediaPlayer.Buffering += MediaPlayerOnBuffering;
|
|
_mediaPlayer.EncounteredError += MediaPlayerOnEncounteredError;
|
|
_mediaPlayer.EndReached += MediaPlayerOnEndReached;
|
|
if (_videoView != null)
|
|
{
|
|
_videoView.MediaPlayer = _mediaPlayer;
|
|
}
|
|
|
|
using var media = new Media(_libVlc, new Uri(streamUrl));
|
|
media.AddOption($":http-referrer={_channel.PageUrl}");
|
|
media.AddOption(":http-header=Origin: https://streamtpmedia.com");
|
|
media.AddOption(":http-header=Accept: */*");
|
|
media.AddOption(":http-header=Connection: keep-alive");
|
|
media.AddOption(":network-caching=1500");
|
|
media.AddOption(":clock-jitter=0");
|
|
media.AddOption(":clock-synchro=0");
|
|
media.AddOption(":adaptive-use-access=true");
|
|
media.AddOption(":http-user-agent=Mozilla/5.0 (Linux; Android 13; Pixel 7 Build/TQ3A.230805.001) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0 Mobile Safari/537.36 ExoPlayer/2.18.7");
|
|
media.AddOption(":input-repeat=-1");
|
|
_mediaPlayer.Play(media);
|
|
}
|
|
|
|
private void MediaPlayerOnEndReached(object? sender, EventArgs e)
|
|
{
|
|
Dispatcher.UIThread.Post(() =>
|
|
{
|
|
if (_loadingOverlay != null)
|
|
{
|
|
_loadingOverlay.IsVisible = false;
|
|
}
|
|
});
|
|
}
|
|
|
|
private void MediaPlayerOnEncounteredError(object? sender, EventArgs e)
|
|
{
|
|
Dispatcher.UIThread.Post(() => ShowError("Error en la reproducción. Intenta nuevamente más tarde."));
|
|
}
|
|
|
|
private void MediaPlayerOnBuffering(object? sender, MediaPlayerBufferingEventArgs e)
|
|
{
|
|
Dispatcher.UIThread.Post(() =>
|
|
{
|
|
if (_loadingOverlay != null)
|
|
{
|
|
_loadingOverlay.IsVisible = true;
|
|
}
|
|
if (_statusText != null)
|
|
{
|
|
_statusText.Text = "Conectando…";
|
|
}
|
|
});
|
|
}
|
|
|
|
private void MediaPlayerOnPlaying(object? sender, EventArgs e)
|
|
{
|
|
Dispatcher.UIThread.Post(() =>
|
|
{
|
|
if (_loadingOverlay != null)
|
|
{
|
|
_loadingOverlay.IsVisible = false;
|
|
}
|
|
SetOverlayVisible(false);
|
|
});
|
|
}
|
|
|
|
private void ShowError(string message)
|
|
{
|
|
if (_loadingOverlay != null)
|
|
{
|
|
_loadingOverlay.IsVisible = false;
|
|
}
|
|
if (_errorOverlay != null)
|
|
{
|
|
_errorOverlay.IsVisible = true;
|
|
}
|
|
if (_errorText != null)
|
|
{
|
|
_errorText.Text = message;
|
|
}
|
|
}
|
|
|
|
private void ReleasePlayer()
|
|
{
|
|
if (_mediaPlayer != null)
|
|
{
|
|
_mediaPlayer.Playing -= MediaPlayerOnPlaying;
|
|
_mediaPlayer.Buffering -= MediaPlayerOnBuffering;
|
|
_mediaPlayer.EncounteredError -= MediaPlayerOnEncounteredError;
|
|
_mediaPlayer.EndReached -= MediaPlayerOnEndReached;
|
|
_mediaPlayer.Dispose();
|
|
_mediaPlayer = null;
|
|
}
|
|
if (_libVlc != null)
|
|
{
|
|
_libVlc.Dispose();
|
|
_libVlc = null;
|
|
}
|
|
}
|
|
|
|
private void OnVideoPointerPressed(object? sender, PointerPressedEventArgs e)
|
|
{
|
|
SetOverlayVisible(!_overlayVisible);
|
|
}
|
|
|
|
private void SetOverlayVisible(bool visible)
|
|
{
|
|
_overlayVisible = visible;
|
|
if (_overlay != null)
|
|
{
|
|
_overlay.IsVisible = visible;
|
|
}
|
|
}
|
|
|
|
private void OnCloseClicked(object? sender, Avalonia.Interactivity.RoutedEventArgs e)
|
|
{
|
|
Close();
|
|
}
|
|
|
|
protected override void OnClosing(WindowClosingEventArgs e)
|
|
{
|
|
base.OnClosing(e);
|
|
_lifetimeCts.Cancel();
|
|
ReleasePlayer();
|
|
_lifetimeCts.Dispose();
|
|
}
|
|
|
|
private void InitializeComponent()
|
|
{
|
|
AvaloniaXamlLoader.Load(this);
|
|
_channelNameText = this.FindControl<TextBlock>("ChannelNameText");
|
|
_loadingOverlay = this.FindControl<Border>("LoadingOverlay");
|
|
_errorOverlay = this.FindControl<Border>("ErrorOverlay");
|
|
_statusText = this.FindControl<TextBlock>("StatusText");
|
|
_videoView = this.FindControl<VideoView>("VideoView");
|
|
_errorText = this.FindControl<TextBlock>("ErrorText");
|
|
_overlay = this.FindControl<Border>("Overlay");
|
|
}
|
|
}
|