/*
|
Tango - Personal Media Center
|
Copyright (C) 2021 Ulrich Hilger
|
|
This program is free software: you can redistribute it and/or modify
|
it under the terms of the GNU Affero General Public License as
|
published by the Free Software Foundation, either version 3 of the
|
License, or (at your option) any later version.
|
|
This program is distributed in the hope that it will be useful,
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
GNU Affero General Public License for more details.
|
|
You should have received a copy of the GNU Affero General Public License
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
*/
|
package de.uhilger.tango.api;
|
|
import com.google.gson.Gson;
|
import com.sun.net.httpserver.HttpExchange;
|
import de.uhilger.tango.PlaylistListener;
|
import de.uhilger.tango.Server;
|
import de.uhilger.tango.entity.Abspielvorgang;
|
import de.uhilger.tango.entity.Abspieler;
|
import de.uhilger.tango.entity.Abspielliste;
|
import de.uhilger.tango.entity.Entity;
|
import de.uhilger.tango.entity.Livestream;
|
import de.uhilger.tango.entity.Titel;
|
import de.uhilger.tango.store.FileStorage;
|
import de.uhilger.tango.store.Storage;
|
import java.io.IOException;
|
import java.net.HttpURLConnection;
|
import java.net.URL;
|
import java.util.Collection;
|
import java.util.HashMap;
|
import java.util.Iterator;
|
import java.util.Map;
|
import java.util.Set;
|
import java.util.logging.Level;
|
import java.util.logging.Logger;
|
|
/**
|
* Die MediaSteuerung verarbeitet HTTP-Signale zur Steuerung von Media-Operationen wie z.B. dem
|
* Spielen einer Abspielliste oder dem Starten oder Stoppen eines Videos auf einem entfernten
|
* Abspielgeraet.
|
*
|
* HTTP GET /mz/api/strg/abspieler/play/liste/[name]
|
* HTTP GET /mz/api/strg/abspieler/next
|
* HTTP GET /mz/api/strg/abspieler/ende
|
*
|
* HTTP POST /mz/api/strg/abspieler/play/titel mit dem Titel im Body
|
* HTTP POST /mz/api/strg/abspieler/play/stream mit dem Livestream im Body (nur Name gefuellt)
|
*
|
* DEPRECATED: HTTP POST /mz/api/strg/abspieler/weiter/titel mit dem Titel im Body
|
*
|
* HTTP GET /mz/api/strg/abspieler/pause
|
* HTTP GET /mz/api/strg/abspieler/stop
|
* HTTP GET /mz/api/strg/abspieler/seek/[sekunden]
|
*
|
* Faustregel: Anzahl Elemente eines URL plus 1 ist die Anzahl der Elemente des
|
* Ergebnisses von String.split.
|
*
|
* Mit der Funktion ende liefert die MediaSteuerung die Moeglichkeit, Titel aus
|
* einer Abspielliste gesteuert von Tango abzuspielen. Die Nutzung der Funktion wird
|
* ausgeloest vom Aufruf an abspieler/play/liste/[name]. Tango erwartet dann von einem
|
* Abspieler die Rueckmeldung, dass dieser den zuletzt von Tango an den
|
* Abspieler zum Abspielen uebermittelten Titel zuende abgespielt hat. Tango
|
* uebergibt dem Abspieler dann den naechsten Titel, bis die Abspielliste zuende ist.
|
*
|
* Diese Form unterscheidet sich vom Abspielen einer Abspielliste als einzelner
|
* Stream, wie es vom StreamHandler realisiert wird.
|
*
|
* @author Ulrich Hilger
|
* @version 1, 9.4.2021
|
*/
|
public class MediaSteuerung extends AbstractHandler implements PlaylistListener {
|
|
private static final Logger logger = Logger.getLogger(MediaSteuerung.class.getName());
|
|
public static final String PL_CMD_PLAY = "play";
|
public static final String PL_CMD_SEEK = "seek";
|
public static final String PL_DEFAULT_PARAMS = "?titel=";
|
public static final String PL_PARAM_RUECK = "&r=";
|
public static final String PL_API_STRG = "api/strg/";
|
public static final String PL_CMD_ENDE = "ende";
|
public static final String PL_CMD_STOP = "stop";
|
public static final String PL_CMD_VOLDN = "voldn";
|
public static final String PL_CMD_VOLUP = "volup";
|
public static final String PL_CMD_PAUSE = "pause";
|
public static final String PL_CMD_PLAYON = "weiter";
|
public static final String PL_CMD_NEXT = "next";
|
public static final String PL_CMD_CALYPSO_STOP = "stop";
|
public static final String PL_CMD_CALYPSO_VOL_INC = "vol-inc";
|
public static final String PL_CMD_CALYPSO_VOL_DEC = "vol-dec";
|
public static final String PL_CMD_CALYPSO_PAUSE = "pause";
|
public static final String PL_CMD_CALYPSO_PLAYON = "playon";
|
public static final String DEFAULT_HOST = "http://localhost:9090";
|
|
public static final String RB_HOST = "host";
|
public static final String RB_PLAYERPARAMS = "playerparams";
|
|
private final Map spielt = new HashMap();
|
|
private String conf;
|
|
public MediaSteuerung(String conf) {
|
this.conf = conf;
|
}
|
|
@Override
|
protected String get(HttpExchange e) {
|
String response;
|
String path = e.getRequestURI().toString();
|
String[] elems = path.split(Server.SLASH);
|
FileStorage fs = new FileStorage(conf);
|
logger.fine(path);
|
|
// Faustregel: Anzahl Elemente eines URL plus 1 ist die Anzahl der Elemente des
|
// Ergebnisses von String.split.
|
switch (elems.length) {
|
case 6:
|
if (elems[5].equalsIgnoreCase(PL_CMD_ENDE)) {
|
response = naechsterTitel(fs, elems[4]);
|
} else if(elems[5].equalsIgnoreCase(PL_CMD_STOP)) {
|
spielt.remove(elems[4]);
|
response = kommandoSenden(fs, elems[4], PL_CMD_CALYPSO_STOP);
|
} else if(elems[5].equalsIgnoreCase(PL_CMD_VOLDN)) {
|
response = kommandoSenden(fs, elems[4], PL_CMD_CALYPSO_VOL_DEC);
|
} else if(elems[5].equalsIgnoreCase(PL_CMD_VOLUP)) {
|
response = kommandoSenden(fs, elems[4], PL_CMD_CALYPSO_VOL_INC);
|
} else if(elems[5].equalsIgnoreCase(PL_CMD_PAUSE)) {
|
response = kommandoSenden(fs, elems[4], PL_CMD_CALYPSO_PAUSE);
|
//} else if(elems[5].equalsIgnoreCase(PL_CMD_PLAYON)) {
|
// response = kommandoSenden(fs, elems[4], PL_CMD_CALYPSO_PLAYON);
|
} else if(elems[5].equalsIgnoreCase(PL_CMD_NEXT)) {
|
response = kommandoSenden(fs, elems[4], PL_CMD_CALYPSO_STOP);
|
//response = naechsterTitel(fs, elems[4]);
|
} else {
|
response = meldung("Ungueltiges Kommando: " + elems[5], AbstractHandler.RTC_NOT_FOUND);
|
}
|
break;
|
case 7:
|
if (elems[5].equalsIgnoreCase(PL_CMD_SEEK)) {
|
// /calypso/seek?pos=[sekunden]
|
response = kommandoSenden(fs, elems[4], "seek?pos=" + elems[6]);
|
} else {
|
response = meldung("Ungueltiges Kommando: " + elems[5], AbstractHandler.RTC_NOT_FOUND);
|
}
|
break;
|
case 8:
|
response = ersterTitel(fs, elems[4], elems[7]);
|
break;
|
default:
|
response = "Ungueltiger URL";
|
break;
|
}
|
return response;
|
}
|
|
@Override
|
protected String post(HttpExchange e) {
|
String response;
|
String abspielerKmd = PL_CMD_PLAY;
|
String path = e.getRequestURI().toString();
|
String[] elems = path.split(Server.SLASH);
|
logger.info(elems[5]);
|
// PLAYON ist deprecated
|
//if(elems[5].equalsIgnoreCase(PL_CMD_PLAYON)) {
|
// abspielerKmd = PL_CMD_CALYPSO_PLAYON;
|
//} else if(elems[5].equalsIgnoreCase(PL_CMD_PLAY)){
|
// abspielerKmd = PL_CMD_PLAY;
|
//}
|
if(elems[5].equalsIgnoreCase(PL_CMD_PLAY)){
|
abspielerKmd = PL_CMD_PLAY;
|
}
|
try {
|
return urlAbspielen(e, abspielerKmd);
|
} catch (IOException ex) {
|
logger.log(Level.SEVERE, null, ex);
|
return meldung(ex.getLocalizedMessage(), 404);
|
}
|
}
|
|
// titel.katalogUrl + titel.pfad + titel.name
|
private String urlAbspielen(HttpExchange e, String abspielerKmd) throws IOException {
|
String path = e.getRequestURI().toString();
|
String[] elems = path.split(Server.SLASH);
|
FileStorage fs = new FileStorage(conf);
|
if(elems[6].equalsIgnoreCase("titel")) {
|
String titelJson = bodyLesen(e);
|
Gson gson = new Gson();
|
Object o = gson.fromJson(titelJson, fs.typeFromName(Titel.class.getSimpleName()).getType());
|
if(o instanceof Titel) {
|
Titel titel = (Titel) o;
|
String titelUrl = titel.getKatalogUrl() + titel.getPfad() + titel.getName();
|
Entity entity = fs.read(FileStorage.ST_ABSPIELER, elems[4]);
|
if (entity instanceof Abspieler) {
|
Abspieler abspieler = (Abspieler) entity;
|
String server = getEinstellung(fs, getResString(RB_HOST), DEFAULT_HOST);
|
String signal = abspielKommando(fs, abspieler, server, titelUrl, abspielerKmd).toString();
|
abspielerKommandoSenden(signal);
|
return signal + "gesendet.";
|
} else {
|
return meldung("Ungueltiger Abspieler.", 404);
|
}
|
} else {
|
return meldung("Ungueltiger Titel.", 404);
|
}
|
} else if(elems[6].equalsIgnoreCase("stream")) {
|
String streamJson = bodyLesen(e);
|
Gson gson = new Gson();
|
Object o = gson.fromJson(streamJson, fs.typeFromName(Livestream.class.getSimpleName()).getType());
|
if(o instanceof Livestream) {
|
Entity entity = fs.read(FileStorage.ST_LIVESTREAM, ((Livestream) o).getName());
|
if(entity instanceof Livestream) {
|
Livestream stream = (Livestream) entity;
|
entity = fs.read(FileStorage.ST_ABSPIELER, elems[4]);
|
if (entity instanceof Abspieler) {
|
Abspieler abspieler = (Abspieler) entity;
|
String server = "";
|
String signal = abspielKommando(fs, abspieler, server, stream.getUrl(), PL_CMD_PLAY).toString();
|
abspielerKommandoSenden(signal);
|
return signal + "gesendet.";
|
} else {
|
return meldung("Ungueltiger Abspieler.", 404);
|
}
|
} else {
|
return meldung("Ungueltiger Livestream.", 404);
|
}
|
} else {
|
return meldung("Ungueltiger Livestream.", 404);
|
}
|
} else {
|
return meldung("Ungueltiger URL.", 404);
|
}
|
}
|
|
|
private String kommandoSenden(Storage s, String aName, String kommando) {
|
Entity entity = s.read(FileStorage.ST_ABSPIELER, aName);
|
if (entity instanceof Abspieler) {
|
Abspieler abspieler = (Abspieler) entity;
|
StringBuilder kmd = new StringBuilder();
|
kmd.append(abspieler.getUrl());
|
kmd.append(kommando);
|
String signal = kmd.toString();
|
//String server = getEinstellung(s, App.getRs(App.RB_HOST), DEFAULT_HOST);
|
//String signal = abspielKommando(s, abspieler, server, stream.getUrl(), PL_CMD_PLAY).toString();
|
abspielerKommandoSenden(signal);
|
return signal + " gesendet.";
|
} else {
|
return meldung("Abspielliste nicht gefunden.", AbstractHandler.RTC_NOT_FOUND);
|
}
|
}
|
|
|
private String ersterTitel(Storage s, String aName, String lName) {
|
String response;
|
Entity entity = s.read(FileStorage.ST_ABSPIELLISTE, lName);
|
if (entity instanceof Abspielliste) {
|
Abspielliste liste = (Abspielliste) entity;
|
response = listentitelSpielen(s, aName, liste, 0);
|
} else {
|
response = meldung("Abspielliste nicht gefunden.", AbstractHandler.RTC_NOT_FOUND);
|
}
|
return response;
|
}
|
|
private String naechsterTitel(Storage s, String abspielerName) {
|
String response;
|
Object o = spielt.get(abspielerName);
|
if (o instanceof Abspielvorgang) {
|
Abspielvorgang av = (Abspielvorgang) o;
|
Entity entity = s.read(FileStorage.ST_ABSPIELLISTE, av.getListe());
|
if (entity instanceof Abspielliste) {
|
Abspielliste liste = (Abspielliste) entity;
|
int titelNr = av.getTitelNr();
|
if (liste.getTitel().size() > ++titelNr) {
|
response = listentitelSpielen(s, abspielerName, liste, titelNr);
|
} else {
|
response = "Liste " + liste.getName() + " ist zuende gespielt.";
|
logger.info(response);
|
}
|
} else {
|
response = meldung("Abspielliste nicht gefunden.", AbstractHandler.RTC_NOT_FOUND);
|
}
|
//response = listenTitelSpielen(e, elems[4]);
|
} else {
|
response = meldung("Abspielvorgang fuer Abspieler " + abspielerName, AbstractHandler.RTC_NOT_FOUND);
|
}
|
return response;
|
}
|
|
private String listentitelSpielen(Storage s, String aName, Abspielliste liste, int titelNr) {
|
String response;
|
Entity entity = s.read(FileStorage.ST_ABSPIELER, aName);
|
if (entity instanceof Abspieler) {
|
Abspieler abspieler = (Abspieler) entity;
|
String kommando = kommandoFuerTitel(s, liste, abspieler, titelNr);
|
//String kommando = kmd.toString();
|
logger.info(kommando);
|
abspielerKommandoSenden(kommando);
|
response = "Abspielen der Liste " + liste.getName() + " auf Abspieler " + aName + " gestartet.";
|
} else {
|
response = meldung("Abspieler nicht gefunden.", AbstractHandler.RTC_NOT_FOUND);
|
}
|
return response;
|
}
|
|
/**
|
* Das Kommando zum Abspielen fuer den Titel einer Abspielliste und einen bestimmten Abspieler
|
* ermitteln.
|
*
|
* @param s die Ablage, in der Abspieler und Abspiellisten zu finden sind
|
* @param liste Name der Liste, die den gewuenschten Titel enthaelt
|
* @param abspieler Name des Abspielers, der zum Abspielen dienen soll
|
* @param titelNr Nummer des Titels in der Liste
|
* @return das Kommando zum Abspielen (ein URL)
|
*/
|
private String kommandoFuerTitel(Storage s, Abspielliste liste, Abspieler abspieler, int titelNr) {
|
// ersten Titel aus Liste holen
|
Titel titel = liste.getTitel().get(titelNr);
|
|
// URL des Titels ermitteln
|
String titelUrl = titel.getKatalogUrl() + titel.getPfad() + titel.getName();
|
logger.log(Level.INFO, "abspielen von {0} auf {1}", new Object[]{titelUrl, abspieler.getUrl()});
|
|
// Titel als 'spielt' vermerken
|
Abspielvorgang vorgang = new Abspielvorgang();
|
vorgang.setAbspieler(abspieler.getName());
|
vorgang.setListe(liste.getName());
|
vorgang.setTitelNr(titelNr);
|
spielt.put(abspieler.getName(), vorgang);
|
|
String server = getEinstellung(s, getResString(RB_HOST), DEFAULT_HOST);
|
|
/*
|
|
// Kommando an den Abspieler zusammenbauen
|
StringBuilder kmd = new StringBuilder();
|
kmd.append(abspieler.getUrl());
|
kmd.append(PL_CMD_PLAY);
|
// Parameter fuer den Abspieler holen
|
kmd.append(getEinstellung(s, App.getRs(App.RB_PLAYERPARAMS), PL_DEFAULT_PARAMS));
|
kmd.append(server);
|
kmd.append(titelUrl);
|
*/
|
StringBuilder kmd = abspielKommando(s, abspieler, server, titelUrl, PL_CMD_PLAY);
|
kmd.append(PL_PARAM_RUECK);
|
kmd.append(server);
|
if(!server.endsWith(Server.SLASH)) {
|
kmd.append(Server.SLASH);
|
}
|
kmd.append(PL_API_STRG);
|
kmd.append(abspieler.getName());
|
kmd.append("/ende");
|
|
return kmd.toString();
|
}
|
|
private StringBuilder abspielKommando(Storage s, Abspieler abspieler, String server, String titelUrl, String abspielKmd) {
|
|
// Kommando an den Abspieler zusammenbauen
|
StringBuilder kmd = new StringBuilder();
|
kmd.append(abspieler.getUrl());
|
//kmd.append(PL_CMD_PLAY);
|
kmd.append(abspielKmd);
|
// Parameter fuer den Abspieler holen
|
kmd.append(getEinstellung(s, getResString(RB_PLAYERPARAMS), PL_DEFAULT_PARAMS));
|
kmd.append(server);
|
kmd.append(titelUrl);
|
|
return kmd;
|
}
|
|
private void abspielerKommandoSenden(String kommando) {
|
/*
|
TODO hier evtl. mit mehreren Versuchen ausgleichen,
|
dass ein einzelner Versuch nicht 'durchkommt'...
|
*/
|
logger.info(kommando);
|
try {
|
HttpURLConnection conn = (HttpURLConnection) new URL(kommando).openConnection();
|
conn.setRequestMethod("GET");
|
conn.connect();
|
int status = conn.getResponseCode();
|
String msg = conn.getResponseMessage();
|
logger.log(Level.INFO, "Kommando {0} mit Status {1} {2} gesendet.", new Object[]{kommando, status, msg});
|
} catch(IOException ex) {
|
logger.log(Level.INFO, ex.getMessage(), ex);
|
}
|
}
|
|
private String meldung(String text, int code) {
|
setReturnCode(code);
|
return text;
|
}
|
|
/* ------- PlaylistListener implementation ------ */
|
|
@Override
|
public void titleRemoved(String listName, int titleIndex) {
|
Set keys = spielt.keySet();
|
Iterator<String> keyIterator = keys.iterator();
|
boolean found = false;
|
while(keyIterator.hasNext() && !found) {
|
String abspielerName = keyIterator.next();
|
Abspielvorgang av = (Abspielvorgang) spielt.get(abspielerName);
|
if(av.getListe().equals(listName)) {
|
found = true;
|
int tnr = av.getTitelNr();
|
if(tnr > titleIndex) {
|
av.setTitelNr(--tnr);
|
logger.info("Abspieler " + abspielerName + " Liste " + av.getListe() + " titelnr jetzt " + tnr);
|
spielt.put(abspielerName, av);
|
}
|
}
|
}
|
}
|
}
|