js实现iput自动补齐并选中

本文介绍了一个自定义的jQuery函数,用于设置输入框中文本的选择范围。该方法兼容多种浏览器,并提供了详细的实现步骤。

jQuery.fn.setSelection = function(selectionStart, selectionEnd) {

    if(this.lengh == 0) return this;

    input = this[0];

 

    if (input.createTextRange) {

        var range = input.createTextRange();

        range.collapse(true);

        range.moveEnd('character', selectionEnd);

        range.moveStart('character', selectionStart);

        range.select();

    } else if (input.setSelectionRange) {

        input.focus();

        input.setSelectionRange(selectionStart, selectionEnd);

    }

 

    return this;

}

 

//var start = $("#god-input").val().length;

 

//$("#god-input").val($('#god_element_'+god_index).attr('dataurl') ); //

//$("#god-input").setSelection(start,$("#god-input").val().length);

 

package com.taixin.dvbc2; import com.google.gson.Gson; import javax.servlet.*; import javax.servlet.http.*; import java.io.*; import java.net.*; import java.sql.*; import java.util.*; import java.util.concurrent.*; /** * SSE + Servlet 3.0 异步版本(含 PMT/ES 细节、入库去重) * - 每个 (ip,port) 唯一一个 ChannelWorker * - 多个订阅者共享该 worker 的统计输出 * - 输出字段:name/no/rate + pmt/vid/vtype/a1/a1t/a2/a2t * - 仅当节目结构或PID/类型变化时触发 SaveProgramData(签名去重) */ public class LoadProgramServlet extends HttpServlet { private static final Gson GSON = new Gson(); // 推送间隔(纳秒):8 秒 private static final long REPORT_INTERVAL_NS = TimeUnit.SECONDS.toNanos(8); // 心跳间隔:15 秒(SSE 注释行) private static final long HEARTBEAT_INTERVAL_NS = TimeUnit.SECONDS.toNanos(15); // 所有工作线程:Key -> ChannelWorker private static final Map<ChannelKey, ChannelWorker> WORKERS = new ConcurrentHashMap<>(); // ===== Servlet 入口 ===== @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException { final String ip = req.getParameter("ip"); final String portStr = req.getParameter("port"); final String idStr = req.getParameter("id"); final String inputChannelName = req.getParameter("name"); if (ip == null || portStr == null) { resp.setStatus(400); resp.setContentType("application/json;charset=UTF-8"); resp.getWriter().write("{\"error\":\"缺少参数 ip 或 port\"}"); return; } final int port = Integer.parseInt(portStr); final Integer inputChannelId = (idStr == null || idStr.trim().isEmpty()) ? null : Integer.valueOf(idStr); final ChannelKey key = new ChannelKey(ip, port); // 设置 SSE 头 resp.setStatus(200); resp.setContentType("text/event-stream;charset=UTF-8"); resp.setCharacterEncoding("UTF-8"); resp.setHeader("Cache-Control", "no-cache"); resp.setHeader("Connection", "keep-alive"); resp.setHeader("X-Accel-Buffering", "no"); // 开启异步 final AsyncContext ac = req.startAsync(req, resp); ac.setTimeout(0); // 永不超时 final PrintWriter writer = resp.getWriter(); final Subscriber sub = new Subscriber(writer, ac); // 获取/创建 worker,登记订阅者 ChannelWorker worker = WORKERS.compute(key, (k, w) -> { if (w == null || !w.isAliveWorker()) { ChannelWorker nw = new ChannelWorker( k, REPORT_INTERVAL_NS, HEARTBEAT_INTERVAL_NS, inputChannelId, inputChannelName ); try { nw.start(); } catch (Exception e) { safeSendSSE(sub, "{\"error\":\"通道启动失败: " + e.getMessage() + "\"}"); completeQuietly(ac); return null; } w = nw; } else { // 已有 worker,补齐一次元信息(为空时才设置,避免覆盖) w.setChannelMetaIfEmpty(inputChannelId, inputChannelName); } w.addSubscriber(sub); return w; }); if (worker == null) { return; // 上面已通知错误 } // 监听清理 ac.addListener(new AsyncListener() { @Override public void onComplete(AsyncEvent event) { cleanup(); } @Override public void onTimeout(AsyncEvent event) { cleanup(); } @Override public void onError(AsyncEvent event) { cleanup(); } @Override public void onStartAsync(AsyncEvent event) {} private void cleanup() { ChannelWorker w = WORKERS.get(key); if (w != null) { w.removeSubscriber(sub); if (w.subscriberCount() == 0) { w.stopWorker(); WORKERS.remove(key); } } } }); // 立即发一条“已连接” safeSendSSE(sub, "{\"status\":\"connected\"}"); } // ====== SSE 订阅者对象 ====== static final class Subscriber { final PrintWriter writer; final AsyncContext ac; volatile long lastHeartbeatNs = System.nanoTime(); Subscriber(PrintWriter writer, AsyncContext ac) { this.writer = writer; this.ac = ac; } synchronized void sendData(String json) { if (writer.checkError()) { complete(); return; } writer.write("data: " + json + "\n\n"); // SSE 以空行分隔 writer.flush(); if (writer.checkError()) { complete(); } } synchronized void heartbeatIfNeeded(long now, long heartbeatIntervalNs) { if (now - lastHeartbeatNs < heartbeatIntervalNs) return; if (writer.checkError()) { complete(); return; } writer.write(": keepalive\n\n"); writer.flush(); lastHeartbeatNs = now; if (writer.checkError()) { complete(); } } synchronized void complete() { completeQuietly(ac); } } // ====== 唯一标识每个组播通道 ====== static final class ChannelKey { final String ip; final int port; ChannelKey(String ip, int port) { this.ip = ip; this.port = port; } @Override public boolean equals(Object o) { if (!(o instanceof ChannelKey)) return false; ChannelKey that = (ChannelKey)o; return this.port == that.port && this.ip.equals(that.ip); } @Override public int hashCode() { return ip.hashCode() * 31 + port; } @Override public String toString() { return ip + ":" + port; } } // ====== 每个通道一个工作线程:接收 TS、解析 PSI、聚合速率、广播 ====== static final class ChannelWorker extends Thread { private final ChannelKey key; private final long reportIntervalNs; private final long heartbeatIntervalNs; private final Set<Subscriber> subscribers = ConcurrentHashMap.newKeySet(); // —— 请求元数据(来自 doGet)—— private volatile Integer inputChannelId; // InputChannelId private volatile String inputChannelName; // InputChannelName // —— 每个通道独有的解析状态(不要 static)—— private final Map<Integer, Integer> progToPmtPid = new HashMap<>(); // program -> PMT PID private final Map<Integer, String> programNames = new HashMap<>(); // program -> name(优先 SDT) private final Map<Integer, Integer> pidToProgram = new HashMap<>(); // pid -> program(用于速率归集) private final Map<Integer, Long> pidBytesCounter = new HashMap<>(); // pid -> bytes since last report private final Map<Integer, SectionAssembler> assemblers = new HashMap<>(); // 新增:每个节目下的 ES 列表(带类型/编码) private final Map<Integer, List<EsInfo>> programEsInfo = new HashMap<>(); // 去重签名:记录“最近一次已保存”的结构快照(字符串签名) private volatile String lastSavedSignature = null; // —— ES 描述结构 —— static final class EsInfo { final int pid; final int streamType; final String kind; // video / audio / other final String codec; // H.264/HEVC/AAC/AC-3 等 EsInfo(int pid, int st, String kind, String codec) { this.pid = pid; this.streamType = st; this.kind = kind; this.codec = codec; } } private volatile boolean running = true; private MulticastSocket sock; private InetAddress group; ChannelWorker(ChannelKey key, long reportIntervalNs, long heartbeatIntervalNs, Integer inputChannelId, String inputChannelName) { super("ChannelWorker-" + key); this.key = key; this.reportIntervalNs = reportIntervalNs; this.heartbeatIntervalNs = heartbeatIntervalNs; this.inputChannelId = inputChannelId; this.inputChannelName = inputChannelName; setDaemon(true); } // 复用已存在的 worker 时补齐元信息 void setChannelMetaIfEmpty(Integer id, String name) { if (this.inputChannelId == null && id != null) this.inputChannelId = id; if ((this.inputChannelName == null || this.inputChannelName.isEmpty()) && name != null) { this.inputChannelName = name; } } boolean isAliveWorker() { return running && isAlive(); } void addSubscriber(Subscriber s) { subscribers.add(s); } void removeSubscriber(Subscriber s) { subscribers.remove(s); } int subscriberCount() { return subscribers.size(); } void stopWorker() { running = false; try { if (sock != null) sock.close(); } catch (Throwable ignore) {} } @Override public void run() { try { sock = new MulticastSocket(key.port); group = InetAddress.getByName(key.ip); sock.joinGroup(group); final int MTU = 7 * 188; byte[] buf = new byte[MTU]; DatagramPacket pkt = new DatagramPacket(buf, buf.length); long lastReport = System.nanoTime(); while (running) { if (subscribers.isEmpty()) { Thread.sleep(80); continue; } sock.receive(pkt); int len = pkt.getLength(); int off = 0; while (off + 188 <= len) { parseTsPacket(buf, off); off += 188; } long now = System.nanoTime(); // 心跳 for (Subscriber s : subscribers) s.heartbeatIfNeeded(now, heartbeatIntervalNs); // 定时汇报 if (now - lastReport >= reportIntervalNs) { String json = buildProgramReportJson(reportIntervalNs); if (json != null) broadcast(json); lastReport = now; } } } catch (SocketException ignore) { // close() 时会来这 } catch (Throwable t) { broadcast("{\"error\":\"worker-crashed: " + safeMsg(t) + "\"}"); } finally { try { if (group != null && sock != null) sock.leaveGroup(group); } catch (Throwable ignore) {} try { if (sock != null) sock.close(); } catch (Throwable ignore) {} for (Subscriber s : subscribers) completeQuietly(s.ac); subscribers.clear(); } } private void broadcast(String json) { for (Subscriber s : subscribers) s.sendData(json); } // === TS/PSI 解析 === private void parseTsPacket(byte[] buf, int off) { if ((buf[off] & 0xFF) != 0x47) return; // sync int tei = (buf[off + 1] & 0x80) >>> 7; int pusi = (buf[off + 1] & 0x40) >>> 6; int pid = ((buf[off + 1] & 0x1F) << 8) | (buf[off + 2] & 0xFF); int afc = (buf[off + 3] & 0x30) >>> 4; if (tei == 1) return; if (pid != 0x1FFF) { Long old = pidBytesCounter.get(pid); pidBytesCounter.put(pid, (old == null ? 0L : old) + 188L); } int i = off + 4; if (afc == 2) return; // 只有 AF,无 payload if (afc == 3) { // 跳过 AF int afl = buf[i] & 0xFF; i += 1 + afl; } if (i >= off + 188) return; // 只重组 PAT/PMT/SDT if (pid == 0x0000 || pid == 0x0011 || containsValue(progToPmtPid, pid)) { SectionAssembler sa = assemblers.get(pid); if (sa == null) { sa = new SectionAssembler(); assemblers.put(pid, sa); } sa.push(buf, i, off + 188 - i, pusi == 1, (buf[off + 3] & 0x0F)); while (sa.hasSection()) { byte[] sec = sa.pollSection(); parseSection(pid, sec); } } } private boolean containsValue(Map<Integer, Integer> map, int val) { for (Integer v : map.values()) if (v != null && v == val) return true; return false; } private void parseSection(int pid, byte[] sec) { if (sec.length < 3) return; int tableId = sec[0] & 0xFF; int sectionLength = ((sec[1] & 0x0F) << 8) | (sec[2] & 0xFF); if (sectionLength + 3 != sec.length) return; if (pid == 0x0000 && tableId == 0x00) { parsePAT(sec); } else if (pid == 0x0011 && (tableId == 0x42 || tableId == 0x46)) { parseSDT(sec); } else if (tableId == 0x02) { parsePMT(sec); } } private void parsePAT(byte[] sec) { int pos = 8, end = sec.length - 4; while (pos + 4 <= end) { int programNumber = ((sec[pos] & 0xFF) << 8) | (sec[pos + 1] & 0xFF); int pmtPid = ((sec[pos + 2] & 0x1F) << 8) | (sec[pos + 3] & 0xFF); pos += 4; if (programNumber == 0) continue; progToPmtPid.put(programNumber, pmtPid); pidToProgram.put(pmtPid, programNumber); } } private void parsePMT(byte[] sec) { int programNumber = ((sec[3] & 0xFF) << 8) | (sec[4] & 0xFF); // 占位名(后续由 SDT 覆盖) if (!programNames.containsKey(programNumber)) { programNames.put(programNumber, "Program " + programNumber); } int progInfoLen = ((sec[10] & 0x0F) << 8) | (sec[11] & 0xFF); int pos = 12 + progInfoLen; int end = sec.length - 4; List<EsInfo> list = new ArrayList<>(); while (pos + 5 <= end) { int streamType = sec[pos] & 0xFF; int esPid = ((sec[pos + 1] & 0x1F) << 8) | (sec[pos + 2] & 0xFF); int esInfoLen = ((sec[pos + 3] & 0x0F) << 8) | (sec[pos + 4] & 0xFF); pos += 5; // ES 描述符区域 byte[] esDesc = null; if (esInfoLen > 0 && pos + esInfoLen <= end) { esDesc = Arrays.copyOfRange(sec, pos, pos + esInfoLen); } pos += esInfoLen; // 归属 program,便于速率归集 pidToProgram.put(esPid, programNumber); // 识别类型/编码 String[] cls = classifyStream(streamType, esDesc); list.add(new EsInfo(esPid, streamType, cls[0], cls[1])); } programEsInfo.put(programNumber, list); } private void parseSDT(byte[] sec) { int pos = 11, end = sec.length - 4; while (pos + 5 <= end) { int serviceId = ((sec[pos] & 0xFF) << 8) | (sec[pos + 1] & 0xFF); int descriptorsLoopLen = ((sec[pos + 3] & 0x0F) << 8) | (sec[pos + 4] & 0xFF); int descPos = pos + 5; int descEnd = descPos + descriptorsLoopLen; String name = null; while (descPos + 2 <= descEnd && descEnd <= end) { int tag = sec[descPos] & 0xFF; int len = sec[descPos + 1] & 0xFF; if (descPos + 2 + len > descEnd) break; if (tag == 0x48 && len >= 5) { // service_descriptor int base = descPos + 2; int provLen = sec[base + 1] & 0xFF; int nameLenPos = base + 2 + provLen; if (nameLenPos < descPos + 2 + len) { int nameLen = sec[nameLenPos] & 0xFF; int nameStart = nameLenPos + 1; int nameEnd = Math.min(nameStart + nameLen, descPos + 2 + len); byte[] nameBytes = Arrays.copyOfRange(sec, nameStart, nameEnd); name = decodeDvbString(nameBytes); } } descPos += 2 + len; } if (name != null && !name.isEmpty()) { programNames.put(serviceId, name); } pos = descEnd; } } // === 码率汇总 + JSON 输出(含“变更才保存”) === private String buildProgramReportJson(long intervalNs) { if (pidBytesCounter.isEmpty()) return null; Map<Integer, Long> programBytes = new HashMap<>(); for (Map.Entry<Integer, Long> e : pidBytesCounter.entrySet()) { int pid = e.getKey(); long bytes = e.getValue(); Integer prog = pidToProgram.get(pid); if (prog != null) { Long old = programBytes.get(prog); programBytes.put(prog, (old == null ? 0L : old) + bytes); } } pidBytesCounter.clear(); if (programBytes.isEmpty()) return null; List<Map<String, Object>> programs = new ArrayList<>(); for (Map.Entry<Integer, Long> e : programBytes.entrySet()) { int program = e.getKey(); long bytes = e.getValue(); double mbps = (bytes * 8.0) / (intervalNs / 1e9) / 1_000_000.0; String name = programNames.get(program); if (name == null) name = "(解析中…)"; Integer pmtPid = progToPmtPid.get(program); EsInfo v = null, a1 = null, a2 = null; List<EsInfo> list = programEsInfo.get(program); if (list != null) { for (EsInfo es : list) { if ("video".equals(es.kind)) { if (v == null) v = es; } else if ("audio".equals(es.kind)) { if (a1 == null) a1 = es; else if (a2 == null) a2 = es; } } } Map<String, Object> one = new LinkedHashMap<>(); one.put("name", name); one.put("no", program); one.put("rate", mbps); one.put("pmt", pmtPid == null ? null : pmtPid); one.put("vid", v == null ? null : v.pid); one.put("vtype", v == null ? null : v.codec); one.put("a1", a1 == null ? null : a1.pid); one.put("a1t", a1 == null ? null : a1.codec); one.put("a2", a2 == null ? null : a2.pid); one.put("a2t", a2 == null ? null : a2.codec); programs.add(one); // ==== 入库去重:仅当签名变化时才保存 ==== // 只保存“首个节目”(你的需求:一个输入通道只有一个节目) final String signature = makeSignature( name, program, pmtPid, (v == null ? null : v.pid), (v == null ? null : v.codec), (a1 == null ? null : a1.pid), (a1 == null ? null : a1.codec), (a2 == null ? null : a2.pid), (a2 == null ? null : a2.codec) ); // System.out.println(signature); // 比较保存 if (lastSavedSignature == null || !signature.equals(lastSavedSignature)) { lastSavedSignature = signature; // 先更新,避免发重复 try { SaveProgramData( this.inputChannelId, this.inputChannelName, this.key.ip, this.key.port, name, program, pmtPid, (v == null ? null : v.pid), (v == null ? null : v.codec), (a1 == null ? null : a1.pid),(a1 == null ? null : a1.codec), (a2 == null ? null : a2.pid),(a2 == null ? null : a2.codec) ); } catch (Exception ex) { // 保存失败不影响 SSE System.err.println("[SaveProgramData][ERROR] " + ex.getMessage()); } } // 一个输入通道只有一个节目——只处理第一个 break; } return GSON.toJson(programs); } // 生成签名(只关注结构/类型/PID变化,速率不参与) private static String makeSignature(String name, Integer progNo, Integer pmt, Integer vid, String vtype, Integer a1, String a1t, Integer a2, String a2t) { return String.valueOf( Objects.hash( safe(name), progNo, pmt, vid, safe(vtype), a1, safe(a1t), a2, safe(a2t) ) ); } private static String safe(String s) { return (s == null ? "" : s); } // === stream_type + 描述符 → 类型/编码 === private static String[] classifyStream(int streamType, byte[] esDesc) { switch (streamType & 0xFF) { case 0x01: return new String[]{"video", "MPEG-1 Video"}; case 0x02: return new String[]{"video", "MPEG-2 Video"}; case 0x10: return new String[]{"video", "MPEG-4 Visual"}; case 0x1B: return new String[]{"video", "H.264/AVC"}; case 0x24: return new String[]{"video", "HEVC/H.265"}; case 0x03: return new String[]{"audio", "MPEG-1 Layer II"}; case 0x04: return new String[]{"audio", "MPEG-2 Audio"}; case 0x0F: return new String[]{"audio", "AAC"}; case 0x11: return new String[]{"audio", "LATM/LOAS AAC"}; case 0x06: if (hasDescriptor(esDesc, (byte)0x6A)) return new String[]{"audio", "AC-3"}; if (hasDescriptor(esDesc, (byte)0x7A)) return new String[]{"audio", "E-AC-3"}; if (hasDescriptor(esDesc, (byte)0x7B)) return new String[]{"audio", "DTS"}; return new String[]{"other", "Private data"}; default: return new String[]{"other", String.format("stream_type 0x%02X", streamType & 0xFF)}; } } private static boolean hasDescriptor(byte[] esDesc, byte tag) { if (esDesc == null) return false; int i = 0, n = esDesc.length; while (i + 2 <= n) { int t = esDesc[i] & 0xFF; int l = esDesc[i+1] & 0xFF; if (i + 2 + l > n) break; if ((byte)t == tag) return true; i += 2 + l; } return false; } // === DVB 字符串解码 === private static String decodeDvbString(byte[] bs) { if (bs == null || bs.length == 0) return ""; int offset = 0; String charset = "GB18030"; int first = bs[0] & 0xFF; if (first == 0x10 && bs.length >= 3) { charset = "UTF-16BE"; offset = 1; } else if (first == 0x15 || first == 0x14) { charset = "GB2312"; offset = 1; } else if (first == 0x1F) { charset = "UTF-8"; offset = 1; } else if (first < 0x20) { offset = 1; } try { return new String(bs, offset, bs.length - offset, charset).trim(); } catch (Exception e) { try { return new String(bs, offset, bs.length - offset, "GB2312").trim(); } catch (Exception ignore) {} try { return new String(bs, offset, bs.length - offset, "UTF-8").trim(); } catch (Exception ignore) {} return ""; } } // === 安全的 section 重组器 === static final class SectionAssembler { private final ByteArrayOutputStream cur = new ByteArrayOutputStream(); private Integer expectedLen = null; private final Queue<byte[]> ready = new ArrayDeque<>(); void push(byte[] src, int off, int len, boolean payloadStart, int continuityCounter) { final int end = off + len; int i = off; if (payloadStart) { if (i >= end) return; int pointer = src[i] & 0xFF; i += 1; if (i + pointer > end) return; i += pointer; startNew(); } while (i < end) { int remaining = end - i; if (expectedLen == null) { if (remaining < 3) { write(src, i, remaining); return; } int sl = ((src[i+1] & 0x0F) << 8) | (src[i+2] & 0xFF); expectedLen = sl + 3; // total including header } int need = expectedLen - cur.size(); if (need <= 0) { startNew(); continue; } int copy = Math.min(need, remaining); write(src, i, copy); i += copy; if (cur.size() == expectedLen) { ready.add(cur.toByteArray()); startNew(); } } } private void write(byte[] src, int off, int len) { if (len > 0) cur.write(src, off, len); } boolean hasSection() { return !ready.isEmpty(); } byte[] pollSection() { return ready.poll(); } private void startNew() { cur.reset(); expectedLen = null; } } } // ====== 工具方法 ====== private static void safeSendSSE(Subscriber s, String json) { try { s.sendData(json); } catch (Throwable ignore) {} } private static void completeQuietly(AsyncContext ac) { try { ac.complete(); } catch (Throwable ignore) {} } private static String safeMsg(Throwable t) { String m = t.getMessage(); return (m == null ? t.getClass().getSimpleName() : m).replace("\"","'"); } // ====== 实际入库(你可以在此调用存储过程) ====== // 这里默认只打印,避免直接写库;你可以改为JDBC调用: // CallableStatement c = conn.prepareCall("{call dbo.sp_UpsertProgram_ByChannel(?,?,?,?,?,?,?,?,?,?,?,?,?,?)}"); // ... set 参数 ... // c.execute(); static void SaveProgramData(Integer inputChannelId, String inputChannelName, String ip, int port, String name, Integer progNo, Integer pmtPid, Integer videoPid, String videoType, Integer audio1Pid, String audio1Type, Integer audio2Pid, String audio2Type) throws Exception { Properties props = new Properties(); try { InputStream inputStream = LoadProgramServlet.class.getClassLoader().getResourceAsStream("jdbc.properties"); if (inputStream != null) { props.load(inputStream); String DB_URL = props.getProperty("jdbc.url"); String DB_USER = props.getProperty("jdbc.username"); String DB_PASSWORD = props.getProperty("jdbc.password"); // 注册数据库驱动 Class.forName(props.getProperty("jdbc.driverClassName")); Connection c = null; CallableStatement cs = null; try { c = DriverManager.getConnection(DB_URL, DB_USER, DB_PASSWORD); String sql = "{call sp_SaveProgramData(?, ?, ?, ?, ?,?,?, ?, ?, ?, ?,?,?)}"; // 调用存储过程 cs = c.prepareCall(sql); // 设置存储过程参数 cs.setString(1, inputChannelName); cs.setInt(2,port); cs.setString(3, ip); cs.setString(4, name); // 当 out_mod 为 256 时 cs.setInt(5, progNo); // 当 out_mod 为 256 时 cs.setInt(6, pmtPid); // 当 out_mod 为 256 时 cs.setInt(7, videoPid); // 当 out_mod 为 256 时 cs.setString(8, videoType); // 当 out_mod 为 256 时 cs.setInt(9, audio1Pid == null ? 0 : audio1Pid); cs.setString(10, audio1Type == null ? "-" : audio1Type); cs.setInt(11, audio2Pid == null ? 0 : audio2Pid); cs.setString(12, audio2Type == null ? "-" : audio2Type); cs.registerOutParameter(13, Types.INTEGER); // @ReturnValue cs.execute(); // 获取输出参数 int returnValue = cs.getInt(13); // 读取 @ReturnValue if (returnValue == 1) { // 插入成功 // System.out.println("保存"+name+"成功"); } else { } } catch (SQLException e) { } finally { // 资源释放 try { if (cs != null) cs.close(); } catch (Exception ignore) { } try { if (c != null) c.close(); } catch (Exception ignore) { } } } else { } } catch (Exception e) { throw new ServletException("Failed to load database properties", e); } } } function getRateMbps(p) { if (typeof p?.rate === 'number' && isFinite(p.rate)) return p.rate; if (typeof p?.rate === 'string') { const n = parseFloat(p.rate); return isFinite(n) ? n : 0; } return 0; } function formatRate(mbps) { if (!isFinite(mbps) || mbps <= 0) return '0'; if (mbps >= 1) return (mbps).toFixed(2) + ' M'; if (mbps >= 0.001) return (mbps * 1000).toFixed(2) + ' K'; return Math.round(mbps * 1e6) + ' bps'; } // 给每个 group 挂一个 es 实例 & 正在解析标记 function startParseForGroup(group) { // 已经自动启动过且连接还活着,直接返回,避免重复创建 if (group._autostarted && group.es && group.es.readyState === 1) { return; } group._autostarted = true; // 已有连接先关(防止旧连接残留) if (group.es) { try { group.es.close(); } catch (_) {} group.es = null; } group.parsing = true; // ⚠️ 确认你的后端路径是否真叫 loadPramgermServlet,别拼错了 const url = `loadPramgermServlet?ip=${encodeURIComponent(group.ip)}&port=${encodeURIComponent(group.port)}&id=${encodeURIComponent(group.id)}&name=${encodeURIComponent(group.name)}`; const es = new EventSource(url); group.es = es; es.onmessage = (event) => { try { const data = JSON.parse(event.data); if (!Array.isArray(data)) return; group.programs = data.map(p => ({ name: p.name, no: p.no, rate: Number(p.rate) || 0, pmt: p.pmt ?? '-', vid: p.vid ?? '-', vtype: p.vtype ?? '-', a1: p.a1 ?? '-', a1t: p.a1t ?? '-', a2: p.a2 ?? '-', a2t: p.a2t ?? '-' })); group.parsing = false; renderTree(); } catch (e) { console.error('解析 SSE 数据失败:', e, event.data); } }; es.onerror = (err) => { console.error('SSE 连接出错', err); group.parsing = false; try { es.close(); } catch (_) {} group.es = null; // 失败后允许再次自动启动 group._autostarted = false; }; } // (可选)为了节省资源,开始解析前关闭其它通道的 SSE function stopOtherParsers(exceptId) { groups.forEach(g => { if (String(g.id) !== String(exceptId) && g.es) { try { g.es.close(); } catch (_) {} g.es = null; g.parsing = false; } }); } // ✅ 按钮事件:只启动选中通道的解析,且只更新该通道 document.getElementById('btnParse').addEventListener('click', () => { const selectedGroup = groups.find(g => g.selected); if (!selectedGroup) { alert('请先选择一个输入通道'); return; } // // 可选:关掉其它通道的 SSE,避免多路同时开 // stopOtherParsers(selectedGroup.id); // 启动这个通道的解析 startParseForGroup(selectedGroup); }); 如果我有好几百个通道 一个通道一个节目100多个通道 我需要每个都展示速率 怎么才能坐到 而且不崩
09-06
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值