初始化
This commit is contained in:
146
client/public/videoplayer/js/gamepadEvents.js
Normal file
146
client/public/videoplayer/js/gamepadEvents.js
Normal file
@@ -0,0 +1,146 @@
|
||||
import * as Logger from "../../module/logger.js";
|
||||
|
||||
const _e = 0.09;
|
||||
const _gameloopInterval = 16.67; //in milliseconds, 60 times a second
|
||||
var gameloop = null;
|
||||
var gamepadsPreviousButtonsStates = {};
|
||||
var gamepadsPreviousAxesStates = {};
|
||||
var gamepadsConnectedTimeStamp = {};
|
||||
const _axisOffset = 100;
|
||||
const _axisMultiplier = 1;
|
||||
const _axisYInverted = -1;
|
||||
|
||||
class GamepadButtonEvent extends Event {
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
this.index = arguments[1].index;
|
||||
this.id = arguments[1].id;
|
||||
this.value = arguments[1].value;
|
||||
}
|
||||
}
|
||||
|
||||
class GamepadAxisEvent extends Event {
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
this.index = arguments[1].index;
|
||||
this.x = arguments[1].x;
|
||||
this.y = arguments[1].y;
|
||||
this.id = arguments[1].id;
|
||||
}
|
||||
}
|
||||
|
||||
function storePreviousState(gamepad) {
|
||||
gamepadsPreviousButtonsStates[gamepad.index] = {};
|
||||
gamepad.buttons.forEach(function (button, index) {
|
||||
gamepadsPreviousButtonsStates[gamepad.index][index] = { value: button.value, pressed: button.pressed };
|
||||
});
|
||||
|
||||
gamepadsPreviousAxesStates[gamepad.index] = [gamepad.axes.length];
|
||||
for (var index = 0; index < gamepad.axes.length; index++)
|
||||
gamepadsPreviousAxesStates[gamepad.index][index] = gamepad.axes[index];
|
||||
}
|
||||
|
||||
function checkAxes(gamepad, previousGamePad) {
|
||||
for (var i = 0; i < gamepad.axes.length; i += 2) {
|
||||
var absX = Math.abs(gamepad.axes[i]);
|
||||
var absY = Math.abs(gamepad.axes[i + 1]);
|
||||
var event = null;
|
||||
if ((absX > _e) ||
|
||||
(absY > _e)) {
|
||||
|
||||
event = new GamepadAxisEvent('gamepadAxis', { id: gamepadsConnectedTimeStamp[gamepad.index], index: i / 2 + _axisOffset, x: gamepad.axes[i] * _axisMultiplier, y: gamepad.axes[i + 1] * _axisMultiplier * _axisYInverted });
|
||||
document.dispatchEvent(event);
|
||||
}
|
||||
else {
|
||||
var previousAbsX = Math.abs(previousGamePad[i]);
|
||||
var previousAbsY = Math.abs(previousGamePad[i + 1]);
|
||||
|
||||
//have to send if previously was moved
|
||||
if ((previousAbsX > _e) ||
|
||||
(previousAbsY > _e)) {
|
||||
event = new GamepadAxisEvent('gamepadAxis', { id: gamepadsConnectedTimeStamp[gamepad.index], index: i / 2 + _axisOffset, x: 0.0, y: 0.0 });
|
||||
document.dispatchEvent(event);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function gameLoop() {
|
||||
Object.keys(gamepadsPreviousAxesStates).forEach(function (gamepadIndex) {
|
||||
var gamepad = navigator.webkitGetGamepads ? navigator.webkitGetGamepads()[gamepadIndex] : navigator.getGamepads()[gamepadIndex];
|
||||
var previousButtons = gamepadsPreviousButtonsStates[gamepadIndex];
|
||||
gamepad.buttons.forEach(function (button, index) {
|
||||
var buttonStatus = navigator.webkitGetGamepads ? button == 1 : (button.value > 0 || button.pressed == true);
|
||||
var previousButtonStatus = navigator.webkitGetGamepads ? previousButtons[index].value == 1 : (previousButtons[index].value > 0 || previousButtons[index].pressed == true);
|
||||
var event;
|
||||
if (buttonStatus != previousButtonStatus) {
|
||||
if (buttonStatus) {
|
||||
event = new GamepadButtonEvent('gamepadButtonDown', { id: gamepadsConnectedTimeStamp[gamepad.index], index: index, value: button.value });
|
||||
}
|
||||
else {
|
||||
event = new GamepadButtonEvent('gamepadButtonUp', { id: gamepadsConnectedTimeStamp[gamepad.index], index: index, value: 0 });
|
||||
}
|
||||
document.dispatchEvent(event);
|
||||
}
|
||||
else if (buttonStatus) {
|
||||
event = new GamepadButtonEvent('gamepadButtonPressed', { id: gamepadsConnectedTimeStamp[gamepad.index], index: index, value: button.value });
|
||||
document.dispatchEvent(event);
|
||||
}
|
||||
|
||||
});
|
||||
checkAxes(gamepad, gamepadsPreviousAxesStates[gamepadIndex]);
|
||||
storePreviousState(gamepad);
|
||||
});
|
||||
}
|
||||
|
||||
function getCookie(cname) {
|
||||
var name = cname + "=";
|
||||
var decodedCookie = decodeURIComponent(document.cookie);
|
||||
var ca = decodedCookie.split(';');
|
||||
for (var i = 0; i < ca.length; i++) {
|
||||
var c = ca[i];
|
||||
while (c.charAt(0) == ' ') {
|
||||
c = c.substring(1);
|
||||
}
|
||||
if (c.indexOf(name) == 0) {
|
||||
return c.substring(name.length, c.length);
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
export function gamepadHandler(event, connecting) {
|
||||
var gamepad = event.gamepad;
|
||||
|
||||
var key = gamepad.id.replace(/\s/g, '');
|
||||
var cookieTimeStamp = getCookie(key);
|
||||
|
||||
if (connecting) {
|
||||
storePreviousState(gamepad);
|
||||
if (Object.keys(gamepadsPreviousAxesStates).length == 1) {
|
||||
gameloop = setInterval(gameLoop, _gameloopInterval);
|
||||
}
|
||||
|
||||
//try to find the timestamp
|
||||
//need to strip the : from the id
|
||||
|
||||
if (cookieTimeStamp == "") {
|
||||
document.cookie = key + "=" + gamepad.timestamp;
|
||||
gamepadsConnectedTimeStamp[gamepad.index] = gamepad.timestamp;
|
||||
}
|
||||
else {
|
||||
gamepadsConnectedTimeStamp[gamepad.index] = cookieTimeStamp;
|
||||
}
|
||||
|
||||
Logger.log("connected: " + gamepadsConnectedTimeStamp[gamepad.index]);
|
||||
|
||||
} else {
|
||||
delete gamepadsPreviousAxesStates[gamepad.index];
|
||||
delete gamepadsPreviousButtonsStates[gamepad.index];
|
||||
if (Object.keys(gamepadsPreviousAxesStates).length == 0) {
|
||||
clearInterval(gameloop);
|
||||
gameloop = null;
|
||||
}
|
||||
Logger.log("disconnected: " + gamepad.id);
|
||||
}
|
||||
}
|
||||
156
client/public/videoplayer/js/main.js
Normal file
156
client/public/videoplayer/js/main.js
Normal file
@@ -0,0 +1,156 @@
|
||||
import { VideoPlayer } from "./video-player.js";
|
||||
import { registerGamepadEvents, registerKeyboardEvents, registerMouseEvents, sendClickEvent } from "./register-events.js";
|
||||
import { getServerConfig } from "../../js/config.js";
|
||||
|
||||
setup();
|
||||
|
||||
let playButton;
|
||||
let videoPlayer;
|
||||
let useWebSocket;
|
||||
|
||||
window.document.oncontextmenu = function () {
|
||||
return false; // cancel default menu
|
||||
};
|
||||
|
||||
window.addEventListener('resize', function () {
|
||||
videoPlayer.resizeVideo();
|
||||
}, true);
|
||||
|
||||
window.addEventListener('beforeunload', async () => {
|
||||
await videoPlayer.stop();
|
||||
}, true);
|
||||
|
||||
async function setup() {
|
||||
const res = await getServerConfig();
|
||||
useWebSocket = res.useWebSocket;
|
||||
showWarningIfNeeded(res.startupMode);
|
||||
showPlayButton();
|
||||
}
|
||||
|
||||
function showWarningIfNeeded(startupMode) {
|
||||
const warningDiv = document.getElementById("warning");
|
||||
if (startupMode == "private") {
|
||||
warningDiv.innerHTML = "<h4>Warning</h4> This sample is not working on Private Mode.";
|
||||
warningDiv.hidden = false;
|
||||
}
|
||||
}
|
||||
|
||||
function showPlayButton() {
|
||||
if (!document.getElementById('playButton')) {
|
||||
let elementPlayButton = document.createElement('img');
|
||||
elementPlayButton.id = 'playButton';
|
||||
elementPlayButton.src = 'images/Play.png';
|
||||
elementPlayButton.alt = 'Start Streaming';
|
||||
playButton = document.getElementById('player').appendChild(elementPlayButton);
|
||||
playButton.addEventListener('click', onClickPlayButton);
|
||||
}
|
||||
}
|
||||
|
||||
function onClickPlayButton() {
|
||||
|
||||
playButton.style.display = 'none';
|
||||
|
||||
const playerDiv = document.getElementById('player');
|
||||
|
||||
// add video player
|
||||
const elementVideo = document.createElement('video');
|
||||
elementVideo.id = 'Video';
|
||||
elementVideo.style.touchAction = 'none';
|
||||
playerDiv.appendChild(elementVideo);
|
||||
|
||||
// add video thumbnail
|
||||
const elementVideoThumb = document.createElement('video');
|
||||
elementVideoThumb.id = 'VideoThumbnail';
|
||||
elementVideoThumb.style.touchAction = 'none';
|
||||
playerDiv.appendChild(elementVideoThumb);
|
||||
|
||||
setupVideoPlayer([elementVideo, elementVideoThumb]).then(value => videoPlayer = value);
|
||||
|
||||
// add blue button
|
||||
const elementBlueButton = document.createElement('button');
|
||||
elementBlueButton.id = "blueButton";
|
||||
elementBlueButton.innerHTML = "Light on";
|
||||
playerDiv.appendChild(elementBlueButton);
|
||||
elementBlueButton.addEventListener("click", function () {
|
||||
sendClickEvent(videoPlayer, 1);
|
||||
});
|
||||
|
||||
// add green button
|
||||
const elementGreenButton = document.createElement('button');
|
||||
elementGreenButton.id = "greenButton";
|
||||
elementGreenButton.innerHTML = "Light off";
|
||||
playerDiv.appendChild(elementGreenButton);
|
||||
elementGreenButton.addEventListener("click", function () {
|
||||
sendClickEvent(videoPlayer, 2);
|
||||
});
|
||||
|
||||
// add orange button
|
||||
const elementOrangeButton = document.createElement('button');
|
||||
elementOrangeButton.id = "orangeButton";
|
||||
elementOrangeButton.innerHTML = "Play audio";
|
||||
playerDiv.appendChild(elementOrangeButton);
|
||||
elementOrangeButton.addEventListener("click", function () {
|
||||
sendClickEvent(videoPlayer, 3);
|
||||
});
|
||||
|
||||
// add fullscreen button
|
||||
const elementFullscreenButton = document.createElement('img');
|
||||
elementFullscreenButton.id = 'fullscreenButton';
|
||||
elementFullscreenButton.src = 'images/FullScreen.png';
|
||||
playerDiv.appendChild(elementFullscreenButton);
|
||||
elementFullscreenButton.addEventListener("click", function () {
|
||||
if (!document.fullscreenElement || !document.webkitFullscreenElement) {
|
||||
if (document.documentElement.requestFullscreen) {
|
||||
document.documentElement.requestFullscreen();
|
||||
}
|
||||
else if (document.documentElement.webkitRequestFullscreen) {
|
||||
document.documentElement.webkitRequestFullscreen(Element.ALLOW_KEYBOARD_INPUT);
|
||||
} else {
|
||||
if (playerDiv.style.position == "absolute") {
|
||||
playerDiv.style.position = "relative";
|
||||
} else {
|
||||
playerDiv.style.position = "absolute";
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
document.addEventListener('webkitfullscreenchange', onFullscreenChange);
|
||||
document.addEventListener('fullscreenchange', onFullscreenChange);
|
||||
|
||||
function onFullscreenChange() {
|
||||
if (document.webkitFullscreenElement || document.fullscreenElement) {
|
||||
playerDiv.style.position = "absolute";
|
||||
elementFullscreenButton.style.display = 'none';
|
||||
}
|
||||
else {
|
||||
playerDiv.style.position = "relative";
|
||||
elementFullscreenButton.style.display = 'block';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function setupVideoPlayer(elements) {
|
||||
const videoPlayer = new VideoPlayer(elements);
|
||||
await videoPlayer.setupConnection(useWebSocket);
|
||||
|
||||
videoPlayer.ondisconnect = onDisconnect;
|
||||
registerGamepadEvents(videoPlayer);
|
||||
registerKeyboardEvents(videoPlayer);
|
||||
registerMouseEvents(videoPlayer, elements[0]);
|
||||
|
||||
return videoPlayer;
|
||||
}
|
||||
|
||||
function onDisconnect() {
|
||||
const playerDiv = document.getElementById('player');
|
||||
clearChildren(playerDiv);
|
||||
videoPlayer.stop();
|
||||
videoPlayer = null;
|
||||
showPlayButton();
|
||||
}
|
||||
|
||||
function clearChildren(element) {
|
||||
while (element.firstChild) {
|
||||
element.removeChild(element.firstChild);
|
||||
}
|
||||
}
|
||||
307
client/public/videoplayer/js/register-events.js
Normal file
307
client/public/videoplayer/js/register-events.js
Normal file
@@ -0,0 +1,307 @@
|
||||
import { gamepadHandler } from "./gamepadEvents.js";
|
||||
import * as Logger from "../../module/logger.js";
|
||||
import { Keymap } from "../../module/keymap.js";
|
||||
|
||||
const InputEvent = {
|
||||
Keyboard: 0,
|
||||
Mouse: 1,
|
||||
MouseWheel: 2,
|
||||
Touch: 3,
|
||||
ButtonClick: 4,
|
||||
Gamepad: 5
|
||||
};
|
||||
|
||||
const KeyboardEventType = {
|
||||
Up: 0,
|
||||
Down: 1
|
||||
};
|
||||
|
||||
const GamepadEventType = {
|
||||
ButtonUp: 0,
|
||||
ButtonDown: 1,
|
||||
ButtonPressed: 2,
|
||||
Axis: 3
|
||||
};
|
||||
|
||||
const PointerPhase = {
|
||||
None: 0,
|
||||
Began: 1,
|
||||
Moved: 2,
|
||||
Ended: 3,
|
||||
Canceled: 4,
|
||||
Stationary: 5
|
||||
};
|
||||
|
||||
let sendGamepadButtonDown = undefined;
|
||||
let sendGamepadButtonUp = undefined;
|
||||
let sendGamepadButtonPressed;
|
||||
let gamepadAxisChange = undefined;
|
||||
let gamepadConnected = undefined;
|
||||
let gamepadDisconnected = undefined;
|
||||
|
||||
export function registerGamepadEvents(videoPlayer) {
|
||||
|
||||
const _videoPlayer = videoPlayer;
|
||||
|
||||
sendGamepadButtonDown = (e) => {
|
||||
Logger.log("gamepad id: " + e.id + " button index: " + e.index + " value " + e.value + " down");
|
||||
let data = new DataView(new ArrayBuffer(19));
|
||||
data.setUint8(0, InputEvent.Gamepad);
|
||||
data.setUint8(1, GamepadEventType.ButtonDown);
|
||||
data.setUint8(2, e.index);
|
||||
data.setFloat64(3, e.value, true);
|
||||
|
||||
_videoPlayer && _videoPlayer.sendMsg(data.buffer);
|
||||
};
|
||||
|
||||
sendGamepadButtonUp = (e) => {
|
||||
Logger.log("gamepad id: " + e.id + " button index: " + e.index + " value " + e.value + " up");
|
||||
let data = new DataView(new ArrayBuffer(19));
|
||||
data.setUint8(0, InputEvent.Gamepad);
|
||||
data.setUint8(1, GamepadEventType.ButtonUp);
|
||||
data.setUint8(2, e.index);
|
||||
data.setFloat64(3, e.value, true);
|
||||
|
||||
_videoPlayer && _videoPlayer.sendMsg(data.buffer);
|
||||
};
|
||||
|
||||
sendGamepadButtonPressed = (e) => {
|
||||
Logger.log("gamepad id: " + e.id + " button index: " + e.index + " value " + e.value + " pressed");
|
||||
let data = new DataView(new ArrayBuffer(19));
|
||||
data.setUint8(0, InputEvent.Gamepad);
|
||||
data.setUint8(1, GamepadEventType.ButtonPressed);
|
||||
data.setUint8(2, e.index);
|
||||
data.setFloat64(3, e.value, true);
|
||||
|
||||
_videoPlayer && _videoPlayer.sendMsg(data.buffer);
|
||||
};
|
||||
|
||||
gamepadAxisChange = (e) => {
|
||||
Logger.log("gamepad id: " + e.id + " axis: " + e.index + " value " + e.value + " x:" + e.x + " y:" + e.y);
|
||||
let data = new DataView(new ArrayBuffer(27));
|
||||
data.setUint8(0, InputEvent.Gamepad);
|
||||
data.setUint8(1, GamepadEventType.Axis);
|
||||
data.setUint8(2, e.index);
|
||||
data.setFloat64(3, e.x, true);
|
||||
data.setFloat64(11, e.y, true);
|
||||
_videoPlayer && _videoPlayer.sendMsg(data.buffer);
|
||||
};
|
||||
|
||||
gamepadConnected = (e) => { gamepadHandler(e, true); };
|
||||
gamepadDisconnected = (e) => { gamepadHandler(e, false); };
|
||||
|
||||
|
||||
document.addEventListener("gamepadButtonDown", sendGamepadButtonDown, false);
|
||||
document.addEventListener("gamepadButtonUp", sendGamepadButtonUp, false);
|
||||
document.addEventListener("gamepadButtonPressed", sendGamepadButtonPressed, false);
|
||||
document.addEventListener("gamepadAxis", gamepadAxisChange, false);
|
||||
|
||||
window.addEventListener("gamepadconnected", gamepadConnected, false);
|
||||
window.addEventListener("gamepaddisconnected", gamepadDisconnected, false);
|
||||
}
|
||||
|
||||
export function unregisterGamepadEvents() {
|
||||
|
||||
document.removeEventListener("gamepadButtonDown", sendGamepadButtonDown, false);
|
||||
document.removeEventListener("gamepadButtonUp", sendGamepadButtonUp, false);
|
||||
document.removeEventListener("gamepadButtonPressed", sendGamepadButtonPressed, false);
|
||||
document.removeEventListener("gamepadAxis", gamepadAxisChange, false);
|
||||
|
||||
window.removeEventListener("gamepadconnected", gamepadConnected, false);
|
||||
window.removeEventListener("gamepaddisconnected", gamepadDisconnected, false);
|
||||
|
||||
}
|
||||
|
||||
|
||||
let sendKeyUp = undefined;
|
||||
let sendKeyDown = undefined;
|
||||
|
||||
|
||||
export function registerKeyboardEvents(videoPlayer) {
|
||||
|
||||
const _videoPlayer = videoPlayer;
|
||||
|
||||
function sendKey(e, type) {
|
||||
const key = Keymap[e.code];
|
||||
const character = e.key.length === 1 ? e.key.charCodeAt(0) : 0;
|
||||
Logger.log("key down " + key + ", repeat = " + e.repeat + ", character = " + character);
|
||||
_videoPlayer && _videoPlayer.sendMsg(new Uint8Array([InputEvent.Keyboard, type, e.repeat, key, character]).buffer);
|
||||
}
|
||||
|
||||
|
||||
sendKeyUp = (e) => {
|
||||
sendKey(e, KeyboardEventType.Up);
|
||||
};
|
||||
|
||||
sendKeyDown = (e) => {
|
||||
sendKey(e, KeyboardEventType.Down);
|
||||
};
|
||||
|
||||
document.addEventListener('keyup', sendKeyUp, false);
|
||||
document.addEventListener('keydown', sendKeyDown, false);
|
||||
}
|
||||
|
||||
|
||||
export function unregisterKeyboardEvents() {
|
||||
|
||||
//Stop listening to keyboard events
|
||||
document.removeEventListener('keyup', sendKeyUp, false);
|
||||
document.removeEventListener('keydown', sendKeyDown, false);
|
||||
}
|
||||
|
||||
|
||||
let sendMouse = undefined;
|
||||
let sendMouseWheel = undefined;
|
||||
let sendTouchEnd = undefined;
|
||||
let sendTouchStart = undefined;
|
||||
let sendTouchCancel = undefined;
|
||||
let sendTouchMove = undefined;
|
||||
|
||||
|
||||
export function registerMouseEvents(videoPlayer, playerElement) {
|
||||
|
||||
const _videoPlayer = videoPlayer;
|
||||
|
||||
function sendTouch(e, phase) {
|
||||
const changedTouches = Array.from(e.changedTouches);
|
||||
const touches = Array.from(e.touches);
|
||||
const phrases = [];
|
||||
|
||||
for (let i = 0; i < changedTouches.length; i++) {
|
||||
if (touches.find(function (t) {
|
||||
return t.identifier === changedTouches[i].identifier;
|
||||
}) === undefined) {
|
||||
touches.push(changedTouches[i]);
|
||||
}
|
||||
}
|
||||
|
||||
for (let i = 0; i < touches.length; i++) {
|
||||
touches[i].identifier;
|
||||
phrases[i] = changedTouches.find(
|
||||
function (e) {
|
||||
return e.identifier === touches[i].identifier;
|
||||
}) === undefined ? PointerPhase.Stationary : phase;
|
||||
}
|
||||
|
||||
Logger.log("touch phase:" + phase + " length:" + changedTouches.length + " pageX" + changedTouches[0].pageX + ", pageX: " + changedTouches[0].pageY + ", force:" + changedTouches[0].force);
|
||||
|
||||
let data = new DataView(new ArrayBuffer(2 + 13 * touches.length));
|
||||
data.setUint8(0, InputEvent.Touch);
|
||||
data.setUint8(1, touches.length);
|
||||
let byteOffset = 2;
|
||||
for (let i = 0; i < touches.length; i++) {
|
||||
|
||||
const scale = _videoPlayer.videoScale;
|
||||
const originX = _videoPlayer.videoOriginX;
|
||||
const originY = _videoPlayer.videoOriginY;
|
||||
|
||||
const x = (touches[i].pageX - originX) / scale;
|
||||
// According to Unity Coordinate system
|
||||
// const y = (touches[i].pageX - originY) / scale;
|
||||
const y = _videoPlayer.videoHeight - (touches[i].pageY - originY) / scale;
|
||||
|
||||
data.setInt32(byteOffset, touches[i].identifier, true);
|
||||
byteOffset += 4;
|
||||
data.setUint8(byteOffset, phrases[i]);
|
||||
byteOffset += 1;
|
||||
data.setInt16(byteOffset, x, true);
|
||||
byteOffset += 2;
|
||||
data.setInt16(byteOffset, y, true);
|
||||
byteOffset += 2;
|
||||
data.setFloat32(byteOffset, touches[i].force, true);
|
||||
byteOffset += 4;
|
||||
}
|
||||
_videoPlayer && _videoPlayer.sendMsg(data.buffer);
|
||||
}
|
||||
|
||||
sendTouchMove = (e) => {
|
||||
sendTouch(e, PointerPhase.Moved);
|
||||
e.preventDefault();
|
||||
};
|
||||
|
||||
sendTouchStart = (e) => {
|
||||
sendTouch(e, PointerPhase.Began);
|
||||
e.preventDefault();
|
||||
};
|
||||
|
||||
sendTouchEnd = (e) => {
|
||||
sendTouch(e, PointerPhase.Ended);
|
||||
e.preventDefault();
|
||||
};
|
||||
|
||||
sendTouchCancel = (e) => {
|
||||
sendTouch(e, PointerPhase.Canceled);
|
||||
e.preventDefault();
|
||||
};
|
||||
|
||||
sendMouse = (e) => {
|
||||
const scale = _videoPlayer.videoScale;
|
||||
const originX = _videoPlayer.videoOriginX;
|
||||
const originY = _videoPlayer.videoOriginY;
|
||||
|
||||
const x = (e.clientX - originX) / scale;
|
||||
// According to Unity Coordinate system
|
||||
// const y = (e.clientY - originY) / scale;
|
||||
const y = _videoPlayer.videoHeight - (e.clientY - originY) / scale;
|
||||
|
||||
Logger.log("x: " + x + ", y: " + y + ", scale: " + scale + ", originX: " + originX + ", originY: " + originY + " mouse button:" + e.buttons);
|
||||
let data = new DataView(new ArrayBuffer(6));
|
||||
data.setUint8(0, InputEvent.Mouse);
|
||||
data.setInt16(1, x, true);
|
||||
data.setInt16(3, y, true);
|
||||
data.setUint8(5, e.buttons);
|
||||
_videoPlayer && _videoPlayer.sendMsg(data.buffer);
|
||||
};
|
||||
|
||||
function sendMouseWheel(e) {
|
||||
Logger.log("mouse wheel with delta " + e.wheelDelta);
|
||||
let data = new DataView(new ArrayBuffer(9));
|
||||
data.setUint8(0, InputEvent.MouseWheel);
|
||||
data.setFloat32(1, e.deltaX, true);
|
||||
data.setFloat32(5, e.deltaY, true);
|
||||
_videoPlayer && _videoPlayer.sendMsg(data.buffer);
|
||||
}
|
||||
|
||||
// Listen to mouse events
|
||||
playerElement.addEventListener('click', sendMouse, false);
|
||||
playerElement.addEventListener('mousedown', sendMouse, false);
|
||||
playerElement.addEventListener('mouseup', sendMouse, false);
|
||||
playerElement.addEventListener('mousemove', sendMouse, false);
|
||||
playerElement.addEventListener('wheel', sendMouseWheel, false);
|
||||
|
||||
// Listen to touch events based on "Touch Events Level1" TR.
|
||||
//
|
||||
// Touch event Level1 https://www.w3.org/TR/touch-events/
|
||||
// Touch event Level2 https://w3c.github.io/touch-events/
|
||||
//
|
||||
playerElement.addEventListener('touchend', sendTouchEnd, false);
|
||||
playerElement.addEventListener('touchstart', sendTouchStart, false);
|
||||
playerElement.addEventListener('touchcancel', sendTouchCancel, false);
|
||||
playerElement.addEventListener('touchmove', sendTouchMove, false);
|
||||
}
|
||||
|
||||
|
||||
export function unregisterMouseEvents(playerElement) {
|
||||
|
||||
// Stop listening to mouse events
|
||||
playerElement.removeEventListener('click', sendMouse, false);
|
||||
playerElement.removeEventListener('mousedown', sendMouse, false);
|
||||
playerElement.removeEventListener('mouseup', sendMouse, false);
|
||||
playerElement.removeEventListener('mousemove', sendMouse, false);
|
||||
playerElement.removeEventListener('wheel', sendMouseWheel, false);
|
||||
|
||||
// Stop listening to touch events based on "Touch Events Level1" TR.
|
||||
playerElement.removeEventListener('touchend', sendTouchEnd, false);
|
||||
playerElement.removeEventListener('touchstart', sendTouchStart, false);
|
||||
playerElement.removeEventListener('touchcancel', sendTouchCancel, false);
|
||||
playerElement.removeEventListener('touchmove', sendTouchMove, false);
|
||||
|
||||
}
|
||||
|
||||
|
||||
export function sendClickEvent(videoPlayer, elementId) {
|
||||
let data = new DataView(new ArrayBuffer(3));
|
||||
data.setUint8(0, InputEvent.ButtonClick);
|
||||
data.setInt16(1, elementId, true);
|
||||
videoPlayer && videoPlayer.sendMsg(data.buffer);
|
||||
}
|
||||
246
client/public/videoplayer/js/video-player.js
Normal file
246
client/public/videoplayer/js/video-player.js
Normal file
@@ -0,0 +1,246 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user