Track how long a user has watched a video - javascript

I teach an online course, and I need to make sure my students actually watch a certain video. Now, I understand that everything can be defeated, but what I'm looking is the 80/20 rule were I can make a few tweaks to start on the journey of accountability for my students.
Q1: Is there a way via JavaScript to fire an event if the current window loses focus?
Q2: Is there a way to fire an event when the video finishes playing?
Q3: Is there a way to make sure the video was played all the way through instead of the student clicking on the end of the timeline?
I feel compelled to say (again), please don't answer this questions with something like "you're wasting your time, the students will defeat anything that you do".

What is the player you are using. If you are using open source video players like JWPlayer or Flow Player. You can track events. I personally prefer flow player and you can use google analytics to track the duration and any other task you want on the page.
as you have an authentication mechanism on the page you can get the username of the student (or an identifier). Push events to google analytic with this as a label and you can track each and everything the student do including the links he clicked, duration what played, when he played ...
you can setup google analytic http://www.google.lk/analytics/ its Free :)
Event tracking guide https://developers.google.com/analytics/devguides/collection/gajs/eventTrackerGuide
Flow player events http://flash.flowplayer.org/documentation/events/player.html
example setup
<script type="text/javascript">
var _gaq = _gaq || [];
_gaq.push(['_setAccount', '#########']);
_gaq.push(['_trackPageview']);
(function() {
var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
})();
</script>
To track
_gaq.push(['_trackEvent', 'Videos', 'Play', 'Gone With the Wind']);
this is part of the live code i took from http://vsp.ideawide.com/ in which I track some of those events.
var events = {
clip : {
onStart: function(clip) {
_gaq.push(['_trackEvent',"Videos", "Play", defaults.source]);
},
onPause: function(clip) {
_gaq.push(['_trackEvent',"Videos", "Pause", defaults.source, parseInt(this.getTime())]);
},
onResume: function(clip) {
_gaq.push(['_trackEvent',"Videos", "Resume", defaults.source, parseInt(this.getTime())]);
},
onSeek: function(clip) {
_gaq.push(['_trackEvent',"Videos", "Seek", defaults.source ]);
},
onStop: function(clip) {
_gaq.push(['_trackEvent',"Videos", "Stop", defaults.source, parseInt(this.getTime())]);
},
onFinish: function(clip) {
_gaq.push(['_trackEvent',"Videos", "Finish", defaults.source]);
}
},
onFullscreen: function() {
_gaq.push(['_trackEvent',"Videos", "Full Screen", defaults.source]);
},
onError: function(errorCode , errorMessage) {
_gaq.push(['_trackEvent',"Videos", "Error", defaults.source, errorCode ]);
}
}
As a final note with analytic properly setup with a proper player you can improve your 80/20 to 99/1.

This is assuming HTML5 video using the video tag.
player = document.getElementById("player");
//Get current percent complete. You may want to check for 95%+ rather than 100%.
setInterval(function(){
percentComplete = player.currentTime/player.duration;
}, 300);
window.onblur = function() {
//this will be called when the window loses focus
};
You can call the video tag without the controls attribute to disable the seek bar. You can then trigger play on click using the following:
player.play();

Problems with the initial approach
I teach an online course, and I need to make sure my students actually watch a certain video.
Here's the issue. Your requirement is that students actually watch a video, but the best we can do programmatically is make sure that a video was sent by a server and received by a client. Aside from using extreme measures like facial recognition and audio loopback, you can't be sure that anyone is watching it, or that the intended viewer is watching it, or that anyone is listening. The student can simply start the video and walk away.
That might sound like a technicality, but really it's an important distinction. What you've got is the equivalent of a system designed to make sure someone reads a book, but all the system can do is check whether the book is open or closed at any given time.
Security
Now, I understand that everything can be defeated, but what I'm looking is the 80/20 rule were I can make a few tweaks to start on the journey of accountability for my students.
Everything can be defeated with enough effort, but the proposed solution can be defeated almost effortlessly. If it's easier for your average user to cheat the system than to use it legitimately, it's probably not worth implementing.
An alternative approach
Instead of focusing on whether the browser is running the video, consider shifting focus back to the user. By requiring some specific user interaction while the video is being displayed, you'll be able to have a much higher level of confidence in the system.
The simplest way to do this would be to split the video up into several small parts, displaying a link to the next video after each one plays.
You could then write a small script to show the time between requests for that series of videos (per user), and flag any requests that were too far apart or too close together given the length of the segments (or compared to the average if that data isn't available for some reason). You can pull this data right from the server logs if you have access to them, or you can record it yourself.
Comprehension
If you want to go the extra step and test comprehension (or retention), this alternative approach would be simple to extend. Instead of having one link to the next video, have several links to it, in the form of answers to a question. If you're worried about students sharing the answers, don't tell them whether they got it right, just send them to the next video. You can look at this data later and decide whether you want to use it and how you want to curve it.

Q1: look for event onblur
Q2: that would depend on your player. There are flash video players you can call a javascript function with ExternalInterface.call("myFunctionName()"); from AS3
Q3: I would try to find a flash player that has all that options but my solution is more as3 to javascript.
I hope this may help you!

Maybe do a setTimeout for the length of the video and fire an event once the length of the video has passed.
Try this to detect when the window loses focus:
window.onblur = function() {
//do something here...
};
If you're really worried about the accuracy of setTimeout, maybe try this:
var start = (new Date).getTime();
function CheckTime() {
var diff = (new Date).getTime() - start;
if (diff >= videoLength) {
FireVideoDoneEvent();
}
else {
setTimeout(function() {
CheckTime();
}, 1000);
}
}

handle window.onblur, as mentioned
you'll get the 'ended' event fire when video is done
you can call mediaElement.played.end() to get # of secs played in browser.
See here and here

Related

Get frame numbers in HTML5 Video

I am trying to capture each frame number of the video however it looks like there is no way of achieving it. So I started my own clock to match the frame numbers of the video but they never match and the difference keeps increasing as the video progress.
Please have a look at my bin. http://jsbin.com/dopuvo/4/edit
I have added the frame number to each frame of the video from Adobe After Effect so I have more accurate information of the difference. The Video is running at 29.97fps and the requestAnimationFrame is also set to increase the number at the same rate, however I am not sure where this difference is coming from.
Sometimes they match and sometimes they don't. I also tried doing it offline but I get the same results. Any help.
I found something on github for this. https://github.com/allensarkisyan/VideoFrame
I have implemented it in this fiddle: https://jsfiddle.net/k0y8tp2v/
var currentFrame = $('#currentFrame');
var video = VideoFrame({
id : 'video',
frameRate: 25,
callback : function(frame) {
currentFrame.html(frame);
}
});
$('#play-pause').click(function(){
if(video.video.paused){
video.video.play();
video.listen('frame');
$(this).html('Pause');
}else{
video.video.pause();
video.stopListen();
$(this).html('Play');
}
});
EDIT: updated fiddle to new video so it works again.
EDIT: As pointed out, the video is 25fps, so I updated it, and while I was there removed reliance on jQuery.
Non jQuery version:
https://jsfiddle.net/k0y8tp2v/1/
var currentFrame = document.getElementById('currentFrame');
var video = VideoFrame({
id : 'video',
frameRate: 25,
callback : function(frame) {
currentFrame.innerHTML = frame ;
}
});
document.getElementById('play-pause').addEventListener('click', function(e){
if(video.video.paused){
video.video.play();
video.listen('frame');
e.target.innerHTML = 'Pause';
}else{
video.video.pause();
video.stopListen();
e.target.innerHTML = 'Play';
}
});
The problem is that setTimeout is not really predictable, so you can't be sure that exactly one new frame has been displayed every time your function runs. You need to check the currentTime of the video every time you update your frame display and multiply that by the frame rate.
Here's a working example: http://jsbin.com/xarekice/1/edit It's off by one frame, but it looks like you may have two frames at the beginning marked "000000".
A few things about the video element that you may want to be aware of:
As you seem to have discovered, there's no reliable way to determine the frame rate, so you have to discover it elsewhere and hard-code it. There are some interesting things going on with video metrics, but they're non-standard, not widely supported and, in my experience, completely ineffective at determining the actual frame rate.
The currentTime is not always exactly representative of what's on the screen. Sometimes it's ahead a little bit, especially when seeking (which is why in my JSBin, I don't update the frame while you're seeking).
I believe currentTime updates on a separate thread from the actual video draw, so it kind of works like it's own clock that just keeps going. It's where the video wants to be, not necessarily where it is. So you can get close, but you need to round the results of the frame calculation, and once in a while, you may be off by one frame.
Starting in M83, Chromium has a requestVideoFrameCallback() API, which might solve your issue.
You can use the mediaTime to get a consistent timestamp, as outlined in this Github issue. From there, you could try something like this:
var frameCounter = (time, metadata) => {
let count = metadata.mediaTime * frameRate;
console.log("Got frame: " + Math.round(count));
// Capture code here.
video.requestVideoFrameCallback(frameCounter);
}
video.requestVideoFrameCallback(frameCounter)
This will only fire on new frames, but you may occasionally miss one (which you can detect from a discontinuity in the metadata.presentedFrames count). You might also be slightly late in capturing the frame (usually 16ms, or one call to window.requestAnimationFrame() later than when the video frame is available).
If you're interested in a high level overview of the API, here's a blogpost, or you can take a look at the API's offical github.

Timer display using Javascript/Jquery

I'm working on a thick client application which uses JavaScript and jQuery. My Session times out if it's left idle for 45 seconds or more (depending on different business/user).
Currently the timeout event is triggered by the vendor code to which I do not have access. I would like to display a timer on the screen if the session is found to be idle beyond a specific time threshold (for example: 25 seconds).
At the same time the timer should also match (marginal diff can be managed) with the third party timer (to which I do not have access).
Can someone suggest the best solution for this?
It's worth sharing any code you already have so that we can offer more precise advise for you.
However, to give you some pointers, take a look at the JavaScript window.setTimeout() method. It essentially triggers a callback after a set period of time. It's fairly safe to say that if your vendor is using JavaScript to keep track of time, then this is the same basic method they will be using too.
You can set and reset a timeout very easily so once set, you could listen for user interaction events (scrolling, clicking, etc) and use these to trigger a reset on the timeout (putting the 'count down' back to the start).
Do a bit of research, I'm sure you'll be able to develop a suitable solution!
With regards to interfacing with your vendor's timeout, without much more information none of us will be able to help. It doesn't sound like you have any control over this so it may not be possible (perhaps contact the vendor and ask?).
If you know what the timeout limits are for each use-case, you could ensure that your timeouts match theirs. However, this would be a little bit annoying given it's code-repetition and would also mean your application would 'break' if your vendor changes their timings.
TL/DR: Read into the JavaScript window.setTimeout() method if you want to write something that will do what you describe. However, the weak link will be that you don't have access to your vendor's timeout routines. With that in mind, my first step would be to contact the vendor and ask, they may have something in their API already available that you're unaware of!
Okay I don't quite get your logic, but a simple search on Google for jQuery plugin timer gives me:
http://www.tripwiremagazine.com/2013/04/jquery-countdown-scripts.html
A lot...
Hope one of them suits your needs.
Halo,
I finally managed to try a timer in my application (touch based). The following code works well for me.
var count = 50;
$(document).ready(function () {
var Timer = $.timer(function() {
$('#counter').html("Your Session will expire in " + --count + "seconds");
});
Timer.set({ time : 1000, autostart : true });
$(this).mousemove(function (e) {
idleTime = 0;
count = 50;
Timer.reset();
Timer.stop();
});
$(this).keypress(function (e) {
idleTime = 0;
count = 50;
Timer.stop();
Timer.reset();
});
var idleInterval = setInterval(function (){
idleTime = idleTime + 1;
if (idleTime > 10)
{
Timer.set({ time : 600, autostart : true });
$('#counter').css("display", "block");

YouTube JS API Breaking Change? Will no longer initialize without the videoid parameter as of 2/27/2013

To demonstrate the breaking change I discovered today I took the basic JS API sample page which can be found with link below and simply commented out the videoId parameter:
YT Sample Code demonstrating initialization error: http://www.multitask123.com/fmgem/YT_Sample2.htm
With said removal of videoId parameter the player will now display/initialize with the following error message in the embedded player, "An error occurred. Please try later." This was not the case until this afternoon.
For the past three months I could call onYouTubePlayerAPIReady as follows and there would be no error message:
function onYouTubePlayerAPIReady() {
player = new YT.Player('player', {
height: '390',
width: '640',
//videoId: 'JW5meKfy3fY', - COMMENTED OUT INTENTIONALLY! HARD REQUIREMENT!
events: {
'onReady': onPlayerReady,
'onError': onPlayerError,
'onStateChange': onPlayerStateChange
}
});
}
The error is caught by the onPlayerError event handler during initialization and the error comes in with the "evt" parameter's data attribute. So onPlayerError(evt) is called on initialization and evt.data = 2. This value is documented as, "The request contains an invalid parameter value" which is clearly the API expecting a videoId.
I do not want to go into too much detail but I spent countless hours getting the initialization of the player to work just right as there are numerous cross browser initialization issues. I need to hide the player until I actually need it to play something.
I forget which browser is the culprit but one of them actually forces me to show the player even though no video is playing.
I discovered I cannot lazy load/JIT the embedded player because it loads asynchronously so if I try to only load it when my users wish to actually view a video or there are other parameters of the URL involved that will load a playlist for example the player will balk because I cannot BOTH initialize the player and ask it to play a video.
So the bottom line is the player NEEDS to be initialized when the page is loading but NOT with a videoId parameter because quite simply I have no idea what is going to be played in 90% of the circumstances and even if I did that would be the result of a different asynchronous call to a different API. I say this because the solution to initialize it with a videoid is unacceptable.
The YouTube API is amazing and I love it so much so that I have spent 18 months developing http://www.fmgem.com and now this breaking change is ruining the first impression beta users will have of my app. So if the YT API team does indeed monitor this please change it back. Pretty please with sugar on top. :))
Something similar happened a few weeks ago with the same function's wmode attribute on load.
//wmode: 'opaque' //--- causes wicked bug in Chrome - And I was able to change opaque to transparent so no biggie there but this would require me to review my entire architecture and methodically handle apx 9 different asynchronous use-cases for load that again, have all had cross-browser quirks.
So I cannot afford to be reactive and I have been forced to put in a "sneezing baby panda" videoId into the live code at http://www.fmgem.com but that looks quite unprofessional and it is merely a band-aid until I can review the entire architecture unless YT Team actually acknowledges this as a breaking change and then puts it back to its prior behavior.
This was detected by some users sometime during the afternoon of 2/27/2013 and I have read on google groups that YouTube's developers monitor SO.
Any answers, solutions, guidance or confirmation would be greatly appreciated.
Thanks!!
You can initially load a blank 0 second movie. Or you can re-arrange your code a tad bit.
Do not call onYouTubePlayerAPIReady function yourself. Use flag variables instead:
var playerAPIReady;
var player;
Set the first variable when the API file calls onYouTubePlayerAPIReady function:
function onYouTubePlayerAPIReady() {
playerAPIReady = true;
}
Set the second variable when the user chooses to play first video:
function createPlayerAndPlayVideo(id) {
if(! playerAPIReady) {
// player API file not loaded
return;
}
if (! player) {
player = new YT.Player('player', {
height: '390',
width: '640',
videoId: id
events: {
'onReady': onPlayerReady,
'onError': onPlayerError,
'onStateChange': onPlayerStateChange
}
});
} else {
player.loadVideoById(id);
}
}
Re-use the same player for subsequent videos as shown in the else block.
While this is a breaking change beyond a doubt now that I got a good night's sleep I found a solution to my own issue to lazy load / JIT the player.
The solution I have a proof of concept of and am currently implementing is as simple as setting a global var to test if I have a videoid.
var cleared = false;
function onYouTubePlayerAPIReady(videoid) {
if (!cleared) return;
.....
Then when I actually want to play a video I just see if the variable is false and if so I call the onYouTubePlayerAPIReady function and pass it the videoid and initialize it. My mistake was to try to initialize and then call the play() function in the same call. The solution for me is to either call the initializer with an id or call the playbyid function if already initialized.
While I do love the YT public API and others that I use it does open the door to lots of breaking changes and that is always a test of one's architectural and mental fortitude.
I'm not ruling out that something may have changed on Feb. 27, but the intention has always been that videoId (or a list id) is required when initializing an iframe Player. It isn't spelled out explicitly in the documentation, but at the same time, it never suggests that the parameter is optional, and all examples given do include a value for videoId.
So I wouldn't count on this behavior changing back in the future.
The most common thing to do when you have a YouTube iframe Player that you want to be initialized and not visible is to use CSS to set its position outside of the viewable area in the browser (such as by using negative x and y coordinates). You can initialize the player with whatever placeholder video you want, then call loadVideoById() when you're ready to play back a real video, and when you detect onStateChange with YT.PlayerState.BUFFERING or YT.PlayerState.PLAYING, you can move the player to the appropriate visible position.
Alternatively, you can just delay loading the player until you need it.

HTML 5 Video, Streaming / Buffering only a certain portion of a longer video

We have a long piece of video, up to 1 hour long.
We want to show users small 30 second chunks of this video.
It's imperative that the video does not stutter at any point.
The user can't then jump around the rest of the video, they only see the 30 second chunk.
An example would be say, a football match, the whole match is on video but clicking a button in another page would load up the full video and play just a goal.
Is this possible with HTML5 Video?
Would it have anything to do with TimeRanges?
Does the video have to served over a pure streaming protocol?
Can we buffer the full 30 second chunk before playing it?
The goal is to cut down on the workflow required to cut out all the little clips (and the time transcoding these to all the different HTML 5 video formats), we can just throw up a trans-coded piece of footage and send the user to a section of that footage.
Your thoughts and input are most welcome, thanks!
At this point in time HTML5 videos are a real PITA -- we have no real API to control the browser buffering, hence they tend to stutter on slower connections, as the browsers try to buffer intelligently, but usually do quite the opposite.
Additionally, if you only want your users to view a particular 30 second chunk of a video (I assume that would be your way of forcing users to registers to view the full videos), HTML5 is not the right choice -- it would be incredibly simple to abuse your system.
What you really need in this case is a decent Flash Player and a Media Server in the backend -- this is when you have full control.
You could do some of this, but then you'd be subject the the browser's own buffering. (You also can't stop it from buffering beyond X sec)
Best put, you could easily have a custom seek control to restrict the ranges and stop the video when is hits the 30 second chunk.
Also, buffering is not something you can control other the tell the browser not to do it. The rest is automatic and support to force a full buffer has been removed from specs.
Anyways, just letting you know this is terrible practice and it could be done but you'll be potentially running into many issues. You could always use a service like Zencoder to help handle transcoding too. Another alternative would be to have ffmpeg or other software on the server to handle clipping and transcoding.
You can set the time using javascript (the video's currentTime property).
In case you want a custom seekbar you can do something like this:
<input type="range" step="any" id="seekbar">
var seekbar = document.getElementById('seekbar');
function setupSeekbar() {
seekbar.max = video.duration;
}
video.ondurationchange = setupSeekbar;
function seekVideo() {
video.currentTime = seekbar.value;
}
function updateUI() {
seekbar.value = video.currentTime;
}
seekbar.onchange = seekVideo;
video.ontimeupdate = updateUI;
function setupSeekbar() {
seekbar.min = video.startTime;
seekbar.max = video.startTime + video.duration;
}
If the video is streaming you will need to "calculate" the "end" time.
var lastBuffered = video.buffered.end(video.buffered.length-1);
function updateUI() {
var lastBuffered = video.buffered.end(video.buffered.length-1);
seekbar.min = video.startTime;
seekbar.max = lastBuffered;
seekbar.value = video.currentTime;
}

How to know if a page is currently being read by the user with Javascript?

I'm making a webpage with dynamic content that enters the view with AJAX polling. The page JS occasionally downloads updated information and renders it on the page while the user is reading other information. This sort of thing is costly to bandwidth and processing time. I would like to have the polling pause when the page is not being viewed.
I've noticed most of the webpages I have open spend the majority of their time minimized or in a nonviewed tab. I'd like to be able to pause the scripts until the page is actually being viewed.
I have no idea how to do it, and it seems to be trying to break out of the sandbox of the html DOM and reach into the user's system. It may be impossible, if the JS engine has no knowledge of its rendering environment. I've never even seen a different site do this (not that the user is intended to see it...)
So it makes for an interesting question for discussion, I think. How would you write a web app that is CPU heavy to pause when not being used? Giving the user a pause button is not reliable, I'd like it to be automatic.
Your best solution would be something like this:
var inactiveTimer;
var active = true;
function setTimer(){
inactiveTimer = setTimeOut("stopAjaxUpdateFunction()", 120000); //120 seconds
}
setTimer();
document.onmouseover = function() { clearTimeout ( inactiveTimer );
setTimer();
resumeAjaxUpdate();
}; //clear the timer and reset it.
function stopAjaxUpdateFunction(){
//Turn off AJAX update
active = false;
}
function resumeAjaxUpdate(){
if(active == false){
//Turn on AJAX update
active = true;
}else{
//do nothing since we are still active and the AJAX update is still on.
}
}
The stopAjaxUpdateFunction should stop the AJAX update progress.
How about setting an "inactivity timeout" which gets reset every time a mouse or keyboard event is received in the DOM? I believe this is how most IM programs decide that you're "away" (though they do it by hooking the input messages at the system-wide level)
I've looked at that problem before for a research project. At the time (2-3 years ago) I did not find a way to get information from the browser about whether or not you are minimized :(
First check when the window loses and gains focus.
window.onblur = function () { /* stop */ };
window.onfocus = function () { /* start */ };
Also, for various reasons, the user may stop reading the page without causing it to lose focus (e.g. he gets up and walks away from the computer). In that case, you have to assume after a period of inactivity (no mouse or keyboard events) that the users' attention has left the page. The code to do that is described in another answer.
I know you've already accepted an answer but I'd personally use a combination of several of the answers mentioned here for various reasons, including:
Using mouse events only alienates users proficient at keyboard based browsing.
Using blur/focus events don't allow for users who go make a cup of tea ;-)
I'd most likely use something like the following as a guideline:
var idleTimer, userIsIdle, pollingTimer;
document.onkeydown = document.onmousemove = resetTimer;
window.onload = function () {
pollingTimer = window.setTimeout(runPollingFunction, 30000);
resetTimer();
/* IE's onblur/onfocus is buggy */
if (window.navigator.appName == "Microsoft Internet Explorer")
document.onfocusin = resetTimer,
document.onfocusout = setIdle;
else
window.onfocus = resetTimer,
window.onblur = setIdle;
}
function resetTimer() {
if (userIsIdle)
setBack();
window.clearTimeout(idleTimer);
idleTimer = window.setTimeout(setIdle, 120000); // 2 minutes of no activity
}
function setIdle() {
userIsIdle = true;
window.clearTimeout(pollingTimer); // Clear the timer that initiates polling
window.clearTimeout(setIdle);
}
function setBack() {
userIsIdle = false;
runPollingFunction(); // call the polling function to instantly update page
pollingTimer = window.setTimeout(runPollingFunction, 300000);
}
You can listen for mousemove and keypress events. If one of those has been fired in the past X seconds, then continue with your updating. Otherwise, don't update.
It's not perfect, but I think it's the best you can do with pure JS.
If you want to venture into the world of Flash, Silverlight, or Java, you may be able to get more information from the browser.

Categories

Resources