Track browser tab visibility, only activate webcam/gcode viewer when 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)
This commit is contained in:
Gina Häußge 2015-11-18 17:13:41 +01:00
parent 454f16a7c3
commit acc85127c5
5 changed files with 165 additions and 31 deletions

View file

@ -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;

View file

@ -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
@ -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) {
@ -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) {
@ -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();
@ -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")) {

View file

@ -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(),
@ -415,31 +418,51 @@ $(function() {
self.onSettingsBeforeSave = self.updateRotatorWidth;
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;
}
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") {
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._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();
}
};
@ -565,7 +588,7 @@ $(function() {
OCTOPRINT_VIEWMODELS.push([
ControlViewModel,
["loginStateViewModel", "settingsViewModel"],
["loginStateViewModel", "settingsViewModel", "tabTracking"],
"#control"
]);
});

View file

@ -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() {
@ -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);
@ -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"
]);
});

View file

@ -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>