604 lines
36 KiB
HTML
604 lines
36 KiB
HTML
<!doctype html>
|
|
<html>
|
|
<head>
|
|
<meta charset='utf-8'>
|
|
<meta name='viewport' content='width=device-width,initial-scale=1'>
|
|
<title>NetSpeaker Demo (on [IP here])</title>
|
|
<link href='https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css' rel='stylesheet'
|
|
integrity='sha384-T3c6CoIi6uLrA9TneNEoa7RxnatzjcDSCmG1MXxSR1GAsXEV/Dwwykc2MPK8M2HN' crossorigin='anonymous'>
|
|
<style>#playbackMuteButton:hover { background-color: #00000000; }</style>
|
|
</head>
|
|
<body data-bs-theme='dark'>
|
|
<div class='text-center container-sm' style='padding: 4em 0em'>
|
|
|
|
<p>Welcome to</p>
|
|
<h1 id='title_frname'>[FRIENDLY NAME HERE]</h1>
|
|
<p id="title_onIP"></p>
|
|
<p style='font-size: .875em; color: var(--bs-code-color); padding-top: .5rem'>[VERSION STRING HERE]</p>
|
|
|
|
<hr style='margin: 3em 0em;'>
|
|
|
|
<!-----------------------------
|
|
---- INFORMATIONS ACCORDION ---
|
|
------------------------------>
|
|
<div class='accordion text-start' id='accordion-info'>
|
|
<div class='accordion-item'>
|
|
<h2 class='accordion-header'>
|
|
<button class='accordion-button collapsed' type='button' data-bs-toggle='collapse' data-bs-target='#accordion-info-playback'
|
|
aria-expanded='true' aria-controls='accordion-info-playback'>
|
|
Playback information
|
|
</button>
|
|
</h2>
|
|
<div id='accordion-info-playback' class='accordion-collapse collapse' data-bs-parent='#accordion-info'>
|
|
<div class='accordion-body'>
|
|
<table class='table table-striped'>
|
|
<tbody>
|
|
<tr>
|
|
<td>Playing</td>
|
|
<td class='text-end'>
|
|
[html += audioPlaying ? "yes" : "no"]
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<td>Volume</td>
|
|
<td class='text-end'>[" + String(currentVolume) + "/" + String(maxVolume) + "]</td>
|
|
</tr>
|
|
<tr>
|
|
<td>Muted</td>
|
|
<td class='text-end'>
|
|
[html += muted ? "yes" : "no"]
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<td>Equalizer Low</td>
|
|
<td class='text-end'>[" + String(eqLow) + "dB"]</td>
|
|
</tr>
|
|
<tr>
|
|
<td>Equalizer Mid</td>
|
|
<td class='text-end'>[" + String(eqMid) + "dB"]</td>
|
|
</tr>
|
|
<tr>
|
|
<td>Equalizer High</td>
|
|
<td class='text-end'>[" + String(eqHigh) + "dB"]</td>
|
|
</tr>
|
|
<tr>
|
|
<td>Balance (range -16 | +16)</td>
|
|
<td class='text-end'>" + String(balanceLevel) + "</td>
|
|
</tr>
|
|
<tr>
|
|
<td>Path to playlist</td>
|
|
<td class='text-end'>" + currentPlaylist + "</td>
|
|
</tr>
|
|
<tr>
|
|
<td>Index in playlist (starting from 0)</td>
|
|
<td class='text-end'>" + String(currentPlaylistPosition) + "</td>
|
|
</tr>
|
|
<tr>
|
|
<td>Resource path</td>
|
|
<td class='text-end'>" + pbInfo.resourcePath + "</td>
|
|
</tr>
|
|
<tr>
|
|
<td>Resource type</td>
|
|
<td class='text-end'>" + pbInfo.type + "</td>
|
|
</tr>
|
|
<tr>
|
|
<td>Resource title</td>
|
|
<td class='text-end'>" + pbInfo.title + "</td>
|
|
</tr>
|
|
<tr>
|
|
<td>Resource album</td>
|
|
<td class='text-end'>" + pbInfo.album + "</td>
|
|
</tr>
|
|
<tr>
|
|
<td>Resource artist</td>
|
|
<td class='text-end'>" + pbInfo.artist + "</td>
|
|
</tr>
|
|
<tr>
|
|
<td>Resource track number</td>
|
|
<td class='text-end'>" + pbInfo.track + "</td>
|
|
</tr>
|
|
<tr>
|
|
<td>Resource year</td>
|
|
<td class='text-end'>" + pbInfo.year + "</td>
|
|
</tr>
|
|
<tr>
|
|
<td>Resource genre</td>
|
|
<td class='text-end'>" + pbInfo.genre + "</td>
|
|
</tr>
|
|
<tr>
|
|
<td>Resource language (for TTS)</td>
|
|
<td class='text-end'>" + pbInfo.tts_language + "</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class='accordion-item'>
|
|
<h2 class='accordion-header'>
|
|
<button class='accordion-button collapsed' type='button' data-bs-toggle='collapse' data-bs-target='#accordion-info-general'
|
|
aria-expanded='true' aria-controls='accordion-info-general'>
|
|
General information
|
|
</button>
|
|
</h2>
|
|
<div id='accordion-info-general' class='accordion-collapse collapse' data-bs-parent='#accordion-info'>
|
|
<div class='accordion-body'>
|
|
<table class='table table-striped'>
|
|
<tbody>
|
|
<tr>
|
|
<td>Version</td>
|
|
<td class='text-end'>" + version + "</td>
|
|
</tr>
|
|
<tr>
|
|
<td>Friendly name</td>
|
|
<td class='text-end'>" + configuration.getString(PREFERENCES_KEY_FRIENDLY_NAME, "
|
|
<not_given>") + "
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<td>Save & Restore state</td>
|
|
<td class='text-end'>
|
|
html += configuration.getBool(PREFERENCES_KEY_RESTORE_OLD_STATE, false) ? "yes" : "no
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<td>Save & Restore playing state (default no)</td>
|
|
<td class='text-end'>
|
|
html += configuration.getBool(PREFERENCES_KEY_RESTORE_PLAYING, false) ? "yes" : "no
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<td>WiFi SSID</td>
|
|
<td class='text-end'>" + configuration.getString(PREFERENCES_KEY_WIFI_SSID, "") + "</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<hr style='margin: 3em 0em;'>
|
|
|
|
|
|
<!---------------------------------->
|
|
<!---- API FUNCTIONS ACCODRION ---->
|
|
<!---------------------------------->
|
|
<div class='accordion text-start' id='accordion'>
|
|
<!--PLAYBACK Collapsible -->
|
|
<div class='accordion-item'>
|
|
<h2 class='accordion-header'>
|
|
<button class='accordion-button collapsed' type='button' data-bs-toggle='collapse'
|
|
data-bs-target='#accordion-collapse-playback' aria-expanded='true' aria-controls='accordion-collapse-playback'>
|
|
Playback
|
|
<span class='badge text-bg-success'>/api/v1/playback/</span>
|
|
<span class='badge text-bg-info'>/api/v1/volume/</span>
|
|
</button>
|
|
</h2>
|
|
<div id='accordion-collapse-playback' class='accordion-collapse collapse' data-bs-parent='#accordion'>
|
|
<div class='accordion-body'>
|
|
<table class='table table-striped'>
|
|
<tbody>
|
|
<tr>
|
|
<td>Playing</td>
|
|
<td class='text-end' id='playbackTable_audioPlayingValue'>
|
|
html += audioPlaying ? "yes" : "no
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<td>Volume</td>
|
|
<td class='text-end' id='playbackTable_volumeValue'>" + String(currentVolume) + "/" + String(maxVolume) + "%
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<td>Muted</td>
|
|
<td class='text-end' id='playbackTable_mutedValue'>
|
|
html += muted ? "yes" : "no
|
|
</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
<hr>
|
|
<div class='btn-group' role='group' aria-label='Basic playback option (play, pause, next, previous)'>
|
|
<button type='button' class='btn btn-secondary' onclick="http
|
|
= new XMLHttpRequest();http.open('GET', '/api/v1/playback/previous');http.send();">
|
|
<svg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='currentColor' class='bi bi-rewind-circle'
|
|
viewBox='0 0 16 16'>
|
|
<path d='M7.729 5.055a.5.5 0 0 0-.52.038l-3.5 2.5a.5.5 0 0 0 0 .814l3.5 2.5A.5.5 0 0 0 8 10.5V8.614l3.21 2.293A.5.5 0 0 0 12 10.5v-5a.5.5 0 0 0-.79-.407L8 7.386V5.5a.5.5 0 0 0-.271-.445'/>
|
|
<path d='M16 8A8 8 0 1 1 0 8a8 8 0 0 1 16 0M1 8a7 7 0 1 0 14 0A7 7 0 0 0 1 8'/>
|
|
</svg>
|
|
</button>
|
|
<button type='button' class='btn btn-success' onclick="http
|
|
= new XMLHttpRequest();http.open('GET',
|
|
'/api/v1/playback/play');http.send();document.getElementById('playbackTable_audioPlayingValue').innerHTML =
|
|
'yes';">
|
|
<svg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='currentColor' class='bi bi-play-circle'
|
|
viewBox='0 0 16 16'>
|
|
<path d='M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14m0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16'></path>
|
|
<path d='M6.271 5.055a.5.5 0 0 1 .52.038l3.5 2.5a.5.5 0 0 1 0 .814l-3.5 2.5A.5.5 0 0 1 6 10.5v-5a.5.5 0 0 1 .271-.445'></path>
|
|
</svg>
|
|
</button>
|
|
<button type='button' class='btn btn-success'
|
|
onclick="http = new XMLHttpRequest();http.open('GET', '/api/v1/playback/pause');http.send();document.getElementById('playbackTable_audioPlayingValue').innerHTML = 'no';\">
|
|
<svg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='currentColor' class='bi bi-pause-circle'
|
|
viewBox='0 0 16 16'>
|
|
<path d='M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14m0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16'/>
|
|
<path d='M5 6.25a1.25 1.25 0 1 1 2.5 0v3.5a1.25 1.25 0 1 1-2.5 0zm3.5 0a1.25 1.25 0 1 1 2.5 0v3.5a1.25 1.25 0 1 1-2.5 0z'/>
|
|
</svg>
|
|
</button>
|
|
<button type='button' class='btn btn-secondary'
|
|
onclick="http = new XMLHttpRequest();http.open('GET', '/api/v1/playback/next');http.send();">
|
|
<svg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='currentColor'
|
|
class='bi bi-skip-forward-circle'
|
|
viewBox='0 0 16 16'>
|
|
<path d='M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14m0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16'/>
|
|
<path d='M4.271 5.055a.5.5 0 0 1 .52.038L7.5 7.028V5.5a.5.5 0 0 1 .79-.407L11 7.028V5.5a.5.5 0 0 1 1 0v5a.5.5 0 0 1-1 0V8.972l-2.71 1.935a.5.5 0 0 1-.79-.407V8.972l-2.71 1.935A.5.5 0 0 1 4 10.5v-5a.5.5 0 0 1 .271-.445'/>
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
<br>
|
|
<hr>
|
|
<br><label for='volumeRange' class='form-label'>Volume</label>
|
|
<input type='range' class='form-range' value='" + String(currentVolume) + "' min='0' max='" + String(maxVolume) + "'
|
|
id='volumeRange'
|
|
onchange='value = document.getElementById(\"volumeRange\").value; http = new XMLHttpRequest();http.open(\"GET\", \"/api/v1/volume/\" + value);http.send();document.getElementById(\"playbackTable_volumeValue\").innerHTML = value + \"/" + String(maxVolume) + "%\"'>
|
|
<button type='button' id='playbackMuteButton' onclick=
|
|
"http = new XMLHttpRequest();
|
|
http.open('GET', '/api/v1/volume/toggle_mute');
|
|
http.send(); button = document.getElementById('playbackMuteButton');
|
|
button.classList.contains('btn-outline-danger') ? button.className = 'btn btn-danger' : button.className = 'btn
|
|
btn-outline-danger';
|
|
mutedValueHTML = document.getElementById('playbackTable_mutedValue');
|
|
button.classList.contains('btn-outline-danger') ? mutedValueHTML.innerHTML = 'no' : mutedValueHTML.innerHTML = 'yes'"
|
|
class='btn btn-danger'> or btn-outline-danger'>
|
|
<svg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='currentColor' class='bi bi-volume-mute'
|
|
viewBox='0 0 16 16'>
|
|
<path d='M6.717 3.55A.5.5 0 0 1 7 4v8a.5.5 0 0 1-.812.39L3.825 10.5H1.5A.5.5 0 0 1 1 10V6a.5.5 0 0 1 .5-.5h2.325l2.363-1.89a.5.5 0 0 1 .529-.06zM6 5.04 4.312 6.39A.5.5 0 0 1 4 6.5H2v3h2a.5.5 0 0 1 .312.11L6 10.96zm7.854.606a.5.5 0 0 1 0 .708L12.207 8l1.647 1.646a.5.5 0 0 1-.708.708L11.5 8.707l-1.646 1.647a.5.5 0 0 1-.708-.708L10.793 8 9.146 6.354a.5.5 0 1 1 .708-.708L11.5 7.293l1.646-1.647a.5.5 0 0 1 .708 0z'></path>
|
|
</svg>
|
|
Mute/Unmute
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!--PLAYLIST Collapsible -->
|
|
<div class='accordion-item'>
|
|
<h2 class='accordion-header'>
|
|
<button class='accordion-button collapsed' type='button' data-bs-toggle='collapse'
|
|
data-bs-target='#accordion-collapse-playlist' aria-expanded='true' aria-controls='accordion-collapse-playlist'>
|
|
Playlist
|
|
<span class='badge text-bg-success'>/api/v1/playlist/</span>
|
|
</button>
|
|
</h2>
|
|
<div id='accordion-collapse-playlist' class='accordion-collapse collapse' data-bs-parent='#accordion'>
|
|
<div class='accordion-body'>
|
|
<!--play playlist (select playlist) api demo -->
|
|
<p>Demo for playing a specific playlist on the SD card over the API.</p>
|
|
<div class='input-group mb-3'>
|
|
<input type='text' class='form-control' placeholder='Playlist path (must start with a /)'
|
|
aria-label='Playlist path (must start with a /)' aria-describedby='playlist_playBtn'
|
|
id='playlist_playPathInput'>
|
|
<button class='btn btn-success' type='button' id='playlist_playBtn' onclick=
|
|
"playlistPath
|
|
= document.getElementById('playlist_playPathInput').value;
|
|
http = new XMLHttpRequest();http.open('GET', '/api/v1/playlist/play?playlist_path='+playlistPath);http.send();">
|
|
Change
|
|
</button>
|
|
</div>
|
|
<hr>
|
|
<!--create playlist api demo -->
|
|
<p>Demo for creating a new playlist containing all the contents of the given folder (on the SD card).</p>
|
|
<p>The new playlist will be located in the given folder and is named <code>.directory.m3u</code>. This can't be changed.
|
|
</p>
|
|
<p>Creating fully personalized playlist or even uploading one will be subject of further development (see also the
|
|
Roadmap of the NetSpeaker project!).</p>
|
|
<div class='input-group mb-3'>
|
|
<input type='text' class='form-control' placeholder='Folder path (must start with and end without a /)'
|
|
aria-label='Folder path (must start with a /)' aria-describedby='playlist_createBtn'
|
|
id='playlist_createPathInput'>
|
|
<button class='btn btn-success' type='button' id='playlist_createBtn' onclick=
|
|
"folderPath
|
|
= document.getElementById('playlist_createPathInput').value;
|
|
http = new XMLHttpRequest();http.open('GET', '/api/v1/playlist/create?folder_path='+folderPath);http.send();">
|
|
Change
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!--BALANCE Collapsible -->
|
|
<div class='accordion-item'>
|
|
<h2 class='accordion-header'>
|
|
<button class='accordion-button collapsed' type='button' data-bs-toggle='collapse'
|
|
data-bs-target='#accordion-collapse-balance' aria-expanded='true' aria-controls='accordion-collapse-balance'>
|
|
Balance
|
|
<span class='badge text-bg-info'>/api/v1/balance/</span>
|
|
</button>
|
|
</h2>
|
|
<div id='accordion-collapse-balance' class='accordion-collapse collapse' data-bs-parent='#accordion'>
|
|
<div class='accordion-body'>
|
|
<p>The Audio library used maps -16 to most right and 16 to most left. Don't be confused by the appearently wrong
|
|
direction the slider moves the balance to right and left.</p>
|
|
<table class='table table-striped'>
|
|
<tbody>
|
|
<tr>
|
|
<td>Balance (range -16 | +16)</td>
|
|
<td class='text-end' id='balanceChangeTable_balanceValue'>" + String(balanceLevel) + "</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
<br>
|
|
<hr>
|
|
<label for='balanceRange' class='form-label'>Balance (-16 to 16)</label>
|
|
<input type='range' class='form-range' value='" + String(balanceLevel+16) + "' min='0' max='32' id='balanceRange'
|
|
onchange='value = document.getElementById(\"balanceRange\").value; http = new XMLHttpRequest();http.open(\"GET\", \"/api/v1/balance/\" + value);http.send(); document.getElementById(\"balanceChangeTable_balanceValue\").innerHTML = value-16;'>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!--EQUALIZER Collapsible -->
|
|
<div class='accordion-item'>
|
|
<h2 class='accordion-header'>
|
|
<button class='accordion-button collapsed' type='button' data-bs-toggle='collapse' data-bs-target='#accordion-collapse-eq'
|
|
aria-expanded='true' aria-controls='accordion-collapse-eq'>
|
|
Equalizer
|
|
<span class='badge text-bg-info'>/api/v1/eq/</span>
|
|
</button>
|
|
</h2>
|
|
<div id='accordion-collapse-eq' class='accordion-collapse collapse' data-bs-parent='#accordion'>
|
|
<div class='accordion-body'>
|
|
<table class='table table-striped'>
|
|
<tbody>
|
|
<tr>
|
|
<td>Equalizer Low (-40dB to 6dB)</td>
|
|
<td class='text-end' id='eqChangeTable_eqLowValue'>" + String(eqLow) + "dB</td>
|
|
</tr>
|
|
<tr>
|
|
<td>Equalizer Mid (-40dB to 6dB)</td>
|
|
<td class='text-end' id='eqChangeTable_eqMidValue'>" + String(eqMid) + "dB</td>
|
|
</tr>
|
|
<tr>
|
|
<td>Equalizer High (-40dB to 6dB)</td>
|
|
<td class='text-end' id='eqChangeTable_eqHighValue'>" + String(eqHigh) + "dB</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
<br>
|
|
<hr>
|
|
<label for='eqLowRange' class='form-label'>Equalizer for lows (-40dB to 6dB)</label>
|
|
<input type='range' class='form-range' value='" + String(eqLow+40) + "' min='0' max='46' id='eqLowRange'
|
|
onchange='value = document.getElementById(\"eqLowRange\").value; http = new XMLHttpRequest();http.open(\"GET\", \"/api/v1/eq/low/\" + value);http.send(); document.getElementById(\"eqChangeTable_eqLowValue\").innerHTML = value-40 + \"dB\";'>
|
|
<br>
|
|
<hr>
|
|
<br><label for='eqMidRange' class='form-label'>Equalizer for mids (-40dB to 6dB)</label>
|
|
<input type='range' class='form-range' value='" + String(eqMid+40) + "' min='0' max='46' id='eqMidRange'
|
|
onchange='value = document.getElementById(\"eqMidRange\").value; http = new XMLHttpRequest();http.open(\"GET\", \"/api/v1/eq/mid/\" + value);http.send(); document.getElementById(\"eqChangeTable_eqMidValue\").innerHTML = value-40 + \"dB\";'>
|
|
<br>
|
|
<hr>
|
|
<br><label for='eqHighRange' class='form-label'>Equalizer for highs (-40dB to 6dB)</label>
|
|
<input type='range' class='form-range' value='" + String(eqHigh+40) + "' min='0' max='46' id='eqHighRange'
|
|
onchange='value = document.getElementById(\"eqHighRange\").value; http = new XMLHttpRequest();http.open(\"GET\", \"/api/v1/eq/high/\" + value);http.send(); document.getElementById(\"eqChangeTable_eqHighValue\").innerHTML = value-40 + \"dB\";'>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!--FRIENDLY NAME Collapsible -->
|
|
<div class='accordion-item'>
|
|
<h2 class='accordion-header'>
|
|
<button class='accordion-button collapsed' type='button' data-bs-toggle='collapse'
|
|
data-bs-target='#accordion-collapse-frname' aria-expanded='true' aria-controls='accordion-collapse-frname'>
|
|
Friendly name
|
|
<span class='badge text-bg-warning'>/api/v1/system/name</span>
|
|
<!--explicitly no trailing / as there are the two endpoints /name and /name/change -->
|
|
</button>
|
|
</h2>
|
|
<div id='accordion-collapse-frname' class='accordion-collapse collapse' data-bs-parent='#accordion'>
|
|
<div class='accordion-body'>
|
|
<div class='input-group mb-3'>
|
|
<span class='input-group-text' id='frnameChange_oldName'>" + configuration.getString(PREFERENCES_KEY_FRIENDLY_NAME) + "</span>
|
|
<span class='input-group-text'> -> </span>
|
|
<input type='text' class='form-control' placeholder='New friendly name' aria-label='New friendly name'
|
|
aria-describedby='frnameChange_BTN_submitName' id='frnameChange_newNameInput'>
|
|
<button class='btn btn-success' type='button' id='frnameChange_BTN_submitName' onclick=
|
|
\"newNameInput
|
|
= document.getElementById('frnameChange_newNameInput'); if(newNameInput.value != ''){ http = new
|
|
XMLHttpRequest();http.open('GET',
|
|
'/api/v1/system/name/change?friendly_name='+newNameInput.value);http.send();document.getElementById('frnameChange_oldName').innerHTML=newNameInput.value;document.getElementById('title_frname').innerHTML=newNameInput.value;newNameInput.value='';
|
|
}\">
|
|
Change</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- WiFi CONFIGURATION Collapsible -->
|
|
<div class='accordion-item'>
|
|
<h2 class='accordion-header'>
|
|
<button class='accordion-button collapsed' type='button' data-bs-toggle='collapse' data-bs-target='#accordion-collapse-wifi'
|
|
aria-expanded='true' aria-controls='accordion-collapse-wifi'>
|
|
WiFi configuration
|
|
<span class='badge text-bg-warning'>/api/v1/system/wifi/</span>
|
|
</button>
|
|
</h2>
|
|
<div id='accordion-collapse-wifi' class='accordion-collapse collapse' data-bs-parent='#accordion'>
|
|
<div class='accordion-body'>
|
|
<p>Demo for changing the wifi SSID and PSK (= Pre-Shared Key; well-known as 'Password' or 'Key'). There's no need to
|
|
give both - if you just wanna change the password - do it!</p>
|
|
<p>After doing this, you have to go down to the restart collapsible. There, restart the NetSpeaker and it should connect
|
|
to the configured WiFi.</p>
|
|
<p>If anything is wrong (either SSID or PSK wrong/typo, or the wifi's not available), the NetSpeaker opens an HotSpot
|
|
with the SSID <code>" + apSSID + "</code> and the PSK <code>" + apPSK + "</code>! </p>
|
|
<div class='input-group mb-3'>
|
|
<span class='input-group-text'>SSID</span>
|
|
<input type='text' class='form-control' placeholder='Type in a new SSID here (optional)'
|
|
aria-label='Type in a new SSID here (optional)' aria-describedby='wifiChange_submitBtn'
|
|
id='wifiChange_ssidInput'>
|
|
<span class='input-group-text'>PSK</span>
|
|
<input type='password' class='form-control' placeholder='Type in a new PSK here (optional)'
|
|
aria-label='Type in a new PSK here (optional)' aria-describedby='wifiChange_submitBtn'
|
|
id='wifiChange_pskInput'>
|
|
<button class='btn btn-success' type='button' id='wifiChange_submitBtn' onclick=
|
|
"newSSID
|
|
= document.getElementById('wifiChange_ssidInput').value;
|
|
newPSK = document.getElementById('wifiChange_pskInput').value;
|
|
http = new XMLHttpRequest();http.open('GET',
|
|
'/api/v1/system/wifi/change?ssid='+newSSID+'&psk='+newPSK);http.send();">
|
|
Change
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!--SAVE AND RESTORE Collapsible -->
|
|
<div class='accordion-item'>
|
|
<h2 class='accordion-header'>
|
|
<button class='accordion-button collapsed' type='button' data-bs-toggle='collapse'
|
|
data-bs-target='#accordion-collapse-restore' aria-expanded='true' aria-controls='accordion-collapse-restore'>
|
|
Save & Restore
|
|
<span class='badge text-bg-warning'>/api/v1/system/restore_state/</span>
|
|
<span class='badge text-bg-warning'>/api/v1/system/restore_playing/</span>
|
|
</button>
|
|
</h2>
|
|
<div id='accordion-collapse-restore' class='accordion-collapse collapse' data-bs-parent='#accordion'>
|
|
<div class='accordion-body'>
|
|
<p>Restoring the old/last state can be toggeled on or off over the API. Doing so can have many reasons, one could be
|
|
that you wanted to build a device always playing some startup sound, or so.</p>
|
|
<div class='form-check form-switch'>
|
|
<input onclick="this.value ? console.log('Toggeled on') : console.log('Toggeled off');" class='form-check-input'
|
|
type='checkbox' role='switch' id='restoreStateSwitch' " +
|
|
configuration.getBool(PREFERENCES_KEY_RESTORE_OLD_STATE,
|
|
true) ? "checked" : String() + " >
|
|
<label class='form-check-label' for='restoreStateSwitch'>Restore last saved state</label>
|
|
</div>
|
|
<div class='form-check form-switch'>
|
|
<input onclick="this.value ? console.log('Toggeled on') : console.log('Toggeled off');" class='form-check-input'
|
|
type='checkbox' role='switch' id='restorePlayingSwitch' " +
|
|
configuration.getBool(PREFERENCES_KEY_RESTORE_PLAYING,
|
|
true) ? "checked" : String() + " >
|
|
<label class='form-check-label' for='restorePlayingSwitch'>Also restore last playing state</label>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!--RESTART Collapsible -->
|
|
<div class='accordion-item'>
|
|
<h2 class='accordion-header'>
|
|
<button class='accordion-button collapsed' type='button' data-bs-toggle='collapse'
|
|
data-bs-target='#accordion-collapse-restart' aria-expanded='true' aria-controls='accordion-collapse-restart'>
|
|
Restart
|
|
<span class='badge text-bg-danger'>/api/v1/system/restart</span>
|
|
</button>
|
|
</h2>
|
|
<div id='accordion-collapse-restart' class='accordion-collapse collapse' data-bs-parent='#accordion'>
|
|
<div class='accordion-body'>
|
|
<button type='button' class='btn btn-danger' onclick="function wait(ms){var start=new Date().getTime();var
|
|
end=start;while(end<start+ms){end
|
|
= new Date().getTime();}}http = new XMLHttpRequest();http.open('GET',
|
|
'/api/v1/system/restart');http.send();wait(3000);window.location.reload();">
|
|
<svg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='currentColor' class='bi bi-bootstrap-reboot'
|
|
viewBox='0 0 16 16'>
|
|
<path d='M1.161 8a6.84 6.84 0 1 0 6.842-6.84.58.58 0 1 1 0-1.16 8 8 0 1 1-6.556 3.412l-.663-.577a.58.58 0 0 1 .227-.997l2.52-.69a.58.58 0 0 1 .728.633l-.332 2.592a.58.58 0 0 1-.956.364l-.643-.56A6.812 6.812 0 0 0 1.16 8z'></path>
|
|
<path d='M6.641 11.671V8.843h1.57l1.498 2.828h1.314L9.377 8.665c.897-.3 1.427-1.106 1.427-2.1 0-1.37-.943-2.246-2.456-2.246H5.5v7.352zm0-3.75V5.277h1.57c.881 0 1.416.499 1.416 1.32 0 .84-.504 1.324-1.386 1.324h-1.6z'></path>
|
|
</svg>
|
|
Restart
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
|
|
<!-- Modal -->
|
|
<div class="modal fade" id="selectIPAddressModal" data-bs-backdrop="static" data-bs-keyboard="false" tabindex="-1"
|
|
aria-labelledby="selectIPAddressModalLabel" aria-hidden="true">
|
|
<div class="modal-dialog">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h1 class="modal-title fs-5" id="selectIPAddressModalLabel">NetSpeaker configuration</h1>
|
|
</div>
|
|
<div class="text-start modal-body">
|
|
<p>Please give the IP adress of you NetSpeaker here, so that this site can access its API.</p>
|
|
<div class="mb-3">
|
|
<div class="input-group">
|
|
<span class="input-group-text">http://</span>
|
|
<input type="text" class="form-control" id="ipAdressInput">
|
|
</div>
|
|
</div>
|
|
<div id="configWorkingAlertPlaceholder"></div>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button type="button" class="btn btn-primary" id="ipAdressModalApplyBtn">Apply & Test</button>
|
|
<button type="button" class="btn btn-success" id="ipAdressModalOkBtn">Ok</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<script src='https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js'
|
|
integrity='sha384-C6RzsynM9kWDrMNeT87bh95OGNyZPhcTNXj1NW7RuBCsyN/o0jlpcV8Qyq46cDfL' crossorigin='anonymous'></script>
|
|
<script type="text/javascript">
|
|
function isValidIP(ipaddress) {
|
|
if (/^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/.test(ipaddress)) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
function appendAlert(alertPlaceholder, message, type) {
|
|
const wrapper = document.createElement('div')
|
|
wrapper.innerHTML = [
|
|
`<div class="alert alert-${type} alert-dismissible" role="alert">`,
|
|
` <div>${message}</div>`,
|
|
' <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>',
|
|
'</div>'
|
|
].join('')
|
|
|
|
alertPlaceholder.append(wrapper)
|
|
}
|
|
|
|
if (document.cookie == "" || !isValidIP(document.cookie)) {
|
|
var selectIPModal = new bootstrap.Modal(document.getElementById('selectIPAddressModal'), {});
|
|
selectIPModal.show();
|
|
|
|
const alertPlaceholder = document.getElementById('configWorkingAlertPlaceholder');
|
|
|
|
function configButtonEventListener(hideModalOnSuccess) {
|
|
var ipAddressEntered = document.getElementById('ipAdressInput').value;
|
|
if(isValidIP(ipAddressEntered)) {
|
|
var request = new XMLHttpRequest();
|
|
request.onreadystatechange = function() {
|
|
if (this.readyState == 4 && this.status == 200) {
|
|
appendAlert(alertPlaceholder, 'Working Configuration!', 'success');
|
|
document.cookie = ipAddressEntered;
|
|
document.title = "NetSpeaker Demo (on " + ipAddressEntered + ")";
|
|
document.getElementById("title_onIP").innerHTML = "on " + document.cookie;
|
|
setTimeout(function(){
|
|
if(hideModalOnSuccess) selectIPModal.hide();
|
|
}, 500);
|
|
} else if (this.readyState != 4) {
|
|
// just do nothing, wait!
|
|
} else {
|
|
appendAlert(alertPlaceholder, 'Configuration not working', 'danger');
|
|
}
|
|
};
|
|
request.open("GET", "http://" + ipAddressEntered + "/api/v1/system/version", true);
|
|
request.send()
|
|
} else {
|
|
appendAlert(alertPlaceholder, 'Configuration not working', 'danger');
|
|
}
|
|
}
|
|
|
|
const ipAdressModalApplyBtn = document.getElementById('ipAdressModalApplyBtn');
|
|
ipAdressModalApplyBtn.addEventListener('click', () => { configButtonEventListener(false); });
|
|
|
|
const ipAdressModalOkBtn = document.getElementById('ipAdressModalOkBtn');
|
|
ipAdressModalOkBtn.addEventListener('click', () => { configButtonEventListener(true); });
|
|
} else if (isValidIP(document.cookie)) {
|
|
document.title = "NetSpeaker Demo (on " + document.cookie + ")";
|
|
document.getElementById("title_onIP").innerHTML = "on " + document.cookie;
|
|
}
|
|
</script>
|
|
</body>
|
|
</html> |