- 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:
Jamie Greunbaum
2025-05-03 01:57:06 -04:00
parent dff0b4003c
commit ef9ccfef19
1183 changed files with 189695 additions and 2153 deletions
@@ -0,0 +1,9 @@
fileFormatVersion: 2
guid: 9c5f6cc7a68822c4c906fad89505801a
folderAsset: yes
timeCreated: 1592333515
licenseType: Pro
DefaultImporter:
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,118 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace RenderHeads.Media.AVProVideo
{
[System.Serializable]
[CreateAssetMenu(fileName = "MediaReference", menuName = "AVPro Video/Media Reference", order = 51)]
public class MediaReference : ScriptableObject
{
[SerializeField] string _alias = string.Empty;
public string Alias { get { return _alias; } set { _alias = value; } }
[SerializeField] MediaPath _mediaPath = new MediaPath();
public MediaPath MediaPath { get { return _mediaPath; } set { _mediaPath = value; } }
[Header("Media Hints")]
[SerializeField] MediaHints _hints = MediaHints.Default;
public MediaHints Hints { get { return _hints; } set { _hints = value; } }
[Header("Platform Overrides")]
[SerializeField] MediaReference _macOS = null;
[SerializeField] MediaReference _windows = null;
[SerializeField] MediaReference _android = null;
[SerializeField] MediaReference _iOS = null;
[SerializeField] MediaReference _tvOS = null;
[SerializeField] MediaReference _windowsUWP = null;
[SerializeField] MediaReference _webGL = null;
#if UNITY_EDITOR
[SerializeField, HideInInspector] byte[] _preview = null;
public Texture2D GeneratePreview(Texture2D texture)
{
_preview = null;
if (texture)
{
texture.Apply(true, false);
_preview = texture.GetRawTextureData();
}
UnityEditor.EditorUtility.SetDirty(this);
return texture;
}
public bool GetPreview(Texture2D texture)
{
if (_preview != null && _preview.Length > 0 && _preview.Length > 128*128*4)
{
texture.LoadRawTextureData(_preview);
texture.Apply(true, false);
return true;
}
return false;
}
#endif
public MediaReference GetCurrentPlatformMediaReference()
{
MediaReference result = null;
#if (UNITY_EDITOR_OSX && UNITY_IOS) || (!UNITY_EDITOR && UNITY_IOS)
result = GetPlatformMediaReference(Platform.iOS);
#elif (UNITY_EDITOR_OSX && UNITY_TVOS) || (!UNITY_EDITOR && UNITY_TVOS)
result = GetPlatformMediaReference(Platform.tvOS);
#elif (UNITY_EDITOR_OSX || (!UNITY_EDITOR && UNITY_STANDALONE_OSX))
result = GetPlatformMediaReference(Platform.MacOSX);
#elif (UNITY_EDITOR_WIN) || (!UNITY_EDITOR && UNITY_STANDALONE_WIN)
result = GetPlatformMediaReference(Platform.Windows);
#elif (!UNITY_EDITOR && UNITY_WSA_10_0)
result = GetPlatformMediaReference(Platform.WindowsUWP);
#elif (!UNITY_EDITOR && UNITY_ANDROID)
result = GetPlatformMediaReference(Platform.Android);
#elif (!UNITY_EDITOR && UNITY_WEBGL)
result = GetPlatformMediaReference(Platform.WebGL);
#endif
if (result == null)
{
result = this;
}
return result;
}
public MediaReference GetPlatformMediaReference(Platform platform)
{
MediaReference result = null;
switch (platform)
{
case Platform.iOS:
result = _iOS;
break;
case Platform.tvOS:
result = _tvOS;
break;
case Platform.MacOSX:
result = _macOS;
break;
case Platform.Windows:
result = _windows;
break;
case Platform.WindowsUWP:
result = _windowsUWP;
break;
case Platform.Android:
result = _android;
break;
case Platform.WebGL:
result = _webGL;
break;
}
return result;
}
}
}
@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 8b1c70b7e7502564e93d418de9017d1f
timeCreated: 1592337480
licenseType: Pro
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {fileID: 2800000, guid: bb83b41b53a59874692b83eab5873998, type: 3}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,9 @@
fileFormatVersion: 2
guid: 40d7664ce355730488a96ff5305f1b5d
folderAsset: yes
timeCreated: 1438698284
licenseType: Pro
DefaultImporter:
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,231 @@
#if UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX || UNITY_IOS || UNITY_TVOS
#define UNITY_PLATFORM_SUPPORTS_YPCBCR
#endif
using UnityEngine;
//-----------------------------------------------------------------------------
// Copyright 2015-2022 RenderHeads Ltd. All rights reserved.
//-----------------------------------------------------------------------------
namespace RenderHeads.Media.AVProVideo
{
/// <summary>
/// Sets up a material to display the video from a MediaPlayer
/// </summary>
[AddComponentMenu("AVPro Video/Apply To Material", 300)]
[HelpURL("https://www.renderheads.com/products/avpro-video/")]
public sealed class ApplyToMaterial : ApplyToBase
{
[Header("Display")]
[Space(8f)]
[Tooltip("Default texture to display when the video texture is preparing")]
[SerializeField] Texture2D _defaultTexture = null;
public Texture2D DefaultTexture
{
get { return _defaultTexture; }
set { if (_defaultTexture != value) { _defaultTexture = value; _isDirty = true; } }
}
[Space(8f)]
[Header("Material Target")]
[SerializeField] Material _material = null;
public Material Material
{
get { return _material; }
set { if (_material != value) { _material = value; _isDirty = true; } }
}
[SerializeField] string _texturePropertyName = Helper.UnityBaseTextureName;
public string TexturePropertyName
{
get { return _texturePropertyName; }
set
{
if (_texturePropertyName != value)
{
_texturePropertyName = value;
// TODO: if the property changes, remove it from the perioud SetTexture()
_propTexture = new LazyShaderProperty(_texturePropertyName);
_isDirty = true;
}
}
}
[SerializeField] Vector2 _offset = Vector2.zero;
public Vector2 Offset
{
get { return _offset; }
set { if (_offset != value) { _offset = value; _isDirty = true; } }
}
[SerializeField] Vector2 _scale = Vector2.one;
public Vector2 Scale
{
get { return _scale; }
set { if (_scale != value) { _scale = value; _isDirty = true; } }
}
private Texture _lastTextureApplied;
private LazyShaderProperty _propTexture;
private Texture _originalTexture;
private Vector2 _originalScale = Vector2.one;
private Vector2 _originalOffset = Vector2.zero;
// We do a LateUpdate() to allow for any changes in the texture that may have happened in Update()
private void LateUpdate()
{
Apply();
}
public override void Apply()
{
bool applied = false;
if (_media != null && _media.TextureProducer != null)
{
Texture resamplerTex = _media.FrameResampler == null || _media.FrameResampler.OutputTexture == null ? null : _media.FrameResampler.OutputTexture[0];
Texture texture = _media.UseResampler ? resamplerTex : _media.TextureProducer.GetTexture(0);
if (texture != null)
{
// Check for changing texture
if (texture != _lastTextureApplied)
{
_isDirty = true;
}
if (_isDirty)
{
int planeCount = _media.UseResampler ? 1 : _media.TextureProducer.GetTextureCount();
for (int plane = 0; plane < planeCount; ++plane)
{
Texture resamplerTexPlane = _media.FrameResampler == null || _media.FrameResampler.OutputTexture == null ? null : _media.FrameResampler.OutputTexture[plane];
texture = _media.UseResampler ? resamplerTexPlane : _media.TextureProducer.GetTexture(plane);
if (texture != null)
{
ApplyMapping(texture, _media.TextureProducer.RequiresVerticalFlip(), plane);
}
}
}
applied = true;
}
}
// If the media didn't apply a texture, then try to apply the default texture
if (!applied)
{
if (_defaultTexture != _lastTextureApplied)
{
_isDirty = true;
}
if (_isDirty)
{
#if UNITY_PLATFORM_SUPPORTS_YPCBCR
if (_material != null && _material.HasProperty(VideoRender.PropUseYpCbCr.Id))
{
_material.DisableKeyword(VideoRender.Keyword_UseYpCbCr);
}
#endif
ApplyMapping(_defaultTexture, false);
}
}
}
private void ApplyMapping(Texture texture, bool requiresYFlip, int plane = 0)
{
if (_material != null)
{
_isDirty = false;
if (plane == 0)
{
VideoRender.SetupMaterialForMedia(_material, _media, _propTexture.Id, texture, texture == _defaultTexture);
_lastTextureApplied = texture;
#if (!UNITY_EDITOR && UNITY_ANDROID)
if (texture == _defaultTexture) { _material.EnableKeyword("USING_DEFAULT_TEXTURE"); }
else { _material.DisableKeyword("USING_DEFAULT_TEXTURE"); }
#endif
if (texture != null)
{
if (requiresYFlip)
{
_material.SetTextureScale(_propTexture.Id, new Vector2(_scale.x, -_scale.y));
_material.SetTextureOffset(_propTexture.Id, Vector2.up + _offset);
}
else
{
_material.SetTextureScale(_propTexture.Id, _scale);
_material.SetTextureOffset(_propTexture.Id, _offset);
}
}
}
else if (plane == 1)
{
if (texture != null)
{
if (requiresYFlip)
{
_material.SetTextureScale(VideoRender.PropChromaTex.Id, new Vector2(_scale.x, -_scale.y));
_material.SetTextureOffset(VideoRender.PropChromaTex.Id, Vector2.up + _offset);
}
else
{
_material.SetTextureScale(VideoRender.PropChromaTex.Id, _scale);
_material.SetTextureOffset(VideoRender.PropChromaTex.Id, _offset);
}
}
}
}
}
protected override void SaveProperties()
{
if (_material != null)
{
if (string.IsNullOrEmpty(_texturePropertyName))
{
_originalTexture = _material.mainTexture;
_originalScale = _material.mainTextureScale;
_originalOffset = _material.mainTextureOffset;
}
else
{
_originalTexture = _material.GetTexture(_texturePropertyName);
_originalScale = _material.GetTextureScale(_texturePropertyName);
_originalOffset = _material.GetTextureOffset(_texturePropertyName);
}
}
_propTexture = new LazyShaderProperty(_texturePropertyName);
}
protected override void RestoreProperties()
{
if (_material != null)
{
if (string.IsNullOrEmpty(_texturePropertyName))
{
_material.mainTexture = _originalTexture;
_material.mainTextureScale = _originalScale;
_material.mainTextureOffset = _originalOffset;
}
else
{
_material.SetTexture(_texturePropertyName, _originalTexture);
_material.SetTextureScale(_texturePropertyName, _originalScale);
_material.SetTextureOffset(_texturePropertyName, _originalOffset);
}
}
}
}
}
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: d2feedce2e2e63647b8f875ec0894a15
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {fileID: 2800000, guid: bb83b41b53a59874692b83eab5873998, type: 3}
userData:
@@ -0,0 +1,248 @@
using UnityEngine;
using UnityEngine.Serialization;
//-----------------------------------------------------------------------------
// Copyright 2015-2022 RenderHeads Ltd. All rights reserved.
//-----------------------------------------------------------------------------
namespace RenderHeads.Media.AVProVideo
{
/// <summary>
/// Sets up a mesh to display the video from a MediaPlayer
/// </summary>
[AddComponentMenu("AVPro Video/Apply To Mesh", 300)]
[HelpURL("https://www.renderheads.com/products/avpro-video/")]
public sealed class ApplyToMesh : ApplyToBase
{
// TODO: add specific material / material index to target in the mesh if there are multiple materials
[Space(8f)]
[Header("Display")]
[Tooltip("Default texture to display when the video texture is preparing")]
[SerializeField] Texture2D _defaultTexture = null;
public Texture2D DefaultTexture
{
get { return _defaultTexture; }
set { ChangeDefaultTexture(value); }
}
[Space(8f)]
[FormerlySerializedAs("_mesh")]
[Header("Renderer Target")]
[SerializeField] Renderer _renderer = null;
public Renderer MeshRenderer
{
get { return _renderer; }
set { ChangeRenderer(value); }
}
[SerializeField] int _materialIndex = -1;
public int MaterialIndex
{
get { return _materialIndex; }
set { _materialIndex = value; }
}
private void ChangeDefaultTexture(Texture2D texture)
{
if (_defaultTexture != texture)
{
_defaultTexture = texture;
ForceUpdate();
}
}
private void ChangeRenderer(Renderer renderer)
{
if (_renderer != renderer)
{
if (_renderer)
{
// TODO: Remove from renderer
}
_renderer = renderer;
if (_renderer)
{
ForceUpdate();
}
}
}
[SerializeField] string _texturePropertyName = Helper.UnityBaseTextureName;
public string TexturePropertyName
{
get { return _texturePropertyName; }
set
{
if (_texturePropertyName != value)
{
_texturePropertyName = value;
// TODO: if the property changes, remove it from the perioud SetTexture()
_propTexture = new LazyShaderProperty(_texturePropertyName);
_isDirty = true;
}
}
}
[SerializeField] Vector2 _offset = Vector2.zero;
public Vector2 Offset
{
get { return _offset; }
set { if (_offset != value) { _offset = value; _isDirty = true; } }
}
[SerializeField] Vector2 _scale = Vector2.one;
public Vector2 Scale
{
get { return _scale; }
set { if (_scale != value) { _scale = value; _isDirty = true; } }
}
private Texture _lastTextureApplied;
private LazyShaderProperty _propTexture;
// We do a LateUpdate() to allow for any changes in the texture that may have happened in Update()
private void LateUpdate()
{
Apply();
}
public override void Apply()
{
bool applied = false;
// Try to apply texture from media
if (_media != null && _media.TextureProducer != null)
{
Texture resamplerTex = _media.FrameResampler == null || _media.FrameResampler.OutputTexture == null ? null : _media.FrameResampler.OutputTexture[0];
Texture texture = _media.UseResampler ? resamplerTex : _media.TextureProducer.GetTexture(0);
if (texture != null)
{
// Check for changing texture
if (texture != _lastTextureApplied)
{
_isDirty = true;
}
if (_isDirty)
{
int planeCount = _media.UseResampler ? 1 : _media.TextureProducer.GetTextureCount();
for (int plane = 0; plane < planeCount; plane++)
{
Texture resamplerTexPlane = _media.FrameResampler == null || _media.FrameResampler.OutputTexture == null ? null : _media.FrameResampler.OutputTexture[plane];
texture = _media.UseResampler ? resamplerTexPlane : _media.TextureProducer.GetTexture(plane);
if (texture != null)
{
ApplyMapping(texture, _media.TextureProducer.RequiresVerticalFlip(), plane, _materialIndex);
}
}
}
applied = true;
}
}
// If the media didn't apply a texture, then try to apply the default texture
if (!applied)
{
if (_defaultTexture != _lastTextureApplied)
{
_isDirty = true;
}
if (_isDirty)
{
ApplyMapping(_defaultTexture, false, 0, _materialIndex);
}
}
}
private void ApplyMapping(Texture texture, bool requiresYFlip, int plane, int materialIndex = -1)
{
if (_renderer != null)
{
_isDirty = false;
Material[] meshMaterials = _renderer.materials;
if (meshMaterials != null)
{
for (int i = 0; i < meshMaterials.Length; i++)
{
if (_materialIndex < 0 || i == _materialIndex)
{
Material mat = meshMaterials[i];
if (mat != null)
{
if (plane == 0)
{
VideoRender.SetupMaterialForMedia(mat, _media, _propTexture.Id, texture, texture == _defaultTexture);
_lastTextureApplied = texture;
#if (!UNITY_EDITOR && UNITY_ANDROID)
if(texture == _defaultTexture) { mat.EnableKeyword("USING_DEFAULT_TEXTURE"); }
else { mat.DisableKeyword("USING_DEFAULT_TEXTURE"); }
#endif
if (texture != null)
{
if (requiresYFlip)
{
mat.SetTextureScale(_propTexture.Id, new Vector2(_scale.x, -_scale.y));
mat.SetTextureOffset(_propTexture.Id, Vector2.up + _offset);
}
else
{
mat.SetTextureScale(_propTexture.Id, _scale);
mat.SetTextureOffset(_propTexture.Id, _offset);
}
}
}
else if (plane == 1)
{
if (texture != null)
{
if (requiresYFlip)
{
mat.SetTextureScale(VideoRender.PropChromaTex.Id, new Vector2(_scale.x, -_scale.y));
mat.SetTextureOffset(VideoRender.PropChromaTex.Id, Vector2.up + _offset);
}
else
{
mat.SetTextureScale(VideoRender.PropChromaTex.Id, _scale);
mat.SetTextureOffset(VideoRender.PropChromaTex.Id, _offset);
}
}
}
}
}
}
}
}
}
protected override void OnEnable()
{
if (_renderer == null)
{
_renderer = this.GetComponent<MeshRenderer>();
if (_renderer == null)
{
Debug.LogWarning("[AVProVideo] No MeshRenderer set or found in gameobject");
}
}
_propTexture = new LazyShaderProperty(_texturePropertyName);
ForceUpdate();
}
protected override void OnDisable()
{
ApplyMapping(_defaultTexture, false, 0, _materialIndex);
}
}
}
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: f6d1977a52888584496b1acc7e998011
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {fileID: 2800000, guid: bb83b41b53a59874692b83eab5873998, type: 3}
userData:
@@ -0,0 +1,81 @@
using UnityEngine;
//-----------------------------------------------------------------------------
// Copyright 2019-2022 RenderHeads Ltd. All rights reserved.
//-----------------------------------------------------------------------------
namespace RenderHeads.Media.AVProVideo
{
/// Allows per-channel volume control
/// Currently supported on Windows and UWP (Media Foundation API only), macOS, iOS, tvOS and Android (ExoPlayer API only)
[AddComponentMenu("AVPro Video/Audio Channel Mixer", 401)]
[HelpURL("https://www.renderheads.com/products/avpro-video/")]
public class AudioChannelMixer : MonoBehaviour
{
const int MaxChannels = 8;
[Range(0f, 1f)]
[SerializeField] float[] _channels = null;
/// Range 0.0 to 1.0
public float[] Channel
{
get { return _channels; }
set { _channels = value; }
}
void Reset()
{
_channels = new float[MaxChannels];
for (int i = 0; i < MaxChannels; i++)
{
_channels[i] = 1f;
}
}
void ChangeChannelCount(int numChannels)
{
float[] channels = new float[numChannels];
if (_channels != null && _channels.Length != 0)
{
for (int i = 0; i < channels.Length; i++)
{
if (i < _channels.Length)
{
channels[i] = _channels[i];
}
else
{
channels[i] = 1f;
}
}
}
else
{
for (int i = 0; i < numChannels; i++)
{
channels[i] = 1f;
}
}
_channels = channels;
}
void OnAudioFilterRead(float[] data, int channels)
{
if (channels != _channels.Length)
{
ChangeChannelCount(channels);
}
int k = 0;
int numSamples = data.Length / channels;
for (int j = 0; j < numSamples; j++)
{
for (int i = 0; i < channels; i++)
{
data[k] *= _channels[i];
k++;
}
}
}
}
}
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 383a68f1e3e94be4b84df59dd26074db
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {fileID: 2800000, guid: bb83b41b53a59874692b83eab5873998, type: 3}
userData:
@@ -0,0 +1,162 @@
using UnityEngine;
//-----------------------------------------------------------------------------
// Copyright 2015-2022 RenderHeads Ltd. All rights reserved.
//-----------------------------------------------------------------------------
namespace RenderHeads.Media.AVProVideo
{
/// <summary>
/// Audio is grabbed from the MediaPlayer and rendered via Unity AudioSource
/// This allows audio to have 3D spatial control, effects applied and to be spatialised for VR
/// Currently supported on Windows and UWP (Media Foundation API only), macOS, iOS, tvOS and Android (ExoPlayer API only)
/// </summary>
[RequireComponent(typeof(AudioSource))]
[AddComponentMenu("AVPro Video/Audio Output", 400)]
[HelpURL("https://www.renderheads.com/products/avpro-video/")]
public class AudioOutput : MonoBehaviour
{
public enum AudioOutputMode
{
OneToAllChannels,
MultipleChannels
}
[SerializeField] MediaPlayer _mediaPlayer = null;
[SerializeField] AudioOutputMode _audioOutputMode = AudioOutputMode.MultipleChannels;
[HideInInspector, SerializeField] int _channelMask = 0xffff;
[SerializeField] bool _supportPositionalAudio = false;
public MediaPlayer Player
{
get { return _mediaPlayer; }
set { ChangeMediaPlayer(value); }
}
public AudioOutputMode OutputMode
{
get { return _audioOutputMode; }
set { _audioOutputMode = value; }
}
public int ChannelMask
{
get { return _channelMask; }
set { _channelMask = value; }
}
private AudioSource _audioSource;
void Awake()
{
_audioSource = this.GetComponent<AudioSource>();
Debug.Assert(_audioSource != null);
}
void Start()
{
AudioSettings.OnAudioConfigurationChanged += OnAudioConfigurationChanged;
ChangeMediaPlayer(_mediaPlayer);
}
void OnAudioConfigurationChanged(bool deviceChanged)
{
if (_mediaPlayer == null || _mediaPlayer.Control == null)
return;
_mediaPlayer.Control.AudioConfigurationChanged(deviceChanged);
}
void OnDestroy()
{
ChangeMediaPlayer(null);
}
void Update()
{
if (_mediaPlayer != null && _mediaPlayer.Control != null && _mediaPlayer.Control.IsPlaying())
{
ApplyAudioSettings(_mediaPlayer, _audioSource);
}
}
public AudioSource GetAudioSource()
{
return _audioSource;
}
public void ChangeMediaPlayer(MediaPlayer newPlayer)
{
// When changing the media player, handle event subscriptions
if (_mediaPlayer != null)
{
_mediaPlayer.AudioSource = null;
_mediaPlayer.Events.RemoveListener(OnMediaPlayerEvent);
_mediaPlayer = null;
}
_mediaPlayer = newPlayer;
if (_mediaPlayer != null)
{
_mediaPlayer.Events.AddListener(OnMediaPlayerEvent);
_mediaPlayer.AudioSource = _audioSource;
}
if (_supportPositionalAudio)
{
if (_audioSource.clip == null)
{
// Position audio is implemented from hints found on this thread:
// https://forum.unity.com/threads/onaudiofilterread-sound-spatialisation.362782/
int frameCount = 2048 * 10;
int sampleCount = frameCount * Helper.GetUnityAudioSpeakerCount();
AudioClip clip = AudioClip.Create("dummy", frameCount, Helper.GetUnityAudioSpeakerCount(), Helper.GetUnityAudioSampleRate(), false);
float[] samples = new float[sampleCount];
for (int i = 0; i < samples.Length; i++) { samples[i] = 1f; }
clip.SetData(samples, 0);
_audioSource.clip = clip;
_audioSource.loop = true;
}
}
else if (_audioSource.clip != null)
{
_audioSource.clip = null;
}
}
// Callback function to handle events
private void OnMediaPlayerEvent(MediaPlayer mp, MediaPlayerEvent.EventType et, ErrorCode errorCode)
{
switch (et)
{
case MediaPlayerEvent.EventType.Closing:
_audioSource.Stop();
break;
case MediaPlayerEvent.EventType.Started:
ApplyAudioSettings(_mediaPlayer, _audioSource);
_audioSource.Play();
break;
}
}
private static void ApplyAudioSettings(MediaPlayer player, AudioSource audioSource)
{
// Apply volume and mute from the MediaPlayer to the AudioSource
if (audioSource != null && player != null && player.Control != null)
{
float volume = player.Control.GetVolume();
bool isMuted = player.Control.IsMuted();
float rate = player.Control.GetPlaybackRate();
audioSource.volume = volume;
audioSource.mute = isMuted;
audioSource.pitch = rate;
}
}
#if (UNITY_EDITOR_WIN || UNITY_EDITOR_OSX) || (!UNITY_EDITOR && (UNITY_STANDALONE_WIN || UNITY_WSA_10_0 || UNITY_STANDALONE_OSX || UNITY_IOS || UNITY_TVOS || UNITY_ANDROID))
void OnAudioFilterRead(float[] audioData, int channelCount)
{
AudioOutputManager.Instance.RequestAudio(this, _mediaPlayer, audioData, channelCount, _channelMask, _audioOutputMode, _supportPositionalAudio);
}
#endif
}
}
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 3b05a64a5de3f8546bf586f42e37b979
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {fileID: 2800000, guid: bb83b41b53a59874692b83eab5873998, type: 3}
userData:
@@ -0,0 +1,321 @@
#if UNITY_EDITOR || UNITY_STANDALONE_OSX || UNITY_STANDALONE_WIN || UNITY_IOS || UNITY_TVOS || UNITY_ANDROID || (UNITY_WEBGL && UNITY_2017_2_OR_NEWER)
#define UNITY_PLATFORM_SUPPORTS_LINEAR
#endif
#if (UNITY_EDITOR_WIN || (!UNITY_EDITOR && UNITY_STANDALONE_WIN))
#define UNITY_PLATFORM_SUPPORTS_VIDEOASPECTRATIO
#endif
using UnityEngine;
using UnityEngine.Serialization;
//-----------------------------------------------------------------------------
// Copyright 2015-2022 RenderHeads Ltd. All rights reserved.
//-----------------------------------------------------------------------------
namespace RenderHeads.Media.AVProVideo
{
/// <summary>
/// Displays the video from MediaPlayer component using IMGUI
/// </summary>
[AddComponentMenu("AVPro Video/Display IMGUI", 200)]
[HelpURL("https://www.renderheads.com/products/avpro-video/")]
[ExecuteInEditMode]
public class DisplayIMGUI : MonoBehaviour
{
[SerializeField] MediaPlayer _mediaPlayer = null;
public MediaPlayer Player
{
get { return _mediaPlayer; }
set { _mediaPlayer = value; Update(); }
}
[SerializeField] ScaleMode _scaleMode = ScaleMode.ScaleToFit;
public ScaleMode ScaleMode { get { return _scaleMode; } set { _scaleMode = value; } }
[SerializeField] Color _color = UnityEngine.Color.white;
public Color Color { get { return _color; } set { _color = value; } }
[FormerlySerializedAs("_alphaBlend")]
[SerializeField] bool _allowTransparency = false;
public bool AllowTransparency { get { return _allowTransparency; } set { _allowTransparency = value; } }
[SerializeField] bool _useDepth = false;
public bool UseDepth { get { return _useDepth; } set { _useDepth = value; } }
[SerializeField] int _depth = 0;
public int Depth { get { return _depth; } set { _depth = value; } }
[Header("Area")]
[FormerlySerializedAs("_fullScreen")]
[SerializeField] bool _isAreaFullScreen = true;
public bool IsAreaFullScreen { get { return _isAreaFullScreen; } set { _isAreaFullScreen = value; } }
[FormerlySerializedAs("_x")]
[Range(0f, 1f)]
[SerializeField] float _areaX = 0f;
public float AreaX { get { return _areaX; } set { _areaX = value; } }
[FormerlySerializedAs("_y")]
[Range(0f, 1f)]
[SerializeField] float _areaY = 0f;
public float AreaY { get { return _areaY; } set { _areaY = value; } }
[FormerlySerializedAs("_width")]
[Range(0f, 1f)]
[SerializeField] float _areaWidth = 1f;
public float AreaWidth { get { return _areaWidth; } set { _areaWidth = value; } }
[FormerlySerializedAs("_height")]
[Range(0f, 1f)]
[SerializeField] float _areaHeight = 1f;
public float AreaHeight { get { return _areaHeight; } set { _areaHeight = value; } }
[FormerlySerializedAs("_displayInEditor")]
[SerializeField] bool _showAreaInEditor = false;
public bool ShowAreaInEditor { get { return _showAreaInEditor; } set { _showAreaInEditor = value; } }
private static Shader _shaderAlphaPacking;
private Material _material;
void Start()
{
// Disabling useGUILayout lets you skip the GUI layout phase which helps performance, but this also breaks the GUI.depth usage.
if (!_useDepth)
{
this.useGUILayout = false;
}
if (!_shaderAlphaPacking)
{
_shaderAlphaPacking = Shader.Find("AVProVideo/Internal/IMGUI/Texture Transparent");
if (!_shaderAlphaPacking)
{
Debug.LogWarning("[AVProVideo] Missing shader 'AVProVideo/Internal/IMGUI/Texture Transparent'");
}
}
}
public void Update()
{
if (_mediaPlayer != null)
{
SetupMaterial();
}
}
void OnDestroy()
{
// Destroy existing material
if (_material != null)
{
#if UNITY_EDITOR
Material.DestroyImmediate(_material);
#else
Material.Destroy(_material);
#endif
_material = null;
}
}
private Shader GetRequiredShader()
{
Shader result = null;
if (result == null && _mediaPlayer.TextureProducer != null)
{
switch (_mediaPlayer.TextureProducer.GetTextureAlphaPacking())
{
case AlphaPacking.None:
break;
case AlphaPacking.LeftRight:
case AlphaPacking.TopBottom:
result = _shaderAlphaPacking;
break;
}
}
#if UNITY_PLATFORM_SUPPORTS_LINEAR
if (result == null && _mediaPlayer.Info != null)
{
// If the player does support generating sRGB textures then we need to use a shader to convert them for display via IMGUI
if (QualitySettings.activeColorSpace == ColorSpace.Linear && !_mediaPlayer.Info.PlayerSupportsLinearColorSpace())
{
result = _shaderAlphaPacking;
}
}
#endif
if (result == null && _mediaPlayer.TextureProducer != null)
{
if (_mediaPlayer.TextureProducer.GetTextureCount() == 2)
{
result = _shaderAlphaPacking;
}
}
return result;
}
private void SetupMaterial()
{
// Get required shader
Shader currentShader = null;
if (_material != null)
{
currentShader = _material.shader;
}
Shader nextShader = GetRequiredShader();
// If the shader requirement has changed
if (currentShader != nextShader)
{
// Destroy existing material
if (_material != null)
{
#if UNITY_EDITOR
Material.DestroyImmediate(_material);
#else
Material.Destroy(_material);
#endif
_material = null;
}
// Create new material
if (nextShader != null)
{
_material = new Material(nextShader);
}
}
}
#if UNITY_EDITOR
private void DrawArea()
{
Rect rect = GetAreaRect();
Rect uv = rect;
uv.x /= Screen.width;
uv.width /= Screen.width;
uv.y /= Screen.height;
uv.height /= Screen.height;
uv.width *= 16f;
uv.height *= 16f;
uv.x += 0.5f;
uv.y += 0.5f;
Texture2D icon = Resources.Load<Texture2D>("AVProVideoIcon");
GUI.depth = _depth;
GUI.color = _color;
GUI.DrawTextureWithTexCoords(rect, icon, uv);
}
#endif
void OnGUI()
{
#if UNITY_EDITOR
if (_showAreaInEditor && !Application.isPlaying)
{
DrawArea();
return;
}
#endif
if (_mediaPlayer == null)
{
return;
}
Texture texture = null;
if (_showAreaInEditor)
{
#if UNITY_EDITOR
texture = Texture2D.whiteTexture;
#endif
}
texture = VideoRender.GetTexture(_mediaPlayer, 0);
if (_mediaPlayer.Info != null && !_mediaPlayer.Info.HasVideo())
{
texture = null;
}
if (texture != null)
{
bool isTextureVisible = (_color.a > 0f || !_allowTransparency);
if (isTextureVisible)
{
GUI.depth = _depth;
GUI.color = _color;
Rect rect = GetAreaRect();
// TODO: change this to a material-only path so we only have a single drawing path
if (_material != null)
{
// TODO: Only setup material when needed
VideoRender.SetupMaterialForMedia(_material, _mediaPlayer);
// NOTE: It seems that Graphics.DrawTexture() behaves differently than GUI.DrawTexture() when it comes to sRGB writing
// on newer versions of Unity (at least 2018.2.19 and above), so now we have to force the conversion to sRGB on writing
bool restoreSRGBWrite = false;
#if UNITY_EDITOR_WIN || (!UNITY_EDITOR && UNITY_STANDALONE_WIN)
if (QualitySettings.activeColorSpace == ColorSpace.Linear && !GL.sRGBWrite)
{
restoreSRGBWrite = true;
}
#endif
if (restoreSRGBWrite)
{
GL.sRGBWrite = true;
}
VideoRender.DrawTexture(rect, texture, _scaleMode, _mediaPlayer.TextureProducer.GetTextureAlphaPacking(), _mediaPlayer.TextureProducer.GetTexturePixelAspectRatio(), _material);
if (restoreSRGBWrite)
{
GL.sRGBWrite = false;
}
}
else
{
bool requiresVerticalFlip = false;
if (_mediaPlayer.TextureProducer != null)
{
requiresVerticalFlip = _mediaPlayer.TextureProducer.RequiresVerticalFlip();
}
if (requiresVerticalFlip)
{
GUIUtility.ScaleAroundPivot(new Vector2(1f, -1f), new Vector2(0f, rect.y + (rect.height / 2f)));
}
#if UNITY_PLATFORM_SUPPORTS_VIDEOASPECTRATIO
float par = _mediaPlayer.TextureProducer.GetTexturePixelAspectRatio();
if (par > 0f)
{
if (par > 1f)
{
GUIUtility.ScaleAroundPivot(new Vector2(par, 1f), new Vector2(rect.x + (rect.width / 2f), rect.y + (rect.height / 2f)));
}
else
{
GUIUtility.ScaleAroundPivot(new Vector2(1f, 1f/par), new Vector2(rect.x + (rect.width / 2f), rect.y + (rect.height / 2f)));
}
}
#endif
GUI.DrawTexture(rect, texture, _scaleMode, _allowTransparency);
}
}
}
}
public Rect GetAreaRect()
{
Rect rect;
if (_isAreaFullScreen)
{
rect = new Rect(0.0f, 0.0f, Screen.width, Screen.height);
}
else
{
rect = new Rect(_areaX * (Screen.width - 1), _areaY * (Screen.height - 1), _areaWidth * Screen.width, _areaHeight * Screen.height);
}
return rect;
}
}
}
@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 75f3b319d2d69934d8bf545ab45c918d
timeCreated: 1544813301
licenseType: Pro
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {fileID: 2800000, guid: bb83b41b53a59874692b83eab5873998, type: 3}
userData:
assetBundleName:
assetBundleVariant:
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 638c870cac4da414fba921606d504407
timeCreated: 1544813302
licenseType: Pro
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {fileID: 2800000, guid: bb83b41b53a59874692b83eab5873998, type: 3}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,469 @@
#if AVPROVIDEO_SUPPORT_BUFFERED_DISPLAY
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using RenderHeads.Media.AVProVideo;
//-----------------------------------------------------------------------------
// Copyright 2015-2022 RenderHeads Ltd. All rights reserved.
//-----------------------------------------------------------------------------
namespace RenderHeads.Media.AVProVideo.Experimental
{
/// <summary>
/// Syncronise multiple MediaPlayer components (currently Windows ONLY using Media Foundation ONLY)
/// This feature requires Ultra Edition
/// </summary>
[AddComponentMenu("AVPro Video/Media Player Sync (BETA)", -90)]
[HelpURL("https://www.renderheads.com/products/avpro-video/")]
public class MediaPlayerSync : MonoBehaviour
{
[SerializeField] MediaPlayer _masterPlayer = null;
[SerializeField] MediaPlayer[] _slavePlayers = null;
[SerializeField] bool _playOnStart = true;
[SerializeField] bool _waitAfterPreroll = false;
[SerializeField] bool _logSyncErrors = false;
public MediaPlayer MasterPlayer { get { return _masterPlayer; } set { _masterPlayer = value; } }
public MediaPlayer[] SlavePlayers { get { return _slavePlayers; } set { _slavePlayers = value; } }
public bool PlayOnStart { get { return _playOnStart; } set { _playOnStart = value; } }
public bool WaitAfterPreroll { get { return _waitAfterPreroll; } set { _waitAfterPreroll = value; } }
public bool LogSyncErrors { get { return _logSyncErrors; } set { _logSyncErrors = value; } }
private enum State
{
Idle,
Loading,
Prerolling,
Prerolled,
Playing,
Finished,
}
private State _state = State.Idle;
void Awake()
{
#if (UNITY_EDITOR_WIN || (!UNITY_EDITOR && UNITY_STANDALONE_WIN))
SetupPlayers();
#else
Debug.LogError("[AVProVideo] This component only works on the Windows platform");
this.enabled = false;
#endif
}
void Start()
{
if (_playOnStart)
{
StartPlayback();
_state = State.Loading;
_playOnStart = false;
}
}
public void OpenMedia(string[] mediaPaths)
{
Debug.Assert(mediaPaths.Length == (_slavePlayers.Length + 1));
_masterPlayer.MediaSource = MediaSource.Path;
_masterPlayer.MediaPath = new MediaPath(mediaPaths[0], MediaPathType.AbsolutePathOrURL);
for (int i = 0; i < _slavePlayers.Length; i++)
{
_slavePlayers[i].MediaSource = MediaSource.Path;
_slavePlayers[i].MediaPath = new MediaPath(mediaPaths[i+1], MediaPathType.AbsolutePathOrURL);
}
StartPlayback();
}
/// <summary>
/// This is called when _autoPlay is false and once the MediaPlayers have had their source media set
/// </summary>
[ContextMenu("StartPlayback")]
public void StartPlayback()
{
SetupPlayers();
if (!IsPrerolled())
{
OpenMediaAll();
_state = State.Loading;
}
else
{
PlayAll();
_state = State.Playing;
}
}
public void Seek(double time, bool approximate = true)
{
if (approximate)
{
SeekFastAll(time);
}
else
{
SeekAll(time);
}
_state = State.Prerolling;
}
public bool IsPrerolled()
{
return (_state == State.Prerolled);
}
void SetupPlayers()
{
SetupPlayer(_masterPlayer);
for (int i = 0; i < _slavePlayers.Length; i++)
{
SetupPlayer(_slavePlayers[i]);
}
}
void SetupPlayer(MediaPlayer player)
{
bool isMaster = (player == _masterPlayer);
player.AutoOpen = false;
player.AutoStart = false;
player.AudioMuted = !isMaster;
player.PlatformOptionsWindows.videoApi = Windows.VideoApi.MediaFoundation;
player.PlatformOptionsWindows.useLowLatency = true;
player.PlatformOptionsWindows.pauseOnPrerollComplete = true;
player.PlatformOptionsWindows.bufferedFrameSelection = isMaster ? BufferedFrameSelectionMode.ElapsedTimeVsynced : BufferedFrameSelectionMode.FromExternalTime;
}
// NOTE: We check on LateUpdate() as MediaPlayer uses Update() to update state and we want to make sure all players have been updated
void LateUpdate()
{
if (_state == State.Idle)
{
}
if (_state == State.Loading)
{
UpdateLoading();
}
if (_state == State.Prerolling)
{
UpdatePrerolling();
}
if (_state == State.Prerolled)
{
/*if (Input.GetKeyDown(KeyCode.Alpha0))
{
StartPlayback();
}*/
}
if (_state == State.Playing)
{
UpdatePlaying();
}
if (_state == State.Finished)
{
}
#if UNITY_EDITOR
if (Input.GetKeyDown(KeyCode.Alpha5))
{
Debug.Log("sleep");
System.Threading.Thread.Sleep(16);
}
/*if (Input.GetKeyDown(KeyCode.Alpha1))
{
double time = Random.Range(0f, (float)_masterPlayer.Info.GetDuration());
Seek(time);
}
long gcMemory = System.GC.GetTotalMemory(false);
//Debug.Log("GC: " + (gcMemory / 1024) + " " + (gcMemory - lastGcMemory));
if ((gcMemory - lastGcMemory) < 0)
{
Debug.LogWarning("COLLECTION!!! " + (lastGcMemory - gcMemory));
}
lastGcMemory = gcMemory;*/
#endif
}
//long lastGcMemory = 0;
void UpdateLoading()
{
// Finished loading?
if (IsAllVideosLoaded())
{
// Assign the master and slaves
_masterPlayer.BufferedDisplay.SetBufferedDisplayMode(BufferedFrameSelectionMode.ElapsedTimeVsynced);
IBufferedDisplay[] slaves = new IBufferedDisplay[_slavePlayers.Length];
for (int i = 0; i < _slavePlayers.Length; i++)
{
slaves[i] = _slavePlayers[i].BufferedDisplay;
}
_masterPlayer.BufferedDisplay.SetSlaves(slaves);
//System.Threading.Thread.Sleep(1250);
// Begin preroll
PlayAll();
_state = State.Prerolling;
}
}
void UpdatePrerolling()
{
if (IsAllVideosPaused())
{
//System.Threading.Thread.Sleep(250);
if (_waitAfterPreroll)
{
_state = State.Prerolled;
}
else
{
PlayAll();
_state = State.Playing;
}
}
}
void UpdatePlaying()
{
if (_masterPlayer.Control.IsPlaying())
{
if (_logSyncErrors)
{
CheckSync();
CheckSmoothness();
}
BufferedFramesState state = _masterPlayer.BufferedDisplay.GetBufferedFramesState();
if (state.bufferedFrameCount < 3)
{
//Debug.LogWarning("FORCE SLEEP");
System.Threading.Thread.Sleep(16);
}
}
else
{
// Pause slaves
for (int i = 0; i < _slavePlayers.Length; i++)
{
MediaPlayer slave = _slavePlayers[i];
slave.Pause();
}
}
// Finished?
if (IsPlaybackFinished(_masterPlayer))
{
_state = State.Finished;
}
}
private long _lastTimeStamp;
private int _sameFrameCount;
void CheckSmoothness()
{
long timeStamp = _masterPlayer.TextureProducer.GetTextureTimeStamp();
//int frameCount = _masterPlayer.TextureProducer.GetTextureFrameCount();
long frameDuration = (long)(10000000f / _masterPlayer.Info.GetVideoFrameRate());
long vsyncDuration = (long)((QualitySettings.vSyncCount * 10000000f) / (float)Screen.currentResolution.refreshRate);
float vsyncFrames = (float)vsyncDuration / frameDuration;
float fractionalFrames = vsyncFrames - Mathf.FloorToInt(vsyncFrames);
if (fractionalFrames == 0f)
{
if (QualitySettings.vSyncCount != 0)
{
if (!Mathf.Approximately(_sameFrameCount, vsyncFrames))
{
Debug.LogWarning("Frame " + timeStamp + " was shown for " + _sameFrameCount + " frames instead of expected " + vsyncFrames);
}
}
}
long d = (timeStamp - _lastTimeStamp);
if (d != 0)
{
long threshold = 10000;
if (d > frameDuration + threshold ||
d < frameDuration - threshold)
{
Debug.LogWarning("Possible frame skip, " + timeStamp + " " + d);
}
_sameFrameCount = 1;
}
else
{
_sameFrameCount++;
}
_lastTimeStamp = timeStamp;
//Debug.Log(frameDuration);
}
void CheckSync()
{
long timeStamp = _masterPlayer.TextureProducer.GetTextureTimeStamp();
bool inSync = true;
foreach (MediaPlayer slavePlayer in _slavePlayers)
{
if (slavePlayer.TextureProducer.GetTextureTimeStamp() != timeStamp)
{
inSync = false;
break;
}
}
if (!inSync)
{
LogSyncState();
Debug.LogWarning("OUT OF SYNC!!!!!!!");
//Debug.Break();
}
else
{
//LogSyncState();
}
}
void LogSyncState()
{
string text = "Time - Full,Free\t\tRange\n";
text += LogSyncState(_masterPlayer) + "\n";
foreach (MediaPlayer slavePlayer in _slavePlayers)
{
text += LogSyncState(slavePlayer) + "\n";
}
Debug.Log(text);
}
string LogSyncState(MediaPlayer player)
{
BufferedFramesState state = player.BufferedDisplay.GetBufferedFramesState();
long timeStamp = player.TextureProducer.GetTextureTimeStamp();
string result = string.Format("{4} - {2},{3}\t\t{0}-{1} ({5})", state.minTimeStamp, state.maxTimeStamp, state.bufferedFrameCount, state.freeFrameCount, timeStamp, Time.deltaTime);
return result;
}
void OpenMediaAll()
{
_masterPlayer.OpenMedia(autoPlay:false);
for (int i = 0; i < _slavePlayers.Length; i++)
{
_slavePlayers[i].OpenMedia(autoPlay:false);
}
}
void PauseAll()
{
_masterPlayer.Pause();
for (int i = 0; i < _slavePlayers.Length; i++)
{
_slavePlayers[i].Pause();
}
}
void PlayAll()
{
_masterPlayer.Play();
for (int i = 0; i < _slavePlayers.Length; i++)
{
_slavePlayers[i].Play();
}
}
void SeekAll(double time)
{
_masterPlayer.Control.Seek(time);
foreach (MediaPlayer player in _slavePlayers)
{
player.Control.Seek(time);
}
}
void SeekFastAll(double time)
{
_masterPlayer.Control.SeekFast(time);
foreach (MediaPlayer player in _slavePlayers)
{
player.Control.SeekFast(time);
}
}
bool IsAllVideosLoaded()
{
bool result = false;
if (IsVideoLoaded(_masterPlayer))
{
result = true;
for (int i = 0; i < _slavePlayers.Length; i++)
{
if (!IsVideoLoaded(_slavePlayers[i]))
{
result = false;
break;
}
}
}
return result;
}
bool IsAllVideosPaused()
{
bool result = false;
if (IsVideoPaused(_masterPlayer))
{
result = true;
for (int i = 0; i < _slavePlayers.Length; i++)
{
if (!IsVideoPaused(_slavePlayers[i]))
{
result = false;
break;
}
}
}
return result;
}
static bool IsPlaybackFinished(MediaPlayer player)
{
bool result = false;
if (player != null && player.Control != null)
{
if (player.Control.IsFinished())
{
BufferedFramesState state = player.BufferedDisplay.GetBufferedFramesState();
if (state.bufferedFrameCount == 0)
{
result = true;
}
}
}
return result;
}
static bool IsVideoLoaded(MediaPlayer player)
{
return (player != null && player.Control != null && player.Control.HasMetaData() && player.Control.CanPlay());
}
static bool IsVideoPaused(MediaPlayer player)
{
return (player != null && player.Control != null && player.Control.IsPaused());
}
}
}
#endif
@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 7b84192b70cb6c14ba22896883851268
timeCreated: 1628084656
licenseType: Pro
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {fileID: 2800000, guid: bb83b41b53a59874692b83eab5873998, type: 3}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,65 @@
#if !(UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX || UNITY_IOS || UNITY_TVOS)
using UnityEngine;
//-----------------------------------------------------------------------------
// Copyright 2015-2022 RenderHeads Ltd. All rights reserved.
//-----------------------------------------------------------------------------
namespace RenderHeads.Media.AVProVideo
{
public partial class MediaPlayer : MonoBehaviour
{
#region Application Focus and Pausing
#if !UNITY_EDITOR
void OnApplicationFocus(bool focusStatus)
{
#if !(UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN)
// Debug.Log("OnApplicationFocus: focusStatus: " + focusStatus);
if (focusStatus && (isActiveAndEnabled && enabled))
{
if (Control != null && _wasPlayingOnPause)
{
_wasPlayingOnPause = false;
Control.Play();
Helper.LogInfo("OnApplicationFocus: playing video again");
}
}
#endif
}
void OnApplicationPause(bool pauseStatus)
{
#if !(UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN)
// Debug.Log("OnApplicationPause: pauseStatus: " + pauseStatus);
if (pauseStatus)
{
if (_pauseMediaOnAppPause)
{
if (Control!= null && Control.IsPlaying())
{
_wasPlayingOnPause = true;
#if !UNITY_IPHONE
Control.Pause();
#endif
Helper.LogInfo("OnApplicationPause: pausing video");
}
}
}
else
{
if (_playMediaOnAppUnpause)
{
// Catch coming back from power off state when no lock screen
OnApplicationFocus(true);
}
}
#endif
}
#endif
#endregion // Application Focus and Pausing
}
}
#endif
@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 3a3464021ab2fb14a81d5d35b3097023
timeCreated: 1544813302
licenseType: Pro
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {fileID: 2800000, guid: bb83b41b53a59874692b83eab5873998, type: 3}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,29 @@
using UnityEngine;
//-----------------------------------------------------------------------------
// Copyright 2015-2022 RenderHeads Ltd. All rights reserved.
//-----------------------------------------------------------------------------
namespace RenderHeads.Media.AVProVideo
{
public partial class MediaPlayer : MonoBehaviour
{
#region Audio Mute Support for Unity Editor
#if UNITY_EDITOR
private bool _unityAudioMasterMute = false;
private void CheckEditorAudioMute()
{
// Detect a change
if (UnityEditor.EditorUtility.audioMasterMute != _unityAudioMasterMute)
{
if (_controlInterface != null)
{
_unityAudioMasterMute = UnityEditor.EditorUtility.audioMasterMute;
_controlInterface.MuteAudio(_audioMuted || _unityAudioMasterMute);
}
}
}
#endif
#endregion // Audio Mute Support for Unity Editor
}
}
@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 16be519f584387149bd75947276c3a72
timeCreated: 1544813302
licenseType: Pro
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {fileID: 2800000, guid: bb83b41b53a59874692b83eab5873998, type: 3}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,83 @@
using UnityEngine;
#if UNITY_EDITOR
//-----------------------------------------------------------------------------
// Copyright 2015-2022 RenderHeads Ltd. All rights reserved.
//-----------------------------------------------------------------------------
namespace RenderHeads.Media.AVProVideo
{
public partial class MediaPlayer : MonoBehaviour
{
#region Play/Pause Support for Unity Editor
// This code handles the pause/play buttons in the editor
private static void SetupEditorPlayPauseSupport()
{
#if UNITY_2017_2_OR_NEWER
UnityEditor.EditorApplication.pauseStateChanged -= OnUnityPauseModeChanged;
UnityEditor.EditorApplication.pauseStateChanged += OnUnityPauseModeChanged;
#else
UnityEditor.EditorApplication.playmodeStateChanged -= OnUnityPlayModeChanged;
UnityEditor.EditorApplication.playmodeStateChanged += OnUnityPlayModeChanged;
#endif
}
#if UNITY_2017_2_OR_NEWER
private static void OnUnityPauseModeChanged(UnityEditor.PauseState state)
{
OnUnityPlayModeChanged();
}
#endif
private static void OnUnityPlayModeChanged()
{
if (UnityEditor.EditorApplication.isPlaying)
{
bool isPaused = UnityEditor.EditorApplication.isPaused;
MediaPlayer[] players = Resources.FindObjectsOfTypeAll<MediaPlayer>();
foreach (MediaPlayer player in players)
{
if (isPaused)
{
player.EditorPause();
}
else
{
player.EditorUnpause();
}
}
}
}
private void EditorPause()
{
if (this.isActiveAndEnabled)
{
if (_controlInterface != null && _controlInterface.IsPlaying())
{
_wasPlayingOnPause = true;
_controlInterface.Pause();
}
StopRenderCoroutine();
}
}
private void EditorUnpause()
{
if (this.isActiveAndEnabled)
{
if (_controlInterface != null && _wasPlayingOnPause)
{
_autoPlayOnStart = true;
_wasPlayingOnPause = false;
_autoPlayOnStartTriggered = false;
}
StartRenderCoroutine();
}
}
#endregion // Play/Pause Support for Unity Editor
}
}
#endif
@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 083c5ace9dbfda84cb8b4afaa19bdcde
timeCreated: 1544813302
licenseType: Pro
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {fileID: 2800000, guid: bb83b41b53a59874692b83eab5873998, type: 3}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,269 @@
using UnityEngine;
//-----------------------------------------------------------------------------
// Copyright 2015-2022 RenderHeads Ltd. All rights reserved.
//-----------------------------------------------------------------------------
namespace RenderHeads.Media.AVProVideo
{
public partial class MediaPlayer : MonoBehaviour
{
#region Events
// Event state
private bool _eventFired_MetaDataReady = false;
private bool _eventFired_ReadyToPlay = false;
private bool _eventFired_Started = false;
private bool _eventFired_FirstFrameReady = false;
private bool _eventFired_FinishedPlaying = false;
private bool _eventState_PlaybackBuffering = false;
private bool _eventState_PlaybackSeeking = false;
private bool _eventState_PlaybackStalled = false;
private int _eventState_PreviousWidth = 0;
private int _eventState_PreviousHeight = 0;
private int _previousSubtitleIndex = -1;
private bool _finishedFrameOpenCheck = false;
#if UNITY_EDITOR
public static MediaPlayerLoadEvent InternalMediaLoadedEvent = new MediaPlayerLoadEvent();
#endif
private void ResetEvents()
{
_eventFired_MetaDataReady = false;
_eventFired_ReadyToPlay = false;
_eventFired_Started = false;
_eventFired_FirstFrameReady = false;
_eventFired_FinishedPlaying = false;
_eventState_PlaybackBuffering = false;
_eventState_PlaybackSeeking = false;
_eventState_PlaybackStalled = false;
_eventState_PreviousWidth = 0;
_eventState_PreviousHeight = 0;
_previousSubtitleIndex = -1;
_finishedFrameOpenCheck = false;
}
private void UpdateEvents()
{
if (_events != null && _controlInterface != null && _events.HasListeners())
{
//NOTE: Fixes a bug where the event was being fired immediately, so when a file is opened, the finishedPlaying fired flag gets set but
//is then set to true immediately afterwards due to the returned value
_finishedFrameOpenCheck = false;
if (IsHandleEvent(MediaPlayerEvent.EventType.FinishedPlaying))
{
if (FireEventIfPossible(MediaPlayerEvent.EventType.FinishedPlaying, _eventFired_FinishedPlaying))
{
_eventFired_FinishedPlaying = !_finishedFrameOpenCheck;
}
}
// Reset some event states that can reset during playback
{
// Keep track of whether the Playing state has changed
if (_eventFired_Started && IsHandleEvent(MediaPlayerEvent.EventType.Started) &&
_controlInterface != null && !_controlInterface.IsPlaying() && !_controlInterface.IsSeeking())
{
// Playing has stopped
_eventFired_Started = false;
}
// NOTE: We check _controlInterface isn't null in case the scene is unloaded in response to the FinishedPlaying event
if (_eventFired_FinishedPlaying && IsHandleEvent(MediaPlayerEvent.EventType.FinishedPlaying) &&
_controlInterface != null && _controlInterface.IsPlaying() && !_controlInterface.IsFinished())
{
bool reset = true;
#if UNITY_EDITOR_WIN || (!UNITY_EDITOR && (UNITY_STANDALONE_WIN || UNITY_WSA))
reset = false;
if (_infoInterface.HasVideo())
{
// Some streaming HLS/Dash content don't provide a frame rate
if (_infoInterface.GetVideoFrameRate() > 0f)
{
// Don't reset if within a frame of the end of the video, important for time > duration workaround
float secondsPerFrame = 1f / _infoInterface.GetVideoFrameRate();
if (_infoInterface.GetDuration() - _controlInterface.GetCurrentTime() > secondsPerFrame)
{
reset = true;
}
}
else
{
// Just check if we're not beyond the duration
if (_controlInterface.GetCurrentTime() < _infoInterface.GetDuration())
{
reset = true;
}
}
}
else
{
// For audio only media just check if we're not beyond the duration
if (_controlInterface.GetCurrentTime() < _infoInterface.GetDuration())
{
reset = true;
}
}
#endif
if (reset)
{
//Debug.Log("Reset");
_eventFired_FinishedPlaying = false;
}
}
}
// Events that can only fire once
{
_eventFired_MetaDataReady = FireEventIfPossible(MediaPlayerEvent.EventType.MetaDataReady, _eventFired_MetaDataReady);
_eventFired_ReadyToPlay = FireEventIfPossible(MediaPlayerEvent.EventType.ReadyToPlay, _eventFired_ReadyToPlay);
_eventFired_Started = FireEventIfPossible(MediaPlayerEvent.EventType.Started, _eventFired_Started);
_eventFired_FirstFrameReady = FireEventIfPossible(MediaPlayerEvent.EventType.FirstFrameReady, _eventFired_FirstFrameReady);
}
// Events that can fire multiple times
{
// Subtitle changing
if (FireEventIfPossible(MediaPlayerEvent.EventType.SubtitleChange, false))
{
_previousSubtitleIndex = _subtitlesInterface.GetSubtitleIndex();
}
// Resolution changing
if (FireEventIfPossible(MediaPlayerEvent.EventType.ResolutionChanged, false))
{
_eventState_PreviousWidth = _infoInterface.GetVideoWidth();
_eventState_PreviousHeight = _infoInterface.GetVideoHeight();
}
// Stalling
if (IsHandleEvent(MediaPlayerEvent.EventType.Stalled))
{
bool newState = _infoInterface.IsPlaybackStalled();
if (newState != _eventState_PlaybackStalled)
{
_eventState_PlaybackStalled = newState;
var newEvent = _eventState_PlaybackStalled ? MediaPlayerEvent.EventType.Stalled : MediaPlayerEvent.EventType.Unstalled;
FireEventIfPossible(newEvent, false);
}
}
// Seeking
if (IsHandleEvent(MediaPlayerEvent.EventType.StartedSeeking))
{
bool newState = _controlInterface.IsSeeking();
if (newState != _eventState_PlaybackSeeking)
{
_eventState_PlaybackSeeking = newState;
var newEvent = _eventState_PlaybackSeeking ? MediaPlayerEvent.EventType.StartedSeeking : MediaPlayerEvent.EventType.FinishedSeeking;
FireEventIfPossible(newEvent, false);
}
}
// Buffering
if (IsHandleEvent(MediaPlayerEvent.EventType.StartedBuffering))
{
bool newState = _controlInterface.IsBuffering();
if (newState != _eventState_PlaybackBuffering)
{
_eventState_PlaybackBuffering = newState;
var newEvent = _eventState_PlaybackBuffering ? MediaPlayerEvent.EventType.StartedBuffering : MediaPlayerEvent.EventType.FinishedBuffering;
FireEventIfPossible(newEvent, false);
}
}
}
}
}
protected bool IsHandleEvent(MediaPlayerEvent.EventType eventType)
{
return ((uint)_eventMask & (1 << (int)eventType)) != 0;
}
private bool FireEventIfPossible(MediaPlayerEvent.EventType eventType, bool hasFired)
{
if (CanFireEvent(eventType, hasFired))
{
#if UNITY_EDITOR
// Special internal global event, called when media is loaded
// Currently used by the RecentItem class
if (eventType == MediaPlayerEvent.EventType.Started)
{
string fullPath = GetResolvedFilePath(_mediaPath.Path, _mediaPath.PathType);
InternalMediaLoadedEvent.Invoke(fullPath);
}
#endif
hasFired = true;
_events.Invoke(this, eventType, ErrorCode.None);
}
return hasFired;
}
private bool CanFireEvent(MediaPlayerEvent.EventType et, bool hasFired)
{
bool result = false;
if (_events != null && _controlInterface != null && !hasFired && IsHandleEvent(et))
{
switch (et)
{
case MediaPlayerEvent.EventType.FinishedPlaying:
result = (!_controlInterface.IsLooping() && _controlInterface.CanPlay() && _controlInterface.IsFinished());
break;
case MediaPlayerEvent.EventType.MetaDataReady:
result = (_controlInterface.HasMetaData());
break;
case MediaPlayerEvent.EventType.FirstFrameReady:
// [MOZ 20/1/21] Removed HasMetaData check as preventing the event from being triggered on (i|mac|tv)OS
result = (_textureInterface != null && _controlInterface.CanPlay() /*&& _controlInterface.HasMetaData()*/ && _textureInterface.GetTextureFrameCount() > 0);
break;
case MediaPlayerEvent.EventType.ReadyToPlay:
result = (!_controlInterface.IsPlaying() && _controlInterface.CanPlay() && !_autoPlayOnStart);
break;
case MediaPlayerEvent.EventType.Started:
result = (_controlInterface.IsPlaying());
break;
case MediaPlayerEvent.EventType.SubtitleChange:
{
result = (_previousSubtitleIndex != _subtitlesInterface.GetSubtitleIndex());
if (!result)
{
result = _baseMediaPlayer.InternalIsChangedTextCue();
}
break;
}
case MediaPlayerEvent.EventType.Stalled:
result = _infoInterface.IsPlaybackStalled();
break;
case MediaPlayerEvent.EventType.Unstalled:
result = !_infoInterface.IsPlaybackStalled();
break;
case MediaPlayerEvent.EventType.StartedSeeking:
result = _controlInterface.IsSeeking();
break;
case MediaPlayerEvent.EventType.FinishedSeeking:
result = !_controlInterface.IsSeeking();
break;
case MediaPlayerEvent.EventType.StartedBuffering:
result = _controlInterface.IsBuffering();
break;
case MediaPlayerEvent.EventType.FinishedBuffering:
result = !_controlInterface.IsBuffering();
break;
case MediaPlayerEvent.EventType.ResolutionChanged:
result = (_infoInterface != null && (_eventState_PreviousWidth != _infoInterface.GetVideoWidth() || _eventState_PreviousHeight != _infoInterface.GetVideoHeight()));
break;
default:
Debug.LogWarning("[AVProVideo] Unhandled event type");
break;
}
}
return result;
}
#endregion // Events
}
}
@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 6be886b3f1f953843bda70e505701ee3
timeCreated: 1544813302
licenseType: Pro
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {fileID: 2800000, guid: bb83b41b53a59874692b83eab5873998, type: 3}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,211 @@
using UnityEngine;
using System.Collections;
//-----------------------------------------------------------------------------
// Copyright 2015-2022 RenderHeads Ltd. All rights reserved.
//-----------------------------------------------------------------------------
namespace RenderHeads.Media.AVProVideo
{
public partial class MediaPlayer : MonoBehaviour
{
#region Extract Frame
private bool ForceWaitForNewFrame(int lastFrameCount, float timeoutMs)
{
bool result = false;
// Wait for the frame to change, or timeout to happen (for the case that there is no new frame for this time)
System.DateTime startTime = System.DateTime.Now;
int iterationCount = 0;
while (Control != null && (System.DateTime.Now - startTime).TotalMilliseconds < (double)timeoutMs)
{
_playerInterface.Update();
// TODO: check if Seeking has completed! Then we don't have to wait
// If frame has changed we can continue
// NOTE: this will never happen because GL.IssuePlugin.Event is never called in this loop
if (lastFrameCount != TextureProducer.GetTextureFrameCount())
{
result = true;
break;
}
iterationCount++;
// NOTE: we tried to add Sleep for 1ms but it was very slow, so switched to this time based method which burns more CPU but about double the speed
// NOTE: had to add the Sleep back in as after too many iterations (over 1000000) of GL.IssuePluginEvent Unity seems to lock up
// NOTE: seems that GL.IssuePluginEvent can't be called if we're stuck in a while loop and they just stack up
//System.Threading.Thread.Sleep(0);
}
_playerInterface.Render();
return result;
}
/// <summary>
/// Create or return (if cached) a camera that is inactive and renders nothing
/// This camera is used to call .Render() on which causes the render thread to run
/// This is useful for forcing GL.IssuePluginEvent() to run and is used for
/// wait for frames to render for ExtractFrame() and UpdateTimeScale()
/// </summary>
private static Camera GetDummyCamera()
{
if (_dummyCamera == null)
{
const string goName = "AVPro Video Dummy Camera";
GameObject go = GameObject.Find(goName);
if (go == null)
{
go = new GameObject(goName);
go.hideFlags = HideFlags.HideInHierarchy | HideFlags.DontSave;
go.SetActive(false);
Object.DontDestroyOnLoad(go);
_dummyCamera = go.AddComponent<Camera>();
_dummyCamera.hideFlags = HideFlags.HideInInspector | HideFlags.DontSave;
_dummyCamera.cullingMask = 0;
_dummyCamera.clearFlags = CameraClearFlags.Nothing;
_dummyCamera.enabled = false;
}
else
{
_dummyCamera = go.GetComponent<Camera>();
}
}
//Debug.Assert(_dummyCamera != null);
return _dummyCamera;
}
private IEnumerator ExtractFrameCoroutine(Texture2D target, ProcessExtractedFrame callback, double timeSeconds = -1.0, bool accurateSeek = true, int timeoutMs = 1000, int timeThresholdMs = 100)
{
#if (!UNITY_EDITOR && UNITY_ANDROID) || UNITY_STANDALONE_WIN || UNITY_EDITOR_WIN || UNITY_STANDALONE_OSX || UNITY_EDITOR_OSX || UNITY_IOS || UNITY_TVOS
Texture2D result = target;
Texture frame = null;
if (_controlInterface != null)
{
if (timeSeconds >= 0f)
{
Pause();
// If the right frame is already available (or close enough) just grab it
if (TextureProducer.GetTexture() != null && (System.Math.Abs(_controlInterface.GetCurrentTime() - timeSeconds) < (timeThresholdMs / 1000.0)))
{
frame = TextureProducer.GetTexture();
}
else
{
int preSeekFrameCount = _textureInterface.GetTextureFrameCount();
// Seek to the frame
if (accurateSeek)
{
_controlInterface.Seek(timeSeconds);
}
else
{
_controlInterface.SeekFast(timeSeconds);
}
// Wait for the new frame to arrive
if (!_controlInterface.WaitForNextFrame(GetDummyCamera(), preSeekFrameCount))
{
// If WaitForNextFrame fails (e.g. in android single threaded), we run the below code to asynchronously wait for the frame
int currFc = TextureProducer.GetTextureFrameCount();
int iterations = 0;
int maxIterations = 50;
//+1 as often there will be an extra frame produced after pause (so we need to wait for the second frame instead)
while((currFc + 1) >= TextureProducer.GetTextureFrameCount() && iterations++ < maxIterations)
{
yield return null;
}
}
frame = TextureProducer.GetTexture();
}
}
else
{
frame = TextureProducer.GetTexture();
}
}
if (frame != null)
{
result = Helper.GetReadableTexture(frame, TextureProducer.RequiresVerticalFlip(), Helper.GetOrientation(Info.GetTextureTransform()), target);
}
#else
Texture2D result = ExtractFrame(target, timeSeconds, accurateSeek, timeoutMs, timeThresholdMs);
#endif
callback(result);
yield return null;
}
public void ExtractFrameAsync(Texture2D target, ProcessExtractedFrame callback, double timeSeconds = -1.0, bool accurateSeek = true, int timeoutMs = 1000, int timeThresholdMs = 100)
{
StartCoroutine(ExtractFrameCoroutine(target, callback, timeSeconds, accurateSeek, timeoutMs, timeThresholdMs));
}
// "target" can be null or you can pass in an existing texture.
public Texture2D ExtractFrame(Texture2D target, double timeSeconds = -1.0, bool accurateSeek = true, int timeoutMs = 1000, int timeThresholdMs = 100)
{
Texture2D result = target;
// Extract frames returns the internal frame of the video player
Texture frame = ExtractFrame(timeSeconds, accurateSeek, timeoutMs, timeThresholdMs);
if (frame != null)
{
result = Helper.GetReadableTexture(frame, TextureProducer.RequiresVerticalFlip(), Helper.GetOrientation(Info.GetTextureTransform()), target);
}
return result;
}
private Texture ExtractFrame(double timeSeconds = -1.0, bool accurateSeek = true, int timeoutMs = 1000, int timeThresholdMs = 100)
{
Texture result = null;
if (_controlInterface != null)
{
if (timeSeconds >= 0f)
{
Pause();
// If the right frame is already available (or close enough) just grab it
if (TextureProducer.GetTexture() != null && (System.Math.Abs(_controlInterface.GetCurrentTime() - timeSeconds) < (timeThresholdMs / 1000.0)))
{
result = TextureProducer.GetTexture();
}
else
{
// Store frame count before seek
int frameCount = TextureProducer.GetTextureFrameCount();
// Seek to the frame
if (accurateSeek)
{
_controlInterface.Seek(timeSeconds);
}
else
{
_controlInterface.SeekFast(timeSeconds);
}
// Wait for frame to change
ForceWaitForNewFrame(frameCount, timeoutMs);
result = TextureProducer.GetTexture();
}
}
else
{
result = TextureProducer.GetTexture();
}
}
return result;
}
#endregion // Extract Frame
}
}
@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 810d3ce69a3b01f409c733c7cfbd119c
timeCreated: 1544813302
licenseType: Pro
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {fileID: 2800000, guid: bb83b41b53a59874692b83eab5873998, type: 3}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,129 @@
using UnityEngine;
//-----------------------------------------------------------------------------
// Copyright 2015-2022 RenderHeads Ltd. All rights reserved.
//-----------------------------------------------------------------------------
namespace RenderHeads.Media.AVProVideo
{
public partial class MediaPlayer : MonoBehaviour
{
public bool OpenMediaFromBuffer(byte[] buffer, bool autoPlay = true)
{
_mediaPath = new MediaPath("buffer", MediaPathType.AbsolutePathOrURL);
_autoPlayOnStart = autoPlay;
if (_controlInterface == null)
{
Initialise();
}
return OpenMediaFromBufferInternal(buffer);
}
public bool StartOpenChunkedMediaFromBuffer(ulong length, bool autoPlay = true)
{
_mediaPath = new MediaPath("buffer", MediaPathType.AbsolutePathOrURL);
_autoPlayOnStart = autoPlay;
if (_controlInterface == null)
{
Initialise();
}
return StartOpenMediaFromBufferInternal(length);
}
public bool AddChunkToVideoBuffer(byte[] chunk, ulong offset, ulong chunkSize)
{
return AddChunkToBufferInternal(chunk, offset, chunkSize);
}
public bool EndOpenChunkedVideoFromBuffer()
{
return EndOpenMediaFromBufferInternal();
}
private bool OpenMediaFromBufferInternal(byte[] buffer)
{
bool result = false;
// Open the video file
if (_controlInterface != null)
{
CloseMedia();
_isMediaOpened = true;
_autoPlayOnStartTriggered = !_autoPlayOnStart;
Helper.LogInfo("Opening buffer of length " + buffer.Length, this);
if (!_controlInterface.OpenMediaFromBuffer(buffer))
{
Debug.LogError("[AVProVideo] Failed to open buffer", this);
if (GetCurrentPlatformOptions() != PlatformOptionsWindows || PlatformOptionsWindows.videoApi != Windows.VideoApi.DirectShow)
{
Debug.LogError("[AVProVideo] Loading from buffer is currently only supported in Windows when using the DirectShow API");
}
}
else
{
SetPlaybackOptions();
result = true;
StartRenderCoroutine();
}
}
return result;
}
private bool StartOpenMediaFromBufferInternal(ulong length)
{
bool result = false;
// Open the video file
if (_controlInterface != null)
{
CloseMedia();
_isMediaOpened = true;
_autoPlayOnStartTriggered = !_autoPlayOnStart;
Helper.LogInfo("Starting Opening buffer of length " + length, this);
if (!_controlInterface.StartOpenMediaFromBuffer(length))
{
Debug.LogError("[AVProVideo] Failed to start open video from buffer", this);
if (GetCurrentPlatformOptions() != PlatformOptionsWindows || PlatformOptionsWindows.videoApi != Windows.VideoApi.DirectShow)
{
Debug.LogError("[AVProVideo] Loading from buffer is currently only supported in Windows when using the DirectShow API");
}
}
else
{
SetPlaybackOptions();
result = true;
StartRenderCoroutine();
}
}
return result;
}
private bool AddChunkToBufferInternal(byte[] chunk, ulong offset, ulong chunkSize)
{
if (Control != null)
{
return Control.AddChunkToMediaBuffer(chunk, offset, chunkSize);
}
return false;
}
private bool EndOpenMediaFromBufferInternal()
{
if (Control != null)
{
return Control.EndOpenMediaFromBuffer();
}
return false;
}
}
}
@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: bd1bd18da7d2dc7468c9799e5b02caea
timeCreated: 1544813302
licenseType: Pro
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {fileID: 2800000, guid: bb83b41b53a59874692b83eab5873998, type: 3}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,60 @@
using UnityEngine;
#if NETFX_CORE
using Windows.Storage.Streams;
#endif
//-----------------------------------------------------------------------------
// Copyright 2015-2022 RenderHeads Ltd. All rights reserved.
//-----------------------------------------------------------------------------
namespace RenderHeads.Media.AVProVideo
{
public partial class MediaPlayer : MonoBehaviour
{
#if NETFX_CORE
public bool OpenVideoFromStream(IRandomAccessStream ras, string path, bool autoPlay = true)
{
_videoLocation = FileLocation.AbsolutePathOrURL;
_videoPath = path;
_autoPlayOnStart = autoPlay;
if (_controlInterface == null)
{
Initialise();
}
return OpenVideoFromStream(ras);
}
private bool OpenVideoFromStream(IRandomAccessStream ras)
{
bool result = false;
// Open the video file
if (_controlInterface != null)
{
CloseVideo();
_isVideoOpened = true;
_autoPlayOnStartTriggered = !_autoPlayOnStart;
// Potentially override the file location
long fileOffset = GetPlatformFileOffset();
if (!Control.OpenVideoFromFile(ras, _videoPath, fileOffset, null, _manuallySetAudioSourceProperties ? _sourceAudioSampleRate : 0,
_manuallySetAudioSourceProperties ? _sourceAudioChannels : 0))
{
Debug.LogError("[AVProVideo] Failed to open " + _videoPath, this);
}
else
{
SetPlaybackOptions();
result = true;
StartRenderCoroutine();
}
}
return result;
}
#endif
}
}
@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 4e6c8c5399247d0478ed7ecf17b7d87f
timeCreated: 1544813302
licenseType: Pro
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {fileID: 2800000, guid: bb83b41b53a59874692b83eab5873998, type: 3}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,717 @@
using UnityEngine;
using System;
using System.Collections.Generic;
//-----------------------------------------------------------------------------
// Copyright 2015-2022 RenderHeads Ltd. All rights reserved.
//-----------------------------------------------------------------------------
namespace RenderHeads.Media.AVProVideo
{
public partial class MediaPlayer : MonoBehaviour
{
#region PlatformOptions
[System.Serializable]
public class PlatformOptions
{
public virtual bool IsModified()
{
return (httpHeaders.IsModified()
|| keyAuth.IsModified()
);
}
public HttpHeaderData httpHeaders = new HttpHeaderData();
public KeyAuthData keyAuth = new KeyAuthData();
// Decryption support
public virtual string GetKeyServerAuthToken() { return keyAuth.keyServerToken; }
//public virtual string GetKeyServerURL() { return null; }
public virtual byte[] GetOverrideDecryptionKey() { return keyAuth.overrideDecryptionKey; }
public virtual bool StartWithHighestBandwidth() { return false; }
}
[System.Serializable]
public class OptionsWindows : PlatformOptions, ISerializationCallbackReceiver
{
public Windows.VideoApi videoApi = Windows.VideoApi.MediaFoundation;
public bool useHardwareDecoding = true;
public bool useTextureMips = false;
public bool use10BitTextures = false;
public bool hintAlphaChannel = false;
public bool useLowLatency = false;
public bool useCustomMovParser = false;
public bool useHapNotchLC = false;
public bool useStereoDetection = true;
public bool useTextTrackSupport = true;
public bool useFacebookAudio360Support = true;
public bool useAudioDelay = false;
public BufferedFrameSelectionMode bufferedFrameSelection = BufferedFrameSelectionMode.None;
public bool pauseOnPrerollComplete = false;
public string forceAudioOutputDeviceName = string.Empty;
public List<string> preferredFilters = new List<string>();
public Windows.AudioOutput audioOutput = Windows.AudioOutput.System;
public Audio360ChannelMode audio360ChannelMode = Audio360ChannelMode.TBE_8_2;
/// WinRT only
public bool startWithHighestBitrate = false;
/// WinRT only
public bool useLowLiveLatency = false;
/// Hap & NotchLC only
[Range(1, 16)]
public int parallelFrameCount = 3;
/// Hap & NotchLC only
[Range(1, 16)]
public int prerollFrameCount = 4;
public override bool IsModified()
{
return (base.IsModified()
|| !useHardwareDecoding
|| useTextureMips
|| use10BitTextures
|| hintAlphaChannel
|| useLowLatency
|| useCustomMovParser
|| useHapNotchLC
|| !useStereoDetection
|| !useTextTrackSupport
|| !useFacebookAudio360Support
|| useAudioDelay
|| pauseOnPrerollComplete
|| bufferedFrameSelection != BufferedFrameSelectionMode.None
|| videoApi != Windows.VideoApi.MediaFoundation
|| audioOutput != Windows.AudioOutput.System
|| audio360ChannelMode != Audio360ChannelMode.TBE_8_2
|| !string.IsNullOrEmpty(forceAudioOutputDeviceName)
|| preferredFilters.Count != 0
|| startWithHighestBitrate
|| useLowLiveLatency
|| parallelFrameCount != 3
|| prerollFrameCount != 4
);
}
public override bool StartWithHighestBandwidth() { return startWithHighestBitrate; }
#region Upgrade from Version 1.x
[SerializeField, HideInInspector]
private bool useUnityAudio = false;
[SerializeField, HideInInspector]
private bool enableAudio360 = false;
void ISerializationCallbackReceiver.OnBeforeSerialize() { }
void ISerializationCallbackReceiver.OnAfterDeserialize()
{
if (useUnityAudio && audioOutput == Windows.AudioOutput.System)
{
audioOutput = Windows.AudioOutput.Unity;
useUnityAudio = false;
}
if (enableAudio360 && audioOutput == Windows.AudioOutput.System)
{
audioOutput = Windows.AudioOutput.FacebookAudio360;
enableAudio360 = false;
}
}
#endregion // Upgrade from Version 1.x
}
[System.Serializable]
public class OptionsWindowsUWP : PlatformOptions
{
public bool useHardwareDecoding = true;
public bool useTextureMips = false;
public bool use10BitTextures = false;
public bool hintOutput10Bit = false;
public bool useLowLatency = false;
public WindowsUWP.VideoApi videoApi = WindowsUWP.VideoApi.WinRT;
public WindowsUWP.AudioOutput audioOutput = WindowsUWP.AudioOutput.System;
public Audio360ChannelMode audio360ChannelMode = Audio360ChannelMode.TBE_8_2;
/// WinRT only
public bool startWithHighestBitrate = false;
/// WinRT only
public bool useLowLiveLatency = false;
public override bool IsModified()
{
return (base.IsModified()
|| !useHardwareDecoding
|| useTextureMips
|| use10BitTextures
|| useLowLatency
|| audioOutput != WindowsUWP.AudioOutput.System
|| (audio360ChannelMode != Audio360ChannelMode.TBE_8_2)
|| videoApi != WindowsUWP.VideoApi.WinRT
|| startWithHighestBitrate
|| useLowLiveLatency
);
}
public override bool StartWithHighestBandwidth() { return startWithHighestBitrate; }
}
[System.Serializable]
public class OptionsApple: PlatformOptions
{
public enum TextureFormat: int
{
BGRA,
YCbCr420,
}
public enum AudioMode
{
SystemDirect,
Unity,
SystemDirectWithCapture,
};
[Flags]
public enum Flags: int
{
// Common
None = 0,
GenerateMipMaps = 1 << 0,
// iOS & macOS
AllowExternalPlayback = 1 << 8,
PlayWithoutBuffering = 1 << 9,
UseSinglePlayerItem = 1 << 10,
// iOS
ResumeMediaPlaybackAfterAudioSessionRouteChange = 1 << 16,
}
public enum Resolution
{
NoPreference,
_480p,
_720p,
_1080p,
_1440p,
_2160p,
Custom
}
public enum BitRateUnits
{
bps,
Kbps,
Mbps,
}
private readonly TextureFormat DefaultTextureFormat;
private readonly Flags DefaultFlags;
public TextureFormat textureFormat;
private AudioMode _previousAudioMode = AudioMode.SystemDirect;
public AudioMode previousAudioMode
{
get { return _previousAudioMode; }
}
[SerializeField]
private AudioMode _audioMode;
public AudioMode audioMode
{
get { return _audioMode; }
set
{
if (_audioMode != value)
{
_previousAudioMode = _audioMode;
_audioMode = value;
_changed |= ChangeFlags.AudioMode;
}
}
}
[SerializeField]
private Flags _flags;
public Flags flags
{
get { return _flags; }
set
{
Flags changed = _flags ^ value;
if (changed != 0)
{
if ((changed & Flags.PlayWithoutBuffering) == Flags.PlayWithoutBuffering)
{
_changed |= ChangeFlags.PlayWithoutBuffering;
}
if ((changed & Flags.ResumeMediaPlaybackAfterAudioSessionRouteChange) == Flags.ResumeMediaPlaybackAfterAudioSessionRouteChange)
{
_changed |= ChangeFlags.ResumeMediaPlaybackAfterAudioSessionRouteChange;
}
_flags = value;
}
}
}
public float maximumPlaybackRate = 2.0f;
[Flags]
public enum ChangeFlags: int
{
None = 0,
PreferredPeakBitRate = 1 << 1,
PreferredForwardBufferDuration = 1 << 2,
PlayWithoutBuffering = 1 << 3,
PreferredMaximumResolution = 1 << 4,
AudioMode = 1 << 5,
ResumeMediaPlaybackAfterAudioSessionRouteChange = 1 << 6,
All = -1
}
private ChangeFlags _changed = ChangeFlags.None;
[SerializeField]
private float _preferredPeakBitRate = 0.0f;
public float preferredPeakBitRate
{
get { return _preferredPeakBitRate; }
set
{
if (_preferredPeakBitRate != value)
{
_changed |= ChangeFlags.PreferredPeakBitRate;
_preferredPeakBitRate = value;
}
}
}
[SerializeField]
private BitRateUnits _preferredPeakBitRateUnits = BitRateUnits.Kbps;
public BitRateUnits preferredPeakBitRateUnits
{
get { return _preferredPeakBitRateUnits; }
set
{
if (_preferredPeakBitRateUnits != value)
{
_changed |= ChangeFlags.PreferredPeakBitRate;
_preferredPeakBitRateUnits = value;
}
}
}
[SerializeField]
private double _preferredForwardBufferDuration = 0.0;
public double preferredForwardBufferDuration
{
get
{
return _preferredForwardBufferDuration;
}
set
{
if (_preferredForwardBufferDuration != value)
{
_changed |= ChangeFlags.PreferredForwardBufferDuration;
_preferredForwardBufferDuration = value;
}
}
}
[SerializeField]
private Resolution _preferredMaximumResolution = Resolution.NoPreference;
public Resolution preferredMaximumResolution
{
get
{
return _preferredMaximumResolution;
}
set
{
if (_preferredMaximumResolution != value)
{
_changed |= ChangeFlags.PreferredMaximumResolution;
_preferredMaximumResolution = value;
}
}
}
#if UNITY_2017_2_OR_NEWER
[SerializeField]
private Vector2Int _customPreferredMaximumResolution = Vector2Int.zero;
public Vector2Int customPreferredMaximumResolution
{
get
{
return _customPreferredMaximumResolution;
}
set
{
if (_customPreferredMaximumResolution != value)
{
_changed |= ChangeFlags.PreferredMaximumResolution;
_customPreferredMaximumResolution = value;
}
}
}
#endif
private static double BitRateInBitsPerSecond(float value, BitRateUnits units)
{
switch (units)
{
case BitRateUnits.bps:
return (double)value;
case BitRateUnits.Kbps:
return (double)value * 1000.0;
case BitRateUnits.Mbps:
return (double)value * 1000000.0;
default:
return 0.0;
}
}
public double GetPreferredPeakBitRateInBitsPerSecond()
{
return BitRateInBitsPerSecond(preferredPeakBitRate, preferredPeakBitRateUnits);
}
public OptionsApple(TextureFormat defaultTextureFormat, Flags defaultFlags)
{
DefaultTextureFormat = defaultTextureFormat;
DefaultFlags = defaultFlags;
textureFormat = defaultTextureFormat;
audioMode = AudioMode.SystemDirect;
flags = defaultFlags;
}
public override bool IsModified()
{
return base.IsModified()
|| textureFormat != DefaultTextureFormat
|| audioMode != AudioMode.SystemDirect
|| flags != DefaultFlags
|| preferredMaximumResolution != Resolution.NoPreference
|| preferredPeakBitRate != 0.0f
|| preferredForwardBufferDuration != 0.0;
}
public bool HasChanged(ChangeFlags flags = ChangeFlags.All)
{
return (_changed & flags) != ChangeFlags.None;
}
public void ClearChanges()
{
_changed = ChangeFlags.None;
}
}
[System.Serializable]
public class OptionsAndroid : PlatformOptions, ISerializationCallbackReceiver
{
public enum Resolution
{
NoPreference,
_480p,
_720p,
_1080p,
_2160p,
Custom
}
public enum BitRateUnits
{
bps,
Kbps,
Mbps,
}
[Flags]
public enum ChangeFlags : int
{
None = 0,
PreferredPeakBitRate = 1 << 1,
PreferredMaximumResolution = 1 << 2,
PreferredCustomResolution = 1 << 3,
All = -1
}
private ChangeFlags _changed = ChangeFlags.None;
[SerializeField]
private Resolution _preferredMaximumResolution = Resolution.NoPreference;
public Resolution preferredMaximumResolution
{
get { return _preferredMaximumResolution; }
set
{
if (_preferredMaximumResolution != value)
{
_changed |= ChangeFlags.PreferredMaximumResolution;
_preferredMaximumResolution = value;
}
}
}
#if UNITY_2017_2_OR_NEWER
[SerializeField]
private Vector2Int _customPreferredMaximumResolution = Vector2Int.zero;
public Vector2Int customPreferredMaximumResolution
{
get { return _customPreferredMaximumResolution; }
set
{
if (_customPreferredMaximumResolution != value)
{
_changed |= ChangeFlags.PreferredCustomResolution;
_customPreferredMaximumResolution = value;
}
}
}
#endif
[SerializeField]
private float _preferredPeakBitRate = 0.0f;
public float preferredPeakBitRate
{
get { return _preferredPeakBitRate; }
set
{
if (_preferredPeakBitRate != value)
{
_changed |= ChangeFlags.PreferredPeakBitRate;
_preferredPeakBitRate = value;
}
}
}
[SerializeField]
private BitRateUnits _preferredPeakBitRateUnits = BitRateUnits.Kbps;
public BitRateUnits preferredPeakBitRateUnits
{
get { return _preferredPeakBitRateUnits; }
set
{
if (_preferredPeakBitRateUnits != value)
{
_changed |= ChangeFlags.PreferredPeakBitRate;
_preferredPeakBitRateUnits = value;
}
}
}
public Android.VideoApi videoApi = Android.VideoApi.ExoPlayer;
public bool useFastOesPath = false;
public bool showPosterFrame = false;
public Android.AudioOutput audioOutput = Android.AudioOutput.System;
public Audio360ChannelMode audio360ChannelMode = Audio360ChannelMode.TBE_8_2;
public bool preferSoftwareDecoder = false;
public bool forceRtpTCP = false;
public Android.TextureFiltering blitTextureFiltering = Android.TextureFiltering.Point;
[SerializeField, Tooltip("Byte offset into the file where the media file is located. This is useful when hiding or packing media files within another file.")]
public int fileOffset = 0;
public bool startWithHighestBitrate = false;
public int minBufferMs = Android.Default_MinBufferTimeMs;
public int maxBufferMs = Android.Default_MaxBufferTimeMs;
public int bufferForPlaybackMs = Android.Default_BufferForPlaybackMs;
public int bufferForPlaybackAfterRebufferMs = Android.Default_BufferForPlaybackAfterRebufferMs;
public override bool IsModified()
{
return (base.IsModified()
|| (fileOffset != 0)
|| useFastOesPath
|| showPosterFrame
|| (videoApi != Android.VideoApi.ExoPlayer)
|| audioOutput != Android.AudioOutput.System
|| (audio360ChannelMode != Audio360ChannelMode.TBE_8_2)
|| preferSoftwareDecoder
|| forceRtpTCP
|| startWithHighestBitrate
|| (minBufferMs != Android.Default_MinBufferTimeMs)
|| (maxBufferMs != Android.Default_MaxBufferTimeMs)
|| (bufferForPlaybackMs != Android.Default_BufferForPlaybackMs)
|| (bufferForPlaybackAfterRebufferMs != Android.Default_BufferForPlaybackAfterRebufferMs)
|| (preferredMaximumResolution != Resolution.NoPreference)
|| (preferredPeakBitRate != 0.0f)
|| (blitTextureFiltering != Android.TextureFiltering.Point)
);
}
private static double BitRateInBitsPerSecond(float value, BitRateUnits units)
{
switch (units)
{
case BitRateUnits.bps:
return (double)value;
case BitRateUnits.Kbps:
return (double)value * 1000.0;
case BitRateUnits.Mbps:
return (double)value * 1000000.0;
default:
return 0.0;
}
}
public double GetPreferredPeakBitRateInBitsPerSecond()
{
_changed &= ~ChangeFlags.PreferredPeakBitRate;
return BitRateInBitsPerSecond(preferredPeakBitRate, preferredPeakBitRateUnits);
}
public override bool StartWithHighestBandwidth()
{
return startWithHighestBitrate;
}
public bool HasChanged(ChangeFlags flags = ChangeFlags.All, bool bClearFlags = false)
{
bool bReturn = ((_changed & flags) != ChangeFlags.None);
if (bClearFlags)
{
_changed = ChangeFlags.None;
}
return bReturn;
}
#region Upgrade from Version 1.x
[SerializeField, HideInInspector]
private bool enableAudio360 = false;
void ISerializationCallbackReceiver.OnBeforeSerialize() { }
void ISerializationCallbackReceiver.OnAfterDeserialize()
{
if (enableAudio360 && audioOutput == Android.AudioOutput.System)
{
audioOutput = Android.AudioOutput.FacebookAudio360;
enableAudio360 = false;
}
}
#endregion // Upgrade from Version 1.x
}
[System.Serializable]
public class OptionsWebGL : PlatformOptions
{
public WebGL.ExternalLibrary externalLibrary = WebGL.ExternalLibrary.None;
public bool useTextureMips = false;
public override bool IsModified()
{
return (base.IsModified() || externalLibrary != WebGL.ExternalLibrary.None || useTextureMips);
}
// Decryption support
public override string GetKeyServerAuthToken() { return null; }
public override byte[] GetOverrideDecryptionKey() { return null; }
}
// TODO: move these to a Setup object
[SerializeField] OptionsWindows _optionsWindows = new OptionsWindows();
[SerializeField] OptionsApple _optionsMacOSX = new OptionsApple(OptionsApple.TextureFormat.BGRA, OptionsApple.Flags.None);
[SerializeField] OptionsApple _optionsIOS = new OptionsApple(OptionsApple.TextureFormat.BGRA, OptionsApple.Flags.None);
[SerializeField] OptionsApple _optionsTVOS = new OptionsApple(OptionsApple.TextureFormat.BGRA, OptionsApple.Flags.None);
[SerializeField] OptionsAndroid _optionsAndroid = new OptionsAndroid();
[SerializeField] OptionsWindowsUWP _optionsWindowsUWP = new OptionsWindowsUWP();
[SerializeField] OptionsWebGL _optionsWebGL = new OptionsWebGL();
public OptionsWindows PlatformOptionsWindows { get { return _optionsWindows; } }
public OptionsApple PlatformOptionsMacOSX { get { return _optionsMacOSX; } }
public OptionsApple PlatformOptionsIOS { get { return _optionsIOS; } }
public OptionsApple PlatformOptionsTVOS { get { return _optionsTVOS; } }
public OptionsAndroid PlatformOptionsAndroid { get { return _optionsAndroid; } }
public OptionsWindowsUWP PlatformOptionsWindowsUWP { get { return _optionsWindowsUWP; } }
public OptionsWebGL PlatformOptionsWebGL { get { return _optionsWebGL; } }
#endregion // PlatformOptions
}
#region PlatformOptionsExtensions
public static class OptionsAppleExtensions
{
public static bool GenerateMipmaps(this MediaPlayer.OptionsApple.Flags flags)
{
return (flags & MediaPlayer.OptionsApple.Flags.GenerateMipMaps) == MediaPlayer.OptionsApple.Flags.GenerateMipMaps;
}
public static MediaPlayer.OptionsApple.Flags SetGenerateMipMaps(this MediaPlayer.OptionsApple.Flags flags, bool b)
{
if (flags.GenerateMipmaps() ^ b)
{
flags = b ? flags | MediaPlayer.OptionsApple.Flags.GenerateMipMaps
: flags & ~MediaPlayer.OptionsApple.Flags.GenerateMipMaps;
}
return flags;
}
public static bool AllowExternalPlayback(this MediaPlayer.OptionsApple.Flags flags)
{
return (flags & MediaPlayer.OptionsApple.Flags.AllowExternalPlayback) == MediaPlayer.OptionsApple.Flags.AllowExternalPlayback;
}
public static MediaPlayer.OptionsApple.Flags SetAllowExternalPlayback(this MediaPlayer.OptionsApple.Flags flags, bool b)
{
if (flags.AllowExternalPlayback() ^ b)
{
flags = b ? flags | MediaPlayer.OptionsApple.Flags.AllowExternalPlayback
: flags & ~MediaPlayer.OptionsApple.Flags.AllowExternalPlayback;
}
return flags;
}
public static bool PlayWithoutBuffering(this MediaPlayer.OptionsApple.Flags flags)
{
return (flags & MediaPlayer.OptionsApple.Flags.PlayWithoutBuffering) == MediaPlayer.OptionsApple.Flags.PlayWithoutBuffering;
}
public static MediaPlayer.OptionsApple.Flags SetPlayWithoutBuffering(this MediaPlayer.OptionsApple.Flags flags, bool b)
{
if (flags.PlayWithoutBuffering() ^ b)
{
flags = b ? flags | MediaPlayer.OptionsApple.Flags.PlayWithoutBuffering
: flags & ~MediaPlayer.OptionsApple.Flags.PlayWithoutBuffering;
}
return flags;
}
public static bool UseSinglePlayerItem(this MediaPlayer.OptionsApple.Flags flags)
{
return (flags & MediaPlayer.OptionsApple.Flags.UseSinglePlayerItem) == MediaPlayer.OptionsApple.Flags.UseSinglePlayerItem;
}
public static MediaPlayer.OptionsApple.Flags SetUseSinglePlayerItem(this MediaPlayer.OptionsApple.Flags flags, bool b)
{
if (flags.UseSinglePlayerItem() ^ b)
{
flags = b ? flags | MediaPlayer.OptionsApple.Flags.UseSinglePlayerItem
: flags & ~MediaPlayer.OptionsApple.Flags.UseSinglePlayerItem;
}
return flags;
}
public static bool ResumePlaybackAfterAudioSessionRouteChange(this MediaPlayer.OptionsApple.Flags flags)
{
return (flags & MediaPlayer.OptionsApple.Flags.ResumeMediaPlaybackAfterAudioSessionRouteChange) == MediaPlayer.OptionsApple.Flags.ResumeMediaPlaybackAfterAudioSessionRouteChange;
}
public static MediaPlayer.OptionsApple.Flags SetResumePlaybackAfterAudioSessionRouteChange(this MediaPlayer.OptionsApple.Flags flags, bool b)
{
if (flags.ResumePlaybackAfterAudioSessionRouteChange() ^ b)
{
flags = b ? flags | MediaPlayer.OptionsApple.Flags.ResumeMediaPlaybackAfterAudioSessionRouteChange
: flags & ~MediaPlayer.OptionsApple.Flags.ResumeMediaPlaybackAfterAudioSessionRouteChange;
}
return flags;
}
}
#endregion // PlatformOptionsExtensions
}
@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 1d9536a1e758279489d9add3e1ba26ad
timeCreated: 1544813302
licenseType: Pro
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {fileID: 2800000, guid: bb83b41b53a59874692b83eab5873998, type: 3}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,150 @@
using UnityEngine;
using System.Collections;
//-----------------------------------------------------------------------------
// Copyright 2015-2022 RenderHeads Ltd. All rights reserved.
//-----------------------------------------------------------------------------
namespace RenderHeads.Media.AVProVideo
{
public partial class MediaPlayer : MonoBehaviour
{
public bool EnableSubtitles(MediaPath mediaPath)
{
bool result = false;
if (_subtitlesInterface != null)
{
if (mediaPath != null && !string.IsNullOrEmpty(mediaPath.Path))
{
string fullPath = mediaPath.GetResolvedFullPath();
bool checkForFileExist = true;
if (fullPath.Contains("://"))
{
checkForFileExist = false;
}
#if (!UNITY_EDITOR && UNITY_ANDROID)
checkForFileExist = false;
#endif
if (checkForFileExist && !System.IO.File.Exists(fullPath))
{
Debug.LogError("[AVProVideo] Subtitle file not found: " + fullPath, this);
}
else
{
Helper.LogInfo("Opening subtitles " + fullPath, this);
_previousSubtitleIndex = -1;
try
{
if (fullPath.Contains("://"))
{
// Use coroutine and WWW class for loading
if (_loadSubtitlesRoutine != null)
{
StopCoroutine(_loadSubtitlesRoutine);
_loadSubtitlesRoutine = null;
}
_loadSubtitlesRoutine = StartCoroutine(LoadSubtitlesCoroutine(fullPath, mediaPath));
}
else
{
// Load directly from file
string subtitleData = System.IO.File.ReadAllText(fullPath);
if (_subtitlesInterface.LoadSubtitlesSRT(subtitleData))
{
_subtitlePath = mediaPath;
_sideloadSubtitles = false;
result = true;
}
else
{
Debug.LogError("[AVProVideo] Failed to load subtitles" + fullPath, this);
}
}
}
catch (System.Exception e)
{
Debug.LogError("[AVProVideo] Failed to load subtitles " + fullPath, this);
Debug.LogException(e, this);
}
}
}
else
{
Debug.LogError("[AVProVideo] No subtitle file path specified", this);
}
}
else
{
_queueSubtitlePath = mediaPath;
}
return result;
}
private IEnumerator LoadSubtitlesCoroutine(string url, MediaPath mediaPath)
{
UnityEngine.Networking.UnityWebRequest www = UnityEngine.Networking.UnityWebRequest.Get(url);
#if UNITY_2017_2_OR_NEWER
yield return www.SendWebRequest();
#else
yield return www.Send();
#endif
string subtitleData = string.Empty;
#if UNITY_2020_1_OR_NEWER
if (www.result == UnityEngine.Networking.UnityWebRequest.Result.Success)
#elif UNITY_2017_1_OR_NEWER
if (!www.isNetworkError)
#else
if (!www.isError)
#endif
{
subtitleData = ((UnityEngine.Networking.DownloadHandler)www.downloadHandler).text;
}
else
{
Debug.LogError("[AVProVideo] Error loading subtitles '" + www.error + "' from " + url);
}
if (_subtitlesInterface.LoadSubtitlesSRT(subtitleData))
{
_subtitlePath = mediaPath;
_sideloadSubtitles = false;
}
else
{
Debug.LogError("[AVProVideo] Failed to load subtitles" + url, this);
}
_loadSubtitlesRoutine = null;
www.Dispose();
}
public void DisableSubtitles()
{
if (_loadSubtitlesRoutine != null)
{
StopCoroutine(_loadSubtitlesRoutine);
_loadSubtitlesRoutine = null;
}
if (_subtitlesInterface != null)
{
_previousSubtitleIndex = -1;
_sideloadSubtitles = false;
_subtitlesInterface.LoadSubtitlesSRT(string.Empty);
}
else
{
_queueSubtitlePath = null;
}
}
}
}
@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: f4ed2744d6ff80845bbbd59e8f6c732b
timeCreated: 1544813302
licenseType: Pro
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {fileID: 2800000, guid: bb83b41b53a59874692b83eab5873998, type: 3}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,93 @@
using UnityEngine;
//-----------------------------------------------------------------------------
// Copyright 2015-2022 RenderHeads Ltd. All rights reserved.
//-----------------------------------------------------------------------------
namespace RenderHeads.Media.AVProVideo
{
public partial class MediaPlayer : MonoBehaviour
{
#region Support for Time Scale
#if AVPROVIDEO_BETA_SUPPORT_TIMESCALE
// Adjust this value to get faster performance but may drop frames.
// Wait longer to ensure there is enough time for frames to process
private const float TimeScaleTimeoutMs = 20f;
private bool _timeScaleIsControlling;
private double _timeScaleVideoTime;
private void UpdateTimeScale()
{
if (Time.timeScale != 1f || Time.captureFramerate != 0)
{
if (_controlInterface.IsPlaying())
{
_controlInterface.Pause();
_timeScaleIsControlling = true;
_timeScaleVideoTime = _controlInterface.GetCurrentTime();
}
if (_timeScaleIsControlling)
{
// Progress time
_timeScaleVideoTime += Time.deltaTime;
// Handle looping
if (_controlInterface.IsLooping() && _timeScaleVideoTime >= Info.GetDuration())
{
// TODO: really we should seek to (_timeScaleVideoTime % Info.GetDuration())
_timeScaleVideoTime = 0.0;
}
int preSeekFrameCount = TextureProducer.GetTextureFrameCount();
// Seek to the new time
{
double preSeekTime = Control.GetCurrentTime();
// Seek
_controlInterface.Seek(_timeScaleVideoTime);
// Early out, if after the seek the time hasn't changed, the seek was probably too small to go to the next frame.
// TODO: This behaviour may be different on other platforms (not Windows) and needs more testing.
if (Mathf.Approximately((float)preSeekTime, (float)_controlInterface.GetCurrentTime()))
{
return;
}
}
// Wait for the new frame to arrive
if (!_controlInterface.WaitForNextFrame(GetDummyCamera(), preSeekFrameCount))
{
// If WaitForNextFrame fails (e.g. in android single threaded), we run the below code to asynchronously wait for the frame
System.DateTime startTime = System.DateTime.Now;
int lastFrameCount = TextureProducer.GetTextureFrameCount();
while (_controlInterface != null && (System.DateTime.Now - startTime).TotalMilliseconds < (double)TimeScaleTimeoutMs)
{
_playerInterface.Update();
_playerInterface.Render();
GetDummyCamera().Render();
if (lastFrameCount != TextureProducer.GetTextureFrameCount())
{
break;
}
}
}
}
}
else
{
// Restore playback when timeScale becomes 1
if (_timeScaleIsControlling)
{
_controlInterface.Play();
_timeScaleIsControlling = false;
}
}
}
#endif
#endregion // Support for Time Scale
}
}
@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: cdb92d6bab7106944bcd3cd7a034df6e
timeCreated: 1544813302
licenseType: Pro
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {fileID: 2800000, guid: bb83b41b53a59874692b83eab5873998, type: 3}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,83 @@
using UnityEngine;
//-----------------------------------------------------------------------------
// Copyright 2015-2022 RenderHeads Ltd. All rights reserved.
//-----------------------------------------------------------------------------
namespace RenderHeads.Media.AVProVideo
{
public partial class MediaPlayer : MonoBehaviour, ISerializationCallbackReceiver
{
#region Upgrade from Version 1.x
[SerializeField, HideInInspector]
private string m_VideoPath;
[SerializeField, HideInInspector]
private FileLocation m_VideoLocation = FileLocation.RelativeToStreamingAssetsFolder;
private enum FileLocation
{
AbsolutePathOrURL,
RelativeToProjectFolder,
RelativeToStreamingAssetsFolder,
RelativeToDataFolder,
RelativeToPersistentDataFolder,
}
/*
[SerializeField, HideInInspector]
private StereoPacking m_StereoPacking;
[SerializeField, HideInInspector]
private AlphaPacking m_AlphaPacking;
*/
void ISerializationCallbackReceiver.OnBeforeSerialize()
{
/*
m_StereoPacking = _fallbackMediaHints.stereoPacking;
m_AlphaPacking = _fallbackMediaHints.alphaPacking;
*/
}
void ISerializationCallbackReceiver.OnAfterDeserialize()
{
if (!string.IsNullOrEmpty(m_VideoPath))
{
MediaPathType mediaPathType = MediaPathType.AbsolutePathOrURL;
switch (m_VideoLocation)
{
default:
case FileLocation.AbsolutePathOrURL:
mediaPathType = MediaPathType.AbsolutePathOrURL;
break;
case FileLocation.RelativeToProjectFolder:
mediaPathType = MediaPathType.RelativeToProjectFolder;
break;
case FileLocation.RelativeToStreamingAssetsFolder:
mediaPathType = MediaPathType.RelativeToStreamingAssetsFolder;
break;
case FileLocation.RelativeToDataFolder:
mediaPathType = MediaPathType.RelativeToDataFolder;
break;
case FileLocation.RelativeToPersistentDataFolder:
mediaPathType = MediaPathType.RelativeToPersistentDataFolder;
break;
}
_mediaPath = new MediaPath(m_VideoPath, mediaPathType);
_mediaSource = MediaSource.Path;
m_VideoPath = null;
}
/*
if (m_StereoPacking != _fallbackMediaHints.stereoPacking)
{
_fallbackMediaHints.stereoPacking = m_StereoPacking;
}
if (m_AlphaPacking != _fallbackMediaHints.alphaPacking)
{
_fallbackMediaHints.alphaPacking = m_AlphaPacking;
}
*/
}
#endregion // Upgrade from Version 1.x
}
}
@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 2e1421b74b1861b42ba7287d322c2f19
timeCreated: 1614963169
licenseType: Pro
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {fileID: 2800000, guid: bb83b41b53a59874692b83eab5873998, type: 3}
userData:
assetBundleName:
assetBundleVariant:
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,17 @@
fileFormatVersion: 2
guid: e9ea31f33222f4b418e4e051a8a5ed24
timeCreated: 1588679963
licenseType: Pro
MonoImporter:
serializedVersion: 2
defaultReferences:
- m_AudioHeadTransform: {instanceID: 0}
- m_AudioFocusTransform: {instanceID: 0}
- _transitionShader: {fileID: 4800000, guid: 73f378cafe7b4a745907b70e76bb3259, type: 3}
- _playerA: {instanceID: 0}
- _playerB: {instanceID: 0}
executionOrder: 0
icon: {fileID: 2800000, guid: bb83b41b53a59874692b83eab5873998, type: 3}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,234 @@
using UnityEngine;
//-----------------------------------------------------------------------------
// Copyright 2019-2023 RenderHeads Ltd. All rights reserved.
//-----------------------------------------------------------------------------
namespace RenderHeads.Media.AVProVideo
{
/// Renders the video texture to a RenderTexture - either one provided by the user (external) or to an internal one.
/// The video frames can optionally be "resolved" to unpack packed alpha, display a single stereo eye, generate mip maps, and apply colorspace conversions
[AddComponentMenu("AVPro Video/Resolve To RenderTexture", 330)]
[HelpURL("https://www.renderheads.com/products/avpro-video/")]
public class ResolveToRenderTexture : MonoBehaviour
{
[SerializeField] MediaPlayer _mediaPlayer = null;
[SerializeField] VideoResolveOptions _options = VideoResolveOptions.Create();
[SerializeField] VideoRender.ResolveFlags _resolveFlags = (VideoRender.ResolveFlags.ColorspaceSRGB | VideoRender.ResolveFlags.Mipmaps | VideoRender.ResolveFlags.PackedAlpha | VideoRender.ResolveFlags.StereoLeft);
[SerializeField] RenderTexture _externalTexture = null;
private Material _materialResolve;
private bool _isMaterialSetup;
private bool _isMaterialDirty;
private bool _isMaterialOES;
private RenderTexture _internalTexture;
private int _textureFrameCount = -1;
// Material used for blitting the texture as we need a shader to provide clamp to border colour style texture sampling
private Material _materialBlit;
private int _srcTexId;
public MediaPlayer MediaPlayer
{
get
{
return _mediaPlayer;
}
set
{
ChangeMediaPlayer(value);
}
}
public VideoResolveOptions VideoResolveOptions
{
get
{
return _options;
}
set
{
_options = value;
_isMaterialDirty = true;
}
}
public RenderTexture ExternalTexture
{
get
{
return _externalTexture;
}
set
{
_externalTexture = value;
}
}
public RenderTexture TargetTexture
{
get
{
if (_externalTexture == null)
return _internalTexture;
return _externalTexture;
}
}
public void SetMaterialDirty()
{
_isMaterialDirty = true;
}
private void ChangeMediaPlayer(MediaPlayer mediaPlayer)
{
if (_mediaPlayer != mediaPlayer)
{
_mediaPlayer = mediaPlayer;
_textureFrameCount = -1;
_isMaterialSetup = false;
_isMaterialDirty = true;
Resolve();
}
}
void Start()
{
_isMaterialOES = _mediaPlayer != null ? _mediaPlayer.IsUsingAndroidOESPath() : false;
_materialResolve = VideoRender.CreateResolveMaterial(_isMaterialOES);
VideoRender.SetupMaterialForMedia(_materialResolve, _mediaPlayer, -1);
_materialBlit = new Material(Shader.Find("AVProVideo/Internal/Blit"));
_srcTexId = Shader.PropertyToID("_SrcTex");
}
void LateUpdate()
{
Debug.Assert(_mediaPlayer != null);
Resolve();
}
public void Resolve()
{
ITextureProducer textureProducer = _mediaPlayer != null ? _mediaPlayer.TextureProducer : null;
if (textureProducer == null)
return;
if (textureProducer.GetTexture())
{
// Check for a swap between OES and none-OES
bool playerIsOES = _mediaPlayer.IsUsingAndroidOESPath();
if (_isMaterialOES != playerIsOES)
{
_isMaterialOES = playerIsOES;
_materialResolve = VideoRender.CreateResolveMaterial(playerIsOES);
}
if (!_isMaterialSetup)
{
VideoRender.SetupMaterialForMedia(_materialResolve, _mediaPlayer, -1);
_isMaterialSetup = true;
_isMaterialDirty = true;
}
if (_isMaterialDirty)
{
VideoRender.SetupResolveMaterial(_materialResolve, _options);
_isMaterialDirty = false;
}
int textureFrameCount = textureProducer.GetTextureFrameCount();
if (textureFrameCount != _textureFrameCount)
{
_internalTexture = VideoRender.ResolveVideoToRenderTexture(_materialResolve, _internalTexture, textureProducer, _resolveFlags);
_textureFrameCount = textureFrameCount;
if (_internalTexture && _externalTexture)
{
float srcAspectRatio = (float)_internalTexture.width / (float)_internalTexture.height;
float dstAspectRatio = (float)_externalTexture.width / (float)_externalTexture.height;
Vector2 offset = Vector2.zero;
Vector2 scale = new Vector2(1.0f, 1.0f);
// No point in handling the aspect ratio if the textures dimension's are the same
if (srcAspectRatio != dstAspectRatio)
{
switch (_options.aspectRatio)
{
case VideoResolveOptions.AspectRatio.NoScaling:
scale.x = (float)_externalTexture.width / (float)_internalTexture.width;
scale.y = (float)_externalTexture.height / (float)_internalTexture.height;
offset.x = (1.0f - scale.x) * 0.5f;
offset.y = (1.0f - scale.y) * 0.5f;
break;
case VideoResolveOptions.AspectRatio.FitVertically:
scale.x = (float)_internalTexture.height / (float)_internalTexture.width * dstAspectRatio;
offset.x = (1.0f - scale.x) * 0.5f;
break;
case VideoResolveOptions.AspectRatio.FitHorizontally:
scale.y = (float)_externalTexture.height / (float)_externalTexture.width * srcAspectRatio;
offset.y = (1.0f - scale.y) * 0.5f;
break;
case VideoResolveOptions.AspectRatio.FitInside:
{
if (srcAspectRatio > dstAspectRatio)
goto case VideoResolveOptions.AspectRatio.FitHorizontally;
else if (srcAspectRatio < dstAspectRatio)
goto case VideoResolveOptions.AspectRatio.FitVertically;
} break;
case VideoResolveOptions.AspectRatio.FitOutside:
{
if (srcAspectRatio > dstAspectRatio)
goto case VideoResolveOptions.AspectRatio.FitVertically;
else if (srcAspectRatio < dstAspectRatio)
goto case VideoResolveOptions.AspectRatio.FitHorizontally;
} break;
case VideoResolveOptions.AspectRatio.Stretch:
break;
}
}
// NOTE: This blit can be removed once we can ResolveVideoToRenderTexture is made not to recreate textures
// NOTE: This blit probably doesn't do correct linear/srgb conversion if the colorspace settings differ, may have to use GL.sRGBWrite
// NOTE: Cannot use _MainTex as Graphics.Blit replaces the texture offset and scale when using a material
_materialBlit.SetTexture(_srcTexId, _internalTexture);
_materialBlit.SetTextureOffset(_srcTexId, offset);
_materialBlit.SetTextureScale(_srcTexId, scale);
Graphics.Blit(null, _externalTexture, _materialBlit, 0);
}
}
}
}
void OnDisable()
{
if (_internalTexture)
{
RenderTexture.ReleaseTemporary(_internalTexture); _internalTexture = null;
}
}
void OnDestroy()
{
if (_materialResolve)
{
Destroy(_materialResolve); _materialResolve = null;
}
}
#if false
void OnGUI()
{
if (TargetTexture)
{
GUI.DrawTexture(new Rect(0f, 0f, Screen.width * 0.8f, Screen.height * 0.8f), TargetTexture, ScaleMode.ScaleToFit, true);
}
}
#endif
}
}
@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 448e5e4039505584c852da1a7cc5c361
timeCreated: 1654790987
licenseType: Pro
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {fileID: 2800000, guid: bb83b41b53a59874692b83eab5873998, type: 3}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,178 @@
#if UNITY_ANDROID
#if USING_URP
#define ANDROID_URP
#endif
#endif
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Rendering;
//-----------------------------------------------------------------------------
// Copyright 2015-2022 RenderHeads Ltd. All rights reserved.
//-----------------------------------------------------------------------------
namespace RenderHeads.Media.AVProVideo
{
/// <summary>
/// This script is needed to send the camera position to the stereo shader so that
/// it can determine which eye it is rendering. This is only needed for multi-pass
/// rendering, as single pass has a built-in shader variable
/// </summary>
[AddComponentMenu("AVPro Video/Update Multi-Pass Stereo", 320)]
[HelpURL("https://www.renderheads.com/products/avpro-video/")]
public class UpdateMultiPassStereo : MonoBehaviour
{
[Header("Stereo camera")]
[SerializeField] Camera _camera = null;
public Camera Camera
{
get { return _camera; }
set { _camera = value; }
}
private static readonly LazyShaderProperty PropWorldCameraPosition = new LazyShaderProperty("_WorldCameraPosition");
private static readonly LazyShaderProperty PropWorldCameraRight = new LazyShaderProperty("_WorldCameraRight");
// State
private Camera _foundCamera;
void Awake()
{
if (_camera == null)
{
Debug.LogWarning("[AVProVideo] No camera set for UpdateMultiPassStereo component. If you are rendering in multi-pass stereo then it is recommended to set this.");
}
}
void Start()
{
LogXRDeviceDetails();
#if ANDROID_URP
if( GetComponent<Camera>() == null )
{
throw new MissingComponentException("[AVProVideo] When using URP the UpdateMultiPassStereo component must be on the Camera gameobject. This component is not required on all VR devices, but if it is then stereo eye rendering may not work correctly.");
}
#endif
}
private void LogXRDeviceDetails()
{
#if UNITY_2019_1_OR_NEWER && !UNITY_TVOS
string logOutput = "[AVProVideo] XR Device details: UnityEngine.XR.XRSettings.loadedDeviceName = " + UnityEngine.XR.XRSettings.loadedDeviceName + " | supportedDevices = ";
string[] aSupportedDevices = UnityEngine.XR.XRSettings.supportedDevices;
int supportedDeviceCount = aSupportedDevices.Length;
for (int i = 0; i < supportedDeviceCount; i++)
{
logOutput += aSupportedDevices[i];
if( i < (supportedDeviceCount - 1 ))
{
logOutput += ", ";
}
}
List<UnityEngine.XR.InputDevice> inputDevices = new List<UnityEngine.XR.InputDevice>();
UnityEngine.XR.InputDevices.GetDevices(inputDevices);
int deviceCount = inputDevices.Count;
if (deviceCount > 0)
{
logOutput += " | XR Devices = ";
for (int i = 0; i < deviceCount; i++)
{
logOutput += inputDevices[i].name;
if( i < (deviceCount -1 ))
{
logOutput += ", ";
}
}
}
UnityEngine.XR.InputDevice headDevice = UnityEngine.XR.InputDevices.GetDeviceAtXRNode(UnityEngine.XR.XRNode.Head);
if( headDevice != null )
{
logOutput += " | headDevice name = " + headDevice.name + ", manufacturer = " + headDevice.manufacturer;
}
Debug.Log(logOutput);
#endif
}
#if ANDROID_URP
void OnEnable()
{
RenderPipelineManager.beginCameraRendering += RenderPipelineManager_beginCameraRendering;
}
void OnDisable()
{
RenderPipelineManager.beginCameraRendering -= RenderPipelineManager_beginCameraRendering;
}
#endif
private static bool IsMultiPassVrEnabled()
{
#if UNITY_TVOS
return false;
#else
#if UNITY_2017_2_OR_NEWER
if (!UnityEngine.XR.XRSettings.enabled) return false;
#endif
#if UNITY_2018_3_OR_NEWER
if (UnityEngine.XR.XRSettings.stereoRenderingMode != UnityEngine.XR.XRSettings.StereoRenderingMode.MultiPass) return false;
#endif
return true;
#endif
}
// We do a LateUpdate() to allow for any changes in the camera position that may have happened in Update()
#if ANDROID_URP
// Android URP
private void RenderPipelineManager_beginCameraRendering(ScriptableRenderContext context, Camera camera)
#else
// Normal render pipeline
private void LateUpdate()
#endif
{
if (!IsMultiPassVrEnabled())
{
return;
}
if (_camera != null && _foundCamera != _camera)
{
_foundCamera = _camera;
}
if (_foundCamera == null)
{
_foundCamera = Camera.main;
if (_foundCamera == null)
{
Debug.LogWarning("[AVProVideo] Cannot find main camera for UpdateMultiPassStereo, this can lead to eyes flickering");
if (Camera.allCameras.Length > 0)
{
_foundCamera = Camera.allCameras[0];
Debug.LogWarning("[AVProVideo] UpdateMultiPassStereo using camera " + _foundCamera.name);
}
}
}
if (_foundCamera != null)
{
#if ANDROID_URP
Shader.EnableKeyword("USING_URP");
#else
Shader.DisableKeyword("USING_URP");
#endif
Shader.SetGlobalVector(PropWorldCameraPosition.Id, _foundCamera.transform.position);
Shader.SetGlobalVector(PropWorldCameraRight.Id, _foundCamera.transform.right);
}
}
}
}
@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 8b2366b5575fcba46a0f97038fb6c5fb
timeCreated: 1611065944
licenseType: Pro
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {fileID: 2800000, guid: bb83b41b53a59874692b83eab5873998, type: 3}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,5 @@
fileFormatVersion: 2
guid: 1bb8f28c4529a1343b4430d732bb5f2a
folderAsset: yes
DefaultImporter:
userData:
@@ -0,0 +1,124 @@
#if UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX || UNITY_IOS || UNITY_TVOS
#define UNITY_PLATFORM_SUPPORTS_YPCBCR
#endif
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace RenderHeads.Media.AVProVideo
{
/// <summary>
/// Base class to apply texture from MediaPlayer
/// </summary>
public abstract class ApplyToBase : MonoBehaviour
{
[Header("Media Source")]
[Space(8f)]
[SerializeField] protected MediaPlayer _media = null;
public MediaPlayer Player
{
get { return _media; }
set { ChangeMediaPlayer(value); }
}
[Space(8f)]
[Header("Display")]
[SerializeField] bool _automaticStereoPacking = true;
public bool AutomaticStereoPacking
{
get { return _automaticStereoPacking; }
set { if (_automaticStereoPacking != value) { _automaticStereoPacking = value; _isDirty = true; } }
}
[SerializeField] StereoPacking _overrideStereoPacking = StereoPacking.None;
public StereoPacking OverrideStereoPacking
{
get { return _overrideStereoPacking; }
set { if (_overrideStereoPacking != value) { _overrideStereoPacking = value; _isDirty = true; } }
}
[SerializeField] bool _stereoRedGreenTint = false;
public bool StereoRedGreenTint { get { return _stereoRedGreenTint; } set { if (_stereoRedGreenTint != value) { _stereoRedGreenTint = value; _isDirty = true; } } }
protected bool _isDirty = false;
void Awake()
{
ChangeMediaPlayer(_media, force:true);
}
private void ChangeMediaPlayer(MediaPlayer player, bool force = false)
{
if (_media != player || force)
{
if (_media != null)
{
_media.Events.RemoveListener(OnMediaPlayerEvent);
}
_media = player;
if (_media != null)
{
_media.Events.AddListener(OnMediaPlayerEvent);
}
_isDirty = true;
}
}
// Callback function to handle events
private void OnMediaPlayerEvent(MediaPlayer mp, MediaPlayerEvent.EventType et, ErrorCode errorCode)
{
switch (et)
{
case MediaPlayerEvent.EventType.FirstFrameReady:
case MediaPlayerEvent.EventType.PropertiesChanged:
ForceUpdate();
break;
}
}
public void ForceUpdate()
{
_isDirty = true;
if (this.isActiveAndEnabled)
{
Apply();
}
}
private void Start()
{
SaveProperties();
Apply();
}
protected virtual void OnEnable()
{
SaveProperties();
ForceUpdate();
}
protected virtual void OnDisable()
{
RestoreProperties();
}
private void OnDestroy()
{
ChangeMediaPlayer(null);
}
protected virtual void SaveProperties()
{
}
protected virtual void RestoreProperties()
{
}
public abstract void Apply();
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 33aa9dfec55e1f6438ee868d02dcabe2
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,215 @@
using System.Collections.Generic;
using UnityEngine;
using System;
//-----------------------------------------------------------------------------
// Copyright 2015-2021 RenderHeads Ltd. All rights reserved.
//-----------------------------------------------------------------------------
namespace RenderHeads.Media.AVProVideo
{
/// <summary>
/// A singleton to handle multiple instances of the AudioOutput component
/// </summary>
public class AudioOutputManager
{
private static AudioOutputManager _instance = null;
public static AudioOutputManager Instance
{
get
{
if (_instance == null)
{
_instance = new AudioOutputManager();
}
return _instance;
}
}
protected class PlayerInstance
{
public HashSet<AudioOutput> outputs;
public float[] pcmData;
public bool isPcmDataReady;
}
private Dictionary<MediaPlayer, PlayerInstance> _instances;
private AudioOutputManager()
{
_instances = new Dictionary<MediaPlayer, PlayerInstance>();
}
public void RequestAudio(AudioOutput outputComponent, MediaPlayer mediaPlayer, float[] audioData, int audioChannelCount, int channelMask, AudioOutput.AudioOutputMode audioOutputMode, bool supportPositionalAudio)
{
if (mediaPlayer == null || mediaPlayer.Control == null)
{
if (supportPositionalAudio)
{
ZeroAudio(audioData, 0);
}
return;
}
int channels = mediaPlayer.Control.GetAudioChannelCount();
if (channels <= 0)
{
if (supportPositionalAudio)
{
ZeroAudio(audioData, 0);
}
return;
}
// total samples requested should be multiple of channels
Debug.Assert(audioData.Length % audioChannelCount == 0);
// Find or create an instance
PlayerInstance instance = null;
if (!_instances.TryGetValue(mediaPlayer, out instance))
{
instance = _instances[mediaPlayer] = new PlayerInstance()
{
outputs = new HashSet<AudioOutput>(),
pcmData = null
};
}
// requests data if it hasn't been requested yet for the current cycle
if (instance.outputs.Count == 0 || instance.outputs.Contains(outputComponent) || instance.pcmData == null)
{
instance.outputs.Clear();
int actualDataRequired = (audioData.Length * channels) / audioChannelCount;
if (instance.pcmData == null || actualDataRequired != instance.pcmData.Length)
{
instance.pcmData = new float[actualDataRequired];
}
instance.isPcmDataReady = GrabAudio(mediaPlayer, instance.pcmData, channels);
instance.outputs.Add(outputComponent);
}
if (instance.isPcmDataReady)
{
// calculate how many samples and what channels are needed and then copy over the data
int samples = Math.Min(audioData.Length / audioChannelCount, instance.pcmData.Length / channels);
int storedPos = 0;
int requestedPos = 0;
// multiple mode, copies over audio from desired channels into the same channels on the audiosource
if (audioOutputMode == AudioOutput.AudioOutputMode.MultipleChannels)
{
int lesserChannels = Math.Min(channels, audioChannelCount);
if (!supportPositionalAudio)
{
for (int i = 0; i < samples; ++i)
{
for (int j = 0; j < lesserChannels; ++j)
{
if ((1 << j & channelMask) > 0)
{
audioData[requestedPos + j] = instance.pcmData[storedPos + j];
}
}
storedPos += channels;
requestedPos += audioChannelCount;
}
}
else
{
for (int i = 0; i < samples; ++i)
{
for (int j = 0; j < lesserChannels; ++j)
{
if ((1 << j & channelMask) > 0)
{
audioData[requestedPos + j] *= instance.pcmData[storedPos + j];
}
}
storedPos += channels;
requestedPos += audioChannelCount;
}
}
}
//Mono mode, copies over single channel to all output channels
else if (audioOutputMode == AudioOutput.AudioOutputMode.OneToAllChannels)
{
int desiredChannel = 0;
for (int i = 0; i < 8; ++i)
{
if ((channelMask & (1 << i)) > 0)
{
desiredChannel = i;
break;
}
}
if (desiredChannel < channels)
{
if (!supportPositionalAudio)
{
for (int i = 0; i < samples; ++i)
{
for (int j = 0; j < audioChannelCount; ++j)
{
audioData[requestedPos + j] = instance.pcmData[storedPos + desiredChannel];
}
storedPos += channels;
requestedPos += audioChannelCount;
}
}
else
{
for (int i = 0; i < samples; ++i)
{
for (int j = 0; j < audioChannelCount; ++j)
{
audioData[requestedPos + j] *= instance.pcmData[storedPos + desiredChannel];
}
storedPos += channels;
requestedPos += audioChannelCount;
}
}
}
}
// If there is left over audio
if (supportPositionalAudio && requestedPos != audioData.Length)
{
// Zero the remaining audio data otherwise there are pops
ZeroAudio(audioData, requestedPos);
}
}
else
{
if (supportPositionalAudio)
{
// Zero the remaining audio data otherwise there are pops
ZeroAudio(audioData, 0);
}
}
}
private void ZeroAudio(float[] audioData, int startPosition)
{
for (int i = startPosition; i < audioData.Length; i++)
{
audioData[i] = 0f;
}
}
private bool GrabAudio(MediaPlayer player, float[] audioData, int channelCount)
{
return (0 != player.Control.GrabAudio(audioData, audioData.Length, channelCount));
}
}
}
@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 714026a371bd2d64c86edb3dab5607d9
timeCreated: 1495699104
licenseType: Free
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,654 @@
#if UNITY_EDITOR || UNITY_STANDALONE_OSX || UNITY_STANDALONE_WIN || UNITY_IOS || UNITY_ANDROID
#define UNITY_PLATFORM_SUPPORTS_LINEAR
#endif
using System;
using System.Collections.Generic;
using UnityEngine;
//-----------------------------------------------------------------------------
// Copyright 2015-2021 RenderHeads Ltd. All rights reserved.
//-----------------------------------------------------------------------------
namespace RenderHeads.Media.AVProVideo
{
/// <summary>
/// Base class for all platform specific MediaPlayers
/// </summary>
public abstract partial class BaseMediaPlayer : IMediaPlayer, IMediaControl, IMediaInfo, IMediaCache, ITextureProducer, IMediaSubtitles, IVideoTracks, IAudioTracks, ITextTracks, IBufferedDisplay, System.IDisposable
{
public BaseMediaPlayer()
{
InitTracks();
}
public abstract string GetVersion();
public abstract string GetExpectedVersion();
/// <inheritdoc/>
public abstract bool OpenMedia(string path, long offset, string customHttpHeaders, MediaHints mediaHints, int forceFileFormat = 0, bool startWithHighestBitrate = false);
#if NETFX_CORE
/// <inheritdoc/>
public virtual bool OpenMedia(Windows.Storage.Streams.IRandomAccessStream ras, string path, long offset, string customHttpHeaders) { return false; }
#endif
/// <inheritdoc/>
public virtual bool OpenMediaFromBuffer(byte[] buffer) { return false; }
/// <inheritdoc/>
public virtual bool StartOpenMediaFromBuffer(ulong length) { return false; }
/// <inheritdoc/>
public virtual bool AddChunkToMediaBuffer(byte[] chunk, ulong offset, ulong length) { return false; }
/// <inheritdoc/>
public virtual bool EndOpenMediaFromBuffer() { return false; }
/// <inheritdoc/>
public virtual void CloseMedia()
{
#if UNITY_EDITOR
_displayRateLastRealTime = 0f;
#endif
_displayRateTimer = 0f;
_displayRateLastFrameCount = 0;
_displayRate = 0f;
_stallDetectionTimer = 0f;
_stallDetectionFrame = 0;
_lastError = ErrorCode.None;
_textTracks.Clear();
_audioTracks.Clear();
_videoTracks.Clear();
_currentTextCue = null;
_mediaHints = new MediaHints();
}
/// <inheritdoc/>
public abstract void SetLooping(bool looping);
/// <inheritdoc/>
public abstract bool IsLooping();
/// <inheritdoc/>
public abstract bool HasMetaData();
/// <inheritdoc/>
public abstract bool CanPlay();
/// <inheritdoc/>
public abstract void Play();
/// <inheritdoc/>
public abstract void Pause();
/// <inheritdoc/>
public abstract void Stop();
/// <inheritdoc/>
public virtual void Rewind() { SeekFast(0.0); }
/// <inheritdoc/>
public abstract void Seek(double time);
/// <inheritdoc/>
public abstract void SeekFast(double time);
/// <inheritdoc/>
public virtual void SeekWithTolerance(double time, double timeDeltaBefore, double timeDeltaAfter) { Seek(time); }
/// <inheritdoc/>
public abstract double GetCurrentTime();
/// <inheritdoc/>
public virtual DateTime GetProgramDateTime() { return DateTime.MinValue; }
/// <inheritdoc/>
public abstract float GetPlaybackRate();
/// <inheritdoc/>
public abstract void SetPlaybackRate(float rate);
// Basic Properties
/// <inheritdoc/>
public abstract double GetDuration();
/// <inheritdoc/>
public abstract int GetVideoWidth();
/// <inheritdoc/>
public abstract int GetVideoHeight();
/// <inheritdoc/>
public abstract float GetVideoFrameRate();
/// <inheritdoc/>
public virtual float GetVideoDisplayRate() { return _displayRate; }
/// <inheritdoc/>
public abstract bool HasAudio();
/// <inheritdoc/>
public abstract bool HasVideo();
/// <inheritdoc/>
public bool IsVideoStereo() { return GetTextureStereoPacking() != StereoPacking.None; }
// Basic State
/// <inheritdoc/>
public abstract bool IsSeeking();
/// <inheritdoc/>
public abstract bool IsPlaying();
/// <inheritdoc/>
public abstract bool IsPaused();
/// <inheritdoc/>
public abstract bool IsFinished();
/// <inheritdoc/>
public abstract bool IsBuffering();
/// <inheritdoc/>
public virtual bool WaitForNextFrame(Camera dummyCamera, int previousFrameCount) { return false; }
// Textures
/// <inheritdoc/>
public virtual int GetTextureCount() { return 1; }
/// <inheritdoc/>
public abstract Texture GetTexture(int index = 0);
/// <inheritdoc/>
public abstract int GetTextureFrameCount();
/// <inheritdoc/>
public virtual bool SupportsTextureFrameCount() { return true; }
/// <inheritdoc/>
public virtual long GetTextureTimeStamp() { return long.MinValue; }
/// <inheritdoc/>
public abstract bool RequiresVerticalFlip();
/// <inheritdoc/>
public virtual float[] GetTextureTransform() { return new float[] { 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f }; }
/// <inheritdoc/>
public virtual float GetTexturePixelAspectRatio() { return 1f; }
/// <inheritdoc/>
public virtual Matrix4x4 GetYpCbCrTransform() { return Matrix4x4.identity; }
public StereoPacking GetTextureStereoPacking()
{
StereoPacking result = InternalGetTextureStereoPacking();
if (result == StereoPacking.Unknown)
{
// If stereo is unknown, fall back to media hints or no packing
result = _mediaHints.stereoPacking;
}
return result;
}
internal abstract StereoPacking InternalGetTextureStereoPacking();
public virtual TransparencyMode GetTextureTransparency()
{
return _mediaHints.transparency;
}
public AlphaPacking GetTextureAlphaPacking()
{
if (GetTextureTransparency() == TransparencyMode.Transparent)
{
return _mediaHints.alphaPacking;
}
return AlphaPacking.None;
}
// Audio General
/// <inheritdoc/>
public abstract void MuteAudio(bool bMuted);
/// <inheritdoc/>
public abstract bool IsMuted();
/// <inheritdoc/>
public abstract void SetVolume(float volume);
/// <inheritdoc/>
public virtual void SetBalance(float balance) { }
/// <inheritdoc/>
public abstract float GetVolume();
/// <inheritdoc/>
public virtual float GetBalance() { return 0f; }
// Audio Grabbing
/// <inheritdoc/>
public virtual int GetAudioChannelCount() { return -1; }
/// <inheritdoc/>
public virtual AudioChannelMaskFlags GetAudioChannelMask() { return 0; }
/// <inheritdoc/>
public virtual int GrabAudio(float[] audioData, int audioDataFloatCount, int channelCount) { return 0; }
/// <inheritdoc/>
public virtual int GetAudioBufferedSampleCount() { return 0; }
/// <inheritdoc/>
public virtual void AudioConfigurationChanged(bool deviceChanged) { }
// 360 Audio
/// <inheritdoc/>
public virtual void SetAudioHeadRotation(Quaternion q) { }
/// <inheritdoc/>
public virtual void ResetAudioHeadRotation() { }
/// <inheritdoc/>
public virtual void SetAudioChannelMode(Audio360ChannelMode channelMode) { }
/// <inheritdoc/>
public virtual void SetAudioFocusEnabled(bool enabled) { }
/// <inheritdoc/>
public virtual void SetAudioFocusProperties(float offFocusLevel, float widthDegrees) { }
/// <inheritdoc/>
public virtual void SetAudioFocusRotation(Quaternion q) { }
/// <inheritdoc/>
public virtual void ResetAudioFocus() { }
// Streaming
/// <inheritdoc/>
public virtual long GetEstimatedTotalBandwidthUsed() { return -1; }
/// <inheritdoc/>
public virtual void SetPlayWithoutBuffering(bool playWithoutBuffering) { }
// Caching
/// <inheritdoc/>
public virtual bool IsMediaCachingSupported() { return false; }
/// <inheritdoc/>
public virtual void AddMediaToCache(string url, string headers, MediaCachingOptions options) { }
/// <inheritdoc/>
public virtual void CancelDownloadOfMediaToCache(string url) { }
/// <inheritdoc/>
public virtual void PauseDownloadOfMediaToCache(string url) { }
/// <inheritdoc/>
public virtual void ResumeDownloadOfMediaToCache(string url) { }
/// <inheritdoc/>
public virtual void RemoveMediaFromCache(string url) { }
/// <inheritdoc/>
public virtual CachedMediaStatus GetCachedMediaStatus(string url, ref float progress) { return CachedMediaStatus.NotCached; }
// /// <inheritdoc/>
// public virtual bool IsMediaCached() { return false; }
// External playback
/// <inheritdoc/>
public virtual bool IsExternalPlaybackSupported() { return false; }
/// <inheritdoc/>
public virtual bool IsExternalPlaybackActive() { return false; }
/// <inheritdoc/>
public virtual void SetAllowsExternalPlayback(bool enable) { }
/// <inheritdoc/>
public virtual void SetExternalPlaybackVideoGravity(ExternalPlaybackVideoGravity gravity) { }
// Authentication
//public virtual void SetKeyServerURL(string url) { }
/// <inheritdoc/>
public virtual void SetKeyServerAuthToken(string token) { }
/// <inheritdoc/>
public virtual void SetOverrideDecryptionKey(byte[] key) { }
// General
/// <inheritdoc/>
public abstract void Update();
/// <inheritdoc/>
public /*abstract*/virtual void BeginRender() { }
/// <inheritdoc/>
public abstract void Render();
/// <inheritdoc/>
public abstract void Dispose();
// Internal method
public virtual bool GetDecoderPerformance(ref int activeDecodeThreadCount, ref int decodedFrameCount, ref int droppedFrameCount) { return false; }
#if false
public void Update()
{
Native.Update(_instance);
if (UpdateTracks())
{
}
if (UpdateTextCue())
{
}
}
#endif
public virtual void EndUpdate() { }
public virtual IntPtr GetNativePlayerHandle() { return IntPtr.Zero; }
public ErrorCode GetLastError()
{
ErrorCode errorCode = _lastError;
_lastError = ErrorCode.None;
return errorCode;
}
/// <inheritdoc/>
public virtual long GetLastExtendedErrorCode()
{
return 0;
}
public string GetPlayerDescription()
{
return _playerDescription;
}
/// <inheritdoc/>
public virtual bool PlayerSupportsLinearColorSpace()
{
#if UNITY_PLATFORM_SUPPORTS_LINEAR
return true;
#else
return false;
#endif
}
protected string _playerDescription = string.Empty;
protected ErrorCode _lastError = ErrorCode.None;
protected FilterMode _defaultTextureFilterMode = FilterMode.Bilinear;
protected TextureWrapMode _defaultTextureWrapMode = TextureWrapMode.Clamp;
protected int _defaultTextureAnisoLevel = 1;
protected MediaHints _mediaHints;
protected TimeRanges _seekableTimes = new TimeRanges();
protected TimeRanges _bufferedTimes = new TimeRanges();
public TimeRanges GetSeekableTimes() { return _seekableTimes; }
public TimeRanges GetBufferedTimes() { return _bufferedTimes; }
public void GetTextureProperties(out FilterMode filterMode, out TextureWrapMode wrapMode, out int anisoLevel)
{
filterMode = _defaultTextureFilterMode;
wrapMode = _defaultTextureWrapMode;
anisoLevel = _defaultTextureAnisoLevel;
}
public void SetTextureProperties(FilterMode filterMode = FilterMode.Bilinear, TextureWrapMode wrapMode = TextureWrapMode.Clamp, int anisoLevel = 0)
{
_defaultTextureFilterMode = filterMode;
_defaultTextureWrapMode = wrapMode;
_defaultTextureAnisoLevel = anisoLevel;
for (int i = 0; i < GetTextureCount(); ++i)
{
ApplyTextureProperties(GetTexture(i));
}
}
protected virtual void ApplyTextureProperties(Texture texture)
{
if (texture != null)
{
texture.filterMode = _defaultTextureFilterMode;
texture.wrapMode = _defaultTextureWrapMode;
texture.anisoLevel = _defaultTextureAnisoLevel;
}
}
#region Video Display Rate
#if UNITY_EDITOR
private float _displayRateLastRealTime = 0f;
#endif
private float _displayRateTimer;
private int _displayRateLastFrameCount;
private float _displayRate = 1f;
protected void UpdateDisplayFrameRate()
{
const float IntervalSeconds = 0.5f;
if (_displayRateTimer >= IntervalSeconds)
{
int frameCount = GetTextureFrameCount();
int frameDelta = (frameCount - _displayRateLastFrameCount);
_displayRate = (float)frameDelta / _displayRateTimer;
_displayRateTimer -= IntervalSeconds;
if (_displayRateTimer >= IntervalSeconds) _displayRateTimer -= IntervalSeconds;
if (_displayRateTimer >= IntervalSeconds) _displayRateTimer = 0f;
_displayRateLastFrameCount = frameCount;
}
float deltaTime = Time.deltaTime;
#if UNITY_EDITOR
if (!Application.isPlaying)
{
// When not playing Time.deltaTime isn't valid so we have to derive it
deltaTime = (Time.realtimeSinceStartup - _displayRateLastRealTime);
_displayRateLastRealTime = Time.realtimeSinceStartup;
}
#endif
_displayRateTimer += deltaTime;
}
#endregion // Video Display Rate
#region Stall Detection
protected bool IsExpectingNewVideoFrame()
{
if (HasVideo())
{
// If we're playing then we expect a new frame
if (!IsFinished() && (!IsPaused() && IsPlaying() && GetPlaybackRate() != 0.0f))
{
// Check that the video is not a single frame and therefore there is no other frame to display
bool isSingleFrame = (GetTextureFrameCount() > 0 && GetDurationFrames() == 1);
if (!isSingleFrame)
{
// NOTE: if a new frame isn't available then we could either be seeking or stalled
return true;
}
}
}
return false;
}
/// <inheritdoc/>
public virtual bool IsPlaybackStalled()
{
const float StallDetectionDuration = 0.5f;
// Manually detect stalled video if the platform doesn't have native support to detect it
if (SupportsTextureFrameCount() && IsExpectingNewVideoFrame())
{
// Detect a new video frame
int frameCount = GetTextureFrameCount();
if (frameCount != _stallDetectionFrame)
{
_stallDetectionTimer = 0f;
_stallDetectionFrame = frameCount;
}
else
{
// Update the detection timer, but never more than once a Unity frame
if (_stallDetectionGuard != Time.frameCount)
{
_stallDetectionTimer += Time.deltaTime;
}
}
_stallDetectionGuard = Time.frameCount;
float thresholdDuration = StallDetectionDuration;
// Scale by the playback rate, but should be at least StallDetectionDuration
thresholdDuration = Mathf.Max(thresholdDuration / Mathf.Abs(GetPlaybackRate()), StallDetectionDuration);
// If a valid FPS is available then make sure the thresholdDuration
// is at least double that. This is mainly for very low FPS
// content (eg 1 or 2 FPS)
float fps = GetVideoFrameRate();
if (fps > 0f && !float.IsNaN(fps))
{
thresholdDuration = Mathf.Max(thresholdDuration, 2f / fps);
}
return (_stallDetectionTimer > thresholdDuration);
}
else
{
_stallDetectionTimer = 0f;
}
return false;
}
private float _stallDetectionTimer;
private int _stallDetectionFrame;
private int _stallDetectionGuard;
#endregion // Stall Detection
protected List<Subtitle> _subtitles;
protected Subtitle _currentSubtitle;
/// <inheritdoc/>
public bool LoadSubtitlesSRT(string data)
{
if (string.IsNullOrEmpty(data))
{
// Disable subtitles
_subtitles = null;
_currentSubtitle = null;
}
else
{
_subtitles = SubtitleUtils.ParseSubtitlesSRT(data);
_currentSubtitle = null;
}
return (_subtitles != null);
}
/// <inheritdoc/>
public virtual void UpdateSubtitles()
{
if (_subtitles != null)
{
double time = GetCurrentTime();
// TODO: implement a more efficient subtitle index searcher
int searchIndex = 0;
if (_currentSubtitle != null)
{
if (!_currentSubtitle.IsTime(time))
{
if (time > _currentSubtitle.timeEnd)
{
searchIndex = _currentSubtitle.index + 1;
}
_currentSubtitle = null;
}
}
if (_currentSubtitle == null)
{
for (int i = searchIndex; i < _subtitles.Count; i++)
{
if (_subtitles[i].IsTime(time))
{
_currentSubtitle = _subtitles[i];
break;
}
}
}
}
}
/// <inheritdoc/>
public virtual int GetSubtitleIndex()
{
int result = -1;
if (_currentSubtitle != null)
{
result = _currentSubtitle.index;
}
return result;
}
/// <inheritdoc/>
public virtual string GetSubtitleText()
{
string result = string.Empty;
if (_currentSubtitle != null)
{
result = _currentSubtitle.text;
}
else if (_currentTextCue != null)
{
result = _currentTextCue.Text;
}
return result;
}
public virtual void OnEnable()
{
}
/// <inheritdoc/>
public int GetCurrentTimeFrames(float overrideFrameRate = 0f)
{
int result = 0;
float frameRate = (overrideFrameRate > 0f) ? overrideFrameRate : GetVideoFrameRate();
if (frameRate > 0f)
{
result = Helper.ConvertTimeSecondsToFrame(GetCurrentTime(), frameRate);
result = Mathf.Min(result, GetMaxFrameNumber(overrideFrameRate));
}
return result;
}
/// <inheritdoc/>
public int GetDurationFrames(float overrideFrameRate = 0f)
{
int result = 0;
float frameRate = (overrideFrameRate > 0f) ? overrideFrameRate : GetVideoFrameRate();
if (frameRate > 0f)
{
result = Helper.ConvertTimeSecondsToFrame(GetDuration(), frameRate);
}
return result;
}
/// <inheritdoc/>
public int GetMaxFrameNumber(float overrideFrameRate = 0f)
{
int result = GetDurationFrames(overrideFrameRate);
result = Mathf.Max(0, result - 1);
return result;
}
/// <inheritdoc/>
public void SeekToFrameRelative(int frameOffset, float overrideFrameRate = 0f)
{
float frameRate = (overrideFrameRate > 0f)?overrideFrameRate:GetVideoFrameRate();
if (frameRate > 0f)
{
int frame = Helper.ConvertTimeSecondsToFrame(GetCurrentTime(), frameRate);
frame += frameOffset;
frame = Mathf.Clamp(frame, 0, GetMaxFrameNumber(frameRate));
double time = Helper.ConvertFrameToTimeSeconds(frame, frameRate);
Seek(time);
}
}
/// <inheritdoc/>
public void SeekToFrame(int frame, float overrideFrameRate = 0f)
{
float frameRate = (overrideFrameRate > 0f)?overrideFrameRate:GetVideoFrameRate();
if (frameRate > 0f)
{
frame = Mathf.Clamp(frame, 0, GetMaxFrameNumber(frameRate));
double time = Helper.ConvertFrameToTimeSeconds(frame, frameRate);
Seek(time);
}
}
#region IBufferedDisplay Implementation
private int _unityFrameCountBufferedDisplayGuard = -1;
/// <inheritdoc/>
public long UpdateBufferedDisplay()
{
// Guard to make sure we're only updating the buffered frame once per Unity frame
if (Time.frameCount == _unityFrameCountBufferedDisplayGuard) return GetTextureTimeStamp();
_unityFrameCountBufferedDisplayGuard = Time.frameCount;
return InternalUpdateBufferedDisplay();
}
internal virtual long InternalUpdateBufferedDisplay() { return 0; }
/// <inheritdoc/>
public virtual BufferedFramesState GetBufferedFramesState()
{
return new BufferedFramesState();
}
/// <inheritdoc/>
public virtual void SetSlaves(IBufferedDisplay[] slaves) { }
/// <inheritdoc/>
public virtual void SetBufferedDisplayMode(BufferedFrameSelectionMode mode, IBufferedDisplay master = null) { }
/// <inheritdoc/>
public virtual void SetBufferedDisplayOptions(bool pauseOnPrerollComplete) { }
#endregion // IBufferedDisplay Implementation
protected PlaybackQualityStats _playbackQualityStats = new PlaybackQualityStats();
public PlaybackQualityStats GetPlaybackQualityStats()
{
return _playbackQualityStats;
}
}
}
@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 4f59504ca098e7d41b036917f4764ee0
timeCreated: 1447782861
licenseType: Free
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,106 @@
using UnityEngine.Events;
using System.Collections.Generic;
//-----------------------------------------------------------------------------
// Copyright 2015-2021 RenderHeads Ltd. All rights reserved.
//-----------------------------------------------------------------------------
namespace RenderHeads.Media.AVProVideo
{
[System.Serializable]
public class MediaPlayerLoadEvent : UnityEvent<string> {}
[System.Serializable]
public class MediaPlayerEvent : UnityEvent<MediaPlayer, MediaPlayerEvent.EventType, ErrorCode>
{
public enum EventType
{
MetaDataReady, // Triggered when meta data(width, duration etc) is available
ReadyToPlay, // Triggered when the video is loaded and ready to play
Started, // Triggered when the playback starts
FirstFrameReady, // Triggered when the first frame has been rendered
FinishedPlaying, // Triggered when a non-looping video has finished playing
Closing, // Triggered when the media is closed
Error, // Triggered when an error occurs
SubtitleChange, // Triggered when the subtitles change
Stalled, // Triggered when media is stalled (eg. when lost connection to media stream)
Unstalled, // Triggered when media is resumed form a stalled state (eg. when lost connection is re-established)
ResolutionChanged, // Triggered when the resolution of the video has changed (including the load) Useful for adaptive streams
StartedSeeking, // Triggered when seeking begins
FinishedSeeking, // Triggered when seeking has finished
StartedBuffering, // Triggered when buffering begins
FinishedBuffering, // Triggered when buffering has finished
PropertiesChanged, // Triggered when any properties (eg stereo packing are changed) - this has to be triggered manually
PlaylistItemChanged,// Triggered when the new item is played in the playlist
PlaylistFinished, // Triggered when the playlist reaches the end
TextTracksChanged, // Triggered when the text tracks are added or removed
TextCueChanged = SubtitleChange, // Triggered when the text to display changes
// TODO:
//StartLoop, // Triggered when the video starts and is in loop mode
//EndLoop, // Triggered when the video ends and is in loop mode
//NewFrame // Trigger when a new video frame is available
}
private List<UnityAction<MediaPlayer, MediaPlayerEvent.EventType, ErrorCode>> _listeners = new List<UnityAction<MediaPlayer, EventType, ErrorCode>>(4);
public bool HasListeners()
{
return (_listeners.Count > 0) || (GetPersistentEventCount() > 0);
}
new public void AddListener(UnityAction<MediaPlayer, MediaPlayerEvent.EventType, ErrorCode> call)
{
if (!_listeners.Contains(call))
{
_listeners.Add(call);
base.AddListener(call);
}
}
new public void RemoveListener(UnityAction<MediaPlayer, MediaPlayerEvent.EventType, ErrorCode> call)
{
int index = _listeners.IndexOf(call);
if (index >= 0)
{
_listeners.RemoveAt(index);
base.RemoveListener(call);
}
}
new public void RemoveAllListeners()
{
_listeners.Clear();
base.RemoveAllListeners();
}
}
#if false
public interface IMediaEvents
{
void AddEventListener(UnityAction<MediaPlayer, MediaPlayerEvent.EventType, ErrorCode> call);
void RemoveListener(UnityAction<MediaPlayer, MediaPlayerEvent.EventType, ErrorCode> call);
void RemoveAllEventListeners();
}
public partial class BaseMediaPlayer
{
void AddEventListener(UnityAction<MediaPlayer, MediaPlayerEvent.EventType, ErrorCode> call)
{
}
void RemoveListener(UnityAction<MediaPlayer, MediaPlayerEvent.EventType, ErrorCode> call)
{
}
void RemoveAllEventListeners()
{
}
private MediaPlayerEvent _eventHandler;
}
#endif
}
@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 16a5efbe992a09144ac89dde2b3e0898
timeCreated: 1438695622
licenseType: Pro
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,519 @@
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
//-----------------------------------------------------------------------------
// Copyright 2015-2022 RenderHeads Ltd. All rights reserved.
//-----------------------------------------------------------------------------
namespace RenderHeads.Media.AVProVideo
{
public static class Helper
{
public const string AVProVideoVersion = "2.8.5";
public sealed class ExpectedPluginVersion
{
public const string Windows = "2.8.5";
public const string WinRT = "2.8.5";
public const string Android = "2.8.5";
public const string Apple = "2.8.5";
}
public const string UnityBaseTextureName = "_MainTex";
public const string UnityBaseTextureName_URP = "_BaseMap";
public const string UnityBaseTextureName_HDRP = "_BaseColorMap";
public static string GetPath(MediaPathType location)
{
string result = string.Empty;
switch (location)
{
case MediaPathType.AbsolutePathOrURL:
break;
case MediaPathType.RelativeToDataFolder:
result = Application.dataPath;
break;
case MediaPathType.RelativeToPersistentDataFolder:
result = Application.persistentDataPath;
break;
case MediaPathType.RelativeToProjectFolder:
#if !UNITY_WINRT_8_1
string path = "..";
#if UNITY_STANDALONE_OSX && !UNITY_EDITOR_OSX
path += "/..";
#endif
result = System.IO.Path.GetFullPath(System.IO.Path.Combine(Application.dataPath, path));
result = result.Replace('\\', '/');
#endif
break;
case MediaPathType.RelativeToStreamingAssetsFolder:
result = Application.streamingAssetsPath;
break;
}
return result;
}
public static string GetFilePath(string path, MediaPathType location)
{
string result = string.Empty;
if (!string.IsNullOrEmpty(path))
{
switch (location)
{
case MediaPathType.AbsolutePathOrURL:
result = path;
break;
case MediaPathType.RelativeToDataFolder:
case MediaPathType.RelativeToPersistentDataFolder:
case MediaPathType.RelativeToProjectFolder:
case MediaPathType.RelativeToStreamingAssetsFolder:
result = System.IO.Path.Combine(GetPath(location), path);
break;
}
}
return result;
}
public static string GetFriendlyResolutionName(int width, int height, float fps)
{
// List of common 16:9 resolutions
int[] areas = { 0, 7680 * 4320, 3840 * 2160, 2560 * 1440, 1920 * 1080, 1280 * 720, 853 * 480, 640 * 360, 426 * 240, 256 * 144 };
string[] names = { "Unknown", "8K", "4K", "1440p", "1080p", "720p", "480p", "360p", "240p", "144p" };
Debug.Assert(areas.Length == names.Length);
// Find the closest resolution
int closestAreaIndex = 0;
int area = width * height;
int minDelta = int.MaxValue;
for (int i = 0; i < areas.Length; i++)
{
int d = Mathf.Abs(areas[i] - area);
// TODO: add a maximum threshold to ignore differences that are too high
if (d < minDelta)
{
closestAreaIndex = i;
minDelta = d;
// If the exact mode is found, early out
if (d == 0)
{
break;
}
}
}
string result = names[closestAreaIndex];
// Append frame rate if valid
if (fps > 0f && !float.IsNaN(fps))
{
result += fps.ToString("0.##");
}
return result;
}
public static string GetErrorMessage(ErrorCode code)
{
string result = string.Empty;
switch (code)
{
case ErrorCode.None:
result = "No Error";
break;
case ErrorCode.LoadFailed:
result = "Loading failed. File not found, codec not supported, video resolution too high or insufficient system resources.";
#if UNITY_STANDALONE_WIN || UNITY_EDITOR_WIN
// Add extra information for older Windows versions that don't have support for modern codecs
if (SystemInfo.operatingSystem.StartsWith("Windows XP") ||
SystemInfo.operatingSystem.StartsWith("Windows Vista"))
{
result += " NOTE: Windows XP and Vista don't have native support for H.264 codec. Consider using an older codec such as DivX or installing 3rd party codecs such as LAV Filters.";
}
#endif
break;
case ErrorCode.DecodeFailed:
result = "Decode failed. Possible codec not supported, video resolution/bit-depth too high, or insufficient system resources.";
#if UNITY_ANDROID
result += " On Android this is generally due to the hardware not having enough resources to decode the video. Most Android devices can only handle a maximum of one 4K video at once.";
#endif
break;
}
return result;
}
public static string GetPlatformName(Platform platform)
{
string result = "Unknown";
switch (platform)
{
case Platform.WindowsUWP:
result = "Windows UWP";
break;
case Platform.MacOSX:
result = "macOS";
break;
default:
result = platform.ToString();
break;
}
return result;
}
public static string[] GetPlatformNames()
{
return new string[] {
GetPlatformName(Platform.Windows),
GetPlatformName(Platform.MacOSX),
GetPlatformName(Platform.iOS),
GetPlatformName(Platform.tvOS),
GetPlatformName(Platform.Android),
GetPlatformName(Platform.WindowsUWP),
GetPlatformName(Platform.WebGL),
};
}
#if AVPROVIDEO_DISABLE_LOGGING
[System.Diagnostics.Conditional("ALWAYS_FALSE")]
#endif
public static void LogInfo(string message, Object context = null)
{
if (context == null)
{
Debug.Log("[AVProVideo] " + message);
}
else
{
Debug.Log("[AVProVideo] " + message, context);
}
}
public static int GetUnityAudioSampleRate()
{
// For standalone builds (not in the editor):
// In Unity 4.6, 5.0, 5.1 when audio is disabled there is no indication from the API.
// But in 5.2.0 and above, it logs an error when trying to call
// AudioSettings.GetDSPBufferSize() or AudioSettings.outputSampleRate
// So to prevent the error, check if AudioSettings.GetConfiguration().sampleRate == 0
return (AudioSettings.GetConfiguration().sampleRate == 0) ? 0 : AudioSettings.outputSampleRate;
}
public static int GetUnityAudioSpeakerCount()
{
switch (AudioSettings.GetConfiguration().speakerMode)
{
case AudioSpeakerMode.Mono: return 1;
case AudioSpeakerMode.Stereo: return 2;
case AudioSpeakerMode.Quad: return 4;
case AudioSpeakerMode.Surround: return 5;
case AudioSpeakerMode.Mode5point1: return 6;
case AudioSpeakerMode.Mode7point1: return 8;
case AudioSpeakerMode.Prologic: return 2;
}
return 0;
}
// Returns a valid range to use for a timeline display
// Either it will return the range 0..duration, or
// for live streams it will return first seekable..last seekable time
public static TimeRange GetTimelineRange(double duration, TimeRanges seekable)
{
TimeRange result = new TimeRange();
if (duration >= 0.0 && duration < 2e10)
{
// Duration is valid
result.startTime = 0f;
result.duration = duration;
}
else
{
// Duration is invalid, so it could be a live stream, so derive from seekable range
result.startTime = seekable.MinTime;
result.duration = seekable.Duration;
}
return result;
}
public const double SecondsToHNS = 10000000.0;
public const double MilliSecondsToHNS = 10000.0;
public static string GetTimeString(double timeSeconds, bool showMilliseconds = false)
{
float totalSeconds = (float)timeSeconds;
int hours = Mathf.FloorToInt(totalSeconds / (60f * 60f));
float usedSeconds = hours * 60f * 60f;
int minutes = Mathf.FloorToInt((totalSeconds - usedSeconds) / 60f);
usedSeconds += minutes * 60f;
int seconds = Mathf.FloorToInt(totalSeconds - usedSeconds);
string result;
if (hours <= 0)
{
if (showMilliseconds)
{
int milliSeconds = (int)((totalSeconds - Mathf.Floor(totalSeconds)) * 1000f);
result = string.Format("{0:00}:{1:00}:{2:000}", minutes, seconds, milliSeconds);
}
else
{
result = string.Format("{0:00}:{1:00}", minutes, seconds);
}
}
else
{
if (showMilliseconds)
{
int milliSeconds = (int)((totalSeconds - Mathf.Floor(totalSeconds)) * 1000f);
result = string.Format("{2}:{0:00}:{1:00}:{3:000}", minutes, seconds, hours, milliSeconds);
}
else
{
result = string.Format("{2}:{0:00}:{1:00}", minutes, seconds, hours);
}
}
return result;
}
/// <summary>
/// Convert texture transform matrix to an enum of orientation types
/// </summary>
public static Orientation GetOrientation(float[] t)
{
Orientation result = Orientation.Landscape;
if (t != null)
{
// TODO: check that the Portrait and PortraitFlipped are the right way around
if (t[0] == 0f && t[1]== 1f && t[2] == -1f && t[3] == 0f)
{
result = Orientation.Portrait;
} else
if (t[0] == 0f && t[1] == -1f && t[2] == 1f && t[3] == 0f)
{
result = Orientation.PortraitFlipped;
} else
if (t[0]== 1f && t[1] == 0f && t[2] == 0f && t[3] == 1f)
{
result = Orientation.Landscape;
} else
if (t[0] == -1f && t[1] == 0f && t[2] == 0f && t[3] == -1f)
{
result = Orientation.LandscapeFlipped;
}
else
if (t[0] == 0f && t[1] == 1f && t[2] == 1f && t[3] == 0f)
{
result = Orientation.PortraitHorizontalMirror;
}
}
return result;
}
private static Matrix4x4 PortraitMatrix = Matrix4x4.TRS(new Vector3(0f, 1f, 0f), Quaternion.Euler(0f, 0f, -90f), Vector3.one);
private static Matrix4x4 PortraitFlippedMatrix = Matrix4x4.TRS(new Vector3(1f, 0f, 0f), Quaternion.Euler(0f, 0f, 90f), Vector3.one);
private static Matrix4x4 LandscapeFlippedMatrix = Matrix4x4.TRS(new Vector3(0f, 1f, 0f), Quaternion.Euler(0f, 0f, -90f), Vector3.one);
public static Matrix4x4 GetMatrixForOrientation(Orientation ori)
{
Matrix4x4 result;
switch (ori)
{
case Orientation.Landscape:
result = Matrix4x4.identity;
break;
case Orientation.LandscapeFlipped:
result = LandscapeFlippedMatrix;
break;
case Orientation.Portrait:
result = PortraitMatrix;
break;
case Orientation.PortraitFlipped:
result = PortraitFlippedMatrix;
break;
case Orientation.PortraitHorizontalMirror:
result = new Matrix4x4();
result.SetColumn(0, new Vector4(0f, 1f, 0f, 0f));
result.SetColumn(1, new Vector4(1f, 0f, 0f, 0f));
result.SetColumn(2, new Vector4(0f, 0f, 1f, 0f));
result.SetColumn(3, new Vector4(0f, 0f, 0f, 1f));
break;
default:
throw new System.Exception("Unknown Orientation type");
}
return result;
}
public static int ConvertTimeSecondsToFrame(double seconds, float frameRate)
{
// NOTE: Generally you should use RountToInt when converting from time to frame number
// but because we're adding a half frame offset (which seems to be the safer thing to do) we need to FloorToInt
seconds = System.Math.Max(0.0, seconds);
frameRate = Mathf.Max(0f, frameRate);
return (int)System.Math.Floor(frameRate * seconds);
}
public static double ConvertFrameToTimeSeconds(int frame, float frameRate)
{
frame = Mathf.Max(0, frame);
frameRate = Mathf.Max(0f, frameRate);
double frameDurationSeconds = 1.0 / frameRate;
return ((double)frame * frameDurationSeconds) + (frameDurationSeconds * 0.5); // Add half a frame we that the time lands in the middle of the frame range and not at the edges
}
public static double FindNextKeyFrameTimeSeconds(double seconds, float frameRate, int keyFrameInterval)
{
seconds = System.Math.Max(0.0, seconds);
frameRate = Mathf.Max(0f, frameRate);
keyFrameInterval = Mathf.Max(0, keyFrameInterval);
int currentFrame = Helper.ConvertTimeSecondsToFrame(seconds, frameRate);
// TODO: allow specifying a minimum number of frames so that if currentFrame is too close to nextKeyFrame, it will calculate the next-next keyframe
int nextKeyFrame = keyFrameInterval * Mathf.CeilToInt((float)(currentFrame + 1) / (float)keyFrameInterval);
return Helper.ConvertFrameToTimeSeconds(nextKeyFrame, frameRate);
}
public static System.DateTime ConvertSecondsSince1970ToDateTime(double secondsSince1970)
{
System.TimeSpan time = System.TimeSpan.FromSeconds(secondsSince1970);
return new System.DateTime(1970, 1, 1).Add(time);
}
#if (UNITY_EDITOR_WIN || (!UNITY_EDITOR && UNITY_STANDALONE_WIN))
[System.Runtime.InteropServices.DllImport("kernel32.dll", CharSet = System.Runtime.InteropServices.CharSet.Unicode, EntryPoint = "GetShortPathNameW", SetLastError=true)]
private static extern int GetShortPathName([System.Runtime.InteropServices.MarshalAs(System.Runtime.InteropServices.UnmanagedType.LPWStr)] string pathName,
[System.Runtime.InteropServices.MarshalAs(System.Runtime.InteropServices.UnmanagedType.LPWStr)] System.Text.StringBuilder shortName,
int cbShortName);
// Handle very long file paths by converting to DOS 8.3 format
internal static string ConvertLongPathToShortDOS83Path(string path)
{
const string pathToken = @"\\?\";
string result = pathToken + path.Replace("/","\\");
int length = GetShortPathName(result, null, 0);
if (length > 0)
{
System.Text.StringBuilder sb = new System.Text.StringBuilder(length);
if (0 != GetShortPathName(result, sb, length))
{
result = sb.ToString().Replace(pathToken, "");
Debug.LogWarning("[AVProVideo] Long path detected. Changing to DOS 8.3 format");
}
}
return result;
}
#endif
// Converts a non-readable texture to a readable Texture2D.
// "targetTexture" can be null or you can pass in an existing texture.
// Remember to Destroy() the returned texture after finished with it
public static Texture2D GetReadableTexture(Texture inputTexture, bool requiresVerticalFlip, Orientation ori, Texture2D targetTexture = null)
{
Texture2D resultTexture = targetTexture;
RenderTexture prevRT = RenderTexture.active;
int textureWidth = inputTexture.width;
int textureHeight = inputTexture.height;
#if UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX || UNITY_IPHONE || UNITY_IOS || UNITY_TVOS
if (ori == Orientation.Portrait || ori == Orientation.PortraitFlipped)
{
textureWidth = inputTexture.height;
textureHeight = inputTexture.width;
}
#endif
// Blit the texture to a temporary RenderTexture
// This handles any format conversion that is required and allows us to use ReadPixels to copy texture from RT to readable texture
RenderTexture tempRT = RenderTexture.GetTemporary(textureWidth, textureHeight, 0, RenderTextureFormat.ARGB32);
if (ori == Orientation.Landscape)
{
if (!requiresVerticalFlip)
{
Graphics.Blit(inputTexture, tempRT);
}
else
{
// The above Blit can't flip unless using a material, so we use Graphics.DrawTexture instead
GL.PushMatrix();
RenderTexture.active = tempRT;
GL.LoadPixelMatrix(0f, tempRT.width, 0f, tempRT.height);
Rect sourceRect = new Rect(0f, 0f, 1f, 1f);
// NOTE: not sure why we need to set y to -1, without this there is a 1px gap at the bottom
Rect destRect = new Rect(0f, -1f, tempRT.width, tempRT.height);
Graphics.DrawTexture(destRect, inputTexture, sourceRect, 0, 0, 0, 0);
GL.PopMatrix();
GL.InvalidateState();
}
}
#if UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX || UNITY_IPHONE || UNITY_IOS || UNITY_TVOS
else
{
Matrix4x4 m = Matrix4x4.identity;
switch (ori)
{
case Orientation.Portrait:
m = Matrix4x4.TRS(new Vector3(0f, inputTexture.width, 0f), Quaternion.Euler(0f, 0f, -90f), Vector3.one);
break;
case Orientation.PortraitFlipped:
m = Matrix4x4.TRS(new Vector3(inputTexture.height, 0f, 0f), Quaternion.Euler(0f, 0f, 90f), Vector3.one);
break;
case Orientation.LandscapeFlipped:
m = Matrix4x4.TRS(new Vector3(inputTexture.width, inputTexture.height, 0f), Quaternion.identity, new Vector3(-1f, -1f, 1f));
break;
}
// The above Blit can't flip unless using a material, so we use Graphics.DrawTexture instead
GL.InvalidateState();
RenderTexture.active = tempRT;
GL.Clear(false, true, Color.black);
GL.PushMatrix();
GL.LoadPixelMatrix(0f, tempRT.width, 0f, tempRT.height);
Rect sourceRect = new Rect(0f, 0f, 1f, 1f);
// NOTE: not sure why we need to set y to -1, without this there is a 1px gap at the bottom
Rect destRect = new Rect(0f, -1f, inputTexture.width, inputTexture.height);
GL.MultMatrix(m);
Graphics.DrawTexture(destRect, inputTexture, sourceRect, 0, 0, 0, 0);
GL.PopMatrix();
GL.InvalidateState();
}
#endif
if (resultTexture == null)
{
resultTexture = new Texture2D(textureWidth, textureHeight, TextureFormat.ARGB32, false);
}
RenderTexture.active = tempRT;
resultTexture.ReadPixels(new Rect(0f, 0f, textureWidth, textureHeight), 0, 0, false);
resultTexture.Apply(false, false);
RenderTexture.ReleaseTemporary(tempRT);
RenderTexture.active = prevRT;
return resultTexture;
}
// Converts a non-readable texture to a readable Texture2D.
// "targetTexture" can be null or you can pass in an existing texture.
// Remember to Destroy() the returned texture after finished with it
public static Texture2D GetReadableTexture(RenderTexture inputTexture, Texture2D targetTexture = null)
{
if (targetTexture == null)
{
targetTexture = new Texture2D(inputTexture.width, inputTexture.height, TextureFormat.ARGB32, false);
}
RenderTexture prevRT = RenderTexture.active;
RenderTexture.active = inputTexture;
targetTexture.ReadPixels(new Rect(0f, 0f, inputTexture.width, inputTexture.height), 0, 0, false);
targetTexture.Apply(false, false);
RenderTexture.active = prevRT;
return targetTexture;
}
}
}
@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 79e446998599e1647804321292c80f42
timeCreated: 1600887818
licenseType: Pro
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 00407cbf3ca503142903894431082ac6
timeCreated: 1438695622
licenseType: Pro
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,225 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
//-----------------------------------------------------------------------------
// Copyright 2015-2021 RenderHeads Ltd. All rights reserved.
//-----------------------------------------------------------------------------
namespace RenderHeads.Media.AVProVideo
{
/// <summary>
/// Attempts to give insight into video playback presentation smoothness quality
/// Keeps track of skipped and duplicated frames and warns about suboptimal setup
/// such as no vsync enabled or video frame rate not being a multiple of the display frame rate
/// </summary>
public class PlaybackQualityStats
{
public int SkippedFrames { get; private set; }
public int DuplicateFrames { get; private set; }
public int UnityDroppedFrames { get; private set; }
public float PerfectFramesT { get; private set; }
public string VSyncStatus { get; private set; }
private int PerfectFrames { get; set; }
private int TotalFrames { get; set; }
public bool LogIssues { get; set; }
private int _sameFrameCount;
private long _lastTimeStamp;
private BaseMediaPlayer _player;
public void Reset()
{
_sameFrameCount = 0;
if (_player != null)
{
_lastTimeStamp = _player.GetTextureTimeStamp();
}
SkippedFrames = 0;
DuplicateFrames = 0;
UnityDroppedFrames = 0;
TotalFrames = 0;
PerfectFrames = 0;
PerfectFramesT = 0f;
}
internal void Start(BaseMediaPlayer player)
{
_player = player;
Reset();
bool vsyncEnabled = true;
if (QualitySettings.vSyncCount == 0)
{
vsyncEnabled = false;
if (LogIssues)
{
Debug.LogWarning("[AVProVideo][Quality] VSync is currently disabled in Quality Settings");
}
}
if (!IsGameViewVSyncEnabled())
{
vsyncEnabled = false;
if (LogIssues)
{
Debug.LogWarning("[AVProVideo][Quality] VSync is currently disabled in the Game View");
}
}
float frameRate = _player.GetVideoFrameRate();
float frameMs = (1000f / frameRate);
if (LogIssues)
{
Debug.Log(string.Format("[AVProVideo][Quality] Video: {0}fps {1}ms", frameRate, frameMs));
}
if (vsyncEnabled)
{
#if UNITY_2022_2_OR_NEWER
float refreshRate = (float)( Screen.currentResolution.refreshRateRatio.value );
#else
float refreshRate = (float)( Screen.currentResolution.refreshRate );
#endif
float vsyncRate = refreshRate / QualitySettings.vSyncCount;
float vsyncMs = (1000f / vsyncRate);
if (LogIssues)
{
Debug.Log(string.Format("[AVProVideo][Quality] VSync: {0}fps {1}ms", vsyncRate, vsyncMs));
}
float framesPerVSync = frameMs / vsyncMs;
float fractionalframesPerVsync = framesPerVSync - Mathf.FloorToInt(framesPerVSync);
if (fractionalframesPerVsync > 0.0001f && LogIssues)
{
Debug.LogWarning("[AVProVideo][Quality] Video is not a multiple of VSync so playback cannot be perfect");
}
VSyncStatus = "VSync " + framesPerVSync;
}
else
{
if (LogIssues)
{
Debug.LogWarning("[AVProVideo][Quality] Running without VSync enabled");
}
VSyncStatus = "No VSync";
}
}
internal void Update()
{
if (_player == null) return;
// Don't analyse stats unless real playback is happening
if (_player.IsPaused() || _player.IsSeeking() || _player.IsFinished()) return;
long timeStamp = _player.GetTextureTimeStamp();
long frameDuration = (long)(Helper.SecondsToHNS / _player.GetVideoFrameRate());
bool isPerfectFrame = true;
// Check for skipped frames
long d = (timeStamp - _lastTimeStamp);
if (d > 0)
{
const long threshold = 10000;
d -= frameDuration;
if (d > threshold)
{
int skippedFrames = Mathf.FloorToInt((float)d / (float)frameDuration);
if (LogIssues)
{
Debug.LogWarning("[AVProVideo][Quality] Possible frame skip, at " + timeStamp + " delta " + d + " = " + skippedFrames + " frames");
}
SkippedFrames += skippedFrames;
isPerfectFrame = false;
}
}
if (QualitySettings.vSyncCount != 0)
{
#if UNITY_2022_2_OR_NEWER
float refreshRate = (float)( Screen.currentResolution.refreshRateRatio.value );
#else
float refreshRate = (float)( Screen.currentResolution.refreshRate );
#endif
long vsyncDuration = (long)((QualitySettings.vSyncCount * Helper.SecondsToHNS) / refreshRate);
if (timeStamp != _lastTimeStamp)
{
float framesPerVSync = (float)frameDuration / (float)vsyncDuration;
//Debug.Log((float)frameDuration + " " + (float)vsyncDuration);
float fractionalFramesPerVSync = framesPerVSync - Mathf.FloorToInt(framesPerVSync);
//Debug.Log(framesPerVSync + " " + fractionalFramesPerVSync);
// VSync rate is a multiple of the video rate so we should be able to get perfectly smooth playback
if (fractionalFramesPerVSync <= 0.0001f)
{
// Check for duplicate frames
if (!Mathf.Approximately(_sameFrameCount, (int)framesPerVSync))
{
if (LogIssues)
{
Debug.LogWarning("[AVProVideo][Quality] Frame " + timeStamp + " was shown for " + _sameFrameCount + " frames instead of expected " + framesPerVSync);
}
DuplicateFrames++;
isPerfectFrame = false;
}
}
_sameFrameCount = 1;
}
else
{
// Count the number of Unity-frames the video-frame is displayed for
_sameFrameCount++;
}
// Check for Unity dropping frames
{
long frameTime = (long)(Time.deltaTime * Helper.SecondsToHNS);
if (frameTime > (vsyncDuration + (vsyncDuration / 3)))
{
if (LogIssues)
{
Debug.LogWarning("[AVProVideo][Quality] Possible Unity dropped frame, delta time: " + (Time.deltaTime * 1000f) + "ms");
}
UnityDroppedFrames++;
isPerfectFrame = false;
}
}
}
if (_lastTimeStamp != timeStamp)
{
if (isPerfectFrame)
{
PerfectFrames++;
}
TotalFrames++;
PerfectFramesT = (float)PerfectFrames / (float)TotalFrames;
}
_lastTimeStamp = timeStamp;
}
private static bool IsGameViewVSyncEnabled()
{
bool result = true;
#if UNITY_EDITOR && UNITY_2019_1_OR_NEWER
System.Reflection.Assembly assembly = typeof(UnityEditor.EditorWindow).Assembly;
System.Type type = assembly.GetType("UnityEditor.GameView");
UnityEditor.EditorWindow window = UnityEditor.EditorWindow.GetWindow(type);
System.Reflection.PropertyInfo prop = type.GetProperty("vSyncEnabled");
if (prop != null)
{
result = (bool)prop.GetValue(window);
}
#endif
return result;
}
}
}
@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 4f344d823db9c4148af4dec2235f690d
timeCreated: 1634119397
licenseType: Pro
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: e2f8dd08c4c77654282b755fd4a069c1
folderAsset: yes
DefaultImporter:
userData:
assetBundleName:
assetBundleVariant:
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 80eb525dd677aa440823910b09b23ae0
timeCreated: 1438698292
licenseType: Pro
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,439 @@
//-----------------------------------------------------------------------------
// Copyright 2015-2022 RenderHeads Ltd. All rights reserved.
//-----------------------------------------------------------------------------
#if UNITY_2017_2_OR_NEWER && (UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX || (!UNITY_EDITOR && (UNITY_IOS || UNITY_TVOS)))
using System;
using System.Runtime.InteropServices;
using UnityEngine;
namespace RenderHeads.Media.AVProVideo
{
public sealed partial class AppleMediaPlayer
{
internal partial struct Native
{
#if UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX
private const string PluginName = "AVProVideo";
#elif UNITY_IOS || UNITY_TVOS
private const string PluginName = "__Internal";
#endif
// Video settings
internal enum AVPPlayerVideoPixelFormat: int
{
Invalid,
Bgra,
YCbCr420
}
[Flags]
internal enum AVPPlayerVideoOutputSettingsFlags: int
{
None = 0,
LinearColorSpace = 1 << 0,
GenerateMipmaps = 1 << 1,
}
// Audio settings
internal enum AVPPlayerAudioOutputMode : int
{
SystemDirect,
Unity,
SystemDirectWithCapture,
}
// Network settings
[Flags]
internal enum AVPPlayerNetworkSettingsFlags: int
{
None = 0,
PlayWithoutBuffering = 1 << 0,
UseSinglePlayerItem = 1 << 1,
}
[StructLayout(LayoutKind.Sequential)]
internal struct AVPPlayerSettings
{
// Video
internal AVPPlayerVideoPixelFormat pixelFormat;
internal AVPPlayerVideoOutputSettingsFlags videoFlags;
internal float preferredMaximumResolution_width;
internal float preferredMaximumResolution_height;
internal float maximumPlaybackRate;
// Audio
internal AVPPlayerAudioOutputMode audioOutputMode;
internal int sampleRate;
internal int bufferLength;
internal int audioFlags;
// Network
internal double preferredPeakBitRate;
internal double preferredForwardBufferDuration;
internal AVPPlayerNetworkSettingsFlags networkFlags;
}
[Flags]
internal enum AVPPlayerStatus : int
{
Unknown = 0,
ReadyToPlay = 1 << 0,
Playing = 1 << 1,
Paused = 1 << 2,
Finished = 1 << 3,
Seeking = 1 << 4,
Buffering = 1 << 5,
Stalled = 1 << 6,
ExternalPlaybackActive = 1 << 7,
Cached = 1 << 8,
FinishedSeeking = 1 << 9,
UpdatedAssetInfo = 1 << 16,
UpdatedTexture = 1 << 17,
UpdatedBufferedTimeRanges = 1 << 18,
UpdatedSeekableTimeRanges = 1 << 19,
UpdatedText = 1 << 20,
HasVideo = 1 << 24,
HasAudio = 1 << 25,
HasText = 1 << 26,
HasMetadata = 1 << 27,
Failed = 1 << 31
}
[Flags]
internal enum AVPPlayerFlags : int
{
None = 0,
Looping = 1 << 0,
Muted = 1 << 1,
AllowExternalPlayback = 1 << 2,
ResumePlayback = 1 << 16, // iOS only, resumes playback after audio session route change
Dirty = 1 << 31
}
internal enum AVPPlayerExternalPlaybackVideoGravity : int
{
Resize,
ResizeAspect,
ResizeAspectFill
};
[StructLayout(LayoutKind.Sequential)]
internal struct AVPPlayerSize
{
internal float width;
internal float height;
}
[StructLayout(LayoutKind.Sequential)]
internal struct AVPAffineTransform
{
internal float a;
internal float b;
internal float c;
internal float d;
internal float tx;
internal float ty;
}
[Flags]
internal enum AVPPlayerAssetFlags : int
{
None = 0,
CompatibleWithAirPlay = 1 << 0,
};
[StructLayout(LayoutKind.Sequential)]
internal struct AVPPlayerAssetInfo
{
internal double duration;
internal AVPPlayerSize dimensions;
internal float frameRate;
internal int videoTrackCount;
internal int audioTrackCount;
internal int textTrackCount;
internal AVPPlayerAssetFlags flags;
}
[Flags]
internal enum AVPPlayerTrackFlags: int
{
Default = 1 << 0,
}
internal enum AVPPlayerVideoTrackStereoMode: int
{
Unknown,
Monoscopic,
StereoscopicTopBottom,
StereoscopicLeftRight,
StereoscopicCustom,
StereoscopicRightLeft,
}
[Flags]
internal enum AVPPlayerVideoTrackFlags: int
{
HasAlpha = 1 << 0,
}
[StructLayout(LayoutKind.Sequential)]
internal struct AVPPlayerVideoTrackInfo
{
[MarshalAs(UnmanagedType.LPWStr)] internal string name;
[MarshalAs(UnmanagedType.LPWStr)] internal string language;
internal int trackId;
internal float estimatedDataRate;
internal uint codecSubtype;
internal AVPPlayerTrackFlags flags;
internal AVPPlayerSize dimensions;
internal float frameRate;
internal AVPAffineTransform transform;
internal AVPPlayerVideoTrackStereoMode stereoMode;
internal int bitsPerComponent;
internal AVPPlayerVideoTrackFlags videoTrackFlags;
internal Matrix4x4 yCbCrTransform;
}
[StructLayout(LayoutKind.Sequential)]
internal struct AVPPlayerAudioTrackInfo
{
[MarshalAs(UnmanagedType.LPWStr)] internal string name;
[MarshalAs(UnmanagedType.LPWStr)] internal string language;
internal int trackId;
internal float estimatedDataRate;
internal uint codecSubtype;
internal AVPPlayerTrackFlags flags;
internal double sampleRate;
internal uint channelCount;
internal uint channelLayoutTag;
internal AudioChannelMaskFlags channelBitmap;
}
[StructLayout(LayoutKind.Sequential)]
internal struct AVPPlayerTextTrackInfo
{
[MarshalAs(UnmanagedType.LPWStr)] internal string name;
[MarshalAs(UnmanagedType.LPWStr)] internal string language;
internal int trackId;
internal float estimatedDataRate;
internal uint codecSubtype;
internal AVPPlayerTrackFlags flags;
}
[StructLayout(LayoutKind.Sequential)]
internal struct AVPPlayerTimeRange
{
internal double start;
internal double duration;
};
[StructLayout(LayoutKind.Sequential)]
internal struct AVPPlayerState
{
internal AVPPlayerStatus status;
internal double currentTime;
internal double currentDate;
internal int selectedVideoTrack;
internal int selectedAudioTrack;
internal int selectedTextTrack;
internal int bufferedTimeRangesCount;
internal int seekableTimeRangesCount;
internal int audioCaptureBufferedSamplesCount;
}
internal enum AVPPlayerTextureFormat: int
{
Unknown,
BGRA8,
R8,
RG8,
BC1,
BC3,
BC4,
BC5,
BC7,
BGR10A2,
R16,
RG16,
BGR10XR,
}
[StructLayout(LayoutKind.Sequential)]
internal struct AVPPlayerTexturePlane
{
internal IntPtr plane;
internal int width;
internal int height;
internal AVPPlayerTextureFormat textureFormat;
}
[Flags]
internal enum AVPPlayerTextureFlags: int
{
None = 0,
Flipped = 1 << 0,
Linear = 1 << 1,
Mipmapped = 1 << 2,
}
internal enum AVPPlayerTextureYCbCrMatrix: int
{
Identity,
ITU_R_601,
ITU_R_709,
}
[StructLayout(LayoutKind.Sequential)]
internal struct AVPPlayerTexture
{
[MarshalAs(UnmanagedType.ByValArray, SizeConst=2)]
internal AVPPlayerTexturePlane[] planes;
internal long itemTime;
internal int frameCount;
internal int planeCount;
internal AVPPlayerTextureFlags flags;
internal AVPPlayerTextureYCbCrMatrix YCbCrMatrix;
};
[StructLayout(LayoutKind.Sequential)]
internal struct AVPPlayerText
{
internal IntPtr buffer;
internal long itemTime;
internal int length;
internal int sequence;
};
internal enum AVPPlayerTrackType: int
{
Video,
Audio,
Text
};
#if !UNITY_EDITOR && (UNITY_IOS || UNITY_TVOS)
[DllImport(PluginName)]
internal static extern void AVPPluginBootstrap();
#endif
[DllImport(PluginName)]
private static extern IntPtr AVPPluginGetVersionStringPointer();
internal static string GetPluginVersion()
{
return System.Runtime.InteropServices.Marshal.PtrToStringAnsi(AVPPluginGetVersionStringPointer());
}
[DllImport(PluginName)]
internal static extern IntPtr AVPPluginMakePlayer(Native.AVPPlayerSettings settings);
[DllImport(PluginName)]
internal static extern IntPtr AVPPlayerRelease(IntPtr player);
[DllImport(PluginName)]
internal static extern void AVPPlayerGetState(IntPtr player, ref AVPPlayerState state);
[DllImport(PluginName)]
internal static extern void AVPPlayerSetFlags(IntPtr player, int flags);
[DllImport(PluginName)]
internal static extern void AVPPlayerGetAssetInfo(IntPtr player, ref AVPPlayerAssetInfo info);
[DllImport(PluginName)]
internal static extern void AVPPlayerGetVideoTrackInfo(IntPtr player, int index, ref AVPPlayerVideoTrackInfo info);
[DllImport(PluginName)]
internal static extern void AVPPlayerGetAudioTrackInfo(IntPtr player, int index, ref AVPPlayerAudioTrackInfo info);
[DllImport(PluginName)]
internal static extern void AVPPlayerGetTextTrackInfo(IntPtr player, int index, ref AVPPlayerTextTrackInfo info);
[DllImport(PluginName)]
internal static extern void AVPPlayerGetBufferedTimeRanges(IntPtr player, AVPPlayerTimeRange[] ranges, int count);
[DllImport(PluginName)]
internal static extern void AVPPlayerGetSeekableTimeRanges(IntPtr player, AVPPlayerTimeRange[] ranges, int count);
[DllImport(PluginName)]
internal static extern void AVPPlayerGetTexture(IntPtr player, ref AVPPlayerTexture texture);
[DllImport(PluginName)]
internal static extern void AVPPlayerGetText(IntPtr player, ref AVPPlayerText text);
[DllImport(PluginName)]
internal static extern void AVPPlayerSetPlayerSettings(IntPtr player, AVPPlayerSettings settings);
[DllImport(PluginName)]
[return: MarshalAs(UnmanagedType.U1)]
internal static extern bool AVPPlayerOpenURL(IntPtr player, string url, string headers);
[DllImport(PluginName)]
internal static extern void AVPPlayerClose(IntPtr player);
[DllImport(PluginName)]
internal static extern int AVPPlayerGetAudio(IntPtr player, float[] buffer, int length);
[DllImport(PluginName)]
internal static extern void AVPPlayerSetRate(IntPtr player, float rate);
[DllImport(PluginName)]
internal static extern void AVPPlayerSetVolume(IntPtr player, float volume);
[DllImport(PluginName)]
internal static extern void AVPPlayerSetExternalPlaybackVideoGravity(IntPtr player, AVPPlayerExternalPlaybackVideoGravity gravity);
[DllImport(PluginName)]
internal static extern void AVPPlayerSeek(IntPtr player, double toTime, double toleranceBefore, double toleranceAfter);
[DllImport(PluginName)]
internal static extern void AVPPlayerSetKeyServerAuthToken(IntPtr player, string token);
[DllImport(PluginName)]
internal static extern void AVPPlayerSetKeyServerURL(IntPtr player, string url);
[DllImport(PluginName)]
internal static extern void AVPPlayerSetDecryptionKey(IntPtr player, byte[] key, int length);
[DllImport(PluginName)]
[return: MarshalAs(UnmanagedType.I1)]
internal static extern bool AVPPlayerSetTrack(IntPtr player, AVPPlayerTrackType type, int index);
#if !UNITY_EDITOR && UNITY_IOS
public struct MediaCachingOptions
{
public double minimumRequiredBitRate;
public float minimumRequiredResolution_width;
public float minimumRequiredResolution_height;
public string title;
public IntPtr artwork;
public int artworkLength;
}
[DllImport(PluginName)]
public static extern void AVPPluginCacheMediaForURL(string url, string headers, MediaCachingOptions options);
[DllImport(PluginName)]
public static extern void AVPPluginCancelDownloadOfMediaForURL(string url);
[DllImport(PluginName)]
public static extern void AVPPluginRemoveCachedMediaForURL(string url);
[DllImport(PluginName)]
public static extern int AVPPluginGetCachedMediaStatusForURL(string url, ref float progress);
#endif
}
}
}
#endif
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 0bf374b5848b649e6b3840fe1dc03cd2
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 3f68628a1ef6349648e502d1c66b5114
timeCreated: 1547113004
licenseType: Free
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,226 @@
//-----------------------------------------------------------------------------
// Copyright 2015-2022 RenderHeads Ltd. All rights reserved.
//-----------------------------------------------------------------------------
#if UNITY_2017_2_OR_NEWER && (UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX || (!UNITY_EDITOR && (UNITY_IOS || UNITY_TVOS)))
using System;
using System.Runtime.InteropServices;
using UnityEngine;
namespace RenderHeads.Media.AVProVideo
{
internal static class AppleMediaPlayerExtensions
{
// AVPPlayerStatus
internal static bool IsReadyToPlay(this AppleMediaPlayer.Native.AVPPlayerStatus status)
{
return (status & AppleMediaPlayer.Native.AVPPlayerStatus.ReadyToPlay) == AppleMediaPlayer.Native.AVPPlayerStatus.ReadyToPlay;
}
internal static bool IsPlaying(this AppleMediaPlayer.Native.AVPPlayerStatus status)
{
return (status & AppleMediaPlayer.Native.AVPPlayerStatus.Playing) == AppleMediaPlayer.Native.AVPPlayerStatus.Playing;
}
internal static bool IsPaused(this AppleMediaPlayer.Native.AVPPlayerStatus status)
{
return (status & AppleMediaPlayer.Native.AVPPlayerStatus.Paused) == AppleMediaPlayer.Native.AVPPlayerStatus.Paused;
}
internal static bool IsFinished(this AppleMediaPlayer.Native.AVPPlayerStatus status)
{
return (status & AppleMediaPlayer.Native.AVPPlayerStatus.Finished) == AppleMediaPlayer.Native.AVPPlayerStatus.Finished;
}
internal static bool IsSeeking(this AppleMediaPlayer.Native.AVPPlayerStatus status)
{
return (status & AppleMediaPlayer.Native.AVPPlayerStatus.Seeking) == AppleMediaPlayer.Native.AVPPlayerStatus.Seeking;
}
internal static bool IsBuffering(this AppleMediaPlayer.Native.AVPPlayerStatus status)
{
return (status & AppleMediaPlayer.Native.AVPPlayerStatus.Buffering) == AppleMediaPlayer.Native.AVPPlayerStatus.Buffering;
}
internal static bool IsStalled(this AppleMediaPlayer.Native.AVPPlayerStatus status)
{
return (status & AppleMediaPlayer.Native.AVPPlayerStatus.Stalled) == AppleMediaPlayer.Native.AVPPlayerStatus.Stalled;
}
internal static bool IsExternalPlaybackActive(this AppleMediaPlayer.Native.AVPPlayerStatus status)
{
return (status & AppleMediaPlayer.Native.AVPPlayerStatus.ExternalPlaybackActive) == AppleMediaPlayer.Native.AVPPlayerStatus.ExternalPlaybackActive;
}
internal static bool IsCached(this AppleMediaPlayer.Native.AVPPlayerStatus status)
{
return (status & AppleMediaPlayer.Native.AVPPlayerStatus.Cached) == AppleMediaPlayer.Native.AVPPlayerStatus.Cached;
}
internal static bool HasFinishedSeeking(this AppleMediaPlayer.Native.AVPPlayerStatus status)
{
return (status & AppleMediaPlayer.Native.AVPPlayerStatus.FinishedSeeking) == AppleMediaPlayer.Native.AVPPlayerStatus.FinishedSeeking;
}
internal static bool HasUpdatedAssetInfo(this AppleMediaPlayer.Native.AVPPlayerStatus status)
{
return (status & AppleMediaPlayer.Native.AVPPlayerStatus.UpdatedAssetInfo) == AppleMediaPlayer.Native.AVPPlayerStatus.UpdatedAssetInfo;
}
internal static bool HasUpdatedTexture(this AppleMediaPlayer.Native.AVPPlayerStatus status)
{
return (status & AppleMediaPlayer.Native.AVPPlayerStatus.UpdatedTexture) == AppleMediaPlayer.Native.AVPPlayerStatus.UpdatedTexture;
}
internal static bool HasUpdatedBufferedTimeRanges(this AppleMediaPlayer.Native.AVPPlayerStatus status)
{
return (status & AppleMediaPlayer.Native.AVPPlayerStatus.UpdatedBufferedTimeRanges) == AppleMediaPlayer.Native.AVPPlayerStatus.UpdatedBufferedTimeRanges;
}
internal static bool HasUpdatedSeekableTimeRanges(this AppleMediaPlayer.Native.AVPPlayerStatus status)
{
return (status & AppleMediaPlayer.Native.AVPPlayerStatus.UpdatedSeekableTimeRanges) == AppleMediaPlayer.Native.AVPPlayerStatus.UpdatedSeekableTimeRanges;
}
internal static bool HasUpdatedText(this AppleMediaPlayer.Native.AVPPlayerStatus status)
{
return (status & AppleMediaPlayer.Native.AVPPlayerStatus.UpdatedText) == AppleMediaPlayer.Native.AVPPlayerStatus.UpdatedText;
}
internal static bool HasVideo(this AppleMediaPlayer.Native.AVPPlayerStatus status)
{
return (status & AppleMediaPlayer.Native.AVPPlayerStatus.HasVideo) == AppleMediaPlayer.Native.AVPPlayerStatus.HasVideo;
}
internal static bool HasAudio(this AppleMediaPlayer.Native.AVPPlayerStatus status)
{
return (status & AppleMediaPlayer.Native.AVPPlayerStatus.HasAudio) == AppleMediaPlayer.Native.AVPPlayerStatus.HasAudio;
}
internal static bool HasText(this AppleMediaPlayer.Native.AVPPlayerStatus status)
{
return (status & AppleMediaPlayer.Native.AVPPlayerStatus.HasText) == AppleMediaPlayer.Native.AVPPlayerStatus.HasText;
}
internal static bool HasMetadata(this AppleMediaPlayer.Native.AVPPlayerStatus status)
{
return (status & AppleMediaPlayer.Native.AVPPlayerStatus.HasMetadata) == AppleMediaPlayer.Native.AVPPlayerStatus.HasMetadata;
}
internal static bool HasFailed(this AppleMediaPlayer.Native.AVPPlayerStatus status)
{
return (status & AppleMediaPlayer.Native.AVPPlayerStatus.Failed) == AppleMediaPlayer.Native.AVPPlayerStatus.Failed;
}
// AVPPlayerFlags
internal static bool IsLooping(this AppleMediaPlayer.Native.AVPPlayerFlags flags)
{
return (flags & AppleMediaPlayer.Native.AVPPlayerFlags.Looping) == AppleMediaPlayer.Native.AVPPlayerFlags.Looping;
}
internal static AppleMediaPlayer.Native.AVPPlayerFlags SetLooping(this AppleMediaPlayer.Native.AVPPlayerFlags flags, bool b)
{
if (flags.IsLooping() ^ b)
{
flags = (b ? flags | AppleMediaPlayer.Native.AVPPlayerFlags.Looping
: flags & ~AppleMediaPlayer.Native.AVPPlayerFlags.Looping) | AppleMediaPlayer.Native.AVPPlayerFlags.Dirty;
}
return flags;
}
internal static bool IsMuted(this AppleMediaPlayer.Native.AVPPlayerFlags flags)
{
return (flags & AppleMediaPlayer.Native.AVPPlayerFlags.Muted) == AppleMediaPlayer.Native.AVPPlayerFlags.Muted;
}
internal static AppleMediaPlayer.Native.AVPPlayerFlags SetMuted(this AppleMediaPlayer.Native.AVPPlayerFlags flags, bool b)
{
if (flags.IsMuted() ^ b)
{
flags = (b ? flags | AppleMediaPlayer.Native.AVPPlayerFlags.Muted
: flags & ~AppleMediaPlayer.Native.AVPPlayerFlags.Muted) | AppleMediaPlayer.Native.AVPPlayerFlags.Dirty;
}
return flags;
}
internal static bool IsExternalPlaybackAllowed(this AppleMediaPlayer.Native.AVPPlayerFlags flags)
{
return (flags & AppleMediaPlayer.Native.AVPPlayerFlags.AllowExternalPlayback) == AppleMediaPlayer.Native.AVPPlayerFlags.AllowExternalPlayback;
}
internal static AppleMediaPlayer.Native.AVPPlayerFlags SetAllowExternalPlayback(this AppleMediaPlayer.Native.AVPPlayerFlags flags, bool b)
{
if (flags.IsExternalPlaybackAllowed() ^ b)
{
flags = (b ? flags | AppleMediaPlayer.Native.AVPPlayerFlags.AllowExternalPlayback
: flags & ~AppleMediaPlayer.Native.AVPPlayerFlags.AllowExternalPlayback) | AppleMediaPlayer.Native.AVPPlayerFlags.Dirty;
}
return flags;
}
internal static bool ResumePlayback(this AppleMediaPlayer.Native.AVPPlayerFlags flags)
{
return (flags & AppleMediaPlayer.Native.AVPPlayerFlags.ResumePlayback) == AppleMediaPlayer.Native.AVPPlayerFlags.ResumePlayback;
}
internal static AppleMediaPlayer.Native.AVPPlayerFlags SetResumePlayback(this AppleMediaPlayer.Native.AVPPlayerFlags flags, bool b)
{
if (flags.ResumePlayback() ^ b)
{
flags = (b ? flags | AppleMediaPlayer.Native.AVPPlayerFlags.ResumePlayback
: flags & ~AppleMediaPlayer.Native.AVPPlayerFlags.ResumePlayback) | AppleMediaPlayer.Native.AVPPlayerFlags.Dirty;
}
return flags;
}
internal static bool IsDirty(this AppleMediaPlayer.Native.AVPPlayerFlags flags)
{
return (flags & AppleMediaPlayer.Native.AVPPlayerFlags.Dirty) == AppleMediaPlayer.Native.AVPPlayerFlags.Dirty;
}
internal static AppleMediaPlayer.Native.AVPPlayerFlags SetDirty(this AppleMediaPlayer.Native.AVPPlayerFlags flags, bool b)
{
if (flags.IsDirty() ^ b)
{
flags = b ? flags | AppleMediaPlayer.Native.AVPPlayerFlags.Dirty : flags & ~AppleMediaPlayer.Native.AVPPlayerFlags.Dirty;
}
return flags;
}
// MARK: AVPPlayerAssetFlags
internal static bool IsCompatibleWithAirPlay(this AppleMediaPlayer.Native.AVPPlayerAssetFlags flags)
{
return (flags & AppleMediaPlayer.Native.AVPPlayerAssetFlags.CompatibleWithAirPlay) == AppleMediaPlayer.Native.AVPPlayerAssetFlags.CompatibleWithAirPlay;
}
// MARK: AVPPlayerTrackFlags
internal static bool IsDefault(this AppleMediaPlayer.Native.AVPPlayerTrackFlags flags)
{
return (flags & AppleMediaPlayer.Native.AVPPlayerTrackFlags.Default) == AppleMediaPlayer.Native.AVPPlayerTrackFlags.Default;
}
// AVPPlayerTextureFlags
internal static bool IsFlipped(this AppleMediaPlayer.Native.AVPPlayerTextureFlags flags)
{
return (flags & AppleMediaPlayer.Native.AVPPlayerTextureFlags.Flipped) == AppleMediaPlayer.Native.AVPPlayerTextureFlags.Flipped;
}
internal static bool IsLinear(this AppleMediaPlayer.Native.AVPPlayerTextureFlags flags)
{
return (flags & AppleMediaPlayer.Native.AVPPlayerTextureFlags.Linear) == AppleMediaPlayer.Native.AVPPlayerTextureFlags.Linear;
}
internal static bool IsMipmapped(this AppleMediaPlayer.Native.AVPPlayerTextureFlags flags)
{
return (flags & AppleMediaPlayer.Native.AVPPlayerTextureFlags.Mipmapped) == AppleMediaPlayer.Native.AVPPlayerTextureFlags.Mipmapped;
}
}
}
#endif
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: e27ea5523e11f44c09e8d368eb1f2983
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,347 @@
using System;
using System.Text;
using UnityEngine;
//-----------------------------------------------------------------------------
// Copyright 2015-2021 RenderHeads Ltd. All rights reserved.
//-----------------------------------------------------------------------------
namespace RenderHeads.Media.AVProVideo
{
/// <summary>
/// This media player fakes video playback for platforms that aren't supported
/// </summary>
public sealed partial class NullMediaPlayer : BaseMediaPlayer
{
private bool _isPlaying = false;
private bool _isPaused = false;
private double _currentTime = 0.0;
// private bool _audioMuted = false;
private float _volume = 0.0f;
private float _playbackRate = 1.0f;
private bool _bLoop;
private int _Width = 256;
private int _height = 256;
private Texture2D _texture;
private Texture2D _texture_AVPro;
private Texture2D _texture_AVPro1;
private float _fakeFlipTime;
private int _frameCount;
private const float FrameRate = 10f;
/// <inheritdoc/>
public override string GetVersion()
{
return "0.0.0";
}
/// <inheritdoc/>
public override string GetExpectedVersion()
{
return GetVersion();
}
/// <inheritdoc/>
public override bool OpenMedia(string path, long offset, string httpHeader, MediaHints mediaHints, int forceFileFormat = 0, bool startWithHighestBitrate = false)
{
_texture_AVPro = (Texture2D)Resources.Load("Textures/AVProVideo-NullPlayer-Frame0");
_texture_AVPro1 = (Texture2D)Resources.Load("Textures/AVProVideo-NullPlayer-Frame1");
if( _texture_AVPro )
{
_Width = _texture_AVPro.width;
_height = _texture_AVPro.height;
}
_texture = _texture_AVPro;
_fakeFlipTime = 0.0f;
_frameCount = 0;
return true;
}
/// <inheritdoc/>
public override void CloseMedia()
{
_frameCount = 0;
Resources.UnloadAsset(_texture_AVPro);
Resources.UnloadAsset(_texture_AVPro1);
base.CloseMedia();
}
/// <inheritdoc/>
public override void SetLooping( bool bLooping )
{
_bLoop = bLooping;
}
/// <inheritdoc/>
public override bool IsLooping()
{
return _bLoop;
}
/// <inheritdoc/>
public override bool HasMetaData()
{
return true;
}
/// <inheritdoc/>
public override bool CanPlay()
{
return true;
}
/// <inheritdoc/>
public override bool HasAudio()
{
return false;
}
/// <inheritdoc/>
public override bool HasVideo()
{
return false;
}
/// <inheritdoc/>
public override void Play()
{
_isPlaying = true;
_isPaused = false;
_fakeFlipTime = 0.0f;
}
/// <inheritdoc/>
public override void Pause()
{
_isPlaying = false;
_isPaused = true;
}
/// <inheritdoc/>
public override void Stop()
{
_isPlaying = false;
_isPaused = false;
}
/// <inheritdoc/>
public override bool IsSeeking()
{
return false;
}
/// <inheritdoc/>
public override bool IsPlaying()
{
return _isPlaying;
}
/// <inheritdoc/>
public override bool IsPaused()
{
return _isPaused;
}
/// <inheritdoc/>
public override bool IsFinished()
{
return _isPlaying && (_currentTime >= GetDuration());
}
/// <inheritdoc/>
public override bool IsBuffering()
{
return false;
}
/// <inheritdoc/>
public override double GetDuration()
{
return 10.0;
}
/// <inheritdoc/>
public override int GetVideoWidth()
{
return _Width;
}
/// <inheritdoc/>
public override int GetVideoHeight()
{
return _height;
}
/// <inheritdoc/>
public override float GetVideoDisplayRate()
{
return FrameRate;
}
/// <inheritdoc/>
public override Texture GetTexture( int index )
{
// return _texture ? _texture : Texture2D.whiteTexture;
return _texture;
}
/// <inheritdoc/>
public override int GetTextureFrameCount()
{
return _frameCount;
}
internal override StereoPacking InternalGetTextureStereoPacking()
{
return StereoPacking.Unknown;
}
/// <inheritdoc/>
public override bool RequiresVerticalFlip()
{
return false;
}
/// <inheritdoc/>
public override void Seek(double time)
{
_currentTime = time;
}
/// <inheritdoc/>
public override void SeekFast(double time)
{
_currentTime = time;
}
/// <inheritdoc/>
public override double GetCurrentTime()
{
return _currentTime;
}
/// <inheritdoc/>
public override void SetPlaybackRate(float rate)
{
_playbackRate = rate;
}
/// <inheritdoc/>
public override float GetPlaybackRate()
{
return _playbackRate;
}
/// <inheritdoc/>
public override void MuteAudio(bool bMuted)
{
// _audioMuted = bMuted;
}
/// <inheritdoc/>
public override bool IsMuted()
{
return true;
}
/// <inheritdoc/>
public override void SetVolume(float volume)
{
_volume = volume;
}
/// <inheritdoc/>
public override float GetVolume()
{
return _volume;
}
/// <inheritdoc/>
public override float GetVideoFrameRate()
{
return 0.0f;
}
/// <inheritdoc/>
public override void Update()
{
UpdateSubtitles();
if (_isPlaying)
{
_currentTime += Time.deltaTime;
if (_currentTime >= GetDuration())
{
_currentTime = GetDuration();
if( _bLoop )
{
Rewind();
}
}
//
_fakeFlipTime += Time.deltaTime;
if( _fakeFlipTime >= (1.0 / FrameRate))
{
_fakeFlipTime = 0.0f;
_texture = ( _texture == _texture_AVPro ) ? _texture_AVPro1 : _texture_AVPro;
_frameCount++;
}
}
}
/// <inheritdoc/>
public override void Render()
{
}
/// <inheritdoc/>
public override void Dispose()
{
}
}
public sealed partial class NullMediaPlayer : BaseMediaPlayer
{
internal override bool InternalSetActiveTrack(TrackType trackType, int trackUid)
{
// Set the active text track using the unique identifier
// Or disable all text tracks if < 0
return false;
}
internal override bool InternalIsChangedTracks(TrackType trackType)
{
// Has the tracks changed since the last frame 'tick'
return false;
}
internal override int InternalGetTrackCount(TrackType trackType)
{
// Return number of text tracks
return 0;
}
internal override TrackBase InternalGetTrackInfo(TrackType trackType, int index, ref bool isActiveTrack)
{
// Get information about the specific track at index, range is [0...InternalGetTextTrackCount)
return null;
}
internal override bool InternalIsChangedTextCue()
{
// Has the text cue changed since the last frame 'tick'
return false;
}
internal override string InternalGetCurrentTextCue()
{
return null;
}
}
}
@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 478671181ab1c9b42be924da77d7fcbe
timeCreated: 1438703159
licenseType: Pro
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,926 @@
//#define AVPRO_WEBGL_USE_RENDERTEXTURE
// NOTE: We only allow this script to compile in editor so we can easily check for compilation issues
#if (UNITY_EDITOR || UNITY_WEBGL)
using UnityEngine;
using System;
using System.Text;
using System.Runtime.InteropServices;
//-----------------------------------------------------------------------------
// Copyright 2015-2021 RenderHeads Ltd. All rights reserved.
//-----------------------------------------------------------------------------
namespace RenderHeads.Media.AVProVideo
{
/// <summary>
/// WebGL implementation of BaseMediaPlayer
/// </summary>
public sealed class WebGLMediaPlayer : BaseMediaPlayer
{
//private enum AVPPlayerStatus
//{
// Unknown,
// ReadyToPlay,
// Playing,
// Finished,
// Seeking,
// Failed
//}
[DllImport("__Internal")]
private static extern bool AVPPlayerInsertVideoElement(string path, int[] idValues, int externalLibrary);
[DllImport("__Internal")]
private static extern int AVPPlayerWidth(int player);
[DllImport("__Internal")]
private static extern int AVPPlayerHeight(int player);
[DllImport("__Internal")]
private static extern int AVPPlayerGetLastError(int player);
[DllImport("__Internal")]
private static extern int AVPPlayerGetVideoTrackCount(int player);
[DllImport("__Internal")]
private static extern int AVPPlayerGetAudioTrackCount(int player);
[DllImport("__Internal")]
private static extern int AVPPlayerGetTextTrackCount(int player);
[DllImport("__Internal")]
private static extern bool AVPPlayerSetActiveVideoTrack(int player, int trackId);
[DllImport("__Internal")]
private static extern bool AVPPlayerSetActiveAudioTrack(int player, int trackId);
[DllImport("__Internal")]
private static extern bool AVPPlayerSetActiveTextTrack(int player, int trackId);
[DllImport("__Internal")]
private static extern void AVPPlayerClose(int player);
[DllImport("__Internal")]
private static extern bool AVPPlayerReady(int player);
[DllImport("__Internal")]
private static extern void AVPPlayerSetLooping(int player, bool loop);
[DllImport("__Internal")]
private static extern bool AVPPlayerIsLooping(int player);
[DllImport("__Internal")]
private static extern bool AVPPlayerIsSeeking(int player);
[DllImport("__Internal")]
private static extern bool AVPPlayerIsPlaying(int player);
[DllImport("__Internal")]
private static extern bool AVPPlayerIsPaused(int player);
[DllImport("__Internal")]
private static extern bool AVPPlayerIsFinished(int player);
[DllImport("__Internal")]
private static extern bool AVPPlayerIsBuffering(int player);
[DllImport("__Internal")]
private static extern bool AVPPlayerIsPlaybackStalled(int player);
[DllImport("__Internal")]
private static extern bool AVPPlayerPlay(int player);
[DllImport("__Internal")]
private static extern void AVPPlayerPause(int player);
[DllImport("__Internal")]
private static extern void AVPPlayerSeekToTime(int player, double time, bool fast);
[DllImport("__Internal")]
private static extern double AVPPlayerGetCurrentTime(int player);
[DllImport("__Internal")]
private static extern float AVPPlayerGetDuration(int player);
[DllImport("__Internal")]
private static extern float AVPPlayerGetPlaybackRate(int player);
[DllImport("__Internal")]
private static extern void AVPPlayerSetPlaybackRate(int player, float rate);
[DllImport("__Internal")]
private static extern void AVPPlayerSetMuted(int player, bool muted);
[DllImport("__Internal")]
private static extern bool AVPPlayerIsMuted(int player);
[DllImport("__Internal")]
private static extern float AVPPlayerGetVolume(int player);
[DllImport("__Internal")]
private static extern void AVPPlayerSetVolume(int player, float volume);
[DllImport("__Internal")]
private static extern bool AVPPlayerHasVideo(int player);
[DllImport("__Internal")]
private static extern bool AVPPlayerHasAudio(int player);
[DllImport("__Internal")]
private static extern void AVPPlayerCreateVideoTexture(int textureId);
[DllImport("__Internal")]
private static extern void AVPPlayerDestroyVideoTexture(int textureId);
[DllImport("__Internal")]
private static extern void AVPPlayerFetchVideoTexture(int player, IntPtr texture, bool init);
[DllImport("__Internal")]
private static extern int AVPPlayerGetDecodedFrameCount(int player);
[DllImport("__Internal")]
private static extern bool AVPPlayerSupportedDecodedFrameCount(int player);
[DllImport("__Internal")]
private static extern bool AVPPlayerHasMetadata(int player);
[DllImport("__Internal")]
private static extern int AVPPlayerUpdatePlayerIndex(int id);
[DllImport("__Internal")]
private static extern int AVPPlayerGetNumBufferedTimeRanges(int id);
[DllImport("__Internal")]
private static extern double AVPPlayerGetTimeRangeStart(int id, int timeRangeIndex);
[DllImport("__Internal")]
private static extern double AVPPlayerGetTimeRangeEnd(int id, int timeRangeIndex);
[DllImport("__Internal")]
private static extern string AVPPlayerGetVideoTrackName(int player, int trackIndex);
[DllImport("__Internal")]
private static extern string AVPPlayerGetAudioTrackName(int player, int trackIndex);
[DllImport("__Internal")]
private static extern string AVPPlayerGetTextTrackName(int player, int trackIndex);
[DllImport("__Internal")]
private static extern string AVPPlayerGetVideoTrackLanguage(int player, int trackIndex);
[DllImport("__Internal")]
private static extern string AVPPlayerGetAudioTrackLanguage(int player, int trackIndex);
[DllImport("__Internal")]
private static extern string AVPPlayerGetTextTrackLanguage(int player, int trackIndex);
[DllImport("__Internal")]
private static extern bool AVPPlayerIsVideoTrackActive(int player, int trackIndex);
[DllImport("__Internal")]
private static extern bool AVPPlayerIsAudioTrackActive(int player, int trackIndex);
[DllImport("__Internal")]
private static extern bool AVPPlayerIsTextTrackActive(int player, int trackIndex);
private WebGL.ExternalLibrary _externalLibrary = WebGL.ExternalLibrary.None;
private int _playerIndex = -1;
private int _playerID = -1;
#if AVPRO_WEBGL_USE_RENDERTEXTURE
private RenderTexture _texture = null;
#else
private Texture2D _texture = null;
#endif
private int _width = 0;
private int _height = 0;
private int _cachedVideoTrackCount = 0;
private int _cachedAudioTrackCount = 0;
private int _cachedTextTrackCount = 0;
private bool _isDirtyVideoTracks = false;
private bool _isDirtyAudioTracks = false;
private bool _isDirtyTextTracks = false;
private bool _useTextureMips = false;
private System.IntPtr _cachedTextureNativePtr = System.IntPtr.Zero;
private static bool _isWebGL1 = false;
public static bool InitialisePlatform()
{
_isWebGL1 = (SystemInfo.graphicsDeviceType == UnityEngine.Rendering.GraphicsDeviceType.OpenGLES2);
return true;
}
public WebGLMediaPlayer(MediaPlayer.OptionsWebGL options)
{
SetOptions(options);
}
public void SetOptions(MediaPlayer.OptionsWebGL options)
{
_externalLibrary = options.externalLibrary;
_useTextureMips = options.useTextureMips;
}
public override string GetVersion()
{
return "2.1.6";
}
public override string GetExpectedVersion()
{
return GetVersion();
}
public override bool OpenMedia(string path, long offset, string httpHeader, MediaHints mediaHints, int forceFileFormat = 0, bool startWithHighestBitrate = false)
{
bool result = false;
if (path.StartsWith("http://") ||
path.StartsWith("https://") ||
path.StartsWith("file://") ||
path.StartsWith("blob:") ||
path.StartsWith("chrome-extension://"))
{
int[] idValues = new int[2];
idValues[0] = -1;
AVPPlayerInsertVideoElement(path, idValues, (int)_externalLibrary);
{
int playerIndex = idValues[0];
_playerID = idValues[1];
if (playerIndex > -1)
{
_playerIndex = playerIndex;
_mediaHints = mediaHints;
result = true;
}
}
}
else
{
Debug.LogError("[AVProVideo] Unknown URL protocol");
}
return result;
}
public override void CloseMedia()
{
if (_playerIndex != -1)
{
Pause();
_width = 0;
_height = 0;
_cachedVideoTrackCount = 0;
_cachedAudioTrackCount = 0;
_cachedTextTrackCount = 0;
_isDirtyVideoTracks = false;
_isDirtyAudioTracks = false;
_isDirtyTextTracks = false;
AVPPlayerClose(_playerIndex);
if (_texture != null)
{
DestroyTexture();
}
_playerIndex = -1;
_playerID = -1;
base.CloseMedia();
}
}
public override bool IsLooping()
{
//Debug.Assert(_player != -1, "no player IsLooping");
bool result = false;
if (_playerIndex != -1)
{
result = AVPPlayerIsLooping(_playerIndex);
}
return result;
}
public override void SetLooping(bool looping)
{
//Debug.Assert(_playerIndex != -1, "no player SetLooping");
AVPPlayerSetLooping(_playerIndex, looping);
}
public override bool HasAudio()
{
//Debug.Assert(_player != -1, "no player HasAudio");
bool result = false;
if (_playerIndex != -1)
{
result = AVPPlayerHasAudio(_playerIndex);
}
return result;
}
public override bool HasVideo()
{
//Debug.Assert(_player != -1, "no player HasVideo");
bool result = false;
if (_playerIndex != -1)
{
result = AVPPlayerHasVideo(_playerIndex);
}
return result;
}
public override bool HasMetaData()
{
//Debug.Assert(_player != -1, "no player HasMetaData");
bool result = false;
if (_playerIndex != -1)
{
result = AVPPlayerHasMetadata(_playerIndex);
}
return result;
}
public override bool CanPlay()
{
//Debug.Assert(_player != -1, "no player CanPlay");
bool result = false;
if (_playerIndex != -1)
{
result = AVPPlayerReady(_playerIndex);
}
return result;
}
public override void Play()
{
Debug.Assert(_playerIndex != -1, "no player Play");
if (!AVPPlayerPlay(_playerIndex))
{
Debug.LogWarning("[AVProVideo] Browser permission prevented video playback");
}
}
public override void Pause()
{
Debug.Assert(_playerIndex != -1, "no player Pause");
AVPPlayerPause(_playerIndex);
}
public override void Stop()
{
Debug.Assert(_playerIndex != -1, "no player Stop");
AVPPlayerPause(_playerIndex);
}
public override void Seek(double time)
{
Debug.Assert(_playerIndex != -1, "no player Seek");
AVPPlayerSeekToTime(_playerIndex, time, false);
}
public override void SeekFast(double time)
{
Debug.Assert(_playerIndex != -1, "no player SeekFast");
AVPPlayerSeekToTime(_playerIndex, time, true);
}
public override double GetCurrentTime()
{
//Debug.Assert(_player != -1, "no player GetCurrentTime");
double result = 0.0;
if (_playerIndex != -1)
{
result = AVPPlayerGetCurrentTime(_playerIndex);
}
return result;
}
public override void SetPlaybackRate(float rate)
{
Debug.Assert(_playerIndex != -1, "no player SetPlaybackRate");
// No HTML implementations allow negative rate yet
rate = Mathf.Clamp(rate, 0.25f, 8f);
AVPPlayerSetPlaybackRate(_playerIndex, rate);
}
public override float GetPlaybackRate()
{
//Debug.Assert(_player != -1, "no player GetPlaybackRate");
float result = 0.0f;
if (_playerIndex != -1)
{
result = AVPPlayerGetPlaybackRate(_playerIndex);
}
return result;
}
public override double GetDuration()
{
//Debug.Assert(_player != -1, "no player GetDuration");
double result = 0.0;
if (_playerIndex != -1)
{
result = AVPPlayerGetDuration(_playerIndex);
}
return result;
}
public override int GetVideoWidth()
{
if (_width == 0)
{
_width = AVPPlayerWidth(_playerIndex);
}
return _width;
}
public override int GetVideoHeight()
{
if (_height == 0)
{
_height = AVPPlayerHeight(_playerIndex);
}
return _height;
}
public override float GetVideoFrameRate()
{
// There is no way in HTML5 yet to get the frame rate of the video
return 0f;
}
public override bool IsSeeking()
{
//Debug.Assert(_player != -1, "no player IsSeeking");
bool result = false;
if (_playerIndex != -1)
{
result = AVPPlayerIsSeeking(_playerIndex);
}
return result;
}
public override bool IsPlaying()
{
//Debug.Assert(_player != -1, "no player IsPlaying");
bool result = false;
if (_playerIndex != -1)
{
result = AVPPlayerIsPlaying(_playerIndex);
}
return result;
}
public override bool IsPaused()
{
//Debug.Assert(_player != -1, "no player IsPaused");
bool result = false;
if (_playerIndex != -1)
{
result = AVPPlayerIsPaused(_playerIndex);
}
return result;
}
public override bool IsFinished()
{
//Debug.Assert(_player != -1, "no player IsFinished");
bool result = false;
if (_playerIndex != -1)
{
result = AVPPlayerIsFinished(_playerIndex);
}
return result;
}
public override bool IsBuffering()
{
//Debug.Assert(_player != -1, "no player IsBuffering");
bool result = false;
if (_playerIndex != -1)
{
result = AVPPlayerIsBuffering(_playerIndex);
}
return result;
}
public override Texture GetTexture( int index )
{
return _texture;
}
public override int GetTextureFrameCount()
{
//Debug.Assert(_player != -1, "no player GetTextureFrameCount");
int result = 0;
if (_playerIndex != -1)
{
result = AVPPlayerGetDecodedFrameCount(_playerIndex);
}
return result;
}
internal override StereoPacking InternalGetTextureStereoPacking()
{
return StereoPacking.Unknown;
}
public override bool SupportsTextureFrameCount()
{
bool result = false;
if (_playerIndex != -1)
{
result = AVPPlayerSupportedDecodedFrameCount(_playerIndex);
}
return result;
}
public override bool RequiresVerticalFlip()
{
return true;
}
public override bool IsMuted()
{
//Debug.Assert(_player != -1, "no player IsMuted");
bool result = false;
if (_playerIndex != -1)
{
result = AVPPlayerIsMuted(_playerIndex);
}
return result;
}
public override void MuteAudio(bool bMute)
{
Debug.Assert(_playerIndex != -1, "no player MuteAudio");
AVPPlayerSetMuted(_playerIndex, bMute);
}
public override void SetVolume(float volume)
{
Debug.Assert(_playerIndex != -1, "no player SetVolume");
AVPPlayerSetVolume(_playerIndex, volume);
}
public override float GetVolume()
{
//Debug.Assert(_player != -1, "no player GetVolume");
float result = 0.0f;
if (_playerIndex != -1)
{
result = AVPPlayerGetVolume(_playerIndex);
}
return result;
}
public override void Render()
{
}
private void UpdateLastErrorCode()
{
var code = AVPPlayerGetLastError(_playerIndex);
switch(code){
case 0:
_lastError = ErrorCode.None;
break;
case 1:
_lastError = ErrorCode.LoadFailed;
break;
case 2:
_lastError = ErrorCode.LoadFailed;
break;
case 3:
_lastError = ErrorCode.DecodeFailed;
break;
case 4:
_lastError = ErrorCode.LoadFailed;
break;
default:
break;
}
}
private bool IsMipMapGenerationSupported(int videoWidth, int videoHeight)
{
if (!_isWebGL1 || (Mathf.IsPowerOfTwo(videoWidth) && Mathf.IsPowerOfTwo(videoHeight)))
{
// Mip generation only supported in WebGL 2.0, or WebGL 1.0 when using power-of-two textures
return true;
}
return false;
}
private void CreateTexture()
{
//Debug.Log("creating texture " + _width + " X " + _height);
#if AVPRO_WEBGL_USE_RENDERTEXTURE
_texture = new RenderTexture(_width, _height, 0, RenderTextureFormat.Default);
_texture.autoGenerateMips = false;
_texture.useMipMap = (_useTextureMips && IsMipMapGenerationSupported(_width, _height));
_texture.Create();
_cachedTextureNativePtr = _texture.GetNativeTexturePtr();
#else
int textureId = 80000 + _playerIndex;
_cachedTextureNativePtr = new System.IntPtr(textureId);
AVPPlayerCreateVideoTexture(textureId);
// TODO: add support for mip generation
_texture = Texture2D.CreateExternalTexture(_width, _height, TextureFormat.RGBA32, false, false, _cachedTextureNativePtr);
if (_useTextureMips)
{
Debug.LogWarning("[AVProVideo] Texture Mips not yet implemented in this WebGL rendering path");
}
//Debug.Log("created texture1 " + _texture);
//Debug.Log("created texture2 " + _texture.GetNativeTexturePtr().ToInt32());
#endif
ApplyTextureProperties(_texture);
bool initTexture = true;
#if AVPRO_WEBGL_USE_RENDERTEXTURE
// Textures in WebGL 2.0 don't require texImage2D as they are already recreated with texStorage2D
initTexture = _isWebGL1;
#endif
AVPPlayerFetchVideoTexture(_playerIndex, _cachedTextureNativePtr, initTexture);
}
private void DestroyTexture()
{
// Have to update with zero to release Metal textures!
//_texture.UpdateExternalTexture(0);
if (_texture != null)
{
#if AVPRO_WEBGL_USE_RENDERTEXTURE
RenderTexture.Destroy(_texture);
#else
Texture2D.Destroy(_texture);
AVPPlayerDestroyVideoTexture(_cachedTextureNativePtr.ToInt32());
#endif
_texture = null;
}
_cachedTextureNativePtr = System.IntPtr.Zero;
}
public override void Update()
{
if(_playerID >= 0) // CheckPlayer's index and update it
{
_playerIndex = AVPPlayerUpdatePlayerIndex(_playerID);
}
if(_playerIndex >= 0)
{
CheckTracksDirty();
UpdateTracks();
UpdateTextCue();
UpdateSubtitles();
UpdateLastErrorCode();
if (AVPPlayerReady(_playerIndex))
{
UpdateTimeRanges();
if (AVPPlayerHasVideo(_playerIndex))
{
_width = AVPPlayerWidth(_playerIndex);
_height = AVPPlayerHeight(_playerIndex);
if (_texture != null && (_texture.width != _width || _texture.height != _height))
{
DestroyTexture();
}
if (_texture == null && _width > 0 && _height > 0)
{
CreateTexture();
}
// Update the texture
if (_cachedTextureNativePtr != System.IntPtr.Zero)
{
// TODO: only update the texture when the frame count changes
// (actually this will break the update for certain browsers such as edge and possibly safari - Sunrise)
AVPPlayerFetchVideoTexture(_playerIndex, _cachedTextureNativePtr, false);
#if AVPRO_WEBGL_USE_RENDERTEXTURE
if (_texture.useMipMap)
{
_texture.GenerateMips();
}
#endif
}
UpdateDisplayFrameRate();
}
}
}
}
private void CheckTracksDirty()
{
_isDirtyVideoTracks = false;
_isDirtyAudioTracks = false;
_isDirtyTextTracks = false;
// TODO: replace this crude polling check with events, or only do it once metadataReady
// Need to add event support as tracks can be added via HTML (especially text)
int videoTrackCount = AVPPlayerGetVideoTrackCount(_playerIndex);
int audioTrackCount = AVPPlayerGetAudioTrackCount(_playerIndex);
int textTrackCount = AVPPlayerGetTextTrackCount(_playerIndex);
_isDirtyVideoTracks = (_cachedVideoTrackCount != videoTrackCount);
_isDirtyAudioTracks = (_cachedAudioTrackCount != audioTrackCount);
_isDirtyTextTracks = ( _cachedTextTrackCount != textTrackCount);
_cachedVideoTrackCount = videoTrackCount;
_cachedAudioTrackCount = audioTrackCount;
_cachedTextTrackCount = textTrackCount;
}
private void UpdateTimeRanges()
{
{
int rangeCount = AVPPlayerGetNumBufferedTimeRanges(_playerIndex);
if (rangeCount != _bufferedTimes.Count)
{
_bufferedTimes._ranges = new TimeRange[rangeCount];
}
for (int i = 0; i < rangeCount; i++)
{
double startTime = AVPPlayerGetTimeRangeStart(_playerIndex, i);
double endTime = AVPPlayerGetTimeRangeEnd(_playerIndex, i);
_bufferedTimes._ranges[i] = new TimeRange(startTime, endTime - startTime);
}
_bufferedTimes.CalculateRange();
}
{
double duration = GetDuration();
if (duration > 0.0)
{
_seekableTimes._ranges = new TimeRange[1];
_seekableTimes._ranges[0] = new TimeRange(0.0, duration);
}
else
{
_seekableTimes._ranges = new TimeRange[0];
}
_seekableTimes.CalculateRange();
}
}
public override void Dispose()
{
CloseMedia();
}
public override bool IsPlaybackStalled()
{
bool result = false;
if (_playerIndex > -1)
{
result = AVPPlayerIsPlaybackStalled(_playerIndex) && IsPlaying();
}
return result;
}
// Tracks
internal override int InternalGetTrackCount(TrackType trackType)
{
int result = 0;
switch (trackType)
{
case TrackType.Video:
result = AVPPlayerGetVideoTrackCount(_playerIndex);
break;
case TrackType.Audio:
result = AVPPlayerGetAudioTrackCount(_playerIndex);
break;
case TrackType.Text:
result = AVPPlayerGetTextTrackCount(_playerIndex);
break;
}
return result;
}
internal override bool InternalIsChangedTracks(TrackType trackType)
{
bool result = false;
switch (trackType)
{
case TrackType.Video:
result = _isDirtyVideoTracks;
break;
case TrackType.Audio:
result = _isDirtyAudioTracks;
break;
case TrackType.Text:
result = _isDirtyTextTracks;
break;
}
return result;
}
internal override bool InternalSetActiveTrack(TrackType trackType, int trackId)
{
bool result = false;
switch (trackType)
{
case TrackType.Video:
result = AVPPlayerSetActiveVideoTrack(_playerIndex, trackId);
break;
case TrackType.Audio:
result = AVPPlayerSetActiveAudioTrack(_playerIndex, trackId);
break;
case TrackType.Text:
result = AVPPlayerSetActiveTextTrack(_playerIndex, trackId);
break;
}
return result;
}
internal override TrackBase InternalGetTrackInfo(TrackType trackType, int trackIndex, ref bool isActiveTrack)
{
TrackBase result = null;
switch (trackType)
{
case TrackType.Video:
{
string trackName = AVPPlayerGetVideoTrackName(_playerIndex, trackIndex);
string trackLanguage = AVPPlayerGetVideoTrackLanguage(_playerIndex, trackIndex);
bool isActive = AVPPlayerIsVideoTrackActive(_playerIndex, trackIndex);
result = new VideoTrack(trackIndex, trackName, trackLanguage, isActive);
break;
}
case TrackType.Audio:
{
string trackName = AVPPlayerGetAudioTrackName(_playerIndex, trackIndex);
string trackLanguage = AVPPlayerGetAudioTrackLanguage(_playerIndex, trackIndex);
bool isActive = AVPPlayerIsAudioTrackActive(_playerIndex, trackIndex);
result = new AudioTrack(trackIndex, trackName, trackLanguage, isActive);
break;
}
case TrackType.Text:
{
string trackName = AVPPlayerGetTextTrackName(_playerIndex, trackIndex);
string trackLanguage = AVPPlayerGetTextTrackLanguage(_playerIndex, trackIndex);
bool isActive = AVPPlayerIsTextTrackActive(_playerIndex, trackIndex);
result = new TextTrack(trackIndex, trackName, trackLanguage, isActive);
break;
}
}
return result;
}
// Text Cue stub methods
internal override bool InternalIsChangedTextCue() { return false; }
internal override string InternalGetCurrentTextCue() { return string.Empty; }
}
}
#endif
@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: c044ff13d5570e64a8156bc718b3cfec
timeCreated: 1468230219
licenseType: Pro
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 6f3c954eb61392a4193295a8376bd8db
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
@@ -0,0 +1,322 @@
// NOTE: We only allow this script to compile in editor so we can easily check for compilation issues
#if (UNITY_EDITOR || (UNITY_STANDALONE_WIN || UNITY_WSA_10_0))
#define AVPROVIDEO_FIXREGRESSION_TEXTUREQUALITY_UNITY542
#if UNITY_WSA_10 || ENABLE_IL2CPP
#define AVPROVIDEO_MARSHAL_RETURN_BOOL
#endif
#if UNITY_2019_3_OR_NEWER && !UNITY_2020_1_OR_NEWER
#define AVPROVIDEO_FIX_UPDATEEXTERNALTEXTURE_LEAK
#endif
using UnityEngine;
using System.Runtime.InteropServices;
using System.Collections.Generic;
using System;
using System.Text;
#if NETFX_CORE
using Windows.Storage.Streams;
#endif
//-----------------------------------------------------------------------------
// Copyright 2015-2021 RenderHeads Ltd. All rights reserved.
//-----------------------------------------------------------------------------
namespace RenderHeads.Media.AVProVideo
{
/// <summary>
/// Windows desktop and UWP implementation of BaseMediaPlayer
/// </summary>
public /*sealed*/ partial class WindowsMediaPlayer : BaseMediaPlayer
{
#region IBufferedDisplay Implementation
private BufferedFrameSelectionMode _frameSelectionMode = BufferedFrameSelectionMode.None;
private bool _pauseOnPrerollComplete = false;
private IBufferedDisplay _masterDisplay;
private IBufferedDisplay[] _slaveDisplays;
private double _displayClockTime = 0.0;
private double _timeAccumulation = 0.0;
private bool _needsInitialFrame = true;
private void FlushFrameBuffering(bool releaseTexture)
{
if (_frameSelectionMode == BufferedFrameSelectionMode.None) return;
if (releaseTexture && _textureFrame.internalNativePointer != System.IntPtr.Zero)
{
Native.UnlockTextureFrame(_instance, ref _textureFrame);
_textureFrame.internalNativePointer = System.IntPtr.Zero;
_textureFrame.texturePointer = System.IntPtr.Zero;
}
Native.FlushFrameBuffering(_instance);
// Native _pauseOnPrerollComplete needs to be reset
//Native.SetFrameBufferingEnabled(_instance, (_frameSelectionMode != BufferedFrameSelectionMode.None), _pauseOnPrerollComplete);
_needsInitialFrame = true;
_timeAccumulation = 0.0;
}
internal override long InternalUpdateBufferedDisplay()
{
BufferedFramesState state = GetBufferedFramesState();
if (state.bufferedFrameCount > 0)
{
if (_frameSelectionMode == BufferedFrameSelectionMode.NewestFrame)
{
SetBufferedDisplayTime(_frameSelectionMode, -1, false);
}
else if (_frameSelectionMode == BufferedFrameSelectionMode.OldestFrame)
{
SetBufferedDisplayTime(_frameSelectionMode, -1, false);
}
else if (_frameSelectionMode == BufferedFrameSelectionMode.ElapsedTime ||
_frameSelectionMode == BufferedFrameSelectionMode.ElapsedTimeVsynced)
{
// Only start consuming frames on these conditions
bool needsInitialFrame = (_textureFrame.texturePointer == System.IntPtr.Zero || _needsInitialFrame);
bool playingBufferedFrames = (IsPrerollComplete() && (IsPlaying() || (!IsPlaying() && IsFinished())));
if (needsInitialFrame || playingBufferedFrames)
{
if (needsInitialFrame)
{
if (SetBufferedDisplayTime(BufferedFrameSelectionMode.OldestFrame, -1, true))
{
_displayClockTime = _textureFrame.timeStamp;
_needsInitialFrame = false;
}
}
else
{
// TODO: run without vsync, just show next frame (use media clock for present?)
// use our own clock...
const double SecondsToHNS = 10000000.0;
double videoFrameDuration = SecondsToHNS / (double)GetVideoFrameRate();
long videoDuration = (long)Math.Floor(SecondsToHNS * GetDuration());
long lastFrameTime = Math.Max(videoDuration, state.maxTimeStamp);
double delta = SecondsToHNS * Time.deltaTime;
if (_frameSelectionMode == BufferedFrameSelectionMode.ElapsedTimeVsynced && QualitySettings.vSyncCount > 0)
{
#if UNITY_2022_2_OR_NEWER
double refreshRate = Screen.currentResolution.refreshRateRatio.value;
#else
double refreshRate = (double)( Screen.currentResolution.refreshRate );
#endif
// Since we're running with vsync enabled, the MINIMUM elapsed time will be 1 monitor refresh (multiplied by QualitySettings.vSyncCount)
double monitorDuration = (QualitySettings.vSyncCount * SecondsToHNS) / refreshRate;
int wholeFrames = (int)System.Math.Floor(_timeAccumulation / monitorDuration);
wholeFrames = System.Math.Max(1, wholeFrames);
delta = monitorDuration * wholeFrames;
//LogBufferState();
if (wholeFrames > 1)
{
//Debug.Log(Time.frameCount + "] " + Time.deltaTime + " " + wholeFrames + " " + _timeAccumulation + " " + _timeAccumulation / SecondsToHNS);
//LogBufferState();
}
_timeAccumulation += (Time.deltaTime * SecondsToHNS) - delta;
//delta = monitorDuration;
/*double actualFrameDuration = Time.deltaTime * SecondsToHNS;
double idealFrameTimeDifference = (actualFrameDuration);// - minMonitorDuration);
if (idealFrameTimeDifference > (minMonitorDuration / 2))
{
int droppedFrames = (int)Math.Round(idealFrameTimeDifference / minMonitorDuration);
//Debug.Log(Time.maximumDeltaTime + " " + Time.deltaTime + " " + actualFrameDuration + " " + idealFrameTimeDifference + " = " + droppedFrames);
delta += minMonitorDuration * droppedFrames;
//LogBufferState();
}
else
{
//Debug.Log(Time.deltaTime);
}
// If we're running slower than this or there is a frame drop, the elapsed time will be a multiple
// of the monitor refresh rate
*/
}
_displayClockTime += delta;
int multiple = (int)videoFrameDuration;
long snappedFrameTime = (long)Math.Floor(_displayClockTime / multiple) * multiple;
if (_isLooping && snappedFrameTime > lastFrameTime)
{
snappedFrameTime %= lastFrameTime;
_needsInitialFrame = true;
}
else
{
snappedFrameTime = Math.Min(snappedFrameTime, lastFrameTime);
}
if (System.Math.Abs(snappedFrameTime - _textureFrame.timeStamp) > 1000)
{
//Debug.Log("1 " + _displayClockTime + " > " + snappedFrameTime + " d:" + delta);
//LogBufferState();
if (_needsInitialFrame)
{
if (SetBufferedDisplayTime(BufferedFrameSelectionMode.OldestFrame, -1, true))
{
_displayClockTime = _textureFrame.timeStamp;
//Debug.Log("initial: " + _displayClockTime);
_needsInitialFrame = false;
}
}
else if (!SetBufferedDisplayTime(_frameSelectionMode, snappedFrameTime, false))
{
//Debug.LogWarning("[AVProVideo] failed to set time at " + snappedFrameTime);
//LogBufferState();
// Try to snap to oldest buffered time
_displayClockTime = (state.minTimeStamp + state.maxTimeStamp) / 2.0;
snappedFrameTime = (long)Math.Floor(_displayClockTime / multiple) * multiple;
if (_isLooping && snappedFrameTime > lastFrameTime)
{
snappedFrameTime %= lastFrameTime;
}
else
{
snappedFrameTime = Math.Min(snappedFrameTime, lastFrameTime);
}
if (SetBufferedDisplayTime(BufferedFrameSelectionMode.FromExternalTimeClosest, snappedFrameTime, false))
{
_displayClockTime = _textureFrame.timeStamp;
//Debug.LogWarning("[AVProVideo] Good set: " + _displayClockTime);
}
else
{
//Debug.LogWarning("[AVProVideo] Failed to display frame time " + snappedFrameTime);
//LogBufferState();
}
}
}
}
}
}
else if (_frameSelectionMode == BufferedFrameSelectionMode.FromExternalTime)
{
if (_masterDisplay != null)
{
// Use the time from the master
long timeStamp = _masterDisplay.UpdateBufferedDisplay();
if (timeStamp != GetTextureTimeStamp())
{
if (!SetBufferedDisplayTime(BufferedFrameSelectionMode.FromExternalTimeClosest, timeStamp, false))
{
Debug.LogWarning("[AVProVideo] Failed to display frame using external clock at time " + timeStamp);
}
}
}
}
}
return GetTextureTimeStamp();
}
private void LogBufferState()
{
BufferedFramesState state = GetBufferedFramesState();
long timeStamp = GetTextureTimeStamp();
string result = string.Format("[AVProVideo] {4} - {2},{3}\t\t{0}-{1} ({5})", state.minTimeStamp, state.maxTimeStamp, state.bufferedFrameCount, state.freeFrameCount, timeStamp, Time.deltaTime);
Debug.Log(result);
}
private bool SetBufferedDisplayTime(BufferedFrameSelectionMode mode, long timeOfDesiredFrameToDisplay, bool ignorePreroll)
{
bool result = false;
//if (!_isPaused)
{
result = Native.LockTextureFrame(_instance, mode, timeOfDesiredFrameToDisplay, ref _textureFrame, ignorePreroll);
}
return result;
}
public override BufferedFramesState GetBufferedFramesState()
{
BufferedFramesState state = new BufferedFramesState();
Native.GetBufferedFramesState(_instance, ref state);
return state;
}
public override void SetBufferedDisplayMode(BufferedFrameSelectionMode mode, IBufferedDisplay master = null)
{
_frameSelectionMode = mode;
_masterDisplay = master;
UpdateBufferedDisplay();
}
public override void SetBufferedDisplayOptions(bool pauseOnPrerollComplete)
{
_pauseOnPrerollComplete = pauseOnPrerollComplete;
Native.SetFrameBufferingEnabled(_instance, (_frameSelectionMode != BufferedFrameSelectionMode.None), _pauseOnPrerollComplete);
}
public override void SetSlaves(IBufferedDisplay[] slaves)
{
foreach (IBufferedDisplay slave in slaves)
{
slave.SetBufferedDisplayMode(BufferedFrameSelectionMode.FromExternalTime, this);
}
_slaveDisplays = slaves;
}
private bool IsPrerollComplete()
{
bool result = true;
if (GetBufferedFramesState().prerolledCount <= 0)
{
result = false;
}
if (_slaveDisplays != null && result)
{
foreach (IBufferedDisplay slave in _slaveDisplays)
{
if (slave.GetBufferedFramesState().prerolledCount <= 0)
{
result = false;
break;
}
}
}
return result;
}
private partial struct Native
{
[DllImport("AVProVideo")]
public static extern bool GetBufferedFramesState(System.IntPtr playerInstance, ref BufferedFramesState state);
[DllImport("AVProVideo")]
#if AVPROVIDEO_MARSHAL_RETURN_BOOL
[return: MarshalAs(UnmanagedType.I1)]
#endif
public static extern bool LockTextureFrame(System.IntPtr instance, BufferedFrameSelectionMode mode, long time, ref TextureFrame textureFrame, bool ignorePreroll);
[DllImport("AVProVideo")]
public static extern void UnlockTextureFrame(System.IntPtr instance, ref TextureFrame textureFrame);
[DllImport("AVProVideo")]
public static extern void ReleaseTextureFrame(System.IntPtr instance, ref TextureFrame textureFrame);
[DllImport("AVProVideo")]
public static extern void FlushFrameBuffering(System.IntPtr instance);
}
#endregion // IBufferedDisplay Implementation
}
}
#endif
@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 2b36cc2d6962ce34e86c5a83a0de6d4a
timeCreated: 1630292296
licenseType: Pro
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,961 @@
// NOTE: We only allow this script to compile in editor so we can easily check for compilation issues
#if (UNITY_EDITOR || (UNITY_STANDALONE_WIN || UNITY_WSA_10_0))
#if UNITY_WSA_10 || ENABLE_IL2CPP
#define AVPROVIDEO_MARSHAL_RETURN_BOOL
#endif
using UnityEngine;
using System.Runtime.InteropServices;
using System.Collections.Generic;
using System;
using System.Text;
//-----------------------------------------------------------------------------
// Copyright 2018-2021 RenderHeads Ltd. All rights reserved.
//-----------------------------------------------------------------------------
namespace RenderHeads.Media.AVProVideo
{
public enum PlaybackState
{
None = 0,
Opening = 1,
Buffering = 2, // Replace with Stalled and add Buffering as State 64??
Playing = 3,
Paused = 4,
StateMask = 7,
Seeking = 32,
}
public class AuthData
{
public string URL { get; set; }
public string Token { get; set; }
public byte[] KeyBytes { get; set; }
public AuthData()
{
Clear();
}
public void Clear()
{
URL = string.Empty;
Token = string.Empty;
KeyBytes = null;
}
public string KeyBase64
{
get
{
if (KeyBytes != null)
{
return System.Convert.ToBase64String(KeyBytes);
}
else
{
return string.Empty;
}
}
set
{
if (value != null)
{
KeyBytes = System.Convert.FromBase64String(value);
}
else
{
KeyBytes = null;
}
}
}
};
public partial class WindowsRtMediaPlayer : BaseMediaPlayer
{
private bool _isMediaLoaded = false;
private bool _use10BitTextures = false;
private bool _useLowLiveLatency = false;
public WindowsRtMediaPlayer(MediaPlayer.OptionsWindows options) : base()
{
_playerDescription = "WinRT";
_use10BitTextures = options.use10BitTextures;
_useLowLiveLatency = options.useLowLiveLatency;
for (int i = 0; i < _eyeTextures.Length; i++)
{
_eyeTextures[i] = new EyeTexture();
}
}
public WindowsRtMediaPlayer(MediaPlayer.OptionsWindowsUWP options) : base()
{
_playerDescription = "WinRT";
_use10BitTextures = options.use10BitTextures;
_useLowLiveLatency = options.useLowLiveLatency;
for (int i = 0; i < _eyeTextures.Length; i++)
{
_eyeTextures[i] = new EyeTexture();
}
}
public override bool CanPlay()
{
return HasMetaData();
}
public override void Dispose()
{
CloseMedia();
if (_playerInstance != System.IntPtr.Zero)
{
Native.DestroyPlayer(_playerInstance); _playerInstance = System.IntPtr.Zero;
Native.IssueRenderThreadEvent_FreeAllTextures();
}
for (int i = 0; i < _eyeTextures.Length; i++)
{
_eyeTextures[i].Dispose();
}
}
public override bool PlayerSupportsLinearColorSpace()
{
// The current player doesn't support rendering to SRGB textures
return false;
}
public override double GetCurrentTime()
{
return Native.GetCurrentPosition(_playerInstance);
}
public override double GetDuration()
{
return Native.GetDuration(_playerInstance);
}
public override float GetPlaybackRate()
{
return Native.GetPlaybackRate(_playerInstance);
}
public override Texture GetTexture(int index = 0)
{
Texture result = null;
if (_frameTimeStamp > 0 && index < _eyeTextures.Length)
{
result = _eyeTextures[index].texture;
}
return result;
}
public override int GetTextureCount()
{
if (_eyeTextures[1].texture != null)
{
return 2;
}
return 1;
}
public override int GetTextureFrameCount()
{
return (int)_frameTimeStamp;
}
internal override StereoPacking InternalGetTextureStereoPacking()
{
return Native.GetStereoPacking(_playerInstance);
}
public override string GetVersion()
{
return _version;
}
public override string GetExpectedVersion()
{
return Helper.ExpectedPluginVersion.WinRT;
}
public override float GetVideoFrameRate()
{
float result = 0f;
Native.VideoTrack videoTrack;
if (Native.GetActiveVideoTrackInfo(_playerInstance, out videoTrack))
{
result = videoTrack.frameRate;
}
return result;
}
public override int GetVideoWidth()
{
int result = 0;
if (_eyeTextures[0].texture)
{
result = _eyeTextures[0].texture.width;
}
return result;
}
public override int GetVideoHeight()
{
int result = 0;
if (_eyeTextures[0].texture)
{
result = _eyeTextures[0].texture.height;
}
return result;
}
public override float GetVolume()
{
return Native.GetAudioVolume(_playerInstance);
}
public override void SetBalance(float balance)
{
Native.SetAudioBalance(_playerInstance, balance);
}
public override float GetBalance()
{
return Native.GetAudioBalance(_playerInstance);
}
public override bool HasAudio()
{
return _audioTracks.Count > 0;
}
public override bool HasMetaData()
{
return Native.GetDuration(_playerInstance) > 0f;
}
public override bool HasVideo()
{
return _videoTracks.Count > 0;
}
public override bool IsBuffering()
{
return ((Native.GetPlaybackState(_playerInstance) & PlaybackState.StateMask) == PlaybackState.Buffering);
}
public override bool IsFinished()
{
bool result = false;
if (IsPaused() && !IsSeeking() && GetCurrentTime() >= GetDuration())
{
result = true;
}
return result;
}
public override bool IsLooping()
{
return Native.IsLooping(_playerInstance);
}
public override bool IsMuted()
{
return Native.IsAudioMuted(_playerInstance);
}
public override bool IsPaused()
{
return ((Native.GetPlaybackState(_playerInstance) & PlaybackState.StateMask) == PlaybackState.Paused);
}
public override bool IsPlaying()
{
return ((Native.GetPlaybackState(_playerInstance) & PlaybackState.StateMask) == PlaybackState.Playing);
}
public override bool IsSeeking()
{
return ((Native.GetPlaybackState(_playerInstance) & PlaybackState.Seeking) != 0);
}
public override void MuteAudio(bool bMuted)
{
Native.SetAudioMuted(_playerInstance, bMuted);
}
// TODO: replace all these options with a structure
public override bool OpenMedia(string path, long offset, string httpHeader, MediaHints mediaHints, int forceFileFormat = 0, bool startWithHighestBitrate = false)
{
bool result = false;
CloseMedia();
if (_playerInstance == System.IntPtr.Zero)
{
_playerInstance = Native.CreatePlayer();
// Force setting any auth data as it wouldn't have been set without a _playerInstance
AuthenticationData = _nextAuthData;
}
if (_playerInstance != System.IntPtr.Zero)
{
result = Native.OpenMedia(_playerInstance, path, httpHeader, (FileFormat)forceFileFormat, startWithHighestBitrate, _use10BitTextures);
if (result)
{
if (_useLowLiveLatency)
{
Native.SetLiveOffset(_playerInstance, 0.0);
}
}
_mediaHints = mediaHints;
}
return result;
}
public override void CloseMedia()
{
// NOTE: This unloads the current video, but the texture should remain
_isMediaLoaded = false;
Native.CloseMedia(_playerInstance);
base.CloseMedia();
}
public override void Pause()
{
Native.Pause(_playerInstance);
}
public override void Play()
{
Native.Play(_playerInstance);
}
public override void Render()
{
Native.IssueRenderThreadEvent_UpdateAllTextures();
}
private void Update_Textures()
{
// See if there is a new frame ready
{
System.IntPtr texturePointerLeft = System.IntPtr.Zero;
System.IntPtr texturePointerRight = System.IntPtr.Zero;
ulong frameTimeStamp = 0;
int width, height;
if (Native.GetLatestFrame(_playerInstance, out texturePointerLeft, out texturePointerRight, out frameTimeStamp, out width, out height))
{
bool isFrameUpdated = false;
bool isNewFrameTime = (frameTimeStamp > _frameTimeStamp) || (_frameTimeStamp == 0 && frameTimeStamp == 0);
for (int i = 0; i < _eyeTextures.Length; i++)
{
EyeTexture eyeTexture = _eyeTextures[i];
System.IntPtr texturePointer = texturePointerLeft;
if (i == 1)
{
texturePointer = texturePointerRight;
}
bool isNewFrameSpecs = (eyeTexture.texture != null && (texturePointer == IntPtr.Zero || eyeTexture.texture.width != width || eyeTexture.texture.height != height));
//Debug.Log("tex? " + i + " " + width + " " + height + " " + (eyeTexture.texture != null) + " " + texturePointer.ToString() + " " + frameTimeStamp);
// Check whether the latest frame is newer than the one we got last time
if (isNewFrameTime || isNewFrameSpecs)
{
if (isNewFrameSpecs)
{
eyeTexture.Dispose();
// TODO: blit from the old texture to the new texture before destroying?
}
/// Switch to the latest texture pointer
if (eyeTexture.texture != null)
{
// TODO: check whether UpdateExternalTexture resets the sampling filter to POINT - it seems to in Unity 5.6.6
if (eyeTexture.nativePointer != texturePointer)
{
eyeTexture.texture.UpdateExternalTexture(texturePointer);
eyeTexture.nativePointer = texturePointer;
}
}
else
{
if (texturePointer != IntPtr.Zero)
{
eyeTexture.texture = Texture2D.CreateExternalTexture(width, height, TextureFormat.BGRA32, false, false, texturePointer);
if (eyeTexture.texture != null)
{
eyeTexture.texture.name = "AVProVideo";
eyeTexture.nativePointer = texturePointer;
ApplyTextureProperties(eyeTexture.texture);
}
else
{
Debug.LogError("[AVProVideo] Failed to create texture");
}
}
}
isFrameUpdated = true;
}
}
if (isFrameUpdated)
{
_frameTimeStamp = frameTimeStamp;
}
}
}
}
private AuthData _nextAuthData = new AuthData();
public AuthData AuthenticationData
{
get
{
return _nextAuthData;
}
set
{
_nextAuthData = value;
Native.SetNextAuthData(_playerInstance, _nextAuthData);
}
}
public override bool RequiresVerticalFlip()
{
return true;
}
public override void Seek(double time)
{
Native.SeekParams seekParams = new Native.SeekParams();
seekParams.timeSeconds = time;
seekParams.mode = Native.SeekMode.Accurate;
Native.Seek(_playerInstance, ref seekParams);
}
public override void SeekFast(double time)
{
// Keyframe seeking is not supported on this platform
Seek(time);
}
public override void SetLooping(bool bLooping)
{
Native.SetLooping(_playerInstance, bLooping);
}
public override void SetPlaybackRate(float rate)
{
// Clamp rate as WinRT doesn't seem to be able to handle negative rate
rate = Mathf.Max(0f, rate);
Native.SetPlaybackRate(_playerInstance, rate);
}
public override void SetVolume(float volume)
{
Native.SetAudioVolume(_playerInstance, volume);
}
public override void Stop()
{
Pause();
}
private void UpdateTimeRanges()
{
UpdateTimeRange(ref _seekableTimes._ranges, Native.TimeRangeTypes.Seekable);
UpdateTimeRange(ref _bufferedTimes._ranges, Native.TimeRangeTypes.Buffered);
_seekableTimes.CalculateRange();
_bufferedTimes.CalculateRange();
}
private void UpdateTimeRange(ref TimeRange[] range, Native.TimeRangeTypes timeRangeType)
{
int newCount = Native.GetTimeRanges(_playerInstance, range, range.Length, timeRangeType);
if (newCount != range.Length)
{
range = new TimeRange[newCount];
Native.GetTimeRanges(_playerInstance, range, range.Length, timeRangeType);
}
}
public override System.DateTime GetProgramDateTime()
{
double seconds = Native.GetCurrentDateTimeSecondsSince1970(_playerInstance);
return Helper.ConvertSecondsSince1970ToDateTime(seconds);
}
public override void Update()
{
Native.Update(_playerInstance);
UpdateTracks();
UpdateTextCue();
_lastError = (ErrorCode)Native.GetLastErrorCode(_playerInstance);
UpdateTimeRanges();
UpdateSubtitles();
Update_Textures();
UpdateDisplayFrameRate();
if (!_isMediaLoaded)
{
if (HasVideo() && _eyeTextures[0].texture != null)
{
Native.VideoTrack videoTrack;
if (Native.GetActiveVideoTrackInfo(_playerInstance, out videoTrack))
{
Helper.LogInfo("Using playback path: " + _playerDescription + " (" + videoTrack.frameWidth + "x" + videoTrack.frameHeight + "@" + videoTrack.frameRate.ToString("F2") + ")");
_isMediaLoaded = true;
}
}
else if (HasAudio() && !HasVideo())
{
Helper.LogInfo("Using playback path: " + _playerDescription);
_isMediaLoaded = true;
}
}
}
/*public override void SetKeyServerURL(string url)
{
_nextAuthData.URL = url;
AuthenticationData = _nextAuthData;
}*/
public override void SetKeyServerAuthToken(string token)
{
_nextAuthData.Token = token;
AuthenticationData = _nextAuthData;
}
public override void SetOverrideDecryptionKey(byte[] key)
{
_nextAuthData.KeyBytes = key;
AuthenticationData = _nextAuthData;
}
}
// Tracks
public sealed partial class WindowsRtMediaPlayer
{
internal override bool InternalSetActiveTrack(TrackType trackType, int trackUid)
{
return Native.SetActiveTrack(_playerInstance, trackType, trackUid);
}
// Has it changed since the last frame 'tick'
internal override bool InternalIsChangedTracks(TrackType trackType)
{
return Native.IsChangedTracks(_playerInstance, trackType);
}
internal override int InternalGetTrackCount(TrackType trackType)
{
return Native.GetTrackCount(_playerInstance, trackType);
}
internal override TrackBase InternalGetTrackInfo(TrackType trackType, int trackIndex, ref bool isActiveTrack)
{
TrackBase result = null;
StringBuilder name = new StringBuilder(128);
StringBuilder language = new StringBuilder(16);
int uid = -1;
if (Native.GetTrackInfo(_playerInstance, trackType, trackIndex, ref uid, ref isActiveTrack, name, name.Capacity, language, language.Capacity))
{
if (trackType == TrackType.Video)
{
result = new VideoTrack(uid, name.ToString(), language.ToString(), false);
}
else if (trackType == TrackType.Audio)
{
result = new AudioTrack(uid, name.ToString(), language.ToString(), false);
}
else if (trackType == TrackType.Text)
{
result = new TextTrack(uid, name.ToString(), language.ToString(), false);
}
}
return result;
}
private partial struct Native
{
[DllImport("AVProVideoWinRT")]
[return: MarshalAs(UnmanagedType.I1)]
public static extern bool IsChangedTracks(System.IntPtr instance, TrackType trackType);
[DllImport("AVProVideoWinRT")]
public static extern int GetTrackCount(System.IntPtr instance, TrackType trackType);
[DllImport("AVProVideoWinRT")]
[return: MarshalAs(UnmanagedType.I1)]
public static extern bool GetTrackInfo(System.IntPtr instance, TrackType trackType, int index, ref int uid,
ref bool isActive,
[MarshalAs(UnmanagedType.LPWStr)] StringBuilder name, int maxNameLength,
[MarshalAs(UnmanagedType.LPWStr)] StringBuilder language, int maxLanguageLength);
[DllImport("AVProVideoWinRT")]
[return: MarshalAs(UnmanagedType.I1)]
public static extern bool SetActiveTrack(System.IntPtr instance, TrackType trackType, int trackUid);
}
}
// Text Cue
public sealed partial class WindowsRtMediaPlayer
{
// Has it changed since the last frame 'tick'
internal override bool InternalIsChangedTextCue()
{
return Native.IsChangedTextCue(_playerInstance);
}
internal override string InternalGetCurrentTextCue()
{
string result = null;
System.IntPtr ptr = Native.GetCurrentTextCue(_playerInstance);
if (ptr != System.IntPtr.Zero)
{
result = System.Runtime.InteropServices.Marshal.PtrToStringUni(ptr);
}
return result;
}
private partial struct Native
{
[DllImport("AVProVideoWinRT")]
[return: MarshalAs(UnmanagedType.I1)]
public static extern bool IsChangedTextCue(System.IntPtr instance);
[DllImport("AVProVideoWinRT")]
public static extern System.IntPtr GetCurrentTextCue(System.IntPtr instance);
}
}
public sealed partial class WindowsRtMediaPlayer
{
private partial struct Native
{
[DllImport("AVProVideoWinRT", EntryPoint = "GetPluginVersion")]
private static extern System.IntPtr GetPluginVersionStringPointer();
public static string GetPluginVersion()
{
return System.Runtime.InteropServices.Marshal.PtrToStringAnsi(GetPluginVersionStringPointer());
}
[DllImport("AVProVideoWinRT")]
public static extern System.IntPtr CreatePlayer();
[DllImport("AVProVideoWinRT")]
public static extern void DestroyPlayer(System.IntPtr playerInstance);
[DllImport("AVProVideoWinRT")]
#if AVPROVIDEO_MARSHAL_RETURN_BOOL
[return: MarshalAs(UnmanagedType.I1)]
#endif
public static extern bool OpenMedia(System.IntPtr playerInstance, [MarshalAs(UnmanagedType.LPWStr)] string filePath,
[MarshalAs(UnmanagedType.LPWStr)] string httpHeader, FileFormat overrideFileFormat,
bool startWithHighestBitrate, bool use10BitTextures);
[DllImport("AVProVideoWinRT")]
public static extern void CloseMedia(System.IntPtr playerInstance);
[DllImport("AVProVideoWinRT")]
public static extern void Pause(System.IntPtr playerInstance);
[DllImport("AVProVideoWinRT")]
public static extern void Play(System.IntPtr playerInstance);
[DllImport("AVProVideoWinRT")]
public static extern void SetAudioVolume(System.IntPtr playerInstance, float volume);
[DllImport("AVProVideoWinRT")]
public static extern void SetAudioBalance(System.IntPtr playerInstance, float balance);
[DllImport("AVProVideoWinRT")]
public static extern void SetPlaybackRate(System.IntPtr playerInstance, float rate);
[DllImport("AVProVideoWinRT")]
public static extern void SetAudioMuted(System.IntPtr playerInstance, bool muted);
[DllImport("AVProVideoWinRT")]
public static extern float GetAudioVolume(System.IntPtr playerInstance);
[DllImport("AVProVideoWinRT")]
#if AVPROVIDEO_MARSHAL_RETURN_BOOL
[return: MarshalAs(UnmanagedType.I1)]
#endif
public static extern bool IsAudioMuted(System.IntPtr playerInstance);
[DllImport("AVProVideoWinRT")]
public static extern float GetAudioBalance(System.IntPtr playerInstance);
[DllImport("AVProVideoWinRT")]
public static extern float GetPlaybackRate(System.IntPtr playerInstance);
[DllImport("AVProVideoWinRT")]
public static extern void SetLooping(System.IntPtr playerInstance, bool looping);
[DllImport("AVProVideoWinRT")]
#if AVPROVIDEO_MARSHAL_RETURN_BOOL
[return: MarshalAs(UnmanagedType.I1)]
#endif
public static extern bool IsLooping(System.IntPtr playerInstance);
[DllImport("AVProVideoWinRT")]
public static extern int GetLastErrorCode(System.IntPtr playerInstance);
[DllImport("AVProVideoWinRT")]
public static extern void Update(System.IntPtr playerInstance);
[DllImport("AVProVideoWinRT")]
public static extern double GetDuration(System.IntPtr playerInstance);
[DllImport("AVProVideoWinRT")]
public static extern StereoPacking GetStereoPacking(System.IntPtr playerInstance);
[DllImport("AVProVideoWinRT")]
public static extern double GetCurrentPosition(System.IntPtr playerInstance);
[DllImport("AVProVideoWinRT")]
#if AVPROVIDEO_MARSHAL_RETURN_BOOL
[return: MarshalAs(UnmanagedType.I1)]
#endif
public static extern bool GetLatestFrame(System.IntPtr playerInstance, out System.IntPtr leftEyeTexturePointer, out System.IntPtr rightEyeTexturePointer, out ulong frameTimeStamp, out int width, out int height);
[DllImport("AVProVideoWinRT")]
public static extern PlaybackState GetPlaybackState(System.IntPtr playerInstance);
[DllImport("AVProVideoWinRT")]
#if AVPROVIDEO_MARSHAL_RETURN_BOOL
[return: MarshalAs(UnmanagedType.I1)]
#endif
public static extern bool GetActiveVideoTrackInfo(System.IntPtr playerInstance, out VideoTrack videoTrack);
[DllImport("AVProVideoWinRT")]
#if AVPROVIDEO_MARSHAL_RETURN_BOOL
[return: MarshalAs(UnmanagedType.I1)]
#endif
public static extern bool GetActiveAudioTrackInfo(System.IntPtr playerInstance, out AudioTrack audioTrack);
[DllImport("AVProVideoWinRT")]
public static extern double GetCurrentDateTimeSecondsSince1970(System.IntPtr playerInstance);
[DllImport("AVProVideoWinRT")]
public static extern void SetLiveOffset(System.IntPtr playerInstance, double seconds);
[DllImport("AVProVideoWinRT")]
public static extern void DebugValues(System.IntPtr playerInstance, out int isD3D, out int isUnityD3D, out int isTexture, out int isSharedTexture, out int isSurface);
public enum SeekMode
{
Fast = 0,
Accurate = 1,
// TODO: Add Fast_Before and Fast_After
}
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct VideoTrack
{
public int trackIndex;
public int frameWidth;
public int frameHeight;
public float frameRate;
public uint averageBitRate;
//public string trackName;
// TODO: add index, language, name, bitrate, codec etc
}
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct AudioTrack
{
public int trackIndex;
public uint channelCount;
public uint sampleRate;
public uint bitsPerSample;
public uint averageBitRate;
//public string trackName;
// TODO: add index, language, name, bitrate, codec etc
}
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct SeekParams
{
public double timeSeconds;
public SeekMode mode;
// TODO: add min-max thresholds
}
[DllImport("AVProVideoWinRT")]
public static extern void Seek(System.IntPtr playerInstance, ref SeekParams seekParams);
public static void SetNextAuthData(System.IntPtr playerInstance, RenderHeads.Media.AVProVideo.AuthData srcAuthData)
{
Native.AuthData ad = new Native.AuthData();
ad.url = string.IsNullOrEmpty(srcAuthData.URL) ? null : srcAuthData.URL;
ad.token = string.IsNullOrEmpty(srcAuthData.Token) ? null : srcAuthData.Token;
if (srcAuthData.KeyBytes != null && srcAuthData.KeyBytes.Length > 0)
{
ad.keyBytes = Marshal.AllocHGlobal(srcAuthData.KeyBytes.Length);
Marshal.Copy(srcAuthData.KeyBytes, 0, ad.keyBytes, srcAuthData.KeyBytes.Length);
ad.keyBytesLength = srcAuthData.KeyBytes.Length;
}
else
{
ad.keyBytes = System.IntPtr.Zero;
ad.keyBytesLength = 0;
}
SetNextAuthData(playerInstance, ref ad);
if (ad.keyBytes != System.IntPtr.Zero)
{
Marshal.FreeHGlobal(ad.keyBytes);
}
}
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct AuthData
{
[MarshalAs(UnmanagedType.LPWStr)]
public string url;
[MarshalAs(UnmanagedType.LPWStr)]
public string token;
public System.IntPtr keyBytes;
public int keyBytesLength;
};
[DllImport("AVProVideoWinRT")]
private static extern void SetNextAuthData(System.IntPtr playerInstance, ref AuthData authData);
internal enum TimeRangeTypes
{
Seekable = 0,
Buffered = 1,
}
[DllImport("AVProVideoWinRT")]
public static extern int GetTimeRanges(System.IntPtr playerInstance, [Out, MarshalAs(UnmanagedType.LPArray, SizeParamIndex=2)] TimeRange[] ranges, int rangeCount, TimeRangeTypes timeRangeType);
// RJT TODO: Clean this up to better match non-WinRT
[DllImport("AVProVideoWinRT")]
public static extern System.IntPtr GetRenderEventFunc();
private static System.IntPtr _nativeFunction_UnityRenderEvent;
public static void IssueRenderThreadEvent_UpdateAllTextures()
{
if (_nativeFunction_UnityRenderEvent == System.IntPtr.Zero)
{
_nativeFunction_UnityRenderEvent = Native.GetRenderEventFunc();
}
if (_nativeFunction_UnityRenderEvent != System.IntPtr.Zero)
{
GL.IssuePluginEvent(_nativeFunction_UnityRenderEvent, /*(int)Native.RenderThreadEvent.UpdateAllTextures*/1);
}
}
public static void IssueRenderThreadEvent_FreeAllTextures()
{
if (_nativeFunction_UnityRenderEvent == System.IntPtr.Zero)
{
_nativeFunction_UnityRenderEvent = Native.GetRenderEventFunc();
}
if (_nativeFunction_UnityRenderEvent != System.IntPtr.Zero)
{
GL.IssuePluginEvent(_nativeFunction_UnityRenderEvent, /*(int)Native.RenderThreadEvent.FreeTextures*/2);
}
}
}
}
public sealed partial class WindowsRtMediaPlayer
{
private static bool _isInitialised = false;
private static string _version = "Plug-in not yet initialised";
private ulong _frameTimeStamp;
private System.IntPtr _playerInstance;
class EyeTexture
{
public Texture2D texture = null;
public System.IntPtr nativePointer = System.IntPtr.Zero;
public void Dispose()
{
if (texture)
{
if (Application.isPlaying) { Texture2D.Destroy(texture); }
else { Texture2D.DestroyImmediate(texture); }
texture = null;
}
nativePointer = System.IntPtr.Zero;
}
}
private EyeTexture[] _eyeTextures = new EyeTexture[2];
public static bool InitialisePlatform()
{
if (!_isInitialised)
{
try
{
#if !UNITY_2019_3_OR_NEWER
if (SystemInfo.graphicsDeviceType == UnityEngine.Rendering.GraphicsDeviceType.Direct3D12)
{
Debug.LogError("[AVProVideo] Direct3D 12 is not supported until Unity 2019.3");
return false;
}
#endif
if (SystemInfo.graphicsDeviceType == UnityEngine.Rendering.GraphicsDeviceType.Null ||
SystemInfo.graphicsDeviceType == UnityEngine.Rendering.GraphicsDeviceType.Direct3D11 ||
SystemInfo.graphicsDeviceType == UnityEngine.Rendering.GraphicsDeviceType.Direct3D12)
{
/*if (!Native.Init(QualitySettings.activeColorSpace == ColorSpace.Linear))
{
Debug.LogError("[AVProVideo] Failing to initialise platform");
}
else*/
{
_isInitialised = true;
_version = Native.GetPluginVersion();
}
}
else
{
Debug.LogError("[AVProVideo] Only Direct3D 11 and 12 are supported, graphicsDeviceType not supported: " + SystemInfo.graphicsDeviceType);
}
}
catch (System.DllNotFoundException e)
{
Debug.LogError("[AVProVideo] Failed to load DLL. " + e.Message);
}
}
return _isInitialised;
}
public static void DeinitPlatform()
{
//Native.Deinit();
_isInitialised = false;
}
}
}
#endif
@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 7b04c4ad4a3b8c44a98a08ea2ae71a6d
timeCreated: 1541807235
licenseType: Pro
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,48 @@
using System.Collections;
using System.Collections.Generic;
using System.Text;
//-----------------------------------------------------------------------------
// Copyright 2015-2021 RenderHeads Ltd. All rights reserved.
//-----------------------------------------------------------------------------
namespace RenderHeads.Media.AVProVideo
{
public class TextCue
{
private TextCue() { }
internal TextCue(string text)
{
Text = text;
}
public string Text { get; private set; }
}
public partial class BaseMediaPlayer : ITextTracks
{
protected TextCue _currentTextCue = null;
public TextCue GetCurrentTextCue() { return _currentTextCue; } // Returns null when there is no active text
protected bool UpdateTextCue(bool force = false)
{
bool result = false;
// Has it changed since the last 'tick'
if (force || InternalIsChangedTextCue())
{
_currentTextCue = null;
string text = InternalGetCurrentTextCue();
if (!string.IsNullOrEmpty(text))
{
_currentTextCue = new TextCue(text);
}
result = true;
}
return result;
}
internal abstract bool InternalIsChangedTextCue();
internal abstract string InternalGetCurrentTextCue();
}
}
@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 70b7a3055e537f74cb49d2fc4e6989e6
timeCreated: 1438695622
licenseType: Pro
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,292 @@
using System.Collections;
using System.Collections.Generic;
//-----------------------------------------------------------------------------
// Copyright 2015-2021 RenderHeads Ltd. All rights reserved.
//-----------------------------------------------------------------------------
namespace RenderHeads.Media.AVProVideo
{
public enum TrackType
{
Video,
Audio,
Text,
}
public class TrackBase
{
protected TrackBase() { }
internal TrackBase(TrackType trackType, int uid, string name, string language, bool isDefault)
{
TrackType = trackType;
Uid = uid;
Name = name;
Language = language;
IsDefault = isDefault;
DisplayName = CreateDisplayName();
}
// The UID is unique to the media
internal int Uid { get; private set; }
public TrackType TrackType { get; private set; }
public string DisplayName { get; private set; }
// Optional
public string Name { get; private set; }
// Optional
public string Language { get; private set; }
// Optional
public bool IsDefault { get; private set; }
protected string CreateDisplayName()
{
string result;
if (!string.IsNullOrEmpty(Name))
{
result = Name;
}
else
{
result = "Track " + Uid.ToString();
}
if (!string.IsNullOrEmpty(Language))
{
result = string.Format("{0} ({1})", result, Language);
}
return result;
}
}
public abstract class TrackCollection : IEnumerable
{
public virtual TrackType TrackType { get; private set; }
public abstract int Count { get; }
public abstract IEnumerator GetEnumerator();
internal abstract void Clear();
internal abstract void Add(TrackBase track);
internal abstract bool HasActiveTrack();
internal abstract bool IsActiveTrack(TrackBase track);
internal abstract void SetActiveTrack(TrackBase track);
internal abstract void SetFirstTrackActive();
}
public class TrackCollection<T> : TrackCollection where T : TrackBase
{
internal TrackCollection() {}
public override IEnumerator GetEnumerator()
{
return _tracks.GetEnumerator();
}
public T this[int index]
{
get
{
return _tracks[index];
}
}
internal T ActiveTrack { get; set; }
internal override bool HasActiveTrack() { return ActiveTrack != null; }
internal override bool IsActiveTrack(TrackBase track)
{
return (ActiveTrack == track);
}
internal override void Clear()
{
_tracks.Clear();
ActiveTrack = null;
}
internal override void Add(TrackBase track)
{
_tracks.Add(track as T);
}
internal override void SetActiveTrack(TrackBase track)
{
ActiveTrack = track as T;
}
internal override void SetFirstTrackActive()
{
if (_tracks.Count > 0)
{
ActiveTrack = _tracks[0];
}
}
public override int Count { get{ return _tracks.Count; } }
internal List<T> _tracks = new List<T>(4);
}
public class VideoTracks : TrackCollection<VideoTrack>
{
public override TrackType TrackType { get { return TrackType.Video; } }
}
public class AudioTracks : TrackCollection<AudioTrack>
{
public override TrackType TrackType { get { return TrackType.Audio; } }
}
public class TextTracks : TrackCollection<TextTrack>
{
public override TrackType TrackType { get { return TrackType.Text; } }
}
public class VideoTrack : TrackBase
{
private VideoTrack() { }
internal VideoTrack(int uid, string name, string language, bool isDefault)
: base(TrackType.Video, uid, name, language, isDefault) { }
// Optional
public int Bitrate { get; set; }
}
public class AudioTrack : TrackBase
{
private AudioTrack() { }
internal AudioTrack(int uid, string name, string language, bool isDefault)
: base(TrackType.Audio, uid, name, language, isDefault) { }
// Optional
public int Bitrate { get; private set; }
// Optional
public int ChannelCount { get; private set; }
}
public class TextTrack : TrackBase
{
private TextTrack() { }
internal TextTrack(int uid, string name, string language, bool isDefault)
: base(TrackType.Text, uid, name, language, isDefault) { }
}
public interface IVideoTracks
{
VideoTracks GetVideoTracks();
VideoTrack GetActiveVideoTrack();
void SetActiveVideoTrack(VideoTrack track);
}
public interface IAudioTracks
{
AudioTracks GetAudioTracks();
AudioTrack GetActiveAudioTrack();
void SetActiveAudioTrack(AudioTrack track);
}
public interface ITextTracks
{
TextTracks GetTextTracks();
TextTrack GetActiveTextTrack();
void SetActiveTextTrack(TextTrack track);
TextCue GetCurrentTextCue();
}
public partial class BaseMediaPlayer : IVideoTracks, IAudioTracks, ITextTracks
{
protected VideoTracks _videoTracks = new VideoTracks();
protected AudioTracks _audioTracks = new AudioTracks();
protected TextTracks _textTracks = new TextTracks();
protected TrackCollection[] _trackCollections;
public VideoTracks GetVideoTracks() { return _videoTracks; }
public AudioTracks GetAudioTracks() { return _audioTracks; }
public TextTracks GetTextTracks() { return _textTracks; }
public VideoTrack GetActiveVideoTrack() { return _videoTracks.ActiveTrack; }
public AudioTrack GetActiveAudioTrack() { return _audioTracks.ActiveTrack; }
public TextTrack GetActiveTextTrack() { return _textTracks.ActiveTrack; }
public void SetActiveVideoTrack(VideoTrack track) { if (track != null) SetActiveTrack(_videoTracks, track); }
public void SetActiveAudioTrack(AudioTrack track) { if (track != null) SetActiveTrack(_audioTracks, track); }
public void SetActiveTextTrack(TextTrack track) { SetActiveTrack(_textTracks, track); }
internal abstract bool InternalIsChangedTracks(TrackType trackType);
internal abstract int InternalGetTrackCount(TrackType trackType);
internal abstract bool InternalSetActiveTrack(TrackType trackType, int trackUid);
internal abstract TrackBase InternalGetTrackInfo(TrackType trackType, int trackIndex, ref bool isActiveTrack);
private void InitTracks()
{
_trackCollections = new TrackCollection[3] { _videoTracks, _audioTracks, _textTracks };
}
protected void UpdateTracks()
{
foreach (TrackCollection trackCollection in _trackCollections)
{
if (InternalIsChangedTracks(trackCollection.TrackType))
{
PopulateTrackCollection(trackCollection);
}
}
}
private void PopulateTrackCollection(TrackCollection collection)
{
collection.Clear();
int trackCount = InternalGetTrackCount(collection.TrackType);
for (int i = 0; i < trackCount; i++)
{
bool isActiveTrack = false;
TrackBase track = InternalGetTrackInfo(collection.TrackType, i, ref isActiveTrack);
if (track != null)
{
collection.Add(track);
if (isActiveTrack)
{
collection.SetActiveTrack(track);
}
}
else
{
UnityEngine.Debug.LogWarning(string.Format("[AVProVideo] Failed to enumerate {0} track {1} ", collection.TrackType, i));
}
}
}
private void SetActiveTrack(TrackCollection collection, TrackBase track)
{
// Check if this is already the active track
if (collection.IsActiveTrack(track)) return;
// Convert from TextTrack to uid
int trackUid = -1;
if (track != null)
{
trackUid = track.Uid;
}
// Set track based on uid (-1 is no active track)
// NOTE: TrackType is pulled from collection as track may be null
if (InternalSetActiveTrack(collection.TrackType, trackUid))
{
collection.SetActiveTrack(track);
switch (collection.TrackType)
{
case TrackType.Text:
UpdateTextCue(force:true);
break;
}
}
}
}
}
@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 087e0a6fc1bd92e4bbd96796ec593162
timeCreated: 1596803411
licenseType: Pro
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,9 @@
fileFormatVersion: 2
guid: c6f0eb1069a1ccc4b94ebe97c97b9cd1
folderAsset: yes
timeCreated: 1551721729
licenseType: Pro
DefaultImporter:
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,127 @@
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
//-----------------------------------------------------------------------------
// Copyright 2020-2021 RenderHeads Ltd. All rights reserved.
//-----------------------------------------------------------------------------
namespace RenderHeads.Media.AVProVideo
{
[System.Serializable]
public struct HttpHeader
{
public string name;
public string value;
public HttpHeader(string name, string value) { this.name = name; this.value = value; }
public bool IsComplete()
{
return (!string.IsNullOrEmpty(name) && !string.IsNullOrEmpty(value));
}
public string ToValidatedString()
{
string result = null;
if (IsComplete())
{
if (IsValid())
{
result = string.Format("{0}:{1}\r\n", name, value);
}
}
return result;
}
public static bool IsValid(string text)
{
if (!string.IsNullOrEmpty(text))
{
if (!IsAscii(text)) return false;
if (text.Contains("\r") || text.Contains("\n")) return false;
}
return true;
}
private static bool IsAscii(string text)
{
foreach (char c in text)
{
if (c >= 128) {
return false;
}
}
return true;
}
private bool IsValid()
{
if (!IsValid(name) || !IsValid(value))
{
return false;
}
// TODO: check via regular expression
return true;
}
}
/// <summary>
/// Data for handling custom HTTP header fields
/// </summary>
[System.Serializable]
public class HttpHeaderData : IEnumerable
{
[SerializeField]
private List<HttpHeader> httpHeaders = new List<HttpHeader>();
public IEnumerator GetEnumerator()
{
return httpHeaders.GetEnumerator();
}
public HttpHeader this[int index]
{
get
{
return httpHeaders[index];
}
}
public void Clear()
{
httpHeaders.Clear();
}
public void Add(string name, string value)
{
httpHeaders.Add(new HttpHeader(name, value));
}
public bool IsModified()
{
return (httpHeaders != null && httpHeaders.Count > 0);
}
public string ToValidatedString()
{
string result = string.Empty;
foreach (HttpHeader header in httpHeaders)
{
if (header.IsComplete())
{
string line = header.ToValidatedString();
if (!string.IsNullOrEmpty(line))
{
result += line;
}
else
{
Debug.LogWarning("[AVProVideo] Custom HTTP header field ignored due to invalid format");
}
}
}
return result;
}
}
}
@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 2cfc6f8c038acdf4a9b384e8cb5e9cb2
timeCreated: 1588604301
licenseType: Pro
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,55 @@
using UnityEngine;
//-----------------------------------------------------------------------------
// Copyright 2020-2021 RenderHeads Ltd. All rights reserved.
//-----------------------------------------------------------------------------
namespace RenderHeads.Media.AVProVideo
{
/// <summary>
/// Data for handling authentication of encrypted AES-128 HLS streams
/// </summary>
[System.Serializable]
public class KeyAuthData : ISerializationCallbackReceiver
{
public string keyServerToken = null;
//public string keyServerURLOverride = null;
[SerializeField, Multiline]
private string overrideDecryptionKeyBase64 = null;
public byte[] overrideDecryptionKey = null;
public bool IsModified()
{
return (overrideDecryptionKey != null && overrideDecryptionKey.Length > 0)
|| (string.IsNullOrEmpty(overrideDecryptionKeyBase64) == false);
}
public void OnBeforeSerialize()
{
if (overrideDecryptionKey != null && !string.IsNullOrEmpty(overrideDecryptionKeyBase64))
{
overrideDecryptionKey = null;
}
}
public void OnAfterDeserialize()
{
if (string.IsNullOrEmpty(overrideDecryptionKeyBase64))
return;
try
{
// Convert overrideDecryptionKeyBase64 to overrideDecryptionKey
overrideDecryptionKey = System.Convert.FromBase64String(overrideDecryptionKeyBase64);
}
catch (System.FormatException e)
{
Debug.LogError("Failed to deserialize decryption key, error: " + e);
overrideDecryptionKeyBase64 = null;
overrideDecryptionKey = null;
}
}
}
}
@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 0e784fab214313d44aaa5906743860fa
timeCreated: 1588604301
licenseType: Pro
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,594 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
//-----------------------------------------------------------------------------
// Copyright 2015-2021 RenderHeads Ltd. All rights reserved.
//-----------------------------------------------------------------------------
namespace RenderHeads.Media.AVProVideo
{
/// <summary>
/// Utility class to resample MediaPlayer video frames to allow for smoother playback
/// Keeps a buffer of frames with timestamps and presents them using its own clock
/// </summary>
public class Resampler
{
private class TimestampedRenderTexture
{
public RenderTexture texture = null;
public long timestamp = 0;
public bool used = false;
}
public enum ResampleMode
{
POINT, LINEAR
}
private List<TimestampedRenderTexture[]> _buffer = new List<TimestampedRenderTexture[]>();
private MediaPlayer _mediaPlayer;
private RenderTexture[] _outputTexture = null;
private int _start = 0;
private int _end = 0;
private int _bufferSize = 0;
private long _baseTimestamp = 0;
private float _elapsedTimeSinceBase = 0f;
private Material _blendMat;
private ResampleMode _resampleMode;
private string _name = "";
private long _lastTimeStamp = -1;
private int _droppedFrames = 0;
private long _lastDisplayedTimestamp = 0;
private int _frameDisplayedTimer = 0;
private long _currentDisplayedTimestamp = 0;
public int DroppedFrames
{
get { return _droppedFrames; }
}
public int FrameDisplayedTimer
{
get { return _frameDisplayedTimer; }
}
public long BaseTimestamp
{
get { return _baseTimestamp; }
set { _baseTimestamp = value; }
}
public float ElapsedTimeSinceBase
{
get { return _elapsedTimeSinceBase; }
set { _elapsedTimeSinceBase = value; }
}
public float LastT
{
get; private set;
}
public long TextureTimeStamp
{
get; private set;
}
private const string ShaderPropT = "_t";
private const string ShaderPropAftertex = "_AfterTex";
private int _propAfterTex;
private int _propT;
private float _videoFrameRate;
public void OnVideoEvent(MediaPlayer mp, MediaPlayerEvent.EventType et, ErrorCode errorCode)
{
switch (et)
{
case MediaPlayerEvent.EventType.MetaDataReady:
_videoFrameRate = mp.Info.GetVideoFrameRate();
_elapsedTimeSinceBase = 0f;
if (_videoFrameRate > 0f)
{
_elapsedTimeSinceBase = _bufferSize / _videoFrameRate;
}
break;
case MediaPlayerEvent.EventType.Closing:
Reset();
break;
default:
break;
}
}
public Resampler(MediaPlayer player, string name, int bufferSize = 2, ResampleMode resampleMode = ResampleMode.LINEAR)
{
_bufferSize = Mathf.Max(2, bufferSize);
player.Events.AddListener(OnVideoEvent);
_mediaPlayer = player;
Shader blendShader = Shader.Find("AVProVideo/Internal/BlendFrames");
if (blendShader != null)
{
_blendMat = new Material(blendShader);
_propT = Shader.PropertyToID(ShaderPropT);
_propAfterTex = Shader.PropertyToID(ShaderPropAftertex);
}
else
{
Debug.LogError("[AVProVideo] Failed to find BlendFrames shader");
}
_resampleMode = resampleMode;
_name = name;
Debug.Log("[AVProVideo] Resampler " + _name + " started");
}
public Texture[] OutputTexture
{
get { return _outputTexture; }
}
public void Reset()
{
_lastTimeStamp = -1;
_baseTimestamp = 0;
InvalidateBuffer();
}
public void Release()
{
ReleaseRenderTextures();
if (_blendMat != null)
{
if (Application.isPlaying)
{
Material.Destroy(_blendMat);
}
else
{
Material.DestroyImmediate(_blendMat);
}
}
}
private void ReleaseRenderTextures()
{
for (int i = 0; i < _buffer.Count; ++i)
{
for (int j = 0; j < _buffer[i].Length; ++j)
{
if (_buffer[i][j].texture != null)
{
RenderTexture.ReleaseTemporary(_buffer[i][j].texture);
_buffer[i][j].texture = null;
}
}
if (_outputTexture != null && _outputTexture[i] != null)
{
RenderTexture.ReleaseTemporary(_outputTexture[i]);
}
}
_outputTexture = null;
}
private void ConstructRenderTextures()
{
ReleaseRenderTextures();
_buffer.Clear();
_outputTexture = new RenderTexture[_mediaPlayer.TextureProducer.GetTextureCount()];
for (int i = 0; i < _mediaPlayer.TextureProducer.GetTextureCount(); ++i)
{
Texture tex = _mediaPlayer.TextureProducer.GetTexture(i);
_buffer.Add(new TimestampedRenderTexture[_bufferSize]);
for (int j = 0; j < _bufferSize; ++j)
{
_buffer[i][j] = new TimestampedRenderTexture();
}
for (int j = 0; j < _buffer[i].Length; ++j)
{
_buffer[i][j].texture = RenderTexture.GetTemporary(tex.width, tex.height, 0);
_buffer[i][j].timestamp = 0;
_buffer[i][j].used = false;
}
_outputTexture[i] = RenderTexture.GetTemporary(tex.width, tex.height, 0);
_outputTexture[i].filterMode = tex.filterMode;
_outputTexture[i].wrapMode = tex.wrapMode;
_outputTexture[i].anisoLevel = tex.anisoLevel;
// TODO: set up the mips level too?
}
}
private bool CheckRenderTexturesValid()
{
for (int i = 0; i < _mediaPlayer.TextureProducer.GetTextureCount(); ++i)
{
Texture tex = _mediaPlayer.TextureProducer.GetTexture(i);
for (int j = 0; j < _buffer.Count; ++j)
{
if (_buffer[i][j].texture == null || _buffer[i][j].texture.width != tex.width || _buffer[i][j].texture.height != tex.height)
{
return false;
}
}
if (_outputTexture == null || _outputTexture[i] == null || _outputTexture[i].width != tex.width || _outputTexture[i].height != tex.height)
{
return false;
}
}
return true;
}
//finds closest frame that occurs before given index
private int FindBeforeFrameIndex(int frameIdx)
{
if (frameIdx >= _buffer.Count)
{
return -1;
}
int foundFrame = -1;
float smallestDif = float.MaxValue;
int closest = -1;
float smallestElapsed = float.MaxValue;
for (int i = 0; i < _buffer[frameIdx].Length; ++i)
{
if (_buffer[frameIdx][i].used)
{
float elapsed = (_buffer[frameIdx][i].timestamp - _baseTimestamp) / 10000000f;
//keep track of closest after frame, just in case no before frame was found
if (elapsed < smallestElapsed)
{
closest = i;
smallestElapsed = elapsed;
}
float dif = _elapsedTimeSinceBase - elapsed;
if (dif >= 0 && dif < smallestDif)
{
smallestDif = dif;
foundFrame = i;
}
}
}
if (foundFrame < 0)
{
if (closest < 0)
{
return -1;
}
return closest;
}
return foundFrame;
}
private int FindClosestFrame(int frameIdx)
{
if (frameIdx >= _buffer.Count)
{
return -1;
}
int foundPos = -1;
float smallestDif = float.MaxValue;
for (int i = 0; i < _buffer[frameIdx].Length; ++i)
{
if (_buffer[frameIdx][i].used)
{
float elapsed = (_buffer[frameIdx][i].timestamp - _baseTimestamp) / 10000000f;
float dif = Mathf.Abs(_elapsedTimeSinceBase - elapsed);
if (dif < smallestDif)
{
foundPos = i;
smallestDif = dif;
}
}
}
return foundPos;
}
//point update selects closest frame and uses that as output
private void PointUpdate()
{
for (int i = 0; i < _buffer.Count; ++i)
{
int frameIndex = FindClosestFrame(i);
if (frameIndex < 0)
{
continue;
}
_outputTexture[i].DiscardContents();
Graphics.Blit(_buffer[i][frameIndex].texture, _outputTexture[i]);
TextureTimeStamp = _currentDisplayedTimestamp = _buffer[i][frameIndex].timestamp;
}
}
//Updates currently displayed frame
private void SampleFrame(int frameIdx, int bufferIdx)
{
_outputTexture[bufferIdx].DiscardContents();
Graphics.Blit(_buffer[bufferIdx][frameIdx].texture, _outputTexture[bufferIdx]);
TextureTimeStamp = _currentDisplayedTimestamp = _buffer[bufferIdx][frameIdx].timestamp;
}
//Same as sample frame, but does a lerp of the two given frames and outputs that image instead
private void SampleFrames(int bufferIdx, int frameIdx1, int frameIdx2, float t)
{
_blendMat.SetFloat(_propT, t);
_blendMat.SetTexture(_propAfterTex, _buffer[bufferIdx][frameIdx2].texture);
_outputTexture[bufferIdx].DiscardContents();
Graphics.Blit(_buffer[bufferIdx][frameIdx1].texture, _outputTexture[bufferIdx], _blendMat);
TextureTimeStamp = (long)Mathf.Lerp(_buffer[bufferIdx][frameIdx1].timestamp, _buffer[bufferIdx][frameIdx2].timestamp, t);
_currentDisplayedTimestamp = _buffer[bufferIdx][frameIdx1].timestamp;
}
private void LinearUpdate()
{
for (int i = 0; i < _buffer.Count; ++i)
{
//find closest frame
int frameIndex = FindBeforeFrameIndex(i);
//no valid frame, this should never ever happen actually...
if (frameIndex < 0)
{
continue;
}
//resample or just use last frame and set current elapsed time to that frame
float frameElapsed = (_buffer[i][frameIndex].timestamp - _baseTimestamp) / 10000000f;
if (frameElapsed > _elapsedTimeSinceBase)
{
SampleFrame(frameIndex, i);
LastT = -1f;
}
else
{
int next = (frameIndex + 1) % _buffer[i].Length;
float nextElapsed = (_buffer[i][next].timestamp - _baseTimestamp) / 10000000f;
//no larger frame, move elapsed time back a bit since we cant predict the future
if (nextElapsed < frameElapsed)
{
SampleFrame(frameIndex, i);
LastT = 2f;
}
//have a before and after frame, interpolate
else
{
float range = nextElapsed - frameElapsed;
float t = (_elapsedTimeSinceBase - frameElapsed) / range;
SampleFrames(i, frameIndex, next, t);
LastT = t;
}
}
}
}
private void InvalidateBuffer()
{
_elapsedTimeSinceBase = (_bufferSize / 2) / _videoFrameRate;
for (int i = 0; i < _buffer.Count; ++i)
{
for (int j = 0; j < _buffer[i].Length; ++j)
{
_buffer[i][j].used = false;
}
}
_start = _end = 0;
}
private float GuessFrameRate()
{
int fpsCount = 0;
long fps = 0;
for (int k = 0; k < _buffer[0].Length; k++)
{
if (_buffer[0][k].used)
{
// Find the pair with the smallest difference
long smallestDiff = long.MaxValue;
for (int j = k + 1; j < _buffer[0].Length; j++)
{
if (_buffer[0][j].used)
{
long diff = System.Math.Abs(_buffer[0][k].timestamp - _buffer[0][j].timestamp);
if (diff < smallestDiff)
{
smallestDiff = diff;
}
}
}
if (smallestDiff != long.MaxValue)
{
fps += smallestDiff;
fpsCount++;
}
}
}
if (fpsCount > 1)
{
fps /= fpsCount;
}
return 10000000f / (float)fps;
}
public void Update()
{
if (_mediaPlayer.TextureProducer == null)
{
return;
}
//recreate textures if invalid
if (_mediaPlayer.TextureProducer == null || _mediaPlayer.TextureProducer.GetTexture() == null)
{
return;
}
if (!CheckRenderTexturesValid())
{
ConstructRenderTextures();
}
long currentTimestamp = _mediaPlayer.TextureProducer.GetTextureTimeStamp();
//if frame has been updated, do a calculation to estimate dropped frames
if (currentTimestamp != _lastTimeStamp)
{
float dif = Mathf.Abs(currentTimestamp - _lastTimeStamp);
float frameLength = (10000000f / _videoFrameRate);
if (dif > frameLength * 1.1f && dif < frameLength * 3.1f)
{
_droppedFrames += (int)((dif - frameLength) / frameLength + 0.5);
}
_lastTimeStamp = currentTimestamp;
}
//Adding texture to buffer logic
long timestamp = _mediaPlayer.TextureProducer.GetTextureTimeStamp();
bool insertNewFrame = !_mediaPlayer.Control.IsSeeking();
//if buffer is not empty, we need to check if we need to reject the new frame
if (_start != _end || _buffer[0][_end].used)
{
int lastFrame = (_end + _buffer[0].Length - 1) % _buffer[0].Length;
//frame is not new and thus we do not need to store it
if (timestamp == _buffer[0][lastFrame].timestamp)
{
insertNewFrame = false;
}
}
bool bufferWasNotFull = (_start != _end) || (!_buffer[0][_end].used);
if (insertNewFrame)
{
//buffer empty, reset base timestamp to current
if (_start == _end && !_buffer[0][_end].used)
{
_baseTimestamp = timestamp;
}
//update buffer counters, if buffer is full, we get rid of the earliest frame by incrementing the start counter
if (_end == _start && _buffer[0][_end].used)
{
_start = (_start + 1) % _buffer[0].Length;
}
for (int i = 0; i < _mediaPlayer.TextureProducer.GetTextureCount(); ++i)
{
Texture currentTexture = _mediaPlayer.TextureProducer.GetTexture(i);
//store frame info
_buffer[i][_end].texture.DiscardContents();
Graphics.Blit(currentTexture, _buffer[i][_end].texture);
_buffer[i][_end].timestamp = timestamp;
_buffer[i][_end].used = true;
}
_end = (_end + 1) % _buffer[0].Length;
}
bool bufferNotFull = (_start != _end) || (!_buffer[0][_end].used);
if (bufferNotFull)
{
for (int i = 0; i < _buffer.Count; ++i)
{
_outputTexture[i].DiscardContents();
Graphics.Blit(_buffer[i][_start].texture, _outputTexture[i]);
_currentDisplayedTimestamp = _buffer[i][_start].timestamp;
}
}
else
{
// If we don't have a valid frame rate and the buffer is now full, guess the frame rate by looking at the buffered timestamps
if (bufferWasNotFull && _videoFrameRate <= 0f)
{
_videoFrameRate = GuessFrameRate();
_elapsedTimeSinceBase = (_bufferSize / 2) / _videoFrameRate;
}
}
if (_mediaPlayer.Control.IsPaused())
{
InvalidateBuffer();
}
//we always wait until buffer is full before display things, just assign first frame in buffer to output so that the user can see something
if (bufferNotFull)
{
return;
}
if (_mediaPlayer.Control.IsPlaying() && !_mediaPlayer.Control.IsFinished())
{
//correct elapsed time if too far out
long ts = _buffer[0][(_start + _bufferSize / 2) % _bufferSize].timestamp - _baseTimestamp;
double dif = Mathf.Abs(((float)((double)_elapsedTimeSinceBase * 10000000) - ts));
double threshold = (_buffer[0].Length / 2) / _videoFrameRate * 10000000;
if (dif > threshold)
{
_elapsedTimeSinceBase = ts / 10000000f;
}
if (_resampleMode == ResampleMode.POINT)
{
PointUpdate();
}
else if (_resampleMode == ResampleMode.LINEAR)
{
LinearUpdate();
}
_elapsedTimeSinceBase += Time.unscaledDeltaTime;
}
}
public void UpdateTimestamp()
{
if (_lastDisplayedTimestamp != _currentDisplayedTimestamp)
{
_lastDisplayedTimestamp = _currentDisplayedTimestamp;
_frameDisplayedTimer = 0;
}
_frameDisplayedTimer++;
}
}
}
@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 8ac8dc09faa6b1d48bf6f490c9888550
timeCreated: 1497356591
licenseType: Free
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,144 @@
using UnityEngine;
using System.Collections.Generic;
//-----------------------------------------------------------------------------
// Copyright 2015-2021 RenderHeads Ltd. All rights reserved.
//-----------------------------------------------------------------------------
namespace RenderHeads.Media.AVProVideo
{
public class Subtitle
{
public int index;
// Rich string can contain <font color=""> <u> etc
public string text;
public double timeStart, timeEnd;
public bool IsBefore(double time)
{
return (time > timeStart && time > timeEnd);
}
public bool IsTime(double time)
{
return (time >= timeStart && time < timeEnd);
}
}
public class SubtitlePlayer
{
// min time, max time
// set time
// event for change(subs added, subs removed)
// list of subs on
}
public class SubtitleUtils
{
/// <summary>
/// Parse time in format: 00:00:48,924 and convert to seconds
/// </summary>
private static double ParseTimeToSeconds(string text)
{
double result = 0.0;
string[] digits = text.Split(new char[] { ':', ',' });
if (digits.Length == 4)
{
int hours = int.Parse(digits[0]);
int minutes = int.Parse(digits[1]);
int seconds = int.Parse(digits[2]);
int milliseconds = int.Parse(digits[3]);
result = (milliseconds / 1000.0) + (seconds + (minutes + (hours * 60)) * 60);
}
return result;
}
/// <summary>
/// Parse subtitles in the SRT format and convert to a list of ordered Subtitle objects
/// </summary>
public static List<Subtitle> ParseSubtitlesSRT(string data)
{
List<Subtitle> result = null;
if (!string.IsNullOrEmpty(data))
{
data = data.Trim();
var rx = new System.Text.RegularExpressions.Regex("\n\r|\r\n|\n|\r");
string[] lines = rx.Split(data);
if (lines.Length >= 3)
{
result = new List<Subtitle>(256);
int count = 0;
int index = 0;
Subtitle subtitle = null;
for (int i = 0; i < lines.Length; i++)
{
if (index == 0)
{
subtitle = new Subtitle();
subtitle.index = count;// int.Parse(lines[i]);
}
else if (index == 1)
{
string[] times = lines[i].Split(new string[] { " --> " }, System.StringSplitOptions.RemoveEmptyEntries);
if (times.Length == 2)
{
subtitle.timeStart = ParseTimeToSeconds(times[0]);
subtitle.timeEnd = ParseTimeToSeconds(times[1]);
}
else
{
throw new System.FormatException("SRT format doesn't appear to be valid");
}
}
else
{
if (!string.IsNullOrEmpty(lines[i]))
{
if (index == 2)
{
subtitle.text = lines[i];
}
else
{
subtitle.text += "\n" + lines[i];
}
}
}
if (string.IsNullOrEmpty(lines[i]) && index > 1)
{
result.Add(subtitle);
index = 0;
count++;
subtitle = null;
}
else
{
index++;
}
}
// Handle the last one
if (subtitle != null)
{
result.Add(subtitle);
subtitle = null;
}
}
else
{
Debug.LogWarning("[AVProVideo] SRT format doesn't appear to be valid");
}
}
return result;
}
}
}
@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: c21f230642ee9284eb9726613241c7bd
timeCreated: 1548861442
licenseType: Pro
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,651 @@
#if UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX || UNITY_IOS || UNITY_TVOS
#define UNITY_PLATFORM_SUPPORTS_YPCBCR
#endif
#if UNITY_EDITOR || UNITY_STANDALONE_OSX || UNITY_STANDALONE_WIN || UNITY_IOS || UNITY_TVOS || UNITY_ANDROID || (UNITY_WEBGL && UNITY_2017_2_OR_NEWER)
#define UNITY_PLATFORM_SUPPORTS_LINEAR
#endif
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Rendering;
//-----------------------------------------------------------------------------
// Copyright 2015-2022 RenderHeads Ltd. All rights reserved.
//-----------------------------------------------------------------------------
namespace RenderHeads.Media.AVProVideo
{
#if AVPRO_FEATURE_VIDEORESOLVE
[System.Serializable]
public class VideoResolve : ITextureProducer
{
[SerializeField] VideoResolveOptions _options = VideoResolveOptions.Create();
[SerializeField] RenderTexture _targetRenderTexture = null;
[SerializeField] ScaleMode _targetRenderTextureScale = ScaleMode.ScaleToFit;
void SetSource(ITextureProducer textureSource)
{
//_commandBuffer.IssuePluginEvent(blahCallback, 0);
//Graphics.ExecuteCommandBuffer(_commandBuffer);
}
// ITextureProducer implementation
/// <inheritdoc/>
public int GetTextureCount() { return 1; }
/// <inheritdoc/>
public Texture GetTexture(int index = 0) { return _texture; }
/// <inheritdoc/>
public int GetTextureFrameCount() { return _textureSource.GetTextureFrameCount(); }
/// <inheritdoc/>
public bool SupportsTextureFrameCount() { return _textureSource.SupportsTextureFrameCount(); }
/// <inheritdoc/>
public long GetTextureTimeStamp() { return _textureSource.GetTextureTimeStamp(); }
/// <inheritdoc/>
public bool RequiresVerticalFlip() { return false; }
/// <inheritdoc/>
public StereoPacking GetTextureStereoPacking() { return StereoPacking.None; }
/// <inheritdoc/>
public TransparencyMode GetTextureTransparency() { return TransparencyMode.Transparent; }
/// <inheritdoc/>
public AlphaPacking GetTextureAlphaPacking() { return AlphaPacking.None; }
/// <inheritdoc/>
public Matrix4x4 GetYpCbCrTransform() { return Matrix4x4.identity; }
private ITextureProducer _textureSource;
private Texture _texture;
private CommandBuffer _commandBuffer;
}
#endif
public struct LazyShaderProperty
{
public LazyShaderProperty(string name)
{
_name = name;
_id = 0;
}
public string Name { get { return _name;} }
public int Id { get { if (_id == 0) { _id = Shader.PropertyToID(_name); } return _id; } }
private string _name;
private int _id;
}
/// <summary>Helper class for everything related to setting up materials for rendering/resolving videos</summary>
public class VideoRender
{
public const string Shader_IMGUI = "AVProVideo/Internal/IMGUI/Texture Transparent";
public const string Shader_Resolve = "AVProVideo/Internal/Resolve";
public const string Shader_ResolveOES = "AVProVideo/Internal/ResolveOES";
public const string Shader_Preview = "AVProVideo/Internal/Preview";
#if UNITY_PLATFORM_SUPPORTS_YPCBCR
public const string Keyword_UseYpCbCr = "USE_YPCBCR";
#endif
public const string Keyword_AlphaPackTopBottom = "ALPHAPACK_TOP_BOTTOM";
public const string Keyword_AlphaPackLeftRight = "ALPHAPACK_LEFT_RIGHT";
public const string Keyword_AlphaPackNone = "ALPHAPACK_NONE";
public const string Keyword_StereoTopBottom = "STEREO_TOP_BOTTOM";
public const string Keyword_StereoLeftRight = "STEREO_LEFT_RIGHT";
public const string Keyword_StereoCustomUV = "STEREO_CUSTOM_UV";
public const string Keyword_StereoTwoTextures = "STEREO_TWOTEXTURES";
public const string Keyword_StereoNone = "MONOSCOPIC";
public const string Keyword_StereoDebug = "STEREO_DEBUG";
public const string Keyword_LayoutEquirect180 = "LAYOUT_EQUIRECT180";
public const string Keyword_LayoutNone = "LAYOUT_NONE";
public const string Keyword_ForceEyeNone = "FORCEEYE_NONE";
public const string Keyword_ForceEyeLeft = "FORCEEYE_LEFT";
public const string Keyword_ForceEyeRight = "FORCEEYE_RIGHT";
public const string Keyword_ApplyGamma = "APPLY_GAMMA";
public static readonly LazyShaderProperty PropChromaTex = new LazyShaderProperty("_ChromaTex");
#if UNITY_PLATFORM_SUPPORTS_YPCBCR
public static readonly LazyShaderProperty PropYpCbCrTransform = new LazyShaderProperty("_YpCbCrTransform");
public static readonly LazyShaderProperty PropUseYpCbCr = new LazyShaderProperty("_UseYpCbCr");
#endif
public static readonly LazyShaderProperty PropVertScale = new LazyShaderProperty("_VertScale");
public static readonly LazyShaderProperty PropApplyGamma = new LazyShaderProperty("_ApplyGamma");
public static readonly LazyShaderProperty PropStereo = new LazyShaderProperty("Stereo");
public static readonly LazyShaderProperty PropAlphaPack = new LazyShaderProperty("AlphaPack");
public static readonly LazyShaderProperty PropLayout = new LazyShaderProperty("Layout");
public static readonly LazyShaderProperty PropViewMatrix = new LazyShaderProperty("_ViewMatrix");
public static readonly LazyShaderProperty PropTextureMatrix = new LazyShaderProperty("_TextureMatrix");
public static string Keyword_UseHSBC = "USE_HSBC";
public static readonly LazyShaderProperty PropHue = new LazyShaderProperty("_Hue");
public static readonly LazyShaderProperty PropSaturation = new LazyShaderProperty("_Saturation");
public static readonly LazyShaderProperty PropContrast = new LazyShaderProperty("_Contrast");
public static readonly LazyShaderProperty PropBrightness = new LazyShaderProperty("_Brightness");
public static readonly LazyShaderProperty PropInvGamma = new LazyShaderProperty("_InvGamma");
public static Material CreateResolveMaterial(bool usingAndroidOES)
{
return new Material(Shader.Find( usingAndroidOES ? VideoRender.Shader_ResolveOES : VideoRender.Shader_Resolve ));
}
public static Material CreateIMGUIMaterial()
{
return new Material(Shader.Find(VideoRender.Shader_Preview));
}
public static void SetupLayoutMaterial(Material material, VideoMapping mapping)
{
switch (mapping)
{
default:
material.DisableKeyword(Keyword_LayoutEquirect180);
material.EnableKeyword(Keyword_LayoutNone);
break;
// Only EquiRectangular180 currently does anything in the shader
case VideoMapping.EquiRectangular180:
material.DisableKeyword(Keyword_LayoutNone);
material.EnableKeyword(Keyword_LayoutEquirect180);
break;
}
}
public static void SetupStereoEyeModeMaterial(Material material, StereoEye mode)
{
switch (mode)
{
case StereoEye.Both:
material.DisableKeyword(Keyword_ForceEyeLeft);
material.DisableKeyword(Keyword_ForceEyeRight);
material.EnableKeyword(Keyword_ForceEyeNone);
break;
case StereoEye.Left:
material.DisableKeyword(Keyword_ForceEyeNone);
material.DisableKeyword(Keyword_ForceEyeRight);
material.EnableKeyword(Keyword_ForceEyeLeft);
break;
case StereoEye.Right:
material.DisableKeyword(Keyword_ForceEyeNone);
material.DisableKeyword(Keyword_ForceEyeLeft);
material.EnableKeyword(Keyword_ForceEyeRight);
break;
}
}
public static void SetupStereoMaterial(Material material, StereoPacking packing)
{
switch (packing)
{
case StereoPacking.None:
material.DisableKeyword(Keyword_StereoTopBottom);
material.DisableKeyword(Keyword_StereoLeftRight);
material.DisableKeyword(Keyword_StereoCustomUV);
material.DisableKeyword(Keyword_StereoTwoTextures);
material.EnableKeyword(Keyword_StereoNone);
break;
case StereoPacking.TopBottom:
material.DisableKeyword(Keyword_StereoNone);
material.DisableKeyword(Keyword_StereoLeftRight);
material.DisableKeyword(Keyword_StereoCustomUV);
material.DisableKeyword(Keyword_StereoTwoTextures);
material.EnableKeyword(Keyword_StereoTopBottom);
break;
case StereoPacking.LeftRight:
material.DisableKeyword(Keyword_StereoNone);
material.DisableKeyword(Keyword_StereoTopBottom);
material.DisableKeyword(Keyword_StereoTwoTextures);
material.DisableKeyword(Keyword_StereoCustomUV);
material.EnableKeyword(Keyword_StereoLeftRight);
break;
case StereoPacking.CustomUV:
material.DisableKeyword(Keyword_StereoNone);
material.DisableKeyword(Keyword_StereoTopBottom);
material.DisableKeyword(Keyword_StereoLeftRight);
material.DisableKeyword(Keyword_StereoTwoTextures);
material.EnableKeyword(Keyword_StereoCustomUV);
break;
case StereoPacking.TwoTextures:
material.DisableKeyword(Keyword_StereoNone);
material.DisableKeyword(Keyword_StereoTopBottom);
material.DisableKeyword(Keyword_StereoLeftRight);
material.DisableKeyword(Keyword_StereoCustomUV);
material.EnableKeyword(Keyword_StereoTwoTextures);
break;
}
}
public static void SetupGlobalDebugStereoTinting(bool enabled)
{
if (enabled)
{
Shader.EnableKeyword(Keyword_StereoDebug);
}
else
{
Shader.DisableKeyword(Keyword_StereoDebug);
}
}
public static void SetupAlphaPackedMaterial(Material material, AlphaPacking packing)
{
switch (packing)
{
case AlphaPacking.None:
material.DisableKeyword(Keyword_AlphaPackTopBottom);
material.DisableKeyword(Keyword_AlphaPackLeftRight);
material.EnableKeyword(Keyword_AlphaPackNone);
break;
case AlphaPacking.TopBottom:
material.DisableKeyword(Keyword_AlphaPackNone);
material.DisableKeyword(Keyword_AlphaPackLeftRight);
material.EnableKeyword(Keyword_AlphaPackTopBottom);
break;
case AlphaPacking.LeftRight:
material.DisableKeyword(Keyword_AlphaPackNone);
material.DisableKeyword(Keyword_AlphaPackTopBottom);
material.EnableKeyword(Keyword_AlphaPackLeftRight);
break;
}
}
public static void SetupGammaMaterial(Material material, bool playerSupportsLinear)
{
#if UNITY_PLATFORM_SUPPORTS_LINEAR
if (QualitySettings.activeColorSpace == ColorSpace.Linear && !playerSupportsLinear)
{
material.EnableKeyword(Keyword_ApplyGamma);
}
else
{
material.DisableKeyword(Keyword_ApplyGamma);
}
#endif
}
public static void SetupTextureMatrix(Material material, float[] transform)
{
#if (!UNITY_EDITOR && UNITY_ANDROID)
// STE: HasProperty doesn't work on Matrix'
// if (material != null && (material.HasProperty(VideoRender.PropTextureMatrix.Id)))
{
if (transform != null)
{
Matrix4x4 m = new Matrix4x4(new Vector4( transform[0], transform[1], transform[2], transform[3] ),
new Vector4( transform[4], transform[5], transform[6], transform[7] ),
new Vector4( transform[8], transform[9], transform[10], transform[11] ),
new Vector4( transform[12], transform[13], transform[14], transform[15] ));
material.SetMatrix(VideoRender.PropTextureMatrix.Id, m);
}
else
{
material.SetMatrix(VideoRender.PropTextureMatrix.Id, Matrix4x4.identity);
}
}
#endif
}
#if UNITY_PLATFORM_SUPPORTS_YPCBCR
public static void SetupYpCbCrMaterial(Material material, bool enable, Matrix4x4 transform, Texture texture)
{
if (material.HasProperty(VideoRender.PropUseYpCbCr.Id))
{
if (enable)
{
material.EnableKeyword(VideoRender.Keyword_UseYpCbCr);
material.SetMatrix(VideoRender.PropYpCbCrTransform.Id, transform);
material.SetTexture(VideoRender.PropChromaTex.Id, texture);
}
else
{
material.DisableKeyword(VideoRender.Keyword_UseYpCbCr);
}
}
}
#endif
public static void SetupVerticalFlipMaterial(Material material, bool flip)
{
material.SetFloat(VideoRender.PropVertScale.Id, flip?-1f:1f);
}
public static Texture GetTexture(MediaPlayer mediaPlayer, int textureIndex)
{
Texture result = null;
if (mediaPlayer != null)
{
if (mediaPlayer.UseResampler && mediaPlayer.FrameResampler != null && mediaPlayer.FrameResampler.OutputTexture != null)
{
if ( mediaPlayer.FrameResampler.OutputTexture.Length > textureIndex)
{
result = mediaPlayer.FrameResampler.OutputTexture[textureIndex];
}
}
else if (mediaPlayer.TextureProducer != null)
{
if (mediaPlayer.TextureProducer.GetTextureCount() > textureIndex)
{
result = mediaPlayer.TextureProducer.GetTexture(textureIndex);
}
}
}
return result;
}
public static void SetupMaterialForMedia(Material material, MediaPlayer mediaPlayer, int texturePropId = -1, Texture fallbackTexture = null, bool forceFallbackTexture = false)
{
Debug.Assert(material != null);
if (mediaPlayer != null)
{
Texture mainTexture = GetTexture(mediaPlayer, 0);
Texture yCbCrTexture = GetTexture(mediaPlayer, 1);
if (texturePropId != -1)
{
if (mainTexture == null || forceFallbackTexture)
{
mainTexture = fallbackTexture;
}
material.SetTexture(texturePropId, mainTexture);
}
SetupMaterial(material,
(mediaPlayer.TextureProducer != null)?mediaPlayer.TextureProducer.RequiresVerticalFlip():false,
(mediaPlayer.Info != null)?mediaPlayer.Info.PlayerSupportsLinearColorSpace():true,
(mediaPlayer.TextureProducer != null)?mediaPlayer.TextureProducer.GetYpCbCrTransform():Matrix4x4.identity,
yCbCrTexture,
(mediaPlayer.Info != null && mediaPlayer.PlatformOptionsAndroid.useFastOesPath)?mediaPlayer.Info.GetTextureTransform():null,
mediaPlayer.VideoLayoutMapping,
(mediaPlayer.TextureProducer != null)?mediaPlayer.TextureProducer.GetTextureStereoPacking():StereoPacking.None,
(mediaPlayer.TextureProducer != null)?mediaPlayer.TextureProducer.GetTextureAlphaPacking():AlphaPacking.None);
}
else
{
if (texturePropId != -1)
{
material.SetTexture(texturePropId, fallbackTexture);
}
SetupMaterial(material, false, true, Matrix4x4.identity, null);
}
}
internal static void SetupMaterial(Material material, bool flipVertically, bool playerSupportsLinear, Matrix4x4 ycbcrTransform, Texture ycbcrTexture = null, float[] textureTransform = null,
VideoMapping mapping = VideoMapping.Normal, StereoPacking stereoPacking = StereoPacking.None, AlphaPacking alphaPacking = AlphaPacking.None)
{
SetupVerticalFlipMaterial(material, flipVertically);
// Apply changes for layout
if (material.HasProperty(VideoRender.PropLayout.Id))
{
VideoRender.SetupLayoutMaterial(material, mapping);
}
// Apply changes for stereo videos
if (material.HasProperty(VideoRender.PropStereo.Id))
{
VideoRender.SetupStereoMaterial(material, stereoPacking);
}
// Apply changes for alpha videos
if (material.HasProperty(VideoRender.PropAlphaPack.Id))
{
VideoRender.SetupAlphaPackedMaterial(material, alphaPacking);
}
// Apply gamma correction
#if UNITY_PLATFORM_SUPPORTS_LINEAR
if (material.HasProperty(VideoRender.PropApplyGamma.Id))
{
VideoRender.SetupGammaMaterial(material, playerSupportsLinear);
}
#endif
// Adjust for cropping (when the decoder decodes in blocks that overrun the video frame size, it pads), OES only as we apply this lower down for none-OES
#if (!UNITY_EDITOR && UNITY_ANDROID)
// STE: HasProperty doesn't work on Matrix'
// if (material.HasProperty(VideoRender.PropTextureMatrix.Id))
{
VideoRender.SetupTextureMatrix(material, textureTransform);
}
#endif
#if UNITY_PLATFORM_SUPPORTS_YPCBCR
VideoRender.SetupYpCbCrMaterial(material, ycbcrTexture != null, ycbcrTransform, ycbcrTexture);
#endif
}
[System.Flags]
public enum ResolveFlags : int
{
Mipmaps = 1 << 0,
PackedAlpha = 1 << 1,
StereoLeft = 1 << 2,
StereoRight = 1 << 3,
ColorspaceSRGB = 1 << 4,
}
public static void SetupResolveMaterial(Material material, VideoResolveOptions options)
{
if (options.IsColourAdjust())
{
material.EnableKeyword(VideoRender.Keyword_UseHSBC);
material.SetFloat(VideoRender.PropHue.Id, options.hue);
material.SetFloat(VideoRender.PropSaturation.Id, options.saturation);
material.SetFloat(VideoRender.PropBrightness.Id, options.brightness);
material.SetFloat(VideoRender.PropContrast.Id, options.contrast);
material.SetFloat(VideoRender.PropInvGamma.Id, 1f / options.gamma);
}
else
{
material.DisableKeyword(VideoRender.Keyword_UseHSBC);
}
material.color = options.tint;
}
public static RenderTexture ResolveVideoToRenderTexture(Material resolveMaterial, RenderTexture targetTexture, ITextureProducer texture, ResolveFlags flags, ScaleMode scaleMode = ScaleMode.StretchToFill)
{
int targetWidth = texture.GetTexture(0).width;
int targetHeight = texture.GetTexture(0).height;
StereoEye eyeMode = StereoEye.Both;
if (((flags & ResolveFlags.StereoLeft) == ResolveFlags.StereoLeft) &&
((flags & ResolveFlags.StereoRight) != ResolveFlags.StereoRight))
{
eyeMode = StereoEye.Left;
}
else if (((flags & ResolveFlags.StereoLeft) != ResolveFlags.StereoLeft) &&
((flags & ResolveFlags.StereoRight) == ResolveFlags.StereoRight))
{
eyeMode = StereoEye.Right;
}
// RJT NOTE: No longer passing in PAR as combined with larger videos (e.g. 8K+) it can lead to textures >16K which most platforms don't support
// - Instead, the PAR is accounted for during drawing (which is more efficient too)
// - https://github.com/RenderHeads/UnityPlugin-AVProVideo/issues/1297
float pixelAspectRatio = 1.0f; // texture.GetTexturePixelAspectRatio();
GetResolveTextureSize(
texture.GetTextureAlphaPacking(),
texture.GetTextureStereoPacking(),
eyeMode,
pixelAspectRatio,
ref targetWidth,
ref targetHeight);
if (targetTexture)
{
bool sizeChanged = (targetTexture.width != targetWidth) || (targetTexture.height != targetHeight);
if (sizeChanged)
{
RenderTexture.ReleaseTemporary(targetTexture);
targetTexture = null;
}
}
if (!targetTexture)
{
RenderTextureReadWrite readWrite = ((flags & ResolveFlags.ColorspaceSRGB) == ResolveFlags.ColorspaceSRGB) ? RenderTextureReadWrite.sRGB : RenderTextureReadWrite.Linear;
targetTexture = RenderTexture.GetTemporary(targetWidth, targetHeight, 0, RenderTextureFormat.ARGB32, readWrite);
}
// Set target mipmap generation support
{
bool requiresMipmap = (flags & ResolveFlags.Mipmaps) == ResolveFlags.Mipmaps;
bool requiresRecreate = (targetTexture.IsCreated() && targetTexture.useMipMap != requiresMipmap);
if (requiresRecreate)
{
targetTexture.Release();
}
if (!targetTexture.IsCreated())
{
targetTexture.useMipMap = targetTexture.autoGenerateMips = requiresMipmap;
targetTexture.Create();
}
}
// Render resolve blit
// TODO: combine these two paths into a single material blit
{
bool prevSRGB = GL.sRGBWrite;
GL.sRGBWrite = targetTexture.sRGB;
RenderTexture prev = RenderTexture.active;
if (scaleMode == ScaleMode.StretchToFill)
{
Graphics.Blit(texture.GetTexture(0), targetTexture, resolveMaterial);
}
else
{
RenderTexture.active = targetTexture;
bool partialAreaRender = (scaleMode == ScaleMode.ScaleToFit);
if (partialAreaRender)
{
GL.Clear(false, true, Color.black);
}
VideoRender.DrawTexture(new Rect(0f, 0f, targetTexture.width, targetTexture.height), texture.GetTexture(0), scaleMode, texture.GetTextureAlphaPacking(), texture.GetTexturePixelAspectRatio(), resolveMaterial);
}
RenderTexture.active = prev;
GL.sRGBWrite = prevSRGB;
}
return targetTexture;
}
public static void GetResolveTextureSize(AlphaPacking alphaPacking, StereoPacking stereoPacking, StereoEye eyeMode, float pixelAspectRatio, ref int width, ref int height)
{
switch (alphaPacking)
{
case AlphaPacking.LeftRight:
width /= 2;
break;
case AlphaPacking.TopBottom:
height /= 2;
break;
}
if (eyeMode != StereoEye.Both)
{
switch (stereoPacking)
{
case StereoPacking.LeftRight:
width /= 2;
break;
case StereoPacking.TopBottom:
height /= 2;
break;
}
}
if (pixelAspectRatio > 0f)
{
if (pixelAspectRatio > 1f)
{
width = Mathf.RoundToInt(width * pixelAspectRatio);
}
else if (pixelAspectRatio < 1f)
{
height = Mathf.RoundToInt(height / pixelAspectRatio);
}
}
// TODO: take into account rotation
}
public static bool RequiresResolve(ITextureProducer texture)
{
return (texture.GetTextureAlphaPacking() != AlphaPacking.None ||
texture.RequiresVerticalFlip() ||
texture.GetTextureStereoPacking() != StereoPacking.None ||
texture.GetTextureCount() > 1
);
}
public static void DrawTexture(Rect destRect, Texture texture, ScaleMode scaleMode, AlphaPacking alphaPacking, float pixelAspectRatio, Material material)
{
if (Event.current == null || Event.current.type == EventType.Repaint)
{
int sourceWidth = texture.width;
int sourceHeight = texture.height;
GetResolveTextureSize(alphaPacking, StereoPacking.Unknown, StereoEye.Both, pixelAspectRatio, ref sourceWidth, ref sourceHeight);
float sourceRatio = (float)sourceWidth / (float)sourceHeight;
Rect sourceRect = new Rect(0f, 0f, 1f, 1f);
switch (scaleMode)
{
case ScaleMode.ScaleAndCrop:
{
float destRatio = destRect.width / destRect.height;
if (destRatio > sourceRatio)
{
float adjust = sourceRatio / destRatio;
sourceRect = new Rect(0f, (1f - adjust) * 0.5f, 1f, adjust);
}
else
{
float adjust = destRatio / sourceRatio;
sourceRect = new Rect(0.5f - adjust * 0.5f, 0f, adjust, 1f);
}
}
break;
case ScaleMode.ScaleToFit:
{
float destRatio = destRect.width / destRect.height;
if (destRatio > sourceRatio)
{
float adjust = sourceRatio / destRatio;
destRect = new Rect(destRect.xMin + destRect.width * (1f - adjust) * 0.5f, destRect.yMin, adjust * destRect.width, destRect.height);
}
else
{
float adjust = destRatio / sourceRatio;
destRect = new Rect(destRect.xMin, destRect.yMin + destRect.height * (1f - adjust) * 0.5f, destRect.width, adjust * destRect.height);
}
}
break;
case ScaleMode.StretchToFill:
break;
}
GL.PushMatrix();
if (RenderTexture.active == null)
{
//GL.LoadPixelMatrix();
GL.LoadPixelMatrix(0f, Screen.width, Screen.height, 0f);
}
else
{
GL.LoadPixelMatrix(0f, RenderTexture.active.width, RenderTexture.active.height, 0f);
}
Graphics.DrawTexture(destRect, texture, sourceRect, 0, 0, 0, 0, GUI.color, material);
GL.PopMatrix();
}
}
}
}
@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: a928f61fef33d1d4986b2190310027bc
timeCreated: 1547737745
licenseType: Pro
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: