play.py 8.09 KB
Newer Older
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
1 2 3 4
#!/usr/bin/env python
# -*- Mode: Python -*-
# vi:si:et:sw=4:sts=4:ts=4

5 6 7 8 9 10
import pygtk
pygtk.require('2.0')

import sys

import gobject
11 12

import pygst
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
13
pygst.require('0.10')
14 15 16 17 18 19 20 21 22 23
import gst
import gst.interfaces
import gtk

class GstPlayer:
    def __init__(self):
        self.player = gst.element_factory_make("playbin", "player")

    def set_video_sink(self, sink):
        self.player.set_property('video-sink', sink)
24
        gst.debug('using videosink %r' % self.player.get_property('video-sink'))
25 26 27 28
        
    def set_location(self, location):
        self.player.set_property('uri', location)

29 30
    def query_position(self):
        "Returns a (position, duration) tuple"
31 32 33 34 35 36 37 38 39 40 41 42 43
        try:
            ret = self.player.query_position(gst.FORMAT_TIME)
        except:
            position = gst.CLOCK_TIME_NONE
        else:
            position = ret[0]

        try:
            ret = self.player.query_duration(gst.FORMAT_TIME)
        except:
            duration = gst.CLOCK_TIME_NONE
        else:
            duration = ret[0]
44

45
        return (position, duration, ret[1])
46 47

    def seek(self, location):
48 49 50 51 52 53 54 55 56 57 58 59 60 61 62
        """
        @param location: time to seek to, in nanoseconds
        """
        gst.debug("seeking to %r" % location)
        event = gst.event_new_seek(1.0, gst.FORMAT_TIME,
            gst.SEEK_FLAG_FLUSH,
            gst.SEEK_TYPE_SET, location,
            gst.SEEK_TYPE_NONE, 0)

        res = self.player.send_event(event)
        if res:
            gst.info("setting new stream time to 0")
            self.player.set_new_stream_time(0L)
        else:
            gst.error("seek to %r failed" % location)
63 64

    def pause(self):
65
        gst.info("pausing player")
66 67 68
        self.player.set_state(gst.STATE_PAUSED)

    def play(self):
69
        gst.info("playing player")
70 71 72
        self.player.set_state(gst.STATE_PLAYING)
        
    def stop(self):
73
        gst.info("stopping player")
74
        self.player.set_state(gst.STATE_READY)
75 76
        gst.info("stopped player")

77
    def get_state(self, timeout=1):
78 79 80 81
        return self.player.get_state(timeout=timeout)

    def is_in_state(self, state):
        gst.debug("checking if player is in state %r" % state)
82
        cur, pen, final = self.get_state(timeout=0)
83 84 85 86 87 88 89
        gst.debug("checked if player is in state %r" % state)
        if pen == gst.STATE_VOID_PENDING and cure == state:
            return True
        return False
    is_playing = lambda self: self.is_in_state(gst.STATE_PLAYING)
    is_paused = lambda self: self.is_in_state(gst.STATE_PAUSED)
    is_stopped = lambda self: self.is_in_state(gst.STATE_READY)
90 91 92 93 94 95 96 97 98
    
class VideoWidget(gtk.DrawingArea):
    def __init__(self, player):
        gtk.DrawingArea.__init__(self)
        self.connect('destroy', self.destroy_cb)
        self.connect_after('realize', self.after_realize_cb)
        self.set_size_request(400, 400)
        
        self.player = player
99
        self.imagesink = gst.element_factory_make('xvimagesink')
100 101 102 103 104 105 106
        self.player.set_video_sink(self.imagesink)

    def destroy_cb(self, da):
        self.set_window_id(0L)

    # Sort of a hack, but it works for now.
    def after_realize_cb(self, window):
107
        gobject.idle_add(self.frame_video_sink)
108

109
    def frame_video_sink(self):
110 111 112 113
        self.set_window_id(self.window.xid)
        
    def set_window_id(self, xid):
        self.imagesink.set_xwindow_id(xid)
114 115 116

    def unframe_video_sink(self):
        self.set_window_id(0L)
117 118 119 120 121 122 123 124 125 126 127 128 129 130
        

class PlayerWindow(gtk.Window):
    UPDATE_INTERVAL = 500
    def __init__(self):
        gtk.Window.__init__(self)
        self.connect('delete-event', gtk.main_quit)
        self.set_default_size(96, 96)

        self.player = GstPlayer()
        
        self.create_ui()

        self.update_id = -1
131 132 133 134 135
        self.changed_id = -1
        self.seek_timeout_id = -1

        self.p_position = gst.CLOCK_TIME_NONE
        self.p_duration = gst.CLOCK_TIME_NONE
136 137 138 139 140 141 142
        
    def load_file(self, location):
        self.player.set_location(location)
                                  
    def create_ui(self):
        vbox = gtk.VBox()

143 144
        self.videowidget = VideoWidget(self.player)
        vbox.pack_start(self.videowidget)
145 146
        
        hbox = gtk.HBox()
147
        vbox.pack_start(hbox, fill=False, expand=False)
148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168
        
        button = gtk.Button('play')
        button.connect('clicked', self.play_clicked_cb)
        hbox.pack_start(button, False)
        
        button = gtk.Button("pause");
        button.connect('clicked', self.pause_clicked_cb)
        hbox.pack_start(button, False)
        
        button = gtk.Button("stop");
        button.connect('clicked', self.stop_clicked_cb)
        hbox.pack_start(button, False)

        self.adjustment = gtk.Adjustment(0.0, 0.00, 100.0, 0.1, 1.0, 1.0)
        hscale = gtk.HScale(self.adjustment)
        hscale.set_digits(2)
        hscale.set_update_policy(gtk.UPDATE_CONTINUOUS)
        hscale.connect('button-press-event', self.scale_button_press_cb)
        hscale.connect('button-release-event', self.scale_button_release_cb)
        hscale.connect('format-value', self.scale_format_value_cb)
        hbox.pack_start(hscale)
169
        self.hscale = hscale
170 171 172 173

        self.add(vbox)

    def scale_format_value_cb(self, scale, value):
174
        if self.p_duration == -1:
175 176
            real = 0
        else:
177
            real = value * self.p_duration / 100
178 179 180 181 182 183
        
        seconds = real / gst.SECOND

        return "%02d:%02d" % (seconds / 60, seconds % 60)

    def scale_button_press_cb(self, widget, event):
184 185
        # see seek.c:start_seek
        gst.debug('starting seek')
186
        self.player.pause()
187 188

        # don't timeout-update position during seek
189
        if self.update_id != -1:
190
            gobject.source_remove(self.update_id)
191
            self.update_id = -1
192 193 194 195 196

        # make sure we get changed notifies
        if self.changed_id == -1:
            self.changed_id = self.hscale.connect('value-changed',
                self.scale_value_changed_cb)
197
            
198 199 200 201
    def scale_value_changed_cb(self, scale):
        # see seek.c:seek_cb
        real = long(scale.get_value() * self.p_duration / 100) # in ns
        gst.debug('value changed, perform seek to %r' % real)
202
        self.player.seek(real)
203
        # allow for a preroll
204
        self.player.get_state(timeout=50) # 50 ms
205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222

    def scale_button_release_cb(self, widget, event):
        # see seek.cstop_seek
        widget.disconnect(self.changed_id)
        self.changed_id = -1

        if self.seek_timeout_id != -1:
            gobject.source_remove(self.seek_timeout_id)
            self.seek_timeout_id = -1
        else:
            gst.debug('released slider, setting back to playing')
            self.player.play()

        if self.update_id != -1:
            self.error('Had a previous update timeout id')
        else:
            self.update_id = gobject.timeout_add(self.UPDATE_INTERVAL,
                self.update_scale_cb)
223 224

    def update_scale_cb(self):
225 226 227
        self.p_position, self.p_duration, format = self.player.query_position()
        if self.p_position != gst.CLOCK_TIME_NONE:
            value = self.p_position * 100.0 / self.p_duration
228 229 230 231 232 233 234 235
            self.adjustment.set_value(value)

        return True

    def play_clicked_cb(self, button):
        if self.player.is_playing():
            return
        
236
        self.videowidget.frame_video_sink()
237
        self.player.play()
238 239
        # keep the time display updated
        self.update_id = gobject.timeout_add(self.UPDATE_INTERVAL,
240 241 242 243 244 245 246 247
                                         self.update_scale_cb)

    def pause_clicked_cb(self, button):
        if self.player.is_paused():
            return
        
        self.player.pause()
        if self.update_id != -1:
248
            gobject.source_remove(self.update_id)
249 250 251 252 253 254 255
            self.update_id = -1

    def stop_clicked_cb(self, button):
        if self.player.is_stopped():
            return
        
        self.player.stop()
256
        self.videowidget.unframe_video_sink()
257
        if self.update_id != -1:
258
            gobject.source_remove(self.update_id)
259 260 261 262 263 264 265 266 267 268 269 270
            self.update_id = -1
        self.adjustment.set_value(0.0)

def main(args):
    w = PlayerWindow()
    w.load_file(args[1])
    w.show_all()

    gtk.main()

if __name__ == '__main__':
    sys.exit(main(sys.argv))