1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 __maintainer__ = 'Benjamin Kampmann <benjamin@fluendo.com>'
19
20
21 from elisa.core.bus import bus, bus_message
22 from elisa.core import common, log
23 from elisa.extern import enum
24
25 from elisa.core.utils import classinit
26
27 from elisa.core.player_engine_registry import NoEngineFound
28
29 import pygst
30 pygst.require('0.10')
31 import gst
32
33 from gobject import GError
34
36 """
37 Sent over the message bus when the first frame or buffer of a media has
38 been outputted.
39 """
40
42 """
43 Sent over the message bus when the playing is stopped.
44
45 @ivar percent: how much of the media has been shown.
46 @type percent: float
47 """
48
50 """
51 Initialize the PlayerStopping Message.
52
53 @param percent: the percent of the media, that was been watched yet
54 @type percent: float
55 """
56 bus_message.Message.__init__(self)
57 self.percent = percent
58
59
60
61
63 """
64 Sent over the message bus when the player is asked to play. When the
65 playback B{really} starts, that is when the first buffer reach the sink,
66 the L{elisa.core.player.PlayerPlaying} message is sent over the bus.
67
68 That message can be understood as the first answer after doing
69 L{elisa.core.player.Player.play}.
70
71 Warning: this message is B{not} sent when the uri is set.
72 """
73
75 """
76 Sent if the player is loading before playing or if it is prebuffering during
77 playing. This message might be sent very often and is mainly useful for
78 user interface updates.
79
80 This message is optional and the frontend should not expect that every
81 player sends it or is able to send it.
82
83 @ivar progress: the progress in percent
84 @type progress: float
85 """
86
87 __metaclass__ = classinit.ClassInitMeta
88 __classinit__ = classinit.build_properties
89
94
97
99 if progress > 100:
100 self._progress = 100
101 elif progress < 0:
102 self._progress = 0
103 else:
104 self._progress = progress
105
107 """
108 Sent over the message bus when the playing is paused.
109 """
110
112 """
113 This message is triggered everytime the engine gets a new Clock.
114 It is independent from any trigger_message-settings. It is simply always
115 send, when gstreamer finds a new one.
116 Even this message is send by the engine, please think, that the
117 message_sender is maybe overritten.
118
119 @ivar clock: the new clock that was found
120 @type clock: L{gst.Clock}
121 """
122 clock = None
129
131 """
132 This message is triggered everytime, the engine got a new BaseTime. This
133 message simply is send everytime a gstreamer change-state message is
134 coming to the engine.
135 This message is used for synchronization.
136
137 @ivar base_time: the new base time
138 @type base_time: L{gst.ClockTime}
139 """
140
144
146 """
147 Sent over the message bus if the player encountered an issue.
148
149 @ivar error: the error that occured
150 @type error: str
151 """
155
156
157
158 STATES = enum.Enum("LOADING", "PLAYING", "PAUSED", "STOPPED")
159 """
160 STATES.LOADING : The media is still loading. When it is finished the state
161 is switched to playing.
162 STATES.PLAYING : The player is currently playing a media.
163 STATES.PAUSED : The player is paused. It means the media is paused at the last
164 played position and will continue to play from there.
165 STATES.STOPPED : The player is stopped. It will restart playing from the
166 beginning of the media.
167 """
168
170 """
171 A player can play one audio or video media at a time. All it needs is a
172 L{elisa.core.media_uri.MediaUri} and the sinks for the video and audio
173 output. It can also do audio only output and has support for subtitles.
174
175 @ivar video_sink: the video sink that this player outputs to
176 @type video_sink: L{gst.BaseSink}
177
178 @ivar name: the name of the player instance
179 @type name: string
180
181 @ivar audio_sink: the audio sink that this player outputs to
182 @type audio_sink: L{gst.BaseSink}
183
184 @ivar volume: the volume level between 0 and 10
185 @type volume: float
186
187 @ivar position: the position we are currently playing in
188 nanoseconds; when set, if the value passed is higher
189 than L{duration}, position is set to L{duration}.
190 If the value passed is lower than 0, position is
191 set to 0.
192 @type position: int
193
194 @ivar duration: (read-only) the total length of the loaded media in
195 nanoseconds
196 @type duration: int
197
198 @ivar speed: The speed of the current playback:
199 - Normal playback is 1.0
200 - a positive value means forward
201 - a negative one backward
202 - the value 0.0 (equivalent to pause) is not
203 allowed
204 @type speed: float
205
206 @ivar state: (read-only) The current state. See
207 L{elisa.core.player.STATES}.
208 @type state: L{elisa.core.player.STATES}
209
210 @ivar playing: (read-only) is the player currently playing? That
211 also returns False if the player is in LOADING
212 state.
213 @type playing: bool
214
215 @ivar uri: the uri of the media loaded in the player.
216 @type uri: L{elisa.core.media_uri.MediaUri}
217
218 @ivar subtitle_uri: the uri for subtitles
219 @type subtitle_uri: L{elisa.core.media_uri.MediaUri}
220
221 @ivar subtitle_callback: the callback, where the timed subtitle texts
222 should be sent to. The callback will get a
223 L{gst.Buffer}, containing the subtitle text to be
224 displayed encoded in text/plain or
225 text/x-pango-markup
226 @type subtitle_callback: callable
227
228 @ivar muted: True if the player is muted, False otherwise. This
229 is independent of the volume attribute (eg. can be
230 False even if volume is 0).
231 @type muted: bool
232 """
233
234 __metaclass__ = classinit.ClassInitMeta
235 __classinit__ = classinit.build_properties
236
273
274
275
276
277 - def play(self, trigger_message=True):
278 """
279 Play the media. If trigger_message is set to True, this triggers first
280 the message L{elisa.core.player.PlayerLoading} message and if the
281 playback is really starting, it triggers
282 L{elisa.core.player.PlayerPlaying}. Otherwise it does not trigger any
283 messages.
284
285 @param trigger_message: should the player trigger messages here
286 @type trigger_message: bool
287 """
288 if not self._loaded:
289 return
290
291 self._engine.play(trigger_message)
292
293 - def pause(self, trigger_message=True):
294 """
295 Pause the playback. If trigger_message is set to True, this triggers
296 the L{elisa.core.player.PlayerPausing} message.
297
298 @param trigger_message: should the player trigger a message here
299 @type trigger_message: bool
300 """
301 if not self._loaded:
302 return
303
304 self._engine.pause(trigger_message)
305 if self._sub_uri:
306 self._sub_pipeline.set_state(gst.STATE_PAUSED)
307
308 - def stop(self, trigger_message=True):
309 """
310 Stop the playback. This is _not_ effecting the subtitles. If
311 trigger_message is set, this method triggers the
312 L{elisa.core.player.PlayerStopping} message.
313
314 @param trigger_message: should the player trigger a message here
315 @type trigger_message: bool
316 """
317 if not self._loaded:
318 return
319
320 if self._engine.state != STATES.STOPPED:
321 self._engine.stop(trigger_message)
322
323
325 """
326 Play the uri from the beginning. This is not triggering any
327 messages.
328 """
329 if not self._loaded:
330 return
331
332
333
334 self._engine.pause(trigger_message=False)
335 self._engine.position = 0
336
337 if self._engine.position != 0:
338
339 self._engine.stop(trigger_message=False)
340
341 self._engine.play(trigger_message=False)
342
343
345 """
346 Toggle the player between play and pause state. If it is not playing
347 yet, then start it. If trigger_message is set, this method might
348 triggers L{elisa.core.player.PlayerPlaying} and
349 L{elisa.core.player.PlayerLoading} or
350 L{elisa.core.player.PlayerPausing}.
351
352
353 @param trigger_message: should the player trigger a message here
354 @type trigger_message: bool
355 """
356 if not self._loaded:
357 return
358
359 self.debug("Toggle Play/Pause")
360
361 if self.playing:
362 self.pause(trigger_message)
363 else:
364 self.play(trigger_message)
365
368
369
370
372 if not self._loaded:
373 return
374
375 self.debug("Volume set to %s" % volume)
376
377 if self.muted:
378 volume = self._unmuted_volume
379 self._unmuted_volume = -1
380
381 self._engine.volume = volume
382
384 if not self._loaded:
385 return
386
387 if self.muted:
388 return self._unmuted_volume
389
390 return self._engine.volume
391
393 if not self._loaded:
394 return
395
396 self.debug("Muting set to %s" % value)
397
398 if value == True:
399 old_volume = self.volume
400 self.volume = 0
401 self._unmuted_volume = old_volume
402 else:
403 if self._unmuted_volume >= 0:
404 self.volume = self._unmuted_volume
405 self._unmuted_volume = -1
406
408 if not self._loaded:
409 return
410 return self._unmuted_volume != -1
411
412
413
475
478
480 self._loaded = False
481 if self._engine:
482 self._engine.stop(trigger_message=False)
483 self._video_sink = self._engine.video_sink
484 self._engine.video_sink = None
485 self._audio_sink = self._engine.audio_sink
486 self._engine.audio_sink = None
487 self._engine = None
488
490 self._sub_uri = uri
491 self.debug("Setting Subtitle URI to: '%s'" % uri)
492 if self._sub_uri == None:
493 return self._update_subtitle_handoff()
494
495 self._make_subtitle_pipeline()
496
497 decode = self._sub_pipeline.get_by_name('src')
498 if decode.get_factory().get_name() == "filesrc":
499 if uri.scheme != 'file':
500 self.warning("We only support file-scheme uris for"
501 " subtitles. If you want another scheme"
502 " please install gstreamer >= 0.10.14 with"
503 " uridecodebin!")
504 self._sub_uri = None
505 else:
506 decode.set_property('location', uri.path)
507 else:
508
509 decode.set_property('uri', str(uri))
510
511 self._sub_pipeline.set_new_stream_time(gst.CLOCK_TIME_NONE)
512
513 self._update_subtitle_handoff()
514
515 if self.playing:
516 self.pause(trigger_message = False)
517 self.play(trigger_message = False)
518
521
523 return self._visualisation
524
526
527 self._visualisation = new_visu
528
529
533
536
539
541 if sender == self:
542 self._sub_pipeline.set_state(gst.STATE_NULL)
543
545 sink = self._sub_pipeline.get_by_name('sink')
546 value = (self._sub_callback != None) and (self._sub_uri != None)
547 self.debug("Signal handoffs activated: %s" % value)
548 sink.set_property('signal-handoffs', value)
549
550
552 if sender != self:
553 return
554
555 new_clock = message.clock
556 self.debug("New clock %s" % new_clock)
557 self._sub_pipeline.use_clock(new_clock)
558
560 if sender != self:
561 return
562
563 base_time = message.base_time
564 if self._sub_uri != None:
565 self._sub_pipeline.set_base_time(base_time)
566 self._sub_pipeline.seek(1, gst.FORMAT_TIME,
567 gst.SEEK_FLAG_FLUSH | gst.SEEK_FLAG_KEY_UNIT,
568 gst.SEEK_TYPE_SET, self.position,
569 gst.SEEK_TYPE_SET, self.duration)
570 self._sub_pipeline.set_state(gst.STATE_PLAYING)
571 self.debug("Got new basetime, starting to play!")
572
573
574
576 if not self._engine:
577 return STATES.STOPPED
578 return self._engine.state
579
581 if not self._engine:
582 return -1
583 return self._engine.position
584
600
602 if not self._engine:
603 return -1
604
605 return self._engine.duration
606
608 if not self._engine:
609 return 1
610
611 return self._engine.speed
612
614 if not self._engine:
615 return
616
617
618 self._engine.speed = speed
619
621 return self._video_sink
622
624 self._video_sink = sink
625 if self._engine:
626 self._engine.video_sink = self._video_sink
627
629 return self._audio_sink
630
632 self._audio_sink = sink
633 if self._engine:
634 self._engine.audio_sink = self._audio_sink
635
636
637
639 if self._sub_callback:
640 self.debug("calling %s with buffer %s" % (self._sub_callback, buffer))
641 self._sub_callback(buffer)
642
644
645 audio_sink = gst.element_factory_make(self._audiosink)
646 for key, value in self._audiosettings.iteritems():
647 audio_sink.set_property(key, value)
648 return audio_sink
649
651 uri_dec = "uridecodebin name=src ! "\
652 "text/plain;text/x-pango-markup ! " \
653 "fakesink name=sink sync=true"
654 filesrc = "filesrc name=src ! "\
655 "decodebin ! text/plain;text/x-pango-markup ! "\
656 "fakesink name=sink sync=true"
657
658 try:
659 self._sub_pipeline = gst.parse_launch(uri_dec)
660 except GError:
661
662 self._sub_pipeline = gst.parse_launch(filesrc)
663
664 sink = self._sub_pipeline.get_by_name('sink')
665 sink.set_property('signal-handoffs', False)
666 sink.connect('handoff', self._new_handoff)
667