2 * Copyright (c) 2010-2024 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 java.io.IOException;
16 import java.util.concurrent.ArrayBlockingQueue;
17 import java.util.concurrent.BlockingQueue;
19 import javax.servlet.ServletOutputStream;
20 import javax.servlet.http.HttpServletResponse;
22 import org.eclipse.jdt.annotation.NonNullByDefault;
23 import org.slf4j.Logger;
24 import org.slf4j.LoggerFactory;
27 * The {@link StreamOutput} Streams mjpeg out to a client
29 * @author Matthew Skinner - Initial contribution
32 public class StreamOutput {
33 public final Logger logger = LoggerFactory.getLogger(getClass());
34 private final HttpServletResponse response;
35 private final String boundary;
36 private String contentType;
37 private final ServletOutputStream output;
38 private BlockingQueue<byte[]> fifo = new ArrayBlockingQueue<byte[]>(50);
39 private boolean connected = false;
40 public boolean isSnapshotBased = false;
42 public StreamOutput(HttpServletResponse response) throws IOException {
43 boundary = "thisMjpegStream";
44 contentType = "multipart/x-mixed-replace; boundary=" + boundary;
45 this.response = response;
46 output = response.getOutputStream();
47 isSnapshotBased = true;
50 public StreamOutput(HttpServletResponse response, String contentType) throws IOException {
52 this.contentType = contentType;
53 this.response = response;
54 output = response.getOutputStream();
55 if (!contentType.isEmpty()) {
61 public void sendSnapshotBasedFrame(byte[] currentSnapshot) throws IOException {
62 String header = "--" + boundary + "\r\n" + "Content-Type: image/jpeg" + "\r\n" + "Content-Length: "
63 + currentSnapshot.length + "\r\n\r\n";
66 // iOS needs to have two jpgs sent for the picture to appear instantly.
67 output.write(header.getBytes());
68 output.write(currentSnapshot);
69 output.write("\r\n".getBytes());
72 output.write(header.getBytes());
73 output.write(currentSnapshot);
74 output.write("\r\n".getBytes());
77 public void queueFrame(byte[] frame) {
80 } catch (IllegalStateException e) {
81 logger.debug("FIFO buffer has run out of space: {}", e.getMessage());
87 public void updateContentType(String contentType) {
88 this.contentType = contentType;
95 public void sendFrame() throws IOException, InterruptedException {
96 if (isSnapshotBased) {
97 sendSnapshotBasedFrame(fifo.take());
98 } else if (connected) {
99 output.write(fifo.take());
103 private void sendInitialHeaders() {
104 response.setContentType(contentType);
105 response.setHeader("Access-Control-Allow-Origin", "*");
106 response.setHeader("Access-Control-Expose-Headers", "*");
109 public void close() {
112 } catch (IOException e) {