立即在 React 中交换视频组件

Swap video components in React with no delay

我有一个显示 video 元素的应用程序。

我希望用户能够点击一个按钮并观看另一个视频。但是,在更改 src 属性时,在呈现下一个 mp4 时会出现轻微的白色闪光。

为了解决这个问题,我相信我需要加载一个,预加载另一个并在准备好时更改它们。

我不确定如何在 React 中执行此操作。

import React, { Component } from 'react';

import './App.css'

import one from './one.mp4';
import two from './two.mp4';
import three from './three.mp4';
import four from './four.mp4';

class App extends Component {

  playlist = [one, two, three, four]

  shuffle = (array) => array.sort(() => Math.random() - 0.5);

  getRandomVideo = () => this.shuffle(this.playlist)[Math.floor(Math.random() * this.playlist.length)] 

  state = { 
    playerOne: {
      isVisible: true,
      inputSrc: this.getRandomVideo()
    },
    playerTwo: {
      isVisible: false,
      inputSrc: this.getRandomVideo()
    } 
  }

  onTap = () => this.setState(prevState => ({ 
    playerOne: {
      isVisible: !prevState.playerOne.isVisible,
      inputSrc: this.getRandomVideo()
    },
    playerTwo: {
      isVisible: !prevState.playerTwo.isVisible,
      inputSrc: this.getRandomVideo()
    } 
  }));

  render() {
    const { playerOne, playerTwo } = this.state
    return (
      <div>
        <div className="outer">
          <button onClick={this.onTap}>Toggle</button>
          {playerOne.isVisible &&  <VideoPlayer src={playerOne.inputSrc} /> }
          {playerTwo.isVisible && <VideoPlayer src={playerTwo.inputSrc} /> }
        </div>
      </div>
    )
  }
}

export default App

const VideoPlayer = ({ src }) => {
  return (
    <video key={src} preload autoPlay loop muted><source src={src} type='video/mp4' /></video>
  )
}

您可以在 video 元素中设置 background 属性 并使用一些 svg 动画来处理闪烁。

例子

const { useState, useEffect } = React;

const getVideos = () => Promise.resolve([
{id: 1,
src: "https://archive.org/download/BigBuckBunny_124/Content/big_buck_bunny_720p_surround.mp4"},
{
id: 2,
src: "https://archive.org/download/ElephantsDream/ed_1024_512kb.mp4"}
])

const App = () => {
  const [{videos, video}, setData] = useState({
    videos: [],
    video: {
      id: -1,
      src: null
    }
  });

  useEffect(() => {
    let isUnmounted = false;
    
    getVideos().then(videos => {
      if(isUnmounted) {
        return;
      }
    
      setData(data => ({
        ...data,
        videos,
        video: videos[0]
      }))
    })
    
    return () => {
      isUnmounted = true;
    }
  
  }, [])
  
  const onNextVideo = () => {
    setData(data => ({
      ...data,
      video: data.videos.find(pr => pr.id !== video.id)
    }))
  }

  return <div>
    <video preload="true" autoPlay loop muted key={video.id}>
      <source
        src={video.src}
        type="video/mp4"/>
      </video>
      <div>
        <button onClick={onNextVideo}>Next Video</button>
      </div>
  </div>
}

ReactDOM.render(
    <App />,
    document.getElementById('root')
  );
video {
    width: 300px;
    height: 200px;
    object-fit: cover;
    background-size: contain;
    background-position: center;
    background-repeat: no-repeat;
    background-color: black;
    background-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI0NSIgaGVpZ2h0PSI0NSIgdmlld0JveD0iMCAwIDQ1IDQ1IiBzdHJva2U9IiNmZmYiPgo8cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSJibGFjayIvPgogICAgPGcgZmlsbD0ibm9uZSIgZmlsbC1ydWxlPSJldmVub2RkIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgxIDEpIiBzdHJva2Utd2lkdGg9IjIiPgogICAgICAgIDxjaXJjbGUgY3g9IjIyIiBjeT0iMjIiIHI9IjExLjA2NTYiIHN0cm9rZS1vcGFjaXR5PSIwIj4KICAgICAgICAgICAgPGFuaW1hdGUgYXR0cmlidXRlTmFtZT0iciIgYmVnaW49IjEuNXMiIGR1cj0iM3MiIHZhbHVlcz0iNjsyMiIgY2FsY01vZGU9ImxpbmVhciIgcmVwZWF0Q291bnQ9ImluZGVmaW5pdGUiLz4KICAgICAgICAgICAgPGFuaW1hdGUgYXR0cmlidXRlTmFtZT0ic3Ryb2tlLW9wYWNpdHkiIGJlZ2luPSIxLjVzIiBkdXI9IjNzIiB2YWx1ZXM9IjE7MCIgY2FsY01vZGU9ImxpbmVhciIgcmVwZWF0Q291bnQ9ImluZGVmaW5pdGUiLz4KICAgICAgICAgICAgPGFuaW1hdGUgYXR0cmlidXRlTmFtZT0ic3Ryb2tlLXdpZHRoIiBiZWdpbj0iMS41cyIgZHVyPSIzcyIgdmFsdWVzPSIyOzAiIGNhbGNNb2RlPSJsaW5lYXIiIHJlcGVhdENvdW50PSJpbmRlZmluaXRlIi8+CiAgICAgICAgPC9jaXJjbGU+CiAgICAgICAgPGNpcmNsZSBjeD0iMjIiIGN5PSIyMiIgcj0iMTkuMDY1NiIgc3Ryb2tlLW9wYWNpdHk9IjAiPgogICAgICAgICAgICA8YW5pbWF0ZSBhdHRyaWJ1dGVOYW1lPSJyIiBiZWdpbj0iM3MiIGR1cj0iM3MiIHZhbHVlcz0iNjsyMiIgY2FsY01vZGU9ImxpbmVhciIgcmVwZWF0Q291bnQ9ImluZGVmaW5pdGUiLz4KICAgICAgICAgICAgPGFuaW1hdGUgYXR0cmlidXRlTmFtZT0ic3Ryb2tlLW9wYWNpdHkiIGJlZ2luPSIzcyIgZHVyPSIzcyIgdmFsdWVzPSIxOzAiIGNhbGNNb2RlPSJsaW5lYXIiIHJlcGVhdENvdW50PSJpbmRlZmluaXRlIi8+CiAgICAgICAgICAgIDxhbmltYXRlIGF0dHJpYnV0ZU5hbWU9InN0cm9rZS13aWR0aCIgYmVnaW49IjNzIiBkdXI9IjNzIiB2YWx1ZXM9IjI7MCIgY2FsY01vZGU9ImxpbmVhciIgcmVwZWF0Q291bnQ9ImluZGVmaW5pdGUiLz4KICAgICAgICA8L2NpcmNsZT4KICAgICAgICA8Y2lyY2xlIGN4PSIyMiIgY3k9IjIyIiByPSIzLjc5OTE4Ij4KICAgICAgICAgICAgPGFuaW1hdGUgYXR0cmlidXRlTmFtZT0iciIgYmVnaW49IjBzIiBkdXI9IjEuNXMiIHZhbHVlcz0iNjsxOzI7Mzs0OzU7NiIgY2FsY01vZGU9ImxpbmVhciIgcmVwZWF0Q291bnQ9ImluZGVmaW5pdGUiLz4KICAgICAgICA8L2NpcmNsZT4KICAgIDwvZz4KPC9zdmc+");
    }
<script src="https://unpkg.com/react/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>

<div id="root"></div>