2 * Copyright (c) 2010-2023 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.*;
18 import java.io.IOException;
19 import java.util.concurrent.TimeUnit;
21 import javax.servlet.http.HttpServletRequest;
22 import javax.servlet.http.HttpServletResponse;
24 import org.eclipse.jdt.annotation.NonNullByDefault;
25 import org.eclipse.jdt.annotation.Nullable;
26 import org.openhab.binding.ipcamera.internal.handler.IpCameraGroupHandler;
27 import org.openhab.core.library.types.OnOffType;
28 import org.openhab.core.thing.ChannelUID;
29 import org.osgi.service.http.HttpService;
32 * The {@link GroupServlet} is responsible for serving files for a rotating feed of multiple cameras back to the Jetty
33 * server normally found on port 8080
35 * @author Matthew Skinner - Initial contribution
38 public class GroupServlet extends IpCameraServlet {
39 private static final long serialVersionUID = -234658667574L;
40 private final IpCameraGroupHandler handler;
41 public int snapshotStreamsOpen = 0;
43 public GroupServlet(IpCameraGroupHandler handler, HttpService httpService) {
44 super(handler, httpService);
45 this.handler = handler;
49 protected void doGet(@Nullable HttpServletRequest req, @Nullable HttpServletResponse resp) throws IOException {
50 if (req == null || resp == null) {
53 String pathInfo = req.getPathInfo();
54 if (pathInfo == null) {
57 logger.debug("GET:{}, received from {}", pathInfo, req.getRemoteHost());
58 if (!"DISABLE".equals(handler.groupConfig.getIpWhitelist())) {
59 String requestIP = "(" + req.getRemoteHost() + ")";
60 if (!handler.groupConfig.getIpWhitelist().contains(requestIP)) {
61 logger.warn("The request made from {} was not in the whiteList and will be ignored.", requestIP);
66 case "/ipcamera.m3u8":
67 if (!handler.hlsTurnedOn) {
69 "HLS requires the groups startStream channel to be turned on first. Just starting it now.");
70 String channelPrefix = "ipcamera:" + handler.getThing().getThingTypeUID() + ":"
71 + handler.getThing().getUID().getId() + ":";
72 handler.handleCommand(new ChannelUID(channelPrefix + CHANNEL_START_STREAM), OnOffType.ON);
74 TimeUnit.MILLISECONDS.sleep(HLS_STARTUP_DELAY_MS);
75 } catch (InterruptedException e) {
79 String playList = handler.getPlayList();
80 sendString(resp, playList, "application/x-mpegURL");
83 sendSnapshotImage(resp, "image/jpg", handler.getSnapshot());
85 case "/ipcamera.mjpeg":
86 case "/snapshots.mjpeg":
87 req.getSession().setMaxInactiveInterval(0);
88 snapshotStreamsOpen++;
89 StreamOutput output = new StreamOutput(resp);
92 output.sendSnapshotBasedFrame(handler.getSnapshot());
94 } catch (InterruptedException | IOException e) {
95 // Never stop streaming until IOException. Occurs when browser stops the stream.
96 snapshotStreamsOpen--;
97 if (snapshotStreamsOpen == 0) {
98 logger.debug("All snapshots.mjpeg streams have stopped.");
104 // example is "/1ipcameraxx.ts"
105 if (pathInfo.endsWith(".ts")) {
106 sendFile(resp, pathInfo, "video/MP2T");
111 private String resolveIndexToPath(String uri) {
112 if (!"i".equals(uri.substring(1, 2))) {
113 return handler.getOutputFolder(Integer.parseInt(uri.substring(1, 2)));
119 protected void sendFile(HttpServletResponse response, String filename, String contentType) throws IOException {
120 // Ensure no files can be sourced from parent or child folders
121 String truncated = filename.substring(filename.lastIndexOf("/"));
122 truncated = resolveIndexToPath(truncated) + truncated.substring(2);
123 File file = new File(truncated);
124 if (!file.exists()) {
126 "HLS File {} was not found. Try adding a larger -hls_delete_threshold to each cameras HLS out options.",
128 response.sendError(HttpServletResponse.SC_NOT_FOUND);
131 super.sendFile(response, truncated, contentType);
135 protected void sendSnapshotImage(HttpServletResponse response, String contentType, byte[] snapshot) {
136 if (handler.cameraIndex >= handler.cameraOrder.size()) {
137 logger.debug("All cameras in this group are OFFLINE and a snapshot was requested.");
140 super.sendSnapshotImage(response, contentType, snapshot);