2 * Copyright (c) 2010-2021 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
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
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.ipcamera.internal.servlet;
15 import static org.openhab.binding.ipcamera.internal.IpCameraBindingConstants.HLS_STARTUP_DELAY_MS;
17 import java.io.IOException;
19 import javax.servlet.ServletInputStream;
20 import javax.servlet.http.HttpServletRequest;
21 import javax.servlet.http.HttpServletResponse;
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;
32 * The {@link CameraServlet} is responsible for serving files for a single camera back to the Jetty server normally
35 * @author Matthew Skinner - Initial contribution
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();
45 public CameraServlet(IpCameraHandler handler, HttpService httpService) {
46 super(handler, httpService);
47 this.handler = handler;
51 protected void doPost(@Nullable HttpServletRequest req, @Nullable HttpServletResponse resp) throws IOException {
52 if (req == null || resp == null) {
55 String pathInfo = req.getPathInfo();
56 if (pathInfo == null) {
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());
67 snapshotData = req.getInputStream();
68 handler.processSnapshot(snapshotData.readAllBytes());
72 handler.onvifCamera.eventRecieved(req.getReader().toString());
75 logger.debug("Recieved unknown request \tPOST:{}", pathInfo);
81 protected void doGet(@Nullable HttpServletRequest req, @Nullable HttpServletResponse resp) throws IOException {
82 if (req == null || resp == null) {
85 String pathInfo = req.getPathInfo();
86 if (pathInfo == null) {
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);
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();
105 localFfmpeg.setKeepAlive(8);
106 sendFile(resp, pathInfo, "application/x-mpegURL");
109 // Allow files to be created, or you get old m3u8 from the last time this ran.
111 Thread.sleep(HLS_STARTUP_DELAY_MS);
112 } catch (InterruptedException e) {
115 sendFile(resp, pathInfo, "application/x-mpegURL");
117 case "/ipcamera.mpd":
118 sendFile(resp, pathInfo, "application/dash+xml");
120 case "/ipcamera.gif":
121 sendFile(resp, pathInfo, "image/gif");
123 case "/ipcamera.jpg":
124 sendSnapshotImage(resp, "image/jpg", handler.getSnapshot());
126 case "/snapshots.mjpeg":
127 req.getSession().setMaxInactiveInterval(0);
128 snapshotStreamsOpen++;
129 handler.streamingSnapshotMjpeg = true;
130 handler.startSnapshotPolling();
131 StreamOutput output = new StreamOutput(resp);
134 output.sendSnapshotBasedFrame(handler.getSnapshot());
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.");
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);
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);
161 logger.debug("Not the first stream requested. Stream from camera already open");
162 output = new StreamOutput(resp, handler.mjpegContentType);
163 openStreams.addStream(output);
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();
178 handler.closeChannel(handler.getTinyUrl(handler.mjpegUri));
180 logger.debug("All ipcamera.mjpeg streams have stopped.");
185 case "/autofps.mjpeg":
186 req.getSession().setMaxInactiveInterval(0);
187 autofpsStreamsOpen++;
188 handler.streamingAutoFps = true;
189 output = new StreamOutput(resp);
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());
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.");
212 InstarHandler instar = new InstarHandler(handler);
213 instar.alarmTriggered(pathInfo + "?" + req.getQueryString());
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");
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);
238 public void dispose() {
239 openStreams.closeAllStreams();