An example of recording iframe-embedded video player states for both YouTube and Vimeo.
A Pen by Jake Albaugh on CodePen.
| <header> | |
| <div class=container> | |
| <div class=col-full> | |
| <h1>Tracking Interaction in YouTube and Vimeo iFrame APIs</h1> | |
| <p>Interact with the videos below and watch the data update. The behavior for both players is very similar, with a few discrepancies as to when different events fire (see below).</p> | |
| </div> | |
| </div> | |
| </header> | |
| <div class=container> | |
| <div class=col-full> | |
| <p>In the data, <code>sequence</code> is a history of the interaction with completed percentages when the event was triggered. <code>furthest</code> is the highest percentage of completion achieved in the interaction. <code>stops</code> is a count of the amount of times the video complete event fired. These metrics should be enough to derive an accurate picture of how the interaction unfolded; however, they are dependent on the pause, play, and stop events. If you were to navigate from the page while the video is playing, progress would not be updated.</p> | |
| <p>This Vimeo approach uses their suggested <a href="https://github.com/vimeo/player-api/tree/master/javascript" target=blank>froogaloop</a> plugin. Vimeo API Documentation can be found on <a href="https://developer.vimeo.com/player/js-api" target=blank>developer.vimeo.com</a>. YouTube's documentation can be found on <a href="https://developers.google.com/youtube/iframe_api_reference" target=blank>developers.google.com</a>. Oh, and <a href="http://www.ratatatmusic.com/" target=blank>Ratatat</a> is amazing.</p> | |
| </div> | |
| </div> | |
| <div class=container> | |
| <div class=col> | |
| <div id=youtube_player></div> | |
| <pre id=yt-output></pre> | |
| </div> | |
| <div class=col> | |
| <iframe id=vimeo_player src="https://player.vimeo.com/video/125104138?api=1&player_id=vimeo_player" width=500 height=281 frameborder=0 webkitallowfullscreen mozallowfullscreen allowfullscreen></iframe> | |
| <pre id=vm-output></pre> | |
| </div> | |
| </div> | |
| <footer> | |
| <div class=container> | |
| <div class=col-full> | |
| <h2>Discrepancies</h2> | |
| <ul> | |
| <li>Vimeo player indicates the <code>stop</code> event and <code>play</code> event after <code>stop</code> are at <code>100.2%</code>.</li> | |
| <li>YouTube player indicates <code>stop</code> is at <code>100%</code> and <code>play</code> after <code>stop</code> is at <code>0%</code>. | |
| <li>Vimeo player fires two <code>stop</code> events on finish.</li> | |
| <li>YouTube player fires an additional <code>play</code> event if you skip ahead past the buffer.</li> | |
| <li>YouTube player fires an additional <code>pause</code> event when skipping ahead while paused.</li> | |
| </ul> | |
| </div> | |
| </div> | |
| </footer> |
| // char entity "icons" for less chars | |
| var i_play = "▸", | |
| i_pause = "▮▮", | |
| i_stop = "▪"; | |
| // | |
| // YouTube iFrame | |
| // API documentation: | |
| // https://developers.google.com/youtube/iframe_api_reference | |
| // | |
| // This code loads the IFrame Player API code asynchronously. | |
| var tag = document.createElement('script'); | |
| var yt_video_data = { | |
| "sequence": [], | |
| "furthest": 0, | |
| "stops": 0 | |
| }; | |
| tag.src = "https://www.youtube.com/iframe_api"; | |
| var firstScriptTag = document.getElementsByTagName('script')[0]; | |
| firstScriptTag.parentNode.insertBefore(tag, firstScriptTag); | |
| // This function creates an <iframe> (and YouTube player) | |
| // after the API code downloads. | |
| var yt_player; | |
| function onYouTubeIframeAPIReady() { | |
| yt_player = new YT.Player('youtube_player', { | |
| height: '281', | |
| width: '500', | |
| videoId: 'f7wkRET0hbo', | |
| // we only need the state change event | |
| events: { | |
| 'onStateChange': onPlayerStateChange | |
| } | |
| }); | |
| } | |
| // The API calls this function when the player's state changes. | |
| // The function indicates that when playing a video (state=1), | |
| function onPlayerStateChange(event) { | |
| if (event.data == YT.PlayerState.PLAYING) { | |
| updateYtVideoData(i_play); | |
| } else if (event.data == YT.PlayerState.PAUSED) { | |
| updateYtVideoData(i_pause); | |
| } else if (event.data == YT.PlayerState.ENDED) { | |
| updateYtVideoData(i_stop); | |
| } | |
| } | |
| // tracking interaction data | |
| function updateYtVideoData(which) { | |
| // getting video progress | |
| var progress = ytVideoProgress(); | |
| // set furthest if progress is the furthest | |
| yt_video_data.furthest = Math.max(yt_video_data.furthest, progress); | |
| // add current video progress to sequence | |
| yt_video_data.sequence.push([which, progress]) | |
| // if video is complete | |
| if (which == i_stop) { yt_video_data.stops++; yt_video_data.furthest = 100; } | |
| // put output in dom | |
| ytPrintData(); | |
| } | |
| // printing the video data | |
| function ytPrintData() { | |
| var output = ""; | |
| for(var key in yt_video_data) { | |
| output += key + ": " + JSON.stringify(yt_video_data[key]) + "\n"; | |
| } | |
| document.getElementById("yt-output").innerHTML = output; | |
| } | |
| // getting video progress in 0-100 percentage value | |
| function ytVideoProgress() { | |
| var ratio = yt_player.getCurrentTime() / yt_player.getDuration(), | |
| percent = ratio * 100, | |
| round_percent = Math.round(percent * 10) / 10; | |
| return round_percent; | |
| } | |
| // initial call | |
| ytPrintData(); | |
| // | |
| // Vimeo player | |
| // requires Vimeo's froogaloop | |
| // API documentation: | |
| // https://developer.vimeo.com/player/js-api | |
| // | |
| var vm_video_data = { | |
| "sequence": [], | |
| "furthest": 0, | |
| "stops": 0 | |
| }; | |
| var player = document.getElementById('vimeo_player'); | |
| $f(player).addEvent('ready', ready); | |
| // crossbrowser event listener, thanks vimeo | |
| function addEvent(element, eventName, callback) { | |
| if (element.addEventListener) { | |
| element.addEventListener(eventName, callback, false); | |
| } | |
| else { | |
| element.attachEvent(eventName, callback, false); | |
| } | |
| } | |
| // when player is ready | |
| function ready(player_id) { | |
| var player = $f(player_id), | |
| duration = 0; | |
| player.addEvent('pause', onPause); | |
| player.addEvent('finish', onStop); | |
| player.addEvent('play', onPlay); | |
| player.api('getDuration', function (value, id) { | |
| duration = value; | |
| }); | |
| function onPlay(id) { updateVmVideoData(i_play); } | |
| function onPause(id) { updateVmVideoData(i_pause); } | |
| function onStop(id) { updateVmVideoData(i_stop); } | |
| // called on each event | |
| function updateVmVideoData(which) { | |
| player.api('getCurrentTime', function (time, id) { | |
| // video progress | |
| var progress = vmVideoProgress(time); | |
| // set furthest value if progress is greater | |
| vm_video_data.furthest = Math.max(vm_video_data.furthest, progress); | |
| // add current data to sequence | |
| vm_video_data.sequence.push([which, progress]); | |
| // if video has ended | |
| if(which == i_stop) { vm_video_data.stops++; vm_video_data.furthest = 100.2;}; | |
| // print vimeo data in dom | |
| vmPrintData(); | |
| }); | |
| } | |
| // yielding a progress in 0-100 percentage format | |
| function vmVideoProgress(time) { | |
| var ratio = time / duration, | |
| percent = ratio * 100, | |
| round_percent = Math.round(percent * 10) / 10; | |
| return round_percent; | |
| } | |
| // printing the video data | |
| function vmPrintData() { | |
| var output = ""; | |
| for(var key in vm_video_data) { | |
| output += key + ": " + JSON.stringify(vm_video_data[key]) + "\n"; | |
| } | |
| document.getElementById("vm-output").innerHTML = output; | |
| } | |
| // initial print | |
| vmPrintData(); | |
| } | |
| jakealbaughSignature(); |
| <script src="https://f.vimeocdn.com/js/froogaloop2.min.js"></script> | |
| // all of this is for the demo. you can ignore. | |
| @import url(http://fonts.googleapis.com/css?family=Open+Sans:300); | |
| body { | |
| background: white; | |
| color: #111; | |
| line-height:1.6; | |
| font-family: "Open Sans", sans-serif; | |
| font-weight: 300; | |
| text-shadow: 1px 1px 0px rgba(255,255,255,0.2); | |
| } | |
| header { | |
| background: #FFBB00; | |
| margin-bottom: 3em; | |
| padding: 4em 0 2em; | |
| border-bottom: 1px solid rgba(255,255,255,0.2); | |
| box-shadow: 0px 0px 12px 4px rgba(0,0,0,0.1); | |
| h1 { | |
| margin-bottom: 1em; | |
| } | |
| p { margin: 0; } | |
| } | |
| footer { | |
| padding: 2em 0; | |
| margin-top: 2em; | |
| background: #111; | |
| color: white; | |
| text-shadow: none; | |
| ul { margin: 0; padding-left: 1.5em; } | |
| h2 { margin-bottom: 1em; margin-top: 0; } | |
| } | |
| h1 { | |
| font-weight: 300; | |
| line-height: 1.2; | |
| margin: 0 0 1.5em; | |
| } | |
| a { color: #ffbb00; text-decoration: none; } | |
| iframe { | |
| margin: 2em auto; | |
| width: 100%; | |
| display: block; | |
| } | |
| iframe, pre { | |
| box-shadow: 0px 4px 12px 0px rgba(0,0,0,0.1); | |
| } | |
| code { | |
| padding: 0.15em 0.25em 0.25em; | |
| margin-top: -0.3em; | |
| display: inline-block; | |
| font-size: 0.8em; | |
| border-radius: 2px; | |
| line-height: 1; | |
| background: #222; | |
| } | |
| pre, code { | |
| color: #FFBB00; | |
| text-shadow: none; | |
| } | |
| pre { | |
| font-size: 12px; | |
| width: 100%; | |
| margin: 0 auto; | |
| padding: 1em; | |
| box-sizing: border-box; | |
| background: #111; | |
| } | |
| .container { | |
| margin: 0 auto; | |
| width: 90%; | |
| &::after { content: ''; display: table; clear: both; } | |
| } | |
| .col, .col-full { | |
| width: 100%; | |
| margin: 0 auto; | |
| box-sizing: border-box; | |
| padding: 0px 10px; | |
| } | |
| @media (min-width: 1040px) { | |
| .col { float: left; width: 50%; padding: 0 10px 2em; } | |
| .container { width: 1040px; } | |
| } | |
An example of recording iframe-embedded video player states for both YouTube and Vimeo.
A Pen by Jake Albaugh on CodePen.