]> git.basschouten.com Git - openhab-addons.git/blob
2a031a4bd2defcd3ad074fb111dc5df28b85e0ef
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2021 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.chromecast.internal;
14
15 import static org.openhab.binding.chromecast.internal.ChromecastBindingConstants.*;
16 import static org.openhab.core.thing.ThingStatusDetail.COMMUNICATION_ERROR;
17
18 import java.io.IOException;
19
20 import org.eclipse.jdt.annotation.NonNullByDefault;
21 import org.eclipse.jdt.annotation.Nullable;
22 import org.openhab.core.library.types.IncreaseDecreaseType;
23 import org.openhab.core.library.types.NextPreviousType;
24 import org.openhab.core.library.types.OnOffType;
25 import org.openhab.core.library.types.PercentType;
26 import org.openhab.core.library.types.PlayPauseType;
27 import org.openhab.core.library.types.StringType;
28 import org.openhab.core.thing.ChannelUID;
29 import org.openhab.core.thing.ThingStatus;
30 import org.openhab.core.types.Command;
31 import org.openhab.core.types.RefreshType;
32 import org.slf4j.Logger;
33 import org.slf4j.LoggerFactory;
34
35 import su.litvak.chromecast.api.v2.Application;
36 import su.litvak.chromecast.api.v2.ChromeCast;
37 import su.litvak.chromecast.api.v2.MediaStatus;
38 import su.litvak.chromecast.api.v2.Status;
39
40 /**
41  * This sends the various commands to the Chromecast.
42  *
43  * @author Jason Holmes - Initial contribution
44  */
45 @NonNullByDefault
46 public class ChromecastCommander {
47     private final Logger logger = LoggerFactory.getLogger(ChromecastCommander.class);
48
49     private final ChromeCast chromeCast;
50     private final ChromecastScheduler scheduler;
51     private final ChromecastStatusUpdater statusUpdater;
52
53     private static final int VOLUMESTEP = 10;
54
55     public ChromecastCommander(ChromeCast chromeCast, ChromecastScheduler scheduler,
56             ChromecastStatusUpdater statusUpdater) {
57         this.chromeCast = chromeCast;
58         this.scheduler = scheduler;
59         this.statusUpdater = statusUpdater;
60     }
61
62     public void handleCommand(final ChannelUID channelUID, final Command command) {
63         if (command instanceof RefreshType) {
64             scheduler.scheduleRefresh();
65             return;
66         }
67
68         switch (channelUID.getId()) {
69             case CHANNEL_CONTROL:
70                 handleControl(command);
71                 break;
72             case CHANNEL_STOP:
73                 handleStop(command);
74                 break;
75             case CHANNEL_VOLUME:
76                 handleVolume(command);
77                 break;
78             case CHANNEL_MUTE:
79                 handleMute(command);
80                 break;
81             case CHANNEL_PLAY_URI:
82                 handlePlayUri(command);
83                 break;
84             default:
85                 logger.debug("Received command {} for unknown channel: {}", command, channelUID);
86                 break;
87         }
88     }
89
90     public void handleRefresh() {
91         if (!chromeCast.isConnected()) {
92             scheduler.cancelRefresh();
93             scheduler.scheduleConnect();
94             return;
95         }
96
97         Status status;
98         try {
99             status = chromeCast.getStatus();
100             statusUpdater.processStatusUpdate(status);
101
102             if (status == null) {
103                 scheduler.cancelRefresh();
104             }
105         } catch (IOException ex) {
106             logger.debug("Failed to request status: {}", ex.getMessage());
107             statusUpdater.updateStatus(ThingStatus.OFFLINE, COMMUNICATION_ERROR, ex.getMessage());
108             scheduler.cancelRefresh();
109             return;
110         }
111
112         try {
113             if (status != null && status.getRunningApp() != null) {
114                 MediaStatus mediaStatus = chromeCast.getMediaStatus();
115                 statusUpdater.updateMediaStatus(mediaStatus);
116
117                 if (mediaStatus != null && mediaStatus.playerState == MediaStatus.PlayerState.IDLE
118                         && mediaStatus.idleReason != null
119                         && mediaStatus.idleReason != MediaStatus.IdleReason.INTERRUPTED) {
120                     stopMediaPlayerApp();
121                 }
122             }
123         } catch (IOException ex) {
124             logger.debug("Failed to request media status with a running app: {}", ex.getMessage());
125             // We were just able to request status, so let's not put the device OFFLINE.
126         }
127     }
128
129     private void handlePlayUri(Command command) {
130         if (command instanceof StringType) {
131             playMedia(null, command.toString(), null);
132         }
133     }
134
135     private void handleControl(final Command command) {
136         try {
137             if (command instanceof NextPreviousType) {
138                 // I can't find a way to control next/previous from the API. The Google app doesn't seem to
139                 // allow it either, so I suspect there isn't a way.
140                 logger.info("{} command not yet implemented", command);
141                 return;
142             }
143
144             Application app = chromeCast.getRunningApp();
145             statusUpdater.updateStatus(ThingStatus.ONLINE);
146             if (app == null) {
147                 logger.debug("{} command ignored because media player app is not running", command);
148                 return;
149             }
150
151             if (command instanceof PlayPauseType) {
152                 MediaStatus mediaStatus = chromeCast.getMediaStatus();
153                 logger.debug("mediaStatus {}", mediaStatus);
154                 if (mediaStatus == null || mediaStatus.playerState == MediaStatus.PlayerState.IDLE) {
155                     logger.debug("{} command ignored because media is not loaded", command);
156                     return;
157                 }
158
159                 final PlayPauseType playPause = (PlayPauseType) command;
160                 if (playPause == PlayPauseType.PLAY) {
161                     chromeCast.play();
162                 } else if (playPause == PlayPauseType.PAUSE
163                         && ((mediaStatus.supportedMediaCommands & 0x00000001) == 0x1)) {
164                     chromeCast.pause();
165                 } else {
166                     logger.info("{} command not supported by current media", command);
167                 }
168             }
169         } catch (final IOException e) {
170             logger.debug("{} command failed: {}", command, e.getMessage());
171             statusUpdater.updateStatus(ThingStatus.OFFLINE, COMMUNICATION_ERROR, e.getMessage());
172         }
173     }
174
175     public void handleStop(final Command command) {
176         if (command == OnOffType.ON) {
177             try {
178                 chromeCast.stopApp();
179                 statusUpdater.updateStatus(ThingStatus.ONLINE);
180             } catch (final IOException ex) {
181                 logger.debug("{} command failed: {}", command, ex.getMessage());
182                 statusUpdater.updateStatus(ThingStatus.OFFLINE, COMMUNICATION_ERROR, ex.getMessage());
183             }
184         }
185     }
186
187     public void handleVolume(final Command command) {
188         if (command instanceof PercentType) {
189             setVolumeInternal((PercentType) command);
190         } else if (command == IncreaseDecreaseType.INCREASE) {
191             setVolumeInternal(new PercentType(
192                     Math.min(statusUpdater.getVolume().intValue() + VOLUMESTEP, PercentType.HUNDRED.intValue())));
193         } else if (command == IncreaseDecreaseType.DECREASE) {
194             setVolumeInternal(new PercentType(
195                     Math.max(statusUpdater.getVolume().intValue() - VOLUMESTEP, PercentType.ZERO.intValue())));
196         }
197     }
198
199     private void setVolumeInternal(PercentType volume) {
200         try {
201             chromeCast.setVolumeByIncrement(volume.floatValue() / 100);
202             statusUpdater.updateStatus(ThingStatus.ONLINE);
203         } catch (final IOException ex) {
204             logger.debug("Set volume failed: {}", ex.getMessage());
205             statusUpdater.updateStatus(ThingStatus.OFFLINE, COMMUNICATION_ERROR, ex.getMessage());
206         }
207     }
208
209     private void handleMute(final Command command) {
210         if (command instanceof OnOffType) {
211             final boolean mute = command == OnOffType.ON;
212             try {
213                 chromeCast.setMuted(mute);
214                 statusUpdater.updateStatus(ThingStatus.ONLINE);
215             } catch (final IOException ex) {
216                 logger.debug("Mute/unmute volume failed: {}", ex.getMessage());
217                 statusUpdater.updateStatus(ThingStatus.OFFLINE, COMMUNICATION_ERROR, ex.getMessage());
218             }
219         }
220     }
221
222     public void playMedia(@Nullable String title, @Nullable String url, @Nullable String mimeType) {
223         try {
224             if (chromeCast.isAppAvailable(MEDIA_PLAYER)) {
225                 if (!chromeCast.isAppRunning(MEDIA_PLAYER)) {
226                     final Application app = chromeCast.launchApp(MEDIA_PLAYER);
227                     statusUpdater.setAppSessionId(app.sessionId);
228                     logger.debug("Application launched: {}", app);
229                 }
230                 if (url != null) {
231                     // If the current track is paused, launching a new request results in nothing happening, therefore
232                     // resume current track.
233                     MediaStatus ms = chromeCast.getMediaStatus();
234                     if (ms != null && MediaStatus.PlayerState.PAUSED == ms.playerState && url.equals(ms.media.url)) {
235                         logger.debug("Current stream paused, resuming");
236                         chromeCast.play();
237                     } else {
238                         chromeCast.load(title, null, url, mimeType);
239                     }
240                 }
241             } else {
242                 logger.warn("Missing media player app - cannot process media.");
243             }
244             statusUpdater.updateStatus(ThingStatus.ONLINE);
245         } catch (final IOException e) {
246             logger.debug("Failed playing media: {}", e.getMessage());
247             statusUpdater.updateStatus(ThingStatus.OFFLINE, COMMUNICATION_ERROR, e.getMessage());
248         }
249     }
250
251     private void stopMediaPlayerApp() {
252         try {
253             Application app = chromeCast.getRunningApp();
254             if (app.id.equals(MEDIA_PLAYER) && app.sessionId.equals(statusUpdater.getAppSessionId())) {
255                 chromeCast.stopApp();
256                 logger.debug("Media player app stopped");
257             }
258         } catch (final IOException e) {
259             logger.debug("Failed stopping media player app", e);
260         }
261     }
262 }