{"version":3,"file":"provider.youtube.js","sources":["webpack:///./src/js/providers/youtube.js"],"sourcesContent":["define([\n 'utils/helpers',\n 'utils/css',\n 'utils/underscore',\n 'events/events',\n 'events/states',\n 'utils/scriptloader',\n 'providers/default',\n 'utils/backbone.events'\n], function(utils, cssUtils, _, events, states, scriptloader, DefaultProvider, Events) {\n var _scriptLoader = new scriptloader(window.location.protocol + '//www.youtube.com/iframe_api'),\n _isMobile = utils.isMobile();\n\n function YoutubeProvider(_playerId, _playerConfig) {\n this.state = states.IDLE;\n\n _.extend(this, Events);\n\n var _this = this,\n // Youtube API and Player Instance\n _youtubeAPI = window.YT,\n _youtubePlayer = null,\n // iFrame Container (this element will be replaced by iFrame element)\n _element = document.createElement('div'),\n // view container\n _container,\n // player state\n _bufferPercent = -1,\n // only add player ready listener once\n _listeningForReady = false,\n // function to call once api and view are ready\n _youtubeEmbedReadyCallback = null,\n // function to call once _ytPlayer api is ready\n _youtubePlayerReadyCallback = null,\n // update timer\n _playingInterval = -1,\n // current Youtube state, tracked because state events fail to fire\n _youtubeState = -1,\n // post roll support\n _beforecompleted = false,\n // user must click video to initiate playback, gets set to false once playback starts\n _requiresUserInteraction = _isMobile;\n\n this.setState = function(state) {\n clearInterval(_playingInterval);\n if (state !== states.IDLE && state !== states.COMPLETE) {\n // always run this interval when not idle because we can't trust events from iFrame\n _playingInterval = setInterval(_checkPlaybackHandler, 250);\n if (state === states.PLAYING) {\n this.seeking = false;\n } else if (state === states.LOADING || state === states.STALLED) {\n _bufferUpdate();\n }\n }\n\n DefaultProvider.setState.apply(this, arguments);\n };\n\n // Load iFrame API\n if (!_youtubeAPI && _scriptLoader && _scriptLoader.getStatus() === scriptloader.loaderstatus.NEW) {\n _scriptLoader.on(events.COMPLETE, _onLoadSuccess);\n _scriptLoader.on(events.ERROR, _onLoadError);\n _scriptLoader.load();\n }\n\n // setup container\n _element.id = _playerId + '_youtube';\n\n function _onLoadSuccess() {\n if (window.YT && window.YT.loaded) {\n _youtubeAPI = window.YT;\n _readyCheck();\n } else {\n // poll until Yo API is loaded\n setTimeout(_onLoadSuccess, 100);\n }\n }\n\n function _onLoadError() {\n if (_scriptLoader) {\n _scriptLoader.off();\n _scriptLoader = null;\n // console.log('Error loading Youtube iFrame API: %o', event);\n // TODO: dispatch video error\n }\n }\n\n function _getVideoLayer() {\n var videoLayer = _element && _element.parentNode;\n if (!videoLayer) {\n // if jwplayer DOM is not ready, do Youtube embed on jwplayer ready\n if (!_listeningForReady) {\n window.jwplayer(_playerId).onReady(_readyCheck);\n _listeningForReady = true;\n }\n return false;\n }\n return videoLayer;\n }\n\n function _readyCheck() {\n if (_youtubeAPI && _getVideoLayer()) {\n // if setItem cued up a video, this callback will handle it now\n if (_youtubeEmbedReadyCallback) {\n _youtubeEmbedReadyCallback.apply(_this);\n }\n }\n }\n\n function _checkPlaybackHandler() {\n // return if player is not initialized and ready\n if (!_youtubePlayer || !_youtubePlayer.getPlayerState) {\n return;\n }\n // manually check for state changes since API fails to do so\n var youtubeState = _youtubePlayer.getPlayerState();\n if (youtubeState !== null &&\n youtubeState !== undefined &&\n youtubeState !== _youtubeState) {\n _onYoutubeStateChange({\n data: youtubeState\n });\n }\n // handle time and buffer updates\n var youtubeStates = _youtubeAPI.PlayerState;\n if (youtubeState === youtubeStates.PLAYING) {\n _timeUpdateHandler();\n } else if (youtubeState === youtubeStates.BUFFERING) {\n _bufferUpdate();\n }\n }\n\n\n function _round(number) {\n return Math.round(number*10)/10;\n }\n function _timeUpdateHandler() {\n _bufferUpdate();\n _this.trigger(events.JWPLAYER_MEDIA_TIME, {\n position: _round(_youtubePlayer.getCurrentTime()),\n duration: _youtubePlayer.getDuration()\n });\n }\n\n function _bufferUpdate() {\n var bufferPercent = 0;\n if (_youtubePlayer && _youtubePlayer.getVideoLoadedFraction) {\n bufferPercent = Math.round(_youtubePlayer.getVideoLoadedFraction() * 100);\n }\n if (_bufferPercent !== bufferPercent) {\n _bufferPercent = bufferPercent;\n _this.trigger(events.JWPLAYER_MEDIA_BUFFER, {\n bufferPercent: bufferPercent\n });\n //if (bufferPercent === 100) this.trigger(events.JWPLAYER_MEDIA_BUFFER_FULL);\n }\n }\n\n function _ended() {\n if (_this.state !== states.IDLE && _this.state !== states.COMPLETE) {\n _beforecompleted = true;\n _this.trigger(events.JWPLAYER_MEDIA_BEFORECOMPLETE);\n _this.setState(states.COMPLETE);\n _beforecompleted = false;\n _this.trigger(events.JWPLAYER_MEDIA_COMPLETE);\n }\n }\n\n function _sendMetaEvent() {\n _this.trigger(events.JWPLAYER_MEDIA_META, {\n duration: _youtubePlayer.getDuration(),\n width: _element.clientWidth,\n height: _element.clientHeight\n });\n }\n\n // Returns a function that is the composition of a list of functions, each\n // consuming the return value of the function that follows.\n function _composeCallbacks() {\n var args = arguments;\n var start = args.length - 1;\n return function() {\n var i = start;\n var result = args[start].apply(this, arguments);\n while (i--) { result = args[i].call(this, result); }\n return result;\n };\n }\n\n function _embedYoutubePlayer(videoId, playerVars) {\n if (!videoId) {\n throw 'invalid Youtube ID';\n }\n\n var videoLayer = _element.parentNode;\n if (!videoLayer) {\n // setContainer() hasn't been run yet\n return;\n }\n\n var ytConfig = {\n height: '100%',\n width: '100%',\n videoId: videoId,\n playerVars: _.extend({\n html5: 1,\n autoplay: 0,\n controls: 0,\n showinfo: 0,\n rel: 0,\n modestbranding: 0,\n playsinline: 1,\n origin: location.protocol + '//' + location.hostname\n }, playerVars),\n events: {\n onReady: _onYoutubePlayerReady,\n onStateChange: _onYoutubeStateChange,\n onPlaybackQualityChange: _onYoutubePlaybackQualityChange,\n // onPlaybackRateChange: _onYoutubePlaybackRateChange,\n onError: _onYoutubePlayerError\n }\n };\n\n // iFrame must be visible or it will not set up properly\n _this.setVisibility(true);\n\n _youtubePlayer = new _youtubeAPI.Player(_element, ytConfig);\n _element = _youtubePlayer.getIframe();\n\n _youtubeEmbedReadyCallback = null;\n }\n\n // Youtube Player Event Handlers\n function _onYoutubePlayerReady() {\n // If setItem was called before the player was ready, update the player now\n if (_youtubePlayerReadyCallback) {\n _youtubePlayerReadyCallback.apply(_this);\n _youtubePlayerReadyCallback = null;\n }\n }\n\n function _onYoutubeStateChange(event) {\n var youtubeStates = _youtubeAPI.PlayerState;\n _youtubeState = event.data;\n\n switch (_youtubeState) {\n\n case youtubeStates.UNSTARTED: // -1: //unstarted\n // play video on android to avoid being stuck in this state\n if (utils.isAndroid()) {\n _youtubePlayer.playVideo();\n }\n return;\n\n case youtubeStates.ENDED: // 0: //ended (idle after playback)\n _ended();\n return;\n\n case youtubeStates.PLAYING: // 1: playing\n\n //prevent duplicate captions when using JW Player captions and YT video has yt:cc=on\n if (_.isFunction(_youtubePlayer.unloadModule)) {\n _youtubePlayer.unloadModule('captions');\n }\n\n // playback has started so stop blocking api.play()\n _requiresUserInteraction = false;\n\n // sent meta size and duration\n _sendMetaEvent();\n\n // send levels when playback starts\n _this.trigger(events.JWPLAYER_MEDIA_LEVELS, {\n levels: _this.getQualityLevels(),\n currentQuality: _this.getCurrentQuality()\n });\n\n _this.setState(states.PLAYING);\n return;\n\n case youtubeStates.PAUSED: // 2: //paused\n _this.setState(states.PAUSED);\n return;\n\n case youtubeStates.BUFFERING: // 3: //buffering\n if (_this.seeking) {\n _this.setState(states.LOADING);\n } else {\n _this.setState(states.STALLED);\n }\n return;\n\n case youtubeStates.CUED: // 5: //video cued (idle before playback)\n _this.setState(states.IDLE);\n // play video on android to avoid being stuck in this state\n if (utils.isAndroid()) {\n _youtubePlayer.playVideo();\n }\n return;\n }\n }\n\n function _onYoutubePlaybackQualityChange() {\n // This event is where the Youtube player and media is actually ready and can be played\n\n // make sure playback starts/resumes\n if (_youtubeState !== _youtubeAPI.PlayerState.ENDED) {\n _this.play();\n }\n\n _this.trigger(events.JWPLAYER_MEDIA_LEVEL_CHANGED, {\n currentQuality: _this.getCurrentQuality(),\n levels: _this.getQualityLevels()\n });\n }\n\n function _onYoutubePlayerError() {\n _this.trigger(events.JWPLAYER_MEDIA_ERROR, {\n message: 'Error loading YouTube: Video could not be played'\n });\n }\n\n function _readyViewForMobile() {\n if (_isMobile) {\n _this.setVisibility(true);\n }\n }\n // Internal operations\n\n function _stopVideo() {\n clearInterval(_playingInterval);\n if (_youtubePlayer && _youtubePlayer.stopVideo) {\n utils.tryCatch(function() {\n _youtubePlayer.stopVideo();\n _youtubePlayer.clearVideo();\n });\n }\n }\n // Additional Provider Methods (not yet implemented in html5.video)\n\n this.init = function(item) {\n // For now, we want each youtube provider to delete and start from scratch\n //this.destroy();\n\n // load item on embed for mobile touch to start\n _setItem(item);\n };\n\n this.destroy = function() {\n this.remove();\n this.off();\n\n _container =\n _element =\n _youtubeAPI =\n _this = null;\n };\n\n\n // Video Provider API\n this.load = function(item) {\n this.setState(states.LOADING);\n\n _setItem(item);\n // start playback if api is ready\n _this.play();\n };\n\n function _setItem(item) {\n _youtubePlayerReadyCallback = null;\n var url = item.sources[0].file;\n var videoId = utils.youTubeID(url);\n\n _this.volume(_playerConfig.volume);\n _this.mute(_playerConfig.mute);\n _this.setVisibility(true);\n\n if (!_youtubeAPI || !_youtubePlayer) {\n // wait for API to be present and jwplayer DOM to be instantiated\n _youtubeEmbedReadyCallback = function() {\n _embedYoutubePlayer(videoId);\n };\n // make sure _youtubeAPI is set before running readyCheck\n _onLoadSuccess();\n return;\n }\n\n if (!_youtubePlayer.getPlayerState) {\n var onStart = function() {\n _this.load(item);\n };\n if (_youtubePlayerReadyCallback) {\n _youtubePlayerReadyCallback = _composeCallbacks(onStart, _youtubePlayerReadyCallback);\n } else {\n _youtubePlayerReadyCallback = onStart;\n }\n return;\n }\n\n var currentVideoId = _youtubePlayer.getVideoData().video_id;\n\n if (currentVideoId !== videoId) {\n // An exception is thrown by the iframe_api - but the call works\n // it's trying to access an element of the controls which is not present\n // because we disabled control in the setup\n if (_requiresUserInteraction) {\n _stopVideo();\n _youtubePlayer.cueVideoById(videoId);\n } else {\n _youtubePlayer.loadVideoById(videoId);\n }\n\n // if player is unstarted, ready for mobile\n var youtubeState = _youtubePlayer.getPlayerState();\n var youtubeStates = _youtubeAPI.PlayerState;\n if (youtubeState === youtubeStates.UNSTARTED || youtubeState === youtubeStates.CUED) {\n _readyViewForMobile();\n }\n } else {\n // replay current video\n if (_youtubePlayer.getCurrentTime() > 0) {\n _youtubePlayer.seekTo(0);\n }\n _sendMetaEvent();\n }\n }\n\n\n this.stop = function() {\n _stopVideo();\n this.setState(states.IDLE);\n };\n\n this.play = function() {\n if (_requiresUserInteraction) {\n return;\n }\n if (_youtubePlayer && _youtubePlayer.playVideo) {\n _youtubePlayer.playVideo();\n } else { // If the _youtubePlayer isn't setup, then play when we're ready\n if (_youtubePlayerReadyCallback) {\n _youtubePlayerReadyCallback = _composeCallbacks(this.play, _youtubePlayerReadyCallback);\n } else {\n _youtubePlayerReadyCallback = this.play;\n }\n }\n };\n\n this.pause = function() {\n if (_requiresUserInteraction) {\n return;\n }\n if (_youtubePlayer.pauseVideo) {\n _youtubePlayer.pauseVideo();\n }\n };\n\n this.seek = function(position) {\n if (_requiresUserInteraction) {\n return;\n }\n if (_youtubePlayer.seekTo) {\n this.seeking = true;\n _youtubePlayer.seekTo(position);\n }\n };\n\n this.volume = function(vol) {\n if (!_.isNumber(vol)) {\n return;\n }\n var volume = Math.min(Math.max(0, vol), 100);\n if (_youtubePlayer && _youtubePlayer.getVolume) {\n _youtubePlayer.setVolume(volume);\n }\n\n };\n\n this.mute = function(mute) {\n var muted = utils.exists(mute) ? !!mute : !_playerConfig.mute;\n if (_youtubePlayer && _youtubePlayer.mute) {\n if (muted) {\n _youtubePlayer.mute();\n } else {\n _youtubePlayer.unMute();\n }\n }\n };\n\n this.detachMedia = function() {\n return null;\n };\n\n this.attachMedia = function() {\n if (_beforecompleted) {\n this.setState(states.COMPLETE);\n this.trigger(events.JWPLAYER_MEDIA_COMPLETE);\n _beforecompleted = false;\n }\n };\n\n this.setContainer = function(parent) {\n _container = parent;\n parent.appendChild(_element);\n this.setVisibility(true);\n };\n\n this.getContainer = function() {\n return _container;\n };\n\n this.remove = function() {\n _stopVideo();\n\n // remove element\n if (_element && _container && _container === _element.parentNode) {\n _container.removeChild(_element);\n }\n\n _youtubeEmbedReadyCallback =\n _youtubePlayerReadyCallback =\n _youtubePlayer = null;\n };\n\n this.setVisibility = function(state) {\n state = !!state;\n if (state) {\n // show\n cssUtils.style(_element, {\n display: 'block'\n });\n cssUtils.style(_container, {\n visibility: 'visible',\n opacity: 1\n });\n } else {\n // hide\n if (!_isMobile) {\n cssUtils.style(_container, {\n opacity: 0\n });\n }\n }\n };\n\n this.resize = function(/* width, height, stretching */) {\n return false;\n };\n\n this.checkComplete = function() {\n return _beforecompleted;\n };\n\n this.getCurrentQuality = function() {\n if (!_youtubePlayer) {\n return -1;\n }\n if (_youtubePlayer.getAvailableQualityLevels) {\n var ytQuality = _youtubePlayer.getPlaybackQuality();\n var ytLevels = _youtubePlayer.getAvailableQualityLevels();\n return ytLevels.indexOf(ytQuality);\n }\n return -1;\n };\n\n this.getQualityLevels = function() {\n if (!_youtubePlayer) {\n return;\n }\n\n if (!_.isFunction(_youtubePlayer.getAvailableQualityLevels)) {\n return [];\n }\n\n var ytLevels = _youtubePlayer.getAvailableQualityLevels();\n\n // If the result is ['auto', 'low'], we prefer to return ['low']\n if (ytLevels.length === 2 && _.contains(ytLevels, 'auto')) {\n return {\n label : _.without(ytLevels, 'auto')\n };\n }\n\n var qualityArray = _.map(ytLevels, function(val) {\n return {\n label : val\n };\n });\n\n // We expect them in decreasing order\n return qualityArray.reverse();\n };\n\n this.setCurrentQuality = function(quality) {\n if (!_youtubePlayer) {\n return;\n }\n if (_youtubePlayer.getAvailableQualityLevels) {\n var ytLevels = _youtubePlayer.getAvailableQualityLevels();\n if (ytLevels.length) {\n var ytQuality = ytLevels[ytLevels.length - quality - 1];\n _youtubePlayer.setPlaybackQuality(ytQuality);\n }\n }\n };\n\n this.getName = YoutubeProvider.getName;\n }\n\n YoutubeProvider.getName = function() {\n return { name: 'youtube' };\n };\n\n YoutubeProvider.register = function(jwplayer) {\n jwplayer.api.registerProvider(YoutubeProvider);\n };\n\n return YoutubeProvider;\n\n});\n\n\n\n//////////////////\n// WEBPACK FOOTER\n// ./src/js/providers/youtube.js\n// module id = 76\n// module chunks = 1"],"mappings":";;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;;","sourceRoot":""}