Closed norbusan closed 5 years ago
Agree about threads. Most of the work are done by external program: VLC, calling Google API, so GIL is not a problem.
How will this thread work? How will we pass the recognized audio to this thread?
one processing state machine states: - idle - busy
So many options, just search for python inter thread communication.
For example a queue https://www.bogotobogo.com/python/Multithread/python_multithreading_Synchronization_Producer_Consumer_using_Queue.php
Controlling VLC via python - it is much better to use the python vlc module, here example code:
import vlc
from time import sleep
player = vlc.MediaPlayer("music/beyond.wav")
player.play()
print("Playing song\n")
sleep(3)
player.pause()
print("Pausing song\n")
sleep(3)
print("Starting song again")
player.play()
sleep(3)
print("Changing song")
player.set_mrl("music/junge.wav")
player.play()
sleep(3)
This can easily be integrated into a receive message system sent from other threads.
Just for the reference, here is a minimal command line player that supports
play <MRL>
pause
resume
stop
and all this is done via a separate thread
BUT: we actually don't need a separate thread, since the VLC module returns immediately, we can just wrap all that up in more simple calls in the state machine ... working on that now ...
#!/usr/bin/python3
import threading
import queue
import vlc
import sys
cmd_q = queue.Queue()
def vlc_thread():
player = vlc.MediaPlayer()
while True:
s = cmd_q.get()
if s is None:
break
cmd = s.split(" ",1)
if (cmd[0] == "play"):
print("Setting mrl to " + cmd[1])
player.set_mrl(cmd[1])
player.pause()
player.play()
elif (cmd[0] == "pause"):
player.pause()
elif (cmd[0] == "resume"):
player.play()
elif (cmd[0] == "stop"):
player.stop()
else:
print("\nVLC thread: Got cmd >>" + cmd[0] + "<< and rest " + cmd[1] + "\n")
def main(args):
vlc = threading.Thread(target=vlc_thread)
vlc.start()
running = True
while running:
line = input('VLC cmd: ')
if (line == "quit"):
cmd_q.put(None)
vlc.join()
running = False
else:
cmd_q.put(line)
if __name__ == '__main__':
main(sys.argv[1:])
@norbusan , if we can run this through a flask server, we can also integrate the play/pause/stop/restart functionality in the mobile app and control the music via the app as-well.
Also , we were using the python-vlc module before but we removed it because of some issue.
@stealthanthrax yes, that would be possible. Then the processing would just curl to the url/port to play a song or say something.
Concerning python-vlc, indeed, now I see that it is in the requirements.txt, but nowhere used.
Do you have any further information why it was not used or what was the problem?
@norbusan , vlc was removed here(https://github.com/fossasia/susi_linux/pull/450/files) in order to add multiple query support for music player. And cvlc
was run as a background process instead.
@stealthanthrax Yes indeed, but I don't see how this properly deals with async support? And why this couldn't have been realized by having a thread (or the flask server as before) doing audio output?
The discussion states
This PR now uses cvlc instead of vlc module, to allow background process for an asynchronous approach of SUSI.
but I don't see that change actually happening. What has happened is that the StopDetector was added, that allows for partial recognition during playback.
Before I do all that back with python-vlc, I would really like to see how you came to the point of saying that it allows asynchronou access, and why this is not possible using python-vlc. Actually, I think it is much easier with it.
@norbusan Now I remember. I was unable to implement a multi-threaded architecture for the complete system and hence I used the cvlc
module to play/pause and stop the music using a temporary way as we needed a way to add music controls at that time.
@stealthanthrax aah, ok, that explains a lot ... but, btw, why do we need a multi threaded approach for the vlc playback? The commands of python-vlc
(like .play(), .pause(), ...) return immediately, so one can actually implement playback control, including reduction of volume etc, in the same main thread.
An example (quick and dirty hacked together while watching my child) is in the branch better-vlc-control
, please look at it. Basically it imports a VlcPlayer module, and uses its commands:
class VlcPlayer():
def __init__(self):
self.instance = vlc.Instance("--no-video")
self.player = self.instance.media_player_new()
self.list_player = self.instance.media_list_player_new()
self.list_player.set_media_player(self.player)
def play(self, mrl):
media = self.instance.media_new(mrl)
media_list = self.instance.media_list_new([mrl])
self.player.set_media(media)
self.list_player.set_media_list(media_list)
self.list_player.play()
def pause(self):
self.list_player.pause()
def resume(self):
self.list_player.resume()
def stop(self):
self.list_player.stop()
def wait_till_end(self):
playing = set([vlc.State.Playing, vlc.State.Buffering])
time_left = True
while time_left == True:
pstate = self.list_player.get_state()
if pstate not in playing:
time_left = False
print("Sleeping for audio output")
time.sleep(1)
def say(self,mrl):
# this is tricky!!!
if self.list_player.is_playing():
self.list_player.pause()
# TODO wait for the length of what is said, then restart main player!
This is of course work in progress ..
@norbusan , this looks nice!! What is the say
function though?
"say" is for short utterances that may interrupt audio playback (music) but the music would be restarted after that. So if I listen to audio I can ask a question, get an answer, and the music would continue.
@stealthanthrax I have tried moving all sound playback (including the ding sound etc) to a flask server and make the susi_linux only hit the respective URLs. That does work indeed, but adds a bit of latency. I am not sure which approach I want to suggest. My current dev branch does all the stuff within the python code, no server is used.
I think it boils down how important music playback via mobile apps is - and I guess it is ...
@norbusan , I wouldn't recommend removing the flask server as it would restrict a lot of future functionalities.
@stealthanthrax I agree, see my comments in the gitter channel. But what the server is providing at the moment is just a json answer of the url of the audio track of a video using pafy, not doing any media playback whatsoever.
I probably will go with my last version:
@norbusan About a dedicated server to play audio and react to pause/play/volume changes, I already thought about MPD. Do you have the same thinking?
Hmm, I was thinking about some home-grown Flask that used python-vlc. It is so simple, I had already the code finished and working, just about 50 lines or so. MPD is nice, but big, and does much much more that we really need.
Then, it is Ok.
@hongquan Ok, here is the code:
My plan is to use the soundserver on the raspi, and play music from susi_linux via the sound server.
From the git commit:
add a sound server and vlcplayer
The vlcplayer is a module that can be loaded and provides
playback capabilities via python-vlc.
The soundserver is a Flask server running at port 7070 and
reacting to requests using an instance of the vlcplayer.
Supported URL schemata are:
- /play?ytb=??? play a youtube video, argument is vid
- /play?mrl=??? play an arbitrary MRL
- /volume?val=up volume up by 10% point
- /volume?val=down volume down by 10% point
- /volume?val=NN adjust volume to NN (0<=NN<=100)
- /say?mrl=??? reduce volume if currently playing, play mrl,
restore volume
- /beep?mrl=??? like say, but does not restore volume afterwards
- /pause pause if playing
- /resume resume if paused
- /stop stop playback
- /save_volume save current volume
- /restore_volume restore current volume
merged
Currently, we have one state machine
In addition, when in the busy state we have a stop detector that allows to interrupt audio/video playback (in principle).
But other actions, like searching, volume, asking the time etc are not possible while in busy state.
I propose the following:
Concerning music thread
implementation notes
states and transitions on messages
Hotword/recognizing state machine:
Processing state machine
when in idle state and "request: ..." is received . go to busy state . send to susi_server, get response . do what is necessary
the busy state shouldn't take too long, since it is mostly the communication with the susi_server and sending messages to the audio thread. We might drop the state approach here, and just use a loop that can be interrupted.
when in busy state and "request: ..." is received (good question!) either cancel the current action? or push new requests into a request queue that is worked upon?
Implementation comments
Implementation can be done via threads, but would suffer from the GIL (Great Interpreter Lock). Threads would be easier to communicate between them, process would needs some IPC. ATM we probably don't need that much computing power so the GIL might not be a problem.