3 # This file is part of Panucci.
4 # Copyright (c) 2008-2010 The Panucci Audiobook and Podcast Player Project
6 # Panucci is free software: you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation, either version 3 of the License, or
9 # (at your option) any later version.
11 # Panucci is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
16 # You should have received a copy of the GNU General Public License
17 # along with Panucci. If not, see <http://www.gnu.org/licenses/>.
20 from __future__
import absolute_import
25 from panucci
.settings
import settings
26 from panucci
.services
import ObservableService
31 class BASE_ERROR_NO_MEDIA(BASE_ERROR
):
32 error
= _("No media selected")
33 class BASE_ERROR_UNSUPPORTED(BASE_ERROR
):
34 error
= _("Unsupported filetype.")
35 class BASE_ERROR_BACKEND(BASE_ERROR
):
36 error
= _("Something wrong with the backend")
37 class BASE_ERROR_HARDWARE(BASE_ERROR
):
38 error
= _("Hardware blocked/in-use")
39 class BASE_ERROR_BAD_FILE(BASE_ERROR
):
40 error
= _("File is corrupted/incomplete")
41 class BASE_ERROR_FILE_NOT_FOUND(BASE_ERROR
):
42 error
= _("File not found, make sure the file still exists.")
45 class BasePlayer(ObservableService
):
46 """ The player base class, this can't be used directly because most of
47 the important functions need to be filled in by subclasses.
53 Issued when the player starts playing
55 Issued when the player pauses
57 Issued when the player stops
59 Issued at the end of a file
60 error : ( error_code, error_string )
61 Issued when an error occurs. "error_code" is a unique code for a
62 specific error, "error_string" is a human-readable string that
65 signals
= [ 'playing', 'paused', 'stopped', 'eof', 'error' ]
66 STATE_PLAYING
, STATE_PAUSED
, STATE_STOPPED
, STATE_NULL
= range(4)
69 self
.__log
= logging
.getLogger('panucci.backends.BasePlayer')
70 ObservableService
.__init
__(self
, self
.signals
, self
.__log
)
71 settings
.register( 'volume_changed', self
._set
_volume
_level
)
73 # Cached copies of position and duration
74 self
.__position
, self
.__duration
= 0, 0
75 self
.seeking
= False # Are we seeking?
78 #############################################
79 # Functions to be implemented by subclasses
82 """ Get the current state of the player.
84 Returns: One of the following flags: player.PLAYING,
90 def load_media(self
, uri
):
91 """ Loads a uri into the player
93 Params: uri - A full path to a media file.
94 Eg. file:///mnt/music/some-file.ogg
101 Returns: The current position in nanoseconds.
102 Signals: Must emit the "paused" signal.
105 def play(self
, position
=None):
106 """ Starts playing the playlist's current track.
108 Params: position is the absolute position to seek to before
109 playing. It is better to use this instead of relying on
110 play(); seek(...) because the player might not be ready to
112 Returns: False if the current track cannot be played.
114 Signals: Must emit the "playing" signal
121 Signals: Must emit the "stopped" signal.
124 def _get_position_duration(self
):
125 """ Get the position and duration of the current file.
127 Returns: ( current position, total duration )
130 def _seek(self
, position
):
131 """ Seek to an absolute position in the current file.
133 Params: position is the position to seek to in nanoseconds.
134 Returns: True if the seek was successfull.
137 def _set_volume_level(self
, level
):
138 """ Sets the volume level of the player. This should only be used in
139 conjunction with the settings manager's "volume_changed" signal.
141 Params: level is a float between 0 and 1.
146 #############################################
149 def do_seek(self
, from_beginning
=None, from_current
=None, percent
=None ):
150 """ A very flexible function to seek in the current file
152 Params: Requires ONE of the following keyword arguments
153 - from_beginning=n: seek n nanoseconds from the start of
155 - from_current=n: seek n nanoseconds from the current
157 - percent=n: seek n percent from the beginning of the file
159 Returns: False if the seek was NOT possible
160 ( position, duration ) if the seek was possible
163 position
, duration
= self
.get_position_duration()
165 # if position and duration are 0 then player_get_position caught an
166 # exception. Therefore self.__player isn't ready to be seeking.
167 if not duration
or self
.get_state
== self
.STATE_NULL
:
170 if from_beginning
is not None:
171 assert from_beginning
>= 0
172 position
= min( from_beginning
, duration
)
173 elif from_current
is not None:
174 position
= max( 0, min( position
+from_current
, duration
))
175 elif percent
is not None:
176 assert 0 <= percent
<= 1
177 position
= int(duration
*percent
)
179 self
.__log
.warning('No seek parameters specified.')
183 self
.__log
.debug('do_seek: Seeking to: %d', position
)
185 return position
, duration
187 self
.__log
.debug('do_seek: Could not seek.')
191 def get_position_duration(self
):
192 """ A cached version of _get_position_duration """
194 self
.__position
, self
.__duration
= self
._get
_position
_duration
()
196 return self
.__position
, self
.__duration
198 def play_pause_toggle(self
):
199 self
.pause() if self
.playing
else self
.play()
203 """ Is the player playing? """
204 return self
.get_state() == self
.STATE_PLAYING