mirror of
https://github.com/redphx/better-xcloud.git
synced 2025-06-07 16:17:20 +02:00
Add screenshot capture feature (#15)
* Add a button to test screenshot feature * Update CSS of the screenshot button * Prevent screenshot button from blocking UI * Add setting to change screenshot button's position
This commit is contained in:
parent
a81cb86140
commit
11c233e14e
@ -17,7 +17,9 @@ const SCRIPT_VERSION = '1.4.2';
|
|||||||
const SCRIPT_HOME = 'https://github.com/redphx/better-xcloud';
|
const SCRIPT_HOME = 'https://github.com/redphx/better-xcloud';
|
||||||
|
|
||||||
const SERVER_REGIONS = {};
|
const SERVER_REGIONS = {};
|
||||||
|
var $STREAM_VIDEO;
|
||||||
|
var $SCREENSHOT_CANVAS;
|
||||||
|
var GAME_TITLE_ID;
|
||||||
|
|
||||||
class StreamStatus {
|
class StreamStatus {
|
||||||
static ipv6 = false;
|
static ipv6 = false;
|
||||||
@ -56,6 +58,7 @@ class Preferences {
|
|||||||
static get FORCE_1080P_STREAM() { return 'force_1080p_stream'; }
|
static get FORCE_1080P_STREAM() { return 'force_1080p_stream'; }
|
||||||
static get USE_DESKTOP_CODEC() { return 'use_desktop_codec'; }
|
static get USE_DESKTOP_CODEC() { return 'use_desktop_codec'; }
|
||||||
|
|
||||||
|
static get SCREENSHOT_BUTTON_POSITION() { return 'screenshot_button_position'; }
|
||||||
static get BLOCK_TRACKING() { return 'block_tracking'; }
|
static get BLOCK_TRACKING() { return 'block_tracking'; }
|
||||||
static get BLOCK_SOCIAL_FEATURES() { return 'block_social_features'; }
|
static get BLOCK_SOCIAL_FEATURES() { return 'block_social_features'; }
|
||||||
static get DISABLE_BANDWIDTH_CHECKING() { return 'disable_bandwidth_checking'; }
|
static get DISABLE_BANDWIDTH_CHECKING() { return 'disable_bandwidth_checking'; }
|
||||||
@ -73,88 +76,71 @@ class Preferences {
|
|||||||
'id': Preferences.SERVER_REGION,
|
'id': Preferences.SERVER_REGION,
|
||||||
'label': 'Region of streaming server',
|
'label': 'Region of streaming server',
|
||||||
'default': 'default',
|
'default': 'default',
|
||||||
},
|
}, {
|
||||||
|
|
||||||
{
|
|
||||||
'id': Preferences.FORCE_1080P_STREAM,
|
'id': Preferences.FORCE_1080P_STREAM,
|
||||||
'label': 'Force 1080p stream',
|
'label': 'Force 1080p stream',
|
||||||
'default': false,
|
'default': false,
|
||||||
},
|
}, {
|
||||||
|
|
||||||
{
|
|
||||||
'id': Preferences.USE_DESKTOP_CODEC,
|
'id': Preferences.USE_DESKTOP_CODEC,
|
||||||
'label': 'Force high quality codec (if possible)',
|
'label': 'Force high quality codec (if possible)',
|
||||||
'default': false,
|
'default': false,
|
||||||
},
|
}, {
|
||||||
|
|
||||||
{
|
|
||||||
'id': Preferences.PREFER_IPV6_SERVER,
|
'id': Preferences.PREFER_IPV6_SERVER,
|
||||||
'label': 'Prefer IPv6 streaming server',
|
'label': 'Prefer IPv6 streaming server',
|
||||||
'default': false,
|
'default': false,
|
||||||
},
|
}, {
|
||||||
|
|
||||||
{
|
|
||||||
'id': Preferences.DISABLE_BANDWIDTH_CHECKING,
|
'id': Preferences.DISABLE_BANDWIDTH_CHECKING,
|
||||||
'label': 'Disable bandwidth checking',
|
'label': 'Disable bandwidth checking',
|
||||||
'default': false,
|
'default': false,
|
||||||
},
|
}, {
|
||||||
|
'id': Preferences.SCREENSHOT_BUTTON_POSITION,
|
||||||
{
|
'label': 'Screenshot button\'s position',
|
||||||
|
'default': 'bottom-left',
|
||||||
|
'options': {
|
||||||
|
'bottom-left': 'Bottom Left',
|
||||||
|
'bottom-right': 'Bottom Right',
|
||||||
|
'none': 'Disable',
|
||||||
|
},
|
||||||
|
}, {
|
||||||
'id': Preferences.SKIP_SPLASH_VIDEO,
|
'id': Preferences.SKIP_SPLASH_VIDEO,
|
||||||
'label': 'Skip Xbox splash video',
|
'label': 'Skip Xbox splash video',
|
||||||
'default': false,
|
'default': false,
|
||||||
},
|
}, {
|
||||||
|
|
||||||
{
|
|
||||||
'id': Preferences.HIDE_DOTS_ICON,
|
'id': Preferences.HIDE_DOTS_ICON,
|
||||||
'label': 'Hide Dots icon while playing',
|
'label': 'Hide Dots icon while playing',
|
||||||
'default': false,
|
'default': false,
|
||||||
},
|
}, {
|
||||||
|
|
||||||
{
|
|
||||||
'id': Preferences.REDUCE_ANIMATIONS,
|
'id': Preferences.REDUCE_ANIMATIONS,
|
||||||
'label': 'Reduce UI animations',
|
'label': 'Reduce UI animations',
|
||||||
'default': false,
|
'default': false,
|
||||||
},
|
}, {
|
||||||
|
|
||||||
{
|
|
||||||
'id': Preferences.BLOCK_SOCIAL_FEATURES,
|
'id': Preferences.BLOCK_SOCIAL_FEATURES,
|
||||||
'label': 'Disable social features',
|
'label': 'Disable social features',
|
||||||
'default': false,
|
'default': false,
|
||||||
},
|
}, {
|
||||||
|
|
||||||
{
|
|
||||||
'id': Preferences.BLOCK_TRACKING,
|
'id': Preferences.BLOCK_TRACKING,
|
||||||
'label': 'Disable xCloud analytics',
|
'label': 'Disable xCloud analytics',
|
||||||
'default': false,
|
'default': false,
|
||||||
},
|
}, {
|
||||||
|
|
||||||
{
|
|
||||||
'id': Preferences.VIDEO_FILL_FULL_SCREEN,
|
'id': Preferences.VIDEO_FILL_FULL_SCREEN,
|
||||||
'label': 'Stretch video to full screen',
|
'label': 'Stretch video to full screen',
|
||||||
'default': false,
|
'default': false,
|
||||||
'hidden': true,
|
'hidden': true,
|
||||||
},
|
}, {
|
||||||
|
|
||||||
{
|
|
||||||
'id': Preferences.VIDEO_SATURATION,
|
'id': Preferences.VIDEO_SATURATION,
|
||||||
'label': 'Video saturation (%)',
|
'label': 'Video saturation (%)',
|
||||||
'default': 100,
|
'default': 100,
|
||||||
'min': 0,
|
'min': 0,
|
||||||
'max': 150,
|
'max': 150,
|
||||||
'hidden': true,
|
'hidden': true,
|
||||||
},
|
}, {
|
||||||
|
|
||||||
{
|
|
||||||
'id': Preferences.VIDEO_CONTRAST,
|
'id': Preferences.VIDEO_CONTRAST,
|
||||||
'label': 'Video contrast (%)',
|
'label': 'Video contrast (%)',
|
||||||
'default': 100,
|
'default': 100,
|
||||||
'min': 0,
|
'min': 0,
|
||||||
'max': 150,
|
'max': 150,
|
||||||
'hidden': true,
|
'hidden': true,
|
||||||
},
|
}, {
|
||||||
|
|
||||||
{
|
|
||||||
'id': Preferences.VIDEO_BRIGHTNESS,
|
'id': Preferences.VIDEO_BRIGHTNESS,
|
||||||
'label': 'Video brightness (%)',
|
'label': 'Video brightness (%)',
|
||||||
'default': 100,
|
'default': 100,
|
||||||
@ -363,6 +349,37 @@ div[class*=StreamMenu-module__menuContainer] > div[class*=Menu-module] {
|
|||||||
border-radius: 0 4px 4px 0;
|
border-radius: 0 4px 4px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.better_xcloud_screenshot_button {
|
||||||
|
display: none;
|
||||||
|
opacity: 0;
|
||||||
|
position: fixed;
|
||||||
|
bottom: 0;
|
||||||
|
width: 60px;
|
||||||
|
height: 60px;
|
||||||
|
padding: 5px;
|
||||||
|
background-size: cover;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-origin: content-box;
|
||||||
|
filter: drop-shadow(0 0 2px #000000B0);
|
||||||
|
transition: opacity 0.1s ease-in-out 0s, padding 0.1s ease-in 0s;
|
||||||
|
z-index: 8888;
|
||||||
|
|
||||||
|
/* Credit: https://www.iconfinder.com/iconsets/user-interface-outline-27 */
|
||||||
|
background-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGhlaWdodD0iMjQiIHdpZHRoPSIyNCIgeG1sbnM6dj0iaHR0cHM6Ly92ZWN0YS5pby9uYW5vIiBmaWxsPSIjZmZmIj48cGF0aCBkPSJNMTIgN2E1LjAyIDUuMDIgMCAwIDAtNSA1IDUuMDIgNS4wMiAwIDAgMCA1IDUgNS4wMiA1LjAyIDAgMCAwIDUtNSA1LjAyIDUuMDIgMCAwIDAtNS01em0wIDJjMS42NjkgMCAzIDEuMzMxIDMgM3MtMS4zMzEgMy0zIDMtMy0xLjMzMS0zLTMgMS4zMzEtMyAzLTN6TTYgMkMzLjgwMSAyIDIgMy44MDEgMiA2djJhMSAxIDAgMSAwIDIgMFY2YTEuOTcgMS45NyAwIDAgMSAyLTJoMmExIDEgMCAxIDAgMC0yek0zIDE1YTEgMSAwIDAgMC0xIDF2MmMwIDIuMTk5IDEuODAxIDQgNCA0aDJhMSAxIDAgMSAwIDAtMkg2YTEuOTcgMS45NyAwIDAgMS0yLTJ2LTJhMSAxIDAgMCAwLTEtMXptMTggMGExIDEgMCAwIDAtMSAxdjJhMS45NyAxLjk3IDAgMCAxLTIgMmgtMmExIDEgMCAxIDAgMCAyaDJjMi4xOTkgMCA0LTEuODAxIDQtNHYtMmExIDEgMCAwIDAtMS0xeiIvPjxwYXRoIGQ9Ik0xNiAyYTEgMSAwIDEgMCAwIDJoMmExLjk3IDEuOTcgMCAwIDEgMiAydjJhMSAxIDAgMSAwIDIgMFY2YzAtMi4xOTktMS44MDEtNC00LTR6Ii8+PC9zdmc+Cg==);
|
||||||
|
}
|
||||||
|
|
||||||
|
.better_xcloud_screenshot_button[data-showing=true] {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.better_xcloud_screenshot_button[data-capturing=true] {
|
||||||
|
padding: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.better_xcloud_screenshot_canvas {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
/* Hide UI elements */
|
/* Hide UI elements */
|
||||||
#headerArea, #uhfSkipToMain, .uhf-footer {
|
#headerArea, #uhfSkipToMain, .uhf-footer {
|
||||||
display: none;
|
display: none;
|
||||||
@ -720,27 +737,41 @@ function injectSettingsButton($parent) {
|
|||||||
|
|
||||||
let $control;
|
let $control;
|
||||||
let labelAttrs = {};
|
let labelAttrs = {};
|
||||||
if (setting.id === Preferences.SERVER_REGION) {
|
if (setting.id === Preferences.SERVER_REGION || setting.options) {
|
||||||
|
let selectedValue;
|
||||||
|
|
||||||
$control = CE('select', {id: 'xcloud_setting_' + setting.id});
|
$control = CE('select', {id: 'xcloud_setting_' + setting.id});
|
||||||
$control.addEventListener('change', e => {
|
$control.addEventListener('change', e => {
|
||||||
PREFS.set(Preferences.SERVER_REGION, e.target.value);
|
PREFS.set(setting.id, e.target.value);
|
||||||
});
|
});
|
||||||
|
|
||||||
for (let regionName in SERVER_REGIONS) {
|
if (setting.id === Preferences.SERVER_REGION) {
|
||||||
const region = SERVER_REGIONS[regionName];
|
selectedValue = preferredRegion;
|
||||||
let value = regionName;
|
setting.options = {};
|
||||||
|
for (let regionName in SERVER_REGIONS) {
|
||||||
|
const region = SERVER_REGIONS[regionName];
|
||||||
|
let value = regionName;
|
||||||
|
|
||||||
let label = regionName;
|
let label = regionName;
|
||||||
if (region.isDefault) {
|
if (region.isDefault) {
|
||||||
label += ' (Default)';
|
label += ' (Default)';
|
||||||
value = 'default';
|
value = 'default';
|
||||||
|
}
|
||||||
|
|
||||||
|
setting.options[value] = label;
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
selectedValue = PREFS.get(setting.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let value in setting.options) {
|
||||||
|
const label = setting.options[value];
|
||||||
|
|
||||||
const $option = CE('option', {value: value}, label);
|
const $option = CE('option', {value: value}, label);
|
||||||
$option.selected = regionName === preferredRegion;
|
$option.selected = value === selectedValue || label.includes(selectedValue);
|
||||||
|
|
||||||
$control.appendChild($option);
|
$control.appendChild($option);
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
$control = CE('input', {
|
$control = CE('input', {
|
||||||
id: 'xcloud_setting_' + setting.id,
|
id: 'xcloud_setting_' + setting.id,
|
||||||
@ -972,6 +1003,7 @@ function injectVideoSettingsButton() {
|
|||||||
|
|
||||||
function patchVideoApi() {
|
function patchVideoApi() {
|
||||||
const PREF_SKIP_SPLASH_VIDEO = PREFS.get(Preferences.SKIP_SPLASH_VIDEO);
|
const PREF_SKIP_SPLASH_VIDEO = PREFS.get(Preferences.SKIP_SPLASH_VIDEO);
|
||||||
|
const PREF_SCREENSHOT_BUTTON_POSITION = PREFS.get(Preferences.SCREENSHOT_BUTTON_POSITION);
|
||||||
|
|
||||||
// Show video player when it's ready
|
// Show video player when it's ready
|
||||||
var showFunc;
|
var showFunc;
|
||||||
@ -980,7 +1012,23 @@ function patchVideoApi() {
|
|||||||
this.removeEventListener('playing', showFunc);
|
this.removeEventListener('playing', showFunc);
|
||||||
|
|
||||||
if (this.videoWidth) {
|
if (this.videoWidth) {
|
||||||
|
$STREAM_VIDEO = this;
|
||||||
|
$SCREENSHOT_CANVAS.width = this.videoWidth;
|
||||||
|
$SCREENSHOT_CANVAS.height = this.videoHeight;
|
||||||
StreamStatus.resolution = {width: this.videoWidth, height: this.videoHeight};
|
StreamStatus.resolution = {width: this.videoWidth, height: this.videoHeight};
|
||||||
|
|
||||||
|
if (PREF_SCREENSHOT_BUTTON_POSITION !== 'none') {
|
||||||
|
const $btn = document.querySelector('.better_xcloud_screenshot_button');
|
||||||
|
$btn.style.display = 'block';
|
||||||
|
|
||||||
|
if (PREF_SCREENSHOT_BUTTON_POSITION === 'bottom-right') {
|
||||||
|
$btn.style.right = '0';
|
||||||
|
} else {
|
||||||
|
$btn.style.left = '0';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
GAME_TITLE_ID = /\/launch\/([^/]+)/.exec(window.location.pathname)[1];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1214,6 +1262,75 @@ function setupVideoSettingsBar() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function setupScreenshotButton() {
|
||||||
|
$SCREENSHOT_CANVAS = createElement('canvas', {'class': 'better_xcloud_screenshot_canvas'});
|
||||||
|
document.documentElement.appendChild($SCREENSHOT_CANVAS);
|
||||||
|
|
||||||
|
const $canvasContext = $SCREENSHOT_CANVAS.getContext('2d');
|
||||||
|
|
||||||
|
const delay = 2000;
|
||||||
|
const $btn = createElement('div', {'class': 'better_xcloud_screenshot_button', 'data-showing': false});
|
||||||
|
|
||||||
|
let timeout;
|
||||||
|
const detectDbClick = e => {
|
||||||
|
if (!$STREAM_VIDEO) {
|
||||||
|
timeout = null;
|
||||||
|
$btn.style.display = 'none';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (timeout) {
|
||||||
|
clearTimeout(timeout);
|
||||||
|
timeout = null;
|
||||||
|
$btn.setAttribute('data-capturing', 'true');
|
||||||
|
|
||||||
|
$canvasContext.drawImage($STREAM_VIDEO, 0, 0, $SCREENSHOT_CANVAS.width, $SCREENSHOT_CANVAS.height);
|
||||||
|
$SCREENSHOT_CANVAS.toBlob(blob => {
|
||||||
|
// Download screenshot
|
||||||
|
const now = +new Date;
|
||||||
|
const $anchor = createElement('a', {
|
||||||
|
'download': `${GAME_TITLE_ID}-${now}.png`,
|
||||||
|
'href': URL.createObjectURL(blob),
|
||||||
|
});
|
||||||
|
$anchor.click();
|
||||||
|
|
||||||
|
// Free screenshot from memory
|
||||||
|
URL.revokeObjectURL($anchor.href);
|
||||||
|
$canvasContext.clearRect(0, 0, $SCREENSHOT_CANVAS.width, $SCREENSHOT_CANVAS.height);
|
||||||
|
|
||||||
|
// Hide button
|
||||||
|
$btn.setAttribute('data-showing', 'false');
|
||||||
|
setTimeout(() => {
|
||||||
|
if (!timeout) {
|
||||||
|
$btn.setAttribute('data-capturing', 'false');
|
||||||
|
}
|
||||||
|
}, 100);
|
||||||
|
}, 'image/png');
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const isShowing = $btn.getAttribute('data-showing') === 'true';
|
||||||
|
if (!isShowing) {
|
||||||
|
// Show button
|
||||||
|
$btn.setAttribute('data-showing', 'true');
|
||||||
|
$btn.setAttribute('data-capturing', 'false');
|
||||||
|
|
||||||
|
clearTimeout(timeout);
|
||||||
|
timeout = setTimeout(() => {
|
||||||
|
timeout = null;
|
||||||
|
$btn.setAttribute('data-showing', 'false');
|
||||||
|
$btn.setAttribute('data-capturing', 'false');
|
||||||
|
}, delay);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$btn.addEventListener('mousedown', detectDbClick);
|
||||||
|
document.documentElement.appendChild($btn);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
function patchHistoryMethod(type) {
|
function patchHistoryMethod(type) {
|
||||||
var orig = window.history[type];
|
var orig = window.history[type];
|
||||||
return function(...args) {
|
return function(...args) {
|
||||||
@ -1236,6 +1353,9 @@ function hideUiOnPageChange() {
|
|||||||
if ($quickBar) {
|
if ($quickBar) {
|
||||||
$quickBar.style.display = 'none';
|
$quickBar.style.display = 'none';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$STREAM_VIDEO = null;
|
||||||
|
document.querySelector('.better_xcloud_screenshot_button').style = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -1257,15 +1377,14 @@ if (PREFS.get(Preferences.DISABLE_BANDWIDTH_CHECKING)) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
patchRtcCodecs();
|
patchRtcCodecs();
|
||||||
|
|
||||||
interceptHttpRequests();
|
interceptHttpRequests();
|
||||||
|
|
||||||
patchVideoApi();
|
patchVideoApi();
|
||||||
|
|
||||||
// Setup UI
|
// Setup UI
|
||||||
addCss();
|
addCss();
|
||||||
updateVideoPlayerCss();
|
updateVideoPlayerCss();
|
||||||
setupVideoSettingsBar();
|
setupVideoSettingsBar();
|
||||||
|
setupScreenshotButton();
|
||||||
|
|
||||||
// Workaround for Hermit browser
|
// Workaround for Hermit browser
|
||||||
var onLoadTriggered = false;
|
var onLoadTriggered = false;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user