001package org.intellimate.izou.sdk.frameworks.music.user; 002 003import org.intellimate.izou.identification.Identifiable; 004import org.intellimate.izou.identification.Identification; 005import org.intellimate.izou.identification.IdentificationManager; 006import org.intellimate.izou.resource.ResourceModel; 007import org.intellimate.izou.sdk.Context; 008import org.intellimate.izou.sdk.frameworks.music.Capabilities; 009import org.intellimate.izou.sdk.frameworks.music.player.Playlist; 010import org.intellimate.izou.sdk.frameworks.music.player.TrackInfo; 011import org.intellimate.izou.sdk.frameworks.music.player.Volume; 012import org.intellimate.izou.sdk.frameworks.music.resources.*; 013import org.intellimate.izou.sdk.util.AddOnModule; 014 015import java.util.ArrayList; 016import java.util.List; 017import java.util.Optional; 018import java.util.concurrent.CompletableFuture; 019import java.util.concurrent.ExecutionException; 020import java.util.concurrent.TimeUnit; 021import java.util.concurrent.TimeoutException; 022 023/** 024 * this is a simple class which should, added as a resource to an Event, request the Player to play the selected 025 * track or playlist. 026 * <p> 027 * To use this class the player has to meet a few criteria:<br> 028 * <ul> 029 * <li>the player must exist and be support the standard defined through the sdk</li> 030 * <li>the players-capabilities must allow requests from outside</li> 031 * <li>(maybe, depends on the create-method) the players-capabilities must allow a requests with specified a specified playlist/trackInfo</li> 032 * </ul><br> 033 * an example:<br> 034 * <pre> 035 * {@code 036 * 037 * Identification playerID; 038 * Playlist playlist; 039 * List<ResourceModel> resources = PlayerRequest.createPlayerRequest(playlist, player, this) //create a new PlayerRequest 040 * .map(PlayerRequest::resourcesForExisting) //creates the resources needed to add to the event 041 * .orElseGet(ArrayList::new); 042 * } 043 * </pre> 044 * @author LeanderK 045 * @version 1.0 046 */ 047public class PlayerRequest { 048 private final TrackInfo trackInfo; 049 private final Playlist playlist; 050 private final boolean permanent; 051 private final Identification player; 052 private final Capabilities capabilities; 053 private final Context context; 054 private final Identifiable identifiable; 055 private Volume volume = null; 056 057 /** 058 * internal Constructor 059 * @param trackInfo the trackInfo 060 * @param playlist the playlist 061 * @param permanent whether the Request is permanent 062 * @param player the player 063 * @param capabilities the capabilities 064 * @param context the context 065 * @param identifiable the identifiable 066 */ 067 protected PlayerRequest(TrackInfo trackInfo, Playlist playlist, boolean permanent, Identification player, 068 Capabilities capabilities, Context context, Identifiable identifiable) { 069 this.trackInfo = trackInfo; 070 this.playlist = playlist; 071 this.permanent = permanent; 072 this.player = player; 073 this.capabilities = capabilities; 074 this.context = context; 075 this.identifiable = identifiable; 076 } 077 078 /** 079 * internal Constructor 080 * @param trackInfo the trackInfo 081 * @param playlist the playlist 082 * @param permanent whether the Request is permanent 083 * @param player the player 084 * @param capabilities the capabilities 085 * @param context the Context 086 * @param identifiable the identifiable 087 * @param volume the Volume to set to 088 */ 089 protected PlayerRequest(TrackInfo trackInfo, Playlist playlist, boolean permanent, Identification player, 090 Capabilities capabilities, Context context, Identifiable identifiable, Volume volume) { 091 this.trackInfo = trackInfo; 092 this.playlist = playlist; 093 this.permanent = permanent; 094 this.player = player; 095 this.capabilities = capabilities; 096 this.context = context; 097 this.identifiable = identifiable; 098 this.volume = volume; 099 } 100 101 /** 102 * sets the Volume if the Player supports it. 103 * @param volume the Volume to set to 104 * @return true if the Player supports setting the Volume 105 */ 106 public boolean setVolume(Volume volume) { 107 if (capabilities.canChangeVolume()) { 108 this.volume = volume; 109 return true; 110 } 111 return false; 112 } 113 114 /** 115 * tries to set the Volume of the PlayerRequest. 116 * <p> 117 * if the Player supports the Change of the Volume, it will create a new PlayerRequest and return it, if not it 118 * returns this. 119 * </p> 120 * @param volume the Volume to set to 121 * @return a new PlayerRequest or this. 122 */ 123 public PlayerRequest trySetVolume(Volume volume) { 124 if (capabilities.canChangeVolume()) { 125 return new PlayerRequest(trackInfo, playlist, permanent, player, capabilities, context, identifiable, volume); 126 } 127 return this; 128 } 129 130 /** 131 * returns a List of Resources that can be added to an already existing event. 132 * <p> 133 * This causes the Addon to block the Event in the OutputPlugin lifecycle of the Event. 134 * @return a List of Resources 135 */ 136 @SuppressWarnings("unused") 137 public List<ResourceModel> resourcesForExisting() { 138 List<ResourceModel> resourceModels = new ArrayList<>(); 139 IdentificationManager.getInstance().getIdentification(identifiable) 140 .map(id -> new MusicUsageResource(id, true)) 141 .ifPresent(resourceModels::add); 142 if (volume != null) { 143 IdentificationManager.getInstance().getIdentification(identifiable) 144 .map(id -> new VolumeResource(id, volume)) 145 .ifPresent(resourceModels::add); 146 } 147 if (playlist != null) { 148 IdentificationManager.getInstance().getIdentification(identifiable) 149 .map(id -> new PlaylistResource(id, playlist)) 150 .ifPresent(resourceModels::add); 151 } 152 if (trackInfo != null) { 153 IdentificationManager.getInstance().getIdentification(identifiable) 154 .map(id -> new TrackInfoResource(id, trackInfo)) 155 .ifPresent(resourceModels::add); 156 } 157 return resourceModels; 158 } 159 160 /** 161 * helper method for PlaylistSelector 162 * @param playlist the playlist selected 163 * @param permanent the permanent addon 164 * @param player the player 165 * @param capabilities the capabilities 166 * @param context the context 167 * @param identifiable the identifiable 168 * @return a PlayerRequest 169 */ 170 static PlayerRequest createPlayerRequest(Playlist playlist, boolean permanent, Identification player, Capabilities capabilities, Context context, Identifiable identifiable) { 171 return new PlayerRequest(null, playlist, permanent, player, capabilities, context, identifiable); 172 } 173 174 /** 175 * creates a new PlayerRequest. 176 * <p> 177 * For this method to return a non-empty Optional the following criteria must be met:<br> 178 * <ul> 179 * <li>the player must exist and be support the standard defined through the sdk</li> 180 * <li>the players-capabilities must allow requests from outside</li> 181 * </ul> 182 * @param permanent true means the player can play indefinitely, but only if no one is currently using audio as 183 * permanent. It will also not block. false is limited to 10 minutes playback, but will block. 184 * @param player the player to target 185 * @param source the addOnModule used for Context etc. 186 * @return the optional PlayerRequest 187 */ 188 @SuppressWarnings("unused") 189 public static Optional<PlayerRequest> createPlayerRequest(boolean permanent, Identification player, AddOnModule source) { 190 if (player == null || source == null) 191 return Optional.empty(); 192 try { 193 return source.getContext().getResources() 194 .generateResource(new CapabilitiesResource(player)) 195 .orElse(CompletableFuture.completedFuture(new ArrayList<>())) 196 .thenApply(list -> list.stream() 197 .filter(resourceModel -> resourceModel.getProvider().equals(player)) 198 .findAny() 199 .flatMap(resource -> Capabilities.importFromResource(resource, source.getContext())) 200 ).get(1, TimeUnit.SECONDS) 201 .filter(capabilities -> { 202 if (!capabilities.handlesPlayRequestFromOutside()) { 203 source.getContext().getLogger().error("player does not handle play-request from outside"); 204 return false; 205 } 206 return true; 207 }) 208 .map(capabilities -> new PlayerRequest(null, null, permanent, player, capabilities, source.getContext(), source)); 209 } catch (InterruptedException | ExecutionException | TimeoutException e) { 210 source.getContext().getLogger().error("unable to obtain capabilities"); 211 return Optional.empty(); 212 } 213 } 214 215 /** 216 * creates a new PlayerRequest. 217 * <p> 218 * the resulting PlayerRequest is not permanent, which means that it will mute all other sound but is limited to 219 * 10 minutes.<br> 220 * For this method to return a non-empty Optional the following criteria must be met:<br> 221 * <ul> 222 * <li>the player must exist and be support the standard defined through the sdk</li> 223 * <li>the players-capabilities must allow requests from outside</li> 224 * <li>the players-capabilities must allow a requests with specified a specified playlist/trackInfo</li> 225 * </ul> 226 * @param trackInfo the trackInfo to pass with the request 227 * @param player the player to target 228 * @param source the addOnModule used for Context etc. 229 * @return the optional PlayerRequest 230 */ 231 @SuppressWarnings("unused") 232 public static Optional<PlayerRequest> createPlayerRequest(TrackInfo trackInfo, Identification player, AddOnModule source) { 233 return createPlayerRequest(trackInfo, false, player, source); 234 } 235 236 /** 237 * creates a new PlayerRequest. 238 * <p> 239 * For this method to return a non-empty Optional the following criteria must be met:<br> 240 * <ul> 241 * <li>the player must exist and be support the standard defined through the sdk</li> 242 * <li>the players-capabilities must allow requests from outside</li> 243 * <li>the players-capabilities must allow a requests with specified a specified playlist/trackInfo</li> 244 * </ul> 245 * @param trackInfo the trackInfo to pass with the request 246 * @param permanent true means the player can play indefinitely, but only if no one is currently using audio as 247 * permanent. It will also not block. false is limited to 10 minutes playback, but will block. 248 * @param player the player to target 249 * @param source the addOnModule used for Context etc. 250 * @return the optional PlayerRequest 251 */ 252 @SuppressWarnings("unused") 253 public static Optional<PlayerRequest> createPlayerRequest(TrackInfo trackInfo, boolean permanent, Identification player, AddOnModule source) { 254 if (trackInfo == null ||player == null || source == null) 255 return Optional.empty(); 256 try { 257 return source.getContext().getResources() 258 .generateResource(new CapabilitiesResource(player)) 259 .orElse(CompletableFuture.completedFuture(new ArrayList<>())) 260 .thenApply(list -> list.stream() 261 .filter(resourceModel -> resourceModel.getProvider().equals(player)) 262 .findAny() 263 .flatMap(resource -> Capabilities.importFromResource(resource, source.getContext())) 264 ).get(1, TimeUnit.SECONDS) 265 .filter(capabilities -> { 266 if (!capabilities.handlesPlayRequestFromOutside()) { 267 source.getContext().getLogger().error("player does not handle play-request from outside"); 268 return false; 269 } 270 if (!capabilities.hasPlayRequestDetailed()) { 271 source.getContext().getLogger().error("player does not handle trackInfo-request from outside"); 272 return false; 273 } 274 return true; 275 }) 276 .map(capabilities -> new PlayerRequest(trackInfo, null, permanent, player, capabilities, source.getContext(), source)); 277 } catch (InterruptedException | ExecutionException | TimeoutException e) { 278 source.getContext().getLogger().error("unable to obtain capabilities"); 279 return Optional.empty(); 280 } 281 } 282 283 /** 284 * creates a new PlayerRequest. 285 * <p> 286 * the resulting PlayerRequest is not permanent, which means that it will mute all other sound but is limited to 287 * 10 minutes.<br> 288 * For this method to return a non-empty Optional the following criteria must be met:<br> 289 * <ul> 290 * <li>the player must exist and be support the standard defined through the sdk</li> 291 * <li>the players-capabilities must allow requests from outside</li> 292 * <li>the players-capabilities must allow a requests with specified a specified playlist/trackInfo</li> 293 * </ul> 294 * @param playlist the playlist to pass with the request 295 * @param player the player to target 296 * @param source the addOnModule used for Context etc. 297 * @return the optional PlayerRequest 298 */ 299 @SuppressWarnings("unused") 300 public static Optional<PlayerRequest> createPlayerRequest(Playlist playlist, Identification player, AddOnModule source) { 301 return createPlayerRequest(playlist, false, player, source); 302 } 303 304 /** 305 * creates a new PlayerRequest. 306 * <p> 307 * For this method to return a non-empty Optional the following criteria must be met:<br> 308 * <ul> 309 * <li>the player must exist and be support the standard defined through the sdk</li> 310 * <li>the players-capabilities must allow requests from outside</li> 311 * <li>the players-capabilities must allow a requests with specified a specified playlist/trackInfo</li> 312 * </ul> 313 * @param playlist the playlist to pass with the request 314 * @param permanent true means the player can play indefinitely, but only if no one is currently using audio as 315 * permanent. It will also not block. false is limited to 10 minutes playback, but will block. 316 * @param player the player to target 317 * @param source the addOnModule used for Context etc. 318 * @return the optional PlayerRequest 319 */ 320 @SuppressWarnings("unused") 321 public static Optional<PlayerRequest> createPlayerRequest(Playlist playlist, boolean permanent, Identification player, AddOnModule source) { 322 if (playlist == null ||player == null || source == null) 323 return Optional.empty(); 324 try { 325 return source.getContext().getResources() 326 .generateResource(new CapabilitiesResource(player)) 327 .orElse(CompletableFuture.completedFuture(new ArrayList<>())) 328 .thenApply(list -> list.stream() 329 .filter(resourceModel -> resourceModel.getProvider().equals(player)) 330 .findAny() 331 .flatMap(resource -> Capabilities.importFromResource(resource, source.getContext())) 332 ).get(1, TimeUnit.SECONDS) 333 .filter(capabilities -> { 334 if (!capabilities.handlesPlayRequestFromOutside()) { 335 source.getContext().getLogger().error("player does not handle play-request from outside"); 336 return false; 337 } 338 if (!capabilities.hasPlayRequestDetailed()) { 339 source.getContext().getLogger().error("player does not handle playlist-request from outside"); 340 return false; 341 } 342 if (!playlist.verify(capabilities)) { 343 source.getContext().getLogger().error("player can not handle the playlist, probably illegal PlaybackModes"); 344 return false; 345 } 346 return true; 347 }) 348 .map(capabilities -> new PlayerRequest(null, playlist, permanent, player, capabilities, source.getContext(), source)); 349 } catch (InterruptedException | ExecutionException | TimeoutException e) { 350 source.getContext().getLogger().debug("unable to obtain capabilities"); 351 return Optional.empty(); 352 } 353 } 354}