# -*- coding: utf-8 -*-
# Moovida - Home multimedia server
# Copyright (C) 2006-2009 Fluendo Embedded S.L. (www.fluendo.com).
# All rights reserved.
#
# This file is available under one of two license agreements.
#
# This file is licensed under the GPL version 3.
# See "LICENSE.GPL" in the root of this distribution including a special
# exception to use Moovida with Fluendo's plugins.
#
# The GPL part of Moovida is also available under a commercial licensing
# agreement from Fluendo.
# See "LICENSE.Moovida" in the root directory of this distribution package
# for details on that license.

from elisa.core import component
from elisa.core.utils import locale_helper
from elisa.plugins.poblesec.widgets.player.status_display import VideoStatusDisplay,\
                                                        BARS
from elisa.plugins.poblesec.widgets.player.control_ribbon import ControlRibbon,\
                                                                 Control

from elisa.plugins.poblesec import constants
from elisa.plugins.poblesec import modal_popup
from elisa.plugins.poblesec.music_library import AlbumsViewMode
from elisa.plugins.database.models import MusicTrack, Video, Movie, TVEpisode
from elisa.plugins.base.models.audio import TrackModel as BaseTrackModel
from elisa.plugins.base.models.video import VideoModel
from elisa.plugins.poblesec.player_base import BasePlayerController
from elisa.plugins.pigment.graph.image import Image
from elisa.plugins.pigment.widgets.widget import Widget
from elisa.plugins.pigment.widgets.const import *
from elisa.plugins.pigment.widgets.theme import Theme
from elisa.plugins.pigment.widgets.panel import PiecePanel
from elisa.plugins.pigment.widgets.button import Button
from elisa.plugins.pigment.widgets.const import STATE_NORMAL, STATE_SELECTED, \
                                                STATE_PRESSED

from elisa.plugins.poblesec.widgets.button import RelookSmoothStatesWidget
from elisa.plugins.poblesec.videoplayer_controls import PlayPauseControl, \
                                                        StopControl, \
                                                        SkipPreviousControl, \
                                                        SkipNextControl, \
                                                        VolumeUpControl, \
                                                        VolumeDownControl, \
                                                        SubtitleStreamControl, \
                                                        AudioStreamControl

from elisa.core.input_event import *
from elisa.core.media_uri import MediaUri
from elisa.core.utils.i18n import install_translation
from elisa.core.utils import misc

import platform

try:
    from elisa.plugins.database.models import File as DBFile
except ImportError:
    # database missing
    DBFile = None

from elisa.core import common

_ = install_translation('poblesec')

from elisa.plugins.base.models.media import PlayableModel, PlaylistModel

from elisa.core.log import Loggable
import gobject

from twisted.internet import reactor, defer
from elisa.plugins.pigment.animation.animation import DECELERATE
from pgm import imaging
import pgm, time
import gst
from gtk import gdk
from gst import pbutils
import math
import os
import platform


def iso_639_to_human_readable(iso_code):
    #FIXME: complete that or find a magical module that knows that.
    if iso_code == 'eng':
        return _("English")
    elif iso_code == 'fre':
        return _("French")
    else:
        return iso_code

def get_default_audio_sink():
    """ Return a GStreamer audio sink name given the current user's
    desktop environment.

    @rtype: C{str}
    @returns: a GStreamer sink name suitable for current desktop
              environment.
    """
    desktop_name = misc.get_user_desktop_name()
    # very basic default sink choosing, if we are launched under
    # GNOME, use gconfaudiosink.
    if desktop_name == 'gnome':
        sink = 'gconfaudiosink'
    else:
        sink = 'autoaudiosink'
    return sink

class VisualisationError(Exception):
    pass

# The Visualisation Fake
class ElisaVisualisationBin(gst.Bin):

    def __init__(self, visualisation, size, pgm_image):
        """
        The ElisaVisualisationBin is a wrapper for the different visualisation
        elements of GStreamer.

        @param visualisation:   the GStreamer Element to use
        @type visualisation:    string
        @param size:            a size in pixel to calculate the visualisation
                                size for
        @type size:             int
        @param pgm_image:       the pgm_image that is used for sinking
        @type pgm_image:         L{pgm.graph.image.Image}
        """

        gst.Bin.__init__(self)

        self._visualisation = visualisation

        self._capsfilter = gst.element_factory_make('capsfilter')
        self.add(self._capsfilter)

        caps_pad = self._capsfilter.get_pad('src')

        self._visu = gst.element_factory_make(self._visualisation)
        self.add(self._visu)

        height = int(round(size * pgm_image.absolute_height / pgm_image.absolute_width))
        height = (height + 3) & ~3

        visu_sink = self._visu.get_pad('sink')
        visu_src = self._visu.get_pad('src')

        caps = visu_src.get_pad_template().caps

        if caps == None:
            raise VisualisationError("The Visualisation element %s does not" \
                                     " have any Caps." % self._visualisation)

        caps = caps.copy()

        if caps.is_fixed() == False:
            caps.make_writable()
            for i in xrange(0, caps.get_size()):
                cur_struct = caps[i]

                cur_struct.fixate_field_nearest_int('width', size)

                cur_struct.fixate_field_nearest_int('height', height)
        else:
            raise VisualisationError("The Caps of the GstElement are fixed.")

        self._capsfilter.set_property('caps', caps)

        self._ghost_sink = gst.GhostPad('sink', visu_sink )
        self._ghost_src = gst.GhostPad('src', caps_pad)

        self._visu.link(self._capsfilter)
        self.add_pad(self._ghost_sink)
        self.add_pad(self._ghost_src)


class BackButtonPanel(PiecePanel):
    pass


class BackButton(Button):

    def _create_widgets(self):
        super(BackButton, self)._create_widgets()
        state_widgets = {STATE_NORMAL: BackButtonPanel,
                         STATE_SELECTED: BackButtonPanel,
                         STATE_PRESSED: BackButtonPanel}
        self.panel = RelookSmoothStatesWidget(state_widgets=state_widgets)
        self.panel.visible = True
        self.add(self.panel, forward_signals=False)

    def do_state_changed(self, previous_state):
        self.panel._current_widget.middle_left.interp = pgm.IMAGE_NEAREST
        self.panel._current_widget.middle_middle.interp = pgm.IMAGE_NEAREST
        self.panel.state = self.state
        super(BackButton, self).do_state_changed(previous_state)


class MouseOsd(Widget):

    def __init__(self):
        super(MouseOsd, self).__init__()

        # back button to leave the player
        self.back_button = BackButton()
        self.back_button.text.label = _("Back")
        self.back_button.visible = True
        self.add(self.back_button)

        # animation facilities
        settings = {'duration': 350,
                    'transformation': DECELERATE}
        self.animated.setup_next_animations(**settings)

        self.update_style_properties(self.style.get_items())

    def hide(self):
        """
        Hide the widget
        """
        self.animated.opacity = 0

    def show(self):
        """
        Show the widget
        """
        if self.animated.opacity != 255:
            self.animated.opacity = 255

class CoverOverlay(Widget):
    def __init__(self):
        super(CoverOverlay, self).__init__()

        # cover overlay
        self.cover = Image()
        self.cover.bg_color = (0, 0, 0, 0)
        self.cover.layout = pgm.IMAGE_ZOOMED
        self.add(self.cover)
        self.cover.size = (1.0, 1.0)
        self.cover.position = (0.0, 0.0, -0.2)
        self.cover.visible = True

        # animation facilities
        settings = {'duration': 350,
                    'transformation': DECELERATE}
        self.animated.setup_next_animations(**settings)

    def hide(self):
        """
        Hide the widget
        """
        self.animated.opacity = 0

    def show(self):
        """
        Show the widget
        """
        if self.animated.opacity != 255:
            self.animated.opacity = 255


class PlayerOsd(Widget):
    """
    On screen display widget for the player.

    Signals:
      - 'shown': emitted when the widget is displayed.
      - 'hidden': emitted when the widget is hidden.
    """

    __gsignals__ = {'shown':
                    (gobject.SIGNAL_RUN_LAST,
                     gobject.TYPE_BOOLEAN,
                     ()),
                    'hidden':
                    (gobject.SIGNAL_RUN_LAST,
                     gobject.TYPE_BOOLEAN,
                     ())}

    status_widget = VideoStatusDisplay

    def __init__(self):
        super(PlayerOsd, self).__init__()

        self.status = self.status_widget()
        self.add(self.status)
        self.status.visible = True

        pbc = self.status.status_display_details.progress_bars
        pbc.buffering_bar_label.label = _('Buffering...')

        self.control_ribbon = ControlRibbon()
        self.add(self.control_ribbon)
        self.control_ribbon.visible = True

        # mouse on screen display only shown on mouse move
        self.mouse_osd = MouseOsd()
        self.add(self.mouse_osd)
        self.mouse_osd.visible = True

        # a total background
        self.background = Image()
        self.add(self.background)
        self.background.visible = True

        # create the cover overlay
        self.cover_overlay = CoverOverlay()
        self.cover_overlay.visible = True
        self.add(self.cover_overlay)

        # create animation stuff
        settings = {'duration': 350,
                    'transformation': DECELERATE}
        self.animated.setup_next_animations(**settings)

        self.opacity = 0
        # FIXME: for some reason the opacity is not taken into account for the
        # ribbon: it is always visible. The following hack fixes it for an
        # unknown reason.
        self.animated.opacity = 0

        self.time_before_hiding = 3.0
        self.controls_time_before_hiding = self.time_before_hiding

        self._hiding_dfr = None

        self.update_style_properties(self.style.get_items())

    def update_style_properties(self, props=None):
        if props is None:
            return

        remaining_props = {}

        for key, value in props.iteritems():
            if key == 'background-gradient':
                # We need 7 white space separated values to compute the
                # background: resource_name, top_x, top_y, top_a, end_x, end_y,
                # end_a.
                try:
                    resource, top_x, top_y, top_a, end_x, end_y, end_a = \
                        value.split()
                except ValueError:
                    continue
                # getting a resource
                path = Theme.get_default().get_resource(resource)
                src = gdk.pixbuf_new_from_file(path)
                dst = imaging.linear_alpha_gradient(src, float(top_x), float(top_y), float(top_a),
                                                    float(end_x), float(end_y), float(end_a))
                self.background.set_from_pixbuf(dst)
            else:
                remaining_props[key] = value

        if len(remaining_props) > 0:
            return super(PlayerOsd, self).update_style_properties(remaining_props)

    def hide(self):
        """
        Hide the OSD.
        """
        if self._hiding_dfr and self._hiding_dfr.active():
            self._hiding_dfr.cancel()
        self.status.status_display_details.progress_bars.set_sensitive(False)
        self.animated.opacity = 0
        self.control_ribbon.hide()
        self.mouse_osd.hide()
        self.cover_overlay.hide()
        self.emit('hidden')

    def show(self, timeout=True, with_ribbon=False, with_mouse=False,
             with_cover=False):
        """
        Show the OSD. Optionally show ribbon controls, mouse controls and
        cover overlay.

        @param timeout: whether or not the OSD will hide automatically after
                        L{time_before_hiding} seconds
        @type timeout:  C{bool}
        @param with_ribbon: whether or not to display ribbon controls
        @bool with_ribbon:  bool
        @param with_mouse:  whether or not to display mouse controls
        @bool with_mouse:   bool
        @param with_cover:  whether or not to display the cover
                            overlay
        @bool with_cover:   bool
        """
        if self._hiding_dfr and self._hiding_dfr.active():
            self._hiding_dfr.cancel()
        
        if self.time_before_hiding and timeout:
            self._hiding_dfr = reactor.callLater(self.time_before_hiding, self.hide)
        else:
            # hide the control ribbon after some time of inactivity
            self._hiding_dfr = reactor.callLater(self.controls_time_before_hiding,
                                                 self._hide_ribbon_and_mouse_osd)

        if with_ribbon:
            self.control_ribbon.show()
        if with_mouse:
            self.mouse_osd.show()
        if with_cover:
            self.cover_overlay.show()

        if self.animated.opacity != 255:
            # we only do these operations if the osd is not already shown or
            # in the process of being shown.

            self.status.status_display_details.progress_bars.set_sensitive(True)
            self.animated.opacity = 255

            self.emit('shown')

    def _hide_ribbon_and_mouse_osd(self):
        self.mouse_osd.hide()
        self.control_ribbon.hide()

    @property
    def is_shown(self):
        """
        Is the control ribbon shown or not to the user.
        Note: this is not named "visible" to avoid interfering with the Pigment
        Graph dark magic.

        @returns: whether or not the ribbon is visible
        @rtype:   C{bool}
        """
        return self.animated.opacity > 0

class Player(gobject.GObject, Loggable):

    STOPPED = 0
    PLAYING = 1
    PAUSED = 2
    BUFFERING = 3

    __gsignals__ = {'status-changed':
                              (gobject.SIGNAL_RUN_LAST,
                              gobject.TYPE_BOOLEAN,
                              (gobject.TYPE_INT,)),
                    'volume-changed':
                              (gobject.SIGNAL_RUN_LAST,
                              gobject.TYPE_BOOLEAN,
                              (gobject.TYPE_FLOAT,)),
                    'playback-ended' :
                              (gobject.SIGNAL_RUN_LAST,
                              gobject.TYPE_NONE,
                              ()),
                    'player-missing-decoder':
                              (gobject.SIGNAL_RUN_LAST,
                               gobject.TYPE_NONE,
                               (gobject.TYPE_STRING, gobject.TYPE_STRING)),
                    'metadata-found':
                              (gobject.SIGNAL_RUN_LAST,
                               gobject.TYPE_NONE,
                               (gobject.TYPE_PYOBJECT,)),
                    'player-codec-error':
                              (gobject.SIGNAL_RUN_LAST,
                               gobject.TYPE_NONE,
                               ()),
                    'player-unknown-error':
                              (gobject.SIGNAL_RUN_LAST,
                               gobject.TYPE_NONE,
                               (gobject.TYPE_STRING,)),
                    'current-model-changed':
                              (gobject.SIGNAL_RUN_LAST,
                               gobject.TYPE_NONE,
                               (gobject.TYPE_PYOBJECT,)),
                    'current-playable-model-changed':
                              (gobject.SIGNAL_RUN_LAST,
                               gobject.TYPE_NONE,
                               (gobject.TYPE_PYOBJECT,))
                              }

    def __init__(self, config):
        super(Player, self).__init__()
        # GStreamer pipeline setup

        self.config = config

        # make sure typefindfunctions is loaded so we can remove the typefinders
        if platform.system() == 'Windows':
            gst.plugin_load_by_name('typefindfunctions')
            registry = gst.registry_get_default()
            typefinders = gst.type_find_factory_get_list()
            for typefinder in typefinders:
                if typefinder.get_name() in ('application/x-ape',
                    'application/x-apetag'):
                    registry.remove_feature(typefinder)

        self.pgm_sink = gst.element_factory_make('pgmimagesink')
        self.pipeline = gst.element_factory_make('playbin')
        self.pipeline.set_property('video-sink', self.pgm_sink)

        self.status = self.STOPPED
        self.last_error = None
        self.filename = None
        self.image = None
        self.volume_max = 2.0
        self.volume_increment = 0.10
        self.volume_decrement = 0.30
        self.seek_backward_seconds = 60
        self.seek_forward_seconds = 60
        self.key_repeat_count_second = 0.150
        self._last_key_time = time.time()
        self.playlist = None
        self.set_playlist(PlaylistModel())

        self._video_size = None

        # These are used to keep track of what interesting streams are
        # available
        self._subtitle_stream_infos = []
        self._subtitle_index = -1
        self._audio_stream_infos = []

        # FIXME: disconnect from there
        self.pipeline.connect('notify::stream-info',
                              self._on_stream_infos_notify)

        # Execute callback when the source is created
        self.pipeline.connect('notify::source', self._source_created_cb)

        pbus = self.pipeline.get_bus()
        pbus.connect('message::eos', self._message_eos_cb)
        pbus.connect('message::error', self._message_error_cb)
        pbus.connect('message::state-changed', self._message_state_changed_cb)
        pbus.connect('message::buffering', self._message_buffering_cb)
        pbus.connect('message::element', self._message_element_cb)
        pbus.connect('message::tag', self._message_tag_cb)
        pbus.add_signal_watch()


    def _on_stream_infos_notify(self, pipeline, *args):
        def do_it(self, pipeline):

            stream_infos = pipeline.get_property('stream-info-value-array')

            audio_stream_infos = []
            subtitle_stream_infos = []

            for stream_info in stream_infos:
                stream_type = stream_info.get_property('type').value_nick
                if stream_type == 'audio':
                    audio_stream_infos.append(stream_info)
                elif stream_type in ('text', 'subpicture'):
                    subtitle_stream_infos.append(stream_info)
                elif stream_type == 'video':
                    # connect on pad's notify::caps so we can get video size
                    # if the caps are not already negociated
                    pad = stream_info.get_property('object')
                    caps = pad.get_negotiated_caps()
                    if caps:
                        self._video_size = (caps[0]['width'], caps[0]['height'])
                        self._update_sub_font_size()
                    else:
                        pad.connect("notify::caps", self._on_video_caps)
            self._audio_stream_infos = audio_stream_infos
            self._subtitle_stream_infos = subtitle_stream_infos
            if subtitle_stream_infos:
                # playbin loads the first subtitle stream if there's any
                self._subtitle_index = 0
            else:
                self._subtitle_index = -1

        reactor.callFromThread(do_it, self, pipeline)

    def _on_video_caps(self, pad, *args):
        caps = pad.get_negotiated_caps()
        if caps:
            self._video_size = (caps[0]['width'], caps[0]['height'])
            self._update_sub_font_size()

    def _update_sub_font_size(self):
        font_name = self.config['subtitle_font']
        font_size = self._compute_subfont_size()
        font_desc = '%s %s' % (font_name, font_size)
        self.pipeline.set_property('subtitle-font-desc', font_desc)

    def _message_eos_cb(self, bus, message):
        self.pipeline.set_state(gst.STATE_NULL)
        self.status = self.STOPPED
        self.emit('status-changed', self.status)
        self.play_next()

    def _message_buffering_cb(self, bus, message):
        if self.status == self.BUFFERING:
            # yet another buffering message. ignore it
            return
        self.status = self.BUFFERING
        self.emit('status-changed', self.status)

    def _message_error_cb(self, bus, message):
        err, msg = message.parse_error()
        self.last_error = (err, msg)
        self.warning("Gstreamer %s:%s" % (err, msg))
        self.pipeline.set_state(gst.STATE_NULL)
        code = message.structure['gerror'].code
        if code == gst.STREAM_ERROR_CODEC_NOT_FOUND:
            self.emit('player-codec-error')
        else:
            self.status = self.STOPPED
            self.emit('status-changed', self.status)
            if code != gst.STREAM_ERROR_FAILED:
                self.emit('player-unknown-error', err)
            else:
                self.emit('player-codec-error')

    def _message_state_changed_cb(self, bus, message):
        if message.src != self.pipeline:
            return

        old_state, new_state, pending = message.parse_state_changed()

        if new_state == gst.STATE_READY and old_state == gst.STATE_NULL:
            # pipeline is prerolling, we handle this as buffering
            self.status = self.BUFFERING
            self.emit('status-changed', self.status)

        elif new_state == gst.STATE_PLAYING:
            if self.status != self.PLAYING:
                self.status = self.PLAYING
                self.emit('status-changed', self.status)
        elif new_state == gst.STATE_PAUSED:
            if old_state == gst.STATE_READY:
                # pipeline is still prerolling, we want to continue to
                # handle this as buffering
                self.status = self.BUFFERING
                self.emit('status-changed', self.status)

            elif self.status != self.PAUSED:
                self.status = self.PAUSED
                self.emit('status-changed', self.status)
        elif self.status != self.STOPPED:
            self.status = self.STOPPED
            self.emit('status-changed', self.status)

    def _message_element_cb(self, bus, message):
        if pbutils.is_missing_plugin_message(message):
            caps =  message.structure['detail']
            details = caps.to_string().split(', ')
            mimetype = details[0]
            decoder = pbutils.get_decoder_description(mimetype)
            self.emit('player-missing-decoder', mimetype, decoder)
        elif message.structure.get_name() == 'redirect':
            # we got a new location for current uri. Only the filename
            # of the uri is concerned. So we build a new uri with
            # updated filename and reset the pipeline state.
            current_uri = MediaUri(self.pipeline.get_property('uri'))
            filename = current_uri.filename
            idx = unicode(current_uri).find(filename)
            new_filename = message.structure['new-location']
            new_uri = current_uri[:idx] + new_filename
            self.pipeline.set_property('uri', new_uri)
            self.pipeline.set_state(gst.STATE_NULL)
            self.pipeline.set_state(gst.STATE_PLAYING)

    def _message_tag_cb(self, bus, message):
        self.emit("metadata-found", dict(message.parse_tag()))

    def _source_created_cb(self, pipeline, *args): 
        current_model = self.playlist.current_playable_model 
        source = self.pipeline.get_property('source') 
        for key, val in current_model.source_properties.iteritems(): 
            # If this media has custom source properties, set them 
            try: 
                source.set_property(key, val) 
            except: 
                self.warning('Source element %s doesn t have property %s' % \
                              (source, key))

    def _load_subs(self, uri):
        found = False
        if uri.scheme == 'file':
            basename, ext = os.path.splitext(uri.filename)
            for sub_ext in ('srt', 'ssa', 'sub', 'txt'):
                sub_file = os.path.join(os.path.dirname(uri.path),
                                        "%s.%s" % (basename, sub_ext))
                if os.path.exists(sub_file):
                    file_uri = MediaUri("file://%s" % sub_file)
                    unicode_uri = unicode(file_uri)
                    sub_uri = \
                        unicode_uri.encode(locale_helper.gst_file_encoding())
                    self.pipeline.set_property('suburi', sub_uri)
                    self.info("Loaded subtitles at %r", sub_uri)
                    found = True
                    break
        if not found:
            self.info("No subtitles found for %r", uri)
            self.pipeline.set_property('suburi', '')

    def _compute_subfont_size(self):
        factor = float(self.config['subtitle_font_size_factor'])
        if self._video_size:
            font_size = self._video_size[1] * factor
        else:
            # defaults to font_size used in previous version of the player
            font_size = 14
        return font_size

    def set_drawable(self, value):
        self.image = value
        self.pgm_sink.set_property('image', value)

    def enqueue_to_playlist(self, media_model):
        self.playlist.append(media_model)

    def play_next(self):
        def model_error(result):
            if self.status != self.STOPPED:
                self.stop()
            self.emit('playback-ended')

        def got_track(track):
            return self._play_playable_model(track)

        dfr = self.playlist.next_track()
        dfr.addCallback(got_track)
        dfr.addErrback(model_error)
        return dfr

    def play_previous(self):
        def got_track(track):
            return self._play_playable_model(track)

        dfr = self.playlist.previous_track()
        dfr.addCallback(got_track)
        return dfr

    def play_model(self, media_model):
        self.set_playlist(PlaylistModel())
        self.playlist.append(media_model)
        self.play_at_index(0)

    def _on_current_model_changed(self, notifier, model):
        self.emit('current-model-changed', model)

    def _on_current_playable_model_changed(self, notifier, model):
        self.emit('current-playable-model-changed', model)

    def set_playlist(self, playable_list_model):
        self.stop()
        if self.playlist is not None:
            self.playlist.notifier.disconnect_by_func(self._on_current_model_changed)
            self.playlist.notifier.disconnect_by_func(self._on_current_playable_model_changed)

        self.playlist = playable_list_model
        self.playlist.notifier.connect('current-model-changed',
                                       self._on_current_model_changed)
        self.playlist.notifier.connect('current-playable-model-changed',
                                       self._on_current_playable_model_changed)

    def _update_playcount_and_time(self, model):
        if model.uri.scheme != 'file':
            # database only support files
            return

        def update(result):
            if not result:
                return

            self.debug("updating playcount")
            result.playcount += 1
            result.last_played = time.time()

        store = common.application.store

        if not store or not DBFile:
            return

        dfr = store.get(DBFile, model.uri.path)
        dfr.addCallback(update)
        return dfr

    def get_current_model(self):
        return self.playlist[self.playlist.current_index]

    def play_at_index(self, index):
        if self.playlist.allow_jump:
            dfr = self.playlist.set_current_index(index)
            dfr.addCallback(self._play_playable_model)
        else:
            dfr = defer.fail(Exception("Not allowed to play at specific index"))
        return dfr

    def _play_playable_model(self, model):
        self._load_playable_model(model)
        return self.play()

    def _load_playable_model(self, model):
        # FIXME: don't define self.filename because it's never used in the Player.
        # Instead, in the PlayerControler, call get_current_model to get the title
        # from the playable model.
        self.filename = model.title or model.uri.filename
        self.stop()
        uri = unicode(model.uri).encode(locale_helper.gst_file_encoding())
        self.pipeline.set_property('uri', uri)
        self._load_subs(model.uri)

        self.update_visualization()
        self.update_audiosink()

    def update_visualization(self):
        # retrieve infos from config and configure the pipeline
        vis_plugin_name = self.config.get('visualization')
        if vis_plugin_name:
            try:
                visu = ElisaVisualisationBin(vis_plugin_name, 400, self.image)
            except gst.ElementNotFoundError:
                self.warning("Failed to set audio visualization %r", vis_plugin_name)
            else:
                self.pipeline.set_property('vis-plugin', visu)
        # TODO: code to disable the vis-plugin on-the-fly

    def update_audiosink(self):
        audio_sink = self.config.get('audio_sink')
        if audio_sink:
            try:
                self.audio_sink = gst.element_factory_make(audio_sink)
            except gst.ElementNotFoundError:
                self.warning("Can't find audio sink '%s'" % audio_sink)
            else:
                self.pipeline.set_property('audio-sink', self.audio_sink)

    def stop(self):
        current = self.pipeline.get_state(0)[1]
        self.pipeline.set_state(gst.STATE_NULL)
        # when setting the state to gst.STATE_NULL the pipeline doesn't emit a
        # "message::state-changed" signal.
        if current != gst.STATE_NULL:
            self.status = self.STOPPED
            self.emit('status-changed', self.status)

    def do_status_changed(self, value):
        if value == self.STOPPED:
            if self.image:
                self.image.clear()

    def pause(self):
        if self.playlist.current_playable_model.allow_pause:
            self.pipeline.set_state(gst.STATE_PAUSED)

    def play(self):
        model = self.playlist.current_playable_model
        if model != None:
            if self.status == self.STOPPED:
                self._load_playable_model(model)
                self._update_playcount_and_time(model)
                self._do_pipeline_play()
            elif self.status == self.PAUSED:
                self._do_pipeline_play()

            return defer.succeed(model)
        else:
            return self.play_next()

    def _do_pipeline_play(self):
        current = self.pipeline.get_state(0)[1]
        if current != gst.STATE_PLAYING:
            if current != gst.STATE_PAUSED:
                self.status = self.BUFFERING
                self.emit('status-changed', self.status)
            self.pipeline.set_state(gst.STATE_PLAYING)

    def get_position(self):
        try:
            position, format = self.pipeline.query_position(gst.FORMAT_TIME)
            return position
        except gst.QueryError:
            return -1

    def set_position(self, value):
        if self.status not in (self.PLAYING, self.PAUSED):
            return

        if not self.playlist.current_playable_model.allow_seek:
            return

        event = self.pipeline.seek(1.0, gst.FORMAT_TIME,
                            gst.SEEK_FLAG_FLUSH | gst.SEEK_FLAG_KEY_UNIT,
                            gst.SEEK_TYPE_SET, value,
                            gst.SEEK_TYPE_SET, -1 )

        duration = self.get_duration()
        if duration != -1 and value >= duration:
            if self.playlist.allow_next:
                self.play_next()

    def get_duration(self):
        try:
            duration, format = self.pipeline.query_duration(gst.FORMAT_TIME)
            return duration
        except gst.QueryError:
            return -1

    def _can_handle_key(self):
        #FIXME Florian will not like that
        t = time.time()
        if (t - self._last_key_time) > self.key_repeat_count_second:
            self._last_key_time = t
            return True
        return False

    def get_volume(self):
        return self.pipeline.get_property('volume')

    def set_volume(self, volume):
        self.pipeline.set_property('volume', volume)
        self.emit('volume-changed', volume)

    def volume_up(self):
        volume = self.get_volume()
        if self._can_handle_key():
            volume += self.volume_increment
            if volume > self.volume_max:
                volume = self.volume_max
            self.set_volume(volume)
        return volume

    def volume_down(self):
        volume = self.get_volume()
        if self._can_handle_key():
            volume -= self.volume_decrement
            if volume < 0:
                volume = 0
            self.set_volume(volume)
        return volume

    def seek_backward(self):
        duration = self.get_duration()
        position = self.get_position()
        if position != -1 and duration > 0 and self._can_handle_key():
            position -= self.seek_backward_seconds * gst.SECOND
            if position < 0:
                # the user wants to seek to the previous track
                if self.playlist.allow_previous:
                    position = 0
                    if self.play_previous():
                        return position
            self.set_position(position)
        return position

    def seek_forward(self):
        duration = self.get_duration()
        position = self.get_position()
        if position != -1 and duration > 0 and self._can_handle_key():
            position += self.seek_forward_seconds * gst.SECOND
            self.set_position(position)
            return position
        return -1

    def _micro_backward_seek(self):
        # seek 1 milli-second backward
        position = self.get_position() / gst.MSECOND
        if position > 1:
            new_position = (position - 1) * gst.MSECOND
            self.set_position(new_position)
        else:
            self.set_position(0)

    def next_subtitle(self):
        # FIXME: add proper support for mixed embedded/external subtitles
        if self.pipeline.get_property('suburi'):
            return None

        language = _("Subtitles Disabled")
        if self._subtitle_stream_infos:
            current_index = self._subtitle_index
            current_index += 1
            try:
                stream_info = self._subtitle_stream_infos[current_index]
            except IndexError:
                # disable subs
                current_index = -1
            else:
                iso_code = stream_info.get_property('language-code')
                if iso_code is None:
                    language = _("Unknown")
                else:
                    language = iso_639_to_human_readable(iso_code)
                language += _(" (%(index)d of %(count)d)") % {'index': current_index + 1,
                                                              'count': len(self._subtitle_stream_infos)}
            self.pipeline.set_property('current-text', current_index)
            self._update_sub_font_size()

            self._micro_backward_seek()
            self._subtitle_index = current_index
        return language

    def next_audio(self):
        language = None
        # no need to switch audio tracks if there's only one
        if len(self._audio_stream_infos) > 1:
            current_index = self.pipeline.get_property('current-audio')
            current_index += 1
            try:
                stream_info = self._audio_stream_infos[current_index]
            except IndexError:
                # go back to first track
                current_index = 0
                stream_info = self._audio_stream_infos[0]

            iso_code = stream_info.get_property('language-code')
            if iso_code is None:
                language = _("Unknown")
            else:
                language = iso_639_to_human_readable(iso_code)
            language += _(" (%(index)d of %(count)d)") % {'index': current_index + 1,
                                                          'count': len(self._audio_stream_infos)}
            self.pipeline.set_property('current-audio', current_index)
            self._micro_backward_seek()

        return language


class PlayerController(BasePlayerController):

    PlayerClass = Player
    PlayerOsdClass = PlayerOsd

    def __init__(self):
        super(PlayerController, self).__init__()

        # FIXME: mute support should probably be moved to Player
        # volume before muting
        self.mute_volume = -1
        self.volume_display_factor = 10.0
        
        self.seek_interval_seconds = 0.200
        self.seek_last_time = time.time()

        self._delayed_call = None

        self.background = Image()
        self.widget.add(self.background)
        self.background.bg_color = (0, 0, 0, 0)
        # FIXME: is that -1.0 necessary?
        self.background.z = -1.0
        self.background.visible = True

        self.player = self.PlayerClass(PlayerController.default_config)
        self.player.set_drawable(self.background)
        self.player.connect('status-changed',self._player_status_cb)

        #Initialise the volume control initial values
        self.player_osd.status.set_volume_max \
                        (self.player.volume_max * self.volume_display_factor)
        self.player_osd.status.set_volume_position \
                        (self.player.get_volume() * self.volume_display_factor)
        
        #connect the seeking bar and volume bar
        self.player_osd.status.connect \
                                ('seeking-changed', self._seeking_bar_cb)

        self._missing_decoders = set()
        self.player.connect('player-missing-decoder', self._player_missing_decoder_cb)
        self.player.connect('player-codec-error', self._check_missing_decoders_cb)

        self.player.connect('player-unknown-error', self._player_unknown_error_cb)

        self.player.connect('playback-ended', self._playback_ended)

        self.player.connect('metadata-found', self._player_metadata_found)
        self.player.connect('current-model-changed',
                            self._on_current_model_changed)

        self.setup_ribbon_controls()

        self.album_helper = AlbumsViewMode()

        self._widget_focus_id = self.widget.connect('focus',
                                                    self._on_focus_changed)

        # whether we update the UI when we receive metadata during playback,
        # this is changed for each model when we start playback
        self._dynamic_metadata_updates = False

    def setup_ribbon_controls(self):
        # video controls
        ribbon = self.player_osd.control_ribbon
        ribbon.add_control(SkipPreviousControl(self))
        ribbon.add_control(PlayPauseControl(self))
        ribbon.add_control(StopControl(self))
        ribbon.add_control(SkipNextControl(self))

        if isinstance(self, VideoPlayerController):
            ribbon.add_control(SubtitleStreamControl(self))
            ribbon.add_control(AudioStreamControl(self))

        ribbon.add_control(VolumeDownControl(self))
        ribbon.add_control(VolumeUpControl(self))


    def initialize(self):
        dfr = super(PlayerController, self).initialize()

        config = self.config
        if config is None:
           config = {}

        self.player.config = self.config

        # we want OSD dock always there if no visualization is displayed
        try:
            viz_osd_timeout = int(config.get('osd_timeout', '3'))
        except ValueError:
            # user supplied empty string
            viz_osd_timeout = 0

        if viz_osd_timeout != 0:
            controls_osd_timeout = viz_osd_timeout
        else:
            controls_osd_timeout = 3

        self.player_osd.time_before_hiding = viz_osd_timeout
        self.player_osd.controls_time_before_hiding = controls_osd_timeout

        return dfr

    def clean(self):
        dfr = super(PlayerController, self).clean()
        self.widget.disconnect(self._widget_focus_id)
        return dfr

    def _on_focus_changed(self, widget, focus):
        if focus and self.player.status in (self.player.PAUSED, self.player.PLAYING):
            self.show_on_screen_display()
        else:
            self.hide_on_screen_display()

    def exit(self):
        """
        Exit the player by asking poblesec's main controller to hide it.
        """
        controllers = self.frontend.retrieve_controllers('/poblesec')
        main = controllers[0]
        self.hide_on_screen_display()
        main.hide_current_player()

    def _play_pause_clicked_cb(self, drawable, x, y, z, button, time, pressure):
        if not self.has_focus():
            return True

        self.toggle_play_pause()
        return True

    def _on_current_model_changed(self, player, model):
        default_video_icon = 'elisa.plugins.poblesec.player.thumbnail.default_movie'
        default_music_album_icon = 'elisa.plugins.poblesec.player.thumbnail.default_album'

        self.info("Played model changed to %r", model)

        # internal functions
        def dfr_error(failure, source):
            self.warning("failure in %s : %s" % (source, failure))

        def got_artwork(file_path, default_resource=None):
            if not file_path:
                # no artwork found
                if default_resource:
                    self.frontend.load_from_theme(default_resource,
                                                  self.player_osd.status.preview)
                return

            self.player_osd.status.preview.set_from_file(file_path)

            if isinstance(model, (MusicTrack, BaseTrackModel)):
                src = gdk.pixbuf_new_from_file(file_path)
                dst = imaging.linear_alpha_gradient(src, 0.25, 0.0, 1.3,
                                                    0.5, 0.5, 0.2)
                self.player_osd.cover_overlay.cover.set_from_pixbuf(dst)

        osd_details = self.player_osd.status.status_display_details

        # clear the OSD
        osd_details.title.label = ""
        osd_details.details.label = ""
        osd_details.details_2.label = ""
        self.background.clear()
        self.player_osd.cover_overlay.cover.clear()

        # FIXME: by default we display the video icon, this is fine in
        # the case of video playable_models but not for audio
        # playable_models. There's no way to make the difference
        # between and audio-only and video playable model, the correct
        # solution would be to fix all the plugins that directly give
        # PlayableModel instances to the player and instead, create
        # VideoModels or TrackModels.
        self.frontend.load_from_theme(default_video_icon,
                                      self.player_osd.status.preview)

        # we want to update the layout for dynamic changes
        self._dynamic_metadata_updates = True

        if isinstance(model, (MusicTrack, BaseTrackModel)):
            osd_details.title.label = model.title

            def got_artist(artist, album):
                osd_details.details_2.label = artist
                dfr = self.album_helper.get_image(album, None)
                dfr.addCallback(got_artwork, default_music_album_icon)
                dfr.addErrback(dfr_error, "in got_artwork")
                return dfr

            def got_album(album):
                if album:
                    osd_details.details.label = album.name
                    dfr = album.get_artist_name()
                    dfr.addCallback(got_artist, album)
                    dfr.addErrback(dfr_error, "in got_artist")
                    return dfr

            album_dfr = model.get_album()
            album_dfr.addCallback(got_album)
            album_dfr.addErrback(dfr_error, "in got_album")

        elif isinstance(model, PlayableModel):
            osd_details.title.label = model.title or ''
            if model.uri.scheme == 'file':
                # only if we are local
                poster = self._find_poster(model.uri.path)
                got_artwork(poster)
        elif isinstance(model, VideoModel):
            osd_details.title.label = model.title or ''
            dfr = model.get_poster()
            dfr.addCallback(got_artwork, default_video_icon)
        elif isinstance(model, Video):
            # a model from database
            osd_details.title.label = model.name
            got_artwork(model.thumbnail_uri, default_video_icon)
        elif isinstance(model, Movie):
            # a movie model from database
            osd_details.title.label = model.title
            poster = getattr(model, 'thumbnail_path', None)
            if poster:
                got_artwork(poster, default_video_icon)
        elif isinstance(model, TVEpisode):
            # a TV Episode model from database
            poster = getattr(model, 'thumbnail_path', None)
            if poster:
                got_artwork(poster, default_video_icon)

            def got_tvshow(tvshow, season):
                osd_details.details.label = tvshow.name
                season_txt = _("Season %(number)s") % {'number': season.number}
                osd_details.details_2.label = season_txt

            def got_season(season):
                dfr = season.tvshow
                dfr.addCallback(got_tvshow, season)
                return dfr

            osd_details.title.label = "%(number)d. %(name)s" % {'number': model.number,
                                                                'name': model.name}
            dfr = model.season
            dfr.addCallback(got_season)

    def _seeking_bar_cb(self, widget, position):
        
        current_time = time.time()
        if current_time - self.seek_last_time < self.seek_interval_seconds:
            return
        
        self.seek_last_time = current_time
        self.player.set_position(position * gst.SECOND)

    def _volume_bar_cb(self, widget, position):
        self.mute_volume = -1
        self.player.set_volume(position / self.volume_display_factor)

    def _volume_mute_cb(self, drawable, x, y, z, button, time, pressure):
        if not self.has_focus():
            return True

        self.toggle_mute()
        return True

    def _find_poster(self, filename):
        dir, file = os.path.split(filename)

        exts = ('jpg', 'png', 'gif')

        tests = [ "%s.%s" % (filename, ext) for ext in exts]
        try:
            extless, ext = os.path.splitext(file)
            for ext in exts:
                tests.append(os.path.join(dir, '%s.%s' % (extless, ext)))
        except ValueError:
            pass

        for ext in exts:
            # for the general poster.jpg and cover.jpg
            tests.append(os.path.join(dir, 'poster.%s' % ext))
            tests.append(os.path.join(dir, 'cover.%s' % ext))
            tests.append(os.path.join(dir, 'front.%s' % ext))

        for file in tests:
            if os.path.exists(file):
                return file
 
    def _player_metadata_found(self, player, data):
        self.debug("Received metadata from player %s",  data)

        if not 'title' in data:
            # FIXME: add more tags as soon as implementations come along
            return

        if self._dynamic_metadata_updates:
            osd_details = self.player_osd.status.status_display_details
            osd_details.title.label = data['title']
    
    def _player_status_cb(self, player, status):

        if status != player.PLAYING:
            self._stop_monitoring()
        else:
            # query duration when playback has started, this can't be
            # reliably done before
            # FIXME: this isn't reliable here either; for DVDs at least, the
            # duration of the currently-played stream can change. Updating
            # that on segment changes might be a good idea
            duration = self.player.get_duration()
            osd_details = self.player_osd.status.status_display_details
            if duration > 0 and osd_details.details.label == '':
                duration_in_min = duration / (gst.SECOND * 60)
                osd_details.details.label = "%s %s" % (duration_in_min, _('min'))

        # show buffering even if focus is not set
        if status == player.BUFFERING:
            pbc = self.player_osd.status.status_display_details.progress_bars
            pbc.set_visible_bar(BARS.BUFFERING_BAR)
            # during buffering, we want to show the cover art we also
            # want to disable the auto-hide feature of the OSD while
            # the player is buffering.
            self.show_on_screen_display(with_cover_overlay=True, with_timeout=False)
        else:
            pbc = self.player_osd.status.status_display_details.progress_bars
            pbc.set_visible_bar(BARS.SEEKING_BAR)

        if status == player.PLAYING:
            self.show_on_screen_display()

            self._monitor()
            self.player_osd.status.set_seeking_duration \
                                        (self.player.get_duration()/gst.SECOND)

            self._check_missing_decoders_cb(self.player, partly_playable=True)
        elif status == player.PAUSED:
            player_position = self.player.get_position()
            self.player_osd.status.set_seeking_position \
                                                (player_position / gst.SECOND)
            self.show_on_screen_display()
        elif status == player.STOPPED:
            self.background.clear()
        return True

    def _stop_monitoring(self, *args):
        self.debug('Stop monitoring')
        # stop the monitoring instantly
        if self._delayed_call and self._delayed_call.called == 0 and \
            self._delayed_call.cancelled == 0:
            self._delayed_call.cancel()

    _player_osd_hidden_cb = _stop_monitoring

    def _monitor(self):
        if self._delayed_call and self._delayed_call.called == 0 and \
            self._delayed_call.cancelled == 0:
            # still a pending one
            return

        self.debug('Monitoring')

        progress_bars = self.player_osd.status.status_display_details.progress_bars
        if not progress_bars.seeking_bar.seek_drag:
            # no update during drag seeking to not flicker around
            player_position = self.player.get_position()
            self.player_osd.status.set_seeking_position \
                                                (player_position / gst.SECOND)

        self._delayed_call = reactor.callLater(0.4, self._monitor)

    def _player_missing_decoder_cb(self, player, mimetype, decoder):
        self._missing_decoders.add(decoder)

    def _check_missing_decoders_cb(self, player, partly_playable=False):
        if self._missing_decoders:
            missing_decoders = list(self._missing_decoders)
            self._missing_decoders.clear()
            main = self.frontend.retrieve_controllers('/poblesec')[0]
            if not partly_playable:
                self.hide_on_screen_display()
                main.hide_current_player()

            title = _('Unable To Play Selected File')

            if partly_playable:
                subtitle = _('Unfortunately Moovida can only partly play the selected file.')
            else:
                subtitle = _('Unfortunately Moovida cannot play the selected file.')
            if missing_decoders:
                text = _(' Please download and install: ')
                # Localization of the names of the missing decoders is
                # automatically taken care of by gstreamer.
                text += _(', ').join(missing_decoders) + _('.')
            else:
                text = _('Unknown error')

            buttons = [(_('Close'), main.hide_popup)]

            main.enqueue_popup(title, subtitle, text, buttons, modal_popup.ErrorPopup)

    def _player_unknown_error_cb(self, player, message):
        main = self.frontend.retrieve_controllers('/poblesec')[0]
        self.hide_on_screen_display()
        main.hide_current_player()

        title = _('Unable To Play Selected File')
        subtitle = _('Unfortunately Moovida cannot play the selected file.')
        text = _('GStreamer error: ') + message

        buttons = [(_('Close'), main.hide_popup)]

        main.enqueue_popup(title, subtitle, text, buttons, modal_popup.ErrorPopup)

    def _playback_ended(self, player):
        self.stop()

    def stop(self):
        main = self.frontend.retrieve_controllers('/poblesec')[0]
        self.player.stop()
        self.hide_on_screen_display()
        self.background.clear()
        if main.current_player is self:
            main.hide_current_player()

    def set_frontend(self, frontend):
        super(PlayerController, self).set_frontend(frontend)
        self.frontend.viewport.connect('motion-notify-event', \
                                                        self._mouse_motion_cb)

    def _mouse_motion_cb(self, viewport, event):
        if self.has_focus():
            if self.player_osd.opacity == 0:
                pbc = self.player_osd.status.status_display_details.progress_bars
                if pbc.get_visible_bar() != BARS.SEEKING_BAR:
                    pbc.set_visible_bar(BARS.SEEKING_BAR)
            self.show_on_screen_display(with_ribbon=True, with_mouse=True, \
                    with_cover_overlay=True)

    def toggle_play_pause(self):
        if self.player.status == self.player.PLAYING:
            self.go_to_pause()
        else:
            self.go_to_play()

    def go_to_pause(self):
        self.player.pause()

    def go_to_play(self):
        self.player.play()

    def next_subtitle(self):
        return self.player.next_subtitle()

    def next_audio(self):
        return self.player.next_audio()

    def toggle_mute(self):
        if self.mute_volume >= 0:
            self.show_on_screen_display()
            self.player.set_volume(self.mute_volume)
            self.mute_volume = -1
        else:
            self.show_on_screen_display()
            self.mute_volume = self.player.get_volume()
            self.player.set_volume(0)

    def volume_up(self):
        pbc = self.player_osd.status.status_display_details.progress_bars
        pbc.set_visible_bar(BARS.VOLUME_BAR)
        self.show_on_screen_display()
        volume = self.player.volume_up() * self.volume_display_factor
        self.player_osd.status.set_volume_position(int(volume))

    def volume_down(self):
        pbc = self.player_osd.status.status_display_details.progress_bars
        pbc.set_visible_bar(BARS.VOLUME_BAR)
        self.show_on_screen_display()
        volume = self.player.volume_down()
        display_volume = int(volume * self.volume_display_factor)

        if display_volume == 0 and volume != 0:
            # when we are displaying that there is no volume then there is no
            # volume anymore!
            self.player.set_volume(0)

        self.player_osd.status.set_volume_position(display_volume)

    def show_on_screen_display(self, with_ribbon=False, with_mouse=False,
            with_cover_overlay=False, with_timeout=True):
        """
        Display player's on screen display. Optionally displays ribbon
        controls and mouse controls.

        @param with_ribbon:         whether or not to display ribbon controls
        @type with_ribbon:          C{bool}
        @param with_mouse:          whether or not to display mouse controls
        @type with_mouse:           C{bool}
        @param with_cover_overlay:  whether or not to display the cover
                                    overlay
        @type with_cover_overlay:   C{bool}
        @param with_timeout:        whether or not to make the OSD auto-hide
                                    after few seconds
        @type with_timeout:         C{bool}
        """
        timeout = with_timeout
        if self.player.status == self.player.PAUSED:
            timeout = False
        elif self.player.status == self.player.PLAYING:
            self._monitor()

        self.player_osd.show(timeout=timeout,
                             with_ribbon=with_ribbon,
                             with_mouse=with_mouse,
                             with_cover=with_cover_overlay)

    def hide_on_screen_display(self):
        """
        Hide player's on screen display including ribbon controls and mouse
        controls.
        """
        self.player_osd.hide()

    def handle_input(self, manager, input_event):
        if input_event.value == EventValue.KEY_MENU:
            pbc = self.player_osd.status.status_display_details.progress_bars
            pbc.set_visible_bar(BARS.SEEKING_BAR)
            if self.player_osd.animated.opacity == 0:
                self.show_on_screen_display(with_ribbon=True,\
                        with_cover_overlay=True)
                return True
            elif self.player_osd.control_ribbon.animated.opacity == 0:
                self.show_on_screen_display(with_ribbon=True, \
                        with_cover_overlay=True)
                return True
            else:
                self.hide_on_screen_display()
                return False
        elif input_event.value in (EventValue.KEY_OK, EventValue.KEY_SPACE,
                                   EventValue.KEY_PLAY_PAUSE):
            pbc = self.player_osd.status.status_display_details.progress_bars
            pbc.set_visible_bar(BARS.SEEKING_BAR)
            if self.player_osd.control_ribbon.opacity == 255:
                self.player_osd.control_ribbon.selected_control.activate()
            else:
                self.toggle_play_pause()
            return True
        elif input_event.value == EventValue.KEY_PLAY:
            pbc = self.player_osd.status.status_display_details.progress_bars
            pbc.set_visible_bar(BARS.SEEKING_BAR)
            if self.player.status == self.player.PAUSED:
                self.go_to_play()
            return True
        elif input_event.value == EventValue.KEY_PAUSE:
            pbc = self.player_osd.status.status_display_details.progress_bars
            pbc.set_visible_bar(BARS.SEEKING_BAR)
            if self.player.status == self.player.PLAYING:
                self.go_to_pause()
            return True
        elif input_event.value == EventValue.KEY_STOP:
            self.stop()
            return True
        elif input_event.value in (EventValue.KEY_GO_UP, EventValue.KEY_VOL_UP):
            self.volume_up()
            return True
        elif input_event.value in (EventValue.KEY_GO_DOWN,
                                   EventValue.KEY_VOL_DOWN):
            self.volume_down()
            return True
        elif input_event.value in (EventValue.KEY_GO_LEFT,
                                   EventValue.KEY_SEEK_BACKWARD):
            if self.player_osd.control_ribbon.opacity == 0:
                pbc = self.player_osd.status.status_display_details.progress_bars
                pbc.set_visible_bar(BARS.SEEKING_BAR)
            self.show_on_screen_display()
            if self.player_osd.control_ribbon.opacity == 255:
                self.player_osd.control_ribbon.select_previous_control()
            else:
                if self.player.playlist.current_playable_model.allow_seek:
                    self.player.seek_backward()
                elif self.player.playlist.allow_previous:
                    self.player.play_previous()
        elif input_event.value in (EventValue.KEY_GO_RIGHT,
                                   EventValue.KEY_SEEK_FORWARD):
            if self.player_osd.control_ribbon.opacity == 0:
                pbc = self.player_osd.status.status_display_details.progress_bars
                pbc.set_visible_bar(BARS.SEEKING_BAR)
            self.show_on_screen_display()
            if self.player_osd.control_ribbon.opacity == 255:
                self.player_osd.control_ribbon.select_next_control()
            else:
                if self.player.playlist.current_playable_model.allow_seek:
                    self.player.seek_forward()
                elif self.player.playlist.allow_next:
                    self.player.play_next()
        elif input_event.value in (EventValue.KEY_x, EventValue.KEY_PREVIOUS):
            if self.player.playlist.allow_previous: 
                pbc = self.player_osd.status.status_display_details.progress_bars 
                pbc.set_visible_bar(BARS.SEEKING_BAR) 
                self.show_on_screen_display() 
                self.player.play_previous() 
        elif input_event.value in (EventValue.KEY_c, EventValue.KEY_NEXT):
            if self.player.playlist.allow_next: 
                pbc = self.player_osd.status.status_display_details.progress_bars 
                pbc.set_visible_bar(BARS.SEEKING_BAR) 
                self.show_on_screen_display() 
                self.player.play_next()
        elif input_event.value in (EventValue.KEY_m, EventValue.KEY_MUTE):
            pbc = self.player_osd.status.status_display_details.progress_bars
            pbc.set_visible_bar(BARS.VOLUME_BAR)
            self.show_on_screen_display()
            self.toggle_mute()

        return False


class VideoPlayerController(PlayerController):
    needs = [constants.PLAYER_PROVIDES_SOUND,
             constants.PLAYER_PROVIDES_IMAGE]
    default_config = {'audio_sink' : get_default_audio_sink(),
                      'subtitle_font' : 'Liberation Sans',
                      'subtitle_font_size_factor': '0.0612',
                      }

    config_doc = {'subtitle_font' : 'Font name used for subtitles',
                  'subtitle_font_size_factor': 'Factor multiplied by video '\
                                               'height to obtain subtitle '\
                                               'font size',
                  'audio_sink' : 'give the name of the gstreamer audio sink' \
                                 'elisa should use. If not set autoaudiosink'\
                                 'will be used.',
                  }
