Remove socket camera streaming (#985)

Removes websocket-based camera streaming functionality. 

Fixes #975. This was caused by destroying the camera streams and recreating them on nickname change. Even when directly using `MJPGFrameConsumer` and the streams were exactly the same, the freeze would occur when creating a new `MjpegServer` and require a refresh. I think this is simply how cscore works?
This commit is contained in:
amquake
2023-10-29 20:03:05 -07:00
committed by GitHub
parent 0898dfe2f7
commit 76e3c6d5a5
8 changed files with 30 additions and 474 deletions

View File

@@ -1,143 +0,0 @@
/*
* Copyright (C) Photon Vision.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.photonvision.server;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.javalin.websocket.WsBinaryMessageContext;
import io.javalin.websocket.WsCloseContext;
import io.javalin.websocket.WsConnectContext;
import io.javalin.websocket.WsContext;
import io.javalin.websocket.WsMessageContext;
import java.net.InetSocketAddress;
import java.time.Duration;
import java.util.HashMap;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import org.photonvision.common.logging.LogGroup;
import org.photonvision.common.logging.Logger;
import org.photonvision.vision.videoStream.SocketVideoStreamManager;
public class CameraSocketHandler {
private final Logger logger = new Logger(CameraSocketHandler.class, LogGroup.WebServer);
private final List<WsContext> users = new CopyOnWriteArrayList<>();
private final SocketVideoStreamManager svsManager = SocketVideoStreamManager.getInstance();
private Thread cameraBroadcastThread;
public static class UIMap extends HashMap<String, Object> {}
private static class ThreadSafeSingleton {
private static final CameraSocketHandler INSTANCE = new CameraSocketHandler();
}
public static CameraSocketHandler getInstance() {
return CameraSocketHandler.ThreadSafeSingleton.INSTANCE;
}
private CameraSocketHandler() {
cameraBroadcastThread = new Thread(this::broadcastFramesTask);
cameraBroadcastThread.setPriority(Thread.MAX_PRIORITY - 3); // fairly high priority
cameraBroadcastThread.start();
}
public void onConnect(WsConnectContext context) {
context.session.setIdleTimeout(
Duration.ofMillis(Long.MAX_VALUE)); // TODO: determine better value
var remote = (InetSocketAddress) context.session.getRemoteAddress();
var host = remote.getAddress().toString() + ":" + remote.getPort();
logger.info("New camera websocket connection from " + host);
users.add(context);
}
protected void onClose(WsCloseContext context) {
var remote = (InetSocketAddress) context.session.getRemoteAddress();
var host = remote.getAddress().toString() + ":" + remote.getPort();
var reason = context.reason() != null ? context.reason() : "Connection closed by client";
logger.info("Closing camera websocket connection from " + host + " for reason: " + reason);
svsManager.removeSubscription(context);
users.remove(context);
}
@SuppressWarnings({"unchecked"})
public void onMessage(WsMessageContext context) {
var messageStr = context.message();
ObjectMapper mapper = new ObjectMapper();
try {
JsonNode actualObj = mapper.readTree(messageStr);
try {
var entryCmd = actualObj.get("cmd").asText();
var socketMessageType = CameraSocketMessageType.fromEntryKey(entryCmd);
logger.trace(() -> "Got Camera WS message: [" + socketMessageType + "]");
if (socketMessageType == null) {
logger.warn("Got unknown socket message command: " + entryCmd);
}
switch (socketMessageType) {
case CSMT_SUBSCRIBE:
{
int portId = actualObj.get("port").asInt();
svsManager.addSubscription(context, portId);
break;
}
case CSMT_UNSUBSCRIBE:
{
svsManager.removeSubscription(context);
break;
}
}
} catch (Exception e) {
logger.error("Failed to parse message!", e);
}
} catch (JsonProcessingException e) {
logger.warn("Could not parse message \"" + messageStr + "\"");
e.printStackTrace();
return;
}
}
@SuppressWarnings({"unchecked"})
public void onBinaryMessage(WsBinaryMessageContext context) {
return; // ignoring binary messages for now
}
private void broadcastFramesTask() {
// Background camera image broadcasting thread
while (!Thread.currentThread().isInterrupted()) {
svsManager.allStreamConvertNextFrame();
try {
Thread.sleep(1);
} catch (InterruptedException e) {
logger.error("Exception waiting for camera stream broadcast semaphore", e);
}
for (var user : users) {
var sendBytes = svsManager.getSendFrame(user);
if (sendBytes != null) {
user.send(sendBytes);
}
}
}
}
}

View File

@@ -1,46 +0,0 @@
/*
* Copyright (C) Photon Vision.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.photonvision.server;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.Map;
@SuppressWarnings("unused")
public enum CameraSocketMessageType {
CSMT_SUBSCRIBE("subscribe"),
CSMT_UNSUBSCRIBE("unsubscribe");
public final String entryKey;
CameraSocketMessageType(String entryKey) {
this.entryKey = entryKey;
}
private static final Map<String, CameraSocketMessageType> entryKeyToValueMap = new HashMap<>();
static {
for (var value : EnumSet.allOf(CameraSocketMessageType.class)) {
entryKeyToValueMap.put(value.entryKey, value);
}
}
public static CameraSocketMessageType fromEntryKey(String entryKey) {
return entryKeyToValueMap.get(entryKey);
}
}

View File

@@ -79,17 +79,6 @@ public class Server {
ws.onBinaryMessage(dsHandler::onBinaryMessage);
});
/*Web Socket Events for Camera Streaming */
var camDsHandler = CameraSocketHandler.getInstance();
app.ws(
"/websocket_cameras",
ws -> {
ws.onConnect(camDsHandler::onConnect);
ws.onClose(camDsHandler::onClose);
ws.onBinaryMessage(camDsHandler::onBinaryMessage);
ws.onMessage(camDsHandler::onMessage);
});
/*API Events*/
// Settings
app.post("/api/settings", RequestHandler::onSettingsImportRequest);

View File

@@ -1,26 +0,0 @@
/*
* Copyright (C) Photon Vision.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.photonvision.server;
import org.photonvision.common.configuration.CameraConfiguration;
import org.photonvision.common.configuration.NetworkConfig;
public class UISettings {
public NetworkConfig networkConfig;
public CameraConfiguration currentCameraConfiguration;
}