瀏覽代碼

meeting room code moved

Flavionel 5 年之前
父節點
當前提交
4c34943956
共有 1 個文件被更改,包括 514 次插入2 次删除
  1. 514 2
      resources/js/components/partials/MeetingRoom.vue

+ 514 - 2
resources/js/components/partials/MeetingRoom.vue

@@ -1,11 +1,523 @@
 <template>
-  <div>
-      This is a meeting room
+  <div class="h-100" v-if="meeting.passwordRequired ? meeting.passwordProvided : true">
+    <div v-if="accessDialogShown">Please allow access to camera and microphone</div>
+      <div class="h-100" ref="videos">
+          <transition-group tag="div" class="video-wrapper" name="tiles">
+              <grid-layout
+                  :layout="videoGrid"
+                  :key="uniqueId"
+                  :col-num="maxCols"
+                  :max-rows="maxRows"
+                  :row-height="rowHeight"
+                  :is-draggable="true"
+                  :is-resizable="true"
+                  :verticalCompact="true"
+                  :margin="[gridPadding, gridPadding]"
+                  :use-css-transforms="true"
+              >
+                  <grid-item v-for="(video, index) in videoGrid" :key="video.id" :id="video.id" :x="video.x" :y="video.y" :w="video.w" :h="video.h" :i="video.i">
+                      <v-btn :color="'#282e38'" fab large dark class="v-btn--kick" @click="kickParcitipant(video)" v-show="!video.video.self && user.type == 'associate'">
+                          <v-icon>mdi-account-remove</v-icon>
+                      </v-btn>
+                  </grid-item>
+              </grid-layout>
+          </transition-group>
+          <!-- <div v-for="(video, index) in videos" :id="video.id" :key="video.id" @click="switchToFullSize(video, index)" :class="{'selected-high': video.id == bigMembersCountSelectedId, 'selected-low': lowMembersCountSelectedId == video.id}" class="video-container">
+          </div>-->
+      </div>
+      <button class="unscale" @click="bigMembersCountSelectedId = null" v-show="bigMembersCountSelectedId !== null">
+          <i class="mdi mdi-close"></i>
+      </button>
+      <div style="display:none" ref="otContainer">
+          <!-- Container to catch OpenTok Elements before reattaching -->
+      </div>
+      <!-- <modal-questions ref="initialModal" @questionsAnswered="getToken" @ready="ready"></modal-questions> -->
   </div>
 </template>
 
 <script>
+import { mapState } from "vuex";
+
+import VueGridLayout from "vue-grid-layout";
+
 export default {
+  components: {
+    GridLayout: VueGridLayout.GridLayout,
+    GridItem: VueGridLayout.GridItem
+  },
+  data() {
+    return {
+        readyForUse: false,
+        uniqueId: Math.floor(Math.random() * Math.floor(10000)),
+        counter: 1,
+        openTokSession: null,
+        publisher: null,
+        screenPublisher: null,
+        subscribers: [],
+        publisherReady: false,
+        accessDialogShown: false,
+        videos: [],
+        videoGrid: [],
+        bigMembersCountSelectedId: null,
+        lowMembersCountSelectedId: null,
+        maxCols: 12,
+        maxRows: 4,
+        rowHeight: 240,
+        gridPadding: 8,
+        loadingInProgress: false
+    };
+  },
+  computed: {
+    ...mapState(["meeting", "user", "session"])
+  },
+  methods: {
+    disconnect() {
+        if (!this.openTokSession) return;
+        this.openTokSession.disconnect();
+        this.openTokSession.off();
+        this.videos = [];
+        this.publisher.destroy();
+        this.publisher = null;
+        this.subscribers = [];
+        this.publisherReady = false;
+        this.$store.commit("setSessionConnectivityState", false);
+        this.openTokSession = null;
+        if (this.screenPublisher) {
+            this.screenPublisher.destroy();
+            this.screenPublisher = null;
+            this.$store.commit("setScreenShareState", null);
+        }
+    },
+    getToken(data, isAssociate) {
+        if (this.meeting.scheduledDate && !this.meeting.startedAt && this.meeting.scheduledDate > new Date() && (this.user.type === "guest" || !confirm("Meeting not started. Start it now?")))
+            return;
+        data.meetingId = this.meeting.id;
+        data.userUid = this.user.UID;
+        $.ajax({
+            url: isAssociate ? "/associate/token" : "/token",
+            method: "POST",
+            headers: {
+                "X-CSRF-TOKEN": $('meta[name="csrf-token"]').attr("content")
+            },
+            data,
+            success: data => {
+                this.$refs.initialModal.hide();
+                if (!isAssociate) {
+                    const user = Object.assign({}, this.user);
+                    user.id = data.participantId;
+                    user.name = data.participantName;
+                    user.pin = data.participantPin;
+                    this.$store.commit("setUser", user);
+                }
+                this.initializeOpenTok(data.apiKey, data.sessionId, data.token);
+            },
+            error: jXhr => {
+                if (jXhr.responseJSON && jXhr.responseJSON.errorCode) {
+                    switch (jXhr.responseJSON.errorCode) {
+                        case "LM-1":
+                            alert("Meeting not started yet.");
+                            break;
+                        case "LM-2":
+                            alert("You was kicked from this meeting.");
+                            window.location = "/";
+                            break;
+                        case "LM-3":
+                            alert("Wrong password");
+                            this.$refs.initialModal.disableWaiting();
+                            break;
+                    }
+                } else {
+                    this.$refs.initialModal.disableWaiting();
+                    alert(getSingleError(jXhr));
+                }
+            }
+        });
+    },
+    createVideoContainer() {
+        let videoCont = {};
+        videoCont.id = `${this.uniqueId}_video_${this.counter++}`;
+        if (this.videos.length == 1 && !this.lowMembersCountSelectedId) {
+            this.lowMembersCountSelectedId = videoCont.id;
+        }
+
+        this.videos.push(videoCont);
+        this.adjustVideoContainers();
+
+        return videoCont;
+    },
+    adjustVideoContainers() {
+        this.videoGrid = [];
+
+        let windowHeight = window.innerHeight;
+        this.rowHeight = (windowHeight - this.gridPadding * (this.maxRows + 1)) / this.maxRows;
+
+        let cols = Math.ceil(Math.sqrt(this.videos.length));
+        let rows = Math.ceil(this.videos.length / cols);
+
+        let elementsLastRow = this.videos.length % cols;
+        let lastNormalIndex = this.videos.length - elementsLastRow;
+
+        if (elementsLastRow) {
+            this.maxCols = cols * elementsLastRow;
+        } else {
+            this.maxCols = cols;
+        }
+
+        let colsPerElement = this.maxCols / cols;
+        let rowsPerElement = this.maxRows / rows;
+
+        let colsLastRow = Math.ceil(this.maxCols / elementsLastRow);
+
+        let cntX = 0;
+        let cntY = 0;
+
+        for (let [index, video] of this.videos.entries()) {
+            video.i = index;
+            video.x = cntX;
+            video.y = cntY;
+            video.h = rowsPerElement;
+            video.w = colsPerElement;
+            cntX += colsPerElement;
+
+            if (cntX >= this.maxCols) {
+                cntX = 0;
+                cntY += rowsPerElement;
+            }
+
+            let videoTemp = Object.assign({}, video);
+            videoTemp.video = video;
+            this.videoGrid.push(videoTemp);
+        }
+
+        /* OLD IMPLEMENTATION */
+
+        /* for (let [index, video] of this.videos.entries()) {
+            video.i = index;
+            if (index < lastNormalIndex) {
+                video.x = cntX;
+                video.y = cntY;
+                video.h = rowsPerElement;
+                video.w = colsPerElement;
+                cntX += colsPerElement;
+
+                if (cntX >= this.maxCols) {
+                    cntX = 0;
+                    cntY += rowsPerElement;
+                }
+            } else {
+                video.x = cntX;
+                video.y = cntY;
+                video.h = rowsPerElement;
+                video.w = colsLastRow;
+                cntX += colsLastRow;
+            }
+
+            let videoTemp = Object.assign({}, video);
+            this.videoGrid.push(videoTemp);
+        } */
+
+        /* let gridColumn, gridRows = ''
+
+        if(rows && cols){
+            gridColumn = `grid-template-columns: repeat(${cols}, minmax(320px, 1fr));`
+            gridRows = `grid-template-rows: repeat(${rows}, minmax(240px, 1fr));`
+        } */
+    },
+    initializeOpenTok(apiKey, sessionId, token) {
+      this.openTokSession = OT.initSession(apiKey, sessionId);
+
+      this.openTokSession.on({
+          sessionDisconnected: event => {
+              if (event.reason === "forceDisconnected") {
+                  alert("You were kicked.");
+                  if (this.user.type === "associate") {
+                      this.disconnect();
+                      this.$store.dispatch("leaveMeeting");
+                  } else {
+                      window.location = "/";
+                  }
+              }
+          }
+      });
+
+      // Create a publisher
+      this.publisher = OT.initPublisher(
+          this.$refs.otContainer,
+          {
+            insertMode: "append",
+            width: "100%",
+            height: "100%",
+            resolution: "1280x720",
+            frameRate: 30,
+            name: this.user.name,
+            style: {
+                nameDisplayMode: "on",
+                archiveStatusDisplayMode: "off"
+            }
+          },
+          error => {
+            if (error) {
+                alert(error.message);
+            } else {
+                this.publisherReady = true;
+                const cont = this.createVideoContainer();
+                this.$nextTick(() => {
+                    cont.el = $(`#${cont.id}`)[0];
+                    cont.el.appendChild(this.publisher.element);
+                    cont.obj = this.publisher;
+                    this.$set(cont, "self", true);
+                    //cont.self = true;
+                });
+            }
+          }
+      );
+
+      this.publisher.on({
+          accessDialogOpened: e => {
+            this.accessDialogShown = true;
+          },
+          accessDialogClosed: e => {
+            this.accessDialogShown = false;
+          }
+      });
+
+      this.openTokSession.on("streamCreated", event => {
+        //console.log("stream Created event");
+        let container;
+        if (event.stream.videoType === "screen") {
+          //TODO: This is
+        }
+        const subscriber = this.openTokSession.subscribe(
+          event.stream,
+          this.$refs.otContainer,
+          {
+            insertMode: "append",
+            width: "100%",
+            height: "100%",
+            style: { nameDisplayMode: "on" }
+          },
+          error => {
+              if (error) {
+                alert(error.message);
+              } else {
+                  const cont = this.createVideoContainer();
+                  this.$nextTick(() => {
+                    cont.el = $(`#${cont.id}`)[0];
+                    cont.el.appendChild(subscriber.element);
+                    cont.obj = subscriber;
+                    container = cont;
+                  });
+              }
+          }
+        );
+        subscriber.on({
+          destroyed: e => {
+            container.el.remove();
+            const index = this.videos.findIndex(v => v.id == container.id);
+            if (index >= 0) this.videos.splice(index, 1);
+            this.adjustVideoContainers();
+          }
+        });
+        this.subscribers.push(subscriber);
+      });
+
+    this.openTokSession.connect(token, error => {
+        // If the connection is successful, publish to the session
+        if (error) {
+            alert(error.message);
+        } else {
+            this.$store.commit("setSessionConnectivityState", true);
+        }
+      });
+      this.loadingInProgress = false;
+    },
+    publishToSession() {
+        this.openTokSession.publish(this.publisher, error => {
+          if (error) {
+            alert(error.message);
+          }
+        });
+    },
+    switchToFullSize(video, index) {
+        if (this.videos.length < 4) {
+          if (this.lowMembersCountSelectedId !== video.id && index !== 0) {
+            this.lowMembersCountSelectedId = video.id;
+          }
+        } else {
+          this.bigMembersCountSelectedId = video.id;
+        }
+    },
+    ready() {
+        this.$nextTick(function() {
+          this.readyForUse = true;
+          if (this.user.type === "associate") {
+            if (!this.meeting.id || this.active_menu_item.template !== "room") return;
+            this.getToken({}, true);
+            return;
+          }
+
+          if (this.meeting.scheduledDate && !this.meeting.startedAt && this.meeting.scheduledDate > new Date()) {
+            alert("Meeting not started.");
+            return;
+          }
+          let participantId = this.user.id;
+          if (!participantId) {
+            this.$refs.initialModal.show(this.meeting.passwordRequired);
+            return;
+          }
+          this.getToken({
+            participantId
+          });
+        });
+    },
+    shareScreen() {
+        if (this.screenPublisher) return;
+        this.screenPublisher = OT.initPublisher(
+            this.$refs.otContainer,
+            {
+              insertMode: "append",
+              width: "100%",
+              height: "100%",
+              resolution: "1280x720",
+              frameRate: 30,
+              videoSource: "screen",
+              name: this.user.name,
+              style: {
+                nameDisplayMode: "on",
+                archiveStatusDisplayMode: "off"
+              }
+            },
+            error => {
+                if (error) {
+                  console.error(error);
+                  if (error.code !== 1500) alert(error.message);
+                  this.screenPublisher = null;
+                } else {
+                  const cont = this.createVideoContainer();
+                  this.$nextTick(() => {
+                    cont.el = $(`#${cont.id}`)[0];
+                    cont.el.appendChild(this.screenPublisher.element);
+                    cont.obj = this.screenPublisher;
+                    // cont.self = true;
+
+                    this.$set(cont, "self", true);
+                  });
+                  this.screenPublisher.on("streamDestroyed", event => {
+                    cont.el.remove();
+                    const index = this.videos.findIndex(v => v.id == cont.id);
+                    if (index >= 0) this.videos.splice(index, 1);
+                    this.adjustVideoContainers();
+                    this.screenPublisher = null;
+                    this.$store.commit("setScreenShareState", false);
+                  });
+                  this.openTokSession.publish(this.screenPublisher, error => {
+                    if (error) {
+                      alert(error.message);
+                    }
+                  });
+                  this.$store.commit("setScreenShareState", true);
+                }
+            }
+        );
+    },
+    kickParcitipantContinue(participant) {
+        const connection = participant.video.obj.stream.connection;
+        const [type, id] = connection.data.split(":");
+        this.openTokSession.forceDisconnect(connection, error => {
+          if (error) {
+            console.error(error);
+          } else {
+            $.ajax({
+              url: `/associate/meeting/${this.meeting.id}/kick`,
+              method: "POST",
+              headers: {
+                "X-CSRF-TOKEN": $('meta[name="csrf-token"]').attr("content")
+              },
+              data: {
+                type,
+                id
+              },
+              error: jXhr => {
+                console.error(getSingleError(jXhr));
+              }
+            });
+          }
+        });
+    },
+    kickParcitipant(participant) {
+      if (confirm("Are you sure want to kick this person out of meeting?")) this.kickParcitipantContinue(participant);
+    }
+},
+watch: {
+    publisherReady(val) {
+      if (val && this.session.sessionConnected) this.publishToSession();
+    },
+    "session.sessionConnected"(val) {
+      if (val && this.publisherReady) this.publishToSession();
+    },
+    "user.is_active_and_visible"(val) {
+      if (!this.publisher || !this.publisher.publishVideo) return;
+      this.publisher.publishVideo(val);
+      this.publisher.publishAudio(val);
+    },
+    "meeting.id"(val) {
+      if (this.loadingInProgress) return;
+      this.loadingInProgress = true;
+      this.disconnect();
+
+      if (val) {
+          this.getToken({}, true);
+      } else {
+          this.loadingInProgress = false;
+      }
+    },
+    "active_menu_item.template"(val) {
+      if (this.loadingInProgress || val !== "room" || !this.meeting.id || this.openTokSession) return;
+      this.loadingInProgress = true;
+      this.getToken({}, true);
+    }
+  },
+  created() {
+    OT.checkScreenSharingCapability(response => {
+      this.$store.commit("setScreesharingAvailability", response.supported);
+    });
+  },
+  mounted() {
+    let self = this;
+
+    this.$eventBus.$on("screenShare", this.shareScreen);
+    this.$eventBus.$on("meetingRejoin", () => {
+      this.loadingInProgress = true;
+      this.disconnect();
+      this.getToken({}, true);
+    });
+
+    let width = $(window).width();
+    let height = $(window).height();
+
+    window.addEventListener("resize", function() {
+      let new_width = $(window).width();
+      let new_height = $(window).height();
+
+      if (width !== new_width || height !== new_height) {
+        self.adjustVideoContainers();
+      }
+    });
 
+    this.$eventBus.$on("leaveMeeting", () => {
+      $.ajax({
+        url: "/associate/meeting/leave",
+        method: "POST",
+        headers: {
+          "X-CSRF-TOKEN": $('meta[name="csrf-token"]').attr("content")
+        },
+        error: jXhr => {
+          console.error(getSingleError(jXhr));
+        }
+      });
+      this.$store.dispatch("leaveMeeting");
+      this.disconnect();
+    });
+  }
 }
 </script>