2 * Copyright (c) 2010-2023 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;
22 import javax.ws.rs.core.UriBuilder;
24 import org.eclipse.jdt.annotation.NonNullByDefault;
25 import org.eclipse.jdt.annotation.Nullable;
26 import org.eclipse.jetty.http.HttpMethod;
27 import org.openhab.binding.freeboxos.internal.api.FreeboxException;
28 import org.openhab.binding.freeboxos.internal.api.Response;
29 import org.openhab.binding.freeboxos.internal.api.rest.PlayerManager.Metadata.PlaybackState;
30 import org.openhab.binding.freeboxos.internal.api.rest.PlayerManager.Metadata.SubtitleTrack;
31 import org.openhab.binding.freeboxos.internal.api.rest.PlayerManager.Metadata.VideoTrack;
32 import org.openhab.binding.freeboxos.internal.api.rest.PlayerManager.PlayerContext.PlayerDetails;
33 import org.openhab.binding.freeboxos.internal.api.rest.SystemManager.ModelInfo;
35 import com.google.gson.annotations.SerializedName;
37 import inet.ipaddr.mac.MACAddress;
40 * The {@link PlayerManager} is the Java class used to handle api requests related to player
42 * @author Gaƫl L'hopital - Initial contribution
45 public class PlayerManager extends ListableRest<PlayerManager.Player, PlayerManager.PlayerResponse> {
46 private static final String STATUS_PATH = "status";
48 protected static class PlayerResponse extends Response<Player> {
51 public enum DeviceModel {
52 FBX7HD_DELTA, // Freebox Player Devialet
63 public static record Player(MACAddress mac, StbType stbType, int id, ZonedDateTime lastTimeReachable,
64 boolean apiAvailable, String deviceName, DeviceModel deviceModel, boolean reachable, String uid,
65 @Nullable String apiVersion, List<String> lanGids) {
66 private enum StbType {
75 * @return a string like eg: '17/api/v8'
77 private @Nullable String baseUrl() {
78 String api = apiVersion;
79 return api != null ? "%d/api/v%s/".formatted(id, api.split("\\.")[0]) : null;
83 private static class StatusResponse extends Response<Status> {
86 public enum PowerState {
92 public static record Status(PowerState powerState, StatusInformation player,
93 @Nullable ForegroundApp foregroundApp) {
95 public @Nullable ForegroundApp foregroundApp() {
100 public static record ForegroundApp(int packageId, @Nullable String curlUrl, @Nullable Object context,
101 @SerializedName(value = "package") String _package) {
104 private static record StatusInformation(String name, ZonedDateTime lastActivity) {
107 private static class ConfigurationResponse extends Response<Configuration> {
110 public static record Configuration(String boardName, boolean configured, String firmwareVersion,
111 @Nullable ModelInfo modelInfo, String serial, String uptime, long uptimeVal) {
114 private enum MediaState {
119 private static record AudioTrack(int bitrate, @SerializedName("channelCount") int channelCount,
120 @Nullable String codec, @SerializedName("codecId") @Nullable String codecId, @Nullable String language,
121 @SerializedName("metadataId") @Nullable String metadataId, int pid, int samplerate, long uid) {
130 protected static record Metadata(@Nullable String album,
131 @SerializedName("albumArtist") @Nullable String albumArtist, @Nullable String artist,
132 @Nullable String author, int bpm, @Nullable String comment, boolean compilation, @Nullable String composer,
133 @Nullable String container, @Nullable String copyright, long date,
134 @SerializedName("discId") @Nullable String discId, @SerializedName("discNumber") int discNumber,
135 @SerializedName("discTotal") int discTotal, @Nullable String genre,
136 @SerializedName("musicbrainzDiscId") @Nullable String musicbrainzDiscId, @Nullable String performer,
137 @Nullable String title, @SerializedName("trackNumber") int trackNumber,
138 @SerializedName("trackTotal") int trackTotal, @Nullable String url) {
140 protected enum PlaybackState {
146 protected static record SubtitleTrack(@Nullable String codec, @Nullable String language, @Nullable String pid,
147 Type type, @Nullable String uid) {
150 protected static record VideoTrack(int bitrate, @Nullable String codec, int height, int pid, int uid,
155 public static record PlayerContext(@Nullable PlayerDetails player) {
156 public static record PlayerDetails(@SerializedName("audioIndex") int audioIndex,
157 @SerializedName("audioList") List<AudioTrack> audioList, @SerializedName("curPos") long curPos,
158 int duration, @SerializedName("livePos") long livePos, @SerializedName("maxPos") long maxPos,
159 @SerializedName("mediaState") MediaState mediaState, @Nullable Metadata metadata,
160 @SerializedName("minPos") long minPos, @SerializedName("playbackState") PlaybackState playbackState,
161 long position, @Nullable String source, @SerializedName("subtitleIndex") int subtitleIndex,
162 @SerializedName("subtitleList") List<SubtitleTrack> subtitleList,
163 @SerializedName("videoIndex") int videoIndex, @SerializedName("videoList") List<VideoTrack> videoList) {
167 private enum BouquetType {
172 private enum ChannelType {
177 private static record Service(long id, @Nullable String name,
178 @SerializedName("qualityLabel") @Nullable String qualityLabel,
179 @SerializedName("qualityName") @Nullable String qualityName, @SerializedName("sortInfo") int sortInfo,
180 @SerializedName("typeLabel") @Nullable String typeLabel,
181 @SerializedName("typeName") @Nullable String typeName, @Nullable String url) {
184 private static record Channel(@SerializedName("bouquetId") long bouquetId,
185 @SerializedName("bouquetName") @Nullable String bouquetName,
186 @SerializedName("bouquetType") BouquetType bouquetType,
187 @SerializedName("channelName") @Nullable String channelName,
188 @SerializedName("channelNumber") int channelNumber,
189 @SerializedName("channelSubNumber") int channelSubNumber,
190 @SerializedName("channelType") ChannelType channelType,
191 @SerializedName("channelUuid") @Nullable String channelUuid,
192 @SerializedName("currentServiceIndex") int currentServiceIndex,
193 @SerializedName("isTimeShifting") boolean isTimeShifting, List<Service> services,
194 @SerializedName("videoIsVisible") boolean videoIsVisible) {
197 public static record TvContext(@Nullable Channel channel, @Nullable PlayerDetails player) {
200 private final Map<Integer, @Nullable String> subPaths = new HashMap<>();
202 public PlayerManager(FreeboxOsSession session) throws FreeboxException {
203 super(session, LoginManager.Permission.PLAYER, PlayerResponse.class,
204 session.getUriBuilder().path(THING_PLAYER));
207 private @Nullable String getSubPath(int id) throws FreeboxException {
208 String subPath = subPaths.get(id);
209 if (subPath != null) {
212 getDevices().stream().filter(Player::apiAvailable).forEach(player -> {
213 String baseUrl = player.baseUrl();
214 if (baseUrl != null) {
215 subPaths.put(player.id, baseUrl);
218 return subPaths.get(id);
221 public @Nullable Status getPlayerStatus(int id) throws FreeboxException {
222 String subPath = getSubPath(id);
223 return subPath != null ? getSingle(StatusResponse.class, subPath, STATUS_PATH) : null;
226 // The player API does not allow to directly request a given player like others api parts
228 public Player getDevice(int id) throws FreeboxException {
229 return getDevices().stream().filter(player -> player.id == id).findFirst().orElse(null);
232 public @Nullable Configuration getConfig(int id) throws FreeboxException {
233 String subPath = getSubPath(id);
234 return subPath != null ? getSingle(ConfigurationResponse.class, subPath, SYSTEM_PATH) : null;
237 public void sendKey(String ip, String code, String key, boolean longPress, int count) {
238 UriBuilder uriBuilder = UriBuilder.fromPath("pub").scheme("http").host(ip).path("remote_control");
239 uriBuilder.queryParam("code", code).queryParam("key", key);
241 uriBuilder.queryParam("long", true);
244 uriBuilder.queryParam("repeat", count);
247 session.execute(uriBuilder.build(), HttpMethod.GET, GenericResponse.class, null);
248 } catch (FreeboxException ignore) {
249 // This call does not return anything, we can safely ignore
253 public boolean reboot(int id) throws FreeboxException {
254 String subPath = getSubPath(id);
255 if (subPath != null) {
256 post(subPath, SYSTEM_PATH, REBOOT_ACTION);