유튜브 자동재생 웹사이트에서 구현하기

Article

영상 자동재생 구현하기

사용자들이 영상을 더 쉽게 접할 수 있도록 자동재생 기능을 구현했습니다.

유튜브의 동작 방식을 분석한 뒤 이를 참고하여 PC와 모바일에서 각각 어떻게 동작하는지 살펴보고, 실제 구현 과정을 정리합니다.

자동재생의 두 가지 시나리오

유튜브의 자동재생은 크게 두 가지 상황으로 나눌 수 있습니다.

  1. PC에서의 영상 자동재생 (마우스 커서 기반)
  2. 스마트폰에서의 영상 자동재생 (포커싱 기준 기반)

PC에서 영상 자동재생

웹에서는 마우스 커서를 이용해 영상 재생을 제어합니다.

PC에서 자동재생

관찰 결과

  1. 마우스 올림 → 썸네일이 사라지고 영상 재생

    • 단, 커서를 올린 즉시 재생되지는 않고 약 0.2초 후에 재생됨 → 디바운싱 적용으로 추정.
  2. 영상은 즉시 이어서 재생

    • 커서를 올리는 시점에 플레이어를 새로 로드하지 않고, 이미 로드된 플레이어에서 이전 재생 지점부터 이어짐.

코드 예시

<VideoCard
  onMouseEnter={() => {
    playVideo();
  }}
  onMouseLeave={() => {
    stopVideo();
  }}
>
  {isPlaying ? null : <ThumbnailImage />}
  <ReactPlayer />
</VideoCard>
<VideoCard
  onMouseEnter={() => {
    playVideo();
  }}
  onMouseLeave={() => {
    stopVideo();
  }}
>
  {isPlaying ? null : <ThumbnailImage />}
  <ReactPlayer />
</VideoCard>
  • onMouseEnteronMouseLeave이벤트를 활용해 마우스가 올려졌는지 여부를 체크하고, 썸네일과 영상을 전환합니다.

실제 코드에서는 자동재생 설정 여부, 영상 재생 가능 여부 등 다양한 조건이 추가됩니다.

마우스를 이용한 영상 재생

스마트폰에서 영상 자동재생

모바일에는 마우스 커서가 없으므로 포커싱 기준이 필요합니다.

PC처럼 커서가 있는게 아니기에 포커싱의 개념을 정의해야 합니다.

스마트폰에서 자동재생

초기 아이디어

  • 영상이 화면 전체에 들어왔을 때 자동재생
  • 단, 한 화면에 두 개 이상 영상이 들어올 수 있음 → 상단의 영상만 재생하도록 처리
const handleScroll = () => {
  if (!cardRef.current) return;

  const rect = cardRef.current.getBoundingClientRect();
  const isTopPosition =
    rect.top < standardPosition && rect.bottom > standardPosition;

  setIsPlaying(isTopPosition);
};

const debouncedHandleScroll = debouncing(handleScroll, 100);

useEffect(() => {
  window.addEventListener('scroll', debouncedHandleScroll);
  return () => {
    window.removeEventListener('scroll', debouncedHandleScroll);
  };
}, [debouncedHandleScroll]);
const handleScroll = () => {
  if (!cardRef.current) return;

  const rect = cardRef.current.getBoundingClientRect();
  const isTopPosition =
    rect.top < standardPosition && rect.bottom > standardPosition;

  setIsPlaying(isTopPosition);
};

const debouncedHandleScroll = debouncing(handleScroll, 100);

useEffect(() => {
  window.addEventListener('scroll', debouncedHandleScroll);
  return () => {
    window.removeEventListener('scroll', debouncedHandleScroll);
  };
}, [debouncedHandleScroll]);
  • 스크롤 이벤트를 감지해서 카드의 위치를 기준으로 재생을 하도록 구현했습니다.
영상카드가 특정 위치에 오면 재생

위 사이트는 간단히 다시 구현해본 결과이며 특정 위치라는 것을 가상의 선 컴포넌트로 표시하였습니다.

선 위치에 카드가 오게 되면 재생됩니다.

문제 발견

여러 페이지에 적용하며 테스트하던중 문제를 발견하게 됩니다.

  1. 재생 기준이 모호함 – 어떤 위치를 기준으로 할지 불명확
  2. 확장성 부족 – 페이지마다 조건을 따로 정의해야 함
  3. 하단 영상 문제 – 상단에 재생 불가 영상이 있으면, 하단의 재생 가능한 영상이 실행되지 않음

결국 위치가 아닌 화면 안 여부에 초점을 맞추어 바꾸었습니다.

개선된 최종 구현

새로운 아이디어

  1. 영상이 화면안에 들어오게 되면, 영상을 '리스트'에 추가합니다. 이때 재생이 불가능한 경우는 추가하지 않습니다.
  2. 재생가능한 영상 리스트중 가장 상단의 있는 영상을 재생합니다.
  3. 화면 밖으로 나가게 되면 리스트에서 제거합니다.

코드예시

useEffect(() => {
  let currentCardRef = cardRef.current;

  const observer = new IntersectionObserver(
    (entries) => {
      const isPlayable = videoData.url !== '';

      if (entries[0].isIntersecting && isPlayable) {
        addActiveVideoIndexList();
      } else {
        removeActiveVideoIndexList();
      }
    },
    { threshold: 0.7 },
  );

  if (cardRef.current) {
    currentCardRef = cardRef.current;
    observer.observe(currentCardRef);
  }

  return () => {
    if (currentCardRef) observer.unobserve(currentCardRef);
  };
}, [addActiveVideoIndexList, removeActiveVideoIndexList, videoData.url]);
useEffect(() => {
  let currentCardRef = cardRef.current;

  const observer = new IntersectionObserver(
    (entries) => {
      const isPlayable = videoData.url !== '';

      if (entries[0].isIntersecting && isPlayable) {
        addActiveVideoIndexList();
      } else {
        removeActiveVideoIndexList();
      }
    },
    { threshold: 0.7 },
  );

  if (cardRef.current) {
    currentCardRef = cardRef.current;
    observer.observe(currentCardRef);
  }

  return () => {
    if (currentCardRef) observer.unobserve(currentCardRef);
  };
}, [addActiveVideoIndexList, removeActiveVideoIndexList, videoData.url]);
  • IntersectionObserver를 사용해 화면 진입/이탈 여부를 감지
  • threshold 0.7 → 70% 이상 보일 때 리스트에 추가
  • 리스트의 가장 앞 인덱스 영상만 재생
리스트를 이용하여 구현한 최종 결과

추가 고민

자동재생은 사용자 경험을 높여주지만, 의도치 않게 데이터 사용량 증가라는 부작용이 있을 수 있었습니다.

이를 해결하기 위해 다음을 추가로 구현했습니다.

  • 설정 페이지에 자동재생 on/off 옵션 추가
  • 기본값은 off
  • off일 때는 아예 플레이어를 로드하지 않도록 최적화

정리

  • PC: 마우스 이벤트 + 디바운싱 활용
  • 모바일: IntersectionObserver로 화면 진입 감지 + 리스트 관리
  • 최종 구현: “재생 가능 + 화면 안에 있는 가장 상단 영상”만 재생
  • 사용성 고려: 자동재생 on/off 옵션 제공
읽어주셔서 감사합니다