+
+
+
+
{
+ selectedEncoder = { value: undefined, label: undefined };
+ selectedResolution = { value: undefined, label: undefined };
+ selectedFramerate = { value: undefined, label: undefined };
+ selectedInputMode = value;
+
+ // Auto-select the next level if there's only one option
+ if (value) {
+ autoSelectNextOption('inputMode');
+ }
+ }}>
+
+
+
+
+
+ {#if groupedPipelines}
+ {#each Object.entries(groupedPipelines) as [pipelineKey, _]}
+ {@const label = pipelineKey.toUpperCase().split(' ')[0]}
+
+ {/each}
+ {/if}
+
+
+
+ {#if selectedInputMode?.value && selectedInputMode.value.toLowerCase().includes('usb')}
+
+ {$_('settings.djiCameraMessage')}
+
+ {/if}
+
-
-
-
- (selectedEncoder = value)}>
-
-
-
-
-
- {#if selectedInputMode?.value && groupedPipelines?.[selectedInputMode.value]}
- {#each Object.keys(groupedPipelines[selectedInputMode.value]) as encoder}
-
- {/each}
- {/if}
-
-
-
-
+
+
+
+ {
+ selectedEncoder = value;
+ selectedResolution = { value: undefined, label: undefined };
+ selectedFramerate = { value: undefined, label: undefined };
+
+ // Auto-select the next level if there's only one option
+ if (value) {
+ autoSelectNextOption('encoder');
+ }
+ }}>
+
+
+
+
+
+ {#if selectedInputMode?.value && groupedPipelines?.[selectedInputMode.value]}
+ {#each Object.keys(groupedPipelines[selectedInputMode.value]) as encoder}
+
+ {/each}
+ {/if}
+
+
+
+
-
-
-
- {
- selectedFramerate = { value: undefined, label: undefined };
- selectedResolution = value;
- }}>
-
-
-
-
-
- {#if selectedEncoder?.value && selectedInputMode?.value && groupedPipelines?.[selectedInputMode.value]?.[selectedEncoder.value]}
- {@const resolutions = Object.keys(groupedPipelines[selectedInputMode.value][selectedEncoder.value])}
- {#each resolutions as resolution}
-
- {/each}
- {/if}
-
-
-
-
+
+
+
+ {
+ selectedResolution = value;
+ selectedFramerate = { value: undefined, label: undefined };
+
+ // Auto-select the next level if there's only one option
+ if (value) {
+ autoSelectNextOption('resolution');
+ }
+ }}>
+
+
+
+
+
+ {#if selectedEncoder?.value && selectedInputMode?.value && groupedPipelines?.[selectedInputMode.value]?.[selectedEncoder.value]}
+ {@const resolutions = Object.keys(
+ groupedPipelines[selectedInputMode.value][selectedEncoder.value],
+ )}
+ {@const sortedResolutions = [...resolutions].sort((a, b) => {
+ // Put "match device resolution" or similar special options first
+ if (a.toLowerCase().includes('match') || a.toLowerCase().includes('device')) return -1;
+ if (b.toLowerCase().includes('match') || b.toLowerCase().includes('device')) return 1;
+
+ // Extract numeric values (like "720" from "720p")
+ const numA = parseInt(a.match(/\d+/)?.[0] || '0', 10);
+ const numB = parseInt(b.match(/\d+/)?.[0] || '0', 10);
+
+ // Sort by numeric value
+ return numA - numB;
+ })}
+ {#each sortedResolutions as resolution}
+
+ {/each}
+ {/if}
+
+
+
+
-
-
-
-
(selectedFramerate = value)}>
-
-
-
-
-
- {#if selectedEncoder?.value && selectedInputMode?.value && selectedResolution?.value && groupedPipelines?.[selectedInputMode.value]?.[selectedEncoder.value][selectedResolution.value]}
- {@const framerates =
- groupedPipelines[selectedInputMode.value][selectedEncoder.value][selectedResolution.value]}
- {#each framerates as framerate}
-
- {/each}
- {/if}
-
-
-
-
-
-
{
- setTimeout(() => {
- selectedBitrate = value[0];
- updateMaxBitrate();
- });
- }} />
- {
- selectedBitrate = normalizeValue(selectedBitrate, 2000, 12000, 50);
- updateMaxBitrate();
- }}>
- {#if isStreaming}
- {$_('settings.changeBitrateNotice')}
+
+
+
+
(selectedFramerate = value)}>
+
+
+
+
+
+ {#if selectedEncoder?.value && selectedInputMode?.value && selectedResolution?.value && groupedPipelines?.[selectedInputMode.value]?.[selectedEncoder.value][selectedResolution.value]}
+ {@const framerates =
+ groupedPipelines[selectedInputMode.value][selectedEncoder.value][selectedResolution.value]}
+ {@const sortedFramerates = [...framerates].sort((a, b) => {
+ // Put "match device output" or similar special options first
+ const fpsA = a.extraction.fps;
+ const fpsB = b.extraction.fps;
+
+ if (typeof fpsA === 'string' && fpsA.toLowerCase().includes('match')) return -1;
+ if (typeof fpsB === 'string' && fpsB.toLowerCase().includes('match')) return 1;
+
+ // Convert to numbers for numeric comparison
+ const numA = typeof fpsA === 'number' ? fpsA : parseFloat(String(fpsA)) || 0;
+ const numB = typeof fpsB === 'number' ? fpsB : parseFloat(String(fpsB)) || 0;
+
+ // Sort by numeric value
+ return numA - numB;
+ })}
+ {#each sortedFramerates as framerate}
+
+ {/each}
+ {/if}
+
+
+
+
+ {#if formErrors.pipeline}
+
{formErrors.pipeline}
{/if}
+
+
+
+
{
+ setTimeout(() => {
+ selectedBitrate = value[0];
+ updateMaxBitrate();
+ });
+ }} />
+ {
+ selectedBitrate = normalizeValue(selectedBitrate, 2000, 12000, 50);
+ updateMaxBitrate();
+ }}>
+ {#if isStreaming}
+ {$_('settings.changeBitrateNotice')}
+ {/if}
+
@@ -407,73 +574,86 @@ const startStreamingWithCurrentConfig = () => {
- {#if audioCodecs && unparsedPipelines && selectedPipeline && unparsedPipelines[selectedPipeline].asrc}
-
- (selectedAudioSource = value)}>
-
-
-
-
-
- {#if audioSources}
- {#each audioSources as audioSource}
-
- {/each}
- {/if}
-
-
-
- {/if}
-
- {#if audioCodecs && unparsedPipelines && selectedPipeline && unparsedPipelines[selectedPipeline].acodec}
-
- (selectedAudioCodec = value)}>
-
-
-
-
-
- {#each Object.entries(audioCodecs) as [codec, label]}
-
- {/each}
-
-
-
-
-
- (selectedAudioDelay = value[0])}
- disabled={isStreaming}
- max={2000}
- min={-2000}
- step={5}>
- {
- selectedAudioDelay = normalizeValue(selectedAudioDelay, 2000, 12000, 50);
- }}>
-
- {/if}
-
- {#if audioCodecs && unparsedPipelines && selectedPipeline && !unparsedPipelines[selectedPipeline].acodec && !unparsedPipelines[selectedPipeline].asrc}
- {$_('settings.noAudioSettingSupport')}
- {/if}
-
- {#if audioCodecs && unparsedPipelines && !selectedPipeline}
- {$_('settings.audioSettingsMessage')}
- {/if}
+
+ {#if audioCodecs && unparsedPipelines && selectedPipeline && unparsedPipelines[selectedPipeline].asrc}
+
+
+ (selectedAudioSource = value)}>
+
+
+
+
+
+ {#if audioSources}
+ {#each audioSources as audioSource}
+
+ {/each}
+ {/if}
+
+
+
+
+ {/if}
+
+ {#if audioCodecs && unparsedPipelines && selectedPipeline && unparsedPipelines[selectedPipeline].acodec}
+
+
+
(selectedAudioCodec = value)}>
+
+
+
+
+
+ {#each Object.entries(audioCodecs) as [codec, label]}
+
+ {/each}
+
+
+
+
+
+ (selectedAudioDelay = value[0])}
+ disabled={isStreaming}
+ max={2000}
+ min={-2000}
+ step={5}>
+ {
+ selectedAudioDelay = normalizeValue(selectedAudioDelay, 2000, 12000, 50);
+ }}>
+
+
+ {/if}
+
+ {#if audioCodecs && unparsedPipelines && selectedPipeline && !unparsedPipelines[selectedPipeline].acodec && !unparsedPipelines[selectedPipeline].asrc}
+
+
{$_('settings.noAudioSettingSupport')}
+
+ {/if}
+
+ {#if audioCodecs && unparsedPipelines && !selectedPipeline}
+
+
{$_('settings.audioSettingsMessage')}
+
+ {/if}
+
@@ -483,73 +663,95 @@ const startStreamingWithCurrentConfig = () => {
-
- value && (selectedRelayServer = value)}>
-
-
-
-
-
- {$_('settings.manualConfiguration')}
- {#if relayMessage?.servers}
- {#each Object.entries(relayMessage?.servers) as [server, serverInfo]}
-
- {/each}
- {/if}
-
-
-
- {#if selectedRelayServer?.value === '-1' || selectedRelayServer?.value === undefined}
-
-
- {/if}
- {#if selectedRelayServer?.value !== '-1' && selectedRelayServer?.value !== undefined}
-
- value && (selectedRelayAccount = value)}>
-
-
-
-
-
- {$_('settings.manualConfiguration')}
- {#if relayMessage?.servers}
- {#each Object.entries(relayMessage?.accounts) as [account, accountInfo]}
-
- {/each}
- {/if}
-
-
-
- {/if}
- {#if selectedRelayAccount?.value === '-1' || selectedRelayAccount?.value === undefined}
-
-
-
-
- {/if}
- {#if srtLatency !== undefined}
-
- (srtLatency = value[0])}>
- {
- srtLatency = normalizeValue(srtLatency, 2000, 12000, 50);
- }}>
- {/if}
+
+
+
+ value && (selectedRelayServer = value)}>
+
+
+
+
+
+ {$_('settings.manualConfiguration')}
+ {#if relayMessage?.servers}
+ {#each Object.entries(relayMessage?.servers) as [server, serverInfo]}
+
+ {/each}
+ {/if}
+
+
+
+
+
+ {#if selectedRelayServer?.value === '-1' || selectedRelayServer?.value === undefined}
+
+
+
+
+ {/if}
+
+ {#if selectedRelayServer?.value !== '-1' && selectedRelayServer?.value !== undefined}
+
+
+ value && (selectedRelayAccount = value)}>
+
+
+
+
+
+ {$_('settings.manualConfiguration')}
+ {#if relayMessage?.servers}
+ {#each Object.entries(relayMessage?.accounts) as [account, accountInfo]}
+
+ {/each}
+ {/if}
+
+
+
+
+ {/if}
+
+ {#if selectedRelayAccount?.value === '-1' || selectedRelayAccount?.value === undefined}
+
+
+
+
+
+
+
+
+ {/if}
+
+ {#if srtLatency !== undefined}
+
+
+ (srtLatency = value[0])}
+ disabled={isStreaming}>
+ {
+ srtLatency = normalizeValue(srtLatency, 2000, 12000, 50);
+ }}>
+
+ {/if}
+
diff --git a/src/main/shared/ModemConfigurator.svelte b/src/main/shared/ModemConfigurator.svelte
index 62721bb1..ce023655 100644
--- a/src/main/shared/ModemConfigurator.svelte
+++ b/src/main/shared/ModemConfigurator.svelte
@@ -1,11 +1,11 @@
-
-
- {
- if (val) modemProperties.selectedNetwork = val;
- }}>
-
-
-
-
-
- {#each modem.network_type.supported as networkType}
- {renameSupportedModemNetwork(networkType)}
- {/each}
-
-
-
-
-
-
- {#if modemProperties.roaming}
-
- {:else}
-
- {/if}
-
-
-
- {$_('network.modem.enableRoaming')}
-
-
-
-
-
-
+
-
-
-
- {#if modemProperties.autoconfig}
-
- {:else}
-
+ {#if errors.selectedNetwork}
+ {errors.selectedNetwork}
{/if}
-
-
-
-
- {$_('network.modem.autoapn')}
-
-
- {#if !modemProperties.autoconfig}
-
-
-
-
-
-
-
+
+
+
(formData.roaming = val)}>
+ {#if formData.roaming}
+
+ {:else}
+
+ {/if}
+
+
+
+ {$_('network.modem.enableRoaming')}
+
+
-
-
-
+
+ {#if formData.roaming}
+
+
+
+
+ {
+ if (val) formData.network = { ...val }; // Create a new object to ensure reactivity
+ }}>
+
+
+ {Number(formData.network.value) === -1
+ ? $_('network.modem.automaticRoamingNetwork')
+ : formData.network.label}
+
+
+
+
+
+ {#if modem.available_networks}
+ {#each Object.entries(modem.available_networks) as [key, availableNetwork]}
+ {#if availableNetwork.availability === 'available'}
+
+ {/if}
+ {/each}
+ {/if}
+
+
+
+
+
+
+
+ {/if}
+
+
+
(formData.autoconfig = val)}>
+ {#if formData.autoconfig}
+
+ {:else}
+
+ {/if}
+
+
+
+ {$_('network.modem.autoapn')}
+
+
- {/if}
-
+ {#if !formData.autoconfig}
+
+
+
(errors.apn = undefined)} />
+ {#if errors.apn}
+
{errors.apn}
+ {/if}
+
+
+
+
+
+
+
+
+
+
+
+ {/if}
+
+
+
+
+
+