基于electron+vue3+vite2+ffmpeg+jsmpeg做个rtsp播放器

2 篇文章 0 订阅
1 篇文章 0 订阅


前言

最近在做个桌面应用程序,用来播放rtsp视频,随着chrome将flash打入冷宫,因此算是基于最近新的前端技术做个dome耍耍(2022.04.11)


注意

  1. 桌面端(需要electron提供的主线程调用ffmpeg来解析rtsp,相当于服务端功能)
  2. 客户端性能要求较高(若多路拉流,主要ffmpeg消耗较大,可通过降低画质降低需要,在优缺点一栏有和vlc性能比对)

一 环境及npm包安装

node.js 需大于等于12, vite需要

// 安装vite
npm init vite@latest
// 根据提示,完成接下来的选择,如下图所示

在这里插入图片描述

使用编辑器打开刚刚的项目,目录如下所示

在这里插入图片描述

安装npm包

// 安装vite及vue依赖
npm i
// 安装electron electron-builder
npm i electron electron-builder -D
// 安装解析需使用包,一定要用-S或者--save,否则打包之后会找不到该模块
npm i ffmpeg-static -S // 可按照不同系统自动下载对应的ffmpeg二进制文件
// rtsp-stream, 用-S或者--save,理由同上
npm i node-rtsp-stream -S
// 安装jsmpeg-player
npm i jsmpeg-player -D
// 安装concurrently及wait-on用于启动electron和vite
npm i concurrently wait-on -D

修改package文件,注意把注释部分去掉,json文件无法添加注释

{
  "name": "electron-player",
  "private": true,
  "version": "0.0.0",
  "main": "electron/main",
  "scripts": {
  	// 添加启动指令
    "start": "concurrently \"vite\" \"wait-on tcp:3000 && electron .\"",
    // 添加打包指令
    "dist": "vite build && electron-builder",
    "dev": "vite",
    "build": "vue-tsc --noEmit && vite build",
    "preview": "vite preview"
  },
  "dependencies": {
  	"ffmpeg-static": "^5.0.0",
  	"node-rtsp-stream": "^0.0.9",
    "vue": "^3.2.25"
  },
  "devDependencies": {
    "@vitejs/plugin-vue": "^2.3.0",
    "concurrently": "^7.1.0",
    "electron": "^18.0.3",
    "electron-builder": "^22.14.13",
    "jsmpeg-player": "^3.0.3",
    "typescript": "^4.5.4",
    "vite": "^2.9.0",
    "vue-tsc": "^0.29.8",
    "wait-on": "^6.0.1"
  },
  /**
   * 修改electron打包文件位置,
   * 默认为dist,因vite打包默认也是dist,
   * 所以修改vite或者electron的打包位置
   */
  "build": {
    "directories": {
      "output": "dist_electron"
    }
  }
}

修改vite.config.ts

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'

// https://vitejs.dev/config/
export default defineConfig(({ command }) => {
  return {
    base: command == 'serve' ? '/' : './', // 打包后文件要用相对路径,否则页面空白
    plugins: [vue()],
  }
})

二 新建并编辑electron/main.js

const { app, BrowserWindow, ipcMain } = require('electron')
const path = require('path')

// app生命周期相关
app.on('ready', createWindow)
   .on('window-all-closed', closeWindow)

// rtsp相关,ffmpeg-static会自动下载对应平台windos,linux,mac(intle),mac(M1版本)的ffmpeg二进制文件
const ffmpegPath = require('ffmpeg-static')
/**
  stream相关配置及方法
  name = options.name
  streamUrl = options.streamUrl
  width = options.width
  height = options.height
  wsPort = options.wsPort
  nputStreamStarted = false
  stream = undefined
  ffmpegPath = options?.ffmpegPath ?? ffmpeg
  stop()
 */
const Stream = require('node-rtsp-stream')
/**
 * rtsp列表
 * interface {
 *   rtspUrl: {
 *     ws: websocket地址
 *     stream: stream实例
 *   }
 * }
 */
const rtspOpenders = {}
let addPort = 9000


/** 
 * 开启rtsp
 * @param rtsp {String} rtsp流地址
 */
ipcMain.on('openRtsp', (event, rtsp) => {
  /** 判断是否已开启,若已开启,直接返回ws地址, 未开启则先开启再返回 */
  if (rtspOpenders[rtsp]) {
    event.returnValue = {
      code: 200,
      msg: '开启成功',
      ws: rtspOpenders[rtsp].ws
    }
  } else {
    addPort++
    const stream = new Stream({
      name: `socket-${addPort}`,
      streamUrl: rtsp,
      wsPort: addPort,
      ffmpegPath: app.isPackaged ? ffmpegPath.replace('app.asar', 'app.asar.unpacked') : ffmpegPath,
      ffmpegOptions: {
        '-stats': '',
        '-r': 30
      }
    }).on('exitWithError', () => {
      stream.stop()
      delete rtspOpenders[rtsp]
      event.returnValue = {
        code: 400,
        msg: '开启失败'
      }
    })
    rtspOpenders[rtsp] = {
      ws: `ws://localhost:${addPort}`,
      stream: stream
    }
    event.returnValue = {
      code: 200,
      msg: '开启成功',
      ws: rtspOpenders[rtsp].ws
    }
  }
})

/**
 * 关闭rtsp
 */
ipcMain.on('closeRtsp', (event, rtsp) => {
  if (rtspOpenders[rtsp]) {
    // 停止解析
    rtspOpenders[rtsp].stream.stop()
    // 删除该项
    delete rtspOpenders[rtsp]
    // 返回结果
    event.returnValue = {
      code: 200,
      msg: '关闭成功'
    }
  } else {
    event.returnValue = {
      code: 400,
      msg: '未找到该rtsp'
    }
  }
})

// 创建窗口
function createWindow () {
  // 创建窗口
  const win = new BrowserWindow({
    // frame: false, // 隐藏目录,window下很难看
    width: 800,
    height: 600,
    webPreferences: {
      // 允许web断使用node
      nodeIntegration: true,
      contextIsolation: false,
      // 同源策略关闭
      webSecurity: false
    }
  })
  // 加载html, 打包环境加载dist下打包文件, 开发环境加载vite服务,vite默认端口3000
  const htmlUrl = app.isPackaged ? `file://${path.join(__dirname, '../dist/index.html')}` : `http://localhost:3000`
  win.loadURL(htmlUrl)
  // 开发环境, 打开chrome调试工具
  if (!app.isPackaged) win.webContents.openDevTools()
}

// 关闭窗口
function closeWindow () {
  /**
   * 在macOS,除非cmd+q确定退出,否则绝大部分应用及其菜单栏都保持激活
   */
  if (process.platform !== 'darwin') app.quit()
}

三 编辑app.vue

<script setup lang="ts">
import { ref } from 'vue'
import MpegPlayer from 'jsmpeg-player'
const { ipcRenderer } = require('electron')
const rtspUrl = ref('')
const mpegPlayer = ref()
const msg = ref('')
let player: any = null

const open = () => {
  const res = ipcRenderer.sendSync('openRtsp', rtspUrl.value)
  if (res.code === 200) {
    player = new MpegPlayer.VideoElement(mpegPlayer.value, res.ws)
  }
  msg.value = res.msg
}
const close = () => {
  const res = ipcRenderer.sendSync('closeRtsp', rtspUrl.value)
  msg.value = res.msg
}
</script>

<template>
  <div class="flexBox">
    <input type="text" v-model="rtspUrl">
    <button @click="open">打开rtsp</button>
    <button @click="close">关闭rtsp</button>
  </div>
  <div class="mpegPlayer" ref="mpegPlayer"></div>
  <div>{{msg}}</div>
</template>

<style>
* {
  padding: 0;
  margin: 0;
}
.flexBox {
  display: flex;
  justify-content: center;
  align-items: center;
  height: 50px;
}
.flexBox input  {
  height: 30px;
  width: 500px;
  box-sizing: border-box;
  padding-left: 8px;
}
.flexBox button {
  height: 30px;
  padding: 0 12px;
}
.mpegPlayer {
  width: 800px;
  height: 450px;
  background: #ccc;
}
</style>

四 效果

// 开启
npm run start
// 打包
npm run dist

在这里插入图片描述

优点

延迟很低,目测比vlc要快1s左右
在这里插入图片描述

缺点

不能在纯浏览器中实现,需要线程来调用ffmpeg解析rtsp
资源消耗相对vlc来说很大,如下图对比(解析同一rtsp,1920*1080分辨率)
优化思路:注意到electron时没有调用gpu,也许可以优化ffmpeg的命令来调用gpu解析,多路播放就很可靠了,也许可以达到vlc的性能?
在这里插入图片描述

遇到的问题

@ffmpeg-installer/ffmpeg 这个包,经测试未匹配M1版本的mac,需手动下载对应的ffmpeg运行文件并配置环境变量,相关代码部分已添加注释(ps:手里的mac是15年的intle版本,此部分暂时无能为力,或者去催催@ffmpeg-installer/ffmpeg作者咯)

问题已解决

2022/04/13 更新
已将@ffmpeg-installer/ffmpeg 替换为 ffmpeg-static ,ffmpeg-static支持M1版本mac,详情查看ffmpeg-static - npm
在这里插入图片描述

  • 10
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 65
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 65
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值