Skip to content
Snippets Groups Projects
media-utils.js 4.29 KiB
Newer Older
import { objectTypeForOriginAndContentType } from "../object-types";
Greg Fodor's avatar
Greg Fodor committed
import { getReticulumFetchUrl } from "./phoenix-utils";
const mediaAPIEndpoint = getReticulumFetchUrl("/api/v1/media");
const fetchContentType = async url => {
  return fetch(url, { method: "HEAD" }).then(r => r.headers.get("content-type"));
netpro2k's avatar
netpro2k committed
const contentIndexCache = new Map();
export const fetchMaxContentIndex = async (documentUrl, pageUrl) => {
netpro2k's avatar
netpro2k committed
  if (contentIndexCache.has(documentUrl)) return contentIndexCache.get(documentUrl);
  const maxIndex = await fetch(pageUrl).then(r => parseInt(r.headers.get("x-max-content-index")));
netpro2k's avatar
netpro2k committed
  contentIndexCache.set(documentUrl, maxIndex);
  return maxIndex;
};
const resolveMediaCache = new Map();
export const resolveMedia = async (url, skipContentType, index) => {
  const parsedUrl = new URL(url);
  const cacheKey = `${url}|${index}`;
  if (resolveMediaCache.has(cacheKey)) return resolveMediaCache.get(cacheKey);
  const isHttpOrHttps = parsedUrl.protocol === "http:" || parsedUrl.protocol === "https:";
  const resolved = !isHttpOrHttps
    ? { raw: url, origin: url }
    : await fetch(mediaAPIEndpoint, {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ media: { url, index } })
      }).then(r => r.json());
  if (isHttpOrHttps && !skipContentType) {
      (resolved.meta && resolved.meta.expected_content_type) || (await fetchContentType(resolved.raw));
  resolveMediaCache.set(cacheKey, resolved);
  return resolved;
export const upload = file => {
  const formData = new FormData();
  formData.append("media", file);
  return fetch(mediaAPIEndpoint, {
    method: "POST",
    body: formData
  }).then(r => r.json());
};

// https://stackoverflow.com/questions/7584794/accessing-jpeg-exif-rotation-data-in-javascript-on-the-client-side/32490603#32490603
function getOrientation(file, callback) {
  const reader = new FileReader();
  reader.onload = function(e) {
    const view = new DataView(e.target.result);
    if (view.getUint16(0, false) != 0xffd8) {
      return callback(-2);
    }
    const length = view.byteLength;
    let offset = 2;
    while (offset < length) {
      if (view.getUint16(offset + 2, false) <= 8) return callback(-1);
      const marker = view.getUint16(offset, false);
      offset += 2;
      if (marker == 0xffe1) {
        if (view.getUint32((offset += 2), false) != 0x45786966) {
          return callback(-1);
        }

        const little = view.getUint16((offset += 6), false) == 0x4949;
        offset += view.getUint32(offset + 4, little);
        const tags = view.getUint16(offset, little);
        offset += 2;
        for (let i = 0; i < tags; i++) {
          if (view.getUint16(offset + i * 12, little) == 0x0112) {
            return callback(view.getUint16(offset + i * 12 + 8, little));
          }
        }
      } else if ((marker & 0xff00) != 0xff00) {
        break;
      } else {
        offset += view.getUint16(offset, false);
      }
    }
    return callback(-1);
  };
  reader.readAsArrayBuffer(file);
}

export const addMedia = (src, template, contentOrigin, resize = false) => {
joni's avatar
joni committed
  const entity = document.createElement("a-entity");
netpro2k's avatar
netpro2k committed
  entity.id = "interactable-media-" + interactableId++;
Kevin Lee's avatar
Kevin Lee committed
  entity.setAttribute("networked", { template: template });
  entity.setAttribute("media-loader", { resize, src: typeof src === "string" ? src : "" });
joni's avatar
joni committed
  scene.appendChild(entity);
  const orientation = new Promise(function(resolve) {
    if (src instanceof File) {
      getOrientation(src, x => {
        resolve(x);
      });
    } else {
      resolve(1);
    }
  });
  if (src instanceof File) {
    upload(src)
      .then(response => {
netpro2k's avatar
netpro2k committed
        const srcUrl = new URL(response.raw);
        srcUrl.searchParams.set("token", response.meta.access_token);
        entity.setAttribute("media-loader", { src: srcUrl.href });
      })
      .catch(() => {
        entity.setAttribute("media-loader", { src: "error" });
      });
  if (contentOrigin) {
    entity.addEventListener("media_resolved", ({ detail }) => {
      const objectType = objectTypeForOriginAndContentType(contentOrigin, detail.contentType);
      scene.emit("object_spawned", { objectType });
    });
  }
  return { entity, orientation };