-
-
Notifications
You must be signed in to change notification settings - Fork 429
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
[audio] Enhance AudioSink capabilities using the AudioServlet #3461
Merged
J-N-K
merged 11 commits into
openhab:main
from
dalgwen:audio_servlet_better_stream_management
Jun 16, 2023
Merged
Changes from 10 commits
Commits
Show all changes
11 commits
Select commit
Hold shift + click to select a range
fc83ff2
[audio] More capabilities for AudioSink using the AudioServlet
dalgwen 120bccf
[audio] More capabilities for AudioSink using the AudioServlet
dalgwen 8f80976
[audio] More capabilities for AudioSink using the AudioServlet
dalgwen fcd81bd
[audio] More capabilities for AudioSink using the AudioServlet
dalgwen db3d1ef
[audio] More capabilities for AudioSink using the AudioServlet
dalgwen f491a54
[audio] More capabilities for AudioSink using the AudioServlet
dalgwen 5f996af
[audio] More capabilities for AudioSink using the AudioServlet
dalgwen 952efc5
[audio] More capabilities for AudioSink using the AudioServlet
dalgwen 041f466
[audio] More capabilities for AudioSink using the AudioServlet
dalgwen b8a0a5b
[audio] More capabilities for AudioSink using the AudioServlet
dalgwen 39e7770
[audio] More capabilities for AudioSink using the AudioServlet
dalgwen File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
114 changes: 114 additions & 0 deletions
114
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,114 @@ | ||
/** | ||
* 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); | ||
|
||
private final Map<AudioStream, CompletableFuture<@Nullable Void>> runnableByAudioStream = new HashMap<>(); | ||
|
||
@Override | ||
public CompletableFuture<@Nullable Void> processAndComplete(@Nullable AudioStream audioStream) | ||
throws UnsupportedAudioFormatException, UnsupportedAudioStreamException { | ||
CompletableFuture<@Nullable Void> completableFuture = new CompletableFuture<@Nullable Void>(); | ||
try { | ||
if (audioStream != null) { | ||
runnableByAudioStream.put(audioStream, completableFuture); | ||
} | ||
processAsynchronously(audioStream); | ||
return completableFuture; | ||
} finally { | ||
if (audioStream == null) { | ||
// No need to delay the post process task | ||
runnableByAudioStream.remove(audioStream); | ||
completableFuture.complete(null); | ||
} | ||
} | ||
} | ||
|
||
@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()); | ||
} | ||
} | ||
} | ||
} | ||
} |
84 changes: 84 additions & 0 deletions
84
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,84 @@ | ||
/** | ||
* 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) | ||
throws UnsupportedAudioFormatException, UnsupportedAudioStreamException { | ||
try { | ||
processSynchronously(audioStream); | ||
} 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()); | ||
} | ||
} | ||
} | ||
} | ||
return CompletableFuture.completedFuture(null); | ||
} | ||
|
||
@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.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If this is true, no
CompletableFuture
was added, so it doesn't need to be removed. In fact, it would be better to do the null check above and return aCompletableFuture.completedFuture(null)
. You can skip the try/catch block then.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Indeed, the remove in the null case is an error.
But, correct me if I'm wrong, as we have to call the processAsynchronously method -even if the stream is null, in order to ask the sink to stop current playing-, then if I remove the try / finally, I cannot be sure that the delayed volume restauration will occur (if there is an exception during the processAsynchronously(null) method).