mirror of
https://github.com/openwrt/luci.git
synced 2026-06-17 14:50:21 +04:00
luci-app-ustreamer: add package
this is a ~replacement for mjpeg-streamer which is no longer maintained. See also: https://github.com/openwrt/packages/pull/28344 drop mjpeg-streamer https://github.com/openwrt/packages/pull/28472 add ustreamer https://github.com/openwrt/packages/pull/28528 ustreamer 6.52 Closes #8221 Signed-off-by: Paul Donald <newtwen+github@gmail.com>
This commit is contained in:
-264
@@ -1,264 +0,0 @@
|
||||
'use strict';
|
||||
'require view';
|
||||
'require form';
|
||||
'require uci';
|
||||
'require ui';
|
||||
'require poll';
|
||||
|
||||
/* Copyright 2014 Roger D < rogerdammit@gmail.com>
|
||||
Licensed to the public under the Apache License 2.0. */
|
||||
|
||||
return view.extend({
|
||||
load: function () {
|
||||
var self = this;
|
||||
poll.add(function () {
|
||||
self.render();
|
||||
}, 5);
|
||||
|
||||
document
|
||||
.querySelector('head')
|
||||
.appendChild(
|
||||
E('style', { type: 'text/css' }, [
|
||||
'.img-preview {display: inline-block !important;height: auto;width: 640px;padding: 4px;line-height: 1.428571429;background-color: #fff;border: 1px solid #ddd;border-radius: 4px;-webkit-transition: all .2s ease-in-out;transition: all .2s ease-in-out;margin-bottom: 5px;display: none;}',
|
||||
]),
|
||||
);
|
||||
|
||||
return Promise.all([uci.load('mjpg-streamer')]);
|
||||
},
|
||||
render: function () {
|
||||
let m, s, o;
|
||||
|
||||
m = new form.Map('mjpg-streamer', 'MJPG-streamer', _('mjpg streamer is a streaming application for Linux-UVC compatible webcams'));
|
||||
|
||||
//General settings
|
||||
|
||||
var section_gen = m.section(form.TypedSection, 'mjpg-streamer', _('General'));
|
||||
section_gen.addremove = false;
|
||||
section_gen.anonymous = true;
|
||||
|
||||
var enabled = section_gen.option(form.Flag, 'enabled', _('Enabled'), _('Enable MJPG-streamer'));
|
||||
|
||||
var input = section_gen.option(form.ListValue, 'input', _('Input plugin'));
|
||||
input.depends('enabled', '1');
|
||||
input.value('uvc', 'UVC');
|
||||
// input: value("file", "File")
|
||||
input.optional = false;
|
||||
|
||||
var output = section_gen.option(form.ListValue, 'output', _('Output plugin'));
|
||||
output.depends('enabled', '1');
|
||||
output.value('http', 'HTTP');
|
||||
output.value('file', 'File');
|
||||
output.optional = false;
|
||||
|
||||
//Plugin settings
|
||||
|
||||
s = m.section(form.TypedSection, 'mjpg-streamer', _('Plugin settings'));
|
||||
s.addremove = false;
|
||||
s.anonymous = true;
|
||||
|
||||
s.tab('output_http', _('HTTP output'));
|
||||
s.tab('output_file', _('File output'));
|
||||
s.tab('input_uvc', _('UVC input'));
|
||||
// s: tab("input_file", _("File input"))
|
||||
|
||||
// Input UVC settings
|
||||
|
||||
var this_tab = 'input_uvc';
|
||||
|
||||
var device = s.taboption(this_tab, form.Value, 'device', _('Device'));
|
||||
device.default = '/dev/video0';
|
||||
//device.datatype = "device"
|
||||
device.value('/dev/video0', '/dev/video0');
|
||||
device.value('/dev/video1', '/dev/video1');
|
||||
device.value('/dev/video2', '/dev/video2');
|
||||
device.optional = false;
|
||||
|
||||
var resolution = s.taboption(this_tab, form.Value, 'resolution', _('Resolution'));
|
||||
resolution.default = '640x480';
|
||||
resolution.value('320x240', '320x240');
|
||||
resolution.value('640x480', '640x480');
|
||||
resolution.value('800x600', '800x600');
|
||||
resolution.value('864x480', '864x480');
|
||||
resolution.value('960x544', '960x544');
|
||||
resolution.value('960x720', '960x720');
|
||||
resolution.value('1280x720', '1280x720');
|
||||
resolution.value('1280x960', '1280x960');
|
||||
resolution.value('1920x1080', '1920x1080');
|
||||
resolution.optional = true;
|
||||
|
||||
var fps = s.taboption(this_tab, form.Value, 'fps', _('Frames per second'));
|
||||
fps.datatype = 'and(uinteger, min(1))';
|
||||
fps.placeholder = '5';
|
||||
fps.optional = true;
|
||||
|
||||
var yuv = s.taboption(this_tab, form.Flag, 'yuv', _('Enable YUYV format'), _('Automatic disabling of MJPEG mode'));
|
||||
|
||||
var quality = s.taboption(
|
||||
this_tab,
|
||||
form.Value,
|
||||
'quality',
|
||||
_('JPEG compression quality'),
|
||||
_('Set the quality in percent. This setting activates YUYV format, disables MJPEG'),
|
||||
);
|
||||
quality.datatype = 'range(0, 100)';
|
||||
|
||||
var minimum_size = s.taboption(
|
||||
this_tab,
|
||||
form.Value,
|
||||
'minimum_size',
|
||||
_('Drop frames smaller than this limit'),
|
||||
_('Set the minimum size if the webcam produces small-sized garbage frames. May happen under low light conditions'),
|
||||
);
|
||||
minimum_size.datatype = 'uinteger';
|
||||
|
||||
var no_dynctrl = s.taboption(this_tab, form.Flag, 'no_dynctrl', _("Don't initialize dynctrls"), _('Do not initialize dynctrls of Linux-UVC driver'));
|
||||
|
||||
var led = s.taboption(this_tab, form.ListValue, 'led', _('Led control'));
|
||||
led.value('on', _('On'));
|
||||
led.value('off', _('Off'));
|
||||
led.value('blink', _('Blink'));
|
||||
led.value('auto', _('Auto'));
|
||||
led.optional = true;
|
||||
|
||||
// Output HTTP settings
|
||||
|
||||
this_tab = 'output_http';
|
||||
|
||||
var port = s.taboption(this_tab, form.Value, 'port', _('Port'), _('TCP port for this HTTP server'));
|
||||
port.datatype = 'port';
|
||||
port.placeholder = '8080';
|
||||
|
||||
var enable_auth = s.taboption(this_tab, form.Flag, 'enable_auth', _('Authentication required'), _('Ask for username and password on connect'));
|
||||
enable_auth.default = false;
|
||||
|
||||
var username = s.taboption(this_tab, form.Value, 'username', _('Username'));
|
||||
username.depends('enable_auth', '1');
|
||||
username.optional = false;
|
||||
|
||||
var password = s.taboption(this_tab, form.Value, 'password', _('Password'));
|
||||
password.depends('enable_auth', '1');
|
||||
password.password = true;
|
||||
password.optional = false;
|
||||
password.default = false;
|
||||
|
||||
var www = s.taboption(this_tab, form.Value, 'www', _('WWW folder'), _('Folder that contains webpages'));
|
||||
www.datatype = 'directory';
|
||||
www.default = '/www/webcam/';
|
||||
www.optional = false;
|
||||
|
||||
|
||||
function init_stream() {
|
||||
console.log('init_stream');
|
||||
start_stream();
|
||||
}
|
||||
|
||||
function _start_stream() {
|
||||
console.log('_start_stream');
|
||||
|
||||
var port = uci.get('mjpg-streamer', 'core', 'port');
|
||||
|
||||
if (uci.get('mjpg-streamer', 'core', 'enable_auth') == '1') {
|
||||
var user = uci.get('mjpg-streamer', 'core', 'username');
|
||||
var pass = uci.get('mjpg-streamer', 'core', 'password');
|
||||
var login = user + ':' + pass + '@';
|
||||
} else {
|
||||
var login = '';
|
||||
}
|
||||
|
||||
var img = document.getElementById('video_preview') || video_preview;
|
||||
img.src = 'http://' + login + location.hostname + ':' + port + '/?action=snapshot' + '&t=' + new Date().getTime();
|
||||
}
|
||||
|
||||
function start_stream() {
|
||||
console.log('start_stream');
|
||||
|
||||
setTimeout(function () {
|
||||
_start_stream();
|
||||
}, 500);
|
||||
}
|
||||
|
||||
function on_error() {
|
||||
console.log('on_error');
|
||||
|
||||
var img = video_preview;
|
||||
img.style.display = 'none';
|
||||
|
||||
var stream_stat = document.getElementById('stream_status') || stream_status;
|
||||
stream_stat.style.display = 'block';
|
||||
|
||||
start_stream();
|
||||
}
|
||||
|
||||
function on_load() {
|
||||
console.log('on_load');
|
||||
|
||||
var img = video_preview;
|
||||
img.style.display = 'block';
|
||||
|
||||
var stream_stat = stream_status;
|
||||
stream_stat.style.display = 'none';
|
||||
}
|
||||
|
||||
//HTTP preview
|
||||
var video_preview = E('img', {
|
||||
'id': 'video_preview',
|
||||
'class': 'img-preview',
|
||||
'error': on_error,
|
||||
'load': on_load,
|
||||
});
|
||||
|
||||
var stream_status = E(
|
||||
'p',
|
||||
{
|
||||
'id': 'stream_status',
|
||||
'style': 'text-align: center; color: orange; font-weight: bold;',
|
||||
},
|
||||
_('Stream unavailable'),
|
||||
);
|
||||
|
||||
|
||||
init_stream();
|
||||
|
||||
var preview = s.taboption(this_tab, form.DummyValue, '_dummy');
|
||||
preview.render = L.bind(function (view, section_id) {
|
||||
return E([], [
|
||||
video_preview,
|
||||
stream_status
|
||||
]);
|
||||
}, preview, this);
|
||||
preview.depends('output', 'http');
|
||||
|
||||
//Output file settings
|
||||
|
||||
this_tab = 'output_file';
|
||||
|
||||
var folder = s.taboption(this_tab, form.Value, 'folder', _('Folder'), _('Set folder to save pictures'));
|
||||
folder.placeholder = '/tmp/images';
|
||||
folder.datatype = 'directory';
|
||||
|
||||
//mjpeg=s.taboption(this_tab, Value, "mjpeg", _("Mjpeg output"), _("Check to save the stream to an mjpeg file"))
|
||||
|
||||
var delay = s.taboption(this_tab, form.Value, 'delay', _('Interval between saving pictures'), _('Set the interval in millisecond'));
|
||||
delay.placeholder = '5000';
|
||||
delay.datatype = 'uinteger';
|
||||
|
||||
var ringbuffer = s.taboption(this_tab, form.Value, 'ringbuffer', _('Ring buffer size'), _('Max. number of pictures to hold'));
|
||||
ringbuffer.placeholder = '10';
|
||||
ringbuffer.datatype = 'uinteger';
|
||||
|
||||
var exceed = s.taboption(this_tab, form.Value, 'exceed', _('Exceed'), _('Allow ringbuffer to exceed limit by this amount'));
|
||||
exceed.datatype = 'uinteger';
|
||||
|
||||
var command = s.taboption(
|
||||
this_tab,
|
||||
form.Value,
|
||||
'command',
|
||||
_('Command to run'),
|
||||
_('Execute command after saving picture. Mjpg-streamer parses the filename as first parameter to your script.'),
|
||||
);
|
||||
|
||||
var link = s.taboption(this_tab, form.Value, 'link', _('Link newest picture to fixed file name'), _('Link the last picture in ringbuffer to fixed named file provided.'));
|
||||
|
||||
return m.render();
|
||||
},
|
||||
});
|
||||
-17
@@ -1,17 +0,0 @@
|
||||
{
|
||||
"admin/services/mjpg-streamer": {
|
||||
"title": "MJPG-streamer",
|
||||
"action": {
|
||||
"type": "view",
|
||||
"path": "mjpg-streamer/mjpg-streamer"
|
||||
},
|
||||
"depends": {
|
||||
"acl": [
|
||||
"luci-app-mjpg-streamer"
|
||||
],
|
||||
"uci": {
|
||||
"mjpg-streamer": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
-15
@@ -1,15 +0,0 @@
|
||||
{
|
||||
"luci-app-mjpg-streamer": {
|
||||
"description": "Grant UCI access for luci-app-mjpg-streamer",
|
||||
"read": {
|
||||
"uci": [
|
||||
"mjpg-streamer"
|
||||
]
|
||||
},
|
||||
"write": {
|
||||
"uci": [
|
||||
"mjpg-streamer"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
+4
-2
@@ -6,12 +6,14 @@
|
||||
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
LUCI_TITLE:=MJPG-Streamer service configuration module
|
||||
LUCI_DEPENDS:=+luci-base +mjpg-streamer
|
||||
LUCI_TITLE:=ustreamer service configuration module
|
||||
LUCI_DEPENDS:=+luci-base +ustreamer
|
||||
|
||||
PKG_LICENSE:=Apache-2.0
|
||||
PKG_MAINTAINER:=Jo-Philipp Wich <jo@mein.io>
|
||||
|
||||
PROVIDES:=luci-app-mjpeg-streamer
|
||||
|
||||
include ../../luci.mk
|
||||
|
||||
# call BuildPackage - OpenWrt buildroot signature
|
||||
+528
@@ -0,0 +1,528 @@
|
||||
'use strict';
|
||||
'require form';
|
||||
'require fs';
|
||||
'require poll';
|
||||
'require uci';
|
||||
'require ui';
|
||||
'require view';
|
||||
|
||||
/* Licensed to the public under the Apache License 2.0. */
|
||||
|
||||
return view.extend({
|
||||
load() {
|
||||
document
|
||||
.querySelector('head')
|
||||
.appendChild(
|
||||
E('style', { type: 'text/css' }, [
|
||||
'.img-preview {display: inline-block !important;height: auto;width: 640px;padding: 4px;line-height: 1.428571429;background-color: #fff;border: 1px solid #ddd;border-radius: 4px;-webkit-transition: all .2s ease-in-out;transition: all .2s ease-in-out;margin-bottom: 5px;display: none;}',
|
||||
]),
|
||||
);
|
||||
|
||||
return Promise.all([
|
||||
L.resolveDefault(fs.list('/dev/'), []).then(entries => entries.filter(e => /^video.*$/.test(e.name)) ),
|
||||
uci.load('ustreamer'),
|
||||
]);
|
||||
},
|
||||
render([video_devs]) {
|
||||
let m, s, o;
|
||||
|
||||
let self = this;
|
||||
poll.add(() => {
|
||||
self.load().then(([video_devs]) => {
|
||||
self.render([video_devs]);
|
||||
});
|
||||
}, 5);
|
||||
|
||||
m = new form.Map('ustreamer', 'ustreamer',
|
||||
_('µStreamer is a lightweight and very quick server to stream MJPEG video from any V4L2 device to the net.'));
|
||||
|
||||
//General settings
|
||||
|
||||
const section_gen = m.section(form.TypedSection, 'ustreamer', _('General'));
|
||||
section_gen.addremove = false;
|
||||
section_gen.anonymous = true;
|
||||
|
||||
const enabled = section_gen.option(form.Flag, 'enabled', _('Enabled'));
|
||||
|
||||
const log_level = section_gen.option(form.Value, 'log_level', _('Log level'));
|
||||
log_level.placeholder = _('info');
|
||||
log_level.value('0', _('info'));
|
||||
log_level.value('1', _('performance'));
|
||||
log_level.value('2', _('verbose'));
|
||||
log_level.value('3', _('debug'));
|
||||
|
||||
//Plugin settings
|
||||
|
||||
s = m.section(form.TypedSection, 'ustreamer', _('Plugin settings'));
|
||||
s.addremove = true;
|
||||
s.anonymous = true;
|
||||
|
||||
s.tab('h264_sink', _('H264 sink'));
|
||||
s.tab('output_http', _('HTTP output'));
|
||||
s.tab('image_control', _('Image control'));
|
||||
s.tab('jpeg_sink', _('JPEG sink'));
|
||||
s.tab('raw_sink', _('RAW sink'));
|
||||
s.tab('input_uvc', _('UVC input'));
|
||||
|
||||
// Input UVC settings
|
||||
|
||||
let this_tab = 'input_uvc';
|
||||
|
||||
const device = s.taboption(this_tab, form.Value, 'device', _('Device'));
|
||||
device.placeholder = '/dev/video0';
|
||||
for (const dev of video_devs)
|
||||
device.value(`/dev/${dev.name}`);
|
||||
device.optional = false;
|
||||
device.rmempty = false;
|
||||
|
||||
const dtimeout = s.taboption(this_tab, form.Value, 'timeout', _('Timeout'), _('units: seconds'));
|
||||
dtimeout.placeholder = '5';
|
||||
dtimeout.datatype = 'uinteger';
|
||||
|
||||
const input = s.taboption(this_tab, form.Flag, 'input', _('Input'));
|
||||
input.default = input.disabled;
|
||||
|
||||
const resolution = s.taboption(this_tab, form.Value, 'resolution', _('Resolution'));
|
||||
resolution.placeholder = '640x480';
|
||||
resolution.value('320x240', '320x240');
|
||||
resolution.value('640x480', '640x480');
|
||||
resolution.value('800x600', '800x600');
|
||||
resolution.value('864x480', '864x480');
|
||||
resolution.value('960x544', '960x544');
|
||||
resolution.value('960x720', '960x720');
|
||||
resolution.value('1280x720', '1280x720');
|
||||
resolution.value('1280x960', '1280x960');
|
||||
resolution.value('1920x1080', '1920x1080');
|
||||
resolution.optional = true;
|
||||
|
||||
const fps = s.taboption(this_tab, form.Value, 'desired_fps', _('Frames per second'),
|
||||
_('Default: maximum possible.'));
|
||||
fps.datatype = 'and(uinteger, min(1))';
|
||||
fps.placeholder = '5';
|
||||
fps.optional = true;
|
||||
|
||||
const format = s.taboption(this_tab, form.Value, 'format', _('Format'));
|
||||
format.placeholder = 'YUYV';
|
||||
format.value('BGR24');
|
||||
format.value('GREY');
|
||||
format.value('JPEG');
|
||||
format.value('MJPEG');
|
||||
format.value('RGB24');
|
||||
format.value('RGB565');
|
||||
format.value('UYVY');
|
||||
format.value('YUV420');
|
||||
format.value('YUYV');
|
||||
format.value('YVU420');
|
||||
format.value('YVYU');
|
||||
|
||||
const encoder = s.taboption(this_tab, form.Value, 'encoder', _('Encoder'));
|
||||
encoder.value('CPU');
|
||||
encoder.value('HW');
|
||||
|
||||
const quality = s.taboption(
|
||||
this_tab,
|
||||
form.Value,
|
||||
'quality',
|
||||
_('Quality'),
|
||||
_('Set the quality in percent.'),
|
||||
);
|
||||
quality.datatype = 'range(0, 100)';
|
||||
|
||||
|
||||
const allow_truncated_frames = s.taboption(this_tab, form.Flag, 'allow_truncated_frames', _('Allow truncated frames'));
|
||||
allow_truncated_frames.default = allow_truncated_frames.disabled;
|
||||
|
||||
const format_swap_rgb = s.taboption(this_tab, form.Flag, 'format_swap_rgb', _('Format: Swap RGB'),
|
||||
_('Enable R-G-B order swapping: RGB to BGR and vice versa.'));
|
||||
format_swap_rgb.default = format_swap_rgb.disabled;
|
||||
|
||||
const persistent = s.taboption(this_tab, form.Flag, 'persistent', _('Persistent'),
|
||||
_("Don't re-initialize device on timeout. Default: disabled."));
|
||||
persistent.default = persistent.disabled;
|
||||
|
||||
const dv_timings = s.taboption(this_tab, form.Flag, 'dv_timings', _('DV Timings'),
|
||||
_("Enable DV-timings querying and events processing to automatic resolution change. Default: disabled."));
|
||||
dv_timings.default = dv_timings.disabled;
|
||||
|
||||
const tv_standard = s.taboption(this_tab, form.Value, 'tv_standard', _('TV standard'));
|
||||
tv_standard.placeholder = '';
|
||||
tv_standard.value('PAL');
|
||||
tv_standard.value('NTSC');
|
||||
tv_standard.value('SECAM');
|
||||
|
||||
const io_method = s.taboption(this_tab, form.Value, 'io_method', _('IO method'));
|
||||
io_method.placeholder = 'MMAP';
|
||||
io_method.value('MMAP');
|
||||
io_method.value('USERPTR');
|
||||
|
||||
const buffers = s.taboption(this_tab, form.Value, 'buffers', _('Buffers'),
|
||||
_('The number of buffers to receive data from the device.') + '<br/>' +
|
||||
_('Each buffer may processed using an independent thread.') + '<br/>' +
|
||||
_('Default: 3 (the number of CPU cores (but not more than 4) + 1).'));
|
||||
buffers.datatype = 'and(uinteger, min(1))';
|
||||
buffers.placeholder = '3';
|
||||
buffers.optional = true;
|
||||
|
||||
const workers = s.taboption(this_tab, form.Value, 'workers', _('Workers'),
|
||||
_('The number of worker threads but not more than buffers.') + '<br/>' +
|
||||
_('Default: 2 (the number of CPU cores (but not more than 4)).'));
|
||||
workers.datatype = 'and(uinteger, min(1))';
|
||||
workers.placeholder = '2';
|
||||
workers.optional = true;
|
||||
|
||||
const m2m_device = s.taboption(this_tab, form.FileUpload, 'm2m_device', _('M2M device'));
|
||||
m2m_device.root_directory = '/dev';
|
||||
m2m_device.show_hidden = true;
|
||||
m2m_device.directory_create = false;
|
||||
m2m_device.enable_download = false;
|
||||
m2m_device.enable_upload = false;
|
||||
m2m_device.enable_remove = false;
|
||||
m2m_device.optional = true;
|
||||
m2m_device.datatype = 'file';
|
||||
|
||||
const min_frame_size = s.taboption(
|
||||
this_tab,
|
||||
form.Value,
|
||||
'min_frame_size',
|
||||
_('Drop frames smaller than this limit'),
|
||||
_('Set the minimum size if the webcam produces small-sized garbage frames. May happen under low light conditions'),
|
||||
);
|
||||
min_frame_size.datatype = 'uinteger';
|
||||
min_frame_size.placeholder = '128';
|
||||
|
||||
const device_error_delay = s.taboption(this_tab, form.Value, 'device_error_delay', _('Device error delay'));
|
||||
device_error_delay.datatype = 'and(uinteger, min(1))';
|
||||
device_error_delay.placeholder = '1';
|
||||
device_error_delay.optional = true;
|
||||
|
||||
// Output HTTP settings
|
||||
|
||||
this_tab = 'output_http';
|
||||
|
||||
const host = s.taboption(this_tab, form.Value, 'host', _('Host'), _('TCP host for this HTTP server'));
|
||||
host.datatype = 'host';
|
||||
host.placeholder = '::';
|
||||
host.datatype = 'or(hostname,ipaddr)';
|
||||
host.optional = false;
|
||||
|
||||
const port = s.taboption(this_tab, form.Value, 'port', _('Port'), _('TCP port for this HTTP server'));
|
||||
port.datatype = 'port';
|
||||
port.placeholder = '8080';
|
||||
port.optional = false;
|
||||
|
||||
const enable_auth = s.taboption(this_tab, form.Flag, 'enable_auth', _('Authentication required'), _('Ask for username and password on connect'));
|
||||
enable_auth.default = false;
|
||||
|
||||
const username = s.taboption(this_tab, form.Value, 'user', _('Username'));
|
||||
username.depends('enable_auth', '1');
|
||||
username.optional = false;
|
||||
|
||||
const password = s.taboption(this_tab, form.Value, 'pass', _('Password'));
|
||||
password.depends('enable_auth', '1');
|
||||
password.password = true;
|
||||
password.optional = false;
|
||||
password.default = false;
|
||||
|
||||
const staticres = s.taboption(this_tab, form.Value, 'static', _('WWW folder'), _('Folder that contains webpages'));
|
||||
staticres.datatype = 'directory';
|
||||
staticres.placeholder = '/www/webcam/';
|
||||
staticres.optional = false;
|
||||
|
||||
const unix = s.taboption(this_tab, form.Value, 'unix', _('Socket'), _('Folder that contains the socket'));
|
||||
unix.datatype = 'file';
|
||||
unix.placeholder = '/path/to/socket';
|
||||
|
||||
const unix_mode = s.taboption(this_tab, form.Value, 'unix_mode', _('Socket Permissions'));
|
||||
unix_mode.datatype = 'string';
|
||||
unix_mode.placeholder = '660';
|
||||
|
||||
const drop_same_frames = s.taboption(this_tab, form.Flag, 'drop_same_frames', _('Drop same frames'));
|
||||
drop_same_frames.default = drop_same_frames.disabled;
|
||||
|
||||
const fake_resolution = s.taboption(this_tab, form.Value, 'fake_resolution', _('Fake resolution'));
|
||||
fake_resolution.placeholder = '640x480';
|
||||
fake_resolution.keylist = resolution.keylist;
|
||||
fake_resolution.vallist = resolution.vallist;
|
||||
|
||||
const allow_origin = s.taboption(this_tab, form.Value, 'allow_origin', _('Allow origin'));
|
||||
allow_origin.values = resolution.values;
|
||||
|
||||
const instance_id = s.taboption(this_tab, form.Value, 'instance_id', _('Instance ID'));
|
||||
|
||||
const server_timeout = s.taboption(this_tab, form.Value, 'server_timeout', _('Server timeout'));
|
||||
server_timeout.datatype = 'uinteger';
|
||||
server_timeout.placeholder = '10';
|
||||
|
||||
|
||||
|
||||
function init_stream() {
|
||||
console.debug('init_stream');
|
||||
start_stream();
|
||||
}
|
||||
|
||||
function _start_stream() {
|
||||
console.debug('_start_stream');
|
||||
|
||||
const port = uci.get('ustreamer', 'core', 'port');
|
||||
let login;
|
||||
|
||||
if (uci.get('ustreamer', 'core', 'enable_auth') == '1') {
|
||||
const user = uci.get('ustreamer', 'core', 'username');
|
||||
const pass = uci.get('ustreamer', 'core', 'password');
|
||||
login = `${user}:${pass}@`;
|
||||
} else {
|
||||
login = '';
|
||||
}
|
||||
|
||||
const img = document.getElementById('video_preview') || video_preview;
|
||||
img.src = 'http://' + login + location.hostname + ':' + port + '/?action=snapshot' + '&t=' + new Date().getTime();
|
||||
}
|
||||
|
||||
function start_stream() {
|
||||
console.debug('start_stream');
|
||||
|
||||
setTimeout(function () {
|
||||
_start_stream();
|
||||
}, 5000);
|
||||
}
|
||||
|
||||
function on_error() {
|
||||
console.warn('on_error');
|
||||
|
||||
const img = video_preview;
|
||||
img.style.display = 'none';
|
||||
|
||||
const stream_stat = document.getElementById('stream_status') || stream_status;
|
||||
stream_stat.style.display = 'block';
|
||||
|
||||
// start_stream();
|
||||
}
|
||||
|
||||
function on_load() {
|
||||
console.debug('on_load');
|
||||
|
||||
const img = video_preview;
|
||||
img.style.display = 'block';
|
||||
|
||||
const stream_stat = stream_status;
|
||||
stream_stat.style.display = 'none';
|
||||
}
|
||||
|
||||
//HTTP preview
|
||||
const video_preview = E('img', {
|
||||
'id': 'video_preview',
|
||||
'class': 'img-preview',
|
||||
'error': on_error,
|
||||
'load': on_load,
|
||||
});
|
||||
|
||||
const stream_status = E('p', {
|
||||
'id': 'stream_status',
|
||||
'style': 'text-align: center; color: orange; font-weight: bold;',
|
||||
},
|
||||
_('Stream unavailable'),
|
||||
);
|
||||
|
||||
|
||||
init_stream();
|
||||
|
||||
const preview = s.taboption(this_tab, form.DummyValue, '_dummy');
|
||||
preview.render = L.bind(function (view, section_id) {
|
||||
return E([], [
|
||||
video_preview,
|
||||
stream_status
|
||||
]);
|
||||
}, preview, this);
|
||||
preview.depends('output', 'http');
|
||||
|
||||
// JPEG sink settings
|
||||
|
||||
this_tab = 'jpeg_sink';
|
||||
|
||||
const jpeg_sink = s.taboption(this_tab, form.Value, 'jpeg_sink', _('JPEG sink'),
|
||||
_('Use the shared memory to sink JPEG frames. Default: disabled.') + '<br/>' +
|
||||
_('The name should end with a suffix ".jpeg".') + '<br/>' +
|
||||
_('Default: disabled.'));
|
||||
jpeg_sink.placeholder = 'name.jpeg';
|
||||
jpeg_sink.optional = true;
|
||||
|
||||
const jpeg_sink_mode = s.taboption(this_tab, form.Value, 'jpeg_sink_mode', _('JPEG sink mode'),
|
||||
_('Set JPEG sink permissions (like 777). Default: 660.'));
|
||||
jpeg_sink_mode.datatype = 'string';
|
||||
jpeg_sink_mode.placeholder = '660';
|
||||
jpeg_sink_mode.optional = true;
|
||||
|
||||
const jpeg_sink_client_ttl = s.taboption(this_tab, form.Value, 'jpeg_sink_client_ttl', _('Client TTL'),
|
||||
_('Default: 10.'));
|
||||
jpeg_sink_client_ttl.datatype = 'uinteger';
|
||||
jpeg_sink_client_ttl.placeholder = '10';
|
||||
jpeg_sink_client_ttl.optional = true;
|
||||
|
||||
const jpeg_sink_timeout = s.taboption(this_tab, form.Value, 'jpeg_sink_timeout', _('JPEG sink timeout'),
|
||||
_('Timeout for lock. Default: 1.'));
|
||||
jpeg_sink_timeout.datatype = 'uinteger';
|
||||
jpeg_sink_timeout.placeholder = '1';
|
||||
jpeg_sink_timeout.optional = true;
|
||||
|
||||
const jpeg_sink_rm = s.taboption(this_tab, form.Flag, 'jpeg_sink_rm', _('Remove JPEG sink'),
|
||||
_('Remove JPEG sink file on exit'));
|
||||
jpeg_sink_rm.default = jpeg_sink_rm.disabled;
|
||||
|
||||
// RAW sink settings
|
||||
|
||||
this_tab = 'raw_sink';
|
||||
|
||||
const raw_sink = s.taboption(this_tab, form.Value, 'raw_sink', _('RAW sink'),
|
||||
_('Use the shared memory to sink RAW frames. Default: disabled.') + '<br/>' +
|
||||
_('The name should end with a suffix ".raw".') + '<br/>' +
|
||||
_('Default: disabled.'));
|
||||
raw_sink.placeholder = 'name.raw';
|
||||
raw_sink.optional = true;
|
||||
|
||||
const raw_sink_mode = s.taboption(this_tab, form.Value, 'raw_sink_mode', _('RAW sink mode'),
|
||||
_('Set RAW sink permissions (like 777). Default: 660.'));
|
||||
raw_sink_mode.datatype = 'string';
|
||||
raw_sink_mode.placeholder = '660';
|
||||
raw_sink_mode.optional = true;
|
||||
|
||||
const raw_sink_client_ttl = s.taboption(this_tab, form.Value, 'raw_sink_client_ttl', _('RAW sink client TTL'),
|
||||
_('Client TTL. Default: 10.'));
|
||||
raw_sink_client_ttl.datatype = 'uinteger';
|
||||
raw_sink_client_ttl.placeholder = '10';
|
||||
raw_sink_client_ttl.optional = true;
|
||||
|
||||
const raw_sink_timeout = s.taboption(this_tab, form.Value, 'raw_sink_timeout', _('RAW sink timeout'),
|
||||
_('Timeout for lock. Default: 1.'));
|
||||
raw_sink_timeout.datatype = 'uinteger';
|
||||
raw_sink_timeout.placeholder = '1';
|
||||
raw_sink_timeout.optional = true;
|
||||
|
||||
const raw_sink_rm = s.taboption(this_tab, form.Flag, 'raw_sink_rm', _('Remove RAW sink'),
|
||||
_('Remove shared memory on stop. Default: disabled.'));
|
||||
raw_sink_rm.default = raw_sink_rm.disabled;
|
||||
|
||||
// H264 sink settings
|
||||
|
||||
this_tab = 'h264_sink';
|
||||
|
||||
const h264_sink = s.taboption(this_tab, form.Value, 'h264_sink', _('H264 sink'),
|
||||
_('Use the shared memory to sink H264 frames. Default: disabled.') + '<br/>' +
|
||||
_('The name should end with a suffix ".h264"') + '<br/>' +
|
||||
_('Default: disabled.'));
|
||||
h264_sink.placeholder = 'name.h264';
|
||||
h264_sink.optional = true;
|
||||
|
||||
const h264_sink_mode = s.taboption(this_tab, form.Value, 'h264_sink_mode', _('H264 sink mode'),
|
||||
_('Set H264 sink permissions (like 777). Default: 660.'));
|
||||
h264_sink_mode.datatype = 'string';
|
||||
h264_sink_mode.placeholder = '660';
|
||||
h264_sink_mode.optional = true;
|
||||
|
||||
const h264_sink_rm = s.taboption(this_tab, form.Flag, 'h264_sink_rm', _('Remove'),
|
||||
_('Remove shared memory on stop. Default: disabled.'));
|
||||
h264_sink_rm.default = h264_sink_rm.disabled;
|
||||
|
||||
const h264_sink_client_ttl = s.taboption(this_tab, form.Value, 'h264_sink_client_ttl', _('Sink client TTL'),
|
||||
_('Client TTL. Default: 10.'));
|
||||
h264_sink_client_ttl.datatype = 'uinteger';
|
||||
h264_sink_client_ttl.placeholder = '10';
|
||||
h264_sink_client_ttl.optional = true;
|
||||
|
||||
const h264_sink_timeout = s.taboption(this_tab, form.Value, 'h264_sink_timeout', _('Sink timeout'),
|
||||
_('Timeout for lock. Default: 1.'));
|
||||
h264_sink_timeout.datatype = 'uinteger';
|
||||
h264_sink_timeout.placeholder = '1';
|
||||
h264_sink_timeout.optional = true;
|
||||
|
||||
const h264_bitrate = s.taboption(this_tab, form.Value, 'h264_bitrate', _('Bitrate'),
|
||||
_('H264 bitrate in Kbps. Default: 5000.'));
|
||||
h264_bitrate.datatype = 'uinteger';
|
||||
h264_bitrate.placeholder = '5000';
|
||||
h264_bitrate.optional = true;
|
||||
|
||||
const h264_gop = s.taboption(this_tab, form.Value, 'h264_gop', _('H264 GOP'),
|
||||
_('Interval between keyframes. Default: 30.'));
|
||||
h264_gop.datatype = 'uinteger';
|
||||
h264_gop.placeholder = '30';
|
||||
h264_gop.optional = true;
|
||||
|
||||
const h264_m2m_device = s.taboption(this_tab, form.FileUpload, 'h264_m2m_device', _('H264 M2M device'),
|
||||
_('Path to V4L2 M2M encoder device. Default: auto select.'));
|
||||
h264_m2m_device.root_directory = '/dev';
|
||||
h264_m2m_device.show_hidden = true;
|
||||
h264_m2m_device.directory_create = false;
|
||||
h264_m2m_device.enable_download = false;
|
||||
h264_m2m_device.enable_upload = false;
|
||||
h264_m2m_device.enable_remove = false;
|
||||
h264_m2m_device.optional = true;
|
||||
h264_m2m_device.datatype = 'file';
|
||||
|
||||
const h264_boost = s.taboption(this_tab, form.Flag, 'h264_boost', _('H264 boost'),
|
||||
_('Increase encoder performance on PiKVM V4. Default: disabled.'));
|
||||
h264_boost.default = h264_boost.disabled;
|
||||
|
||||
const exit_on_no_clients = s.taboption(this_tab, form.Flag, 'exit_on_no_clients', _('Exit on no clients'),
|
||||
_('Exit the program if there have been no stream or sink clients ') +
|
||||
_('or any HTTP requests in the last N seconds. Default: 0 (disabled).'));
|
||||
exit_on_no_clients.default = exit_on_no_clients.disabled;
|
||||
|
||||
// Image control settings
|
||||
|
||||
this_tab = 'image_control';
|
||||
|
||||
const image_default = s.taboption(this_tab, form.Flag, 'image_default', _('Use device defaults'));
|
||||
image_default.default = image_default.disabled;
|
||||
|
||||
const brightness = s.taboption(this_tab, form.Value, 'brightness', _('Brightness'));
|
||||
brightness.placeholder = '128 | auto';
|
||||
brightness.optional = true;
|
||||
|
||||
const contrast = s.taboption(this_tab, form.Value, 'contrast', _('Contrast'));
|
||||
contrast.placeholder = '128';
|
||||
contrast.optional = true;
|
||||
|
||||
const saturation = s.taboption(this_tab, form.Value, 'saturation', _('Saturation'));
|
||||
saturation.placeholder = '128';
|
||||
saturation.optional = true;
|
||||
|
||||
const hue = s.taboption(this_tab, form.Value, 'hue', _('Hue'));
|
||||
hue.placeholder = '128 | auto';
|
||||
hue.optional = true;
|
||||
|
||||
const gamma = s.taboption(this_tab, form.Value, 'gamma', _('Gamma'));
|
||||
gamma.placeholder = '128';
|
||||
gamma.optional = true;
|
||||
|
||||
const sharpness = s.taboption(this_tab, form.Value, 'sharpness', _('Sharpness'));
|
||||
sharpness.placeholder = '128';
|
||||
sharpness.optional = true;
|
||||
|
||||
const backlight_compensation = s.taboption(this_tab, form.Value, 'backlight_compensation', _('Backlight compensation'));
|
||||
backlight_compensation.placeholder = '128';
|
||||
backlight_compensation.optional = true;
|
||||
|
||||
const white_balance = s.taboption(this_tab, form.Value, 'white_balance', _('White balance'));
|
||||
white_balance.placeholder = '128 | auto';
|
||||
white_balance.optional = true;
|
||||
|
||||
const gain = s.taboption(this_tab, form.Value, 'gain', _('Gain'));
|
||||
gain.placeholder = '128 | auto';
|
||||
gain.optional = true;
|
||||
|
||||
const color_effect = s.taboption(this_tab, form.Value, 'color_effect', _('Color effect'));
|
||||
color_effect.placeholder = '128';
|
||||
color_effect.optional = true;
|
||||
|
||||
const rotate = s.taboption(this_tab, form.Value, 'rotate', _('Rotate'));
|
||||
rotate.datatype = 'uinteger';
|
||||
rotate.optional = true;
|
||||
|
||||
const flip_horizontal = s.taboption(this_tab, form.Flag, 'flip_horizontal', _('Flip horizontally'));
|
||||
flip_horizontal.default = flip_horizontal.disabled;
|
||||
|
||||
const flip_vertical = s.taboption(this_tab, form.Flag, 'flip_vertical', _('Flip vertically'));
|
||||
flip_vertical.default = flip_vertical.disabled;
|
||||
|
||||
return m.render();
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"admin/services/ustreamer": {
|
||||
"title": "ustreamer",
|
||||
"action": {
|
||||
"type": "view",
|
||||
"path": "ustreamer/ustreamer"
|
||||
},
|
||||
"depends": {
|
||||
"acl": [
|
||||
"luci-app-ustreamer"
|
||||
],
|
||||
"uci": {
|
||||
"ustreamer": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"luci-app-ustreamer": {
|
||||
"description": "Grant UCI access for luci-app-ustreamer",
|
||||
"read": {
|
||||
"file": [
|
||||
"/dev/*"
|
||||
],
|
||||
"uci": [
|
||||
"ustreamer"
|
||||
]
|
||||
},
|
||||
"write": {
|
||||
"uci": [
|
||||
"ustreamer"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user