diff --git a/RemoteCamera/README.md b/RemoteCamera/README.md new file mode 100644 index 0000000..a44b9db --- /dev/null +++ b/RemoteCamera/README.md @@ -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 \ No newline at end of file diff --git a/RemoteCamera/assets/scheme.html b/RemoteCamera/assets/scheme.html new file mode 100644 index 0000000..04bc064 --- /dev/null +++ b/RemoteCamera/assets/scheme.html @@ -0,0 +1,21 @@ + + + +
+ + +
+ + +
+ + +
+
+ +
+ +
+ +
+
\ No newline at end of file diff --git a/RemoteCamera/build/debug/README.md b/RemoteCamera/build/debug/README.md new file mode 100644 index 0000000..a44b9db --- /dev/null +++ b/RemoteCamera/build/debug/README.md @@ -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 \ No newline at end of file diff --git a/RemoteCamera/build/debug/main.css b/RemoteCamera/build/debug/main.css new file mode 100644 index 0000000..0f48adf --- /dev/null +++ b/RemoteCamera/build/debug/main.css @@ -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; +} \ No newline at end of file diff --git a/RemoteCamera/build/debug/main.js b/RemoteCamera/build/debug/main.js new file mode 100644 index 0000000..a6d2829 --- /dev/null +++ b/RemoteCamera/build/debug/main.js @@ -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); diff --git a/RemoteCamera/build/debug/package.json b/RemoteCamera/build/debug/package.json new file mode 100644 index 0000000..406327f --- /dev/null +++ b/RemoteCamera/build/debug/package.json @@ -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": {} +} \ No newline at end of file diff --git a/RemoteCamera/build/debug/scheme.html b/RemoteCamera/build/debug/scheme.html new file mode 100644 index 0000000..04bc064 --- /dev/null +++ b/RemoteCamera/build/debug/scheme.html @@ -0,0 +1,21 @@ + + + +
+ + +
+ + +
+ + +
+
+ +
+ +
+ +
+
\ No newline at end of file diff --git a/RemoteCamera/coffees/main.coffee b/RemoteCamera/coffees/main.coffee new file mode 100644 index 0000000..4fcc9ae --- /dev/null +++ b/RemoteCamera/coffees/main.coffee @@ -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 \ No newline at end of file diff --git a/RemoteCamera/css/main.css b/RemoteCamera/css/main.css new file mode 100644 index 0000000..0b1854a --- /dev/null +++ b/RemoteCamera/css/main.css @@ -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; +} \ No newline at end of file diff --git a/RemoteCamera/package.json b/RemoteCamera/package.json new file mode 100644 index 0000000..406327f --- /dev/null +++ b/RemoteCamera/package.json @@ -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": {} +} \ No newline at end of file diff --git a/RemoteCamera/project.json b/RemoteCamera/project.json new file mode 100644 index 0000000..54687c9 --- /dev/null +++ b/RemoteCamera/project.json @@ -0,0 +1,7 @@ +{ + "name": "RemoteCamera", + "css": ["css/main.css"], + "javascripts": [], + "coffees": ["coffees/main.coffee"], + "copies": ["assets/scheme.html", "package.json", "README.md"] +} \ No newline at end of file