300 lines
8.3 KiB
C#
300 lines
8.3 KiB
C#
// Copyright (c) 2021 homuler
|
|
//
|
|
// Use of this source code is governed by an MIT-style
|
|
// license that can be found in the LICENSE file or at
|
|
// https://opensource.org/licenses/MIT.
|
|
|
|
using System;
|
|
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using UnityEngine;
|
|
|
|
#if UNITY_ANDROID
|
|
using UnityEngine.Android;
|
|
#endif
|
|
|
|
namespace Mediapipe.Unity
|
|
{
|
|
public class WebCamSource : ImageSource
|
|
{
|
|
[Tooltip("For the default resolution, the one whose width is closest to this value will be chosen")]
|
|
[SerializeField] private int _preferableDefaultWidth = 1280;
|
|
|
|
private const string _TAG = nameof(WebCamSource);
|
|
|
|
[SerializeField]
|
|
private ResolutionStruct[] _defaultAvailableResolutions = new ResolutionStruct[] {
|
|
new ResolutionStruct(176, 144, 30),
|
|
new ResolutionStruct(320, 240, 30),
|
|
new ResolutionStruct(424, 240, 30),
|
|
new ResolutionStruct(640, 360, 30),
|
|
new ResolutionStruct(640, 480, 30),
|
|
new ResolutionStruct(848, 480, 30),
|
|
new ResolutionStruct(960, 540, 30),
|
|
new ResolutionStruct(1280, 720, 30),
|
|
new ResolutionStruct(1600, 896, 30),
|
|
new ResolutionStruct(1920, 1080, 30),
|
|
};
|
|
|
|
private static readonly object _PermissionLock = new object();
|
|
private static bool _IsPermitted = false;
|
|
|
|
private WebCamTexture _webCamTexture;
|
|
private WebCamTexture webCamTexture
|
|
{
|
|
get => _webCamTexture;
|
|
set
|
|
{
|
|
if (_webCamTexture != null)
|
|
{
|
|
_webCamTexture.Stop();
|
|
}
|
|
_webCamTexture = value;
|
|
}
|
|
}
|
|
|
|
public override int textureWidth => !isPrepared ? 0 : webCamTexture.width;
|
|
public override int textureHeight => !isPrepared ? 0 : webCamTexture.height;
|
|
|
|
public override bool isVerticallyFlipped => isPrepared && webCamTexture.videoVerticallyMirrored;
|
|
public override bool isFrontFacing => isPrepared && (webCamDevice is WebCamDevice valueOfWebCamDevice) && valueOfWebCamDevice.isFrontFacing;
|
|
public override RotationAngle rotation => !isPrepared ? RotationAngle.Rotation0 : (RotationAngle)webCamTexture.videoRotationAngle;
|
|
|
|
private WebCamDevice? _webCamDevice;
|
|
private WebCamDevice? webCamDevice
|
|
{
|
|
get => _webCamDevice;
|
|
set
|
|
{
|
|
if (_webCamDevice is WebCamDevice valueOfWebCamDevice)
|
|
{
|
|
if (value is WebCamDevice valueOfValue && valueOfValue.name == valueOfWebCamDevice.name)
|
|
{
|
|
// not changed
|
|
return;
|
|
}
|
|
}
|
|
else if (value == null)
|
|
{
|
|
// not changed
|
|
return;
|
|
}
|
|
_webCamDevice = value;
|
|
resolution = GetDefaultResolution();
|
|
}
|
|
}
|
|
public override string sourceName => (webCamDevice is WebCamDevice valueOfWebCamDevice) ? valueOfWebCamDevice.name : null;
|
|
|
|
private WebCamDevice[] _availableSources;
|
|
private WebCamDevice[] availableSources
|
|
{
|
|
get
|
|
{
|
|
if (_availableSources == null)
|
|
{
|
|
_availableSources = WebCamTexture.devices;
|
|
}
|
|
|
|
return _availableSources;
|
|
}
|
|
set => _availableSources = value;
|
|
}
|
|
|
|
public override string[] sourceCandidateNames => availableSources?.Select(device => device.name).ToArray();
|
|
|
|
#pragma warning disable IDE0025
|
|
public override ResolutionStruct[] availableResolutions
|
|
{
|
|
get
|
|
{
|
|
#if (UNITY_ANDROID || UNITY_IOS) && !UNITY_EDITOR
|
|
if (webCamDevice is WebCamDevice valueOfWebCamDevice) {
|
|
return valueOfWebCamDevice.availableResolutions.Select(resolution => new ResolutionStruct(resolution)).ToArray();
|
|
}
|
|
#endif
|
|
return webCamDevice == null ? null : _defaultAvailableResolutions;
|
|
}
|
|
}
|
|
#pragma warning restore IDE0025
|
|
|
|
public override bool isPrepared => webCamTexture != null;
|
|
public override bool isPlaying => webCamTexture != null && webCamTexture.isPlaying;
|
|
private bool _isInitialized;
|
|
|
|
private IEnumerator Start()
|
|
{
|
|
yield return GetPermission();
|
|
|
|
if (!_IsPermitted)
|
|
{
|
|
_isInitialized = true;
|
|
yield break;
|
|
}
|
|
|
|
availableSources = WebCamTexture.devices;
|
|
|
|
if (availableSources != null && availableSources.Length > 0)
|
|
{
|
|
webCamDevice = availableSources[0];
|
|
}
|
|
|
|
_isInitialized = true;
|
|
}
|
|
|
|
private IEnumerator GetPermission()
|
|
{
|
|
lock (_PermissionLock)
|
|
{
|
|
if (_IsPermitted)
|
|
{
|
|
yield break;
|
|
}
|
|
|
|
#if UNITY_ANDROID
|
|
if (!Permission.HasUserAuthorizedPermission(Permission.Camera))
|
|
{
|
|
Permission.RequestUserPermission(Permission.Camera);
|
|
yield return new WaitForSeconds(0.1f);
|
|
}
|
|
#elif UNITY_IOS
|
|
if (!Application.HasUserAuthorization(UserAuthorization.WebCam)) {
|
|
yield return Application.RequestUserAuthorization(UserAuthorization.WebCam);
|
|
}
|
|
#endif
|
|
|
|
#if UNITY_ANDROID
|
|
if (!Permission.HasUserAuthorizedPermission(Permission.Camera))
|
|
{
|
|
Logger.LogWarning(_TAG, "Not permitted to use Camera");
|
|
yield break;
|
|
}
|
|
#elif UNITY_IOS
|
|
if (!Application.HasUserAuthorization(UserAuthorization.WebCam)) {
|
|
Logger.LogWarning(_TAG, "Not permitted to use WebCam");
|
|
yield break;
|
|
}
|
|
#endif
|
|
_IsPermitted = true;
|
|
|
|
yield return new WaitForEndOfFrame();
|
|
}
|
|
}
|
|
|
|
public override void SelectSource(int sourceId)
|
|
{
|
|
if (sourceId < 0 || sourceId >= availableSources.Length)
|
|
{
|
|
throw new ArgumentException($"Invalid source ID: {sourceId}");
|
|
}
|
|
|
|
webCamDevice = availableSources[sourceId];
|
|
}
|
|
|
|
public override IEnumerator Play()
|
|
{
|
|
yield return new WaitUntil(() => _isInitialized);
|
|
if (!_IsPermitted)
|
|
{
|
|
throw new InvalidOperationException("Not permitted to access cameras");
|
|
}
|
|
|
|
InitializeWebCamTexture();
|
|
webCamTexture.Play();
|
|
yield return WaitForWebCamTexture();
|
|
}
|
|
|
|
public override IEnumerator Resume()
|
|
{
|
|
if (!isPrepared)
|
|
{
|
|
throw new InvalidOperationException("WebCamTexture is not prepared yet");
|
|
}
|
|
if (!webCamTexture.isPlaying)
|
|
{
|
|
webCamTexture.Play();
|
|
}
|
|
yield return WaitForWebCamTexture();
|
|
}
|
|
|
|
public override void Pause()
|
|
{
|
|
if (isPlaying)
|
|
{
|
|
webCamTexture.Pause();
|
|
}
|
|
}
|
|
|
|
public override void Stop()
|
|
{
|
|
if (webCamTexture != null)
|
|
{
|
|
webCamTexture.Stop();
|
|
}
|
|
webCamTexture = null;
|
|
}
|
|
|
|
public override Texture GetCurrentTexture()
|
|
{
|
|
return webCamTexture;
|
|
}
|
|
|
|
private ResolutionStruct GetDefaultResolution()
|
|
{
|
|
var resolutions = availableResolutions;
|
|
return resolutions == null || resolutions.Length == 0 ? new ResolutionStruct() : resolutions.OrderBy(resolution => resolution, new ResolutionStructComparer(_preferableDefaultWidth)).First();
|
|
}
|
|
|
|
private void InitializeWebCamTexture()
|
|
{
|
|
Stop();
|
|
if (webCamDevice is WebCamDevice valueOfWebCamDevice)
|
|
{
|
|
webCamTexture = new WebCamTexture(valueOfWebCamDevice.name, resolution.width, resolution.height, (int)resolution.frameRate);
|
|
return;
|
|
}
|
|
throw new InvalidOperationException("Cannot initialize WebCamTexture because WebCamDevice is not selected");
|
|
}
|
|
|
|
private IEnumerator WaitForWebCamTexture()
|
|
{
|
|
const int timeoutFrame = 2000;
|
|
var count = 0;
|
|
Logger.LogVerbose("Waiting for WebCamTexture to start");
|
|
yield return new WaitUntil(() => count++ > timeoutFrame || webCamTexture.width > 16);
|
|
|
|
if (webCamTexture.width <= 16)
|
|
{
|
|
throw new TimeoutException("Failed to start WebCam");
|
|
}
|
|
}
|
|
|
|
private class ResolutionStructComparer : IComparer<ResolutionStruct>
|
|
{
|
|
private readonly int _preferableDefaultWidth;
|
|
|
|
public ResolutionStructComparer(int preferableDefaultWidth)
|
|
{
|
|
_preferableDefaultWidth = preferableDefaultWidth;
|
|
}
|
|
|
|
public int Compare(ResolutionStruct a, ResolutionStruct b)
|
|
{
|
|
var aDiff = Mathf.Abs(a.width - _preferableDefaultWidth);
|
|
var bDiff = Mathf.Abs(b.width - _preferableDefaultWidth);
|
|
if (aDiff != bDiff)
|
|
{
|
|
return aDiff - bDiff;
|
|
}
|
|
if (a.height != b.height)
|
|
{
|
|
// prefer smaller height
|
|
return a.height - b.height;
|
|
}
|
|
// prefer smaller frame rate
|
|
return (int)(a.frameRate - b.frameRate);
|
|
}
|
|
}
|
|
}
|
|
}
|