| |
| new Vue({ |
| el: '#app', |
| data: { |
| showToolbar: true, |
| targetName: '拡大表示', |
| teacherEmail: '', |
| myMail: '', |
| roomPath: '', |
| roomRef: null, |
| pc: null, |
| timeoutHide: null |
| }, |
| mounted() { |
| |
| const urlParams = new URLSearchParams(window.location.search); |
| this.teacherEmail = urlParams.get('teacher') || ''; |
| this.myMail = urlParams.get('student') || ''; |
| const className = urlParams.get('class') || ''; |
| if (!this.teacherEmail || !this.myMail || !className) { |
| alert('不正な起動です。コントローラから開いてください。'); |
| window.close(); |
| } |
| const domain = this.teacherEmail.split('@')[1]; |
| const safeDomain = domain.replace(/\./g, '_'); |
| this.roomPath = `classes/${safeDomain}/${className}`; |
| this.roomRef = firebase.database().ref(this.roomPath); |
| |
| this.setupWebRTC(); |
| this.setupSignaling(); |
| |
| |
| setTimeout(() => this.hideToolbarDelayed(), 3000); |
| }, |
| methods: { |
| async setupWebRTC() { |
| const config = { iceServers: [{ urls: 'stun:stun.l.google.com:19302' }] }; |
| this.pc = new RTCPeerConnection(config); |
| |
| this.pc.ontrack = (event) => { |
| const videoEl = document.getElementById('remoteVideo'); |
| videoEl.srcObject = event.streams[0]; |
| videoEl.style.display = 'block'; |
| document.getElementById('remoteImage').style.display = 'none'; |
| }; |
| |
| this.pc.onicecandidate = (event) => { |
| if (event.candidate) { |
| this.sendSignal({ type: 'candidate', candidate: event.candidate }); |
| } |
| }; |
| |
| |
| this.pc.ondatachannel = (event) => { |
| const dc = event.channel; |
| dc.onmessage = (ev) => { |
| const msg = JSON.parse(ev.data); |
| if (msg.cmd === 'lock') { |
| this.applyLock(msg.params); |
| } else if (msg.cmd === 'unlock') { |
| this.releaseLock(); |
| } |
| }; |
| }; |
| |
| |
| |
| const dc = this.pc.createDataChannel('command'); |
| dc.onopen = () => console.log('DataChannel open'); |
| dc.onmessage = (ev) => { |
| const msg = JSON.parse(ev.data); |
| if (msg.cmd === 'lock') this.applyLock(msg.params); |
| else if (msg.cmd === 'unlock') this.releaseLock(); |
| }; |
| |
| const offer = await this.pc.createOffer(); |
| await this.pc.setLocalDescription(offer); |
| this.sendSignal({ type: 'offer', sdp: offer.sdp }); |
| }, |
| |
| sendSignal(data) { |
| const signalRef = this.roomRef.child('signaling'); |
| signalRef.push({ |
| from: this.myMail, |
| to: this.teacherEmail, |
| type: data.type, |
| data: data |
| }); |
| }, |
| |
| setupSignaling() { |
| this.roomRef.child('signaling').on('child_added', async (snap) => { |
| const msg = snap.val(); |
| if (msg.to === this.myMail && msg.from === this.teacherEmail) { |
| const { type, data } = msg; |
| if (type === 'offer') { |
| await this.pc.setRemoteDescription(new RTCSessionDescription({ type: 'offer', sdp: data.sdp })); |
| const answer = await this.pc.createAnswer(); |
| await this.pc.setLocalDescription(answer); |
| this.sendSignal({ type: 'answer', sdp: answer.sdp }); |
| } else if (type === 'answer') { |
| await this.pc.setRemoteDescription(new RTCSessionDescription({ type: 'answer', sdp: data.sdp })); |
| } else if (type === 'candidate') { |
| await this.pc.addIceCandidate(new RTCIceCandidate(data.candidate)); |
| } else if (type === 'command') { |
| |
| if (data.cmd === 'lock') this.applyLock(data.params); |
| else if (data.cmd === 'unlock') this.releaseLock(); |
| } |
| snap.ref.remove(); |
| } |
| }); |
| }, |
| |
| applyLock(params) { |
| |
| if (params.type === 'text') { |
| document.body.style.backgroundColor = params.bg_color; |
| document.body.style.color = params.font_color; |
| document.body.innerHTML = `<div style="display:flex; justify-content:center; align-items:center; height:100vh; font-size:2rem;">${params.text}</div>`; |
| } else { |
| |
| const imgUrl = `./assets/lock_${params.image}.png`; |
| document.body.innerHTML = `<img src="${imgUrl}" style="width:100%; height:100%; object-fit:cover;">`; |
| } |
| document.body.style.overflow = 'hidden'; |
| }, |
| |
| releaseLock() { |
| |
| location.reload(); |
| }, |
| |
| hideToolbarDelayed() { |
| if (this.timeoutHide) clearTimeout(this.timeoutHide); |
| this.timeoutHide = setTimeout(() => { this.showToolbar = false; }, 3000); |
| }, |
| |
| closeWindow() { |
| window.close(); |
| } |
| } |
| }); |