1

使用 pyobjc 和 wxPython 将 QTMovieView(和 QTMovie)小部件嵌入到 wxPython 面板中,然后注册所需的通知,但是不会触发 QTMovieDidEndNotification。

可下载的代码版本https ://dl.dropboxusercontent.com/u/12781104/QT%20Test%20Download.zip

值得注意的代码在 QuicktimeCtrl 类的 processMovie 方法中,它注册了通知观察者。

预计应用程序关闭时崩溃,似乎与释放内存有关,此时我正专注于解决其他问题。

你需要有 wxPython 2.9 版本的 COCOA;较低的版本是基于 Carbon 的 GetHandle 调用提供 NSHIObjects(至少据我所知,我无法在其上嵌入 QTMovieView)而不是 NSView

QT Embed 代码封装在面板中

'''
    Sublcasses wxMediaCtrl to fix aspect ratio issue
    http://forums.wxwidgets.org/viewtopic.php?t=23461&p=100319

    Why?
    - Yes wx.media.MediaCtrl has a quicktime backend
    but it has an issue with the aspect ratio not
    correctly displaying (stretches instead of black
    bars (seem to be from not setting 
    PreserveAspectRatio). Also has major issues with
    url playback (note only occurs when media is video
    mp3s play fine) with only very sporadic and none
    reliable ability to play (usually just fails to
    load)
    '''

import wx
import wx.media
from wx.lib.newevent import NewCommandEvent
import os

print "Import objc items"
# pyobj c stuff
import ctypes
import objc
from Foundation import NSURL, NSString, NSRect, NSDictionary
from AppKit import NSViewWidthSizable, NSViewHeightSizable
print "Importing QTMovie"
from QTKit import QTMovie as M, QTMovieDidEndNotification, QTMovieLoadStateDidChangeNotification, QTMovieView, QTMovieFileNameAttribute, QTMovieOpenAsyncOKAttribute, QTMovieLoadStateAttribute

print "Importing twisted logging module"
from twisted.python import log

# States
# might not be the numbers wx.media.MediaCtrl uses
MEDIASTATE_PLAYING = 0
MEDIASTATE_PAUSED =  1
MEDIASTATE_STOPPED = 2

# mc events are not control events so we get to admit our own "alias" versions
# Events
media_loaded_event  , EVT_MEDIA_LOADED   = NewCommandEvent()
media_play_event    , EVT_MEDIA_PLAY     = NewCommandEvent()
media_pause_event   , EVT_MEDIA_PAUSE    = NewCommandEvent()
media_stop_event    , EVT_MEDIA_STOP     = NewCommandEvent()
media_finished_event, EVT_MEDIA_FINISHED = NewCommandEvent()


class QuicktimeCtrl(wx.Panel):
    def __init__(self, *args, **kw):
        wx.Panel.__init__(self, *args, **kw)
        self.SetBackgroundColour("BLACK")

        ptr = self.GetHandle()
        void_ptr = ctypes.c_void_p(ptr)
        view = objc.objc_object(c_void_p=void_ptr)

        pos = (0, 0)
        size = self.GetSize()


        r = NSRect(pos, size)
        self.mv = QTMovieView.alloc().initWithFrame_(r)
        # setup the MacOSX equivalent of sizers
        self.mv.setAutoresizesSubviews_(True)
        self.mv.setAutoresizingMask_(NSViewWidthSizable | NSViewHeightSizable)
        # don't show video controls
        self.mv.setControllerVisible_(False)
        # preseve aspect ratio
        self.mv.setPreservesAspectRatio_(True)

        self.stop = True
        self.pause = False

        view.addSubview_(self.mv)

    def _send_event(self, event):
        print "Firing an event"
        evt = event(self.GetId())
        wx.PostEvent(self, evt)

    def OnMediaLoaded(self, event):
        self._send_event(media_loaded_event)

    def OnMediaPlaying(self, event):
        self._send_event(media_play_event)

    def OnMediaPaused(self, event):
        self._send_event(media_pause_event)

    def OnMediaFinished(self, event):
        self._send_event(media_finished_event)

    def OnMediaStopped(self, event):
        self._send_event(media_stop_event)

    def Load(self, file_path):
        # get movie constraints
        encoded_path = NSString.alloc().initWithString_(file_path)
        dict = NSDictionary.dictionaryWithDictionary_({QTMovieFileNameAttribute : encoded_path,
                                                      QTMovieOpenAsyncOKAttribute: False}
                                                      )

        (movie, error) = M.movieWithAttributes_error_(dict, None)


        if movie is None:
            print file_path
            print "[QT] An error occured"
            print error
            return False

        return self.processMovie(movie)

    def LoadURI(self, uri):
        encoded_url = NSURL.alloc().initWithString_(uri)

        dict = NSDictionary.dictionaryWithDictionary_({QTMovieURLAttribute : encoded_url,
                                                      QTMovieOpenAsyncOKAttribute: False}
                                                      )

        (movie, error) = M.movieWithAttributes_error_(dict, None)


        if movie is None:
            print uri
            print "[QT] An error occured"
            print error
            return False

        return self.processMovie(movie)

    def processMovie(self, m):
        #m.setDelegate_(self)
        # dispose of any old movie and set new one
        self.mv.setMovie_(m)

        #enum {
        #    QTMovieLoadStateError = -1L,
        #    QTMovieLoadStateLoading = 1000,
        #    QTMovieLoadStateLoaded = 2000,
        #    QTMovieLoadStatePlayable = 10000,
        #    QTMovieLoadStatePlaythroughOK = 20000,
        #    QTMovieLoadStateComplete = 100000L
        #};
        #typedef NSInteger QTMovieLoadState;
        loadState = m.attributeForKey_(QTMovieLoadStateAttribute).longValue()
        if loadState == -1:
            # error
            print "[QT] Error playing media"
            return False

        if loadState == 1000:
            # error in qt as it should be loaded synchnously
            print "[QT] Error wans't loaded synchronously"
            return False

        elif loadState == 2000:
            # loaded but not playable
            # attach a handler to get when it is playable
            # and send load
            self.loaded = True
            self.playable = False
            notf = NSNotificationCenter.defaultCenter()
            load_selector = objc.selector(self.OnQTLoad, signature = "v@:@")
            end_selector = objc.selector(self.OnQTMovieEnd, signature = "v@:@")
            notf.addObserver_selector_name_object_(self, load_selector, QTMovieLoadStateDidChangeNotification, m)
            notf.addObserver_selector_name_object_(self, end_selector,  QTMovieDidEndNotification,             m)

            return True

        elif loadState >= 10000:
            self.loaded = True
            self.playable = True
            # loaded and playable
            # fire evt
            wx.CallAfter(self._send_event, media_loaded_event)
            return True

        return False

    def OnQTLoad(self, m):
        print "QT LOAD"
        loadState = m.attributeForKey_(QTMovieLoadStateAttribute).longValue()
        if loadState == -1:
            # error
            print "[QT] [In Notification] Error playing media"
            # send stop event 
            self._send_event(media_stop_event)

        if loadState == 1000:
            # error in qt as it should be loaded synchnously
            print "[QT] [In Notification] Error wans't loaded synchronously"
            self._send_event(media_stop_event)

        if loadState >= 10000:
            # if now playable
            self._send_event(media_loaded_event)

    def OnQTMovieEnd(self, notf):
        print "QT END"
        print "THIS SHOULD BE PRINTED WHEN THE MOVIE ENDS\n\n\n\n\n\n"
        # movie is finished
        self.Stop() # dont care if succeeds not much we can do otherwise
        # then fire finish event
        self._send_event(media_finished_event)


    def Play(self):
        if self.mv.movie() is None:
            return False

        self.mv.play_(None)
        # confirms it works
        rate = self.mv.movie().rate()
        print "[Play] rate %s " % str(rate)
        if rate == 1.0:
            self.stop = False
            self.pause = False
            self._send_event(media_play_event)
            return True
        else:
            return False


    def Pause(self):
        if self.mv.movie() is None:
            return False

        self.mv.pause_(None)
        rate = self.mv.movie().rate()
        print "[Pause] rate %s " % str(rate)
        if rate == 0.0:
            self.stop = False
            self.pause = True
            self._send_event(media_pause_event)
            return True
        else:
            return False


    def Stop(self):
        if self.mv.movie() is None:
            return False

        # sets it to the beginning; follows wxMediaCtrl that hitting play after starts from the beginning
        self.mv.gotoBeginning_(None)
        rate = self.mv.movie().rate()
        print "[Stop] rate %s " % str(rate)
        if rate == 0.0:
            self.stop = True
            self.pause = False
            self._send_event(media_stop_event)
            return True
        else:
            return False

    def GetState(self):
        if self.stop:
            return MEDIASTATE_STOPPED
        if self.pause:
            return MEDIASTATE_PAUSED

        return MEDIASTATE_PLAYING

    def SetVolume(self, volume):
        if self.mv.movie() is None:
            return False

        # takes same 0 to 1 value as MediaCtrl so just pass through
        # http://docs.wxwidgets.org/2.8/wx_wxmediactrl.html#wxmediactrlsetvolume
        self.mv.movie().setVolume(float(volume))
        return True

    def Tell(self):
        return 0

    def Length(self):
        return 1

    def Seek(self, position):
        return True

和简单的测试应用程序(易于用 MediaCtrl 替换,但存在问题)

# Player
import re
from json import dumps
from urllib import quote

import wxversion
wxversion.select('2.9-osx_cocoa')


import wx
#import wx.media
from wx.lib.buttons import GenBitmapButton as BitmapButton

from quicktime_adapter import QuicktimeCtrl, EVT_MEDIA_STOP, EVT_MEDIA_PLAY, EVT_MEDIA_PAUSE, EVT_MEDIA_LOADED, EVT_MEDIA_FINISHED


CENTER = wx.ALIGN_CENTER | wx.ALL

class Panel(wx.Panel):

    def __init__(self, parent):
        wx.Panel.__init__(self, parent, -1, size = (400, 500))

        #self.url_re = re.compile("^http\://[a-zA-Z0-9\-\.]+\.[a-zA-Z]{2,3}(/\S*)?$")

        self.mpc = QuicktimeCtrl(self, -1, style=wx.SIMPLE_BORDER)
        self.load_file_button = wx.Button(self, -1, label = "Load File")
        self.load_url_button = wx.Button(self, -1, label = "Load URL")
        self.play_button = wx.Button(self, -1, label = "Play")
        self.pause_button = wx.Button(self, -1, label = "Pause")
        self.stop_button = wx.Button(self, -1, label = "Stop")
        size = (40, 40)
        size_bttn = (50, 50)

        #image = wx.Image('play.png', wx.BITMAP_TYPE_ANY).ShrinkBy(10, 10).ConvertToBitmap()
        #self.play_button = BitmapButton(self, -1,  image, size = size_bttn, style=wx.BORDER_NONE)

        #image = wx.Image('pause.png', wx.BITMAP_TYPE_ANY).ShrinkBy(10, 10).ConvertToBitmap()
        #self.pause_button = BitmapButton(self, -1,  image, size = size_bttn, style=wx.BORDER_NONE)

        #image = wx.Image('stop.png', wx.BITMAP_TYPE_ANY).ShrinkBy(10, 10).ConvertToBitmap()
        #self.stop_button = BitmapButton(self, -1,  image, size = size_bttn, style=wx.BORDER_NONE)

        self.play_button.Disable()

        bttn_sizer = wx.BoxSizer(wx.HORIZONTAL)
        load_buttons_sizer = wx.BoxSizer(wx.VERTICAL)
        app_sizer = wx.BoxSizer(wx.VERTICAL)

        load_buttons_sizer.Add(self.load_file_button, border = 5, flag = wx.ALL, proportion = 0)
        load_buttons_sizer.Add(self.load_url_button, border = 5, flag = wx.ALL, proportion = 0)

        #bttn_sizer.Add(self.load_file_button, border = 5, flag = CENTER, proportion = 0)
        #bttn_sizer.Add(self.load_url_button, border = 5, flag = CENTER, proportion = 0)
        bttn_sizer.Add(load_buttons_sizer, border = 0, flag = wx.ALIGN_LEFT | wx.ALL, proportion = 0)
        bttn_sizer.AddStretchSpacer(1)
        bttn_sizer.Add(self.play_button, border = 5, flag = CENTER, proportion = 0)
        bttn_sizer.Add(self.pause_button, border = 5, flag = CENTER, proportion = 0)
        bttn_sizer.Add(self.stop_button, border = 5, flag = CENTER, proportion = 0)

        app_sizer.Add(self.mpc, border = 10, flag = CENTER | wx.EXPAND, proportion = 1)
        app_sizer.Add(bttn_sizer, border = 5, proportion = 1)
        self.SetSizer(app_sizer)
        self.Fit()
        #wx.CallAfter(self.OnLoadURL, None)

        # Binds
        self.Bind(EVT_MEDIA_LOADED, self.OnMediaLoaded, self.mpc)
        self.Bind(EVT_MEDIA_PLAY, self.OnMediaPlaying, self.mpc)
        self.Bind(EVT_MEDIA_PAUSE, self.OnMediaPaused, self.mpc)
        self.Bind(EVT_MEDIA_STOP, self.OnMediaStopped, self.mpc)
        self.Bind(EVT_MEDIA_FINISHED, self.OnMediaFinished, self.mpc)

        self.Bind(wx.EVT_BUTTON, self.OnLoadFile, self.load_file_button)
        self.Bind(wx.EVT_BUTTON, self.OnLoadURL, self.load_url_button)
        self.Bind(wx.EVT_BUTTON, self.OnPlay, self.play_button)
        self.Bind(wx.EVT_BUTTON, self.OnPause, self.pause_button)
        self.Bind(wx.EVT_BUTTON, self.OnStop, self.stop_button)

    def OnLoadFile(self, event):
        self.play_button.Disable()
        dlg = wx.FileDialog(self, message="Choose a media file", style=wx.OPEN | wx.CHANGE_DIR )
        if dlg.ShowModal() == wx.ID_OK:
            path = dlg.GetPath()
            if not self.mpc.Load(path):
                wx.MessageBox("Unable to load %s: Unsupported format?" % path, "ERROR", wx.ICON_ERROR | wx.OK)
            else:
                self.mpc.SetInitialSize()
                self.GetSizer().Layout()
        dlg.Destroy()

    def OnLoadURL(self, event):
        self.play_button.Disable()
        dlg = wx.TextEntryDialog(self, "Enter URL", "URL", defaultValue = "")
        if dlg.ShowModal() == wx.ID_OK:
            url = dlg.GetValue()
            if url is not None:
                self.play_button.Disable()
                self.mpc.LoadURI(url)

            else:
                wx.MessageBox("Error: The URL you enter is invalid, Please enter a valid URL.", "Invalid URL", wx.ICON_ERROR | wx.OK)

        dlg.Destroy()

    def LoadURL(self, url, postdata = None):
        self.mpc.LoadURI(url, postdata)

    def OnPlay(self, event):
        print "OnPlay"
        self.mpc.Play()

    def OnPause(self, event):
        print "OnPause"
        self.mpc.Pause()

    def OnStop(self, event):
        print "OnStop"
        self.mpc.Stop()

    def OnMediaLoaded(self, event):
        print "Media Loaded"
        self.play_button.Enable()


    def OnMediaPlaying(self, event):
        print "Playing"

    def OnMediaPaused(self, event):
        print "Paused"

    def OnMediaStopped(self, event):
        print "Stopped"

    def OnMediaFinished(self, event):
        print "Finished"


if __name__ == "__main__":
    #from twisted.internet import wxreactor
    #wxreactor.install()
    #from twisted.internet import reactor
    app = wx.App(False)
    f = wx.Frame(None, -1, size = (400, 500), title = "Player")
    f.p = Panel(f)
    f.Show()
    app.MainLoop()
    #reactor.registerWxApp(app)
    #reactor.run()
4

0 回答 0