#if UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX || UNITY_IPHONE || UNITY_IOS || UNITY_TVOS #if UNITY_5 || UNITY_5_4_OR_NEWER #if !UNITY_5_0 && !UNITY_5_1 #define AVPROVIDEO_ISSUEPLUGINEVENT_UNITY52 #endif #endif using UnityEngine; using System; using System.Collections; using System.Runtime.InteropServices; using System.Text.RegularExpressions; using AOT; //----------------------------------------------------------------------------- // Copyright 2015-2018 RenderHeads Ltd. All rights reserverd. //----------------------------------------------------------------------------- namespace RenderHeads.Media.AVProVideo { /// /// macOS, iOS and tvOS implementation of BaseMediaPlayer /// public class OSXMediaPlayer : BaseMediaPlayer { #if UNITY_STANDALONE_OSX || UNITY_EDITOR_OSX private const string PluginName = "AVProVideo"; #elif UNITY_IPHONE || UNITY_IOS || UNITY_TVOS private const string PluginName = "__Internal"; #endif // Native Interface private enum AVPPluginEventType { PlayerRender, PlayerFreeResources, } private enum AVPPluginColorSpace { Uninitialized, Gamma, Linear }; private enum AVPPlayerStatus { Failed = -1, Unknown, ReadyToPlay, Playing, Finished, Seeking, Buffering } private enum AVPLogFlag { Error = 1 << 0, Warning = 1 << 1, Info = 1 << 2, Debug = 1 << 3, Verbose = 1 << 4, }; private enum AVPLogLevel { Off = 0, Error = AVPLogFlag.Error, Warning = AVPLogFlag.Error | AVPLogFlag.Warning, Info = AVPLogFlag.Error | AVPLogFlag.Warning | AVPLogFlag.Info, Debug = AVPLogFlag.Error | AVPLogFlag.Warning | AVPLogFlag.Info | AVPLogFlag.Debug, Verbose = AVPLogFlag.Error | AVPLogFlag.Warning | AVPLogFlag.Info | AVPLogFlag.Debug | AVPLogFlag.Verbose, All = -1 }; [StructLayout(LayoutKind.Sequential, Pack=4)] private struct AVPPlayerTextureInfo { public IntPtr native; public int width; public int height; public int format; public int flipped; }; [StructLayout(LayoutKind.Sequential, Pack=4)] private struct AVPPlayerTimeRange { public double start; public double duration; }; [DllImport(PluginName)] private static extern string AVPGetVersion(); #if AVPROVIDEO_ISSUEPLUGINEVENT_UNITY52 [DllImport(PluginName)] private static extern IntPtr AVPGetRenderEventFunc(); #endif [DllImport(PluginName)] private static extern ErrorCode AVPPlayerGetLastError(IntPtr player); [DllImport(PluginName)] private static extern double AVPPlayerGetCurrentTime(IntPtr player); [DllImport(PluginName)] private static extern double AVPPlayerGetCurrentDate(IntPtr player); [DllImport(PluginName)] private static extern double AVPPlayerGetDuration(IntPtr player); [DllImport(PluginName)] private static extern int AVPPlayerGetFrameCount(IntPtr player); [DllImport(PluginName)] private static extern double AVPPlayerGetFrameRate(IntPtr player); [DllImport(PluginName)] private static extern long AVPPlayerGetFrameTimeStamp(IntPtr player); [DllImport(PluginName)] private static extern float AVPPlayerGetNominalFrameRate(IntPtr player); [DllImport(PluginName)] private static extern int AVPPlayerGetHandle(IntPtr player); [DllImport(PluginName)] private static extern AVPPlayerStatus AVPPlayerGetStatus(IntPtr player); [DllImport(PluginName)] private static extern float AVPPlayerGetBufferingProgress(IntPtr player); [DllImport(PluginName)] private static extern int AVPPlayerGetBufferedTimeRangeCount(IntPtr player); [DllImport(PluginName)] private static extern bool AVPPlayerGetBufferedTimeRange(IntPtr player, int index, out float start, out float end); [DllImport(PluginName)] private static extern int AVPPlayerGetSeekableTimeRanges(IntPtr player, [In, Out] AVPPlayerTimeRange[] ranges, ref int count); [DllImport(PluginName)] private static extern bool AVPPlayerGetTextures(IntPtr player, [In, Out] AVPPlayerTextureInfo[] textures, ref int count); [DllImport(PluginName)] private static extern bool AVPPlayerGetTextureTransform(IntPtr player, [In, Out] float[] transform); [DllImport(PluginName)] private static extern float AVPPlayerGetVolume(IntPtr player); [DllImport(PluginName)] private static extern bool AVPPlayerHasAudio(IntPtr player); [DllImport(PluginName)] private static extern bool AVPPlayerHasVideo(IntPtr player); [DllImport(PluginName)] private static extern bool AVPPlayerHasMetaData(IntPtr player); [DllImport(PluginName)] private static extern bool AVPPlayerIsLooping(IntPtr player); [DllImport(PluginName)] private static extern void AVPPlayerSetLooping(IntPtr player, bool looping); [DllImport(PluginName)] private static extern bool AVPPlayerIsMuted(IntPtr player); [DllImport(PluginName)] private static extern void AVPPlayerSetMuted(IntPtr player, bool muted); [DllImport(PluginName)] private static extern void AVPPlayerSetVolume(IntPtr player, float volume); [DllImport(PluginName)] private static extern IntPtr AVPPlayerNew(bool useYuv); [DllImport(PluginName)] private static extern IntPtr AVPPlayerRelease(IntPtr player); [DllImport(PluginName)] private static extern bool AVPPlayerOpenFile(IntPtr player, string path); [DllImport(PluginName)] private static extern bool AVPPlayerOpenURL(IntPtr player, string url, string headers); [DllImport(PluginName)] private static extern void AVPPlayerClose(IntPtr player); [DllImport(PluginName)] private static extern void AVPPlayerPlay(IntPtr player); [DllImport(PluginName)] private static extern void AVPPlayerPause(IntPtr player); [DllImport(PluginName)] private static extern void AVPPlayerSeek(IntPtr player, double time); [DllImport(PluginName)] private static extern void AVPPlayerSeekFast(IntPtr player, double time); [DllImport(PluginName)] private static extern void AVPPlayerSeekWithTolerance(IntPtr player, double time, double before, double after); [DllImport(PluginName)] private static extern float AVPPlayerGetPlaybackRate(IntPtr player); [DllImport(PluginName)] private static extern void AVPPlayerSetPlaybackRate(IntPtr player, float rate); [DllImport(PluginName)] private static extern bool AVPPlayerUpdate(IntPtr player); [DllImport(PluginName)] private static extern int AVPPlayerGetAudioTrackCount(IntPtr player); [DllImport(PluginName)] private static extern string AVPPlayerGetAudioTrackId(IntPtr player, int index); [DllImport(PluginName)] private static extern int AVPPlayerGetCurrentAudioTrack(IntPtr player); [DllImport(PluginName)] private static extern string AVPPlayerGetCurrentAudioTrackId(IntPtr player); [DllImport(PluginName)] private static extern int AVPPlayerGetCurrentAudioTrackBitrate(IntPtr player); [DllImport(PluginName)] private static extern int AVPPlayerGetVideoTrackCount(IntPtr player); [DllImport(PluginName)] private static extern string AVPPlayerGetVideoTrackId(IntPtr player, int index); [DllImport(PluginName)] private static extern int AVPPlayerGetCurrentVideoTrack(IntPtr player); [DllImport(PluginName)] private static extern string AVPPlayerGetCurrentVideoTrackId(IntPtr player); [DllImport(PluginName)] private static extern int AVPPlayerGetCurrentVideoTrackBitrate(IntPtr player); [DllImport(PluginName)] private static extern void AVPPlayerSetAudioTrack(IntPtr player, int track); [DllImport(PluginName)] private static extern void AVPPlayerSetVideoTrack(IntPtr player, int track); [DllImport(PluginName)] private static extern int AVPPlayerExtractFrame(IntPtr player, [In, Out] AVPPlayerTextureInfo[] textures, ref int count, double timeout); [DllImport(PluginName)] private static extern void AVPPlayerAddValueDidChangeObserver(IntPtr player, IntPtr self, IntPtr callback, string key, uint flags); [DllImport(PluginName)] private static extern void AVPPluginRegister(); [DllImport(PluginName)] private static extern void AVPPluginInitialise(AVPPluginColorSpace colorSpace); [DllImport(PluginName)] private static extern void AVPPluginSetDebugLogFunction(IntPtr fn); // MediaPlayer Interface private static bool _initialised = false; #if AVPROVIDEO_ISSUEPLUGINEVENT_UNITY52 private static IntPtr _renderEventFunc = IntPtr.Zero; #endif private static Regex _matchURLRegex = null; #if UNITY_STANDALONE_OSX || UNITY_EDITOR_OSX [UnmanagedFunctionPointer(CallingConvention.Cdecl)] #endif private delegate void DebugLogCallbackDelegate(int level, int flags, string str); #if UNITY_IPHONE || UNITY_IOS || UNITY_TVOS [MonoPInvokeCallback(typeof(DebugLogCallbackDelegate))] #endif private static void DebugLogCallback(int level, int flags, string str) { if ((flags & (int)AVPLogFlag.Error) == (int)AVPLogFlag.Error) { Debug.LogError(str); } else if ((flags & (int)AVPLogFlag.Warning) == (int)AVPLogFlag.Warning) { Debug.LogWarning(str); } else { Debug.Log(str); } } private static void IssuePluginEvent(AVPPluginEventType type, int param) { // Build eventId from the type and param. int eventId = 0x0FA60000 | ((int)type << 12); eventId |= param & 0xfff; #if AVPROVIDEO_ISSUEPLUGINEVENT_UNITY52 GL.IssuePluginEvent(_renderEventFunc, eventId); #else GL.IssuePluginEvent(eventId); #endif } #if UNITY_STANDALONE_OSX || UNITY_EDITOR_OSX [UnmanagedFunctionPointer(CallingConvention.Cdecl)] #endif private delegate void ValueAtKeyPathDidChangeDelegate(IntPtr self, string keyPath); #if UNITY_IPHONE || UNITY_IOS || UNITY_TVOS [MonoPInvokeCallback(typeof(ValueAtKeyPathDidChangeDelegate))] #endif private static void ValueAtKeyPathDidChangeThunk(IntPtr self, string keyPath) { GCHandle handle = GCHandle.FromIntPtr(self); OSXMediaPlayer player = (OSXMediaPlayer)handle.Target; player.ValueAtKeyPathDidChange(keyPath); } private void ValueAtKeyPathDidChange(string keyPath) { if (keyPath == "seekableTimeRanges") { AVPPlayerTimeRange[] ranges = new AVPPlayerTimeRange[4]; int count = ranges.Length; int numRanges = AVPPlayerGetSeekableTimeRanges(_player, ranges, ref count); if (numRanges > count) { ranges = new AVPPlayerTimeRange[numRanges]; count = numRanges; AVPPlayerGetSeekableTimeRanges(_player, ranges, ref count); } if (_seekableTimeRanges.Length != count) { _seekableTimeRanges = new TimeRange[count]; } for (int i = 0; i < count; ++i) { _seekableTimeRanges[i].startTime = (float)(ranges[i].start * 1000.0); _seekableTimeRanges[i].duration = (float)(ranges[i].duration * 1000.0); } } } static void Initialise() { if (!_initialised) { _initialised = true; #if ((UNITY_STANDALONE_OSX || UNITY_EDITOR_OSX) && !UNITY_5) || (UNITY_IPHONE || UNITY_IOS || UNITY_TVOS) AVPPluginRegister(); #endif DebugLogCallbackDelegate callbackDelegate = new DebugLogCallbackDelegate(DebugLogCallback); IntPtr func = Marshal.GetFunctionPointerForDelegate(callbackDelegate); AVPPluginSetDebugLogFunction(func); AVPPluginColorSpace colorSpace = QualitySettings.activeColorSpace == ColorSpace.Linear ? AVPPluginColorSpace.Linear : AVPPluginColorSpace.Gamma; AVPPluginInitialise(colorSpace); #if AVPROVIDEO_ISSUEPLUGINEVENT_UNITY52 _renderEventFunc = AVPGetRenderEventFunc(); #endif _matchURLRegex = new Regex("^[a-zA-Z][a-zA-Z0-9+-.]*://.*$"); } } private IntPtr _player = IntPtr.Zero; // The native player instance. private int _handle = 0; // Handle to the native player for use with IssuePluginEvent. private AVPPlayerStatus _status = AVPPlayerStatus.Unknown; private int _planeCount = 0; private Texture2D[] _texture = new Texture2D[2]; private int _width = 0; private int _height = 0; private bool _flipped = false; private bool _isMetaDataReady = false; private GCHandle _thisHandle; static OSXMediaPlayer() { Initialise(); } public OSXMediaPlayer(bool useYuv420Textures = false) { _player = AVPPlayerNew(useYuv420Textures); _handle = AVPPlayerGetHandle(_player); _thisHandle = GCHandle.Alloc(this, GCHandleType.Normal); ValueAtKeyPathDidChangeDelegate callbackDelegate = new ValueAtKeyPathDidChangeDelegate(ValueAtKeyPathDidChangeThunk); IntPtr self = GCHandle.ToIntPtr(_thisHandle); IntPtr callback = Marshal.GetFunctionPointerForDelegate(callbackDelegate); AVPPlayerAddValueDidChangeObserver(_player, self, callback, "seekableTimeRanges", 0); } // Convenience method for calling OSXMediaPlayer.IssuePluginEvent. // private void IssuePluginEvent(AVPPluginEventType type) { OSXMediaPlayer.IssuePluginEvent(type, _handle); } // BaseMediaPlayer Overrides public override string GetVersion() { return AVPGetVersion(); } public override bool OpenVideoFromFile(string path, long offset /* ignored */, string httpHeaderJson, uint sourceSamplerate = 0, uint sourceChannels = 0, int forceFileFormat = 0) { if (_matchURLRegex.IsMatch(path)) { return AVPPlayerOpenURL(_player, path, httpHeaderJson); } else { return AVPPlayerOpenFile(_player, path); } } public override void CloseVideo() { AVPPlayerClose(_player); if (_texture[0] != null) { IssuePluginEvent(AVPPluginEventType.PlayerFreeResources); // Have to update with zero to release Metal textures! for (int i = 0; i < _planeCount; ++i) { _texture[i].UpdateExternalTexture(IntPtr.Zero); Texture2D.Destroy(_texture[i]); _texture[i] = null; } } _width = 0; _height = 0; _isMetaDataReady = false; _planeCount = 0; base.CloseVideo(); } public override bool IsLooping() { return AVPPlayerIsLooping(_player); } public override void SetLooping(bool looping) { AVPPlayerSetLooping(_player, looping); } public override bool HasAudio() { return AVPPlayerHasAudio(_player); } public override bool HasVideo() { return AVPPlayerHasVideo(_player); } public override bool HasMetaData() { return _isMetaDataReady; } public override bool CanPlay() { return _status >= AVPPlayerStatus.ReadyToPlay; } public override void Play() { AVPPlayerPlay(_player); } public override void Pause() { AVPPlayerPause(_player); } public override void Stop() { AVPPlayerPause(_player); } public override void Rewind() { AVPPlayerSeekFast(_player, 0.0); } public override void Seek(float ms) { AVPPlayerSeek(_player, ms / 1000.0); } public override void SeekFast(float ms) { AVPPlayerSeekFast(_player, ms / 1000.0); } public override void SeekWithTolerance(float timeMs, float beforeMs, float afterMs) { AVPPlayerSeekWithTolerance(_player, timeMs / 1000.0, beforeMs / 1000.0, afterMs / 1000.0); } public override float GetCurrentTimeMs() { return (float)(AVPPlayerGetCurrentTime(_player) * 1000.0f); } public override double GetCurrentDateTimeSecondsSince1970() { return AVPPlayerGetCurrentDate(_player); } public override void SetPlaybackRate(float rate) { AVPPlayerSetPlaybackRate(_player, rate); } public override float GetPlaybackRate() { return AVPPlayerGetPlaybackRate(_player); } public override float GetDurationMs() { return (float)(AVPPlayerGetDuration(_player) * 1000.0f); } public override int GetVideoWidth() { return _width; } public override int GetVideoHeight() { return _height; } public override float GetVideoDisplayRate() { return (float)AVPPlayerGetFrameRate(_player); } public override bool IsSeeking() { return _status == AVPPlayerStatus.Seeking; } public override bool IsPlaying() { return _status == AVPPlayerStatus.Playing; } public override bool IsPaused() { return _status == AVPPlayerStatus.ReadyToPlay; } public override bool IsFinished() { return _status == AVPPlayerStatus.Finished; } public override bool IsBuffering() { return _status == AVPPlayerStatus.Buffering; } public override float GetBufferingProgress() { return AVPPlayerGetBufferingProgress(_player); } public override int GetBufferedTimeRangeCount() { return AVPPlayerGetBufferedTimeRangeCount(_player); } public override bool GetBufferedTimeRange(int index, ref float startTimeMs, ref float endTimeMs) { return AVPPlayerGetBufferedTimeRange(_player, index, out startTimeMs, out endTimeMs); } public override bool WaitForNextFrame(Camera camera, int previousFrameCount) { int count = 2; AVPPlayerTextureInfo[] textures = new AVPPlayerTextureInfo[count]; int ret = AVPPlayerExtractFrame(_player, textures, ref count, 0.0); if (ret == 0) { _planeCount = count; for (int i = 0; i < count; ++i) { if (_texture[i] == null || _texture[i].width != textures[i].width || _texture[i].height != textures[i].height || _texture[i].format != (TextureFormat)textures[i].format) { _texture[i] = Texture2D.CreateExternalTexture(textures[i].width, textures[i].height, (TextureFormat)textures[i].format, /*mipmap*/ false, /*linear*/ false, textures[i].native); if (i == 0) { _width = textures[i].width; _height = textures[i].height; _flipped = textures[i].flipped != 0; } } else { _texture[i].UpdateExternalTexture(textures[i].native); } } return true; } return false; } // IMediaProducer public override int GetTextureCount() { return _planeCount; } public override Texture GetTexture(int index) { return _texture[index]; } public override int GetTextureFrameCount() { return AVPPlayerGetFrameCount(_player); } public override long GetTextureTimeStamp() { return AVPPlayerGetFrameTimeStamp(_player); } public override bool RequiresVerticalFlip() { return _flipped; } public override float[] GetTextureTransform() { float[] transform = new float[6]; AVPPlayerGetTextureTransform(_player, transform); return transform; } // public override bool IsMuted() { return AVPPlayerIsMuted(_player); } public override void MuteAudio( bool bMute ) { AVPPlayerSetMuted(_player, bMute); } public override void SetVolume(float volume) { AVPPlayerSetVolume(_player, volume); } public override float GetVolume() { return AVPPlayerGetVolume(_player); } public override int GetAudioTrackCount() { return AVPPlayerGetAudioTrackCount(_player); } public override string GetAudioTrackId(int index) { string identifier = AVPPlayerGetAudioTrackId(_player, index); if (identifier == null) identifier = base.GetAudioTrackId(index); return identifier; } public override int GetCurrentAudioTrack() { return AVPPlayerGetCurrentAudioTrack(_player); } public override void SetAudioTrack(int track) { AVPPlayerSetAudioTrack(_player, track); } public override string GetCurrentAudioTrackId() { return AVPPlayerGetCurrentAudioTrackId(_player); } public override int GetCurrentAudioTrackBitrate() { return AVPPlayerGetCurrentAudioTrackBitrate(_player); } public override int GetVideoTrackCount() { return AVPPlayerGetVideoTrackCount(_player); } public override int GetCurrentVideoTrack() { return AVPPlayerGetCurrentVideoTrack(_player); } public override void SetVideoTrack(int index) { AVPPlayerSetVideoTrack(_player, index); } public override string GetVideoTrackId(int index) { string identifier = AVPPlayerGetVideoTrackId(_player, index); if (identifier == null) identifier = base.GetVideoTrackId(index); return identifier; } public override string GetCurrentVideoTrackId() { return AVPPlayerGetCurrentVideoTrackId(_player); } public override int GetCurrentVideoTrackBitrate() { return AVPPlayerGetCurrentVideoTrackBitrate(_player); } public override float GetVideoFrameRate() { return AVPPlayerGetNominalFrameRate(_player); } public override void Render() { } public void UpdateTextures() { AVPPlayerTextureInfo[] textures = new AVPPlayerTextureInfo[2]; int count = textures.Length; if (AVPPlayerGetTextures(_player, textures, ref count)) { _planeCount = count; for (int i = 0; i < count; ++i) { if (_texture[i] == null || _texture[i].width != textures[i].width || _texture[i].height != textures[i].height || _texture[i].format != (TextureFormat)textures[i].format) { _texture[i] = Texture2D.CreateExternalTexture(textures[i].width, textures[i].height, (TextureFormat)textures[i].format, /*mipmap*/ false, /*linear*/ false, textures[i].native); if (i == 0) { _width = textures[i].width; _height = textures[i].height; _flipped = textures[i].flipped != 0; } } else { _texture[i].UpdateExternalTexture(textures[i].native); } } } } public override void Update() { _status = AVPPlayerGetStatus(_player); if (AVPPlayerUpdate(_player)) { IssuePluginEvent(AVPPluginEventType.PlayerRender); } _lastError = AVPPlayerGetLastError(_player); UpdateTextures(); UpdateSubtitles(); // Check for meta data to become available if (!_isMetaDataReady) { if (AVPPlayerHasMetaData(_player) || CanPlay()) { // MOZ - had to move this outside of HasVideo check _isMetaDataReady = true; if (HasVideo()) { if (_width > 0 && _height > 0) { if (Mathf.Max(_width, _height) > SystemInfo.maxTextureSize) { Debug.LogError("[AVProVideo] Video dimensions larger than maxTextureSize"); } } _playerDescription = "AVFoundation"; Helper.LogInfo("Using playback path: " + _playerDescription + " (" + _width + "x" + _height + "@" + GetVideoFrameRate().ToString("F2") + ")"); } else if (HasAudio()) { } } } } public override void Dispose() { CloseVideo(); AVPPlayerRelease(_player); _player = IntPtr.Zero; _thisHandle.Free(); } } } #endif