No Description
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

NativeCamera.cs 19KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618
  1. using System;
  2. using System.Globalization;
  3. using System.IO;
  4. using UnityEngine;
  5. #if UNITY_2018_4_OR_NEWER && !NATIVE_CAMERA_DISABLE_ASYNC_FUNCTIONS
  6. using System.Threading.Tasks;
  7. using Unity.Collections;
  8. using UnityEngine.Networking;
  9. #endif
  10. #if UNITY_ANDROID || UNITY_IOS
  11. using NativeCameraNamespace;
  12. #endif
  13. using Object = UnityEngine.Object;
  14. public static class NativeCamera
  15. {
  16. public struct ImageProperties
  17. {
  18. public readonly int width;
  19. public readonly int height;
  20. public readonly string mimeType;
  21. public readonly ImageOrientation orientation;
  22. public ImageProperties( int width, int height, string mimeType, ImageOrientation orientation )
  23. {
  24. this.width = width;
  25. this.height = height;
  26. this.mimeType = mimeType;
  27. this.orientation = orientation;
  28. }
  29. }
  30. public struct VideoProperties
  31. {
  32. public readonly int width;
  33. public readonly int height;
  34. public readonly long duration;
  35. public readonly float rotation;
  36. public VideoProperties( int width, int height, long duration, float rotation )
  37. {
  38. this.width = width;
  39. this.height = height;
  40. this.duration = duration;
  41. this.rotation = rotation;
  42. }
  43. }
  44. public enum Permission { Denied = 0, Granted = 1, ShouldAsk = 2 };
  45. public enum Quality { Default = -1, Low = 0, Medium = 1, High = 2 };
  46. public enum PreferredCamera { Default = -1, Rear = 0, Front = 1 }
  47. // EXIF orientation: http://sylvana.net/jpegcrop/exif_orientation.html (indices are reordered)
  48. public enum ImageOrientation { Unknown = -1, Normal = 0, Rotate90 = 1, Rotate180 = 2, Rotate270 = 3, FlipHorizontal = 4, Transpose = 5, FlipVertical = 6, Transverse = 7 };
  49. public delegate void PermissionCallback( Permission permission );
  50. public delegate void CameraCallback( string path );
  51. #region Platform Specific Elements
  52. #if !UNITY_EDITOR && UNITY_ANDROID
  53. private static AndroidJavaClass m_ajc = null;
  54. private static AndroidJavaClass AJC
  55. {
  56. get
  57. {
  58. if( m_ajc == null )
  59. m_ajc = new AndroidJavaClass( "com.yasirkula.unity.NativeCamera" );
  60. return m_ajc;
  61. }
  62. }
  63. private static AndroidJavaObject m_context = null;
  64. private static AndroidJavaObject Context
  65. {
  66. get
  67. {
  68. if( m_context == null )
  69. {
  70. using( AndroidJavaObject unityClass = new AndroidJavaClass( "com.unity3d.player.UnityPlayer" ) )
  71. {
  72. m_context = unityClass.GetStatic<AndroidJavaObject>( "currentActivity" );
  73. }
  74. }
  75. return m_context;
  76. }
  77. }
  78. #elif !UNITY_EDITOR && UNITY_IOS
  79. [System.Runtime.InteropServices.DllImport( "__Internal" )]
  80. private static extern int _NativeCamera_CheckPermission();
  81. [System.Runtime.InteropServices.DllImport( "__Internal" )]
  82. private static extern int _NativeCamera_RequestPermission( int asyncMode );
  83. [System.Runtime.InteropServices.DllImport( "__Internal" )]
  84. private static extern int _NativeCamera_CanOpenSettings();
  85. [System.Runtime.InteropServices.DllImport( "__Internal" )]
  86. private static extern void _NativeCamera_OpenSettings();
  87. [System.Runtime.InteropServices.DllImport( "__Internal" )]
  88. private static extern int _NativeCamera_HasCamera();
  89. [System.Runtime.InteropServices.DllImport( "__Internal" )]
  90. private static extern void _NativeCamera_TakePicture( string imageSavePath, int maxSize, int preferredCamera );
  91. [System.Runtime.InteropServices.DllImport( "__Internal" )]
  92. private static extern void _NativeCamera_RecordVideo( int quality, int maxDuration, int preferredCamera );
  93. [System.Runtime.InteropServices.DllImport( "__Internal" )]
  94. private static extern string _NativeCamera_GetImageProperties( string path );
  95. [System.Runtime.InteropServices.DllImport( "__Internal" )]
  96. private static extern string _NativeCamera_GetVideoProperties( string path );
  97. [System.Runtime.InteropServices.DllImport( "__Internal" )]
  98. private static extern string _NativeCamera_GetVideoThumbnail( string path, string thumbnailSavePath, int maxSize, double captureTimeInSeconds );
  99. [System.Runtime.InteropServices.DllImport( "__Internal" )]
  100. private static extern string _NativeCamera_LoadImageAtPath( string path, string temporaryFilePath, int maxSize );
  101. #endif
  102. #if !UNITY_EDITOR && ( UNITY_ANDROID || UNITY_IOS )
  103. private static string m_temporaryImagePath = null;
  104. private static string TemporaryImagePath
  105. {
  106. get
  107. {
  108. if( m_temporaryImagePath == null )
  109. {
  110. m_temporaryImagePath = Path.Combine( Application.temporaryCachePath, "tmpImg" );
  111. Directory.CreateDirectory( Application.temporaryCachePath );
  112. }
  113. return m_temporaryImagePath;
  114. }
  115. }
  116. #endif
  117. #if !UNITY_EDITOR && UNITY_IOS
  118. private static string m_iOSSelectedImagePath = null;
  119. private static string IOSSelectedImagePath
  120. {
  121. get
  122. {
  123. if( m_iOSSelectedImagePath == null )
  124. {
  125. m_iOSSelectedImagePath = Path.Combine( Application.temporaryCachePath, "CameraImg" );
  126. Directory.CreateDirectory( Application.temporaryCachePath );
  127. }
  128. return m_iOSSelectedImagePath;
  129. }
  130. }
  131. #endif
  132. #endregion
  133. #region Runtime Permissions
  134. public static Permission CheckPermission( bool isPicturePermission )
  135. {
  136. #if !UNITY_EDITOR && UNITY_ANDROID
  137. Permission result = (Permission) AJC.CallStatic<int>( "CheckPermission", Context, isPicturePermission );
  138. if( result == Permission.Denied && (Permission) PlayerPrefs.GetInt( "NativeCameraPermission", (int) Permission.ShouldAsk ) == Permission.ShouldAsk )
  139. result = Permission.ShouldAsk;
  140. return result;
  141. #elif !UNITY_EDITOR && UNITY_IOS
  142. return (Permission) _NativeCamera_CheckPermission();
  143. #else
  144. return Permission.Granted;
  145. #endif
  146. }
  147. public static Permission RequestPermission( bool isPicturePermission )
  148. {
  149. // Don't block the main thread if the permission is already granted
  150. if( CheckPermission( isPicturePermission ) == Permission.Granted )
  151. return Permission.Granted;
  152. #if !UNITY_EDITOR && UNITY_ANDROID
  153. object threadLock = new object();
  154. lock( threadLock )
  155. {
  156. NCPermissionCallbackAndroid nativeCallback = new NCPermissionCallbackAndroid( threadLock );
  157. AJC.CallStatic( "RequestPermission", Context, nativeCallback, isPicturePermission, (int) Permission.ShouldAsk );
  158. if( nativeCallback.Result == -1 )
  159. System.Threading.Monitor.Wait( threadLock );
  160. if( (Permission) nativeCallback.Result != Permission.ShouldAsk && PlayerPrefs.GetInt( "NativeCameraPermission", -1 ) != nativeCallback.Result )
  161. {
  162. PlayerPrefs.SetInt( "NativeCameraPermission", nativeCallback.Result );
  163. PlayerPrefs.Save();
  164. }
  165. return (Permission) nativeCallback.Result;
  166. }
  167. #elif !UNITY_EDITOR && UNITY_IOS
  168. return (Permission) _NativeCamera_RequestPermission( 0 );
  169. #else
  170. return Permission.Granted;
  171. #endif
  172. }
  173. public static void RequestPermissionAsync( PermissionCallback callback, bool isPicturePermission )
  174. {
  175. #if !UNITY_EDITOR && UNITY_ANDROID
  176. NCPermissionCallbackAsyncAndroid nativeCallback = new NCPermissionCallbackAsyncAndroid( callback );
  177. AJC.CallStatic( "RequestPermission", Context, nativeCallback, isPicturePermission, (int) Permission.ShouldAsk );
  178. #elif !UNITY_EDITOR && UNITY_IOS
  179. NCPermissionCallbackiOS.Initialize( callback );
  180. _NativeCamera_RequestPermission( 1 );
  181. #else
  182. callback( Permission.Granted );
  183. #endif
  184. }
  185. #if UNITY_2018_4_OR_NEWER && !NATIVE_CAMERA_DISABLE_ASYNC_FUNCTIONS
  186. public static Task<Permission> RequestPermissionAsync( bool isPicturePermission )
  187. {
  188. TaskCompletionSource<Permission> tcs = new TaskCompletionSource<Permission>();
  189. RequestPermissionAsync( ( permission ) => tcs.SetResult( permission ), isPicturePermission );
  190. return tcs.Task;
  191. }
  192. #endif
  193. public static bool CanOpenSettings()
  194. {
  195. #if !UNITY_EDITOR && UNITY_IOS
  196. return _NativeCamera_CanOpenSettings() == 1;
  197. #else
  198. return true;
  199. #endif
  200. }
  201. public static void OpenSettings()
  202. {
  203. #if !UNITY_EDITOR && UNITY_ANDROID
  204. AJC.CallStatic( "OpenSettings", Context );
  205. #elif !UNITY_EDITOR && UNITY_IOS
  206. _NativeCamera_OpenSettings();
  207. #endif
  208. }
  209. #endregion
  210. #region Camera Functions
  211. public static Permission TakePicture( CameraCallback callback, int maxSize = -1, bool saveAsJPEG = true, PreferredCamera preferredCamera = PreferredCamera.Default )
  212. {
  213. Permission result = RequestPermission( true );
  214. if( result == Permission.Granted && !IsCameraBusy() )
  215. {
  216. #if UNITY_EDITOR
  217. string pickedFile = UnityEditor.EditorUtility.OpenFilePanelWithFilters( "Select image", "", new string[] { "Image files", "png,jpg,jpeg", "All files", "*" } );
  218. if( callback != null )
  219. callback( pickedFile != "" ? pickedFile : null );
  220. #elif UNITY_ANDROID
  221. AJC.CallStatic( "TakePicture", Context, new NCCameraCallbackAndroid( callback ), (int) preferredCamera );
  222. #elif UNITY_IOS
  223. if( maxSize <= 0 )
  224. maxSize = SystemInfo.maxTextureSize;
  225. NCCameraCallbackiOS.Initialize( callback );
  226. _NativeCamera_TakePicture( IOSSelectedImagePath + ( saveAsJPEG ? ".jpeg" : ".png" ), maxSize, (int) preferredCamera );
  227. #else
  228. if( callback != null )
  229. callback( null );
  230. #endif
  231. }
  232. return result;
  233. }
  234. public static Permission RecordVideo( CameraCallback callback, Quality quality = Quality.Default, int maxDuration = 0, long maxSizeBytes = 0L, PreferredCamera preferredCamera = PreferredCamera.Default )
  235. {
  236. Permission result = RequestPermission( false );
  237. if( result == Permission.Granted && !IsCameraBusy() )
  238. {
  239. #if UNITY_EDITOR
  240. string pickedFile = UnityEditor.EditorUtility.OpenFilePanelWithFilters( "Select video", "", new string[] { "Video files", "mp4,mov,wav,avi", "All files", "*" } );
  241. if( callback != null )
  242. callback( pickedFile != "" ? pickedFile : null );
  243. #elif UNITY_ANDROID
  244. AJC.CallStatic( "RecordVideo", Context, new NCCameraCallbackAndroid( callback ), (int) preferredCamera, (int) quality, maxDuration, maxSizeBytes );
  245. #elif UNITY_IOS
  246. NCCameraCallbackiOS.Initialize( callback );
  247. _NativeCamera_RecordVideo( (int) quality, maxDuration, (int) preferredCamera );
  248. #else
  249. if( callback != null )
  250. callback( null );
  251. #endif
  252. }
  253. return result;
  254. }
  255. public static bool DeviceHasCamera()
  256. {
  257. #if !UNITY_EDITOR && UNITY_ANDROID
  258. return AJC.CallStatic<bool>( "HasCamera", Context );
  259. #elif !UNITY_EDITOR && UNITY_IOS
  260. return _NativeCamera_HasCamera() == 1;
  261. #else
  262. return true;
  263. #endif
  264. }
  265. public static bool IsCameraBusy()
  266. {
  267. #if !UNITY_EDITOR && UNITY_IOS
  268. return NCCameraCallbackiOS.IsBusy;
  269. #else
  270. return false;
  271. #endif
  272. }
  273. #endregion
  274. #region Utility Functions
  275. public static Texture2D LoadImageAtPath( string imagePath, int maxSize = -1, bool markTextureNonReadable = true, bool generateMipmaps = true, bool linearColorSpace = false )
  276. {
  277. if( string.IsNullOrEmpty( imagePath ) )
  278. throw new ArgumentException( "Parameter 'imagePath' is null or empty!" );
  279. if( !File.Exists( imagePath ) )
  280. throw new FileNotFoundException( "File not found at " + imagePath );
  281. if( maxSize <= 0 )
  282. maxSize = SystemInfo.maxTextureSize;
  283. #if !UNITY_EDITOR && UNITY_ANDROID
  284. string loadPath = AJC.CallStatic<string>( "LoadImageAtPath", Context, imagePath, TemporaryImagePath, maxSize );
  285. #elif !UNITY_EDITOR && UNITY_IOS
  286. string loadPath = _NativeCamera_LoadImageAtPath( imagePath, TemporaryImagePath, maxSize );
  287. #else
  288. string loadPath = imagePath;
  289. #endif
  290. string extension = Path.GetExtension( imagePath ).ToLowerInvariant();
  291. TextureFormat format = ( extension == ".jpg" || extension == ".jpeg" ) ? TextureFormat.RGB24 : TextureFormat.RGBA32;
  292. Texture2D result = new Texture2D( 2, 2, format, generateMipmaps, linearColorSpace );
  293. try
  294. {
  295. if( !result.LoadImage( File.ReadAllBytes( loadPath ), markTextureNonReadable ) )
  296. {
  297. Debug.LogWarning( "Couldn't load image at path: " + loadPath );
  298. Object.DestroyImmediate( result );
  299. return null;
  300. }
  301. }
  302. catch( Exception e )
  303. {
  304. Debug.LogException( e );
  305. Object.DestroyImmediate( result );
  306. return null;
  307. }
  308. finally
  309. {
  310. if( loadPath != imagePath )
  311. {
  312. try
  313. {
  314. File.Delete( loadPath );
  315. }
  316. catch { }
  317. }
  318. }
  319. return result;
  320. }
  321. #if UNITY_2018_4_OR_NEWER && !NATIVE_CAMERA_DISABLE_ASYNC_FUNCTIONS
  322. public static async Task<Texture2D> LoadImageAtPathAsync( string imagePath, int maxSize = -1, bool markTextureNonReadable = true )
  323. {
  324. if( string.IsNullOrEmpty( imagePath ) )
  325. throw new ArgumentException( "Parameter 'imagePath' is null or empty!" );
  326. if( !File.Exists( imagePath ) )
  327. throw new FileNotFoundException( "File not found at " + imagePath );
  328. if( maxSize <= 0 )
  329. maxSize = SystemInfo.maxTextureSize;
  330. #if !UNITY_EDITOR && UNITY_ANDROID
  331. string temporaryImagePath = TemporaryImagePath; // Must be accessed from main thread
  332. string loadPath = await TryCallNativeAndroidFunctionOnSeparateThread( () => AJC.CallStatic<string>( "LoadImageAtPath", Context, imagePath, temporaryImagePath, maxSize ) );
  333. #elif !UNITY_EDITOR && UNITY_IOS
  334. string temporaryImagePath = TemporaryImagePath; // Must be accessed from main thread
  335. string loadPath = await Task.Run( () => _NativeCamera_LoadImageAtPath( imagePath, temporaryImagePath, maxSize ) );
  336. #else
  337. string loadPath = imagePath;
  338. #endif
  339. Texture2D result = null;
  340. using( UnityWebRequest www = UnityWebRequestTexture.GetTexture( "file://" + loadPath, markTextureNonReadable ) )
  341. {
  342. UnityWebRequestAsyncOperation asyncOperation = www.SendWebRequest();
  343. while( !asyncOperation.isDone )
  344. await Task.Yield();
  345. #if UNITY_2020_1_OR_NEWER
  346. if( www.result != UnityWebRequest.Result.Success )
  347. #else
  348. if( www.isNetworkError || www.isHttpError )
  349. #endif
  350. {
  351. Debug.LogWarning( "Couldn't use UnityWebRequest to load image, falling back to LoadImage: " + www.error );
  352. }
  353. else
  354. result = DownloadHandlerTexture.GetContent( www );
  355. }
  356. if( !result ) // Fallback to Texture2D.LoadImage if something goes wrong
  357. {
  358. string extension = Path.GetExtension( imagePath ).ToLowerInvariant();
  359. TextureFormat format = ( extension == ".jpg" || extension == ".jpeg" ) ? TextureFormat.RGB24 : TextureFormat.RGBA32;
  360. result = new Texture2D( 2, 2, format, true, false );
  361. try
  362. {
  363. if( !result.LoadImage( File.ReadAllBytes( loadPath ), markTextureNonReadable ) )
  364. {
  365. Debug.LogWarning( "Couldn't load image at path: " + loadPath );
  366. Object.DestroyImmediate( result );
  367. return null;
  368. }
  369. }
  370. catch( Exception e )
  371. {
  372. Debug.LogException( e );
  373. Object.DestroyImmediate( result );
  374. return null;
  375. }
  376. finally
  377. {
  378. if( loadPath != imagePath )
  379. {
  380. try
  381. {
  382. File.Delete( loadPath );
  383. }
  384. catch { }
  385. }
  386. }
  387. }
  388. return result;
  389. }
  390. #endif
  391. public static Texture2D GetVideoThumbnail( string videoPath, int maxSize = -1, double captureTimeInSeconds = -1.0, bool markTextureNonReadable = true, bool generateMipmaps = true, bool linearColorSpace = false )
  392. {
  393. if( maxSize <= 0 )
  394. maxSize = SystemInfo.maxTextureSize;
  395. #if !UNITY_EDITOR && UNITY_ANDROID
  396. string thumbnailPath = AJC.CallStatic<string>( "GetVideoThumbnail", Context, videoPath, TemporaryImagePath + ".png", false, maxSize, captureTimeInSeconds );
  397. #elif !UNITY_EDITOR && UNITY_IOS
  398. string thumbnailPath = _NativeCamera_GetVideoThumbnail( videoPath, TemporaryImagePath + ".png", maxSize, captureTimeInSeconds );
  399. #else
  400. string thumbnailPath = null;
  401. #endif
  402. if( !string.IsNullOrEmpty( thumbnailPath ) )
  403. return LoadImageAtPath( thumbnailPath, maxSize, markTextureNonReadable, generateMipmaps, linearColorSpace );
  404. else
  405. return null;
  406. }
  407. #if UNITY_2018_4_OR_NEWER && !NATIVE_CAMERA_DISABLE_ASYNC_FUNCTIONS
  408. public static async Task<Texture2D> GetVideoThumbnailAsync( string videoPath, int maxSize = -1, double captureTimeInSeconds = -1.0, bool markTextureNonReadable = true )
  409. {
  410. if( maxSize <= 0 )
  411. maxSize = SystemInfo.maxTextureSize;
  412. #if !UNITY_EDITOR && UNITY_ANDROID
  413. string temporaryImagePath = TemporaryImagePath; // Must be accessed from main thread
  414. string thumbnailPath = await TryCallNativeAndroidFunctionOnSeparateThread( () => AJC.CallStatic<string>( "GetVideoThumbnail", Context, videoPath, temporaryImagePath + ".png", false, maxSize, captureTimeInSeconds ) );
  415. #elif !UNITY_EDITOR && UNITY_IOS
  416. string temporaryImagePath = TemporaryImagePath; // Must be accessed from main thread
  417. string thumbnailPath = await Task.Run( () => _NativeCamera_GetVideoThumbnail( videoPath, temporaryImagePath + ".png", maxSize, captureTimeInSeconds ) );
  418. #else
  419. string thumbnailPath = null;
  420. #endif
  421. if( !string.IsNullOrEmpty( thumbnailPath ) )
  422. return await LoadImageAtPathAsync( thumbnailPath, maxSize, markTextureNonReadable );
  423. else
  424. return null;
  425. }
  426. private static async Task<T> TryCallNativeAndroidFunctionOnSeparateThread<T>( Func<T> function )
  427. {
  428. T result = default( T );
  429. bool hasResult = false;
  430. await Task.Run( () =>
  431. {
  432. if( AndroidJNI.AttachCurrentThread() != 0 )
  433. Debug.LogWarning( "Couldn't attach JNI thread, calling native function on the main thread" );
  434. else
  435. {
  436. try
  437. {
  438. result = function();
  439. hasResult = true;
  440. }
  441. finally
  442. {
  443. AndroidJNI.DetachCurrentThread();
  444. }
  445. }
  446. } );
  447. return hasResult ? result : function();
  448. }
  449. #endif
  450. public static ImageProperties GetImageProperties( string imagePath )
  451. {
  452. if( !File.Exists( imagePath ) )
  453. throw new FileNotFoundException( "File not found at " + imagePath );
  454. #if !UNITY_EDITOR && UNITY_ANDROID
  455. string value = AJC.CallStatic<string>( "GetImageProperties", Context, imagePath );
  456. #elif !UNITY_EDITOR && UNITY_IOS
  457. string value = _NativeCamera_GetImageProperties( imagePath );
  458. #else
  459. string value = null;
  460. #endif
  461. int width = 0, height = 0;
  462. string mimeType = null;
  463. ImageOrientation orientation = ImageOrientation.Unknown;
  464. if( !string.IsNullOrEmpty( value ) )
  465. {
  466. string[] properties = value.Split( '>' );
  467. if( properties != null && properties.Length >= 4 )
  468. {
  469. if( !int.TryParse( properties[0].Trim(), out width ) )
  470. width = 0;
  471. if( !int.TryParse( properties[1].Trim(), out height ) )
  472. height = 0;
  473. mimeType = properties[2].Trim();
  474. if( mimeType.Length == 0 )
  475. {
  476. string extension = Path.GetExtension( imagePath ).ToLowerInvariant();
  477. if( extension == ".png" )
  478. mimeType = "image/png";
  479. else if( extension == ".jpg" || extension == ".jpeg" )
  480. mimeType = "image/jpeg";
  481. else if( extension == ".gif" )
  482. mimeType = "image/gif";
  483. else if( extension == ".bmp" )
  484. mimeType = "image/bmp";
  485. else
  486. mimeType = null;
  487. }
  488. int orientationInt;
  489. if( int.TryParse( properties[3].Trim(), out orientationInt ) )
  490. orientation = (ImageOrientation) orientationInt;
  491. }
  492. }
  493. return new ImageProperties( width, height, mimeType, orientation );
  494. }
  495. public static VideoProperties GetVideoProperties( string videoPath )
  496. {
  497. if( !File.Exists( videoPath ) )
  498. throw new FileNotFoundException( "File not found at " + videoPath );
  499. #if !UNITY_EDITOR && UNITY_ANDROID
  500. string value = AJC.CallStatic<string>( "GetVideoProperties", Context, videoPath );
  501. #elif !UNITY_EDITOR && UNITY_IOS
  502. string value = _NativeCamera_GetVideoProperties( videoPath );
  503. #else
  504. string value = null;
  505. #endif
  506. int width = 0, height = 0;
  507. long duration = 0L;
  508. float rotation = 0f;
  509. if( !string.IsNullOrEmpty( value ) )
  510. {
  511. string[] properties = value.Split( '>' );
  512. if( properties != null && properties.Length >= 4 )
  513. {
  514. if( !int.TryParse( properties[0].Trim(), out width ) )
  515. width = 0;
  516. if( !int.TryParse( properties[1].Trim(), out height ) )
  517. height = 0;
  518. if( !long.TryParse( properties[2].Trim(), out duration ) )
  519. duration = 0L;
  520. if( !float.TryParse( properties[3].Trim().Replace( ',', '.' ), NumberStyles.Float, CultureInfo.InvariantCulture, out rotation ) )
  521. rotation = 0f;
  522. }
  523. }
  524. if( rotation == -90f )
  525. rotation = 270f;
  526. return new VideoProperties( width, height, duration, rotation );
  527. }
  528. #endregion
  529. }