]> git.basschouten.com Git - openhab-addons.git/blob
12ca6b259e8997e6134fab9348a408a3c16fd8eb
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2021 Contributors to the openHAB project
3  *
4  * See the NOTICE file(s) distributed with this work for additional
5  * information.
6  *
7  * This program and the accompanying materials are made available under the
8  * terms of the Eclipse Public License 2.0 which is available at
9  * http://www.eclipse.org/legal/epl-2.0
10  *
11  * SPDX-License-Identifier: EPL-2.0
12  */
13 package org.openhab.binding.ipcamera.internal.servlet;
14
15 import static org.openhab.binding.ipcamera.internal.IpCameraBindingConstants.HLS_STARTUP_DELAY_MS;
16
17 import java.io.IOException;
18
19 import javax.servlet.ServletInputStream;
20 import javax.servlet.http.HttpServletRequest;
21 import javax.servlet.http.HttpServletResponse;
22
23 import org.eclipse.jdt.annotation.NonNullByDefault;
24 import org.eclipse.jdt.annotation.Nullable;
25 import org.openhab.binding.ipcamera.internal.Ffmpeg;
26 import org.openhab.binding.ipcamera.internal.InstarHandler;
27 import org.openhab.binding.ipcamera.internal.IpCameraBindingConstants.FFmpegFormat;
28 import org.openhab.binding.ipcamera.internal.handler.IpCameraHandler;
29 import org.osgi.service.http.HttpService;
30
31 /**
32  * The {@link CameraServlet} is responsible for serving files for a single camera back to the Jetty server normally
33  * found on port 8080
34  *
35  * @author Matthew Skinner - Initial contribution
36  */
37 @NonNullByDefault
38 public class CameraServlet extends IpCameraServlet {
39     private static final long serialVersionUID = -134658667574L;
40     private final IpCameraHandler handler;
41     private int autofpsStreamsOpen = 0;
42     private int snapshotStreamsOpen = 0;
43     public OpenStreams openStreams = new OpenStreams();
44
45     public CameraServlet(IpCameraHandler handler, HttpService httpService) {
46         super(handler, httpService);
47         this.handler = handler;
48     }
49
50     @Override
51     protected void doPost(@Nullable HttpServletRequest req, @Nullable HttpServletResponse resp) throws IOException {
52         if (req == null || resp == null) {
53             return;
54         }
55         String pathInfo = req.getPathInfo();
56         if (pathInfo == null) {
57             return;
58         }
59         switch (pathInfo) {
60             case "/ipcamera.jpg":
61                 // ffmpeg sends data here for ipcamera.mjpeg streams when camera has no native stream.
62                 ServletInputStream snapshotData = req.getInputStream();
63                 openStreams.queueFrame(snapshotData.readAllBytes());
64                 snapshotData.close();
65                 break;
66             case "/snapshot.jpg":
67                 snapshotData = req.getInputStream();
68                 handler.processSnapshot(snapshotData.readAllBytes());
69                 snapshotData.close();
70                 break;
71             case "/OnvifEvent":
72                 handler.onvifCamera.eventRecieved(req.getReader().toString());
73                 break;
74             default:
75                 logger.debug("Recieved unknown request \tPOST:{}", pathInfo);
76                 break;
77         }
78     }
79
80     @Override
81     protected void doGet(@Nullable HttpServletRequest req, @Nullable HttpServletResponse resp) throws IOException {
82         if (req == null || resp == null) {
83             return;
84         }
85         String pathInfo = req.getPathInfo();
86         if (pathInfo == null) {
87             return;
88         }
89         logger.debug("GET:{}, received from {}", pathInfo, req.getRemoteHost());
90         if (!"DISABLE".equals(handler.getWhiteList())) {
91             String requestIP = "(" + req.getRemoteHost() + ")";
92             if (!handler.getWhiteList().contains(requestIP)) {
93                 logger.warn("The request made from {} was not in the whiteList and will be ignored.", requestIP);
94                 return;
95             }
96         }
97         switch (pathInfo) {
98             case "/ipcamera.m3u8":
99                 Ffmpeg localFfmpeg = handler.ffmpegHLS;
100                 if (localFfmpeg == null) {
101                     handler.setupFfmpegFormat(FFmpegFormat.HLS);
102                 } else if (!localFfmpeg.getIsAlive()) {
103                     localFfmpeg.startConverting();
104                 } else {
105                     localFfmpeg.setKeepAlive(8);
106                     sendFile(resp, pathInfo, "application/x-mpegURL");
107                     return;
108                 }
109                 // Allow files to be created, or you get old m3u8 from the last time this ran.
110                 try {
111                     Thread.sleep(HLS_STARTUP_DELAY_MS);
112                 } catch (InterruptedException e) {
113                     return;
114                 }
115                 sendFile(resp, pathInfo, "application/x-mpegURL");
116                 return;
117             case "/ipcamera.mpd":
118                 sendFile(resp, pathInfo, "application/dash+xml");
119                 return;
120             case "/ipcamera.gif":
121                 sendFile(resp, pathInfo, "image/gif");
122                 return;
123             case "/ipcamera.jpg":
124                 sendSnapshotImage(resp, "image/jpg", handler.getSnapshot());
125                 return;
126             case "/snapshots.mjpeg":
127                 req.getSession().setMaxInactiveInterval(0);
128                 snapshotStreamsOpen++;
129                 handler.streamingSnapshotMjpeg = true;
130                 handler.startSnapshotPolling();
131                 StreamOutput output = new StreamOutput(resp);
132                 do {
133                     try {
134                         output.sendSnapshotBasedFrame(handler.getSnapshot());
135                         Thread.sleep(1005);
136                     } catch (InterruptedException | IOException e) {
137                         // Never stop streaming until IOException. Occurs when browser stops the stream.
138                         snapshotStreamsOpen--;
139                         if (snapshotStreamsOpen == 0) {
140                             handler.streamingSnapshotMjpeg = false;
141                             handler.stopSnapshotPolling();
142                             logger.debug("All snapshots.mjpeg streams have stopped.");
143                         }
144                         return;
145                     }
146                 } while (true);
147             case "/ipcamera.mjpeg":
148                 req.getSession().setMaxInactiveInterval(0);
149                 if (handler.mjpegUri.isEmpty() || "ffmpeg".equals(handler.mjpegUri)) {
150                     if (openStreams.isEmpty()) {
151                         handler.setupFfmpegFormat(FFmpegFormat.MJPEG);
152                     }
153                     output = new StreamOutput(resp);
154                     openStreams.addStream(output);
155                 } else if (openStreams.isEmpty()) {
156                     logger.debug("First stream requested, opening up stream from camera");
157                     handler.openCamerasStream();
158                     output = new StreamOutput(resp, handler.mjpegContentType);
159                     openStreams.addStream(output);
160                 } else {
161                     logger.debug("Not the first stream requested. Stream from camera already open");
162                     output = new StreamOutput(resp, handler.mjpegContentType);
163                     openStreams.addStream(output);
164                 }
165                 do {
166                     try {
167                         output.sendFrame();
168                     } catch (InterruptedException | IOException e) {
169                         // Never stop streaming until IOException. Occurs when browser stops the stream.
170                         openStreams.removeStream(output);
171                         if (openStreams.isEmpty()) {
172                             if (output.isSnapshotBased) {
173                                 Ffmpeg localMjpeg = handler.ffmpegMjpeg;
174                                 if (localMjpeg != null) {
175                                     localMjpeg.stopConverting();
176                                 }
177                             } else {
178                                 handler.closeChannel(handler.getTinyUrl(handler.mjpegUri));
179                             }
180                             logger.debug("All ipcamera.mjpeg streams have stopped.");
181                         }
182                         return;
183                     }
184                 } while (true);
185             case "/autofps.mjpeg":
186                 req.getSession().setMaxInactiveInterval(0);
187                 autofpsStreamsOpen++;
188                 handler.streamingAutoFps = true;
189                 output = new StreamOutput(resp);
190                 int counter = 0;
191                 do {
192                     try {
193                         if (handler.motionDetected) {
194                             output.sendSnapshotBasedFrame(handler.getSnapshot());
195                         } // every 8 seconds if no motion or the first three snapshots to fill any FIFO
196                         else if (counter % 8 == 0 || counter < 3) {
197                             output.sendSnapshotBasedFrame(handler.getSnapshot());
198                         }
199                         counter++;
200                         Thread.sleep(1000);
201                     } catch (InterruptedException | IOException e) {
202                         // Never stop streaming until IOException. Occurs when browser stops the stream.
203                         autofpsStreamsOpen--;
204                         if (autofpsStreamsOpen == 0) {
205                             handler.streamingAutoFps = false;
206                             logger.debug("All autofps.mjpeg streams have stopped.");
207                         }
208                         return;
209                     }
210                 } while (true);
211             case "/instar":
212                 InstarHandler instar = new InstarHandler(handler);
213                 instar.alarmTriggered(pathInfo + "?" + req.getQueryString());
214                 return;
215             default:
216                 if (pathInfo.endsWith(".ts")) {
217                     sendFile(resp, pathInfo, "video/MP2T");
218                 } else if (pathInfo.endsWith(".gif")) {
219                     sendFile(resp, pathInfo, "image/gif");
220                 } else if (pathInfo.endsWith(".jpg")) {
221                     // Allow access to the preroll and postroll jpg files
222                     sendFile(resp, pathInfo, "image/jpg");
223                 } else if (pathInfo.endsWith(".mp4")) {
224                     sendFile(resp, pathInfo, "video/mp4");
225                 }
226                 return;
227         }
228     }
229
230     @Override
231     protected void sendFile(HttpServletResponse response, String filename, String contentType) throws IOException {
232         // Ensure no files can be sourced from parent or child folders
233         String truncated = filename.substring(filename.lastIndexOf("/"));
234         super.sendFile(response, handler.cameraConfig.getFfmpegOutput() + truncated, contentType);
235     }
236
237     @Override
238     public void dispose() {
239         openStreams.closeAllStreams();
240         super.dispose();
241     }
242 }