var Clicks = function(youTubeAPIKey, container, takeDocumentHashOwnership, trackingID) { this.youTubeAPIKey_ = youTubeAPIKey; this.container_ = container; this.players_ = []; this.activePlayer_ = null; this.delayedConfig_ = {}; this.buildUI_(); this.eventTarget_ = document.createDocumentFragment(); this.addEventListener = this.eventTarget_.addEventListener.bind(this.eventTarget_); this.removeEventListener = this.eventTarget_.removeEventListener.bind(this.eventTarget_); this.dispatchEvent = this.eventTarget_.dispatchEvent.bind(this.eventTarget_); if (trackingID) { this.loadAnalytics(trackingID); } window.addEventListener('resize', this.onWindowResize_.bind(this)); this.addEventListener('configchange', this.updateControls_.bind(this)); if (takeDocumentHashOwnership) { this.takeDocumentHashOwnership(); } window.addEventListener('keypress', this.onKeyPress_.bind(this)); window.setInterval(this.fireConfigChange.bind(this), 300); }; Clicks.youTubeIframeAPIReady = false; Clicks.onYouTubeIframeAPIReady = []; Clicks.keyStrings = { ' ': '', '\x1b': '', }; Clicks.createElementAndAppend = function(className, parentNode) { var element = document.createElement('div'); element.className = className; parentNode.appendChild(element); return element; }; Clicks.prototype.trackEvent_ = function(var_args) { if (this.analyticsObj_) { this.analyticsObj_.apply(this, arguments); } else if (this.analyticsObjName_) { window[this.analyticsObjName_].q.push(arguments); } }; Clicks.prototype.loadAnalytics = function(trackingID) { this.analyticsObjName_ = 'ClicksAnalytics' + Math.round(Math.random() * 10000000).toString(); window['GoogleAnalyticsObject'] = this.analyticsObjName_; var completeCallback = (function() { this.analyticsObj_ = window[this.analyticsObjName_]; delete window[this.analyticsObjName_]; }).bind(this); window[this.analyticsObjName_] = { 'l': 1 * new Date(), 'q': [], }; var script = document.createElement('script'); script.src = 'https://www.google-analytics.com/analytics.js'; script.async = true; script.onload = completeCallback; document.body.appendChild(script); this.trackEvent_('create', trackingID, { 'storage': 'none', 'clientId': localStorage['clicks_tracking_client_id'] }); this.trackEvent_((function(analytics) { localStorage['clicks_tracking_client_id'] = analytics.get('clientId'); }).bind(this)); }; Clicks.prototype.takeDocumentHashOwnership = function() { if (document.location.hash.length > 1) { this.parseConfigString(document.location.hash.substring(1)); } this.addEventListener('configchange', function(e) { if (e.detail.length == 0) { return; } document.location.hash = '#' + e.detail; }); }; Clicks.prototype.onAddVideoValueChanged_ = function(e) { var value = e.target.textContent; if (value.length == 11 && value.indexOf(':') == -1 && value.indexOf('.') == -1) { // Plausible YouTube video ID this.addVideo(value); return; } var parse = document.createElement('a'); parse.href = value; if ((parse.hostname == 'youtu.be' || parse.hostname == 'www.youtu.be') && parse.pathname.length == 12) { this.addVideo(parse.pathname.substring(1)); return; } var re = new RegExp('[?&]v=([^&]{11})(&|$)'); var match = re.exec(parse.search); if (match) { this.addVideo(match[1]); return; } } Clicks.prototype.buildUI_ = function() { this.container_.tabIndex = -1; this.addVideo_ = Clicks.createElementAndAppend( 'clicks-add-video clicks-add-video-active', this.container_); var addVideoDialog = Clicks.createElementAndAppend( 'clicks-add-video-dialog', this.addVideo_); this.addVideoValue_ = Clicks.createElementAndAppend( 'clicks-add-video-input', addVideoDialog); this.addVideoValue_.contentEditable = true; this.addVideoValue_.addEventListener('keypress', function(e) { e.stopPropagation(); }); this.addVideoValue_.addEventListener('input', this.onAddVideoValueChanged_.bind(this)); this.addVideoValue_.focus(); this.loading_ = Clicks.createElementAndAppend( 'clicks-loading', this.container_); this.controls_ = Clicks.createElementAndAppend( 'clicks-controls', this.container_); var infoArea = Clicks.createElementAndAppend( 'clicks-control-info-area', this.controls_); this.title_ = Clicks.createElementAndAppend( 'clicks-title', infoArea); this.channel_ = Clicks.createElementAndAppend( 'clicks-channel', infoArea); this.currentTime_ = Clicks.createElementAndAppend( 'clicks-current-time', infoArea); this.totalTime_ = Clicks.createElementAndAppend( 'clicks-total-time', infoArea); this.buffering_ = document.createElement('img'); this.buffering_.src = '/static/images/buffering.svg'; this.buffering_.className = 'clicks-buffering'; infoArea.appendChild(this.buffering_); var controls = [ { 'title': 'Transport', 'buttons': [ [ { 'img': 'playpause', 'key': ' ', }, { 'img': 'play', 'key': 'a', }, { 'img': 'pause', 'key': 's', }, ], [ { 'img': 'jumpback-1f', 'key': 't', }, { 'img': 'jumpback-1s', 'key': 'r', }, { 'img': 'jumpback-10s', 'key': 'e', }, { 'img': 'jumpback-1m', 'key': 'w', }, { 'img': 'jumpback-10m', 'key': 'q', }, ], [ { 'img': 'jumpforward-1f', 'key': 'y', }, { 'img': 'jumpforward-1s', 'key': 'u', }, { 'img': 'jumpforward-10s', 'key': 'i', }, { 'img': 'jumpforward-1m', 'key': 'o', }, { 'img': 'jumpforward-10m', 'key': 'p', }, ], ], }, { 'title': 'Rate', 'buttons': [ [ { 'img': 'slower', 'key': '[', }, { 'img': 'faster', 'key': ']', }, ], [ { 'img': 'rate-025x', 'key': '3', }, { 'img': 'rate-05x', 'key': '4', }, { 'img': 'rate-1x', 'key': '5', }, { 'img': 'rate-125x', 'key': '6', }, { 'img': 'rate-15x', 'key': '7', }, { 'img': 'rate-2x', 'key': '8', }, ], ], }, { 'title': 'Zoom', 'buttons': [ [ { 'img': 'zoom-out', 'key': '-', }, { 'img': 'zoom-in', 'key': '+', }, ], ], }, { 'title': 'Markers', 'buttons': [], }, { 'title': 'Player', 'buttons': [ [ { 'img': 'togglefullscreen', 'key': 'd', }, { 'img': 'fullscreen', 'key': 'f', }, { 'img': 'exitfullscreen', 'key': '\x1b', }, ], [ { 'img': 'mutetoggle', 'key': 'm', }, { 'img': 'mute', 'key': 'b', }, { 'img': 'unmute', 'key': 'n', }, ], ], }, ]; var selectArea = Clicks.createElementAndAppend( 'clicks-control-section-select-area', this.controls_); this.sectionSelectors_ = {}; for (var i = 0; i < controls.length; i++) { var section = controls[i]; var sectionSelect = Clicks.createElementAndAppend( 'clicks-control-section-select', selectArea); sectionSelect.textContent = section.title; sectionSelect.addEventListener( 'click', this.activateControlSection_.bind(this, section.title)); this.sectionSelectors_[section.title] = sectionSelect; } var sectionArea = Clicks.createElementAndAppend( 'clicks-control-section-area', this.controls_); this.sections_ = {}; this.buttons_ = {}; for (var i = 0; i < controls.length; i++) { var section = controls[i]; var sectionNode = Clicks.createElementAndAppend( 'clicks-control-section', sectionArea); this.sections_[section.title] = sectionNode; for (var j = 0; j < section.buttons.length; j++) { var buttons = section.buttons[j]; var row = Clicks.createElementAndAppend( 'clicks-control-section-row', sectionNode); for (var k = 0; k < buttons.length; k++) { var button = buttons[k]; var buttonNode = this.buildButton_(button.img, button.key); row.appendChild(buttonNode); this.buttons_[button.img] = buttonNode; } } } this.playersContainer_ = Clicks.createElementAndAppend( 'clicks-players-container', this.container_); this.activateControlSection_(controls[0].title); this.container_.addEventListener('click', this.showHideControls_.bind(this)); }; Clicks.prototype.activateControlSection_ = function(title, e) { if (e) { e.stopPropagation(); } for (var key in this.sections_) { if (key == title) { this.sectionSelectors_[key].className = 'clicks-control-section-select clicks-control-section-select-active'; this.sections_[key].className = 'clicks-control-section clicks-control-section-active'; } else { this.sectionSelectors_[key].className = 'clicks-control-section-select'; this.sections_[key].className = 'clicks-control-section'; }; } }; Clicks.prototype.buildButton_ = function(image, key) { var button = document.createElement('div'); button.className = 'clicks-button'; var img = document.createElement('img'); img.src = '/static/images/' + image + '.svg'; button.appendChild(img); var shortcut = document.createElement('div'); shortcut.className = 'clicks-shortcut'; shortcut.textContent = Clicks.keyStrings[key] || key; button.appendChild(shortcut); button.addEventListener('click', function(e) { this.onKeyPress_({ 'charCode': key.charCodeAt(0), }); e.stopPropagation(); }.bind(this)); return button; }; Clicks.prototype.isFullScreen_ = function() { return window.innerHeight == screen.height; }; Clicks.prototype.parseConfigString = function(str) { var params = str.split(','); for (var i = 0; i < params.length; i++) { var keyValue = params[i].split('=', 2); switch (keyValue[0]) { case 'ytid': this.addVideo(keyValue[1]); break; case 'rate': case 'zoom': case 'muted': case 'time': this.delayedConfig_[keyValue[0]] = keyValue[1]; break; } } }; Clicks.prototype.getConfigString = function() { if (!this.activePlayer_) { return ''; } var config = { 'ytid': this.activePlayer_.id, 'rate': this.activePlayer_.getRate().realRate, 'zoom': this.activePlayer_.getZoomLevel(), 'muted': this.activePlayer_.player.isMuted() ? 1 : 0, 'time': this.activePlayer_.player.getCurrentTime(), }; var params = []; for (var key in config) { params.push(key + '=' + config[key]); } return params.join(','); }; Clicks.prototype.durationToString_ = function(num) { function zeroPad(x) { var xstr = x.toString(); return xstr.length == 1 ? '0' + xstr : xstr; } return ( zeroPad(Math.floor(num / 3600)) + 'h ' + zeroPad(Math.floor(num % 3600 / 60)) + 'm ' + zeroPad(Math.floor(num % 60)) + '.' + zeroPad(Math.floor(num * 100 % 100)) + 's' ); }; Clicks.prototype.setButtonActive_ = function(name, active) { if (active) { this.buttons_[name].className = 'clicks-button clicks-button-active'; } else { this.buttons_[name].className = 'clicks-button'; } }; Clicks.prototype.fireConfigChange = function() { var e = new CustomEvent('configchange', { 'detail': this.getConfigString(), }); this.dispatchEvent(e); }; Clicks.prototype.updateControls_ = function(e) { if (this.isFullScreen_()) { this.setButtonActive_('fullscreen', true); this.setButtonActive_('exitfullscreen', false); } else { this.setButtonActive_('fullscreen', false); this.setButtonActive_('exitfullscreen', true); } if (!this.activePlayer_) { return; } this.currentTime_.textContent = this.durationToString_( this.activePlayer_.player.getCurrentTime()); this.totalTime_.textContent = this.durationToString_( this.activePlayer_.player.getDuration()); if (this.activePlayer_.player.getPlayerState() == YT.PlayerState.BUFFERING) { this.buffering_.className = 'clicks-buffering clicks-buffering-active'; } else { this.buffering_.className = 'clicks-buffering'; } if (this.activePlayer_.player.getPlayerState() == YT.PlayerState.PLAYING) { this.setButtonActive_('play', true); this.setButtonActive_('pause', false); } else { this.setButtonActive_('play', false); this.setButtonActive_('pause', true); } if (this.activePlayer_.player.isMuted()) { this.setButtonActive_('mute', true); this.setButtonActive_('unmute', false); } else { this.setButtonActive_('mute', false); this.setButtonActive_('unmute', true); } var activeRate = this.activePlayer_.getRate().realRate; this.setButtonActive_('rate-025x', activeRate == 0.25); this.setButtonActive_('rate-05x', activeRate == 0.5); this.setButtonActive_('rate-1x', activeRate == 1.0); this.setButtonActive_('rate-125x', activeRate == 1.25); this.setButtonActive_('rate-15x', activeRate == 1.5); this.setButtonActive_('rate-2x', activeRate == 2.0); }; Clicks.prototype.addVideo = function(id) { console.log('Adding YouTube video ID:', id); new ClicksVideo(this.youTubeAPIKey_, id, this.playersContainer_, this.onVideoAdded_.bind(this)); this.addVideo_.className = 'clicks-add-video'; this.container_.focus(); this.addVideoValue_.textContent = ''; this.trackEvent_('send', 'event', 'Video', 'Add', id); }; Clicks.prototype.onVideoAdded_ = function(player) { this.players_.push(player); this.activePlayer_ = player; for (var key in this.delayedConfig_) { var value = this.delayedConfig_[key]; switch (key) { case 'rate': this.activePlayer_.setRate(parseFloat(value)); break; case 'zoom': this.activePlayer_.zoom(parseFloat(value)); break; case 'muted': if (parseInt(value)) { this.activePlayer_.player.mute(); } else { this.activePlayer_.player.unMute(); } break; case 'time': this.activePlayer_.player.seekTo(parseFloat(value), true); break; } } this.activePlayer_.resize(); this.activePlayer_.player.unMute(); document.title = player.metadata.title; this.title_.textContent = player.metadata.title; this.channel_.textContent = player.metadata.channelTitle; for (var i = 0; i < player.metadata.markers.length; i++) { var marker = player.metadata.markers[i]; var markerNode = document.createElement('div'); markerNode.className = 'clicks-controls-marker'; var markerName = document.createElement('div'); markerName.className = 'clicks-controls-marker-name'; markerName.textContent = marker[1]; markerNode.appendChild(markerName); var markerTime = document.createElement('div'); markerTime.className = 'clicks-controls-marker-time'; markerTime.textContent = this.durationToString_(marker[0]); markerNode.appendChild(markerTime); markerNode.addEventListener('click', function(time, e) { this.activePlayer_.player.seekTo(time, true); e.stopPropagation(); }.bind(this, marker[0])); this.sections_['Markers'].appendChild(markerNode); } player.unhide(); this.loading_.className = 'clicks-loading clicks-loading-complete'; player.player.playVideo(); this.fireConfigChange(); }; Clicks.prototype.onWindowResize_ = function(e) { for (var i = 0; i < this.players_.length; i++) { this.players_[i].resize(); } }; Clicks.prototype.onKeyPress_ = function(e) { switch (String.fromCharCode(e.charCode).toLowerCase()) { case ' ': if (this.activePlayer_.player.getPlayerState() == YT.PlayerState.PLAYING) { this.activePlayer_.player.pauseVideo(); } else { this.activePlayer_.player.playVideo(); } break; case 'a': this.activePlayer_.player.playVideo(); break; case 's': this.activePlayer_.player.pauseVideo(); break; case '[': var i = this.activePlayer_.getRateIndex(); if (i > 0) { this.activePlayer_.setRate(this.activePlayer_.rates[i - 1].realRate); } break; case ']': var i = this.activePlayer_.getRateIndex(); if (i < this.activePlayer_.rates.length - 1) { this.activePlayer_.setRate(this.activePlayer_.rates[i + 1].realRate); } break; case '3': this.activePlayer_.setRate(0.25); break; case '4': this.activePlayer_.setRate(0.5); break; case '5': this.activePlayer_.setRate(1.0); break; case '6': this.activePlayer_.setRate(1.25); break; case '7': this.activePlayer_.setRate(1.5); break; case '8': this.activePlayer_.setRate(2); break; case 't': this.activePlayer_.seekRelative(0 - this.activePlayer_.frameSkip); break; case 'r': this.activePlayer_.seekRelative(-1); break; case 'e': this.activePlayer_.seekRelative(-10); break; case 'w': this.activePlayer_.seekRelative(-60); break; case 'q': this.activePlayer_.seekRelative(-600); break; case 'y': this.activePlayer_.seekRelative(this.activePlayer_.frameSkip); break; case 'u': this.activePlayer_.seekRelative(1); break; case 'i': this.activePlayer_.seekRelative(10); break; case 'o': this.activePlayer_.seekRelative(60); break; case 'p': this.activePlayer_.seekRelative(600); break; case 'd': if (this.isFullScreen_()) { this.exitFullScreen_(); } else { this.fullScreen_(); } case 'f': this.fullScreen_(); break; case '\x1b': this.exitFullScreen_(); break; case 'm': if (this.activePlayer_.player.isMuted()) { this.activePlayer_.player.unMute(); } else { this.activePlayer_.player.mute(); } break; case 'b': this.activePlayer_.player.mute(); break; case 'n': this.activePlayer_.player.unMute(); break; case '-': case '_': this.activePlayer_.zoomOut(); break; case '+': case '=': this.activePlayer_.zoomIn(); break; } this.fireConfigChange(); }; Clicks.prototype.showHideControls_ = function(e) { if (this.controls_.className == 'clicks-controls clicks-controls-active') { this.controls_.className = 'clicks-controls'; } else { this.controls_.className = 'clicks-controls clicks-controls-active'; } e.stopPropagation(); }; Clicks.prototype.fullScreen_ = function() { if (this.container_.requestFullscreen) { this.container_.requestFullscreen(); } else if (this.container_.webkitRequestFullscreen) { this.container_.webkitRequestFullscreen(); } else if (this.container_.mozRequestFullScreen) { this.container_.mozRequestFullScreen(); } }; Clicks.prototype.exitFullScreen_ = function() { if (document.exitFullscreen) { document.exitFullscreen(); } else if (document.webkitExitFullscreen) { document.webkitExitFullscreen(); } else if (document.mozCancelFullScreen) { document.mozCancelFullScreen(); } }; var ClicksVideo = function(youTubeAPIKey, id, container, onReady) { this.youTubeAPIKey_ = youTubeAPIKey; this.id = id; this.container_ = container; this.onReady_ = onReady; this.loading_ = true; this.zoomLevel_ = 1.0; this.fetchVideoInfo_(id, this.onMetadataResponse_.bind(this)); if (Clicks.youTubeIframeAPIReady) { this.onAPIReady_(); } else { Clicks.onYouTubeIframeAPIReady.push(this.onAPIReady_.bind(this)); } }; ClicksVideo.prototype.zoomLevels_ = function() { // TODO: make this dynamic and refuse to zoom beyond 1:1 return [ 1.0, 1.5, 2.0, 2.5, 3.0, ]; }; ClicksVideo.prototype.getZoomLevel = function() { return this.zoomLevel_; }; ClicksVideo.prototype.zoom = function(zoomLevel) { // TODO: sotp overzoom this.zoomLevel_ = zoomLevel; this.resize(); }; ClicksVideo.prototype.zoomOut = function() { var zoomLevels = this.zoomLevels_(); var i = zoomLevels.indexOf(this.zoomLevel_); this.zoom(zoomLevels[i - 1] || this.zoomLevel_); }; ClicksVideo.prototype.zoomIn = function() { var zoomLevels = this.zoomLevels_(); var i = zoomLevels.indexOf(this.zoomLevel_); this.zoom(zoomLevels[i + 1] || this.zoomLevel_); }; ClicksVideo.prototype.unhide = function() { this.playerContainer_.className = 'clicks-player-container clicks-player-container-active'; }; ClicksVideo.prototype.resize = function() { var zoom = Math.min( this.container_.clientWidth / this.videoRes[0], this.container_.clientHeight / this.videoRes[1]); zoom = Math.min(zoom * this.zoomLevel_, 1.0); this.playerScale_.style.transform = [ 'scale(' + zoom + ',' + zoom + ')', ].join(' '); this.playerScale_.style.width = this.videoRes[0]; this.playerScale_.style.height = this.videoRes[1]; this.playerCrop_.style.width = Math.ceil(this.videoRes[0] * zoom); this.playerCrop_.style.height = Math.ceil(this.videoRes[1] * zoom); }; ClicksVideo.prototype.onMetadataResponse_ = function(response) { this.metadata = this.parseVideoDescription_(response); this.checkComplete_(); }; ClicksVideo.prototype.onPlayerReady_ = function(e) { this.playerRates = e.target.getAvailablePlaybackRates(); this.checkComplete_(); }; ClicksVideo.prototype.onPlayerStateChange_ = function(e) { if (e.data == YT.PlayerState.PLAYING && this.loading_) { this.player.pauseVideo(); this.player.seekTo(0, true); this.setRate(1.0); this.loading_ = false; this.onReady_(this); } }; ClicksVideo.prototype.onAPIReady_ = function() { this.playerContainer_ = Clicks.createElementAndAppend( 'clicks-player-container', this.container_); this.playerCrop_ = Clicks.createElementAndAppend( 'clicks-player-crop', this.playerContainer_); this.playerScale_ = Clicks.createElementAndAppend( 'clicks-player-scale', this.playerCrop_); this.playerOverlay_ = Clicks.createElementAndAppend( 'clicks-player-overlay', this.playerScale_); var tempNode = document.createElement('div'); this.playerScale_.appendChild(tempNode); this.player = new YT.Player(tempNode, { height: '1080', width: '1920', videoId: this.id, playerVars: { 'controls': 0, 'enablejsapi': 1, 'disablekb': 1, 'showinfo': 0, }, events: { 'onReady': this.onPlayerReady_.bind(this), 'onStateChange': this.onPlayerStateChange_.bind(this), }, }); }; ClicksVideo.prototype.checkComplete_ = function() { if (!this.metadata || !this.playerRates) { return; } var baseRate = parseFloat(this.metadata.tags.realfps) / parseFloat(this.metadata.tags.ytfps) this.rates = []; for (var i = 0; i < this.playerRates.length; i++) { this.rates.push({ 'playerRate': this.playerRates[i], 'realRate': this.playerRates[i] * baseRate, 'fps': this.metadata.tags['ytfps'] * this.playerRates[i], }); } this.frameSkip = 1.0 / parseFloat(this.metadata.tags.ytfps); var yRes = parseInt(this.metadata.tags.res); this.videoRes = [yRes * 16 / 9, yRes]; this.player.setSize(this.videoRes[0], this.videoRes[1]); this.player.setPlaybackQuality('highres'); this.player.setVolume(100); this.player.mute(); this.player.playVideo(); }; ClicksVideo.prototype.seekRelative = function(offset) { this.player.seekTo(this.player.getCurrentTime() + offset, true); }; ClicksVideo.prototype.getRateIndex = function() { var playerRate = this.player.getPlaybackRate(); for (var i = 0; i < this.rates.length; i++) { if (this.rates[i].playerRate == playerRate) { return i; } } return null; }; ClicksVideo.prototype.getRate = function() { return this.rates[this.getRateIndex()]; }; ClicksVideo.prototype.setRate = function(realRate) { for (var i = 0; i < this.rates.length; i++) { if (this.rates[i].realRate == realRate) { this.player.setPlaybackRate(this.rates[i].playerRate); return; } } }; ClicksVideo.prototype.buildQueryString_ = function(args) { var ret = []; for (var key in args) { ret.push(encodeURIComponent(key) + '=' + encodeURIComponent(args[key])); } return ret.join('&'); }; ClicksVideo.prototype.fetchVideoInfo_ = function(id, callback) { var queryString = this.buildQueryString_({ 'key': this.youTubeAPIKey_, 'part': 'snippet', 'id': id, }); var sendRequest = function() { var xhr = new XMLHttpRequest(); xhr.responseType = 'json'; xhr.open('GET', 'https://www.googleapis.com/youtube/v3/videos?' + queryString); xhr.addEventListener('load', function(e) { if (!e.target.response.items || e.target.response.items.length != 1) { console.log('Invalid response:', e.target); setTimeout(sendRequest, 1000); return; } callback(e.target.response.items[0]); }); xhr.addEventListener('error', function(e) { setTimeout(sendRequest, 1000); }); xhr.send(); }; sendRequest(); }; ClicksVideo.prototype.parseVideoDescription_ = function(video) { var markerRe = new RegExp('^fctv:marker=(\\d+):(.*)$'); var tagRe = new RegExp('^fctv:(.*?)=(.*)$'); var ret = { 'title': video.snippet.title, 'channelTitle': video.snippet.channelTitle, 'tags': { // Best guesses 'ytfps': 30.0, 'realfps': 30.0, 'res': '1080', }, 'markers': [], }; for (var i = 0; i < video.snippet.tags.length; i++) { var match = markerRe.exec(video.snippet.tags[i]); if (match) { ret.markers.push([parseFloat(match[1]), match[2]]); continue; } match = tagRe.exec(video.snippet.tags[i]); if (match) { ret.tags[match[1]] = match[2]; } } return ret; }; var onYouTubeIframeAPIReady = function() { Clicks.youTubeIframeAPIReady = true; for (var i = 0; i < Clicks.onYouTubeIframeAPIReady.length; i++) { Clicks.onYouTubeIframeAPIReady[i](); } Clicks.onYouTubeIframeAPIReady.length = 0; };