百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 热门文章 > 正文

Electron案例-调用VLC播放器

bigegpt 2024-09-10 11:20 4 浏览

大家好,我是yangyang.今天要给大家分享的electron一个实用小案例: 调用vlc播放器播放视频并控制vlc播放器,支持播放、暂停、截图、ab片段播放、ab循环播放、快进、快退等操作.

关于VLC

VLC 媒体播放器(最初为 VideoLAN Client)是一款高度便携的多媒体播放器,可播放各种音频和视频格式(MPEG、DivX/Xvid、Ogg 等)以及 DVD、VCD 和各种流媒体协议。 不过,近年来它也成为了一个功能极其强大的服务器,可将多种格式的实时和点播视频流传输到我们的网络和互联网上。 VLC 由非营利基金会 VideoLAN 制作。

优点:

  • 支持多种音视频格式
  • 跨平台,支持Window、Mac

实战

正是因为VLC的跨平台的超强播放器,因此作者才会选择接入.可能有朋友会问:“既然vlc跨平台的软件,那我下载使用就行,为什么需要electron再去调用呢?”,对于这样的问题我的回答是存在即合理,业务需要.

业务说明

还是简单说下需求:electron构建一个app,app主要可以打开本地视频播放,同时app支持设置快捷键来控制打开的vlc播放器.比如使用快捷键截取当前帧并提取相关文字等.

核心代码-使用系统调用api来打开vlc播放器

关于 exec 和 spawn

exec 和 spawn 是 Node.js 中 child_process 模块提供的两个方法,用于在子进程中执行命令。它们的主要区别在于它们如何处理输入输出数据以及适用的使用场景。

exec

exec 方法用于执行一个 shell 命令并且将其输出(包括标准输出和标准错误)作为一个缓冲区返回。它适用于执行简单的、一次性的命令,并且不需要与子进程进行大量的交互。

使用示例:

const { exec } = require('child_process');

// 执行一个 shell 命令
exec('ls -l', (error, stdout, stderr) => {
  if (error) {
    console.error(`执行出错: ${error}`);
    return;
  }
  console.log(`标准输出:\n${stdout}`);
  console.error(`标准错误:\n${stderr}`);
});

特点:

  • 输出作为一个缓冲区(字符串)返回。
  • 默认情况下有一个 200KB 的输出限制,可以通过 maxBuffer 选项增加。
  • 适用于需要执行简单命令并一次性获取输出的场景。

spawn

spawn 方法用于启动一个新的进程并且为其输入输出流提供一个接口。它更适合处理长时间运行的进程或者需要与子进程进行大量交互的场景。

使用示例:

const { spawn } = require('child_process');

// 启动一个新的进程
const ls = spawn('ls', ['-l']);

ls.stdout.on('data', (data) => {
  console.log(`标准输出: ${data}`);
});

ls.stderr.on('data', (data) => {
  console.error(`标准错误: ${data}`);
});

ls.on('close', (code) => {
  console.log(`子进程退出码: ${code}`);
});

特点:

  • 返回一个 ChildProcess 对象,提供了 stdout 和 stderr 流来实时处理输出。
  • 没有输出缓冲区大小限制。
  • 适用于需要实时处理输出或者与子进程进行交互的场景。

总结

  • 使用 exec 当你需要执行简单命令并获取其输出。
  • 使用 spawn 当你需要处理长时间运行的进程,或者需要实时处理进程输出和交互

核心代码-打开播放器业务代码

  async openVLC(videoPath, seekTime = null) {
    try {
      const isVlcRunning = this.vlcProcess !== null
      console.log('vlc.isVlcRunning=', isVlcRunning)
      if (isVlcRunning) {
        return this.onOpenVideo(videoPath, seekTime)
      }

      const args = [videoPath]
      let selfSeekTime = false
      if (seekTime) {
        const {
          mode,
          times
        } = this.parseSeekTime(seekTime)
        if (mode === 'ab') {
          selfSeekTime = true
          // A-B片段
          args.push(
            `--start-time=${times[0]}`,
            // `--stop-time=${times[1]}`,
            // '--play-and-pause',
          )
        } else if (mode === 'ab-loop') {
          selfSeekTime = true
          args.push(
            `--start-time=${times[0]}`,
            // `--stop-time=${times[1]}`,
            // '--loop',
          )
        } else {
          args.push(`--start-time=${this.convertSeekTimeToSeconds(seekTime)}`)
        }
      }

      if (SystemConfig.isWindows) {
        args.push('--intf', 'qt')
      }


      console.log('vlc cmd:', this.vlcPath, args.join(' '))
      const vlcProcess = spawn(this.vlcPath, args, {
        detached: true,
        stdio: 'ignore',
      })

      vlcProcess.unref()
      vlcProcess.on('close', (code) => {
        console.log(`VLC process exited with code ${code}`)
        this.vlcProcess = null
        this.clearAbCheckTimeInterval()
        this.clearAbLoopCheckTimeInterval()
      })

      vlcProcess.on('spawn', () => {
        if (selfSeekTime) {
          this.seekTimeLoop(seekTime)
        }
        console.log('VLC opened successfully')
      })
      this.vlcProcess = vlcProcess
      return true
    } catch (error) {
      console.log(`Failed to open VLC: ${error.message}`)
      return false
    }
  }

代码解释:

  • 首先,它检查vlc是否已经在运行,如果是,则调用onOpenVideo方法并返回。
  • 然后,根据传入的seekTime参数来处理开始播放的时间。如果seekTime的模式是ab或ab-loop,则会处理A-B片段的播放,并将selfSeekTime设置为true。
  • 接下来,根据系统是否是Windows来添加相应的参数。
  • 在接下来,使用spawn方法创建一个新的vlc进程,并设置相应的参数和事件监听器。如果selfSeekTime为true,则会调用seekTimeLoop方法。
  • 最后,将创建的vlc进程赋值给this.vlcProcess,并返回true表示成功打开vlc。如果出现错误,则会打印错误信息并返回false。

到这里我们就打开了VLC播放器,然后这里在解释下vlc终端指令几个关键参数:

  • --start-time 指定开始播放的时间点
  • --stop-time 指定结束播放的时间点
  • --loop 循环播放
  • --intf qt 打开的播放器画面拥有功能栏、进度栏等丰富的界面元素,不加只会弹出一个播放器

基本上,我们现在通过终端指令已经可以实现打开本地任一视频播放了,而且也能实现a-b片段、ab片段循环,当然作者这里实际是放弃了指令实现的ab操作,具体实现有兴趣的朋友可以留言.接下来,我们继续看其他操作的实现.

实现播放、暂停、获取当前播放状态、快进、快退等操作

对于vlc来讲,它已经为我们提供很好的对接方式,那就是vlc http api.因此我们便可以通过接口来实现我们的操作.

第一步:播放器设置http

作者给出mac的设置(默认显示基本,就会有个http 密码设置),??启用并设置vlc密码.就开启了我们http接口.windows用户设置基本一样,具体看vlc软件.


第二步:查看官方文档

文档地址: VLC HTTP requests - VideoLAN Wiki

第三步: 封装一个请求方法

  // 获取 VLC 的当前状态
  async getVlcStatus() {
    const {
      port,
      password
    } = await this.getVlcConfig()
    const url = `http://${SystemConfig.vlcHttpHost}:${port}/requests/status.xml`
    try {
      const response = await axios.get(url, {
        auth: {
          username: '',
          password: password,
        },
      })
      const parseStringPromise = promisify(xml2js.parseString)
      const result = await parseStringPromise(response.data)
      return result
    } catch (error) {
      console.error(`Error fetching VLC status: ${error.message}`)
      throw error
    }
  }
  // 发送 VLC HTTP 请求的函数
  async sendVlcHttpCommand(command) {
    const {
      port,
      password
    } = await this.getVlcConfig()
    const url = `http://${SystemConfig.vlcHttpHost}:${port}/requests/status.xml?command=${command}`
    console.log('sendVlcHttpCommand:', url)
    try {
      const response = await axios.get(url, {
        auth: {
          username: '',
          password: password,
        },
      })
      //console.log('vlc http api result:', response.data)
      return response.data
    } catch (error) {
      console.error(`Error sending VLC http command: ${error.message}`)
      throw error
    }
  }

到这里,我们已经实现了electron 操作vlc播放器的大部分需求了. 那最后我们还要在实现一个截取当前播放帧画面.对于它我们来看下该如何实现.

实现截图

终端指令和vlc接口都不好使的情况下,我们能想到的是ffmpeg.那这样我们的实现方式就有了: 获取当前播放的视频信息->判断有没有在播的视频->存在信息拿到播放路径和当前播放时间->调用ffmpeg指令

当然,我们这里的场景方案只针对单开的情况,如果vlc开启多开情况(好像可以)就不太对了,不过vlc接口获取的是一个播放列表,说不定可以操作.

  async takeScreenshot(videoPath, videoTime, outputDir) {
    const outputFilePath = path.join(outputDir, 'vlc-local-video-snapshot.png')
    return new Promise((resolve, reject) => {
      ffmpeg(decodeURIComponent(videoPath))
        .seekInput(videoTime)
        .outputOptions('-frames:v 1')
        .output(outputFilePath)
        .on('end', () => {
          console.log(`VlC Local Video Screenshot saved to ${outputFilePath}`)
          resolve(outputFilePath)
        })
        .on('error', (err) => {
          console.error(
            `VlC Local Video Error taking screenshot: ${err.message}`,
          )
          reject(err)
        })
        .run()
    })
  }

总结

通过以上操作,我们完成了electron调用vlc播放器的需求,后续关于如何自己实现ab操作有感兴趣的朋友可以留言.下一期,小编将出一期 electron 调用 potPlayer播放器的文章.

相关推荐

Go语言泛型-泛型约束与实践(go1.7泛型)

来源:械说在Go语言中,Go泛型-泛型约束与实践部分主要探讨如何定义和使用泛型约束(Constraints),以及如何在实际开发中利用泛型进行更灵活的编程。以下是详细内容:一、什么是泛型约束?**泛型...

golang总结(golang实战教程)

基础部分Go语言有哪些优势?1简单易学:语法简洁,减少了代码的冗余。高效并发:内置强大的goroutine和channel,使并发编程更加高效且易于管理。内存管理:拥有自动垃圾回收机制,减少内...

Go 官宣:新版 Protobuf API(go pro版本)

原文作者:JoeTsai,DamienNeil和HerbieOng原文链接:https://blog.golang.org/a-new-go-api-for-protocol-buffer...

Golang开发的一些注意事项(一)(golang入门项目)

1.channel关闭后读的问题当channel关闭之后再去读取它,虽然不会引发panic,但会直接得到零值,而且ok的值为false。packagemainimport"...

golang 托盘菜单应用及打开系统默认浏览器

之前看到一个应用,用go语言编写,说是某某程序的windows图形化客户端,体验一下发现只是一个托盘,然后托盘菜单的控制面板功能直接打开本地浏览器访问程序启动的webserver网页完成gui相关功...

golang标准库每日一库之 io/ioutil

一、核心函数概览函数作用描述替代方案(Go1.16+)ioutil.ReadFile(filename)一次性读取整个文件内容(返回[]byte)os.ReadFileioutil.WriteFi...

文件类型更改器——GoLang 中的 CLI 工具

我是如何为一项琐碎的工作任务创建一个简单的工具的,你也可以上周我开始玩GoLang,它是一种由Google制作的类C编译语言,非常轻量和快速,事实上它经常在Techempower的基准测...

Go (Golang) 中的 Channels 简介(golang channel长度和容量)

这篇文章重点介绍Channels(通道)在Go中的工作方式,以及如何在代码中使用它们。在Go中,Channels是一种编程结构,它允许我们在代码的不同部分之间移动数据,通常来自不同的goro...

Golang引入泛型:Go将Interface「」替换为“Any”

现在Go将拥有泛型:Go将Interface{}替换为“Any”,这是一个类型别名:typeany=interface{}这会引入了泛型作好准备,实际上,带有泛型的Go1.18Beta...

一文带你看懂Golang最新特性(golang2.0特性)

作者:腾讯PCG代码委员会经过十余年的迭代,Go语言逐渐成为云计算时代主流的编程语言。下到云计算基础设施,上到微服务,越来越多的流行产品使用Go语言编写。可见其影响力已经非常强大。一、Go语言发展历史...

Go 每日一库之 java 转 go 遇到 Apollo?让 agollo 来平滑迁移

以下文章来源于GoOfficialBlog,作者GoOfficialBlogIntroductionagollo是Apollo的Golang客户端Apollo(阿波罗)是携程框架部门研...

Golang使用grpc详解(golang gcc)

gRPC是Google开源的一种高性能、跨语言的远程过程调用(RPC)框架,它使用ProtocolBuffers作为序列化工具,支持多种编程语言,如C++,Java,Python,Go等。gR...

Etcd服务注册与发现封装实现--golang

服务注册register.gopackageregisterimport("fmt""time"etcd3"github.com/cor...

Golang:将日志以Json格式输出到Kafka

在上一篇文章中我实现了一个支持Debug、Info、Error等多个级别的日志库,并将日志写到了磁盘文件中,代码比较简单,适合练手。有兴趣的可以通过这个链接前往:https://github.com/...

如何从 PHP 过渡到 Golang?(php转golang)

我是PHP开发者,转Go两个月了吧,记录一下使用Golang怎么一步步开发新项目。本着有坑填坑,有错改错的宗旨,从零开始,开始学习。因为我司没有专门的Golang大牛,所以我也只能一步步自己去...