最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Nodejs拉取??低曅熊囉涗泝x攝像頭視頻流的實(shí)現(xiàn)方法

 更新時間:2026年02月04日 09:19:38   作者:s1h2e34  
在市場上海康威視的產(chǎn)品一直處于領(lǐng)先地位,提供了一系列高質(zhì)量的視頻監(jiān)控解決方案,下面這篇文章主要介紹了Nodejs拉取??低曅熊囉涗泝x攝像頭視頻流的實(shí)現(xiàn)方法,文中通過代碼介紹的非常詳細(xì),需要的朋友可以參考下

1.背景

現(xiàn)有需求,將??低曅熊囉涗泝x攝像頭視頻流顯示在開發(fā)web頁面上,基于諸多方面的考慮,采用??档脑破脚_作為轉(zhuǎn)發(fā)、存儲視頻流的平臺,而沒有搭建私有的視頻解決方案。如何將視頻流顯示在自己的web頁面上呢,采用了利用??堤峁┑膕dk截取轉(zhuǎn)發(fā)視頻流的方式。在研發(fā)此方案的過程中,經(jīng)歷了諸多試錯,本方案只是諸多嘗試中的可以成功運(yùn)行的方案之一。

本方案采用的是nodejs方案,將RTSP視頻流通過FFmpeg轉(zhuǎn)換為FLV格式,并通過WebSocket轉(zhuǎn)發(fā)給前端播放器。優(yōu)點(diǎn)是支持多通道并發(fā)并能支持一定的延時緩沖。

2實(shí)現(xiàn)

2.1??灯嘢DK

??灯噑dk即??灯囯娮釉艫PI服務(wù),為開發(fā)者提供https接口,即開發(fā)者通過https方式發(fā)起檢索請求,獲取返回json數(shù)據(jù)。具體代碼可參見??档墓倬W(wǎng):https://open.hikvisionauto.com/#/developDoc/api/HttpsAPI
官網(wǎng)似乎進(jìn)行了改版,對代碼進(jìn)行了刪減。改版前有java語言的版本,功能較豐富。以下是根據(jù)java語言代碼改寫成的nodejs語言的版本,支持預(yù)覽,獲得錄像列表及查看錄像:

/**
 * ??低暺囋O(shè)備SDK封裝類
 * 用于獲取設(shè)備預(yù)覽、視頻列表和回放功能
 */
const crypto = require('crypto');
const axios = require('axios');
const { v4: uuidv4 } = require('uuid');
const querystring = require('querystring');

class CarSDKApp {
  /**
   * 構(gòu)造函數(shù) - 初始化SDK配置參數(shù)
   */
  constructor() {
    // API訪問密鑰
    this.ACCESS_KEY = "****************";
    // API訪問密鑰
    this.ACCESS_SECRET = "**************";
    // 簽名方法
    this.SIGNATURE_METHOD = "HMAC-SHA1";
    // 默認(rèn)字符編碼
    this.DEFAULT_CHARSET = "UTF-8";
    // 區(qū)域ID
    this.REGION_ID = "cn-hangzhou";
    // API版本號
    this.VERSION = "2.1.0";
    // API基礎(chǔ)URL
    this.BASE_URL = "https://open.hikvisionauto.com:14021/v2/";
  }

  /**
   * 獲取基礎(chǔ)請求參數(shù)
   * @returns {Object} 包含簽名所需基礎(chǔ)參數(shù)的對象
   */
  getBaseParams() {
    const params = {
      SignatureMethod: this.SIGNATURE_METHOD,
      SignatureNonce: uuidv4(),  // 隨機(jī)唯一標(biāo)識符
      AccessKey: this.ACCESS_KEY,
      Timestamp: Date.now().toString(),  // 當(dāng)前時間戳
      Version: this.VERSION,
      RegionId: this.REGION_ID
    };
    
    // 按鍵名排序參數(shù)
    return Object.keys(params).sort().reduce((acc, key) => {
      acc[key] = params[key];
      return acc;
    }, {});
  }

  /**
   * 特殊URL編碼
   * 將特殊字符按照API要求進(jìn)行編碼
   * @param {string} value - 需要編碼的值
   * @returns {string} 編碼后的字符串
   */
  specialUrlEncode(value) {
    return querystring.escape(value)
      .replace(/\+/g, '%20')
      .replace(/\*/g, '%2A')
      .replace(/%7E/g, '~');
  }

  /**
   * 生成待簽名字符串
   * @param {Object} params - 請求參數(shù)對象
   * @param {string} method - HTTP請求方法(GET/POST)
   * @returns {string} 待簽名字符串
   */
  getStringToSign(params, method) {
    // 確保參數(shù)按鍵名排序
    const sortedParams = Object.keys(params).sort().reduce((acc, key) => {
      acc[key] = params[key];
      return acc;
    }, {});

    // 構(gòu)建排序后的查詢字符串
    const sortQueryStringTmp = Object.entries(sortedParams)
      .map(([key, value]) => `&${this.specialUrlEncode(key)}=${this.specialUrlEncode(value)}`)
      .join('');

    // 返回格式:HTTP方法&URL編碼的路徑&URL編碼的查詢字符串
    return `${method}&${this.specialUrlEncode('/')}&${this.specialUrlEncode(sortQueryStringTmp.substring(1))}`;
  }

  /**
   * 生成API簽名
   * @param {string} accessSecret - 訪問密鑰
   * @param {Object} params - 請求參數(shù)
   * @param {string} method - HTTP請求方法
   * @returns {string} Base64編碼的簽名字符串
   */
  sign(accessSecret, params, method) {
    const stringToSign = this.getStringToSign(params, method);
    console.log(`StringToSign = [${stringToSign}]`);
    
    // 使用HMAC-SHA1算法生成簽名
    const hmac = crypto.createHmac('sha1', accessSecret);
    hmac.update(stringToSign);
    return hmac.digest('base64');
  }

  /**
   * 獲取設(shè)備實(shí)時預(yù)覽URL
   * @param {string} deviceCode - 設(shè)備編碼
   * @param {number} channelID - 通道ID(可選)
   * @returns {Promise<string>} 預(yù)覽URL
   */
  async preview(deviceCode, channelID) {
    const url = this.BASE_URL + "device/preview/";
    const BASE_PARAMS = this.getBaseParams();

    // 添加業(yè)務(wù)參數(shù)
    BASE_PARAMS.deviceCode = deviceCode;
    if (channelID !== undefined) {
      BASE_PARAMS.channelNo = channelID.toString();
    }
    BASE_PARAMS.streamType = "0";  // 0:主碼流 1:子碼流

    // 生成簽名(注意:簽名時不包含 Signature 參數(shù)本身)
    const sign = this.sign(this.ACCESS_SECRET + "&", BASE_PARAMS, "GET");

    // 構(gòu)建完整的查詢字符串(包含簽名)
    const allParams = { ...BASE_PARAMS, Signature: sign };
    const queryString = Object.entries(allParams)
      .map(([key, value]) => `${this.specialUrlEncode(key)}=${this.specialUrlEncode(value)}`)
      .join('&');

    const fullUrl = `${url}?${queryString}`;
    console.log("sendurl:", fullUrl);

    try {
      const response = await axios.get(fullUrl);
      console.log("response:", response.data);

      // 檢查響應(yīng)狀態(tài)
      if (response.data.status !== 0) {
        throw new Error(`API Error: ${response.data.msg} (status: ${response.data.status})`);
      }

      // 提取預(yù)覽URL
      const previewUrl = response.data.data;
      console.log("previewUrl:", previewUrl);
      return previewUrl;
    } catch (error) {
      console.error('Error in preview:', error);
      throw error;
    }
  }

  /**
   * 獲取設(shè)備視頻列表
   * @param {string} deviceCode - 設(shè)備編碼
   * @param {string} startTime - 開始時間(格式:YYYY-MM-DD HH:mm:ss)
   * @param {string} endTime - 結(jié)束時間(格式:YYYY-MM-DD HH:mm:ss)
   * @returns {Promise<Object>} 視頻列表響應(yīng)數(shù)據(jù)
   */
  async list(deviceCode, startTime, endTime) {
    const url = this.BASE_URL + "device/videoList/";
    const BASE_PARAMS = this.getBaseParams();
    BASE_PARAMS.deviceCode = deviceCode;
    BASE_PARAMS.startTime = startTime;
    BASE_PARAMS.endTime = endTime;
    
    const sortQueryStringTmp = Object.entries(BASE_PARAMS)
      .map(([key, value]) => `&${this.specialUrlEncode(key)}=${this.specialUrlEncode(value)}`)
      .join('');
    
    const sign = this.sign(this.ACCESS_SECRET + "&", BASE_PARAMS, "GET");
    const fullUrl = url + `?Signature=${this.specialUrlEncode(sign)}${sortQueryStringTmp}`;
    
    try {
      const response = await axios.get(fullUrl);
      console.log(response.data);
      return response.data;
    } catch (error) {
      console.error('Error in list:', error);
      throw error;
    }
  }

  /**
   * 獲取設(shè)備視頻回放URL
   * @param {string} deviceCode - 設(shè)備編碼
   * @param {string} startTime - 開始時間(格式:YYYY-MM-DD HH:mm:ss)
   * @param {string} endTime - 結(jié)束時間(格式:YYYY-MM-DD HH:mm:ss)
   * @param {number} fileSize - 文件大?。ㄗ止?jié))
   * @returns {Promise<Object>} 回放URL響應(yīng)數(shù)據(jù)
   */
  async replay(deviceCode, startTime, endTime, fileSize) {
    const url = this.BASE_URL + "device/videoReplay/";
    const BASE_PARAMS = this.getBaseParams();
    BASE_PARAMS.deviceCode = deviceCode;
    BASE_PARAMS.startTime = startTime;
    BASE_PARAMS.endTime = endTime;
    BASE_PARAMS.fileSize = fileSize.toString();
    
    const sortQueryStringTmp = Object.entries(BASE_PARAMS)
      .map(([key, value]) => `&${this.specialUrlEncode(key)}=${this.specialUrlEncode(value)}`)
      .join('');
    
    const sign = this.sign(this.ACCESS_SECRET + "&", BASE_PARAMS, "GET");
    const fullUrl = url + `?Signature=${this.specialUrlEncode(sign)}${sortQueryStringTmp}`;
    
    try {
      const response = await axios.get(fullUrl);
      console.log(response.data);
      return response.data;
    } catch (error) {
      console.error('Error in replay:', error);
      throw error;
    }
  }
}

2.2Nodejs視頻流服務(wù)器

首先是引用包

/**
 * RTSP視頻流WebSocket轉(zhuǎn)發(fā)服務(wù)器
 * 功能:將RTSP視頻流通過FFmpeg轉(zhuǎn)換為FLV格式,并通過WebSocket轉(zhuǎn)發(fā)給前端播放器
 */

var express =  require("express");
var expressWebSocket = require("express-ws");
var ffmpeg = require("fluent-ffmpeg");
// 設(shè)置FFmpeg可執(zhí)行文件路徑
ffmpeg.setFfmpegPath("C:\\ffmpeg-7.1.1-full_build\\bin\\ffmpeg.exe");
var webSocketStream = require("websocket-stream/stream");
var WebSocket = require("websocket-stream");
var http = require("http");

然后是具體實(shí)現(xiàn)代碼。

// 獲取車牌號到設(shè)備ID的映射
const carToDeviceDict = initFromDB();

/**
 * 啟動本地WebSocket服務(wù)器
 * 監(jiān)聽8002端口,處理RTSP視頻流轉(zhuǎn)發(fā)請求
 */
function localServer() {
    let app = express();
    // 提供靜態(tài)文件服務(wù)
    app.use(express.static(__dirname));
    // 添加WebSocket支持,啟用消息壓縮
    expressWebSocket(app, null, {
        perMessageDeflate: true
    });
    // 注冊WebSocket路由處理函數(shù)
    app.ws("/rtsp/", rtspRequestHandle)
    // 啟動HTTP服務(wù)器監(jiān)聽8002端口
    app.listen(8002);
    console.log("express listened")
}

/**
 * 處理RTSP視頻流WebSocket請求
 * @param {WebSocket} ws - WebSocket連接對象
 * @param {Request} req - HTTP請求對象
 */
async function rtspRequestHandle(ws, req) {
    console.log("rtsp request handle");
    // 將WebSocket轉(zhuǎn)換為可讀寫的流
    const stream = webSocketStream(ws, {
        binary: true,  // 使用二進(jìn)制模式傳輸
        browserBufferTimeout: 1000000  // 瀏覽器緩沖區(qū)超時時間(毫秒)
    }, {
        browserBufferTimeout: 1000000
    });

    // 從請求參數(shù)中獲取車牌號和通道號
    let carID = req.query.carID;
    let channel = req.query.channel;

    console.log("car number:", carID);

    // 將車牌號轉(zhuǎn)換為設(shè)備ID
    const dict = initFromDB();
    const deviceId = dict[carID];

    // 檢查設(shè)備ID是否存在
    if (!deviceId) {
        console.error("Device ID not found for car:", carID);
        ws.close();
        return;
    }

    console.log("deviceId:", deviceId, "channel:", channel);

    // 導(dǎo)入海康威視汽車設(shè)備SDK
    const CarSDKApp = require("./carsdkapp");
    const carSDKApp = new CarSDKApp();

    try {
        // 調(diào)用SDK獲取設(shè)備實(shí)時預(yù)覽URL
        const url = await carSDKApp.preview(deviceId, channel);
        console.log("url:", url);

        // 使用FFmpeg處理RTSP流并轉(zhuǎn)換為FLV格式
        ffmpeg(url)
            // 輸入選項(xiàng)配置
            .addInputOption("-rtsp_transport", "tcp", "-buffer_size", "102400")  // 使用TCP傳輸,設(shè)置緩沖區(qū)大小
            .addInputOption("-fflags", "nobuffer")  // 禁用輸入緩沖,減少延遲
            .addInputOption("-flags", "low_delay")  // 低延遲模式
            .addInputOption("-strict", "experimental")  // 允許實(shí)驗(yàn)性編解碼器
            // 輸出選項(xiàng)配置
            .addOutputOption("-f", "flv")  // 輸出格式為FLV
            .addOutputOption("-preset", "ultrafast")  // 編碼預(yù)設(shè):最快速度
            .addOutputOption("-tune", "zerolatency")  // 調(diào)優(yōu):零延遲
            .addOutputOption("-g", "30")  // 關(guān)鍵幀間隔(GOP大?。?
            .addOutputOption("-keyint_min", "30")  // 最小關(guān)鍵幀間隔
            .addOutputOption("-sc_threshold", "0")  // 禁用場景切換檢測
            // 視頻編解碼器設(shè)置
            .videoCodec("libx264")  // 使用H.264編碼
            .noAudio()  // 禁用音頻
            .format("flv")  // 輸出格式為FLV
            // 事件監(jiān)聽器
            .on("start", function (commandLine) {
                console.log("FFmpeg started with command:", commandLine);
            })
            .on("codecData", function (data) {
                console.log("Stream codecData:", data);
                // 攝像機(jī)在線處理
            })
            .on("error", function (err) {
                console.log("FFmpeg error:", err.message);
                console.log("Error details:", err);
            })
            .on("end", function () {
                console.log("Stream ended");
                // 攝像機(jī)斷線的處理
            })
            .on("stderr", function (stderrLine) {
                console.log("FFmpeg stderr:", stderrLine);
            })
            // 將處理后的流通過WebSocket發(fā)送給客戶端
            .pipe(stream, { end: true });
    } catch (error) {
        console.log("Error getting preview URL or starting ffmpeg:", error);
    }
}

// 啟動服務(wù)器
localServer();

2.3頁面代碼

<body>
    <!-- 頁面標(biāo)題 -->
    <div class="header">
        六通道視頻監(jiān)控系統(tǒng)
    </div>
    
    <!-- 系統(tǒng)狀態(tài)欄 -->
    <div class="status-bar">
        <span id="status">系統(tǒng)準(zhǔn)備就緒 - 正在初始化...</span>
    </div>
    
    <!-- 通道狀態(tài)指示器 -->
    <div class="channel-status">
        <div class="channel-indicator" id="indicator1">通道1: 斷開</div>
        <div class="channel-indicator" id="indicator2">通道2: 斷開</div>
        <div class="channel-indicator" id="indicator3">通道3: 斷開</div>
        <div class="channel-indicator" id="indicator4">通道4: 斷開</div>
        <div class="channel-indicator" id="indicator5">通道5: 斷開</div>
        <div class="channel-indicator" id="indicator6">通道6: 斷開</div>
    </div>
    
    <!-- 視頻播放網(wǎng)格 -->
    <div class="video-grid">
        <!-- 通道1 -->
        <div class="video-container">
            <div class="video-header">通道 1</div>
            <div class="video-box">
                <video controls="controls" class="demo-video" id="player1" muted></video>
                <div id="loading1" class="loading" style="display: block;">加載中...</div>
            </div>
        </div>
        
        <!-- 通道2 -->
        <div class="video-container">
            <div class="video-header">通道 2</div>
            <div class="video-box">
                <video controls="controls" class="demo-video" id="player2" muted></video>
                <div id="loading2" class="loading" style="display: block;">加載中...</div>
            </div>
        </div>
        
        <!-- 通道3 -->
        <div class="video-container">
            <div class="video-header">通道 3</div>
            <div class="video-box">
                <video controls="controls" class="demo-video" id="player3" muted></video>
                <div id="loading3" class="loading" style="display: block;">加載中...</div>
            </div>
        </div>
        
        <!-- 通道4 -->
        <div class="video-container">
            <div class="video-header">通道 4</div>
            <div class="video-box">
                <video controls="controls" class="demo-video" id="player4" muted></video>
                <div id="loading4" class="loading" style="display: block;">加載中...</div>
            </div>
        </div>
        
        <!-- 通道5 -->
        <div class="video-container">
            <div class="video-header">通道 5</div>
            <div class="video-box">
                <video controls="controls" class="demo-video" id="player5" muted></video>
                <div id="loading5" class="loading" style="display: block;">加載中...</div>
            </div>
        </div>
        
        <!-- 通道6 -->
        <div class="video-container">
            <div class="video-header">通道 6</div>
            <div class="video-box">
                <video controls="controls" class="demo-video" id="player6" muted></video>
                <div id="loading6" class="loading" style="display: block;">加載中...</div>
            </div>
        </div>
    </div>
    
    <!-- 控制按鈕區(qū)域 -->
    <div class="controls">
        <button class="btn" onclick="reconnectAll()">?? 重新連接所有通道</button>
        <div class="btn-group">
            <button class="btn" onclick="reconnectChannel(1)">重連通道1</button>
            <button class="btn" onclick="reconnectChannel(2)">重連通道2</button>
            <button class="btn" onclick="reconnectChannel(3)">重連通道3</button>
        </div>
        <div class="btn-group">
            <button class="btn" onclick="reconnectChannel(4)">重連通道4</button>
            <button class="btn" onclick="reconnectChannel(5)">重連通道5</button>
            <button class="btn" onclick="reconnectChannel(6)">重連通道6</button>
        </div>
    </div>

    <!-- 引入 flv.js 庫 - 用于播放FLV格式的視頻流 -->
    <script src="./flv.js"></script>
    
    <script>
        /**
         * 視頻播放器類
         * 封裝單個通道的視頻播放功能
         */
        class VideoPlayer {
            /**
             * 構(gòu)造函數(shù)
             * @param {number} channel - 通道編號(1-6)
             */
            constructor(channel) {
                this.channel = channel;
                this.player = null;  // flv.js播放器實(shí)例
                this.loading = true;  // 加載狀態(tài)標(biāo)志
                this.videoElement = document.getElementById(`player${channel}`);
                this.loadingElement = document.getElementById(`loading${channel}`);
                // WebSocket視頻流地址
                this.rtspUrl = `ws://localhost:8888/rtsp?deviceId=16065442039&channel=${channel}`;

                this.init();
            }

            /**
             * 初始化播放器
             * 綁定事件監(jiān)聽器并開始播放
             */
            init() {
                // 綁定雙擊全屏事件
                this.videoElement.addEventListener('dblclick', () => {
                    this.fullScreen();
                });

                // 等待 flv.js 加載完成后播放視頻
                this.waitForFlvjs();

                // 頁面卸載時清理資源
                window.addEventListener('beforeunload', () => {
                    this.cleanup();
                });
            }

            /**
             * 等待flv.js庫加載完成
             * 輪詢檢查直到flv.js可用
             */
            waitForFlvjs() {
                // 檢查 flv.js 是否已加載
                if (typeof flvjs !== 'undefined') {
                    this.playVideo();
                } else {
                    // 如果還沒加載,等待 100ms 后重試
                    setTimeout(() => {
                        this.waitForFlvjs();
                    }, 100);
                }
            }
            
            /**
             * 切換全屏播放
             */
            fullScreen() {
                const video = this.videoElement;
                if (video.requestFullscreen) {
                    video.requestFullscreen();
                } else if (video.mozRequestFullScreen) {
                    video.mozRequestFullScreen();
                } else if (video.webkitRequestFullScreen) {
                    video.webkitRequestFullScreen();
                } else if (video.msRequestFullscreen) {
                    video.msRequestFullscreen();
                }
            }

            /**
             * 播放視頻
             * 創(chuàng)建flv.js播放器并開始播放FLV流
             */
            playVideo() {
                const time1 = new Date().getTime();
                this.updateChannelStatus('connecting');

                // 檢查瀏覽器是否支持FLV.js
                if (flvjs.isSupported()) {
                    const video = this.videoElement;

                    if (video) {
                        // 如果已有播放器,先清理
                        if (this.player) {
                            this.player.unload();
                            this.player.destroy();
                            this.player = null;
                            this.loading = true;
                            this.loadingElement.style.display = 'block';
                        }

                        // 創(chuàng)建新的播放器
                        this.player = flvjs.createPlayer({
                            type: 'flv',
                            isLive: true,  // 標(biāo)記為直播流
                            url: this.rtspUrl
                        });

                        // 將播放器綁定到video元素
                        this.player.attachMediaElement(video);

                        try {
                            // 加載并播放視頻
                            this.player.load();
                            this.player.play().then(() => {
                                console.log(`通道${this.channel}播放開始,耗時:`, new Date().getTime() - time1, 'ms');
                                this.loading = false;
                                this.loadingElement.style.display = 'none';
                                this.updateChannelStatus('connected');
                                this.updateGlobalStatus(`通道${this.channel}連接成功`);
                            }).catch((error) => {
                                console.error(`通道${this.channel}播放失敗:`, error);
                                this.loadingElement.textContent = '播放失敗';
                                this.updateChannelStatus('disconnected');
                                this.updateGlobalStatus(`通道${this.channel}播放失敗`);
                            });
                        } catch (error) {
                            console.error(`通道${this.channel}加載失敗:`, error);
                            this.loadingElement.textContent = '加載失敗';
                            this.updateChannelStatus('disconnected');
                            this.updateGlobalStatus(`通道${this.channel}加載失敗`);
                        }
                    }
                } else {
                    console.error('當(dāng)前瀏覽器不支持 FLV.js');
                    this.loadingElement.textContent = '當(dāng)前瀏覽器不支持 FLV 播放';
                    this.updateChannelStatus('disconnected');
                    this.updateGlobalStatus('瀏覽器不支持 FLV 播放');
                }
            }

            /**
             * 更新通道狀態(tài)指示器
             * @param {string} status - 狀態(tài)值:connected/connecting/disconnected
             */
            updateChannelStatus(status) {
                const indicator = document.getElementById(`indicator${this.channel}`);
                indicator.className = 'channel-indicator';

                switch(status) {
                    case 'connected':
                        indicator.classList.add('connected');
                        indicator.textContent = `通道${this.channel}: 已連接`;
                        break;
                    case 'connecting':
                        indicator.classList.add('connecting');
                        indicator.textContent = `通道${this.channel}: 連接中...`;
                        break;
                    case 'disconnected':
                    default:
                        indicator.textContent = `通道${this.channel}: 斷開`;
                        break;
                }
            }

            /**
             * 更新全局狀態(tài)欄
             * @param {string} message - 狀態(tài)消息
             */
            updateGlobalStatus(message) {
                const statusElement = document.getElementById('status');
                const timestamp = new Date().toLocaleTimeString();
                statusElement.textContent = `[${timestamp}] ${message}`;
            }

            /**
             * 重新播放(用于重連等場景)
             */
            replay() {
                this.playVideo();
            }

            /**
             * 清理播放器資源
             */
            cleanup() {
                if (this.player) {
                    this.player.unload();
                    this.player.destroy();
                    this.player = null;
                }
            }
        }

        // 全局變量存儲播放器實(shí)例
        let videoPlayers = {};

        /**
         * 初始化所有視頻播放器
         * 確保flv.js加載完成后創(chuàng)建六個通道的播放器
         */
        function initVideoPlayers() {
            if (typeof flvjs !== 'undefined') {
                // 初始化六個通道的播放器
                for (let i = 1; i <= 6; i++) {
                    videoPlayers[i] = new VideoPlayer(i);
                }

                console.log('六通道視頻播放器初始化完成');
                updateGlobalStatus('六通道視頻播放器初始化完成');

                // 注冊鍵盤快捷鍵
                document.addEventListener('keydown', function(event) {
                    switch(event.key.toLowerCase()) {
                        case 'r':
                            // R鍵:重連所有通道
                            reconnectAll();
                            break;
                        case '1':
                        case '2':
                        case '3':
                        case '4':
                        case '5':
                        case '6':
                            // 數(shù)字鍵1-6:重連對應(yīng)通道
                            reconnectChannel(parseInt(event.key));
                            break;
                        case 'a':
                            // A鍵:重連所有通道
                            reconnectAll();
                            break;
                    }
                });
            } else {
                // 如果 flv.js 還沒加載,等待 100ms 后重試
                setTimeout(initVideoPlayers, 100);
            }
        }

        /**
         * 重連所有通道
         * 依次延遲啟動每個通道,避免同時發(fā)起太多連接
         */
        function reconnectAll() {
            console.log('重新連接所有通道...');
            updateGlobalStatus('正在重新連接所有通道...');

            Object.values(videoPlayers).forEach((player, index) => {
                if (player) {
                    // 延遲啟動,避免同時發(fā)起太多連接
                    setTimeout(() => {
                        player.replay();
                    }, index * 500);
                }
            });
        }

        /**
         * 重連指定通道
         * @param {number} channel - 通道編號(1-6)
         */
        function reconnectChannel(channel) {
            console.log(`重新連接通道${channel}...`);
            if (videoPlayers[channel]) {
                videoPlayers[channel].replay();
                updateGlobalStatus(`正在重新連接通道${channel}...`);
            }
        }

        /**
         * 更新全局狀態(tài)
         * @param {string} message - 狀態(tài)消息
         */
        function updateGlobalStatus(message) {
            const statusElement = document.getElementById('status');
            const timestamp = new Date().toLocaleTimeString();
            statusElement.textContent = `[${timestamp}] ${message}`;
        }

        /**
         * 獲取連接統(tǒng)計(jì)信息
         * @returns {Object} 包含已連接、連接中、斷開數(shù)量的對象
         */
        function getConnectionStats() {
            const indicators = document.querySelectorAll('.channel-indicator');
            let connected = 0;
            let connecting = 0;
            let disconnected = 0;

            indicators.forEach(indicator => {
                if (indicator.classList.contains('connected')) {
                    connected++;
                } else if (indicator.classList.contains('connecting')) {
                    connecting++;
                } else {
                    disconnected++;
                }
            });

            return { connected, connecting, disconnected };
        }

        // 定期更新連接統(tǒng)計(jì)(每5秒)
        setInterval(() => {
            const stats = getConnectionStats();
            if (stats.connected > 0 || stats.connecting > 0) {
                updateGlobalStatus(`連接狀態(tài): ${stats.connected}個已連接, ${stats.connecting}個連接中, ${stats.disconnected}個斷開`);
            }
        }, 5000);

        // 頁面加載完成后初始化播放器
        document.addEventListener('DOMContentLoaded', initVideoPlayers);
    </script>
</body>

3總結(jié)

使用nodejs拉取rtsp視頻流,通過WebSocket轉(zhuǎn)發(fā)給前端,并使用flv播放,不失為一種可行方案。

到此這篇關(guān)于Nodejs拉取??低曅熊囉涗泝x攝像頭視頻流的文章就介紹到這了,更多相關(guān)Nodejs拉取??低曅熊囉涗泝x內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • node.js中的Socket.IO使用實(shí)例

    node.js中的Socket.IO使用實(shí)例

    這篇文章主要介紹了node.js中的Socket.IO使用實(shí)例,本文講解了Socket.IO的安裝、結(jié)合express來構(gòu)建服務(wù)器、基本使用方法及一個網(wǎng)絡(luò)聊天室的完整案例,需要的朋友可以參考下
    2014-11-11
  • 使用node.js搭建服務(wù)器

    使用node.js搭建服務(wù)器

    最近需要完成一個課程設(shè)計(jì),被項(xiàng)目經(jīng)理(組長)分配寫界面功能,下面小編通過本文給大家分享使用使用node搭建小型服務(wù)器的方法,需要的朋友參考下
    2017-05-05
  • 一文帶你了解Node.js有哪些架構(gòu)模式

    一文帶你了解Node.js有哪些架構(gòu)模式

    Node.js 憑借其非阻塞、事件驅(qū)動的架構(gòu),已成為構(gòu)建各種應(yīng)用程序的流行選擇,使用 Node.js 進(jìn)行開發(fā)時,選擇正確的架構(gòu)模式來滿足項(xiàng)目需求至關(guān)重要,在本文中,我們將探討幾種 Node.js 架構(gòu)模式并提供示例來說明它們的用法,需要的朋友可以參考下
    2023-09-09
  • Nodejs中的計(jì)時器(setTimeout?setIntervals?etImmediate)使用案例解析

    Nodejs中的計(jì)時器(setTimeout?setIntervals?etImmediate)使用案例解析

    這篇文章主要介紹了Nodejs中的計(jì)時器(setTimeout?setIntervals?etImmediate)使用案例解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-06-06
  • 最新評論

    米脂县| 田阳县| 当阳市| 砀山县| 揭东县| 宜章县| 德化县| 仙居县| 兴城市| 金阳县| 武义县| 连南| 九寨沟县| 木里| 遂宁市| 修文县| 莱州市| 札达县| 闸北区| 楚雄市| 东辽县| 万载县| 陈巴尔虎旗| 拉孜县| 杭锦后旗| 班玛县| 镇原县| 繁峙县| 阿拉善左旗| 砀山县| 桐庐县| 商都县| 安平县| 同心县| 开阳县| 龙山县| 孟津县| 定陶县| 磐安县| 乡宁县| 呼图壁县|