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