- Added trees outside the stable.
- Added a video player with horse songs preloaded. - Adjusted post-processing to be less harsh outdoors, and more visible indoors.
This commit is contained in:
@@ -0,0 +1,124 @@
|
||||
#if UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX || UNITY_IOS || UNITY_TVOS
|
||||
#define UNITY_PLATFORM_SUPPORTS_YPCBCR
|
||||
#endif
|
||||
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace RenderHeads.Media.AVProVideo
|
||||
{
|
||||
/// <summary>
|
||||
/// Base class to apply texture from MediaPlayer
|
||||
/// </summary>
|
||||
public abstract class ApplyToBase : MonoBehaviour
|
||||
{
|
||||
[Header("Media Source")]
|
||||
[Space(8f)]
|
||||
|
||||
[SerializeField] protected MediaPlayer _media = null;
|
||||
|
||||
public MediaPlayer Player
|
||||
{
|
||||
get { return _media; }
|
||||
set { ChangeMediaPlayer(value); }
|
||||
}
|
||||
|
||||
[Space(8f)]
|
||||
[Header("Display")]
|
||||
|
||||
[SerializeField] bool _automaticStereoPacking = true;
|
||||
public bool AutomaticStereoPacking
|
||||
{
|
||||
get { return _automaticStereoPacking; }
|
||||
set { if (_automaticStereoPacking != value) { _automaticStereoPacking = value; _isDirty = true; } }
|
||||
}
|
||||
|
||||
[SerializeField] StereoPacking _overrideStereoPacking = StereoPacking.None;
|
||||
public StereoPacking OverrideStereoPacking
|
||||
{
|
||||
get { return _overrideStereoPacking; }
|
||||
set { if (_overrideStereoPacking != value) { _overrideStereoPacking = value; _isDirty = true; } }
|
||||
}
|
||||
|
||||
[SerializeField] bool _stereoRedGreenTint = false;
|
||||
public bool StereoRedGreenTint { get { return _stereoRedGreenTint; } set { if (_stereoRedGreenTint != value) { _stereoRedGreenTint = value; _isDirty = true; } } }
|
||||
|
||||
protected bool _isDirty = false;
|
||||
|
||||
void Awake()
|
||||
{
|
||||
ChangeMediaPlayer(_media, force:true);
|
||||
}
|
||||
|
||||
private void ChangeMediaPlayer(MediaPlayer player, bool force = false)
|
||||
{
|
||||
if (_media != player || force)
|
||||
{
|
||||
if (_media != null)
|
||||
{
|
||||
_media.Events.RemoveListener(OnMediaPlayerEvent);
|
||||
}
|
||||
_media = player;
|
||||
if (_media != null)
|
||||
{
|
||||
_media.Events.AddListener(OnMediaPlayerEvent);
|
||||
}
|
||||
_isDirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Callback function to handle events
|
||||
private void OnMediaPlayerEvent(MediaPlayer mp, MediaPlayerEvent.EventType et, ErrorCode errorCode)
|
||||
{
|
||||
switch (et)
|
||||
{
|
||||
case MediaPlayerEvent.EventType.FirstFrameReady:
|
||||
case MediaPlayerEvent.EventType.PropertiesChanged:
|
||||
ForceUpdate();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public void ForceUpdate()
|
||||
{
|
||||
_isDirty = true;
|
||||
if (this.isActiveAndEnabled)
|
||||
{
|
||||
Apply();
|
||||
}
|
||||
}
|
||||
|
||||
private void Start()
|
||||
{
|
||||
SaveProperties();
|
||||
Apply();
|
||||
}
|
||||
|
||||
protected virtual void OnEnable()
|
||||
{
|
||||
SaveProperties();
|
||||
ForceUpdate();
|
||||
}
|
||||
|
||||
protected virtual void OnDisable()
|
||||
{
|
||||
RestoreProperties();
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
ChangeMediaPlayer(null);
|
||||
}
|
||||
|
||||
protected virtual void SaveProperties()
|
||||
{
|
||||
}
|
||||
|
||||
protected virtual void RestoreProperties()
|
||||
{
|
||||
}
|
||||
|
||||
public abstract void Apply();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 33aa9dfec55e1f6438ee868d02dcabe2
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,215 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using System;
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Copyright 2015-2021 RenderHeads Ltd. All rights reserved.
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
namespace RenderHeads.Media.AVProVideo
|
||||
{
|
||||
/// <summary>
|
||||
/// A singleton to handle multiple instances of the AudioOutput component
|
||||
/// </summary>
|
||||
public class AudioOutputManager
|
||||
{
|
||||
private static AudioOutputManager _instance = null;
|
||||
|
||||
public static AudioOutputManager Instance
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_instance == null)
|
||||
{
|
||||
_instance = new AudioOutputManager();
|
||||
}
|
||||
return _instance;
|
||||
}
|
||||
}
|
||||
|
||||
protected class PlayerInstance
|
||||
{
|
||||
public HashSet<AudioOutput> outputs;
|
||||
public float[] pcmData;
|
||||
public bool isPcmDataReady;
|
||||
}
|
||||
|
||||
private Dictionary<MediaPlayer, PlayerInstance> _instances;
|
||||
|
||||
private AudioOutputManager()
|
||||
{
|
||||
_instances = new Dictionary<MediaPlayer, PlayerInstance>();
|
||||
}
|
||||
|
||||
public void RequestAudio(AudioOutput outputComponent, MediaPlayer mediaPlayer, float[] audioData, int audioChannelCount, int channelMask, AudioOutput.AudioOutputMode audioOutputMode, bool supportPositionalAudio)
|
||||
{
|
||||
if (mediaPlayer == null || mediaPlayer.Control == null)
|
||||
{
|
||||
if (supportPositionalAudio)
|
||||
{
|
||||
ZeroAudio(audioData, 0);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
int channels = mediaPlayer.Control.GetAudioChannelCount();
|
||||
if (channels <= 0)
|
||||
{
|
||||
if (supportPositionalAudio)
|
||||
{
|
||||
ZeroAudio(audioData, 0);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// total samples requested should be multiple of channels
|
||||
Debug.Assert(audioData.Length % audioChannelCount == 0);
|
||||
|
||||
// Find or create an instance
|
||||
PlayerInstance instance = null;
|
||||
if (!_instances.TryGetValue(mediaPlayer, out instance))
|
||||
{
|
||||
instance = _instances[mediaPlayer] = new PlayerInstance()
|
||||
{
|
||||
outputs = new HashSet<AudioOutput>(),
|
||||
pcmData = null
|
||||
};
|
||||
}
|
||||
|
||||
// requests data if it hasn't been requested yet for the current cycle
|
||||
if (instance.outputs.Count == 0 || instance.outputs.Contains(outputComponent) || instance.pcmData == null)
|
||||
{
|
||||
instance.outputs.Clear();
|
||||
|
||||
int actualDataRequired = (audioData.Length * channels) / audioChannelCount;
|
||||
if (instance.pcmData == null || actualDataRequired != instance.pcmData.Length)
|
||||
{
|
||||
instance.pcmData = new float[actualDataRequired];
|
||||
}
|
||||
|
||||
instance.isPcmDataReady = GrabAudio(mediaPlayer, instance.pcmData, channels);
|
||||
|
||||
instance.outputs.Add(outputComponent);
|
||||
}
|
||||
|
||||
if (instance.isPcmDataReady)
|
||||
{
|
||||
// calculate how many samples and what channels are needed and then copy over the data
|
||||
int samples = Math.Min(audioData.Length / audioChannelCount, instance.pcmData.Length / channels);
|
||||
int storedPos = 0;
|
||||
int requestedPos = 0;
|
||||
|
||||
// multiple mode, copies over audio from desired channels into the same channels on the audiosource
|
||||
if (audioOutputMode == AudioOutput.AudioOutputMode.MultipleChannels)
|
||||
{
|
||||
int lesserChannels = Math.Min(channels, audioChannelCount);
|
||||
|
||||
if (!supportPositionalAudio)
|
||||
{
|
||||
for (int i = 0; i < samples; ++i)
|
||||
{
|
||||
for (int j = 0; j < lesserChannels; ++j)
|
||||
{
|
||||
if ((1 << j & channelMask) > 0)
|
||||
{
|
||||
audioData[requestedPos + j] = instance.pcmData[storedPos + j];
|
||||
}
|
||||
}
|
||||
|
||||
storedPos += channels;
|
||||
requestedPos += audioChannelCount;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = 0; i < samples; ++i)
|
||||
{
|
||||
for (int j = 0; j < lesserChannels; ++j)
|
||||
{
|
||||
if ((1 << j & channelMask) > 0)
|
||||
{
|
||||
audioData[requestedPos + j] *= instance.pcmData[storedPos + j];
|
||||
}
|
||||
}
|
||||
|
||||
storedPos += channels;
|
||||
requestedPos += audioChannelCount;
|
||||
}
|
||||
}
|
||||
}
|
||||
//Mono mode, copies over single channel to all output channels
|
||||
else if (audioOutputMode == AudioOutput.AudioOutputMode.OneToAllChannels)
|
||||
{
|
||||
int desiredChannel = 0;
|
||||
|
||||
for (int i = 0; i < 8; ++i)
|
||||
{
|
||||
if ((channelMask & (1 << i)) > 0)
|
||||
{
|
||||
desiredChannel = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (desiredChannel < channels)
|
||||
{
|
||||
if (!supportPositionalAudio)
|
||||
{
|
||||
for (int i = 0; i < samples; ++i)
|
||||
{
|
||||
for (int j = 0; j < audioChannelCount; ++j)
|
||||
{
|
||||
audioData[requestedPos + j] = instance.pcmData[storedPos + desiredChannel];
|
||||
}
|
||||
|
||||
storedPos += channels;
|
||||
requestedPos += audioChannelCount;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = 0; i < samples; ++i)
|
||||
{
|
||||
for (int j = 0; j < audioChannelCount; ++j)
|
||||
{
|
||||
audioData[requestedPos + j] *= instance.pcmData[storedPos + desiredChannel];
|
||||
}
|
||||
|
||||
storedPos += channels;
|
||||
requestedPos += audioChannelCount;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If there is left over audio
|
||||
if (supportPositionalAudio && requestedPos != audioData.Length)
|
||||
{
|
||||
// Zero the remaining audio data otherwise there are pops
|
||||
ZeroAudio(audioData, requestedPos);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (supportPositionalAudio)
|
||||
{
|
||||
// Zero the remaining audio data otherwise there are pops
|
||||
ZeroAudio(audioData, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ZeroAudio(float[] audioData, int startPosition)
|
||||
{
|
||||
for (int i = startPosition; i < audioData.Length; i++)
|
||||
{
|
||||
audioData[i] = 0f;
|
||||
}
|
||||
}
|
||||
|
||||
private bool GrabAudio(MediaPlayer player, float[] audioData, int channelCount)
|
||||
{
|
||||
return (0 != player.Control.GrabAudio(audioData, audioData.Length, channelCount));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 714026a371bd2d64c86edb3dab5607d9
|
||||
timeCreated: 1495699104
|
||||
licenseType: Free
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,654 @@
|
||||
#if UNITY_EDITOR || UNITY_STANDALONE_OSX || UNITY_STANDALONE_WIN || UNITY_IOS || UNITY_ANDROID
|
||||
#define UNITY_PLATFORM_SUPPORTS_LINEAR
|
||||
#endif
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Copyright 2015-2021 RenderHeads Ltd. All rights reserved.
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
namespace RenderHeads.Media.AVProVideo
|
||||
{
|
||||
/// <summary>
|
||||
/// Base class for all platform specific MediaPlayers
|
||||
/// </summary>
|
||||
public abstract partial class BaseMediaPlayer : IMediaPlayer, IMediaControl, IMediaInfo, IMediaCache, ITextureProducer, IMediaSubtitles, IVideoTracks, IAudioTracks, ITextTracks, IBufferedDisplay, System.IDisposable
|
||||
{
|
||||
public BaseMediaPlayer()
|
||||
{
|
||||
InitTracks();
|
||||
}
|
||||
|
||||
public abstract string GetVersion();
|
||||
public abstract string GetExpectedVersion();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public abstract bool OpenMedia(string path, long offset, string customHttpHeaders, MediaHints mediaHints, int forceFileFormat = 0, bool startWithHighestBitrate = false);
|
||||
|
||||
#if NETFX_CORE
|
||||
/// <inheritdoc/>
|
||||
public virtual bool OpenMedia(Windows.Storage.Streams.IRandomAccessStream ras, string path, long offset, string customHttpHeaders) { return false; }
|
||||
#endif
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual bool OpenMediaFromBuffer(byte[] buffer) { return false; }
|
||||
/// <inheritdoc/>
|
||||
public virtual bool StartOpenMediaFromBuffer(ulong length) { return false; }
|
||||
/// <inheritdoc/>
|
||||
public virtual bool AddChunkToMediaBuffer(byte[] chunk, ulong offset, ulong length) { return false; }
|
||||
/// <inheritdoc/>
|
||||
public virtual bool EndOpenMediaFromBuffer() { return false; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual void CloseMedia()
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
_displayRateLastRealTime = 0f;
|
||||
#endif
|
||||
_displayRateTimer = 0f;
|
||||
_displayRateLastFrameCount = 0;
|
||||
_displayRate = 0f;
|
||||
|
||||
_stallDetectionTimer = 0f;
|
||||
_stallDetectionFrame = 0;
|
||||
_lastError = ErrorCode.None;
|
||||
|
||||
_textTracks.Clear();
|
||||
_audioTracks.Clear();
|
||||
_videoTracks.Clear();
|
||||
_currentTextCue = null;
|
||||
_mediaHints = new MediaHints();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public abstract void SetLooping(bool looping);
|
||||
/// <inheritdoc/>
|
||||
public abstract bool IsLooping();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public abstract bool HasMetaData();
|
||||
/// <inheritdoc/>
|
||||
public abstract bool CanPlay();
|
||||
/// <inheritdoc/>
|
||||
public abstract void Play();
|
||||
/// <inheritdoc/>
|
||||
public abstract void Pause();
|
||||
/// <inheritdoc/>
|
||||
public abstract void Stop();
|
||||
/// <inheritdoc/>
|
||||
public virtual void Rewind() { SeekFast(0.0); }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public abstract void Seek(double time);
|
||||
/// <inheritdoc/>
|
||||
public abstract void SeekFast(double time);
|
||||
/// <inheritdoc/>
|
||||
public virtual void SeekWithTolerance(double time, double timeDeltaBefore, double timeDeltaAfter) { Seek(time); }
|
||||
/// <inheritdoc/>
|
||||
public abstract double GetCurrentTime();
|
||||
/// <inheritdoc/>
|
||||
public virtual DateTime GetProgramDateTime() { return DateTime.MinValue; }
|
||||
/// <inheritdoc/>
|
||||
public abstract float GetPlaybackRate();
|
||||
/// <inheritdoc/>
|
||||
public abstract void SetPlaybackRate(float rate);
|
||||
|
||||
// Basic Properties
|
||||
/// <inheritdoc/>
|
||||
public abstract double GetDuration();
|
||||
/// <inheritdoc/>
|
||||
public abstract int GetVideoWidth();
|
||||
/// <inheritdoc/>
|
||||
public abstract int GetVideoHeight();
|
||||
/// <inheritdoc/>
|
||||
public abstract float GetVideoFrameRate();
|
||||
/// <inheritdoc/>
|
||||
public virtual float GetVideoDisplayRate() { return _displayRate; }
|
||||
/// <inheritdoc/>
|
||||
public abstract bool HasAudio();
|
||||
/// <inheritdoc/>
|
||||
public abstract bool HasVideo();
|
||||
/// <inheritdoc/>
|
||||
public bool IsVideoStereo() { return GetTextureStereoPacking() != StereoPacking.None; }
|
||||
|
||||
// Basic State
|
||||
/// <inheritdoc/>
|
||||
public abstract bool IsSeeking();
|
||||
/// <inheritdoc/>
|
||||
public abstract bool IsPlaying();
|
||||
/// <inheritdoc/>
|
||||
public abstract bool IsPaused();
|
||||
/// <inheritdoc/>
|
||||
public abstract bool IsFinished();
|
||||
/// <inheritdoc/>
|
||||
public abstract bool IsBuffering();
|
||||
/// <inheritdoc/>
|
||||
public virtual bool WaitForNextFrame(Camera dummyCamera, int previousFrameCount) { return false; }
|
||||
|
||||
// Textures
|
||||
/// <inheritdoc/>
|
||||
public virtual int GetTextureCount() { return 1; }
|
||||
/// <inheritdoc/>
|
||||
public abstract Texture GetTexture(int index = 0);
|
||||
/// <inheritdoc/>
|
||||
public abstract int GetTextureFrameCount();
|
||||
/// <inheritdoc/>
|
||||
public virtual bool SupportsTextureFrameCount() { return true; }
|
||||
/// <inheritdoc/>
|
||||
public virtual long GetTextureTimeStamp() { return long.MinValue; }
|
||||
/// <inheritdoc/>
|
||||
public abstract bool RequiresVerticalFlip();
|
||||
/// <inheritdoc/>
|
||||
public virtual float[] GetTextureTransform() { return new float[] { 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f }; }
|
||||
/// <inheritdoc/>
|
||||
public virtual float GetTexturePixelAspectRatio() { return 1f; }
|
||||
/// <inheritdoc/>
|
||||
public virtual Matrix4x4 GetYpCbCrTransform() { return Matrix4x4.identity; }
|
||||
|
||||
public StereoPacking GetTextureStereoPacking()
|
||||
{
|
||||
StereoPacking result = InternalGetTextureStereoPacking();
|
||||
if (result == StereoPacking.Unknown)
|
||||
{
|
||||
// If stereo is unknown, fall back to media hints or no packing
|
||||
result = _mediaHints.stereoPacking;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
internal abstract StereoPacking InternalGetTextureStereoPacking();
|
||||
|
||||
public virtual TransparencyMode GetTextureTransparency()
|
||||
{
|
||||
return _mediaHints.transparency;
|
||||
}
|
||||
|
||||
public AlphaPacking GetTextureAlphaPacking()
|
||||
{
|
||||
if (GetTextureTransparency() == TransparencyMode.Transparent)
|
||||
{
|
||||
return _mediaHints.alphaPacking;
|
||||
}
|
||||
return AlphaPacking.None;
|
||||
}
|
||||
|
||||
// Audio General
|
||||
/// <inheritdoc/>
|
||||
public abstract void MuteAudio(bool bMuted);
|
||||
/// <inheritdoc/>
|
||||
public abstract bool IsMuted();
|
||||
/// <inheritdoc/>
|
||||
public abstract void SetVolume(float volume);
|
||||
/// <inheritdoc/>
|
||||
public virtual void SetBalance(float balance) { }
|
||||
/// <inheritdoc/>
|
||||
public abstract float GetVolume();
|
||||
/// <inheritdoc/>
|
||||
public virtual float GetBalance() { return 0f; }
|
||||
|
||||
// Audio Grabbing
|
||||
/// <inheritdoc/>
|
||||
public virtual int GetAudioChannelCount() { return -1; }
|
||||
/// <inheritdoc/>
|
||||
public virtual AudioChannelMaskFlags GetAudioChannelMask() { return 0; }
|
||||
/// <inheritdoc/>
|
||||
public virtual int GrabAudio(float[] audioData, int audioDataFloatCount, int channelCount) { return 0; }
|
||||
/// <inheritdoc/>
|
||||
public virtual int GetAudioBufferedSampleCount() { return 0; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual void AudioConfigurationChanged(bool deviceChanged) { }
|
||||
|
||||
// 360 Audio
|
||||
/// <inheritdoc/>
|
||||
public virtual void SetAudioHeadRotation(Quaternion q) { }
|
||||
/// <inheritdoc/>
|
||||
public virtual void ResetAudioHeadRotation() { }
|
||||
/// <inheritdoc/>
|
||||
public virtual void SetAudioChannelMode(Audio360ChannelMode channelMode) { }
|
||||
/// <inheritdoc/>
|
||||
public virtual void SetAudioFocusEnabled(bool enabled) { }
|
||||
/// <inheritdoc/>
|
||||
public virtual void SetAudioFocusProperties(float offFocusLevel, float widthDegrees) { }
|
||||
/// <inheritdoc/>
|
||||
public virtual void SetAudioFocusRotation(Quaternion q) { }
|
||||
/// <inheritdoc/>
|
||||
public virtual void ResetAudioFocus() { }
|
||||
|
||||
// Streaming
|
||||
/// <inheritdoc/>
|
||||
public virtual long GetEstimatedTotalBandwidthUsed() { return -1; }
|
||||
/// <inheritdoc/>
|
||||
public virtual void SetPlayWithoutBuffering(bool playWithoutBuffering) { }
|
||||
|
||||
// Caching
|
||||
/// <inheritdoc/>
|
||||
public virtual bool IsMediaCachingSupported() { return false; }
|
||||
/// <inheritdoc/>
|
||||
public virtual void AddMediaToCache(string url, string headers, MediaCachingOptions options) { }
|
||||
/// <inheritdoc/>
|
||||
public virtual void CancelDownloadOfMediaToCache(string url) { }
|
||||
/// <inheritdoc/>
|
||||
public virtual void PauseDownloadOfMediaToCache(string url) { }
|
||||
/// <inheritdoc/>
|
||||
public virtual void ResumeDownloadOfMediaToCache(string url) { }
|
||||
/// <inheritdoc/>
|
||||
public virtual void RemoveMediaFromCache(string url) { }
|
||||
/// <inheritdoc/>
|
||||
public virtual CachedMediaStatus GetCachedMediaStatus(string url, ref float progress) { return CachedMediaStatus.NotCached; }
|
||||
// /// <inheritdoc/>
|
||||
// public virtual bool IsMediaCached() { return false; }
|
||||
|
||||
// External playback
|
||||
/// <inheritdoc/>
|
||||
public virtual bool IsExternalPlaybackSupported() { return false; }
|
||||
/// <inheritdoc/>
|
||||
public virtual bool IsExternalPlaybackActive() { return false; }
|
||||
/// <inheritdoc/>
|
||||
public virtual void SetAllowsExternalPlayback(bool enable) { }
|
||||
/// <inheritdoc/>
|
||||
public virtual void SetExternalPlaybackVideoGravity(ExternalPlaybackVideoGravity gravity) { }
|
||||
|
||||
// Authentication
|
||||
//public virtual void SetKeyServerURL(string url) { }
|
||||
/// <inheritdoc/>
|
||||
public virtual void SetKeyServerAuthToken(string token) { }
|
||||
/// <inheritdoc/>
|
||||
public virtual void SetOverrideDecryptionKey(byte[] key) { }
|
||||
|
||||
// General
|
||||
/// <inheritdoc/>
|
||||
public abstract void Update();
|
||||
/// <inheritdoc/>
|
||||
public /*abstract*/virtual void BeginRender() { }
|
||||
/// <inheritdoc/>
|
||||
public abstract void Render();
|
||||
/// <inheritdoc/>
|
||||
public abstract void Dispose();
|
||||
|
||||
// Internal method
|
||||
public virtual bool GetDecoderPerformance(ref int activeDecodeThreadCount, ref int decodedFrameCount, ref int droppedFrameCount) { return false; }
|
||||
|
||||
#if false
|
||||
public void Update()
|
||||
{
|
||||
Native.Update(_instance);
|
||||
if (UpdateTracks())
|
||||
{
|
||||
|
||||
}
|
||||
if (UpdateTextCue())
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
public virtual void EndUpdate() { }
|
||||
|
||||
public virtual IntPtr GetNativePlayerHandle() { return IntPtr.Zero; }
|
||||
|
||||
public ErrorCode GetLastError()
|
||||
{
|
||||
ErrorCode errorCode = _lastError;
|
||||
_lastError = ErrorCode.None;
|
||||
return errorCode;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual long GetLastExtendedErrorCode()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
public string GetPlayerDescription()
|
||||
{
|
||||
return _playerDescription;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual bool PlayerSupportsLinearColorSpace()
|
||||
{
|
||||
#if UNITY_PLATFORM_SUPPORTS_LINEAR
|
||||
return true;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
protected string _playerDescription = string.Empty;
|
||||
protected ErrorCode _lastError = ErrorCode.None;
|
||||
protected FilterMode _defaultTextureFilterMode = FilterMode.Bilinear;
|
||||
protected TextureWrapMode _defaultTextureWrapMode = TextureWrapMode.Clamp;
|
||||
protected int _defaultTextureAnisoLevel = 1;
|
||||
protected MediaHints _mediaHints;
|
||||
protected TimeRanges _seekableTimes = new TimeRanges();
|
||||
protected TimeRanges _bufferedTimes = new TimeRanges();
|
||||
|
||||
public TimeRanges GetSeekableTimes() { return _seekableTimes; }
|
||||
public TimeRanges GetBufferedTimes() { return _bufferedTimes; }
|
||||
|
||||
public void GetTextureProperties(out FilterMode filterMode, out TextureWrapMode wrapMode, out int anisoLevel)
|
||||
{
|
||||
filterMode = _defaultTextureFilterMode;
|
||||
wrapMode = _defaultTextureWrapMode;
|
||||
anisoLevel = _defaultTextureAnisoLevel;
|
||||
}
|
||||
|
||||
public void SetTextureProperties(FilterMode filterMode = FilterMode.Bilinear, TextureWrapMode wrapMode = TextureWrapMode.Clamp, int anisoLevel = 0)
|
||||
{
|
||||
_defaultTextureFilterMode = filterMode;
|
||||
_defaultTextureWrapMode = wrapMode;
|
||||
_defaultTextureAnisoLevel = anisoLevel;
|
||||
for (int i = 0; i < GetTextureCount(); ++i)
|
||||
{
|
||||
ApplyTextureProperties(GetTexture(i));
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void ApplyTextureProperties(Texture texture)
|
||||
{
|
||||
if (texture != null)
|
||||
{
|
||||
texture.filterMode = _defaultTextureFilterMode;
|
||||
texture.wrapMode = _defaultTextureWrapMode;
|
||||
texture.anisoLevel = _defaultTextureAnisoLevel;
|
||||
}
|
||||
}
|
||||
|
||||
#region Video Display Rate
|
||||
#if UNITY_EDITOR
|
||||
private float _displayRateLastRealTime = 0f;
|
||||
#endif
|
||||
private float _displayRateTimer;
|
||||
private int _displayRateLastFrameCount;
|
||||
private float _displayRate = 1f;
|
||||
|
||||
protected void UpdateDisplayFrameRate()
|
||||
{
|
||||
const float IntervalSeconds = 0.5f;
|
||||
if (_displayRateTimer >= IntervalSeconds)
|
||||
{
|
||||
int frameCount = GetTextureFrameCount();
|
||||
int frameDelta = (frameCount - _displayRateLastFrameCount);
|
||||
_displayRate = (float)frameDelta / _displayRateTimer;
|
||||
_displayRateTimer -= IntervalSeconds;
|
||||
if (_displayRateTimer >= IntervalSeconds) _displayRateTimer -= IntervalSeconds;
|
||||
if (_displayRateTimer >= IntervalSeconds) _displayRateTimer = 0f;
|
||||
_displayRateLastFrameCount = frameCount;
|
||||
}
|
||||
|
||||
float deltaTime = Time.deltaTime;
|
||||
#if UNITY_EDITOR
|
||||
if (!Application.isPlaying)
|
||||
{
|
||||
// When not playing Time.deltaTime isn't valid so we have to derive it
|
||||
deltaTime = (Time.realtimeSinceStartup - _displayRateLastRealTime);
|
||||
_displayRateLastRealTime = Time.realtimeSinceStartup;
|
||||
}
|
||||
#endif
|
||||
_displayRateTimer += deltaTime;
|
||||
}
|
||||
#endregion // Video Display Rate
|
||||
|
||||
#region Stall Detection
|
||||
protected bool IsExpectingNewVideoFrame()
|
||||
{
|
||||
if (HasVideo())
|
||||
{
|
||||
// If we're playing then we expect a new frame
|
||||
if (!IsFinished() && (!IsPaused() && IsPlaying() && GetPlaybackRate() != 0.0f))
|
||||
{
|
||||
// Check that the video is not a single frame and therefore there is no other frame to display
|
||||
bool isSingleFrame = (GetTextureFrameCount() > 0 && GetDurationFrames() == 1);
|
||||
if (!isSingleFrame)
|
||||
{
|
||||
// NOTE: if a new frame isn't available then we could either be seeking or stalled
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual bool IsPlaybackStalled()
|
||||
{
|
||||
const float StallDetectionDuration = 0.5f;
|
||||
|
||||
// Manually detect stalled video if the platform doesn't have native support to detect it
|
||||
if (SupportsTextureFrameCount() && IsExpectingNewVideoFrame())
|
||||
{
|
||||
// Detect a new video frame
|
||||
int frameCount = GetTextureFrameCount();
|
||||
if (frameCount != _stallDetectionFrame)
|
||||
{
|
||||
_stallDetectionTimer = 0f;
|
||||
_stallDetectionFrame = frameCount;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Update the detection timer, but never more than once a Unity frame
|
||||
if (_stallDetectionGuard != Time.frameCount)
|
||||
{
|
||||
_stallDetectionTimer += Time.deltaTime;
|
||||
}
|
||||
}
|
||||
_stallDetectionGuard = Time.frameCount;
|
||||
|
||||
float thresholdDuration = StallDetectionDuration;
|
||||
|
||||
// Scale by the playback rate, but should be at least StallDetectionDuration
|
||||
thresholdDuration = Mathf.Max(thresholdDuration / Mathf.Abs(GetPlaybackRate()), StallDetectionDuration);
|
||||
|
||||
// If a valid FPS is available then make sure the thresholdDuration
|
||||
// is at least double that. This is mainly for very low FPS
|
||||
// content (eg 1 or 2 FPS)
|
||||
float fps = GetVideoFrameRate();
|
||||
if (fps > 0f && !float.IsNaN(fps))
|
||||
{
|
||||
thresholdDuration = Mathf.Max(thresholdDuration, 2f / fps);
|
||||
}
|
||||
|
||||
return (_stallDetectionTimer > thresholdDuration);
|
||||
}
|
||||
else
|
||||
{
|
||||
_stallDetectionTimer = 0f;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private float _stallDetectionTimer;
|
||||
private int _stallDetectionFrame;
|
||||
private int _stallDetectionGuard;
|
||||
#endregion // Stall Detection
|
||||
|
||||
protected List<Subtitle> _subtitles;
|
||||
protected Subtitle _currentSubtitle;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool LoadSubtitlesSRT(string data)
|
||||
{
|
||||
if (string.IsNullOrEmpty(data))
|
||||
{
|
||||
// Disable subtitles
|
||||
_subtitles = null;
|
||||
_currentSubtitle = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
_subtitles = SubtitleUtils.ParseSubtitlesSRT(data);
|
||||
_currentSubtitle = null;
|
||||
}
|
||||
return (_subtitles != null);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual void UpdateSubtitles()
|
||||
{
|
||||
if (_subtitles != null)
|
||||
{
|
||||
double time = GetCurrentTime();
|
||||
|
||||
// TODO: implement a more efficient subtitle index searcher
|
||||
int searchIndex = 0;
|
||||
if (_currentSubtitle != null)
|
||||
{
|
||||
if (!_currentSubtitle.IsTime(time))
|
||||
{
|
||||
if (time > _currentSubtitle.timeEnd)
|
||||
{
|
||||
searchIndex = _currentSubtitle.index + 1;
|
||||
}
|
||||
_currentSubtitle = null;
|
||||
}
|
||||
}
|
||||
|
||||
if (_currentSubtitle == null)
|
||||
{
|
||||
for (int i = searchIndex; i < _subtitles.Count; i++)
|
||||
{
|
||||
if (_subtitles[i].IsTime(time))
|
||||
{
|
||||
_currentSubtitle = _subtitles[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual int GetSubtitleIndex()
|
||||
{
|
||||
int result = -1;
|
||||
if (_currentSubtitle != null)
|
||||
{
|
||||
result = _currentSubtitle.index;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual string GetSubtitleText()
|
||||
{
|
||||
string result = string.Empty;
|
||||
if (_currentSubtitle != null)
|
||||
{
|
||||
result = _currentSubtitle.text;
|
||||
}
|
||||
else if (_currentTextCue != null)
|
||||
{
|
||||
result = _currentTextCue.Text;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public virtual void OnEnable()
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public int GetCurrentTimeFrames(float overrideFrameRate = 0f)
|
||||
{
|
||||
int result = 0;
|
||||
float frameRate = (overrideFrameRate > 0f) ? overrideFrameRate : GetVideoFrameRate();
|
||||
if (frameRate > 0f)
|
||||
{
|
||||
result = Helper.ConvertTimeSecondsToFrame(GetCurrentTime(), frameRate);
|
||||
result = Mathf.Min(result, GetMaxFrameNumber(overrideFrameRate));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public int GetDurationFrames(float overrideFrameRate = 0f)
|
||||
{
|
||||
int result = 0;
|
||||
float frameRate = (overrideFrameRate > 0f) ? overrideFrameRate : GetVideoFrameRate();
|
||||
if (frameRate > 0f)
|
||||
{
|
||||
result = Helper.ConvertTimeSecondsToFrame(GetDuration(), frameRate);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public int GetMaxFrameNumber(float overrideFrameRate = 0f)
|
||||
{
|
||||
int result = GetDurationFrames(overrideFrameRate);
|
||||
result = Mathf.Max(0, result - 1);
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void SeekToFrameRelative(int frameOffset, float overrideFrameRate = 0f)
|
||||
{
|
||||
float frameRate = (overrideFrameRate > 0f)?overrideFrameRate:GetVideoFrameRate();
|
||||
if (frameRate > 0f)
|
||||
{
|
||||
int frame = Helper.ConvertTimeSecondsToFrame(GetCurrentTime(), frameRate);
|
||||
frame += frameOffset;
|
||||
frame = Mathf.Clamp(frame, 0, GetMaxFrameNumber(frameRate));
|
||||
double time = Helper.ConvertFrameToTimeSeconds(frame, frameRate);
|
||||
Seek(time);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void SeekToFrame(int frame, float overrideFrameRate = 0f)
|
||||
{
|
||||
float frameRate = (overrideFrameRate > 0f)?overrideFrameRate:GetVideoFrameRate();
|
||||
if (frameRate > 0f)
|
||||
{
|
||||
frame = Mathf.Clamp(frame, 0, GetMaxFrameNumber(frameRate));
|
||||
double time = Helper.ConvertFrameToTimeSeconds(frame, frameRate);
|
||||
Seek(time);
|
||||
}
|
||||
}
|
||||
|
||||
#region IBufferedDisplay Implementation
|
||||
|
||||
private int _unityFrameCountBufferedDisplayGuard = -1;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public long UpdateBufferedDisplay()
|
||||
{
|
||||
// Guard to make sure we're only updating the buffered frame once per Unity frame
|
||||
if (Time.frameCount == _unityFrameCountBufferedDisplayGuard) return GetTextureTimeStamp();
|
||||
|
||||
_unityFrameCountBufferedDisplayGuard = Time.frameCount;
|
||||
|
||||
return InternalUpdateBufferedDisplay();
|
||||
}
|
||||
|
||||
internal virtual long InternalUpdateBufferedDisplay() { return 0; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual BufferedFramesState GetBufferedFramesState()
|
||||
{
|
||||
return new BufferedFramesState();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual void SetSlaves(IBufferedDisplay[] slaves) { }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual void SetBufferedDisplayMode(BufferedFrameSelectionMode mode, IBufferedDisplay master = null) { }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual void SetBufferedDisplayOptions(bool pauseOnPrerollComplete) { }
|
||||
|
||||
#endregion // IBufferedDisplay Implementation
|
||||
|
||||
protected PlaybackQualityStats _playbackQualityStats = new PlaybackQualityStats();
|
||||
|
||||
public PlaybackQualityStats GetPlaybackQualityStats()
|
||||
{
|
||||
return _playbackQualityStats;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4f59504ca098e7d41b036917f4764ee0
|
||||
timeCreated: 1447782861
|
||||
licenseType: Free
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,106 @@
|
||||
using UnityEngine.Events;
|
||||
using System.Collections.Generic;
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Copyright 2015-2021 RenderHeads Ltd. All rights reserved.
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
namespace RenderHeads.Media.AVProVideo
|
||||
{
|
||||
[System.Serializable]
|
||||
public class MediaPlayerLoadEvent : UnityEvent<string> {}
|
||||
|
||||
[System.Serializable]
|
||||
public class MediaPlayerEvent : UnityEvent<MediaPlayer, MediaPlayerEvent.EventType, ErrorCode>
|
||||
{
|
||||
public enum EventType
|
||||
{
|
||||
MetaDataReady, // Triggered when meta data(width, duration etc) is available
|
||||
ReadyToPlay, // Triggered when the video is loaded and ready to play
|
||||
Started, // Triggered when the playback starts
|
||||
FirstFrameReady, // Triggered when the first frame has been rendered
|
||||
FinishedPlaying, // Triggered when a non-looping video has finished playing
|
||||
Closing, // Triggered when the media is closed
|
||||
Error, // Triggered when an error occurs
|
||||
SubtitleChange, // Triggered when the subtitles change
|
||||
Stalled, // Triggered when media is stalled (eg. when lost connection to media stream)
|
||||
Unstalled, // Triggered when media is resumed form a stalled state (eg. when lost connection is re-established)
|
||||
ResolutionChanged, // Triggered when the resolution of the video has changed (including the load) Useful for adaptive streams
|
||||
StartedSeeking, // Triggered when seeking begins
|
||||
FinishedSeeking, // Triggered when seeking has finished
|
||||
StartedBuffering, // Triggered when buffering begins
|
||||
FinishedBuffering, // Triggered when buffering has finished
|
||||
PropertiesChanged, // Triggered when any properties (eg stereo packing are changed) - this has to be triggered manually
|
||||
PlaylistItemChanged,// Triggered when the new item is played in the playlist
|
||||
PlaylistFinished, // Triggered when the playlist reaches the end
|
||||
|
||||
TextTracksChanged, // Triggered when the text tracks are added or removed
|
||||
TextCueChanged = SubtitleChange, // Triggered when the text to display changes
|
||||
|
||||
// TODO:
|
||||
//StartLoop, // Triggered when the video starts and is in loop mode
|
||||
//EndLoop, // Triggered when the video ends and is in loop mode
|
||||
//NewFrame // Trigger when a new video frame is available
|
||||
}
|
||||
|
||||
private List<UnityAction<MediaPlayer, MediaPlayerEvent.EventType, ErrorCode>> _listeners = new List<UnityAction<MediaPlayer, EventType, ErrorCode>>(4);
|
||||
|
||||
public bool HasListeners()
|
||||
{
|
||||
return (_listeners.Count > 0) || (GetPersistentEventCount() > 0);
|
||||
}
|
||||
|
||||
new public void AddListener(UnityAction<MediaPlayer, MediaPlayerEvent.EventType, ErrorCode> call)
|
||||
{
|
||||
if (!_listeners.Contains(call))
|
||||
{
|
||||
_listeners.Add(call);
|
||||
base.AddListener(call);
|
||||
}
|
||||
}
|
||||
|
||||
new public void RemoveListener(UnityAction<MediaPlayer, MediaPlayerEvent.EventType, ErrorCode> call)
|
||||
{
|
||||
int index = _listeners.IndexOf(call);
|
||||
if (index >= 0)
|
||||
{
|
||||
_listeners.RemoveAt(index);
|
||||
base.RemoveListener(call);
|
||||
}
|
||||
}
|
||||
|
||||
new public void RemoveAllListeners()
|
||||
{
|
||||
_listeners.Clear();
|
||||
base.RemoveAllListeners();
|
||||
}
|
||||
}
|
||||
|
||||
#if false
|
||||
public interface IMediaEvents
|
||||
{
|
||||
void AddEventListener(UnityAction<MediaPlayer, MediaPlayerEvent.EventType, ErrorCode> call);
|
||||
void RemoveListener(UnityAction<MediaPlayer, MediaPlayerEvent.EventType, ErrorCode> call);
|
||||
void RemoveAllEventListeners();
|
||||
}
|
||||
|
||||
public partial class BaseMediaPlayer
|
||||
{
|
||||
void AddEventListener(UnityAction<MediaPlayer, MediaPlayerEvent.EventType, ErrorCode> call)
|
||||
{
|
||||
|
||||
}
|
||||
void RemoveListener(UnityAction<MediaPlayer, MediaPlayerEvent.EventType, ErrorCode> call)
|
||||
{
|
||||
|
||||
}
|
||||
void RemoveAllEventListeners()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
private MediaPlayerEvent _eventHandler;
|
||||
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 16a5efbe992a09144ac89dde2b3e0898
|
||||
timeCreated: 1438695622
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,519 @@
|
||||
using UnityEngine;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Copyright 2015-2022 RenderHeads Ltd. All rights reserved.
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
namespace RenderHeads.Media.AVProVideo
|
||||
{
|
||||
public static class Helper
|
||||
{
|
||||
public const string AVProVideoVersion = "2.8.5";
|
||||
public sealed class ExpectedPluginVersion
|
||||
{
|
||||
public const string Windows = "2.8.5";
|
||||
public const string WinRT = "2.8.5";
|
||||
public const string Android = "2.8.5";
|
||||
public const string Apple = "2.8.5";
|
||||
}
|
||||
|
||||
public const string UnityBaseTextureName = "_MainTex";
|
||||
public const string UnityBaseTextureName_URP = "_BaseMap";
|
||||
public const string UnityBaseTextureName_HDRP = "_BaseColorMap";
|
||||
|
||||
public static string GetPath(MediaPathType location)
|
||||
{
|
||||
string result = string.Empty;
|
||||
switch (location)
|
||||
{
|
||||
case MediaPathType.AbsolutePathOrURL:
|
||||
break;
|
||||
case MediaPathType.RelativeToDataFolder:
|
||||
result = Application.dataPath;
|
||||
break;
|
||||
case MediaPathType.RelativeToPersistentDataFolder:
|
||||
result = Application.persistentDataPath;
|
||||
break;
|
||||
case MediaPathType.RelativeToProjectFolder:
|
||||
#if !UNITY_WINRT_8_1
|
||||
string path = "..";
|
||||
#if UNITY_STANDALONE_OSX && !UNITY_EDITOR_OSX
|
||||
path += "/..";
|
||||
#endif
|
||||
result = System.IO.Path.GetFullPath(System.IO.Path.Combine(Application.dataPath, path));
|
||||
result = result.Replace('\\', '/');
|
||||
#endif
|
||||
break;
|
||||
case MediaPathType.RelativeToStreamingAssetsFolder:
|
||||
result = Application.streamingAssetsPath;
|
||||
break;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public static string GetFilePath(string path, MediaPathType location)
|
||||
{
|
||||
string result = string.Empty;
|
||||
if (!string.IsNullOrEmpty(path))
|
||||
{
|
||||
switch (location)
|
||||
{
|
||||
case MediaPathType.AbsolutePathOrURL:
|
||||
result = path;
|
||||
break;
|
||||
case MediaPathType.RelativeToDataFolder:
|
||||
case MediaPathType.RelativeToPersistentDataFolder:
|
||||
case MediaPathType.RelativeToProjectFolder:
|
||||
case MediaPathType.RelativeToStreamingAssetsFolder:
|
||||
result = System.IO.Path.Combine(GetPath(location), path);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public static string GetFriendlyResolutionName(int width, int height, float fps)
|
||||
{
|
||||
// List of common 16:9 resolutions
|
||||
int[] areas = { 0, 7680 * 4320, 3840 * 2160, 2560 * 1440, 1920 * 1080, 1280 * 720, 853 * 480, 640 * 360, 426 * 240, 256 * 144 };
|
||||
string[] names = { "Unknown", "8K", "4K", "1440p", "1080p", "720p", "480p", "360p", "240p", "144p" };
|
||||
|
||||
Debug.Assert(areas.Length == names.Length);
|
||||
|
||||
// Find the closest resolution
|
||||
int closestAreaIndex = 0;
|
||||
int area = width * height;
|
||||
int minDelta = int.MaxValue;
|
||||
for (int i = 0; i < areas.Length; i++)
|
||||
{
|
||||
int d = Mathf.Abs(areas[i] - area);
|
||||
// TODO: add a maximum threshold to ignore differences that are too high
|
||||
if (d < minDelta)
|
||||
{
|
||||
closestAreaIndex = i;
|
||||
minDelta = d;
|
||||
// If the exact mode is found, early out
|
||||
if (d == 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
string result = names[closestAreaIndex];
|
||||
|
||||
// Append frame rate if valid
|
||||
if (fps > 0f && !float.IsNaN(fps))
|
||||
{
|
||||
result += fps.ToString("0.##");
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static string GetErrorMessage(ErrorCode code)
|
||||
{
|
||||
string result = string.Empty;
|
||||
switch (code)
|
||||
{
|
||||
case ErrorCode.None:
|
||||
result = "No Error";
|
||||
break;
|
||||
case ErrorCode.LoadFailed:
|
||||
result = "Loading failed. File not found, codec not supported, video resolution too high or insufficient system resources.";
|
||||
#if UNITY_STANDALONE_WIN || UNITY_EDITOR_WIN
|
||||
// Add extra information for older Windows versions that don't have support for modern codecs
|
||||
if (SystemInfo.operatingSystem.StartsWith("Windows XP") ||
|
||||
SystemInfo.operatingSystem.StartsWith("Windows Vista"))
|
||||
{
|
||||
result += " NOTE: Windows XP and Vista don't have native support for H.264 codec. Consider using an older codec such as DivX or installing 3rd party codecs such as LAV Filters.";
|
||||
}
|
||||
#endif
|
||||
break;
|
||||
case ErrorCode.DecodeFailed:
|
||||
result = "Decode failed. Possible codec not supported, video resolution/bit-depth too high, or insufficient system resources.";
|
||||
#if UNITY_ANDROID
|
||||
result += " On Android this is generally due to the hardware not having enough resources to decode the video. Most Android devices can only handle a maximum of one 4K video at once.";
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public static string GetPlatformName(Platform platform)
|
||||
{
|
||||
string result = "Unknown";
|
||||
switch (platform)
|
||||
{
|
||||
case Platform.WindowsUWP:
|
||||
result = "Windows UWP";
|
||||
break;
|
||||
case Platform.MacOSX:
|
||||
result = "macOS";
|
||||
break;
|
||||
default:
|
||||
result = platform.ToString();
|
||||
break;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public static string[] GetPlatformNames()
|
||||
{
|
||||
return new string[] {
|
||||
GetPlatformName(Platform.Windows),
|
||||
GetPlatformName(Platform.MacOSX),
|
||||
GetPlatformName(Platform.iOS),
|
||||
GetPlatformName(Platform.tvOS),
|
||||
GetPlatformName(Platform.Android),
|
||||
GetPlatformName(Platform.WindowsUWP),
|
||||
GetPlatformName(Platform.WebGL),
|
||||
};
|
||||
}
|
||||
|
||||
#if AVPROVIDEO_DISABLE_LOGGING
|
||||
[System.Diagnostics.Conditional("ALWAYS_FALSE")]
|
||||
#endif
|
||||
public static void LogInfo(string message, Object context = null)
|
||||
{
|
||||
if (context == null)
|
||||
{
|
||||
Debug.Log("[AVProVideo] " + message);
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.Log("[AVProVideo] " + message, context);
|
||||
}
|
||||
}
|
||||
|
||||
public static int GetUnityAudioSampleRate()
|
||||
{
|
||||
// For standalone builds (not in the editor):
|
||||
// In Unity 4.6, 5.0, 5.1 when audio is disabled there is no indication from the API.
|
||||
// But in 5.2.0 and above, it logs an error when trying to call
|
||||
// AudioSettings.GetDSPBufferSize() or AudioSettings.outputSampleRate
|
||||
// So to prevent the error, check if AudioSettings.GetConfiguration().sampleRate == 0
|
||||
return (AudioSettings.GetConfiguration().sampleRate == 0) ? 0 : AudioSettings.outputSampleRate;
|
||||
}
|
||||
|
||||
public static int GetUnityAudioSpeakerCount()
|
||||
{
|
||||
switch (AudioSettings.GetConfiguration().speakerMode)
|
||||
{
|
||||
case AudioSpeakerMode.Mono: return 1;
|
||||
case AudioSpeakerMode.Stereo: return 2;
|
||||
case AudioSpeakerMode.Quad: return 4;
|
||||
case AudioSpeakerMode.Surround: return 5;
|
||||
case AudioSpeakerMode.Mode5point1: return 6;
|
||||
case AudioSpeakerMode.Mode7point1: return 8;
|
||||
case AudioSpeakerMode.Prologic: return 2;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Returns a valid range to use for a timeline display
|
||||
// Either it will return the range 0..duration, or
|
||||
// for live streams it will return first seekable..last seekable time
|
||||
public static TimeRange GetTimelineRange(double duration, TimeRanges seekable)
|
||||
{
|
||||
TimeRange result = new TimeRange();
|
||||
if (duration >= 0.0 && duration < 2e10)
|
||||
{
|
||||
// Duration is valid
|
||||
result.startTime = 0f;
|
||||
result.duration = duration;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Duration is invalid, so it could be a live stream, so derive from seekable range
|
||||
result.startTime = seekable.MinTime;
|
||||
result.duration = seekable.Duration;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public const double SecondsToHNS = 10000000.0;
|
||||
public const double MilliSecondsToHNS = 10000.0;
|
||||
|
||||
public static string GetTimeString(double timeSeconds, bool showMilliseconds = false)
|
||||
{
|
||||
float totalSeconds = (float)timeSeconds;
|
||||
int hours = Mathf.FloorToInt(totalSeconds / (60f * 60f));
|
||||
float usedSeconds = hours * 60f * 60f;
|
||||
|
||||
int minutes = Mathf.FloorToInt((totalSeconds - usedSeconds) / 60f);
|
||||
usedSeconds += minutes * 60f;
|
||||
|
||||
int seconds = Mathf.FloorToInt(totalSeconds - usedSeconds);
|
||||
|
||||
string result;
|
||||
if (hours <= 0)
|
||||
{
|
||||
if (showMilliseconds)
|
||||
{
|
||||
int milliSeconds = (int)((totalSeconds - Mathf.Floor(totalSeconds)) * 1000f);
|
||||
result = string.Format("{0:00}:{1:00}:{2:000}", minutes, seconds, milliSeconds);
|
||||
}
|
||||
else
|
||||
{
|
||||
result = string.Format("{0:00}:{1:00}", minutes, seconds);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (showMilliseconds)
|
||||
{
|
||||
int milliSeconds = (int)((totalSeconds - Mathf.Floor(totalSeconds)) * 1000f);
|
||||
result = string.Format("{2}:{0:00}:{1:00}:{3:000}", minutes, seconds, hours, milliSeconds);
|
||||
}
|
||||
else
|
||||
{
|
||||
result = string.Format("{2}:{0:00}:{1:00}", minutes, seconds, hours);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convert texture transform matrix to an enum of orientation types
|
||||
/// </summary>
|
||||
public static Orientation GetOrientation(float[] t)
|
||||
{
|
||||
Orientation result = Orientation.Landscape;
|
||||
if (t != null)
|
||||
{
|
||||
// TODO: check that the Portrait and PortraitFlipped are the right way around
|
||||
if (t[0] == 0f && t[1]== 1f && t[2] == -1f && t[3] == 0f)
|
||||
{
|
||||
result = Orientation.Portrait;
|
||||
} else
|
||||
if (t[0] == 0f && t[1] == -1f && t[2] == 1f && t[3] == 0f)
|
||||
{
|
||||
result = Orientation.PortraitFlipped;
|
||||
} else
|
||||
if (t[0]== 1f && t[1] == 0f && t[2] == 0f && t[3] == 1f)
|
||||
{
|
||||
result = Orientation.Landscape;
|
||||
} else
|
||||
if (t[0] == -1f && t[1] == 0f && t[2] == 0f && t[3] == -1f)
|
||||
{
|
||||
result = Orientation.LandscapeFlipped;
|
||||
}
|
||||
else
|
||||
if (t[0] == 0f && t[1] == 1f && t[2] == 1f && t[3] == 0f)
|
||||
{
|
||||
result = Orientation.PortraitHorizontalMirror;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private static Matrix4x4 PortraitMatrix = Matrix4x4.TRS(new Vector3(0f, 1f, 0f), Quaternion.Euler(0f, 0f, -90f), Vector3.one);
|
||||
private static Matrix4x4 PortraitFlippedMatrix = Matrix4x4.TRS(new Vector3(1f, 0f, 0f), Quaternion.Euler(0f, 0f, 90f), Vector3.one);
|
||||
private static Matrix4x4 LandscapeFlippedMatrix = Matrix4x4.TRS(new Vector3(0f, 1f, 0f), Quaternion.Euler(0f, 0f, -90f), Vector3.one);
|
||||
|
||||
public static Matrix4x4 GetMatrixForOrientation(Orientation ori)
|
||||
{
|
||||
Matrix4x4 result;
|
||||
switch (ori)
|
||||
{
|
||||
case Orientation.Landscape:
|
||||
result = Matrix4x4.identity;
|
||||
break;
|
||||
case Orientation.LandscapeFlipped:
|
||||
result = LandscapeFlippedMatrix;
|
||||
break;
|
||||
case Orientation.Portrait:
|
||||
result = PortraitMatrix;
|
||||
break;
|
||||
case Orientation.PortraitFlipped:
|
||||
result = PortraitFlippedMatrix;
|
||||
break;
|
||||
case Orientation.PortraitHorizontalMirror:
|
||||
result = new Matrix4x4();
|
||||
result.SetColumn(0, new Vector4(0f, 1f, 0f, 0f));
|
||||
result.SetColumn(1, new Vector4(1f, 0f, 0f, 0f));
|
||||
result.SetColumn(2, new Vector4(0f, 0f, 1f, 0f));
|
||||
result.SetColumn(3, new Vector4(0f, 0f, 0f, 1f));
|
||||
break;
|
||||
default:
|
||||
throw new System.Exception("Unknown Orientation type");
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public static int ConvertTimeSecondsToFrame(double seconds, float frameRate)
|
||||
{
|
||||
// NOTE: Generally you should use RountToInt when converting from time to frame number
|
||||
// but because we're adding a half frame offset (which seems to be the safer thing to do) we need to FloorToInt
|
||||
seconds = System.Math.Max(0.0, seconds);
|
||||
frameRate = Mathf.Max(0f, frameRate);
|
||||
return (int)System.Math.Floor(frameRate * seconds);
|
||||
}
|
||||
|
||||
public static double ConvertFrameToTimeSeconds(int frame, float frameRate)
|
||||
{
|
||||
frame = Mathf.Max(0, frame);
|
||||
frameRate = Mathf.Max(0f, frameRate);
|
||||
double frameDurationSeconds = 1.0 / frameRate;
|
||||
return ((double)frame * frameDurationSeconds) + (frameDurationSeconds * 0.5); // Add half a frame we that the time lands in the middle of the frame range and not at the edges
|
||||
}
|
||||
|
||||
public static double FindNextKeyFrameTimeSeconds(double seconds, float frameRate, int keyFrameInterval)
|
||||
{
|
||||
seconds = System.Math.Max(0.0, seconds);
|
||||
frameRate = Mathf.Max(0f, frameRate);
|
||||
keyFrameInterval = Mathf.Max(0, keyFrameInterval);
|
||||
int currentFrame = Helper.ConvertTimeSecondsToFrame(seconds, frameRate);
|
||||
// TODO: allow specifying a minimum number of frames so that if currentFrame is too close to nextKeyFrame, it will calculate the next-next keyframe
|
||||
int nextKeyFrame = keyFrameInterval * Mathf.CeilToInt((float)(currentFrame + 1) / (float)keyFrameInterval);
|
||||
return Helper.ConvertFrameToTimeSeconds(nextKeyFrame, frameRate);
|
||||
}
|
||||
|
||||
public static System.DateTime ConvertSecondsSince1970ToDateTime(double secondsSince1970)
|
||||
{
|
||||
System.TimeSpan time = System.TimeSpan.FromSeconds(secondsSince1970);
|
||||
return new System.DateTime(1970, 1, 1).Add(time);
|
||||
}
|
||||
|
||||
#if (UNITY_EDITOR_WIN || (!UNITY_EDITOR && UNITY_STANDALONE_WIN))
|
||||
[System.Runtime.InteropServices.DllImport("kernel32.dll", CharSet = System.Runtime.InteropServices.CharSet.Unicode, EntryPoint = "GetShortPathNameW", SetLastError=true)]
|
||||
private static extern int GetShortPathName([System.Runtime.InteropServices.MarshalAs(System.Runtime.InteropServices.UnmanagedType.LPWStr)] string pathName,
|
||||
[System.Runtime.InteropServices.MarshalAs(System.Runtime.InteropServices.UnmanagedType.LPWStr)] System.Text.StringBuilder shortName,
|
||||
int cbShortName);
|
||||
|
||||
// Handle very long file paths by converting to DOS 8.3 format
|
||||
internal static string ConvertLongPathToShortDOS83Path(string path)
|
||||
{
|
||||
const string pathToken = @"\\?\";
|
||||
string result = pathToken + path.Replace("/","\\");
|
||||
int length = GetShortPathName(result, null, 0);
|
||||
if (length > 0)
|
||||
{
|
||||
System.Text.StringBuilder sb = new System.Text.StringBuilder(length);
|
||||
if (0 != GetShortPathName(result, sb, length))
|
||||
{
|
||||
result = sb.ToString().Replace(pathToken, "");
|
||||
Debug.LogWarning("[AVProVideo] Long path detected. Changing to DOS 8.3 format");
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
#endif
|
||||
|
||||
// Converts a non-readable texture to a readable Texture2D.
|
||||
// "targetTexture" can be null or you can pass in an existing texture.
|
||||
// Remember to Destroy() the returned texture after finished with it
|
||||
public static Texture2D GetReadableTexture(Texture inputTexture, bool requiresVerticalFlip, Orientation ori, Texture2D targetTexture = null)
|
||||
{
|
||||
Texture2D resultTexture = targetTexture;
|
||||
|
||||
RenderTexture prevRT = RenderTexture.active;
|
||||
|
||||
int textureWidth = inputTexture.width;
|
||||
int textureHeight = inputTexture.height;
|
||||
#if UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX || UNITY_IPHONE || UNITY_IOS || UNITY_TVOS
|
||||
if (ori == Orientation.Portrait || ori == Orientation.PortraitFlipped)
|
||||
{
|
||||
textureWidth = inputTexture.height;
|
||||
textureHeight = inputTexture.width;
|
||||
}
|
||||
#endif
|
||||
|
||||
// Blit the texture to a temporary RenderTexture
|
||||
// This handles any format conversion that is required and allows us to use ReadPixels to copy texture from RT to readable texture
|
||||
RenderTexture tempRT = RenderTexture.GetTemporary(textureWidth, textureHeight, 0, RenderTextureFormat.ARGB32);
|
||||
|
||||
if (ori == Orientation.Landscape)
|
||||
{
|
||||
if (!requiresVerticalFlip)
|
||||
{
|
||||
Graphics.Blit(inputTexture, tempRT);
|
||||
}
|
||||
else
|
||||
{
|
||||
// The above Blit can't flip unless using a material, so we use Graphics.DrawTexture instead
|
||||
GL.PushMatrix();
|
||||
RenderTexture.active = tempRT;
|
||||
GL.LoadPixelMatrix(0f, tempRT.width, 0f, tempRT.height);
|
||||
Rect sourceRect = new Rect(0f, 0f, 1f, 1f);
|
||||
// NOTE: not sure why we need to set y to -1, without this there is a 1px gap at the bottom
|
||||
Rect destRect = new Rect(0f, -1f, tempRT.width, tempRT.height);
|
||||
|
||||
Graphics.DrawTexture(destRect, inputTexture, sourceRect, 0, 0, 0, 0);
|
||||
GL.PopMatrix();
|
||||
GL.InvalidateState();
|
||||
}
|
||||
}
|
||||
#if UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX || UNITY_IPHONE || UNITY_IOS || UNITY_TVOS
|
||||
else
|
||||
{
|
||||
Matrix4x4 m = Matrix4x4.identity;
|
||||
switch (ori)
|
||||
{
|
||||
case Orientation.Portrait:
|
||||
m = Matrix4x4.TRS(new Vector3(0f, inputTexture.width, 0f), Quaternion.Euler(0f, 0f, -90f), Vector3.one);
|
||||
break;
|
||||
case Orientation.PortraitFlipped:
|
||||
m = Matrix4x4.TRS(new Vector3(inputTexture.height, 0f, 0f), Quaternion.Euler(0f, 0f, 90f), Vector3.one);
|
||||
break;
|
||||
case Orientation.LandscapeFlipped:
|
||||
m = Matrix4x4.TRS(new Vector3(inputTexture.width, inputTexture.height, 0f), Quaternion.identity, new Vector3(-1f, -1f, 1f));
|
||||
break;
|
||||
}
|
||||
|
||||
// The above Blit can't flip unless using a material, so we use Graphics.DrawTexture instead
|
||||
GL.InvalidateState();
|
||||
RenderTexture.active = tempRT;
|
||||
GL.Clear(false, true, Color.black);
|
||||
GL.PushMatrix();
|
||||
GL.LoadPixelMatrix(0f, tempRT.width, 0f, tempRT.height);
|
||||
Rect sourceRect = new Rect(0f, 0f, 1f, 1f);
|
||||
// NOTE: not sure why we need to set y to -1, without this there is a 1px gap at the bottom
|
||||
Rect destRect = new Rect(0f, -1f, inputTexture.width, inputTexture.height);
|
||||
GL.MultMatrix(m);
|
||||
|
||||
Graphics.DrawTexture(destRect, inputTexture, sourceRect, 0, 0, 0, 0);
|
||||
GL.PopMatrix();
|
||||
GL.InvalidateState();
|
||||
}
|
||||
#endif
|
||||
|
||||
if (resultTexture == null)
|
||||
{
|
||||
resultTexture = new Texture2D(textureWidth, textureHeight, TextureFormat.ARGB32, false);
|
||||
}
|
||||
|
||||
RenderTexture.active = tempRT;
|
||||
resultTexture.ReadPixels(new Rect(0f, 0f, textureWidth, textureHeight), 0, 0, false);
|
||||
resultTexture.Apply(false, false);
|
||||
RenderTexture.ReleaseTemporary(tempRT);
|
||||
|
||||
RenderTexture.active = prevRT;
|
||||
|
||||
return resultTexture;
|
||||
}
|
||||
|
||||
// Converts a non-readable texture to a readable Texture2D.
|
||||
// "targetTexture" can be null or you can pass in an existing texture.
|
||||
// Remember to Destroy() the returned texture after finished with it
|
||||
public static Texture2D GetReadableTexture(RenderTexture inputTexture, Texture2D targetTexture = null)
|
||||
{
|
||||
if (targetTexture == null)
|
||||
{
|
||||
targetTexture = new Texture2D(inputTexture.width, inputTexture.height, TextureFormat.ARGB32, false);
|
||||
}
|
||||
|
||||
RenderTexture prevRT = RenderTexture.active;
|
||||
RenderTexture.active = inputTexture;
|
||||
targetTexture.ReadPixels(new Rect(0f, 0f, inputTexture.width, inputTexture.height), 0, 0, false);
|
||||
targetTexture.Apply(false, false);
|
||||
RenderTexture.active = prevRT;
|
||||
|
||||
return targetTexture;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 79e446998599e1647804321292c80f42
|
||||
timeCreated: 1600887818
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 00407cbf3ca503142903894431082ac6
|
||||
timeCreated: 1438695622
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,225 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Copyright 2015-2021 RenderHeads Ltd. All rights reserved.
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
namespace RenderHeads.Media.AVProVideo
|
||||
{
|
||||
/// <summary>
|
||||
/// Attempts to give insight into video playback presentation smoothness quality
|
||||
/// Keeps track of skipped and duplicated frames and warns about suboptimal setup
|
||||
/// such as no vsync enabled or video frame rate not being a multiple of the display frame rate
|
||||
/// </summary>
|
||||
public class PlaybackQualityStats
|
||||
{
|
||||
public int SkippedFrames { get; private set; }
|
||||
public int DuplicateFrames { get; private set; }
|
||||
public int UnityDroppedFrames { get; private set; }
|
||||
public float PerfectFramesT { get; private set; }
|
||||
public string VSyncStatus { get; private set; }
|
||||
private int PerfectFrames { get; set; }
|
||||
private int TotalFrames { get; set; }
|
||||
|
||||
public bool LogIssues { get; set; }
|
||||
|
||||
private int _sameFrameCount;
|
||||
private long _lastTimeStamp;
|
||||
private BaseMediaPlayer _player;
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
_sameFrameCount = 0;
|
||||
if (_player != null)
|
||||
{
|
||||
_lastTimeStamp = _player.GetTextureTimeStamp();
|
||||
}
|
||||
|
||||
SkippedFrames = 0;
|
||||
DuplicateFrames = 0;
|
||||
UnityDroppedFrames = 0;
|
||||
TotalFrames = 0;
|
||||
PerfectFrames = 0;
|
||||
PerfectFramesT = 0f;
|
||||
}
|
||||
|
||||
internal void Start(BaseMediaPlayer player)
|
||||
{
|
||||
_player = player;
|
||||
Reset();
|
||||
|
||||
bool vsyncEnabled = true;
|
||||
if (QualitySettings.vSyncCount == 0)
|
||||
{
|
||||
vsyncEnabled = false;
|
||||
if (LogIssues)
|
||||
{
|
||||
Debug.LogWarning("[AVProVideo][Quality] VSync is currently disabled in Quality Settings");
|
||||
}
|
||||
}
|
||||
if (!IsGameViewVSyncEnabled())
|
||||
{
|
||||
vsyncEnabled = false;
|
||||
if (LogIssues)
|
||||
{
|
||||
Debug.LogWarning("[AVProVideo][Quality] VSync is currently disabled in the Game View");
|
||||
}
|
||||
}
|
||||
|
||||
float frameRate = _player.GetVideoFrameRate();
|
||||
float frameMs = (1000f / frameRate);
|
||||
if (LogIssues)
|
||||
{
|
||||
Debug.Log(string.Format("[AVProVideo][Quality] Video: {0}fps {1}ms", frameRate, frameMs));
|
||||
}
|
||||
|
||||
if (vsyncEnabled)
|
||||
{
|
||||
#if UNITY_2022_2_OR_NEWER
|
||||
float refreshRate = (float)( Screen.currentResolution.refreshRateRatio.value );
|
||||
#else
|
||||
float refreshRate = (float)( Screen.currentResolution.refreshRate );
|
||||
#endif
|
||||
|
||||
float vsyncRate = refreshRate / QualitySettings.vSyncCount;
|
||||
float vsyncMs = (1000f / vsyncRate);
|
||||
|
||||
if (LogIssues)
|
||||
{
|
||||
Debug.Log(string.Format("[AVProVideo][Quality] VSync: {0}fps {1}ms", vsyncRate, vsyncMs));
|
||||
}
|
||||
|
||||
float framesPerVSync = frameMs / vsyncMs;
|
||||
float fractionalframesPerVsync = framesPerVSync - Mathf.FloorToInt(framesPerVSync);
|
||||
if (fractionalframesPerVsync > 0.0001f && LogIssues)
|
||||
{
|
||||
Debug.LogWarning("[AVProVideo][Quality] Video is not a multiple of VSync so playback cannot be perfect");
|
||||
}
|
||||
VSyncStatus = "VSync " + framesPerVSync;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (LogIssues)
|
||||
{
|
||||
Debug.LogWarning("[AVProVideo][Quality] Running without VSync enabled");
|
||||
}
|
||||
VSyncStatus = "No VSync";
|
||||
}
|
||||
}
|
||||
|
||||
internal void Update()
|
||||
{
|
||||
if (_player == null) return;
|
||||
|
||||
// Don't analyse stats unless real playback is happening
|
||||
if (_player.IsPaused() || _player.IsSeeking() || _player.IsFinished()) return;
|
||||
|
||||
long timeStamp = _player.GetTextureTimeStamp();
|
||||
long frameDuration = (long)(Helper.SecondsToHNS / _player.GetVideoFrameRate());
|
||||
|
||||
bool isPerfectFrame = true;
|
||||
|
||||
// Check for skipped frames
|
||||
long d = (timeStamp - _lastTimeStamp);
|
||||
if (d > 0)
|
||||
{
|
||||
const long threshold = 10000;
|
||||
d -= frameDuration;
|
||||
if (d > threshold)
|
||||
{
|
||||
int skippedFrames = Mathf.FloorToInt((float)d / (float)frameDuration);
|
||||
if (LogIssues)
|
||||
{
|
||||
Debug.LogWarning("[AVProVideo][Quality] Possible frame skip, at " + timeStamp + " delta " + d + " = " + skippedFrames + " frames");
|
||||
}
|
||||
SkippedFrames += skippedFrames;
|
||||
isPerfectFrame = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (QualitySettings.vSyncCount != 0)
|
||||
{
|
||||
#if UNITY_2022_2_OR_NEWER
|
||||
float refreshRate = (float)( Screen.currentResolution.refreshRateRatio.value );
|
||||
#else
|
||||
float refreshRate = (float)( Screen.currentResolution.refreshRate );
|
||||
#endif
|
||||
|
||||
long vsyncDuration = (long)((QualitySettings.vSyncCount * Helper.SecondsToHNS) / refreshRate);
|
||||
if (timeStamp != _lastTimeStamp)
|
||||
{
|
||||
float framesPerVSync = (float)frameDuration / (float)vsyncDuration;
|
||||
//Debug.Log((float)frameDuration + " " + (float)vsyncDuration);
|
||||
float fractionalFramesPerVSync = framesPerVSync - Mathf.FloorToInt(framesPerVSync);
|
||||
|
||||
//Debug.Log(framesPerVSync + " " + fractionalFramesPerVSync);
|
||||
// VSync rate is a multiple of the video rate so we should be able to get perfectly smooth playback
|
||||
if (fractionalFramesPerVSync <= 0.0001f)
|
||||
{
|
||||
// Check for duplicate frames
|
||||
if (!Mathf.Approximately(_sameFrameCount, (int)framesPerVSync))
|
||||
{
|
||||
if (LogIssues)
|
||||
{
|
||||
Debug.LogWarning("[AVProVideo][Quality] Frame " + timeStamp + " was shown for " + _sameFrameCount + " frames instead of expected " + framesPerVSync);
|
||||
}
|
||||
DuplicateFrames++;
|
||||
isPerfectFrame = false;
|
||||
}
|
||||
}
|
||||
|
||||
_sameFrameCount = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Count the number of Unity-frames the video-frame is displayed for
|
||||
_sameFrameCount++;
|
||||
}
|
||||
|
||||
// Check for Unity dropping frames
|
||||
{
|
||||
long frameTime = (long)(Time.deltaTime * Helper.SecondsToHNS);
|
||||
if (frameTime > (vsyncDuration + (vsyncDuration / 3)))
|
||||
{
|
||||
if (LogIssues)
|
||||
{
|
||||
Debug.LogWarning("[AVProVideo][Quality] Possible Unity dropped frame, delta time: " + (Time.deltaTime * 1000f) + "ms");
|
||||
}
|
||||
UnityDroppedFrames++;
|
||||
isPerfectFrame = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (_lastTimeStamp != timeStamp)
|
||||
{
|
||||
if (isPerfectFrame)
|
||||
{
|
||||
PerfectFrames++;
|
||||
}
|
||||
TotalFrames++;
|
||||
PerfectFramesT = (float)PerfectFrames / (float)TotalFrames;
|
||||
}
|
||||
|
||||
_lastTimeStamp = timeStamp;
|
||||
}
|
||||
|
||||
private static bool IsGameViewVSyncEnabled()
|
||||
{
|
||||
bool result = true;
|
||||
#if UNITY_EDITOR && UNITY_2019_1_OR_NEWER
|
||||
System.Reflection.Assembly assembly = typeof(UnityEditor.EditorWindow).Assembly;
|
||||
System.Type type = assembly.GetType("UnityEditor.GameView");
|
||||
UnityEditor.EditorWindow window = UnityEditor.EditorWindow.GetWindow(type);
|
||||
System.Reflection.PropertyInfo prop = type.GetProperty("vSyncEnabled");
|
||||
if (prop != null)
|
||||
{
|
||||
result = (bool)prop.GetValue(window);
|
||||
}
|
||||
#endif
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4f344d823db9c4148af4dec2235f690d
|
||||
timeCreated: 1634119397
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e2f8dd08c4c77654282b755fd4a069c1
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 80eb525dd677aa440823910b09b23ae0
|
||||
timeCreated: 1438698292
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,439 @@
|
||||
//-----------------------------------------------------------------------------
|
||||
// Copyright 2015-2022 RenderHeads Ltd. All rights reserved.
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
#if UNITY_2017_2_OR_NEWER && (UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX || (!UNITY_EDITOR && (UNITY_IOS || UNITY_TVOS)))
|
||||
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using UnityEngine;
|
||||
|
||||
namespace RenderHeads.Media.AVProVideo
|
||||
{
|
||||
public sealed partial class AppleMediaPlayer
|
||||
{
|
||||
internal partial struct Native
|
||||
{
|
||||
#if UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX
|
||||
private const string PluginName = "AVProVideo";
|
||||
#elif UNITY_IOS || UNITY_TVOS
|
||||
private const string PluginName = "__Internal";
|
||||
#endif
|
||||
|
||||
// Video settings
|
||||
|
||||
internal enum AVPPlayerVideoPixelFormat: int
|
||||
{
|
||||
Invalid,
|
||||
Bgra,
|
||||
YCbCr420
|
||||
}
|
||||
|
||||
[Flags]
|
||||
internal enum AVPPlayerVideoOutputSettingsFlags: int
|
||||
{
|
||||
None = 0,
|
||||
LinearColorSpace = 1 << 0,
|
||||
GenerateMipmaps = 1 << 1,
|
||||
}
|
||||
|
||||
// Audio settings
|
||||
|
||||
internal enum AVPPlayerAudioOutputMode : int
|
||||
{
|
||||
SystemDirect,
|
||||
Unity,
|
||||
SystemDirectWithCapture,
|
||||
}
|
||||
|
||||
// Network settings
|
||||
|
||||
[Flags]
|
||||
internal enum AVPPlayerNetworkSettingsFlags: int
|
||||
{
|
||||
None = 0,
|
||||
PlayWithoutBuffering = 1 << 0,
|
||||
UseSinglePlayerItem = 1 << 1,
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
internal struct AVPPlayerSettings
|
||||
{
|
||||
// Video
|
||||
internal AVPPlayerVideoPixelFormat pixelFormat;
|
||||
internal AVPPlayerVideoOutputSettingsFlags videoFlags;
|
||||
internal float preferredMaximumResolution_width;
|
||||
internal float preferredMaximumResolution_height;
|
||||
internal float maximumPlaybackRate;
|
||||
|
||||
// Audio
|
||||
internal AVPPlayerAudioOutputMode audioOutputMode;
|
||||
internal int sampleRate;
|
||||
internal int bufferLength;
|
||||
internal int audioFlags;
|
||||
|
||||
// Network
|
||||
internal double preferredPeakBitRate;
|
||||
internal double preferredForwardBufferDuration;
|
||||
internal AVPPlayerNetworkSettingsFlags networkFlags;
|
||||
}
|
||||
|
||||
[Flags]
|
||||
internal enum AVPPlayerStatus : int
|
||||
{
|
||||
Unknown = 0,
|
||||
ReadyToPlay = 1 << 0,
|
||||
Playing = 1 << 1,
|
||||
Paused = 1 << 2,
|
||||
Finished = 1 << 3,
|
||||
Seeking = 1 << 4,
|
||||
Buffering = 1 << 5,
|
||||
Stalled = 1 << 6,
|
||||
ExternalPlaybackActive = 1 << 7,
|
||||
Cached = 1 << 8,
|
||||
FinishedSeeking = 1 << 9,
|
||||
|
||||
UpdatedAssetInfo = 1 << 16,
|
||||
UpdatedTexture = 1 << 17,
|
||||
UpdatedBufferedTimeRanges = 1 << 18,
|
||||
UpdatedSeekableTimeRanges = 1 << 19,
|
||||
UpdatedText = 1 << 20,
|
||||
|
||||
HasVideo = 1 << 24,
|
||||
HasAudio = 1 << 25,
|
||||
HasText = 1 << 26,
|
||||
HasMetadata = 1 << 27,
|
||||
|
||||
Failed = 1 << 31
|
||||
}
|
||||
|
||||
[Flags]
|
||||
internal enum AVPPlayerFlags : int
|
||||
{
|
||||
None = 0,
|
||||
Looping = 1 << 0,
|
||||
Muted = 1 << 1,
|
||||
AllowExternalPlayback = 1 << 2,
|
||||
ResumePlayback = 1 << 16, // iOS only, resumes playback after audio session route change
|
||||
Dirty = 1 << 31
|
||||
}
|
||||
|
||||
internal enum AVPPlayerExternalPlaybackVideoGravity : int
|
||||
{
|
||||
Resize,
|
||||
ResizeAspect,
|
||||
ResizeAspectFill
|
||||
};
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
internal struct AVPPlayerSize
|
||||
{
|
||||
internal float width;
|
||||
internal float height;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
internal struct AVPAffineTransform
|
||||
{
|
||||
internal float a;
|
||||
internal float b;
|
||||
internal float c;
|
||||
internal float d;
|
||||
internal float tx;
|
||||
internal float ty;
|
||||
}
|
||||
|
||||
[Flags]
|
||||
internal enum AVPPlayerAssetFlags : int
|
||||
{
|
||||
None = 0,
|
||||
CompatibleWithAirPlay = 1 << 0,
|
||||
};
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
internal struct AVPPlayerAssetInfo
|
||||
{
|
||||
internal double duration;
|
||||
internal AVPPlayerSize dimensions;
|
||||
internal float frameRate;
|
||||
internal int videoTrackCount;
|
||||
internal int audioTrackCount;
|
||||
internal int textTrackCount;
|
||||
internal AVPPlayerAssetFlags flags;
|
||||
}
|
||||
|
||||
[Flags]
|
||||
internal enum AVPPlayerTrackFlags: int
|
||||
{
|
||||
Default = 1 << 0,
|
||||
}
|
||||
|
||||
internal enum AVPPlayerVideoTrackStereoMode: int
|
||||
{
|
||||
Unknown,
|
||||
Monoscopic,
|
||||
StereoscopicTopBottom,
|
||||
StereoscopicLeftRight,
|
||||
StereoscopicCustom,
|
||||
StereoscopicRightLeft,
|
||||
}
|
||||
|
||||
[Flags]
|
||||
internal enum AVPPlayerVideoTrackFlags: int
|
||||
{
|
||||
HasAlpha = 1 << 0,
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
internal struct AVPPlayerVideoTrackInfo
|
||||
{
|
||||
[MarshalAs(UnmanagedType.LPWStr)] internal string name;
|
||||
[MarshalAs(UnmanagedType.LPWStr)] internal string language;
|
||||
internal int trackId;
|
||||
internal float estimatedDataRate;
|
||||
internal uint codecSubtype;
|
||||
internal AVPPlayerTrackFlags flags;
|
||||
|
||||
internal AVPPlayerSize dimensions;
|
||||
internal float frameRate;
|
||||
internal AVPAffineTransform transform;
|
||||
internal AVPPlayerVideoTrackStereoMode stereoMode;
|
||||
internal int bitsPerComponent;
|
||||
internal AVPPlayerVideoTrackFlags videoTrackFlags;
|
||||
|
||||
internal Matrix4x4 yCbCrTransform;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
internal struct AVPPlayerAudioTrackInfo
|
||||
{
|
||||
[MarshalAs(UnmanagedType.LPWStr)] internal string name;
|
||||
[MarshalAs(UnmanagedType.LPWStr)] internal string language;
|
||||
internal int trackId;
|
||||
internal float estimatedDataRate;
|
||||
internal uint codecSubtype;
|
||||
internal AVPPlayerTrackFlags flags;
|
||||
|
||||
internal double sampleRate;
|
||||
internal uint channelCount;
|
||||
internal uint channelLayoutTag;
|
||||
internal AudioChannelMaskFlags channelBitmap;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
internal struct AVPPlayerTextTrackInfo
|
||||
{
|
||||
[MarshalAs(UnmanagedType.LPWStr)] internal string name;
|
||||
[MarshalAs(UnmanagedType.LPWStr)] internal string language;
|
||||
internal int trackId;
|
||||
internal float estimatedDataRate;
|
||||
internal uint codecSubtype;
|
||||
internal AVPPlayerTrackFlags flags;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
internal struct AVPPlayerTimeRange
|
||||
{
|
||||
internal double start;
|
||||
internal double duration;
|
||||
};
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
internal struct AVPPlayerState
|
||||
{
|
||||
internal AVPPlayerStatus status;
|
||||
internal double currentTime;
|
||||
internal double currentDate;
|
||||
internal int selectedVideoTrack;
|
||||
internal int selectedAudioTrack;
|
||||
internal int selectedTextTrack;
|
||||
internal int bufferedTimeRangesCount;
|
||||
internal int seekableTimeRangesCount;
|
||||
internal int audioCaptureBufferedSamplesCount;
|
||||
}
|
||||
|
||||
internal enum AVPPlayerTextureFormat: int
|
||||
{
|
||||
Unknown,
|
||||
BGRA8,
|
||||
R8,
|
||||
RG8,
|
||||
BC1,
|
||||
BC3,
|
||||
BC4,
|
||||
BC5,
|
||||
BC7,
|
||||
BGR10A2,
|
||||
R16,
|
||||
RG16,
|
||||
BGR10XR,
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
internal struct AVPPlayerTexturePlane
|
||||
{
|
||||
internal IntPtr plane;
|
||||
internal int width;
|
||||
internal int height;
|
||||
internal AVPPlayerTextureFormat textureFormat;
|
||||
}
|
||||
|
||||
[Flags]
|
||||
internal enum AVPPlayerTextureFlags: int
|
||||
{
|
||||
None = 0,
|
||||
Flipped = 1 << 0,
|
||||
Linear = 1 << 1,
|
||||
Mipmapped = 1 << 2,
|
||||
}
|
||||
|
||||
internal enum AVPPlayerTextureYCbCrMatrix: int
|
||||
{
|
||||
Identity,
|
||||
ITU_R_601,
|
||||
ITU_R_709,
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
internal struct AVPPlayerTexture
|
||||
{
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst=2)]
|
||||
internal AVPPlayerTexturePlane[] planes;
|
||||
internal long itemTime;
|
||||
internal int frameCount;
|
||||
internal int planeCount;
|
||||
internal AVPPlayerTextureFlags flags;
|
||||
internal AVPPlayerTextureYCbCrMatrix YCbCrMatrix;
|
||||
};
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
internal struct AVPPlayerText
|
||||
{
|
||||
internal IntPtr buffer;
|
||||
internal long itemTime;
|
||||
internal int length;
|
||||
internal int sequence;
|
||||
};
|
||||
|
||||
internal enum AVPPlayerTrackType: int
|
||||
{
|
||||
Video,
|
||||
Audio,
|
||||
Text
|
||||
};
|
||||
|
||||
#if !UNITY_EDITOR && (UNITY_IOS || UNITY_TVOS)
|
||||
[DllImport(PluginName)]
|
||||
internal static extern void AVPPluginBootstrap();
|
||||
#endif
|
||||
|
||||
[DllImport(PluginName)]
|
||||
private static extern IntPtr AVPPluginGetVersionStringPointer();
|
||||
|
||||
internal static string GetPluginVersion()
|
||||
{
|
||||
return System.Runtime.InteropServices.Marshal.PtrToStringAnsi(AVPPluginGetVersionStringPointer());
|
||||
}
|
||||
|
||||
[DllImport(PluginName)]
|
||||
internal static extern IntPtr AVPPluginMakePlayer(Native.AVPPlayerSettings settings);
|
||||
|
||||
[DllImport(PluginName)]
|
||||
internal static extern IntPtr AVPPlayerRelease(IntPtr player);
|
||||
|
||||
[DllImport(PluginName)]
|
||||
internal static extern void AVPPlayerGetState(IntPtr player, ref AVPPlayerState state);
|
||||
|
||||
[DllImport(PluginName)]
|
||||
internal static extern void AVPPlayerSetFlags(IntPtr player, int flags);
|
||||
|
||||
[DllImport(PluginName)]
|
||||
internal static extern void AVPPlayerGetAssetInfo(IntPtr player, ref AVPPlayerAssetInfo info);
|
||||
|
||||
[DllImport(PluginName)]
|
||||
internal static extern void AVPPlayerGetVideoTrackInfo(IntPtr player, int index, ref AVPPlayerVideoTrackInfo info);
|
||||
|
||||
[DllImport(PluginName)]
|
||||
internal static extern void AVPPlayerGetAudioTrackInfo(IntPtr player, int index, ref AVPPlayerAudioTrackInfo info);
|
||||
|
||||
[DllImport(PluginName)]
|
||||
internal static extern void AVPPlayerGetTextTrackInfo(IntPtr player, int index, ref AVPPlayerTextTrackInfo info);
|
||||
|
||||
[DllImport(PluginName)]
|
||||
internal static extern void AVPPlayerGetBufferedTimeRanges(IntPtr player, AVPPlayerTimeRange[] ranges, int count);
|
||||
|
||||
[DllImport(PluginName)]
|
||||
internal static extern void AVPPlayerGetSeekableTimeRanges(IntPtr player, AVPPlayerTimeRange[] ranges, int count);
|
||||
|
||||
[DllImport(PluginName)]
|
||||
internal static extern void AVPPlayerGetTexture(IntPtr player, ref AVPPlayerTexture texture);
|
||||
|
||||
[DllImport(PluginName)]
|
||||
internal static extern void AVPPlayerGetText(IntPtr player, ref AVPPlayerText text);
|
||||
|
||||
[DllImport(PluginName)]
|
||||
internal static extern void AVPPlayerSetPlayerSettings(IntPtr player, AVPPlayerSettings settings);
|
||||
|
||||
[DllImport(PluginName)]
|
||||
[return: MarshalAs(UnmanagedType.U1)]
|
||||
internal static extern bool AVPPlayerOpenURL(IntPtr player, string url, string headers);
|
||||
|
||||
[DllImport(PluginName)]
|
||||
internal static extern void AVPPlayerClose(IntPtr player);
|
||||
|
||||
[DllImport(PluginName)]
|
||||
internal static extern int AVPPlayerGetAudio(IntPtr player, float[] buffer, int length);
|
||||
|
||||
[DllImport(PluginName)]
|
||||
internal static extern void AVPPlayerSetRate(IntPtr player, float rate);
|
||||
|
||||
[DllImport(PluginName)]
|
||||
internal static extern void AVPPlayerSetVolume(IntPtr player, float volume);
|
||||
|
||||
[DllImport(PluginName)]
|
||||
internal static extern void AVPPlayerSetExternalPlaybackVideoGravity(IntPtr player, AVPPlayerExternalPlaybackVideoGravity gravity);
|
||||
|
||||
[DllImport(PluginName)]
|
||||
internal static extern void AVPPlayerSeek(IntPtr player, double toTime, double toleranceBefore, double toleranceAfter);
|
||||
|
||||
[DllImport(PluginName)]
|
||||
internal static extern void AVPPlayerSetKeyServerAuthToken(IntPtr player, string token);
|
||||
|
||||
[DllImport(PluginName)]
|
||||
internal static extern void AVPPlayerSetKeyServerURL(IntPtr player, string url);
|
||||
|
||||
[DllImport(PluginName)]
|
||||
internal static extern void AVPPlayerSetDecryptionKey(IntPtr player, byte[] key, int length);
|
||||
|
||||
[DllImport(PluginName)]
|
||||
[return: MarshalAs(UnmanagedType.I1)]
|
||||
internal static extern bool AVPPlayerSetTrack(IntPtr player, AVPPlayerTrackType type, int index);
|
||||
|
||||
#if !UNITY_EDITOR && UNITY_IOS
|
||||
public struct MediaCachingOptions
|
||||
{
|
||||
public double minimumRequiredBitRate;
|
||||
public float minimumRequiredResolution_width;
|
||||
public float minimumRequiredResolution_height;
|
||||
public string title;
|
||||
public IntPtr artwork;
|
||||
public int artworkLength;
|
||||
}
|
||||
|
||||
[DllImport(PluginName)]
|
||||
public static extern void AVPPluginCacheMediaForURL(string url, string headers, MediaCachingOptions options);
|
||||
|
||||
[DllImport(PluginName)]
|
||||
public static extern void AVPPluginCancelDownloadOfMediaForURL(string url);
|
||||
|
||||
[DllImport(PluginName)]
|
||||
public static extern void AVPPluginRemoveCachedMediaForURL(string url);
|
||||
|
||||
[DllImport(PluginName)]
|
||||
public static extern int AVPPluginGetCachedMediaStatusForURL(string url, ref float progress);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0bf374b5848b649e6b3840fe1dc03cd2
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3f68628a1ef6349648e502d1c66b5114
|
||||
timeCreated: 1547113004
|
||||
licenseType: Free
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,226 @@
|
||||
//-----------------------------------------------------------------------------
|
||||
// Copyright 2015-2022 RenderHeads Ltd. All rights reserved.
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
#if UNITY_2017_2_OR_NEWER && (UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX || (!UNITY_EDITOR && (UNITY_IOS || UNITY_TVOS)))
|
||||
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using UnityEngine;
|
||||
|
||||
namespace RenderHeads.Media.AVProVideo
|
||||
{
|
||||
internal static class AppleMediaPlayerExtensions
|
||||
{
|
||||
// AVPPlayerStatus
|
||||
|
||||
internal static bool IsReadyToPlay(this AppleMediaPlayer.Native.AVPPlayerStatus status)
|
||||
{
|
||||
return (status & AppleMediaPlayer.Native.AVPPlayerStatus.ReadyToPlay) == AppleMediaPlayer.Native.AVPPlayerStatus.ReadyToPlay;
|
||||
}
|
||||
|
||||
internal static bool IsPlaying(this AppleMediaPlayer.Native.AVPPlayerStatus status)
|
||||
{
|
||||
return (status & AppleMediaPlayer.Native.AVPPlayerStatus.Playing) == AppleMediaPlayer.Native.AVPPlayerStatus.Playing;
|
||||
}
|
||||
|
||||
internal static bool IsPaused(this AppleMediaPlayer.Native.AVPPlayerStatus status)
|
||||
{
|
||||
return (status & AppleMediaPlayer.Native.AVPPlayerStatus.Paused) == AppleMediaPlayer.Native.AVPPlayerStatus.Paused;
|
||||
}
|
||||
|
||||
internal static bool IsFinished(this AppleMediaPlayer.Native.AVPPlayerStatus status)
|
||||
{
|
||||
return (status & AppleMediaPlayer.Native.AVPPlayerStatus.Finished) == AppleMediaPlayer.Native.AVPPlayerStatus.Finished;
|
||||
}
|
||||
|
||||
internal static bool IsSeeking(this AppleMediaPlayer.Native.AVPPlayerStatus status)
|
||||
{
|
||||
return (status & AppleMediaPlayer.Native.AVPPlayerStatus.Seeking) == AppleMediaPlayer.Native.AVPPlayerStatus.Seeking;
|
||||
}
|
||||
|
||||
internal static bool IsBuffering(this AppleMediaPlayer.Native.AVPPlayerStatus status)
|
||||
{
|
||||
return (status & AppleMediaPlayer.Native.AVPPlayerStatus.Buffering) == AppleMediaPlayer.Native.AVPPlayerStatus.Buffering;
|
||||
}
|
||||
|
||||
internal static bool IsStalled(this AppleMediaPlayer.Native.AVPPlayerStatus status)
|
||||
{
|
||||
return (status & AppleMediaPlayer.Native.AVPPlayerStatus.Stalled) == AppleMediaPlayer.Native.AVPPlayerStatus.Stalled;
|
||||
}
|
||||
|
||||
internal static bool IsExternalPlaybackActive(this AppleMediaPlayer.Native.AVPPlayerStatus status)
|
||||
{
|
||||
return (status & AppleMediaPlayer.Native.AVPPlayerStatus.ExternalPlaybackActive) == AppleMediaPlayer.Native.AVPPlayerStatus.ExternalPlaybackActive;
|
||||
}
|
||||
|
||||
internal static bool IsCached(this AppleMediaPlayer.Native.AVPPlayerStatus status)
|
||||
{
|
||||
return (status & AppleMediaPlayer.Native.AVPPlayerStatus.Cached) == AppleMediaPlayer.Native.AVPPlayerStatus.Cached;
|
||||
}
|
||||
|
||||
internal static bool HasFinishedSeeking(this AppleMediaPlayer.Native.AVPPlayerStatus status)
|
||||
{
|
||||
return (status & AppleMediaPlayer.Native.AVPPlayerStatus.FinishedSeeking) == AppleMediaPlayer.Native.AVPPlayerStatus.FinishedSeeking;
|
||||
}
|
||||
|
||||
internal static bool HasUpdatedAssetInfo(this AppleMediaPlayer.Native.AVPPlayerStatus status)
|
||||
{
|
||||
return (status & AppleMediaPlayer.Native.AVPPlayerStatus.UpdatedAssetInfo) == AppleMediaPlayer.Native.AVPPlayerStatus.UpdatedAssetInfo;
|
||||
}
|
||||
|
||||
internal static bool HasUpdatedTexture(this AppleMediaPlayer.Native.AVPPlayerStatus status)
|
||||
{
|
||||
return (status & AppleMediaPlayer.Native.AVPPlayerStatus.UpdatedTexture) == AppleMediaPlayer.Native.AVPPlayerStatus.UpdatedTexture;
|
||||
}
|
||||
|
||||
internal static bool HasUpdatedBufferedTimeRanges(this AppleMediaPlayer.Native.AVPPlayerStatus status)
|
||||
{
|
||||
return (status & AppleMediaPlayer.Native.AVPPlayerStatus.UpdatedBufferedTimeRanges) == AppleMediaPlayer.Native.AVPPlayerStatus.UpdatedBufferedTimeRanges;
|
||||
}
|
||||
|
||||
internal static bool HasUpdatedSeekableTimeRanges(this AppleMediaPlayer.Native.AVPPlayerStatus status)
|
||||
{
|
||||
return (status & AppleMediaPlayer.Native.AVPPlayerStatus.UpdatedSeekableTimeRanges) == AppleMediaPlayer.Native.AVPPlayerStatus.UpdatedSeekableTimeRanges;
|
||||
}
|
||||
|
||||
internal static bool HasUpdatedText(this AppleMediaPlayer.Native.AVPPlayerStatus status)
|
||||
{
|
||||
return (status & AppleMediaPlayer.Native.AVPPlayerStatus.UpdatedText) == AppleMediaPlayer.Native.AVPPlayerStatus.UpdatedText;
|
||||
}
|
||||
|
||||
internal static bool HasVideo(this AppleMediaPlayer.Native.AVPPlayerStatus status)
|
||||
{
|
||||
return (status & AppleMediaPlayer.Native.AVPPlayerStatus.HasVideo) == AppleMediaPlayer.Native.AVPPlayerStatus.HasVideo;
|
||||
}
|
||||
|
||||
internal static bool HasAudio(this AppleMediaPlayer.Native.AVPPlayerStatus status)
|
||||
{
|
||||
return (status & AppleMediaPlayer.Native.AVPPlayerStatus.HasAudio) == AppleMediaPlayer.Native.AVPPlayerStatus.HasAudio;
|
||||
}
|
||||
|
||||
internal static bool HasText(this AppleMediaPlayer.Native.AVPPlayerStatus status)
|
||||
{
|
||||
return (status & AppleMediaPlayer.Native.AVPPlayerStatus.HasText) == AppleMediaPlayer.Native.AVPPlayerStatus.HasText;
|
||||
}
|
||||
|
||||
internal static bool HasMetadata(this AppleMediaPlayer.Native.AVPPlayerStatus status)
|
||||
{
|
||||
return (status & AppleMediaPlayer.Native.AVPPlayerStatus.HasMetadata) == AppleMediaPlayer.Native.AVPPlayerStatus.HasMetadata;
|
||||
}
|
||||
|
||||
internal static bool HasFailed(this AppleMediaPlayer.Native.AVPPlayerStatus status)
|
||||
{
|
||||
return (status & AppleMediaPlayer.Native.AVPPlayerStatus.Failed) == AppleMediaPlayer.Native.AVPPlayerStatus.Failed;
|
||||
}
|
||||
|
||||
// AVPPlayerFlags
|
||||
|
||||
internal static bool IsLooping(this AppleMediaPlayer.Native.AVPPlayerFlags flags)
|
||||
{
|
||||
return (flags & AppleMediaPlayer.Native.AVPPlayerFlags.Looping) == AppleMediaPlayer.Native.AVPPlayerFlags.Looping;
|
||||
}
|
||||
|
||||
internal static AppleMediaPlayer.Native.AVPPlayerFlags SetLooping(this AppleMediaPlayer.Native.AVPPlayerFlags flags, bool b)
|
||||
{
|
||||
if (flags.IsLooping() ^ b)
|
||||
{
|
||||
flags = (b ? flags | AppleMediaPlayer.Native.AVPPlayerFlags.Looping
|
||||
: flags & ~AppleMediaPlayer.Native.AVPPlayerFlags.Looping) | AppleMediaPlayer.Native.AVPPlayerFlags.Dirty;
|
||||
}
|
||||
return flags;
|
||||
}
|
||||
|
||||
internal static bool IsMuted(this AppleMediaPlayer.Native.AVPPlayerFlags flags)
|
||||
{
|
||||
return (flags & AppleMediaPlayer.Native.AVPPlayerFlags.Muted) == AppleMediaPlayer.Native.AVPPlayerFlags.Muted;
|
||||
}
|
||||
|
||||
internal static AppleMediaPlayer.Native.AVPPlayerFlags SetMuted(this AppleMediaPlayer.Native.AVPPlayerFlags flags, bool b)
|
||||
{
|
||||
if (flags.IsMuted() ^ b)
|
||||
{
|
||||
flags = (b ? flags | AppleMediaPlayer.Native.AVPPlayerFlags.Muted
|
||||
: flags & ~AppleMediaPlayer.Native.AVPPlayerFlags.Muted) | AppleMediaPlayer.Native.AVPPlayerFlags.Dirty;
|
||||
}
|
||||
return flags;
|
||||
}
|
||||
|
||||
internal static bool IsExternalPlaybackAllowed(this AppleMediaPlayer.Native.AVPPlayerFlags flags)
|
||||
{
|
||||
return (flags & AppleMediaPlayer.Native.AVPPlayerFlags.AllowExternalPlayback) == AppleMediaPlayer.Native.AVPPlayerFlags.AllowExternalPlayback;
|
||||
}
|
||||
|
||||
internal static AppleMediaPlayer.Native.AVPPlayerFlags SetAllowExternalPlayback(this AppleMediaPlayer.Native.AVPPlayerFlags flags, bool b)
|
||||
{
|
||||
if (flags.IsExternalPlaybackAllowed() ^ b)
|
||||
{
|
||||
flags = (b ? flags | AppleMediaPlayer.Native.AVPPlayerFlags.AllowExternalPlayback
|
||||
: flags & ~AppleMediaPlayer.Native.AVPPlayerFlags.AllowExternalPlayback) | AppleMediaPlayer.Native.AVPPlayerFlags.Dirty;
|
||||
}
|
||||
return flags;
|
||||
}
|
||||
|
||||
internal static bool ResumePlayback(this AppleMediaPlayer.Native.AVPPlayerFlags flags)
|
||||
{
|
||||
return (flags & AppleMediaPlayer.Native.AVPPlayerFlags.ResumePlayback) == AppleMediaPlayer.Native.AVPPlayerFlags.ResumePlayback;
|
||||
}
|
||||
|
||||
internal static AppleMediaPlayer.Native.AVPPlayerFlags SetResumePlayback(this AppleMediaPlayer.Native.AVPPlayerFlags flags, bool b)
|
||||
{
|
||||
if (flags.ResumePlayback() ^ b)
|
||||
{
|
||||
flags = (b ? flags | AppleMediaPlayer.Native.AVPPlayerFlags.ResumePlayback
|
||||
: flags & ~AppleMediaPlayer.Native.AVPPlayerFlags.ResumePlayback) | AppleMediaPlayer.Native.AVPPlayerFlags.Dirty;
|
||||
}
|
||||
return flags;
|
||||
}
|
||||
|
||||
internal static bool IsDirty(this AppleMediaPlayer.Native.AVPPlayerFlags flags)
|
||||
{
|
||||
return (flags & AppleMediaPlayer.Native.AVPPlayerFlags.Dirty) == AppleMediaPlayer.Native.AVPPlayerFlags.Dirty;
|
||||
}
|
||||
|
||||
internal static AppleMediaPlayer.Native.AVPPlayerFlags SetDirty(this AppleMediaPlayer.Native.AVPPlayerFlags flags, bool b)
|
||||
{
|
||||
if (flags.IsDirty() ^ b)
|
||||
{
|
||||
flags = b ? flags | AppleMediaPlayer.Native.AVPPlayerFlags.Dirty : flags & ~AppleMediaPlayer.Native.AVPPlayerFlags.Dirty;
|
||||
}
|
||||
return flags;
|
||||
}
|
||||
|
||||
// MARK: AVPPlayerAssetFlags
|
||||
|
||||
internal static bool IsCompatibleWithAirPlay(this AppleMediaPlayer.Native.AVPPlayerAssetFlags flags)
|
||||
{
|
||||
return (flags & AppleMediaPlayer.Native.AVPPlayerAssetFlags.CompatibleWithAirPlay) == AppleMediaPlayer.Native.AVPPlayerAssetFlags.CompatibleWithAirPlay;
|
||||
}
|
||||
|
||||
// MARK: AVPPlayerTrackFlags
|
||||
|
||||
internal static bool IsDefault(this AppleMediaPlayer.Native.AVPPlayerTrackFlags flags)
|
||||
{
|
||||
return (flags & AppleMediaPlayer.Native.AVPPlayerTrackFlags.Default) == AppleMediaPlayer.Native.AVPPlayerTrackFlags.Default;
|
||||
}
|
||||
|
||||
// AVPPlayerTextureFlags
|
||||
|
||||
internal static bool IsFlipped(this AppleMediaPlayer.Native.AVPPlayerTextureFlags flags)
|
||||
{
|
||||
return (flags & AppleMediaPlayer.Native.AVPPlayerTextureFlags.Flipped) == AppleMediaPlayer.Native.AVPPlayerTextureFlags.Flipped;
|
||||
}
|
||||
|
||||
internal static bool IsLinear(this AppleMediaPlayer.Native.AVPPlayerTextureFlags flags)
|
||||
{
|
||||
return (flags & AppleMediaPlayer.Native.AVPPlayerTextureFlags.Linear) == AppleMediaPlayer.Native.AVPPlayerTextureFlags.Linear;
|
||||
}
|
||||
|
||||
internal static bool IsMipmapped(this AppleMediaPlayer.Native.AVPPlayerTextureFlags flags)
|
||||
{
|
||||
return (flags & AppleMediaPlayer.Native.AVPPlayerTextureFlags.Mipmapped) == AppleMediaPlayer.Native.AVPPlayerTextureFlags.Mipmapped;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e27ea5523e11f44c09e8d368eb1f2983
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,347 @@
|
||||
using System;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Copyright 2015-2021 RenderHeads Ltd. All rights reserved.
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
namespace RenderHeads.Media.AVProVideo
|
||||
{
|
||||
/// <summary>
|
||||
/// This media player fakes video playback for platforms that aren't supported
|
||||
/// </summary>
|
||||
public sealed partial class NullMediaPlayer : BaseMediaPlayer
|
||||
{
|
||||
private bool _isPlaying = false;
|
||||
private bool _isPaused = false;
|
||||
private double _currentTime = 0.0;
|
||||
// private bool _audioMuted = false;
|
||||
private float _volume = 0.0f;
|
||||
private float _playbackRate = 1.0f;
|
||||
private bool _bLoop;
|
||||
|
||||
private int _Width = 256;
|
||||
private int _height = 256;
|
||||
private Texture2D _texture;
|
||||
private Texture2D _texture_AVPro;
|
||||
private Texture2D _texture_AVPro1;
|
||||
private float _fakeFlipTime;
|
||||
private int _frameCount;
|
||||
|
||||
private const float FrameRate = 10f;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string GetVersion()
|
||||
{
|
||||
return "0.0.0";
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string GetExpectedVersion()
|
||||
{
|
||||
return GetVersion();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool OpenMedia(string path, long offset, string httpHeader, MediaHints mediaHints, int forceFileFormat = 0, bool startWithHighestBitrate = false)
|
||||
{
|
||||
_texture_AVPro = (Texture2D)Resources.Load("Textures/AVProVideo-NullPlayer-Frame0");
|
||||
_texture_AVPro1 = (Texture2D)Resources.Load("Textures/AVProVideo-NullPlayer-Frame1");
|
||||
|
||||
if( _texture_AVPro )
|
||||
{
|
||||
_Width = _texture_AVPro.width;
|
||||
_height = _texture_AVPro.height;
|
||||
}
|
||||
|
||||
_texture = _texture_AVPro;
|
||||
|
||||
_fakeFlipTime = 0.0f;
|
||||
_frameCount = 0;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void CloseMedia()
|
||||
{
|
||||
_frameCount = 0;
|
||||
Resources.UnloadAsset(_texture_AVPro);
|
||||
Resources.UnloadAsset(_texture_AVPro1);
|
||||
|
||||
base.CloseMedia();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void SetLooping( bool bLooping )
|
||||
{
|
||||
_bLoop = bLooping;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool IsLooping()
|
||||
{
|
||||
return _bLoop;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool HasMetaData()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool CanPlay()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool HasAudio()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool HasVideo()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Play()
|
||||
{
|
||||
_isPlaying = true;
|
||||
_isPaused = false;
|
||||
_fakeFlipTime = 0.0f;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Pause()
|
||||
{
|
||||
_isPlaying = false;
|
||||
_isPaused = true;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Stop()
|
||||
{
|
||||
_isPlaying = false;
|
||||
_isPaused = false;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool IsSeeking()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
/// <inheritdoc/>
|
||||
public override bool IsPlaying()
|
||||
{
|
||||
return _isPlaying;
|
||||
}
|
||||
/// <inheritdoc/>
|
||||
public override bool IsPaused()
|
||||
{
|
||||
return _isPaused;
|
||||
}
|
||||
/// <inheritdoc/>
|
||||
public override bool IsFinished()
|
||||
{
|
||||
return _isPlaying && (_currentTime >= GetDuration());
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool IsBuffering()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override double GetDuration()
|
||||
{
|
||||
return 10.0;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override int GetVideoWidth()
|
||||
{
|
||||
return _Width;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override int GetVideoHeight()
|
||||
{
|
||||
return _height;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override float GetVideoDisplayRate()
|
||||
{
|
||||
return FrameRate;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override Texture GetTexture( int index )
|
||||
{
|
||||
// return _texture ? _texture : Texture2D.whiteTexture;
|
||||
return _texture;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override int GetTextureFrameCount()
|
||||
{
|
||||
return _frameCount;
|
||||
}
|
||||
|
||||
internal override StereoPacking InternalGetTextureStereoPacking()
|
||||
{
|
||||
return StereoPacking.Unknown;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool RequiresVerticalFlip()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Seek(double time)
|
||||
{
|
||||
_currentTime = time;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void SeekFast(double time)
|
||||
{
|
||||
_currentTime = time;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override double GetCurrentTime()
|
||||
{
|
||||
return _currentTime;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void SetPlaybackRate(float rate)
|
||||
{
|
||||
_playbackRate = rate;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override float GetPlaybackRate()
|
||||
{
|
||||
return _playbackRate;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void MuteAudio(bool bMuted)
|
||||
{
|
||||
// _audioMuted = bMuted;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool IsMuted()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void SetVolume(float volume)
|
||||
{
|
||||
_volume = volume;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override float GetVolume()
|
||||
{
|
||||
return _volume;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override float GetVideoFrameRate()
|
||||
{
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Update()
|
||||
{
|
||||
UpdateSubtitles();
|
||||
|
||||
if (_isPlaying)
|
||||
{
|
||||
_currentTime += Time.deltaTime;
|
||||
if (_currentTime >= GetDuration())
|
||||
{
|
||||
_currentTime = GetDuration();
|
||||
if( _bLoop )
|
||||
{
|
||||
Rewind();
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
_fakeFlipTime += Time.deltaTime;
|
||||
if( _fakeFlipTime >= (1.0 / FrameRate))
|
||||
{
|
||||
_fakeFlipTime = 0.0f;
|
||||
_texture = ( _texture == _texture_AVPro ) ? _texture_AVPro1 : _texture_AVPro;
|
||||
_frameCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Render()
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Dispose()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public sealed partial class NullMediaPlayer : BaseMediaPlayer
|
||||
{
|
||||
internal override bool InternalSetActiveTrack(TrackType trackType, int trackUid)
|
||||
{
|
||||
// Set the active text track using the unique identifier
|
||||
// Or disable all text tracks if < 0
|
||||
return false;
|
||||
}
|
||||
|
||||
internal override bool InternalIsChangedTracks(TrackType trackType)
|
||||
{
|
||||
// Has the tracks changed since the last frame 'tick'
|
||||
return false;
|
||||
}
|
||||
|
||||
internal override int InternalGetTrackCount(TrackType trackType)
|
||||
{
|
||||
// Return number of text tracks
|
||||
return 0;
|
||||
}
|
||||
|
||||
internal override TrackBase InternalGetTrackInfo(TrackType trackType, int index, ref bool isActiveTrack)
|
||||
{
|
||||
// Get information about the specific track at index, range is [0...InternalGetTextTrackCount)
|
||||
return null;
|
||||
}
|
||||
|
||||
internal override bool InternalIsChangedTextCue()
|
||||
{
|
||||
// Has the text cue changed since the last frame 'tick'
|
||||
return false;
|
||||
}
|
||||
|
||||
internal override string InternalGetCurrentTextCue()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 478671181ab1c9b42be924da77d7fcbe
|
||||
timeCreated: 1438703159
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,926 @@
|
||||
//#define AVPRO_WEBGL_USE_RENDERTEXTURE
|
||||
// NOTE: We only allow this script to compile in editor so we can easily check for compilation issues
|
||||
#if (UNITY_EDITOR || UNITY_WEBGL)
|
||||
using UnityEngine;
|
||||
using System;
|
||||
using System.Text;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Copyright 2015-2021 RenderHeads Ltd. All rights reserved.
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
namespace RenderHeads.Media.AVProVideo
|
||||
{
|
||||
/// <summary>
|
||||
/// WebGL implementation of BaseMediaPlayer
|
||||
/// </summary>
|
||||
public sealed class WebGLMediaPlayer : BaseMediaPlayer
|
||||
{
|
||||
//private enum AVPPlayerStatus
|
||||
//{
|
||||
// Unknown,
|
||||
// ReadyToPlay,
|
||||
// Playing,
|
||||
// Finished,
|
||||
// Seeking,
|
||||
// Failed
|
||||
//}
|
||||
|
||||
[DllImport("__Internal")]
|
||||
private static extern bool AVPPlayerInsertVideoElement(string path, int[] idValues, int externalLibrary);
|
||||
|
||||
[DllImport("__Internal")]
|
||||
private static extern int AVPPlayerWidth(int player);
|
||||
|
||||
[DllImport("__Internal")]
|
||||
private static extern int AVPPlayerHeight(int player);
|
||||
|
||||
[DllImport("__Internal")]
|
||||
private static extern int AVPPlayerGetLastError(int player);
|
||||
|
||||
[DllImport("__Internal")]
|
||||
private static extern int AVPPlayerGetVideoTrackCount(int player);
|
||||
|
||||
[DllImport("__Internal")]
|
||||
private static extern int AVPPlayerGetAudioTrackCount(int player);
|
||||
|
||||
[DllImport("__Internal")]
|
||||
private static extern int AVPPlayerGetTextTrackCount(int player);
|
||||
|
||||
[DllImport("__Internal")]
|
||||
private static extern bool AVPPlayerSetActiveVideoTrack(int player, int trackId);
|
||||
|
||||
[DllImport("__Internal")]
|
||||
private static extern bool AVPPlayerSetActiveAudioTrack(int player, int trackId);
|
||||
|
||||
[DllImport("__Internal")]
|
||||
private static extern bool AVPPlayerSetActiveTextTrack(int player, int trackId);
|
||||
|
||||
[DllImport("__Internal")]
|
||||
private static extern void AVPPlayerClose(int player);
|
||||
|
||||
[DllImport("__Internal")]
|
||||
private static extern bool AVPPlayerReady(int player);
|
||||
|
||||
[DllImport("__Internal")]
|
||||
private static extern void AVPPlayerSetLooping(int player, bool loop);
|
||||
|
||||
[DllImport("__Internal")]
|
||||
private static extern bool AVPPlayerIsLooping(int player);
|
||||
|
||||
[DllImport("__Internal")]
|
||||
private static extern bool AVPPlayerIsSeeking(int player);
|
||||
|
||||
[DllImport("__Internal")]
|
||||
private static extern bool AVPPlayerIsPlaying(int player);
|
||||
|
||||
[DllImport("__Internal")]
|
||||
private static extern bool AVPPlayerIsPaused(int player);
|
||||
|
||||
[DllImport("__Internal")]
|
||||
private static extern bool AVPPlayerIsFinished(int player);
|
||||
|
||||
[DllImport("__Internal")]
|
||||
private static extern bool AVPPlayerIsBuffering(int player);
|
||||
|
||||
[DllImport("__Internal")]
|
||||
private static extern bool AVPPlayerIsPlaybackStalled(int player);
|
||||
|
||||
[DllImport("__Internal")]
|
||||
private static extern bool AVPPlayerPlay(int player);
|
||||
|
||||
[DllImport("__Internal")]
|
||||
private static extern void AVPPlayerPause(int player);
|
||||
|
||||
[DllImport("__Internal")]
|
||||
private static extern void AVPPlayerSeekToTime(int player, double time, bool fast);
|
||||
|
||||
[DllImport("__Internal")]
|
||||
private static extern double AVPPlayerGetCurrentTime(int player);
|
||||
|
||||
[DllImport("__Internal")]
|
||||
private static extern float AVPPlayerGetDuration(int player);
|
||||
|
||||
[DllImport("__Internal")]
|
||||
private static extern float AVPPlayerGetPlaybackRate(int player);
|
||||
|
||||
[DllImport("__Internal")]
|
||||
private static extern void AVPPlayerSetPlaybackRate(int player, float rate);
|
||||
|
||||
[DllImport("__Internal")]
|
||||
private static extern void AVPPlayerSetMuted(int player, bool muted);
|
||||
|
||||
[DllImport("__Internal")]
|
||||
private static extern bool AVPPlayerIsMuted(int player);
|
||||
|
||||
[DllImport("__Internal")]
|
||||
private static extern float AVPPlayerGetVolume(int player);
|
||||
|
||||
[DllImport("__Internal")]
|
||||
private static extern void AVPPlayerSetVolume(int player, float volume);
|
||||
|
||||
[DllImport("__Internal")]
|
||||
private static extern bool AVPPlayerHasVideo(int player);
|
||||
|
||||
[DllImport("__Internal")]
|
||||
private static extern bool AVPPlayerHasAudio(int player);
|
||||
|
||||
[DllImport("__Internal")]
|
||||
private static extern void AVPPlayerCreateVideoTexture(int textureId);
|
||||
|
||||
[DllImport("__Internal")]
|
||||
private static extern void AVPPlayerDestroyVideoTexture(int textureId);
|
||||
|
||||
[DllImport("__Internal")]
|
||||
private static extern void AVPPlayerFetchVideoTexture(int player, IntPtr texture, bool init);
|
||||
|
||||
[DllImport("__Internal")]
|
||||
private static extern int AVPPlayerGetDecodedFrameCount(int player);
|
||||
|
||||
[DllImport("__Internal")]
|
||||
private static extern bool AVPPlayerSupportedDecodedFrameCount(int player);
|
||||
|
||||
[DllImport("__Internal")]
|
||||
private static extern bool AVPPlayerHasMetadata(int player);
|
||||
|
||||
[DllImport("__Internal")]
|
||||
private static extern int AVPPlayerUpdatePlayerIndex(int id);
|
||||
|
||||
[DllImport("__Internal")]
|
||||
private static extern int AVPPlayerGetNumBufferedTimeRanges(int id);
|
||||
|
||||
[DllImport("__Internal")]
|
||||
private static extern double AVPPlayerGetTimeRangeStart(int id, int timeRangeIndex);
|
||||
[DllImport("__Internal")]
|
||||
private static extern double AVPPlayerGetTimeRangeEnd(int id, int timeRangeIndex);
|
||||
|
||||
[DllImport("__Internal")]
|
||||
private static extern string AVPPlayerGetVideoTrackName(int player, int trackIndex);
|
||||
|
||||
[DllImport("__Internal")]
|
||||
private static extern string AVPPlayerGetAudioTrackName(int player, int trackIndex);
|
||||
|
||||
[DllImport("__Internal")]
|
||||
private static extern string AVPPlayerGetTextTrackName(int player, int trackIndex);
|
||||
|
||||
[DllImport("__Internal")]
|
||||
private static extern string AVPPlayerGetVideoTrackLanguage(int player, int trackIndex);
|
||||
|
||||
[DllImport("__Internal")]
|
||||
private static extern string AVPPlayerGetAudioTrackLanguage(int player, int trackIndex);
|
||||
|
||||
[DllImport("__Internal")]
|
||||
private static extern string AVPPlayerGetTextTrackLanguage(int player, int trackIndex);
|
||||
|
||||
[DllImport("__Internal")]
|
||||
private static extern bool AVPPlayerIsVideoTrackActive(int player, int trackIndex);
|
||||
|
||||
[DllImport("__Internal")]
|
||||
private static extern bool AVPPlayerIsAudioTrackActive(int player, int trackIndex);
|
||||
|
||||
[DllImport("__Internal")]
|
||||
private static extern bool AVPPlayerIsTextTrackActive(int player, int trackIndex);
|
||||
|
||||
private WebGL.ExternalLibrary _externalLibrary = WebGL.ExternalLibrary.None;
|
||||
private int _playerIndex = -1;
|
||||
private int _playerID = -1;
|
||||
|
||||
#if AVPRO_WEBGL_USE_RENDERTEXTURE
|
||||
private RenderTexture _texture = null;
|
||||
#else
|
||||
private Texture2D _texture = null;
|
||||
#endif
|
||||
|
||||
private int _width = 0;
|
||||
private int _height = 0;
|
||||
private int _cachedVideoTrackCount = 0;
|
||||
private int _cachedAudioTrackCount = 0;
|
||||
private int _cachedTextTrackCount = 0;
|
||||
private bool _isDirtyVideoTracks = false;
|
||||
private bool _isDirtyAudioTracks = false;
|
||||
private bool _isDirtyTextTracks = false;
|
||||
private bool _useTextureMips = false;
|
||||
private System.IntPtr _cachedTextureNativePtr = System.IntPtr.Zero;
|
||||
|
||||
private static bool _isWebGL1 = false;
|
||||
|
||||
public static bool InitialisePlatform()
|
||||
{
|
||||
_isWebGL1 = (SystemInfo.graphicsDeviceType == UnityEngine.Rendering.GraphicsDeviceType.OpenGLES2);
|
||||
return true;
|
||||
}
|
||||
|
||||
public WebGLMediaPlayer(MediaPlayer.OptionsWebGL options)
|
||||
{
|
||||
SetOptions(options);
|
||||
}
|
||||
|
||||
public void SetOptions(MediaPlayer.OptionsWebGL options)
|
||||
{
|
||||
_externalLibrary = options.externalLibrary;
|
||||
_useTextureMips = options.useTextureMips;
|
||||
}
|
||||
|
||||
public override string GetVersion()
|
||||
{
|
||||
return "2.1.6";
|
||||
}
|
||||
|
||||
public override string GetExpectedVersion()
|
||||
{
|
||||
return GetVersion();
|
||||
}
|
||||
|
||||
public override bool OpenMedia(string path, long offset, string httpHeader, MediaHints mediaHints, int forceFileFormat = 0, bool startWithHighestBitrate = false)
|
||||
{
|
||||
bool result = false;
|
||||
|
||||
if (path.StartsWith("http://") ||
|
||||
path.StartsWith("https://") ||
|
||||
path.StartsWith("file://") ||
|
||||
path.StartsWith("blob:") ||
|
||||
path.StartsWith("chrome-extension://"))
|
||||
{
|
||||
int[] idValues = new int[2];
|
||||
idValues[0] = -1;
|
||||
AVPPlayerInsertVideoElement(path, idValues, (int)_externalLibrary);
|
||||
{
|
||||
int playerIndex = idValues[0];
|
||||
_playerID = idValues[1];
|
||||
|
||||
if (playerIndex > -1)
|
||||
{
|
||||
_playerIndex = playerIndex;
|
||||
_mediaHints = mediaHints;
|
||||
result = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogError("[AVProVideo] Unknown URL protocol");
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public override void CloseMedia()
|
||||
{
|
||||
if (_playerIndex != -1)
|
||||
{
|
||||
Pause();
|
||||
|
||||
_width = 0;
|
||||
_height = 0;
|
||||
_cachedVideoTrackCount = 0;
|
||||
_cachedAudioTrackCount = 0;
|
||||
_cachedTextTrackCount = 0;
|
||||
_isDirtyVideoTracks = false;
|
||||
_isDirtyAudioTracks = false;
|
||||
_isDirtyTextTracks = false;
|
||||
|
||||
AVPPlayerClose(_playerIndex);
|
||||
|
||||
if (_texture != null)
|
||||
{
|
||||
DestroyTexture();
|
||||
}
|
||||
|
||||
_playerIndex = -1;
|
||||
_playerID = -1;
|
||||
|
||||
base.CloseMedia();
|
||||
}
|
||||
}
|
||||
|
||||
public override bool IsLooping()
|
||||
{
|
||||
//Debug.Assert(_player != -1, "no player IsLooping");
|
||||
bool result = false;
|
||||
|
||||
if (_playerIndex != -1)
|
||||
{
|
||||
result = AVPPlayerIsLooping(_playerIndex);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public override void SetLooping(bool looping)
|
||||
{
|
||||
//Debug.Assert(_playerIndex != -1, "no player SetLooping");
|
||||
|
||||
AVPPlayerSetLooping(_playerIndex, looping);
|
||||
}
|
||||
|
||||
public override bool HasAudio()
|
||||
{
|
||||
//Debug.Assert(_player != -1, "no player HasAudio");
|
||||
bool result = false;
|
||||
|
||||
if (_playerIndex != -1)
|
||||
{
|
||||
result = AVPPlayerHasAudio(_playerIndex);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public override bool HasVideo()
|
||||
{
|
||||
//Debug.Assert(_player != -1, "no player HasVideo");
|
||||
bool result = false;
|
||||
|
||||
if (_playerIndex != -1)
|
||||
{
|
||||
result = AVPPlayerHasVideo(_playerIndex);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public override bool HasMetaData()
|
||||
{
|
||||
//Debug.Assert(_player != -1, "no player HasMetaData");
|
||||
bool result = false;
|
||||
|
||||
if (_playerIndex != -1)
|
||||
{
|
||||
result = AVPPlayerHasMetadata(_playerIndex);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public override bool CanPlay()
|
||||
{
|
||||
//Debug.Assert(_player != -1, "no player CanPlay");
|
||||
bool result = false;
|
||||
|
||||
if (_playerIndex != -1)
|
||||
{
|
||||
result = AVPPlayerReady(_playerIndex);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public override void Play()
|
||||
{
|
||||
Debug.Assert(_playerIndex != -1, "no player Play");
|
||||
|
||||
if (!AVPPlayerPlay(_playerIndex))
|
||||
{
|
||||
Debug.LogWarning("[AVProVideo] Browser permission prevented video playback");
|
||||
}
|
||||
}
|
||||
|
||||
public override void Pause()
|
||||
{
|
||||
Debug.Assert(_playerIndex != -1, "no player Pause");
|
||||
|
||||
AVPPlayerPause(_playerIndex);
|
||||
}
|
||||
|
||||
public override void Stop()
|
||||
{
|
||||
Debug.Assert(_playerIndex != -1, "no player Stop");
|
||||
|
||||
AVPPlayerPause(_playerIndex);
|
||||
}
|
||||
|
||||
public override void Seek(double time)
|
||||
{
|
||||
Debug.Assert(_playerIndex != -1, "no player Seek");
|
||||
AVPPlayerSeekToTime(_playerIndex, time, false);
|
||||
}
|
||||
|
||||
public override void SeekFast(double time)
|
||||
{
|
||||
Debug.Assert(_playerIndex != -1, "no player SeekFast");
|
||||
AVPPlayerSeekToTime(_playerIndex, time, true);
|
||||
}
|
||||
|
||||
public override double GetCurrentTime()
|
||||
{
|
||||
//Debug.Assert(_player != -1, "no player GetCurrentTime");
|
||||
double result = 0.0;
|
||||
if (_playerIndex != -1)
|
||||
{
|
||||
result = AVPPlayerGetCurrentTime(_playerIndex);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public override void SetPlaybackRate(float rate)
|
||||
{
|
||||
Debug.Assert(_playerIndex != -1, "no player SetPlaybackRate");
|
||||
|
||||
// No HTML implementations allow negative rate yet
|
||||
rate = Mathf.Clamp(rate, 0.25f, 8f);
|
||||
|
||||
AVPPlayerSetPlaybackRate(_playerIndex, rate);
|
||||
}
|
||||
|
||||
public override float GetPlaybackRate()
|
||||
{
|
||||
//Debug.Assert(_player != -1, "no player GetPlaybackRate");
|
||||
float result = 0.0f;
|
||||
|
||||
if (_playerIndex != -1)
|
||||
{
|
||||
result = AVPPlayerGetPlaybackRate(_playerIndex);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public override double GetDuration()
|
||||
{
|
||||
//Debug.Assert(_player != -1, "no player GetDuration");
|
||||
double result = 0.0;
|
||||
if (_playerIndex != -1)
|
||||
{
|
||||
result = AVPPlayerGetDuration(_playerIndex);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public override int GetVideoWidth()
|
||||
{
|
||||
if (_width == 0)
|
||||
{
|
||||
_width = AVPPlayerWidth(_playerIndex);
|
||||
}
|
||||
return _width;
|
||||
}
|
||||
|
||||
public override int GetVideoHeight()
|
||||
{
|
||||
if (_height == 0)
|
||||
{
|
||||
_height = AVPPlayerHeight(_playerIndex);
|
||||
}
|
||||
return _height;
|
||||
}
|
||||
|
||||
public override float GetVideoFrameRate()
|
||||
{
|
||||
// There is no way in HTML5 yet to get the frame rate of the video
|
||||
return 0f;
|
||||
}
|
||||
|
||||
public override bool IsSeeking()
|
||||
{
|
||||
//Debug.Assert(_player != -1, "no player IsSeeking");
|
||||
bool result = false;
|
||||
|
||||
if (_playerIndex != -1)
|
||||
{
|
||||
result = AVPPlayerIsSeeking(_playerIndex);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public override bool IsPlaying()
|
||||
{
|
||||
//Debug.Assert(_player != -1, "no player IsPlaying");
|
||||
bool result = false;
|
||||
|
||||
if (_playerIndex != -1)
|
||||
{
|
||||
result = AVPPlayerIsPlaying(_playerIndex);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public override bool IsPaused()
|
||||
{
|
||||
//Debug.Assert(_player != -1, "no player IsPaused");
|
||||
bool result = false;
|
||||
|
||||
if (_playerIndex != -1)
|
||||
{
|
||||
result = AVPPlayerIsPaused(_playerIndex);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public override bool IsFinished()
|
||||
{
|
||||
//Debug.Assert(_player != -1, "no player IsFinished");
|
||||
bool result = false;
|
||||
|
||||
if (_playerIndex != -1)
|
||||
{
|
||||
result = AVPPlayerIsFinished(_playerIndex);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public override bool IsBuffering()
|
||||
{
|
||||
//Debug.Assert(_player != -1, "no player IsBuffering");
|
||||
bool result = false;
|
||||
|
||||
if (_playerIndex != -1)
|
||||
{
|
||||
result = AVPPlayerIsBuffering(_playerIndex);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public override Texture GetTexture( int index )
|
||||
{
|
||||
return _texture;
|
||||
}
|
||||
|
||||
public override int GetTextureFrameCount()
|
||||
{
|
||||
//Debug.Assert(_player != -1, "no player GetTextureFrameCount");
|
||||
int result = 0;
|
||||
|
||||
if (_playerIndex != -1)
|
||||
{
|
||||
result = AVPPlayerGetDecodedFrameCount(_playerIndex);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
internal override StereoPacking InternalGetTextureStereoPacking()
|
||||
{
|
||||
return StereoPacking.Unknown;
|
||||
}
|
||||
|
||||
public override bool SupportsTextureFrameCount()
|
||||
{
|
||||
bool result = false;
|
||||
|
||||
if (_playerIndex != -1)
|
||||
{
|
||||
result = AVPPlayerSupportedDecodedFrameCount(_playerIndex);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public override bool RequiresVerticalFlip()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public override bool IsMuted()
|
||||
{
|
||||
//Debug.Assert(_player != -1, "no player IsMuted");
|
||||
bool result = false;
|
||||
|
||||
if (_playerIndex != -1)
|
||||
{
|
||||
result = AVPPlayerIsMuted(_playerIndex);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public override void MuteAudio(bool bMute)
|
||||
{
|
||||
Debug.Assert(_playerIndex != -1, "no player MuteAudio");
|
||||
|
||||
AVPPlayerSetMuted(_playerIndex, bMute);
|
||||
}
|
||||
|
||||
public override void SetVolume(float volume)
|
||||
{
|
||||
Debug.Assert(_playerIndex != -1, "no player SetVolume");
|
||||
|
||||
AVPPlayerSetVolume(_playerIndex, volume);
|
||||
}
|
||||
|
||||
public override float GetVolume()
|
||||
{
|
||||
//Debug.Assert(_player != -1, "no player GetVolume");
|
||||
float result = 0.0f;
|
||||
|
||||
if (_playerIndex != -1)
|
||||
{
|
||||
result = AVPPlayerGetVolume(_playerIndex);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public override void Render()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
private void UpdateLastErrorCode()
|
||||
{
|
||||
var code = AVPPlayerGetLastError(_playerIndex);
|
||||
|
||||
switch(code){
|
||||
case 0:
|
||||
_lastError = ErrorCode.None;
|
||||
break;
|
||||
case 1:
|
||||
_lastError = ErrorCode.LoadFailed;
|
||||
break;
|
||||
case 2:
|
||||
_lastError = ErrorCode.LoadFailed;
|
||||
break;
|
||||
case 3:
|
||||
_lastError = ErrorCode.DecodeFailed;
|
||||
break;
|
||||
case 4:
|
||||
_lastError = ErrorCode.LoadFailed;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsMipMapGenerationSupported(int videoWidth, int videoHeight)
|
||||
{
|
||||
if (!_isWebGL1 || (Mathf.IsPowerOfTwo(videoWidth) && Mathf.IsPowerOfTwo(videoHeight)))
|
||||
{
|
||||
// Mip generation only supported in WebGL 2.0, or WebGL 1.0 when using power-of-two textures
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void CreateTexture()
|
||||
{
|
||||
//Debug.Log("creating texture " + _width + " X " + _height);
|
||||
|
||||
#if AVPRO_WEBGL_USE_RENDERTEXTURE
|
||||
_texture = new RenderTexture(_width, _height, 0, RenderTextureFormat.Default);
|
||||
_texture.autoGenerateMips = false;
|
||||
_texture.useMipMap = (_useTextureMips && IsMipMapGenerationSupported(_width, _height));
|
||||
_texture.Create();
|
||||
_cachedTextureNativePtr = _texture.GetNativeTexturePtr();
|
||||
#else
|
||||
int textureId = 80000 + _playerIndex;
|
||||
_cachedTextureNativePtr = new System.IntPtr(textureId);
|
||||
AVPPlayerCreateVideoTexture(textureId);
|
||||
// TODO: add support for mip generation
|
||||
_texture = Texture2D.CreateExternalTexture(_width, _height, TextureFormat.RGBA32, false, false, _cachedTextureNativePtr);
|
||||
if (_useTextureMips)
|
||||
{
|
||||
Debug.LogWarning("[AVProVideo] Texture Mips not yet implemented in this WebGL rendering path");
|
||||
}
|
||||
//Debug.Log("created texture1 " + _texture);
|
||||
//Debug.Log("created texture2 " + _texture.GetNativeTexturePtr().ToInt32());
|
||||
#endif
|
||||
|
||||
ApplyTextureProperties(_texture);
|
||||
|
||||
bool initTexture = true;
|
||||
#if AVPRO_WEBGL_USE_RENDERTEXTURE
|
||||
// Textures in WebGL 2.0 don't require texImage2D as they are already recreated with texStorage2D
|
||||
initTexture = _isWebGL1;
|
||||
#endif
|
||||
AVPPlayerFetchVideoTexture(_playerIndex, _cachedTextureNativePtr, initTexture);
|
||||
}
|
||||
|
||||
private void DestroyTexture()
|
||||
{
|
||||
// Have to update with zero to release Metal textures!
|
||||
//_texture.UpdateExternalTexture(0);
|
||||
if (_texture != null)
|
||||
{
|
||||
#if AVPRO_WEBGL_USE_RENDERTEXTURE
|
||||
RenderTexture.Destroy(_texture);
|
||||
#else
|
||||
Texture2D.Destroy(_texture);
|
||||
AVPPlayerDestroyVideoTexture(_cachedTextureNativePtr.ToInt32());
|
||||
#endif
|
||||
_texture = null;
|
||||
}
|
||||
_cachedTextureNativePtr = System.IntPtr.Zero;
|
||||
}
|
||||
|
||||
public override void Update()
|
||||
{
|
||||
if(_playerID >= 0) // CheckPlayer's index and update it
|
||||
{
|
||||
_playerIndex = AVPPlayerUpdatePlayerIndex(_playerID);
|
||||
}
|
||||
|
||||
if(_playerIndex >= 0)
|
||||
{
|
||||
CheckTracksDirty();
|
||||
UpdateTracks();
|
||||
UpdateTextCue();
|
||||
UpdateSubtitles();
|
||||
UpdateLastErrorCode();
|
||||
|
||||
if (AVPPlayerReady(_playerIndex))
|
||||
{
|
||||
UpdateTimeRanges();
|
||||
if (AVPPlayerHasVideo(_playerIndex))
|
||||
{
|
||||
_width = AVPPlayerWidth(_playerIndex);
|
||||
_height = AVPPlayerHeight(_playerIndex);
|
||||
|
||||
if (_texture != null && (_texture.width != _width || _texture.height != _height))
|
||||
{
|
||||
DestroyTexture();
|
||||
}
|
||||
|
||||
if (_texture == null && _width > 0 && _height > 0)
|
||||
{
|
||||
CreateTexture();
|
||||
}
|
||||
|
||||
// Update the texture
|
||||
if (_cachedTextureNativePtr != System.IntPtr.Zero)
|
||||
{
|
||||
// TODO: only update the texture when the frame count changes
|
||||
// (actually this will break the update for certain browsers such as edge and possibly safari - Sunrise)
|
||||
AVPPlayerFetchVideoTexture(_playerIndex, _cachedTextureNativePtr, false);
|
||||
|
||||
#if AVPRO_WEBGL_USE_RENDERTEXTURE
|
||||
if (_texture.useMipMap)
|
||||
{
|
||||
_texture.GenerateMips();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
UpdateDisplayFrameRate();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void CheckTracksDirty()
|
||||
{
|
||||
_isDirtyVideoTracks = false;
|
||||
_isDirtyAudioTracks = false;
|
||||
_isDirtyTextTracks = false;
|
||||
|
||||
// TODO: replace this crude polling check with events, or only do it once metadataReady
|
||||
// Need to add event support as tracks can be added via HTML (especially text)
|
||||
int videoTrackCount = AVPPlayerGetVideoTrackCount(_playerIndex);
|
||||
int audioTrackCount = AVPPlayerGetAudioTrackCount(_playerIndex);
|
||||
int textTrackCount = AVPPlayerGetTextTrackCount(_playerIndex);
|
||||
|
||||
_isDirtyVideoTracks = (_cachedVideoTrackCount != videoTrackCount);
|
||||
_isDirtyAudioTracks = (_cachedAudioTrackCount != audioTrackCount);
|
||||
_isDirtyTextTracks = ( _cachedTextTrackCount != textTrackCount);
|
||||
|
||||
_cachedVideoTrackCount = videoTrackCount;
|
||||
_cachedAudioTrackCount = audioTrackCount;
|
||||
_cachedTextTrackCount = textTrackCount;
|
||||
}
|
||||
|
||||
private void UpdateTimeRanges()
|
||||
{
|
||||
{
|
||||
int rangeCount = AVPPlayerGetNumBufferedTimeRanges(_playerIndex);
|
||||
if (rangeCount != _bufferedTimes.Count)
|
||||
{
|
||||
_bufferedTimes._ranges = new TimeRange[rangeCount];
|
||||
}
|
||||
for (int i = 0; i < rangeCount; i++)
|
||||
{
|
||||
double startTime = AVPPlayerGetTimeRangeStart(_playerIndex, i);
|
||||
double endTime = AVPPlayerGetTimeRangeEnd(_playerIndex, i);
|
||||
_bufferedTimes._ranges[i] = new TimeRange(startTime, endTime - startTime);
|
||||
}
|
||||
_bufferedTimes.CalculateRange();
|
||||
}
|
||||
|
||||
{
|
||||
double duration = GetDuration();
|
||||
if (duration > 0.0)
|
||||
{
|
||||
_seekableTimes._ranges = new TimeRange[1];
|
||||
_seekableTimes._ranges[0] = new TimeRange(0.0, duration);
|
||||
}
|
||||
else
|
||||
{
|
||||
_seekableTimes._ranges = new TimeRange[0];
|
||||
}
|
||||
_seekableTimes.CalculateRange();
|
||||
}
|
||||
}
|
||||
|
||||
public override void Dispose()
|
||||
{
|
||||
CloseMedia();
|
||||
}
|
||||
|
||||
public override bool IsPlaybackStalled()
|
||||
{
|
||||
bool result = false;
|
||||
if (_playerIndex > -1)
|
||||
{
|
||||
result = AVPPlayerIsPlaybackStalled(_playerIndex) && IsPlaying();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// Tracks
|
||||
internal override int InternalGetTrackCount(TrackType trackType)
|
||||
{
|
||||
int result = 0;
|
||||
switch (trackType)
|
||||
{
|
||||
case TrackType.Video:
|
||||
result = AVPPlayerGetVideoTrackCount(_playerIndex);
|
||||
break;
|
||||
case TrackType.Audio:
|
||||
result = AVPPlayerGetAudioTrackCount(_playerIndex);
|
||||
break;
|
||||
case TrackType.Text:
|
||||
result = AVPPlayerGetTextTrackCount(_playerIndex);
|
||||
break;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
internal override bool InternalIsChangedTracks(TrackType trackType)
|
||||
{
|
||||
bool result = false;
|
||||
switch (trackType)
|
||||
{
|
||||
case TrackType.Video:
|
||||
result = _isDirtyVideoTracks;
|
||||
break;
|
||||
case TrackType.Audio:
|
||||
result = _isDirtyAudioTracks;
|
||||
break;
|
||||
case TrackType.Text:
|
||||
result = _isDirtyTextTracks;
|
||||
break;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
internal override bool InternalSetActiveTrack(TrackType trackType, int trackId)
|
||||
{
|
||||
bool result = false;
|
||||
switch (trackType)
|
||||
{
|
||||
case TrackType.Video:
|
||||
result = AVPPlayerSetActiveVideoTrack(_playerIndex, trackId);
|
||||
break;
|
||||
case TrackType.Audio:
|
||||
result = AVPPlayerSetActiveAudioTrack(_playerIndex, trackId);
|
||||
break;
|
||||
case TrackType.Text:
|
||||
result = AVPPlayerSetActiveTextTrack(_playerIndex, trackId);
|
||||
break;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
internal override TrackBase InternalGetTrackInfo(TrackType trackType, int trackIndex, ref bool isActiveTrack)
|
||||
{
|
||||
TrackBase result = null;
|
||||
|
||||
switch (trackType)
|
||||
{
|
||||
case TrackType.Video:
|
||||
{
|
||||
string trackName = AVPPlayerGetVideoTrackName(_playerIndex, trackIndex);
|
||||
string trackLanguage = AVPPlayerGetVideoTrackLanguage(_playerIndex, trackIndex);
|
||||
bool isActive = AVPPlayerIsVideoTrackActive(_playerIndex, trackIndex);
|
||||
result = new VideoTrack(trackIndex, trackName, trackLanguage, isActive);
|
||||
break;
|
||||
}
|
||||
case TrackType.Audio:
|
||||
{
|
||||
string trackName = AVPPlayerGetAudioTrackName(_playerIndex, trackIndex);
|
||||
string trackLanguage = AVPPlayerGetAudioTrackLanguage(_playerIndex, trackIndex);
|
||||
bool isActive = AVPPlayerIsAudioTrackActive(_playerIndex, trackIndex);
|
||||
result = new AudioTrack(trackIndex, trackName, trackLanguage, isActive);
|
||||
break;
|
||||
}
|
||||
case TrackType.Text:
|
||||
{
|
||||
string trackName = AVPPlayerGetTextTrackName(_playerIndex, trackIndex);
|
||||
string trackLanguage = AVPPlayerGetTextTrackLanguage(_playerIndex, trackIndex);
|
||||
bool isActive = AVPPlayerIsTextTrackActive(_playerIndex, trackIndex);
|
||||
result = new TextTrack(trackIndex, trackName, trackLanguage, isActive);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// Text Cue stub methods
|
||||
internal override bool InternalIsChangedTextCue() { return false; }
|
||||
internal override string InternalGetCurrentTextCue() { return string.Empty; }
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c044ff13d5570e64a8156bc718b3cfec
|
||||
timeCreated: 1468230219
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6f3c954eb61392a4193295a8376bd8db
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
+322
@@ -0,0 +1,322 @@
|
||||
// NOTE: We only allow this script to compile in editor so we can easily check for compilation issues
|
||||
#if (UNITY_EDITOR || (UNITY_STANDALONE_WIN || UNITY_WSA_10_0))
|
||||
|
||||
#define AVPROVIDEO_FIXREGRESSION_TEXTUREQUALITY_UNITY542
|
||||
#if UNITY_WSA_10 || ENABLE_IL2CPP
|
||||
#define AVPROVIDEO_MARSHAL_RETURN_BOOL
|
||||
#endif
|
||||
#if UNITY_2019_3_OR_NEWER && !UNITY_2020_1_OR_NEWER
|
||||
#define AVPROVIDEO_FIX_UPDATEEXTERNALTEXTURE_LEAK
|
||||
#endif
|
||||
|
||||
using UnityEngine;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Text;
|
||||
|
||||
#if NETFX_CORE
|
||||
using Windows.Storage.Streams;
|
||||
#endif
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Copyright 2015-2021 RenderHeads Ltd. All rights reserved.
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
namespace RenderHeads.Media.AVProVideo
|
||||
{
|
||||
/// <summary>
|
||||
/// Windows desktop and UWP implementation of BaseMediaPlayer
|
||||
/// </summary>
|
||||
public /*sealed*/ partial class WindowsMediaPlayer : BaseMediaPlayer
|
||||
{
|
||||
|
||||
#region IBufferedDisplay Implementation
|
||||
|
||||
private BufferedFrameSelectionMode _frameSelectionMode = BufferedFrameSelectionMode.None;
|
||||
private bool _pauseOnPrerollComplete = false;
|
||||
private IBufferedDisplay _masterDisplay;
|
||||
private IBufferedDisplay[] _slaveDisplays;
|
||||
private double _displayClockTime = 0.0;
|
||||
private double _timeAccumulation = 0.0;
|
||||
private bool _needsInitialFrame = true;
|
||||
|
||||
private void FlushFrameBuffering(bool releaseTexture)
|
||||
{
|
||||
if (_frameSelectionMode == BufferedFrameSelectionMode.None) return;
|
||||
|
||||
if (releaseTexture && _textureFrame.internalNativePointer != System.IntPtr.Zero)
|
||||
{
|
||||
Native.UnlockTextureFrame(_instance, ref _textureFrame);
|
||||
_textureFrame.internalNativePointer = System.IntPtr.Zero;
|
||||
_textureFrame.texturePointer = System.IntPtr.Zero;
|
||||
}
|
||||
|
||||
Native.FlushFrameBuffering(_instance);
|
||||
|
||||
// Native _pauseOnPrerollComplete needs to be reset
|
||||
//Native.SetFrameBufferingEnabled(_instance, (_frameSelectionMode != BufferedFrameSelectionMode.None), _pauseOnPrerollComplete);
|
||||
|
||||
_needsInitialFrame = true;
|
||||
_timeAccumulation = 0.0;
|
||||
}
|
||||
|
||||
internal override long InternalUpdateBufferedDisplay()
|
||||
{
|
||||
BufferedFramesState state = GetBufferedFramesState();
|
||||
if (state.bufferedFrameCount > 0)
|
||||
{
|
||||
if (_frameSelectionMode == BufferedFrameSelectionMode.NewestFrame)
|
||||
{
|
||||
SetBufferedDisplayTime(_frameSelectionMode, -1, false);
|
||||
}
|
||||
else if (_frameSelectionMode == BufferedFrameSelectionMode.OldestFrame)
|
||||
{
|
||||
SetBufferedDisplayTime(_frameSelectionMode, -1, false);
|
||||
}
|
||||
else if (_frameSelectionMode == BufferedFrameSelectionMode.ElapsedTime ||
|
||||
_frameSelectionMode == BufferedFrameSelectionMode.ElapsedTimeVsynced)
|
||||
{
|
||||
// Only start consuming frames on these conditions
|
||||
bool needsInitialFrame = (_textureFrame.texturePointer == System.IntPtr.Zero || _needsInitialFrame);
|
||||
bool playingBufferedFrames = (IsPrerollComplete() && (IsPlaying() || (!IsPlaying() && IsFinished())));
|
||||
if (needsInitialFrame || playingBufferedFrames)
|
||||
{
|
||||
if (needsInitialFrame)
|
||||
{
|
||||
if (SetBufferedDisplayTime(BufferedFrameSelectionMode.OldestFrame, -1, true))
|
||||
{
|
||||
_displayClockTime = _textureFrame.timeStamp;
|
||||
_needsInitialFrame = false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// TODO: run without vsync, just show next frame (use media clock for present?)
|
||||
// use our own clock...
|
||||
const double SecondsToHNS = 10000000.0;
|
||||
double videoFrameDuration = SecondsToHNS / (double)GetVideoFrameRate();
|
||||
long videoDuration = (long)Math.Floor(SecondsToHNS * GetDuration());
|
||||
long lastFrameTime = Math.Max(videoDuration, state.maxTimeStamp);
|
||||
double delta = SecondsToHNS * Time.deltaTime;
|
||||
|
||||
if (_frameSelectionMode == BufferedFrameSelectionMode.ElapsedTimeVsynced && QualitySettings.vSyncCount > 0)
|
||||
{
|
||||
#if UNITY_2022_2_OR_NEWER
|
||||
double refreshRate = Screen.currentResolution.refreshRateRatio.value;
|
||||
#else
|
||||
double refreshRate = (double)( Screen.currentResolution.refreshRate );
|
||||
#endif
|
||||
|
||||
// Since we're running with vsync enabled, the MINIMUM elapsed time will be 1 monitor refresh (multiplied by QualitySettings.vSyncCount)
|
||||
double monitorDuration = (QualitySettings.vSyncCount * SecondsToHNS) / refreshRate;
|
||||
|
||||
int wholeFrames = (int)System.Math.Floor(_timeAccumulation / monitorDuration);
|
||||
wholeFrames = System.Math.Max(1, wholeFrames);
|
||||
delta = monitorDuration * wholeFrames;
|
||||
|
||||
//LogBufferState();
|
||||
if (wholeFrames > 1)
|
||||
{
|
||||
//Debug.Log(Time.frameCount + "] " + Time.deltaTime + " " + wholeFrames + " " + _timeAccumulation + " " + _timeAccumulation / SecondsToHNS);
|
||||
//LogBufferState();
|
||||
}
|
||||
|
||||
_timeAccumulation += (Time.deltaTime * SecondsToHNS) - delta;
|
||||
|
||||
//delta = monitorDuration;
|
||||
|
||||
/*double actualFrameDuration = Time.deltaTime * SecondsToHNS;
|
||||
double idealFrameTimeDifference = (actualFrameDuration);// - minMonitorDuration);
|
||||
if (idealFrameTimeDifference > (minMonitorDuration / 2))
|
||||
{
|
||||
int droppedFrames = (int)Math.Round(idealFrameTimeDifference / minMonitorDuration);
|
||||
//Debug.Log(Time.maximumDeltaTime + " " + Time.deltaTime + " " + actualFrameDuration + " " + idealFrameTimeDifference + " = " + droppedFrames);
|
||||
delta += minMonitorDuration * droppedFrames;
|
||||
|
||||
//LogBufferState();
|
||||
}
|
||||
else
|
||||
{
|
||||
//Debug.Log(Time.deltaTime);
|
||||
}
|
||||
// If we're running slower than this or there is a frame drop, the elapsed time will be a multiple
|
||||
// of the monitor refresh rate
|
||||
*/
|
||||
}
|
||||
|
||||
_displayClockTime += delta;
|
||||
|
||||
int multiple = (int)videoFrameDuration;
|
||||
long snappedFrameTime = (long)Math.Floor(_displayClockTime / multiple) * multiple;
|
||||
if (_isLooping && snappedFrameTime > lastFrameTime)
|
||||
{
|
||||
snappedFrameTime %= lastFrameTime;
|
||||
_needsInitialFrame = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
snappedFrameTime = Math.Min(snappedFrameTime, lastFrameTime);
|
||||
}
|
||||
|
||||
if (System.Math.Abs(snappedFrameTime - _textureFrame.timeStamp) > 1000)
|
||||
{
|
||||
//Debug.Log("1 " + _displayClockTime + " > " + snappedFrameTime + " d:" + delta);
|
||||
//LogBufferState();
|
||||
|
||||
if (_needsInitialFrame)
|
||||
{
|
||||
if (SetBufferedDisplayTime(BufferedFrameSelectionMode.OldestFrame, -1, true))
|
||||
{
|
||||
_displayClockTime = _textureFrame.timeStamp;
|
||||
//Debug.Log("initial: " + _displayClockTime);
|
||||
_needsInitialFrame = false;
|
||||
}
|
||||
}
|
||||
else if (!SetBufferedDisplayTime(_frameSelectionMode, snappedFrameTime, false))
|
||||
{
|
||||
//Debug.LogWarning("[AVProVideo] failed to set time at " + snappedFrameTime);
|
||||
//LogBufferState();
|
||||
|
||||
// Try to snap to oldest buffered time
|
||||
_displayClockTime = (state.minTimeStamp + state.maxTimeStamp) / 2.0;
|
||||
snappedFrameTime = (long)Math.Floor(_displayClockTime / multiple) * multiple;
|
||||
if (_isLooping && snappedFrameTime > lastFrameTime)
|
||||
{
|
||||
snappedFrameTime %= lastFrameTime;
|
||||
}
|
||||
else
|
||||
{
|
||||
snappedFrameTime = Math.Min(snappedFrameTime, lastFrameTime);
|
||||
}
|
||||
|
||||
if (SetBufferedDisplayTime(BufferedFrameSelectionMode.FromExternalTimeClosest, snappedFrameTime, false))
|
||||
{
|
||||
_displayClockTime = _textureFrame.timeStamp;
|
||||
//Debug.LogWarning("[AVProVideo] Good set: " + _displayClockTime);
|
||||
}
|
||||
else
|
||||
{
|
||||
//Debug.LogWarning("[AVProVideo] Failed to display frame time " + snappedFrameTime);
|
||||
//LogBufferState();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (_frameSelectionMode == BufferedFrameSelectionMode.FromExternalTime)
|
||||
{
|
||||
if (_masterDisplay != null)
|
||||
{
|
||||
// Use the time from the master
|
||||
long timeStamp = _masterDisplay.UpdateBufferedDisplay();
|
||||
if (timeStamp != GetTextureTimeStamp())
|
||||
{
|
||||
if (!SetBufferedDisplayTime(BufferedFrameSelectionMode.FromExternalTimeClosest, timeStamp, false))
|
||||
{
|
||||
Debug.LogWarning("[AVProVideo] Failed to display frame using external clock at time " + timeStamp);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return GetTextureTimeStamp();
|
||||
}
|
||||
|
||||
private void LogBufferState()
|
||||
{
|
||||
BufferedFramesState state = GetBufferedFramesState();
|
||||
long timeStamp = GetTextureTimeStamp();
|
||||
string result = string.Format("[AVProVideo] {4} - {2},{3}\t\t{0}-{1} ({5})", state.minTimeStamp, state.maxTimeStamp, state.bufferedFrameCount, state.freeFrameCount, timeStamp, Time.deltaTime);
|
||||
Debug.Log(result);
|
||||
}
|
||||
|
||||
private bool SetBufferedDisplayTime(BufferedFrameSelectionMode mode, long timeOfDesiredFrameToDisplay, bool ignorePreroll)
|
||||
{
|
||||
bool result = false;
|
||||
//if (!_isPaused)
|
||||
{
|
||||
result = Native.LockTextureFrame(_instance, mode, timeOfDesiredFrameToDisplay, ref _textureFrame, ignorePreroll);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public override BufferedFramesState GetBufferedFramesState()
|
||||
{
|
||||
BufferedFramesState state = new BufferedFramesState();
|
||||
Native.GetBufferedFramesState(_instance, ref state);
|
||||
return state;
|
||||
}
|
||||
|
||||
public override void SetBufferedDisplayMode(BufferedFrameSelectionMode mode, IBufferedDisplay master = null)
|
||||
{
|
||||
_frameSelectionMode = mode;
|
||||
_masterDisplay = master;
|
||||
UpdateBufferedDisplay();
|
||||
}
|
||||
|
||||
public override void SetBufferedDisplayOptions(bool pauseOnPrerollComplete)
|
||||
{
|
||||
_pauseOnPrerollComplete = pauseOnPrerollComplete;
|
||||
Native.SetFrameBufferingEnabled(_instance, (_frameSelectionMode != BufferedFrameSelectionMode.None), _pauseOnPrerollComplete);
|
||||
}
|
||||
|
||||
public override void SetSlaves(IBufferedDisplay[] slaves)
|
||||
{
|
||||
foreach (IBufferedDisplay slave in slaves)
|
||||
{
|
||||
slave.SetBufferedDisplayMode(BufferedFrameSelectionMode.FromExternalTime, this);
|
||||
}
|
||||
_slaveDisplays = slaves;
|
||||
}
|
||||
|
||||
private bool IsPrerollComplete()
|
||||
{
|
||||
bool result = true;
|
||||
|
||||
if (GetBufferedFramesState().prerolledCount <= 0)
|
||||
{
|
||||
result = false;
|
||||
}
|
||||
if (_slaveDisplays != null && result)
|
||||
{
|
||||
foreach (IBufferedDisplay slave in _slaveDisplays)
|
||||
{
|
||||
if (slave.GetBufferedFramesState().prerolledCount <= 0)
|
||||
{
|
||||
result = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private partial struct Native
|
||||
{
|
||||
[DllImport("AVProVideo")]
|
||||
public static extern bool GetBufferedFramesState(System.IntPtr playerInstance, ref BufferedFramesState state);
|
||||
|
||||
[DllImport("AVProVideo")]
|
||||
#if AVPROVIDEO_MARSHAL_RETURN_BOOL
|
||||
[return: MarshalAs(UnmanagedType.I1)]
|
||||
#endif
|
||||
public static extern bool LockTextureFrame(System.IntPtr instance, BufferedFrameSelectionMode mode, long time, ref TextureFrame textureFrame, bool ignorePreroll);
|
||||
|
||||
[DllImport("AVProVideo")]
|
||||
public static extern void UnlockTextureFrame(System.IntPtr instance, ref TextureFrame textureFrame);
|
||||
|
||||
[DllImport("AVProVideo")]
|
||||
public static extern void ReleaseTextureFrame(System.IntPtr instance, ref TextureFrame textureFrame);
|
||||
|
||||
[DllImport("AVProVideo")]
|
||||
public static extern void FlushFrameBuffering(System.IntPtr instance);
|
||||
}
|
||||
|
||||
#endregion // IBufferedDisplay Implementation
|
||||
|
||||
}
|
||||
}
|
||||
#endif
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2b36cc2d6962ce34e86c5a83a0de6d4a
|
||||
timeCreated: 1630292296
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,961 @@
|
||||
// NOTE: We only allow this script to compile in editor so we can easily check for compilation issues
|
||||
#if (UNITY_EDITOR || (UNITY_STANDALONE_WIN || UNITY_WSA_10_0))
|
||||
|
||||
#if UNITY_WSA_10 || ENABLE_IL2CPP
|
||||
#define AVPROVIDEO_MARSHAL_RETURN_BOOL
|
||||
#endif
|
||||
|
||||
using UnityEngine;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Text;
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Copyright 2018-2021 RenderHeads Ltd. All rights reserved.
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
namespace RenderHeads.Media.AVProVideo
|
||||
{
|
||||
public enum PlaybackState
|
||||
{
|
||||
None = 0,
|
||||
Opening = 1,
|
||||
Buffering = 2, // Replace with Stalled and add Buffering as State 64??
|
||||
Playing = 3,
|
||||
Paused = 4,
|
||||
StateMask = 7,
|
||||
|
||||
Seeking = 32,
|
||||
}
|
||||
|
||||
public class AuthData
|
||||
{
|
||||
public string URL { get; set; }
|
||||
public string Token { get; set; }
|
||||
public byte[] KeyBytes { get; set; }
|
||||
|
||||
public AuthData()
|
||||
{
|
||||
Clear();
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
URL = string.Empty;
|
||||
Token = string.Empty;
|
||||
KeyBytes = null;
|
||||
}
|
||||
|
||||
public string KeyBase64
|
||||
{
|
||||
get
|
||||
{
|
||||
if (KeyBytes != null)
|
||||
{
|
||||
return System.Convert.ToBase64String(KeyBytes);
|
||||
}
|
||||
else
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
set
|
||||
{
|
||||
if (value != null)
|
||||
{
|
||||
KeyBytes = System.Convert.FromBase64String(value);
|
||||
}
|
||||
else
|
||||
{
|
||||
KeyBytes = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
public partial class WindowsRtMediaPlayer : BaseMediaPlayer
|
||||
{
|
||||
private bool _isMediaLoaded = false;
|
||||
private bool _use10BitTextures = false;
|
||||
private bool _useLowLiveLatency = false;
|
||||
|
||||
public WindowsRtMediaPlayer(MediaPlayer.OptionsWindows options) : base()
|
||||
{
|
||||
_playerDescription = "WinRT";
|
||||
_use10BitTextures = options.use10BitTextures;
|
||||
_useLowLiveLatency = options.useLowLiveLatency;
|
||||
for (int i = 0; i < _eyeTextures.Length; i++)
|
||||
{
|
||||
_eyeTextures[i] = new EyeTexture();
|
||||
}
|
||||
}
|
||||
|
||||
public WindowsRtMediaPlayer(MediaPlayer.OptionsWindowsUWP options) : base()
|
||||
{
|
||||
_playerDescription = "WinRT";
|
||||
_use10BitTextures = options.use10BitTextures;
|
||||
_useLowLiveLatency = options.useLowLiveLatency;
|
||||
for (int i = 0; i < _eyeTextures.Length; i++)
|
||||
{
|
||||
_eyeTextures[i] = new EyeTexture();
|
||||
}
|
||||
}
|
||||
|
||||
public override bool CanPlay()
|
||||
{
|
||||
return HasMetaData();
|
||||
}
|
||||
|
||||
public override void Dispose()
|
||||
{
|
||||
CloseMedia();
|
||||
if (_playerInstance != System.IntPtr.Zero)
|
||||
{
|
||||
Native.DestroyPlayer(_playerInstance); _playerInstance = System.IntPtr.Zero;
|
||||
Native.IssueRenderThreadEvent_FreeAllTextures();
|
||||
}
|
||||
for (int i = 0; i < _eyeTextures.Length; i++)
|
||||
{
|
||||
_eyeTextures[i].Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
public override bool PlayerSupportsLinearColorSpace()
|
||||
{
|
||||
// The current player doesn't support rendering to SRGB textures
|
||||
return false;
|
||||
}
|
||||
|
||||
public override double GetCurrentTime()
|
||||
{
|
||||
return Native.GetCurrentPosition(_playerInstance);
|
||||
}
|
||||
|
||||
public override double GetDuration()
|
||||
{
|
||||
return Native.GetDuration(_playerInstance);
|
||||
}
|
||||
|
||||
public override float GetPlaybackRate()
|
||||
{
|
||||
return Native.GetPlaybackRate(_playerInstance);
|
||||
}
|
||||
|
||||
public override Texture GetTexture(int index = 0)
|
||||
{
|
||||
Texture result = null;
|
||||
if (_frameTimeStamp > 0 && index < _eyeTextures.Length)
|
||||
{
|
||||
result = _eyeTextures[index].texture;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public override int GetTextureCount()
|
||||
{
|
||||
if (_eyeTextures[1].texture != null)
|
||||
{
|
||||
return 2;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
public override int GetTextureFrameCount()
|
||||
{
|
||||
return (int)_frameTimeStamp;
|
||||
}
|
||||
|
||||
internal override StereoPacking InternalGetTextureStereoPacking()
|
||||
{
|
||||
return Native.GetStereoPacking(_playerInstance);
|
||||
}
|
||||
|
||||
public override string GetVersion()
|
||||
{
|
||||
return _version;
|
||||
}
|
||||
|
||||
public override string GetExpectedVersion()
|
||||
{
|
||||
return Helper.ExpectedPluginVersion.WinRT;
|
||||
}
|
||||
|
||||
public override float GetVideoFrameRate()
|
||||
{
|
||||
float result = 0f;
|
||||
Native.VideoTrack videoTrack;
|
||||
if (Native.GetActiveVideoTrackInfo(_playerInstance, out videoTrack))
|
||||
{
|
||||
result = videoTrack.frameRate;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public override int GetVideoWidth()
|
||||
{
|
||||
int result = 0;
|
||||
if (_eyeTextures[0].texture)
|
||||
{
|
||||
result = _eyeTextures[0].texture.width;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public override int GetVideoHeight()
|
||||
{
|
||||
int result = 0;
|
||||
if (_eyeTextures[0].texture)
|
||||
{
|
||||
result = _eyeTextures[0].texture.height;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public override float GetVolume()
|
||||
{
|
||||
return Native.GetAudioVolume(_playerInstance);
|
||||
}
|
||||
|
||||
public override void SetBalance(float balance)
|
||||
{
|
||||
Native.SetAudioBalance(_playerInstance, balance);
|
||||
}
|
||||
|
||||
public override float GetBalance()
|
||||
{
|
||||
return Native.GetAudioBalance(_playerInstance);
|
||||
}
|
||||
|
||||
public override bool HasAudio()
|
||||
{
|
||||
return _audioTracks.Count > 0;
|
||||
}
|
||||
|
||||
public override bool HasMetaData()
|
||||
{
|
||||
return Native.GetDuration(_playerInstance) > 0f;
|
||||
}
|
||||
|
||||
public override bool HasVideo()
|
||||
{
|
||||
return _videoTracks.Count > 0;
|
||||
}
|
||||
|
||||
public override bool IsBuffering()
|
||||
{
|
||||
return ((Native.GetPlaybackState(_playerInstance) & PlaybackState.StateMask) == PlaybackState.Buffering);
|
||||
}
|
||||
|
||||
public override bool IsFinished()
|
||||
{
|
||||
bool result = false;
|
||||
if (IsPaused() && !IsSeeking() && GetCurrentTime() >= GetDuration())
|
||||
{
|
||||
result = true;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public override bool IsLooping()
|
||||
{
|
||||
return Native.IsLooping(_playerInstance);
|
||||
}
|
||||
|
||||
public override bool IsMuted()
|
||||
{
|
||||
return Native.IsAudioMuted(_playerInstance);
|
||||
}
|
||||
|
||||
public override bool IsPaused()
|
||||
{
|
||||
return ((Native.GetPlaybackState(_playerInstance) & PlaybackState.StateMask) == PlaybackState.Paused);
|
||||
}
|
||||
|
||||
public override bool IsPlaying()
|
||||
{
|
||||
return ((Native.GetPlaybackState(_playerInstance) & PlaybackState.StateMask) == PlaybackState.Playing);
|
||||
}
|
||||
|
||||
public override bool IsSeeking()
|
||||
{
|
||||
return ((Native.GetPlaybackState(_playerInstance) & PlaybackState.Seeking) != 0);
|
||||
}
|
||||
|
||||
public override void MuteAudio(bool bMuted)
|
||||
{
|
||||
Native.SetAudioMuted(_playerInstance, bMuted);
|
||||
}
|
||||
|
||||
// TODO: replace all these options with a structure
|
||||
public override bool OpenMedia(string path, long offset, string httpHeader, MediaHints mediaHints, int forceFileFormat = 0, bool startWithHighestBitrate = false)
|
||||
{
|
||||
bool result = false;
|
||||
|
||||
CloseMedia();
|
||||
|
||||
if (_playerInstance == System.IntPtr.Zero)
|
||||
{
|
||||
_playerInstance = Native.CreatePlayer();
|
||||
|
||||
// Force setting any auth data as it wouldn't have been set without a _playerInstance
|
||||
AuthenticationData = _nextAuthData;
|
||||
}
|
||||
if (_playerInstance != System.IntPtr.Zero)
|
||||
{
|
||||
result = Native.OpenMedia(_playerInstance, path, httpHeader, (FileFormat)forceFileFormat, startWithHighestBitrate, _use10BitTextures);
|
||||
if (result)
|
||||
{
|
||||
if (_useLowLiveLatency)
|
||||
{
|
||||
Native.SetLiveOffset(_playerInstance, 0.0);
|
||||
}
|
||||
}
|
||||
_mediaHints = mediaHints;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public override void CloseMedia()
|
||||
{
|
||||
// NOTE: This unloads the current video, but the texture should remain
|
||||
_isMediaLoaded = false;
|
||||
Native.CloseMedia(_playerInstance);
|
||||
|
||||
base.CloseMedia();
|
||||
}
|
||||
|
||||
public override void Pause()
|
||||
{
|
||||
Native.Pause(_playerInstance);
|
||||
}
|
||||
|
||||
public override void Play()
|
||||
{
|
||||
Native.Play(_playerInstance);
|
||||
}
|
||||
|
||||
public override void Render()
|
||||
{
|
||||
Native.IssueRenderThreadEvent_UpdateAllTextures();
|
||||
}
|
||||
|
||||
private void Update_Textures()
|
||||
{
|
||||
// See if there is a new frame ready
|
||||
{
|
||||
System.IntPtr texturePointerLeft = System.IntPtr.Zero;
|
||||
System.IntPtr texturePointerRight = System.IntPtr.Zero;
|
||||
ulong frameTimeStamp = 0;
|
||||
int width, height;
|
||||
if (Native.GetLatestFrame(_playerInstance, out texturePointerLeft, out texturePointerRight, out frameTimeStamp, out width, out height))
|
||||
{
|
||||
bool isFrameUpdated = false;
|
||||
bool isNewFrameTime = (frameTimeStamp > _frameTimeStamp) || (_frameTimeStamp == 0 && frameTimeStamp == 0);
|
||||
for (int i = 0; i < _eyeTextures.Length; i++)
|
||||
{
|
||||
EyeTexture eyeTexture = _eyeTextures[i];
|
||||
System.IntPtr texturePointer = texturePointerLeft;
|
||||
if (i == 1)
|
||||
{
|
||||
texturePointer = texturePointerRight;
|
||||
}
|
||||
|
||||
bool isNewFrameSpecs = (eyeTexture.texture != null && (texturePointer == IntPtr.Zero || eyeTexture.texture.width != width || eyeTexture.texture.height != height));
|
||||
//Debug.Log("tex? " + i + " " + width + " " + height + " " + (eyeTexture.texture != null) + " " + texturePointer.ToString() + " " + frameTimeStamp);
|
||||
|
||||
// Check whether the latest frame is newer than the one we got last time
|
||||
if (isNewFrameTime || isNewFrameSpecs)
|
||||
{
|
||||
if (isNewFrameSpecs)
|
||||
{
|
||||
eyeTexture.Dispose();
|
||||
// TODO: blit from the old texture to the new texture before destroying?
|
||||
}
|
||||
|
||||
/// Switch to the latest texture pointer
|
||||
if (eyeTexture.texture != null)
|
||||
{
|
||||
// TODO: check whether UpdateExternalTexture resets the sampling filter to POINT - it seems to in Unity 5.6.6
|
||||
if (eyeTexture.nativePointer != texturePointer)
|
||||
{
|
||||
eyeTexture.texture.UpdateExternalTexture(texturePointer);
|
||||
eyeTexture.nativePointer = texturePointer;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (texturePointer != IntPtr.Zero)
|
||||
{
|
||||
eyeTexture.texture = Texture2D.CreateExternalTexture(width, height, TextureFormat.BGRA32, false, false, texturePointer);
|
||||
if (eyeTexture.texture != null)
|
||||
{
|
||||
eyeTexture.texture.name = "AVProVideo";
|
||||
eyeTexture.nativePointer = texturePointer;
|
||||
ApplyTextureProperties(eyeTexture.texture);
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogError("[AVProVideo] Failed to create texture");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
isFrameUpdated = true;
|
||||
}
|
||||
}
|
||||
if (isFrameUpdated)
|
||||
{
|
||||
_frameTimeStamp = frameTimeStamp;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private AuthData _nextAuthData = new AuthData();
|
||||
|
||||
public AuthData AuthenticationData
|
||||
{
|
||||
get
|
||||
{
|
||||
return _nextAuthData;
|
||||
}
|
||||
set
|
||||
{
|
||||
_nextAuthData = value;
|
||||
Native.SetNextAuthData(_playerInstance, _nextAuthData);
|
||||
}
|
||||
}
|
||||
|
||||
public override bool RequiresVerticalFlip()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public override void Seek(double time)
|
||||
{
|
||||
Native.SeekParams seekParams = new Native.SeekParams();
|
||||
seekParams.timeSeconds = time;
|
||||
seekParams.mode = Native.SeekMode.Accurate;
|
||||
Native.Seek(_playerInstance, ref seekParams);
|
||||
}
|
||||
|
||||
public override void SeekFast(double time)
|
||||
{
|
||||
// Keyframe seeking is not supported on this platform
|
||||
Seek(time);
|
||||
}
|
||||
|
||||
public override void SetLooping(bool bLooping)
|
||||
{
|
||||
Native.SetLooping(_playerInstance, bLooping);
|
||||
}
|
||||
|
||||
public override void SetPlaybackRate(float rate)
|
||||
{
|
||||
// Clamp rate as WinRT doesn't seem to be able to handle negative rate
|
||||
rate = Mathf.Max(0f, rate);
|
||||
Native.SetPlaybackRate(_playerInstance, rate);
|
||||
}
|
||||
|
||||
public override void SetVolume(float volume)
|
||||
{
|
||||
Native.SetAudioVolume(_playerInstance, volume);
|
||||
}
|
||||
|
||||
public override void Stop()
|
||||
{
|
||||
Pause();
|
||||
}
|
||||
|
||||
private void UpdateTimeRanges()
|
||||
{
|
||||
UpdateTimeRange(ref _seekableTimes._ranges, Native.TimeRangeTypes.Seekable);
|
||||
UpdateTimeRange(ref _bufferedTimes._ranges, Native.TimeRangeTypes.Buffered);
|
||||
_seekableTimes.CalculateRange();
|
||||
_bufferedTimes.CalculateRange();
|
||||
}
|
||||
|
||||
private void UpdateTimeRange(ref TimeRange[] range, Native.TimeRangeTypes timeRangeType)
|
||||
{
|
||||
int newCount = Native.GetTimeRanges(_playerInstance, range, range.Length, timeRangeType);
|
||||
if (newCount != range.Length)
|
||||
{
|
||||
range = new TimeRange[newCount];
|
||||
Native.GetTimeRanges(_playerInstance, range, range.Length, timeRangeType);
|
||||
}
|
||||
}
|
||||
|
||||
public override System.DateTime GetProgramDateTime()
|
||||
{
|
||||
double seconds = Native.GetCurrentDateTimeSecondsSince1970(_playerInstance);
|
||||
return Helper.ConvertSecondsSince1970ToDateTime(seconds);
|
||||
}
|
||||
|
||||
public override void Update()
|
||||
{
|
||||
Native.Update(_playerInstance);
|
||||
UpdateTracks();
|
||||
UpdateTextCue();
|
||||
|
||||
_lastError = (ErrorCode)Native.GetLastErrorCode(_playerInstance);
|
||||
|
||||
UpdateTimeRanges();
|
||||
UpdateSubtitles();
|
||||
Update_Textures();
|
||||
UpdateDisplayFrameRate();
|
||||
|
||||
if (!_isMediaLoaded)
|
||||
{
|
||||
if (HasVideo() && _eyeTextures[0].texture != null)
|
||||
{
|
||||
Native.VideoTrack videoTrack;
|
||||
if (Native.GetActiveVideoTrackInfo(_playerInstance, out videoTrack))
|
||||
{
|
||||
Helper.LogInfo("Using playback path: " + _playerDescription + " (" + videoTrack.frameWidth + "x" + videoTrack.frameHeight + "@" + videoTrack.frameRate.ToString("F2") + ")");
|
||||
_isMediaLoaded = true;
|
||||
}
|
||||
}
|
||||
else if (HasAudio() && !HasVideo())
|
||||
{
|
||||
Helper.LogInfo("Using playback path: " + _playerDescription);
|
||||
_isMediaLoaded = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*public override void SetKeyServerURL(string url)
|
||||
{
|
||||
_nextAuthData.URL = url;
|
||||
AuthenticationData = _nextAuthData;
|
||||
}*/
|
||||
|
||||
public override void SetKeyServerAuthToken(string token)
|
||||
{
|
||||
_nextAuthData.Token = token;
|
||||
AuthenticationData = _nextAuthData;
|
||||
}
|
||||
|
||||
public override void SetOverrideDecryptionKey(byte[] key)
|
||||
{
|
||||
_nextAuthData.KeyBytes = key;
|
||||
AuthenticationData = _nextAuthData;
|
||||
}
|
||||
}
|
||||
|
||||
// Tracks
|
||||
public sealed partial class WindowsRtMediaPlayer
|
||||
{
|
||||
internal override bool InternalSetActiveTrack(TrackType trackType, int trackUid)
|
||||
{
|
||||
return Native.SetActiveTrack(_playerInstance, trackType, trackUid);
|
||||
}
|
||||
|
||||
// Has it changed since the last frame 'tick'
|
||||
internal override bool InternalIsChangedTracks(TrackType trackType)
|
||||
{
|
||||
return Native.IsChangedTracks(_playerInstance, trackType);
|
||||
}
|
||||
|
||||
internal override int InternalGetTrackCount(TrackType trackType)
|
||||
{
|
||||
return Native.GetTrackCount(_playerInstance, trackType);
|
||||
}
|
||||
|
||||
internal override TrackBase InternalGetTrackInfo(TrackType trackType, int trackIndex, ref bool isActiveTrack)
|
||||
{
|
||||
TrackBase result = null;
|
||||
StringBuilder name = new StringBuilder(128);
|
||||
StringBuilder language = new StringBuilder(16);
|
||||
int uid = -1;
|
||||
if (Native.GetTrackInfo(_playerInstance, trackType, trackIndex, ref uid, ref isActiveTrack, name, name.Capacity, language, language.Capacity))
|
||||
{
|
||||
if (trackType == TrackType.Video)
|
||||
{
|
||||
result = new VideoTrack(uid, name.ToString(), language.ToString(), false);
|
||||
}
|
||||
else if (trackType == TrackType.Audio)
|
||||
{
|
||||
result = new AudioTrack(uid, name.ToString(), language.ToString(), false);
|
||||
}
|
||||
else if (trackType == TrackType.Text)
|
||||
{
|
||||
result = new TextTrack(uid, name.ToString(), language.ToString(), false);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private partial struct Native
|
||||
{
|
||||
[DllImport("AVProVideoWinRT")]
|
||||
[return: MarshalAs(UnmanagedType.I1)]
|
||||
public static extern bool IsChangedTracks(System.IntPtr instance, TrackType trackType);
|
||||
|
||||
[DllImport("AVProVideoWinRT")]
|
||||
public static extern int GetTrackCount(System.IntPtr instance, TrackType trackType);
|
||||
|
||||
[DllImport("AVProVideoWinRT")]
|
||||
[return: MarshalAs(UnmanagedType.I1)]
|
||||
public static extern bool GetTrackInfo(System.IntPtr instance, TrackType trackType, int index, ref int uid,
|
||||
ref bool isActive,
|
||||
[MarshalAs(UnmanagedType.LPWStr)] StringBuilder name, int maxNameLength,
|
||||
[MarshalAs(UnmanagedType.LPWStr)] StringBuilder language, int maxLanguageLength);
|
||||
|
||||
[DllImport("AVProVideoWinRT")]
|
||||
[return: MarshalAs(UnmanagedType.I1)]
|
||||
public static extern bool SetActiveTrack(System.IntPtr instance, TrackType trackType, int trackUid);
|
||||
}
|
||||
}
|
||||
|
||||
// Text Cue
|
||||
public sealed partial class WindowsRtMediaPlayer
|
||||
{
|
||||
// Has it changed since the last frame 'tick'
|
||||
internal override bool InternalIsChangedTextCue()
|
||||
{
|
||||
return Native.IsChangedTextCue(_playerInstance);
|
||||
}
|
||||
|
||||
internal override string InternalGetCurrentTextCue()
|
||||
{
|
||||
string result = null;
|
||||
System.IntPtr ptr = Native.GetCurrentTextCue(_playerInstance);
|
||||
if (ptr != System.IntPtr.Zero)
|
||||
{
|
||||
result = System.Runtime.InteropServices.Marshal.PtrToStringUni(ptr);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private partial struct Native
|
||||
{
|
||||
[DllImport("AVProVideoWinRT")]
|
||||
[return: MarshalAs(UnmanagedType.I1)]
|
||||
public static extern bool IsChangedTextCue(System.IntPtr instance);
|
||||
|
||||
[DllImport("AVProVideoWinRT")]
|
||||
public static extern System.IntPtr GetCurrentTextCue(System.IntPtr instance);
|
||||
}
|
||||
}
|
||||
|
||||
public sealed partial class WindowsRtMediaPlayer
|
||||
{
|
||||
private partial struct Native
|
||||
{
|
||||
[DllImport("AVProVideoWinRT", EntryPoint = "GetPluginVersion")]
|
||||
private static extern System.IntPtr GetPluginVersionStringPointer();
|
||||
|
||||
public static string GetPluginVersion()
|
||||
{
|
||||
return System.Runtime.InteropServices.Marshal.PtrToStringAnsi(GetPluginVersionStringPointer());
|
||||
}
|
||||
|
||||
[DllImport("AVProVideoWinRT")]
|
||||
public static extern System.IntPtr CreatePlayer();
|
||||
|
||||
[DllImport("AVProVideoWinRT")]
|
||||
public static extern void DestroyPlayer(System.IntPtr playerInstance);
|
||||
|
||||
[DllImport("AVProVideoWinRT")]
|
||||
#if AVPROVIDEO_MARSHAL_RETURN_BOOL
|
||||
[return: MarshalAs(UnmanagedType.I1)]
|
||||
#endif
|
||||
public static extern bool OpenMedia(System.IntPtr playerInstance, [MarshalAs(UnmanagedType.LPWStr)] string filePath,
|
||||
[MarshalAs(UnmanagedType.LPWStr)] string httpHeader, FileFormat overrideFileFormat,
|
||||
bool startWithHighestBitrate, bool use10BitTextures);
|
||||
|
||||
[DllImport("AVProVideoWinRT")]
|
||||
public static extern void CloseMedia(System.IntPtr playerInstance);
|
||||
|
||||
|
||||
[DllImport("AVProVideoWinRT")]
|
||||
public static extern void Pause(System.IntPtr playerInstance);
|
||||
|
||||
[DllImport("AVProVideoWinRT")]
|
||||
public static extern void Play(System.IntPtr playerInstance);
|
||||
|
||||
[DllImport("AVProVideoWinRT")]
|
||||
public static extern void SetAudioVolume(System.IntPtr playerInstance, float volume);
|
||||
|
||||
[DllImport("AVProVideoWinRT")]
|
||||
public static extern void SetAudioBalance(System.IntPtr playerInstance, float balance);
|
||||
|
||||
[DllImport("AVProVideoWinRT")]
|
||||
public static extern void SetPlaybackRate(System.IntPtr playerInstance, float rate);
|
||||
|
||||
[DllImport("AVProVideoWinRT")]
|
||||
public static extern void SetAudioMuted(System.IntPtr playerInstance, bool muted);
|
||||
|
||||
[DllImport("AVProVideoWinRT")]
|
||||
public static extern float GetAudioVolume(System.IntPtr playerInstance);
|
||||
|
||||
[DllImport("AVProVideoWinRT")]
|
||||
#if AVPROVIDEO_MARSHAL_RETURN_BOOL
|
||||
[return: MarshalAs(UnmanagedType.I1)]
|
||||
#endif
|
||||
public static extern bool IsAudioMuted(System.IntPtr playerInstance);
|
||||
|
||||
[DllImport("AVProVideoWinRT")]
|
||||
public static extern float GetAudioBalance(System.IntPtr playerInstance);
|
||||
|
||||
[DllImport("AVProVideoWinRT")]
|
||||
public static extern float GetPlaybackRate(System.IntPtr playerInstance);
|
||||
|
||||
[DllImport("AVProVideoWinRT")]
|
||||
public static extern void SetLooping(System.IntPtr playerInstance, bool looping);
|
||||
|
||||
[DllImport("AVProVideoWinRT")]
|
||||
#if AVPROVIDEO_MARSHAL_RETURN_BOOL
|
||||
[return: MarshalAs(UnmanagedType.I1)]
|
||||
#endif
|
||||
public static extern bool IsLooping(System.IntPtr playerInstance);
|
||||
|
||||
[DllImport("AVProVideoWinRT")]
|
||||
public static extern int GetLastErrorCode(System.IntPtr playerInstance);
|
||||
|
||||
|
||||
[DllImport("AVProVideoWinRT")]
|
||||
public static extern void Update(System.IntPtr playerInstance);
|
||||
|
||||
[DllImport("AVProVideoWinRT")]
|
||||
public static extern double GetDuration(System.IntPtr playerInstance);
|
||||
|
||||
[DllImport("AVProVideoWinRT")]
|
||||
public static extern StereoPacking GetStereoPacking(System.IntPtr playerInstance);
|
||||
|
||||
[DllImport("AVProVideoWinRT")]
|
||||
public static extern double GetCurrentPosition(System.IntPtr playerInstance);
|
||||
|
||||
[DllImport("AVProVideoWinRT")]
|
||||
#if AVPROVIDEO_MARSHAL_RETURN_BOOL
|
||||
[return: MarshalAs(UnmanagedType.I1)]
|
||||
#endif
|
||||
public static extern bool GetLatestFrame(System.IntPtr playerInstance, out System.IntPtr leftEyeTexturePointer, out System.IntPtr rightEyeTexturePointer, out ulong frameTimeStamp, out int width, out int height);
|
||||
|
||||
[DllImport("AVProVideoWinRT")]
|
||||
public static extern PlaybackState GetPlaybackState(System.IntPtr playerInstance);
|
||||
|
||||
[DllImport("AVProVideoWinRT")]
|
||||
#if AVPROVIDEO_MARSHAL_RETURN_BOOL
|
||||
[return: MarshalAs(UnmanagedType.I1)]
|
||||
#endif
|
||||
public static extern bool GetActiveVideoTrackInfo(System.IntPtr playerInstance, out VideoTrack videoTrack);
|
||||
|
||||
[DllImport("AVProVideoWinRT")]
|
||||
#if AVPROVIDEO_MARSHAL_RETURN_BOOL
|
||||
[return: MarshalAs(UnmanagedType.I1)]
|
||||
#endif
|
||||
public static extern bool GetActiveAudioTrackInfo(System.IntPtr playerInstance, out AudioTrack audioTrack);
|
||||
|
||||
[DllImport("AVProVideoWinRT")]
|
||||
public static extern double GetCurrentDateTimeSecondsSince1970(System.IntPtr playerInstance);
|
||||
|
||||
[DllImport("AVProVideoWinRT")]
|
||||
public static extern void SetLiveOffset(System.IntPtr playerInstance, double seconds);
|
||||
|
||||
[DllImport("AVProVideoWinRT")]
|
||||
public static extern void DebugValues(System.IntPtr playerInstance, out int isD3D, out int isUnityD3D, out int isTexture, out int isSharedTexture, out int isSurface);
|
||||
|
||||
public enum SeekMode
|
||||
{
|
||||
Fast = 0,
|
||||
Accurate = 1,
|
||||
// TODO: Add Fast_Before and Fast_After
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||
public struct VideoTrack
|
||||
{
|
||||
public int trackIndex;
|
||||
public int frameWidth;
|
||||
public int frameHeight;
|
||||
public float frameRate;
|
||||
public uint averageBitRate;
|
||||
//public string trackName;
|
||||
// TODO: add index, language, name, bitrate, codec etc
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||
public struct AudioTrack
|
||||
{
|
||||
public int trackIndex;
|
||||
public uint channelCount;
|
||||
public uint sampleRate;
|
||||
public uint bitsPerSample;
|
||||
public uint averageBitRate;
|
||||
//public string trackName;
|
||||
// TODO: add index, language, name, bitrate, codec etc
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||
public struct SeekParams
|
||||
{
|
||||
public double timeSeconds;
|
||||
public SeekMode mode;
|
||||
// TODO: add min-max thresholds
|
||||
}
|
||||
|
||||
[DllImport("AVProVideoWinRT")]
|
||||
public static extern void Seek(System.IntPtr playerInstance, ref SeekParams seekParams);
|
||||
|
||||
public static void SetNextAuthData(System.IntPtr playerInstance, RenderHeads.Media.AVProVideo.AuthData srcAuthData)
|
||||
{
|
||||
Native.AuthData ad = new Native.AuthData();
|
||||
ad.url = string.IsNullOrEmpty(srcAuthData.URL) ? null : srcAuthData.URL;
|
||||
ad.token = string.IsNullOrEmpty(srcAuthData.Token) ? null : srcAuthData.Token;
|
||||
if (srcAuthData.KeyBytes != null && srcAuthData.KeyBytes.Length > 0)
|
||||
{
|
||||
ad.keyBytes = Marshal.AllocHGlobal(srcAuthData.KeyBytes.Length);
|
||||
Marshal.Copy(srcAuthData.KeyBytes, 0, ad.keyBytes, srcAuthData.KeyBytes.Length);
|
||||
ad.keyBytesLength = srcAuthData.KeyBytes.Length;
|
||||
}
|
||||
else
|
||||
{
|
||||
ad.keyBytes = System.IntPtr.Zero;
|
||||
ad.keyBytesLength = 0;
|
||||
}
|
||||
|
||||
SetNextAuthData(playerInstance, ref ad);
|
||||
|
||||
if (ad.keyBytes != System.IntPtr.Zero)
|
||||
{
|
||||
Marshal.FreeHGlobal(ad.keyBytes);
|
||||
}
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||
public struct AuthData
|
||||
{
|
||||
[MarshalAs(UnmanagedType.LPWStr)]
|
||||
public string url;
|
||||
|
||||
[MarshalAs(UnmanagedType.LPWStr)]
|
||||
public string token;
|
||||
|
||||
public System.IntPtr keyBytes;
|
||||
public int keyBytesLength;
|
||||
};
|
||||
|
||||
[DllImport("AVProVideoWinRT")]
|
||||
private static extern void SetNextAuthData(System.IntPtr playerInstance, ref AuthData authData);
|
||||
|
||||
internal enum TimeRangeTypes
|
||||
{
|
||||
Seekable = 0,
|
||||
Buffered = 1,
|
||||
}
|
||||
|
||||
[DllImport("AVProVideoWinRT")]
|
||||
public static extern int GetTimeRanges(System.IntPtr playerInstance, [Out, MarshalAs(UnmanagedType.LPArray, SizeParamIndex=2)] TimeRange[] ranges, int rangeCount, TimeRangeTypes timeRangeType);
|
||||
|
||||
// RJT TODO: Clean this up to better match non-WinRT
|
||||
|
||||
[DllImport("AVProVideoWinRT")]
|
||||
public static extern System.IntPtr GetRenderEventFunc();
|
||||
|
||||
private static System.IntPtr _nativeFunction_UnityRenderEvent;
|
||||
public static void IssueRenderThreadEvent_UpdateAllTextures()
|
||||
{
|
||||
if (_nativeFunction_UnityRenderEvent == System.IntPtr.Zero)
|
||||
{
|
||||
_nativeFunction_UnityRenderEvent = Native.GetRenderEventFunc();
|
||||
}
|
||||
if (_nativeFunction_UnityRenderEvent != System.IntPtr.Zero)
|
||||
{
|
||||
GL.IssuePluginEvent(_nativeFunction_UnityRenderEvent, /*(int)Native.RenderThreadEvent.UpdateAllTextures*/1);
|
||||
}
|
||||
}
|
||||
|
||||
public static void IssueRenderThreadEvent_FreeAllTextures()
|
||||
{
|
||||
if (_nativeFunction_UnityRenderEvent == System.IntPtr.Zero)
|
||||
{
|
||||
_nativeFunction_UnityRenderEvent = Native.GetRenderEventFunc();
|
||||
}
|
||||
if (_nativeFunction_UnityRenderEvent != System.IntPtr.Zero)
|
||||
{
|
||||
GL.IssuePluginEvent(_nativeFunction_UnityRenderEvent, /*(int)Native.RenderThreadEvent.FreeTextures*/2);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public sealed partial class WindowsRtMediaPlayer
|
||||
{
|
||||
private static bool _isInitialised = false;
|
||||
private static string _version = "Plug-in not yet initialised";
|
||||
|
||||
private ulong _frameTimeStamp;
|
||||
private System.IntPtr _playerInstance;
|
||||
|
||||
class EyeTexture
|
||||
{
|
||||
public Texture2D texture = null;
|
||||
public System.IntPtr nativePointer = System.IntPtr.Zero;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (texture)
|
||||
{
|
||||
if (Application.isPlaying) { Texture2D.Destroy(texture); }
|
||||
else { Texture2D.DestroyImmediate(texture); }
|
||||
texture = null;
|
||||
}
|
||||
nativePointer = System.IntPtr.Zero;
|
||||
}
|
||||
}
|
||||
|
||||
private EyeTexture[] _eyeTextures = new EyeTexture[2];
|
||||
|
||||
public static bool InitialisePlatform()
|
||||
{
|
||||
if (!_isInitialised)
|
||||
{
|
||||
try
|
||||
{
|
||||
#if !UNITY_2019_3_OR_NEWER
|
||||
if (SystemInfo.graphicsDeviceType == UnityEngine.Rendering.GraphicsDeviceType.Direct3D12)
|
||||
{
|
||||
Debug.LogError("[AVProVideo] Direct3D 12 is not supported until Unity 2019.3");
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
if (SystemInfo.graphicsDeviceType == UnityEngine.Rendering.GraphicsDeviceType.Null ||
|
||||
SystemInfo.graphicsDeviceType == UnityEngine.Rendering.GraphicsDeviceType.Direct3D11 ||
|
||||
SystemInfo.graphicsDeviceType == UnityEngine.Rendering.GraphicsDeviceType.Direct3D12)
|
||||
{
|
||||
/*if (!Native.Init(QualitySettings.activeColorSpace == ColorSpace.Linear))
|
||||
{
|
||||
Debug.LogError("[AVProVideo] Failing to initialise platform");
|
||||
}
|
||||
else*/
|
||||
{
|
||||
_isInitialised = true;
|
||||
_version = Native.GetPluginVersion();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogError("[AVProVideo] Only Direct3D 11 and 12 are supported, graphicsDeviceType not supported: " + SystemInfo.graphicsDeviceType);
|
||||
}
|
||||
}
|
||||
catch (System.DllNotFoundException e)
|
||||
{
|
||||
Debug.LogError("[AVProVideo] Failed to load DLL. " + e.Message);
|
||||
}
|
||||
}
|
||||
|
||||
return _isInitialised;
|
||||
}
|
||||
|
||||
public static void DeinitPlatform()
|
||||
{
|
||||
//Native.Deinit();
|
||||
_isInitialised = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7b04c4ad4a3b8c44a98a08ea2ae71a6d
|
||||
timeCreated: 1541807235
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,48 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Copyright 2015-2021 RenderHeads Ltd. All rights reserved.
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
namespace RenderHeads.Media.AVProVideo
|
||||
{
|
||||
public class TextCue
|
||||
{
|
||||
private TextCue() { }
|
||||
|
||||
internal TextCue(string text)
|
||||
{
|
||||
Text = text;
|
||||
}
|
||||
|
||||
public string Text { get; private set; }
|
||||
}
|
||||
|
||||
public partial class BaseMediaPlayer : ITextTracks
|
||||
{
|
||||
protected TextCue _currentTextCue = null;
|
||||
public TextCue GetCurrentTextCue() { return _currentTextCue; } // Returns null when there is no active text
|
||||
|
||||
protected bool UpdateTextCue(bool force = false)
|
||||
{
|
||||
bool result = false;
|
||||
// Has it changed since the last 'tick'
|
||||
if (force || InternalIsChangedTextCue())
|
||||
{
|
||||
_currentTextCue = null;
|
||||
string text = InternalGetCurrentTextCue();
|
||||
if (!string.IsNullOrEmpty(text))
|
||||
{
|
||||
_currentTextCue = new TextCue(text);
|
||||
}
|
||||
result = true;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
internal abstract bool InternalIsChangedTextCue();
|
||||
internal abstract string InternalGetCurrentTextCue();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 70b7a3055e537f74cb49d2fc4e6989e6
|
||||
timeCreated: 1438695622
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,292 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Copyright 2015-2021 RenderHeads Ltd. All rights reserved.
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
namespace RenderHeads.Media.AVProVideo
|
||||
{
|
||||
public enum TrackType
|
||||
{
|
||||
Video,
|
||||
Audio,
|
||||
Text,
|
||||
}
|
||||
|
||||
public class TrackBase
|
||||
{
|
||||
protected TrackBase() { }
|
||||
|
||||
internal TrackBase(TrackType trackType, int uid, string name, string language, bool isDefault)
|
||||
{
|
||||
TrackType = trackType;
|
||||
Uid = uid;
|
||||
Name = name;
|
||||
Language = language;
|
||||
IsDefault = isDefault;
|
||||
DisplayName = CreateDisplayName();
|
||||
}
|
||||
|
||||
// The UID is unique to the media
|
||||
internal int Uid { get; private set; }
|
||||
|
||||
public TrackType TrackType { get; private set; }
|
||||
|
||||
public string DisplayName { get; private set; }
|
||||
|
||||
// Optional
|
||||
public string Name { get; private set; }
|
||||
|
||||
// Optional
|
||||
public string Language { get; private set; }
|
||||
|
||||
// Optional
|
||||
public bool IsDefault { get; private set; }
|
||||
|
||||
protected string CreateDisplayName()
|
||||
{
|
||||
string result;
|
||||
if (!string.IsNullOrEmpty(Name))
|
||||
{
|
||||
result = Name;
|
||||
}
|
||||
else
|
||||
{
|
||||
result = "Track " + Uid.ToString();
|
||||
}
|
||||
if (!string.IsNullOrEmpty(Language))
|
||||
{
|
||||
result = string.Format("{0} ({1})", result, Language);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
public abstract class TrackCollection : IEnumerable
|
||||
{
|
||||
public virtual TrackType TrackType { get; private set; }
|
||||
public abstract int Count { get; }
|
||||
public abstract IEnumerator GetEnumerator();
|
||||
|
||||
internal abstract void Clear();
|
||||
internal abstract void Add(TrackBase track);
|
||||
internal abstract bool HasActiveTrack();
|
||||
internal abstract bool IsActiveTrack(TrackBase track);
|
||||
internal abstract void SetActiveTrack(TrackBase track);
|
||||
internal abstract void SetFirstTrackActive();
|
||||
}
|
||||
|
||||
public class TrackCollection<T> : TrackCollection where T : TrackBase
|
||||
{
|
||||
internal TrackCollection() {}
|
||||
|
||||
public override IEnumerator GetEnumerator()
|
||||
{
|
||||
return _tracks.GetEnumerator();
|
||||
}
|
||||
|
||||
public T this[int index]
|
||||
{
|
||||
get
|
||||
{
|
||||
return _tracks[index];
|
||||
}
|
||||
}
|
||||
|
||||
internal T ActiveTrack { get; set; }
|
||||
|
||||
internal override bool HasActiveTrack() { return ActiveTrack != null; }
|
||||
|
||||
internal override bool IsActiveTrack(TrackBase track)
|
||||
{
|
||||
return (ActiveTrack == track);
|
||||
}
|
||||
|
||||
internal override void Clear()
|
||||
{
|
||||
_tracks.Clear();
|
||||
ActiveTrack = null;
|
||||
}
|
||||
|
||||
internal override void Add(TrackBase track)
|
||||
{
|
||||
_tracks.Add(track as T);
|
||||
}
|
||||
|
||||
internal override void SetActiveTrack(TrackBase track)
|
||||
{
|
||||
ActiveTrack = track as T;
|
||||
}
|
||||
|
||||
internal override void SetFirstTrackActive()
|
||||
{
|
||||
if (_tracks.Count > 0)
|
||||
{
|
||||
ActiveTrack = _tracks[0];
|
||||
}
|
||||
}
|
||||
|
||||
public override int Count { get{ return _tracks.Count; } }
|
||||
|
||||
internal List<T> _tracks = new List<T>(4);
|
||||
}
|
||||
|
||||
public class VideoTracks : TrackCollection<VideoTrack>
|
||||
{
|
||||
public override TrackType TrackType { get { return TrackType.Video; } }
|
||||
}
|
||||
|
||||
public class AudioTracks : TrackCollection<AudioTrack>
|
||||
{
|
||||
public override TrackType TrackType { get { return TrackType.Audio; } }
|
||||
}
|
||||
|
||||
public class TextTracks : TrackCollection<TextTrack>
|
||||
{
|
||||
public override TrackType TrackType { get { return TrackType.Text; } }
|
||||
}
|
||||
|
||||
public class VideoTrack : TrackBase
|
||||
{
|
||||
private VideoTrack() { }
|
||||
|
||||
internal VideoTrack(int uid, string name, string language, bool isDefault)
|
||||
: base(TrackType.Video, uid, name, language, isDefault) { }
|
||||
|
||||
// Optional
|
||||
public int Bitrate { get; set; }
|
||||
}
|
||||
|
||||
public class AudioTrack : TrackBase
|
||||
{
|
||||
private AudioTrack() { }
|
||||
|
||||
internal AudioTrack(int uid, string name, string language, bool isDefault)
|
||||
: base(TrackType.Audio, uid, name, language, isDefault) { }
|
||||
|
||||
// Optional
|
||||
public int Bitrate { get; private set; }
|
||||
|
||||
// Optional
|
||||
public int ChannelCount { get; private set; }
|
||||
}
|
||||
|
||||
public class TextTrack : TrackBase
|
||||
{
|
||||
private TextTrack() { }
|
||||
|
||||
internal TextTrack(int uid, string name, string language, bool isDefault)
|
||||
: base(TrackType.Text, uid, name, language, isDefault) { }
|
||||
}
|
||||
|
||||
public interface IVideoTracks
|
||||
{
|
||||
VideoTracks GetVideoTracks();
|
||||
VideoTrack GetActiveVideoTrack();
|
||||
void SetActiveVideoTrack(VideoTrack track);
|
||||
}
|
||||
|
||||
public interface IAudioTracks
|
||||
{
|
||||
AudioTracks GetAudioTracks();
|
||||
AudioTrack GetActiveAudioTrack();
|
||||
void SetActiveAudioTrack(AudioTrack track);
|
||||
}
|
||||
|
||||
public interface ITextTracks
|
||||
{
|
||||
TextTracks GetTextTracks();
|
||||
TextTrack GetActiveTextTrack();
|
||||
void SetActiveTextTrack(TextTrack track);
|
||||
TextCue GetCurrentTextCue();
|
||||
}
|
||||
|
||||
public partial class BaseMediaPlayer : IVideoTracks, IAudioTracks, ITextTracks
|
||||
{
|
||||
protected VideoTracks _videoTracks = new VideoTracks();
|
||||
protected AudioTracks _audioTracks = new AudioTracks();
|
||||
protected TextTracks _textTracks = new TextTracks();
|
||||
protected TrackCollection[] _trackCollections;
|
||||
|
||||
public VideoTracks GetVideoTracks() { return _videoTracks; }
|
||||
public AudioTracks GetAudioTracks() { return _audioTracks; }
|
||||
public TextTracks GetTextTracks() { return _textTracks; }
|
||||
public VideoTrack GetActiveVideoTrack() { return _videoTracks.ActiveTrack; }
|
||||
public AudioTrack GetActiveAudioTrack() { return _audioTracks.ActiveTrack; }
|
||||
public TextTrack GetActiveTextTrack() { return _textTracks.ActiveTrack; }
|
||||
public void SetActiveVideoTrack(VideoTrack track) { if (track != null) SetActiveTrack(_videoTracks, track); }
|
||||
public void SetActiveAudioTrack(AudioTrack track) { if (track != null) SetActiveTrack(_audioTracks, track); }
|
||||
public void SetActiveTextTrack(TextTrack track) { SetActiveTrack(_textTracks, track); }
|
||||
|
||||
internal abstract bool InternalIsChangedTracks(TrackType trackType);
|
||||
internal abstract int InternalGetTrackCount(TrackType trackType);
|
||||
internal abstract bool InternalSetActiveTrack(TrackType trackType, int trackUid);
|
||||
internal abstract TrackBase InternalGetTrackInfo(TrackType trackType, int trackIndex, ref bool isActiveTrack);
|
||||
|
||||
private void InitTracks()
|
||||
{
|
||||
_trackCollections = new TrackCollection[3] { _videoTracks, _audioTracks, _textTracks };
|
||||
}
|
||||
|
||||
protected void UpdateTracks()
|
||||
{
|
||||
foreach (TrackCollection trackCollection in _trackCollections)
|
||||
{
|
||||
if (InternalIsChangedTracks(trackCollection.TrackType))
|
||||
{
|
||||
PopulateTrackCollection(trackCollection);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void PopulateTrackCollection(TrackCollection collection)
|
||||
{
|
||||
collection.Clear();
|
||||
int trackCount = InternalGetTrackCount(collection.TrackType);
|
||||
for (int i = 0; i < trackCount; i++)
|
||||
{
|
||||
bool isActiveTrack = false;
|
||||
TrackBase track = InternalGetTrackInfo(collection.TrackType, i, ref isActiveTrack);
|
||||
if (track != null)
|
||||
{
|
||||
collection.Add(track);
|
||||
if (isActiveTrack)
|
||||
{
|
||||
collection.SetActiveTrack(track);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
UnityEngine.Debug.LogWarning(string.Format("[AVProVideo] Failed to enumerate {0} track {1} ", collection.TrackType, i));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void SetActiveTrack(TrackCollection collection, TrackBase track)
|
||||
{
|
||||
// Check if this is already the active track
|
||||
if (collection.IsActiveTrack(track)) return;
|
||||
|
||||
// Convert from TextTrack to uid
|
||||
int trackUid = -1;
|
||||
if (track != null)
|
||||
{
|
||||
trackUid = track.Uid;
|
||||
}
|
||||
|
||||
// Set track based on uid (-1 is no active track)
|
||||
// NOTE: TrackType is pulled from collection as track may be null
|
||||
if (InternalSetActiveTrack(collection.TrackType, trackUid))
|
||||
{
|
||||
collection.SetActiveTrack(track);
|
||||
switch (collection.TrackType)
|
||||
{
|
||||
case TrackType.Text:
|
||||
UpdateTextCue(force:true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 087e0a6fc1bd92e4bbd96796ec593162
|
||||
timeCreated: 1596803411
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,9 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c6f0eb1069a1ccc4b94ebe97c97b9cd1
|
||||
folderAsset: yes
|
||||
timeCreated: 1551721729
|
||||
licenseType: Pro
|
||||
DefaultImporter:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,127 @@
|
||||
using UnityEngine;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Copyright 2020-2021 RenderHeads Ltd. All rights reserved.
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
namespace RenderHeads.Media.AVProVideo
|
||||
{
|
||||
[System.Serializable]
|
||||
public struct HttpHeader
|
||||
{
|
||||
public string name;
|
||||
public string value;
|
||||
|
||||
public HttpHeader(string name, string value) { this.name = name; this.value = value; }
|
||||
|
||||
public bool IsComplete()
|
||||
{
|
||||
return (!string.IsNullOrEmpty(name) && !string.IsNullOrEmpty(value));
|
||||
}
|
||||
|
||||
public string ToValidatedString()
|
||||
{
|
||||
string result = null;
|
||||
if (IsComplete())
|
||||
{
|
||||
if (IsValid())
|
||||
{
|
||||
result = string.Format("{0}:{1}\r\n", name, value);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public static bool IsValid(string text)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(text))
|
||||
{
|
||||
if (!IsAscii(text)) return false;
|
||||
if (text.Contains("\r") || text.Contains("\n")) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool IsAscii(string text)
|
||||
{
|
||||
foreach (char c in text)
|
||||
{
|
||||
if (c >= 128) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool IsValid()
|
||||
{
|
||||
if (!IsValid(name) || !IsValid(value))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
// TODO: check via regular expression
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Data for handling custom HTTP header fields
|
||||
/// </summary>
|
||||
[System.Serializable]
|
||||
public class HttpHeaderData : IEnumerable
|
||||
{
|
||||
[SerializeField]
|
||||
private List<HttpHeader> httpHeaders = new List<HttpHeader>();
|
||||
|
||||
public IEnumerator GetEnumerator()
|
||||
{
|
||||
return httpHeaders.GetEnumerator();
|
||||
}
|
||||
|
||||
public HttpHeader this[int index]
|
||||
{
|
||||
get
|
||||
{
|
||||
return httpHeaders[index];
|
||||
}
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
httpHeaders.Clear();
|
||||
}
|
||||
|
||||
public void Add(string name, string value)
|
||||
{
|
||||
httpHeaders.Add(new HttpHeader(name, value));
|
||||
}
|
||||
|
||||
public bool IsModified()
|
||||
{
|
||||
return (httpHeaders != null && httpHeaders.Count > 0);
|
||||
}
|
||||
|
||||
public string ToValidatedString()
|
||||
{
|
||||
string result = string.Empty;
|
||||
foreach (HttpHeader header in httpHeaders)
|
||||
{
|
||||
if (header.IsComplete())
|
||||
{
|
||||
string line = header.ToValidatedString();
|
||||
if (!string.IsNullOrEmpty(line))
|
||||
{
|
||||
result += line;
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogWarning("[AVProVideo] Custom HTTP header field ignored due to invalid format");
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2cfc6f8c038acdf4a9b384e8cb5e9cb2
|
||||
timeCreated: 1588604301
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,55 @@
|
||||
using UnityEngine;
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Copyright 2020-2021 RenderHeads Ltd. All rights reserved.
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
namespace RenderHeads.Media.AVProVideo
|
||||
{
|
||||
/// <summary>
|
||||
/// Data for handling authentication of encrypted AES-128 HLS streams
|
||||
/// </summary>
|
||||
[System.Serializable]
|
||||
public class KeyAuthData : ISerializationCallbackReceiver
|
||||
{
|
||||
public string keyServerToken = null;
|
||||
|
||||
//public string keyServerURLOverride = null;
|
||||
|
||||
[SerializeField, Multiline]
|
||||
private string overrideDecryptionKeyBase64 = null;
|
||||
public byte[] overrideDecryptionKey = null;
|
||||
|
||||
public bool IsModified()
|
||||
{
|
||||
return (overrideDecryptionKey != null && overrideDecryptionKey.Length > 0)
|
||||
|| (string.IsNullOrEmpty(overrideDecryptionKeyBase64) == false);
|
||||
}
|
||||
|
||||
public void OnBeforeSerialize()
|
||||
{
|
||||
if (overrideDecryptionKey != null && !string.IsNullOrEmpty(overrideDecryptionKeyBase64))
|
||||
{
|
||||
overrideDecryptionKey = null;
|
||||
}
|
||||
}
|
||||
|
||||
public void OnAfterDeserialize()
|
||||
{
|
||||
if (string.IsNullOrEmpty(overrideDecryptionKeyBase64))
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
// Convert overrideDecryptionKeyBase64 to overrideDecryptionKey
|
||||
overrideDecryptionKey = System.Convert.FromBase64String(overrideDecryptionKeyBase64);
|
||||
}
|
||||
catch (System.FormatException e)
|
||||
{
|
||||
Debug.LogError("Failed to deserialize decryption key, error: " + e);
|
||||
overrideDecryptionKeyBase64 = null;
|
||||
overrideDecryptionKey = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0e784fab214313d44aaa5906743860fa
|
||||
timeCreated: 1588604301
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,594 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Copyright 2015-2021 RenderHeads Ltd. All rights reserved.
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
namespace RenderHeads.Media.AVProVideo
|
||||
{
|
||||
/// <summary>
|
||||
/// Utility class to resample MediaPlayer video frames to allow for smoother playback
|
||||
/// Keeps a buffer of frames with timestamps and presents them using its own clock
|
||||
/// </summary>
|
||||
public class Resampler
|
||||
{
|
||||
private class TimestampedRenderTexture
|
||||
{
|
||||
public RenderTexture texture = null;
|
||||
public long timestamp = 0;
|
||||
public bool used = false;
|
||||
}
|
||||
|
||||
public enum ResampleMode
|
||||
{
|
||||
POINT, LINEAR
|
||||
}
|
||||
|
||||
private List<TimestampedRenderTexture[]> _buffer = new List<TimestampedRenderTexture[]>();
|
||||
private MediaPlayer _mediaPlayer;
|
||||
private RenderTexture[] _outputTexture = null;
|
||||
|
||||
private int _start = 0;
|
||||
private int _end = 0;
|
||||
private int _bufferSize = 0;
|
||||
|
||||
private long _baseTimestamp = 0;
|
||||
private float _elapsedTimeSinceBase = 0f;
|
||||
|
||||
private Material _blendMat;
|
||||
|
||||
private ResampleMode _resampleMode;
|
||||
private string _name = "";
|
||||
|
||||
private long _lastTimeStamp = -1;
|
||||
|
||||
private int _droppedFrames = 0;
|
||||
|
||||
private long _lastDisplayedTimestamp = 0;
|
||||
private int _frameDisplayedTimer = 0;
|
||||
private long _currentDisplayedTimestamp = 0;
|
||||
|
||||
public int DroppedFrames
|
||||
{
|
||||
get { return _droppedFrames; }
|
||||
}
|
||||
|
||||
public int FrameDisplayedTimer
|
||||
{
|
||||
get { return _frameDisplayedTimer; }
|
||||
}
|
||||
|
||||
public long BaseTimestamp
|
||||
{
|
||||
get { return _baseTimestamp; }
|
||||
set { _baseTimestamp = value; }
|
||||
}
|
||||
|
||||
public float ElapsedTimeSinceBase
|
||||
{
|
||||
get { return _elapsedTimeSinceBase; }
|
||||
set { _elapsedTimeSinceBase = value; }
|
||||
}
|
||||
|
||||
public float LastT
|
||||
{
|
||||
get; private set;
|
||||
}
|
||||
|
||||
public long TextureTimeStamp
|
||||
{
|
||||
get; private set;
|
||||
}
|
||||
|
||||
private const string ShaderPropT = "_t";
|
||||
private const string ShaderPropAftertex = "_AfterTex";
|
||||
private int _propAfterTex;
|
||||
private int _propT;
|
||||
private float _videoFrameRate;
|
||||
|
||||
public void OnVideoEvent(MediaPlayer mp, MediaPlayerEvent.EventType et, ErrorCode errorCode)
|
||||
{
|
||||
switch (et)
|
||||
{
|
||||
case MediaPlayerEvent.EventType.MetaDataReady:
|
||||
_videoFrameRate = mp.Info.GetVideoFrameRate();
|
||||
_elapsedTimeSinceBase = 0f;
|
||||
if (_videoFrameRate > 0f)
|
||||
{
|
||||
_elapsedTimeSinceBase = _bufferSize / _videoFrameRate;
|
||||
}
|
||||
break;
|
||||
case MediaPlayerEvent.EventType.Closing:
|
||||
Reset();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public Resampler(MediaPlayer player, string name, int bufferSize = 2, ResampleMode resampleMode = ResampleMode.LINEAR)
|
||||
{
|
||||
_bufferSize = Mathf.Max(2, bufferSize);
|
||||
|
||||
player.Events.AddListener(OnVideoEvent);
|
||||
|
||||
_mediaPlayer = player;
|
||||
|
||||
Shader blendShader = Shader.Find("AVProVideo/Internal/BlendFrames");
|
||||
if (blendShader != null)
|
||||
{
|
||||
_blendMat = new Material(blendShader);
|
||||
_propT = Shader.PropertyToID(ShaderPropT);
|
||||
_propAfterTex = Shader.PropertyToID(ShaderPropAftertex);
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogError("[AVProVideo] Failed to find BlendFrames shader");
|
||||
}
|
||||
|
||||
_resampleMode = resampleMode;
|
||||
_name = name;
|
||||
|
||||
Debug.Log("[AVProVideo] Resampler " + _name + " started");
|
||||
}
|
||||
|
||||
public Texture[] OutputTexture
|
||||
{
|
||||
get { return _outputTexture; }
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
_lastTimeStamp = -1;
|
||||
_baseTimestamp = 0;
|
||||
InvalidateBuffer();
|
||||
}
|
||||
|
||||
public void Release()
|
||||
{
|
||||
ReleaseRenderTextures();
|
||||
if (_blendMat != null)
|
||||
{
|
||||
if (Application.isPlaying)
|
||||
{
|
||||
Material.Destroy(_blendMat);
|
||||
}
|
||||
else
|
||||
{
|
||||
Material.DestroyImmediate(_blendMat);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ReleaseRenderTextures()
|
||||
{
|
||||
for (int i = 0; i < _buffer.Count; ++i)
|
||||
{
|
||||
for (int j = 0; j < _buffer[i].Length; ++j)
|
||||
{
|
||||
if (_buffer[i][j].texture != null)
|
||||
{
|
||||
RenderTexture.ReleaseTemporary(_buffer[i][j].texture);
|
||||
_buffer[i][j].texture = null;
|
||||
}
|
||||
}
|
||||
|
||||
if (_outputTexture != null && _outputTexture[i] != null)
|
||||
{
|
||||
RenderTexture.ReleaseTemporary(_outputTexture[i]);
|
||||
}
|
||||
}
|
||||
|
||||
_outputTexture = null;
|
||||
}
|
||||
|
||||
private void ConstructRenderTextures()
|
||||
{
|
||||
ReleaseRenderTextures();
|
||||
_buffer.Clear();
|
||||
|
||||
_outputTexture = new RenderTexture[_mediaPlayer.TextureProducer.GetTextureCount()];
|
||||
|
||||
for (int i = 0; i < _mediaPlayer.TextureProducer.GetTextureCount(); ++i)
|
||||
{
|
||||
Texture tex = _mediaPlayer.TextureProducer.GetTexture(i);
|
||||
_buffer.Add(new TimestampedRenderTexture[_bufferSize]);
|
||||
for (int j = 0; j < _bufferSize; ++j)
|
||||
{
|
||||
_buffer[i][j] = new TimestampedRenderTexture();
|
||||
}
|
||||
|
||||
for (int j = 0; j < _buffer[i].Length; ++j)
|
||||
{
|
||||
_buffer[i][j].texture = RenderTexture.GetTemporary(tex.width, tex.height, 0);
|
||||
_buffer[i][j].timestamp = 0;
|
||||
_buffer[i][j].used = false;
|
||||
}
|
||||
|
||||
_outputTexture[i] = RenderTexture.GetTemporary(tex.width, tex.height, 0);
|
||||
_outputTexture[i].filterMode = tex.filterMode;
|
||||
_outputTexture[i].wrapMode = tex.wrapMode;
|
||||
_outputTexture[i].anisoLevel = tex.anisoLevel;
|
||||
// TODO: set up the mips level too?
|
||||
}
|
||||
}
|
||||
|
||||
private bool CheckRenderTexturesValid()
|
||||
{
|
||||
for (int i = 0; i < _mediaPlayer.TextureProducer.GetTextureCount(); ++i)
|
||||
{
|
||||
Texture tex = _mediaPlayer.TextureProducer.GetTexture(i);
|
||||
for (int j = 0; j < _buffer.Count; ++j)
|
||||
{
|
||||
if (_buffer[i][j].texture == null || _buffer[i][j].texture.width != tex.width || _buffer[i][j].texture.height != tex.height)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (_outputTexture == null || _outputTexture[i] == null || _outputTexture[i].width != tex.width || _outputTexture[i].height != tex.height)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//finds closest frame that occurs before given index
|
||||
private int FindBeforeFrameIndex(int frameIdx)
|
||||
{
|
||||
if (frameIdx >= _buffer.Count)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
int foundFrame = -1;
|
||||
float smallestDif = float.MaxValue;
|
||||
int closest = -1;
|
||||
float smallestElapsed = float.MaxValue;
|
||||
|
||||
for (int i = 0; i < _buffer[frameIdx].Length; ++i)
|
||||
{
|
||||
if (_buffer[frameIdx][i].used)
|
||||
{
|
||||
float elapsed = (_buffer[frameIdx][i].timestamp - _baseTimestamp) / 10000000f;
|
||||
|
||||
//keep track of closest after frame, just in case no before frame was found
|
||||
if (elapsed < smallestElapsed)
|
||||
{
|
||||
closest = i;
|
||||
smallestElapsed = elapsed;
|
||||
}
|
||||
|
||||
float dif = _elapsedTimeSinceBase - elapsed;
|
||||
|
||||
if (dif >= 0 && dif < smallestDif)
|
||||
{
|
||||
smallestDif = dif;
|
||||
foundFrame = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (foundFrame < 0)
|
||||
{
|
||||
if (closest < 0)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
return closest;
|
||||
}
|
||||
|
||||
return foundFrame;
|
||||
}
|
||||
|
||||
private int FindClosestFrame(int frameIdx)
|
||||
{
|
||||
if (frameIdx >= _buffer.Count)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
int foundPos = -1;
|
||||
float smallestDif = float.MaxValue;
|
||||
|
||||
for (int i = 0; i < _buffer[frameIdx].Length; ++i)
|
||||
{
|
||||
if (_buffer[frameIdx][i].used)
|
||||
{
|
||||
float elapsed = (_buffer[frameIdx][i].timestamp - _baseTimestamp) / 10000000f;
|
||||
float dif = Mathf.Abs(_elapsedTimeSinceBase - elapsed);
|
||||
if (dif < smallestDif)
|
||||
{
|
||||
foundPos = i;
|
||||
smallestDif = dif;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return foundPos;
|
||||
}
|
||||
|
||||
//point update selects closest frame and uses that as output
|
||||
private void PointUpdate()
|
||||
{
|
||||
for (int i = 0; i < _buffer.Count; ++i)
|
||||
{
|
||||
int frameIndex = FindClosestFrame(i);
|
||||
if (frameIndex < 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
_outputTexture[i].DiscardContents();
|
||||
Graphics.Blit(_buffer[i][frameIndex].texture, _outputTexture[i]);
|
||||
TextureTimeStamp = _currentDisplayedTimestamp = _buffer[i][frameIndex].timestamp;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//Updates currently displayed frame
|
||||
private void SampleFrame(int frameIdx, int bufferIdx)
|
||||
{
|
||||
_outputTexture[bufferIdx].DiscardContents();
|
||||
Graphics.Blit(_buffer[bufferIdx][frameIdx].texture, _outputTexture[bufferIdx]);
|
||||
TextureTimeStamp = _currentDisplayedTimestamp = _buffer[bufferIdx][frameIdx].timestamp;
|
||||
}
|
||||
|
||||
//Same as sample frame, but does a lerp of the two given frames and outputs that image instead
|
||||
private void SampleFrames(int bufferIdx, int frameIdx1, int frameIdx2, float t)
|
||||
{
|
||||
_blendMat.SetFloat(_propT, t);
|
||||
_blendMat.SetTexture(_propAfterTex, _buffer[bufferIdx][frameIdx2].texture);
|
||||
_outputTexture[bufferIdx].DiscardContents();
|
||||
Graphics.Blit(_buffer[bufferIdx][frameIdx1].texture, _outputTexture[bufferIdx], _blendMat);
|
||||
TextureTimeStamp = (long)Mathf.Lerp(_buffer[bufferIdx][frameIdx1].timestamp, _buffer[bufferIdx][frameIdx2].timestamp, t);
|
||||
_currentDisplayedTimestamp = _buffer[bufferIdx][frameIdx1].timestamp;
|
||||
}
|
||||
|
||||
private void LinearUpdate()
|
||||
{
|
||||
for (int i = 0; i < _buffer.Count; ++i)
|
||||
{
|
||||
//find closest frame
|
||||
int frameIndex = FindBeforeFrameIndex(i);
|
||||
|
||||
//no valid frame, this should never ever happen actually...
|
||||
if (frameIndex < 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
//resample or just use last frame and set current elapsed time to that frame
|
||||
float frameElapsed = (_buffer[i][frameIndex].timestamp - _baseTimestamp) / 10000000f;
|
||||
if (frameElapsed > _elapsedTimeSinceBase)
|
||||
{
|
||||
SampleFrame(frameIndex, i);
|
||||
LastT = -1f;
|
||||
}
|
||||
else
|
||||
{
|
||||
int next = (frameIndex + 1) % _buffer[i].Length;
|
||||
float nextElapsed = (_buffer[i][next].timestamp - _baseTimestamp) / 10000000f;
|
||||
|
||||
//no larger frame, move elapsed time back a bit since we cant predict the future
|
||||
if (nextElapsed < frameElapsed)
|
||||
{
|
||||
SampleFrame(frameIndex, i);
|
||||
LastT = 2f;
|
||||
}
|
||||
//have a before and after frame, interpolate
|
||||
else
|
||||
{
|
||||
|
||||
float range = nextElapsed - frameElapsed;
|
||||
float t = (_elapsedTimeSinceBase - frameElapsed) / range;
|
||||
SampleFrames(i, frameIndex, next, t);
|
||||
LastT = t;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void InvalidateBuffer()
|
||||
{
|
||||
_elapsedTimeSinceBase = (_bufferSize / 2) / _videoFrameRate;
|
||||
|
||||
for (int i = 0; i < _buffer.Count; ++i)
|
||||
{
|
||||
for (int j = 0; j < _buffer[i].Length; ++j)
|
||||
{
|
||||
_buffer[i][j].used = false;
|
||||
}
|
||||
}
|
||||
|
||||
_start = _end = 0;
|
||||
}
|
||||
|
||||
private float GuessFrameRate()
|
||||
{
|
||||
int fpsCount = 0;
|
||||
long fps = 0;
|
||||
|
||||
for (int k = 0; k < _buffer[0].Length; k++)
|
||||
{
|
||||
if (_buffer[0][k].used)
|
||||
{
|
||||
// Find the pair with the smallest difference
|
||||
long smallestDiff = long.MaxValue;
|
||||
for (int j = k + 1; j < _buffer[0].Length; j++)
|
||||
{
|
||||
if (_buffer[0][j].used)
|
||||
{
|
||||
long diff = System.Math.Abs(_buffer[0][k].timestamp - _buffer[0][j].timestamp);
|
||||
if (diff < smallestDiff)
|
||||
{
|
||||
smallestDiff = diff;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (smallestDiff != long.MaxValue)
|
||||
{
|
||||
fps += smallestDiff;
|
||||
fpsCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (fpsCount > 1)
|
||||
{
|
||||
fps /= fpsCount;
|
||||
}
|
||||
return 10000000f / (float)fps;
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
if (_mediaPlayer.TextureProducer == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
//recreate textures if invalid
|
||||
if (_mediaPlayer.TextureProducer == null || _mediaPlayer.TextureProducer.GetTexture() == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!CheckRenderTexturesValid())
|
||||
{
|
||||
ConstructRenderTextures();
|
||||
}
|
||||
|
||||
long currentTimestamp = _mediaPlayer.TextureProducer.GetTextureTimeStamp();
|
||||
|
||||
//if frame has been updated, do a calculation to estimate dropped frames
|
||||
if (currentTimestamp != _lastTimeStamp)
|
||||
{
|
||||
float dif = Mathf.Abs(currentTimestamp - _lastTimeStamp);
|
||||
float frameLength = (10000000f / _videoFrameRate);
|
||||
if (dif > frameLength * 1.1f && dif < frameLength * 3.1f)
|
||||
{
|
||||
_droppedFrames += (int)((dif - frameLength) / frameLength + 0.5);
|
||||
}
|
||||
_lastTimeStamp = currentTimestamp;
|
||||
}
|
||||
|
||||
//Adding texture to buffer logic
|
||||
long timestamp = _mediaPlayer.TextureProducer.GetTextureTimeStamp();
|
||||
bool insertNewFrame = !_mediaPlayer.Control.IsSeeking();
|
||||
//if buffer is not empty, we need to check if we need to reject the new frame
|
||||
if (_start != _end || _buffer[0][_end].used)
|
||||
{
|
||||
int lastFrame = (_end + _buffer[0].Length - 1) % _buffer[0].Length;
|
||||
//frame is not new and thus we do not need to store it
|
||||
if (timestamp == _buffer[0][lastFrame].timestamp)
|
||||
{
|
||||
insertNewFrame = false;
|
||||
}
|
||||
}
|
||||
|
||||
bool bufferWasNotFull = (_start != _end) || (!_buffer[0][_end].used);
|
||||
|
||||
if (insertNewFrame)
|
||||
{
|
||||
//buffer empty, reset base timestamp to current
|
||||
if (_start == _end && !_buffer[0][_end].used)
|
||||
{
|
||||
_baseTimestamp = timestamp;
|
||||
}
|
||||
|
||||
//update buffer counters, if buffer is full, we get rid of the earliest frame by incrementing the start counter
|
||||
if (_end == _start && _buffer[0][_end].used)
|
||||
{
|
||||
_start = (_start + 1) % _buffer[0].Length;
|
||||
}
|
||||
|
||||
for (int i = 0; i < _mediaPlayer.TextureProducer.GetTextureCount(); ++i)
|
||||
{
|
||||
Texture currentTexture = _mediaPlayer.TextureProducer.GetTexture(i);
|
||||
|
||||
//store frame info
|
||||
_buffer[i][_end].texture.DiscardContents();
|
||||
Graphics.Blit(currentTexture, _buffer[i][_end].texture);
|
||||
_buffer[i][_end].timestamp = timestamp;
|
||||
_buffer[i][_end].used = true;
|
||||
}
|
||||
|
||||
_end = (_end + 1) % _buffer[0].Length;
|
||||
}
|
||||
|
||||
bool bufferNotFull = (_start != _end) || (!_buffer[0][_end].used);
|
||||
|
||||
if (bufferNotFull)
|
||||
{
|
||||
for (int i = 0; i < _buffer.Count; ++i)
|
||||
{
|
||||
_outputTexture[i].DiscardContents();
|
||||
Graphics.Blit(_buffer[i][_start].texture, _outputTexture[i]);
|
||||
_currentDisplayedTimestamp = _buffer[i][_start].timestamp;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// If we don't have a valid frame rate and the buffer is now full, guess the frame rate by looking at the buffered timestamps
|
||||
if (bufferWasNotFull && _videoFrameRate <= 0f)
|
||||
{
|
||||
_videoFrameRate = GuessFrameRate();
|
||||
_elapsedTimeSinceBase = (_bufferSize / 2) / _videoFrameRate;
|
||||
}
|
||||
}
|
||||
|
||||
if (_mediaPlayer.Control.IsPaused())
|
||||
{
|
||||
InvalidateBuffer();
|
||||
}
|
||||
|
||||
//we always wait until buffer is full before display things, just assign first frame in buffer to output so that the user can see something
|
||||
if (bufferNotFull)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_mediaPlayer.Control.IsPlaying() && !_mediaPlayer.Control.IsFinished())
|
||||
{
|
||||
//correct elapsed time if too far out
|
||||
long ts = _buffer[0][(_start + _bufferSize / 2) % _bufferSize].timestamp - _baseTimestamp;
|
||||
double dif = Mathf.Abs(((float)((double)_elapsedTimeSinceBase * 10000000) - ts));
|
||||
double threshold = (_buffer[0].Length / 2) / _videoFrameRate * 10000000;
|
||||
|
||||
if (dif > threshold)
|
||||
{
|
||||
_elapsedTimeSinceBase = ts / 10000000f;
|
||||
}
|
||||
|
||||
if (_resampleMode == ResampleMode.POINT)
|
||||
{
|
||||
PointUpdate();
|
||||
}
|
||||
else if (_resampleMode == ResampleMode.LINEAR)
|
||||
{
|
||||
LinearUpdate();
|
||||
}
|
||||
|
||||
_elapsedTimeSinceBase += Time.unscaledDeltaTime;
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdateTimestamp()
|
||||
{
|
||||
if (_lastDisplayedTimestamp != _currentDisplayedTimestamp)
|
||||
{
|
||||
_lastDisplayedTimestamp = _currentDisplayedTimestamp;
|
||||
_frameDisplayedTimer = 0;
|
||||
}
|
||||
_frameDisplayedTimer++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8ac8dc09faa6b1d48bf6f490c9888550
|
||||
timeCreated: 1497356591
|
||||
licenseType: Free
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,144 @@
|
||||
using UnityEngine;
|
||||
using System.Collections.Generic;
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Copyright 2015-2021 RenderHeads Ltd. All rights reserved.
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
namespace RenderHeads.Media.AVProVideo
|
||||
{
|
||||
public class Subtitle
|
||||
{
|
||||
public int index;
|
||||
// Rich string can contain <font color=""> <u> etc
|
||||
public string text;
|
||||
public double timeStart, timeEnd;
|
||||
|
||||
public bool IsBefore(double time)
|
||||
{
|
||||
return (time > timeStart && time > timeEnd);
|
||||
}
|
||||
|
||||
public bool IsTime(double time)
|
||||
{
|
||||
return (time >= timeStart && time < timeEnd);
|
||||
}
|
||||
}
|
||||
|
||||
public class SubtitlePlayer
|
||||
{
|
||||
// min time, max time
|
||||
// set time
|
||||
// event for change(subs added, subs removed)
|
||||
// list of subs on
|
||||
}
|
||||
|
||||
public class SubtitleUtils
|
||||
{
|
||||
/// <summary>
|
||||
/// Parse time in format: 00:00:48,924 and convert to seconds
|
||||
/// </summary>
|
||||
private static double ParseTimeToSeconds(string text)
|
||||
{
|
||||
double result = 0.0;
|
||||
|
||||
string[] digits = text.Split(new char[] { ':', ',' });
|
||||
|
||||
if (digits.Length == 4)
|
||||
{
|
||||
int hours = int.Parse(digits[0]);
|
||||
int minutes = int.Parse(digits[1]);
|
||||
int seconds = int.Parse(digits[2]);
|
||||
int milliseconds = int.Parse(digits[3]);
|
||||
|
||||
result = (milliseconds / 1000.0) + (seconds + (minutes + (hours * 60)) * 60);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parse subtitles in the SRT format and convert to a list of ordered Subtitle objects
|
||||
/// </summary>
|
||||
public static List<Subtitle> ParseSubtitlesSRT(string data)
|
||||
{
|
||||
List<Subtitle> result = null;
|
||||
|
||||
if (!string.IsNullOrEmpty(data))
|
||||
{
|
||||
data = data.Trim();
|
||||
var rx = new System.Text.RegularExpressions.Regex("\n\r|\r\n|\n|\r");
|
||||
string[] lines = rx.Split(data);
|
||||
|
||||
if (lines.Length >= 3)
|
||||
{
|
||||
result = new List<Subtitle>(256);
|
||||
|
||||
int count = 0;
|
||||
int index = 0;
|
||||
Subtitle subtitle = null;
|
||||
for (int i = 0; i < lines.Length; i++)
|
||||
{
|
||||
if (index == 0)
|
||||
{
|
||||
subtitle = new Subtitle();
|
||||
subtitle.index = count;// int.Parse(lines[i]);
|
||||
}
|
||||
else if (index == 1)
|
||||
{
|
||||
string[] times = lines[i].Split(new string[] { " --> " }, System.StringSplitOptions.RemoveEmptyEntries);
|
||||
if (times.Length == 2)
|
||||
{
|
||||
subtitle.timeStart = ParseTimeToSeconds(times[0]);
|
||||
subtitle.timeEnd = ParseTimeToSeconds(times[1]);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new System.FormatException("SRT format doesn't appear to be valid");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!string.IsNullOrEmpty(lines[i]))
|
||||
{
|
||||
if (index == 2)
|
||||
{
|
||||
subtitle.text = lines[i];
|
||||
}
|
||||
else
|
||||
{
|
||||
subtitle.text += "\n" + lines[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(lines[i]) && index > 1)
|
||||
{
|
||||
result.Add(subtitle);
|
||||
index = 0;
|
||||
count++;
|
||||
subtitle = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
index++;
|
||||
}
|
||||
}
|
||||
|
||||
// Handle the last one
|
||||
if (subtitle != null)
|
||||
{
|
||||
result.Add(subtitle);
|
||||
subtitle = null;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogWarning("[AVProVideo] SRT format doesn't appear to be valid");
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c21f230642ee9284eb9726613241c7bd
|
||||
timeCreated: 1548861442
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,651 @@
|
||||
#if UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX || UNITY_IOS || UNITY_TVOS
|
||||
#define UNITY_PLATFORM_SUPPORTS_YPCBCR
|
||||
#endif
|
||||
|
||||
#if UNITY_EDITOR || UNITY_STANDALONE_OSX || UNITY_STANDALONE_WIN || UNITY_IOS || UNITY_TVOS || UNITY_ANDROID || (UNITY_WEBGL && UNITY_2017_2_OR_NEWER)
|
||||
#define UNITY_PLATFORM_SUPPORTS_LINEAR
|
||||
#endif
|
||||
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Rendering;
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Copyright 2015-2022 RenderHeads Ltd. All rights reserved.
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
namespace RenderHeads.Media.AVProVideo
|
||||
{
|
||||
#if AVPRO_FEATURE_VIDEORESOLVE
|
||||
[System.Serializable]
|
||||
public class VideoResolve : ITextureProducer
|
||||
{
|
||||
[SerializeField] VideoResolveOptions _options = VideoResolveOptions.Create();
|
||||
[SerializeField] RenderTexture _targetRenderTexture = null;
|
||||
[SerializeField] ScaleMode _targetRenderTextureScale = ScaleMode.ScaleToFit;
|
||||
|
||||
void SetSource(ITextureProducer textureSource)
|
||||
{
|
||||
//_commandBuffer.IssuePluginEvent(blahCallback, 0);
|
||||
//Graphics.ExecuteCommandBuffer(_commandBuffer);
|
||||
}
|
||||
|
||||
// ITextureProducer implementation
|
||||
|
||||
/// <inheritdoc/>
|
||||
public int GetTextureCount() { return 1; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Texture GetTexture(int index = 0) { return _texture; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public int GetTextureFrameCount() { return _textureSource.GetTextureFrameCount(); }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool SupportsTextureFrameCount() { return _textureSource.SupportsTextureFrameCount(); }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public long GetTextureTimeStamp() { return _textureSource.GetTextureTimeStamp(); }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool RequiresVerticalFlip() { return false; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public StereoPacking GetTextureStereoPacking() { return StereoPacking.None; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public TransparencyMode GetTextureTransparency() { return TransparencyMode.Transparent; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public AlphaPacking GetTextureAlphaPacking() { return AlphaPacking.None; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Matrix4x4 GetYpCbCrTransform() { return Matrix4x4.identity; }
|
||||
|
||||
private ITextureProducer _textureSource;
|
||||
private Texture _texture;
|
||||
private CommandBuffer _commandBuffer;
|
||||
}
|
||||
#endif
|
||||
|
||||
public struct LazyShaderProperty
|
||||
{
|
||||
public LazyShaderProperty(string name)
|
||||
{
|
||||
_name = name;
|
||||
_id = 0;
|
||||
}
|
||||
|
||||
public string Name { get { return _name;} }
|
||||
public int Id { get { if (_id == 0) { _id = Shader.PropertyToID(_name); } return _id; } }
|
||||
|
||||
private string _name;
|
||||
private int _id;
|
||||
}
|
||||
|
||||
/// <summary>Helper class for everything related to setting up materials for rendering/resolving videos</summary>
|
||||
public class VideoRender
|
||||
{
|
||||
public const string Shader_IMGUI = "AVProVideo/Internal/IMGUI/Texture Transparent";
|
||||
public const string Shader_Resolve = "AVProVideo/Internal/Resolve";
|
||||
public const string Shader_ResolveOES = "AVProVideo/Internal/ResolveOES";
|
||||
public const string Shader_Preview = "AVProVideo/Internal/Preview";
|
||||
|
||||
#if UNITY_PLATFORM_SUPPORTS_YPCBCR
|
||||
public const string Keyword_UseYpCbCr = "USE_YPCBCR";
|
||||
#endif
|
||||
public const string Keyword_AlphaPackTopBottom = "ALPHAPACK_TOP_BOTTOM";
|
||||
public const string Keyword_AlphaPackLeftRight = "ALPHAPACK_LEFT_RIGHT";
|
||||
public const string Keyword_AlphaPackNone = "ALPHAPACK_NONE";
|
||||
public const string Keyword_StereoTopBottom = "STEREO_TOP_BOTTOM";
|
||||
public const string Keyword_StereoLeftRight = "STEREO_LEFT_RIGHT";
|
||||
public const string Keyword_StereoCustomUV = "STEREO_CUSTOM_UV";
|
||||
public const string Keyword_StereoTwoTextures = "STEREO_TWOTEXTURES";
|
||||
public const string Keyword_StereoNone = "MONOSCOPIC";
|
||||
public const string Keyword_StereoDebug = "STEREO_DEBUG";
|
||||
public const string Keyword_LayoutEquirect180 = "LAYOUT_EQUIRECT180";
|
||||
public const string Keyword_LayoutNone = "LAYOUT_NONE";
|
||||
public const string Keyword_ForceEyeNone = "FORCEEYE_NONE";
|
||||
public const string Keyword_ForceEyeLeft = "FORCEEYE_LEFT";
|
||||
public const string Keyword_ForceEyeRight = "FORCEEYE_RIGHT";
|
||||
public const string Keyword_ApplyGamma = "APPLY_GAMMA";
|
||||
|
||||
public static readonly LazyShaderProperty PropChromaTex = new LazyShaderProperty("_ChromaTex");
|
||||
|
||||
#if UNITY_PLATFORM_SUPPORTS_YPCBCR
|
||||
public static readonly LazyShaderProperty PropYpCbCrTransform = new LazyShaderProperty("_YpCbCrTransform");
|
||||
public static readonly LazyShaderProperty PropUseYpCbCr = new LazyShaderProperty("_UseYpCbCr");
|
||||
#endif
|
||||
|
||||
public static readonly LazyShaderProperty PropVertScale = new LazyShaderProperty("_VertScale");
|
||||
public static readonly LazyShaderProperty PropApplyGamma = new LazyShaderProperty("_ApplyGamma");
|
||||
public static readonly LazyShaderProperty PropStereo = new LazyShaderProperty("Stereo");
|
||||
public static readonly LazyShaderProperty PropAlphaPack = new LazyShaderProperty("AlphaPack");
|
||||
public static readonly LazyShaderProperty PropLayout = new LazyShaderProperty("Layout");
|
||||
public static readonly LazyShaderProperty PropViewMatrix = new LazyShaderProperty("_ViewMatrix");
|
||||
public static readonly LazyShaderProperty PropTextureMatrix = new LazyShaderProperty("_TextureMatrix");
|
||||
|
||||
public static string Keyword_UseHSBC = "USE_HSBC";
|
||||
public static readonly LazyShaderProperty PropHue = new LazyShaderProperty("_Hue");
|
||||
public static readonly LazyShaderProperty PropSaturation = new LazyShaderProperty("_Saturation");
|
||||
public static readonly LazyShaderProperty PropContrast = new LazyShaderProperty("_Contrast");
|
||||
public static readonly LazyShaderProperty PropBrightness = new LazyShaderProperty("_Brightness");
|
||||
public static readonly LazyShaderProperty PropInvGamma = new LazyShaderProperty("_InvGamma");
|
||||
|
||||
public static Material CreateResolveMaterial(bool usingAndroidOES)
|
||||
{
|
||||
return new Material(Shader.Find( usingAndroidOES ? VideoRender.Shader_ResolveOES : VideoRender.Shader_Resolve ));
|
||||
}
|
||||
|
||||
public static Material CreateIMGUIMaterial()
|
||||
{
|
||||
return new Material(Shader.Find(VideoRender.Shader_Preview));
|
||||
}
|
||||
|
||||
public static void SetupLayoutMaterial(Material material, VideoMapping mapping)
|
||||
{
|
||||
switch (mapping)
|
||||
{
|
||||
default:
|
||||
material.DisableKeyword(Keyword_LayoutEquirect180);
|
||||
material.EnableKeyword(Keyword_LayoutNone);
|
||||
break;
|
||||
// Only EquiRectangular180 currently does anything in the shader
|
||||
case VideoMapping.EquiRectangular180:
|
||||
material.DisableKeyword(Keyword_LayoutNone);
|
||||
material.EnableKeyword(Keyword_LayoutEquirect180);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public static void SetupStereoEyeModeMaterial(Material material, StereoEye mode)
|
||||
{
|
||||
switch (mode)
|
||||
{
|
||||
case StereoEye.Both:
|
||||
material.DisableKeyword(Keyword_ForceEyeLeft);
|
||||
material.DisableKeyword(Keyword_ForceEyeRight);
|
||||
material.EnableKeyword(Keyword_ForceEyeNone);
|
||||
break;
|
||||
case StereoEye.Left:
|
||||
material.DisableKeyword(Keyword_ForceEyeNone);
|
||||
material.DisableKeyword(Keyword_ForceEyeRight);
|
||||
material.EnableKeyword(Keyword_ForceEyeLeft);
|
||||
break;
|
||||
case StereoEye.Right:
|
||||
material.DisableKeyword(Keyword_ForceEyeNone);
|
||||
material.DisableKeyword(Keyword_ForceEyeLeft);
|
||||
material.EnableKeyword(Keyword_ForceEyeRight);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public static void SetupStereoMaterial(Material material, StereoPacking packing)
|
||||
{
|
||||
switch (packing)
|
||||
{
|
||||
case StereoPacking.None:
|
||||
material.DisableKeyword(Keyword_StereoTopBottom);
|
||||
material.DisableKeyword(Keyword_StereoLeftRight);
|
||||
material.DisableKeyword(Keyword_StereoCustomUV);
|
||||
material.DisableKeyword(Keyword_StereoTwoTextures);
|
||||
material.EnableKeyword(Keyword_StereoNone);
|
||||
break;
|
||||
case StereoPacking.TopBottom:
|
||||
material.DisableKeyword(Keyword_StereoNone);
|
||||
material.DisableKeyword(Keyword_StereoLeftRight);
|
||||
material.DisableKeyword(Keyword_StereoCustomUV);
|
||||
material.DisableKeyword(Keyword_StereoTwoTextures);
|
||||
material.EnableKeyword(Keyword_StereoTopBottom);
|
||||
break;
|
||||
case StereoPacking.LeftRight:
|
||||
material.DisableKeyword(Keyword_StereoNone);
|
||||
material.DisableKeyword(Keyword_StereoTopBottom);
|
||||
material.DisableKeyword(Keyword_StereoTwoTextures);
|
||||
material.DisableKeyword(Keyword_StereoCustomUV);
|
||||
material.EnableKeyword(Keyword_StereoLeftRight);
|
||||
break;
|
||||
case StereoPacking.CustomUV:
|
||||
material.DisableKeyword(Keyword_StereoNone);
|
||||
material.DisableKeyword(Keyword_StereoTopBottom);
|
||||
material.DisableKeyword(Keyword_StereoLeftRight);
|
||||
material.DisableKeyword(Keyword_StereoTwoTextures);
|
||||
material.EnableKeyword(Keyword_StereoCustomUV);
|
||||
break;
|
||||
case StereoPacking.TwoTextures:
|
||||
material.DisableKeyword(Keyword_StereoNone);
|
||||
material.DisableKeyword(Keyword_StereoTopBottom);
|
||||
material.DisableKeyword(Keyword_StereoLeftRight);
|
||||
material.DisableKeyword(Keyword_StereoCustomUV);
|
||||
material.EnableKeyword(Keyword_StereoTwoTextures);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public static void SetupGlobalDebugStereoTinting(bool enabled)
|
||||
{
|
||||
if (enabled)
|
||||
{
|
||||
Shader.EnableKeyword(Keyword_StereoDebug);
|
||||
}
|
||||
else
|
||||
{
|
||||
Shader.DisableKeyword(Keyword_StereoDebug);
|
||||
}
|
||||
}
|
||||
|
||||
public static void SetupAlphaPackedMaterial(Material material, AlphaPacking packing)
|
||||
{
|
||||
switch (packing)
|
||||
{
|
||||
case AlphaPacking.None:
|
||||
material.DisableKeyword(Keyword_AlphaPackTopBottom);
|
||||
material.DisableKeyword(Keyword_AlphaPackLeftRight);
|
||||
material.EnableKeyword(Keyword_AlphaPackNone);
|
||||
break;
|
||||
case AlphaPacking.TopBottom:
|
||||
material.DisableKeyword(Keyword_AlphaPackNone);
|
||||
material.DisableKeyword(Keyword_AlphaPackLeftRight);
|
||||
material.EnableKeyword(Keyword_AlphaPackTopBottom);
|
||||
break;
|
||||
case AlphaPacking.LeftRight:
|
||||
material.DisableKeyword(Keyword_AlphaPackNone);
|
||||
material.DisableKeyword(Keyword_AlphaPackTopBottom);
|
||||
material.EnableKeyword(Keyword_AlphaPackLeftRight);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public static void SetupGammaMaterial(Material material, bool playerSupportsLinear)
|
||||
{
|
||||
#if UNITY_PLATFORM_SUPPORTS_LINEAR
|
||||
if (QualitySettings.activeColorSpace == ColorSpace.Linear && !playerSupportsLinear)
|
||||
{
|
||||
material.EnableKeyword(Keyword_ApplyGamma);
|
||||
}
|
||||
else
|
||||
{
|
||||
material.DisableKeyword(Keyword_ApplyGamma);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
public static void SetupTextureMatrix(Material material, float[] transform)
|
||||
{
|
||||
#if (!UNITY_EDITOR && UNITY_ANDROID)
|
||||
// STE: HasProperty doesn't work on Matrix'
|
||||
// if (material != null && (material.HasProperty(VideoRender.PropTextureMatrix.Id)))
|
||||
{
|
||||
if (transform != null)
|
||||
{
|
||||
Matrix4x4 m = new Matrix4x4(new Vector4( transform[0], transform[1], transform[2], transform[3] ),
|
||||
new Vector4( transform[4], transform[5], transform[6], transform[7] ),
|
||||
new Vector4( transform[8], transform[9], transform[10], transform[11] ),
|
||||
new Vector4( transform[12], transform[13], transform[14], transform[15] ));
|
||||
material.SetMatrix(VideoRender.PropTextureMatrix.Id, m);
|
||||
}
|
||||
else
|
||||
{
|
||||
material.SetMatrix(VideoRender.PropTextureMatrix.Id, Matrix4x4.identity);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#if UNITY_PLATFORM_SUPPORTS_YPCBCR
|
||||
public static void SetupYpCbCrMaterial(Material material, bool enable, Matrix4x4 transform, Texture texture)
|
||||
{
|
||||
if (material.HasProperty(VideoRender.PropUseYpCbCr.Id))
|
||||
{
|
||||
if (enable)
|
||||
{
|
||||
material.EnableKeyword(VideoRender.Keyword_UseYpCbCr);
|
||||
material.SetMatrix(VideoRender.PropYpCbCrTransform.Id, transform);
|
||||
material.SetTexture(VideoRender.PropChromaTex.Id, texture);
|
||||
}
|
||||
else
|
||||
{
|
||||
material.DisableKeyword(VideoRender.Keyword_UseYpCbCr);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
public static void SetupVerticalFlipMaterial(Material material, bool flip)
|
||||
{
|
||||
material.SetFloat(VideoRender.PropVertScale.Id, flip?-1f:1f);
|
||||
}
|
||||
|
||||
public static Texture GetTexture(MediaPlayer mediaPlayer, int textureIndex)
|
||||
{
|
||||
Texture result = null;
|
||||
if (mediaPlayer != null)
|
||||
{
|
||||
if (mediaPlayer.UseResampler && mediaPlayer.FrameResampler != null && mediaPlayer.FrameResampler.OutputTexture != null)
|
||||
{
|
||||
if ( mediaPlayer.FrameResampler.OutputTexture.Length > textureIndex)
|
||||
{
|
||||
result = mediaPlayer.FrameResampler.OutputTexture[textureIndex];
|
||||
}
|
||||
}
|
||||
else if (mediaPlayer.TextureProducer != null)
|
||||
{
|
||||
if (mediaPlayer.TextureProducer.GetTextureCount() > textureIndex)
|
||||
{
|
||||
result = mediaPlayer.TextureProducer.GetTexture(textureIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public static void SetupMaterialForMedia(Material material, MediaPlayer mediaPlayer, int texturePropId = -1, Texture fallbackTexture = null, bool forceFallbackTexture = false)
|
||||
{
|
||||
Debug.Assert(material != null);
|
||||
if (mediaPlayer != null)
|
||||
{
|
||||
Texture mainTexture = GetTexture(mediaPlayer, 0);
|
||||
Texture yCbCrTexture = GetTexture(mediaPlayer, 1);
|
||||
|
||||
if (texturePropId != -1)
|
||||
{
|
||||
if (mainTexture == null || forceFallbackTexture)
|
||||
{
|
||||
mainTexture = fallbackTexture;
|
||||
}
|
||||
material.SetTexture(texturePropId, mainTexture);
|
||||
}
|
||||
|
||||
SetupMaterial(material,
|
||||
(mediaPlayer.TextureProducer != null)?mediaPlayer.TextureProducer.RequiresVerticalFlip():false,
|
||||
(mediaPlayer.Info != null)?mediaPlayer.Info.PlayerSupportsLinearColorSpace():true,
|
||||
(mediaPlayer.TextureProducer != null)?mediaPlayer.TextureProducer.GetYpCbCrTransform():Matrix4x4.identity,
|
||||
yCbCrTexture,
|
||||
(mediaPlayer.Info != null && mediaPlayer.PlatformOptionsAndroid.useFastOesPath)?mediaPlayer.Info.GetTextureTransform():null,
|
||||
mediaPlayer.VideoLayoutMapping,
|
||||
(mediaPlayer.TextureProducer != null)?mediaPlayer.TextureProducer.GetTextureStereoPacking():StereoPacking.None,
|
||||
(mediaPlayer.TextureProducer != null)?mediaPlayer.TextureProducer.GetTextureAlphaPacking():AlphaPacking.None);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (texturePropId != -1)
|
||||
{
|
||||
material.SetTexture(texturePropId, fallbackTexture);
|
||||
}
|
||||
SetupMaterial(material, false, true, Matrix4x4.identity, null);
|
||||
}
|
||||
}
|
||||
|
||||
internal static void SetupMaterial(Material material, bool flipVertically, bool playerSupportsLinear, Matrix4x4 ycbcrTransform, Texture ycbcrTexture = null, float[] textureTransform = null,
|
||||
VideoMapping mapping = VideoMapping.Normal, StereoPacking stereoPacking = StereoPacking.None, AlphaPacking alphaPacking = AlphaPacking.None)
|
||||
{
|
||||
SetupVerticalFlipMaterial(material, flipVertically);
|
||||
|
||||
// Apply changes for layout
|
||||
if (material.HasProperty(VideoRender.PropLayout.Id))
|
||||
{
|
||||
VideoRender.SetupLayoutMaterial(material, mapping);
|
||||
}
|
||||
|
||||
// Apply changes for stereo videos
|
||||
if (material.HasProperty(VideoRender.PropStereo.Id))
|
||||
{
|
||||
VideoRender.SetupStereoMaterial(material, stereoPacking);
|
||||
}
|
||||
|
||||
// Apply changes for alpha videos
|
||||
if (material.HasProperty(VideoRender.PropAlphaPack.Id))
|
||||
{
|
||||
VideoRender.SetupAlphaPackedMaterial(material, alphaPacking);
|
||||
}
|
||||
|
||||
// Apply gamma correction
|
||||
#if UNITY_PLATFORM_SUPPORTS_LINEAR
|
||||
if (material.HasProperty(VideoRender.PropApplyGamma.Id))
|
||||
{
|
||||
VideoRender.SetupGammaMaterial(material, playerSupportsLinear);
|
||||
}
|
||||
#endif
|
||||
|
||||
// Adjust for cropping (when the decoder decodes in blocks that overrun the video frame size, it pads), OES only as we apply this lower down for none-OES
|
||||
#if (!UNITY_EDITOR && UNITY_ANDROID)
|
||||
// STE: HasProperty doesn't work on Matrix'
|
||||
// if (material.HasProperty(VideoRender.PropTextureMatrix.Id))
|
||||
{
|
||||
VideoRender.SetupTextureMatrix(material, textureTransform);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if UNITY_PLATFORM_SUPPORTS_YPCBCR
|
||||
VideoRender.SetupYpCbCrMaterial(material, ycbcrTexture != null, ycbcrTransform, ycbcrTexture);
|
||||
#endif
|
||||
}
|
||||
|
||||
[System.Flags]
|
||||
public enum ResolveFlags : int
|
||||
{
|
||||
Mipmaps = 1 << 0,
|
||||
PackedAlpha = 1 << 1,
|
||||
StereoLeft = 1 << 2,
|
||||
StereoRight = 1 << 3,
|
||||
ColorspaceSRGB = 1 << 4,
|
||||
}
|
||||
|
||||
public static void SetupResolveMaterial(Material material, VideoResolveOptions options)
|
||||
{
|
||||
if (options.IsColourAdjust())
|
||||
{
|
||||
material.EnableKeyword(VideoRender.Keyword_UseHSBC);
|
||||
material.SetFloat(VideoRender.PropHue.Id, options.hue);
|
||||
material.SetFloat(VideoRender.PropSaturation.Id, options.saturation);
|
||||
material.SetFloat(VideoRender.PropBrightness.Id, options.brightness);
|
||||
material.SetFloat(VideoRender.PropContrast.Id, options.contrast);
|
||||
material.SetFloat(VideoRender.PropInvGamma.Id, 1f / options.gamma);
|
||||
}
|
||||
else
|
||||
{
|
||||
material.DisableKeyword(VideoRender.Keyword_UseHSBC);
|
||||
}
|
||||
|
||||
material.color = options.tint;
|
||||
}
|
||||
|
||||
public static RenderTexture ResolveVideoToRenderTexture(Material resolveMaterial, RenderTexture targetTexture, ITextureProducer texture, ResolveFlags flags, ScaleMode scaleMode = ScaleMode.StretchToFill)
|
||||
{
|
||||
int targetWidth = texture.GetTexture(0).width;
|
||||
int targetHeight = texture.GetTexture(0).height;
|
||||
|
||||
StereoEye eyeMode = StereoEye.Both;
|
||||
if (((flags & ResolveFlags.StereoLeft) == ResolveFlags.StereoLeft) &&
|
||||
((flags & ResolveFlags.StereoRight) != ResolveFlags.StereoRight))
|
||||
{
|
||||
eyeMode = StereoEye.Left;
|
||||
}
|
||||
else if (((flags & ResolveFlags.StereoLeft) != ResolveFlags.StereoLeft) &&
|
||||
((flags & ResolveFlags.StereoRight) == ResolveFlags.StereoRight))
|
||||
{
|
||||
eyeMode = StereoEye.Right;
|
||||
}
|
||||
|
||||
// RJT NOTE: No longer passing in PAR as combined with larger videos (e.g. 8K+) it can lead to textures >16K which most platforms don't support
|
||||
// - Instead, the PAR is accounted for during drawing (which is more efficient too)
|
||||
// - https://github.com/RenderHeads/UnityPlugin-AVProVideo/issues/1297
|
||||
float pixelAspectRatio = 1.0f; // texture.GetTexturePixelAspectRatio();
|
||||
GetResolveTextureSize(
|
||||
texture.GetTextureAlphaPacking(),
|
||||
texture.GetTextureStereoPacking(),
|
||||
eyeMode,
|
||||
pixelAspectRatio,
|
||||
ref targetWidth,
|
||||
ref targetHeight);
|
||||
|
||||
if (targetTexture)
|
||||
{
|
||||
bool sizeChanged = (targetTexture.width != targetWidth) || (targetTexture.height != targetHeight);
|
||||
if (sizeChanged)
|
||||
{
|
||||
RenderTexture.ReleaseTemporary(targetTexture);
|
||||
targetTexture = null;
|
||||
}
|
||||
}
|
||||
|
||||
if (!targetTexture)
|
||||
{
|
||||
RenderTextureReadWrite readWrite = ((flags & ResolveFlags.ColorspaceSRGB) == ResolveFlags.ColorspaceSRGB) ? RenderTextureReadWrite.sRGB : RenderTextureReadWrite.Linear;
|
||||
targetTexture = RenderTexture.GetTemporary(targetWidth, targetHeight, 0, RenderTextureFormat.ARGB32, readWrite);
|
||||
}
|
||||
|
||||
// Set target mipmap generation support
|
||||
{
|
||||
bool requiresMipmap = (flags & ResolveFlags.Mipmaps) == ResolveFlags.Mipmaps;
|
||||
bool requiresRecreate = (targetTexture.IsCreated() && targetTexture.useMipMap != requiresMipmap);
|
||||
if (requiresRecreate)
|
||||
{
|
||||
targetTexture.Release();
|
||||
}
|
||||
if (!targetTexture.IsCreated())
|
||||
{
|
||||
targetTexture.useMipMap = targetTexture.autoGenerateMips = requiresMipmap;
|
||||
targetTexture.Create();
|
||||
}
|
||||
}
|
||||
|
||||
// Render resolve blit
|
||||
// TODO: combine these two paths into a single material blit
|
||||
{
|
||||
bool prevSRGB = GL.sRGBWrite;
|
||||
GL.sRGBWrite = targetTexture.sRGB;
|
||||
RenderTexture prev = RenderTexture.active;
|
||||
if (scaleMode == ScaleMode.StretchToFill)
|
||||
{
|
||||
Graphics.Blit(texture.GetTexture(0), targetTexture, resolveMaterial);
|
||||
}
|
||||
else
|
||||
{
|
||||
RenderTexture.active = targetTexture;
|
||||
bool partialAreaRender = (scaleMode == ScaleMode.ScaleToFit);
|
||||
if (partialAreaRender)
|
||||
{
|
||||
GL.Clear(false, true, Color.black);
|
||||
}
|
||||
VideoRender.DrawTexture(new Rect(0f, 0f, targetTexture.width, targetTexture.height), texture.GetTexture(0), scaleMode, texture.GetTextureAlphaPacking(), texture.GetTexturePixelAspectRatio(), resolveMaterial);
|
||||
}
|
||||
RenderTexture.active = prev;
|
||||
GL.sRGBWrite = prevSRGB;
|
||||
}
|
||||
|
||||
return targetTexture;
|
||||
}
|
||||
|
||||
public static void GetResolveTextureSize(AlphaPacking alphaPacking, StereoPacking stereoPacking, StereoEye eyeMode, float pixelAspectRatio, ref int width, ref int height)
|
||||
{
|
||||
switch (alphaPacking)
|
||||
{
|
||||
case AlphaPacking.LeftRight:
|
||||
width /= 2;
|
||||
break;
|
||||
case AlphaPacking.TopBottom:
|
||||
height /= 2;
|
||||
break;
|
||||
}
|
||||
|
||||
if (eyeMode != StereoEye.Both)
|
||||
{
|
||||
switch (stereoPacking)
|
||||
{
|
||||
case StereoPacking.LeftRight:
|
||||
width /= 2;
|
||||
break;
|
||||
case StereoPacking.TopBottom:
|
||||
height /= 2;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (pixelAspectRatio > 0f)
|
||||
{
|
||||
if (pixelAspectRatio > 1f)
|
||||
{
|
||||
width = Mathf.RoundToInt(width * pixelAspectRatio);
|
||||
}
|
||||
else if (pixelAspectRatio < 1f)
|
||||
{
|
||||
height = Mathf.RoundToInt(height / pixelAspectRatio);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: take into account rotation
|
||||
}
|
||||
|
||||
public static bool RequiresResolve(ITextureProducer texture)
|
||||
{
|
||||
return (texture.GetTextureAlphaPacking() != AlphaPacking.None ||
|
||||
texture.RequiresVerticalFlip() ||
|
||||
texture.GetTextureStereoPacking() != StereoPacking.None ||
|
||||
texture.GetTextureCount() > 1
|
||||
);
|
||||
}
|
||||
|
||||
public static void DrawTexture(Rect destRect, Texture texture, ScaleMode scaleMode, AlphaPacking alphaPacking, float pixelAspectRatio, Material material)
|
||||
{
|
||||
if (Event.current == null || Event.current.type == EventType.Repaint)
|
||||
{
|
||||
int sourceWidth = texture.width;
|
||||
int sourceHeight = texture.height;
|
||||
GetResolveTextureSize(alphaPacking, StereoPacking.Unknown, StereoEye.Both, pixelAspectRatio, ref sourceWidth, ref sourceHeight);
|
||||
|
||||
float sourceRatio = (float)sourceWidth / (float)sourceHeight;
|
||||
Rect sourceRect = new Rect(0f, 0f, 1f, 1f);
|
||||
switch (scaleMode)
|
||||
{
|
||||
case ScaleMode.ScaleAndCrop:
|
||||
{
|
||||
float destRatio = destRect.width / destRect.height;
|
||||
if (destRatio > sourceRatio)
|
||||
{
|
||||
float adjust = sourceRatio / destRatio;
|
||||
sourceRect = new Rect(0f, (1f - adjust) * 0.5f, 1f, adjust);
|
||||
}
|
||||
else
|
||||
{
|
||||
float adjust = destRatio / sourceRatio;
|
||||
sourceRect = new Rect(0.5f - adjust * 0.5f, 0f, adjust, 1f);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case ScaleMode.ScaleToFit:
|
||||
{
|
||||
float destRatio = destRect.width / destRect.height;
|
||||
if (destRatio > sourceRatio)
|
||||
{
|
||||
float adjust = sourceRatio / destRatio;
|
||||
destRect = new Rect(destRect.xMin + destRect.width * (1f - adjust) * 0.5f, destRect.yMin, adjust * destRect.width, destRect.height);
|
||||
}
|
||||
else
|
||||
{
|
||||
float adjust = destRatio / sourceRatio;
|
||||
destRect = new Rect(destRect.xMin, destRect.yMin + destRect.height * (1f - adjust) * 0.5f, destRect.width, adjust * destRect.height);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case ScaleMode.StretchToFill:
|
||||
break;
|
||||
}
|
||||
|
||||
GL.PushMatrix();
|
||||
if (RenderTexture.active == null)
|
||||
{
|
||||
//GL.LoadPixelMatrix();
|
||||
GL.LoadPixelMatrix(0f, Screen.width, Screen.height, 0f);
|
||||
}
|
||||
else
|
||||
{
|
||||
GL.LoadPixelMatrix(0f, RenderTexture.active.width, RenderTexture.active.height, 0f);
|
||||
}
|
||||
Graphics.DrawTexture(destRect, texture, sourceRect, 0, 0, 0, 0, GUI.color, material);
|
||||
GL.PopMatrix();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a928f61fef33d1d4986b2190310027bc
|
||||
timeCreated: 1547737745
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user