forked from openhab/openhab-addons
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[Onkyo] Set "CurrentURIMetaData" for "SetAVTransportURI" action (open…
…hab#17755) (openhab#17770) * [Onkyo] Set "CurrentURIMetaData" for "SetAVTransportURI" action (openhab#17755) Signed-off-by: Krzysztof Goworek <[email protected]>
- Loading branch information
Showing
4 changed files
with
222 additions
and
8 deletions.
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
118 changes: 118 additions & 0 deletions
118
....onkyo/src/main/java/org/openhab/binding/onkyo/internal/handler/URIMetaDataProcessor.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,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()); | ||
} | ||
} | ||
} |
93 changes: 93 additions & 0 deletions
93
...yo/src/test/java/org/openhab/binding/onkyo/internal/handler/URIMetaDataProcessorTest.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,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"); | ||
} | ||
} | ||
} |