mirror of
https://github.com/openhab/openhab-addons.git
synced 2025-01-10 15:11:59 +01:00
* [Onkyo] Set "CurrentURIMetaData" for "SetAVTransportURI" action (#17755) Signed-off-by: Krzysztof Goworek <krzysztof.goworek@gmail.com> Signed-off-by: Ciprian Pascu <contact@ciprianpascu.ro>
This commit is contained in:
parent
e2ced84eb2
commit
40e0b845bd
@ -45,9 +45,9 @@ public class OnkyoAudioSink extends AudioSinkAsync {
|
||||
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;
|
||||
private final OnkyoHandler handler;
|
||||
private final AudioHTTPServer audioHTTPServer;
|
||||
private final @Nullable String callbackUrl;
|
||||
|
||||
public OnkyoAudioSink(OnkyoHandler handler, AudioHTTPServer audioHTTPServer, @Nullable String callbackUrl) {
|
||||
this.handler = handler;
|
||||
@ -106,7 +106,7 @@ public class OnkyoAudioSink extends AudioSinkAsync {
|
||||
tryClose(audioStream);
|
||||
return;
|
||||
}
|
||||
handler.playMedia(url);
|
||||
handler.playMedia(url, audioStream);
|
||||
}
|
||||
|
||||
private void tryClose(@Nullable InputStream is) {
|
||||
|
@ -16,6 +16,7 @@ import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.openhab.binding.onkyo.internal.OnkyoBindingConstants;
|
||||
import org.openhab.core.audio.AudioStream;
|
||||
import org.openhab.core.io.transport.upnp.UpnpIOParticipant;
|
||||
import org.openhab.core.io.transport.upnp.UpnpIOService;
|
||||
import org.openhab.core.library.types.StringType;
|
||||
@ -35,7 +36,9 @@ public abstract class OnkyoUpnpHandler extends BaseThingHandler implements UpnpI
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(OnkyoUpnpHandler.class);
|
||||
|
||||
private UpnpIOService service;
|
||||
private final UpnpIOService service;
|
||||
|
||||
private final URIMetaDataProcessor uriMetaDataProcessor = new URIMetaDataProcessor();
|
||||
|
||||
public OnkyoUpnpHandler(Thing thing, UpnpIOService upnpIOService) {
|
||||
super(thing);
|
||||
@ -45,7 +48,7 @@ public abstract class OnkyoUpnpHandler extends BaseThingHandler implements UpnpI
|
||||
protected void handlePlayUri(Command command) {
|
||||
if (command instanceof StringType) {
|
||||
try {
|
||||
playMedia(command.toString());
|
||||
playMedia(command.toString(), null);
|
||||
|
||||
} catch (IllegalStateException e) {
|
||||
logger.warn("Cannot play URI ({})", e.getMessage());
|
||||
@ -53,7 +56,7 @@ public abstract class OnkyoUpnpHandler extends BaseThingHandler implements UpnpI
|
||||
}
|
||||
}
|
||||
|
||||
public void playMedia(String url) {
|
||||
public void playMedia(String url, AudioStream audioStream) {
|
||||
stop();
|
||||
removeAllTracksFromQueue();
|
||||
|
||||
@ -61,7 +64,7 @@ public abstract class OnkyoUpnpHandler extends BaseThingHandler implements UpnpI
|
||||
url = "x-file-cifs:" + url;
|
||||
}
|
||||
|
||||
setCurrentURI(url, "");
|
||||
setCurrentURI(url, uriMetaDataProcessor.generate(url, audioStream));
|
||||
|
||||
play();
|
||||
}
|
||||
|
@ -0,0 +1,118 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2024 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 static org.jupnp.model.XMLUtil.appendNewElement;
|
||||
|
||||
import java.io.StringWriter;
|
||||
|
||||
import javax.xml.transform.OutputKeys;
|
||||
import javax.xml.transform.Transformer;
|
||||
import javax.xml.transform.TransformerException;
|
||||
import javax.xml.transform.TransformerFactory;
|
||||
import javax.xml.transform.dom.DOMSource;
|
||||
import javax.xml.transform.stream.StreamResult;
|
||||
|
||||
import org.jupnp.transport.impl.PooledXmlProcessor;
|
||||
import org.openhab.core.audio.AudioFormat;
|
||||
import org.openhab.core.audio.AudioStream;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.Element;
|
||||
|
||||
/**
|
||||
* Utility class for building metadata XML for UPnP "SetAVTransportURI" action.
|
||||
*
|
||||
* @author Krzysztof Goworek - Initial contribution
|
||||
*/
|
||||
public class URIMetaDataProcessor extends PooledXmlProcessor {
|
||||
private static final String XML_NAMESPACE_URI = "http://www.w3.org/2000/xmlns/";
|
||||
private static final String DIDL_NAMESPACE_URI = "urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/";
|
||||
private static final String UPNP_NAMESPACE_URI = "urn:schemas-upnp-org:metadata-1-0/upnp/";
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(getClass());
|
||||
|
||||
/**
|
||||
* Generates metadata XML for given audio stream.
|
||||
*
|
||||
* @param url media stream URL
|
||||
* @param audioStream audio format specification
|
||||
* @return generated XML document as {@code String}
|
||||
*/
|
||||
public String generate(String url, AudioStream audioStream) {
|
||||
if (audioStream != null) {
|
||||
try {
|
||||
Document document = this.newDocument();
|
||||
Element rootElement = document.createElementNS(DIDL_NAMESPACE_URI, "DIDL-Lite");
|
||||
document.appendChild(rootElement);
|
||||
rootElement.setAttributeNS(XML_NAMESPACE_URI, "xmlns:upnp", UPNP_NAMESPACE_URI);
|
||||
|
||||
Element itemElement = appendNewElement(document, rootElement, "item");
|
||||
setAttributeIfNotNull(itemElement, "id", audioStream.getId());
|
||||
|
||||
appendNewElement(document, itemElement, "upnp:class", "object.item.audioItem", UPNP_NAMESPACE_URI);
|
||||
Element resourceElement = appendNewElement(document, itemElement, "res", url);
|
||||
setFormatAttributes(resourceElement, audioStream.getFormat());
|
||||
|
||||
return documentToString(document);
|
||||
} catch (Exception e) {
|
||||
logger.warn("Unable to build metadata for {}: {}", url, e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
private void setFormatAttributes(Element resourceElement, AudioFormat format) {
|
||||
setAttributeIfNotNull(resourceElement, "nrAudioChannels", format.getChannels());
|
||||
setAttributeIfNotNull(resourceElement, "sampleFrequency", format.getFrequency());
|
||||
setAttributeIfNotNull(resourceElement, "bitrate", format.getBitRate());
|
||||
setAttributeIfNotNull(resourceElement, "protocolInfo", getProtocolInfo(format));
|
||||
}
|
||||
|
||||
private String getProtocolInfo(AudioFormat format) {
|
||||
String[] protocolInfo = { "http-get", "*", getFormatMimeType(format), "*" };
|
||||
return String.join(":", protocolInfo);
|
||||
}
|
||||
|
||||
private String getFormatMimeType(AudioFormat format) {
|
||||
if (AudioFormat.MP3.isCompatible(format)) {
|
||||
return "audio/mpeg";
|
||||
} else if (AudioFormat.WAV.isCompatible(format)) {
|
||||
return "audio/wav";
|
||||
} else if (AudioFormat.OGG.isCompatible(format)) {
|
||||
return "audio/ogg";
|
||||
} else if (AudioFormat.AAC.isCompatible(format)) {
|
||||
return "audio/aac";
|
||||
} else if (AudioFormat.PCM_SIGNED.isCompatible(format)) {
|
||||
return "audio/pcm";
|
||||
}
|
||||
throw new IllegalArgumentException("Invalid audio format given: " + format);
|
||||
}
|
||||
|
||||
private String documentToString(Document document) throws TransformerException {
|
||||
TransformerFactory transformerFactory = TransformerFactory.newInstance();
|
||||
Transformer transformer = transformerFactory.newTransformer();
|
||||
StringWriter out = new StringWriter();
|
||||
transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
|
||||
transformer.transform(new DOMSource(document), new StreamResult(out));
|
||||
return out.toString();
|
||||
}
|
||||
|
||||
private static void setAttributeIfNotNull(Element element, String name, Object value) {
|
||||
if (value != null) {
|
||||
element.setAttribute(name, value.toString());
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,93 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2024 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 static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.openhab.core.audio.AudioFormat;
|
||||
import org.openhab.core.audio.AudioStream;
|
||||
|
||||
/**
|
||||
* @author Krzysztof Goworek - Initial contribution
|
||||
*/
|
||||
class URIMetaDataProcessorTest {
|
||||
private static final String MP3_STREAM_URL = "http://audio.server.local/audio.mp3";
|
||||
private static final String AAC_STREAM_URL = "http://audio.server.local/audio.aac";
|
||||
|
||||
@Test
|
||||
void generateShouldReturnEmptyStringWhenStreamIsNull() {
|
||||
String result = new URIMetaDataProcessor().generate(MP3_STREAM_URL, null);
|
||||
|
||||
assertEquals("", result);
|
||||
}
|
||||
|
||||
@Test
|
||||
void generateShouldReturnEmptyStringWhenExceptionOccurs() {
|
||||
AudioStream audioStream = new TestAudioStream(AudioFormat.MP3) {
|
||||
@Override
|
||||
public @Nullable String getId() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
};
|
||||
|
||||
String result = new URIMetaDataProcessor().generate(MP3_STREAM_URL, audioStream);
|
||||
|
||||
assertEquals("", result);
|
||||
}
|
||||
|
||||
@Test
|
||||
void generateShouldReturnXMLWithGivenURLAndProtocolInfo() {
|
||||
String result = new URIMetaDataProcessor().generate(MP3_STREAM_URL, new TestAudioStream(AudioFormat.MP3));
|
||||
|
||||
assertTrue(result.matches("<DIDL-Lite.+</DIDL-Lite>"));
|
||||
assertTrue(result.contains(">" + MP3_STREAM_URL + "</res></item></DIDL-Lite>"));
|
||||
assertTrue(result.contains("protocolInfo=\"http-get:*:audio/mpeg:*\""));
|
||||
}
|
||||
|
||||
@Test
|
||||
void generateShouldReturnXMLWithAllFormatAttributes() {
|
||||
AudioFormat audioFormat = new AudioFormat(AudioFormat.CONTAINER_NONE, AudioFormat.CODEC_AAC, true, 8, 192000,
|
||||
48000L, 2);
|
||||
|
||||
String result = new URIMetaDataProcessor().generate(AAC_STREAM_URL, new TestAudioStream(audioFormat));
|
||||
|
||||
assertTrue(result.contains("nrAudioChannels=\"2\""));
|
||||
assertTrue(result.contains("sampleFrequency=\"48000\""));
|
||||
assertTrue(result.contains("bitrate=\"192000\""));
|
||||
assertTrue(result.contains("protocolInfo=\"http-get:*:audio/aac:*\""));
|
||||
}
|
||||
|
||||
@NonNullByDefault
|
||||
private static class TestAudioStream extends AudioStream {
|
||||
private final AudioFormat audioFormat;
|
||||
|
||||
private TestAudioStream(AudioFormat audioFormat) {
|
||||
this.audioFormat = audioFormat;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AudioFormat getFormat() {
|
||||
return audioFormat;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read() throws IOException {
|
||||
throw new IOException("Unsupported operation");
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user