titanium-sdk icon indicating copy to clipboard operation
titanium-sdk copied to clipboard

feat(android): use cameraX

Open m1ga opened this issue 3 years ago • 6 comments

JIRA: https://jira-archive.titaniumsdk.com/AC-6676 Fixes https://github.com/tidev/titanium_mobile/issues/13525

Optional Description:

Will use CameraX on Android when using a camera with an overlay

const win = Ti.UI.createWindow({
	layout: "vertical",
	backgroundColor: "#fff"
});
const buttons = [
	"take picture",
	"take picture (no autoHide)",
	"take picture (flash on)",
	"take picture (flash auto)",
	"take picture (to gallery)",
	"record video ",
	"record video (low resolution)",
	"record video (max duration)",
	"record video (to gallery)",
]

const results = Ti.UI.createView({
	height: Ti.UI.SIZE,
	width: Ti.UI.FILL,
	layout: "horizontal"
})
const img = Ti.UI.createImageView({
	width: "45%",
	height: Ti.UI.SIZE,
	autorotate: true
});
const videoPlayer = Titanium.Media.createVideoPlayer({
	height: 200,
	width: "45%",
	mediaControlStyle: Titanium.Media.VIDEO_CONTROL_DEFAULT,
	scalingMode: Titanium.Media.VIDEO_SCALING_RESIZE_ASPECT
});
results.add([img, videoPlayer])

const overlay = Ti.UI.createView({
	width: Ti.UI.FILL,
	height: Ti.UI.FILL,
	touchEnabled: false
});
const overlayButtons = Ti.UI.createView({
	layout: "vertical",
	bottom: 0,
	height: Ti.UI.SIZE
})
const overlayLabel = Ti.UI.createLabel({
	text: "",
	top: 20
});
overlay.add(overlayLabel);
overlay.add(overlayButtons);

for (var i = 0; i < buttons.length; ++i) {
	var btn = Ti.UI.createButton({
		title: buttons[i],
		idx: i + 1
	});
	btn.addEventListener("click", openCamera);
	win.add(btn);
}
win.add(results);

function openCamera(e) {
	overlayButtons.removeAllChildren();
	var sourceId = e.source.idx;
	var mediaType = Titanium.Media.MEDIA_TYPE_IMAGE;
	var isVideo = (sourceId == "6" || sourceId == "7" || sourceId == "8" || sourceId == "9");
	var btnText = (isVideo) ? "Record video" : "Take picture";
	var isPaused = false;
	var isBack = true;
	var isRecording = false;
	var autohide = true;
	var saveToPhotoGallery = false;
	var videoMaximumDuration = 0;
	var maxResolution = null;

	const btn = Ti.UI.createButton({
		title: btnText,
	});
	const btn2 = Ti.UI.createButton({
		title: "Switch cam",
	});
	overlayButtons.add([btn, btn2]);

	if (isVideo) {
		mediaType = Titanium.Media.MEDIA_TYPE_VIDEO;

		const btn3 = Ti.UI.createButton({
			title: "pause/resume"
		});
		overlayButtons.add(btn3);
		overlayLabel.text = "Recording: stopped";

		btn3.addEventListener("click", function() {
			if (isRecording) {
				if (isPaused) {
					Ti.Media.resumeVideoCapture();
				} else {
					Ti.Media.pauseVideoCapture();
					overlayLabel.text = "Recording: paused";
				}
				isPaused = !isPaused;
			}
		});
	}

	btn.addEventListener("click", function() {
		if (isVideo) {
			if (!isRecording) {
				Ti.Media.startVideoCapture()
				isRecording = true;
				btn.title = "Stop";
			} else {
				Ti.Media.stopVideoCapture()
				isRecording = false;
				btn.title = btnText;
				overlayLabel.text = "Recording: stopped";
			}
		} else {
			Ti.Media.takePicture()
		}
	})
	btn2.addEventListener("click", function() {
		if (isBack) {
			Ti.Media.switchCamera(Titanium.Media.CAMERA_FRONT)
		} else {
			Ti.Media.switchCamera(Titanium.Media.CAMERA_REAR)
		}
		isBack = !isBack;
	})

	var flashMode = Titanium.Media.CAMERA_FLASH_OFF;
	if (sourceId == "3") {
		flashMode = Titanium.Media.CAMERA_FLASH_ON;
	} else if (sourceId == "4") {
		flashMode = Titanium.Media.CAMERA_FLASH_AUTO;
	}

	if (sourceId == "2") {
		autohide = false;
	}

	if (sourceId == "7") {
		maxResolution = Titanium.Media.VIDEO_SD;
	}

	if (sourceId == "8") {
		videoMaximumDuration = 2000;
	}
	if (sourceId == "5" || sourceId == "9") {
		saveToPhotoGallery = true;
	}

	Ti.Media.requestCameraPermissions(function(e) {
		if (e.success) {
			Ti.Media.showCamera({
				autohide: autohide,
				cameraFlashMode: flashMode,
				overlay: overlay,
				mediaTypes: [mediaType],
				zoomEnabled: true,
				saveToPhotoGallery: saveToPhotoGallery,
				maxResolution: maxResolution,
				videoMaximumDuration: videoMaximumDuration,
				recording: function(e) {
					overlayLabel.text = "Recording: running (" + e.duration + "ms)";
				},
				success: function(e) {
					if (e.mediaType == "public.movie") {
						var f = Ti.Filesystem.getFile(Ti.Filesystem.applicationDataDirectory, "tmp.mp4");
						f.write(e.media);
						videoPlayer.url = f.nativePath;
						videoPlayer.play();
					} else {
						img.image = e.media;
						console.log(e.mediaType);
						console.log(e.media.width, e.media.height);
					}
				}
			});
		}
	});
}

win.open();

Features:

  • [x] open camera
  • [x] close camera
  • [x] add overlay
  • [x] switch front/back
  • [x] take picture
  • [x] video support
  • [x] mediatype
  • [x] video quality
  • [x] video max duration
  • [x] flash
  • [x] pinch to zoom (photo & video)
  • [x] minZoomLevel, maxZoomLevel and zoomLevel to set the zoom in code
  • [x] check file destination / saveToGallery
  • [x] clean up code

m1ga avatar Jan 18 '21 11:01 m1ga

Fails
:no_entry_sign: Test reports missing for Android 5.0, Android Main, CLI, iPad, iPhone, MacOS. This indicates that a build failed or the test app crashed
:no_entry_sign:

:microscope: There are library changes, but no changes to the unit tests. That's OK as long as you're refactoring existing code, but will require an admin to merge this PR. Please see README.md#unit-tests for docs on unit testing.

Warnings
:warning:

Commit 05cc839edb945fb0ce0b46b1e31ce177f882841f has a message "merge" giving 2 errors:

  • subject may not be empty
  • type may not be empty
:warning:

Commit 3502f498dbec6625d28474b29edae95267694c59 has a message "max video duration" giving 2 errors:

  • subject may not be empty
  • type may not be empty
:warning:

Commit 2899cea19fa9a1769614ae640e690806198eba4e has a message "version update" giving 2 errors:

  • subject may not be empty
  • type may not be empty
:warning:

Commit 4ba122e18142fc494372db09c2ad1b9e52e8fc32 has a message "version update, permission check" giving 2 errors:

  • subject may not be empty
  • type may not be empty
:warning:

Commit fb833e758401cb0448f97a10c7c51eaf01296bfa has a message "update" giving 2 errors:

  • subject may not be empty
  • type may not be empty
:warning:

Commit 9e50759946640e9f9946559c0fbfec556f2f47cb has a message "remove old files" giving 2 errors:

  • subject may not be empty
  • type may not be empty
:warning:

Commit 9021cadc3265ee2240080aa792bd6709365db44e has a message "update library version" giving 2 errors:

  • subject may not be empty
  • type may not be empty
:warning:

Commit 3427c8e92c51b760dd67b6fab0b0fa8126d5d6ab has a message "bit rate, max resolution, bug fixes" giving 2 errors:

  • subject may not be empty
  • type may not be empty
:warning:

Commit cc0e5d221f5e52ca2352d31be011985b4f0cedb7 has a message "change video camera" giving 2 errors:

  • subject may not be empty
  • type may not be empty
:warning:

Commit be9b5eaee6003cca90c342b30db31395ca3c109b has a message "video start" giving 2 errors:

  • subject may not be empty
  • type may not be empty
:warning:

Commit cdea992513877c121a659ad06fcdf59c8a3f1a46 has a message "save image" giving 2 errors:

  • subject may not be empty
  • type may not be empty
:warning:

Commit ba2d6380b1c9d23f4cd97c39c2d18f8e0b8d2278 has a message "flash" giving 2 errors:

  • subject may not be empty
  • type may not be empty
:warning:

Commit e0f549500527c738fe267abde5ab593cbee49070 has a message "android back, switch lens" giving 2 errors:

  • subject may not be empty
  • type may not be empty
:warning:

Commit 89a9a5e6ed3481eb5e0b22bea43a51670e5637b0 has a message "close camera" giving 2 errors:

  • subject may not be empty
  • type may not be empty
:warning:

Commit 79e21969e43798d674122447a8491d0bd3525544 has a message "camerax overlay" giving 2 errors:

  • subject may not be empty
  • type may not be empty
:warning:

:mag: Can't find junit reports at ./junit.*.xml, skipping generating JUnit Report.

:warning: This PR has milestone set to 10.0.0, but the version defined in package.json is 10.2.0 Please either: - Update the milestone on the PR - Update the version in package.json - Hold the PR to be merged later after a release and version bump on this branch
Messages
:book:

:rotating_light: This PR has one or more commits with warnings/errors for commit messages not matching our configuration. You may want to squash merge this PR and edit the message to match our conventions, or ask the original developer to modify their history.

:book: :tada: Another contribution from our awesome community member, m1ga! Thanks again for helping us make Titanium SDK better. :thumbsup:
:book: :thumbsup: Hey!, You deleted more code than you added. That's awesome!

Generated by :no_entry_sign: dangerJS against cfe1f05f2d71bdbeeb7c85bfc4e61a74a7b01030

build avatar Jan 18 '21 13:01 build

@m1ga Big one. Will you likely finish it or should be close it? I'd be happy to take it if it's stable. But maybe through a module first? Like we did with Ti.WKWebView.

hansemannn avatar Mar 21 '22 21:03 hansemannn

I think we should keep this open. Everything was working fine already just couldn't write the file to the gallery but I guess that is due to the new permissions. The main purpose was to remove the old stuff from the SDK 😄

m1ga avatar Mar 21 '22 22:03 m1ga

great, latest camerax version changed a lot in the video part. Have to rewrite that part. But it looks like you can record without audio permission now :+1:

m1ga avatar Apr 18 '22 11:04 m1ga

I can now:

  • record a video to a file and to the media gallery (and still get the file in Ti)
  • capture an image to file and to the media gallery (and still get the file in Ti)
  • added a recording callback to get some infos (duration, size) during a video recording

now testing and some error feedback

m1ga avatar Apr 18 '22 21:04 m1ga

Ready to test :rocket: I've tested it on a Pixel 4 (Android 12) and a Samsung A5 (Android 8)

https://user-images.githubusercontent.com/4334997/163989774-49326b25-f15b-42b7-aff8-b927935b8f7f.mp4

m1ga avatar Apr 19 '22 11:04 m1ga

added Ti.Media.torch = true/false to switch on the light when the camera is open. That will light up the whole time and not like the flash only when you take the image

m1ga avatar Oct 08 '22 10:10 m1ga

Added output image parameters:

var cameraSizes = Ti.Media.cameraOutputSizes;
for (var i=0; i<cameraSizes.length;++i){
	console.log(cameraSizes[i].cameraType);
	console.log(cameraSizes[i].values);
}

to get the potential sizes.

And you can set them with:

targetImageWidth: 800,
targetImageHeight: 600,

m1ga avatar Nov 25 '22 12:11 m1ga

some new additions that makes working with overlays a lot easier:

  • open callback that contains the size of the preview (e.g. if you use 4:3 it will be wider than your screen)
  • aspectRatio with two constants Ti.Media.ASPECT_RATIO_16_9 and Ti.Media.ASPECT_RATIO_4_3
  • scalingMode with Ti.Media.IMAGE_SCALING_ASPECT_FIT and Ti.Media.IMAGE_SCALING_ASPECT_FILL

imageScreenshot_20230319-161953

having the preview only be FIT (full width) makes it easier to work with overlays:

20230319_162945

m1ga avatar Mar 19 '23 15:03 m1ga

  • verticalAlign with Ti.Media.VERTICAL_ALIGN_BOTTOM (top, bottom, center)

Screenshot_20230319-163636 Screenshot_20230319-163647

m1ga avatar Mar 19 '23 15:03 m1ga

added the old files back. The new cameraX part is used when you set useCameraX: true opening the camera. That way we can merge this quicker as the default is still the old way and you can opt-in to the new way

m1ga avatar May 01 '23 16:05 m1ga

Let's take this in and give it some testing during the next 1-2 releases. If it runs smooth, we can make it default after that 🙏

hansemannn avatar May 01 '23 17:05 hansemannn

Thanks for the feedback! Already adjusted most of it!

to wrap it up inside a module to be able to quickly iterate on it, then include it to the SDK once stable? We did the same with ti.wkwebview back then.

I did so much work here already that I don't want to rip it out and create a module. I think more people test it if we just say "enable useCameraX" and don't have to add a module. It would be different too (no Ti.Media... namespace) so people would need to change code after it's merged into the SDK.

My idea would be: have people test it in the RC/beta phase to see that it is working as a replacement (using the current features, just with the flag enabled) and then we know it is working without a difference.

If the new features have to be changed it will be in a minor bugfix release as they are "new features" that won't break existing apps. There wasn't any demand for those features so they aren't time critical (and they are Android only).

m1ga avatar May 03 '23 20:05 m1ga

Totally fine! Let's merge it and await the feedback!

hansemannn avatar May 03 '23 20:05 hansemannn

awesome! Thanks!

m1ga avatar May 03 '23 20:05 m1ga