Bläddra i källkod

Added experimental camera module

TC pushbot 5 4 år sedan
förälder
incheckning
f652eb36d3

+ 1 - 1
AGI Documentation.md

@@ -440,7 +440,7 @@ if (!requirelib("filelib")){
 	filelib.isDir("user:/Desktop/NewFolder/");
 	filelib.md5("user:/Desktop/test.jpg");
 	filelib.mkdir("user/Desktop/NewFolder");	
-	filelib.mtime("user:/Desktop/test.jpg"); 							//Get modification time, return unix timestamp
+	filelib.mtime("user:/Desktop/test.jpg", true); 							//Get modification time, return unix timestamp. Set the 2nd paramter to false for human readble format
 	filelib.rname("user:/Deskop"); 										//Get Rootname, return "User"
 ```
 

+ 46 - 0
web/Camera/backend/loadLatestPhoto.js

@@ -0,0 +1,46 @@
+/*
+
+    This script load the latest image from the save directory
+    Require paramter: savetarget
+*/
+
+requirelib('filelib');
+
+
+
+function getLatestPhotoFilename(){
+    if (savetarget == ""){
+        sendJSONResp(JSON.stringify({
+            error: "savetarget not defined"
+        }));
+        return
+    }
+
+    if (!filelib.fileExists(savetarget)){
+        sendJSONResp(JSON.stringify({
+            error: "savetarget not exists"
+        }));
+        return
+    }
+
+    if (savetarget.substring(savetarget.length - 1,1) != "/"){
+        savetarget = savetarget + "/";
+    }
+
+    //Save target exists. Glob it
+    var files = filelib.aglob(savetarget + "*.jpg");
+    var latestFileMtime = 0;
+    var latestFilename = "";
+    for (var i = 0; i < files.length; i++){
+        var thisFile = files[i];
+        var thisModTime = filelib.mtime(thisFile, true);
+        if (thisModTime > latestFileMtime){
+            latestFileMtime = thisModTime;
+            latestFilename = thisFile;
+        }
+    }
+
+    sendJSONResp(JSON.stringify(latestFilename));
+}
+
+getLatestPhotoFilename();

+ 11 - 0
web/Camera/backend/readydir.js

@@ -0,0 +1,11 @@
+/*
+    ReadyDir.js
+
+    This script prepare the required folder structure for the camera app
+*/
+
+requirelib("filelib");
+filelib.mkdir(savetarget);
+
+//Return ok
+sendResp("OK");

BIN
web/Camera/img/desktop_icon.png


BIN
web/Camera/img/desktop_icon.psd


BIN
web/Camera/img/function_icon.png


BIN
web/Camera/img/function_icon.psd


BIN
web/Camera/img/module_icon.png


BIN
web/Camera/img/module_icon.psd


BIN
web/Camera/img/outline_photo_camera_black_48dp.png


BIN
web/Camera/img/pwa/128.png


BIN
web/Camera/img/pwa/192.png


BIN
web/Camera/img/pwa/256.png


BIN
web/Camera/img/pwa/512.png


BIN
web/Camera/img/shutter.png


BIN
web/Camera/img/shutter.psd


BIN
web/Camera/img/small_icon.png


BIN
web/Camera/img/small_icon.psd


+ 301 - 0
web/Camera/index.html

@@ -0,0 +1,301 @@
+<!DOCTYPE html>
+<html>
+	<head>
+		<meta charset="UTF-8">
+		<meta name="apple-mobile-web-app-capable" content="yes" />
+		<meta name="viewport" content="user-scalable=no, width=device-width, initial-scale=1, maximum-scale=1"/>
+		<meta name="theme-color" content="#4b75ff">
+		<link rel="stylesheet" href="../script/semantic/semantic.min.css">
+		<script src="../script/jquery.min.js"></script>
+		<script src="../script/semantic/semantic.min.js"></script>
+		<script src="../script/ao_module.js"></script>
+
+		<link rel="manifest" crossorigin="use-credentials" href="manifest.json">
+		<title>Camera</title>
+		<style>
+			body{
+				background-color: #000000;
+			}
+			.previewer{
+				width: 100%;
+				max-height: 100%;
+			}
+
+			#controls{
+				position: fixed;
+				left: 0px;
+				bottom: 0px;
+				width: 100%;
+				padding: 12px;
+				padding-bottom: 20px;
+				
+			}
+
+			#verticalUI .shutterContainer{
+				display: flex;
+				justify-content: space-evenly;
+			}
+
+			#verticalUI .shutter.image{
+				width: 5em;
+			}
+
+			#sidewayUI .shutterContainer{
+				position: fixed;
+				right: 0px;
+				height: 100%;
+				top:0px;
+				width: 5em;
+			}
+			
+			#sidewayUI .shutter.image{
+				position: absolute;
+				right: 20px;
+				width: 5em;
+				top: 50%;
+				-ms-transform: translateY(-50%);
+				transform: translateY(-50%);
+			}
+
+			#shutterCover{
+				position: fixed;
+				top:0px;
+				left: 0px;
+				width: 100%;
+				height: 100%;
+				background-color: black;
+				display:none;
+			}
+
+			#albumn{
+				position: fixed;
+				bottom: 20px;
+				right: 12px;
+				width: 6em;
+			}
+
+			.ablumnpreview{
+				width: 5em;
+			}
+
+			.latestPreview{
+				width: 5em !important;
+				height: 5em !important;
+			}
+
+			.zoombarcontainer{
+				width: 100%;
+				padding-bottom: 8px;
+				padding-left: 20px;
+				padding-right: 20px;
+			}
+
+			.sideway.zoombarcontainer{
+				display: flex;
+				justify-content: space-evenly;
+			}
+
+			#zoombar{
+				width: 100%;
+			}
+
+			#zoombar.sideway{
+				width: 50% !important;
+			}
+		</style>
+	</head>
+	<body>
+		<video id="camera" class="previewer" autoplay></video>
+		<div id="shutterCover"></div>
+		<canvas id="canvas" style="display:none;"></canvas>
+		<div id="albumn">
+			<div class="ablumnpreview">
+				<img class="ui fluid image latestPreview" src="img/module_icon.png"> 
+			</div>
+		</div>
+		<div id="controls">
+			<div class="zoombarcontainer">
+				<input id="zoombar" type="range" min="1" max="100" value="50">
+			</div>
+			<div id="verticalUI">
+				<div class="shutterContainer">
+					<div onclick="takePicture();" ontouchdown="takePicture();">
+						<img class="ui shutter image" src="img/shutter.png">
+					</div>
+				</div>
+			</div>
+			<div id="sidewayUI">
+				<div class="shutterContainer">
+					<div onclick="takePicture();" ontouchdown="takePicture();">
+						<img class="ui shutter image" src="img/shutter.png">
+					</div>
+				</div>
+			</div>
+
+		</div>
+		<script>
+			let isMobile = false;
+			let saveFolder = "user:/Photo/DCIM";
+			if( /Android|webOS|iPhone|iPad|Mac|Macintosh|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent) ) {
+				isMobile = true;
+			}
+
+			$(document).ready(function(){
+				ao_module_agirun("Camera/backend/readydir.js", {savetarget: saveFolder}, function(){
+					//Folder structure ready
+					initcamera();
+				});
+				loadAblumnPreview();
+			});
+
+			$(window).on("resize", function(){
+				handleWindowResize();
+			});	
+
+			handleWindowResize();
+
+			function handleWindowResize(){
+				var width = window.innerWidth;
+				var height = window.innerHeight;
+				if (width > height){
+					//Using the phone sideway
+					$("#verticalUI").hide();
+					$("#sidewayUI").show();
+
+					$(".zoombarcontainer").addClass("sideway");
+					$("#zoombar").addClass("sideway");
+				}else{
+					//Use the phone vertically
+					$("#verticalUI").show();
+					$("#sidewayUI").hide();
+					$(".zoombarcontainer").removeClass("sideway");
+					$("#zoombar").removeClass("sideway");
+				}
+			}
+
+			function loadAblumnPreview(){
+				ao_module_agirun("Camera/backend/loadLatestPhoto.js", {savetarget: saveFolder}, function(data){
+					if (data.error !== undefined){
+						console.log(data.error);
+					}else{
+						$(".latestPreview").attr("src", `../media/?file=${data}`);
+						console.log(data);
+					}
+					
+				});
+			}
+			
+
+			function takePicture(){
+				$("#shutterCover").show(0);
+				setTimeout(function(){
+					$("#shutterCover").hide();
+				}, 500);
+				var canvas = document.getElementById('canvas');     
+				var video = document.getElementById('camera');
+				canvas.width = video.videoWidth;
+				canvas.height = video.videoHeight;
+				canvas.getContext('2d').drawImage(video, 0, 0, video.videoWidth, video.videoHeight);  
+				canvas.toBlob(function(blob){
+					//const img = new Image();
+					//img.src = URL.createObjectURL(blob);
+					//window.open(img.src);
+
+					//Generate a filename for this photo
+					var d = new Date();
+					var filename = "DSC_" + d.getFullYear() + d.getMonth() +  d.getDate() + "_" + d.getHours()  + d.getMinutes() + d.getSeconds() + ".jpg";
+
+					//Upload it to server
+					uploadImageToServer(filename, blob, function(){
+						//Update the image preview
+						loadAblumnPreview();
+					});
+				}, "image/jpeg");
+			}
+
+			//Upload the file to server
+			function uploadFile(file, uploadPath, callback=undefined){
+				let url = '../../system/file_system/upload'
+				let formData = new FormData()
+                let xhr = new XMLHttpRequest()
+                formData.append('file', file);
+                formData.append('path', uploadPath);
+				xhr.open('POST', url, true)
+				xhr.upload.addEventListener("progress", function(e) {
+					var progress = (e.loaded * 100.0 / e.total) || 100;
+					console.log(progress);
+				});
+
+				xhr.addEventListener('readystatechange', function(e) {
+                    if (xhr.readyState == 4 && xhr.status == 200) {
+						//Uplaod process ok
+						var resp = JSON.parse(e.target.response);
+						if (callback != undefined){
+							callback(resp);
+						}
+					}else{
+						//Upload failed
+						if (callback != undefined){
+							callback({
+								error: "File upload failed"
+							});
+						}
+					}
+				});
+				xhr.send(formData);
+			}
+
+			function uploadImageToServer(filename, blob, callback){
+				var imageFile = ao_module_utils.blobToFile(blob, filename, blob.type);
+				uploadFile(imageFile, "user:/Photo/DCIM", callback);
+			}
+
+			function initcamera(){
+				//Define the camera constraints
+				const constraints = {
+					video: {
+						width: { ideal: 2048 },
+						height: { ideal: 1080 }, 
+						zoom: true,
+						facingMode: 'environment'
+					},
+					audio: false
+				};
+
+				const video = document.querySelector("video");
+				navigator.mediaDevices.getUserMedia(constraints).then((stream) => {
+					const [track] = stream.getVideoTracks();
+					try{
+						const capabilities = track.getCapabilities();
+						const settings = track.getSettings();
+						console.log(track, settings);
+						video.srcObject = stream;
+
+						//Check if zoom exists. If yes, allow zoom
+						if (!('zoom' in settings)) {
+							$("#zoombar").hide();
+						}else{
+							var input = $("#zoombar")[0];
+							
+							input.min = capabilities.zoom.min;
+							input.max = capabilities.zoom.max;
+							input.step = capabilities.zoom.step;
+							input.value = settings.zoom;
+							input.oninput = function(event) {
+								track.applyConstraints({
+									advanced: [ {zoom: event.target.value} ]
+								});
+							}
+						}
+					}catch(ex){
+						//This video input support nothing
+						$("#zoombar").hide();
+						video.srcObject = stream;
+					}
+				
+				});
+			}
+			
+		</script>
+	</body>
+</html>	

+ 22 - 0
web/Camera/init.agi

@@ -0,0 +1,22 @@
+/*
+	Camera App Register Script
+*/
+
+//Setup the module information
+var moduleLaunchInfo = {
+    Name: "Camera",
+	Desc: "The camera app that will never run out of storage",
+	Group: "Media",
+	IconPath: "Camera/img/module_icon.png",
+	Version: "1.0",
+	StartDir: "Camera/index.html",
+	SupportFW: true,
+	LaunchFWDir: "Camera/index.html",
+	SupportEmb: false,
+	InitFWSize: [475, 700],
+	SupportedExt: []
+}
+
+
+//Register the module
+registerModule(JSON.stringify(moduleLaunchInfo));

+ 26 - 0
web/Camera/manifest.json

@@ -0,0 +1,26 @@
+{
+  "name": "Camera",
+  "short_name": "Camera",
+  "icons": [{
+    "src": "img/pwa/128.png",
+      "sizes": "128x128",
+      "type": "image/png"
+    },{
+      "src": "img/pwa/192.png",
+      "sizes": "192x192",
+      "type": "image/png"
+    }, {
+      "src": "img/pwa/256.png",
+      "sizes": "256x256",
+      "type": "image/png"
+    }, {
+      "src": "img/pwa/512.png",
+      "sizes": "512x512",
+      "type": "image/png"
+    }],
+  "start_url": "index.html",
+  "display": "fullscreen",
+  "scope": "./",
+  "background_color": "#2a2d2d",
+  "theme_color": "#3b3632"
+}

+ 1 - 1
web/SystemAO/file_system/file_explorer.html

@@ -4272,7 +4272,7 @@
 
                 xhr.open('POST', url, true)
                 xhr.upload.addEventListener("progress", function(e) {
-                    progress = (e.loaded * 100.0 / e.total) || 100;
+                    var progress = (e.loaded * 100.0 / e.total) || 100;
                     $(".uploadTask").each(function(){
                         if ($(this).attr("taskID") == taskUUID){
                             //Update this progress bar

+ 3 - 3
web/Video/index.html

@@ -86,10 +86,10 @@
                         Small
                     </button>
                  -->
-                <button class="ui tiny button" onclick="setScreenSize('70%');">
+                <button class="ui tiny button" onclick="setScreenSize('70%'); previousScreenSize='70%';">
                     Normal
                 </button>
-                <button class="ui tiny button" onclick="setScreenSize('100%');">
+                <button class="ui tiny button" onclick="setScreenSize('100%');previousScreenSize='100%';">
                     Full Width
                 </button>
                 <div class="ui toggle right floated checkbox">
@@ -190,7 +190,7 @@
 
                 //Handle full screen
                 dp.on('fullscreen', function() {
-                    previousScreenSize = $("#dplayer").css("width");
+                    //previousScreenSize = $("#dplayer").css("width");
                     setScreenSize("100%");
                 });