- Added trees outside the stable.
- Added a video player with horse songs preloaded. - Adjusted post-processing to be less harsh outdoors, and more visible indoors.
This commit is contained in:
@@ -0,0 +1,9 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9c5f6cc7a68822c4c906fad89505801a
|
||||
folderAsset: yes
|
||||
timeCreated: 1592333515
|
||||
licenseType: Pro
|
||||
DefaultImporter:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,118 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace RenderHeads.Media.AVProVideo
|
||||
{
|
||||
[System.Serializable]
|
||||
[CreateAssetMenu(fileName = "MediaReference", menuName = "AVPro Video/Media Reference", order = 51)]
|
||||
public class MediaReference : ScriptableObject
|
||||
{
|
||||
[SerializeField] string _alias = string.Empty;
|
||||
public string Alias { get { return _alias; } set { _alias = value; } }
|
||||
|
||||
[SerializeField] MediaPath _mediaPath = new MediaPath();
|
||||
public MediaPath MediaPath { get { return _mediaPath; } set { _mediaPath = value; } }
|
||||
|
||||
[Header("Media Hints")]
|
||||
|
||||
[SerializeField] MediaHints _hints = MediaHints.Default;
|
||||
public MediaHints Hints { get { return _hints; } set { _hints = value; } }
|
||||
|
||||
[Header("Platform Overrides")]
|
||||
|
||||
[SerializeField] MediaReference _macOS = null;
|
||||
[SerializeField] MediaReference _windows = null;
|
||||
[SerializeField] MediaReference _android = null;
|
||||
[SerializeField] MediaReference _iOS = null;
|
||||
[SerializeField] MediaReference _tvOS = null;
|
||||
[SerializeField] MediaReference _windowsUWP = null;
|
||||
[SerializeField] MediaReference _webGL = null;
|
||||
|
||||
#if UNITY_EDITOR
|
||||
[SerializeField, HideInInspector] byte[] _preview = null;
|
||||
|
||||
public Texture2D GeneratePreview(Texture2D texture)
|
||||
{
|
||||
_preview = null;
|
||||
if (texture)
|
||||
{
|
||||
texture.Apply(true, false);
|
||||
_preview = texture.GetRawTextureData();
|
||||
}
|
||||
UnityEditor.EditorUtility.SetDirty(this);
|
||||
return texture;
|
||||
}
|
||||
|
||||
public bool GetPreview(Texture2D texture)
|
||||
{
|
||||
if (_preview != null && _preview.Length > 0 && _preview.Length > 128*128*4)
|
||||
{
|
||||
texture.LoadRawTextureData(_preview);
|
||||
texture.Apply(true, false);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
public MediaReference GetCurrentPlatformMediaReference()
|
||||
{
|
||||
MediaReference result = null;
|
||||
|
||||
#if (UNITY_EDITOR_OSX && UNITY_IOS) || (!UNITY_EDITOR && UNITY_IOS)
|
||||
result = GetPlatformMediaReference(Platform.iOS);
|
||||
#elif (UNITY_EDITOR_OSX && UNITY_TVOS) || (!UNITY_EDITOR && UNITY_TVOS)
|
||||
result = GetPlatformMediaReference(Platform.tvOS);
|
||||
#elif (UNITY_EDITOR_OSX || (!UNITY_EDITOR && UNITY_STANDALONE_OSX))
|
||||
result = GetPlatformMediaReference(Platform.MacOSX);
|
||||
#elif (UNITY_EDITOR_WIN) || (!UNITY_EDITOR && UNITY_STANDALONE_WIN)
|
||||
result = GetPlatformMediaReference(Platform.Windows);
|
||||
#elif (!UNITY_EDITOR && UNITY_WSA_10_0)
|
||||
result = GetPlatformMediaReference(Platform.WindowsUWP);
|
||||
#elif (!UNITY_EDITOR && UNITY_ANDROID)
|
||||
result = GetPlatformMediaReference(Platform.Android);
|
||||
#elif (!UNITY_EDITOR && UNITY_WEBGL)
|
||||
result = GetPlatformMediaReference(Platform.WebGL);
|
||||
#endif
|
||||
|
||||
if (result == null)
|
||||
{
|
||||
result = this;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public MediaReference GetPlatformMediaReference(Platform platform)
|
||||
{
|
||||
MediaReference result = null;
|
||||
|
||||
switch (platform)
|
||||
{
|
||||
case Platform.iOS:
|
||||
result = _iOS;
|
||||
break;
|
||||
case Platform.tvOS:
|
||||
result = _tvOS;
|
||||
break;
|
||||
case Platform.MacOSX:
|
||||
result = _macOS;
|
||||
break;
|
||||
case Platform.Windows:
|
||||
result = _windows;
|
||||
break;
|
||||
case Platform.WindowsUWP:
|
||||
result = _windowsUWP;
|
||||
break;
|
||||
case Platform.Android:
|
||||
result = _android;
|
||||
break;
|
||||
case Platform.WebGL:
|
||||
result = _webGL;
|
||||
break;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8b1c70b7e7502564e93d418de9017d1f
|
||||
timeCreated: 1592337480
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {fileID: 2800000, guid: bb83b41b53a59874692b83eab5873998, type: 3}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,9 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 40d7664ce355730488a96ff5305f1b5d
|
||||
folderAsset: yes
|
||||
timeCreated: 1438698284
|
||||
licenseType: Pro
|
||||
DefaultImporter:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,231 @@
|
||||
#if UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX || UNITY_IOS || UNITY_TVOS
|
||||
#define UNITY_PLATFORM_SUPPORTS_YPCBCR
|
||||
#endif
|
||||
|
||||
using UnityEngine;
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Copyright 2015-2022 RenderHeads Ltd. All rights reserved.
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
namespace RenderHeads.Media.AVProVideo
|
||||
{
|
||||
/// <summary>
|
||||
/// Sets up a material to display the video from a MediaPlayer
|
||||
/// </summary>
|
||||
[AddComponentMenu("AVPro Video/Apply To Material", 300)]
|
||||
[HelpURL("https://www.renderheads.com/products/avpro-video/")]
|
||||
public sealed class ApplyToMaterial : ApplyToBase
|
||||
{
|
||||
[Header("Display")]
|
||||
[Space(8f)]
|
||||
|
||||
[Tooltip("Default texture to display when the video texture is preparing")]
|
||||
[SerializeField] Texture2D _defaultTexture = null;
|
||||
|
||||
public Texture2D DefaultTexture
|
||||
{
|
||||
get { return _defaultTexture; }
|
||||
set { if (_defaultTexture != value) { _defaultTexture = value; _isDirty = true; } }
|
||||
}
|
||||
|
||||
[Space(8f)]
|
||||
[Header("Material Target")]
|
||||
|
||||
[SerializeField] Material _material = null;
|
||||
|
||||
public Material Material
|
||||
{
|
||||
get { return _material; }
|
||||
set { if (_material != value) { _material = value; _isDirty = true; } }
|
||||
}
|
||||
|
||||
[SerializeField] string _texturePropertyName = Helper.UnityBaseTextureName;
|
||||
|
||||
public string TexturePropertyName
|
||||
{
|
||||
get { return _texturePropertyName; }
|
||||
set
|
||||
{
|
||||
if (_texturePropertyName != value)
|
||||
{
|
||||
_texturePropertyName = value;
|
||||
// TODO: if the property changes, remove it from the perioud SetTexture()
|
||||
_propTexture = new LazyShaderProperty(_texturePropertyName);
|
||||
_isDirty = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[SerializeField] Vector2 _offset = Vector2.zero;
|
||||
|
||||
public Vector2 Offset
|
||||
{
|
||||
get { return _offset; }
|
||||
set { if (_offset != value) { _offset = value; _isDirty = true; } }
|
||||
}
|
||||
|
||||
[SerializeField] Vector2 _scale = Vector2.one;
|
||||
|
||||
public Vector2 Scale
|
||||
{
|
||||
get { return _scale; }
|
||||
set { if (_scale != value) { _scale = value; _isDirty = true; } }
|
||||
}
|
||||
|
||||
private Texture _lastTextureApplied;
|
||||
private LazyShaderProperty _propTexture;
|
||||
|
||||
private Texture _originalTexture;
|
||||
private Vector2 _originalScale = Vector2.one;
|
||||
private Vector2 _originalOffset = Vector2.zero;
|
||||
|
||||
// We do a LateUpdate() to allow for any changes in the texture that may have happened in Update()
|
||||
private void LateUpdate()
|
||||
{
|
||||
Apply();
|
||||
}
|
||||
|
||||
public override void Apply()
|
||||
{
|
||||
bool applied = false;
|
||||
|
||||
if (_media != null && _media.TextureProducer != null)
|
||||
{
|
||||
Texture resamplerTex = _media.FrameResampler == null || _media.FrameResampler.OutputTexture == null ? null : _media.FrameResampler.OutputTexture[0];
|
||||
Texture texture = _media.UseResampler ? resamplerTex : _media.TextureProducer.GetTexture(0);
|
||||
if (texture != null)
|
||||
{
|
||||
// Check for changing texture
|
||||
if (texture != _lastTextureApplied)
|
||||
{
|
||||
_isDirty = true;
|
||||
}
|
||||
|
||||
if (_isDirty)
|
||||
{
|
||||
int planeCount = _media.UseResampler ? 1 : _media.TextureProducer.GetTextureCount();
|
||||
for (int plane = 0; plane < planeCount; ++plane)
|
||||
{
|
||||
Texture resamplerTexPlane = _media.FrameResampler == null || _media.FrameResampler.OutputTexture == null ? null : _media.FrameResampler.OutputTexture[plane];
|
||||
texture = _media.UseResampler ? resamplerTexPlane : _media.TextureProducer.GetTexture(plane);
|
||||
if (texture != null)
|
||||
{
|
||||
ApplyMapping(texture, _media.TextureProducer.RequiresVerticalFlip(), plane);
|
||||
}
|
||||
}
|
||||
}
|
||||
applied = true;
|
||||
}
|
||||
}
|
||||
|
||||
// If the media didn't apply a texture, then try to apply the default texture
|
||||
if (!applied)
|
||||
{
|
||||
if (_defaultTexture != _lastTextureApplied)
|
||||
{
|
||||
_isDirty = true;
|
||||
}
|
||||
if (_isDirty)
|
||||
{
|
||||
#if UNITY_PLATFORM_SUPPORTS_YPCBCR
|
||||
if (_material != null && _material.HasProperty(VideoRender.PropUseYpCbCr.Id))
|
||||
{
|
||||
_material.DisableKeyword(VideoRender.Keyword_UseYpCbCr);
|
||||
}
|
||||
#endif
|
||||
ApplyMapping(_defaultTexture, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void ApplyMapping(Texture texture, bool requiresYFlip, int plane = 0)
|
||||
{
|
||||
if (_material != null)
|
||||
{
|
||||
_isDirty = false;
|
||||
|
||||
if (plane == 0)
|
||||
{
|
||||
VideoRender.SetupMaterialForMedia(_material, _media, _propTexture.Id, texture, texture == _defaultTexture);
|
||||
_lastTextureApplied = texture;
|
||||
|
||||
#if (!UNITY_EDITOR && UNITY_ANDROID)
|
||||
if (texture == _defaultTexture) { _material.EnableKeyword("USING_DEFAULT_TEXTURE"); }
|
||||
else { _material.DisableKeyword("USING_DEFAULT_TEXTURE"); }
|
||||
#endif
|
||||
|
||||
if (texture != null)
|
||||
{
|
||||
if (requiresYFlip)
|
||||
{
|
||||
_material.SetTextureScale(_propTexture.Id, new Vector2(_scale.x, -_scale.y));
|
||||
_material.SetTextureOffset(_propTexture.Id, Vector2.up + _offset);
|
||||
}
|
||||
else
|
||||
{
|
||||
_material.SetTextureScale(_propTexture.Id, _scale);
|
||||
_material.SetTextureOffset(_propTexture.Id, _offset);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (plane == 1)
|
||||
{
|
||||
if (texture != null)
|
||||
{
|
||||
if (requiresYFlip)
|
||||
{
|
||||
_material.SetTextureScale(VideoRender.PropChromaTex.Id, new Vector2(_scale.x, -_scale.y));
|
||||
_material.SetTextureOffset(VideoRender.PropChromaTex.Id, Vector2.up + _offset);
|
||||
}
|
||||
else
|
||||
{
|
||||
_material.SetTextureScale(VideoRender.PropChromaTex.Id, _scale);
|
||||
_material.SetTextureOffset(VideoRender.PropChromaTex.Id, _offset);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override void SaveProperties()
|
||||
{
|
||||
if (_material != null)
|
||||
{
|
||||
if (string.IsNullOrEmpty(_texturePropertyName))
|
||||
{
|
||||
_originalTexture = _material.mainTexture;
|
||||
_originalScale = _material.mainTextureScale;
|
||||
_originalOffset = _material.mainTextureOffset;
|
||||
}
|
||||
else
|
||||
{
|
||||
_originalTexture = _material.GetTexture(_texturePropertyName);
|
||||
_originalScale = _material.GetTextureScale(_texturePropertyName);
|
||||
_originalOffset = _material.GetTextureOffset(_texturePropertyName);
|
||||
}
|
||||
}
|
||||
_propTexture = new LazyShaderProperty(_texturePropertyName);
|
||||
}
|
||||
|
||||
protected override void RestoreProperties()
|
||||
{
|
||||
if (_material != null)
|
||||
{
|
||||
if (string.IsNullOrEmpty(_texturePropertyName))
|
||||
{
|
||||
_material.mainTexture = _originalTexture;
|
||||
_material.mainTextureScale = _originalScale;
|
||||
_material.mainTextureOffset = _originalOffset;
|
||||
}
|
||||
else
|
||||
{
|
||||
_material.SetTexture(_texturePropertyName, _originalTexture);
|
||||
_material.SetTextureScale(_texturePropertyName, _originalScale);
|
||||
_material.SetTextureOffset(_texturePropertyName, _originalOffset);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d2feedce2e2e63647b8f875ec0894a15
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {fileID: 2800000, guid: bb83b41b53a59874692b83eab5873998, type: 3}
|
||||
userData:
|
||||
@@ -0,0 +1,248 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.Serialization;
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Copyright 2015-2022 RenderHeads Ltd. All rights reserved.
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
namespace RenderHeads.Media.AVProVideo
|
||||
{
|
||||
/// <summary>
|
||||
/// Sets up a mesh to display the video from a MediaPlayer
|
||||
/// </summary>
|
||||
[AddComponentMenu("AVPro Video/Apply To Mesh", 300)]
|
||||
[HelpURL("https://www.renderheads.com/products/avpro-video/")]
|
||||
public sealed class ApplyToMesh : ApplyToBase
|
||||
{
|
||||
// TODO: add specific material / material index to target in the mesh if there are multiple materials
|
||||
|
||||
[Space(8f)]
|
||||
[Header("Display")]
|
||||
|
||||
[Tooltip("Default texture to display when the video texture is preparing")]
|
||||
[SerializeField] Texture2D _defaultTexture = null;
|
||||
|
||||
public Texture2D DefaultTexture
|
||||
{
|
||||
get { return _defaultTexture; }
|
||||
set { ChangeDefaultTexture(value); }
|
||||
}
|
||||
|
||||
[Space(8f)]
|
||||
[FormerlySerializedAs("_mesh")]
|
||||
[Header("Renderer Target")]
|
||||
[SerializeField] Renderer _renderer = null;
|
||||
|
||||
public Renderer MeshRenderer
|
||||
{
|
||||
get { return _renderer; }
|
||||
set { ChangeRenderer(value); }
|
||||
}
|
||||
|
||||
[SerializeField] int _materialIndex = -1;
|
||||
|
||||
public int MaterialIndex
|
||||
{
|
||||
get { return _materialIndex; }
|
||||
set { _materialIndex = value; }
|
||||
}
|
||||
|
||||
private void ChangeDefaultTexture(Texture2D texture)
|
||||
{
|
||||
if (_defaultTexture != texture)
|
||||
{
|
||||
_defaultTexture = texture;
|
||||
ForceUpdate();
|
||||
}
|
||||
}
|
||||
private void ChangeRenderer(Renderer renderer)
|
||||
{
|
||||
if (_renderer != renderer)
|
||||
{
|
||||
if (_renderer)
|
||||
{
|
||||
// TODO: Remove from renderer
|
||||
}
|
||||
_renderer = renderer;
|
||||
if (_renderer)
|
||||
{
|
||||
ForceUpdate();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[SerializeField] string _texturePropertyName = Helper.UnityBaseTextureName;
|
||||
|
||||
public string TexturePropertyName
|
||||
{
|
||||
get { return _texturePropertyName; }
|
||||
set
|
||||
{
|
||||
if (_texturePropertyName != value)
|
||||
{
|
||||
_texturePropertyName = value;
|
||||
// TODO: if the property changes, remove it from the perioud SetTexture()
|
||||
_propTexture = new LazyShaderProperty(_texturePropertyName);
|
||||
_isDirty = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[SerializeField] Vector2 _offset = Vector2.zero;
|
||||
|
||||
public Vector2 Offset
|
||||
{
|
||||
get { return _offset; }
|
||||
set { if (_offset != value) { _offset = value; _isDirty = true; } }
|
||||
}
|
||||
|
||||
[SerializeField] Vector2 _scale = Vector2.one;
|
||||
|
||||
public Vector2 Scale
|
||||
{
|
||||
get { return _scale; }
|
||||
set { if (_scale != value) { _scale = value; _isDirty = true; } }
|
||||
}
|
||||
|
||||
private Texture _lastTextureApplied;
|
||||
private LazyShaderProperty _propTexture;
|
||||
|
||||
// We do a LateUpdate() to allow for any changes in the texture that may have happened in Update()
|
||||
private void LateUpdate()
|
||||
{
|
||||
Apply();
|
||||
}
|
||||
|
||||
public override void Apply()
|
||||
{
|
||||
bool applied = false;
|
||||
|
||||
// Try to apply texture from media
|
||||
if (_media != null && _media.TextureProducer != null)
|
||||
{
|
||||
Texture resamplerTex = _media.FrameResampler == null || _media.FrameResampler.OutputTexture == null ? null : _media.FrameResampler.OutputTexture[0];
|
||||
Texture texture = _media.UseResampler ? resamplerTex : _media.TextureProducer.GetTexture(0);
|
||||
if (texture != null)
|
||||
{
|
||||
// Check for changing texture
|
||||
if (texture != _lastTextureApplied)
|
||||
{
|
||||
_isDirty = true;
|
||||
}
|
||||
|
||||
if (_isDirty)
|
||||
{
|
||||
int planeCount = _media.UseResampler ? 1 : _media.TextureProducer.GetTextureCount();
|
||||
for (int plane = 0; plane < planeCount; plane++)
|
||||
{
|
||||
Texture resamplerTexPlane = _media.FrameResampler == null || _media.FrameResampler.OutputTexture == null ? null : _media.FrameResampler.OutputTexture[plane];
|
||||
texture = _media.UseResampler ? resamplerTexPlane : _media.TextureProducer.GetTexture(plane);
|
||||
if (texture != null)
|
||||
{
|
||||
ApplyMapping(texture, _media.TextureProducer.RequiresVerticalFlip(), plane, _materialIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
applied = true;
|
||||
}
|
||||
}
|
||||
|
||||
// If the media didn't apply a texture, then try to apply the default texture
|
||||
if (!applied)
|
||||
{
|
||||
if (_defaultTexture != _lastTextureApplied)
|
||||
{
|
||||
_isDirty = true;
|
||||
}
|
||||
if (_isDirty)
|
||||
{
|
||||
ApplyMapping(_defaultTexture, false, 0, _materialIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ApplyMapping(Texture texture, bool requiresYFlip, int plane, int materialIndex = -1)
|
||||
{
|
||||
if (_renderer != null)
|
||||
{
|
||||
_isDirty = false;
|
||||
|
||||
Material[] meshMaterials = _renderer.materials;
|
||||
if (meshMaterials != null)
|
||||
{
|
||||
for (int i = 0; i < meshMaterials.Length; i++)
|
||||
{
|
||||
if (_materialIndex < 0 || i == _materialIndex)
|
||||
{
|
||||
Material mat = meshMaterials[i];
|
||||
if (mat != null)
|
||||
{
|
||||
if (plane == 0)
|
||||
{
|
||||
VideoRender.SetupMaterialForMedia(mat, _media, _propTexture.Id, texture, texture == _defaultTexture);
|
||||
_lastTextureApplied = texture;
|
||||
|
||||
#if (!UNITY_EDITOR && UNITY_ANDROID)
|
||||
if(texture == _defaultTexture) { mat.EnableKeyword("USING_DEFAULT_TEXTURE"); }
|
||||
else { mat.DisableKeyword("USING_DEFAULT_TEXTURE"); }
|
||||
#endif
|
||||
|
||||
if (texture != null)
|
||||
{
|
||||
if (requiresYFlip)
|
||||
{
|
||||
mat.SetTextureScale(_propTexture.Id, new Vector2(_scale.x, -_scale.y));
|
||||
mat.SetTextureOffset(_propTexture.Id, Vector2.up + _offset);
|
||||
}
|
||||
else
|
||||
{
|
||||
mat.SetTextureScale(_propTexture.Id, _scale);
|
||||
mat.SetTextureOffset(_propTexture.Id, _offset);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (plane == 1)
|
||||
{
|
||||
if (texture != null)
|
||||
{
|
||||
if (requiresYFlip)
|
||||
{
|
||||
mat.SetTextureScale(VideoRender.PropChromaTex.Id, new Vector2(_scale.x, -_scale.y));
|
||||
mat.SetTextureOffset(VideoRender.PropChromaTex.Id, Vector2.up + _offset);
|
||||
}
|
||||
else
|
||||
{
|
||||
mat.SetTextureScale(VideoRender.PropChromaTex.Id, _scale);
|
||||
mat.SetTextureOffset(VideoRender.PropChromaTex.Id, _offset);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnEnable()
|
||||
{
|
||||
if (_renderer == null)
|
||||
{
|
||||
_renderer = this.GetComponent<MeshRenderer>();
|
||||
if (_renderer == null)
|
||||
{
|
||||
Debug.LogWarning("[AVProVideo] No MeshRenderer set or found in gameobject");
|
||||
}
|
||||
}
|
||||
|
||||
_propTexture = new LazyShaderProperty(_texturePropertyName);
|
||||
|
||||
ForceUpdate();
|
||||
}
|
||||
|
||||
protected override void OnDisable()
|
||||
{
|
||||
ApplyMapping(_defaultTexture, false, 0, _materialIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f6d1977a52888584496b1acc7e998011
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {fileID: 2800000, guid: bb83b41b53a59874692b83eab5873998, type: 3}
|
||||
userData:
|
||||
@@ -0,0 +1,81 @@
|
||||
using UnityEngine;
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Copyright 2019-2022 RenderHeads Ltd. All rights reserved.
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
namespace RenderHeads.Media.AVProVideo
|
||||
{
|
||||
/// Allows per-channel volume control
|
||||
/// Currently supported on Windows and UWP (Media Foundation API only), macOS, iOS, tvOS and Android (ExoPlayer API only)
|
||||
[AddComponentMenu("AVPro Video/Audio Channel Mixer", 401)]
|
||||
[HelpURL("https://www.renderheads.com/products/avpro-video/")]
|
||||
public class AudioChannelMixer : MonoBehaviour
|
||||
{
|
||||
const int MaxChannels = 8;
|
||||
|
||||
[Range(0f, 1f)]
|
||||
[SerializeField] float[] _channels = null;
|
||||
|
||||
/// Range 0.0 to 1.0
|
||||
public float[] Channel
|
||||
{
|
||||
get { return _channels; }
|
||||
set { _channels = value; }
|
||||
}
|
||||
|
||||
void Reset()
|
||||
{
|
||||
_channels = new float[MaxChannels];
|
||||
for (int i = 0; i < MaxChannels; i++)
|
||||
{
|
||||
_channels[i] = 1f;
|
||||
}
|
||||
}
|
||||
|
||||
void ChangeChannelCount(int numChannels)
|
||||
{
|
||||
float[] channels = new float[numChannels];
|
||||
if (_channels != null && _channels.Length != 0)
|
||||
{
|
||||
for (int i = 0; i < channels.Length; i++)
|
||||
{
|
||||
if (i < _channels.Length)
|
||||
{
|
||||
channels[i] = _channels[i];
|
||||
}
|
||||
else
|
||||
{
|
||||
channels[i] = 1f;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = 0; i < numChannels; i++)
|
||||
{
|
||||
channels[i] = 1f;
|
||||
}
|
||||
}
|
||||
_channels = channels;
|
||||
}
|
||||
|
||||
void OnAudioFilterRead(float[] data, int channels)
|
||||
{
|
||||
if (channels != _channels.Length)
|
||||
{
|
||||
ChangeChannelCount(channels);
|
||||
}
|
||||
int k = 0;
|
||||
int numSamples = data.Length / channels;
|
||||
for (int j = 0; j < numSamples; j++)
|
||||
{
|
||||
for (int i = 0; i < channels; i++)
|
||||
{
|
||||
data[k] *= _channels[i];
|
||||
k++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 383a68f1e3e94be4b84df59dd26074db
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {fileID: 2800000, guid: bb83b41b53a59874692b83eab5873998, type: 3}
|
||||
userData:
|
||||
@@ -0,0 +1,162 @@
|
||||
using UnityEngine;
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Copyright 2015-2022 RenderHeads Ltd. All rights reserved.
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
namespace RenderHeads.Media.AVProVideo
|
||||
{
|
||||
/// <summary>
|
||||
/// Audio is grabbed from the MediaPlayer and rendered via Unity AudioSource
|
||||
/// This allows audio to have 3D spatial control, effects applied and to be spatialised for VR
|
||||
/// Currently supported on Windows and UWP (Media Foundation API only), macOS, iOS, tvOS and Android (ExoPlayer API only)
|
||||
/// </summary>
|
||||
[RequireComponent(typeof(AudioSource))]
|
||||
[AddComponentMenu("AVPro Video/Audio Output", 400)]
|
||||
[HelpURL("https://www.renderheads.com/products/avpro-video/")]
|
||||
public class AudioOutput : MonoBehaviour
|
||||
{
|
||||
public enum AudioOutputMode
|
||||
{
|
||||
OneToAllChannels,
|
||||
MultipleChannels
|
||||
}
|
||||
|
||||
[SerializeField] MediaPlayer _mediaPlayer = null;
|
||||
[SerializeField] AudioOutputMode _audioOutputMode = AudioOutputMode.MultipleChannels;
|
||||
[HideInInspector, SerializeField] int _channelMask = 0xffff;
|
||||
[SerializeField] bool _supportPositionalAudio = false;
|
||||
|
||||
public MediaPlayer Player
|
||||
{
|
||||
get { return _mediaPlayer; }
|
||||
set { ChangeMediaPlayer(value); }
|
||||
}
|
||||
|
||||
public AudioOutputMode OutputMode
|
||||
{
|
||||
get { return _audioOutputMode; }
|
||||
set { _audioOutputMode = value; }
|
||||
}
|
||||
|
||||
public int ChannelMask
|
||||
{
|
||||
get { return _channelMask; }
|
||||
set { _channelMask = value; }
|
||||
}
|
||||
|
||||
private AudioSource _audioSource;
|
||||
|
||||
void Awake()
|
||||
{
|
||||
_audioSource = this.GetComponent<AudioSource>();
|
||||
Debug.Assert(_audioSource != null);
|
||||
}
|
||||
|
||||
void Start()
|
||||
{
|
||||
AudioSettings.OnAudioConfigurationChanged += OnAudioConfigurationChanged;
|
||||
ChangeMediaPlayer(_mediaPlayer);
|
||||
}
|
||||
|
||||
void OnAudioConfigurationChanged(bool deviceChanged)
|
||||
{
|
||||
if (_mediaPlayer == null || _mediaPlayer.Control == null)
|
||||
return;
|
||||
_mediaPlayer.Control.AudioConfigurationChanged(deviceChanged);
|
||||
}
|
||||
|
||||
void OnDestroy()
|
||||
{
|
||||
ChangeMediaPlayer(null);
|
||||
}
|
||||
|
||||
void Update()
|
||||
{
|
||||
if (_mediaPlayer != null && _mediaPlayer.Control != null && _mediaPlayer.Control.IsPlaying())
|
||||
{
|
||||
ApplyAudioSettings(_mediaPlayer, _audioSource);
|
||||
}
|
||||
}
|
||||
|
||||
public AudioSource GetAudioSource()
|
||||
{
|
||||
return _audioSource;
|
||||
}
|
||||
|
||||
public void ChangeMediaPlayer(MediaPlayer newPlayer)
|
||||
{
|
||||
// When changing the media player, handle event subscriptions
|
||||
if (_mediaPlayer != null)
|
||||
{
|
||||
_mediaPlayer.AudioSource = null;
|
||||
_mediaPlayer.Events.RemoveListener(OnMediaPlayerEvent);
|
||||
_mediaPlayer = null;
|
||||
}
|
||||
|
||||
_mediaPlayer = newPlayer;
|
||||
if (_mediaPlayer != null)
|
||||
{
|
||||
_mediaPlayer.Events.AddListener(OnMediaPlayerEvent);
|
||||
_mediaPlayer.AudioSource = _audioSource;
|
||||
}
|
||||
|
||||
if (_supportPositionalAudio)
|
||||
{
|
||||
if (_audioSource.clip == null)
|
||||
{
|
||||
// Position audio is implemented from hints found on this thread:
|
||||
// https://forum.unity.com/threads/onaudiofilterread-sound-spatialisation.362782/
|
||||
int frameCount = 2048 * 10;
|
||||
int sampleCount = frameCount * Helper.GetUnityAudioSpeakerCount();
|
||||
AudioClip clip = AudioClip.Create("dummy", frameCount, Helper.GetUnityAudioSpeakerCount(), Helper.GetUnityAudioSampleRate(), false);
|
||||
float[] samples = new float[sampleCount];
|
||||
for (int i = 0; i < samples.Length; i++) { samples[i] = 1f; }
|
||||
clip.SetData(samples, 0);
|
||||
_audioSource.clip = clip;
|
||||
_audioSource.loop = true;
|
||||
}
|
||||
}
|
||||
else if (_audioSource.clip != null)
|
||||
{
|
||||
_audioSource.clip = null;
|
||||
}
|
||||
}
|
||||
|
||||
// Callback function to handle events
|
||||
private void OnMediaPlayerEvent(MediaPlayer mp, MediaPlayerEvent.EventType et, ErrorCode errorCode)
|
||||
{
|
||||
switch (et)
|
||||
{
|
||||
case MediaPlayerEvent.EventType.Closing:
|
||||
_audioSource.Stop();
|
||||
break;
|
||||
case MediaPlayerEvent.EventType.Started:
|
||||
ApplyAudioSettings(_mediaPlayer, _audioSource);
|
||||
_audioSource.Play();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private static void ApplyAudioSettings(MediaPlayer player, AudioSource audioSource)
|
||||
{
|
||||
// Apply volume and mute from the MediaPlayer to the AudioSource
|
||||
if (audioSource != null && player != null && player.Control != null)
|
||||
{
|
||||
float volume = player.Control.GetVolume();
|
||||
bool isMuted = player.Control.IsMuted();
|
||||
float rate = player.Control.GetPlaybackRate();
|
||||
audioSource.volume = volume;
|
||||
audioSource.mute = isMuted;
|
||||
audioSource.pitch = rate;
|
||||
}
|
||||
}
|
||||
|
||||
#if (UNITY_EDITOR_WIN || UNITY_EDITOR_OSX) || (!UNITY_EDITOR && (UNITY_STANDALONE_WIN || UNITY_WSA_10_0 || UNITY_STANDALONE_OSX || UNITY_IOS || UNITY_TVOS || UNITY_ANDROID))
|
||||
void OnAudioFilterRead(float[] audioData, int channelCount)
|
||||
{
|
||||
AudioOutputManager.Instance.RequestAudio(this, _mediaPlayer, audioData, channelCount, _channelMask, _audioOutputMode, _supportPositionalAudio);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3b05a64a5de3f8546bf586f42e37b979
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {fileID: 2800000, guid: bb83b41b53a59874692b83eab5873998, type: 3}
|
||||
userData:
|
||||
@@ -0,0 +1,321 @@
|
||||
#if UNITY_EDITOR || UNITY_STANDALONE_OSX || UNITY_STANDALONE_WIN || UNITY_IOS || UNITY_TVOS || UNITY_ANDROID || (UNITY_WEBGL && UNITY_2017_2_OR_NEWER)
|
||||
#define UNITY_PLATFORM_SUPPORTS_LINEAR
|
||||
#endif
|
||||
#if (UNITY_EDITOR_WIN || (!UNITY_EDITOR && UNITY_STANDALONE_WIN))
|
||||
#define UNITY_PLATFORM_SUPPORTS_VIDEOASPECTRATIO
|
||||
#endif
|
||||
|
||||
using UnityEngine;
|
||||
using UnityEngine.Serialization;
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Copyright 2015-2022 RenderHeads Ltd. All rights reserved.
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
namespace RenderHeads.Media.AVProVideo
|
||||
{
|
||||
/// <summary>
|
||||
/// Displays the video from MediaPlayer component using IMGUI
|
||||
/// </summary>
|
||||
[AddComponentMenu("AVPro Video/Display IMGUI", 200)]
|
||||
[HelpURL("https://www.renderheads.com/products/avpro-video/")]
|
||||
[ExecuteInEditMode]
|
||||
public class DisplayIMGUI : MonoBehaviour
|
||||
{
|
||||
[SerializeField] MediaPlayer _mediaPlayer = null;
|
||||
public MediaPlayer Player
|
||||
{
|
||||
get { return _mediaPlayer; }
|
||||
set { _mediaPlayer = value; Update(); }
|
||||
}
|
||||
|
||||
[SerializeField] ScaleMode _scaleMode = ScaleMode.ScaleToFit;
|
||||
public ScaleMode ScaleMode { get { return _scaleMode; } set { _scaleMode = value; } }
|
||||
|
||||
[SerializeField] Color _color = UnityEngine.Color.white;
|
||||
public Color Color { get { return _color; } set { _color = value; } }
|
||||
|
||||
[FormerlySerializedAs("_alphaBlend")]
|
||||
[SerializeField] bool _allowTransparency = false;
|
||||
public bool AllowTransparency { get { return _allowTransparency; } set { _allowTransparency = value; } }
|
||||
|
||||
[SerializeField] bool _useDepth = false;
|
||||
public bool UseDepth { get { return _useDepth; } set { _useDepth = value; } }
|
||||
|
||||
[SerializeField] int _depth = 0;
|
||||
public int Depth { get { return _depth; } set { _depth = value; } }
|
||||
|
||||
[Header("Area")]
|
||||
|
||||
[FormerlySerializedAs("_fullScreen")]
|
||||
[SerializeField] bool _isAreaFullScreen = true;
|
||||
public bool IsAreaFullScreen { get { return _isAreaFullScreen; } set { _isAreaFullScreen = value; } }
|
||||
|
||||
[FormerlySerializedAs("_x")]
|
||||
[Range(0f, 1f)]
|
||||
[SerializeField] float _areaX = 0f;
|
||||
public float AreaX { get { return _areaX; } set { _areaX = value; } }
|
||||
|
||||
[FormerlySerializedAs("_y")]
|
||||
[Range(0f, 1f)]
|
||||
[SerializeField] float _areaY = 0f;
|
||||
public float AreaY { get { return _areaY; } set { _areaY = value; } }
|
||||
|
||||
[FormerlySerializedAs("_width")]
|
||||
[Range(0f, 1f)]
|
||||
[SerializeField] float _areaWidth = 1f;
|
||||
public float AreaWidth { get { return _areaWidth; } set { _areaWidth = value; } }
|
||||
|
||||
[FormerlySerializedAs("_height")]
|
||||
[Range(0f, 1f)]
|
||||
[SerializeField] float _areaHeight = 1f;
|
||||
public float AreaHeight { get { return _areaHeight; } set { _areaHeight = value; } }
|
||||
|
||||
[FormerlySerializedAs("_displayInEditor")]
|
||||
[SerializeField] bool _showAreaInEditor = false;
|
||||
public bool ShowAreaInEditor { get { return _showAreaInEditor; } set { _showAreaInEditor = value; } }
|
||||
|
||||
private static Shader _shaderAlphaPacking;
|
||||
private Material _material;
|
||||
|
||||
void Start()
|
||||
{
|
||||
// Disabling useGUILayout lets you skip the GUI layout phase which helps performance, but this also breaks the GUI.depth usage.
|
||||
if (!_useDepth)
|
||||
{
|
||||
this.useGUILayout = false;
|
||||
}
|
||||
|
||||
if (!_shaderAlphaPacking)
|
||||
{
|
||||
_shaderAlphaPacking = Shader.Find("AVProVideo/Internal/IMGUI/Texture Transparent");
|
||||
if (!_shaderAlphaPacking)
|
||||
{
|
||||
Debug.LogWarning("[AVProVideo] Missing shader 'AVProVideo/Internal/IMGUI/Texture Transparent'");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
if (_mediaPlayer != null)
|
||||
{
|
||||
SetupMaterial();
|
||||
}
|
||||
}
|
||||
|
||||
void OnDestroy()
|
||||
{
|
||||
// Destroy existing material
|
||||
if (_material != null)
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
Material.DestroyImmediate(_material);
|
||||
#else
|
||||
Material.Destroy(_material);
|
||||
#endif
|
||||
_material = null;
|
||||
}
|
||||
}
|
||||
|
||||
private Shader GetRequiredShader()
|
||||
{
|
||||
Shader result = null;
|
||||
|
||||
if (result == null && _mediaPlayer.TextureProducer != null)
|
||||
{
|
||||
switch (_mediaPlayer.TextureProducer.GetTextureAlphaPacking())
|
||||
{
|
||||
case AlphaPacking.None:
|
||||
break;
|
||||
case AlphaPacking.LeftRight:
|
||||
case AlphaPacking.TopBottom:
|
||||
result = _shaderAlphaPacking;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
#if UNITY_PLATFORM_SUPPORTS_LINEAR
|
||||
if (result == null && _mediaPlayer.Info != null)
|
||||
{
|
||||
// If the player does support generating sRGB textures then we need to use a shader to convert them for display via IMGUI
|
||||
if (QualitySettings.activeColorSpace == ColorSpace.Linear && !_mediaPlayer.Info.PlayerSupportsLinearColorSpace())
|
||||
{
|
||||
result = _shaderAlphaPacking;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
if (result == null && _mediaPlayer.TextureProducer != null)
|
||||
{
|
||||
if (_mediaPlayer.TextureProducer.GetTextureCount() == 2)
|
||||
{
|
||||
result = _shaderAlphaPacking;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private void SetupMaterial()
|
||||
{
|
||||
// Get required shader
|
||||
Shader currentShader = null;
|
||||
if (_material != null)
|
||||
{
|
||||
currentShader = _material.shader;
|
||||
}
|
||||
Shader nextShader = GetRequiredShader();
|
||||
|
||||
// If the shader requirement has changed
|
||||
if (currentShader != nextShader)
|
||||
{
|
||||
// Destroy existing material
|
||||
if (_material != null)
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
Material.DestroyImmediate(_material);
|
||||
#else
|
||||
Material.Destroy(_material);
|
||||
#endif
|
||||
_material = null;
|
||||
}
|
||||
|
||||
// Create new material
|
||||
if (nextShader != null)
|
||||
{
|
||||
_material = new Material(nextShader);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
private void DrawArea()
|
||||
{
|
||||
Rect rect = GetAreaRect();
|
||||
Rect uv = rect;
|
||||
uv.x /= Screen.width;
|
||||
uv.width /= Screen.width;
|
||||
uv.y /= Screen.height;
|
||||
uv.height /= Screen.height;
|
||||
uv.width *= 16f;
|
||||
uv.height *= 16f;
|
||||
uv.x += 0.5f;
|
||||
uv.y += 0.5f;
|
||||
Texture2D icon = Resources.Load<Texture2D>("AVProVideoIcon");
|
||||
GUI.depth = _depth;
|
||||
GUI.color = _color;
|
||||
GUI.DrawTextureWithTexCoords(rect, icon, uv);
|
||||
}
|
||||
#endif
|
||||
|
||||
void OnGUI()
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
if (_showAreaInEditor && !Application.isPlaying)
|
||||
{
|
||||
DrawArea();
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (_mediaPlayer == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Texture texture = null;
|
||||
if (_showAreaInEditor)
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
texture = Texture2D.whiteTexture;
|
||||
#endif
|
||||
}
|
||||
texture = VideoRender.GetTexture(_mediaPlayer, 0);
|
||||
if (_mediaPlayer.Info != null && !_mediaPlayer.Info.HasVideo())
|
||||
{
|
||||
texture = null;
|
||||
}
|
||||
|
||||
if (texture != null)
|
||||
{
|
||||
bool isTextureVisible = (_color.a > 0f || !_allowTransparency);
|
||||
if (isTextureVisible)
|
||||
{
|
||||
GUI.depth = _depth;
|
||||
GUI.color = _color;
|
||||
|
||||
Rect rect = GetAreaRect();
|
||||
|
||||
// TODO: change this to a material-only path so we only have a single drawing path
|
||||
if (_material != null)
|
||||
{
|
||||
// TODO: Only setup material when needed
|
||||
VideoRender.SetupMaterialForMedia(_material, _mediaPlayer);
|
||||
|
||||
// NOTE: It seems that Graphics.DrawTexture() behaves differently than GUI.DrawTexture() when it comes to sRGB writing
|
||||
// on newer versions of Unity (at least 2018.2.19 and above), so now we have to force the conversion to sRGB on writing
|
||||
bool restoreSRGBWrite = false;
|
||||
#if UNITY_EDITOR_WIN || (!UNITY_EDITOR && UNITY_STANDALONE_WIN)
|
||||
if (QualitySettings.activeColorSpace == ColorSpace.Linear && !GL.sRGBWrite)
|
||||
{
|
||||
restoreSRGBWrite = true;
|
||||
}
|
||||
#endif
|
||||
if (restoreSRGBWrite)
|
||||
{
|
||||
GL.sRGBWrite = true;
|
||||
}
|
||||
|
||||
VideoRender.DrawTexture(rect, texture, _scaleMode, _mediaPlayer.TextureProducer.GetTextureAlphaPacking(), _mediaPlayer.TextureProducer.GetTexturePixelAspectRatio(), _material);
|
||||
|
||||
if (restoreSRGBWrite)
|
||||
{
|
||||
GL.sRGBWrite = false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
bool requiresVerticalFlip = false;
|
||||
if (_mediaPlayer.TextureProducer != null)
|
||||
{
|
||||
requiresVerticalFlip = _mediaPlayer.TextureProducer.RequiresVerticalFlip();
|
||||
}
|
||||
if (requiresVerticalFlip)
|
||||
{
|
||||
GUIUtility.ScaleAroundPivot(new Vector2(1f, -1f), new Vector2(0f, rect.y + (rect.height / 2f)));
|
||||
}
|
||||
#if UNITY_PLATFORM_SUPPORTS_VIDEOASPECTRATIO
|
||||
float par = _mediaPlayer.TextureProducer.GetTexturePixelAspectRatio();
|
||||
if (par > 0f)
|
||||
{
|
||||
if (par > 1f)
|
||||
{
|
||||
GUIUtility.ScaleAroundPivot(new Vector2(par, 1f), new Vector2(rect.x + (rect.width / 2f), rect.y + (rect.height / 2f)));
|
||||
}
|
||||
else
|
||||
{
|
||||
GUIUtility.ScaleAroundPivot(new Vector2(1f, 1f/par), new Vector2(rect.x + (rect.width / 2f), rect.y + (rect.height / 2f)));
|
||||
}
|
||||
}
|
||||
#endif
|
||||
GUI.DrawTexture(rect, texture, _scaleMode, _allowTransparency);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Rect GetAreaRect()
|
||||
{
|
||||
Rect rect;
|
||||
if (_isAreaFullScreen)
|
||||
{
|
||||
rect = new Rect(0.0f, 0.0f, Screen.width, Screen.height);
|
||||
}
|
||||
else
|
||||
{
|
||||
rect = new Rect(_areaX * (Screen.width - 1), _areaY * (Screen.height - 1), _areaWidth * Screen.width, _areaHeight * Screen.height);
|
||||
}
|
||||
|
||||
return rect;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 75f3b319d2d69934d8bf545ab45c918d
|
||||
timeCreated: 1544813301
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {fileID: 2800000, guid: bb83b41b53a59874692b83eab5873998, type: 3}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 638c870cac4da414fba921606d504407
|
||||
timeCreated: 1544813302
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {fileID: 2800000, guid: bb83b41b53a59874692b83eab5873998, type: 3}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,469 @@
|
||||
#if AVPROVIDEO_SUPPORT_BUFFERED_DISPLAY
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using RenderHeads.Media.AVProVideo;
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Copyright 2015-2022 RenderHeads Ltd. All rights reserved.
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
namespace RenderHeads.Media.AVProVideo.Experimental
|
||||
{
|
||||
/// <summary>
|
||||
/// Syncronise multiple MediaPlayer components (currently Windows ONLY using Media Foundation ONLY)
|
||||
/// This feature requires Ultra Edition
|
||||
/// </summary>
|
||||
[AddComponentMenu("AVPro Video/Media Player Sync (BETA)", -90)]
|
||||
[HelpURL("https://www.renderheads.com/products/avpro-video/")]
|
||||
public class MediaPlayerSync : MonoBehaviour
|
||||
{
|
||||
[SerializeField] MediaPlayer _masterPlayer = null;
|
||||
[SerializeField] MediaPlayer[] _slavePlayers = null;
|
||||
[SerializeField] bool _playOnStart = true;
|
||||
[SerializeField] bool _waitAfterPreroll = false;
|
||||
[SerializeField] bool _logSyncErrors = false;
|
||||
|
||||
public MediaPlayer MasterPlayer { get { return _masterPlayer; } set { _masterPlayer = value; } }
|
||||
public MediaPlayer[] SlavePlayers { get { return _slavePlayers; } set { _slavePlayers = value; } }
|
||||
public bool PlayOnStart { get { return _playOnStart; } set { _playOnStart = value; } }
|
||||
public bool WaitAfterPreroll { get { return _waitAfterPreroll; } set { _waitAfterPreroll = value; } }
|
||||
public bool LogSyncErrors { get { return _logSyncErrors; } set { _logSyncErrors = value; } }
|
||||
|
||||
private enum State
|
||||
{
|
||||
Idle,
|
||||
Loading,
|
||||
Prerolling,
|
||||
Prerolled,
|
||||
Playing,
|
||||
Finished,
|
||||
}
|
||||
|
||||
private State _state = State.Idle;
|
||||
|
||||
void Awake()
|
||||
{
|
||||
#if (UNITY_EDITOR_WIN || (!UNITY_EDITOR && UNITY_STANDALONE_WIN))
|
||||
SetupPlayers();
|
||||
#else
|
||||
Debug.LogError("[AVProVideo] This component only works on the Windows platform");
|
||||
this.enabled = false;
|
||||
#endif
|
||||
}
|
||||
|
||||
void Start()
|
||||
{
|
||||
if (_playOnStart)
|
||||
{
|
||||
StartPlayback();
|
||||
_state = State.Loading;
|
||||
_playOnStart = false;
|
||||
}
|
||||
}
|
||||
|
||||
public void OpenMedia(string[] mediaPaths)
|
||||
{
|
||||
Debug.Assert(mediaPaths.Length == (_slavePlayers.Length + 1));
|
||||
|
||||
_masterPlayer.MediaSource = MediaSource.Path;
|
||||
_masterPlayer.MediaPath = new MediaPath(mediaPaths[0], MediaPathType.AbsolutePathOrURL);
|
||||
|
||||
for (int i = 0; i < _slavePlayers.Length; i++)
|
||||
{
|
||||
_slavePlayers[i].MediaSource = MediaSource.Path;
|
||||
_slavePlayers[i].MediaPath = new MediaPath(mediaPaths[i+1], MediaPathType.AbsolutePathOrURL);
|
||||
}
|
||||
|
||||
StartPlayback();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This is called when _autoPlay is false and once the MediaPlayers have had their source media set
|
||||
/// </summary>
|
||||
[ContextMenu("StartPlayback")]
|
||||
public void StartPlayback()
|
||||
{
|
||||
SetupPlayers();
|
||||
|
||||
if (!IsPrerolled())
|
||||
{
|
||||
OpenMediaAll();
|
||||
_state = State.Loading;
|
||||
}
|
||||
else
|
||||
{
|
||||
PlayAll();
|
||||
_state = State.Playing;
|
||||
}
|
||||
}
|
||||
|
||||
public void Seek(double time, bool approximate = true)
|
||||
{
|
||||
if (approximate)
|
||||
{
|
||||
SeekFastAll(time);
|
||||
}
|
||||
else
|
||||
{
|
||||
SeekAll(time);
|
||||
}
|
||||
|
||||
_state = State.Prerolling;
|
||||
}
|
||||
|
||||
public bool IsPrerolled()
|
||||
{
|
||||
return (_state == State.Prerolled);
|
||||
}
|
||||
|
||||
void SetupPlayers()
|
||||
{
|
||||
SetupPlayer(_masterPlayer);
|
||||
for (int i = 0; i < _slavePlayers.Length; i++)
|
||||
{
|
||||
SetupPlayer(_slavePlayers[i]);
|
||||
}
|
||||
}
|
||||
|
||||
void SetupPlayer(MediaPlayer player)
|
||||
{
|
||||
bool isMaster = (player == _masterPlayer);
|
||||
player.AutoOpen = false;
|
||||
player.AutoStart = false;
|
||||
player.AudioMuted = !isMaster;
|
||||
player.PlatformOptionsWindows.videoApi = Windows.VideoApi.MediaFoundation;
|
||||
player.PlatformOptionsWindows.useLowLatency = true;
|
||||
player.PlatformOptionsWindows.pauseOnPrerollComplete = true;
|
||||
player.PlatformOptionsWindows.bufferedFrameSelection = isMaster ? BufferedFrameSelectionMode.ElapsedTimeVsynced : BufferedFrameSelectionMode.FromExternalTime;
|
||||
}
|
||||
|
||||
// NOTE: We check on LateUpdate() as MediaPlayer uses Update() to update state and we want to make sure all players have been updated
|
||||
void LateUpdate()
|
||||
{
|
||||
if (_state == State.Idle)
|
||||
{
|
||||
}
|
||||
if (_state == State.Loading)
|
||||
{
|
||||
UpdateLoading();
|
||||
}
|
||||
if (_state == State.Prerolling)
|
||||
{
|
||||
UpdatePrerolling();
|
||||
}
|
||||
if (_state == State.Prerolled)
|
||||
{
|
||||
/*if (Input.GetKeyDown(KeyCode.Alpha0))
|
||||
{
|
||||
StartPlayback();
|
||||
}*/
|
||||
}
|
||||
if (_state == State.Playing)
|
||||
{
|
||||
UpdatePlaying();
|
||||
}
|
||||
if (_state == State.Finished)
|
||||
{
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
if (Input.GetKeyDown(KeyCode.Alpha5))
|
||||
{
|
||||
Debug.Log("sleep");
|
||||
System.Threading.Thread.Sleep(16);
|
||||
}
|
||||
|
||||
/*if (Input.GetKeyDown(KeyCode.Alpha1))
|
||||
{
|
||||
double time = Random.Range(0f, (float)_masterPlayer.Info.GetDuration());
|
||||
Seek(time);
|
||||
}
|
||||
|
||||
long gcMemory = System.GC.GetTotalMemory(false);
|
||||
//Debug.Log("GC: " + (gcMemory / 1024) + " " + (gcMemory - lastGcMemory));
|
||||
if ((gcMemory - lastGcMemory) < 0)
|
||||
{
|
||||
Debug.LogWarning("COLLECTION!!! " + (lastGcMemory - gcMemory));
|
||||
}
|
||||
lastGcMemory = gcMemory;*/
|
||||
#endif
|
||||
}
|
||||
|
||||
//long lastGcMemory = 0;
|
||||
|
||||
void UpdateLoading()
|
||||
{
|
||||
// Finished loading?
|
||||
if (IsAllVideosLoaded())
|
||||
{
|
||||
// Assign the master and slaves
|
||||
_masterPlayer.BufferedDisplay.SetBufferedDisplayMode(BufferedFrameSelectionMode.ElapsedTimeVsynced);
|
||||
|
||||
IBufferedDisplay[] slaves = new IBufferedDisplay[_slavePlayers.Length];
|
||||
for (int i = 0; i < _slavePlayers.Length; i++)
|
||||
{
|
||||
slaves[i] = _slavePlayers[i].BufferedDisplay;
|
||||
}
|
||||
_masterPlayer.BufferedDisplay.SetSlaves(slaves);
|
||||
|
||||
//System.Threading.Thread.Sleep(1250);
|
||||
|
||||
// Begin preroll
|
||||
PlayAll();
|
||||
|
||||
_state = State.Prerolling;
|
||||
}
|
||||
}
|
||||
|
||||
void UpdatePrerolling()
|
||||
{
|
||||
if (IsAllVideosPaused())
|
||||
{
|
||||
//System.Threading.Thread.Sleep(250);
|
||||
|
||||
if (_waitAfterPreroll)
|
||||
{
|
||||
_state = State.Prerolled;
|
||||
}
|
||||
else
|
||||
{
|
||||
PlayAll();
|
||||
_state = State.Playing;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void UpdatePlaying()
|
||||
{
|
||||
if (_masterPlayer.Control.IsPlaying())
|
||||
{
|
||||
if (_logSyncErrors)
|
||||
{
|
||||
CheckSync();
|
||||
CheckSmoothness();
|
||||
}
|
||||
|
||||
BufferedFramesState state = _masterPlayer.BufferedDisplay.GetBufferedFramesState();
|
||||
if (state.bufferedFrameCount < 3)
|
||||
{
|
||||
//Debug.LogWarning("FORCE SLEEP");
|
||||
System.Threading.Thread.Sleep(16);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Pause slaves
|
||||
for (int i = 0; i < _slavePlayers.Length; i++)
|
||||
{
|
||||
MediaPlayer slave = _slavePlayers[i];
|
||||
slave.Pause();
|
||||
}
|
||||
}
|
||||
|
||||
// Finished?
|
||||
if (IsPlaybackFinished(_masterPlayer))
|
||||
{
|
||||
_state = State.Finished;
|
||||
}
|
||||
}
|
||||
|
||||
private long _lastTimeStamp;
|
||||
private int _sameFrameCount;
|
||||
|
||||
void CheckSmoothness()
|
||||
{
|
||||
long timeStamp = _masterPlayer.TextureProducer.GetTextureTimeStamp();
|
||||
//int frameCount = _masterPlayer.TextureProducer.GetTextureFrameCount();
|
||||
long frameDuration = (long)(10000000f / _masterPlayer.Info.GetVideoFrameRate());
|
||||
|
||||
long vsyncDuration = (long)((QualitySettings.vSyncCount * 10000000f) / (float)Screen.currentResolution.refreshRate);
|
||||
float vsyncFrames = (float)vsyncDuration / frameDuration;
|
||||
|
||||
float fractionalFrames = vsyncFrames - Mathf.FloorToInt(vsyncFrames);
|
||||
|
||||
if (fractionalFrames == 0f)
|
||||
{
|
||||
if (QualitySettings.vSyncCount != 0)
|
||||
{
|
||||
if (!Mathf.Approximately(_sameFrameCount, vsyncFrames))
|
||||
{
|
||||
Debug.LogWarning("Frame " + timeStamp + " was shown for " + _sameFrameCount + " frames instead of expected " + vsyncFrames);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
long d = (timeStamp - _lastTimeStamp);
|
||||
if (d != 0)
|
||||
{
|
||||
long threshold = 10000;
|
||||
if (d > frameDuration + threshold ||
|
||||
d < frameDuration - threshold)
|
||||
{
|
||||
Debug.LogWarning("Possible frame skip, " + timeStamp + " " + d);
|
||||
}
|
||||
|
||||
_sameFrameCount = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
_sameFrameCount++;
|
||||
}
|
||||
|
||||
_lastTimeStamp = timeStamp;
|
||||
//Debug.Log(frameDuration);
|
||||
}
|
||||
|
||||
void CheckSync()
|
||||
{
|
||||
long timeStamp = _masterPlayer.TextureProducer.GetTextureTimeStamp();
|
||||
|
||||
bool inSync = true;
|
||||
foreach (MediaPlayer slavePlayer in _slavePlayers)
|
||||
{
|
||||
if (slavePlayer.TextureProducer.GetTextureTimeStamp() != timeStamp)
|
||||
{
|
||||
inSync = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!inSync)
|
||||
{
|
||||
LogSyncState();
|
||||
Debug.LogWarning("OUT OF SYNC!!!!!!!");
|
||||
//Debug.Break();
|
||||
}
|
||||
else
|
||||
{
|
||||
//LogSyncState();
|
||||
}
|
||||
}
|
||||
|
||||
void LogSyncState()
|
||||
{
|
||||
string text = "Time - Full,Free\t\tRange\n";
|
||||
text += LogSyncState(_masterPlayer) + "\n";
|
||||
foreach (MediaPlayer slavePlayer in _slavePlayers)
|
||||
{
|
||||
text += LogSyncState(slavePlayer) + "\n";
|
||||
}
|
||||
Debug.Log(text);
|
||||
}
|
||||
|
||||
string LogSyncState(MediaPlayer player)
|
||||
{
|
||||
BufferedFramesState state = player.BufferedDisplay.GetBufferedFramesState();
|
||||
long timeStamp = player.TextureProducer.GetTextureTimeStamp();
|
||||
string result = string.Format("{4} - {2},{3}\t\t{0}-{1} ({5})", state.minTimeStamp, state.maxTimeStamp, state.bufferedFrameCount, state.freeFrameCount, timeStamp, Time.deltaTime);
|
||||
return result;
|
||||
}
|
||||
|
||||
void OpenMediaAll()
|
||||
{
|
||||
_masterPlayer.OpenMedia(autoPlay:false);
|
||||
for (int i = 0; i < _slavePlayers.Length; i++)
|
||||
{
|
||||
_slavePlayers[i].OpenMedia(autoPlay:false);
|
||||
}
|
||||
}
|
||||
|
||||
void PauseAll()
|
||||
{
|
||||
_masterPlayer.Pause();
|
||||
for (int i = 0; i < _slavePlayers.Length; i++)
|
||||
{
|
||||
_slavePlayers[i].Pause();
|
||||
}
|
||||
}
|
||||
|
||||
void PlayAll()
|
||||
{
|
||||
_masterPlayer.Play();
|
||||
for (int i = 0; i < _slavePlayers.Length; i++)
|
||||
{
|
||||
_slavePlayers[i].Play();
|
||||
}
|
||||
}
|
||||
|
||||
void SeekAll(double time)
|
||||
{
|
||||
_masterPlayer.Control.Seek(time);
|
||||
foreach (MediaPlayer player in _slavePlayers)
|
||||
{
|
||||
player.Control.Seek(time);
|
||||
}
|
||||
}
|
||||
|
||||
void SeekFastAll(double time)
|
||||
{
|
||||
_masterPlayer.Control.SeekFast(time);
|
||||
foreach (MediaPlayer player in _slavePlayers)
|
||||
{
|
||||
player.Control.SeekFast(time);
|
||||
}
|
||||
}
|
||||
|
||||
bool IsAllVideosLoaded()
|
||||
{
|
||||
bool result = false;
|
||||
if (IsVideoLoaded(_masterPlayer))
|
||||
{
|
||||
result = true;
|
||||
for (int i = 0; i < _slavePlayers.Length; i++)
|
||||
{
|
||||
if (!IsVideoLoaded(_slavePlayers[i]))
|
||||
{
|
||||
result = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
bool IsAllVideosPaused()
|
||||
{
|
||||
bool result = false;
|
||||
if (IsVideoPaused(_masterPlayer))
|
||||
{
|
||||
result = true;
|
||||
for (int i = 0; i < _slavePlayers.Length; i++)
|
||||
{
|
||||
if (!IsVideoPaused(_slavePlayers[i]))
|
||||
{
|
||||
result = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
static bool IsPlaybackFinished(MediaPlayer player)
|
||||
{
|
||||
bool result = false;
|
||||
if (player != null && player.Control != null)
|
||||
{
|
||||
if (player.Control.IsFinished())
|
||||
{
|
||||
BufferedFramesState state = player.BufferedDisplay.GetBufferedFramesState();
|
||||
if (state.bufferedFrameCount == 0)
|
||||
{
|
||||
result = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static bool IsVideoLoaded(MediaPlayer player)
|
||||
{
|
||||
return (player != null && player.Control != null && player.Control.HasMetaData() && player.Control.CanPlay());
|
||||
}
|
||||
static bool IsVideoPaused(MediaPlayer player)
|
||||
{
|
||||
return (player != null && player.Control != null && player.Control.IsPaused());
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7b84192b70cb6c14ba22896883851268
|
||||
timeCreated: 1628084656
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {fileID: 2800000, guid: bb83b41b53a59874692b83eab5873998, type: 3}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,65 @@
|
||||
#if !(UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX || UNITY_IOS || UNITY_TVOS)
|
||||
using UnityEngine;
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Copyright 2015-2022 RenderHeads Ltd. All rights reserved.
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
namespace RenderHeads.Media.AVProVideo
|
||||
{
|
||||
public partial class MediaPlayer : MonoBehaviour
|
||||
{
|
||||
#region Application Focus and Pausing
|
||||
#if !UNITY_EDITOR
|
||||
void OnApplicationFocus(bool focusStatus)
|
||||
{
|
||||
#if !(UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN)
|
||||
// Debug.Log("OnApplicationFocus: focusStatus: " + focusStatus);
|
||||
|
||||
if (focusStatus && (isActiveAndEnabled && enabled))
|
||||
{
|
||||
if (Control != null && _wasPlayingOnPause)
|
||||
{
|
||||
_wasPlayingOnPause = false;
|
||||
Control.Play();
|
||||
|
||||
Helper.LogInfo("OnApplicationFocus: playing video again");
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void OnApplicationPause(bool pauseStatus)
|
||||
{
|
||||
#if !(UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN)
|
||||
// Debug.Log("OnApplicationPause: pauseStatus: " + pauseStatus);
|
||||
|
||||
if (pauseStatus)
|
||||
{
|
||||
if (_pauseMediaOnAppPause)
|
||||
{
|
||||
if (Control!= null && Control.IsPlaying())
|
||||
{
|
||||
_wasPlayingOnPause = true;
|
||||
#if !UNITY_IPHONE
|
||||
Control.Pause();
|
||||
#endif
|
||||
Helper.LogInfo("OnApplicationPause: pausing video");
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_playMediaOnAppUnpause)
|
||||
{
|
||||
// Catch coming back from power off state when no lock screen
|
||||
OnApplicationFocus(true);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
#endregion // Application Focus and Pausing
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3a3464021ab2fb14a81d5d35b3097023
|
||||
timeCreated: 1544813302
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {fileID: 2800000, guid: bb83b41b53a59874692b83eab5873998, type: 3}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,29 @@
|
||||
using UnityEngine;
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Copyright 2015-2022 RenderHeads Ltd. All rights reserved.
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
namespace RenderHeads.Media.AVProVideo
|
||||
{
|
||||
public partial class MediaPlayer : MonoBehaviour
|
||||
{
|
||||
#region Audio Mute Support for Unity Editor
|
||||
#if UNITY_EDITOR
|
||||
private bool _unityAudioMasterMute = false;
|
||||
private void CheckEditorAudioMute()
|
||||
{
|
||||
// Detect a change
|
||||
if (UnityEditor.EditorUtility.audioMasterMute != _unityAudioMasterMute)
|
||||
{
|
||||
if (_controlInterface != null)
|
||||
{
|
||||
_unityAudioMasterMute = UnityEditor.EditorUtility.audioMasterMute;
|
||||
_controlInterface.MuteAudio(_audioMuted || _unityAudioMasterMute);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#endregion // Audio Mute Support for Unity Editor
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 16be519f584387149bd75947276c3a72
|
||||
timeCreated: 1544813302
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {fileID: 2800000, guid: bb83b41b53a59874692b83eab5873998, type: 3}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,83 @@
|
||||
using UnityEngine;
|
||||
|
||||
#if UNITY_EDITOR
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Copyright 2015-2022 RenderHeads Ltd. All rights reserved.
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
namespace RenderHeads.Media.AVProVideo
|
||||
{
|
||||
public partial class MediaPlayer : MonoBehaviour
|
||||
{
|
||||
#region Play/Pause Support for Unity Editor
|
||||
// This code handles the pause/play buttons in the editor
|
||||
private static void SetupEditorPlayPauseSupport()
|
||||
{
|
||||
#if UNITY_2017_2_OR_NEWER
|
||||
UnityEditor.EditorApplication.pauseStateChanged -= OnUnityPauseModeChanged;
|
||||
UnityEditor.EditorApplication.pauseStateChanged += OnUnityPauseModeChanged;
|
||||
#else
|
||||
UnityEditor.EditorApplication.playmodeStateChanged -= OnUnityPlayModeChanged;
|
||||
UnityEditor.EditorApplication.playmodeStateChanged += OnUnityPlayModeChanged;
|
||||
#endif
|
||||
}
|
||||
|
||||
#if UNITY_2017_2_OR_NEWER
|
||||
private static void OnUnityPauseModeChanged(UnityEditor.PauseState state)
|
||||
{
|
||||
OnUnityPlayModeChanged();
|
||||
}
|
||||
#endif
|
||||
|
||||
private static void OnUnityPlayModeChanged()
|
||||
{
|
||||
if (UnityEditor.EditorApplication.isPlaying)
|
||||
{
|
||||
bool isPaused = UnityEditor.EditorApplication.isPaused;
|
||||
MediaPlayer[] players = Resources.FindObjectsOfTypeAll<MediaPlayer>();
|
||||
foreach (MediaPlayer player in players)
|
||||
{
|
||||
if (isPaused)
|
||||
{
|
||||
player.EditorPause();
|
||||
}
|
||||
else
|
||||
{
|
||||
player.EditorUnpause();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void EditorPause()
|
||||
{
|
||||
if (this.isActiveAndEnabled)
|
||||
{
|
||||
if (_controlInterface != null && _controlInterface.IsPlaying())
|
||||
{
|
||||
_wasPlayingOnPause = true;
|
||||
_controlInterface.Pause();
|
||||
}
|
||||
StopRenderCoroutine();
|
||||
}
|
||||
}
|
||||
|
||||
private void EditorUnpause()
|
||||
{
|
||||
if (this.isActiveAndEnabled)
|
||||
{
|
||||
if (_controlInterface != null && _wasPlayingOnPause)
|
||||
{
|
||||
_autoPlayOnStart = true;
|
||||
_wasPlayingOnPause = false;
|
||||
_autoPlayOnStartTriggered = false;
|
||||
}
|
||||
StartRenderCoroutine();
|
||||
}
|
||||
}
|
||||
#endregion // Play/Pause Support for Unity Editor
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 083c5ace9dbfda84cb8b4afaa19bdcde
|
||||
timeCreated: 1544813302
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {fileID: 2800000, guid: bb83b41b53a59874692b83eab5873998, type: 3}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,269 @@
|
||||
using UnityEngine;
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Copyright 2015-2022 RenderHeads Ltd. All rights reserved.
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
namespace RenderHeads.Media.AVProVideo
|
||||
{
|
||||
public partial class MediaPlayer : MonoBehaviour
|
||||
{
|
||||
|
||||
#region Events
|
||||
|
||||
// Event state
|
||||
private bool _eventFired_MetaDataReady = false;
|
||||
private bool _eventFired_ReadyToPlay = false;
|
||||
private bool _eventFired_Started = false;
|
||||
private bool _eventFired_FirstFrameReady = false;
|
||||
private bool _eventFired_FinishedPlaying = false;
|
||||
private bool _eventState_PlaybackBuffering = false;
|
||||
private bool _eventState_PlaybackSeeking = false;
|
||||
private bool _eventState_PlaybackStalled = false;
|
||||
private int _eventState_PreviousWidth = 0;
|
||||
private int _eventState_PreviousHeight = 0;
|
||||
private int _previousSubtitleIndex = -1;
|
||||
private bool _finishedFrameOpenCheck = false;
|
||||
|
||||
#if UNITY_EDITOR
|
||||
public static MediaPlayerLoadEvent InternalMediaLoadedEvent = new MediaPlayerLoadEvent();
|
||||
#endif
|
||||
|
||||
private void ResetEvents()
|
||||
{
|
||||
_eventFired_MetaDataReady = false;
|
||||
_eventFired_ReadyToPlay = false;
|
||||
_eventFired_Started = false;
|
||||
_eventFired_FirstFrameReady = false;
|
||||
_eventFired_FinishedPlaying = false;
|
||||
_eventState_PlaybackBuffering = false;
|
||||
_eventState_PlaybackSeeking = false;
|
||||
_eventState_PlaybackStalled = false;
|
||||
_eventState_PreviousWidth = 0;
|
||||
_eventState_PreviousHeight = 0;
|
||||
_previousSubtitleIndex = -1;
|
||||
_finishedFrameOpenCheck = false;
|
||||
}
|
||||
|
||||
private void UpdateEvents()
|
||||
{
|
||||
if (_events != null && _controlInterface != null && _events.HasListeners())
|
||||
{
|
||||
//NOTE: Fixes a bug where the event was being fired immediately, so when a file is opened, the finishedPlaying fired flag gets set but
|
||||
//is then set to true immediately afterwards due to the returned value
|
||||
_finishedFrameOpenCheck = false;
|
||||
if (IsHandleEvent(MediaPlayerEvent.EventType.FinishedPlaying))
|
||||
{
|
||||
if (FireEventIfPossible(MediaPlayerEvent.EventType.FinishedPlaying, _eventFired_FinishedPlaying))
|
||||
{
|
||||
_eventFired_FinishedPlaying = !_finishedFrameOpenCheck;
|
||||
}
|
||||
}
|
||||
|
||||
// Reset some event states that can reset during playback
|
||||
{
|
||||
// Keep track of whether the Playing state has changed
|
||||
if (_eventFired_Started && IsHandleEvent(MediaPlayerEvent.EventType.Started) &&
|
||||
_controlInterface != null && !_controlInterface.IsPlaying() && !_controlInterface.IsSeeking())
|
||||
{
|
||||
// Playing has stopped
|
||||
_eventFired_Started = false;
|
||||
}
|
||||
|
||||
// NOTE: We check _controlInterface isn't null in case the scene is unloaded in response to the FinishedPlaying event
|
||||
if (_eventFired_FinishedPlaying && IsHandleEvent(MediaPlayerEvent.EventType.FinishedPlaying) &&
|
||||
_controlInterface != null && _controlInterface.IsPlaying() && !_controlInterface.IsFinished())
|
||||
{
|
||||
bool reset = true;
|
||||
#if UNITY_EDITOR_WIN || (!UNITY_EDITOR && (UNITY_STANDALONE_WIN || UNITY_WSA))
|
||||
reset = false;
|
||||
if (_infoInterface.HasVideo())
|
||||
{
|
||||
// Some streaming HLS/Dash content don't provide a frame rate
|
||||
if (_infoInterface.GetVideoFrameRate() > 0f)
|
||||
{
|
||||
// Don't reset if within a frame of the end of the video, important for time > duration workaround
|
||||
float secondsPerFrame = 1f / _infoInterface.GetVideoFrameRate();
|
||||
if (_infoInterface.GetDuration() - _controlInterface.GetCurrentTime() > secondsPerFrame)
|
||||
{
|
||||
reset = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Just check if we're not beyond the duration
|
||||
if (_controlInterface.GetCurrentTime() < _infoInterface.GetDuration())
|
||||
{
|
||||
reset = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// For audio only media just check if we're not beyond the duration
|
||||
if (_controlInterface.GetCurrentTime() < _infoInterface.GetDuration())
|
||||
{
|
||||
reset = true;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
if (reset)
|
||||
{
|
||||
//Debug.Log("Reset");
|
||||
_eventFired_FinishedPlaying = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Events that can only fire once
|
||||
{
|
||||
_eventFired_MetaDataReady = FireEventIfPossible(MediaPlayerEvent.EventType.MetaDataReady, _eventFired_MetaDataReady);
|
||||
_eventFired_ReadyToPlay = FireEventIfPossible(MediaPlayerEvent.EventType.ReadyToPlay, _eventFired_ReadyToPlay);
|
||||
_eventFired_Started = FireEventIfPossible(MediaPlayerEvent.EventType.Started, _eventFired_Started);
|
||||
_eventFired_FirstFrameReady = FireEventIfPossible(MediaPlayerEvent.EventType.FirstFrameReady, _eventFired_FirstFrameReady);
|
||||
}
|
||||
|
||||
// Events that can fire multiple times
|
||||
{
|
||||
// Subtitle changing
|
||||
if (FireEventIfPossible(MediaPlayerEvent.EventType.SubtitleChange, false))
|
||||
{
|
||||
_previousSubtitleIndex = _subtitlesInterface.GetSubtitleIndex();
|
||||
}
|
||||
|
||||
// Resolution changing
|
||||
if (FireEventIfPossible(MediaPlayerEvent.EventType.ResolutionChanged, false))
|
||||
{
|
||||
_eventState_PreviousWidth = _infoInterface.GetVideoWidth();
|
||||
_eventState_PreviousHeight = _infoInterface.GetVideoHeight();
|
||||
}
|
||||
|
||||
// Stalling
|
||||
if (IsHandleEvent(MediaPlayerEvent.EventType.Stalled))
|
||||
{
|
||||
bool newState = _infoInterface.IsPlaybackStalled();
|
||||
if (newState != _eventState_PlaybackStalled)
|
||||
{
|
||||
_eventState_PlaybackStalled = newState;
|
||||
|
||||
var newEvent = _eventState_PlaybackStalled ? MediaPlayerEvent.EventType.Stalled : MediaPlayerEvent.EventType.Unstalled;
|
||||
FireEventIfPossible(newEvent, false);
|
||||
}
|
||||
}
|
||||
// Seeking
|
||||
if (IsHandleEvent(MediaPlayerEvent.EventType.StartedSeeking))
|
||||
{
|
||||
bool newState = _controlInterface.IsSeeking();
|
||||
if (newState != _eventState_PlaybackSeeking)
|
||||
{
|
||||
_eventState_PlaybackSeeking = newState;
|
||||
|
||||
var newEvent = _eventState_PlaybackSeeking ? MediaPlayerEvent.EventType.StartedSeeking : MediaPlayerEvent.EventType.FinishedSeeking;
|
||||
FireEventIfPossible(newEvent, false);
|
||||
}
|
||||
}
|
||||
// Buffering
|
||||
if (IsHandleEvent(MediaPlayerEvent.EventType.StartedBuffering))
|
||||
{
|
||||
bool newState = _controlInterface.IsBuffering();
|
||||
if (newState != _eventState_PlaybackBuffering)
|
||||
{
|
||||
_eventState_PlaybackBuffering = newState;
|
||||
|
||||
var newEvent = _eventState_PlaybackBuffering ? MediaPlayerEvent.EventType.StartedBuffering : MediaPlayerEvent.EventType.FinishedBuffering;
|
||||
FireEventIfPossible(newEvent, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected bool IsHandleEvent(MediaPlayerEvent.EventType eventType)
|
||||
{
|
||||
return ((uint)_eventMask & (1 << (int)eventType)) != 0;
|
||||
}
|
||||
|
||||
private bool FireEventIfPossible(MediaPlayerEvent.EventType eventType, bool hasFired)
|
||||
{
|
||||
if (CanFireEvent(eventType, hasFired))
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
// Special internal global event, called when media is loaded
|
||||
// Currently used by the RecentItem class
|
||||
if (eventType == MediaPlayerEvent.EventType.Started)
|
||||
{
|
||||
string fullPath = GetResolvedFilePath(_mediaPath.Path, _mediaPath.PathType);
|
||||
InternalMediaLoadedEvent.Invoke(fullPath);
|
||||
}
|
||||
#endif
|
||||
|
||||
hasFired = true;
|
||||
_events.Invoke(this, eventType, ErrorCode.None);
|
||||
}
|
||||
return hasFired;
|
||||
}
|
||||
|
||||
private bool CanFireEvent(MediaPlayerEvent.EventType et, bool hasFired)
|
||||
{
|
||||
bool result = false;
|
||||
if (_events != null && _controlInterface != null && !hasFired && IsHandleEvent(et))
|
||||
{
|
||||
switch (et)
|
||||
{
|
||||
case MediaPlayerEvent.EventType.FinishedPlaying:
|
||||
result = (!_controlInterface.IsLooping() && _controlInterface.CanPlay() && _controlInterface.IsFinished());
|
||||
break;
|
||||
case MediaPlayerEvent.EventType.MetaDataReady:
|
||||
result = (_controlInterface.HasMetaData());
|
||||
break;
|
||||
case MediaPlayerEvent.EventType.FirstFrameReady:
|
||||
// [MOZ 20/1/21] Removed HasMetaData check as preventing the event from being triggered on (i|mac|tv)OS
|
||||
result = (_textureInterface != null && _controlInterface.CanPlay() /*&& _controlInterface.HasMetaData()*/ && _textureInterface.GetTextureFrameCount() > 0);
|
||||
break;
|
||||
case MediaPlayerEvent.EventType.ReadyToPlay:
|
||||
result = (!_controlInterface.IsPlaying() && _controlInterface.CanPlay() && !_autoPlayOnStart);
|
||||
break;
|
||||
case MediaPlayerEvent.EventType.Started:
|
||||
result = (_controlInterface.IsPlaying());
|
||||
break;
|
||||
case MediaPlayerEvent.EventType.SubtitleChange:
|
||||
{
|
||||
result = (_previousSubtitleIndex != _subtitlesInterface.GetSubtitleIndex());
|
||||
if (!result)
|
||||
{
|
||||
result = _baseMediaPlayer.InternalIsChangedTextCue();
|
||||
}
|
||||
break;
|
||||
}
|
||||
case MediaPlayerEvent.EventType.Stalled:
|
||||
result = _infoInterface.IsPlaybackStalled();
|
||||
break;
|
||||
case MediaPlayerEvent.EventType.Unstalled:
|
||||
result = !_infoInterface.IsPlaybackStalled();
|
||||
break;
|
||||
case MediaPlayerEvent.EventType.StartedSeeking:
|
||||
result = _controlInterface.IsSeeking();
|
||||
break;
|
||||
case MediaPlayerEvent.EventType.FinishedSeeking:
|
||||
result = !_controlInterface.IsSeeking();
|
||||
break;
|
||||
case MediaPlayerEvent.EventType.StartedBuffering:
|
||||
result = _controlInterface.IsBuffering();
|
||||
break;
|
||||
case MediaPlayerEvent.EventType.FinishedBuffering:
|
||||
result = !_controlInterface.IsBuffering();
|
||||
break;
|
||||
case MediaPlayerEvent.EventType.ResolutionChanged:
|
||||
result = (_infoInterface != null && (_eventState_PreviousWidth != _infoInterface.GetVideoWidth() || _eventState_PreviousHeight != _infoInterface.GetVideoHeight()));
|
||||
break;
|
||||
default:
|
||||
Debug.LogWarning("[AVProVideo] Unhandled event type");
|
||||
break;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
#endregion // Events
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6be886b3f1f953843bda70e505701ee3
|
||||
timeCreated: 1544813302
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {fileID: 2800000, guid: bb83b41b53a59874692b83eab5873998, type: 3}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,211 @@
|
||||
using UnityEngine;
|
||||
using System.Collections;
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Copyright 2015-2022 RenderHeads Ltd. All rights reserved.
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
namespace RenderHeads.Media.AVProVideo
|
||||
{
|
||||
public partial class MediaPlayer : MonoBehaviour
|
||||
{
|
||||
#region Extract Frame
|
||||
|
||||
private bool ForceWaitForNewFrame(int lastFrameCount, float timeoutMs)
|
||||
{
|
||||
bool result = false;
|
||||
// Wait for the frame to change, or timeout to happen (for the case that there is no new frame for this time)
|
||||
System.DateTime startTime = System.DateTime.Now;
|
||||
int iterationCount = 0;
|
||||
while (Control != null && (System.DateTime.Now - startTime).TotalMilliseconds < (double)timeoutMs)
|
||||
{
|
||||
_playerInterface.Update();
|
||||
|
||||
// TODO: check if Seeking has completed! Then we don't have to wait
|
||||
|
||||
// If frame has changed we can continue
|
||||
// NOTE: this will never happen because GL.IssuePlugin.Event is never called in this loop
|
||||
if (lastFrameCount != TextureProducer.GetTextureFrameCount())
|
||||
{
|
||||
result = true;
|
||||
break;
|
||||
}
|
||||
|
||||
iterationCount++;
|
||||
|
||||
// NOTE: we tried to add Sleep for 1ms but it was very slow, so switched to this time based method which burns more CPU but about double the speed
|
||||
// NOTE: had to add the Sleep back in as after too many iterations (over 1000000) of GL.IssuePluginEvent Unity seems to lock up
|
||||
// NOTE: seems that GL.IssuePluginEvent can't be called if we're stuck in a while loop and they just stack up
|
||||
//System.Threading.Thread.Sleep(0);
|
||||
}
|
||||
|
||||
_playerInterface.Render();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create or return (if cached) a camera that is inactive and renders nothing
|
||||
/// This camera is used to call .Render() on which causes the render thread to run
|
||||
/// This is useful for forcing GL.IssuePluginEvent() to run and is used for
|
||||
/// wait for frames to render for ExtractFrame() and UpdateTimeScale()
|
||||
/// </summary>
|
||||
private static Camera GetDummyCamera()
|
||||
{
|
||||
if (_dummyCamera == null)
|
||||
{
|
||||
const string goName = "AVPro Video Dummy Camera";
|
||||
GameObject go = GameObject.Find(goName);
|
||||
if (go == null)
|
||||
{
|
||||
go = new GameObject(goName);
|
||||
go.hideFlags = HideFlags.HideInHierarchy | HideFlags.DontSave;
|
||||
go.SetActive(false);
|
||||
Object.DontDestroyOnLoad(go);
|
||||
|
||||
_dummyCamera = go.AddComponent<Camera>();
|
||||
_dummyCamera.hideFlags = HideFlags.HideInInspector | HideFlags.DontSave;
|
||||
_dummyCamera.cullingMask = 0;
|
||||
_dummyCamera.clearFlags = CameraClearFlags.Nothing;
|
||||
_dummyCamera.enabled = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
_dummyCamera = go.GetComponent<Camera>();
|
||||
}
|
||||
}
|
||||
//Debug.Assert(_dummyCamera != null);
|
||||
return _dummyCamera;
|
||||
}
|
||||
|
||||
private IEnumerator ExtractFrameCoroutine(Texture2D target, ProcessExtractedFrame callback, double timeSeconds = -1.0, bool accurateSeek = true, int timeoutMs = 1000, int timeThresholdMs = 100)
|
||||
{
|
||||
#if (!UNITY_EDITOR && UNITY_ANDROID) || UNITY_STANDALONE_WIN || UNITY_EDITOR_WIN || UNITY_STANDALONE_OSX || UNITY_EDITOR_OSX || UNITY_IOS || UNITY_TVOS
|
||||
Texture2D result = target;
|
||||
|
||||
Texture frame = null;
|
||||
|
||||
if (_controlInterface != null)
|
||||
{
|
||||
if (timeSeconds >= 0f)
|
||||
{
|
||||
Pause();
|
||||
|
||||
// If the right frame is already available (or close enough) just grab it
|
||||
if (TextureProducer.GetTexture() != null && (System.Math.Abs(_controlInterface.GetCurrentTime() - timeSeconds) < (timeThresholdMs / 1000.0)))
|
||||
{
|
||||
frame = TextureProducer.GetTexture();
|
||||
}
|
||||
else
|
||||
{
|
||||
int preSeekFrameCount = _textureInterface.GetTextureFrameCount();
|
||||
|
||||
// Seek to the frame
|
||||
if (accurateSeek)
|
||||
{
|
||||
_controlInterface.Seek(timeSeconds);
|
||||
}
|
||||
else
|
||||
{
|
||||
_controlInterface.SeekFast(timeSeconds);
|
||||
}
|
||||
|
||||
// Wait for the new frame to arrive
|
||||
if (!_controlInterface.WaitForNextFrame(GetDummyCamera(), preSeekFrameCount))
|
||||
{
|
||||
// If WaitForNextFrame fails (e.g. in android single threaded), we run the below code to asynchronously wait for the frame
|
||||
int currFc = TextureProducer.GetTextureFrameCount();
|
||||
int iterations = 0;
|
||||
int maxIterations = 50;
|
||||
|
||||
//+1 as often there will be an extra frame produced after pause (so we need to wait for the second frame instead)
|
||||
while((currFc + 1) >= TextureProducer.GetTextureFrameCount() && iterations++ < maxIterations)
|
||||
{
|
||||
yield return null;
|
||||
}
|
||||
}
|
||||
frame = TextureProducer.GetTexture();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
frame = TextureProducer.GetTexture();
|
||||
}
|
||||
}
|
||||
if (frame != null)
|
||||
{
|
||||
result = Helper.GetReadableTexture(frame, TextureProducer.RequiresVerticalFlip(), Helper.GetOrientation(Info.GetTextureTransform()), target);
|
||||
}
|
||||
#else
|
||||
Texture2D result = ExtractFrame(target, timeSeconds, accurateSeek, timeoutMs, timeThresholdMs);
|
||||
#endif
|
||||
callback(result);
|
||||
|
||||
yield return null;
|
||||
}
|
||||
|
||||
public void ExtractFrameAsync(Texture2D target, ProcessExtractedFrame callback, double timeSeconds = -1.0, bool accurateSeek = true, int timeoutMs = 1000, int timeThresholdMs = 100)
|
||||
{
|
||||
StartCoroutine(ExtractFrameCoroutine(target, callback, timeSeconds, accurateSeek, timeoutMs, timeThresholdMs));
|
||||
}
|
||||
|
||||
// "target" can be null or you can pass in an existing texture.
|
||||
public Texture2D ExtractFrame(Texture2D target, double timeSeconds = -1.0, bool accurateSeek = true, int timeoutMs = 1000, int timeThresholdMs = 100)
|
||||
{
|
||||
Texture2D result = target;
|
||||
|
||||
// Extract frames returns the internal frame of the video player
|
||||
Texture frame = ExtractFrame(timeSeconds, accurateSeek, timeoutMs, timeThresholdMs);
|
||||
if (frame != null)
|
||||
{
|
||||
result = Helper.GetReadableTexture(frame, TextureProducer.RequiresVerticalFlip(), Helper.GetOrientation(Info.GetTextureTransform()), target);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private Texture ExtractFrame(double timeSeconds = -1.0, bool accurateSeek = true, int timeoutMs = 1000, int timeThresholdMs = 100)
|
||||
{
|
||||
Texture result = null;
|
||||
|
||||
if (_controlInterface != null)
|
||||
{
|
||||
if (timeSeconds >= 0f)
|
||||
{
|
||||
Pause();
|
||||
|
||||
// If the right frame is already available (or close enough) just grab it
|
||||
if (TextureProducer.GetTexture() != null && (System.Math.Abs(_controlInterface.GetCurrentTime() - timeSeconds) < (timeThresholdMs / 1000.0)))
|
||||
{
|
||||
result = TextureProducer.GetTexture();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Store frame count before seek
|
||||
int frameCount = TextureProducer.GetTextureFrameCount();
|
||||
|
||||
// Seek to the frame
|
||||
if (accurateSeek)
|
||||
{
|
||||
_controlInterface.Seek(timeSeconds);
|
||||
}
|
||||
else
|
||||
{
|
||||
_controlInterface.SeekFast(timeSeconds);
|
||||
}
|
||||
|
||||
// Wait for frame to change
|
||||
ForceWaitForNewFrame(frameCount, timeoutMs);
|
||||
result = TextureProducer.GetTexture();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
result = TextureProducer.GetTexture();
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
#endregion // Extract Frame
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 810d3ce69a3b01f409c733c7cfbd119c
|
||||
timeCreated: 1544813302
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {fileID: 2800000, guid: bb83b41b53a59874692b83eab5873998, type: 3}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,129 @@
|
||||
using UnityEngine;
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Copyright 2015-2022 RenderHeads Ltd. All rights reserved.
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
namespace RenderHeads.Media.AVProVideo
|
||||
{
|
||||
public partial class MediaPlayer : MonoBehaviour
|
||||
{
|
||||
public bool OpenMediaFromBuffer(byte[] buffer, bool autoPlay = true)
|
||||
{
|
||||
_mediaPath = new MediaPath("buffer", MediaPathType.AbsolutePathOrURL);
|
||||
_autoPlayOnStart = autoPlay;
|
||||
|
||||
if (_controlInterface == null)
|
||||
{
|
||||
Initialise();
|
||||
}
|
||||
|
||||
return OpenMediaFromBufferInternal(buffer);
|
||||
}
|
||||
|
||||
public bool StartOpenChunkedMediaFromBuffer(ulong length, bool autoPlay = true)
|
||||
{
|
||||
_mediaPath = new MediaPath("buffer", MediaPathType.AbsolutePathOrURL);
|
||||
_autoPlayOnStart = autoPlay;
|
||||
|
||||
if (_controlInterface == null)
|
||||
{
|
||||
Initialise();
|
||||
}
|
||||
|
||||
return StartOpenMediaFromBufferInternal(length);
|
||||
}
|
||||
|
||||
public bool AddChunkToVideoBuffer(byte[] chunk, ulong offset, ulong chunkSize)
|
||||
{
|
||||
return AddChunkToBufferInternal(chunk, offset, chunkSize);
|
||||
}
|
||||
|
||||
public bool EndOpenChunkedVideoFromBuffer()
|
||||
{
|
||||
return EndOpenMediaFromBufferInternal();
|
||||
}
|
||||
|
||||
private bool OpenMediaFromBufferInternal(byte[] buffer)
|
||||
{
|
||||
bool result = false;
|
||||
// Open the video file
|
||||
if (_controlInterface != null)
|
||||
{
|
||||
CloseMedia();
|
||||
|
||||
_isMediaOpened = true;
|
||||
_autoPlayOnStartTriggered = !_autoPlayOnStart;
|
||||
|
||||
Helper.LogInfo("Opening buffer of length " + buffer.Length, this);
|
||||
|
||||
if (!_controlInterface.OpenMediaFromBuffer(buffer))
|
||||
{
|
||||
Debug.LogError("[AVProVideo] Failed to open buffer", this);
|
||||
if (GetCurrentPlatformOptions() != PlatformOptionsWindows || PlatformOptionsWindows.videoApi != Windows.VideoApi.DirectShow)
|
||||
{
|
||||
Debug.LogError("[AVProVideo] Loading from buffer is currently only supported in Windows when using the DirectShow API");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
SetPlaybackOptions();
|
||||
result = true;
|
||||
StartRenderCoroutine();
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private bool StartOpenMediaFromBufferInternal(ulong length)
|
||||
{
|
||||
bool result = false;
|
||||
// Open the video file
|
||||
if (_controlInterface != null)
|
||||
{
|
||||
CloseMedia();
|
||||
|
||||
_isMediaOpened = true;
|
||||
_autoPlayOnStartTriggered = !_autoPlayOnStart;
|
||||
|
||||
Helper.LogInfo("Starting Opening buffer of length " + length, this);
|
||||
|
||||
if (!_controlInterface.StartOpenMediaFromBuffer(length))
|
||||
{
|
||||
Debug.LogError("[AVProVideo] Failed to start open video from buffer", this);
|
||||
if (GetCurrentPlatformOptions() != PlatformOptionsWindows || PlatformOptionsWindows.videoApi != Windows.VideoApi.DirectShow)
|
||||
{
|
||||
Debug.LogError("[AVProVideo] Loading from buffer is currently only supported in Windows when using the DirectShow API");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
SetPlaybackOptions();
|
||||
result = true;
|
||||
StartRenderCoroutine();
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private bool AddChunkToBufferInternal(byte[] chunk, ulong offset, ulong chunkSize)
|
||||
{
|
||||
if (Control != null)
|
||||
{
|
||||
return Control.AddChunkToMediaBuffer(chunk, offset, chunkSize);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool EndOpenMediaFromBufferInternal()
|
||||
{
|
||||
if (Control != null)
|
||||
{
|
||||
return Control.EndOpenMediaFromBuffer();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: bd1bd18da7d2dc7468c9799e5b02caea
|
||||
timeCreated: 1544813302
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {fileID: 2800000, guid: bb83b41b53a59874692b83eab5873998, type: 3}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,60 @@
|
||||
using UnityEngine;
|
||||
#if NETFX_CORE
|
||||
using Windows.Storage.Streams;
|
||||
#endif
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Copyright 2015-2022 RenderHeads Ltd. All rights reserved.
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
namespace RenderHeads.Media.AVProVideo
|
||||
{
|
||||
public partial class MediaPlayer : MonoBehaviour
|
||||
{
|
||||
|
||||
#if NETFX_CORE
|
||||
public bool OpenVideoFromStream(IRandomAccessStream ras, string path, bool autoPlay = true)
|
||||
{
|
||||
_videoLocation = FileLocation.AbsolutePathOrURL;
|
||||
_videoPath = path;
|
||||
_autoPlayOnStart = autoPlay;
|
||||
|
||||
if (_controlInterface == null)
|
||||
{
|
||||
Initialise();
|
||||
}
|
||||
|
||||
return OpenVideoFromStream(ras);
|
||||
}
|
||||
|
||||
private bool OpenVideoFromStream(IRandomAccessStream ras)
|
||||
{
|
||||
bool result = false;
|
||||
// Open the video file
|
||||
if (_controlInterface != null)
|
||||
{
|
||||
CloseVideo();
|
||||
|
||||
_isVideoOpened = true;
|
||||
_autoPlayOnStartTriggered = !_autoPlayOnStart;
|
||||
|
||||
// Potentially override the file location
|
||||
long fileOffset = GetPlatformFileOffset();
|
||||
|
||||
if (!Control.OpenVideoFromFile(ras, _videoPath, fileOffset, null, _manuallySetAudioSourceProperties ? _sourceAudioSampleRate : 0,
|
||||
_manuallySetAudioSourceProperties ? _sourceAudioChannels : 0))
|
||||
{
|
||||
Debug.LogError("[AVProVideo] Failed to open " + _videoPath, this);
|
||||
}
|
||||
else
|
||||
{
|
||||
SetPlaybackOptions();
|
||||
result = true;
|
||||
StartRenderCoroutine();
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4e6c8c5399247d0478ed7ecf17b7d87f
|
||||
timeCreated: 1544813302
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {fileID: 2800000, guid: bb83b41b53a59874692b83eab5873998, type: 3}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,717 @@
|
||||
using UnityEngine;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Copyright 2015-2022 RenderHeads Ltd. All rights reserved.
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
namespace RenderHeads.Media.AVProVideo
|
||||
{
|
||||
public partial class MediaPlayer : MonoBehaviour
|
||||
{
|
||||
#region PlatformOptions
|
||||
[System.Serializable]
|
||||
public class PlatformOptions
|
||||
{
|
||||
public virtual bool IsModified()
|
||||
{
|
||||
return (httpHeaders.IsModified()
|
||||
|| keyAuth.IsModified()
|
||||
);
|
||||
}
|
||||
|
||||
public HttpHeaderData httpHeaders = new HttpHeaderData();
|
||||
public KeyAuthData keyAuth = new KeyAuthData();
|
||||
|
||||
// Decryption support
|
||||
public virtual string GetKeyServerAuthToken() { return keyAuth.keyServerToken; }
|
||||
//public virtual string GetKeyServerURL() { return null; }
|
||||
public virtual byte[] GetOverrideDecryptionKey() { return keyAuth.overrideDecryptionKey; }
|
||||
|
||||
public virtual bool StartWithHighestBandwidth() { return false; }
|
||||
}
|
||||
|
||||
[System.Serializable]
|
||||
public class OptionsWindows : PlatformOptions, ISerializationCallbackReceiver
|
||||
{
|
||||
public Windows.VideoApi videoApi = Windows.VideoApi.MediaFoundation;
|
||||
public bool useHardwareDecoding = true;
|
||||
public bool useTextureMips = false;
|
||||
public bool use10BitTextures = false;
|
||||
public bool hintAlphaChannel = false;
|
||||
public bool useLowLatency = false;
|
||||
public bool useCustomMovParser = false;
|
||||
public bool useHapNotchLC = false;
|
||||
public bool useStereoDetection = true;
|
||||
public bool useTextTrackSupport = true;
|
||||
public bool useFacebookAudio360Support = true;
|
||||
public bool useAudioDelay = false;
|
||||
public BufferedFrameSelectionMode bufferedFrameSelection = BufferedFrameSelectionMode.None;
|
||||
public bool pauseOnPrerollComplete = false;
|
||||
public string forceAudioOutputDeviceName = string.Empty;
|
||||
public List<string> preferredFilters = new List<string>();
|
||||
public Windows.AudioOutput audioOutput = Windows.AudioOutput.System;
|
||||
public Audio360ChannelMode audio360ChannelMode = Audio360ChannelMode.TBE_8_2;
|
||||
|
||||
/// WinRT only
|
||||
public bool startWithHighestBitrate = false;
|
||||
|
||||
/// WinRT only
|
||||
public bool useLowLiveLatency = false;
|
||||
|
||||
/// Hap & NotchLC only
|
||||
[Range(1, 16)]
|
||||
public int parallelFrameCount = 3;
|
||||
/// Hap & NotchLC only
|
||||
[Range(1, 16)]
|
||||
public int prerollFrameCount = 4;
|
||||
|
||||
public override bool IsModified()
|
||||
{
|
||||
return (base.IsModified()
|
||||
|| !useHardwareDecoding
|
||||
|| useTextureMips
|
||||
|| use10BitTextures
|
||||
|| hintAlphaChannel
|
||||
|| useLowLatency
|
||||
|| useCustomMovParser
|
||||
|| useHapNotchLC
|
||||
|| !useStereoDetection
|
||||
|| !useTextTrackSupport
|
||||
|| !useFacebookAudio360Support
|
||||
|| useAudioDelay
|
||||
|| pauseOnPrerollComplete
|
||||
|| bufferedFrameSelection != BufferedFrameSelectionMode.None
|
||||
|| videoApi != Windows.VideoApi.MediaFoundation
|
||||
|| audioOutput != Windows.AudioOutput.System
|
||||
|| audio360ChannelMode != Audio360ChannelMode.TBE_8_2
|
||||
|| !string.IsNullOrEmpty(forceAudioOutputDeviceName)
|
||||
|| preferredFilters.Count != 0
|
||||
|| startWithHighestBitrate
|
||||
|| useLowLiveLatency
|
||||
|| parallelFrameCount != 3
|
||||
|| prerollFrameCount != 4
|
||||
);
|
||||
}
|
||||
|
||||
public override bool StartWithHighestBandwidth() { return startWithHighestBitrate; }
|
||||
|
||||
#region Upgrade from Version 1.x
|
||||
[SerializeField, HideInInspector]
|
||||
private bool useUnityAudio = false;
|
||||
[SerializeField, HideInInspector]
|
||||
private bool enableAudio360 = false;
|
||||
|
||||
void ISerializationCallbackReceiver.OnBeforeSerialize() { }
|
||||
|
||||
void ISerializationCallbackReceiver.OnAfterDeserialize()
|
||||
{
|
||||
if (useUnityAudio && audioOutput == Windows.AudioOutput.System)
|
||||
{
|
||||
audioOutput = Windows.AudioOutput.Unity;
|
||||
useUnityAudio = false;
|
||||
}
|
||||
if (enableAudio360 && audioOutput == Windows.AudioOutput.System)
|
||||
{
|
||||
audioOutput = Windows.AudioOutput.FacebookAudio360;
|
||||
enableAudio360 = false;
|
||||
}
|
||||
}
|
||||
#endregion // Upgrade from Version 1.x
|
||||
}
|
||||
|
||||
[System.Serializable]
|
||||
public class OptionsWindowsUWP : PlatformOptions
|
||||
{
|
||||
public bool useHardwareDecoding = true;
|
||||
public bool useTextureMips = false;
|
||||
public bool use10BitTextures = false;
|
||||
public bool hintOutput10Bit = false;
|
||||
public bool useLowLatency = false;
|
||||
public WindowsUWP.VideoApi videoApi = WindowsUWP.VideoApi.WinRT;
|
||||
public WindowsUWP.AudioOutput audioOutput = WindowsUWP.AudioOutput.System;
|
||||
public Audio360ChannelMode audio360ChannelMode = Audio360ChannelMode.TBE_8_2;
|
||||
|
||||
/// WinRT only
|
||||
public bool startWithHighestBitrate = false;
|
||||
|
||||
/// WinRT only
|
||||
public bool useLowLiveLatency = false;
|
||||
|
||||
public override bool IsModified()
|
||||
{
|
||||
return (base.IsModified()
|
||||
|| !useHardwareDecoding
|
||||
|| useTextureMips
|
||||
|| use10BitTextures
|
||||
|| useLowLatency
|
||||
|| audioOutput != WindowsUWP.AudioOutput.System
|
||||
|| (audio360ChannelMode != Audio360ChannelMode.TBE_8_2)
|
||||
|| videoApi != WindowsUWP.VideoApi.WinRT
|
||||
|| startWithHighestBitrate
|
||||
|| useLowLiveLatency
|
||||
);
|
||||
}
|
||||
|
||||
public override bool StartWithHighestBandwidth() { return startWithHighestBitrate; }
|
||||
}
|
||||
|
||||
[System.Serializable]
|
||||
public class OptionsApple: PlatformOptions
|
||||
{
|
||||
public enum TextureFormat: int
|
||||
{
|
||||
BGRA,
|
||||
YCbCr420,
|
||||
}
|
||||
|
||||
public enum AudioMode
|
||||
{
|
||||
SystemDirect,
|
||||
Unity,
|
||||
SystemDirectWithCapture,
|
||||
};
|
||||
|
||||
[Flags]
|
||||
public enum Flags: int
|
||||
{
|
||||
// Common
|
||||
None = 0,
|
||||
GenerateMipMaps = 1 << 0,
|
||||
|
||||
// iOS & macOS
|
||||
AllowExternalPlayback = 1 << 8,
|
||||
PlayWithoutBuffering = 1 << 9,
|
||||
UseSinglePlayerItem = 1 << 10,
|
||||
|
||||
// iOS
|
||||
ResumeMediaPlaybackAfterAudioSessionRouteChange = 1 << 16,
|
||||
}
|
||||
|
||||
public enum Resolution
|
||||
{
|
||||
NoPreference,
|
||||
_480p,
|
||||
_720p,
|
||||
_1080p,
|
||||
_1440p,
|
||||
_2160p,
|
||||
Custom
|
||||
}
|
||||
|
||||
public enum BitRateUnits
|
||||
{
|
||||
bps,
|
||||
Kbps,
|
||||
Mbps,
|
||||
}
|
||||
|
||||
private readonly TextureFormat DefaultTextureFormat;
|
||||
private readonly Flags DefaultFlags;
|
||||
public TextureFormat textureFormat;
|
||||
|
||||
private AudioMode _previousAudioMode = AudioMode.SystemDirect;
|
||||
public AudioMode previousAudioMode
|
||||
{
|
||||
get { return _previousAudioMode; }
|
||||
}
|
||||
|
||||
[SerializeField]
|
||||
private AudioMode _audioMode;
|
||||
public AudioMode audioMode
|
||||
{
|
||||
get { return _audioMode; }
|
||||
set
|
||||
{
|
||||
if (_audioMode != value)
|
||||
{
|
||||
_previousAudioMode = _audioMode;
|
||||
_audioMode = value;
|
||||
_changed |= ChangeFlags.AudioMode;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[SerializeField]
|
||||
private Flags _flags;
|
||||
public Flags flags
|
||||
{
|
||||
get { return _flags; }
|
||||
set
|
||||
{
|
||||
Flags changed = _flags ^ value;
|
||||
if (changed != 0)
|
||||
{
|
||||
if ((changed & Flags.PlayWithoutBuffering) == Flags.PlayWithoutBuffering)
|
||||
{
|
||||
_changed |= ChangeFlags.PlayWithoutBuffering;
|
||||
}
|
||||
if ((changed & Flags.ResumeMediaPlaybackAfterAudioSessionRouteChange) == Flags.ResumeMediaPlaybackAfterAudioSessionRouteChange)
|
||||
{
|
||||
_changed |= ChangeFlags.ResumeMediaPlaybackAfterAudioSessionRouteChange;
|
||||
}
|
||||
_flags = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public float maximumPlaybackRate = 2.0f;
|
||||
|
||||
[Flags]
|
||||
public enum ChangeFlags: int
|
||||
{
|
||||
None = 0,
|
||||
PreferredPeakBitRate = 1 << 1,
|
||||
PreferredForwardBufferDuration = 1 << 2,
|
||||
PlayWithoutBuffering = 1 << 3,
|
||||
PreferredMaximumResolution = 1 << 4,
|
||||
AudioMode = 1 << 5,
|
||||
ResumeMediaPlaybackAfterAudioSessionRouteChange = 1 << 6,
|
||||
All = -1
|
||||
}
|
||||
|
||||
private ChangeFlags _changed = ChangeFlags.None;
|
||||
|
||||
[SerializeField]
|
||||
private float _preferredPeakBitRate = 0.0f;
|
||||
public float preferredPeakBitRate
|
||||
{
|
||||
get { return _preferredPeakBitRate; }
|
||||
set
|
||||
{
|
||||
if (_preferredPeakBitRate != value)
|
||||
{
|
||||
_changed |= ChangeFlags.PreferredPeakBitRate;
|
||||
_preferredPeakBitRate = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[SerializeField]
|
||||
private BitRateUnits _preferredPeakBitRateUnits = BitRateUnits.Kbps;
|
||||
public BitRateUnits preferredPeakBitRateUnits
|
||||
{
|
||||
get { return _preferredPeakBitRateUnits; }
|
||||
set
|
||||
{
|
||||
if (_preferredPeakBitRateUnits != value)
|
||||
{
|
||||
_changed |= ChangeFlags.PreferredPeakBitRate;
|
||||
_preferredPeakBitRateUnits = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[SerializeField]
|
||||
private double _preferredForwardBufferDuration = 0.0;
|
||||
public double preferredForwardBufferDuration
|
||||
{
|
||||
get
|
||||
{
|
||||
return _preferredForwardBufferDuration;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (_preferredForwardBufferDuration != value)
|
||||
{
|
||||
_changed |= ChangeFlags.PreferredForwardBufferDuration;
|
||||
_preferredForwardBufferDuration = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[SerializeField]
|
||||
private Resolution _preferredMaximumResolution = Resolution.NoPreference;
|
||||
public Resolution preferredMaximumResolution
|
||||
{
|
||||
get
|
||||
{
|
||||
return _preferredMaximumResolution;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (_preferredMaximumResolution != value)
|
||||
{
|
||||
_changed |= ChangeFlags.PreferredMaximumResolution;
|
||||
_preferredMaximumResolution = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if UNITY_2017_2_OR_NEWER
|
||||
[SerializeField]
|
||||
private Vector2Int _customPreferredMaximumResolution = Vector2Int.zero;
|
||||
public Vector2Int customPreferredMaximumResolution
|
||||
{
|
||||
get
|
||||
{
|
||||
return _customPreferredMaximumResolution;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (_customPreferredMaximumResolution != value)
|
||||
{
|
||||
_changed |= ChangeFlags.PreferredMaximumResolution;
|
||||
_customPreferredMaximumResolution = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
private static double BitRateInBitsPerSecond(float value, BitRateUnits units)
|
||||
{
|
||||
switch (units)
|
||||
{
|
||||
case BitRateUnits.bps:
|
||||
return (double)value;
|
||||
case BitRateUnits.Kbps:
|
||||
return (double)value * 1000.0;
|
||||
case BitRateUnits.Mbps:
|
||||
return (double)value * 1000000.0;
|
||||
default:
|
||||
return 0.0;
|
||||
}
|
||||
}
|
||||
|
||||
public double GetPreferredPeakBitRateInBitsPerSecond()
|
||||
{
|
||||
return BitRateInBitsPerSecond(preferredPeakBitRate, preferredPeakBitRateUnits);
|
||||
}
|
||||
|
||||
public OptionsApple(TextureFormat defaultTextureFormat, Flags defaultFlags)
|
||||
{
|
||||
DefaultTextureFormat = defaultTextureFormat;
|
||||
DefaultFlags = defaultFlags;
|
||||
textureFormat = defaultTextureFormat;
|
||||
audioMode = AudioMode.SystemDirect;
|
||||
flags = defaultFlags;
|
||||
}
|
||||
|
||||
public override bool IsModified()
|
||||
{
|
||||
return base.IsModified()
|
||||
|| textureFormat != DefaultTextureFormat
|
||||
|| audioMode != AudioMode.SystemDirect
|
||||
|| flags != DefaultFlags
|
||||
|| preferredMaximumResolution != Resolution.NoPreference
|
||||
|| preferredPeakBitRate != 0.0f
|
||||
|| preferredForwardBufferDuration != 0.0;
|
||||
}
|
||||
|
||||
public bool HasChanged(ChangeFlags flags = ChangeFlags.All)
|
||||
{
|
||||
return (_changed & flags) != ChangeFlags.None;
|
||||
}
|
||||
|
||||
public void ClearChanges()
|
||||
{
|
||||
_changed = ChangeFlags.None;
|
||||
}
|
||||
}
|
||||
|
||||
[System.Serializable]
|
||||
public class OptionsAndroid : PlatformOptions, ISerializationCallbackReceiver
|
||||
{
|
||||
public enum Resolution
|
||||
{
|
||||
NoPreference,
|
||||
_480p,
|
||||
_720p,
|
||||
_1080p,
|
||||
_2160p,
|
||||
Custom
|
||||
}
|
||||
|
||||
public enum BitRateUnits
|
||||
{
|
||||
bps,
|
||||
Kbps,
|
||||
Mbps,
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum ChangeFlags : int
|
||||
{
|
||||
None = 0,
|
||||
PreferredPeakBitRate = 1 << 1,
|
||||
PreferredMaximumResolution = 1 << 2,
|
||||
PreferredCustomResolution = 1 << 3,
|
||||
All = -1
|
||||
}
|
||||
|
||||
private ChangeFlags _changed = ChangeFlags.None;
|
||||
|
||||
[SerializeField]
|
||||
private Resolution _preferredMaximumResolution = Resolution.NoPreference;
|
||||
public Resolution preferredMaximumResolution
|
||||
{
|
||||
get { return _preferredMaximumResolution; }
|
||||
set
|
||||
{
|
||||
if (_preferredMaximumResolution != value)
|
||||
{
|
||||
_changed |= ChangeFlags.PreferredMaximumResolution;
|
||||
_preferredMaximumResolution = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if UNITY_2017_2_OR_NEWER
|
||||
[SerializeField]
|
||||
private Vector2Int _customPreferredMaximumResolution = Vector2Int.zero;
|
||||
public Vector2Int customPreferredMaximumResolution
|
||||
{
|
||||
get { return _customPreferredMaximumResolution; }
|
||||
set
|
||||
{
|
||||
if (_customPreferredMaximumResolution != value)
|
||||
{
|
||||
_changed |= ChangeFlags.PreferredCustomResolution;
|
||||
_customPreferredMaximumResolution = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
[SerializeField]
|
||||
private float _preferredPeakBitRate = 0.0f;
|
||||
public float preferredPeakBitRate
|
||||
{
|
||||
get { return _preferredPeakBitRate; }
|
||||
set
|
||||
{
|
||||
if (_preferredPeakBitRate != value)
|
||||
{
|
||||
_changed |= ChangeFlags.PreferredPeakBitRate;
|
||||
_preferredPeakBitRate = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[SerializeField]
|
||||
private BitRateUnits _preferredPeakBitRateUnits = BitRateUnits.Kbps;
|
||||
public BitRateUnits preferredPeakBitRateUnits
|
||||
{
|
||||
get { return _preferredPeakBitRateUnits; }
|
||||
set
|
||||
{
|
||||
if (_preferredPeakBitRateUnits != value)
|
||||
{
|
||||
_changed |= ChangeFlags.PreferredPeakBitRate;
|
||||
_preferredPeakBitRateUnits = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public Android.VideoApi videoApi = Android.VideoApi.ExoPlayer;
|
||||
public bool useFastOesPath = false;
|
||||
public bool showPosterFrame = false;
|
||||
public Android.AudioOutput audioOutput = Android.AudioOutput.System;
|
||||
public Audio360ChannelMode audio360ChannelMode = Audio360ChannelMode.TBE_8_2;
|
||||
public bool preferSoftwareDecoder = false;
|
||||
public bool forceRtpTCP = false;
|
||||
public Android.TextureFiltering blitTextureFiltering = Android.TextureFiltering.Point;
|
||||
|
||||
[SerializeField, Tooltip("Byte offset into the file where the media file is located. This is useful when hiding or packing media files within another file.")]
|
||||
public int fileOffset = 0;
|
||||
|
||||
public bool startWithHighestBitrate = false;
|
||||
|
||||
public int minBufferMs = Android.Default_MinBufferTimeMs;
|
||||
public int maxBufferMs = Android.Default_MaxBufferTimeMs;
|
||||
public int bufferForPlaybackMs = Android.Default_BufferForPlaybackMs;
|
||||
public int bufferForPlaybackAfterRebufferMs = Android.Default_BufferForPlaybackAfterRebufferMs;
|
||||
|
||||
public override bool IsModified()
|
||||
{
|
||||
return (base.IsModified()
|
||||
|| (fileOffset != 0)
|
||||
|| useFastOesPath
|
||||
|| showPosterFrame
|
||||
|| (videoApi != Android.VideoApi.ExoPlayer)
|
||||
|| audioOutput != Android.AudioOutput.System
|
||||
|| (audio360ChannelMode != Audio360ChannelMode.TBE_8_2)
|
||||
|| preferSoftwareDecoder
|
||||
|| forceRtpTCP
|
||||
|| startWithHighestBitrate
|
||||
|| (minBufferMs != Android.Default_MinBufferTimeMs)
|
||||
|| (maxBufferMs != Android.Default_MaxBufferTimeMs)
|
||||
|| (bufferForPlaybackMs != Android.Default_BufferForPlaybackMs)
|
||||
|| (bufferForPlaybackAfterRebufferMs != Android.Default_BufferForPlaybackAfterRebufferMs)
|
||||
|| (preferredMaximumResolution != Resolution.NoPreference)
|
||||
|| (preferredPeakBitRate != 0.0f)
|
||||
|| (blitTextureFiltering != Android.TextureFiltering.Point)
|
||||
);
|
||||
}
|
||||
|
||||
private static double BitRateInBitsPerSecond(float value, BitRateUnits units)
|
||||
{
|
||||
switch (units)
|
||||
{
|
||||
case BitRateUnits.bps:
|
||||
return (double)value;
|
||||
case BitRateUnits.Kbps:
|
||||
return (double)value * 1000.0;
|
||||
case BitRateUnits.Mbps:
|
||||
return (double)value * 1000000.0;
|
||||
default:
|
||||
return 0.0;
|
||||
}
|
||||
}
|
||||
|
||||
public double GetPreferredPeakBitRateInBitsPerSecond()
|
||||
{
|
||||
_changed &= ~ChangeFlags.PreferredPeakBitRate;
|
||||
return BitRateInBitsPerSecond(preferredPeakBitRate, preferredPeakBitRateUnits);
|
||||
}
|
||||
|
||||
public override bool StartWithHighestBandwidth()
|
||||
{
|
||||
return startWithHighestBitrate;
|
||||
}
|
||||
|
||||
public bool HasChanged(ChangeFlags flags = ChangeFlags.All, bool bClearFlags = false)
|
||||
{
|
||||
bool bReturn = ((_changed & flags) != ChangeFlags.None);
|
||||
if (bClearFlags)
|
||||
{
|
||||
_changed = ChangeFlags.None;
|
||||
}
|
||||
return bReturn;
|
||||
}
|
||||
|
||||
#region Upgrade from Version 1.x
|
||||
[SerializeField, HideInInspector]
|
||||
private bool enableAudio360 = false;
|
||||
|
||||
void ISerializationCallbackReceiver.OnBeforeSerialize() { }
|
||||
|
||||
void ISerializationCallbackReceiver.OnAfterDeserialize()
|
||||
{
|
||||
if (enableAudio360 && audioOutput == Android.AudioOutput.System)
|
||||
{
|
||||
audioOutput = Android.AudioOutput.FacebookAudio360;
|
||||
enableAudio360 = false;
|
||||
}
|
||||
}
|
||||
#endregion // Upgrade from Version 1.x
|
||||
}
|
||||
|
||||
[System.Serializable]
|
||||
public class OptionsWebGL : PlatformOptions
|
||||
{
|
||||
public WebGL.ExternalLibrary externalLibrary = WebGL.ExternalLibrary.None;
|
||||
public bool useTextureMips = false;
|
||||
|
||||
public override bool IsModified()
|
||||
{
|
||||
return (base.IsModified() || externalLibrary != WebGL.ExternalLibrary.None || useTextureMips);
|
||||
}
|
||||
|
||||
// Decryption support
|
||||
public override string GetKeyServerAuthToken() { return null; }
|
||||
public override byte[] GetOverrideDecryptionKey() { return null; }
|
||||
}
|
||||
|
||||
// TODO: move these to a Setup object
|
||||
[SerializeField] OptionsWindows _optionsWindows = new OptionsWindows();
|
||||
[SerializeField] OptionsApple _optionsMacOSX = new OptionsApple(OptionsApple.TextureFormat.BGRA, OptionsApple.Flags.None);
|
||||
[SerializeField] OptionsApple _optionsIOS = new OptionsApple(OptionsApple.TextureFormat.BGRA, OptionsApple.Flags.None);
|
||||
[SerializeField] OptionsApple _optionsTVOS = new OptionsApple(OptionsApple.TextureFormat.BGRA, OptionsApple.Flags.None);
|
||||
[SerializeField] OptionsAndroid _optionsAndroid = new OptionsAndroid();
|
||||
[SerializeField] OptionsWindowsUWP _optionsWindowsUWP = new OptionsWindowsUWP();
|
||||
[SerializeField] OptionsWebGL _optionsWebGL = new OptionsWebGL();
|
||||
|
||||
public OptionsWindows PlatformOptionsWindows { get { return _optionsWindows; } }
|
||||
public OptionsApple PlatformOptionsMacOSX { get { return _optionsMacOSX; } }
|
||||
public OptionsApple PlatformOptionsIOS { get { return _optionsIOS; } }
|
||||
public OptionsApple PlatformOptionsTVOS { get { return _optionsTVOS; } }
|
||||
public OptionsAndroid PlatformOptionsAndroid { get { return _optionsAndroid; } }
|
||||
public OptionsWindowsUWP PlatformOptionsWindowsUWP { get { return _optionsWindowsUWP; } }
|
||||
public OptionsWebGL PlatformOptionsWebGL { get { return _optionsWebGL; } }
|
||||
|
||||
#endregion // PlatformOptions
|
||||
}
|
||||
|
||||
#region PlatformOptionsExtensions
|
||||
public static class OptionsAppleExtensions
|
||||
{
|
||||
public static bool GenerateMipmaps(this MediaPlayer.OptionsApple.Flags flags)
|
||||
{
|
||||
return (flags & MediaPlayer.OptionsApple.Flags.GenerateMipMaps) == MediaPlayer.OptionsApple.Flags.GenerateMipMaps;
|
||||
}
|
||||
|
||||
public static MediaPlayer.OptionsApple.Flags SetGenerateMipMaps(this MediaPlayer.OptionsApple.Flags flags, bool b)
|
||||
{
|
||||
if (flags.GenerateMipmaps() ^ b)
|
||||
{
|
||||
flags = b ? flags | MediaPlayer.OptionsApple.Flags.GenerateMipMaps
|
||||
: flags & ~MediaPlayer.OptionsApple.Flags.GenerateMipMaps;
|
||||
}
|
||||
return flags;
|
||||
}
|
||||
|
||||
public static bool AllowExternalPlayback(this MediaPlayer.OptionsApple.Flags flags)
|
||||
{
|
||||
return (flags & MediaPlayer.OptionsApple.Flags.AllowExternalPlayback) == MediaPlayer.OptionsApple.Flags.AllowExternalPlayback;
|
||||
}
|
||||
|
||||
public static MediaPlayer.OptionsApple.Flags SetAllowExternalPlayback(this MediaPlayer.OptionsApple.Flags flags, bool b)
|
||||
{
|
||||
if (flags.AllowExternalPlayback() ^ b)
|
||||
{
|
||||
flags = b ? flags | MediaPlayer.OptionsApple.Flags.AllowExternalPlayback
|
||||
: flags & ~MediaPlayer.OptionsApple.Flags.AllowExternalPlayback;
|
||||
}
|
||||
return flags;
|
||||
}
|
||||
|
||||
public static bool PlayWithoutBuffering(this MediaPlayer.OptionsApple.Flags flags)
|
||||
{
|
||||
return (flags & MediaPlayer.OptionsApple.Flags.PlayWithoutBuffering) == MediaPlayer.OptionsApple.Flags.PlayWithoutBuffering;
|
||||
}
|
||||
|
||||
public static MediaPlayer.OptionsApple.Flags SetPlayWithoutBuffering(this MediaPlayer.OptionsApple.Flags flags, bool b)
|
||||
{
|
||||
if (flags.PlayWithoutBuffering() ^ b)
|
||||
{
|
||||
flags = b ? flags | MediaPlayer.OptionsApple.Flags.PlayWithoutBuffering
|
||||
: flags & ~MediaPlayer.OptionsApple.Flags.PlayWithoutBuffering;
|
||||
}
|
||||
return flags;
|
||||
}
|
||||
|
||||
public static bool UseSinglePlayerItem(this MediaPlayer.OptionsApple.Flags flags)
|
||||
{
|
||||
return (flags & MediaPlayer.OptionsApple.Flags.UseSinglePlayerItem) == MediaPlayer.OptionsApple.Flags.UseSinglePlayerItem;
|
||||
}
|
||||
|
||||
public static MediaPlayer.OptionsApple.Flags SetUseSinglePlayerItem(this MediaPlayer.OptionsApple.Flags flags, bool b)
|
||||
{
|
||||
if (flags.UseSinglePlayerItem() ^ b)
|
||||
{
|
||||
flags = b ? flags | MediaPlayer.OptionsApple.Flags.UseSinglePlayerItem
|
||||
: flags & ~MediaPlayer.OptionsApple.Flags.UseSinglePlayerItem;
|
||||
}
|
||||
return flags;
|
||||
}
|
||||
|
||||
public static bool ResumePlaybackAfterAudioSessionRouteChange(this MediaPlayer.OptionsApple.Flags flags)
|
||||
{
|
||||
return (flags & MediaPlayer.OptionsApple.Flags.ResumeMediaPlaybackAfterAudioSessionRouteChange) == MediaPlayer.OptionsApple.Flags.ResumeMediaPlaybackAfterAudioSessionRouteChange;
|
||||
}
|
||||
|
||||
public static MediaPlayer.OptionsApple.Flags SetResumePlaybackAfterAudioSessionRouteChange(this MediaPlayer.OptionsApple.Flags flags, bool b)
|
||||
{
|
||||
if (flags.ResumePlaybackAfterAudioSessionRouteChange() ^ b)
|
||||
{
|
||||
flags = b ? flags | MediaPlayer.OptionsApple.Flags.ResumeMediaPlaybackAfterAudioSessionRouteChange
|
||||
: flags & ~MediaPlayer.OptionsApple.Flags.ResumeMediaPlaybackAfterAudioSessionRouteChange;
|
||||
}
|
||||
return flags;
|
||||
}
|
||||
}
|
||||
#endregion // PlatformOptionsExtensions
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1d9536a1e758279489d9add3e1ba26ad
|
||||
timeCreated: 1544813302
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {fileID: 2800000, guid: bb83b41b53a59874692b83eab5873998, type: 3}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,150 @@
|
||||
using UnityEngine;
|
||||
using System.Collections;
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Copyright 2015-2022 RenderHeads Ltd. All rights reserved.
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
namespace RenderHeads.Media.AVProVideo
|
||||
{
|
||||
public partial class MediaPlayer : MonoBehaviour
|
||||
{
|
||||
public bool EnableSubtitles(MediaPath mediaPath)
|
||||
{
|
||||
bool result = false;
|
||||
if (_subtitlesInterface != null)
|
||||
{
|
||||
if (mediaPath != null && !string.IsNullOrEmpty(mediaPath.Path))
|
||||
{
|
||||
string fullPath = mediaPath.GetResolvedFullPath();
|
||||
|
||||
bool checkForFileExist = true;
|
||||
if (fullPath.Contains("://"))
|
||||
{
|
||||
checkForFileExist = false;
|
||||
}
|
||||
#if (!UNITY_EDITOR && UNITY_ANDROID)
|
||||
checkForFileExist = false;
|
||||
#endif
|
||||
|
||||
if (checkForFileExist && !System.IO.File.Exists(fullPath))
|
||||
{
|
||||
Debug.LogError("[AVProVideo] Subtitle file not found: " + fullPath, this);
|
||||
}
|
||||
else
|
||||
{
|
||||
Helper.LogInfo("Opening subtitles " + fullPath, this);
|
||||
|
||||
_previousSubtitleIndex = -1;
|
||||
|
||||
try
|
||||
{
|
||||
if (fullPath.Contains("://"))
|
||||
{
|
||||
// Use coroutine and WWW class for loading
|
||||
if (_loadSubtitlesRoutine != null)
|
||||
{
|
||||
StopCoroutine(_loadSubtitlesRoutine);
|
||||
_loadSubtitlesRoutine = null;
|
||||
}
|
||||
_loadSubtitlesRoutine = StartCoroutine(LoadSubtitlesCoroutine(fullPath, mediaPath));
|
||||
}
|
||||
else
|
||||
{
|
||||
// Load directly from file
|
||||
string subtitleData = System.IO.File.ReadAllText(fullPath);
|
||||
if (_subtitlesInterface.LoadSubtitlesSRT(subtitleData))
|
||||
{
|
||||
_subtitlePath = mediaPath;
|
||||
_sideloadSubtitles = false;
|
||||
result = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogError("[AVProVideo] Failed to load subtitles" + fullPath, this);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
catch (System.Exception e)
|
||||
{
|
||||
Debug.LogError("[AVProVideo] Failed to load subtitles " + fullPath, this);
|
||||
Debug.LogException(e, this);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogError("[AVProVideo] No subtitle file path specified", this);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_queueSubtitlePath = mediaPath;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private IEnumerator LoadSubtitlesCoroutine(string url, MediaPath mediaPath)
|
||||
{
|
||||
UnityEngine.Networking.UnityWebRequest www = UnityEngine.Networking.UnityWebRequest.Get(url);
|
||||
#if UNITY_2017_2_OR_NEWER
|
||||
yield return www.SendWebRequest();
|
||||
#else
|
||||
yield return www.Send();
|
||||
#endif
|
||||
|
||||
string subtitleData = string.Empty;
|
||||
|
||||
#if UNITY_2020_1_OR_NEWER
|
||||
if (www.result == UnityEngine.Networking.UnityWebRequest.Result.Success)
|
||||
#elif UNITY_2017_1_OR_NEWER
|
||||
if (!www.isNetworkError)
|
||||
#else
|
||||
if (!www.isError)
|
||||
#endif
|
||||
{
|
||||
subtitleData = ((UnityEngine.Networking.DownloadHandler)www.downloadHandler).text;
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogError("[AVProVideo] Error loading subtitles '" + www.error + "' from " + url);
|
||||
}
|
||||
|
||||
if (_subtitlesInterface.LoadSubtitlesSRT(subtitleData))
|
||||
{
|
||||
_subtitlePath = mediaPath;
|
||||
_sideloadSubtitles = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogError("[AVProVideo] Failed to load subtitles" + url, this);
|
||||
}
|
||||
|
||||
_loadSubtitlesRoutine = null;
|
||||
|
||||
www.Dispose();
|
||||
}
|
||||
|
||||
public void DisableSubtitles()
|
||||
{
|
||||
if (_loadSubtitlesRoutine != null)
|
||||
{
|
||||
StopCoroutine(_loadSubtitlesRoutine);
|
||||
_loadSubtitlesRoutine = null;
|
||||
}
|
||||
|
||||
if (_subtitlesInterface != null)
|
||||
{
|
||||
_previousSubtitleIndex = -1;
|
||||
_sideloadSubtitles = false;
|
||||
_subtitlesInterface.LoadSubtitlesSRT(string.Empty);
|
||||
}
|
||||
else
|
||||
{
|
||||
_queueSubtitlePath = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f4ed2744d6ff80845bbbd59e8f6c732b
|
||||
timeCreated: 1544813302
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {fileID: 2800000, guid: bb83b41b53a59874692b83eab5873998, type: 3}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,93 @@
|
||||
using UnityEngine;
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Copyright 2015-2022 RenderHeads Ltd. All rights reserved.
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
namespace RenderHeads.Media.AVProVideo
|
||||
{
|
||||
public partial class MediaPlayer : MonoBehaviour
|
||||
{
|
||||
|
||||
#region Support for Time Scale
|
||||
#if AVPROVIDEO_BETA_SUPPORT_TIMESCALE
|
||||
// Adjust this value to get faster performance but may drop frames.
|
||||
// Wait longer to ensure there is enough time for frames to process
|
||||
private const float TimeScaleTimeoutMs = 20f;
|
||||
private bool _timeScaleIsControlling;
|
||||
private double _timeScaleVideoTime;
|
||||
|
||||
private void UpdateTimeScale()
|
||||
{
|
||||
if (Time.timeScale != 1f || Time.captureFramerate != 0)
|
||||
{
|
||||
if (_controlInterface.IsPlaying())
|
||||
{
|
||||
_controlInterface.Pause();
|
||||
_timeScaleIsControlling = true;
|
||||
_timeScaleVideoTime = _controlInterface.GetCurrentTime();
|
||||
}
|
||||
|
||||
if (_timeScaleIsControlling)
|
||||
{
|
||||
// Progress time
|
||||
_timeScaleVideoTime += Time.deltaTime;
|
||||
|
||||
// Handle looping
|
||||
if (_controlInterface.IsLooping() && _timeScaleVideoTime >= Info.GetDuration())
|
||||
{
|
||||
// TODO: really we should seek to (_timeScaleVideoTime % Info.GetDuration())
|
||||
_timeScaleVideoTime = 0.0;
|
||||
}
|
||||
|
||||
int preSeekFrameCount = TextureProducer.GetTextureFrameCount();
|
||||
|
||||
// Seek to the new time
|
||||
{
|
||||
double preSeekTime = Control.GetCurrentTime();
|
||||
|
||||
// Seek
|
||||
_controlInterface.Seek(_timeScaleVideoTime);
|
||||
|
||||
// Early out, if after the seek the time hasn't changed, the seek was probably too small to go to the next frame.
|
||||
// TODO: This behaviour may be different on other platforms (not Windows) and needs more testing.
|
||||
if (Mathf.Approximately((float)preSeekTime, (float)_controlInterface.GetCurrentTime()))
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Wait for the new frame to arrive
|
||||
if (!_controlInterface.WaitForNextFrame(GetDummyCamera(), preSeekFrameCount))
|
||||
{
|
||||
// If WaitForNextFrame fails (e.g. in android single threaded), we run the below code to asynchronously wait for the frame
|
||||
System.DateTime startTime = System.DateTime.Now;
|
||||
int lastFrameCount = TextureProducer.GetTextureFrameCount();
|
||||
|
||||
while (_controlInterface != null && (System.DateTime.Now - startTime).TotalMilliseconds < (double)TimeScaleTimeoutMs)
|
||||
{
|
||||
_playerInterface.Update();
|
||||
_playerInterface.Render();
|
||||
GetDummyCamera().Render();
|
||||
if (lastFrameCount != TextureProducer.GetTextureFrameCount())
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Restore playback when timeScale becomes 1
|
||||
if (_timeScaleIsControlling)
|
||||
{
|
||||
_controlInterface.Play();
|
||||
_timeScaleIsControlling = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#endregion // Support for Time Scale
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: cdb92d6bab7106944bcd3cd7a034df6e
|
||||
timeCreated: 1544813302
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {fileID: 2800000, guid: bb83b41b53a59874692b83eab5873998, type: 3}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,83 @@
|
||||
using UnityEngine;
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Copyright 2015-2022 RenderHeads Ltd. All rights reserved.
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
namespace RenderHeads.Media.AVProVideo
|
||||
{
|
||||
public partial class MediaPlayer : MonoBehaviour, ISerializationCallbackReceiver
|
||||
{
|
||||
#region Upgrade from Version 1.x
|
||||
[SerializeField, HideInInspector]
|
||||
private string m_VideoPath;
|
||||
[SerializeField, HideInInspector]
|
||||
private FileLocation m_VideoLocation = FileLocation.RelativeToStreamingAssetsFolder;
|
||||
|
||||
private enum FileLocation
|
||||
{
|
||||
AbsolutePathOrURL,
|
||||
RelativeToProjectFolder,
|
||||
RelativeToStreamingAssetsFolder,
|
||||
RelativeToDataFolder,
|
||||
RelativeToPersistentDataFolder,
|
||||
}
|
||||
|
||||
/*
|
||||
[SerializeField, HideInInspector]
|
||||
private StereoPacking m_StereoPacking;
|
||||
[SerializeField, HideInInspector]
|
||||
private AlphaPacking m_AlphaPacking;
|
||||
*/
|
||||
|
||||
void ISerializationCallbackReceiver.OnBeforeSerialize()
|
||||
{
|
||||
/*
|
||||
m_StereoPacking = _fallbackMediaHints.stereoPacking;
|
||||
m_AlphaPacking = _fallbackMediaHints.alphaPacking;
|
||||
*/
|
||||
}
|
||||
|
||||
void ISerializationCallbackReceiver.OnAfterDeserialize()
|
||||
{
|
||||
if (!string.IsNullOrEmpty(m_VideoPath))
|
||||
{
|
||||
MediaPathType mediaPathType = MediaPathType.AbsolutePathOrURL;
|
||||
switch (m_VideoLocation)
|
||||
{
|
||||
default:
|
||||
case FileLocation.AbsolutePathOrURL:
|
||||
mediaPathType = MediaPathType.AbsolutePathOrURL;
|
||||
break;
|
||||
case FileLocation.RelativeToProjectFolder:
|
||||
mediaPathType = MediaPathType.RelativeToProjectFolder;
|
||||
break;
|
||||
case FileLocation.RelativeToStreamingAssetsFolder:
|
||||
mediaPathType = MediaPathType.RelativeToStreamingAssetsFolder;
|
||||
break;
|
||||
case FileLocation.RelativeToDataFolder:
|
||||
mediaPathType = MediaPathType.RelativeToDataFolder;
|
||||
break;
|
||||
case FileLocation.RelativeToPersistentDataFolder:
|
||||
mediaPathType = MediaPathType.RelativeToPersistentDataFolder;
|
||||
break;
|
||||
}
|
||||
_mediaPath = new MediaPath(m_VideoPath, mediaPathType);
|
||||
_mediaSource = MediaSource.Path;
|
||||
m_VideoPath = null;
|
||||
}
|
||||
|
||||
/*
|
||||
if (m_StereoPacking != _fallbackMediaHints.stereoPacking)
|
||||
{
|
||||
_fallbackMediaHints.stereoPacking = m_StereoPacking;
|
||||
}
|
||||
if (m_AlphaPacking != _fallbackMediaHints.alphaPacking)
|
||||
{
|
||||
_fallbackMediaHints.alphaPacking = m_AlphaPacking;
|
||||
}
|
||||
*/
|
||||
}
|
||||
#endregion // Upgrade from Version 1.x
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2e1421b74b1861b42ba7287d322c2f19
|
||||
timeCreated: 1614963169
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {fileID: 2800000, guid: bb83b41b53a59874692b83eab5873998, type: 3}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,17 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e9ea31f33222f4b418e4e051a8a5ed24
|
||||
timeCreated: 1588679963
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences:
|
||||
- m_AudioHeadTransform: {instanceID: 0}
|
||||
- m_AudioFocusTransform: {instanceID: 0}
|
||||
- _transitionShader: {fileID: 4800000, guid: 73f378cafe7b4a745907b70e76bb3259, type: 3}
|
||||
- _playerA: {instanceID: 0}
|
||||
- _playerB: {instanceID: 0}
|
||||
executionOrder: 0
|
||||
icon: {fileID: 2800000, guid: bb83b41b53a59874692b83eab5873998, type: 3}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,234 @@
|
||||
using UnityEngine;
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Copyright 2019-2023 RenderHeads Ltd. All rights reserved.
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
namespace RenderHeads.Media.AVProVideo
|
||||
{
|
||||
/// Renders the video texture to a RenderTexture - either one provided by the user (external) or to an internal one.
|
||||
/// The video frames can optionally be "resolved" to unpack packed alpha, display a single stereo eye, generate mip maps, and apply colorspace conversions
|
||||
[AddComponentMenu("AVPro Video/Resolve To RenderTexture", 330)]
|
||||
[HelpURL("https://www.renderheads.com/products/avpro-video/")]
|
||||
public class ResolveToRenderTexture : MonoBehaviour
|
||||
{
|
||||
[SerializeField] MediaPlayer _mediaPlayer = null;
|
||||
[SerializeField] VideoResolveOptions _options = VideoResolveOptions.Create();
|
||||
[SerializeField] VideoRender.ResolveFlags _resolveFlags = (VideoRender.ResolveFlags.ColorspaceSRGB | VideoRender.ResolveFlags.Mipmaps | VideoRender.ResolveFlags.PackedAlpha | VideoRender.ResolveFlags.StereoLeft);
|
||||
[SerializeField] RenderTexture _externalTexture = null;
|
||||
|
||||
private Material _materialResolve;
|
||||
private bool _isMaterialSetup;
|
||||
private bool _isMaterialDirty;
|
||||
private bool _isMaterialOES;
|
||||
private RenderTexture _internalTexture;
|
||||
private int _textureFrameCount = -1;
|
||||
|
||||
// Material used for blitting the texture as we need a shader to provide clamp to border colour style texture sampling
|
||||
private Material _materialBlit;
|
||||
private int _srcTexId;
|
||||
|
||||
public MediaPlayer MediaPlayer
|
||||
{
|
||||
get
|
||||
{
|
||||
return _mediaPlayer;
|
||||
}
|
||||
set
|
||||
{
|
||||
ChangeMediaPlayer(value);
|
||||
}
|
||||
}
|
||||
|
||||
public VideoResolveOptions VideoResolveOptions
|
||||
{
|
||||
get
|
||||
{
|
||||
return _options;
|
||||
}
|
||||
set
|
||||
{
|
||||
_options = value;
|
||||
_isMaterialDirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
public RenderTexture ExternalTexture
|
||||
{
|
||||
get
|
||||
{
|
||||
return _externalTexture;
|
||||
}
|
||||
set
|
||||
{
|
||||
_externalTexture = value;
|
||||
}
|
||||
}
|
||||
|
||||
public RenderTexture TargetTexture
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_externalTexture == null)
|
||||
return _internalTexture;
|
||||
return _externalTexture;
|
||||
}
|
||||
}
|
||||
|
||||
public void SetMaterialDirty()
|
||||
{
|
||||
_isMaterialDirty = true;
|
||||
}
|
||||
|
||||
private void ChangeMediaPlayer(MediaPlayer mediaPlayer)
|
||||
{
|
||||
if (_mediaPlayer != mediaPlayer)
|
||||
{
|
||||
_mediaPlayer = mediaPlayer;
|
||||
_textureFrameCount = -1;
|
||||
_isMaterialSetup = false;
|
||||
_isMaterialDirty = true;
|
||||
Resolve();
|
||||
}
|
||||
}
|
||||
|
||||
void Start()
|
||||
{
|
||||
_isMaterialOES = _mediaPlayer != null ? _mediaPlayer.IsUsingAndroidOESPath() : false;
|
||||
_materialResolve = VideoRender.CreateResolveMaterial(_isMaterialOES);
|
||||
VideoRender.SetupMaterialForMedia(_materialResolve, _mediaPlayer, -1);
|
||||
|
||||
_materialBlit = new Material(Shader.Find("AVProVideo/Internal/Blit"));
|
||||
_srcTexId = Shader.PropertyToID("_SrcTex");
|
||||
}
|
||||
|
||||
void LateUpdate()
|
||||
{
|
||||
Debug.Assert(_mediaPlayer != null);
|
||||
Resolve();
|
||||
}
|
||||
|
||||
public void Resolve()
|
||||
{
|
||||
ITextureProducer textureProducer = _mediaPlayer != null ? _mediaPlayer.TextureProducer : null;
|
||||
if (textureProducer == null)
|
||||
return;
|
||||
|
||||
if (textureProducer.GetTexture())
|
||||
{
|
||||
// Check for a swap between OES and none-OES
|
||||
bool playerIsOES = _mediaPlayer.IsUsingAndroidOESPath();
|
||||
if (_isMaterialOES != playerIsOES)
|
||||
{
|
||||
_isMaterialOES = playerIsOES;
|
||||
_materialResolve = VideoRender.CreateResolveMaterial(playerIsOES);
|
||||
}
|
||||
|
||||
if (!_isMaterialSetup)
|
||||
{
|
||||
VideoRender.SetupMaterialForMedia(_materialResolve, _mediaPlayer, -1);
|
||||
_isMaterialSetup = true;
|
||||
_isMaterialDirty = true;
|
||||
}
|
||||
|
||||
if (_isMaterialDirty)
|
||||
{
|
||||
VideoRender.SetupResolveMaterial(_materialResolve, _options);
|
||||
_isMaterialDirty = false;
|
||||
}
|
||||
|
||||
int textureFrameCount = textureProducer.GetTextureFrameCount();
|
||||
if (textureFrameCount != _textureFrameCount)
|
||||
{
|
||||
_internalTexture = VideoRender.ResolveVideoToRenderTexture(_materialResolve, _internalTexture, textureProducer, _resolveFlags);
|
||||
_textureFrameCount = textureFrameCount;
|
||||
|
||||
if (_internalTexture && _externalTexture)
|
||||
{
|
||||
float srcAspectRatio = (float)_internalTexture.width / (float)_internalTexture.height;
|
||||
float dstAspectRatio = (float)_externalTexture.width / (float)_externalTexture.height;
|
||||
|
||||
Vector2 offset = Vector2.zero;
|
||||
Vector2 scale = new Vector2(1.0f, 1.0f);
|
||||
|
||||
// No point in handling the aspect ratio if the textures dimension's are the same
|
||||
if (srcAspectRatio != dstAspectRatio)
|
||||
{
|
||||
switch (_options.aspectRatio)
|
||||
{
|
||||
case VideoResolveOptions.AspectRatio.NoScaling:
|
||||
scale.x = (float)_externalTexture.width / (float)_internalTexture.width;
|
||||
scale.y = (float)_externalTexture.height / (float)_internalTexture.height;
|
||||
offset.x = (1.0f - scale.x) * 0.5f;
|
||||
offset.y = (1.0f - scale.y) * 0.5f;
|
||||
break;
|
||||
|
||||
case VideoResolveOptions.AspectRatio.FitVertically:
|
||||
scale.x = (float)_internalTexture.height / (float)_internalTexture.width * dstAspectRatio;
|
||||
offset.x = (1.0f - scale.x) * 0.5f;
|
||||
break;
|
||||
|
||||
case VideoResolveOptions.AspectRatio.FitHorizontally:
|
||||
scale.y = (float)_externalTexture.height / (float)_externalTexture.width * srcAspectRatio;
|
||||
offset.y = (1.0f - scale.y) * 0.5f;
|
||||
break;
|
||||
|
||||
case VideoResolveOptions.AspectRatio.FitInside:
|
||||
{
|
||||
if (srcAspectRatio > dstAspectRatio)
|
||||
goto case VideoResolveOptions.AspectRatio.FitHorizontally;
|
||||
else if (srcAspectRatio < dstAspectRatio)
|
||||
goto case VideoResolveOptions.AspectRatio.FitVertically;
|
||||
} break;
|
||||
|
||||
case VideoResolveOptions.AspectRatio.FitOutside:
|
||||
{
|
||||
if (srcAspectRatio > dstAspectRatio)
|
||||
goto case VideoResolveOptions.AspectRatio.FitVertically;
|
||||
else if (srcAspectRatio < dstAspectRatio)
|
||||
goto case VideoResolveOptions.AspectRatio.FitHorizontally;
|
||||
} break;
|
||||
|
||||
case VideoResolveOptions.AspectRatio.Stretch:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE: This blit can be removed once we can ResolveVideoToRenderTexture is made not to recreate textures
|
||||
// NOTE: This blit probably doesn't do correct linear/srgb conversion if the colorspace settings differ, may have to use GL.sRGBWrite
|
||||
// NOTE: Cannot use _MainTex as Graphics.Blit replaces the texture offset and scale when using a material
|
||||
_materialBlit.SetTexture(_srcTexId, _internalTexture);
|
||||
_materialBlit.SetTextureOffset(_srcTexId, offset);
|
||||
_materialBlit.SetTextureScale(_srcTexId, scale);
|
||||
Graphics.Blit(null, _externalTexture, _materialBlit, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void OnDisable()
|
||||
{
|
||||
if (_internalTexture)
|
||||
{
|
||||
RenderTexture.ReleaseTemporary(_internalTexture); _internalTexture = null;
|
||||
}
|
||||
}
|
||||
|
||||
void OnDestroy()
|
||||
{
|
||||
if (_materialResolve)
|
||||
{
|
||||
Destroy(_materialResolve); _materialResolve = null;
|
||||
}
|
||||
}
|
||||
#if false
|
||||
void OnGUI()
|
||||
{
|
||||
if (TargetTexture)
|
||||
{
|
||||
GUI.DrawTexture(new Rect(0f, 0f, Screen.width * 0.8f, Screen.height * 0.8f), TargetTexture, ScaleMode.ScaleToFit, true);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 448e5e4039505584c852da1a7cc5c361
|
||||
timeCreated: 1654790987
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {fileID: 2800000, guid: bb83b41b53a59874692b83eab5873998, type: 3}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,178 @@
|
||||
#if UNITY_ANDROID
|
||||
#if USING_URP
|
||||
#define ANDROID_URP
|
||||
#endif
|
||||
#endif
|
||||
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Rendering;
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Copyright 2015-2022 RenderHeads Ltd. All rights reserved.
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
namespace RenderHeads.Media.AVProVideo
|
||||
{
|
||||
/// <summary>
|
||||
/// This script is needed to send the camera position to the stereo shader so that
|
||||
/// it can determine which eye it is rendering. This is only needed for multi-pass
|
||||
/// rendering, as single pass has a built-in shader variable
|
||||
/// </summary>
|
||||
[AddComponentMenu("AVPro Video/Update Multi-Pass Stereo", 320)]
|
||||
[HelpURL("https://www.renderheads.com/products/avpro-video/")]
|
||||
public class UpdateMultiPassStereo : MonoBehaviour
|
||||
{
|
||||
[Header("Stereo camera")]
|
||||
[SerializeField] Camera _camera = null;
|
||||
|
||||
public Camera Camera
|
||||
{
|
||||
get { return _camera; }
|
||||
set { _camera = value; }
|
||||
}
|
||||
|
||||
private static readonly LazyShaderProperty PropWorldCameraPosition = new LazyShaderProperty("_WorldCameraPosition");
|
||||
private static readonly LazyShaderProperty PropWorldCameraRight = new LazyShaderProperty("_WorldCameraRight");
|
||||
|
||||
// State
|
||||
|
||||
private Camera _foundCamera;
|
||||
|
||||
void Awake()
|
||||
{
|
||||
if (_camera == null)
|
||||
{
|
||||
Debug.LogWarning("[AVProVideo] No camera set for UpdateMultiPassStereo component. If you are rendering in multi-pass stereo then it is recommended to set this.");
|
||||
}
|
||||
}
|
||||
|
||||
void Start()
|
||||
{
|
||||
LogXRDeviceDetails();
|
||||
|
||||
#if ANDROID_URP
|
||||
if( GetComponent<Camera>() == null )
|
||||
{
|
||||
throw new MissingComponentException("[AVProVideo] When using URP the UpdateMultiPassStereo component must be on the Camera gameobject. This component is not required on all VR devices, but if it is then stereo eye rendering may not work correctly.");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
private void LogXRDeviceDetails()
|
||||
{
|
||||
#if UNITY_2019_1_OR_NEWER && !UNITY_TVOS
|
||||
string logOutput = "[AVProVideo] XR Device details: UnityEngine.XR.XRSettings.loadedDeviceName = " + UnityEngine.XR.XRSettings.loadedDeviceName + " | supportedDevices = ";
|
||||
|
||||
string[] aSupportedDevices = UnityEngine.XR.XRSettings.supportedDevices;
|
||||
int supportedDeviceCount = aSupportedDevices.Length;
|
||||
for (int i = 0; i < supportedDeviceCount; i++)
|
||||
{
|
||||
logOutput += aSupportedDevices[i];
|
||||
if( i < (supportedDeviceCount - 1 ))
|
||||
{
|
||||
logOutput += ", ";
|
||||
}
|
||||
}
|
||||
|
||||
List<UnityEngine.XR.InputDevice> inputDevices = new List<UnityEngine.XR.InputDevice>();
|
||||
UnityEngine.XR.InputDevices.GetDevices(inputDevices);
|
||||
int deviceCount = inputDevices.Count;
|
||||
if (deviceCount > 0)
|
||||
{
|
||||
logOutput += " | XR Devices = ";
|
||||
|
||||
for (int i = 0; i < deviceCount; i++)
|
||||
{
|
||||
logOutput += inputDevices[i].name;
|
||||
if( i < (deviceCount -1 ))
|
||||
{
|
||||
logOutput += ", ";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
UnityEngine.XR.InputDevice headDevice = UnityEngine.XR.InputDevices.GetDeviceAtXRNode(UnityEngine.XR.XRNode.Head);
|
||||
if( headDevice != null )
|
||||
{
|
||||
logOutput += " | headDevice name = " + headDevice.name + ", manufacturer = " + headDevice.manufacturer;
|
||||
}
|
||||
|
||||
Debug.Log(logOutput);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
#if ANDROID_URP
|
||||
void OnEnable()
|
||||
{
|
||||
RenderPipelineManager.beginCameraRendering += RenderPipelineManager_beginCameraRendering;
|
||||
}
|
||||
void OnDisable()
|
||||
{
|
||||
RenderPipelineManager.beginCameraRendering -= RenderPipelineManager_beginCameraRendering;
|
||||
}
|
||||
#endif
|
||||
|
||||
private static bool IsMultiPassVrEnabled()
|
||||
{
|
||||
#if UNITY_TVOS
|
||||
return false;
|
||||
#else
|
||||
#if UNITY_2017_2_OR_NEWER
|
||||
if (!UnityEngine.XR.XRSettings.enabled) return false;
|
||||
#endif
|
||||
#if UNITY_2018_3_OR_NEWER
|
||||
if (UnityEngine.XR.XRSettings.stereoRenderingMode != UnityEngine.XR.XRSettings.StereoRenderingMode.MultiPass) return false;
|
||||
#endif
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
// We do a LateUpdate() to allow for any changes in the camera position that may have happened in Update()
|
||||
#if ANDROID_URP
|
||||
// Android URP
|
||||
private void RenderPipelineManager_beginCameraRendering(ScriptableRenderContext context, Camera camera)
|
||||
#else
|
||||
// Normal render pipeline
|
||||
private void LateUpdate()
|
||||
#endif
|
||||
{
|
||||
if (!IsMultiPassVrEnabled())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_camera != null && _foundCamera != _camera)
|
||||
{
|
||||
_foundCamera = _camera;
|
||||
}
|
||||
if (_foundCamera == null)
|
||||
{
|
||||
_foundCamera = Camera.main;
|
||||
if (_foundCamera == null)
|
||||
{
|
||||
Debug.LogWarning("[AVProVideo] Cannot find main camera for UpdateMultiPassStereo, this can lead to eyes flickering");
|
||||
if (Camera.allCameras.Length > 0)
|
||||
{
|
||||
_foundCamera = Camera.allCameras[0];
|
||||
Debug.LogWarning("[AVProVideo] UpdateMultiPassStereo using camera " + _foundCamera.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (_foundCamera != null)
|
||||
{
|
||||
#if ANDROID_URP
|
||||
Shader.EnableKeyword("USING_URP");
|
||||
#else
|
||||
Shader.DisableKeyword("USING_URP");
|
||||
#endif
|
||||
|
||||
Shader.SetGlobalVector(PropWorldCameraPosition.Id, _foundCamera.transform.position);
|
||||
Shader.SetGlobalVector(PropWorldCameraRight.Id, _foundCamera.transform.right);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8b2366b5575fcba46a0f97038fb6c5fb
|
||||
timeCreated: 1611065944
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {fileID: 2800000, guid: bb83b41b53a59874692b83eab5873998, type: 3}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1bb8f28c4529a1343b4430d732bb5f2a
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
userData:
|
||||
@@ -0,0 +1,124 @@
|
||||
#if UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX || UNITY_IOS || UNITY_TVOS
|
||||
#define UNITY_PLATFORM_SUPPORTS_YPCBCR
|
||||
#endif
|
||||
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace RenderHeads.Media.AVProVideo
|
||||
{
|
||||
/// <summary>
|
||||
/// Base class to apply texture from MediaPlayer
|
||||
/// </summary>
|
||||
public abstract class ApplyToBase : MonoBehaviour
|
||||
{
|
||||
[Header("Media Source")]
|
||||
[Space(8f)]
|
||||
|
||||
[SerializeField] protected MediaPlayer _media = null;
|
||||
|
||||
public MediaPlayer Player
|
||||
{
|
||||
get { return _media; }
|
||||
set { ChangeMediaPlayer(value); }
|
||||
}
|
||||
|
||||
[Space(8f)]
|
||||
[Header("Display")]
|
||||
|
||||
[SerializeField] bool _automaticStereoPacking = true;
|
||||
public bool AutomaticStereoPacking
|
||||
{
|
||||
get { return _automaticStereoPacking; }
|
||||
set { if (_automaticStereoPacking != value) { _automaticStereoPacking = value; _isDirty = true; } }
|
||||
}
|
||||
|
||||
[SerializeField] StereoPacking _overrideStereoPacking = StereoPacking.None;
|
||||
public StereoPacking OverrideStereoPacking
|
||||
{
|
||||
get { return _overrideStereoPacking; }
|
||||
set { if (_overrideStereoPacking != value) { _overrideStereoPacking = value; _isDirty = true; } }
|
||||
}
|
||||
|
||||
[SerializeField] bool _stereoRedGreenTint = false;
|
||||
public bool StereoRedGreenTint { get { return _stereoRedGreenTint; } set { if (_stereoRedGreenTint != value) { _stereoRedGreenTint = value; _isDirty = true; } } }
|
||||
|
||||
protected bool _isDirty = false;
|
||||
|
||||
void Awake()
|
||||
{
|
||||
ChangeMediaPlayer(_media, force:true);
|
||||
}
|
||||
|
||||
private void ChangeMediaPlayer(MediaPlayer player, bool force = false)
|
||||
{
|
||||
if (_media != player || force)
|
||||
{
|
||||
if (_media != null)
|
||||
{
|
||||
_media.Events.RemoveListener(OnMediaPlayerEvent);
|
||||
}
|
||||
_media = player;
|
||||
if (_media != null)
|
||||
{
|
||||
_media.Events.AddListener(OnMediaPlayerEvent);
|
||||
}
|
||||
_isDirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Callback function to handle events
|
||||
private void OnMediaPlayerEvent(MediaPlayer mp, MediaPlayerEvent.EventType et, ErrorCode errorCode)
|
||||
{
|
||||
switch (et)
|
||||
{
|
||||
case MediaPlayerEvent.EventType.FirstFrameReady:
|
||||
case MediaPlayerEvent.EventType.PropertiesChanged:
|
||||
ForceUpdate();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public void ForceUpdate()
|
||||
{
|
||||
_isDirty = true;
|
||||
if (this.isActiveAndEnabled)
|
||||
{
|
||||
Apply();
|
||||
}
|
||||
}
|
||||
|
||||
private void Start()
|
||||
{
|
||||
SaveProperties();
|
||||
Apply();
|
||||
}
|
||||
|
||||
protected virtual void OnEnable()
|
||||
{
|
||||
SaveProperties();
|
||||
ForceUpdate();
|
||||
}
|
||||
|
||||
protected virtual void OnDisable()
|
||||
{
|
||||
RestoreProperties();
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
ChangeMediaPlayer(null);
|
||||
}
|
||||
|
||||
protected virtual void SaveProperties()
|
||||
{
|
||||
}
|
||||
|
||||
protected virtual void RestoreProperties()
|
||||
{
|
||||
}
|
||||
|
||||
public abstract void Apply();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 33aa9dfec55e1f6438ee868d02dcabe2
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,215 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using System;
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Copyright 2015-2021 RenderHeads Ltd. All rights reserved.
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
namespace RenderHeads.Media.AVProVideo
|
||||
{
|
||||
/// <summary>
|
||||
/// A singleton to handle multiple instances of the AudioOutput component
|
||||
/// </summary>
|
||||
public class AudioOutputManager
|
||||
{
|
||||
private static AudioOutputManager _instance = null;
|
||||
|
||||
public static AudioOutputManager Instance
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_instance == null)
|
||||
{
|
||||
_instance = new AudioOutputManager();
|
||||
}
|
||||
return _instance;
|
||||
}
|
||||
}
|
||||
|
||||
protected class PlayerInstance
|
||||
{
|
||||
public HashSet<AudioOutput> outputs;
|
||||
public float[] pcmData;
|
||||
public bool isPcmDataReady;
|
||||
}
|
||||
|
||||
private Dictionary<MediaPlayer, PlayerInstance> _instances;
|
||||
|
||||
private AudioOutputManager()
|
||||
{
|
||||
_instances = new Dictionary<MediaPlayer, PlayerInstance>();
|
||||
}
|
||||
|
||||
public void RequestAudio(AudioOutput outputComponent, MediaPlayer mediaPlayer, float[] audioData, int audioChannelCount, int channelMask, AudioOutput.AudioOutputMode audioOutputMode, bool supportPositionalAudio)
|
||||
{
|
||||
if (mediaPlayer == null || mediaPlayer.Control == null)
|
||||
{
|
||||
if (supportPositionalAudio)
|
||||
{
|
||||
ZeroAudio(audioData, 0);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
int channels = mediaPlayer.Control.GetAudioChannelCount();
|
||||
if (channels <= 0)
|
||||
{
|
||||
if (supportPositionalAudio)
|
||||
{
|
||||
ZeroAudio(audioData, 0);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// total samples requested should be multiple of channels
|
||||
Debug.Assert(audioData.Length % audioChannelCount == 0);
|
||||
|
||||
// Find or create an instance
|
||||
PlayerInstance instance = null;
|
||||
if (!_instances.TryGetValue(mediaPlayer, out instance))
|
||||
{
|
||||
instance = _instances[mediaPlayer] = new PlayerInstance()
|
||||
{
|
||||
outputs = new HashSet<AudioOutput>(),
|
||||
pcmData = null
|
||||
};
|
||||
}
|
||||
|
||||
// requests data if it hasn't been requested yet for the current cycle
|
||||
if (instance.outputs.Count == 0 || instance.outputs.Contains(outputComponent) || instance.pcmData == null)
|
||||
{
|
||||
instance.outputs.Clear();
|
||||
|
||||
int actualDataRequired = (audioData.Length * channels) / audioChannelCount;
|
||||
if (instance.pcmData == null || actualDataRequired != instance.pcmData.Length)
|
||||
{
|
||||
instance.pcmData = new float[actualDataRequired];
|
||||
}
|
||||
|
||||
instance.isPcmDataReady = GrabAudio(mediaPlayer, instance.pcmData, channels);
|
||||
|
||||
instance.outputs.Add(outputComponent);
|
||||
}
|
||||
|
||||
if (instance.isPcmDataReady)
|
||||
{
|
||||
// calculate how many samples and what channels are needed and then copy over the data
|
||||
int samples = Math.Min(audioData.Length / audioChannelCount, instance.pcmData.Length / channels);
|
||||
int storedPos = 0;
|
||||
int requestedPos = 0;
|
||||
|
||||
// multiple mode, copies over audio from desired channels into the same channels on the audiosource
|
||||
if (audioOutputMode == AudioOutput.AudioOutputMode.MultipleChannels)
|
||||
{
|
||||
int lesserChannels = Math.Min(channels, audioChannelCount);
|
||||
|
||||
if (!supportPositionalAudio)
|
||||
{
|
||||
for (int i = 0; i < samples; ++i)
|
||||
{
|
||||
for (int j = 0; j < lesserChannels; ++j)
|
||||
{
|
||||
if ((1 << j & channelMask) > 0)
|
||||
{
|
||||
audioData[requestedPos + j] = instance.pcmData[storedPos + j];
|
||||
}
|
||||
}
|
||||
|
||||
storedPos += channels;
|
||||
requestedPos += audioChannelCount;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = 0; i < samples; ++i)
|
||||
{
|
||||
for (int j = 0; j < lesserChannels; ++j)
|
||||
{
|
||||
if ((1 << j & channelMask) > 0)
|
||||
{
|
||||
audioData[requestedPos + j] *= instance.pcmData[storedPos + j];
|
||||
}
|
||||
}
|
||||
|
||||
storedPos += channels;
|
||||
requestedPos += audioChannelCount;
|
||||
}
|
||||
}
|
||||
}
|
||||
//Mono mode, copies over single channel to all output channels
|
||||
else if (audioOutputMode == AudioOutput.AudioOutputMode.OneToAllChannels)
|
||||
{
|
||||
int desiredChannel = 0;
|
||||
|
||||
for (int i = 0; i < 8; ++i)
|
||||
{
|
||||
if ((channelMask & (1 << i)) > 0)
|
||||
{
|
||||
desiredChannel = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (desiredChannel < channels)
|
||||
{
|
||||
if (!supportPositionalAudio)
|
||||
{
|
||||
for (int i = 0; i < samples; ++i)
|
||||
{
|
||||
for (int j = 0; j < audioChannelCount; ++j)
|
||||
{
|
||||
audioData[requestedPos + j] = instance.pcmData[storedPos + desiredChannel];
|
||||
}
|
||||
|
||||
storedPos += channels;
|
||||
requestedPos += audioChannelCount;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = 0; i < samples; ++i)
|
||||
{
|
||||
for (int j = 0; j < audioChannelCount; ++j)
|
||||
{
|
||||
audioData[requestedPos + j] *= instance.pcmData[storedPos + desiredChannel];
|
||||
}
|
||||
|
||||
storedPos += channels;
|
||||
requestedPos += audioChannelCount;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If there is left over audio
|
||||
if (supportPositionalAudio && requestedPos != audioData.Length)
|
||||
{
|
||||
// Zero the remaining audio data otherwise there are pops
|
||||
ZeroAudio(audioData, requestedPos);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (supportPositionalAudio)
|
||||
{
|
||||
// Zero the remaining audio data otherwise there are pops
|
||||
ZeroAudio(audioData, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ZeroAudio(float[] audioData, int startPosition)
|
||||
{
|
||||
for (int i = startPosition; i < audioData.Length; i++)
|
||||
{
|
||||
audioData[i] = 0f;
|
||||
}
|
||||
}
|
||||
|
||||
private bool GrabAudio(MediaPlayer player, float[] audioData, int channelCount)
|
||||
{
|
||||
return (0 != player.Control.GrabAudio(audioData, audioData.Length, channelCount));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 714026a371bd2d64c86edb3dab5607d9
|
||||
timeCreated: 1495699104
|
||||
licenseType: Free
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,654 @@
|
||||
#if UNITY_EDITOR || UNITY_STANDALONE_OSX || UNITY_STANDALONE_WIN || UNITY_IOS || UNITY_ANDROID
|
||||
#define UNITY_PLATFORM_SUPPORTS_LINEAR
|
||||
#endif
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Copyright 2015-2021 RenderHeads Ltd. All rights reserved.
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
namespace RenderHeads.Media.AVProVideo
|
||||
{
|
||||
/// <summary>
|
||||
/// Base class for all platform specific MediaPlayers
|
||||
/// </summary>
|
||||
public abstract partial class BaseMediaPlayer : IMediaPlayer, IMediaControl, IMediaInfo, IMediaCache, ITextureProducer, IMediaSubtitles, IVideoTracks, IAudioTracks, ITextTracks, IBufferedDisplay, System.IDisposable
|
||||
{
|
||||
public BaseMediaPlayer()
|
||||
{
|
||||
InitTracks();
|
||||
}
|
||||
|
||||
public abstract string GetVersion();
|
||||
public abstract string GetExpectedVersion();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public abstract bool OpenMedia(string path, long offset, string customHttpHeaders, MediaHints mediaHints, int forceFileFormat = 0, bool startWithHighestBitrate = false);
|
||||
|
||||
#if NETFX_CORE
|
||||
/// <inheritdoc/>
|
||||
public virtual bool OpenMedia(Windows.Storage.Streams.IRandomAccessStream ras, string path, long offset, string customHttpHeaders) { return false; }
|
||||
#endif
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual bool OpenMediaFromBuffer(byte[] buffer) { return false; }
|
||||
/// <inheritdoc/>
|
||||
public virtual bool StartOpenMediaFromBuffer(ulong length) { return false; }
|
||||
/// <inheritdoc/>
|
||||
public virtual bool AddChunkToMediaBuffer(byte[] chunk, ulong offset, ulong length) { return false; }
|
||||
/// <inheritdoc/>
|
||||
public virtual bool EndOpenMediaFromBuffer() { return false; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual void CloseMedia()
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
_displayRateLastRealTime = 0f;
|
||||
#endif
|
||||
_displayRateTimer = 0f;
|
||||
_displayRateLastFrameCount = 0;
|
||||
_displayRate = 0f;
|
||||
|
||||
_stallDetectionTimer = 0f;
|
||||
_stallDetectionFrame = 0;
|
||||
_lastError = ErrorCode.None;
|
||||
|
||||
_textTracks.Clear();
|
||||
_audioTracks.Clear();
|
||||
_videoTracks.Clear();
|
||||
_currentTextCue = null;
|
||||
_mediaHints = new MediaHints();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public abstract void SetLooping(bool looping);
|
||||
/// <inheritdoc/>
|
||||
public abstract bool IsLooping();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public abstract bool HasMetaData();
|
||||
/// <inheritdoc/>
|
||||
public abstract bool CanPlay();
|
||||
/// <inheritdoc/>
|
||||
public abstract void Play();
|
||||
/// <inheritdoc/>
|
||||
public abstract void Pause();
|
||||
/// <inheritdoc/>
|
||||
public abstract void Stop();
|
||||
/// <inheritdoc/>
|
||||
public virtual void Rewind() { SeekFast(0.0); }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public abstract void Seek(double time);
|
||||
/// <inheritdoc/>
|
||||
public abstract void SeekFast(double time);
|
||||
/// <inheritdoc/>
|
||||
public virtual void SeekWithTolerance(double time, double timeDeltaBefore, double timeDeltaAfter) { Seek(time); }
|
||||
/// <inheritdoc/>
|
||||
public abstract double GetCurrentTime();
|
||||
/// <inheritdoc/>
|
||||
public virtual DateTime GetProgramDateTime() { return DateTime.MinValue; }
|
||||
/// <inheritdoc/>
|
||||
public abstract float GetPlaybackRate();
|
||||
/// <inheritdoc/>
|
||||
public abstract void SetPlaybackRate(float rate);
|
||||
|
||||
// Basic Properties
|
||||
/// <inheritdoc/>
|
||||
public abstract double GetDuration();
|
||||
/// <inheritdoc/>
|
||||
public abstract int GetVideoWidth();
|
||||
/// <inheritdoc/>
|
||||
public abstract int GetVideoHeight();
|
||||
/// <inheritdoc/>
|
||||
public abstract float GetVideoFrameRate();
|
||||
/// <inheritdoc/>
|
||||
public virtual float GetVideoDisplayRate() { return _displayRate; }
|
||||
/// <inheritdoc/>
|
||||
public abstract bool HasAudio();
|
||||
/// <inheritdoc/>
|
||||
public abstract bool HasVideo();
|
||||
/// <inheritdoc/>
|
||||
public bool IsVideoStereo() { return GetTextureStereoPacking() != StereoPacking.None; }
|
||||
|
||||
// Basic State
|
||||
/// <inheritdoc/>
|
||||
public abstract bool IsSeeking();
|
||||
/// <inheritdoc/>
|
||||
public abstract bool IsPlaying();
|
||||
/// <inheritdoc/>
|
||||
public abstract bool IsPaused();
|
||||
/// <inheritdoc/>
|
||||
public abstract bool IsFinished();
|
||||
/// <inheritdoc/>
|
||||
public abstract bool IsBuffering();
|
||||
/// <inheritdoc/>
|
||||
public virtual bool WaitForNextFrame(Camera dummyCamera, int previousFrameCount) { return false; }
|
||||
|
||||
// Textures
|
||||
/// <inheritdoc/>
|
||||
public virtual int GetTextureCount() { return 1; }
|
||||
/// <inheritdoc/>
|
||||
public abstract Texture GetTexture(int index = 0);
|
||||
/// <inheritdoc/>
|
||||
public abstract int GetTextureFrameCount();
|
||||
/// <inheritdoc/>
|
||||
public virtual bool SupportsTextureFrameCount() { return true; }
|
||||
/// <inheritdoc/>
|
||||
public virtual long GetTextureTimeStamp() { return long.MinValue; }
|
||||
/// <inheritdoc/>
|
||||
public abstract bool RequiresVerticalFlip();
|
||||
/// <inheritdoc/>
|
||||
public virtual float[] GetTextureTransform() { return new float[] { 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f }; }
|
||||
/// <inheritdoc/>
|
||||
public virtual float GetTexturePixelAspectRatio() { return 1f; }
|
||||
/// <inheritdoc/>
|
||||
public virtual Matrix4x4 GetYpCbCrTransform() { return Matrix4x4.identity; }
|
||||
|
||||
public StereoPacking GetTextureStereoPacking()
|
||||
{
|
||||
StereoPacking result = InternalGetTextureStereoPacking();
|
||||
if (result == StereoPacking.Unknown)
|
||||
{
|
||||
// If stereo is unknown, fall back to media hints or no packing
|
||||
result = _mediaHints.stereoPacking;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
internal abstract StereoPacking InternalGetTextureStereoPacking();
|
||||
|
||||
public virtual TransparencyMode GetTextureTransparency()
|
||||
{
|
||||
return _mediaHints.transparency;
|
||||
}
|
||||
|
||||
public AlphaPacking GetTextureAlphaPacking()
|
||||
{
|
||||
if (GetTextureTransparency() == TransparencyMode.Transparent)
|
||||
{
|
||||
return _mediaHints.alphaPacking;
|
||||
}
|
||||
return AlphaPacking.None;
|
||||
}
|
||||
|
||||
// Audio General
|
||||
/// <inheritdoc/>
|
||||
public abstract void MuteAudio(bool bMuted);
|
||||
/// <inheritdoc/>
|
||||
public abstract bool IsMuted();
|
||||
/// <inheritdoc/>
|
||||
public abstract void SetVolume(float volume);
|
||||
/// <inheritdoc/>
|
||||
public virtual void SetBalance(float balance) { }
|
||||
/// <inheritdoc/>
|
||||
public abstract float GetVolume();
|
||||
/// <inheritdoc/>
|
||||
public virtual float GetBalance() { return 0f; }
|
||||
|
||||
// Audio Grabbing
|
||||
/// <inheritdoc/>
|
||||
public virtual int GetAudioChannelCount() { return -1; }
|
||||
/// <inheritdoc/>
|
||||
public virtual AudioChannelMaskFlags GetAudioChannelMask() { return 0; }
|
||||
/// <inheritdoc/>
|
||||
public virtual int GrabAudio(float[] audioData, int audioDataFloatCount, int channelCount) { return 0; }
|
||||
/// <inheritdoc/>
|
||||
public virtual int GetAudioBufferedSampleCount() { return 0; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual void AudioConfigurationChanged(bool deviceChanged) { }
|
||||
|
||||
// 360 Audio
|
||||
/// <inheritdoc/>
|
||||
public virtual void SetAudioHeadRotation(Quaternion q) { }
|
||||
/// <inheritdoc/>
|
||||
public virtual void ResetAudioHeadRotation() { }
|
||||
/// <inheritdoc/>
|
||||
public virtual void SetAudioChannelMode(Audio360ChannelMode channelMode) { }
|
||||
/// <inheritdoc/>
|
||||
public virtual void SetAudioFocusEnabled(bool enabled) { }
|
||||
/// <inheritdoc/>
|
||||
public virtual void SetAudioFocusProperties(float offFocusLevel, float widthDegrees) { }
|
||||
/// <inheritdoc/>
|
||||
public virtual void SetAudioFocusRotation(Quaternion q) { }
|
||||
/// <inheritdoc/>
|
||||
public virtual void ResetAudioFocus() { }
|
||||
|
||||
// Streaming
|
||||
/// <inheritdoc/>
|
||||
public virtual long GetEstimatedTotalBandwidthUsed() { return -1; }
|
||||
/// <inheritdoc/>
|
||||
public virtual void SetPlayWithoutBuffering(bool playWithoutBuffering) { }
|
||||
|
||||
// Caching
|
||||
/// <inheritdoc/>
|
||||
public virtual bool IsMediaCachingSupported() { return false; }
|
||||
/// <inheritdoc/>
|
||||
public virtual void AddMediaToCache(string url, string headers, MediaCachingOptions options) { }
|
||||
/// <inheritdoc/>
|
||||
public virtual void CancelDownloadOfMediaToCache(string url) { }
|
||||
/// <inheritdoc/>
|
||||
public virtual void PauseDownloadOfMediaToCache(string url) { }
|
||||
/// <inheritdoc/>
|
||||
public virtual void ResumeDownloadOfMediaToCache(string url) { }
|
||||
/// <inheritdoc/>
|
||||
public virtual void RemoveMediaFromCache(string url) { }
|
||||
/// <inheritdoc/>
|
||||
public virtual CachedMediaStatus GetCachedMediaStatus(string url, ref float progress) { return CachedMediaStatus.NotCached; }
|
||||
// /// <inheritdoc/>
|
||||
// public virtual bool IsMediaCached() { return false; }
|
||||
|
||||
// External playback
|
||||
/// <inheritdoc/>
|
||||
public virtual bool IsExternalPlaybackSupported() { return false; }
|
||||
/// <inheritdoc/>
|
||||
public virtual bool IsExternalPlaybackActive() { return false; }
|
||||
/// <inheritdoc/>
|
||||
public virtual void SetAllowsExternalPlayback(bool enable) { }
|
||||
/// <inheritdoc/>
|
||||
public virtual void SetExternalPlaybackVideoGravity(ExternalPlaybackVideoGravity gravity) { }
|
||||
|
||||
// Authentication
|
||||
//public virtual void SetKeyServerURL(string url) { }
|
||||
/// <inheritdoc/>
|
||||
public virtual void SetKeyServerAuthToken(string token) { }
|
||||
/// <inheritdoc/>
|
||||
public virtual void SetOverrideDecryptionKey(byte[] key) { }
|
||||
|
||||
// General
|
||||
/// <inheritdoc/>
|
||||
public abstract void Update();
|
||||
/// <inheritdoc/>
|
||||
public /*abstract*/virtual void BeginRender() { }
|
||||
/// <inheritdoc/>
|
||||
public abstract void Render();
|
||||
/// <inheritdoc/>
|
||||
public abstract void Dispose();
|
||||
|
||||
// Internal method
|
||||
public virtual bool GetDecoderPerformance(ref int activeDecodeThreadCount, ref int decodedFrameCount, ref int droppedFrameCount) { return false; }
|
||||
|
||||
#if false
|
||||
public void Update()
|
||||
{
|
||||
Native.Update(_instance);
|
||||
if (UpdateTracks())
|
||||
{
|
||||
|
||||
}
|
||||
if (UpdateTextCue())
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
public virtual void EndUpdate() { }
|
||||
|
||||
public virtual IntPtr GetNativePlayerHandle() { return IntPtr.Zero; }
|
||||
|
||||
public ErrorCode GetLastError()
|
||||
{
|
||||
ErrorCode errorCode = _lastError;
|
||||
_lastError = ErrorCode.None;
|
||||
return errorCode;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual long GetLastExtendedErrorCode()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
public string GetPlayerDescription()
|
||||
{
|
||||
return _playerDescription;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual bool PlayerSupportsLinearColorSpace()
|
||||
{
|
||||
#if UNITY_PLATFORM_SUPPORTS_LINEAR
|
||||
return true;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
protected string _playerDescription = string.Empty;
|
||||
protected ErrorCode _lastError = ErrorCode.None;
|
||||
protected FilterMode _defaultTextureFilterMode = FilterMode.Bilinear;
|
||||
protected TextureWrapMode _defaultTextureWrapMode = TextureWrapMode.Clamp;
|
||||
protected int _defaultTextureAnisoLevel = 1;
|
||||
protected MediaHints _mediaHints;
|
||||
protected TimeRanges _seekableTimes = new TimeRanges();
|
||||
protected TimeRanges _bufferedTimes = new TimeRanges();
|
||||
|
||||
public TimeRanges GetSeekableTimes() { return _seekableTimes; }
|
||||
public TimeRanges GetBufferedTimes() { return _bufferedTimes; }
|
||||
|
||||
public void GetTextureProperties(out FilterMode filterMode, out TextureWrapMode wrapMode, out int anisoLevel)
|
||||
{
|
||||
filterMode = _defaultTextureFilterMode;
|
||||
wrapMode = _defaultTextureWrapMode;
|
||||
anisoLevel = _defaultTextureAnisoLevel;
|
||||
}
|
||||
|
||||
public void SetTextureProperties(FilterMode filterMode = FilterMode.Bilinear, TextureWrapMode wrapMode = TextureWrapMode.Clamp, int anisoLevel = 0)
|
||||
{
|
||||
_defaultTextureFilterMode = filterMode;
|
||||
_defaultTextureWrapMode = wrapMode;
|
||||
_defaultTextureAnisoLevel = anisoLevel;
|
||||
for (int i = 0; i < GetTextureCount(); ++i)
|
||||
{
|
||||
ApplyTextureProperties(GetTexture(i));
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void ApplyTextureProperties(Texture texture)
|
||||
{
|
||||
if (texture != null)
|
||||
{
|
||||
texture.filterMode = _defaultTextureFilterMode;
|
||||
texture.wrapMode = _defaultTextureWrapMode;
|
||||
texture.anisoLevel = _defaultTextureAnisoLevel;
|
||||
}
|
||||
}
|
||||
|
||||
#region Video Display Rate
|
||||
#if UNITY_EDITOR
|
||||
private float _displayRateLastRealTime = 0f;
|
||||
#endif
|
||||
private float _displayRateTimer;
|
||||
private int _displayRateLastFrameCount;
|
||||
private float _displayRate = 1f;
|
||||
|
||||
protected void UpdateDisplayFrameRate()
|
||||
{
|
||||
const float IntervalSeconds = 0.5f;
|
||||
if (_displayRateTimer >= IntervalSeconds)
|
||||
{
|
||||
int frameCount = GetTextureFrameCount();
|
||||
int frameDelta = (frameCount - _displayRateLastFrameCount);
|
||||
_displayRate = (float)frameDelta / _displayRateTimer;
|
||||
_displayRateTimer -= IntervalSeconds;
|
||||
if (_displayRateTimer >= IntervalSeconds) _displayRateTimer -= IntervalSeconds;
|
||||
if (_displayRateTimer >= IntervalSeconds) _displayRateTimer = 0f;
|
||||
_displayRateLastFrameCount = frameCount;
|
||||
}
|
||||
|
||||
float deltaTime = Time.deltaTime;
|
||||
#if UNITY_EDITOR
|
||||
if (!Application.isPlaying)
|
||||
{
|
||||
// When not playing Time.deltaTime isn't valid so we have to derive it
|
||||
deltaTime = (Time.realtimeSinceStartup - _displayRateLastRealTime);
|
||||
_displayRateLastRealTime = Time.realtimeSinceStartup;
|
||||
}
|
||||
#endif
|
||||
_displayRateTimer += deltaTime;
|
||||
}
|
||||
#endregion // Video Display Rate
|
||||
|
||||
#region Stall Detection
|
||||
protected bool IsExpectingNewVideoFrame()
|
||||
{
|
||||
if (HasVideo())
|
||||
{
|
||||
// If we're playing then we expect a new frame
|
||||
if (!IsFinished() && (!IsPaused() && IsPlaying() && GetPlaybackRate() != 0.0f))
|
||||
{
|
||||
// Check that the video is not a single frame and therefore there is no other frame to display
|
||||
bool isSingleFrame = (GetTextureFrameCount() > 0 && GetDurationFrames() == 1);
|
||||
if (!isSingleFrame)
|
||||
{
|
||||
// NOTE: if a new frame isn't available then we could either be seeking or stalled
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual bool IsPlaybackStalled()
|
||||
{
|
||||
const float StallDetectionDuration = 0.5f;
|
||||
|
||||
// Manually detect stalled video if the platform doesn't have native support to detect it
|
||||
if (SupportsTextureFrameCount() && IsExpectingNewVideoFrame())
|
||||
{
|
||||
// Detect a new video frame
|
||||
int frameCount = GetTextureFrameCount();
|
||||
if (frameCount != _stallDetectionFrame)
|
||||
{
|
||||
_stallDetectionTimer = 0f;
|
||||
_stallDetectionFrame = frameCount;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Update the detection timer, but never more than once a Unity frame
|
||||
if (_stallDetectionGuard != Time.frameCount)
|
||||
{
|
||||
_stallDetectionTimer += Time.deltaTime;
|
||||
}
|
||||
}
|
||||
_stallDetectionGuard = Time.frameCount;
|
||||
|
||||
float thresholdDuration = StallDetectionDuration;
|
||||
|
||||
// Scale by the playback rate, but should be at least StallDetectionDuration
|
||||
thresholdDuration = Mathf.Max(thresholdDuration / Mathf.Abs(GetPlaybackRate()), StallDetectionDuration);
|
||||
|
||||
// If a valid FPS is available then make sure the thresholdDuration
|
||||
// is at least double that. This is mainly for very low FPS
|
||||
// content (eg 1 or 2 FPS)
|
||||
float fps = GetVideoFrameRate();
|
||||
if (fps > 0f && !float.IsNaN(fps))
|
||||
{
|
||||
thresholdDuration = Mathf.Max(thresholdDuration, 2f / fps);
|
||||
}
|
||||
|
||||
return (_stallDetectionTimer > thresholdDuration);
|
||||
}
|
||||
else
|
||||
{
|
||||
_stallDetectionTimer = 0f;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private float _stallDetectionTimer;
|
||||
private int _stallDetectionFrame;
|
||||
private int _stallDetectionGuard;
|
||||
#endregion // Stall Detection
|
||||
|
||||
protected List<Subtitle> _subtitles;
|
||||
protected Subtitle _currentSubtitle;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool LoadSubtitlesSRT(string data)
|
||||
{
|
||||
if (string.IsNullOrEmpty(data))
|
||||
{
|
||||
// Disable subtitles
|
||||
_subtitles = null;
|
||||
_currentSubtitle = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
_subtitles = SubtitleUtils.ParseSubtitlesSRT(data);
|
||||
_currentSubtitle = null;
|
||||
}
|
||||
return (_subtitles != null);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual void UpdateSubtitles()
|
||||
{
|
||||
if (_subtitles != null)
|
||||
{
|
||||
double time = GetCurrentTime();
|
||||
|
||||
// TODO: implement a more efficient subtitle index searcher
|
||||
int searchIndex = 0;
|
||||
if (_currentSubtitle != null)
|
||||
{
|
||||
if (!_currentSubtitle.IsTime(time))
|
||||
{
|
||||
if (time > _currentSubtitle.timeEnd)
|
||||
{
|
||||
searchIndex = _currentSubtitle.index + 1;
|
||||
}
|
||||
_currentSubtitle = null;
|
||||
}
|
||||
}
|
||||
|
||||
if (_currentSubtitle == null)
|
||||
{
|
||||
for (int i = searchIndex; i < _subtitles.Count; i++)
|
||||
{
|
||||
if (_subtitles[i].IsTime(time))
|
||||
{
|
||||
_currentSubtitle = _subtitles[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual int GetSubtitleIndex()
|
||||
{
|
||||
int result = -1;
|
||||
if (_currentSubtitle != null)
|
||||
{
|
||||
result = _currentSubtitle.index;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual string GetSubtitleText()
|
||||
{
|
||||
string result = string.Empty;
|
||||
if (_currentSubtitle != null)
|
||||
{
|
||||
result = _currentSubtitle.text;
|
||||
}
|
||||
else if (_currentTextCue != null)
|
||||
{
|
||||
result = _currentTextCue.Text;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public virtual void OnEnable()
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public int GetCurrentTimeFrames(float overrideFrameRate = 0f)
|
||||
{
|
||||
int result = 0;
|
||||
float frameRate = (overrideFrameRate > 0f) ? overrideFrameRate : GetVideoFrameRate();
|
||||
if (frameRate > 0f)
|
||||
{
|
||||
result = Helper.ConvertTimeSecondsToFrame(GetCurrentTime(), frameRate);
|
||||
result = Mathf.Min(result, GetMaxFrameNumber(overrideFrameRate));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public int GetDurationFrames(float overrideFrameRate = 0f)
|
||||
{
|
||||
int result = 0;
|
||||
float frameRate = (overrideFrameRate > 0f) ? overrideFrameRate : GetVideoFrameRate();
|
||||
if (frameRate > 0f)
|
||||
{
|
||||
result = Helper.ConvertTimeSecondsToFrame(GetDuration(), frameRate);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public int GetMaxFrameNumber(float overrideFrameRate = 0f)
|
||||
{
|
||||
int result = GetDurationFrames(overrideFrameRate);
|
||||
result = Mathf.Max(0, result - 1);
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void SeekToFrameRelative(int frameOffset, float overrideFrameRate = 0f)
|
||||
{
|
||||
float frameRate = (overrideFrameRate > 0f)?overrideFrameRate:GetVideoFrameRate();
|
||||
if (frameRate > 0f)
|
||||
{
|
||||
int frame = Helper.ConvertTimeSecondsToFrame(GetCurrentTime(), frameRate);
|
||||
frame += frameOffset;
|
||||
frame = Mathf.Clamp(frame, 0, GetMaxFrameNumber(frameRate));
|
||||
double time = Helper.ConvertFrameToTimeSeconds(frame, frameRate);
|
||||
Seek(time);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void SeekToFrame(int frame, float overrideFrameRate = 0f)
|
||||
{
|
||||
float frameRate = (overrideFrameRate > 0f)?overrideFrameRate:GetVideoFrameRate();
|
||||
if (frameRate > 0f)
|
||||
{
|
||||
frame = Mathf.Clamp(frame, 0, GetMaxFrameNumber(frameRate));
|
||||
double time = Helper.ConvertFrameToTimeSeconds(frame, frameRate);
|
||||
Seek(time);
|
||||
}
|
||||
}
|
||||
|
||||
#region IBufferedDisplay Implementation
|
||||
|
||||
private int _unityFrameCountBufferedDisplayGuard = -1;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public long UpdateBufferedDisplay()
|
||||
{
|
||||
// Guard to make sure we're only updating the buffered frame once per Unity frame
|
||||
if (Time.frameCount == _unityFrameCountBufferedDisplayGuard) return GetTextureTimeStamp();
|
||||
|
||||
_unityFrameCountBufferedDisplayGuard = Time.frameCount;
|
||||
|
||||
return InternalUpdateBufferedDisplay();
|
||||
}
|
||||
|
||||
internal virtual long InternalUpdateBufferedDisplay() { return 0; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual BufferedFramesState GetBufferedFramesState()
|
||||
{
|
||||
return new BufferedFramesState();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual void SetSlaves(IBufferedDisplay[] slaves) { }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual void SetBufferedDisplayMode(BufferedFrameSelectionMode mode, IBufferedDisplay master = null) { }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual void SetBufferedDisplayOptions(bool pauseOnPrerollComplete) { }
|
||||
|
||||
#endregion // IBufferedDisplay Implementation
|
||||
|
||||
protected PlaybackQualityStats _playbackQualityStats = new PlaybackQualityStats();
|
||||
|
||||
public PlaybackQualityStats GetPlaybackQualityStats()
|
||||
{
|
||||
return _playbackQualityStats;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4f59504ca098e7d41b036917f4764ee0
|
||||
timeCreated: 1447782861
|
||||
licenseType: Free
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,106 @@
|
||||
using UnityEngine.Events;
|
||||
using System.Collections.Generic;
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Copyright 2015-2021 RenderHeads Ltd. All rights reserved.
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
namespace RenderHeads.Media.AVProVideo
|
||||
{
|
||||
[System.Serializable]
|
||||
public class MediaPlayerLoadEvent : UnityEvent<string> {}
|
||||
|
||||
[System.Serializable]
|
||||
public class MediaPlayerEvent : UnityEvent<MediaPlayer, MediaPlayerEvent.EventType, ErrorCode>
|
||||
{
|
||||
public enum EventType
|
||||
{
|
||||
MetaDataReady, // Triggered when meta data(width, duration etc) is available
|
||||
ReadyToPlay, // Triggered when the video is loaded and ready to play
|
||||
Started, // Triggered when the playback starts
|
||||
FirstFrameReady, // Triggered when the first frame has been rendered
|
||||
FinishedPlaying, // Triggered when a non-looping video has finished playing
|
||||
Closing, // Triggered when the media is closed
|
||||
Error, // Triggered when an error occurs
|
||||
SubtitleChange, // Triggered when the subtitles change
|
||||
Stalled, // Triggered when media is stalled (eg. when lost connection to media stream)
|
||||
Unstalled, // Triggered when media is resumed form a stalled state (eg. when lost connection is re-established)
|
||||
ResolutionChanged, // Triggered when the resolution of the video has changed (including the load) Useful for adaptive streams
|
||||
StartedSeeking, // Triggered when seeking begins
|
||||
FinishedSeeking, // Triggered when seeking has finished
|
||||
StartedBuffering, // Triggered when buffering begins
|
||||
FinishedBuffering, // Triggered when buffering has finished
|
||||
PropertiesChanged, // Triggered when any properties (eg stereo packing are changed) - this has to be triggered manually
|
||||
PlaylistItemChanged,// Triggered when the new item is played in the playlist
|
||||
PlaylistFinished, // Triggered when the playlist reaches the end
|
||||
|
||||
TextTracksChanged, // Triggered when the text tracks are added or removed
|
||||
TextCueChanged = SubtitleChange, // Triggered when the text to display changes
|
||||
|
||||
// TODO:
|
||||
//StartLoop, // Triggered when the video starts and is in loop mode
|
||||
//EndLoop, // Triggered when the video ends and is in loop mode
|
||||
//NewFrame // Trigger when a new video frame is available
|
||||
}
|
||||
|
||||
private List<UnityAction<MediaPlayer, MediaPlayerEvent.EventType, ErrorCode>> _listeners = new List<UnityAction<MediaPlayer, EventType, ErrorCode>>(4);
|
||||
|
||||
public bool HasListeners()
|
||||
{
|
||||
return (_listeners.Count > 0) || (GetPersistentEventCount() > 0);
|
||||
}
|
||||
|
||||
new public void AddListener(UnityAction<MediaPlayer, MediaPlayerEvent.EventType, ErrorCode> call)
|
||||
{
|
||||
if (!_listeners.Contains(call))
|
||||
{
|
||||
_listeners.Add(call);
|
||||
base.AddListener(call);
|
||||
}
|
||||
}
|
||||
|
||||
new public void RemoveListener(UnityAction<MediaPlayer, MediaPlayerEvent.EventType, ErrorCode> call)
|
||||
{
|
||||
int index = _listeners.IndexOf(call);
|
||||
if (index >= 0)
|
||||
{
|
||||
_listeners.RemoveAt(index);
|
||||
base.RemoveListener(call);
|
||||
}
|
||||
}
|
||||
|
||||
new public void RemoveAllListeners()
|
||||
{
|
||||
_listeners.Clear();
|
||||
base.RemoveAllListeners();
|
||||
}
|
||||
}
|
||||
|
||||
#if false
|
||||
public interface IMediaEvents
|
||||
{
|
||||
void AddEventListener(UnityAction<MediaPlayer, MediaPlayerEvent.EventType, ErrorCode> call);
|
||||
void RemoveListener(UnityAction<MediaPlayer, MediaPlayerEvent.EventType, ErrorCode> call);
|
||||
void RemoveAllEventListeners();
|
||||
}
|
||||
|
||||
public partial class BaseMediaPlayer
|
||||
{
|
||||
void AddEventListener(UnityAction<MediaPlayer, MediaPlayerEvent.EventType, ErrorCode> call)
|
||||
{
|
||||
|
||||
}
|
||||
void RemoveListener(UnityAction<MediaPlayer, MediaPlayerEvent.EventType, ErrorCode> call)
|
||||
{
|
||||
|
||||
}
|
||||
void RemoveAllEventListeners()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
private MediaPlayerEvent _eventHandler;
|
||||
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 16a5efbe992a09144ac89dde2b3e0898
|
||||
timeCreated: 1438695622
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,519 @@
|
||||
using UnityEngine;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Copyright 2015-2022 RenderHeads Ltd. All rights reserved.
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
namespace RenderHeads.Media.AVProVideo
|
||||
{
|
||||
public static class Helper
|
||||
{
|
||||
public const string AVProVideoVersion = "2.8.5";
|
||||
public sealed class ExpectedPluginVersion
|
||||
{
|
||||
public const string Windows = "2.8.5";
|
||||
public const string WinRT = "2.8.5";
|
||||
public const string Android = "2.8.5";
|
||||
public const string Apple = "2.8.5";
|
||||
}
|
||||
|
||||
public const string UnityBaseTextureName = "_MainTex";
|
||||
public const string UnityBaseTextureName_URP = "_BaseMap";
|
||||
public const string UnityBaseTextureName_HDRP = "_BaseColorMap";
|
||||
|
||||
public static string GetPath(MediaPathType location)
|
||||
{
|
||||
string result = string.Empty;
|
||||
switch (location)
|
||||
{
|
||||
case MediaPathType.AbsolutePathOrURL:
|
||||
break;
|
||||
case MediaPathType.RelativeToDataFolder:
|
||||
result = Application.dataPath;
|
||||
break;
|
||||
case MediaPathType.RelativeToPersistentDataFolder:
|
||||
result = Application.persistentDataPath;
|
||||
break;
|
||||
case MediaPathType.RelativeToProjectFolder:
|
||||
#if !UNITY_WINRT_8_1
|
||||
string path = "..";
|
||||
#if UNITY_STANDALONE_OSX && !UNITY_EDITOR_OSX
|
||||
path += "/..";
|
||||
#endif
|
||||
result = System.IO.Path.GetFullPath(System.IO.Path.Combine(Application.dataPath, path));
|
||||
result = result.Replace('\\', '/');
|
||||
#endif
|
||||
break;
|
||||
case MediaPathType.RelativeToStreamingAssetsFolder:
|
||||
result = Application.streamingAssetsPath;
|
||||
break;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public static string GetFilePath(string path, MediaPathType location)
|
||||
{
|
||||
string result = string.Empty;
|
||||
if (!string.IsNullOrEmpty(path))
|
||||
{
|
||||
switch (location)
|
||||
{
|
||||
case MediaPathType.AbsolutePathOrURL:
|
||||
result = path;
|
||||
break;
|
||||
case MediaPathType.RelativeToDataFolder:
|
||||
case MediaPathType.RelativeToPersistentDataFolder:
|
||||
case MediaPathType.RelativeToProjectFolder:
|
||||
case MediaPathType.RelativeToStreamingAssetsFolder:
|
||||
result = System.IO.Path.Combine(GetPath(location), path);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public static string GetFriendlyResolutionName(int width, int height, float fps)
|
||||
{
|
||||
// List of common 16:9 resolutions
|
||||
int[] areas = { 0, 7680 * 4320, 3840 * 2160, 2560 * 1440, 1920 * 1080, 1280 * 720, 853 * 480, 640 * 360, 426 * 240, 256 * 144 };
|
||||
string[] names = { "Unknown", "8K", "4K", "1440p", "1080p", "720p", "480p", "360p", "240p", "144p" };
|
||||
|
||||
Debug.Assert(areas.Length == names.Length);
|
||||
|
||||
// Find the closest resolution
|
||||
int closestAreaIndex = 0;
|
||||
int area = width * height;
|
||||
int minDelta = int.MaxValue;
|
||||
for (int i = 0; i < areas.Length; i++)
|
||||
{
|
||||
int d = Mathf.Abs(areas[i] - area);
|
||||
// TODO: add a maximum threshold to ignore differences that are too high
|
||||
if (d < minDelta)
|
||||
{
|
||||
closestAreaIndex = i;
|
||||
minDelta = d;
|
||||
// If the exact mode is found, early out
|
||||
if (d == 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
string result = names[closestAreaIndex];
|
||||
|
||||
// Append frame rate if valid
|
||||
if (fps > 0f && !float.IsNaN(fps))
|
||||
{
|
||||
result += fps.ToString("0.##");
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static string GetErrorMessage(ErrorCode code)
|
||||
{
|
||||
string result = string.Empty;
|
||||
switch (code)
|
||||
{
|
||||
case ErrorCode.None:
|
||||
result = "No Error";
|
||||
break;
|
||||
case ErrorCode.LoadFailed:
|
||||
result = "Loading failed. File not found, codec not supported, video resolution too high or insufficient system resources.";
|
||||
#if UNITY_STANDALONE_WIN || UNITY_EDITOR_WIN
|
||||
// Add extra information for older Windows versions that don't have support for modern codecs
|
||||
if (SystemInfo.operatingSystem.StartsWith("Windows XP") ||
|
||||
SystemInfo.operatingSystem.StartsWith("Windows Vista"))
|
||||
{
|
||||
result += " NOTE: Windows XP and Vista don't have native support for H.264 codec. Consider using an older codec such as DivX or installing 3rd party codecs such as LAV Filters.";
|
||||
}
|
||||
#endif
|
||||
break;
|
||||
case ErrorCode.DecodeFailed:
|
||||
result = "Decode failed. Possible codec not supported, video resolution/bit-depth too high, or insufficient system resources.";
|
||||
#if UNITY_ANDROID
|
||||
result += " On Android this is generally due to the hardware not having enough resources to decode the video. Most Android devices can only handle a maximum of one 4K video at once.";
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public static string GetPlatformName(Platform platform)
|
||||
{
|
||||
string result = "Unknown";
|
||||
switch (platform)
|
||||
{
|
||||
case Platform.WindowsUWP:
|
||||
result = "Windows UWP";
|
||||
break;
|
||||
case Platform.MacOSX:
|
||||
result = "macOS";
|
||||
break;
|
||||
default:
|
||||
result = platform.ToString();
|
||||
break;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public static string[] GetPlatformNames()
|
||||
{
|
||||
return new string[] {
|
||||
GetPlatformName(Platform.Windows),
|
||||
GetPlatformName(Platform.MacOSX),
|
||||
GetPlatformName(Platform.iOS),
|
||||
GetPlatformName(Platform.tvOS),
|
||||
GetPlatformName(Platform.Android),
|
||||
GetPlatformName(Platform.WindowsUWP),
|
||||
GetPlatformName(Platform.WebGL),
|
||||
};
|
||||
}
|
||||
|
||||
#if AVPROVIDEO_DISABLE_LOGGING
|
||||
[System.Diagnostics.Conditional("ALWAYS_FALSE")]
|
||||
#endif
|
||||
public static void LogInfo(string message, Object context = null)
|
||||
{
|
||||
if (context == null)
|
||||
{
|
||||
Debug.Log("[AVProVideo] " + message);
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.Log("[AVProVideo] " + message, context);
|
||||
}
|
||||
}
|
||||
|
||||
public static int GetUnityAudioSampleRate()
|
||||
{
|
||||
// For standalone builds (not in the editor):
|
||||
// In Unity 4.6, 5.0, 5.1 when audio is disabled there is no indication from the API.
|
||||
// But in 5.2.0 and above, it logs an error when trying to call
|
||||
// AudioSettings.GetDSPBufferSize() or AudioSettings.outputSampleRate
|
||||
// So to prevent the error, check if AudioSettings.GetConfiguration().sampleRate == 0
|
||||
return (AudioSettings.GetConfiguration().sampleRate == 0) ? 0 : AudioSettings.outputSampleRate;
|
||||
}
|
||||
|
||||
public static int GetUnityAudioSpeakerCount()
|
||||
{
|
||||
switch (AudioSettings.GetConfiguration().speakerMode)
|
||||
{
|
||||
case AudioSpeakerMode.Mono: return 1;
|
||||
case AudioSpeakerMode.Stereo: return 2;
|
||||
case AudioSpeakerMode.Quad: return 4;
|
||||
case AudioSpeakerMode.Surround: return 5;
|
||||
case AudioSpeakerMode.Mode5point1: return 6;
|
||||
case AudioSpeakerMode.Mode7point1: return 8;
|
||||
case AudioSpeakerMode.Prologic: return 2;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Returns a valid range to use for a timeline display
|
||||
// Either it will return the range 0..duration, or
|
||||
// for live streams it will return first seekable..last seekable time
|
||||
public static TimeRange GetTimelineRange(double duration, TimeRanges seekable)
|
||||
{
|
||||
TimeRange result = new TimeRange();
|
||||
if (duration >= 0.0 && duration < 2e10)
|
||||
{
|
||||
// Duration is valid
|
||||
result.startTime = 0f;
|
||||
result.duration = duration;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Duration is invalid, so it could be a live stream, so derive from seekable range
|
||||
result.startTime = seekable.MinTime;
|
||||
result.duration = seekable.Duration;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public const double SecondsToHNS = 10000000.0;
|
||||
public const double MilliSecondsToHNS = 10000.0;
|
||||
|
||||
public static string GetTimeString(double timeSeconds, bool showMilliseconds = false)
|
||||
{
|
||||
float totalSeconds = (float)timeSeconds;
|
||||
int hours = Mathf.FloorToInt(totalSeconds / (60f * 60f));
|
||||
float usedSeconds = hours * 60f * 60f;
|
||||
|
||||
int minutes = Mathf.FloorToInt((totalSeconds - usedSeconds) / 60f);
|
||||
usedSeconds += minutes * 60f;
|
||||
|
||||
int seconds = Mathf.FloorToInt(totalSeconds - usedSeconds);
|
||||
|
||||
string result;
|
||||
if (hours <= 0)
|
||||
{
|
||||
if (showMilliseconds)
|
||||
{
|
||||
int milliSeconds = (int)((totalSeconds - Mathf.Floor(totalSeconds)) * 1000f);
|
||||
result = string.Format("{0:00}:{1:00}:{2:000}", minutes, seconds, milliSeconds);
|
||||
}
|
||||
else
|
||||
{
|
||||
result = string.Format("{0:00}:{1:00}", minutes, seconds);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (showMilliseconds)
|
||||
{
|
||||
int milliSeconds = (int)((totalSeconds - Mathf.Floor(totalSeconds)) * 1000f);
|
||||
result = string.Format("{2}:{0:00}:{1:00}:{3:000}", minutes, seconds, hours, milliSeconds);
|
||||
}
|
||||
else
|
||||
{
|
||||
result = string.Format("{2}:{0:00}:{1:00}", minutes, seconds, hours);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convert texture transform matrix to an enum of orientation types
|
||||
/// </summary>
|
||||
public static Orientation GetOrientation(float[] t)
|
||||
{
|
||||
Orientation result = Orientation.Landscape;
|
||||
if (t != null)
|
||||
{
|
||||
// TODO: check that the Portrait and PortraitFlipped are the right way around
|
||||
if (t[0] == 0f && t[1]== 1f && t[2] == -1f && t[3] == 0f)
|
||||
{
|
||||
result = Orientation.Portrait;
|
||||
} else
|
||||
if (t[0] == 0f && t[1] == -1f && t[2] == 1f && t[3] == 0f)
|
||||
{
|
||||
result = Orientation.PortraitFlipped;
|
||||
} else
|
||||
if (t[0]== 1f && t[1] == 0f && t[2] == 0f && t[3] == 1f)
|
||||
{
|
||||
result = Orientation.Landscape;
|
||||
} else
|
||||
if (t[0] == -1f && t[1] == 0f && t[2] == 0f && t[3] == -1f)
|
||||
{
|
||||
result = Orientation.LandscapeFlipped;
|
||||
}
|
||||
else
|
||||
if (t[0] == 0f && t[1] == 1f && t[2] == 1f && t[3] == 0f)
|
||||
{
|
||||
result = Orientation.PortraitHorizontalMirror;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private static Matrix4x4 PortraitMatrix = Matrix4x4.TRS(new Vector3(0f, 1f, 0f), Quaternion.Euler(0f, 0f, -90f), Vector3.one);
|
||||
private static Matrix4x4 PortraitFlippedMatrix = Matrix4x4.TRS(new Vector3(1f, 0f, 0f), Quaternion.Euler(0f, 0f, 90f), Vector3.one);
|
||||
private static Matrix4x4 LandscapeFlippedMatrix = Matrix4x4.TRS(new Vector3(0f, 1f, 0f), Quaternion.Euler(0f, 0f, -90f), Vector3.one);
|
||||
|
||||
public static Matrix4x4 GetMatrixForOrientation(Orientation ori)
|
||||
{
|
||||
Matrix4x4 result;
|
||||
switch (ori)
|
||||
{
|
||||
case Orientation.Landscape:
|
||||
result = Matrix4x4.identity;
|
||||
break;
|
||||
case Orientation.LandscapeFlipped:
|
||||
result = LandscapeFlippedMatrix;
|
||||
break;
|
||||
case Orientation.Portrait:
|
||||
result = PortraitMatrix;
|
||||
break;
|
||||
case Orientation.PortraitFlipped:
|
||||
result = PortraitFlippedMatrix;
|
||||
break;
|
||||
case Orientation.PortraitHorizontalMirror:
|
||||
result = new Matrix4x4();
|
||||
result.SetColumn(0, new Vector4(0f, 1f, 0f, 0f));
|
||||
result.SetColumn(1, new Vector4(1f, 0f, 0f, 0f));
|
||||
result.SetColumn(2, new Vector4(0f, 0f, 1f, 0f));
|
||||
result.SetColumn(3, new Vector4(0f, 0f, 0f, 1f));
|
||||
break;
|
||||
default:
|
||||
throw new System.Exception("Unknown Orientation type");
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public static int ConvertTimeSecondsToFrame(double seconds, float frameRate)
|
||||
{
|
||||
// NOTE: Generally you should use RountToInt when converting from time to frame number
|
||||
// but because we're adding a half frame offset (which seems to be the safer thing to do) we need to FloorToInt
|
||||
seconds = System.Math.Max(0.0, seconds);
|
||||
frameRate = Mathf.Max(0f, frameRate);
|
||||
return (int)System.Math.Floor(frameRate * seconds);
|
||||
}
|
||||
|
||||
public static double ConvertFrameToTimeSeconds(int frame, float frameRate)
|
||||
{
|
||||
frame = Mathf.Max(0, frame);
|
||||
frameRate = Mathf.Max(0f, frameRate);
|
||||
double frameDurationSeconds = 1.0 / frameRate;
|
||||
return ((double)frame * frameDurationSeconds) + (frameDurationSeconds * 0.5); // Add half a frame we that the time lands in the middle of the frame range and not at the edges
|
||||
}
|
||||
|
||||
public static double FindNextKeyFrameTimeSeconds(double seconds, float frameRate, int keyFrameInterval)
|
||||
{
|
||||
seconds = System.Math.Max(0.0, seconds);
|
||||
frameRate = Mathf.Max(0f, frameRate);
|
||||
keyFrameInterval = Mathf.Max(0, keyFrameInterval);
|
||||
int currentFrame = Helper.ConvertTimeSecondsToFrame(seconds, frameRate);
|
||||
// TODO: allow specifying a minimum number of frames so that if currentFrame is too close to nextKeyFrame, it will calculate the next-next keyframe
|
||||
int nextKeyFrame = keyFrameInterval * Mathf.CeilToInt((float)(currentFrame + 1) / (float)keyFrameInterval);
|
||||
return Helper.ConvertFrameToTimeSeconds(nextKeyFrame, frameRate);
|
||||
}
|
||||
|
||||
public static System.DateTime ConvertSecondsSince1970ToDateTime(double secondsSince1970)
|
||||
{
|
||||
System.TimeSpan time = System.TimeSpan.FromSeconds(secondsSince1970);
|
||||
return new System.DateTime(1970, 1, 1).Add(time);
|
||||
}
|
||||
|
||||
#if (UNITY_EDITOR_WIN || (!UNITY_EDITOR && UNITY_STANDALONE_WIN))
|
||||
[System.Runtime.InteropServices.DllImport("kernel32.dll", CharSet = System.Runtime.InteropServices.CharSet.Unicode, EntryPoint = "GetShortPathNameW", SetLastError=true)]
|
||||
private static extern int GetShortPathName([System.Runtime.InteropServices.MarshalAs(System.Runtime.InteropServices.UnmanagedType.LPWStr)] string pathName,
|
||||
[System.Runtime.InteropServices.MarshalAs(System.Runtime.InteropServices.UnmanagedType.LPWStr)] System.Text.StringBuilder shortName,
|
||||
int cbShortName);
|
||||
|
||||
// Handle very long file paths by converting to DOS 8.3 format
|
||||
internal static string ConvertLongPathToShortDOS83Path(string path)
|
||||
{
|
||||
const string pathToken = @"\\?\";
|
||||
string result = pathToken + path.Replace("/","\\");
|
||||
int length = GetShortPathName(result, null, 0);
|
||||
if (length > 0)
|
||||
{
|
||||
System.Text.StringBuilder sb = new System.Text.StringBuilder(length);
|
||||
if (0 != GetShortPathName(result, sb, length))
|
||||
{
|
||||
result = sb.ToString().Replace(pathToken, "");
|
||||
Debug.LogWarning("[AVProVideo] Long path detected. Changing to DOS 8.3 format");
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
#endif
|
||||
|
||||
// Converts a non-readable texture to a readable Texture2D.
|
||||
// "targetTexture" can be null or you can pass in an existing texture.
|
||||
// Remember to Destroy() the returned texture after finished with it
|
||||
public static Texture2D GetReadableTexture(Texture inputTexture, bool requiresVerticalFlip, Orientation ori, Texture2D targetTexture = null)
|
||||
{
|
||||
Texture2D resultTexture = targetTexture;
|
||||
|
||||
RenderTexture prevRT = RenderTexture.active;
|
||||
|
||||
int textureWidth = inputTexture.width;
|
||||
int textureHeight = inputTexture.height;
|
||||
#if UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX || UNITY_IPHONE || UNITY_IOS || UNITY_TVOS
|
||||
if (ori == Orientation.Portrait || ori == Orientation.PortraitFlipped)
|
||||
{
|
||||
textureWidth = inputTexture.height;
|
||||
textureHeight = inputTexture.width;
|
||||
}
|
||||
#endif
|
||||
|
||||
// Blit the texture to a temporary RenderTexture
|
||||
// This handles any format conversion that is required and allows us to use ReadPixels to copy texture from RT to readable texture
|
||||
RenderTexture tempRT = RenderTexture.GetTemporary(textureWidth, textureHeight, 0, RenderTextureFormat.ARGB32);
|
||||
|
||||
if (ori == Orientation.Landscape)
|
||||
{
|
||||
if (!requiresVerticalFlip)
|
||||
{
|
||||
Graphics.Blit(inputTexture, tempRT);
|
||||
}
|
||||
else
|
||||
{
|
||||
// The above Blit can't flip unless using a material, so we use Graphics.DrawTexture instead
|
||||
GL.PushMatrix();
|
||||
RenderTexture.active = tempRT;
|
||||
GL.LoadPixelMatrix(0f, tempRT.width, 0f, tempRT.height);
|
||||
Rect sourceRect = new Rect(0f, 0f, 1f, 1f);
|
||||
// NOTE: not sure why we need to set y to -1, without this there is a 1px gap at the bottom
|
||||
Rect destRect = new Rect(0f, -1f, tempRT.width, tempRT.height);
|
||||
|
||||
Graphics.DrawTexture(destRect, inputTexture, sourceRect, 0, 0, 0, 0);
|
||||
GL.PopMatrix();
|
||||
GL.InvalidateState();
|
||||
}
|
||||
}
|
||||
#if UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX || UNITY_IPHONE || UNITY_IOS || UNITY_TVOS
|
||||
else
|
||||
{
|
||||
Matrix4x4 m = Matrix4x4.identity;
|
||||
switch (ori)
|
||||
{
|
||||
case Orientation.Portrait:
|
||||
m = Matrix4x4.TRS(new Vector3(0f, inputTexture.width, 0f), Quaternion.Euler(0f, 0f, -90f), Vector3.one);
|
||||
break;
|
||||
case Orientation.PortraitFlipped:
|
||||
m = Matrix4x4.TRS(new Vector3(inputTexture.height, 0f, 0f), Quaternion.Euler(0f, 0f, 90f), Vector3.one);
|
||||
break;
|
||||
case Orientation.LandscapeFlipped:
|
||||
m = Matrix4x4.TRS(new Vector3(inputTexture.width, inputTexture.height, 0f), Quaternion.identity, new Vector3(-1f, -1f, 1f));
|
||||
break;
|
||||
}
|
||||
|
||||
// The above Blit can't flip unless using a material, so we use Graphics.DrawTexture instead
|
||||
GL.InvalidateState();
|
||||
RenderTexture.active = tempRT;
|
||||
GL.Clear(false, true, Color.black);
|
||||
GL.PushMatrix();
|
||||
GL.LoadPixelMatrix(0f, tempRT.width, 0f, tempRT.height);
|
||||
Rect sourceRect = new Rect(0f, 0f, 1f, 1f);
|
||||
// NOTE: not sure why we need to set y to -1, without this there is a 1px gap at the bottom
|
||||
Rect destRect = new Rect(0f, -1f, inputTexture.width, inputTexture.height);
|
||||
GL.MultMatrix(m);
|
||||
|
||||
Graphics.DrawTexture(destRect, inputTexture, sourceRect, 0, 0, 0, 0);
|
||||
GL.PopMatrix();
|
||||
GL.InvalidateState();
|
||||
}
|
||||
#endif
|
||||
|
||||
if (resultTexture == null)
|
||||
{
|
||||
resultTexture = new Texture2D(textureWidth, textureHeight, TextureFormat.ARGB32, false);
|
||||
}
|
||||
|
||||
RenderTexture.active = tempRT;
|
||||
resultTexture.ReadPixels(new Rect(0f, 0f, textureWidth, textureHeight), 0, 0, false);
|
||||
resultTexture.Apply(false, false);
|
||||
RenderTexture.ReleaseTemporary(tempRT);
|
||||
|
||||
RenderTexture.active = prevRT;
|
||||
|
||||
return resultTexture;
|
||||
}
|
||||
|
||||
// Converts a non-readable texture to a readable Texture2D.
|
||||
// "targetTexture" can be null or you can pass in an existing texture.
|
||||
// Remember to Destroy() the returned texture after finished with it
|
||||
public static Texture2D GetReadableTexture(RenderTexture inputTexture, Texture2D targetTexture = null)
|
||||
{
|
||||
if (targetTexture == null)
|
||||
{
|
||||
targetTexture = new Texture2D(inputTexture.width, inputTexture.height, TextureFormat.ARGB32, false);
|
||||
}
|
||||
|
||||
RenderTexture prevRT = RenderTexture.active;
|
||||
RenderTexture.active = inputTexture;
|
||||
targetTexture.ReadPixels(new Rect(0f, 0f, inputTexture.width, inputTexture.height), 0, 0, false);
|
||||
targetTexture.Apply(false, false);
|
||||
RenderTexture.active = prevRT;
|
||||
|
||||
return targetTexture;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 79e446998599e1647804321292c80f42
|
||||
timeCreated: 1600887818
|
||||
licenseType: Pro
|
||||
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,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 00407cbf3ca503142903894431082ac6
|
||||
timeCreated: 1438695622
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,225 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Copyright 2015-2021 RenderHeads Ltd. All rights reserved.
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
namespace RenderHeads.Media.AVProVideo
|
||||
{
|
||||
/// <summary>
|
||||
/// Attempts to give insight into video playback presentation smoothness quality
|
||||
/// Keeps track of skipped and duplicated frames and warns about suboptimal setup
|
||||
/// such as no vsync enabled or video frame rate not being a multiple of the display frame rate
|
||||
/// </summary>
|
||||
public class PlaybackQualityStats
|
||||
{
|
||||
public int SkippedFrames { get; private set; }
|
||||
public int DuplicateFrames { get; private set; }
|
||||
public int UnityDroppedFrames { get; private set; }
|
||||
public float PerfectFramesT { get; private set; }
|
||||
public string VSyncStatus { get; private set; }
|
||||
private int PerfectFrames { get; set; }
|
||||
private int TotalFrames { get; set; }
|
||||
|
||||
public bool LogIssues { get; set; }
|
||||
|
||||
private int _sameFrameCount;
|
||||
private long _lastTimeStamp;
|
||||
private BaseMediaPlayer _player;
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
_sameFrameCount = 0;
|
||||
if (_player != null)
|
||||
{
|
||||
_lastTimeStamp = _player.GetTextureTimeStamp();
|
||||
}
|
||||
|
||||
SkippedFrames = 0;
|
||||
DuplicateFrames = 0;
|
||||
UnityDroppedFrames = 0;
|
||||
TotalFrames = 0;
|
||||
PerfectFrames = 0;
|
||||
PerfectFramesT = 0f;
|
||||
}
|
||||
|
||||
internal void Start(BaseMediaPlayer player)
|
||||
{
|
||||
_player = player;
|
||||
Reset();
|
||||
|
||||
bool vsyncEnabled = true;
|
||||
if (QualitySettings.vSyncCount == 0)
|
||||
{
|
||||
vsyncEnabled = false;
|
||||
if (LogIssues)
|
||||
{
|
||||
Debug.LogWarning("[AVProVideo][Quality] VSync is currently disabled in Quality Settings");
|
||||
}
|
||||
}
|
||||
if (!IsGameViewVSyncEnabled())
|
||||
{
|
||||
vsyncEnabled = false;
|
||||
if (LogIssues)
|
||||
{
|
||||
Debug.LogWarning("[AVProVideo][Quality] VSync is currently disabled in the Game View");
|
||||
}
|
||||
}
|
||||
|
||||
float frameRate = _player.GetVideoFrameRate();
|
||||
float frameMs = (1000f / frameRate);
|
||||
if (LogIssues)
|
||||
{
|
||||
Debug.Log(string.Format("[AVProVideo][Quality] Video: {0}fps {1}ms", frameRate, frameMs));
|
||||
}
|
||||
|
||||
if (vsyncEnabled)
|
||||
{
|
||||
#if UNITY_2022_2_OR_NEWER
|
||||
float refreshRate = (float)( Screen.currentResolution.refreshRateRatio.value );
|
||||
#else
|
||||
float refreshRate = (float)( Screen.currentResolution.refreshRate );
|
||||
#endif
|
||||
|
||||
float vsyncRate = refreshRate / QualitySettings.vSyncCount;
|
||||
float vsyncMs = (1000f / vsyncRate);
|
||||
|
||||
if (LogIssues)
|
||||
{
|
||||
Debug.Log(string.Format("[AVProVideo][Quality] VSync: {0}fps {1}ms", vsyncRate, vsyncMs));
|
||||
}
|
||||
|
||||
float framesPerVSync = frameMs / vsyncMs;
|
||||
float fractionalframesPerVsync = framesPerVSync - Mathf.FloorToInt(framesPerVSync);
|
||||
if (fractionalframesPerVsync > 0.0001f && LogIssues)
|
||||
{
|
||||
Debug.LogWarning("[AVProVideo][Quality] Video is not a multiple of VSync so playback cannot be perfect");
|
||||
}
|
||||
VSyncStatus = "VSync " + framesPerVSync;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (LogIssues)
|
||||
{
|
||||
Debug.LogWarning("[AVProVideo][Quality] Running without VSync enabled");
|
||||
}
|
||||
VSyncStatus = "No VSync";
|
||||
}
|
||||
}
|
||||
|
||||
internal void Update()
|
||||
{
|
||||
if (_player == null) return;
|
||||
|
||||
// Don't analyse stats unless real playback is happening
|
||||
if (_player.IsPaused() || _player.IsSeeking() || _player.IsFinished()) return;
|
||||
|
||||
long timeStamp = _player.GetTextureTimeStamp();
|
||||
long frameDuration = (long)(Helper.SecondsToHNS / _player.GetVideoFrameRate());
|
||||
|
||||
bool isPerfectFrame = true;
|
||||
|
||||
// Check for skipped frames
|
||||
long d = (timeStamp - _lastTimeStamp);
|
||||
if (d > 0)
|
||||
{
|
||||
const long threshold = 10000;
|
||||
d -= frameDuration;
|
||||
if (d > threshold)
|
||||
{
|
||||
int skippedFrames = Mathf.FloorToInt((float)d / (float)frameDuration);
|
||||
if (LogIssues)
|
||||
{
|
||||
Debug.LogWarning("[AVProVideo][Quality] Possible frame skip, at " + timeStamp + " delta " + d + " = " + skippedFrames + " frames");
|
||||
}
|
||||
SkippedFrames += skippedFrames;
|
||||
isPerfectFrame = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (QualitySettings.vSyncCount != 0)
|
||||
{
|
||||
#if UNITY_2022_2_OR_NEWER
|
||||
float refreshRate = (float)( Screen.currentResolution.refreshRateRatio.value );
|
||||
#else
|
||||
float refreshRate = (float)( Screen.currentResolution.refreshRate );
|
||||
#endif
|
||||
|
||||
long vsyncDuration = (long)((QualitySettings.vSyncCount * Helper.SecondsToHNS) / refreshRate);
|
||||
if (timeStamp != _lastTimeStamp)
|
||||
{
|
||||
float framesPerVSync = (float)frameDuration / (float)vsyncDuration;
|
||||
//Debug.Log((float)frameDuration + " " + (float)vsyncDuration);
|
||||
float fractionalFramesPerVSync = framesPerVSync - Mathf.FloorToInt(framesPerVSync);
|
||||
|
||||
//Debug.Log(framesPerVSync + " " + fractionalFramesPerVSync);
|
||||
// VSync rate is a multiple of the video rate so we should be able to get perfectly smooth playback
|
||||
if (fractionalFramesPerVSync <= 0.0001f)
|
||||
{
|
||||
// Check for duplicate frames
|
||||
if (!Mathf.Approximately(_sameFrameCount, (int)framesPerVSync))
|
||||
{
|
||||
if (LogIssues)
|
||||
{
|
||||
Debug.LogWarning("[AVProVideo][Quality] Frame " + timeStamp + " was shown for " + _sameFrameCount + " frames instead of expected " + framesPerVSync);
|
||||
}
|
||||
DuplicateFrames++;
|
||||
isPerfectFrame = false;
|
||||
}
|
||||
}
|
||||
|
||||
_sameFrameCount = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Count the number of Unity-frames the video-frame is displayed for
|
||||
_sameFrameCount++;
|
||||
}
|
||||
|
||||
// Check for Unity dropping frames
|
||||
{
|
||||
long frameTime = (long)(Time.deltaTime * Helper.SecondsToHNS);
|
||||
if (frameTime > (vsyncDuration + (vsyncDuration / 3)))
|
||||
{
|
||||
if (LogIssues)
|
||||
{
|
||||
Debug.LogWarning("[AVProVideo][Quality] Possible Unity dropped frame, delta time: " + (Time.deltaTime * 1000f) + "ms");
|
||||
}
|
||||
UnityDroppedFrames++;
|
||||
isPerfectFrame = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (_lastTimeStamp != timeStamp)
|
||||
{
|
||||
if (isPerfectFrame)
|
||||
{
|
||||
PerfectFrames++;
|
||||
}
|
||||
TotalFrames++;
|
||||
PerfectFramesT = (float)PerfectFrames / (float)TotalFrames;
|
||||
}
|
||||
|
||||
_lastTimeStamp = timeStamp;
|
||||
}
|
||||
|
||||
private static bool IsGameViewVSyncEnabled()
|
||||
{
|
||||
bool result = true;
|
||||
#if UNITY_EDITOR && UNITY_2019_1_OR_NEWER
|
||||
System.Reflection.Assembly assembly = typeof(UnityEditor.EditorWindow).Assembly;
|
||||
System.Type type = assembly.GetType("UnityEditor.GameView");
|
||||
UnityEditor.EditorWindow window = UnityEditor.EditorWindow.GetWindow(type);
|
||||
System.Reflection.PropertyInfo prop = type.GetProperty("vSyncEnabled");
|
||||
if (prop != null)
|
||||
{
|
||||
result = (bool)prop.GetValue(window);
|
||||
}
|
||||
#endif
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4f344d823db9c4148af4dec2235f690d
|
||||
timeCreated: 1634119397
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e2f8dd08c4c77654282b755fd4a069c1
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 80eb525dd677aa440823910b09b23ae0
|
||||
timeCreated: 1438698292
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,439 @@
|
||||
//-----------------------------------------------------------------------------
|
||||
// Copyright 2015-2022 RenderHeads Ltd. All rights reserved.
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
#if UNITY_2017_2_OR_NEWER && (UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX || (!UNITY_EDITOR && (UNITY_IOS || UNITY_TVOS)))
|
||||
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using UnityEngine;
|
||||
|
||||
namespace RenderHeads.Media.AVProVideo
|
||||
{
|
||||
public sealed partial class AppleMediaPlayer
|
||||
{
|
||||
internal partial struct Native
|
||||
{
|
||||
#if UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX
|
||||
private const string PluginName = "AVProVideo";
|
||||
#elif UNITY_IOS || UNITY_TVOS
|
||||
private const string PluginName = "__Internal";
|
||||
#endif
|
||||
|
||||
// Video settings
|
||||
|
||||
internal enum AVPPlayerVideoPixelFormat: int
|
||||
{
|
||||
Invalid,
|
||||
Bgra,
|
||||
YCbCr420
|
||||
}
|
||||
|
||||
[Flags]
|
||||
internal enum AVPPlayerVideoOutputSettingsFlags: int
|
||||
{
|
||||
None = 0,
|
||||
LinearColorSpace = 1 << 0,
|
||||
GenerateMipmaps = 1 << 1,
|
||||
}
|
||||
|
||||
// Audio settings
|
||||
|
||||
internal enum AVPPlayerAudioOutputMode : int
|
||||
{
|
||||
SystemDirect,
|
||||
Unity,
|
||||
SystemDirectWithCapture,
|
||||
}
|
||||
|
||||
// Network settings
|
||||
|
||||
[Flags]
|
||||
internal enum AVPPlayerNetworkSettingsFlags: int
|
||||
{
|
||||
None = 0,
|
||||
PlayWithoutBuffering = 1 << 0,
|
||||
UseSinglePlayerItem = 1 << 1,
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
internal struct AVPPlayerSettings
|
||||
{
|
||||
// Video
|
||||
internal AVPPlayerVideoPixelFormat pixelFormat;
|
||||
internal AVPPlayerVideoOutputSettingsFlags videoFlags;
|
||||
internal float preferredMaximumResolution_width;
|
||||
internal float preferredMaximumResolution_height;
|
||||
internal float maximumPlaybackRate;
|
||||
|
||||
// Audio
|
||||
internal AVPPlayerAudioOutputMode audioOutputMode;
|
||||
internal int sampleRate;
|
||||
internal int bufferLength;
|
||||
internal int audioFlags;
|
||||
|
||||
// Network
|
||||
internal double preferredPeakBitRate;
|
||||
internal double preferredForwardBufferDuration;
|
||||
internal AVPPlayerNetworkSettingsFlags networkFlags;
|
||||
}
|
||||
|
||||
[Flags]
|
||||
internal enum AVPPlayerStatus : int
|
||||
{
|
||||
Unknown = 0,
|
||||
ReadyToPlay = 1 << 0,
|
||||
Playing = 1 << 1,
|
||||
Paused = 1 << 2,
|
||||
Finished = 1 << 3,
|
||||
Seeking = 1 << 4,
|
||||
Buffering = 1 << 5,
|
||||
Stalled = 1 << 6,
|
||||
ExternalPlaybackActive = 1 << 7,
|
||||
Cached = 1 << 8,
|
||||
FinishedSeeking = 1 << 9,
|
||||
|
||||
UpdatedAssetInfo = 1 << 16,
|
||||
UpdatedTexture = 1 << 17,
|
||||
UpdatedBufferedTimeRanges = 1 << 18,
|
||||
UpdatedSeekableTimeRanges = 1 << 19,
|
||||
UpdatedText = 1 << 20,
|
||||
|
||||
HasVideo = 1 << 24,
|
||||
HasAudio = 1 << 25,
|
||||
HasText = 1 << 26,
|
||||
HasMetadata = 1 << 27,
|
||||
|
||||
Failed = 1 << 31
|
||||
}
|
||||
|
||||
[Flags]
|
||||
internal enum AVPPlayerFlags : int
|
||||
{
|
||||
None = 0,
|
||||
Looping = 1 << 0,
|
||||
Muted = 1 << 1,
|
||||
AllowExternalPlayback = 1 << 2,
|
||||
ResumePlayback = 1 << 16, // iOS only, resumes playback after audio session route change
|
||||
Dirty = 1 << 31
|
||||
}
|
||||
|
||||
internal enum AVPPlayerExternalPlaybackVideoGravity : int
|
||||
{
|
||||
Resize,
|
||||
ResizeAspect,
|
||||
ResizeAspectFill
|
||||
};
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
internal struct AVPPlayerSize
|
||||
{
|
||||
internal float width;
|
||||
internal float height;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
internal struct AVPAffineTransform
|
||||
{
|
||||
internal float a;
|
||||
internal float b;
|
||||
internal float c;
|
||||
internal float d;
|
||||
internal float tx;
|
||||
internal float ty;
|
||||
}
|
||||
|
||||
[Flags]
|
||||
internal enum AVPPlayerAssetFlags : int
|
||||
{
|
||||
None = 0,
|
||||
CompatibleWithAirPlay = 1 << 0,
|
||||
};
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
internal struct AVPPlayerAssetInfo
|
||||
{
|
||||
internal double duration;
|
||||
internal AVPPlayerSize dimensions;
|
||||
internal float frameRate;
|
||||
internal int videoTrackCount;
|
||||
internal int audioTrackCount;
|
||||
internal int textTrackCount;
|
||||
internal AVPPlayerAssetFlags flags;
|
||||
}
|
||||
|
||||
[Flags]
|
||||
internal enum AVPPlayerTrackFlags: int
|
||||
{
|
||||
Default = 1 << 0,
|
||||
}
|
||||
|
||||
internal enum AVPPlayerVideoTrackStereoMode: int
|
||||
{
|
||||
Unknown,
|
||||
Monoscopic,
|
||||
StereoscopicTopBottom,
|
||||
StereoscopicLeftRight,
|
||||
StereoscopicCustom,
|
||||
StereoscopicRightLeft,
|
||||
}
|
||||
|
||||
[Flags]
|
||||
internal enum AVPPlayerVideoTrackFlags: int
|
||||
{
|
||||
HasAlpha = 1 << 0,
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
internal struct AVPPlayerVideoTrackInfo
|
||||
{
|
||||
[MarshalAs(UnmanagedType.LPWStr)] internal string name;
|
||||
[MarshalAs(UnmanagedType.LPWStr)] internal string language;
|
||||
internal int trackId;
|
||||
internal float estimatedDataRate;
|
||||
internal uint codecSubtype;
|
||||
internal AVPPlayerTrackFlags flags;
|
||||
|
||||
internal AVPPlayerSize dimensions;
|
||||
internal float frameRate;
|
||||
internal AVPAffineTransform transform;
|
||||
internal AVPPlayerVideoTrackStereoMode stereoMode;
|
||||
internal int bitsPerComponent;
|
||||
internal AVPPlayerVideoTrackFlags videoTrackFlags;
|
||||
|
||||
internal Matrix4x4 yCbCrTransform;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
internal struct AVPPlayerAudioTrackInfo
|
||||
{
|
||||
[MarshalAs(UnmanagedType.LPWStr)] internal string name;
|
||||
[MarshalAs(UnmanagedType.LPWStr)] internal string language;
|
||||
internal int trackId;
|
||||
internal float estimatedDataRate;
|
||||
internal uint codecSubtype;
|
||||
internal AVPPlayerTrackFlags flags;
|
||||
|
||||
internal double sampleRate;
|
||||
internal uint channelCount;
|
||||
internal uint channelLayoutTag;
|
||||
internal AudioChannelMaskFlags channelBitmap;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
internal struct AVPPlayerTextTrackInfo
|
||||
{
|
||||
[MarshalAs(UnmanagedType.LPWStr)] internal string name;
|
||||
[MarshalAs(UnmanagedType.LPWStr)] internal string language;
|
||||
internal int trackId;
|
||||
internal float estimatedDataRate;
|
||||
internal uint codecSubtype;
|
||||
internal AVPPlayerTrackFlags flags;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
internal struct AVPPlayerTimeRange
|
||||
{
|
||||
internal double start;
|
||||
internal double duration;
|
||||
};
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
internal struct AVPPlayerState
|
||||
{
|
||||
internal AVPPlayerStatus status;
|
||||
internal double currentTime;
|
||||
internal double currentDate;
|
||||
internal int selectedVideoTrack;
|
||||
internal int selectedAudioTrack;
|
||||
internal int selectedTextTrack;
|
||||
internal int bufferedTimeRangesCount;
|
||||
internal int seekableTimeRangesCount;
|
||||
internal int audioCaptureBufferedSamplesCount;
|
||||
}
|
||||
|
||||
internal enum AVPPlayerTextureFormat: int
|
||||
{
|
||||
Unknown,
|
||||
BGRA8,
|
||||
R8,
|
||||
RG8,
|
||||
BC1,
|
||||
BC3,
|
||||
BC4,
|
||||
BC5,
|
||||
BC7,
|
||||
BGR10A2,
|
||||
R16,
|
||||
RG16,
|
||||
BGR10XR,
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
internal struct AVPPlayerTexturePlane
|
||||
{
|
||||
internal IntPtr plane;
|
||||
internal int width;
|
||||
internal int height;
|
||||
internal AVPPlayerTextureFormat textureFormat;
|
||||
}
|
||||
|
||||
[Flags]
|
||||
internal enum AVPPlayerTextureFlags: int
|
||||
{
|
||||
None = 0,
|
||||
Flipped = 1 << 0,
|
||||
Linear = 1 << 1,
|
||||
Mipmapped = 1 << 2,
|
||||
}
|
||||
|
||||
internal enum AVPPlayerTextureYCbCrMatrix: int
|
||||
{
|
||||
Identity,
|
||||
ITU_R_601,
|
||||
ITU_R_709,
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
internal struct AVPPlayerTexture
|
||||
{
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst=2)]
|
||||
internal AVPPlayerTexturePlane[] planes;
|
||||
internal long itemTime;
|
||||
internal int frameCount;
|
||||
internal int planeCount;
|
||||
internal AVPPlayerTextureFlags flags;
|
||||
internal AVPPlayerTextureYCbCrMatrix YCbCrMatrix;
|
||||
};
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
internal struct AVPPlayerText
|
||||
{
|
||||
internal IntPtr buffer;
|
||||
internal long itemTime;
|
||||
internal int length;
|
||||
internal int sequence;
|
||||
};
|
||||
|
||||
internal enum AVPPlayerTrackType: int
|
||||
{
|
||||
Video,
|
||||
Audio,
|
||||
Text
|
||||
};
|
||||
|
||||
#if !UNITY_EDITOR && (UNITY_IOS || UNITY_TVOS)
|
||||
[DllImport(PluginName)]
|
||||
internal static extern void AVPPluginBootstrap();
|
||||
#endif
|
||||
|
||||
[DllImport(PluginName)]
|
||||
private static extern IntPtr AVPPluginGetVersionStringPointer();
|
||||
|
||||
internal static string GetPluginVersion()
|
||||
{
|
||||
return System.Runtime.InteropServices.Marshal.PtrToStringAnsi(AVPPluginGetVersionStringPointer());
|
||||
}
|
||||
|
||||
[DllImport(PluginName)]
|
||||
internal static extern IntPtr AVPPluginMakePlayer(Native.AVPPlayerSettings settings);
|
||||
|
||||
[DllImport(PluginName)]
|
||||
internal static extern IntPtr AVPPlayerRelease(IntPtr player);
|
||||
|
||||
[DllImport(PluginName)]
|
||||
internal static extern void AVPPlayerGetState(IntPtr player, ref AVPPlayerState state);
|
||||
|
||||
[DllImport(PluginName)]
|
||||
internal static extern void AVPPlayerSetFlags(IntPtr player, int flags);
|
||||
|
||||
[DllImport(PluginName)]
|
||||
internal static extern void AVPPlayerGetAssetInfo(IntPtr player, ref AVPPlayerAssetInfo info);
|
||||
|
||||
[DllImport(PluginName)]
|
||||
internal static extern void AVPPlayerGetVideoTrackInfo(IntPtr player, int index, ref AVPPlayerVideoTrackInfo info);
|
||||
|
||||
[DllImport(PluginName)]
|
||||
internal static extern void AVPPlayerGetAudioTrackInfo(IntPtr player, int index, ref AVPPlayerAudioTrackInfo info);
|
||||
|
||||
[DllImport(PluginName)]
|
||||
internal static extern void AVPPlayerGetTextTrackInfo(IntPtr player, int index, ref AVPPlayerTextTrackInfo info);
|
||||
|
||||
[DllImport(PluginName)]
|
||||
internal static extern void AVPPlayerGetBufferedTimeRanges(IntPtr player, AVPPlayerTimeRange[] ranges, int count);
|
||||
|
||||
[DllImport(PluginName)]
|
||||
internal static extern void AVPPlayerGetSeekableTimeRanges(IntPtr player, AVPPlayerTimeRange[] ranges, int count);
|
||||
|
||||
[DllImport(PluginName)]
|
||||
internal static extern void AVPPlayerGetTexture(IntPtr player, ref AVPPlayerTexture texture);
|
||||
|
||||
[DllImport(PluginName)]
|
||||
internal static extern void AVPPlayerGetText(IntPtr player, ref AVPPlayerText text);
|
||||
|
||||
[DllImport(PluginName)]
|
||||
internal static extern void AVPPlayerSetPlayerSettings(IntPtr player, AVPPlayerSettings settings);
|
||||
|
||||
[DllImport(PluginName)]
|
||||
[return: MarshalAs(UnmanagedType.U1)]
|
||||
internal static extern bool AVPPlayerOpenURL(IntPtr player, string url, string headers);
|
||||
|
||||
[DllImport(PluginName)]
|
||||
internal static extern void AVPPlayerClose(IntPtr player);
|
||||
|
||||
[DllImport(PluginName)]
|
||||
internal static extern int AVPPlayerGetAudio(IntPtr player, float[] buffer, int length);
|
||||
|
||||
[DllImport(PluginName)]
|
||||
internal static extern void AVPPlayerSetRate(IntPtr player, float rate);
|
||||
|
||||
[DllImport(PluginName)]
|
||||
internal static extern void AVPPlayerSetVolume(IntPtr player, float volume);
|
||||
|
||||
[DllImport(PluginName)]
|
||||
internal static extern void AVPPlayerSetExternalPlaybackVideoGravity(IntPtr player, AVPPlayerExternalPlaybackVideoGravity gravity);
|
||||
|
||||
[DllImport(PluginName)]
|
||||
internal static extern void AVPPlayerSeek(IntPtr player, double toTime, double toleranceBefore, double toleranceAfter);
|
||||
|
||||
[DllImport(PluginName)]
|
||||
internal static extern void AVPPlayerSetKeyServerAuthToken(IntPtr player, string token);
|
||||
|
||||
[DllImport(PluginName)]
|
||||
internal static extern void AVPPlayerSetKeyServerURL(IntPtr player, string url);
|
||||
|
||||
[DllImport(PluginName)]
|
||||
internal static extern void AVPPlayerSetDecryptionKey(IntPtr player, byte[] key, int length);
|
||||
|
||||
[DllImport(PluginName)]
|
||||
[return: MarshalAs(UnmanagedType.I1)]
|
||||
internal static extern bool AVPPlayerSetTrack(IntPtr player, AVPPlayerTrackType type, int index);
|
||||
|
||||
#if !UNITY_EDITOR && UNITY_IOS
|
||||
public struct MediaCachingOptions
|
||||
{
|
||||
public double minimumRequiredBitRate;
|
||||
public float minimumRequiredResolution_width;
|
||||
public float minimumRequiredResolution_height;
|
||||
public string title;
|
||||
public IntPtr artwork;
|
||||
public int artworkLength;
|
||||
}
|
||||
|
||||
[DllImport(PluginName)]
|
||||
public static extern void AVPPluginCacheMediaForURL(string url, string headers, MediaCachingOptions options);
|
||||
|
||||
[DllImport(PluginName)]
|
||||
public static extern void AVPPluginCancelDownloadOfMediaForURL(string url);
|
||||
|
||||
[DllImport(PluginName)]
|
||||
public static extern void AVPPluginRemoveCachedMediaForURL(string url);
|
||||
|
||||
[DllImport(PluginName)]
|
||||
public static extern int AVPPluginGetCachedMediaStatusForURL(string url, ref float progress);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0bf374b5848b649e6b3840fe1dc03cd2
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3f68628a1ef6349648e502d1c66b5114
|
||||
timeCreated: 1547113004
|
||||
licenseType: Free
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,226 @@
|
||||
//-----------------------------------------------------------------------------
|
||||
// Copyright 2015-2022 RenderHeads Ltd. All rights reserved.
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
#if UNITY_2017_2_OR_NEWER && (UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX || (!UNITY_EDITOR && (UNITY_IOS || UNITY_TVOS)))
|
||||
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using UnityEngine;
|
||||
|
||||
namespace RenderHeads.Media.AVProVideo
|
||||
{
|
||||
internal static class AppleMediaPlayerExtensions
|
||||
{
|
||||
// AVPPlayerStatus
|
||||
|
||||
internal static bool IsReadyToPlay(this AppleMediaPlayer.Native.AVPPlayerStatus status)
|
||||
{
|
||||
return (status & AppleMediaPlayer.Native.AVPPlayerStatus.ReadyToPlay) == AppleMediaPlayer.Native.AVPPlayerStatus.ReadyToPlay;
|
||||
}
|
||||
|
||||
internal static bool IsPlaying(this AppleMediaPlayer.Native.AVPPlayerStatus status)
|
||||
{
|
||||
return (status & AppleMediaPlayer.Native.AVPPlayerStatus.Playing) == AppleMediaPlayer.Native.AVPPlayerStatus.Playing;
|
||||
}
|
||||
|
||||
internal static bool IsPaused(this AppleMediaPlayer.Native.AVPPlayerStatus status)
|
||||
{
|
||||
return (status & AppleMediaPlayer.Native.AVPPlayerStatus.Paused) == AppleMediaPlayer.Native.AVPPlayerStatus.Paused;
|
||||
}
|
||||
|
||||
internal static bool IsFinished(this AppleMediaPlayer.Native.AVPPlayerStatus status)
|
||||
{
|
||||
return (status & AppleMediaPlayer.Native.AVPPlayerStatus.Finished) == AppleMediaPlayer.Native.AVPPlayerStatus.Finished;
|
||||
}
|
||||
|
||||
internal static bool IsSeeking(this AppleMediaPlayer.Native.AVPPlayerStatus status)
|
||||
{
|
||||
return (status & AppleMediaPlayer.Native.AVPPlayerStatus.Seeking) == AppleMediaPlayer.Native.AVPPlayerStatus.Seeking;
|
||||
}
|
||||
|
||||
internal static bool IsBuffering(this AppleMediaPlayer.Native.AVPPlayerStatus status)
|
||||
{
|
||||
return (status & AppleMediaPlayer.Native.AVPPlayerStatus.Buffering) == AppleMediaPlayer.Native.AVPPlayerStatus.Buffering;
|
||||
}
|
||||
|
||||
internal static bool IsStalled(this AppleMediaPlayer.Native.AVPPlayerStatus status)
|
||||
{
|
||||
return (status & AppleMediaPlayer.Native.AVPPlayerStatus.Stalled) == AppleMediaPlayer.Native.AVPPlayerStatus.Stalled;
|
||||
}
|
||||
|
||||
internal static bool IsExternalPlaybackActive(this AppleMediaPlayer.Native.AVPPlayerStatus status)
|
||||
{
|
||||
return (status & AppleMediaPlayer.Native.AVPPlayerStatus.ExternalPlaybackActive) == AppleMediaPlayer.Native.AVPPlayerStatus.ExternalPlaybackActive;
|
||||
}
|
||||
|
||||
internal static bool IsCached(this AppleMediaPlayer.Native.AVPPlayerStatus status)
|
||||
{
|
||||
return (status & AppleMediaPlayer.Native.AVPPlayerStatus.Cached) == AppleMediaPlayer.Native.AVPPlayerStatus.Cached;
|
||||
}
|
||||
|
||||
internal static bool HasFinishedSeeking(this AppleMediaPlayer.Native.AVPPlayerStatus status)
|
||||
{
|
||||
return (status & AppleMediaPlayer.Native.AVPPlayerStatus.FinishedSeeking) == AppleMediaPlayer.Native.AVPPlayerStatus.FinishedSeeking;
|
||||
}
|
||||
|
||||
internal static bool HasUpdatedAssetInfo(this AppleMediaPlayer.Native.AVPPlayerStatus status)
|
||||
{
|
||||
return (status & AppleMediaPlayer.Native.AVPPlayerStatus.UpdatedAssetInfo) == AppleMediaPlayer.Native.AVPPlayerStatus.UpdatedAssetInfo;
|
||||
}
|
||||
|
||||
internal static bool HasUpdatedTexture(this AppleMediaPlayer.Native.AVPPlayerStatus status)
|
||||
{
|
||||
return (status & AppleMediaPlayer.Native.AVPPlayerStatus.UpdatedTexture) == AppleMediaPlayer.Native.AVPPlayerStatus.UpdatedTexture;
|
||||
}
|
||||
|
||||
internal static bool HasUpdatedBufferedTimeRanges(this AppleMediaPlayer.Native.AVPPlayerStatus status)
|
||||
{
|
||||
return (status & AppleMediaPlayer.Native.AVPPlayerStatus.UpdatedBufferedTimeRanges) == AppleMediaPlayer.Native.AVPPlayerStatus.UpdatedBufferedTimeRanges;
|
||||
}
|
||||
|
||||
internal static bool HasUpdatedSeekableTimeRanges(this AppleMediaPlayer.Native.AVPPlayerStatus status)
|
||||
{
|
||||
return (status & AppleMediaPlayer.Native.AVPPlayerStatus.UpdatedSeekableTimeRanges) == AppleMediaPlayer.Native.AVPPlayerStatus.UpdatedSeekableTimeRanges;
|
||||
}
|
||||
|
||||
internal static bool HasUpdatedText(this AppleMediaPlayer.Native.AVPPlayerStatus status)
|
||||
{
|
||||
return (status & AppleMediaPlayer.Native.AVPPlayerStatus.UpdatedText) == AppleMediaPlayer.Native.AVPPlayerStatus.UpdatedText;
|
||||
}
|
||||
|
||||
internal static bool HasVideo(this AppleMediaPlayer.Native.AVPPlayerStatus status)
|
||||
{
|
||||
return (status & AppleMediaPlayer.Native.AVPPlayerStatus.HasVideo) == AppleMediaPlayer.Native.AVPPlayerStatus.HasVideo;
|
||||
}
|
||||
|
||||
internal static bool HasAudio(this AppleMediaPlayer.Native.AVPPlayerStatus status)
|
||||
{
|
||||
return (status & AppleMediaPlayer.Native.AVPPlayerStatus.HasAudio) == AppleMediaPlayer.Native.AVPPlayerStatus.HasAudio;
|
||||
}
|
||||
|
||||
internal static bool HasText(this AppleMediaPlayer.Native.AVPPlayerStatus status)
|
||||
{
|
||||
return (status & AppleMediaPlayer.Native.AVPPlayerStatus.HasText) == AppleMediaPlayer.Native.AVPPlayerStatus.HasText;
|
||||
}
|
||||
|
||||
internal static bool HasMetadata(this AppleMediaPlayer.Native.AVPPlayerStatus status)
|
||||
{
|
||||
return (status & AppleMediaPlayer.Native.AVPPlayerStatus.HasMetadata) == AppleMediaPlayer.Native.AVPPlayerStatus.HasMetadata;
|
||||
}
|
||||
|
||||
internal static bool HasFailed(this AppleMediaPlayer.Native.AVPPlayerStatus status)
|
||||
{
|
||||
return (status & AppleMediaPlayer.Native.AVPPlayerStatus.Failed) == AppleMediaPlayer.Native.AVPPlayerStatus.Failed;
|
||||
}
|
||||
|
||||
// AVPPlayerFlags
|
||||
|
||||
internal static bool IsLooping(this AppleMediaPlayer.Native.AVPPlayerFlags flags)
|
||||
{
|
||||
return (flags & AppleMediaPlayer.Native.AVPPlayerFlags.Looping) == AppleMediaPlayer.Native.AVPPlayerFlags.Looping;
|
||||
}
|
||||
|
||||
internal static AppleMediaPlayer.Native.AVPPlayerFlags SetLooping(this AppleMediaPlayer.Native.AVPPlayerFlags flags, bool b)
|
||||
{
|
||||
if (flags.IsLooping() ^ b)
|
||||
{
|
||||
flags = (b ? flags | AppleMediaPlayer.Native.AVPPlayerFlags.Looping
|
||||
: flags & ~AppleMediaPlayer.Native.AVPPlayerFlags.Looping) | AppleMediaPlayer.Native.AVPPlayerFlags.Dirty;
|
||||
}
|
||||
return flags;
|
||||
}
|
||||
|
||||
internal static bool IsMuted(this AppleMediaPlayer.Native.AVPPlayerFlags flags)
|
||||
{
|
||||
return (flags & AppleMediaPlayer.Native.AVPPlayerFlags.Muted) == AppleMediaPlayer.Native.AVPPlayerFlags.Muted;
|
||||
}
|
||||
|
||||
internal static AppleMediaPlayer.Native.AVPPlayerFlags SetMuted(this AppleMediaPlayer.Native.AVPPlayerFlags flags, bool b)
|
||||
{
|
||||
if (flags.IsMuted() ^ b)
|
||||
{
|
||||
flags = (b ? flags | AppleMediaPlayer.Native.AVPPlayerFlags.Muted
|
||||
: flags & ~AppleMediaPlayer.Native.AVPPlayerFlags.Muted) | AppleMediaPlayer.Native.AVPPlayerFlags.Dirty;
|
||||
}
|
||||
return flags;
|
||||
}
|
||||
|
||||
internal static bool IsExternalPlaybackAllowed(this AppleMediaPlayer.Native.AVPPlayerFlags flags)
|
||||
{
|
||||
return (flags & AppleMediaPlayer.Native.AVPPlayerFlags.AllowExternalPlayback) == AppleMediaPlayer.Native.AVPPlayerFlags.AllowExternalPlayback;
|
||||
}
|
||||
|
||||
internal static AppleMediaPlayer.Native.AVPPlayerFlags SetAllowExternalPlayback(this AppleMediaPlayer.Native.AVPPlayerFlags flags, bool b)
|
||||
{
|
||||
if (flags.IsExternalPlaybackAllowed() ^ b)
|
||||
{
|
||||
flags = (b ? flags | AppleMediaPlayer.Native.AVPPlayerFlags.AllowExternalPlayback
|
||||
: flags & ~AppleMediaPlayer.Native.AVPPlayerFlags.AllowExternalPlayback) | AppleMediaPlayer.Native.AVPPlayerFlags.Dirty;
|
||||
}
|
||||
return flags;
|
||||
}
|
||||
|
||||
internal static bool ResumePlayback(this AppleMediaPlayer.Native.AVPPlayerFlags flags)
|
||||
{
|
||||
return (flags & AppleMediaPlayer.Native.AVPPlayerFlags.ResumePlayback) == AppleMediaPlayer.Native.AVPPlayerFlags.ResumePlayback;
|
||||
}
|
||||
|
||||
internal static AppleMediaPlayer.Native.AVPPlayerFlags SetResumePlayback(this AppleMediaPlayer.Native.AVPPlayerFlags flags, bool b)
|
||||
{
|
||||
if (flags.ResumePlayback() ^ b)
|
||||
{
|
||||
flags = (b ? flags | AppleMediaPlayer.Native.AVPPlayerFlags.ResumePlayback
|
||||
: flags & ~AppleMediaPlayer.Native.AVPPlayerFlags.ResumePlayback) | AppleMediaPlayer.Native.AVPPlayerFlags.Dirty;
|
||||
}
|
||||
return flags;
|
||||
}
|
||||
|
||||
internal static bool IsDirty(this AppleMediaPlayer.Native.AVPPlayerFlags flags)
|
||||
{
|
||||
return (flags & AppleMediaPlayer.Native.AVPPlayerFlags.Dirty) == AppleMediaPlayer.Native.AVPPlayerFlags.Dirty;
|
||||
}
|
||||
|
||||
internal static AppleMediaPlayer.Native.AVPPlayerFlags SetDirty(this AppleMediaPlayer.Native.AVPPlayerFlags flags, bool b)
|
||||
{
|
||||
if (flags.IsDirty() ^ b)
|
||||
{
|
||||
flags = b ? flags | AppleMediaPlayer.Native.AVPPlayerFlags.Dirty : flags & ~AppleMediaPlayer.Native.AVPPlayerFlags.Dirty;
|
||||
}
|
||||
return flags;
|
||||
}
|
||||
|
||||
// MARK: AVPPlayerAssetFlags
|
||||
|
||||
internal static bool IsCompatibleWithAirPlay(this AppleMediaPlayer.Native.AVPPlayerAssetFlags flags)
|
||||
{
|
||||
return (flags & AppleMediaPlayer.Native.AVPPlayerAssetFlags.CompatibleWithAirPlay) == AppleMediaPlayer.Native.AVPPlayerAssetFlags.CompatibleWithAirPlay;
|
||||
}
|
||||
|
||||
// MARK: AVPPlayerTrackFlags
|
||||
|
||||
internal static bool IsDefault(this AppleMediaPlayer.Native.AVPPlayerTrackFlags flags)
|
||||
{
|
||||
return (flags & AppleMediaPlayer.Native.AVPPlayerTrackFlags.Default) == AppleMediaPlayer.Native.AVPPlayerTrackFlags.Default;
|
||||
}
|
||||
|
||||
// AVPPlayerTextureFlags
|
||||
|
||||
internal static bool IsFlipped(this AppleMediaPlayer.Native.AVPPlayerTextureFlags flags)
|
||||
{
|
||||
return (flags & AppleMediaPlayer.Native.AVPPlayerTextureFlags.Flipped) == AppleMediaPlayer.Native.AVPPlayerTextureFlags.Flipped;
|
||||
}
|
||||
|
||||
internal static bool IsLinear(this AppleMediaPlayer.Native.AVPPlayerTextureFlags flags)
|
||||
{
|
||||
return (flags & AppleMediaPlayer.Native.AVPPlayerTextureFlags.Linear) == AppleMediaPlayer.Native.AVPPlayerTextureFlags.Linear;
|
||||
}
|
||||
|
||||
internal static bool IsMipmapped(this AppleMediaPlayer.Native.AVPPlayerTextureFlags flags)
|
||||
{
|
||||
return (flags & AppleMediaPlayer.Native.AVPPlayerTextureFlags.Mipmapped) == AppleMediaPlayer.Native.AVPPlayerTextureFlags.Mipmapped;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e27ea5523e11f44c09e8d368eb1f2983
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,347 @@
|
||||
using System;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Copyright 2015-2021 RenderHeads Ltd. All rights reserved.
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
namespace RenderHeads.Media.AVProVideo
|
||||
{
|
||||
/// <summary>
|
||||
/// This media player fakes video playback for platforms that aren't supported
|
||||
/// </summary>
|
||||
public sealed partial class NullMediaPlayer : BaseMediaPlayer
|
||||
{
|
||||
private bool _isPlaying = false;
|
||||
private bool _isPaused = false;
|
||||
private double _currentTime = 0.0;
|
||||
// private bool _audioMuted = false;
|
||||
private float _volume = 0.0f;
|
||||
private float _playbackRate = 1.0f;
|
||||
private bool _bLoop;
|
||||
|
||||
private int _Width = 256;
|
||||
private int _height = 256;
|
||||
private Texture2D _texture;
|
||||
private Texture2D _texture_AVPro;
|
||||
private Texture2D _texture_AVPro1;
|
||||
private float _fakeFlipTime;
|
||||
private int _frameCount;
|
||||
|
||||
private const float FrameRate = 10f;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string GetVersion()
|
||||
{
|
||||
return "0.0.0";
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string GetExpectedVersion()
|
||||
{
|
||||
return GetVersion();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool OpenMedia(string path, long offset, string httpHeader, MediaHints mediaHints, int forceFileFormat = 0, bool startWithHighestBitrate = false)
|
||||
{
|
||||
_texture_AVPro = (Texture2D)Resources.Load("Textures/AVProVideo-NullPlayer-Frame0");
|
||||
_texture_AVPro1 = (Texture2D)Resources.Load("Textures/AVProVideo-NullPlayer-Frame1");
|
||||
|
||||
if( _texture_AVPro )
|
||||
{
|
||||
_Width = _texture_AVPro.width;
|
||||
_height = _texture_AVPro.height;
|
||||
}
|
||||
|
||||
_texture = _texture_AVPro;
|
||||
|
||||
_fakeFlipTime = 0.0f;
|
||||
_frameCount = 0;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void CloseMedia()
|
||||
{
|
||||
_frameCount = 0;
|
||||
Resources.UnloadAsset(_texture_AVPro);
|
||||
Resources.UnloadAsset(_texture_AVPro1);
|
||||
|
||||
base.CloseMedia();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void SetLooping( bool bLooping )
|
||||
{
|
||||
_bLoop = bLooping;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool IsLooping()
|
||||
{
|
||||
return _bLoop;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool HasMetaData()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool CanPlay()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool HasAudio()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool HasVideo()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Play()
|
||||
{
|
||||
_isPlaying = true;
|
||||
_isPaused = false;
|
||||
_fakeFlipTime = 0.0f;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Pause()
|
||||
{
|
||||
_isPlaying = false;
|
||||
_isPaused = true;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Stop()
|
||||
{
|
||||
_isPlaying = false;
|
||||
_isPaused = false;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool IsSeeking()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
/// <inheritdoc/>
|
||||
public override bool IsPlaying()
|
||||
{
|
||||
return _isPlaying;
|
||||
}
|
||||
/// <inheritdoc/>
|
||||
public override bool IsPaused()
|
||||
{
|
||||
return _isPaused;
|
||||
}
|
||||
/// <inheritdoc/>
|
||||
public override bool IsFinished()
|
||||
{
|
||||
return _isPlaying && (_currentTime >= GetDuration());
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool IsBuffering()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override double GetDuration()
|
||||
{
|
||||
return 10.0;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override int GetVideoWidth()
|
||||
{
|
||||
return _Width;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override int GetVideoHeight()
|
||||
{
|
||||
return _height;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override float GetVideoDisplayRate()
|
||||
{
|
||||
return FrameRate;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override Texture GetTexture( int index )
|
||||
{
|
||||
// return _texture ? _texture : Texture2D.whiteTexture;
|
||||
return _texture;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override int GetTextureFrameCount()
|
||||
{
|
||||
return _frameCount;
|
||||
}
|
||||
|
||||
internal override StereoPacking InternalGetTextureStereoPacking()
|
||||
{
|
||||
return StereoPacking.Unknown;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool RequiresVerticalFlip()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Seek(double time)
|
||||
{
|
||||
_currentTime = time;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void SeekFast(double time)
|
||||
{
|
||||
_currentTime = time;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override double GetCurrentTime()
|
||||
{
|
||||
return _currentTime;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void SetPlaybackRate(float rate)
|
||||
{
|
||||
_playbackRate = rate;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override float GetPlaybackRate()
|
||||
{
|
||||
return _playbackRate;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void MuteAudio(bool bMuted)
|
||||
{
|
||||
// _audioMuted = bMuted;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool IsMuted()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void SetVolume(float volume)
|
||||
{
|
||||
_volume = volume;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override float GetVolume()
|
||||
{
|
||||
return _volume;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override float GetVideoFrameRate()
|
||||
{
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Update()
|
||||
{
|
||||
UpdateSubtitles();
|
||||
|
||||
if (_isPlaying)
|
||||
{
|
||||
_currentTime += Time.deltaTime;
|
||||
if (_currentTime >= GetDuration())
|
||||
{
|
||||
_currentTime = GetDuration();
|
||||
if( _bLoop )
|
||||
{
|
||||
Rewind();
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
_fakeFlipTime += Time.deltaTime;
|
||||
if( _fakeFlipTime >= (1.0 / FrameRate))
|
||||
{
|
||||
_fakeFlipTime = 0.0f;
|
||||
_texture = ( _texture == _texture_AVPro ) ? _texture_AVPro1 : _texture_AVPro;
|
||||
_frameCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Render()
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Dispose()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public sealed partial class NullMediaPlayer : BaseMediaPlayer
|
||||
{
|
||||
internal override bool InternalSetActiveTrack(TrackType trackType, int trackUid)
|
||||
{
|
||||
// Set the active text track using the unique identifier
|
||||
// Or disable all text tracks if < 0
|
||||
return false;
|
||||
}
|
||||
|
||||
internal override bool InternalIsChangedTracks(TrackType trackType)
|
||||
{
|
||||
// Has the tracks changed since the last frame 'tick'
|
||||
return false;
|
||||
}
|
||||
|
||||
internal override int InternalGetTrackCount(TrackType trackType)
|
||||
{
|
||||
// Return number of text tracks
|
||||
return 0;
|
||||
}
|
||||
|
||||
internal override TrackBase InternalGetTrackInfo(TrackType trackType, int index, ref bool isActiveTrack)
|
||||
{
|
||||
// Get information about the specific track at index, range is [0...InternalGetTextTrackCount)
|
||||
return null;
|
||||
}
|
||||
|
||||
internal override bool InternalIsChangedTextCue()
|
||||
{
|
||||
// Has the text cue changed since the last frame 'tick'
|
||||
return false;
|
||||
}
|
||||
|
||||
internal override string InternalGetCurrentTextCue()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 478671181ab1c9b42be924da77d7fcbe
|
||||
timeCreated: 1438703159
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,926 @@
|
||||
//#define AVPRO_WEBGL_USE_RENDERTEXTURE
|
||||
// NOTE: We only allow this script to compile in editor so we can easily check for compilation issues
|
||||
#if (UNITY_EDITOR || UNITY_WEBGL)
|
||||
using UnityEngine;
|
||||
using System;
|
||||
using System.Text;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Copyright 2015-2021 RenderHeads Ltd. All rights reserved.
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
namespace RenderHeads.Media.AVProVideo
|
||||
{
|
||||
/// <summary>
|
||||
/// WebGL implementation of BaseMediaPlayer
|
||||
/// </summary>
|
||||
public sealed class WebGLMediaPlayer : BaseMediaPlayer
|
||||
{
|
||||
//private enum AVPPlayerStatus
|
||||
//{
|
||||
// Unknown,
|
||||
// ReadyToPlay,
|
||||
// Playing,
|
||||
// Finished,
|
||||
// Seeking,
|
||||
// Failed
|
||||
//}
|
||||
|
||||
[DllImport("__Internal")]
|
||||
private static extern bool AVPPlayerInsertVideoElement(string path, int[] idValues, int externalLibrary);
|
||||
|
||||
[DllImport("__Internal")]
|
||||
private static extern int AVPPlayerWidth(int player);
|
||||
|
||||
[DllImport("__Internal")]
|
||||
private static extern int AVPPlayerHeight(int player);
|
||||
|
||||
[DllImport("__Internal")]
|
||||
private static extern int AVPPlayerGetLastError(int player);
|
||||
|
||||
[DllImport("__Internal")]
|
||||
private static extern int AVPPlayerGetVideoTrackCount(int player);
|
||||
|
||||
[DllImport("__Internal")]
|
||||
private static extern int AVPPlayerGetAudioTrackCount(int player);
|
||||
|
||||
[DllImport("__Internal")]
|
||||
private static extern int AVPPlayerGetTextTrackCount(int player);
|
||||
|
||||
[DllImport("__Internal")]
|
||||
private static extern bool AVPPlayerSetActiveVideoTrack(int player, int trackId);
|
||||
|
||||
[DllImport("__Internal")]
|
||||
private static extern bool AVPPlayerSetActiveAudioTrack(int player, int trackId);
|
||||
|
||||
[DllImport("__Internal")]
|
||||
private static extern bool AVPPlayerSetActiveTextTrack(int player, int trackId);
|
||||
|
||||
[DllImport("__Internal")]
|
||||
private static extern void AVPPlayerClose(int player);
|
||||
|
||||
[DllImport("__Internal")]
|
||||
private static extern bool AVPPlayerReady(int player);
|
||||
|
||||
[DllImport("__Internal")]
|
||||
private static extern void AVPPlayerSetLooping(int player, bool loop);
|
||||
|
||||
[DllImport("__Internal")]
|
||||
private static extern bool AVPPlayerIsLooping(int player);
|
||||
|
||||
[DllImport("__Internal")]
|
||||
private static extern bool AVPPlayerIsSeeking(int player);
|
||||
|
||||
[DllImport("__Internal")]
|
||||
private static extern bool AVPPlayerIsPlaying(int player);
|
||||
|
||||
[DllImport("__Internal")]
|
||||
private static extern bool AVPPlayerIsPaused(int player);
|
||||
|
||||
[DllImport("__Internal")]
|
||||
private static extern bool AVPPlayerIsFinished(int player);
|
||||
|
||||
[DllImport("__Internal")]
|
||||
private static extern bool AVPPlayerIsBuffering(int player);
|
||||
|
||||
[DllImport("__Internal")]
|
||||
private static extern bool AVPPlayerIsPlaybackStalled(int player);
|
||||
|
||||
[DllImport("__Internal")]
|
||||
private static extern bool AVPPlayerPlay(int player);
|
||||
|
||||
[DllImport("__Internal")]
|
||||
private static extern void AVPPlayerPause(int player);
|
||||
|
||||
[DllImport("__Internal")]
|
||||
private static extern void AVPPlayerSeekToTime(int player, double time, bool fast);
|
||||
|
||||
[DllImport("__Internal")]
|
||||
private static extern double AVPPlayerGetCurrentTime(int player);
|
||||
|
||||
[DllImport("__Internal")]
|
||||
private static extern float AVPPlayerGetDuration(int player);
|
||||
|
||||
[DllImport("__Internal")]
|
||||
private static extern float AVPPlayerGetPlaybackRate(int player);
|
||||
|
||||
[DllImport("__Internal")]
|
||||
private static extern void AVPPlayerSetPlaybackRate(int player, float rate);
|
||||
|
||||
[DllImport("__Internal")]
|
||||
private static extern void AVPPlayerSetMuted(int player, bool muted);
|
||||
|
||||
[DllImport("__Internal")]
|
||||
private static extern bool AVPPlayerIsMuted(int player);
|
||||
|
||||
[DllImport("__Internal")]
|
||||
private static extern float AVPPlayerGetVolume(int player);
|
||||
|
||||
[DllImport("__Internal")]
|
||||
private static extern void AVPPlayerSetVolume(int player, float volume);
|
||||
|
||||
[DllImport("__Internal")]
|
||||
private static extern bool AVPPlayerHasVideo(int player);
|
||||
|
||||
[DllImport("__Internal")]
|
||||
private static extern bool AVPPlayerHasAudio(int player);
|
||||
|
||||
[DllImport("__Internal")]
|
||||
private static extern void AVPPlayerCreateVideoTexture(int textureId);
|
||||
|
||||
[DllImport("__Internal")]
|
||||
private static extern void AVPPlayerDestroyVideoTexture(int textureId);
|
||||
|
||||
[DllImport("__Internal")]
|
||||
private static extern void AVPPlayerFetchVideoTexture(int player, IntPtr texture, bool init);
|
||||
|
||||
[DllImport("__Internal")]
|
||||
private static extern int AVPPlayerGetDecodedFrameCount(int player);
|
||||
|
||||
[DllImport("__Internal")]
|
||||
private static extern bool AVPPlayerSupportedDecodedFrameCount(int player);
|
||||
|
||||
[DllImport("__Internal")]
|
||||
private static extern bool AVPPlayerHasMetadata(int player);
|
||||
|
||||
[DllImport("__Internal")]
|
||||
private static extern int AVPPlayerUpdatePlayerIndex(int id);
|
||||
|
||||
[DllImport("__Internal")]
|
||||
private static extern int AVPPlayerGetNumBufferedTimeRanges(int id);
|
||||
|
||||
[DllImport("__Internal")]
|
||||
private static extern double AVPPlayerGetTimeRangeStart(int id, int timeRangeIndex);
|
||||
[DllImport("__Internal")]
|
||||
private static extern double AVPPlayerGetTimeRangeEnd(int id, int timeRangeIndex);
|
||||
|
||||
[DllImport("__Internal")]
|
||||
private static extern string AVPPlayerGetVideoTrackName(int player, int trackIndex);
|
||||
|
||||
[DllImport("__Internal")]
|
||||
private static extern string AVPPlayerGetAudioTrackName(int player, int trackIndex);
|
||||
|
||||
[DllImport("__Internal")]
|
||||
private static extern string AVPPlayerGetTextTrackName(int player, int trackIndex);
|
||||
|
||||
[DllImport("__Internal")]
|
||||
private static extern string AVPPlayerGetVideoTrackLanguage(int player, int trackIndex);
|
||||
|
||||
[DllImport("__Internal")]
|
||||
private static extern string AVPPlayerGetAudioTrackLanguage(int player, int trackIndex);
|
||||
|
||||
[DllImport("__Internal")]
|
||||
private static extern string AVPPlayerGetTextTrackLanguage(int player, int trackIndex);
|
||||
|
||||
[DllImport("__Internal")]
|
||||
private static extern bool AVPPlayerIsVideoTrackActive(int player, int trackIndex);
|
||||
|
||||
[DllImport("__Internal")]
|
||||
private static extern bool AVPPlayerIsAudioTrackActive(int player, int trackIndex);
|
||||
|
||||
[DllImport("__Internal")]
|
||||
private static extern bool AVPPlayerIsTextTrackActive(int player, int trackIndex);
|
||||
|
||||
private WebGL.ExternalLibrary _externalLibrary = WebGL.ExternalLibrary.None;
|
||||
private int _playerIndex = -1;
|
||||
private int _playerID = -1;
|
||||
|
||||
#if AVPRO_WEBGL_USE_RENDERTEXTURE
|
||||
private RenderTexture _texture = null;
|
||||
#else
|
||||
private Texture2D _texture = null;
|
||||
#endif
|
||||
|
||||
private int _width = 0;
|
||||
private int _height = 0;
|
||||
private int _cachedVideoTrackCount = 0;
|
||||
private int _cachedAudioTrackCount = 0;
|
||||
private int _cachedTextTrackCount = 0;
|
||||
private bool _isDirtyVideoTracks = false;
|
||||
private bool _isDirtyAudioTracks = false;
|
||||
private bool _isDirtyTextTracks = false;
|
||||
private bool _useTextureMips = false;
|
||||
private System.IntPtr _cachedTextureNativePtr = System.IntPtr.Zero;
|
||||
|
||||
private static bool _isWebGL1 = false;
|
||||
|
||||
public static bool InitialisePlatform()
|
||||
{
|
||||
_isWebGL1 = (SystemInfo.graphicsDeviceType == UnityEngine.Rendering.GraphicsDeviceType.OpenGLES2);
|
||||
return true;
|
||||
}
|
||||
|
||||
public WebGLMediaPlayer(MediaPlayer.OptionsWebGL options)
|
||||
{
|
||||
SetOptions(options);
|
||||
}
|
||||
|
||||
public void SetOptions(MediaPlayer.OptionsWebGL options)
|
||||
{
|
||||
_externalLibrary = options.externalLibrary;
|
||||
_useTextureMips = options.useTextureMips;
|
||||
}
|
||||
|
||||
public override string GetVersion()
|
||||
{
|
||||
return "2.1.6";
|
||||
}
|
||||
|
||||
public override string GetExpectedVersion()
|
||||
{
|
||||
return GetVersion();
|
||||
}
|
||||
|
||||
public override bool OpenMedia(string path, long offset, string httpHeader, MediaHints mediaHints, int forceFileFormat = 0, bool startWithHighestBitrate = false)
|
||||
{
|
||||
bool result = false;
|
||||
|
||||
if (path.StartsWith("http://") ||
|
||||
path.StartsWith("https://") ||
|
||||
path.StartsWith("file://") ||
|
||||
path.StartsWith("blob:") ||
|
||||
path.StartsWith("chrome-extension://"))
|
||||
{
|
||||
int[] idValues = new int[2];
|
||||
idValues[0] = -1;
|
||||
AVPPlayerInsertVideoElement(path, idValues, (int)_externalLibrary);
|
||||
{
|
||||
int playerIndex = idValues[0];
|
||||
_playerID = idValues[1];
|
||||
|
||||
if (playerIndex > -1)
|
||||
{
|
||||
_playerIndex = playerIndex;
|
||||
_mediaHints = mediaHints;
|
||||
result = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogError("[AVProVideo] Unknown URL protocol");
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public override void CloseMedia()
|
||||
{
|
||||
if (_playerIndex != -1)
|
||||
{
|
||||
Pause();
|
||||
|
||||
_width = 0;
|
||||
_height = 0;
|
||||
_cachedVideoTrackCount = 0;
|
||||
_cachedAudioTrackCount = 0;
|
||||
_cachedTextTrackCount = 0;
|
||||
_isDirtyVideoTracks = false;
|
||||
_isDirtyAudioTracks = false;
|
||||
_isDirtyTextTracks = false;
|
||||
|
||||
AVPPlayerClose(_playerIndex);
|
||||
|
||||
if (_texture != null)
|
||||
{
|
||||
DestroyTexture();
|
||||
}
|
||||
|
||||
_playerIndex = -1;
|
||||
_playerID = -1;
|
||||
|
||||
base.CloseMedia();
|
||||
}
|
||||
}
|
||||
|
||||
public override bool IsLooping()
|
||||
{
|
||||
//Debug.Assert(_player != -1, "no player IsLooping");
|
||||
bool result = false;
|
||||
|
||||
if (_playerIndex != -1)
|
||||
{
|
||||
result = AVPPlayerIsLooping(_playerIndex);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public override void SetLooping(bool looping)
|
||||
{
|
||||
//Debug.Assert(_playerIndex != -1, "no player SetLooping");
|
||||
|
||||
AVPPlayerSetLooping(_playerIndex, looping);
|
||||
}
|
||||
|
||||
public override bool HasAudio()
|
||||
{
|
||||
//Debug.Assert(_player != -1, "no player HasAudio");
|
||||
bool result = false;
|
||||
|
||||
if (_playerIndex != -1)
|
||||
{
|
||||
result = AVPPlayerHasAudio(_playerIndex);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public override bool HasVideo()
|
||||
{
|
||||
//Debug.Assert(_player != -1, "no player HasVideo");
|
||||
bool result = false;
|
||||
|
||||
if (_playerIndex != -1)
|
||||
{
|
||||
result = AVPPlayerHasVideo(_playerIndex);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public override bool HasMetaData()
|
||||
{
|
||||
//Debug.Assert(_player != -1, "no player HasMetaData");
|
||||
bool result = false;
|
||||
|
||||
if (_playerIndex != -1)
|
||||
{
|
||||
result = AVPPlayerHasMetadata(_playerIndex);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public override bool CanPlay()
|
||||
{
|
||||
//Debug.Assert(_player != -1, "no player CanPlay");
|
||||
bool result = false;
|
||||
|
||||
if (_playerIndex != -1)
|
||||
{
|
||||
result = AVPPlayerReady(_playerIndex);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public override void Play()
|
||||
{
|
||||
Debug.Assert(_playerIndex != -1, "no player Play");
|
||||
|
||||
if (!AVPPlayerPlay(_playerIndex))
|
||||
{
|
||||
Debug.LogWarning("[AVProVideo] Browser permission prevented video playback");
|
||||
}
|
||||
}
|
||||
|
||||
public override void Pause()
|
||||
{
|
||||
Debug.Assert(_playerIndex != -1, "no player Pause");
|
||||
|
||||
AVPPlayerPause(_playerIndex);
|
||||
}
|
||||
|
||||
public override void Stop()
|
||||
{
|
||||
Debug.Assert(_playerIndex != -1, "no player Stop");
|
||||
|
||||
AVPPlayerPause(_playerIndex);
|
||||
}
|
||||
|
||||
public override void Seek(double time)
|
||||
{
|
||||
Debug.Assert(_playerIndex != -1, "no player Seek");
|
||||
AVPPlayerSeekToTime(_playerIndex, time, false);
|
||||
}
|
||||
|
||||
public override void SeekFast(double time)
|
||||
{
|
||||
Debug.Assert(_playerIndex != -1, "no player SeekFast");
|
||||
AVPPlayerSeekToTime(_playerIndex, time, true);
|
||||
}
|
||||
|
||||
public override double GetCurrentTime()
|
||||
{
|
||||
//Debug.Assert(_player != -1, "no player GetCurrentTime");
|
||||
double result = 0.0;
|
||||
if (_playerIndex != -1)
|
||||
{
|
||||
result = AVPPlayerGetCurrentTime(_playerIndex);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public override void SetPlaybackRate(float rate)
|
||||
{
|
||||
Debug.Assert(_playerIndex != -1, "no player SetPlaybackRate");
|
||||
|
||||
// No HTML implementations allow negative rate yet
|
||||
rate = Mathf.Clamp(rate, 0.25f, 8f);
|
||||
|
||||
AVPPlayerSetPlaybackRate(_playerIndex, rate);
|
||||
}
|
||||
|
||||
public override float GetPlaybackRate()
|
||||
{
|
||||
//Debug.Assert(_player != -1, "no player GetPlaybackRate");
|
||||
float result = 0.0f;
|
||||
|
||||
if (_playerIndex != -1)
|
||||
{
|
||||
result = AVPPlayerGetPlaybackRate(_playerIndex);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public override double GetDuration()
|
||||
{
|
||||
//Debug.Assert(_player != -1, "no player GetDuration");
|
||||
double result = 0.0;
|
||||
if (_playerIndex != -1)
|
||||
{
|
||||
result = AVPPlayerGetDuration(_playerIndex);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public override int GetVideoWidth()
|
||||
{
|
||||
if (_width == 0)
|
||||
{
|
||||
_width = AVPPlayerWidth(_playerIndex);
|
||||
}
|
||||
return _width;
|
||||
}
|
||||
|
||||
public override int GetVideoHeight()
|
||||
{
|
||||
if (_height == 0)
|
||||
{
|
||||
_height = AVPPlayerHeight(_playerIndex);
|
||||
}
|
||||
return _height;
|
||||
}
|
||||
|
||||
public override float GetVideoFrameRate()
|
||||
{
|
||||
// There is no way in HTML5 yet to get the frame rate of the video
|
||||
return 0f;
|
||||
}
|
||||
|
||||
public override bool IsSeeking()
|
||||
{
|
||||
//Debug.Assert(_player != -1, "no player IsSeeking");
|
||||
bool result = false;
|
||||
|
||||
if (_playerIndex != -1)
|
||||
{
|
||||
result = AVPPlayerIsSeeking(_playerIndex);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public override bool IsPlaying()
|
||||
{
|
||||
//Debug.Assert(_player != -1, "no player IsPlaying");
|
||||
bool result = false;
|
||||
|
||||
if (_playerIndex != -1)
|
||||
{
|
||||
result = AVPPlayerIsPlaying(_playerIndex);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public override bool IsPaused()
|
||||
{
|
||||
//Debug.Assert(_player != -1, "no player IsPaused");
|
||||
bool result = false;
|
||||
|
||||
if (_playerIndex != -1)
|
||||
{
|
||||
result = AVPPlayerIsPaused(_playerIndex);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public override bool IsFinished()
|
||||
{
|
||||
//Debug.Assert(_player != -1, "no player IsFinished");
|
||||
bool result = false;
|
||||
|
||||
if (_playerIndex != -1)
|
||||
{
|
||||
result = AVPPlayerIsFinished(_playerIndex);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public override bool IsBuffering()
|
||||
{
|
||||
//Debug.Assert(_player != -1, "no player IsBuffering");
|
||||
bool result = false;
|
||||
|
||||
if (_playerIndex != -1)
|
||||
{
|
||||
result = AVPPlayerIsBuffering(_playerIndex);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public override Texture GetTexture( int index )
|
||||
{
|
||||
return _texture;
|
||||
}
|
||||
|
||||
public override int GetTextureFrameCount()
|
||||
{
|
||||
//Debug.Assert(_player != -1, "no player GetTextureFrameCount");
|
||||
int result = 0;
|
||||
|
||||
if (_playerIndex != -1)
|
||||
{
|
||||
result = AVPPlayerGetDecodedFrameCount(_playerIndex);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
internal override StereoPacking InternalGetTextureStereoPacking()
|
||||
{
|
||||
return StereoPacking.Unknown;
|
||||
}
|
||||
|
||||
public override bool SupportsTextureFrameCount()
|
||||
{
|
||||
bool result = false;
|
||||
|
||||
if (_playerIndex != -1)
|
||||
{
|
||||
result = AVPPlayerSupportedDecodedFrameCount(_playerIndex);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public override bool RequiresVerticalFlip()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public override bool IsMuted()
|
||||
{
|
||||
//Debug.Assert(_player != -1, "no player IsMuted");
|
||||
bool result = false;
|
||||
|
||||
if (_playerIndex != -1)
|
||||
{
|
||||
result = AVPPlayerIsMuted(_playerIndex);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public override void MuteAudio(bool bMute)
|
||||
{
|
||||
Debug.Assert(_playerIndex != -1, "no player MuteAudio");
|
||||
|
||||
AVPPlayerSetMuted(_playerIndex, bMute);
|
||||
}
|
||||
|
||||
public override void SetVolume(float volume)
|
||||
{
|
||||
Debug.Assert(_playerIndex != -1, "no player SetVolume");
|
||||
|
||||
AVPPlayerSetVolume(_playerIndex, volume);
|
||||
}
|
||||
|
||||
public override float GetVolume()
|
||||
{
|
||||
//Debug.Assert(_player != -1, "no player GetVolume");
|
||||
float result = 0.0f;
|
||||
|
||||
if (_playerIndex != -1)
|
||||
{
|
||||
result = AVPPlayerGetVolume(_playerIndex);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public override void Render()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
private void UpdateLastErrorCode()
|
||||
{
|
||||
var code = AVPPlayerGetLastError(_playerIndex);
|
||||
|
||||
switch(code){
|
||||
case 0:
|
||||
_lastError = ErrorCode.None;
|
||||
break;
|
||||
case 1:
|
||||
_lastError = ErrorCode.LoadFailed;
|
||||
break;
|
||||
case 2:
|
||||
_lastError = ErrorCode.LoadFailed;
|
||||
break;
|
||||
case 3:
|
||||
_lastError = ErrorCode.DecodeFailed;
|
||||
break;
|
||||
case 4:
|
||||
_lastError = ErrorCode.LoadFailed;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsMipMapGenerationSupported(int videoWidth, int videoHeight)
|
||||
{
|
||||
if (!_isWebGL1 || (Mathf.IsPowerOfTwo(videoWidth) && Mathf.IsPowerOfTwo(videoHeight)))
|
||||
{
|
||||
// Mip generation only supported in WebGL 2.0, or WebGL 1.0 when using power-of-two textures
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void CreateTexture()
|
||||
{
|
||||
//Debug.Log("creating texture " + _width + " X " + _height);
|
||||
|
||||
#if AVPRO_WEBGL_USE_RENDERTEXTURE
|
||||
_texture = new RenderTexture(_width, _height, 0, RenderTextureFormat.Default);
|
||||
_texture.autoGenerateMips = false;
|
||||
_texture.useMipMap = (_useTextureMips && IsMipMapGenerationSupported(_width, _height));
|
||||
_texture.Create();
|
||||
_cachedTextureNativePtr = _texture.GetNativeTexturePtr();
|
||||
#else
|
||||
int textureId = 80000 + _playerIndex;
|
||||
_cachedTextureNativePtr = new System.IntPtr(textureId);
|
||||
AVPPlayerCreateVideoTexture(textureId);
|
||||
// TODO: add support for mip generation
|
||||
_texture = Texture2D.CreateExternalTexture(_width, _height, TextureFormat.RGBA32, false, false, _cachedTextureNativePtr);
|
||||
if (_useTextureMips)
|
||||
{
|
||||
Debug.LogWarning("[AVProVideo] Texture Mips not yet implemented in this WebGL rendering path");
|
||||
}
|
||||
//Debug.Log("created texture1 " + _texture);
|
||||
//Debug.Log("created texture2 " + _texture.GetNativeTexturePtr().ToInt32());
|
||||
#endif
|
||||
|
||||
ApplyTextureProperties(_texture);
|
||||
|
||||
bool initTexture = true;
|
||||
#if AVPRO_WEBGL_USE_RENDERTEXTURE
|
||||
// Textures in WebGL 2.0 don't require texImage2D as they are already recreated with texStorage2D
|
||||
initTexture = _isWebGL1;
|
||||
#endif
|
||||
AVPPlayerFetchVideoTexture(_playerIndex, _cachedTextureNativePtr, initTexture);
|
||||
}
|
||||
|
||||
private void DestroyTexture()
|
||||
{
|
||||
// Have to update with zero to release Metal textures!
|
||||
//_texture.UpdateExternalTexture(0);
|
||||
if (_texture != null)
|
||||
{
|
||||
#if AVPRO_WEBGL_USE_RENDERTEXTURE
|
||||
RenderTexture.Destroy(_texture);
|
||||
#else
|
||||
Texture2D.Destroy(_texture);
|
||||
AVPPlayerDestroyVideoTexture(_cachedTextureNativePtr.ToInt32());
|
||||
#endif
|
||||
_texture = null;
|
||||
}
|
||||
_cachedTextureNativePtr = System.IntPtr.Zero;
|
||||
}
|
||||
|
||||
public override void Update()
|
||||
{
|
||||
if(_playerID >= 0) // CheckPlayer's index and update it
|
||||
{
|
||||
_playerIndex = AVPPlayerUpdatePlayerIndex(_playerID);
|
||||
}
|
||||
|
||||
if(_playerIndex >= 0)
|
||||
{
|
||||
CheckTracksDirty();
|
||||
UpdateTracks();
|
||||
UpdateTextCue();
|
||||
UpdateSubtitles();
|
||||
UpdateLastErrorCode();
|
||||
|
||||
if (AVPPlayerReady(_playerIndex))
|
||||
{
|
||||
UpdateTimeRanges();
|
||||
if (AVPPlayerHasVideo(_playerIndex))
|
||||
{
|
||||
_width = AVPPlayerWidth(_playerIndex);
|
||||
_height = AVPPlayerHeight(_playerIndex);
|
||||
|
||||
if (_texture != null && (_texture.width != _width || _texture.height != _height))
|
||||
{
|
||||
DestroyTexture();
|
||||
}
|
||||
|
||||
if (_texture == null && _width > 0 && _height > 0)
|
||||
{
|
||||
CreateTexture();
|
||||
}
|
||||
|
||||
// Update the texture
|
||||
if (_cachedTextureNativePtr != System.IntPtr.Zero)
|
||||
{
|
||||
// TODO: only update the texture when the frame count changes
|
||||
// (actually this will break the update for certain browsers such as edge and possibly safari - Sunrise)
|
||||
AVPPlayerFetchVideoTexture(_playerIndex, _cachedTextureNativePtr, false);
|
||||
|
||||
#if AVPRO_WEBGL_USE_RENDERTEXTURE
|
||||
if (_texture.useMipMap)
|
||||
{
|
||||
_texture.GenerateMips();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
UpdateDisplayFrameRate();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void CheckTracksDirty()
|
||||
{
|
||||
_isDirtyVideoTracks = false;
|
||||
_isDirtyAudioTracks = false;
|
||||
_isDirtyTextTracks = false;
|
||||
|
||||
// TODO: replace this crude polling check with events, or only do it once metadataReady
|
||||
// Need to add event support as tracks can be added via HTML (especially text)
|
||||
int videoTrackCount = AVPPlayerGetVideoTrackCount(_playerIndex);
|
||||
int audioTrackCount = AVPPlayerGetAudioTrackCount(_playerIndex);
|
||||
int textTrackCount = AVPPlayerGetTextTrackCount(_playerIndex);
|
||||
|
||||
_isDirtyVideoTracks = (_cachedVideoTrackCount != videoTrackCount);
|
||||
_isDirtyAudioTracks = (_cachedAudioTrackCount != audioTrackCount);
|
||||
_isDirtyTextTracks = ( _cachedTextTrackCount != textTrackCount);
|
||||
|
||||
_cachedVideoTrackCount = videoTrackCount;
|
||||
_cachedAudioTrackCount = audioTrackCount;
|
||||
_cachedTextTrackCount = textTrackCount;
|
||||
}
|
||||
|
||||
private void UpdateTimeRanges()
|
||||
{
|
||||
{
|
||||
int rangeCount = AVPPlayerGetNumBufferedTimeRanges(_playerIndex);
|
||||
if (rangeCount != _bufferedTimes.Count)
|
||||
{
|
||||
_bufferedTimes._ranges = new TimeRange[rangeCount];
|
||||
}
|
||||
for (int i = 0; i < rangeCount; i++)
|
||||
{
|
||||
double startTime = AVPPlayerGetTimeRangeStart(_playerIndex, i);
|
||||
double endTime = AVPPlayerGetTimeRangeEnd(_playerIndex, i);
|
||||
_bufferedTimes._ranges[i] = new TimeRange(startTime, endTime - startTime);
|
||||
}
|
||||
_bufferedTimes.CalculateRange();
|
||||
}
|
||||
|
||||
{
|
||||
double duration = GetDuration();
|
||||
if (duration > 0.0)
|
||||
{
|
||||
_seekableTimes._ranges = new TimeRange[1];
|
||||
_seekableTimes._ranges[0] = new TimeRange(0.0, duration);
|
||||
}
|
||||
else
|
||||
{
|
||||
_seekableTimes._ranges = new TimeRange[0];
|
||||
}
|
||||
_seekableTimes.CalculateRange();
|
||||
}
|
||||
}
|
||||
|
||||
public override void Dispose()
|
||||
{
|
||||
CloseMedia();
|
||||
}
|
||||
|
||||
public override bool IsPlaybackStalled()
|
||||
{
|
||||
bool result = false;
|
||||
if (_playerIndex > -1)
|
||||
{
|
||||
result = AVPPlayerIsPlaybackStalled(_playerIndex) && IsPlaying();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// Tracks
|
||||
internal override int InternalGetTrackCount(TrackType trackType)
|
||||
{
|
||||
int result = 0;
|
||||
switch (trackType)
|
||||
{
|
||||
case TrackType.Video:
|
||||
result = AVPPlayerGetVideoTrackCount(_playerIndex);
|
||||
break;
|
||||
case TrackType.Audio:
|
||||
result = AVPPlayerGetAudioTrackCount(_playerIndex);
|
||||
break;
|
||||
case TrackType.Text:
|
||||
result = AVPPlayerGetTextTrackCount(_playerIndex);
|
||||
break;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
internal override bool InternalIsChangedTracks(TrackType trackType)
|
||||
{
|
||||
bool result = false;
|
||||
switch (trackType)
|
||||
{
|
||||
case TrackType.Video:
|
||||
result = _isDirtyVideoTracks;
|
||||
break;
|
||||
case TrackType.Audio:
|
||||
result = _isDirtyAudioTracks;
|
||||
break;
|
||||
case TrackType.Text:
|
||||
result = _isDirtyTextTracks;
|
||||
break;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
internal override bool InternalSetActiveTrack(TrackType trackType, int trackId)
|
||||
{
|
||||
bool result = false;
|
||||
switch (trackType)
|
||||
{
|
||||
case TrackType.Video:
|
||||
result = AVPPlayerSetActiveVideoTrack(_playerIndex, trackId);
|
||||
break;
|
||||
case TrackType.Audio:
|
||||
result = AVPPlayerSetActiveAudioTrack(_playerIndex, trackId);
|
||||
break;
|
||||
case TrackType.Text:
|
||||
result = AVPPlayerSetActiveTextTrack(_playerIndex, trackId);
|
||||
break;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
internal override TrackBase InternalGetTrackInfo(TrackType trackType, int trackIndex, ref bool isActiveTrack)
|
||||
{
|
||||
TrackBase result = null;
|
||||
|
||||
switch (trackType)
|
||||
{
|
||||
case TrackType.Video:
|
||||
{
|
||||
string trackName = AVPPlayerGetVideoTrackName(_playerIndex, trackIndex);
|
||||
string trackLanguage = AVPPlayerGetVideoTrackLanguage(_playerIndex, trackIndex);
|
||||
bool isActive = AVPPlayerIsVideoTrackActive(_playerIndex, trackIndex);
|
||||
result = new VideoTrack(trackIndex, trackName, trackLanguage, isActive);
|
||||
break;
|
||||
}
|
||||
case TrackType.Audio:
|
||||
{
|
||||
string trackName = AVPPlayerGetAudioTrackName(_playerIndex, trackIndex);
|
||||
string trackLanguage = AVPPlayerGetAudioTrackLanguage(_playerIndex, trackIndex);
|
||||
bool isActive = AVPPlayerIsAudioTrackActive(_playerIndex, trackIndex);
|
||||
result = new AudioTrack(trackIndex, trackName, trackLanguage, isActive);
|
||||
break;
|
||||
}
|
||||
case TrackType.Text:
|
||||
{
|
||||
string trackName = AVPPlayerGetTextTrackName(_playerIndex, trackIndex);
|
||||
string trackLanguage = AVPPlayerGetTextTrackLanguage(_playerIndex, trackIndex);
|
||||
bool isActive = AVPPlayerIsTextTrackActive(_playerIndex, trackIndex);
|
||||
result = new TextTrack(trackIndex, trackName, trackLanguage, isActive);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// Text Cue stub methods
|
||||
internal override bool InternalIsChangedTextCue() { return false; }
|
||||
internal override string InternalGetCurrentTextCue() { return string.Empty; }
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c044ff13d5570e64a8156bc718b3cfec
|
||||
timeCreated: 1468230219
|
||||
licenseType: Pro
|
||||
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,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6f3c954eb61392a4193295a8376bd8db
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
+322
@@ -0,0 +1,322 @@
|
||||
// NOTE: We only allow this script to compile in editor so we can easily check for compilation issues
|
||||
#if (UNITY_EDITOR || (UNITY_STANDALONE_WIN || UNITY_WSA_10_0))
|
||||
|
||||
#define AVPROVIDEO_FIXREGRESSION_TEXTUREQUALITY_UNITY542
|
||||
#if UNITY_WSA_10 || ENABLE_IL2CPP
|
||||
#define AVPROVIDEO_MARSHAL_RETURN_BOOL
|
||||
#endif
|
||||
#if UNITY_2019_3_OR_NEWER && !UNITY_2020_1_OR_NEWER
|
||||
#define AVPROVIDEO_FIX_UPDATEEXTERNALTEXTURE_LEAK
|
||||
#endif
|
||||
|
||||
using UnityEngine;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Text;
|
||||
|
||||
#if NETFX_CORE
|
||||
using Windows.Storage.Streams;
|
||||
#endif
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Copyright 2015-2021 RenderHeads Ltd. All rights reserved.
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
namespace RenderHeads.Media.AVProVideo
|
||||
{
|
||||
/// <summary>
|
||||
/// Windows desktop and UWP implementation of BaseMediaPlayer
|
||||
/// </summary>
|
||||
public /*sealed*/ partial class WindowsMediaPlayer : BaseMediaPlayer
|
||||
{
|
||||
|
||||
#region IBufferedDisplay Implementation
|
||||
|
||||
private BufferedFrameSelectionMode _frameSelectionMode = BufferedFrameSelectionMode.None;
|
||||
private bool _pauseOnPrerollComplete = false;
|
||||
private IBufferedDisplay _masterDisplay;
|
||||
private IBufferedDisplay[] _slaveDisplays;
|
||||
private double _displayClockTime = 0.0;
|
||||
private double _timeAccumulation = 0.0;
|
||||
private bool _needsInitialFrame = true;
|
||||
|
||||
private void FlushFrameBuffering(bool releaseTexture)
|
||||
{
|
||||
if (_frameSelectionMode == BufferedFrameSelectionMode.None) return;
|
||||
|
||||
if (releaseTexture && _textureFrame.internalNativePointer != System.IntPtr.Zero)
|
||||
{
|
||||
Native.UnlockTextureFrame(_instance, ref _textureFrame);
|
||||
_textureFrame.internalNativePointer = System.IntPtr.Zero;
|
||||
_textureFrame.texturePointer = System.IntPtr.Zero;
|
||||
}
|
||||
|
||||
Native.FlushFrameBuffering(_instance);
|
||||
|
||||
// Native _pauseOnPrerollComplete needs to be reset
|
||||
//Native.SetFrameBufferingEnabled(_instance, (_frameSelectionMode != BufferedFrameSelectionMode.None), _pauseOnPrerollComplete);
|
||||
|
||||
_needsInitialFrame = true;
|
||||
_timeAccumulation = 0.0;
|
||||
}
|
||||
|
||||
internal override long InternalUpdateBufferedDisplay()
|
||||
{
|
||||
BufferedFramesState state = GetBufferedFramesState();
|
||||
if (state.bufferedFrameCount > 0)
|
||||
{
|
||||
if (_frameSelectionMode == BufferedFrameSelectionMode.NewestFrame)
|
||||
{
|
||||
SetBufferedDisplayTime(_frameSelectionMode, -1, false);
|
||||
}
|
||||
else if (_frameSelectionMode == BufferedFrameSelectionMode.OldestFrame)
|
||||
{
|
||||
SetBufferedDisplayTime(_frameSelectionMode, -1, false);
|
||||
}
|
||||
else if (_frameSelectionMode == BufferedFrameSelectionMode.ElapsedTime ||
|
||||
_frameSelectionMode == BufferedFrameSelectionMode.ElapsedTimeVsynced)
|
||||
{
|
||||
// Only start consuming frames on these conditions
|
||||
bool needsInitialFrame = (_textureFrame.texturePointer == System.IntPtr.Zero || _needsInitialFrame);
|
||||
bool playingBufferedFrames = (IsPrerollComplete() && (IsPlaying() || (!IsPlaying() && IsFinished())));
|
||||
if (needsInitialFrame || playingBufferedFrames)
|
||||
{
|
||||
if (needsInitialFrame)
|
||||
{
|
||||
if (SetBufferedDisplayTime(BufferedFrameSelectionMode.OldestFrame, -1, true))
|
||||
{
|
||||
_displayClockTime = _textureFrame.timeStamp;
|
||||
_needsInitialFrame = false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// TODO: run without vsync, just show next frame (use media clock for present?)
|
||||
// use our own clock...
|
||||
const double SecondsToHNS = 10000000.0;
|
||||
double videoFrameDuration = SecondsToHNS / (double)GetVideoFrameRate();
|
||||
long videoDuration = (long)Math.Floor(SecondsToHNS * GetDuration());
|
||||
long lastFrameTime = Math.Max(videoDuration, state.maxTimeStamp);
|
||||
double delta = SecondsToHNS * Time.deltaTime;
|
||||
|
||||
if (_frameSelectionMode == BufferedFrameSelectionMode.ElapsedTimeVsynced && QualitySettings.vSyncCount > 0)
|
||||
{
|
||||
#if UNITY_2022_2_OR_NEWER
|
||||
double refreshRate = Screen.currentResolution.refreshRateRatio.value;
|
||||
#else
|
||||
double refreshRate = (double)( Screen.currentResolution.refreshRate );
|
||||
#endif
|
||||
|
||||
// Since we're running with vsync enabled, the MINIMUM elapsed time will be 1 monitor refresh (multiplied by QualitySettings.vSyncCount)
|
||||
double monitorDuration = (QualitySettings.vSyncCount * SecondsToHNS) / refreshRate;
|
||||
|
||||
int wholeFrames = (int)System.Math.Floor(_timeAccumulation / monitorDuration);
|
||||
wholeFrames = System.Math.Max(1, wholeFrames);
|
||||
delta = monitorDuration * wholeFrames;
|
||||
|
||||
//LogBufferState();
|
||||
if (wholeFrames > 1)
|
||||
{
|
||||
//Debug.Log(Time.frameCount + "] " + Time.deltaTime + " " + wholeFrames + " " + _timeAccumulation + " " + _timeAccumulation / SecondsToHNS);
|
||||
//LogBufferState();
|
||||
}
|
||||
|
||||
_timeAccumulation += (Time.deltaTime * SecondsToHNS) - delta;
|
||||
|
||||
//delta = monitorDuration;
|
||||
|
||||
/*double actualFrameDuration = Time.deltaTime * SecondsToHNS;
|
||||
double idealFrameTimeDifference = (actualFrameDuration);// - minMonitorDuration);
|
||||
if (idealFrameTimeDifference > (minMonitorDuration / 2))
|
||||
{
|
||||
int droppedFrames = (int)Math.Round(idealFrameTimeDifference / minMonitorDuration);
|
||||
//Debug.Log(Time.maximumDeltaTime + " " + Time.deltaTime + " " + actualFrameDuration + " " + idealFrameTimeDifference + " = " + droppedFrames);
|
||||
delta += minMonitorDuration * droppedFrames;
|
||||
|
||||
//LogBufferState();
|
||||
}
|
||||
else
|
||||
{
|
||||
//Debug.Log(Time.deltaTime);
|
||||
}
|
||||
// If we're running slower than this or there is a frame drop, the elapsed time will be a multiple
|
||||
// of the monitor refresh rate
|
||||
*/
|
||||
}
|
||||
|
||||
_displayClockTime += delta;
|
||||
|
||||
int multiple = (int)videoFrameDuration;
|
||||
long snappedFrameTime = (long)Math.Floor(_displayClockTime / multiple) * multiple;
|
||||
if (_isLooping && snappedFrameTime > lastFrameTime)
|
||||
{
|
||||
snappedFrameTime %= lastFrameTime;
|
||||
_needsInitialFrame = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
snappedFrameTime = Math.Min(snappedFrameTime, lastFrameTime);
|
||||
}
|
||||
|
||||
if (System.Math.Abs(snappedFrameTime - _textureFrame.timeStamp) > 1000)
|
||||
{
|
||||
//Debug.Log("1 " + _displayClockTime + " > " + snappedFrameTime + " d:" + delta);
|
||||
//LogBufferState();
|
||||
|
||||
if (_needsInitialFrame)
|
||||
{
|
||||
if (SetBufferedDisplayTime(BufferedFrameSelectionMode.OldestFrame, -1, true))
|
||||
{
|
||||
_displayClockTime = _textureFrame.timeStamp;
|
||||
//Debug.Log("initial: " + _displayClockTime);
|
||||
_needsInitialFrame = false;
|
||||
}
|
||||
}
|
||||
else if (!SetBufferedDisplayTime(_frameSelectionMode, snappedFrameTime, false))
|
||||
{
|
||||
//Debug.LogWarning("[AVProVideo] failed to set time at " + snappedFrameTime);
|
||||
//LogBufferState();
|
||||
|
||||
// Try to snap to oldest buffered time
|
||||
_displayClockTime = (state.minTimeStamp + state.maxTimeStamp) / 2.0;
|
||||
snappedFrameTime = (long)Math.Floor(_displayClockTime / multiple) * multiple;
|
||||
if (_isLooping && snappedFrameTime > lastFrameTime)
|
||||
{
|
||||
snappedFrameTime %= lastFrameTime;
|
||||
}
|
||||
else
|
||||
{
|
||||
snappedFrameTime = Math.Min(snappedFrameTime, lastFrameTime);
|
||||
}
|
||||
|
||||
if (SetBufferedDisplayTime(BufferedFrameSelectionMode.FromExternalTimeClosest, snappedFrameTime, false))
|
||||
{
|
||||
_displayClockTime = _textureFrame.timeStamp;
|
||||
//Debug.LogWarning("[AVProVideo] Good set: " + _displayClockTime);
|
||||
}
|
||||
else
|
||||
{
|
||||
//Debug.LogWarning("[AVProVideo] Failed to display frame time " + snappedFrameTime);
|
||||
//LogBufferState();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (_frameSelectionMode == BufferedFrameSelectionMode.FromExternalTime)
|
||||
{
|
||||
if (_masterDisplay != null)
|
||||
{
|
||||
// Use the time from the master
|
||||
long timeStamp = _masterDisplay.UpdateBufferedDisplay();
|
||||
if (timeStamp != GetTextureTimeStamp())
|
||||
{
|
||||
if (!SetBufferedDisplayTime(BufferedFrameSelectionMode.FromExternalTimeClosest, timeStamp, false))
|
||||
{
|
||||
Debug.LogWarning("[AVProVideo] Failed to display frame using external clock at time " + timeStamp);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return GetTextureTimeStamp();
|
||||
}
|
||||
|
||||
private void LogBufferState()
|
||||
{
|
||||
BufferedFramesState state = GetBufferedFramesState();
|
||||
long timeStamp = GetTextureTimeStamp();
|
||||
string result = string.Format("[AVProVideo] {4} - {2},{3}\t\t{0}-{1} ({5})", state.minTimeStamp, state.maxTimeStamp, state.bufferedFrameCount, state.freeFrameCount, timeStamp, Time.deltaTime);
|
||||
Debug.Log(result);
|
||||
}
|
||||
|
||||
private bool SetBufferedDisplayTime(BufferedFrameSelectionMode mode, long timeOfDesiredFrameToDisplay, bool ignorePreroll)
|
||||
{
|
||||
bool result = false;
|
||||
//if (!_isPaused)
|
||||
{
|
||||
result = Native.LockTextureFrame(_instance, mode, timeOfDesiredFrameToDisplay, ref _textureFrame, ignorePreroll);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public override BufferedFramesState GetBufferedFramesState()
|
||||
{
|
||||
BufferedFramesState state = new BufferedFramesState();
|
||||
Native.GetBufferedFramesState(_instance, ref state);
|
||||
return state;
|
||||
}
|
||||
|
||||
public override void SetBufferedDisplayMode(BufferedFrameSelectionMode mode, IBufferedDisplay master = null)
|
||||
{
|
||||
_frameSelectionMode = mode;
|
||||
_masterDisplay = master;
|
||||
UpdateBufferedDisplay();
|
||||
}
|
||||
|
||||
public override void SetBufferedDisplayOptions(bool pauseOnPrerollComplete)
|
||||
{
|
||||
_pauseOnPrerollComplete = pauseOnPrerollComplete;
|
||||
Native.SetFrameBufferingEnabled(_instance, (_frameSelectionMode != BufferedFrameSelectionMode.None), _pauseOnPrerollComplete);
|
||||
}
|
||||
|
||||
public override void SetSlaves(IBufferedDisplay[] slaves)
|
||||
{
|
||||
foreach (IBufferedDisplay slave in slaves)
|
||||
{
|
||||
slave.SetBufferedDisplayMode(BufferedFrameSelectionMode.FromExternalTime, this);
|
||||
}
|
||||
_slaveDisplays = slaves;
|
||||
}
|
||||
|
||||
private bool IsPrerollComplete()
|
||||
{
|
||||
bool result = true;
|
||||
|
||||
if (GetBufferedFramesState().prerolledCount <= 0)
|
||||
{
|
||||
result = false;
|
||||
}
|
||||
if (_slaveDisplays != null && result)
|
||||
{
|
||||
foreach (IBufferedDisplay slave in _slaveDisplays)
|
||||
{
|
||||
if (slave.GetBufferedFramesState().prerolledCount <= 0)
|
||||
{
|
||||
result = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private partial struct Native
|
||||
{
|
||||
[DllImport("AVProVideo")]
|
||||
public static extern bool GetBufferedFramesState(System.IntPtr playerInstance, ref BufferedFramesState state);
|
||||
|
||||
[DllImport("AVProVideo")]
|
||||
#if AVPROVIDEO_MARSHAL_RETURN_BOOL
|
||||
[return: MarshalAs(UnmanagedType.I1)]
|
||||
#endif
|
||||
public static extern bool LockTextureFrame(System.IntPtr instance, BufferedFrameSelectionMode mode, long time, ref TextureFrame textureFrame, bool ignorePreroll);
|
||||
|
||||
[DllImport("AVProVideo")]
|
||||
public static extern void UnlockTextureFrame(System.IntPtr instance, ref TextureFrame textureFrame);
|
||||
|
||||
[DllImport("AVProVideo")]
|
||||
public static extern void ReleaseTextureFrame(System.IntPtr instance, ref TextureFrame textureFrame);
|
||||
|
||||
[DllImport("AVProVideo")]
|
||||
public static extern void FlushFrameBuffering(System.IntPtr instance);
|
||||
}
|
||||
|
||||
#endregion // IBufferedDisplay Implementation
|
||||
|
||||
}
|
||||
}
|
||||
#endif
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2b36cc2d6962ce34e86c5a83a0de6d4a
|
||||
timeCreated: 1630292296
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,961 @@
|
||||
// NOTE: We only allow this script to compile in editor so we can easily check for compilation issues
|
||||
#if (UNITY_EDITOR || (UNITY_STANDALONE_WIN || UNITY_WSA_10_0))
|
||||
|
||||
#if UNITY_WSA_10 || ENABLE_IL2CPP
|
||||
#define AVPROVIDEO_MARSHAL_RETURN_BOOL
|
||||
#endif
|
||||
|
||||
using UnityEngine;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Text;
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Copyright 2018-2021 RenderHeads Ltd. All rights reserved.
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
namespace RenderHeads.Media.AVProVideo
|
||||
{
|
||||
public enum PlaybackState
|
||||
{
|
||||
None = 0,
|
||||
Opening = 1,
|
||||
Buffering = 2, // Replace with Stalled and add Buffering as State 64??
|
||||
Playing = 3,
|
||||
Paused = 4,
|
||||
StateMask = 7,
|
||||
|
||||
Seeking = 32,
|
||||
}
|
||||
|
||||
public class AuthData
|
||||
{
|
||||
public string URL { get; set; }
|
||||
public string Token { get; set; }
|
||||
public byte[] KeyBytes { get; set; }
|
||||
|
||||
public AuthData()
|
||||
{
|
||||
Clear();
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
URL = string.Empty;
|
||||
Token = string.Empty;
|
||||
KeyBytes = null;
|
||||
}
|
||||
|
||||
public string KeyBase64
|
||||
{
|
||||
get
|
||||
{
|
||||
if (KeyBytes != null)
|
||||
{
|
||||
return System.Convert.ToBase64String(KeyBytes);
|
||||
}
|
||||
else
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
set
|
||||
{
|
||||
if (value != null)
|
||||
{
|
||||
KeyBytes = System.Convert.FromBase64String(value);
|
||||
}
|
||||
else
|
||||
{
|
||||
KeyBytes = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
public partial class WindowsRtMediaPlayer : BaseMediaPlayer
|
||||
{
|
||||
private bool _isMediaLoaded = false;
|
||||
private bool _use10BitTextures = false;
|
||||
private bool _useLowLiveLatency = false;
|
||||
|
||||
public WindowsRtMediaPlayer(MediaPlayer.OptionsWindows options) : base()
|
||||
{
|
||||
_playerDescription = "WinRT";
|
||||
_use10BitTextures = options.use10BitTextures;
|
||||
_useLowLiveLatency = options.useLowLiveLatency;
|
||||
for (int i = 0; i < _eyeTextures.Length; i++)
|
||||
{
|
||||
_eyeTextures[i] = new EyeTexture();
|
||||
}
|
||||
}
|
||||
|
||||
public WindowsRtMediaPlayer(MediaPlayer.OptionsWindowsUWP options) : base()
|
||||
{
|
||||
_playerDescription = "WinRT";
|
||||
_use10BitTextures = options.use10BitTextures;
|
||||
_useLowLiveLatency = options.useLowLiveLatency;
|
||||
for (int i = 0; i < _eyeTextures.Length; i++)
|
||||
{
|
||||
_eyeTextures[i] = new EyeTexture();
|
||||
}
|
||||
}
|
||||
|
||||
public override bool CanPlay()
|
||||
{
|
||||
return HasMetaData();
|
||||
}
|
||||
|
||||
public override void Dispose()
|
||||
{
|
||||
CloseMedia();
|
||||
if (_playerInstance != System.IntPtr.Zero)
|
||||
{
|
||||
Native.DestroyPlayer(_playerInstance); _playerInstance = System.IntPtr.Zero;
|
||||
Native.IssueRenderThreadEvent_FreeAllTextures();
|
||||
}
|
||||
for (int i = 0; i < _eyeTextures.Length; i++)
|
||||
{
|
||||
_eyeTextures[i].Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
public override bool PlayerSupportsLinearColorSpace()
|
||||
{
|
||||
// The current player doesn't support rendering to SRGB textures
|
||||
return false;
|
||||
}
|
||||
|
||||
public override double GetCurrentTime()
|
||||
{
|
||||
return Native.GetCurrentPosition(_playerInstance);
|
||||
}
|
||||
|
||||
public override double GetDuration()
|
||||
{
|
||||
return Native.GetDuration(_playerInstance);
|
||||
}
|
||||
|
||||
public override float GetPlaybackRate()
|
||||
{
|
||||
return Native.GetPlaybackRate(_playerInstance);
|
||||
}
|
||||
|
||||
public override Texture GetTexture(int index = 0)
|
||||
{
|
||||
Texture result = null;
|
||||
if (_frameTimeStamp > 0 && index < _eyeTextures.Length)
|
||||
{
|
||||
result = _eyeTextures[index].texture;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public override int GetTextureCount()
|
||||
{
|
||||
if (_eyeTextures[1].texture != null)
|
||||
{
|
||||
return 2;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
public override int GetTextureFrameCount()
|
||||
{
|
||||
return (int)_frameTimeStamp;
|
||||
}
|
||||
|
||||
internal override StereoPacking InternalGetTextureStereoPacking()
|
||||
{
|
||||
return Native.GetStereoPacking(_playerInstance);
|
||||
}
|
||||
|
||||
public override string GetVersion()
|
||||
{
|
||||
return _version;
|
||||
}
|
||||
|
||||
public override string GetExpectedVersion()
|
||||
{
|
||||
return Helper.ExpectedPluginVersion.WinRT;
|
||||
}
|
||||
|
||||
public override float GetVideoFrameRate()
|
||||
{
|
||||
float result = 0f;
|
||||
Native.VideoTrack videoTrack;
|
||||
if (Native.GetActiveVideoTrackInfo(_playerInstance, out videoTrack))
|
||||
{
|
||||
result = videoTrack.frameRate;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public override int GetVideoWidth()
|
||||
{
|
||||
int result = 0;
|
||||
if (_eyeTextures[0].texture)
|
||||
{
|
||||
result = _eyeTextures[0].texture.width;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public override int GetVideoHeight()
|
||||
{
|
||||
int result = 0;
|
||||
if (_eyeTextures[0].texture)
|
||||
{
|
||||
result = _eyeTextures[0].texture.height;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public override float GetVolume()
|
||||
{
|
||||
return Native.GetAudioVolume(_playerInstance);
|
||||
}
|
||||
|
||||
public override void SetBalance(float balance)
|
||||
{
|
||||
Native.SetAudioBalance(_playerInstance, balance);
|
||||
}
|
||||
|
||||
public override float GetBalance()
|
||||
{
|
||||
return Native.GetAudioBalance(_playerInstance);
|
||||
}
|
||||
|
||||
public override bool HasAudio()
|
||||
{
|
||||
return _audioTracks.Count > 0;
|
||||
}
|
||||
|
||||
public override bool HasMetaData()
|
||||
{
|
||||
return Native.GetDuration(_playerInstance) > 0f;
|
||||
}
|
||||
|
||||
public override bool HasVideo()
|
||||
{
|
||||
return _videoTracks.Count > 0;
|
||||
}
|
||||
|
||||
public override bool IsBuffering()
|
||||
{
|
||||
return ((Native.GetPlaybackState(_playerInstance) & PlaybackState.StateMask) == PlaybackState.Buffering);
|
||||
}
|
||||
|
||||
public override bool IsFinished()
|
||||
{
|
||||
bool result = false;
|
||||
if (IsPaused() && !IsSeeking() && GetCurrentTime() >= GetDuration())
|
||||
{
|
||||
result = true;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public override bool IsLooping()
|
||||
{
|
||||
return Native.IsLooping(_playerInstance);
|
||||
}
|
||||
|
||||
public override bool IsMuted()
|
||||
{
|
||||
return Native.IsAudioMuted(_playerInstance);
|
||||
}
|
||||
|
||||
public override bool IsPaused()
|
||||
{
|
||||
return ((Native.GetPlaybackState(_playerInstance) & PlaybackState.StateMask) == PlaybackState.Paused);
|
||||
}
|
||||
|
||||
public override bool IsPlaying()
|
||||
{
|
||||
return ((Native.GetPlaybackState(_playerInstance) & PlaybackState.StateMask) == PlaybackState.Playing);
|
||||
}
|
||||
|
||||
public override bool IsSeeking()
|
||||
{
|
||||
return ((Native.GetPlaybackState(_playerInstance) & PlaybackState.Seeking) != 0);
|
||||
}
|
||||
|
||||
public override void MuteAudio(bool bMuted)
|
||||
{
|
||||
Native.SetAudioMuted(_playerInstance, bMuted);
|
||||
}
|
||||
|
||||
// TODO: replace all these options with a structure
|
||||
public override bool OpenMedia(string path, long offset, string httpHeader, MediaHints mediaHints, int forceFileFormat = 0, bool startWithHighestBitrate = false)
|
||||
{
|
||||
bool result = false;
|
||||
|
||||
CloseMedia();
|
||||
|
||||
if (_playerInstance == System.IntPtr.Zero)
|
||||
{
|
||||
_playerInstance = Native.CreatePlayer();
|
||||
|
||||
// Force setting any auth data as it wouldn't have been set without a _playerInstance
|
||||
AuthenticationData = _nextAuthData;
|
||||
}
|
||||
if (_playerInstance != System.IntPtr.Zero)
|
||||
{
|
||||
result = Native.OpenMedia(_playerInstance, path, httpHeader, (FileFormat)forceFileFormat, startWithHighestBitrate, _use10BitTextures);
|
||||
if (result)
|
||||
{
|
||||
if (_useLowLiveLatency)
|
||||
{
|
||||
Native.SetLiveOffset(_playerInstance, 0.0);
|
||||
}
|
||||
}
|
||||
_mediaHints = mediaHints;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public override void CloseMedia()
|
||||
{
|
||||
// NOTE: This unloads the current video, but the texture should remain
|
||||
_isMediaLoaded = false;
|
||||
Native.CloseMedia(_playerInstance);
|
||||
|
||||
base.CloseMedia();
|
||||
}
|
||||
|
||||
public override void Pause()
|
||||
{
|
||||
Native.Pause(_playerInstance);
|
||||
}
|
||||
|
||||
public override void Play()
|
||||
{
|
||||
Native.Play(_playerInstance);
|
||||
}
|
||||
|
||||
public override void Render()
|
||||
{
|
||||
Native.IssueRenderThreadEvent_UpdateAllTextures();
|
||||
}
|
||||
|
||||
private void Update_Textures()
|
||||
{
|
||||
// See if there is a new frame ready
|
||||
{
|
||||
System.IntPtr texturePointerLeft = System.IntPtr.Zero;
|
||||
System.IntPtr texturePointerRight = System.IntPtr.Zero;
|
||||
ulong frameTimeStamp = 0;
|
||||
int width, height;
|
||||
if (Native.GetLatestFrame(_playerInstance, out texturePointerLeft, out texturePointerRight, out frameTimeStamp, out width, out height))
|
||||
{
|
||||
bool isFrameUpdated = false;
|
||||
bool isNewFrameTime = (frameTimeStamp > _frameTimeStamp) || (_frameTimeStamp == 0 && frameTimeStamp == 0);
|
||||
for (int i = 0; i < _eyeTextures.Length; i++)
|
||||
{
|
||||
EyeTexture eyeTexture = _eyeTextures[i];
|
||||
System.IntPtr texturePointer = texturePointerLeft;
|
||||
if (i == 1)
|
||||
{
|
||||
texturePointer = texturePointerRight;
|
||||
}
|
||||
|
||||
bool isNewFrameSpecs = (eyeTexture.texture != null && (texturePointer == IntPtr.Zero || eyeTexture.texture.width != width || eyeTexture.texture.height != height));
|
||||
//Debug.Log("tex? " + i + " " + width + " " + height + " " + (eyeTexture.texture != null) + " " + texturePointer.ToString() + " " + frameTimeStamp);
|
||||
|
||||
// Check whether the latest frame is newer than the one we got last time
|
||||
if (isNewFrameTime || isNewFrameSpecs)
|
||||
{
|
||||
if (isNewFrameSpecs)
|
||||
{
|
||||
eyeTexture.Dispose();
|
||||
// TODO: blit from the old texture to the new texture before destroying?
|
||||
}
|
||||
|
||||
/// Switch to the latest texture pointer
|
||||
if (eyeTexture.texture != null)
|
||||
{
|
||||
// TODO: check whether UpdateExternalTexture resets the sampling filter to POINT - it seems to in Unity 5.6.6
|
||||
if (eyeTexture.nativePointer != texturePointer)
|
||||
{
|
||||
eyeTexture.texture.UpdateExternalTexture(texturePointer);
|
||||
eyeTexture.nativePointer = texturePointer;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (texturePointer != IntPtr.Zero)
|
||||
{
|
||||
eyeTexture.texture = Texture2D.CreateExternalTexture(width, height, TextureFormat.BGRA32, false, false, texturePointer);
|
||||
if (eyeTexture.texture != null)
|
||||
{
|
||||
eyeTexture.texture.name = "AVProVideo";
|
||||
eyeTexture.nativePointer = texturePointer;
|
||||
ApplyTextureProperties(eyeTexture.texture);
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogError("[AVProVideo] Failed to create texture");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
isFrameUpdated = true;
|
||||
}
|
||||
}
|
||||
if (isFrameUpdated)
|
||||
{
|
||||
_frameTimeStamp = frameTimeStamp;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private AuthData _nextAuthData = new AuthData();
|
||||
|
||||
public AuthData AuthenticationData
|
||||
{
|
||||
get
|
||||
{
|
||||
return _nextAuthData;
|
||||
}
|
||||
set
|
||||
{
|
||||
_nextAuthData = value;
|
||||
Native.SetNextAuthData(_playerInstance, _nextAuthData);
|
||||
}
|
||||
}
|
||||
|
||||
public override bool RequiresVerticalFlip()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public override void Seek(double time)
|
||||
{
|
||||
Native.SeekParams seekParams = new Native.SeekParams();
|
||||
seekParams.timeSeconds = time;
|
||||
seekParams.mode = Native.SeekMode.Accurate;
|
||||
Native.Seek(_playerInstance, ref seekParams);
|
||||
}
|
||||
|
||||
public override void SeekFast(double time)
|
||||
{
|
||||
// Keyframe seeking is not supported on this platform
|
||||
Seek(time);
|
||||
}
|
||||
|
||||
public override void SetLooping(bool bLooping)
|
||||
{
|
||||
Native.SetLooping(_playerInstance, bLooping);
|
||||
}
|
||||
|
||||
public override void SetPlaybackRate(float rate)
|
||||
{
|
||||
// Clamp rate as WinRT doesn't seem to be able to handle negative rate
|
||||
rate = Mathf.Max(0f, rate);
|
||||
Native.SetPlaybackRate(_playerInstance, rate);
|
||||
}
|
||||
|
||||
public override void SetVolume(float volume)
|
||||
{
|
||||
Native.SetAudioVolume(_playerInstance, volume);
|
||||
}
|
||||
|
||||
public override void Stop()
|
||||
{
|
||||
Pause();
|
||||
}
|
||||
|
||||
private void UpdateTimeRanges()
|
||||
{
|
||||
UpdateTimeRange(ref _seekableTimes._ranges, Native.TimeRangeTypes.Seekable);
|
||||
UpdateTimeRange(ref _bufferedTimes._ranges, Native.TimeRangeTypes.Buffered);
|
||||
_seekableTimes.CalculateRange();
|
||||
_bufferedTimes.CalculateRange();
|
||||
}
|
||||
|
||||
private void UpdateTimeRange(ref TimeRange[] range, Native.TimeRangeTypes timeRangeType)
|
||||
{
|
||||
int newCount = Native.GetTimeRanges(_playerInstance, range, range.Length, timeRangeType);
|
||||
if (newCount != range.Length)
|
||||
{
|
||||
range = new TimeRange[newCount];
|
||||
Native.GetTimeRanges(_playerInstance, range, range.Length, timeRangeType);
|
||||
}
|
||||
}
|
||||
|
||||
public override System.DateTime GetProgramDateTime()
|
||||
{
|
||||
double seconds = Native.GetCurrentDateTimeSecondsSince1970(_playerInstance);
|
||||
return Helper.ConvertSecondsSince1970ToDateTime(seconds);
|
||||
}
|
||||
|
||||
public override void Update()
|
||||
{
|
||||
Native.Update(_playerInstance);
|
||||
UpdateTracks();
|
||||
UpdateTextCue();
|
||||
|
||||
_lastError = (ErrorCode)Native.GetLastErrorCode(_playerInstance);
|
||||
|
||||
UpdateTimeRanges();
|
||||
UpdateSubtitles();
|
||||
Update_Textures();
|
||||
UpdateDisplayFrameRate();
|
||||
|
||||
if (!_isMediaLoaded)
|
||||
{
|
||||
if (HasVideo() && _eyeTextures[0].texture != null)
|
||||
{
|
||||
Native.VideoTrack videoTrack;
|
||||
if (Native.GetActiveVideoTrackInfo(_playerInstance, out videoTrack))
|
||||
{
|
||||
Helper.LogInfo("Using playback path: " + _playerDescription + " (" + videoTrack.frameWidth + "x" + videoTrack.frameHeight + "@" + videoTrack.frameRate.ToString("F2") + ")");
|
||||
_isMediaLoaded = true;
|
||||
}
|
||||
}
|
||||
else if (HasAudio() && !HasVideo())
|
||||
{
|
||||
Helper.LogInfo("Using playback path: " + _playerDescription);
|
||||
_isMediaLoaded = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*public override void SetKeyServerURL(string url)
|
||||
{
|
||||
_nextAuthData.URL = url;
|
||||
AuthenticationData = _nextAuthData;
|
||||
}*/
|
||||
|
||||
public override void SetKeyServerAuthToken(string token)
|
||||
{
|
||||
_nextAuthData.Token = token;
|
||||
AuthenticationData = _nextAuthData;
|
||||
}
|
||||
|
||||
public override void SetOverrideDecryptionKey(byte[] key)
|
||||
{
|
||||
_nextAuthData.KeyBytes = key;
|
||||
AuthenticationData = _nextAuthData;
|
||||
}
|
||||
}
|
||||
|
||||
// Tracks
|
||||
public sealed partial class WindowsRtMediaPlayer
|
||||
{
|
||||
internal override bool InternalSetActiveTrack(TrackType trackType, int trackUid)
|
||||
{
|
||||
return Native.SetActiveTrack(_playerInstance, trackType, trackUid);
|
||||
}
|
||||
|
||||
// Has it changed since the last frame 'tick'
|
||||
internal override bool InternalIsChangedTracks(TrackType trackType)
|
||||
{
|
||||
return Native.IsChangedTracks(_playerInstance, trackType);
|
||||
}
|
||||
|
||||
internal override int InternalGetTrackCount(TrackType trackType)
|
||||
{
|
||||
return Native.GetTrackCount(_playerInstance, trackType);
|
||||
}
|
||||
|
||||
internal override TrackBase InternalGetTrackInfo(TrackType trackType, int trackIndex, ref bool isActiveTrack)
|
||||
{
|
||||
TrackBase result = null;
|
||||
StringBuilder name = new StringBuilder(128);
|
||||
StringBuilder language = new StringBuilder(16);
|
||||
int uid = -1;
|
||||
if (Native.GetTrackInfo(_playerInstance, trackType, trackIndex, ref uid, ref isActiveTrack, name, name.Capacity, language, language.Capacity))
|
||||
{
|
||||
if (trackType == TrackType.Video)
|
||||
{
|
||||
result = new VideoTrack(uid, name.ToString(), language.ToString(), false);
|
||||
}
|
||||
else if (trackType == TrackType.Audio)
|
||||
{
|
||||
result = new AudioTrack(uid, name.ToString(), language.ToString(), false);
|
||||
}
|
||||
else if (trackType == TrackType.Text)
|
||||
{
|
||||
result = new TextTrack(uid, name.ToString(), language.ToString(), false);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private partial struct Native
|
||||
{
|
||||
[DllImport("AVProVideoWinRT")]
|
||||
[return: MarshalAs(UnmanagedType.I1)]
|
||||
public static extern bool IsChangedTracks(System.IntPtr instance, TrackType trackType);
|
||||
|
||||
[DllImport("AVProVideoWinRT")]
|
||||
public static extern int GetTrackCount(System.IntPtr instance, TrackType trackType);
|
||||
|
||||
[DllImport("AVProVideoWinRT")]
|
||||
[return: MarshalAs(UnmanagedType.I1)]
|
||||
public static extern bool GetTrackInfo(System.IntPtr instance, TrackType trackType, int index, ref int uid,
|
||||
ref bool isActive,
|
||||
[MarshalAs(UnmanagedType.LPWStr)] StringBuilder name, int maxNameLength,
|
||||
[MarshalAs(UnmanagedType.LPWStr)] StringBuilder language, int maxLanguageLength);
|
||||
|
||||
[DllImport("AVProVideoWinRT")]
|
||||
[return: MarshalAs(UnmanagedType.I1)]
|
||||
public static extern bool SetActiveTrack(System.IntPtr instance, TrackType trackType, int trackUid);
|
||||
}
|
||||
}
|
||||
|
||||
// Text Cue
|
||||
public sealed partial class WindowsRtMediaPlayer
|
||||
{
|
||||
// Has it changed since the last frame 'tick'
|
||||
internal override bool InternalIsChangedTextCue()
|
||||
{
|
||||
return Native.IsChangedTextCue(_playerInstance);
|
||||
}
|
||||
|
||||
internal override string InternalGetCurrentTextCue()
|
||||
{
|
||||
string result = null;
|
||||
System.IntPtr ptr = Native.GetCurrentTextCue(_playerInstance);
|
||||
if (ptr != System.IntPtr.Zero)
|
||||
{
|
||||
result = System.Runtime.InteropServices.Marshal.PtrToStringUni(ptr);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private partial struct Native
|
||||
{
|
||||
[DllImport("AVProVideoWinRT")]
|
||||
[return: MarshalAs(UnmanagedType.I1)]
|
||||
public static extern bool IsChangedTextCue(System.IntPtr instance);
|
||||
|
||||
[DllImport("AVProVideoWinRT")]
|
||||
public static extern System.IntPtr GetCurrentTextCue(System.IntPtr instance);
|
||||
}
|
||||
}
|
||||
|
||||
public sealed partial class WindowsRtMediaPlayer
|
||||
{
|
||||
private partial struct Native
|
||||
{
|
||||
[DllImport("AVProVideoWinRT", EntryPoint = "GetPluginVersion")]
|
||||
private static extern System.IntPtr GetPluginVersionStringPointer();
|
||||
|
||||
public static string GetPluginVersion()
|
||||
{
|
||||
return System.Runtime.InteropServices.Marshal.PtrToStringAnsi(GetPluginVersionStringPointer());
|
||||
}
|
||||
|
||||
[DllImport("AVProVideoWinRT")]
|
||||
public static extern System.IntPtr CreatePlayer();
|
||||
|
||||
[DllImport("AVProVideoWinRT")]
|
||||
public static extern void DestroyPlayer(System.IntPtr playerInstance);
|
||||
|
||||
[DllImport("AVProVideoWinRT")]
|
||||
#if AVPROVIDEO_MARSHAL_RETURN_BOOL
|
||||
[return: MarshalAs(UnmanagedType.I1)]
|
||||
#endif
|
||||
public static extern bool OpenMedia(System.IntPtr playerInstance, [MarshalAs(UnmanagedType.LPWStr)] string filePath,
|
||||
[MarshalAs(UnmanagedType.LPWStr)] string httpHeader, FileFormat overrideFileFormat,
|
||||
bool startWithHighestBitrate, bool use10BitTextures);
|
||||
|
||||
[DllImport("AVProVideoWinRT")]
|
||||
public static extern void CloseMedia(System.IntPtr playerInstance);
|
||||
|
||||
|
||||
[DllImport("AVProVideoWinRT")]
|
||||
public static extern void Pause(System.IntPtr playerInstance);
|
||||
|
||||
[DllImport("AVProVideoWinRT")]
|
||||
public static extern void Play(System.IntPtr playerInstance);
|
||||
|
||||
[DllImport("AVProVideoWinRT")]
|
||||
public static extern void SetAudioVolume(System.IntPtr playerInstance, float volume);
|
||||
|
||||
[DllImport("AVProVideoWinRT")]
|
||||
public static extern void SetAudioBalance(System.IntPtr playerInstance, float balance);
|
||||
|
||||
[DllImport("AVProVideoWinRT")]
|
||||
public static extern void SetPlaybackRate(System.IntPtr playerInstance, float rate);
|
||||
|
||||
[DllImport("AVProVideoWinRT")]
|
||||
public static extern void SetAudioMuted(System.IntPtr playerInstance, bool muted);
|
||||
|
||||
[DllImport("AVProVideoWinRT")]
|
||||
public static extern float GetAudioVolume(System.IntPtr playerInstance);
|
||||
|
||||
[DllImport("AVProVideoWinRT")]
|
||||
#if AVPROVIDEO_MARSHAL_RETURN_BOOL
|
||||
[return: MarshalAs(UnmanagedType.I1)]
|
||||
#endif
|
||||
public static extern bool IsAudioMuted(System.IntPtr playerInstance);
|
||||
|
||||
[DllImport("AVProVideoWinRT")]
|
||||
public static extern float GetAudioBalance(System.IntPtr playerInstance);
|
||||
|
||||
[DllImport("AVProVideoWinRT")]
|
||||
public static extern float GetPlaybackRate(System.IntPtr playerInstance);
|
||||
|
||||
[DllImport("AVProVideoWinRT")]
|
||||
public static extern void SetLooping(System.IntPtr playerInstance, bool looping);
|
||||
|
||||
[DllImport("AVProVideoWinRT")]
|
||||
#if AVPROVIDEO_MARSHAL_RETURN_BOOL
|
||||
[return: MarshalAs(UnmanagedType.I1)]
|
||||
#endif
|
||||
public static extern bool IsLooping(System.IntPtr playerInstance);
|
||||
|
||||
[DllImport("AVProVideoWinRT")]
|
||||
public static extern int GetLastErrorCode(System.IntPtr playerInstance);
|
||||
|
||||
|
||||
[DllImport("AVProVideoWinRT")]
|
||||
public static extern void Update(System.IntPtr playerInstance);
|
||||
|
||||
[DllImport("AVProVideoWinRT")]
|
||||
public static extern double GetDuration(System.IntPtr playerInstance);
|
||||
|
||||
[DllImport("AVProVideoWinRT")]
|
||||
public static extern StereoPacking GetStereoPacking(System.IntPtr playerInstance);
|
||||
|
||||
[DllImport("AVProVideoWinRT")]
|
||||
public static extern double GetCurrentPosition(System.IntPtr playerInstance);
|
||||
|
||||
[DllImport("AVProVideoWinRT")]
|
||||
#if AVPROVIDEO_MARSHAL_RETURN_BOOL
|
||||
[return: MarshalAs(UnmanagedType.I1)]
|
||||
#endif
|
||||
public static extern bool GetLatestFrame(System.IntPtr playerInstance, out System.IntPtr leftEyeTexturePointer, out System.IntPtr rightEyeTexturePointer, out ulong frameTimeStamp, out int width, out int height);
|
||||
|
||||
[DllImport("AVProVideoWinRT")]
|
||||
public static extern PlaybackState GetPlaybackState(System.IntPtr playerInstance);
|
||||
|
||||
[DllImport("AVProVideoWinRT")]
|
||||
#if AVPROVIDEO_MARSHAL_RETURN_BOOL
|
||||
[return: MarshalAs(UnmanagedType.I1)]
|
||||
#endif
|
||||
public static extern bool GetActiveVideoTrackInfo(System.IntPtr playerInstance, out VideoTrack videoTrack);
|
||||
|
||||
[DllImport("AVProVideoWinRT")]
|
||||
#if AVPROVIDEO_MARSHAL_RETURN_BOOL
|
||||
[return: MarshalAs(UnmanagedType.I1)]
|
||||
#endif
|
||||
public static extern bool GetActiveAudioTrackInfo(System.IntPtr playerInstance, out AudioTrack audioTrack);
|
||||
|
||||
[DllImport("AVProVideoWinRT")]
|
||||
public static extern double GetCurrentDateTimeSecondsSince1970(System.IntPtr playerInstance);
|
||||
|
||||
[DllImport("AVProVideoWinRT")]
|
||||
public static extern void SetLiveOffset(System.IntPtr playerInstance, double seconds);
|
||||
|
||||
[DllImport("AVProVideoWinRT")]
|
||||
public static extern void DebugValues(System.IntPtr playerInstance, out int isD3D, out int isUnityD3D, out int isTexture, out int isSharedTexture, out int isSurface);
|
||||
|
||||
public enum SeekMode
|
||||
{
|
||||
Fast = 0,
|
||||
Accurate = 1,
|
||||
// TODO: Add Fast_Before and Fast_After
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||
public struct VideoTrack
|
||||
{
|
||||
public int trackIndex;
|
||||
public int frameWidth;
|
||||
public int frameHeight;
|
||||
public float frameRate;
|
||||
public uint averageBitRate;
|
||||
//public string trackName;
|
||||
// TODO: add index, language, name, bitrate, codec etc
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||
public struct AudioTrack
|
||||
{
|
||||
public int trackIndex;
|
||||
public uint channelCount;
|
||||
public uint sampleRate;
|
||||
public uint bitsPerSample;
|
||||
public uint averageBitRate;
|
||||
//public string trackName;
|
||||
// TODO: add index, language, name, bitrate, codec etc
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||
public struct SeekParams
|
||||
{
|
||||
public double timeSeconds;
|
||||
public SeekMode mode;
|
||||
// TODO: add min-max thresholds
|
||||
}
|
||||
|
||||
[DllImport("AVProVideoWinRT")]
|
||||
public static extern void Seek(System.IntPtr playerInstance, ref SeekParams seekParams);
|
||||
|
||||
public static void SetNextAuthData(System.IntPtr playerInstance, RenderHeads.Media.AVProVideo.AuthData srcAuthData)
|
||||
{
|
||||
Native.AuthData ad = new Native.AuthData();
|
||||
ad.url = string.IsNullOrEmpty(srcAuthData.URL) ? null : srcAuthData.URL;
|
||||
ad.token = string.IsNullOrEmpty(srcAuthData.Token) ? null : srcAuthData.Token;
|
||||
if (srcAuthData.KeyBytes != null && srcAuthData.KeyBytes.Length > 0)
|
||||
{
|
||||
ad.keyBytes = Marshal.AllocHGlobal(srcAuthData.KeyBytes.Length);
|
||||
Marshal.Copy(srcAuthData.KeyBytes, 0, ad.keyBytes, srcAuthData.KeyBytes.Length);
|
||||
ad.keyBytesLength = srcAuthData.KeyBytes.Length;
|
||||
}
|
||||
else
|
||||
{
|
||||
ad.keyBytes = System.IntPtr.Zero;
|
||||
ad.keyBytesLength = 0;
|
||||
}
|
||||
|
||||
SetNextAuthData(playerInstance, ref ad);
|
||||
|
||||
if (ad.keyBytes != System.IntPtr.Zero)
|
||||
{
|
||||
Marshal.FreeHGlobal(ad.keyBytes);
|
||||
}
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||
public struct AuthData
|
||||
{
|
||||
[MarshalAs(UnmanagedType.LPWStr)]
|
||||
public string url;
|
||||
|
||||
[MarshalAs(UnmanagedType.LPWStr)]
|
||||
public string token;
|
||||
|
||||
public System.IntPtr keyBytes;
|
||||
public int keyBytesLength;
|
||||
};
|
||||
|
||||
[DllImport("AVProVideoWinRT")]
|
||||
private static extern void SetNextAuthData(System.IntPtr playerInstance, ref AuthData authData);
|
||||
|
||||
internal enum TimeRangeTypes
|
||||
{
|
||||
Seekable = 0,
|
||||
Buffered = 1,
|
||||
}
|
||||
|
||||
[DllImport("AVProVideoWinRT")]
|
||||
public static extern int GetTimeRanges(System.IntPtr playerInstance, [Out, MarshalAs(UnmanagedType.LPArray, SizeParamIndex=2)] TimeRange[] ranges, int rangeCount, TimeRangeTypes timeRangeType);
|
||||
|
||||
// RJT TODO: Clean this up to better match non-WinRT
|
||||
|
||||
[DllImport("AVProVideoWinRT")]
|
||||
public static extern System.IntPtr GetRenderEventFunc();
|
||||
|
||||
private static System.IntPtr _nativeFunction_UnityRenderEvent;
|
||||
public static void IssueRenderThreadEvent_UpdateAllTextures()
|
||||
{
|
||||
if (_nativeFunction_UnityRenderEvent == System.IntPtr.Zero)
|
||||
{
|
||||
_nativeFunction_UnityRenderEvent = Native.GetRenderEventFunc();
|
||||
}
|
||||
if (_nativeFunction_UnityRenderEvent != System.IntPtr.Zero)
|
||||
{
|
||||
GL.IssuePluginEvent(_nativeFunction_UnityRenderEvent, /*(int)Native.RenderThreadEvent.UpdateAllTextures*/1);
|
||||
}
|
||||
}
|
||||
|
||||
public static void IssueRenderThreadEvent_FreeAllTextures()
|
||||
{
|
||||
if (_nativeFunction_UnityRenderEvent == System.IntPtr.Zero)
|
||||
{
|
||||
_nativeFunction_UnityRenderEvent = Native.GetRenderEventFunc();
|
||||
}
|
||||
if (_nativeFunction_UnityRenderEvent != System.IntPtr.Zero)
|
||||
{
|
||||
GL.IssuePluginEvent(_nativeFunction_UnityRenderEvent, /*(int)Native.RenderThreadEvent.FreeTextures*/2);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public sealed partial class WindowsRtMediaPlayer
|
||||
{
|
||||
private static bool _isInitialised = false;
|
||||
private static string _version = "Plug-in not yet initialised";
|
||||
|
||||
private ulong _frameTimeStamp;
|
||||
private System.IntPtr _playerInstance;
|
||||
|
||||
class EyeTexture
|
||||
{
|
||||
public Texture2D texture = null;
|
||||
public System.IntPtr nativePointer = System.IntPtr.Zero;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (texture)
|
||||
{
|
||||
if (Application.isPlaying) { Texture2D.Destroy(texture); }
|
||||
else { Texture2D.DestroyImmediate(texture); }
|
||||
texture = null;
|
||||
}
|
||||
nativePointer = System.IntPtr.Zero;
|
||||
}
|
||||
}
|
||||
|
||||
private EyeTexture[] _eyeTextures = new EyeTexture[2];
|
||||
|
||||
public static bool InitialisePlatform()
|
||||
{
|
||||
if (!_isInitialised)
|
||||
{
|
||||
try
|
||||
{
|
||||
#if !UNITY_2019_3_OR_NEWER
|
||||
if (SystemInfo.graphicsDeviceType == UnityEngine.Rendering.GraphicsDeviceType.Direct3D12)
|
||||
{
|
||||
Debug.LogError("[AVProVideo] Direct3D 12 is not supported until Unity 2019.3");
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
if (SystemInfo.graphicsDeviceType == UnityEngine.Rendering.GraphicsDeviceType.Null ||
|
||||
SystemInfo.graphicsDeviceType == UnityEngine.Rendering.GraphicsDeviceType.Direct3D11 ||
|
||||
SystemInfo.graphicsDeviceType == UnityEngine.Rendering.GraphicsDeviceType.Direct3D12)
|
||||
{
|
||||
/*if (!Native.Init(QualitySettings.activeColorSpace == ColorSpace.Linear))
|
||||
{
|
||||
Debug.LogError("[AVProVideo] Failing to initialise platform");
|
||||
}
|
||||
else*/
|
||||
{
|
||||
_isInitialised = true;
|
||||
_version = Native.GetPluginVersion();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogError("[AVProVideo] Only Direct3D 11 and 12 are supported, graphicsDeviceType not supported: " + SystemInfo.graphicsDeviceType);
|
||||
}
|
||||
}
|
||||
catch (System.DllNotFoundException e)
|
||||
{
|
||||
Debug.LogError("[AVProVideo] Failed to load DLL. " + e.Message);
|
||||
}
|
||||
}
|
||||
|
||||
return _isInitialised;
|
||||
}
|
||||
|
||||
public static void DeinitPlatform()
|
||||
{
|
||||
//Native.Deinit();
|
||||
_isInitialised = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7b04c4ad4a3b8c44a98a08ea2ae71a6d
|
||||
timeCreated: 1541807235
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,48 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Copyright 2015-2021 RenderHeads Ltd. All rights reserved.
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
namespace RenderHeads.Media.AVProVideo
|
||||
{
|
||||
public class TextCue
|
||||
{
|
||||
private TextCue() { }
|
||||
|
||||
internal TextCue(string text)
|
||||
{
|
||||
Text = text;
|
||||
}
|
||||
|
||||
public string Text { get; private set; }
|
||||
}
|
||||
|
||||
public partial class BaseMediaPlayer : ITextTracks
|
||||
{
|
||||
protected TextCue _currentTextCue = null;
|
||||
public TextCue GetCurrentTextCue() { return _currentTextCue; } // Returns null when there is no active text
|
||||
|
||||
protected bool UpdateTextCue(bool force = false)
|
||||
{
|
||||
bool result = false;
|
||||
// Has it changed since the last 'tick'
|
||||
if (force || InternalIsChangedTextCue())
|
||||
{
|
||||
_currentTextCue = null;
|
||||
string text = InternalGetCurrentTextCue();
|
||||
if (!string.IsNullOrEmpty(text))
|
||||
{
|
||||
_currentTextCue = new TextCue(text);
|
||||
}
|
||||
result = true;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
internal abstract bool InternalIsChangedTextCue();
|
||||
internal abstract string InternalGetCurrentTextCue();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 70b7a3055e537f74cb49d2fc4e6989e6
|
||||
timeCreated: 1438695622
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,292 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Copyright 2015-2021 RenderHeads Ltd. All rights reserved.
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
namespace RenderHeads.Media.AVProVideo
|
||||
{
|
||||
public enum TrackType
|
||||
{
|
||||
Video,
|
||||
Audio,
|
||||
Text,
|
||||
}
|
||||
|
||||
public class TrackBase
|
||||
{
|
||||
protected TrackBase() { }
|
||||
|
||||
internal TrackBase(TrackType trackType, int uid, string name, string language, bool isDefault)
|
||||
{
|
||||
TrackType = trackType;
|
||||
Uid = uid;
|
||||
Name = name;
|
||||
Language = language;
|
||||
IsDefault = isDefault;
|
||||
DisplayName = CreateDisplayName();
|
||||
}
|
||||
|
||||
// The UID is unique to the media
|
||||
internal int Uid { get; private set; }
|
||||
|
||||
public TrackType TrackType { get; private set; }
|
||||
|
||||
public string DisplayName { get; private set; }
|
||||
|
||||
// Optional
|
||||
public string Name { get; private set; }
|
||||
|
||||
// Optional
|
||||
public string Language { get; private set; }
|
||||
|
||||
// Optional
|
||||
public bool IsDefault { get; private set; }
|
||||
|
||||
protected string CreateDisplayName()
|
||||
{
|
||||
string result;
|
||||
if (!string.IsNullOrEmpty(Name))
|
||||
{
|
||||
result = Name;
|
||||
}
|
||||
else
|
||||
{
|
||||
result = "Track " + Uid.ToString();
|
||||
}
|
||||
if (!string.IsNullOrEmpty(Language))
|
||||
{
|
||||
result = string.Format("{0} ({1})", result, Language);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
public abstract class TrackCollection : IEnumerable
|
||||
{
|
||||
public virtual TrackType TrackType { get; private set; }
|
||||
public abstract int Count { get; }
|
||||
public abstract IEnumerator GetEnumerator();
|
||||
|
||||
internal abstract void Clear();
|
||||
internal abstract void Add(TrackBase track);
|
||||
internal abstract bool HasActiveTrack();
|
||||
internal abstract bool IsActiveTrack(TrackBase track);
|
||||
internal abstract void SetActiveTrack(TrackBase track);
|
||||
internal abstract void SetFirstTrackActive();
|
||||
}
|
||||
|
||||
public class TrackCollection<T> : TrackCollection where T : TrackBase
|
||||
{
|
||||
internal TrackCollection() {}
|
||||
|
||||
public override IEnumerator GetEnumerator()
|
||||
{
|
||||
return _tracks.GetEnumerator();
|
||||
}
|
||||
|
||||
public T this[int index]
|
||||
{
|
||||
get
|
||||
{
|
||||
return _tracks[index];
|
||||
}
|
||||
}
|
||||
|
||||
internal T ActiveTrack { get; set; }
|
||||
|
||||
internal override bool HasActiveTrack() { return ActiveTrack != null; }
|
||||
|
||||
internal override bool IsActiveTrack(TrackBase track)
|
||||
{
|
||||
return (ActiveTrack == track);
|
||||
}
|
||||
|
||||
internal override void Clear()
|
||||
{
|
||||
_tracks.Clear();
|
||||
ActiveTrack = null;
|
||||
}
|
||||
|
||||
internal override void Add(TrackBase track)
|
||||
{
|
||||
_tracks.Add(track as T);
|
||||
}
|
||||
|
||||
internal override void SetActiveTrack(TrackBase track)
|
||||
{
|
||||
ActiveTrack = track as T;
|
||||
}
|
||||
|
||||
internal override void SetFirstTrackActive()
|
||||
{
|
||||
if (_tracks.Count > 0)
|
||||
{
|
||||
ActiveTrack = _tracks[0];
|
||||
}
|
||||
}
|
||||
|
||||
public override int Count { get{ return _tracks.Count; } }
|
||||
|
||||
internal List<T> _tracks = new List<T>(4);
|
||||
}
|
||||
|
||||
public class VideoTracks : TrackCollection<VideoTrack>
|
||||
{
|
||||
public override TrackType TrackType { get { return TrackType.Video; } }
|
||||
}
|
||||
|
||||
public class AudioTracks : TrackCollection<AudioTrack>
|
||||
{
|
||||
public override TrackType TrackType { get { return TrackType.Audio; } }
|
||||
}
|
||||
|
||||
public class TextTracks : TrackCollection<TextTrack>
|
||||
{
|
||||
public override TrackType TrackType { get { return TrackType.Text; } }
|
||||
}
|
||||
|
||||
public class VideoTrack : TrackBase
|
||||
{
|
||||
private VideoTrack() { }
|
||||
|
||||
internal VideoTrack(int uid, string name, string language, bool isDefault)
|
||||
: base(TrackType.Video, uid, name, language, isDefault) { }
|
||||
|
||||
// Optional
|
||||
public int Bitrate { get; set; }
|
||||
}
|
||||
|
||||
public class AudioTrack : TrackBase
|
||||
{
|
||||
private AudioTrack() { }
|
||||
|
||||
internal AudioTrack(int uid, string name, string language, bool isDefault)
|
||||
: base(TrackType.Audio, uid, name, language, isDefault) { }
|
||||
|
||||
// Optional
|
||||
public int Bitrate { get; private set; }
|
||||
|
||||
// Optional
|
||||
public int ChannelCount { get; private set; }
|
||||
}
|
||||
|
||||
public class TextTrack : TrackBase
|
||||
{
|
||||
private TextTrack() { }
|
||||
|
||||
internal TextTrack(int uid, string name, string language, bool isDefault)
|
||||
: base(TrackType.Text, uid, name, language, isDefault) { }
|
||||
}
|
||||
|
||||
public interface IVideoTracks
|
||||
{
|
||||
VideoTracks GetVideoTracks();
|
||||
VideoTrack GetActiveVideoTrack();
|
||||
void SetActiveVideoTrack(VideoTrack track);
|
||||
}
|
||||
|
||||
public interface IAudioTracks
|
||||
{
|
||||
AudioTracks GetAudioTracks();
|
||||
AudioTrack GetActiveAudioTrack();
|
||||
void SetActiveAudioTrack(AudioTrack track);
|
||||
}
|
||||
|
||||
public interface ITextTracks
|
||||
{
|
||||
TextTracks GetTextTracks();
|
||||
TextTrack GetActiveTextTrack();
|
||||
void SetActiveTextTrack(TextTrack track);
|
||||
TextCue GetCurrentTextCue();
|
||||
}
|
||||
|
||||
public partial class BaseMediaPlayer : IVideoTracks, IAudioTracks, ITextTracks
|
||||
{
|
||||
protected VideoTracks _videoTracks = new VideoTracks();
|
||||
protected AudioTracks _audioTracks = new AudioTracks();
|
||||
protected TextTracks _textTracks = new TextTracks();
|
||||
protected TrackCollection[] _trackCollections;
|
||||
|
||||
public VideoTracks GetVideoTracks() { return _videoTracks; }
|
||||
public AudioTracks GetAudioTracks() { return _audioTracks; }
|
||||
public TextTracks GetTextTracks() { return _textTracks; }
|
||||
public VideoTrack GetActiveVideoTrack() { return _videoTracks.ActiveTrack; }
|
||||
public AudioTrack GetActiveAudioTrack() { return _audioTracks.ActiveTrack; }
|
||||
public TextTrack GetActiveTextTrack() { return _textTracks.ActiveTrack; }
|
||||
public void SetActiveVideoTrack(VideoTrack track) { if (track != null) SetActiveTrack(_videoTracks, track); }
|
||||
public void SetActiveAudioTrack(AudioTrack track) { if (track != null) SetActiveTrack(_audioTracks, track); }
|
||||
public void SetActiveTextTrack(TextTrack track) { SetActiveTrack(_textTracks, track); }
|
||||
|
||||
internal abstract bool InternalIsChangedTracks(TrackType trackType);
|
||||
internal abstract int InternalGetTrackCount(TrackType trackType);
|
||||
internal abstract bool InternalSetActiveTrack(TrackType trackType, int trackUid);
|
||||
internal abstract TrackBase InternalGetTrackInfo(TrackType trackType, int trackIndex, ref bool isActiveTrack);
|
||||
|
||||
private void InitTracks()
|
||||
{
|
||||
_trackCollections = new TrackCollection[3] { _videoTracks, _audioTracks, _textTracks };
|
||||
}
|
||||
|
||||
protected void UpdateTracks()
|
||||
{
|
||||
foreach (TrackCollection trackCollection in _trackCollections)
|
||||
{
|
||||
if (InternalIsChangedTracks(trackCollection.TrackType))
|
||||
{
|
||||
PopulateTrackCollection(trackCollection);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void PopulateTrackCollection(TrackCollection collection)
|
||||
{
|
||||
collection.Clear();
|
||||
int trackCount = InternalGetTrackCount(collection.TrackType);
|
||||
for (int i = 0; i < trackCount; i++)
|
||||
{
|
||||
bool isActiveTrack = false;
|
||||
TrackBase track = InternalGetTrackInfo(collection.TrackType, i, ref isActiveTrack);
|
||||
if (track != null)
|
||||
{
|
||||
collection.Add(track);
|
||||
if (isActiveTrack)
|
||||
{
|
||||
collection.SetActiveTrack(track);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
UnityEngine.Debug.LogWarning(string.Format("[AVProVideo] Failed to enumerate {0} track {1} ", collection.TrackType, i));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void SetActiveTrack(TrackCollection collection, TrackBase track)
|
||||
{
|
||||
// Check if this is already the active track
|
||||
if (collection.IsActiveTrack(track)) return;
|
||||
|
||||
// Convert from TextTrack to uid
|
||||
int trackUid = -1;
|
||||
if (track != null)
|
||||
{
|
||||
trackUid = track.Uid;
|
||||
}
|
||||
|
||||
// Set track based on uid (-1 is no active track)
|
||||
// NOTE: TrackType is pulled from collection as track may be null
|
||||
if (InternalSetActiveTrack(collection.TrackType, trackUid))
|
||||
{
|
||||
collection.SetActiveTrack(track);
|
||||
switch (collection.TrackType)
|
||||
{
|
||||
case TrackType.Text:
|
||||
UpdateTextCue(force:true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 087e0a6fc1bd92e4bbd96796ec593162
|
||||
timeCreated: 1596803411
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,9 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c6f0eb1069a1ccc4b94ebe97c97b9cd1
|
||||
folderAsset: yes
|
||||
timeCreated: 1551721729
|
||||
licenseType: Pro
|
||||
DefaultImporter:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,127 @@
|
||||
using UnityEngine;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Copyright 2020-2021 RenderHeads Ltd. All rights reserved.
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
namespace RenderHeads.Media.AVProVideo
|
||||
{
|
||||
[System.Serializable]
|
||||
public struct HttpHeader
|
||||
{
|
||||
public string name;
|
||||
public string value;
|
||||
|
||||
public HttpHeader(string name, string value) { this.name = name; this.value = value; }
|
||||
|
||||
public bool IsComplete()
|
||||
{
|
||||
return (!string.IsNullOrEmpty(name) && !string.IsNullOrEmpty(value));
|
||||
}
|
||||
|
||||
public string ToValidatedString()
|
||||
{
|
||||
string result = null;
|
||||
if (IsComplete())
|
||||
{
|
||||
if (IsValid())
|
||||
{
|
||||
result = string.Format("{0}:{1}\r\n", name, value);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public static bool IsValid(string text)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(text))
|
||||
{
|
||||
if (!IsAscii(text)) return false;
|
||||
if (text.Contains("\r") || text.Contains("\n")) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool IsAscii(string text)
|
||||
{
|
||||
foreach (char c in text)
|
||||
{
|
||||
if (c >= 128) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool IsValid()
|
||||
{
|
||||
if (!IsValid(name) || !IsValid(value))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
// TODO: check via regular expression
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Data for handling custom HTTP header fields
|
||||
/// </summary>
|
||||
[System.Serializable]
|
||||
public class HttpHeaderData : IEnumerable
|
||||
{
|
||||
[SerializeField]
|
||||
private List<HttpHeader> httpHeaders = new List<HttpHeader>();
|
||||
|
||||
public IEnumerator GetEnumerator()
|
||||
{
|
||||
return httpHeaders.GetEnumerator();
|
||||
}
|
||||
|
||||
public HttpHeader this[int index]
|
||||
{
|
||||
get
|
||||
{
|
||||
return httpHeaders[index];
|
||||
}
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
httpHeaders.Clear();
|
||||
}
|
||||
|
||||
public void Add(string name, string value)
|
||||
{
|
||||
httpHeaders.Add(new HttpHeader(name, value));
|
||||
}
|
||||
|
||||
public bool IsModified()
|
||||
{
|
||||
return (httpHeaders != null && httpHeaders.Count > 0);
|
||||
}
|
||||
|
||||
public string ToValidatedString()
|
||||
{
|
||||
string result = string.Empty;
|
||||
foreach (HttpHeader header in httpHeaders)
|
||||
{
|
||||
if (header.IsComplete())
|
||||
{
|
||||
string line = header.ToValidatedString();
|
||||
if (!string.IsNullOrEmpty(line))
|
||||
{
|
||||
result += line;
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogWarning("[AVProVideo] Custom HTTP header field ignored due to invalid format");
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2cfc6f8c038acdf4a9b384e8cb5e9cb2
|
||||
timeCreated: 1588604301
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,55 @@
|
||||
using UnityEngine;
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Copyright 2020-2021 RenderHeads Ltd. All rights reserved.
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
namespace RenderHeads.Media.AVProVideo
|
||||
{
|
||||
/// <summary>
|
||||
/// Data for handling authentication of encrypted AES-128 HLS streams
|
||||
/// </summary>
|
||||
[System.Serializable]
|
||||
public class KeyAuthData : ISerializationCallbackReceiver
|
||||
{
|
||||
public string keyServerToken = null;
|
||||
|
||||
//public string keyServerURLOverride = null;
|
||||
|
||||
[SerializeField, Multiline]
|
||||
private string overrideDecryptionKeyBase64 = null;
|
||||
public byte[] overrideDecryptionKey = null;
|
||||
|
||||
public bool IsModified()
|
||||
{
|
||||
return (overrideDecryptionKey != null && overrideDecryptionKey.Length > 0)
|
||||
|| (string.IsNullOrEmpty(overrideDecryptionKeyBase64) == false);
|
||||
}
|
||||
|
||||
public void OnBeforeSerialize()
|
||||
{
|
||||
if (overrideDecryptionKey != null && !string.IsNullOrEmpty(overrideDecryptionKeyBase64))
|
||||
{
|
||||
overrideDecryptionKey = null;
|
||||
}
|
||||
}
|
||||
|
||||
public void OnAfterDeserialize()
|
||||
{
|
||||
if (string.IsNullOrEmpty(overrideDecryptionKeyBase64))
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
// Convert overrideDecryptionKeyBase64 to overrideDecryptionKey
|
||||
overrideDecryptionKey = System.Convert.FromBase64String(overrideDecryptionKeyBase64);
|
||||
}
|
||||
catch (System.FormatException e)
|
||||
{
|
||||
Debug.LogError("Failed to deserialize decryption key, error: " + e);
|
||||
overrideDecryptionKeyBase64 = null;
|
||||
overrideDecryptionKey = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0e784fab214313d44aaa5906743860fa
|
||||
timeCreated: 1588604301
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,594 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Copyright 2015-2021 RenderHeads Ltd. All rights reserved.
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
namespace RenderHeads.Media.AVProVideo
|
||||
{
|
||||
/// <summary>
|
||||
/// Utility class to resample MediaPlayer video frames to allow for smoother playback
|
||||
/// Keeps a buffer of frames with timestamps and presents them using its own clock
|
||||
/// </summary>
|
||||
public class Resampler
|
||||
{
|
||||
private class TimestampedRenderTexture
|
||||
{
|
||||
public RenderTexture texture = null;
|
||||
public long timestamp = 0;
|
||||
public bool used = false;
|
||||
}
|
||||
|
||||
public enum ResampleMode
|
||||
{
|
||||
POINT, LINEAR
|
||||
}
|
||||
|
||||
private List<TimestampedRenderTexture[]> _buffer = new List<TimestampedRenderTexture[]>();
|
||||
private MediaPlayer _mediaPlayer;
|
||||
private RenderTexture[] _outputTexture = null;
|
||||
|
||||
private int _start = 0;
|
||||
private int _end = 0;
|
||||
private int _bufferSize = 0;
|
||||
|
||||
private long _baseTimestamp = 0;
|
||||
private float _elapsedTimeSinceBase = 0f;
|
||||
|
||||
private Material _blendMat;
|
||||
|
||||
private ResampleMode _resampleMode;
|
||||
private string _name = "";
|
||||
|
||||
private long _lastTimeStamp = -1;
|
||||
|
||||
private int _droppedFrames = 0;
|
||||
|
||||
private long _lastDisplayedTimestamp = 0;
|
||||
private int _frameDisplayedTimer = 0;
|
||||
private long _currentDisplayedTimestamp = 0;
|
||||
|
||||
public int DroppedFrames
|
||||
{
|
||||
get { return _droppedFrames; }
|
||||
}
|
||||
|
||||
public int FrameDisplayedTimer
|
||||
{
|
||||
get { return _frameDisplayedTimer; }
|
||||
}
|
||||
|
||||
public long BaseTimestamp
|
||||
{
|
||||
get { return _baseTimestamp; }
|
||||
set { _baseTimestamp = value; }
|
||||
}
|
||||
|
||||
public float ElapsedTimeSinceBase
|
||||
{
|
||||
get { return _elapsedTimeSinceBase; }
|
||||
set { _elapsedTimeSinceBase = value; }
|
||||
}
|
||||
|
||||
public float LastT
|
||||
{
|
||||
get; private set;
|
||||
}
|
||||
|
||||
public long TextureTimeStamp
|
||||
{
|
||||
get; private set;
|
||||
}
|
||||
|
||||
private const string ShaderPropT = "_t";
|
||||
private const string ShaderPropAftertex = "_AfterTex";
|
||||
private int _propAfterTex;
|
||||
private int _propT;
|
||||
private float _videoFrameRate;
|
||||
|
||||
public void OnVideoEvent(MediaPlayer mp, MediaPlayerEvent.EventType et, ErrorCode errorCode)
|
||||
{
|
||||
switch (et)
|
||||
{
|
||||
case MediaPlayerEvent.EventType.MetaDataReady:
|
||||
_videoFrameRate = mp.Info.GetVideoFrameRate();
|
||||
_elapsedTimeSinceBase = 0f;
|
||||
if (_videoFrameRate > 0f)
|
||||
{
|
||||
_elapsedTimeSinceBase = _bufferSize / _videoFrameRate;
|
||||
}
|
||||
break;
|
||||
case MediaPlayerEvent.EventType.Closing:
|
||||
Reset();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public Resampler(MediaPlayer player, string name, int bufferSize = 2, ResampleMode resampleMode = ResampleMode.LINEAR)
|
||||
{
|
||||
_bufferSize = Mathf.Max(2, bufferSize);
|
||||
|
||||
player.Events.AddListener(OnVideoEvent);
|
||||
|
||||
_mediaPlayer = player;
|
||||
|
||||
Shader blendShader = Shader.Find("AVProVideo/Internal/BlendFrames");
|
||||
if (blendShader != null)
|
||||
{
|
||||
_blendMat = new Material(blendShader);
|
||||
_propT = Shader.PropertyToID(ShaderPropT);
|
||||
_propAfterTex = Shader.PropertyToID(ShaderPropAftertex);
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogError("[AVProVideo] Failed to find BlendFrames shader");
|
||||
}
|
||||
|
||||
_resampleMode = resampleMode;
|
||||
_name = name;
|
||||
|
||||
Debug.Log("[AVProVideo] Resampler " + _name + " started");
|
||||
}
|
||||
|
||||
public Texture[] OutputTexture
|
||||
{
|
||||
get { return _outputTexture; }
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
_lastTimeStamp = -1;
|
||||
_baseTimestamp = 0;
|
||||
InvalidateBuffer();
|
||||
}
|
||||
|
||||
public void Release()
|
||||
{
|
||||
ReleaseRenderTextures();
|
||||
if (_blendMat != null)
|
||||
{
|
||||
if (Application.isPlaying)
|
||||
{
|
||||
Material.Destroy(_blendMat);
|
||||
}
|
||||
else
|
||||
{
|
||||
Material.DestroyImmediate(_blendMat);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ReleaseRenderTextures()
|
||||
{
|
||||
for (int i = 0; i < _buffer.Count; ++i)
|
||||
{
|
||||
for (int j = 0; j < _buffer[i].Length; ++j)
|
||||
{
|
||||
if (_buffer[i][j].texture != null)
|
||||
{
|
||||
RenderTexture.ReleaseTemporary(_buffer[i][j].texture);
|
||||
_buffer[i][j].texture = null;
|
||||
}
|
||||
}
|
||||
|
||||
if (_outputTexture != null && _outputTexture[i] != null)
|
||||
{
|
||||
RenderTexture.ReleaseTemporary(_outputTexture[i]);
|
||||
}
|
||||
}
|
||||
|
||||
_outputTexture = null;
|
||||
}
|
||||
|
||||
private void ConstructRenderTextures()
|
||||
{
|
||||
ReleaseRenderTextures();
|
||||
_buffer.Clear();
|
||||
|
||||
_outputTexture = new RenderTexture[_mediaPlayer.TextureProducer.GetTextureCount()];
|
||||
|
||||
for (int i = 0; i < _mediaPlayer.TextureProducer.GetTextureCount(); ++i)
|
||||
{
|
||||
Texture tex = _mediaPlayer.TextureProducer.GetTexture(i);
|
||||
_buffer.Add(new TimestampedRenderTexture[_bufferSize]);
|
||||
for (int j = 0; j < _bufferSize; ++j)
|
||||
{
|
||||
_buffer[i][j] = new TimestampedRenderTexture();
|
||||
}
|
||||
|
||||
for (int j = 0; j < _buffer[i].Length; ++j)
|
||||
{
|
||||
_buffer[i][j].texture = RenderTexture.GetTemporary(tex.width, tex.height, 0);
|
||||
_buffer[i][j].timestamp = 0;
|
||||
_buffer[i][j].used = false;
|
||||
}
|
||||
|
||||
_outputTexture[i] = RenderTexture.GetTemporary(tex.width, tex.height, 0);
|
||||
_outputTexture[i].filterMode = tex.filterMode;
|
||||
_outputTexture[i].wrapMode = tex.wrapMode;
|
||||
_outputTexture[i].anisoLevel = tex.anisoLevel;
|
||||
// TODO: set up the mips level too?
|
||||
}
|
||||
}
|
||||
|
||||
private bool CheckRenderTexturesValid()
|
||||
{
|
||||
for (int i = 0; i < _mediaPlayer.TextureProducer.GetTextureCount(); ++i)
|
||||
{
|
||||
Texture tex = _mediaPlayer.TextureProducer.GetTexture(i);
|
||||
for (int j = 0; j < _buffer.Count; ++j)
|
||||
{
|
||||
if (_buffer[i][j].texture == null || _buffer[i][j].texture.width != tex.width || _buffer[i][j].texture.height != tex.height)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (_outputTexture == null || _outputTexture[i] == null || _outputTexture[i].width != tex.width || _outputTexture[i].height != tex.height)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//finds closest frame that occurs before given index
|
||||
private int FindBeforeFrameIndex(int frameIdx)
|
||||
{
|
||||
if (frameIdx >= _buffer.Count)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
int foundFrame = -1;
|
||||
float smallestDif = float.MaxValue;
|
||||
int closest = -1;
|
||||
float smallestElapsed = float.MaxValue;
|
||||
|
||||
for (int i = 0; i < _buffer[frameIdx].Length; ++i)
|
||||
{
|
||||
if (_buffer[frameIdx][i].used)
|
||||
{
|
||||
float elapsed = (_buffer[frameIdx][i].timestamp - _baseTimestamp) / 10000000f;
|
||||
|
||||
//keep track of closest after frame, just in case no before frame was found
|
||||
if (elapsed < smallestElapsed)
|
||||
{
|
||||
closest = i;
|
||||
smallestElapsed = elapsed;
|
||||
}
|
||||
|
||||
float dif = _elapsedTimeSinceBase - elapsed;
|
||||
|
||||
if (dif >= 0 && dif < smallestDif)
|
||||
{
|
||||
smallestDif = dif;
|
||||
foundFrame = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (foundFrame < 0)
|
||||
{
|
||||
if (closest < 0)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
return closest;
|
||||
}
|
||||
|
||||
return foundFrame;
|
||||
}
|
||||
|
||||
private int FindClosestFrame(int frameIdx)
|
||||
{
|
||||
if (frameIdx >= _buffer.Count)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
int foundPos = -1;
|
||||
float smallestDif = float.MaxValue;
|
||||
|
||||
for (int i = 0; i < _buffer[frameIdx].Length; ++i)
|
||||
{
|
||||
if (_buffer[frameIdx][i].used)
|
||||
{
|
||||
float elapsed = (_buffer[frameIdx][i].timestamp - _baseTimestamp) / 10000000f;
|
||||
float dif = Mathf.Abs(_elapsedTimeSinceBase - elapsed);
|
||||
if (dif < smallestDif)
|
||||
{
|
||||
foundPos = i;
|
||||
smallestDif = dif;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return foundPos;
|
||||
}
|
||||
|
||||
//point update selects closest frame and uses that as output
|
||||
private void PointUpdate()
|
||||
{
|
||||
for (int i = 0; i < _buffer.Count; ++i)
|
||||
{
|
||||
int frameIndex = FindClosestFrame(i);
|
||||
if (frameIndex < 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
_outputTexture[i].DiscardContents();
|
||||
Graphics.Blit(_buffer[i][frameIndex].texture, _outputTexture[i]);
|
||||
TextureTimeStamp = _currentDisplayedTimestamp = _buffer[i][frameIndex].timestamp;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//Updates currently displayed frame
|
||||
private void SampleFrame(int frameIdx, int bufferIdx)
|
||||
{
|
||||
_outputTexture[bufferIdx].DiscardContents();
|
||||
Graphics.Blit(_buffer[bufferIdx][frameIdx].texture, _outputTexture[bufferIdx]);
|
||||
TextureTimeStamp = _currentDisplayedTimestamp = _buffer[bufferIdx][frameIdx].timestamp;
|
||||
}
|
||||
|
||||
//Same as sample frame, but does a lerp of the two given frames and outputs that image instead
|
||||
private void SampleFrames(int bufferIdx, int frameIdx1, int frameIdx2, float t)
|
||||
{
|
||||
_blendMat.SetFloat(_propT, t);
|
||||
_blendMat.SetTexture(_propAfterTex, _buffer[bufferIdx][frameIdx2].texture);
|
||||
_outputTexture[bufferIdx].DiscardContents();
|
||||
Graphics.Blit(_buffer[bufferIdx][frameIdx1].texture, _outputTexture[bufferIdx], _blendMat);
|
||||
TextureTimeStamp = (long)Mathf.Lerp(_buffer[bufferIdx][frameIdx1].timestamp, _buffer[bufferIdx][frameIdx2].timestamp, t);
|
||||
_currentDisplayedTimestamp = _buffer[bufferIdx][frameIdx1].timestamp;
|
||||
}
|
||||
|
||||
private void LinearUpdate()
|
||||
{
|
||||
for (int i = 0; i < _buffer.Count; ++i)
|
||||
{
|
||||
//find closest frame
|
||||
int frameIndex = FindBeforeFrameIndex(i);
|
||||
|
||||
//no valid frame, this should never ever happen actually...
|
||||
if (frameIndex < 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
//resample or just use last frame and set current elapsed time to that frame
|
||||
float frameElapsed = (_buffer[i][frameIndex].timestamp - _baseTimestamp) / 10000000f;
|
||||
if (frameElapsed > _elapsedTimeSinceBase)
|
||||
{
|
||||
SampleFrame(frameIndex, i);
|
||||
LastT = -1f;
|
||||
}
|
||||
else
|
||||
{
|
||||
int next = (frameIndex + 1) % _buffer[i].Length;
|
||||
float nextElapsed = (_buffer[i][next].timestamp - _baseTimestamp) / 10000000f;
|
||||
|
||||
//no larger frame, move elapsed time back a bit since we cant predict the future
|
||||
if (nextElapsed < frameElapsed)
|
||||
{
|
||||
SampleFrame(frameIndex, i);
|
||||
LastT = 2f;
|
||||
}
|
||||
//have a before and after frame, interpolate
|
||||
else
|
||||
{
|
||||
|
||||
float range = nextElapsed - frameElapsed;
|
||||
float t = (_elapsedTimeSinceBase - frameElapsed) / range;
|
||||
SampleFrames(i, frameIndex, next, t);
|
||||
LastT = t;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void InvalidateBuffer()
|
||||
{
|
||||
_elapsedTimeSinceBase = (_bufferSize / 2) / _videoFrameRate;
|
||||
|
||||
for (int i = 0; i < _buffer.Count; ++i)
|
||||
{
|
||||
for (int j = 0; j < _buffer[i].Length; ++j)
|
||||
{
|
||||
_buffer[i][j].used = false;
|
||||
}
|
||||
}
|
||||
|
||||
_start = _end = 0;
|
||||
}
|
||||
|
||||
private float GuessFrameRate()
|
||||
{
|
||||
int fpsCount = 0;
|
||||
long fps = 0;
|
||||
|
||||
for (int k = 0; k < _buffer[0].Length; k++)
|
||||
{
|
||||
if (_buffer[0][k].used)
|
||||
{
|
||||
// Find the pair with the smallest difference
|
||||
long smallestDiff = long.MaxValue;
|
||||
for (int j = k + 1; j < _buffer[0].Length; j++)
|
||||
{
|
||||
if (_buffer[0][j].used)
|
||||
{
|
||||
long diff = System.Math.Abs(_buffer[0][k].timestamp - _buffer[0][j].timestamp);
|
||||
if (diff < smallestDiff)
|
||||
{
|
||||
smallestDiff = diff;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (smallestDiff != long.MaxValue)
|
||||
{
|
||||
fps += smallestDiff;
|
||||
fpsCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (fpsCount > 1)
|
||||
{
|
||||
fps /= fpsCount;
|
||||
}
|
||||
return 10000000f / (float)fps;
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
if (_mediaPlayer.TextureProducer == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
//recreate textures if invalid
|
||||
if (_mediaPlayer.TextureProducer == null || _mediaPlayer.TextureProducer.GetTexture() == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!CheckRenderTexturesValid())
|
||||
{
|
||||
ConstructRenderTextures();
|
||||
}
|
||||
|
||||
long currentTimestamp = _mediaPlayer.TextureProducer.GetTextureTimeStamp();
|
||||
|
||||
//if frame has been updated, do a calculation to estimate dropped frames
|
||||
if (currentTimestamp != _lastTimeStamp)
|
||||
{
|
||||
float dif = Mathf.Abs(currentTimestamp - _lastTimeStamp);
|
||||
float frameLength = (10000000f / _videoFrameRate);
|
||||
if (dif > frameLength * 1.1f && dif < frameLength * 3.1f)
|
||||
{
|
||||
_droppedFrames += (int)((dif - frameLength) / frameLength + 0.5);
|
||||
}
|
||||
_lastTimeStamp = currentTimestamp;
|
||||
}
|
||||
|
||||
//Adding texture to buffer logic
|
||||
long timestamp = _mediaPlayer.TextureProducer.GetTextureTimeStamp();
|
||||
bool insertNewFrame = !_mediaPlayer.Control.IsSeeking();
|
||||
//if buffer is not empty, we need to check if we need to reject the new frame
|
||||
if (_start != _end || _buffer[0][_end].used)
|
||||
{
|
||||
int lastFrame = (_end + _buffer[0].Length - 1) % _buffer[0].Length;
|
||||
//frame is not new and thus we do not need to store it
|
||||
if (timestamp == _buffer[0][lastFrame].timestamp)
|
||||
{
|
||||
insertNewFrame = false;
|
||||
}
|
||||
}
|
||||
|
||||
bool bufferWasNotFull = (_start != _end) || (!_buffer[0][_end].used);
|
||||
|
||||
if (insertNewFrame)
|
||||
{
|
||||
//buffer empty, reset base timestamp to current
|
||||
if (_start == _end && !_buffer[0][_end].used)
|
||||
{
|
||||
_baseTimestamp = timestamp;
|
||||
}
|
||||
|
||||
//update buffer counters, if buffer is full, we get rid of the earliest frame by incrementing the start counter
|
||||
if (_end == _start && _buffer[0][_end].used)
|
||||
{
|
||||
_start = (_start + 1) % _buffer[0].Length;
|
||||
}
|
||||
|
||||
for (int i = 0; i < _mediaPlayer.TextureProducer.GetTextureCount(); ++i)
|
||||
{
|
||||
Texture currentTexture = _mediaPlayer.TextureProducer.GetTexture(i);
|
||||
|
||||
//store frame info
|
||||
_buffer[i][_end].texture.DiscardContents();
|
||||
Graphics.Blit(currentTexture, _buffer[i][_end].texture);
|
||||
_buffer[i][_end].timestamp = timestamp;
|
||||
_buffer[i][_end].used = true;
|
||||
}
|
||||
|
||||
_end = (_end + 1) % _buffer[0].Length;
|
||||
}
|
||||
|
||||
bool bufferNotFull = (_start != _end) || (!_buffer[0][_end].used);
|
||||
|
||||
if (bufferNotFull)
|
||||
{
|
||||
for (int i = 0; i < _buffer.Count; ++i)
|
||||
{
|
||||
_outputTexture[i].DiscardContents();
|
||||
Graphics.Blit(_buffer[i][_start].texture, _outputTexture[i]);
|
||||
_currentDisplayedTimestamp = _buffer[i][_start].timestamp;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// If we don't have a valid frame rate and the buffer is now full, guess the frame rate by looking at the buffered timestamps
|
||||
if (bufferWasNotFull && _videoFrameRate <= 0f)
|
||||
{
|
||||
_videoFrameRate = GuessFrameRate();
|
||||
_elapsedTimeSinceBase = (_bufferSize / 2) / _videoFrameRate;
|
||||
}
|
||||
}
|
||||
|
||||
if (_mediaPlayer.Control.IsPaused())
|
||||
{
|
||||
InvalidateBuffer();
|
||||
}
|
||||
|
||||
//we always wait until buffer is full before display things, just assign first frame in buffer to output so that the user can see something
|
||||
if (bufferNotFull)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_mediaPlayer.Control.IsPlaying() && !_mediaPlayer.Control.IsFinished())
|
||||
{
|
||||
//correct elapsed time if too far out
|
||||
long ts = _buffer[0][(_start + _bufferSize / 2) % _bufferSize].timestamp - _baseTimestamp;
|
||||
double dif = Mathf.Abs(((float)((double)_elapsedTimeSinceBase * 10000000) - ts));
|
||||
double threshold = (_buffer[0].Length / 2) / _videoFrameRate * 10000000;
|
||||
|
||||
if (dif > threshold)
|
||||
{
|
||||
_elapsedTimeSinceBase = ts / 10000000f;
|
||||
}
|
||||
|
||||
if (_resampleMode == ResampleMode.POINT)
|
||||
{
|
||||
PointUpdate();
|
||||
}
|
||||
else if (_resampleMode == ResampleMode.LINEAR)
|
||||
{
|
||||
LinearUpdate();
|
||||
}
|
||||
|
||||
_elapsedTimeSinceBase += Time.unscaledDeltaTime;
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdateTimestamp()
|
||||
{
|
||||
if (_lastDisplayedTimestamp != _currentDisplayedTimestamp)
|
||||
{
|
||||
_lastDisplayedTimestamp = _currentDisplayedTimestamp;
|
||||
_frameDisplayedTimer = 0;
|
||||
}
|
||||
_frameDisplayedTimer++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8ac8dc09faa6b1d48bf6f490c9888550
|
||||
timeCreated: 1497356591
|
||||
licenseType: Free
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,144 @@
|
||||
using UnityEngine;
|
||||
using System.Collections.Generic;
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Copyright 2015-2021 RenderHeads Ltd. All rights reserved.
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
namespace RenderHeads.Media.AVProVideo
|
||||
{
|
||||
public class Subtitle
|
||||
{
|
||||
public int index;
|
||||
// Rich string can contain <font color=""> <u> etc
|
||||
public string text;
|
||||
public double timeStart, timeEnd;
|
||||
|
||||
public bool IsBefore(double time)
|
||||
{
|
||||
return (time > timeStart && time > timeEnd);
|
||||
}
|
||||
|
||||
public bool IsTime(double time)
|
||||
{
|
||||
return (time >= timeStart && time < timeEnd);
|
||||
}
|
||||
}
|
||||
|
||||
public class SubtitlePlayer
|
||||
{
|
||||
// min time, max time
|
||||
// set time
|
||||
// event for change(subs added, subs removed)
|
||||
// list of subs on
|
||||
}
|
||||
|
||||
public class SubtitleUtils
|
||||
{
|
||||
/// <summary>
|
||||
/// Parse time in format: 00:00:48,924 and convert to seconds
|
||||
/// </summary>
|
||||
private static double ParseTimeToSeconds(string text)
|
||||
{
|
||||
double result = 0.0;
|
||||
|
||||
string[] digits = text.Split(new char[] { ':', ',' });
|
||||
|
||||
if (digits.Length == 4)
|
||||
{
|
||||
int hours = int.Parse(digits[0]);
|
||||
int minutes = int.Parse(digits[1]);
|
||||
int seconds = int.Parse(digits[2]);
|
||||
int milliseconds = int.Parse(digits[3]);
|
||||
|
||||
result = (milliseconds / 1000.0) + (seconds + (minutes + (hours * 60)) * 60);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parse subtitles in the SRT format and convert to a list of ordered Subtitle objects
|
||||
/// </summary>
|
||||
public static List<Subtitle> ParseSubtitlesSRT(string data)
|
||||
{
|
||||
List<Subtitle> result = null;
|
||||
|
||||
if (!string.IsNullOrEmpty(data))
|
||||
{
|
||||
data = data.Trim();
|
||||
var rx = new System.Text.RegularExpressions.Regex("\n\r|\r\n|\n|\r");
|
||||
string[] lines = rx.Split(data);
|
||||
|
||||
if (lines.Length >= 3)
|
||||
{
|
||||
result = new List<Subtitle>(256);
|
||||
|
||||
int count = 0;
|
||||
int index = 0;
|
||||
Subtitle subtitle = null;
|
||||
for (int i = 0; i < lines.Length; i++)
|
||||
{
|
||||
if (index == 0)
|
||||
{
|
||||
subtitle = new Subtitle();
|
||||
subtitle.index = count;// int.Parse(lines[i]);
|
||||
}
|
||||
else if (index == 1)
|
||||
{
|
||||
string[] times = lines[i].Split(new string[] { " --> " }, System.StringSplitOptions.RemoveEmptyEntries);
|
||||
if (times.Length == 2)
|
||||
{
|
||||
subtitle.timeStart = ParseTimeToSeconds(times[0]);
|
||||
subtitle.timeEnd = ParseTimeToSeconds(times[1]);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new System.FormatException("SRT format doesn't appear to be valid");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!string.IsNullOrEmpty(lines[i]))
|
||||
{
|
||||
if (index == 2)
|
||||
{
|
||||
subtitle.text = lines[i];
|
||||
}
|
||||
else
|
||||
{
|
||||
subtitle.text += "\n" + lines[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(lines[i]) && index > 1)
|
||||
{
|
||||
result.Add(subtitle);
|
||||
index = 0;
|
||||
count++;
|
||||
subtitle = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
index++;
|
||||
}
|
||||
}
|
||||
|
||||
// Handle the last one
|
||||
if (subtitle != null)
|
||||
{
|
||||
result.Add(subtitle);
|
||||
subtitle = null;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogWarning("[AVProVideo] SRT format doesn't appear to be valid");
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c21f230642ee9284eb9726613241c7bd
|
||||
timeCreated: 1548861442
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,651 @@
|
||||
#if UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX || UNITY_IOS || UNITY_TVOS
|
||||
#define UNITY_PLATFORM_SUPPORTS_YPCBCR
|
||||
#endif
|
||||
|
||||
#if UNITY_EDITOR || UNITY_STANDALONE_OSX || UNITY_STANDALONE_WIN || UNITY_IOS || UNITY_TVOS || UNITY_ANDROID || (UNITY_WEBGL && UNITY_2017_2_OR_NEWER)
|
||||
#define UNITY_PLATFORM_SUPPORTS_LINEAR
|
||||
#endif
|
||||
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Rendering;
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Copyright 2015-2022 RenderHeads Ltd. All rights reserved.
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
namespace RenderHeads.Media.AVProVideo
|
||||
{
|
||||
#if AVPRO_FEATURE_VIDEORESOLVE
|
||||
[System.Serializable]
|
||||
public class VideoResolve : ITextureProducer
|
||||
{
|
||||
[SerializeField] VideoResolveOptions _options = VideoResolveOptions.Create();
|
||||
[SerializeField] RenderTexture _targetRenderTexture = null;
|
||||
[SerializeField] ScaleMode _targetRenderTextureScale = ScaleMode.ScaleToFit;
|
||||
|
||||
void SetSource(ITextureProducer textureSource)
|
||||
{
|
||||
//_commandBuffer.IssuePluginEvent(blahCallback, 0);
|
||||
//Graphics.ExecuteCommandBuffer(_commandBuffer);
|
||||
}
|
||||
|
||||
// ITextureProducer implementation
|
||||
|
||||
/// <inheritdoc/>
|
||||
public int GetTextureCount() { return 1; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Texture GetTexture(int index = 0) { return _texture; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public int GetTextureFrameCount() { return _textureSource.GetTextureFrameCount(); }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool SupportsTextureFrameCount() { return _textureSource.SupportsTextureFrameCount(); }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public long GetTextureTimeStamp() { return _textureSource.GetTextureTimeStamp(); }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool RequiresVerticalFlip() { return false; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public StereoPacking GetTextureStereoPacking() { return StereoPacking.None; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public TransparencyMode GetTextureTransparency() { return TransparencyMode.Transparent; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public AlphaPacking GetTextureAlphaPacking() { return AlphaPacking.None; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Matrix4x4 GetYpCbCrTransform() { return Matrix4x4.identity; }
|
||||
|
||||
private ITextureProducer _textureSource;
|
||||
private Texture _texture;
|
||||
private CommandBuffer _commandBuffer;
|
||||
}
|
||||
#endif
|
||||
|
||||
public struct LazyShaderProperty
|
||||
{
|
||||
public LazyShaderProperty(string name)
|
||||
{
|
||||
_name = name;
|
||||
_id = 0;
|
||||
}
|
||||
|
||||
public string Name { get { return _name;} }
|
||||
public int Id { get { if (_id == 0) { _id = Shader.PropertyToID(_name); } return _id; } }
|
||||
|
||||
private string _name;
|
||||
private int _id;
|
||||
}
|
||||
|
||||
/// <summary>Helper class for everything related to setting up materials for rendering/resolving videos</summary>
|
||||
public class VideoRender
|
||||
{
|
||||
public const string Shader_IMGUI = "AVProVideo/Internal/IMGUI/Texture Transparent";
|
||||
public const string Shader_Resolve = "AVProVideo/Internal/Resolve";
|
||||
public const string Shader_ResolveOES = "AVProVideo/Internal/ResolveOES";
|
||||
public const string Shader_Preview = "AVProVideo/Internal/Preview";
|
||||
|
||||
#if UNITY_PLATFORM_SUPPORTS_YPCBCR
|
||||
public const string Keyword_UseYpCbCr = "USE_YPCBCR";
|
||||
#endif
|
||||
public const string Keyword_AlphaPackTopBottom = "ALPHAPACK_TOP_BOTTOM";
|
||||
public const string Keyword_AlphaPackLeftRight = "ALPHAPACK_LEFT_RIGHT";
|
||||
public const string Keyword_AlphaPackNone = "ALPHAPACK_NONE";
|
||||
public const string Keyword_StereoTopBottom = "STEREO_TOP_BOTTOM";
|
||||
public const string Keyword_StereoLeftRight = "STEREO_LEFT_RIGHT";
|
||||
public const string Keyword_StereoCustomUV = "STEREO_CUSTOM_UV";
|
||||
public const string Keyword_StereoTwoTextures = "STEREO_TWOTEXTURES";
|
||||
public const string Keyword_StereoNone = "MONOSCOPIC";
|
||||
public const string Keyword_StereoDebug = "STEREO_DEBUG";
|
||||
public const string Keyword_LayoutEquirect180 = "LAYOUT_EQUIRECT180";
|
||||
public const string Keyword_LayoutNone = "LAYOUT_NONE";
|
||||
public const string Keyword_ForceEyeNone = "FORCEEYE_NONE";
|
||||
public const string Keyword_ForceEyeLeft = "FORCEEYE_LEFT";
|
||||
public const string Keyword_ForceEyeRight = "FORCEEYE_RIGHT";
|
||||
public const string Keyword_ApplyGamma = "APPLY_GAMMA";
|
||||
|
||||
public static readonly LazyShaderProperty PropChromaTex = new LazyShaderProperty("_ChromaTex");
|
||||
|
||||
#if UNITY_PLATFORM_SUPPORTS_YPCBCR
|
||||
public static readonly LazyShaderProperty PropYpCbCrTransform = new LazyShaderProperty("_YpCbCrTransform");
|
||||
public static readonly LazyShaderProperty PropUseYpCbCr = new LazyShaderProperty("_UseYpCbCr");
|
||||
#endif
|
||||
|
||||
public static readonly LazyShaderProperty PropVertScale = new LazyShaderProperty("_VertScale");
|
||||
public static readonly LazyShaderProperty PropApplyGamma = new LazyShaderProperty("_ApplyGamma");
|
||||
public static readonly LazyShaderProperty PropStereo = new LazyShaderProperty("Stereo");
|
||||
public static readonly LazyShaderProperty PropAlphaPack = new LazyShaderProperty("AlphaPack");
|
||||
public static readonly LazyShaderProperty PropLayout = new LazyShaderProperty("Layout");
|
||||
public static readonly LazyShaderProperty PropViewMatrix = new LazyShaderProperty("_ViewMatrix");
|
||||
public static readonly LazyShaderProperty PropTextureMatrix = new LazyShaderProperty("_TextureMatrix");
|
||||
|
||||
public static string Keyword_UseHSBC = "USE_HSBC";
|
||||
public static readonly LazyShaderProperty PropHue = new LazyShaderProperty("_Hue");
|
||||
public static readonly LazyShaderProperty PropSaturation = new LazyShaderProperty("_Saturation");
|
||||
public static readonly LazyShaderProperty PropContrast = new LazyShaderProperty("_Contrast");
|
||||
public static readonly LazyShaderProperty PropBrightness = new LazyShaderProperty("_Brightness");
|
||||
public static readonly LazyShaderProperty PropInvGamma = new LazyShaderProperty("_InvGamma");
|
||||
|
||||
public static Material CreateResolveMaterial(bool usingAndroidOES)
|
||||
{
|
||||
return new Material(Shader.Find( usingAndroidOES ? VideoRender.Shader_ResolveOES : VideoRender.Shader_Resolve ));
|
||||
}
|
||||
|
||||
public static Material CreateIMGUIMaterial()
|
||||
{
|
||||
return new Material(Shader.Find(VideoRender.Shader_Preview));
|
||||
}
|
||||
|
||||
public static void SetupLayoutMaterial(Material material, VideoMapping mapping)
|
||||
{
|
||||
switch (mapping)
|
||||
{
|
||||
default:
|
||||
material.DisableKeyword(Keyword_LayoutEquirect180);
|
||||
material.EnableKeyword(Keyword_LayoutNone);
|
||||
break;
|
||||
// Only EquiRectangular180 currently does anything in the shader
|
||||
case VideoMapping.EquiRectangular180:
|
||||
material.DisableKeyword(Keyword_LayoutNone);
|
||||
material.EnableKeyword(Keyword_LayoutEquirect180);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public static void SetupStereoEyeModeMaterial(Material material, StereoEye mode)
|
||||
{
|
||||
switch (mode)
|
||||
{
|
||||
case StereoEye.Both:
|
||||
material.DisableKeyword(Keyword_ForceEyeLeft);
|
||||
material.DisableKeyword(Keyword_ForceEyeRight);
|
||||
material.EnableKeyword(Keyword_ForceEyeNone);
|
||||
break;
|
||||
case StereoEye.Left:
|
||||
material.DisableKeyword(Keyword_ForceEyeNone);
|
||||
material.DisableKeyword(Keyword_ForceEyeRight);
|
||||
material.EnableKeyword(Keyword_ForceEyeLeft);
|
||||
break;
|
||||
case StereoEye.Right:
|
||||
material.DisableKeyword(Keyword_ForceEyeNone);
|
||||
material.DisableKeyword(Keyword_ForceEyeLeft);
|
||||
material.EnableKeyword(Keyword_ForceEyeRight);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public static void SetupStereoMaterial(Material material, StereoPacking packing)
|
||||
{
|
||||
switch (packing)
|
||||
{
|
||||
case StereoPacking.None:
|
||||
material.DisableKeyword(Keyword_StereoTopBottom);
|
||||
material.DisableKeyword(Keyword_StereoLeftRight);
|
||||
material.DisableKeyword(Keyword_StereoCustomUV);
|
||||
material.DisableKeyword(Keyword_StereoTwoTextures);
|
||||
material.EnableKeyword(Keyword_StereoNone);
|
||||
break;
|
||||
case StereoPacking.TopBottom:
|
||||
material.DisableKeyword(Keyword_StereoNone);
|
||||
material.DisableKeyword(Keyword_StereoLeftRight);
|
||||
material.DisableKeyword(Keyword_StereoCustomUV);
|
||||
material.DisableKeyword(Keyword_StereoTwoTextures);
|
||||
material.EnableKeyword(Keyword_StereoTopBottom);
|
||||
break;
|
||||
case StereoPacking.LeftRight:
|
||||
material.DisableKeyword(Keyword_StereoNone);
|
||||
material.DisableKeyword(Keyword_StereoTopBottom);
|
||||
material.DisableKeyword(Keyword_StereoTwoTextures);
|
||||
material.DisableKeyword(Keyword_StereoCustomUV);
|
||||
material.EnableKeyword(Keyword_StereoLeftRight);
|
||||
break;
|
||||
case StereoPacking.CustomUV:
|
||||
material.DisableKeyword(Keyword_StereoNone);
|
||||
material.DisableKeyword(Keyword_StereoTopBottom);
|
||||
material.DisableKeyword(Keyword_StereoLeftRight);
|
||||
material.DisableKeyword(Keyword_StereoTwoTextures);
|
||||
material.EnableKeyword(Keyword_StereoCustomUV);
|
||||
break;
|
||||
case StereoPacking.TwoTextures:
|
||||
material.DisableKeyword(Keyword_StereoNone);
|
||||
material.DisableKeyword(Keyword_StereoTopBottom);
|
||||
material.DisableKeyword(Keyword_StereoLeftRight);
|
||||
material.DisableKeyword(Keyword_StereoCustomUV);
|
||||
material.EnableKeyword(Keyword_StereoTwoTextures);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public static void SetupGlobalDebugStereoTinting(bool enabled)
|
||||
{
|
||||
if (enabled)
|
||||
{
|
||||
Shader.EnableKeyword(Keyword_StereoDebug);
|
||||
}
|
||||
else
|
||||
{
|
||||
Shader.DisableKeyword(Keyword_StereoDebug);
|
||||
}
|
||||
}
|
||||
|
||||
public static void SetupAlphaPackedMaterial(Material material, AlphaPacking packing)
|
||||
{
|
||||
switch (packing)
|
||||
{
|
||||
case AlphaPacking.None:
|
||||
material.DisableKeyword(Keyword_AlphaPackTopBottom);
|
||||
material.DisableKeyword(Keyword_AlphaPackLeftRight);
|
||||
material.EnableKeyword(Keyword_AlphaPackNone);
|
||||
break;
|
||||
case AlphaPacking.TopBottom:
|
||||
material.DisableKeyword(Keyword_AlphaPackNone);
|
||||
material.DisableKeyword(Keyword_AlphaPackLeftRight);
|
||||
material.EnableKeyword(Keyword_AlphaPackTopBottom);
|
||||
break;
|
||||
case AlphaPacking.LeftRight:
|
||||
material.DisableKeyword(Keyword_AlphaPackNone);
|
||||
material.DisableKeyword(Keyword_AlphaPackTopBottom);
|
||||
material.EnableKeyword(Keyword_AlphaPackLeftRight);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public static void SetupGammaMaterial(Material material, bool playerSupportsLinear)
|
||||
{
|
||||
#if UNITY_PLATFORM_SUPPORTS_LINEAR
|
||||
if (QualitySettings.activeColorSpace == ColorSpace.Linear && !playerSupportsLinear)
|
||||
{
|
||||
material.EnableKeyword(Keyword_ApplyGamma);
|
||||
}
|
||||
else
|
||||
{
|
||||
material.DisableKeyword(Keyword_ApplyGamma);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
public static void SetupTextureMatrix(Material material, float[] transform)
|
||||
{
|
||||
#if (!UNITY_EDITOR && UNITY_ANDROID)
|
||||
// STE: HasProperty doesn't work on Matrix'
|
||||
// if (material != null && (material.HasProperty(VideoRender.PropTextureMatrix.Id)))
|
||||
{
|
||||
if (transform != null)
|
||||
{
|
||||
Matrix4x4 m = new Matrix4x4(new Vector4( transform[0], transform[1], transform[2], transform[3] ),
|
||||
new Vector4( transform[4], transform[5], transform[6], transform[7] ),
|
||||
new Vector4( transform[8], transform[9], transform[10], transform[11] ),
|
||||
new Vector4( transform[12], transform[13], transform[14], transform[15] ));
|
||||
material.SetMatrix(VideoRender.PropTextureMatrix.Id, m);
|
||||
}
|
||||
else
|
||||
{
|
||||
material.SetMatrix(VideoRender.PropTextureMatrix.Id, Matrix4x4.identity);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#if UNITY_PLATFORM_SUPPORTS_YPCBCR
|
||||
public static void SetupYpCbCrMaterial(Material material, bool enable, Matrix4x4 transform, Texture texture)
|
||||
{
|
||||
if (material.HasProperty(VideoRender.PropUseYpCbCr.Id))
|
||||
{
|
||||
if (enable)
|
||||
{
|
||||
material.EnableKeyword(VideoRender.Keyword_UseYpCbCr);
|
||||
material.SetMatrix(VideoRender.PropYpCbCrTransform.Id, transform);
|
||||
material.SetTexture(VideoRender.PropChromaTex.Id, texture);
|
||||
}
|
||||
else
|
||||
{
|
||||
material.DisableKeyword(VideoRender.Keyword_UseYpCbCr);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
public static void SetupVerticalFlipMaterial(Material material, bool flip)
|
||||
{
|
||||
material.SetFloat(VideoRender.PropVertScale.Id, flip?-1f:1f);
|
||||
}
|
||||
|
||||
public static Texture GetTexture(MediaPlayer mediaPlayer, int textureIndex)
|
||||
{
|
||||
Texture result = null;
|
||||
if (mediaPlayer != null)
|
||||
{
|
||||
if (mediaPlayer.UseResampler && mediaPlayer.FrameResampler != null && mediaPlayer.FrameResampler.OutputTexture != null)
|
||||
{
|
||||
if ( mediaPlayer.FrameResampler.OutputTexture.Length > textureIndex)
|
||||
{
|
||||
result = mediaPlayer.FrameResampler.OutputTexture[textureIndex];
|
||||
}
|
||||
}
|
||||
else if (mediaPlayer.TextureProducer != null)
|
||||
{
|
||||
if (mediaPlayer.TextureProducer.GetTextureCount() > textureIndex)
|
||||
{
|
||||
result = mediaPlayer.TextureProducer.GetTexture(textureIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public static void SetupMaterialForMedia(Material material, MediaPlayer mediaPlayer, int texturePropId = -1, Texture fallbackTexture = null, bool forceFallbackTexture = false)
|
||||
{
|
||||
Debug.Assert(material != null);
|
||||
if (mediaPlayer != null)
|
||||
{
|
||||
Texture mainTexture = GetTexture(mediaPlayer, 0);
|
||||
Texture yCbCrTexture = GetTexture(mediaPlayer, 1);
|
||||
|
||||
if (texturePropId != -1)
|
||||
{
|
||||
if (mainTexture == null || forceFallbackTexture)
|
||||
{
|
||||
mainTexture = fallbackTexture;
|
||||
}
|
||||
material.SetTexture(texturePropId, mainTexture);
|
||||
}
|
||||
|
||||
SetupMaterial(material,
|
||||
(mediaPlayer.TextureProducer != null)?mediaPlayer.TextureProducer.RequiresVerticalFlip():false,
|
||||
(mediaPlayer.Info != null)?mediaPlayer.Info.PlayerSupportsLinearColorSpace():true,
|
||||
(mediaPlayer.TextureProducer != null)?mediaPlayer.TextureProducer.GetYpCbCrTransform():Matrix4x4.identity,
|
||||
yCbCrTexture,
|
||||
(mediaPlayer.Info != null && mediaPlayer.PlatformOptionsAndroid.useFastOesPath)?mediaPlayer.Info.GetTextureTransform():null,
|
||||
mediaPlayer.VideoLayoutMapping,
|
||||
(mediaPlayer.TextureProducer != null)?mediaPlayer.TextureProducer.GetTextureStereoPacking():StereoPacking.None,
|
||||
(mediaPlayer.TextureProducer != null)?mediaPlayer.TextureProducer.GetTextureAlphaPacking():AlphaPacking.None);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (texturePropId != -1)
|
||||
{
|
||||
material.SetTexture(texturePropId, fallbackTexture);
|
||||
}
|
||||
SetupMaterial(material, false, true, Matrix4x4.identity, null);
|
||||
}
|
||||
}
|
||||
|
||||
internal static void SetupMaterial(Material material, bool flipVertically, bool playerSupportsLinear, Matrix4x4 ycbcrTransform, Texture ycbcrTexture = null, float[] textureTransform = null,
|
||||
VideoMapping mapping = VideoMapping.Normal, StereoPacking stereoPacking = StereoPacking.None, AlphaPacking alphaPacking = AlphaPacking.None)
|
||||
{
|
||||
SetupVerticalFlipMaterial(material, flipVertically);
|
||||
|
||||
// Apply changes for layout
|
||||
if (material.HasProperty(VideoRender.PropLayout.Id))
|
||||
{
|
||||
VideoRender.SetupLayoutMaterial(material, mapping);
|
||||
}
|
||||
|
||||
// Apply changes for stereo videos
|
||||
if (material.HasProperty(VideoRender.PropStereo.Id))
|
||||
{
|
||||
VideoRender.SetupStereoMaterial(material, stereoPacking);
|
||||
}
|
||||
|
||||
// Apply changes for alpha videos
|
||||
if (material.HasProperty(VideoRender.PropAlphaPack.Id))
|
||||
{
|
||||
VideoRender.SetupAlphaPackedMaterial(material, alphaPacking);
|
||||
}
|
||||
|
||||
// Apply gamma correction
|
||||
#if UNITY_PLATFORM_SUPPORTS_LINEAR
|
||||
if (material.HasProperty(VideoRender.PropApplyGamma.Id))
|
||||
{
|
||||
VideoRender.SetupGammaMaterial(material, playerSupportsLinear);
|
||||
}
|
||||
#endif
|
||||
|
||||
// Adjust for cropping (when the decoder decodes in blocks that overrun the video frame size, it pads), OES only as we apply this lower down for none-OES
|
||||
#if (!UNITY_EDITOR && UNITY_ANDROID)
|
||||
// STE: HasProperty doesn't work on Matrix'
|
||||
// if (material.HasProperty(VideoRender.PropTextureMatrix.Id))
|
||||
{
|
||||
VideoRender.SetupTextureMatrix(material, textureTransform);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if UNITY_PLATFORM_SUPPORTS_YPCBCR
|
||||
VideoRender.SetupYpCbCrMaterial(material, ycbcrTexture != null, ycbcrTransform, ycbcrTexture);
|
||||
#endif
|
||||
}
|
||||
|
||||
[System.Flags]
|
||||
public enum ResolveFlags : int
|
||||
{
|
||||
Mipmaps = 1 << 0,
|
||||
PackedAlpha = 1 << 1,
|
||||
StereoLeft = 1 << 2,
|
||||
StereoRight = 1 << 3,
|
||||
ColorspaceSRGB = 1 << 4,
|
||||
}
|
||||
|
||||
public static void SetupResolveMaterial(Material material, VideoResolveOptions options)
|
||||
{
|
||||
if (options.IsColourAdjust())
|
||||
{
|
||||
material.EnableKeyword(VideoRender.Keyword_UseHSBC);
|
||||
material.SetFloat(VideoRender.PropHue.Id, options.hue);
|
||||
material.SetFloat(VideoRender.PropSaturation.Id, options.saturation);
|
||||
material.SetFloat(VideoRender.PropBrightness.Id, options.brightness);
|
||||
material.SetFloat(VideoRender.PropContrast.Id, options.contrast);
|
||||
material.SetFloat(VideoRender.PropInvGamma.Id, 1f / options.gamma);
|
||||
}
|
||||
else
|
||||
{
|
||||
material.DisableKeyword(VideoRender.Keyword_UseHSBC);
|
||||
}
|
||||
|
||||
material.color = options.tint;
|
||||
}
|
||||
|
||||
public static RenderTexture ResolveVideoToRenderTexture(Material resolveMaterial, RenderTexture targetTexture, ITextureProducer texture, ResolveFlags flags, ScaleMode scaleMode = ScaleMode.StretchToFill)
|
||||
{
|
||||
int targetWidth = texture.GetTexture(0).width;
|
||||
int targetHeight = texture.GetTexture(0).height;
|
||||
|
||||
StereoEye eyeMode = StereoEye.Both;
|
||||
if (((flags & ResolveFlags.StereoLeft) == ResolveFlags.StereoLeft) &&
|
||||
((flags & ResolveFlags.StereoRight) != ResolveFlags.StereoRight))
|
||||
{
|
||||
eyeMode = StereoEye.Left;
|
||||
}
|
||||
else if (((flags & ResolveFlags.StereoLeft) != ResolveFlags.StereoLeft) &&
|
||||
((flags & ResolveFlags.StereoRight) == ResolveFlags.StereoRight))
|
||||
{
|
||||
eyeMode = StereoEye.Right;
|
||||
}
|
||||
|
||||
// RJT NOTE: No longer passing in PAR as combined with larger videos (e.g. 8K+) it can lead to textures >16K which most platforms don't support
|
||||
// - Instead, the PAR is accounted for during drawing (which is more efficient too)
|
||||
// - https://github.com/RenderHeads/UnityPlugin-AVProVideo/issues/1297
|
||||
float pixelAspectRatio = 1.0f; // texture.GetTexturePixelAspectRatio();
|
||||
GetResolveTextureSize(
|
||||
texture.GetTextureAlphaPacking(),
|
||||
texture.GetTextureStereoPacking(),
|
||||
eyeMode,
|
||||
pixelAspectRatio,
|
||||
ref targetWidth,
|
||||
ref targetHeight);
|
||||
|
||||
if (targetTexture)
|
||||
{
|
||||
bool sizeChanged = (targetTexture.width != targetWidth) || (targetTexture.height != targetHeight);
|
||||
if (sizeChanged)
|
||||
{
|
||||
RenderTexture.ReleaseTemporary(targetTexture);
|
||||
targetTexture = null;
|
||||
}
|
||||
}
|
||||
|
||||
if (!targetTexture)
|
||||
{
|
||||
RenderTextureReadWrite readWrite = ((flags & ResolveFlags.ColorspaceSRGB) == ResolveFlags.ColorspaceSRGB) ? RenderTextureReadWrite.sRGB : RenderTextureReadWrite.Linear;
|
||||
targetTexture = RenderTexture.GetTemporary(targetWidth, targetHeight, 0, RenderTextureFormat.ARGB32, readWrite);
|
||||
}
|
||||
|
||||
// Set target mipmap generation support
|
||||
{
|
||||
bool requiresMipmap = (flags & ResolveFlags.Mipmaps) == ResolveFlags.Mipmaps;
|
||||
bool requiresRecreate = (targetTexture.IsCreated() && targetTexture.useMipMap != requiresMipmap);
|
||||
if (requiresRecreate)
|
||||
{
|
||||
targetTexture.Release();
|
||||
}
|
||||
if (!targetTexture.IsCreated())
|
||||
{
|
||||
targetTexture.useMipMap = targetTexture.autoGenerateMips = requiresMipmap;
|
||||
targetTexture.Create();
|
||||
}
|
||||
}
|
||||
|
||||
// Render resolve blit
|
||||
// TODO: combine these two paths into a single material blit
|
||||
{
|
||||
bool prevSRGB = GL.sRGBWrite;
|
||||
GL.sRGBWrite = targetTexture.sRGB;
|
||||
RenderTexture prev = RenderTexture.active;
|
||||
if (scaleMode == ScaleMode.StretchToFill)
|
||||
{
|
||||
Graphics.Blit(texture.GetTexture(0), targetTexture, resolveMaterial);
|
||||
}
|
||||
else
|
||||
{
|
||||
RenderTexture.active = targetTexture;
|
||||
bool partialAreaRender = (scaleMode == ScaleMode.ScaleToFit);
|
||||
if (partialAreaRender)
|
||||
{
|
||||
GL.Clear(false, true, Color.black);
|
||||
}
|
||||
VideoRender.DrawTexture(new Rect(0f, 0f, targetTexture.width, targetTexture.height), texture.GetTexture(0), scaleMode, texture.GetTextureAlphaPacking(), texture.GetTexturePixelAspectRatio(), resolveMaterial);
|
||||
}
|
||||
RenderTexture.active = prev;
|
||||
GL.sRGBWrite = prevSRGB;
|
||||
}
|
||||
|
||||
return targetTexture;
|
||||
}
|
||||
|
||||
public static void GetResolveTextureSize(AlphaPacking alphaPacking, StereoPacking stereoPacking, StereoEye eyeMode, float pixelAspectRatio, ref int width, ref int height)
|
||||
{
|
||||
switch (alphaPacking)
|
||||
{
|
||||
case AlphaPacking.LeftRight:
|
||||
width /= 2;
|
||||
break;
|
||||
case AlphaPacking.TopBottom:
|
||||
height /= 2;
|
||||
break;
|
||||
}
|
||||
|
||||
if (eyeMode != StereoEye.Both)
|
||||
{
|
||||
switch (stereoPacking)
|
||||
{
|
||||
case StereoPacking.LeftRight:
|
||||
width /= 2;
|
||||
break;
|
||||
case StereoPacking.TopBottom:
|
||||
height /= 2;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (pixelAspectRatio > 0f)
|
||||
{
|
||||
if (pixelAspectRatio > 1f)
|
||||
{
|
||||
width = Mathf.RoundToInt(width * pixelAspectRatio);
|
||||
}
|
||||
else if (pixelAspectRatio < 1f)
|
||||
{
|
||||
height = Mathf.RoundToInt(height / pixelAspectRatio);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: take into account rotation
|
||||
}
|
||||
|
||||
public static bool RequiresResolve(ITextureProducer texture)
|
||||
{
|
||||
return (texture.GetTextureAlphaPacking() != AlphaPacking.None ||
|
||||
texture.RequiresVerticalFlip() ||
|
||||
texture.GetTextureStereoPacking() != StereoPacking.None ||
|
||||
texture.GetTextureCount() > 1
|
||||
);
|
||||
}
|
||||
|
||||
public static void DrawTexture(Rect destRect, Texture texture, ScaleMode scaleMode, AlphaPacking alphaPacking, float pixelAspectRatio, Material material)
|
||||
{
|
||||
if (Event.current == null || Event.current.type == EventType.Repaint)
|
||||
{
|
||||
int sourceWidth = texture.width;
|
||||
int sourceHeight = texture.height;
|
||||
GetResolveTextureSize(alphaPacking, StereoPacking.Unknown, StereoEye.Both, pixelAspectRatio, ref sourceWidth, ref sourceHeight);
|
||||
|
||||
float sourceRatio = (float)sourceWidth / (float)sourceHeight;
|
||||
Rect sourceRect = new Rect(0f, 0f, 1f, 1f);
|
||||
switch (scaleMode)
|
||||
{
|
||||
case ScaleMode.ScaleAndCrop:
|
||||
{
|
||||
float destRatio = destRect.width / destRect.height;
|
||||
if (destRatio > sourceRatio)
|
||||
{
|
||||
float adjust = sourceRatio / destRatio;
|
||||
sourceRect = new Rect(0f, (1f - adjust) * 0.5f, 1f, adjust);
|
||||
}
|
||||
else
|
||||
{
|
||||
float adjust = destRatio / sourceRatio;
|
||||
sourceRect = new Rect(0.5f - adjust * 0.5f, 0f, adjust, 1f);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case ScaleMode.ScaleToFit:
|
||||
{
|
||||
float destRatio = destRect.width / destRect.height;
|
||||
if (destRatio > sourceRatio)
|
||||
{
|
||||
float adjust = sourceRatio / destRatio;
|
||||
destRect = new Rect(destRect.xMin + destRect.width * (1f - adjust) * 0.5f, destRect.yMin, adjust * destRect.width, destRect.height);
|
||||
}
|
||||
else
|
||||
{
|
||||
float adjust = destRatio / sourceRatio;
|
||||
destRect = new Rect(destRect.xMin, destRect.yMin + destRect.height * (1f - adjust) * 0.5f, destRect.width, adjust * destRect.height);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case ScaleMode.StretchToFill:
|
||||
break;
|
||||
}
|
||||
|
||||
GL.PushMatrix();
|
||||
if (RenderTexture.active == null)
|
||||
{
|
||||
//GL.LoadPixelMatrix();
|
||||
GL.LoadPixelMatrix(0f, Screen.width, Screen.height, 0f);
|
||||
}
|
||||
else
|
||||
{
|
||||
GL.LoadPixelMatrix(0f, RenderTexture.active.width, RenderTexture.active.height, 0f);
|
||||
}
|
||||
Graphics.DrawTexture(destRect, texture, sourceRect, 0, 0, 0, 0, GUI.color, material);
|
||||
GL.PopMatrix();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a928f61fef33d1d4986b2190310027bc
|
||||
timeCreated: 1547737745
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user