]> git.basschouten.com Git - openhab-addons.git/blob
143e73eae01c1a2a169dd36d984ef5f59661c7f0
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2024 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.squeezebox.internal.handler;
14
15 import java.io.Closeable;
16
17 import org.openhab.binding.squeezebox.internal.utils.SqueezeBoxTimeoutException;
18 import org.openhab.core.library.types.StringType;
19 import org.slf4j.Logger;
20 import org.slf4j.LoggerFactory;
21
22 /***
23  * Utility class to play a notification message. The message is added
24  * to the playlist, played and the previous state of the playlist and the
25  * player is restored.
26  *
27  * @author Mark Hilbush - Initial Contribution
28  * @author Patrik Gfeller - Utility class added reduce complexity and length of SqueezeBoxPlayerHandler.java
29  * @author Mark Hilbush - Convert sound notification volume from channel to config parameter
30  *
31  */
32 class SqueezeBoxNotificationPlayer implements Closeable {
33     private final Logger logger = LoggerFactory.getLogger(SqueezeBoxNotificationPlayer.class);
34
35     // An exception is thrown if we do not receive an acknowledge
36     // for a volume set command in the given amount of time [s].
37     private static final int VOLUME_COMMAND_TIMEOUT = 4;
38
39     // We expect the media server to acknowledge a playlist command.
40     // An exception is thrown if the playlist command was not processed
41     // after the defined amount in [s]
42     private static final int PLAYLIST_COMMAND_TIMEOUT = 5;
43
44     private final SqueezeBoxPlayerState playerState;
45     private final SqueezeBoxPlayerHandler squeezeBoxPlayerHandler;
46     private final SqueezeBoxServerHandler squeezeBoxServerHandler;
47     private final StringType uri;
48     private final String mac;
49
50     boolean playlistModified;
51
52     private int notificationMessagePlaylistsIndex;
53
54     SqueezeBoxNotificationPlayer(SqueezeBoxPlayerHandler squeezeBoxPlayerHandler,
55             SqueezeBoxServerHandler squeezeBoxServerHandler, StringType uri) {
56         this.squeezeBoxPlayerHandler = squeezeBoxPlayerHandler;
57         this.squeezeBoxServerHandler = squeezeBoxServerHandler;
58         this.mac = squeezeBoxPlayerHandler.getMac();
59         this.uri = uri;
60         this.playerState = new SqueezeBoxPlayerState(squeezeBoxPlayerHandler);
61     }
62
63     void play() throws InterruptedException, SqueezeBoxTimeoutException {
64         if (squeezeBoxServerHandler == null) {
65             logger.warn("Server handler is null");
66             return;
67         }
68         setupPlayerForNotification();
69         addNotificationMessageToPlaylist();
70         playNotification();
71     }
72
73     @Override
74     public void close() {
75         restorePlayerState();
76     }
77
78     private void setupPlayerForNotification() throws InterruptedException, SqueezeBoxTimeoutException {
79         logger.debug("Setting up player for notification");
80         if (!playerState.isPoweredOn()) {
81             logger.debug("Powering on the player");
82             squeezeBoxServerHandler.powerOn(mac);
83         }
84         if (playerState.isShuffling()) {
85             logger.debug("Turning off shuffle");
86             squeezeBoxServerHandler.setShuffleMode(mac, 0);
87         }
88         if (playerState.isRepeating()) {
89             logger.debug("Turning off repeat");
90             squeezeBoxServerHandler.setRepeatMode(mac, 0);
91         }
92         if (playerState.isPlaying()) {
93             squeezeBoxServerHandler.stop(mac);
94         }
95         setVolume(squeezeBoxPlayerHandler.getNotificationSoundVolume().intValue());
96     }
97
98     /**
99      * Sends a volume set command if target volume is not equal to the current volume.
100      *
101      * @param requestedVolume The requested volume value.
102      * @throws InterruptedException Thread interrupted during while we were waiting for an answer from the media server.
103      * @throws SqueezeBoxTimeoutException Volume command was not acknowledged by the media server.
104      */
105     private void setVolume(int requestedVolume) throws InterruptedException, SqueezeBoxTimeoutException {
106         if (playerState.getVolume() == requestedVolume) {
107             return;
108         }
109
110         SqueezeBoxNotificationListener listener = new SqueezeBoxNotificationListener(mac);
111         listener.resetVolumeUpdated();
112
113         squeezeBoxServerHandler.registerSqueezeBoxPlayerListener(listener);
114         squeezeBoxServerHandler.setVolume(mac, requestedVolume);
115
116         logger.trace("Waiting up to {} s for volume to be updated...", VOLUME_COMMAND_TIMEOUT);
117
118         try {
119             int timeoutCount = 0;
120
121             while (!listener.isVolumeUpdated(requestedVolume)) {
122                 Thread.sleep(100);
123                 if (timeoutCount++ > VOLUME_COMMAND_TIMEOUT * 10) {
124                     throw new SqueezeBoxTimeoutException("Unable to update volume.");
125                 }
126             }
127         } finally {
128             squeezeBoxServerHandler.unregisterSqueezeBoxPlayerListener(listener);
129         }
130     }
131
132     private void addNotificationMessageToPlaylist() throws InterruptedException, SqueezeBoxTimeoutException {
133         logger.debug("Adding notification message to playlist");
134         SqueezeBoxNotificationListener listener = new SqueezeBoxNotificationListener(mac);
135         listener.resetPlaylistUpdated();
136
137         squeezeBoxServerHandler.registerSqueezeBoxPlayerListener(listener);
138         squeezeBoxServerHandler.addPlaylistItem(mac, uri.toString(), "Notification");
139
140         try {
141             updatePlaylist(listener);
142             this.playlistModified = true;
143         } finally {
144             squeezeBoxServerHandler.unregisterSqueezeBoxPlayerListener(listener);
145         }
146     }
147
148     private void removeNotificationMessageFromPlaylist() throws InterruptedException, SqueezeBoxTimeoutException {
149         logger.debug("Removing notification message from playlist");
150         SqueezeBoxNotificationListener listener = new SqueezeBoxNotificationListener(mac);
151         listener.resetPlaylistUpdated();
152
153         squeezeBoxServerHandler.registerSqueezeBoxPlayerListener(listener);
154         squeezeBoxServerHandler.deletePlaylistItem(mac, notificationMessagePlaylistsIndex);
155
156         try {
157             updatePlaylist(listener);
158         } finally {
159             squeezeBoxServerHandler.unregisterSqueezeBoxPlayerListener(listener);
160         }
161     }
162
163     /**
164      * Monitor the number of playlist entries. When it changes, then we know the playlist
165      * has been updated with the notification URL. There's probably an edge case here where
166      * someone is updating the playlist at the same time, but that should be rare.
167      *
168      * @param listener
169      * @throws InterruptedException
170      * @throws SqueezeBoxTimeoutException
171      */
172     private void updatePlaylist(SqueezeBoxNotificationListener listener)
173             throws InterruptedException, SqueezeBoxTimeoutException {
174         logger.trace("Waiting up to {} s for playlist to be updated...", PLAYLIST_COMMAND_TIMEOUT);
175
176         int timeoutCount = 0;
177
178         while (!listener.isPlaylistUpdated()) {
179             Thread.sleep(100);
180             if (timeoutCount++ > PLAYLIST_COMMAND_TIMEOUT * 10) {
181                 logger.debug("Update playlist timed out after {} seconds", PLAYLIST_COMMAND_TIMEOUT);
182                 throw new SqueezeBoxTimeoutException("Unable to update playlist.");
183             }
184         }
185         logger.debug("Playlist updated");
186     }
187
188     private void playNotification() throws InterruptedException, SqueezeBoxTimeoutException {
189         logger.debug("Playing notification");
190
191         notificationMessagePlaylistsIndex = squeezeBoxPlayerHandler.currentNumberPlaylistTracks() - 1;
192         SqueezeBoxNotificationListener listener = new SqueezeBoxNotificationListener(mac);
193         listener.resetStopped();
194
195         squeezeBoxServerHandler.registerSqueezeBoxPlayerListener(listener);
196         squeezeBoxServerHandler.playPlaylistItem(mac, notificationMessagePlaylistsIndex);
197
198         try {
199             int notificationTimeout = squeezeBoxPlayerHandler.getNotificationTimeout();
200             int timeoutCount = 0;
201
202             logger.trace("Waiting up to {} s for stop...", notificationTimeout);
203             while (!listener.isStopped()) {
204                 Thread.sleep(100);
205                 if (timeoutCount++ > notificationTimeout * 10) {
206                     logger.debug("Notification message timed out after {} seconds", notificationTimeout);
207                     throw new SqueezeBoxTimeoutException("Notification message timed out");
208                 }
209             }
210         } finally {
211             squeezeBoxServerHandler.unregisterSqueezeBoxPlayerListener(listener);
212         }
213     }
214
215     private void restorePlayerState() {
216         logger.debug("Restoring player state");
217
218         // Mute the player to prevent any noise during the transition to saved state
219         // Don't wait for the volume acknowledge as thereĀ“s nothing to do about it at this point.
220         squeezeBoxServerHandler.setVolume(mac, 0);
221
222         if (playlistModified) {
223             try {
224                 removeNotificationMessageFromPlaylist();
225             } catch (InterruptedException | SqueezeBoxTimeoutException e) {
226                 // Not much we can do here except log it and continue on
227                 logger.debug("Exception while removing notification from playlist: {}", e.getMessage());
228             }
229         }
230
231         // Resume playing saved playlist item.
232         // Note that setting the time doesn't work for remote streams.
233         squeezeBoxServerHandler.playPlaylistItem(mac, playerState.getPlaylistIndex());
234         squeezeBoxServerHandler.setPlayingTime(mac, playerState.getPlayingTime());
235
236         switch (playerState.getPlayState()) {
237             case PLAY:
238                 logger.debug("Resuming last item playing");
239                 break;
240             case PAUSE:
241                 /*
242                  * If the player was paused, stop it. We stop it because the LMS
243                  * doesn't respond to a pause command while it's processing the
244                  * above 'playPlaylist item' command. The consequence of this is
245                  * we lose the ability to resume local music from saved playing time.
246                  */
247                 logger.debug("Stopping the player");
248                 squeezeBoxServerHandler.stop(mac);
249                 break;
250             case STOP:
251                 logger.debug("Stopping the player");
252                 squeezeBoxServerHandler.stop(mac);
253                 break;
254         }
255
256         // Restore the saved volume level
257         squeezeBoxServerHandler.setVolume(mac, playerState.getVolume());
258
259         if (playerState.isShuffling()) {
260             logger.debug("Restoring shuffle mode");
261             squeezeBoxServerHandler.setShuffleMode(mac, playerState.getShuffle());
262         }
263         if (playerState.isRepeating()) {
264             logger.debug("Restoring repeat mode");
265             squeezeBoxServerHandler.setRepeatMode(mac, playerState.getRepeat());
266         }
267         if (playerState.isMuted()) {
268             logger.debug("Re-muting the player");
269             squeezeBoxServerHandler.mute(mac);
270         }
271         if (!playerState.isPoweredOn()) {
272             logger.debug("Powering off the player");
273             squeezeBoxServerHandler.powerOff(mac);
274         }
275     }
276 }