I am developing an app that must run on iOS, Android, and W32/64 using Delphi FireMonkey on RAD Studio XE5. I am trying to access the video camera in order to scan in barcodes.
I've got the example VideoCaptureHD app running on W32 using a USB webcam, but on Android the app just shows the background color, and no image from the camera. I've added a breakpoint to the SampleBufferReady method, but it never gets called.
Also, the capture button is enabled indicating that the camera device is recognized at some level.
//---------------------------------------------------------------------------
// This software is Copyright (c) 2012 Embarcadero Technologies, Inc.
// You may only use this software if you are an authorized licensee
// of Delphi, C++Builder or RAD Studio (Embarcadero Products).
// This software is considered a Redistributable as defined under
// the software license agreement that comes with the Embarcadero Products
// and is subject to that software license agreement.
//---------------------------------------------------------------------------
unit CaptureForm;
interface
uses
System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants,
FMX.Types, FMX.Controls, FMX.Forms, FMX.Dialogs, FMX.Objects, FMX.Media, FMX.StdCtrls,
FMX.Layouts, FMX.ListBox;
type
TForm1 = class(TForm)
Image1: TImage;
CaptureButton: TSpeedButton;
Layout1: TLayout;
SaveDialog1: TSaveDialog;
Ellipse1: TEllipse;
StopButton: TSpeedButton;
procedure FormCreate(Sender: TObject);
procedure CaptureButtonClick(Sender: TObject);
procedure StopButtonClick(Sender: TObject);
procedure FormDestroy(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
VideoCamera: TVideoCaptureDevice;
procedure SampleBufferReady(Sender: TObject; const ATime: TMediaTime);
end;
var
Form1: TForm1;
implementation
{$R *.fmx}
procedure TForm1.FormCreate(Sender: TObject);
begin
VideoCamera := TCaptureDeviceManager.Current.DefaultVideoCaptureDevice;
if VideoCamera <> nil then
begin
Ellipse1.AnimateFloat('Opacity', 1, 1.0);
VideoCamera.OnSampleBufferReady := SampleBufferReady;
VideoCamera.StartCapture;
end
else
begin
CaptureButton.Enabled := False;
Caption := 'Video capture devices not available.';
end;
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
if VideoCamera <> nil then
VideoCamera.StopCapture;
end;
procedure TForm1.SampleBufferReady(Sender: TObject; const ATime: TMediaTime);
begin
VideoCamera.SampleBufferToBitmap(Image1.Bitmap, True);
end;
procedure TForm1.StopButtonClick(Sender: TObject);
begin
if VideoCamera <> nil then
begin
if VideoCamera.State = TCaptureDeviceState.Capturing then
begin
Ellipse1.AnimateFloat('Opacity', 0, 1.0);
StopButton.Text := 'Capture';
VideoCamera.StopCapture;
end
else
begin
Ellipse1.AnimateFloat('Opacity', 1, 1.0);
StopButton.Text := 'Stop';
VideoCamera.StartCapture
end;
end;
end;
procedure TForm1.CaptureButtonClick(Sender: TObject);
begin
if SaveDialog1.Execute then
Image1.Bitmap.SaveToFile(SaveDialog1.FileName);
end;
end.
I can't find bug or mistake in your application.
You should also check if in Project->Options->UsesPermisions->Android->Camera if is True.
I did similar application and it works (public variables the same as yours):
procedure TForm1.Button1Click(Sender: TObject); //starts capturing on click
begin
VideoCamera.StartCapture;
end;
procedure TForm1.Button2Click(Sender: TObject); //stops capturing on click
begin
VideoCamera.StopCapture;
end;
procedure TForm1.Button3Click(Sender: TObject); //create camera on click
begin
VideoCamera := TCaptureDeviceManager.Current.DefaultVideoCaptureDevice;
if VideoCamera <> nil then
begin
VideoCamera.OnSampleBufferReady := SampleBufferReady;
end
else
begin
ShowMessage('Video capture devices not available.');
end;
end;
procedure TForm1.SampleBufferReady(Sender: TObject; const ATime: TMediaTime); // copy to bitmap works perfect
begin
VideoCamera.SampleBufferToBitmap(Image1.Bitmap, True);
end;
Related
I'm using Delphi 10.3 Community Edition to write a simple Android app and trying to invoke native device camera, but getting an error instead.
I'm following the official Delphi guide:
On the Form Designer, select the button (for taking a photo).
In the Object Inspector, select the drop-down list for the Action property.
Select New Standard Action | Media Library | TTakePhotoFromCameraAction:
On the Events tab, expand the Action node, and then double-click the OnDidFinishTaking event.
Add the following code to the OnDidFinishTaking event handler:
procedure TForm1.TakePhotoFromCameraAction1DidFinishTaking(Image: TBitmap);
begin
Image1.Bitmap.Assign(Image);
end;
This code assigns a picture taken from the mobile device camera to the Bitmap property of the TImage component.
I have verified that Project | Options | Uses Permissions - Camera setting is set to true. I'm requesting required permission on the app start as well. There's no difference between running in Debug or Release.
However there is a problem. When clicking the button I get the following error message:
java.lang.NullPointerException: Attempt to invoke virtual method 'android.content.res.XmlResourceParser android.content.pm.PackageItemInfo.loadXmlMetaData(android.content.pm.PackageManager, java.lang.String)' on a null object reference.
Here's the code I've written for the simplest test app:
unit Unit1;
interface
uses
System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants,
FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs, System.Permissions,
FMX.StdCtrls, FMX.MediaLibrary, FMX.Platform, System.Messaging, FMX.Objects,
System.Actions, FMX.ActnList, FMX.StdActns, FMX.MediaLibrary.Actions,
FMX.Controls.Presentation;
type
TForm1 = class(TForm)
Button1: TButton;
ActionList1: TActionList;
TakePhotoFromCameraAction1: TTakePhotoFromCameraAction;
procedure FormCreate(Sender: TObject);
private
procedure PermissionRequestResult(Sender: TObject; const APermissions: TArray<string>; const AGrantResults: TArray<TPermissionStatus>);
procedure DisplayRationale(Sender: TObject; const APermissions: TArray<string>; const APostRationaleProc: TProc);
end;
var
Form1: TForm1;
implementation
uses
{$IFDEF ANDROID}
Androidapi.Helpers,
Androidapi.JNI.JavaTypes,
Androidapi.JNI.Os,
{$ENDIF}
FMX.DialogService;
{$R *.fmx}
procedure TForm1.PermissionRequestResult(Sender: TObject; const APermissions: TArray<string>; const AGrantResults: TArray<TPermissionStatus>);
begin
// 3 permission involved
if (Length(AGrantResults) = 3)
and (AGrantResults[0] = TPermissionStatus.Granted)
and (AGrantResults[1] = TPermissionStatus.Granted)
and (AGrantResults[2] = TPermissionStatus.Granted) then
else
ShowMessage('Required permission has not been granted') ;
end;
procedure TForm1.DisplayRationale(Sender: TObject; const APermissions: TArray<string>; const APostRationaleProc: TProc);
begin
TDialogService.ShowMessage('Need to access the camera',
procedure(const AResult: TModalResult)
begin
APostRationaleProc;
end);
end;
procedure TForm1.FormCreate(Sender: TObject);
var
permCam, permRead, permWrite: string;
begin
// Request permissions
permCam := JStringToString(TJManifest_permission.JavaClass.CAMERA);
permRead := JStringToString(TJManifest_permission.JavaClass.READ_EXTERNAL_STORAGE);
permWrite := JStringToString(TJManifest_permission.JavaClass.WRITE_EXTERNAL_STORAGE);
PermissionsService.RequestPermissions([permCam, permRead, permWrite], PermissionRequestResult, DisplayRationale);
end;
end.
How to make the native camera TTakePhotoFromCameraAction to work?
Check that the Project Options > Entitlements List > Secure File Sharing option is set to true.
For anyone porting a project from an older version to 10.3, make sure that your AndroidManifest.xml includes the <%provider%> tag right above the <%application-meta-data%> tag.
A lot of comments I've found on other sides and here are suggesting that this file can be found here:
C:\Users\(yourusername)\AppData\Roaming\Embarcadero\BDS\20.0\AndroidManifest.xml
But if that doesn't work then you likely already have an AndroidManifest.template.xml file in your source directory. If that is the case then the compiler will use this template file and ignore the file in the AppData folder!
I was having issues where Android 9 would work but some devices running Android 10 wouldn't. I needed to do the steps in the answers listed above but mine was still not working until I added:
android:requestLegacyExternalStorage="true"
to the application section of my AndroidManifest.template.xml
Does anyone know how to dim the screen on Delphi Firemonkey for Android? I've been googling around and searching but could only find how to remove the auto dim for android by acquiring a Wakelock (Delphi XE5 Android. How to use PowerManager.WakeLock?).
At best I'd like to achieve two states:
Evening = 20% brightness
Daytime = 100% brightness
I am using Delphi 10.1 Berlin.
Thank you for your help.
Taking this Stack Overflow question, which has the Java solution, as a guide I rustled up this helper unit that should work in Delphi versions from around XE8 to 10.1 Berlin, which seems to do the trick:
unit ScreenBrightnessU;
interface
function GetScreenBrightness: Byte;
procedure SetScreenBrightness(Brightness: Byte);
implementation
uses
MiscU,
FMX.Helpers.Android,
{$IF RTLVersion >= 31}
FMX.DialogService,
{$ELSE}
FMX.Dialogs,
{$ENDIF}
System.UITypes,
System.SysUtils,
Androidapi.Helpers,
Androidapi.JNI.App,
Androidapi.JNI.Provider,
Androidapi.JNI.GraphicsContentViewText;
function GetScreenBrightness: Byte;
var
Resolver: JContentResolver;
begin
Resolver :=
{$IF RTLVersion >= 30}
TAndroidHelper.ContentResolver;
{$ELSE}
SharedActivityContext.getContentResolver;
{$ENDIF}
Result := TJSettings_System.JavaClass.getInt(
Resolver,
TJSettings_System.JavaClass.SCREEN_BRIGHTNESS);
end;
procedure SetScreenBrightness(Brightness: Byte);
var
Resolver: JContentResolver;
AttainedBrightness: Single;
LayoutParams: JWindowManager_LayoutParams;
Window: JWindow;
begin
if not HasPermission('android.permission.WRITE_SETTINGS') then
{$IF RTLVersion >= 31}
TDialogService.MessageDialog('App does not have the WRITE_SETTINGS permission', TMsgDlgType.mtError, [TMsgDlgBtn.mbCancel], TMsgDlgBtn.mbCancel, 0, nil)
{$ELSE}
MessageDlg('App does not have the WRITE_SETTINGS permission', TMsgDlgType.mtError, [TMsgDlgBtn.mbCancel], 0)
{$ENDIF}
else
begin
{$IF RTLVersion >= 30}
Resolver := TAndroidHelper.ContentResolver;
{$ELSE}
Resolver := SharedActivityContext.getContentResolver;
{$ENDIF}
// This will set the manual mode (set the automatic mode off)
TJSettings_System.JavaClass.putInt(
Resolver,
TJSettings_System.JavaClass.SCREEN_BRIGHTNESS_MODE,
TJSettings_System.JavaClass.SCREEN_BRIGHTNESS_MODE_MANUAL);
// This will set the required brightness
TJSettings_System.JavaClass.putInt(
Resolver,
TJSettings_System.JavaClass.SCREEN_BRIGHTNESS,
Brightness);
try
AttainedBrightness := GetScreenBrightness;
CallInUIThread(
procedure
begin
{$IF RTLVersion >= 30}
Window := TAndroidHelper.Activity.getWindow;
{$ELSE}
Window := SharedActivity.getWindow;
{$ENDIF}
LayoutParams := Window.getAttributes;
LayoutParams.screenBrightness := AttainedBrightness / 255;
Window.setAttributes(LayoutParams);
end);
except
// ONOES!!!!
// <sweeps issue under the carpet>
end;
end;
end;
end.
You'll note that the code does permission checking via the helper unit below. This is not vital so long as you ensure you have the WRITE_SETTINGS permission set in your project for all the Android build configurations.
unit MiscU;
interface
function HasPermission(const Permission: string): Boolean;
implementation
uses
FMX.Helpers.Android,
Androidapi.Helpers,
Androidapi.JNI.JavaTypes,
Androidapi.JNI.GraphicsContentViewText;
function HasPermission(const Permission: string): Boolean;
begin
//Permissions listed at http://d.android.com/reference/android/Manifest.permission.html
{$IF RTLVersion >= 30}
Result := TAndroidHelper.Context.checkCallingOrSelfPermission(
{$ELSE}
Result := SharedActivityContext.checkCallingOrSelfPermission(
{$ENDIF}
StringToJString(Permission)) =
TJPackageManager.JavaClass.PERMISSION_GRANTED
end;
end.
I'm using Delphi 10.1 Berlin for developing android mobile application. And in that I need to check if the location sensor is not switched ON, then I need to redirect to the Location sensor Settings in android mobile. How can I able to implement using Delphi? I have seen example using JAVA but not found for Delphi. And Thanks in Advance.
You could try some code like the following. Here is a helper unit:
unit LocationU;
interface
function IsGPSProviderEnabled: Boolean;
function IsNetworkProviderEnabled: Boolean;
procedure LaunchLocationSettings;
implementation
uses
System.SysUtils,
Androidapi.Helpers,
Androidapi.JNI.JavaTypes,
Androidapi.JNI.GraphicsContentViewText,
Androidapi.JNI.Location,
Androidapi.JNI.Provider;
function IsProviderEnabled(const Provider: JString): Boolean;
var
LocationManagerObj: JObject;
LocationManager: JLocationManager;
begin
LocationManagerObj := TAndroidHelper.Context.getSystemService(
TJContext.JavaClass.LOCATION_SERVICE);
LocationManager := TJLocationManager.Wrap(LocationManagerObj);
Result := LocationManager.isProviderEnabled(Provider);
end;
function IsGPSProviderEnabled: Boolean;
begin
Result := IsProviderEnabled(TJLocationManager.JavaClass.GPS_PROVIDER);
end;
function IsNetworkProviderEnabled: Boolean;
begin
Result := IsProviderEnabled(TJLocationManager.JavaClass.NETWORK_PROVIDER);
end;
procedure LaunchLocationSettings;
begin
TAndroidHelper.Activity.startActivity(
TJIntent.JavaClass.init(TJSettings.JavaClass.ACTION_LOCATION_SOURCE_SETTINGS));
end;
end.
and here is some code that calls it:
uses
FMX.Helpers.Android,
Androidapi.Helpers,
Androidapi.JNI.Widget,
LocationU;
procedure TMainForm.FormCreate(Sender: TObject);
begin
if not IsGPSProviderEnabled and not IsNetworkProviderEnabled then
begin
CallInUiThread(
procedure
begin
TJToast.JavaClass.makeText(
TAndroidHelper.Context,
StrToJCharSequence('Location services not enabled - launching settings'),
TJToast.JavaClass.LENGTH_SHORT).show
end);
LaunchLocationSettings;
end;
end;
I am porting an older project (app for iOS ans Android) for a customer from an older AppMethod version to the newest RAD Studio version (10.0 Berlin). MessageDlg and similar were used often in the project but this does not work anymore. The apps shows a message, that modal dialogs are not supported on Android. I know and understand why this is so for the Android platform (so please don't mark this question as a dublicate by referencing to an explanation - I'm asking for something else!).
Firemonkey allows to set an anonymous function to be executed asynchronously after the user taps on a button in the dialog box or when it is closed. However, there is often code which depends on users decision or code which must be executed after the dialog box, regardless of which button the user taps on. For example, a dialog box must ask the user for his decision in the middle of an ongoing operation. Further operations then depend on the users decision (which might also include to stop further code execution in the current application!). Since RAD Studio does not support blocking dialogs on Android, I seem to be forced to break up my code - from one function to multiple functions (worse readability...). This gets even more complicated when there are nested function calls with possible modal dialog boxes, which require user interaction.
Is there still a way to emulate blocking dialogs somehow? It doesn't has to be perfect. I just don't want to rewrite a lot of code and outsource even small code fragments into many separate functions, everwhere where user interaction is required.
OK, I think the following "dirty" solution might be sufficient for me:
unit Unit1;
interface
uses
System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants,
FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs, FMX.DialogService,
FMX.Controls.Presentation, FMX.StdCtrls;
type
TForm1 = class(TForm)
Button1: TButton;
procedure Button1Click(Sender: TObject);
private
{ Private-Deklarationen }
public
{ Public-Deklarationen }
end;
var
Form1: TForm1;
blockDlg: array[0..99] of Boolean;
blockDlgIdx: Integer = 0;
implementation
{$R *.fmx}
procedure NShowMessageAsync(AMessage: String);
begin
TDialogService.ShowMessage(AMessage);
end;
procedure NShowMessageSync(AMessage: String);
var
locBlockIdx: Integer;
begin
if blockDlgIdx = Length(blockDlg)-1 then blockDlgIdx := 0 else Inc(blockDlgIdx);
locBlockIdx := blockDlgIdx;
blockDlg[locBlockIdx] := true;
TDialogService.ShowMessage(AMessage,
procedure(const AResult: TModalResult)
begin
blockDlg[locBlockIdx] := false;
end);
while blockDlg[locBlockIdx] do begin
Sleep(40);
Application.ProcessMessages();
end;
end;
procedure NMessageDialogAsync(const AMessage: string; const ADialogType: TMsgDlgType;
const AButtons: TMsgDlgButtons; const ADefaultButton: TMsgDlgBtn;
const AHelpCtx: LongInt);
begin
TDialogService.MessageDialog(AMessage, ADialogType, AButtons, ADefaultButton,
AHelpCtx, procedure(const AResult: TModalResult) begin end);
end;
function NMessageDialogSync(const AMessage: string; const ADialogType: TMsgDlgType;
const AButtons: TMsgDlgButtons; const ADefaultButton: TMsgDlgBtn;
const AHelpCtx: LongInt): TModalResult;
var
locBlockIdx: Integer;
LModalResult: TModalResult;
begin
Result := -1;
if blockDlgIdx = Length(blockDlg)-1 then blockDlgIdx := 0 else Inc(blockDlgIdx);
locBlockIdx := blockDlgIdx;
blockDlg[locBlockIdx] := true;
TDialogService.MessageDialog(AMessage, ADialogType, AButtons, ADefaultButton, AHelpCtx,
procedure(const AResult: TModalResult)
begin
LModalResult := AResult;
blockDlg[locBlockIdx] := false;
end);
while blockDlg[locBlockIdx] do begin
Sleep(40);
Application.ProcessMessages();
end;
Result := LModalResult;
end;
procedure TForm1.Button1Click(Sender: TObject);
var
mr: TModalResult;
begin
mr := NMessageDialogSync('1',
System.UITypes.TMsgDlgType.mtInformation,
[System.UITypes.TMsgDlgBtn.mbYes, System.UITypes.TMsgDlgBtn.mbNo, System.UITypes.TMsgDlgBtn.mbCancel],
System.UITypes.TMsgDlgBtn.mbYes,
0);
NShowMessageSync(IntToStr(mr));
mr := NMessageDialogSync('2',
System.UITypes.TMsgDlgType.mtWarning,
[System.UITypes.TMsgDlgBtn.mbYesToAll, System.UITypes.TMsgDlgBtn.mbAbort],
System.UITypes.TMsgDlgBtn.mbAbort,
0);
NShowMessageSync(IntToStr(mr));
mr := NMessageDialogSync('3',
System.UITypes.TMsgDlgType.mtInformation,
[System.UITypes.TMsgDlgBtn.mbIgnore, System.UITypes.TMsgDlgBtn.mbAll, System.UITypes.TMsgDlgBtn.mbHelp, System.UITypes.TMsgDlgBtn.mbClose],
System.UITypes.TMsgDlgBtn.mbClose,
0);
NShowMessageSync(IntToStr(mr));
Form1.Fill.Kind := TBrushKind.Solid;
if Form1.Fill.Color = TAlphaColors.Red then
Form1.Fill.Color := TAlphaColors.Blue
else
Form1.Fill.Color := TAlphaColors.Red;
end;
end.
Not very clean, but at least I can use this for a while.
Has anyone been able to take pictures from camera on Android from within the app written in Delphi Firemonkey XE5? How about the video capture?
This is believed to be either a bug in a framework or just something with missing documentation about it.
Can anyone tell why the code bellow doesn't work / retrieve any image from a camera on Android?
Dropped a TCameraComponent on a form, and a TImage component as well, and nothing happens.
procedure TCameraComponentForm.OnCreate(Sender: TObject);
begin
CameraComponent1.Kind := FMX.Media.TCameraKind.ckFrontCamera;
CameraComponent1.FlashMode := FMX.Media.TFlashMode.fmFlashOff;
CameraComponent1.Active := True;
end;
procedure TCameraComponentForm.CameraComponent1SampleBufferReady(
Sender: TObject; const ATime: Int64);
begin
CameraComponent1.SampleBufferToBitmap(Image1.Bitmap, True);
Image1.Width := Image1.Bitmap.Width;
Image1.Height := Image1.Bitmap.Height;
end;
Permissions are set correctly.
This code works fine:
procedure TfrmPrincipal.SampleBufferSync;
begin
cmcPrincipal.SampleBufferToBitmap(imgFoto.Bitmap, true);
end;
procedure TfrmPrincipal.cmcPrincipalSampleBufferReady(Sender: TObject;
const ATime: Int64);
begin
TThread.Synchronize(TThread.CurrentThread, SampleBufferSync);
// CameraComponent1.SampleBufferToBitmap(imgFoto.Bitmap, True);
// imgFoto.Width := imgFoto.Bitmap.Width;
// imgFoto.Height := imgFoto.Bitmap.Height;
end;
procedure TfrmPrincipal.FormShow(Sender: TObject);
begin
cmcPrincipal.Kind := FMX.Media.TCameraKind.ckBackCamera;
try
cmcPrincipal.FlashMode := FMX.Media.TFlashMode.fmFlashOff;
except
end;
cmcPrincipal.Active := True;
end;