diff --git a/samples/LibVLCSharp.Android.Sample/Resources/Resource.Designer.cs b/samples/LibVLCSharp.Android.Sample/Resources/Resource.Designer.cs index a1dca9f4f..26deb00a7 100644 --- a/samples/LibVLCSharp.Android.Sample/Resources/Resource.Designer.cs +++ b/samples/LibVLCSharp.Android.Sample/Resources/Resource.Designer.cs @@ -14,7 +14,7 @@ namespace LibVLCSharp.Android.Sample { - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Xamarin.Android.Build.Tasks", "12.2.0.155")] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Xamarin.Android.Build.Tasks", "13.2.2.120")] public partial class Resource { diff --git a/samples/MAUI/LibVLCSharp.MAUI.Sample.MediaElement/App.xaml b/samples/MAUI/LibVLCSharp.MAUI.Sample.MediaElement/App.xaml new file mode 100644 index 000000000..50ca86b67 --- /dev/null +++ b/samples/MAUI/LibVLCSharp.MAUI.Sample.MediaElement/App.xaml @@ -0,0 +1,9 @@ + + + + + + diff --git a/samples/MAUI/LibVLCSharp.MAUI.Sample.MediaElement/App.xaml.cs b/samples/MAUI/LibVLCSharp.MAUI.Sample.MediaElement/App.xaml.cs new file mode 100644 index 000000000..88286d909 --- /dev/null +++ b/samples/MAUI/LibVLCSharp.MAUI.Sample.MediaElement/App.xaml.cs @@ -0,0 +1,51 @@ +using System; +using System.Threading.Tasks; +using Microsoft.Maui.Controls; + +namespace LibVLCSharp.MAUI.Sample.MediaElement +{ + /// + /// Represents the main App. + /// + public partial class App : Application + { + /// + /// Initializes a new instance of class. + /// + public App() + { + InitializeComponent(); + ConfigureUnhandledExceptionHandling(); + + MainPage = new MainPage(); + } + + private static void ConfigureUnhandledExceptionHandling() + { + AppDomain.CurrentDomain.UnhandledException += (sender, e) => + { + if (e.ExceptionObject is Exception ex) + { + HandleException(ex); + } + }; + + TaskScheduler.UnobservedTaskException += (sender, e) => + { + if (e.Exception is Exception ex) + { + HandleException(ex); + } + e.SetObserved(); // Prevents the process from terminating. + }; + } + + private static void HandleException(Exception ex) + { + if (ex != null) + { + Console.WriteLine(ex.Message); + } + } + } +} diff --git a/samples/MAUI/LibVLCSharp.MAUI.Sample.MediaElement/AppShell.xaml b/samples/MAUI/LibVLCSharp.MAUI.Sample.MediaElement/AppShell.xaml new file mode 100644 index 000000000..6885a1c3a --- /dev/null +++ b/samples/MAUI/LibVLCSharp.MAUI.Sample.MediaElement/AppShell.xaml @@ -0,0 +1,14 @@ + + + + + + diff --git a/samples/MAUI/LibVLCSharp.MAUI.Sample.MediaElement/AppShell.xaml.cs b/samples/MAUI/LibVLCSharp.MAUI.Sample.MediaElement/AppShell.xaml.cs new file mode 100644 index 000000000..7d3493c6f --- /dev/null +++ b/samples/MAUI/LibVLCSharp.MAUI.Sample.MediaElement/AppShell.xaml.cs @@ -0,0 +1,16 @@ +namespace LibVLCSharp.MAUI.Sample.MediaElement +{ + /// + /// Represents the main shell of the application. + /// + public partial class AppShell : Shell + { + /// + /// Initializes a new instance of the class. + /// + public AppShell() + { + InitializeComponent(); + } + } +} diff --git a/samples/MAUI/LibVLCSharp.MAUI.Sample.MediaElement/LibVLCSharp.MAUI.Sample.MediaElement.csproj b/samples/MAUI/LibVLCSharp.MAUI.Sample.MediaElement/LibVLCSharp.MAUI.Sample.MediaElement.csproj new file mode 100644 index 000000000..b4b416ddf --- /dev/null +++ b/samples/MAUI/LibVLCSharp.MAUI.Sample.MediaElement/LibVLCSharp.MAUI.Sample.MediaElement.csproj @@ -0,0 +1,61 @@ + + + net8.0-android;net8.0-ios + + + Exe + LibVLCSharp.MAUI.Sample.MediaElement + true + true + enable + latest + + LibVLCSharp.MAUI.Sample.MediaElement + + com.companyname.libvlcsharp.maui.sample.mediaelement + 3e92aff6-59b5-48cd-a2a0-3ecb5d63d8fb + + 1.0 + 1 + 11.0 + 13.1 + 21.0 + 10.0.17763.0 + 6.5 + True + 10.0.19041.0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/samples/MAUI/LibVLCSharp.MAUI.Sample.MediaElement/MainPage.xaml b/samples/MAUI/LibVLCSharp.MAUI.Sample.MediaElement/MainPage.xaml new file mode 100644 index 000000000..8477da926 --- /dev/null +++ b/samples/MAUI/LibVLCSharp.MAUI.Sample.MediaElement/MainPage.xaml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + diff --git a/samples/MAUI/LibVLCSharp.MAUI.Sample.MediaElement/MainPage.xaml.cs b/samples/MAUI/LibVLCSharp.MAUI.Sample.MediaElement/MainPage.xaml.cs new file mode 100644 index 000000000..bccf31a82 --- /dev/null +++ b/samples/MAUI/LibVLCSharp.MAUI.Sample.MediaElement/MainPage.xaml.cs @@ -0,0 +1,30 @@ +using Microsoft.Maui.Controls; + +namespace LibVLCSharp.MAUI.Sample.MediaElement +{ + /// + /// Represnets the Main Page. + /// + public partial class MainPage : ContentPage + { + /// + /// Initializes a new instance of class. + /// + public MainPage() + { + InitializeComponent(); + } + + void OnAppearing(object sender, System.EventArgs e) + { + base.OnAppearing(); + ((MainViewModel)BindingContext).OnAppearing(); + } + + void OnDisappearing(object sender, System.EventArgs e) + { + base.OnDisappearing(); + ((MainViewModel)BindingContext).OnDisappearing(); + } + } +} diff --git a/samples/MAUI/LibVLCSharp.MAUI.Sample.MediaElement/MainViewModel.cs b/samples/MAUI/LibVLCSharp.MAUI.Sample.MediaElement/MainViewModel.cs new file mode 100644 index 000000000..6d8304e67 --- /dev/null +++ b/samples/MAUI/LibVLCSharp.MAUI.Sample.MediaElement/MainViewModel.cs @@ -0,0 +1,92 @@ +using System; +using System.ComponentModel; +using System.Runtime.CompilerServices; +using LibVLCSharp.Shared; + +namespace LibVLCSharp.MAUI.Sample.MediaElement +{ + /// + /// Represents the main viewmodel. + /// + public class MainViewModel : INotifyPropertyChanged + { + /// + /// Property changed event + /// + public event PropertyChangedEventHandler PropertyChanged = null!; + + /// + /// Initializes a new instance of class. + /// + public MainViewModel() + { + } + + private LibVLC _libVLC; + + /// + /// Gets the instance. + /// + public LibVLC LibVLC + { + get => _libVLC; + private set => SetProperty(ref _libVLC, value); + } + + private LibVLCSharp.Shared.MediaPlayer _mediaPlayer; + /// + /// Gets the instance. + /// + public LibVLCSharp.Shared.MediaPlayer MediaPlayer + { + get => _mediaPlayer; + private set => SetProperty(ref _mediaPlayer, value); + } + + /// + /// Initialize LibVLC and playback when page appears + /// + public void OnAppearing() + { + if (LibVLC == null) + { + LibVLC = new LibVLC(enableDebugLogs: true); + } + + if (MediaPlayer == null) + { + var media = new Media(LibVLC, new Uri("http://streams.videolan.org/streams/mkv/multiple_tracks.mkv")); + MediaPlayer = new LibVLCSharp.Shared.MediaPlayer(media) + { + EnableHardwareDecoding = true + }; + media.Dispose(); + MediaPlayer.Play(); + } + } + + /// + /// Dispose MediaPlayer and LibVLC when page disappears + /// + public void OnDisappearing() + { + MediaPlayer?.Dispose(); + MediaPlayer = null; + + LibVLC?.Dispose(); + LibVLC = null; + } + + /// + /// Set property and notify UI + /// + private void SetProperty(ref T field, T value, [CallerMemberName] string propertyName = "") + { + if (!Equals(field, value)) + { + field = value; + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } + } + } +} diff --git a/samples/MAUI/LibVLCSharp.MAUI.Sample.MediaElement/MauiProgram.cs b/samples/MAUI/LibVLCSharp.MAUI.Sample.MediaElement/MauiProgram.cs new file mode 100644 index 000000000..a259a4e0f --- /dev/null +++ b/samples/MAUI/LibVLCSharp.MAUI.Sample.MediaElement/MauiProgram.cs @@ -0,0 +1,33 @@ +using Microsoft.Extensions.Logging; + +namespace LibVLCSharp.MAUI.Sample.MediaElement +{ + /// + /// The MauiProgram class is responsible for creating and configuring the MAUI application. + /// + public static class MauiProgram + { + /// + /// Creates and configures the MAUI application. + /// + /// The configured MAUI application. + public static MauiApp CreateMauiApp() + { + var builder = MauiApp.CreateBuilder(); + builder + .UseMauiApp() + .ConfigureFonts(fonts => + { + fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular"); + fonts.AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold"); + }) + .UseLibVLCSharp(); // Add your custom fonts and handler from your library + +#if DEBUG + builder.Logging.AddDebug(); +#endif + + return builder.Build(); + } + } +} diff --git a/samples/MAUI/LibVLCSharp.MAUI.Sample.MediaElement/Platforms/Android/AndroidManifest.xml b/samples/MAUI/LibVLCSharp.MAUI.Sample.MediaElement/Platforms/Android/AndroidManifest.xml new file mode 100644 index 000000000..e9937ad77 --- /dev/null +++ b/samples/MAUI/LibVLCSharp.MAUI.Sample.MediaElement/Platforms/Android/AndroidManifest.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/samples/MAUI/LibVLCSharp.MAUI.Sample.MediaElement/Platforms/Android/MainActivity.cs b/samples/MAUI/LibVLCSharp.MAUI.Sample.MediaElement/Platforms/Android/MainActivity.cs new file mode 100644 index 000000000..83cdd2401 --- /dev/null +++ b/samples/MAUI/LibVLCSharp.MAUI.Sample.MediaElement/Platforms/Android/MainActivity.cs @@ -0,0 +1,12 @@ +using Android.App; +using Android.Content.PM; +using Android.OS; +using Microsoft.Maui; + +namespace LibVLCSharp.MAUI.Sample.MediaElement +{ + [Activity(Theme = "@style/Maui.SplashTheme", MainLauncher = true, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation | ConfigChanges.UiMode | ConfigChanges.ScreenLayout | ConfigChanges.SmallestScreenSize | ConfigChanges.Density)] + public class MainActivity : MauiAppCompatActivity + { + } +} diff --git a/samples/MAUI/LibVLCSharp.MAUI.Sample.MediaElement/Platforms/Android/MainApplication.cs b/samples/MAUI/LibVLCSharp.MAUI.Sample.MediaElement/Platforms/Android/MainApplication.cs new file mode 100644 index 000000000..c28054c29 --- /dev/null +++ b/samples/MAUI/LibVLCSharp.MAUI.Sample.MediaElement/Platforms/Android/MainApplication.cs @@ -0,0 +1,16 @@ +using Android.App; +using Android.Runtime; + +namespace LibVLCSharp.MAUI.Sample.MediaElement +{ + [Application] + public class MainApplication : MauiApplication + { + public MainApplication(IntPtr handle, JniHandleOwnership ownership) + : base(handle, ownership) + { + } + + protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp(); + } +} diff --git a/samples/MAUI/LibVLCSharp.MAUI.Sample.MediaElement/Platforms/Android/Resources/values/colors.xml b/samples/MAUI/LibVLCSharp.MAUI.Sample.MediaElement/Platforms/Android/Resources/values/colors.xml new file mode 100644 index 000000000..c04d7492a --- /dev/null +++ b/samples/MAUI/LibVLCSharp.MAUI.Sample.MediaElement/Platforms/Android/Resources/values/colors.xml @@ -0,0 +1,6 @@ + + + #512BD4 + #2B0B98 + #2B0B98 + \ No newline at end of file diff --git a/samples/MAUI/LibVLCSharp.MAUI.Sample.MediaElement/Platforms/MacCatalyst/AppDelegate.cs b/samples/MAUI/LibVLCSharp.MAUI.Sample.MediaElement/Platforms/MacCatalyst/AppDelegate.cs new file mode 100644 index 000000000..f0e33d31d --- /dev/null +++ b/samples/MAUI/LibVLCSharp.MAUI.Sample.MediaElement/Platforms/MacCatalyst/AppDelegate.cs @@ -0,0 +1,10 @@ +using Foundation; + +namespace LibVLCSharp.MAUI.Sample.MediaElement +{ + [Register("AppDelegate")] + public class AppDelegate : MauiUIApplicationDelegate + { + protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp(); + } +} diff --git a/samples/MAUI/LibVLCSharp.MAUI.Sample.MediaElement/Platforms/MacCatalyst/Info.plist b/samples/MAUI/LibVLCSharp.MAUI.Sample.MediaElement/Platforms/MacCatalyst/Info.plist new file mode 100644 index 000000000..c96dd0a22 --- /dev/null +++ b/samples/MAUI/LibVLCSharp.MAUI.Sample.MediaElement/Platforms/MacCatalyst/Info.plist @@ -0,0 +1,30 @@ + + + + + UIDeviceFamily + + 1 + 2 + + UIRequiredDeviceCapabilities + + arm64 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + XSAppIconAssets + Assets.xcassets/appicon.appiconset + + diff --git a/samples/MAUI/LibVLCSharp.MAUI.Sample.MediaElement/Platforms/MacCatalyst/Program.cs b/samples/MAUI/LibVLCSharp.MAUI.Sample.MediaElement/Platforms/MacCatalyst/Program.cs new file mode 100644 index 000000000..30090b8eb --- /dev/null +++ b/samples/MAUI/LibVLCSharp.MAUI.Sample.MediaElement/Platforms/MacCatalyst/Program.cs @@ -0,0 +1,16 @@ +using ObjCRuntime; +using UIKit; + +namespace LibVLCSharp.MAUI.Sample.MediaElement +{ + public class Program + { + // This is the main entry point of the application. + static void Main(string[] args) + { + // if you want to use a different Application Delegate class from "AppDelegate" + // you can specify it here. + UIApplication.Main(args, null, typeof(AppDelegate)); + } + } +} diff --git a/samples/MAUI/LibVLCSharp.MAUI.Sample.MediaElement/Platforms/Windows/App.xaml b/samples/MAUI/LibVLCSharp.MAUI.Sample.MediaElement/Platforms/Windows/App.xaml new file mode 100644 index 000000000..8daf553d0 --- /dev/null +++ b/samples/MAUI/LibVLCSharp.MAUI.Sample.MediaElement/Platforms/Windows/App.xaml @@ -0,0 +1,8 @@ + + + diff --git a/samples/MAUI/LibVLCSharp.MAUI.Sample.MediaElement/Platforms/Windows/App.xaml.cs b/samples/MAUI/LibVLCSharp.MAUI.Sample.MediaElement/Platforms/Windows/App.xaml.cs new file mode 100644 index 000000000..e5405ac9b --- /dev/null +++ b/samples/MAUI/LibVLCSharp.MAUI.Sample.MediaElement/Platforms/Windows/App.xaml.cs @@ -0,0 +1,25 @@ +using Microsoft.UI.Xaml; + +// To learn more about WinUI, the WinUI project structure, +// and more about our project templates, see: http://aka.ms/winui-project-info. + +namespace LibVLCSharp.MAUI.Sample.MediaElement.WinUI +{ + /// + /// Provides application-specific behavior to supplement the default Application class. + /// + public partial class App : MauiWinUIApplication + { + /// + /// Initializes the singleton application object. This is the first line of authored code + /// executed, and as such is the logical equivalent of main() or WinMain(). + /// + public App() + { + this.InitializeComponent(); + } + + protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp(); + } + +} diff --git a/samples/MAUI/LibVLCSharp.MAUI.Sample.MediaElement/Platforms/Windows/Package.appxmanifest b/samples/MAUI/LibVLCSharp.MAUI.Sample.MediaElement/Platforms/Windows/Package.appxmanifest new file mode 100644 index 000000000..fcdf34f18 --- /dev/null +++ b/samples/MAUI/LibVLCSharp.MAUI.Sample.MediaElement/Platforms/Windows/Package.appxmanifest @@ -0,0 +1,46 @@ + + + + + + + + + $placeholder$ + User Name + $placeholder$.png + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/samples/MAUI/LibVLCSharp.MAUI.Sample.MediaElement/Platforms/Windows/app.manifest b/samples/MAUI/LibVLCSharp.MAUI.Sample.MediaElement/Platforms/Windows/app.manifest new file mode 100644 index 000000000..a62726014 --- /dev/null +++ b/samples/MAUI/LibVLCSharp.MAUI.Sample.MediaElement/Platforms/Windows/app.manifest @@ -0,0 +1,15 @@ + + + + + + + + true/PM + PerMonitorV2, PerMonitor + + + diff --git a/samples/MAUI/LibVLCSharp.MAUI.Sample.MediaElement/Platforms/iOS/AppDelegate.cs b/samples/MAUI/LibVLCSharp.MAUI.Sample.MediaElement/Platforms/iOS/AppDelegate.cs new file mode 100644 index 000000000..6e7ff1fe9 --- /dev/null +++ b/samples/MAUI/LibVLCSharp.MAUI.Sample.MediaElement/Platforms/iOS/AppDelegate.cs @@ -0,0 +1,18 @@ +using Foundation; +using Microsoft.Maui; + +namespace LibVLCSharp.MAUI.Sample.MediaElement +{ + /// + /// The AppDelegate class is responsible for handling application-level events. + /// + [Register("AppDelegate")] + public class AppDelegate : MauiUIApplicationDelegate + { + /// + /// This method is used to create and configure the Maui application instance. + /// + /// Returns the configured application instance. + protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp(); + } +} diff --git a/samples/MAUI/LibVLCSharp.MAUI.Sample.MediaElement/Platforms/iOS/Info.plist b/samples/MAUI/LibVLCSharp.MAUI.Sample.MediaElement/Platforms/iOS/Info.plist new file mode 100644 index 000000000..0004a4fde --- /dev/null +++ b/samples/MAUI/LibVLCSharp.MAUI.Sample.MediaElement/Platforms/iOS/Info.plist @@ -0,0 +1,32 @@ + + + + + LSRequiresIPhoneOS + + UIDeviceFamily + + 1 + 2 + + UIRequiredDeviceCapabilities + + arm64 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + XSAppIconAssets + Assets.xcassets/appicon.appiconset + + diff --git a/samples/MAUI/LibVLCSharp.MAUI.Sample.MediaElement/Platforms/iOS/Program.cs b/samples/MAUI/LibVLCSharp.MAUI.Sample.MediaElement/Platforms/iOS/Program.cs new file mode 100644 index 000000000..30641945e --- /dev/null +++ b/samples/MAUI/LibVLCSharp.MAUI.Sample.MediaElement/Platforms/iOS/Program.cs @@ -0,0 +1,22 @@ +using ObjCRuntime; +using UIKit; + +namespace LibVLCSharp.MAUI.Sample.MediaElement +{ + /// + /// The Program class contains the main entry point of the application. + /// + public class Program + { + /// + /// The main entry point of the application. + /// + /// An array of command-line arguments. + static void Main(string[] args) + { + // if you want to use a different Application Delegate class from "AppDelegate" + // you can specify it here. + UIApplication.Main(args, null, typeof(AppDelegate)); + } + } +} diff --git a/samples/MAUI/LibVLCSharp.MAUI.Sample.MediaElement/Properties/launchSettings.json b/samples/MAUI/LibVLCSharp.MAUI.Sample.MediaElement/Properties/launchSettings.json new file mode 100644 index 000000000..edf8aadcc --- /dev/null +++ b/samples/MAUI/LibVLCSharp.MAUI.Sample.MediaElement/Properties/launchSettings.json @@ -0,0 +1,8 @@ +{ + "profiles": { + "Windows Machine": { + "commandName": "MsixPackage", + "nativeDebugging": false + } + } +} \ No newline at end of file diff --git a/samples/MAUI/LibVLCSharp.MAUI.Sample.MediaElement/Resources/AppIcon/appicon.svg b/samples/MAUI/LibVLCSharp.MAUI.Sample.MediaElement/Resources/AppIcon/appicon.svg new file mode 100644 index 000000000..9d63b6513 --- /dev/null +++ b/samples/MAUI/LibVLCSharp.MAUI.Sample.MediaElement/Resources/AppIcon/appicon.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/samples/MAUI/LibVLCSharp.MAUI.Sample.MediaElement/Resources/AppIcon/appiconfg.svg b/samples/MAUI/LibVLCSharp.MAUI.Sample.MediaElement/Resources/AppIcon/appiconfg.svg new file mode 100644 index 000000000..21dfb25f1 --- /dev/null +++ b/samples/MAUI/LibVLCSharp.MAUI.Sample.MediaElement/Resources/AppIcon/appiconfg.svg @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/samples/MAUI/LibVLCSharp.MAUI.Sample.MediaElement/Resources/Fonts/OpenSans-Regular.ttf b/samples/MAUI/LibVLCSharp.MAUI.Sample.MediaElement/Resources/Fonts/OpenSans-Regular.ttf new file mode 100644 index 000000000..39b6aff94 Binary files /dev/null and b/samples/MAUI/LibVLCSharp.MAUI.Sample.MediaElement/Resources/Fonts/OpenSans-Regular.ttf differ diff --git a/samples/MAUI/LibVLCSharp.MAUI.Sample.MediaElement/Resources/Fonts/OpenSans-Semibold.ttf b/samples/MAUI/LibVLCSharp.MAUI.Sample.MediaElement/Resources/Fonts/OpenSans-Semibold.ttf new file mode 100644 index 000000000..e0d2fa56e Binary files /dev/null and b/samples/MAUI/LibVLCSharp.MAUI.Sample.MediaElement/Resources/Fonts/OpenSans-Semibold.ttf differ diff --git a/samples/MAUI/LibVLCSharp.MAUI.Sample.MediaElement/Resources/Images/dotnet_bot.svg b/samples/MAUI/LibVLCSharp.MAUI.Sample.MediaElement/Resources/Images/dotnet_bot.svg new file mode 100644 index 000000000..abfaff26a --- /dev/null +++ b/samples/MAUI/LibVLCSharp.MAUI.Sample.MediaElement/Resources/Images/dotnet_bot.svg @@ -0,0 +1,93 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/samples/MAUI/LibVLCSharp.MAUI.Sample.MediaElement/Resources/Raw/AboutAssets.txt b/samples/MAUI/LibVLCSharp.MAUI.Sample.MediaElement/Resources/Raw/AboutAssets.txt new file mode 100644 index 000000000..15d624484 --- /dev/null +++ b/samples/MAUI/LibVLCSharp.MAUI.Sample.MediaElement/Resources/Raw/AboutAssets.txt @@ -0,0 +1,15 @@ +Any raw assets you want to be deployed with your application can be placed in +this directory (and child directories). Deployment of the asset to your application +is automatically handled by the following `MauiAsset` Build Action within your `.csproj`. + + + +These files will be deployed with you package and will be accessible using Essentials: + + async Task LoadMauiAsset() + { + using var stream = await FileSystem.OpenAppPackageFileAsync("AboutAssets.txt"); + using var reader = new StreamReader(stream); + + var contents = reader.ReadToEnd(); + } diff --git a/samples/MAUI/LibVLCSharp.MAUI.Sample.MediaElement/Resources/Splash/splash.svg b/samples/MAUI/LibVLCSharp.MAUI.Sample.MediaElement/Resources/Splash/splash.svg new file mode 100644 index 000000000..21dfb25f1 --- /dev/null +++ b/samples/MAUI/LibVLCSharp.MAUI.Sample.MediaElement/Resources/Splash/splash.svg @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/samples/MAUI/LibVLCSharp.MAUI.Sample.MediaElement/Resources/Styles/Colors.xaml b/samples/MAUI/LibVLCSharp.MAUI.Sample.MediaElement/Resources/Styles/Colors.xaml new file mode 100644 index 000000000..43ebcdedc --- /dev/null +++ b/samples/MAUI/LibVLCSharp.MAUI.Sample.MediaElement/Resources/Styles/Colors.xaml @@ -0,0 +1,44 @@ + + + + + #512BD4 + #DFD8F7 + #2B0B98 + White + Black + #E1E1E1 + #C8C8C8 + #ACACAC + #919191 + #6E6E6E + #404040 + #212121 + #141414 + + + + + + + + + + + + + + + #F7B548 + #FFD590 + #FFE5B9 + #28C2D1 + #7BDDEF + #C3F2F4 + #3E8EED + #72ACF1 + #A7CBF6 + + \ No newline at end of file diff --git a/samples/MAUI/LibVLCSharp.MAUI.Sample.MediaElement/Resources/Styles/Styles.xaml b/samples/MAUI/LibVLCSharp.MAUI.Sample.MediaElement/Resources/Styles/Styles.xaml new file mode 100644 index 000000000..7159c06db --- /dev/null +++ b/samples/MAUI/LibVLCSharp.MAUI.Sample.MediaElement/Resources/Styles/Styles.xaml @@ -0,0 +1,405 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/LibVLCSharp.MAUI/AppHostBuilderExtensions.cs b/src/LibVLCSharp.MAUI/AppHostBuilderExtensions.cs index ae0515d8d..9b40c1ec9 100644 --- a/src/LibVLCSharp.MAUI/AppHostBuilderExtensions.cs +++ b/src/LibVLCSharp.MAUI/AppHostBuilderExtensions.cs @@ -10,10 +10,22 @@ public static class AppHostBuilderExtensions /// /// MauiAppBuilder /// configured builder for libvlcsharp - public static MauiAppBuilder UseLibVLCSharp(this MauiAppBuilder builder) => + public static MauiAppBuilder UseLibVLCSharp(this MauiAppBuilder builder) + { + // Register LibVLCSharp handlers builder.ConfigureMauiHandlers(handlers => { handlers.AddHandler(typeof(VideoView), typeof(VideoViewHandler)); }); + + // Configure custom fonts + builder.ConfigureFonts(fonts => + { + fonts.AddFont("FontAwesome5Brands.otf", "FontAwesomeBrands"); + fonts.AddFont("FontAwesome5Solid.otf", "FontAwesomeSolid"); + }); + + return builder; + } } } diff --git a/src/LibVLCSharp.MAUI/Converters/BufferingProgressToBoolConverter.cs b/src/LibVLCSharp.MAUI/Converters/BufferingProgressToBoolConverter.cs new file mode 100644 index 000000000..39d705c09 --- /dev/null +++ b/src/LibVLCSharp.MAUI/Converters/BufferingProgressToBoolConverter.cs @@ -0,0 +1,33 @@ +using System; +using System.Globalization; +using Microsoft.Maui.Controls; + +namespace LibVLCSharp.MAUI.Converters +{ + /// + /// Converts a value not equals to 0 and 1 to true. + /// + internal class BufferingProgressToBoolConverter : IValueConverter + { + /// + /// Modifies the source data before passing it to the target for display in the UI. + /// + /// The source data being passed to the target. + /// The type of the target property. + /// An optional parameter to be used in the converter logic. + /// The culture of the conversion. + /// true if value is not equals to 0 or 1, false otherwise + public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture) + { + return value is double d && d != 0 && d != 1; + } + + /// + /// Not implemented + /// + public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) + { + throw new NotImplementedException(); + } + } +} diff --git a/src/LibVLCSharp.MAUI/Converters/ObjectToBoolConverter.cs b/src/LibVLCSharp.MAUI/Converters/ObjectToBoolConverter.cs new file mode 100644 index 000000000..6bad71bec --- /dev/null +++ b/src/LibVLCSharp.MAUI/Converters/ObjectToBoolConverter.cs @@ -0,0 +1,33 @@ +using System; +using System.Globalization; +using Microsoft.Maui.Controls; + +namespace LibVLCSharp.MAUI.Converters +{ + /// + /// Converts not null object to true. + /// + internal class ObjectToBoolConverter : IValueConverter + { + /// + /// Modifies the source data before passing it to the target for display in the UI. + /// + /// The source data being passed to the target. + /// The type of the target property. + /// An optional parameter to be used in the converter logic. + /// The culture of the conversion. + /// true if value is not null, false otherwise + public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture) + { + return value != null; + } + + /// + /// Not implemented + /// + public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) + { + throw new NotImplementedException(); + } + } +} diff --git a/src/LibVLCSharp.MAUI/Dispatcher.cs b/src/LibVLCSharp.MAUI/Dispatcher.cs new file mode 100644 index 000000000..eabc330c4 --- /dev/null +++ b/src/LibVLCSharp.MAUI/Dispatcher.cs @@ -0,0 +1,25 @@ +using System; +using System.Threading.Tasks; +using LibVLCSharp.Shared.MediaPlayerElement; +using Microsoft.Maui.Controls; +using Microsoft.Maui; + +namespace LibVLCSharp.MAUI +{ + /// + /// Object that provides services for managing the queue of work items for a thread + /// + internal class Dispatcher : Shared.MediaPlayerElement.IDispatcher + { + /// + /// Schedules the provided callback on the UI thread from a worker thread + /// + /// The callback on which the dispatcher returns when the event is dispatched + /// The task object representing the asynchronous operation + public Task InvokeAsync(Action action) + { + Application.Current?.Dispatcher.Dispatch(action); + return Task.CompletedTask; + } + } +} diff --git a/src/LibVLCSharp.MAUI/DisplayInformation.cs b/src/LibVLCSharp.MAUI/DisplayInformation.cs new file mode 100644 index 000000000..b1ff2b516 --- /dev/null +++ b/src/LibVLCSharp.MAUI/DisplayInformation.cs @@ -0,0 +1,17 @@ +using LibVLCSharp.Shared.MediaPlayerElement; +using Microsoft.Maui.Controls; +using Microsoft.Maui; + +namespace LibVLCSharp.MAUI +{ + /// + /// Monitors display-related information for an application view. + /// + internal class DisplayInformation : IDisplayInformation + { + /// + /// Gets the scale factor + /// + public double ScalingFactor => DeviceDisplay.MainDisplayInfo.Density; + } +} diff --git a/src/LibVLCSharp.MAUI/DisplayRequest.cs b/src/LibVLCSharp.MAUI/DisplayRequest.cs new file mode 100644 index 000000000..720915834 --- /dev/null +++ b/src/LibVLCSharp.MAUI/DisplayRequest.cs @@ -0,0 +1,28 @@ +using LibVLCSharp.Shared.MediaPlayerElement; +using Microsoft.Maui.Controls; +using Microsoft.Maui; + +namespace LibVLCSharp.MAUI +{ + /// + /// Represents a display request + /// + internal class DisplayRequest : IDisplayRequest + { + /// + /// Activates a display request + /// + public void RequestActive() + { + DeviceDisplay.Current.KeepScreenOn = true; + } + + /// + /// Deactivates a display request + /// + public void RequestRelease() + { + DeviceDisplay.Current.KeepScreenOn = false; + } + } +} diff --git a/src/LibVLCSharp.MAUI/Effects/ClickEffect.cs b/src/LibVLCSharp.MAUI/Effects/ClickEffect.cs new file mode 100644 index 000000000..5b6642e66 --- /dev/null +++ b/src/LibVLCSharp.MAUI/Effects/ClickEffect.cs @@ -0,0 +1,20 @@ +using Microsoft.Maui.Controls; + +namespace LibVLCSharp.MAUI.Effects +{ + /// + /// Click effect. + /// + internal class ClickEffect : TriggerAction + { + /// + /// Apply a click effect. + /// + /// The object on which to invoke the trigger action. + protected override async void Invoke(VisualElement sender) + { + await sender.ScaleTo(0.85, 100); + await sender.ScaleTo(1, 50); + } + } +} diff --git a/src/LibVLCSharp.MAUI/IOrientationHandler.cs b/src/LibVLCSharp.MAUI/IOrientationHandler.cs new file mode 100644 index 000000000..2a8e868ec --- /dev/null +++ b/src/LibVLCSharp.MAUI/IOrientationHandler.cs @@ -0,0 +1,18 @@ +namespace LibVLCSharp.MAUI +{ + /// + /// Force Device Orientation. + /// + public interface IOrientationHandler + { + /// + /// Lock device's orientation. + /// + void LockOrientation(); + + /// + /// Unlock device's orientation. + /// + void UnLockOrientation(); + } +} diff --git a/src/LibVLCSharp.MAUI/IPowerManager.cs b/src/LibVLCSharp.MAUI/IPowerManager.cs new file mode 100644 index 000000000..fee934bb7 --- /dev/null +++ b/src/LibVLCSharp.MAUI/IPowerManager.cs @@ -0,0 +1,13 @@ +namespace LibVLCSharp.MAUI +{ + /// + /// Interface for power management. + /// + public interface IPowerManager + { + /// + /// Gets or sets a value indicating whether the screen should be kept on. + /// + bool KeepScreenOn { get; set; } + } +} diff --git a/src/LibVLCSharp.MAUI/ISystemUI.cs b/src/LibVLCSharp.MAUI/ISystemUI.cs new file mode 100644 index 000000000..07c6e5a5f --- /dev/null +++ b/src/LibVLCSharp.MAUI/ISystemUI.cs @@ -0,0 +1,8 @@ +namespace LibVLCSharp.MAUI +{ + internal interface ISystemUI + { + void ShowSystemUI(); + void HideSystemUI(); + } +} diff --git a/src/LibVLCSharp.MAUI/LibVLCSharp.MAUI.csproj b/src/LibVLCSharp.MAUI/LibVLCSharp.MAUI.csproj index d1af6db4b..40905a1ed 100644 --- a/src/LibVLCSharp.MAUI/LibVLCSharp.MAUI.csproj +++ b/src/LibVLCSharp.MAUI/LibVLCSharp.MAUI.csproj @@ -16,8 +16,40 @@ LibVLC needs to be installed separately, see VideoLAN.LibVLC.* packages. + + + + + + + + + %(Filename) + Code + + + %(Filename) + Code + + + %(Filename) + Code + + + + + + MSBuild:Compile + Designer + + + MSBuild:Compile + Designer + + + diff --git a/src/LibVLCSharp.MAUI/MediaPlayerElement.xaml b/src/LibVLCSharp.MAUI/MediaPlayerElement.xaml new file mode 100644 index 000000000..1e7ea8494 --- /dev/null +++ b/src/LibVLCSharp.MAUI/MediaPlayerElement.xaml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/LibVLCSharp.MAUI/MediaPlayerElement.xaml.cs b/src/LibVLCSharp.MAUI/MediaPlayerElement.xaml.cs new file mode 100644 index 000000000..ca2e5b735 --- /dev/null +++ b/src/LibVLCSharp.MAUI/MediaPlayerElement.xaml.cs @@ -0,0 +1,315 @@ +using System; +using LibVLCSharp.Shared; +using Microsoft.Maui.Controls.Xaml; +using Microsoft.Maui.Controls; +using Microsoft.Maui.Storage; + +namespace LibVLCSharp.MAUI +{ + /// + /// Represents event data for page-related events. + /// + public class PageEventArgs : EventArgs + { + /// + /// Gets the associated instance. + /// + public Page Page { get; } + + /// + /// Initializes a new instance of the class with the specified page. + /// + /// The page associated with the event. + public PageEventArgs(Page page) + { + Page = page; + } + } + + /// + /// Provides helper methods for handling page lifecycle events. + /// + public static class LifecycleHelper + { + private static readonly WeakEventManager _pageAppearingEventManager = new(); + private static readonly WeakEventManager _pageDisappearingEventManager = new(); + + /// + /// Occurs when a page is appearing. + /// + public static event EventHandler PageAppearing + { + add => _pageAppearingEventManager.AddEventHandler(value); + remove => _pageAppearingEventManager.RemoveEventHandler(value); + } + + /// + /// Occurs when a page is disappearing. + /// + public static event EventHandler PageDisappearing + { + add => _pageDisappearingEventManager.AddEventHandler(value); + remove => _pageDisappearingEventManager.RemoveEventHandler(value); + } + + /// + /// Registers the page lifecycle events to track page appearing and disappearing. + /// + public static void RegisterPageLifecycleEvents() + { + if (Application.Current != null) + { + Application.Current.PageAppearing += (s, e) => _pageAppearingEventManager.HandleEvent(s ?? Application.Current, new PageEventArgs(e), nameof(PageAppearing)); + Application.Current.PageDisappearing += (s, e) => _pageDisappearingEventManager.HandleEvent(s ?? Application.Current, new PageEventArgs(e), nameof(PageDisappearing)); + } + } + } + + /// + /// Represents an object that uses a to render audio and video to the display. + /// + [XamlCompilation(XamlCompilationOptions.Compile)] + public partial class MediaPlayerElement : ContentView + { + /// + /// Initializes a new instance of the class. + /// + public MediaPlayerElement() + { + InitializeComponent(); + LifecycleHelper.RegisterPageLifecycleEvents(); + } + + private bool Initialized { get; set; } + + /// + /// Identifies the dependency property. + /// + public static readonly BindableProperty LibVLCProperty = BindableProperty.Create(nameof(LibVLC), typeof(LibVLC), + typeof(MediaPlayerElement), propertyChanged: LibVLCPropertyChanged); + + /// + /// Gets the instance. + /// + public LibVLC LibVLC + { + get => (LibVLC)GetValue(LibVLCProperty); + set => SetValue(LibVLCProperty, value); + } + + /// + /// Identifies the dependency property. + /// + public static readonly BindableProperty MediaPlayerProperty = BindableProperty.Create(nameof(MediaPlayer), + typeof(LibVLCSharp.Shared.MediaPlayer), typeof(MediaPlayerElement), propertyChanged: MediaPlayerPropertyChanged); + + /// + /// Gets the instance. + /// + public LibVLCSharp.Shared.MediaPlayer MediaPlayer + { + get => (LibVLCSharp.Shared.MediaPlayer)GetValue(MediaPlayerProperty); + set => SetValue(MediaPlayerProperty, value); + } + + private static readonly BindableProperty PlaybackControlsProperty = BindableProperty.Create(nameof(PlaybackControls), + typeof(PlaybackControls), typeof(MediaPlayerElement), propertyChanged: PlaybackControlsPropertyChanged); + + /// + /// Gets or sets the playback controls for the media. + /// + public PlaybackControls PlaybackControls + { + get => (PlaybackControls)GetValue(PlaybackControlsProperty); + set => SetValue(PlaybackControlsProperty, value); + } + + /// + /// Identifies the dependency property. + /// + private static readonly BindableProperty VideoViewProperty = BindableProperty.Create(nameof(VideoView), typeof(VideoView), + typeof(MediaPlayerElement), propertyChanged: VideoViewPropertyChanged); + + /// + /// Gets or sets the video view. + /// + public VideoView? VideoView + { + get => (VideoView)GetValue(VideoViewProperty); + private set => SetValue(VideoViewProperty, value); + } + + private static readonly BindableProperty EnableRendererDiscoveryProperty = BindableProperty.Create(nameof(EnableRendererDiscovery), + typeof(bool), typeof(PlaybackControls), true, propertyChanged: EnableRendererDiscoveryPropertyChanged); + + /// + /// Enable or disable renderer discovery. + /// + public bool EnableRendererDiscovery + { + get => (bool)GetValue(EnableRendererDiscoveryProperty); + set => SetValue(EnableRendererDiscoveryProperty, value); + } + + private void OnVideoViewChanged(VideoView videoView) + { + if (videoView != null) + { + videoView.MediaPlayer = MediaPlayer; + var playbackControls = PlaybackControls; + if (playbackControls != null) + { + playbackControls.VideoView = videoView; + } + } + } + + private void OnLibVLCChanged(LibVLC libVLC) + { + var playbackControls = PlaybackControls; + if (playbackControls != null) + { + playbackControls.LibVLC = libVLC; + } + } + + private void OnMediaPlayerChanged(LibVLCSharp.Shared.MediaPlayer mediaPlayer) + { + var videoView = VideoView; + if (videoView != null) + { + videoView.MediaPlayer = mediaPlayer; + } + var playbackControls = PlaybackControls; + if (playbackControls != null) + { + playbackControls.MediaPlayer = mediaPlayer; + } + } + + private void OnPlayControlsChanged(PlaybackControls playbackControls) + { + if (playbackControls != null) + { + playbackControls.IsCastButtonVisible = EnableRendererDiscovery; + playbackControls.LibVLC = LibVLC; + playbackControls.MediaPlayer = MediaPlayer; + playbackControls.VideoView = VideoView; + } + } + + private void OnEnableRendererDiscoveryChanged(bool enableRendererDiscovery) + { + var playbackControls = PlaybackControls; + if (playbackControls != null) + { + playbackControls.IsCastButtonVisible = enableRendererDiscovery; + } + } + + private static void VideoViewPropertyChanged(BindableObject bindable, object oldValue, object newValue) + { + ((MediaPlayerElement)bindable).OnVideoViewChanged((VideoView)newValue); + } + + private static void LibVLCPropertyChanged(BindableObject bindable, object oldValue, object newValue) + { + ((MediaPlayerElement)bindable).OnLibVLCChanged((LibVLC)newValue); + } + + private static void MediaPlayerPropertyChanged(BindableObject bindable, object oldValue, object newValue) + { + ((MediaPlayerElement)bindable).OnMediaPlayerChanged((LibVLCSharp.Shared.MediaPlayer)newValue); + } + + private static void PlaybackControlsPropertyChanged(BindableObject bindable, object oldValue, object newValue) + { + ((MediaPlayerElement)bindable).OnPlayControlsChanged((PlaybackControls)newValue); + } + + private static void EnableRendererDiscoveryPropertyChanged(BindableObject bindable, object oldValue, object newValue) + { + ((MediaPlayerElement)bindable).OnEnableRendererDiscoveryChanged((bool)newValue); + } + + /// + /// Invoked whenever the of an element is set. + /// Implement this method in order to add behavior when the element is added to a parent. + /// + /// Implementors must call the base method. + protected override void OnParentSet() + { + base.OnParentSet(); + + if (Parent != null && !Initialized) + { + Initialized = true; + + if (VideoView == null) + { + VideoView = new VideoView(); + } + + if (PlaybackControls == null) + { + PlaybackControls = new PlaybackControls(); + } + + AttachLifecycleEvents(); + } + } + + private void AttachLifecycleEvents() + { + LifecycleHelper.PageAppearing += OnPageAppearing; + LifecycleHelper.PageDisappearing += OnPageDisappearing; + } + + /// + /// Handle page appearing logic + /// + /// + /// + private void OnPageAppearing(object? sender, EventArgs e) + { + if (sender is Page page && page == this.FindAncestor()) + { + var mediaPlayer = MediaPlayer; + if (mediaPlayer != null) + { + Preferences.Set($"VLC_{mediaPlayer.NativeReference}_MediaPlayerElement_Position", mediaPlayer.Position); + Preferences.Set($"VLC_{mediaPlayer.NativeReference}_MediaPlayerElement_IsPlaying", mediaPlayer.State == VLCState.Playing); + mediaPlayer.Stop(); + } + VideoView = null; + } + } + + /// + /// Handle page disappearing logic + /// minimize change. + /// + /// + private void OnPageDisappearing(object? sender, EventArgs e) + { + if (sender is Page page && page == this.FindAncestor()) + { + VideoView = new VideoView(); + var mediaPlayer = MediaPlayer; + if (mediaPlayer != null) + { + if (Preferences.Get($"VLC_{mediaPlayer.NativeReference}_MediaPlayerElement_IsPlaying", false)) + { + mediaPlayer.Play(); + mediaPlayer.Position = Preferences.Get($"VLC_{mediaPlayer.NativeReference}_MediaPlayerElement_Position", 0f); + } + } + } + } + + private void GestureRecognized(object sender, EventArgs e) + { + PlaybackControls.Show(); + } + } +} diff --git a/src/LibVLCSharp.MAUI/PlaybackControls.xaml b/src/LibVLCSharp.MAUI/PlaybackControls.xaml new file mode 100644 index 000000000..fd279fb27 --- /dev/null +++ b/src/LibVLCSharp.MAUI/PlaybackControls.xaml @@ -0,0 +1,10 @@ + + + + + + diff --git a/src/LibVLCSharp.MAUI/PlaybackControls.xaml.cs b/src/LibVLCSharp.MAUI/PlaybackControls.xaml.cs new file mode 100644 index 000000000..8cceda26c --- /dev/null +++ b/src/LibVLCSharp.MAUI/PlaybackControls.xaml.cs @@ -0,0 +1,1363 @@ +using System; +using System.Collections.ObjectModel; +using System.Linq; +using System.Resources; +using System.Threading.Tasks; +using LibVLCSharp.MAUI.Resources; +using LibVLCSharp.Shared; +using LibVLCSharp.Shared.MediaPlayerElement; +using Microsoft.Maui.Controls.Xaml; +using Microsoft.Maui.Graphics; +using Microsoft.Maui.Controls; +using Microsoft.Maui; + +namespace LibVLCSharp.MAUI +{ + /// + /// Represents the playback controls for a . + /// + [XamlCompilation(XamlCompilationOptions.Compile)] + public partial class PlaybackControls : TemplatedView + { + private const string AudioSelectionAvailableState = "AudioSelectionAvailable"; + private const string VideoSelectionAvailableState = "VideoSelectionAvailable"; + private const string AudioSelectionUnavailableState = "AudioSelectionUnavailable"; + private const string VideoSelectionUnavailableState = "videoSelectionUnavailable"; + private const string ClosedCaptionsSelectionAvailableState = "ClosedCaptionsSelectionAvailable"; + private const string ClosedCaptionsSelectionUnavailableState = "ClosedCaptionsSelectionUnavailable"; + private const string PlayState = "PlayState"; + private const string PauseState = "PauseState"; + private const string PauseAvailableState = "PauseAvailable"; + private const string PauseUnavailableState = "PauseUnavailable"; + private const string SeekAvailableState = "SeekAvailable"; + private const string SeekUnavailableState = "SeekUnavailable"; + private const string CastAvailableState = "CastAvailable"; + private const string CastUnavailableState = "CastUnavailable"; + + /// + /// Initializes a new instance of class. + /// + public PlaybackControls() + { + InitializeComponent(); + + try + { + ButtonColor = (Color)(Resources[nameof(ButtonColor)] ?? Colors.Transparent); + Foreground = (Color)(Resources[nameof(Foreground)] ?? Colors.White); + MainColor = (Color)(Resources[nameof(MainColor)] ?? Colors.Transparent); + TracksButtonStyle = Resources[nameof(TracksButtonStyle)] as Style; + BufferingProgressBarStyle = Resources[nameof(BufferingProgressBarStyle)] as Style; + ButtonBarStyle = Resources[nameof(ButtonBarStyle)] as Style; + CastButtonStyle = Resources[nameof(CastButtonStyle)] as Style; + ControlsPanelStyle = Resources[nameof(ControlsPanelStyle)] as Style; + MessageStyle = Resources[nameof(MessageStyle)] as Style; + PlayPauseButtonStyle = Resources[nameof(PlayPauseButtonStyle)] as Style; + RemainingTimeLabelStyle = Resources[nameof(RemainingTimeLabelStyle)] as Style; + ElapsedTimeLabelStyle = Resources[nameof(ElapsedTimeLabelStyle)] as Style; + SeekBarStyle = Resources[nameof(SeekBarStyle)] as Style; + StopButtonStyle = Resources[nameof(StopButtonStyle)] as Style; + AspectRatioButtonStyle = Resources[nameof(AspectRatioButtonStyle)] as Style; + RewindButtonStyle = Resources[nameof(RewindButtonStyle)] as Style; + SeekButtonStyle = Resources[nameof(SeekButtonStyle)] as Style; + LockButtonStyle = Resources[nameof(LockButtonStyle)] as Style; + UnLockButtonStyle = Resources[nameof(UnLockButtonStyle)] as Style; + UnLockControlsPanelStyle = Resources[nameof(UnLockControlsPanelStyle)] as Style; + + Manager = new MediaPlayerElementManager(new Dispatcher(), new DisplayInformation(), new DisplayRequest()); + var autoHideManager = Manager.Get(); + autoHideManager.Shown += async (sender, e) => await FadeInAsync(); + autoHideManager.Hidden += async (sender, e) => await FadeOutAsync(); + autoHideManager.Enabled = ShowAndHideAutomatically; + var audioTrackManager = Manager.Get(); + audioTrackManager.TrackAdded += OnAudioTracksChanged; + audioTrackManager.TrackDeleted += OnAudioTracksChanged; + audioTrackManager.TracksCleared += OnAudioTracksChanged; + var videoTrackManager = Manager.Get(); + videoTrackManager.TrackAdded += OnVideoTracksChanged; + videoTrackManager.TrackDeleted += OnVideoTracksChanged; + videoTrackManager.TracksCleared += OnVideoTracksChanged; + var subTitlesTrackManager = Manager.Get(); + subTitlesTrackManager.TrackAdded += OnSubtitlesTracksChanged; + subTitlesTrackManager.TrackDeleted += OnSubtitlesTracksChanged; + subTitlesTrackManager.TracksCleared += OnSubtitlesTracksChanged; + var castRenderersDiscoverer = Manager.Get(); + castRenderersDiscoverer.CastAvailableChanged += (sender, e) => UpdateCastAvailability(); + castRenderersDiscoverer.Enabled = IsCastButtonVisible; + var seekBarManager = Manager.Get(); + seekBarManager.PositionChanged += SeekBarManager_PositionChanged; + seekBarManager.SeekableChanged += (sender, e) => UpdateSeekAvailability(); + var bufferingProgressNotifier = Manager.Get(); + bufferingProgressNotifier.Buffering += (sender, e) => OnBuffering(); + var stateManager = Manager.Get(); + stateManager.ErrorOccured += (sender, e) => ShowError(); + stateManager.ErrorCleared += (sender, e) => ErrorMessage = null; + stateManager.Playing += (sender, e) => OnPlaying(); + stateManager.Paused += (sender, e) => OnStoppedOrPaused(); + stateManager.Stopped += (sender, e) => OnStoppedOrPaused(); + stateManager.PlayPauseAvailableChanged += (sender, e) => UpdatePauseAvailability(); + } + catch (Exception ex) + { + ShowErrorMessageBox(ex); + } + } + + private void SeekBarManager_PositionChanged(object? sender, EventArgs e) + { + if (!Manager.Get().IsDragging) + { + UpdateTime(); + } + } + + /// + /// Finalizer + /// + ~PlaybackControls() + { + Manager.Dispose(); + } + + private MediaPlayerElementManager Manager { get; } = default!; + private Button? TracksButton { get; set; } + private Button? CastButton { get; set; } + private VisualElement? ControlsPanel { get; set; } + private VisualElement? ButtonBar { get; set; } + private VisualElement? UnLockControlsPanel { get; set; } + private VisualElement? TracksOverlayView { get; set; } + private SwipeToUnLockView? SwipeToUnLock { get; set; } + private Label? TrackBarLabel { get; set; } + private Label? AudioTracksLabel { get; set; } + private Label? VideoTracksLabel { get; set; } + private Label? SubtileTracksLabel { get; set; } + private Button? PlayPauseButton { get; set; } + private Label? RemainingTimeLabel { get; set; } + private Label? ElapsedTimeLabel { get; set; } + private Label? AspectRatioLabel { get; set; } + + private Slider? SeekBar { get; set; } + private ListView? AudioTracksListView { get; set; } + private ListView? VideoTracksListView { get; set; } + private ListView? SubtitlesTracksListView { get; set; } + private bool ScreenLockModeEnable { get; set; } = false; + + private bool Initialized { get; set; } + private ISystemUI? SystemUI => DependencyService.Get(); + private IOrientationHandler? OrientationHandler => DependencyService.Get(); + + private const int SEEK_OFFSET = 2000; + private bool RemoteRendering { get; set; } = false; + private const string Disconnect = "Disconnect"; + private const string Cancel = "Cancel"; + + /// + /// Identifies the dependency property. + /// + public static readonly BindableProperty ButtonColorProperty = BindableProperty.Create(nameof(ButtonColor), typeof(Color), + typeof(PlaybackControls)); + /// + /// Gets or sets the button color. + /// + public Color ButtonColor + { + get => (Color)GetValue(ButtonColorProperty); + set => SetValue(ButtonColorProperty, value); + } + + /// + /// Identifies the dependency property. + /// + public static readonly BindableProperty ForegroundProperty = BindableProperty.Create(nameof(Foreground), typeof(Color), + typeof(PlaybackControls)); + /// + /// Gets or sets the button color. + /// + public Color Foreground + { + get => (Color)GetValue(ForegroundProperty); + set => SetValue(ForegroundProperty, value); + } + + /// + /// Identifies the dependency property. + /// + public static readonly BindableProperty MainColorProperty = BindableProperty.Create(nameof(MainColor), typeof(Color), + typeof(PlaybackControls)); + /// + /// Gets or sets the main color. + /// + public Color MainColor + { + get => (Color)GetValue(MainColorProperty); + set => SetValue(MainColorProperty, value); + } + + /// + /// Identifies the dependency property. + /// + public static readonly BindableProperty TracksButtonStyleProperty = BindableProperty.Create( + nameof(TracksButtonStyle), typeof(Style), typeof(PlaybackControls)); + /// + /// Gets or sets the tracks button style. + /// + public Style? TracksButtonStyle + { + get => (Style)GetValue(TracksButtonStyleProperty); + set => SetValue(TracksButtonStyleProperty, value); + } + + + /// + /// Identifies the dependency property. + /// + public static readonly BindableProperty BufferingProgressBarStyleProperty = BindableProperty.Create(nameof(BufferingProgressBarStyle), + typeof(Style), typeof(PlaybackControls)); + /// + /// Gets or sets the controls panel style. + /// + public Style? BufferingProgressBarStyle + { + get => (Style)GetValue(BufferingProgressBarStyleProperty); + set => SetValue(BufferingProgressBarStyleProperty, value); + } + + /// + /// Identifies the dependency property. + /// + public static readonly BindableProperty ButtonBarStyleProperty = BindableProperty.Create(nameof(ButtonBarStyle), typeof(Style), + typeof(PlaybackControls)); + /// + /// Gets or sets the button bar style. + /// + public Style? ButtonBarStyle + { + get => (Style)GetValue(ButtonBarStyleProperty); + set => SetValue(ButtonBarStyleProperty, value); + } + + /// + /// Identifies the dependency property. + /// + public static readonly BindableProperty CastButtonStyleProperty = BindableProperty.Create(nameof(CastButtonStyle), typeof(Style), + typeof(PlaybackControls)); + /// + /// Gets or sets the cast button style. + /// + public Style? CastButtonStyle + { + get => (Style)GetValue(CastButtonStyleProperty); + set => SetValue(CastButtonStyleProperty, value); + } + + /// + /// Identifies the dependency property. + /// + public static readonly BindableProperty ControlsPanelStyleProperty = BindableProperty.Create(nameof(ControlsPanelStyle), typeof(Style), + typeof(PlaybackControls)); + /// + /// Gets or sets the controls panel style. + /// + public Style? ControlsPanelStyle + { + get => (Style)GetValue(ControlsPanelStyleProperty); + set => SetValue(ControlsPanelStyleProperty, value); + } + + /// + /// Identifies the dependency property. + /// + public static readonly BindableProperty UnLockControlsPanelStyleProperty = BindableProperty.Create(nameof(UnLockControlsPanelStyle), typeof(Style), + typeof(PlaybackControls)); + /// + /// Gets or sets the unlock controls panel style. + /// + public Style? UnLockControlsPanelStyle + { + get => (Style)GetValue(UnLockControlsPanelStyleProperty); + set => SetValue(UnLockControlsPanelStyleProperty, value); + } + + /// + /// Identifies the dependency property. + /// + public static readonly BindableProperty UnLockButtonStyleProperty = BindableProperty.Create(nameof(UnLockButtonStyle), typeof(Style), + typeof(PlaybackControls)); + /// + /// Gets or sets the unlock controls panel style. + /// + public Style? UnLockButtonStyle + { + get => (Style)GetValue(UnLockButtonStyleProperty); + set => SetValue(UnLockButtonStyleProperty, value); + } + + /// + /// Identifies the dependency property. + /// + public static readonly BindableProperty MessageStyleProperty = BindableProperty.Create(nameof(MessageStyle), typeof(Style), + typeof(PlaybackControls)); + /// + /// Gets or sets the message style. + /// + public Style? MessageStyle + { + get => (Style)GetValue(MessageStyleProperty); + set => SetValue(MessageStyleProperty, value); + } + + /// + /// Identifies the dependency property. + /// + public static readonly BindableProperty PlayPauseButtonStyleProperty = BindableProperty.Create(nameof(PlayPauseButtonStyle), typeof(Style), + typeof(PlaybackControls)); + /// + /// Gets or sets the play/pause button style. + /// + public Style? PlayPauseButtonStyle + { + get => (Style)GetValue(PlayPauseButtonStyleProperty); + set => SetValue(PlayPauseButtonStyleProperty, value); + } + + /// + /// Identifies the dependency property. + /// + public static readonly BindableProperty RemainingTimeLabelStyleProperty = BindableProperty.Create(nameof(RemainingTimeLabelStyle), + typeof(Style), typeof(PlaybackControls)); + /// + /// Gets or sets the remaining time label style. + /// + public Style? RemainingTimeLabelStyle + { + get => (Style)GetValue(RemainingTimeLabelStyleProperty); + set => SetValue(RemainingTimeLabelStyleProperty, value); + } + + /// + /// Identifies the dependency property. + /// + public static readonly BindableProperty ElapsedTimeLabelStyleProperty = BindableProperty.Create(nameof(ElapsedTimeLabelStyle), + typeof(Style), typeof(PlaybackControls)); + /// + /// Gets or sets the elapsed time label style. + /// + public Style? ElapsedTimeLabelStyle + { + get => (Style)GetValue(ElapsedTimeLabelStyleProperty); + set => SetValue(ElapsedTimeLabelStyleProperty, value); + } + + /// + /// Identifies the dependency property. + /// + public static readonly BindableProperty SeekBarStyleProperty = BindableProperty.Create(nameof(SeekBarStyle), typeof(Style), + typeof(PlaybackControls)); + /// + /// Gets or sets the seek bar style. + /// + public Style? SeekBarStyle + { + get => (Style)GetValue(SeekBarStyleProperty); + set => SetValue(SeekBarStyleProperty, value); + } + + /// + /// Identifies the dependency property. + /// + public static readonly BindableProperty StopButtonStyleProperty = BindableProperty.Create(nameof(StopButtonStyle), typeof(Style), + typeof(PlaybackControls)); + /// + /// Gets or sets the stop button style. + /// + public Style? StopButtonStyle + { + get => (Style)GetValue(StopButtonStyleProperty); + set => SetValue(StopButtonStyleProperty, value); + } + + /// + /// Identifies the dependency property. + /// + public static readonly BindableProperty VideoViewProperty = BindableProperty.Create(nameof(VideoView), typeof(VideoView), + typeof(PlaybackControls), + propertyChanged: (bindable, oldValue, newValue) => ((PlaybackControls)bindable).Manager.VideoView = (IVideoControl)newValue); + + /// + /// Gets or sets the associated . + /// + /// It is only useful to set this property for the aspect ratio feature. + public VideoView? VideoView + { + get => (VideoView)GetValue(VideoViewProperty); + set => SetValue(VideoViewProperty, value); + } + + /// + /// Identifies the dependency property. + /// + public static readonly BindableProperty LockButtonStyleProperty = BindableProperty.Create(nameof(LockButtonStyle), typeof(Style), + typeof(PlaybackControls)); + /// + /// Gets or sets the Lock button style. + /// + public Style? LockButtonStyle + { + get => (Style)GetValue(LockButtonStyleProperty); + set => SetValue(LockButtonStyleProperty, value); + } + + /// + /// Identifies the dependency property. + /// + public static readonly BindableProperty AspectRatioButtonStyleProperty = BindableProperty.Create(nameof(AspectRatioButtonStyle), typeof(Style), + typeof(PlaybackControls)); + /// + /// Gets or sets the aspect ratio button style. + /// + public Style? AspectRatioButtonStyle + { + get => (Style)GetValue(AspectRatioButtonStyleProperty); + set => SetValue(AspectRatioButtonStyleProperty, value); + } + + /// + /// Identifies the dependency property. + /// + public static readonly BindableProperty RewindButtonStyleProperty = BindableProperty.Create(nameof(RewindButtonStyle), typeof(Style), + typeof(PlaybackControls)); + /// + /// Gets or sets the rewind button style. + /// + public Style? RewindButtonStyle + { + get => (Style)GetValue(RewindButtonStyleProperty); + set => SetValue(RewindButtonStyleProperty, value); + } + + /// + /// Identifies the dependency property. + /// + public static readonly BindableProperty SeekButtonStyleProperty = BindableProperty.Create(nameof(SeekButtonStyle), typeof(Style), + typeof(PlaybackControls)); + /// + /// Gets or sets the rewind button style. + /// + public Style? SeekButtonStyle + { + get => (Style)GetValue(SeekButtonStyleProperty); + set => SetValue(SeekButtonStyleProperty, value); + } + + /// + /// Identifies the dependency property. + /// + public static readonly BindableProperty ButtonBarStartAreaProperty = BindableProperty.Create(nameof(ButtonBarStartArea), typeof(View), + typeof(PlaybackControls)); + /// + /// Gets or sets the view in the button bar start area. + /// + public View ButtonBarStartArea + { + get => (View)GetValue(ButtonBarStartAreaProperty); + set => SetValue(ButtonBarStartAreaProperty, value); + } + + /// + /// Identifies the dependency property. + /// + public static readonly BindableProperty ButtonBarEndAreaProperty = BindableProperty.Create(nameof(ButtonBarEndArea), typeof(View), + typeof(PlaybackControls)); + /// + /// Gets or sets the view in the button bar end area. + /// + public View ButtonBarEndArea + { + get => (View)GetValue(ButtonBarEndAreaProperty); + set => SetValue(ButtonBarEndAreaProperty, value); + } + + /// + /// Identifies the dependency property. + /// + public static readonly BindableProperty LibVLCProperty = BindableProperty.Create(nameof(LibVLC), typeof(LibVLC), typeof(PlaybackControls), + propertyChanged: LibVLCPropertyChanged); + /// + /// Gets or sets the instance. + /// + public LibVLC LibVLC + { + get => (LibVLC)GetValue(LibVLCProperty); + set => SetValue(LibVLCProperty, value); + } + + /// + /// Identifies the dependency property. + /// + public static readonly BindableProperty MediaPlayerProperty = BindableProperty.Create(nameof(MediaPlayer), + typeof(LibVLCSharp.Shared.MediaPlayer), typeof(PlaybackControls), propertyChanged: MediaPlayerPropertyChanged); + /// + /// Gets or sets the instance. + /// + public LibVLCSharp.Shared.MediaPlayer MediaPlayer + { + get => (LibVLCSharp.Shared.MediaPlayer)GetValue(MediaPlayerProperty); + set => SetValue(MediaPlayerProperty, value); + } + + /// + /// Identifies the dependency property. + /// + public static readonly BindableProperty BufferingProgressProperty = BindableProperty.Create(nameof(BufferingProgress), typeof(double), + typeof(PlaybackControls)); + /// + /// Gets or sets a value corresponding to the buffering progress. + /// + public double BufferingProgress + { + get => (double)GetValue(BufferingProgressProperty); + set => SetValue(BufferingProgressProperty, value); + } + + /// + /// Identifies the dependency property. + /// + public static readonly BindableProperty ErrorMessageProperty = BindableProperty.Create(nameof(ErrorMessage), typeof(string), + typeof(PlaybackControls)); + /// + /// Gets the last error message. + /// + public string? ErrorMessage + { + get => (string)GetValue(ErrorMessageProperty); + private set => SetValue(ErrorMessageProperty, value); + } + + /// + /// Identifies the dependency property. + /// + public static readonly BindableProperty KeepScreenOnProperty = BindableProperty.Create(nameof(KeepScreenOn), typeof(bool), + typeof(PlaybackControls), true, propertyChanged: KeepScreenOnPropertyChangedAsync); + /// + /// Gets or sets a value indicating whether the screen must be kept on when playing. + /// + public bool KeepScreenOn + { + get => (bool)GetValue(KeepScreenOnProperty); + set => SetValue(KeepScreenOnProperty, value); + } + + /// + /// Identifies the dependency property. + /// + public static readonly BindableProperty PositionProperty = BindableProperty.Create(nameof(Position), typeof(TimeSpan), + typeof(PlaybackControls)); + /// + /// Gets or sets the playback position within the media. + /// + public TimeSpan Position + { + get => (TimeSpan)GetValue(PositionProperty); + set => SetValue(PositionProperty, value); + } + + /// + /// Identifies the dependency property. + /// + public static readonly BindableProperty ResourceManagerProperty = BindableProperty.Create(nameof(ResourceManager), typeof(ResourceManager), + typeof(PlaybackControls)); + /// + /// Gets or sets the resource manager to localize strings. + /// + public ResourceManager ResourceManager + { + get => (ResourceManager)GetValue(ResourceManagerProperty) ?? Strings.ResourceManager; + set => SetValue(ResourceManagerProperty, value); + } + + /// + /// Identifies the dependency property. + /// + public static readonly BindableProperty ShowAndHideAutomaticallyProperty = BindableProperty.Create(nameof(ShowAndHideAutomatically), + typeof(bool), typeof(PlaybackControls), true, + propertyChanged: (bindable, oldValue, newValue) => ((PlaybackControls)bindable).OnShowAndHideAutomaticallyPropertyChanged()); + /// + /// Gets or sets a value that indicates whether the controls are shown and hidden automatically. + /// + public bool ShowAndHideAutomatically + { + get => (bool)GetValue(ShowAndHideAutomaticallyProperty); + set => SetValue(ShowAndHideAutomaticallyProperty, value); + } + + /// + /// Identifies the dependency property. + /// + public static readonly BindableProperty IsLockButtonVisibleProperty = BindableProperty.Create(nameof(IsLockButtonVisible), typeof(bool), + typeof(PlaybackControls), true); + /// + /// Gets or sets a value that indicates whether the lock button is shown. + /// + public bool IsLockButtonVisible + { + get => (bool)GetValue(IsLockButtonVisibleProperty); + set => SetValue(IsLockButtonVisibleProperty, value); + } + + /// + /// Identifies the dependency property. + /// + public static readonly BindableProperty IsTracksButtonVisibleProperty = BindableProperty.Create(nameof(IsTracksButtonVisible), typeof(bool), + typeof(PlaybackControls), true); + /// + /// Gets or sets a value that indicates whether the tracks button is shown. + /// + public bool IsTracksButtonVisible + { + get => (bool)GetValue(IsTracksButtonVisibleProperty); + set => SetValue(IsTracksButtonVisibleProperty, value); + } + + /// + /// Identifies the dependency property. + /// + public static readonly BindableProperty IsCastButtonVisibleProperty = BindableProperty.Create(nameof(IsCastButtonVisible), typeof(bool), + typeof(PlaybackControls), true, propertyChanged: IsCastButtonVisiblePropertyChangedAsync); + /// + /// Gets or sets a value indicating whether the cast button is shown. + /// + public bool IsCastButtonVisible + { + get => (bool)GetValue(IsCastButtonVisibleProperty); + set => SetValue(IsCastButtonVisibleProperty, value); + } + + /// + /// Identifies the dependency property. + /// + public static readonly BindableProperty IsPlayPauseButtonVisibleProperty = BindableProperty.Create(nameof(IsPlayPauseButtonVisible), + typeof(bool), typeof(PlaybackControls), true, propertyChanged: IsPlayPauseButtonVisiblePropertyChanged); + /// + /// Gets or sets a value indicating whether the play/pause button is shown. + /// + public bool IsPlayPauseButtonVisible + { + get => (bool)GetValue(IsPlayPauseButtonVisibleProperty); + set => SetValue(IsPlayPauseButtonVisibleProperty, value); + } + + /// + /// Identifies the dependency property. + /// + public static readonly BindableProperty IsSeekEnabledProperty = BindableProperty.Create(nameof(IsSeekEnabled), typeof(bool), + typeof(PlaybackControls), true); + /// + /// Gets or sets a value that indicates whether a user can use the seek bar to find a location in the media. + /// + public bool IsSeekEnabled + { + get => (bool)GetValue(IsSeekEnabledProperty); + set => SetValue(IsSeekEnabledProperty, value); + } + + /// + /// Identifies the dependency property. + /// + public static readonly BindableProperty IsSeekBarVisibleProperty = BindableProperty.Create(nameof(IsSeekBarVisible), typeof(bool), + typeof(PlaybackControls), true); + /// + /// Gets or sets a value that indicates whether the seek bar is shown. + /// + public bool IsSeekBarVisible + { + get => (bool)GetValue(IsSeekBarVisibleProperty); + set => SetValue(IsSeekBarVisibleProperty, value); + } + + /// + /// Identifies the dependency property. + /// + public static readonly BindableProperty IsStopButtonVisibleProperty = BindableProperty.Create(nameof(IsStopButtonVisible), typeof(bool), + typeof(PlaybackControls)); + /// + /// Gets or sets a value that indicates whether the stop button is shown. + /// + public bool IsStopButtonVisible + { + get => (bool)GetValue(IsStopButtonVisibleProperty); + set => SetValue(IsStopButtonVisibleProperty, value); + } + + /// + /// Identifies the dependency property. + /// + public static readonly BindableProperty IsAspectRatioButtonVisibleProperty = BindableProperty.Create(nameof(IsAspectRatioButtonVisible), + typeof(bool), typeof(PlaybackControls), true); + /// + /// Gets or sets a value indicating whether the aspect ratio button is shown. + /// + public bool IsAspectRatioButtonVisible + { + get => (bool)GetValue(IsAspectRatioButtonVisibleProperty); + set => SetValue(IsAspectRatioButtonVisibleProperty, value); + } + + /// + /// Identifies the dependency property. + /// s + public static readonly BindableProperty IsRewindButtonVisibleProperty = BindableProperty.Create(nameof(IsRewindButtonVisible), + typeof(bool), typeof(PlaybackControls), true); + /// + /// Gets or sets a value indicating whether the rewind button is shown. + /// + public bool IsRewindButtonVisible + { + get => (bool)GetValue(IsRewindButtonVisibleProperty); + set => SetValue(IsRewindButtonVisibleProperty, value); + } + + /// + /// Identifies the dependency property. + /// + public static readonly BindableProperty IsSeekButtonVisibleProperty = BindableProperty.Create(nameof(IsSeekButtonVisible), + typeof(bool), typeof(PlaybackControls), true); + /// + /// Gets or sets a value indicating whether the seek button is shown. + /// + public bool IsSeekButtonVisible + { + get => (bool)GetValue(IsSeekButtonVisibleProperty); + set => SetValue(IsSeekButtonVisibleProperty, value); + } + + /// + /// Called when the property has changed. + /// + protected override void OnParentSet() + { + base.OnParentSet(); + if (Parent != null && !Initialized) + { + Initialized = true; + OnApplyCustomTemplate(); + } + } + + private void OnApplyCustomTemplate() + { + TracksButton = SetClickEventHandler(nameof(TracksButton), TracksButton_Clicked); + CastButton = SetClickEventHandler(nameof(CastButton), CastButton_ClickedAsync); + PlayPauseButton = SetClickEventHandler(nameof(PlayPauseButton), PlayPauseButton_Clicked); + SetClickEventHandler("StopButton", StopButton_Clicked); + SetClickEventHandler("LockButton", LockButton_ClickedAsync); + SetClickEventHandler("AspectRatioButton", AspectRatioButton_ClickedAsync); + ControlsPanel = this.FindChild(nameof(ControlsPanel)); + ButtonBar = this.FindChild(nameof(ButtonBar)); + UnLockControlsPanel = this.FindChild(nameof(UnLockControlsPanel)); + TracksOverlayView = this.FindChild(nameof(TracksOverlayView)); + SwipeToUnLock = this.FindChild(nameof(SwipeToUnLock)); + SeekBar = this.FindChild(nameof(SeekBar)); + AudioTracksListView = this.FindChild(nameof(AudioTracksListView)); + if (AudioTracksListView != null) + AudioTracksListView.ItemTapped += AudioTracksItemTapped; + VideoTracksListView = this.FindChild(nameof(VideoTracksListView)); + if (VideoTracksListView != null) + VideoTracksListView.ItemTapped += VideoTracksItemTapped; + SubtitlesTracksListView = this.FindChild(nameof(SubtitlesTracksListView)); + if (SubtitlesTracksListView != null) + SubtitlesTracksListView.ItemTapped += SubtitlesTracksItemTapped; + TrackBarLabel = this.FindChild