视频控件无法在移动设备上运行

问题描述 投票:0回答:1

我正在使用react-player库在Next JS中制作一个自定义视频播放器,除了正常的控件之外,我还在桌面上添加了悬停缩略图预览,并在触摸设备上添加了onTouchMove。控件在桌面上工作正常,但当我在 iOS 或 Android 设备上打开它时,视频播放器不会将视频搜索到触摸位置,并且在搜索栏上移动手指时,它只显示视频的预览和滑块不改变它的位置,相反,我想要的是,在视频播放期间,无论我将手指放在搜索栏上,视频都应该从那里开始播放

VideoPlayer 组件看起来像这样


const VideoPlayer = () => {

  //refs for video player and video preview
  const videoRef = useRef(null);
  const previewVideoRef = useRef(null);

  const [videoState, setVideoState] = useState({
    playing: true,
    isPlaying: true, //added to preserve the playing status of the video when seeked
    played: 0,
    playbackRate: 1.0,
    seeking: false,
    duration: 0,
    loaded: 0,
  });
  const {
    playing,
    played,
    playbackRate,
    seeking,
    duration,
    isPlaying,
    loaded,
  } = videoState;

  //showing the total duration of video
  const handleDuration = (duration) => {
    setVideoState((prevValue) => ({ ...prevValue, duration }));
  };

  //pause and play the video
  const playPauseHandler = () => {
    setVideoState((prevValue) => ({
      ...prevValue,
      playing: !prevValue.playing,
      isPlaying: !prevValue.isPlaying,
    }));
  };

  //seekbar moving with video progress
  const progressHandler = (e) => {
    if (!seeking) {
      setVideoState((prevValue) => ({ ...prevValue, ...e }));
    }
  };

  //changing seekbar position

  const seekHandler = (e, newValue) => {
    const playedValue = parseFloat(newValue);
    if (Number.isFinite(playedValue)) {
      setVideoState((prevValue) => ({ ...prevValue, played: playedValue }));
      if (seeking)
        videoRef.current.seekTo(
          previewVideoRef.current.getInternalPlayer().currentTime
        );
    }
  };

  //when seekbar is dragged with a click

  const seekMouseDownHandler = () => {
    setVideoState((prevValue) => ({
      ...prevValue,
      seeking: true,
      playing: false,
    }));
  };

  //when click is released

  const seekMouseUpHandler = (e) => {
    setVideoState((prevValue) => ({
      ...prevValue,
      seeking: false,
      playing: isPlaying,
    }));
  };

  //restart the video

  const handleRestart = () => {
    videoRef.current.seekTo(0);
    setVideoState((prevValue) => ({ ...prevValue, playing: true }));
  };

  //controlling playback rate

  const handlePlaybackRateChange = (rate) => {
    setVideoState((prevValue) => ({ ...prevValue, playbackRate: rate }));
  };
  return (
    <TransformWrapper>
      {({ zoomIn, zoomOut, ...rest }) => (
        <>
          <div className="react-player">
            <Flex
              gap={0}
              align="center"
              justify="center"
              vertical={true}
              className=""
            >
              <div className="video-player-box">
                <TransformComponent>
                  <ReactPlayer
                    ref={videoRef}
                    fallback={<Loading />}
                    url="https://test-streams.mux.dev/x36xhzz/x36xhzz.m3u8"
                    playing={playing}
                    width="100%"
                    muted={true}
                    playsinline={true}
                    onDuration={handleDuration}
                    onProgress={progressHandler}
                    playbackRate={playbackRate}
                  />
                </TransformComponent>
              </div>
              <ReactPlayer
                url="https://test-streams.mux.dev/x36xhzz/x36xhzz.m3u8"
                style={{ display: "none" }}
                ref={previewVideoRef}
                playing={false}
                loop={true}
              />

              <VideoControls
                videoRef={videoRef}
                previewVideoRef={previewVideoRef}
                playing={playing}
                onPlayPause={playPauseHandler}
                played={played}
                onSeek={seekHandler}
                onSeekMouseUp={seekMouseUpHandler}
                onSeekMouseDown={seekMouseDownHandler}
                onRestart={handleRestart}
                duration={duration}
                playbackRate={playbackRate}
                onPlaybackRateChange={handlePlaybackRateChange}
                loaded={loaded}
              />
            </Flex>
          </div>
        </>
      )}
    </TransformWrapper>
  );
};

export default VideoPlayer;

视频控件看起来像这样


function parseDate(date) {
  if (date) return dayjs(date).format("hh:mm:ss A");
  else return "";
}

const calcSliderPosition = (e) => {
  let clientX;
  // Check if it's a touch event
  if (e.type === "touchmove") {
    // Use the clientX property from the first touch point
    clientX = e.touches[0].clientX;
  } else {
    // It's a mouse event, use offsetX
    clientX = e.nativeEvent.offsetX;
  }
  return (
    (clientX < 0 ? 0 : clientX / e.target.clientWidth) *
    e.target.getAttribute("max")
  );
};

const VideoControls = ({
  playing,
  videoRef,
  previewVideoRef,
  onPlayPause,
  played,
  onSeek,
  onSeekMouseUp,
  onSeekMouseDown,
  onRestart,
  duration,
  playbackRate,
  onPlaybackRateChange,
  loaded,
}) => {
  const { details } = useAppSelector((state) => state.alarms.selectedAlarm);
  const [position, setPosition] = useState(0);
  const [previewSrc, setPreviewSrc] = useState(null);

  function handlePosition(e) {
    const THUMBNAIL_MIDDLE_POINT = 88;
    const THUMBNAIL_WIDTH = 178;
    const PRECISION_VALUE = 1;
    const TOTAL_WIDTH = e.target.offsetWidth;

    let clientX;
    // Check if it's a touch event
    if (e.type === "touchmove") {
      // Use the clientX property from the first touch point
      clientX = e.touches[0].clientX - e.target.getBoundingClientRect().left;
    } else {
      // It's a mouse event, use offsetX
      clientX = e.nativeEvent.offsetX;
    }

    //change the position only when the pointer is between the video length
    if (clientX > 1 && clientX < TOTAL_WIDTH) {
      //check for extreme left thumbnail preview
      if (clientX > THUMBNAIL_MIDDLE_POINT)
        if (clientX < TOTAL_WIDTH - THUMBNAIL_MIDDLE_POINT)
          setPosition(clientX - THUMBNAIL_MIDDLE_POINT);
        else {
          let nextPosition = clientX - clientX + TOTAL_WIDTH - THUMBNAIL_WIDTH;
          if (
            nextPosition < position - PRECISION_VALUE ||
            nextPosition > position + PRECISION_VALUE
          )
            setPosition(nextPosition);
        }
      //in case there is not enough space hold the thumnail to the left
      else {
        // let nextPosition = e.nativeEvent.layerX - e.nativeEvent.offsetX;
        let nextPosition = clientX - clientX;
        if (
          nextPosition < position - PRECISION_VALUE ||
          nextPosition > position + PRECISION_VALUE
        )
          setPosition(nextPosition);
      }
    }
  }

  const generatePreview = async (e) => {
    try {
      let seekTime = calcSliderPosition(e);
      let newTime = seekTime * previewVideoRef.current.getDuration();
      let prevTime = previewVideoRef.current.getCurrentTime();

      const PRECISION_VALUE = 0.01;
      if (
        newTime > prevTime - PRECISION_VALUE &&
        newTime < prevTime + PRECISION_VALUE
      ) {
        // console.log("do not create");
      } else {
        const canvas = document.createElement("canvas");
        const context = canvas.getContext("2d");

        previewVideoRef.current.seekTo(seekTime);
        const video = previewVideoRef.current.getInternalPlayer();
        const videoWidth = 1920 / 6;
        const videoHeight = 1080 / 6;
        setTimeout(() => {
          context.drawImage(video, 0, 0, videoWidth, videoHeight);
          setPreviewSrc(canvas.toDataURL());
        }, 100);
      }
      handlePosition(e);
    } catch (error) {
      console.error("Error generating preview:", error);
      return null;
    }
  };

  return (
    <div className="video-controls">
      <div className="slider-container">
        <progress className="seek-bar" max={1} value={loaded} />
        <input
          className="slider"
          type="range"
          min={0}
          max={0.999999}
          step="any"
          value={played}
          // onChange={(e) => onSeek(e, e.target.value)}
          onInput={(e) => onSeek(e, e.target.value)}
          onMouseUp={onSeekMouseUp}
          onMouseDown={onSeekMouseDown}
          onMouseMove={generatePreview}
          onMouseLeave={() => {
            setPosition(-100);
          }}
          onTouchStart={(e) => {
            e.preventDefault();
            onSeekMouseDown(e);
          }}
          onTouchEnd={(e) => {
            e.preventDefault();
            const newValue = e.target.value;
            onSeekMouseUp(e);
            setPosition(-100);
          }}
          onTouchMove={(e) => {
            e.preventDefault();
            generatePreview(e);
          }}
        />

        {previewSrc && position !== -100 && (
          <div className="preview-container" style={{ left: position }}>
            <Image
              priority={true}
              width={173}
              height={96.89}
              className="preview"
              src={previewSrc}
              alt="Preview"
            />
            <span class="preview_time">
              {parseDate(
                new Date(details?.occurrenceTime).getTime() +
                  previewVideoRef.current.getCurrentTime() * 1000
              )}
            </span>
          </div>
        )}
      </div>

      <Flex
        gap={0}
        align="center"
        justify="space-between"
        vertical={false}
        className="video-timer"
      >
        <div> {parseDate(details?.occurrenceTime)}</div>
        <div>
          <span className="time-spent">
            <Duration seconds={duration * played} />
          </span>
          <span className="dot" />
          <Duration seconds={duration} />
        </div>
        <div> {parseDate(details?.endTime)}</div>
      </Flex>
      <Flex gap={28} className="button-controls">
        <Image
          priority={true}
          onClick={() =>
            videoRef.current.seekTo(videoRef.current.getCurrentTime() - 5)
          }
          width={24}
          height={24}
          src="/svg/skip-video-back.svg"
          alt="skip-video-back-icon"
        />

        {!playing ? (
          <Image
            priority={true}
            width={24}
            height={24}
            src="/svg/play-video.svg"
            alt="play-icon"
            onClick={onPlayPause}
          />
        ) : (
          <Image
            priority={true}
            width={24}
            height={24}
            src="/svg/pause-video.svg"
            alt="pause-icon"
            onClick={onPlayPause}
          />
        )}

        <Image
          priority={true}
          onClick={() =>
            videoRef.current.seekTo(videoRef.current.getCurrentTime() + 5)
          }
          width={24}
          height={24}
          src="/svg/skip-video-forward.svg"
          alt="skip-forward-icon"
        />

        <Image
          priority={true}
          onClick={onRestart}
          width={24}
          height={24}
          src="/svg/restart-video.svg"
          alt="restart-icon"
        />
        <PlaybackSpeed
          playbackRate={playbackRate}
          onPlaybackRateChange={onPlaybackRateChange}
        />
      </Flex>
    </div>
  );
};

export default VideoControls;

我尝试过直接在这些触摸函数中使用引用值,并且也尝试过不使用 PreventDefault 函数,但它仍然不起作用

reactjs next.js touch-event seekbar react-player
1个回答
0
投票

使用

touches[0].clientX
进行触摸事件:

calcSliderPosition
中,不检查e.type,直接访问
touches[0].clientX
来获取触摸事件:

const calcSliderPosition = (e) => {
  let clientX;
  if (e.type === "touchmove") {
    clientX = e.touches[0].clientX;
  } else {
    clientX = e.nativeEvent.offsetX;
  }
  // ... rest of the function
}

© www.soinside.com 2019 - 2024. All rights reserved.