/*
* Sample for v4l2-ruby * * Copyright (C) 2019 Hiroshi Kuwagata <kgt9221@gmail.com> */
(function () {
/* * define constants */ /* * declar package global variabled */ var session; var capabilities; var controls; var sliders; var imageWidth; var imageHeight; var framerate; var previewCanvas; var previewGc; var cameraState; /* * declar functions */ function setDeviceFile(name) { $('h3#device-file').text(name); } function setState(state) { var fg; var bg; var lb; cameraState = state; switch (state) { case "STOP": default: fg = "royalblue"; bg = "rgba(160, 160, 160, 0.5)"; lb = "START"; clearPreviewCanvas(); break; case "ALIVE": fg = "springgreen"; bg = "rgba(0, 0, 0, 0.5)"; lb = "STOP"; break; case "ABORT": fg = "crimson"; bg = "rgba(160, 160, 160, 0.5)"; lb = "RECOVER"; clearPreviewCanvas(); break; } $('button#action').text(lb); $('div#state') .css('color', fg) .css('-webkit-text-stroke', `0.5px ${bg}`) .text(state); } function setupScreenSize() { var height; height = $('body').height() - $('div.jumbotron').outerHeight(true); $('div#main-area').height(height); setTimeout(() => { $('div#preview').getNiceScroll().resize(); $('div#config').getNiceScroll().resize(); }, 0); } function setIdentString(str) { $('h6#device-name').text(str); } function sortCapabilities() { capabilities.sort((a, b) => { return ((a.width * a.height) - (b.width * b.height)); }); capabilities.forEach((info) => { info["rate"].sort((a, b) => {return (a[0] / a[1]) - (b[0] / b[1])}); }); } function setImageSizeSelect() { /* * 画像サイズ */ $('select#image-size').empty(); capabilities.forEach((obj) => { $('select#image-size') .append($('<option>') .attr('value', `${obj.width},${obj.height}`) .text(`${obj.width} \u00d7 ${obj.height}`) ); }); $('select#image-size') .val(`${imageWidth},${imageHeight}`) .on('change', (e) => { let val; let res; val = $(e.target).val(); res = val.match(/(\d+),(\d+)/); session.setImageSize(parseInt(res[1]), parseInt(res[2])); }); } function framerateString(val) { var ret; ret = Math.trunc((val[0] / val[1]) * 100) / 100; return `${ret} fps`; } function findCapability() { var ret; ret = capabilities.find((obj) => { return ((obj.width == imageWidth) && (obj.height == imageHeight)); }); return ret; } function selectFramerate(rates) { var list; var targ; targ = framerate[0] / framerate[1]; if (!rates) { rates = findCapability()["rate"]; } list = rates.concat(); list.sort((a, b) => { return Math.abs((a[0] / a[1]) - targ) - Math.abs((b[0] / b[1]) - targ); }); $('select#framerate').val(`${list[0][0]},${list[0][1]}`); } function setFramerateSelect() { var info; $('select#framerate') .empty() .off('change'); info = findCapability(); if (info) { info["rate"].forEach((obj) => { $('select#framerate') .append($('<option>') .attr('value', `${obj[0]},${obj[1]}`) .text(framerateString(obj)) ); }); selectFramerate(info['rate']); $('select#framerate') .on('change', (e) => {; let val; let res; val = $(e.target).val(); res = val.match(/(\d+),(\d+)/) session.setFramerate(parseInt(res[1]), parseInt(res[2])); }); } } function addIntegerForm(info) { var $input; var tics; $input = $('<input>') .attr('id', `control-${info["id"]}`) .attr('type', 'range'); $('div#controls') .append($('<div>') .addClass("mb-2") .append($('<label>') .addClass("form-label") .attr("for", `control-${info["id"]}`) .text(info["name"]) ) .append($('<div>') .addClass('form-group ml-3') .append($input) ) ); tics = info["max"] - info["min"]; sliders[info["id"]] = $input .ionRangeSlider({ type: "single", min: info["min"], max: info["max"], step: info["step"], from: info["value"], skin: "sharp", keyboard: true, hide_min_max: true, grid: true, grid_num: (tics > 10)? 10: tics, prettify_separator: ",", onFinish: (data) => { session.setControl(info["id"], data["from"]) } }); } function addBooleanForm(info) { var $input; $input = $('<input>') .attr('id', `control-${info["id"]}`) .attr('type', 'checkbox') .attr('checked', info["value"]) .on('change', (e) => { session.setControl(info["id"], $(e.target).is(':checked')); }); $('div#controls') .append($('<div>') .addClass('form-group') .append($('<div>') .addClass('pretty p-default my-2') .append($input) .append($('<div>') .addClass('state p-primary') .append($('<label>') .addClass('form-label') .attr("for", `control-${info["id"]}`) .text(info["name"]) ) ) ) ); } function addMenuForm(info) { var $select; $select = $('<select>') .attr('id', `control-${info["id"]}`) .addClass('form-control offset-1 col-11'); for (const [label, value] of Object.entries(info["items"])) { $select .append($('<option>') .attr("value", value) .text(label) ); } $select .val(info["value"]) .on('change', (e) => { session.setControl(info["id"], parseInt($(e.target).val())); }); $('div#controls') .append($('<div>') .addClass("form-group") .append($('<label>') .addClass('form-label') .attr("for", `control-${info["id"]}`) .text(info["name"]) ) .append($select) ); } function setControlForm(info) { $('div#controls').empty(); $('div#controls').append($("<hr>")); info.forEach((entry) => { if (entry["type"] == "boolean") { addBooleanForm(entry); } }); $('div#controls').append($("<hr>")); info.forEach((entry) => { if (entry["type"] == "menu") { addMenuForm(entry); } }); $('div#controls').append($("<hr>")); sliders = {}; info.forEach((entry) => { if (entry["type"] == "integer") { addIntegerForm(entry); } }); } function resizePreviewCanvas() { previewCanvas.width = imageWidth; previewCanvas.height = imageHeight; clearPreviewCanvas(); setTimeout(() => { $('div#preview').getNiceScroll().resize(); }, 0); } function updatePreviewCanvas(img) { previewGc.drawImage(img, 0, 0, img.width, img.height, 0, 0, imageWidth, imageHeight); } function updateImage(data) { Utils.loadImageFromData(data) .then((img) => { updatePreviewCanvas(img); }) .fail((error) => { console.log(error); }); } function updateImageSize(width, height) { imageWidth = width; imageHeight = height; resizePreviewCanvas(); setFramerateSelect(); } function updateFramerate(num, deno) { framerate = [num, deno] selectFramerate(); } function updateControl(id, val) { var info; info = controls.find((obj) => obj["id"] == id); switch (info["type"]) { case "boolean": $(`input#control-${id}`).prop('checked', val); break; case "integer": $(`input#control-${id}`).data('ionRangeSlider').update({from:val}); break; case "menu": $(`select#control-${id}`).val(val); break; } } function changeState(state) { var pr1; var pr2; setState(state); if (state == "ALIVE") { pr1 = session.getIdentString() .then((str) => { setIdentString(str); }); pr2 = session.getConfig() .then((info) => { let rates; capabilities = info["capabilities"]; controls = info["controls"]; imageWidth = info["image_width"]; imageHeight = info["image_height"]; framerate = info["framerate"]; resizePreviewCanvas(); sortCapabilities(); setImageSizeSelect(); setFramerateSelect(); setControlForm(info["controls"]); setTimeout(() => { $('div#config').getNiceScroll().resize(); }, 0); }); $.when(pr1, pr2) .done(() => { setupScreenSize(); }); } else { $('select#image-size > option').remove(); $('select#framerate > option').remove(); $('div#controls').empty(); clearPreviewCanvas(); } $('button#action').prop('disabled', false); } function startSession() { session .on('update_image', (data) => { updateImage(data); }) .on('update_image_size', (width, height) => { updateImageSize(width, height); }) .on('update_framerate', (num, deno) => { updateFramerate(num, deno); }) .on('update_control', (id, val) => { updateControl(id, val); }) .on('change_state', (state) => { changeState(state); }) .on('save_complete', () => { $('#save-complete-toast').toast('show'); }) .on('session_closed', () => { Utils.showAbortShield("session closed"); }); session.start() .then(() => { return session.getCameraInfo(); }) .then((info) => { setDeviceFile(info["device"]); changeState(info["state"]); return session.addNotifyRequest(); }) .fail((error) => { Utils.showAbortShield(error); }); } function setupScreen() { $('div#preview') .niceScroll({ enablekeyboard: true, zindex: 100, autohidemode: true, horizrailenabled: false }); $('div#config') .niceScroll({ enablekeyboard: true, zindex: 100, autohidemode: true, horizrailenabled: false }); } function setupButtons() { $('button#action') .on('click', () => { clearPreviewCanvas(); $('button#action').prop('disabled', true); switch (cameraState) { case "STOP": case "ABORT": session.startCamera(); break; case "ALIVE": session.stopCamera(); break; } }); $('button#save-config') .on('click', () => { session.saveConfig(); }); $('button#copy-url') .on('click', () => { let url; url = `${location.protocol}//${location.host}/stream`; Utils.copyToClipboard(url); $('#url-copied-toast').toast('show'); }); } function clearPreviewCanvas() { previewGc.fillStyle = "black"; previewGc.fillRect(0, 0, previewCanvas.width, previewCanvas.height); } function initialize() { session = new Session(WEBSOCK_URL); capabilities = null; controls = null; sliders = null; imageWidth = null; imageHeight = null; framerate = null; previewCanvas = $('canvas#preview-canvas')[0]; previewGc = previewCanvas.getContext('2d'); setupScreen(); setupButtons(); clearPreviewCanvas(); startSession(); } /* * set handler for global objects */ /* エントリーポイントの設定 */ $(window) .on('load', () => { let list = [ "/css/bootstrap.min.css", "/css/ion.rangeSlider.min.css", "/css/pretty-checkbox.min.css", "/js/popper.min.js", "/js/bootstrap.min.js", "/js/msgpack.min.js", "/js/jquery.nicescroll.min.js", "/js/ion.rangeSlider.min.js", "/css/main/style.scss", "/js/msgpack-rpc.js", "/js/session.js", ]; Utils.require(list) .done(() => { initialize(); }); }); $(window) .on('resize', () => { setupScreenSize(); }); /* デフォルトではコンテキストメニューをOFF */ $(document) .on('contextmenu', (e) => { e.stopPropagation(); return false; }); /* Drop&Dragを無効にしておく */ $(document) .on('dragover', (e) => { e.stopPropagation(); return false; }) .on('dragenter', (e) => { e.stopPropagation(); return false; }) .on('drop', (e) => { e.stopPropagation(); return false; });
})();