]> git.basschouten.com Git - openhab-addons.git/blob
72122cf8d12301d28145993ea4f937b147874790
[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.kaleidescape.internal.handler;
14
15 import static org.openhab.binding.kaleidescape.internal.KaleidescapeBindingConstants.*;
16
17 import java.util.Arrays;
18 import java.util.Collection;
19 import java.util.HashMap;
20 import java.util.HashSet;
21 import java.util.List;
22 import java.util.Map;
23 import java.util.Set;
24 import java.util.concurrent.ScheduledFuture;
25 import java.util.concurrent.TimeUnit;
26
27 import javax.measure.Unit;
28 import javax.measure.quantity.Time;
29
30 import org.eclipse.jdt.annotation.NonNullByDefault;
31 import org.eclipse.jdt.annotation.Nullable;
32 import org.eclipse.jetty.client.HttpClient;
33 import org.openhab.binding.kaleidescape.internal.KaleidescapeException;
34 import org.openhab.binding.kaleidescape.internal.KaleidescapeThingActions;
35 import org.openhab.binding.kaleidescape.internal.communication.KaleidescapeConnector;
36 import org.openhab.binding.kaleidescape.internal.communication.KaleidescapeDefaultConnector;
37 import org.openhab.binding.kaleidescape.internal.communication.KaleidescapeIpConnector;
38 import org.openhab.binding.kaleidescape.internal.communication.KaleidescapeMessageEvent;
39 import org.openhab.binding.kaleidescape.internal.communication.KaleidescapeMessageEventListener;
40 import org.openhab.binding.kaleidescape.internal.communication.KaleidescapeSerialConnector;
41 import org.openhab.binding.kaleidescape.internal.configuration.KaleidescapeThingConfiguration;
42 import org.openhab.core.io.transport.serial.SerialPortManager;
43 import org.openhab.core.library.types.NextPreviousType;
44 import org.openhab.core.library.types.OnOffType;
45 import org.openhab.core.library.types.PercentType;
46 import org.openhab.core.library.types.PlayPauseType;
47 import org.openhab.core.library.types.RewindFastforwardType;
48 import org.openhab.core.library.types.StringType;
49 import org.openhab.core.library.unit.Units;
50 import org.openhab.core.thing.ChannelUID;
51 import org.openhab.core.thing.Thing;
52 import org.openhab.core.thing.ThingStatus;
53 import org.openhab.core.thing.ThingStatusDetail;
54 import org.openhab.core.thing.ThingTypeUID;
55 import org.openhab.core.thing.binding.BaseThingHandler;
56 import org.openhab.core.thing.binding.ThingHandlerService;
57 import org.openhab.core.types.Command;
58 import org.openhab.core.types.RefreshType;
59 import org.openhab.core.types.State;
60 import org.slf4j.Logger;
61 import org.slf4j.LoggerFactory;
62
63 /**
64  * The {@link KaleidescapeHandler} is responsible for handling commands, which are sent to one of the channels.
65  *
66  * Based on the Rotel binding by Laurent Garnier
67  *
68  * @author Michael Lobstein - Initial contribution
69  */
70 @NonNullByDefault
71 public class KaleidescapeHandler extends BaseThingHandler implements KaleidescapeMessageEventListener {
72     private static final long RECON_POLLING_INTERVAL_S = 60;
73     private static final long POLLING_INTERVAL_S = 20;
74
75     private final Logger logger = LoggerFactory.getLogger(KaleidescapeHandler.class);
76     private final SerialPortManager serialPortManager;
77     private final Map<String, String> cache = new HashMap<>();
78
79     protected final HttpClient httpClient;
80     protected final Unit<Time> apiSecondUnit = Units.SECOND;
81
82     private ThingTypeUID thingTypeUID = THING_TYPE_PLAYER;
83     private @Nullable ScheduledFuture<?> reconnectJob;
84     private @Nullable ScheduledFuture<?> pollingJob;
85     private long lastEventReceived = 0;
86     private int updatePeriod = 0;
87
88     protected KaleidescapeConnector connector = new KaleidescapeDefaultConnector();
89     protected int metaRuntimeMultiple = 1;
90     protected int volume = 0;
91     protected boolean volumeEnabled = false;
92     protected boolean volumeBasicEnabled = false;
93     protected boolean isMuted = false;
94     protected boolean isLoadHighlightedDetails = false;
95     protected boolean isLoadAlbumDetails = false;
96     protected String friendlyName = EMPTY;
97     protected Object sequenceLock = new Object();
98
99     public KaleidescapeHandler(Thing thing, SerialPortManager serialPortManager, HttpClient httpClient) {
100         super(thing);
101         this.serialPortManager = serialPortManager;
102         this.httpClient = httpClient;
103     }
104
105     protected void updateChannel(String channelUID, State state) {
106         this.updateState(channelUID, state);
107     }
108
109     protected void updateDetailChannel(String channelUID, State state) {
110         this.updateState(DETAIL + channelUID, state);
111     }
112
113     protected void updateThingProperty(String name, String value) {
114         thing.setProperty(name, value);
115     }
116
117     protected boolean isChannelLinked(String channel) {
118         return isLinked(channel);
119     }
120
121     @Override
122     public void initialize() {
123         final String uid = this.getThing().getUID().getAsString();
124         KaleidescapeThingConfiguration config = getConfigAs(KaleidescapeThingConfiguration.class);
125
126         this.thingTypeUID = thing.getThingTypeUID();
127
128         // Check configuration settings
129         String configError = null;
130         final String serialPort = config.serialPort;
131         final String host = config.host;
132         final Integer port = config.port;
133         final Integer updatePeriod = config.updatePeriod;
134         this.isLoadHighlightedDetails = config.loadHighlightedDetails;
135         this.isLoadAlbumDetails = config.loadAlbumDetails;
136
137         if ((serialPort == null || serialPort.isEmpty()) && (host == null || host.isEmpty())) {
138             configError = "undefined serialPort and host configuration settings; please set one of them";
139         } else if (host == null || host.isEmpty()) {
140             if (serialPort != null && serialPort.toLowerCase().startsWith("rfc2217")) {
141                 configError = "use host and port configuration settings for a serial over IP connection";
142             }
143         } else {
144             if (port == null) {
145                 configError = "undefined port configuration setting";
146             } else if (port <= 0) {
147                 configError = "invalid port configuration setting";
148             }
149         }
150
151         if (configError != null) {
152             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, configError);
153             return;
154         }
155
156         if (updatePeriod != null) {
157             this.updatePeriod = updatePeriod;
158         }
159
160         // check if volume is enabled
161         if (config.volumeEnabled) {
162             this.volumeEnabled = true;
163             this.volume = config.initialVolume;
164             this.updateState(VOLUME, new PercentType(this.volume));
165             this.updateState(MUTE, OnOffType.OFF);
166         } else if (config.volumeBasicEnabled) {
167             this.volumeBasicEnabled = true;
168         }
169
170         if (serialPort != null) {
171             connector = new KaleidescapeSerialConnector(serialPortManager, serialPort, uid);
172         } else if (port != null) {
173             connector = new KaleidescapeIpConnector(host, port, uid);
174         } else {
175             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
176                     "Either Serial port or Host & Port must be specifed");
177             return;
178         }
179
180         updateStatus(ThingStatus.UNKNOWN);
181
182         scheduleReconnectJob();
183         schedulePollingJob();
184     }
185
186     @Override
187     public void dispose() {
188         cancelReconnectJob();
189         cancelPollingJob();
190         closeConnection();
191     }
192
193     @Override
194     public Collection<Class<? extends ThingHandlerService>> getServices() {
195         return List.of(KaleidescapeThingActions.class);
196     }
197
198     public void handleRawCommand(@Nullable String command) {
199         synchronized (sequenceLock) {
200             try {
201                 connector.sendCommand(command);
202             } catch (KaleidescapeException e) {
203                 logger.warn("K Command: {} failed", command);
204             }
205         }
206     }
207
208     @Override
209     public void handleCommand(ChannelUID channelUID, Command command) {
210         String channel = channelUID.getId();
211
212         if (getThing().getStatus() != ThingStatus.ONLINE) {
213             logger.debug("Thing is not ONLINE; command {} from channel {} is ignored", command, channel);
214             return;
215         }
216         synchronized (sequenceLock) {
217             if (!connector.isConnected()) {
218                 logger.debug("Command {} from channel {} is ignored: connection not established", command, channel);
219                 return;
220             }
221
222             try {
223                 if (command instanceof RefreshType) {
224                     handleRefresh(channel);
225                     return;
226                 }
227
228                 switch (channel) {
229                     case POWER:
230                         if (command instanceof OnOffType) {
231                             connector.sendCommand(command == OnOffType.ON ? LEAVE_STANDBY : ENTER_STANDBY);
232                         }
233                         break;
234                     case VOLUME:
235                         if (command instanceof PercentType percentCommand) {
236                             this.volume = (int) percentCommand.doubleValue();
237                             logger.debug("Got volume command {}", this.volume);
238                             connector.sendCommand(SEND_EVENT_VOLUME_LEVEL_EQ + this.volume);
239                         }
240                         break;
241                     case MUTE:
242                         if (command instanceof OnOffType) {
243                             this.isMuted = command == OnOffType.ON ? true : false;
244                         }
245                         connector.sendCommand(SEND_EVENT_MUTE + (this.isMuted ? MUTE_ON : MUTE_OFF));
246                         break;
247                     case MUSIC_REPEAT:
248                         if (command instanceof OnOffType) {
249                             connector.sendCommand(command == OnOffType.ON ? MUSIC_REPEAT_ON : MUSIC_REPEAT_OFF);
250                         }
251                         break;
252                     case MUSIC_RANDOM:
253                         if (command instanceof OnOffType) {
254                             connector.sendCommand(command == OnOffType.ON ? MUSIC_RANDOM_ON : MUSIC_RANDOM_OFF);
255                         }
256                         break;
257                     case CONTROL:
258                     case MUSIC_CONTROL:
259                         handleControlCommand(command);
260                         break;
261                     case CHANNEL_TYPE_SENDCMD:
262                         connector.sendCommand(command.toString());
263                         break;
264                     default:
265                         logger.debug("Command {} from channel {} failed: unexpected command", command, channel);
266                         break;
267                 }
268             } catch (KaleidescapeException e) {
269                 logger.debug("Command {} from channel {} failed: {}", command, channel, e.getMessage());
270                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "Sending command failed");
271                 closeConnection();
272                 scheduleReconnectJob();
273             }
274         }
275     }
276
277     /**
278      * Open the connection with the Kaleidescape component
279      *
280      * @return true if the connection is opened successfully or false if not
281      */
282     private synchronized boolean openConnection() {
283         connector.addEventListener(this);
284         try {
285             connector.open();
286         } catch (KaleidescapeException e) {
287             logger.debug("openConnection() failed: {}", e.getMessage());
288         }
289         logger.debug("openConnection(): {}", connector.isConnected() ? "connected" : "disconnected");
290         return connector.isConnected();
291     }
292
293     /**
294      * Close the connection with the Kaleidescape component
295      */
296     private synchronized void closeConnection() {
297         if (connector.isConnected()) {
298             connector.close();
299             connector.removeEventListener(this);
300             logger.debug("closeConnection(): disconnected");
301         }
302     }
303
304     @Override
305     public void onNewMessageEvent(KaleidescapeMessageEvent evt) {
306         lastEventReceived = System.currentTimeMillis();
307
308         // check if we are in standby
309         if (STANDBY_MSG.equals(evt.getKey())) {
310             if (!ThingStatusDetail.BRIDGE_OFFLINE.equals(thing.getStatusInfo().getStatusDetail())) {
311                 updateStatus(ThingStatus.ONLINE, ThingStatusDetail.BRIDGE_OFFLINE, STANDBY_MSG);
312             }
313             return;
314         }
315         try {
316             // Use the Enum valueOf to handle the message based on the event key. Otherwise there would be a huge
317             // case statement here
318             KaleidescapeMessageHandler.valueOf(evt.getKey()).handleMessage(evt.getValue(), this);
319
320             if (!evt.isCached()) {
321                 cache.put(evt.getKey(), evt.getValue());
322             }
323
324             if (ThingStatusDetail.BRIDGE_OFFLINE.equals(thing.getStatusInfo().getStatusDetail())) {
325                 // no longer in standby, update the status
326                 updateStatus(ThingStatus.ONLINE, ThingStatusDetail.NONE, this.friendlyName);
327             }
328         } catch (IllegalArgumentException e) {
329             logger.debug("Unhandled message: key {} = {}", evt.getKey(), evt.getValue());
330         }
331     }
332
333     /**
334      * Schedule the reconnection job
335      */
336     private void scheduleReconnectJob() {
337         logger.debug("Schedule reconnect job");
338         cancelReconnectJob();
339         reconnectJob = scheduler.scheduleWithFixedDelay(() -> {
340             synchronized (sequenceLock) {
341                 if (!connector.isConnected()) {
342                     logger.debug("Trying to reconnect...");
343                     closeConnection();
344                     String error = EMPTY;
345                     if (openConnection()) {
346                         try {
347                             cache.clear();
348
349                             // register the connection in the Kaleidescape System log
350                             connector.sendCommand(SEND_TO_SYSLOG + "openHAB Kaleidescape Binding version "
351                                     + org.openhab.core.OpenHAB.getVersion());
352
353                             Set<String> initialCommands = new HashSet<>(Arrays.asList(GET_DEVICE_TYPE_NAME,
354                                     GET_FRIENDLY_NAME, GET_DEVICE_INFO, GET_SYSTEM_VERSION, GET_DEVICE_POWER_STATE,
355                                     GET_CINEMASCAPE_MASK, GET_CINEMASCAPE_MODE, GET_SCALE_MODE, GET_SCREEN_MASK,
356                                     GET_SCREEN_MASK2, GET_VIDEO_MODE, GET_UI_STATE, GET_HIGHLIGHTED_SELECTION,
357                                     GET_CHILD_MODE_STATE, GET_PLAY_STATUS, GET_MOVIE_LOCATION, GET_MOVIE_MEDIA_TYPE,
358                                     GET_PLAYING_TITLE_NAME));
359
360                             // Premiere Players and Cinema One support music
361                             if (thingTypeUID.equals(THING_TYPE_PLAYER) || thingTypeUID.equals(THING_TYPE_CINEMA_ONE)) {
362                                 initialCommands.addAll(Arrays.asList(GET_MUSIC_NOW_PLAYING_STATUS,
363                                         GET_MUSIC_PLAY_STATUS, GET_MUSIC_TITLE));
364                             }
365
366                             // everything after Premiere Player supports GET_SYSTEM_READINESS_STATE
367                             if (!thingTypeUID.equals(THING_TYPE_PLAYER)) {
368                                 initialCommands.add(GET_SYSTEM_READINESS_STATE);
369                             }
370
371                             // only Strato supports the GET_*_COLOR commands
372                             if (thingTypeUID.equals(THING_TYPE_STRATO)) {
373                                 initialCommands.addAll(Arrays.asList(GET_VIDEO_COLOR, GET_CONTENT_COLOR));
374                             }
375
376                             initialCommands.forEach(command -> {
377                                 try {
378                                     connector.sendCommand(command);
379                                 } catch (KaleidescapeException e) {
380                                     logger.debug("{}: {}", "Error sending initial commands", e.getMessage());
381                                 }
382                             });
383
384                             if (this.updatePeriod == 1) {
385                                 connector.sendCommand(SET_STATUS_CUE_PERIOD_1);
386                             }
387                         } catch (KaleidescapeException e) {
388                             error = "First command after connection failed";
389                             logger.debug("{}: {}", error, e.getMessage());
390                             closeConnection();
391                         }
392                     } else {
393                         error = "Reconnection failed";
394                     }
395                     if (!error.equals(EMPTY)) {
396                         updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, error);
397                         return;
398                     }
399                     updateStatus(ThingStatus.ONLINE, ThingStatusDetail.NONE, this.friendlyName);
400                     lastEventReceived = System.currentTimeMillis();
401                 }
402             }
403         }, 1, RECON_POLLING_INTERVAL_S, TimeUnit.SECONDS);
404     }
405
406     /**
407      * Cancel the reconnection job
408      */
409     private void cancelReconnectJob() {
410         ScheduledFuture<?> reconnectJob = this.reconnectJob;
411         if (reconnectJob != null) {
412             reconnectJob.cancel(true);
413             this.reconnectJob = null;
414         }
415     }
416
417     /**
418      * Schedule the polling job
419      */
420     private void schedulePollingJob() {
421         logger.debug("Schedule polling job");
422         cancelPollingJob();
423
424         pollingJob = scheduler.scheduleWithFixedDelay(() -> {
425             synchronized (sequenceLock) {
426                 if (connector.isConnected()) {
427                     logger.debug("Polling the component for updated status...");
428                     try {
429                         connector.ping();
430                         cache.clear();
431                     } catch (KaleidescapeException e) {
432                         logger.debug("Polling error: {}", e.getMessage());
433                     }
434
435                     // if the last successful polling update was more than 1.25 intervals ago,
436                     // the component is not responding even though the connection is still good
437                     if ((System.currentTimeMillis() - lastEventReceived) > (POLLING_INTERVAL_S * 1.25 * 1000)) {
438                         logger.debug("Component not responding to status requests");
439                         updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
440                                 "Component not responding to status requests");
441                         closeConnection();
442                         scheduleReconnectJob();
443                     }
444                 }
445             }
446         }, POLLING_INTERVAL_S, POLLING_INTERVAL_S, TimeUnit.SECONDS);
447     }
448
449     /**
450      * Cancel the polling job
451      */
452     private void cancelPollingJob() {
453         ScheduledFuture<?> pollingJob = this.pollingJob;
454         if (pollingJob != null) {
455             pollingJob.cancel(true);
456             this.pollingJob = null;
457         }
458     }
459
460     private void handleControlCommand(Command command) throws KaleidescapeException {
461         if (command instanceof PlayPauseType) {
462             if (command == PlayPauseType.PLAY) {
463                 connector.sendCommand(PLAY);
464             } else if (command == PlayPauseType.PAUSE) {
465                 connector.sendCommand(PAUSE);
466             }
467         } else if (command instanceof NextPreviousType) {
468             if (command == NextPreviousType.NEXT) {
469                 connector.sendCommand(NEXT);
470             } else if (command == NextPreviousType.PREVIOUS) {
471                 connector.sendCommand(PREVIOUS);
472             }
473         } else if (command instanceof RewindFastforwardType) {
474             if (command == RewindFastforwardType.FASTFORWARD) {
475                 connector.sendCommand(SCAN_FORWARD);
476             } else if (command == RewindFastforwardType.REWIND) {
477                 connector.sendCommand(SCAN_REVERSE);
478             }
479         } else {
480             logger.warn("Unknown control command: {}", command);
481         }
482     }
483
484     private void handleRefresh(String channel) throws KaleidescapeException {
485         switch (channel) {
486             case POWER:
487                 connector.sendCommand(GET_DEVICE_POWER_STATE, cache.get("DEVICE_POWER_STATE"));
488                 break;
489             case VOLUME:
490                 updateState(channel, new PercentType(this.volume));
491                 break;
492             case MUTE:
493                 updateState(channel, OnOffType.from(this.isMuted));
494                 break;
495             case TITLE_NAME:
496                 connector.sendCommand(GET_PLAYING_TITLE_NAME, cache.get("TITLE_NAME"));
497                 break;
498             case PLAY_MODE:
499             case PLAY_SPEED:
500             case TITLE_NUM:
501             case TITLE_LENGTH:
502             case TITLE_LOC:
503             case CHAPTER_NUM:
504             case CHAPTER_LENGTH:
505             case CHAPTER_LOC:
506                 connector.sendCommand(GET_PLAY_STATUS, cache.get("PLAY_STATUS"));
507                 break;
508             case MOVIE_MEDIA_TYPE:
509                 connector.sendCommand(GET_MOVIE_MEDIA_TYPE, cache.get("MOVIE_MEDIA_TYPE"));
510                 break;
511             case MOVIE_LOCATION:
512                 connector.sendCommand(GET_MOVIE_LOCATION, cache.get("MOVIE_LOCATION"));
513                 break;
514             case VIDEO_MODE:
515             case VIDEO_MODE_COMPOSITE:
516             case VIDEO_MODE_COMPONENT:
517             case VIDEO_MODE_HDMI:
518                 connector.sendCommand(GET_VIDEO_MODE, cache.get("VIDEO_MODE"));
519                 break;
520             case VIDEO_COLOR:
521             case VIDEO_COLOR_EOTF:
522                 connector.sendCommand(GET_VIDEO_COLOR, cache.get("VIDEO_COLOR"));
523                 break;
524             case CONTENT_COLOR:
525             case CONTENT_COLOR_EOTF:
526                 connector.sendCommand(GET_CONTENT_COLOR, cache.get("CONTENT_COLOR"));
527                 break;
528             case SCALE_MODE:
529                 connector.sendCommand(GET_SCALE_MODE, cache.get("SCALE_MODE"));
530                 break;
531             case ASPECT_RATIO:
532             case SCREEN_MASK:
533                 connector.sendCommand(GET_SCREEN_MASK, cache.get("SCREEN_MASK"));
534                 break;
535             case SCREEN_MASK2:
536                 connector.sendCommand(GET_SCREEN_MASK2, cache.get("SCREEN_MASK2"));
537                 break;
538             case CINEMASCAPE_MASK:
539                 connector.sendCommand(GET_CINEMASCAPE_MASK, cache.get("GET_CINEMASCAPE_MASK"));
540                 break;
541             case CINEMASCAPE_MODE:
542                 connector.sendCommand(GET_CINEMASCAPE_MODE, cache.get("CINEMASCAPE_MODE"));
543                 break;
544             case UI_STATE:
545                 connector.sendCommand(GET_UI_STATE, cache.get("UI_STATE"));
546                 break;
547             case CHILD_MODE_STATE:
548                 connector.sendCommand(GET_CHILD_MODE_STATE, cache.get("CHILD_MODE_STATE"));
549                 break;
550             case SYSTEM_READINESS_STATE:
551                 connector.sendCommand(GET_SYSTEM_READINESS_STATE, cache.get("SYSTEM_READINESS_STATE"));
552                 break;
553             case HIGHLIGHTED_SELECTION:
554                 connector.sendCommand(GET_HIGHLIGHTED_SELECTION, cache.get("HIGHLIGHTED_SELECTION"));
555                 break;
556             case USER_DEFINED_EVENT:
557             case USER_INPUT:
558             case USER_INPUT_PROMPT:
559                 updateState(channel, StringType.EMPTY);
560                 break;
561             case MUSIC_REPEAT:
562             case MUSIC_RANDOM:
563                 connector.sendCommand(GET_MUSIC_NOW_PLAYING_STATUS, cache.get("MUSIC_NOW_PLAYING_STATUS"));
564                 break;
565             case MUSIC_TRACK:
566             case MUSIC_ARTIST:
567             case MUSIC_ALBUM:
568             case MUSIC_TRACK_HANDLE:
569             case MUSIC_ALBUM_HANDLE:
570             case MUSIC_NOWPLAY_HANDLE:
571                 connector.sendCommand(GET_MUSIC_TITLE, cache.get("MUSIC_TITLE"));
572                 break;
573             case MUSIC_PLAY_MODE:
574             case MUSIC_PLAY_SPEED:
575             case MUSIC_TRACK_LENGTH:
576             case MUSIC_TRACK_POSITION:
577             case MUSIC_TRACK_PROGRESS:
578                 connector.sendCommand(GET_MUSIC_PLAY_STATUS, cache.get("MUSIC_PLAY_STATUS"));
579                 break;
580             case DETAIL_TYPE:
581             case DETAIL_TITLE:
582             case DETAIL_ALBUM_TITLE:
583             case DETAIL_COVER_ART:
584             case DETAIL_COVER_URL:
585             case DETAIL_HIRES_COVER_URL:
586             case DETAIL_RATING:
587             case DETAIL_YEAR:
588             case DETAIL_RUNNING_TIME:
589             case DETAIL_ACTORS:
590             case DETAIL_ARTIST:
591             case DETAIL_DIRECTORS:
592             case DETAIL_GENRES:
593             case DETAIL_RATING_REASON:
594             case DETAIL_SYNOPSIS:
595             case DETAIL_REVIEW:
596             case DETAIL_COLOR_DESCRIPTION:
597             case DETAIL_COUNTRY:
598             case DETAIL_ASPECT_RATIO:
599             case DETAIL_DISC_LOCATION:
600                 updateState(channel, StringType.EMPTY);
601                 break;
602         }
603     }
604 }