ball 项目提交

This commit is contained in:
2026-04-20 12:06:34 +08:00
parent 4331ebba60
commit 99145facbd
6052 changed files with 576445 additions and 0 deletions
@@ -0,0 +1,9 @@
fileFormatVersion: 2
guid: 0a607dcda26e7614f86300c6ca717295
folderAsset: yes
timeCreated: 1498722617
licenseType: Pro
DefaultImporter:
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,51 @@
using System;
using UnityEngine;
namespace NativeGalleryNamespace
{
public class NGCallbackHelper : MonoBehaviour
{
private bool autoDestroyWithCallback;
private Action mainThreadAction = null;
public static NGCallbackHelper Create( bool autoDestroyWithCallback )
{
NGCallbackHelper result = new GameObject( "NGCallbackHelper" ).AddComponent<NGCallbackHelper>();
result.autoDestroyWithCallback = autoDestroyWithCallback;
DontDestroyOnLoad( result.gameObject );
return result;
}
public void CallOnMainThread( Action function )
{
lock( this )
{
mainThreadAction += function;
}
}
private void Update()
{
if( mainThreadAction != null )
{
try
{
Action temp;
lock( this )
{
temp = mainThreadAction;
mainThreadAction = null;
}
temp();
}
finally
{
if( autoDestroyWithCallback )
Destroy( gameObject );
}
}
}
}
}
@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 2d517fd0f2f85f24698df2775bee58e9
timeCreated: 1544889149
licenseType: Free
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,62 @@
#if UNITY_EDITOR || UNITY_ANDROID
using UnityEngine;
namespace NativeGalleryNamespace
{
public class NGMediaReceiveCallbackAndroid : AndroidJavaProxy
{
private readonly NativeGallery.MediaPickCallback callback;
private readonly NativeGallery.MediaPickMultipleCallback callbackMultiple;
private readonly NGCallbackHelper callbackHelper;
public NGMediaReceiveCallbackAndroid( NativeGallery.MediaPickCallback callback, NativeGallery.MediaPickMultipleCallback callbackMultiple ) : base( "com.yasirkula.unity.NativeGalleryMediaReceiver" )
{
this.callback = callback;
this.callbackMultiple = callbackMultiple;
callbackHelper = NGCallbackHelper.Create( true );
}
[UnityEngine.Scripting.Preserve]
public void OnMediaReceived( string path )
{
callbackHelper.CallOnMainThread( () => callback( !string.IsNullOrEmpty( path ) ? path : null ) );
}
[UnityEngine.Scripting.Preserve]
public void OnMultipleMediaReceived( string paths )
{
string[] result = null;
if( !string.IsNullOrEmpty( paths ) )
{
string[] pathsSplit = paths.Split( '>' );
int validPathCount = 0;
for( int i = 0; i < pathsSplit.Length; i++ )
{
if( !string.IsNullOrEmpty( pathsSplit[i] ) )
validPathCount++;
}
if( validPathCount == 0 )
pathsSplit = new string[0];
else if( validPathCount != pathsSplit.Length )
{
string[] validPaths = new string[validPathCount];
for( int i = 0, j = 0; i < pathsSplit.Length; i++ )
{
if( !string.IsNullOrEmpty( pathsSplit[i] ) )
validPaths[j++] = pathsSplit[i];
}
pathsSplit = validPaths;
}
result = pathsSplit;
}
callbackHelper.CallOnMainThread( () => callbackMultiple( ( result != null && result.Length > 0 ) ? result : null ) );
}
}
}
#endif
@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 4c18d702b07a63945968db47201b95c9
timeCreated: 1519060539
licenseType: Free
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,24 @@
#if UNITY_EDITOR || UNITY_ANDROID
using UnityEngine;
namespace NativeGalleryNamespace
{
public class NGPermissionCallbackAndroid : AndroidJavaProxy
{
private readonly NativeGallery.PermissionCallback callback;
private readonly NGCallbackHelper callbackHelper;
public NGPermissionCallbackAndroid( NativeGallery.PermissionCallback callback ) : base( "com.yasirkula.unity.NativeGalleryPermissionReceiver" )
{
this.callback = callback;
callbackHelper = NGCallbackHelper.Create( true );
}
[UnityEngine.Scripting.Preserve]
public void OnPermissionResult( int result )
{
callbackHelper.CallOnMainThread( () => callback( (NativeGallery.Permission) result ) );
}
}
}
#endif
@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: a07afac614af1294d8e72a3c083be028
timeCreated: 1519060539
licenseType: Free
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,33 @@
fileFormatVersion: 2
guid: db4d55e1212537e4baa84cac66eb6645
timeCreated: 1569764737
licenseType: Free
PluginImporter:
serializedVersion: 2
iconMap: {}
executionOrder: {}
isPreloaded: 0
isOverridable: 0
platformData:
data:
first:
Android: Android
second:
enabled: 1
settings: {}
data:
first:
Any:
second:
enabled: 0
settings: {}
data:
first:
Editor: Editor
second:
enabled: 0
settings:
DefaultValueInitialized: true
userData:
assetBundleName:
assetBundleVariant:
+9
View File
@@ -0,0 +1,9 @@
fileFormatVersion: 2
guid: 19fc6b8ce781591438a952d8aa9104f8
folderAsset: yes
timeCreated: 1521452097
licenseType: Free
DefaultImporter:
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,119 @@
using System.IO;
using UnityEditor;
using UnityEngine;
#if UNITY_IOS
using UnityEditor.Callbacks;
using UnityEditor.iOS.Xcode;
#endif
namespace NativeGalleryNamespace
{
[System.Serializable]
public class Settings
{
private const string SAVE_PATH = "ProjectSettings/NativeGallery.json";
public bool AutomatedSetup = true;
public string PhotoLibraryUsageDescription = "The app requires access to Photos to interact with it.";
public string PhotoLibraryAdditionsUsageDescription = "The app requires access to Photos to save media to it.";
public bool DontAskLimitedPhotosPermissionAutomaticallyOnIos14 = true; // See: https://mackuba.eu/2020/07/07/photo-library-changes-ios-14/
private static Settings m_instance = null;
public static Settings Instance
{
get
{
if( m_instance == null )
{
try
{
if( File.Exists( SAVE_PATH ) )
m_instance = JsonUtility.FromJson<Settings>( File.ReadAllText( SAVE_PATH ) );
else
m_instance = new Settings();
}
catch( System.Exception e )
{
Debug.LogException( e );
m_instance = new Settings();
}
}
return m_instance;
}
}
public void Save()
{
File.WriteAllText( SAVE_PATH, JsonUtility.ToJson( this, true ) );
}
[SettingsProvider]
public static SettingsProvider CreatePreferencesGUI()
{
return new SettingsProvider( "Project/yasirkula/Native Gallery", SettingsScope.Project )
{
guiHandler = ( searchContext ) => PreferencesGUI(),
keywords = new System.Collections.Generic.HashSet<string>() { "Native", "Gallery", "Android", "iOS" }
};
}
public static void PreferencesGUI()
{
EditorGUI.BeginChangeCheck();
Instance.AutomatedSetup = EditorGUILayout.Toggle( "Automated Setup", Instance.AutomatedSetup );
EditorGUI.BeginDisabledGroup( !Instance.AutomatedSetup );
Instance.PhotoLibraryUsageDescription = EditorGUILayout.DelayedTextField( "Photo Library Usage Description", Instance.PhotoLibraryUsageDescription );
Instance.PhotoLibraryAdditionsUsageDescription = EditorGUILayout.DelayedTextField( "Photo Library Additions Usage Description", Instance.PhotoLibraryAdditionsUsageDescription );
Instance.DontAskLimitedPhotosPermissionAutomaticallyOnIos14 = EditorGUILayout.Toggle( new GUIContent( "Don't Ask Limited Photos Permission Automatically", "See: https://mackuba.eu/2020/07/07/photo-library-changes-ios-14/. It's recommended to keep this setting enabled" ), Instance.DontAskLimitedPhotosPermissionAutomaticallyOnIos14 );
EditorGUI.EndDisabledGroup();
if( EditorGUI.EndChangeCheck() )
Instance.Save();
}
}
public class NGPostProcessBuild
{
#if UNITY_IOS
[PostProcessBuild( 1 )]
public static void OnPostprocessBuild( BuildTarget target, string buildPath )
{
if( !Settings.Instance.AutomatedSetup )
return;
if( target == BuildTarget.iOS )
{
string pbxProjectPath = PBXProject.GetPBXProjectPath( buildPath );
string plistPath = Path.Combine( buildPath, "Info.plist" );
PBXProject pbxProject = new PBXProject();
pbxProject.ReadFromFile( pbxProjectPath );
string targetGUID = pbxProject.GetUnityFrameworkTargetGuid();
pbxProject.AddFrameworkToProject( targetGUID, "PhotosUI.framework", true );
pbxProject.AddFrameworkToProject( targetGUID, "Photos.framework", false );
pbxProject.AddFrameworkToProject( targetGUID, "MobileCoreServices.framework", false );
pbxProject.AddFrameworkToProject( targetGUID, "ImageIO.framework", false );
File.WriteAllText( pbxProjectPath, pbxProject.WriteToString() );
PlistDocument plist = new PlistDocument();
plist.ReadFromString( File.ReadAllText( plistPath ) );
PlistElementDict rootDict = plist.root;
if( !string.IsNullOrEmpty( Settings.Instance.PhotoLibraryUsageDescription ) )
rootDict.SetString( "NSPhotoLibraryUsageDescription", Settings.Instance.PhotoLibraryUsageDescription );
if( !string.IsNullOrEmpty( Settings.Instance.PhotoLibraryAdditionsUsageDescription ) )
rootDict.SetString( "NSPhotoLibraryAddUsageDescription", Settings.Instance.PhotoLibraryAdditionsUsageDescription );
if( Settings.Instance.DontAskLimitedPhotosPermissionAutomaticallyOnIos14 )
rootDict.SetBoolean( "PHPhotoLibraryPreventAutomaticLimitedAccessAlert", true );
File.WriteAllText( plistPath, plist.WriteToString() );
}
}
#endif
}
}
@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: dff1540cf22bfb749a2422f445cf9427
timeCreated: 1521452119
licenseType: Free
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,15 @@
{
"name": "NativeGallery.Editor",
"references": [],
"includePlatforms": [
"Editor"
],
"excludePlatforms": [],
"allowUnsafeCode": false,
"overrideReferences": false,
"precompiledReferences": [],
"autoReferenced": true,
"defineConstraints": [],
"versionDefines": [],
"noEngineReferences": false
}
@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 3dffc8e654f00c545a82d0a5274d51eb
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,3 @@
{
"name": "NativeGallery.Runtime"
}
@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 6e5063adab271564ba0098a06a8cebda
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,988 @@
using System;
using System.Globalization;
using System.IO;
using UnityEngine;
using System.Threading.Tasks;
using UnityEngine.Networking;
using NativeGalleryNamespace;
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
}
@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: ce1403606c3629046a0147d3e705f7cc
timeCreated: 1498722610
licenseType: Pro
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+6
View File
@@ -0,0 +1,6 @@
= Native Gallery for Android & iOS (v1.9.1) =
Documentation: https://github.com/yasirkula/UnityNativeGallery
FAQ: https://github.com/yasirkula/UnityNativeGallery#faq
Example code: https://github.com/yasirkula/UnityNativeGallery#example-code
E-mail: yasirkula@gmail.com
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: be769f45b807c40459e5bafb18e887d6
timeCreated: 1563308465
licenseType: Free
TextScriptImporter:
userData:
assetBundleName:
assetBundleVariant:
+9
View File
@@ -0,0 +1,9 @@
fileFormatVersion: 2
guid: 9c623599351a41a4c84c20f73c9d8976
folderAsset: yes
timeCreated: 1498722622
licenseType: Pro
DefaultImporter:
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,132 @@
#if UNITY_EDITOR || UNITY_IOS
using UnityEngine;
namespace NativeGalleryNamespace
{
public class NGMediaReceiveCallbackiOS : MonoBehaviour
{
private static NGMediaReceiveCallbackiOS instance;
private NativeGallery.MediaPickCallback callback;
private NativeGallery.MediaPickMultipleCallback callbackMultiple;
private float nextBusyCheckTime;
public static bool IsBusy { get; private set; }
[System.Runtime.InteropServices.DllImport( "__Internal" )]
private static extern int _NativeGallery_IsMediaPickerBusy();
public static void Initialize( NativeGallery.MediaPickCallback callback, NativeGallery.MediaPickMultipleCallback callbackMultiple )
{
if( IsBusy )
return;
if( instance == null )
{
instance = new GameObject( "NGMediaReceiveCallbackiOS" ).AddComponent<NGMediaReceiveCallbackiOS>();
DontDestroyOnLoad( instance.gameObject );
}
instance.callback = callback;
instance.callbackMultiple = callbackMultiple;
instance.nextBusyCheckTime = Time.realtimeSinceStartup + 1f;
IsBusy = true;
}
private void Update()
{
if( IsBusy )
{
if( Time.realtimeSinceStartup >= nextBusyCheckTime )
{
nextBusyCheckTime = Time.realtimeSinceStartup + 1f;
if( _NativeGallery_IsMediaPickerBusy() == 0 )
{
IsBusy = false;
NativeGallery.MediaPickCallback _callback = callback;
callback = null;
NativeGallery.MediaPickMultipleCallback _callbackMultiple = callbackMultiple;
callbackMultiple = null;
if( _callback != null )
_callback( null );
if( _callbackMultiple != null )
_callbackMultiple( null );
}
}
}
}
[UnityEngine.Scripting.Preserve]
public void OnMediaReceived( string path )
{
IsBusy = false;
if( string.IsNullOrEmpty( path ) )
path = null;
NativeGallery.MediaPickCallback _callback = callback;
callback = null;
if( _callback != null )
_callback( path );
}
[UnityEngine.Scripting.Preserve]
public void OnMultipleMediaReceived( string paths )
{
IsBusy = false;
string[] _paths = SplitPaths( paths );
if( _paths != null && _paths.Length == 0 )
_paths = null;
NativeGallery.MediaPickMultipleCallback _callbackMultiple = callbackMultiple;
callbackMultiple = null;
if( _callbackMultiple != null )
_callbackMultiple( _paths );
}
private string[] SplitPaths( string paths )
{
string[] result = null;
if( !string.IsNullOrEmpty( paths ) )
{
string[] pathsSplit = paths.Split( '>' );
int validPathCount = 0;
for( int i = 0; i < pathsSplit.Length; i++ )
{
if( !string.IsNullOrEmpty( pathsSplit[i] ) )
validPathCount++;
}
if( validPathCount == 0 )
pathsSplit = new string[0];
else if( validPathCount != pathsSplit.Length )
{
string[] validPaths = new string[validPathCount];
for( int i = 0, j = 0; i < pathsSplit.Length; i++ )
{
if( !string.IsNullOrEmpty( pathsSplit[i] ) )
validPaths[j++] = pathsSplit[i];
}
pathsSplit = validPaths;
}
result = pathsSplit;
}
return result;
}
}
}
#endif
@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 71fb861c149c2d1428544c601e52a33c
timeCreated: 1519060539
licenseType: Free
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,45 @@
#if UNITY_EDITOR || UNITY_IOS
using UnityEngine;
namespace NativeGalleryNamespace
{
public class NGMediaSaveCallbackiOS : MonoBehaviour
{
private static NGMediaSaveCallbackiOS instance;
private NativeGallery.MediaSaveCallback callback;
public static void Initialize( NativeGallery.MediaSaveCallback callback )
{
if( instance == null )
{
instance = new GameObject( "NGMediaSaveCallbackiOS" ).AddComponent<NGMediaSaveCallbackiOS>();
DontDestroyOnLoad( instance.gameObject );
}
else if( instance.callback != null )
instance.callback( false, null );
instance.callback = callback;
}
[UnityEngine.Scripting.Preserve]
public void OnMediaSaveCompleted( string message )
{
NativeGallery.MediaSaveCallback _callback = callback;
callback = null;
if( _callback != null )
_callback( true, null );
}
[UnityEngine.Scripting.Preserve]
public void OnMediaSaveFailed( string error )
{
NativeGallery.MediaSaveCallback _callback = callback;
callback = null;
if( _callback != null )
_callback( false, null );
}
}
}
#endif
@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 9cbb865d0913a0d47bb6d2eb3ad04c4f
timeCreated: 1519060539
licenseType: Free
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,35 @@
#if UNITY_EDITOR || UNITY_IOS
using UnityEngine;
namespace NativeGalleryNamespace
{
public class NGPermissionCallbackiOS : MonoBehaviour
{
private static NGPermissionCallbackiOS instance;
private NativeGallery.PermissionCallback callback;
public static void Initialize( NativeGallery.PermissionCallback callback )
{
if( instance == null )
{
instance = new GameObject( "NGPermissionCallbackiOS" ).AddComponent<NGPermissionCallbackiOS>();
DontDestroyOnLoad( instance.gameObject );
}
else if( instance.callback != null )
instance.callback( NativeGallery.Permission.ShouldAsk );
instance.callback = callback;
}
[UnityEngine.Scripting.Preserve]
public void OnPermissionRequested( string message )
{
NativeGallery.PermissionCallback _callback = callback;
callback = null;
if( _callback != null )
_callback( (NativeGallery.Permission) int.Parse( message ) );
}
}
}
#endif
@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: bc6d7fa0a99114a45b1a6800097c6eb1
timeCreated: 1519060539
licenseType: Free
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,33 @@
fileFormatVersion: 2
guid: 953e0b740eb03144883db35f72cad8a6
timeCreated: 1498722774
licenseType: Pro
PluginImporter:
serializedVersion: 2
iconMap: {}
executionOrder: {}
isPreloaded: 0
isOverridable: 0
platformData:
data:
first:
Any:
second:
enabled: 0
settings: {}
data:
first:
Editor: Editor
second:
enabled: 0
settings:
DefaultValueInitialized: true
data:
first:
iPhone: iOS
second:
enabled: 1
settings: {}
userData:
assetBundleName:
assetBundleVariant: