SOURCE

console 命令行工具 X clear

                    
>
console
document.querySelectorAll('.btns button').forEach(v => {
    v.setAttribute('disabled', true)
    document.getElementById('domOpenConnect').removeAttribute('disabled')
})

let ws = null
const img = document.getElementById("stream");
const canvas = document.querySelector("canvas");
const ctx = canvas.getContext("2d");
const domNowTime = document.getElementById('nowTime')
const domMessage = document.getElementById('message')
const buffSize = 500
let buffData = []
let boxes = []
let boxesNew = []

var lastWsTime = 0
var frameBuffObj = []
var frameBuff = []
var boxBuff = []
var boxBuffObj = []
var currIndex = 0, indexNum = 0
var boxIndex = 0, boxNum = 0
var selBoxIndex = 0
var lastTime = 0
var timer02 = null
var funcId = ''

function doTest() {
    ctx.strokeRect(0, 0, 100, 100);
    ctx.strokeRect(0, 1080 / 2 - 50, 100, 100);
    ctx.strokeRect(1920 - 100, 0, 100, 100);
    ctx.strokeRect(1920 - 100, 1080 / 2 - 50, 100, 100);
    ctx.strokeRect(0, 1080 - 100, 100, 100);
    ctx.strokeRect(1920 - 100, 1080 - 100, 100, 100);

    ctx.strokeRect(1920 / 2 - 50, 0, 100, 100);
    ctx.strokeRect(1920 / 2 - 50, 1080 - 100, 100, 100);
    ctx.strokeRect(1920 / 2 - 50, 1080 / 2 - 50, 100, 100);
    // var obj = event.target
    // if (timer02) {
    //     clearInterval(timer02)
    //     timer02 = null
    //     obj.innerHTML = '开启'
    // } else {
    //     obj.innerHTML = '关闭'
    //     timer02 = setInterval(doTestLoop, 500)
    // }
}

function doTestLoop() {

    message.innerHTML = ''
    frameBuffObj.forEach(v => {
        if (!v) return
        var img = new Image()
        img.src = v.data
        // message.prepend(img)
        var div = document.createElement('div')
        div.innerHTML = new Date(v.time).toLocaleString() + ' ' + (v.time)
        message.prepend(div)
    })
    process1.innerHTML = currIndex
    process1.style.width = (((currIndex / buffSize) * 100) + '%')

    message2.innerHTML = ''
    boxBuffObj.forEach(v => {
        var div = document.createElement('div')
        div.innerHTML = new Date(v.time).toLocaleString() + ' ' + (v.time)
        message2.prepend(div)
    })
    process2.style.width = (((boxIndex / buffSize) * 100) + '%')

    // draw_image_and_boxes(frame, res)
}

var mdate = {}
function openConnect() {
    init()
}

function closeConnect() {
    if (ws) {
        ws.close()
        ws = null
    }
    console.log(JSON.stringify(mdate))
}
function sendDebug() {
    let data = {
        action: 'debug',
        // params: cameraId + ""
    }
    ws.send(JSON.stringify(data))
}

function sendFunc() {
    var cameraId = document.getElementById('cameraId').value
    let data = {
        action: 'func',
        params: cameraId + ""
    }
    ws.send(JSON.stringify(data))
}

function sendFuncStop() {
    let data = {
        action: 'func.stop',
        id: funcId
    }
    alert(JSON.stringify(data))
    ws.send(JSON.stringify(data))
}


function doRender() {
    var obj = event.target
    if (!isRender) {
        //     isRender = true
        startRender()
        obj.innerHTML = 'Stop Render'
    } else {
        stopRender()
        obj.innerHTML = 'Render'
    }
}

function openStream() {
    canvas.width = 0
    var cameraId = document.getElementById('cameraId').value
    let data = {
        action: 'start',
        params: cameraId + ""
    }
    ws.send(JSON.stringify(data))
    logClean()
    selBoxIndex = currIndex
}


function closeStream() {
    var cameraId = document.getElementById('cameraId').value
    let data = {
        action: 'stop',
        params: cameraId + ""
    }
    ws.send(JSON.stringify(data))
    // currIndex = 0
    // indexNum = 0
    // selBoxIndex = 0

}

var boxNum = 0
function init() {
    ws = new WebSocket("ws://localhost:12800/ws");
    // ws = new WebSocket("ws://localhost:12801/ws");
    frameBuff = []
    boxBuff = []
    currIndex = 0
    boxIndex = 0

    for (var i = 0; i < buffSize; i++) {
        frameBuff[i + ""] = ""
    }
    for (var i = 0; i < buffSize; i++) {
        boxBuff[i + ""] = []
    }

    ws.onmessage = function (event) {
        if (typeof event.data === "object") {
            var blob = new Blob([event.data], { type: "image/jpeg" })
            var url = URL.createObjectURL(blob)

            frameBuff[currIndex + ""] = url
            frameBuffObj[currIndex + ""] = {
                data: url,
                time: lastWsTime
            }
            // console.log("frameTime: ", new Date(lastWsTime).toLocaleString())
            selBoxIndex = currIndex
            render()

            currIndex++
            if (currIndex >= buffSize) {
                currIndex = 0
                indexNum++
            }

            process1.innerHTML = boxes.length //(new Date(lastWsTime).toLocaleString())
            process1.style.width = (((currIndex / buffSize) * 100) + '%')
            // var k = parseInt(new Date().getTime() / 1000)

            // if (!mdate[k]) {
            //     mdate[k] = 0
            // }
            // mdate[k]++


        } else {
            //if (!event.data) return
            //domNowTime.innerHTML = (new Date().toLocaleTimeString())
            // console.log(event.data)
            var data = {}
            try {
                data = JSON.parse(event.data)
            } catch {
                data = {}
            }

            if (data.type) {
                if (data.type === 'point') {
                    var res = data.boxes
                    if (!res) return
                    if (boxes.length > 50) boxes = []
                    boxes = boxes.concat(res)

                    // console.log(boxes.length)
                    // console.log("######### time  ######", res)
                    // boxIndex = data.index

                    boxBuff[boxIndex + ""] = res
                    boxBuffObj[boxIndex + ""] = {
                        data: res,
                        time: data.time
                    }
                    // console.log("boxTime: ", new Date(data.time).toLocaleString())
                    boxIndex++
                    if (boxIndex == buffSize) {
                        boxIndex = 0
                        boxNum++
                    }
                    //console.log(parseInt((data.time - 1739255034069)/1000), data.index)
                    let n = new Date().getTime()
                    if (data.time > 0) {
                        // console.log('##',n - data.time)
                        boxesNew.push({
                            data: res,
                            time: data.time
                        })
                    }

                    // 只保留3s内的框
                    boxesNew = boxesNew.filter(v => {
                        return n - v.time < 500
                    })

                    draw_boxes(boxesNew)
                } else if (data.type === 'frame') {
                    //  if (Object.keys(buffData).length > 100) {
                    //     buffData =[]
                    // }
                    var time = parseInt(data.time)
                    lastWsTime = time
                    var key = parseInt(time / 1000) + ''
                    //  console.log(key, typeof key)
                    if (!buffData[key]) {
                        buffData[key] = 0
                    }
                    buffData[key]++

                    // console.log('## time ##', time)
                } else if (data.type === 'strm1') {
                    var index0 = parseInt(data.d1)
                    var index1 = parseInt(data.d2)

                } else if (data.type == 'error') {
                    log(data.error)
                    log("close")
                } else if (data.type === 'func') {
                    // alert(data.id)
                    funcId = data.id
                }
            } else {

            }
        }
    };

    ws.onopen = function () {
        document.querySelectorAll('.btns button').forEach(v => {
            v.removeAttribute('disabled')
            document.getElementById('domOpenConnect').setAttribute('disabled', true)
        })
    };

    ws.onclose = function () {
        document.querySelectorAll('.btns button').forEach(v => {
            v.setAttribute('disabled', true)
            document.getElementById('domOpenConnect').removeAttribute('disabled')
        })
    };

    ws.onerror = function (error) {
        console.error("WebSocket error:", error);
    };
}

var timer01 = null
var nowIndex = 0
var latestBox = []
var isRender = false

function startRender() {
    isRender = true
    if (window) return

    nowIndex = currIndex
    // nowIndex = currIndex - 1
    // nowIndex = nowIndex<0?(buffSize-1):nowIndex

    latestBox = []
    if (timer01) {
        clearInterval(timer01)
        timer01 = null
    } else {
        timer01 = setInterval(render, 1000 / 25)
    }
}

function stopRender() {
    if (timer01) {
        clearInterval(timer01)
        timer01 = null
    }
    isRender = false
}

function render1() {
    var dt = (indexNum * buffSize + currIndex) - (boxNum * buffSize + boxIndex)
    if (dt < 0) {
        console.log('消费太快')
        return
    }

    if (dt > buffSize) {
        console.log('消费太慢')
        boxIndex = currIndex
        boxNum = indexNum
    }

    var frame = frameBuff[nowIndex + '']
    if (!frame) {
        return
    }

    draw_image_and_boxes(frame, [])

    nowIndex++
    if (nowIndex >= buffSize) {
        nowIndex = 0
        boxNum++
    }

    // latestBox = res
}

var lastBox = []
function render() {
    /*
     var frameBuffObj = []
     var boxBuffObj = []
     */
    var frame = null
    // if (selBoxIndex > boxBuffObj.length) {
    //     return
    // }

    frame = boxBuffObj[selBoxIndex + '']
    frame = frameBuffObj[selBoxIndex + '']
    // console.log(selBoxIndex)
    if (!frame) {
        selBoxIndex++
        selBoxIndex = selBoxIndex % buffSize

        return
    }
    var t = frame.time
    var box = boxBuffObj.find((v, index) => {
        var dt = Math.abs(t - v.time) / 1000
        //console.log(new Date(t).toLocaleString(), new Date( v.time).toLocaleString())
        return dt >= 0 && dt < 2 //&& index > lastBoxIndex
    })

    if (box) {
        lastBox = box.data
    } else {
        box = lastBox
    }
    //console.log(box.length>0?true:false, Object.keys(boxBuffObj).length)
    // console.log(frame.time)
    draw_image_and_boxes(frame.data, [])//boxes)//box?box:[])
    // var index1 = frameBuffObj.find(v=>{
    //     return Math.abs(v.time - frame.time) < 10
    // })


    // if (index1) {
    //     draw_image_and_boxes(index1.data, frame.data)
    // }

    selBoxIndex++
    selBoxIndex = selBoxIndex % buffSize
}

function draw_image_and_boxes(src, boxes) {
    let img = new Image()
    img.src = src
    img.onload = () => {
        if (!canvas.width) {
            canvas.width = img.width;
            canvas.height = img.height;
        }

        ctx.drawImage(img, 0, 0);
        draw_boxes(boxesNew)
        // if (boxes && boxes.length > 0) {
        //     ctx.strokeStyle = "#00FF00";
        //     ctx.lineWidth = 3;
        //     ctx.font = "18px serif";
        //     boxes.forEach(([x1, y1, x2, y2, label]) => {
        //         ctx.strokeRect(x1, y1, x2 - x1, y2 - y1);
        //         ctx.fillStyle = "#00ff00";
        //         const width = ctx.measureText(label).width;
        //         ctx.fillRect(x1, y1, width + 10, 25);
        //         ctx.fillStyle = "#000000";
        //         ctx.fillText(label, x1, y1 + 18);
        //     });
        // }
    }
}

function draw_boxes(p1) {
    ctx.strokeStyle = "#00FF00";
    ctx.lineWidth = 3;
    ctx.font = "18px serif";

    let n = new Date().getTime()

    let boxes1 = []
    p1.map(v => {
        boxes1 = boxes1.concat(v.data)
    })

    //    console.log(boxes1.length)
    // console.log(boxes1.length)
    boxes1.forEach(([x1, y1, x2, y2, label, prob]) => {
        //console.log(x1, y1, x2, y2, label)
        prob = prob.toFixed(2)
        if (prob < 0.1) return
        ctx.strokeStyle = stringToColor(label)
        const width = ctx.measureText(label + ' ' + prob).width;
        let h = y2 - y1
        if (h < 18) h = 20
        let w = x2 - x1
        if (w < width) w = width + 2
        // 75 -> 0
        ctx.strokeRect(x1, y1 - 0, w, h);
        ctx.fillStyle = ctx.strokeStyle;

        ctx.fillRect(x1 + 2, y1 - 0, width + 10, 25);
        ctx.fillStyle = "#000000";
        ctx.fillText(label + ' ' + prob, x1, y1 + 18 - 0);
    });
}

function log(message) {
    domMessage.innerHTML += "[" + new Date().toLocaleTimeString() + "] " + message + "<br>"
}

function logClean() {
    domMessage.innerHTML = ''
}

let colorMap = {}
function stringToColor(str) {

    let hash = 0;
    // 生成字符串哈希值
    for (let i = 0; i < str.length; i++) {
        hash = str.charCodeAt(i) + ((hash << 5) - hash);
    }

    if (colorMap[hash]) {
        return colorMap[hash]
    }

    // 将哈希值转换为RGB分量
    const r = (hash & 0xFF0000) >> 16;
    const g = (hash & 0x00FF00) >> 8;
    const b = hash & 0x0000FF;

    // 调整颜色亮度(避免太暗)
    colorMap[hash] = `rgb(${r | 0x80}, ${g | 0x80}, ${b | 0x80})`

    return colorMap[hash];
}
<h1>
	StreamManager
	<div style="display: inline-block" class="btns">
		<button onclick="openConnect()" id="domOpenConnect">连接</button>
    <button onclick="closeConnect()" id="domCloseConnect">断开</button>
    <select id="cameraId" style="padding: .18rem 1rem;">
        <option value="1">1</option>
        <option value="2">2</option>
        <option value="3">3</option>
        <option value="4">4</option>
    </select>
    <button onclick="openStream()" id="domOpenStream">打开</button>
    <button onclick="closeStream()" id="domCloseStream">关闭</button>
     <button onclick="sendDebug()" id="domSendDebug">调试</button>
     <button onclick="sendFunc()" id="domSendFunc">Func</button>
     <button onclick="sendFuncStop()" id="domSendFuncStop">FuncStop</button>
     <button onclick="doRender()" id="domRenderFunc">Render</button>
     <button onclick="doTest()" id="domTest">开启</button>
    </div>

    <span id="domTime"></span>
    <div id="nowTime"></div>
</h1>

<img id="stream" width="640" height="360" style="opacity: 0.6; height: 0;"/>

<div style="display: flex; max-height: 80vh; overflow-y: auto;">
    <div style="flex: 10; width1: 50%;">
        <canvas style="width: 100%" width="0" height="0"></canvas>
    </div>
    <div  style="flex: 1; ">
        <div id="process1" style="height: 1rem;background: red;"></div>
        <div id="message">
       
        </div>
    </div>
    <div  style="flex: 1; ">
         <div id="process2" style="height: 1rem;background: blue;"></div>
        <div id="message2">
        
        </div>
     </div>
</div>
.btns button {
    
}
canvas {
    opacity: 1.2;
}

#message img {
    display: block;
    width: 100%;
}