Skip to content

Commit

Permalink
Track browser tab visibility, only activate webcam/gcode viewer when …
Browse files Browse the repository at this point in the history
…visible

This might help with #1065 if indeed is related to background tab suspending behaviours in
browsers, but is a completely blind guess at this point since I still have not been able to
reproduce that issue myself.

Backported.

(cherry picked from commit 720cdad)
  • Loading branch information
foosel committed Nov 20, 2015
1 parent 454f16a commit acc8512
Show file tree
Hide file tree
Showing 5 changed files with 165 additions and 31 deletions.
2 changes: 1 addition & 1 deletion src/octoprint/static/gcodeviewer/js/renderer.js
Expand Up @@ -379,7 +379,7 @@ GCODE.renderer = (function(){
};

var drawLayer = function(layerNum, fromProgress, toProgress, isNotCurrentLayer){
console.log("Drawing layer " + layerNum + " from " + fromProgress + " to " + toProgress + " (current: " + !isNotCurrentLayer + ")");
log.trace("Drawing layer " + layerNum + " from " + fromProgress + " to " + toProgress + " (current: " + !isNotCurrentLayer + ")");

var i;

Expand Down
105 changes: 101 additions & 4 deletions src/octoprint/static/js/app/main.js
Expand Up @@ -7,6 +7,72 @@ $(function() {

log.setLevel(CONFIG_DEBUG ? "debug" : "info");

//~~ setup browser and internal tab tracking (in 1.3.0 that will be
// much nicer with the global OctoPrint object...)

var tabTracking = (function() {
var exports = {
browserTabVisibility: undefined,
selectedTab: undefined
};

var browserVisibilityCallbacks = [];

var getHiddenProp = function() {
var prefixes = ["webkit", "moz", "ms", "o"];

// if "hidden" is natively supported just return it
if ("hidden" in document) {
return "hidden"
}

// otherwise loop over all the known prefixes until we find one
var vendorPrefix = _.find(prefixes, function(prefix) {
return (prefix + "Hidden" in document);
});
if (vendorPrefix !== undefined) {
return vendorPrefix + "Hidden";
}

// nothing found
return undefined;
};

var isHidden = function() {
var prop = getHiddenProp();
if (!prop) return false;

return document[prop];
};

var updateBrowserVisibility = function() {
var visible = !isHidden();
exports.browserTabVisible = visible;
_.each(browserVisibilityCallbacks, function(callback) {
callback(visible);
})
};

// register for browser visibility tracking

var prop = getHiddenProp();
if (!prop) return undefined;

var eventName = prop.replace(/[H|h]idden/, "") + "visibilitychange";
document.addEventListener(eventName, updateBrowserVisibility);

updateBrowserVisibility();

// exports

exports.isVisible = function() { return !isHidden() };
exports.onBrowserVisibilityChange = function(callback) {
browserVisibilityCallbacks.push(callback);
};

return exports;
})();

//~~ AJAX setup

// work around a stupid iOS6 bug where ajax requests get cached and only work once, as described at
Expand Down Expand Up @@ -67,6 +133,18 @@ $(function() {
// the view model map is our basic look up table for dependencies that may be injected into other view models
var viewModelMap = {};

// We put our tabTracking into the viewModelMap as a workaround until
// our global OctoPrint object becomes available in 1.3.0. This way
// we'll still be able to access it in our view models.
//
// NOTE TO DEVELOPERS: Do NOT depend on this dependency in your custom
// view models. It is ONLY provided for the core application to be able
// to backport a fix from the 1.3.0 development branch and WILL BE
// REMOVED once 1.3.0 gets released without any fallback!
//
// TODO: Remove with release of 1.3.0
viewModelMap.tabTracking = tabTracking;

// Fix Function#name on browsers that do not support it (IE):
// see: http://stackoverflow.com/questions/6903762/function-name-not-supported-in-ie
if (!(function f() {}).name) {
Expand Down Expand Up @@ -371,16 +449,22 @@ $(function() {
$('.nav-pills, .nav-tabs').tabdrop();

// Allow components to react to tab change
var tabs = $('#tabs a[data-toggle="tab"]');
tabs.on('show', function (e) {
var current = e.target.hash;
var previous = e.relatedTarget.hash;
var onTabChange = function(current, previous) {
log.debug("Selected OctoPrint tab changed: previous = " + previous + ", current = " + current);
tabTracking.selectedTab = current;

_.each(allViewModels, function(viewModel) {
if (viewModel.hasOwnProperty("onTabChange")) {
viewModel.onTabChange(current, previous);
}
});
};

var tabs = $('#tabs a[data-toggle="tab"]');
tabs.on('show', function (e) {
var current = e.target.hash;
var previous = e.relatedTarget.hash;
onTabChange(current, previous);
});

tabs.on('shown', function (e) {
Expand All @@ -394,6 +478,8 @@ $(function() {
});
});

onTabChange(OCTOPRINT_INITIAL_TAB);

// Fix input element click problems on dropdowns
$(".dropdown input, .dropdown label").click(function(e) {
e.stopPropagation();
Expand Down Expand Up @@ -485,11 +571,22 @@ $(function() {
});
log.info("... binding done");

// startup complete
_.each(allViewModels, function(viewModel) {
if (viewModel.hasOwnProperty("onStartupComplete")) {
viewModel.onStartupComplete();
}
});

// make sure we can track the browser tab visibility
tabTracking.onBrowserVisibilityChange(function(status) {
log.debug("Browser tab is now " + (status ? "visible" : "hidden"));
_.each(allViewModels, function(viewModel) {
if (viewModel.hasOwnProperty("onBrowserTabVisibilityChange")) {
viewModel.onBrowserTabVisibilityChange(status);
}
});
});
};

if (!_.has(viewModelMap, "settingsViewModel")) {
Expand Down
69 changes: 46 additions & 23 deletions src/octoprint/static/js/app/viewmodels/control.js
Expand Up @@ -5,6 +5,9 @@ $(function() {
self.loginState = parameters[0];
self.settings = parameters[1];

// TODO remove with release of 1.3.0 and switch to OctoPrint.coreui usage
self.tabTracking = parameters[2];

self._createToolEntry = function () {
return {
name: ko.observable(),
Expand Down Expand Up @@ -415,31 +418,51 @@ $(function() {

self.onSettingsBeforeSave = self.updateRotatorWidth;

self.onTabChange = function (current, previous) {
if (current == "#control") {
if (self.webcamDisableTimeout != undefined) {
clearTimeout(self.webcamDisableTimeout);
}
var webcamImage = $("#webcam_image");
var currentSrc = webcamImage.attr("src");
if (currentSrc === undefined || currentSrc.trim() == "") {
var newSrc = CONFIG_WEBCAM_STREAM;
if (CONFIG_WEBCAM_STREAM.lastIndexOf("?") > -1) {
newSrc += "&";
} else {
newSrc += "?";
}
newSrc += new Date().getTime();
self._disableWebcam = function() {
// only disable webcam stream if tab is out of focus for more than 5s, otherwise we might cause
// more load by the constant connection creation than by the actual webcam stream
self.webcamDisableTimeout = setTimeout(function () {
$("#webcam_image").attr("src", "");
}, 5000);
};

self._enableWebcam = function() {
if (self.tabTracking.selectedTab != "#control" || !self.tabTracking.browserTabVisible) {
return;
}

self.updateRotatorWidth();
webcamImage.attr("src", newSrc);
if (self.webcamDisableTimeout != undefined) {
clearTimeout(self.webcamDisableTimeout);
}
var webcamImage = $("#webcam_image");
var currentSrc = webcamImage.attr("src");
if (currentSrc === undefined || currentSrc.trim() == "") {
var newSrc = CONFIG_WEBCAM_STREAM;
if (CONFIG_WEBCAM_STREAM.lastIndexOf("?") > -1) {
newSrc += "&";
} else {
newSrc += "?";
}
newSrc += new Date().getTime();

self.updateRotatorWidth();
webcamImage.attr("src", newSrc);
}
};

self.onTabChange = function (current, previous) {
if (current == "#control") {
self._enableWebcam();
} else if (previous == "#control") {
// only disable webcam stream if tab is out of focus for more than 5s, otherwise we might cause
// more load by the constant connection creation than by the actual webcam stream
self.webcamDisableTimeout = setTimeout(function () {
$("#webcam_image").attr("src", "");
}, 5000);
self._disableWebcam();
}
};

self.onBrowserTabVisibilityChange = function(status) {
if (status) {
self._enableWebcam();
} else {
self._disableWebcam();
}
};

Expand Down Expand Up @@ -565,7 +588,7 @@ $(function() {

OCTOPRINT_VIEWMODELS.push([
ControlViewModel,
["loginStateViewModel", "settingsViewModel"],
["loginStateViewModel", "settingsViewModel", "tabTracking"],
"#control"
]);
});
12 changes: 9 additions & 3 deletions src/octoprint/static/js/app/viewmodels/gcode.js
Expand Up @@ -5,6 +5,9 @@ $(function() {
self.loginState = parameters[0];
self.settings = parameters[1];

// TODO remove with release of 1.3.0 and switch to OctoPrint.coreui usage
self.tabTracking = parameters[2];

self.ui_progress_percentage = ko.observable();
self.ui_progress_type = ko.observable();
self.ui_progress_text = ko.computed(function() {
Expand Down Expand Up @@ -358,7 +361,7 @@ $(function() {
if(self.loadedFilename
&& self.loadedFilename == data.job.file.name
&& self.loadedFileDate == data.job.file.date) {
if (self.currentlyPrinting && self.renderer_syncProgress() && !self.waitForApproval()) {
if (self.tabTracking.browserTabVisible && self.tabActive && self.currentlyPrinting && self.renderer_syncProgress() && !self.waitForApproval()) {
var cmdIndex = GCODE.gCodeReader.getCmdIndexForPercentage(data.progress.completion);
if(cmdIndex){
GCODE.renderer.render(cmdIndex.layer, 0, cmdIndex.cmd);
Expand Down Expand Up @@ -504,13 +507,16 @@ $(function() {

self.onBeforeBinding = function() {
self.initialize();
}
};

self.onTabChange = function(current, previous) {
self.tabActive = current == "#gcode";
};
}

OCTOPRINT_VIEWMODELS.push([
GcodeViewModel,
["loginStateViewModel", "settingsViewModel"],
["loginStateViewModel", "settingsViewModel", "tabTracking"],
"#gcode"
]);
});
8 changes: 8 additions & 0 deletions src/octoprint/templates/initscript.jinja2
Expand Up @@ -33,4 +33,12 @@
var OCTOPRINT_VIEWMODELS = [];
var ADDITIONAL_VIEWMODELS = [];
var OCTOPRINT_ADDITIONAL_BINDINGS = [];
{% if templates.tab.order %}
{% set first_tab = templates.tab.order[0] %}
{% set entry, data = templates.tab.entries[first_tab] %}
var OCTOPRINT_INITIAL_TAB = "#{{ data._div }}";
{% else %}
var OCTOPRINT_INITIAL_TAB = undefined;
{% endif %}
</script>

0 comments on commit acc8512

Please sign in to comment.