import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
+import org.openhab.binding.onkyo.internal.handler.OnkyoAudioSink;
import org.openhab.binding.onkyo.internal.handler.OnkyoHandler;
import org.openhab.core.audio.AudioHTTPServer;
import org.openhab.core.audio.AudioSink;
if (SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID)) {
String callbackUrl = createCallbackUrl();
- OnkyoHandler handler = new OnkyoHandler(thing, upnpIOService, audioHTTPServer, callbackUrl,
- stateDescriptionProvider);
- if (callbackUrl != null) {
- @SuppressWarnings("unchecked")
- ServiceRegistration<AudioSink> reg = (ServiceRegistration<AudioSink>) bundleContext
- .registerService(AudioSink.class.getName(), handler, new Hashtable<>());
- audioSinkRegistrations.put(thing.getUID().toString(), reg);
- }
+ OnkyoHandler handler = new OnkyoHandler(thing, upnpIOService, stateDescriptionProvider);
+ OnkyoAudioSink audioSink = new OnkyoAudioSink(handler, audioHTTPServer, callbackUrl);
+ @SuppressWarnings("unchecked")
+ ServiceRegistration<AudioSink> reg = (ServiceRegistration<AudioSink>) bundleContext
+ .registerService(AudioSink.class.getName(), audioSink, new Hashtable<>());
+ audioSinkRegistrations.put(thing.getUID().toString(), reg);
return handler;
}
--- /dev/null
+/**
+ * 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.binding.onkyo.internal.handler;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Locale;
+import java.util.Set;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.core.audio.AudioFormat;
+import org.openhab.core.audio.AudioHTTPServer;
+import org.openhab.core.audio.AudioSinkAsync;
+import org.openhab.core.audio.AudioStream;
+import org.openhab.core.audio.StreamServed;
+import org.openhab.core.audio.URLAudioStream;
+import org.openhab.core.audio.UnsupportedAudioFormatException;
+import org.openhab.core.audio.UnsupportedAudioStreamException;
+import org.openhab.core.library.types.PercentType;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * * The {@link OnkyoAudioSink} implements the AudioSink interface.
+ *
+ * @author Paul Frank - Initial contribution
+ * @author Laurent Garnier - Extracted from UpnpAudioSinkHandler to extend AudioSinkAsync
+ */
+@NonNullByDefault
+public class OnkyoAudioSink extends AudioSinkAsync {
+
+ private final Logger logger = LoggerFactory.getLogger(OnkyoAudioSink.class);
+
+ private static final Set<AudioFormat> SUPPORTED_FORMATS = Set.of(AudioFormat.WAV, AudioFormat.MP3);
+ private static final Set<Class<? extends AudioStream>> SUPPORTED_STREAMS = Set.of(AudioStream.class);
+
+ private OnkyoHandler handler;
+ private AudioHTTPServer audioHTTPServer;
+ private @Nullable String callbackUrl;
+
+ public OnkyoAudioSink(OnkyoHandler handler, AudioHTTPServer audioHTTPServer, @Nullable String callbackUrl) {
+ this.handler = handler;
+ this.audioHTTPServer = audioHTTPServer;
+ this.callbackUrl = callbackUrl;
+ }
+
+ @Override
+ public Set<AudioFormat> getSupportedFormats() {
+ return SUPPORTED_FORMATS;
+ }
+
+ @Override
+ public Set<Class<? extends AudioStream>> getSupportedStreams() {
+ return SUPPORTED_STREAMS;
+ }
+
+ @Override
+ public String getId() {
+ return handler.getThing().getUID().toString();
+ }
+
+ @Override
+ public @Nullable String getLabel(@Nullable Locale locale) {
+ return handler.getThing().getLabel();
+ }
+
+ @Override
+ protected void processAsynchronously(@Nullable AudioStream audioStream)
+ throws UnsupportedAudioFormatException, UnsupportedAudioStreamException {
+ if (audioStream == null) {
+ handler.stop();
+ return;
+ }
+
+ String url;
+ if (audioStream instanceof URLAudioStream urlAudioStream) {
+ // it is an external URL, the speaker can access it itself and play it.
+ url = urlAudioStream.getURL();
+ tryClose(audioStream);
+ } else if (callbackUrl != null) {
+ // we serve it on our own HTTP server
+ StreamServed streamServed;
+ try {
+ streamServed = audioHTTPServer.serve(audioStream, 10, true);
+ } catch (IOException e) {
+ tryClose(audioStream);
+ throw new UnsupportedAudioStreamException(
+ "Onkyo was not able to handle the audio stream (cache on disk failed).", audioStream.getClass(),
+ e);
+ }
+ url = callbackUrl + streamServed.url();
+ streamServed.playEnd().thenRun(() -> this.playbackFinished(audioStream));
+ } else {
+ logger.warn("We do not have any callback url, so Onkyo cannot play the audio stream!");
+ tryClose(audioStream);
+ return;
+ }
+ handler.playMedia(url);
+ }
+
+ private void tryClose(@Nullable InputStream is) {
+ if (is != null) {
+ try {
+ is.close();
+ } catch (IOException ignored) {
+ }
+ }
+ }
+
+ @Override
+ public PercentType getVolume() throws IOException {
+ return handler.getVolume();
+ }
+
+ @Override
+ public void setVolume(PercentType volume) throws IOException {
+ handler.setVolume(volume);
+ }
+}
import org.openhab.binding.onkyo.internal.config.OnkyoDeviceConfiguration;
import org.openhab.binding.onkyo.internal.eiscp.EiscpCommand;
import org.openhab.binding.onkyo.internal.eiscp.EiscpMessage;
-import org.openhab.core.audio.AudioHTTPServer;
import org.openhab.core.io.net.http.HttpUtil;
import org.openhab.core.io.transport.upnp.UpnpIOService;
import org.openhab.core.library.types.DecimalType;
* @author Pauli Anttila - lot of refactoring
* @author Stewart Cossey - add dynamic state description provider
*/
-public class OnkyoHandler extends UpnpAudioSinkHandler implements OnkyoEventListener {
+public class OnkyoHandler extends OnkyoUpnpHandler implements OnkyoEventListener {
private final Logger logger = LoggerFactory.getLogger(OnkyoHandler.class);
private static final int NET_USB_ID = 43;
- public OnkyoHandler(Thing thing, UpnpIOService upnpIOService, AudioHTTPServer audioHTTPServer, String callbackUrl,
+ public OnkyoHandler(Thing thing, UpnpIOService upnpIOService,
OnkyoStateDescriptionProvider stateDescriptionProvider) {
- super(thing, upnpIOService, audioHTTPServer, callbackUrl);
+ super(thing, upnpIOService);
this.stateDescriptionProvider = stateDescriptionProvider;
}
return new PercentType(((Double) (volume.intValue() / configuration.volumeScale)).intValue());
}
- @Override
public PercentType getVolume() throws IOException {
if (volumeLevelZone1 instanceof PercentType) {
return (PercentType) volumeLevelZone1;
throw new IOException();
}
- @Override
public void setVolume(PercentType volume) throws IOException {
handleVolumeSet(EiscpCommand.Zone.ZONE1, volumeLevelZone1, downScaleVolume(volume));
}
--- /dev/null
+/**
+ * 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.binding.onkyo.internal.handler;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.openhab.binding.onkyo.internal.OnkyoBindingConstants;
+import org.openhab.core.io.transport.upnp.UpnpIOParticipant;
+import org.openhab.core.io.transport.upnp.UpnpIOService;
+import org.openhab.core.library.types.StringType;
+import org.openhab.core.thing.Thing;
+import org.openhab.core.thing.binding.BaseThingHandler;
+import org.openhab.core.types.Command;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The {@link OnkyoUpnpHandler} is a base class for ThingHandlers for devices which support UPnP playback.
+ *
+ * @author Paul Frank - Initial contribution
+ * @author Laurent Garnier - Separated into OnkyoUpnpHandler and OnkyoAudioSink
+ */
+public abstract class OnkyoUpnpHandler extends BaseThingHandler implements UpnpIOParticipant {
+
+ private final Logger logger = LoggerFactory.getLogger(OnkyoUpnpHandler.class);
+
+ private UpnpIOService service;
+
+ public OnkyoUpnpHandler(Thing thing, UpnpIOService upnpIOService) {
+ super(thing);
+ this.service = upnpIOService;
+ }
+
+ protected void handlePlayUri(Command command) {
+ if (command != null && command instanceof StringType) {
+ try {
+ playMedia(command.toString());
+
+ } catch (IllegalStateException e) {
+ logger.warn("Cannot play URI ({})", e.getMessage());
+ }
+ }
+ }
+
+ public void playMedia(String url) {
+ stop();
+ removeAllTracksFromQueue();
+
+ if (!url.startsWith("x-") && (!url.startsWith("http"))) {
+ url = "x-file-cifs:" + url;
+ }
+
+ setCurrentURI(url, "");
+
+ play();
+ }
+
+ public void stop() {
+ Map<String, String> inputs = new HashMap<>();
+ inputs.put("InstanceID", "0");
+
+ Map<String, String> result = service.invokeAction(this, "AVTransport", "Stop", inputs);
+
+ for (String variable : result.keySet()) {
+ this.onValueReceived(variable, result.get(variable), "AVTransport");
+ }
+ }
+
+ private void play() {
+ Map<String, String> inputs = new HashMap<>();
+ inputs.put("InstanceID", "0");
+ inputs.put("Speed", "1");
+ Map<String, String> result = service.invokeAction(this, "AVTransport", "Play", inputs);
+
+ for (String variable : result.keySet()) {
+ this.onValueReceived(variable, result.get(variable), "AVTransport");
+ }
+ }
+
+ private void removeAllTracksFromQueue() {
+ Map<String, String> inputs = new HashMap<>();
+ inputs.put("InstanceID", "0");
+
+ Map<String, String> result = service.invokeAction(this, "AVTransport", "RemoveAllTracksFromQueue", inputs);
+
+ for (String variable : result.keySet()) {
+ this.onValueReceived(variable, result.get(variable), "AVTransport");
+ }
+ }
+
+ private void setCurrentURI(String uri, String uriMetaData) {
+ if (uri != null && uriMetaData != null) {
+ Map<String, String> inputs = new HashMap<>();
+
+ try {
+ inputs.put("InstanceID", "0");
+ inputs.put("CurrentURI", uri);
+ inputs.put("CurrentURIMetaData", uriMetaData);
+ } catch (NumberFormatException ex) {
+ logger.error("Action Invalid Value Format Exception {}", ex.getMessage());
+ }
+
+ Map<String, String> result = service.invokeAction(this, "AVTransport", "SetAVTransportURI", inputs);
+
+ for (String variable : result.keySet()) {
+ this.onValueReceived(variable, result.get(variable), "AVTransport");
+ }
+ }
+ }
+
+ @Override
+ public String getUDN() {
+ return (String) this.getConfig().get(OnkyoBindingConstants.UDN_PARAMETER);
+ }
+
+ @Override
+ public void onValueReceived(String variable, String value, String service) {
+ logger.debug("received variable {} with value {} from service {}", variable, value, service);
+ }
+
+ @Override
+ public void onServiceSubscribed(String service, boolean succeeded) {
+ }
+
+ @Override
+ public void onStatusChanged(boolean status) {
+ }
+}
+++ /dev/null
-/**
- * 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.binding.onkyo.internal.handler;
-
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Locale;
-import java.util.Map;
-import java.util.Set;
-
-import org.eclipse.jdt.annotation.Nullable;
-import org.openhab.binding.onkyo.internal.OnkyoBindingConstants;
-import org.openhab.core.audio.AudioFormat;
-import org.openhab.core.audio.AudioHTTPServer;
-import org.openhab.core.audio.AudioSink;
-import org.openhab.core.audio.AudioStream;
-import org.openhab.core.audio.FixedLengthAudioStream;
-import org.openhab.core.audio.URLAudioStream;
-import org.openhab.core.audio.UnsupportedAudioFormatException;
-import org.openhab.core.audio.UnsupportedAudioStreamException;
-import org.openhab.core.io.transport.upnp.UpnpIOParticipant;
-import org.openhab.core.io.transport.upnp.UpnpIOService;
-import org.openhab.core.library.types.StringType;
-import org.openhab.core.thing.Thing;
-import org.openhab.core.thing.binding.BaseThingHandler;
-import org.openhab.core.types.Command;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * * The {@link UpnpAudioSinkHandler} is a base class for ThingHandlers for devices which support UPnP playback. It
- * implements the AudioSink interface.
- * This will allow to register the derived ThingHandler to be registered as an AudioSink in the framework.
- *
- * @author Paul Frank - Initial contribution
- */
-public abstract class UpnpAudioSinkHandler extends BaseThingHandler implements AudioSink, UpnpIOParticipant {
-
- private static final Set<AudioFormat> SUPPORTED_FORMATS = new HashSet<>();
- private static final Set<Class<? extends AudioStream>> SUPPORTED_STREAMS = new HashSet<>();
-
- static {
- SUPPORTED_FORMATS.add(AudioFormat.WAV);
- SUPPORTED_FORMATS.add(AudioFormat.MP3);
-
- SUPPORTED_STREAMS.add(AudioStream.class);
- }
-
- private final Logger logger = LoggerFactory.getLogger(getClass());
-
- private AudioHTTPServer audioHTTPServer;
- private String callbackUrl;
- private UpnpIOService service;
-
- public UpnpAudioSinkHandler(Thing thing, UpnpIOService upnpIOService, AudioHTTPServer audioHTTPServer,
- String callbackUrl) {
- super(thing);
- this.audioHTTPServer = audioHTTPServer;
- this.callbackUrl = callbackUrl;
- if (upnpIOService != null) {
- this.service = upnpIOService;
- }
- }
-
- protected void handlePlayUri(Command command) {
- if (command != null && command instanceof StringType) {
- try {
- playMedia(command.toString());
-
- } catch (IllegalStateException e) {
- logger.warn("Cannot play URI ({})", e.getMessage());
- }
- }
- }
-
- private void playMedia(String url) {
- stop();
- removeAllTracksFromQueue();
-
- if (!url.startsWith("x-") && (!url.startsWith("http"))) {
- url = "x-file-cifs:" + url;
- }
-
- setCurrentURI(url, "");
-
- play();
- }
-
- @Override
- public Set<AudioFormat> getSupportedFormats() {
- return SUPPORTED_FORMATS;
- }
-
- @Override
- public Set<Class<? extends AudioStream>> getSupportedStreams() {
- return SUPPORTED_STREAMS;
- }
-
- private void stop() {
- Map<String, String> inputs = new HashMap<>();
- inputs.put("InstanceID", "0");
-
- Map<String, String> result = service.invokeAction(this, "AVTransport", "Stop", inputs);
-
- for (String variable : result.keySet()) {
- this.onValueReceived(variable, result.get(variable), "AVTransport");
- }
- }
-
- private void play() {
- Map<String, String> inputs = new HashMap<>();
- inputs.put("InstanceID", "0");
- inputs.put("Speed", "1");
- Map<String, String> result = service.invokeAction(this, "AVTransport", "Play", inputs);
-
- for (String variable : result.keySet()) {
- this.onValueReceived(variable, result.get(variable), "AVTransport");
- }
- }
-
- private void removeAllTracksFromQueue() {
- Map<String, String> inputs = new HashMap<>();
- inputs.put("InstanceID", "0");
-
- Map<String, String> result = service.invokeAction(this, "AVTransport", "RemoveAllTracksFromQueue", inputs);
-
- for (String variable : result.keySet()) {
- this.onValueReceived(variable, result.get(variable), "AVTransport");
- }
- }
-
- private void setCurrentURI(String uri, String uriMetaData) {
- if (uri != null && uriMetaData != null) {
- Map<String, String> inputs = new HashMap<>();
-
- try {
- inputs.put("InstanceID", "0");
- inputs.put("CurrentURI", uri);
- inputs.put("CurrentURIMetaData", uriMetaData);
- } catch (NumberFormatException ex) {
- logger.error("Action Invalid Value Format Exception {}", ex.getMessage());
- }
-
- Map<String, String> result = service.invokeAction(this, "AVTransport", "SetAVTransportURI", inputs);
-
- for (String variable : result.keySet()) {
- this.onValueReceived(variable, result.get(variable), "AVTransport");
- }
- }
- }
-
- @Override
- public String getId() {
- return getThing().getUID().toString();
- }
-
- @Override
- public String getLabel(Locale locale) {
- return getThing().getLabel();
- }
-
- @Override
- public void process(@Nullable AudioStream audioStream)
- throws UnsupportedAudioFormatException, UnsupportedAudioStreamException {
- if (audioStream == null) {
- stop();
- return;
- }
-
- String url = null;
- if (audioStream instanceof URLAudioStream) {
- // it is an external URL, the speaker can access it itself and play it.
- URLAudioStream urlAudioStream = (URLAudioStream) audioStream;
- url = urlAudioStream.getURL();
- } else {
- if (callbackUrl != null) {
- String relativeUrl;
- if (audioStream instanceof FixedLengthAudioStream) {
- // we serve it on our own HTTP server
- relativeUrl = audioHTTPServer.serve((FixedLengthAudioStream) audioStream, 20);
- } else {
- relativeUrl = audioHTTPServer.serve(audioStream);
- }
- url = callbackUrl + relativeUrl;
- } else {
- logger.warn("We do not have any callback url, so onkyo cannot play the audio stream!");
- return;
- }
- }
-
- playMedia(url);
- }
-
- @Override
- public String getUDN() {
- return (String) this.getConfig().get(OnkyoBindingConstants.UDN_PARAMETER);
- }
-
- @Override
- public void onValueReceived(String variable, String value, String service) {
- logger.debug("received variable {} with value {} from service {}", variable, value, service);
- }
-
- @Override
- public void onServiceSubscribed(String service, boolean succeeded) {
- }
-
- @Override
- public void onStatusChanged(boolean status) {
- }
-}