spacepaste

  1.  
  2. import os
  3. import StringIO
  4. import pymedia
  5. import threading
  6. import logging
  7. # Audio options
  8. WAV = {
  9. "bytes_minimum":4096,
  10. }
  11. MP3 = {
  12. "bytes_minimum":4096*2,
  13. }
  14. OGG = {
  15. "bytes_minimum":4096*4,
  16. }
  17. FORMATS = {
  18. ".wav":WAV,
  19. ".mp3":MP3,
  20. ".ogg":OGG,
  21. }
  22. __doc__ = """This library contains the Player class which is the threaded player for music/sfx playback with the backing of pymedia library."""
  23. class Player(threading.Thread):
  24. '''Simple media player written with threaded support based on pymedia.
  25. This threaded player will attempt to retrieve data from any kind of file object with seek
  26. and read method. It will then, based on the passed filename, decide which codec to use and
  27. demux + decode the data on the fly. It will require the constant FORMATS
  28. to be declared, with the number of bytes per tick to read.
  29. Player can be played, paused or stopped. However, the change will only manifest
  30. itself when the tick passed (ie enough data was processed). This is FORMATS based
  31. because different formats need different minimal data volume per block to success
  32. fully demux and decode.
  33. '''
  34. def __init__(self):
  35. '''Constructor'''
  36. threading.Thread.__init__(self)
  37. self.alive = True
  38. self.dec = None
  39. self.volume = 1.0
  40. self.repeat = True
  41. self.data = None
  42. self.finfoformat = None
  43. self.audioobj = None
  44. self.mplay = False
  45. def null():
  46. pass # empty call in case we do not need the exception_call
  47. self.exception_call = null
  48. def set_exception_callback(self, what):
  49. self.exception_call = what
  50. def __set_volume(self):
  51. """Internal volume setter.
  52. This method it called every tick to set the volume.
  53. """
  54. volume = int(100 * self.volume)
  55. vol = (volume << 8) + volume
  56. if vol > ((100 << 8) + 100):
  57. vol = (100 << 8) + 100
  58. elif vol < 0:
  59. vol = 0
  60. self.audioobj.setVolume(vol)
  61. def get_volume(self):
  62. """returns the volume.
  63. Returns:
  64. volume - 0xFFFF formated volume (100 is max).
  65. This volume is the volume of the sound mixer, not the track itself!
  66. """
  67. volume = self.audioobj.getVolume()
  68. return volume
  69. def is_alive(self):
  70. """returns the thread status."""
  71. return self.alive
  72. def change_repeat(self, repeatval):
  73. """changes the repeat of the music/sound playback.
  74. Parameters:
  75. repeatval - Boolean
  76. """
  77. self.repeat = repeatval
  78. def add_data(self, io, ext="foo.wav"):
  79. """loads the demuxer and Input/Output object.
  80. Parameters:
  81. io - File like object, with seek and read methods
  82. Optional parameters:
  83. ext - File name containing extension, used for initializing demuxer.
  84. """
  85. self.finfoformat = FORMATS[os.path.splitext(ext)[1]]
  86. self.dm = pymedia.muxer.Demuxer(str.split(ext,".")[-1].lower())
  87. self.data = io
  88. self.data.seek(0)
  89. def play(self):
  90. """starts the playback."""
  91. self.mplay = True
  92. def pause(self):
  93. """pauses the playback."""
  94. self.mplay = False
  95. def resume(self):
  96. """resumes the playback"""
  97. self.play()
  98. def stop(self):
  99. """suspends the playback and kills the thread."""
  100. self.pause()
  101. self.kill()
  102. def kill(self):
  103. """kills the thread."""
  104. self.alive = False
  105. def run(self):
  106. """main thread instance loop.
  107. This loop operates in ticks, with one tick being one round of the loop.
  108. The loop starts with loading the data from the IO source into the buffer
  109. then if the data is less than the full amount, it will either decide to
  110. reload the buffer or to kill the playback and the loop (based on the
  111. repeat value).
  112. Afterwards, it will try to demux the frames out of the loaded buffer
  113. and should it succeed, it will then play all the frames it was successful
  114. in the demuxing.
  115. The volume setter is set every time the frame is being played, so the fadeout
  116. should be working on the system that is supporting it (not supporting systems
  117. include PulseAudio or Alsa, but Windows seems to work).
  118. In this part of the loop, the audio playback device is instantiated, and should
  119. it fail with SoundError exception, it will be logged and thread will be promptly
  120. killed with a return. The callback exception will be called as well.
  121. If no problems were detected, the playback will continue until the whole IO object
  122. was used and either loop or end.
  123. """
  124. buf = ""
  125. while self.alive:
  126. if self.mplay:
  127. buf += self.data.read(self.finfoformat["bytes_minimum"])
  128. if len(buf) < self.finfoformat["bytes_minimum"]:
  129. if self.repeat:
  130. self.data.seek(0)
  131. else:
  132. self.kill()
  133. try:
  134. frames = self.dm.parse(buf)
  135. except:
  136. frames = None
  137. if frames:
  138. for fr in frames:
  139. if not self.mplay:
  140. break
  141. if self.dec is None:
  142. self.dec = pymedia.audio.acodec.Decoder(self.dm.streams[fr[0]])
  143. dat = self.dec.decode(fr[1])
  144. if dat is not None:
  145. if self.audioobj is None:
  146. try:
  147. self.audioobj = pymedia.audio.sound.Output(dat.sample_rate, dat.channels, pymedia.audio.sound.AFMT_S16_LE)
  148. except pymedia.audio.sound.SoundError, e:
  149. logging.error("Music thread failed with: %s" %str(e))
  150. self.exception_call()
  151. return
  152. self.__set_volume()
  153. self.audioobj.play(dat.data)
  154. buf = ""
  155.