[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: