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

基于SpringBoot+FFmpeg+ZLMediaKit實(shí)現(xiàn)本地視頻推流

 更新時間:2025年11月03日 09:07:11   作者:皮皮林551  
文章介紹了如何在服務(wù)器上安裝和配置ZLMediaKit和FFmpeg,以及如何使用SpringBoot實(shí)現(xiàn)視頻推流和播放功能,包括環(huán)境準(zhǔn)備、后端實(shí)現(xiàn)和使用說明,涵蓋了推流和播放的完整流程,需要的朋友可以參考下

1. 環(huán)境準(zhǔn)備

1.1 ZLMediaKit 安裝配置

下載安裝

# 拉取鏡像
docker pull zlmediakit/zlmediakit:master

# 啟動
docker run -d \
  --name zlm-server \
  -p 1935:1935 \
  -p 8099:80 \
  -p 8554:554 \
  -p 10000:10000 \
  -p 10000:10000/udp \
  -p 8000:8000/udp \
  -v /docker-volumes/zlmediakit/conf/config.ini:/opt/media/conf/config.ini \
  zlmediakit/zlmediakit:master

配置文件 (config.ini)

[hls]
broadcastRecordTs=0
deleteDelaySec=300    # 推流的視頻保存多久(5分鐘)
fileBufSize=65536
filePath=./www    # 保存路徑
segDur=2    # 單個.ts 切片時長(秒)。
segNum=1000  # 直播時.m3u8 里最多同時保留多少個切片。
segRetain=9999    # 磁盤上實(shí)際保留多少個歷史切片

啟動服務(wù)

# 查看啟動狀態(tài)
docker logs -f zlm-server

1.2 FFmpeg 安裝

# 下載路徑
https://www.gyan.dev/ffmpeg/builds/

這兩個都可以選

配置環(huán)境變量

C:\ffmpeg\ffmpeg-7.0.2-essentials_build\bin

找到 bin 目錄,將其配到 path 環(huán)境變量中。

出來版本就成功了。

2. Spring Boot 后端實(shí)現(xiàn)

2.1 添加依賴

<dependencies>
    
    <!-- 進(jìn)程管理 -->
    <dependency>
        <groupId>org.apache.commons</groupId>
        <artifactId>commons-exec</artifactId>
        <version>1.3</version>
    </dependency>
    
</dependencies>

2.2 推流配置類

package com.lyk.plugflow.config;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

@Data
@Component
@ConfigurationProperties(prefix = "stream")
public class StreamConfig {

    /**
     * ZLMediaKit服務(wù)地址
     */
    private String zlmHost;

    /**
     * RTMP推流端口
     */
    private Integer rtmpPort;

    /**
     * HTTP-FLV拉流端口
     */
    private Integer httpPort;

    /**
     * FFmpeg可執(zhí)行文件路徑
     */
    private String ffmpegPath;

    /**
     * 視頻存儲路徑
     */
    private String videoPath;

}

2.3 推流服務(wù)類

package com.lyk.plugflow.service;

import com.lyk.plugflow.config.StreamConfig;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.exec.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

@Slf4j
@Service
public class StreamService {

    @Autowired
    private StreamConfig streamConfig;

    // 存儲推流進(jìn)程
    private final Map<String, DefaultExecutor> streamProcesses = new ConcurrentHashMap<>();

    // 添加手動停止標(biāo)記
    private final Map<String, Boolean> manualStopFlags = new ConcurrentHashMap<>();

    /**
     * 開始推流
     */
    public boolean startStream(String videoPath, String streamKey) {
        try {
            // 檢查視頻文件是否存在
            File videoFile = new File(videoPath);
            if (!videoFile.exists()) {
                log.error("視頻文件不存在: {}", videoPath);
                returnfalse;
            }

            // 構(gòu)建RTMP推流地址
            String rtmpUrl = String.format("rtmp://%s:%d/live/%s",
                    streamConfig.getZlmHost(), streamConfig.getRtmpPort(), streamKey);

            // 構(gòu)建FFmpeg命令
            CommandLine cmdLine = getCommandLine(videoPath, rtmpUrl);

            // 創(chuàng)建執(zhí)行器
            DefaultExecutor executor = new DefaultExecutor();
            executor.setExitValue(0);

            // 設(shè)置watchdog用于進(jìn)程管理
            ExecuteWatchdog watchdog = new ExecuteWatchdog(ExecuteWatchdog.INFINITE_TIMEOUT);
            executor.setWatchdog(watchdog);

            // 設(shè)置輸出流
            ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
            PumpStreamHandler streamHandler = new PumpStreamHandler(outputStream);
            executor.setStreamHandler(streamHandler);

            // 異步執(zhí)行
            executor.execute(cmdLine, new ExecuteResultHandler() {
                @Override
                public void onProcessComplete(int exitValue) {
                    log.info("推流完成, streamKey: {}, exitValue: {}", streamKey, exitValue);
                    streamProcesses.remove(streamKey);
                }

                @Override
                public void onProcessFailed(ExecuteException e) {
                    boolean isManualStop = manualStopFlags.remove(streamKey);
                    if (isManualStop) {
                        log.info("推流已手動停止, streamKey: {}", streamKey);
                    } else {
                        log.error("推流失敗, streamKey: {}, error: {}", streamKey, e.getMessage());
                    }
                    streamProcesses.remove(streamKey);
                }
            });

            // 保存進(jìn)程引用
            streamProcesses.put(streamKey, executor);

            log.info("開始推流, streamKey: {}, rtmpUrl: {}", streamKey, rtmpUrl);
            returntrue;

        } catch (Exception e) {
            log.error("推流啟動失敗", e);
            returnfalse;
        }
    }

    private CommandLine getCommandLine(String videoPath, String rtmpUrl) {
        CommandLine cmdLine = new CommandLine(streamConfig.getFfmpegPath());
        cmdLine.addArgument("-re"); // 按原始幀率讀取
        cmdLine.addArgument("-i");
        cmdLine.addArgument(videoPath);
        cmdLine.addArgument("-c:v");
        cmdLine.addArgument("libx264"); // 視頻編碼
        cmdLine.addArgument("-c:a");
        cmdLine.addArgument("aac"); // 音頻編碼
        cmdLine.addArgument("-f");
        cmdLine.addArgument("flv"); // 輸出格式
        cmdLine.addArgument("-flvflags");
        cmdLine.addArgument("no_duration_filesize");
        cmdLine.addArgument(rtmpUrl);
        return cmdLine;
    }

    /**
     * 停止推流
     */
    public boolean stopStream(String streamKey) {
        try {
            DefaultExecutor executor = streamProcesses.get(streamKey);
            if (executor != null) {
                // 設(shè)置手動停止標(biāo)記
                manualStopFlags.put(streamKey, true);

                ExecuteWatchdog watchdog = executor.getWatchdog();
                if (watchdog != null) {
                    watchdog.destroyProcess();
                } else {
                    log.warn("進(jìn)程沒有watchdog,無法強(qiáng)制終止, streamKey: {}", streamKey);
                }
                streamProcesses.remove(streamKey);
                log.info("停止推流成功, streamKey: {}", streamKey);
                returntrue;
            }
            returnfalse;
        } catch (Exception e) {
            log.error("停止推流失敗", e);
            returnfalse;
        }
    }

    /**
     * 獲取拉流地址
     */
    public String getPlayUrl(String streamKey, String protocol) {
        return switch (protocol.toLowerCase()) {
            case"flv" -> String.format("http://%s:%d/live/%s.live.flv",
                    streamConfig.getZlmHost(), streamConfig.getHttpPort(), streamKey);
            case"hls" -> String.format("http://%s:%d/live/%s/hls.m3u8",
                    streamConfig.getZlmHost(), streamConfig.getHttpPort(), streamKey);
            default -> null;
        };
    }

    /**
     * 檢查推流狀態(tài)
     */
    public boolean isStreaming(String streamKey) {
        return streamProcesses.containsKey(streamKey);
    }
}

2.4 配置文件

stream:
  zlm-host: 192.168.159.129
  rtmp-port: 1935
  http-port: 8099
  ffmpeg-path: ffmpeg
  video-path: \videos\

# 文件上傳配置
spring:
  servlet:
    multipart:
      max-file-size: 1GB
      max-request-size: 1GB

3. 使用說明

3.1 推流流程

  • • 啟動 ZLMediaKit 服務(wù)
  • • 上傳視頻文件到服務(wù)器
  • • 調(diào)用推流接口,指定視頻路徑和推流密鑰
  • • Spring Boot 執(zhí)行 FFmpeg 命令推流到 ZLMediaKit

3.2 播放流程

  • • 獲取推流地址(HTTP-FLV 或 HLS)
  • • 支持實(shí)時播放和回放
ffmpeg -re -i "C:\Users\lyk19\Videos\8月9日.mp4" -c:v libx264 -preset ultrafast -tune zerolatency -c:a aac -ar 44100 -b:a 128k -f flv rtmp://192.168.159.129:1935/live/stream
  • • 前端播放
<!DOCTYPE html>
<html lang="zh-CN">

  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>FLV直播播放器</title>
    <style>
      body {
        margin: 0;
        padding: 20px;
        font-family: Arial, sans-serif;
        background-color: #f0f0f0;
      }

      .player-container {
        max-width: 800px;
        margin: 0 auto;
        background: white;
        border-radius: 8px;
        padding: 20px;
        box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
      }

      #videoElement {
        width: 100%;
        height: 450px;
        background-color: #000;
        border-radius: 4px;
      }

      .controls {
        margin-top: 15px;
        text-align: center;
      }

      button {
        padding: 10px 20px;
        margin: 0 5px;
        border: none;
        border-radius: 4px;
        background-color: #007bff;
        color: white;
        cursor: pointer;
        font-size: 14px;
      }

      button:hover {
        background-color: #0056b3;
      }

      button:disabled {
        background-color: #ccc;
        cursor: not-allowed;
      }

      .status {
        margin-top: 10px;
        padding: 10px;
        border-radius: 4px;
        text-align: center;
      }

      .status.success {
        background-color: #d4edda;
        color: #155724;
      }

      .status.error {
        background-color: #f8d7da;
        color: #721c24;
      }

      .status.info {
        background-color: #d1ecf1;
        color: #0c5460;
      }
    </style>
  </head>

  <body>
    <div class="player-container">
      <h1>FLV直播播放器</h1>
      <video id="videoElement" controls muted>
        您的瀏覽器不支持視頻播放
      </video>

      <div class="controls">
        <button id="playBtn">播放</button>
        <button id="pauseBtn" disabled>暫停</button>
        <button id="stopBtn" disabled>停止</button>
        <button id="muteBtn">靜音</button>
      </div>

      <div id="status" class="status info">
        準(zhǔn)備就緒,點(diǎn)擊播放開始觀看直播
      </div>
    </div>

    <!-- 使用flv.js庫 -->
    <script src="https://cdn.jsdelivr.net/npm/flv.js@1.6.2/dist/flv.min.js"></script>

    <script>
      let flvPlayer = null;
      const videoElement = document.getElementById('videoElement');
      const playBtn = document.getElementById('playBtn');
      const pauseBtn = document.getElementById('pauseBtn');
      const stopBtn = document.getElementById('stopBtn');
      const muteBtn = document.getElementById('muteBtn');
      const statusDiv = document.getElementById('status');

      // 你的流地址
      const streamUrl = 'http://192.168.159.129:8099/live/stream.live.flv';

      function updateStatus(message, type) {
        statusDiv.textContent = message;
        statusDiv.className = `status ${type}`;
          console.log(`[${type.toUpperCase()}] ${message}`);
        }

        function updateButtons(playEnabled, pauseEnabled, stopEnabled) {
            playBtn.disabled = !playEnabled;
            pauseBtn.disabled = !pauseEnabled;
            stopBtn.disabled = !stopEnabled;
        }

        // 檢查瀏覽器支持
        if (!flvjs.isSupported()) {
            updateStatus('您的瀏覽器不支持FLV播放,請使用Chrome、Firefox或Edge瀏覽器', 'error');
            playBtn.disabled = true;
        }

        // 播放功能
        playBtn.addEventListener('click', function () {
            try {
                if (flvPlayer) {
                    flvPlayer.destroy();
                }

                // 創(chuàng)建FLV播放器
                flvPlayer = flvjs.createPlayer({
                    type: 'flv',
                    url: streamUrl,
                    isLive: true
                }, {
                    enableWorker: false,
                    lazyLoad: true,
                    lazyLoadMaxDuration: 3 * 60,
                    deferLoadAfterSourceOpen: false,
                    autoCleanupSourceBuffer: true,
                    enableStashBuffer: false
                });

                flvPlayer.attachMediaElement(videoElement);
                flvPlayer.load();

                // 監(jiān)聽事件
                flvPlayer.on(flvjs.Events.ERROR, function (errorType, errorDetail, errorInfo) {
                    console.error('FLV播放器錯誤:', errorType, errorDetail, errorInfo);
                    updateStatus(`播放錯誤: ${errorDetail}`, 'error');
                });

                flvPlayer.on(flvjs.Events.LOADING_COMPLETE, function () {
                    updateStatus('流加載完成', 'success');
                });

                flvPlayer.on(flvjs.Events.RECOVERED_EARLY_EOF, function () {
                    updateStatus('從早期EOF恢復(fù)', 'info');
                });

                // 開始播放
                videoElement.play().then(() => {
                    updateStatus('正在播放直播流', 'success');
                    updateButtons(false, true, true);
                }).catch(error => {
                    console.error('播放失敗:', error);
                    updateStatus('播放失敗: ' + error.message, 'error');
                });

            } catch (error) {
                console.error('創(chuàng)建播放器失敗:', error);
                updateStatus('創(chuàng)建播放器失敗: ' + error.message, 'error');
            }
        });

        // 暫停功能
        pauseBtn.addEventListener('click', function () {
            if (videoElement && !videoElement.paused) {
                videoElement.pause();
                updateStatus('播放已暫停', 'info');
                updateButtons(true, false, true);
            }
        });

        // 停止功能
        stopBtn.addEventListener('click', function () {
            if (flvPlayer) {
                flvPlayer.pause();
                flvPlayer.unload();
                flvPlayer.destroy();
                flvPlayer = null;
            }

            videoElement.src = '';
            videoElement.load();

            updateStatus('播放已停止', 'info');
            updateButtons(true, false, false);
        });

        // 靜音功能
        muteBtn.addEventListener('click', function () {
            videoElement.muted = !videoElement.muted;
            muteBtn.textContent = videoElement.muted ? '取消靜音' : '靜音';
            updateStatus(videoElement.muted ? '已靜音' : '已取消靜音', 'info');
        });

        // 視頻事件監(jiān)聽
        videoElement.addEventListener('loadstart', function () {
            updateStatus('開始加載視頻流...', 'info');
        });

        videoElement.addEventListener('canplay', function () {
            updateStatus('視頻流已準(zhǔn)備就緒', 'success');
        });

        videoElement.addEventListener('playing', function () {
            updateStatus('正在播放直播流', 'success');
            updateButtons(false, true, true);
        });

        videoElement.addEventListener('pause', function () {
            updateStatus('播放已暫停', 'info');
            updateButtons(true, false, true);
        });

        videoElement.addEventListener('error', function (e) {
            updateStatus('視頻播放出錯', 'error');
            updateButtons(true, false, false);
        });
    </script>
</body>

</html>

以上就是基于SpringBoot+FFmpeg+ZLMediaKit實(shí)現(xiàn)本地視頻推流的詳細(xì)內(nèi)容,更多關(guān)于SpringBoot FFmpeg本地視頻推流的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • java實(shí)現(xiàn)點(diǎn)贊功能

    java實(shí)現(xiàn)點(diǎn)贊功能

    這篇文章主要為大家詳細(xì)介紹了java實(shí)現(xiàn)點(diǎn)贊功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2022-08-08
  • ArrayList源碼探秘之Java動態(tài)數(shù)組的實(shí)現(xiàn)

    ArrayList源碼探秘之Java動態(tài)數(shù)組的實(shí)現(xiàn)

    這篇文章將帶大家從ArrayList源碼來探秘一下Java動態(tài)數(shù)組的實(shí)現(xiàn),文中的示例代碼講解詳細(xì),對我們深入了解JavaScript有一定的幫助,需要的可以參考一下
    2023-08-08
  • SpringBoot中@FeignClient 注解的作用

    SpringBoot中@FeignClient 注解的作用

    Feign可以幫助我們定義和實(shí)現(xiàn)服務(wù)之間的 RESTful 接口,使得服務(wù)之間的調(diào)用更加方便和可靠,本文主要介紹了SpringBoot中@FeignClient 注解的作用,感興趣的可以了解一下
    2024-06-06
  • Java使用Gateway自定義負(fù)載均衡過濾器

    Java使用Gateway自定義負(fù)載均衡過濾器

    這篇文章主要介紹了Java使用Gateway自定義負(fù)載均衡過濾器,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-07-07
  • spring cloud實(shí)現(xiàn)Eureka注冊中心的HA的方法

    spring cloud實(shí)現(xiàn)Eureka注冊中心的HA的方法

    本篇文章主要介紹了spring cloud實(shí)現(xiàn)Eureka注冊中心的HA的方法,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2018-01-01
  • Java實(shí)現(xiàn)快速并查集

    Java實(shí)現(xiàn)快速并查集

    這篇文章主要為大家詳細(xì)介紹了Java實(shí)現(xiàn)快速并查集,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2020-07-07
  • springBoot接入阿里云oss的實(shí)現(xiàn)步驟

    springBoot接入阿里云oss的實(shí)現(xiàn)步驟

    這篇文章主要介紹了springBoot接入阿里云oss的實(shí)現(xiàn)步驟,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2021-01-01
  • Java并發(fā)編程-volatile可見性詳解

    Java并發(fā)編程-volatile可見性詳解

    這篇文章主要介紹了Java并發(fā)編程-volatile可見性詳解,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-03-03
  • JAVA  字符串加密、密碼加密實(shí)現(xiàn)方法

    JAVA 字符串加密、密碼加密實(shí)現(xiàn)方法

    這篇文章主要介紹了JAVA 字符串加密、密碼加密實(shí)現(xiàn)方法的相關(guān)資料,需要的朋友可以參考下
    2016-10-10
  • 關(guān)于MyBatis中映射對象關(guān)系的舉例

    關(guān)于MyBatis中映射對象關(guān)系的舉例

    這篇文章主要介紹了關(guān)于MyBatis中映射對象關(guān)系的舉例,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-06-06

最新評論

临朐县| 沅江市| 高阳县| 大竹县| 通化县| 罗源县| 远安县| 新竹县| 阳谷县| 栾川县| 米易县| 镇康县| 南宫市| 科尔| 讷河市| 普陀区| 安岳县| 柯坪县| 无锡市| 左云县| 南投县| 商南县| 甘谷县| 葵青区| 攀枝花市| 图们市| 襄垣县| 万盛区| 田阳县| 利辛县| 静乐县| 安多县| 湘阴县| 丽水市| 西充县| 徐汇区| 平罗县| 垦利县| 黄石市| 图木舒克市| 华容县|