如何在 React 中创建视频播放器 让我们来编码

2025-05-28

如何在 React 中创建视频播放器

让我们开始编码

最近我最感兴趣的事情之一是创建一个完全定制的视频播放器。显然,现在我们有一些提供小部件的服务可以在我们的网站上使用。

或者,另一方面,您已经拥有可以安装并开始使用的依赖项。但这些功能是有代价的,在这种情况下,就是缺乏定制或难以定制。

这就是我有创建自己的视频播放器的想法的原因,显然它并不像我想象的那么难,而且最终我发现它很有趣。

正是由于这个原因,我才有了写这篇文章的想法,逐步解释如何制作一个简单的视频播放器,但用同样的逻辑,你可以走得更远。

在今天的例子中,我们将使用这个视频,它有声音并且完全免费。

让我们开始编码

今天我们不会使用任何外部依赖项,因此您将完全熟悉所有内容。

关于样式,最后我会给出 CSS 代码,这是因为本文的重点是讲授视频播放器工作背后的逻辑。

我要求你做的第一件事是下载上面提到的视频,然后将文件重命名为video.mp4。最后在你的项目中创建一个名为的文件夹assets,并将文件拖到该文件夹​​中。

为了避免将代码放在单个文件中,我们需要创建自己的钩子来负责控制视频播放器的整个操作。

// @src/hooks/useVideoPlayer.js

const useVideoPlayer = () => {
  // ...
};

export default useVideoPlayer;
Enter fullscreen mode Exit fullscreen mode

在我们的钩子中,我们将只使用两个 React 钩子,useState()useEffect()

// @src/hooks/useVideoPlayer.js

import { useState, useEffect } from "react";

const useVideoPlayer = () => {
  // ...
};

export default useVideoPlayer;
Enter fullscreen mode Exit fullscreen mode

现在我们可以开始创建状态,我们称之为playerState。这个状态有四个属性:isPlaying、isMuted、progress 和 speed。

// @src/hooks/useVideoPlayer.js

import { useState, useEffect } from "react";

const useVideoPlayer = () => {
  const [playerState, setPlayerState] = useState({
    isPlaying: false,
    progress: 0,
    speed: 1,
    isMuted: false,
  });
  // ...
};

export default useVideoPlayer;
Enter fullscreen mode Exit fullscreen mode

我希望你记住的一件事是,我们的钩子必须采用一个参数,在这种情况下,它将是我们视频的引用,我们将其命名为 videoElement。

// @src/hooks/useVideoPlayer.js

import { useState, useEffect } from "react";

const useVideoPlayer = (videoElement) => {
  const [playerState, setPlayerState] = useState({
    isPlaying: false,
    progress: 0,
    speed: 1,
    isMuted: false,
  });
  // ...
};

export default useVideoPlayer;
Enter fullscreen mode Exit fullscreen mode

现在我们可以创建一个函数来判断播放器是否暂停。为此,我们将保留 playerState 中所有其他属性的值,并且每次执行该函数时,都会返回 isPlaying 当前状态的逆值。

// @src/hooks/useVideoPlayer.js

import { useState, useEffect } from "react";

const useVideoPlayer = (videoElement) => {
  const [playerState, setPlayerState] = useState({
    isPlaying: false,
    progress: 0,
    speed: 1,
    isMuted: false,
  });

  const togglePlay = () => {
    setPlayerState({
      ...playerState,
      isPlaying: !playerState.isPlaying,
    });
  };
  // ...
};

export default useVideoPlayer;
Enter fullscreen mode Exit fullscreen mode

现在我们需要useEffect()通过 isPlaying 属性的值来暂停或不暂停视频。

// @src/hooks/useVideoPlayer.js

import { useState, useEffect } from "react";

const useVideoPlayer = (videoElement) => {
  const [playerState, setPlayerState] = useState({
    isPlaying: false,
    progress: 0,
    speed: 1,
    isMuted: false,
  });

  const togglePlay = () => {
    setPlayerState({
      ...playerState,
      isPlaying: !playerState.isPlaying,
    });
  };

  useEffect(() => {
    playerState.isPlaying
      ? videoElement.current.play()
      : videoElement.current.pause();
  }, [playerState.isPlaying, videoElement]);
  // ...
};

export default useVideoPlayer;
Enter fullscreen mode Exit fullscreen mode

现在我们必须创建一个函数来帮助我们了解视频的进度,即根据视频的时长,我们希望进度条显示我们已经看了多少视频。

为此,我们将创建一个名为 的函数handleOnTimeUpdate(),用于计算视频已观看量以及剩余观看量。之后,我们将保留状态中所有其他属性的值,并仅更新进度值。

// @src/hooks/useVideoPlayer.js

import { useState, useEffect } from "react";

const useVideoPlayer = (videoElement) => {
  const [playerState, setPlayerState] = useState({
    isPlaying: false,
    progress: 0,
    speed: 1,
    isMuted: false,
  });

  const togglePlay = () => {
    setPlayerState({
      ...playerState,
      isPlaying: !playerState.isPlaying,
    });
  };

  useEffect(() => {
    playerState.isPlaying
      ? videoElement.current.play()
      : videoElement.current.pause();
  }, [playerState.isPlaying, videoElement]);

  const handleOnTimeUpdate = () => {
    const progress = (videoElement.current.currentTime / videoElement.current.duration) * 100;
    setPlayerState({
      ...playerState,
      progress,
    });
  };
  // ...
};

export default useVideoPlayer;
Enter fullscreen mode Exit fullscreen mode

我们想要实现的功能之一是可以拖动进度条,这样我们就可以选择在哪里观看视频。

这样,我们将创建一个名为的函数handleVideoProgress(),它将有一个参数,在本例中就是事件。

然后,我们将事件值从字符串转换为数字。这是因为我们想直接告诉 videoElement 当前观看时长等于我们手动更改的值。最后,我们保留状态中所有其他属性的值,只更新进度。

// @src/hooks/useVideoPlayer.js

import { useState, useEffect } from "react";

const useVideoPlayer = (videoElement) => {
  const [playerState, setPlayerState] = useState({
    isPlaying: false,
    progress: 0,
    speed: 1,
    isMuted: false,
  });

  const togglePlay = () => {
    setPlayerState({
      ...playerState,
      isPlaying: !playerState.isPlaying,
    });
  };

  useEffect(() => {
    playerState.isPlaying
      ? videoElement.current.play()
      : videoElement.current.pause();
  }, [playerState.isPlaying, videoElement]);

  const handleOnTimeUpdate = () => {
    const progress = (videoElement.current.currentTime / videoElement.current.duration) * 100;
    setPlayerState({
      ...playerState,
      progress,
    });
  };

  const handleVideoProgress = (event) => {
    const manualChange = Number(event.target.value);
    videoElement.current.currentTime = (videoElement.current.duration / 100) * manualChange;
    setPlayerState({
      ...playerState,
      progress: manualChange,
    });
  };
  // ...
};

export default useVideoPlayer;
Enter fullscreen mode Exit fullscreen mode

我们想要实现的另一个功能是视频播放速度,因为我相信并不是每个人都是 1.0 倍速的粉丝,而且有些人会以 1.25 倍速观看视频。

为此,我们将创建一个名为的函数handleVideoSpeed(),该函数将接收一个事件作为单个参数,然后将该事件的值转换为数字,最后我们将告诉 videoElement 播放速率等于事件值。

在我们的状态下,我们保留除速度之外的所有属性的值。

// @src/hooks/useVideoPlayer.js

import { useState, useEffect } from "react";

const useVideoPlayer = (videoElement) => {
  const [playerState, setPlayerState] = useState({
    isPlaying: false,
    progress: 0,
    speed: 1,
    isMuted: false,
  });

  const togglePlay = () => {
    setPlayerState({
      ...playerState,
      isPlaying: !playerState.isPlaying,
    });
  };

  useEffect(() => {
    playerState.isPlaying
      ? videoElement.current.play()
      : videoElement.current.pause();
  }, [playerState.isPlaying, videoElement]);

  const handleOnTimeUpdate = () => {
    const progress = (videoElement.current.currentTime / videoElement.current.duration) * 100;
    setPlayerState({
      ...playerState,
      progress,
    });
  };

  const handleVideoProgress = (event) => {
    const manualChange = Number(event.target.value);
    videoElement.current.currentTime = (videoElement.current.duration / 100) * manualChange;
    setPlayerState({
      ...playerState,
      progress: manualChange,
    });
  };

  const handleVideoSpeed = (event) => {
    const speed = Number(event.target.value);
    videoElement.current.playbackRate = speed;
    setPlayerState({
      ...playerState,
      speed,
    });
  };
  // ...
};

export default useVideoPlayer;
Enter fullscreen mode Exit fullscreen mode

我想要添加的最后一个功能是视频静音和取消静音的功能。其计算逻辑与播放/暂停非常相似。

// @src/hooks/useVideoPlayer.js

import { useState, useEffect } from "react";

const useVideoPlayer = (videoElement) => {
  const [playerState, setPlayerState] = useState({
    isPlaying: false,
    progress: 0,
    speed: 1,
    isMuted: false,
  });

  const togglePlay = () => {
    setPlayerState({
      ...playerState,
      isPlaying: !playerState.isPlaying,
    });
  };

  useEffect(() => {
    playerState.isPlaying
      ? videoElement.current.play()
      : videoElement.current.pause();
  }, [playerState.isPlaying, videoElement]);

  const handleOnTimeUpdate = () => {
    const progress = (videoElement.current.currentTime / videoElement.current.duration) * 100;
    setPlayerState({
      ...playerState,
      progress,
    });
  };

  const handleVideoProgress = (event) => {
    const manualChange = Number(event.target.value);
    videoElement.current.currentTime = (videoElement.current.duration / 100) * manualChange;
    setPlayerState({
      ...playerState,
      progress: manualChange,
    });
  };

  const handleVideoSpeed = (event) => {
    const speed = Number(event.target.value);
    videoElement.current.playbackRate = speed;
    setPlayerState({
      ...playerState,
      speed,
    });
  };

  const toggleMute = () => {
    setPlayerState({
      ...playerState,
      isMuted: !playerState.isMuted,
    });
  };

  useEffect(() => {
    playerState.isMuted
      ? (videoElement.current.muted = true)
      : (videoElement.current.muted = false);
  }, [playerState.isMuted, videoElement]);
  // ...
};

export default useVideoPlayer;
Enter fullscreen mode Exit fullscreen mode

最后,只需返回我们的状态和所有创建的函数。

// @src/hooks/useVideoPlayer.js

import { useState, useEffect } from "react";

const useVideoPlayer = (videoElement) => {
  const [playerState, setPlayerState] = useState({
    isPlaying: false,
    progress: 0,
    speed: 1,
    isMuted: false,
  });

  const togglePlay = () => {
    setPlayerState({
      ...playerState,
      isPlaying: !playerState.isPlaying,
    });
  };

  useEffect(() => {
    playerState.isPlaying
      ? videoElement.current.play()
      : videoElement.current.pause();
  }, [playerState.isPlaying, videoElement]);

  const handleOnTimeUpdate = () => {
    const progress = (videoElement.current.currentTime / videoElement.current.duration) * 100;
    setPlayerState({
      ...playerState,
      progress,
    });
  };

  const handleVideoProgress = (event) => {
    const manualChange = Number(event.target.value);
    videoElement.current.currentTime = (videoElement.current.duration / 100) * manualChange;
    setPlayerState({
      ...playerState,
      progress: manualChange,
    });
  };

  const handleVideoSpeed = (event) => {
    const speed = Number(event.target.value);
    videoElement.current.playbackRate = speed;
    setPlayerState({
      ...playerState,
      speed,
    });
  };

  const toggleMute = () => {
    setPlayerState({
      ...playerState,
      isMuted: !playerState.isMuted,
    });
  };

  useEffect(() => {
    playerState.isMuted
      ? (videoElement.current.muted = true)
      : (videoElement.current.muted = false);
  }, [playerState.isMuted, videoElement]);

  return {
    playerState,
    togglePlay,
    handleOnTimeUpdate,
    handleVideoProgress,
    handleVideoSpeed,
    toggleMute,
  };
};

export default useVideoPlayer;
Enter fullscreen mode Exit fullscreen mode

现在我们可以开始处理我们的App.jsx组件了,记录一下,使用的图标库是Boxicons,字体是DM Sans

首先,我将给出我们的 css 代码App.css

body {
  background: #EEEEEE;
}

.container {
  display: flex;
  justify-content: center;
  align-items: center;
  height: 100vh;
}

h1 {
  color: white;
}

video {
  width: 100%;
}

.video-wrapper {
  width: 100%;
  max-width: 700px;
  position: relative;
  display: flex;
  justify-content: center;
  overflow: hidden;
  border-radius: 10px;
}

.video-wrapper:hover .controls {
  transform: translateY(0%);
}

.controls {
  display: flex;
  align-items: center;
  justify-content: space-evenly;
  position: absolute;
  bottom: 30px;
  padding: 14px;
  width: 100%;
  max-width: 500px;
  flex-wrap: wrap;
  background: rgba(255, 255, 255, 0.25);
  box-shadow: 0 8px 32px 0 rgba(255, 255, 255, 0.1);
  backdrop-filter: blur(4px);
  -webkit-backdrop-filter: blur(4px);
  border-radius: 10px;
  border: 1px solid rgba(255, 255, 255, 0.18);
  transform: translateY(150%);
  transition: all 0.3s ease-in-out;
}

.actions button {
  background: none;
  border: none;
  outline: none;
  cursor: pointer;
}

.actions button i {
  background-color: none;
  color: white;
  font-size: 30px;
}

input[type="range"] {
  -webkit-appearance: none !important;
  background: rgba(255, 255, 255, 0.2);
  border-radius: 20px;
  height: 4px;
  width: 350px;
}

input[type="range"]::-webkit-slider-thumb {
  -webkit-appearance: none !important;
  cursor: pointer;
  height: 6px;
}

input[type="range"]::-moz-range-progress {
  background: white;
}

.velocity {
  appearance: none;
  background: none;
  color: white;
  outline: none;
  border: none;
  text-align: center;
  font-size: 16px;
}

.mute-btn {
  background: none;
  border: none;
  outline: none;
  cursor: pointer;
}

.mute-btn i {
  background-color: none;
  color: white;
  font-size: 20px;
}
Enter fullscreen mode Exit fullscreen mode

现在我们可以开始处理我们的组件,为此我们将导入我们需要的一切,在这种情况下,它是我们的样式、我们的视频和我们的钩子。

// @src/App.jsx

import React from "react";
import "./App.css";

import video from "./assets/video.mp4";
import useVideoPlayer from "./hooks/useVideoPlayer";

const App = () => {
  // ...
};

export default App;
Enter fullscreen mode Exit fullscreen mode

然后我们导入useRef()钩子来创建 videoElement 的引用。像这样:

// @src/App.jsx

import React, { useRef } from "react";
import "./App.css";

import video from "./assets/video.mp4";
import useVideoPlayer from "./hooks/useVideoPlayer";

const App = () => {
  const videoElement = useRef(null);
  // ...
};

export default App;
Enter fullscreen mode Exit fullscreen mode

然后,我们可以从钩子中获取 playerState 和每个函数。像这样:

// @src/App.jsx

import React, { useRef } from "react";
import "./App.css";

import video from "./assets/video.mp4";
import useVideoPlayer from "./hooks/useVideoPlayer";

const App = () => {
  const videoElement = useRef(null);
  const {
    playerState,
    togglePlay,
    handleOnTimeUpdate,
    handleVideoProgress,
    handleVideoSpeed,
    toggleMute,
  } = useVideoPlayer(videoElement);
  // ...
};

export default App;
Enter fullscreen mode Exit fullscreen mode

现在我们终于可以开始处理我们的模板了,这样我们将开始处理我们的视频元素,它将具有三个道具,来源将是我们的视频,我们仍然将传递我们的参考和我们的handleOnTimeUpdate()功能。

// @src/App.jsx

import React, { useRef } from "react";
import "./App.css";

import video from "./assets/video.mp4";
import useVideoPlayer from "./hooks/useVideoPlayer";

const App = () => {
  const videoElement = useRef(null);
  const {
    playerState,
    togglePlay,
    handleOnTimeUpdate,
    handleVideoProgress,
    handleVideoSpeed,
    toggleMute,
  } = useVideoPlayer(videoElement);
  return (
    <div className="container">
      <div className="video-wrapper">
        <video
          src={video}
          ref={videoElement}
          onTimeUpdate={handleOnTimeUpdate}
        />
        // ...
      </div>
    </div>
  );
};

export default App;
Enter fullscreen mode Exit fullscreen mode

现在我们可以开始处理视频控件了,先从播放和暂停按钮开始。我们将向其传递函数togglePlay()并进行条件渲染,以便根据 isPlaying 属性的值显示相应的图标。

// @src/App.jsx

import React, { useRef } from "react";
import "./App.css";

import video from "./assets/video.mp4";
import useVideoPlayer from "./hooks/useVideoPlayer";

const App = () => {
  const videoElement = useRef(null);
  const {
    playerState,
    togglePlay,
    handleOnTimeUpdate,
    handleVideoProgress,
    handleVideoSpeed,
    toggleMute,
  } = useVideoPlayer(videoElement);
  return (
    <div className="container">
      <div className="video-wrapper">
        <video
          src={video}
          ref={videoElement}
          onTimeUpdate={handleOnTimeUpdate}
        />
        <div className="controls">
          <div className="actions">
            <button onClick={togglePlay}>
              {!playerState.isPlaying ? (
                <i className="bx bx-play"></i>
              ) : (
                <i className="bx bx-pause"></i>
              )}
            </button>
          </div>
          // ...
        </div>
      </div>
    </div>
  );
};

export default App;
Enter fullscreen mode Exit fullscreen mode

现在我们可以开始处理输入了,它将是 range 类型,最小值为 0,最大值为 100。同样,我们将传递handleVideoProgress()函数和 progress 属性的值。

// @src/App.jsx

import React, { useRef } from "react";
import "./App.css";

import video from "./assets/video.mp4";
import useVideoPlayer from "./hooks/useVideoPlayer";

const App = () => {
  const videoElement = useRef(null);
  const {
    playerState,
    togglePlay,
    handleOnTimeUpdate,
    handleVideoProgress,
    handleVideoSpeed,
    toggleMute,
  } = useVideoPlayer(videoElement);
  return (
    <div className="container">
      <div className="video-wrapper">
        <video
          src={video}
          ref={videoElement}
          onTimeUpdate={handleOnTimeUpdate}
        />
        <div className="controls">
          <div className="actions">
            <button onClick={togglePlay}>
              {!playerState.isPlaying ? (
                <i className="bx bx-play"></i>
              ) : (
                <i className="bx bx-pause"></i>
              )}
            </button>
          </div>
          <input
            type="range"
            min="0"
            max="100"
            value={playerState.progress}
            onChange={(e) => handleVideoProgress(e)}
          />
          // ...
        </div>
      </div>
    </div>
  );
};

export default App;
Enter fullscreen mode Exit fullscreen mode

现在我们将处理用于选择视频播放速度的元素。我们将向其传递 speed 属性和handleVideoSpeed()函数的值。

// @src/App.jsx

import React, { useRef } from "react";
import "./App.css";

import video from "./assets/video.mp4";
import useVideoPlayer from "./hooks/useVideoPlayer";

const App = () => {
  const videoElement = useRef(null);
  const {
    playerState,
    togglePlay,
    handleOnTimeUpdate,
    handleVideoProgress,
    handleVideoSpeed,
    toggleMute,
  } = useVideoPlayer(videoElement);
  return (
    <div className="container">
      <div className="video-wrapper">
        <video
          src={video}
          ref={videoElement}
          onTimeUpdate={handleOnTimeUpdate}
        />
        <div className="controls">
          <div className="actions">
            <button onClick={togglePlay}>
              {!playerState.isPlaying ? (
                <i className="bx bx-play"></i>
              ) : (
                <i className="bx bx-pause"></i>
              )}
            </button>
          </div>
          <input
            type="range"
            min="0"
            max="100"
            value={playerState.progress}
            onChange={(e) => handleVideoProgress(e)}
          />
          <select
            className="velocity"
            value={playerState.speed}
            onChange={(e) => handleVideoSpeed(e)}
          >
            <option value="0.50">0.50x</option>
            <option value="1">1x</option>
            <option value="1.25">1.25x</option>
            <option value="2">2x</option>
          </select>
          // ...
        </div>
      </div>
    </div>
  );
};

export default App;
Enter fullscreen mode Exit fullscreen mode

最后,同样重要的是,我们将添加一个按钮,用于控制视频的静音和取消静音。我们将向该按钮传递一个toggleMute()函数,并根据 isMuted 属性进行条件渲染,以显示相应的图标。

// @src/App.jsx

import React, { useRef } from "react";
import "./App.css";

import video from "./assets/video.mp4";
import useVideoPlayer from "./hooks/useVideoPlayer";

const App = () => {
  const videoElement = useRef(null);
  const {
    playerState,
    togglePlay,
    handleOnTimeUpdate,
    handleVideoProgress,
    handleVideoSpeed,
    toggleMute,
  } = useVideoPlayer(videoElement);
  return (
    <div className="container">
      <div className="video-wrapper">
        <video
          src={video}
          ref={videoElement}
          onTimeUpdate={handleOnTimeUpdate}
        />
        <div className="controls">
          <div className="actions">
            <button onClick={togglePlay}>
              {!playerState.isPlaying ? (
                <i className="bx bx-play"></i>
              ) : (
                <i className="bx bx-pause"></i>
              )}
            </button>
          </div>
          <input
            type="range"
            min="0"
            max="100"
            value={playerState.progress}
            onChange={(e) => handleVideoProgress(e)}
          />
          <select
            className="velocity"
            value={playerState.speed}
            onChange={(e) => handleVideoSpeed(e)}
          >
            <option value="0.50">0.50x</option>
            <option value="1">1x</option>
            <option value="1.25">1.25x</option>
            <option value="2">2x</option>
          </select>
          <button className="mute-btn" onClick={toggleMute}>
            {!playerState.isMuted ? (
              <i className="bx bxs-volume-full"></i>
            ) : (
              <i className="bx bxs-volume-mute"></i>
            )}
          </button>
        </div>
      </div>
    </div>
  );
};

export default App;
Enter fullscreen mode Exit fullscreen mode

最终结果应该如下所示:

最终应用程序

结论

一如既往,希望你觉得这篇文章有趣。如果你发现本文有任何错误,请在评论区指出。🥳

祝你度过美好的一天!🙌

文章来源:https://dev.to/franciscomendes10866/how-to-create-a-video-player-in-react-40jj
PREV
Ravot(实时仪表盘) 我构建了什么 我如何构建它(堆栈是什么?我是否遇到问题或在此过程中发现了新的东西?) 团队成员 它是如何构建的 其他资源/信息 结论
NEXT
最佳创造性解决问题技巧之一