wib / oneClient.js
izuemon's picture
Create oneClient.js
e86388b verified
Raw
History Blame Contribute Delete
6.15 kB
// 拡大表示画面: 教師からの映像/画像を受信して表示
new Vue({
el: '#app',
data: {
showToolbar: true,
targetName: '拡大表示',
teacherEmail: '',
myMail: '',
roomPath: '',
roomRef: null,
pc: null,
timeoutHide: null
},
mounted() {
// URLパラメータから教師メール、生徒メール、クラス名を取得
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();
}
};
};
// Offerを受信するために、受信側はダミーのオファーを送る?
// 簡易実装: コントローラからのOfferを待つので、先にデータチャネルを作成しておく
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') {
// コマンド直接受信 (fallback)
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();
}
}
});