diff --git a/src/assets/LoadingObject_Atom.glb b/src/assets/LoadingObject_Atom.glb
new file mode 100755
index 0000000000000000000000000000000000000000..d00834ee7da5c3f55aa0ec8ff252b704e25f7a28
Binary files /dev/null and b/src/assets/LoadingObject_Atom.glb differ
diff --git a/src/assets/environments/environments.js b/src/assets/environments/environments.js
index 9bf05b6d13b8cdf97ae6cfff3d7d77def96a2536..74c00f1f125dc202d49127adb29ce87d95bb1db9 100644
--- a/src/assets/environments/environments.js
+++ b/src/assets/environments/environments.js
@@ -2,7 +2,8 @@ export const ENVIRONMENT_URLS = [
   process.env.ASSET_BUNDLE_SERVER + "/rooms/meetingroom/MeetingRoom.bundle.json",
   process.env.ASSET_BUNDLE_SERVER + "/rooms/atrium/Atrium.bundle.json",
   process.env.ASSET_BUNDLE_SERVER + "/rooms/MedievalFantasyBook/MedievalFantasyBook.bundle.json",
-  process.env.ASSET_BUNDLE_SERVER + "/rooms/rooftopbuilding1/RooftopBuilding1.bundle.json"
+  process.env.ASSET_BUNDLE_SERVER + "/rooms/rooftopbuilding1/RooftopBuilding1.bundle.json",
+  process.env.ASSET_BUNDLE_SERVER + "/rooms/wideopenspace/WideOpenSpace.bundle.json"
 ];
 
 export const DEFAULT_ENVIRONMENT_URL = ENVIRONMENT_URLS[0];
diff --git a/src/components/media-loader.js b/src/components/media-loader.js
index f505d9c39bf2d7f3065fcdf2c98264f79d186af4..071d1fc4d55f87492171e494f8786bb2657b8da9 100644
--- a/src/components/media-loader.js
+++ b/src/components/media-loader.js
@@ -1,6 +1,14 @@
 import { getBox, getScaleCoefficient } from "../utils/auto-box-collider";
 import { resolveMedia, fetchMaxContentIndex } from "../utils/media-utils";
 
+import "three/examples/js/loaders/GLTFLoader";
+import loadingObjectSrc from "../assets/LoadingObject_Atom.glb";
+const gltfLoader = new THREE.GLTFLoader();
+let loadingObject;
+gltfLoader.load(loadingObjectSrc, gltf => {
+  loadingObject = gltf;
+});
+
 AFRAME.registerComponent("media-loader", {
   schema: {
     src: { type: "string" },
@@ -41,6 +49,12 @@ AFRAME.registerComponent("media-loader", {
     }
   },
 
+  tick(t, dt) {
+    if (this.loaderMixer) {
+      this.loaderMixer.update(dt / 1000);
+    }
+  },
+
   onError() {
     this.el.removeAttribute("gltf-model-plus");
     this.el.removeAttribute("media-pager");
@@ -51,14 +65,26 @@ AFRAME.registerComponent("media-loader", {
   },
 
   showLoader() {
-    const loadingObj = new THREE.Mesh(new THREE.BoxGeometry(), new THREE.MeshBasicMaterial());
-    this.el.setObject3D("mesh", loadingObj);
+    const useFancyLoader = !!loadingObject;
+    const mesh = useFancyLoader
+      ? loadingObject.scene.clone()
+      : new THREE.Mesh(new THREE.BoxGeometry(), new THREE.MeshBasicMaterial());
+    if (useFancyLoader) {
+      this.loaderMixer = new THREE.AnimationMixer(mesh);
+      this.loadingClip = this.loaderMixer.clipAction(loadingObject.animations[0]);
+      this.loadingClip.play();
+    }
+    this.el.setObject3D("mesh", mesh);
     this.setShapeAndScale(true);
     delete this.showLoaderTimeout;
   },
 
   clearLoadingTimeout() {
     clearTimeout(this.showLoaderTimeout);
+    if (this.loaderMixer) {
+      this.loadingClip.stop();
+      delete this.loaderMixer;
+    }
     delete this.showLoaderTimeout;
   },
 
diff --git a/src/components/media-views.js b/src/components/media-views.js
index a408e8ca646208636156c380cceeedde80e90a59..a6b598eb2684f11dbb0c059b25f9c62f8b54b4c0 100644
--- a/src/components/media-views.js
+++ b/src/components/media-views.js
@@ -161,6 +161,16 @@ function createImageTexture(url) {
   });
 }
 
+function disposeTexture(texture) {
+  if (texture.image instanceof HTMLVideoElement) {
+    const video = texture.image;
+    video.pause();
+    video.src = "";
+    video.load();
+  }
+  texture.dispose();
+}
+
 class TextureCache {
   cache = new Map();
 
@@ -193,13 +203,7 @@ class TextureCache {
     // console.log("release", src, cacheItem.count);
     if (cacheItem.count <= 0) {
       // Unload the video element to prevent it from continuing to play in the background
-      if (cacheItem.texture.image instanceof HTMLVideoElement) {
-        const video = cacheItem.texture.image;
-        video.pause();
-        video.src = "";
-        video.load();
-      }
-      cacheItem.texture.dispose();
+      disposeTexture(cacheItem.texture);
       this.cache.delete(src);
     }
   }
@@ -253,8 +257,12 @@ AFRAME.registerComponent("media-video", {
   },
 
   remove() {
-    if (this.data.src) {
-      textureCache.release(this.data.src);
+    if (this.mesh && this.mesh.material) {
+      disposeTexture(this.mesh.material.map);
+    }
+    if (this.video) {
+      this.video.removeEventListener("pause", this.onPauseStateChange);
+      this.video.removeEventListener("play", this.onPauseStateChange);
     }
   },
 
@@ -265,24 +273,19 @@ AFRAME.registerComponent("media-video", {
   async updateTexture(src) {
     let texture;
     try {
-      if (textureCache.has(src)) {
-        texture = textureCache.retain(src);
-      } else {
-        texture = await createVideoTexture(src);
-        texture.audioSource = this.el.sceneEl.audioListener.context.createMediaElementSource(texture.image);
-        this.video = texture.image;
+      texture = await createVideoTexture(src);
 
-        this.video.addEventListener("pause", this.onPauseStateChange);
-        this.video.addEventListener("play", this.onPauseStateChange);
+      // No way to cancel promises, so if src has changed while we were creating the texture just throw it away.
+      if (this.data.src !== src) {
+        disposeTexture(texture);
+        return;
+      }
 
-        textureCache.set(src, texture);
+      texture.audioSource = this.el.sceneEl.audioListener.context.createMediaElementSource(texture.image);
+      this.video = texture.image;
 
-        // No way to cancel promises, so if src has changed while we were creating the texture just throw it away.
-        if (this.data.src !== src) {
-          textureCache.release(src);
-          return;
-        }
-      }
+      this.video.addEventListener("pause", this.onPauseStateChange);
+      this.video.addEventListener("play", this.onPauseStateChange);
 
       const sound = new THREE.PositionalAudio(this.el.sceneEl.audioListener);
       sound.setNodeSource(texture.audioSource);
@@ -325,12 +328,10 @@ AFRAME.registerComponent("media-video", {
 
     if (!src || src === oldData.src) return;
 
-    if (this.mesh && this.mesh.map) {
+    this.remove();
+    if (this.mesh && this.mesh.material) {
       this.mesh.material.map = null;
       this.mesh.material.needsUpdate = true;
-      if (this.mesh.map !== errorTexture) {
-        textureCache.release(oldData.src);
-      }
     }
 
     this.updateTexture(src);
diff --git a/src/gltf-component-mappings.js b/src/gltf-component-mappings.js
index 66cc0b95db7624a190e2c02aac6c0e735eae4558..e814ab700b634a19732317e98ccd29bf8df72fe4 100644
--- a/src/gltf-component-mappings.js
+++ b/src/gltf-component-mappings.js
@@ -9,6 +9,7 @@ AFRAME.GLTFModelPlus.registerComponent("css-class", "css-class");
 AFRAME.GLTFModelPlus.registerComponent("scene-shadow", "scene-shadow");
 AFRAME.GLTFModelPlus.registerComponent("super-spawner", "super-spawner");
 AFRAME.GLTFModelPlus.registerComponent("gltf-model-plus", "gltf-model-plus");
+AFRAME.GLTFModelPlus.registerComponent("media-loader", "media-loader");
 AFRAME.GLTFModelPlus.registerComponent("body", "body");
 AFRAME.GLTFModelPlus.registerComponent("hide-when-quality", "hide-when-quality");
 AFRAME.GLTFModelPlus.registerComponent("light", "light");