文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

基于electron的音视频播放器

2024-04-02 19:55

关注

前言

我是一个前端工程师,前一段时间想着搞一个属于自己的作品,所以就突发奇想搞了一个基于electron的音视频播放器桌面应用程序。经过几个月的开发,终于实现了大部分的功能,所以我想在这里总结一下前面一段时间的工作,以及在开发的时候遇见的各种坑坑洼洼,希望可以对想要从事electron桌面软件开发的朋友有点帮助吧。

选择做一个音视频播放器桌面应用程序原因

一开始我是打算做个网站或者webAPP那种的,但是作为一名前端工程师来说,网站或者webAPP并没有什么可以吸引我的地方,因为那种东西都是司空见惯的,千篇一律,所以我就想着使用electron做一个音视频播放器,搞点与众不同的东西。虽然网上有很多视频播放器,但是那些播放器基本都是使用C写出来的,并没有使用electron做出来的播放器。

技术的选型

我主要使用的技术是electron、node、vue、express、HTML5相关技术、DPlayer。Electron主要是用来构建音视频播放器所需要的环境,提供访问系统资源的api(调用资源管理器,浏览器等等)以及打包成桌面应用程序。其实说白了electron就是相当于一个浏览器和服务器后台的结合,但是electron打破了传统浏览器的界限,提供了调用系统底层资源的api,使得开发者可以使用系统的资源,比如摄像头,麦克风等等。同时开发者还可以在electron中调用node的模块,搭建一个后台等等。electron有2种进程,一种是渲染进程,另一种是主进程。主进程只能有一个,负责调用系统底层资源,管理窗口那些。渲染进程可以有多条,负责渲染页面的。Node主要使用了fs和path这2个模块,因为这个音视频播放器涉及到了的文件读取操作,所以这2个模块是必不可少的。Vue是负责构建界面的。Express是用来在应用程序中构建一个微型后台,负责把视频读取出来变成流的形式,然后返回给前端界面。html5主要使用了拖拽api、全屏api、Notification消息通知等技术。DPlayer是整个音视频播放器的核心组件,负责播放音视频的。

已经实现了的功能

音视频播放实现

音视频播放实现。一开始我是想着直接使用HTML5提供的标签,但是这个标签局限性很大,它只支持三种视频格式:MP4、WebM、Ogg,但是目前主流视频格式还有avi、mkv、wmv等视频格式。然后我就想着对那些不是MP4、WebM、Ogg的视频格式进行转码,但是需要使用ffmepg来进行转码,electron进行打包的时候是不会把ffmepg这个工具打包进去的,所以这就要求每一个使用这个音视频播放器的用户需要自己去手动安装ffmepg和配置环境,这种做法显然是不行的。同时转码的过程是需要时间,一旦遇见那些几个G是视频,起码要花费几分钟进行转码,然后才能响应用户的操作,这对于用户来说是极其差的用户体验。最后我选择了使用express在electron中搭建一个微型服务器,当express接收到前端界面的请求时,就把所需要的视频读取出来,以流的形式返回给前端,因为实在electron环境下,所以使用的是localhost,这样就可以快速的响应用户的操作,逼近原生播放器的体验。

代码:

let pathSrc = req.query.video;
let stat = fs.statSync(pathSrc);
let fileSize = stat.size;
let range = req.headers.range;
if (range) {
    //有range头才使用206状态码
    let parts = range.replace(/bytes=/, "").split("-");
    let start = parseInt(parts[0], 10);
    let end = parts[1] ? parseInt(parts[1], 10) : start + 999999;

    // end 在最后取值为 fileSize - 1 
    end = end > fileSize - 1 ? fileSize - 1 : end;

    let chunksize = (end - start) + 1;
    let file = fs.createReadStream(pathSrc, {
        start,
        end
    });
    let head = {
        'Content-Range': `bytes ${start}-${end}/${fileSize}`,
        'Accept-Ranges': 'bytes',
        'Content-Length': chunksize,
        'Content-Type': 'video/mp4',
    };
    res.writeHead(206, head);
    file.pipe(res);
} else {
    let head = {
        'Content-Length': fileSize,
        'Content-Type': 'video/mp4',
    };
    res.writeHead(200, head);
    fs.createReadStream(pathSrc).pipe(res);
}


右键菜单实现

右键菜单我一开始的做法是监听右键事件,通过动态生成DOM,然后插入到页面中。但是这种做法并不可行。因为生成的右键菜单需要出现在用户鼠标点击的位置附近,用户鼠标出现的位置可能是应用程序中间,可能是左上角,右上角等等。因为是使用DOM生成,渲染出来的右键菜单不能超出文档的范围,否则就会出现滚动条。所以当用户的鼠标位置在界面边界的时候,需要计算出右键菜单应该出现在鼠标所在位置的上面、下面或者左上角等等,这需要经过一系列大量的计算才能得出结果,这在electron的渲染进程显然是不可行,因为这么复杂的计算可能会造成页面卡顿。所以后面我是用electron的Menu模块,在主进程中生成右键菜单,减轻渲染进程的负担,同是还减少了大量的DOM操作,但是是用electron的Menu模块生成的右键菜单就是白底黑字,样式可能没有符合预期的效果。但是通过2种生成右键菜单的利益权衡后,采用electron的Menu模块生成右键菜单才是最佳的选择。

代码

 let contextMenuTemplate = [
        {
          label: "播放顺序",
          submenu: [
            {
              label: this.playMode == 1 ? "√ 单个播放" : "   单个播放",
              click: () => {
                this.setPlayMode(1);
              }
            },
            {
              label: this.playMode == 2 ? "√ 单个循环" : "   单个循环",
              click: () => {
                this.setPlayMode(2);
              }
            },
            {
              label: this.playMode == 3 ? "√ 循环列表" : "   循环列表",
              click: () => {
                this.setPlayMode(3);
              }
            },
            {
              label: this.playMode == 4 ? "√ 顺序播放" : "   顺序播放",
              click: () => {
                this.setPlayMode(4);
              }
            },
            {
              label: this.playMode == 5 ? "√ 随机播放" : "   随机播放",
              click: () => {
                this.setPlayMode(5);
              }
            }
          ]
        },
        {
          type: "separator"
        },
        {
          label: "声音",
          submenu: [
            {
              label: this.volumePercent == 0.1?"√ 10%":"   10%",
              click:()=>{
                let inWidth = 0.1*62
                this.setInWidth(inWidth)
              }
            },
            {
              label: this.volumePercent == 0.2?"√ 20%":"   20%",
              click:()=>{
                let inWidth = 0.2*62
                this.setInWidth(inWidth)
              }
            },
            {
              label: this.volumePercent == 0.3?"√ 30%":"   30%",
              click:()=>{
                let inWidth = 0.3*62
                this.setInWidth(inWidth)
              }
            },
            {
              label: this.volumePercent == 0.4?"√ 40%":"   40%",
              click:()=>{
                let inWidth = 0.4*62
                this.setInWidth(inWidth)
              }
            },
            {
              label: this.volumePercent == 0.5?"√ 50%":"   50%",
              click:()=>{
                let inWidth = 0.5*62
                this.setInWidth(inWidth)
              }
            },
            {
              label: this.volumePercent == 0.6?"√ 60%":"   60%",
              click:()=>{
                let inWidth = 0.6*62
                this.setInWidth(inWidth)
              }
            },
            {
              label: this.volumePercent == 0.7?"√ 70%":"   70%",
              click:()=>{
                let inWidth = 0.7*62
                this.setInWidth(inWidth)
              }
            },
            {
              label: this.volumePercent == 0.8?"√ 80%":"   80%",
              click:()=>{
                let inWidth = 0.8*62
                this.setInWidth(inWidth)
              }
            },
            {
              label: this.volumePercent == 0.9?"√ 90%":"   90%",
              click:()=>{
                let inWidth = 0.9*62
                this.setInWidth(inWidth)
              }
            },
            {
              label: this.volumePercent == 1?"√ 100%":"   100%",
              click:()=>{
                let inWidth = 1*62
                this.setInWidth(inWidth)
              }
            },
            {
              label: (this.volumePercent != 0.1&&this.volumePercent != 0.2&&this.volumePercent != 0.3&&this.volumePercent != 0.4&&this.volumePercent != 0.5&&this.volumePercent != 0.6&&this.volumePercent != 0.7&&this.volumePercent != 0.8&&this.volumePercent != 0.9&&this.volumePercent != 1&&this.volumePercent != 0)?`√ 其他(${Math.round(this.volumePercent*100)}%)`:"   其他",
            },
            {
              label: this.volumePercent == 0?"√ 静音":"   静音",
              click:()=>{
                let inWidth = 0
                this.setInWidth(inWidth)
              }
            }
          ]
        },
        {
          type: "separator"
        },
        {
          label: "设置"
        }
      ];
      if (this.currentVideo) {
        let addMenu = [
          {
            label: this.isPlaying ? "暂停" : "播放",
            click: () => {
              this.setPlaying(!this.isPlaying);
            }
          },
          {
            type: "separator"
          }
        ];
        contextMenuTemplate.unshift(...addMenu);

        contextMenuTemplate.splice(4, 0, {
          label: this.isFullScreen ? "退出全屏" : "全屏",
          click: () => {
            this.setFullScreen(!this.isFullScreen);
          }
        });

        contextMenuTemplate.push({
          label:'文件信息',
          click:()=>{
            this.videoInfo = this.currentVideo
            this.isShowInfo = true
          }
        })
      }
      let m = Menu.buildFromTemplate(contextMenuTemplate);
      Menu.setApplicationMenu(m);
      m.popup({ window: remote.getCurrentWindow() });

总结

为什么直说音视频播放和右键菜单实现?因为这2个功能是我重写的次数最多的功能,特别是音视频播放这个功能,我还写了很多demo去测试不同的播放方法,测试不同播放方法的性能问题,最终才选择了搭建一个微型服务器这个方法。其他的功能没什么需要特别讲解的地方,其他功能都是细节问题,同住还要注意封装公共代码,降低耦合度,分模块,分功能去编写代码。因为一开始我并没有注意到这些地方,写到后面代码越来越多,出现问题的时候都无从下手,不知道改哪里,这使得我花费了大量的时间对代码进行重构,整理。

效果图

在这里插入图片描述

效果图1

在这里插入图片描述

效果图2

在这里插入图片描述

效果图3

在这里插入图片描述

效果图4

在这里插入图片描述

效果图5

在这里插入图片描述

效果图6

在这里插入图片描述

效果图7

最后如果大家觉得我这个音视频播放器还可以的话,欢迎去我的github:

https://github.com/c10342/player 给个star

到此这篇关于基于electron的音视频播放器的文章就介绍到这了,更多相关electron音视频播放器内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-前端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯