Reproductor Multimedia Avalonia con VisioForge¶
Esta guía le guiará a través del proceso de construcción de una aplicación de reproductor multimedia multiplataforma utilizando Avalonia UI con el patrón Modelo-Vista-Modelo de Vista (MVVM) y el SDK de Reproductor Multimedia de VisioForge. La aplicación será capaz de reproducir archivos de video en Windows, macOS, Linux, Android e iOS.
Haremos referencia al proyecto de ejemplo SimplePlayerMVVM, que demuestra los conceptos centrales y los detalles de implementación.
1. Requisitos Previos¶
Antes de comenzar, asegúrese de tener instalado lo siguiente:
- .NET SDK (última versión, ej., .NET 8 o más reciente)
- Un IDE como Visual Studio, JetBrains Rider o VS Code con extensiones de C# y Avalonia.
- Para desarrollo en Android:
- Android SDK
- Java Development Kit (JDK)
- Para desarrollo en iOS (requiere una máquina macOS):
- Xcode
- Perfiles de aprovisionamiento y certificados necesarios.
- VisioForge .NET SDK (MediaPlayer SDK X). Puede obtenerlo desde el sitio web de VisioForge. Los paquetes necesarios se agregarán a través de NuGet.
2. Configuración del Proyecto¶
Esta sección describe cómo configurar la estructura de la solución e incluir los paquetes necesarios del SDK de VisioForge.
2.1. Estructura de la Solución¶
La solución SimplePlayerMVVM consta de varios proyectos:
- SimplePlayerMVVM: Una biblioteca .NET Standard que contiene la lógica central de la aplicación, incluyendo ViewModels, Views (AXAML) e interfaces compartidas. Este es el proyecto principal donde reside la mayor parte de nuestra lógica de aplicación.
- SimplePlayerMVVM.Android: El proyecto principal específico para Android.
- SimplePlayerMVVM.Desktop: El proyecto principal específico para escritorio (Windows, macOS, Linux).
- SimplePlayerMVVM.iOS: El proyecto principal específico para iOS.
2.2. Proyecto Central (SimplePlayerMVVM.csproj)¶
El proyecto principal, SimplePlayerMVVM.csproj, apunta a múltiples plataformas. Las referencias de paquetes clave incluyen:
Avalonia: El marco de trabajo central de Avalonia UI.Avalonia.Themes.Fluent: Proporciona un tema de Diseño Fluido.Avalonia.ReactiveUI: Para soporte MVVM usando ReactiveUI.VisioForge.DotNet.MediaBlocks: Componentes centrales de procesamiento de medios de VisioForge.VisioForge.DotNet.Core.UI.Avalonia: Componentes de UI de VisioForge para Avalonia, incluyendoVideoView.
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<Nullable>enable</Nullable>
<LangVersion>latest</LangVersion>
<AvaloniaUseCompiledBindingsByDefault>true</AvaloniaUseCompiledBindingsByDefault>
</PropertyGroup>
<ItemGroup>
<AvaloniaResource Include="Assets\**" />
</ItemGroup>
<PropertyGroup Condition="$([MSBuild]::IsOsPlatform('Windows'))">
<TargetFrameworks>net8.0-android;net8.0-ios;net8.0-windows</TargetFrameworks>
</PropertyGroup>
<PropertyGroup Condition="$([MSBuild]::IsOsPlatform('OSX'))">
<TargetFrameworks>net8.0-android;net8.0-ios;net8.0-macos14.0</TargetFrameworks>
</PropertyGroup>
<PropertyGroup Condition="$([MSBuild]::IsOsPlatform('Linux'))">
<TargetFrameworks>net8.0-android;net8.0</TargetFrameworks>
</PropertyGroup>
<ItemGroup>
<AvaloniaResource Include="Assets\**" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Avalonia" Version="11.3.0" />
<PackageReference Include="Avalonia.Themes.Fluent" Version="11.3.0" />
<PackageReference Include="Avalonia.Fonts.Inter" Version="11.3.0" />
<!--La condición a continuación es necesaria para eliminar el paquete Avalonia.Diagnostics de la salida de compilación en la configuración Release.-->
<PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" Version="11.3.0" />
<PackageReference Include="Avalonia.ReactiveUI" Version="$(AvaloniaVersion)" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'net8.0-android'">
<PackageReference Include="Avalonia.Android" Version="$(AvaloniaVersion)" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="VisioForge.DotNet.MediaBlocks" Version="2025.5.1" />
<PackageReference Include="VisioForge.DotNet.Core.UI.Avalonia" Version="2025.5.1" />
</ItemGroup>
</Project>
Esta configuración permite que la lógica central se comparta entre todas las plataformas de destino.
2.3. Proyectos Específicos de Plataforma¶
Cada proyecto principal de plataforma (SimplePlayerMVVM.Android.csproj, SimplePlayerMVVM.Desktop.csproj, SimplePlayerMVVM.iOS.csproj) incluye dependencias y configuraciones específicas de la plataforma.
Escritorio (SimplePlayerMVVM.Desktop.csproj):
- Referencia
Avalonia.Desktop. - Incluye bibliotecas nativas de VisioForge específicas de la plataforma (ej.,
VisioForge.CrossPlatform.Core.Windows.x64,VisioForge.CrossPlatform.Core.macOS).
<PropertyGroup Condition="$([MSBuild]::IsOsPlatform('Windows'))">
<TargetFramework>net8.0-windows</TargetFramework>
<OutputType>WinExe</OutputType>
</PropertyGroup>
<ItemGroup Condition="$([MSBuild]::IsOsPlatform('Windows'))">
<PackageReference Include="VisioForge.CrossPlatform.Core.Windows.x64" Version="2025.4.9" />
<PackageReference Include="VisioForge.CrossPlatform.Libav.Windows.x64.UPX" Version="2025.4.9" />
</ItemGroup>
<PropertyGroup Condition="$([MSBuild]::IsOsPlatform('OSX'))">
<TargetFramework>net8.0-macos14.0</TargetFramework>
<OutputType>Exe</OutputType>
</PropertyGroup>
<ItemGroup Condition="$([MSBuild]::IsOsPlatform('OSX'))">
<PackageReference Include="VisioForge.CrossPlatform.Core.macOS" Version="2025.2.15" />
</ItemGroup>
<PropertyGroup Condition="$([MSBuild]::IsOsPlatform('Linux'))">
<TargetFramework>net8.0</TargetFramework>
<OutputType>Exe</OutputType>
</PropertyGroup>
Android (SimplePlayerMVVM.Android.csproj):
- Referencia
Avalonia.Android. - Incluye bibliotecas de VisioForge específicas de Android y dependencias como
VisioForge.CrossPlatform.Core.Android.
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0-android</TargetFramework>
<SupportedOSPlatformVersion>21</SupportedOSPlatformVersion>
<Nullable>enable</Nullable>
<ApplicationId>com.CompanyName.Simple_Player_MVVM</ApplicationId>
<ApplicationVersion>1</ApplicationVersion>
<ApplicationDisplayVersion>1.0</ApplicationDisplayVersion>
<AndroidPackageFormat>apk</AndroidPackageFormat>
<AndroidEnableProfiledAot>false</AndroidEnableProfiledAot>
</PropertyGroup>
<!-- ... otros elementos ... -->
<ItemGroup>
<ProjectReference Include="..\..\..\..\AndroidDependency\VisioForge.Core.Android.X8.csproj" />
<ProjectReference Include="..\SimplePlayerMVVM\SimplePlayerMVVM.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="VisioForge.CrossPlatform.Core.Android" Version="15.10.33" />
</ItemGroup>
</Project>
iOS (SimplePlayerMVVM.iOS.csproj):
- Referencia
Avalonia.iOS. - Incluye bibliotecas de VisioForge específicas de iOS como
VisioForge.CrossPlatform.Core.iOS.
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0-ios</TargetFramework>
<SupportedOSPlatformVersion>13.0</SupportedOSPlatformVersion>
<Nullable>enable</Nullable>
<RootNamespace>Simple_Player_MVVM.iOS</RootNamespace>
<ApplicationId>com.visioforge.avaloniaplayer</ApplicationId>
</PropertyGroup>
<!-- ... otros elementos ... -->
<ItemGroup>
<PackageReference Include="Avalonia.iOS" Version="$(AvaloniaVersion)" />
<PackageReference Include="VisioForge.CrossPlatform.Core.iOS" Version="2025.0.16" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\SimplePlayerMVVM\SimplePlayerMVVM.csproj" />
</ItemGroup>
</Project>
Estos archivos de proyecto son cruciales para gestionar dependencias y configuraciones de compilación para cada plataforma.
3. Estructura MVVM Central¶
La aplicación sigue el patrón MVVM, separando la UI (Vistas) de la lógica (Modelos de Vista) y los datos (Modelos). Se utiliza ReactiveUI para facilitar este patrón.
3.1. ViewModelBase.cs¶
Esta clase abstracta sirve como base para todos los ViewModels en la aplicación. Hereda de ReactiveObject, que es parte de ReactiveUI y proporciona la infraestructura necesaria para notificaciones de cambio de propiedad.
using ReactiveUI;
namespace Simple_Player_MVVM.ViewModels
{
public abstract class ViewModelBase : ReactiveObject
{
}
}
Cualquier ViewModel que necesite notificar a la UI sobre cambios de propiedad debe heredar de ViewModelBase.
3.2. ViewLocator.cs¶
La clase ViewLocator es responsable de localizar e instanciar Vistas basadas en el tipo de su ViewModel correspondiente. Implementa la interfaz IDataTemplate de Avalonia.
using Avalonia.Controls;
using Avalonia.Controls.Templates;
using Simple_Player_MVVM.ViewModels;
using System;
namespace Simple_Player_MVVM
{
public class ViewLocator : IDataTemplate
{
public Control? Build(object? data)
{
if (data is null)
return null;
var name = data.GetType().FullName!.Replace("ViewModel", "View", StringComparison.Ordinal);
var type = Type.GetType(name);
if (type != null)
{
return (Control)Activator.CreateInstance(type)!;
}
return new TextBlock { Text = "Not Found: " + name };
}
public bool Match(object? data)
{
return data is ViewModelBase;
}
}
}
Cuando Avalonia necesita mostrar un ViewModel, el método Match de ViewLocator verifica si el objeto de datos es un ViewModelBase. Si lo es, el método Build intenta encontrar una Vista correspondiente reemplazando "ViewModel" con "View" en el nombre de la clase del ViewModel y la instancia.
Este enfoque basado en convenciones simplifica la asociación entre Vistas y ViewModels.
3.3. Inicialización de la Aplicación (App.axaml y App.axaml.cs)¶
El archivo App.axaml define los recursos a nivel de aplicación, incluyendo el ViewLocator como una plantilla de datos y el tema (FluentTheme).
App.axaml:
<Application xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:Simple_Player_MVVM"
x:Class="Simple_Player_MVVM.App"
RequestedThemeVariant="Default">
<Application.DataTemplates>
<local:ViewLocator/>
</Application.DataTemplates>
<Application.Styles>
<FluentTheme />
</Application.Styles>
</Application>
App.axaml.cs:
El archivo App.axaml.cs maneja la inicialización y el ciclo de vida de la aplicación.
Responsabilidades clave en OnFrameworkInitializationCompleted:
- Crea una instancia de
MainViewModel. - Configura la ventana o vista principal basada en el tiempo de vida de la aplicación (
IClassicDesktopStyleApplicationLifetimepara escritorio,ISingleViewApplicationLifetimepara vistas móviles/web). - Asigna la instancia de
MainViewModelcomo elDataContextpara la ventana/vista principal. - Recupera la instancia de
IVideoViewdesdeMainView(alojada dentro deMainWindowo directamente comoMainView). - Pasa el
IVideoViewy el controlTopLevel(necesario para diálogos de archivo y otras interacciones de nivel superior) alMainViewModel.
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Markup.Xaml;
using Simple_Player_MVVM.ViewModels;
using Simple_Player_MVVM.Views;
using VisioForge.Core.Types;
namespace Simple_Player_MVVM
{
public partial class App : Application
{
public override void Initialize()
{
AvaloniaXamlLoader.Load(this);
}
public override void OnFrameworkInitializationCompleted()
{
IVideoView videoView = null;
var model = new MainViewModel();
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
{
desktop.MainWindow = new MainWindow
{
DataContext = model
};
videoView = (desktop.MainWindow as MainWindow).GetVideoView();
model.VideoViewIntf = videoView;
model.TopLevel = desktop.MainWindow;
}
else if (ApplicationLifetime is ISingleViewApplicationLifetime singleViewPlatform)
{
singleViewPlatform.MainView = new MainView
{
DataContext = model
};
videoView = (singleViewPlatform.MainView as MainView).GetVideoView();
model.VideoViewIntf = videoView;
model.TopLevel = TopLevel.GetTopLevel(singleViewPlatform.MainView);
}
base.OnFrameworkInitializationCompleted();
}
}
}
Esta configuración asegura que el MainViewModel tenga acceso a los componentes de UI necesarios para la reproducción de video e interacción, independientemente de la plataforma.
4. Implementación de MainViewModel (MainViewModel.cs)¶
El MainViewModel es central para la funcionalidad del reproductor multimedia. Gestiona el estado del reproductor, maneja las interacciones del usuario y se comunica con el motor MediaPlayerCoreX de VisioForge.
Componentes clave de MainViewModel:
4.1. Propiedades para Enlace de UI¶
El ViewModel expone varias propiedades que están enlazadas a elementos de UI en MainView.axaml. Estas propiedades usan RaiseAndSetIfChanged de ReactiveUI para notificar a la UI de los cambios.
VideoViewIntf(IVideoView): Una referencia al controlVideoViewen la UI, pasada desdeApp.axaml.cs.TopLevel(TopLevel): Una referencia al control de nivel superior, usado para mostrar diálogos de archivo.Position(string?): Posición de reproducción actual (ej., "00:01:23").Duration(string?): Duración total del archivo multimedia (ej., "00:05:00").Filename(string? o Foundation.NSUrl? para iOS): El nombre o ruta del archivo cargado actualmente.VolumeValue(double?): Nivel de volumen actual (0-100).PlayPauseText(string?): Texto para el botón Reproducir/Pausar (ej., "PLAY" o "PAUSE").SpeedText(string?): Texto indicando la velocidad de reproducción actual (ej., "SPEED: 1X").SeekingValue(double?): Valor actual del control deslizante de búsqueda.SeekingMaximum(double?): Valor máximo del control deslizante de búsqueda (corresponde a la duración del medio en milisegundos).
// Ejemplo de propiedad
private string? _Position = "00:00:00";
public string? Position
{
get => _Position;
set => this.RaiseAndSetIfChanged(ref _Position, value);
}
// ... otras propiedades ...
4.2. Comandos para Interacciones de UI¶
Se utilizan instancias de ReactiveCommand de ReactiveUI para manejar acciones activadas por elementos de UI (ej., clics de botón, cambios de valor de control deslizante).
OpenFileCommand: Abre un diálogo de archivo para seleccionar un archivo multimedia.PlayPauseCommand: Reproduce o pausa el medio.StopCommand: Detiene la reproducción.SpeedCommand: Cicla a través de velocidades de reproducción (1x, 2x, 0.5x).VolumeValueChangedCommand: Actualiza el volumen del reproductor cuando cambia el control deslizante de volumen.SeekingValueChangedCommand: Busca una nueva posición cuando cambia el control deslizante de búsqueda.WindowClosingCommand: Maneja la limpieza cuando la ventana de la aplicación se está cerrando.
// Constructor - Inicialización de comandos
public MainViewModel()
{
OpenFileCommand = ReactiveCommand.Create(OpenFileAsync);
PlayPauseCommand = ReactiveCommand.CreateFromTask(PlayPauseAsync);
StopCommand = ReactiveCommand.CreateFromTask(StopAsync);
// ... otras inicializaciones de comandos ...
// Suscribirse a cambios de propiedad para activar comandos para controles deslizantes
this.WhenAnyValue(x => x.VolumeValue).Subscribe(_ => VolumeValueChangedCommand.Execute().Subscribe());
this.WhenAnyValue(x => x.SeekingValue).Subscribe(_ => SeekingValueChangedCommand.Execute().Subscribe());
_tmPosition = new System.Timers.Timer(1000); // Temporizador para actualizaciones de posición
_tmPosition.Elapsed += tmPosition_Elapsed;
VisioForgeX.InitSDK(); // Inicializar SDK de VisioForge
}
Nota: VisioForgeX.InitSDK() inicializa el SDK de VisioForge. Esto debe llamarse una vez al inicio de la aplicación.
4.3. Integración de MediaPlayerCoreX de VisioForge¶
Un campo privado _player de tipo MediaPlayerCoreX contiene la instancia del motor del reproductor multimedia de VisioForge.
4.4. Creación del Motor (CreateEngineAsync)¶
Este método asíncrono inicializa o reinicializa la instancia de MediaPlayerCoreX.
private async Task CreateEngineAsync()
{
if (_player != null)
{
await _player.StopAsync();
await _player.DisposeAsync();
}
_player = new MediaPlayerCoreX(VideoViewIntf); // Pasar el VideoView de Avalonia
_player.OnError += _player_OnError; // Suscribirse a eventos de error
_player.Audio_Play = true; // Asegurar que el audio esté habilitado
// Crear configuraciones de fuente desde el nombre de archivo
var sourceSettings = await UniversalSourceSettings.CreateAsync(Filename);
await _player.OpenAsync(sourceSettings);
}
Pasos clave:
- Desecha cualquier instancia de reproductor existente.
- Crea un nuevo
MediaPlayerCoreX, pasando elIVideoViewdesde la UI. - Se suscribe al evento
OnErrorpara manejo de errores. - Establece
Audio_Play = truepara habilitar la reproducción de audio por defecto. - Usa
UniversalSourceSettings.CreateAsync(Filename)para crear configuraciones de fuente apropiadas para el archivo seleccionado. - Abre la fuente de medios usando
_player.OpenAsync(sourceSettings).
4.5. Apertura de Archivo (OpenFileAsync)¶
Este método es responsable de permitir al usuario seleccionar un archivo multimedia.
private async Task OpenFileAsync()
{
await StopAllAsync(); // Detener cualquier reproducción actual
PlayPauseText = "PLAY";
#if __IOS__ && !__MACCATALYST__
// Específico de iOS: Usar IDocumentPickerService
var filePicker = Locator.Current.GetService<IDocumentPickerService>();
var res = await filePicker.PickVideoAsync();
if (res != null)
{
Filename = (Foundation.NSUrl)res;
var access = IOSHelper.CheckFileAccess(Filename); // Ayudante para verificar acceso a archivo
if (!access)
{
IOSHelper.ShowToast("Error de acceso a archivo");
return;
}
}
#else
// Otras plataformas: Usar StorageProvider de Avalonia
try
{
var files = await TopLevel.StorageProvider.OpenFilePickerAsync(new FilePickerOpenOptions
{
Title = "Abrir archivo de video",
AllowMultiple = false
});
if (files.Count >= 1)
{
var file = files[0];
Filename = file.Path.AbsoluteUri;
#if __ANDROID__
// Específico de Android: Convertir URI de contenido a ruta de archivo si es necesario
if (!Filename.StartsWith('/'))
{
Filename = global::VisioForge.Core.UI.Android.FileDialogHelper.GetFilePathFromUri(AndroidHelper.GetContext(), file.Path);
}
#endif
}
}
catch (Exception ex)
{
// Manejar cancelación o errores
Debug.WriteLine($"Error al abrir archivo: {ex.Message}");
}
#endif
}
Consideraciones específicas de plataforma:
- iOS: Usa un
IDocumentPickerService(resuelto víaLocator.Current.GetService) para presentar el selector de documentos nativo.IOSHelper.CheckFileAccessse usa para asegurar que la aplicación tenga permiso para acceder al archivo seleccionado. El nombre de archivo se almacena como unNSUrl. - Android: Si la ruta obtenida del selector de archivos es un URI de contenido, se usa
FileDialogHelper.GetFilePathFromUri(deVisioForge.Core.UI.Android) para convertirlo a una ruta de archivo real. Esto requiere unIAndroidHelperpara obtener el contexto de Android. - Escritorio/Otros: Usa
TopLevel.StorageProvider.OpenFilePickerAsyncpara el diálogo de archivo estándar de Avalonia.
4.6. Controles de Reproducción¶
PlayPauseAsync:- Si el reproductor no está inicializado o detenido (
PlaybackState.Free), llama aCreateEngineAsyncy luego a_player.PlayAsync(). - Si está reproduciendo (
PlaybackState.Play), llama a_player.PauseAsync(). - Si está pausado (
PlaybackState.Pause), llama a_player.ResumeAsync(). -
Actualiza
PlayPauseTexten consecuencia e inicia/detiene el temporizador_tmPosition.private async Task PlayPauseAsync() { // ... (verificación de nombre de archivo nulo/vacío) ... if (_player == null || _player.State == PlaybackState.Free) { await CreateEngineAsync(); await _player.PlayAsync(); _tmPosition.Start(); PlayPauseText = "PAUSE"; } else if (_player.State == PlaybackState.Play) { await _player.PauseAsync(); PlayPauseText = "PLAY"; } else if (_player.State == PlaybackState.Pause) { await _player.ResumeAsync(); PlayPauseText = "PAUSE"; } } -
StopAsync: - Llama a
StopAllAsyncpara detener el reproductor y restablecer elementos de UI. -
Restablece
SpeedTextyPlayPauseText. -
StopAllAsync(Ayudante): - Detiene el temporizador
_tmPosition. - Llama a
_player.StopAsync(). -
Restablece
SeekingMaximuma nulo (para que se recalcule en la próxima reproducción).
4.7. Velocidad de Reproducción (SpeedAsync)¶
Cicla a través de tasas de reproducción: 1.0, 2.0 y 0.5.
private async Task SpeedAsync()
{
if (SpeedText == "SPEED: 1X")
{
SpeedText = "SPEED: 2X";
await _player.Rate_SetAsync(2.0);
}
else if (SpeedText == "SPEED: 2X")
{
SpeedText = "SPEED: 0.5X";
await _player.Rate_SetAsync(0.5);
}
else if (SpeedText == "SPEED: 0.5X") // Asume que este era el estado anterior
{
SpeedText = "SPEED: 1X";
await _player.Rate_SetAsync(1.0);
}
}
Usa _player.Rate_SetAsync(double rate) para cambiar la velocidad de reproducción.
4.8. Actualizaciones de Posición y Duración (tmPosition_Elapsed)¶
Este método es llamado por el temporizador _tmPosition (típicamente cada segundo) para actualizar la UI con la posición y duración de reproducción actuales.
private async void tmPosition_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
if (_player == null) return;
var pos = await _player.Position_GetAsync();
var progress = (int)pos.TotalMilliseconds;
try
{
await Dispatcher.UIThread.InvokeAsync(async () =>
{
if (_player == null) return;
_isTimerUpdate = true; // Bandera para prevenir bucle de búsqueda
if (SeekingMaximum == null)
{
SeekingMaximum = (int)(await _player.DurationAsync()).TotalMilliseconds;
}
SeekingValue = Math.Min(progress, (int)(SeekingMaximum ?? progress));
Position = $"{pos.ToString(@"hh\:mm\:ss", CultureInfo.InvariantCulture)}";
Duration = $"{(await _player.DurationAsync()).ToString(@"hh\:mm\:ss", CultureInfo.InvariantCulture)}";
_isTimerUpdate = false;
});
}
catch (Exception exception)
{
System.Diagnostics.Debug.WriteLine(exception);
}
}
Acciones clave:
- Recupera la posición actual (
_player.Position_GetAsync()) y duración (_player.DurationAsync()). - Actualiza
SeekingMaximumsi aún no se ha establecido (usualmente después de abrir un archivo). - Actualiza
SeekingValuecon el progreso actual. - Formatea y actualiza las cadenas
PositionyDuration. - Usa
Dispatcher.UIThread.InvokeAsyncpara asegurar que las actualizaciones de UI ocurran en el hilo de UI. - Establece
_isTimerUpdate = trueantes de actualizarSeekingValueyfalsedespués, para prevenir que el manejadorOnSeekingValueChangedvuelva a buscar cuando el temporizador actualiza la posición del control deslizante.
4.9. Búsqueda (OnSeekingValueChanged)¶
Llamado cuando la propiedad SeekingValue cambia (es decir, el usuario mueve el control deslizante de búsqueda).
private async Task OnSeekingValueChanged()
{
if (!_isTimerUpdate && _player != null && SeekingValue.HasValue)
{
await _player.Position_SetAsync(TimeSpan.FromMilliseconds(SeekingValue.Value));
}
}
Si no está siendo actualizado actualmente por el temporizador (!_isTimerUpdate), llama a _player.Position_SetAsync() para buscar la nueva posición.
4.10. Control de Volumen (OnVolumeValueChanged)¶
Llamado cuando la propiedad VolumeValue cambia (es decir, el usuario mueve el control deslizante de volumen).
private void OnVolumeValueChanged()
{
if (_player != null && VolumeValue.HasValue)
{
// El volumen para MediaPlayerCoreX es 0.0 a 1.0
_player.Audio_OutputDevice_Volume = VolumeValue.Value / 100.0;
}
}
Establece _player.Audio_OutputDevice_Volume. Note que el ViewModel usa una escala de 0-100 para VolumeValue, mientras que el reproductor espera 0.0-1.0.
4.11. Manejo de Errores (_player_OnError)¶
Un manejador de errores simple que registra errores en la consola de depuración.
private void _player_OnError(object sender, VisioForge.Core.Types.Events.ErrorsEventArgs e)
{
Debug.WriteLine(e.Message);
}
Aquí se podría implementar un manejo de errores más sofisticado (ej., mostrar un mensaje al usuario).
4.12. Limpieza de Recursos (OnWindowClosing)¶
Este método se invoca cuando la ventana principal se está cerrando. Asegura que los recursos del SDK de VisioForge se liberen adecuadamente.
private void OnWindowClosing()
{
if (_player != null)
{
_player.OnError -= _player_OnError; // Cancelar suscripción a eventos
_player.Stop(); // Asegurar que el reproductor esté detenido (versión síncrona aquí para limpieza rápida)
_player.Dispose();
_player = null;
}
VisioForgeX.DestroySDK(); // Destruir instancia del SDK de VisioForge
}
Detiene el reproductor, lo desecha y, lo que es más importante, llama a VisioForgeX.DestroySDK() para liberar todos los recursos del SDK. Esto es crucial para prevenir fugas de memoria o problemas cuando la aplicación sale.
Este ViewModel orquesta toda la lógica central del reproductor multimedia, desde cargar archivos hasta controlar la reproducción e interactuar con el SDK de VisioForge.
5. Interfaz de Usuario (Vistas)¶
La interfaz de usuario se define usando Avalonia XAML (archivos .axaml).
5.1. MainView.axaml - La Interfaz del Reproductor¶
Este UserControl define el diseño y los controles para el reproductor multimedia.
Elementos de UI Clave:
-
avalonia:VideoView: Este es el control de VisioForge responsable de renderizar video. Se coloca en el área principal de la cuadrícula y se establece para estirarse. -
Control Deslizante de Búsqueda (
Slider Name="slSeeking"): Maximum="{Binding SeekingMaximum}": Se enlaza a la propiedadSeekingMaximumenMainViewModel.Value="{Binding SeekingValue}": Se enlaza bidireccionalmente a la propiedadSeekingValueenMainViewModel. Los cambios a este control deslizante por el usuario actualizaránSeekingValue, activandoOnSeekingValueChanged. Las actualizaciones aSeekingValuedesde el ViewModel (ej., por el temporizador) actualizarán la posición del control deslizante.- Visualización de Tiempo (
TextBlocks para Posición y Duración): - Enlazado a las propiedades
PositionyDurationenMainViewModel. TextBlock Text="{Binding Filename}"muestra el nombre del archivo actual.- Botones de Control de Reproducción (
Buttons): - Abrir Archivo:
Command="{Binding OpenFileCommand}" - Reproducir/Pausar:
Command="{Binding PlayPauseCommand}",Content="{Binding PlayPauseText}"(cambia dinámicamente el texto del botón). - Detener:
Command="{Binding StopCommand}" - Controles de Volumen y Velocidad:
- Control Deslizante de Volumen:
Value="{Binding VolumeValue}"(se enlaza aVolumeValuepara control de volumen). - Botón de Velocidad:
Command="{Binding SpeedCommand}",Content="{Binding SpeedText}".
Diseño:
La vista usa un Grid para organizar el VideoView y un StackPanel para los controles en la parte inferior. Los controles mismos se organizan usando StackPanels anidados y Grids para alineación.
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="clr-namespace:Simple_Player_MVVM.ViewModels"
xmlns:avalonia="clr-namespace:VisioForge.Core.UI.Avalonia;assembly=VisioForge.Core.UI.Avalonia"
x:Class="Simple_Player_MVVM.Views.MainView"
x:DataType="vm:MainViewModel">
<Design.DataContext>
<vm:MainViewModel />
</Design.DataContext>
<Grid RowDefinitions="*,Auto" ColumnDefinitions="*">
<!-- Marcador de posición de Vista de Video -->
<Border Grid.Row="0" Background="Black" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<avalonia:VideoView x:Name="videoView1" Margin="0,0,0,0" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Background="#0C0C0C" />
</Border>
<!-- Controles -->
<StackPanel Grid.Row="1" Background="#1e1e1e" Orientation="Vertical">
<!-- Control deslizante para búsqueda -->
<Slider Name="slSeeking" Margin="16,16,16,0" VerticalAlignment="Center" Maximum="{Binding SeekingMaximum}" Value="{Binding SeekingValue}"/>
<!-- Visualización de tiempo y nombre de archivo -->
<Grid Margin="0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="auto"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="{Binding Position}" Foreground="White" VerticalAlignment="Center" Margin="5,0,5,0"/>
<TextBlock Grid.Column="1" Text="{Binding Filename}" Foreground="White" HorizontalAlignment="Center" />
<TextBlock Grid.Column="2" Text="{Binding Duration}" Foreground="White" VerticalAlignment="Center" Margin="5,0,5,0"/>
</Grid>
<!-- Controles de Reproducción -->
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Margin="16,0,5,0">
<Button Command="{Binding OpenFileCommand}" Content="OPEN FILE" Margin="5" VerticalAlignment="Center"/>
<Button Name="btPlayPause" Command="{Binding PlayPauseCommand}" Content="{Binding PlayPauseText}" Margin="5"/>
<Button Name="btStop" Command="{Binding StopCommand}" Content="STOP" Margin="5"/>
</StackPanel>
<!-- Controles de Volumen y Velocidad -->
<StackPanel Orientation="Horizontal" HorizontalAlignment="Left" Margin="16,0,5,5">
<TextBlock Text="Volume" Foreground="White" VerticalAlignment="Center"/>
<Slider Value="{Binding VolumeValue}" Minimum="0" Maximum="100" Width="150" Margin="15,0,5,0" VerticalAlignment="Center"/>
<Button Command="{Binding SpeedCommand}" Content="{Binding SpeedText}" Margin="5"/>
</StackPanel>
</StackPanel>
</Grid>
</UserControl>
La directiva x:DataType="vm:MainViewModel" habilita enlaces compilados, proporcionando mejor rendimiento y verificación en tiempo de compilación de rutas de enlace. Design.DataContext se usa para proporcionar datos para el previsualizador XAML en IDEs.
5.2. MainView.axaml.cs - Código Detrás¶
El código detrás para MainView es mínimo. Su propósito principal es proporcionar una forma para que el código de configuración de la aplicación (en App.axaml.cs) acceda a la instancia del control VideoView.
using Avalonia.Controls;
using VisioForge.Core.Types;
namespace Simple_Player_MVVM.Views
{
public partial class MainView : UserControl
{
// Proporciona acceso a la instancia del control VideoView
public IVideoView GetVideoView()
{
return videoView1; // videoView1 es el x:Name del VideoView en XAML
}
public MainView()
{
InitializeComponent(); // Inicialización estándar de control Avalonia
}
}
}
Este método GetVideoView() se llama durante el inicio de la aplicación para pasar la referencia de VideoView al MainViewModel.
5.3. MainWindow.axaml - La Ventana Principal de la Aplicación (Escritorio)¶
Para plataformas de escritorio, MainWindow.axaml sirve como la ventana de nivel superior que aloja el MainView.
<Window xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="using:Simple_Player_MVVM.ViewModels"
xmlns:views="clr-namespace:Simple_Player_MVVM.Views"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Simple_Player_MVVM.Views.MainWindow"
Icon="/Assets/avalonia-logo.ico"
Title="Simple_Player_MVVM">
<views:MainView />
</Window>
Simplemente incrusta el control MainView. El DataContext (que será una instancia de MainViewModel) se establece típicamente en App.axaml.cs cuando se crea MainWindow.
5.4. MainWindow.axaml.cs - Código Detrás de Ventana Principal¶
El código detrás para MainWindow maneja principalmente dos cosas:
- Proporciona una forma de obtener el
VideoViewdesde elMainViewcontenido. - Se engancha al evento
Closingde la ventana para activar elWindowClosingCommanden elMainViewModelpara limpieza de recursos.
using Avalonia.Controls;
using Simple_Player_MVVM.ViewModels;
using System;
using VisioForge.Core.Types;
namespace Simple_Player_MVVM.Views
{
public partial class MainWindow : Window
{
// Ayudante para obtener VideoView desde el contenido de MainView
public IVideoView GetVideoView()
{
return (Content as MainView).GetVideoView();
}
public MainWindow()
{
InitializeComponent();
// Manejar el evento de cierre de ventana para activar limpieza en ViewModel
Closing += async (sender, e) =>
{
if (DataContext is MainViewModel viewModel)
{
// Ejecutar el comando y manejar errores potenciales o finalización
viewModel.WindowClosingCommand.Execute()
.Subscribe(_ => { /* Opcional: acción al completar */ },
ex => Console.WriteLine($"Error durante el cierre: {ex.Message}"));
}
};
}
}
}
Cuando la ventana se cierra, verifica si el DataContext es un MainViewModel y luego ejecuta su WindowClosingCommand. Esto asegura que el MainViewModel pueda realizar la limpieza necesaria, como desechar la instancia de MediaPlayerCoreX y llamar a VisioForgeX.DestroySDK().
6. Detalles de Implementación Específicos de Plataforma¶
Aunque Avalonia y .NET proporcionan un alto grado de compatibilidad multiplataforma, ciertos aspectos como el acceso al sistema de archivos y permisos requieren manejo específico de plataforma.
6.1. Interfaces para Servicios de Plataforma¶
Para abstraer la funcionalidad específica de plataforma, se definen interfaces en el proyecto central SimplePlayerMVVM:
-
IAndroidHelper.cs:namespace SimplePlayerMVVM { public interface IAndroidHelper { #if __ANDROID__ global::Android.Content.Context GetContext(); #endif } }Esta interfaz se usa para obtener el
Contextde Android, que se necesita para operaciones como convertir URIs de contenido a rutas de archivo. -
IDocumentPickerService.cs:using System.Threading.Tasks; namespace SimplePlayerMVVM; public interface IDocumentPickerService { Task<object?> PickVideoAsync(); }Esta interfaz abstrae el mecanismo de selección de archivos, específicamente para iOS donde se prefiere un selector de documentos nativo.
6.2. Implementación Android (Proyecto SimplePlayerMVVM.Android)¶
MainActivity.cs:- Hereda de
AvaloniaMainActivity<App>e implementaIAndroidHelper. CustomizeAppBuilder: Configuración estándar de Avalonia Android.-
Permisos: En
OnCreate, llama aRequestPermissionsAsyncpara solicitar permisos necesarios comoManifest.Permission.Internet,Manifest.Permission.ReadExternalStorageyManifest.Permission.ReadMediaVideo(para versiones más nuevas de Android).```csharp // En MainActivity.cs protected override void OnCreate(Bundle savedInstanceState) { base.OnCreate(savedInstanceState); MainViewModel.AndroidHelper = this; // Proporcionar implementación IAndroidHelper a ViewModel RequestPermissionsAsync(); } private async void RequestPermissionsAsync() { RequestPermissions( new String[]{ Manifest.Permission.Internet, Manifest.Permission.ReadExternalStorage, Manifest.Permission.ReadMediaVideo}, 1004); } public Context GetContext() // Implementación IAndroidHelper { return this; } ``` -
La línea
MainViewModel.AndroidHelper = this;hace que la instancia deMainActivity(que implementaIAndroidHelper) esté disponible para elMainViewModelpara obtener el contexto de Android. -
El Manifiesto de Android (
AndroidManifest.xml, no mostrado explícitamente en los archivos proporcionados pero esencial) también debe declarar estos permisos. -
Manejo de Ruta de Archivo: Como se ve en
MainViewModel.OpenFileAsync, el métodoFileDialogHelper.GetFilePathFromUriusa el contexto obtenido víaIAndroidHelperpara resolver rutas de archivo desde URIs de contenido, lo cual es común al usar el selector de archivos de Android. -
Archivo de Proyecto (
SimplePlayerMVVM.Android.csproj): Configura la compilación específica de Android, versiones de SDK de destino e incluye bibliotecas de VisioForge Android necesarias.
6.3. Implementación iOS (Proyecto SimplePlayerMVVM.iOS)¶
AppDelegate.cs:- Hereda de
AvaloniaAppDelegate<App>. -
CustomizeAppBuilder: Registra elIOSDocumentPickerServicecon el resolutor de dependencias Splat (Locator.CurrentMutable.RegisterConstant). Esto hace que elIDocumentPickerServiceesté disponible para inyección o ubicación de servicio en elMainViewModel.```csharp // En AppDelegate.cs protected override AppBuilder CustomizeAppBuilder(AppBuilder builder) { Locator.CurrentMutable.RegisterConstant(new IOSDocumentPickerService(), typeof(IDocumentPickerService)); return base.CustomizeAppBuilder(builder) .WithInterFont() .UseReactiveUI(); } ``` -
IOSDocumentPickerService.cs: - Implementa
IDocumentPickerService. - Usa
UIDocumentPickerViewControllerpara presentar el selector de archivos nativo de iOS para archivos de video (UTType.Video,UTType.Movie). - Maneja los eventos
DidPickDocumentAtUrlsyWasCancelleddel selector. - Retorna la URL del archivo seleccionado (
NSUrl) vía unTaskCompletionSource. -
Incluye código de utilidad (
GetTopViewController) para encontrar el controlador de vista superior desde donde presentar el selector.// Fragmento de IOSDocumentPickerService.cs public Task<object?> PickVideoAsync() { _tcs = new TaskCompletionSource<object?>(); string[] allowedUTIs = { UTType.Video, UTType.Movie }; var picker = new UIDocumentPickerViewController(allowedUTIs, UIDocumentPickerMode.Import); // ... suscripciones a eventos y presentación ... return _tcs.Task; } private void OnDocumentPicked(object sender, UIDocumentPickedAtUrlsEventArgs e) { // ... maneja URL seleccionada, resuelve _tcs ... NSUrl fileUrl = e.Urls[0]; _tcs?.TrySetResult(fileUrl); } -
Info.plist: -
Este archivo es crucial para aplicaciones iOS. Debe incluir claves como
NSPhotoLibraryUsageDescriptionsi se accede a la biblioteca de fotos, u otros permisos relevantes dependiendo de dónde se almacenen/accedan los archivos. ElInfo.plistproporcionado incluye:```xml <key>NSPhotoLibraryUsageDescription</key> <string>Biblioteca de fotos usada para reproducir archivos</string> ``` -
También define identificadores de paquete, números de versión, orientaciones soportadas, etc.
-
Archivo de Proyecto (
SimplePlayerMVVM.iOS.csproj): Configura la compilación específica de iOS, versión de SO de destino e incluye bibliotecas de VisioForge iOS.
6.4. Implementación de Escritorio (Proyecto SimplePlayerMVVM.Desktop)¶
Program.cs:- Contiene el punto de entrada
Mainpara aplicaciones de escritorio. - Usa
BuildAvaloniaApp().StartWithClassicDesktopLifetime(args);para inicializar y ejecutar la aplicación Avalonia para escritorio. -
BuildAvaloniaApp()configura Avalonia con detección de plataforma, fuentes, ReactiveUI y registro. -
Acceso a Archivos: En escritorio, el acceso a archivos es generalmente más directo. El
MainViewModelusaTopLevel.StorageProvider.OpenFilePickerAsyncque funciona a través de Windows, macOS y Linux sin complejidades específicas de servicios de ayuda como las de URI/permisos de Android o iOS. -
Archivo de Proyecto (
SimplePlayerMVVM.Desktop.csproj): - Apunta a marcos de trabajo de escritorio específicos (ej.,
net8.0-windows,net8.0-macos14.0,net8.0para Linux). - Incluye
Avalonia.Desktop. -
Incluye bibliotecas nativas de VisioForge específicas de plataforma para Windows (x64) y macOS a través de condiciones
PackageReference.```xml <ItemGroup Condition="$([MSBuild]::IsOsPlatform('Windows'))"> <PackageReference Include="VisioForge.CrossPlatform.Core.Windows.x64" Version="..." /> <PackageReference Include="VisioForge.CrossPlatform.Libav.Windows.x64.UPX" Version="..." /> </ItemGroup> <ItemGroup Condition="$([MSBuild]::IsOsPlatform('OSX'))"> <PackageReference Include="VisioForge.CrossPlatform.Core.macOS" Version="..." /> </ItemGroup> ``` -
Manifiesto de Windows (
app.manifest): Usado en Windows para configuraciones de aplicación, como compatibilidad con versiones específicas de Windows. - Info de macOS (
Info.plisten proyecto Desktop): Proporciona información de paquete para aplicaciones macOS.
Estos proyectos y configuraciones específicos de plataforma aseguran que la lógica central compartida en SimplePlayerMVVM pueda interactuar correctamente con las características nativas y requisitos de cada sistema operativo.
7. Componentes Clave del SDK de VisioForge Usados¶
Esta aplicación aprovecha varios componentes clave del SDK de Reproductor Multimedia X de VisioForge:
VisioForge.Core.MediaPlayerX.MediaPlayerCoreX:- El motor central para reproducción de medios. Maneja la apertura de fuentes de medios, control de reproducción (reproducir, pausar, detener, buscar, tasa), gestión de audio y video, y proporciona información de estado (posición, duración).
- Inicializado en
MainViewModely vinculado alVideoViewIntf. VisioForge.Core.UI.Avalonia.VideoView:- Un control de Avalonia que sirve como la superficie de renderizado para video. Se declara en
MainView.axamly su referencia (IVideoView) se pasa aMediaPlayerCoreX. VisioForge.Core.Types.X.Sources.UniversalSourceSettings:- Usado para configurar la fuente de medios.
UniversalSourceSettings.CreateAsync(filename)determina automáticamente la mejor manera de abrir un archivo o URL dado. - Clase estática
VisioForge.Core.VisioForgeX: InitSDK(): Inicializa el SDK de VisioForge. Esto debe llamarse una vez al inicio de la aplicación (hecho en el constructor deMainViewModelen este ejemplo, pero también puede hacerse enApp.axaml.cs).DestroySDK(): Libera todos los recursos del SDK. Esto debe llamarse cuando la aplicación se está cerrando para prevenir fugas de recursos (hecho enMainViewModel.OnWindowClosing).- Bibliotecas Específicas de Plataforma:
- Como se detalla en la configuración del proyecto y secciones específicas de plataforma, varios paquetes NuGet como
VisioForge.CrossPlatform.Core.Windows.x64,VisioForge.CrossPlatform.Core.macOS,VisioForge.CrossPlatform.Core.AndroidyVisioForge.CrossPlatform.Core.iOSproporcionan los binarios nativos y enlaces necesarios para cada plataforma. VisioForge.Core.UI.Android.FileDialogHelper(para Android):- Contiene métodos de ayuda como
GetFilePathFromUripara trabajar con el sistema de archivos de Android y URIs de contenido.
Entender estos componentes es crucial para trabajar con el SDK de VisioForge y extender la funcionalidad del reproductor.
8. Construyendo y Ejecutando la Aplicación¶
- Clonar/Descargar el Código Fuente: Obtenga el proyecto de ejemplo
SimplePlayerMVVM. - Restaurar Paquetes NuGet: Abra la solución en su IDE y asegúrese de que todos los paquetes NuGet estén restaurados para todos los proyectos.
- Seleccionar Proyecto de Inicio y Destino:
- Escritorio: Establezca
SimplePlayerMVVM.Desktopcomo el proyecto de inicio. Luego puede ejecutarlo directamente en Windows, macOS o Linux (asegúrese de tener el tiempo de ejecución .NET para su SO). - Android: Establezca
SimplePlayerMVVM.Androidcomo el proyecto de inicio. Seleccione un emulador de Android o dispositivo conectado. Compile e implemente.- Asegúrese de que el SDK de Android y los emuladores/dispositivos estén configurados correctamente en su IDE.
- Es posible que necesite aceptar solicitudes de permiso en el dispositivo/emulador al primer lanzamiento.
- iOS: Establezca
SimplePlayerMVVM.iOScomo el proyecto de inicio. Seleccione un simulador de iOS o dispositivo conectado (requiere una máquina de compilación macOS y aprovisionamiento de Desarrollador de Apple apropiado).- Asegúrese de que Xcode y las herramientas de desarrollador estén configurados correctamente.
- Es posible que necesite confiar en el certificado de desarrollador en el dispositivo.
- Escritorio: Establezca
- Compilar y Ejecutar: Compile el proyecto de inicio seleccionado y ejecútelo.
9. Conclusión¶
Esta guía ha demostrado cómo construir un reproductor multimedia multiplataforma usando Avalonia UI con el patrón MVVM y el SDK de Reproductor Multimedia X de VisioForge. Al aprovechar un proyecto central compartido para ViewModels y Vistas, y manejar los aspectos específicos de plataforma en proyectos principales dedicados, podemos crear una aplicación mantenible que se ejecuta en una amplia gama de dispositivos.
Puntos clave:
- El patrón MVVM ayuda a separar preocupaciones y mejora la capacidad de prueba.
- ReactiveUI simplifica la implementación de MVVM con Avalonia.
- El SDK de Reproductor Multimedia X de VisioForge proporciona potentes capacidades de reproducción de medios, con
MediaPlayerCoreXcomo el motor central yVideoViewpara integración con Avalonia UI. - Las consideraciones específicas de plataforma, especialmente para acceso a archivos y permisos, se manejan a través de interfaces e implementaciones específicas de plataforma.
- La inicialización adecuada (
VisioForgeX.InitSDK()) y limpieza (VisioForgeX.DestroySDK()) del SDK de VisioForge son esenciales.
Puede extender este ejemplo agregando más características como soporte de listas de reproducción, transmisión en red, efectos de video o controles de UI más avanzados.