java.nio.channels.spi.SelectorProvider

public abstract class
SelectorProvider
extends Object
java.lang.Object
? java.nio.channels.spi.SelectorProvider


Class Overview

SelectorProvider is an abstract base class that declares methods for providing instances of DatagramChannel, Pipe, Selector , ServerSocketChannel, and SocketChannel. All the methods of this class are thread-safe.
该类是一个抽象基类,为DatagramChannel, Pipe, Selector , ServerSocketChannel, and SocketChannel提供SelectorProvider实例。该类的所有方法均是线程安全的。

A provider instance can be retrieved through a system property or the configuration file in a jar file; if no provider is available that way then the system default provider is returned.
Provider实例可以通过系统属性或者jar文件中的一个配置文件;如果这两种方式下没有provider可用,那么该类返回一个系统默认的provider。

Summary

Protected Constructors
SelectorProvider()
Constructs a new SelectorProvider.
构造一个新的SelectorProvider.

Public Methods
Channel inheritedChannel()
Returns the channel inherited from the instance that created this virtual machine.
返回一个继承自创建该虚拟机的实例的channel。

abstract DatagramChannel openDatagramChannel()
Creates a new open DatagramChannel.
创建一个新的DatagramChannel。

abstract Pipe openPipe()
Creates a new Pipe.
创建一个新的Pipe.

abstract AbstractSelector openSelector()
Creates a new selector.
创建一个新的selector.

abstract ServerSocketChannel openServerSocketChannel()
Creates a new open ServerSocketChannel.
创建一个新的打开的ServerSocketChannel。

abstract SocketChannel openSocketChannel()
Create a new open SocketChannel.
创建一个新的打开的SocketChannel.

synchronized static SelectorProvider provider()
Gets a provider instance by executing the following steps when called for the first time:
if the system property "java.nio.channels.spi.SelectorProvider" is set, the value of this property is the class name of the provider returned;
if there is a provider-configuration file named "java.nio.channels.spi.SelectorProvider" in META-INF/services of a jar file valid in the system class loader, the first class name is the provider's class name;
otherwise, a system default provider will be returned.
第一次调用该方法时执行以下操作,返回一个SelectorProvider:
如果系统属性"java.nio.channels.spi.SelectorProvider"设置了,那么该属性的值就是返回的provider的类名。
如果在META-INF/services中的一个jar文件中存在一个"java.nio.channels.spi.SelectorProvider"provider配置文件在系统的类装载器中是有效的,那么配置文件中第一个类名就是provider的类名。
否则,返回一个系统默认的provider。

[Expand]
Inherited Methods
From class java.lang.Object
Protected Constructors

protected SelectorProvider ()
Since: API Level 1
Constructs a new SelectorProvider.
Throws
SecurityException if there is a security manager installed that does not permit the runtime permission labeled "selectorProvider".
安全管理员不允许"selectorProvider"运行权限。

Public Methods

public Channel inheritedChannel ()
Since: API Level 1
Returns the channel inherited from the instance that created this virtual machine.
Returns
the channel.
Throws
IOException if an I/O error occurs.
SecurityException if there is a security manager installed that does not permit the runtime permission labeled "selectorProvider".
public abstract DatagramChannel openDatagramChannel ()

Since: API Level 1
Creates a new open DatagramChannel.
Returns
the new channel.
Throws
IOException if an I/O error occurs.

public abstract Pipe openPipe ()
Since: API Level 1
Creates a new Pipe.
Returns
the new pipe.
Throws
IOException if an I/O error occurs.

public abstract AbstractSelector openSelector ()
Since: API Level 1
Creates a new selector.
Returns
the new selector.
Throws
IOException if an I/O error occurs.

public abstract ServerSocketChannel openServerSocketChannel ()
Since: API Level 1
Creates a new open ServerSocketChannel.
Returns
the new channel.
Throws
IOException if an I/O error occurs.

public abstract SocketChannel openSocketChannel ()
Since: API Level 1
Create a new open SocketChannel.
Returns
the new channel.
Throws
IOException if an I/O error occurs.

public static synchronized SelectorProvider provider ()
Since: API Level 1
Gets a provider instance by executing the following steps when called for the first time:
if the system property "java.nio.channels.spi.SelectorProvider" is set, the value of this property is the class name of the provider returned;
if there is a provider-configuration file named "java.nio.channels.spi.SelectorProvider" in META-INF/services of a jar file valid in the system class loader,
the first class name is the provider's class name;
otherwise, a system default provider will be returned.
Returns
the provider.
/* ================== 单 WebSocket 聚合所有通道 ================== */ let ws = null; let wsConnected = false; // 构造要解析的全部通道清单 function buildAllChannelsPayload() { const list = groups .filter(g => g && g.ip && g.port) .map(g => ({ id: g.id ?? '', name: g.name ?? '', ip: String(g.ip), port: String(g.port) })); return { action: "start", channels: list }; } // 根据 key 更新对应 group 的节目与速率,并刷新 UI function applyWsUpdate(msg) { // msg 形如:{ key:"ip:port", programs:[ {name,no,rate,pmt,vid,vtype,a1,a1t,a2,a2t}, ... ] } const key = String(msg.key || ''); const [ip, port] = key.split(':'); if (!ip || !port) return; const g = groups.find(x => String(x.ip) === ip && String(x.port) === port); if (!g) return; const progs = Array.isArray(msg.programs) ? msg.programs : []; g.programs = progs.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 ?? '-' })); // 直接重渲染 summary 的速率与节目表 renderTree(); updateStatus && updateStatus('解析节目成功', 'ok'); } function openWsAndStartAll() { const url = (location.protocol === 'https:' ? 'wss://' : 'ws://') + location.host + '/ws/parse-all'; // 如果已连,直接发送一次 start(支持重复覆盖订阅) if (ws && wsConnected) { try { ws.send(JSON.stringify(buildAllChannelsPayload())); } catch(e){ console.error(e); } return; } try { ws?.close(); } catch(_) {} ws = new WebSocket(url); wsConnected = false; ws.onopen = () => { wsConnected = true; ws.send(JSON.stringify(buildAllChannelsPayload())); }; ws.onmessage = (ev) => { const msg = JSON.parse(ev.data); // 后端返回错误格式: {"ok":false,"error":"java.lang.IllegalArgumentException: Group not a multicast address"} if (msg && msg.ok === false && msg.error) { if (msg.error.includes("Group not a multicast address")) { alert("解析失败:地址不是组播地址,请检查输入的 IP 是否在 224.0.0.0 – 239.255.255.255 范围内。"); } else { alert("解析失败:" + msg.error); } return; } try { // const msg = JSON.parse(ev.data); if (Array.isArray(msg)) { msg.forEach(applyWsUpdate); // 批量 } else { applyWsUpdate(msg); // 单条 } } catch (e) { console.error('WS parse error', e, ev.data); } }; ws.onerror = (e) => { console.error('WS error', e); }; ws.onclose = () => { wsConnected = false; // 简单重连(可加指数回退) setTimeout(() => openWsAndStartAll(), 2000); }; } package com.taixin.dvbc2; import com.google.gson.*; import com.google.gson.reflect.TypeToken; import javax.servlet.*; import javax.servlet.http.*; import javax.websocket.*; import javax.websocket.server.ServerEndpoint; import java.io.*; import java.lang.reflect.Type; import java.net.*; import java.nio.ByteBuffer; import java.nio.channels.*; import java.nio.channels.spi.SelectorProvider; import java.sql.*; import java.util.*; import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicBoolean; import static java.net.StandardProtocolFamily.INET; import static java.net.StandardSocketOptions.SO_REUSEADDR; /** * 单机 Reactor 解析 + SSE(单路调试) + WebSocket 聚合(/ws/parse-all) * * 前端(你提供的)会连接 /ws/parse-all,并发送: * { "action":"start", "channels":[ {"id":"","name":"","ip":"239.1.1.1","port":"59000"}, ... ] } * * 后端会为这些 (ip,port) 启/复用解析器,并持续向该 session 推送: * { "key":"ip:port", "programs":[ {name,no,rate,pmt,vid,vtype,a1,a1t,a2,a2t}, ... ] } */ public class LoadProgramServlet extends HttpServlet { // ===== 配置 ===== static final Gson GSON = new Gson(); static final long REPORT_INTERVAL_NS = TimeUnit.SECONDS.toNanos(8); static final long HEARTBEAT_INTERVAL_NS = TimeUnit.SECONDS.toNanos(15); static final int BUF_SIZE = 2048; static final int TS_SIZE = 188; // 共享 Reactor(NIO 线程 + 定时器 + 通道状态) static final Reactor REACTOR = new Reactor(); // ========== (可选) 兼容原先的 SSE 单路调试入口 ========== @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException { final String ip = req.getParameter("ip"); final String pstr = req.getParameter("port"); final String idStr= req.getParameter("id"); final String name = req.getParameter("name"); if (ip == null || pstr == null) { resp.setStatus(400); resp.setContentType("application/json;charset=UTF-8"); resp.getWriter().write("{\"error\":\"缺少参数 ip 或 port\"}"); return; } final int port = Integer.parseInt(pstr); final Integer inputChannelId = (idStr == null || idStr.isEmpty()) ? null : Integer.valueOf(idStr); // SSE 响应 resp.setStatus(200); resp.setHeader("Cache-Control", "no-cache"); resp.setHeader("Connection", "keep-alive"); resp.setHeader("X-Accel-Buffering", "no"); resp.setContentType("text/event-stream;charset=UTF-8"); resp.setCharacterEncoding("UTF-8"); final AsyncContext ac = req.startAsync(); ac.setTimeout(0); final PrintWriter writer = resp.getWriter(); final SseSubscriber sub = new SseSubscriber(writer, ac); final ChannelKey key = new ChannelKey(ip, port); try { REACTOR.ensureChannel(key, inputChannelId, name); REACTOR.addSseSubscriber(key, sub); } catch (Exception e) { safeSendSSE(sub, "{\"error\":\"通道启动失败: " + safeMsg(e) + "\"}"); completeQuietly(ac); 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() { REACTOR.removeSseSubscriber(key, sub); } }); safeSendSSE(sub, "{\"status\":\"connected\"}"); } /* ============= SSE 辅助 ============= */ static final class SseSubscriber { final PrintWriter writer; final AsyncContext ac; volatile long lastHeartbeatNs = System.nanoTime(); SseSubscriber(PrintWriter w, AsyncContext ac) { this.writer = w; this.ac = ac; } synchronized void send(String json) { if (writer.checkError()) { complete(); return; } writer.write("data: " + json + "\n\n"); writer.flush(); if (writer.checkError()) { complete(); } } synchronized void heartbeatIfNeeded(long now) { if (now - lastHeartbeatNs < HEARTBEAT_INTERVAL_NS) 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 String toString(){ return ip + ":" + port; } @Override public boolean equals(Object o){ if (!(o instanceof ChannelKey)) return false; ChannelKey k=(ChannelKey)o; return port==k.port && ip.equals(k.ip); } @Override public int hashCode(){ return ip.hashCode()*31 + port; } } /* ============= Reactor:NIO 收包 + 定时聚合广播 ============= */ static final class Reactor { private final Selector selector; private final Thread ioThread; private final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(r -> { Thread t = new Thread(r, "ReportScheduler"); t.setDaemon(true); return t; }); // 每个通道的注册信息 private static final class Reg { final DatagramChannel ch; final ChannelState st; final Set<SseSubscriber> sseSubs = ConcurrentHashMap.newKeySet(); // 单路调试用 final AtomicBoolean alive = new AtomicBoolean(true); final NetworkInterface nif; Reg(DatagramChannel ch, ChannelState st, NetworkInterface nif){ this.ch = ch; this.st = st; this.nif = nif; } } // key -> 通道注册 private final Map<ChannelKey, Reg> regs = new ConcurrentHashMap<>(); // WebSocket 订阅关系:某 session 订阅了哪些 key;某 key 有哪些 session private final Map<Session, Set<ChannelKey>> wsSubsBySession = new ConcurrentHashMap<>(); private final Map<ChannelKey, Set<Session>> wsSessionsByKey = new ConcurrentHashMap<>(); Reactor() { try { this.selector = SelectorProvider.provider().openSelector(); } catch (IOException e) { throw new RuntimeException(e); } // I/O 线程:收 UDP -> 解析 TS this.ioThread = new Thread(this::ioLoop, "UdpReactor"); this.ioThread.setDaemon(true); this.ioThread.start(); // 定时器:聚合 -> 推送(SSE+WS) long periodMs = Math.max(1000, TimeUnit.NANOSECONDS.toMillis(REPORT_INTERVAL_NS/2)); scheduler.scheduleAtFixedRate(this::tick, periodMs, periodMs, TimeUnit.MILLISECONDS); } /* 公开:确保通道存在(若无则创建并 join 组播) */ void ensureChannel(ChannelKey key, Integer inputChannelId, String inputChannelName) { regs.computeIfAbsent(key, k -> { try { DatagramChannel ch = DatagramChannel.open(INET); ch.setOption(SO_REUSEADDR, true); ch.configureBlocking(false); ch.bind(new InetSocketAddress(k.port)); NetworkInterface nif = pickMulticastInterface(); InetAddress group = InetAddress.getByName(k.ip); ch.join(group, nif); ch.register(selector, SelectionKey.OP_READ, k); ChannelState st = new ChannelState(k); st.inputChannelId = inputChannelId; st.inputChannelName = inputChannelName; wsSessionsByKey.putIfAbsent(k, ConcurrentHashMap.newKeySet()); return new Reg(ch, st, nif); } catch (Exception e) { throw new RuntimeException(e); } }); // 补齐 meta Reg reg = regs.get(key); if (reg != null) { if (reg.st.inputChannelId == null && inputChannelId != null) reg.st.inputChannelId = inputChannelId; if ((reg.st.inputChannelName == null || reg.st.inputChannelName.isEmpty()) && inputChannelName != null) reg.st.inputChannelName = inputChannelName; } } /* SSE 订阅管理(可选) */ void addSseSubscriber(ChannelKey key, SseSubscriber sub) { regs.get(key).sseSubs.add(sub); } void removeSseSubscriber(ChannelKey key, SseSubscriber sub) { Reg reg = regs.get(key); if (reg!=null) reg.sseSubs.remove(sub); } /* WS 订阅管理 */ void wsSubscribe(Session s, Collection<ChannelKey> keys) { wsSubsBySession.computeIfAbsent(s, k -> ConcurrentHashMap.newKeySet()).clear(); for (ChannelKey key : keys) { ensureChannel(key, null, null); wsSubsBySession.get(s).add(key); wsSessionsByKey.computeIfAbsent(key, k -> ConcurrentHashMap.newKeySet()).add(s); } } void wsUnsubscribeAll(Session s) { Set<ChannelKey> ks = wsSubsBySession.remove(s); if (ks != null) { for (ChannelKey k : ks) { Set<Session> set = wsSessionsByKey.get(k); if (set != null) set.remove(s); } } } /* UDP -> TS 解析 */ private void ioLoop() { ByteBuffer buf = ByteBuffer.allocateDirect(BUF_SIZE); while (true) { try { selector.select(500); Set<SelectionKey> keys = selector.selectedKeys(); if (keys.isEmpty()) continue; Iterator<SelectionKey> it = keys.iterator(); while (it.hasNext()) { SelectionKey sk = it.next(); it.remove(); if (!sk.isValid() || !sk.isReadable()) continue; DatagramChannel ch = (DatagramChannel) sk.channel(); ChannelKey key = (ChannelKey) sk.attachment(); Reg reg = regs.get(key); if (reg == null || !reg.alive.get()) continue; buf.clear(); if (ch.receive(buf) == null) continue; int n = buf.position(); if (n <= 0) continue; buf.flip(); byte[] arr = new byte[n]; buf.get(arr); int off = 0; if (n >= 13 && arr[0] != 0x47 && arr[12] == 0x47) { off = 12; } int left = n - off; if (left > 0 && arr[off] != 0x47) { int sync = findTsSync(arr, n, off); if (sync >= 0) off = sync; else continue; left = n - off; } int aligned = left - (left % TS_SIZE); for (int i = 0; i + TS_SIZE <= aligned; i += TS_SIZE) { TsParsers.parseTsPacket(reg.st, arr, off + i); } } } catch (Throwable t) { t.printStackTrace(); } } } /* 定时聚合 + 广播(SSE 心跳 + WS 推送) */ private void tick() { long now = System.nanoTime(); regs.forEach((key, reg) -> { // SSE 心跳 for (SseSubscriber s : reg.sseSubs) s.heartbeatIfNeeded(now); // 到点聚合 if (reg.st.needReport(now, REPORT_INTERVAL_NS)) { // 生成该通道的节目数组 JSON(字符串) String progJson = reg.st.buildProgramArrayJson(REPORT_INTERVAL_NS); reg.st.afterReport(now); if (progJson != null) { // SSE:按你原来的格式(数组)发 for (SseSubscriber s : reg.sseSubs) s.send(progJson); // WS:包装成 {key, programs:[...]} 发给订阅了该 key 的所有 session Set<Session> sessions = wsSessionsByKey.get(key); if (sessions != null && !sessions.isEmpty()) { String wrapped = "{\"key\":\"" + key.toString() + "\",\"programs\":" + progJson + "}"; // 清理死 session List<Session> dead = new ArrayList<>(); for (Session ss : sessions) { if (ss == null || !ss.isOpen()) { dead.add(ss); continue; } try { ss.getAsyncRemote().sendText(wrapped); } catch (Throwable t) { dead.add(ss); } } sessions.removeAll(dead); } } } }); } // 选择一个可用的组播网卡 private NetworkInterface pickMulticastInterface() throws SocketException { Enumeration<NetworkInterface> ifs = NetworkInterface.getNetworkInterfaces(); while (ifs.hasMoreElements()) { NetworkInterface nif = ifs.nextElement(); if (!nif.isUp() || nif.isLoopback() || !nif.supportsMulticast()) continue; return nif; } return NetworkInterface.getByInetAddress(InetAddress.getLoopbackAddress()); } } /* ============= 通道解析状态与输出 ============= */ static final class ChannelState { final ChannelKey key; volatile Integer inputChannelId; volatile String inputChannelName; final Map<Integer, Integer> progToPmtPid = new HashMap<>(); final Map<Integer, String> programNames = new HashMap<>(); final Map<Integer, Integer> pidToProgram = new HashMap<>(); final Map<Integer, Long> pidBytes = new HashMap<>(); final Map<Integer, SectionAssembler> assemblers = new HashMap<>(); final Map<Integer, List<EsInfo>> programEsInfo = new HashMap<>(); volatile String lastSavedSignature = null; private long lastReportTs = System.nanoTime(); ChannelState(ChannelKey key){ this.key = key; } boolean needReport(long now, long intervalNs){ return now - lastReportTs >= intervalNs; } void afterReport(long now){ lastReportTs = now; } // 输出:返回“节目数组”的 JSON 字符串(例如:[ {...} ]),给 WS 再包一层 String buildProgramArrayJson(long intervalNs) { if (pidBytes.isEmpty()) return null; Map<Integer, Long> programBytes = new HashMap<>(); for (Map.Entry<Integer, Long> e : pidBytes.entrySet()) { int pid = e.getKey(); long bytes = e.getValue(); Integer prog = pidToProgram.get(pid); if (prog != null) programBytes.merge(prog, bytes, Long::sum); } pidBytes.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.getOrDefault(program, "(解析中…)"); 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); // —— 入库去重:仅在结构/类型/PID变化时 ——(只保存第一个节目) final String sig = 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) ); if (lastSavedSignature == null || !lastSavedSignature.equals(sig)) { lastSavedSignature = sig; try { // ✅ 兜底:名字没传时用 ip:port,避免 NULL 破坏存储过程 final String safeName = (inputChannelName == null || inputChannelName.isEmpty()) ? (key.ip + ":" + key.port) : inputChannelName; SaveProgramData( inputChannelId, safeName, // <== 使用 safeName key.ip, 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) { System.err.println("[SaveProgramData][ERROR] " + ex.getMessage()); } } break; // 一个输入通道只有一个节目 } return GSON.toJson(programs); } /* 解析结构 */ static final class EsInfo { final int pid, streamType; final String kind, codec; EsInfo(int pid, int st, String kind, String codec){ this.pid=pid; this.streamType=st; this.kind=kind; this.codec=codec; } } 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 cc) { 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; } 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 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); } } /* ============= TS 解析器(与你之前一致) ============= */ static final class TsParsers { static void parseTsPacket(ChannelState st, byte[] buf, int off) { if ((buf[off] & 0xFF) != 0x47) return; 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) st.pidBytes.merge(pid, 188L, Long::sum); int i = off + 4; if (afc == 2) return; if (afc == 3) { int afl = buf[i] & 0xFF; i += 1 + afl; } if (i >= off + 188) return; if (pid == 0x0000 || pid == 0x0011 || containsValue(st.progToPmtPid, pid)) { ChannelState.SectionAssembler sa = st.assemblers.get(pid); if (sa == null) { sa = new ChannelState.SectionAssembler(); st.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(st, pid, sec); } } } private static boolean containsValue(Map<Integer,Integer> map, int val){ for (Integer v : map.values()) if (v != null && v == val) return true; return false; } private static void parseSection(ChannelState st, 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(st, sec); else if (pid == 0x0011 && (tableId == 0x42 || tableId == 0x46)) parseSDT(st, sec); else if (tableId == 0x02) parsePMT(st, sec); } private static void parsePAT(ChannelState st, 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; st.progToPmtPid.put(programNumber, pmtPid); st.pidToProgram.put(pmtPid, programNumber); } } private static void parsePMT(ChannelState st, byte[] sec) { int programNumber = ((sec[3] & 0xFF) << 8) | (sec[4] & 0xFF); if (!st.programNames.containsKey(programNumber)) st.programNames.put(programNumber, "Program " + programNumber); int progInfoLen = ((sec[10] & 0x0F) << 8) | (sec[11] & 0xFF); int pos = 12 + progInfoLen; int end = sec.length - 4; List<ChannelState.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; byte[] esDesc = null; if (esInfoLen > 0 && pos + esInfoLen <= end) esDesc = Arrays.copyOfRange(sec, pos, pos + esInfoLen); pos += esInfoLen; st.pidToProgram.put(esPid, programNumber); String[] cls = classifyStream(streamType, esDesc); list.add(new ChannelState.EsInfo(esPid, streamType, cls[0], cls[1])); } st.programEsInfo.put(programNumber, list); } private static void parseSDT(ChannelState st, 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) { 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()) st.programNames.put(serviceId, name); pos = descEnd; } } 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; } 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 ""; } } } /* ============= WebSocket 端点:/ws/parse-all ============= */ @ServerEndpoint(value = "/ws/parse-all") public static class ParseAllEndpoint { // 复用 REACTOR private static final Type START_PAYLOAD_TYPE = new TypeToken<Map<String, Object>>(){}.getType(); @OnOpen public void onOpen(Session session) { // nothing } @OnMessage public void onMessage(String text, Session session) { try { Map<String, Object> root = GSON.fromJson(text, START_PAYLOAD_TYPE); String action = String.valueOf(root.getOrDefault("action", "start")); if (!"start".equalsIgnoreCase(action)) return; Object chs = root.get("channels"); if (!(chs instanceof List)) return; List<Map<String, Object>> channels = (List<Map<String, Object>>) chs; if (channels.isEmpty()) return; List<ChannelKey> keys = new ArrayList<>(); for (Map<String, Object> c : channels) { String ipStr = c.get("ip") == null ? null : String.valueOf(c.get("ip")).trim(); String portStr = c.get("port") == null ? null : String.valueOf(c.get("port")).trim(); if (ipStr == null || portStr == null) continue; Integer id = parseIntOrNull(c.get("id")); String name = c.get("name") == null ? null : String.valueOf(c.get("name")).trim(); try { int p = Integer.parseInt(portStr); ChannelKey key = new ChannelKey(ipStr, p); keys.add(key); // ✅ 关键:把 meta 写进解析器(首次创建或补齐空白) REACTOR.ensureChannel(key, id, name); } catch (NumberFormatException ignore) {} } // 覆盖订阅 REACTOR.wsSubscribe(session, keys); session.getAsyncRemote().sendText("{\"ok\":true,\"subscribed\":" + keys.size() + "}"); } catch (Throwable t) { try { session.getAsyncRemote().sendText("{\"ok\":false,\"error\":\"" + safeMsg(t) + "\"}"); } catch (Exception ignore){} } } // 工具:容忍 id 传字符串或数字 private static Integer parseIntOrNull(Object v) { if (v == null) return null; if (v instanceof Number) return ((Number)v).intValue(); try { return Integer.valueOf(String.valueOf(v).trim()); } catch (Exception e) { return null; } } @OnClose public void onClose(Session session, CloseReason reason) { REACTOR.wsUnsubscribeAll(session); } @OnError public void onError(Session session, Throwable thr) { REACTOR.wsUnsubscribeAll(session); } } /* ============= 通用工具 ============= */ private static void safeSendSSE(SseSubscriber s, String json) { try { s.send(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("\"", "'"); } private static int findTsSync(byte[] b, int n, int start) { for (int i = start; i + TS_SIZE < n; ++i) { if (b[i] != 0x47) continue; if (i + 2*TS_SIZE < n) { if (b[i + TS_SIZE] == 0x47 && b[i + 2*TS_SIZE] == 0x47) return i; } else return i; } return -1; } /* ============= 入库:保持你原来的存储过程调用 ============= */ 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 in = LoadProgramServlet.class.getClassLoader().getResourceAsStream("jdbc.properties"); if (in != null) { props.load(in); String DB_URL = props.getProperty("jdbc.url"); String DB_USER = props.getProperty("jdbc.username"); String DB_PWD = 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_PWD); 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); cs.setInt(5, progNo==null?0:progNo); cs.setInt(6, pmtPid==null?0:pmtPid); cs.setInt(7, videoPid==null?0:videoPid); cs.setString(8, videoType==null?"-":videoType); 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); cs.execute(); int ret = cs.getInt(13); // ret==1 插入成功;保留静默 } finally { try { if (cs!=null) cs.close(); } catch (Exception ignore){} try { if (c!=null) c.close(); } catch (Exception ignore){} } } } catch (Exception e) { throw new ServletException("Failed to load database properties", e); } } } /* ================== 事件绑定:点击“解析节目” => 单 WS 解析全部 ================== */ document.getElementById('btnParse')?.addEventListener('click', () => { if (!groups.length) { alert('没有可解析的输入通道'); return; } openWsAndStartAll(); }); 点击解析节目 获取不到 rate
最新发布
11-22
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值