Package elisa :: Package plugins :: Package bad :: Package poblenou_frontend :: Module player_view
[hide private]
[frames] | no frames]

Source Code for Module elisa.plugins.bad.poblenou_frontend.player_view

  1  # -*- coding: utf-8 -*- 
  2  # Elisa - Home multimedia server 
  3  # Copyright (C) 2006-2008 Fluendo Embedded S.L. (www.fluendo.com). 
  4  # All rights reserved. 
  5  # 
  6  # This file is available under one of two license agreements. 
  7  # 
  8  # This file is licensed under the GPL version 3. 
  9  # See "LICENSE.GPL" in the root of this distribution including a special 
 10  # exception to use Elisa with Fluendo's plugins. 
 11  # 
 12  # The GPL part of Elisa is also available under a commercial licensing 
 13  # agreement from Fluendo. 
 14  # See "LICENSE.Elisa" in the root directory of this distribution package 
 15  # for details on that license. 
 16   
 17   
 18  __maintainer__ = 'Florian Boucault <florian@fluendo.com>' 
 19  __maintainer2__ = 'Lionel Martin <lionel@fluendo.com>' 
 20   
 21  import pygst 
 22  pygst.require('0.10') 
 23  import gst 
 24   
 25  from elisa.core import plugin_registry, common 
 26  from elisa.core import player 
 27  from elisa.extern.translation import gettexter, N_, Translatable 
 28  T_ = gettexter('elisa-poblenou-frontend') 
 29   
 30  import pgm, gst, math 
 31   
 32  from pgm.graph.group import Group 
 33  from pgm.graph.image import Image 
 34  from pgm.graph.text import Text 
 35   
 36  from poblenou_widgets.player_osd import PlayerOsd 
 37  from poblenou_widgets.volume_osd import VolumeOsd 
 38  from poblenou_widgets.dock import Dock 
 39   
 40  from twisted.internet import reactor 
 41   
42 -class VisualisationError(Exception):
43 pass
44 45 # The Visualisation Fake
46 -class ElisaVisualisationBin(gst.Bin):
47
48 - def __init__(self, visualisation, size, pgm_image):
49 """ 50 The ElisaVisualisationBin is a wrapper for the different visualisation 51 elements of GStreamer. 52 53 @param visualisation: the GStreamer Element to use 54 @type visualisation: string 55 @param size: a size in pixel to calculate the visualisation 56 size for 57 @type size: int 58 @param pgm_image: the pgm_image that is used for sinking 59 @type pgm_image: L{pgm.graph.image.Image} 60 """ 61 62 gst.Bin.__init__(self) 63 64 self._visualisation = visualisation 65 self._pgm_image = pgm_image 66 67 self._capsfilter = gst.element_factory_make('capsfilter') 68 self.add(self._capsfilter) 69 70 caps_pad = self._capsfilter.get_pad('src') 71 72 self._visu = gst.element_factory_make(self._visualisation) 73 self.add(self._visu) 74 75 height = size * pgm_image.height / pgm_image.width 76 77 visu_sink = self._visu.get_pad('sink') 78 visu_src = self._visu.get_pad('src') 79 80 caps = visu_src.get_pad_template().caps 81 82 if caps == None: 83 raise VisualisationError("The Visualisation element %s does not" \ 84 " have any Caps." % self._visualisation) 85 86 caps = caps.copy() 87 88 if caps.is_fixed() == False: 89 caps.make_writable() 90 for i in xrange(0, caps.get_size()): 91 cur_struct = caps.get_structure(i) 92 93 cur_struct.fixate_field_nearest_int('width', size) 94 95 cur_struct.fixate_field_nearest_int('height', height) 96 else: 97 raise VisualisationError("The Caps of the GstElement are fixed.") 98 99 self._capsfilter.set_property('caps', caps) 100 101 self._ghost_sink = gst.GhostPad('sink', visu_sink ) 102 self._ghost_src = gst.GhostPad('src', caps_pad) 103 104 self._visu.link(self._capsfilter) 105 self.add_pad(self._ghost_sink) 106 self.add_pad(self._ghost_src)
107 108 109 BasePlayerView = plugin_registry.get_component_class('base:player_view') 110 # The View
111 -class PlayerView(BasePlayerView):
112 supported_controllers = ('base:player_controller',) 113 114 config_doc = {'subtitle_font_size' : 'The size the subtitle font' \ 115 ' should have (in pgm-size)', 116 'visualisation' : 'Use this GStreamer Element for the' \ 117 ' visualisation in the player', 118 'visualisation_size' : 'the size the visualisation should'\ 119 ' use for calculating the real size' 120 } 121 default_config = {'subtitle_font_size' : '0.2', 122 'visualisation' : 'libvisual_jess', 123 'visualisation_size' : 400, 124 } 125
126 - def __init__(self):
127 super(PlayerView, self).__init__() 128 self.context_path = "pigment:pigment_context" 129 130 # group containing all the widgets of the player view 131 self.group = None 132 133 # background image displaying the video 134 self._background = None 135 136 # Rotating for the loading animation 137 self._interaction = None 138 self._loading = False 139 self._rotation_matrix = pgm.mat4x4_new_identity() 140 self._rotation_matrix.translate(0.5, 0.5, 0.0) 141 self._rotation_matrix.rotate_z(math.pi / 30.0) 142 self._rotation_matrix.translate(-0.5, -0.5, 0.0) 143 144 # subtitles overlay 145 self._subtitles = None 146 147 # status (current position, title...) on screen display 148 self._osd_status = None 149 150 # play/pause button 151 self._osd_play_button = None 152 153 # go back button 154 self._osd_back_button = None 155 156 # volume control 157 self._osd_volume = None 158 159 # two possible layouts: 'normal' or 'mouse' 160 self._osd_layout = 'normal' 161 self._osd_fadeout_delay = 3.0 162 163 self.debug("Connecting to player messages sent over the bus") 164 bus = common.application.bus 165 bus.register(self._player_loading, player.PlayerLoading) 166 bus.register(self._player_buffering, player.PlayerBuffering) 167 bus.register(self._player_starting, player.PlayerPlaying) 168 169 # The id of the reactor.callLater, for the time, when the subtitle 170 # should be cleared (because the duration of the buffer wants that) 171 self._sub_cleaner = None
172 173
174 - def initialize(self):
175 super(PlayerView, self).initialize() 176 self._subtitle_font_size = float(self.config.get('subtitle_font_size', 177 '0.24'))
178 179 180
181 - def update_subtitle_buffer(self, input_buffer):
182 def clean(): 183 self._subtitles.label = ''
184 185 if self._sub_cleaner and self._sub_cleaner.active(): 186 self._sub_cleaner.cancel() 187 188 self._subtitles.markup = str(input_buffer) 189 duration = input_buffer.duration 190 if duration > 0: 191 self._sub_cleaner = reactor.callLater(duration / gst.SECOND, clean)
192
193 - def _player_buffering(self, msg, sender):
194 self._player_loading(msg, sender)
195
196 - def _player_loading(self, msg, sender):
197 if sender == self.player: 198 self._background.bg_a = 0 199 self._enable_loading_animation(True)
200
201 - def _player_starting(self, msg, sender):
202 if sender == self.player: 203 self._enable_loading_animation(False) 204 if self.controller.model.media_type == 'video': 205 self._background.bg_a = 255
206
207 - def _error(self, msg, sender):
208 if sender == self.player: 209 self._enable_loading_animation(False) 210 content = self.controller.model.media_type 211 error_icon = self.frontend.theme.get_media("file_%s_unreadable" % 212 content) 213 self._background.clear() 214 self._interaction.set_from_file(error_icon) 215 self._interaction.set_name('error_icon') 216 super(PlayerView, self)._error(msg, sender)
217
218 - def mouse_moved(self, viewport, event):
219 if not self.controller.focused: 220 return False 221 222 if self.controller.model.media_type == 'audio': 223 self.osd_show(-1, True) 224 else: 225 self.osd_show(self._osd_fadeout_delay, True) 226 227 return True
228
229 - def osd_hide(self):
230 self._osd_status.hide() 231 self._osd_back_button.hide() 232 self._osd_volume.hide() 233 self._osd_play_button.hide()
234
235 - def osd_show(self, time_visible=-1, from_mouse=False, show_volume=False):
236 if self.frontend.context.touchscreen == True: 237 from_mouse = True 238 239 if from_mouse: 240 self._osd_back_button.show(time_visible) 241 else: 242 self._osd_back_button.hide() 243 244 if show_volume: 245 self._osd_volume.show(time_visible) 246 else: 247 self._osd_status.show(time_visible) 248 self._osd_play_button.show(time_visible)
249
250 - def _background_clicked(self, drawable, x, y, z, button, time):
251 if self.group.visible == False or self.group.opacity == 0.0: 252 return False 253 254 if (not self.controller.focused) or \ 255 (self._background.storage_type == pgm.IMAGE_EMPTY): 256 return False 257 258 if self.player.state == player.STATES.STOPPED: 259 return False 260 261 if not self.controller.focused: 262 self.controller.focus() 263 else: 264 if self.controller.model.media_type == 'audio': 265 self.osd_show(-1,True) 266 else: 267 self.osd_show(self._osd_fadeout_delay, True) 268 269 return True
270
271 - def _osd_status_clicked(self, drawable, x, y, z, button, time):
272 if not self.controller.focused: 273 return False 274 275 # FIXME: should handle setting the current position in the media 276 277 return False
278
279 - def _osd_back_button_clicked(self, drawable, x, y, z, button, time):
280 if not self.controller.focused: 281 return False 282 self.controller.parent.focus() 283 return True
284 285
286 - def _osd_play_button_clicked(self, drawable, x, y, z, button, time):
287 if not self.controller.focused: 288 return False 289 290 if self.controller.model.state == player.STATES.PLAYING: 291 self.controller.model.state = player.STATES.PAUSED 292 else: 293 self.controller.model.state = player.STATES.PLAYING 294 295 return True
296
297 - def _update_play_button(self, state):
298 if state == player.STATES.PAUSED: 299 icon = self._play_button_image 300 else: 301 icon = self._pause_button_image 302 303 self._osd_play_button.image_from_path(icon)
304
305 - def _enable_loading_animation(self, enabled):
306 if enabled and self.frontend: 307 if self._loading: 308 return 309 wait_icon = self.frontend.theme.get_media("waiting_icon") 310 self._interaction.visible = True 311 self._interaction.set_from_file(wait_icon) 312 self._interaction.set_name('wait icon') 313 self._loading = True 314 reactor.callLater(0.023, self._transform_mapping_matrix_cb) 315 else: 316 self._loading = False 317 self._interaction.clear() 318 self._interaction.visible = False 319 self._background.visible = True
320
321 - def _transform_mapping_matrix_cb(self):
322 if self._loading: 323 self._interaction.mapping_matrix *= self._rotation_matrix 324 reactor.callLater(0.017, self._transform_mapping_matrix_cb)
325
326 - def attribute_set(self, origin, key, old_value, new_value):
327 # On the first initialize 328 if key == 'uri' and old_value == None: 329 # Visualisation Support 330 try: 331 self._visualisation = ElisaVisualisationBin( 332 self.config.get('visualisation'), 333 int(self.config.get('visualisation_size')), 334 self._background) 335 self.player.visualisation = self._visualisation 336 except gst.PluginNotFoundError, exc: 337 if isinstance(exc, gst.PluginNotFoundError): 338 self.warning("VisualisationElement '%s' not found." \ 339 " Deactivated." % exc) 340 341 except VisualisationError, exc: 342 self.warning("Visualisation deactivated: %s" % exc) 343 self.player.visualisation = None 344 345 super(PlayerView, self).attribute_set(origin, key, old_value, new_value)
346
347 - def frontend_changed(self, previous_frontend, new_frontend):
348 if new_frontend == None: 349 return 350 351 super(PlayerView,self).frontend_changed(previous_frontend, new_frontend) 352 353 viewport = new_frontend.context.viewport_handle 354 viewport.connect("motion-notify-event", self.mouse_moved) 355 356 if self.group == None: 357 canvas = self.frontend.context.viewport_handle.get_canvas() 358 359 # FIXME: why is the OSD not in self.group? Because of resizing 360 # issue that will be solved when drawable.regenerate is in place 361 # in Pigment. 362 363 # create the group containing all the widgets of the player 364 self.group = Group(canvas, pgm.DRAWABLE_FAR) 365 self.group.visible = True 366 self.context_handle = self.group 367 368 # media retrieval from the theme 369 # FIXME: need theme_changed support 370 theme = self.frontend.theme 371 self._normal_osd_status_bg = theme.get_media("dock_background") 372 self._mouse_osd_status_bg = theme.get_media("dock_background_mouse") 373 self._play_button_image = theme.get_media("play_button_mouse") 374 self._pause_button_image = theme.get_media("pause_button_mouse") 375 376 self._create_background() 377 self._create_interaction() 378 self._create_subtitles() 379 self._create_playpause_button() 380 self._create_osd_status_widget() 381 self._create_osd_volume_widget() 382 self._create_back_widget()
383 384
385 - def _create_subtitles(self):
386 canvas = self.frontend.context.viewport_handle.get_canvas() 387 388 # create the subtitles overlay 389 self._subtitles = Text() 390 self._subtitles.font_height = self._subtitle_font_size 391 self._subtitles.weight = pgm.TEXT_WEIGHT_BOLD 392 393 # 2 lines of subtitles should fit 394 height = self._subtitle_font_size * 2 395 self._subtitles.position = (0.0, canvas.height - height - 0.1, 1.1) 396 self._subtitles.size = (canvas.width, height) 397 398 self._subtitles.alignment = pgm.TEXT_ALIGN_CENTER 399 self._subtitles.bg_color = (0, 255, 0, 0) 400 self._subtitles.fg_color = (255, 255, 255, 255) 401 self._subtitles.outline_width = self._subtitles.height * 0.015 402 self._subtitles.font_family = "Nimbus Sans L Bold" 403 self._subtitles.outline_color = (0, 0, 0, 255) 404 self._subtitles.opacity = 255 405 self._subtitles.visible = True 406 self.group.add(self._subtitles) 407 408 self.player.subtitle_callback = self.update_subtitle_buffer
409
410 - def _create_playpause_button(self):
411 canvas = self.frontend.context.viewport_handle.get_canvas() 412 413 px, py = 0.7, 0.8 414 iw, ih = canvas.width, canvas.height 415 osd_status_w = iw * px 416 osd_status_h = ih * 0.15 417 418 self._osd_play_button = Dock(canvas, 419 pgm.DRAWABLE_FAR, 420 osd_status_h, osd_status_h, 421 self._pause_button_image) 422 self._osd_play_button.opacity = 0 423 self._osd_play_button.visible = True 424 425 px, py = 0.7, 0.8 426 iw, ih = canvas.width, canvas.height 427 osd_status_w = iw * px 428 osd_status_h = ih * 0.15 429 430 self._osd_status_position = ((1-px) * iw/2.0, ih * py, 0.0) 431 x = self._osd_status_position[0] - osd_status_h/2.0 + iw * 0.02 432 self._osd_play_button.position = (x, 433 self._osd_status_position[1], 434 self._osd_status_position[2]) 435 436 self._osd_play_button.connect("clicked", self._osd_play_button_clicked) 437
438 - def _create_osd_status_widget(self):
439 canvas = self.frontend.context.viewport_handle.get_canvas() 440 theme = self.frontend.theme 441 442 osd_status_bar_bg = theme.get_media("dock_bar_bg") 443 osd_status_bar_fg = theme.get_media("dock_bar_fg") 444 445 px, py = 0.7, 0.8 446 iw, ih = canvas.width, canvas.height 447 osd_status_w = iw * px 448 osd_status_h = ih * 0.15 449 self._osd_status = PlayerOsd(canvas, 450 pgm.DRAWABLE_FAR, 451 10000, 452 osd_status_w, osd_status_h, 453 osd_status_bar_bg, 454 osd_status_bar_fg, 455 self._normal_osd_status_bg) 456 self._osd_status.opacity = 0 457 self._osd_status.position = self._osd_status_position 458 self._update_osd_layout() 459 self._osd_status.visible = True 460 self._osd_status.connect("clicked", self._osd_status_clicked)
461
462 - def _create_osd_volume_widget(self):
463 canvas = self.frontend.context.viewport_handle.get_canvas() 464 iw, ih = canvas.width, canvas.height 465 466 trans = common.application.translator 467 volume_name = trans.translateTranslatable(T_(N_("Volume: %s%%")), 468 self.frontend.languages) 469 470 osd_volume_bg = self.frontend.theme.get_media("vol_dock_background") 471 472 self._osd_volume = VolumeOsd(canvas, 473 pgm.DRAWABLE_FAR, 474 volume_name, 475 iw*0.28, ih*0.1, 476 osd_volume_bg) 477 self._osd_volume.position = (0.15, 0.10, 0) 478 self._osd_volume.opacity = 0 479 self._osd_volume.visible = True
480
481 - def _create_back_widget(self):
482 canvas = self.frontend.context.viewport_handle.get_canvas() 483 484 px, py = 0.7, 0.8 485 iw, ih = canvas.width, canvas.height 486 osd_status_w = iw*px 487 osd_status_h = ih*0.15 488 back_button_image = self.frontend.theme.get_media("back_button") 489 back_size = osd_status_h 490 offset = ih * 0.02 491 492 self._osd_back_button = Dock(canvas, 493 pgm.DRAWABLE_FAR, 494 back_size, back_size, 495 back_button_image) 496 self._osd_back_button.position = (iw - back_size - offset, offset, 0) 497 self._osd_back_button.opacity = 0 498 self._osd_back_button.visible = True 499 self._osd_back_button.connect("clicked", self._osd_back_button_clicked)
500
501 - def _create_interaction(self):
502 canvas = self.frontend.context.viewport_handle.get_canvas() 503 504 self._interaction = Image() 505 self._interaction.position = (0.0, 0.0, 0.0) 506 width = canvas.width / 2.0 507 height = canvas.height / 2.0 508 self._interaction.width = width 509 self._interaction.height = height 510 self._interaction.x = width / 2.0 511 self._interaction.y = height / 2.0 512 513 # downscaling 514 scale = pgm.mat4x4_new_identity() 515 scale.translate(0.5, 0.5, 0.0) 516 factor = 2.0 517 scale.scale(factor, factor, 0.0) 518 scale.translate(-0.5, -0.5, 0.0) 519 # self._interaction.mapping_matrix *= scale 520 521 self._interaction.bg_color = (0, 0, 0, 0) 522 self._interaction.opacity = 255 523 self._interaction.visible = True 524 self._interaction.connect("clicked", self._background_clicked) 525 self.group.add(self._interaction)
526 527
528 - def _create_background(self):
529 canvas = self.frontend.context.viewport_handle.get_canvas() 530 531 self._background = Image() 532 self._background.position = (0.0, 0.0, 0.0) 533 self._background.size = canvas.size 534 535 self._background.bg_color = (0, 0, 0, 0) 536 self._background.opacity = 255 537 self._background.visible = True 538 self._background.connect("clicked", self._background_clicked) 539 self.group.add(self._background) 540 541 # create the output video sink and link it to the player 542 video_sink = gst.element_factory_make('pgmimagesink') 543 video_sink.set_property('image', self._background) 544 self.player.video_sink = video_sink
545
546 - def _update_osd_layout(self):
547 px, py = 0.7, 0.8 548 canvas = self.frontend.context.viewport_handle.get_canvas() 549 iw, ih = canvas.width, canvas.height 550 osd_status_h = ih*0.15 551 552 x = self._osd_status_position[0] + (osd_status_h/2.0) 553 self._osd_status.position = (x, 554 self._osd_status_position[1], 555 self._osd_status_position[2]) 556 self._osd_status.image_from_path(self._mouse_osd_status_bg)
557 558
559 - def uri_changed(self, uri):
560 self._background.clear() 561 if uri == None: 562 label = '' 563 else: 564 label = uri.label 565 if isinstance(label, Translatable): 566 translator = common.application.translator 567 label = translator.translateTranslatable(label, 568 self.frontend.languages) 569 570 self._osd_status.title = label 571 super(PlayerView, self).uri_changed(uri)
572
573 - def focused_changed(self, new_focused):
574 if not new_focused: 575 self.osd_hide() 576 else: 577 if self.controller.state == player.STATES.PLAYING: 578 if self.controller.model.media_type == 'audio': 579 self.osd_show() 580 self.group.visible = True 581 else: 582 self.osd_show(self._osd_fadeout_delay, True)
583
584 - def state_changed(self, state):
585 super(PlayerView, self).state_changed(state) 586 self.debug("state changed to: %r", state) 587 588 self._update_play_button(state) 589 590 if self.controller.model.uri and self.controller.focused: 591 # FIXME: this part is not working in playlist on the first item 592 if self.controller.model.media_type == 'audio': 593 osd_time = -1 594 else: 595 osd_time = self._osd_fadeout_delay 596 597 if state == player.STATES.PAUSED: 598 self.osd_show(osd_time) 599 self.group.visible = True 600 elif state == player.STATES.PLAYING: 601 self.osd_show(osd_time) 602 elif state == player.STATES.STOPPED: 603 self._background.clear() 604 else: 605 self.group.visible = True 606 # FIXME: this whole refresh_dock looks hackish 607 reactor.callLater(0.05, self.refresh_dock) 608 609 elif state == player.STATES.STOPPED: 610 self._background.clear()
611
612 - def seek_to_changed(self, position):
613 super(PlayerView, self).seek_to_changed(position) 614 self._osd_status.time = self.player.position / 1000000.00 615 if self.controller.model.media_type == 'audio': 616 self.osd_show() 617 else: 618 self.osd_show(self._osd_fadeout_delay)
619
620 - def volume_changed(self, value):
621 super(PlayerView, self).volume_changed(value) 622 self._osd_volume.volume = value * 50.0 623 if self.controller.state in (player.STATES.PLAYING, 624 player.STATES.PAUSED) and \ 625 self.controller.focused: 626 self.osd_show(self._osd_fadeout_delay, False, True)
627
628 - def pause_requested_changed(self, value):
629 # show the dock if the user press OK once 630 if self.controller.model.state == player.STATES.PLAYING and value: 631 if not self._osd_status.is_visible(): 632 self.osd_show(self._osd_fadeout_delay, True) 633 else: 634 self.controller.model.state = player.STATES.PAUSED
635
636 - def refresh_dock(self):
637 # FIXME: a division in a code called every 50 ms is not good 638 duration = int(self.player.duration / 1000000) 639 self._osd_status.playing_length = duration 640 641 self._osd_status.time = self.player.position / 1000000.0 642 643 if self.controller.state == player.STATES.PLAYING: 644 reactor.callLater(0.05, self.refresh_dock)
645