Skip to content

Commit

Permalink
[onkyo] Support for more audio streams through the HTTP audio servlet
Browse files Browse the repository at this point in the history
Related to openhab#15113

Signed-off-by: Laurent Garnier <lg.hc@free.fr>
  • Loading branch information
lolodomo committed Jul 1, 2023
1 parent 3c5ce72 commit 3d914ce
Show file tree
Hide file tree
Showing 4 changed files with 136 additions and 104 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
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;
Expand Down Expand Up @@ -77,14 +78,12 @@ protected ThingHandler createHandler(Thing thing) {

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 audiSink = new OnkyoAudioSink(handler, audioHTTPServer, callbackUrl);
@SuppressWarnings("unchecked")
ServiceRegistration<AudioSink> reg = (ServiceRegistration<AudioSink>) bundleContext
.registerService(AudioSink.class.getName(), audiSink, new Hashtable<>());
audioSinkRegistrations.put(thing.getUID().toString(), reg);
return handler;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
/**
* 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.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;
}

if (audioStream instanceof URLAudioStream urlAudioStream) {
// it is an external URL, the speaker can access it itself and play it.
handler.playMedia(urlAudioStream.getURL());
try {
audioStream.close();
} catch (IOException e) {
}
} else if (callbackUrl != null) {
// we serve it on our own HTTP server
StreamServed streamServed;
try {
streamServed = audioHTTPServer.serve(audioStream, 5, true);
} catch (IOException e) {
throw new UnsupportedAudioStreamException(
"Onkyo was not able to handle the audio stream (cache on disk failed).", audioStream.getClass(),
e);
}
streamServed.playEnd().thenRun(() -> this.playbackFinished(audioStream));
handler.playMedia(callbackUrl + streamServed.url());
} else {
logger.warn("We do not have any callback url, so Onkyo cannot play the audio stream!");
}
}

@Override
public PercentType getVolume() throws IOException {
return handler.getVolume();
}

@Override
public void setVolume(PercentType volume) throws IOException {
handler.setVolume(volume);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@
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;
Expand Down Expand Up @@ -77,7 +76,7 @@
* @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);

Expand All @@ -98,9 +97,9 @@ public class OnkyoHandler extends UpnpAudioSinkHandler implements OnkyoEventList

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;
}

Expand Down Expand Up @@ -921,7 +920,6 @@ private PercentType scaleVolumeFromReceiver(DecimalType volume) {
return new PercentType(((Double) (volume.intValue() / configuration.volumeScale)).intValue());
}

@Override
public PercentType getVolume() throws IOException {
if (volumeLevelZone1 instanceof PercentType) {
return (PercentType) volumeLevelZone1;
Expand All @@ -930,7 +928,6 @@ public PercentType getVolume() throws IOException {
throw new IOException();
}

@Override
public void setVolume(PercentType volume) throws IOException {
handleVolumeSet(EiscpCommand.Zone.ZONE1, volumeLevelZone1, downScaleVolume(volume));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,21 +13,9 @@
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;
Expand All @@ -38,38 +26,20 @@
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.
* 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 UpnpAudioSinkHandler extends BaseThingHandler implements AudioSink, UpnpIOParticipant {
public abstract class OnkyoUpnpHandler extends BaseThingHandler implements UpnpIOParticipant {

private static final Set<AudioFormat> SUPPORTED_FORMATS = new HashSet<>();
private static final Set<Class<? extends AudioStream>> SUPPORTED_STREAMS = new HashSet<>();
private final Logger logger = LoggerFactory.getLogger(OnkyoUpnpHandler.class);

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) {
public OnkyoUpnpHandler(Thing thing, UpnpIOService upnpIOService) {
super(thing);
this.audioHTTPServer = audioHTTPServer;
this.callbackUrl = callbackUrl;
if (upnpIOService != null) {
this.service = upnpIOService;
}
this.service = upnpIOService;
}

protected void handlePlayUri(Command command) {
Expand All @@ -83,7 +53,7 @@ protected void handlePlayUri(Command command) {
}
}

private void playMedia(String url) {
public void playMedia(String url) {
stop();
removeAllTracksFromQueue();

Expand All @@ -96,17 +66,7 @@ private void playMedia(String url) {
play();
}

@Override
public Set<AudioFormat> getSupportedFormats() {
return SUPPORTED_FORMATS;
}

@Override
public Set<Class<? extends AudioStream>> getSupportedStreams() {
return SUPPORTED_STREAMS;
}

private void stop() {
public void stop() {
Map<String, String> inputs = new HashMap<>();
inputs.put("InstanceID", "0");

Expand Down Expand Up @@ -159,48 +119,6 @@ private void setCurrentURI(String uri, String uriMetaData) {
}
}

@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);
Expand Down

0 comments on commit 3d914ce

Please sign in to comment.