using UnityEngine; using System; using System.Collections.Generic; //----------------------------------------------------------------------------- // Copyright 2015-2022 RenderHeads Ltd. All rights reserved. //----------------------------------------------------------------------------- namespace RenderHeads.Media.AVProVideo { public partial class MediaPlayer : MonoBehaviour { #region PlatformOptions [System.Serializable] public class PlatformOptions { public enum TextureFormat : int { BGRA, YCbCr420_OES, } public enum Resolution : int { NoPreference, _480p, _720p, _1080p, _1440p, _2160p, Custom } public enum AudioMode : int { SystemDirect, Unity, SystemDirectWithCapture, FacebookAudio360, } public enum BitRateUnits : int { bps, Kbps, Mbps } public virtual bool IsModified() { return (httpHeaders.IsModified() || keyAuth.IsModified() ); } public virtual bool HasChanged() { return false; } public virtual void ClearChanges() { } public HttpHeaderData httpHeaders = new HttpHeaderData(); public KeyAuthData keyAuth = new KeyAuthData(); // Decryption support public virtual string GetKeyServerAuthToken() { return keyAuth.keyServerToken; } //public virtual string GetKeyServerURL() { return null; } public virtual byte[] GetOverrideDecryptionKey() { return keyAuth.overrideDecryptionKey; } public virtual bool StartWithHighestBandwidth() { return false; } } [System.Serializable] public class OptionsWindows : PlatformOptions, ISerializationCallbackReceiver { public Windows.VideoApi videoApi = Windows.VideoApi.MediaFoundation; public bool useHardwareDecoding = true; public bool useRendererSync = true; public bool useTextureMips = false; public bool use10BitTextures = false; public bool hintAlphaChannel = false; public bool useLowLatency = false; public bool useCustomMovParser = false; public bool useHapNotchLC = false; public bool useStereoDetection = true; public bool useTextTrackSupport = true; public bool useFacebookAudio360Support = true; public bool useAudioDelay = false; public string forceAudioOutputDeviceName = string.Empty; public List preferredFilters = new List(); public Windows.AudioOutput _audioMode = Windows.AudioOutput.System; public Audio360ChannelMode audio360ChannelMode = Audio360ChannelMode.TBE_8_2; /// WinRT only public bool startWithHighestBitrate = false; /// WinRT only public bool useLowLiveLatency = false; /// Hap & NotchLC only [Range(1, 16)] public int parallelFrameCount = 3; /// Hap & NotchLC only [Range(1, 16)] public int prerollFrameCount = 4; public override bool IsModified() { return (base.IsModified() || !useHardwareDecoding || !useRendererSync || useTextureMips || use10BitTextures || hintAlphaChannel || useLowLatency || useCustomMovParser || useHapNotchLC || !useStereoDetection || !useTextTrackSupport || !useFacebookAudio360Support || useAudioDelay || videoApi != Windows.VideoApi.MediaFoundation || _audioMode != Windows.AudioOutput.System || audio360ChannelMode != Audio360ChannelMode.TBE_8_2 || !string.IsNullOrEmpty(forceAudioOutputDeviceName) || preferredFilters.Count != 0 || startWithHighestBitrate || useLowLiveLatency || parallelFrameCount != 3 || prerollFrameCount != 4 ); } public override bool StartWithHighestBandwidth() { return startWithHighestBitrate; } #region Upgrade from Version 1.x [SerializeField, HideInInspector] private bool useUnityAudio = false; [SerializeField, HideInInspector] private bool enableAudio360 = false; void ISerializationCallbackReceiver.OnBeforeSerialize() { } void ISerializationCallbackReceiver.OnAfterDeserialize() { if (useUnityAudio && _audioMode == Windows.AudioOutput.System) { _audioMode = Windows.AudioOutput.Unity; useUnityAudio = false; } if (enableAudio360 && _audioMode == Windows.AudioOutput.System) { _audioMode = Windows.AudioOutput.FacebookAudio360; enableAudio360 = false; } } #endregion // Upgrade from Version 1.x } [System.Serializable] public class OptionsWindowsUWP : PlatformOptions { public bool useHardwareDecoding = true; public bool useRendererSync = true; public bool useTextureMips = false; public bool use10BitTextures = false; public bool hintOutput10Bit = false; public bool useLowLatency = false; public WindowsUWP.VideoApi videoApi = WindowsUWP.VideoApi.WinRT; public WindowsUWP.AudioOutput _audioMode = WindowsUWP.AudioOutput.System; public Audio360ChannelMode audio360ChannelMode = Audio360ChannelMode.TBE_8_2; /// WinRT only public bool startWithHighestBitrate = false; /// WinRT only public bool useLowLiveLatency = false; public override bool IsModified() { return (base.IsModified() || !useHardwareDecoding || !useRendererSync || useTextureMips || use10BitTextures || useLowLatency || _audioMode != WindowsUWP.AudioOutput.System || (audio360ChannelMode != Audio360ChannelMode.TBE_8_2) || videoApi != WindowsUWP.VideoApi.WinRT || startWithHighestBitrate || useLowLiveLatency ); } public override bool StartWithHighestBandwidth() { return startWithHighestBitrate; } } [System.Serializable] public class OptionsApple: PlatformOptions { [Flags] public enum Flags: int { // Common None = 0, GenerateMipMaps = 1 << 0, // iOS & macOS AllowExternalPlayback = 1 << 8, PlayWithoutBuffering = 1 << 9, UseSinglePlayerItem = 1 << 10, // iOS ResumeMediaPlaybackAfterAudioSessionRouteChange = 1 << 16, } private readonly TextureFormat DefaultTextureFormat; private readonly Flags DefaultFlags; public TextureFormat textureFormat; private AudioMode _previousAudioMode = AudioMode.SystemDirect; public AudioMode previousAudioMode { get { return _previousAudioMode; } } [SerializeField] private AudioMode _audioMode; public AudioMode audioMode { get { return _audioMode; } set { if (_audioMode != value) { _previousAudioMode = _audioMode; _audioMode = value; _changed |= ChangeFlags.AudioMode; } } } [SerializeField] private Flags _flags; public Flags flags { get { return _flags; } set { Flags changed = _flags ^ value; if (changed != 0) { if ((changed & Flags.PlayWithoutBuffering) == Flags.PlayWithoutBuffering) { _changed |= ChangeFlags.PlayWithoutBuffering; } if ((changed & Flags.ResumeMediaPlaybackAfterAudioSessionRouteChange) == Flags.ResumeMediaPlaybackAfterAudioSessionRouteChange) { _changed |= ChangeFlags.ResumeMediaPlaybackAfterAudioSessionRouteChange; } _flags = value; } } } public float maximumPlaybackRate = 2.0f; [Flags] public enum ChangeFlags: int { None = 0, PreferredPeakBitRate = 1 << 1, PreferredForwardBufferDuration = 1 << 2, PlayWithoutBuffering = 1 << 3, PreferredMaximumResolution = 1 << 4, AudioMode = 1 << 5, ResumeMediaPlaybackAfterAudioSessionRouteChange = 1 << 6, All = -1 } private ChangeFlags _changed = ChangeFlags.None; [SerializeField] private float _preferredPeakBitRate = 0.0f; public float preferredPeakBitRate { get { return _preferredPeakBitRate; } set { if (_preferredPeakBitRate != value) { _changed |= ChangeFlags.PreferredPeakBitRate; _preferredPeakBitRate = value; } } } [SerializeField] private BitRateUnits _preferredPeakBitRateUnits = BitRateUnits.Kbps; public BitRateUnits preferredPeakBitRateUnits { get { return _preferredPeakBitRateUnits; } set { if (_preferredPeakBitRateUnits != value) { _changed |= ChangeFlags.PreferredPeakBitRate; _preferredPeakBitRateUnits = value; } } } [SerializeField] private double _preferredForwardBufferDuration = 0.0; public double preferredForwardBufferDuration { get { return _preferredForwardBufferDuration; } set { if (_preferredForwardBufferDuration != value) { _changed |= ChangeFlags.PreferredForwardBufferDuration; _preferredForwardBufferDuration = value; } } } [SerializeField] private Resolution _preferredMaximumResolution = Resolution.NoPreference; public Resolution preferredMaximumResolution { get { return _preferredMaximumResolution; } set { if (_preferredMaximumResolution != value) { _changed |= ChangeFlags.PreferredMaximumResolution; _preferredMaximumResolution = value; } } } #if UNITY_2017_2_OR_NEWER [SerializeField] private Vector2Int _customPreferredMaximumResolution = Vector2Int.zero; public Vector2Int customPreferredMaximumResolution { get { return _customPreferredMaximumResolution; } set { if (_customPreferredMaximumResolution != value) { _changed |= ChangeFlags.PreferredMaximumResolution; _customPreferredMaximumResolution = value; } } } #endif private static double BitRateInBitsPerSecond(float value, BitRateUnits units) { switch (units) { case BitRateUnits.bps: return (double)value; case BitRateUnits.Kbps: return (double)value * 1000.0; case BitRateUnits.Mbps: return (double)value * 1000000.0; default: return 0.0; } } public double GetPreferredPeakBitRateInBitsPerSecond() { return BitRateInBitsPerSecond(preferredPeakBitRate, preferredPeakBitRateUnits); } public OptionsApple(TextureFormat defaultTextureFormat, Flags defaultFlags) { DefaultTextureFormat = defaultTextureFormat; DefaultFlags = defaultFlags; textureFormat = defaultTextureFormat; audioMode = AudioMode.SystemDirect; flags = defaultFlags; } public override bool IsModified() { return base.IsModified() || textureFormat != DefaultTextureFormat || audioMode != AudioMode.SystemDirect || flags != DefaultFlags || preferredMaximumResolution != Resolution.NoPreference || preferredPeakBitRate != 0.0f || preferredForwardBufferDuration != 0.0; } public override bool HasChanged() { return HasChanged(ChangeFlags.All); } public bool HasChanged(ChangeFlags flags) { return (_changed & flags) != ChangeFlags.None; } public override void ClearChanges() { _changed = ChangeFlags.None; } } [System.Serializable] public class OptionsAndroid : PlatformOptions, ISerializationCallbackReceiver { [Flags] public enum ChangeFlags : int { None = 0, PreferredPeakBitRate = 1 << 1, PreferredMaximumResolution = 1 << 2, PreferredCustomResolution = 1 << 3, AudioMode = 1 << 4, GenerateMipmaps = 1 << 5, All = -1 } private ChangeFlags _changed = ChangeFlags.None; private readonly TextureFormat DefaultTextureFormat; public TextureFormat textureFormat; [SerializeField] private bool _generateMipmaps = false; public bool generateMipmaps { get { return _generateMipmaps; } set { if (value != _generateMipmaps) { _generateMipmaps = value; _changed |= ChangeFlags.GenerateMipmaps; } } } private AudioMode _previousAudioMode = AudioMode.SystemDirect; public AudioMode previousAudioMode { get { return _previousAudioMode; } } [SerializeField] private AudioMode _audioMode; public AudioMode audioMode { get { return _audioMode; } set { if (_audioMode != value) { _previousAudioMode = _audioMode; _audioMode = value; _changed |= ChangeFlags.AudioMode; } } } [SerializeField] private Resolution _preferredMaximumResolution = Resolution.NoPreference; public Resolution preferredMaximumResolution { get { return _preferredMaximumResolution; } set { if (_preferredMaximumResolution != value) { _changed |= ChangeFlags.PreferredMaximumResolution; _preferredMaximumResolution = value; } } } #if UNITY_2017_2_OR_NEWER [SerializeField] private Vector2Int _customPreferredMaximumResolution = Vector2Int.zero; public Vector2Int customPreferredMaximumResolution { get { return _customPreferredMaximumResolution; } set { if (_customPreferredMaximumResolution != value) { _changed |= ChangeFlags.PreferredCustomResolution; _customPreferredMaximumResolution = value; } } } #endif [SerializeField] private float _preferredPeakBitRate = 0.0f; public float preferredPeakBitRate { get { return _preferredPeakBitRate; } set { if (_preferredPeakBitRate != value) { _changed |= ChangeFlags.PreferredPeakBitRate; _preferredPeakBitRate = value; } } } [SerializeField] private BitRateUnits _preferredPeakBitRateUnits = BitRateUnits.Kbps; public BitRateUnits preferredPeakBitRateUnits { get { return _preferredPeakBitRateUnits; } set { if (_preferredPeakBitRateUnits != value) { _changed |= ChangeFlags.PreferredPeakBitRate; _preferredPeakBitRateUnits = value; } } } public Android.VideoApi videoApi = Android.VideoApi.ExoPlayer; public bool showPosterFrame = false; // NOTE 2024.09.26: DEPRECATED public Audio360ChannelMode audio360ChannelMode = Audio360ChannelMode.TBE_8_2; public int audio360LatencyMS = 0; public bool preferSoftwareDecoder = false; public bool forceRtpTCP = false; public bool forceEnableMediaCodecAsynchronousQueueing = false; [SerializeField, Tooltip("Byte offset into the file where the media file is located. This is useful when hiding or packing media files within another file.")] public int fileOffset = 0; public bool startWithHighestBitrate = false; public int minBufferMs = Android.Default_MinBufferTimeMs; public int maxBufferMs = Android.Default_MaxBufferTimeMs; public int bufferForPlaybackMs = Android.Default_BufferForPlaybackMs; public int bufferForPlaybackAfterRebufferMs = Android.Default_BufferForPlaybackAfterRebufferMs; [Obsolete("useFastOesPath is deprecated and replaced with TextureFormat")] public bool useFastOesPath; [Obsolete("audioOutput is deprecated and replaced with audioMode")] public int audioOutput; [Obsolete("blitTextureFiltering is deprecated and its functionality has been removed")] public int blitTextureFiltering; [Obsolete("forceEnableMediaCodecAsyncQueueing is deprecated and replaced with forceEnableMediaCodecAsynchronousQueueing")] public bool forceEnableMediaCodecAsyncQueueing; public override bool IsModified() { return (base.IsModified() || (fileOffset != 0) || textureFormat != DefaultTextureFormat || audioMode != AudioMode.SystemDirect // || showPosterFrame || (videoApi != Android.VideoApi.ExoPlayer) || (audio360ChannelMode != Audio360ChannelMode.TBE_8_2) || (audio360LatencyMS != 0 ) || preferSoftwareDecoder || forceRtpTCP || forceEnableMediaCodecAsynchronousQueueing || startWithHighestBitrate || (minBufferMs != Android.Default_MinBufferTimeMs) || (maxBufferMs != Android.Default_MaxBufferTimeMs) || (bufferForPlaybackMs != Android.Default_BufferForPlaybackMs) || (bufferForPlaybackAfterRebufferMs != Android.Default_BufferForPlaybackAfterRebufferMs) || (preferredMaximumResolution != Resolution.NoPreference) || (preferredPeakBitRate != 0.0f) ); } private static double BitRateInBitsPerSecond(float value, BitRateUnits units) { switch (units) { case BitRateUnits.bps: return (double)value; case BitRateUnits.Kbps: return (double)value * 1000.0; case BitRateUnits.Mbps: return (double)value * 1000000.0; default: return 0.0; } } public double GetPreferredPeakBitRateInBitsPerSecond() { _changed &= ~ChangeFlags.PreferredPeakBitRate; return BitRateInBitsPerSecond(preferredPeakBitRate, preferredPeakBitRateUnits); } public override bool StartWithHighestBandwidth() { return startWithHighestBitrate; } public override bool HasChanged() { return HasChanged(ChangeFlags.All, false); } public bool HasChanged(ChangeFlags flags, bool bClearFlags = false) { bool bReturn = ((_changed & flags) != ChangeFlags.None); if (bClearFlags) { _changed = ChangeFlags.None; } return bReturn; } public override void ClearChanges() { _changed = ChangeFlags.None; } #region Upgrade from Version 1.x [SerializeField, HideInInspector] private bool enableAudio360 = false; void ISerializationCallbackReceiver.OnBeforeSerialize() { } void ISerializationCallbackReceiver.OnAfterDeserialize() { #if false if (enableAudio360 && audioOutput == Android.AudioOutput.System) { audioOutput = Android.AudioOutput.FacebookAudio360; enableAudio360 = false; } #else if (enableAudio360 && audioMode == AudioMode.SystemDirect) { audioMode = AudioMode.FacebookAudio360; enableAudio360 = false; } #endif } #endregion // Upgrade from Version 1.x } [System.Serializable] public class OptionsWebGL : PlatformOptions { public WebGL.ExternalLibrary externalLibrary = WebGL.ExternalLibrary.None; public bool useTextureMips = false; public override bool IsModified() { return (base.IsModified() || externalLibrary != WebGL.ExternalLibrary.None || useTextureMips); } // Decryption support public override string GetKeyServerAuthToken() { return null; } public override byte[] GetOverrideDecryptionKey() { return null; } } // TODO: move these to a Setup object [SerializeField] OptionsWindows _optionsWindows = new OptionsWindows(); [SerializeField] OptionsApple _options_macOS = new OptionsApple(OptionsApple.TextureFormat.BGRA, OptionsApple.Flags.None); [SerializeField] OptionsApple _options_iOS = new OptionsApple(OptionsApple.TextureFormat.BGRA, OptionsApple.Flags.None); [SerializeField] OptionsApple _options_tvOS = new OptionsApple(OptionsApple.TextureFormat.BGRA, OptionsApple.Flags.None); [SerializeField] OptionsApple _options_visionOS = new OptionsApple(OptionsApple.TextureFormat.BGRA, OptionsApple.Flags.None); [SerializeField] OptionsAndroid _optionsAndroid = new OptionsAndroid(); [SerializeField] OptionsWindowsUWP _optionsWindowsUWP = new OptionsWindowsUWP(); [SerializeField] OptionsWebGL _optionsWebGL = new OptionsWebGL(); public OptionsWindows PlatformOptionsWindows { get { return _optionsWindows; } } public OptionsApple PlatformOptions_macOS { get { return _options_macOS; } } public OptionsApple PlatformOptions_iOS { get { return _options_iOS; } } public OptionsApple PlatformOptions_tvOS { get { return _options_tvOS; } } public OptionsApple PlatformOptions_visionOS { get { return _options_visionOS; } } public OptionsAndroid PlatformOptionsAndroid { get { return _optionsAndroid; } } public OptionsWindowsUWP PlatformOptionsWindowsUWP { get { return _optionsWindowsUWP; } } public OptionsWebGL PlatformOptionsWebGL { get { return _optionsWebGL; } } #endregion // PlatformOptions } #region PlatformOptionsExtensions public static class OptionsAppleExtensions { public static bool GenerateMipmaps(this MediaPlayer.OptionsApple.Flags flags) { return (flags & MediaPlayer.OptionsApple.Flags.GenerateMipMaps) == MediaPlayer.OptionsApple.Flags.GenerateMipMaps; } public static MediaPlayer.OptionsApple.Flags SetGenerateMipMaps(this MediaPlayer.OptionsApple.Flags flags, bool b) { if (flags.GenerateMipmaps() ^ b) { flags = b ? flags | MediaPlayer.OptionsApple.Flags.GenerateMipMaps : flags & ~MediaPlayer.OptionsApple.Flags.GenerateMipMaps; } return flags; } public static bool AllowExternalPlayback(this MediaPlayer.OptionsApple.Flags flags) { return (flags & MediaPlayer.OptionsApple.Flags.AllowExternalPlayback) == MediaPlayer.OptionsApple.Flags.AllowExternalPlayback; } public static MediaPlayer.OptionsApple.Flags SetAllowExternalPlayback(this MediaPlayer.OptionsApple.Flags flags, bool b) { if (flags.AllowExternalPlayback() ^ b) { flags = b ? flags | MediaPlayer.OptionsApple.Flags.AllowExternalPlayback : flags & ~MediaPlayer.OptionsApple.Flags.AllowExternalPlayback; } return flags; } public static bool PlayWithoutBuffering(this MediaPlayer.OptionsApple.Flags flags) { return (flags & MediaPlayer.OptionsApple.Flags.PlayWithoutBuffering) == MediaPlayer.OptionsApple.Flags.PlayWithoutBuffering; } public static MediaPlayer.OptionsApple.Flags SetPlayWithoutBuffering(this MediaPlayer.OptionsApple.Flags flags, bool b) { if (flags.PlayWithoutBuffering() ^ b) { flags = b ? flags | MediaPlayer.OptionsApple.Flags.PlayWithoutBuffering : flags & ~MediaPlayer.OptionsApple.Flags.PlayWithoutBuffering; } return flags; } public static bool UseSinglePlayerItem(this MediaPlayer.OptionsApple.Flags flags) { return (flags & MediaPlayer.OptionsApple.Flags.UseSinglePlayerItem) == MediaPlayer.OptionsApple.Flags.UseSinglePlayerItem; } public static MediaPlayer.OptionsApple.Flags SetUseSinglePlayerItem(this MediaPlayer.OptionsApple.Flags flags, bool b) { if (flags.UseSinglePlayerItem() ^ b) { flags = b ? flags | MediaPlayer.OptionsApple.Flags.UseSinglePlayerItem : flags & ~MediaPlayer.OptionsApple.Flags.UseSinglePlayerItem; } return flags; } public static bool ResumePlaybackAfterAudioSessionRouteChange(this MediaPlayer.OptionsApple.Flags flags) { return (flags & MediaPlayer.OptionsApple.Flags.ResumeMediaPlaybackAfterAudioSessionRouteChange) == MediaPlayer.OptionsApple.Flags.ResumeMediaPlaybackAfterAudioSessionRouteChange; } public static MediaPlayer.OptionsApple.Flags SetResumePlaybackAfterAudioSessionRouteChange(this MediaPlayer.OptionsApple.Flags flags, bool b) { if (flags.ResumePlaybackAfterAudioSessionRouteChange() ^ b) { flags = b ? flags | MediaPlayer.OptionsApple.Flags.ResumeMediaPlaybackAfterAudioSessionRouteChange : flags & ~MediaPlayer.OptionsApple.Flags.ResumeMediaPlaybackAfterAudioSessionRouteChange; } return flags; } } #endregion // PlatformOptionsExtensions }