2 * Copyright (c) 2010-2023 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
7 * This program and the accompanying materials are made available under the
8 * terms of the Eclipse Public License 2.0 which is available at
9 * http://www.eclipse.org/legal/epl-2.0
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.onkyo.internal.handler;
15 import java.util.HashMap;
16 import java.util.HashSet;
17 import java.util.Locale;
21 import org.eclipse.jdt.annotation.Nullable;
22 import org.openhab.binding.onkyo.internal.OnkyoBindingConstants;
23 import org.openhab.core.audio.AudioFormat;
24 import org.openhab.core.audio.AudioHTTPServer;
25 import org.openhab.core.audio.AudioSink;
26 import org.openhab.core.audio.AudioStream;
27 import org.openhab.core.audio.FixedLengthAudioStream;
28 import org.openhab.core.audio.URLAudioStream;
29 import org.openhab.core.audio.UnsupportedAudioFormatException;
30 import org.openhab.core.audio.UnsupportedAudioStreamException;
31 import org.openhab.core.io.transport.upnp.UpnpIOParticipant;
32 import org.openhab.core.io.transport.upnp.UpnpIOService;
33 import org.openhab.core.library.types.StringType;
34 import org.openhab.core.thing.Thing;
35 import org.openhab.core.thing.binding.BaseThingHandler;
36 import org.openhab.core.types.Command;
37 import org.slf4j.Logger;
38 import org.slf4j.LoggerFactory;
41 * * The {@link UpnpAudioSinkHandler} is a base class for ThingHandlers for devices which support UPnP playback. It
42 * implements the AudioSink interface.
43 * This will allow to register the derived ThingHandler to be registered as an AudioSink in the framework.
45 * @author Paul Frank - Initial contribution
47 public abstract class UpnpAudioSinkHandler extends BaseThingHandler implements AudioSink, UpnpIOParticipant {
49 private static final Set<AudioFormat> SUPPORTED_FORMATS = new HashSet<>();
50 private static final Set<Class<? extends AudioStream>> SUPPORTED_STREAMS = new HashSet<>();
53 SUPPORTED_FORMATS.add(AudioFormat.WAV);
54 SUPPORTED_FORMATS.add(AudioFormat.MP3);
56 SUPPORTED_STREAMS.add(AudioStream.class);
59 private final Logger logger = LoggerFactory.getLogger(getClass());
61 private AudioHTTPServer audioHTTPServer;
62 private String callbackUrl;
63 private UpnpIOService service;
65 public UpnpAudioSinkHandler(Thing thing, UpnpIOService upnpIOService, AudioHTTPServer audioHTTPServer,
68 this.audioHTTPServer = audioHTTPServer;
69 this.callbackUrl = callbackUrl;
70 if (upnpIOService != null) {
71 this.service = upnpIOService;
75 protected void handlePlayUri(Command command) {
76 if (command != null && command instanceof StringType) {
78 playMedia(command.toString());
80 } catch (IllegalStateException e) {
81 logger.warn("Cannot play URI ({})", e.getMessage());
86 private void playMedia(String url) {
88 removeAllTracksFromQueue();
90 if (!url.startsWith("x-") && (!url.startsWith("http"))) {
91 url = "x-file-cifs:" + url;
94 setCurrentURI(url, "");
100 public Set<AudioFormat> getSupportedFormats() {
101 return SUPPORTED_FORMATS;
105 public Set<Class<? extends AudioStream>> getSupportedStreams() {
106 return SUPPORTED_STREAMS;
109 private void stop() {
110 Map<String, String> inputs = new HashMap<>();
111 inputs.put("InstanceID", "0");
113 Map<String, String> result = service.invokeAction(this, "AVTransport", "Stop", inputs);
115 for (String variable : result.keySet()) {
116 this.onValueReceived(variable, result.get(variable), "AVTransport");
120 private void play() {
121 Map<String, String> inputs = new HashMap<>();
122 inputs.put("InstanceID", "0");
123 inputs.put("Speed", "1");
124 Map<String, String> result = service.invokeAction(this, "AVTransport", "Play", inputs);
126 for (String variable : result.keySet()) {
127 this.onValueReceived(variable, result.get(variable), "AVTransport");
131 private void removeAllTracksFromQueue() {
132 Map<String, String> inputs = new HashMap<>();
133 inputs.put("InstanceID", "0");
135 Map<String, String> result = service.invokeAction(this, "AVTransport", "RemoveAllTracksFromQueue", inputs);
137 for (String variable : result.keySet()) {
138 this.onValueReceived(variable, result.get(variable), "AVTransport");
142 private void setCurrentURI(String uri, String uriMetaData) {
143 if (uri != null && uriMetaData != null) {
144 Map<String, String> inputs = new HashMap<>();
147 inputs.put("InstanceID", "0");
148 inputs.put("CurrentURI", uri);
149 inputs.put("CurrentURIMetaData", uriMetaData);
150 } catch (NumberFormatException ex) {
151 logger.error("Action Invalid Value Format Exception {}", ex.getMessage());
154 Map<String, String> result = service.invokeAction(this, "AVTransport", "SetAVTransportURI", inputs);
156 for (String variable : result.keySet()) {
157 this.onValueReceived(variable, result.get(variable), "AVTransport");
163 public String getId() {
164 return getThing().getUID().toString();
168 public String getLabel(Locale locale) {
169 return getThing().getLabel();
173 public void process(@Nullable AudioStream audioStream)
174 throws UnsupportedAudioFormatException, UnsupportedAudioStreamException {
175 if (audioStream == null) {
181 if (audioStream instanceof URLAudioStream) {
182 // it is an external URL, the speaker can access it itself and play it.
183 URLAudioStream urlAudioStream = (URLAudioStream) audioStream;
184 url = urlAudioStream.getURL();
186 if (callbackUrl != null) {
188 if (audioStream instanceof FixedLengthAudioStream) {
189 // we serve it on our own HTTP server
190 relativeUrl = audioHTTPServer.serve((FixedLengthAudioStream) audioStream, 20);
192 relativeUrl = audioHTTPServer.serve(audioStream);
194 url = callbackUrl + relativeUrl;
196 logger.warn("We do not have any callback url, so onkyo cannot play the audio stream!");
205 public String getUDN() {
206 return (String) this.getConfig().get(OnkyoBindingConstants.UDN_PARAMETER);
210 public void onValueReceived(String variable, String value, String service) {
211 logger.debug("received variable {} with value {} from service {}", variable, value, service);
215 public void onServiceSubscribed(String service, boolean succeeded) {
219 public void onStatusChanged(boolean status) {