247 lines
7.3 KiB
JavaScript
247 lines
7.3 KiB
JavaScript
import { Signaling, WebSocketSignaling } from "../../module/signaling.js";
|
|
import Peer from "../../module/peer.js";
|
|
import * as Logger from "../../module/logger.js";
|
|
|
|
|
|
// enum type of event sending from Unity
|
|
var UnityEventType = {
|
|
SWITCH_VIDEO: 0
|
|
};
|
|
|
|
function uuid4() {
|
|
var temp_url = URL.createObjectURL(new Blob());
|
|
var uuid = temp_url.toString();
|
|
URL.revokeObjectURL(temp_url);
|
|
return uuid.split(/[:/]/g).pop().toLowerCase(); // remove prefixes
|
|
}
|
|
|
|
export class VideoPlayer {
|
|
constructor(elements) {
|
|
const _this = this;
|
|
this.pc = null;
|
|
this.channel = null;
|
|
this.connectionId = null;
|
|
|
|
// main video
|
|
this.localStream = new MediaStream();
|
|
this.video = elements[0];
|
|
this.video.playsInline = true;
|
|
this.video.addEventListener('loadedmetadata', function () {
|
|
_this.video.play();
|
|
_this.resizeVideo();
|
|
}, true);
|
|
|
|
// secondly video
|
|
this.localStream2 = new MediaStream();
|
|
this.videoThumb = elements[1];
|
|
this.videoThumb.playsInline = true;
|
|
this.videoThumb.addEventListener('loadedmetadata', function () {
|
|
_this.videoThumb.play();
|
|
}, true);
|
|
|
|
this.videoTrackList = [];
|
|
this.videoTrackIndex = 0;
|
|
this.maxVideoTrackLength = 2;
|
|
|
|
this.ondisconnect = function () { };
|
|
}
|
|
|
|
async setupConnection(useWebSocket) {
|
|
const _this = this;
|
|
// close current RTCPeerConnection
|
|
if (this.pc) {
|
|
Logger.log('Close current PeerConnection');
|
|
this.pc.close();
|
|
this.pc = null;
|
|
}
|
|
|
|
if (useWebSocket) {
|
|
this.signaling = new WebSocketSignaling();
|
|
} else {
|
|
this.signaling = new Signaling();
|
|
}
|
|
|
|
this.connectionId = uuid4();
|
|
|
|
// Create peerConnection with proxy server and set up handlers
|
|
this.pc = new Peer(this.connectionId, true);
|
|
this.pc.addEventListener('disconnect', () => {
|
|
_this.ondisconnect();
|
|
});
|
|
this.pc.addEventListener('trackevent', (e) => {
|
|
const data = e.detail;
|
|
if (data.track.kind == 'video') {
|
|
_this.videoTrackList.push(data.track);
|
|
}
|
|
if (data.track.kind == 'audio') {
|
|
_this.localStream.addTrack(data.track);
|
|
}
|
|
if (_this.videoTrackList.length == _this.maxVideoTrackLength) {
|
|
_this.switchVideo(_this.videoTrackIndex);
|
|
}
|
|
});
|
|
this.pc.addEventListener('sendoffer', (e) => {
|
|
const offer = e.detail;
|
|
_this.signaling.sendOffer(offer.connectionId, offer.sdp);
|
|
});
|
|
this.pc.addEventListener('sendanswer', (e) => {
|
|
const answer = e.detail;
|
|
_this.signaling.sendAnswer(answer.connectionId, answer.sdp);
|
|
});
|
|
this.pc.addEventListener('sendcandidate', (e) => {
|
|
const candidate = e.detail;
|
|
_this.signaling.sendCandidate(candidate.connectionId, candidate.candidate, candidate.sdpMid, candidate.sdpMLineIndex);
|
|
});
|
|
|
|
this.signaling.addEventListener('disconnect', async (e) => {
|
|
const data = e.detail;
|
|
if (_this.pc != null && _this.pc.connectionId == data.connectionId) {
|
|
_this.ondisconnect();
|
|
}
|
|
});
|
|
this.signaling.addEventListener('offer', async (e) => {
|
|
const offer = e.detail;
|
|
const desc = new RTCSessionDescription({ sdp: offer.sdp, type: "offer" });
|
|
if (_this.pc != null) {
|
|
await _this.pc.onGotDescription(offer.connectionId, desc);
|
|
}
|
|
});
|
|
this.signaling.addEventListener('answer', async (e) => {
|
|
const answer = e.detail;
|
|
const desc = new RTCSessionDescription({ sdp: answer.sdp, type: "answer" });
|
|
if (_this.pc != null) {
|
|
await _this.pc.onGotDescription(answer.connectionId, desc);
|
|
}
|
|
});
|
|
this.signaling.addEventListener('candidate', async (e) => {
|
|
const candidate = e.detail;
|
|
const iceCandidate = new RTCIceCandidate({ candidate: candidate.candidate, sdpMid: candidate.sdpMid, sdpMLineIndex: candidate.sdpMLineIndex });
|
|
if (_this.pc != null) {
|
|
await _this.pc.onGotCandidate(candidate.connectionId, iceCandidate);
|
|
}
|
|
});
|
|
|
|
// setup signaling
|
|
await this.signaling.start();
|
|
|
|
// Create data channel with proxy server and set up handlers
|
|
this.channel = this.pc.createDataChannel(this.connectionId, 'data');
|
|
this.channel.onopen = function () {
|
|
Logger.log('Datachannel connected.');
|
|
};
|
|
this.channel.onerror = function (e) {
|
|
Logger.log("The error " + e.error.message + " occurred\n while handling data with proxy server.");
|
|
};
|
|
this.channel.onclose = function () {
|
|
Logger.log('Datachannel disconnected.');
|
|
};
|
|
this.channel.onmessage = async (msg) => {
|
|
// receive message from unity and operate message
|
|
let data;
|
|
// receive message data type is blob only on Firefox
|
|
if (navigator.userAgent.indexOf('Firefox') != -1) {
|
|
data = await msg.data.arrayBuffer();
|
|
} else {
|
|
data = msg.data;
|
|
}
|
|
const bytes = new Uint8Array(data);
|
|
_this.videoTrackIndex = bytes[1];
|
|
switch (bytes[0]) {
|
|
case UnityEventType.SWITCH_VIDEO:
|
|
_this.switchVideo(_this.videoTrackIndex);
|
|
break;
|
|
}
|
|
};
|
|
}
|
|
|
|
resizeVideo() {
|
|
const clientRect = this.video.getBoundingClientRect();
|
|
const videoRatio = this.videoWidth / this.videoHeight;
|
|
const clientRatio = clientRect.width / clientRect.height;
|
|
|
|
this._videoScale = videoRatio > clientRatio ? clientRect.width / this.videoWidth : clientRect.height / this.videoHeight;
|
|
const videoOffsetX = videoRatio > clientRatio ? 0 : (clientRect.width - this.videoWidth * this._videoScale) * 0.5;
|
|
const videoOffsetY = videoRatio > clientRatio ? (clientRect.height - this.videoHeight * this._videoScale) * 0.5 : 0;
|
|
this._videoOriginX = clientRect.left + videoOffsetX;
|
|
this._videoOriginY = clientRect.top + videoOffsetY;
|
|
}
|
|
|
|
// switch streaming destination main video and secondly video
|
|
switchVideo(indexVideoTrack) {
|
|
this.video.srcObject = this.localStream;
|
|
this.videoThumb.srcObject = this.localStream2;
|
|
|
|
if (indexVideoTrack == 0) {
|
|
this.replaceTrack(this.localStream, this.videoTrackList[0]);
|
|
this.replaceTrack(this.localStream2, this.videoTrackList[1]);
|
|
}
|
|
else {
|
|
this.replaceTrack(this.localStream, this.videoTrackList[1]);
|
|
this.replaceTrack(this.localStream2, this.videoTrackList[0]);
|
|
}
|
|
}
|
|
|
|
// replace video track related the MediaStream
|
|
replaceTrack(stream, newTrack) {
|
|
const tracks = stream.getVideoTracks();
|
|
for (const track of tracks) {
|
|
if (track.kind == 'video') {
|
|
stream.removeTrack(track);
|
|
}
|
|
}
|
|
stream.addTrack(newTrack);
|
|
}
|
|
|
|
get videoWidth() {
|
|
return this.video.videoWidth;
|
|
}
|
|
|
|
get videoHeight() {
|
|
return this.video.videoHeight;
|
|
}
|
|
|
|
get videoOriginX() {
|
|
return this._videoOriginX;
|
|
}
|
|
|
|
get videoOriginY() {
|
|
return this._videoOriginY;
|
|
}
|
|
|
|
get videoScale() {
|
|
return this._videoScale;
|
|
}
|
|
|
|
sendMsg(msg) {
|
|
if (this.channel == null) {
|
|
return;
|
|
}
|
|
switch (this.channel.readyState) {
|
|
case 'connecting':
|
|
Logger.log('Connection not ready');
|
|
break;
|
|
case 'open':
|
|
this.channel.send(msg);
|
|
break;
|
|
case 'closing':
|
|
Logger.log('Attempt to sendMsg message while closing');
|
|
break;
|
|
case 'closed':
|
|
Logger.log('Attempt to sendMsg message while connection closed.');
|
|
break;
|
|
}
|
|
}
|
|
|
|
async stop() {
|
|
if (this.signaling) {
|
|
await this.signaling.stop();
|
|
this.signaling = null;
|
|
}
|
|
|
|
if (this.pc) {
|
|
this.pc.close();
|
|
this.pc = null;
|
|
}
|
|
}
|
|
}
|