< Summary

Class:DCL.Components.DCLVideoTexture
Assembly:MainScripts
File(s):/tmp/workspace/unity-renderer/unity-renderer/Assets/Scripts/MainScripts/DCL/Components/Video/DCLVideoTexture.cs
Covered lines:190
Uncovered lines:55
Coverable lines:245
Total lines:536
Line coverage:77.5% (190 of 245)
Covered branches:0
Total branches:0

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity NPath complexity Sequence coverage
DCLVideoTexture()0%330100%
Model()0%110100%
GetDataFromJSON(...)0%110100%
DCLVideoTexture()0%110100%
ApplyChanges()0%38.4721065.91%
Initialize(...)0%220100%
GetVolume()0%2100%
HasTexturePropertiesChanged()0%6200%
ApplyTextureProperties()0%2100%
OnUpdate()0%330100%
UpdateDirtyState()0%220100%
UpdateVideoTexture()0%440100%
UpdateProgressReport()0%440100%
ReportVideoProgress()0%110100%
IsTimeToReportVideoProgress()0%2100%
CalculateVideoVolumeAndPlayStatus()0%880100%
OnVirtualAudioMixerChangedValue(...)0%2100%
UpdateVolume()0%4.014090.91%
IsPlayerInSameSceneAsComponent(...)0%6.65060%
OnPlayerCoordsChanged(...)0%110100%
OnSceneIDChanged(...)0%110100%
AttachTo(...)0%2100%
DetachFrom(...)0%2100%
AttachTo(...)0%110100%
DetachFrom(...)0%110100%
AttachToMaterial(...)0%440100%
DetachFromMaterial(...)0%220100%
AttachTo(...)0%6200%
DetachFrom(...)0%6200%
OnEntityRemoved(...)0%110100%
OnAudioSettingsChanged(...)0%110100%
Dispose()0%440100%
OnEntityAttachedMaterial(...)0%2100%
OnEntityDetachedMaterial(...)0%220100%
OnEntityShapeUpdated(...)0%110100%
GetClosestDistanceSqr(...)0%550100%
IsVisible()0%440100%
IsEntityVisible(...)0%3.073080%
MaterialComponent(...)0%2100%
GetClosestDistanceSqr(...)0%2100%
IsVisible()0%6200%
IsParentVisible(...)0%12300%
UIShapeComponent(...)0%2100%

File(s)

/tmp/workspace/unity-renderer/unity-renderer/Assets/Scripts/MainScripts/DCL/Components/Video/DCLVideoTexture.cs

#LineLine coverage
 1using System.Collections;
 2using System.Collections.Generic;
 3using DCL.Controllers;
 4using DCL.Models;
 5using UnityEngine;
 6using DCL.Components.Video.Plugin;
 7using DCL.Helpers;
 8using DCL.Interface;
 9using DCL.SettingsCommon;
 10using AudioSettings = DCL.SettingsCommon.AudioSettings;
 11
 12namespace DCL.Components
 13{
 14    public class DCLVideoTexture : DCLTexture
 15    {
 116        public static bool VERBOSE = false;
 117        public static ILogger logger = new Logger(Debug.unityLogger) { filterLogType = VERBOSE ? LogType.Log : LogType.E
 18
 19        private const float OUTOFSCENE_TEX_UPDATE_INTERVAL_IN_SECONDS = 1.5f;
 20        private const float VIDEO_PROGRESS_UPDATE_INTERVAL_IN_SECONDS = 1f;
 21
 122        public static System.Func<IVideoPluginWrapper> videoPluginWrapperBuilder = () => new VideoPluginWrapper_WebGL();
 23
 24        [System.Serializable]
 25        new public class Model : BaseModel
 26        {
 27            public string videoClipId;
 28            public bool playing = false;
 4529            public float volume = 1f;
 4530            public float playbackRate = 1f;
 31            public bool loop = false;
 4532            public float seek = -1;
 33            public BabylonWrapMode wrap = BabylonWrapMode.CLAMP;
 4534            public FilterMode samplingMode = FilterMode.Bilinear;
 35
 1536            public override BaseModel GetDataFromJSON(string json) { return Utils.SafeFromJson<Model>(json); }
 37        }
 38
 39        internal WebVideoPlayer texturePlayer;
 40        private Coroutine texturePlayerUpdateRoutine;
 41        private float baseVolume;
 1542        private float distanceVolumeModifier = 1f;
 43        private bool isPlayStateDirty = false;
 44        internal bool isVisible = false;
 45
 1546        private bool isPlayerInScene = true;
 1547        private float currUpdateIntervalTime = OUTOFSCENE_TEX_UPDATE_INTERVAL_IN_SECONDS;
 48        private float lastVideoProgressReportTime;
 49
 1550        internal Dictionary<string, MaterialInfo> attachedMaterials = new Dictionary<string, MaterialInfo>();
 51        private string lastVideoClipID;
 52        private VideoState previousVideoState;
 53
 1554        public DCLVideoTexture()
 55        {
 1556            model = new Model();
 57
 1558            DataStore.i.virtualAudioMixer.sceneSFXVolume.OnChange += OnVirtualAudioMixerChangedValue;
 1559        }
 60
 61        public override IEnumerator ApplyChanges(BaseModel newModel)
 62        {
 3063            yield return new WaitUntil(() => CommonScriptableObjects.rendererState.Get());
 64
 65            //If the scene creates and destroy the component before our renderer has been turned on bad things happen!
 66            //TODO: Analyze if we can catch this upstream and stop the IEnumerator
 1567            if (isDisposed)
 68            {
 069                yield break;
 70            }
 71
 1572            var model = (Model) newModel;
 73
 1574            unitySamplingMode = model.samplingMode;
 75
 1576            switch (model.wrap)
 77            {
 78                case BabylonWrapMode.CLAMP:
 1579                    unityWrap = TextureWrapMode.Clamp;
 1580                    break;
 81                case BabylonWrapMode.WRAP:
 082                    unityWrap = TextureWrapMode.Repeat;
 083                    break;
 84                case BabylonWrapMode.MIRROR:
 085                    unityWrap = TextureWrapMode.Mirror;
 86                    break;
 87            }
 88
 1589            lastVideoClipID = model.videoClipId;
 90
 1591            if (texturePlayer == null)
 92            {
 1593                DCLVideoClip dclVideoClip = scene.GetSharedComponent(lastVideoClipID) as DCLVideoClip;
 94
 1595                if (dclVideoClip == null)
 96                {
 097                    logger.LogError("DCLVideoTexture", "Wrong video clip type when playing VideoTexture!!");
 098                    yield break;
 99                }
 100
 15101                Initialize(dclVideoClip);
 102            }
 103
 15104            if (texture == null)
 105            {
 0106                while (texturePlayer.texture == null && !texturePlayer.isError)
 107                {
 0108                    yield return null;
 109                }
 110
 0111                if (texturePlayer.isError)
 112                {
 0113                    if (texturePlayerUpdateRoutine != null)
 114                    {
 0115                        CoroutineStarter.Stop(texturePlayerUpdateRoutine);
 0116                        texturePlayerUpdateRoutine = null;
 117                    }
 118
 0119                    yield break;
 120                }
 121
 0122                texture = texturePlayer.texture;
 0123                isPlayStateDirty = true;
 124            }
 125
 15126            if (texturePlayer != null)
 127            {
 15128                if (model.seek >= 0)
 129                {
 1130                    texturePlayer.SetTime(model.seek);
 1131                    model.seek = -1;
 132
 133                    // Applying seek is not immediate
 1134                    yield return null;
 135                }
 136
 15137                if (model.playing)
 138                {
 3139                    texturePlayer.Play();
 3140                }
 141                else
 142                {
 12143                    texturePlayer.Pause();
 144                }
 145
 15146                ReportVideoProgress();
 147
 15148                if (baseVolume != model.volume)
 149                {
 15150                    baseVolume = model.volume;
 15151                    UpdateVolume();
 152                }
 153
 15154                texturePlayer.SetPlaybackRate(model.playbackRate);
 15155                texturePlayer.SetLoop(model.loop);
 156            }
 15157        }
 158
 159        private void Initialize(DCLVideoClip dclVideoClip)
 160        {
 15161            string videoId = (!string.IsNullOrEmpty(scene.sceneData.id)) ? scene.sceneData.id + id : scene.GetHashCode()
 15162            texturePlayer = new WebVideoPlayer(videoId, dclVideoClip.GetUrl(), dclVideoClip.isStream, videoPluginWrapper
 15163            texturePlayerUpdateRoutine = CoroutineStarter.Start(OnUpdate());
 15164            CommonScriptableObjects.playerCoords.OnChange += OnPlayerCoordsChanged;
 15165            CommonScriptableObjects.sceneID.OnChange += OnSceneIDChanged;
 15166            scene.OnEntityRemoved += OnEntityRemoved;
 167
 15168            Settings.i.audioSettings.OnChanged += OnAudioSettingsChanged;
 169
 15170            OnSceneIDChanged(CommonScriptableObjects.sceneID.Get(), null);
 15171        }
 172
 0173        public float GetVolume() { return ((Model) model).volume; }
 174
 0175        private bool HasTexturePropertiesChanged() { return texture.wrapMode != unityWrap || texture.filterMode != unity
 176
 177        private void ApplyTextureProperties()
 178        {
 0179            texture.wrapMode = unityWrap;
 0180            texture.filterMode = unitySamplingMode;
 0181            texture.Compress(false);
 0182            texture.Apply(unitySamplingMode != FilterMode.Point, true);
 0183        }
 184
 185        private IEnumerator OnUpdate()
 186        {
 50187            while (true)
 188            {
 65189                UpdateDirtyState();
 65190                UpdateVideoTexture();
 65191                UpdateProgressReport();
 65192                yield return null;
 193            }
 194        }
 195
 196        private void UpdateDirtyState()
 197        {
 65198            if (isPlayStateDirty)
 199            {
 20200                CalculateVideoVolumeAndPlayStatus();
 20201                isPlayStateDirty = false;
 202            }
 65203        }
 204
 205        private void UpdateVideoTexture()
 206        {
 65207            if (!isPlayerInScene && currUpdateIntervalTime < OUTOFSCENE_TEX_UPDATE_INTERVAL_IN_SECONDS)
 208            {
 8209                currUpdateIntervalTime += Time.unscaledDeltaTime;
 8210            }
 57211            else if (texturePlayer != null)
 212            {
 57213                currUpdateIntervalTime = 0;
 57214                texturePlayer.Update();
 57215                texture = texturePlayer.texture;
 216            }
 57217        }
 218
 219        private void UpdateProgressReport()
 220        {
 65221            var currentState = texturePlayer.GetState();
 222
 65223            if ( currentState == VideoState.PLAYING
 224                 && IsTimeToReportVideoProgress()
 225                 || previousVideoState != currentState)
 226            {
 23227                ReportVideoProgress();
 228            }
 65229        }
 230
 231        private void ReportVideoProgress()
 232        {
 38233            lastVideoProgressReportTime = Time.unscaledTime;
 38234            VideoState videoState = texturePlayer.GetState();
 38235            previousVideoState = videoState;
 38236            var videoStatus = (int)videoState;
 38237            var currentOffset = texturePlayer.GetTime();
 38238            var length = texturePlayer.GetDuration();
 38239            WebInterface.ReportVideoProgressEvent(id, scene.sceneData.id, lastVideoClipID, videoStatus, currentOffset, l
 38240        }
 241
 242        private bool IsTimeToReportVideoProgress()
 243        {
 0244            return Time.unscaledTime - lastVideoProgressReportTime > VIDEO_PROGRESS_UPDATE_INTERVAL_IN_SECONDS;
 245        }
 246
 247        private void CalculateVideoVolumeAndPlayStatus()
 248        {
 20249            isVisible = false;
 20250            float minDistance = float.MaxValue;
 20251            distanceVolumeModifier = 0;
 252
 20253            if (attachedMaterials.Count > 0)
 254            {
 20255                using (var iterator = attachedMaterials.GetEnumerator())
 256                {
 31257                    while (iterator.MoveNext())
 258                    {
 20259                        var materialInfo = iterator.Current;
 20260                        if (materialInfo.Value.IsVisible())
 261                        {
 9262                            isVisible = true;
 9263                            var entityDist = materialInfo.Value.GetClosestDistanceSqr(DCLCharacterController.i.transform
 9264                            if (entityDist < minDistance)
 9265                                minDistance = entityDist;
 266                            // NOTE: if current minDistance is enough for full volume then there is no need to keep iter
 9267                            if (minDistance <= DCL.Configuration.ParcelSettings.PARCEL_SIZE * DCL.Configuration.ParcelSe
 9268                                break;
 269                        }
 270                    }
 11271                }
 272            }
 273
 20274            if (isVisible)
 275            {
 276                const float maxDistanceBlockForSound = 12;
 9277                float sqrParcelDistance = DCL.Configuration.ParcelSettings.PARCEL_SIZE * DCL.Configuration.ParcelSetting
 9278                distanceVolumeModifier = 1 - Mathf.Clamp01(Mathf.FloorToInt(minDistance / sqrParcelDistance) / maxDistan
 279            }
 280
 20281            if (texturePlayer != null)
 282            {
 20283                texturePlayer.visible = isVisible;
 284            }
 285
 20286            UpdateVolume();
 20287        }
 288
 0289        private void OnVirtualAudioMixerChangedValue(float currentValue, float previousValue) { UpdateVolume(); }
 290
 291        private void UpdateVolume()
 292        {
 37293            if (texturePlayer == null)
 0294                return;
 295
 37296            float targetVolume = 0f;
 297
 37298            if (CommonScriptableObjects.rendererState.Get() && IsPlayerInSameSceneAsComponent(CommonScriptableObjects.sc
 299            {
 30300                targetVolume = baseVolume * distanceVolumeModifier;
 30301                float virtualMixerVolume = DataStore.i.virtualAudioMixer.sceneSFXVolume.Get();
 30302                float sceneSFXSetting = Settings.i.audioSettings.Data.sceneSFXVolume;
 30303                float masterSetting = Settings.i.audioSettings.Data.masterVolume;
 30304                targetVolume *= Utils.ToVolumeCurve(virtualMixerVolume * sceneSFXSetting * masterSetting);
 305            }
 306
 37307            texturePlayer.SetVolume(targetVolume);
 37308        }
 309
 310        private bool IsPlayerInSameSceneAsComponent(string currentSceneId)
 311        {
 55312            if (scene == null)
 0313                return false;
 55314            if (string.IsNullOrEmpty(currentSceneId))
 0315                return false;
 316
 55317            return (scene.sceneData.id == currentSceneId) || (scene is GlobalScene globalScene && globalScene.isPortable
 318        }
 319
 8320        private void OnPlayerCoordsChanged(Vector2Int coords, Vector2Int prevCoords) { isPlayStateDirty = true; }
 321
 36322        private void OnSceneIDChanged(string current, string previous) { isPlayerInScene = IsPlayerInSameSceneAsComponen
 323
 324        public override void AttachTo(PBRMaterial material)
 325        {
 0326            base.AttachTo(material);
 0327            AttachToMaterial(material);
 0328        }
 329
 330        public override void DetachFrom(PBRMaterial material)
 331        {
 0332            base.DetachFrom(material);
 0333            DetachFromMaterial(material);
 0334        }
 335
 336        public override void AttachTo(BasicMaterial material)
 337        {
 10338            base.AttachTo(material);
 10339            AttachToMaterial(material);
 10340        }
 341
 342        public override void DetachFrom(BasicMaterial material)
 343        {
 11344            base.DetachFrom(material);
 11345            DetachFromMaterial(material);
 11346        }
 347
 348        private void AttachToMaterial(BaseDisposable baseDisposable)
 349        {
 10350            if (!attachedMaterials.ContainsKey(baseDisposable.id))
 351            {
 10352                attachedMaterials.Add(baseDisposable.id, new MaterialComponent(baseDisposable));
 10353                baseDisposable.OnAttach += OnEntityAttachedMaterial;
 10354                baseDisposable.OnDetach += OnEntityDetachedMaterial;
 10355                isPlayStateDirty = true;
 356
 10357                if (baseDisposable.attachedEntities.Count > 0)
 358                {
 8359                    using (var iterator = baseDisposable.attachedEntities.GetEnumerator())
 360                    {
 16361                        while (iterator.MoveNext())
 362                        {
 8363                            var entity = iterator.Current;
 8364                            entity.OnShapeUpdated -= OnEntityShapeUpdated;
 8365                            entity.OnShapeUpdated += OnEntityShapeUpdated;
 366                        }
 8367                    }
 368                }
 369            }
 10370        }
 371
 372        private void DetachFromMaterial(BaseDisposable baseDisposable)
 373        {
 11374            if (attachedMaterials.ContainsKey(baseDisposable.id))
 375            {
 10376                attachedMaterials.Remove(baseDisposable.id);
 10377                baseDisposable.OnAttach -= OnEntityAttachedMaterial;
 10378                baseDisposable.OnDetach -= OnEntityDetachedMaterial;
 10379                isPlayStateDirty = true;
 380            }
 11381        }
 382
 383        // TODO: we will need an event for visibility change on UI for supporting video
 384        public override void AttachTo(UIImage image)
 385        {
 0386            if (!attachedMaterials.ContainsKey(image.id))
 387            {
 0388                attachedMaterials.Add(image.id, new UIShapeComponent(image));
 0389                isPlayStateDirty = true;
 390            }
 0391        }
 392
 393        public override void DetachFrom(UIImage image)
 394        {
 0395            if (attachedMaterials.ContainsKey(image.id))
 396            {
 0397                attachedMaterials.Remove(image.id);
 0398                isPlayStateDirty = true;
 399            }
 0400        }
 401
 2402        void OnEntityRemoved(IDCLEntity entity) { isPlayStateDirty = true; }
 403
 4404        void OnAudioSettingsChanged(AudioSettings settings) { UpdateVolume(); }
 405
 406        public override void Dispose()
 407        {
 16408            DataStore.i.virtualAudioMixer.sceneSFXVolume.OnChange -= OnVirtualAudioMixerChangedValue;
 16409            Settings.i.audioSettings.OnChanged -= OnAudioSettingsChanged;
 16410            CommonScriptableObjects.playerCoords.OnChange -= OnPlayerCoordsChanged;
 16411            CommonScriptableObjects.sceneID.OnChange -= OnSceneIDChanged;
 412
 16413            if (scene != null)
 16414                scene.OnEntityRemoved -= OnEntityRemoved;
 16415            if (texturePlayerUpdateRoutine != null)
 416            {
 15417                CoroutineStarter.Stop(texturePlayerUpdateRoutine);
 15418                texturePlayerUpdateRoutine = null;
 419            }
 420
 16421            if (texturePlayer != null)
 422            {
 15423                texturePlayer.Dispose();
 15424                texturePlayer = null;
 425            }
 426
 16427            Utils.SafeDestroy(texture);
 16428            base.Dispose();
 16429        }
 430
 0431        private void OnEntityAttachedMaterial(IDCLEntity entity) { entity.OnShapeUpdated += OnEntityShapeUpdated; }
 432
 433        private void OnEntityDetachedMaterial(IDCLEntity entity)
 434        {
 1435            if (texturePlayer != null)
 1436                texturePlayer.Pause();
 437
 1438            entity.OnShapeUpdated -= OnEntityShapeUpdated;
 1439        }
 440
 28441        private void OnEntityShapeUpdated(IDCLEntity entity) { isPlayStateDirty = true; }
 442
 443        internal interface MaterialInfo
 444        {
 445            float GetClosestDistanceSqr(Vector3 fromPosition);
 446            bool IsVisible();
 447        }
 448
 449        struct MaterialComponent : MaterialInfo
 450        {
 451            BaseDisposable component;
 452
 453            float MaterialInfo.GetClosestDistanceSqr(Vector3 fromPosition)
 454            {
 9455                float dist = int.MaxValue;
 9456                if (component.attachedEntities.Count > 0)
 457                {
 9458                    using (var iterator = component.attachedEntities.GetEnumerator())
 459                    {
 18460                        while (iterator.MoveNext())
 461                        {
 9462                            var entity = iterator.Current;
 9463                            if (IsEntityVisible(entity))
 464                            {
 9465                                var entityDist = (entity.meshRootGameObject.transform.position - fromPosition).sqrMagnit
 9466                                if (entityDist < dist)
 9467                                    dist = entityDist;
 468                            }
 469                        }
 9470                    }
 471                }
 472
 9473                return dist;
 474            }
 475
 476            bool MaterialInfo.IsVisible()
 477            {
 20478                if (component.attachedEntities.Count > 0)
 479                {
 18480                    using (var iterator = component.attachedEntities.GetEnumerator())
 481                    {
 27482                        while (iterator.MoveNext())
 483                        {
 18484                            if (IsEntityVisible(iterator.Current))
 485                            {
 9486                                return true;
 487                            }
 488                        }
 9489                    }
 490                }
 491
 11492                return false;
 9493            }
 494
 495            bool IsEntityVisible(IDCLEntity entity)
 496            {
 27497                if (entity.meshesInfo == null)
 0498                    return false;
 27499                if (entity.meshesInfo.currentShape == null)
 7500                    return false;
 20501                return entity.meshesInfo.currentShape.IsVisible();
 502            }
 503
 0504            public MaterialComponent(BaseDisposable component) { this.component = component; }
 505        }
 506
 507        struct UIShapeComponent : MaterialInfo
 508        {
 509            UIShape shape;
 510
 0511            float MaterialInfo.GetClosestDistanceSqr(Vector3 fromPosition) { return 0; }
 512
 513            bool MaterialInfo.IsVisible()
 514            {
 0515                if (!((UIShape.Model) shape.GetModel()).visible)
 0516                    return false;
 0517                return IsParentVisible(shape);
 518            }
 519
 520            bool IsParentVisible(UIShape shape)
 521            {
 0522                UIShape parent = shape.parentUIComponent;
 0523                if (parent == null)
 0524                    return true;
 0525                if (parent.referencesContainer.canvasGroup.alpha == 0)
 526                {
 0527                    return false;
 528                }
 529
 0530                return IsParentVisible(parent);
 531            }
 532
 0533            public UIShapeComponent(UIShape image) { shape = image; }
 534        }
 535    }
 536}