<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12

      Loading

      視頻中臺解決方案:組織樹組件+多路視頻直播界面開發(fā)

      前言

      最近準備搞新項目了

      這次應(yīng)該不會咕咕咕了,我編寫了完整的計劃

      如果按計劃來的話,應(yīng)該可以在一個月內(nèi)搞定 MVP 上線

      不過在開始新項目之前,得把我之前的工作整理一下,輸出幾篇筆記記錄一下

      在公眾號后臺回復(fù)「樹組件」可以獲取本文樹組件的相關(guān)代

      介紹

      這個項目是中臺里的一個子項目,視頻中臺

      主要功能是管理各個項目的監(jiān)控設(shè)備、攝像頭,以及查看監(jiān)控直播

      在此之前,我只用 SRS 部署了直播平臺,然后使用 RTMP 協(xié)議推流實現(xiàn)直播

      但這種方式適合的場景更多是像 B 站、抖音這種直播平臺

      對于視頻監(jiān)控,業(yè)內(nèi)有個更專業(yè)的方式:GB28181-2016 標準

      也就是常說的 28181 協(xié)議

      最終我們選擇的監(jiān)控后端是開源的 WVP (https://github.com/648540858/wvp-GB28181-pro)

      初見這個項目主頁,一股濃濃的國產(chǎn)粗獷風格撲面而來,主打一個湊合看看得了,簡單的文檔后標明了收費內(nèi)容和付費咨詢渠道。

      不過也就是這么個粗看其貌不揚的項目,卻意外的…能用?

      總之就是這么個項目,支撐起幾千個設(shè)備的視頻監(jiān)控播放。

      主要的設(shè)備就是海康、大華、宇視等品牌的 IPC、NVR,一開始我還 NVR(錄像機)和 IPC(攝像頭)拆分開,沒想到這系統(tǒng)里是不拆分的,我后面也發(fā)現(xiàn)了不拆分更好,一律按照設(shè)備來記錄,然后實際視頻流再按照通道來區(qū)分。

      視頻中臺

      視頻中臺這塊的技術(shù)方面其實不會很復(fù)雜

      主要的工作量和復(fù)雜度還是在溝通、協(xié)調(diào)等流程方面,原因有以下幾點:

      • 不單單是做這么一個系統(tǒng),還需要讓現(xiàn)場人員去配置攝像頭,讓管理人員錄入攝像頭信息
      • 現(xiàn)場人員很多不會操作電腦,如何指導(dǎo)他們配置攝像頭(類似配置路由器)
      • 存量設(shè)備和新增設(shè)備如何管理?
      • 攝像頭和錄像機如何編碼?
      • 編碼完成后如何讓現(xiàn)場人員知道哪個編碼對應(yīng)哪個設(shè)備?
      • ……

      這里只列舉一部分,實際運行的問題只會更多。

      其實這些都還好,只要理清了整個流程,實施起來還是有可行性的。

      但一旦涉及得人過多,沒有人負責推動,最終就會變成互相推諉,效率低下,一個月都不一定能完成一臺設(shè)備的接入。

      OK,廢話太多了,說回正題,先來看看系統(tǒng)界面。

      主頁

      直接上截圖吧,這是視頻中臺的截圖(敏感信息和數(shù)據(jù)已經(jīng)全部用假數(shù)據(jù)代替,請放心查看)

      image

      從界面可以看出,核心功能就是管理視頻和播放視頻

      視頻播放

      PS: 敏感信息已打碼,請放心查看

      視頻播放界面,就是本文要重點介紹的

      image

      可以切換 1 路、4 路、9 路、16 路播放,這里再截一個 16 路視頻的播放截圖吧,其他就不放了,相信聰明的讀者們能理解的 ??

      image

      技術(shù)實現(xiàn)

      技術(shù)方面,我繼續(xù)發(fā)揚之前「Less is more」的思路: 返璞歸真!使用 Alpine.js 開發(fā)交互式 web 應(yīng)用,拋棄 node_modules 和 webpack 吧!

      使用 Alpine.js + HTMX 來實現(xiàn)整個頁面

      代碼

      頁面布局

      頁面布局使用 tailwindcss

      交互使用剛才說的 Alpine.js

      <main x-data="playApp()">
        <div class="grid grid-cols-12 gap-4">
          {# 左側(cè)組織/項目樹 #}
          <div class="col-span-4" id="tree-list">
            <div class="bg-white rounded-lg shadow h-full">
              <div class="border-b border-gray-200 bg-[#f1f5fa] px-4 py-3">
                <div class="flex items-center justify-between gap-2 h-8">
                  <div class="flex items-center">
                    <span class="w-1.5 h-4 bg-[#156bd2] rounded mr-2"></span>
                    <h5 class="text-lg font-medium text-gray-900">組織架構(gòu)</h5>
                  </div>
                  <button
                          type="button"
                          class="inline-flex gap-2 items-center px-2 py-1 bg-transparent border border-[#0f5cb9] shadow-sm text-sm font-medium rounded-md text-[#0f5cb9] bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 disabled:opacity-50 disabled:cursor-not-allowed"
                          x-on:click="refreshTree()"
                          x-bind:disabled="isLoading"
                          >
                    <i class="fa-solid fa-arrow-rotate-right"></i>
                    <span x-text="isLoading ? '加載中...' : '刷新'"></span>
                  </button>
                </div>
              </div>
              <div class="p-4 h-full flex flex-col gap-4">
                <!-- 搜索框 -->
                <div x-show="!isLoading">
                  <div class="relative">
                    <input
                           type="text"
                           id="tree-search"
                           placeholder="搜索組織或項目..."
                           class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"
                           x-on:input="tree && tree.search($event.target.value)"
                           >
                    <div class="absolute inset-y-0 right-0 flex items-center pr-3">
                      <svg class="h-4 w-4 text-gray-400" fill="none" stroke="currentColor"
                           viewBox="0 0 24 24">
                        <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
                              d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path>
                      </svg>
                    </div>
                  </div>
                  <!-- 搜索結(jié)果統(tǒng)計 -->
                  <div id="search-stats" class="mt-2 text-sm text-gray-500" style="display: none;"></div>
                </div>
      
                <!-- 加載動畫骨架屏 -->
                <div x-show="isLoading" class="space-y-3">
                  <div class="animate-pulse">
                    <div class="flex items-center space-x-3">
                      <div class="w-4 h-4 bg-gray-300 rounded"></div>
                      <div class="h-4 bg-gray-300 rounded w-3/4"></div>
                    </div>
                  </div>
                  <div class="animate-pulse ml-6">
                    <div class="flex items-center space-x-3">
                      <div class="w-4 h-4 bg-gray-300 rounded"></div>
                      <div class="h-4 bg-gray-300 rounded w-2/3"></div>
                    </div>
                  </div>
                  <div class="animate-pulse ml-12">
                    <div class="flex items-center space-x-3">
                      <div class="w-4 h-4 bg-gray-300 rounded"></div>
                      <div class="h-4 bg-gray-300 rounded w-1/2"></div>
                    </div>
                  </div>
                  <div class="animate-pulse ml-6">
                    <div class="flex items-center space-x-3">
                      <div class="w-4 h-4 bg-gray-300 rounded"></div>
                      <div class="h-4 bg-gray-300 rounded w-3/5"></div>
                    </div>
                  </div>
                  <div class="animate-pulse">
                    <div class="flex items-center space-x-3">
                      <div class="w-4 h-4 bg-gray-300 rounded"></div>
                      <div class="h-4 bg-gray-300 rounded w-4/5"></div>
                    </div>
                  </div>
                  <div class="animate-pulse ml-6">
                    <div class="flex items-center space-x-3">
                      <div class="w-4 h-4 bg-gray-300 rounded"></div>
                      <div class="h-4 bg-gray-300 rounded w-1/3"></div>
                    </div>
                  </div>
                </div>
      
                <!-- 樹形結(jié)構(gòu)容器 -->
                <div id="tree-container" class="tree-view" x-show="!isLoading"></div>
              </div>
            </div>
          </div>
      
          <!-- 播放器 -->
          <div class="col-span-8">
            <div class="bg-white rounded-lg shadow h-full">
              <div class="border-b border-gray-200 bg-[#f1f5fa] px-4 py-3">
                <div class="flex items-center h-8">
                  <span class="w-1.5 h-4 bg-[#156bd2] rounded mr-2"></span>
                  <h5 class="text-lg font-medium text-gray-900">視頻播放</h5>
                </div>
              </div>
              <div class="p-4">
                <div class="bg-blue-50 text-blue-700 px-4 py-3 rounded-lg mb-2" x-show="!selectedProject">
                  請先選擇攝像頭
                </div>
                <!-- 加載攝像頭提示 -->
                <div class="bg-yellow-50 text-yellow-700 px-4 py-3 rounded-lg mb-2" x-show="isLoadingCameras">
                  <div class="flex items-center">
                    <i class="fas fa-spinner fa-spin mr-2"></i>
                    正在加載攝像頭列表...
                  </div>
                </div>
                <div class="space-y-4">
                  <!-- 視頻播放控制欄 -->
                  <div class="flex items-center justify-between bg-gray-50 px-4 py-3 rounded-lg">
                    <div class="flex items-center space-x-4">
                      <span class="text-sm font-medium text-gray-700">播放模式:</span>
                      <select x-model="videoLayout" x-on:change="changeVideoLayout()"
                              class="px-3 py-1 border border-gray-300 rounded-md text-sm focus:outline-none focus:ring-2 focus:ring-blue-500">
                        <option value="1">1路播放</option>
                        <option value="4">4路播放</option>
                        <option value="9">9路播放</option>
                        <option value="16">16路播放</option>
                      </select>
                    </div>
                    <div class="flex items-center space-x-2">
                      <span class="text-sm text-gray-600"
                            x-text="`已播放: ${activeVideos.filter(v => v).length}/${videoLayout}`"></span>
                      <button x-on:click="clearAllVideos()"
                              class="px-3 py-1 bg-red-500 text-white text-sm rounded hover:bg-red-600 focus:outline-none focus:ring-2 focus:ring-red-500">
                        清空全部
                      </button>
                    </div>
                  </div>
      
                  <!-- 讓tailwind生成樣式 -->
                  <div class="hidden grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4"></div>
      
                  <!-- 視頻播放網(wǎng)格 -->
                  <div id="video-grid" class="grid gap-2" x-bind:class="getGridClass()">
                    <template x-for="(video, index) in activeVideos" :key="index">
                      <div class="relative bg-black rounded-lg overflow-hidden aspect-video">
                        <template x-if="activeVideos[index]">
                          <div class="relative w-full h-full">
                            <video
                                   x-bind:id="`video-player-${index}`"
                                   class="w-full h-full object-cover"
                                   controls
                                   muted
                                   x-bind:data-camera-guid="activeVideos[index].guid"
                                   ></video>
                            <!-- 視頻信息覆蓋層 -->
                            <div class="absolute top-2 left-2 bg-black bg-opacity-70 text-white px-2 py-1 rounded text-xs">
                              <span x-text="activeVideos[index].name"></span>
                            </div>
                            <!-- 控制按鈕 -->
                            <div class="absolute top-2 right-2 flex space-x-1">
                              <button x-on:click="fullscreenVideo(index)"
                                      class="bg-black bg-opacity-70 text-white p-1 rounded hover:bg-opacity-90">
                                <i class="fas fa-expand text-xs"></i>
                              </button>
                              <button x-on:click="removeVideo(index)"
                                      class="bg-red-500 bg-opacity-70 text-white p-1 rounded hover:bg-opacity-90">
                                <i class="fas fa-times text-xs"></i>
                              </button>
                            </div>
                          </div>
                        </template>
                        <template x-if="!activeVideos[index]">
                          <div class="flex items-center justify-center h-full text-gray-400">
                            <div class="text-center">
                              <i class="fas fa-video text-4xl mb-2"></i>
                              <p class="text-sm">空閑位置</p>
                            </div>
                          </div>
                        </template>
                      </div>
                    </template>
                  </div>
                </div>
              </div>
            </div>
          </div>
        </div>
      </main>
      

      播放器實現(xiàn)

      播放器我選擇了 mpegts.js - https://github.com/xqq/mpegts.js

      mpegts.js 是在 HTML5 上直接播放 MPEG2-TS 流的播放器,針對低延遲直播優(yōu)化,可用于 DVB/ISDB 數(shù)字電視流或監(jiān)控攝像頭等的低延遲回放。

      這是 B 站開源的 flv.js 的 fork 版本

      B 站和國內(nèi)其他大廠的尿性一樣,管生不管養(yǎng),flv.js 項目已經(jīng)四年多沒更新了,issues 一大堆也不處理,基本處于廢棄狀態(tài)。

      估計這也是 B 站的一個 KPI 開源項目吧…

      我一開始看到是 B 站開源的,以為會很好用,用 flv.js 來播放,結(jié)果根本沒法播放,一看才知道 flv.js 只支持 H.264 編碼

      而現(xiàn)在攝像頭很多都是 H.265 編碼了…

      WVP 項目的播放使用的是 Jessibuca 這個播放器

      不過這個項目的文檔比 WVP 還亂,讓人根本沒有想要使用的欲望…(雖說這個項目可能兼容性和性能都會好一些?)

      而且因為用了 wasm,不能使用 npm 安裝,集成也麻煩,我還是選擇了純 js 實現(xiàn)的方案。

      安裝也簡單

      pnpm i mpegts.js
      

      經(jīng)過 gulp 配置后集成到靜態(tài)文件里

      <script src="{% static 'lib/mpegts.js/dist/mpegts.js' %}"></script>
      

      播放視頻流的代碼也比較簡單

      console.log('播放攝像頭:', camera.name, 'GUID:', camera.guid);
      
      // 獲取攝像頭直播地址
      const url = window.API_URLS.cameraStreamUrl.replace('__camera_guid__', camera.guid);
      axios.get(url)
        .then(res => {
        if (res.data.success && res.data.data && res.data.data.stream_url) {
          const streamUrl = res.data.data.stream_url.trim();
          console.log('直播地址:', streamUrl);
          this.addVideoToGrid(camera, streamUrl);
        } else {
          alert('獲取直播地址失敗:無效的響應(yīng)數(shù)據(jù)');
        }
      })
        .catch(err => {
        console.error('獲取直播地址失敗:', err);
        alert('獲取直播地址失敗,請重試');
      });
      

      純 Alpine.js 的樹組件實現(xiàn)

      使用 react/vue 時,應(yīng)該有比較多可選的樹組件

      不過純 js 的樹組件就都是純一坨,根本沒有能用的!

      在一番嘗試之后,我決定使用 Alpine.js 自己寫一個!

      效果在前面的截圖里也有了,可以實現(xiàn)樹節(jié)點展開、實時搜索過濾,需要的功能都有,完美~

      代碼由于篇幅關(guān)系就不放了,有興趣的同學可以在公眾號后臺回復(fù)「樹組件」獲取相關(guān)代碼~

      小結(jié)

      OK,就這樣了,完成了一篇工作內(nèi)容的整理。

      距離我開啟新項目又近了一步!

      posted @ 2025-07-04 11:55  程序設(shè)計實驗室  閱讀(487)  評論(3)    收藏  舉報
      主站蜘蛛池模板: 国产精品久久中文字幕| 天堂av资源在线免费| 亚洲三级香港三级久久| 国产成人综合色就色综合| 亚洲产在线精品亚洲第一站一| 精品三级在线| 欧美亚洲另类自拍偷在线拍| 无码国产偷倩在线播放| 久久精品中文字幕有码| 丁香婷婷在线观看| 亚洲成aⅴ人在线电影| 国产精品福利自产拍在线观看 | 国产永久免费高清在线| 草草浮力影院| 成av人电影在线观看| 亚洲av与日韩av在线| 中文字幕国产精品二区| 国产av一区二区三区| 国产精品国产亚洲区久久| 国产麻豆一区二区精彩视频| 久久青草国产精品一区| 女人18片毛片60分钟| 亚洲精品国产男人的天堂| 办公室强奷漂亮少妇同事| 妓院一钑片免看黄大片| 国内精品亚洲成av人片| 日韩精品一区二区三区久| 国产AV无码专区亚洲AV漫画| 夜色福利站WWW国产在线视频| 无码人妻一区二区三区精品视频| 亚洲色大成成人网站久久| 邳州市| 久久婷婷五月综合色和啪| 九九热在线视频观看最新| 国产在线观看免费观看不卡| 奶头好大揉着好爽视频| 久久亚洲国产精品五月天| 国产成年码AV片在线观看| 蜜桃精品成人影片| 日韩熟女精品一区二区三区| 被拉到野外强要好爽|