videomail-client
Version:
A wicked npm package to record videos directly in the browser, wohooo!
393 lines (309 loc) • 9.22 kB
JavaScript
import hidden from 'hidden'
import h from 'hyperscript'
import util from 'util'
import Events from '../events'
import EventEmitter from '../util/eventEmitter'
import RecorderInsides from './visuals/inside/recorderInsides'
import Notifier from './visuals/notifier'
import Recorder from './visuals/recorder'
import Replay from './visuals/replay'
const Visuals = function (container, options) {
EventEmitter.call(this, options, 'Visuals')
const self = this
// can be overwritten with setter fn
const replay = new Replay(this, options)
const recorder = new Recorder(this, replay, options)
const recorderInsides = new RecorderInsides(this, options)
const notifier = new Notifier(this, options)
const debug = options.debug
let visualsElement
let built
function buildNoScriptTag() {
let noScriptElement = container.querySelector('noscript')
if (!noScriptElement) {
noScriptElement = h('noscript')
noScriptElement.innerHTML = 'Please enable Javascript'
visualsElement.appendChild(noScriptElement)
}
}
function buildChildren() {
debug('Visuals: buildChildren()')
buildNoScriptTag()
if (!options.playerOnly) {
notifier.build()
recorderInsides.build()
}
replay.build()
debug('Visuals: built.')
}
function initEvents() {
if (!options.playerOnly) {
debug('Visuals: initEvents()')
self
.on(Events.USER_MEDIA_READY, function () {
built = true
self.endWaiting()
container.enableForm(false)
})
.on(Events.PREVIEW, () => {
self.endWaiting()
})
.on(Events.BLOCKING, function (blockingOptions) {
if (!blockingOptions.hideForm && !options.adjustFormOnBrowserError) {
// do nothing, user still can enter form inputs
// can be useful when you are on i.E. seeflow's contact page and
// still want to tick off the webcam option
} else {
container.disableForm(true)
}
})
.on(Events.PREVIEW_SHOWN, function () {
container.validate(true)
})
.on(Events.LOADED_META_DATA, function () {
correctDimensions()
})
.on(Events.ERROR, function (err) {
if (err.removeDimensions && err.removeDimensions()) {
removeDimensions()
}
})
}
}
function correctDimensions() {
visualsElement.style.width = self.getRecorderWidth(true) + 'px'
visualsElement.style.height = self.getRecorderHeight(true) + 'px'
}
function removeDimensions() {
visualsElement.style.width = 'auto'
visualsElement.style.height = 'auto'
}
this.getRatio = function () {
if (visualsElement.clientWidth) {
// special case for safari, see getRatio() in recorder
return visualsElement.clientHeight / visualsElement.clientWidth
}
return 0
}
function isRecordable() {
return !self.isNotifying() && !replay.isShown() && !self.isCountingDown()
}
this.isCountingDown = function () {
return recorderInsides.isCountingDown()
}
this.build = function () {
visualsElement = container.querySelector('.' + options.selectors.visualsClass)
if (!visualsElement) {
visualsElement = h('div.' + options.selectors.visualsClass)
const buttonsElement = container.querySelector('.' + options.selectors.buttonsClass)
// make sure it's placed before the buttons, but only if it's a child
// element of the container = inside the container
if (buttonsElement && !container.isOutsideElementOf(buttonsElement)) {
container.insertBefore(visualsElement, buttonsElement)
} else {
container.appendChild(visualsElement)
}
}
// do not hide visuals element so that apps can give it a predefined
// width or height through css but hide all children
visualsElement.classList.add('visuals')
correctDimensions()
!built && initEvents()
buildChildren()
// needed for replay handling and container.isOutsideElementOf()
self.parentNode = visualsElement.parentNode
built = true
}
this.querySelector = function (selector) {
return visualsElement && visualsElement.querySelector(selector)
}
this.appendChild = function (child) {
visualsElement && visualsElement.appendChild(child)
}
this.removeChild = function (child) {
visualsElement.removeChild(child)
}
this.reset = function () {
this.endWaiting()
recorder.reset()
}
this.beginWaiting = function () {
container.beginWaiting()
}
this.endWaiting = function () {
container.endWaiting()
}
this.stop = function (params) {
recorder.stop(params)
recorderInsides.hidePause()
}
this.back = function (params, cb) {
if (!cb && params) {
cb = params
params = {}
}
replay.hide()
notifier.hide()
if (params && params.keepHidden) {
recorder.hide()
cb && cb()
} else {
recorder.back(cb)
}
}
this.recordAgain = function () {
this.back(function () {
self.once(Events.USER_MEDIA_READY, function () {
self.record()
})
})
}
this.unload = function (e) {
try {
recorder.unload(e)
recorderInsides.unload(e)
replay.unload(e)
built = false
} catch (exc) {
this.emit(Events.ERROR, exc)
}
}
this.isNotifying = function () {
return notifier.isVisible()
}
this.isReplayShown = function () {
return replay.isShown()
}
this.pause = function (params) {
recorder.pause(params)
recorderInsides.showPause()
}
this.resume = function () {
if (recorderInsides.isCountingDown()) {
recorderInsides.resumeCountdown()
} else {
recorder.resume()
}
recorderInsides.hidePause()
}
this.pauseOrResume = function () {
if (isRecordable.call(this)) {
if (this.isRecording()) {
this.pause()
} else if (recorder.isPaused()) {
this.resume()
} else if (recorder.isReady()) {
this.record()
}
}
}
this.recordOrStop = function () {
if (isRecordable()) {
if (this.isRecording()) {
this.stop()
} else if (recorder.isReady()) {
this.record()
}
}
}
this.record = function () {
if (options.video.countdown) {
this.emit(Events.COUNTDOWN)
recorderInsides.startCountdown(recorder.record.bind(recorder))
} else {
recorder.record()
}
}
this.getRecorder = function () {
return recorder
}
this.getReplay = function () {
return replay
}
this.validate = function () {
return recorder.validate() && this.isReplayShown()
}
this.getRecordingStats = function () {
return recorder.getRecordingStats()
}
this.getAudioSampleRate = function () {
return recorder.getAudioSampleRate()
}
this.isPaused = function () {
return recorder.isPaused()
}
this.error = function (err) {
notifier.error(err)
}
this.hide = function () {
if (visualsElement) {
hidden(visualsElement, true)
this.emit(Events.HIDE)
}
}
this.isHidden = function () {
if (!built) {
return true
} else if (visualsElement) {
return hidden(visualsElement)
}
}
this.showVisuals = function () {
visualsElement && hidden(visualsElement, false)
}
this.show = function () {
!this.isReplayShown() && visualsElement && recorder.build()
this.showVisuals()
}
this.showReplayOnly = function () {
!this.isReplayShown() && replay.show()
this.show()
recorder.hide()
notifier.hide()
}
this.isRecorderUnloaded = function () {
return recorder.isUnloaded()
}
this.isConnecting = function () {
return recorder.isConnecting()
}
this.getRecorderWidth = function (responsive) {
return recorder.getRecorderWidth(responsive)
}
this.getRecorderHeight = function (responsive) {
return recorder.getRecorderHeight(responsive)
}
this.limitWidth = function (width) {
return container.limitWidth(width, options)
}
this.limitHeight = function (height) {
return container.limitHeight(height)
}
this.calculateWidth = function (options) {
return container.calculateWidth(options)
}
this.calculateHeight = function (options) {
return container.calculateHeight(options)
}
this.getReplay = function () {
return replay
}
this.getBoundingClientRect = function () {
// fixes https://github.com/binarykitchen/videomail-client/issues/126
return visualsElement && visualsElement.getBoundingClientRect()
}
this.checkTimer = function (intervalSum) {
recorderInsides.checkTimer(intervalSum)
}
this.isNotifierBuilt = function () {
return notifier && notifier.isBuilt()
}
this.isReplayShown = replay.isShown.bind(replay)
this.hideReplay = replay.hide.bind(replay)
this.hideRecorder = recorder.hide.bind(recorder)
this.isRecording = recorder.isRecording.bind(recorder)
this.isUserMediaLoaded = recorder.isUserMediaLoaded.bind(recorder)
this.isConnected = recorder.isConnected.bind(recorder)
}
util.inherits(Visuals, EventEmitter)
export default Visuals