jamesmontemagno / MediaPlugin

Take & Pick Photos and Video Plugin for Xamarin and Windows
MIT License
711 stars 360 forks source link

TakeVideoAsync doesn't save recorded video to the gallery with SaveToAlbum = true; #473

Open Sompatbu opened 6 years ago

Sompatbu commented 6 years ago

I've tried recording video on Xamarin Forms Application and found the problem when I try to picking up the video I've recorded. The video that is just recorded doesn't show in video picker. But if I switch to taking picture and pick it the picture up there's no problem

Bug Information

Version Number of Plugin: 3.1.3 Device Tested On: - Simulator Tested On: Android Emulator run on Oreo Version of VS: Visual Studio for Mac(Community) 7.3.3 Version of Xamarin: 2.5.0.280555 Versions of other things you are using: -

Steps to reproduce the Behavior

  1. Install MediaPlugin to Xamarin Forms application
  2. Handle Button click to record video specifying StoreVideoOptions with SaveToAlbum, Name and Directory properties set
  3. Handle another button click to pick video with PickVideoAsync
  4. Run the application and click on the button to record video.
  5. After finished recorded the video, click the pick video button

Expected Behavior

Application will show the video that's recorded

Actual Behavior

Nothing show on the pick video page

Code snippet

`async void OnCaremaIconClicked(object sender, EventArgs e) { await CrossMedia.Current.Initialize();

        if (!CrossMedia.Current.IsCameraAvailable || !CrossMedia.Current.IsTakeVideoSupported)
        {
            DisplayAlert("No Camera", ":( No camera avaialble.", "OK");
            return;
        }

        var file = await Plugin.Media.CrossMedia.Current.TakeVideoAsync(new Plugin.Media.Abstractions.StoreVideoOptions
        {
            Directory = "DefaultVideos",
            Name = "video.mp4",
            SaveToAlbum = true
        });
        //await Task.Delay(6000);
        if (file == null)
            return;

        await DisplayAlert("Video Recorded", "Location: " + file.AlbumPath, "OK");
        file.Dispose();
        //=====================================Picture==================================

        //if (!CrossMedia.Current.IsCameraAvailable || !CrossMedia.Current.IsTakePhotoSupported)
        //{
        //    DisplayAlert("No Camera", ":( No camera available.", "OK");
        //    return;
        //}

        //var file = await CrossMedia.Current.TakePhotoAsync(new Plugin.Media.Abstractions.StoreCameraMediaOptions
        //{
        //    Directory = "Sample",
        //    Name = "test.jpg",
        //    SaveToAlbum = true
        //});

        //if (file == null)
        //    return;

        //await DisplayAlert("File Location", file.AlbumPath, "OK");

    }

    async void OnVideoPickerIconClicked(object sender, EventArgs e)
    {
        await CrossMedia.Current.Initialize();

        if (!CrossMedia.Current.IsPickVideoSupported)
        {
            DisplayAlert("Videos Not Supported", ":( Permission not granted to videos.", "OK");
            return;
        }
        var file = await Plugin.Media.CrossMedia.Current.PickVideoAsync();

        if (file == null)
            return;

        await DisplayAlert("Video Selected", "Location: " + file.AlbumPath, "OK");
        //file.Dispose();

        //================================PICTURE==============================

        //if (!CrossMedia.Current.IsPickPhotoSupported)
        //{
        //    DisplayAlert("Photos Not Supported", ":( Permission not granted to photos.", "OK");
        //    return;
        //}
        //var file = await Plugin.Media.CrossMedia.Current.PickPhotoAsync(new Plugin.Media.Abstractions.PickMediaOptions
        //{
        //    PhotoSize = Plugin.Media.Abstractions.PhotoSize.Medium,

        //});

        //if (file == null)
        //    return;
        //await DisplayAlert("Video Selected", "Location: " + file.AlbumPath, "OK");
    }`

Screenshots

This screenshots show the AlbumPath of the file. First picture is the AlbumPath of video. Second is the AlbumPath of picture. Both have SaveToAlbum = true; in the StoreVideoOptions/StoreCameraMediaOptions https://imgur.com/a/aAN4U

KhwahishBatra commented 6 years ago

I am also facing the same issue. This functionality is working fine in iOS. In Android, it is saved to the App Memory (/storage/emulated/0/Android/data/com.companyname.AppName/files/Movies/) but While we try to Pick the same Video, we are unable to find this.

acaliaro commented 6 years ago

there are news for this problem?

Sompatbu commented 6 years ago

^ nope they didn't give this issue any attention for now. No feedback from them.

hauteur commented 6 years ago

I am seeing the same issue on iOS, except that there is an exception:

unable to save to album:System.NullReferenceException: Object reference not set to an instance of an object at Plugin.Media.MediaPickerDelegate+d__28.MoveNext () [0x00166] in :0

Here is the code to create the options:

var file = await CrossMedia.Current.TakeVideoAsync(new Plugin.Media.Abstractions.StoreVideoOptions {
        Name = fileName,
        Directory = directoryName,
        DesiredLength = new TimeSpan(0, 0, seconds),
        CompressionQuality = 50,
        Quality = Plugin.Media.Abstractions.VideoQuality.Medium,
        SaveToAlbum = true,
        DefaultCamera = Plugin.Media.Abstractions.CameraDevice.Rear, 
    });

Also, here are my plist entries:

 <key>NSCameraUsageDescription</key>
<string>This app needs access to the camera to take photos.</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>This app needs access to photos.</string>
<key>NSMicrophoneUsageDescription</key>
<string>This app needs access to microphone.</string>
<key>NSPhotoLibraryAddUsageDescription</key>
<string>This app needs access to the photo gallery.</string>
urosmil commented 6 years ago

Hi, any news about this? After recording video is not saved to gallery, that's really important issue to fix... :/

wegascee commented 5 years ago

The issue is simple: StoreVideoOptions is not respected in TakeMediaAsync(). Only TakePhotoAsync() respects the options and uses the MediaScanner intent to save the file into the gallery. See here for details.

Either one would extend this library or you would have to implement it yourself in your app ...

ch4m0n1x101 commented 5 years ago

Hi there, Has anyone had any success remedying this? I notice that the file.AlbumPath is different from taking a photo from when recording video.

@jamesmontemagno I notice that in the file src/Android/MediaImplementation.cs - line 202 (TakePhotoAsync) the option SaveToAlbum is checked and the proceeding code (to line 247) presumably saves to the device album.

However looking at TakeVideoAsync (line 308) there is no check on the SaveToAlbum property.

Will it be as simple as adding line 202 to 247 from TakePhotoAsync to TakeVideoAsync???

ch4m0n1x101 commented 5 years ago

FYI I've added the code from line 202 to 247 of MediaImplementation as a dependency service and it seems to work for saving the Video to Gallery. Only tested on a Samsung S8 though.

jamesmontemagno commented 5 years ago

Please send a PR

Sinkanakagug commented 4 years ago

I am having the issue of Taken Videos not saving to the device on both Android and IOS.

The change that @ledragon101 made does seem to solve the problem for Android.

Wondering if anyone is still not able to save their Taken Videos on IOS also?

magnusblb commented 4 years ago

@Sinkanakagug Using the latest 5.0.0-beta, the SaveToAlbum=true property does seem to store recorded videos to the gallery on iOS (tested on an iPhone XS running iOS 13.3.1). However on a Sony Xperia with Android 8.0.0, this option does not have any effect.

xiufengpeng commented 4 years ago

Just pulled the latest code, but on Android (tested on Samsung S8+ phone and tab S4) still doesn't work, can't save the video from TakeVideoAsync to gallery, it only works in memory, and the videos don't get saved in either Album or internal storage. I could not rebuild the package from source using VS 2019 for Mac like @ledragon101 did, get the UAP 10.0...missing error. Any pointers will be much appreciated

meopoc commented 4 years ago

@xiufengpeng I didn;t rebuild the package. I just took the code from line 202 to 247 of MediaImplementation and created a separate DependencyService then passed the result of TakeVideoAsync to it.

Rebuilding packages a bit beyond me

xiufengpeng commented 4 years ago

@markelliottopoc Thanks for the info. I tried to use your method to save, but got the "part of the path" not found error on Android (Samsung S4 tab, Android 9), can you share your code ? @jamesmontemagno, any plan to fix this? Thanks a lot.

ch4m0n1x101 commented 4 years ago

Interface

namespace xxx.xxx.xxx.xxx
{
    public interface IGalleryService
    {
        void SaveToDevice(MediaFile media, StoreVideoOptions options);
    }
}

Dependency Service:

[assembly: Dependency(typeof(GalleryService))]
namespace xxx.xxx.xxx.xxx
{
    public class GalleryService : IGalleryService
    {
        //private Activity _activity;

        /// <summary>
        /// 
        /// </summary>
        public GalleryService()
        {
            //_activity = CrossCurrentActivity.Current.Activity;
        }

        public void SaveToDevice(MediaFile media, StoreVideoOptions options)
        {
            if (options.SaveToAlbum)
            {
                try
                {
                    //var context = _activity.ApplicationContext;
                    var context = Xamarin.Essentials.Platform.AppContext;

                    // Current Activity or null if not initialized or not started.
                    //var activity = Xamarin.Essentials.Platform.CurrentActivity;

                    var fileName = Path.GetFileName(media.Path);
                    var publicUri = MediaPickerActivity.GetOutputMediaFile(context, options.Directory ?? "temp", fileName, true, true);

                    using (Stream input = File.OpenRead(media.Path))
                    using (Stream output = File.Create(publicUri.Path))
                        input.CopyTo(output);

                    media.AlbumPath = publicUri.Path;

                    var f = new Java.IO.File(publicUri.Path);

                    //MediaStore.Images.Media.InsertImage(context.ContentResolver, f.AbsolutePath, f.Name, null);

                    try
                    {
                        Android.Media.MediaScannerConnection.ScanFile(context, new[] { f.AbsolutePath }, null, context as MediaPickerActivity);

                        ContentValues values = new ContentValues();
                        values.Put(MediaStore.Images.Media.InterfaceConsts.Title, Path.GetFileNameWithoutExtension(f.AbsolutePath));
                        values.Put(MediaStore.Images.Media.InterfaceConsts.Description, string.Empty);
                        values.Put(MediaStore.Images.Media.InterfaceConsts.DateTaken, Java.Lang.JavaSystem.CurrentTimeMillis());
                        values.Put(MediaStore.Images.ImageColumns.BucketId, f.ToString().ToLowerInvariant().GetHashCode());
                        values.Put(MediaStore.Images.ImageColumns.BucketDisplayName, f.Name.ToLowerInvariant());
                        values.Put("_data", f.AbsolutePath);

                        var cr = context.ContentResolver;

                        cr.Insert(MediaStore.Images.Media.ExternalContentUri, values);
                    }
                    catch (Exception ex1)
                    {
                        Console.WriteLine("Unable to save to scan file: " + ex1);
                    }

                    var contentUri = Android.Net.Uri.FromFile(f);
                    var mediaScanIntent = new Intent(Intent.ActionMediaScannerScanFile, contentUri);

                    context.SendBroadcast(mediaScanIntent);
                }
                catch (Exception ex2)
                {
                    Console.WriteLine("Unable to save to gallery: " + ex2);
                }
            }
        }
    }
}

Above in use in Viewmodel:

            await CrossMedia.Current.Initialize().ConfigureAwait(false);

            var isCameraAvailable = CrossMedia.Current.IsCameraAvailable;
            var isTakePhotoSupported = CrossMedia.Current.IsTakePhotoSupported;

            if (!isCameraAvailable || !isTakePhotoSupported) return;

            var videoOptions = new StoreVideoOptions
            {
                DefaultCamera = CameraDevice.Rear,
                SaveToAlbum = true,
                CompressionQuality = 100,
                Quality = (VideoQuality)Enum.Parse(typeof(VideoQuality), Settings.VideoSetting, true)
            };

            //TODO: Try Catch
            using (var file = await CrossMedia.Current.TakeVideoAsync(videoOptions).ConfigureAwait(false))
            {
                if (file == null) return;

                //Check Video Size
                var contentLength = new FileInfo(file.Path).Length;
                var contentLengthString = contentLength.ToString();

                if (contentLength >= (1 << 30)) contentLengthString = $"{contentLength >> 30}Gb";
                else if (contentLength >= (1 << 20)) contentLengthString = $"{contentLength >> 20}Mb";
                else if (contentLength >= (1 << 10)) contentLengthString = $"{contentLength >> 10}Kb";

                var confirm = await _userDialog.ConfirmAsync($"Upload this {contentLengthString} video? Duration will depend on your connection speed. You can amend video quality in Settings to reduce file size", "Confirm", "OK").ConfigureAwait(false);

                //Save To Gallery code for Android Video bug
                if (videoOptions.SaveToAlbum && Device.RuntimePlatform == Device.Android)
                {
                    var galleryService = DependencyService.Get<IGalleryService>();
                    galleryService.SaveToDevice(file, videoOptions);
                }

                if (confirm) await AddImageOrVideoToRfi(rfiDto, file).ConfigureAwait(false);
            }

I use Prism and it's not advisable to use XF Dependency Service:

https://prismlibrary.com/docs/xamarin-forms/Dependency-Service.html#use-the-service

I'll get round to fixing but works for now.

xiufengpeng commented 4 years ago

Thanks @ledragon101 I was able to re-build the package from James Git code by adding the codes to Android platform, but then ran into a runtime error "This functionality is not implemented in the portable version of this assembly. You should reference the NuGet package from your main application project in order to reference the platform-specific implementation.", tried to set the Android and iOS target versions but no luck, maybe James can shed some light on this issue. Regardless, your code works well on Android (Samsung tab s4, android 9).

szepligeti commented 3 years ago

Hi, Could someone please send me a link to a simple sample example? I don't understand these line insertions. Android app should be able to just make a video and put it in my video folder, which I can easily achieve. Thanks in advance if anyone bothers to help me.

Feri