Attention Minutes
by zanes
HTML
<script src="http://cdnjs.cloudflare.com/ajax/libs/bacon.js/0.6.19/Bacon.min.js"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/lodash.js/2.2.1/lodash.compat.min.js"></script>
<script src="//netdna.bootstrapcdn.com/bootstrap/3.0.0/js/bootstrap.min.js"></script>
<link rel="stylesheet" href="http://netdna.bootstrapcdn.com/bootstrap/3.0.0/css/bootstrap.min.css">
<link rel="stylesheet" href="http:////netdna.bootstrapcdn.com/bootstrap/3.0.0/css/bootstrap-theme.min.css">
<script src="http://www.youtube.com/player_api"></script>
<div>
<span id="seconds"></span>
<span id="label"></span>
</div>
<br />
<iframe id="QPKKQnijnsM" type="text/html" width="128" height="78" src="http://www.youtube.com/embed/QPKKQnijnsM?enablejsapi=1" frameborder="0">
</iframe>
<iframe id="pCkL9UlmCOE" type="text/html" width="128" height="78" src="http://www.youtube.com/embed/pCkL9UlmCOE?enablejsapi=1" frameborder="0">
</iframe>
<iframe id="buRLc2eWGPQ" type="text/html" width="128" height="78" src="http://www.youtube.com/embed/buRLc2eWGPQ?enablejsapi=1" frameborder="0">
</iframe>
JavaScript
/* global $, _, Bacon, YT */
function merge(stream1, stream2) {
return stream1.merge(stream2);
}
function eventStream(eventName) {
return $(window).asEventStream(eventName);
}
// A map to player state codes from their human-readable sting equivalents.
// https://developers.google.com/youtube/js_api_reference
var PLAYER_CODES = {
beginning: -1,
completed: 0,
play: 1,
pause: 2,
buffer: 3,
video: 5
};
var DECAY = 5000; // milliseconds
var EVENT_NAMES = ["focus", "click", "scroll", "mousemove", "touchstart", "touchend", "touchcancel", "touchleave", "touchmove"];
var playerEventStream = new Bacon.Bus();
var isPlaying = playerEventStream
.scan({}, function(states, event) {
var update = {};
var id = event.target.getIframe().id;
update[id] = event.data;
return _.assign(states, update);
})
.map(_.values)
.map(_.partialRight(_.contains, PLAYER_CODES.play));
var playerEvents = function(id) {
var events = new Bacon.Bus();
var player = new YT.Player(id, {
events: {
'onStateChange': function(event) {
playerEventStream.push(event);
}
}
});
return events;
};
var isFocused = eventStream("focus").map(true)
.merge(eventStream("blur").map(false))
.toProperty(true);
var streams = _.map(EVENT_NAMES, eventStream);
var interactionStream = _.reduce(streams, merge);
var activityStream = interactionStream.merge(playerEventStream);
var recentlyActive = activityStream
.map(true)
.flatMapLatest(function() {
return Bacon.once(true).merge(Bacon.once(false).delay(DECAY));
})
.toProperty(false);
var isActive = (recentlyActive.and(isFocused)).or(isPlaying);
var secondsActive = Bacon.mergeAll(isActive.changes(), isActive.sample(1000))
.map(function(isActive) {
return {
isActive: isActive,
timestamp: new Date().getTime()
};
})
.slidingWindow(2,2)
.filter(function(span) { return span[0].isActive; })
.map(function(span) { return...