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, 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));
205 getDevices().stream().filter(Player::apiAvailable).forEach(player -> {
206 String baseUrl = player.baseUrl();
207 if (baseUrl != null) {
208 subPaths.put(player.id, baseUrl);
213 public Status getPlayerStatus(int id) throws FreeboxException {
214 return getSingle(StatusResponse.class, subPaths.get(id), STATUS_PATH);
217 // The player API does not allow to directly request a given player like others api parts
219 public Player getDevice(int id) throws FreeboxException {
220 return getDevices().stream().filter(player -> player.id == id).findFirst().orElse(null);
223 public Configuration getConfig(int id) throws FreeboxException {
224 return getSingle(ConfigurationResponse.class, subPaths.get(id), SYSTEM_PATH);
227 public void sendKey(String ip, String code, String key, boolean longPress, int count) {
228 UriBuilder uriBuilder = UriBuilder.fromPath("pub").scheme("http").host(ip).path("remote_control");
229 uriBuilder.queryParam("code", code).queryParam("key", key);
231 uriBuilder.queryParam("long", true);
234 uriBuilder.queryParam("repeat", count);
237 session.execute(uriBuilder.build(), HttpMethod.GET, GenericResponse.class, null);
238 } catch (FreeboxException ignore) {
239 // This call does not return anything, we can safely ignore
243 public void reboot(int id) throws FreeboxException {
244 post(subPaths.get(id), SYSTEM_PATH, REBOOT_ACTION);