Source: components/atoms/VideoComponent.js

import dom from '../../DOM.js'
import Component from '../../Component.js'
import { lt, ld, ldt } from '../../Localizer.js'

/**
VideoComponent displays a single video.

If you want to display controls, use {@link VideoPlayerComponent}.

@example
* const component = new VideoComponent(undefined, {
* 	preview: '/static/preview-image.png',
* 	video: '/static/video.mp4',
* 	mimeType: 'video/mp4'
* })

*/
const VideoComponent = class extends Component {
	/**
	@param {DataObject} [dataObject=null]
	@param {Object} [options={}]
	@param {string} [options.preview=null] the URL to a still image to show until the user plays the video
	@param {string} [options.video=null] - the URL to a video
	@param {string} [options.mimeType=null] - the mimeType for the video

	*/
	constructor(dataObject = null, options = {}) {
		super(
			dataObject,
			Object.assign(
				{
					preview: null,
					video: null,
					mimeType: null,
				},
				options
			)
		)
		this.addClass('video-component')
		this.setName('VideoComponent')
		this._handleVideoCanPlay = this._handleVideoCanPlay.bind(this)
		this._videoCanPlay = false // False until the video is loaded

		this._videoRequested = false // False until the video is asked to load or play
		this._source = null
		this._video = null

		this._heightScale = 0.01
		this._widthScale = null
		this._ratio = null
		this.resize(VideoComponent.RATIO_16x9)

		if (this.options.preview) {
			this._preview = dom.img({ src: this.options.preview }).appendTo(this.dom).addClass('preview')
		} else {
			this._preview = dom.span(lt('Click to play')).appendTo(this.dom).addClass('preview')
		}
		this.listenTo('click', this._preview, (ev) => {
			if (this._videoCanPlay === false) {
				this.loadVideo() // Will nop if already requested
				this.listenTo(
					VideoComponent.VideoCanPlayEvent,
					this,
					() => {
						this._video.play()
					},
					true
				)
			} else {
				this._video.play()
			}
		})
	}

	cleanup() {
		if (this._video) this._video.removeEventListener('canplay', this._handleVideoCanPlay, false)
	}

	get source() {
		return this._source
	}

	get video() {
		return this._video
	}

	get paused() {
		if (this._videoRequested === false) return true
		return this._video.paused
	}

	get currentTime() {
		if (this._videoRequested === false) return 0
		return this._video.currentTime
	}

	/**
	@val {float} the time at the current frame in seconds
	*/
	set currentTime(val) {
		if (this._videoRequested === false) return
		this._video.currentTime = val
	}

	/**
	@return {float} the total duration of the video in seconds or 0 if it isn't yet loaded
	*/
	get duration() {
		if (this._videoRequested === false) return 0
		return this._video.duration
	}

	/**
	Play the video, loading it if it isn't already.
	*/
	play() {
		if (this._videoRequested === false) this.loadVideo()
		this._video.play()
	}

	/**
	Pause the video, loading it if it isn't already.
	*/
	pause() {
		if (this._videoRequested === false) this.loadVideo()
		this._video.pause()
	}

	/**
	Call this to switch between playing and paused states.
	*/
	toggle() {
		if (this._videoRequested === false) this.loadVideo()
		if (this.paused) {
			this.play()
		} else {
			this.pause()
		}
	}

	/**
	Call this to start loading the video
	*/
	loadVideo() {
		if (this._videoRequested) return false
		this._videoRequested = true

		this._source = dom.source({
			src: this.options.video,
			type: this.options.mimeType,
		})
		this._video = dom.video(this._source)

		this.dom.removeChild(this._preview)
		this.dom.appendChild(this._video)

		this._video.crossOrigin = 'anonymous'
		this._video.addEventListener('canplay', this._handleVideoCanPlay, false)
		this._video.load()

		this.trigger(VideoComponent.VideoInitializedEvent, this)
		return true
	}

	/**
	Sets the attributes on the `video` HTMLElement.
	@param {string} url - the relative or full URL to the video
	@param {string} mimeType - a mime type like 'video/mp4'
	*/
	setSourceAttributes(url, mimeType) {
		this.options.video = url
		this.options.mimeType = mimeType
		if (this._videoRequested) {
			// the video elements are already created so reset video
			this._source.setAttribute('src', url)
			this._source.setAttribute('type', mimeType)
			this._video.load()
		}
	}

	_handleVideoCanPlay(ev) {
		const videoWidth = ev.target.videoWidth
		const videoHeight = ev.target.videoHeight
		if (videoWidth <= 0 || videoHeight <= 0) {
			console.error('Could not read the video dimensions', ev)
			return
		}
		this.resize(videoWidth / videoHeight)
		this._videoCanPlay = true
		this.trigger(VideoComponent.VideoCanPlayEvent, this)
	}

	resize(ratio) {
		this._ratio = ratio
		this._widthScale = this._heightScale * this._ratio
		// TODO resize
	}
}

VideoComponent.VideoCanPlayEvent = Symbol('vc-video-can-play')
VideoComponent.VideoInitializedEvent = Symbol('vc-video-component-initialized')

VideoComponent.RATIO_16x9 = 16 / 9
VideoComponent.RATIO_1x1 = 1

export default VideoComponent
export { VideoComponent }