Video Controls are not working on mobile devices

i am making a custom video player in next js using the react-player library, in addition to the normal controls i am adding a thumbnail preview onhover in desktop and ontouch move in case of touc devices controls are working fine on the desktop, but when i open it in ios or android devices video player is not seeking the video to the touch location and upon moving finger on the seekbar it only shows the preview of the video and slider doesnot changes its position,

VideoPlayer component looks like this


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;

and the VideoControls look like this


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;

i have tried using the ref values directly into these touch functions and also tried without the preventDefault function, but still it is not working

New contributor

Muhammad Ishaq Saqib is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.

Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa Dịch vụ tổ chức sự kiện 5 sao Thông tin về chúng tôi Dịch vụ sinh nhật bé trai Dịch vụ sinh nhật bé gái Sự kiện trọn gói Các tiết mục giải trí Dịch vụ bổ trợ Tiệc cưới sang trọng Dịch vụ khai trương Tư vấn tổ chức sự kiện Hình ảnh sự kiện Cập nhật tin tức Liên hệ ngay Thuê chú hề chuyên nghiệp Tiệc tất niên cho công ty Trang trí tiệc cuối năm Tiệc tất niên độc đáo Sinh nhật bé Hải Đăng Sinh nhật đáng yêu bé Khánh Vân Sinh nhật sang trọng Bích Ngân Tiệc sinh nhật bé Thanh Trang Dịch vụ ông già Noel Xiếc thú vui nhộn Biểu diễn xiếc quay đĩa Dịch vụ tổ chức tiệc uy tín Khám phá dịch vụ của chúng tôi Tiệc sinh nhật cho bé trai Trang trí tiệc cho bé gái Gói sự kiện chuyên nghiệp Chương trình giải trí hấp dẫn Dịch vụ hỗ trợ sự kiện Trang trí tiệc cưới đẹp Khởi đầu thành công với khai trương Chuyên gia tư vấn sự kiện Xem ảnh các sự kiện đẹp Tin mới về sự kiện Kết nối với đội ngũ chuyên gia Chú hề vui nhộn cho tiệc sinh nhật Ý tưởng tiệc cuối năm Tất niên độc đáo Trang trí tiệc hiện đại Tổ chức sinh nhật cho Hải Đăng Sinh nhật độc quyền Khánh Vân Phong cách tiệc Bích Ngân Trang trí tiệc bé Thanh Trang Thuê dịch vụ ông già Noel chuyên nghiệp Xem xiếc khỉ đặc sắc Xiếc quay đĩa thú vị
Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa
Thiết kế website Thiết kế website Thiết kế website Cách kháng tài khoản quảng cáo Mua bán Fanpage Facebook Dịch vụ SEO Tổ chức sinh nhật