Theme changes, fix for unicode conversion errors, misc
[rox-musicbox.git] / player.py
blob398301ee325126f2b0135e47ee688538c7c427df
1 """
2 player.py (play various sound/music files)
4 based on ogg123.py By Andrew Chatham Based on ogg123.c by Keneth Arnold.
5 also based on mpg123.py from the pymad module (no attribution in those sources)
7 Copyright 2005 Kenneth Hayber <ken@hayber.us>, All rights reserved.
9 This program is free software; you can redistribute it and/or modify
10 it under the terms of the GNU General Public License as published by
11 the Free Software Foundation; either version 2 of the License.
13 This program is distributed in the hope that it will be useful
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 GNU General Public License for more details.
18 You should have received a copy of the GNU General Public License
19 along with this program; if not, write to the Free Software
20 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
21 """
23 import os, time, string, sys, Queue
24 import plugins
26 try:
27 import alsaaudio
28 HAVE_ALSA = True
29 except:
30 print 'No ALSA support'
31 HAVE_ALSA = False
33 try:
34 import ossaudiodev
35 HAVE_OSS = True
36 except:
37 print 'No OSS support!!'
38 HAVE_OSS = False
40 try:
41 import ao
42 HAVE_AO = True
43 except:
44 HAVE_AO = False
46 if not HAVE_AO and not HAVE_OSS:
47 print 'No OSS and no AO support Falling back to linuxaudiodev which sucks!!'
49 try:
50 import linuxaudiodev
51 HAVE_LINUX = True
52 except:
53 HAVE_LINUX = False
55 class Player:
56 """A class to playback sound files to an audio device."""
57 state = 'stop'
58 remain = 0
59 elapse = 0
60 last_elapse = 0
61 total_time = 0
62 seek_val = 0
64 def __init__(self, driver='ao', id='esd', buffersize=4096, device='/dev/dsp',):
65 """Initialize the Player instance"""
66 self.driver = driver
67 self.id = id
68 self.buffersize = buffersize
69 self.dev = None
70 self.device = device
71 self.queue = Queue.Queue(5)
73 def open(self, rate=44100, channels=2):
74 """Open and configure the audio device driver"""
75 bits=16
76 #try 3 times to open the device, then give up.
77 for x in range(3):
78 if self.driver == 'alsa':
79 self.dev = alsaaudio.PCM(cardname=self.device)
80 self.dev.setchannels(channels)
81 self.dev.setrate(rate)
82 if sys.byteorder == 'big':
83 format = alsaaudio.PCM_FORMAT_S16_BE
84 else:
85 format = alsaaudio.PCM_FORMAT_S16_LE
86 self.dev.setformat(format)
87 elif self.driver == 'ao':
88 self.dev = ao.AudioDevice(self.id, bits, rate, channels)
89 elif self.driver == 'oss':
90 self.dev = ossaudiodev.open(self.device, 'w')
91 if sys.byteorder == 'big':
92 format = ossaudiodev.AFMT_S16_BE
93 else:
94 format = ossaudiodev.AFMT_S16_LE
95 self.dev.setparameters(format, channels, rate)
96 elif self.driver == 'linux':
97 self.dev = linuxaudiodev.open('w')
98 if sys.byteorder == 'big':
99 format = linuxaudiodev.AFMT_S16_BE
100 else:
101 format = linuxaudiodev.AFMT_S16_LE
102 self.dev.setparameters(rate, bits, channels, format)
103 else:
104 raise 'Driver Error'
106 def close(self):
107 """Close the current device if open and delete it"""
108 if self.dev:
109 if self.driver in ('ao', 'alsa', 'linux'):
110 self.dev = None
111 elif self.driver in ('oss',):
112 self.dev.close()
115 def write(self, buff, bytes):
116 """Write data to the audio device"""
117 if self.driver in ('ao',):
118 self.dev.play(buff, bytes)
119 elif self.driver in ('oss', 'alsa'):
120 self.dev.write(buff)
121 else:
122 while self.dev.obuffree() < bytes:
123 time.sleep(0.2)
124 self.dev.write(buff[:bytes])
126 def play(self, name, type):
127 """Push the song info on the queue"""
128 if not os.path.isfile(name):
129 raise SyntaxError, "File not found or not accessible (%s)." % name
130 self.queue.put((name, type))
132 def run(self):
133 """Check the filename and type, create a decoder and start playing"""
134 while True:
135 (name, type) = self.queue.get()
136 if os.path.isfile(name):
137 try:
138 self.decoder = plugins.get_decoder(name, type, self.buffersize)
139 except:
140 continue #get the next song
141 else:
142 raise SyntaxError, 'play takes a filename.'
143 self.start()
145 def start(self):
146 """Start playing a file calling the current decoder to get file info and data"""
147 self.state = 'play'
149 self.decoder.open()
150 self.total_time = self.decoder.length()
151 self.remain = self.total_time
152 self.elapse = 0
153 last_elapse = 0
155 try:
156 self.open(self.decoder.samplerate(), self.decoder.channels())
158 while self.state == 'play' or self.state == 'pause':
159 if self.state == 'pause':
160 time.sleep(1)
161 elif self.seek_val:
162 self.decoder.seek(self.seek_val)
163 self.seek_val = 0
164 else:
165 (buff, bytes) = self.decoder.read()
166 if buff is None:
167 self.state = 'eof'
168 self.elapse = self.total_time
169 last_elapse = 0
170 remain = 0
171 else:
172 self.elapse = self.decoder.tell()
173 self.remain = max(0, self.total_time - self.elapse)
174 self.write(buff, bytes)
175 if self.elapse != last_elapse or self.state == 'pause':
176 last_elapse = self.elapse
177 except:
178 self.state = 'stop'
179 self.decoder.close()
180 self.close()
182 def stop(self):
183 """Set a flag telling the current play-loop to exit and close the device"""
184 self.state = 'stop'
185 while True:
186 try: self.queue.get_nowait()
187 except Queue.Empty: break
189 def pause(self):
190 """Pause playback (works as a toggle between play and pause)"""
191 if self.state == 'play':
192 self.state = 'pause'
193 elif self.state == 'pause':
194 self.state = 'play'
196 def seek(self, percent):
197 """Jump to a specific point in the song by percent"""
198 self.seek_val = percent
200 def set_volume(self, volume, device=None, channel='PCM'):
201 """Set the PCM volume to a new value"""
202 vol = int(volume)
203 if HAVE_ALSA:
204 try:
205 mixer = alsaaudio.Mixer(channel, 0, device)
206 mixer.setvolume(vol)
207 except:
208 print >>sys.stderr, "Failed to open mixer device %s" % device
210 elif HAVE_OSS:
211 try:
212 mixer = ossaudiodev.openmixer(device)
213 mixer.set(ossaudiodev.SOUND_MIXER_PCM, (vol, vol))
214 except:
215 print >>sys.stderr, "Failed to open mixer device %s" % device
216 else:
217 pass
219 def get_volume(self, device=None, channel='PCM'):
220 """Return the current volume setting"""
221 if HAVE_ALSA:
222 try:
223 mixer = alsaaudio.Mixer(channel, 0, device)
224 vol = mixer.getvolume()
225 return float(max(vol[0], vol[1]))
226 except:
227 print >>sys.stderr, "Failed to open mixer device %s, channel %s" % (device, channel)
228 return 0
230 elif HAVE_OSS:
231 try:
232 mixer = ossaudiodev.openmixer(device)
233 vol = mixer.get(ossaudiodev.SOUND_MIXER_PCM)
234 return float(max(vol[0], vol[1]))
235 except:
236 print >>sys.stderr, "Failed to open mixer device %s" % device
237 return 0
238 else:
239 return 0