< 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:186
Uncovered lines:59
Coverable lines:245
Total lines:530
Line coverage:75.9% (186 of 245)
Covered branches:0
Total branches:0

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity NPath complexity Sequence coverage
DCLVideoTexture()0%110100%
Model()0%110100%
GetDataFromJSON(...)0%110100%
DCLVideoTexture()0%110100%
ApplyChanges()0%40.1923068.09%
Initialize(...)0%220100%
GetVolume()0%2100%
HasTexturePropertiesChanged()0%6200%
ApplyTextureProperties()0%2100%
OnUpdate()0%330100%
UpdateDirtyState()0%220100%
UpdateVideoTexture()0%9.665042.86%
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    {
 16#if UNITY_EDITOR
 117        internal static bool isTest = true;
 18#else
 19        internal static bool isTest = false;
 20#endif
 21
 22        private const float OUTOFSCENE_TEX_UPDATE_INTERVAL_IN_SECONDS = 1.5f;
 23        private const float VIDEO_PROGRESS_UPDATE_INTERVAL_IN_SECONDS = 1f;
 24
 25        [System.Serializable]
 26        new public class Model : BaseModel
 27        {
 28            public string videoClipId;
 29            public bool playing = false;
 4530            public float volume = 1f;
 4531            public float playbackRate = 1f;
 32            public bool loop = false;
 4533            public float seek = -1;
 34            public BabylonWrapMode wrap = BabylonWrapMode.CLAMP;
 4535            public FilterMode samplingMode = FilterMode.Bilinear;
 36
 1537            public override BaseModel GetDataFromJSON(string json) { return Utils.SafeFromJson<Model>(json); }
 38        }
 39
 40        internal WebVideoPlayer texturePlayer;
 41        private Coroutine texturePlayerUpdateRoutine;
 42        private float baseVolume;
 1543        private float distanceVolumeModifier = 1f;
 44        private bool isPlayStateDirty = false;
 45        internal bool isVisible = false;
 46
 1547        private bool isPlayerInScene = true;
 1548        private float currUpdateIntervalTime = OUTOFSCENE_TEX_UPDATE_INTERVAL_IN_SECONDS;
 49        private float lastVideoProgressReportTime;
 50
 1551        internal Dictionary<string, MaterialInfo> attachedMaterials = new Dictionary<string, MaterialInfo>();
 52        private string lastVideoClipID;
 53        private VideoState previousVideoState;
 54
 1555        public DCLVideoTexture()
 56        {
 1557            model = new Model();
 58
 1559            DataStore.i.virtualAudioMixer.sceneSFXVolume.OnChange += OnVirtualAudioMixerChangedValue;
 1560        }
 61
 62        public override IEnumerator ApplyChanges(BaseModel newModel)
 63        {
 3064            yield return new WaitUntil(() => CommonScriptableObjects.rendererState.Get());
 65
 66            //If the scene creates and destroy the component before our renderer has been turned on bad things happen!
 67            //TODO: Analyze if we can catch this upstream and stop the IEnumerator
 1568            if (isDisposed)
 069                yield break;
 70
 1571            var model = (Model) newModel;
 72
 1573            unitySamplingMode = model.samplingMode;
 74
 1575            switch (model.wrap)
 76            {
 77                case BabylonWrapMode.CLAMP:
 1578                    unityWrap = TextureWrapMode.Clamp;
 1579                    break;
 80                case BabylonWrapMode.WRAP:
 081                    unityWrap = TextureWrapMode.Repeat;
 082                    break;
 83                case BabylonWrapMode.MIRROR:
 084                    unityWrap = TextureWrapMode.Mirror;
 85                    break;
 86            }
 1587            lastVideoClipID = model.videoClipId;
 88
 1589            if (texturePlayer == null)
 90            {
 1591                DCLVideoClip dclVideoClip = scene.GetSharedComponent(lastVideoClipID) as DCLVideoClip;
 92
 1593                if (dclVideoClip == null)
 94                {
 095                    Debug.LogError("Wrong video clip type when playing VideoTexture!!");
 096                    yield break;
 97                }
 98
 1599                Initialize(dclVideoClip);
 100            }
 101
 102            // NOTE: create texture for testing cause real texture will only be created on web platform
 15103            if (isTest)
 104            {
 15105                if (texture == null)
 15106                    texture = new Texture2D(1, 1);
 107            }
 108
 15109            if (texture == null)
 110            {
 0111                while (texturePlayer.texture == null && !texturePlayer.isError)
 112                {
 0113                    yield return null;
 114                }
 115
 0116                if (texturePlayer.isError)
 117                {
 0118                    if (texturePlayerUpdateRoutine != null)
 119                    {
 0120                        CoroutineStarter.Stop(texturePlayerUpdateRoutine);
 0121                        texturePlayerUpdateRoutine = null;
 122                    }
 123
 0124                    yield break;
 125                }
 126
 0127                texture = texturePlayer.texture;
 0128                isPlayStateDirty = true;
 129            }
 130
 15131            if (texturePlayer != null)
 132            {
 15133                if (model.seek >= 0)
 134                {
 1135                    texturePlayer.SetTime(model.seek);
 1136                    model.seek = -1;
 137
 138                    // Applying seek is not immediate
 1139                    yield return null;
 140                }
 141
 15142                if (model.playing)
 143                {
 3144                    texturePlayer.Play();
 3145                }
 146                else
 147                {
 12148                    texturePlayer.Pause();
 149                }
 150
 15151                ReportVideoProgress();
 152
 15153                if (baseVolume != model.volume)
 154                {
 15155                    baseVolume = model.volume;
 15156                    UpdateVolume();
 157                }
 158
 15159                texturePlayer.SetPlaybackRate(model.playbackRate);
 15160                texturePlayer.SetLoop(model.loop);
 161            }
 15162        }
 163        private void Initialize(DCLVideoClip dclVideoClip)
 164        {
 15165            string videoId = (!string.IsNullOrEmpty(scene.sceneData.id)) ? scene.sceneData.id + id : scene.GetHashCode()
 15166            texturePlayer = new WebVideoPlayer(videoId, dclVideoClip.GetUrl(), dclVideoClip.isStream, new WebVideoPlayer
 15167            texturePlayerUpdateRoutine = CoroutineStarter.Start(OnUpdate());
 15168            CommonScriptableObjects.playerCoords.OnChange += OnPlayerCoordsChanged;
 15169            CommonScriptableObjects.sceneID.OnChange += OnSceneIDChanged;
 15170            scene.OnEntityRemoved += OnEntityRemoved;
 171
 15172            Settings.i.audioSettings.OnChanged += OnAudioSettingsChanged;
 173
 15174            OnSceneIDChanged(CommonScriptableObjects.sceneID.Get(), null);
 15175        }
 176
 0177        public float GetVolume() { return ((Model) model).volume; }
 178
 0179        private bool HasTexturePropertiesChanged() { return texture.wrapMode != unityWrap || texture.filterMode != unity
 180
 181        private void ApplyTextureProperties()
 182        {
 0183            texture.wrapMode = unityWrap;
 0184            texture.filterMode = unitySamplingMode;
 0185            texture.Compress(false);
 0186            texture.Apply(unitySamplingMode != FilterMode.Point, true);
 0187        }
 188
 189        private IEnumerator OnUpdate()
 190        {
 50191            while (true)
 192            {
 65193                UpdateDirtyState();
 65194                UpdateVideoTexture();
 65195                UpdateProgressReport();
 65196                yield return null;
 197            }
 198        }
 199        private void UpdateDirtyState()
 200        {
 65201            if (isPlayStateDirty)
 202            {
 20203                CalculateVideoVolumeAndPlayStatus();
 20204                isPlayStateDirty = false;
 205            }
 65206        }
 207        private void UpdateVideoTexture()
 208        {
 65209            if (!isPlayerInScene && currUpdateIntervalTime < OUTOFSCENE_TEX_UPDATE_INTERVAL_IN_SECONDS)
 210            {
 0211                currUpdateIntervalTime += Time.unscaledDeltaTime;
 0212            }
 65213            else if (texturePlayer != null && !isTest)
 214            {
 0215                currUpdateIntervalTime = 0;
 0216                texturePlayer.UpdateWebVideoTexture();
 217            }
 65218        }
 219        private void UpdateProgressReport()
 220        {
 65221            var currentState = texturePlayer.GetState();
 65222            if ( currentState == VideoState.PLAYING
 223                 && IsTimeToReportVideoProgress()
 224                 || previousVideoState != currentState)
 225            {
 15226                ReportVideoProgress();
 227            }
 65228        }
 229        private void ReportVideoProgress()
 230        {
 30231            lastVideoProgressReportTime = Time.unscaledTime;
 30232            VideoState videoState = texturePlayer.GetState();
 30233            previousVideoState = videoState;
 30234            var videoStatus = (int)videoState;
 30235            var currentOffset = texturePlayer.GetTime();
 30236            var length = texturePlayer.GetDuration();
 30237            WebInterface.ReportVideoProgressEvent(id, scene.sceneData.id, lastVideoClipID, videoStatus, currentOffset, l
 30238        }
 0239        private bool IsTimeToReportVideoProgress() { return Time.unscaledTime - lastVideoProgressReportTime > VIDEO_PROG
 240
 241        private void CalculateVideoVolumeAndPlayStatus()
 242        {
 20243            isVisible = false;
 20244            float minDistance = float.MaxValue;
 20245            distanceVolumeModifier = 0;
 246
 20247            if (attachedMaterials.Count > 0)
 248            {
 20249                using (var iterator = attachedMaterials.GetEnumerator())
 250                {
 31251                    while (iterator.MoveNext())
 252                    {
 20253                        var materialInfo = iterator.Current;
 20254                        if (materialInfo.Value.IsVisible())
 255                        {
 9256                            isVisible = true;
 9257                            var entityDist = materialInfo.Value.GetClosestDistanceSqr(DCLCharacterController.i.transform
 9258                            if (entityDist < minDistance)
 9259                                minDistance = entityDist;
 260                            // NOTE: if current minDistance is enough for full volume then there is no need to keep iter
 9261                            if (minDistance <= DCL.Configuration.ParcelSettings.PARCEL_SIZE * DCL.Configuration.ParcelSe
 9262                                break;
 263                        }
 264                    }
 11265                }
 266            }
 267
 20268            if (isVisible)
 269            {
 270                const float maxDistanceBlockForSound = 6;
 9271                float sqrParcelDistance = DCL.Configuration.ParcelSettings.PARCEL_SIZE * DCL.Configuration.ParcelSetting
 9272                distanceVolumeModifier = 1 - Mathf.Clamp01(Mathf.FloorToInt(minDistance / sqrParcelDistance) / maxDistan
 273            }
 274
 20275            if (texturePlayer != null)
 276            {
 20277                texturePlayer.visible = isVisible;
 278            }
 279
 20280            UpdateVolume();
 20281        }
 282
 0283        private void OnVirtualAudioMixerChangedValue(float currentValue, float previousValue) { UpdateVolume(); }
 284
 285        private void UpdateVolume()
 286        {
 37287            if (texturePlayer == null)
 0288                return;
 289
 37290            float targetVolume = 0f;
 291
 37292            if (CommonScriptableObjects.rendererState.Get() && IsPlayerInSameSceneAsComponent(CommonScriptableObjects.sc
 293            {
 30294                targetVolume = baseVolume * distanceVolumeModifier;
 30295                float virtualMixerVolume = DataStore.i.virtualAudioMixer.sceneSFXVolume.Get();
 30296                float sceneSFXSetting = Settings.i.audioSettings.Data.sceneSFXVolume;
 30297                float masterSetting = Settings.i.audioSettings.Data.masterVolume;
 30298                targetVolume *= Utils.ToVolumeCurve(virtualMixerVolume * sceneSFXSetting * masterSetting);
 299            }
 300
 37301            texturePlayer.SetVolume(targetVolume);
 37302        }
 303
 304        private bool IsPlayerInSameSceneAsComponent(string currentSceneId)
 305        {
 54306            if (scene == null)
 0307                return false;
 54308            if (string.IsNullOrEmpty(currentSceneId))
 0309                return false;
 310
 54311            return (scene.sceneData.id == currentSceneId) || (scene is GlobalScene globalScene && globalScene.isPortable
 312        }
 313
 8314        private void OnPlayerCoordsChanged(Vector2Int coords, Vector2Int prevCoords) { isPlayStateDirty = true; }
 315
 34316        private void OnSceneIDChanged(string current, string previous) { isPlayerInScene = IsPlayerInSameSceneAsComponen
 317
 318        public override void AttachTo(PBRMaterial material)
 319        {
 0320            base.AttachTo(material);
 0321            AttachToMaterial(material);
 0322        }
 323
 324        public override void DetachFrom(PBRMaterial material)
 325        {
 0326            base.DetachFrom(material);
 0327            DetachFromMaterial(material);
 0328        }
 329
 330        public override void AttachTo(BasicMaterial material)
 331        {
 10332            base.AttachTo(material);
 10333            AttachToMaterial(material);
 10334        }
 335
 336        public override void DetachFrom(BasicMaterial material)
 337        {
 11338            base.DetachFrom(material);
 11339            DetachFromMaterial(material);
 11340        }
 341
 342        private void AttachToMaterial(BaseDisposable baseDisposable)
 343        {
 10344            if (!attachedMaterials.ContainsKey(baseDisposable.id))
 345            {
 10346                attachedMaterials.Add(baseDisposable.id, new MaterialComponent(baseDisposable));
 10347                baseDisposable.OnAttach += OnEntityAttachedMaterial;
 10348                baseDisposable.OnDetach += OnEntityDetachedMaterial;
 10349                isPlayStateDirty = true;
 350
 10351                if (baseDisposable.attachedEntities.Count > 0)
 352                {
 8353                    using (var iterator = baseDisposable.attachedEntities.GetEnumerator())
 354                    {
 16355                        while (iterator.MoveNext())
 356                        {
 8357                            var entity = iterator.Current;
 8358                            entity.OnShapeUpdated -= OnEntityShapeUpdated;
 8359                            entity.OnShapeUpdated += OnEntityShapeUpdated;
 360                        }
 8361                    }
 362                }
 363            }
 10364        }
 365
 366        private void DetachFromMaterial(BaseDisposable baseDisposable)
 367        {
 11368            if (attachedMaterials.ContainsKey(baseDisposable.id))
 369            {
 10370                attachedMaterials.Remove(baseDisposable.id);
 10371                baseDisposable.OnAttach -= OnEntityAttachedMaterial;
 10372                baseDisposable.OnDetach -= OnEntityDetachedMaterial;
 10373                isPlayStateDirty = true;
 374            }
 11375        }
 376
 377        // TODO: we will need an event for visibility change on UI for supporting video
 378        public override void AttachTo(UIImage image)
 379        {
 0380            if (!attachedMaterials.ContainsKey(image.id))
 381            {
 0382                attachedMaterials.Add(image.id, new UIShapeComponent(image));
 0383                isPlayStateDirty = true;
 384            }
 0385        }
 386
 387        public override void DetachFrom(UIImage image)
 388        {
 0389            if (attachedMaterials.ContainsKey(image.id))
 390            {
 0391                attachedMaterials.Remove(image.id);
 0392                isPlayStateDirty = true;
 393            }
 0394        }
 395
 2396        void OnEntityRemoved(IDCLEntity entity) { isPlayStateDirty = true; }
 397
 4398        void OnAudioSettingsChanged(AudioSettings settings) { UpdateVolume(); }
 399
 400        public override void Dispose()
 401        {
 16402            DataStore.i.virtualAudioMixer.sceneSFXVolume.OnChange -= OnVirtualAudioMixerChangedValue;
 16403            Settings.i.audioSettings.OnChanged -= OnAudioSettingsChanged;
 16404            CommonScriptableObjects.playerCoords.OnChange -= OnPlayerCoordsChanged;
 16405            CommonScriptableObjects.sceneID.OnChange -= OnSceneIDChanged;
 406
 16407            if (scene != null)
 16408                scene.OnEntityRemoved -= OnEntityRemoved;
 16409            if (texturePlayerUpdateRoutine != null)
 410            {
 15411                CoroutineStarter.Stop(texturePlayerUpdateRoutine);
 15412                texturePlayerUpdateRoutine = null;
 413            }
 414
 16415            if (texturePlayer != null)
 416            {
 15417                texturePlayer.Dispose();
 15418                texturePlayer = null;
 419            }
 420
 16421            Utils.SafeDestroy(texture);
 16422            base.Dispose();
 16423        }
 424
 0425        private void OnEntityAttachedMaterial(IDCLEntity entity) { entity.OnShapeUpdated += OnEntityShapeUpdated; }
 426
 427        private void OnEntityDetachedMaterial(IDCLEntity entity)
 428        {
 1429            if (texturePlayer != null)
 1430                texturePlayer.Pause();
 431
 1432            entity.OnShapeUpdated -= OnEntityShapeUpdated;
 1433        }
 434
 28435        private void OnEntityShapeUpdated(IDCLEntity entity) { isPlayStateDirty = true; }
 436
 437        internal interface MaterialInfo
 438        {
 439            float GetClosestDistanceSqr(Vector3 fromPosition);
 440            bool IsVisible();
 441        }
 442
 443        struct MaterialComponent : MaterialInfo
 444        {
 445            BaseDisposable component;
 446
 447            float MaterialInfo.GetClosestDistanceSqr(Vector3 fromPosition)
 448            {
 9449                float dist = int.MaxValue;
 9450                if (component.attachedEntities.Count > 0)
 451                {
 9452                    using (var iterator = component.attachedEntities.GetEnumerator())
 453                    {
 18454                        while (iterator.MoveNext())
 455                        {
 9456                            var entity = iterator.Current;
 9457                            if (IsEntityVisible(entity))
 458                            {
 9459                                var entityDist = (entity.meshRootGameObject.transform.position - fromPosition).sqrMagnit
 9460                                if (entityDist < dist)
 9461                                    dist = entityDist;
 462                            }
 463                        }
 9464                    }
 465                }
 466
 9467                return dist;
 468            }
 469
 470            bool MaterialInfo.IsVisible()
 471            {
 20472                if (component.attachedEntities.Count > 0)
 473                {
 18474                    using (var iterator = component.attachedEntities.GetEnumerator())
 475                    {
 27476                        while (iterator.MoveNext())
 477                        {
 18478                            if (IsEntityVisible(iterator.Current))
 479                            {
 9480                                return true;
 481                            }
 482                        }
 9483                    }
 484                }
 485
 11486                return false;
 9487            }
 488
 489            bool IsEntityVisible(IDCLEntity entity)
 490            {
 27491                if (entity.meshesInfo == null)
 0492                    return false;
 27493                if (entity.meshesInfo.currentShape == null)
 7494                    return false;
 20495                return entity.meshesInfo.currentShape.IsVisible();
 496            }
 497
 0498            public MaterialComponent(BaseDisposable component) { this.component = component; }
 499        }
 500
 501        struct UIShapeComponent : MaterialInfo
 502        {
 503            UIShape shape;
 504
 0505            float MaterialInfo.GetClosestDistanceSqr(Vector3 fromPosition) { return 0; }
 506
 507            bool MaterialInfo.IsVisible()
 508            {
 0509                if (!((UIShape.Model) shape.GetModel()).visible)
 0510                    return false;
 0511                return IsParentVisible(shape);
 512            }
 513
 514            bool IsParentVisible(UIShape shape)
 515            {
 0516                UIShape parent = shape.parentUIComponent;
 0517                if (parent == null)
 0518                    return true;
 0519                if (parent.referencesContainer.canvasGroup.alpha == 0)
 520                {
 0521                    return false;
 522                }
 523
 0524                return IsParentVisible(parent);
 525            }
 526
 0527            public UIShapeComponent(UIShape image) { shape = image; }
 528        }
 529    }
 530}