move to plugins/ where it belongs
[rox-musicbox.git] / player.py
blob636c872976d583a21597360fe6bbe03ed305c54d
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!!'
48 import linuxaudiodev
50 USING_DEV = None
52 class Player:
53 """A class to playback sound files to an audio device."""
54 state = 'stop'
55 remain = 0
56 elapse = 0
57 last_elapse = 0
58 total_time = 0
59 seek_val = 0
61 def __init__(self, id='esd', buffersize=4096, device='/dev/dsp',):
62 """Initialize the Player instance"""
63 self.id = id
64 self.buffersize = buffersize
65 self.dev = None
66 self.device = device
67 self.queue = Queue.Queue(5)
69 def open(self, rate=44100, channels=2):
70 """Open and configure the audio device driver"""
71 global USING_DEV
72 bits=16
73 #try 3 times to open the device, then give up.
74 for x in range(3):
75 try:
76 if HAVE_AO:
77 self.dev = ao.AudioDevice(self.id, bits, rate, channels)
78 USING_DEV = 'ao'
79 elif HAVE_ALSA and self.id in ('alsa09',):
80 self.dev = alsaaudio.PCM(cardname=self.device)
81 self.dev.setchannels(channels)
82 self.dev.setrate(rate)
83 if sys.byteorder == 'big':
84 format = alsaaudio.PCM_FORMAT_S16_BE
85 else:
86 format = alsaaudio.PCM_FORMAT_S16_LE
87 self.dev.setformat(format)
88 USING_DEV = 'alsa'
89 elif HAVE_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 USING_DEV = 'oss'
97 else:
98 self.dev = linuxaudiodev.open('w')
99 if sys.byteorder == 'big':
100 format = linuxaudiodev.AFMT_S16_BE
101 else:
102 format = linuxaudiodev.AFMT_S16_LE
103 self.dev.setparameters(rate, bits, channels, format)
104 USING_DEV = 'other'
105 break
106 except:
107 time.sleep(1)
110 def close(self):
111 """Close the current device if open and delete it"""
112 if self.dev:
113 if USING_DEV in ('ao', 'alsa', 'other'):
114 self.dev = None
115 elif USING_DEV in ('oss',):
116 self.dev.close()
119 def write(self, buff, bytes):
120 """Write data to the audio device"""
121 if USING_DEV in ('ao',):
122 self.dev.play(buff, bytes)
123 elif USING_DEV in ('oss', 'alsa'):
124 self.dev.write(buff)
125 else:
126 while self.dev.obuffree() < bytes:
127 time.sleep(0.2)
128 self.dev.write(buff[:bytes])
130 def play(self, name, type):
131 """Push the song info on the queue"""
132 if not os.path.isfile(name):
133 raise SyntaxError, "File not found or not accessible (%s)." % name
134 self.queue.put((name, type))
136 def run(self):
137 """Check the filename and type, create a decoder and start playing"""
138 while True:
139 (name, type) = self.queue.get()
140 if os.path.isfile(name):
141 self.decoder = plugins.get_decoder(name, type, self.buffersize)
142 else:
143 raise SyntaxError, 'play takes a filename.'
144 self.start()
146 def start(self):
147 """Start playing a file calling the current decoder to get file info and data"""
148 self.state = 'play'
150 self.decoder.open()
151 self.total_time = self.decoder.length()
152 self.remain = self.total_time
153 self.elapse = 0
154 last_elapse = 0
156 try:
157 self.open(self.decoder.samplerate(), self.decoder.channels())
159 while self.state == 'play' or self.state == 'pause':
160 if self.state == 'pause':
161 time.sleep(1)
162 elif self.seek_val:
163 self.decoder.seek(self.seek_val)
164 self.seek_val = 0
165 else:
166 (buff, bytes) = self.decoder.read()
167 if buff is None:
168 self.state = 'eof'
169 self.elapse = self.total_time
170 last_elapse = 0
171 remain = 0
172 else:
173 self.elapse = self.decoder.tell()
174 self.remain = max(0, self.total_time - self.elapse)
175 self.write(buff, bytes)
176 if self.elapse != last_elapse or self.state == 'pause':
177 last_elapse = self.elapse
178 except:
179 self.state = 'stop'
180 self.decoder.close()
181 self.close()
183 def stop(self):
184 """Set a flag telling the current play-loop to exit and close the device"""
185 self.state = 'stop'
186 while True:
187 try: self.queue.get_nowait()
188 except Queue.Empty: break
190 def pause(self):
191 """Pause playback (works as a toggle between play and pause)"""
192 if self.state == 'play':
193 self.state = 'pause'
194 elif self.state == 'pause':
195 self.state = 'play'
197 def seek(self, percent):
198 """Jump to a specific point in the song by percent"""
199 self.seek_val = percent
201 def set_volume(self, volume, device=None):
202 """Set the PCM volume to a new value"""
203 vol = int(volume)
204 if HAVE_ALSA:
205 try:
206 mixer = alsaaudio.Mixer('PCM', 0, device)
207 mixer.setvolume(vol)
208 except:
209 print >>sys.stderr, "Failed to open mixer device %s" % device
211 elif HAVE_OSS:
212 try:
213 mixer = ossaudiodev.openmixer(device)
214 mixer.set(ossaudiodev.SOUND_MIXER_PCM, (vol, vol))
215 except:
216 print >>sys.stderr, "Failed to open mixer device %s" % device
217 else:
218 pass
220 def get_volume(self, device=None):
221 """Return the current volume setting"""
222 if HAVE_ALSA:
223 try:
224 mixer = alsaaudio.Mixer('PCM', 0, device)
225 vol = mixer.getvolume()
226 return float(max(vol[0], vol[1]))
227 except:
228 print >>sys.stderr, "Failed to open mixer device %s" % device
229 return 0
231 elif HAVE_OSS:
232 try:
233 mixer = ossaudiodev.openmixer(device)
234 vol = mixer.get(ossaudiodev.SOUND_MIXER_PCM)
235 return float(max(vol[0], vol[1]))
236 except:
237 print >>sys.stderr, "Failed to open mixer device %s" % device
238 return 0
239 else:
240 return 0