Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for NativeControlHost inside a UserControl #6237

Closed
mfkl opened this issue Jul 12, 2021 · 12 comments
Closed

Add support for NativeControlHost inside a UserControl #6237

mfkl opened this issue Jul 12, 2021 · 12 comments

Comments

@mfkl
Copy link

mfkl commented Jul 12, 2021

Is your feature request related to a problem? Please describe.

https://stackoverflow.com/questions/68292849/avaloniaui-cannot-embed-videoview-control-libvlcsharp-in-a-usercontrol

It seems the native window handle is not surfaced when the NativeControlHost is positioned inside a UserControl.

Describe the solution you'd like

Essentially a more advanced version of https://github.com/AvaloniaUI/Avalonia/blob/master/samples/interop/NativeEmbedSample/EmbedSample.cs

Being able to do so would improve the NativeControlHost support and help building native-based reusable components.

Describe alternatives you've considered

Other than that, the view is working fine on its own https://code.videolan.org/videolan/LibVLCSharp/-/blob/3.x/src/LibVLCSharp.Avalonia/VideoView.cs

Additional context

@mysteryx93
Copy link

I'm looking into these same topics to attempt an integration of MPV media player

Note about embedding methods

Due to the various platform-specific behavior and problems (in particular on OSX), using the render API is currently recommended over window embedding.

This wID integration should work in Windows and Linux but may cause troubles under MacOS. Does VLC support the OpenGL Render API that MPV provides? Have you done any attempts with it to see whether it works and see any downsides?

Currently, NativeControlHost seems to have many issues and limitations, and at the end of it, it still won't work as expected on MacOS.

@kekekeks
Copy link
Member

kekekeks commented Dec 1, 2021

After some investigation it turned out that the problem is caused by API misuse by LibVLCSharp. It tries to create the player before VideoView is attached to visual tree and before CreateNativeControlCore is called.

It works for Window simply because XAML loader adds visuals to their parents before setting properties (see UsableDuringInitializationAttribute). While Window is assembled it's object graph is already rooted into the Window itself. When UserControl constructor initiates XAML loader there is no Window to attach to. So CreateNativeControlCore is called after UserControl's constructor has finished and UserControl instance gets attached to the Window.

So it's LibVLCSharp's bug.

@mfkl
Copy link
Author

mfkl commented Dec 2, 2021

After some investigation it turned out that the problem is caused by API misuse by LibVLCSharp. It tries to create the player before VideoView is attached to visual tree and before CreateNativeControlCore is called.

The MediaPlayer is independent from the view, in the sense that the user can create it and set it on the VideoView whenever they want. LibVLCSharp does not create it.

Anyway, closing this for now. Thanks for your investigations. I will have another look and see if the LVS.Avalonia VideoView can be adapted, or if the problem can only be fixed from user code (e.g. the code from the stackoverfow question).

Cheers!

@mfkl mfkl closed this as completed Dec 2, 2021
@SuperJMN
Copy link
Contributor

SuperJMN commented Dec 2, 2021

Hi everyone! I was asked to share this piece of code. It's a full replacement of the VideoView that should fix this issue. I've tested it and works acceptably well.

using System;
using System.Reactive.Linq;
using System.Reactive.Subjects;
using System.Runtime.InteropServices;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Data;
using Avalonia.Platform;
using CSharpFunctionalExtensions;
using LibVLCSharp.Shared;

/// <summary>
///     Avalonia VideoView for Windows, Linux and Mac.
/// </summary>
public class VideoView : NativeControlHost
{
    public static readonly DirectProperty<VideoView, Maybe<MediaPlayer>> MediaPlayerProperty =
        AvaloniaProperty.RegisterDirect<VideoView, Maybe<MediaPlayer>>(
            nameof(MediaPlayer),
            o => o.MediaPlayer,
            (o, v) => o.MediaPlayer = v.GetValueOrDefault(),
            defaultBindingMode: BindingMode.TwoWay);

    private readonly IDisposable attacher;
    private readonly BehaviorSubject<Maybe<MediaPlayer>> mediaPlayers = new(Maybe<MediaPlayer>.None);
    private readonly BehaviorSubject<Maybe<IPlatformHandle>> platformHandles = new(Maybe<IPlatformHandle>.None);

    public VideoView()
    {
        attacher = platformHandles.WithLatestFrom(mediaPlayers).Subscribe(x =>
        {
            var playerAndHandle = from h in x.First
                from mp in x.Second
                select new {n = h, m = mp};

            playerAndHandle.Execute(a => a.m.SetHandle(a.n));
        });
    }

    public MediaPlayer? MediaPlayer
    {
        get => mediaPlayers.Value.GetValueOrDefault();
        set => mediaPlayers.OnNext(value);
    }


    /// <inheritdoc />
    protected override IPlatformHandle CreateNativeControlCore(IPlatformHandle parent)
    {
        var handle = base.CreateNativeControlCore(parent);
        platformHandles.OnNext(Maybe<IPlatformHandle>.From(handle));
        return handle;
    }

    /// <inheritdoc />
    protected override void DestroyNativeControlCore(IPlatformHandle control)
    {
        attacher.Dispose();
        base.DestroyNativeControlCore(control);
        mediaPlayers.Value.Execute(MediaPlayerExtensions.DisposeHandle);
    }
}

public static class MediaPlayerExtensions
{
    public static void DisposeHandle(this MediaPlayer player)
    {
        if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
            player.Hwnd = IntPtr.Zero;
        else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
            player.XWindow = 0;
        else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) player.NsObject = IntPtr.Zero;
    }

    public static void SetHandle(this MediaPlayer player, IPlatformHandle handle)
    {
        if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
            player.Hwnd = handle.Handle;
        else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
            player.XWindow = (uint) handle.Handle;
        else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) player.NsObject = handle.Handle;
    }
}

Please, be sure the add the CSharpFunctionalExtensions Nuget package.

@radiolondra
Copy link

About questions on How-To:

  • Embed LibVLCSharp Avalonia VLC VideoView in Avalonia Window
  • Embed LibVLCSharp Avalonia VLC VideoView in Avalonia UserControl
  • Put and use an Avalonia UserControl on top of VideoView (VLC MediaPlayer), for example, a set of buttons/controls to control the VLC MediaPlayer

you can find my answer here:
#2571 (comment)
https://stackoverflow.com/a/71696308/4973374

Here the links to the 2 Proof of Concept repositories: AvaVLCWindow, AvaVLCControl

@mfkl
Copy link
Author

mfkl commented Apr 1, 2022

Hi @radiolondra, good work! Would you like to contribute this to libvlcsharp so everybody can benefit?
Edit: Just saw you are using the video callbacks.. you lose GPU decoding with this API :-/

@radiolondra
Copy link

radiolondra commented Apr 1, 2022

Hi @mfkl,

Just saw you are using the video callbacks.. you lose GPU decoding with this API

I uploaded some old and not used test files by mistake in the LibVLCSharp.Avalonia project in both AvaVLCWindow and AvaVLCControl, where you found the usage of video callbacks. I just uploaded a clean solution for both repos.

Would you like to contribute this to libvlcsharp so everybody can benefit?

Martin, you could add the solutions in the right place better than me, or you could simply add links to the following posts:
How-to post
Avalonia Video Player post
Stackoverflow post
Github POC repository (VLC and overlay in Avalonia Window)
Github POC repository (VLC and overlay in Avalonia UserControl)

Anyway remember that this is a POC and maybe it needs a bit of more work. Also it has to be tested on other platforms too (I tested it on Windows 10 only right now).

Thanks for your attention.

EDIT:
Added references in Videolan LibVlcSharp:
https://code.videolan.org/videolan/LibVLCSharp/-/issues/525#note_317266
https://code.videolan.org/videolan/LibVLCSharp/-/issues/408#note_317267

EDIT:
Successfully tested on Kubuntu 18.04

AvaVLCWindow-Kubuntux64

Successfully tested on MacOS 10.13 (High Sierra)

AvaVLCWindow-MacOS

Next step test: Raspberry Pi

@radiolondra
Copy link

Test on Raspberry Pi (Raspbian Stretch)
926MB RAM

The generated on-top window has no transparency and opacity is not respected. I tried every possible combinations of Background and TransparencyLevelHint without any success. Maybe Avalonia has some issues for linux-arm or maybe there are some other settings I don't know.

AvaVLCWindow-RaspberryPi

Having 926MB of RAM only and just 1 CPU, the video is played in jerks. Anyway, it works BUT the transparency and opacity issues.

@radiolondra
Copy link

New test on Raspberry Pi (DietPi with MATE or Xfce)

It works fine!

Transparency and Opacity are fully honored.

In my opinion, the problem is not Avalonia vs Raspberry, the problem is what is installed or not in Raspberry.

With DietPi OS and MATE (or Xfce) as X, the sample works perfectly. And playback is smooth without jerks, interruptions or whatever.

As root, I installed libx11-dev, libvlc-dev and vlc, copied the published sample (built in Windows 10 with dotnet publish -c release -f net5.0 -r linux-arm), chmod 777 the whole folder content and ran the sample. It has to be ran as root or with sudo.

rpi
rpi_000

@radiolondra
Copy link

For anyone is interested:

A sample to apply in the real world the changes I did in LibVLCSharp.Avalonia

I created YAMP2 repository (Yet-Another-Media-Player v2) here, which fully uses my new LibVLCSharp.Avalonia.Unofficial lib.

To make tests, build it by yourselves and run.

Enjoy.

@radiolondra
Copy link

radiolondra commented Apr 27, 2022

The new release of the Unofficial Avalonia LibVLCSharp, allows to display multiple draggable controls over the scene of a video played with LibVLCSharp in an Avalonia app.

For anyone is interested.
Enjoy.

output.mp4

@radiolondra
Copy link

All the links updated (latest libs and samples versions)

Avalonia Unofficial LibVLCSharp Links

VLC video player inside an Avalonia Window/UserControl embedding single static customizable control (e.g.player buttons)
LibVLCSharp.Avalonia.Unofficial

VLC video player inside an Avalonia Window/UserControl embedding multiple draggable customizable controls (e.g.player buttons, images,...)
LibVLCSharp.Avalonia.Unofficial.UCanvas

LibVLCSharp.Avalonia.Unofficial Samples
LibVLCSharp.Avalonia.Unofficial.Samples

LibVLCSharp.Avalonia.Unofficial.UCanvas sample
LibVLCSharp.Avalonia.Unofficial.UCanvas.Samples

YAMP 2 - Open source video player sample using LibVLCSharp.Avalonia.Unofficial library
Github sources
Watch YAMP2 in action

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

5 participants