2020-10-29 21:23:37 +00:00
|
|
|
from queue import Empty
|
|
|
|
import multiprocessing
|
|
|
|
import setproctitle
|
|
|
|
import copy
|
|
|
|
import json
|
|
|
|
import time
|
|
|
|
from pygame import mixer
|
2020-10-25 01:23:24 +00:00
|
|
|
from state_manager import StateManager
|
|
|
|
from mutagen.mp3 import MP3
|
|
|
|
import os
|
|
|
|
os.environ['PYGAME_HIDE_SUPPORT_PROMPT'] = "hide"
|
2020-10-23 23:45:27 +00:00
|
|
|
|
2020-10-23 20:10:32 +00:00
|
|
|
|
2020-10-24 20:31:52 +00:00
|
|
|
class Player():
|
|
|
|
state = None
|
|
|
|
running = False
|
|
|
|
|
|
|
|
__default_state = {
|
|
|
|
"filename": "",
|
|
|
|
"channel": -1,
|
|
|
|
"playing": False,
|
2020-10-29 22:25:17 +00:00
|
|
|
"loaded": False,
|
2020-10-24 20:31:52 +00:00
|
|
|
"pos": 0,
|
|
|
|
"remaining": 0,
|
|
|
|
"length": 0,
|
|
|
|
"loop": False,
|
|
|
|
"output": None
|
|
|
|
}
|
|
|
|
|
2020-10-29 22:25:17 +00:00
|
|
|
@property
|
2020-10-24 20:31:52 +00:00
|
|
|
def isInit(self):
|
|
|
|
try:
|
2020-10-25 01:23:24 +00:00
|
|
|
mixer.music.get_busy()
|
2020-10-24 20:31:52 +00:00
|
|
|
except:
|
|
|
|
return False
|
2020-10-29 22:25:17 +00:00
|
|
|
|
|
|
|
return True
|
2020-10-24 20:31:52 +00:00
|
|
|
|
2020-10-29 21:23:37 +00:00
|
|
|
@property
|
2020-10-24 20:31:52 +00:00
|
|
|
def isPlaying(self):
|
2020-10-29 22:25:17 +00:00
|
|
|
if self.isInit:
|
|
|
|
return bool(mixer.music.get_busy())
|
|
|
|
return False
|
2020-10-24 20:31:52 +00:00
|
|
|
|
2020-10-29 21:23:37 +00:00
|
|
|
@property
|
|
|
|
def isLoaded(self):
|
2020-10-29 22:25:17 +00:00
|
|
|
if not self.state.state["filename"]:
|
|
|
|
return False
|
|
|
|
if self.isPlaying:
|
|
|
|
return True
|
|
|
|
|
|
|
|
try:
|
|
|
|
current_pos = mixer.music.get_pos()
|
|
|
|
mixer.music.set_pos(current_pos)
|
|
|
|
except:
|
|
|
|
# TODO: Trigger specially off the SDLError (couldn't find it)
|
|
|
|
return False
|
|
|
|
|
2020-10-29 21:23:37 +00:00
|
|
|
return False
|
|
|
|
|
2020-10-24 20:31:52 +00:00
|
|
|
def play(self):
|
2020-10-29 22:25:17 +00:00
|
|
|
if not self.isPlaying:
|
|
|
|
try:
|
|
|
|
mixer.music.play(0)
|
|
|
|
except:
|
|
|
|
return False
|
2020-10-24 20:31:52 +00:00
|
|
|
|
2020-10-29 22:25:17 +00:00
|
|
|
return True
|
|
|
|
return False
|
2020-10-24 20:31:52 +00:00
|
|
|
|
|
|
|
def pause(self):
|
2020-10-29 22:25:17 +00:00
|
|
|
if self.isPlaying:
|
|
|
|
try:
|
|
|
|
mixer.music.pause()
|
|
|
|
except:
|
|
|
|
return False
|
|
|
|
|
|
|
|
return True
|
|
|
|
return False
|
2020-10-24 20:31:52 +00:00
|
|
|
|
|
|
|
def unpause(self):
|
2020-10-29 22:25:17 +00:00
|
|
|
if not self.isPlaying:
|
|
|
|
try:
|
|
|
|
mixer.music.play(0, self.state.state["pos"])
|
|
|
|
except:
|
|
|
|
return False
|
|
|
|
|
|
|
|
return True
|
|
|
|
return False
|
2020-10-24 20:31:52 +00:00
|
|
|
|
|
|
|
def stop(self):
|
2020-10-29 22:25:17 +00:00
|
|
|
if self.isPlaying:
|
|
|
|
try:
|
|
|
|
mixer.music.stop()
|
|
|
|
except:
|
|
|
|
return False
|
|
|
|
|
|
|
|
return True
|
|
|
|
return False
|
2020-10-24 20:31:52 +00:00
|
|
|
|
|
|
|
def seek(self, pos):
|
2020-10-29 22:25:17 +00:00
|
|
|
if self.isPlaying:
|
|
|
|
try:
|
|
|
|
mixer.music.play(0, pos)
|
|
|
|
except:
|
|
|
|
return False
|
|
|
|
return True
|
|
|
|
|
|
|
|
return False
|
2020-10-24 20:31:52 +00:00
|
|
|
|
|
|
|
def load(self, filename):
|
2020-10-29 22:25:17 +00:00
|
|
|
if not self.isPlaying:
|
|
|
|
|
2020-10-24 20:31:52 +00:00
|
|
|
self.state.update("filename", filename)
|
2020-10-29 22:25:17 +00:00
|
|
|
|
|
|
|
try:
|
|
|
|
mixer.music.load(filename)
|
|
|
|
except:
|
|
|
|
# We couldn't load that file.
|
|
|
|
print("Couldn't load file:", filename)
|
|
|
|
return False
|
|
|
|
|
|
|
|
try:
|
|
|
|
if ".mp3" in filename:
|
|
|
|
song = MP3(filename)
|
|
|
|
self.state.update("length", song.info.length)
|
|
|
|
else:
|
|
|
|
self.state.update("length", mixer.Sound(filename).get_length()/1000)
|
|
|
|
except:
|
|
|
|
return False
|
|
|
|
return True
|
|
|
|
|
|
|
|
def unload(self):
|
|
|
|
if not self.isPlaying:
|
|
|
|
try:
|
|
|
|
mixer.music.unload()
|
|
|
|
self.state.update("filename", "")
|
|
|
|
except:
|
|
|
|
return False
|
|
|
|
return not self.isLoaded
|
2020-10-24 20:31:52 +00:00
|
|
|
|
|
|
|
def quit(self):
|
2020-10-25 01:23:24 +00:00
|
|
|
mixer.quit()
|
2020-10-24 20:31:52 +00:00
|
|
|
|
|
|
|
def output(self, name=None):
|
|
|
|
self.quit()
|
|
|
|
try:
|
|
|
|
if name:
|
2020-10-25 01:23:24 +00:00
|
|
|
mixer.init(44100, -16, 1, 1024, devicename=name)
|
2020-10-24 20:31:52 +00:00
|
|
|
else:
|
2020-10-25 01:23:24 +00:00
|
|
|
mixer.init(44100, -16, 1, 1024)
|
2020-10-24 20:31:52 +00:00
|
|
|
except:
|
|
|
|
return "FAIL:Failed to init mixer, check sound devices."
|
|
|
|
else:
|
|
|
|
self.state.update("output", name)
|
|
|
|
|
|
|
|
return "OK"
|
|
|
|
|
|
|
|
def updateState(self, pos=None):
|
2020-10-29 21:23:37 +00:00
|
|
|
self.state.update("playing", self.isPlaying)
|
2020-10-29 22:25:17 +00:00
|
|
|
self.state.update("loaded", self.isLoaded)
|
2020-10-24 20:31:52 +00:00
|
|
|
if (pos):
|
|
|
|
self.state.update("pos", max(0, pos))
|
|
|
|
else:
|
2020-10-25 01:23:24 +00:00
|
|
|
self.state.update("pos", max(0, mixer.music.get_pos()/1000))
|
2020-10-24 20:31:52 +00:00
|
|
|
self.state.update("remaining", self.state.state["length"] - self.state.state["pos"])
|
|
|
|
|
|
|
|
def getDetails(self):
|
|
|
|
res = "RESP:DETAILS: " + json.dumps(self.state.state)
|
|
|
|
return res
|
|
|
|
|
|
|
|
def __init__(self, channel, in_q, out_q):
|
|
|
|
self.running = True
|
2020-10-25 01:23:24 +00:00
|
|
|
setproctitle.setproctitle("BAPSicle - Player " + str(channel))
|
2020-10-24 20:31:52 +00:00
|
|
|
|
|
|
|
self.state = StateManager("channel" + str(channel), self.__default_state)
|
|
|
|
|
|
|
|
self.state.update("channel", channel)
|
|
|
|
|
|
|
|
loaded_state = copy.copy(self.state.state)
|
|
|
|
|
|
|
|
if loaded_state["output"]:
|
|
|
|
print("Setting output to: " + loaded_state["output"])
|
|
|
|
self.output(loaded_state["output"])
|
|
|
|
else:
|
2020-10-29 21:23:37 +00:00
|
|
|
print("Using default output device.")
|
2020-10-24 20:31:52 +00:00
|
|
|
self.output()
|
|
|
|
|
|
|
|
if loaded_state["filename"]:
|
|
|
|
print("Loading filename: " + loaded_state["filename"])
|
|
|
|
self.load(loaded_state["filename"])
|
|
|
|
|
2020-10-29 21:23:37 +00:00
|
|
|
if loaded_state["pos"] != 0:
|
|
|
|
print("Seeking to pos: " + str(loaded_state["pos"]))
|
|
|
|
self.seek(loaded_state["pos"])
|
2020-10-24 20:31:52 +00:00
|
|
|
|
2020-10-29 21:23:37 +00:00
|
|
|
if loaded_state["playing"] == True:
|
|
|
|
print("Resuming.")
|
|
|
|
self.unpause()
|
|
|
|
else:
|
|
|
|
print("No file was previously loaded.")
|
2020-10-24 20:31:52 +00:00
|
|
|
|
|
|
|
while self.running:
|
|
|
|
time.sleep(0.01)
|
2020-10-29 21:23:37 +00:00
|
|
|
try:
|
|
|
|
try:
|
|
|
|
incoming_msg = in_q.get_nowait()
|
|
|
|
except Empty:
|
|
|
|
# The incomming message queue was empty,
|
|
|
|
# skip message processing
|
|
|
|
pass
|
|
|
|
else:
|
|
|
|
# We got a message.
|
2020-10-29 22:25:17 +00:00
|
|
|
if self.isInit:
|
|
|
|
|
2020-10-29 21:23:37 +00:00
|
|
|
self.updateState()
|
|
|
|
|
|
|
|
if (incoming_msg == 'LOADED?'):
|
|
|
|
out_q.put(self.isLoaded)
|
|
|
|
continue
|
|
|
|
|
|
|
|
if (incoming_msg == 'PLAY'):
|
|
|
|
self.play()
|
|
|
|
if (incoming_msg == 'PAUSE'):
|
|
|
|
self.pause()
|
|
|
|
if (incoming_msg == 'UNPAUSE'):
|
|
|
|
self.unpause()
|
|
|
|
if (incoming_msg == 'STOP'):
|
|
|
|
self.stop()
|
|
|
|
if (incoming_msg == 'QUIT'):
|
|
|
|
self.quit()
|
|
|
|
self.running = False
|
|
|
|
if (incoming_msg.startswith("SEEK")):
|
|
|
|
split = incoming_msg.split(":")
|
|
|
|
self.seek(float(split[1]))
|
|
|
|
if (incoming_msg.startswith("LOAD")):
|
|
|
|
split = incoming_msg.split(":")
|
|
|
|
self.load(split[1])
|
|
|
|
if (incoming_msg == 'DETAILS'):
|
|
|
|
out_q.put(self.getDetails())
|
|
|
|
|
|
|
|
if (incoming_msg.startswith("OUTPUT")):
|
|
|
|
split = incoming_msg.split(":")
|
|
|
|
out_q.put(self.output(split[1]))
|
|
|
|
|
|
|
|
# Catch the player being killed externally.
|
|
|
|
except KeyboardInterrupt:
|
|
|
|
break
|
|
|
|
except SystemExit:
|
|
|
|
break
|
|
|
|
except:
|
|
|
|
raise
|
2020-10-24 20:31:52 +00:00
|
|
|
|
|
|
|
print("Quiting player ", channel)
|
2020-10-29 21:23:37 +00:00
|
|
|
self.quit()
|
|
|
|
|
|
|
|
|
|
|
|
def showOutput(in_q, out_q):
|
|
|
|
print("Starting showOutput().")
|
|
|
|
while True:
|
|
|
|
time.sleep(0.01)
|
|
|
|
incoming_msg = out_q.get()
|
|
|
|
print(incoming_msg)
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
|
|
|
|
in_q = multiprocessing.Queue()
|
|
|
|
out_q = multiprocessing.Queue()
|
|
|
|
|
|
|
|
outputProcess = multiprocessing.Process(
|
|
|
|
target=showOutput,
|
|
|
|
args=(in_q, out_q),
|
|
|
|
).start()
|
|
|
|
|
|
|
|
playerProcess = multiprocessing.Process(
|
|
|
|
target=Player,
|
|
|
|
args=(-1, in_q, out_q),
|
|
|
|
).start()
|
|
|
|
|
|
|
|
# Do some testing
|
2020-10-29 22:25:17 +00:00
|
|
|
in_q.put("LOADED?")
|
|
|
|
in_q.put("PLAY")
|
|
|
|
in_q.put("LOAD:\\Users\\matth\\Documents\\GitHub\\bapsicle\\dev\\test.mp3")
|
|
|
|
in_q.put("LOADED?")
|
|
|
|
in_q.put("PLAY")
|
2020-10-29 21:23:37 +00:00
|
|
|
print("Entering infinite loop.")
|
|
|
|
while True:
|
|
|
|
pass
|