유튜브 자동재생 웹사이트에서 구현하기
• Article
영상 자동재생 구현하기
사용자들이 영상을 더 쉽게 접할 수 있도록 자동재생 기능을 구현했습니다.
유튜브의 동작 방식을 분석한 뒤 이를 참고하여 PC와 모바일에서 각각 어떻게 동작하는지 살펴보고, 실제 구현 과정을 정리합니다.
자동재생의 두 가지 시나리오
유튜브의 자동재생은 크게 두 가지 상황으로 나눌 수 있습니다.
- PC에서의 영상 자동재생 (마우스 커서 기반)
- 스마트폰에서의 영상 자동재생 (포커싱 기준 기반)
PC에서 영상 자동재생
웹에서는 마우스 커서를 이용해 영상 재생을 제어합니다.
관찰 결과
-
마우스 올림 → 썸네일이 사라지고 영상 재생
- 단, 커서를 올린 즉시 재생되지는 않고 약 0.2초 후에 재생됨 → 디바운싱 적용으로 추정.
-
영상은 즉시 이어서 재생
- 커서를 올리는 시점에 플레이어를 새로 로드하지 않고, 이미 로드된 플레이어에서 이전 재생 지점부터 이어짐.
코드 예시
<VideoCard
onMouseEnter={() => {
playVideo();
}}
onMouseLeave={() => {
stopVideo();
}}
>
{isPlaying ? null : <ThumbnailImage />}
<ReactPlayer />
</VideoCard>
<VideoCard
onMouseEnter={() => {
playVideo();
}}
onMouseLeave={() => {
stopVideo();
}}
>
{isPlaying ? null : <ThumbnailImage />}
<ReactPlayer />
</VideoCard>
onMouseEnter
와onMouseLeave
이벤트를 활용해 마우스가 올려졌는지 여부를 체크하고, 썸네일과 영상을 전환합니다.
실제 코드에서는 자동재생 설정 여부, 영상 재생 가능 여부 등 다양한 조건이 추가됩니다.
마우스를 이용한 영상 재생
스마트폰에서 영상 자동재생
모바일에는 마우스 커서가 없으므로 포커싱 기준이 필요합니다.
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]);
- 스크롤 이벤트를 감지해서 카드의 위치를 기준으로 재생을 하도록 구현했습니다.
영상카드가 특정 위치에 오면 재생
위 사이트는 간단히 다시 구현해본 결과이며 특정 위치라는 것을 가상의 선 컴포넌트로 표시하였습니다.
선 위치에 카드가 오게 되면 재생됩니다.
문제 발견
여러 페이지에 적용하며 테스트하던중 문제를 발견하게 됩니다.
- 재생 기준이 모호함 – 어떤 위치를 기준으로 할지 불명확
- 확장성 부족 – 페이지마다 조건을 따로 정의해야 함
- 하단 영상 문제 – 상단에 재생 불가 영상이 있으면, 하단의 재생 가능한 영상이 실행되지 않음
결국 위치가 아닌 화면 안 여부에 초점을 맞추어 바꾸었습니다.
개선된 최종 구현
새로운 아이디어
- 영상이 화면안에 들어오게 되면, 영상을 '리스트'에 추가합니다. 이때 재생이 불가능한 경우는 추가하지 않습니다.
- 재생가능한 영상 리스트중 가장 상단의 있는 영상을 재생합니다.
- 화면 밖으로 나가게 되면 리스트에서 제거합니다.
코드예시
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 옵션 제공