iT邦幫忙

2024 iThome 鐵人賽

DAY 6
0

Day11 要做的是客製化的影片播放器

資料設定

const VIDEO_URL =
  "https://github.com/wesbos/JavaScript30/raw/master/11%20-%20Custom%20Video%20Player/652333414.mp4";


  const [isPlaying, setIsPlaying] = useState<boolean>(false);
  const [volume, setVolume] = useState<number>(1);
  const [isMuted, setIsMuted] = useState<boolean>(false);
  const [playbackRate, setPlaybackRate] = useState<number>(1);
  const [progress, setProgress] = useState<number>(0);

  const videoRef = useRef<HTMLVideoElement>(null);
  const progressRef = useRef<HTMLInputElement>(null);

調整進度

  const updateProgress = (): void => {
    const video = videoRef.current;
    if (video) {
      const percentage = (video.currentTime / video.duration) * 100;
      setProgress(percentage);
    }
  };

  const skip = (amount: number): void => {
    const video = videoRef.current;
    if (video) {
      video.currentTime += amount;
    }
  };

  const handleProgressChange = (e: ChangeEvent<HTMLInputElement>): void => {
    const video = videoRef.current;
    if (video) {
      const newTime = (parseFloat(e.target.value) / 100) * video.duration;
      video.currentTime = newTime;
      setProgress(parseFloat(e.target.value));
    }
  };

播放

  const togglePlay = (): void => {
    const video = videoRef.current;
    if (video) {
      if (isPlaying) {
        video.pause();
      } else {
        video.play();
      }
      setIsPlaying(!isPlaying);
    }
  };

音量

  const handleVolumeChange = (e: ChangeEvent<HTMLInputElement>): void => {
    const newVolume = parseFloat(e.target.value);
    setVolume(newVolume);
    if (videoRef.current) {
      videoRef.current.volume = newVolume;
    }
    setIsMuted(newVolume === 0);
  };

  const toggleMute = (): void => {
    const video = videoRef.current;
    if (video) {
      if (isMuted) {
        video.volume = volume;
        setIsMuted(false);
      } else {
        video.volume = 0;
        setIsMuted(true);
      }
    }
  };

速度

  const handlePlaybackRateChange = (
    e: ChangeEvent<HTMLSelectElement>
  ): void => {
    const newRate = parseFloat(e.target.value);
    setPlaybackRate(newRate);
    if (videoRef.current) {
      videoRef.current.playbackRate = newRate;
    }
  };

畫面結構

  • 先顯示影片元素

  • 控制項則是利用絕對定位印在影片上

    <div className="max-w-4xl mx-auto p-4 bg-gray-900 rounded-lg shadow-lg">
      <div className="relative overflow-hidden rounded-lg">
        <video
          ref={videoRef}
          className="w-full cursor-pointer"
          onClick={togglePlay}
          onTimeUpdate={updateProgress}
          src={VIDEO_URL}
        >
          Your browser does not support the video tag.
        </video>

        <div className="absolute bottom-0 left-0 right-0 bg-gradient-to-t from-black to-transparent p-4">
          <div className="flex items-center justify-between mb-2">
            <button
              onClick={togglePlay}
              className="text-white hover:text-blue-400 transition-colors duration-200"
            >
              {isPlaying ? (
                <PauseIcon className="w-8 h-8" />
              ) : (
                <PlayArrowIcon className="w-8 h-8" />
              )}
            </button>

            <div className="flex items-center space-x-4">
              <button
                onClick={toggleMute}
                className="text-white hover:text-blue-400 transition-colors duration-200"
              >
                {isMuted ? (
                  <VolumeOffIcon className="w-6 h-6" />
                ) : (
                  <VolumeUpIcon className="w-6 h-6" />
                )}
              </button>
              <input
                type="range"
                min="0"
                max="1"
                step="0.1"
                value={volume}
                onChange={handleVolumeChange}
                className="w-24 accent-blue-500"
              />
            </div>

            <div className="flex items-center space-x-2">
              <button
                onClick={() => skip(-10)}
                className="text-white hover:text-blue-400 transition-colors duration-200"
              >
                <Replay10Icon className="w-6 h-6" />
              </button>
              <button
                onClick={() => skip(10)}
                className="text-white hover:text-blue-400 transition-colors duration-200"
              >
                <Forward10Icon className="w-6 h-6" />
              </button>
            </div>

            <select
              value={playbackRate}
              onChange={handlePlaybackRateChange}
              className="bg-transparent text-white border border-white rounded p-1 hover:border-blue-400 transition-colors duration-200"
            >
              <option value="0.5">0.5x</option>
              <option value="1">1x</option>
              <option value="1.5">1.5x</option>
              <option value="2">2x</option>
            </select>
          </div>

          <input
            ref={progressRef}
            type="range"
            min="0"
            max="100"
            value={progress}
            onChange={handleProgressChange}
            className="w-full accent-blue-500"
          />
        </div>
      </div>
    </div>

DEMO

https://codesandbox.io/p/devbox/485dp3


上一篇
[Day10]_Hold-Shift-and-Check-Checkboxes
下一篇
[Day12]_Key-Sequence-Detection
系列文
React30——用 React 探索 JavaScript30 的魅力30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言