UNPKG

videomail-client

Version:

A wicked npm package to record videos directly in the browser, wohooo!

344 lines (273 loc) 8.76 kB
import addEventListenerWithOptions from 'add-eventlistener-with-options' import hidden from 'hidden' import h from 'hyperscript' import enableInlineVideo from 'iphone-inline-video' import util from 'util' import Events from '../../events' import Browser from '../../util/browser' import EventEmitter from '../../util/eventEmitter' import VideomailError from '../../util/videomailError' const Replay = function (parentElement, options) { EventEmitter.call(this, options, 'Replay') const self = this const browser = new Browser(options) const debug = options.debug let built let replayElement let videomail function buildElement() { debug('Replay: buildElement()') replayElement = h('video.' + options.selectors.replayClass) if (!replayElement.setAttribute) { throw VideomailError.create('Please upgrade browser', options) } parentElement.appendChild(replayElement) } function isStandalone() { return parentElement.constructor.name === 'HTMLDivElement' } function copyAttributes(newVideomail) { let attributeContainer Object.keys(newVideomail).forEach(function (attribute) { attributeContainer = parentElement.querySelector('.' + attribute) if (attributeContainer) { attributeContainer.innerHTML = newVideomail[attribute] } }) } function correctDimensions(options) { let width, height if (videomail && videomail.playerWidth) { width = videomail.playerWidth } else if (parentElement.calculateWidth) { width = parentElement.calculateWidth(options) } if (videomail && videomail.playerHeight) { height = videomail.playerHeight } else if (parentElement.calculateHeight) { height = parentElement.calculateHeight(options) } if (width > 0) { replayElement.style.width = width + 'px' } else { replayElement.style.width = 'auto' } if (height > 0) { replayElement.style.height = height + 'px' } else { replayElement.style.height = 'auto' } } this.setVideomail = function (newVideomail) { videomail = newVideomail if (videomail) { if (videomail.mp4) { this.setMp4Source(videomail.mp4) } if (videomail.webm) { this.setWebMSource(videomail.webm) } if (videomail.poster) { replayElement.setAttribute('poster', videomail.poster) } copyAttributes(videomail) } const hasAudio = videomail && videomail.recordingStats && videomail.recordingStats.sampleRate > 0 this.show(videomail && videomail.width, videomail && videomail.height, hasAudio) } this.show = function (recorderWidth, recorderHeight, hasAudio) { if (videomail) { correctDimensions({ responsive: true, // beware that recorderWidth and recorderHeight can be null sometimes videoWidth: recorderWidth || replayElement.videoWidth, videoHeight: recorderHeight || replayElement.videoHeight }) } hidden(replayElement, false) // parent element can be any object, be careful! if (parentElement) { if (parentElement.style) { hidden(parentElement, false) } else if (parentElement.show) { parentElement.show() } } if (hasAudio) { // https://github.com/binarykitchen/videomail-client/issues/115 // do not set mute to false as this will mess up. just do not mention this attribute at all replayElement.setAttribute('volume', 1) } else if (!options.isAudioEnabled()) { replayElement.setAttribute('muted', true) } // this must be called after setting the sources and when becoming visible // see https://github.com/bfred-it/iphone-inline-video/issues/16 enableInlineVideo && enableInlineVideo(replayElement, { iPad: true }) // this forces to actually fetch the videos from the server replayElement.load() if (!videomail) { self.emit(Events.PREVIEW_SHOWN) } else { self.emit(Events.REPLAY_SHOWN) } } this.build = function () { debug('Replay: build()') replayElement = parentElement.querySelector('video.' + options.selectors.replayClass) if (!replayElement) { buildElement() } this.hide() replayElement.setAttribute('autoplay', true) replayElement.setAttribute('autostart', true) replayElement.setAttribute('autobuffer', true) replayElement.setAttribute('playsinline', true) replayElement.setAttribute('webkit-playsinline', 'webkit-playsinline') replayElement.setAttribute('controls', 'controls') replayElement.setAttribute('preload', 'auto') if (!built) { if (!isStandalone()) { this.on(Events.PREVIEW, function (key, recorderWidth, recorderHeight) { self.show(recorderWidth, recorderHeight) }) } // makes use of passive option automatically for better performance // https://www.npmjs.com/package/add-eventlistener-with-options addEventListenerWithOptions(replayElement, 'touchstart', function (e) { try { e && e.preventDefault() } catch (exc) { // ignore errors like // Unable to preventDefault inside passive event listener invocation. } if (this.paused) { play() } else { pause() } }) replayElement.onclick = function (e) { e && e.preventDefault() if (this.paused) { play() } else { pause() } } } built = true debug('Replay: built.') } this.unload = function () { built = false } this.getVideoSource = function (type) { const sources = replayElement.getElementsByTagName('source') const l = sources && sources.length const videoType = 'video/' + type let source if (l) { let i for (i = 0; i < l && !source; i++) { if (sources[i].getAttribute('type') === videoType) { source = sources[i] } } } return source } function setVideoSource(type, src, bustCache) { let source = self.getVideoSource(type) if (src && bustCache) { src += '?' + Date.now() } if (!source) { if (src) { const fps = options.video.fps // Ensure it's greater than the frame duration itself const t = 2 * (1 / fps) source = h('source', { // Ensures HTML video thumbnail turns up on iOS, see // https://muffinman.io/blog/hack-for-ios-safari-to-display-html-video-thumbnail/ src: src + '#t=' + t, type: 'video/' + type }) replayElement.appendChild(source) } } else { if (src) { source.setAttribute('src', src) } else { replayElement.removeChild(source) } } } this.setMp4Source = function (src, bustCache) { setVideoSource('mp4', src, bustCache) } this.setWebMSource = function (src, bustCache) { setVideoSource('webm', src, bustCache) } this.getVideoType = function () { return browser.getVideoType(replayElement) } function pause(cb) { // avoids race condition, inspired by // http://stackoverflow.com/questions/36803176/how-to-prevent-the-play-request-was-interrupted-by-a-call-to-pause-error setTimeout(() => { try { replayElement.pause() } catch (exc) { // just ignore, see https://github.com/binarykitchen/videomail.io/issues/386 options.logger.warn(exc) } cb && cb() }, 15) } function play() { if (replayElement && replayElement.play) { let p try { p = replayElement.play() } catch (exc) { // this in the hope to catch InvalidStateError, see // https://github.com/binarykitchen/videomail-client/issues/149 options.logger.warn('Caught replay exception:', exc) } if (p && typeof Promise !== 'undefined' && p instanceof Promise) { p.catch((reason) => { options.logger.warn('Caught pending replay promise exception: %s', reason) }) } } } this.reset = function (cb) { // pause video to make sure it won't consume any memory pause(() => { if (replayElement) { self.setMp4Source(null) self.setWebMSource(null) } cb && cb() }) } this.hide = function () { if (isStandalone()) { hidden(parentElement, true) } else { replayElement && hidden(replayElement, true) } } this.isShown = function () { return replayElement && !hidden(replayElement) } this.getParentElement = function () { return parentElement } } util.inherits(Replay, EventEmitter) export default Replay