فهرست منبع

调整代码,增加播放器数量

kwl 3 هفته پیش
والد
کامیت
47068d5e87

+ 0 - 3
src/main/java/com/jttserver/relay/FlvRealtimeStreamRelay.java

@@ -4,12 +4,9 @@ package com.jttserver.relay;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import com.jttserver.codec.FlvPacketizer;
-import com.jttserver.protocol.Jtt1078PacketParams;
 import com.jttserver.relay.workerthreads.BroadcastWorker;
 import com.jttserver.service.publisher.PublishServer;
 import com.jttserver.service.receiver.RecvSever;
-import com.jttserver.utils.SimCardUtils;
 
 import io.netty.buffer.Unpooled;
 import io.netty.channel.Channel;

+ 1 - 1
src/main/java/com/jttserver/relay/workerthreads/PlaybackWorker.java

@@ -135,7 +135,7 @@ public class PlaybackWorker {
     }
 
     /**
-     * 清空指定 streamId 的缓存队列(可选操作)
+     * 清空指定 streamId 的缓存队列
      * @param streamId 流 ID
      */
     public static void clearQueue(String streamId) {

+ 0 - 2
src/main/java/com/jttserver/relay/workerthreads/VideoPublishWorker.java

@@ -6,13 +6,11 @@ import java.util.concurrent.RejectedExecutionException;
 import java.util.concurrent.ThreadFactory;
 import java.util.concurrent.ThreadPoolExecutor;
 import java.util.concurrent.TimeUnit;
-import java.util.stream.Stream;
 
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import com.jttserver.config.ConfigManager;
-import com.jttserver.protocol.Jtt1078NaluPacket;
 import com.jttserver.protocol.Jtt1078PacketParams;
 import com.jttserver.protocol.JttConstants;
 import com.jttserver.relay.StreamRelay;

+ 0 - 3
src/main/java/com/jttserver/service/publisher/WebsockServer.java

@@ -4,8 +4,6 @@ import com.jttserver.relay.StreamRelay;
 import com.jttserver.utils.CommUtils;
 
 import java.net.InetSocketAddress;
-import java.util.ArrayList;
-import java.util.List;
 import java.util.Map;
 import java.util.concurrent.ConcurrentHashMap;
 
@@ -22,7 +20,6 @@ import io.netty.channel.ChannelId;
 import io.netty.channel.ChannelInboundHandlerAdapter;
 import io.netty.channel.ChannelInitializer;
 import io.netty.channel.ChannelOption;
-import io.netty.channel.EventLoopGroup;
 import io.netty.channel.group.ChannelGroup;
 import io.netty.channel.group.DefaultChannelGroup;
 import io.netty.channel.nio.NioEventLoopGroup;

+ 0 - 1
src/main/java/com/jttserver/service/receiver/JttVideoRecvServer.java

@@ -14,7 +14,6 @@ import com.jttserver.device.DeviceManager;
 import com.jttserver.protocol.Jtt1078NaluPacket;
 import com.jttserver.protocol.Jtt1078PacketParams;
 import com.jttserver.protocol.Jtt1078PacketParser;
-import com.jttserver.relay.FlvRealtimeStreamRelay;
 import com.jttserver.relay.StreamRelay;
 import com.jttserver.relay.StreamRelayType;
 import com.jttserver.relay.workerthreads.BroadcastWorker;

+ 435 - 142
src/main/resources/web/jessibuca/demo.html

@@ -2,189 +2,482 @@
 <html lang="en">
 <head>
     <meta charset="UTF-8">
-    <title>Title</title>
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>Jessibuca 多播放器演示</title>
     <script src="./jessibuca.js"></script>
     <style>
-        .root {
+        * {
+            box-sizing: border-box;
+        }
+
+        body {
+            margin: 0;
+            padding: 20px;
+            background: #f0f0f0;
+            font-family: Arial, sans-serif;
+        }
+
+        .config-panel {
+            background: white;
+            padding: 20px;
+            margin-bottom: 20px;
+            border-radius: 8px;
+            box-shadow: 0 2px 8px rgba(0,0,0,0.1);
+        }
+
+        .config-panel h2 {
+            margin-top: 0;
+            color: #333;
+        }
+
+        .config-row {
             display: flex;
-            place-content: center;
-            margin-top: 3rem;
+            align-items: center;
+            gap: 15px;
+            margin-bottom: 15px;
+            flex-wrap: wrap;
         }
 
-        .container-shell {
-            backdrop-filter: blur(5px);
-            background: hsla(0, 0%, 50%, 0.5);
-            padding: 30px 4px 10px 4px;
-            /* border: 2px solid black; */
-            width: auto;
-            position: relative;
-            border-radius: 5px;
-            box-shadow: 0 10px 20px;
+        .config-row label {
+            font-weight: bold;
+            color: #555;
+            min-width: 80px;
         }
 
-        .container-shell:before {
-            content: "jessibuca demo player";
-            position: absolute;
-            color: darkgray;
-            top: 4px;
-            left: 10px;
-            text-shadow: 1px 1px black;
+        .config-row input {
+            width: 80px;
+            padding: 8px;
+            border: 1px solid #ddd;
+            border-radius: 4px;
+            font-size: 14px;
         }
 
-        #container {
-            background: rgba(13, 14, 27, 0.7);
-            width: 640px;
-            height: 398px;
+        .config-row button {
+            padding: 8px 20px;
+            background: #4CAF50;
+            color: white;
+            border: none;
+            border-radius: 4px;
+            cursor: pointer;
+            font-size: 14px;
+            transition: background 0.3s;
         }
 
-        .input {
+        .config-row button:hover {
+            background: #45a049;
+        }
+
+        .batch-controls {
             display: flex;
-            margin-top: 10px;
+            gap: 10px;
+            margin-top: 15px;
+        }
+
+        .batch-controls button {
+            padding: 8px 16px;
+            background: #2196F3;
             color: white;
-            place-content: stretch;
+            border: none;
+            border-radius: 4px;
+            cursor: pointer;
+            font-size: 14px;
         }
 
-        .input2 {
-            bottom: 0px;
+        .batch-controls button:hover {
+            background: #0b7dda;
         }
 
-        .input input {
-            flex: auto;
+        .batch-controls button.danger {
+            background: #f44336;
         }
 
-        .err {
-            position: absolute;
-            top: 40px;
-            left: 10px;
-            color: red;
+        .batch-controls button.danger:hover {
+            background: #da190b;
         }
 
-        .option {
-            position: absolute;
-            top: 4px;
-            right: 10px;
+        .players-grid {
+            display: grid;
+            gap: 15px;
+            width: 100%;
+        }
+
+        .player-item {
+            background: white;
+            border-radius: 8px;
+            padding: 15px;
+            box-shadow: 0 2px 8px rgba(0,0,0,0.1);
+            position: relative;
+        }
+
+        .player-item-header {
             display: flex;
-            place-content: center;
+            justify-content: space-between;
+            align-items: center;
+            margin-bottom: 10px;
+        }
+
+        .player-item-title {
+            font-weight: bold;
+            color: #333;
+            font-size: 14px;
+        }
+
+        .player-item-controls {
+            display: flex;
+            gap: 5px;
+        }
+
+        .player-item-controls button {
+            padding: 4px 12px;
             font-size: 12px;
+            border: none;
+            border-radius: 4px;
+            cursor: pointer;
+            transition: opacity 0.3s;
+        }
+
+        .player-item-controls button:hover {
+            opacity: 0.8;
         }
 
-        .option span {
+        .btn-play {
+            background: #4CAF50;
             color: white;
         }
 
-        .page {
-            background: white;
-            background-repeat: no-repeat;
-            background-position: top;
+        .btn-pause {
+            background: #ff9800;
+            color: white;
+        }
+
+        .btn-destroy {
+            background: #f44336;
+            color: white;
+        }
+
+        .player-container {
+            width: 100%;
+            background: rgba(13, 14, 27, 0.7);
+            position: relative;
+            overflow: hidden;
+            border-radius: 4px;
+        }
+
+        .player-container::before {
+            content: '';
+            display: block;
+            padding-top: 56.25%; /* 16:9 比例 */
+        }
+
+        .player-container > div {
+            position: absolute;
+            top: 0;
+            left: 0;
+            width: 100%;
+            height: 100%;
+        }
+
+        .player-url-input {
+            width: 100%;
+            padding: 8px;
+            margin-top: 10px;
+            border: 1px solid #ddd;
+            border-radius: 4px;
+            font-size: 12px;
         }
 
-        @media (max-width: 720px) {
-            #container {
-                width: 90vw;
-                height: 52.7vw;
+        .player-url-input:focus {
+            outline: none;
+            border-color: #4CAF50;
+        }
+
+        @media (max-width: 768px) {
+            .players-grid {
+                grid-template-columns: 1fr;
             }
         }
     </style>
 </head>
-<body class="page">
-<div class="root">
-    <div class="container-shell">
-        <div id="container"></div>
-        <div class="input">
-            <div>输入URL:</div>
-            <input
-                autocomplete="on"
-                id="playUrl"
-                value=""
-            />
-            <button id="play">播放</button>
-            <button id="pause" style="display: none">停止</button>
+<body>
+    <div class="config-panel">
+        <h2>播放器配置</h2>
+        <div class="config-row">
+            <label>行数 (Rows):</label>
+            <input type="number" id="rows" value="3" min="1" max="10">
+            <label>列数 (Columns):</label>
+            <input type="number" id="cols" value="3" min="1" max="10">
+            <button id="createGrid">创建网格</button>
+        </div>
+        <div class="config-row">
+            <label>默认 URL:</label>
+            <input type="text" id="defaultUrl" placeholder="ws://127.0.0.1:18090/realtime/40912468992-1.flv" style="flex: 1; min-width: 300px;" value="ws://127.0.0.1:18090/realtime/40912468992-1.flv">
         </div>
-        <div class="input" style="line-height: 30px">
-            <button id="destroy">销毁</button>
+        <div class="batch-controls">
+            <button id="playAll">全部播放</button>
+            <button id="pauseAll">全部暂停</button>
+            <button id="destroyAll" class="danger">全部销毁</button>
         </div>
     </div>
-</div>
-
-<script>
-    var $player = document.getElementById('play');
-    var $pause = document.getElementById('pause');
-    var $playHref = document.getElementById('playUrl');
-    var $container = document.getElementById('container');
-    var $destroy = document.getElementById('destroy');
-
-    var showOperateBtns = false; // 是否显示按钮
-    var forceNoOffscreen = true; //
-    var jessibuca = null;
-
-    function create() {
-        jessibuca = null;
-        jessibuca = new Jessibuca({
-            container: $container,
-            videoBuffer: 0.2, // 缓存时长
-            isResize: false,
-            text: "",
-            loadingText: "",
-            useMSE: false,
-            debug: true,
-            showBandwidth: showOperateBtns, // 显示网速
-            operateBtns: {
-                fullscreen: showOperateBtns,
-                screenshot: showOperateBtns,
-                play: showOperateBtns,
-                audio: true,
-                recorder: false
-            },
-            forceNoOffscreen: forceNoOffscreen,
-            isNotMute: false,
-        },);
-
-        jessibuca.on('audioInfo', function (audioInfo) {
-            console.log('audioInfo',audioInfo);
-        })
-
-        jessibuca.on('videoInfo', function (videoInfo) {
-            console.log('videoInfo',videoInfo);
-        })
-
-        $player.style.display = 'inline-block';
-        $pause.style.display = 'none';
-        $destroy.style.display = 'none';
-    }
-
-
-    create();
-
-    $player.addEventListener('click', function () {
-        var href = $playHref.value;
-        if (href) {
-            jessibuca.play(href);
-            $player.style.display = 'none';
-            $pause.style.display = 'inline-block';
-            $destroy.style.display = 'inline-block';
-        }
-    }, false)
-
-
-    $pause.addEventListener('click', function () {
-        $player.style.display = 'inline-block';
-        $pause.style.display = 'none';
-        jessibuca.pause();
-    })
-
-    $destroy.addEventListener('click', function () {
-        if (jessibuca) {
-            jessibuca.destroy().then(()=>{
-                create();
+
+    <div id="playersGrid" class="players-grid"></div>
+
+    <script>
+        var players = []; // 存储所有播放器实例
+        var playerContainers = []; // 存储所有容器元素
+        var rows = 2;
+        var cols = 2;
+
+        var showOperateBtns = false;
+        var forceNoOffscreen = true;
+
+        // 创建播放器网格
+        function createGrid() {
+            rows = parseInt(document.getElementById('rows').value) || 2;
+            cols = parseInt(document.getElementById('cols').value) || 2;
+            
+            // 销毁现有播放器
+            destroyAllPlayers();
+            
+            var grid = document.getElementById('playersGrid');
+            grid.innerHTML = '';
+            grid.style.gridTemplateColumns = `repeat(${cols}, 1fr)`;
+            
+            players = [];
+            playerContainers = [];
+            
+            var total = rows * cols;
+            for (var i = 0; i < total; i++) {
+                createPlayerItem(i);
+            }
+        }
+
+        // 创建单个播放器项
+        function createPlayerItem(index) {
+            var grid = document.getElementById('playersGrid');
+            
+            var item = document.createElement('div');
+            item.className = 'player-item';
+            item.id = 'player-item-' + index;
+            
+            var header = document.createElement('div');
+            header.className = 'player-item-header';
+            
+            var title = document.createElement('div');
+            title.className = 'player-item-title';
+            title.textContent = '播放器 #' + (index + 1);
+            
+            var controls = document.createElement('div');
+            controls.className = 'player-item-controls';
+            
+            var playBtn = document.createElement('button');
+            playBtn.className = 'btn-play';
+            playBtn.textContent = '播放';
+            playBtn.id = 'play-btn-' + index;
+            playBtn.onclick = function() { playPlayer(index); };
+            
+            var pauseBtn = document.createElement('button');
+            pauseBtn.className = 'btn-pause';
+            pauseBtn.textContent = '暂停';
+            pauseBtn.id = 'pause-btn-' + index;
+            pauseBtn.style.display = 'none';
+            pauseBtn.onclick = function() { pausePlayer(index); };
+            
+            var destroyBtn = document.createElement('button');
+            destroyBtn.className = 'btn-destroy';
+            destroyBtn.textContent = '销毁';
+            destroyBtn.id = 'destroy-btn-' + index;
+            destroyBtn.onclick = function() { destroyPlayer(index); };
+            
+            controls.appendChild(playBtn);
+            controls.appendChild(pauseBtn);
+            controls.appendChild(destroyBtn);
+            
+            header.appendChild(title);
+            header.appendChild(controls);
+            
+            var container = document.createElement('div');
+            container.className = 'player-container';
+            container.id = 'player-container-' + index;
+            
+            var urlInput = document.createElement('input');
+            urlInput.type = 'text';
+            urlInput.className = 'player-url-input';
+            urlInput.id = 'url-input-' + index;
+            urlInput.placeholder = '输入 WebSocket URL';
+            
+            var defaultUrl = document.getElementById('defaultUrl').value;
+            if (defaultUrl) {
+                var playerNumber = index + 1;
+                // 如果URL以.flv结尾,在.flv之前插入编号
+                if (defaultUrl.toLowerCase().endsWith('.flv')) {
+                    urlInput.value = defaultUrl.slice(0, -4) + playerNumber + '.flv';
+                } else {
+                    // 否则在末尾追加编号
+                    urlInput.value = defaultUrl + playerNumber;
+                }
+            }
+            
+            item.appendChild(header);
+            item.appendChild(container);
+            item.appendChild(urlInput);
+            
+            grid.appendChild(item);
+            
+            // 创建播放器实例
+            var playerContainer = document.createElement('div');
+            container.appendChild(playerContainer);
+            
+            var jessibuca = new Jessibuca({
+                container: playerContainer,
+                videoBuffer: 0.2,
+                isResize: false,
+                text: "",
+                loadingText: "",
+                useMSE: false,
+                debug: true,
+                showBandwidth: showOperateBtns,
+                operateBtns: {
+                    fullscreen: showOperateBtns,
+                    screenshot: showOperateBtns,
+                    play: true,
+                    audio: true,
+                    recorder: false
+                },
+                forceNoOffscreen: forceNoOffscreen,
+                isNotMute: false,
+            });
+            
+            jessibuca.on('audioInfo', function (audioInfo) {
+                console.log('Player #' + (index + 1) + ' audioInfo', audioInfo);
+            });
+            
+            jessibuca.on('videoInfo', function (videoInfo) {
+                console.log('Player #' + (index + 1) + ' videoInfo', videoInfo);
             });
+            
+            players[index] = jessibuca;
+            playerContainers[index] = {
+                container: container,
+                playBtn: playBtn,
+                pauseBtn: pauseBtn,
+                destroyBtn: destroyBtn,
+                urlInput: urlInput
+            };
         }
-        else {
-            create();
+
+        // 播放指定播放器
+        function playPlayer(index) {
+            if (!players[index]) return;
+            
+            var url = playerContainers[index].urlInput.value;
+            if (!url) {
+                alert('请输入 URL');
+                return;
+            }
+            
+            players[index].play(url);
+            playerContainers[index].playBtn.style.display = 'none';
+            playerContainers[index].pauseBtn.style.display = 'inline-block';
         }
-    })
 
-</script>
+        // 暂停指定播放器
+        function pausePlayer(index) {
+            if (!players[index]) return;
+            
+            players[index].pause();
+            playerContainers[index].playBtn.style.display = 'inline-block';
+            playerContainers[index].pauseBtn.style.display = 'none';
+        }
 
+        // 销毁指定播放器
+        function destroyPlayer(index) {
+            if (!players[index]) return;
+            
+            players[index].destroy().then(function() {
+                // 重新创建播放器
+                var container = document.getElementById('player-container-' + index);
+                var playerContainer = document.createElement('div');
+                container.innerHTML = '';
+                container.appendChild(playerContainer);
+                
+                var jessibuca = new Jessibuca({
+                    container: playerContainer,
+                    videoBuffer: 0.2,
+                    isResize: false,
+                    text: "",
+                    loadingText: "",
+                    useMSE: false,
+                    debug: true,
+                    showBandwidth: showOperateBtns,
+                    operateBtns: {
+                        fullscreen: showOperateBtns,
+                        screenshot: showOperateBtns,
+                        play: true,
+                        audio: true,
+                        recorder: false
+                    },
+                    forceNoOffscreen: forceNoOffscreen,
+                    isNotMute: false,
+                });
+                
+                jessibuca.on('audioInfo', function (audioInfo) {
+                    console.log('Player #' + (index + 1) + ' audioInfo', audioInfo);
+                });
+                
+                jessibuca.on('videoInfo', function (videoInfo) {
+                    console.log('Player #' + (index + 1) + ' videoInfo', videoInfo);
+                });
+                
+                players[index] = jessibuca;
+                playerContainers[index].playBtn.style.display = 'inline-block';
+                playerContainers[index].pauseBtn.style.display = 'none';
+            });
+        }
+
+        // 全部播放
+        function playAll() {
+            for (var i = 0; i < players.length; i++) {
+                if (players[i] && playerContainers[i].urlInput.value) {
+                    playPlayer(i);
+                }
+            }
+        }
+
+        // 全部暂停
+        function pauseAll() {
+            for (var i = 0; i < players.length; i++) {
+                if (players[i]) {
+                    pausePlayer(i);
+                }
+            }
+        }
+
+        // 全部销毁
+        function destroyAllPlayers() {
+            for (var i = 0; i < players.length; i++) {
+                if (players[i]) {
+                    players[i].destroy().catch(function(err) {
+                        console.error('销毁播放器 #' + (i + 1) + ' 时出错:', err);
+                    });
+                }
+            }
+            players = [];
+            playerContainers = [];
+        }
+
+        // 事件监听
+        document.getElementById('createGrid').addEventListener('click', createGrid);
+        document.getElementById('playAll').addEventListener('click', playAll);
+        document.getElementById('pauseAll').addEventListener('click', pauseAll);
+        document.getElementById('destroyAll').addEventListener('click', function() {
+            if (confirm('确定要销毁所有播放器吗?')) {
+                destroyAllPlayers();
+                createGrid();
+            }
+        });
+
+        // 初始化
+        createGrid();
+    </script>
 </body>
 </html>
-