jishi / node-sonos-http-api

An HTTP API bridge for Sonos easing automation. Hostable on any node.js capable device, like a raspberry pi or similar.
http://jishi.github.io/node-sonos-http-api/
MIT License
1.84k stars 463 forks source link

cannot play long CLIP/MP3 files from javascript #726

Open blumfisch opened 4 years ago

blumfisch commented 4 years ago

I try to play MP3-files from my 2019 latest Updates RasPi-Sonos-to-HTTP-Server. The commands come from a local javascript webpage running on Chrome (it's for an escape room, events happening there, but also music and atmoshere playing).

I use:

<script> const openasync = async (website) => { const response = await fetch(website); } </script> to send the http-request.

openasync('http://192.168.178.101:5005/sonos1/clip/sail.mp3'); let's my Sonos play the song. And this works.

Also with other calls like HTTP.XHR.request or new image=(website);or open.window(website); it works, though the latter has limitations.

If I call another clip/song.mp3 while playing ... this works too. 1st song stops and 2nd plays immediately. But if I call a third while still playing everything breaks down. Nothing works anymore and I have to reset my webpage. Sonos starts playing my Clips repeatedly or in wild order. Chrome seems to wait for somthing. And sometimes I get errors like: net::ERR_EMPTY_RESPONSE

What am I missing?

I just would like it to send a command with javascript to the Sonos so it immediately stops doing what it just did and starts playing the new clip.mp3-File or pauses. Files are all there. I can play them one after another. But as soon as the interfere something seems to break?

Please help?

blumfisch commented 4 years ago

I forgot: My RasPi 3 runs Node.js v10.16.3 and NPM 6.10.3

and here is my test-webpage that tests different ways of calling Sonos-http: The only one that works is "openpage", but it opens a new tab and steals focus, so it's not usable to me.

<html>
<button onclick="opendirect('http://192.168.178.101:5005/bib1/clip/bibarchivatmo.mp3/20')">VOID(OPEN DIRECT(ATMO))</button>

<button onclick="opendirect('http://192.168.178.101:5005/bib1/clip/bibshowdown.mp3/20')">VOID(OPEN DIRECT(SD))</button>

<button onclick="openpage('http://192.168.178.101:5005/bib1/clip/bibarchivatmo.mp3/20')">VOID(OPEN PAGE(ATMO))</button>

<button onclick="openpage('http://192.168.178.101:5005/bib1/clip/bibshowdown.mp3/20')">VOID(OPEN PAGE(SD))</button>

<button onclick="openasync('http://192.168.178.101:5005/bib1/clip/bibarchivatmo.mp3/20')">ASYNC FETCH(ATMO)</button>

<button onclick="openasync('http://192.168.178.101:5005/bib1/clip/bibshowdown.mp3/20')">ASYNC FETCH(SD)</button>

<button onclick="makeCorsRequest('http://192.168.178.101:5005/bib1/clip/bibarchivatmo.mp3/20')">CORS(ATMO)</button>

<button onclick="makeCorsRequest('http://192.168.178.101:5005/bib1/clip/bibshowdown.mp3/20')">CORS(SD)</button>

<button onclick="newimage('http://192.168.178.101:5005/bib1/clip/bibarchivatmo.mp3/20')">NEW IMAGE(ATMO)</button>

<button onclick="newimage('http://192.168.178.101:5005/bib1/clip/bibshowdown.mp3/20')">NEW IMAGE(SD)
</button>

<script>
    function opendirect(website) {
        void(open(website,"_self"));
    }
</script>

<script> 
    function openpage(website) {
        void(open(website,"_blank"));
    }
</script>

<script>
    const openasync = async (website) => {
      const response = await fetch(website);
      const myJson = await response.text(); //extract JSON from the http response
      //var jsonText = JSON.stringify(xmlToJson(xmlDoc));
      // do something with myJson
      //console.log(myJson);
    }
</script>

<script> 
    // reagiert nicht auf weitere Eingaben
    function newimage(website) {
        new Image().src = website;
    }
</script>

<script>
    // Create the XHR object.
    function createCORSRequest(method, url) {
      var xhr = new XMLHttpRequest();
      if ("withCredentials" in xhr) {
        // XHR for Chrome/Firefox/Opera/Safari.
        xhr.open(method, url, true);
      } else if (typeof XDomainRequest != "undefined") {
        // XDomainRequest for IE.
        xhr = new XDomainRequest();
        xhr.open(method, url);
      } else {
        // CORS not supported.
        xhr = null;
      }
      return xhr;
    }

    // Helper method to parse the title tag from the response.
    function getTitle(text) {
      return text.match('<title>(.*)?</title>')[1];
    }

    // Make the actual CORS request.
    function makeCorsRequest(url) {
      var xhr = createCORSRequest('GET', url);
      if (!xhr) {
        alert('CORS not supported');
        return;
      }

      // Response handlers.
      xhr.onload = function() {
        var text = xhr.responseText;
        var title = getTitle(text);
        alert('Response from CORS request to ' + url + ': ' + title);
      };

      xhr.onerror = function() {
        //alert('Woops, there was an error making the request.');
      };

      xhr.send();
    }
    </script>
</html>
blumfisch commented 4 years ago

Still doesnt work.

Here is what I found out so far:

The HTTP requests (doesnt matter which one I use, see above) seem to wait for a response from the server (from Sonos-HTTP-API?). Which comes only after a while for long songs, e.g. "{success:success}". If you change songs/commands, while they are still running, there will be new unfullfilled requests, which stack up - and at some time will crash ... I dont know: Chrome? HTTP-Sonos-API? The RasPi it's running on? Or the Sonos speaker itself? I still couldnt figure out. It also happens when I make HTTP requests from my external homematic/CCU3 via CuxD if anybody knows this. Same erratic behaviour.

The way I do it now is: I make HTTPRequests via NodeRed, which runs on the same Raspi Sonos-Http-Api is running on. NodeRed will also throw error messages: "no response from server". But it seems to handle them better, meaning: It will not crash after the 3rd song.

Could someone tell me how to make requests that dont wait for a response from the server? I really don't need it. I just want to issue the HTTP command to HTTP-Sonos-api to play a certain MP3 sound via clip.

Or is this a bug? Could the response come earlier, e.g. after the command is issued and the clip starts playing? Not only after the song has finished playing?

Thanks if anyone is still with me.

jishi commented 4 years ago

The decision to wait with the response was a deliberate change to allow "chaining" of requests, but this introduces a significant delay for long clips of course. You need to have significant timeout settings when invoking the requests.

It won't be possible change the current default behavior, but maybe it would be possible to add additional options to "queue" clips and say commands and return early. I'll have to think about it. There are a few other issues with clips and say (if you mix them) that needs to be resolved as well.

blumfisch commented 4 years ago

Thanks for the reply.

It would be really awesome if there was another command like CLIP that results in an immediate reply like {success: start playing} and doesnt wait until the song has finished..

I will try if I can get down to timeout settings, but I'm not a programmer and my first google searches were not very promising ("fetch" seems to have no way of setting the timeout. A freind of mine suggestest "axios", but I have to find time and courage to dig into that).

Unfortunately this renders my Sonos speakers and the http-API unusable for anything longer than 2 minute-files (seems to be the timeout setting for node red and chrome).

It's a pity, because all worked so well in the beginning, http-API was set up easily, and I was really looking forward to use Sonos speakers as a all-in-one easy solution for so many speacial effects in my rooms.

Please let me know if you change anything or if anyone knows of another solution. That would be really great.

But thanks for the API anyway. It's great and for "normal" uses it's probably really awesome.