You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1443 lines
44 KiB
1443 lines
44 KiB
using System;
|
|
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using UnityEngine;
|
|
using UnityEngine.Events;
|
|
using System.IO;
|
|
using UMP.Services;
|
|
using UMP.Services.Youtube;
|
|
using UnityEngine.Networking;
|
|
#if UNITY_EDITOR
|
|
using UnityEditor;
|
|
#endif
|
|
|
|
namespace UMP
|
|
{
|
|
public class UniversalMediaPlayer : MonoBehaviour, IMediaListener, IPathPreparedListener, IPlayerPreparedListener, IPlayerTimeChangedListener, IPlayerPositionChangedListener, IPlayerSnapshotTakenListener
|
|
{
|
|
private const float DEFAULT_POSITION_CHANGED_OFFSET = 0.05f;
|
|
|
|
#region Editor Visible Properties
|
|
[SerializeField]
|
|
private GameObject[] _renderingObjects;
|
|
|
|
[SerializeField]
|
|
private string _path = string.Empty;
|
|
|
|
[SerializeField]
|
|
private bool _autoPlay = false;
|
|
|
|
[SerializeField]
|
|
private bool _loop = false;
|
|
|
|
[SerializeField]
|
|
private bool _loopSmooth = false;
|
|
|
|
[SerializeField]
|
|
private bool _mute = false;
|
|
|
|
#pragma warning disable 0414
|
|
[SerializeField]
|
|
private bool _useAdvanced = false;
|
|
|
|
[SerializeField]
|
|
private bool _useFixedSize = false;
|
|
|
|
[SerializeField]
|
|
private int _fixedVideoWidth = -1;
|
|
|
|
[SerializeField]
|
|
private int _fixedVideoHeight = -1;
|
|
|
|
[SerializeField]
|
|
private int _chosenPlatform = 0;
|
|
|
|
[SerializeField]
|
|
private int _volume = 50;
|
|
|
|
[SerializeField]
|
|
private float _playRate = 1;
|
|
|
|
[SerializeField]
|
|
private float _position = 0;
|
|
|
|
[SerializeField]
|
|
private LogLevels _logDetail = LogLevels.Disable;
|
|
|
|
[SerializeField]
|
|
private string _lastEventMsg = string.Empty;
|
|
#pragma warning restore 0414
|
|
|
|
#region Desktop Options
|
|
[SerializeField]
|
|
private AudioOutput[] _desktopAudioOutputs;
|
|
|
|
[SerializeField]
|
|
private PlayerOptions.States _desktopHardwareDecoding = PlayerOptions.States.Default;
|
|
|
|
[SerializeField]
|
|
private bool _desktopFlipVertically = true;
|
|
|
|
[SerializeField]
|
|
private bool _desktopVideoBufferSize = false;
|
|
|
|
[SerializeField]
|
|
private bool _desktopOutputToFile = false;
|
|
|
|
[SerializeField]
|
|
private bool _desktopDisplayOutput = false;
|
|
|
|
[SerializeField]
|
|
private string _desktopOutputFilePath = string.Empty;
|
|
|
|
[SerializeField]
|
|
private bool _desktopRtspOverTcp = false;
|
|
|
|
[SerializeField]
|
|
private int _desktopFileCaching = 300;
|
|
|
|
[SerializeField]
|
|
private int _desktopLiveCaching = 300;
|
|
|
|
[SerializeField]
|
|
private int _desktopDiskCaching = 300;
|
|
|
|
[SerializeField]
|
|
private int _desktopNetworkCaching = 300;
|
|
#endregion
|
|
|
|
#region Android Options
|
|
[SerializeField]
|
|
private PlayerOptionsAndroid.PlayerTypes _androidPlayerType = PlayerOptionsAndroid.PlayerTypes.LibVLC;
|
|
|
|
[SerializeField]
|
|
private PlayerOptionsAndroid.DecodingStates _androidHardwareAcceleration = PlayerOptionsAndroid.DecodingStates.Automatic;
|
|
|
|
[SerializeField]
|
|
private PlayerOptions.States _androidOpenGLDecoding = PlayerOptions.States.Disable;
|
|
|
|
[SerializeField]
|
|
private PlayerOptionsAndroid.ChromaTypes _androidVideoChroma = PlayerOptionsAndroid.ChromaTypes.RGB16Bit;
|
|
|
|
[SerializeField]
|
|
private bool _androidPlayInBackground = false;
|
|
|
|
[SerializeField]
|
|
private bool _androidRtspOverTcp = false;
|
|
|
|
[SerializeField]
|
|
private int _androidNetworkCaching = 300;
|
|
#endregion
|
|
|
|
#region IPhone Options
|
|
[SerializeField]
|
|
private PlayerOptionsIPhone.PlayerTypes _iphonePlayerType = PlayerOptionsIPhone.PlayerTypes.FFmpeg;
|
|
|
|
[SerializeField]
|
|
private bool _iphoneFlipVertically = true;
|
|
|
|
[SerializeField]
|
|
private bool _iphoneVideoToolbox = true;
|
|
|
|
[SerializeField]
|
|
private int _iphoneVideoToolboxMaxFrameWidth = 4096;
|
|
|
|
[SerializeField]
|
|
private bool _iphoneVideoToolboxAsync = false;
|
|
|
|
[SerializeField]
|
|
private bool _iphoneVideoToolboxWaitAsync = true;
|
|
|
|
[SerializeField]
|
|
private bool _iphonePlayInBackground = false;
|
|
|
|
[SerializeField]
|
|
private bool _iphoneRtspOverTcp = false;
|
|
|
|
[SerializeField]
|
|
private bool _iphonePacketBuffering = true;
|
|
|
|
[SerializeField]
|
|
private int _iphoneMaxBufferSize = 15 * 1024 * 1024;
|
|
|
|
[SerializeField]
|
|
private int _iphoneMinFrames = 50000;
|
|
|
|
[SerializeField]
|
|
private bool _iphoneInfbuf = false;
|
|
|
|
[SerializeField]
|
|
private int _iphoneFramedrop = 0;
|
|
|
|
[SerializeField]
|
|
private int _iphoneMaxFps = 31;
|
|
#endregion
|
|
|
|
[Serializable]
|
|
private class EventTextType : UnityEvent<string> { }
|
|
|
|
[Serializable]
|
|
private class EventFloatType : UnityEvent<float> { }
|
|
|
|
[Serializable]
|
|
private class EventLongType : UnityEvent<long> { }
|
|
|
|
[Serializable]
|
|
private class EventSizeType : UnityEvent<int, int> { }
|
|
|
|
[Serializable]
|
|
private class EventTextureType : UnityEvent<Texture2D> { }
|
|
|
|
[SerializeField]
|
|
private EventTextType _pathPreparedEvent = new EventTextType();
|
|
|
|
[SerializeField]
|
|
private UnityEvent _openingEvent = new UnityEvent();
|
|
|
|
[SerializeField]
|
|
private EventFloatType _bufferingEvent = new EventFloatType();
|
|
|
|
[SerializeField]
|
|
private EventTextureType _imageReadyEvent = new EventTextureType();
|
|
|
|
[SerializeField]
|
|
private EventSizeType _preparedEvent = new EventSizeType();
|
|
|
|
[SerializeField]
|
|
private UnityEvent _playingEvent = new UnityEvent();
|
|
|
|
[SerializeField]
|
|
private UnityEvent _pausedEvent = new UnityEvent();
|
|
|
|
[SerializeField]
|
|
private UnityEvent _stoppedEvent = new UnityEvent();
|
|
|
|
[SerializeField]
|
|
private UnityEvent _endReachedEvent = new UnityEvent();
|
|
|
|
[SerializeField]
|
|
private UnityEvent _encounteredErrorEvent = new UnityEvent();
|
|
|
|
[SerializeField]
|
|
private EventLongType _timeChangedEvent = new EventLongType();
|
|
|
|
[SerializeField]
|
|
private EventFloatType _positionChangedEvent = new EventFloatType();
|
|
|
|
[SerializeField]
|
|
private EventTextType _snapshotTakenEvent = new EventTextType();
|
|
#endregion
|
|
|
|
#region Properties
|
|
/// <summary>
|
|
/// Get/Set simple array that consist with Unity 'GameObject' that have 'Mesh Renderer' (with some material)
|
|
/// or 'Raw Image' component
|
|
/// </summary>
|
|
public GameObject[] RenderingObjects
|
|
{
|
|
get
|
|
{
|
|
if (_mediaPlayer != null)
|
|
return _mediaPlayer.VideoOutputObjects;
|
|
return null;
|
|
}
|
|
set
|
|
{
|
|
if (_mediaPlayer != null)
|
|
_mediaPlayer.VideoOutputObjects = value;
|
|
|
|
_renderingObjects = value;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get simple array that consist with Unity 'AudioSource' components
|
|
/// (will be used for audio data output instead default one)
|
|
/// * Warning: in current stage correctly working only with audio that has 2 channels
|
|
/// </summary>
|
|
public AudioOutput[] AudioOutputs
|
|
{
|
|
get
|
|
{
|
|
return _desktopAudioOutputs;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get media player object for current running platform
|
|
/// (supported: Standalone, WebGL, Android and iOS platforms)
|
|
/// for get more additional possibilities that exists only for this platform.
|
|
/// Example of using:
|
|
/// if (_mediaPlayer.PlatformPlayer is MediaPlayerStandalone)
|
|
/// return (_mediaPlayer.PlatformPlayer as MediaPlayerStandalone).GetLastError();
|
|
/// </summary>
|
|
public object PlatformPlayer
|
|
{
|
|
get
|
|
{
|
|
return _mediaPlayer != null ? _mediaPlayer.PlatformPlayer : null;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get/Set local path or url link to your video/audio file/stream
|
|
/// Example of using:
|
|
/// Local storage space - 'file:///C:\MyFolder\Videos\video1.mp4' or 'C:\MyFolder\Videos\video1.mp4' or
|
|
/// 'file:///DCIM/100ANDRO/MyVideo.mp4' (example for Android platform);
|
|
/// Remote space (streams) - 'rtsp://wowzaec2demo.streamlock.net/vod/mp4:BigBuckBunny_115k.mov';
|
|
/// 'StreamingAssets' folder - 'file:///myVideoFile.mp4';
|
|
/// </summary>
|
|
public string Path
|
|
{
|
|
set { _path = value; }
|
|
get { return _path; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get/Set start playback automatically after video is buffered
|
|
/// </summary>
|
|
public bool AutoPlay
|
|
{
|
|
set { _autoPlay = value; }
|
|
get { return _autoPlay; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get/Set jumps to the start and plays again if playback reaches the end position
|
|
/// </summary>
|
|
public bool Loop
|
|
{
|
|
set { _loop = value; }
|
|
get { return _loop; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get/Set mute status for current video playback
|
|
/// </summary>
|
|
public bool Mute
|
|
{
|
|
set
|
|
{
|
|
_mediaPlayer.Mute = value;
|
|
_mute = value;
|
|
}
|
|
get { return _mediaPlayer.Mute; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get/Set current software audio volume
|
|
/// (by default you can change this value from '0' to '100')
|
|
/// </summary>
|
|
public float Volume
|
|
{
|
|
set
|
|
{
|
|
_mediaPlayer.Volume = (int)value;
|
|
_volume = (int)value;
|
|
}
|
|
get { return _mediaPlayer.Volume; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get/Set video position.
|
|
/// This has no effect if playback is not enabled.
|
|
/// This might not work depending on the underlying input format and protocol
|
|
/// </summary>
|
|
public float Position
|
|
{
|
|
set { _mediaPlayer.Position = value; }
|
|
get { return _mediaPlayer.Position; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get/Set the current video time (in milliseconds).
|
|
/// This has no effect if no media is being played.
|
|
/// Not all formats and protocols support this
|
|
/// </summary>
|
|
public long Time
|
|
{
|
|
set
|
|
{
|
|
if (_mediaPlayer != null)
|
|
_mediaPlayer.Time = value;
|
|
}
|
|
get
|
|
{
|
|
if (_mediaPlayer != null)
|
|
return _mediaPlayer.Time;
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get/Set the requested video play rate
|
|
/// </summary>
|
|
public float PlayRate
|
|
{
|
|
set
|
|
{
|
|
_mediaPlayer.PlaybackRate = value;
|
|
_playRate = value;
|
|
}
|
|
get { return _mediaPlayer.PlaybackRate; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Is the player able to play
|
|
/// </summary>
|
|
public bool AbleToPlay
|
|
{
|
|
get
|
|
{
|
|
if (_mediaPlayer != null)
|
|
return _mediaPlayer.AbleToPlay;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Is media is currently playing
|
|
/// </summary>
|
|
public bool IsPlaying
|
|
{
|
|
get
|
|
{
|
|
if (_mediaPlayer != null)
|
|
return _mediaPlayer.IsPlaying;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Is media is ready to play (first frame available)
|
|
/// </summary>
|
|
public bool IsReady
|
|
{
|
|
get
|
|
{
|
|
if (_mediaPlayer != null)
|
|
return _mediaPlayer.IsReady;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Is current media is parsing from video hosting service (in our case Youtube)
|
|
/// </summary>
|
|
public bool IsParsing
|
|
{
|
|
get
|
|
{
|
|
return _isParsing;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get frames per second (fps) for current video playback.
|
|
/// * Warning: it's not a predefined value from video file/stream - calculated in video playback process
|
|
/// </summary>
|
|
public float FrameRate
|
|
{
|
|
get
|
|
{
|
|
if (_mediaPlayer != null)
|
|
return _mediaPlayer.FrameRate;
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get video frames counter
|
|
/// </summary>
|
|
public long FramesCounter
|
|
{
|
|
get
|
|
{
|
|
if (_mediaPlayer != null)
|
|
return _mediaPlayer.FramesCounter;
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get pixels of current video frame
|
|
/// Example of using:
|
|
/// texture.LoadRawTextureData(_player.FramePixels);
|
|
/// texture.Apply();
|
|
/// </summary>
|
|
public byte[] FramePixels
|
|
{
|
|
get
|
|
{
|
|
if (_mediaPlayer != null)
|
|
return _mediaPlayer.FramePixels;
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get the current video length (in milliseconds)
|
|
/// </summary>
|
|
public long Length
|
|
{
|
|
get
|
|
{
|
|
if (_mediaPlayer != null)
|
|
return _mediaPlayer.Length;
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get current video width in pixels
|
|
/// </summary>
|
|
public int VideoWidth
|
|
{
|
|
get
|
|
{
|
|
if (_mediaPlayer != null)
|
|
return _mediaPlayer.VideoWidth;
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get current video height in pixels
|
|
/// </summary>
|
|
public int VideoHeight
|
|
{
|
|
get
|
|
{
|
|
if (_mediaPlayer != null)
|
|
return _mediaPlayer.VideoHeight;
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get the pixel dimensions of current video
|
|
/// </summary>
|
|
public Vector2 VideoSize
|
|
{
|
|
get
|
|
{
|
|
if (_mediaPlayer != null)
|
|
return _mediaPlayer.VideoSize;
|
|
return new Vector2(0, 0);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get/Set the current audio track
|
|
/// </summary>
|
|
public MediaTrackInfo AudioTrack
|
|
{
|
|
set
|
|
{
|
|
if (_mediaPlayer != null)
|
|
_mediaPlayer.AudioTrack = value;
|
|
}
|
|
get
|
|
{
|
|
if (_mediaPlayer != null)
|
|
return _mediaPlayer.AudioTrack;
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get the available audio tracks
|
|
/// </summary>
|
|
public MediaTrackInfo[] AudioTracks
|
|
{
|
|
get
|
|
{
|
|
if (_mediaPlayer != null)
|
|
return _mediaPlayer.AudioTracks;
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get/Set the current spu (subtitle) track (supported only on Standalone platform)
|
|
/// </summary>
|
|
public MediaTrackInfo SpuTrack
|
|
{
|
|
set
|
|
{
|
|
if (_mediaPlayer != null)
|
|
_mediaPlayer.SpuTrack = value;
|
|
}
|
|
get
|
|
{
|
|
if (_mediaPlayer != null)
|
|
return _mediaPlayer.SpuTrack;
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the available spu (subtitle) tracks (supported only on Standalone platform)
|
|
/// </summary>
|
|
public MediaTrackInfo[] SpuTracks
|
|
{
|
|
get
|
|
{
|
|
if (_mediaPlayer != null)
|
|
return _mediaPlayer.SpuTracks;
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get event manager for current media player to add possibility to attach/detach special playback listeners
|
|
/// </summary>
|
|
public PlayerManagerEvents EventManager
|
|
{
|
|
get
|
|
{
|
|
if (_mediaPlayer != null)
|
|
return _mediaPlayer.EventManager;
|
|
return null;
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
private MediaPlayer _mediaPlayer;
|
|
private MediaPlayer _mediaPlayerLoop;
|
|
private VideoServices _videoServices;
|
|
|
|
private string _tmpPath = string.Empty;
|
|
private bool _isParsing;
|
|
private static bool _isExportCompleted;
|
|
private static Dictionary<string, string> _cachedVideoPaths = new Dictionary<string, string>();
|
|
|
|
private static IEnumerator _exportedHandlerEnum;
|
|
private IEnumerator _videoPathPreparingEnum;
|
|
|
|
#pragma warning disable 0414
|
|
private bool _isFirstEditorStateChange = true;
|
|
#pragma warning restore 0414
|
|
|
|
private void Awake()
|
|
{
|
|
#if UNITY_EDITOR
|
|
#if UNITY_4 || UNITY_5 || UNITY_2017_1
|
|
EditorApplication.playmodeStateChanged += HandleOnPlayModeChanged;
|
|
#else
|
|
EditorApplication.playModeStateChanged += HandleOnPlayModeChanged;
|
|
#endif
|
|
#endif
|
|
|
|
if (UMPSettings.Instance.UseAudioSource && (_desktopAudioOutputs == null || _desktopAudioOutputs.Length <= 0))
|
|
{
|
|
var audioOutput = gameObject.AddComponent<UMPAudioOutput>();
|
|
_desktopAudioOutputs = new UMPAudioOutput[] { audioOutput };
|
|
}
|
|
|
|
PlayerOptions options = new PlayerOptions(null);
|
|
|
|
switch (UMPSettings.RuntimePlatform)
|
|
{
|
|
case UMPSettings.Platforms.Win:
|
|
case UMPSettings.Platforms.Mac:
|
|
case UMPSettings.Platforms.Linux:
|
|
var standaloneOptions = new PlayerOptionsStandalone(null)
|
|
{
|
|
FixedVideoSize = _useFixedSize ? new Vector2(_fixedVideoWidth, _fixedVideoHeight) : Vector2.zero,
|
|
AudioOutputs = _desktopAudioOutputs,
|
|
//DirectAudioDevice = "Digital Audio",
|
|
HardwareDecoding = _desktopHardwareDecoding,
|
|
FlipVertically = _desktopFlipVertically,
|
|
VideoBufferSize = _desktopVideoBufferSize,
|
|
UseTCP = _desktopRtspOverTcp,
|
|
FileCaching = _desktopFileCaching,
|
|
LiveCaching = _desktopLiveCaching,
|
|
DiskCaching = _desktopDiskCaching,
|
|
NetworkCaching = _desktopNetworkCaching
|
|
};
|
|
|
|
if (_desktopOutputToFile)
|
|
standaloneOptions.RedirectToFile(_desktopDisplayOutput, _desktopOutputFilePath);
|
|
|
|
standaloneOptions.SetLogDetail(_logDetail, UnityConsoleLogging);
|
|
options = standaloneOptions;
|
|
break;
|
|
|
|
case UMPSettings.Platforms.Android:
|
|
var androidOptions = new PlayerOptionsAndroid(null)
|
|
{
|
|
FixedVideoSize = _useFixedSize ? new Vector2(_fixedVideoWidth, _fixedVideoHeight) : Vector2.zero,
|
|
PlayerType = _androidPlayerType,
|
|
HardwareAcceleration = _androidHardwareAcceleration,
|
|
OpenGLDecoding = _androidOpenGLDecoding,
|
|
VideoChroma = _androidVideoChroma,
|
|
PlayInBackground = _androidPlayInBackground,
|
|
UseTCP = _androidRtspOverTcp,
|
|
NetworkCaching = _androidNetworkCaching
|
|
};
|
|
|
|
options = androidOptions;
|
|
|
|
if (_exportedHandlerEnum == null)
|
|
{
|
|
_exportedHandlerEnum = AndroidExpoterdHandler();
|
|
StartCoroutine(_exportedHandlerEnum);
|
|
}
|
|
|
|
break;
|
|
|
|
case UMPSettings.Platforms.iOS:
|
|
var iphoneOptions = new PlayerOptionsIPhone(null)
|
|
{
|
|
FixedVideoSize = _useFixedSize ? new Vector2(_fixedVideoWidth, _fixedVideoHeight) : Vector2.zero,
|
|
PlayerType = _iphonePlayerType,
|
|
FlipVertically = _iphoneFlipVertically,
|
|
VideoToolbox = _iphoneVideoToolbox,
|
|
VideoToolboxFrameWidth = _iphoneVideoToolboxMaxFrameWidth,
|
|
VideoToolboxAsync = _iphoneVideoToolboxAsync,
|
|
VideoToolboxWaitAsync = _iphoneVideoToolboxWaitAsync,
|
|
PlayInBackground = _iphonePlayInBackground,
|
|
UseTCP = _iphoneRtspOverTcp,
|
|
PacketBuffering = _iphonePacketBuffering,
|
|
MaxBufferSize = _iphoneMaxBufferSize,
|
|
MinFrames = _iphoneMinFrames,
|
|
Infbuf = _iphoneInfbuf,
|
|
Framedrop = _iphoneFramedrop,
|
|
MaxFps = _iphoneMaxFps
|
|
};
|
|
|
|
options = iphoneOptions;
|
|
break;
|
|
}
|
|
|
|
_mediaPlayer = new MediaPlayer(this, _renderingObjects, options);
|
|
|
|
// Create scpecial parser to add possibiity of get video link from different video hosting servies (like youtube)
|
|
_videoServices = new VideoServices(this);
|
|
|
|
// Attach scecial listeners to MediaPlayer instance
|
|
AddListeners();
|
|
// Create additional media player for add smooth loop possibility
|
|
if (_loopSmooth)
|
|
{
|
|
_mediaPlayerLoop = new MediaPlayer(this, _mediaPlayer);
|
|
_mediaPlayerLoop.VideoOutputObjects = null;
|
|
_mediaPlayerLoop.EventManager.RemoveAllEvents();
|
|
}
|
|
}
|
|
|
|
private IEnumerator AndroidExpoterdHandler()
|
|
{
|
|
var settings = UMPSettings.Instance;
|
|
|
|
foreach (var exportedPath in settings.AndroidExportedPaths)
|
|
{
|
|
var tempFilePath = System.IO.Path.Combine(Application.temporaryCachePath, exportedPath);
|
|
var saPath = "Assets" + System.IO.Path.AltDirectorySeparatorChar + "StreamingAssets" + System.IO.Path.AltDirectorySeparatorChar;
|
|
var localFilePath = exportedPath.Replace(saPath, "");
|
|
|
|
if (File.Exists(tempFilePath))
|
|
{
|
|
_cachedVideoPaths.Add(localFilePath, tempFilePath);
|
|
continue;
|
|
}
|
|
|
|
var data = new byte[0];
|
|
|
|
#if UNITY_2017_2_OR_NEWER
|
|
var www = UnityWebRequest.Get(System.IO.Path.Combine(Application.streamingAssetsPath, localFilePath));
|
|
yield return www.SendWebRequest();
|
|
data = www.downloadHandler.data;
|
|
#else
|
|
var www = new WWW(System.IO.Path.Combine(Application.streamingAssetsPath, localFilePath));
|
|
yield return www;
|
|
data = www.bytes;
|
|
#endif
|
|
|
|
if (string.IsNullOrEmpty(www.error))
|
|
{
|
|
var tempFile = new FileInfo(tempFilePath);
|
|
tempFile.Directory.Create();
|
|
File.WriteAllBytes(tempFile.FullName, data);
|
|
_cachedVideoPaths.Add(localFilePath, tempFilePath);
|
|
}
|
|
else
|
|
{
|
|
Debug.LogError("Can't create temp file from asset folder: " + www.error);
|
|
}
|
|
|
|
www.Dispose();
|
|
}
|
|
|
|
_isExportCompleted = true;
|
|
}
|
|
|
|
#region Editor Additional Possibility
|
|
#if UNITY_EDITOR
|
|
private bool _cachedPlayState;
|
|
|
|
#if UNITY_4 || UNITY_5 || UNITY_2017_1
|
|
//Used for defined in new Unity version 'playmodeStateChanged' method
|
|
private void HandleOnPlayModeChanged()
|
|
#else
|
|
//Used for new 'playModeStateChanged' method that available in new Unity 2017.2+ version
|
|
private void HandleOnPlayModeChanged(PlayModeStateChange modeState)
|
|
#endif
|
|
{
|
|
if (_isFirstEditorStateChange)
|
|
{
|
|
_isFirstEditorStateChange = false;
|
|
return;
|
|
}
|
|
|
|
if (_mediaPlayer == null)
|
|
return;
|
|
|
|
if (EditorApplication.isPaused)
|
|
{
|
|
_cachedPlayState = _mediaPlayer.IsPlaying;
|
|
Pause();
|
|
}
|
|
else
|
|
{
|
|
if (!isActiveAndEnabled)
|
|
{
|
|
Stop();
|
|
return;
|
|
}
|
|
|
|
if (_cachedPlayState)
|
|
{
|
|
_mediaPlayer.Play();
|
|
}
|
|
else
|
|
{
|
|
Pause();
|
|
}
|
|
}
|
|
}
|
|
|
|
private void OnValidate()
|
|
{
|
|
if (_mediaPlayer != null && _mediaPlayer.IsReady)
|
|
{
|
|
if (_mediaPlayer.Mute != _mute)
|
|
_mediaPlayer.Mute = _mute;
|
|
|
|
if (_mediaPlayer.Volume != _volume)
|
|
_mediaPlayer.Volume = _volume;
|
|
|
|
if (_mediaPlayer.PlaybackRate != _playRate)
|
|
_mediaPlayer.PlaybackRate = _playRate;
|
|
|
|
if (_position > _mediaPlayer.Position + DEFAULT_POSITION_CHANGED_OFFSET ||
|
|
_position < _mediaPlayer.Position - DEFAULT_POSITION_CHANGED_OFFSET)
|
|
{
|
|
_mediaPlayer.Position = _position;
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
#endregion
|
|
|
|
private void Start()
|
|
{
|
|
if (!_autoPlay)
|
|
return;
|
|
|
|
Play();
|
|
}
|
|
|
|
private void OnDisable()
|
|
{
|
|
if (_mediaPlayer != null && _mediaPlayer.IsPlaying)
|
|
{
|
|
Stop();
|
|
}
|
|
}
|
|
|
|
private void OnDestroy()
|
|
{
|
|
#if UNITY_EDITOR
|
|
#if UNITY_4 || UNITY_5 || UNITY_2017_1
|
|
EditorApplication.playmodeStateChanged -= HandleOnPlayModeChanged;
|
|
#else
|
|
EditorApplication.playModeStateChanged -= HandleOnPlayModeChanged;
|
|
#endif
|
|
#endif
|
|
|
|
if (_mediaPlayer != null)
|
|
{
|
|
// Release MediaPlayer
|
|
Release();
|
|
}
|
|
}
|
|
|
|
private void AddListeners()
|
|
{
|
|
if (_mediaPlayer == null || _mediaPlayer.EventManager == null)
|
|
return;
|
|
|
|
// Add to MediaPlayer new main group of listeners
|
|
_mediaPlayer.AddMediaListener(this);
|
|
// Add to MediaPlayer new "OnPlayerTimeChanged" listener
|
|
_mediaPlayer.EventManager.PlayerTimeChangedListener += OnPlayerTimeChanged;
|
|
// Add to MediaPlayer new "OnPlayerPositionChanged" listener
|
|
_mediaPlayer.EventManager.PlayerPositionChangedListener += OnPlayerPositionChanged;
|
|
// Add to MediaPlayer new "OnPlayerSnapshotTaken" listener
|
|
_mediaPlayer.EventManager.PlayerSnapshotTakenListener += OnPlayerSnapshotTaken;
|
|
}
|
|
|
|
private void RemoveListeners()
|
|
{
|
|
if (_mediaPlayer == null)
|
|
return;
|
|
|
|
// Remove from MediaPlayer the main group of listeners
|
|
_mediaPlayer.RemoveMediaListener(this);
|
|
// Remove from MediaPlayer "OnPlayerTimeChanged" listener
|
|
_mediaPlayer.EventManager.PlayerTimeChangedListener -= OnPlayerTimeChanged;
|
|
// Remove from MediaPlayer "OnPlayerPositionChanged" listener
|
|
_mediaPlayer.EventManager.PlayerPositionChangedListener -= OnPlayerPositionChanged;
|
|
// Remove from MediaPlayer new "OnPlayerSnapshotTaken" listener
|
|
_mediaPlayer.EventManager.PlayerSnapshotTakenListener -= OnPlayerSnapshotTaken;
|
|
}
|
|
|
|
private IEnumerator VideoPathPreparing(string path, bool playImmediately, IPathPreparedListener listener)
|
|
{
|
|
if (_cachedVideoPaths.ContainsKey(path))
|
|
{
|
|
listener.OnPathPrepared(_cachedVideoPaths[path], playImmediately);
|
|
yield break;
|
|
}
|
|
|
|
#if UNITY_EDITOR
|
|
_lastEventMsg = "Path Preparing";
|
|
#endif
|
|
|
|
if (UMPSettings.RuntimePlatform == UMPSettings.Platforms.Android)
|
|
{
|
|
/// Check if we try to play exported videos and wait when export process will be completed
|
|
var exptPaths = UMPSettings.Instance.AndroidExportedPaths;
|
|
var filePath = path.Replace("file:///", "");
|
|
|
|
foreach (var exptPath in exptPaths)
|
|
{
|
|
if (exptPath.Contains(filePath))
|
|
{
|
|
while (!_isExportCompleted)
|
|
yield return null;
|
|
|
|
if (_cachedVideoPaths.ContainsKey(filePath))
|
|
{
|
|
listener.OnPathPrepared(_cachedVideoPaths[filePath], playImmediately);
|
|
yield break;
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
if ((_mediaPlayer.Options as PlayerOptionsAndroid).PlayerType ==
|
|
PlayerOptionsAndroid.PlayerTypes.LibVLC
|
|
&& MediaPlayerHelper.IsAssetsFile(path))
|
|
{
|
|
var tempFilePath = System.IO.Path.Combine(Application.temporaryCachePath, filePath);
|
|
if (File.Exists(tempFilePath))
|
|
{
|
|
_cachedVideoPaths.Add(path, tempFilePath);
|
|
listener.OnPathPrepared(tempFilePath, playImmediately);
|
|
yield break;
|
|
}
|
|
|
|
var data = new byte[0];
|
|
|
|
#if UNITY_2017_2_OR_NEWER
|
|
var www = UnityWebRequest.Get(System.IO.Path.Combine(Application.streamingAssetsPath, filePath));
|
|
yield return www.SendWebRequest();
|
|
data = www.downloadHandler.data;
|
|
#else
|
|
var www = new WWW(System.IO.Path.Combine(Application.streamingAssetsPath, filePath));
|
|
yield return www;
|
|
data = www.bytes;
|
|
#endif
|
|
if (string.IsNullOrEmpty(www.error))
|
|
{
|
|
var tempFile = new FileInfo(tempFilePath);
|
|
tempFile.Directory.Create();
|
|
File.WriteAllBytes(tempFile.FullName, data);
|
|
_cachedVideoPaths.Add(path, tempFilePath);
|
|
path = tempFilePath;
|
|
}
|
|
else
|
|
{
|
|
Debug.LogError("Can't create temp file from asset folder: " + www.error);
|
|
}
|
|
|
|
www.Dispose();
|
|
}
|
|
}
|
|
|
|
if (_videoServices.ValidUrl(path))
|
|
{
|
|
Video serviceVideo = null;
|
|
_isParsing = true;
|
|
|
|
yield return _videoServices.GetVideos(path, (videos) =>
|
|
{
|
|
_isParsing = false;
|
|
serviceVideo = VideoServices.FindVideo(videos, int.MaxValue, int.MaxValue);
|
|
}, (error) =>
|
|
{
|
|
_isParsing = false;
|
|
Debug.LogError(string.Format("[UniversalMediaPlayer.GetVideos] {0}", error));
|
|
OnPlayerEncounteredError();
|
|
});
|
|
|
|
if (serviceVideo == null)
|
|
{
|
|
Debug.LogError("[UniversalMediaPlayer.VideoPathPreparing] Can't get service video information");
|
|
OnPlayerEncounteredError();
|
|
}
|
|
else
|
|
{
|
|
if (serviceVideo is YoutubeVideo)
|
|
{
|
|
yield return (serviceVideo as YoutubeVideo).Decrypt(UMPSettings.Instance.YoutubeDecryptFunction, (error) =>
|
|
{
|
|
Debug.LogError(string.Format("[UniversalMediaPlayer.Decrypt] {0}", error));
|
|
OnPlayerEncounteredError();
|
|
});
|
|
}
|
|
|
|
path = serviceVideo.Url;
|
|
}
|
|
}
|
|
|
|
listener.OnPathPrepared(path, playImmediately);
|
|
yield return null;
|
|
}
|
|
|
|
public void Prepare()
|
|
{
|
|
if (_videoPathPreparingEnum != null)
|
|
StopCoroutine(_videoPathPreparingEnum);
|
|
|
|
_videoPathPreparingEnum = VideoPathPreparing(_path, false, this);
|
|
StartCoroutine(_videoPathPreparingEnum);
|
|
}
|
|
|
|
public void Play()
|
|
{
|
|
if (_videoPathPreparingEnum != null)
|
|
StopCoroutine(_videoPathPreparingEnum);
|
|
|
|
_videoPathPreparingEnum = VideoPathPreparing(_path, true, this);
|
|
StartCoroutine(_videoPathPreparingEnum);
|
|
}
|
|
|
|
public void Pause()
|
|
{
|
|
if (_mediaPlayer == null)
|
|
return;
|
|
|
|
if (_mediaPlayer.IsPlaying)
|
|
_mediaPlayer.Pause();
|
|
}
|
|
|
|
public void Stop()
|
|
{
|
|
Stop(true);
|
|
}
|
|
|
|
public void Stop(bool clearVideoTexture)
|
|
{
|
|
#if UNITY_EDITOR
|
|
if (EditorApplication.isPaused)
|
|
return;
|
|
#endif
|
|
if (_videoPathPreparingEnum != null)
|
|
StopCoroutine(_videoPathPreparingEnum);
|
|
|
|
_position = 0;
|
|
|
|
if (_mediaPlayer == null)
|
|
return;
|
|
|
|
_mediaPlayer.Stop(clearVideoTexture);
|
|
|
|
if (_mediaPlayerLoop != null)
|
|
_mediaPlayerLoop.Stop(clearVideoTexture);
|
|
}
|
|
|
|
public void Release()
|
|
{
|
|
Stop();
|
|
|
|
if (_mediaPlayer != null)
|
|
{
|
|
// Release MediaPlayer
|
|
_mediaPlayer.Release();
|
|
_mediaPlayer = null;
|
|
|
|
if (_mediaPlayerLoop != null)
|
|
_mediaPlayerLoop.Release();
|
|
|
|
RemoveListeners();
|
|
|
|
_openingEvent.RemoveAllListeners();
|
|
_bufferingEvent.RemoveAllListeners();
|
|
_imageReadyEvent.RemoveAllListeners();
|
|
_preparedEvent.RemoveAllListeners();
|
|
_playingEvent.RemoveAllListeners();
|
|
_pausedEvent.RemoveAllListeners();
|
|
_stoppedEvent.RemoveAllListeners();
|
|
_endReachedEvent.RemoveAllListeners();
|
|
_encounteredErrorEvent.RemoveAllListeners();
|
|
_timeChangedEvent.RemoveAllListeners();
|
|
_positionChangedEvent.RemoveAllListeners();
|
|
_snapshotTakenEvent.RemoveAllListeners();
|
|
}
|
|
}
|
|
|
|
public string GetFormattedLength(bool detail)
|
|
{
|
|
if (_mediaPlayer != null)
|
|
return _mediaPlayer.GetFormattedLength(detail);
|
|
return string.Empty;
|
|
}
|
|
|
|
public void Snapshot(string path)
|
|
{
|
|
#if UNITY_EDITOR
|
|
if (EditorApplication.isPaused)
|
|
return;
|
|
#endif
|
|
|
|
if (_mediaPlayer == null)
|
|
return;
|
|
|
|
if (_mediaPlayer.AbleToPlay)
|
|
{
|
|
if (_mediaPlayer.PlatformPlayer is MediaPlayerStandalone)
|
|
(_mediaPlayer.PlatformPlayer as MediaPlayerStandalone).TakeSnapShot(path);
|
|
#if UNITY_EDITOR
|
|
Debug.Log("Snapshot path: " + path);
|
|
#endif
|
|
}
|
|
}
|
|
|
|
private void UnityConsoleLogging(PlayerManagerLogs.PlayerLog args)
|
|
{
|
|
if (args.Level != _logDetail)
|
|
return;
|
|
|
|
Debug.Log(args.Level.ToString() + ": " + args.Message);
|
|
}
|
|
|
|
public void OnPathPrepared(string path, bool playImmediately)
|
|
{
|
|
#if UNITY_EDITOR
|
|
if (EditorApplication.isPaused)
|
|
return;
|
|
#endif
|
|
|
|
_mediaPlayer.Mute = _mute;
|
|
_mediaPlayer.Volume = _volume;
|
|
_mediaPlayer.PlaybackRate = _playRate;
|
|
|
|
if (!_path.Equals(_tmpPath))
|
|
{
|
|
if (IsPlaying)
|
|
Stop();
|
|
|
|
_tmpPath = _path;
|
|
_mediaPlayer.DataSource = path;
|
|
}
|
|
|
|
if (!playImmediately)
|
|
_mediaPlayer.Prepare();
|
|
else
|
|
_mediaPlayer.Play();
|
|
|
|
if (_mediaPlayerLoop != null && !_mediaPlayerLoop.IsReady)
|
|
{
|
|
_mediaPlayerLoop.DataSource = _mediaPlayer.DataSource;
|
|
_mediaPlayerLoop.Prepare();
|
|
}
|
|
|
|
if (_pathPreparedEvent != null)
|
|
_pathPreparedEvent.Invoke(path);
|
|
}
|
|
|
|
public void AddPathPreparedEvent(UnityAction<string> action)
|
|
{
|
|
_pathPreparedEvent.AddListener(action);
|
|
}
|
|
|
|
public void RemovePathPreparedEvent(UnityAction<string> action)
|
|
{
|
|
_pathPreparedEvent.RemoveListener(action);
|
|
}
|
|
|
|
public void OnPlayerOpening()
|
|
{
|
|
#if UNITY_EDITOR
|
|
_lastEventMsg = "Opening";
|
|
#endif
|
|
if (_openingEvent != null)
|
|
_openingEvent.Invoke();
|
|
}
|
|
|
|
public void AddOpeningEvent(UnityAction action)
|
|
{
|
|
_openingEvent.AddListener(action);
|
|
}
|
|
|
|
public void RemoveOpeningEvent(UnityAction action)
|
|
{
|
|
_openingEvent.RemoveListener(action);
|
|
}
|
|
|
|
public void OnPlayerBuffering(float percentage)
|
|
{
|
|
#if UNITY_EDITOR
|
|
_lastEventMsg = "Buffering: " + percentage;
|
|
#endif
|
|
if (_bufferingEvent != null)
|
|
_bufferingEvent.Invoke(percentage);
|
|
}
|
|
|
|
public void AddBufferingEvent(UnityAction<float> action)
|
|
{
|
|
_bufferingEvent.AddListener(action);
|
|
}
|
|
|
|
public void RemoveBufferingEvent(UnityAction<float> action)
|
|
{
|
|
_bufferingEvent.RemoveListener(action);
|
|
}
|
|
|
|
public void OnPlayerImageReady(Texture2D image)
|
|
{
|
|
#if UNITY_EDITOR
|
|
_lastEventMsg = "ImageReady";
|
|
#endif
|
|
|
|
if (_imageReadyEvent != null)
|
|
_imageReadyEvent.Invoke(image);
|
|
}
|
|
|
|
public void AddImageReadyEvent(UnityAction<Texture2D> action)
|
|
{
|
|
_imageReadyEvent.AddListener(action);
|
|
}
|
|
|
|
public void RemoveImageReadyEvent(UnityAction<Texture2D> action)
|
|
{
|
|
_imageReadyEvent.RemoveListener(action);
|
|
}
|
|
|
|
public void OnPlayerPrepared(int videoWidth, int videoHeight)
|
|
{
|
|
#if UNITY_EDITOR
|
|
_lastEventMsg = "Prepared";
|
|
#endif
|
|
|
|
_mediaPlayer.Mute = _mute;
|
|
_mediaPlayer.Volume = _volume;
|
|
_mediaPlayer.PlaybackRate = _playRate;
|
|
|
|
if (_preparedEvent != null)
|
|
_preparedEvent.Invoke(videoWidth, videoHeight);
|
|
}
|
|
|
|
public void AddPreparedEvent(UnityAction<int, int> action)
|
|
{
|
|
_preparedEvent.AddListener(action);
|
|
}
|
|
|
|
public void RemovePreparedEvent(UnityAction<int, int> action)
|
|
{
|
|
_preparedEvent.RemoveListener(action);
|
|
}
|
|
|
|
public void OnPlayerPlaying()
|
|
{
|
|
#if UNITY_EDITOR
|
|
_lastEventMsg = "Playing";
|
|
#endif
|
|
if (_playingEvent != null)
|
|
_playingEvent.Invoke();
|
|
}
|
|
|
|
public void AddPlayingEvent(UnityAction action)
|
|
{
|
|
_playingEvent.AddListener(action);
|
|
}
|
|
|
|
public void RemovePlayingEvent(UnityAction action)
|
|
{
|
|
_playingEvent.RemoveListener(action);
|
|
}
|
|
|
|
public void OnPlayerPaused()
|
|
{
|
|
#if UNITY_EDITOR
|
|
_lastEventMsg = "Paused";
|
|
#endif
|
|
if (_pausedEvent != null)
|
|
_pausedEvent.Invoke();
|
|
}
|
|
|
|
public void AddPausedEvent(UnityAction action)
|
|
{
|
|
_pausedEvent.AddListener(action);
|
|
}
|
|
|
|
public void RemovePausedEvent(UnityAction action)
|
|
{
|
|
_pausedEvent.RemoveListener(action);
|
|
}
|
|
|
|
public void OnPlayerStopped()
|
|
{
|
|
#if UNITY_EDITOR
|
|
if (!_lastEventMsg.Contains("Error"))
|
|
_lastEventMsg = "Stopped";
|
|
#endif
|
|
if (_stoppedEvent != null)
|
|
_stoppedEvent.Invoke();
|
|
}
|
|
|
|
public void AddStoppedEvent(UnityAction action)
|
|
{
|
|
_stoppedEvent.AddListener(action);
|
|
}
|
|
|
|
public void RemoveStoppedEvent(UnityAction action)
|
|
{
|
|
_stoppedEvent.RemoveListener(action);
|
|
}
|
|
|
|
public void OnPlayerEndReached()
|
|
{
|
|
#if UNITY_EDITOR
|
|
_lastEventMsg = "End";
|
|
#endif
|
|
|
|
_position = 0;
|
|
_mediaPlayer.Stop(!_loop);
|
|
|
|
if (_loop)
|
|
{
|
|
if (_mediaPlayerLoop != null)
|
|
{
|
|
_mediaPlayerLoop.EventManager.CopyPlayerEvents(_mediaPlayer.EventManager);
|
|
_mediaPlayerLoop.VideoOutputObjects = _mediaPlayer.VideoOutputObjects;
|
|
_mediaPlayer.VideoOutputObjects = null;
|
|
_mediaPlayer.EventManager.RemoveAllEvents();
|
|
|
|
var tempPlayer = _mediaPlayer;
|
|
_mediaPlayer = _mediaPlayerLoop;
|
|
_mediaPlayerLoop = tempPlayer;
|
|
}
|
|
|
|
if (!string.IsNullOrEmpty(_path))
|
|
Play();
|
|
}
|
|
|
|
if (_endReachedEvent != null)
|
|
_endReachedEvent.Invoke();
|
|
}
|
|
|
|
public void AddEndReachedEvent(UnityAction action)
|
|
{
|
|
_endReachedEvent.AddListener(action);
|
|
}
|
|
|
|
public void RemoveEndReachedEvent(UnityAction action)
|
|
{
|
|
_endReachedEvent.RemoveListener(action);
|
|
}
|
|
|
|
public void OnPlayerEncounteredError()
|
|
{
|
|
#if UNITY_EDITOR
|
|
_lastEventMsg = "Error (" + (_mediaPlayer.PlatformPlayer as MediaPlayerStandalone).GetLastError() + ")";
|
|
#endif
|
|
Stop();
|
|
|
|
if (_encounteredErrorEvent != null)
|
|
_encounteredErrorEvent.Invoke();
|
|
}
|
|
|
|
public void AddEncounteredErrorEvent(UnityAction action)
|
|
{
|
|
_encounteredErrorEvent.AddListener(action);
|
|
}
|
|
|
|
public void RemoveEncounteredErrorEvent(UnityAction action)
|
|
{
|
|
_encounteredErrorEvent.RemoveListener(action);
|
|
}
|
|
|
|
public void OnPlayerTimeChanged(long time)
|
|
{
|
|
#if UNITY_EDITOR
|
|
_lastEventMsg = "TimeChanged";
|
|
#endif
|
|
|
|
if (_timeChangedEvent != null)
|
|
_timeChangedEvent.Invoke(time);
|
|
}
|
|
|
|
public void AddTimeChangedEvent(UnityAction<long> action)
|
|
{
|
|
_timeChangedEvent.AddListener(action);
|
|
}
|
|
|
|
public void RemoveTimeChangedEvent(UnityAction<long> action)
|
|
{
|
|
_timeChangedEvent.RemoveListener(action);
|
|
}
|
|
|
|
public void OnPlayerPositionChanged(float position)
|
|
{
|
|
#if UNITY_EDITOR
|
|
_lastEventMsg = "PositionChanged";
|
|
#endif
|
|
_position = _mediaPlayer.Position;
|
|
|
|
if (_positionChangedEvent != null)
|
|
_positionChangedEvent.Invoke(position);
|
|
}
|
|
|
|
public void AddPositionChangedEvent(UnityAction<float> action)
|
|
{
|
|
_positionChangedEvent.AddListener(action);
|
|
}
|
|
|
|
public void RemovePositionChangedEvent(UnityAction<float> action)
|
|
{
|
|
_positionChangedEvent.RemoveListener(action);
|
|
}
|
|
|
|
public void OnPlayerSnapshotTaken(string path)
|
|
{
|
|
if (_snapshotTakenEvent != null)
|
|
_snapshotTakenEvent.Invoke(path);
|
|
}
|
|
|
|
public void AddSnapshotTakenEvent(UnityAction<string> action)
|
|
{
|
|
_snapshotTakenEvent.AddListener(action);
|
|
}
|
|
|
|
public void RemoveSnapshotTakenEvent(UnityAction<string> action)
|
|
{
|
|
_snapshotTakenEvent.RemoveListener(action);
|
|
}
|
|
}
|
|
} |