diff --git a/src/components/super-spawner.js b/src/components/super-spawner.js
index 3045dace6ad28649c5176d36a705640b2f3f92df..67e3b63fc4cc0eaf036111d6078155699f4adec7 100644
--- a/src/components/super-spawner.js
+++ b/src/components/super-spawner.js
@@ -88,7 +88,7 @@ AFRAME.registerComponent("super-spawner", {
     const thisGrabId = nextGrabId++;
     this.heldEntities.set(hand, thisGrabId);
 
-    const entity = await addMedia(this.data.src);
+    const entity = addMedia(this.data.src);
     entity.object3D.position.copy(
       this.data.useCustomSpawnPosition ? this.data.spawnPosition : this.el.object3D.position
     );
diff --git a/src/hub.html b/src/hub.html
index d31d47e24f4264d9e498af7cc6004aa9f5c2db82..70b171429ccade2a686fad03b41391fdf5247bd0 100644
--- a/src/hub.html
+++ b/src/hub.html
@@ -158,9 +158,8 @@
                 </a-entity>
             </template>
 
-            <template id="interactable-model">
+            <template id="interactable-media">
                 <a-entity
-                    gltf-model-plus="inflate: true;"
                     class="interactable"
                     super-networked-interactable="counter: #media-counter;"
                     body="type: dynamic; shape: none; mass: 1;"
@@ -179,27 +178,6 @@
                 </a-entity>
             </template>
 
-            <template id="interactable-image">
-                <a-entity
-                    class="interactable"
-                    super-networked-interactable="counter: #media-counter;"
-                    body="type: dynamic; shape: none; mass: 1;"
-                    auto-scale-cannon-physics-body
-                    grabbable
-                    stretchable="useWorldPosition: true; usePhysics: never"
-                    hoverable
-                    image-plus
-                    sticky-object="autoLockOnLoad: true; autoLockOnRelease: true;"
-                    position-at-box-shape-border="target:.delete-button;dirs:forward,back"
-                    destroy-at-extreme-distances
-                >
-                    <a-entity class="delete-button" visible-while-frozen>
-                        <a-entity mixin="rounded-text-button" remove-networked-object-button position="0 0 0"> </a-entity>
-                        <a-entity text=" value:Delete; width:2.5; align:center;" text-raycast-hack position="0 0 0.01"></a-entity>
-                    </a-entity>
-                </a-entity>
-            </template>
-
             <a-mixin id="rounded-text-button"
                 text-button="
                     haptic:#player-right-controller;
diff --git a/src/network-schemas.js b/src/network-schemas.js
index e471c3b013c84c6805aa3d73b89831f2e8a95f18..5993934a4b0c613ca5b311d14c585fda7a44856c 100644
--- a/src/network-schemas.js
+++ b/src/network-schemas.js
@@ -82,7 +82,7 @@ function registerNetworkSchemas() {
   });
 
   NAF.schemas.add({
-    template: "#interactable-image",
+    template: "#interactable-media",
     components: [
       {
         component: "position",
@@ -93,23 +93,7 @@ function registerNetworkSchemas() {
         requiresNetworkUpdate: vectorRequiresUpdate(0.5)
       },
       "scale",
-      "image-plus"
-    ]
-  });
-
-  NAF.schemas.add({
-    template: "#interactable-model",
-    components: [
-      {
-        component: "position",
-        requiresNetworkUpdate: vectorRequiresUpdate(0.001)
-      },
-      {
-        component: "rotation",
-        requiresNetworkUpdate: vectorRequiresUpdate(0.5)
-      },
-      "scale",
-      "gltf-model-plus"
+      "media-loader"
     ]
   });
 }
diff --git a/src/utils/media-utils.js b/src/utils/media-utils.js
index 52b0c85787c7a0db789fbdde9ee45236ec1ca80a..5749035d5ea11b371e12891b990c8493c9e2369a 100644
--- a/src/utils/media-utils.js
+++ b/src/utils/media-utils.js
@@ -24,17 +24,17 @@ let interactableId = 0;
 const offset = { x: 0, y: 0, z: -1.5 };
 export const spawnNetworkedImage = (entity, src, contentType) => {
   entity.id = "interactable-image-" + interactableId++;
-  entity.setAttribute("networked", { template: "#interactable-image" });
-  entity.addEventListener("image-loaded", function onImageLoaded() {
-    entity.removeEventListener("image-loaded", onImageLoaded);
-  });
+  // entity.setAttribute("networked", { template: "#interactable-image" });
+  // entity.addEventListener("image-loaded", function onBodyLoaded() {
+  //   entity.removeEventListener("image-loaded", onBodyLoaded);
+  // });
   entity.setAttribute("image-plus", { src, contentType });
   return entity;
 };
 
 export const spawnNetworkedInteractable = (entity, src, basePath) => {
   entity.id = "interactable-model-" + interactableId++;
-  entity.setAttribute("networked", { template: "#interactable-model" });
+  // entity.setAttribute("networked", { template: "#interactable-model" });
   entity.addEventListener("model-loaded", function onModelLoaded(evt) {
     entity.removeEventListener("model-loaded", onModelLoaded);
     setShapeAndScale(entity, evt.detail.didInflate);
@@ -43,42 +43,60 @@ export const spawnNetworkedInteractable = (entity, src, basePath) => {
   return entity;
 };
 
-export const addMedia = async url => {
+AFRAME.registerComponent("media-loader", {
+  schema: {
+    src: { type: "string" }
+  },
+
+  async update() {
+    const entity = this.el;
+    const url = this.data.src;
+
+    try {
+      // show loading mesh
+      entity.setObject3D("mesh", new THREE.Mesh(new THREE.BoxGeometry(), new THREE.MeshBasicMaterial()));
+      setShapeAndScale(entity);
+
+      const { raw, origin, meta } = await resolveFarsparkUrl(url);
+      console.log("resolved", url, raw, origin, meta);
+
+      const contentType = (meta && meta.expected_content_type) || (await fetchContentType(raw));
+      if (contentType.startsWith("image/") || contentType.startsWith("video/")) {
+        return spawnNetworkedImage(entity, raw, contentType);
+      } else if (contentType.startsWith("model/gltf") || url.endsWith(".gltf") || url.endsWith(".glb")) {
+        return spawnNetworkedInteractable(entity, raw, THREE.LoaderUtils.extractUrlBase(origin));
+      } else {
+        throw new Error(`Unsupported content type: ${contentType}`);
+      }
+    } catch (e) {
+      console.error("Error adding media", e);
+      return spawnNetworkedImage(entity, "error");
+    }
+  }
+});
+
+export const addMedia = url => {
   const scene = AFRAME.scenes[0];
 
   const entity = document.createElement("a-entity");
-  entity.setObject3D("mesh", new THREE.Mesh(new THREE.BoxGeometry(), new THREE.MeshBasicMaterial()));
-  entity.classList.add("interactable");
-  entity.setAttribute("body", { type: "dynamic", shape: "none", mass: "1" });
-  entity.setAttribute("grabbable", "");
-  entity.setAttribute("hoverable", "");
-  entity.setAttribute("stretchable", { useWorldPosition: true, usePhysics: "never" });
-  entity.setAttribute("sticky-object", { autoLockOnRelease: true, autoLockOnLoad: true });
-  entity.setAttribute("destroy-at-extreme-distances", "");
+  entity.setAttribute("networked", { template: "#interactable-media" });
+  // entity.setObject3D("mesh", new THREE.Mesh(new THREE.BoxGeometry(), new THREE.MeshBasicMaterial()));
+  // entity.classList.add("interactable");
+  // entity.setAttribute("body", { type: "dynamic", shape: "none", mass: "1" });
+  // entity.setAttribute("grabbable", "");
+  // entity.setAttribute("hoverable", "");
+  // entity.setAttribute("stretchable", { useWorldPosition: true, usePhysics: "never" });
+  // entity.setAttribute("sticky-object", { autoLockOnRelease: true, autoLockOnLoad: true });
+  // entity.setAttribute("destroy-at-extreme-distances", "");
   entity.setAttribute("offset-relative-to", {
     target: "#player-camera",
     offset: offset,
     selfDestruct: true
   });
-  setShapeAndScale(entity);
+  entity.setAttribute("media-loader", { src: url });
+  // setShapeAndScale(entity);
   scene.appendChild(entity);
-
-  try {
-    const { raw, origin, meta } = await resolveFarsparkUrl(url);
-    console.log("resolved", url, raw, origin, meta);
-
-    const contentType = (meta && meta.expected_content_type) || (await fetchContentType(raw));
-    if (contentType.startsWith("image/") || contentType.startsWith("video/")) {
-      return spawnNetworkedImage(entity, raw, contentType);
-    } else if (contentType.startsWith("model/gltf") || url.endsWith(".gltf") || url.endsWith(".glb")) {
-      return spawnNetworkedInteractable(entity, raw, THREE.LoaderUtils.extractUrlBase(origin));
-    } else {
-      throw new Error(`Unsupported content type: ${contentType}`);
-    }
-  } catch (e) {
-    console.error("Error adding media", e);
-    return spawnNetworkedImage(entity, "error");
-  }
+  return entity;
 };
 
 function setShapeAndScale(entity, didInflate) {