commit | author | age
|
1ff360
|
1 |
package de.uhilger.tango.api; |
U |
2 |
|
5bf530
|
3 |
import com.sun.net.httpserver.Headers; |
U |
4 |
import com.sun.net.httpserver.HttpExchange; |
|
5 |
import de.uhilger.tango.App; |
|
6 |
import de.uhilger.tango.Server; |
|
7 |
import static de.uhilger.tango.api.FileHandler.CONTENT_LENGTH; |
|
8 |
import static de.uhilger.tango.api.FileHandler.HTTP_GET; |
|
9 |
import static de.uhilger.tango.api.FileHandler.RANGE_HEADER; |
|
10 |
import static de.uhilger.tango.api.FileHandler.RB_NOT_FOUND; |
|
11 |
import static de.uhilger.tango.api.FileHandler.RB_WELCOME_FILE; |
|
12 |
import static de.uhilger.tango.api.FileHandler.SC_NOT_FOUND; |
|
13 |
import static de.uhilger.tango.api.FileHandler.SC_OK; |
|
14 |
import static de.uhilger.tango.api.FileHandler.STR_BLANK; |
|
15 |
import static de.uhilger.tango.api.FileHandler.STR_DOT; |
|
16 |
import de.uhilger.tango.entity.Ablageort; |
|
17 |
import de.uhilger.tango.entity.Abspielliste; |
|
18 |
import de.uhilger.tango.entity.Entity; |
|
19 |
import de.uhilger.tango.entity.Titel; |
|
20 |
import de.uhilger.tango.store.FileStorage; |
|
21 |
import java.io.File; |
|
22 |
import java.io.FileInputStream; |
|
23 |
import java.io.FileNotFoundException; |
|
24 |
import java.io.IOException; |
|
25 |
import java.io.InputStream; |
|
26 |
import java.io.OutputStream; |
|
27 |
import java.nio.charset.StandardCharsets; |
|
28 |
import java.util.HashMap; |
|
29 |
import java.util.Iterator; |
|
30 |
import java.util.List; |
|
31 |
import java.util.Map; |
|
32 |
import java.util.ResourceBundle; |
|
33 |
import java.util.logging.Level; |
|
34 |
import java.util.logging.Logger; |
|
35 |
|
1ff360
|
36 |
/** |
5bf530
|
37 |
* Der StreamHandler liefert ganze Abspiellisten als einzelnen Stream aus. Die in Tango mit dem |
U |
38 |
* ListHandler erstellen Abspiellisten werden als ein zusammenhaengender Stream ausgegeben. |
|
39 |
* |
|
40 |
* Den Inhalt einer Abspielliste als Stream ausgeben HTTP GET /tango/api/stream/liste/[name] |
|
41 |
* |
|
42 |
* |
|
43 |
* |
|
44 |
* |
|
45 |
* die folgenden URLs ggf. wieder wegnehmen |
|
46 |
* |
|
47 |
* HTTP GET /tango/api/stream/play/liste/[name] |
|
48 |
* |
|
49 |
* HTTP GET /tango/api/stream/pause/liste/[name] HTTP GET /tango/api/stream/stop/liste/[name] HTTP |
|
50 |
* GET /tango/api/stream/seek/liste/[name]/[sekunden] |
|
51 |
* |
|
52 |
* Die Funktionen des StreamHandlers ergaenzen so die Ausgabe einzelner Media-Dateien als Stream, |
|
53 |
* wie sie mit dem FileHandler und seinen Subklassen sowie mit der MediaSteuerung erfolgen. |
|
54 |
* |
1ff360
|
55 |
* @author Ulrich Hilger |
U |
56 |
*/ |
5bf530
|
57 |
public class StreamHandler extends FileHandler { |
U |
58 |
private static final Logger logger = Logger.getLogger(StreamHandler.class.getName()); |
|
59 |
|
|
60 |
public static final String PLAY = "play"; |
|
61 |
public static final String LISTE = "liste"; |
|
62 |
|
|
63 |
private HashMap listen; |
|
64 |
private HashMap<String, File> files; |
|
65 |
private HashMap<String, Long> bytes; |
|
66 |
private HashMap<String, String> kataloge; |
|
67 |
private String conf; |
|
68 |
|
|
69 |
public StreamHandler(String conf) { |
|
70 |
super(""); // wird spaeter gesetzt |
|
71 |
listen = new HashMap<String, Integer>(); // abspiellistenname -> z.Zt. spielender Index |
|
72 |
kataloge = new HashMap(); // url -> abs. pfad |
|
73 |
files = new HashMap<String, File>(); // abspiellistenname -> z zt spielende datei |
|
74 |
bytes = new HashMap<String, Long>(); // abspiellistenname -> gespielte bytes |
|
75 |
this.conf = conf; |
|
76 |
ablageorteLesen(); |
|
77 |
} |
|
78 |
|
|
79 |
@Override |
|
80 |
public void handle(HttpExchange e) throws IOException { |
|
81 |
String path = e.getRequestURI().toString(); |
|
82 |
String[] elems = path.split(Server.SLASH); |
|
83 |
String lName = elems[5]; |
|
84 |
|
|
85 |
Integer index; |
|
86 |
File file; |
|
87 |
FileStorage s = new FileStorage(conf); |
|
88 |
Object o = listen.get(lName); |
|
89 |
if (o instanceof Integer) { |
|
90 |
// liste spielt schon |
|
91 |
index = (Integer) o; |
|
92 |
file = files.get(lName); |
|
93 |
} else { |
|
94 |
index = 0; |
|
95 |
// liste spielt noch nicht |
|
96 |
listen.put(lName, index); |
|
97 |
file = getFileToPlay(s, lName, index.intValue()); |
|
98 |
files.put(lName, file); |
|
99 |
bytes.put(lName, Long.valueOf(0)); |
|
100 |
} |
|
101 |
|
|
102 |
//String fName = getFileName(e); |
|
103 |
String fName = file.getName(); |
|
104 |
if (fName.startsWith(STR_DOT)) { |
|
105 |
sendNotFound(e, fName); |
|
106 |
} else { |
|
107 |
Headers headers = e.getRequestHeaders(); |
|
108 |
int indexVal = index.intValue(); |
|
109 |
if (headers.containsKey(RANGE_HEADER)) { |
|
110 |
logger.info("range header present, serving list file parts"); |
|
111 |
serveListParts(e, s, lName, file, indexVal); |
|
112 |
//serveList(e, s, lName, file, indexVal); |
|
113 |
} else { |
|
114 |
//if (fName.length() < 1 || fName.endsWith(Server.SLASH)) { |
|
115 |
// ResourceBundle rb = ResourceBundle.getBundle(App.RB_NAME); |
|
116 |
// fName += getResString(RB_WELCOME_FILE); |
|
117 |
//} |
|
118 |
|
|
119 |
logger.info("no range header or header ignored, streaming whole files"); |
|
120 |
serveList(e, s, lName, file, indexVal); |
|
121 |
//while(file != null) { |
|
122 |
// files.put(lName, file); |
|
123 |
// listen.put(lName, index); |
|
124 |
// serveFile(e, file); |
|
125 |
// file = getFileToPlay(s, lName, ++indexVal); |
|
126 |
//} |
|
127 |
} |
|
128 |
} |
|
129 |
|
|
130 |
/* |
|
131 |
String response = lName; |
|
132 |
Headers headers = e.getResponseHeaders(); |
|
133 |
headers.add("Content-Type", "application/json"); |
|
134 |
e.sendResponseHeaders(200, response.length()); |
|
135 |
OutputStream os = e.getResponseBody(); |
|
136 |
os.write(response.getBytes()); |
|
137 |
os.close(); |
|
138 |
*/ |
|
139 |
} |
1ff360
|
140 |
|
5bf530
|
141 |
private void serveListParts(HttpExchange e, FileStorage s, String lName, File file, int indexVal) throws IOException { |
U |
142 |
if (file.exists()) { |
|
143 |
setHeaders(e, file); |
|
144 |
//e.getResponseHeaders().set(CONTENT_LENGTH, Long.toString(Long.MAX_VALUE)); |
|
145 |
//e.sendResponseHeaders(SC_OK, Long.MAX_VALUE); |
|
146 |
//e.getResponseHeaders().set(CONTENT_LENGTH, Long.toString(Long.MAX_VALUE)); |
|
147 |
//e.sendResponseHeaders(SC_OK, Long.MAX_VALUE); |
|
148 |
//e.sendResponseHeaders(SC_PARTIAL_CONTENT, Long.MAX_VALUE); |
|
149 |
logger.info("playing " + file.getName()); |
|
150 |
logger.info("file length: " + file.length()); |
|
151 |
InputStream is = new FileInputStream(file); |
|
152 |
OutputStream os = e.getResponseBody(); |
|
153 |
while (file != null) { |
|
154 |
//if(is instanceof InputStream) { |
|
155 |
// is.close(); |
|
156 |
//} |
|
157 |
//is = new FileInputStream(file); |
|
158 |
files.put(lName, file); |
|
159 |
listen.put(lName, indexVal); |
|
160 |
file = serveFileParts(e, file, is, os, s, lName, indexVal); |
|
161 |
//serveFile(e, file); |
|
162 |
if(bytes.get(lName) == 0l) { |
|
163 |
file = getFileToPlay(s, lName, ++indexVal); |
|
164 |
if(is instanceof InputStream) { |
|
165 |
is.close(); |
|
166 |
} |
|
167 |
is = new FileInputStream(file); |
|
168 |
logger.info("file length: " + file.length()); |
|
169 |
} |
|
170 |
logger.info("playing " + file.getName()); |
|
171 |
} |
|
172 |
//os.flush(); |
|
173 |
if(is instanceof InputStream) { |
|
174 |
is.close(); |
|
175 |
} |
|
176 |
logger.info("fertig os flush und close "); |
|
177 |
os.flush(); |
|
178 |
os.close(); |
|
179 |
} else { |
|
180 |
sendNotFound(e, file.getName()); |
|
181 |
} |
|
182 |
logger.info("ende"); |
|
183 |
} |
|
184 |
|
|
185 |
|
|
186 |
|
|
187 |
protected File serveFileParts(HttpExchange e, File file, InputStream is, OutputStream os, FileStorage s, String lName, int indexVal) throws IOException { |
|
188 |
if (file.exists()) { |
|
189 |
setHeaders(e, file); |
|
190 |
//Long byteCount = bytes.get(lName); |
|
191 |
//logger.info("byteCount at start: " + byteCount); |
|
192 |
long responseLength = 0; |
|
193 |
long start = 0; |
|
194 |
long end; |
|
195 |
String hdr; |
|
196 |
RangeGroup rangeGroup = parseRanges(e, file); |
|
197 |
Iterator<Range> i = rangeGroup.getRanges(); |
|
198 |
Headers resHeaders = e.getResponseHeaders(); |
|
199 |
while (i.hasNext()) { |
|
200 |
Range range = i.next(); |
|
201 |
start = range.getStart(); |
|
202 |
//start = 0; |
|
203 |
end = range.getEnd(); |
|
204 |
//end = file.length(); |
|
205 |
//responseLength += (end - start); |
|
206 |
//start = 0; |
|
207 |
//end = responseLength; |
|
208 |
//range.setStart(start); |
|
209 |
//range.setEnd(end); |
|
210 |
|
|
211 |
hdr = contentRangeHdr(range, file); |
|
212 |
resHeaders.add(CONTENT_RANGE_HEADER, hdr); |
|
213 |
logger.info("added header " + hdr); |
|
214 |
responseLength += (end - start); |
|
215 |
} |
|
216 |
logger.info("responseLength: " + responseLength); |
|
217 |
e.sendResponseHeaders(SC_PARTIAL_CONTENT, responseLength); |
|
218 |
//e.sendResponseHeaders(SC_PARTIAL_CONTENT, Long.MAX_VALUE); |
|
219 |
//logger.info("responseHeaders gesendet "); |
|
220 |
//if(HTTP_GET.equalsIgnoreCase(e.getRequestMethod())) { |
|
221 |
//InputStream is = new FileInputStream(file); |
|
222 |
//OutputStream os = e.getResponseBody(); |
|
223 |
//long streamPos = bytes.get(lName); |
|
224 |
long count = 0; |
|
225 |
if (start > 0) { |
|
226 |
logger.info("skip to " + start); |
|
227 |
is.skip(start); |
|
228 |
} |
|
229 |
int byteRead = is.read(); |
|
230 |
logger.info("starte while mit count=" + count); |
|
231 |
while (/*byteRead > -1 && */ count < responseLength) { |
|
232 |
++count; |
|
233 |
//++streamPos; |
|
234 |
os.write(byteRead); |
|
235 |
byteRead = is.read(); |
|
236 |
//logger.info("byteRead " + byteRead); |
|
237 |
if(byteRead < 0 && count < responseLength) { |
|
238 |
logger.info("dateiende, naechste Datei"); |
|
239 |
|
|
240 |
file = getFileToPlay(s, lName, ++indexVal); |
|
241 |
if(file != null) { |
|
242 |
logger.info("playing " + file.getName()); |
|
243 |
//streamPos = 0; |
|
244 |
is.close(); |
|
245 |
is = new FileInputStream(file); |
|
246 |
byteRead = is.read(); |
|
247 |
logger.info("neue Datei, count " + count + " responseLength " + responseLength); |
|
248 |
logger.info("file length: " + file.length()); |
|
249 |
} else { |
|
250 |
//logger.info("Liste zuende"); |
|
251 |
count = Long.MAX_VALUE; |
|
252 |
logger.info("Liste zuende"); |
|
253 |
} |
|
254 |
} |
|
255 |
} |
|
256 |
logger.info("while ende, count " + count); |
|
257 |
//if(streamPos != responseLength) { |
|
258 |
// bytes.put(lName, streamPos); |
|
259 |
// logger.info("streamPos " + streamPos); |
|
260 |
//} else { |
|
261 |
// bytes.put(lName, Long.valueOf(0)); |
|
262 |
// logger.info("streamPos " + 0); |
|
263 |
//} |
|
264 |
//byteCount = count; |
|
265 |
|
|
266 |
//logger.info("byteCount at end: " + byteCount); |
|
267 |
//os.flush(); |
|
268 |
//os.close(); |
|
269 |
// is.close(); |
|
270 |
} |
|
271 |
//} else { |
|
272 |
// sendNotFound(e, file.getName()); |
|
273 |
//} |
|
274 |
logger.info("ende"); |
|
275 |
return file; |
|
276 |
} |
|
277 |
|
|
278 |
protected String contentRangeHdr(Range range, File file) { |
|
279 |
StringBuilder sb = new StringBuilder(); |
|
280 |
sb.append(getResString(RB_BYTES)); |
|
281 |
sb.append(STR_BLANK); |
|
282 |
sb.append(range.getStart()); |
|
283 |
sb.append(getResString(RB_DASH)); |
|
284 |
sb.append(range.getEnd()); |
|
285 |
sb.append(Server.SLASH); |
|
286 |
//sb.append(file.length()); |
|
287 |
sb.append(Long.MAX_VALUE); |
|
288 |
return sb.toString(); |
|
289 |
} |
|
290 |
|
|
291 |
|
|
292 |
|
|
293 |
private void serveList(HttpExchange e, FileStorage s, String lName, File file, int indexVal) throws IOException { |
|
294 |
if (file.exists()) { |
|
295 |
setHeaders(e, file); |
|
296 |
e.getResponseHeaders().set(CONTENT_LENGTH, Long.toString(Long.MAX_VALUE)); |
|
297 |
//e.sendResponseHeaders(SC_OK, Long.MAX_VALUE); |
|
298 |
InputStream in = new FileInputStream(file); |
|
299 |
OutputStream out = e.getResponseBody(); |
|
300 |
while (file != null) { |
|
301 |
files.put(lName, file); |
|
302 |
listen.put(lName, indexVal); |
|
303 |
serveFile(e, file, in, out); |
|
304 |
file = getFileToPlay(s, lName, ++indexVal); |
|
305 |
} |
|
306 |
out.flush(); |
|
307 |
out.close(); |
|
308 |
in.close(); |
|
309 |
} else { |
|
310 |
sendNotFound(e, file.getName()); |
|
311 |
} |
|
312 |
} |
|
313 |
|
|
314 |
/** |
|
315 |
* Den Inhalt einer Datei ausliefern |
|
316 |
* |
|
317 |
* @param e das Objekt mit Methoden zur Untersuchung der Anfrage sowie zum |
|
318 |
* Anfertigen und Senden der Antwort |
|
319 |
* @param file die Datei, deren Inhalt ausgeliefert werden soll |
|
320 |
* @throws IOException falls etwas schief geht entsteht dieser Fehler |
|
321 |
*/ |
|
322 |
protected void serveFile(HttpExchange e, File file, InputStream in, OutputStream out) throws IOException { |
|
323 |
logger.info("serving file " + file.getName()); |
|
324 |
//if(HTTP_GET.equalsIgnoreCase(e.getRequestMethod())) { |
|
325 |
int b = in.read(); |
|
326 |
while (b > -1) { |
|
327 |
out.write(b); |
|
328 |
b = in.read(); |
|
329 |
} |
|
330 |
logger.info("done serving file " + file.getName()); |
|
331 |
//in.close(); |
|
332 |
//out.flush(); |
|
333 |
//} |
|
334 |
} |
|
335 |
|
|
336 |
|
|
337 |
|
|
338 |
private File getFileToPlay(FileStorage s, String lName, int index) { |
|
339 |
Entity entity = s.read(FileStorage.ST_ABSPIELLISTE, lName); |
|
340 |
if (entity instanceof Abspielliste) { |
|
341 |
Abspielliste liste = (Abspielliste) entity; |
|
342 |
List<Titel> titelListe = liste.getTitel(); |
|
343 |
if(titelListe.size() > index) { |
|
344 |
Titel t = titelListe.get(index); |
|
345 |
String katalogUrl = t.getKatalogUrl(); |
|
346 |
String pfad = kataloge.get(katalogUrl); |
|
347 |
return new File(pfad + t.getPfad(), t.getName()); |
|
348 |
} else { |
|
349 |
return null; |
|
350 |
} |
|
351 |
} else { |
|
352 |
// keine Abspielliste |
|
353 |
return null; |
|
354 |
} |
|
355 |
} |
|
356 |
|
|
357 |
private void ablageorteLesen() { |
|
358 |
String typ = Ablageort.class.getSimpleName(); |
|
359 |
FileStorage store = new FileStorage(conf); |
|
360 |
List<String> orte = store.list(typ); |
|
361 |
Iterator<String> i = orte.iterator(); |
|
362 |
while (i.hasNext()) { |
|
363 |
String ortName = i.next(); |
|
364 |
Entity e = store.read(typ, ortName); |
|
365 |
if (e instanceof Ablageort) { |
|
366 |
Ablageort ablageort = (Ablageort) e; |
|
367 |
Logger logger = Logger.getLogger(StreamHandler.class.getName()); |
|
368 |
//logger.log(Level.FINE, "{0}{1}", new Object[]{ctx, ablageort.getUrl()}); |
|
369 |
logger.fine(ablageort.getOrt() + " " + ablageort.getUrl()); |
|
370 |
kataloge.put(ablageort.getUrl(), ablageort.getOrt()); |
|
371 |
//server.createContext(ctx + ablageort.getUrl(), |
|
372 |
//new ListFileHandler(new File(ablageort.getOrt()).getAbsolutePath(), conf)); |
|
373 |
} |
|
374 |
} |
|
375 |
} |
|
376 |
|
1ff360
|
377 |
} |