Merge lp:~samuel-buffet/entertainer/reactive_videoOSD into lp:entertainer

Proposed by Samuel Buffet
Status: Merged
Approved by: Matt Layman
Approved revision: 391
Merged at revision: not available
Proposed branch: lp:~samuel-buffet/entertainer/reactive_videoOSD
Merge into: lp:entertainer
Diff against target: None lines
To merge this branch: bzr merge lp:~samuel-buffet/entertainer/reactive_videoOSD
Reviewer Review Type Date Requested Status
Matt Layman Approve
Review via email: mp+7767@code.launchpad.net

Commit message

The video screen is now reactive to pointer events.

To post a comment you must log in.
Revision history for this message
Samuel Buffet (samuel-buffet) wrote :

Matt,

Here is a branch to give the videoOSD texture reactive capacities.

By a single click on it, it's possible to play/pause a video.
It is also possible to seek forward or backward through a video by "click slide and release".
The seek progression is a function of the horizontal distance since the first click.

Some related modification have been done to the player and a simplification of the videoOSD texture as well.

Samuel-

390. By Samuel Buffet

Merged with trunk 386.

Revision history for this message
Matt Layman (mblayman) wrote :

I really love the one click pause feature. That's definitely my favorite part about this branch. Anyway, time for some review comments:

main.py:
 * The connect to 'refresh' could now be one line.

progress_bar.py:
 * I found the two lines `self._progess = None; self.progress = 0` to be a bit confusing. I realize now that the second line is actually calling the property setter function, but it wasn't very clear to me that that was happening until I hunted around. Could you please add a comment there to help remove that confusion? Something like "# Call the property setter." between the two lines.
 * _reset_auto_display_timeout docstring has odd spacing and is missing a period.
 * The progress bar does not update its position after a seek while the media was paused. I mentioned this to you on IRC.

media_player.py:
 * _internal_timer_callback docstring doesn't really make sense anymore (because it's not just emitting a position-changed event).
 * The amount of calls to _internal_timer_callback and the frequency at which they occur kind of bothers me. A media player object is created when Entertainer starts and the way this init is written means that we *never* stop calling that timer (which fires every 1/5 of a second). That seems pretty resource intensive to me. Any ideas of how to make this timer only run when media is *actually* playing (because I think that is the only time when it is really needed)?
 * Is the is_reactive property logically backwards? I must confess that I am confused by it. In the init, self.is_reactive is set to False, then a few lines later, self.video_texture.set_reactive(True). I don't get it.
 * The video_texture connects mix " and ' quote marks. Could you pick one or the other?
 * position += self._seek_step -> What if you're seeking backwards? Should it decrement instead? Also, what is position was 1 and then you just added the seek_step to it. Would gstreamer complain?

General:
 * Can you think of any worthwhile tests for some of this?

review: Needs Information
391. By Samuel Buffet

Modifications according to Matt's comments.

Revision history for this message
Samuel Buffet (samuel-buffet) wrote :
Download full text (3.4 KiB)

Matt,

> main.py:
> * The connect to 'refresh' could now be one line.

Modified.

> progress_bar.py:
> * I found the two lines `self._progess = None; self.progress = 0` to be a bit
> confusing. I realize now that the second line is actually calling the property
> setter function, but it wasn't very clear to me that that was happening until
> I hunted around. Could you please add a comment there to help remove that
> confusion? Something like "# Call the property setter." between the two lines.

I have added a comment there to explain that I set the progress property to initialize the widget.

> * _reset_auto_display_timeout docstring has odd spacing and is missing a
> period.

Yes, Fixed.

> * The progress bar does not update its position after a seek while the media
> was paused. I mentioned this to you on IRC.

Yes you're right. Unfortunatly, fix is not as trivial as expected at the widget level. I think that's because we do to much gstreamer queries. I plan to make another branch for that if you're okay.

> media_player.py:
> * _internal_timer_callback docstring doesn't really make sense anymore
> (because it's not just emitting a position-changed event).

Right, I've modified that docstring.

> * The amount of calls to _internal_timer_callback and the frequency at which
> they occur kind of bothers me. A media player object is created when
> Entertainer starts and the way this init is written means that we *never* stop
> calling that timer (which fires every 1/5 of a second). That seems pretty
> resource intensive to me. Any ideas of how to make this timer only run when
> media is *actually* playing (because I think that is the only time when it is
> really needed)?

Well, I don't really see the point here because I think we don't have to be afraid of timers like that. The tasks done here are very*100 limited for a CPU. But the fix is trivial so I've done the modification (but it's an added complexity!)

> * Is the is_reactive property logically backwards? I must confess that I am
> confused by it. In the init, self.is_reactive is set to False, then a few
> lines later, self.video_texture.set_reactive(True). I don't get it.

Yeah, I've renamed it to is_reactive_allowed to limit confusion. This variable is used to neutralize the reactive commands when the video texture of the player is on the background on other screen but video_osd screen.

Indeed, it would be a very bad user experience imho if we were able pause/seek on other screens.

> * The video_texture connects mix " and ' quote marks. Could you pick one or
> the other?

Yep, it's fixed.

> * position += self._seek_step -> What if you're seeking backwards? Should it
> decrement instead? Also, what is position was 1 and then you just added the
> seek_step to it. Would gstreamer complain?

No in fact because I've protected it against bad values. Check the set_media_position method, I've added test on the position parameter.

> General:
> * Can you think of any worthwhile tests for some of this?

Yes, that is really something I have in mind. The main issue is that we need to start a clutter main loop during our tests. Otherwise events won't work. Even timeout...

Read more...

Revision history for this message
Matt Layman (mblayman) wrote :

>> * The progress bar does not update its position after a seek while the media
>> was paused. I mentioned this to you on IRC.
>
>Yes you're right. Unfortunatly, fix is not as trivial as expected at the widget level. I think >that's because we do to much gstreamer queries. I plan to make another branch for that if you're >okay.

Okay, that's fine with me. I'd rather see this branch land in a mostly working state with a couple of minor issues than to hold it back until it's absolutely perfect.

>> * The amount of calls to _internal_timer_callback and the frequency at which
>> they occur kind of bothers me. A media player object is created when
>> Entertainer starts and the way this init is written means that we *never* stop
>> calling that timer (which fires every 1/5 of a second). That seems pretty
>> resource intensive to me. Any ideas of how to make this timer only run when
>> media is *actually* playing (because I think that is the only time when it is
>> really needed)?
>
>Well, I don't really see the point here because I think we don't have to be afraid of timers like >that. The tasks done here are very*100 limited for a CPU. But the fix is trivial so I've done the >modification (but it's an added complexity!)

Thanks for applying this fix anyway. It may not seem like much, but since the level of effort to fix it was minimal, I think it was worth it in this case.

>> * Is the is_reactive property logically backwards? I must confess that I am
>> confused by it. In the init, self.is_reactive is set to False, then a few
>> lines later, self.video_texture.set_reactive(True). I don't get it.
>
>Yeah, I've renamed it to is_reactive_allowed to limit confusion. This variable is used to >neutralize the reactive commands when the video texture of the player is on the background on >other screen but video_osd screen.
>
>Indeed, it would be a very bad user experience imho if we were able pause/seek on other screens.

Okay, that helped a lot. I agree that it would be bad if users were able to pause/seek on other screens (outside of keyboard methods, of course).

>> * position += self._seek_step -> What if you're seeking backwards? Should it
>> decrement instead? Also, what is position was 1 and then you just added the
>> seek_step to it. Would gstreamer complain?
>
>No in fact because I've protected it against bad values. Check the set_media_position method, >I've added test on the position parameter.

Ah, sorry, you're right. For some reason, I thought the position checking stuff was just a few lines above this section of code. I still don't know what happens in the seek backwards case (it seems to me like you'd want to decrement instead of increment), but I'm fine with this.

I understand about not being able to get tests in place. I wonder if there is any way we could simulate/mock events in a future branch.

review: Approve
Revision history for this message
Paul Hummer (rockstar) wrote :
Download full text (20.7 KiB)

`which trial` entertainerlib.tests
entertainerlib.tests.test_base
  BaseTest
    testCreate ... [OK]
    testGetAbsX ... [OK]
    testGetAbsY ... [OK]
entertainerlib.tests.test_configuration
  ConfigurationTest
    testBorg ... [OK]
    testCreate ... [OK]
    testGetCfgDir ... [OK]
    testGetSlideshowStep ... [OK]
    testGetStageHeight ... [OK]
    testGetStageWidth ... [OK]
    testGetThemeName ... [OK]
    testSetStageHeight ... [OK]
    testSetStageWidth ... [OK]
    testStartAutoServer ... [OK]
    testTrayIconEnabled ... [OK]
    test_create_dir ... [OK]
    test_hidden_files_folders ... [OK]
    test_sanitize ... [OK]
    test_taint ... [OK]
    test_taint_in_memory ... [OK]
    test_write_content_value ... [OK]
    test_write_preference_value ... [OK]
entertainerlib.tests.test_connection
  ConnectionServerTest
    testPortBinding ... [OK]
entertainerlib.tests.test_database
  DatabaseTest
    testCreate ... [OK]
    testUseExisting ... [OK]
entertainerlib.tests.test_feedconfigtools
  FeedConfigToolsTest
    testAddFeedsToWidget001 ... [OK]
    testAddFeedsToWidget002 ... [OK]
    testAddFileFeedsToWidget001 ... [OK]
    testAddFileFeedsToWidget002 ... [OK]
entertainerlib.tests.test_feedentryparser
  FeedEntryParserTest
    testConvert ... [OK]
    testStripNonPangoTags ... [OK]
    testStripTags001 ... [OK]
    testStripTags002 ... [OK]
entertainerlib.tests.test_frontendfeed
  FrontendFeedTest
    test_date ... [OK]
    test_description ... [OK]
    test_entries ... ...

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'entertainerlib/frontend/gui/screens/main.py'
2--- entertainerlib/frontend/gui/screens/main.py 2009-05-27 10:33:21 +0000
3+++ entertainerlib/frontend/gui/screens/main.py 2009-05-28 19:44:40 +0000
4@@ -31,7 +31,7 @@
5 self.media_player = media_player
6 self.media_player.connect("stop", self.update)
7 self.media_player.connect("play", self.update)
8- self.media_player.connect('position_changed',
9+ self.media_player.connect('refresh',
10 self._update_preview_title)
11
12 self.theme = self.config.theme
13@@ -244,10 +244,10 @@
14 Update screen widgets. This is called always when screen is poped from
15 the screen history. Updates main menu widget.
16 """
17- if self.media_player.is_playing() and \
18+ if self.media_player.is_playing and \
19 (self.menu.get_index("playing") == -1):
20 self.menu.add_item(_("Playing now..."), "playing")
21- elif not self.media_player.is_playing() and \
22+ elif not self.media_player.is_playing and \
23 (self.menu.get_index("playing") != -1):
24 self.menu.remove_item("playing")
25
26
27=== modified file 'entertainerlib/frontend/gui/screens/video_osd.py'
28--- entertainerlib/frontend/gui/screens/video_osd.py 2009-05-16 19:14:01 +0000
29+++ entertainerlib/frontend/gui/screens/video_osd.py 2009-05-25 11:07:55 +0000
30@@ -59,13 +59,13 @@
31 self.pause_texture.hide()
32 self.add(self.pause_texture)
33
34- pause_timeline = clutter.Timeline(fps=120, duration=2000)
35+ pause_timeline = clutter.Timeline(fps=20, duration=2000)
36 pause_timeline.set_loop(True)
37 alpha_pause = clutter.Alpha(pause_timeline, clutter.sine_func)
38
39 self.pause_opacity = clutter.BehaviourOpacity(alpha=alpha_pause,
40- opacity_start=0, opacity_end=255)
41- self.pause_scale = clutter.BehaviourScale(1.0, 1.0, 1.3, 1.3,
42+ opacity_start=100, opacity_end=255)
43+ self.pause_scale = clutter.BehaviourScale(1.0, 1.0, 1.4, 1.4,
44 alpha_pause)
45
46 self.pause_opacity.apply(self.pause_texture)
47@@ -154,7 +154,7 @@
48
49 def _handle_play_pause(self, event=None):
50 '''Handle UserEvent.PLAYER_PLAY_PAUSE.'''
51- if self.media_player.is_playing():
52+ if self.media_player.is_playing:
53 self.pause_texture.hide()
54 else:
55 self.pause_texture.show()
56
57=== modified file 'entertainerlib/frontend/gui/user_interface.py'
58--- entertainerlib/frontend/gui/user_interface.py 2009-06-03 01:54:03 +0000
59+++ entertainerlib/frontend/gui/user_interface.py 2009-06-05 05:12:59 +0000
60@@ -94,10 +94,9 @@
61 self.config.get_stage_width(), self.config.get_stage_height())
62
63 # Initialize menu overlay texture
64- self.overlay_status = False
65+ self.is_overlay = False
66 self.menu_overlay = MenuOverlay(self.config.theme)
67 self.menu_overlay.set_opacity(0)
68- self.menu_overlay.set_name("overlay")
69 self.menu_overlay.set_size(
70 self.config.get_stage_width(), self.config.get_stage_height())
71 self.stage.add(self.menu_overlay)
72@@ -258,36 +257,27 @@
73 a video playing and menu showing at the same time. Overlay is not part
74 of any specific screen. It is used for all screens when neccesary.
75 """
76- self.overlay_status = True
77- if self.config.show_effects():
78+ if not self.is_overlay:
79+ self.is_overlay = True
80 self.menu_overlay.fade_in()
81- else:
82- self.menu_overlay.set_opacity(255)
83+ self.player.is_reactive = False
84
85 def disable_menu_overlay(self):
86 """
87 Disable menu overlay. Overlay should be disabled when current screen is
88 a type of Screen.OSD.
89 """
90- self.overlay_status = False
91- if self.config.show_effects():
92+ if self.is_overlay:
93+ self.is_overlay = False
94 self.menu_overlay.fade_out()
95- else:
96- self.menu_overlay.set_opacity(0)
97-
98- def is_overlay(self):
99- """
100- Get menu overlay status.
101- @return boolean value. True if menu overlay is active, otherwise False.
102- """
103- return self.overlay_status
104+ self.player.is_reactive = True
105
106 def change_screen(self, screen, direction):
107 '''Transition the given screen in the direction provided.'''
108 # Enable/Disable menu overlay
109- if screen.kind == Screen.OSD and self.is_overlay():
110+ if screen.kind == Screen.OSD:
111 self.disable_menu_overlay()
112- elif screen.kind != Screen.OSD and not self.is_overlay():
113+ else:
114 self.enable_menu_overlay()
115
116 # Add current screen to screen history
117@@ -367,7 +357,7 @@
118 if self.current.is_interested_in_play_action():
119 self.current.execute_play_action()
120 else:
121- if self.player.is_playing():
122+ if self.player.is_playing:
123 self.player.pause()
124 self.current.handle_user_event(event)
125 else:
126@@ -390,7 +380,7 @@
127
128 def _handle_player_stop(self, event):
129 '''Handle UserEvent.PLAYER_STOP.'''
130- if self.player.is_playing():
131+ if self.player.is_playing:
132 self.player.stop()
133 self.current.handle_user_event(event)
134
135
136=== modified file 'entertainerlib/frontend/gui/widgets/menu_overlay.py'
137--- entertainerlib/frontend/gui/widgets/menu_overlay.py 2009-05-10 06:24:43 +0000
138+++ entertainerlib/frontend/gui/widgets/menu_overlay.py 2009-05-25 10:22:25 +0000
139@@ -1,5 +1,5 @@
140 # Copyright (c) 2009 Entertainer Developers - See COPYING - GPLv2
141-'''Texture that is displayed over video when menu is active'''
142+'''Texture that is displayed over video when menu is active.'''
143
144 import clutter
145
146@@ -18,25 +18,17 @@
147 """Initialize overlay texture."""
148 Texture.__init__(self, filename = theme.getImage("menu_overlay"))
149 self.timeline = clutter.Timeline(13, 26)
150- self.timeline.set_loop(False)
151+ self.alpha = clutter.Alpha(self.timeline, clutter.smoothstep_inc_func)
152+ self.behaviour = clutter.BehaviourOpacity(255, 0, self.alpha)
153+ self.behaviour.apply(self)
154
155 def fade_in(self):
156- """
157- Fade texture in. Animate texture's opacity from 0 to 255
158- """
159- self.alpha = clutter.Alpha(self.timeline, clutter.smoothstep_dec_func)
160- self.behaviour = clutter.BehaviourOpacity(255, 0, self.alpha)
161- self.behaviour.apply(self)
162- self.timeline.rewind()
163+ """Fade texture in."""
164+ self.behaviour.set_bounds(0, 255)
165 self.timeline.start()
166
167 def fade_out(self):
168- """
169- Fade texture out. Animate texture's opacity from 255 to 0
170- """
171- self.alpha = clutter.Alpha(self.timeline, clutter.smoothstep_inc_func)
172- self.behaviour = clutter.BehaviourOpacity(255, 0, self.alpha)
173- self.behaviour.apply(self)
174- self.timeline.rewind()
175+ """Fade texture out."""
176+ self.behaviour.set_bounds(255, 0)
177 self.timeline.start()
178
179
180=== modified file 'entertainerlib/frontend/gui/widgets/progress_bar.py'
181--- entertainerlib/frontend/gui/widgets/progress_bar.py 2009-05-27 10:33:21 +0000
182+++ entertainerlib/frontend/gui/widgets/progress_bar.py 2009-05-28 19:44:40 +0000
183@@ -33,7 +33,6 @@
184
185 self.set_position(self.get_abs_x(x), self.get_abs_y(y))
186
187- self._progress = 0
188 self._color = self._color_to_cairo_color(
189 self.config.theme.get_color(color))
190
191@@ -65,6 +64,9 @@
192 self._behaviour = clutter.BehaviourOpacity(0, 255, self._alpha)
193 self._behaviour.apply(self)
194
195+ self._progress = None
196+ self.progress = 0
197+
198 # Preparation to pointer events handling.
199 self._motion_handler = 0
200 self.set_reactive(True)
201@@ -81,9 +83,10 @@
202 def _set_media_player(self, media_player):
203 '''media_player property setter.'''
204 self._media_player = media_player
205- self._media_player.connect('position_changed',
206- self._update_media_position)
207+ self._media_player.connect('refresh', self._update_media_position)
208 self._media_player.connect('stop', self._update_media_position)
209+ self._media_player.connect('position-changed',
210+ self._on_player_position_changed)
211
212 media_player = property(_get_media_player, _set_media_player)
213
214@@ -137,6 +140,13 @@
215 self.visible = False
216 return False
217
218+ def _reset_auto_display_timeout(self):
219+ '''Reset the timeout if auto_display = True'''
220+ if self._hide_timeout_key is not None:
221+ gobject.source_remove(self._hide_timeout_key)
222+ self._hide_timeout_key = gobject.timeout_add(3000,
223+ self._hide_progress_bar)
224+
225 def _update_media_position(self, event=None):
226 '''Update the media's position.'''
227 if not self._progress_bar_moving:
228@@ -268,12 +278,18 @@
229 return False
230
231 def _on_enter_event(self, stage, clutter_event):
232- '''Shows the button.'''
233+ '''Shows the progress bar.'''
234 if self.auto_display and not self._progress_bar_moving:
235 self.visible = True
236
237 def _on_leave_event(self, stage, clutter_event):
238- '''Hides the button.'''
239+ '''Hides the progress bar.'''
240 if self.auto_display and not self._progress_bar_moving:
241 self.visible = False
242
243+ def _on_player_position_changed(self, event=None):
244+ '''Shows the progress bar.'''
245+ if self.auto_display and not self._progress_bar_moving:
246+ self.visible = True
247+ self._reset_auto_display_timeout()
248+
249
250=== modified file 'entertainerlib/frontend/media_player.py'
251--- entertainerlib/frontend/media_player.py 2009-05-26 17:32:56 +0000
252+++ entertainerlib/frontend/media_player.py 2009-06-22 19:12:37 +0000
253@@ -9,6 +9,7 @@
254 import gobject
255 import gst
256
257+from entertainerlib.frontend.gui.widgets.motion_buffer import MotionBuffer
258 from entertainerlib.frontend.gui.widgets.texture import Texture
259 from entertainerlib.frontend.medialibrary.playable import Playable
260 from entertainerlib.utils.logger import Logger
261@@ -31,7 +32,8 @@
262 'stop' : ( gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, () ),
263 'skip-forward' : ( gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, () ),
264 'skip-backward' : ( gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, () ),
265- 'position_changed' : ( gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, () ),
266+ 'position-changed' : ( gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, () ),
267+ 'refresh' : ( gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, () ),
268 }
269
270 # Ratio constants
271@@ -42,9 +44,17 @@
272 ZOOM = 4
273 INTELLIGENT = 5
274
275+ MODE_NONE = 0
276+ MODE_PLAYPAUSE = 1
277+ MODE_SEEK = 2
278+
279 def __init__(self, stage, width, height):
280 gobject.GObject.__init__(self)
281
282+ self._motion_buffer = MotionBuffer()
283+ self._event_mode = self.MODE_NONE
284+ self._motion_handler = 0
285+
286 self.stage = stage # Stage that displays textures
287 # Stage background color when not playing
288 self.bgcolor = stage.get_color()
289@@ -58,7 +68,8 @@
290 self.media = None # Current media (Playable object)
291 self.shuffle = False # Shuffle mode
292 self.repeat = False # Repeat mode
293- self.playing = False # Is media player currently playing
294+ self.is_playing = False # Is media player currently playing
295+ self.is_reactive = False # Is the video_texture reactive
296
297 self.logger = Logger().getLogger('frontend.MediaPlayer')
298
299@@ -68,10 +79,15 @@
300 self.bus.add_signal_watch()
301 self.bus.connect('message', self._on_gst_message)
302
303- self.video_texture.connect("size-change", self.change_ratio_callback)
304- self.video_texture.show()
305+ gobject.timeout_add(200, self._internal_timer_callback)
306
307- gobject.timeout_add(500, self._emit_position_changed)
308+ self.video_texture.set_reactive(True)
309+ self.video_texture.connect("size-change", self._on_size_change)
310+ self.video_texture.connect('scroll-event', self._on_scroll_event)
311+ self.video_texture.connect("button-press-event",
312+ self._on_button_press_event)
313+ self.video_texture.connect('button-release-event',
314+ self._on_button_release_event)
315
316 def _on_gst_message(self, bus, message):
317 '''
318@@ -113,7 +129,7 @@
319 self.playlist = None
320
321 # If player is currently playing then we stop it
322- if self.is_playing():
323+ if self.is_playing:
324 self.stop()
325
326 # Update media information
327@@ -173,7 +189,7 @@
328 return
329
330 if self.media.get_type() == Playable.AUDIO_STREAM:
331- self.playing = True
332+ self.is_playing = True
333 self.video_texture.set_playing(True)
334 self.emit('play')
335
336@@ -182,20 +198,20 @@
337 if (self.video_texture.get_parent() == None):
338 self.stage.add(self.video_texture)
339 self.video_texture.lower_bottom()
340- self.playing = True
341+ self.is_playing = True
342 self.stage.set_color((0, 0, 0, 0))
343 self.video_texture.set_playing(True)
344 self.emit('play')
345
346 def pause(self):
347 '''Pause media player.'''
348- self.playing = False
349+ self.is_playing = False
350 self.video_texture.set_playing(False)
351 self.emit('pause')
352
353 def stop(self):
354 '''Stop media player.'''
355- self.playing = False
356+ self.is_playing = False
357 if self.media.get_type() == Playable.VIDEO_STREAM:
358 self.stage.set_color(self.bgcolor)
359 self.stage.remove(self.video_texture)
360@@ -248,10 +264,6 @@
361 seek_ns)
362 self.emit('skip-backward')
363
364- def is_playing(self):
365- '''Is media player currently playing.'''
366- return self.playing
367-
368 def get_media_position(self):
369 '''Get current position of the play back.'''
370 try:
371@@ -277,6 +289,12 @@
372
373 def set_media_position(self, position):
374 '''Set position of the current media.'''
375+ if position < 0.0:
376+ position = 0.0
377+
378+ if position > 1.0:
379+ position = 1.0
380+
381 if (self.media.get_type() == Playable.AUDIO_STREAM) or \
382 (self.media.get_type() == Playable.VIDEO_STREAM):
383 dur = self.playbin.query_duration(gst.FORMAT_TIME, None)[0]
384@@ -331,7 +349,7 @@
385 time_str = time_str + "0" + str(time_int)
386 return time_str
387
388- def change_ratio_callback(self, texture, width, height):
389+ def _on_size_change(self, texture, width, height):
390 '''
391 Callback for changing video texture's aspect ratio. This is called when
392 video texture size changes.
393@@ -467,9 +485,76 @@
394 else:
395 return None
396
397- def _emit_position_changed(self):
398+ def _internal_timer_callback(self):
399 '''A `position-changed` event is regulary emited if media is playing.'''
400- if self.is_playing():
401- self.emit('position_changed')
402+ if self.is_playing:
403+ self.emit('refresh')
404+
405+ if self._event_mode == self.MODE_SEEK:
406+ position = self.get_media_position()
407+ position += self._seek_step
408+ self.set_media_position(position)
409+ self.emit('position-changed')
410 return True
411
412+ def _on_button_press_event(self, actor, event):
413+ """`button-press` event handler."""
414+ if not self.is_reactive:
415+ return
416+
417+ clutter.grab_pointer(self.video_texture)
418+ if not self.video_texture.handler_is_connected(self._motion_handler):
419+ self._motion_handler = self.video_texture.connect('motion-event',
420+ self._on_motion_event)
421+
422+ self._motion_buffer.start(event)
423+ self._event_mode = self.MODE_PLAYPAUSE
424+
425+ def _on_button_release_event(self, actor, event):
426+ """`button-press` event handler."""
427+ if not self.is_reactive:
428+ return
429+
430+ clutter.ungrab_pointer()
431+ if self.video_texture.handler_is_connected(self._motion_handler):
432+ self.video_texture.disconnect_by_func(self._on_motion_event)
433+
434+ if self._event_mode == self.MODE_PLAYPAUSE:
435+ if self.is_playing:
436+ self.pause()
437+ else:
438+ self.play()
439+
440+ self._event_mode = self.MODE_NONE
441+
442+ def _on_motion_event(self, actor, event):
443+ """`motion-event` event handler."""
444+ # threshold in pixels = the minimum distance we have to move before we
445+ # consider a motion has started
446+ motion_threshold = 20
447+
448+ self._motion_buffer.compute_from_start(event)
449+ if self._motion_buffer.distance_from_start > motion_threshold:
450+ self._motion_buffer.take_new_motion_event(event)
451+ self._event_mode = self.MODE_SEEK
452+ self._seek_step = float(self._motion_buffer.dx_from_start)
453+ self._seek_step /= self.video_texture.get_width()
454+ self._seek_step *= 0.01
455+
456+ return False
457+
458+ def _on_scroll_event(self, actor, event):
459+ '''`scroll-event` event handler (mouse's wheel).'''
460+ # +/- 2% per scroll event on the position of the media stream.
461+ scroll_progress_ratio = 0.02
462+
463+ position = self.get_media_position()
464+
465+ if event.direction == clutter.SCROLL_DOWN:
466+ position -= scroll_progress_ratio
467+ else:
468+ position += scroll_progress_ratio
469+
470+ self.set_media_position(position)
471+ self.emit('position-changed')
472+
473
474=== modified file 'entertainerlib/tests/mock.py'
475--- entertainerlib/tests/mock.py 2009-05-27 10:27:48 +0000
476+++ entertainerlib/tests/mock.py 2009-05-28 19:44:40 +0000
477@@ -136,21 +136,19 @@
478 'stop' : ( gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, () ),
479 'skip-forward' : ( gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, () ),
480 'skip-backward' : ( gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, () ),
481- 'position_changed' : ( gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, () ),
482+ 'position-changed' : ( gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, () ),
483+ 'refresh' : ( gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, () ),
484 }
485
486 def __init__(self, stage=None, width=None, height=None):
487 '''Override any actual media player set up.'''
488 gobject.GObject.__init__(self)
489+ self.is_playing = False
490
491 def has_media(self):
492 '''See `MediaPlayer.has_media`.'''
493 return False
494
495- def is_playing(self):
496- '''See `MediaPlayer.is_playing`.'''
497- return False
498-
499 def play(self):
500 '''See `MediaPlayer.play`.'''
501
502
503=== modified file 'entertainerlib/tests/test_progressbar.py'
504--- entertainerlib/tests/test_progressbar.py 2009-05-17 12:26:38 +0000
505+++ entertainerlib/tests/test_progressbar.py 2009-06-06 15:45:10 +0000
506@@ -16,10 +16,6 @@
507 self.media_player = MockMediaPlayer()
508 self.progress_bar.media_player = self.media_player
509
510- def tearDown(self):
511- '''Clean up after the test.'''
512- EntertainerTest.tearDown(self)
513-
514 def test_create(self):
515 '''Test correct ProgressBar initialization.'''
516 self.assertTrue(isinstance(self.progress_bar, ProgressBar))

Subscribers

People subscribed via source and target branches