import Button from '@material-ui/core/Button'; import Ajv from 'ajv'; import { useEffect } from 'react'; import timeline, { slide } from '../timeline'; import * as timelineSchema from '../timeline.schema.json'; import ExitToAppRoundedIcon from '@material-ui/icons/ExitToAppRounded'; import PlayArrowRoundedIcon from '@material-ui/icons/PlayArrowRounded'; import SettingsRemoteRoundedIcon from '@material-ui/icons/SettingsRemoteRounded'; import SettingsRoundedIcon from '@material-ui/icons/SettingsRounded'; import CodeRoundedIcon from '@material-ui/icons/CodeRounded'; import MovieRoundedIcon from '@material-ui/icons/MovieRounded'; class TimedVideoPlayer { slide: number; timeline: timeline; precision: number; player: HTMLVideoElement; video: string; registeredEventListeners: boolean; frame: number; framerate: number; constructor() { this.slide = -1; this.precision = 3; this.frame = 0; this.framerate = 0; this.offset = 0; this.registeredEventListeners = false; } timestampToFrame(timestamp: number): number { return Math.round((timestamp * 1e3) / (1e3 / this.framerate)); } frameToTimestamp(frame: number): number { return frame / this.framerate; } registerPlayer(player: HTMLVideoElement) { this.player = player; if (this.video) this.player.src = this.video; this.registerEventListeners(); } jumpToFrame(frame: number) { this.player.currentTime = this.frameToTimestamp(frame); this.frame = frame; } jumpToSlide(slide: slide) { this.jumpToFrame(slide.frame); this.player.pause(); } registerEventListeners() { if ( !this.video || !this.player || !this.timeline || this.registeredEventListeners ) { return; } setInterval(() => { if (this.player.paused) return; this.frame = this.timestampToFrame(this.player.currentTime); // debug document.getElementById('frame').innerText = this.frame.toString(); var slide = this.timeline.slides[this.slide]; if (!slide) return; if (this.frame >= slide.frame) { this.jumpToSlide(slide); } }, 1e3 / (this.precision * this.framerate)); this.registeredEventListeners = true; } loadVideo(base64Video: string) { this.video = base64Video; if (this.player) this.player.src = this.video; this.registerEventListeners(); } loadSlides(jsonString: string) { try { var timeline = JSON.parse(jsonString); } catch (e) { console.log('invalid json object!' + e); return; } var ajv = new Ajv({ allErrors: true }); var validate = ajv.compile(timelineSchema); if (!validate(timeline)) { console.log('schema not passed!'); return; } this.timeline = timeline as timeline; this.framerate = this.timeline.framerate; this.timeline.slides[-1] = { frame: 0, type: 'default', clickThroughBehaviour: 'ImmediatelySkip', }; this.registerEventListeners(); } skip() { var slide = this.timeline.slides[this.slide]; if (slide.clickThroughBehaviour == 'ImmediatelySkip') this.jumpToSlide(slide); } next() { if (!this.registeredEventListeners) return; var slide = this.timeline.slides[this.slide + 1]; if (!this.player.paused && this.frame < slide?.frame) { this.skip(); } this.slide++; this.player.play(); console.log(this.slide); } previous() { if (!this.registeredEventListeners) return; this.slide = Math.max(this.slide - 1, -1); var slide = this.timeline.slides[this.slide]; if (!slide) return; this.jumpToSlide(slide); console.log(this.slide, slide); } } export default function Present() { useEffect(() => { setInterval(() => { document.getElementById('time').innerText = new Date().toLocaleTimeString(); }, 500); }, []); var player = new TimedVideoPlayer(); useEffect(() => { var videoEL = document.getElementById('player') as HTMLVideoElement; player.registerPlayer(videoEL); /* videoEL.addEventListener('loadeddata', () => { */ /* console.log('initial load'); */ /* }); */ /* videoEL.addEventListener('canplaythrough', () => { */ /* console.log('full load') */ /* }); */ }, []); return