mirror of
https://github.com/giongto35/cloud-game.git
synced 2026-01-23 02:34:42 +00:00
Add screen component
This commit is contained in:
parent
effa5c46c5
commit
22d1bd7620
11 changed files with 192 additions and 179 deletions
|
|
@ -3,12 +3,11 @@
|
|||
src: url('/fonts/6809-Chargen.woff2');
|
||||
}
|
||||
|
||||
|
||||
/*noinspection CssInvalidPseudoSelector*/
|
||||
.no-media-controls::-webkit-media-controls {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
|
||||
html {
|
||||
/* force full size for Firefox */
|
||||
width: 100%;
|
||||
|
|
@ -170,25 +169,6 @@ body {
|
|||
transform: translateY(-50%);
|
||||
}
|
||||
|
||||
|
||||
#bottom-screen {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
width: 320px;
|
||||
height: 240px;
|
||||
position: absolute;
|
||||
top: 23px;
|
||||
left: 150px;
|
||||
overflow: hidden;
|
||||
background-color: #333;
|
||||
|
||||
border-radius: 5px 5px 5px 5px;
|
||||
|
||||
box-shadow: 0 0 2px 2px rgba(25, 25, 25, 1);
|
||||
}
|
||||
|
||||
#color-button-holder {
|
||||
display: block;
|
||||
width: 120px;
|
||||
|
|
@ -419,14 +399,8 @@ body {
|
|||
opacity: 0.75;
|
||||
}
|
||||
|
||||
#bottom-screen {
|
||||
position: absolute;
|
||||
/* popups under the screen fix */
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
.game-screen {
|
||||
position: absolute;
|
||||
position: relative;
|
||||
object-fit: contain;
|
||||
width: inherit;
|
||||
height: inherit;
|
||||
|
|
@ -665,6 +639,7 @@ body {
|
|||
position: absolute;
|
||||
z-index: 200;
|
||||
backface-visibility: hidden;
|
||||
cursor: default;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
|
@ -674,13 +649,13 @@ body {
|
|||
right: 1.1em;
|
||||
color: #fff;
|
||||
background: #000;
|
||||
opacity: .765;
|
||||
padding: .5em 1em .1em 1em;
|
||||
opacity: .465;
|
||||
|
||||
font-size: 2vh;
|
||||
font-family: monospace;
|
||||
font-size: 40%;
|
||||
min-width: 3.5em;
|
||||
|
||||
width: 70px;
|
||||
padding-right: .2em;
|
||||
|
||||
visibility: hidden;
|
||||
}
|
||||
|
|
@ -689,8 +664,7 @@ body {
|
|||
display: flex;
|
||||
flex-flow: wrap;
|
||||
justify-content: space-between;
|
||||
|
||||
margin-bottom: .7em;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
#stats-overlay > div > div {
|
||||
|
|
|
|||
|
|
@ -227,3 +227,24 @@
|
|||
.app-button:hover {
|
||||
color: #7e7e7e;
|
||||
}
|
||||
|
||||
|
||||
#screen {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
position: absolute;
|
||||
/* popups under the screen fix */
|
||||
z-index: -1;
|
||||
|
||||
width: 320px;
|
||||
height: 240px;
|
||||
top: 23px;
|
||||
left: 150px;
|
||||
overflow: hidden;
|
||||
background-color: #333;
|
||||
|
||||
border-radius: 5px 5px 5px 5px;
|
||||
box-shadow: 0 0 2px 2px rgba(25, 25, 25, 1);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@
|
|||
<meta property="og:type" content="cloud-game"/>
|
||||
<meta property="og:title" content="Web-based Cloud Gaming for Retro Games"/>
|
||||
<meta property="og:description" content="Play and share cloud gaming experience with your friends"/>
|
||||
<meta property="og:image" content="http://cloud.webgame2d.com/static/img/ogimage.jpg"/>
|
||||
<meta property="og:url" content=""/>
|
||||
<meta property="og:site_name" content="Cloud Retro"/>
|
||||
<meta property="og:author" content="giongto35 trichimtrich"/>
|
||||
|
|
@ -31,14 +30,9 @@
|
|||
<div id="circle-pad"></div>
|
||||
</div>
|
||||
|
||||
<div id="bottom-screen">
|
||||
<div id="screen">
|
||||
<div id="stats-overlay"></div>
|
||||
<!--NOTE: New browser doesn't allow unmuted video player. So we muted here.
|
||||
There is still audio because current audio flow is not from media but it is manually encoded (technical webRTC challenge). Later, when we can integrate audio to media, we can face the issue with mute again .
|
||||
https://developers.google.com/web/updates/2017/09/autoplay-policy-changes
|
||||
-->
|
||||
<video id="stream" class="game-screen" hidden muted preload="none"></video>
|
||||
|
||||
<div id="menu-screen">
|
||||
<div id="menu-container"></div>
|
||||
<div id="menu-item-choice" class="hidden"></div>
|
||||
|
|
|
|||
|
|
@ -24,7 +24,6 @@ import {
|
|||
KEY_PRESSED,
|
||||
KEY_RELEASED,
|
||||
LATENCY_CHECK_REQUESTED,
|
||||
MENU_HANDLER_ATTACHED,
|
||||
MESSAGE,
|
||||
RECORDING_STATUS_CHANGED,
|
||||
RECORDING_TOGGLED,
|
||||
|
|
@ -45,9 +44,11 @@ import {socket, webrtc} from 'network';
|
|||
import {debounce} from 'utils';
|
||||
|
||||
import {gameList} from './gameList.js?v=3';
|
||||
import {menu} from './menu.js?v=3';
|
||||
import {message} from './message.js?v=3';
|
||||
import {recording} from './recording.js?v=3';
|
||||
import {room} from './room.js?v=3';
|
||||
import {screen} from './screen.js?v=3';
|
||||
import {stats} from './stats.js?v=3';
|
||||
import {stream} from './stream.js?v=3';
|
||||
import {workerManager} from "./workerManager.js?v=3";
|
||||
|
|
@ -59,10 +60,12 @@ let lastState;
|
|||
// first user interaction
|
||||
let interacted = false;
|
||||
|
||||
const menuScreen = document.getElementById('menu-screen');
|
||||
const helpOverlay = document.getElementById('help-overlay');
|
||||
const playerIndex = document.getElementById('playeridx');
|
||||
|
||||
// screen init
|
||||
screen.add(menu, stream);
|
||||
|
||||
// keymap
|
||||
const keyButtons = {};
|
||||
Object.keys(KEY).forEach(button => {
|
||||
|
|
@ -121,18 +124,12 @@ const onLatencyCheck = async (data) => {
|
|||
};
|
||||
|
||||
const helpScreen = {
|
||||
// don't call $ if holding the button
|
||||
shown: false,
|
||||
// use function () if you need "this"
|
||||
show: function (show, event) {
|
||||
if (this.shown === show) return;
|
||||
|
||||
const isGameScreen = state === app.state.game
|
||||
if (isGameScreen) {
|
||||
stream.toggle(!show);
|
||||
} else {
|
||||
gui.toggle(menuScreen, !show);
|
||||
}
|
||||
screen.toggle(undefined, !show);
|
||||
|
||||
gui.toggle(keyButtons[KEY.SAVE], show || isGameScreen);
|
||||
gui.toggle(keyButtons[KEY.LOAD], show || isGameScreen);
|
||||
|
|
@ -148,12 +145,11 @@ const helpScreen = {
|
|||
const showMenuScreen = () => {
|
||||
log.debug('[control] loading menu screen');
|
||||
|
||||
stream.toggle(false);
|
||||
gui.hide(keyButtons[KEY.SAVE]);
|
||||
gui.hide(keyButtons[KEY.LOAD]);
|
||||
|
||||
gameList.show();
|
||||
gui.show(menuScreen);
|
||||
screen.toggle(menu);
|
||||
|
||||
setState(app.state.menu);
|
||||
};
|
||||
|
|
@ -185,9 +181,7 @@ const startGame = () => {
|
|||
|
||||
// clear menu screen
|
||||
retropad.poll.disable();
|
||||
gui.hide(menuScreen);
|
||||
stream.toggle(true);
|
||||
stream.forceFullscreenMaybe();
|
||||
screen.toggle(stream);
|
||||
gui.show(keyButtons[KEY.SAVE]);
|
||||
gui.show(keyButtons[KEY.LOAD]);
|
||||
// end clear
|
||||
|
|
@ -427,7 +421,7 @@ const app = {
|
|||
loadGame();
|
||||
break;
|
||||
case KEY.FULL:
|
||||
stream.video.toggleFullscreen();
|
||||
screen.fullscreen();
|
||||
break;
|
||||
case KEY.PAD1:
|
||||
updatePlayerIndex(0);
|
||||
|
|
@ -481,7 +475,7 @@ sub(WEBRTC_NEW_CONNECTION, (data) => {
|
|||
});
|
||||
sub(WEBRTC_ICE_CANDIDATE_FOUND, (data) => api.server.sendIceCandidate(data.candidate));
|
||||
sub(WEBRTC_SDP_ANSWER, (data) => api.server.sendSdp(data.sdp));
|
||||
sub(WEBRTC_SDP_OFFER, (data) => webrtc.setRemoteDescription(data.sdp, stream.video.el()));
|
||||
sub(WEBRTC_SDP_OFFER, (data) => webrtc.setRemoteDescription(data.sdp, stream.video.el));
|
||||
sub(WEBRTC_ICE_CANDIDATE_RECEIVED, (data) => webrtc.addCandidate(data.candidate));
|
||||
sub(WEBRTC_ICE_CANDIDATES_FLUSH, () => webrtc.flushCandidates());
|
||||
sub(WEBRTC_CONNECTION_READY, onConnectionReady);
|
||||
|
|
@ -492,23 +486,20 @@ sub(WEBRTC_CONNECTION_CLOSED, () => {
|
|||
sub(LATENCY_CHECK_REQUESTED, onLatencyCheck);
|
||||
sub(GAMEPAD_CONNECTED, () => message.show('Gamepad connected'));
|
||||
sub(GAMEPAD_DISCONNECTED, () => message.show('Gamepad disconnected'));
|
||||
// touch stuff
|
||||
sub(MENU_HANDLER_ATTACHED, (data) => {
|
||||
menuScreen.addEventListener(data.event, data.handler, {passive: true});
|
||||
});
|
||||
sub(KEY_PRESSED, onKeyPress);
|
||||
sub(KEY_RELEASED, onKeyRelease);
|
||||
sub(SETTINGS_CHANGED, () => message.show('Settings have been updated'));
|
||||
sub(AXIS_CHANGED, onAxisChanged);
|
||||
sub(CONTROLLER_UPDATED, data => webrtc.input(data));
|
||||
// recording
|
||||
sub(RECORDING_TOGGLED, handleRecording);
|
||||
sub(RECORDING_STATUS_CHANGED, handleRecordingStatus);
|
||||
|
||||
sub(SETTINGS_CHANGED, () => {
|
||||
const newValue = settings.get()[opts.LOG_LEVEL];
|
||||
if (newValue !== log.level) {
|
||||
log.level = newValue;
|
||||
const s = settings.get();
|
||||
log.level = s[opts.LOG_LEVEL];
|
||||
if (state.showPing !== s[opts.SHOW_PING]) {
|
||||
state.showPing = s[opts.SHOW_PING];
|
||||
stats.toggle();
|
||||
}
|
||||
});
|
||||
|
||||
|
|
@ -519,6 +510,7 @@ keyboard.init();
|
|||
joystick.init();
|
||||
touch.init();
|
||||
stream.init();
|
||||
screen.init();
|
||||
|
||||
let [roomId, zone] = room.loadMaybe();
|
||||
// find worker id if present
|
||||
|
|
@ -528,32 +520,21 @@ socket.init(roomId, wid, zone);
|
|||
api.transport = socket;
|
||||
|
||||
// stats
|
||||
let WEBRTC_STATS_FRAME_DELAY;
|
||||
let WEBRTC_STATS_RTT;
|
||||
|
||||
stats.modules = [
|
||||
{
|
||||
mui: stats.mui('Ping', true),
|
||||
mui: stats.mui(),
|
||||
init() {
|
||||
WEBRTC_STATS_RTT = (v) => (this.val = v)
|
||||
},
|
||||
},
|
||||
{
|
||||
mui: stats.mui('FrameDelay', false, () => ''),
|
||||
init() {
|
||||
WEBRTC_STATS_FRAME_DELAY = (v) => (this.val = v)
|
||||
}
|
||||
},
|
||||
{
|
||||
async stats() {
|
||||
const stats = await webrtc.stats();
|
||||
if (!stats) return;
|
||||
|
||||
stats.forEach(report => {
|
||||
const {framesReceived, framesDecoded, framesDropped} = report;
|
||||
if (framesReceived !== undefined && framesDecoded !== undefined && framesDropped !== undefined) {
|
||||
WEBRTC_STATS_FRAME_DELAY(framesReceived - framesDecoded - framesDropped)
|
||||
}
|
||||
const {nominated, currentRoundTripTime} = report;
|
||||
if (nominated && currentRoundTripTime !== undefined) {
|
||||
WEBRTC_STATS_RTT(currentRoundTripTime * 1000);
|
||||
|
|
@ -567,3 +548,6 @@ stats.modules = [
|
|||
window.clearInterval(this.interval);
|
||||
},
|
||||
}]
|
||||
|
||||
state.showPing = settings.loadOr(opts.SHOW_PING, true);
|
||||
state.showPing && stats.toggle();
|
||||
|
|
|
|||
|
|
@ -74,7 +74,11 @@ const isPortrait = () => getWidth(page) < getHeight(page);
|
|||
|
||||
const toggleFullscreen = (enable, element) => {
|
||||
const el = enable ? element : document;
|
||||
enable ? el.requestFullscreen?.() : el.exitFullscreen?.();
|
||||
if (enable) {
|
||||
el.requestFullscreen?.().then().catch();
|
||||
return
|
||||
}
|
||||
el.exitFullscreen?.().then().catch();
|
||||
}
|
||||
|
||||
function getHeight(el) {
|
||||
|
|
@ -94,9 +98,9 @@ export const env = {
|
|||
getBrowser: _browser(),
|
||||
isMobileDevice: isMobile(),
|
||||
display: () => ({
|
||||
isPortrait: isPortrait,
|
||||
toggleFullscreen: toggleFullscreen,
|
||||
fixScreenLayout: fixScreenLayout,
|
||||
isPortrait,
|
||||
toggleFullscreen,
|
||||
fixScreenLayout,
|
||||
isLayoutSwitched: isLayoutSwitched
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -194,11 +194,11 @@ const hide = (el) => {
|
|||
}
|
||||
|
||||
const toggle = (el, what) => {
|
||||
if (what) {
|
||||
show(el)
|
||||
} else {
|
||||
hide(el)
|
||||
if (what === undefined) {
|
||||
el.classList.toggle('hidden')
|
||||
return
|
||||
}
|
||||
what ? show(el) : hide(el)
|
||||
}
|
||||
|
||||
const fadeIn = async (el, speed = .1) => {
|
||||
|
|
|
|||
17
web/js/menu.js
Normal file
17
web/js/menu.js
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
import {gui} from 'gui';
|
||||
import {
|
||||
sub,
|
||||
MENU_HANDLER_ATTACHED,
|
||||
} from 'event';
|
||||
|
||||
const rootEl = document.getElementById('menu-screen');
|
||||
|
||||
// touch stuff
|
||||
sub(MENU_HANDLER_ATTACHED, (data) => {
|
||||
rootEl.addEventListener(data.event, data.handler, {passive: true});
|
||||
});
|
||||
|
||||
export const menu = {
|
||||
toggle: (show) => show === undefined ? gui.toggle(rootEl) : gui.toggle(rootEl, show),
|
||||
noFullscreen: true,
|
||||
}
|
||||
48
web/js/screen.js
Normal file
48
web/js/screen.js
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
import {opts, settings} from 'settings';
|
||||
import {SETTINGS_CHANGED, sub} from "event";
|
||||
import {env} from "env";
|
||||
|
||||
const rootEl = document.getElementById('screen');
|
||||
|
||||
const state = {
|
||||
components: [],
|
||||
current: undefined,
|
||||
forceFullscreen: false,
|
||||
}
|
||||
|
||||
const toggle = (component, force) => {
|
||||
component && (state.current = component); // keep the last component
|
||||
state.components.forEach(c => c.toggle(false));
|
||||
state.current?.toggle(force);
|
||||
component && !env.isMobileDevice && !state.current?.noFullscreen && state.forceFullscreen && fullscreen();
|
||||
}
|
||||
|
||||
const init = () => {
|
||||
state.forceFullscreen = settings.loadOr(opts.FORCE_FULLSCREEN, false);
|
||||
sub(SETTINGS_CHANGED, () => {
|
||||
state.forceFullscreen = settings.get()[opts.FORCE_FULLSCREEN];
|
||||
});
|
||||
}
|
||||
|
||||
const fullscreen = () => {
|
||||
let h = parseFloat(getComputedStyle(rootEl, null)
|
||||
.height
|
||||
.replace('px', '')
|
||||
)
|
||||
env.display().toggleFullscreen(h !== window.innerHeight, rootEl);
|
||||
}
|
||||
|
||||
rootEl.addEventListener('fullscreenchange', () => {
|
||||
state.current?.onFullscreen?.(document.fullscreenElement !== null)
|
||||
})
|
||||
|
||||
export const screen = {
|
||||
fullscreen,
|
||||
toggle,
|
||||
/**
|
||||
* Adds a component. It should have toggle(bool) method and
|
||||
* an optional noFullscreen (bool) property.
|
||||
*/
|
||||
add: (...o) => state.components.push(...o),
|
||||
init,
|
||||
}
|
||||
|
|
@ -22,12 +22,13 @@ export const opts = {
|
|||
INPUT_KEYBOARD_MAP: 'input.keyboard.map',
|
||||
MIRROR_SCREEN: 'mirror.screen',
|
||||
VOLUME: 'volume',
|
||||
FORCE_FULLSCREEN: 'force.fullscreen'
|
||||
FORCE_FULLSCREEN: 'force.fullscreen',
|
||||
SHOW_PING: 'show.ping',
|
||||
}
|
||||
|
||||
|
||||
// internal structure version
|
||||
const revision = 1.51;
|
||||
const revision = 1.6;
|
||||
|
||||
// default settings
|
||||
// keep them for revert to defaults option
|
||||
|
|
@ -510,6 +511,12 @@ const render = function () {
|
|||
.add(gui.checkbox(k, onChange, value, 'Enabled', 'settings__option-checkbox'))
|
||||
.build()
|
||||
break;
|
||||
case opts.SHOW_PING:
|
||||
_option(data).withName('Show ping')
|
||||
.withDescription('Always display ping info on the screen')
|
||||
.add(gui.checkbox(k, onChange, value, 'Enabled', 'settings__option-checkbox'))
|
||||
.build()
|
||||
break;
|
||||
default:
|
||||
_option(data).withName(k).add(value).build();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -134,7 +134,7 @@ const moduleUi = (label = '', withGraph = false, postfix = () => 'ms') => {
|
|||
|
||||
_label.innerHTML = label;
|
||||
|
||||
const withPostfix = (value) => postfix_ = value;
|
||||
const withPostfix = (value) => (postfix_ = value);
|
||||
|
||||
const update = (value) => {
|
||||
if (_graph) _graph.add(value);
|
||||
|
|
@ -198,8 +198,8 @@ const disable = () => {
|
|||
_hide();
|
||||
}
|
||||
|
||||
const _show = () => statsOverlayEl.style.visibility = 'visible';
|
||||
const _hide = () => statsOverlayEl.style.visibility = 'hidden';
|
||||
const _show = () => (statsOverlayEl.style.visibility = 'visible');
|
||||
const _hide = () => (statsOverlayEl.style.visibility = 'hidden');
|
||||
|
||||
/**
|
||||
* Handles help overlay toggle event.
|
||||
|
|
@ -224,7 +224,6 @@ const onHelpOverlayToggle = (overlay) => {
|
|||
|
||||
const render = () => modules(m => m.render(), false);
|
||||
|
||||
// add submodules
|
||||
sub(HELP_OVERLAY_TOGGLED, onHelpOverlayToggle)
|
||||
|
||||
/**
|
||||
|
|
|
|||
141
web/js/stream.js
141
web/js/stream.js
|
|
@ -1,4 +1,3 @@
|
|||
import {env} from 'env';
|
||||
import {
|
||||
sub,
|
||||
APP_VIDEO_CHANGED,
|
||||
|
|
@ -8,49 +7,36 @@ import {gui} from 'gui';
|
|||
import {log} from 'log';
|
||||
import {opts, settings} from 'settings';
|
||||
|
||||
const screen = document.getElementById('stream');
|
||||
const videoEl = document.getElementById('stream');
|
||||
|
||||
let options = {
|
||||
volume: 0.5,
|
||||
poster: '/img/screen_loading.gif',
|
||||
mirrorMode: null,
|
||||
mirrorUpdateRate: 1 / 60,
|
||||
forceFullscreen: true,
|
||||
},
|
||||
state = {
|
||||
screen: screen,
|
||||
fullscreen: false,
|
||||
timerId: null,
|
||||
w: 0,
|
||||
h: 0,
|
||||
aspect: 4 / 3
|
||||
};
|
||||
const options = {
|
||||
volume: 0.5,
|
||||
poster: '/img/screen_loading.gif',
|
||||
mirrorMode: null,
|
||||
mirrorUpdateRate: 1 / 60,
|
||||
}
|
||||
|
||||
const mute = (mute) => (screen.muted = mute)
|
||||
const state = {
|
||||
screen: videoEl,
|
||||
timerId: null,
|
||||
w: 0,
|
||||
h: 0,
|
||||
aspect: 4 / 3
|
||||
}
|
||||
|
||||
const mute = (mute) => (videoEl.muted = mute)
|
||||
|
||||
const _stream = () => {
|
||||
screen.play()
|
||||
videoEl.play()
|
||||
.then(() => log.info('Media can autoplay'))
|
||||
.catch(error => {
|
||||
log.error('Media failed to play', error);
|
||||
});
|
||||
}
|
||||
|
||||
const toggle = (show) => {
|
||||
state.screen.toggleAttribute('hidden', !show)
|
||||
}
|
||||
const toggle = (show) => state.screen.toggleAttribute('hidden', show === undefined ? show : !show)
|
||||
|
||||
const toggleFullscreen = () => {
|
||||
let h = parseFloat(getComputedStyle(state.screen, null)
|
||||
.height
|
||||
.replace('px', '')
|
||||
)
|
||||
env.display().toggleFullscreen(h !== window.innerHeight, state.screen);
|
||||
}
|
||||
|
||||
const getVideoEl = () => screen
|
||||
|
||||
screen.onerror = (e) => {
|
||||
videoEl.onerror = (e) => {
|
||||
// video playback failed - show a message saying why
|
||||
switch (e.target.error.code) {
|
||||
case e.target.error.MEDIA_ERR_ABORTED:
|
||||
|
|
@ -71,18 +57,18 @@ screen.onerror = (e) => {
|
|||
}
|
||||
};
|
||||
|
||||
screen.addEventListener('loadedmetadata', () => {
|
||||
if (state.screen !== screen) {
|
||||
state.screen.setAttribute('width', screen.videoWidth);
|
||||
state.screen.setAttribute('height', screen.videoHeight);
|
||||
videoEl.addEventListener('loadedmetadata', () => {
|
||||
if (state.screen !== videoEl) {
|
||||
state.screen.setAttribute('width', videoEl.videoWidth);
|
||||
state.screen.setAttribute('height', videoEl.videoHeight);
|
||||
}
|
||||
}, false);
|
||||
screen.addEventListener('loadstart', () => {
|
||||
screen.volume = options.volume;
|
||||
screen.poster = options.poster;
|
||||
videoEl.addEventListener('loadstart', () => {
|
||||
videoEl.volume = options.volume;
|
||||
videoEl.poster = options.poster;
|
||||
}, false);
|
||||
screen.addEventListener('canplay', () => {
|
||||
screen.poster = '';
|
||||
videoEl.addEventListener('canplay', () => {
|
||||
videoEl.poster = '';
|
||||
useCustomScreen(options.mirrorMode === 'mirror');
|
||||
}, false);
|
||||
|
||||
|
|
@ -90,39 +76,25 @@ const screenToAspect = (el) => {
|
|||
const w = window.screen.width ?? window.innerWidth;
|
||||
const hh = el.innerHeight || el.clientHeight || 0;
|
||||
const dw = (w - hh * state.aspect) / 2
|
||||
screen.style.padding = `0 ${dw}px`
|
||||
videoEl.style.padding = `0 ${dw}px`
|
||||
}
|
||||
|
||||
screen.addEventListener('fullscreenchange', () => {
|
||||
state.fullscreen = !!document.fullscreenElement;
|
||||
|
||||
if (state.fullscreen) {
|
||||
const onFullscreen = (y) => {
|
||||
if (y) {
|
||||
screenToAspect(document.fullscreenElement);
|
||||
// chrome bug
|
||||
setTimeout(() => {
|
||||
screenToAspect(document.fullscreenElement)
|
||||
}, 1)
|
||||
} else {
|
||||
screen.style.padding = '0'
|
||||
videoEl.style.padding = '0'
|
||||
}
|
||||
makeFullscreen(state.fullscreen);
|
||||
|
||||
// !to flipped
|
||||
})
|
||||
|
||||
const makeFullscreen = (make = false) => {
|
||||
screen.classList.toggle('no-media-controls', make)
|
||||
}
|
||||
|
||||
const forceFullscreenMaybe = () => {
|
||||
const touchMode = env.isMobileDevice;
|
||||
log.debug('touch check', touchMode)
|
||||
!touchMode && options.forceFullscreen && toggleFullscreen();
|
||||
videoEl.classList.toggle('no-media-controls', !!y)
|
||||
}
|
||||
|
||||
const useCustomScreen = (use) => {
|
||||
if (use) {
|
||||
if (screen.paused || screen.ended) return;
|
||||
if (videoEl.paused || videoEl.ended) return;
|
||||
|
||||
let id = state.screen.getAttribute('id');
|
||||
if (id === 'canvas-mirror') return;
|
||||
|
|
@ -130,34 +102,33 @@ const useCustomScreen = (use) => {
|
|||
const canvas = gui.create('canvas');
|
||||
canvas.setAttribute('id', 'canvas-mirror');
|
||||
canvas.setAttribute('hidden', '');
|
||||
canvas.setAttribute('width', screen.videoWidth);
|
||||
canvas.setAttribute('height', screen.videoHeight);
|
||||
canvas.setAttribute('width', videoEl.videoWidth);
|
||||
canvas.setAttribute('height', videoEl.videoHeight);
|
||||
canvas.style['image-rendering'] = 'pixelated';
|
||||
canvas.style.width = '100%'
|
||||
canvas.style.height = '100%'
|
||||
canvas.classList.add('game-screen');
|
||||
|
||||
// stretch depending on the video orientation
|
||||
// portrait -- vertically, landscape -- horizontally
|
||||
const isPortrait = screen.videoWidth < screen.videoHeight;
|
||||
const isPortrait = videoEl.videoWidth < videoEl.videoHeight;
|
||||
canvas.style.width = isPortrait ? 'auto' : canvas.style.width;
|
||||
// canvas.style.height = isPortrait ? canvas.style.height : 'auto';
|
||||
|
||||
let surface = canvas.getContext('2d');
|
||||
screen.parentNode.insertBefore(canvas, screen.nextSibling);
|
||||
videoEl.parentNode.insertBefore(canvas, videoEl.nextSibling);
|
||||
toggle(false)
|
||||
state.screen = canvas
|
||||
toggle(true)
|
||||
state.timerId = setInterval(function () {
|
||||
if (screen.paused || screen.ended || !surface) return;
|
||||
surface.drawImage(screen, 0, 0);
|
||||
if (videoEl.paused || videoEl.ended || !surface) return;
|
||||
surface.drawImage(videoEl, 0, 0);
|
||||
}, options.mirrorUpdateRate);
|
||||
} else {
|
||||
clearInterval(state.timerId);
|
||||
let mirror = state.screen;
|
||||
state.screen = screen;
|
||||
state.screen = videoEl;
|
||||
toggle(true);
|
||||
if (mirror !== screen) {
|
||||
if (mirror !== videoEl) {
|
||||
mirror.parentNode.removeChild(mirror);
|
||||
}
|
||||
}
|
||||
|
|
@ -166,22 +137,16 @@ const useCustomScreen = (use) => {
|
|||
const init = () => {
|
||||
options.mirrorMode = settings.loadOr(opts.MIRROR_SCREEN, 'none');
|
||||
options.volume = settings.loadOr(opts.VOLUME, 50) / 100;
|
||||
options.forceFullscreen = settings.loadOr(opts.FORCE_FULLSCREEN, false);
|
||||
sub(SETTINGS_CHANGED, () => {
|
||||
const s = settings.get();
|
||||
const newValue = s[opts.MIRROR_SCREEN];
|
||||
if (newValue !== options.mirrorMode) {
|
||||
useCustomScreen(newValue === 'mirror');
|
||||
options.mirrorMode = newValue;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
sub(SETTINGS_CHANGED, () => {
|
||||
const s = settings.get();
|
||||
const newValue = s[opts.MIRROR_SCREEN];
|
||||
if (newValue !== options.mirrorMode) {
|
||||
useCustomScreen(newValue === 'mirror');
|
||||
options.mirrorMode = newValue;
|
||||
}
|
||||
const newValue2 = s[opts.FORCE_FULLSCREEN];
|
||||
if (newValue2 !== options.forceFullscreen) {
|
||||
options.forceFullscreen = newValue2;
|
||||
}
|
||||
});
|
||||
|
||||
const fit = 'contain'
|
||||
|
||||
sub(APP_VIDEO_CHANGED, (payload) => {
|
||||
|
|
@ -211,10 +176,10 @@ sub(APP_VIDEO_CHANGED, (payload) => {
|
|||
*/
|
||||
export const stream = {
|
||||
audio: {mute},
|
||||
video: {toggleFullscreen, el: getVideoEl},
|
||||
video: {el: videoEl},
|
||||
play: _stream,
|
||||
toggle,
|
||||
useCustomScreen,
|
||||
forceFullscreenMaybe,
|
||||
init
|
||||
init,
|
||||
onFullscreen,
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue