From 1db62c9846b005dacd1c79d1942d034ec8411535 Mon Sep 17 00:00:00 2001
From: Greg Fodor <gfodor@gmail.com>
Date: Mon, 29 Oct 2018 04:21:22 +0000
Subject: [PATCH] Use fake webrtc:// protocol for screen sharing

---
 src/components/media-views.js             | 16 +++--
 src/components/networked-video-player.css | 11 ---
 src/components/networked-video-player.js  | 67 -----------------
 src/hub.html                              | 27 -------
 src/hub.js                                |  1 -
 src/scene-entry-manager.js                | 88 ++++++++---------------
 src/utils/media-utils.js                  |  6 ++
 7 files changed, 49 insertions(+), 167 deletions(-)
 delete mode 100644 src/components/networked-video-player.css
 delete mode 100644 src/components/networked-video-player.js

diff --git a/src/components/media-views.js b/src/components/media-views.js
index 1109c304c..f82840a42 100644
--- a/src/components/media-views.js
+++ b/src/components/media-views.js
@@ -79,19 +79,27 @@ async function createGIFTexture(url) {
  * @param {string} src - Url to a video file.
  * @returns {Element} Video element.
  */
-function createVideoEl(src) {
+async function createVideoEl(src) {
   const videoEl = document.createElement("video");
   videoEl.setAttribute("playsinline", "");
   videoEl.setAttribute("webkit-playsinline", "");
   videoEl.loop = true;
   videoEl.crossOrigin = "anonymous";
-  videoEl.src = src;
+
+  if (!src.startsWith("webrtc://")) {
+    videoEl.src = src;
+  } else {
+    const streamClientId = src.substring(9);
+    const stream = await NAF.connection.adapter.getMediaStream(streamClientId, "video");
+    videoEl.srcObject = new MediaStream(stream.getVideoTracks());
+  }
+
   return videoEl;
 }
 
 function createVideoTexture(url) {
-  return new Promise((resolve, reject) => {
-    const videoEl = createVideoEl(url);
+  return new Promise(async (resolve, reject) => {
+    const videoEl = await createVideoEl(url);
 
     const texture = new THREE.VideoTexture(videoEl);
     texture.minFilter = THREE.LinearFilter;
diff --git a/src/components/networked-video-player.css b/src/components/networked-video-player.css
deleted file mode 100644
index 00dd3a897..000000000
--- a/src/components/networked-video-player.css
+++ /dev/null
@@ -1,11 +0,0 @@
-:local(.video) {
-  /* 1x1px so that Safari on iOS allows us to autoplay the video */
-  width: 1px; height: 1px; /* toggle to show debug video elements */
-  background: black;
-}
-
-:local(.container) {
-    position: absolute;
-    bottom: 0;
-    width: 10px; height: 10px; /* toggle to show debug video elements */
-}
diff --git a/src/components/networked-video-player.js b/src/components/networked-video-player.js
deleted file mode 100644
index 0fc058fcd..000000000
--- a/src/components/networked-video-player.js
+++ /dev/null
@@ -1,67 +0,0 @@
-import styles from "./networked-video-player.css";
-
-const nafConnected = function() {
-  return new Promise(resolve => {
-    NAF.clientId ? resolve() : document.body.addEventListener("connected", resolve);
-  });
-};
-
-/**
- * Instantiates and plays a network video stream, setting the video as the source material for this entity.
- * @namespace network
- * @component networked-video-player
- */
-AFRAME.registerComponent("networked-video-player", {
-  schema: {},
-  async init() {
-    await nafConnected();
-
-    const networkedEl = await NAF.utils.getNetworkedEntity(this.el);
-    if (!networkedEl) {
-      throw new Error("Video player must be added on a node, or a child of a node, with the `networked` component.");
-    }
-
-    const ownerId = networkedEl.components.networked.data.owner;
-
-    let container = document.getElementById("nvp-debug-container");
-    if (!container) {
-      container = document.createElement("div");
-      container.id = "nvp-debug-container";
-      container.classList.add(styles.container);
-      document.body.appendChild(container);
-    }
-
-    const stream = await NAF.connection.adapter.getMediaStream(ownerId, "video");
-    if (!stream) {
-      return;
-    }
-
-    const v = document.createElement("video");
-    v.id = `nvp-video-${ownerId}`;
-    // muted and autoplay so that more restrictive browsers (e.g. Safari on iOS) will actually play the video.
-    v.muted = true;
-    v.autoplay = true;
-    v.playsInline = true;
-    v.classList.add(styles.video);
-    v.srcObject = new MediaStream(stream.getVideoTracks()); // We only want the video track so make a new MediaStream
-    container.appendChild(v);
-    v.play();
-
-    this.videoEl = v;
-
-    v.onloadedmetadata = () => {
-      const ratio = v.videoWidth / v.videoHeight;
-      this.el.setAttribute("geometry", {
-        width: ratio * 1,
-        height: 1
-      });
-      this.el.setAttribute("material", "src", v);
-    };
-  },
-
-  remove() {
-    if (this.videoEl) {
-      this.videoEl.parentNode.removeChild(this.videoEl);
-    }
-  }
-});
diff --git a/src/hub.html b/src/hub.html
index e85da6fda..7c5915409 100644
--- a/src/hub.html
+++ b/src/hub.html
@@ -71,33 +71,6 @@
 
             <img id="water-normal-map" crossorigin="anonymous" src="./assets/waternormals.jpg">
 
-            <!-- Templates -->
-            <template id="screen-template">
-                <a-entity
-                    class="interactable"
-                    geometry="primitive: plane;"
-                    material="side: double; shader: flat;"
-                    super-networked-interactable="counter: #screen-counter;"
-                    body="type: dynamic; shape: none; mass: 1;"
-                    grabbable
-                    stretchable="useWorldPosition: true; usePhysics: never"
-                    hoverable
-                    auto-scale-cannon-physics-body
-                    sticky-object="autoLockOnRelease: true; autoLockOnLoad: true;"
-                    position-at-box-shape-border="target:.freeze-menu"
-                    destroy-at-extreme-distances
-                    set-yxz-order
-                    networked-video-player>
-
-                    <a-entity class="interactable-ui">
-                        <a-entity class="freeze-menu" visible-while-frozen>
-                            <a-entity mixin="rounded-text-button" remove-networked-object-button position="0 -0.125 0.01"> </a-entity>
-                            <a-entity text=" value:remove; width:1.75; align:center;" text-raycast-hack position="0 -0.125 0.02"></a-entity>
-                        </a-entity>
-                    </a-entity>
-                </a-entity>
-            </template>
-
             <template id="remote-avatar-template">
                 <a-entity networked-avatar ik-root player-info>
                     <a-entity class="camera"></a-entity>
diff --git a/src/hub.js b/src/hub.js
index 812759e57..1d74a8864 100644
--- a/src/hub.js
+++ b/src/hub.js
@@ -34,7 +34,6 @@ import "./components/ik-controller";
 import "./components/hand-controls2";
 import "./components/character-controller";
 import "./components/haptic-feedback";
-import "./components/networked-video-player";
 import "./components/offset-relative-to";
 import "./components/player-info";
 import "./components/debug";
diff --git a/src/scene-entry-manager.js b/src/scene-entry-manager.js
index e12a0bc37..6b956f587 100644
--- a/src/scene-entry-manager.js
+++ b/src/scene-entry-manager.js
@@ -62,8 +62,7 @@ export default class SceneEntryManager {
 
     this._setupPlayerRig();
     this._setupBlocking();
-    this._setupMedia();
-    this._setupScreenShare(mediaStream);
+    this._setupMedia(mediaStream);
     this._setupCamera();
 
     if (qsTruthy("offline")) return;
@@ -147,58 +146,6 @@ export default class SceneEntryManager {
     this.scene.emit("username-changed", { username: displayName });
   };
 
-  _setupScreenShare = mediaStream => {
-    let isSharing = false;
-    let isToggling = false;
-    let screenEntity = null;
-
-    this.scene.addEventListener("action_share_screen", async () => {
-      if (isToggling) return;
-      isToggling = true;
-
-      if (!isSharing) {
-        const constraints = {
-          video: {
-            mediaSource: "screen",
-            // Work around BMO 1449832 by calculating the width. This will break for multi monitors if you share anything
-            // other than your current monitor that has a different aspect ratio.
-            width: 720 * (screen.width / screen.height),
-            height: 720,
-            frameRate: 30
-          }
-        };
-
-        console.log("a");
-        const newStream = await navigator.mediaDevices.getUserMedia(constraints);
-        console.log(newStream);
-        const videoTracks = newStream ? newStream.getVideoTracks() : [];
-
-        if (videoTracks.length > 0) {
-          newStream.getVideoTracks().forEach(track => mediaStream.addTrack(track));
-          NAF.connection.adapter.setLocalMediaStream(mediaStream);
-          console.log("b");
-
-          screenEntity = document.createElement("a-entity");
-          screenEntity.setAttribute("offset-relative-to", { target: "#player-camera", offset: "0 0 -1.5" });
-          screenEntity.setAttribute("networked", { template: "#screen-template" });
-          this.scene.appendChild(screenEntity);
-          isSharing = true;
-        }
-      } else {
-        screenEntity.parentNode.removeChild(screenEntity);
-
-        for (const track of mediaStream.getVideoTracks()) {
-          mediaStream.removeTrack(track);
-        }
-
-        NAF.connection.adapter.setLocalMediaStream(mediaStream);
-        isSharing = false;
-      }
-
-      isToggling = false;
-    });
-  };
-
   _setupBlocking = () => {
     document.body.addEventListener("blocked", ev => {
       NAF.connection.entities.removeEntitiesOfClient(ev.detail.clientId);
@@ -209,10 +156,16 @@ export default class SceneEntryManager {
     });
   };
 
-  _setupMedia = () => {
+  _setupMedia = mediaStream => {
     const offset = { x: 0, y: 0, z: -1.5 };
     const spawnMediaInfrontOfPlayer = (src, contentOrigin) => {
-      const { entity, orientation } = addMedia(src, "#interactable-media", contentOrigin, true, true);
+      const { entity, orientation } = addMedia(
+        src,
+        "#interactable-media",
+        contentOrigin,
+        !(src instanceof MediaStream),
+        true
+      );
 
       orientation.then(or => {
         entity.setAttribute("offset-relative-to", {
@@ -281,6 +234,28 @@ export default class SceneEntryManager {
         }
       }
     });
+
+    this.scene.addEventListener("action_share_screen", async () => {
+      const constraints = {
+        video: {
+          mediaSource: "screen",
+          // Work around BMO 1449832 by calculating the width. This will break for multi monitors if you share anything
+          // other than your current monitor that has a different aspect ratio.
+          width: 720 * (screen.width / screen.height),
+          height: 720,
+          frameRate: 30
+        }
+      };
+
+      const newStream = await navigator.mediaDevices.getUserMedia(constraints);
+      const videoTracks = newStream ? newStream.getVideoTracks() : [];
+
+      if (videoTracks.length > 0) {
+        newStream.getVideoTracks().forEach(track => mediaStream.addTrack(track));
+        NAF.connection.adapter.setLocalMediaStream(mediaStream);
+        spawnMediaInfrontOfPlayer(mediaStream, undefined);
+      }
+    });
   };
 
   _setupCamera = () => {
@@ -295,7 +270,6 @@ export default class SceneEntryManager {
     });
 
     this.scene.addEventListener("photo_taken", e => {
-      console.log(e);
       this.hubChannel.sendMessage({ src: e.detail }, "spawn");
     });
   };
diff --git a/src/utils/media-utils.js b/src/utils/media-utils.js
index d72a453fa..4b5a78e9a 100644
--- a/src/utils/media-utils.js
+++ b/src/utils/media-utils.js
@@ -22,6 +22,8 @@ function b64EncodeUnicode(str) {
 }
 
 export const proxiedUrlFor = (url, index) => {
+  if (url.startsWith("webrtc://")) return url;
+
   // farspark doesn't know how to read '=' base64 padding characters
   const base64Url = b64EncodeUnicode(url).replace(/=+$/g, "");
   // translate base64 + to - and / to _ for URL safety
@@ -44,6 +46,7 @@ export const resolveUrl = async (url, index) => {
 };
 
 export const guessContentType = url => {
+  if (url.startsWith("webrtc://")) return "video/webrtc";
   const extension = new URL(url).pathname.split(".").pop();
   return commonKnownContentTypes[extension];
 };
@@ -126,6 +129,9 @@ export const addMedia = (src, template, contentOrigin, resolve = false, resize =
         entity.setAttribute("media-loader", { src: "error" });
       });
   }
+  if (src instanceof MediaStream) {
+    entity.setAttribute("media-loader", { src: `webrtc://${NAF.clientId}` });
+  }
 
   if (contentOrigin) {
     entity.addEventListener("media_resolved", ({ detail }) => {
-- 
GitLab