[lgwebos] Subscribe to Play/Pause state changes from the TV (#18119)

Signed-off-by: Jimmy Tanagra <jcode@tanagra.id.au>
main
jimtng 2025-01-21 07:01:54 +10:00 committed by GitHub
parent f51e8f6186
commit 89187cc645
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 134 additions and 11 deletions

View File

@ -60,7 +60,7 @@ Thing lgwebos:WebOSTV:tv1 [host="192.168.2.119", key="6ef1dff6c7c936c8dc5056fc85
| volume | Dimmer | Current volume setting. Setting and reporting absolute percent values only works when using internal speakers. When connected to an external amp, the volume should be controlled using increase and decrease commands. | RW |
| channel | String | Current channel. Use the channel number or channel id as command to update the channel. | RW |
| toast | String | Displays a short message on the TV screen. See also rules section. | W |
| mediaPlayer | Player | Media control player | W |
| mediaPlayer | Player | Media control player | RW |
| mediaStop | Switch | Media control stop | W |
| appLauncher | String | Application ID of currently running application. This also allows to start applications on the TV by sending a specific Application ID to this channel. | RW |
| rcButton | String | Simulates pressing of a button on the TV's remote control. See below for a list of button names. | W |
@ -69,6 +69,9 @@ The available application IDs for your TV can be listed using a console command
You have to use one of these IDs as command for the appLauncher channel.
Here are examples of values that could be available for your TV: airplay, amazon, com.apple.appletv, com.webos.app.browser, com.webos.app.externalinput.av1, com.webos.app.externalinput.av2, com.webos.app.externalinput.component, com.webos.app.hdmi1, com.webos.app.hdmi2, com.webos.app.hdmi3, com.webos.app.hdmi4, com.webos.app.homeconnect, com.webos.app.igallery, com.webos.app.livetv, com.webos.app.music, com.webos.app.photovideo, com.webos.app.recordings, com.webos.app.screensaver, googleplaymovieswebos, netflix, youtube.leanback.v4.
Older WebOS versions don't publish its play state, so the `mediaPlayer` channel is write-only.
Starting from WebOS version 7, the `mediaPlayer` channel receives the PLAY/PAUSE state from the TV.
### Remote Control Buttons
This is a list of button codes that are known to work with several LG WebOS TV models.
@ -131,7 +134,6 @@ String LG_TV0_Toast { channel="lgwebos:WebOSTV:3aab9eea
Switch LG_TV0_Stop "Stop" { autoupdate="false", channel="lgwebos:WebOSTV:3aab9eea-953b-4272-bdbd-f0cd0ecf4a46:mediaStop" }
String LG_TV0_Application "Application [%s]" { channel="lgwebos:WebOSTV:3aab9eea-953b-4272-bdbd-f0cd0ecf4a46:appLauncher"}
Player LG_TV0_Player { channel="lgwebos:WebOSTV:3aab9eea-953b-4272-bdbd-f0cd0ecf4a46:mediaPlayer"}
```
demo.sitemap:

View File

@ -12,13 +12,21 @@
*/
package org.openhab.binding.lgwebos.internal;
import java.util.Optional;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.lgwebos.internal.handler.LGWebOSHandler;
import org.openhab.binding.lgwebos.internal.handler.command.ServiceSubscription;
import org.openhab.binding.lgwebos.internal.handler.core.AppInfo;
import org.openhab.binding.lgwebos.internal.handler.core.CommandConfirmation;
import org.openhab.binding.lgwebos.internal.handler.core.MediaAppInfo;
import org.openhab.binding.lgwebos.internal.handler.core.ResponseListener;
import org.openhab.core.library.types.PlayPauseType;
import org.openhab.core.library.types.RewindFastforwardType;
import org.openhab.core.types.Command;
import org.openhab.core.types.RefreshType;
import org.openhab.core.types.UnDefType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -27,28 +35,72 @@ import org.slf4j.LoggerFactory;
*
*
* @author Sebastian Prehn - Initial contribution
* @author Jimmy Tanagra - Add play/pause state subscription
*/
@NonNullByDefault
public class MediaControlPlayer extends BaseChannelHandler<CommandConfirmation> {
public class MediaControlPlayer extends BaseChannelHandler<MediaAppInfo> {
private final Logger logger = LoggerFactory.getLogger(MediaControlPlayer.class);
private final ResponseListener<CommandConfirmation> commandConfirmationResponseListener = createResponseListener();
@Override
public void onReceiveCommand(String channelId, LGWebOSHandler handler, Command command) {
if (RefreshType.REFRESH == command) {
// nothing to do
handler.getSocket().getMediaState(createResponseListener(channelId, handler));
} else if (PlayPauseType.PLAY == command) {
handler.getSocket().play(getDefaultResponseListener());
handler.getSocket().play(commandConfirmationResponseListener);
} else if (PlayPauseType.PAUSE == command) {
handler.getSocket().pause(getDefaultResponseListener());
handler.getSocket().pause(commandConfirmationResponseListener);
} else if (RewindFastforwardType.FASTFORWARD == command) {
handler.getSocket().fastForward(getDefaultResponseListener());
handler.getSocket().fastForward(commandConfirmationResponseListener);
} else if (RewindFastforwardType.REWIND == command) {
handler.getSocket().rewind(getDefaultResponseListener());
handler.getSocket().rewind(commandConfirmationResponseListener);
} else {
logger.info("Only accept PlayPauseType, RewindFastforwardType, RefreshType. Type was {}.",
command.getClass());
}
}
// TODO: playstatesubscription
@Override
protected Optional<ServiceSubscription<MediaAppInfo>> getSubscription(String channelId, LGWebOSHandler handler) {
return Optional.of(handler.getSocket().subscribeMediaState(createResponseListener(channelId, handler)));
}
private ResponseListener<MediaAppInfo> createResponseListener(String channelId, LGWebOSHandler handler) {
return new ResponseListener<>() {
@Override
public void onError(@Nullable String error) {
logger.debug("Error in retrieving application: {}.", error);
}
@Override
public void onSuccess(@Nullable MediaAppInfo mediaAppInfo) {
logger.trace("Received media app info: {}.", mediaAppInfo);
if (mediaAppInfo == null || mediaAppInfo.getForegroundAppInfo() == null) {
return;
}
if (mediaAppInfo.getForegroundAppInfo().isEmpty()) {
handler.postUpdate(channelId, UnDefType.UNDEF);
return;
}
AppInfo appInfo = mediaAppInfo.getForegroundAppInfo().get(0);
if (appInfo == null || appInfo.getPlayState() == null) {
return;
}
switch (appInfo.getPlayState()) {
case "playing":
handler.postUpdate(channelId, PlayPauseType.PLAY);
break;
case "paused":
handler.postUpdate(channelId, PlayPauseType.PAUSE);
break;
default:
handler.postUpdate(channelId, UnDefType.UNDEF);
}
}
};
}
}

View File

@ -71,6 +71,7 @@ import org.openhab.binding.lgwebos.internal.handler.core.ChannelInfo;
import org.openhab.binding.lgwebos.internal.handler.core.CommandConfirmation;
import org.openhab.binding.lgwebos.internal.handler.core.LaunchSession;
import org.openhab.binding.lgwebos.internal.handler.core.LaunchSession.LaunchSessionType;
import org.openhab.binding.lgwebos.internal.handler.core.MediaAppInfo;
import org.openhab.binding.lgwebos.internal.handler.core.Response;
import org.openhab.binding.lgwebos.internal.handler.core.ResponseListener;
import org.openhab.binding.lgwebos.internal.handler.core.TextInputStatusInfo;
@ -88,12 +89,14 @@ import com.google.gson.reflect.TypeToken;
*
* @author Hyun Kook Khang - Initial contribution
* @author Sebastian Prehn - Web Socket implementation and adoption for openHAB
* @author Jimmy Tanagra - Add media state subscription
*/
@WebSocket()
@NonNullByDefault
public class LGWebOSTVSocket {
private static final String FOREGROUND_APP = "ssap://com.webos.applicationManager/getForegroundAppInfo";
private static final String MEDIA_FOREGROUND_APP = "ssap://com.webos.media/getForegroundAppInfo";
// private static final String APP_STATUS = "ssap://com.webos.service.appstatus/getAppStatus";
// private static final String APP_STATE = "ssap://system.launcher/getAppState";
private static final String VOLUME = "ssap://audio/getVolume";
@ -733,6 +736,20 @@ public class LGWebOSTVSocket {
sendCommand(request);
}
public ServiceSubscription<MediaAppInfo> subscribeMediaState(ResponseListener<MediaAppInfo> listener) {
ServiceSubscription<MediaAppInfo> request = new ServiceSubscription<>(MEDIA_FOREGROUND_APP, null,
jsonObj -> GSON.fromJson(jsonObj, MediaAppInfo.class), listener);
sendCommand(request);
return request;
}
public ServiceCommand<MediaAppInfo> getMediaState(ResponseListener<MediaAppInfo> listener) {
ServiceCommand<MediaAppInfo> request = new ServiceCommand<>(MEDIA_FOREGROUND_APP, null,
jsonObj -> GSON.fromJson(jsonObj, MediaAppInfo.class), listener);
sendCommand(request);
return request;
}
// APPS
public void getAppList(final ResponseListener<List<AppInfo>> listener) {

View File

@ -50,14 +50,17 @@ public class AppInfo {
private String id;
@SerializedName(value = "name", alternate = { "appName", "title" })
private String name;
@SerializedName(value = "playState")
private String playState; // see MediaAppInfo for possible values
public AppInfo() {
// no-argument constructor for gson
}
public AppInfo(String id, String name) {
public AppInfo(String id, String name, String playState) {
this.id = id;
this.name = name;
this.playState = playState;
}
public String getId() {
@ -68,9 +71,13 @@ public class AppInfo {
return name;
}
public String getPlayState() {
return playState;
}
@Override
public String toString() {
return "AppInfo [id=" + id + ", name=" + name + "]";
return "AppInfo [id=" + id + ", name=" + name + ", playState=" + playState + "]";
}
@Override

View File

@ -0,0 +1,45 @@
/*
* Copyright (c) 2010-2025 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.lgwebos.internal.handler.core;
import java.util.List;
/**
* {@link MediaAppInfo} represents the payload that describes the foreground app's media state (playState) on WebOSTV.
*
* @author Jimmy Tanagra - Initial contribution
*/
public class MediaAppInfo {
// Example payload:
// {"subscribed":true,"returnValue":true,"foregroundAppInfo":[{"appId":"netflix","playState":"loaded","type":"media","mediaId":"_fdTzfnKXXXXX","windowId":"_Window_Id_1"}]}
// playState values: starting, loaded, playing, paused
private List<AppInfo> foregroundAppInfo;
public MediaAppInfo() {
// no-argument constructor for gson
}
public MediaAppInfo(List<AppInfo> foregroundAppInfo) {
this.foregroundAppInfo = foregroundAppInfo;
}
public List<AppInfo> getForegroundAppInfo() {
return foregroundAppInfo;
}
@Override
public String toString() {
return "MediaAppInfo [foregroundAppInfo=" + foregroundAppInfo + "]";
}
}