Files
xtrhrj/src/main/java/com/xtrh/node/controller/WorkerController.java
T

71 lines
3.2 KiB
Java

package com.xtrh.node.controller;
import com.xtrh.node.service.WorkerService;
import com.xtrh.node.config.WorkerCapabilityCondition;
import org.springframework.context.annotation.Conditional;
import org.springframework.http.CacheControl;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
import java.nio.charset.StandardCharsets;
/**
* 节点 HTTP 入口只负责查询与流输出,业务许可由服务层校验。
* 播放地址包含任务号和版本,迁移时由总控向 PXHJ 回传新地址。
* 主节点兼任执行端时在同一端口提供媒体入口,UDP 网关不承担视频任务。
*/
@RestController
@Conditional(WorkerCapabilityCondition.class)
public class WorkerController {
private final WorkerService worker;
public WorkerController(WorkerService worker) {
this.worker = worker;
}
// 状态快照由服务生成,控制层不读取或修改内部任务集合。
@GetMapping("/worker/status")
public Object status() {
return worker.status();
}
/**
* 输出浏览器可直接读取的 MJPEG 流,每个分段是一张完整 JPEG。
* 先检查授权再返回成功响应,避免无效地址建立空的长连接。
* 不缓存实时画面,防止浏览器复用结束或迁移前的内容。
*/
@GetMapping("/media/{taskId}/{version}/stream")
public ResponseEntity<StreamingResponseBody> stream(
@PathVariable String taskId, @PathVariable long version, @RequestParam String key) {
worker.frame(taskId, version, key);
StreamingResponseBody body = output -> {
try {
while (true) {
// 每帧重新取得当前状态,准备转运行后不再引用旧任务对象。
byte[] frame = worker.frame(taskId, version, key);
if (frame != null) {
String header = "--frame\r\nContent-Type: image/jpeg\r\nContent-Length: "
+ frame.length + "\r\n\r\n";
output.write(header.getBytes(StandardCharsets.US_ASCII));
output.write(frame);
output.write("\r\n".getBytes(StandardCharsets.US_ASCII));
output.flush();
}
Thread.sleep(250);
}
} catch (InterruptedException interrupted) {
// 容器关闭线程时保留中断标记,流循环随之结束。
Thread.currentThread().interrupt();
} catch (IllegalArgumentException inactiveTask) {
// 迁移、结束或许可到期后停止旧流,不能继续发送缓存画面。
}
};
return ResponseEntity.ok().cacheControl(CacheControl.noStore())
.header("Content-Type", "multipart/x-mixed-replace; boundary=frame").body(body);
}
}