OctoPrint-BitBang 0.2.6__tar.gz → 0.2.7__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. {octoprint_bitbang-0.2.6 → octoprint_bitbang-0.2.7/OctoPrint_BitBang.egg-info}/PKG-INFO +1 -4
  2. {octoprint_bitbang-0.2.6 → octoprint_bitbang-0.2.7}/OctoPrint_BitBang.egg-info/requires.txt +0 -3
  3. {octoprint_bitbang-0.2.6/OctoPrint_BitBang.egg-info → octoprint_bitbang-0.2.7}/PKG-INFO +1 -4
  4. {octoprint_bitbang-0.2.6 → octoprint_bitbang-0.2.7}/octoprint_bitbang/__init__.py +8 -1
  5. {octoprint_bitbang-0.2.6 → octoprint_bitbang-0.2.7}/octoprint_bitbang/_plugin.py +5 -2
  6. {octoprint_bitbang-0.2.6 → octoprint_bitbang-0.2.7}/octoprint_bitbang/bin/bitbang-linux-amd64 +0 -0
  7. {octoprint_bitbang-0.2.6 → octoprint_bitbang-0.2.7}/octoprint_bitbang/bin/bitbang-linux-arm64 +0 -0
  8. {octoprint_bitbang-0.2.6 → octoprint_bitbang-0.2.7}/octoprint_bitbang/bin/bitbang-linux-armv7 +0 -0
  9. {octoprint_bitbang-0.2.6 → octoprint_bitbang-0.2.7}/octoprint_bitbang/static/js/bitbang.js +78 -0
  10. octoprint_bitbang-0.2.7/octoprint_bitbang/templates/bitbang_navbar.jinja2 +4 -0
  11. octoprint_bitbang-0.2.7/octoprint_bitbang/templates/bitbang_settings.jinja2 +93 -0
  12. {octoprint_bitbang-0.2.6 → octoprint_bitbang-0.2.7}/pyproject.toml +1 -4
  13. {octoprint_bitbang-0.2.6 → octoprint_bitbang-0.2.7}/setup.py +1 -1
  14. octoprint_bitbang-0.2.6/octoprint_bitbang/templates/bitbang_navbar.jinja2 +0 -29
  15. octoprint_bitbang-0.2.6/octoprint_bitbang/templates/bitbang_settings.jinja2 +0 -169
  16. {octoprint_bitbang-0.2.6 → octoprint_bitbang-0.2.7}/LICENSE +0 -0
  17. {octoprint_bitbang-0.2.6 → octoprint_bitbang-0.2.7}/MANIFEST.in +0 -0
  18. {octoprint_bitbang-0.2.6 → octoprint_bitbang-0.2.7}/OctoPrint_BitBang.egg-info/SOURCES.txt +0 -0
  19. {octoprint_bitbang-0.2.6 → octoprint_bitbang-0.2.7}/OctoPrint_BitBang.egg-info/dependency_links.txt +0 -0
  20. {octoprint_bitbang-0.2.6 → octoprint_bitbang-0.2.7}/OctoPrint_BitBang.egg-info/entry_points.txt +0 -0
  21. {octoprint_bitbang-0.2.6 → octoprint_bitbang-0.2.7}/OctoPrint_BitBang.egg-info/top_level.txt +0 -0
  22. {octoprint_bitbang-0.2.6 → octoprint_bitbang-0.2.7}/README.md +0 -0
  23. {octoprint_bitbang-0.2.6 → octoprint_bitbang-0.2.7}/octoprint_bitbang/__main__.py +0 -0
  24. {octoprint_bitbang-0.2.6 → octoprint_bitbang-0.2.7}/octoprint_bitbang/app.py +0 -0
  25. {octoprint_bitbang-0.2.6 → octoprint_bitbang-0.2.7}/octoprint_bitbang/camera.py +0 -0
  26. {octoprint_bitbang-0.2.6 → octoprint_bitbang-0.2.7}/octoprint_bitbang/flip_track.py +0 -0
  27. {octoprint_bitbang-0.2.6 → octoprint_bitbang-0.2.7}/octoprint_bitbang/index.html +0 -0
  28. {octoprint_bitbang-0.2.6 → octoprint_bitbang-0.2.7}/octoprint_bitbang/octoprint_adapter.py +0 -0
  29. {octoprint_bitbang-0.2.6 → octoprint_bitbang-0.2.7}/octoprint_bitbang/pi_camera_track.py +0 -0
  30. {octoprint_bitbang-0.2.6 → octoprint_bitbang-0.2.7}/octoprint_bitbang/pi_h264_source.py +0 -0
  31. {octoprint_bitbang-0.2.6 → octoprint_bitbang-0.2.7}/octoprint_bitbang/static/favicon.png +0 -0
  32. {octoprint_bitbang-0.2.6 → octoprint_bitbang-0.2.7}/octoprint_bitbang/templates/bitbang_webcam.jinja2 +0 -0
  33. {octoprint_bitbang-0.2.6 → octoprint_bitbang-0.2.7}/octoprint_bitbang/usb_camera_source.py +0 -0
  34. {octoprint_bitbang-0.2.6 → octoprint_bitbang-0.2.7}/octoprint_bitbang/v4l2_h264_source.py +0 -0
  35. {octoprint_bitbang-0.2.6 → octoprint_bitbang-0.2.7}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: OctoPrint-BitBang
3
- Version: 0.2.6
3
+ Version: 0.2.7
4
4
  Summary: Remote OctoPrint access with live H.264 video via BitBang WebRTC
5
5
  Author-email: Rich LeGrand <rich.m.legrand@gmail.com>
6
6
  License-Expression: MIT
@@ -24,9 +24,6 @@ Requires-Python: >=3.10
24
24
  Description-Content-Type: text/markdown
25
25
  License-File: LICENSE
26
26
  Requires-Dist: bitbang>=0.1.53
27
- Requires-Dist: aiohttp>=3.8.0
28
- Requires-Dist: Pillow>=9.0.0
29
- Requires-Dist: numpy>=1.20.0
30
27
  Requires-Dist: aiortc<1.11
31
28
  Requires-Dist: av<12; platform_machine == "armv7l"
32
29
  Dynamic: license-file
@@ -1,7 +1,4 @@
1
1
  bitbang>=0.1.53
2
- aiohttp>=3.8.0
3
- Pillow>=9.0.0
4
- numpy>=1.20.0
5
2
  aiortc<1.11
6
3
 
7
4
  [:platform_machine == "armv7l"]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: OctoPrint-BitBang
3
- Version: 0.2.6
3
+ Version: 0.2.7
4
4
  Summary: Remote OctoPrint access with live H.264 video via BitBang WebRTC
5
5
  Author-email: Rich LeGrand <rich.m.legrand@gmail.com>
6
6
  License-Expression: MIT
@@ -24,9 +24,6 @@ Requires-Python: >=3.10
24
24
  Description-Content-Type: text/markdown
25
25
  License-File: LICENSE
26
26
  Requires-Dist: bitbang>=0.1.53
27
- Requires-Dist: aiohttp>=3.8.0
28
- Requires-Dist: Pillow>=9.0.0
29
- Requires-Dist: numpy>=1.20.0
30
27
  Requires-Dist: aiortc<1.11
31
28
  Requires-Dist: av<12; platform_machine == "armv7l"
32
29
  Dynamic: license-file
@@ -4,8 +4,15 @@ Remote OctoPrint access with live H.264 video via BitBang WebRTC.
4
4
  No account, no subscription, no port forwarding. One shareable link.
5
5
  """
6
6
 
7
+ from importlib.metadata import PackageNotFoundError, version
8
+
7
9
  __plugin_name__ = "BitBang"
8
- __plugin_version__ = "0.2.6"
10
+ try:
11
+ # Single source of truth: read the version from the installed package
12
+ # metadata (declared once in pyproject.toml) rather than duplicating it here.
13
+ __plugin_version__ = version("OctoPrint-BitBang")
14
+ except PackageNotFoundError: # running from a source checkout, not pip-installed
15
+ __plugin_version__ = "0.0.0+unknown"
9
16
  __plugin_description__ = "Remote OctoPrint access with live H.264 video via BitBang WebRTC. No account, no port forwarding, one shareable link."
10
17
  __plugin_url__ = "https://github.com/richlegrand/OctoPrint-BitBang"
11
18
  __plugin_author__ = "Rich LeGrand"
@@ -201,6 +201,9 @@ class BitBangPlugin(
201
201
  self._settings.set(["url"], url)
202
202
  self._settings.save()
203
203
  self._logger.info(f"BitBang remote access: {url}")
204
+ # Push the URL to the frontend so the navbar reflects it live, instead
205
+ # of the browser polling the settings API.
206
+ self._plugin_manager.send_plugin_message(self._identifier, {"url": url})
204
207
 
205
208
  # Split-transport: a supervised Go proxy (data + HTTP/WS over native-SCTP
206
209
  # pion) owns the transport under our shared identity, and our camera
@@ -764,8 +767,8 @@ class BitBangPlugin(
764
767
 
765
768
  def get_template_configs(self):
766
769
  return [
767
- {"type": "settings", "custom_bindings": False},
768
- {"type": "navbar", "custom_bindings": False},
770
+ {"type": "settings", "custom_bindings": True},
771
+ {"type": "navbar", "custom_bindings": True},
769
772
  # Render the live view as a proper webcam provider template so
770
773
  # OctoPrint shows it only when "BitBang Camera" is the selected
771
774
  # webcam -- no DOM-replacing the classic webcam.
@@ -321,3 +321,81 @@
321
321
  }
322
322
  });
323
323
  })();
324
+
325
+ /*
326
+ * Settings + navbar viewmodel.
327
+ *
328
+ * The navbar link and the camera/resolution dropdowns used to read plugin
329
+ * state via raw fetch('/api/settings') + manual DOM. This binds them through
330
+ * the standard settingsViewModel and Knockout instead: settings come from the
331
+ * settings observable, camera lists from the plugin's blueprint endpoints, and
332
+ * the live remote URL arrives via a plugin message (no polling).
333
+ */
334
+ function BitBangViewModel(parameters) {
335
+ var self = this;
336
+ self.settings = parameters[0];
337
+
338
+ self.cameras = ko.observableArray([]);
339
+ self.resolutions = ko.observableArray([]);
340
+ self.remoteUrl = ko.observable("");
341
+
342
+ function bb() { return self.settings.settings.plugins.bitbang; }
343
+
344
+ self.loadResolutions = function (device) {
345
+ return OctoPrint.get(
346
+ "plugin/bitbang/resolutions?device=" + encodeURIComponent(device || "")
347
+ ).done(function (resolutions) {
348
+ self.resolutions(resolutions);
349
+ // Keep the saved resolution if it's still valid for this device,
350
+ // otherwise fall back to the first available.
351
+ if (resolutions.indexOf(bb().camera_resolution()) < 0) {
352
+ bb().camera_resolution(resolutions[0] || "");
353
+ }
354
+ });
355
+ };
356
+
357
+ self.loadCameras = function () {
358
+ return OctoPrint.get("plugin/bitbang/cameras").done(function (cameras) {
359
+ // Lead with an explicit auto-detect entry (value "") so the saved
360
+ // empty setting round-trips cleanly.
361
+ self.cameras([{ device: "", name: "Auto-detect" }].concat(cameras));
362
+ self.loadResolutions(bb().camera_device());
363
+ });
364
+ };
365
+
366
+ self.refreshCameras = function () { self.loadCameras(); };
367
+
368
+ self.showUrl = function () {
369
+ var url = self.remoteUrl();
370
+ if (url) {
371
+ window.prompt("BitBang URL (Ctrl+C to copy):", url);
372
+ } else {
373
+ alert("BitBang URL not available yet");
374
+ }
375
+ };
376
+
377
+ // Repopulate the dropdowns each time the settings dialog opens.
378
+ self.onSettingsShown = function () { self.loadCameras(); };
379
+
380
+ self.onStartupComplete = function () {
381
+ self.remoteUrl(bb().url() || "");
382
+ // Reload resolutions whenever the selected camera changes.
383
+ bb().camera_device.subscribe(function (device) {
384
+ self.loadResolutions(device);
385
+ });
386
+ };
387
+
388
+ // The plugin pushes the remote URL when BitBang connects, so the navbar
389
+ // reflects it live.
390
+ self.onDataUpdaterPluginMessage = function (plugin, data) {
391
+ if (plugin === "bitbang" && data && data.url !== undefined) {
392
+ self.remoteUrl(data.url);
393
+ }
394
+ };
395
+ }
396
+
397
+ OCTOPRINT_VIEWMODELS.push({
398
+ construct: BitBangViewModel,
399
+ dependencies: ["settingsViewModel"],
400
+ elements: ["#navbar_plugin_bitbang", "#settings_plugin_bitbang"]
401
+ });
@@ -0,0 +1,4 @@
1
+ <a href="javascript:void(0)" title="BitBang remote access"
2
+ data-bind="visible: settings.settings.plugins.bitbang.enabled, click: showUrl">
3
+ <i class="fas fa-link"></i> <span class="hidden-phone">BitBang</span>
4
+ </a>
@@ -0,0 +1,93 @@
1
+ <div id="settings_bitbang">
2
+ <h3>BitBang Remote Access</h3>
3
+
4
+ <form class="form-horizontal">
5
+ <div class="control-group">
6
+ <label class="control-label">Enabled</label>
7
+ <div class="controls">
8
+ <input type="checkbox" data-bind="checked: settings.settings.plugins.bitbang.enabled">
9
+ </div>
10
+ </div>
11
+
12
+ <div class="control-group">
13
+ <label class="control-label">PIN Protection</label>
14
+ <div class="controls">
15
+ <input type="text" class="input-medium"
16
+ data-bind="value: settings.settings.plugins.bitbang.pin"
17
+ placeholder="Empty to disable">
18
+ <span class="help-inline">Optional PIN to protect remote access</span>
19
+ </div>
20
+ </div>
21
+
22
+ <div class="control-group">
23
+ <label class="control-label">Remote URL</label>
24
+ <div class="controls">
25
+ <span data-bind="visible: settings.settings.plugins.bitbang.url()">
26
+ <a data-bind="attr: {href: settings.settings.plugins.bitbang.url()}, text: settings.settings.plugins.bitbang.url()"
27
+ target="_blank" rel="noopener"></a>
28
+ </span>
29
+ <span data-bind="visible: !settings.settings.plugins.bitbang.url()" class="muted">
30
+ URL will appear after BitBang connects
31
+ </span>
32
+ <span class="help-inline">Share this link for remote access</span>
33
+ </div>
34
+ </div>
35
+
36
+ <h3>Camera</h3>
37
+
38
+ <div class="control-group">
39
+ <label class="control-label">Camera</label>
40
+ <div class="controls">
41
+ <select class="input-xlarge"
42
+ data-bind="options: cameras,
43
+ optionsText: function(c) { return c.device ? c.name + ' (' + c.device + ')' : c.name; },
44
+ optionsValue: 'device',
45
+ value: settings.settings.plugins.bitbang.camera_device,
46
+ valueAllowUnset: true"></select>
47
+ <button class="btn btn-mini" type="button" data-bind="click: refreshCameras" title="Refresh camera list">
48
+ <i class="fas fa-sync"></i>
49
+ </button>
50
+ </div>
51
+ </div>
52
+
53
+ <div class="control-group">
54
+ <label class="control-label">Resolution</label>
55
+ <div class="controls">
56
+ <select class="input-medium"
57
+ data-bind="options: resolutions,
58
+ value: settings.settings.plugins.bitbang.camera_resolution,
59
+ valueAllowUnset: true"></select>
60
+ </div>
61
+ </div>
62
+
63
+ <div class="control-group">
64
+ <label class="control-label">Image orientation</label>
65
+ <div class="controls">
66
+ <label class="checkbox inline">
67
+ <input type="checkbox" data-bind="checked: settings.settings.plugins.bitbang.flip_horizontal">
68
+ Flip horizontal
69
+ </label>
70
+ <label class="checkbox inline">
71
+ <input type="checkbox" data-bind="checked: settings.settings.plugins.bitbang.flip_vertical">
72
+ Flip vertical
73
+ </label>
74
+ </div>
75
+ </div>
76
+
77
+ <h3>Advanced</h3>
78
+
79
+ <div class="control-group">
80
+ <label class="control-label">Signaling server</label>
81
+ <div class="controls">
82
+ <input type="text" class="input-xlarge"
83
+ data-bind="value: settings.settings.plugins.bitbang.signaling_server">
84
+ <span class="help-inline">Default <code>bitba.ng</code>. To use your own <a href="https://github.com/richlegrand/bitbang-server" target="_blank" rel="noopener">bitbang-server</a>, replace with the hostname (e.g. <code>signaling.example.com</code>).</span>
85
+ </div>
86
+ </div>
87
+ </form>
88
+
89
+ <div class="alert alert-info" style="margin-top: 16px;">
90
+ <i class="fas fa-info-circle"></i>
91
+ <strong>Restart OctoPrint</strong> after changing settings for them to take effect.
92
+ </div>
93
+ </div>
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "OctoPrint-BitBang"
7
- version = "0.2.6"
7
+ version = "0.2.7"
8
8
  description = "Remote OctoPrint access with live H.264 video via BitBang WebRTC"
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -27,9 +27,6 @@ classifiers = [
27
27
  ]
28
28
  dependencies = [
29
29
  "bitbang>=0.1.53",
30
- "aiohttp>=3.8.0",
31
- "Pillow>=9.0.0",
32
- "numpy>=1.20.0",
33
30
  # aiortc/av come transitively via bitbang. aiortc<1.11 already caps av<14.
34
31
  # 32-bit (armv7l) has NO PyAV wheels at any in-range version, so av builds
35
32
  # from source against the system FFmpeg (Bookworm 5.1); av<12 lands it on the
@@ -1,2 +1,2 @@
1
1
  from setuptools import setup
2
- setup()
2
+ setup(license="MIT")
@@ -1,29 +0,0 @@
1
- <a id="bitbang_navbar_link" href="javascript:void(0)" title="BitBang remote access" style="display:none" onclick="
2
- fetch('/api/settings')
3
- .then(function(r) { return r.json(); })
4
- .then(function(data) {
5
- var url = data.plugins && data.plugins.bitbang && data.plugins.bitbang.url;
6
- if (url) {
7
- prompt('BitBang URL (Ctrl+C to copy):', url);
8
- } else {
9
- alert('BitBang URL not available yet');
10
- }
11
- })
12
- .catch(function() { alert('Could not load settings'); });
13
- ">
14
- <i id="bitbang_navbar_icon" class="fas fa-link"></i> <span class="hidden-phone">BitBang</span>
15
- </a>
16
- <script>
17
- (function () {
18
- fetch('/api/settings')
19
- .then(function (r) { return r.json(); })
20
- .then(function (data) {
21
- var enabled = data.plugins && data.plugins.bitbang && data.plugins.bitbang.enabled;
22
- if (enabled) {
23
- var link = document.getElementById('bitbang_navbar_link');
24
- if (link) link.style.display = '';
25
- }
26
- })
27
- .catch(function () {});
28
- })();
29
- </script>
@@ -1,169 +0,0 @@
1
- <div id="settings_bitbang">
2
- <h3>BitBang Remote Access</h3>
3
-
4
- <form class="form-horizontal">
5
- <div class="control-group">
6
- <label class="control-label">Enabled</label>
7
- <div class="controls">
8
- <input type="checkbox" data-bind="checked: settings.plugins.bitbang.enabled">
9
- </div>
10
- </div>
11
-
12
- <div class="control-group">
13
- <label class="control-label">PIN Protection</label>
14
- <div class="controls">
15
- <input type="text" class="input-medium"
16
- data-bind="value: settings.plugins.bitbang.pin"
17
- placeholder="Empty to disable">
18
- <span class="help-inline">Optional PIN to protect remote access</span>
19
- </div>
20
- </div>
21
-
22
- <div class="control-group">
23
- <label class="control-label">Remote URL</label>
24
- <div class="controls">
25
- <span data-bind="visible: settings.plugins.bitbang.url()">
26
- <a data-bind="attr: {href: settings.plugins.bitbang.url()}, text: settings.plugins.bitbang.url()"
27
- target="_blank" rel="noopener"></a>
28
- </span>
29
- <span data-bind="visible: !settings.plugins.bitbang.url()" class="muted">
30
- URL will appear after BitBang connects
31
- </span>
32
- <span class="help-inline">Share this link for remote access</span>
33
- </div>
34
- </div>
35
-
36
- <h3>Camera</h3>
37
-
38
- <div class="control-group">
39
- <label class="control-label">Camera</label>
40
- <div class="controls">
41
- <select id="bitbang_camera_select" class="input-xlarge"
42
- data-bind="value: settings.plugins.bitbang.camera_device, valueAllowUnset: true">
43
- <option value="">Auto-detect</option>
44
- </select>
45
- <button class="btn btn-mini" id="bitbang_refresh_cameras" type="button">
46
- <i class="fas fa-sync"></i>
47
- </button>
48
- </div>
49
- </div>
50
-
51
- <div class="control-group">
52
- <label class="control-label">Resolution</label>
53
- <div class="controls">
54
- <select id="bitbang_resolution_select" class="input-medium"
55
- data-bind="value: settings.plugins.bitbang.camera_resolution, valueAllowUnset: true">
56
- <option value="640x480">640x480</option>
57
- </select>
58
- </div>
59
- </div>
60
-
61
- <div class="control-group">
62
- <label class="control-label">Image orientation</label>
63
- <div class="controls">
64
- <label class="checkbox inline">
65
- <input type="checkbox" data-bind="checked: settings.plugins.bitbang.flip_horizontal">
66
- Flip horizontal
67
- </label>
68
- <label class="checkbox inline">
69
- <input type="checkbox" data-bind="checked: settings.plugins.bitbang.flip_vertical">
70
- Flip vertical
71
- </label>
72
- </div>
73
- </div>
74
-
75
- <h3>Advanced</h3>
76
-
77
- <div class="control-group">
78
- <label class="control-label">Signaling server</label>
79
- <div class="controls">
80
- <input type="text" class="input-xlarge"
81
- data-bind="value: settings.plugins.bitbang.signaling_server">
82
- <span class="help-inline">Default <code>bitba.ng</code>. To use your own <a href="https://github.com/richlegrand/bitbang-server" target="_blank" rel="noopener">bitbang-server</a>, replace with the hostname (e.g. <code>signaling.example.com</code>).</span>
83
- </div>
84
- </div>
85
- </form>
86
-
87
- <div class="alert alert-info" style="margin-top: 16px;">
88
- <i class="fas fa-info-circle"></i>
89
- <strong>Restart OctoPrint</strong> after changing settings for them to take effect.
90
- </div>
91
- </div>
92
-
93
- <script>
94
- (function () {
95
- var cameraSelect = document.getElementById("bitbang_camera_select");
96
- var resolutionSelect = document.getElementById("bitbang_resolution_select");
97
- var refreshBtn = document.getElementById("bitbang_refresh_cameras");
98
- if (!cameraSelect || !resolutionSelect) return;
99
-
100
- // Set a <select> and notify Knockout's value binding — it doesn't see
101
- // options added via raw DOM, so without the dispatched 'change' the
102
- // observable (and thus the saved setting) won't follow the selection.
103
- // Suppress the camera change handler during programmatic sets so we don't
104
- // reload resolutions twice.
105
- function setSelect(sel, value) {
106
- var ok = value && Array.prototype.some.call(
107
- sel.options, function (o) { return o.value === value; });
108
- sel._suppress = true;
109
- if (ok) sel.value = value;
110
- sel.dispatchEvent(new Event("change"));
111
- sel._suppress = false;
112
- }
113
-
114
- function loadResolutions(device, wanted) {
115
- return fetch("/plugin/bitbang/resolutions?device=" + encodeURIComponent(device || ""))
116
- .then(function (r) { return r.json(); })
117
- .then(function (resolutions) {
118
- resolutionSelect.innerHTML = "";
119
- resolutions.forEach(function (res) {
120
- var opt = document.createElement("option");
121
- opt.value = res;
122
- opt.textContent = res;
123
- resolutionSelect.appendChild(opt);
124
- });
125
- setSelect(resolutionSelect,
126
- resolutions.indexOf(wanted) >= 0 ? wanted : resolutions[0]);
127
- })
128
- .catch(function () {});
129
- }
130
-
131
- function loadCameras(wantedCamera, wantedRes) {
132
- return fetch("/plugin/bitbang/cameras")
133
- .then(function (r) { return r.json(); })
134
- .then(function (cameras) {
135
- while (cameraSelect.options.length > 1) cameraSelect.remove(1);
136
- cameras.forEach(function (cam) {
137
- var opt = document.createElement("option");
138
- opt.value = cam.device;
139
- opt.textContent = cam.name + " (" + cam.device + ")";
140
- cameraSelect.appendChild(opt);
141
- });
142
- setSelect(cameraSelect, wantedCamera);
143
- return loadResolutions(cameraSelect.value, wantedRes);
144
- })
145
- .catch(function () {});
146
- }
147
-
148
- cameraSelect.addEventListener("change", function () {
149
- if (cameraSelect._suppress) return; // programmatic set — skip reload
150
- loadResolutions(cameraSelect.value, resolutionSelect.value);
151
- });
152
-
153
- if (refreshBtn) {
154
- refreshBtn.addEventListener("click", function () {
155
- loadCameras(cameraSelect.value, resolutionSelect.value);
156
- });
157
- }
158
-
159
- // Populate from the SAVED config — not the (Knockout-clobbered) DOM value —
160
- // so the dropdowns reflect what's persisted and a Save round-trips cleanly.
161
- fetch("/api/settings")
162
- .then(function (r) { return r.json(); })
163
- .then(function (s) {
164
- var bb = (s.plugins && s.plugins.bitbang) || {};
165
- loadCameras(bb.camera_device || "", bb.camera_resolution || "");
166
- })
167
- .catch(function () { loadCameras("", ""); });
168
- })();
169
- </script>