[APP][Pro] Sonos (LocalAPI)

Agree, though if changed, then please consider showing nothing, not a ‘nothing is playing’ text. I have various flows that depend on the contents of the Track tag, which I would have to change :blush:. Leaving tags empty if there is not data would keep it consistent.

What to do if multiple messages need to be played back-to-back?

For me, the first message stops and the second message starts playing. I have already tried using all sorts of variables, conditions, and timers to ensure that multiple messages are played consecutively, but that was too much hassle; it caused more disadvantages than advantages.

Sonos Beam is playing, Cloud says Beam is playing. Local says Beam isn’t playing, but Move 2 isn’t playing and Cloud says it is playing.. While Local says Move 2 isn’t playing..

But at the same time the Beam Local did switch track when i did…

Were those speakers perhaps powered down/switched off before?

If so, then restarting the Sonos app might help. I have a start-up flow that powers on my Sonos speakers, waits while they boot and then restarts the Sonos app.

Somehow both the Cloud and Local API app need some ‘help’ to re-initialize the playing state of a speaker after it was powered down.

I mean… my WiFi went off for a little.. So that might be the reason. I have Deco connected to Homey, so could probably make the apps restart when Deco comes back online.

If by messages, you mean Text-To-Speech, I use the Google Services app. It knows how long the speech file is.

I then use Advanced Triggers to trigger the Google speech, which will play all speech in sequence (it has debounce/sequence flowcards). Even if you add other sounds in it with f.i. the Universal Media Player app.

Yes, Text-to-Speech messages.
I had already created a script to determine the playback duration of an Text-to-Speech message MP3, but it remains complex. I link a base64 notification sound prior to the Text-to-Speech notification.

Could you share the Advanced Triggers settings and any other logic?

// Script: calculate_mp3_duration
// Argument: The full URL to your mp3 file (e.g., http://192.168.x.x:5080/file.mp3)

// Store the argument in a local variable
let url = args[0];

// Check if a URL was provided
if (!url) {
    throw new Error("No URL provided");
}

try {
    // Perform a 'HEAD' request. This only retrieves the metadata, not the entire file (fast execution)
    let response = await fetch(url, { method: 'HEAD' });
    
    // Check if the network response is successful
    if (!response.ok) {
        throw new Error(`Error fetching file: ${response.statusText}`);
    }

    // Retrieve the file size in bytes from the headers
    let bytes = response.headers.get('content-length');
    
    // Check if the file size header is present
    if (!bytes) {
        throw new Error("Could not determine file size.");
    }

    // Formula: Bytes / 12000 (since 96kbps / 8 bits = 12000 bytes/sec)
    // Round to 1 decimal place
    const bitrateBytesPerSec = 12000; 
    let durationSeconds = (parseInt(bytes) / bitrateBytesPerSec);
    
    // Format the final result as a number with one decimal place
    const result = Number(durationSeconds.toFixed(1));

    // Return the calculated duration in seconds
    return result;

} catch (err) {
    // Log the error and rethrow it
    console.error(err);
  
    throw err;
}

For me, this is the best possible result to prevent multiple TTS messages from being sent to Sonos, where the last message might cut off earlier playing messages.


Since news headlines are also spoken aloud, I have also taken longer texts into account.

Can this primitive solution be improved?
Most difficult to solve:

  • wait time > 30 seconds (time outs in scripts)
  • variable wait times
  • no Chronograph app to use, because the Advanced Trigger flow then thinks it has finished and starts the next text in the TTS queue.

Well, the last part, after the HomeyScript, for the wait, you can replace with a wait function within the homeyscript i guess:

await wait(2000);

This being an await of 2000 milliseconds.

Not sure how long you can await for it, before the flowcard will timeout.

Perhaps use a second HS card with the above promise, and connect the error from itself, to the card itself (if the error was timeout)? Then it would just keep looping i guess till the delay has past.

One sec, working on example.

EDIT:

Just to give you an idea. Its still a horrible way, but i think its the most dynamic you can get in flowcards atm. and since its just a repeat of the exact same flowcard, i find it somewhat more easier for the eyes.

The 80000 is 80 seconds.
Each flowcard will ‘error’ after 60 seconds. So just repeat this as many times as you would need minutes.

In this example, the first HS wait card times-out, but the second one completes after about 20sec.

Oh, and you first row, when you trigger the Advanced Trigger for speech, set the await to no instead of yes. Else this card will timeout because it will take longer than 60sec. And there is no need to await for that card, since the Advanced Trigger will wait for itself since you put it on sequential :wink:

I can’t get the app to say anything. What am I doing wrong?

For this LocalAPI app you need a Azure TTS speech key. Use the official Sonos Cloud app by Athom for text to speech if you don’t want to pay. If you do have an Azure TTS speech key go in the Sonos LocalAPI app settings and enter it.

Mmmmm, just edited ALL my flows. Going back then. Anoying…

Yeah..

Ah, that is a pity.
Have the same problem here.

Is there no other way to get Sonos to say something via this App, @xiutit?

Would have to revert to the Athom app as well then, paying for only this for Azure speech is a bit overdone… :frowning:

Personally, i just use a HomeyScript running Google translate speech URLs from Advanced flows, i start the HomeyScript with selected language and text. Then a Play URL with volume card from the Sonos LocalAPI plays the TTS.

I’m working on a change for a next version where Azure TTS will work “out of the box”, without needing to configure your own Azure speech key. I’m going to sponsor the Azure costs myself/business-wise, with some kind of rate limiting in place to prevent abuse or accidental excessive usage.

I do need to monitor how much it is actually used once released, to make sure the costs stay reasonable.

Just a recommendation: maybe you can also add a “cache” of mp3’s (within the userdata folder or on a VPS/server), this prevents users who frequently use the same text from sending unnecessary API requests

Can you give an example of how to do this? Or point us to an example?

You could have installed and run both Sonos apps at the same time and tried out the Sonos (LocalAPI) app first… :man_shrugging:t3: