徹夜で9時間作ってるアプリのソースコード
色々あって寝ずにコード書いてました。 現在9時間経過しています。 朝になってしまったのでソースコードだけ公開します。
<template> <div style="height: 1000px"> <header-menu></header-menu> <div class="p-dialogue"> <div class="p-dialogue__content"> <p class="p-dialogue__txtWrap"> <span class="p-dialogue__txt" v-if="!roomID"> {{`ルームを作成するか、作成済みのルームに参加してください。`}} </span> <span class="p-dialogue__txt" v-if="roomID"> {{`ルームIDは${this.roomID}です。`}} </span> <span class="p-dialogue__txt" v-if="!isStarted && roomID && isRight"> {{`十分な人数が集まったら対話を開始してください。`}} </span> <span class="p-dialogue__txt" v-if="!isStarted && roomID && !isRight"> {{`ホストが対話を開始するまで自由にご歓談ください。`}} </span> <span class="p-dialogue__txt" v-if="!roomID && isStarted"> {{`アイスブレイクタイム。お題は「お気に入りのリラックス方法」です。`}} </span> <span class="p-dialogue__txt" v-if="isTalking"> {{`質問セクションです。お題に関係のないことでも構いませんので、1人1回は質問してみましょう。`}} {{`残り時間: ${time}秒`}} </span> </p> <div class="p-dialogue__btnWrap"> <button v-if="!roomID" @click="makeRoom" class="p-dialogue__btn">ルームを作成</button> <button v-if="!roomID" @click="joinRoom" class="p-dialogue__btn">ルームに参加</button> <button v-if="!isStarted && isRight" @click="startDialogue" class="p-dialogue__btn">哲学対話を開始する</button> </div> <div class="p-dialogue__videoWrap" :class="{'p-dialogue__videoWrap--muted': isMuted}"> <video id="my-video" class="p-dialogue__video" autoplay muted playsinline> </video> <div class="p-dialogue__person" :class="{'p-dialogue__person--active': isCameraOff}"> <img src="../assets/person.svg" class="p-dialogue__person__img"> </div> <div class="p-dialogue__mic" :class="{'p-dialogue__mic--disabled': (isStarted && !isRight) && !isTalking}" @click="isMuted = !isMuted"> <img :src="require(isMuted ? '../assets/mic_off.svg' : '../assets/mic.svg')"> </div> <div class="p-dialogue__camera" @click="isCameraOff = !isCameraOff"> <img :src="require(isCameraOff ? '../assets/camera_off.svg' : '../assets/camera.svg')"> </div> <div class="p-dialogue__timer" v-show="time !== 0"> {{ this.time }} </div> </div> <div v-for="participant in participants" :key="participant.id" class="p-dialogue__videoWrap" :class="{'p-dialogue__videoWrap--muted': participant.isMuted}"> <video :id="participant.id" autoplay playsinline @click="transferRight(participant.id)" class="p-dialogue__video"></video> <div class="p-dialogue__person" :class="{'p-dialogue__person--active': participant.isCameraOff}"> <img src="../assets/person.svg" class="p-dialogue__person__img"> </div> </div> </div> </div> <p id="my-id"></p> </div> </template> <script> import firebase from 'firebase'; import Peer from 'skyway-js'; import Swal from 'sweetalert2'; import headerMenu from '@/components/headerMenu.vue'; import $ from 'jquery'; export default { components: { headerMenu }, name: 'Dialogue', data() { return { localStream: null, roomID: '', joinRoomID: '', peer: {}, participants: [], isMuted: true, isCameraOff: true, isStarted: false, isQuestion: false, isRight: false, isTalking: false, right: '', time: 0, } }, watch: { async isCameraOff(newValue) { const stream = this.localStream; stream.getVideoTracks()[0].enabled = !newValue; this.localStream = stream; await firebase.firestore().collection('rooms').doc(this.roomID).collection('participants') .doc(this.peer.id).update({'isCameraOff': newValue}); }, async isMuted(newValue) { const stream = this.localStream; stream.getAudioTracks()[0].enabled = !newValue; this.localStream = stream; await firebase.firestore().collection('rooms').doc(this.roomID).collection('participants') .doc(this.peer.id).update({'isMuted': newValue}); }, async isStarted(newValue) { if (!newValue) return; if(this.right !== this.peer.id) { this.isMuted = true; this.isRight = false; } else { this.isRight = true; this.isMuted = true; await Swal.fire( 'トーキングオブジェクトをお渡しします!', 'アイスブレイクタイムです。\n深呼吸したらマイクをオンにして喋ってみましょう。\n話すことに慣れるのが大事ですよ。', 'success' ); const self = this; await new Promise(resolve => self.timer(resolve, 10)); await Swal.fire( 'スピーチお疲れさまでした!', '質問セクションに移行します。参加者全員の準備が整うまでお待ち下さい。', 'success', ); firebase.firestore().collection('rooms').doc(this.roomID).update({ isQuestion: true, }); } }, async isQuestion(newValue) { if (!newValue) return; await Swal.fire( '質問セクションに移行します!', '回答者に質問してみましょう。お題に関係ないことでも構いません。1人1回は質問してみてくださいね。', 'success' ); await firebase.firestore().collection('rooms').doc(this.roomID).collection('participants') .doc(this.peer.id).update({ isReadyQuestion: true, }); }, async participants(newValue) { if (!this.isStarted) return; let isTalking = true; await Promise.all(newValue.map(async participant => { if (!participant.isReadyQuestion) { isTalking = false; } })); this.isTalking = isTalking; }, async isTalking(newValue) { const self = this; if (!newValue) return; await new Promise(resolve => self.timer(resolve, 30)); await Swal.fire( '質問セクション終了です!', 'お疲れさまでした。\n時間いっぱいになりましたので質問セクションを終了します。\n次の回答者の方の準備が整うまでお待ち下さい。', 'success', ); await firebase.firestore().collection('rooms').doc(this.roomID) .collection('participants').doc(this.peer.id).update({ isReadyQuestion: false, }); this.isMuted = true; this.isTalking = false; if (this.isRight) { firebase.firestore().collection('rooms').doc(this.roomID).update({ isQuestion: false, }); const snapshot = await firebase.firestore().collection('rooms') .doc(this.roomID).get(); let respondent = snapshot.data().respondent; if (respondent > this.participants.length) { Swal.fire( 'アイスブレイクを終了します!', 'お疲れさまでした!\n引き続き哲学対話へ移行します。\nしばらくお待ち下さい!' ) } console.log('respondentは'); console.log(respondent); console.log('participantsは'); console.log(this.participants[respondent]); await firebase.firestore().collection('rooms').doc(this.roomID) .update({right: this.participants[respondent].id}); await firebase.firestore().collection('rooms').doc(this.roomID) .update({respondent: respondent}); } }, async right(newValue) { if (!this.isStarted) return; if (newValue !== this.peer.id) { this.isMuted = true; this.isRight = false; } else { this.isRight = true; await Swal.fire( 'トーキングオブジェクトを受け取りました!', 'お題に関係あること、ないこと、どんなことでも構いません。\nあなたの心のままに語ってみましょう。', 'success' ); const self = this; await new Promise(resolve => self.timer(resolve, 30)); await Swal.fire( 'スピーチお疲れさまでした!', '質問セクションに移行します。参加者全員の準備が整うまでお待ち下さい。', 'success', ); firebase.firestore().collection('rooms').doc(this.roomID).update({ isQuestion: true, }); } } }, mounted() { // カメラ映像取得 navigator.mediaDevices.getUserMedia({video: true, audio: true}) .then( stream => { stream.getAudioTracks()[0].enabled = !this.isMuted; stream.getVideoTracks()[0].enabled = !this.isCameraOff; // 成功時にvideo要素にカメラ映像をセットし、再生 const videoElm = document.getElementById('my-video') videoElm.srcObject = stream; videoElm.play(); // 着信時に相手にカメラ映像を返せるように、グローバル変数に保存しておく this.localStream = stream; }).catch( error => { // 失敗時にはエラーログを出力 console.error('mediaDevice.getUserMedia() error:', error); return; }); // skyway接続 this.peer = new Peer({ key: 'skywayのid', debug: 2, }); this.peer.on('open', () => { document.getElementById('my-id').textContent = this.peer.id; }); // エラー時処理 this.peer.on('error', err => { alert(err.message); }); }, methods: { timer(resolve, time) { this.time = time; const self = this; const interval = setInterval(function() { console.log(self.time); self.time--; if (!self.time) { clearInterval(interval); return resolve(); } }, 1000); }, transferRight(participant) { if (!this.isRight) return; Swal.fire({ title: 'トーキングオブジェクトを渡しますか?', text: "次にトーキングオブジェクトを受け取るまでマイクはミュートされます。", icon: 'question', showCancelButton: true, confirmButtonColor: '#3085d6', cancelButtonColor: '#d33', confirmButtonText: '渡す', cancelButtonText: 'もう少し話す', }).then(async result => { if (result.value) { await firebase.firestore().collection('rooms').doc(this.roomID).update({right: participant}); Swal.fire( 'トーキングオブジェクトを渡しました!', 'みんなの発言を聞くこともまた対話です。\nゆっくりじっくり聞いてみましょう。', 'success' ) } }); }, async startDialogue() { await firebase.firestore().collection('rooms').doc(this.roomID).update({isStarted: true}); }, async makeRoom() { this.roomID = Math.random().toString(32).substring(2); const mediaConnection = this.peer.joinRoom(this.roomID, { mode: 'sfu', stream: this.localStream, }); // ルームを作成 firebase.firestore().collection('rooms').doc(this.roomID).set({ createdAt: firebase.firestore.FieldValue.serverTimestamp(), isStarted: false, isQuestion: false, right: this.peer.id, respondent: 0, }); firebase.firestore().collection('rooms').doc(this.roomID).collection('participants') .doc(this.peer.id).set({ isMuted: true, isCameraOff: true, joinedAt: firebase.firestore.FieldValue.serverTimestamp(), }); this.isRight = true; this.setEventListener(mediaConnection); }, async joinRoom() { let joinRoomID = ''; await Swal.fire({ title: '参加したいルームのIDをご入力ください', input: 'text', inputAttributes: { autocapitalize: 'off' }, showCancelButton: true, confirmButtonText: '参加する', }).then((result) => { joinRoomID = result.value; }); if (!joinRoomID) { Swal.fire( 'ルームIDが未入力です。', 'お手数ですがもう一度ご入力ください。', 'warning' ); return; } const snapshot = await firebase.firestore().collection('rooms') .doc(joinRoomID).get(); if (!snapshot.exists) { Swal.fire( 'ルームが存在しないようです...', '正しいルーム名を入力されているかご確認ください。', 'warning' ) return; } const mediaConnection = this.peer.joinRoom(joinRoomID, { mode: 'sfu', stream: this.localStream, }); // participantsに自分を追加 firebase.firestore().collection('rooms').doc(joinRoomID).collection('participants') .doc(this.peer.id).set({ isMuted: true, isCameraOff: true, isReadyQuestion: false, joinedAt: firebase.firestore.FieldValue.serverTimestamp(), }); this.roomID = joinRoomID; this.setEventListener(mediaConnection); }, async addVideo(mediaConnection) { let remoteStreams = []; mediaConnection.on('stream', () => { // video要素にカメラ映像をセットして再生 Object.keys(mediaConnection.remoteStreams).forEach( key => { const remoteStream = mediaConnection.remoteStreams[key]; remoteStreams.push(remoteStream); }); }); await new Promise(resolve => setTimeout(resolve, 1000)); remoteStreams.forEach(async remoteStream => { const videoElm = document.getElementById(remoteStream.peerId); videoElm.srcObject = remoteStream; videoElm.play(); }); }, async removeVideo(mediaConnection) { let remoteStreams = []; // video要素にカメラ映像をセットして再生 Object.keys(mediaConnection.remoteStreams).forEach( key => { const remoteStream = mediaConnection.remoteStreams[key]; remoteStreams.push(remoteStream); }); await new Promise(resolve => setTimeout(resolve, 1000)); remoteStreams.forEach(async remoteStream => { const videoElm = document.getElementById(remoteStream.peerId); videoElm.srcObject = remoteStream; videoElm.play(); }); }, async setEventListener(mediaConnection) { this.addVideo(mediaConnection); const self = this; mediaConnection.on('peerJoin', () => { self.addVideo(mediaConnection); }); mediaConnection.on('peerLeave', () => { self.removeVideo(mediaConnection); }); $(window).on('unload', () => { mediaConnection.close(); firebase.firestore().collection('rooms').doc(self.roomID) .collection('participants').doc(self.peer.id).delete(); }); await firebase.firestore().collection('rooms').doc(this.roomID).onSnapshot(snapshot => { const room = snapshot.data(); this.isStarted = room.isStarted; this.right = room.right; this.isQuestion = room.isQuestion; }); await firebase.firestore().collection('rooms').doc(this.roomID) .collection('participants').orderBy('joinedAt', 'asc').onSnapshot(snapshot => { const participants = []; snapshot.docs.map(doc => { if (doc.id === this.peer.id) return; const participant = doc.data(); const id = doc.id; const isMuted = participant.isMuted; const isCameraOff = participant.isCameraOff; const isReadyQuestion = participant.isReadyQuestion; participants.push({id,isMuted,isCameraOff,isReadyQuestion}); }); this.participants = participants; }); } }, } </script> <style lang="scss"> #my-id{ display: none; } .p-dialogue { &__content{ box-sizing: border-box; display: flex; justify-content: space-between; align-items: flex-start; flex-wrap: wrap; width: 70%; max-width: 100%; box-shadow: 3px 0 6px rgba(0,0,0,0.4); border-radius: 3px; padding: 35px; @media screen and (max-width: 1000px){ width: 100%; } } &__txtWrap{ width: 100%; } &__txt{ font-size: 18px; display: block; text-align: right; } &__btnWrap{ display: flex; justify-content: flex-end; width: 100%; margin-bottom: 30px; } &__btn{ background: #fff; border: 1px solid #3c3c3c; border-radius: 3px; padding: 10px 30px; margin-right: 20px; // color: #fff; cursor: pointer; // font-weight: bold; &:hover{ opacity: 0.7; } } &__videoWrap{ position: relative; width: calc(50% - 15px); margin-bottom: 30px; box-shadow: 0 0 10px 10px rgba(249, 255, 100, 0.5); &--muted{ box-shadow: 0 0 5px 5px rgba(0, 0, 0, 0.3); } video{ vertical-align: bottom; width: 100%; } } &__mic, &__camera, &__timer { display: block; position: absolute; left: 5px; bottom: 5px; width: 40px; height: 40px; background: #fff; border-radius: 50%; padding: 10px; box-sizing: border-box; cursor: pointer; } &__mic { &--disabled { pointer-events: none; } } &__camera { left: 50px; } &__timer{ text-align: center; left: auto; right: 10px; } &__person { display: none; position: absolute; top: 0; left: 0; width: 100%; height: 100%; justify-content: center; align-items: center; background: #ddd; &--active{ display: flex; } &__img{ width: 30%; } } } .swal2-html-container{ white-space: pre-wrap; } </style>