]> git.basschouten.com Git - openhab-addons.git/blob
6abf53c79e076a80aa3dc5d4971efd43f92ed6cc
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2022 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             Application app = chromeCast.getRunningApp();
138             statusUpdater.updateStatus(ThingStatus.ONLINE);
139             if (app == null) {
140                 logger.debug("{} command ignored because media player app is not running", command);
141                 return;
142             }
143
144             if (command instanceof PlayPauseType) {
145                 MediaStatus mediaStatus = chromeCast.getMediaStatus();
146                 logger.debug("mediaStatus {}", mediaStatus);
147                 if (mediaStatus == null || mediaStatus.playerState == MediaStatus.PlayerState.IDLE) {
148                     logger.debug("{} command ignored because media is not loaded", command);
149                     return;
150                 }
151
152                 final PlayPauseType playPause = (PlayPauseType) command;
153                 if (playPause == PlayPauseType.PLAY) {
154                     chromeCast.play();
155                 } else if (playPause == PlayPauseType.PAUSE
156                         && ((mediaStatus.supportedMediaCommands & 0x00000001) == 0x1)) {
157                     chromeCast.pause();
158                 } else {
159                     logger.info("{} command not supported by current media", command);
160                 }
161             }
162
163             if (command instanceof NextPreviousType) {
164                 // Next is implemented by seeking to the end of the current media
165                 if (command == NextPreviousType.NEXT) {
166
167                     Double duration = statusUpdater.getLastDuration();
168                     if (duration != null) {
169                         chromeCast.seek(duration.doubleValue() - 5);
170                     } else {
171                         logger.info("{} command failed - unknown media duration", command);
172                     }
173                 } else {
174                     logger.info("{} command not yet implemented", command);
175                     return;
176                 }
177             }
178
179         } catch (final IOException e) {
180             logger.debug("{} command failed: {}", command, e.getMessage());
181             statusUpdater.updateStatus(ThingStatus.OFFLINE, COMMUNICATION_ERROR, e.getMessage());
182         }
183     }
184
185     public void handleStop(final Command command) {
186         if (command == OnOffType.ON) {
187             try {
188                 chromeCast.stopApp();
189                 statusUpdater.updateStatus(ThingStatus.ONLINE);
190             } catch (final IOException ex) {
191                 logger.debug("{} command failed: {}", command, ex.getMessage());
192                 statusUpdater.updateStatus(ThingStatus.OFFLINE, COMMUNICATION_ERROR, ex.getMessage());
193             }
194         }
195     }
196
197     public void handleVolume(final Command command) {
198         if (command instanceof PercentType) {
199             setVolumeInternal((PercentType) command);
200         } else if (command == IncreaseDecreaseType.INCREASE) {
201             setVolumeInternal(new PercentType(
202                     Math.min(statusUpdater.getVolume().intValue() + VOLUMESTEP, PercentType.HUNDRED.intValue())));
203         } else if (command == IncreaseDecreaseType.DECREASE) {
204             setVolumeInternal(new PercentType(
205                     Math.max(statusUpdater.getVolume().intValue() - VOLUMESTEP, PercentType.ZERO.intValue())));
206         }
207     }
208
209     private void setVolumeInternal(PercentType volume) {
210         try {
211             chromeCast.setVolumeByIncrement(volume.floatValue() / 100);
212             statusUpdater.updateStatus(ThingStatus.ONLINE);
213         } catch (final IOException ex) {
214             logger.debug("Set volume failed: {}", ex.getMessage());
215             statusUpdater.updateStatus(ThingStatus.OFFLINE, COMMUNICATION_ERROR, ex.getMessage());
216         }
217     }
218
219     private void handleMute(final Command command) {
220         if (command instanceof OnOffType) {
221             final boolean mute = command == OnOffType.ON;
222             try {
223                 chromeCast.setMuted(mute);
224                 statusUpdater.updateStatus(ThingStatus.ONLINE);
225             } catch (final IOException ex) {
226                 logger.debug("Mute/unmute volume failed: {}", ex.getMessage());
227                 statusUpdater.updateStatus(ThingStatus.OFFLINE, COMMUNICATION_ERROR, ex.getMessage());
228             }
229         }
230     }
231
232     public void playMedia(@Nullable String title, @Nullable String url, @Nullable String mimeType) {
233         try {
234             if (chromeCast.isAppAvailable(MEDIA_PLAYER)) {
235                 if (!chromeCast.isAppRunning(MEDIA_PLAYER)) {
236                     final Application app = chromeCast.launchApp(MEDIA_PLAYER);
237                     statusUpdater.setAppSessionId(app.sessionId);
238                     logger.debug("Application launched: {}", app);
239                 }
240                 if (url != null) {
241                     // If the current track is paused, launching a new request results in nothing happening, therefore
242                     // resume current track.
243                     MediaStatus ms = chromeCast.getMediaStatus();
244                     if (ms != null && MediaStatus.PlayerState.PAUSED == ms.playerState && url.equals(ms.media.url)) {
245                         logger.debug("Current stream paused, resuming");
246                         chromeCast.play();
247                     } else {
248                         chromeCast.load(title, null, url, mimeType);
249                     }
250                 }
251             } else {
252                 logger.warn("Missing media player app - cannot process media.");
253             }
254             statusUpdater.updateStatus(ThingStatus.ONLINE);
255         } catch (final IOException e) {
256             logger.debug("Failed playing media: {}", e.getMessage());
257             statusUpdater.updateStatus(ThingStatus.OFFLINE, COMMUNICATION_ERROR, e.getMessage());
258         }
259     }
260
261     private void stopMediaPlayerApp() {
262         try {
263             Application app = chromeCast.getRunningApp();
264             if (app.id.equals(MEDIA_PLAYER) && app.sessionId.equals(statusUpdater.getAppSessionId())) {
265                 chromeCast.stopApp();
266                 logger.debug("Media player app stopped");
267             }
268         } catch (final IOException e) {
269             logger.debug("Failed stopping media player app", e);
270         }
271     }
272 }