# -*- coding: utf-8 -*-
#
# Author: Ingelrest François (Francois.Ingelrest@gmail.com)
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Library General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA

import gst


class AudioPlayer:

    def __init__(self, callbackEnded, usePlaybin2=True):
        """ Constructor """
        self.player        = None
        self.volume        = 1
        self.rgEnabled     = False
        self.eqzLevels     = None
        self.equalizer     = None
        self.eqzEnabled    = False
        self.usePlaybin2   = usePlaybin2
        self.cdReadSpeed   = 1
        self.callbackEnded = callbackEnded


    def __getPlayer(self):
        """ Construct and return the GStreamer player """
        self.__constructPlayer()
        self.__getPlayer = self.__getPlayer_post       # I love Python

        return self.player


    def __getPlayer_post(self):
        """ Return the GStreamer player """
        return self.player


    def __constructPlayer(self):
        """ Create the GStreamer pipeline """
        if self.usePlaybin2:
            self.player = gst.element_factory_make('playbin2', 'player')
            self.player.connect('about-to-finish', self.__onAboutToFinish)
        else:
            self.player = gst.element_factory_make('playbin', 'player')

        # No video
        self.player.set_property('video-sink', gst.element_factory_make('fakesink', 'fakesink'))

        # Restore volume
        self.player.set_property('volume', self.volume)

        # Change the audio sink to our own bin, so that an equalizer/replay gain element can be added later on if needed
        self.audiobin  = gst.Bin('audiobin')
        self.audiosink = gst.element_factory_make('autoaudiosink', 'audiosink')

        # Callback when the source of the playbin is changed
        self.player.connect('notify::source', self.__onNewPlaybinSource)

        self.audiobin.add(self.audiosink)
        self.audiobin.add_pad(gst.GhostPad('sink', self.audiosink.get_pad('sink')))
        self.player.set_property('audio-sink', self.audiobin)

        # Monitor messages generated by the player
        bus = self.player.get_bus()
        bus.add_signal_watch()
        bus.connect('message', self.__onGstMessage)

        # Add equalizer?
        if self.eqzEnabled:
            self.equalizer = gst.element_factory_make('equalizer-10bands', 'equalizer')
            self.audiobin.add(self.equalizer)
            self.audiobin.get_pad('sink').set_target(self.equalizer.get_pad('sink'))
            self.equalizer.link(self.audiosink)

            if self.eqzLevels is not None:
                self.setEqualizerLvls(self.eqzLevels)

        # Add replay gain?
        if self.rgEnabled:
            replaygain = gst.element_factory_make('rgvolume', 'replaygain')

            self.audiobin.add(replaygain)
            self.audiobin.get_pad('sink').set_target(replaygain.get_pad('sink'))

            if self.equalizer is None: replaygain.link(self.audiosink)
            else:                      replaygain.link(self.equalizer)


    def enableEqualizer(self):
        """ Add an equalizer to the audio chain """
        self.eqzEnabled = True


    def enableReplayGain(self):
        """ Add/Enable a replay gain element """
        self.rgEnabled = True


    def setEqualizerLvls(self, lvls):
        """ Set the level of the 10-bands of the equalizer (levels must be a list/tuple with 10 values lying between -24 and +12) """
        if len(lvls) == 10:
            self.eqzLevels = lvls

            if self.equalizer is not None:
                self.equalizer.set_property('band0', lvls[0])
                self.equalizer.set_property('band1', lvls[1])
                self.equalizer.set_property('band2', lvls[2])
                self.equalizer.set_property('band3', lvls[3])
                self.equalizer.set_property('band4', lvls[4])
                self.equalizer.set_property('band5', lvls[5])
                self.equalizer.set_property('band6', lvls[6])
                self.equalizer.set_property('band7', lvls[7])
                self.equalizer.set_property('band8', lvls[8])
                self.equalizer.set_property('band9', lvls[9])


    def __onNewPlaybinSource(self, playbin, params):
        """ Change the CR-ROM drive speed to 1 when applicable """
        source = self.__getPlayer().get_by_name('source')

        # Didn't find a way to determine the real class of source
        # So we use the 'paranoia-mode' property to determine whether it's indeed a CD we're playing
        try:
            source.get_property('paranoia-mode')
            source.set_property('read-speed', self.cdReadSpeed)
        except:
            pass


    def __onAboutToFinish(self, isLast):
        """ End of the track """
        self.callbackEnded(False)


    def __onGstMessage(self, bus, msg):
        """ A new message generated by the player """
        if msg.type == gst.MESSAGE_EOS:
            self.callbackEnded(False)
        elif msg.type == gst.MESSAGE_ERROR:
            self.stop()
            # It seems that the pipeline may not be able to play again any valid stream when an error occurs
            # We thus create a new one, even if that's quite a ugly solution
            self.__constructPlayer()
            self.callbackEnded(True)

        return True


    def setCDReadSpeed(self, speed):
        """ Set the CD-ROM drive read speed """
        self.cdReadSpeed = speed


    def setNextURI(self, uri):
        """ Set the next URI """
        self.__getPlayer().set_property('uri', uri.replace('%', '%25').replace('#', '%23'))


    def setVolume(self, level):
        """ Set the volume to the given level (0 <= level <= 1) """
        if level < 0:   self.volume = 0
        elif level > 1: self.volume = 1
        else:           self.volume = level

        if self.player is not None:
            self.player.set_property('volume', self.volume)


    def isPaused(self):
        """ Return whether the player is paused """
        return self.__getPlayer().get_state()[1] == gst.STATE_PAUSED


    def isPlaying(self):
        """ Return whether the player is paused """
        return self.__getPlayer().get_state()[1] == gst.STATE_PLAYING


    def setURI(self, uri):
        """ Play the given URI """
        self.__getPlayer().set_property('uri', uri.replace('%', '%25').replace('#', '%23'))


    def play(self):
        """ Play """
        self.__getPlayer().set_state(gst.STATE_PLAYING)


    def pause(self):
        """ Pause """
        self.__getPlayer().set_state(gst.STATE_PAUSED)


    def stop(self):
        """ Stop playing """
        self.__getPlayer().set_state(gst.STATE_NULL)


    def seek(self, where):
        """ Jump to the given location """
        self.__getPlayer().seek_simple(gst.FORMAT_TIME, gst.SEEK_FLAG_FLUSH, where)


    def getPosition(self):
        """ Return the current position """
        try:    return self.__getPlayer().query_position(gst.FORMAT_TIME)[0]
        except: return 0


    def getDuration(self):
        """ Return the duration of the current stream """
        try:    return self.__getPlayer().query_duration(gst.FORMAT_TIME)[0]
        except: return 0
