mirror of
https://github.com/LizardByte/Sunshine.git
synced 2025-01-29 09:32:39 +00:00
1422 lines
61 KiB
HTML
1422 lines
61 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
|
|
<head>
|
|
<%- header %>
|
|
<style>
|
|
.config-page {
|
|
padding: 1em;
|
|
border: 1px solid #dee2e6;
|
|
border-top: none;
|
|
}
|
|
|
|
.buttons {
|
|
padding: 1em 0;
|
|
}
|
|
|
|
.ms-item {
|
|
background-color: #ccc;
|
|
font-size: 12px;
|
|
font-weight: bold;
|
|
}
|
|
</style>
|
|
</head>
|
|
|
|
<body id="app">
|
|
<Navbar></Navbar>
|
|
<div class="container">
|
|
<h1 class="my-4">Configuration</h1>
|
|
<div class="form" v-if="config">
|
|
<!-- Header -->
|
|
<ul class="nav nav-tabs">
|
|
<li class="nav-item" v-for="tab in tabs" :key="tab.id">
|
|
<a class="nav-link" :class="{'active': tab.id === currentTab}" href="#"
|
|
@click="currentTab = tab.id">{{tab.name}}</a>
|
|
</li>
|
|
</ul>
|
|
|
|
<!-- General Tab -->
|
|
<div id="general" v-if="currentTab === 'general'" class="config-page">
|
|
<!-- Sunshine Name -->
|
|
<div class="mb-3">
|
|
<label for="sunshine_name" class="form-label">Sunshine Name</label>
|
|
<input type="text" class="form-control" id="sunshine_name" placeholder="Sunshine"
|
|
v-model="config.sunshine_name" />
|
|
<div class="form-text">
|
|
The name displayed by Moonlight. If not specified, the PC's hostname is used
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Log Level -->
|
|
<div class="mb-3">
|
|
<label for="min_log_level" class="form-label">Log Level</label>
|
|
<select id="min_log_level" class="form-select" v-model="config.min_log_level">
|
|
<option value="0">Verbose</option>
|
|
<option value="1">Debug</option>
|
|
<option value="2">Info</option>
|
|
<option value="3">Warning</option>
|
|
<option value="4">Error</option>
|
|
<option value="5">Fatal</option>
|
|
<option value="6">None</option>
|
|
</select>
|
|
<div class="form-text">
|
|
The minimum log level printed to standard out
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Global Prep Commands -->
|
|
<div id="global_prep_cmd" class="mb-3 d-flex flex-column">
|
|
<label class="form-label">Command Preparations</label>
|
|
<div class="form-text">
|
|
Configure a list of commands to be executed before or after running any application.
|
|
If any of the specified preparation commands fail, the application launch process will be aborted.
|
|
</div>
|
|
<table class="table" v-if="global_prep_cmd.length > 0">
|
|
<thead>
|
|
<tr>
|
|
<th scope="col"><i class="fas fa-play"></i> Do Command</th>
|
|
<th scope="col"><i class="fas fa-undo"></i> Undo Command</th>
|
|
<th scope="col" v-if="platform === 'windows'">
|
|
<i class="fas fa-shield-alt"></i> Run as Admin
|
|
</th>
|
|
<th scope="col"></th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr v-for="(c, i) in global_prep_cmd">
|
|
<td>
|
|
<input type="text" class="form-control monospace" v-model="c.do" />
|
|
</td>
|
|
<td>
|
|
<input type="text" class="form-control monospace" v-model="c.undo" />
|
|
</td>
|
|
<td v-if="platform === 'windows'">
|
|
<div class="form-check">
|
|
<input type="checkbox" class="form-check-input" :id="'prep-cmd-admin-' + i" v-model="c.elevated"
|
|
true-value="true" false-value="false" />
|
|
<label :for="'prep-cmd-admin-' + i" class="form-check-label">Elevated</label>
|
|
</div>
|
|
</td>
|
|
<td>
|
|
<button class="btn btn-danger" @click="global_prep_cmd.splice(i,1)">
|
|
<i class="fas fa-trash"></i>
|
|
</button>
|
|
<button class="btn btn-success" @click="add_global_prep_cmd">
|
|
<i class="fas fa-plus"></i>
|
|
</button>
|
|
</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
<button class="ms-0 mt-2 btn btn-success" style="margin: 0 auto" @click="add_global_prep_cmd">
|
|
+ Add
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Files Tab -->
|
|
<div id="files" v-if="currentTab === 'files'" class="config-page">
|
|
<!-- Apps File -->
|
|
<div class="mb-3">
|
|
<label for="file_apps" class="form-label">Apps File</label>
|
|
<input type="text" class="form-control" id="file_apps" placeholder="apps.json" v-model="config.file_apps" />
|
|
<div class="form-text">
|
|
The file where current apps of Sunshine are stored
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Credentials File -->
|
|
<div class="mb-3">
|
|
<label for="credentials_file" class="form-label">Credentials File</label>
|
|
<input type="text" class="form-control" id="credentials_file" placeholder="sunshine_state.json" v-model="config.credentials_file" />
|
|
<div class="form-text">
|
|
Store Username/Password separately from Sunshine's state file.
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Log Path -->
|
|
<div class="mb-3">
|
|
<label for="log_path" class="form-label">Logfile Path</label>
|
|
<input type="text" class="form-control" id="log_path" placeholder="sunshine.log" v-model="config.log_path" />
|
|
<div class="form-text">
|
|
The file where the current logs of Sunshine are stored.
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Private Key -->
|
|
<div class="mb-3">
|
|
<label for="pkey" class="form-label">Private Key</label>
|
|
<input type="text" class="form-control" id="pkey" placeholder="/dir/pkey.pem" v-model="config.pkey" />
|
|
<div class="form-text">The private key used for the web UI and Moonlight client pairing. For best
|
|
compatibility, this should be an RSA-2048 private key.</div>
|
|
</div>
|
|
|
|
<!-- Certificate -->
|
|
<div class="mb-3">
|
|
<label for="cert" class="form-label">Certificate</label>
|
|
<input type="text" class="form-control" id="cert" placeholder="/dir/cert.pem" v-model="config.cert" />
|
|
<div class="form-text">
|
|
The certificate used for the web UI and Moonlight client pairing. For best compatibility, this should have
|
|
an RSA-2048 public key.
|
|
</div>
|
|
</div>
|
|
|
|
<!-- State File -->
|
|
<div class="mb-3">
|
|
<label for="file_state" class="form-label">State File</label>
|
|
<input type="text" class="form-control" id="file_state" placeholder="sunshine_state.json"
|
|
v-model="config.file_state" />
|
|
<div class="form-text">
|
|
The file where current state of Sunshine is stored
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<!-- Input Tab -->
|
|
<div id="input" v-if="currentTab === 'input'" class="config-page">
|
|
<!-- Enable Gamepad Input -->
|
|
<div class="mb-3">
|
|
<label for="controller" class="form-label">Enable Gamepad Input</label>
|
|
<select id="controller" class="form-select" v-model="config.controller">
|
|
<option value="disabled">Disabled</option>
|
|
<option value="enabled">Enabled</option>
|
|
</select>
|
|
<div class="form-text">
|
|
Allows guests to control the host system with a gamepad / controller
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Emulated Gamepad Type -->
|
|
<div class="mb-3" v-if="config.controller === 'enabled' && platform === 'windows'">
|
|
<label for="gamepad" class="form-label">Emulated Gamepad Type</label>
|
|
<select id="gamepad" class="form-select" v-model="config.gamepad">
|
|
<option value="auto">Automatic</option>
|
|
<option value="ds4">DS4 (PS4)</option>
|
|
<option value="x360">X360 (Xbox 360)</option>
|
|
</select>
|
|
<div class="form-text">Choose which type of gamepad to emulate on the host</div>
|
|
</div>
|
|
<div class="accordion" v-if="config.gamepad === 'ds4'">
|
|
<div class="accordion-item">
|
|
<h2 class="accordion-header">
|
|
<button class="accordion-button" type="button" data-bs-toggle="collapse"
|
|
data-bs-target="#panelsStayOpen-collapseOne">
|
|
Manual DS4 options
|
|
</button>
|
|
</h2>
|
|
<div id="panelsStayOpen-collapseOne" class="accordion-collapse collapse show"
|
|
aria-labelledby="panelsStayOpen-headingOne">
|
|
<div class="accordion-body">
|
|
<div>
|
|
<label for="ds4_back_as_touchpad_click" class="form-label">Map Back/Select to Touchpad Click</label>
|
|
<select id="ds4_back_as_touchpad_click" class="form-select"
|
|
v-model="config.ds4_back_as_touchpad_click">
|
|
<option value="disabled">Disabled</option>
|
|
<option value="enabled">Enabled (default)</option>
|
|
</select>
|
|
<div class="form-text">When forcing DS4 emulation, map Back/Select to Touchpad Click</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="accordion" v-if="config.controller === 'enabled' && config.gamepad === 'auto'">
|
|
<div class="accordion-item">
|
|
<h2 class="accordion-header">
|
|
<button class="accordion-button" type="button" data-bs-toggle="collapse"
|
|
data-bs-target="#panelsStayOpen-collapseOne">
|
|
Automatic selection options
|
|
</button>
|
|
</h2>
|
|
<div id="panelsStayOpen-collapseOne" class="accordion-collapse collapse show"
|
|
aria-labelledby="panelsStayOpen-headingOne">
|
|
<div class="accordion-body">
|
|
<div>
|
|
<label for="motion_as_ds4" class="form-label">Emulate a DS4 gamepad if the client gamepad reports motion sensors are present</label>
|
|
<select id="motion_as_ds4" class="form-select"
|
|
v-model="config.motion_as_ds4">
|
|
<option value="disabled">Disabled</option>
|
|
<option value="enabled">Enabled (default)</option>
|
|
</select>
|
|
<div class="form-text">If disabled, motion sensors will not be taken into account during gamepad type selection.</div>
|
|
</div>
|
|
<div>
|
|
<label for="touchpad_as_ds4" class="form-label">Emulate a DS4 gamepad if the client gamepad reports a touchpad is present</label>
|
|
<select id="touchpad_as_ds4" class="form-select"
|
|
v-model="config.touchpad_as_ds4">
|
|
<option value="disabled">Disabled</option>
|
|
<option value="enabled">Enabled (default)</option>
|
|
</select>
|
|
<div class="form-text">If disabled, touchpad presence will not be taken into account during gamepad type selection.</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Home/Guide Button Emulation Timeout -->
|
|
<div class="mb-3" v-if="config.controller === 'enabled'">
|
|
<label for="back_button_timeout" class="form-label">Home/Guide Button Emulation Timeout</label>
|
|
<input type="text" class="form-control" id="back_button_timeout" placeholder="-1"
|
|
v-model="config.back_button_timeout" />
|
|
<div class="form-text">
|
|
If the Back/Select button is held down for the specified number of milliseconds, a Home/Guide button press
|
|
is emulated.<br>
|
|
If set to a value < 0 (default), holding the Back/Select button will not emulate the Home/Guide button.<br>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Enable Keyboard Input -->
|
|
<hr>
|
|
<div class="mb-3">
|
|
<label for="keyboard" class="form-label">Enable Keyboard Input</label>
|
|
<select id="keyboard" class="form-select" v-model="config.keyboard">
|
|
<option value="disabled">Disabled</option>
|
|
<option value="enabled">Enabled</option>
|
|
</select>
|
|
<div class="form-text">
|
|
Allows guests to control the host system with the keyboard
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Key Repeat Delay-->
|
|
<div class="mb-3" v-if="config.keyboard === 'enabled' && platform === 'windows'">
|
|
<label for="key_repeat_delay" class="form-label">Key Repeat Delay</label>
|
|
<input type="text" class="form-control" id="key_repeat_delay" placeholder="500"
|
|
v-model="config.key_repeat_delay" />
|
|
<div class="form-text">
|
|
Control how fast keys will repeat themselves<br>
|
|
The initial delay in milliseconds before repeating keys
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Key Repeat Frequency-->
|
|
<div class="mb-3" v-if="config.keyboard === 'enabled' && platform === 'windows'">
|
|
<label for="key_repeat_frequency" class="form-label">Key Repeat Frequency</label>
|
|
<input type="text" class="form-control" id="key_repeat_frequency" placeholder="24.9"
|
|
v-model="config.key_repeat_frequency" />
|
|
<div class="form-text">
|
|
How often keys repeat every second<br>
|
|
This configurable option supports decimals
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Always send scancodes -->
|
|
<div class="mb-3" v-if="config.keyboard === 'enabled' && platform === 'windows'">
|
|
<label for="always_send_scancodes" class="form-label">Always Send Scancodes</label>
|
|
<select id="always_send_scancodes" class="form-select" v-model="config.always_send_scancodes">
|
|
<option value="disabled">Disabled</option>
|
|
<option value="enabled">Enabled</option>
|
|
</select>
|
|
<div class="form-text">
|
|
Sending scancodes enhances compatibility with games and apps but may result in incorrect keyboard
|
|
input from certain clients that aren't using a US English keyboard layout.<br>
|
|
Enable if keyboard input is not working at all in certain applications.<br>
|
|
Disable if keys on the client are generating the wrong input on the host.
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Mapping Key AltRight to Key Windows -->
|
|
<div class="mb-3" v-if="config.keyboard === 'enabled'">
|
|
<label for="key_rightalt_to_key_win" class="form-label">Map Right Alt key to Windows key</label>
|
|
<select id="key_rightalt_to_key_win" class="form-select" v-model="config.key_rightalt_to_key_win">
|
|
<option value="disabled">Disabled</option>
|
|
<option value="enabled">Enabled</option>
|
|
</select>
|
|
<div class="form-text">
|
|
It may be possible that you cannot send the Windows Key from Moonlight directly.<br>
|
|
In those cases it may be useful to make Sunshine think the Right Alt key is the Windows key
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Enable Mouse Input -->
|
|
<hr>
|
|
<div class="mb-3">
|
|
<label for="mouse" class="form-label">Enable Mouse Input</label>
|
|
<select id="mouse" class="form-select" v-model="config.mouse">
|
|
<option value="disabled">Disabled</option>
|
|
<option value="enabled">Enabled</option>
|
|
</select>
|
|
<div class="form-text">
|
|
Allows guests to control the host system with the mouse
|
|
</div>
|
|
</div>
|
|
|
|
<!-- High resolution scrolling support -->
|
|
<div class="mb-3" v-if="config.mouse === 'enabled'">
|
|
<label for="high_resolution_scrolling" class="form-label">High Resolution Scrolling Support</label>
|
|
<select id="high_resolution_scrolling" class="form-select" v-model="config.high_resolution_scrolling">
|
|
<option value="disabled">Disabled</option>
|
|
<option value="enabled">Enabled</option>
|
|
</select>
|
|
<div class="form-text">
|
|
When enabled, Sunshine will pass through high resolution scroll events from Moonlight clients.<br>
|
|
This can be useful to disable for older applications that scroll too fast with high resolution
|
|
scroll events.
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Native pen/touch support -->
|
|
<div class="mb-3" v-if="config.mouse === 'enabled'">
|
|
<label for="native_pen_touch" class="form-label">Native Pen/Touch Support</label>
|
|
<select id="native_pen_touch" class="form-select" v-model="config.native_pen_touch">
|
|
<option value="disabled">Disabled</option>
|
|
<option value="enabled">Enabled</option>
|
|
</select>
|
|
<div class="form-text">
|
|
When enabled, Sunshine will pass through native pen/touch events from Moonlight clients.<br>
|
|
This can be useful to disable for older applications without native pen/touch support.
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<!-- Audio/Video Tab -->
|
|
<div id="audio-video" v-if="currentTab === 'av'" class="config-page">
|
|
<!-- Audio Sink -->
|
|
<div class="mb-3" v-if="platform === 'windows'">
|
|
<label for="audio_sink" class="form-label">Audio Sink</label>
|
|
<input type="text" class="form-control" id="audio_sink" placeholder="Speakers (High Definition Audio Device)"
|
|
v-model="config.audio_sink" />
|
|
<div class="form-text">
|
|
Manually specify a specific audio device to capture. If unset, the device is chosen automatically.<br>
|
|
<b>We strongly recommend leaving this field blank to use automatic device selection!</b><br>
|
|
If you have multiple audio devices with identical names, you can get the Device ID using the following
|
|
command:<br>
|
|
<pre>tools\audio-info.exe</pre>
|
|
</div>
|
|
</div>
|
|
<div class="mb-3" v-if="platform === 'linux'">
|
|
<label for="audio_sink" class="form-label">Audio Sink</label>
|
|
<input type="text" class="form-control" id="audio_sink"
|
|
placeholder="alsa_output.pci-0000_09_00.3.analog-stereo" v-model="config.audio_sink" />
|
|
<div class="form-text">
|
|
The name of the audio sink used for Audio Loopback<br>
|
|
If you do not specify this variable, pulseaudio will select the default monitor device.<br>
|
|
<br>
|
|
You can find the name of the audio sink using either command:<br>
|
|
<pre>pacmd list-sinks | grep "name:"</pre>
|
|
<pre>pactl info | grep Source</pre>
|
|
<br>
|
|
</div>
|
|
</div>
|
|
<div class="mb-3" v-if="platform === 'macos'">
|
|
<label for="audio_sink" class="form-label">Audio Sink</label>
|
|
<input type="text" class="form-control" id="audio_sink" placeholder="BlackHole 2ch"
|
|
v-model="config.audio_sink" />
|
|
<div class="form-text">
|
|
The name of the audio sink used for Audio Loopback<br>
|
|
Sunshine can only access microphones on macOS due to system limitations.<br>
|
|
To stream system audio using
|
|
<a href="https://github.com/mattingalls/Soundflower" target="_blank">Soundflower</a> or
|
|
<a href="https://github.com/ExistentialAudio/BlackHole" target="_blank">BlackHole</a>.
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Virtual Sink -->
|
|
<div class="mb-3" v-if="platform === 'windows'">
|
|
<label for="virtual_sink" class="form-label">Virtual Sink</label>
|
|
<input type="text" class="form-control" id="virtual_sink" placeholder="Steam Streaming Speakers"
|
|
v-model="config.virtual_sink" />
|
|
<div class="form-text">
|
|
Manually specify a virtual audio device to use. If unset, the device is chosen automatically.<br>
|
|
<b>We strongly recommend leaving this field blank to use automatic device selection!</b><br>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Install Steam Audio Drivers -->
|
|
<div class="mb-3" v-if="platform === 'windows'">
|
|
<label for="install_steam_audio_drivers" class="form-label">Install Steam Audio Drivers</label>
|
|
<select id="install_steam_audio_drivers" class="form-select" v-model="config.install_steam_audio_drivers">
|
|
<option value="disabled">Disabled</option>
|
|
<option value="enabled">Enabled</option>
|
|
</select>
|
|
<div class="form-text">
|
|
If Steam is installed, this will automatically install the Steam Streaming Speakers driver to support
|
|
5.1/7.1 surround sound and muting host audio.
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Adapter Name -->
|
|
<div class="mb-3" v-if="platform === 'windows'">
|
|
<label for="adapter_name" class="form-label">Adapter Name</label>
|
|
<input type="text" class="form-control" id="adapter_name" placeholder="Radeon RX 580 Series"
|
|
v-model="config.adapter_name" />
|
|
<div class="form-text">
|
|
Manually specify a GPU to use for capture. If unset, the GPU is chosen automatically.<br>
|
|
<b>We strongly recommend leaving this field blank to use automatic GPU selection!</b><br>
|
|
Note: This GPU must have a display connected and powered on.<br>
|
|
The appropriate values can be found using the following command:<br>
|
|
<pre>tools\dxgi-info.exe</pre>
|
|
</div>
|
|
</div>
|
|
<div class="mb-3" v-if="platform === 'linux'">
|
|
<label for="adapter_name" class="form-label">Adapter Name</label>
|
|
<input class="form-control" id="adapter_name" placeholder="/dev/dri/renderD128" v-model="config.adapter_name" />
|
|
<div class="form-text">
|
|
Manually specify a GPU to use for capture.<br>
|
|
<pre>ls /dev/dri/renderD* # to find all devices capable of VAAPI</pre>
|
|
<pre>
|
|
vainfo --display drm --device /dev/dri/renderD129 | \
|
|
grep -E "((VAProfileH264High|VAProfileHEVCMain|VAProfileHEVCMain10).*VAEntrypointEncSlice)|Driver version"
|
|
</pre>
|
|
Replace ``renderD129`` with the device from above to lists the name and capabilities of the device.<br>
|
|
To be supported by Sunshine, it needs to have at the very minimum:
|
|
<i>VAProfileH264High : VAEntrypointEncSlice</i>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Output Name -->
|
|
<div class="mb-3" v-if="platform === 'windows'">
|
|
<label for="output_name" class="form-label">Output Name</label>
|
|
<input type="text" class="form-control" id="output_name" placeholder="\\.\DISPLAY1"
|
|
v-model="config.output_name" />
|
|
<div class="form-text">
|
|
Manually specify a display to use for capture. If unset, the primary display is captured.<br>
|
|
Note: If you specified a GPU above, this display must be connected to that GPU.<br>
|
|
The appropriate values can be found using the following command:<br>
|
|
tools\dxgi-info.exe<br>
|
|
</div>
|
|
</div>
|
|
<div class="mb-3" v-if="platform === 'linux'">
|
|
<label for="output_name" class="form-label">Monitor number</label>
|
|
<input type="text" class="form-control" id="output_name" placeholder="0" v-model="config.output_name" />
|
|
<div class="form-text">
|
|
During Sunshine startup, you should see the list of detected monitors, e.g.:<br>
|
|
<br>
|
|
<pre style="white-space: pre-line;">
|
|
Info: Detecting connected monitors
|
|
Info: Detected monitor 0: DVI-D-0, connected: false
|
|
Info: Detected monitor 1: HDMI-0, connected: true
|
|
Info: Detected monitor 2: DP-0, connected: true
|
|
Info: Detected monitor 3: DP-1, connected: false
|
|
Info: Detected monitor 4: DVI-D-1, connected: false
|
|
</pre>
|
|
You need to use the value before the colon in the output, e.g. <b>1</b>.
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Display Modes -->
|
|
<div class="mb-3">
|
|
<!-- Advertised Resolutions -->
|
|
<div id="resolutions" class="resolutions-container">
|
|
<label>Advertised Resolutions</label>
|
|
<div class="resolutions d-flex flex-wrap">
|
|
<div class="p-2 ms-item m-2 d-flex justify-content-between" v-for="(r,i) in resolutions" :key="r">
|
|
<span class="px-2">{{r}}</span>
|
|
<span style="cursor: pointer" @click="resolutions.splice(i,1)">×</span>
|
|
</div>
|
|
</div>
|
|
<form @submit.prevent="resolutions.push(resIn);resIn = '';" class="d-flex align-items-center">
|
|
<input type="text" v-model="resIn" required pattern="[0-9]+x[0-9]+" style="
|
|
width: 12ch;
|
|
border-top-right-radius: 0;
|
|
border-bottom-right-radius: 0;
|
|
" class="form-control" />
|
|
<button style="border-top-left-radius: 0; border-bottom-left-radius: 0" class="btn btn-success">
|
|
+
|
|
</button>
|
|
</form>
|
|
</div>
|
|
|
|
<!-- Advertised FPS -->
|
|
<div id="fps" class="fps-container">
|
|
<label>Advertised FPS</label>
|
|
<div class="fps d-flex flex-wrap">
|
|
<div class="p-2 ms-item m-2 d-flex justify-content-between" v-for="(f,i) in fps" :key="f">
|
|
<span class="px-2">{{f}}</span>
|
|
<span style="cursor: pointer" @click="fps.splice(i,1)">×</span>
|
|
</div>
|
|
</div>
|
|
<form @submit.prevent="fps.push(fpsIn);fpsIn = '';" class="d-flex align-items-center">
|
|
<input type="text" v-model="fpsIn" required pattern="[0-9]+" style="
|
|
width: 6ch;
|
|
border-top-right-radius: 0;
|
|
border-bottom-right-radius: 0;
|
|
" class="form-control" />
|
|
<button style="border-top-left-radius: 0; border-bottom-left-radius: 0" class="btn btn-success">
|
|
+
|
|
</button>
|
|
</form>
|
|
</div>
|
|
|
|
<div class="form-text">
|
|
The display modes advertised by Sunshine.<br>
|
|
Some versions of Moonlight, such as Moonlight-nx (Switch), rely on these lists to ensure that the requested
|
|
resolutions and fps are supported.<br>
|
|
This setting does <b>not</b> change how the screen stream is sent to Moonlight.
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<!-- Network Tab -->
|
|
<div id="network" v-if="currentTab === 'network'" class="config-page">
|
|
<!-- Address family -->
|
|
<div class="mb-3">
|
|
<label for="address_family" class="form-label">Address Family</label>
|
|
<select id="address_family" class="form-select" v-model="config.address_family">
|
|
<option value="ipv4">IPv4 only</option>
|
|
<option value="both">IPv4+IPv6</option>
|
|
</select>
|
|
<div class="form-text">Set the address family used by Sunshine</div>
|
|
</div>
|
|
|
|
<!-- Port family -->
|
|
<div class="mb-3">
|
|
<label for="port" class="form-label">Port</label>
|
|
<input type="number" min="1029" max="65514" class="form-control" id="port" placeholder="47989"
|
|
v-model="config.port" />
|
|
<div class="form-text">Set the family of ports used by Sunshine</div>
|
|
<!-- Add warning if any port is less than 1024 -->
|
|
<div class="alert alert-danger" v-if="(+effectivePort - 5) < 1024">
|
|
<i class="fa-solid fa-xl fa-triangle-exclamation"></i> Sunshine cannot use ports below 1024!
|
|
</div>
|
|
<!-- Add warning if any port is above 65535 -->
|
|
<div class="alert alert-danger" v-if="(+effectivePort + 21) > 65535">
|
|
<i class="fa-solid fa-xl fa-triangle-exclamation"></i> Ports above 65535 are not available!
|
|
</div>
|
|
<!-- Create a port table for the various ports needed by Sunshine -->
|
|
<table class="table">
|
|
<thead>
|
|
<tr>
|
|
<th scope="col">Protocol</th>
|
|
<th scope="col">Port</th>
|
|
<th scope="col">Note</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr>
|
|
<!-- HTTPS -->
|
|
<td>TCP</td>
|
|
<td>{{+effectivePort - 5}}</td>
|
|
<td></td>
|
|
</tr>
|
|
<tr>
|
|
<!-- HTTP -->
|
|
<td>TCP</td>
|
|
<td>{{+effectivePort}}</td>
|
|
<td>
|
|
<div class="alert alert-primary" role="alert" v-if="+effectivePort !== 47989">
|
|
<i class="fa-solid fa-xl fa-circle-info"></i> Use this port to connect with Moonlight.
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<!-- Web UI -->
|
|
<td>TCP</td>
|
|
<td>{{+effectivePort + 1}}</td>
|
|
<td>Web UI</td>
|
|
</tr>
|
|
<tr>
|
|
<!-- RTSP -->
|
|
<td>TCP</td>
|
|
<td>{{+effectivePort + 21}}</td>
|
|
<td></td>
|
|
</tr>
|
|
<tr>
|
|
<!-- Video, Control, Audio -->
|
|
<td>UDP</td>
|
|
<td>{{+effectivePort + 9}} - {{+effectivePort + 11}}</td>
|
|
<td></td>
|
|
</tr>
|
|
<!-- <tr>-->
|
|
<!-- <!– Mic –>-->
|
|
<!-- <td>UDP</td>-->
|
|
<!-- <td>{{+effectivePort + 13}}</td>-->
|
|
<!-- <td></td>-->
|
|
<!-- </tr>-->
|
|
</tbody>
|
|
</table>
|
|
<!-- add warning about exposing web ui to the internet -->
|
|
<div class="alert alert-warning" v-if="config.origin_web_ui_allowed === 'wan'">
|
|
<i class="fa-solid fa-xl fa-triangle-exclamation"></i> Exposing the Web UI to the internet is a security
|
|
risk! Proceed at your own risk!
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Origin Web UI Allowed -->
|
|
<div class="mb-3">
|
|
<label for="origin_web_ui_allowed" class="form-label">Origin Web UI Allowed</label>
|
|
<select id="origin_web_ui_allowed" class="form-select" v-model="config.origin_web_ui_allowed">
|
|
<option value="pc">Only localhost may access Web UI</option>
|
|
<option value="lan">Only those in LAN may access Web UI</option>
|
|
<option value="wan">Anyone may access Web UI</option>
|
|
</select>
|
|
<div class="form-text">
|
|
The origin of the remote endpoint address that is not denied access to Web UI
|
|
</div>
|
|
</div>
|
|
|
|
<!-- UPnP -->
|
|
<div class="mb-3">
|
|
<label for="upnp" class="form-label">UPnP</label>
|
|
<select id="upnp" class="form-select" v-model="config.upnp">
|
|
<option value="disabled">Disabled</option>
|
|
<option value="enabled">Enabled</option>
|
|
</select>
|
|
<div class="form-text">Automatically configure port forwarding</div>
|
|
</div>
|
|
|
|
<!-- External IP -->
|
|
<div class="mb-3">
|
|
<label for="external_ip" class="form-label">External IP</label>
|
|
<input type="text" class="form-control" id="external_ip" placeholder="123.456.789.12" v-model="config.external_ip" />
|
|
<div class="form-text">
|
|
If no external IP address is given, Sunshine will automatically detect external IP
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Ping Timeout -->
|
|
<div class="mb-3">
|
|
<label for="ping_timeout" class="form-label">Ping Timeout</label>
|
|
<input type="text" class="form-control" id="ping_timeout" placeholder="10000" v-model="config.ping_timeout" />
|
|
<div class="form-text">
|
|
How long to wait in milliseconds for data from moonlight before shutting down the stream
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<!-- Advanced Tab -->
|
|
<div v-if="currentTab === 'advanced'" class="config-page">
|
|
<!-- Channels -->
|
|
<div class="mb-3">
|
|
<label for="channels" class="form-label">Channels</label>
|
|
<input type="text" class="form-control" id="channels" placeholder="1" v-model="config.channels" />
|
|
<div class="form-text">
|
|
When multicasting, it could be useful to have different configurations for each connected Client. For example:
|
|
<ul>
|
|
<li>Clients connected through WAN and LAN have different bitrate constraints.</li>
|
|
<li>Decoders may require different settings for color</li>
|
|
</ul>
|
|
Unlike simply broadcasting to multiple Client, this will generate distinct video streams.<br>
|
|
Note, CPU usage increases for each distinct video stream generated
|
|
</div>
|
|
</div>
|
|
|
|
<!-- FEC Percentage -->
|
|
<div class="mb-3">
|
|
<label for="fec_percentage" class="form-label">FEC Percentage</label>
|
|
<input type="text" class="form-control" id="fec_percentage" placeholder="20" v-model="config.fec_percentage" />
|
|
<div class="form-text">
|
|
Percentage of error correcting packets per data packet in each video frame.<br>
|
|
Higher values can correct for more network packet loss, but at the cost of increasing bandwidth usage.<br>
|
|
The default value of 20 is what GeForce Experience uses.
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Quantization Parameter -->
|
|
<div class="mb-3">
|
|
<label for="qp" class="form-label">Quantization Parameter</label>
|
|
<input type="number" class="form-control" id="qp" placeholder="28" v-model="config.qp" />
|
|
<div class="form-text">
|
|
Quantization Parameter<br>
|
|
Some devices may not support Constant Bit Rate.<br>
|
|
For those devices, QP is used instead.<br>
|
|
Higher value means more compression, but less quality<br>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Min Threads -->
|
|
<div class="mb-3">
|
|
<label for="min_threads" class="form-label">Minimum Software Encoding Thread Count</label>
|
|
<input type="number" class="form-control" id="min_threads" placeholder="1" min="1" v-model="config.min_threads" />
|
|
<div class="form-text">
|
|
Increasing the value slightly reduces encoding efficiency, but the tradeoff is usually<br>
|
|
worth it to gain the use of more CPU cores for encoding. The ideal value is the lowest<br>
|
|
value that can reliably encode at your desired streaming settings on your hardware.
|
|
</div>
|
|
</div>
|
|
|
|
<!-- HEVC Support -->
|
|
<div class="mb-3">
|
|
<label for="hevc_mode" class="form-label">HEVC Support</label>
|
|
<select id="hevc_mode" class="form-select" v-model="config.hevc_mode">
|
|
<option value="0">
|
|
Sunshine will advertise support for HEVC based on encoder capabilities (recommended)
|
|
</option>
|
|
<option value="1">
|
|
Sunshine will not advertise support for HEVC
|
|
</option>
|
|
<option value="2">
|
|
Sunshine will advertise support for HEVC Main profile
|
|
</option>
|
|
<option value="3">
|
|
Sunshine will advertise support for HEVC Main and Main10 (HDR) profiles
|
|
</option>
|
|
</select>
|
|
<div class="form-text">
|
|
Allows the client to request HEVC Main or HEVC Main10 video streams.<br>
|
|
HEVC is more CPU-intensive to encode, so enabling this may reduce performance when using software encoding.
|
|
</div>
|
|
</div>
|
|
|
|
<!-- AV1 Support -->
|
|
<div class="mb-3">
|
|
<label for="av1_mode" class="form-label">AV1 Support</label>
|
|
<select id="av1_mode" class="form-select" v-model="config.av1_mode">
|
|
<option value="0">
|
|
Sunshine will advertise support for AV1 based on encoder capabilities (recommended)
|
|
</option>
|
|
<option value="1">
|
|
Sunshine will not advertise support for AV1
|
|
</option>
|
|
<option value="2">
|
|
Sunshine will advertise support for AV1 Main 8-bit profile
|
|
</option>
|
|
<option value="3">
|
|
Sunshine will advertise support for AV1 Main 8-bit and 10-bit (HDR) profiles
|
|
</option>
|
|
</select>
|
|
<div class="form-text">
|
|
Allows the client to request AV1 Main 8-bit or 10-bit video streams.<br>
|
|
AV1 is more CPU-intensive to encode, so enabling this may reduce performance when using software encoding.
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Capture -->
|
|
<div class="mb-3" v-if="platform === 'linux'">
|
|
<label for="capture" class="form-label">Force a Specific Capture Method</label>
|
|
<select id="capture" class="form-select" v-model="config.capture">
|
|
<option value="">Autodetect (recommended)</option>
|
|
<option value="nvfbc">NvFBC</option>
|
|
<option value="wlr">wlroots</option>
|
|
<option value="kms">KMS</option>
|
|
<option value="x11">X11</option>
|
|
</select>
|
|
<div class="form-text">
|
|
Force a specific capture method, otherwise Sunshine will use the first one that works.
|
|
NvFBC requires patched nvidia drivers.
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Encoder -->
|
|
<div class="mb-3">
|
|
<label for="encoder" class="form-label">Force a Specific Encoder</label>
|
|
<select id="encoder" class="form-select" v-model="config.encoder">
|
|
<option value="">Autodetect (recommended)</option>
|
|
<option value="nvenc" v-if="platform === 'windows' || platform === 'linux'">NVIDIA NVENC</option>
|
|
<option value="quicksync" v-if="platform === 'windows'">Intel QuickSync</option>
|
|
<option value="amdvce" v-if="platform === 'windows'">AMD AMF/VCE</option>
|
|
<option value="vaapi" v-if="platform === 'linux'">VA-API</option>
|
|
<option value="videotoolbox" v-if="platform === 'macos'">VideoToolbox</option>
|
|
<option value="software">Software</option>
|
|
</select>
|
|
<div class="form-text">
|
|
Force a specific encoder, otherwise Sunshine will select the best available option.<br>
|
|
Note: If you specify a hardware encoder on Windows, it must match the GPU where the display is connected.
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<!-- NVIDIA NVENC Encoder Tab -->
|
|
<div id="nvidia-nvenc-encoder" v-if="currentTab === 'nv'" class="config-page">
|
|
<!-- Performance preset -->
|
|
<div class="mb-3">
|
|
<label for="nvenc_preset" class="form-label">Performance preset</label>
|
|
<select id="nvenc_preset" class="form-select" v-model="config.nvenc_preset">
|
|
<option value="1">P1 (fastest, default)</option>
|
|
<option value="2">P2</option>
|
|
<option value="3">P3</option>
|
|
<option value="4">P4</option>
|
|
<option value="5">P5</option>
|
|
<option value="6">P6</option>
|
|
<option value="7">P7 (slowest)</option>
|
|
</select>
|
|
<div class="form-text">Higher numbers improve compression (quality at given bitrate) at the cost of
|
|
<strong>increased encoding latency</strong>.<br>
|
|
Recommended to change only when limited by network or decoder, otherwise similar effect can be
|
|
accomplished by increasing bitrate.
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Two-pass mode -->
|
|
<div class="mb-3">
|
|
<label for="nvenc_twopass" class="form-label">Two-pass mode</label>
|
|
<select id="nvenc_twopass" class="form-select" v-model="config.nvenc_twopass">
|
|
<option value="disabled">Disabled (fastest, not recommended)</option>
|
|
<option value="quarter_res">Quarter resolution (faster, default)</option>
|
|
<option value="full_res">Full resolution (slower)</option>
|
|
</select>
|
|
<div class="form-text">Adds preliminary encoding pass.<br>
|
|
This allows to detect more motion vectors, better distribute bitrate across the frame and more strictly
|
|
adhere to bitrate limits.<br>
|
|
Disabling it is not recommended since this can lead to occasional bitrate overshoot and subsequent packet
|
|
loss.
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Miscellaneous options -->
|
|
<div class="accordion">
|
|
<div class="accordion-item">
|
|
<h2 class="accordion-header">
|
|
<button class="accordion-button" type="button" data-bs-toggle="collapse"
|
|
data-bs-target="#panelsStayOpen-collapseOne">
|
|
Miscellaneous options
|
|
</button>
|
|
</h2>
|
|
<div id="panelsStayOpen-collapseOne" class="accordion-collapse collapse show"
|
|
aria-labelledby="panelsStayOpen-headingOne">
|
|
<div class="accordion-body">
|
|
<!-- NVENC Realtime HAGS priority -->
|
|
<div class="mb-3" v-if="platform === 'windows'">
|
|
<label for="nvenc_realtime_hags" class="form-label">Use realtime priority in hardware accelerated
|
|
gpu
|
|
scheduling</label>
|
|
<select id="nvenc_realtime_hags" class="form-select" v-model="config.nvenc_realtime_hags">
|
|
<option value="disabled">Disabled</option>
|
|
<option value="enabled">Enabled (default)</option>
|
|
</select>
|
|
<div class="form-text">Currently NVIDIA drivers may freeze in encoder when
|
|
<a href="https://devblogs.microsoft.com/directx/hardware-accelerated-gpu-scheduling/">HAGS</a>
|
|
is enabled, realtime priority is used and VRAM utilization is close to maximum.<br>
|
|
Disabling this option lowers the priority to high, sidestepping the freeze at the cost of reduced
|
|
capture performance when the GPU is heavily loaded.
|
|
</div>
|
|
</div>
|
|
|
|
<!-- NVENC H264 CAVLC -->
|
|
<div>
|
|
<label for="nvenc_h264_cavlc" class="form-label">Prefer CAVLC over CABAC in H.264</label>
|
|
<select id="nvenc_h264_cavlc" class="form-select" v-model="config.nvenc_h264_cavlc">
|
|
<option value="disabled">Disabled (default)</option>
|
|
<option value="enabled">Enabled</option>
|
|
</select>
|
|
<div class="form-text">Simpler form of entropy coding.<br>
|
|
CAVLC needs around 10% more bitrate for same quality.<br>
|
|
Only relevant for really old decoding devices.
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Intel QuickSync Encoder Tab -->
|
|
<div id="intel-quicksync-encoder" v-if="currentTab === 'qsv'" class="config-page">
|
|
<!-- QuickSync Preset -->
|
|
<div class="mb-3">
|
|
<label for="qsv_preset" class="form-label">QuickSync Preset</label>
|
|
<select id="qsv_preset" class="form-select" v-model="config.qsv_preset">
|
|
<option value="veryfast">fastest (lowest quality)</option>
|
|
<option value="faster">faster (lower quality)</option>
|
|
<option value="fast">fast (low quality)</option>
|
|
<option value="medium">medium (default)</option>
|
|
<option value="slow">slow (good quality)</option>
|
|
<option value="slower">slower (better quality)</option>
|
|
<option value="slowest">slowest (best quality)</option>
|
|
</select>
|
|
</div>
|
|
|
|
<!-- QuickSync Coder (H264) -->
|
|
<div class="mb-3">
|
|
<label for="qsv_coder" class="form-label">QuickSync Coder (H264)</label>
|
|
<select id="qsv_coder" class="form-select" v-model="config.qsv_coder">
|
|
<option value="auto">auto -- let ffmpeg decide (default)</option>
|
|
<option value="cabac">cabac -- context adaptive binary arithmetic coding - higher quality</option>
|
|
<option value="cavlc">cavlc -- context adaptive variable-length coding - faster decode</option>
|
|
</select>
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<!-- AMD AMF Encoder Tab -->
|
|
<div id="amd-amf-encoder" v-if="currentTab === 'amd'" class="config-page">
|
|
<!-- AMF Quality -->
|
|
<div class="mb-3">
|
|
<label for="amd_quality" class="form-label">AMF Quality</label>
|
|
<select id="amd_quality" class="form-select" v-model="config.amd_quality">
|
|
<option value="speed">speed -- prefer speed</option>
|
|
<option value="balanced">balanced -- balanced (default)</option>
|
|
<option value="quality">quality -- prefer quality</option>
|
|
</select>
|
|
</div>
|
|
|
|
<!-- AMF Rate Control -->
|
|
<div class="mb-3">
|
|
<label for="amd_rc" class="form-label">AMF Rate Control</label>
|
|
<select id="amd_rc" class="form-select" v-model="config.amd_rc">
|
|
<option value="cqp">cqp -- constant qp mode</option>
|
|
<option value="vbr_latency">vbr_latency -- latency constrained variable bitrate (default)</option>
|
|
<option value="vbr_peak">vbr_peak -- peak constrained variable bitrate</option>
|
|
<option value="cbr">cbr -- constant bitrate</option>
|
|
</select>
|
|
</div>
|
|
|
|
<!-- AMF Usage -->
|
|
<div class="mb-3">
|
|
<label for="amd_usage" class="form-label">AMF Usage</label>
|
|
<select id="amd_usage" class="form-select" v-model="config.amd_usage">
|
|
<option value="transcoding">transcoding -- transcoding (slowest)</option>
|
|
<option value="webcam">webcam -- webcam (slow)</option>
|
|
<option value="lowlatency">lowlatency - low latency (fast)</option>
|
|
<option value="ultralowlatency">ultralowlatency - ultra low latency (fastest)</option>
|
|
</select>
|
|
</div>
|
|
|
|
<!-- AMD Preanalysis -->
|
|
<div class="mb-3">
|
|
<label for="amd_preanalysis" class="form-label">AMF Preanalysis</label>
|
|
<select id="amd_preanalysis" class="form-select" v-model="config.amd_preanalysis">
|
|
<option value="enabled">enabled</option>
|
|
<option value="disabled">disabled (default)</option>
|
|
</select>
|
|
</div>
|
|
|
|
<!-- AMD VBAQ -->
|
|
<div class="mb-3">
|
|
<label for="amd_vbaq" class="form-label">AMF Variance Based Adaptive Quantization (VBAQ)</label>
|
|
<select id="amd_vbaq" class="form-select" v-model="config.amd_vbaq">
|
|
<option value="enabled">enabled (default)</option>
|
|
<option value="disabled">disabled</option>
|
|
</select>
|
|
</div>
|
|
|
|
<!-- AMF Coder (H264) -->
|
|
<div class="mb-3">
|
|
<label for="amd_coder" class="form-label">AMF Coder (H264)</label>
|
|
<select id="amd_coder" class="form-select" v-model="config.amd_coder">
|
|
<option value="auto">auto -- let ffmpeg decide (default)</option>
|
|
<option value="cabac">cabac -- context adaptive variable-length coding - higher quality</option>
|
|
<option value="cavlc">cavlc -- context adaptive binary arithmetic coding - faster decode</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- VideoToolbox Encoder Tab -->
|
|
<div id="videotoolbox-encoder" v-if="currentTab === 'vt'" class="config-page">
|
|
<!-- Presets -->
|
|
<div class="mb-3">
|
|
<label for="vt_coder" class="form-label">VideoToolbox Coder</label>
|
|
<select id="vt_coder" class="form-select" v-model="config.vt_coder">
|
|
<option value="auto">auto</option>
|
|
<option value="cabac">cabac</option>
|
|
<option value="cavlc">cavlc</option>
|
|
</select>
|
|
</div>
|
|
<div class="mb-3">
|
|
<label for="vt_software" class="form-label">VideoToolbox Software Encoding</label>
|
|
<select id="vt_software" class="form-select" v-model="config.vt_software">
|
|
<option value="auto">auto</option>
|
|
<option value="disabled">disabled</option>
|
|
<option value="allowed">allowed</option>
|
|
<option value="forced">forced</option>
|
|
</select>
|
|
</div>
|
|
<div class="mb-3">
|
|
<label for="vt_realtime" class="form-label">VideoToolbox Realtime Encoding</label>
|
|
<select id="vt_realtime" class="form-select" v-model="config.vt_realtime">
|
|
<option value="enabled">enabled</option>
|
|
<option value="disabled">disabled</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Software Encoder Tab -->
|
|
<div id="software-encoder" v-if="currentTab === 'sw'" class="config-page">
|
|
<div class="mb-3">
|
|
<label for="sw_preset" class="form-label">SW Presets</label>
|
|
<select id="sw_preset" class="form-select" v-model="config.sw_preset">
|
|
<option value="ultrafast">ultrafast</option>
|
|
<option value="superfast">superfast (default)</option>
|
|
<option value="veryfast">veryfast</option>
|
|
<option value="faster">faster</option>
|
|
<option value="fast">fast</option>
|
|
<option value="medium">medium</option>
|
|
<option value="slow">slow</option>
|
|
<option value="slower">slower</option>
|
|
<option value="veryslow">veryslow</option>
|
|
</select>
|
|
<div class="form-text">
|
|
Optimize the trade-off between encoding speed (encoded frames per second) and compression efficiency
|
|
(quality per bit in the bitstream). Defaults to superfast.
|
|
</div>
|
|
</div>
|
|
<div class="mb-3">
|
|
<label for="sw_tune" class="form-label">SW Tune</label>
|
|
<select id="sw_tune" class="form-select" v-model="config.sw_tune">
|
|
<option value="film">film -- use for high quality movie content; lowers deblocking</option>
|
|
<option value="animation">animation -- good for cartoons; uses higher deblocking and more reference frames
|
|
</option>
|
|
<option value="grain">grain -- preserves the grain structure in old, grainy film material</option>
|
|
<option value="stillimage">stillimage -- good for slideshow-like content</option>
|
|
<option value="fastdecode">fastdecode -- allows faster decoding by disabling certain filters</option>
|
|
<option value="zerolatency">zerolatency -- good for fast encoding and low-latency streaming (default)
|
|
</option>
|
|
</select>
|
|
<div class="form-text">
|
|
Tuning options, which are applied after the preset. Defaults to zerolatency.
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<!-- Save and Apply buttons -->
|
|
<div class="alert alert-success my-4" v-if="saved && !restarted">
|
|
<b>Success!</b> Click 'Apply' to restart Sunshine and apply changes. This will terminate any running sessions.
|
|
</div>
|
|
<div class="alert alert-success my-4" v-if="restarted">
|
|
<b>Success!</b> Sunshine is restarting to apply changes.
|
|
</div>
|
|
<div class="mb-3 buttons">
|
|
<button class="btn btn-primary" @click="save">Save</button>
|
|
<button class="btn btn-success" @click="apply" v-if="saved && !restarted">Apply</button>
|
|
</div>
|
|
|
|
</div>
|
|
</body>
|
|
<script type="module">
|
|
import { createApp } from 'vue'
|
|
import Navbar from './Navbar.vue'
|
|
|
|
const app = createApp({
|
|
components: {
|
|
Navbar
|
|
},
|
|
data() {
|
|
return {
|
|
platform: "",
|
|
saved: false,
|
|
restarted: false,
|
|
config: null,
|
|
fps: [],
|
|
resolutions: [],
|
|
currentTab: "general",
|
|
resIn: "",
|
|
fpsIn: "",
|
|
global_prep_cmd: [],
|
|
tabs: [
|
|
{
|
|
id: "general",
|
|
name: "General",
|
|
options: {
|
|
"sunshine_name": "",
|
|
"min_log_level": 2,
|
|
"global_prep_cmd": "[]",
|
|
},
|
|
},
|
|
{
|
|
id: "files",
|
|
name: "Files",
|
|
options: {
|
|
"file_apps": "",
|
|
"credentials_file": "",
|
|
"log_path": "",
|
|
"pkey": "",
|
|
"cert": "",
|
|
"file_state": "",
|
|
},
|
|
},
|
|
{
|
|
id: "input",
|
|
name: "Input",
|
|
options: {
|
|
"controller": "enabled",
|
|
"gamepad": "auto",
|
|
"ds4_back_as_touchpad_click": "enabled",
|
|
"motion_as_ds4": "enabled",
|
|
"touchpad_as_ds4": "enabled",
|
|
"back_button_timeout": -1,
|
|
"keyboard": "enabled",
|
|
"key_repeat_delay": 500,
|
|
"key_repeat_frequency": 24.9,
|
|
"always_send_scancodes": "enabled",
|
|
"key_rightalt_to_key_win": "disabled",
|
|
"mouse": "enabled",
|
|
"high_resolution_scrolling": "enabled",
|
|
"native_pen_touch": "enabled",
|
|
"keybindings": "[0x10,0xA0,0x11,0xA2,0x12,0xA4]", // todo: add this to UI
|
|
},
|
|
},
|
|
{
|
|
id: "av",
|
|
name: "Audio/Video",
|
|
options: {
|
|
"audio_sink": "",
|
|
"virtual_sink": "",
|
|
"install_steam_audio_drivers": "enabled",
|
|
"adapter_name": "",
|
|
"output_name": "",
|
|
"resolutions": "[352x240,480x360,858x480,1280x720,1920x1080,2560x1080,3440x1440,1920x1200,3840x2160,3840x1600]",
|
|
"fps": "[10,30,60,90,120]",
|
|
},
|
|
},
|
|
{
|
|
id: "network",
|
|
name: "Network",
|
|
options: {
|
|
"address_family": "ipv4",
|
|
"port": 47989,
|
|
"origin_web_ui_allowed": "lan",
|
|
"upnp": "disabled",
|
|
"external_ip": "",
|
|
"ping_timeout": 10000,
|
|
},
|
|
},
|
|
{
|
|
id: "advanced",
|
|
name: "Advanced",
|
|
options: {
|
|
"channels": 1,
|
|
"fec_percentage": 20,
|
|
"qp": 28,
|
|
"min_threads": 1,
|
|
"hevc_mode": 0,
|
|
"av1_mode": 0,
|
|
"capture": "",
|
|
"encoder": "",
|
|
},
|
|
},
|
|
{
|
|
id: "nv",
|
|
name: "NVIDIA NVENC Encoder",
|
|
options: {
|
|
"nvenc_preset": 1,
|
|
"nvenc_twopass": "quarter_res",
|
|
"nvenc_realtime_hags": "enabled",
|
|
"nvenc_h264_cavlc": "disabled",
|
|
},
|
|
},
|
|
{
|
|
id: "qsv",
|
|
name: "Intel QuickSync Encoder",
|
|
options: {
|
|
"qsv_preset": "medium",
|
|
"qsv_coder": "auto",
|
|
},
|
|
},
|
|
{
|
|
id: "amd",
|
|
name: "AMD AMF Encoder",
|
|
options: {
|
|
"amd_quality": "balanced",
|
|
"amd_rc": "vbr_latency",
|
|
"amd_usage": "ultralowlatency",
|
|
"amd_preanalysis": "disabled",
|
|
"amd_vbaq": "enabled",
|
|
"amd_coder": "auto",
|
|
},
|
|
},
|
|
{
|
|
id: "vt",
|
|
name: "VideoToolbox Encoder",
|
|
options: {
|
|
"vt_coder": "auto",
|
|
"vt_software": "auto",
|
|
"vt_realtime": "enabled",
|
|
},
|
|
},
|
|
{
|
|
id: "sw",
|
|
name: "Software Encoder",
|
|
options: {
|
|
"sw_preset": "superfast",
|
|
"sw_tune": "zerolatency",
|
|
},
|
|
},
|
|
],
|
|
};
|
|
},
|
|
created() {
|
|
fetch("/api/config")
|
|
.then((r) => r.json())
|
|
.then((r) => {
|
|
this.config = r;
|
|
this.platform = this.config.platform;
|
|
|
|
var app = document.getElementById("app");
|
|
if (this.platform === "windows") {
|
|
this.tabs = this.tabs.filter((el) => {
|
|
return el.id !== "vt";
|
|
});
|
|
}
|
|
if (this.platform === "linux") {
|
|
this.tabs = this.tabs.filter((el) => {
|
|
return el.id !== "amd" && el.id !== "qsv" && el.id !== "vt";
|
|
});
|
|
}
|
|
if (this.platform === "macos") {
|
|
this.tabs = this.tabs.filter((el) => {
|
|
return el.id !== "amd" && el.id !== "nv" && el.id !== "qsv";
|
|
});
|
|
}
|
|
|
|
// remove values we don't want in the config file
|
|
delete this.config.platform;
|
|
delete this.config.status;
|
|
delete this.config.version;
|
|
|
|
// Populate default values from tabs options
|
|
this.tabs.forEach(tab => {
|
|
Object.keys(tab.options).forEach(optionKey => {
|
|
if (this.config[optionKey] === undefined) {
|
|
this.config[optionKey] = tab.options[optionKey];
|
|
}
|
|
});
|
|
});
|
|
|
|
this.fps = JSON.parse(this.config.fps);
|
|
//Resolutions should be fixed because are not valid JSON
|
|
let res = this.config.resolutions.substring(
|
|
1,
|
|
this.config.resolutions.length - 1
|
|
);
|
|
let resolutions = [];
|
|
res.split(",").forEach((r) => resolutions.push(r.trim()));
|
|
this.resolutions = resolutions;
|
|
|
|
this.config.global_prep_cmd = this.config.global_prep_cmd || [];
|
|
this.global_prep_cmd = JSON.parse(this.config.global_prep_cmd);
|
|
});
|
|
},
|
|
methods: {
|
|
forceUpdate() {
|
|
this.$forceUpdate()
|
|
},
|
|
serialize() {
|
|
let nl = this.config === "windows" ? "\r\n" : "\n";
|
|
this.config.resolutions =
|
|
"[" +
|
|
nl +
|
|
" " +
|
|
this.resolutions.join("," + nl + " ") +
|
|
nl +
|
|
"]";
|
|
// remove quotes from values in fps
|
|
this.config.fps = JSON.stringify(this.fps).replace(/"/g, "");
|
|
this.config.global_prep_cmd = JSON.stringify(this.global_prep_cmd);
|
|
},
|
|
save() {
|
|
this.saved = false;
|
|
this.restarted = false;
|
|
this.serialize();
|
|
|
|
// create a temp copy of this.config to use for the post request
|
|
let config = JSON.parse(JSON.stringify(this.config))
|
|
|
|
// delete default values from this.config
|
|
this.tabs.forEach(tab => {
|
|
Object.keys(tab.options).forEach(optionKey => {
|
|
let delete_value = false
|
|
if (optionKey === "global_prep_cmd" || optionKey === "resolutions" || optionKey === "fps") {
|
|
let regex = /([\d]+x[\d]+)/g // this regex is only needed for resolutions
|
|
// Use a regular expression to find each value and replace it with a quoted version
|
|
|
|
let config_value = JSON.parse(config[optionKey].replace(regex, '"$1"')).toString()
|
|
let default_value = JSON.parse(tab.options[optionKey].replace(regex, '"$1"')).toString()
|
|
|
|
if (config_value === default_value) {
|
|
delete_value = true
|
|
}
|
|
}
|
|
|
|
// todo: add proper type checking
|
|
if (String(config[optionKey]) === String(tab.options[optionKey])) {
|
|
delete_value = true
|
|
}
|
|
|
|
if (delete_value) {
|
|
delete config[optionKey]
|
|
}
|
|
});
|
|
});
|
|
|
|
return fetch("/api/config", {
|
|
method: "POST",
|
|
body: JSON.stringify(config),
|
|
}).then((r) => {
|
|
if (r.status === 200) {
|
|
this.saved = true
|
|
return this.saved
|
|
}
|
|
else {
|
|
return false
|
|
}
|
|
});
|
|
},
|
|
apply() {
|
|
this.saved = this.restarted = false;
|
|
let saved = this.save();
|
|
|
|
saved.then((result) => {
|
|
if (result === true) {
|
|
this.restarted = true;
|
|
setTimeout(() => {
|
|
this.saved = this.restarted = false;
|
|
}, 5000);
|
|
fetch("/api/restart", {
|
|
method: "POST"
|
|
});
|
|
}
|
|
});
|
|
},
|
|
add_global_prep_cmd() {
|
|
let template = {
|
|
do: "",
|
|
undo: "",
|
|
};
|
|
|
|
if (this.platform === 'windows') {
|
|
template = { ...template, elevated: false };
|
|
}
|
|
this.global_prep_cmd.push(template);
|
|
},
|
|
},
|
|
computed: {
|
|
effectivePort() {
|
|
// Convert config.port to a number.
|
|
const port = +this.config?.port
|
|
|
|
// Check if port is NaN or a falsy value (like 0, empty string, etc.).
|
|
// If so, default to config port. Otherwise, use the value of port.
|
|
return port ? port : 47989
|
|
},
|
|
},
|
|
mounted() {
|
|
// Handle hashchange events
|
|
const handleHash = () => {
|
|
let hash = window.location.hash;
|
|
if (hash) {
|
|
// remove the # from the hash
|
|
let stripped_hash = hash.substring(1);
|
|
|
|
this.tabs.forEach(tab => {
|
|
Object.keys(tab.options).forEach(key => {
|
|
if (tab.id === stripped_hash || key === stripped_hash) {
|
|
this.currentTab = tab.id;
|
|
}
|
|
if (key === stripped_hash) {
|
|
// sleep for 2 seconds to allow the page to load
|
|
setTimeout(() => {
|
|
let element = document.getElementById(stripped_hash);
|
|
if (element) {
|
|
window.location.hash = hash;
|
|
}
|
|
}, 2000);
|
|
}
|
|
|
|
if (this.currentTab === tab.id) {
|
|
// stop looping
|
|
return true;
|
|
}
|
|
});
|
|
});
|
|
}
|
|
};
|
|
|
|
// Call handleHash for the initial load
|
|
handleHash();
|
|
|
|
// Add hashchange event listener
|
|
window.addEventListener("hashchange", handleHash);
|
|
},
|
|
});
|
|
|
|
app.mount("#app");
|
|
</script>
|