视频笔记软件JumpVideo技术解析一:Electron案例-调用VLC播放器

          大家好,我是TheGodOfKing,是 最强考研学习神器,免费视频笔记应用JumpVideo,可以快速添加截图时间戳,支持所有笔记软件,学习效率MAX!的开发者之一,分享技术的目的是想找到更多志同道合的人,如果有大学生加入,我们还允许他把项目作为毕设(只有一个名额哟)群(689978959),那么今天要给大家分享软件框架electron一个实用小案例: 调用vlc播放器播放视频并控制vlc播放器,支持生成时间戳、播放、暂停、截图、ab片段播放、ab循环播放、快进、快退等操作
image

业务说明

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

关于VLC

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

  • 支持多种音视频格式
  • 跨平台,支持Window、Mac
    正因为VLC是跨平台的超强播放器,因此我才会选择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软件.

Pasted image 20240804042154.png

显示基本

Pasted image 20240804042224.png

显示全部

第二步:查看官方文档

文档地址: VLC HTTP requests - VideoLAN Wiki

Pasted image 20240804042305.png

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

  // 获取 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播放器的需求,如果有大佬对项目感兴趣的可以加群(689978959)私信我,如果是大学生加入我们还允许他把项目作为毕设(只有一个名额哟) ,后续关于如何自己实现ab操作有感兴趣的朋友可以留言。下一期,小编将出一期 electron 调用 potPlayer播放器的文章。

原文

https://juejin.cn/post/7399273700116955186

posted @ 2024-08-05 12:53  yangboom  阅读(104)  评论(0编辑  收藏  举报
TOP