[onkyo] Support for more audio streams through the HTTP audio servlet (#15117)
* [onkyo] Support for more audio streams through the HTTP audio servlet Related to #15113 Signed-off-by: Laurent Garnier <lg.hc@free.fr>
This commit is contained in:
parent
af89237d6b
commit
287cee32a5
|
@ -19,6 +19,7 @@ import java.util.Hashtable;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
|
import org.openhab.binding.onkyo.internal.handler.OnkyoAudioSink;
|
||||||
import org.openhab.binding.onkyo.internal.handler.OnkyoHandler;
|
import org.openhab.binding.onkyo.internal.handler.OnkyoHandler;
|
||||||
import org.openhab.core.audio.AudioHTTPServer;
|
import org.openhab.core.audio.AudioHTTPServer;
|
||||||
import org.openhab.core.audio.AudioSink;
|
import org.openhab.core.audio.AudioSink;
|
||||||
|
@ -77,14 +78,12 @@ public class OnkyoHandlerFactory extends BaseThingHandlerFactory {
|
||||||
|
|
||||||
if (SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID)) {
|
if (SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID)) {
|
||||||
String callbackUrl = createCallbackUrl();
|
String callbackUrl = createCallbackUrl();
|
||||||
OnkyoHandler handler = new OnkyoHandler(thing, upnpIOService, audioHTTPServer, callbackUrl,
|
OnkyoHandler handler = new OnkyoHandler(thing, upnpIOService, stateDescriptionProvider);
|
||||||
stateDescriptionProvider);
|
OnkyoAudioSink audioSink = new OnkyoAudioSink(handler, audioHTTPServer, callbackUrl);
|
||||||
if (callbackUrl != null) {
|
@SuppressWarnings("unchecked")
|
||||||
@SuppressWarnings("unchecked")
|
ServiceRegistration<AudioSink> reg = (ServiceRegistration<AudioSink>) bundleContext
|
||||||
ServiceRegistration<AudioSink> reg = (ServiceRegistration<AudioSink>) bundleContext
|
.registerService(AudioSink.class.getName(), audioSink, new Hashtable<>());
|
||||||
.registerService(AudioSink.class.getName(), handler, new Hashtable<>());
|
audioSinkRegistrations.put(thing.getUID().toString(), reg);
|
||||||
audioSinkRegistrations.put(thing.getUID().toString(), reg);
|
|
||||||
}
|
|
||||||
return handler;
|
return handler;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,130 @@
|
||||||
|
/**
|
||||||
|
* 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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -37,7 +37,6 @@ import org.openhab.binding.onkyo.internal.automation.modules.OnkyoThingActions;
|
||||||
import org.openhab.binding.onkyo.internal.config.OnkyoDeviceConfiguration;
|
import org.openhab.binding.onkyo.internal.config.OnkyoDeviceConfiguration;
|
||||||
import org.openhab.binding.onkyo.internal.eiscp.EiscpCommand;
|
import org.openhab.binding.onkyo.internal.eiscp.EiscpCommand;
|
||||||
import org.openhab.binding.onkyo.internal.eiscp.EiscpMessage;
|
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.net.http.HttpUtil;
|
||||||
import org.openhab.core.io.transport.upnp.UpnpIOService;
|
import org.openhab.core.io.transport.upnp.UpnpIOService;
|
||||||
import org.openhab.core.library.types.DecimalType;
|
import org.openhab.core.library.types.DecimalType;
|
||||||
|
@ -77,7 +76,7 @@ import org.xml.sax.SAXException;
|
||||||
* @author Pauli Anttila - lot of refactoring
|
* @author Pauli Anttila - lot of refactoring
|
||||||
* @author Stewart Cossey - add dynamic state description provider
|
* @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 final Logger logger = LoggerFactory.getLogger(OnkyoHandler.class);
|
||||||
|
|
||||||
|
@ -98,9 +97,9 @@ public class OnkyoHandler extends UpnpAudioSinkHandler implements OnkyoEventList
|
||||||
|
|
||||||
private static final int NET_USB_ID = 43;
|
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) {
|
OnkyoStateDescriptionProvider stateDescriptionProvider) {
|
||||||
super(thing, upnpIOService, audioHTTPServer, callbackUrl);
|
super(thing, upnpIOService);
|
||||||
this.stateDescriptionProvider = stateDescriptionProvider;
|
this.stateDescriptionProvider = stateDescriptionProvider;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -921,7 +920,6 @@ public class OnkyoHandler extends UpnpAudioSinkHandler implements OnkyoEventList
|
||||||
return new PercentType(((Double) (volume.intValue() / configuration.volumeScale)).intValue());
|
return new PercentType(((Double) (volume.intValue() / configuration.volumeScale)).intValue());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public PercentType getVolume() throws IOException {
|
public PercentType getVolume() throws IOException {
|
||||||
if (volumeLevelZone1 instanceof PercentType) {
|
if (volumeLevelZone1 instanceof PercentType) {
|
||||||
return (PercentType) volumeLevelZone1;
|
return (PercentType) volumeLevelZone1;
|
||||||
|
@ -930,7 +928,6 @@ public class OnkyoHandler extends UpnpAudioSinkHandler implements OnkyoEventList
|
||||||
throw new IOException();
|
throw new IOException();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setVolume(PercentType volume) throws IOException {
|
public void setVolume(PercentType volume) throws IOException {
|
||||||
handleVolumeSet(EiscpCommand.Zone.ZONE1, volumeLevelZone1, downScaleVolume(volume));
|
handleVolumeSet(EiscpCommand.Zone.ZONE1, volumeLevelZone1, downScaleVolume(volume));
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,21 +13,9 @@
|
||||||
package org.openhab.binding.onkyo.internal.handler;
|
package org.openhab.binding.onkyo.internal.handler;
|
||||||
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.Locale;
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
import org.eclipse.jdt.annotation.Nullable;
|
|
||||||
import org.openhab.binding.onkyo.internal.OnkyoBindingConstants;
|
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.UpnpIOParticipant;
|
||||||
import org.openhab.core.io.transport.upnp.UpnpIOService;
|
import org.openhab.core.io.transport.upnp.UpnpIOService;
|
||||||
import org.openhab.core.library.types.StringType;
|
import org.openhab.core.library.types.StringType;
|
||||||
|
@ -38,38 +26,20 @@ import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* * The {@link UpnpAudioSinkHandler} is a base class for ThingHandlers for devices which support UPnP playback. It
|
* The {@link OnkyoUpnpHandler} is a base class for ThingHandlers for devices which support UPnP playback.
|
||||||
* 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
|
* @author Paul Frank - Initial contribution
|
||||||
|
* @author Laurent Garnier - Separated into OnkyoUpnpHandler and OnkyoAudioSink
|
||||||
*/
|
*/
|
||||||
public abstract class UpnpAudioSinkHandler extends BaseThingHandler implements AudioSink, UpnpIOParticipant {
|
public abstract class OnkyoUpnpHandler extends BaseThingHandler implements UpnpIOParticipant {
|
||||||
|
|
||||||
private static final Set<AudioFormat> SUPPORTED_FORMATS = new HashSet<>();
|
private final Logger logger = LoggerFactory.getLogger(OnkyoUpnpHandler.class);
|
||||||
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;
|
private UpnpIOService service;
|
||||||
|
|
||||||
public UpnpAudioSinkHandler(Thing thing, UpnpIOService upnpIOService, AudioHTTPServer audioHTTPServer,
|
public OnkyoUpnpHandler(Thing thing, UpnpIOService upnpIOService) {
|
||||||
String callbackUrl) {
|
|
||||||
super(thing);
|
super(thing);
|
||||||
this.audioHTTPServer = audioHTTPServer;
|
this.service = upnpIOService;
|
||||||
this.callbackUrl = callbackUrl;
|
|
||||||
if (upnpIOService != null) {
|
|
||||||
this.service = upnpIOService;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void handlePlayUri(Command command) {
|
protected void handlePlayUri(Command command) {
|
||||||
|
@ -83,7 +53,7 @@ public abstract class UpnpAudioSinkHandler extends BaseThingHandler implements A
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void playMedia(String url) {
|
public void playMedia(String url) {
|
||||||
stop();
|
stop();
|
||||||
removeAllTracksFromQueue();
|
removeAllTracksFromQueue();
|
||||||
|
|
||||||
|
@ -96,17 +66,7 @@ public abstract class UpnpAudioSinkHandler extends BaseThingHandler implements A
|
||||||
play();
|
play();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
public void stop() {
|
||||||
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<>();
|
Map<String, String> inputs = new HashMap<>();
|
||||||
inputs.put("InstanceID", "0");
|
inputs.put("InstanceID", "0");
|
||||||
|
|
||||||
|
@ -159,48 +119,6 @@ public abstract class UpnpAudioSinkHandler extends BaseThingHandler implements A
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@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
|
@Override
|
||||||
public String getUDN() {
|
public String getUDN() {
|
||||||
return (String) this.getConfig().get(OnkyoBindingConstants.UDN_PARAMETER);
|
return (String) this.getConfig().get(OnkyoBindingConstants.UDN_PARAMETER);
|
Loading…
Reference in New Issue