In this blog post, we are going to cover how the audio from Youtube is being used in SUSI Smart Speaker and how we reduced the response time from ~40 seconds to ~4 seconds for an average music video length.
First Approach
Earlier, we were using MPV player’s inbuilt feature to fetch the YouTube music. However, MPV player was a bulky option and the music server had to be started every time before initiating a music video.
video_process = subprocess.Popen([‘mpv’, ‘–no-video’, ‘https://www.youtube.com/watch?v=’ + video_url[4:], ‘–really-quiet’]) # nosec #pylint-disable type: ignore requests.get(‘http://localhost:7070/song/’ + video_url) self.video_process = video_process stopAction.run() stopAction.detector.terminate()
Making it Efficient
To reduce the response time, we created a custom Music Server based on Flask,python-vlc and python-pafy which accepts requests from the main client and instructs the System to play the music with just 90% more efficiency.
app = Flask(__name__)
Instance = vlc.Instance(‘–no-video’)
player = Instance.media_player_new()
url = ” @app.route(‘/song’, methods=[‘GET’])
def youtube():
vid = request.args.get(‘vid’)
url = ‘https://www.youtube.com/watch?v=’ + vid
video = pafy.new(url)
streams = video.audiostreams
best = streams[3]
playurl = best.url
Media = Instance.media_new(playurl)
Media.get_mrl()
player.set_media(Media)
player.play()
display_message = {“song”:“started”}
resp = jsonify(display_message)
resp.status_code = 200
return resp
However, shifting to this Server removed the ability to process multiple queries and hence we were unable to pause/play/stop the music until it completed the time duration. We wanted to retain the ability to have ‘play/pause/stop’ actions without implementing multiprocessing or multithreading as it would’ve required extensive testing to successfully implement them without creating deadlocks and would’ve been overkill for a simple feature.
Bringing Back the Lost Functionalities
The first Step we took was to remove the vlc-python module and implement a way to obtain an URL that we use in another asynchronous music player.
@app.route(‘/song’, methods=[‘GET’])
def youtube():
vid = request.args.get(‘vid’)
streams = video.audiostreams
best = streams[3]
playurl = best.url
display_message = {“song”: “started”, “url”: playurl}
resp = jsonify(display_message)
resp.status_code = 200
return resp
The next issue was to actually find a way to run the Music Player asynchronously. We used the `subprocess. Popen` method and cvlc to play the songs asynchronously.
try:
x = requests.get(‘http://localhost:7070/song?vid=’ + video_url[4:])
data = x.json()
url = data[‘url’]
video_process = subprocess.Popen([‘cvlc’, ‘http’ + url[5:], ‘–no-video’])
self.video_process = video_process
except Exception as e:
logger.error(e);
And this is how we were able to increase the efficiency of the music player while maintaining the functionalities.