AURA Web Client

Hello everyone,

I’ve made a client for the AURA specification which can be used with the beets AURA plugin I made.

It has a very unoriginal name and it’s not beautiful, but I quite like how it works (not surprising since I made it). Take a look at it here.

I don’t generally like web apps so at some point I might make something more like cmus (maybe fork it so I don’t have to do the hard bits), but I thought for a first client it made sense to make something that can be used instead of the web plugin.

Let me know what you think :slight_smile:

Callum

3 Likes

Aaa, this is so awesome! It doesn’t have to be beautiful to be very very cool. I really like that it uses a queue-based player! I will definitely use this. :tada:

1 Like

Thanks! In quite a few other players I’ve found the queue to be a bit of an afterthought so I wanted to make it more important

1 Like

Thanks Callum ! I’m switching to this client too.

1 Like

other beets users seem to be able to make this work, but i get a ‘failed to fetch’ exception, both when i use xdg-open index.html or open the index.html directly from a browser.

here’s the full stack trace:

  pi@raspberrypi:~ $ [2021-01-19 13:23:24,357] ERROR in app: Exception on /aura/server [GET]
  Traceback (most recent call last):
  File "/usr/lib/python3/dist-packages/flask/app.py", line 1974, in make_response
  rv = self.response_class.force_type(rv, request.environ)
  File "/usr/lib/python3/dist-packages/werkzeug/wrappers.py", line 921, in force_type
  response = BaseResponse(*_run_wsgi_app(response, environ))
  File "/usr/lib/python3/dist-packages/werkzeug/wrappers.py", line 59, in _run_wsgi_app
  return _run_wsgi_app(*args)
  File "/usr/lib/python3/dist-packages/werkzeug/test.py", line 923, in run_wsgi_app
  app_rv = app(environ, start_response)
  TypeError: 'dict' object is not callable

  During handling of the above exception, another exception occurred:

  Traceback (most recent call last):
  File "/usr/lib/python3/dist-packages/flask/app.py", line 2292, in wsgi_app
  response = self.full_dispatch_request()
  File "/usr/lib/python3/dist-packages/flask/app.py", line 1816, in full_dispatch_request
  return self.finalize_request(rv)
  File "/usr/lib/python3/dist-packages/flask/app.py", line 1831, in finalize_request
  response = self.make_response(rv)
  File "/usr/lib/python3/dist-packages/flask/app.py", line 1982, in make_response
  reraise(TypeError, new_error, sys.exc_info()[2])
  File "/usr/lib/python3/dist-packages/flask/_compat.py", line 34, in reraise
  raise value.with_traceback(tb)
  File "/usr/lib/python3/dist-packages/flask/app.py", line 1974, in make_response
  rv = self.response_class.force_type(rv, request.environ)
  File "/usr/lib/python3/dist-packages/werkzeug/wrappers.py", line 921, in force_type
  response = BaseResponse(*_run_wsgi_app(response, environ))
  File "/usr/lib/python3/dist-packages/werkzeug/wrappers.py", line 59, in _run_wsgi_app
  return _run_wsgi_app(*args)
  File "/usr/lib/python3/dist-packages/werkzeug/test.py", line 923, in run_wsgi_app
  app_rv = app(environ, start_response)
  TypeError: 'dict' object is not callable
  The view function did not return a valid response. The return type must be a string, tuple, Response instance, or WSGI callable, but it was a dict.
  127.0.0.1 - - [19/Jan/2021 13:23:24] "GET /aura/server HTTP/1.1" 500 

[i sent this to your sr.ht list, too; let me know which forum you prefer.]

Rik

Hi, I sent a response via email, which you should also be able to see on the sr.ht email list.

I don’t mind using this forum but I’d like to see how sr.ht works as much as anything :slight_smile:

Hi again Callum, i apologize for my long gap in conversation. Real Life keeps getting in the way of music(:

when we last left our thread, we thought the issue was my using an old flask module. i’ve since updated
>>> import flask
>>> flask.version
‘1.1.2’
but now the fetch is failing because something is adding server/ to the request URL? see attached
any ideas? need to see more debugging output? - Rik

Hi, don’t worry about the gap!

The client accesses the /server endpoint to check the server is running and what features it supports.

I have a suspicion the failure is due to cross-origin resource sharing. Maybe try adding something like this to your config:

aura:
    cors:
        - 'null'

If that doesn’t work you could open the network tab in developer tools, reload the page and try again. It might give more info on what’s going on.

i had the aura.cors parameter set to 127.0.0.1. changing it to null didn’t change anything. but using the devp tools was a good idea! it points to getAuraDoc @ aura-web-client.js:77 view attached.

Yep that’s a CORS problem. Do you have flask-cors installed?

If you already do using null should work, but make sure to have quotes around it, like "null".
You could also try setting it to allow all origins by adding "*" to the list, but it’s better to be specific.

Also, unless you’ve set the port to 80 in the config you’ll want to include it in the server URL:

http://127.0.0.1:8337/aura/

duh! sorry i had forgot the :8337 port last time. and now i can navigate albums, add to queue, etc. but when i try to play a track i get this 404 error: GET /aura/tracks/15/audio HTTP/1.1" 404 -. the devTools console shows only Failed to load resource: the server responded with a status of 404 (Not Found)

perhaps related are these (still CORS-related?) Issues:

1. Indicate whether to send a cookie in a cross-site request by specifying its SameSite attribute

  1. Because a cookie’s `SameSite` attribute was not set or is invalid, it defaults to `SameSite=Lax` , which prevents the cookie from being sent in a cross-site request. This behavior protects user data from accidentally leaking to third parties and cross-site request forgery.

Resolve this issue by updating the attributes of the cookie:

    * Specify `SameSite=None` and `Secure` if the cookie should be sent in cross-site requests. This enables third-party use.
    * Specify `SameSite=Strict` or `SameSite=Lax` if the cookie should not be sent in cross-site requests.

Hmm. The only things I can think of is that there is no file where it should be, or there is something funky going on with mime types.

What happens if you go to http://127.0.0.1:8337/aura/tracks/15/audio ?

the files are there, i can access them using other tools (eg VLC). i can also get them using the new beetle client.

doing your experiment was interesting: it nicely displays a n error in JSON :

{"errors":[{"status":"404 Not Found","title":"No audio file for the requested track.","detail":"There is no audio file for track 15 at the expected location"}]}

Hmm. Are you by any chance using an old version? I fixed something recently which could theoretically cause an error like you’re seeing.

The up to date aura plugin is now on the master branch of beets.

I should probably try to figure out how to display that error directly in the web client.

just downloaded the freshest version (Mar 7) but same behavior.

	could not get filesize: [Errno 2] No such file or directory: b"/media/pi/music4beets/music4beets/music-ornette/Cannonball Adderley/Somethin'' Else/01 Autumn Leaves.m4a"
	...

	127.0.0.1 - - [23/Mar/2021 16:37:03] "GET /aura/tracks/8426?include=artists%2Calbums HTTP/1.1" 200 -
	127.0.0.1 - - [23/Mar/2021 16:37:30] "GET /aura/tracks/8426/audio HTTP/1.1" 404 -

also: what takes so long on startup? there is a good 5+ minute wait while aura builds some index?

This is getting stranger and stranger.

There definitely shouldn’t be a five minute wait, it should be ready no more than a couple seconds after typing beet aura. Do you have any more info on what that looks like?

I’m assuming that the Autumn Leaves file does actually exist where it says it is?

Could you try copying this into a file in the beets source directory and running it with python <filename>.py

from os.path import isfile
from beets import config
from beets.ui import _open_library
from beets.util import py3_path
from beets.library import Item
lib = _open_library(config)
track = lib.get_item(15)
path3 = py3_path(track.path)
print("track.path:", track.path)
print("isfile:", isfile(track.path))
print("")
print("py3_path:", path3)
print("isfile:", isfile(path3))