ipcam 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,486 @@
1
+ /*
2
+ * Sample for v4l2-ruby
3
+ *
4
+ * Copyright (C) 2019 Hiroshi Kuwagata <kgt9221@gmail.com>
5
+ */
6
+
7
+ (function () {
8
+ /*
9
+ * define constants
10
+ */
11
+
12
+ const WS_URL = `ws://${location.hostname}:${parseInt(location.port)+1}/`;
13
+
14
+ /*
15
+ * declar package global variabled
16
+ */
17
+
18
+ var session;
19
+ var capabilities;
20
+ var controls;
21
+
22
+ var imageWidth;
23
+ var imageHeight;
24
+ var framerate;
25
+
26
+ var previewCanvas;
27
+ var previewGc;
28
+
29
+ /*
30
+ * declar functions
31
+ */
32
+
33
+ function setCameraInfo(info) {
34
+ var fg;
35
+ var bg;
36
+
37
+ $('h3#device-file').text(info["device"]);
38
+
39
+ switch (info["state"]) {
40
+ case "READY":
41
+ default:
42
+ fg = "royalblue";
43
+ bg = "white";
44
+ break;
45
+
46
+ case "BUSY":
47
+ fg = "gold";
48
+ bg = "black";
49
+ break;
50
+
51
+ case "ABORT":
52
+ fg = "crimson";
53
+ bg = "white";
54
+ }
55
+
56
+ $('div#state')
57
+ .css('color', fg)
58
+ .css('-webkit-text-stroke', `0.5px ${bg}`)
59
+ .text(info["state"]);
60
+ }
61
+
62
+ function setIdentString(str) {
63
+ var height;
64
+
65
+ $('h5#device-name').text(str);
66
+
67
+ height = $('body').height() - $('div.jumbotron').outerHeight(true);
68
+ $('div#main-area').height(height);
69
+ }
70
+
71
+ function sortCapabilities() {
72
+ capabilities.sort((a, b) => {
73
+ return ((a.width * a.height) - (b.width * b.height));
74
+ });
75
+
76
+ capabilities.forEach((info) => {
77
+ info["rate"].sort((a, b) => {return (a[0] / a[1]) - (b[0] / b[1])});
78
+ });
79
+ }
80
+
81
+ function setImageSizeSelect() {
82
+ /*
83
+ * 画像サイズ
84
+ */
85
+ $('select#image-size').empty();
86
+
87
+ capabilities.forEach((obj) => {
88
+ $('select#image-size')
89
+ .append($('<option>')
90
+ .attr('value', `${obj.width},${obj.height}`)
91
+ .text(`${obj.width} \u00d7 ${obj.height}`)
92
+ );
93
+ });
94
+
95
+ $('select#image-size')
96
+ .val(`${imageWidth},${imageHeight}`)
97
+ .on('change', (e) => {
98
+ let val;
99
+ let res;
100
+
101
+ val = $(e.target).val();
102
+ res = val.match(/(\d+),(\d+)/);
103
+
104
+ session.setImageSize(parseInt(res[1]), parseInt(res[2]));
105
+ });
106
+ }
107
+
108
+ function framerateString(val) {
109
+ var ret;
110
+
111
+ ret = Math.trunc((val[0] / val[1]) * 100) / 100;
112
+
113
+ return `${ret} fps`;
114
+ }
115
+
116
+ function chooseFramerate(rate) {
117
+ var info;
118
+ var list;
119
+ var targ;
120
+
121
+ targ = rate[0] / rate[1];
122
+
123
+ info = capabilities.find((obj) => {
124
+ return ((obj.width == imageWidth) && (obj.height == imageHeight));
125
+ });
126
+
127
+ list = info["rate"].reduce((m, n) => {m.push(n); return m}, []);
128
+ list.sort((a, b) => {
129
+ return Math.abs((a[0] / a[1]) - targ) - Math.abs((b[0] / b[1]) - targ);
130
+ });
131
+
132
+ return `${list[0][0]},${list[0][1]}`;
133
+ }
134
+
135
+ function setFramerateSelect() {
136
+ var info;
137
+
138
+ /*
139
+ * フレームレート
140
+ */
141
+
142
+ $('select#framerate').empty();
143
+
144
+ info = capabilities.find((obj) => {
145
+ return ((obj.width == imageWidth) && (obj.height == imageHeight));
146
+ });
147
+
148
+ if (info) {
149
+ info["rate"].forEach((obj) => {
150
+ $('select#framerate')
151
+ .append($('<option>')
152
+ .attr('value', `${obj[0]},${obj[1]}`)
153
+ .text(framerateString(obj))
154
+ );
155
+ });
156
+
157
+ $('select#framerate')
158
+ .val(chooseFramerate(framerate))
159
+ .on('change', (e) => {;
160
+ let val;
161
+ let res;
162
+
163
+ val = $(e.target).val();
164
+ res = val.match(/(\d+),(\d+)/)
165
+
166
+ session.setFramerate(parseInt(res[1]), parseInt(res[2]));
167
+ });
168
+ }
169
+ }
170
+
171
+ function addIntegerForm(info) {
172
+ var $input;
173
+ var tics;
174
+
175
+ $input = $('<input>')
176
+ .attr('id', `control-${info["id"]}`)
177
+ .attr('type', 'range')
178
+ .attr('value', info["value"]);
179
+
180
+ $('div#controls')
181
+ .append($('<div>')
182
+ .addClass("mb-2")
183
+ .append($('<label>')
184
+ .addClass("form-label")
185
+ .attr("for", `control-${info["id"]}`)
186
+ .text(info["name"])
187
+ )
188
+ .append($('<div>')
189
+ .addClass('form-group ml-3')
190
+ .append($input)
191
+ )
192
+ );
193
+
194
+ tics = info["max"] - info["min"];
195
+
196
+ $input
197
+ .ionRangeSlider({
198
+ type: "single",
199
+ min: info["min"],
200
+ max: info["max"],
201
+ step: info["step"],
202
+ skin: "sharp",
203
+ keyboard: true,
204
+ hide_min_max: true,
205
+ grid: true,
206
+ grid_num: (tics > 10)? 10: tics,
207
+
208
+ onFinish: (data) => {
209
+ session.setControl(info["id"], data["from"])
210
+ }
211
+ });
212
+ }
213
+
214
+ function addBooleanForm(info) {
215
+ var $input;
216
+
217
+ $input = $('<input>')
218
+ .attr('id', `control-${info["id"]}`)
219
+ .attr('type', 'checkbox')
220
+ .attr('checked', info["value"])
221
+ .on('change', (e) => {
222
+ session.setControl(info["id"], $(e.target).is(':checked'));
223
+ });
224
+
225
+ $('div#controls')
226
+ .append($('<div>')
227
+ .addClass('pretty p-default my-2')
228
+ .append($input)
229
+ .append($('<div>')
230
+ .addClass('state p-primary')
231
+ .append($('<label>')
232
+ .addClass('form-label')
233
+ .attr("for", `control-${info["id"]}`)
234
+ .text(info["name"])
235
+ )
236
+ )
237
+ );
238
+ }
239
+
240
+ function addMenuForm(info) {
241
+ var $select;
242
+
243
+ $select = $('<select>')
244
+ .attr('id', `control-${info["id"]}`)
245
+ .addClass('form-control offset-1 col-11');
246
+
247
+ for (const [label, value] of Object.entries(info["items"])) {
248
+ $select
249
+ .append($('<option>')
250
+ .attr("value", value)
251
+ .text(label)
252
+ );
253
+ }
254
+
255
+ $select
256
+ .val(info["value"])
257
+ .on('change', (e) => {
258
+ session.setControl(info["id"], parseInt($(e.target).val()));
259
+ });
260
+
261
+ $('div#controls')
262
+ .append($('<div>')
263
+ .addClass("form-group")
264
+ .append($('<label>')
265
+ .addClass('form-label')
266
+ .attr("for", `control-${info["id"]}`)
267
+ .text(info["name"])
268
+ )
269
+ .append($select)
270
+ );
271
+ }
272
+
273
+ function setControlForm(info) {
274
+ $('div#controls').empty();
275
+
276
+ $('div#controls').append($("<hr>"));
277
+
278
+ info.forEach((entry) => {
279
+ if (entry["type"] == "boolean") {
280
+ addBooleanForm(entry);
281
+ }
282
+ });
283
+
284
+ $('div#controls').append($("<hr>"));
285
+
286
+ info.forEach((entry) => {
287
+ if (entry["type"] == "menu") {
288
+ addMenuForm(entry);
289
+ }
290
+ });
291
+
292
+ $('div#controls').append($("<hr>"));
293
+
294
+ info.forEach((entry) => {
295
+ if (entry["type"] == "integer") {
296
+ addIntegerForm(entry);
297
+ }
298
+ });
299
+ }
300
+
301
+ function resizePreviewCanvas() {
302
+ previewCanvas.width = imageWidth;
303
+ previewCanvas.height = imageHeight;
304
+
305
+ $('div#preview').getNiceScroll().resize();
306
+ }
307
+
308
+ function updatePreviewCanvas(img) {
309
+ previewGc.drawImage(img,
310
+ 0,
311
+ 0,
312
+ img.width,
313
+ img.height,
314
+ 0,
315
+ 0,
316
+ imageWidth,
317
+ imageHeight);
318
+ }
319
+
320
+ function startSession() {
321
+ session
322
+ .on('update_image', (data) => {
323
+ Utils.loadImageFromData(data)
324
+ .then((img) => {
325
+ updatePreviewCanvas(img);
326
+ })
327
+ .fail((error) => {
328
+ console.log(error);
329
+ });
330
+ })
331
+ .on('update_image_size', (width, height) => {
332
+ imageWidth = width;
333
+ imageHeight = height;
334
+
335
+ resizePreviewCanvas();
336
+ setFramerateSelect();
337
+ })
338
+ .on('update_framerate', (num, deno) => {
339
+ console.log("not implemented yet");
340
+ })
341
+ .on('update_control', (id, val) => {
342
+ console.log("not implemented yet");
343
+ });
344
+
345
+ session.start()
346
+ .then(() => {
347
+ return session.getCameraInfo();
348
+ })
349
+ .then((info) => {
350
+ setCameraInfo(info);
351
+
352
+ if (info["state"] == "BUSY") {
353
+ session.getIdentString()
354
+ .then((str) => {
355
+ setIdentString(str);
356
+ });
357
+
358
+ session.getConfig()
359
+ .then((info) => {
360
+ let rates;
361
+
362
+ capabilities = info["capabilities"];
363
+ controls = info["controls"];
364
+ imageWidth = info["image_width"];
365
+ imageHeight = info["image_height"];
366
+ framerate = info["framerate"];
367
+
368
+ sortCapabilities();
369
+
370
+ resizePreviewCanvas();
371
+ setImageSizeSelect();
372
+ setFramerateSelect();
373
+
374
+ setControlForm(info["controls"]);
375
+
376
+ $('div#config').getNiceScroll().resize();
377
+ });
378
+ }
379
+
380
+ return session.addNotifyRequest();
381
+ })
382
+ .fail((error) => {
383
+ console.log(error);
384
+ });
385
+ }
386
+
387
+ function setupScreen() {
388
+ $('div#preview')
389
+ .niceScroll({
390
+ enablekeyboard: true,
391
+ zindex: 100,
392
+ autohidemode: true,
393
+ horizrailenabled: false
394
+ });
395
+
396
+ $('div#config')
397
+ .niceScroll({
398
+ enablekeyboard: true,
399
+ zindex: 100,
400
+ autohidemode: true,
401
+ horizrailenabled: false
402
+ });
403
+ }
404
+
405
+ function setupButtons() {
406
+ $('button#save-config')
407
+ .on('click', () => {
408
+ session.saveConfig();
409
+ });
410
+
411
+ $('button#copy-url')
412
+ .on('click', () => {
413
+ let url;
414
+
415
+ url = `${location.protocol}//${location.host}/stream`;
416
+ Utils.copyToClipboard(url);
417
+ });
418
+ }
419
+
420
+ function initialize() {
421
+ session = new Session(WS_URL);
422
+ capabilities = null;
423
+ controls = null;
424
+ imageWidth = null;
425
+ imageHeight = null;
426
+ framerate = null;
427
+ previewCanvas = $('canvas#preview-canvas')[0];
428
+ previewGc = previewCanvas.getContext('2d');
429
+
430
+ setupScreen();
431
+ setupButtons();
432
+
433
+ startSession();
434
+ }
435
+
436
+ /*
437
+ * set handler for global objects
438
+ */
439
+
440
+ /* エントリーポイントの設定 */
441
+ $(window)
442
+ .on('load', () => {
443
+ let list = [
444
+ "/css/bootstrap.min.css",
445
+ "/css/ion.rangeSlider.min.css",
446
+ "/css/pretty-checkbox.min.css",
447
+
448
+ "/js/popper.min.js",
449
+ "/js/bootstrap.min.js",
450
+ "/js/msgpack.min.js",
451
+ "/js/jquery.nicescroll.min.js",
452
+ "/js/ion.rangeSlider.min.js",
453
+
454
+ "/css/main/style.scss",
455
+ "/js/msgpack-rpc.js",
456
+ "/js/session.js",
457
+ ];
458
+
459
+ Utils.require(list)
460
+ .done(() => {
461
+ initialize();
462
+ });
463
+ });
464
+
465
+ /* デフォルトではコンテキストメニューをOFF */
466
+ $(document)
467
+ .on('contextmenu', (e) => {
468
+ e.stopPropagation();
469
+ return false;
470
+ });
471
+
472
+ /* Drop&Dragを無効にしておく */
473
+ $(document)
474
+ .on('dragover', (e) => {
475
+ e.stopPropagation();
476
+ return false;
477
+ })
478
+ .on('dragenter', (e) => {
479
+ e.stopPropagation();
480
+ return false;
481
+ })
482
+ .on('drop', (e) => {
483
+ e.stopPropagation();
484
+ return false;
485
+ });
486
+ })();
@@ -0,0 +1,61 @@
1
+ /*
2
+ * Sample for v4l2-ruby
3
+ *
4
+ * Copyright (C) 2019 Hiroshi Kuwagata <kgt9221@gmail.com>
5
+ */
6
+
7
+ if (!msgpack || !msgpack.rpc) {
8
+ throw "msgpack-lite.js and msgpack-rpc.js is not load yet"
9
+ }
10
+
11
+ (function () {
12
+ Session = class extends msgpack.rpc {
13
+ constructor(url) {
14
+ super(url)
15
+ }
16
+
17
+ hello() {
18
+ return this.remoteCall('hello');
19
+ }
20
+
21
+ addNotifyRequest() {
22
+ var args;
23
+
24
+ args = Array.prototype.slice.call(arguments);
25
+ if (args.length == 0) {
26
+ args = Object.keys(this.handlers);
27
+ }
28
+
29
+ return this.remoteCall('add_notify_request', ...args);
30
+ }
31
+
32
+ getCameraInfo() {
33
+ return this.remoteCall('get_camera_info');
34
+ }
35
+
36
+ getIdentString() {
37
+ return this.remoteCall('get_ident_string');
38
+ }
39
+
40
+ getConfig() {
41
+ return this.remoteCall('get_config');
42
+ }
43
+
44
+ setImageSize(width, height) {
45
+ return this.remoteCall('set_image_size', width, height);
46
+ }
47
+
48
+ setFramerate(num, deno) {
49
+ return this.remoteCall('set_framerate', num, deno);
50
+ }
51
+
52
+ setControl(id, val) {
53
+ return this.remoteCall('set_control', id, val);
54
+ }
55
+
56
+ saveConfig() {
57
+ return this.remoteCall('save_config');
58
+ }
59
+
60
+ }
61
+ })();
@@ -0,0 +1,91 @@
1
+ @charset "UTF-8";
2
+
3
+ body {
4
+ width: 100%;
5
+ height: 100%;
6
+
7
+ div.jumbotron {
8
+ padding: 2rem 1rem;
9
+ margin-bottom: 0px;
10
+
11
+ h3 {
12
+ font-weight: bold;
13
+ }
14
+
15
+ h5 {
16
+ font-weight: lighter;
17
+ }
18
+ }
19
+
20
+ div#switches {
21
+ width: 15%;
22
+ height: 100%;
23
+ text-align: center;
24
+
25
+ button {
26
+ width: 90%;
27
+ }
28
+ }
29
+
30
+ div#preview {
31
+ width: 60%;
32
+ height: 100%;
33
+
34
+ text-align: center;
35
+ position: relative;
36
+
37
+ div#preview-wrapper {
38
+ position: relative;
39
+
40
+ div#state {
41
+ position: absolute;
42
+ top: 0px;
43
+ left: 16px;
44
+ font-weight: bold;
45
+ font-size: 24pt;
46
+ }
47
+
48
+ canvas {
49
+ width: 100%;
50
+ border: solid 1px rgba(0, 0, 0, 0.3);
51
+ }
52
+ }
53
+ }
54
+
55
+ div#config {
56
+ width: 25%;
57
+ height: 100%;
58
+ overflow: hidden;
59
+
60
+ select {
61
+ font-size: 80%;
62
+ }
63
+
64
+ .irs {
65
+ font-family: monospace;
66
+ }
67
+
68
+ .irs--sharp {
69
+ .irs-bar {
70
+ background-color: cornflowerblue;
71
+ }
72
+
73
+ .irs-handle {
74
+ background-color: dodgerblue;
75
+ }
76
+
77
+ .irs-handle > i:first-child {
78
+ border-top-color: dodgerblue;
79
+ }
80
+
81
+ .irs-min, .irs-max {
82
+ visibility: hidden;
83
+ background-color: transparent;
84
+ }
85
+
86
+ .irs-from, .irs-to, .irs-single {
87
+ background-color: gray;
88
+ }
89
+ }
90
+ }
91
+ }
@@ -0,0 +1,68 @@
1
+ <html>
2
+ <head>
3
+ <meta charset="utf-8">
4
+ <title>Camera</title>
5
+
6
+ <% if not $develop_mode %>
7
+ <meta http-equiv="Pragma" content="no-cache">
8
+ <meta http-equiv="Cache-Control" content="no-cache">
9
+ <meta http-equiv="Expires" content="-1">
10
+ <% end %>
11
+ </meta>
12
+
13
+ <script type="text/javascript" src="/js/jquery-3.4.1.min.js"></script>
14
+ <script type="text/javascript" src="/js/util.js"></script>
15
+ <script type="text/javascript" src="/js/main.js"></script>
16
+ </head>
17
+
18
+ <body>
19
+ <div class="jumbotron jumbotron-fluid">
20
+ <div class="container">
21
+ <h3 id="device-file"></h3>
22
+ <h5 id="device-name"></h3>
23
+ </div>
24
+ </div>
25
+
26
+ <div id="main-area" class="d-flex d-flex-row">
27
+ <div id="switches">
28
+ <div class="form-grounp mt-3">
29
+ <button class="btn btn-primary" disabled>STOP</button>
30
+ </div>
31
+
32
+ <div class="form-grounp mt-3">
33
+ <button id="save-config" class="btn btn-primary">SAVE</button>
34
+ </div>
35
+
36
+ <div class="form-grounp mt-3">
37
+ <button id="copy-url" class="btn btn-primary">COPY URL</button>
38
+ </div>
39
+ </div>
40
+
41
+ <div id="preview">
42
+ <div id="preview-wrapper" class="mx-3 my-3">
43
+ <div id="state"></div>
44
+ <canvas id="preview-canvas" width=320 height=240></canvas>
45
+ </div>
46
+ </div>
47
+
48
+ <div id="config">
49
+ <div class="mx-3 my-3">
50
+ <div class="form-group">
51
+ <label for="image-size">image size</label>
52
+ <select id="image-size"
53
+ class="form-control offset-1 col-11"></select>
54
+ </div>
55
+
56
+ <div class="form-group">
57
+ <label for="framerate">framerate</label>
58
+ <select id="framerate"
59
+ class="form-control offset-1 col-11"></select>
60
+ </div>
61
+
62
+ <div id="controls">
63
+ </div>
64
+ </div>
65
+ </div>
66
+ </div>
67
+ </body>
68
+ </html>