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("ChannelNameText"); _loadingOverlay = this.FindControl("LoadingOverlay"); _errorOverlay = this.FindControl("ErrorOverlay"); _statusText = this.FindControl("StatusText"); _videoView = this.FindControl("VideoView"); _errorText = this.FindControl("ErrorText"); _overlay = this.FindControl("Overlay"); } }