用JavaScript和React开发一个自定义的视频播放器

首先

为了能随时查阅在个人开发过程中获取的知识,这篇文章的目的是保存而编写的,因此可能有很多地方言不足、不适当。如果您发现了问题所在,请告知我,我将不胜感激。

如果将视频播放器嵌入到Web应用程序中,我认为首选应考虑使用现有的出色模块。然而,这次我想兼顾学习,故意自己制作。

只需要一个选项, 请将以下内容用中文重述:

前提

    • Node.jsのインストールが済んでいる

Material UIのインストールが済んでいる

虽然Material UI不是必需的,但我们将使用它来制作视频播放按钮和滑动条等功能。当然,我们也可以自己制作这些功能,但为了避免偏离主题,我们会使用现有的软件包。

准备一个适当的目录,在其中进行开发。目录名称可以任意选择。可以使用create-react-app,也可以自己进行设置。在这种情况下,可以参考以下链接。

 

若要使用Webpack嵌入视频,则需要获取和配置file-loader。

我想要创建的东西

    • 再生される動画とシークバー、経過時間が連動している

 

    • シークバーを動かすことで、動画の経過時間を動的に変化できる

 

    動画の再生 / 停止とボタンを連動させる

现在我们开始实施。

实施

由于将所有内容集中在一个文件中会变得冗长且难以管理,所以我希望将其分成几个文件来实现。另外,我在创建时使用的视频文件cat.mp4是从Pixabay获取的。猫咪很可爱。。。

再 生 / 停 止 按 钮 / zhǐ àn niǔ)

首先,我们将进行按钮相关的实现。从父组件中,我们接收到视频是否正在播放的 isPlaying 以及控制视频播放/暂停的 handlePlayPause。为了美化界面,我们还添加了跳过按钮的实现,但它本身并不具备功能。根据需要,我们也可以进行功能扩展。

import React from "react"
import Stack from "@mui/material/Stack";
import IconButton from "@mui/material/IconButton";
import PlayArrowIcon from '@mui/icons-material/PlayArrow';
import SkipNextIcon from '@mui/icons-material/SkipNext';
import SkipPreviousIcon from '@mui/icons-material/SkipPrevious';
import PauseIcon from '@mui/icons-material/Pause';

export default function PlayPauseButtons(props) {
  const {isPlaying, handlePlayPause} = props

  return (
    <Stack direction="row" alignItems="center">
      <IconButton><SkipNextIcon /></IconButton>
      {isPlaying ?
        <IconButton onClick={handlePlayPause}><PauseIcon /></IconButton> :
        <IconButton onClick={handlePlayPause}><PlayArrowIcon /></IconButton>
      }
      <IconButton><SkipPreviousIcon /></IconButton>
    </Stack>
  )
}

滑动条

接下来,我们需要实现滑动条。我们从父组件接收到当前视频的时间(currentInSec)、视频的长度(durationInSec),以及用于同步滑动条和当前视频时间的handleSeekbarValue。为了将当前时间和视频长度以秒为单位接收到并将其分割成分和秒的函数sec2min和将数字填充到指定位数的函数zeroPadding也需要实现。

import React from "react"
import Stack from '@mui/material/Stack'
import Slider from "@mui/material/Slider"

export default function Seekbar(props) {
  const {currentInSec, handleSeekbarValue, durationInSec} = props
  const current = sec2min(currentInSec)
  const duration = sec2min(durationInSec)

  function sec2min(sec) {
    const min = Math.floor(sec / 60)
    const secrem = Math.floor(sec % 60)
    return {
      min: min,
      sec: secrem,
    }
  }

  function zeroPadding(num, len) {
    if (len < num.toString().length) {
      console.error('指定桁数よりも大きな数字が入力されました。')
      return num
    }
    return (Array(len).join('0') + num).slice(-len)
  }

  return (
    <Stack spacing={2} direction="row" alignItems="center">
      <p>{current.min}:{zeroPadding(current.sec, 2)} / {duration.min}:{zeroPadding(duration.sec, 2)}</p>
      <Slider
        value={currentInSec}
        min={0}
        max={durationInSec}
        onChange={handleSeekbarValue}
        step={0.2}
      />
    </Stack>
  )
}

主要组件

在导入先前创建的组件的同时,实现其他必要的功能。主要组件作为React的状态变量,拥有视频是否正在播放(isPlaying)、当前视频时间(currentInSec)和视频长度(durationInSec)。然后使用React.useRef将它与视频元素绑定(videoRef)。这样可以更容易地获取视频的播放/暂停以及属性值等。

import React from "react"
import PlayPauseButtons from "./buttons"
import Seekbar from "./seekbar"

import video from '../../assets/cat.mp4'

export default function VideoPlayer() {
  const [isPlaying, setIsPlaying] = React.useState(false)
  const [currentInSec, setCurrentInSec] = React.useState(0)
  const [durationInSec, setDurationInSec] = React.useState(0)

  const videoRef = React.useRef(null)

  function handlePlayPause() {
    isPlaying ? videoRef.current.pause() : videoRef.current.play()
    setIsPlaying(!isPlaying)
  }

  function handleLoadedMetadata() {
    if (videoRef.current) {
      console.log(`ビデオの長さ: ${videoRef.current.duration}秒`)
      setDurationInSec(videoRef.current.duration)
    } else {
      console.error('ビデオのメタデータを取得不能')
    }
  }

  function handleSeekbarValue(event) {
    setCurrentInSec(event.target.value)
    videoRef.current.currentTime = event.target.value
  }

  React.useEffect(function() {
    document.title="Home made video player"
  }, [])
  
  React.useEffect(function() {
    if (isPlaying) {
      const interval = setInterval(function() {
          setCurrentInSec(videoRef.current.currentTime)
      }, 200)
      return function() {clearInterval(interval)}
    }
  }, [isPlaying])

  return (
    <div className="m-4">
      <video
        ref={videoRef}
        src={video}
        width="100%"
        onClick={handlePlayPause}
        onLoadedMetadata={handleLoadedMetadata}
      />
      <PlayPauseButtons
        isPlaying={isPlaying}
        handlePlayPause={handlePlayPause}
      />
      <Seekbar
        currentInSec={currentInSec}
        handleSeekbarValue={handleSeekbarValue}
        durationInSec={durationInSec}
      />
    </div>
  )
}

我想详细讨论一下主要组件的实现。

处理加载的元数据()

video标签的onLoadedMetadata属性可以指定在视频的元数据加载完成时要执行的函数。如果在等待元数据完成之前访问video元素,则videoRef.current.duration = NaN会被返回,并且无法正确获取值。

  function handleLoadedMetadata() {
    if (videoRef.current) {
      console.log(`ビデオの長さ: ${videoRef.current.duration}秒`)
      setDurationInSec(videoRef.current.duration)
    } else {
      console.error('ビデオのメタデータを取得不能')
    }
  }
  ...
        <video
        ref={videoRef}
        src={video}
        width="100%"
        onClick={handlePlayPause}
        onLoadedMetadata={handleLoadedMetadata}
      />

两次使用React.useEffect(…)。

React.useEffect()函数是在渲染之后执行的函数,通过给定依赖关系来控制触发条件。
前者只会在初次渲染时执行,并且会改变HTML的标题。
后者只会在isPlaying改变时执行,内部使用setInterval()函数进行重复操作,每0.2秒获取一次视频的当前时间。当前时间用于更新currentInSec,并且会反映在进度条的样式上。

  React.useEffect(function() {
    document.title="Home made video player"
  }, [])
  
  React.useEffect(function() {
    if (isPlaying) {
      const interval = setInterval(function() {
        setCurrentInSec(videoRef.current.currentTime)
      }, 200)
      return function() {clearInterval(interval)}
    }
  }, [isPlaying])

以上是实现的全部内容。

制作完成的东西

cat.gif

以上就是全部内容。非常感谢您的阅读。

广告
将在 10 秒后关闭
bannerAds