Created
April 29, 2026 16:14
-
-
Save hpaul/d088f035bf8025772d6ffcf1cb084d2e to your computer and use it in GitHub Desktop.
Wistia Aurora player usage
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import { WistiaPlayer as OriginalWistiaPlayer } from '@wistia/wistia-player'; | |
| import { WistiaPlayer } from '@wistia/wistia-player-react'; | |
| const VideoModa = ({ videoId, videoReference: threePlayVideoId, ...props }) => { | |
| const { | |
| params, | |
| location: { query }, | |
| } = props.router || {}; | |
| const { apd } = query; | |
| const { subject: subjectId } = params || {}; | |
| const [ | |
| sendTelemetryIntermediatePlayEvent, | |
| setSendTelemetryIntermediatePlayEvent, | |
| ] = useState(false); | |
| const player = useRef<HTMLElement>(null); | |
| const video = useRef(); | |
| const interval = useRef<ReturnType<typeof setInterval>>(); | |
| const saveDelayCounter = useRef(0); | |
| const updateSaveDelayCounter = (payload: number) => { | |
| saveDelayCounter.current = payload; | |
| }; | |
| const { onPlay, onPlaying, onPause, onSeeked, onEnd, onClose } = | |
| eventCallbacks; | |
| const progress = useRef<number[]>([]); | |
| const containerRef = useRef<HTMLDivElement>(null); | |
| const getProgress = () => { | |
| if (player?.current) { | |
| let currentProgress = player?.current?.secondsWatchedVector; | |
| let previousProgress = progressVector; | |
| const duration = player?.current?.duration; | |
| const getEmptyProgress = (): number[] => { | |
| return Array.from({ length: duration + 1 }, () => 0); | |
| }; | |
| if (previousProgress.length === 0) { | |
| previousProgress = getEmptyProgress(); | |
| } | |
| if (currentProgress.length === 0) { | |
| currentProgress = getEmptyProgress(); | |
| } | |
| return mergeProgress(previousProgress, currentProgress); | |
| } else { | |
| return progress?.current; | |
| } | |
| }; | |
| const updateProgress = () => { | |
| progress.current = getProgress(); | |
| }; | |
| const latestVideoPosition = useRef('0.00'); | |
| const getLatestVideoPosition = () => { | |
| if (player?.current) { | |
| const truncateNumber = (num: number): number => | |
| Math.trunc(num * 1000) / 1000; | |
| const currentTime = player?.current?.currentTime; | |
| const duration = player?.current?.duration; | |
| const percentWatched = currentTime / duration; | |
| const latestVideoPosition = truncateNumber(percentWatched).toString(); | |
| return latestVideoPosition; | |
| } else { | |
| return latestVideoPosition?.current; | |
| } | |
| }; | |
| const updateLatestVideoPosition = () => { | |
| latestVideoPosition.current = getLatestVideoPosition(); | |
| }; | |
| const getProgressInfo = (): ProgressInfo => ({ | |
| progress: getProgress(), | |
| latestVideoPosition: getLatestVideoPosition(), | |
| }); | |
| const handlePlay = () => { | |
| const progressInfo = getProgressInfo(); | |
| sendTelemetryEvent('play', progressInfo); | |
| createInterval(); | |
| onPlay(progressInfo); | |
| }; | |
| const handleEnd = () => { | |
| const progressInfo = getProgressInfo(); | |
| sendTelemetryEvent('end', progressInfo); | |
| deleteInterval(); | |
| onEnd(progressInfo); | |
| }; | |
| const handlePause = () => { | |
| const progressInfo = getProgressInfo(); | |
| sendTelemetryEvent('stop', progressInfo); | |
| deleteInterval(); | |
| onPause(progressInfo); | |
| }; | |
| const seekedTimer = useRef<ReturnType<typeof setTimeout>>(); | |
| const handleSeeked = () => { | |
| clearTimeout(seekedTimer.current); | |
| // Seeking should not trigger one call per each second (100s seeked -> 100req) | |
| // Applying 300ms is enough to debounce and persist the intention | |
| seekedTimer.current = setTimeout(() => { | |
| const progressInfo = getProgressInfo(); | |
| sendTelemetryEvent('seeked', progressInfo); | |
| onSeeked(progressInfo); | |
| }, 300); | |
| }; | |
| const handlePlaying = () => { | |
| if (!player?.current) return; | |
| const newSecondsHaveElapsed = saveDelayCounter?.current >= 14; | |
| if (newSecondsHaveElapsed) { | |
| const progressInfo = getProgressInfo(); | |
| sendTelemetryEvent('playing', progressInfo); | |
| onPlaying(false, newSecondsHaveElapsed, progressInfo); | |
| updateSaveDelayCounter(0); | |
| } | |
| }; | |
| const handleClose = () => { | |
| const progressInfo = getProgressInfo(); | |
| sendTelemetryEvent('close', progressInfo); | |
| deleteInterval(); | |
| onClose(progressInfo); | |
| if (isImpersonating && apdCarouselData?.[subjectId]) { | |
| setAPDCarouselData({ subject: subjectId, videoData: null }); | |
| } | |
| }; | |
| useEffect(() => handleClose, []); | |
| const onSecondChange = () => { | |
| updateProgress(); | |
| updateLatestVideoPosition(); | |
| updateSaveDelayCounter(saveDelayCounter?.current + 1); | |
| }; | |
| const [isVideoPreloaded, setIsVideoPreloaded] = useState(false); | |
| const { | |
| me: { initId: userId, sections }, | |
| chosenEducationPeriodCode, | |
| } = userData; | |
| const createInterval = () => { | |
| interval.current = setInterval( | |
| () => setSendTelemetryIntermediatePlayEvent(true), | |
| 15000, | |
| ); | |
| }; | |
| const deleteInterval = () => { | |
| if (interval.current) { | |
| clearInterval(interval.current); | |
| setSendTelemetryIntermediatePlayEvent(false); | |
| } | |
| }; | |
| const onVideoPreloaded = () => setIsVideoPreloaded(true); | |
| // Send telemetry event whenever the `sendTelemetryIntermediatePlayEvent` is `true`. | |
| useEffect(() => { | |
| if (sendTelemetryIntermediatePlayEvent) { | |
| const progressInfo = getProgressInfo(); | |
| sendTelemetryEvent('play', progressInfo); | |
| setSendTelemetryIntermediatePlayEvent(false); | |
| } | |
| }, [sendTelemetryIntermediatePlayEvent]); | |
| const initialCurrentTime = useMemo(() => { | |
| const duration = progressVector.length; | |
| return duration * Number(videoPosition); | |
| }, [videoPosition, progressVector]); | |
| const videoContainerId = `video_container_${videoId}`; | |
| return ( | |
| <div ref={containerRef}> | |
| {!isVideoPreloaded && <LoadingSpinner containerClassName="flex" />} | |
| <WistiaPlayer | |
| id={videoContainerId} | |
| swatch | |
| resumable={false} | |
| currentTime={initialCurrentTime} | |
| mediaId={videoId} | |
| ref={player} | |
| onSwatchLoaded={onVideoPreloaded} | |
| onPlay={handlePlay} | |
| onPause={handlePause} | |
| onSeeked={handleSeeked} | |
| onEnded={handleEnd} | |
| onPercentWatchedChange={handlePlaying} | |
| onSecondChange={onSecondChange} | |
| /> | |
| <ThreePlayPlugin | |
| target={videoContainerId} | |
| videoId={videoId} | |
| threePlayVideoId={threePlayVideoId} | |
| /> | |
| </div> | |
| ); | |
| }; | |
| export default VideoModal; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment