Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Beta test and vote: Notification queue #119

Closed
svrooij opened this issue Jan 19, 2021 · 4 comments
Closed

Beta test and vote: Notification queue #119

svrooij opened this issue Jan 19, 2021 · 4 comments
Labels
Feature request help wanted Extra attention is needed

Comments

@svrooij
Copy link
Owner

svrooij commented Jan 19, 2021

Since 2.4.0-beta.2 we support a second implementation of notification queues #89

I added both so everybody can check them out. This issue described the differences and I'm curious to what version works best.

To just vote for an implementation, react with:

  • 🚀 for PlayNotification
  • 🎉 for PlayNotificationTwo by @theimo1221

But it's more helpful to reply and explain what you've found.

Pros and cons

The list of pros and cons might not be complete, please reply and I'll edit this issue

Pros and cons for PlayNotification

Pros:

  • Using calling thread to play notification
  • Added two public methods on SonosDevice GetState and RestoreState, to build your own app around getting and restoring state.
  • Better maintainable
  • Less complex
  • ...

Cons:

  • The second call to PlayNotification returns immediately instead of waiting for the notification to actually play
  • ...

Pros and cons for PlayNotificationTwo

Pros:

  • Returns only when the notification actually played
  • ...

Cons:

  • Less maintainable (more code, that is also more complex)
  • ...

Details

PlayNotification

This method uses the calling thread of the first PlayNotification call. The first call will return when all notifications have played. The calls while currently playing a notification will return when that notification is added to the array and will be played in the thread of the first call.

Each notification can still use the timeout (in seconds) to stop the playback if the event isn't received or if you're playing a stream.

This implementation uses about 120 lines of code and the complexity is moderate.

public async GetState(): Promise<SonosState> {
return {
transportState: this.CurrentTransportStateSimple ?? (await this.AVTransportService.GetTransportInfo()).CurrentTransportState as TransportState,
mediaInfo: await this.AVTransportService.GetMediaInfo(),
positionInfo: await this.AVTransportService.GetPositionInfo(),
volume: this.Volume ?? (await this.RenderingControlService.GetVolume({ InstanceID: 0, Channel: 'Master' })).CurrentVolume,
};
}
/**
* Restore to the state from before, used internally by the notification system.
*
* @param state The state of the speaker from 'GetState()'
* @param delayBetweenCommands Sonos speakers cannot process commands fast after each other. use 50ms - 800ms for best results
*/
public async RestoreState(state: SonosState, delayBetweenCommands: number | undefined = undefined): Promise<boolean> {
if (this.Volume !== state.volume) {
await this.SetVolume(state.volume);
if (delayBetweenCommands !== undefined) await AsyncHelper.Delay(delayBetweenCommands);
}
const isBroadcast = typeof state.mediaInfo.CurrentURIMetaData !== 'string' // Should not happen, is parsed in the service
&& state.mediaInfo.CurrentURIMetaData?.UpnpClass === 'object.item.audioItem.audioBroadcast'; // This UpnpClass should for sure be skipped.
await this.AVTransportService.SetAVTransportURI({ InstanceID: 0, CurrentURI: state.mediaInfo.CurrentURI, CurrentURIMetaData: state.mediaInfo.CurrentURIMetaData });
if (delayBetweenCommands !== undefined) await AsyncHelper.Delay(delayBetweenCommands);
if (state.positionInfo.Track > 1 && state.mediaInfo.NrTracks > 1) {
this.debug('Selecting track %d', state.positionInfo.Track);
await this.SeekTrack(state.positionInfo.Track)
.catch((err) => {
this.debug('Error selecting track, happens with some music services %o', err);
});
if (delayBetweenCommands !== undefined) await AsyncHelper.Delay(delayBetweenCommands);
}
if (state.positionInfo.RelTime && state.mediaInfo.MediaDuration !== '0:00:00' && !isBroadcast) {
this.debug('Setting back time to %s', state.positionInfo.RelTime);
await this.SeekPosition(state.positionInfo.RelTime)
.catch((err) => {
this.debug('Reverting back track time failed, happens for some music services (radio or stream). %o', err);
});
if (delayBetweenCommands !== undefined) await AsyncHelper.Delay(delayBetweenCommands);
}
if (state.transportState === TransportState.Playing || state.transportState === TransportState.Transitioning) {
await this.AVTransportService.Play({ InstanceID: 0, Speed: '1' });
}
return true;
}
// Internal notification queue
private notifications: PlayNotificationOptions[] = [];
private playingNotification?: boolean;
/**
* Play some url, and revert back to what was playing before. Very usefull for playing a notification or TTS sound.
*
* @param {PlayNotificationOptions} options The options
* @param {string} [options.trackUri] The uri of the sound to play as notification, can be every supported sonos uri.
* @param {string|Track} [options.metadata] The metadata of the track to play, will be guesses if undefined.
* @param {number} [options.delayMs] Delay in ms between commands, for better notification playback stability. Use 100 to 800 for best results
* @param {callback} [options.notificationFired] Specify a callback that is called when this notification has played.
* @param {boolean} [options.onlyWhenPlaying] Only play a notification if currently playing music. You don't have to check if the user is home ;)
* @param {number} [options.timeout] Number of seconds the notification should play, as a fallback if the event doesn't come through.
* @param {number} [options.volume] Change the volume for the notication and revert afterwards.
* @returns {Promise<true>} Returns when added to queue or (for the first) when all notifications have played.
* @remarks The first notification will return when all notifications have played, notifications send in between will return when added to the queue.
* Use 'notificationFired' in the request if you want to know when your specific notification has played.
* @memberof SonosDevice
*/
public async PlayNotification(options: PlayNotificationOptions): Promise<boolean> {
this.debug('PlayNotification(%o)', options);
if (options.delayMs !== undefined && (options.delayMs < 1 || options.delayMs > 4000)) {
throw new Error('Delay (if specified) should be between 1 and 4000');
}
if (options.volume !== undefined && (options.volume < 1 || options.volume > 100)) {
throw new Error('Volume needs to be between 1 and 100');
}
const playingNotification = this.playingNotification === true;
this.playingNotification = true;
// Generate metadata if needed
if (options.metadata === undefined) {
const metaOptions = options;
const guessedMetaData = MetadataHelper.GuessMetaDataAndTrackUri(options.trackUri);
metaOptions.metadata = guessedMetaData.metadata;
metaOptions.trackUri = guessedMetaData.trackUri;
this.notifications.push(metaOptions);
} else {
this.notifications.push(options);
}
if (playingNotification) {
this.debug('Notification added to queue');
return false;
}
const state = await this.GetState();
this.debug('Current transport state is %s', state.transportState);
// Play all notifications (if calls itself if notifications where added in between)
const shouldRevert = await this.PlayNextNotification(state.transportState);
if (shouldRevert) {
// Revert everything back
this.debug('Reverting everything back to normal');
await this.RestoreState(state, options.delayMs);
}
this.playingNotification = undefined;
return shouldRevert;
}

private async PlayNextNotification(originalState: TransportState, havePlayed?: boolean): Promise<boolean> {
let result = havePlayed === true;
if (this.notifications.length === 0) {
return Promise.resolve(result);
}
// Start the notification
const notification = this.notifications[0];
if (notification.onlyWhenPlaying === true && !(originalState === TransportState.Playing || originalState === TransportState.Transitioning)) {
this.debug('Skip notification, because of not playing %s', notification.trackUri);
if (notification.notificationFired !== undefined) {
notification.notificationFired(false);
}
} else {
result = true;
this.debug('Start notification playback uri %s', notification.trackUri);
await this.AVTransportService.SetAVTransportURI({ InstanceID: 0, CurrentURI: notification.trackUri, CurrentURIMetaData: notification.metadata ?? '' });
if (notification.volume !== undefined && notification.volume !== this.volume) {
await this.SetVolume(notification.volume);
if (notification.delayMs !== undefined) await AsyncHelper.Delay(notification.delayMs);
}
await this.AVTransportService.Play({ InstanceID: 0, Speed: '1' }).catch((err) => { this.debug('Play threw error, wrong url? %o', err); });
// Wait for event (or timeout)
await AsyncHelper.AsyncEvent<any>(this.Events, SonosEvents.PlaybackStopped, notification.timeout).catch((err) => this.debug(err));
if (notification.notificationFired !== undefined) {
notification.notificationFired(true);
}
}
// Remove first item from queue.
this.notifications.shift();
return this.PlayNextNotification(originalState, result);
}

PlayNotificationTwo

This implementation uses several timers to disconnect the thread, but each call to PlayNotificationTwo returns when that specific notification has played.

This implementation uses about 250 lines of code and the complexity is high.

public async PlayNotificationTwo(options: PlayNotificationOptions): Promise<boolean> {
const resolveAfterRevert = options.resolveAfterRevert === undefined ? true : options.resolveAfterRevert;
this.debug('PlayNotificationTwo(%o)', options);
if (options.delayMs !== undefined && (options.delayMs < 1 || options.delayMs > 4000)) {
throw new Error('Delay (if specified) should be between 1 and 4000');
}
const promise = new Promise<boolean>((resolve, reject) => {
this.addToNotificationQueue(options, resolve, reject, resolveAfterRevert);
});
return promise;
}

private async playQueue(originalState: TransportState): Promise<boolean> {
this.debug('playQueue: Called, current Queue length: %d', this.notificationQueue.queue.length);
if (this.notificationQueue.queue.length === 0) {
throw new Error('Queue is already empty');
}
const currentItem = this.notificationQueue.queue[0];
const currentName = currentItem.options.trackUri;
if (currentItem.generalTimeout !== undefined && currentItem.generalTimeout.timeLeft() < 0) {
this.debug('General timeout for Notification ("%s") fired already current Timestamp: %o, FireTime: %o', currentName, (new Date()).getTime(), currentItem.generalTimeout.fireTime);
// The Timeout already fired so play next item
return await this.playNextQueueItem(originalState);
}
const currentOptions = currentItem.options;
if (currentOptions.onlyWhenPlaying === true && !(originalState === TransportState.Playing || originalState === TransportState.Transitioning)) {
this.debug('playQueue: Notification ("%s") cancelled, player not playing', currentName);
await this.resolvePlayingQueueItem(currentItem, false);
return await this.playNextQueueItem(originalState);
}
if (currentItem.options.specificTimeout) {
const fireTime = (new Date()).getTime() + currentItem.options.specificTimeout * 1000;
this.debug('Play notification ("%s") timeout will fire at %o', currentName, fireTime);
const timeout = setTimeout(() => {
if (currentItem.generalTimeout) {
clearTimeout(currentItem.generalTimeout.timeout);
}
this.debug('Specific timeout for Notification ("%s") fired already current Timestamp: %o, FireTime: %o', currentName, (new Date()).getTime(), currentItem.individualTimeout?.fireTime);
this.resolvePlayingQueueItem(currentItem, false);
}, currentItem.options.specificTimeout * 1000);
currentItem.individualTimeout = new NotificationQueueTimeoutItem(timeout, fireTime);
}
this.debug('playQueue: Going to play next notification ("")', currentName);
// this.jestDebug.push(`${(new Date()).getTime()}: playQueue: Set next Transport URL, Queue Length: ${this.notificationQueue.queue.length}`);
// Generate metadata if needed
if (currentOptions.metadata === undefined) {
const guessedMetaData = MetadataHelper.GuessMetaDataAndTrackUri(currentOptions.trackUri);
currentOptions.metadata = guessedMetaData.metadata;
currentOptions.trackUri = guessedMetaData.trackUri;
}
this.notificationQueue.anythingPlayed = true;
// Start the notification
await this.AVTransportService.SetAVTransportURI({ InstanceID: 0, CurrentURI: currentOptions.trackUri, CurrentURIMetaData: currentOptions.metadata ?? '' });
if (currentOptions.volume !== undefined) {
this.notificationQueue.volumeChanged = true;
this.debug('playQueue: Changing Volume to %o', currentOptions.volume);
await this.RenderingControlService.SetVolume({ InstanceID: 0, Channel: 'Master', DesiredVolume: currentOptions.volume });
if (currentOptions.delayMs !== undefined) await AsyncHelper.Delay(currentOptions.delayMs);
}
if (currentItem.individualTimeout !== undefined && currentItem.individualTimeout.timeLeft() < 0) {
this.debug('Specific timeout for Notification ("") fired already current Timestamp: %o, FireTime: %o', currentName, (new Date()).getTime(), currentItem.individualTimeout?.fireTime);
return await this.playNextQueueItem(originalState);
}
if (currentItem.generalTimeout !== undefined && currentItem.generalTimeout.timeLeft() < 0) {
// The Timeout already fired so play next item
if (currentItem.individualTimeout) {
clearTimeout(currentItem.individualTimeout.timeout);
}
this.debug('General timeout for Notification ("%s") fired already current Timestamp: %o, FireTime: %o', currentName, (new Date()).getTime(), currentItem.individualTimeout?.fireTime);
return await this.playNextQueueItem(originalState);
}
this.debug('playQueue: Initiating notification playing for current Queue Item ("%s").', currentName);
// this.jestDebug.push(`${(new Date()).getTime()}: playQueue: Execute Play, Queue Length: ${this.notificationQueue.queue.length}`);
await this.AVTransportService.Play({ InstanceID: 0, Speed: '1' })
.catch((err) => { this.debug('Play threw error, wrong url? %o', err); });
// Wait for event (or timeout)
// this.jestDebug.push(`${(new Date()).getTime()}: playQueue: Wait for PlaybackStopped Event, Queue Length: ${this.notificationQueue.queue.length}`);
let remainingTime: number = currentItem.options.defaultTimeout === undefined
? 1800
: currentItem.options.defaultTimeout; // 30 Minutes Default Timeout
if (currentItem.generalTimeout) {
remainingTime = Math.max(remainingTime, currentItem.generalTimeout.timeLeft() / 1000);
}
if (currentItem.individualTimeout) {
remainingTime = Math.min(remainingTime, currentItem.individualTimeout.timeLeft() / 1000);
}
this.debug('playQueue: Notification("%s") --> Maximum wait time for PlayBackStopped Event %d s.', currentName, remainingTime);
// Timeout + 1 to ensure the timeout action fired already
await AsyncHelper.AsyncEvent<any>(this.Events, SonosEvents.PlaybackStopped, remainingTime + 5).catch((err) => this.debug(err));
this.debug('Recieved Playback Stop Event or Timeout for current PlayNotification("%s")', currentName);
if (currentItem.individualTimeout === undefined) {
if (currentOptions.delayMs !== undefined) await AsyncHelper.Delay(currentOptions.delayMs);
this.debug('Playing notification("%s") finished sucessfully', currentName);
await this.resolvePlayingQueueItem(currentItem, true);
return await this.playNextQueueItem(originalState);
}
const timeLeft = currentItem.individualTimeout.timeLeft();
if (timeLeft > 0) {
clearTimeout(currentItem.individualTimeout.timeout);
await this.resolvePlayingQueueItem(currentItem, true);
}
if (currentOptions.delayMs !== undefined) await AsyncHelper.Delay(currentOptions.delayMs);
this.debug('Playing notification("%s") finished with %d ms left on specific timeout', currentName, timeLeft);
return await this.playNextQueueItem(originalState);
}
private async resolvePlayingQueueItem(currentItem: NotificationQueueItem, resolveValue: boolean) {
if (currentItem.resolveAfterRevert === false) {
if (currentItem.generalTimeout !== undefined && currentItem.generalTimeout.timeLeft() > 0) {
clearTimeout(currentItem.generalTimeout.timeout);
}
currentItem.resolve(resolveValue);
} else {
this.notificationQueue.promisesToResolve.push(
{ promise: currentItem.resolve, value: resolveValue, timeout: currentItem.generalTimeout },
);
}
return true;
}
private async playNextQueueItem(originalState: TransportState) {
this.notificationQueue.queue.shift();
if (this.notificationQueue.queue.length > 0) {
this.debug('There are some items left in the queue --> play them');
return await this.playQueue(originalState);
}
this.debug('There are no items left in the queue --> Resolve Play Queue promise');
return true;
}
private addToNotificationQueue(
options: PlayNotificationOptions,
resolve: (resolve: boolean | PromiseLike<boolean>) => void,
reject: (reject: boolean | PromiseLike<boolean>) => void,
resolveAfterRevert: boolean,
): void {
const queueItem: NotificationQueueItem = {
options,
resolve,
reject,
resolveAfterRevert,
};
if (options.timeout) {
const fireTime = (new Date()).getTime() + options.timeout * 1000;
this.debug('Play notification timeout will fire at %d', fireTime);
const timeout = setTimeout(() => {
this.debug('Notification timeout fired --> resolve(false)');
// this.jestDebug.push(`Notification timeout fired (Firetime: ${fireTime})`);
resolve(false);
}, options.timeout * 1000);
queueItem.generalTimeout = new NotificationQueueTimeoutItem(timeout, fireTime);
}
this.notificationQueue.queue.push(queueItem);
if (!this.notificationQueue.playing) {
this.notificationQueue.playing = true;
setTimeout(() => {
this.startQueue(options);
});
}
}
private async startQueue(options: PlayNotificationOptions): Promise<boolean> {
const originalState = (await this.AVTransportService.GetTransportInfo()).CurrentTransportState as TransportState;
this.debug('Current state is %s', originalState);
if (!(originalState === TransportState.Playing || originalState === TransportState.Transitioning)) {
// TH 01.01.2021 Check if we only got items in queue which should only play, currently playing
let onlyItemsWithOnlyWhenPlaying = true;
this.notificationQueue.queue.forEach((element) => {
if (!onlyItemsWithOnlyWhenPlaying || element.options.onlyWhenPlaying === true) {
return;
}
onlyItemsWithOnlyWhenPlaying = false;
});
if (onlyItemsWithOnlyWhenPlaying) {
/*
* TH 01.01.2021: We have only items in the queue which are only to be played if any items in queue
* --> directly resolve and exit
*/
this.notificationQueue.queue.forEach((element) => {
element.resolve(false);
});
this.notificationQueue.queue = [];
this.notificationQueue.playing = false;
return false;
}
}
// Original data to revert to
const originalVolume = (await this.RenderingControlService.GetVolume({ InstanceID: 0, Channel: 'Master' })).CurrentVolume;
const originalMediaInfo = await this.AVTransportService.GetMediaInfo();
const originalPositionInfo = await this.AVTransportService.GetPositionInfo();
this.debug('Starting Notification Queue');
// this.jestDebug.push(`${(new Date()).getTime()}: Start Queue playing`);
await this.playQueue(originalState);
this.debug('Notification Queue finished');
if (this.notificationQueue.anythingPlayed) {
// Revert everything back
this.debug('Reverting everything back to normal');
let isBroadcast = false;
if (
// TODO: Analyze under which circumstances CurrentURIMetaData is undefined
originalMediaInfo.CurrentURIMetaData !== undefined
&& typeof originalMediaInfo.CurrentURIMetaData !== 'string' // Should not happen, is parsed in the service
&& originalMediaInfo.CurrentURIMetaData.UpnpClass === 'object.item.audioItem.audioBroadcast' // This UpnpClass should for sure be skipped.
) {
isBroadcast = true;
}
if (originalVolume !== undefined && this.notificationQueue.volumeChanged === true) {
this.debug('This Queue changed the volume so revert it');
await this.RenderingControlService.SetVolume({ InstanceID: 0, Channel: 'Master', DesiredVolume: originalVolume });
if (options.delayMs !== undefined) await AsyncHelper.Delay(options.delayMs);
this.notificationQueue.volumeChanged = false;
}
await this.AVTransportService.SetAVTransportURI({ InstanceID: 0, CurrentURI: originalMediaInfo.CurrentURI, CurrentURIMetaData: originalMediaInfo.CurrentURIMetaData });
if (options.delayMs !== undefined) await AsyncHelper.Delay(options.delayMs);
if (originalPositionInfo.Track > 1 && originalMediaInfo.NrTracks > 1) {
this.debug('Selecting track %d', originalPositionInfo.Track);
await this.SeekTrack(originalPositionInfo.Track)
.catch((err) => {
this.debug('Error selecting track, happens with some music services %o', err);
});
}
if (originalPositionInfo.RelTime && originalMediaInfo.MediaDuration !== '0:00:00' && !isBroadcast) {
this.debug('Setting back time to %s', originalPositionInfo.RelTime);
await this.SeekPosition(originalPositionInfo.RelTime)
.catch((err) => {
this.debug('Reverting back track time failed, happens for some music services (radio or stream). %o', err);
});
}
if (originalState === TransportState.Playing || originalState === TransportState.Transitioning) {
await this.AVTransportService.Play({ InstanceID: 0, Speed: '1' });
}
}
// this.jestDebug.push(`${(new Date()).getTime()}: Resolve all remaining promises`);
this.notificationQueue.promisesToResolve.forEach((element) => {
if (element.timeout === undefined) {
element.promise(element.value);
return;
}
if (element.timeout.timeLeft() > 0) {
clearTimeout(element.timeout.timeout);
element.promise(element.value);
}
});
this.notificationQueue.anythingPlayed = false;
this.notificationQueue.promisesToResolve = [];
if (this.notificationQueue.queue.length > 0) {
setTimeout(() => {
this.startQueue(options);
});
} else {
this.notificationQueue.playing = false;
}
return true;
}
// #endregion

import { PlayNotificationOptions } from './requests';
export interface NotificationQueueItem {
/**
* Desired PlayNotificationOptions
*
* @type {number}
*/
options: PlayNotificationOptions;
/**
* The Resolve Promise we have to resolve once finished successfully
*
* @type {(reject: boolean | PromiseLike<boolean>) => void}
*/
resolve: (resolve: boolean | PromiseLike<boolean>) => void;
/**
* The Reject Promise we have to resolve once finished with failure
*
* @type {(reject: boolean | PromiseLike<boolean>) => void}
*/
reject: (reject: boolean | PromiseLike<boolean>) => void;
/**
* Whether we should only resolve the promise when we reverted correctly
*
* @type {boolean}
*/
resolveAfterRevert: boolean;
/**
* Object with details regarding the timeout for this queue item
*
* @type {NotificationQueueTimeoutItem}
*/
generalTimeout?: NotificationQueueTimeoutItem;
/**
* Object with details regarding the timeout for this specific queue item (Starting when this is Queue Item becomes first)
*
* @type {NotificationQueueTimeoutItem}
*/
individualTimeout?: NotificationQueueTimeoutItem;
}
export class NotificationQueueTimeoutItem {
public constructor(
/**
* The timeout reference to clear if anything is okay
*
* @type {NotificationQueueTimeoutItem}
*/
public timeout: NodeJS.Timeout,
/**
* The timestamp when the timeout will fire
*
* @type {NotificationQueueTimeoutItem}
*/
public fireTime: number,
) { }
public timeLeft(): number {
return this.fireTime - (new Date()).getTime();
}
}
export class NotificationQueue {
public queue: NotificationQueueItem[] = [];
public promisesToResolve: Array<{
promise: (resolve: boolean | PromiseLike<boolean>) => void,
value: boolean,
timeout?: NotificationQueueTimeoutItem,
}> = [];
public playing = false;
/**
* Whether any item in the Queue changed the volume
*
* @type {boolean}
*/
public volumeChanged = false;
/**
* Whether the Queue played any item at all
*
* @type {boolean}
*/
public anythingPlayed = false;
}

@svrooij svrooij added help wanted Extra attention is needed Feature request labels Jan 19, 2021
@svrooij svrooij pinned this issue Jan 19, 2021
@svrooij svrooij changed the title Experimental feature: Notification queue Beta test and vote: Notification queue Jan 19, 2021
@hklages
Copy link
Contributor

hklages commented Jan 28, 2021

I have already a solution in place (currently based on node-sonos):

  • group.create.snap creates as snapshot of the group (all player: volumes, mutestate; coordinator: playbackstate, avTransport, track, etc, and also the list of members)
  • group.play.snap, reverting everything to the snapshot
  • group.save.queue to save the current SONOS queue (which can be restored with mysonos.queue.item )

and I leave the queuing of notifications in the responsibility of the user. In Node-RED you can do that with a simple "Queue" node in front of a player. So init, refresh, reset, and the queuing itself of notification is in his responsibility :-)

Anyway: The issue with "notification queuing" is that usually there are several people in a household and they (can) use different apps to send commands to the SONOS player. So the system can be easily messed up.

Bottom line: PlayNotification would be my preferred solution as I might reuse getState/restoreStore methods and replace my group.create/play.snap.

@svrooij
Copy link
Owner Author

svrooij commented May 18, 2021

Closing stale issue, lets keep the new notification way in the code for now.

@svrooij svrooij closed this as completed May 18, 2021
@svrooij svrooij unpinned this issue May 18, 2021
@theimo1221
Copy link
Contributor

@svrooij just as a short update from my side:

I retried using your solution and can't run it in my enviroment due to the following:

  1. System starts playing a weather report with TTS split in several pieces (to allow using cached versions for already spoken messages, e.g. "It won't rain today")
  2. System doesn't recieve tts end, thus not starting the item in 2nd position
  3. System will start speaking 2nd messages after the default 30 minutes

I can't set timeout because, I would need to set it considering the other items in queue.
So in my case I'm kinda dependent on using the option specificTimeout from my variant.

Shall I take a look to implement the specificTimeout in your variant as well?

@svrooij
Copy link
Owner Author

svrooij commented Jul 4, 2021

I guess the PlayTtsTwo doesn't work correctly because I rushed it. This should be modified to use the send notification method.

public async PlayTTSTwo(options: PlayTtsOptions): Promise<boolean> {
    this.debug('PlayTTSTwo(%o)', options);

    const notificationOptions = await TtsHelper.TtsOptionsToNotification(options);

    return await this.PlayNotification(notificationOptions);
  }

I guess if you fix this method, the regular notification method wouldn't need any changes.

@svrooij svrooij reopened this Jul 5, 2021
@svrooij svrooij pinned this issue Jul 5, 2021
svrooij added a commit that referenced this issue Jul 5, 2021
@svrooij svrooij closed this as completed Oct 3, 2021
@svrooij svrooij mentioned this issue Jan 8, 2022
5 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Feature request help wanted Extra attention is needed
Projects
None yet
Development

No branches or pull requests

3 participants