VUE 3.0 + NGINX + Hls.js + OBS -- 直播推拉流、流视频播放

news2025/2/28 23:13:16

🛴🛴前言:
Demo 基于 OBS推流 + Nginx + Vue 3.0 + Nplayer.js + hls.js ,目的只是实现流媒体播放,以及简易推拉流直播。

文章目录

    • 前端组件 NPlayer.js
      • 安装 nplayer.js
    • 流视频播放
      • 页面元素
      • 初始化播放器
      • 清晰度控件样式
      • 效果如下
    • 直播推拉流
      • Nginx 安装 rmtp 模块
        • Linux 环境可以通过重新编译 nginx 加入 rtmp 模块
        • Window 安装 rmtp 模块
      • Nginx 配置 hls 服务器
        • 配置服务器
        • 启动nginx
      • OBS推流
        • 下载OBS
        • 简单配置一下
        • 开启直播
      • hls.js 拉流 + NPlayer 播放
        • 引入 hls.js
        • 切换视频流来源
        • 查看直播

前端组件 NPlayer.js

一个比较简单自定义的视频流播放组件,可以自定义控件元素等,自带弹幕组件,具体使用文档可访问 https://nplayer.js.org/

安装 nplayer.js

npm i -S nplayer
或者
yarn add nplayer

根据官网描述,需要创建根组件
在main.ts中添加

import App from './App.vue';
import { createApp } from 'vue';
import NPlayer from '@nplayer/vue'; //加上这一行
async function bootstrap() {
  const app = createApp(App);
  ... ...
  app.use(NPlayer); //加上这一行
  app.mount('#app');
}

bootstrap();

流视频播放

页面元素

<template>
  <div class="broad">
    <div class="left_adv"> TODO </div>
    <div ref="centerPlayer" class="center_player">
    </div>
    <div class="right_chat"> TODO </div>
  </div>
  <div class="bottom"> </div>
</template>

初始化播放器

设置播放器,并增加弹幕发送、清晰度调整,修改播放速排序等。

<script lang="ts">
  import { ref } from 'vue';
  import NPlayer, { Popover } from 'nplayer';
  import Danmaku from '@nplayer/danmaku';
  import Hls from 'hls.js';
  import './style.less';
  export default {
    components: {},
    setup() {
      let player = null;
      const centerPlayer = ref(null);

      // 右键菜单增加截图
      const Screenshot = {
        html: '截图',
        click(player) {
          const canvas = document.createElement('canvas');
          canvas.width = player.video.videoWidth;
          canvas.height = player.video.videoHeight;
          canvas.getContext('2d').drawImage(player.video, 0, 0, canvas.width, canvas.height);
          canvas.toBlob((blob) => {
            let dataURL = URL.createObjectURL(blob);
            const link = document.createElement('a');
            link.href = dataURL;
            link.download = 'NPlayer.png';
            link.style.display = 'none';
            document.body.appendChild(link);
            link.click();
            document.body.removeChild(link);
            URL.revokeObjectURL(dataURL);
          });
        },
      };

      // 插件设置
      // 速度设置
      const speedSettingItem = (): SettingItem => ({
        id: 'speed',
        html: '播放速度',
        type: 'select',
        value: 1,
        options: [
          { value: 2, html: '2' },
          { value: 1.5, html: '1.5' },
          { value: 1, html: '正常' },
          { value: 0.5, html: '0.5' },
          { value: 0.25, html: '0.25' },
        ],
        init(player) {
          player.playbackRate = 1;
        },
        change(value, player) {
          this.value = player.playbackRate = value;
        },
      });
      // 1. 首先创建一个清晰度控制条项
      const Quantity = {
        el: document.createElement('div'),
        init() {
          this.btn = document.createElement('div');
          this.btn.textContent = '画质';

          this.el.appendChild(this.btn);
          this.popover = new Popover(this.el);
          this.btn.addEventListener('click', () => this.popover.show());
          // 点击按钮的时候展示 popover
          // 默认隐藏
          this.el.style.display = 'none';
          this.el.classList.add('quantity');
          this.btn.classList.add('quantity_btn');
        },
      };
      const newPlugin = {
        apply(player) {
          player.registerSettingItem(speedSettingItem(), 'speed');
        },
      };

      // 弹幕设置
      const danmakuOptions = {
        items: [{ time: 1, text: '前方弹幕来袭~' }],
      };

      //初始化播放器
      const initPlayer = () => {
        // 设置视频
        const video = document.createElement('video');
        player = new NPlayer({
          seekStep: 10,
          volumeStep: 0.1,
          video: video,
          videoProps: { autoplay: 'true' },
          contextMenus: [Screenshot, 'loop', 'pip'],
          contextMenuToggle: true,
          controls: [
            [
              'play',
              'volume',
              'time',
              'spacer',
              Quantity,
              'airplay',
              'settings',
              'web-fullscreen',
              'fullscreen',
            ],
            ['progress'],
          ],
          bpControls: {},
          plugins: [new Danmaku(danmakuOptions), newPlugin],
        });

        // 绑定流
        const hls = new Hls();
        hls.on(Hls.Events.MEDIA_ATTACHED, function () {
          hls.on(Hls.Events.MANIFEST_PARSED, function () {
            // 4. 给清晰度排序,清晰度越高的排在最前面
            hls.levels.sort((a, b) => b.height - a.height);
            const frag = document.createDocumentFragment();
            // 5. 给与清晰度对应的元素添加,点击切换清晰度功能
            const listener = (i) => (init) => {
              const last = Quantity.itemElements[Quantity.itemElements.length - 1];
              const prev = Quantity.itemElements[Quantity.value] || last;
              const el = Quantity.itemElements[i] || last;
              prev.classList.remove('quantity_item-active');
              el.classList.add('quantity_item-active');
              Quantity.btn.textContent = el.textContent;
              if (init !== true && !player.paused) setTimeout(() => player.play());
              // 因为 HLS 切换清晰度会使正在播放的视频暂停,我们这里让它再自动恢复播放
              Quantity.value = hls.currentLevel = hls.loadLevel = i;
              Quantity.popover.hide();
            };
            // 6. 添加清晰度对应元素
            Quantity.itemElements = hls.levels.map((l, i) => {
              const el = document.createElement('div');
              el.textContent = l.name + 'P';
              if (l.height === 1080) el.textContent += ' 超清';
              if (l.height === 720) el.textContent += ' 高清';
              if (l.height === 480) el.textContent += ' 清晰';
              el.classList.add('quantity_item');
              el.addEventListener('click', listener(i));
              frag.appendChild(el);
              return el;
            });

            const el = document.createElement('div');
            el.classList.add('quantity_item');
            el.textContent = '自动';
            el.addEventListener('click', listener(-1));
            frag.appendChild(el);
            Quantity.itemElements.push(el);
            // 这里再添加一个 `自动` 选项,HLS 默认是根据网速自动切换清晰度
            Quantity.popover.panelEl.appendChild(frag);
            Quantity.el.style.display = 'block';
            listener(hls.currentLevel)(true);
            // 初始化当前清晰度
          });

          // 绑定 video 元素成功的时候,去加载视频
          hls.loadSource('https://test-streams.mux.dev/x36xhzz/x36xhzz.m3u8');
        });
        hls.attachMedia(video);
        player.mount(centerPlayer.value);
      };
      return {
        player,
        initPlayer,
        centerPlayer,
        setPlayer: (p) => (player = p),
      };
    },
    mounted() {
      this.initPlayer();
      console.log(this.player);
    },
  };
</script>
<style lang="less" scoped>
  .tab-header {
    background: white;
    padding-left: 1%;
    padding-right: 1%;
  }
  .broad {
    display: flex;
    width: 100%;
    height: 80%;
  }
  .left_adv {
    width: 60px;
    border: #222222 2px solid;
  }
  .center_player {
    flex: 1;
    border: #222222 2px solid;
    border-left: 0px;
    border-right: 0px;
  }
  .right_chat {
    width: 400px;
    border: #222222 2px solid;
  }
  .bottom {
    width: 100%;
    height: 20%;
    border: #222222 2px solid;
    border-top: 0px;
  }
</style>

清晰度控件样式

清晰度设置的样式在一个单独的样式文件中 ./style.less ,因为我们的清晰度设置控件是在代码中动态生成的,但是当前文件为了防止样式渗透影响到其他页面或公共组件的样式,所以加上了 scoped 属性,这就导致动态生成的元素获取不到当前文件中定义的样式。
在这里插入图片描述
所以我们将清晰度样式单独存放

.quantity_btn {
  font-family: sans-serif;
  color: rgb(255, 255, 255);
}
.quantity {
  position: relative;
  padding: 0 8px;
  cursor: pointer;
  font-size: 14px;
  font-weight: bold;
  white-space: nowrap;
  opacity: 0.8;
}
.quantity:hover {
  opacity: 1;
}
.quantity_item {
  height: 30px;
  width: 100px;
  padding: 5px;
  font-weight: normal;
}
.quantity_item:hover {
  background: rgba(255, 255, 255, 0.3);
}
.quantity_item-active {
  height: 30px;
  width: 100px;
  padding: 5px;
  color: var(--theme-color);
}

效果如下

在这里插入图片描述

直播推拉流

为实现直播间的推拉流,使用 nginx-rtmp-module 模块扩展 Nginx 功能,借助 Nginx 的 Rtmp 模块搭建一个流服务器

Nginx 安装 rmtp 模块

Linux 环境可以通过重新编译 nginx 加入 rtmp 模块

  1. 先安装 nginx 必要的库
    sudo apt-get install build-essential libpcre3 libpcre3-dev libssl-dev
    
  2. 下载 nginx
    wget http://nginx.org/download/nginx-1.20.2.tar.gz
    
  3. 下载 nginx-rtmp-module 模块
    wget https://github.com/arut/nginx-rtmp-module/archive/master.zip
    
  4. 解压下载的文件并进入 nginx 文件夹
    tar -zxvf nginx-1.20.2.tar.gz
    unzip nginx-rtmp-module-master.zip
    cd nginx-1.20.2
    
  5. 重新编译nginx
    ./configure --with-http_ssl_module --add-module=../nginx-rtmp-module-master
    make
    make install
    

Window 安装 rmtp 模块

官方网站 windows 版本的 nginx 并未提供 rtmp 模块,我们可以从以下网站下载,亲测可用
http://nginx-win.ecsds.eu/download/
版本为

nginx 1.7.11.3 Gryphon.zip  

Nginx 配置 hls 服务器

使用 nginx 做一个 hls 的服务器,hls协议可以跨平台,而且码率切换快,基本不会被防火墙屏蔽。

配置服务器

修改 nginx.conf

rtmp {
    server {
        listen 1935;
        chunk_size 4096;
		application hls {
			live on;
			hls on;
			hls_path F:/,,,SpringCloudEnv/nginxrtmpvideo/;#视频流存放地址
			hls_fragment 5s;
			hls_playlist_length 10s;
			hls_continuous on; #连续模式。
			hls_cleanup off;    #对多余的切片进行删除。默认会自动删除,如果想要保留直播内容需要加上这一行
			hls_nested on;     #嵌套模式。
		}
    }
}
 
http {
    include       mime.types;
    default_type  application/octet-stream;

    sendfile        on;

    keepalive_timeout  65;
    server {
        listen       81;
        server_name  localhost;

        location / {
            root   html;
            index  index.html index.htm;
        }
		# 前端请求的时候需要请求这里的直播文件
		location /hls {
			types {
				application/vnd.apple.mpegurl m3u8;
				video/mp2t ts;
			}
			#访问权限开启,否则访问这个地址会报403
			autoindex on;
			alias F:/,,,SpringCloudEnv/nginxrtmpvideo/;#视频流存放地址,与上面的hls_path相对应,这里root和alias的区别可自行百度
			expires -1;
			add_header Cache-Control no-cache;
			#防止跨域问题
			add_header 'Access-Control-Allow-Origin' '*';
			add_header 'Access-Control-Allow-Credentials' 'true';
			add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
			add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type';  
		}
		
        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }
    }
}

启动nginx

查看是否正常启动nginx

OBS推流

下载OBS

https://obs.yjjxx.cn/index.html?bd_vid=8452749110375400746

简单配置一下

在这里插入图片描述
要注意 这里设置的推流码,会在nginx对应存储直播流的文件夹中创建一个同名文件夹 test1,在开启直播后会在 test1 文件中生成一个 m3u8 索引文件,和一系列媒体文件。如下
在这里插入图片描述

开启直播

可以使用摄像头、或者使用现存的视频设置媒体源
在这里插入图片描述
在这里插入图片描述
设置好媒体源后,点击开始直播,nginx 文件夹中就有媒体文件不断生成了

hls.js 拉流 + NPlayer 播放

引入 hls.js

npm install hls.js -S
或者 
yarn add hls.js 

上面的代码中已经使用了 hls.js ,所以不用再做修改。我们只需要把视频流来源切换一下。

切换视频流来源

在这里插入图片描述

查看直播

直播开了延迟
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/602293.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

D*算法详解 (D星算法 / Dynamic A*算法/ Dstar算法)(死循环解决)

所需先验知识&#xff08;没有也无大碍&#xff0c;只是了解的话会对D*的理解有帮助&#xff09;&#xff1a;A*算法/ Dijkstra算法 何为D*算法 Dijkstra算法是无启发的寻找图中两节点的最短连接路径的算法&#xff0c;A*算法则是在Dijkstra算法的基础上加入了启发函数h(x)&am…

js正则中的match()

在前端开发中&#xff0c;正则表达式是一大利器。所以我们这次就来讨论下match()方法。 match本身是JavaScript语言中字符串对象的一个方法&#xff0c;该方法的签名是 match([string] | [RegExp]) 它的参数既可以是一个字符串&#xff0c;也可以是一个正则表达式。该方法绝…

windows 达梦数据库服务连接时提示:登录服务器失败,错误号6001,错误消息:网络通信异常 之数据库服务不存在的处理方式

在windows客户端上连接部署在windows操作系统上的达梦数据库&#xff0c; 使用DM管理工具连接数据库 正确输入用户名与密码之后点击确定按钮之后出现&#xff1a; 登录服务器失败&#xff0c;错误号6001&#xff0c;错误消息&#xff1a;网络通信异常 现象 如下图所示&#…

银行从业资格证 个人理财 各种年金计算公式总结

变量说明&#xff1a; C C C &#xff1a;每期投入的现金流 r r r&#xff1a;利率&#xff08;收益率/贴现率&#xff09; n n n &#xff1a;计息期数&#xff1b; F V FV FV&#xff1a;终值 P V PV PV&#xff1a;现值 推导计算过程用到等比数列求和公式 S n a 1 ∗ 1 −…

【算法训练(day7)】区间和并,离散化数组模板

目录 一.区间和并 二 .离散化数组 一.区间和并 问题&#xff1a;给定 n个区间 [li,ri]&#xff0c;要求合并所有有交集的区间。注意如果在端点处相交&#xff0c;也算有交集。输出合并完成后的区间个数。例如&#xff1a;[1,3][1,3] 和 [2,6][2,6] 可以合并为一个区间 [1,6][1…

htmlCSS-----CSS介绍与样式书写

目录 前言&#xff1a; 1. CSS是什么 2. CSS书写样式 (1)行内样式 (2)内部样式 3.外部样式 4.三者之间的比较 前言&#xff1a; 前面我们学习了HTML的相关标签和框架写法&#xff0c;那我们在了解这些标签用法了之后就要学会怎么去通过相关方法来使得界面美化处理&#xf…

06 Redis分布式锁

常见面试问题 Redis除了拿来做缓存&#xff0c;你还见过基于Redis的什么用法&#xff1f;Redis 做分布式锁的时候有需要注意的问题&#xff1f;如果是 Redis 是单点部署的&#xff0c;会带来什么问题&#xff1f;那你准备怎么解决单点问题呢&#xff1f;集群模式下&#xff0c…

LeetCode刷题集(七)(2315.统计星号)

&#x1f626;学习目标&#xff1a;拿下LeetCode2315.统计星号题目 &#x1f624; 学完本章节知识即可掌握本题&#xff01; 学习内容&#xff1a;LeetCode2315.统计星号 &#x1f624;题目&#xff1a;给你一个字符串 s &#xff0c;每 两个 连续竖线 ‘|’ 为 一对 。换言之&…

知识图谱涉及技术点分析

文章目录 数据从哪里来为什么通常将知识图谱划分到NLP领域&#xff1f;常用NLP技术点分析只是NLP任务吗&#xff1f;graph embedding知识融合业务还是算法&#xff1f;知识图谱组成 数据从哪里来 是手动提取关系吗&#xff1f;数据很多&#xff0c;关系确难涉及大量NLP技术关系…

Ansible基础五——条件语句、循环语句、handlers、任务失败处理

文章目录 一、 循环语句1.1 单量循环1.2 多量循环1.3 老版本用法1.4 loopregister 二、条件判断2.1 根据变量状态判断2.2 根据变量是否存在判断2.3 根据事实判断2.4 多条件判断2.4.1 and用法2.4.2 or用法 2.5 循环判断2.6 根据上个任务结果判断 三、handlers处理程序四、任务失…

5月《中国数据库行业分析报告》正式发布,首发时序、实时数据库两大【全球产业图谱】

为了帮助大家及时了解中国数据库行业发展现状、梳理当前数据库市场环境和产品生态等情况&#xff0c;从2022年4月起&#xff0c;墨天轮社区行业分析研究团队出品将持续每月为大家推出最新《中国数据库行业分析报告》&#xff0c;持续传播数据技术知识、努力促进技术创新与行业生…

ubuntu20安装xrdp以及解决黑屏问题

1、安装xrdp sudo apt-get install xrdp 2、将xrdp用户加入到如下用户组 sudo adduser xrdp ssl-cert 3、重启xrdp sudo service xrdp restart 4、打开windows远程面&#xff0c;连接&#xff0c;如果出现黑屏 sudo -s sudo vim /etc/xrdp/startwm.sh 加入如下内容&#xff…

攻防世界-web-Web_php_unserialize

1. 题目描述&#xff1a;查看以下代码&#xff0c;获取flag 2. 思路分析 从代码中不难看出&#xff0c;这里共有三个地方需要绕过 2.1 __wakeup函数&#xff1a;若在对象的魔法函数中存在的__wakeup方法&#xff0c;那么之后再调用 unserilize() 方法进行反序列化之前则会先…

数据分析概述

数据分析概述 数据的性质数据的概念数据与信息的区别和联系 数据的类型按照度量尺度分按时间状况分 什么是数据分析数据分析的重要性数据分析的内容数据分析作用 数据分析的基本流程典型的数据分析的流程 数据分析方法对比分析法分组分析法定量数据分布分析——具体事例 结构分…

上海亚商投顾:沪指高开高走 地产股迎来久违反弹

上海亚商投顾前言&#xff1a;无惧大盘涨跌&#xff0c;解密龙虎榜资金&#xff0c;跟踪一线游资和机构资金动向&#xff0c;识别短期热点和强势个股。 市场情绪 三大指数今日高开高走&#xff0c;沪指午后涨近1%&#xff0c;深成指、创业板指涨超1.2%&#xff0c;上证50盘中大…

惠更斯定理和格林定理

惠更斯原理和格林定理 惠更斯原理显示了表面上的波场如何决定表面 S S S外的波场。惠更斯在17世纪启发性地表达了这一概念。但这个想法的数学表达是由于19世纪的乔治格林。这一概念可以在数学上表达为标量波和矢量波。矢量波情形的推导与标量波情形是同态的。但是标量波情况下…

少儿编程python-一级

少儿编程python 文章目录 前言CSP-J与CSP-S少儿编程证书含金量排名&#xff08;国家承认的少儿编程证书&#xff09;非专业级软件能力认证&#xff08;CSP-J/S&#xff09;青少年编程能力等级测试&#xff08;CPA&#xff09;蓝桥杯青少年信息技术等级考试全国青少年软件编程等…

造船厂事故/风险(背景+官方统计数据)

造船厂事故/风险&#xff08;背景官方统计数据&#xff09; 船厂工地常见事故船厂事故:发人深省的伤害统计船厂工地常见的风险有哪些? 造船业是周期性的、资本密集型的行业。更严格的环境法规于2020年初生效&#xff0c;引发了对抑制船舶废气硫排放技术的需求。与此同时&#…

数据标记工具

检测分割标定 labelstudio https://labelstud.io/sudo apt install libpq-dev python3-devconda activate paddle_envpip install label-studiolabel-studio startlabel-studio --data-dir /data/data_label_studio<View><Image name"image" value"$im…

【shiro】shiro整合JWT——2.如何整合

前言 shiro整合JWT系列&#xff0c;主要记录核心思路–如何在shiroredis整合JWTToken。 上一篇中&#xff0c;我们知道了需要创建JwtToken、JwtUtil、JwtFilter。 该篇主要讲如何在shiro框架中&#xff0c;配置Jwt。 ps&#xff1a;本文主要以记录核心思路为主。 1、ShiroCon…