Usamos cookies. Tienes opciones. Las cookies nos ayudan a mantener el sitio funcionando sin problemas e informar sobre nuestra publicidad, pero si deseas realizar ajustes, puedes visitar nuestro Aviso de cookies para más información.
Utilizamos cookies propias y de terceros para analizar su actividad en el sitio web con el objetivo de enviarle publicidad personalizada y mejorar el funcionamiento de la web. Puedes aceptar todas las cookies pulsando el botón “ACEPTAR” o seleccionarlas en función de su funcionalidad pulsando el botón “AJUSTES”.
×

Medición de vídeos de YouTube con Google Analytics

A menudo, uno de los problemas más comunes en el mundo de la analítica, es realizar etiquetados (mediciones) de activos en una web que no se integran directamente en el DOM, como puede ser contenidos de iframes cargados desde fuentes remotas, aplicaciones java o flashvídeos en general (HTML5, Flash, YouTube, etc.) o cualquier elemento cuyos cambios no puedan verse directamente en el DOM, por lo que su captura es más compleja.

En este artículo, comentaremos cómo realizar el etiquetado de vídeos de YouTube a partir de la propia API Javascript que nos ofrece Google.

 

Hemos utilizado JavaScript y el framework jQuery para obtener los datos y enviar los eventos. Además, cabe destacar que el script puede realizar los envíos a Google Analytics directamente o a través de un data layer de Google Tag Manager, con lo cual se adapta automáticamente a la mayoría de casuísticas.

Para ello, hemos utilizado, como fuente, un script proporcionado por los chicos de LunaMetrics, que hemos modificado y ampliado para nuestro propósito.

Objetivo de la medición: detectar cuándo un vídeo se empieza a reproducir, cuándo se pausa, cuándo se ve el vídeo completo y, además, detectar porcentajes intermedios de reproducción. En este caso, queremos que nos envíe un evento de reproducción alcanzada, en los porcentajes 10%, 25%, 45%, 50%, 75% y 90%. Por la propia naturaleza de un vídeo, las mediciones se enviarán como eventos y no como páginas vistas.

Vamos explicando paso a paso las partes del código (al final del artículo se encuentra el código completo).

Parte 1: Configuración

Aquí se configurará la propiedad de Google Analytics a la que se enviará la información, los eventos a medir (playpause y vídeo completo) y los porcentajes que se quieren enviar (se enviará una custom metric diferente para cada uno).

  • Configuración
  1. // ###################################################
  2. // ######## CONFIGURACION DE GOOGLE ANALYTICS ########
  3. // ###################################################
  4.  
  5. // EDITAR ESTE PARAMETRO PARA PONER LA PROPIEDAD DE GA
  6. // A LA QUE SE VAN A ENVIAR LOS DATOS.
  7.  
  8. var GA_PROPERTY = 'UA-XXXXXX-01';
  9.  
  10.  
  11. // ###################################################
  12. // ######## CONFIGURACION DE LA MEDICION #############
  13. // ###################################################
  14.  
  15. // Por un lado se configuran los eventos a lanzar,
  16. // por otro se configuran los porcentajes de video
  17. // a medir, en este caso cada 25% y además el 10%, 45%
  18. // y 90%.
  19.  
  20. (document, window, {
  21. 'events': {
  22. 'Play': true,
  23. 'Pause': true,
  24. 'Watch to End': true
  25. },
  26. 'percentageTracking': {
  27. 'every': 25,
  28. 'each': [10, 45, 90]
  29. }
  30. });ja

Parte 2: Carga de la API

En esta parte se incluye la huella de Google Analytics, se carga la API de YouTube y se hacen las primeras inicializaciones.

  • Primeros pasos
  1. // INCLUSION DE LA HUELLA DE GOOGLE ANALYTICS
  2.  
  3. (function(i, s, o, g, r, a, m) {
  4. i['GoogleAnalyticsObject'] = r;
  5. i[r] = i[r] || function() {
  6. (i[r].q = i[r].q || []).push(arguments);
  7. }, i[r].l = 1 * new Date();
  8. a = s.createElement(o),
  9. m = s.getElementsByTagName(o)[0];
  10. a.async = 1;
  11. a.src = g;
  12. m.parentNode.insertBefore(a, m);
  13. })(window, document, 'script', '//www.google-analytics.com/analytics.js', 'ga');
  14. ga('create', GA_PROPERTY, 'auto');
  15.  
  16. // ###################################################
  17. // ######## ETIQUETADO DE VIDEOS DE YOUTUBE ##########
  18. // ###################################################
  19.  
  20.  
  21. (function(document, window, config) {
  22.  
  23. // ###################################################
  24. // ######## IMPLEMENTACION DE LA LOGICA ##############
  25. // ###################################################
  26.  
  27. 'use strict';
  28. var _config = config || {};
  29. var forceSyntax = _config.forceSyntax || 0;
  30. var dataLayerName = _config.dataLayerName || 'dataLayer';
  31.  
  32. // Se configuran los eventos que se van a lanzar,
  33. // inicio video, pausa video, video visto hasta el final.
  34. // Para desactivarlos basta con setearlos a false.
  35.  
  36. var eventsFired = {
  37. 'Play': true,
  38. 'Pause': true,
  39. 'Watch to End': true
  40. };
  41. var key;
  42.  
  43. // Carga la API Javascript de YouTube
  44.  
  45. var tag = document.createElement('script');
  46. tag.src = '//www.youtube.com/iframe_api';
  47. var firstScriptTag = document.getElementsByTagName('script')[0];
  48. firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);
  49.  
  50. // Lee la configuración de los eventos a lanzar
  51.  
  52. for (key in _config.events) {
  53. if (_config.events.hasOwnProperty(key)) {
  54. eventsFired[key] = _config.events[key];
  55. }
  56. }
  57. window.onYouTubeIframeAPIReady = (function() {
  58. var cached = window.onYouTubeIframeAPIReady;
  59. return function() {
  60. if (cached) {
  61. cached.apply(this, arguments);
  62. }
  63.  
  64. // En caso de detectar Internet Explorer 6 o 7 el script finaliza
  65. // por problemas de compatibilidad.
  66.  
  67. if (navigator.userAgent.match(/MSIE [67]\./gi)) return;
  68. if (document.readyState !== 'loading') {
  69. init();
  70. } else {
  71. // Fix para Internet Explorer 8, ya que el DOM ready se llama window.load
  72.  
  73. if (document.addEventListener) {
  74. addEvent(document, 'DOMContentLoaded', init);
  75. } else {
  76. addEvent(window, 'load', init);
  77. }
  78. }
  79. };
  80. })();
  81.  
  82. // Una vez cargada la API de YouTube se llama a esta función que obtiene los videos
  83. // de la página para poder medirlos.
  84.  
  85. function init() {
  86. var potentialVideos = getTagsAsArr_('iframe').concat(getTagsAsArr_('embed'));
  87. digestPotentialVideos(potentialVideos);
  88.  
  89. // Si se añaden nuevos videos al DOM de forma dinámica también se cargarán, excepto en IE8
  90.  
  91. if ('addEventListener' in document) {
  92. document.addEventListener('load', bindToNewVideos_, true);
  93. }
  94.  
  95. }

Parte 3: Implementación de la lógica.

Con este código se añade toda la funcionalidad para la identificación y procesado de los vídeos.

  • Implementación de la lógica
  1. // Función que procesa los objetos de los videos para poder medirlos.
  2.  
  3. function digestPotentialVideos(potentialVideos) {
  4. var i;
  5. for (i = 0; i < potentialVideos.length; i++) {
  6. var isYouTubeVideo = checkIfYouTubeVideo(potentialVideos[i]);
  7. if (isYouTubeVideo) {
  8. var normalizedYouTubeIframe = normalizeYouTubeIframe(potentialVideos[i]);
  9. addYouTubeEvents(normalizedYouTubeIframe);
  10. }
  11. }
  12. }
  13. // Función que determina si el video que se ha cargado realmente es de YouTube o no
  14. function checkIfYouTubeVideo(potentialYouTubeVideo) {
  15. var potentialYouTubeVideoSrc = potentialYouTubeVideo.src || '';
  16. if (potentialYouTubeVideoSrc.indexOf('youtube.com/embed/') > -1 ||
  17. potentialYouTubeVideoSrc.indexOf('youtube.com/v/') > -1) {
  18. return true;
  19. }
  20. return false;
  21. }
  22. // Función que determina si el video tiene el parámetro 'enablejsapi', imprescindible
  23. // para realizar la medición, ya que habilita el acceso via API.
  24.  
  25. function jsApiEnabled(url) {
  26. return url.indexOf('enablejsapi') > -1;
  27. }
  28.  
  29. // Función que determina si el video tiene el parámetro 'origin'
  30.  
  31. function originEnabled(url) {
  32. return url.indexOf('origin') > -1;
  33. }
  34.  
  35. // Función que procesa los videos embebidos para convertirlos en iframes y asignarles los parametros correctos.
  36.  
  37. function normalizeYouTubeIframe(youTubeVideo) {
  38. var loc = window.location;
  39. var a = document.createElement('a');
  40. a.href = youTubeVideo.src;
  41. a.hostname = 'www.youtube.com';
  42. a.protocol = loc.protocol;
  43. var tmpPathname = a.pathname.charAt(0) === '/' ? a.pathname : '/' + a.pathname; // IE10 shim
  44.  
  45. // Por motivos de seguridad, YouTube requiere el parametro origen, si no está, lo creamos.
  46.  
  47. if (!jsApiEnabled(a.search)) {
  48. a.search = (a.search.length > 0 ? a.search + '&' : '') + 'enablejsapi=1';
  49. }
  50. if (!originEnabled(a.search) && loc.hostname.indexOf('localhost') === -1) {
  51.  
  52. var port = loc.port ? ':' + loc.port : '';
  53. var origin = loc.protocol + '%2F%2F' + loc.hostname + port;
  54. a.search = a.search + '&origin=' + origin;
  55. }
  56. if (youTubeVideo.type === 'application/x-shockwave-flash') {
  57. var newIframe = document.createElement('iframe');
  58. newIframe.height = youTubeVideo.height;
  59. newIframe.width = youTubeVideo.width;
  60. tmpPathname = tmpPathname.replace('/v/', '/embed/');
  61. youTubeVideo.parentNode.parentNode.replaceChild(newIframe, youTubeVideo.parentNode);
  62. youTubeVideo = newIframe;
  63. }
  64. a.pathname = tmpPathname;
  65. if (youTubeVideo.src !== a.href + a.hash) {
  66. youTubeVideo.src = a.href + a.hash;
  67. }
  68. return youTubeVideo;
  69. }
  70. // Función que asigna handlers para los eventos emitidos por la API de YouTube
  71.  
  72. function addYouTubeEvents(youTubeIframe) {
  73. var player = YT.get(youTubeIframe.id);
  74. if (!player) {
  75. player = new YT.Player(youTubeIframe, {});
  76. }
  77. if (typeof youTubeIframe.pauseFlag === 'undefined') {
  78. youTubeIframe.pauseFlag = false;
  79. player.addEventListener('onStateChange', function(evt) {
  80. onStateChangeHandler(evt, youTubeIframe);
  81. });
  82. }
  83. }
  84.  
  85. // Funcion que retorna los porcentajes de video vistos.
  86.  
  87. function getMarks(duration) {
  88. var marks = {};
  89. if (_config.events['Watch to End']) {
  90. marks['Watch to End'] = Math.min(duration - 3, Math.floor(duration * 0.99));
  91. }
  92. if (_config.percentageTracking) {
  93. var points = [];
  94. var i;
  95. if (_config.percentageTracking.each) {
  96. points = points.concat(_config.percentageTracking.each);
  97. }
  98. if (_config.percentageTracking.every) {
  99. var every = parseInt(_config.percentageTracking.every, 10);
  100. var num = 100 / every;
  101. for (i = 1; i < num; i++) {
  102. points.push(i * every);
  103. }
  104. }
  105. for (i = 0; i < points.length; i++) {
  106. var _point = points[i];
  107. var _mark = _point + '%';
  108. var _time = duration * _point / 100;
  109. marks[_mark] = Math.floor(_time);
  110. }
  111. }
  112. return marks;
  113. }
  114.  
  115. // Función que devuelve si el video se ha visto por completo o no
  116.  
  117. function checkCompletion(player, marks, videoId) {
  118. var currentTime = player.getCurrentTime();
  119. var key;
  120. player[videoId] = player[videoId] || {};
  121. for (key in marks) {
  122. if (marks[key] <= currentTime && !player[videoId][key]) {
  123. player[videoId][key] = true;
  124. fireAnalyticsEvent(videoId, key);
  125. }
  126. }
  127. }
  128.  
  129. // Función que gestiona los eventos devueltos por la API
  130.  
  131. function onStateChangeHandler(evt, youTubeIframe) {
  132. var stateIndex = evt.data;
  133. var player = evt.target;
  134. var targetVideoUrl = player.getVideoUrl();
  135. var targetVideoId = targetVideoUrl.match(/[?&]v=([^&#]*)/)[1];
  136. var playerState = player.getPlayerState();
  137. var duration = Math.floor(player.getDuration());
  138. var marks = getMarks(duration);
  139. var playerStatesIndex = {
  140. '1': 'Play',
  141. '2': 'Pause'
  142. };
  143. var state = playerStatesIndex[stateIndex];
  144. youTubeIframe.playTracker = youTubeIframe.playTracker || {};
  145. if (playerState === 1 && !youTubeIframe.timer) {
  146. clearInterval(youTubeIframe.timer);
  147. youTubeIframe.timer = setInterval(function() {
  148. // Comprueba los porcentajes cada 1 segundo
  149. checkCompletion(player, marks, youTubeIframe.videoId);
  150. }, 1000);
  151. } else {
  152. clearInterval(youTubeIframe.timer);
  153. youTubeIframe.timer = false;
  154. }
  155. if (stateIndex === 1) {
  156. youTubeIframe.playTracker[targetVideoId] = true;
  157. youTubeIframe.videoId = targetVideoId;
  158. youTubeIframe.pauseFlag = false;
  159. }
  160. if (!youTubeIframe.playTracker[youTubeIframe.videoId]) {
  161.  
  162. // Excluye la publicidad inicial
  163.  
  164. return false;
  165. }
  166. if (stateIndex === 2) {
  167. if (!youTubeIframe.pauseFlag) {
  168. youTubeIframe.pauseFlag = true;
  169. } else {
  170. // Fix para evitar enviar varios pauses seguidos
  171. return false;
  172. }
  173. }
  174.  
  175. // Elimina los eventos que no se quieren trackear
  176.  
  177. if (eventsFired[state]) {
  178. fireAnalyticsEvent(youTubeIframe.videoId, state);
  179. }
  180. }
  181.  
  182. // Función que hace compatibles los eventos con todos los navegadores.
  183.  
  184. function addEvent(el, name, fn) {
  185. if (el.addEventListener) {
  186. el.addEventListener(name, fn);
  187. } else if (el.attachEvent) {
  188. el.attachEvent('on' + name, function(evt) {
  189. evt.target = evt.target || evt.srcElement;
  190. // Call the event to ensure uniform 'this' handling, pass it event
  191. fn.call(el, evt);
  192. });
  193. } else if (typeof el['on' + name] === 'undefined' || el['on' + name] === null) {
  194.  
  195. el['on' + name] = function(evt) {
  196. evt.target = evt.target || evt.srcElement;
  197. // Call the event to ensure uniform 'this' handling, pass it event
  198. fn.call(el, evt);
  199. };
  200. }
  201. }
  202.  
  203. // Función que obtiene tags
  204.  
  205. function getTagsAsArr_(tagName) {
  206. return [].slice.call(document.getElementsByTagName(tagName));
  207. }
  208.  
  209. // Función que bindea videos como videos nuevos
  210.  
  211. function bindToNewVideos_(evt) {
  212. var el = evt.target || evt.srcElement;
  213. var isYT = checkIfYouTubeVideo(el);
  214. if (el.tagName === 'IFRAME' && isYT && jsApiEnabled(el.src) && originEnabled(el.src)) {
  215. addYouTubeEvents(el);
  216. }
  217. }

Parte 4: Envíos a Google Analytics

Finalmente, realizamos el envío a Google Analytics. Aquí, podemos configurar las dimensiones a enviar y las custom metrics (en nuestro caso se envió una custom metric por cada evento de porcentaje).

Nota: no olvidéis definir las custom dimensions y las custom metrics en vuestra propiedad de Google Analytics que va a recibir los datos o de lo contrario no se quedarán almacenados.

  • Envíos a Google Analytics
  1. // Funcion que dice si el evento va a Google Analytics o a Google Tag Manager
  2. // en función de si hay dataLayer o no.
  3.  
  4. function fireAnalyticsEvent(videoId, state) {
  5. var videoUrl = 'https://www.youtube.com/watch?v=' + videoId;
  6. var _ga = window.GoogleAnalyticsObject;
  7. if (typeof window[dataLayerName] !== 'undefined' && !_config.forceSyntax) {
  8. window[dataLayerName].push({
  9. 'event': 'youTubeTrack',
  10. 'attributes': {
  11. 'videoUrl': videoUrl,
  12. 'videoAction': state
  13. }
  14. });
  15. } else if (typeof window[_ga] === 'function' &&
  16. typeof window[_ga].getAll === 'function' &&
  17. _config.forceSyntax !== 2) {
  18. // Envio a Google Analytics, con configuración de Custom Dimensions y
  19. // Custom Metrics, una para cada porcentaje medido
  20. window[_ga]('send', 'event', 'medicion de videos', state, videoUrl, {
  21. title: 'titulo a enviar',
  22. language: 'idioma a enviar',
  23. dimension1: 'contenido de la custom dimension1',
  24. dimension2: 'contenido de la custom dimension2',
  25. dimension3: 'contenido de la custom dimension3',
  26. metric1: (state.indexOf('Play') != -1) ? 1 : null,
  27. metric2: (state.indexOf('Pause') != -1) ? 1 : null,
  28. metric3: (state.indexOf('Watch to End') != -1) ? 1 : null,
  29. metric4: (state.indexOf('10') != -1) ? 1 : null,
  30. metric5: (state.indexOf('25') != -1) ? 1 : null,
  31. metric6: (state.indexOf('45') != -1) ? 1 : null,
  32. metric7: (state.indexOf('50') != -1) ? 1 : null,
  33. metric8: (state.indexOf('75') != -1) ? 1 : null,
  34. metric9: (state.indexOf('90') != -1) ? 1 : null
  35. });
  36. } else if (typeof window._gaq !== 'undefined' && forceSyntax !== 1) {
  37. window._gaq.push(['_trackEvent', 'Videos', state, videoUrl]);
  38. }
  39. }

 

Por último, así nos queda el script final.

  • Código completo
  1. // ###################################################
  2. // ######## CONFIGURACION DE GOOGLE ANALYTICS ########
  3. // ###################################################
  4.  
  5. // EDITAR ESTE PARAMETRO PARA PONER LA PROPIEDAD DE GA
  6. // A LA QUE SE VAN A ENVIAR LOS DATOS.
  7.  
  8. var GA_PROPERTY = 'UA-XXXXXX-01';
  9.  
  10.  
  11. // ###################################################
  12. // ######## CONFIGURACION DE LA MEDICION #############
  13. // ###################################################
  14.  
  15. // Por un lado se configuran los eventos a lanzar,
  16. // por otro se configuran los porcentajes de video
  17. // a medir, en este caso cada 25% y además el 10%, 45%
  18. // y 90%.
  19.  
  20. (document, window, {
  21. 'events': {
  22. 'Play': true,
  23. 'Pause': true,
  24. 'Watch to End': true
  25. },
  26. 'percentageTracking': {
  27. 'every': 25,
  28. 'each': [10, 45, 90]
  29. }
  30. });
  31.  
  32. // ###################################################
  33. // ######## FIN DE CONFIGURACION DE LA MEDICION ######
  34. // ###################################################
  35.  
  36. // INCLUSION DE LA HUELLA DE GOOGLE ANALYTICS
  37.  
  38. (function(i, s, o, g, r, a, m) {
  39. i['GoogleAnalyticsObject'] = r;
  40. i[r] = i[r] || function() {
  41. (i[r].q = i[r].q || []).push(arguments);
  42. }, i[r].l = 1 * new Date();
  43. a = s.createElement(o),
  44. m = s.getElementsByTagName(o)[0];
  45. a.async = 1;
  46. a.src = g;
  47. m.parentNode.insertBefore(a, m);
  48. })(window, document, 'script', '//www.google-analytics.com/analytics.js', 'ga');
  49. ga('create', GA_PROPERTY, 'auto');
  50.  
  51. // ###################################################
  52. // ######## ETIQUETADO DE VIDEOS DE YOUTUBE ##########
  53. // ###################################################
  54.  
  55.  
  56. (function(document, window, config) {
  57.  
  58. // ###################################################
  59. // ######## IMPLEMENTACION DE LA LOGICA ##############
  60. // ###################################################
  61.  
  62. 'use strict';
  63. var _config = config || {};
  64. var forceSyntax = _config.forceSyntax || 0;
  65. var dataLayerName = _config.dataLayerName || 'dataLayer';
  66.  
  67. // Se configuran los eventos que se van a lanzar,
  68. // inicio video, pausa video, video visto hasta el final.
  69. // Para desactivarlos basta con setearlos a false.
  70.  
  71. var eventsFired = {
  72. 'Play': true,
  73. 'Pause': true,
  74. 'Watch to End': true
  75. };
  76. var key;
  77.  
  78. // Carga la API Javascript de YouTube
  79.  
  80. var tag = document.createElement('script');
  81. tag.src = '//www.youtube.com/iframe_api';
  82. var firstScriptTag = document.getElementsByTagName('script')[0];
  83. firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);
  84.  
  85. // Lee la configuración de los eventos a lanzar
  86.  
  87. for (key in _config.events) {
  88. if (_config.events.hasOwnProperty(key)) {
  89. eventsFired[key] = _config.events[key];
  90. }
  91. }
  92. window.onYouTubeIframeAPIReady = (function() {
  93. var cached = window.onYouTubeIframeAPIReady;
  94. return function() {
  95. if (cached) {
  96. cached.apply(this, arguments);
  97. }
  98.  
  99. // En caso de detectar Internet Explorer 6 o 7 el script finaliza
  100. // por problemas de compatibilidad.
  101.  
  102. if (navigator.userAgent.match(/MSIE [67]\./gi)) return;
  103. if (document.readyState !== 'loading') {
  104. init();
  105. } else {
  106. // Fix para Internet Explorer 8, ya que el DOM ready se llama window.load
  107.  
  108. if (document.addEventListener) {
  109. addEvent(document, 'DOMContentLoaded', init);
  110. } else {
  111. addEvent(window, 'load', init);
  112. }
  113. }
  114. };
  115. })();
  116.  
  117. // Una vez cargada la API de YouTube se llama a esta función que obtiene los videos
  118. // de la página para poder medirlos.
  119.  
  120. function init() {
  121. var potentialVideos = getTagsAsArr_('iframe').concat(getTagsAsArr_('embed'));
  122. digestPotentialVideos(potentialVideos);
  123.  
  124. // Si se añaden nuevos videos al DOM de forma dinámica también se cargarán, excepto en IE8
  125.  
  126. if ('addEventListener' in document) {
  127. document.addEventListener('load', bindToNewVideos_, true);
  128. }
  129.  
  130. }
  131.  
  132. // Función que procesa los objetos de los videos para poder medirlos.
  133.  
  134. function digestPotentialVideos(potentialVideos) {
  135. var i;
  136. for (i = 0; i < potentialVideos.length; i++) {
  137. var isYouTubeVideo = checkIfYouTubeVideo(potentialVideos[i]);
  138. if (isYouTubeVideo) {
  139. var normalizedYouTubeIframe = normalizeYouTubeIframe(potentialVideos[i]);
  140. addYouTubeEvents(normalizedYouTubeIframe);
  141. }
  142. }
  143. }
  144. // Función que determina si el video que se ha cargado realmente es de YouTube o no
  145. function checkIfYouTubeVideo(potentialYouTubeVideo) {
  146. var potentialYouTubeVideoSrc = potentialYouTubeVideo.src || '';
  147. if (potentialYouTubeVideoSrc.indexOf('youtube.com/embed/') > -1 ||
  148. potentialYouTubeVideoSrc.indexOf('youtube.com/v/') > -1) {
  149. return true;
  150. }
  151. return false;
  152. }
  153. // Función que determina si el video tiene el parámetro 'enablejsapi', imprescindible
  154. // para realizar la medición, ya que habilita el acceso via API.
  155.  
  156. function jsApiEnabled(url) {
  157. return url.indexOf('enablejsapi') > -1;
  158. }
  159.  
  160. // Función que determina si el video tiene el parámetro 'origin'
  161.  
  162. function originEnabled(url) {
  163. return url.indexOf('origin') > -1;
  164. }
  165.  
  166. // Función que procesa los videos embebidos para convertirlos en iframes y asignarles los parametros correctos.
  167.  
  168. function normalizeYouTubeIframe(youTubeVideo) {
  169. var loc = window.location;
  170. var a = document.createElement('a');
  171. a.href = youTubeVideo.src;
  172. a.hostname = 'www.youtube.com';
  173. a.protocol = loc.protocol;
  174. var tmpPathname = a.pathname.charAt(0) === '/' ? a.pathname : '/' + a.pathname; // IE10 shim
  175.  
  176. // Por motivos de seguridad, YouTube requiere el parametro origen, si no está, lo creamos.
  177.  
  178. if (!jsApiEnabled(a.search)) {
  179. a.search = (a.search.length > 0 ? a.search + '&' : '') + 'enablejsapi=1';
  180. }
  181. if (!originEnabled(a.search) && loc.hostname.indexOf('localhost') === -1) {
  182.  
  183. var port = loc.port ? ':' + loc.port : '';
  184. var origin = loc.protocol + '%2F%2F' + loc.hostname + port;
  185. a.search = a.search + '&origin=' + origin;
  186. }
  187. if (youTubeVideo.type === 'application/x-shockwave-flash') {
  188. var newIframe = document.createElement('iframe');
  189. newIframe.height = youTubeVideo.height;
  190. newIframe.width = youTubeVideo.width;
  191. tmpPathname = tmpPathname.replace('/v/', '/embed/');
  192. youTubeVideo.parentNode.parentNode.replaceChild(newIframe, youTubeVideo.parentNode);
  193. youTubeVideo = newIframe;
  194. }
  195. a.pathname = tmpPathname;
  196. if (youTubeVideo.src !== a.href + a.hash) {
  197. youTubeVideo.src = a.href + a.hash;
  198. }
  199. return youTubeVideo;
  200. }
  201. // Función que asigna handlers para los eventos emitidos por la API de YouTube
  202.  
  203. function addYouTubeEvents(youTubeIframe) {
  204. var player = YT.get(youTubeIframe.id);
  205. if (!player) {
  206. player = new YT.Player(youTubeIframe, {});
  207. }
  208. if (typeof youTubeIframe.pauseFlag === 'undefined') {
  209. youTubeIframe.pauseFlag = false;
  210. player.addEventListener('onStateChange', function(evt) {
  211. onStateChangeHandler(evt, youTubeIframe);
  212. });
  213. }
  214. }
  215.  
  216. // Funcion que retorna los porcentajes de video vistos.
  217.  
  218. function getMarks(duration) {
  219. var marks = {};
  220. if (_config.events['Watch to End']) {
  221. marks['Watch to End'] = Math.min(duration - 3, Math.floor(duration * 0.99));
  222. }
  223. if (_config.percentageTracking) {
  224. var points = [];
  225. var i;
  226. if (_config.percentageTracking.each) {
  227. points = points.concat(_config.percentageTracking.each);
  228. }
  229. if (_config.percentageTracking.every) {
  230. var every = parseInt(_config.percentageTracking.every, 10);
  231. var num = 100 / every;
  232. for (i = 1; i < num; i++) {
  233. points.push(i * every);
  234. }
  235. }
  236. for (i = 0; i < points.length; i++) {
  237. var _point = points[i];
  238. var _mark = _point + '%';
  239. var _time = duration * _point / 100;
  240. marks[_mark] = Math.floor(_time);
  241. }
  242. }
  243. return marks;
  244. }
  245.  
  246. // Función que devuelve si el video se ha visto por completo o no
  247.  
  248. function checkCompletion(player, marks, videoId) {
  249. var currentTime = player.getCurrentTime();
  250. var key;
  251. player[videoId] = player[videoId] || {};
  252. for (key in marks) {
  253. if (marks[key] <= currentTime && !player[videoId][key]) {
  254. player[videoId][key] = true;
  255. fireAnalyticsEvent(videoId, key);
  256. }
  257. }
  258. }
  259.  
  260. // Función que gestiona los eventos devueltos por la API
  261.  
  262. function onStateChangeHandler(evt, youTubeIframe) {
  263. var stateIndex = evt.data;
  264. var player = evt.target;
  265. var targetVideoUrl = player.getVideoUrl();
  266. var targetVideoId = targetVideoUrl.match(/[?&]v=([^&#]*)/)[1];
  267. var playerState = player.getPlayerState();
  268. var duration = Math.floor(player.getDuration());
  269. var marks = getMarks(duration);
  270. var playerStatesIndex = {
  271. '1': 'Play',
  272. '2': 'Pause'
  273. };
  274. var state = playerStatesIndex[stateIndex];
  275. youTubeIframe.playTracker = youTubeIframe.playTracker || {};
  276. if (playerState === 1 && !youTubeIframe.timer) {
  277. clearInterval(youTubeIframe.timer);
  278. youTubeIframe.timer = setInterval(function() {
  279. // Comprueba los porcentajes cada 1 segundo
  280. checkCompletion(player, marks, youTubeIframe.videoId);
  281. }, 1000);
  282. } else {
  283. clearInterval(youTubeIframe.timer);
  284. youTubeIframe.timer = false;
  285. }
  286. if (stateIndex === 1) {
  287. youTubeIframe.playTracker[targetVideoId] = true;
  288. youTubeIframe.videoId = targetVideoId;
  289. youTubeIframe.pauseFlag = false;
  290. }
  291. if (!youTubeIframe.playTracker[youTubeIframe.videoId]) {
  292.  
  293. // Excluye la publicidad inicial
  294.  
  295. return false;
  296. }
  297. if (stateIndex === 2) {
  298. if (!youTubeIframe.pauseFlag) {
  299. youTubeIframe.pauseFlag = true;
  300. } else {
  301. // Fix para evitar enviar varios pauses seguidos
  302. return false;
  303. }
  304. }
  305.  
  306. // Elimina los eventos que no se quieren trackear
  307.  
  308. if (eventsFired[state]) {
  309. fireAnalyticsEvent(youTubeIframe.videoId, state);
  310. }
  311. }
  312.  
  313. // Funcion que dice si el evento va a Google Analytics o a Google Tag Manager
  314. // en función de si hay dataLayer o no.
  315.  
  316. function fireAnalyticsEvent(videoId, state) {
  317. var videoUrl = 'https://www.youtube.com/watch?v=' + videoId;
  318. var _ga = window.GoogleAnalyticsObject;
  319. if (typeof window[dataLayerName] !== 'undefined' && !_config.forceSyntax) {
  320. window[dataLayerName].push({
  321. 'event': 'youTubeTrack',
  322. 'attributes': {
  323. 'videoUrl': videoUrl,
  324. 'videoAction': state
  325. }
  326. });
  327. } else if (typeof window[_ga] === 'function' &&
  328. typeof window[_ga].getAll === 'function' &&
  329. _config.forceSyntax !== 2) {
  330. // Envio a Google Analytics, con configuración de Custom Dimensions y
  331. // Custom Metrics, una para cada porcentaje medido
  332. window[_ga]('send', 'event', 'medicion de videos', state, videoUrl, {
  333. title: 'titulo a enviar',
  334. language: 'idioma a enviar',
  335. dimension1: 'contenido de la custom dimension1',
  336. dimension2: 'contenido de la custom dimension2',
  337. dimension3: 'contenido de la custom dimension3',
  338. metric1: (state.indexOf('Play') != -1) ? 1 : null,
  339. metric2: (state.indexOf('Pause') != -1) ? 1 : null,
  340. metric3: (state.indexOf('Watch to End') != -1) ? 1 : null,
  341. metric4: (state.indexOf('10') != -1) ? 1 : null,
  342. metric5: (state.indexOf('25') != -1) ? 1 : null,
  343. metric6: (state.indexOf('45') != -1) ? 1 : null,
  344. metric7: (state.indexOf('50') != -1) ? 1 : null,
  345. metric8: (state.indexOf('75') != -1) ? 1 : null,
  346. metric9: (state.indexOf('90') != -1) ? 1 : null
  347. });
  348. } else if (typeof window._gaq !== 'undefined' && forceSyntax !== 1) {
  349. window._gaq.push(['_trackEvent', 'Videos', state, videoUrl]);
  350. }
  351. }
  352.  
  353. // Función que hace compatibles los eventos con todos los navegadores.
  354.  
  355. function addEvent(el, name, fn) {
  356. if (el.addEventListener) {
  357. el.addEventListener(name, fn);
  358. } else if (el.attachEvent) {
  359. el.attachEvent('on' + name, function(evt) {
  360. evt.target = evt.target || evt.srcElement;
  361. // Call the event to ensure uniform 'this' handling, pass it event
  362. fn.call(el, evt);
  363. });
  364. } else if (typeof el['on' + name] === 'undefined' || el['on' + name] === null) {
  365.  
  366. el['on' + name] = function(evt) {
  367. evt.target = evt.target || evt.srcElement;
  368. // Call the event to ensure uniform 'this' handling, pass it event
  369. fn.call(el, evt);
  370. };
  371. }
  372. }
  373.  
  374. // Función que obtiene tags
  375.  
  376. function getTagsAsArr_(tagName) {
  377. return [].slice.call(document.getElementsByTagName(tagName));
  378. }
  379.  
  380. // Función que bindea videos como videos nuevos
  381.  
  382. function bindToNewVideos_(evt) {
  383. var el = evt.target || evt.srcElement;
  384. var isYT = checkIfYouTubeVideo(el);
  385. if (el.tagName === 'IFRAME' && isYT && jsApiEnabled(el.src) && originEnabled(el.src)) {
  386. addYouTubeEvents(el);
  387. }
  388. }
  389. })

De esta manera, tenemos nuestro script de medición de vídeos de YouTube con Google Analytics completo, con la inclusión de la huella de Google Analytics para realizar envíos a la propiedad correspondiente, la carga de la API de YouTube, la generación de eventos personalizados con las custom dimensions y custom metrics que queramos añadir y su posterior envío a Google para almacenar la información.

 

 

En nuestra compañía