Persoenliche Mediazentrale
undisclosed
2023-01-05 1ff360777df36ba9980000823e1131d3e05fdeb7
commit | author | age
b56bb3 1 /*
94b1c2 2   Tango - Personal Media Center
b56bb3 3   Copyright (C) 2021  Ulrich Hilger
U 4
5   This program is free software: you can redistribute it and/or modify
6   it under the terms of the GNU Affero General Public License as
7   published by the Free Software Foundation, either version 3 of the
8   License, or (at your option) any later version.
9
10   This program is distributed in the hope that it will be useful,
11   but WITHOUT ANY WARRANTY; without even the implied warranty of
12   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13   GNU Affero General Public License for more details.
14
15   You should have received a copy of the GNU Affero General Public License
16   along with this program.  If not, see <https://www.gnu.org/licenses/>.
17  */
94b1c2 18 package de.uhilger.tango.api;
b56bb3 19
1c5fa4 20 import com.google.gson.Gson;
b56bb3 21 import com.sun.net.httpserver.HttpExchange;
94b1c2 22 import de.uhilger.tango.App;
U 23 import de.uhilger.tango.Server;
24 import de.uhilger.tango.entity.Abspielvorgang;
25 import de.uhilger.tango.entity.Abspieler;
26 import de.uhilger.tango.entity.Abspielliste;
27 import de.uhilger.tango.entity.Einstellung;
28 import de.uhilger.tango.entity.Entity;
29 import de.uhilger.tango.entity.Livestream;
30 import de.uhilger.tango.entity.Titel;
31 import de.uhilger.tango.store.FileStorage;
32 import de.uhilger.tango.store.Storage;
005d7a 33 import java.io.IOException;
U 34 import java.net.HttpURLConnection;
35 import java.net.URL;
b56bb3 36 import java.util.HashMap;
U 37 import java.util.Map;
0e9cd3 38 import java.util.logging.Level;
b56bb3 39 import java.util.logging.Logger;
U 40
41 /**
0e9cd3 42  * Die MediaSteuerung verarbeitet HTTP-Signale zur Steuerung von Media-Operationen wie z.B. dem
U 43  * Spielen einer Abspielliste oder dem Starten oder Stoppen eines Videos auf einem entfernten
44  * Abspielgeraet.
45  *
46  * HTTP GET /mz/api/strg/abspieler/play/liste/[name] 
47  * HTTP GET /mz/api/strg/abspieler/ende
48  *
d027b5 49  * HTTP POST /mz/api/strg/abspieler/play/titel mit dem Titel im Body
U 50  * HTTP POST /mz/api/strg/abspieler/play/stream mit dem Livestream im Body (nur Name gefuellt)
c6fdc4 51  * 
U 52  * HTTP POST /mz/api/strg/abspieler/weiter/titel mit dem Titel im Body
0e9cd3 53  *
589850 54  * HTTP GET /mz/api/strg/abspieler/pause 
U 55  * HTTP GET /mz/api/strg/abspieler/stop 
792b21 56  * HTTP GET /mz/api/strg/abspieler/seek/[sekunden]
b56bb3 57  * 
0e9cd3 58  * Faustregel: Anzahl Elemente eines URL plus 1 ist die Anzahl der Elemente des 
U 59  * Ergebnisses von String.split.
60  *
1ff360 61  * Mit der Funktion ende liefert die MediaSteuerung die Moeglichkeit, Titel aus 
U 62  * einer Abspielliste gesteuert von Tango abzuspielen. Die Nutzung der Funktion wird 
63  * ausgeloest vom Aufruf an abspieler/play/liste/[name]. Tango erwartet dann von einem 
64  * Abspieler die Rueckmeldung, dass dieser den zuletzt von Tango an den 
65  * Abspieler zum Abspielen uebermittelten Titel zuende abgespielt hat. Tango 
66  * uebergibt dem Abspieler dann den naechsten Titel, bis die Abspielliste zuende ist.
67  * 
68  * Diese Form unterscheidet sich vom Abspielen einer Abspielliste als einzelner 
69  * Stream, wie es vom StreamHandler realisiert wird.
0e9cd3 70  *
b56bb3 71  * @author Ulrich Hilger
U 72  * @version 1, 9.4.2021
73  */
74 public class MediaSteuerung extends AbstractHandler {
75
76   private static final Logger logger = Logger.getLogger(MediaSteuerung.class.getName());
0e9cd3 77
f9f819 78   public static final String PL_CMD_PLAY = "play";
792b21 79   public static final String PL_CMD_SEEK = "seek";
0e9cd3 80   public static final String PL_DEFAULT_PARAMS = "?titel=";
005d7a 81   public static final String PL_PARAM_RUECK = "&r=";
960359 82   public static final String PL_API_STRG = "api/strg/"; 
0e9cd3 83   public static final String PL_CMD_ENDE = "ende";
589850 84   public static final String PL_CMD_STOP = "stop";
392dc9 85   public static final String PL_CMD_VOLDN = "voldn";
U 86   public static final String PL_CMD_VOLUP = "volup";
589850 87   public static final String PL_CMD_PAUSE = "pause";
c6fdc4 88   public static final String PL_CMD_PLAYON = "weiter";
f9f819 89   public static final String PL_CMD_CALYPSO_STOP = "stop";
392dc9 90   public static final String PL_CMD_CALYPSO_VOL_INC = "vol-inc";
U 91   public static final String PL_CMD_CALYPSO_VOL_DEC = "vol-dec";
f9f819 92   public static final String PL_CMD_CALYPSO_PAUSE = "pause";
U 93   public static final String PL_CMD_CALYPSO_PLAYON = "playon";
005d7a 94   public static final String DEFAULT_HOST = "http://localhost:9090";
0e9cd3 95
ad3e2d 96   public static final String RB_HOST = "host";
U 97   public static final String RB_PLAYERPARAMS = "playerparams";
98   
0e9cd3 99   private final Map spielt = new HashMap();
f70acb 100   
U 101   private String conf;
102   
103   public MediaSteuerung(String conf) {
104     this.conf = conf;
105   }
b56bb3 106
U 107   @Override
108   protected String get(HttpExchange e) {
0e9cd3 109     String response;
b56bb3 110     String path = e.getRequestURI().toString();
0e9cd3 111     String[] elems = path.split(Server.SLASH);
f70acb 112     FileStorage fs = new FileStorage(conf);
589850 113     logger.fine(path);
0e9cd3 114     
U 115     // Faustregel: Anzahl Elemente eines URL plus 1 ist die Anzahl der Elemente des 
116     // Ergebnisses von String.split.
117     switch (elems.length) {
118       case 6:
119         if (elems[5].equalsIgnoreCase(PL_CMD_ENDE)) {
120           response = naechsterTitel(fs, elems[4]);
589850 121         } else if(elems[5].equalsIgnoreCase(PL_CMD_STOP)) {
d12f6e 122           spielt.remove(elems[4]);
4bbb9f 123           response = kommandoSenden(fs, elems[4], PL_CMD_CALYPSO_STOP);
392dc9 124         } else if(elems[5].equalsIgnoreCase(PL_CMD_VOLDN)) {
U 125           response = kommandoSenden(fs, elems[4], PL_CMD_CALYPSO_VOL_DEC);
126         } else if(elems[5].equalsIgnoreCase(PL_CMD_VOLUP)) {
127           response = kommandoSenden(fs, elems[4], PL_CMD_CALYPSO_VOL_INC);
589850 128         } else if(elems[5].equalsIgnoreCase(PL_CMD_PAUSE)) {
4bbb9f 129           response = kommandoSenden(fs, elems[4], PL_CMD_CALYPSO_PAUSE);
c6fdc4 130         //} else if(elems[5].equalsIgnoreCase(PL_CMD_PLAYON)) {
U 131         //  response = kommandoSenden(fs, elems[4], PL_CMD_CALYPSO_PLAYON);
0e9cd3 132         } else {
U 133           response = meldung("Ungueltiges Kommando: " + elems[5], AbstractHandler.RTC_NOT_FOUND);
134         }
135         break;
792b21 136       case 7:
U 137         if (elems[5].equalsIgnoreCase(PL_CMD_SEEK)) {
138           // /calypso/seek?pos=[sekunden]
139           response = kommandoSenden(fs, elems[4], "seek?pos=" + elems[6]);
140         } else {
141           response = meldung("Ungueltiges Kommando: " + elems[5], AbstractHandler.RTC_NOT_FOUND);
142         }
143         break;
b56bb3 144       case 8:
1c5fa4 145         response = ersterTitel(fs, elems[4], elems[7]);
0e9cd3 146         break;
U 147       default:
148         response = "Ungueltiger URL";
b56bb3 149         break;
U 150     }
151     return response;
589850 152   }
1c5fa4 153
U 154   @Override
155   protected String post(HttpExchange e) {
156     String response;
c6fdc4 157     String abspielerKmd = PL_CMD_PLAY;
U 158     String path = e.getRequestURI().toString();
159     String[] elems = path.split(Server.SLASH);
160     logger.info(elems[5]);
161     if(elems[5].equalsIgnoreCase(PL_CMD_PLAYON)) {
162       abspielerKmd = PL_CMD_CALYPSO_PLAYON;
163     } else if(elems[5].equalsIgnoreCase(PL_CMD_PLAY)){
164       abspielerKmd = PL_CMD_PLAY;
165     }
1c5fa4 166     try {
c6fdc4 167       return urlAbspielen(e, abspielerKmd);
1c5fa4 168     } catch (IOException ex) {
U 169       logger.log(Level.SEVERE, null, ex);
170       return meldung(ex.getLocalizedMessage(), 404);
171     }
172   }
589850 173   
1c5fa4 174   // titel.katalogUrl + titel.pfad + titel.name
c6fdc4 175   private String urlAbspielen(HttpExchange e, String abspielerKmd) throws IOException {
1c5fa4 176     String path = e.getRequestURI().toString();
U 177     String[] elems = path.split(Server.SLASH);
f70acb 178     FileStorage fs = new FileStorage(conf);
c6fdc4 179     if(elems[6].equalsIgnoreCase("titel")) {
d027b5 180       String titelJson = bodyLesen(e);
U 181       Gson gson = new Gson();
182       Object o = gson.fromJson(titelJson, fs.typeFromName(Titel.class.getSimpleName()).getType());
183       if(o instanceof Titel) {
184         Titel titel = (Titel) o;
185         String titelUrl = titel.getKatalogUrl() + titel.getPfad() + titel.getName();
186         Entity entity = fs.read(FileStorage.ST_ABSPIELER, elems[4]);
187         if (entity instanceof Abspieler) {
188           Abspieler abspieler = (Abspieler) entity;
ad3e2d 189           String server = getEinstellung(fs, getResString(RB_HOST), DEFAULT_HOST);
c6fdc4 190           String signal = abspielKommando(fs, abspieler, server, titelUrl, abspielerKmd).toString();
d027b5 191           abspielerKommandoSenden(signal);
U 192           return signal + "gesendet.";
193         } else {
194           return meldung("Ungueltiger Abspieler.", 404);
195         }
1c5fa4 196       } else {
d027b5 197         return meldung("Ungueltiger Titel.", 404);
1c5fa4 198       }
c6fdc4 199     } else if(elems[6].equalsIgnoreCase("stream")) {
d027b5 200       String streamJson = bodyLesen(e);
U 201       Gson gson = new Gson();
202       Object o = gson.fromJson(streamJson, fs.typeFromName(Livestream.class.getSimpleName()).getType());
203       if(o instanceof Livestream) {
204         Entity entity = fs.read(FileStorage.ST_LIVESTREAM, ((Livestream) o).getName());
205         if(entity instanceof Livestream) {
206           Livestream stream = (Livestream) entity;
207           entity = fs.read(FileStorage.ST_ABSPIELER, elems[4]);
208           if (entity instanceof Abspieler) {
209             Abspieler abspieler = (Abspieler) entity;
210             String server = "";
c6fdc4 211             String signal = abspielKommando(fs, abspieler, server, stream.getUrl(), PL_CMD_PLAY).toString();
d027b5 212             abspielerKommandoSenden(signal);
U 213             return signal + "gesendet.";
214           } else {
215             return meldung("Ungueltiger Abspieler.", 404);
216           }
217         } else {
218           return meldung("Ungueltiger Livestream.", 404); 
219         }
220       } else {
221        return meldung("Ungueltiger Livestream.", 404); 
222       }      
1c5fa4 223     } else {
d027b5 224       return meldung("Ungueltiger URL.", 404);
658c14 225     }
U 226   }
227   
c6fdc4 228   
589850 229   private String kommandoSenden(Storage s, String aName, String kommando) {
U 230     Entity entity = s.read(FileStorage.ST_ABSPIELER, aName);
231     if (entity instanceof Abspieler) {
232       Abspieler abspieler = (Abspieler) entity;
233       StringBuilder kmd = new StringBuilder();
234       kmd.append(abspieler.getUrl());
235       kmd.append(kommando);
9e14ef 236       String signal = kmd.toString();
c6fdc4 237       //String server = getEinstellung(s, App.getRs(App.RB_HOST), DEFAULT_HOST);
U 238       //String signal = abspielKommando(s, abspieler, server, stream.getUrl(), PL_CMD_PLAY).toString();
9e14ef 239       abspielerKommandoSenden(signal);
U 240       return signal + " gesendet.";
589850 241     } else {
U 242       return meldung("Abspielliste nicht gefunden.", AbstractHandler.RTC_NOT_FOUND);
243     }
b56bb3 244   }
c6fdc4 245   
b56bb3 246   
5f7e0b 247   private String ersterTitel(Storage s, String aName, String lName) {
U 248     String response;
249     Entity entity = s.read(FileStorage.ST_ABSPIELLISTE, lName);
250     if (entity instanceof Abspielliste) {
251       Abspielliste liste = (Abspielliste) entity;
252       response = listentitelSpielen(s, aName, liste, 0);
253     } else {
254       response = meldung("Abspielliste nicht gefunden.", AbstractHandler.RTC_NOT_FOUND);
255     }
256     return response;
0e9cd3 257   }
U 258
259   private String naechsterTitel(Storage s, String abspielerName) {
260     String response;
261     Object o = spielt.get(abspielerName);
262     if (o instanceof Abspielvorgang) {
263       Abspielvorgang av = (Abspielvorgang) o;
264       Entity entity = s.read(FileStorage.ST_ABSPIELLISTE, av.getListe());
265       if (entity instanceof Abspielliste) {
b56bb3 266         Abspielliste liste = (Abspielliste) entity;
0e9cd3 267         int titelNr = av.getTitelNr();
U 268         if (liste.getTitel().size() > ++titelNr) {
005d7a 269           response = listentitelSpielen(s, abspielerName, liste, titelNr);
0e9cd3 270         } else {
U 271           response = "Liste " + liste.getName() + " ist zuende gespielt.";
589850 272           logger.info(response);
0e9cd3 273         }
U 274       } else {
275         response = meldung("Abspielliste nicht gefunden.", AbstractHandler.RTC_NOT_FOUND);
b56bb3 276       }
0e9cd3 277       //response = listenTitelSpielen(e, elems[4]);
U 278     } else {
279       response = meldung("Abspielvorgang fuer Abspieler " + abspielerName, AbstractHandler.RTC_NOT_FOUND);
b56bb3 280     }
U 281     return response;
282   }
0e9cd3 283
005d7a 284   private String listentitelSpielen(Storage s, String aName, Abspielliste liste, int titelNr) {
0e9cd3 285     String response;
U 286     Entity entity = s.read(FileStorage.ST_ABSPIELER, aName);
287     if (entity instanceof Abspieler) {
288       Abspieler abspieler = (Abspieler) entity;
289       String kommando = kommandoFuerTitel(s, liste, abspieler, titelNr);
290       //String kommando = kmd.toString();
291       logger.info(kommando);
5f7e0b 292       abspielerKommandoSenden(kommando);
0e9cd3 293       response = "Abspielen der Liste " + liste.getName() + " auf Abspieler " + aName + " gestartet.";
U 294     } else {
295       response = meldung("Abspieler nicht gefunden.", AbstractHandler.RTC_NOT_FOUND);
296     }
297     return response;
298   }
299
300   /**
301    * Das Kommando zum Abspielen fuer den Titel einer Abspielliste und einen bestimmten Abspieler
302    * ermitteln.
303    *
304    * @param s die Ablage, in der Abspieler und Abspiellisten zu finden sind
305    * @param liste Name der Liste, die den gewuenschten Titel enthaelt
306    * @param abspieler Name des Abspielers, der zum Abspielen dienen soll
307    * @param titelNr Nummer des Titels in der Liste
308    * @return das Kommando zum Abspielen (ein URL)
309    */
310   private String kommandoFuerTitel(Storage s, Abspielliste liste, Abspieler abspieler, int titelNr) {
311     // ersten Titel aus Liste holen
312     Titel titel = liste.getTitel().get(titelNr);
313
314     // URL des Titels ermitteln
315     String titelUrl = titel.getKatalogUrl() + titel.getPfad() + titel.getName();
316     logger.log(Level.INFO, "abspielen von {0} auf {1}", new Object[]{titelUrl, abspieler.getUrl()});
317
318     // Titel als 'spielt' vermerken
319     Abspielvorgang vorgang = new Abspielvorgang();
320     vorgang.setAbspieler(abspieler.getName());
321     vorgang.setListe(liste.getName());
322     vorgang.setTitelNr(titelNr);
323     spielt.put(abspieler.getName(), vorgang);
005d7a 324     
ad3e2d 325     String server = getEinstellung(s, getResString(RB_HOST), DEFAULT_HOST);
0e9cd3 326
1c5fa4 327     /*
U 328
0e9cd3 329     // Kommando an den Abspieler zusammenbauen
U 330     StringBuilder kmd = new StringBuilder();
331     kmd.append(abspieler.getUrl());
332     kmd.append(PL_CMD_PLAY);
333     // Parameter fuer den Abspieler holen
005d7a 334     kmd.append(getEinstellung(s, App.getRs(App.RB_PLAYERPARAMS), PL_DEFAULT_PARAMS));
U 335     kmd.append(server);
0e9cd3 336     kmd.append(titelUrl);
1c5fa4 337     */
c6fdc4 338     StringBuilder kmd = abspielKommando(s, abspieler, server, titelUrl, PL_CMD_PLAY);
005d7a 339     kmd.append(PL_PARAM_RUECK);
U 340     kmd.append(server);
3a0556 341     if(!server.endsWith(Server.SLASH)) {
U 342       kmd.append(Server.SLASH);
343     }
005d7a 344     kmd.append(PL_API_STRG);
U 345     kmd.append(abspieler.getName());
346     kmd.append("/ende");
0e9cd3 347
U 348     return kmd.toString();
349   }
1c5fa4 350
c6fdc4 351   private StringBuilder abspielKommando(Storage s, Abspieler abspieler, String server, String titelUrl, String abspielKmd) {
1c5fa4 352     
U 353     // Kommando an den Abspieler zusammenbauen
354     StringBuilder kmd = new StringBuilder();
355     kmd.append(abspieler.getUrl());
c6fdc4 356     //kmd.append(PL_CMD_PLAY);
U 357     kmd.append(abspielKmd);
1c5fa4 358     // Parameter fuer den Abspieler holen
ad3e2d 359     kmd.append(getEinstellung(s, getResString(RB_PLAYERPARAMS), PL_DEFAULT_PARAMS));
1c5fa4 360     kmd.append(server);
U 361     kmd.append(titelUrl);
362
363     return kmd;    
364   }
005d7a 365   
U 366   private String getEinstellung(Storage s, String key, String standardWert) {
367     Entity entity = s.read(Einstellung.class.getSimpleName(), key);
368     if (entity instanceof Einstellung) {
369       Einstellung einstellung = (Einstellung) entity;
370       Object o = einstellung.getValue();
371       if(o instanceof String) {
372         return o.toString();
373       } else {
374         return standardWert;
375       }
376     } else {
377       return standardWert;
378     }
379   }
380   
381   private void abspielerKommandoSenden(String kommando) {
382     /*
383       TODO hier evtl. mit mehreren Versuchen ausgleichen, 
384       dass ein einzelner Versuch nicht 'durchkommt'...
385     */
386     logger.info(kommando);
387     try {
388       HttpURLConnection conn = (HttpURLConnection) new URL(kommando).openConnection();
389       conn.setRequestMethod("GET");
390       conn.connect();
391       int status = conn.getResponseCode();
392       String msg = conn.getResponseMessage();
393       logger.log(Level.INFO, "Kommando {0} mit Status {1} {2} gesendet.", new Object[]{kommando, status, msg});
394     } catch(IOException ex) {
395       logger.log(Level.INFO, ex.getMessage(), ex);
396     }
397   }
398   
5f7e0b 399   private String meldung(String text, int code) {
U 400     setReturnCode(code);
401     return text;
402   }
b56bb3 403 }