Forum Discussion
Idle timer in Storyline 360
I need help with an idle timer that uses JavaScript. The problem is that the idle popup is showing up when a user pauses a video in the course, even though it hasn't reached 300 seconds. Here is what I have (the actual course is in Docebo, if that makes a difference):
On the idle master:
The JavaScript on the master layer is this: if(window.maybeSuspendOnIdle){window.maybeSuspendOnIdle();}
The Idle Loop has a LoopTick that adds 1s like this:
The Idle Popup layer looks like this:
The first JavaScript is: sendResumed();
The second JavaScript is: // Resume Storyline timeline from popup layer
try { GetPlayer().ResumeState(); } catch(e){}
There is also a first slide that runs the following:
/* ===== Storyline Idle + Docebo xAPI + Media-Aware Starter (NO mousemove) ===== */
(function(){
if (window.__IdleAllInOne) { console.log("[IdleXAPI] already initialized"); return; }
window.__IdleAllInOne = true;
var IDLE_THRESHOLD_SECS = 300; // set to 10 for quick testing, then back to 300
var LOG_PREFIX = "[IdleXAPI]";
function getPlayer() { try { return GetPlayer(); } catch(e){ return null; } }
function log(){ try { console.log.apply(console, [LOG_PREFIX].concat([].slice.call(arguments))); } catch(e){} }
function setVar(name, val){ var p=getPlayer(); if (!p) return; try { p.SetVar(name, val); } catch(e){} }
function getVar(name){ var p=getPlayer(); if (!p) return null; try { return p.GetVar(name); } catch(e){ return null; } }
/* ------------------ xAPI helpers ------------------ */
(function(){
if (window.__IdleXAPI) return; window.__IdleXAPI = { sentSuspend:false, lastStatement:null };
function findTinCan(){
var ctx=window, hops=0;
while (ctx && hops<5){
if (ctx.tincan) return { type:"tincan", api:ctx.tincan, win:ctx };
if (ctx.TinCan) { if (ctx.tincan) return { type:"tincan", api:ctx.tincan, win:ctx }; return { type:"TinCan", api:ctx.TinCan, win:ctx }; }
if (ctx===ctx.parent) break; ctx = ctx.parent; hops++;
}
return null;
}
function getActor(tcWrap){
try{
if (tcWrap && tcWrap.api && typeof tcWrap.api.getContext === "function") {
var c = tcWrap.api.getContext(); if (c && c.actor) return c.actor;
}
if (tcWrap && tcWrap.api && tcWrap.api.actor) return tcWrap.api.actor;
}catch(e){}
return { name:"Learner", mbox:"mailto:noreply@example.com" };
}
function activityId(){
var v = getVar("CourseActivityId");
if (v && String(v).trim().length) return String(v).trim();
return location.href.split("#")[0];
}
function buildStatement(verbId, display){
var tc = findTinCan();
var stmt = {
actor: getActor(tc),
verb: { id: verbId, display: { "en-US": display } },
object: {
id: activityId(),
definition: { name: { "en-US": document.title || "Storyline Course" },
type: "http://adlnet.gov/expapi/activities/course" },
objectType: "Activity"
},
timestamp: new Date().toISOString()
};
return { tc: tc, stmt: stmt };
}
function sendWithTinCan(tcWrap, stmt, cb){
try{
if (!tcWrap) throw new Error("No TinCan context");
if (tcWrap.type === "tincan" && typeof tcWrap.api.sendStatement === "function") {
tcWrap.api.sendStatement(stmt, function(err, xhr){
if (err) log("sendStatement error:", err);
else log("sendStatement ok:", xhr && xhr.status);
cb && cb(!err);
});
return;
}
log("TinCan present but not configured; skipping send.");
cb && cb(false);
}catch(e){ log("sendWithTinCan exception:", e.message); cb && cb(false); }
}
function sendStatement(verbId, display, cb){
var pack = buildStatement(verbId, display);
window.__IdleXAPI.lastStatement = pack.stmt;
if (!pack.tc) { log("No tincan/TinCan context; skipping xAPI send."); cb && cb(false); return; }
sendWithTinCan(pack.tc, pack.stmt, cb);
}
window.maybeSuspendOnIdle = function(){
var isIdle = !!getVar("IsIdle");
if (isIdle && !window.__IdleXAPI.sentSuspend) {
sendStatement("http://adlnet.gov/expapi/verbs/suspended", "suspended", function(ok){
if (ok) window.__IdleXAPI.sentSuspend = true;
});
}
};
window.sendResumed = function(){
sendStatement("http://adlnet.gov/expapi/verbs/resumed", "resumed", function(ok){
if (ok) window.__IdleXAPI.sentSuspend = false;
});
};
})();
/* ------------------ Timeline-independent idle timer ------------------ */
(function(){
if (window.__IdleTickInterval) return;
function tick(){
var isIdle = !!getVar("IsIdle");
var idleTime = parseInt(getVar("IdleTime"), 10) || 0;
if (!isIdle) {
setVar("IdleTime", ++idleTime);
if (idleTime >= IDLE_THRESHOLD_SECS) setVar("IsIdle", true);
}
}
window.__IdleTickInterval = setInterval(tick, 1000);
log("JS idle interval running (independent of slide timeline).");
})();
/* ------------------ Global interaction listeners (NO mousemove) ------------------ */
(function(){
if (window.__IdleInputHooked_NoMouseMove) return;
window.__IdleInputHooked_NoMouseMove = true;
function resetIdle(){ setVar("IdleTime", 0); setVar("IsIdle", false); }
// Intentionally excluding 'mousemove'
["mousedown","keydown","touchstart","pointerdown","wheel","click"].forEach(function(evt){
window.addEventListener(evt, resetIdle, { passive:true });
});
log("Global input listeners attached (no mousemove).");
})();
/* ------------------ Media-aware resets for <video>/<audio> ------------------ */
(function(){
if (window.__IdleMediaHooked) return;
window.__IdleMediaHooked = true;
function resetIdle(){ setVar("IdleTime", 0); setVar("IsIdle", false); }
function hook(el){
if (!el || el.__idleHooked) return;
el.__idleHooked = true;
["play","playing","timeupdate","seeking","seeked","ratechange","volumechange"].forEach(function(evt){
el.addEventListener(evt, resetIdle, { passive:true });
});
}
function scan(){
try { Array.from(document.querySelectorAll("video,audio")).forEach(hook); } catch(e){}
}
scan();
var obs = new MutationObserver(scan);
obs.observe(document.documentElement, { childList:true, subtree:true });
log("Media-aware reset attached.");
})();
log("All-in-one Idle + xAPI injector initialized (mousemove ignored).");
})();
It took me forever to get this working, and I don't know where the error is. It was all working when I previewed, but now learners are reporting that it doesn’t always take the full five minutes before the Idle Popup shows up. Where am I going wrong?
Any help at all would be hugely appreciated!
Related Content
- 6 months ago
- 3 months ago
- 10 months ago
- 11 months ago
- 11 months ago