Patrick Goode
Patrick Goode

Reputation: 1452

Xam.Plugin.Media 'Too many open files'

I have a Xamarin forms PCL app, using the Xam.Plugin.Media helper. I have a page where the user invokes a camera helper on a button push to take a picture. The bytes from the camera helper return to the page and serve as the source for an image. There is a save button on the page where I basically call the messaging service and save the bytes to PCL SQlite storage. The problem is I get about 3 successful loads of this page, and can take a picture with the camera helper before I get an exception AFTER the image is taken with the camera, but before it returns with the bytes. The exception message is 'Too many open files'. This is for iOS. All relevant code is below. Thanks

Camera Helper:

 public class CameraHelper
{
    private MediaFile file;

    public async Task<byte[]> TakePicture()
    {
        await CrossMedia.Current.Initialize();

        if (!CrossMedia.Current.IsCameraAvailable || !CrossMedia.Current.IsTakePhotoSupported)
        {
            throw new Exception("No camera available");
        }

       using (MediaFile file = await CrossMedia.Current.TakePhotoAsync(new Plugin.Media.Abstractions.StoreCameraMediaOptions
        {
            Name = $"photo{DateTime.Now.ToString("yyyyMMddHHmmss")}.jpg",
            PhotoSize = PhotoSize.Small,
            CompressionQuality = 80,
            AllowCropping = true,
        }))

        {
            if (file == null)
            {
                return null;
            }
            using (System.IO.Stream stream = file.GetStream())
            {
                ImgBytes = new byte[stream.Length];
                await stream.ReadAsync(ImgBytes, 0, Convert.ToInt32(stream.Length));
                file.Dispose();
            }
        }
        return ImgBytes;
    }
}

Take Photo Page:

 <ContentPage.Content>
    <StackLayout VerticalOptions="FillAndExpand" Padding="12,10,12,15">

        <Label x:Name="photoTypeLabel" Text="Take *photo type* Photo" VerticalOptions="Start" />

        <StackLayout Padding="0,30,0,70">

            <ffimageloading:CachedImage x:Name="Image" Grid.Row="0" FadeAnimationEnabled="true"  Aspect="AspectFill"
                                         HeightRequest="200" WidthRequest="125" >
                <ffimageloading:CachedImage.GestureRecognizers>
                    <TapGestureRecognizer Tapped="OnImageTapped" />
                </ffimageloading:CachedImage.GestureRecognizers>
            </ffimageloading:CachedImage>
        </StackLayout>

        <Label Grid.Row="0" Grid.Column="1"
               Text="{ x:Static local:GrialShapesFont.PhotoCamera }"
                Style="{StaticResource FontIcon}"
                HorizontalTextAlignment="Center"
                Opacity="1"
                FontSize="60"
                TextColor="#FF000000"
                VerticalOptions="Center"
                HorizontalOptions="Center">
            <Label.GestureRecognizers>
                <TapGestureRecognizer Tapped="OnCameraTapped" />
            </Label.GestureRecognizers>
        </Label>

        <Button Style="{StaticResource PrimaryActionButtonStyle}" VerticalOptions="End" Text="Save" WidthRequest="{ artina:OnOrientationDouble
                    LandscapePhone=200,
                    LandscapeTablet=400 }" HorizontalOptions="{ artina:OnOrientationLayoutOptions
                    PortraitPhone=Fill,
                    LandscapePhone=Center,
                    PortraitTablet=Fill,
                    LandscapeTablet=Center }" Clicked="saveButtonClicked" />

        <Button  Style="{StaticResource PrimaryActionButtonStyle}" VerticalOptions="End" Text="Cancel" WidthRequest="{ artina:OnOrientationDouble
                    LandscapePhone=200,
                    LandscapeTablet=400 }" HorizontalOptions="{ artina:OnOrientationLayoutOptions
                    PortraitPhone=Fill,
                    LandscapePhone=Center,
                    PortraitTablet=Fill,
                    LandscapeTablet=Center }"  Clicked="cancelButtonClicked" />
    </StackLayout>
</ContentPage.Content>

Take Photo Page Code Behind:

  private async void OnCameraTapped(object sender, EventArgs args)
    {
        CameraHelper cameraHelper = new CameraHelper();

        try
        {
            ImgBytes = await cameraHelper.TakePicture();
            Image.Source = ImageSource.FromStream(() =>
            {
                return new MemoryStream(ImgBytes);
            });
        }
        catch (Exception ex)
        {
            if (ex.Message == "No camera available")
            {
                await DisplayAlert("Error", "No camera available", "Ok");
            }
            else
            {
                await DisplayAlert("Error", "Unable to take picture.", "Ok");
            }
        }
    }

Upvotes: 0

Views: 281

Answers (2)

Rodrigo Shigueaki
Rodrigo Shigueaki

Reputation: 1

I sugest you to see Xamarin Essential camera view. You can implement your own custom camera screen.

https://learn.microsoft.com/pt-br/xamarin/community-toolkit/views/cameraview?source=recommendations

Then you can save the images in your local storage and just save the path in local database, or if you want can save in Base64 string, but i don't recomend.

Upvotes: 0

If I'm reading the code correctly, GetStream opens a new stream to the file every time. I would try wrapping your CrossMedia.Current.TakePhotoAsync call and the stream in using statements just to make sure they get disposed of properly.

public class CameraHelper
{
    public async Task<byte[]> TakePicture()
    {
        await CrossMedia.Current.Initialize();

        if (!CrossMedia.Current.IsCameraAvailable || !CrossMedia.Current.IsTakePhotoSupported)
        {
            throw new Exception("No camera available");
        }

        using ( MediaFile file = await CrossMedia.Current.TakePhotoAsync(new Plugin.Media.Abstractions.StoreCameraMediaOptions
        {
            Name = $"photo{DateTime.Now.ToString("yyyyMMddHHmmss")}.jpg",
            PhotoSize = PhotoSize.Small,
            CompressionQuality = 80,
            AllowCropping = true,

        }) ) {

            if (file == null) {
                return null;
            }
            using (System.IO.Stream stream = file.GetStream()) {
                byte[] ImgBytes;
                ImgBytes = new byte[stream.Length];
                stream.Read(ImgBytes, 0, Convert.ToInt32(stream.Length));
            }
        }
        return ImgBytes;
    }
}

Upvotes: 2

Related Questions