]> git.basschouten.com Git - openhab-addons.git/blob
d8ba8698a71733d780009253fbffe3dfa7f9d670
[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.freeboxos.internal.api.rest;
14
15 import static org.openhab.binding.freeboxos.internal.FreeboxOsBindingConstants.THING_PLAYER;
16
17 import java.time.ZonedDateTime;
18 import java.util.HashMap;
19 import java.util.List;
20 import java.util.Map;
21 import java.util.Objects;
22
23 import javax.ws.rs.core.UriBuilder;
24
25 import org.eclipse.jdt.annotation.NonNullByDefault;
26 import org.eclipse.jdt.annotation.Nullable;
27 import org.eclipse.jetty.http.HttpMethod;
28 import org.openhab.binding.freeboxos.internal.api.FreeboxException;
29 import org.openhab.binding.freeboxos.internal.api.Response;
30 import org.openhab.binding.freeboxos.internal.api.rest.PlayerManager.Metadata.PlaybackState;
31 import org.openhab.binding.freeboxos.internal.api.rest.PlayerManager.Metadata.SubtitleTrack;
32 import org.openhab.binding.freeboxos.internal.api.rest.PlayerManager.Metadata.VideoTrack;
33 import org.openhab.binding.freeboxos.internal.api.rest.PlayerManager.PlayerContext.PlayerDetails;
34 import org.openhab.binding.freeboxos.internal.api.rest.SystemManager.ModelInfo;
35
36 import com.google.gson.annotations.SerializedName;
37
38 import inet.ipaddr.mac.MACAddress;
39
40 /**
41  * The {@link PlayerManager} is the Java class used to handle api requests related to player
42  *
43  * @author GaĆ«l L'hopital - Initial contribution
44  */
45 @NonNullByDefault
46 public class PlayerManager extends ListableRest<PlayerManager.Player, PlayerManager.PlayerResponse> {
47     private static final String STATUS_PATH = "status";
48
49     protected static class PlayerResponse extends Response<Player> {
50     }
51
52     public enum DeviceModel {
53         FBX7HD_DELTA, // Freebox Player Devialet
54         TBX8AM, // Player Pop
55         FBX6HD,
56         FBX6LC,
57         FBX6LCV2,
58         FBX7HD,
59         FBX7HD_ONE,
60         FBX8AM,
61         UNKNOWN
62     }
63
64     public static record Player(MACAddress mac, StbType stbType, int id, ZonedDateTime lastTimeReachable,
65             boolean apiAvailable, String deviceName, DeviceModel deviceModel, boolean reachable, String uid,
66             @Nullable String apiVersion, List<String> lanGids) {
67         private enum StbType {
68             STB_ANDROID,
69             STB_V6,
70             STB_V7,
71             STB_V8,
72             UNKNOWN
73         }
74
75         /**
76          * @return a string like eg: '17/api/v8'
77          */
78         private @Nullable String baseUrl() {
79             String api = apiVersion;
80             return api != null ? "%d/api/v%s/".formatted(id, api.split("\\.")[0]) : null;
81         }
82     }
83
84     private static class StatusResponse extends Response<Status> {
85     }
86
87     public enum PowerState {
88         STANDBY,
89         RUNNING,
90         UNKNOWN
91     }
92
93     public static record Status(PowerState powerState, StatusInformation player,
94             @Nullable ForegroundApp foregroundApp) {
95
96         public @Nullable ForegroundApp foregroundApp() {
97             return foregroundApp;
98         }
99     }
100
101     public static record ForegroundApp(int packageId, @Nullable String curlUrl, @Nullable Object context,
102             @SerializedName(value = "package") String _package) {
103     }
104
105     private static record StatusInformation(String name, ZonedDateTime lastActivity) {
106     }
107
108     private static class ConfigurationResponse extends Response<Configuration> {
109     }
110
111     public static record Configuration(String boardName, boolean configured, String firmwareVersion,
112             @Nullable ModelInfo modelInfo, String serial, String uptime, long uptimeVal) {
113     }
114
115     private enum MediaState {
116         READY,
117         UNKNOWN
118     }
119
120     private static record AudioTrack(int bitrate, @SerializedName("channelCount") int channelCount,
121             @Nullable String codec, @SerializedName("codecId") @Nullable String codecId, @Nullable String language,
122             @SerializedName("metadataId") @Nullable String metadataId, int pid, int samplerate, long uid) {
123     }
124
125     private enum Type {
126         NORMAL,
127         HEARINGIMPAIRED,
128         UNKNOWN
129     }
130
131     protected static record Metadata(@Nullable String album,
132             @SerializedName("albumArtist") @Nullable String albumArtist, @Nullable String artist,
133             @Nullable String author, int bpm, @Nullable String comment, boolean compilation, @Nullable String composer,
134             @Nullable String container, @Nullable String copyright, long date,
135             @SerializedName("discId") @Nullable String discId, @SerializedName("discNumber") int discNumber,
136             @SerializedName("discTotal") int discTotal, @Nullable String genre,
137             @SerializedName("musicbrainzDiscId") @Nullable String musicbrainzDiscId, @Nullable String performer,
138             @Nullable String title, @SerializedName("trackNumber") int trackNumber,
139             @SerializedName("trackTotal") int trackTotal, @Nullable String url) {
140
141         protected enum PlaybackState {
142             PLAY,
143             PAUSE,
144             UNKNOWN
145         }
146
147         protected static record SubtitleTrack(@Nullable String codec, @Nullable String language, @Nullable String pid,
148                 Type type, @Nullable String uid) {
149         }
150
151         protected static record VideoTrack(int bitrate, @Nullable String codec, int height, int pid, int uid,
152                 int width) {
153         }
154     }
155
156     public static record PlayerContext(@Nullable PlayerDetails player) {
157         public static record PlayerDetails(@SerializedName("audioIndex") int audioIndex,
158                 @SerializedName("audioList") List<AudioTrack> audioList, @SerializedName("curPos") long curPos,
159                 int duration, @SerializedName("livePos") long livePos, @SerializedName("maxPos") long maxPos,
160                 @SerializedName("mediaState") MediaState mediaState, @Nullable Metadata metadata,
161                 @SerializedName("minPos") long minPos, @SerializedName("playbackState") PlaybackState playbackState,
162                 long position, @Nullable String source, @SerializedName("subtitleIndex") int subtitleIndex,
163                 @SerializedName("subtitleList") List<SubtitleTrack> subtitleList,
164                 @SerializedName("videoIndex") int videoIndex, @SerializedName("videoList") List<VideoTrack> videoList) {
165         }
166     }
167
168     private enum BouquetType {
169         ADSL,
170         UNKNOWN
171     }
172
173     private enum ChannelType {
174         REGULAR,
175         UNKNOWN
176     }
177
178     private static record Service(long id, @Nullable String name,
179             @SerializedName("qualityLabel") @Nullable String qualityLabel,
180             @SerializedName("qualityName") @Nullable String qualityName, @SerializedName("sortInfo") int sortInfo,
181             @SerializedName("typeLabel") @Nullable String typeLabel,
182             @SerializedName("typeName") @Nullable String typeName, @Nullable String url) {
183     }
184
185     private static record Channel(@SerializedName("bouquetId") long bouquetId,
186             @SerializedName("bouquetName") @Nullable String bouquetName,
187             @SerializedName("bouquetType") BouquetType bouquetType,
188             @SerializedName("channelName") @Nullable String channelName,
189             @SerializedName("channelNumber") int channelNumber,
190             @SerializedName("channelSubNumber") int channelSubNumber,
191             @SerializedName("channelType") ChannelType channelType,
192             @SerializedName("channelUuid") @Nullable String channelUuid,
193             @SerializedName("currentServiceIndex") int currentServiceIndex,
194             @SerializedName("isTimeShifting") boolean isTimeShifting, List<Service> services,
195             @SerializedName("videoIsVisible") boolean videoIsVisible) {
196     }
197
198     public static record TvContext(@Nullable Channel channel, @Nullable PlayerDetails player) {
199     }
200
201     private final Map<Integer, @Nullable String> subPaths = new HashMap<>();
202
203     public PlayerManager(FreeboxOsSession session) throws FreeboxException {
204         super(session, LoginManager.Permission.PLAYER, PlayerResponse.class,
205                 session.getUriBuilder().path(THING_PLAYER));
206     }
207
208     private @Nullable String getSubPath(int id) throws FreeboxException {
209         String subPath = subPaths.get(id);
210         if (subPath != null) {
211             return subPath;
212         }
213         getDevices().stream().filter(Player::apiAvailable).forEach(player -> {
214             String baseUrl = player.baseUrl();
215             if (baseUrl != null) {
216                 subPaths.put(player.id, baseUrl);
217             }
218         });
219         return subPaths.get(id);
220     }
221
222     public @Nullable Status getPlayerStatus(int id) throws FreeboxException {
223         String subPath = getSubPath(id);
224         return subPath != null ? getSingle(StatusResponse.class, subPath, STATUS_PATH) : null;
225     }
226
227     // The player API does not allow to directly request a given player like others api parts
228     @Override
229     public Player getDevice(int id) throws FreeboxException {
230         return Objects.requireNonNull(getDevices().stream().filter(player -> player.id == id).findFirst().orElse(null));
231     }
232
233     public @Nullable Configuration getConfig(int id) throws FreeboxException {
234         String subPath = getSubPath(id);
235         return subPath != null ? getSingle(ConfigurationResponse.class, subPath, SYSTEM_PATH) : null;
236     }
237
238     public void sendKey(String ip, String code, String key, boolean longPress, int count) {
239         UriBuilder uriBuilder = UriBuilder.fromPath("pub").scheme("http").host(ip).path("remote_control");
240         uriBuilder.queryParam("code", code).queryParam("key", key);
241         if (longPress) {
242             uriBuilder.queryParam("long", true);
243         }
244         if (count > 1) {
245             uriBuilder.queryParam("repeat", count);
246         }
247         try {
248             session.execute(uriBuilder.build(), HttpMethod.GET, GenericResponse.class, null);
249         } catch (FreeboxException ignore) {
250             // This call does not return anything, we can safely ignore
251         }
252     }
253
254     public boolean reboot(int id) throws FreeboxException {
255         String subPath = getSubPath(id);
256         if (subPath != null) {
257             post(subPath, SYSTEM_PATH, REBOOT_ACTION);
258             return true;
259         }
260         return false;
261     }
262 }