]> git.basschouten.com Git - openhab-addons.git/blob
01b376283460bf2ec6bde9ecbff29569f7af840d
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2020 Contributors to the openHAB project
3  *
4  * See the NOTICE file(s) distributed with this work for additional
5  * information.
6  *
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
10  *
11  * SPDX-License-Identifier: EPL-2.0
12  */
13 package org.openhab.binding.yamahareceiver.internal.protocol.xml;
14
15 import static org.openhab.binding.yamahareceiver.internal.YamahaReceiverBindingConstants.Inputs.INPUT_SPOTIFY;
16 import static org.openhab.binding.yamahareceiver.internal.protocol.xml.XMLConstants.Commands.PLAYBACK_STATUS_CMD;
17 import static org.openhab.binding.yamahareceiver.internal.protocol.xml.XMLProtocolService.getResponse;
18 import static org.openhab.binding.yamahareceiver.internal.protocol.xml.XMLUtils.*;
19
20 import java.io.IOException;
21
22 import org.apache.commons.lang.StringUtils;
23 import org.openhab.binding.yamahareceiver.internal.config.YamahaBridgeConfig;
24 import org.openhab.binding.yamahareceiver.internal.protocol.AbstractConnection;
25 import org.openhab.binding.yamahareceiver.internal.protocol.InputWithPlayControl;
26 import org.openhab.binding.yamahareceiver.internal.protocol.ReceivedMessageParseException;
27 import org.openhab.binding.yamahareceiver.internal.state.DeviceInformationState;
28 import org.openhab.binding.yamahareceiver.internal.state.PlayInfoState;
29 import org.openhab.binding.yamahareceiver.internal.state.PlayInfoStateListener;
30 import org.openhab.binding.yamahareceiver.internal.state.PresetInfoState;
31 import org.slf4j.LoggerFactory;
32 import org.w3c.dom.Node;
33
34 /**
35  * This class implements the Yamaha Receiver protocol related to navigation functionally. USB, NET_RADIO, IPOD and
36  * other inputs are using the same way of playback control.
37  * <p>
38  * The XML nodes <Play_Info> and <Play_Control> are used.
39  * <p>
40  * Example:
41  * <p>
42  * InputWithPlayControl menu = new InputWithPlayControl("NET_RADIO", comObject);
43  * menu.goToPath(menuDir);
44  * menu.selectItem(stationName);
45  * <p>
46  * No state will be saved in here, but in {@link PlayInfoState} and
47  * {@link PresetInfoState} instead.
48  *
49  * @author David Graeff
50  * @author Tomasz Maruszak - Spotify support, refactoring
51  */
52 public class InputWithPlayControlXML extends AbstractInputControlXML implements InputWithPlayControl {
53
54     private final PlayInfoStateListener observer;
55     private final YamahaBridgeConfig bridgeConfig;
56
57     protected CommandTemplate playCmd = new CommandTemplate("<Play_Control><Playback>%s</Playback></Play_Control>",
58             "Play_Info/Playback_Info");
59     protected CommandTemplate skipCmd = new CommandTemplate("<Play_Control><Playback>%s</Playback></Play_Control>");
60     protected String skipForwardValue = "Skip Fwd";
61     protected String skipBackwardValue = "Skip Rev";
62
63     /**
64      * Create a InputWithPlayControl object for altering menu positions and requesting current menu information as well
65      * as controlling the playback and choosing a preset item.
66      *
67      * @param inputID The input ID like USB or NET_RADIO.
68      * @param com The Yamaha communication object to send http requests.
69      */
70     public InputWithPlayControlXML(String inputID, AbstractConnection com, PlayInfoStateListener observer,
71             YamahaBridgeConfig bridgeConfig, DeviceInformationState deviceInformationState) {
72         super(LoggerFactory.getLogger(InputWithPlayControlXML.class), inputID, com, deviceInformationState);
73
74         this.observer = observer;
75         this.bridgeConfig = bridgeConfig;
76
77         this.applyModelVariations();
78     }
79
80     /**
81      * Apply command changes to ensure compatibility with all supported models
82      */
83     protected void applyModelVariations() {
84         if (inputFeatureDescriptor != null) {
85             // For RX-V3900
86             if (inputFeatureDescriptor.hasCommandEnding("Play_Control,Play")) {
87                 playCmd = new CommandTemplate("<Play_Control><Play>%s</Play></Play_Control>", "Play_Info/Status");
88                 logger.debug("Input {} - adjusting command to: {}", inputElement, playCmd);
89             }
90             // For RX-V3900
91             if (inputFeatureDescriptor.hasCommandEnding("Play_Control,Skip")) {
92                 // For RX-V3900 the command value is also different
93                 skipForwardValue = "Fwd";
94                 skipBackwardValue = "Rev";
95
96                 skipCmd = new CommandTemplate("<Play_Control><Skip>%s</Skip></Play_Control>");
97                 logger.debug("Input {} - adjusting command to: {}", inputElement, skipCmd);
98             }
99         }
100     }
101
102     /**
103      * Start the playback of the content which is usually selected by the means of the Navigation control class or
104      * which has been stopped by stop().
105      *
106      * @throws Exception
107      */
108     @Override
109     public void play() throws IOException, ReceivedMessageParseException {
110         sendCommand(playCmd.apply("Play"));
111     }
112
113     /**
114      * Stop the currently playing content. Use start() to start again.
115      *
116      * @throws Exception
117      */
118     @Override
119     public void stop() throws IOException, ReceivedMessageParseException {
120         sendCommand(playCmd.apply("Stop"));
121     }
122
123     /**
124      * Pause the currently playing content. This is not available for streaming content like on NET_RADIO.
125      *
126      * @throws Exception
127      */
128     @Override
129     public void pause() throws IOException, ReceivedMessageParseException {
130         sendCommand(playCmd.apply("Pause"));
131     }
132
133     /**
134      * Skip forward. This is not available for streaming content like on NET_RADIO.
135      *
136      * @throws Exception
137      */
138     @Override
139     public void skipFF() throws IOException, ReceivedMessageParseException {
140         if (INPUT_SPOTIFY.equals(inputID)) {
141             logger.warn("Command skip forward is not supported for input {}", inputID);
142             return;
143         }
144         sendCommand(skipCmd.apply(">>|"));
145     }
146
147     /**
148      * Skip reverse. This is not available for streaming content like on NET_RADIO.
149      *
150      * @throws Exception
151      */
152     @Override
153     public void skipREV() throws IOException, ReceivedMessageParseException {
154         if (INPUT_SPOTIFY.equals(inputID)) {
155             logger.warn("Command skip reverse is not supported for input {}", inputID);
156             return;
157         }
158         sendCommand(skipCmd.apply("|<<"));
159     }
160
161     /**
162      * Next track. This is not available for streaming content like on NET_RADIO.
163      *
164      * @throws Exception
165      */
166     @Override
167     public void nextTrack() throws IOException, ReceivedMessageParseException {
168         sendCommand(skipCmd.apply(skipForwardValue));
169     }
170
171     /**
172      * Previous track. This is not available for streaming content like on NET_RADIO.
173      *
174      * @throws Exception
175      */
176     @Override
177     public void previousTrack() throws IOException, ReceivedMessageParseException {
178         sendCommand(skipCmd.apply(skipBackwardValue));
179     }
180
181     /**
182      * Sends a playback command to the AVR. After command is invoked, the state is also being refreshed.
183      *
184      * @param command - the protocol level command name
185      * @throws IOException
186      * @throws ReceivedMessageParseException
187      */
188     private void sendCommand(String command) throws IOException, ReceivedMessageParseException {
189         comReference.get().send(wrInput(command));
190         update();
191     }
192
193     /**
194      * Updates the playback information
195      *
196      * @throws Exception
197      */
198     @Override
199     public void update() throws IOException, ReceivedMessageParseException {
200         if (observer == null) {
201             return;
202         }
203
204         // <YAMAHA_AV rsp="GET" RC="0">
205         // <Spotify>
206         // <Play_Info>
207         // <Feature_Availability>Ready</Feature_Availability>
208         // <Playback_Info>Play</Playback_Info>
209         // <Meta_Info>
210         // <Artist>Way Out West</Artist>
211         // <Album>Tuesday Maybe</Album>
212         // <Track>Tuesday Maybe</Track>
213         // </Meta_Info>
214         // <Album_ART>
215         // <URL>/YamahaRemoteControl/AlbumART/AlbumART3929.jpg</URL>
216         // <ID>39290</ID>
217         // <Format>JPEG</Format>
218         // </Album_ART>
219         // <Input_Logo>
220         // <URL_S>/YamahaRemoteControl/Logos/logo005.png</URL_S>
221         // <URL_M></URL_M>
222         // <URL_L></URL_L>
223         // </Input_Logo>
224         // </Play_Info>
225         // </Spotify>
226         // </YAMAHA_AV>
227
228         AbstractConnection con = comReference.get();
229         Node node = getResponse(con, wrInput(PLAYBACK_STATUS_CMD), inputElement);
230
231         PlayInfoState msg = new PlayInfoState();
232
233         msg.playbackMode = getNodeContentOrDefault(node, playCmd.getPath(), msg.playbackMode);
234
235         // elements for these are named differently per model and per input, so we try to match any known element
236         msg.station = getAnyNodeContentOrDefault(node, msg.station, "Play_Info/Meta_Info/Radio_Text_A",
237                 "Play_Info/Meta_Info/Station", "Play_Info/RDS/Program_Service");
238         msg.artist = getAnyNodeContentOrDefault(node, msg.artist, "Play_Info/Meta_Info/Artist",
239                 "Play_Info/Title/Artist", "Play_Info/RDS/Radio_Text_A");
240         msg.album = getAnyNodeContentOrDefault(node, msg.album, "Play_Info/Meta_Info/Album", "Play_Info/Title/Album",
241                 "Play_Info/RDS/Program_Type");
242         msg.song = getAnyNodeContentOrDefault(node, msg.song, "Play_Info/Meta_Info/Track", "Play_Info/Meta_Info/Song",
243                 "Play_Info/Title/Song", "Play_Info/RDS/Radio_Text_B");
244
245         // Spotify and NET RADIO input supports song cover image (at least on RX-S601D)
246         String songImageUrl = getNodeContentOrEmpty(node, "Play_Info/Album_ART/URL");
247         msg.songImageUrl = StringUtils.isNotEmpty(songImageUrl)
248                 ? String.format("http://%s%s", con.getHost(), songImageUrl)
249                 : bridgeConfig.getAlbumUrl();
250
251         logger.trace("Playback: {}, Station: {}, Artist: {}, Album: {}, Song: {}, SongImageUrl: {}", msg.playbackMode,
252                 msg.station, msg.artist, msg.album, msg.song, msg.songImageUrl);
253
254         observer.playInfoUpdated(msg);
255     }
256 }