add remote camera module

This commit is contained in:
lxsang 2020-12-28 20:25:42 +00:00
parent befa74e64c
commit 9ac6f51a66
11 changed files with 514 additions and 0 deletions

6
RemoteCamera/README.md Normal file
View File

@ -0,0 +1,6 @@
# RemoteCamera
Connect to a V4L2 camera on server via Antunnel.
This application reauires the **tunel plugin** and the **ant-tunnel v4l2 publisher**
on the server-side

View File

@ -0,0 +1,21 @@
<afx-app-window apptitle="RemoteCamera" width="650" height="550" data-id="RemoteCamera">
<afx-vbox >
<afx-hbox data-height="30">
<div data-width="10"></div>
<afx-label text="__(Resolution)" data-width="80"></afx-label>
<afx-list-view dropdown=true data-id="resoctl"></afx-list-view>
<div data-width="10"></div>
<afx-label text="__(JPEG Quality)" data-width="90"></afx-label>
<afx-slider data-id="qctl" ></afx-slider>
<div data-width="10"></div>
<afx-label text="__(FPS)" data-width="40"></afx-label>
<afx-list-view dropdown=true data-id="fpsctl"></afx-list-view>
<div data-width="10"></div>
</afx-hbox>
<div data-id="container">
<canvas data-id="player"></canvas>
</div>
</afx-vbox>
</afx-app-window>

View File

@ -0,0 +1,6 @@
# RemoteCamera
Connect to a V4L2 camera on server via Antunnel.
This application reauires the **tunel plugin** and the **ant-tunnel v4l2 publisher**
on the server-side

View File

@ -0,0 +1,12 @@
afx-app-window[data-id="RemoteCamera"] div[data-id="container"]
{
display: block;
overflow: auto;
}
afx-app-window[data-id="RemoteCamera"] div[data-id="container"] canvas
{
display: block;
margin:0 auto;
}

View File

@ -0,0 +1,221 @@
(function() {
var RemoteCamera;
RemoteCamera = class RemoteCamera extends this.OS.application.BaseApplication {
constructor(args) {
super("RemoteCamera", args);
}
main() {
var fps, i, j;
this.mute = false;
this.player = this.find("player");
this.qctl = this.find("qctl");
this.fpsctl = this.find("fpsctl");
this.cam_setting = {
w: 640,
h: 480,
fps: 10,
quality: 60
};
fps = [];
for (i = j = 5; j <= 30; i = j += 5) {
fps.push({
text: `${i}`,
value: i
});
}
this.fpsctl.data = fps;
this.fpsctl.selected = this.cam_setting.fps / 5 - 1;
this.fpsctl.onlistselect = (e) => {
if (this.mute) {
return;
}
this.cam_setting.fps = e.data.item.data.value;
return this.setCameraSetting();
};
this.qctl.value = this.cam_setting.quality;
this.resoctl = this.find("resoctl");
this.resoctl.data = [
{
text: __("320x240"),
mode: "qvga"
},
{
text: __("640x480"),
selected: true,
mode: "vga"
},
{
text: __("800x600"),
mode: "svga"
},
{
text: __("1024x760"),
mode: "hd"
},
{
text: __("1920×1080"),
mode: "fhd"
}
];
this.resoctl.onlistselect = (e) => {
if (this.mute) {
return;
}
switch (e.data.item.data.mode) {
case "qvga":
this.cam_setting.w = 320;
this.cam_setting.h = 240;
break;
case "vga":
this.cam_setting.w = 640;
this.cam_setting.h = 480;
break;
case "svga":
this.cam_setting.w = 800;
this.cam_setting.h = 600;
break;
case "hd":
this.cam_setting.w = 1024;
this.cam_setting.h = 768;
break;
case "fhd":
this.cam_setting.w = 1920;
this.cam_setting.h = 1080;
}
return this.setCameraSetting();
};
this.qctl.onvaluechange = (e) => {
if (this.mute) {
return;
}
this.cam_setting.quality = e.data;
return this.setCameraSetting();
};
if (!Antunnel.tunnel) {
return this.notify(__("Antunnel service is not available"));
}
if (!this.setting.channel) {
return this.requestChannel();
} else {
return this.openSession();
}
}
requestChannel() {
return this.openDialog("PromptDialog", {
title: __("Enter camera channel"),
label: __("Please enter camera channel name")
}).then((v) => {
this.setting.channel = v;
return this.openSession();
});
}
menu() {
return {
text: "__(Option)",
nodes: [
{
text: "__(Camera channel)"
}
],
onchildselect: (e) => {
return this.requestChannel();
}
};
}
openSession() {
if (!Antunnel) {
return;
}
if (!this.setting.channel) {
return;
}
this.tunnel = Antunnel.tunnel;
this.sub = new Antunnel.Subscriber(this.setting.channel);
this.sub.onopen = () => {
return console.log("Subscribed to camera channel");
};
this.sub.onerror = (e) => {
return this.error(__("Error: {0}", new TextDecoder("utf-8").decode(e.data)), e);
};
//@sub = undefined
this.sub.onctrl = (e) => {
var res;
this.cam_setting.w = Antunnel.Msg.int_from(e.data, 0);
this.cam_setting.h = Antunnel.Msg.int_from(e.data, 2);
this.cam_setting.fps = e.data[4];
this.cam_setting.quality = e.data[5];
this.mute = true;
this.qctl.value = this.cam_setting.quality;
res = `${this.cam_setting.w}x${this.cam_setting.h}`;
switch (res) {
case "320x240":
this.resoctl.selected = 0;
break;
case "640x480":
this.resoctl.selected = 1;
break;
case "800x600":
this.resoctl.selected = 2;
break;
case "1024x768":
this.resoctl.selected = 3;
break;
case "1920x1080":
this.resoctl.selected = 4;
}
this.fpsctl.selected = this.cam_setting.fps / 5 - 1;
return this.mute = false;
};
this.sub.onmessage = (e) => {
var context, imgData, jpeg;
jpeg = new JpegImage();
jpeg.parse(e.data);
context = this.player.getContext("2d");
this.player.width = jpeg.width;
this.player.height = jpeg.height;
//jpeg.copyToImageData(d)
imgData = context.getImageData(0, 0, jpeg.width, jpeg.height);
jpeg.copyToImageData(imgData);
return context.putImageData(imgData, 0, 0);
};
this.sub.onclose = () => {
this.sub = void 0;
this.notify(__("Unsubscribed to the camera service"));
return this.quit();
};
return Antunnel.tunnel.subscribe(this.sub);
}
cleanup() {
if (this.sub) {
return this.sub.close();
}
}
setCameraSetting() {
var arr;
if (!this.sub) {
return;
}
arr = new Uint8Array(6);
arr.set(Antunnel.Msg.bytes_of(this.cam_setting.w), 0);
arr.set(Antunnel.Msg.bytes_of(this.cam_setting.h), 2);
arr[4] = this.cam_setting.fps;
arr[5] = this.cam_setting.quality;
return this.sub.send(Antunnel.Msg.CTRL, arr);
}
};
RemoteCamera.singleton = true;
RemoteCamera.dependencies = ["pkg://libjpeg/jpg.js"];
this.OS.register("RemoteCamera", RemoteCamera);
}).call(this);

View File

@ -0,0 +1,16 @@
{
"pkgname": "RemoteCamera",
"app":"RemoteCamera",
"name":"Remote Camera",
"description":"Connect to remote camera via Antunnel",
"info":{
"author": "",
"email": ""
},
"version":"0.0.1-a",
"category":"Other",
"iconclass":"fa fa-camera",
"mimes":["none"],
"dependencies":["libjpeg@0.1.1-a", "Antunnel@0.1.8-a"],
"locale": {}
}

View File

@ -0,0 +1,21 @@
<afx-app-window apptitle="RemoteCamera" width="650" height="550" data-id="RemoteCamera">
<afx-vbox >
<afx-hbox data-height="30">
<div data-width="10"></div>
<afx-label text="__(Resolution)" data-width="80"></afx-label>
<afx-list-view dropdown=true data-id="resoctl"></afx-list-view>
<div data-width="10"></div>
<afx-label text="__(JPEG Quality)" data-width="90"></afx-label>
<afx-slider data-id="qctl" ></afx-slider>
<div data-width="10"></div>
<afx-label text="__(FPS)" data-width="40"></afx-label>
<afx-list-view dropdown=true data-id="fpsctl"></afx-list-view>
<div data-width="10"></div>
</afx-hbox>
<div data-id="container">
<canvas data-id="player"></canvas>
</div>
</afx-vbox>
</afx-app-window>

View File

@ -0,0 +1,177 @@
class RemoteCamera extends this.OS.application.BaseApplication
constructor: ( args ) ->
super "RemoteCamera", args
main: () ->
@mute = false
@player = @find "player"
@qctl = @find "qctl"
@fpsctl = @find "fpsctl"
@cam_setting = {
w: 640,
h: 480,
fps: 10,
quality: 60
}
fps = []
for i in [5..30] by 5
fps.push {
text: "#{i}",
value: i
}
@fpsctl.data = fps
@fpsctl.selected = @cam_setting.fps/5 -1
@fpsctl.onlistselect = (e) =>
return if @mute
@cam_setting.fps = e.data.item.data.value
@setCameraSetting()
@qctl.value = @cam_setting.quality
@resoctl = @find "resoctl"
@resoctl.data = [
{
text: __("320x240"),
mode: "qvga"
},
{
text: __("640x480"),
selected: true,
mode: "vga"
},
{
text: __("800x600"),
mode: "svga"
},
{
text: __("1024x760"),
mode: "hd"
},
{
text: __("1920×1080"),
mode: "fhd"
}
]
@resoctl.onlistselect = (e) =>
return if @mute
switch e.data.item.data.mode
when "qvga"
@cam_setting.w = 320
@cam_setting.h = 240
when "vga"
@cam_setting.w = 640
@cam_setting.h = 480
when "svga"
@cam_setting.w = 800
@cam_setting.h = 600
when "hd"
@cam_setting.w = 1024
@cam_setting.h = 768
when "fhd"
@cam_setting.w = 1920
@cam_setting.h = 1080
@setCameraSetting()
@qctl.onvaluechange = (e) =>
return if @mute
@cam_setting.quality = e.data
@setCameraSetting()
return @notify __("Antunnel service is not available") unless Antunnel.tunnel
if not @setting.channel
@requestChannel()
else
@openSession()
requestChannel: () ->
@openDialog "PromptDialog", {
title: __("Enter camera channel"),
label: __("Please enter camera channel name")
}
.then (v) =>
@setting.channel = v
@openSession()
menu: () ->
{
text: "__(Option)",
nodes: [
{ text: "__(Camera channel)" }
],
onchildselect: (e) => @requestChannel()
}
openSession: () ->
return unless Antunnel
return unless @setting.channel
@tunnel = Antunnel.tunnel
@sub = new Antunnel.Subscriber(@setting.channel)
@sub.onopen = () =>
console.log("Subscribed to camera channel")
@sub.onerror = (e) =>
@error __("Error: {0}", new TextDecoder("utf-8").decode(e.data)), e
#@sub = undefined
@sub.onctrl = (e) =>
@cam_setting.w = Antunnel.Msg.int_from(e.data,0)
@cam_setting.h = Antunnel.Msg.int_from(e.data,2)
@cam_setting.fps = e.data[4]
@cam_setting.quality = e.data[5]
@mute = true
@qctl.value = @cam_setting.quality
res = "#{@cam_setting.w}x#{@cam_setting.h}"
switch res
when "320x240"
@resoctl.selected = 0
when "640x480"
@resoctl.selected = 1
when "800x600"
@resoctl.selected = 2
when "1024x768"
@resoctl.selected = 3
when "1920x1080"
@resoctl.selected = 4
@fpsctl.selected = @cam_setting.fps/5 -1
@mute = false
@sub.onmessage = (e) =>
jpeg = new JpegImage()
jpeg.parse e.data
context = @player.getContext("2d")
@player.width = jpeg.width
@player.height = jpeg.height
#jpeg.copyToImageData(d)
imgData = context.getImageData(0,0,jpeg.width,jpeg.height)
jpeg.copyToImageData imgData
context.putImageData(imgData, 0, 0)
@sub.onclose = () =>
@sub = undefined
@notify __("Unsubscribed to the camera service")
@quit()
Antunnel.tunnel.subscribe @sub
cleanup: () ->
@sub.close() if @sub
setCameraSetting: () ->
return unless @sub
arr = new Uint8Array(6)
arr.set Antunnel.Msg.bytes_of(@cam_setting.w), 0
arr.set Antunnel.Msg.bytes_of(@cam_setting.h), 2
arr[4] = @cam_setting.fps
arr[5] = @cam_setting.quality
@sub.send Antunnel.Msg.CTRL, arr
RemoteCamera.singleton = true
RemoteCamera.dependencies = [
"pkg://libjpeg/jpg.js"
]
this.OS.register "RemoteCamera", RemoteCamera

11
RemoteCamera/css/main.css Normal file
View File

@ -0,0 +1,11 @@
afx-app-window[data-id="RemoteCamera"] div[data-id="container"]
{
display: block;
overflow: auto;
}
afx-app-window[data-id="RemoteCamera"] div[data-id="container"] canvas
{
display: block;
margin:0 auto;
}

16
RemoteCamera/package.json Normal file
View File

@ -0,0 +1,16 @@
{
"pkgname": "RemoteCamera",
"app":"RemoteCamera",
"name":"Remote Camera",
"description":"Connect to remote camera via Antunnel",
"info":{
"author": "",
"email": ""
},
"version":"0.0.1-a",
"category":"Other",
"iconclass":"fa fa-camera",
"mimes":["none"],
"dependencies":["libjpeg@0.1.1-a", "Antunnel@0.1.8-a"],
"locale": {}
}

View File

@ -0,0 +1,7 @@
{
"name": "RemoteCamera",
"css": ["css/main.css"],
"javascripts": [],
"coffees": ["coffees/main.coffee"],
"copies": ["assets/scheme.html", "package.json", "README.md"]
}