mirror of
https://github.com/PhotonVision/photonvision
synced 2026-06-20 00:51:41 +00:00
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:
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
Reference in New Issue
Block a user