Back to development
[platform/upstream/gstreamer.git] / subprojects / gst-python / old_examples / play.py
1 #!/usr/bin/env python
2 # -*- Mode: Python -*-
3 # vi:si:et:sw=4:sts=4:ts=4
4
5 import pygtk
6 pygtk.require('2.0')
7
8 import sys
9
10 import gobject
11 gobject.threads_init()
12
13 import pygst
14 pygst.require('0.10')
15 import gst
16 import gst.interfaces
17 import gtk
18 gtk.gdk.threads_init()
19
20 class GstPlayer:
21     def __init__(self, videowidget):
22         self.playing = False
23         self.player = gst.element_factory_make("playbin", "player")
24         self.videowidget = videowidget
25         self.on_eos = False
26
27         bus = self.player.get_bus()
28         bus.enable_sync_message_emission()
29         bus.add_signal_watch()
30         bus.connect('sync-message::element', self.on_sync_message)
31         bus.connect('message', self.on_message)
32
33     def on_sync_message(self, bus, message):
34         if message.structure is None:
35             return
36         if message.structure.get_name() == 'prepare-xwindow-id':
37             # Sync with the X server before giving the X-id to the sink
38             gtk.gdk.threads_enter()
39             gtk.gdk.display_get_default().sync()
40             self.videowidget.set_sink(message.src)
41             message.src.set_property('force-aspect-ratio', True)
42             gtk.gdk.threads_leave()
43             
44     def on_message(self, bus, message):
45         t = message.type
46         if t == gst.MESSAGE_ERROR:
47             err, debug = message.parse_error()
48             print "Error: %s" % err, debug
49             if self.on_eos:
50                 self.on_eos()
51             self.playing = False
52         elif t == gst.MESSAGE_EOS:
53             if self.on_eos:
54                 self.on_eos()
55             self.playing = False
56
57     def set_location(self, location):
58         self.player.set_property('uri', location)
59
60     def query_position(self):
61         "Returns a (position, duration) tuple"
62         try:
63             position, format = self.player.query_position(gst.FORMAT_TIME)
64         except:
65             position = gst.CLOCK_TIME_NONE
66
67         try:
68             duration, format = self.player.query_duration(gst.FORMAT_TIME)
69         except:
70             duration = gst.CLOCK_TIME_NONE
71
72         return (position, duration)
73
74     def seek(self, location):
75         """
76         @param location: time to seek to, in nanoseconds
77         """
78         gst.debug("seeking to %r" % location)
79         event = gst.event_new_seek(1.0, gst.FORMAT_TIME,
80             gst.SEEK_FLAG_FLUSH | gst.SEEK_FLAG_ACCURATE,
81             gst.SEEK_TYPE_SET, location,
82             gst.SEEK_TYPE_NONE, 0)
83
84         res = self.player.send_event(event)
85         if res:
86             gst.info("setting new stream time to 0")
87             self.player.set_new_stream_time(0L)
88         else:
89             gst.error("seek to %r failed" % location)
90
91     def pause(self):
92         gst.info("pausing player")
93         self.player.set_state(gst.STATE_PAUSED)
94         self.playing = False
95
96     def play(self):
97         gst.info("playing player")
98         self.player.set_state(gst.STATE_PLAYING)
99         self.playing = True
100         
101     def stop(self):
102         self.player.set_state(gst.STATE_NULL)
103         gst.info("stopped player")
104
105     def get_state(self, timeout=1):
106         return self.player.get_state(timeout=timeout)
107
108     def is_playing(self):
109         return self.playing
110     
111 class VideoWidget(gtk.DrawingArea):
112     def __init__(self):
113         gtk.DrawingArea.__init__(self)
114         self.imagesink = None
115         self.unset_flags(gtk.DOUBLE_BUFFERED)
116
117     def do_expose_event(self, event):
118         if self.imagesink:
119             self.imagesink.expose()
120             return False
121         else:
122             return True
123
124     def set_sink(self, sink):
125         assert self.window.xid
126         self.imagesink = sink
127         self.imagesink.set_xwindow_id(self.window.xid)
128
129 class PlayerWindow(gtk.Window):
130     UPDATE_INTERVAL = 500
131     def __init__(self):
132         gtk.Window.__init__(self)
133         self.set_default_size(410, 325)
134
135         self.create_ui()
136
137         self.player = GstPlayer(self.videowidget)
138
139         def on_eos():
140             self.player.seek(0L)
141             self.play_toggled()
142         self.player.on_eos = lambda *x: on_eos()
143         
144         self.update_id = -1
145         self.changed_id = -1
146         self.seek_timeout_id = -1
147
148         self.p_position = gst.CLOCK_TIME_NONE
149         self.p_duration = gst.CLOCK_TIME_NONE
150
151         def on_delete_event():
152             self.player.stop()
153             gtk.main_quit()
154         self.connect('delete-event', lambda *x: on_delete_event())
155
156     def load_file(self, location):
157         self.player.set_location(location)
158                                   
159     def create_ui(self):
160         vbox = gtk.VBox()
161         self.add(vbox)
162
163         self.videowidget = VideoWidget()
164         vbox.pack_start(self.videowidget)
165         
166         hbox = gtk.HBox()
167         vbox.pack_start(hbox, fill=False, expand=False)
168         
169         self.pause_image = gtk.image_new_from_stock(gtk.STOCK_MEDIA_PAUSE,
170                                                     gtk.ICON_SIZE_BUTTON)
171         self.pause_image.show()
172         self.play_image = gtk.image_new_from_stock(gtk.STOCK_MEDIA_PLAY,
173                                                    gtk.ICON_SIZE_BUTTON)
174         self.play_image.show()
175         self.button = button = gtk.Button()
176         button.add(self.play_image)
177         button.set_property('can-default', True)
178         button.set_focus_on_click(False)
179         button.show()
180         hbox.pack_start(button, False)
181         button.set_property('has-default', True)
182         button.connect('clicked', lambda *args: self.play_toggled())
183         
184         self.adjustment = gtk.Adjustment(0.0, 0.00, 100.0, 0.1, 1.0, 1.0)
185         hscale = gtk.HScale(self.adjustment)
186         hscale.set_digits(2)
187         hscale.set_update_policy(gtk.UPDATE_CONTINUOUS)
188         hscale.connect('button-press-event', self.scale_button_press_cb)
189         hscale.connect('button-release-event', self.scale_button_release_cb)
190         hscale.connect('format-value', self.scale_format_value_cb)
191         hbox.pack_start(hscale)
192         self.hscale = hscale
193
194         self.videowidget.connect_after('realize',
195                                        lambda *x: self.play_toggled())
196
197     def play_toggled(self):
198         self.button.remove(self.button.child)
199         if self.player.is_playing():
200             self.player.pause()
201             self.button.add(self.play_image)
202         else:
203             self.player.play()
204             if self.update_id == -1:
205                 self.update_id = gobject.timeout_add(self.UPDATE_INTERVAL,
206                                                      self.update_scale_cb)
207             self.button.add(self.pause_image)
208
209     def scale_format_value_cb(self, scale, value):
210         if self.p_duration == -1:
211             real = 0
212         else:
213             real = value * self.p_duration / 100
214         
215         seconds = real / gst.SECOND
216
217         return "%02d:%02d" % (seconds / 60, seconds % 60)
218
219     def scale_button_press_cb(self, widget, event):
220         # see seek.c:start_seek
221         gst.debug('starting seek')
222         
223         self.button.set_sensitive(False)
224         self.was_playing = self.player.is_playing()
225         if self.was_playing:
226             self.player.pause()
227
228         # don't timeout-update position during seek
229         if self.update_id != -1:
230             gobject.source_remove(self.update_id)
231             self.update_id = -1
232
233         # make sure we get changed notifies
234         if self.changed_id == -1:
235             self.changed_id = self.hscale.connect('value-changed',
236                 self.scale_value_changed_cb)
237             
238     def scale_value_changed_cb(self, scale):
239         # see seek.c:seek_cb
240         real = long(scale.get_value() * self.p_duration / 100) # in ns
241         gst.debug('value changed, perform seek to %r' % real)
242         self.player.seek(real)
243         # allow for a preroll
244         self.player.get_state(timeout=50*gst.MSECOND) # 50 ms
245
246     def scale_button_release_cb(self, widget, event):
247         # see seek.cstop_seek
248         widget.disconnect(self.changed_id)
249         self.changed_id = -1
250
251         self.button.set_sensitive(True)
252         if self.seek_timeout_id != -1:
253             gobject.source_remove(self.seek_timeout_id)
254             self.seek_timeout_id = -1
255         else:
256             gst.debug('released slider, setting back to playing')
257             if self.was_playing:
258                 self.player.play()
259
260         if self.update_id != -1:
261             self.error('Had a previous update timeout id')
262         else:
263             self.update_id = gobject.timeout_add(self.UPDATE_INTERVAL,
264                 self.update_scale_cb)
265
266     def update_scale_cb(self):
267         self.p_position, self.p_duration = self.player.query_position()
268         if self.p_position != gst.CLOCK_TIME_NONE:
269             value = self.p_position * 100.0 / self.p_duration
270             self.adjustment.set_value(value)
271
272         return True
273
274 def main(args):
275     def usage():
276         sys.stderr.write("usage: %s URI-OF-MEDIA-FILE\n" % args[0])
277         sys.exit(1)
278
279     # Need to register our derived widget types for implicit event
280     # handlers to get called.
281     gobject.type_register(PlayerWindow)
282     gobject.type_register(VideoWidget)
283
284     w = PlayerWindow()
285
286     if len(args) != 2:
287         usage()
288
289     if not gst.uri_is_valid(args[1]):
290         sys.stderr.write("Error: Invalid URI: %s\n" % args[1])
291         sys.exit(1)
292
293     w.load_file(args[1])
294     w.show_all()
295
296     gtk.main()
297
298 if __name__ == '__main__':
299     sys.exit(main(sys.argv))