123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988 |
- using System;
- using System.Globalization;
- using System.IO;
- using UnityEngine;
- using System.Threading.Tasks;
- using UnityEngine.Networking;
- #if UNITY_ANDROID || UNITY_IOS
- using NativeGalleryNamespace;
- #endif
- using Object = UnityEngine.Object;
-
- public static class NativeGallery
- {
- public struct ImageProperties
- {
- public readonly int width;
- public readonly int height;
- public readonly string mimeType;
- public readonly ImageOrientation orientation;
-
- public ImageProperties( int width, int height, string mimeType, ImageOrientation orientation )
- {
- this.width = width;
- this.height = height;
- this.mimeType = mimeType;
- this.orientation = orientation;
- }
- }
-
- public struct VideoProperties
- {
- public readonly int width;
- public readonly int height;
- public readonly long duration;
- public readonly float rotation;
-
- public VideoProperties( int width, int height, long duration, float rotation )
- {
- this.width = width;
- this.height = height;
- this.duration = duration;
- this.rotation = rotation;
- }
- }
-
- public enum PermissionType { Read = 0, Write = 1 };
- public enum Permission { Denied = 0, Granted = 1, ShouldAsk = 2 };
-
- [Flags]
- public enum MediaType { Image = 1, Video = 2, Audio = 4 };
-
- // EXIF orientation: http://sylvana.net/jpegcrop/exif_orientation.html (indices are reordered)
- public enum ImageOrientation { Unknown = -1, Normal = 0, Rotate90 = 1, Rotate180 = 2, Rotate270 = 3, FlipHorizontal = 4, Transpose = 5, FlipVertical = 6, Transverse = 7 };
-
- public delegate void PermissionCallback( Permission permission );
- public delegate void MediaSaveCallback( bool success, string path );
- public delegate void MediaPickCallback( string path );
- public delegate void MediaPickMultipleCallback( string[] paths );
-
- #region Platform Specific Elements
- #if !UNITY_EDITOR && UNITY_ANDROID
- private static AndroidJavaClass m_ajc = null;
- private static AndroidJavaClass AJC
- {
- get
- {
- if( m_ajc == null )
- m_ajc = new AndroidJavaClass( "com.yasirkula.unity.NativeGallery" );
-
- return m_ajc;
- }
- }
-
- private static AndroidJavaObject m_context = null;
- private static AndroidJavaObject Context
- {
- get
- {
- if( m_context == null )
- {
- using( AndroidJavaObject unityClass = new AndroidJavaClass( "com.unity3d.player.UnityPlayer" ) )
- {
- m_context = unityClass.GetStatic<AndroidJavaObject>( "currentActivity" );
- }
- }
-
- return m_context;
- }
- }
- #elif !UNITY_EDITOR && UNITY_IOS
- [System.Runtime.InteropServices.DllImport( "__Internal" )]
- private static extern int _NativeGallery_CheckPermission( int readPermission, int permissionFreeMode );
-
- [System.Runtime.InteropServices.DllImport( "__Internal" )]
- private static extern void _NativeGallery_RequestPermission( int readPermission, int permissionFreeMode );
-
- [System.Runtime.InteropServices.DllImport( "__Internal" )]
- private static extern void _NativeGallery_ShowLimitedLibraryPicker();
-
- [System.Runtime.InteropServices.DllImport( "__Internal" )]
- private static extern void _NativeGallery_OpenSettings();
-
- [System.Runtime.InteropServices.DllImport( "__Internal" )]
- private static extern int _NativeGallery_CanPickMultipleMedia();
-
- [System.Runtime.InteropServices.DllImport( "__Internal" )]
- private static extern int _NativeGallery_GetMediaTypeFromExtension( string extension );
-
- [System.Runtime.InteropServices.DllImport( "__Internal" )]
- private static extern void _NativeGallery_ImageWriteToAlbum( string path, string album, int permissionFreeMode );
-
- [System.Runtime.InteropServices.DllImport( "__Internal" )]
- private static extern void _NativeGallery_VideoWriteToAlbum( string path, string album, int permissionFreeMode );
-
- [System.Runtime.InteropServices.DllImport( "__Internal" )]
- private static extern void _NativeGallery_PickMedia( string mediaSavePath, int mediaType, int permissionFreeMode, int selectionLimit );
-
- [System.Runtime.InteropServices.DllImport( "__Internal" )]
- private static extern string _NativeGallery_GetImageProperties( string path );
-
- [System.Runtime.InteropServices.DllImport( "__Internal" )]
- private static extern string _NativeGallery_GetVideoProperties( string path );
-
- [System.Runtime.InteropServices.DllImport( "__Internal" )]
- private static extern string _NativeGallery_GetVideoThumbnail( string path, string thumbnailSavePath, int maxSize, double captureTimeInSeconds );
-
- [System.Runtime.InteropServices.DllImport( "__Internal" )]
- private static extern string _NativeGallery_LoadImageAtPath( string path, string temporaryFilePath, int maxSize );
- #endif
-
- #if !UNITY_EDITOR && ( UNITY_ANDROID || UNITY_IOS )
- private static string m_temporaryImagePath = null;
- private static string TemporaryImagePath
- {
- get
- {
- if( m_temporaryImagePath == null )
- {
- m_temporaryImagePath = Path.Combine( Application.temporaryCachePath, "tmpImg" );
- Directory.CreateDirectory( Application.temporaryCachePath );
- }
-
- return m_temporaryImagePath;
- }
- }
-
- private static string m_selectedMediaPath = null;
- private static string SelectedMediaPath
- {
- get
- {
- if( m_selectedMediaPath == null )
- {
- m_selectedMediaPath = Path.Combine( Application.temporaryCachePath, "pickedMedia" );
- Directory.CreateDirectory( Application.temporaryCachePath );
- }
-
- return m_selectedMediaPath;
- }
- }
- #endif
- #endregion
-
- #region Runtime Permissions
- // PermissionFreeMode was initially planned to be a toggleable setting on iOS but it has its own issues when set to false, so its value is forced to true.
- // These issues are:
- // - Presented permission dialog will have a "Select Photos" option on iOS 14+ but clicking it will freeze and eventually crash the app (I'm guessing that
- // this is caused by how permissions are handled synchronously in NativeGallery)
- // - While saving images/videos to Photos, iOS 14+ users would see the "Select Photos" option (which is irrelevant in this context, hence confusing) and
- // the user must grant full Photos access in order to save the image/video to a custom album
- // The only downside of having PermissionFreeMode = true is that, on iOS 14+, images/videos will be saved to the default Photos album rather than the
- // provided custom album
- private const bool PermissionFreeMode = true;
-
- public static bool CheckPermission( PermissionType permissionType, MediaType mediaTypes )
- {
- #if !UNITY_EDITOR && UNITY_ANDROID
- return AJC.CallStatic<int>( "CheckPermission", Context, permissionType == PermissionType.Read, (int) mediaTypes ) == 1;
- #elif !UNITY_EDITOR && UNITY_IOS
- return ProcessPermission( (Permission) _NativeGallery_CheckPermission( permissionType == PermissionType.Read ? 1 : 0, PermissionFreeMode ? 1 : 0 ) ) == Permission.Granted;
- #else
- return true;
- #endif
- }
-
- public static void RequestPermissionAsync( PermissionCallback callback, PermissionType permissionType, MediaType mediaTypes )
- {
- #if !UNITY_EDITOR && UNITY_ANDROID
- NGPermissionCallbackAndroid nativeCallback = new( callback );
- AJC.CallStatic( "RequestPermission", Context, nativeCallback, permissionType == PermissionType.Read, (int) mediaTypes );
- #elif !UNITY_EDITOR && UNITY_IOS
- NGPermissionCallbackiOS.Initialize( ( result ) => callback( ProcessPermission( result ) ) );
- _NativeGallery_RequestPermission( permissionType == PermissionType.Read ? 1 : 0, PermissionFreeMode ? 1 : 0 );
- #else
- callback( Permission.Granted );
- #endif
- }
-
- public static Task<Permission> RequestPermissionAsync( PermissionType permissionType, MediaType mediaTypes )
- {
- TaskCompletionSource<Permission> tcs = new TaskCompletionSource<Permission>();
- RequestPermissionAsync( ( permission ) => tcs.SetResult( permission ), permissionType, mediaTypes );
- return tcs.Task;
- }
-
- private static Permission ProcessPermission( Permission permission )
- {
- // result == 3: LimitedAccess permission on iOS, no need to handle it when PermissionFreeMode is set to true
- return ( PermissionFreeMode && (int) permission == 3 ) ? Permission.Granted : permission;
- }
-
- // This function isn't needed when PermissionFreeMode is set to true
- private static void TryExtendLimitedAccessPermission()
- {
- if( IsMediaPickerBusy() )
- return;
-
- #if !UNITY_EDITOR && UNITY_IOS
- _NativeGallery_ShowLimitedLibraryPicker();
- #endif
- }
-
- public static void OpenSettings()
- {
- #if !UNITY_EDITOR && UNITY_ANDROID
- AJC.CallStatic( "OpenSettings", Context );
- #elif !UNITY_EDITOR && UNITY_IOS
- _NativeGallery_OpenSettings();
- #endif
- }
- #endregion
-
- #region Save Functions
- public static void SaveImageToGallery( byte[] mediaBytes, string album, string filename, MediaSaveCallback callback = null )
- {
- SaveToGallery( mediaBytes, album, filename, MediaType.Image, callback );
- }
-
- public static void SaveImageToGallery( string existingMediaPath, string album, string filename, MediaSaveCallback callback = null )
- {
- SaveToGallery( existingMediaPath, album, filename, MediaType.Image, callback );
- }
-
- public static void SaveImageToGallery( Texture2D image, string album, string filename, MediaSaveCallback callback = null )
- {
- if( image == null )
- throw new ArgumentException( "Parameter 'image' is null!" );
-
- if( filename.EndsWith( ".jpeg", StringComparison.OrdinalIgnoreCase ) || filename.EndsWith( ".jpg", StringComparison.OrdinalIgnoreCase ) )
- SaveToGallery( GetTextureBytes( image, true ), album, filename, MediaType.Image, callback );
- else if( filename.EndsWith( ".png", StringComparison.OrdinalIgnoreCase ) )
- SaveToGallery( GetTextureBytes( image, false ), album, filename, MediaType.Image, callback );
- else
- SaveToGallery( GetTextureBytes( image, false ), album, filename + ".png", MediaType.Image, callback );
- }
-
- public static void SaveVideoToGallery( byte[] mediaBytes, string album, string filename, MediaSaveCallback callback = null )
- {
- SaveToGallery( mediaBytes, album, filename, MediaType.Video, callback );
- }
-
- public static void SaveVideoToGallery( string existingMediaPath, string album, string filename, MediaSaveCallback callback = null )
- {
- SaveToGallery( existingMediaPath, album, filename, MediaType.Video, callback );
- }
-
- private static void SaveAudioToGallery( byte[] mediaBytes, string album, string filename, MediaSaveCallback callback = null )
- {
- SaveToGallery( mediaBytes, album, filename, MediaType.Audio, callback );
- }
-
- private static void SaveAudioToGallery( string existingMediaPath, string album, string filename, MediaSaveCallback callback = null )
- {
- SaveToGallery( existingMediaPath, album, filename, MediaType.Audio, callback );
- }
- #endregion
-
- #region Load Functions
- public static bool CanSelectMultipleFilesFromGallery()
- {
- #if !UNITY_EDITOR && UNITY_ANDROID
- return AJC.CallStatic<bool>( "CanSelectMultipleMedia" );
- #elif !UNITY_EDITOR && UNITY_IOS
- return _NativeGallery_CanPickMultipleMedia() == 1;
- #else
- return false;
- #endif
- }
-
- public static bool CanSelectMultipleMediaTypesFromGallery()
- {
- #if UNITY_EDITOR
- return true;
- #elif UNITY_ANDROID
- return AJC.CallStatic<bool>( "CanSelectMultipleMediaTypes" );
- #elif UNITY_IOS
- return true;
- #else
- return false;
- #endif
- }
-
- public static void GetImageFromGallery( MediaPickCallback callback, string title = "", string mime = "image/*" )
- {
- GetMediaFromGallery( callback, MediaType.Image, mime, title );
- }
-
- public static void GetVideoFromGallery( MediaPickCallback callback, string title = "", string mime = "video/*" )
- {
- GetMediaFromGallery( callback, MediaType.Video, mime, title );
- }
-
- public static void GetAudioFromGallery( MediaPickCallback callback, string title = "", string mime = "audio/*" )
- {
- GetMediaFromGallery( callback, MediaType.Audio, mime, title );
- }
-
- public static void GetMixedMediaFromGallery( MediaPickCallback callback, MediaType mediaTypes, string title = "" )
- {
- GetMediaFromGallery( callback, mediaTypes, "*/*", title );
- }
-
- public static void GetImagesFromGallery( MediaPickMultipleCallback callback, string title = "", string mime = "image/*" )
- {
- GetMultipleMediaFromGallery( callback, MediaType.Image, mime, title );
- }
-
- public static void GetVideosFromGallery( MediaPickMultipleCallback callback, string title = "", string mime = "video/*" )
- {
- GetMultipleMediaFromGallery( callback, MediaType.Video, mime, title );
- }
-
- public static void GetAudiosFromGallery( MediaPickMultipleCallback callback, string title = "", string mime = "audio/*" )
- {
- GetMultipleMediaFromGallery( callback, MediaType.Audio, mime, title );
- }
-
- public static void GetMixedMediasFromGallery( MediaPickMultipleCallback callback, MediaType mediaTypes, string title = "" )
- {
- GetMultipleMediaFromGallery( callback, mediaTypes, "*/*", title );
- }
-
- public static bool IsMediaPickerBusy()
- {
- #if !UNITY_EDITOR && UNITY_IOS
- return NGMediaReceiveCallbackiOS.IsBusy;
- #else
- return false;
- #endif
- }
-
- public static MediaType GetMediaTypeOfFile( string path )
- {
- if( string.IsNullOrEmpty( path ) )
- return (MediaType) 0;
-
- string extension = Path.GetExtension( path );
- if( string.IsNullOrEmpty( extension ) )
- return (MediaType) 0;
-
- if( extension[0] == '.' )
- {
- if( extension.Length == 1 )
- return (MediaType) 0;
-
- extension = extension.Substring( 1 );
- }
-
- #if UNITY_EDITOR
- extension = extension.ToLowerInvariant();
- if( extension == "png" || extension == "jpg" || extension == "jpeg" || extension == "gif" || extension == "bmp" || extension == "tiff" )
- return MediaType.Image;
- else if( extension == "mp4" || extension == "mov" || extension == "wav" || extension == "avi" )
- return MediaType.Video;
- else if( extension == "mp3" || extension == "aac" || extension == "flac" )
- return MediaType.Audio;
-
- return (MediaType) 0;
- #elif UNITY_ANDROID
- string mime = AJC.CallStatic<string>( "GetMimeTypeFromExtension", extension.ToLowerInvariant() );
- if( string.IsNullOrEmpty( mime ) )
- return (MediaType) 0;
- else if( mime.StartsWith( "image/" ) )
- return MediaType.Image;
- else if( mime.StartsWith( "video/" ) )
- return MediaType.Video;
- else if( mime.StartsWith( "audio/" ) )
- return MediaType.Audio;
- else
- return (MediaType) 0;
- #elif UNITY_IOS
- return (MediaType) _NativeGallery_GetMediaTypeFromExtension( extension.ToLowerInvariant() );
- #else
- return (MediaType) 0;
- #endif
- }
- #endregion
-
- #region Internal Functions
- private static void SaveToGallery( byte[] mediaBytes, string album, string filename, MediaType mediaType, MediaSaveCallback callback )
- {
- if( mediaBytes == null || mediaBytes.Length == 0 )
- throw new ArgumentException( "Parameter 'mediaBytes' is null or empty!" );
-
- if( album == null || album.Length == 0 )
- throw new ArgumentException( "Parameter 'album' is null or empty!" );
-
- if( filename == null || filename.Length == 0 )
- throw new ArgumentException( "Parameter 'filename' is null or empty!" );
-
- if( string.IsNullOrEmpty( Path.GetExtension( filename ) ) )
- Debug.LogWarning( "'filename' doesn't have an extension, this might result in unexpected behaviour!" );
-
- RequestPermissionAsync( ( permission ) =>
- {
- if( permission != Permission.Granted )
- {
- callback?.Invoke( false, null );
- return;
- }
-
- string path = GetTemporarySavePath( filename );
- #if UNITY_EDITOR
- Debug.Log( "SaveToGallery called successfully in the Editor" );
- #else
- File.WriteAllBytes( path, mediaBytes );
- #endif
-
- SaveToGalleryInternal( path, album, mediaType, callback );
- }, PermissionType.Write, mediaType );
- }
-
- private static void SaveToGallery( string existingMediaPath, string album, string filename, MediaType mediaType, MediaSaveCallback callback )
- {
- if( !File.Exists( existingMediaPath ) )
- throw new FileNotFoundException( "File not found at " + existingMediaPath );
-
- if( album == null || album.Length == 0 )
- throw new ArgumentException( "Parameter 'album' is null or empty!" );
-
- if( filename == null || filename.Length == 0 )
- throw new ArgumentException( "Parameter 'filename' is null or empty!" );
-
- if( string.IsNullOrEmpty( Path.GetExtension( filename ) ) )
- {
- string originalExtension = Path.GetExtension( existingMediaPath );
- if( string.IsNullOrEmpty( originalExtension ) )
- Debug.LogWarning( "'filename' doesn't have an extension, this might result in unexpected behaviour!" );
- else
- filename += originalExtension;
- }
-
- RequestPermissionAsync( ( permission ) =>
- {
- if( permission != Permission.Granted )
- {
- callback?.Invoke( false, null );
- return;
- }
-
- string path = GetTemporarySavePath( filename );
- #if UNITY_EDITOR
- Debug.Log( "SaveToGallery called successfully in the Editor" );
- #else
- File.Copy( existingMediaPath, path, true );
- #endif
-
- SaveToGalleryInternal( path, album, mediaType, callback );
- }, PermissionType.Write, mediaType );
- }
-
- private static void SaveToGalleryInternal( string path, string album, MediaType mediaType, MediaSaveCallback callback )
- {
- #if !UNITY_EDITOR && UNITY_ANDROID
- string savePath = AJC.CallStatic<string>( "SaveMedia", Context, (int) mediaType, path, album );
-
- File.Delete( path );
-
- if( callback != null )
- callback( !string.IsNullOrEmpty( savePath ), savePath );
- #elif !UNITY_EDITOR && UNITY_IOS
- if( mediaType == MediaType.Audio )
- {
- Debug.LogError( "Saving audio files is not supported on iOS" );
-
- if( callback != null )
- callback( false, null );
-
- return;
- }
-
- Debug.Log( "Saving to Pictures: " + Path.GetFileName( path ) );
-
- NGMediaSaveCallbackiOS.Initialize( callback );
- if( mediaType == MediaType.Image )
- _NativeGallery_ImageWriteToAlbum( path, album, PermissionFreeMode ? 1 : 0 );
- else if( mediaType == MediaType.Video )
- _NativeGallery_VideoWriteToAlbum( path, album, PermissionFreeMode ? 1 : 0 );
- #else
- if( callback != null )
- callback( true, null );
- #endif
- }
-
- private static string GetTemporarySavePath( string filename )
- {
- string saveDir = Path.Combine( Application.persistentDataPath, "NGallery" );
- Directory.CreateDirectory( saveDir );
-
- #if !UNITY_EDITOR && UNITY_IOS
- // Ensure a unique temporary filename on iOS:
- // iOS internally copies images/videos to Photos directory of the system,
- // but the process is async. The redundant file is deleted by objective-c code
- // automatically after the media is saved but while it is being saved, the file
- // should NOT be overwritten. Therefore, always ensure a unique filename on iOS
- string path = Path.Combine( saveDir, filename );
- if( File.Exists( path ) )
- {
- int fileIndex = 0;
- string filenameWithoutExtension = Path.GetFileNameWithoutExtension( filename );
- string extension = Path.GetExtension( filename );
-
- do
- {
- path = Path.Combine( saveDir, string.Concat( filenameWithoutExtension, ++fileIndex, extension ) );
- } while( File.Exists( path ) );
- }
-
- return path;
- #else
- return Path.Combine( saveDir, filename );
- #endif
- }
-
- private static void GetMediaFromGallery( MediaPickCallback callback, MediaType mediaType, string mime, string title )
- {
- RequestPermissionAsync( ( permission ) =>
- {
- if( permission != Permission.Granted || IsMediaPickerBusy() )
- {
- callback?.Invoke( null );
- return;
- }
-
- #if UNITY_EDITOR
- System.Collections.Generic.List<string> editorFilters = new System.Collections.Generic.List<string>( 4 );
-
- if( ( mediaType & MediaType.Image ) == MediaType.Image )
- {
- editorFilters.Add( "Image files" );
- editorFilters.Add( "png,jpg,jpeg" );
- }
-
- if( ( mediaType & MediaType.Video ) == MediaType.Video )
- {
- editorFilters.Add( "Video files" );
- editorFilters.Add( "mp4,mov,webm,avi" );
- }
-
- if( ( mediaType & MediaType.Audio ) == MediaType.Audio )
- {
- editorFilters.Add( "Audio files" );
- editorFilters.Add( "mp3,wav,aac,flac" );
- }
-
- editorFilters.Add( "All files" );
- editorFilters.Add( "*" );
-
- string pickedFile = UnityEditor.EditorUtility.OpenFilePanelWithFilters( "Select file", "", editorFilters.ToArray() );
-
- if( callback != null )
- callback( pickedFile != "" ? pickedFile : null );
- #elif UNITY_ANDROID
- AJC.CallStatic( "PickMedia", Context, new NGMediaReceiveCallbackAndroid( callback, null ), (int) mediaType, false, SelectedMediaPath, mime, title );
- #elif UNITY_IOS
- if( mediaType == MediaType.Audio )
- {
- Debug.LogError( "Picking audio files is not supported on iOS" );
-
- if( callback != null ) // Selecting audio files is not supported on iOS
- callback( null );
- }
- else
- {
- NGMediaReceiveCallbackiOS.Initialize( callback, null );
- _NativeGallery_PickMedia( SelectedMediaPath, (int) ( mediaType & ~MediaType.Audio ), PermissionFreeMode ? 1 : 0, 1 );
- }
- #else
- if( callback != null )
- callback( null );
- #endif
- }, PermissionType.Read, mediaType );
- }
-
- private static void GetMultipleMediaFromGallery( MediaPickMultipleCallback callback, MediaType mediaType, string mime, string title )
- {
- RequestPermissionAsync( ( permission ) =>
- {
- if( permission != Permission.Granted || IsMediaPickerBusy() )
- {
- callback?.Invoke( null );
- return;
- }
-
- if( CanSelectMultipleFilesFromGallery() )
- {
- #if !UNITY_EDITOR && UNITY_ANDROID
- AJC.CallStatic( "PickMedia", Context, new NGMediaReceiveCallbackAndroid( null, callback ), (int) mediaType, true, SelectedMediaPath, mime, title );
- #elif !UNITY_EDITOR && UNITY_IOS
- if( mediaType == MediaType.Audio )
- {
- Debug.LogError( "Picking audio files is not supported on iOS" );
-
- if( callback != null ) // Selecting audio files is not supported on iOS
- callback( null );
- }
- else
- {
- NGMediaReceiveCallbackiOS.Initialize( null, callback );
- _NativeGallery_PickMedia( SelectedMediaPath, (int) ( mediaType & ~MediaType.Audio ), PermissionFreeMode ? 1 : 0, 0 );
- }
- #else
- if( callback != null )
- callback( null );
- #endif
- }
- else if( callback != null )
- callback( null );
- }, PermissionType.Read, mediaType );
- }
-
- private static byte[] GetTextureBytes( Texture2D texture, bool isJpeg )
- {
- try
- {
- return isJpeg ? texture.EncodeToJPG( 100 ) : texture.EncodeToPNG();
- }
- catch( UnityException )
- {
- return GetTextureBytesFromCopy( texture, isJpeg );
- }
- catch( ArgumentException )
- {
- return GetTextureBytesFromCopy( texture, isJpeg );
- }
-
- #pragma warning disable 0162
- return null;
- #pragma warning restore 0162
- }
-
- private static byte[] GetTextureBytesFromCopy( Texture2D texture, bool isJpeg )
- {
- // Texture is marked as non-readable, create a readable copy and save it instead
- Debug.LogWarning( "Saving non-readable textures is slower than saving readable textures" );
-
- Texture2D sourceTexReadable = null;
- RenderTexture rt = RenderTexture.GetTemporary( texture.width, texture.height );
- RenderTexture activeRT = RenderTexture.active;
-
- try
- {
- Graphics.Blit( texture, rt );
- RenderTexture.active = rt;
-
- sourceTexReadable = new Texture2D( texture.width, texture.height, isJpeg ? TextureFormat.RGB24 : TextureFormat.RGBA32, false );
- sourceTexReadable.ReadPixels( new Rect( 0, 0, texture.width, texture.height ), 0, 0, false );
- sourceTexReadable.Apply( false, false );
- }
- catch( Exception e )
- {
- Debug.LogException( e );
-
- Object.DestroyImmediate( sourceTexReadable );
- return null;
- }
- finally
- {
- RenderTexture.active = activeRT;
- RenderTexture.ReleaseTemporary( rt );
- }
-
- try
- {
- return isJpeg ? sourceTexReadable.EncodeToJPG( 100 ) : sourceTexReadable.EncodeToPNG();
- }
- catch( Exception e )
- {
- Debug.LogException( e );
- return null;
- }
- finally
- {
- Object.DestroyImmediate( sourceTexReadable );
- }
- }
-
- #if UNITY_ANDROID
- private static async Task<T> TryCallNativeAndroidFunctionOnSeparateThread<T>( Func<T> function )
- {
- T result = default( T );
- bool hasResult = false;
-
- await Task.Run( () =>
- {
- if( AndroidJNI.AttachCurrentThread() != 0 )
- Debug.LogWarning( "Couldn't attach JNI thread, calling native function on the main thread" );
- else
- {
- try
- {
- result = function();
- hasResult = true;
- }
- finally
- {
- AndroidJNI.DetachCurrentThread();
- }
- }
- } );
-
- return hasResult ? result : function();
- }
- #endif
- #endregion
-
- #region Utility Functions
- public static Texture2D LoadImageAtPath( string imagePath, int maxSize = -1, bool markTextureNonReadable = true, bool generateMipmaps = true, bool linearColorSpace = false )
- {
- if( string.IsNullOrEmpty( imagePath ) )
- throw new ArgumentException( "Parameter 'imagePath' is null or empty!" );
-
- if( !File.Exists( imagePath ) )
- throw new FileNotFoundException( "File not found at " + imagePath );
-
- if( maxSize <= 0 )
- maxSize = SystemInfo.maxTextureSize;
-
- #if !UNITY_EDITOR && UNITY_ANDROID
- string loadPath = AJC.CallStatic<string>( "LoadImageAtPath", Context, imagePath, TemporaryImagePath, maxSize );
- #elif !UNITY_EDITOR && UNITY_IOS
- string loadPath = _NativeGallery_LoadImageAtPath( imagePath, TemporaryImagePath, maxSize );
- #else
- string loadPath = imagePath;
- #endif
-
- string extension = Path.GetExtension( imagePath ).ToLowerInvariant();
- TextureFormat format = ( extension == ".jpg" || extension == ".jpeg" ) ? TextureFormat.RGB24 : TextureFormat.RGBA32;
-
- Texture2D result = new Texture2D( 2, 2, format, generateMipmaps, linearColorSpace );
-
- try
- {
- if( !result.LoadImage( File.ReadAllBytes( loadPath ), markTextureNonReadable ) )
- {
- Debug.LogWarning( "Couldn't load image at path: " + loadPath );
-
- Object.DestroyImmediate( result );
- return null;
- }
- }
- catch( Exception e )
- {
- Debug.LogException( e );
-
- Object.DestroyImmediate( result );
- return null;
- }
- finally
- {
- if( loadPath != imagePath )
- {
- try
- {
- File.Delete( loadPath );
- }
- catch { }
- }
- }
-
- return result;
- }
-
- public static async Task<Texture2D> LoadImageAtPathAsync( string imagePath, int maxSize = -1, bool markTextureNonReadable = true )
- {
- if( string.IsNullOrEmpty( imagePath ) )
- throw new ArgumentException( "Parameter 'imagePath' is null or empty!" );
-
- if( !File.Exists( imagePath ) )
- throw new FileNotFoundException( "File not found at " + imagePath );
-
- if( maxSize <= 0 )
- maxSize = SystemInfo.maxTextureSize;
-
- #if !UNITY_EDITOR && UNITY_ANDROID
- string temporaryImagePath = TemporaryImagePath; // Must be accessed from main thread
- string loadPath = await TryCallNativeAndroidFunctionOnSeparateThread( () => AJC.CallStatic<string>( "LoadImageAtPath", Context, imagePath, temporaryImagePath, maxSize ) );
- #elif !UNITY_EDITOR && UNITY_IOS
- string temporaryImagePath = TemporaryImagePath; // Must be accessed from main thread
- string loadPath = await Task.Run( () => _NativeGallery_LoadImageAtPath( imagePath, temporaryImagePath, maxSize ) );
- #else
- string loadPath = imagePath;
- #endif
-
- Texture2D result = null;
-
- using( UnityWebRequest www = UnityWebRequestTexture.GetTexture( "file://" + loadPath, markTextureNonReadable ) )
- {
- UnityWebRequestAsyncOperation asyncOperation = www.SendWebRequest();
- while( !asyncOperation.isDone )
- await Task.Yield();
-
- if( www.result != UnityWebRequest.Result.Success )
- Debug.LogWarning( "Couldn't use UnityWebRequest to load image, falling back to LoadImage: " + www.error );
- else
- result = DownloadHandlerTexture.GetContent( www );
- }
-
- if( !result ) // Fallback to Texture2D.LoadImage if something goes wrong
- {
- string extension = Path.GetExtension( imagePath ).ToLowerInvariant();
- TextureFormat format = ( extension == ".jpg" || extension == ".jpeg" ) ? TextureFormat.RGB24 : TextureFormat.RGBA32;
-
- result = new Texture2D( 2, 2, format, true, false );
-
- try
- {
- if( !result.LoadImage( File.ReadAllBytes( loadPath ), markTextureNonReadable ) )
- {
- Debug.LogWarning( "Couldn't load image at path: " + loadPath );
-
- Object.DestroyImmediate( result );
- return null;
- }
- }
- catch( Exception e )
- {
- Debug.LogException( e );
-
- Object.DestroyImmediate( result );
- return null;
- }
- finally
- {
- if( loadPath != imagePath )
- {
- try
- {
- File.Delete( loadPath );
- }
- catch { }
- }
- }
- }
-
- return result;
- }
-
- public static Texture2D GetVideoThumbnail( string videoPath, int maxSize = -1, double captureTimeInSeconds = -1.0, bool markTextureNonReadable = true, bool generateMipmaps = true, bool linearColorSpace = false )
- {
- if( maxSize <= 0 )
- maxSize = SystemInfo.maxTextureSize;
-
- #if !UNITY_EDITOR && UNITY_ANDROID
- string thumbnailPath = AJC.CallStatic<string>( "GetVideoThumbnail", Context, videoPath, TemporaryImagePath + ".png", false, maxSize, captureTimeInSeconds );
- #elif !UNITY_EDITOR && UNITY_IOS
- string thumbnailPath = _NativeGallery_GetVideoThumbnail( videoPath, TemporaryImagePath + ".png", maxSize, captureTimeInSeconds );
- #else
- string thumbnailPath = null;
- #endif
-
- if( !string.IsNullOrEmpty( thumbnailPath ) )
- return LoadImageAtPath( thumbnailPath, maxSize, markTextureNonReadable, generateMipmaps, linearColorSpace );
- else
- return null;
- }
-
- public static async Task<Texture2D> GetVideoThumbnailAsync( string videoPath, int maxSize = -1, double captureTimeInSeconds = -1.0, bool markTextureNonReadable = true )
- {
- if( maxSize <= 0 )
- maxSize = SystemInfo.maxTextureSize;
-
- #if !UNITY_EDITOR && UNITY_ANDROID
- string temporaryImagePath = TemporaryImagePath; // Must be accessed from main thread
- string thumbnailPath = await TryCallNativeAndroidFunctionOnSeparateThread( () => AJC.CallStatic<string>( "GetVideoThumbnail", Context, videoPath, temporaryImagePath + ".png", false, maxSize, captureTimeInSeconds ) );
- #elif !UNITY_EDITOR && UNITY_IOS
- string temporaryImagePath = TemporaryImagePath; // Must be accessed from main thread
- string thumbnailPath = await Task.Run( () => _NativeGallery_GetVideoThumbnail( videoPath, temporaryImagePath + ".png", maxSize, captureTimeInSeconds ) );
- #else
- string thumbnailPath = null;
- #endif
-
- if( !string.IsNullOrEmpty( thumbnailPath ) )
- return await LoadImageAtPathAsync( thumbnailPath, maxSize, markTextureNonReadable );
- else
- return null;
- }
-
- public static ImageProperties GetImageProperties( string imagePath )
- {
- if( !File.Exists( imagePath ) )
- throw new FileNotFoundException( "File not found at " + imagePath );
-
- #if !UNITY_EDITOR && UNITY_ANDROID
- string value = AJC.CallStatic<string>( "GetImageProperties", Context, imagePath );
- #elif !UNITY_EDITOR && UNITY_IOS
- string value = _NativeGallery_GetImageProperties( imagePath );
- #else
- string value = null;
- #endif
-
- int width = 0, height = 0;
- string mimeType = null;
- ImageOrientation orientation = ImageOrientation.Unknown;
- if( !string.IsNullOrEmpty( value ) )
- {
- string[] properties = value.Split( '>' );
- if( properties != null && properties.Length >= 4 )
- {
- if( !int.TryParse( properties[0].Trim(), out width ) )
- width = 0;
- if( !int.TryParse( properties[1].Trim(), out height ) )
- height = 0;
-
- mimeType = properties[2].Trim();
- if( mimeType.Length == 0 )
- {
- string extension = Path.GetExtension( imagePath ).ToLowerInvariant();
- if( extension == ".png" )
- mimeType = "image/png";
- else if( extension == ".jpg" || extension == ".jpeg" )
- mimeType = "image/jpeg";
- else if( extension == ".gif" )
- mimeType = "image/gif";
- else if( extension == ".bmp" )
- mimeType = "image/bmp";
- else
- mimeType = null;
- }
-
- int orientationInt;
- if( int.TryParse( properties[3].Trim(), out orientationInt ) )
- orientation = (ImageOrientation) orientationInt;
- }
- }
-
- return new ImageProperties( width, height, mimeType, orientation );
- }
-
- public static VideoProperties GetVideoProperties( string videoPath )
- {
- if( !File.Exists( videoPath ) )
- throw new FileNotFoundException( "File not found at " + videoPath );
-
- #if !UNITY_EDITOR && UNITY_ANDROID
- string value = AJC.CallStatic<string>( "GetVideoProperties", Context, videoPath );
- #elif !UNITY_EDITOR && UNITY_IOS
- string value = _NativeGallery_GetVideoProperties( videoPath );
- #else
- string value = null;
- #endif
-
- int width = 0, height = 0;
- long duration = 0L;
- float rotation = 0f;
- if( !string.IsNullOrEmpty( value ) )
- {
- string[] properties = value.Split( '>' );
- if( properties != null && properties.Length >= 4 )
- {
- if( !int.TryParse( properties[0].Trim(), out width ) )
- width = 0;
- if( !int.TryParse( properties[1].Trim(), out height ) )
- height = 0;
- if( !long.TryParse( properties[2].Trim(), out duration ) )
- duration = 0L;
- if( !float.TryParse( properties[3].Trim().Replace( ',', '.' ), NumberStyles.Float, CultureInfo.InvariantCulture, out rotation ) )
- rotation = 0f;
- }
- }
-
- if( rotation == -90f )
- rotation = 270f;
-
- return new VideoProperties( width, height, duration, rotation );
- }
- #endregion
- }
|