openvibers/openvibe.cam Commits
The camera works after an in-place move; openvibe-shared 4.102.0 (#57)
Changes
@@@ -10,11 +10,11 @@"domain": "openvibe.cam","contracts": "openvibe-contracts v0.237.0","sdk": "openvibe-sdk v0.81.0",-- "shared": "openvibe-shared v4.100.1",++ "shared": "openvibe-shared v4.102.0","packages": ["openvibe-contracts v0.237.0","openvibe-sdk v0.81.0",-- "openvibe-shared v4.100.1"++ "openvibe-shared v4.102.0"],"summary": "OpenVibe.Cam is the camera app, the private wall for cameras a person owns, the API Live's cam button calls, and F1's event timeline. The camera preview, timer, burst, grid and filter run in the browser; a photo is posted to Cam and forwarded to OpenVibe.Pics with the person's own Network token. A camera is private: its owner sees it, and a viewer sees it only while a grant naming them is unrevoked and unexpired, checked on every read, so a revocation is refused at once. Familiar faces proxy the owner's own token to OpenVibe.Vision's library; Cam keeps only the Vision library id and no template or crop. A detection becomes one event with a 10 s/20 s clip reference, a still, away-mode priority, a Network notification, a private link and a police export.","works": [@@@ -17,7 +17,7 @@"iovalkey": "^0.4.0","openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.237.0","openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.81.0",-- "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.100.1",++ "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.102.0","pg": "^8.23.0"},"devDependencies": {@@@ -1194,9 +1194,9 @@}},"node_modules/openvibe-shared": {-- "version": "4.100.1",-- "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.100.1",-- "integrity": "sha512-99cgDZNFPj/+EsDJRfjbS5exWMTFA55w2Ixu95apRw/07L7aiQTuE00ZkA6jCTJ6rZxz4U7xgbgZvc2KSM+vyA==",++ "version": "4.102.0",++ "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.102.0",++ "integrity": "sha512-9giJmk+LwIp6skYUejaBtkHfCRHTQlbGM3PIfGZVXo7rUdwLeFC89CLtQehEnPw2Q9inlffsgVblJjALq88EQg==","license": "MIT","dependencies": {"jsonwebtoken": "^9.0.0",@@@ -25,7 +25,7 @@"iovalkey": "^0.4.0","openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.237.0","openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.81.0",-- "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.100.1",++ "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.102.0","pg": "^8.23.0"},"engines": {@@@ -4,24 +4,6 @@(function () {'use strict';-- var root = document.getElementById('cm-camera');-- if (!root) return;-- var video = root.querySelector('[data-cm-preview]');-- var canvas = root.querySelector('[data-cm-canvas]');-- var statusEl = root.querySelector('[data-cm-status]');-- var startBtn = root.querySelector('[data-cm-start]');-- var shootBtn = root.querySelector('[data-cm-shoot]');-- var timerSel = root.querySelector('[data-cm-timer]');-- var burstBtn = root.querySelector('[data-cm-burst]');-- var filterSel = root.querySelector('[data-cm-filter]');-- var captionEl = root.querySelector('[data-cm-caption]');-- var gridBtn = root.querySelector('[data-cm-grid-toggle]');-- var gridEl = root.querySelector('[data-cm-grid]');-- var fileInput = root.querySelector('[data-cm-file]');-- var gallery = document.getElementById('cm-gallery');-- var stream = null;-- var busy = false;--var FILTERS = {none: 'none',mono: 'grayscale(1) contrast(1.08)',@@@ -32,119 +14,149 @@blur: 'blur(1.5px)'};-- function say(text) { if (statusEl) statusEl.textContent = text; }++ // The camera on the page now. Leaving the page, for good or for another one swapped in place (boost), turns it off.++ var stopCamera = null;++ function off() { if (stopCamera) { stopCamera(); stopCamera = null; } }++ window.addEventListener('pagehide', off);++ document.addEventListener('ov:boost:before', off);++++ // The camera app is wired when its page is shown: on load, and when boost swaps it in (a link from the home or the++ // navbar). It used to be wired only on load, so a visit from another page left a camera that did nothing.++ function wire() {++ var root = document.getElementById('cm-camera');++ if (!root || root.dataset.cmReady) return;++ root.dataset.cmReady = '1';++ var video = root.querySelector('[data-cm-preview]');++ var canvas = root.querySelector('[data-cm-canvas]');++ var statusEl = root.querySelector('[data-cm-status]');++ var startBtn = root.querySelector('[data-cm-start]');++ var shootBtn = root.querySelector('[data-cm-shoot]');++ var timerSel = root.querySelector('[data-cm-timer]');++ var burstBtn = root.querySelector('[data-cm-burst]');++ var filterSel = root.querySelector('[data-cm-filter]');++ var captionEl = root.querySelector('[data-cm-caption]');++ var gridBtn = root.querySelector('[data-cm-grid-toggle]');++ var gridEl = root.querySelector('[data-cm-grid]');++ var fileInput = root.querySelector('[data-cm-file]');++ var gallery = document.getElementById('cm-gallery');++ var stream = null;++ var busy = false;++-- function start() {-- if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {-- say('This browser has no camera input. Pick a picture below instead.');-- return;++ function say(text) { if (statusEl) statusEl.textContent = text; }++++ function start() {++ if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {++ say('This browser has no camera input. Pick a picture below instead.');++ return;++ }++ navigator.mediaDevices.getUserMedia({ video: { facingMode: 'user' }, audio: false }).then(function (s) {++ stream = s;++ video.srcObject = s;++ return video.play();++ }).then(function () {++ shootBtn.disabled = false;++ startBtn.textContent = 'Camera on';++ say('The camera is on. Nothing is recorded.');++ }).catch(function () {++ say('The camera was not allowed. Allow it in the browser, or pick a picture below.');++ });}-- navigator.mediaDevices.getUserMedia({ video: { facingMode: 'user' }, audio: false }).then(function (s) {-- stream = s;-- video.srcObject = s;-- return video.play();-- }).then(function () {-- shootBtn.disabled = false;-- startBtn.textContent = 'Camera on';-- say('The camera is on. Nothing is recorded.');-- }).catch(function () {-- say('The camera was not allowed. Allow it in the browser, or pick a picture below.');-- });-- }-- function drawFiltered() {-- var w = video.videoWidth || 1280;-- var h = video.videoHeight || 720;-- canvas.width = w;-- canvas.height = h;-- var ctx = canvas.getContext('2d');-- ctx.filter = FILTERS[filterSel.value] || 'none';-- ctx.drawImage(video, 0, 0, w, h);-- }++ function drawFiltered() {++ var w = video.videoWidth || 1280;++ var h = video.videoHeight || 720;++ canvas.width = w;++ canvas.height = h;++ var ctx = canvas.getContext('2d');++ ctx.filter = FILTERS[filterSel.value] || 'none';++ ctx.drawImage(video, 0, 0, w, h);++ }-- function addToGallery(p) {-- if (!gallery || !p || !p.pics_direct) return;-- var list = gallery.querySelector('.cm-photos');-- if (!list) {-- list = document.createElement('ul');-- list.className = 'cm-photos';-- gallery.appendChild(list);++ function addToGallery(p) {++ if (!gallery || !p || !p.pics_direct) return;++ var list = gallery.querySelector('.cm-photos');++ if (!list) {++ list = document.createElement('ul');++ list.className = 'cm-photos';++ gallery.appendChild(list);++ }++ var li = document.createElement('li');++ li.className = 'cm-photo';++ var a = document.createElement('a');++ a.className = 'cm-photo-link';++ a.href = p.pics_url;++ a.rel = 'noopener';++ var img = document.createElement('img');++ img.src = p.pics_direct;++ img.alt = p.caption || 'A photo on OpenVibe.Cam';++ img.loading = 'lazy';++ a.appendChild(img);++ li.appendChild(a);++ list.insertBefore(li, list.firstChild);}-- var li = document.createElement('li');-- li.className = 'cm-photo';-- var a = document.createElement('a');-- a.className = 'cm-photo-link';-- a.href = p.pics_url;-- a.rel = 'noopener';-- var img = document.createElement('img');-- img.src = p.pics_direct;-- img.alt = p.caption || 'A photo on OpenVibe.Cam';-- img.loading = 'lazy';-- a.appendChild(img);-- li.appendChild(a);-- list.insertBefore(li, list.firstChild);-- }-- function upload(blob, type, filterName) {-- if (busy) return Promise.resolve(null);-- busy = true;-- var headers = { 'Content-Type': type, 'X-Filter': filterName || 'none', 'X-Filename': encodeURIComponent('cam-' + Date.now() + '.jpg') };-- var caption = captionEl && captionEl.value ? captionEl.value : '';-- if (caption) headers['X-Caption'] = encodeURIComponent(caption);-- return fetch('/api/v1/photos', { method: 'POST', credentials: 'same-origin', headers: headers, body: blob })-- .then(function (res) {-- return res.json().catch(function () { return {}; }).then(function (body) {-- if (res.status === 401 || res.status === 403) { say('Sign in to save this photo to OpenVibe.Pics.'); return null; }-- if (!res.ok) { say((body && body.detail) || 'The photo was not saved.'); return null; }-- say('Saved to OpenVibe.Pics.');-- addToGallery(body);-- return body;-- });-- })-- .catch(function () { say('The photo was not saved: the network did not answer.'); return null; })-- .then(function (out) { busy = false; return out; });-- }++ function upload(blob, type, filterName) {++ if (busy) return Promise.resolve(null);++ busy = true;++ var headers = { 'Content-Type': type, 'X-Filter': filterName || 'none', 'X-Filename': encodeURIComponent('cam-' + Date.now() + '.jpg') };++ var caption = captionEl && captionEl.value ? captionEl.value : '';++ if (caption) headers['X-Caption'] = encodeURIComponent(caption);++ return fetch('/api/v1/photos', { method: 'POST', credentials: 'same-origin', headers: headers, body: blob })++ .then(function (res) {++ return res.json().catch(function () { return {}; }).then(function (body) {++ if (res.status === 401 || res.status === 403) { say('Sign in to save this photo to OpenVibe.Pics.'); return null; }++ if (!res.ok) { say((body && body.detail) || 'The photo was not saved.'); return null; }++ say('Saved to OpenVibe.Pics.');++ addToGallery(body);++ return body;++ });++ })++ .catch(function () { say('The photo was not saved: the network did not answer.'); return null; })++ .then(function (out) { busy = false; return out; });++ }-- function snap() {-- if (!stream) { say('Turn the camera on first.'); return; }-- drawFiltered();-- canvas.toBlob(function (blob) { if (blob) upload(blob, 'image/jpeg', filterSel.value); }, 'image/jpeg', 0.92);-- }++ function snap() {++ if (!stream) { say('Turn the camera on first.'); return; }++ drawFiltered();++ canvas.toBlob(function (blob) { if (blob) upload(blob, 'image/jpeg', filterSel.value); }, 'image/jpeg', 0.92);++ }-- function countdown(n) {-- if (n <= 0) { snap(); return; }-- say(n + '…');-- setTimeout(function () { countdown(n - 1); }, 1000);-- }++ function countdown(n) {++ if (n <= 0) { snap(); return; }++ say(n + '…');++ setTimeout(function () { countdown(n - 1); }, 1000);++ }-- function shoot() {-- var n = parseInt(timerSel.value, 10) || 0;-- if (n > 0) countdown(n); else snap();-- }++ function shoot() {++ var n = parseInt(timerSel.value, 10) || 0;++ if (n > 0) countdown(n); else snap();++ }-- function burst() {-- if (!stream) { say('Turn the camera on first.'); return; }-- say('Burst of three…');-- for (var i = 0; i < 3; i++) setTimeout(snap, i * 450);-- }++ function burst() {++ if (!stream) { say('Turn the camera on first.'); return; }++ say('Burst of three…');++ for (var i = 0; i < 3; i++) setTimeout(snap, i * 450);++ }-- startBtn.addEventListener('click', start);-- shootBtn.addEventListener('click', shoot);-- burstBtn.addEventListener('click', burst);-- if (gridBtn) gridBtn.addEventListener('click', function () {-- if (!gridEl) return;-- var show = gridEl.hidden;-- gridEl.hidden = !show;-- gridBtn.setAttribute('aria-pressed', show ? 'true' : 'false');-- });-- if (fileInput) fileInput.addEventListener('change', function () {-- var f = fileInput.files && fileInput.files[0];-- if (!f) return;-- if (!/^image\//.test(f.type)) { say('That is not a picture.'); return; }-- upload(f, f.type, 'none');-- fileInput.value = '';-- });-- window.addEventListener('pagehide', function () {-- if (stream) stream.getTracks().forEach(function (t) { t.stop(); });-- });++ startBtn.addEventListener('click', start);++ shootBtn.addEventListener('click', shoot);++ burstBtn.addEventListener('click', burst);++ if (gridBtn) gridBtn.addEventListener('click', function () {++ if (!gridEl) return;++ var show = gridEl.hidden;++ gridEl.hidden = !show;++ gridBtn.setAttribute('aria-pressed', show ? 'true' : 'false');++ });++ if (fileInput) fileInput.addEventListener('change', function () {++ var f = fileInput.files && fileInput.files[0];++ if (!f) return;++ if (!/^image\//.test(f.type)) { say('That is not a picture.'); return; }++ upload(f, f.type, 'none');++ fileInput.value = '';++ });++ stopCamera = function () { if (stream) stream.getTracks().forEach(function (t) { t.stop(); }); stream = null; };++ }++ if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', wire); else wire();++ document.addEventListener('ov:boost:load', wire);})();
@@ -10,11 +10,11 @@
@@ -10,11 +10,11 @@
"domain": "openvibe.cam",
"domain": "openvibe.cam",
"contracts": "openvibe-contracts v0.237.0",
"contracts": "openvibe-contracts v0.237.0",
"sdk": "openvibe-sdk v0.81.0",
"sdk": "openvibe-sdk v0.81.0",
- "shared": "openvibe-shared v4.100.1",
+ "shared": "openvibe-shared v4.102.0",
"packages": [
"packages": [
"openvibe-contracts v0.237.0",
"openvibe-contracts v0.237.0",
"openvibe-sdk v0.81.0",
"openvibe-sdk v0.81.0",
- "openvibe-shared v4.100.1"
+ "openvibe-shared v4.102.0"
],
],
"summary": "OpenVibe.Cam is the camera app, the private wall for cameras a person owns, the API Live's cam button calls, and F1's event timeline. The camera preview, timer, burst, grid and filter run in the browser; a photo is posted to Cam and forwarded to OpenVibe.Pics with the person's own Network token. A camera is private: its owner sees it, and a viewer sees it only while a grant naming them is unrevoked and unexpired, checked on every read, so a revocation is refused at once. Familiar faces proxy the owner's own token to OpenVibe.Vision's library; Cam keeps only the Vision library id and no template or crop. A detection becomes one event with a 10 s/20 s clip reference, a still, away-mode priority, a Network notification, a private link and a police export.",
"summary": "OpenVibe.Cam is the camera app, the private wall for cameras a person owns, the API Live's cam button calls, and F1's event timeline. The camera preview, timer, burst, grid and filter run in the browser; a photo is posted to Cam and forwarded to OpenVibe.Pics with the person's own Network token. A camera is private: its owner sees it, and a viewer sees it only while a grant naming them is unrevoked and unexpired, checked on every read, so a revocation is refused at once. Familiar faces proxy the owner's own token to OpenVibe.Vision's library; Cam keeps only the Vision library id and no template or crop. A detection becomes one event with a 10 s/20 s clip reference, a still, away-mode priority, a Network notification, a private link and a police export.",
"works": [
"works": [
@@ -17,7 +17,7 @@
@@ -17,7 +17,7 @@
"iovalkey": "^0.4.0",
"iovalkey": "^0.4.0",
"openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.237.0",
"openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.237.0",
"openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.81.0",
"openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.81.0",
- "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.100.1",
+ "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.102.0",
"pg": "^8.23.0"
"pg": "^8.23.0"
},
},
"devDependencies": {
"devDependencies": {
@@ -1194,9 +1194,9 @@
@@ -1194,9 +1194,9 @@
}
}
},
},
"node_modules/openvibe-shared": {
"node_modules/openvibe-shared": {
- "version": "4.100.1",
- "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.100.1",
- "integrity": "sha512-99cgDZNFPj/+EsDJRfjbS5exWMTFA55w2Ixu95apRw/07L7aiQTuE00ZkA6jCTJ6rZxz4U7xgbgZvc2KSM+vyA==",
+ "version": "4.102.0",
+ "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.102.0",
+ "integrity": "sha512-9giJmk+LwIp6skYUejaBtkHfCRHTQlbGM3PIfGZVXo7rUdwLeFC89CLtQehEnPw2Q9inlffsgVblJjALq88EQg==",
"license": "MIT",
"license": "MIT",
"dependencies": {
"dependencies": {
"jsonwebtoken": "^9.0.0",
"jsonwebtoken": "^9.0.0",
@@ -25,7 +25,7 @@
@@ -25,7 +25,7 @@
"iovalkey": "^0.4.0",
"iovalkey": "^0.4.0",
"openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.237.0",
"openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.237.0",
"openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.81.0",
"openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.81.0",
- "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.100.1",
+ "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.102.0",
"pg": "^8.23.0"
"pg": "^8.23.0"
},
},
"engines": {
"engines": {
@@ -4,24 +4,6 @@
@@ -4,24 +4,6 @@
(function () {
(function () {
'use strict';
'use strict';
- var root = document.getElementById('cm-camera');
- if (!root) return;
- var video = root.querySelector('[data-cm-preview]');
- var canvas = root.querySelector('[data-cm-canvas]');
- var statusEl = root.querySelector('[data-cm-status]');
- var startBtn = root.querySelector('[data-cm-start]');
- var shootBtn = root.querySelector('[data-cm-shoot]');
- var timerSel = root.querySelector('[data-cm-timer]');
- var burstBtn = root.querySelector('[data-cm-burst]');
- var filterSel = root.querySelector('[data-cm-filter]');
- var captionEl = root.querySelector('[data-cm-caption]');
- var gridBtn = root.querySelector('[data-cm-grid-toggle]');
- var gridEl = root.querySelector('[data-cm-grid]');
- var fileInput = root.querySelector('[data-cm-file]');
- var gallery = document.getElementById('cm-gallery');
- var stream = null;
- var busy = false;
-
var FILTERS = {
var FILTERS = {
none: 'none',
none: 'none',
mono: 'grayscale(1) contrast(1.08)',
mono: 'grayscale(1) contrast(1.08)',
@@ -32,119 +14,149 @@
@@ -32,119 +14,149 @@
blur: 'blur(1.5px)'
blur: 'blur(1.5px)'
};
};
- function say(text) { if (statusEl) statusEl.textContent = text; }
+ // The camera on the page now. Leaving the page, for good or for another one swapped in place (boost), turns it off.
+ var stopCamera = null;
+ function off() { if (stopCamera) { stopCamera(); stopCamera = null; } }
+ window.addEventListener('pagehide', off);
+ document.addEventListener('ov:boost:before', off);
+
+ // The camera app is wired when its page is shown: on load, and when boost swaps it in (a link from the home or the
+ // navbar). It used to be wired only on load, so a visit from another page left a camera that did nothing.
+ function wire() {
+ var root = document.getElementById('cm-camera');
+ if (!root || root.dataset.cmReady) return;
+ root.dataset.cmReady = '1';
+ var video = root.querySelector('[data-cm-preview]');
+ var canvas = root.querySelector('[data-cm-canvas]');
+ var statusEl = root.querySelector('[data-cm-status]');
+ var startBtn = root.querySelector('[data-cm-start]');
+ var shootBtn = root.querySelector('[data-cm-shoot]');
+ var timerSel = root.querySelector('[data-cm-timer]');
+ var burstBtn = root.querySelector('[data-cm-burst]');
+ var filterSel = root.querySelector('[data-cm-filter]');
+ var captionEl = root.querySelector('[data-cm-caption]');
+ var gridBtn = root.querySelector('[data-cm-grid-toggle]');
+ var gridEl = root.querySelector('[data-cm-grid]');
+ var fileInput = root.querySelector('[data-cm-file]');
+ var gallery = document.getElementById('cm-gallery');
+ var stream = null;
+ var busy = false;
+
- function start() {
- if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {
- say('This browser has no camera input. Pick a picture below instead.');
- return;
+ function say(text) { if (statusEl) statusEl.textContent = text; }
+
+ function start() {
+ if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {
+ say('This browser has no camera input. Pick a picture below instead.');
+ return;
+ }
+ navigator.mediaDevices.getUserMedia({ video: { facingMode: 'user' }, audio: false }).then(function (s) {
+ stream = s;
+ video.srcObject = s;
+ return video.play();
+ }).then(function () {
+ shootBtn.disabled = false;
+ startBtn.textContent = 'Camera on';
+ say('The camera is on. Nothing is recorded.');
+ }).catch(function () {
+ say('The camera was not allowed. Allow it in the browser, or pick a picture below.');
+ });
}
}
- navigator.mediaDevices.getUserMedia({ video: { facingMode: 'user' }, audio: false }).then(function (s) {
- stream = s;
- video.srcObject = s;
- return video.play();
- }).then(function () {
- shootBtn.disabled = false;
- startBtn.textContent = 'Camera on';
- say('The camera is on. Nothing is recorded.');
- }).catch(function () {
- say('The camera was not allowed. Allow it in the browser, or pick a picture below.');
- });
- }
- function drawFiltered() {
- var w = video.videoWidth || 1280;
- var h = video.videoHeight || 720;
- canvas.width = w;
- canvas.height = h;
- var ctx = canvas.getContext('2d');
- ctx.filter = FILTERS[filterSel.value] || 'none';
- ctx.drawImage(video, 0, 0, w, h);
- }
+ function drawFiltered() {
+ var w = video.videoWidth || 1280;
+ var h = video.videoHeight || 720;
+ canvas.width = w;
+ canvas.height = h;
+ var ctx = canvas.getContext('2d');
+ ctx.filter = FILTERS[filterSel.value] || 'none';
+ ctx.drawImage(video, 0, 0, w, h);
+ }
- function addToGallery(p) {
- if (!gallery || !p || !p.pics_direct) return;
- var list = gallery.querySelector('.cm-photos');
- if (!list) {
- list = document.createElement('ul');
- list.className = 'cm-photos';
- gallery.appendChild(list);
+ function addToGallery(p) {
+ if (!gallery || !p || !p.pics_direct) return;
+ var list = gallery.querySelector('.cm-photos');
+ if (!list) {
+ list = document.createElement('ul');
+ list.className = 'cm-photos';
+ gallery.appendChild(list);
+ }
+ var li = document.createElement('li');
+ li.className = 'cm-photo';
+ var a = document.createElement('a');
+ a.className = 'cm-photo-link';
+ a.href = p.pics_url;
+ a.rel = 'noopener';
+ var img = document.createElement('img');
+ img.src = p.pics_direct;
+ img.alt = p.caption || 'A photo on OpenVibe.Cam';
+ img.loading = 'lazy';
+ a.appendChild(img);
+ li.appendChild(a);
+ list.insertBefore(li, list.firstChild);
}
}
- var li = document.createElement('li');
- li.className = 'cm-photo';
- var a = document.createElement('a');
- a.className = 'cm-photo-link';
- a.href = p.pics_url;
- a.rel = 'noopener';
- var img = document.createElement('img');
- img.src = p.pics_direct;
- img.alt = p.caption || 'A photo on OpenVibe.Cam';
- img.loading = 'lazy';
- a.appendChild(img);
- li.appendChild(a);
- list.insertBefore(li, list.firstChild);
- }
- function upload(blob, type, filterName) {
- if (busy) return Promise.resolve(null);
- busy = true;
- var headers = { 'Content-Type': type, 'X-Filter': filterName || 'none', 'X-Filename': encodeURIComponent('cam-' + Date.now() + '.jpg') };
- var caption = captionEl && captionEl.value ? captionEl.value : '';
- if (caption) headers['X-Caption'] = encodeURIComponent(caption);
- return fetch('/api/v1/photos', { method: 'POST', credentials: 'same-origin', headers: headers, body: blob })
- .then(function (res) {
- return res.json().catch(function () { return {}; }).then(function (body) {
- if (res.status === 401 || res.status === 403) { say('Sign in to save this photo to OpenVibe.Pics.'); return null; }
- if (!res.ok) { say((body && body.detail) || 'The photo was not saved.'); return null; }
- say('Saved to OpenVibe.Pics.');
- addToGallery(body);
- return body;
- });
- })
- .catch(function () { say('The photo was not saved: the network did not answer.'); return null; })
- .then(function (out) { busy = false; return out; });
- }
+ function upload(blob, type, filterName) {
+ if (busy) return Promise.resolve(null);
+ busy = true;
+ var headers = { 'Content-Type': type, 'X-Filter': filterName || 'none', 'X-Filename': encodeURIComponent('cam-' + Date.now() + '.jpg') };
+ var caption = captionEl && captionEl.value ? captionEl.value : '';
+ if (caption) headers['X-Caption'] = encodeURIComponent(caption);
+ return fetch('/api/v1/photos', { method: 'POST', credentials: 'same-origin', headers: headers, body: blob })
+ .then(function (res) {
+ return res.json().catch(function () { return {}; }).then(function (body) {
+ if (res.status === 401 || res.status === 403) { say('Sign in to save this photo to OpenVibe.Pics.'); return null; }
+ if (!res.ok) { say((body && body.detail) || 'The photo was not saved.'); return null; }
+ say('Saved to OpenVibe.Pics.');
+ addToGallery(body);
+ return body;
+ });
+ })
+ .catch(function () { say('The photo was not saved: the network did not answer.'); return null; })
+ .then(function (out) { busy = false; return out; });
+ }
- function snap() {
- if (!stream) { say('Turn the camera on first.'); return; }
- drawFiltered();
- canvas.toBlob(function (blob) { if (blob) upload(blob, 'image/jpeg', filterSel.value); }, 'image/jpeg', 0.92);
- }
+ function snap() {
+ if (!stream) { say('Turn the camera on first.'); return; }
+ drawFiltered();
+ canvas.toBlob(function (blob) { if (blob) upload(blob, 'image/jpeg', filterSel.value); }, 'image/jpeg', 0.92);
+ }
- function countdown(n) {
- if (n <= 0) { snap(); return; }
- say(n + '…');
- setTimeout(function () { countdown(n - 1); }, 1000);
- }
+ function countdown(n) {
+ if (n <= 0) { snap(); return; }
+ say(n + '…');
+ setTimeout(function () { countdown(n - 1); }, 1000);
+ }
- function shoot() {
- var n = parseInt(timerSel.value, 10) || 0;
- if (n > 0) countdown(n); else snap();
- }
+ function shoot() {
+ var n = parseInt(timerSel.value, 10) || 0;
+ if (n > 0) countdown(n); else snap();
+ }
- function burst() {
- if (!stream) { say('Turn the camera on first.'); return; }
- say('Burst of three…');
- for (var i = 0; i < 3; i++) setTimeout(snap, i * 450);
- }
+ function burst() {
+ if (!stream) { say('Turn the camera on first.'); return; }
+ say('Burst of three…');
+ for (var i = 0; i < 3; i++) setTimeout(snap, i * 450);
+ }
- startBtn.addEventListener('click', start);
- shootBtn.addEventListener('click', shoot);
- burstBtn.addEventListener('click', burst);
- if (gridBtn) gridBtn.addEventListener('click', function () {
- if (!gridEl) return;
- var show = gridEl.hidden;
- gridEl.hidden = !show;
- gridBtn.setAttribute('aria-pressed', show ? 'true' : 'false');
- });
- if (fileInput) fileInput.addEventListener('change', function () {
- var f = fileInput.files && fileInput.files[0];
- if (!f) return;
- if (!/^image\//.test(f.type)) { say('That is not a picture.'); return; }
- upload(f, f.type, 'none');
- fileInput.value = '';
- });
- window.addEventListener('pagehide', function () {
- if (stream) stream.getTracks().forEach(function (t) { t.stop(); });
- });
+ startBtn.addEventListener('click', start);
+ shootBtn.addEventListener('click', shoot);
+ burstBtn.addEventListener('click', burst);
+ if (gridBtn) gridBtn.addEventListener('click', function () {
+ if (!gridEl) return;
+ var show = gridEl.hidden;
+ gridEl.hidden = !show;
+ gridBtn.setAttribute('aria-pressed', show ? 'true' : 'false');
+ });
+ if (fileInput) fileInput.addEventListener('change', function () {
+ var f = fileInput.files && fileInput.files[0];
+ if (!f) return;
+ if (!/^image\//.test(f.type)) { say('That is not a picture.'); return; }
+ upload(f, f.type, 'none');
+ fileInput.value = '';
+ });
+ stopCamera = function () { if (stream) stream.getTracks().forEach(function (t) { t.stop(); }); stream = null; };
+ }
+ if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', wire); else wire();
+ document.addEventListener('ov:boost:load', wire);
})();
})();