Persoenliche Mediazentrale
ulrich
2021-04-12 4f2589e620f467d117003a908a0fc1bf2f3c476a
commit | author | age
b56bb3 1 /*
U 2   Mediazentrale - Personal Media Center
3   Copyright (C) 2021  Ulrich Hilger
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  */
18 package de.uhilger.mediaz.api;
19
1c5fa4 20 import com.google.gson.Gson;
b56bb3 21 import com.sun.net.httpserver.HttpExchange;
U 22 import de.uhilger.mediaz.App;
23 import de.uhilger.mediaz.Server;
0e9cd3 24 import de.uhilger.mediaz.entity.Abspielvorgang;
b56bb3 25 import de.uhilger.mediaz.entity.Abspieler;
U 26 import de.uhilger.mediaz.entity.Abspielliste;
0e9cd3 27 import de.uhilger.mediaz.entity.Einstellung;
b56bb3 28 import de.uhilger.mediaz.entity.Entity;
U 29 import de.uhilger.mediaz.entity.Titel;
30 import de.uhilger.mediaz.store.FileStorage;
0e9cd3 31 import de.uhilger.mediaz.store.Storage;
005d7a 32 import java.io.IOException;
U 33 import java.net.HttpURLConnection;
34 import java.net.URL;
b56bb3 35 import java.util.HashMap;
U 36 import java.util.Map;
0e9cd3 37 import java.util.logging.Level;
b56bb3 38 import java.util.logging.Logger;
U 39
40 /**
0e9cd3 41  * Die MediaSteuerung verarbeitet HTTP-Signale zur Steuerung von Media-Operationen wie z.B. dem
U 42  * Spielen einer Abspielliste oder dem Starten oder Stoppen eines Videos auf einem entfernten
43  * Abspielgeraet.
44  *
45  * HTTP GET /mz/api/strg/abspieler/play/liste/[name] 
46  * HTTP GET /mz/api/strg/abspieler/ende
47  *
1c5fa4 48  * HTTP POST /mz/api/strg/abspieler/play mit dem Titel im Body
0e9cd3 49  *
589850 50  * HTTP GET /mz/api/strg/abspieler/pause 
U 51  * HTTP GET /mz/api/strg/abspieler/stop 
52  * HTTP GET /mz/api/strg/abspieler/weiter
b56bb3 53  * 
0e9cd3 54  * Faustregel: Anzahl Elemente eines URL plus 1 ist die Anzahl der Elemente des 
U 55  * Ergebnisses von String.split.
56  *
57  *
b56bb3 58  * @author Ulrich Hilger
U 59  * @version 1, 9.4.2021
60  */
61 public class MediaSteuerung extends AbstractHandler {
62
63   private static final Logger logger = Logger.getLogger(MediaSteuerung.class.getName());
0e9cd3 64
U 65   public static final String PL_CMD_PLAY = "avd/play";
66   public static final String PL_DEFAULT_PARAMS = "?titel=";
005d7a 67   public static final String PL_PARAM_RUECK = "&r=";
U 68   public static final String PL_API_STRG = "/api/strg/"; 
0e9cd3 69   public static final String PL_CMD_ENDE = "ende";
589850 70   public static final String PL_CMD_STOP = "stop";
U 71   public static final String PL_CMD_PAUSE = "pause";
72   public static final String PL_CMD_PLAYON = "playon";
73   public static final String PL_CMD_AVD_STOP = "avd/stop";
74   public static final String PL_CMD_AVD_PAUSE = "avd/pause";
75   public static final String PL_CMD_AVD_PLAYON = "avd/playon";
005d7a 76   public static final String DEFAULT_HOST = "http://localhost:9090";
0e9cd3 77
U 78   private final Map spielt = new HashMap();
b56bb3 79
U 80   @Override
81   protected String get(HttpExchange e) {
0e9cd3 82     String response;
b56bb3 83     String path = e.getRequestURI().toString();
0e9cd3 84     String[] elems = path.split(Server.SLASH);
U 85     FileStorage fs = new FileStorage(App.getInitParameter(App.getRs(App.RB_AP_CONF)));
589850 86     logger.fine(path);
0e9cd3 87     
U 88     // Faustregel: Anzahl Elemente eines URL plus 1 ist die Anzahl der Elemente des 
89     // Ergebnisses von String.split.
90     switch (elems.length) {
91       case 6:
92         if (elems[5].equalsIgnoreCase(PL_CMD_ENDE)) {
93           response = naechsterTitel(fs, elems[4]);
589850 94         } else if(elems[5].equalsIgnoreCase(PL_CMD_STOP)) {
d12f6e 95           spielt.remove(elems[4]);
589850 96           response = kommandoSenden(fs, elems[4], PL_CMD_AVD_STOP);
U 97         } else if(elems[5].equalsIgnoreCase(PL_CMD_PAUSE)) {
98           response = kommandoSenden(fs, elems[4], PL_CMD_AVD_PAUSE);
99         } else if(elems[5].equalsIgnoreCase(PL_CMD_PLAYON)) {
100           response = kommandoSenden(fs, elems[4], PL_CMD_AVD_PLAYON);
0e9cd3 101         } else {
U 102           response = meldung("Ungueltiges Kommando: " + elems[5], AbstractHandler.RTC_NOT_FOUND);
103         }
104         break;
b56bb3 105       case 8:
1c5fa4 106         response = ersterTitel(fs, elems[4], elems[7]);
0e9cd3 107         break;
U 108       default:
109         response = "Ungueltiger URL";
b56bb3 110         break;
U 111     }
112     return response;
589850 113   }
1c5fa4 114
U 115   @Override
116   protected String post(HttpExchange e) {
117     String response;
118     try {
119       return urlAbspielen(e);
120     } catch (IOException ex) {
121       logger.log(Level.SEVERE, null, ex);
122       return meldung(ex.getLocalizedMessage(), 404);
123     }
124   }
589850 125   
1c5fa4 126   // titel.katalogUrl + titel.pfad + titel.name
U 127   private String urlAbspielen(HttpExchange e) throws IOException {
128     String path = e.getRequestURI().toString();
129     String[] elems = path.split(Server.SLASH);
130     FileStorage fs = new FileStorage(App.getInitParameter(App.getRs(App.RB_AP_CONF)));
131     
132     String titelJson = bodyLesen(e);
133     Gson gson = new Gson();
134     Object o = gson.fromJson(titelJson, fs.typeFromName(Titel.class.getSimpleName()).getType());
135     if(o instanceof Titel) {
136       Titel titel = (Titel) o;
137       String titelUrl = titel.getKatalogUrl() + titel.getPfad() + titel.getName();
138       Entity entity = fs.read(FileStorage.ST_ABSPIELER, elems[4]);
139       if (entity instanceof Abspieler) {
140         Abspieler abspieler = (Abspieler) entity;
141         String server = getEinstellung(fs, App.getRs(App.RB_HOST), DEFAULT_HOST);
142         String signal = abspielKommando(fs, abspieler, server, titelUrl).toString();
143         abspielerKommandoSenden(signal);
144         return signal + "gesendet.";
145       } else {
146         return meldung("Ungueltiger Abspieler.", 404);
147       }
148     } else {
149       return meldung("Ungueltiger Titel.", 404);
658c14 150     }
U 151   }
152   
589850 153   private String kommandoSenden(Storage s, String aName, String kommando) {
U 154     Entity entity = s.read(FileStorage.ST_ABSPIELER, aName);
155     if (entity instanceof Abspieler) {
156       Abspieler abspieler = (Abspieler) entity;
157       StringBuilder kmd = new StringBuilder();
158       kmd.append(abspieler.getUrl());
159       kmd.append(kommando);
9e14ef 160       String signal = kmd.toString();
U 161       abspielerKommandoSenden(signal);
162       return signal + " gesendet.";
589850 163     } else {
U 164       return meldung("Abspielliste nicht gefunden.", AbstractHandler.RTC_NOT_FOUND);
165     }
b56bb3 166   }
U 167   
5f7e0b 168   private String ersterTitel(Storage s, String aName, String lName) {
U 169     String response;
170     Entity entity = s.read(FileStorage.ST_ABSPIELLISTE, lName);
171     if (entity instanceof Abspielliste) {
172       Abspielliste liste = (Abspielliste) entity;
173       response = listentitelSpielen(s, aName, liste, 0);
174     } else {
175       response = meldung("Abspielliste nicht gefunden.", AbstractHandler.RTC_NOT_FOUND);
176     }
177     return response;
0e9cd3 178   }
U 179
180   private String naechsterTitel(Storage s, String abspielerName) {
181     String response;
182     Object o = spielt.get(abspielerName);
183     if (o instanceof Abspielvorgang) {
184       Abspielvorgang av = (Abspielvorgang) o;
185       Entity entity = s.read(FileStorage.ST_ABSPIELLISTE, av.getListe());
186       if (entity instanceof Abspielliste) {
b56bb3 187         Abspielliste liste = (Abspielliste) entity;
0e9cd3 188         int titelNr = av.getTitelNr();
U 189         if (liste.getTitel().size() > ++titelNr) {
005d7a 190           response = listentitelSpielen(s, abspielerName, liste, titelNr);
0e9cd3 191         } else {
U 192           response = "Liste " + liste.getName() + " ist zuende gespielt.";
589850 193           logger.info(response);
0e9cd3 194         }
U 195       } else {
196         response = meldung("Abspielliste nicht gefunden.", AbstractHandler.RTC_NOT_FOUND);
b56bb3 197       }
0e9cd3 198       //response = listenTitelSpielen(e, elems[4]);
U 199     } else {
200       response = meldung("Abspielvorgang fuer Abspieler " + abspielerName, AbstractHandler.RTC_NOT_FOUND);
b56bb3 201     }
U 202     return response;
203   }
0e9cd3 204
005d7a 205   private String listentitelSpielen(Storage s, String aName, Abspielliste liste, int titelNr) {
0e9cd3 206     String response;
U 207     Entity entity = s.read(FileStorage.ST_ABSPIELER, aName);
208     if (entity instanceof Abspieler) {
209       Abspieler abspieler = (Abspieler) entity;
210       String kommando = kommandoFuerTitel(s, liste, abspieler, titelNr);
211       //String kommando = kmd.toString();
212       logger.info(kommando);
5f7e0b 213       abspielerKommandoSenden(kommando);
0e9cd3 214       response = "Abspielen der Liste " + liste.getName() + " auf Abspieler " + aName + " gestartet.";
U 215     } else {
216       response = meldung("Abspieler nicht gefunden.", AbstractHandler.RTC_NOT_FOUND);
217     }
218     return response;
219   }
220
221   /**
222    * Das Kommando zum Abspielen fuer den Titel einer Abspielliste und einen bestimmten Abspieler
223    * ermitteln.
224    *
225    * @param s die Ablage, in der Abspieler und Abspiellisten zu finden sind
226    * @param liste Name der Liste, die den gewuenschten Titel enthaelt
227    * @param abspieler Name des Abspielers, der zum Abspielen dienen soll
228    * @param titelNr Nummer des Titels in der Liste
229    * @return das Kommando zum Abspielen (ein URL)
230    */
231   private String kommandoFuerTitel(Storage s, Abspielliste liste, Abspieler abspieler, int titelNr) {
232     // ersten Titel aus Liste holen
233     Titel titel = liste.getTitel().get(titelNr);
234
235     // URL des Titels ermitteln
236     String titelUrl = titel.getKatalogUrl() + titel.getPfad() + titel.getName();
237     logger.log(Level.INFO, "abspielen von {0} auf {1}", new Object[]{titelUrl, abspieler.getUrl()});
238
239     // Titel als 'spielt' vermerken
240     Abspielvorgang vorgang = new Abspielvorgang();
241     vorgang.setAbspieler(abspieler.getName());
242     vorgang.setListe(liste.getName());
243     vorgang.setTitelNr(titelNr);
244     spielt.put(abspieler.getName(), vorgang);
005d7a 245     
U 246     String server = getEinstellung(s, App.getRs(App.RB_HOST), DEFAULT_HOST);
0e9cd3 247
1c5fa4 248     /*
U 249
0e9cd3 250     // Kommando an den Abspieler zusammenbauen
U 251     StringBuilder kmd = new StringBuilder();
252     kmd.append(abspieler.getUrl());
253     kmd.append(PL_CMD_PLAY);
254     // Parameter fuer den Abspieler holen
005d7a 255     kmd.append(getEinstellung(s, App.getRs(App.RB_PLAYERPARAMS), PL_DEFAULT_PARAMS));
U 256     kmd.append(server);
0e9cd3 257     kmd.append(titelUrl);
1c5fa4 258     */
U 259     StringBuilder kmd = abspielKommando(s, abspieler, server, titelUrl);
005d7a 260     kmd.append(PL_PARAM_RUECK);
U 261     kmd.append(server);
262     kmd.append(PL_API_STRG);
263     kmd.append(abspieler.getName());
264     kmd.append("/ende");
0e9cd3 265
U 266     return kmd.toString();
267   }
1c5fa4 268
U 269   private StringBuilder abspielKommando(Storage s, Abspieler abspieler, String server, String titelUrl) {
270     
271     // Kommando an den Abspieler zusammenbauen
272     StringBuilder kmd = new StringBuilder();
273     kmd.append(abspieler.getUrl());
274     kmd.append(PL_CMD_PLAY);
275     // Parameter fuer den Abspieler holen
276     kmd.append(getEinstellung(s, App.getRs(App.RB_PLAYERPARAMS), PL_DEFAULT_PARAMS));
277     kmd.append(server);
278     kmd.append(titelUrl);
279
280     return kmd;    
281   }
005d7a 282   
U 283   private String getEinstellung(Storage s, String key, String standardWert) {
284     Entity entity = s.read(Einstellung.class.getSimpleName(), key);
285     if (entity instanceof Einstellung) {
286       Einstellung einstellung = (Einstellung) entity;
287       Object o = einstellung.getValue();
288       if(o instanceof String) {
289         return o.toString();
290       } else {
291         return standardWert;
292       }
293     } else {
294       return standardWert;
295     }
296   }
297   
298   private void abspielerKommandoSenden(String kommando) {
299     /*
300       TODO hier evtl. mit mehreren Versuchen ausgleichen, 
301       dass ein einzelner Versuch nicht 'durchkommt'...
302     */
303     logger.info(kommando);
304     try {
305       HttpURLConnection conn = (HttpURLConnection) new URL(kommando).openConnection();
306       conn.setRequestMethod("GET");
307       conn.connect();
308       int status = conn.getResponseCode();
309       String msg = conn.getResponseMessage();
310       logger.log(Level.INFO, "Kommando {0} mit Status {1} {2} gesendet.", new Object[]{kommando, status, msg});
311     } catch(IOException ex) {
312       logger.log(Level.INFO, ex.getMessage(), ex);
313     }
314   }
315   
5f7e0b 316   private String meldung(String text, int code) {
U 317     setReturnCode(code);
318     return text;
319   }
320   
b56bb3 321   // rpi4-az:9090/avd/play?titel=/Filme/S/sound_city.m4v&th=60&ti=60&o=local
U 322   // aUrl http://rpi4-wz:9090/
323   // titelUrl /media/test/A/The-Alan-Parsons-Project/I-Robot/02-I-Wouldnt-Want-to-Be-Like-You.mp3
324 }