๐The Interview Setting
Company: YouTube/Google Round: Frontend System Design Duration: 45 minutes
๐๐ค Interviewer: "Design a video player like YouTube."
Candidate's Clarifications:
Interviewer: "Yes to adaptive streaming. Include subtitles, speed. Skip live for now."
๐๐ Component Architecture
VideoPlayer|+-- VideoContainer| +-- VideoElement| +-- SubtitleOverlay| +-- BufferingSpinner|+-- Controls| +-- ProgressBar| | +-- BufferedIndicator| | +-- PlayedIndicator| | +-- SeekPreview| | +-- ChapterMarkers| +-- LeftControls| | +-- PlayPauseButton| | +-- VolumeControl| | +-- TimeDisplay| +-- RightControls| +-- SubtitleButton| +-- SettingsMenu| +-- TheaterButton| +-- PiPButton| +-- FullscreenButton|+-- SettingsPanel +-- QualitySelector +-- SpeedSelector +-- SubtitleSelector
๐๐ค Interviewer: "How do you implement adaptive bitrate streaming?"
Candidate: "Use HLS.js or dash.js for ABR..."
import Hls from 'hls.js';const useAdaptiveStreaming = (videoRef, manifestUrl) => { const [quality, setQuality] = useState('auto'); const [availableQualities, setAvailableQualities] = useState([]); const hlsRef = useRef(null); useEffect(() => { const video = videoRef.current; if (Hls.isSupported()) { const hls = new Hls({ enableWorker: true, lowLatencyMode: false, backBufferLength: 90, maxBufferLength: 30, maxMaxBufferLength: 600 }); hlsRef.current = hls; hls.loadSource(manifestUrl); hls.attachMedia(video); hls.on(Hls.Events.MANIFEST_PARSED, (event, data) => { const qualities = data.levels.map((level, index) => ({ index, height: level.height, bitrate: level.bitrate, label: `${level.height}p` })); setAvailableQualities([{ index: -1, label: 'Auto' }, ...qualities]); }); // Monitor quality switches hls.on(Hls.Events.LEVEL_SWITCHED, (event, data) => { console.log('Quality switched to:', data.level); }); return () => hls.destroy(); } else if (video.canPlayType('application/vnd.apple.mpegurl')) { // Native HLS (Safari) video.src = manifestUrl; } }, [manifestUrl]); const setQualityLevel = (levelIndex) => { if (hlsRef.current) { hlsRef.current.currentLevel = levelIndex; // -1 for auto setQuality(levelIndex === -1 ? 'auto' : levelIndex); } }; return { quality, availableQualities, setQualityLevel };};
๐๐ค Interviewer: "How do you handle keyboard shortcuts?"
Candidate:
const useKeyboardShortcuts = (videoRef, playerState) => { useEffect(() => { const handleKeyDown = (e) => { // Don't trigger if typing in input if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA') { return; } const video = videoRef.current; switch (e.key.toLowerCase()) { case ' ': case 'k': e.preventDefault(); video.paused ? video.play() : video.pause(); break; case 'f': e.preventDefault(); toggleFullscreen(); break; case 'm': e.preventDefault(); video.muted = !video.muted; break; case 'arrowleft': case 'j': e.preventDefault(); video.currentTime -= e.key === 'j' ? 10 : 5; break; case 'arrowright': case 'l': e.preventDefault(); video.currentTime += e.key === 'l' ? 10 : 5; break; case 'arrowup': e.preventDefault(); video.volume = Math.min(1, video.volume + 0.1); break; case 'arrowdown': e.preventDefault(); video.volume = Math.max(0, video.volume - 0.1); break; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': e.preventDefault(); const percent = parseInt(e.key) * 10; video.currentTime = (percent / 100) * video.duration; break; case '>': e.preventDefault(); video.playbackRate = Math.min(2, video.playbackRate + 0.25); break; case '<': e.preventDefault(); video.playbackRate = Math.max(0.25, video.playbackRate - 0.25); break; case 'c': e.preventDefault(); toggleSubtitles(); break; } }; document.addEventListener('keydown', handleKeyDown); return () => document.removeEventListener('keydown', handleKeyDown); }, []);};
๐๐ค Interviewer: "What edge cases do you handle?"
Candidate:
1. Buffering & Stalling
video.addEventListener('waiting', () => setIsBuffering(true));video.addEventListener('playing', () => setIsBuffering(false));// Detect stallslet lastPlayPos = 0;let currentPlayPos = 0;let bufferingDetected = false;setInterval(() => { currentPlayPos = video.currentTime; if (!bufferingDetected && currentPlayPos === lastPlayPos && !video.paused) { bufferingDetected = true; setIsBuffering(true); } if (bufferingDetected && currentPlayPos !== lastPlayPos) { bufferingDetected = false; setIsBuffering(false); } lastPlayPos = currentPlayPos;}, 100);2. Error Recovery
video.addEventListener('error', (e) => { const error = e.target.error; switch (error.code) { case MediaError.MEDIA_ERR_NETWORK: // Retry with backoff retryWithBackoff(); break; case MediaError.MEDIA_ERR_DECODE: // Try lower quality switchToLowerQuality(); break; case MediaError.MEDIA_ERR_SRC_NOT_SUPPORTED: showError('Video format not supported'); break; }});3. Fullscreen Compatibility
const toggleFullscreen = () => { const container = playerRef.current; if (!document.fullscreenElement) { if (container.requestFullscreen) { container.requestFullscreen(); } else if (container.webkitRequestFullscreen) { container.webkitRequestFullscreen(); } else if (container.msRequestFullscreen) { container.msRequestFullscreen(); } } else { document.exitFullscreen?.() || document.webkitExitFullscreen?.() || document.msExitFullscreen?.(); }};4. Resume Playback
// Save positionvideo.addEventListener('timeupdate', () => { localStorage.setItem(`video-${videoId}`, video.currentTime);});// Resume on loaduseEffect(() => { const savedTime = localStorage.getItem(`video-${videoId}`); if (savedTime && parseFloat(savedTime) > 5) { video.currentTime = parseFloat(savedTime); toast('Resuming from where you left off'); }}, [videoId]);
๐๐ Interview Scoring
Result: Strong Hire ๐