最近更新: 2011-06-28

Embed VLC video in your GTK app and control via DBus

VLC 這套媒體播放軟體的擴充性實在好用,在 Linux 桌面環境能玩的花樣更多。 自從它實作了 Freedesktop Media Player DBus 介面之後,我們只需要透過基本的 D-Bus 功能,就可操作它的播放動作。 再配合它的 --drawable-xid 指定呈像視窗,就可在我們的桌面應用軟體中,嵌入影片播放功能。 實現這個功能,並不需要撰寫複雜的程式碼。

欲由另一個程式控制 VLC 的播放行為,可利用 Freedesktop Media Player DBus 介面。關於此介面的說明,可參考 VLC D-Bus spec。 請留意這個 DBus 介面是一個通用介面,有許多 Linux 環境的媒體播放軟體也逐漸加入此介面的支援行列。 例如 banshee, rhythmbox, amarok 。所以你也可以透過同樣的 DBus 介面控制它們。 甚至只需要用 shell script 就可以實現一些簡單的小功能。 但若你要把播放的影像內容嵌入,則你必須再看那些媒體播放軟體是否提供你指定呈像視窗的選項。 VLC 就可透過 --drawable-xid 指定。

在此稍微說明一下指定呈像視窗所需的 XID 是什麼東西。 Linux 桌面環境目前主要是以 X11 協定為主要的 GUI 架構。在 X11 協定中,你所看到的每一個視覺控制項都有一個識別代號 — X11 稱之為 XID。 任何行程只要知道視覺控制項的 XID 為何,就可以對它送出控制命令。 而 VLC 播放影片時,影像所呈現的區域,其實也是一個視窗控制項,所以也會有一個 XID。 VLC 同樣是利用這個 XID 告訴 X11 server 在哪裡顯示視訊內容。

我們可以利用 xwininfo 查看指定軟體使用的 XID 為何。如下所示範,可查知 XID 為 79691795。

rock:~$ xwininfo -name "VLC media player" -int

xwininfo: Window id: 79691795 "VLC media player"

  Absolute upper-left X:  1620
  Absolute upper-left Y:  138
  Relative upper-left X:  1620
  Relative upper-left Y:  138
  Width: 432
  Height: 113
  Depth: 24
  Visual: 0x21

那麼我們是否可以自己先配置一個視窗區域,然後要求 VLC 用這個區域顯示視訊內容呢? 答案自然是可以,因此我們才能把影像播放功能嵌入我們自己的軟體畫面。

  • 並非每種視覺控制項都可以用於繪出視訊內容。在 GTK 工具中,我們應使用 DrawingArea 控制項才能供 VLC 繪出視訊內容。
  • To enable vlc's dbus control function: (ref: http://wiki.videolan.org/DBus)
    $ vlc --control dbus
    
  • To ask vlc to render video in foreign area (by XID or HWND): (ref: http://trac.videolan.org/vlc/ticket/4655)
    #X11:
    $ vlc --drawable-xid $XID
    
    #Windows:
    C:> vlc --drawable-hwnd $HWND
    

我用 Python 寫了一個 GTK 範例程式示範利用 VLC 將影像播放內容嵌入自己的應用軟體的功能。另外加了一個暫停與播放的按鈕,示範透過 VLC 的 DBus 介面控制播放動作。

範例程式首先透過 GTK 建立了一個視窗,然後在視窗中配置一個固定位置的佈局板(fixed panel)。接著,在佈局板上放置一個 DrawingArea 控制項並取得其 XID。稍候要將其 XID 告知 VLC , VLC 就會把影像呈現在該控制項內。佈局板上再加入兩個按鈕,一個 Play/Pause 可透過 VLC DBus 介面暫停或播放影像,另一個 Quit 就是結束此程式了。

當 GTK 完成上述視覺控制項的配置工作並顯示在螢幕上後,程式會執行 media_play() 函數。它先啟動一個 VLC 行程(launch_vlc_service),靜待2秒等 VLC 行程啟動完成。接著配置 VLC DBus 的代理個體,透過其 DBus 介面,將指定的影片加入媒體清單並播放之。

#!/usr/bin/python

import os, sys, time, subprocess
import gtk, gtk.gdk
import dbus
#from dbus.mainloop.glib import DBusGMainLoop


vlc_service = 'org.mpris.vlc'
vlc_path = '/'
vlc_player_path = '/Player'
vlc_tracklist_path = '/TrackList'
mediaplayer_ifacename = 'org.freedesktop.MediaPlayer'

XID = None
Vlc = None
Player = None
Tracklist = None
#DBusGMainLoop(set_as_default=True)


def init_xid(widget):
    global XID
    XID = widget.get_window().xid
    print("XID: %d\n" % XID)

def launch_vlc_service(view_id):
    cmd = "vlc -I dummy --control dbus --drawable-xid %d" % view_id
    cmd = cmd.split(" ")
    pid = subprocess.Popen(cmd).pid # background and NOWAIT


def init_player():
    global Vlc, Player, Tracklist
    vlc_proxy = bus.get_object(vlc_service, vlc_path)
    Vlc = dbus.Interface(vlc_proxy, mediaplayer_ifacename)
    player_proxy = bus.get_object(vlc_service, vlc_player_path)
    Player = dbus.Interface(player_proxy, mediaplayer_ifacename)
    tracklist_proxy = bus.get_object(vlc_service, vlc_tracklist_path)
    Tracklist = dbus.Interface(tracklist_proxy, mediaplayer_ifacename)

def play_video(mrl):
    Tracklist.AddTrack(mrl, True)

def program_quit(widget=None):
    Vlc.Quit()
    time.sleep(2)
    gtk.main_quit()
    print("Good bye")

def media_play(widget):
    init_xid(widget)
    launch_vlc_service(XID)
    time.sleep(2)
    init_player()
    play_video("file:///home/rock/Video/maids_05.rmvb")

def media_toggle_play(widget):
    Player.Pause()

print("PID: %d" % os.getpid())

bus = dbus.SessionBus()

w = gtk.Window(gtk.WINDOW_TOPLEVEL)

w.set_default_size(800, 640)

fixed_panel = gtk.Fixed()
w.add(fixed_panel)

media_view = gtk.DrawingArea()
media_view.set_size_request(800, 600)
fixed_panel.put(media_view, 0, 0)

btn_toggle_play = gtk.Button("Play/Pause")
btn_toggle_play.connect_object("clicked", media_toggle_play, w)
fixed_panel.put(btn_toggle_play, 100, 601)

btn_quit = gtk.Button("Quit")
btn_quit.connect_object("clicked", program_quit, w)
fixed_panel.put(btn_quit, 200, 601)

media_view.connect("map", media_play)

w.set_decorated(False) # disable all decorations.

w.move(10,100)

w.connect("destroy", program_quit)

w.show_all()
try:
    gtk.main()
except:
    pass

請記得修改範例程式想播放的影像位址,否則不會看到任何影像。 下圖便是 vlc-dbus.py 實際執行時的畫面擷圖。我故意取消了該視窗的外框裝飾,所以不見視窗邊框與上方的視窗標題列。

vlc-dbus.py 播放影像圖

關於 Python 撰寫 DBus 程式的教學文件,可閱讀本人《Python DBus 教學精要》。

我不清楚 VLC 從哪一版起實作了 DBus 與 drawable-xid 兩個選項。 我的環境是 vlc 1.0.6 與 vlc 1.1.10 。我不知道 vlc vlc 0.9 是否支援。 如果你是用舊版 vlc 的,也許不能這麼玩。

樂多舊網址: http://blog.roodo.com/rocksaying/archives/15945609.html