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