< 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:196
Uncovered lines:48
Coverable lines:244
Total lines:532
Line coverage:80.3% (196 of 244)
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%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%5.25080%
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            {
 1294106                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        private void Initialize(DCLVideoClip dclVideoClip)
 156        {
 15157            string videoId = (!string.IsNullOrEmpty(scene.sceneData.id)) ? scene.sceneData.id + id : scene.GetHashCode()
 15158            texturePlayer = new WebVideoPlayer(videoId, dclVideoClip.GetUrl(), dclVideoClip.isStream, videoPluginWrapper
 15159            texturePlayerUpdateRoutine = CoroutineStarter.Start(OnUpdate());
 15160            CommonScriptableObjects.playerCoords.OnChange += OnPlayerCoordsChanged;
 15161            CommonScriptableObjects.sceneID.OnChange += OnSceneIDChanged;
 15162            scene.OnEntityRemoved += OnEntityRemoved;
 163
 15164            Settings.i.audioSettings.OnChanged += OnAudioSettingsChanged;
 165
 15166            OnSceneIDChanged(CommonScriptableObjects.sceneID.Get(), null);
 15167        }
 168
 0169        public float GetVolume() { return ((Model) model).volume; }
 170
 0171        private bool HasTexturePropertiesChanged() { return texture.wrapMode != unityWrap || texture.filterMode != unity
 172
 173        private void ApplyTextureProperties()
 174        {
 0175            texture.wrapMode = unityWrap;
 0176            texture.filterMode = unitySamplingMode;
 0177            texture.Compress(false);
 0178            texture.Apply(unitySamplingMode != FilterMode.Point, true);
 0179        }
 180
 181        private IEnumerator OnUpdate()
 182        {
 1330183            while (true)
 184            {
 1345185                UpdateDirtyState();
 1345186                UpdateVideoTexture();
 1345187                UpdateProgressReport();
 1345188                yield return null;
 189            }
 190        }
 191
 192        private void UpdateDirtyState()
 193        {
 1345194            if (isPlayStateDirty)
 195            {
 35196                CalculateVideoVolumeAndPlayStatus();
 35197                isPlayStateDirty = false;
 198            }
 1345199        }
 200
 201        private void UpdateVideoTexture()
 202        {
 1345203            if (!isPlayerInScene && currUpdateIntervalTime < OUTOFSCENE_TEX_UPDATE_INTERVAL_IN_SECONDS)
 204            {
 1284205                currUpdateIntervalTime += Time.unscaledDeltaTime;
 1284206            }
 61207            else if (texturePlayer != null)
 208            {
 61209                currUpdateIntervalTime = 0;
 61210                texturePlayer.Update();
 61211                texture = texturePlayer.texture;
 212            }
 61213        }
 214
 215        private void UpdateProgressReport()
 216        {
 1345217            var currentState = texturePlayer.GetState();
 218
 1345219            if ( currentState == VideoState.PLAYING
 220                 && IsTimeToReportVideoProgress()
 221                 || previousVideoState != currentState)
 222            {
 30223                ReportVideoProgress();
 224            }
 1345225        }
 226
 227        private void ReportVideoProgress()
 228        {
 45229            lastVideoProgressReportTime = Time.unscaledTime;
 45230            VideoState videoState = texturePlayer.GetState();
 45231            previousVideoState = videoState;
 45232            var videoStatus = (int)videoState;
 45233            var currentOffset = texturePlayer.GetTime();
 45234            var length = texturePlayer.GetDuration();
 45235            WebInterface.ReportVideoProgressEvent(id, scene.sceneData.id, lastVideoClipID, videoStatus, currentOffset, l
 45236        }
 237
 238        private bool IsTimeToReportVideoProgress()
 239        {
 8240            return Time.unscaledTime - lastVideoProgressReportTime > VIDEO_PROGRESS_UPDATE_INTERVAL_IN_SECONDS;
 241        }
 242
 243        private void CalculateVideoVolumeAndPlayStatus()
 244        {
 35245            isVisible = false;
 35246            float minDistance = float.MaxValue;
 35247            distanceVolumeModifier = 0;
 248
 35249            if (attachedMaterials.Count > 0)
 250            {
 20251                using (var iterator = attachedMaterials.GetEnumerator())
 252                {
 31253                    while (iterator.MoveNext())
 254                    {
 20255                        var materialInfo = iterator.Current;
 20256                        if (materialInfo.Value.IsVisible())
 257                        {
 9258                            isVisible = true;
 9259                            var entityDist = materialInfo.Value.GetClosestDistanceSqr(DCLCharacterController.i.transform
 9260                            if (entityDist < minDistance)
 9261                                minDistance = entityDist;
 262                            // NOTE: if current minDistance is enough for full volume then there is no need to keep iter
 9263                            if (minDistance <= DCL.Configuration.ParcelSettings.PARCEL_SIZE * DCL.Configuration.ParcelSe
 9264                                break;
 265                        }
 266                    }
 11267                }
 268            }
 269
 35270            if (isVisible)
 271            {
 272                const float maxDistanceBlockForSound = 12;
 9273                float sqrParcelDistance = DCL.Configuration.ParcelSettings.PARCEL_SIZE * DCL.Configuration.ParcelSetting
 9274                distanceVolumeModifier = 1 - Mathf.Clamp01(Mathf.FloorToInt(minDistance / sqrParcelDistance) / maxDistan
 275            }
 276
 35277            if (texturePlayer != null)
 278            {
 35279                texturePlayer.visible = isVisible;
 280            }
 281
 35282            UpdateVolume();
 35283        }
 284
 0285        private void OnVirtualAudioMixerChangedValue(float currentValue, float previousValue) { UpdateVolume(); }
 286
 287        private void UpdateVolume()
 288        {
 52289            if (texturePlayer == null)
 0290                return;
 291
 52292            float targetVolume = 0f;
 293
 52294            if (CommonScriptableObjects.rendererState.Get() && IsPlayerInSameSceneAsComponent(CommonScriptableObjects.sc
 295            {
 16296                targetVolume = baseVolume * distanceVolumeModifier;
 16297                float virtualMixerVolume = DataStore.i.virtualAudioMixer.sceneSFXVolume.Get();
 16298                float sceneSFXSetting = Settings.i.audioSettings.Data.sceneSFXVolume;
 16299                float masterSetting = Settings.i.audioSettings.Data.masterVolume;
 16300                targetVolume *= Utils.ToVolumeCurve(virtualMixerVolume * sceneSFXSetting * masterSetting);
 301            }
 302
 52303            texturePlayer.SetVolume(targetVolume);
 52304        }
 305
 306        private bool IsPlayerInSameSceneAsComponent(string currentSceneId)
 307        {
 69308            if (scene == null)
 0309                return false;
 69310            if (string.IsNullOrEmpty(currentSceneId))
 6311                return false;
 312
 63313            return (scene.sceneData.id == currentSceneId) || (scene is GlobalScene globalScene && globalScene.isPortable
 314        }
 315
 8316        private void OnPlayerCoordsChanged(Vector2Int coords, Vector2Int prevCoords) { isPlayStateDirty = true; }
 317
 34318        private void OnSceneIDChanged(string current, string previous) { isPlayerInScene = IsPlayerInSameSceneAsComponen
 319
 320        public override void AttachTo(PBRMaterial material)
 321        {
 0322            base.AttachTo(material);
 0323            AttachToMaterial(material);
 0324        }
 325
 326        public override void DetachFrom(PBRMaterial material)
 327        {
 0328            base.DetachFrom(material);
 0329            DetachFromMaterial(material);
 0330        }
 331
 332        public override void AttachTo(BasicMaterial material)
 333        {
 10334            base.AttachTo(material);
 10335            AttachToMaterial(material);
 10336        }
 337
 338        public override void DetachFrom(BasicMaterial material)
 339        {
 11340            base.DetachFrom(material);
 11341            DetachFromMaterial(material);
 11342        }
 343
 344        private void AttachToMaterial(BaseDisposable baseDisposable)
 345        {
 10346            if (!attachedMaterials.ContainsKey(baseDisposable.id))
 347            {
 10348                attachedMaterials.Add(baseDisposable.id, new MaterialComponent(baseDisposable));
 10349                baseDisposable.OnAttach += OnEntityAttachedMaterial;
 10350                baseDisposable.OnDetach += OnEntityDetachedMaterial;
 10351                isPlayStateDirty = true;
 352
 10353                if (baseDisposable.attachedEntities.Count > 0)
 354                {
 8355                    using (var iterator = baseDisposable.attachedEntities.GetEnumerator())
 356                    {
 16357                        while (iterator.MoveNext())
 358                        {
 8359                            var entity = iterator.Current;
 8360                            entity.OnShapeUpdated -= OnEntityShapeUpdated;
 8361                            entity.OnShapeUpdated += OnEntityShapeUpdated;
 362                        }
 8363                    }
 364                }
 365            }
 10366        }
 367
 368        private void DetachFromMaterial(BaseDisposable baseDisposable)
 369        {
 11370            if (attachedMaterials.ContainsKey(baseDisposable.id))
 371            {
 10372                attachedMaterials.Remove(baseDisposable.id);
 10373                baseDisposable.OnAttach -= OnEntityAttachedMaterial;
 10374                baseDisposable.OnDetach -= OnEntityDetachedMaterial;
 10375                isPlayStateDirty = true;
 376            }
 11377        }
 378
 379        // TODO: we will need an event for visibility change on UI for supporting video
 380        public override void AttachTo(UIImage image)
 381        {
 0382            if (!attachedMaterials.ContainsKey(image.id))
 383            {
 0384                attachedMaterials.Add(image.id, new UIShapeComponent(image));
 0385                isPlayStateDirty = true;
 386            }
 0387        }
 388
 389        public override void DetachFrom(UIImage image)
 390        {
 0391            if (attachedMaterials.ContainsKey(image.id))
 392            {
 0393                attachedMaterials.Remove(image.id);
 0394                isPlayStateDirty = true;
 395            }
 0396        }
 397
 2398        void OnEntityRemoved(IDCLEntity entity) { isPlayStateDirty = true; }
 399
 4400        void OnAudioSettingsChanged(AudioSettings settings) { UpdateVolume(); }
 401
 402        public override void Dispose()
 403        {
 16404            DataStore.i.virtualAudioMixer.sceneSFXVolume.OnChange -= OnVirtualAudioMixerChangedValue;
 16405            Settings.i.audioSettings.OnChanged -= OnAudioSettingsChanged;
 16406            CommonScriptableObjects.playerCoords.OnChange -= OnPlayerCoordsChanged;
 16407            CommonScriptableObjects.sceneID.OnChange -= OnSceneIDChanged;
 408
 16409            if (scene != null)
 16410                scene.OnEntityRemoved -= OnEntityRemoved;
 16411            if (texturePlayerUpdateRoutine != null)
 412            {
 15413                CoroutineStarter.Stop(texturePlayerUpdateRoutine);
 15414                texturePlayerUpdateRoutine = null;
 415            }
 416
 16417            if (texturePlayer != null)
 418            {
 15419                texturePlayer.Dispose();
 15420                texturePlayer = null;
 421            }
 422
 16423            Utils.SafeDestroy(texture);
 16424            base.Dispose();
 16425        }
 426
 0427        private void OnEntityAttachedMaterial(IDCLEntity entity) { entity.OnShapeUpdated += OnEntityShapeUpdated; }
 428
 429        private void OnEntityDetachedMaterial(IDCLEntity entity)
 430        {
 1431            if (texturePlayer != null)
 1432                texturePlayer.Pause();
 433
 1434            entity.OnShapeUpdated -= OnEntityShapeUpdated;
 1435        }
 436
 28437        private void OnEntityShapeUpdated(IDCLEntity entity) { isPlayStateDirty = true; }
 438
 439        internal interface MaterialInfo
 440        {
 441            float GetClosestDistanceSqr(Vector3 fromPosition);
 442            bool IsVisible();
 443        }
 444
 445        struct MaterialComponent : MaterialInfo
 446        {
 447            BaseDisposable component;
 448
 449            float MaterialInfo.GetClosestDistanceSqr(Vector3 fromPosition)
 450            {
 9451                float dist = int.MaxValue;
 9452                if (component.attachedEntities.Count > 0)
 453                {
 9454                    using (var iterator = component.attachedEntities.GetEnumerator())
 455                    {
 18456                        while (iterator.MoveNext())
 457                        {
 9458                            var entity = iterator.Current;
 9459                            if (IsEntityVisible(entity))
 460                            {
 9461                                var entityDist = (entity.meshRootGameObject.transform.position - fromPosition).sqrMagnit
 9462                                if (entityDist < dist)
 9463                                    dist = entityDist;
 464                            }
 465                        }
 9466                    }
 467                }
 468
 9469                return dist;
 470            }
 471
 472            bool MaterialInfo.IsVisible()
 473            {
 20474                if (component.attachedEntities.Count > 0)
 475                {
 18476                    using (var iterator = component.attachedEntities.GetEnumerator())
 477                    {
 27478                        while (iterator.MoveNext())
 479                        {
 18480                            if (IsEntityVisible(iterator.Current))
 481                            {
 9482                                return true;
 483                            }
 484                        }
 9485                    }
 486                }
 487
 11488                return false;
 9489            }
 490
 491            bool IsEntityVisible(IDCLEntity entity)
 492            {
 27493                if (entity.meshesInfo == null)
 0494                    return false;
 27495                if (entity.meshesInfo.currentShape == null)
 7496                    return false;
 20497                return entity.meshesInfo.currentShape.IsVisible();
 498            }
 499
 0500            public MaterialComponent(BaseDisposable component) { this.component = component; }
 501        }
 502
 503        struct UIShapeComponent : MaterialInfo
 504        {
 505            UIShape shape;
 506
 0507            float MaterialInfo.GetClosestDistanceSqr(Vector3 fromPosition) { return 0; }
 508
 509            bool MaterialInfo.IsVisible()
 510            {
 0511                if (!((UIShape.Model) shape.GetModel()).visible)
 0512                    return false;
 0513                return IsParentVisible(shape);
 514            }
 515
 516            bool IsParentVisible(UIShape shape)
 517            {
 0518                UIShape parent = shape.parentUIComponent;
 0519                if (parent == null)
 0520                    return true;
 0521                if (parent.referencesContainer.canvasGroup.alpha == 0)
 522                {
 0523                    return false;
 524                }
 525
 0526                return IsParentVisible(parent);
 527            }
 528
 0529            public UIShapeComponent(UIShape image) { shape = image; }
 530        }
 531    }
 532}