- 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,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: