Skip to content

[Suggestion] Windows API example "winapivlc.py" (for the examples folder) #305

Description

@59de44955ebd

Hi,

I realized that in the examples folder there are examples e.g. for pyobjc and gtk, but none for the (plain) Windows API, i.e. no example that only uses ctypes/libffi in Windows (without depending on third-party modules like PyWin32). In case you want to add such an example, below a 1:1 port of the PyQt5 example pyqt5vlc.py to plain Windows API. Attached a screenshot of it running in Windows 11.

Image

Example winapivlc.py

"""
A simple example for VLC python bindings using the plain Windows API.

Author: https://github.com/59de44955ebd, Berlin
Date: 25.08.2026
"""
from ctypes import *
from ctypes.wintypes import *
import sys

import vlc

########################################
# Additional used Windows API types
########################################
_is_64_bit = sys.maxsize > 2**32
LONG_PTR = c_longlong if _is_64_bit else c_long
UINT_PTR = WPARAM
WNDPROC = WINFUNCTYPE(LONG_PTR, HWND, UINT, WPARAM, LPARAM)

########################################
# Used Windows API structs
########################################
class MINMAXINFO(Structure):
    _fields_ = [
        ("ptReserved", POINT),
        ("ptMaxSize", POINT),
        ("ptMaxPosition", POINT),
        ("ptMinTrackSize", POINT),
        ("ptMaxTrackSize", POINT),
    ]

class OPENFILENAMEW(Structure):
    def __init__(self, *args, **kwargs):
        super(OPENFILENAMEW, self).__init__(*args, **kwargs)
        self.lStructSize = sizeof(OPENFILENAMEW)
    _fields_ = (
        ("lStructSize", DWORD),
        ("hwndOwner", HWND),
        ("hInstance", HINSTANCE),
        ("lpstrFilter", LPWSTR),
        ("lpstrCustomFilter", LPWSTR),
        ("nMaxCustFilter", DWORD),
        ("nFilterIndex", DWORD),
        ("lpstrFile", LPWSTR),
        ("nMaxFile", DWORD),
        ("lpstrFileTitle", LPWSTR),
        ("nMaxFileTitle", DWORD),
        ("lpstrInitialDir", LPCWSTR),
        ("lpstrTitle", LPCWSTR),
        ("Flags", DWORD),
        ("nFileOffset", WORD),
        ("nFileExtension", WORD),
        ("lpstrDefExt", LPCWSTR),
        ("lCustData", LPARAM),
        ("lpfnHook", LPVOID),  # LPOFNHOOKPROC, not used
        ("lpTemplateName", LPCWSTR),
        ("pvReserved", LPVOID),
        ("dwReserved", DWORD),
        ("FlagsEx", DWORD),
    )

class WNDCLASSEXW(Structure):
    def __init__(self, *args, **kwargs):
        super(WNDCLASSEXW, self).__init__(*args, **kwargs)
        self.cbSize = sizeof(self)
    _fields_ = [
        ("cbSize", UINT),
        ("style", UINT),
        ("lpfnWndProc", WNDPROC),
        ("cbClsExtra", INT),
        ("cbWndExtra", INT),
        ("hInstance", HANDLE),
        ("hIcon", HANDLE),
        ("hCursor", HANDLE),
        ("hbrBackground", HANDLE),
        ("lpszMenuName", LPCWSTR),
        ("lpszClassName", LPCWSTR),
        ("hIconSm", HANDLE)
    ]

########################################
# Used Windows API functions
########################################
comdlg32 = windll.comdlg32
comdlg32.GetOpenFileNameW.argtypes = (POINTER(OPENFILENAMEW),)

gdi32 = windll.gdi32
gdi32.CreateFontW.argtypes = (INT, INT, INT, INT, INT, DWORD, DWORD, DWORD, DWORD, DWORD, DWORD, DWORD, DWORD, LPCWSTR)
gdi32.CreateFontW.restype = HFONT
gdi32.CreateSolidBrush.argtypes = (COLORREF,)
gdi32.CreateSolidBrush.restype = HBRUSH

user32 = windll.user32
user32.AppendMenuW.argtypes = (HWND, UINT, UINT_PTR, LPCWSTR)
user32.CreateWindowExW.argtypes = (DWORD, LPCWSTR, LPCWSTR, DWORD, INT, INT, INT, INT, HWND, HMENU, HINSTANCE, LPVOID)
user32.DefWindowProcW.argtypes = (HWND, UINT, WPARAM, LPARAM)
user32.DispatchMessageW.argtypes = (LPMSG,)
user32.EnableWindow.argytpes = (HWND, BOOL)
user32.GetMessageW.argtypes = (LPMSG, HWND, UINT, UINT)
user32.GetWindowRect.argtypes = (HWND, LPRECT)
user32.InvalidateRect.argtypes = (HWND, LPRECT, BOOL)
user32.IsDialogMessageW.argtypes = (HWND, LPMSG)
user32.IsDialogMessageW.restype = BOOL
user32.SendMessageW.argtypes = (HWND, UINT, LPVOID, LPVOID)
user32.SetWindowPos.argtypes = (HWND, LONG_PTR, INT, INT, INT, INT, UINT)
user32.TranslateMessage.argtypes = (LPMSG,)

########################################
# Used Windows API constants
########################################
ANSI_CHARSET = 0
BN_CLICKED = 0
CLIP_DEFAULT_PRECIS = 0
COLOR_3DFACE = 15
CS_HREDRAW = 2
CS_VREDRAW = 1
CW_USEDEFAULT = -2147483648
DEFAULT_PITCH = 0
DEFAULT_QUALITY = 0
FF_DONTCARE = 0
FW_DONTCARE = 0
IDC_ARROW = 32512
MAX_PATH = 260
MF_POPUP = 16
MF_STRING = 0
OFN_ENABLESIZING = 8388608
OFN_PATHMUSTEXIST = 2048
OUT_TT_PRECIS = 4
SWP_NOMOVE = 2
SWP_NOSIZE = 1
SW_SHOWNORMAL = 1
TBM_SETPAGESIZE = 1045
TBM_SETPOS = 1029
TBM_SETRANGE = 1030
TBM_SETRANGEMAX = 1032
TB_ENDTRACK = 8
TB_PAGEDOWN = 3
TB_PAGEUP = 2
WC_BUTTON = "Button"
WC_TRACKBAR = "msctls_trackbar32"
WM_CLOSE = 16
WM_COMMAND = 273
WM_GETMINMAXINFO = 36
WM_HSCROLL = 276
WM_SETFONT = 48
WM_SIZE = 5
WM_TIMER = 275
WS_CHILD = 1073741824
WS_DISABLED = 134217728
WS_OVERLAPPEDWINDOW = 13565952
WS_VISIBLE = 268435456


class Player():
    """A simple Media Player using VLC and Windows API"""

    def __init__(self, master=None):

        # Create a "menubar"
        self.h_menu = user32.CreateMenu()
        hmenu_child = user32.CreateMenu()
        user32.AppendMenuW(self.h_menu, MF_POPUP, hmenu_child, "&File")
        user32.AppendMenuW(hmenu_child, MF_STRING, 1, "Load Video")
        user32.AppendMenuW(hmenu_child, MF_STRING, 2, "Close App")

        # Create the main application window

        def _window_proc_callback(hwnd, msg, wparam, lparam):

            if msg == WM_CLOSE:
                self.quit()

            elif msg == WM_TIMER:
                self.update_ui()

            elif msg == WM_SIZE:
                width, height = lparam & 0xFFFF, (lparam >> 16) & 0xFFFF
                user32.SetWindowPos(self.hwnd_video_frame, 0, 0, 0, width, height - 70, SWP_NOMOVE)
                user32.SetWindowPos(self.hwnd_button_play, 0, 10, height - 33, 0, 0, SWP_NOSIZE)
                user32.SetWindowPos(self.hwnd_button_stop, 0, 100, height - 33, 0, 0, SWP_NOSIZE)
                user32.SetWindowPos(self.hwnd_trackbar_position, 0, 7, height - 60, width - 14, 23, 0)
                user32.SetWindowPos(self.hwnd_trackbar_volume, 0, width - 107, height - 33, 0, 0, SWP_NOSIZE)

                # Our main window's class has no CS_HREDRAW/CS_VREDRAW class styles,
                # because those would VLC make flicker when the window is resized.
                # Therefor we have to invalidate the non-VLC part of the UI (bottom rect)
                # manually to avoid visual artefacts.
                user32.InvalidateRect(hwnd, byref(RECT(0, height - 70, width, height)), 1)

            elif msg == WM_GETMINMAXINFO:
                mmi = cast(lparam, POINTER(MINMAXINFO)).contents
                mmi.ptMinTrackSize = POINT(320, 128)
                return 0

            elif msg == WM_HSCROLL:
                if lparam == self.hwnd_trackbar_position:
                    lo, hi, = wparam & 0xFFFF, (wparam >> 16) & 0xFFFF
                    if lo == TB_ENDTRACK:
                        return 0
                    if lo in (TB_PAGEDOWN, TB_PAGEUP):  # Click into slider
                        pt = POINT()
                        user32.GetCursorPos(byref(pt))
                        user32.MapWindowPoints(None, self.hwnd_trackbar_position, byref(pt), 1)
                        rc = RECT()
                        user32.GetWindowRect(self.hwnd_trackbar_position, byref(rc))
                        hi = int((pt.x - 10) / (rc.right - rc.left - 20) * 2000)
                        user32.SendMessageW(self.hwnd_trackbar_position, TBM_SETPOS, 1, hi)
                    self.set_position(hi)

                elif lparam == self.hwnd_trackbar_volume:
                    lo, hi, = wparam & 0xFFFF, (wparam >> 16) & 0xFFFF
                    if lo == TB_ENDTRACK:
                        return 0
                    if lo in (TB_PAGEDOWN, TB_PAGEUP):  # Click into slider
                        pt = POINT()
                        user32.GetCursorPos(byref(pt))
                        user32.MapWindowPoints(None, self.hwnd_trackbar_volume, byref(pt), 1)
                        rc = RECT()
                        user32.GetWindowRect(self.hwnd_trackbar_volume, byref(rc))
                        hi = int((pt.x - 10) / (rc.right - rc.left - 20) * 100)
                        user32.SendMessageW(self.hwnd_trackbar_volume, TBM_SETPOS, 1, hi)
                    self.set_volume(hi)

            elif msg == WM_COMMAND:
                command_id = WORD(wparam & 0xFFFF).value  # LOWORD(wparam)
                if lparam == 0:
                    if command_id == 1:
                        self.open_file()
                    elif command_id == 2:
                        self.quit()
                elif lparam == self.hwnd_button_play:
                    if command_id == BN_CLICKED:
                        self.play_pause()
                elif lparam == self.hwnd_button_stop:
                    if command_id == BN_CLICKED:
                        self.stop()

            return user32.DefWindowProcW(hwnd, msg, wparam, lparam)

        self.windowproc = WNDPROC(_window_proc_callback)

        newclass = WNDCLASSEXW()
        newclass.lpfnWndProc = self.windowproc
        newclass.lpszClassName = "MediaPlayer"
        newclass.hbrBackground = COLOR_3DFACE + 1
        newclass.hCursor = user32.LoadCursorW(None, IDC_ARROW)
        user32.RegisterClassExW(byref(newclass))

        hwnd_desktop = user32.GetDesktopWindow()
        rc = RECT()
        user32.GetClientRect(hwnd_desktop, byref(rc))
        cx, cy = 640, 480
        x, y = (rc.right - cx) // 2, (rc.bottom - cy) // 2

        self.hwnd = user32.CreateWindowExW(
            0,
            newclass.lpszClassName,
            "Media Player",
            WS_OVERLAPPEDWINDOW,
            x, y, cx, cy,
            0,
            self.h_menu,
            None, 0
        )

        # Create a basic vlc instance
        self.instance = vlc.Instance(["--quiet", "--no-osd"])

        self.media = None

        # Create an empty vlc media player
        self.mediaplayer = self.instance.media_player_new()

        self.create_ui()

        self.is_paused = False

        # The media player has to be "connected" to our VideoFrame
        self.mediaplayer.set_hwnd(self.hwnd_video_frame)

        user32.ShowWindow(self.hwnd, SW_SHOWNORMAL)

    def run(self):
        msg = MSG()
        while user32.GetMessageW(byref(msg), None, 0, 0):
            if user32.IsDialogMessage(self.hwnd, byref(msg)):
                continue
            user32.TranslateMessage(byref(msg))
            user32.DispatchMessageW(byref(msg))
        return 0

    def quit(self):
        user32.PostQuitMessage(0)

    def timer_start(self):
        user32.SetTimer(self.hwnd, 1, 100, 0)  # 1 is our timer id

    def timer_stop(self):
        user32.KillTimer(self.hwnd, 1)

    def create_ui(self):
        """Set up the user interface"""
        self.dummy_proc = WNDPROC(user32.DefWindowProcW)
        newclass = WNDCLASSEXW()
        newclass.lpfnWndProc = self.dummy_proc
        newclass.lpszClassName = "VideoFrame"
        newclass.hbrBackground = gdi32.CreateSolidBrush(0x000000)
        newclass.hCursor = user32.LoadCursorW(None, IDC_ARROW)
        user32.RegisterClassExW(byref(newclass))

        self.hwnd_video_frame = user32.CreateWindowExW(
            0,
            newclass.lpszClassName,
            "VideoFrame",
            WS_CHILD | WS_VISIBLE,
            0, 0, 0, 0,
            self.hwnd,
            None, None, 0
        )

        h_font_shell = gdi32.CreateFontW(
            -11, 0, 0, 0, FW_DONTCARE, 0, 0, 0, ANSI_CHARSET, OUT_TT_PRECIS,
            CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, DEFAULT_PITCH | FF_DONTCARE, 'Segoe UI'
        )

        self.hwnd_button_play = user32.CreateWindowExW(
            0,
            WC_BUTTON,
            "Play",
            WS_CHILD | WS_VISIBLE,
            0, 0, 80, 23,
            self.hwnd,
            None, None, 0
        )
        user32.SendMessageW(self.hwnd_button_play, WM_SETFONT, h_font_shell, 1)

        self.hwnd_button_stop = user32.CreateWindowExW(
            0,
            WC_BUTTON,
            "Stop",
            WS_CHILD | WS_VISIBLE,
            0, 0, 80, 23,
            self.hwnd,
            None, None, 0
        )
        user32.SendMessageW(self.hwnd_button_stop, WM_SETFONT, h_font_shell, 1)

        self.hwnd_trackbar_position = user32.CreateWindowExW(
            0,
            WC_TRACKBAR,
            "",
            WS_CHILD | WS_VISIBLE | WS_DISABLED,
            0, 0, 0, 0,
            self.hwnd,
            None, None, 0
        )
        user32.SendMessageW(self.hwnd_trackbar_position, TBM_SETRANGEMAX, 0, 2000)
        user32.SendMessageW(self.hwnd_trackbar_position, TBM_SETPAGESIZE, 0, 1)

        self.hwnd_trackbar_volume = user32.CreateWindowExW(
            0,
            WC_TRACKBAR,
            "",
            WS_CHILD | WS_VISIBLE,
            0, 0, 100, 23,
            self.hwnd,
            None, None, 0
        )
        user32.SendMessageW(self.hwnd_trackbar_volume, TBM_SETRANGEMAX, 0, 100)
        user32.SendMessageW(self.hwnd_trackbar_volume, TBM_SETPAGESIZE, 0, 1)
        user32.SendMessageW(self.hwnd_trackbar_volume, TBM_SETPOS, 1, self.mediaplayer.audio_get_volume())

    def play_pause(self):
        """Toggle play/pause status"""
        if self.mediaplayer.is_playing():
            self.mediaplayer.pause()
            user32.SetWindowTextW(self.hwnd_button_play, "Play")
            self.is_paused = True
            self.timer_stop()
        else:
            if self.mediaplayer.play() == -1:
                self.open_file()
                return

            self.mediaplayer.play()
            user32.SetWindowTextW(self.hwnd_button_play, "Pause")
            self.is_paused = False
            self.timer_start()
        user32.EnableWindow(self.hwnd_trackbar_position, 1)

    def stop(self):
        """Stop player"""
        self.mediaplayer.stop()
        user32.SetWindowTextW(self.hwnd_button_play, "Play")
        user32.EnableWindow(self.hwnd_trackbar_position, 0)

    def open_file(self):
        """Open a media file in a MediaPlayer"""
        file_buffer = create_unicode_buffer(MAX_PATH)
        ofn = OPENFILENAMEW()
        ofn.hwndOwner = self.hwnd
        ofn.lpstrTitle = "Choose Media File"
        ofn.lpstrFile = cast(file_buffer, LPWSTR)
        ofn.nMaxFile = MAX_PATH
        ofn.Flags = OFN_ENABLESIZING | OFN_PATHMUSTEXIST
        if not comdlg32.GetOpenFileNameW(byref(ofn)):
            return

        filename = file_buffer[:].split("\0", 1)[0]
        self.media = self.instance.media_new(filename)

        # Put the media in the media player
        self.mediaplayer.set_media(self.media)

        # Parse the metadata of the file
        self.media.parse()

        # Set the title of the track as window title
        user32.SetWindowTextW(self.hwnd, self.media.get_meta(0))

        self.play_pause()

    def set_volume(self, volume):
        """Set the volume"""
        self.mediaplayer.audio_set_volume(volume)

    def set_position(self, pos):
        """Set the movie position according to the position slider"""
        # Set the media position to where the slider was dragged
        self.timer_stop()
        self.mediaplayer.set_position(pos / 2000.0)
        self.timer_start()

    def update_ui(self):
        """Update the user interface"""
        # Set the slider"s position to its corresponding media position
        media_pos = int(self.mediaplayer.get_position() * 2000)
        user32.SendMessageW(self.hwnd_trackbar_position, TBM_SETPOS, 1, media_pos)

        # No need for a timer if nothing is played
        if not self.mediaplayer.is_playing():
            self.timer_stop()

            # After the video finished, the play button stills shows "Pause",
            # which is not the desired behavior of a media player.
            # This fixes that "bug".
            if not self.is_paused:
                self.stop()


def main():
    """Entry point for our simple vlc player"""
    player = Player()
    sys.exit(player.run())

if __name__ == "__main__":
    main()

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions