< 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:195
Uncovered lines:49
Coverable lines:244
Total lines:533
Line coverage:79.9% (195 of 244)
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%23.5419076.74%
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%110100%
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 Logger logger = new Logger("DCLVideoTexture") { verboseEnabled = VERBOSE };
 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.Error( "Wrong video clip type when playing VideoTexture!!");
 098                    yield break;
 99                }
 100
 15101                Initialize(dclVideoClip);
 102            }
 103
 15104            if (texture == null)
 105            {
 477106                yield return new WaitUntil(() => texturePlayer == null || ((texturePlayer.texture != null && texturePlay
 107
 15108                if (texturePlayer.isError)
 109                {
 0110                    if (texturePlayerUpdateRoutine != null)
 111                    {
 0112                        CoroutineStarter.Stop(texturePlayerUpdateRoutine);
 0113                        texturePlayerUpdateRoutine = null;
 114                    }
 115
 0116                    yield break;
 117                }
 118
 15119                texture = texturePlayer.texture;
 15120                isPlayStateDirty = true;
 121            }
 122
 15123            if (texturePlayer != null)
 124            {
 15125                if (model.seek >= 0)
 126                {
 1127                    texturePlayer.SetTime(model.seek);
 1128                    model.seek = -1;
 129
 130                    // Applying seek is not immediate
 1131                    yield return null;
 132                }
 133
 15134                if (model.playing)
 135                {
 3136                    texturePlayer.Play();
 3137                }
 138                else
 139                {
 12140                    texturePlayer.Pause();
 141                }
 142
 15143                ReportVideoProgress();
 144
 15145                if (baseVolume != model.volume)
 146                {
 15147                    baseVolume = model.volume;
 15148                    UpdateVolume();
 149                }
 150
 15151                texturePlayer.SetPlaybackRate(model.playbackRate);
 15152                texturePlayer.SetLoop(model.loop);
 153            }
 15154        }
 155
 156        private void Initialize(DCLVideoClip dclVideoClip)
 157        {
 15158            string videoId = (!string.IsNullOrEmpty(scene.sceneData.id)) ? scene.sceneData.id + id : scene.GetHashCode()
 15159            texturePlayer = new WebVideoPlayer(videoId, dclVideoClip.GetUrl(), dclVideoClip.isStream, videoPluginWrapper
 15160            texturePlayerUpdateRoutine = CoroutineStarter.Start(OnUpdate());
 15161            CommonScriptableObjects.playerCoords.OnChange += OnPlayerCoordsChanged;
 15162            CommonScriptableObjects.sceneID.OnChange += OnSceneIDChanged;
 15163            scene.OnEntityRemoved += OnEntityRemoved;
 164
 15165            Settings.i.audioSettings.OnChanged += OnAudioSettingsChanged;
 166
 15167            OnSceneIDChanged(CommonScriptableObjects.sceneID.Get(), null);
 15168        }
 169
 0170        public float GetVolume() { return ((Model) model).volume; }
 171
 0172        private bool HasTexturePropertiesChanged() { return texture.wrapMode != unityWrap || texture.filterMode != unity
 173
 174        private void ApplyTextureProperties()
 175        {
 0176            texture.wrapMode = unityWrap;
 0177            texture.filterMode = unitySamplingMode;
 0178            texture.Compress(false);
 0179            texture.Apply(unitySamplingMode != FilterMode.Point, true);
 0180        }
 181
 182        private IEnumerator OnUpdate()
 183        {
 514184            while (true)
 185            {
 529186                UpdateDirtyState();
 529187                UpdateVideoTexture();
 529188                UpdateProgressReport();
 529189                yield return null;
 190            }
 191        }
 192
 193        private void UpdateDirtyState()
 194        {
 529195            if (isPlayStateDirty)
 196            {
 35197                CalculateVideoVolumeAndPlayStatus();
 35198                isPlayStateDirty = false;
 199            }
 529200        }
 201
 202        private void UpdateVideoTexture()
 203        {
 529204            if (!isPlayerInScene && currUpdateIntervalTime < OUTOFSCENE_TEX_UPDATE_INTERVAL_IN_SECONDS)
 205            {
 458206                currUpdateIntervalTime += Time.unscaledDeltaTime;
 458207            }
 71208            else if (texturePlayer != null)
 209            {
 71210                currUpdateIntervalTime = 0;
 71211                texturePlayer.Update();
 71212                texture = texturePlayer.texture;
 213            }
 71214        }
 215
 216        private void UpdateProgressReport()
 217        {
 529218            var currentState = texturePlayer.GetState();
 219
 529220            if ( currentState == VideoState.PLAYING
 221                 && IsTimeToReportVideoProgress()
 222                 || previousVideoState != currentState)
 223            {
 30224                ReportVideoProgress();
 225            }
 529226        }
 227
 228        private void ReportVideoProgress()
 229        {
 45230            lastVideoProgressReportTime = Time.unscaledTime;
 45231            VideoState videoState = texturePlayer.GetState();
 45232            previousVideoState = videoState;
 45233            var videoStatus = (int)videoState;
 45234            var currentOffset = texturePlayer.GetTime();
 45235            var length = texturePlayer.GetDuration();
 45236            WebInterface.ReportVideoProgressEvent(id, scene.sceneData.id, lastVideoClipID, videoStatus, currentOffset, l
 45237        }
 238
 239        private bool IsTimeToReportVideoProgress()
 240        {
 8241            return Time.unscaledTime - lastVideoProgressReportTime > VIDEO_PROGRESS_UPDATE_INTERVAL_IN_SECONDS;
 242        }
 243
 244        private void CalculateVideoVolumeAndPlayStatus()
 245        {
 35246            isVisible = false;
 35247            float minDistance = float.MaxValue;
 35248            distanceVolumeModifier = 0;
 249
 35250            if (attachedMaterials.Count > 0)
 251            {
 20252                using (var iterator = attachedMaterials.GetEnumerator())
 253                {
 31254                    while (iterator.MoveNext())
 255                    {
 20256                        var materialInfo = iterator.Current;
 20257                        if (materialInfo.Value.IsVisible())
 258                        {
 9259                            isVisible = true;
 9260                            var entityDist = materialInfo.Value.GetClosestDistanceSqr(DCLCharacterController.i.transform
 9261                            if (entityDist < minDistance)
 9262                                minDistance = entityDist;
 263                            // NOTE: if current minDistance is enough for full volume then there is no need to keep iter
 9264                            if (minDistance <= DCL.Configuration.ParcelSettings.PARCEL_SIZE * DCL.Configuration.ParcelSe
 9265                                break;
 266                        }
 267                    }
 11268                }
 269            }
 270
 35271            if (isVisible)
 272            {
 273                const float maxDistanceBlockForSound = 12;
 9274                float sqrParcelDistance = DCL.Configuration.ParcelSettings.PARCEL_SIZE * DCL.Configuration.ParcelSetting
 9275                distanceVolumeModifier = 1 - Mathf.Clamp01(Mathf.FloorToInt(minDistance / sqrParcelDistance) / maxDistan
 276            }
 277
 35278            if (texturePlayer != null)
 279            {
 35280                texturePlayer.visible = isVisible;
 281            }
 282
 35283            UpdateVolume();
 35284        }
 285
 0286        private void OnVirtualAudioMixerChangedValue(float currentValue, float previousValue) { UpdateVolume(); }
 287
 288        private void UpdateVolume()
 289        {
 52290            if (texturePlayer == null)
 0291                return;
 292
 52293            float targetVolume = 0f;
 294
 52295            if (CommonScriptableObjects.rendererState.Get() && IsPlayerInSameSceneAsComponent(CommonScriptableObjects.sc
 296            {
 20297                targetVolume = baseVolume * distanceVolumeModifier;
 20298                float virtualMixerVolume = DataStore.i.virtualAudioMixer.sceneSFXVolume.Get();
 20299                float sceneSFXSetting = Settings.i.audioSettings.Data.sceneSFXVolume;
 20300                float masterSetting = Settings.i.audioSettings.Data.masterVolume;
 20301                targetVolume *= Utils.ToVolumeCurve(virtualMixerVolume * sceneSFXSetting * masterSetting);
 302            }
 303
 52304            texturePlayer.SetVolume(targetVolume);
 52305        }
 306
 307        private bool IsPlayerInSameSceneAsComponent(string currentSceneId)
 308        {
 110309            if (scene == null)
 0310                return false;
 110311            if (string.IsNullOrEmpty(currentSceneId))
 0312                return false;
 313
 110314            return (scene.sceneData.id == currentSceneId) || (scene is GlobalScene globalScene && globalScene.isPortable
 315        }
 316
 68317        private void OnPlayerCoordsChanged(Vector2Int coords, Vector2Int prevCoords) { isPlayStateDirty = true; }
 318
 116319        private void OnSceneIDChanged(string current, string previous) { isPlayerInScene = IsPlayerInSameSceneAsComponen
 320
 321        public override void AttachTo(PBRMaterial component)
 322        {
 0323            base.AttachTo(component);
 0324            AttachToMaterial(component);
 0325        }
 326
 327        public override void DetachFrom(PBRMaterial component)
 328        {
 0329            base.DetachFrom(component);
 0330            DetachFromMaterial(component);
 0331        }
 332
 333        public override void AttachTo(BasicMaterial component)
 334        {
 10335            base.AttachTo(component);
 10336            AttachToMaterial(component);
 10337        }
 338
 339        public override void DetachFrom(BasicMaterial component)
 340        {
 1341            base.DetachFrom(component);
 1342            DetachFromMaterial(component);
 1343        }
 344
 345        private void AttachToMaterial(BaseDisposable baseDisposable)
 346        {
 10347            if (!attachedMaterials.ContainsKey(baseDisposable.id))
 348            {
 10349                attachedMaterials.Add(baseDisposable.id, new MaterialComponent(baseDisposable));
 10350                baseDisposable.OnAttach += OnEntityAttachedMaterial;
 10351                baseDisposable.OnDetach += OnEntityDetachedMaterial;
 10352                isPlayStateDirty = true;
 353
 10354                if (baseDisposable.attachedEntities.Count > 0)
 355                {
 8356                    using (var iterator = baseDisposable.attachedEntities.GetEnumerator())
 357                    {
 16358                        while (iterator.MoveNext())
 359                        {
 8360                            var entity = iterator.Current;
 8361                            entity.OnShapeUpdated -= OnEntityShapeUpdated;
 8362                            entity.OnShapeUpdated += OnEntityShapeUpdated;
 363                        }
 8364                    }
 365                }
 366            }
 10367        }
 368
 369        private void DetachFromMaterial(BaseDisposable baseDisposable)
 370        {
 1371            if (attachedMaterials.ContainsKey(baseDisposable.id))
 372            {
 1373                attachedMaterials.Remove(baseDisposable.id);
 1374                baseDisposable.OnAttach -= OnEntityAttachedMaterial;
 1375                baseDisposable.OnDetach -= OnEntityDetachedMaterial;
 1376                isPlayStateDirty = true;
 377            }
 1378        }
 379
 380        // TODO: we will need an event for visibility change on UI for supporting video
 381        public override void AttachTo(UIImage component)
 382        {
 0383            if (!attachedMaterials.ContainsKey(component.id))
 384            {
 0385                attachedMaterials.Add(component.id, new UIShapeComponent(component));
 0386                isPlayStateDirty = true;
 387            }
 0388        }
 389
 390        public override void DetachFrom(UIImage component)
 391        {
 0392            if (attachedMaterials.ContainsKey(component.id))
 393            {
 0394                attachedMaterials.Remove(component.id);
 0395                isPlayStateDirty = true;
 396            }
 0397        }
 398
 2399        void OnEntityRemoved(IDCLEntity entity) { isPlayStateDirty = true; }
 400
 4401        void OnAudioSettingsChanged(AudioSettings settings) { UpdateVolume(); }
 402
 403        public override void Dispose()
 404        {
 1405            DataStore.i.virtualAudioMixer.sceneSFXVolume.OnChange -= OnVirtualAudioMixerChangedValue;
 1406            Settings.i.audioSettings.OnChanged -= OnAudioSettingsChanged;
 1407            CommonScriptableObjects.playerCoords.OnChange -= OnPlayerCoordsChanged;
 1408            CommonScriptableObjects.sceneID.OnChange -= OnSceneIDChanged;
 409
 1410            if (scene != null)
 1411                scene.OnEntityRemoved -= OnEntityRemoved;
 1412            if (texturePlayerUpdateRoutine != null)
 413            {
 1414                CoroutineStarter.Stop(texturePlayerUpdateRoutine);
 1415                texturePlayerUpdateRoutine = null;
 416            }
 417
 1418            if (texturePlayer != null)
 419            {
 1420                texturePlayer.Dispose();
 1421                texturePlayer = null;
 422            }
 423
 1424            Utils.SafeDestroy(texture);
 1425            base.Dispose();
 1426        }
 427
 0428        private void OnEntityAttachedMaterial(IDCLEntity entity) { entity.OnShapeUpdated += OnEntityShapeUpdated; }
 429
 430        private void OnEntityDetachedMaterial(IDCLEntity entity)
 431        {
 1432            if (texturePlayer != null)
 1433                texturePlayer.Pause();
 434
 1435            entity.OnShapeUpdated -= OnEntityShapeUpdated;
 1436        }
 437
 16438        private void OnEntityShapeUpdated(IDCLEntity entity) { isPlayStateDirty = true; }
 439
 440        internal interface MaterialInfo
 441        {
 442            float GetClosestDistanceSqr(Vector3 fromPosition);
 443            bool IsVisible();
 444        }
 445
 446        struct MaterialComponent : MaterialInfo
 447        {
 448            BaseDisposable component;
 449
 450            float MaterialInfo.GetClosestDistanceSqr(Vector3 fromPosition)
 451            {
 9452                float dist = int.MaxValue;
 9453                if (component.attachedEntities.Count > 0)
 454                {
 9455                    using (var iterator = component.attachedEntities.GetEnumerator())
 456                    {
 18457                        while (iterator.MoveNext())
 458                        {
 9459                            var entity = iterator.Current;
 9460                            if (IsEntityVisible(entity))
 461                            {
 9462                                var entityDist = (entity.meshRootGameObject.transform.position - fromPosition).sqrMagnit
 9463                                if (entityDist < dist)
 9464                                    dist = entityDist;
 465                            }
 466                        }
 9467                    }
 468                }
 469
 9470                return dist;
 471            }
 472
 473            bool MaterialInfo.IsVisible()
 474            {
 20475                if (component.attachedEntities.Count > 0)
 476                {
 18477                    using (var iterator = component.attachedEntities.GetEnumerator())
 478                    {
 27479                        while (iterator.MoveNext())
 480                        {
 18481                            if (IsEntityVisible(iterator.Current))
 482                            {
 9483                                return true;
 484                            }
 485                        }
 9486                    }
 487                }
 488
 11489                return false;
 9490            }
 491
 492            bool IsEntityVisible(IDCLEntity entity)
 493            {
 27494                if (entity.meshesInfo == null)
 0495                    return false;
 27496                if (entity.meshesInfo.currentShape == null)
 7497                    return false;
 20498                return entity.meshesInfo.currentShape.IsVisible();
 499            }
 500
 0501            public MaterialComponent(BaseDisposable component) { this.component = component; }
 502        }
 503
 504        struct UIShapeComponent : MaterialInfo
 505        {
 506            UIShape shape;
 507
 0508            float MaterialInfo.GetClosestDistanceSqr(Vector3 fromPosition) { return 0; }
 509
 510            bool MaterialInfo.IsVisible()
 511            {
 0512                if (!((UIShape.Model) shape.GetModel()).visible)
 0513                    return false;
 0514                return IsParentVisible(shape);
 515            }
 516
 517            bool IsParentVisible(UIShape shape)
 518            {
 0519                UIShape parent = shape.parentUIComponent;
 0520                if (parent == null)
 0521                    return true;
 0522                if (parent.referencesContainer.canvasGroup.alpha == 0)
 523                {
 0524                    return false;
 525                }
 526
 0527                return IsParentVisible(parent);
 528            }
 529
 0530            public UIShapeComponent(UIShape image) { shape = image; }
 531        }
 532    }
 533}