2 * Copyright (c) 2010-2024 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
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
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.freeboxos.internal.api.rest;
15 import static org.openhab.binding.freeboxos.internal.FreeboxOsBindingConstants.THING_PLAYER;
17 import java.time.ZonedDateTime;
18 import java.util.HashMap;
19 import java.util.List;
21 import java.util.Objects;
23 import javax.ws.rs.core.UriBuilder;
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;
36 import com.google.gson.annotations.SerializedName;
38 import inet.ipaddr.mac.MACAddress;
41 * The {@link PlayerManager} is the Java class used to handle api requests related to player
43 * @author Gaƫl L'hopital - Initial contribution
46 public class PlayerManager extends ListableRest<PlayerManager.Player, PlayerManager.PlayerResponse> {
47 private static final String STATUS_PATH = "status";
49 protected static class PlayerResponse extends Response<Player> {
52 public enum DeviceModel {
53 FBX7HD_DELTA, // Freebox Player Devialet
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 {
76 * @return a string like eg: '17/api/v8'
78 private @Nullable String baseUrl() {
79 String api = apiVersion;
80 return api != null ? "%d/api/v%s/".formatted(id, api.split("\\.")[0]) : null;
84 private static class StatusResponse extends Response<Status> {
87 public enum PowerState {
93 public static record Status(PowerState powerState, StatusInformation player,
94 @Nullable ForegroundApp foregroundApp) {
96 public @Nullable ForegroundApp foregroundApp() {
101 public static record ForegroundApp(int packageId, @Nullable String curlUrl, @Nullable Object context,
102 @SerializedName(value = "package") String _package) {
105 private static record StatusInformation(String name, ZonedDateTime lastActivity) {
108 private static class ConfigurationResponse extends Response<Configuration> {
111 public static record Configuration(String boardName, boolean configured, String firmwareVersion,
112 @Nullable ModelInfo modelInfo, String serial, String uptime, long uptimeVal) {
115 private enum MediaState {
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) {
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) {
141 protected enum PlaybackState {
147 protected static record SubtitleTrack(@Nullable String codec, @Nullable String language, @Nullable String pid,
148 Type type, @Nullable String uid) {
151 protected static record VideoTrack(int bitrate, @Nullable String codec, int height, int pid, int uid,
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) {
168 private enum BouquetType {
173 private enum ChannelType {
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) {
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) {
198 public static record TvContext(@Nullable Channel channel, @Nullable PlayerDetails player) {
201 private final Map<Integer, @Nullable String> subPaths = new HashMap<>();
203 public PlayerManager(FreeboxOsSession session) throws FreeboxException {
204 super(session, LoginManager.Permission.PLAYER, PlayerResponse.class,
205 session.getUriBuilder().path(THING_PLAYER));
208 private @Nullable String getSubPath(int id) throws FreeboxException {
209 String subPath = subPaths.get(id);
210 if (subPath != null) {
213 getDevices().stream().filter(Player::apiAvailable).forEach(player -> {
214 String baseUrl = player.baseUrl();
215 if (baseUrl != null) {
216 subPaths.put(player.id, baseUrl);
219 return subPaths.get(id);
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;
227 // The player API does not allow to directly request a given player like others api parts
229 public Player getDevice(int id) throws FreeboxException {
230 return Objects.requireNonNull(getDevices().stream().filter(player -> player.id == id).findFirst().orElse(null));
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;
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);
242 uriBuilder.queryParam("long", true);
245 uriBuilder.queryParam("repeat", count);
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
254 public boolean reboot(int id) throws FreeboxException {
255 String subPath = getSubPath(id);
256 if (subPath != null) {
257 post(subPath, SYSTEM_PATH, REBOOT_ACTION);