diff --git a/src/octoprint/static/js/app/helpers.js b/src/octoprint/static/js/app/helpers.js index fcea464ef6..fc288aa1f3 100644 --- a/src/octoprint/static/js/app/helpers.js +++ b/src/octoprint/static/js/app/helpers.js @@ -1542,41 +1542,74 @@ var copyToClipboard = function (text) { temp.remove(); }; -var determineWebcamStreamType = function (streamUrl) { - if (streamUrl) { - if (streamUrl.startsWith("webrtc")) { - return "webrtc"; - } +var getExternalHostUrl = function () { + var loc = window.location; + var port = ""; + if ( + (loc.protocol === "http:" && loc.port !== "80") || + (loc.protocol === "https:" && loc.port !== "443") + ) { + port = ":" + loc.port; + } + return loc.protocol + "//" + loc.hostname + port; +}; - var lastDotPosition = streamUrl.lastIndexOf("."); - var firstQuotationSignPosition = streamUrl.indexOf("?"); - if ( - lastDotPosition != -1 && - firstQuotationSignPosition != -1 && - lastDotPosition >= firstQuotationSignPosition - ) { - throw "Malformed URL. Cannot determine stream type."; - } +var validateWebcamUrl = function (streamUrl) { + if (!streamUrl) { + return false; + } - // If we have found a dot, try to extract the extension. - if (lastDotPosition > -1) { - if (firstQuotationSignPosition > -1) { - var extension = streamUrl.slice( - lastDotPosition + 1, - firstQuotationSignPosition - 1 - ); - } else { - var extension = streamUrl.slice(lastDotPosition + 1); - } - if (extension.toLowerCase() == "m3u8") { - return "hls"; - } - } - // By default, 'mjpg' is the stream type. - return "mjpg"; + var lower = streamUrl.toLowerCase(); + var toParse = streamUrl; + + if (lower.startsWith("//")) { + // protocol relative + toParse = window.location.protocol + streamUrl; + } else if (lower.startsWith("/")) { + // host relative + toParse = getExternalHostUrl() + streamUrl; + } else if ( + lower.startsWith("http:") || + lower.startsWith("https:") || + lower.startsWith("webrtc:") + ) { + // absolute & http/https/webrtc + toParse = streamUrl; } else { + return false; + } + + try { + return new URL(toParse); + } catch (e) { + return false; + } +}; + +var determineWebcamStreamType = function (streamUrl) { + if (!streamUrl) { throw "Empty streamUrl. Cannot determine stream type."; } + + var parsed = validateWebcamUrl(streamUrl); + if (!parsed) { + throw "Invalid streamUrl. Cannot determine stream type."; + } + + if (parsed.protocol === "webrtc:") { + return "webrtc"; + } + + var lastDotPosition = parsed.pathname.lastIndexOf("."); + if (lastDotPosition !== -1) { + var extension = parsed.pathname.substring(lastDotPosition + 1); + if (extension.toLowerCase() === "m3u8") { + return "hls"; + } + } + + // By default, 'mjpg' is the stream type. + return "mjpg"; }; var saveToLocalStorage = function (key, data) { diff --git a/src/octoprint/static/js/app/viewmodels/settings.js b/src/octoprint/static/js/app/viewmodels/settings.js index 838c567351..d3644b8d92 100644 --- a/src/octoprint/static/js/app/viewmodels/settings.js +++ b/src/octoprint/static/js/app/viewmodels/settings.js @@ -319,6 +319,10 @@ $(function () { return ""; } }); + self.webcam_streamValid = ko.pureComputed(function () { + var url = self.webcam_streamUrl(); + return !url || validateWebcamUrl(url); + }); self.server_onlineCheckText = ko.observable(); self.server_onlineCheckOk = ko.observable(false); @@ -450,12 +454,19 @@ $(function () { var text = gettext( "If you see your webcam stream below, the entered stream URL is ok." ); - var streamType = self.webcam_streamType(); + + var streamType; + try { + streamType = self.webcam_streamType(); + } catch (e) { + streamType = ""; + } + var webcam_element; var webrtc_peer_connection; - if (streamType == "mjpg") { + if (streamType === "mjpg") { webcam_element = $(''); - } else if (streamType == "hls") { + } else if (streamType === "hls") { webcam_element = $( '