diff --git a/.env.defaults b/.env.defaults
index 059fe70a2e8f383e7122e640f41ce4fc31dff595..01516302b442854410acdbf4067d42d5341034b2 100644
--- a/.env.defaults
+++ b/.env.defaults
@@ -11,6 +11,10 @@ RETICULUM_SERVER="dev.reticulum.io"
 # The root URL under which Hubs expects environment GLTF bundles to be served.
 ASSET_BUNDLE_SERVER="https://asset-bundles-prod.reticulum.io"
 
+# A comma-separated list of environment URLs to make available in the picker, besides the defaults.
+# These URLs are expected to be relative to ASSET_BUNDLE_SERVER.
+EXTRA_ENVIRONMENTS=
+
 # The root URL under which Hubs expects static assets to be served.
 BASE_ASSETS_PATH=/
 
diff --git a/src/assets/environments/environments.js b/src/assets/environments/environments.js
index 74c00f1f125dc202d49127adb29ce87d95bb1db9..07ca2aa30f7f5223baed69a7b8c775ac5477d85a 100644
--- a/src/assets/environments/environments.js
+++ b/src/assets/environments/environments.js
@@ -1,9 +1,12 @@
-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/wideopenspace/WideOpenSpace.bundle.json"
+const BASE_ENVIRONMENTS = [
+  "/rooms/meetingroom/MeetingRoom.bundle.json",
+  "/rooms/atrium/Atrium.bundle.json",
+  "/rooms/MedievalFantasyBook/MedievalFantasyBook.bundle.json",
+  "/rooms/rooftopbuilding1/RooftopBuilding1.bundle.json",
+  "/rooms/wideopenspace/WideOpenSpace.bundle.json"
 ];
+const EXTRA_ENVIRONMENTS = process.env.EXTRA_ENVIRONMENTS ? process.env.EXTRA_ENVIRONMENTS.split(",") : [];
+const ALL_ENVIRONMENTS = EXTRA_ENVIRONMENTS.concat(BASE_ENVIRONMENTS);
 
+export const ENVIRONMENT_URLS = ALL_ENVIRONMENTS.map(x => process.env.ASSET_BUNDLE_SERVER + x);
 export const DEFAULT_ENVIRONMENT_URL = ENVIRONMENT_URLS[0];
diff --git a/src/components/gltf-model-plus.js b/src/components/gltf-model-plus.js
index 58a21f90fa09ea0a3ff4296fb82d3cf9f226ffd0..b134eba9747b628fff41b3882a995413c429779d 100644
--- a/src/components/gltf-model-plus.js
+++ b/src/components/gltf-model-plus.js
@@ -1,5 +1,4 @@
 import SketchfabZipWorker from "../workers/sketchfab-zip.worker.js";
-import { resolveMedia } from "../utils/media-utils";
 import cubeMapPosX from "../assets/images/cubemap/posx.jpg";
 import cubeMapNegX from "../assets/images/cubemap/negx.jpg";
 import cubeMapPosY from "../assets/images/cubemap/posy.jpg";
@@ -209,25 +208,8 @@ async function loadEnvMap() {
   return texture;
 }
 
-async function resolveGLTFUri(gltfProperty, basePath) {
-  const url = new URL(gltfProperty.uri, basePath);
-
-  if (url.protocol === "blob:") {
-    gltfProperty.uri = url.href;
-  } else {
-    const { raw } = await resolveMedia(url.href, true);
-    gltfProperty.uri = raw;
-  }
-}
-
 async function loadGLTF(src, contentType, preferredTechnique, onProgress) {
-  const resolved = await resolveMedia(src);
-  const raw = resolved.raw;
-  const origin = resolved.origin;
-  contentType = contentType || resolved.contentType;
-  const basePath = THREE.LoaderUtils.extractUrlBase(origin);
-
-  let gltfUrl = raw;
+  let gltfUrl = src;
   let fileMap;
 
   if (contentType.includes("model/gltf+zip") || contentType.includes("application/x-zip-compressed")) {
@@ -236,34 +218,11 @@ async function loadGLTF(src, contentType, preferredTechnique, onProgress) {
   }
 
   const gltfLoader = new THREE.GLTFLoader();
-  gltfLoader.setPath(basePath);
   gltfLoader.setLazy(true);
 
   const { parser } = await new Promise((resolve, reject) => gltfLoader.load(gltfUrl, resolve, onProgress, reject));
 
-  const json = parser.json;
-  const images = json.images;
-  const buffers = json.buffers;
-  const materials = json.materials;
-
-  const pendingFarsparkPromises = [];
-
-  if (images) {
-    for (const image of images) {
-      if (image.uri) {
-        pendingFarsparkPromises.push(resolveGLTFUri(image, parser.options.path));
-      }
-    }
-  }
-
-  if (buffers) {
-    for (const buffer of buffers) {
-      if (buffer.uri) {
-        pendingFarsparkPromises.push(resolveGLTFUri(buffer, parser.options.path));
-      }
-    }
-  }
-
+  const materials = parser.json.materials;
   if (materials) {
     for (let i = 0; i < materials.length; i++) {
       const material = materials[i];
@@ -283,8 +242,6 @@ async function loadGLTF(src, contentType, preferredTechnique, onProgress) {
     CachedEnvMapTexture = loadEnvMap();
   }
 
-  await Promise.all(pendingFarsparkPromises);
-
   const gltf = await new Promise((resolve, reject) =>
     parser.parse(
       (scene, scenes, cameras, animations, json) => {
diff --git a/src/components/media-loader.js b/src/components/media-loader.js
index 071d1fc4d55f87492171e494f8786bb2657b8da9..916e8c796b4545927446f97d5c57e27bf033b173 100644
--- a/src/components/media-loader.js
+++ b/src/components/media-loader.js
@@ -155,8 +155,8 @@ AFRAME.registerComponent("media-loader", {
         );
         this.el.addEventListener("model-error", this.onError, { once: true });
         this.el.setAttribute("gltf-model-plus", {
-          src,
-          contentType,
+          src: raw,
+          contentType: contentType,
           inflate: true
         });
       } else {
diff --git a/webpack.config.js b/webpack.config.js
index 4ca201bb996a3e6120c3c35d23b5fe51f4a13048..3e481d53ccfc6cbdc15365ef51d6c11978a5f6ee 100644
--- a/webpack.config.js
+++ b/webpack.config.js
@@ -231,6 +231,7 @@ module.exports = (env, argv) => ({
         JANUS_SERVER: process.env.JANUS_SERVER,
         RETICULUM_SERVER: process.env.RETICULUM_SERVER,
         ASSET_BUNDLE_SERVER: process.env.ASSET_BUNDLE_SERVER,
+        EXTRA_ENVIRONMENTS: process.env.EXTRA_ENVIRONMENTS,
         BUILD_VERSION: process.env.BUILD_VERSION
       })
     })