Skip to content

Commit

Permalink
[audio] Enhance AudioSink capabilities using the AudioServlet (openha…
Browse files Browse the repository at this point in the history
…b#3461)

* [audio] More capabilities for AudioSink using the AudioServlet

AudioServlet can now serve all type of AudioStream multiple times by buffering data in memory or in temporary file.
Adding method to ease disposal of temporary file after playing a sound
Adding an identifyier to audio stream for further development (allow audio sink to cache computation data)

We can now send audio with a Runnable for a delayed task to be executed after. This delayed task includes temporary file deletion and volume restoration.
This is a no breaking change / no behaviour modification for other addon AudioSink, as existing AudioSink must explicitly override the old behaviour to use this capability.
Add AudioSinkSync / AudioSinkAsync abstract classes to use this capability easily.
WebAudioSink now implements this capability, with the help of a modified AudioServlet

Adding (approximative, better than nothing) sound duration computation method for MP3 and WAV.
Use this sound duration computation to guess when the async sound is finished and when to do the post process (i.e. volume restoration)

Signed-off-by: Gwendal Roulleau <[email protected]>
GitOrigin-RevId: 8eddad5
  • Loading branch information
dalgwen authored and splatch committed Jul 12, 2023
1 parent 5317da1 commit 21eead5
Show file tree
Hide file tree
Showing 25 changed files with 888 additions and 208 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@

import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.core.common.Disposable;
import org.openhab.core.storage.Storage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand Down Expand Up @@ -233,6 +234,9 @@ protected void closeStreamClient() throws IOException {
if (inputStreamLocal != null) {
inputStreamLocal.close();
}
if (inputStreamLocal instanceof Disposable disposableStream) {
disposableStream.dispose();
}
}
}
} finally {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,6 @@
import org.openhab.core.audio.AudioStream;
import org.openhab.core.audio.FileAudioStream;
import org.openhab.core.audio.URLAudioStream;
import org.openhab.core.audio.UnsupportedAudioFormatException;
import org.openhab.core.audio.UnsupportedAudioStreamException;
import org.openhab.core.audio.utils.ToneSynthesizer;
import org.openhab.core.config.core.ConfigOptionProvider;
import org.openhab.core.config.core.ConfigurableService;
Expand Down Expand Up @@ -122,39 +120,11 @@ public void play(@Nullable AudioStream audioStream, @Nullable String sinkId) {
public void play(@Nullable AudioStream audioStream, @Nullable String sinkId, @Nullable PercentType volume) {
AudioSink sink = getSink(sinkId);
if (sink != null) {
PercentType oldVolume = null;
// set notification sound volume
if (volume != null) {
try {
// get current volume
oldVolume = sink.getVolume();
} catch (IOException e) {
logger.debug("An exception occurred while getting the volume of sink '{}' : {}", sink.getId(),
e.getMessage(), e);
}

try {
sink.setVolume(volume);
} catch (IOException e) {
logger.debug("An exception occurred while setting the volume of sink '{}' : {}", sink.getId(),
e.getMessage(), e);
}
}
try {
sink.process(audioStream);
} catch (UnsupportedAudioFormatException | UnsupportedAudioStreamException e) {
logger.warn("Error playing '{}': {}", audioStream, e.getMessage(), e);
} finally {
if (volume != null && oldVolume != null) {
// restore volume only if it was set before
try {
sink.setVolume(oldVolume);
} catch (IOException e) {
logger.debug("An exception occurred while setting the volume of sink '{}' : {}", sink.getId(),
e.getMessage(), e);
}
}
}
Runnable restoreVolume = handleVolumeCommand(volume, sink);
sink.processAndComplete(audioStream).exceptionally((exception) -> {
logger.warn("Error playing '{}': {}", audioStream, exception.getMessage(), exception);
return null;
}).thenRun(restoreVolume);
} else {
logger.warn("Failed playing audio stream '{}' as no audio sink was found.", audioStream);
}
Expand Down Expand Up @@ -351,6 +321,53 @@ public Set<String> getSinkIds(String pattern) {
return null;
}

@Override
public Runnable handleVolumeCommand(@Nullable PercentType volume, AudioSink sink) {
boolean volumeChanged = false;
PercentType oldVolume = null;

Runnable toRunWhenProcessFinished = () -> {
};

if (volume == null) {
return toRunWhenProcessFinished;
}

// set notification sound volume
try {
// get current volume
oldVolume = sink.getVolume();
} catch (IOException | UnsupportedOperationException e) {
logger.debug("An exception occurred while getting the volume of sink '{}' : {}", sink.getId(),
e.getMessage(), e);
}

if (!volume.equals(oldVolume) || oldVolume == null) {
try {
sink.setVolume(volume);
volumeChanged = true;
} catch (IOException | UnsupportedOperationException e) {
logger.debug("An exception occurred while setting the volume of sink '{}' : {}", sink.getId(),
e.getMessage(), e);
}
}

final PercentType oldVolumeFinal = oldVolume;
// restore volume only if it was set before
if (volumeChanged && oldVolumeFinal != null) {
toRunWhenProcessFinished = () -> {
try {
sink.setVolume(oldVolumeFinal);
} catch (IOException | UnsupportedOperationException e) {
logger.debug("An exception occurred while setting the volume of sink '{}' : {}", sink.getId(),
e.getMessage(), e);
}
};
}

return toRunWhenProcessFinished;
}

@Reference(cardinality = ReferenceCardinality.MULTIPLE, policy = ReferencePolicy.DYNAMIC)
protected void addAudioSource(AudioSource audioSource) {
this.audioSources.put(audioSource.getId(), audioSource);
Expand Down
Loading

0 comments on commit 21eead5

Please sign in to comment.