- 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,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:
|
||||
Reference in New Issue
Block a user