-
-
Notifications
You must be signed in to change notification settings - Fork 428
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[audio] Enhance AudioSink capabilities using the AudioServlet (#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]>
- Loading branch information
Showing
25 changed files
with
888 additions
and
208 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
113 changes: 113 additions & 0 deletions
113
bundles/org.openhab.core.audio/src/main/java/org/openhab/core/audio/AudioSinkAsync.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
/** | ||
* Copyright (c) 2010-2023 Contributors to the openHAB project | ||
* | ||
* See the NOTICE file(s) distributed with this work for additional | ||
* information. | ||
* | ||
* This program and the accompanying materials are made available under the | ||
* terms of the Eclipse Public License 2.0 which is available at | ||
* http://www.eclipse.org/legal/epl-2.0 | ||
* | ||
* SPDX-License-Identifier: EPL-2.0 | ||
*/ | ||
package org.openhab.core.audio; | ||
|
||
import java.io.IOException; | ||
import java.util.HashMap; | ||
import java.util.Map; | ||
import java.util.concurrent.CompletableFuture; | ||
|
||
import org.eclipse.jdt.annotation.NonNullByDefault; | ||
import org.eclipse.jdt.annotation.Nullable; | ||
import org.openhab.core.common.Disposable; | ||
import org.slf4j.Logger; | ||
import org.slf4j.LoggerFactory; | ||
|
||
/** | ||
* Definition of an audio output like headphones, a speaker or for writing to | ||
* a file / clip. | ||
* Helper class for asynchronous sink : when the process() method returns, the {@link AudioStream} | ||
* may or may not be played. It is the responsibility of the implementing AudioSink class to | ||
* complete the CompletableFuture when playing is done. Any delayed tasks will then be performed, such as volume | ||
* restoration. | ||
* | ||
* @author Gwendal Roulleau - Initial contribution | ||
*/ | ||
@NonNullByDefault | ||
public abstract class AudioSinkAsync implements AudioSink { | ||
|
||
private final Logger logger = LoggerFactory.getLogger(AudioSinkAsync.class); | ||
|
||
protected final Map<AudioStream, CompletableFuture<@Nullable Void>> runnableByAudioStream = new HashMap<>(); | ||
|
||
@Override | ||
public CompletableFuture<@Nullable Void> processAndComplete(@Nullable AudioStream audioStream) { | ||
CompletableFuture<@Nullable Void> completableFuture = new CompletableFuture<@Nullable Void>(); | ||
if (audioStream != null) { | ||
runnableByAudioStream.put(audioStream, completableFuture); | ||
} | ||
try { | ||
processAsynchronously(audioStream); | ||
} catch (UnsupportedAudioFormatException | UnsupportedAudioStreamException e) { | ||
completableFuture.completeExceptionally(e); | ||
} | ||
if (audioStream == null) { | ||
// No need to delay the post process task | ||
completableFuture.complete(null); | ||
} | ||
return completableFuture; | ||
} | ||
|
||
@Override | ||
public void process(@Nullable AudioStream audioStream) | ||
throws UnsupportedAudioFormatException, UnsupportedAudioStreamException { | ||
processAsynchronously(audioStream); | ||
} | ||
|
||
/** | ||
* Processes the passed {@link AudioStream} asynchronously. This method is expected to return before the stream is | ||
* fully played. This is the sink responsibility to call the {@link AudioSinkAsync#playbackFinished(AudioStream)} | ||
* when it is. | ||
* | ||
* If the passed {@link AudioStream} is not supported by this instance, an {@link UnsupportedAudioStreamException} | ||
* is thrown. | ||
* | ||
* If the passed {@link AudioStream} has an {@link AudioFormat} not supported by this instance, | ||
* an {@link UnsupportedAudioFormatException} is thrown. | ||
* | ||
* In case the audioStream is null, this should be interpreted as a request to end any currently playing stream. | ||
* | ||
* @param audioStream the audio stream to play or null to keep quiet | ||
* @throws UnsupportedAudioFormatException If audioStream format is not supported | ||
* @throws UnsupportedAudioStreamException If audioStream is not supported | ||
*/ | ||
protected abstract void processAsynchronously(@Nullable AudioStream audioStream) | ||
throws UnsupportedAudioFormatException, UnsupportedAudioStreamException; | ||
|
||
/** | ||
* Will complete the future previously returned, allowing the core to run delayed task. | ||
* | ||
* @param audioStream The AudioStream is the key to find the delayed CompletableFuture in the storage. | ||
*/ | ||
protected void playbackFinished(AudioStream audioStream) { | ||
CompletableFuture<@Nullable Void> completableFuture = runnableByAudioStream.remove(audioStream); | ||
if (completableFuture != null) { | ||
completableFuture.complete(null); | ||
} | ||
|
||
// if the stream is not needed anymore, then we should call back the AudioStream to let it a chance | ||
// to auto dispose. | ||
if (audioStream instanceof Disposable disposableAudioStream) { | ||
try { | ||
disposableAudioStream.dispose(); | ||
} catch (IOException e) { | ||
String fileName = audioStream instanceof FileAudioStream file ? file.toString() : "unknown"; | ||
if (logger.isDebugEnabled()) { | ||
logger.debug("Cannot dispose of stream {}", fileName, e); | ||
} else { | ||
logger.warn("Cannot dispose of stream {}, reason {}", fileName, e.getMessage()); | ||
} | ||
} | ||
} | ||
} | ||
} |
85 changes: 85 additions & 0 deletions
85
bundles/org.openhab.core.audio/src/main/java/org/openhab/core/audio/AudioSinkSync.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
/** | ||
* Copyright (c) 2010-2023 Contributors to the openHAB project | ||
* | ||
* See the NOTICE file(s) distributed with this work for additional | ||
* information. | ||
* | ||
* This program and the accompanying materials are made available under the | ||
* terms of the Eclipse Public License 2.0 which is available at | ||
* http://www.eclipse.org/legal/epl-2.0 | ||
* | ||
* SPDX-License-Identifier: EPL-2.0 | ||
*/ | ||
package org.openhab.core.audio; | ||
|
||
import java.io.IOException; | ||
import java.util.concurrent.CompletableFuture; | ||
|
||
import org.eclipse.jdt.annotation.NonNullByDefault; | ||
import org.eclipse.jdt.annotation.Nullable; | ||
import org.openhab.core.common.Disposable; | ||
import org.slf4j.Logger; | ||
import org.slf4j.LoggerFactory; | ||
|
||
/** | ||
* Definition of an audio output like headphones, a speaker or for writing to | ||
* a file / clip. | ||
* Helper class for synchronous sink : when the process() method returns, | ||
* the source is considered played, and could be disposed. | ||
* Any delayed tasks can then be performed, such as volume restoration. | ||
* | ||
* @author Gwendal Roulleau - Initial contribution | ||
*/ | ||
@NonNullByDefault | ||
public abstract class AudioSinkSync implements AudioSink { | ||
|
||
private final Logger logger = LoggerFactory.getLogger(AudioSinkSync.class); | ||
|
||
@Override | ||
public CompletableFuture<@Nullable Void> processAndComplete(@Nullable AudioStream audioStream) { | ||
try { | ||
processSynchronously(audioStream); | ||
return CompletableFuture.completedFuture(null); | ||
} catch (UnsupportedAudioFormatException | UnsupportedAudioStreamException e) { | ||
return CompletableFuture.failedFuture(e); | ||
} finally { | ||
// as the stream is not needed anymore, we should dispose of it | ||
if (audioStream instanceof Disposable disposableAudioStream) { | ||
try { | ||
disposableAudioStream.dispose(); | ||
} catch (IOException e) { | ||
String fileName = audioStream instanceof FileAudioStream file ? file.toString() : "unknown"; | ||
if (logger.isDebugEnabled()) { | ||
logger.debug("Cannot dispose of stream {}", fileName, e); | ||
} else { | ||
logger.warn("Cannot dispose of stream {}, reason {}", fileName, e.getMessage()); | ||
} | ||
} | ||
} | ||
} | ||
} | ||
|
||
@Override | ||
public void process(@Nullable AudioStream audioStream) | ||
throws UnsupportedAudioFormatException, UnsupportedAudioStreamException { | ||
processSynchronously(audioStream); | ||
} | ||
|
||
/** | ||
* Processes the passed {@link AudioStream} and returns only when the playback is ended. | ||
* | ||
* If the passed {@link AudioStream} is not supported by this instance, an {@link UnsupportedAudioStreamException} | ||
* is thrown. | ||
* | ||
* If the passed {@link AudioStream} has an {@link AudioFormat} not supported by this instance, | ||
* an {@link UnsupportedAudioFormatException} is thrown. | ||
* | ||
* In case the audioStream is null, this should be interpreted as a request to end any currently playing stream. | ||
* | ||
* @param audioStream the audio stream to play or null to keep quiet | ||
* @throws UnsupportedAudioFormatException If audioStream format is not supported | ||
* @throws UnsupportedAudioStreamException If audioStream is not supported | ||
*/ | ||
protected abstract void processSynchronously(@Nullable AudioStream audioStream) | ||
throws UnsupportedAudioFormatException, UnsupportedAudioStreamException; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
35 changes: 35 additions & 0 deletions
35
bundles/org.openhab.core.audio/src/main/java/org/openhab/core/audio/ClonableAudioStream.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
/** | ||
* Copyright (c) 2010-2023 Contributors to the openHAB project | ||
* | ||
* See the NOTICE file(s) distributed with this work for additional | ||
* information. | ||
* | ||
* This program and the accompanying materials are made available under the | ||
* terms of the Eclipse Public License 2.0 which is available at | ||
* http://www.eclipse.org/legal/epl-2.0 | ||
* | ||
* SPDX-License-Identifier: EPL-2.0 | ||
*/ | ||
package org.openhab.core.audio; | ||
|
||
import java.io.InputStream; | ||
|
||
import org.eclipse.jdt.annotation.NonNullByDefault; | ||
|
||
/** | ||
* This is an {@link AudioStream}, that can be cloned | ||
* | ||
* @author Gwendal Roulleau - Initial contribution, separation from FixedLengthAudioStream | ||
*/ | ||
@NonNullByDefault | ||
public abstract class ClonableAudioStream extends AudioStream { | ||
|
||
/** | ||
* Returns a new, fully independent stream instance, which can be read and closed without impacting the original | ||
* instance. | ||
* | ||
* @return a new input stream that can be consumed by the caller | ||
* @throws AudioException if stream cannot be created | ||
*/ | ||
public abstract InputStream getClonedStream() throws AudioException; | ||
} |
Oops, something went wrong.