Hls.js播放m3u8视频 & DPlayer视频播放器(easypan) & MSE简介

文章目录

    • 学习链接
    • hls.js播放m3u8视频
      • 效果
      • 代码
        • 前端代码
          • 安装hls.js
          • App.vue
        • 后台代码
          • 准备文件
            • mp4文件切片java实现
          • TsController
          • TsService
    • DPlayer播放m3u8视频
      • 效果
        • 扩展内容:MSE(Media Source Extensions)介绍
          • 1 概述
          • 2 why MSE
      • 代码
        • 安装依赖
        • App.vue

学习链接

vue3项目实战 仿百度网盘

hls的github文档中的api详细说明
hlsjs-dev官方的demo演示
hls播放m3u8 添加header请求头,在请求ts的url上添加参数

Vue 之 视频流 - Hls.js

hls.js如何播放m3u8文件(实例)

网页前端video播放m3u8(HLS)
ffmpeg 视频ts切片生成m3u8

mp4视频分片生成m3u8流文件并加密,
Java使用FFmpeg(自定义cmd)系列之官方API获取视频/音频信息(URL方式)
Java使用FFmpeg(自定义cmd)系列之MP4 转码 HLS m3u8 AES128 加密(源码已保存在gitee上)
这个加密后面可以试一下,因为分片完,前端随便拿一个分片,这个分片单独保存下来使用vlc播放器也是可以播放的
Spring boot视频播放(解决MP4大文件无法播放),整合ffmpeg,用m3u8切片播放。(包含加密方式)

Java使用ffmpeg进行视频格式转换、音视频合并、播放、截图


DPlayer官网文档
vue使用dplayer 播放m3u8格式的视频——播放m3u8格式视频(三)
vue使用vue-dplayer播放m3u8格式的视频——播放m3u8格式视频(二)
vue使用原生videojs 播放m3u8格式的视频——播放m3u8格式视频(一)

MSE(Media Source Extensions)介绍

hls.js播放m3u8视频

HLS (HTTP Live Streaming)是Apple公司研发的流媒体传输技术,包括一个m3u8的索引文件、多个ts分片文件和key加密串文件。这项技术主要应用于点播和直播领域。

效果

  • 发送的请求有2个相同的是因为第一个是预检请求
  • 请求中可以携带自定义请求头,因此在后台可以校验这个请求头,从而验证用户,才允许播放,但不能防止用户获取视频数据合并下载
  • api可以参考hls的github上的地址

代码

前端代码

安装hls.js
npm install --save hls.js
App.vue
<template>
  <div style="text-align: center;">
    
    <video id="video" ref="videoRef" width="800" height="400" controls>video>
    <p>
      
      <button @click="playVideo">播放视频button>  
      <button @click="pauseVideo">暂停视频button>
    p>
  div>
template>

<script setup>
import { ref, reactive, onMounted } from 'vue'
import Hls from 'hls.js';

const videoRef = ref()

var videoSrc = 'http://127.0.0.1:8085/ts/video/playM3U8video/YsIlFSjnlh'; // 用于获取m3u8文件的地址, 后端处理跨域

function playVideo() {
  videoRef.value.play()
}

function pauseVideo() {
  videoRef.value.pause()
}

function loadVideo() {
  if (Hls.isSupported()) {

    // 组件挂载完成, 才能获取到video元素
    var video = document.getElementById('video');

    // 可参考: 
    // hls的github文档中的api:(https://github.com/video-dev/hls.js/blob/master/docs/API.md)
    // hls.js如何播放m3u8文件(实例)?(https://blog.csdn.net/ffffffff8/article/details/129314268)
    // Vue 之 视频流 - Hls.js(https://blog.csdn.net/a15297701931/article/details/115478652)
    let config = {
      xhrSetup: function (xhr, url) {
        xhr.withCredentials = true; // 会携带cookie
        xhr.setRequestHeader('token',"my-token")
      },
    }
    var hls = new Hls(config);

    hls.on(Hls.Events.MEDIA_ATTACHED, function () {
      console.log('video and hls.js are now bound together !');
    });

    hls.on(Hls.Events.MANIFEST_PARSED, function (event, data) {
      console.log('manifest loaded, found ' + data.levels.length + ' quality level');
      // video.play() // 不能在这里就播放, 需要用于与dom元素有交互才可以播放, 否则浏览器会报错
    });

    hls.loadSource(videoSrc);
    // bind them together
    hls.attachMedia(video);

  }
}

onMounted(() => {
  loadVideo()
})

script>

<style scoped>
p {
  margin: 0;
}
style>

后台代码

准备文件

Hls.js播放m3u8视频 & DPlayer视频播放器(easypan) & MSE简介_第1张图片
其中index.m3u8文件

#EXTM3U
#EXT-X-VERSION:3
#EXT-X-MEDIA-SEQUENCE:0
#EXT-X-ALLOW-CACHE:YES
#EXT-X-TARGETDURATION:30
#EXTINF:30.000000,
YsIlFSjnlh_0000.ts
#EXTINF:30.000000,
YsIlFSjnlh_0001.ts
#EXTINF:30.000000,
YsIlFSjnlh_0002.ts
#EXTINF:30.000000,
YsIlFSjnlh_0003.ts
#EXTINF:30.000000,
YsIlFSjnlh_0004.ts
#EXTINF:30.000000,
YsIlFSjnlh_0005.ts
#EXTINF:30.000000,
YsIlFSjnlh_0006.ts
#EXTINF:30.000000,
YsIlFSjnlh_0007.ts
#EXTINF:30.000000,
YsIlFSjnlh_0008.ts
#EXTINF:30.000000,
YsIlFSjnlh_0009.ts
#EXTINF:30.000000,
YsIlFSjnlh_0010.ts
#EXTINF:30.000000,
YsIlFSjnlh_0011.ts
#EXTINF:30.000000,
YsIlFSjnlh_0012.ts
#EXTINF:30.000000,
YsIlFSjnlh_0013.ts
#EXTINF:30.000000,
YsIlFSjnlh_0014.ts
#EXTINF:30.000000,
YsIlFSjnlh_0015.ts
#EXTINF:30.000000,
YsIlFSjnlh_0016.ts
#EXTINF:30.000000,
YsIlFSjnlh_0017.ts
#EXTINF:5.400000,
YsIlFSjnlh_0018.ts
#EXT-X-ENDLIST

mp4文件切片java实现

摘自 B站程序员老罗网盘项目代码
cutFile4Video

private void cutFile4Video(String fileId, String videoFilePath) {

    // videoFilePath 如: D:/document/easypan/easypan-java/file//202305/{userId}{fileId}.mp4

    //创建同名切片目录,
    // tsFolder 与 视频文件在同一目录下,并且tsFolder是以视频真实文件名(不带后缀)作为文件夹名
    // tsFolder 如: D:/document/easypan/easypan-java/file//202305/{userId}{fileId} - 这是个文件夹
    File tsFolder = new File(videoFilePath.substring(0, videoFilePath.lastIndexOf(".")));

    if (!tsFolder.exists()) {
        tsFolder.mkdirs();
    }

    // 这里也尝试过直接对mp4文件直接转为.m3u8 + ts分段视频文件,但转的速度比较慢,不知道是不是参数设置的原因,ffmpeg不是很清楚它的命令
    // 命令如:ffmpeg -i ./jvm.mp4 -c:v h264 -flags +cgop -g 30 -hls_time 60 -hls_list_size 0 -hls_segment_filename index%3d.ts index.m3u8
    // 这个命令,会将执行命令时所在的当前目录下的jvm.mp4文件按照视频的每60s切割成一个ts文件 和 .m3u8索引文件 到 当前目录

    // 这里先转为ts文件,然后,再切割这个ts文件,生成.m3u8索引文件(速度比上面快)

    // 1. 将 整个视频文件 转成ts文件:index.ts
    //    ffmpeg -y -i {mp4视频文件路径}  -vcodec copy -acodec copy -vbsf h264_mp4toannexb {放入到哪一个文件位置}
    //    如:ffmpeg -y -i D:/test/jvm.mp4  -vcodec copy -acodec copy -vbsf h264_mp4toannexb D:/test/jvm/index.ts
    //    这个命令会将 第一个 所指向的mp4视频文件 转成 ts文件 存储到 D:/test/jvm/index.ts,注意第二个文件路径在执行命令前必须要存在

    // 2. 将index.ts文件进行分片
    //    ffmpeg -i {index文件的文件路径} -c copy -map 0 -f segment -segment_list {要生成的m3u8索引文件路径} -segment_time 30 {生成的ts切片路径}/{文件名前面部分}_%4d.ts
    //    生成的ts文件路径%%4d,写了2个百分号是为了防止jdk的MessageFormat处理
    //    如:ffmpeg -i D:/test/jvm/index.ts -c copy -map 0 -f segment -segment_list D:/test/jvm/index.m3u8 -segment_time 30 D:/test/jvm/jjvvmm_%4d.ts
    //    这个 命令会将 第一个 所指向的ts视频文件 按照 每30s 切割成一个小的ts视频文件,放入到指定的文件夹中,并且有指定格式的文件名(占4位,递增),并且会生成一个m3u8的索引文件

    // mp4转ts文件的ffmpeg命令
    // 如:ffmpeg -y -i D:/document/easypan/easypan-java/file//202305/{userId}{fileId}.mp4  -vcodec copy -acodec copy -vbsf h264_mp4toannexb D:/document/easypan/easypan-java/file//202305/{userId}{fileId}/index.ts
    final String CMD_TRANSFER_2TS = "ffmpeg -y -i %s  -vcodec copy -acodec copy -vbsf h264_mp4toannexb %s";

    // ts文件拆分成小的ts文件 和 生成一个.m3u8文件
    // 如:ffmpeg -i D:/document/easypan/easypan-java/file//202305/{userId}{fileId}/index.ts -c copy -map 0 -f segment -segment_list D:/document/easypan/easypan-java/file//202305/{userId}{fileId}/index.m3u8 -segment_time 30 D:/document/easypan/easypan-java/file//202305/{userId}{fileId}/{fileId}_%%4d.ts
    final String CMD_CUT_TS = "ffmpeg -i %s -c copy -map 0 -f segment -segment_list %s -segment_time 30 %s/%s_%%4d.ts";

    // 转成的ts文件所在路径:D:/document/easypan/easypan-java/file//202305/{userId}{fileId}/index.ts
    String tsPath = tsFolder + "/" + Constants.TS_NAME;

    String cmd = String.format(CMD_TRANSFER_2TS, videoFilePath, tsPath);

    ProcessUtils.executeCommand(cmd, false);

    //生成索引文件.m3u8 和切片.ts
    cmd = String.format(CMD_CUT_TS, tsPath, tsFolder.getPath() + "/" + Constants.M3U8_NAME, tsFolder.getPath(), fileId);

    ProcessUtils.executeCommand(cmd, false);

    //删除index.ts
    new File(tsPath).delete();
}

ProcessUtils 工具类

package com.easypan.utils;

import com.easypan.exception.BusinessException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;

public class ProcessUtils {
    private static final Logger logger = LoggerFactory.getLogger(ProcessUtils.class);

    public static String executeCommand(String cmd, Boolean outprintLog) throws BusinessException {
        if (StringTools.isEmpty(cmd)) {
            logger.error("--- 指令执行失败,因为要执行的FFmpeg指令为空! ---");
            return null;
        }

        Runtime runtime = Runtime.getRuntime();
        Process process = null;
        try {
            process = Runtime.getRuntime().exec(cmd);
            // 执行ffmpeg指令
            // 取出输出流和错误流的信息
            // 注意:必须要取出ffmpeg在执行命令过程中产生的输出信息,如果不取的话当输出流信息填满jvm存储输出留信息的缓冲区时,线程就回阻塞住
            PrintStream errorStream = new PrintStream(process.getErrorStream());
            PrintStream inputStream = new PrintStream(process.getInputStream());
            errorStream.start();
            inputStream.start();
            // 等待ffmpeg命令执行完
            process.waitFor();
            // 获取执行结果字符串
            String result = errorStream.stringBuffer.append(inputStream.stringBuffer + "\n").toString();
            // 输出执行的命令信息

            if (outprintLog) {
                logger.info("执行命令:{},已执行完毕,执行结果:{}", cmd, result);
            } else {
                logger.info("执行命令:{},已执行完毕", cmd);
            }
            return result;
        } catch (Exception e) {
            // logger.error("执行命令失败:{} ", e.getMessage());
            e.printStackTrace();
            throw new BusinessException("视频转换失败");
        } finally {
            if (null != process) {
                ProcessKiller ffmpegKiller = new ProcessKiller(process);
                runtime.addShutdownHook(ffmpegKiller);
            }
        }
    }

    /**
     * 在程序退出前结束已有的FFmpeg进程
     */
    private static class ProcessKiller extends Thread {
        private Process process;

        public ProcessKiller(Process process) {
            this.process = process;
        }

        @Override
        public void run() {
            this.process.destroy();
        }
    }


    /**
     * 用于取出ffmpeg线程执行过程中产生的各种输出和错误流的信息
     */
    static class PrintStream extends Thread {
        InputStream inputStream = null;
        BufferedReader bufferedReader = null;
        StringBuffer stringBuffer = new StringBuffer();

        public PrintStream(InputStream inputStream) {
            this.inputStream = inputStream;
        }

        @Override
        public void run() {
            try {
                if (null == inputStream) {
                    return;
                }
                bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
                String line = null;
                while ((line = bufferedReader.readLine()) != null) {
                    stringBuffer.append(line);
                }
            } catch (Exception e) {
                logger.error("读取输入流出错了!错误信息:" + e.getMessage());
            } finally {
                try {
                    if (null != bufferedReader) {
                        bufferedReader.close();
                    }
                    if (null != inputStream) {
                        inputStream.close();
                    }
                } catch (IOException e) {
                    logger.error("调用PrintStream读取输出流后,关闭流时出错!");
                }
            }
        }
    }
}

TsController
@RestController
@RequestMapping("ts/video")
public class TsController {

    @Autowired
    private TsService tsService;

    @GetMapping("playM3U8video/{fileId}")
    public void playM3U8video(@PathVariable("fileId") String fileId) {
        tsService.playM3U8video(fileId);
    }
}
TsService
@Slf4j
@Service
public class TsService {

    private static final String VIDEO_ROOT_PATH = "d:\\test";

    @Autowired
    @SuppressWarnings("all")
    private HttpServletResponse response;

    // 可参考:ffmpeg 视频ts切片生成m3u8
    // 网页前端video播放m3u8(HLS) https://www.cnblogs.com/fieldtianye/p/13166957.html
    // https://www.cnblogs.com/fieldtianye/category/1789297.html
    public void playM3U8video(String fileId) {

        FileSystemResourceLoader fileResourceLoader = new FileSystemResourceLoader();

        // 先获取m3u8文件
        if (!fileId.endsWith("ts")) {

            Resource resource = fileResourceLoader.getResource(VIDEO_ROOT_PATH + File.separator + fileId + "/index.m3u8");
            try {
                StreamUtils.copy(resource.getInputStream(), response.getOutputStream());
            } catch (IOException e) {
                log.error("写出m3u8文件流失败:{}",e);
            }

        } else {
            // 请求以ts结尾,则获取ts分段文件
            Resource resource = fileResourceLoader.getResource(VIDEO_ROOT_PATH + File.separator + fileId.split("_")[0] + File.separator +fileId);
            try {
                StreamUtils.copy(resource.getInputStream(), response.getOutputStream());
            } catch (IOException e) {
                log.error("写出m3u8文件流失败:{}",e);
            }
        }

    }
}

DPlayer播放m3u8视频

效果

  • 效果与直接使用hls效果一致,但是优点是它还支持其它的MSE(Media Source Extensions)

  • 携带了自定义的请求头

Hls.js播放m3u8视频 & DPlayer视频播放器(easypan) & MSE简介_第2张图片

扩展内容:MSE(Media Source Extensions)介绍

摘自:MSE(Media Source Extensions)介绍

本文介绍 MSE(Media Source Extensions) 的相关知识。

1 概述

MSE(Media Source Extensions),即媒体源扩展,可以理解为一种API,其提供了实现无插件(js插件外的插件)且基于 Web 的流媒体的功能。通过 MSE,媒体串流能够通过 JavaScript 创建,并且可以使用 HTML5 的 标签进行播放。

2 why MSE

以前,用户在通过Web浏览器浏览网页内容,尤其是视频内容时,需要使用诸如 Adobe Flash或是微软的 Silverlight 等类似的插件,才能播放视音频内容,这些插件对于Web浏览器来说,扮演着媒体播放器角色。但是,在浏览器中使用插件是不便捷并且不安全的(不法分子会在插件上动手脚),因此,最新的HTML5标准中,定义了一系列新的元素来避免使用插件,其中就包含了大名鼎鼎的

有了,但是 HTML5 的

我们可以把

从另外一个角度来说,通过引入 MSE,HTML5 的。如此一来,我们就可以通过 (具备MSE功能的)JS,把一些 标签原本不支持的视频流格式,转化为其支持的格式(如 H.264 的 mp4)。B站开源的 flv.js 就是此技术的一个典型应用场景,B站的HTML5播放器,通过使用 MSE 技术,将FLV源用 JS(flv.js) 实时转码成 HTML5 支持的视频流编码格式,提供给 HTML5 播放器播放。

代码

后端代码不变,修改前端代码即可。

安装依赖

npm i dplayer -S // 视频播放器插件
npm i hls.js -S  // 播放hls流插件

App.vue

<template>
  
  <div id="dplayer" style="width: 400px;">div>
  <p>
    
    <button @click="playVideo">播放视频button>  
    <button @click="pauseVideo">暂停视频button>
  p>
template>

<script setup>

import { ref, reactive, onMounted } from 'vue'

import Hls from 'hls.js';
import DPlayer from 'dplayer';

console.log(DPlayer);

let dpInstance = null

function playVideo() {
  console.log(dpInstance.play);
  dpInstance.play()
}

function pauseVideo() {
  dpInstance.pause()
}

onMounted(() => {

  // 另一种方式,使用 customType
  const dp = new DPlayer({
    container: document.getElementById('dplayer'),
    video: {
      url: 'http://127.0.0.1:8085/ts/video/playM3U8video/YsIlFSjnlh',
      type: 'customHls',
      customType: {
        customHls: function (video, player) {
          let config = {
            xhrSetup: function (xhr, url) {
              xhr.withCredentials = true; // 会携带cookie
              xhr.setRequestHeader('token', "my-token")
            },
          }
          const hls = new Hls(config);
          hls.loadSource(video.src);
          hls.attachMedia(video);
        },
      },
    },
  });

  window.dp = dp
  dpInstance = dp

})

script>

<style lang="scss">style>

你可能感兴趣的:(视频,javascript,音视频,ffmpeg)