beeware / toga

A Python native, OS native GUI toolkit.
https://toga.readthedocs.io/en/latest/
BSD 3-Clause "New" or "Revised" License
4.35k stars 670 forks source link

[Question] Is it possible to embed a VLC player instance inside a Toga widget? #1240

Closed radiolondra closed 3 years ago

radiolondra commented 3 years ago

Question preamble

Using python-vlc and Tkinter I can easily do this job in 5 steps (with VLC installed or needed libs in the right paths):

Step 1 Simply create a panel, inside the app's main window, where the VLC player will play the video:

self.videopanel = ttk.Frame(self.parent)
self.canvas = Tk.Canvas(self.videopanel).pack(fill=Tk.BOTH,expand=1)
self.videopanel.pack(fill=Tk.BOTH,expand=1)

Step 2 Then create the VLC player:

self.Instance = vlc.Instance()
self.player = self.Instance.media_player_new()

Step 3 set the media I want to play:

media = self.Instance.media_new(<whatever local video file path or URL>)
self.player.set_media(media)

Step 4 set the window's id where to render the VLC player output (this is the subject of my question):

if platform.system() == 'Windows':
    self.player.set_hwnd(self.videopanel.winfo_id())
else ... # other OSes hwnd setting

Step 5 and finally play the video inside the videopanel: self.player.play()

Now the Question

The most important step is Step 4, where we assign the videopanel id to the vlc player hwnd. Can I do the same using Toga?

freakboy3742 commented 3 years ago

It should be possible; although you may need to do some poking around for work out the details.

Toga has a 3 layer architecture:

So - the HWND will be a property of the native layer on Windows.

If you start with a Toga Box as the widget you're trying to fill with the video content, then the native implementation on WinForms will be a Panel. You can then use the .Handle property of winforms.Panel to get to the HWND for the window.

So - if you instantiate box = toga.Box(), then box._impl.native.Handle will give you the Handle of the native window (on Winforms, anyway; there will be something analogous on other platforms).

I'm not 100% sure what manipulation you will need to do to turn the value provided by Handle into something that VLC will accept; if you're lucky, it might just be the value of Handle. Combine this with some platform-checking code (like you have in your step 4), and you should be done.

The pro-level step would be take that code, and wrap it up as a widget that handles the platform-checking for you. That way you could expose a "VLCView" widget (or similar), and let Toga manage the platform identification. Two examples of this are toga-chart, and the Bitmap widget in the Yorkshire4 code.

If you get this working, let us know - it would be good to capture this knowledge as documentation, (or better yet, as a widget). We probably won't add a VLC widget to the Toga core because of the external library dependency - but I can definitely see a place for it as a standalone widget (like toga-chart).

radiolondra commented 3 years ago

I wrote a simple test app which works fine indeed (Windows 10):

import toga
import vlc
from toga.style.pack import CENTER, COLUMN, ROW, Pack

class Player(toga.App):
    def startup(self):
        self.main_window = toga.MainWindow(title=self.name)

        self.player_panel = toga.Box(style=Pack(flex=1))
        self.player_hwnd = self.player_panel._impl.native.Handle # IntPtr

        self.Instance = vlc.Instance()
        self.player = self.Instance.media_player_new()
        self.player.set_hwnd(str(self.player_hwnd))

        self.filename = "https://woolyss.com/f/caminandes-1-llama-drama-av1-opus.webm"

        box = toga.Box(
            children=[
                toga.Box(
                    children=[
                        toga.Button('Go', on_press=self.play_video, style=Pack(width=50, padding_left=5)),
                    ],
                    style=Pack(
                        direction=ROW,
                        alignment=CENTER,
                        padding=5,
                    )
                ),
                self.player_panel,
            ],
            style=Pack(
                direction=COLUMN
            )
        )

        self.main_window.content = box

        # Show the main window
        self.main_window.show()

    def play_video(self, widget):
        media = self.Instance.media_new(self.filename)
        self.player.set_media(media)
        self.player.play() # hit the player button

def main():
    return Player('VLC Player Test', 'org.togatest.vlc')

if __name__ == '__main__':
    main().main_loop()

This is obviously just the tip of the iceberg... more work and tests are needed to implement the same at least for MacOS and Linux. Peraphs some headaches more to implement this in Android and iOS.

Just a question: does Toga offer a native video player?

freakboy3742 commented 3 years ago

Nice work! Hopefully the macOS and Linux versions won't be that much more difficult - the only piece that will be significantly different is what comes after .native on each platform. Android and iOS support will be very difficult, as you'll need to manage getting the VLC libraries in place.

Toga doesn't currently have a native video player - but that's definitely on our wish list.

radiolondra commented 3 years ago

I forgot to mention (for who is interested) that, to have the code above working, you need to have VLC installed. You can use VLC portable version too (and put it wherever you want), but you need to have the VLC folder in PATH and create the following environ variables:

PYTHON_VLC_LIB_PATH - the path and filename of the libvlc library (for example in Windows: C:\vlc\App\vlc\libvlc.dll)
PYTHON_VLC_MODULE_PATH - the path of the VLC executable (for example in Windows: C:\vlc\App\vlc)

In this way, you could distribute the VLC portable folder together with your app. The app setup could create and set the new environment variables and add the distributed VLC folder to the target machine PATH.