All checks were successful
Build Plugin / build (push) Successful in 2m21s
651 lines
32 KiB
HTML
651 lines
32 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<title>JellyLMS</title>
|
|
<style>
|
|
.sync-group {
|
|
border: 2px solid #00a4dc;
|
|
border-radius: 8px;
|
|
padding: 12px;
|
|
margin-bottom: 12px;
|
|
background: rgba(0, 164, 220, 0.05);
|
|
}
|
|
.sync-group-header {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
margin-bottom: 8px;
|
|
}
|
|
.sync-group-players {
|
|
color: #ccc;
|
|
}
|
|
.player-sync-row {
|
|
display: flex;
|
|
align-items: center;
|
|
padding: 8px 0;
|
|
border-bottom: 1px solid #333;
|
|
}
|
|
.player-sync-row:last-child {
|
|
border-bottom: none;
|
|
}
|
|
.player-sync-checkbox {
|
|
margin-right: 12px;
|
|
}
|
|
.player-sync-name {
|
|
flex: 1;
|
|
font-weight: 500;
|
|
}
|
|
.player-sync-status {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 6px;
|
|
color: #888;
|
|
font-size: 0.9em;
|
|
}
|
|
.status-dot {
|
|
width: 8px;
|
|
height: 8px;
|
|
border-radius: 50%;
|
|
}
|
|
.status-dot.on { background: #52b54b; }
|
|
.status-dot.standby { background: #f9a825; }
|
|
.status-dot.off { background: #f44336; }
|
|
.sync-actions {
|
|
margin-top: 15px;
|
|
display: flex;
|
|
gap: 10px;
|
|
align-items: center;
|
|
}
|
|
#syncStatus {
|
|
margin-left: 10px;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div id="JellyLmsConfigPage" data-role="page" class="page type-interior pluginConfigurationPage" data-require="emby-input,emby-button,emby-select,emby-checkbox">
|
|
<div data-role="content">
|
|
<div class="content-primary">
|
|
<h2>JellyLMS Configuration</h2>
|
|
<p>Configure the connection between Jellyfin and Logitech Media Server (LMS) for multi-room audio playback.</p>
|
|
|
|
<form id="JellyLmsConfigForm">
|
|
<div class="verticalSection">
|
|
<h3>LMS Server Settings</h3>
|
|
|
|
<div class="inputContainer">
|
|
<label class="inputLabel inputLabelUnfocused" for="LmsServerUrl">LMS Server URL</label>
|
|
<input id="LmsServerUrl" name="LmsServerUrl" type="url" is="emby-input" />
|
|
<div class="fieldDescription">The URL of your LMS server (e.g., http://192.168.1.100:9000)</div>
|
|
</div>
|
|
|
|
<div class="inputContainer">
|
|
<label class="inputLabel inputLabelUnfocused" for="LmsUsername">LMS Username (optional)</label>
|
|
<input id="LmsUsername" name="LmsUsername" type="text" is="emby-input" />
|
|
<div class="fieldDescription">Username if LMS authentication is enabled</div>
|
|
</div>
|
|
|
|
<div class="inputContainer">
|
|
<label class="inputLabel inputLabelUnfocused" for="LmsPassword">LMS Password (optional)</label>
|
|
<input id="LmsPassword" name="LmsPassword" type="password" is="emby-input" />
|
|
<div class="fieldDescription">Password if LMS authentication is enabled</div>
|
|
</div>
|
|
|
|
<div>
|
|
<button is="emby-button" type="button" id="btnTestConnection" class="raised button-alt block emby-button">
|
|
<span>Test Connection</span>
|
|
</button>
|
|
<div id="connectionStatus" style="margin-top: 10px;"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="verticalSection">
|
|
<h3>Jellyfin Server Settings</h3>
|
|
|
|
<div class="inputContainer">
|
|
<label class="inputLabel inputLabelUnfocused" for="JellyfinServerUrl">Jellyfin Server URL</label>
|
|
<input id="JellyfinServerUrl" name="JellyfinServerUrl" type="url" is="emby-input" />
|
|
<div class="fieldDescription">The URL that LMS will use to stream audio from Jellyfin. This must be accessible from the LMS server (e.g., http://192.168.1.4:8096).</div>
|
|
</div>
|
|
|
|
<div class="inputContainer">
|
|
<label class="inputLabel inputLabelUnfocused" for="JellyfinApiKey">Jellyfin API Key</label>
|
|
<input id="JellyfinApiKey" name="JellyfinApiKey" type="password" is="emby-input" />
|
|
<div class="fieldDescription">API key for LMS to authenticate with Jellyfin. Create one in Dashboard > API Keys.</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="verticalSection">
|
|
<h3>Playback Settings</h3>
|
|
|
|
<div class="inputContainer">
|
|
<label class="inputLabel inputLabelUnfocused" for="ConnectionTimeoutSeconds">Connection Timeout (seconds)</label>
|
|
<input id="ConnectionTimeoutSeconds" name="ConnectionTimeoutSeconds" type="number" is="emby-input" min="5" max="60" />
|
|
<div class="fieldDescription">Timeout for LMS API requests</div>
|
|
</div>
|
|
|
|
<div class="checkboxContainer checkboxContainer-withDescription">
|
|
<label class="emby-checkbox-label">
|
|
<input id="EnableAutoSync" name="EnableAutoSync" type="checkbox" is="emby-checkbox" />
|
|
<span>Enable Auto-Sync</span>
|
|
</label>
|
|
<div class="fieldDescription checkboxFieldDescription">Automatically sync players when playing to multiple devices</div>
|
|
</div>
|
|
|
|
<div class="inputContainer" style="margin-top: 15px;">
|
|
<label class="inputLabel inputLabelUnfocused" for="DefaultPlayerMac">Default Player</label>
|
|
<select is="emby-select" id="DefaultPlayerMac" name="DefaultPlayerMac" class="emby-select-withcolor emby-select">
|
|
<option value="">None</option>
|
|
</select>
|
|
<div class="fieldDescription">Default player to use when none is specified</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="verticalSection">
|
|
<h3>Direct File Access (Optional)</h3>
|
|
<p class="fieldDescription">If LMS and Jellyfin share the same storage (e.g., NAS), enable direct file access for native seeking support. This provides smooth seeking without audio restart.</p>
|
|
|
|
<div class="checkboxContainer checkboxContainer-withDescription">
|
|
<label class="emby-checkbox-label">
|
|
<input id="UseDirectFilePath" name="UseDirectFilePath" type="checkbox" is="emby-checkbox" />
|
|
<span>Enable Direct File Access</span>
|
|
</label>
|
|
<div class="fieldDescription checkboxFieldDescription">When enabled, LMS will access files directly instead of streaming via HTTP</div>
|
|
</div>
|
|
|
|
<div id="directPathSettings" style="margin-top: 15px;">
|
|
<h4 style="margin-bottom: 10px;">Path Mappings</h4>
|
|
<p class="fieldDescription">Map Jellyfin paths to LMS paths. Add multiple mappings if your music and podcasts are in different locations.</p>
|
|
|
|
<div style="margin-bottom: 15px;">
|
|
<button is="emby-button" type="button" id="btnDiscoverPaths" class="raised button-alt emby-button">
|
|
<span>Discover Jellyfin Paths</span>
|
|
</button>
|
|
<span id="discoverStatus" style="margin-left: 10px;"></span>
|
|
</div>
|
|
|
|
<div id="discoveredPaths" style="display: none; margin-bottom: 15px; padding: 10px; background: rgba(0,0,0,0.2); border-radius: 4px;">
|
|
<strong>Detected Jellyfin paths:</strong>
|
|
<ul id="detectedPrefixList" style="margin: 5px 0; padding-left: 20px;"></ul>
|
|
<div class="fieldDescription">Click a path to use it as the Jellyfin path in a new mapping</div>
|
|
</div>
|
|
|
|
<div id="pathMappingsList">
|
|
<!-- Dynamic path mappings will be added here -->
|
|
</div>
|
|
|
|
<div style="margin-top: 10px;">
|
|
<button is="emby-button" type="button" id="btnAddMapping" class="raised button-alt emby-button">
|
|
<span>+ Add Mapping</span>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="verticalSection">
|
|
<h3>Player Sync</h3>
|
|
<p class="fieldDescription">Select players to sync together for multi-room audio. Synced players play in perfect sync.</p>
|
|
|
|
<div id="currentSyncGroups">
|
|
<!-- Existing sync groups shown here -->
|
|
</div>
|
|
|
|
<div id="playerSyncList">
|
|
<p>Loading players...</p>
|
|
</div>
|
|
|
|
<div class="sync-actions">
|
|
<button is="emby-button" type="button" id="btnSyncSelected" class="raised button-submit emby-button" disabled>
|
|
<span>Sync Selected</span>
|
|
</button>
|
|
<button is="emby-button" type="button" id="btnRefreshPlayers" class="raised button-alt emby-button">
|
|
<span>Refresh</span>
|
|
</button>
|
|
<span id="syncStatus"></span>
|
|
</div>
|
|
</div>
|
|
|
|
<div>
|
|
<button is="emby-button" type="submit" class="raised button-submit block emby-button">
|
|
<span>Save</span>
|
|
</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
<script type="text/javascript">
|
|
var JellyLmsConfig = {
|
|
pluginUniqueId: 'a5b8c9d0-1e2f-3a4b-5c6d-7e8f9a0b1c2d',
|
|
players: [],
|
|
syncGroups: []
|
|
};
|
|
|
|
function getStatusClass(player) {
|
|
if (!player.IsConnected) return 'off';
|
|
return player.IsPoweredOn ? 'on' : 'standby';
|
|
}
|
|
|
|
function getStatusText(player) {
|
|
if (!player.IsConnected) return 'Disconnected';
|
|
return player.IsPoweredOn ? 'On' : 'Standby';
|
|
}
|
|
|
|
function loadPlayers() {
|
|
var defaultSelect = document.querySelector('#DefaultPlayerMac');
|
|
var currentDefault = defaultSelect.value;
|
|
|
|
ApiClient.ajax({
|
|
url: ApiClient.getUrl('JellyLms/Players', { refresh: true }),
|
|
type: 'GET',
|
|
dataType: 'json'
|
|
}).then(function(players) {
|
|
JellyLmsConfig.players = players || [];
|
|
defaultSelect.innerHTML = '<option value="">None</option>';
|
|
|
|
players.forEach(function(player) {
|
|
var option = document.createElement('option');
|
|
option.value = player.MacAddress;
|
|
option.text = player.Name;
|
|
if (player.MacAddress === currentDefault) {
|
|
option.selected = true;
|
|
}
|
|
defaultSelect.appendChild(option);
|
|
});
|
|
|
|
renderPlayerList();
|
|
}).catch(function(err) {
|
|
document.querySelector('#playerSyncList').innerHTML = '<p style="color: red;">Error loading players. Check LMS connection.</p>';
|
|
console.error('Error loading players:', err);
|
|
});
|
|
}
|
|
|
|
function loadSyncGroups() {
|
|
ApiClient.ajax({
|
|
url: ApiClient.getUrl('JellyLms/SyncGroups'),
|
|
type: 'GET',
|
|
dataType: 'json'
|
|
}).then(function(groups) {
|
|
JellyLmsConfig.syncGroups = groups || [];
|
|
renderSyncGroups();
|
|
renderPlayerList();
|
|
}).catch(function(err) {
|
|
console.error('Error loading sync groups:', err);
|
|
});
|
|
}
|
|
|
|
function renderSyncGroups() {
|
|
var container = document.querySelector('#currentSyncGroups');
|
|
var groups = JellyLmsConfig.syncGroups;
|
|
|
|
if (!groups || groups.length === 0) {
|
|
container.innerHTML = '';
|
|
return;
|
|
}
|
|
|
|
var html = '';
|
|
groups.forEach(function(group) {
|
|
var playerNames = [];
|
|
var masterPlayer = JellyLmsConfig.players.find(function(p) { return p.MacAddress === group.MasterMac; });
|
|
if (masterPlayer) playerNames.push(masterPlayer.Name);
|
|
|
|
group.SlaveMacs.forEach(function(mac) {
|
|
var player = JellyLmsConfig.players.find(function(p) { return p.MacAddress === mac; });
|
|
if (player) playerNames.push(player.Name);
|
|
});
|
|
|
|
html += '<div class="sync-group">';
|
|
html += '<div class="sync-group-header">';
|
|
html += '<span class="sync-group-players">' + playerNames.join(' + ') + '</span>';
|
|
html += '<button is="emby-button" type="button" class="raised button-alt emby-button btnUnsyncGroup" data-master="' + group.MasterMac + '">';
|
|
html += '<span>Unsync</span>';
|
|
html += '</button>';
|
|
html += '</div>';
|
|
html += '</div>';
|
|
});
|
|
|
|
container.innerHTML = html;
|
|
|
|
container.querySelectorAll('.btnUnsyncGroup').forEach(function(btn) {
|
|
btn.addEventListener('click', function() {
|
|
unsyncGroup(this.getAttribute('data-master'));
|
|
});
|
|
});
|
|
}
|
|
|
|
function renderPlayerList() {
|
|
var container = document.querySelector('#playerSyncList');
|
|
var players = JellyLmsConfig.players;
|
|
|
|
if (!players || players.length === 0) {
|
|
container.innerHTML = '<p>No players found. Make sure LMS is running.</p>';
|
|
updateSyncButton();
|
|
return;
|
|
}
|
|
|
|
// Get MACs of already synced players
|
|
var syncedMacs = new Set();
|
|
JellyLmsConfig.syncGroups.forEach(function(group) {
|
|
syncedMacs.add(group.MasterMac);
|
|
group.SlaveMacs.forEach(function(mac) { syncedMacs.add(mac); });
|
|
});
|
|
|
|
// Only show unsynced players
|
|
var unsyncedPlayers = players.filter(function(p) {
|
|
return !syncedMacs.has(p.MacAddress);
|
|
});
|
|
|
|
if (unsyncedPlayers.length === 0) {
|
|
container.innerHTML = '<p>All players are synced.</p>';
|
|
updateSyncButton();
|
|
return;
|
|
}
|
|
|
|
var html = '';
|
|
unsyncedPlayers.forEach(function(player) {
|
|
var statusClass = getStatusClass(player);
|
|
html += '<div class="player-sync-row">';
|
|
html += '<input type="checkbox" class="player-sync-checkbox" data-mac="' + player.MacAddress + '" id="sync-' + player.MacAddress + '">';
|
|
html += '<label class="player-sync-name" for="sync-' + player.MacAddress + '">' + player.Name + '</label>';
|
|
html += '<div class="player-sync-status">';
|
|
html += '<span class="status-dot ' + statusClass + '"></span>';
|
|
html += '<span>' + getStatusText(player) + '</span>';
|
|
html += '</div>';
|
|
html += '</div>';
|
|
});
|
|
|
|
container.innerHTML = html;
|
|
|
|
// Add change listeners
|
|
container.querySelectorAll('.player-sync-checkbox').forEach(function(cb) {
|
|
cb.addEventListener('change', updateSyncButton);
|
|
});
|
|
|
|
updateSyncButton();
|
|
}
|
|
|
|
function getSelectedMacs() {
|
|
var checkboxes = document.querySelectorAll('.player-sync-checkbox:checked');
|
|
var macs = [];
|
|
checkboxes.forEach(function(cb) {
|
|
macs.push(cb.getAttribute('data-mac'));
|
|
});
|
|
return macs;
|
|
}
|
|
|
|
function updateSyncButton() {
|
|
var btn = document.querySelector('#btnSyncSelected');
|
|
var selected = getSelectedMacs();
|
|
btn.disabled = selected.length < 2;
|
|
}
|
|
|
|
function syncSelected() {
|
|
var macs = getSelectedMacs();
|
|
if (macs.length < 2) return;
|
|
|
|
var statusDiv = document.querySelector('#syncStatus');
|
|
statusDiv.innerHTML = '<span style="color: orange;">Syncing...</span>';
|
|
|
|
// First MAC becomes master (arbitrary, user doesn't need to know)
|
|
var masterMac = macs[0];
|
|
var slaveMacs = macs.slice(1);
|
|
|
|
ApiClient.ajax({
|
|
url: ApiClient.getUrl('JellyLms/SyncGroups'),
|
|
type: 'POST',
|
|
contentType: 'application/json',
|
|
data: JSON.stringify({
|
|
MasterMac: masterMac,
|
|
SlaveMacs: slaveMacs
|
|
})
|
|
}).then(function() {
|
|
statusDiv.innerHTML = '<span style="color: green;">Synced!</span>';
|
|
setTimeout(function() {
|
|
statusDiv.innerHTML = '';
|
|
loadSyncGroups();
|
|
}, 1500);
|
|
}).catch(function(err) {
|
|
statusDiv.innerHTML = '<span style="color: red;">Failed to sync.</span>';
|
|
console.error('Error syncing:', err);
|
|
});
|
|
}
|
|
|
|
function unsyncGroup(masterMac) {
|
|
var statusDiv = document.querySelector('#syncStatus');
|
|
statusDiv.innerHTML = '<span style="color: orange;">Unsyncing...</span>';
|
|
|
|
ApiClient.ajax({
|
|
url: ApiClient.getUrl('JellyLms/SyncGroups/' + encodeURIComponent(masterMac)),
|
|
type: 'DELETE'
|
|
}).then(function() {
|
|
statusDiv.innerHTML = '';
|
|
loadSyncGroups();
|
|
}).catch(function(err) {
|
|
statusDiv.innerHTML = '<span style="color: red;">Failed to unsync.</span>';
|
|
console.error('Error unsyncing:', err);
|
|
});
|
|
}
|
|
|
|
function testConnection() {
|
|
var statusDiv = document.querySelector('#connectionStatus');
|
|
statusDiv.innerHTML = '<span style="color: orange;">Testing connection...</span>';
|
|
|
|
ApiClient.ajax({
|
|
url: ApiClient.getUrl('JellyLms/TestConnection'),
|
|
type: 'POST',
|
|
dataType: 'json'
|
|
}).then(function(result) {
|
|
if (result.IsConnected) {
|
|
statusDiv.innerHTML = '<span style="color: green;">Connected! Found ' + result.PlayerCount + ' player(s).</span>';
|
|
loadPlayers();
|
|
loadSyncGroups();
|
|
} else {
|
|
statusDiv.innerHTML = '<span style="color: red;">Connection failed: ' + (result.LastError || 'Unknown error') + '</span>';
|
|
}
|
|
}).catch(function(err) {
|
|
statusDiv.innerHTML = '<span style="color: red;">Connection failed. Check the server URL.</span>';
|
|
console.error('Connection test failed:', err);
|
|
});
|
|
}
|
|
|
|
// Path Mapping Functions
|
|
function renderPathMappings(mappings) {
|
|
var container = document.querySelector('#pathMappingsList');
|
|
if (!mappings || mappings.length === 0) {
|
|
container.innerHTML = '<p class="fieldDescription">No path mappings configured. Click "Discover Jellyfin Paths" to get started.</p>';
|
|
return;
|
|
}
|
|
|
|
var html = '';
|
|
mappings.forEach(function(mapping, index) {
|
|
html += '<div class="path-mapping-row" style="display: flex; gap: 10px; align-items: flex-end; margin-bottom: 10px; padding: 10px; background: rgba(0,0,0,0.1); border-radius: 4px;">';
|
|
html += '<div style="flex: 1;">';
|
|
html += '<label class="inputLabel inputLabelUnfocused">Jellyfin Path</label>';
|
|
html += '<input type="text" is="emby-input" class="mapping-jellyfin-path" data-index="' + index + '" value="' + (mapping.JellyfinPath || '') + '" placeholder="/media/music" />';
|
|
html += '</div>';
|
|
html += '<div style="flex: 1;">';
|
|
html += '<label class="inputLabel inputLabelUnfocused">LMS Path</label>';
|
|
html += '<input type="text" is="emby-input" class="mapping-lms-path" data-index="' + index + '" value="' + (mapping.LmsPath || '') + '" placeholder="/mnt/music" />';
|
|
html += '</div>';
|
|
html += '<button is="emby-button" type="button" class="raised button-alt emby-button btnRemoveMapping" data-index="' + index + '" style="margin-bottom: 0;">';
|
|
html += '<span>Remove</span>';
|
|
html += '</button>';
|
|
html += '</div>';
|
|
});
|
|
container.innerHTML = html;
|
|
|
|
// Add remove handlers
|
|
container.querySelectorAll('.btnRemoveMapping').forEach(function(btn) {
|
|
btn.addEventListener('click', function() {
|
|
var idx = parseInt(this.getAttribute('data-index'));
|
|
JellyLmsConfig.pathMappings.splice(idx, 1);
|
|
renderPathMappings(JellyLmsConfig.pathMappings);
|
|
});
|
|
});
|
|
|
|
// Update stored mappings when inputs change
|
|
container.querySelectorAll('.mapping-jellyfin-path, .mapping-lms-path').forEach(function(input) {
|
|
input.addEventListener('change', function() {
|
|
var idx = parseInt(this.getAttribute('data-index'));
|
|
if (this.classList.contains('mapping-jellyfin-path')) {
|
|
JellyLmsConfig.pathMappings[idx].JellyfinPath = this.value;
|
|
} else {
|
|
JellyLmsConfig.pathMappings[idx].LmsPath = this.value;
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
function addPathMapping(jellyfinPath, lmsPath) {
|
|
JellyLmsConfig.pathMappings = JellyLmsConfig.pathMappings || [];
|
|
JellyLmsConfig.pathMappings.push({
|
|
JellyfinPath: jellyfinPath || '',
|
|
LmsPath: lmsPath || ''
|
|
});
|
|
renderPathMappings(JellyLmsConfig.pathMappings);
|
|
}
|
|
|
|
function discoverPaths() {
|
|
var statusDiv = document.querySelector('#discoverStatus');
|
|
statusDiv.innerHTML = '<span style="color: orange;">Discovering...</span>';
|
|
|
|
ApiClient.ajax({
|
|
url: ApiClient.getUrl('JellyLms/DiscoverPaths'),
|
|
type: 'GET',
|
|
dataType: 'json'
|
|
}).then(function(result) {
|
|
statusDiv.innerHTML = '';
|
|
var discoveredDiv = document.querySelector('#discoveredPaths');
|
|
var prefixList = document.querySelector('#detectedPrefixList');
|
|
|
|
if (result.DetectedPrefixes && result.DetectedPrefixes.length > 0) {
|
|
discoveredDiv.style.display = 'block';
|
|
var html = '';
|
|
result.DetectedPrefixes.forEach(function(prefix) {
|
|
html += '<li><a href="#" class="detected-prefix-link" data-path="' + prefix + '" style="color: #00a4dc;">' + prefix + '</a></li>';
|
|
});
|
|
prefixList.innerHTML = html;
|
|
|
|
// Add click handlers to use detected paths
|
|
prefixList.querySelectorAll('.detected-prefix-link').forEach(function(link) {
|
|
link.addEventListener('click', function(e) {
|
|
e.preventDefault();
|
|
addPathMapping(this.getAttribute('data-path'), '');
|
|
});
|
|
});
|
|
} else {
|
|
discoveredDiv.style.display = 'block';
|
|
prefixList.innerHTML = '<li>No audio files found in library</li>';
|
|
}
|
|
}).catch(function(err) {
|
|
statusDiv.innerHTML = '<span style="color: red;">Discovery failed</span>';
|
|
console.error('Path discovery failed:', err);
|
|
});
|
|
}
|
|
|
|
function getPathMappingsFromUI() {
|
|
var mappings = [];
|
|
document.querySelectorAll('.path-mapping-row').forEach(function(row) {
|
|
var jellyfinPath = row.querySelector('.mapping-jellyfin-path').value;
|
|
var lmsPath = row.querySelector('.mapping-lms-path').value;
|
|
if (jellyfinPath || lmsPath) {
|
|
mappings.push({ JellyfinPath: jellyfinPath, LmsPath: lmsPath });
|
|
}
|
|
});
|
|
return mappings;
|
|
}
|
|
|
|
document.querySelector('#JellyLmsConfigPage')
|
|
.addEventListener('pageshow', function() {
|
|
Dashboard.showLoadingMsg();
|
|
ApiClient.getPluginConfiguration(JellyLmsConfig.pluginUniqueId).then(function (config) {
|
|
document.querySelector('#LmsServerUrl').value = config.LmsServerUrl || 'http://localhost:9000';
|
|
document.querySelector('#LmsUsername').value = config.LmsUsername || '';
|
|
document.querySelector('#LmsPassword').value = config.LmsPassword || '';
|
|
document.querySelector('#JellyfinServerUrl').value = config.JellyfinServerUrl || 'http://localhost:8096';
|
|
document.querySelector('#JellyfinApiKey').value = config.JellyfinApiKey || '';
|
|
document.querySelector('#ConnectionTimeoutSeconds').value = config.ConnectionTimeoutSeconds || 10;
|
|
document.querySelector('#EnableAutoSync').checked = config.EnableAutoSync !== false;
|
|
document.querySelector('#DefaultPlayerMac').value = config.DefaultPlayerMac || '';
|
|
document.querySelector('#UseDirectFilePath').checked = config.UseDirectFilePath || false;
|
|
|
|
// Load path mappings (new list format, with fallback to legacy single mapping)
|
|
JellyLmsConfig.pathMappings = config.PathMappings || [];
|
|
// If no list mappings but legacy single mapping exists, show it
|
|
if (JellyLmsConfig.pathMappings.length === 0 && config.JellyfinMediaPath && config.LmsMediaPath) {
|
|
JellyLmsConfig.pathMappings = [{
|
|
JellyfinPath: config.JellyfinMediaPath,
|
|
LmsPath: config.LmsMediaPath
|
|
}];
|
|
}
|
|
renderPathMappings(JellyLmsConfig.pathMappings);
|
|
|
|
Dashboard.hideLoadingMsg();
|
|
|
|
loadPlayers();
|
|
loadSyncGroups();
|
|
});
|
|
});
|
|
|
|
document.querySelector('#btnTestConnection')
|
|
.addEventListener('click', function() {
|
|
ApiClient.getPluginConfiguration(JellyLmsConfig.pluginUniqueId).then(function (config) {
|
|
config.LmsServerUrl = document.querySelector('#LmsServerUrl').value;
|
|
config.LmsUsername = document.querySelector('#LmsUsername').value;
|
|
config.LmsPassword = document.querySelector('#LmsPassword').value;
|
|
ApiClient.updatePluginConfiguration(JellyLmsConfig.pluginUniqueId, config).then(function() {
|
|
testConnection();
|
|
});
|
|
});
|
|
});
|
|
|
|
document.querySelector('#btnRefreshPlayers')
|
|
.addEventListener('click', function() {
|
|
loadPlayers();
|
|
loadSyncGroups();
|
|
});
|
|
|
|
document.querySelector('#btnSyncSelected')
|
|
.addEventListener('click', function() {
|
|
syncSelected();
|
|
});
|
|
|
|
document.querySelector('#JellyLmsConfigForm')
|
|
.addEventListener('submit', function(e) {
|
|
Dashboard.showLoadingMsg();
|
|
ApiClient.getPluginConfiguration(JellyLmsConfig.pluginUniqueId).then(function (config) {
|
|
config.LmsServerUrl = document.querySelector('#LmsServerUrl').value;
|
|
config.LmsUsername = document.querySelector('#LmsUsername').value;
|
|
config.LmsPassword = document.querySelector('#LmsPassword').value;
|
|
config.JellyfinServerUrl = document.querySelector('#JellyfinServerUrl').value;
|
|
config.JellyfinApiKey = document.querySelector('#JellyfinApiKey').value;
|
|
config.ConnectionTimeoutSeconds = parseInt(document.querySelector('#ConnectionTimeoutSeconds').value) || 10;
|
|
config.EnableAutoSync = document.querySelector('#EnableAutoSync').checked;
|
|
config.DefaultPlayerMac = document.querySelector('#DefaultPlayerMac').value;
|
|
config.UseDirectFilePath = document.querySelector('#UseDirectFilePath').checked;
|
|
// Save path mappings (clear legacy single mapping when using list)
|
|
config.PathMappings = getPathMappingsFromUI();
|
|
config.JellyfinMediaPath = '';
|
|
config.LmsMediaPath = '';
|
|
ApiClient.updatePluginConfiguration(JellyLmsConfig.pluginUniqueId, config).then(function (result) {
|
|
Dashboard.processPluginConfigurationUpdateResult(result);
|
|
});
|
|
});
|
|
|
|
e.preventDefault();
|
|
return false;
|
|
});
|
|
|
|
document.querySelector('#btnDiscoverPaths')
|
|
.addEventListener('click', function() {
|
|
discoverPaths();
|
|
});
|
|
|
|
document.querySelector('#btnAddMapping')
|
|
.addEventListener('click', function() {
|
|
addPathMapping('', '');
|
|
});
|
|
</script>
|
|
</div>
|
|
</body>
|
|
</html>
|