Placeholder for webcam image to avoid moving controls on load
An aspect ratio of 16:9 is assumed, with other ratios showing black letterbox borders as required. During loading a text "Webcam loading..." is displayed in the webcam space. On loading error, the (broken) image is hidden and an error text is displayed instead. Solves #478
This commit is contained in:
parent
f32d7c434d
commit
c844217f82
4 changed files with 130 additions and 30 deletions
File diff suppressed because one or more lines are too long
|
|
@ -37,6 +37,8 @@ $(function() {
|
||||||
self.additionalControls = [];
|
self.additionalControls = [];
|
||||||
|
|
||||||
self.webcamDisableTimeout = undefined;
|
self.webcamDisableTimeout = undefined;
|
||||||
|
self.webcamLoaded = ko.observable(false);
|
||||||
|
self.webcamError = ko.observable(false);
|
||||||
|
|
||||||
self.keycontrolActive = ko.observable(false);
|
self.keycontrolActive = ko.observable(false);
|
||||||
self.keycontrolHelpActive = ko.observable(false);
|
self.keycontrolHelpActive = ko.observable(false);
|
||||||
|
|
@ -343,42 +345,24 @@ $(function() {
|
||||||
self.requestData();
|
self.requestData();
|
||||||
};
|
};
|
||||||
|
|
||||||
self.updateRotatorWidth = function() {
|
|
||||||
var webcamImage = $("#webcam_image");
|
|
||||||
if (self.settings.webcam_rotate90()) {
|
|
||||||
if (webcamImage.width() > 0) {
|
|
||||||
$("#webcam_rotator").css("height", webcamImage.width());
|
|
||||||
} else {
|
|
||||||
webcamImage.off("load.rotator");
|
|
||||||
webcamImage.on("load.rotator", function() {
|
|
||||||
$("#webcam_rotator").css("height", webcamImage.width());
|
|
||||||
webcamImage.off("load.rotator");
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
$("#webcam_rotator").css("height", "");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
self.onSettingsBeforeSave = self.updateRotatorWidth;
|
|
||||||
|
|
||||||
self._isSafari = function() {
|
self._isSafari = function() {
|
||||||
var is_chrome = navigator.userAgent.indexOf('Chrome') > -1;
|
var is_chrome = navigator.userAgent.indexOf('Chrome') > -1;
|
||||||
var is_safari = navigator.userAgent.indexOf("Safari") > -1;
|
var is_safari = navigator.userAgent.indexOf("Safari") > -1;
|
||||||
return is_safari && !is_chrome;
|
return is_safari && !is_chrome;
|
||||||
}
|
};
|
||||||
|
|
||||||
self._disableWebcam = function() {
|
self._disableWebcam = function() {
|
||||||
// only disable webcam stream if tab is out of focus for more than 5s, otherwise we might cause
|
// 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
|
// more load by the constant connection creation than by the actual webcam stream
|
||||||
|
|
||||||
// safari bug doesn't release the mjpeg stream, so we just disable this for safari.
|
// safari bug doesn't release the mjpeg stream, so we just disable this for safari.
|
||||||
if (self._isSafari()) {
|
if (self._isSafari()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
self.webcamDisableTimeout = setTimeout(function () {
|
self.webcamDisableTimeout = setTimeout(function () {
|
||||||
$("#webcam_image").attr("src", "");
|
$("#webcam_image").attr("src", "");
|
||||||
|
self.webcamLoaded(false);
|
||||||
}, 5000);
|
}, 5000);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -392,12 +376,12 @@ $(function() {
|
||||||
}
|
}
|
||||||
var webcamImage = $("#webcam_image");
|
var webcamImage = $("#webcam_image");
|
||||||
var currentSrc = webcamImage.attr("src");
|
var currentSrc = webcamImage.attr("src");
|
||||||
|
|
||||||
// safari bug doesn't release the mjpeg stream, so we just set it up the once
|
// safari bug doesn't release the mjpeg stream, so we just set it up the once
|
||||||
if (self._isSafari() && currentSrc != undefined) {
|
if (self._isSafari() && currentSrc != undefined) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var newSrc = self.settings.webcam_streamUrl();
|
var newSrc = self.settings.webcam_streamUrl();
|
||||||
if (currentSrc != newSrc) {
|
if (currentSrc != newSrc) {
|
||||||
if (newSrc.lastIndexOf("?") > -1) {
|
if (newSrc.lastIndexOf("?") > -1) {
|
||||||
|
|
@ -407,11 +391,24 @@ $(function() {
|
||||||
}
|
}
|
||||||
newSrc += new Date().getTime();
|
newSrc += new Date().getTime();
|
||||||
|
|
||||||
self.updateRotatorWidth();
|
self.webcamLoaded(false);
|
||||||
|
self.webcamError(false);
|
||||||
webcamImage.attr("src", newSrc);
|
webcamImage.attr("src", newSrc);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
self.onWebcamLoaded = function() {
|
||||||
|
log.debug("Webcam stream loaded");
|
||||||
|
self.webcamLoaded(true);
|
||||||
|
self.webcamError(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
self.onWebcamErrored = function() {
|
||||||
|
log.debug("Webcam stream failed to load/disabled");
|
||||||
|
self.webcamLoaded(false);
|
||||||
|
self.webcamError(true);
|
||||||
|
};
|
||||||
|
|
||||||
self.onTabChange = function (current, previous) {
|
self.onTabChange = function (current, previous) {
|
||||||
if (current == "#control") {
|
if (current == "#control") {
|
||||||
self._enableWebcam();
|
self._enableWebcam();
|
||||||
|
|
|
||||||
|
|
@ -491,7 +491,7 @@ ul.dropdown-menu li a {
|
||||||
position: relative;
|
position: relative;
|
||||||
outline: none;
|
outline: none;
|
||||||
|
|
||||||
//min-height: 440px;
|
background-color: black;
|
||||||
|
|
||||||
.keycontrol_overlay {
|
.keycontrol_overlay {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
|
@ -526,6 +526,84 @@ ul.dropdown-menu li a {
|
||||||
float: left;
|
float: left;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.nowebcam {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
|
||||||
|
.text {
|
||||||
|
color: white;
|
||||||
|
text-align: center;
|
||||||
|
position: relative;
|
||||||
|
margin: auto;
|
||||||
|
width: 80%;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
display: block;
|
||||||
|
|
||||||
|
&.webcam_loading {
|
||||||
|
animation: pulsate 3s ease-out;
|
||||||
|
animation-iteration-count: infinite;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.webcam_rotated {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
padding-bottom: 100%;
|
||||||
|
|
||||||
|
.webcam_fixed_ratio {
|
||||||
|
position: absolute;
|
||||||
|
transform: rotate(-90deg);
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
|
||||||
|
.webcam_fixed_ratio_inner {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.webcam_unrotated {
|
||||||
|
.webcam_fixed_ratio {
|
||||||
|
width: 100%;
|
||||||
|
padding-bottom: 100%;
|
||||||
|
|
||||||
|
&.ratio43 {
|
||||||
|
padding-bottom: 75%;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.ratio169 {
|
||||||
|
padding-bottom: 56.25%;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.ratio1610 {
|
||||||
|
padding-bottom: 62.5%;
|
||||||
|
}
|
||||||
|
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
.webcam_fixed_ratio_inner {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
img {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: contain;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** State sidebar panel */
|
/** State sidebar panel */
|
||||||
|
|
@ -757,7 +835,7 @@ ul.dropdown-menu li a {
|
||||||
#terminal-sendpanel {
|
#terminal-sendpanel {
|
||||||
text-align: right;
|
text-align: right;
|
||||||
}
|
}
|
||||||
|
|
||||||
#terminal-output span {
|
#terminal-output span {
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
@ -871,6 +949,18 @@ textarea.block {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@keyframes pulsate {
|
||||||
|
0% {
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
opacity: 1.0;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#drop_overlay {
|
#drop_overlay {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: 0;
|
top: 0;
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,20 @@
|
||||||
{% if webcamStream %}
|
{% if webcamStream %}
|
||||||
<div id="webcam_container" tabindex="0" data-bind="event: { keydown: onKeyDown, mouseover: onMouseOver, mouseout: onMouseOut, focus: onFocus }">
|
<div id="webcam_container" tabindex="0" data-bind="event: { keydown: onKeyDown, mouseover: onMouseOver, mouseout: onMouseOut, focus: onFocus }">
|
||||||
<div id="webcam_rotator" data-bind="css: { rotate90: settings.webcam_rotate90() }">
|
<div class="nowebcam" data-bind="visible: !webcamLoaded()">
|
||||||
<img id="webcam_image" data-bind="css: { flipH: settings.webcam_flipH(), flipV: settings.webcam_flipV() }">
|
<div class="text webcam_loading" data-bind="visible: !webcamLoaded() && !webcamError()">
|
||||||
|
<p><strong>{{ _('Webcam stream loading...') }}</strong></p>
|
||||||
|
</div>
|
||||||
|
<div class="text webcam_error" data-bind="visible: !webcamLoaded() && webcamError()">
|
||||||
|
<p><strong>{{ _('Webcam stream not loaded') }}</strong></p>
|
||||||
|
<p><small>{{ _('It might not be correctly configured. You can change the URL of the stream under "Settings" > "Webcam & Timelapse" > "Stream URL". If you don\'t have a webcam just set the URL to an empty value.') }}</small></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="webcam_rotator" data-bind="css: { webcam_rotated: settings.webcam_rotate90(), webcam_unrotated: !settings.webcam_rotate90() }">
|
||||||
|
<div class="webcam_fixed_ratio ratio169">
|
||||||
|
<div class="webcam_fixed_ratio_inner">
|
||||||
|
<img id="webcam_image" data-bind="css: { flipH: settings.webcam_flipH(), flipV: settings.webcam_flipV() }, event: { load: onWebcamLoaded, error: onWebcamErrored }, visible: !webcamError()">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="keycontrol_overlay" data-bind="visible: showKeycontrols">
|
<div class="keycontrol_overlay" data-bind="visible: showKeycontrols">
|
||||||
<div class="keycontrol_overlay_heading">{{ _("Keyboard controls active") }} <a href="#" data-bind="click: toggleKeycontrolHelp"><i data-bind="css: { 'icon-chevron-down': !keycontrolHelpActive(), 'icon-chevron-up': keycontrolHelpActive() }"></i></a></div>
|
<div class="keycontrol_overlay_heading">{{ _("Keyboard controls active") }} <a href="#" data-bind="click: toggleKeycontrolHelp"><i data-bind="css: { 'icon-chevron-down': !keycontrolHelpActive(), 'icon-chevron-up': keycontrolHelpActive() }"></i></a></div>
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue